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