34
34
import com .google .gerrit .server .events .ChangeEvent ;
35
35
import com .google .gerrit .server .events .EventDispatcher ;
36
36
import com .google .gerrit .server .events .EventFactory ;
37
+ import com .google .gerrit .server .git .CodeReviewCommit ;
38
+ import com .google .gerrit .server .git .MergeUtil ;
37
39
import com .google .gerrit .server .notedb .ChangeNotes ;
38
40
import com .google .gerrit .server .permissions .PermissionBackendException ;
39
41
import com .google .gerrit .server .project .NoSuchRefException ;
42
+ import com .google .gerrit .server .project .ProjectCache ;
43
+ import com .google .gerrit .server .project .ProjectState ;
40
44
import com .google .gerrit .server .query .change .ChangeData ;
41
45
import com .google .gerrit .server .query .change .InternalChangeQuery ;
42
46
import com .google .gerrit .server .submit .IntegrationException ;
50
54
import java .sql .Timestamp ;
51
55
import java .util .AbstractMap ;
52
56
import java .util .ArrayList ;
57
+ import java .util .Date ;
53
58
import java .util .HashMap ;
54
59
import java .util .Iterator ;
55
60
import java .util .List ;
60
65
import org .eclipse .jgit .lib .Constants ;
61
66
import org .eclipse .jgit .lib .ObjectId ;
62
67
import org .eclipse .jgit .lib .ObjectInserter ;
68
+ import org .eclipse .jgit .lib .ObjectReader ;
63
69
import org .eclipse .jgit .lib .PersonIdent ;
64
70
import org .eclipse .jgit .lib .Ref ;
65
71
import org .eclipse .jgit .lib .RefUpdate ;
@@ -92,6 +98,8 @@ public class QtUtil {
92
98
private final ChangeNotes .Factory changeNotesFactory ;
93
99
private final DynamicItem <EventDispatcher > eventDispatcher ;
94
100
private final EventFactory eventFactory ;
101
+ private final MergeUtil .Factory mergeUtilFactory ;
102
+ private final ProjectCache projectCache ;
95
103
private final QtCherryPickPatch qtCherryPickPatch ;
96
104
private final QtChangeUpdateOp .Factory qtUpdateFactory ;
97
105
private final QtEmailSender qtEmailSender ;
@@ -104,6 +112,8 @@ public class QtUtil {
104
112
ChangeNotes .Factory changeNotesFactory ,
105
113
EventFactory eventFactory ,
106
114
DynamicItem <EventDispatcher > eventDispatcher ,
115
+ MergeUtil .Factory mergeUtilFactory ,
116
+ ProjectCache projectCache ,
107
117
QtCherryPickPatch qtCherryPickPatch ,
108
118
QtChangeUpdateOp .Factory qtUpdateFactory ,
109
119
QtEmailSender qtEmailSender ) {
@@ -113,6 +123,8 @@ public class QtUtil {
113
123
this .changeNotesFactory = changeNotesFactory ;
114
124
this .eventDispatcher = eventDispatcher ;
115
125
this .eventFactory = eventFactory ;
126
+ this .mergeUtilFactory = mergeUtilFactory ;
127
+ this .projectCache = projectCache ;
116
128
this .qtCherryPickPatch = qtCherryPickPatch ;
117
129
this .qtUpdateFactory = qtUpdateFactory ;
118
130
this .qtEmailSender = qtEmailSender ;
@@ -604,11 +616,10 @@ public static RevCommit merge(
604
616
String message = customCommitMessage ;
605
617
if (message == null ) {
606
618
try {
607
- message = revWalk .parseCommit (toMerge ).getShortMessage ();
619
+ message = "Merge \" " + revWalk .parseCommit (toMerge ).getShortMessage () + " \" " ;
608
620
} catch (Exception e ) {
609
- message = toMerge . toString () ;
621
+ message = "Merge" ;
610
622
}
611
- message = "Merge \" " + toMerge .toString () + "\" " ;
612
623
}
613
624
614
625
final CommitBuilder mergeCommit = new CommitBuilder ();
@@ -675,6 +686,213 @@ private static RefUpdate.Result mergeObjectToBranch(
675
686
}
676
687
}
677
688
689
+ private RefUpdate .Result fastForwardBranch (Repository git ,
690
+ String branchName , ObjectId toObjectId ) {
691
+
692
+ RefUpdate .Result result = null ;
693
+
694
+ try {
695
+ RefUpdate refUpdate = git .updateRef (branchName );
696
+ refUpdate .setNewObjectId (toObjectId );
697
+ refUpdate .setForceUpdate (false );
698
+ result = refUpdate .update ();
699
+ logger .atInfo ().log ("fastforward branch %s to %s, result: %s" , branchName ,
700
+ toObjectId .name (), result );
701
+ } catch (Exception e ) {
702
+ result = null ;
703
+ logger .atWarning ().log ("fastforward failed for %s: %s" , branchName , e );
704
+ }
705
+
706
+ return result ;
707
+ }
708
+
709
+ private List <RevCommit > listCommitsInIntegrationBranch (Repository git ,
710
+ ObjectId integrationHeadId , ObjectId targetBranchHeadId ) {
711
+
712
+ List <RevCommit > commits = new ArrayList <RevCommit >();
713
+ int count = 0 ;
714
+
715
+ try {
716
+ RevWalk revWalk = new RevWalk (git );
717
+ RevCommit commit = revWalk .parseCommit (integrationHeadId );
718
+ RevCommit targetHeadCommit = revWalk .parseCommit (targetBranchHeadId );
719
+
720
+ do {
721
+ if (revWalk .isMergedInto (commit , targetHeadCommit )) {
722
+ commit = null ;
723
+ } else {
724
+ commits .add (0 , commit );
725
+ if (commit .getParentCount () > 0 ) {
726
+ // Qt Gerrit plugins's merges always have the branch as parent 0
727
+ commit = revWalk .parseCommit (commit .getParent (0 ));
728
+ } else commit = null ;
729
+ }
730
+ count ++;
731
+ } while (commit != null && count < 100 );
732
+ } catch (Exception e ) {
733
+ commits = null ;
734
+ logger .atWarning ().log ("listing commits in a branch failed: %s" , e );
735
+ }
736
+
737
+ if (count >= 100 ) {
738
+ logger .atWarning ().log ("listing commits in a branch failed: too many commmits" );
739
+ return null ;
740
+ }
741
+
742
+ return commits ;
743
+ }
744
+
745
+ private Boolean isCherryPickingAllowed (List <RevCommit > commits ) {
746
+ // Cherry-picking merge commits is not allowed, because it would squash
747
+ // the merge into one single commit
748
+ for (RevCommit commit : commits ) {
749
+ if (commit .getParentCount () > 1 ) return false ;
750
+ }
751
+ return true ;
752
+ }
753
+
754
+ private List <RevCommit > cherryPickCommitsToBranch (Repository git ,
755
+ Project .NameKey project , String branchName , List <RevCommit > commits ) {
756
+
757
+ try {
758
+ ProjectState projectState = projectCache .checkedGet (project );
759
+ MergeUtil mergeUtil = mergeUtilFactory .create (projectState , true );
760
+ ObjectInserter objInserter = git .newObjectInserter ();
761
+ ObjectReader reader = objInserter .newReader ();
762
+ CodeReviewCommit .CodeReviewRevWalk revWalk = CodeReviewCommit .newRevWalk (reader );
763
+ RevCommit newBranchHead = revWalk .parseCommit (git .resolve (branchName ));
764
+
765
+ List <RevCommit > cherryPicked = new ArrayList <RevCommit >();
766
+
767
+ for (RevCommit commit : commits ) {
768
+ CodeReviewCommit cherryPickCommit =
769
+ mergeUtil .createCherryPickFromCommit (
770
+ objInserter ,
771
+ git .getConfig (),
772
+ newBranchHead ,
773
+ commit ,
774
+ new PersonIdent (commit .getCommitterIdent (), new Date ()),
775
+ commit .getFullMessage (),
776
+ revWalk ,
777
+ 0 ,
778
+ true , // ignoreIdenticalTree
779
+ false ); // allowConflicts
780
+ objInserter .flush ();
781
+ logger .atInfo ().log ("created cherrypick commit %s from %s" , cherryPickCommit .name (),
782
+ commit .name ());
783
+ newBranchHead = cherryPickCommit ;
784
+ cherryPicked .add (cherryPickCommit );
785
+ }
786
+
787
+ RefUpdate .Result result = fastForwardBranch (git , branchName , newBranchHead );
788
+ if (result != RefUpdate .Result .FAST_FORWARD ) return null ;
789
+
790
+ return cherryPicked ;
791
+ } catch (Exception e ) {
792
+ logger .atWarning ().log ("cherrypicking commits to branch failed: %s" , e );
793
+ return null ;
794
+ }
795
+ }
796
+
797
+ private List <Map .Entry <ChangeData , RevCommit >> listChanges (Repository git ,
798
+ BranchNameKey destination , List <RevCommit > commits ) throws Exception {
799
+
800
+ Map <Change .Id , Map .Entry <ChangeData , RevCommit >> map = new HashMap <>();
801
+
802
+ for (RevCommit commit : commits ) {
803
+ String changeId = getChangeId (commit );
804
+ List <ChangeData > changes = null ;
805
+
806
+ if (changeId == null && commit .getParentCount () > 1 ) {
807
+ // Merge commit without Change-Id, so done by plugin: changes merged in will be parent 1
808
+ changeId = getChangeId (commit .getParent (1 ));
809
+ }
810
+
811
+ if (changeId != null ) {
812
+ Change .Key key = Change .key (changeId );
813
+ changes = queryProvider .get ().byBranchKey (destination , key );
814
+ }
815
+
816
+ if (changes != null && !changes .isEmpty ()) {
817
+ if (changes .size () > 1 ) {
818
+ String msg = String .format ("Same Change-Id in several changes on same branch: %s" ,
819
+ commit .name ());
820
+ throw new Exception (msg );
821
+ }
822
+ ChangeData cd = changes .get (0 );
823
+ map .put (cd .getId (), new AbstractMap .SimpleEntry <ChangeData , RevCommit >(cd , commit ));
824
+ }
825
+ }
826
+ return new ArrayList <Map .Entry <ChangeData , RevCommit >>(map .values ());
827
+ }
828
+
829
+ public List <Map .Entry <ChangeData , RevCommit >> mergeIntegrationToBranch (
830
+ IdentifiedUser user ,
831
+ Repository git ,
832
+ Project .NameKey project ,
833
+ final BranchNameKey integrationBranch ,
834
+ final BranchNameKey targetBranch ,
835
+ String customCommitMessage )
836
+ throws MergeConflictException , NoSuchRefException {
837
+
838
+ List <RevCommit > commitsInBranch = null ;
839
+ RevCommit newBranchHead = null ;
840
+ RefUpdate .Result result = null ;
841
+ ObjectId sourceId = null ;
842
+ ObjectId targetId = null ;
843
+
844
+ logger .atInfo ().log ("start merging integration %s to %s" , integrationBranch .branch (),
845
+ targetBranch .branch ());
846
+
847
+ try {
848
+ sourceId = git .resolve (integrationBranch .branch ());
849
+ if (sourceId == null ) throw new NoSuchRefException ("Invalid Revision: " + integrationBranch );
850
+
851
+ Ref targetRef = git .getRefDatabase ().getRef (targetBranch .branch ());
852
+ if (targetRef == null ) throw new NoSuchRefException ("No such branch: " + targetBranch );
853
+
854
+ targetId = git .resolve (targetBranch .branch ());
855
+ if (targetId == null ) throw new NoSuchRefException ("Invalid Revision: " + targetBranch );
856
+
857
+ if (sourceId .equals (targetId )) throw new NoSuchRefException ("Nothing to merge" );
858
+
859
+ commitsInBranch = listCommitsInIntegrationBranch (git , sourceId , targetId );
860
+ if (commitsInBranch == null )
861
+ throw new NoSuchRefException ("Failed to list commits in " + integrationBranch );
862
+ else if (commitsInBranch .isEmpty ())
863
+ throw new NoSuchRefException ("No commits in " + integrationBranch );
864
+ } catch (Exception e ) {
865
+ logger .atWarning ().log ("preconditions of merging integration failed: %s" , e );
866
+ throw new NoSuchRefException (e .getMessage ());
867
+ }
868
+
869
+ try {
870
+ logger .atInfo ().log ("Trying to fast forward..." );
871
+ result = fastForwardBranch (git , targetBranch .branch (), sourceId );
872
+ if (result == RefUpdate .Result .FAST_FORWARD )
873
+ return listChanges (git , targetBranch , commitsInBranch );
874
+
875
+ if (isCherryPickingAllowed (commitsInBranch )) {
876
+ logger .atInfo ().log ("Trying to rebase integration onto the target branch..." );
877
+ List <RevCommit > cherrypicks = cherryPickCommitsToBranch (git , project , targetBranch .branch (), commitsInBranch );
878
+ if (cherrypicks != null ) return listChanges (git , targetBranch , cherrypicks );
879
+ }
880
+
881
+ logger .atInfo ().log ("Trying to create merge commit..." );
882
+ List <Map .Entry <ChangeData , RevCommit >> mergedCommits =
883
+ listChangesNotMerged (git , integrationBranch , targetBranch );
884
+ result = mergeBranches (user , git , integrationBranch , targetBranch , customCommitMessage );
885
+ if (result != RefUpdate .Result .FAST_FORWARD ) throw new Exception ("Merge conflict" );
886
+ return mergedCommits ;
887
+ } catch (Exception e ) {
888
+ result = null ;
889
+ logger .atWarning ().log ("Merging integration %s to %s failed: %s" ,
890
+ integrationBranch .branch (), targetBranch .branch (), e .getMessage ());
891
+ throw new MergeConflictException ("Merge conflict:" + integrationBranch .branch () +
892
+ " to " + targetBranch .branch ());
893
+ }
894
+ }
895
+
678
896
private Supplier <ChangeAttribute > changeAttributeSupplier (Change change , ChangeNotes notes ) {
679
897
return Suppliers .memoize (
680
898
() -> {
0 commit comments