View Javadoc
1   /*
2    * Copyright (c) 2002-2020, City of Paris
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without
6    * modification, are permitted provided that the following conditions
7    * are met:
8    *
9    *  1. Redistributions of source code must retain the above copyright notice
10   *     and the following disclaimer.
11   *
12   *  2. Redistributions in binary form must reproduce the above copyright notice
13   *     and the following disclaimer in the documentation and/or other materials
14   *     provided with the distribution.
15   *
16   *  3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
17   *     contributors may be used to endorse or promote products derived from
18   *     this software without specific prior written permission.
19   *
20   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
24   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30   * POSSIBILITY OF SUCH DAMAGE.
31   *
32   * License 1.0
33   */
34  package fr.paris.lutece.plugins.appcenter.modules.fastdeployapplication.util.vcs;
35  
36  import java.io.File;
37  import java.io.IOException;
38  import java.nio.file.Files;
39  import java.nio.file.Path;
40  import java.nio.file.Paths;
41  import java.util.ArrayList;
42  import java.util.Collection;
43  import java.util.Iterator;
44  import java.util.List;
45  
46  import org.apache.commons.io.FileUtils;
47  import org.eclipse.jgit.api.CloneCommand;
48  import org.eclipse.jgit.api.CreateBranchCommand.SetupUpstreamMode;
49  import org.eclipse.jgit.api.Git;
50  import org.eclipse.jgit.api.MergeResult;
51  import org.eclipse.jgit.api.PullResult;
52  import org.eclipse.jgit.api.errors.CanceledException;
53  import org.eclipse.jgit.api.errors.CheckoutConflictException;
54  import org.eclipse.jgit.api.errors.DetachedHeadException;
55  import org.eclipse.jgit.api.errors.GitAPIException;
56  import org.eclipse.jgit.api.errors.InvalidConfigurationException;
57  import org.eclipse.jgit.api.errors.InvalidRefNameException;
58  import org.eclipse.jgit.api.errors.InvalidRemoteException;
59  import org.eclipse.jgit.api.errors.NoHeadException;
60  import org.eclipse.jgit.api.errors.RefAlreadyExistsException;
61  import org.eclipse.jgit.api.errors.RefNotFoundException;
62  import org.eclipse.jgit.api.errors.TransportException;
63  import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
64  import org.eclipse.jgit.lib.Ref;
65  import org.eclipse.jgit.lib.Repository;
66  import org.eclipse.jgit.revwalk.RevCommit;
67  import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
68  import org.eclipse.jgit.transport.RefSpec;
69  import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
70  
71  import fr.paris.lutece.plugins.appcenter.modules.fastdeployapplication.business.CommandResult;
72  import fr.paris.lutece.plugins.appcenter.modules.fastdeployapplication.business.vcs.GitUser;
73  import fr.paris.lutece.plugins.appcenter.modules.fastdeployapplication.util.DeploymentUtils;
74  import fr.paris.lutece.plugins.appcenter.modules.fastdeployapplication.util.FileUtil;
75  import fr.paris.lutece.portal.service.util.AppLogService;
76  import fr.paris.lutece.util.httpaccess.HttpAccess;
77  import fr.paris.lutece.util.httpaccess.HttpAccessException;
78  import fr.paris.lutece.util.signrequest.BasicAuthorizationAuthenticator;
79  import org.eclipse.jgit.api.LsRemoteCommand;
80  import org.eclipse.jgit.api.PullCommand;
81  import org.eclipse.jgit.attributes.AttributesNodeProvider;
82  import org.eclipse.jgit.lib.ObjectDatabase;
83  import org.eclipse.jgit.lib.RefDatabase;
84  import org.eclipse.jgit.lib.ReflogReader;
85  import org.eclipse.jgit.lib.StoredConfig;
86  
87  public class GitUtils
88  {
89  
90      public static final String MASTER_BRANCH = "master";
91      public static final String DEVELOP_BRANCH = "develop";
92      private static final String CONSTANTE_REF_TAG = "refs/tags/";
93  
94      public static Collection<String> getTagNameList( String strRepoUrl, CommandResult commandResult, GitUser user )
95      {
96          List<String> listTagName = new ArrayList<>( );
97  
98          Collection<Ref> colTags = lsRemote( strRepoUrl, commandResult, user, false, true );
99          for ( Ref ref : colTags )
100         {
101             listTagName.add( ref.getName( ).replace( CONSTANTE_REF_TAG, "" ) );
102         }
103 
104         return listTagName;
105     }
106 
107     public static Git getGit( String strClonePath, CommandResult commandResult )
108     {
109         Git git = null;
110         Repository repository = null;
111 
112         File fGitDir = new File( strClonePath + "/.git" );
113 
114         if ( !fGitDir.exists( ) )
115         {
116             return null;
117         }
118 
119         try
120         {
121             FileRepositoryBuilder builder = new FileRepositoryBuilder( );
122             repository = builder.setGitDir( fGitDir ).readEnvironment( ).findGitDir( ).build( );
123 
124             git = new Git( repository );
125         }
126         catch( IOException e )
127         {
128             DeploymentUtils.addTechnicalError( commandResult, e.getMessage( ), e );
129         }
130         finally
131         {
132             repository.close( );
133         }
134 
135         return git;
136     }
137 
138     public static Git cloneGit( String sClonePath, String sRepoURL, CommandResult commandResult, GitUser user, String strBranch, String strTagName )
139     {
140         Git git = null;
141         Repository repository = null;
142 
143         File fGitDir = new File( sClonePath );
144 
145         if ( fGitDir.exists( ) )
146         {
147             if ( !FileUtil.delete( fGitDir, commandResult.getLog( ) ) )
148             {
149                 DeploymentUtils.addTechnicalError( commandResult, commandResult.getLog( ).toString( ), null );
150                 return git;
151             }
152         }
153 
154         try
155         {
156             FileRepositoryBuilder builder = new FileRepositoryBuilder( );
157 
158             repository = builder.setGitDir( fGitDir ).readEnvironment( ).findGitDir( ).build( );
159 
160             CloneCommand clone = Git.cloneRepository( ).setBare( false ).setCloneAllBranches( true ).setDirectory( fGitDir ).setURI( getRepoUrl( sRepoURL ) );
161 
162             if ( user.getLogin( ) != null && user.getPassword( ) != null )
163             {
164                 clone.setCredentialsProvider( new UsernamePasswordCredentialsProvider( user.getLogin( ), user.getPassword( ) ) );
165             }
166             git = clone.call( );
167             createLocalBranch( git, DEVELOP_BRANCH, commandResult );
168             createLocalBranch( git, MASTER_BRANCH, commandResult );
169             repository.getConfig( ).setString( "user", null, "name", user.getLogin( ) );
170             repository.getConfig( ).save( );
171 
172             checkout( git, strBranch, strTagName, commandResult );
173         }
174         catch( InvalidRemoteException e )
175         {
176 
177             DeploymentUtils.addTechnicalError( commandResult, e.getMessage( ), e );
178 
179         }
180         catch( TransportException e )
181         {
182             DeploymentUtils.addTechnicalError( commandResult, e.getMessage( ), e );
183 
184         }
185         catch( IOException e )
186         {
187             DeploymentUtils.addTechnicalError( commandResult, e.getMessage( ), e );
188         }
189         catch( GitAPIException e )
190         {
191             DeploymentUtils.addTechnicalError( commandResult, e.getMessage( ), e );
192         }
193         finally
194         {
195             repository.close( );
196         }
197         return git;
198 
199     }
200 
201     public static PullResult pullGit( String strClonePath, String strRepoURL, CommandResult commandResult, GitUser user, String strBranch, String strTagName )
202     {
203         PullResult pullResult = null;
204         Repository repository = null;
205 
206         File fGitDir = new File( strClonePath + "/.git" );
207 
208         if ( !fGitDir.exists( ) )
209         {
210             cloneGit( strClonePath, strRepoURL, commandResult, user, null, null );
211         }
212 
213         try
214         {
215             FileRepositoryBuilder builder = new FileRepositoryBuilder( );
216             repository = builder.setGitDir( fGitDir ).readEnvironment( ).findGitDir( ).build( );
217 
218             Git git = new Git( repository );
219             PullCommand pullCommand = git.pull( ).setRemoteBranchName( strBranch );
220 
221             if ( user.getLogin( ) != null && user.getPassword( ) != null )
222             {
223                 pullCommand.setCredentialsProvider( new UsernamePasswordCredentialsProvider( user.getLogin( ), user.getPassword( ) ) );
224             }
225 
226             pullResult = pullCommand.call( );
227 
228             checkout( git, strBranch, strTagName, commandResult );
229         }
230         catch( InvalidRemoteException e )
231         {
232             DeploymentUtils.addTechnicalError( commandResult, e.getMessage( ), e );
233         }
234         catch( TransportException e )
235         {
236             DeploymentUtils.addTechnicalError( commandResult, e.getMessage( ), e );
237         }
238         catch( IOException e )
239         {
240             DeploymentUtils.addTechnicalError( commandResult, e.getMessage( ), e );
241         }
242         catch( GitAPIException e )
243         {
244             DeploymentUtils.addTechnicalError( commandResult, e.getMessage( ), e );
245         }
246         finally
247         {
248             repository.close( );
249         }
250 
251         return pullResult;
252     }
253 
254     public static void checkoutRepoBranch( Git git, String sBranchName, CommandResult commandResult )
255     {
256         try
257         {
258             git.checkout( ).setName( sBranchName ).setForce( true ).call( );
259         }
260         catch( InvalidRemoteException e )
261         {
262 
263             DeploymentUtils.addTechnicalError( commandResult, e.getMessage( ), e );
264 
265         }
266         catch( TransportException e )
267         {
268             DeploymentUtils.addTechnicalError( commandResult, e.getMessage( ), e );
269 
270         }
271 
272         catch( GitAPIException e )
273         {
274             DeploymentUtils.addTechnicalError( commandResult, e.getMessage( ), e );
275         }
276 
277     }
278 
279     public static void createLocalBranch( Git git, String sBranchName, CommandResult commandResult )
280     {
281         try
282         {
283             git.branchCreate( ).setName( sBranchName ).setUpstreamMode( SetupUpstreamMode.SET_UPSTREAM ).setStartPoint( "origin/" + sBranchName )
284                     .setForce( true ).call( );
285         }
286         catch( InvalidRemoteException e )
287         {
288 
289             DeploymentUtils.addTechnicalError( commandResult, e.getMessage( ), e );
290 
291         }
292         catch( TransportException e )
293         {
294             DeploymentUtils.addTechnicalError( commandResult, e.getMessage( ), e );
295 
296         }
297 
298         catch( GitAPIException e )
299         {
300             DeploymentUtils.addTechnicalError( commandResult, e.getMessage( ), e );
301         }
302 
303     }
304 
305     public static String getRefBranch( Git git, String sBranchName, CommandResult commandResult )
306     {
307 
308         String refLastCommit = null;
309         try
310         {
311             git.checkout( ).setName( sBranchName ).call( );
312             refLastCommit = getLastCommitId( git );
313         }
314 
315         catch( RefAlreadyExistsException e )
316         {
317             DeploymentUtils.addTechnicalError( commandResult, e.getMessage( ), e );
318         }
319         catch( RefNotFoundException e )
320         {
321             DeploymentUtils.addTechnicalError( commandResult, e.getMessage( ), e );
322         }
323         catch( InvalidRefNameException e )
324         {
325             DeploymentUtils.addTechnicalError( commandResult, e.getMessage( ), e );
326         }
327         catch( CheckoutConflictException e )
328         {
329             DeploymentUtils.addTechnicalError( commandResult, e.getMessage( ), e );
330         }
331         catch( GitAPIException e )
332         {
333             DeploymentUtils.addTechnicalError( commandResult, e.getMessage( ), e );
334         }
335         return refLastCommit;
336     }
337 
338     public static void pushForce( Git git, String strRefSpec, String strUserName, String strPassword )
339             throws InvalidRemoteException, TransportException, GitAPIException
340     {
341 
342         git.push( ).setRemote( "origin" ).setRefSpecs( new RefSpec( strRefSpec ) ).setForce( true )
343                 .setCredentialsProvider( new UsernamePasswordCredentialsProvider( strUserName, strPassword ) ).call( );
344 
345     }
346 
347     public static PullResult pullRepoBranch( Git git, String sRepoURL, String sBranchName )
348             throws IOException, WrongRepositoryStateException, InvalidConfigurationException, DetachedHeadException, InvalidRemoteException, CanceledException,
349             RefNotFoundException, NoHeadException, TransportException, GitAPIException
350     {
351         PullResult pPullResult = git.pull( ).call( );
352         return pPullResult;
353     }
354 
355     public static MergeResult mergeRepoBranch( Git git, String strBranchToMerge )
356             throws IOException, WrongRepositoryStateException, InvalidConfigurationException, DetachedHeadException, InvalidRemoteException, CanceledException,
357             RefNotFoundException, NoHeadException, TransportException, GitAPIException
358     {
359         List<Ref> call = git.branchList( ).call( );
360         Ref mergedBranchRef = null;
361         for ( Ref ref : call )
362         {
363             if ( ref.getName( ).equals( "refs/heads/" + strBranchToMerge ) )
364             {
365                 mergedBranchRef = ref;
366                 break;
367             }
368         }
369         MergeResult mergeResult = git.merge( ).include( mergedBranchRef ).call( );
370         return mergeResult;
371     }
372 
373     public static String getLastLog( Git git, int nMaxCommit ) throws NoHeadException, GitAPIException
374     {
375         Iterable<RevCommit> logList = git.log( ).setMaxCount( 1 ).call( );
376         Iterator i = logList.iterator( );
377         String sCommitMessages = "";
378         while ( i.hasNext( ) )
379         {
380             RevCommit revCommit = (RevCommit) i.next( );
381             sCommitMessages += revCommit.getFullMessage( );
382             sCommitMessages += "\n";
383             sCommitMessages += revCommit.getCommitterIdent( );
384         }
385         return sCommitMessages;
386     }
387 
388     public static String getLastCommitId( Git git ) throws NoHeadException, GitAPIException
389     {
390         Iterable<RevCommit> logList = git.log( ).setMaxCount( 1 ).call( );
391         Iterator i = logList.iterator( );
392         String strCommitId = null;
393         while ( i.hasNext( ) )
394         {
395             RevCommit revCommit = (RevCommit) i.next( );
396             strCommitId = revCommit.getName( );
397 
398         }
399         return strCommitId;
400     }
401 
402     public static MergeResult mergeBack( Git git, String strUserName, String strPassword, CommandResult commandResult ) throws IOException, GitAPIException
403     {
404 
405         Ref tag = getTagLinkedToLastRelease( git );
406 
407         git.checkout( ).setName( MASTER_BRANCH ).call( );
408         List<Ref> call = git.branchList( ).call( );
409 
410         Ref mergedBranchRef = null;
411         for ( Ref ref : call )
412         {
413             if ( ref.getName( ).equals( "refs/heads/" + DEVELOP_BRANCH ) )
414             {
415                 mergedBranchRef = ref;
416                 break;
417             }
418         }
419 
420         if ( tag != null )
421         {
422             mergedBranchRef = tag;
423         }
424         MergeResult mergeResult = git.merge( ).include( mergedBranchRef ).call( );
425         if ( mergeResult.getMergeStatus( ).equals( MergeResult.MergeStatus.CHECKOUT_CONFLICT )
426                 || mergeResult.getMergeStatus( ).equals( MergeResult.MergeStatus.CONFLICTING )
427                 || mergeResult.getMergeStatus( ).equals( MergeResult.MergeStatus.FAILED )
428                 || mergeResult.getMergeStatus( ).equals( MergeResult.MergeStatus.NOT_SUPPORTED ) )
429         {
430 
431             DeploymentUtils.addTechnicalError( commandResult,
432                     mergeResult.getMergeStatus( ).toString( ) + "\nPlease merge manually master into" + DEVELOP_BRANCH + "branch." );
433         }
434         else
435         {
436             git.push( ).setCredentialsProvider( new UsernamePasswordCredentialsProvider( strUserName, strPassword ) ).call( );
437             commandResult.getLog( ).append( mergeResult.getMergeStatus( ) );
438         }
439         return mergeResult;
440 
441     }
442 
443     public static String getFileContent( String strFullName, String strPathFile, String strBranch, String strUserName, String strPassword )
444     {
445         HttpAccess httpAccess = new HttpAccess( );
446         String strUrl = "https://raw.githubusercontent.com/" + strFullName + "/" + strBranch + "/" + strPathFile;
447         String strResponse = "";
448 
449         try
450         {
451             strResponse = httpAccess.doGet( strUrl, new BasicAuthorizationAuthenticator( strUserName, strPassword ), null );
452         }
453         catch( HttpAccessException ex )
454         {
455             AppLogService.error( ex );
456         }
457 
458         return strResponse;
459     }
460 
461     private static Ref getTagLinkedToLastRelease( Git git ) throws GitAPIException
462     {
463         final String TOKEN = "[maven-release-plugin] prepare release ";
464         Ref res = null;
465         String sTagName = null;
466 
467         Iterable<RevCommit> logList = git.log( ).setMaxCount( 10 ).call( );
468         Iterator i = logList.iterator( );
469         String sCommitMessages = "";
470         while ( i.hasNext( ) )
471         {
472             RevCommit revCommit = (RevCommit) i.next( );
473             sCommitMessages = revCommit.getFullMessage( );
474             int index = sCommitMessages.indexOf( TOKEN );
475             if ( index >= 0 )
476             {
477                 sTagName = sCommitMessages.replace( TOKEN, "" );
478                 break;
479             }
480         }
481 
482         if ( ( sTagName != null ) && ( !( sTagName.trim( ).equals( "" ) ) ) )
483         {
484             List<Ref> tags = git.tagList( ).call( );
485             for ( int j = 0; j < tags.size( ); j++ )
486             {
487                 Ref tag = tags.get( tags.size( ) - 1 - j );
488                 String tagName = tag.getName( );
489                 if ( tagName.equals( "refs/tags/" + sTagName ) )
490                 {
491                     res = tag;
492                     break;
493                 }
494             }
495         }
496 
497         return res;
498     }
499 
500     private static String getRepoUrl( String strRepoUrl )
501     {
502 
503         if ( strRepoUrl != null && strRepoUrl.startsWith( "scm:git:" ) )
504         {
505             strRepoUrl = strRepoUrl.substring( 8 );
506 
507         }
508 
509         return strRepoUrl;
510     }
511 
512     private static void checkout( Git git, String strBranch, String strTagName, CommandResult commandResult )
513     {
514         if ( strBranch == null && strTagName == null )
515         {
516             checkoutRepoBranch( git, DEVELOP_BRANCH, commandResult );
517             return;
518         }
519         if ( strBranch != null && strTagName != null )
520         {
521             DeploymentUtils.addTechnicalError( commandResult, "A branch name and a tag name are provided while checkouting the git repo" );
522             return;
523         }
524         if ( strBranch != null )
525         {
526             checkoutRepoBranch( git, strBranch, commandResult );
527         }
528         if ( strTagName != null )
529         {
530             checkoutTag( git, strTagName, commandResult );
531         }
532     }
533 
534     private static void checkoutTag( Git git, String strTagName, CommandResult commandResult )
535     {
536 
537         try
538         {
539             git.checkout( ).setName( CONSTANTE_REF_TAG + strTagName ).call( );
540 
541         }
542         catch( InvalidRemoteException e )
543         {
544 
545             DeploymentUtils.addTechnicalError( commandResult, e.getMessage( ), e );
546 
547         }
548         catch( TransportException e )
549         {
550             DeploymentUtils.addTechnicalError( commandResult, e.getMessage( ), e );
551 
552         }
553 
554         catch( GitAPIException e )
555         {
556             DeploymentUtils.addTechnicalError( commandResult, e.getMessage( ), e );
557         }
558     }
559 
560     public static Collection<Ref> lsRemote( String sRepoURL, CommandResult commandResult, GitUser user )
561     {
562         return lsRemote( sRepoURL, commandResult, user, false, false );
563     }
564 
565     public static Collection<Ref> lsRemote( String sRepoURL, CommandResult commandResult, GitUser user, boolean bHeads, boolean bTags )
566     {
567         Collection<Ref> refs = null;
568 
569         try
570         {
571             LsRemoteCommand lsRemote = Git.lsRemoteRepository( ).setRemote( getRepoUrl( sRepoURL ) ).setHeads( bHeads ).setTags( bTags );
572 
573             if ( user.getLogin( ) != null && user.getPassword( ) != null )
574             {
575                 lsRemote.setCredentialsProvider( new UsernamePasswordCredentialsProvider( user.getLogin( ), user.getPassword( ) ) );
576             }
577 
578             refs = lsRemote.call( );
579         }
580         catch( InvalidRemoteException e )
581         {
582             DeploymentUtils.addTechnicalError( commandResult, e.getMessage( ), e );
583         }
584         catch( TransportException e )
585         {
586             DeploymentUtils.addTechnicalError( commandResult, e.getMessage( ), e );
587         }
588         catch( GitAPIException e )
589         {
590             DeploymentUtils.addTechnicalError( commandResult, e.getMessage( ), e );
591         }
592 
593         return refs;
594     }
595 }