001package org.apache.maven.scm.provider.git.jgit.command; 002 003import org.apache.maven.scm.ScmException; 004import org.apache.maven.scm.ScmFile; 005import org.apache.maven.scm.ScmFileSet; 006import org.apache.maven.scm.ScmFileStatus; 007import org.apache.maven.scm.log.ScmLogger; 008import org.apache.maven.scm.provider.git.repository.GitScmProviderRepository; 009import org.apache.maven.scm.util.FilenameUtils; 010import org.codehaus.plexus.util.StringUtils; 011import org.eclipse.jgit.api.AddCommand; 012import org.eclipse.jgit.api.Git; 013import org.eclipse.jgit.api.Status; 014import org.eclipse.jgit.api.errors.GitAPIException; 015import org.eclipse.jgit.api.errors.InvalidRemoteException; 016import org.eclipse.jgit.api.errors.NoFilepatternException; 017import org.eclipse.jgit.api.errors.TransportException; 018import org.eclipse.jgit.diff.DiffEntry; 019import org.eclipse.jgit.diff.DiffEntry.ChangeType; 020import org.eclipse.jgit.diff.DiffFormatter; 021import org.eclipse.jgit.diff.RawTextComparator; 022import org.eclipse.jgit.errors.CorruptObjectException; 023import org.eclipse.jgit.errors.IncorrectObjectTypeException; 024import org.eclipse.jgit.errors.MissingObjectException; 025import org.eclipse.jgit.errors.StopWalkException; 026import org.eclipse.jgit.lib.Constants; 027import org.eclipse.jgit.lib.ObjectId; 028import org.eclipse.jgit.lib.ProgressMonitor; 029import org.eclipse.jgit.lib.Repository; 030import org.eclipse.jgit.lib.StoredConfig; 031import org.eclipse.jgit.lib.TextProgressMonitor; 032import org.eclipse.jgit.revwalk.RevCommit; 033import org.eclipse.jgit.revwalk.RevFlag; 034import org.eclipse.jgit.revwalk.RevSort; 035import org.eclipse.jgit.revwalk.RevWalk; 036import org.eclipse.jgit.revwalk.filter.CommitTimeRevFilter; 037import org.eclipse.jgit.revwalk.filter.RevFilter; 038import org.eclipse.jgit.transport.CredentialsProvider; 039import org.eclipse.jgit.transport.PushResult; 040import org.eclipse.jgit.transport.RefSpec; 041import org.eclipse.jgit.transport.RemoteRefUpdate; 042import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; 043import org.eclipse.jgit.util.io.DisabledOutputStream; 044 045import java.io.File; 046import java.io.IOException; 047import java.net.URI; 048import java.util.ArrayList; 049import java.util.Collection; 050import java.util.Date; 051import java.util.HashSet; 052import java.util.Iterator; 053import java.util.List; 054import java.util.Set; 055 056/* 057 * Licensed to the Apache Software Foundation (ASF) under one 058 * or more contributor license agreements. See the NOTICE file 059 * distributed with this work for additional information 060 * regarding copyright ownership. The ASF licenses this file 061 * to you under the Apache License, Version 2.0 (the 062 * "License"); you may not use this file except in compliance 063 * with the License. You may obtain a copy of the License at 064 * 065 * http://www.apache.org/licenses/LICENSE-2.0 066 * 067 * Unless required by applicable law or agreed to in writing, 068 * software distributed under the License is distributed on an 069 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 070 * KIND, either express or implied. See the License for the 071 * specific language governing permissions and limitations 072 * under the License. 073 */ 074 075/** 076 * JGit utility functions. 077 * 078 * @author <a href="mailto:struberg@yahoo.de">Mark Struberg</a> 079 * @author Dominik Bartholdi (imod) 080 * @since 1.9 081 */ 082public class JGitUtils 083{ 084 085 private JGitUtils() 086 { 087 // no op 088 } 089 090 /** 091 * Closes the repository wrapped by the passed git object 092 * @param git 093 */ 094 public static void closeRepo( Git git ) 095 { 096 if ( git != null && git.getRepository() != null ) 097 { 098 git.getRepository().close(); 099 } 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}