View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.scm.provider.git.gitexe.command.checkout;
20  
21  import java.io.File;
22  import java.util.Map;
23  
24  import org.apache.commons.lang3.StringUtils;
25  import org.apache.maven.scm.CommandParameter;
26  import org.apache.maven.scm.CommandParameters;
27  import org.apache.maven.scm.ScmBranch;
28  import org.apache.maven.scm.ScmException;
29  import org.apache.maven.scm.ScmFileSet;
30  import org.apache.maven.scm.ScmFileStatus;
31  import org.apache.maven.scm.ScmResult;
32  import org.apache.maven.scm.ScmTag;
33  import org.apache.maven.scm.ScmVersion;
34  import org.apache.maven.scm.command.checkout.AbstractCheckOutCommand;
35  import org.apache.maven.scm.command.checkout.CheckOutScmResult;
36  import org.apache.maven.scm.command.remoteinfo.RemoteInfoScmResult;
37  import org.apache.maven.scm.provider.ScmProviderRepository;
38  import org.apache.maven.scm.provider.git.command.GitCommand;
39  import org.apache.maven.scm.provider.git.gitexe.command.GitCommandLineUtils;
40  import org.apache.maven.scm.provider.git.gitexe.command.list.GitListCommand;
41  import org.apache.maven.scm.provider.git.gitexe.command.list.GitListConsumer;
42  import org.apache.maven.scm.provider.git.gitexe.command.remoteinfo.GitRemoteInfoCommand;
43  import org.apache.maven.scm.provider.git.repository.GitScmProviderRepository;
44  import org.codehaus.plexus.util.cli.CommandLineUtils;
45  import org.codehaus.plexus.util.cli.Commandline;
46  
47  /**
48   * @author <a href="mailto:struberg@yahoo.de">Mark Struberg</a>
49   */
50  public class GitCheckOutCommand extends AbstractCheckOutCommand implements GitCommand {
51      private final Map<String, String> environmentVariables;
52  
53      public GitCheckOutCommand(Map<String, String> environmentVariables) {
54          super();
55          this.environmentVariables = environmentVariables;
56      }
57  
58      /**
59       * For git, the given repository is a remote one.
60       * We have to clone it first if the working directory does not contain a git repo yet,
61       * otherwise we have to git-pull it.
62       * <p>
63       * TODO We currently assume a '.git' directory, so this does not work for --bare repos
64       * {@inheritDoc}
65       */
66      @Override
67      public ScmResult executeCommand(ScmProviderRepository repo, ScmFileSet fileSet, CommandParameters parameters)
68              throws ScmException {
69          ScmVersion version = parameters.getScmVersion(CommandParameter.SCM_VERSION, null);
70          boolean binary = parameters.getBoolean(CommandParameter.BINARY, false);
71          boolean shallow = parameters.getBoolean(CommandParameter.SHALLOW, false);
72  
73          GitScmProviderRepository repository = (GitScmProviderRepository) repo;
74  
75          if (GitScmProviderRepository.PROTOCOL_FILE.equals(
76                          repository.getFetchInfo().getProtocol())
77                  && repository
78                                  .getFetchInfo()
79                                  .getPath()
80                                  .indexOf(fileSet.getBasedir().getPath())
81                          >= 0) {
82              throw new ScmException("remote repository must not be the working directory");
83          }
84  
85          int exitCode;
86  
87          CommandLineUtils.StringStreamConsumer stdout = new CommandLineUtils.StringStreamConsumer();
88          CommandLineUtils.StringStreamConsumer stderr = new CommandLineUtils.StringStreamConsumer();
89  
90          String lastCommandLine = "git-nothing-to-do";
91  
92          if (!fileSet.getBasedir().exists() || !(new File(fileSet.getBasedir(), ".git").exists())) {
93              if (fileSet.getBasedir().exists()) {
94                  // git refuses to clone otherwise
95                  fileSet.getBasedir().delete();
96              }
97  
98              // no git repo seems to exist, let's clone the original repo
99              Commandline gitClone = createCloneCommand(repository, fileSet.getBasedir(), version, binary, shallow);
100 
101             exitCode = GitCommandLineUtils.execute(gitClone, stdout, stderr);
102             if (exitCode != 0) {
103                 return new CheckOutScmResult(
104                         gitClone.toString(), "The git clone command failed.", stderr.getOutput(), false);
105             }
106             lastCommandLine = gitClone.toString();
107         }
108 
109         GitRemoteInfoCommand gitRemoteInfoCommand = new GitRemoteInfoCommand(environmentVariables);
110 
111         RemoteInfoScmResult result = gitRemoteInfoCommand.executeRemoteInfoCommand(repository, null, null);
112 
113         if (fileSet.getBasedir().exists()
114                 && new File(fileSet.getBasedir(), ".git").exists()
115                 && result.getBranches().size() > 0) {
116             // git repo exists, so we must git-pull the changes
117             Commandline gitPull = createPullCommand(repository, fileSet.getBasedir(), version);
118 
119             exitCode = GitCommandLineUtils.execute(gitPull, stdout, stderr);
120             if (exitCode != 0) {
121                 return new CheckOutScmResult(
122                         gitPull.toString(), "The git pull command failed.", stderr.getOutput(), false);
123             }
124 
125             // and now let's do the git-checkout itself
126             Commandline gitCheckout = createCommandLine(repository, fileSet.getBasedir(), version);
127 
128             exitCode = GitCommandLineUtils.execute(gitCheckout, stdout, stderr);
129             if (exitCode != 0) {
130                 return new CheckOutScmResult(
131                         gitCheckout.toString(), "The git checkout command failed.", stderr.getOutput(), false);
132             }
133             lastCommandLine = gitCheckout.toString();
134         }
135 
136         // and now search for the files
137         GitListConsumer listConsumer = new GitListConsumer(fileSet.getBasedir(), ScmFileStatus.CHECKED_IN);
138 
139         Commandline gitList = GitListCommand.createCommandLine(repository, fileSet.getBasedir());
140 
141         exitCode = GitCommandLineUtils.execute(gitList, listConsumer, stderr);
142         if (exitCode != 0) {
143             return new CheckOutScmResult(
144                     gitList.toString(), "The git ls-files command failed.", stderr.getOutput(), false);
145         }
146 
147         return new CheckOutScmResult(lastCommandLine, listConsumer.getListedFiles());
148     }
149 
150     // ----------------------------------------------------------------------
151     //
152     // ----------------------------------------------------------------------
153 
154     public static Commandline createCommandLine(
155             GitScmProviderRepository repository, File workingDirectory, ScmVersion version) {
156         Commandline gitCheckout = GitCommandLineUtils.getBaseGitCommandLine(workingDirectory, "checkout");
157 
158         if (version != null && StringUtils.isNotEmpty(version.getName())) {
159             gitCheckout.createArg().setValue(version.getName());
160         }
161 
162         return gitCheckout;
163     }
164 
165     /**
166      * Create a git-clone repository command.
167      */
168     private Commandline createCloneCommand(
169             GitScmProviderRepository repository,
170             File workingDirectory,
171             ScmVersion version,
172             boolean binary,
173             boolean shallow) {
174         Commandline gitClone = GitCommandLineUtils.getBaseGitCommandLine(
175                 workingDirectory.getParentFile(), "clone", repository, environmentVariables);
176 
177         forceBinary(gitClone, binary);
178 
179         if (shallow) {
180             gitClone.createArg().setValue("--depth");
181 
182             gitClone.createArg().setValue("1");
183         }
184 
185         if (version instanceof ScmBranch) {
186 
187             gitClone.createArg().setValue("--branch");
188 
189             gitClone.createArg().setValue(version.getName());
190         }
191 
192         gitClone.createArg().setValue(repository.getFetchUrl());
193 
194         gitClone.createArg().setValue(workingDirectory.getName());
195 
196         return gitClone;
197     }
198 
199     private void forceBinary(Commandline commandLine, boolean binary) {
200         if (binary) {
201             commandLine.createArg().setValue("-c");
202             commandLine.createArg().setValue("core.autocrlf=false");
203         }
204     }
205 
206     /**
207      * Create a git fetch or git pull repository command.
208      */
209     private Commandline createPullCommand(
210             GitScmProviderRepository repository, File workingDirectory, ScmVersion version) {
211 
212         if (version != null && StringUtils.isNotEmpty(version.getName())) {
213             if (version instanceof ScmTag) {
214                 // A tag will not be pulled but we only fetch all the commits from the upstream repo
215                 // This is done because checking out a tag might not happen on the current branch
216                 // but create a 'detached HEAD'.
217                 // In fact, a tag in git may be in multiple branches. This occurs if
218                 // you create a branch after the tag has been created
219                 Commandline gitFetch = GitCommandLineUtils.getBaseGitCommandLine(
220                         workingDirectory, "fetch", repository, environmentVariables);
221 
222                 gitFetch.createArg().setValue(repository.getFetchUrl());
223                 return gitFetch;
224             } else {
225                 Commandline gitPull = GitCommandLineUtils.getBaseGitCommandLine(
226                         workingDirectory, "pull", repository, environmentVariables);
227                 gitPull.createArg().setValue(repository.getFetchUrl());
228                 gitPull.createArg().setValue(version.getName() + ":" + version.getName());
229                 return gitPull;
230             }
231         } else {
232             Commandline gitPull = GitCommandLineUtils.getBaseGitCommandLine(
233                     workingDirectory, "pull", repository, environmentVariables);
234             gitPull.createArg().setValue(repository.getFetchUrl());
235             gitPull.createArg().setValue("master");
236             return gitPull;
237         }
238     }
239 
240     /**
241      * The overridden {@link #executeCommand(ScmProviderRepository, ScmFileSet, CommandParameters)} in this class will
242      * not call this method!
243      * <p>
244      * {@inheritDoc}
245      */
246     @Override
247     protected CheckOutScmResult executeCheckOutCommand(
248             ScmProviderRepository repo, ScmFileSet fileSet, ScmVersion version, boolean recursive, boolean shallow)
249             throws ScmException {
250         throw new UnsupportedOperationException("Should not get here");
251     }
252 }