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   */
51  public class GitCheckOutCommand extends AbstractCheckOutCommand implements GitCommand {
52      private final Map<String, String> environmentVariables;
53  
54      public GitCheckOutCommand(Map<String, String> environmentVariables) {
55          super();
56          this.environmentVariables = environmentVariables;
57      }
58  
59      /**
60       * For git, the given repository is a remote one.
61       * We have to clone it first if the working directory does not contain a git repo yet,
62       * otherwise we have to git-pull it.
63       * <p>
64       * TODO We currently assume a '.git' directory, so this does not work for --bare repos
65       * {@inheritDoc}
66       */
67      @Override
68      public ScmResult executeCommand(ScmProviderRepository repo, ScmFileSet fileSet, CommandParameters parameters)
69              throws ScmException {
70          ScmVersion version = parameters.getScmVersion(CommandParameter.SCM_VERSION, null);
71          boolean binary = parameters.getBoolean(CommandParameter.BINARY, false);
72          boolean shallow = parameters.getBoolean(CommandParameter.SHALLOW, false);
73  
74          GitScmProviderRepository repository = (GitScmProviderRepository) repo;
75  
76          if (GitScmProviderRepository.PROTOCOL_FILE.equals(
77                          repository.getFetchInfo().getProtocol())
78                  && repository
79                                  .getFetchInfo()
80                                  .getPath()
81                                  .indexOf(fileSet.getBasedir().getPath())
82                          >= 0) {
83              throw new ScmException("remote repository must not be the working directory");
84          }
85  
86          int exitCode;
87  
88          CommandLineUtils.StringStreamConsumer stdout = new CommandLineUtils.StringStreamConsumer();
89          CommandLineUtils.StringStreamConsumer stderr = new CommandLineUtils.StringStreamConsumer();
90  
91          String lastCommandLine = "git-nothing-to-do";
92  
93          if (!fileSet.getBasedir().exists() || !(new File(fileSet.getBasedir(), ".git").exists())) {
94              if (fileSet.getBasedir().exists()) {
95                  // git refuses to clone otherwise
96                  fileSet.getBasedir().delete();
97              }
98  
99              // no git repo seems to exist, let's clone the original repo
100             Commandline clClone = createCloneCommand(repository, fileSet.getBasedir(), version, binary, shallow);
101 
102             exitCode = GitCommandLineUtils.execute(clClone, stdout, stderr);
103             if (exitCode != 0) {
104                 return new CheckOutScmResult(
105                         clClone.toString(), "The git-clone command failed.", stderr.getOutput(), false);
106             }
107             lastCommandLine = clClone.toString();
108         }
109 
110         GitRemoteInfoCommand gitRemoteInfoCommand = new GitRemoteInfoCommand(environmentVariables);
111 
112         RemoteInfoScmResult result = gitRemoteInfoCommand.executeRemoteInfoCommand(repository, null, null);
113 
114         if (fileSet.getBasedir().exists()
115                 && new File(fileSet.getBasedir(), ".git").exists()
116                 && result.getBranches().size() > 0) {
117             // git repo exists, so we must git-pull the changes
118             Commandline clPull = createPullCommand(repository, fileSet.getBasedir(), version);
119 
120             exitCode = GitCommandLineUtils.execute(clPull, stdout, stderr);
121             if (exitCode != 0) {
122                 return new CheckOutScmResult(
123                         clPull.toString(), "The git-pull command failed.", stderr.getOutput(), false);
124             }
125             lastCommandLine = clPull.toString();
126 
127             // and now lets do the git-checkout itself
128             Commandline clCheckout = createCommandLine(repository, fileSet.getBasedir(), version);
129 
130             exitCode = GitCommandLineUtils.execute(clCheckout, stdout, stderr);
131             if (exitCode != 0) {
132                 return new CheckOutScmResult(
133                         clCheckout.toString(), "The git-checkout command failed.", stderr.getOutput(), false);
134             }
135             lastCommandLine = clCheckout.toString();
136         }
137 
138         // and now search for the files
139         GitListConsumer listConsumer = new GitListConsumer(fileSet.getBasedir(), ScmFileStatus.CHECKED_IN);
140 
141         Commandline clList = GitListCommand.createCommandLine(repository, fileSet.getBasedir());
142 
143         exitCode = GitCommandLineUtils.execute(clList, listConsumer, stderr);
144         if (exitCode != 0) {
145             return new CheckOutScmResult(
146                     clList.toString(), "The git-ls-files command failed.", stderr.getOutput(), false);
147         }
148 
149         return new CheckOutScmResult(lastCommandLine, listConsumer.getListedFiles());
150     }
151 
152     // ----------------------------------------------------------------------
153     //
154     // ----------------------------------------------------------------------
155 
156     public static Commandline createCommandLine(
157             GitScmProviderRepository repository, File workingDirectory, ScmVersion version) {
158         Commandline cl = GitCommandLineUtils.getBaseGitCommandLine(workingDirectory, "checkout");
159 
160         if (version != null && StringUtils.isNotEmpty(version.getName())) {
161             cl.createArg().setValue(version.getName());
162         }
163 
164         return cl;
165     }
166 
167     /**
168      * create a git-clone repository command
169      */
170     private Commandline createCloneCommand(
171             GitScmProviderRepository repository,
172             File workingDirectory,
173             ScmVersion version,
174             boolean binary,
175             boolean shallow) {
176         Commandline cl = GitCommandLineUtils.getBaseGitCommandLine(
177                 workingDirectory.getParentFile(), "clone", repository, environmentVariables);
178 
179         forceBinary(cl, binary);
180 
181         if (shallow) {
182             cl.createArg().setValue("--depth");
183 
184             cl.createArg().setValue("1");
185         }
186 
187         if (version != null && (version instanceof ScmBranch)) {
188 
189             cl.createArg().setValue("--branch");
190 
191             cl.createArg().setValue(version.getName());
192         }
193 
194         cl.createArg().setValue(repository.getFetchUrl());
195 
196         cl.createArg().setValue(workingDirectory.getName());
197 
198         return cl;
199     }
200 
201     private void forceBinary(Commandline cl, boolean binary) {
202         if (binary) {
203             cl.createArg().setValue("-c");
204             cl.createArg().setValue("core.autocrlf=false");
205         }
206     }
207 
208     /**
209      * create a git-pull repository command
210      */
211     private Commandline createPullCommand(
212             GitScmProviderRepository repository, File workingDirectory, ScmVersion version) {
213         Commandline cl;
214 
215         if (version != null && StringUtils.isNotEmpty(version.getName())) {
216             if (version instanceof ScmTag) {
217                 // A tag will not be pulled but we only fetch all the commits from the upstream repo
218                 // This is done because checking out a tag might not happen on the current branch
219                 // but create a 'detached HEAD'.
220                 // In fact, a tag in git may be in multiple branches. This occurs if
221                 // you create a branch after the tag has been created
222                 cl = GitCommandLineUtils.getBaseGitCommandLine(
223                         workingDirectory, "fetch", repository, environmentVariables);
224 
225                 cl.createArg().setValue(repository.getFetchUrl());
226             } else {
227                 cl = GitCommandLineUtils.getBaseGitCommandLine(
228                         workingDirectory, "pull", repository, environmentVariables);
229 
230                 cl.createArg().setValue(repository.getFetchUrl());
231 
232                 cl.createArg().setValue(version.getName() + ":" + version.getName());
233             }
234         } else {
235             cl = GitCommandLineUtils.getBaseGitCommandLine(workingDirectory, "pull", repository, environmentVariables);
236 
237             cl.createArg().setValue(repository.getFetchUrl());
238             cl.createArg().setValue("master");
239         }
240         return cl;
241     }
242 
243     /**
244      * The overriden {@link #executeCommand(ScmProviderRepository, ScmFileSet, CommandParameters)} in this class will
245      * not call this method!
246      * <p>
247      * {@inheritDoc}
248      */
249     protected CheckOutScmResult executeCheckOutCommand(
250             ScmProviderRepository repo, ScmFileSet fileSet, ScmVersion version, boolean recursive, boolean shallow)
251             throws ScmException {
252         throw new UnsupportedOperationException("Should not get here");
253     }
254 }