View Javadoc
1   package org.apache.maven.scm.provider.git.jgit.command;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   * http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import org.apache.maven.scm.ScmFile;
23  import org.apache.maven.scm.ScmFileSet;
24  import org.apache.maven.scm.ScmFileStatus;
25  import org.apache.maven.scm.log.ScmLogger;
26  import org.apache.maven.scm.provider.git.repository.GitScmProviderRepository;
27  import org.apache.maven.scm.util.FilenameUtils;
28  import org.codehaus.plexus.util.StringUtils;
29  import org.eclipse.jgit.api.AddCommand;
30  import org.eclipse.jgit.api.Git;
31  import org.eclipse.jgit.api.Status;
32  import org.eclipse.jgit.api.errors.GitAPIException;
33  import org.eclipse.jgit.api.errors.InvalidRemoteException;
34  import org.eclipse.jgit.api.errors.NoFilepatternException;
35  import org.eclipse.jgit.api.errors.TransportException;
36  import org.eclipse.jgit.diff.DiffEntry;
37  import org.eclipse.jgit.diff.DiffEntry.ChangeType;
38  import org.eclipse.jgit.diff.DiffFormatter;
39  import org.eclipse.jgit.diff.RawTextComparator;
40  import org.eclipse.jgit.errors.CorruptObjectException;
41  import org.eclipse.jgit.errors.IncorrectObjectTypeException;
42  import org.eclipse.jgit.errors.MissingObjectException;
43  import org.eclipse.jgit.errors.StopWalkException;
44  import org.eclipse.jgit.lib.Constants;
45  import org.eclipse.jgit.lib.ObjectId;
46  import org.eclipse.jgit.lib.ProgressMonitor;
47  import org.eclipse.jgit.lib.Repository;
48  import org.eclipse.jgit.lib.StoredConfig;
49  import org.eclipse.jgit.lib.TextProgressMonitor;
50  import org.eclipse.jgit.revwalk.RevCommit;
51  import org.eclipse.jgit.revwalk.RevFlag;
52  import org.eclipse.jgit.revwalk.RevSort;
53  import org.eclipse.jgit.revwalk.RevWalk;
54  import org.eclipse.jgit.revwalk.filter.CommitTimeRevFilter;
55  import org.eclipse.jgit.revwalk.filter.RevFilter;
56  import org.eclipse.jgit.transport.CredentialsProvider;
57  import org.eclipse.jgit.transport.PushResult;
58  import org.eclipse.jgit.transport.RefSpec;
59  import org.eclipse.jgit.transport.RemoteRefUpdate;
60  import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
61  import org.eclipse.jgit.util.io.DisabledOutputStream;
62  
63  import java.io.File;
64  import java.io.IOException;
65  import java.net.URI;
66  import java.util.ArrayList;
67  import java.util.Collection;
68  import java.util.Date;
69  import java.util.HashSet;
70  import java.util.Iterator;
71  import java.util.List;
72  import java.util.Set;
73  
74  /**
75   * JGit utility functions.
76   *
77   * @author <a href="mailto:struberg@yahoo.de">Mark Struberg</a>
78   * @author Dominik Bartholdi (imod)
79   * @since 1.9
80   */
81  public class JGitUtils
82  {
83      
84      private JGitUtils()
85      {
86          // no op
87      }
88  
89      /**
90       * Closes the repository wrapped by the passed git object
91       * @param git 
92       */
93      public static void closeRepo( Git git )
94      {
95          if ( git != null && git.getRepository() != null )
96          {
97              git.getRepository().close();
98          }
99      }
100 
101     /**
102      * Construct a logging ProgressMonitor for all JGit operations.
103      *
104      * @param logger
105      * @return a ProgressMonitor for use
106      */
107     public static ProgressMonitor getMonitor( ScmLogger logger )
108     {
109         // X TODO write an own ProgressMonitor which logs to ScmLogger!
110         return new TextProgressMonitor();
111     }
112 
113     /**
114      * Prepares the in memory configuration of git to connect to the configured
115      * repository. It configures the following settings in memory: <br />
116      * <li>push url</li> <li>fetch url</li>
117      * <p/>
118      *
119      * @param logger     used to log some details
120      * @param git        the instance to configure (only in memory, not saved)
121      * @param repository the repo config to be used
122      * @return {@link CredentialsProvider} in case there are credentials
123      *         informations configured in the repository.
124      */
125     public static CredentialsProvider prepareSession( ScmLogger logger, Git git, GitScmProviderRepository repository )
126     {
127         StoredConfig config = git.getRepository().getConfig();
128         config.setString( "remote", "origin", "url", repository.getFetchUrl() );
129         config.setString( "remote", "origin", "pushURL", repository.getPushUrl() );
130 
131         // make sure we do not log any passwords to the output
132         String password =
133             StringUtils.isNotBlank( repository.getPassword() ) ? repository.getPassword().trim() : "no-pwd-defined";
134         logger.info( "fetch url: " + repository.getFetchUrl().replace( password, "******" ) );
135         logger.info( "push url: " + repository.getPushUrl().replace( password, "******" ) );
136         return getCredentials( repository );
137     }
138 
139     /**
140      * Creates a credentials provider from the information passed in the
141      * repository. Current implementation supports: <br />
142      * <li>UserName/Password</li>
143      * <p/>
144      *
145      * @param repository the config to get the details from
146      * @return <code>null</code> if there is not enough info to create a
147      *         provider with
148      */
149     public static CredentialsProvider getCredentials( GitScmProviderRepository repository )
150     {
151         if ( StringUtils.isNotBlank( repository.getUser() ) && StringUtils.isNotBlank( repository.getPassword() ) )
152         {
153             return new UsernamePasswordCredentialsProvider( repository.getUser().trim(),
154                                                             repository.getPassword().trim() );
155         }
156         return null;
157     }
158 
159     public static Iterable<PushResult> push( ScmLogger logger, Git git, GitScmProviderRepository repo, RefSpec refSpec )
160         throws GitAPIException, InvalidRemoteException, TransportException
161     {
162         CredentialsProvider credentials = JGitUtils.prepareSession( logger, git, repo );
163         Iterable<PushResult> pushResultList =
164             git.push().setCredentialsProvider( credentials ).setRefSpecs( refSpec ).call();
165         for ( PushResult pushResult : pushResultList )
166         {
167             Collection<RemoteRefUpdate> ru = pushResult.getRemoteUpdates();
168             for ( RemoteRefUpdate remoteRefUpdate : ru )
169             {
170                 logger.info( remoteRefUpdate.getStatus() + " - " + remoteRefUpdate.toString() );
171             }
172         }
173         return pushResultList;
174     }
175 
176     /**
177      * Does the Repository have any commits?
178      *
179      * @param repo
180      * @return false if there are no commits
181      */
182     public static boolean hasCommits( Repository repo )
183     {
184         if ( repo != null && repo.getDirectory().exists() )
185         {
186             return ( new File( repo.getDirectory(), "objects" ).list().length > 2 ) || (
187                 new File( repo.getDirectory(), "objects/pack" ).list().length > 0 );
188         }
189         return false;
190     }
191 
192     /**
193      * get a list of all files in the given commit
194      *
195      * @param repository the repo
196      * @param commit     the commit to get the files from
197      * @return a list of files included in the commit
198      * @throws MissingObjectException
199      * @throws IncorrectObjectTypeException
200      * @throws CorruptObjectException
201      * @throws IOException
202      */
203     public static List<ScmFile> getFilesInCommit( Repository repository, RevCommit commit )
204         throws MissingObjectException, IncorrectObjectTypeException, CorruptObjectException, IOException
205     {
206         List<ScmFile> list = new ArrayList<ScmFile>();
207         if ( JGitUtils.hasCommits( repository ) )
208         {
209             RevWalk rw = new RevWalk( repository );
210             RevCommit realParant = commit.getParentCount() > 0 ? commit.getParent( 0 ) : commit;
211             RevCommit parent = rw.parseCommit( realParant.getId() );
212             DiffFormatter df = new DiffFormatter( DisabledOutputStream.INSTANCE );
213             df.setRepository( repository );
214             df.setDiffComparator( RawTextComparator.DEFAULT );
215             df.setDetectRenames( true );
216             List<DiffEntry> diffs = df.scan( parent.getTree(), commit.getTree() );
217             for ( DiffEntry diff : diffs )
218             {
219                 list.add( new ScmFile( diff.getNewPath(), ScmFileStatus.CHECKED_IN ) );
220             }
221             rw.release();
222         }
223         return list;
224     }
225 
226     /**
227      * Translate a {@code FileStatus} in the matching {@code ScmFileStatus}.
228      *
229      * @param changeType
230      * @return the matching ScmFileStatus
231      */
232     public static ScmFileStatus getScmFileStatus( ChangeType changeType )
233     {
234         switch ( changeType )
235         {
236             case ADD:
237                 return ScmFileStatus.ADDED;
238             case MODIFY:
239                 return ScmFileStatus.MODIFIED;
240             case DELETE:
241                 return ScmFileStatus.DELETED;
242             case RENAME:
243                 return ScmFileStatus.RENAMED;
244             case COPY:
245                 return ScmFileStatus.COPIED;
246             default:
247                 return ScmFileStatus.UNKNOWN;
248         }
249     }
250 
251     /**
252      * Adds all files in the given fileSet to the repository.
253      *
254      * @param git     the repo to add the files to
255      * @param fileSet the set of files within the workspace, the files are added
256      *                relative to the basedir of this fileset
257      * @return a list of added files
258      * @throws GitAPIException
259      * @throws NoFilepatternException
260      */
261     public static List<ScmFile> addAllFiles( Git git, ScmFileSet fileSet )
262         throws GitAPIException, NoFilepatternException
263     {
264         URI baseUri = fileSet.getBasedir().toURI();
265         AddCommand add = git.add();
266         for ( File file : fileSet.getFileList() )
267         {
268             if ( !file.isAbsolute() )
269             {
270                 file = new File( fileSet.getBasedir().getPath(), file.getPath() );
271             }
272 
273             if ( file.exists() )
274             {
275                 String path = relativize( baseUri, file );
276                 add.addFilepattern( path );
277                 add.addFilepattern( file.getAbsolutePath() );
278             }
279         }
280         add.call();
281         
282         Status status = git.status().call();
283 
284         Set<String> allInIndex = new HashSet<String>();
285         allInIndex.addAll( status.getAdded() );
286         allInIndex.addAll( status.getChanged() );
287 
288         // System.out.println("All in index: "+allInIndex.size());
289 
290         List<ScmFile> addedFiles = new ArrayList<ScmFile>( allInIndex.size() );
291 
292         // rewrite all detected files to now have status 'checked_in'
293         for ( String entry : allInIndex )
294         {
295             ScmFile scmfile = new ScmFile( entry, ScmFileStatus.ADDED );
296 
297             // if a specific fileSet is given, we have to check if the file is
298             // really tracked
299             for ( Iterator<File> itfl = fileSet.getFileList().iterator(); itfl.hasNext(); )
300             {
301                 String path = FilenameUtils.normalizeFilename( relativize( baseUri, itfl.next() ) );
302                 if ( path.equals( FilenameUtils.normalizeFilename( scmfile.getPath() ) ) )
303                 {
304                     addedFiles.add( scmfile );
305                 }
306             }
307         }
308         return addedFiles;
309     }
310 
311     private static String relativize( URI baseUri, File f )
312     {
313         String path = f.getPath();
314         if ( f.isAbsolute() )
315         {
316             path = baseUri.relativize( new File( path ).toURI() ).getPath();
317         }
318         return path;
319     }
320 
321     /**
322      * Get a list of commits between two revisions.
323      *
324      * @param repo     the repository to work on
325      * @param sortings sorting
326      * @param fromRev  start revision
327      * @param toRev    if null, falls back to head
328      * @param fromDate from which date on
329      * @param toDate   until which date
330      * @param maxLines max number of lines
331      * @return a list of commits, might be empty, but never <code>null</code>
332      * @throws IOException
333      * @throws MissingObjectException
334      * @throws IncorrectObjectTypeException
335      */
336     public static List<RevCommit> getRevCommits( Repository repo, RevSort[] sortings, String fromRev, String toRev,
337                                                  final Date fromDate, final Date toDate, int maxLines )
338         throws IOException, MissingObjectException, IncorrectObjectTypeException
339     {
340 
341         List<RevCommit> revs = new ArrayList<RevCommit>();
342         RevWalk walk = new RevWalk( repo );
343 
344         ObjectId fromRevId = fromRev != null ? repo.resolve( fromRev ) : null;
345         ObjectId toRevId = toRev != null ? repo.resolve( toRev ) : null;
346 
347         if ( sortings == null || sortings.length == 0 )
348         {
349             sortings = new RevSort[]{ RevSort.TOPO, RevSort.COMMIT_TIME_DESC };
350         }
351 
352         for ( final RevSort s : sortings )
353         {
354             walk.sort( s, true );
355         }
356 
357         if ( fromDate != null && toDate != null )
358         {
359             //walk.setRevFilter( CommitTimeRevFilter.between( fromDate, toDate ) );
360             walk.setRevFilter( new RevFilter()
361             {
362                 @Override
363                 public boolean include( RevWalk walker, RevCommit cmit )
364                     throws StopWalkException, MissingObjectException, IncorrectObjectTypeException, IOException
365                 {
366                     int cmtTime = cmit.getCommitTime();
367 
368                     return ( cmtTime >= ( fromDate.getTime() / 1000 ) ) && ( cmtTime <= ( toDate.getTime() / 1000 ) );
369                 }
370 
371                 @Override
372                 public RevFilter clone()
373                 {
374                     return this;
375                 }
376             } );
377         }
378         else
379         {
380             if ( fromDate != null )
381             {
382                 walk.setRevFilter( CommitTimeRevFilter.after( fromDate ) );
383             }
384             if ( toDate != null )
385             {
386                 walk.setRevFilter( CommitTimeRevFilter.before( toDate ) );
387             }
388         }
389 
390         if ( fromRevId != null )
391         {
392             RevCommit c = walk.parseCommit( fromRevId );
393             c.add( RevFlag.UNINTERESTING );
394             RevCommit real = walk.parseCommit( c );
395             walk.markUninteresting( real );
396         }
397 
398         if ( toRevId != null )
399         {
400             RevCommit c = walk.parseCommit( toRevId );
401             c.remove( RevFlag.UNINTERESTING );
402             RevCommit real = walk.parseCommit( c );
403             walk.markStart( real );
404         }
405         else
406         {
407             final ObjectId head = repo.resolve( Constants.HEAD );
408             if ( head == null )
409             {
410                 throw new RuntimeException( "Cannot resolve " + Constants.HEAD );
411             }
412             RevCommit real = walk.parseCommit( head );
413             walk.markStart( real );
414         }
415 
416         int n = 0;
417         for ( final RevCommit c : walk )
418         {
419             n++;
420             if ( maxLines != -1 && n > maxLines )
421             {
422                 break;
423             }
424 
425             revs.add( c );
426         }
427         return revs;
428     }
429 
430 }