@@ -508,6 +508,195 @@ sub cmd_set_tree {
508
508
unlink $gs -> {index };
509
509
}
510
510
511
+ sub split_merge_info_range {
512
+ my ($range ) = @_ ;
513
+ if ($range =~ / (\d +)-(\d +)/ ) {
514
+ return (int ($1 ), int ($2 ));
515
+ } else {
516
+ return (int ($range ), int ($range ));
517
+ }
518
+ }
519
+
520
+ sub combine_ranges {
521
+ my ($in ) = @_ ;
522
+
523
+ my @fnums = ();
524
+ my @arr = split (/ ,/ , $in );
525
+ for my $element (@arr ) {
526
+ my ($start , $end ) = split_merge_info_range($element );
527
+ push @fnums , $start ;
528
+ }
529
+
530
+ my @sorted = @arr [ sort {
531
+ $fnums [$a ] <=> $fnums [$b ]
532
+ } 0..$#arr ];
533
+
534
+ my @return = ();
535
+ my $last = -1;
536
+ my $first = -1;
537
+ for my $element (@sorted ) {
538
+ my ($start , $end ) = split_merge_info_range($element );
539
+
540
+ if ($last == -1) {
541
+ $first = $start ;
542
+ $last = $end ;
543
+ next ;
544
+ }
545
+ if ($start <= $last +1) {
546
+ if ($end > $last ) {
547
+ $last = $end ;
548
+ }
549
+ next ;
550
+ }
551
+ if ($first == $last ) {
552
+ push @return , " $first " ;
553
+ } else {
554
+ push @return , " $first -$last " ;
555
+ }
556
+ $first = $start ;
557
+ $last = $end ;
558
+ }
559
+
560
+ if ($first != -1) {
561
+ if ($first == $last ) {
562
+ push @return , " $first " ;
563
+ } else {
564
+ push @return , " $first -$last " ;
565
+ }
566
+ }
567
+
568
+ return join (' ,' , @return );
569
+ }
570
+
571
+ sub merge_revs_into_hash {
572
+ my ($hash , $minfo ) = @_ ;
573
+ my @lines = split (' ' , $minfo );
574
+
575
+ for my $line (@lines ) {
576
+ my ($branchpath , $revs ) = split (/ :/ , $line );
577
+
578
+ if (exists ($hash -> {$branchpath })) {
579
+ # Merge the two revision sets
580
+ my $combined = " $hash ->{$branchpath },$revs " ;
581
+ $hash -> {$branchpath } = combine_ranges($combined );
582
+ } else {
583
+ # Just do range combining for consolidation
584
+ $hash -> {$branchpath } = combine_ranges($revs );
585
+ }
586
+ }
587
+ }
588
+
589
+ sub merge_merge_info {
590
+ my ($mergeinfo_one , $mergeinfo_two ) = @_ ;
591
+ my %result_hash = ();
592
+
593
+ merge_revs_into_hash(\%result_hash , $mergeinfo_one );
594
+ merge_revs_into_hash(\%result_hash , $mergeinfo_two );
595
+
596
+ my $result = ' ' ;
597
+ # Sort below is for consistency's sake
598
+ for my $branchname (sort keys (%result_hash )) {
599
+ my $revlist = $result_hash {$branchname };
600
+ $result .= " $branchname :$revlist \n "
601
+ }
602
+ return $result ;
603
+ }
604
+
605
+ sub populate_merge_info {
606
+ my ($d , $gs , $uuid , $linear_refs , $rewritten_parent ) = @_ ;
607
+
608
+ my %parentshash ;
609
+ read_commit_parents(\%parentshash , $d );
610
+ my @parents = @{$parentshash {$d }};
611
+ if ($#parents > 0) {
612
+ # Merge commit
613
+ my $all_parents_ok = 1;
614
+ my $aggregate_mergeinfo = ' ' ;
615
+ my $rooturl = $gs -> repos_root;
616
+
617
+ if (defined ($rewritten_parent )) {
618
+ # Replace first parent with newly-rewritten version
619
+ shift @parents ;
620
+ unshift @parents , $rewritten_parent ;
621
+ }
622
+
623
+ foreach my $parent (@parents ) {
624
+ my ($branchurl , $svnrev , $paruuid ) =
625
+ cmt_metadata($parent );
626
+
627
+ unless (defined ($svnrev )) {
628
+ # Should have been caught be preflight check
629
+ fatal " merge commit $d has ancestor $parent , but that change "
630
+ ." does not have git-svn metadata!" ;
631
+ }
632
+ unless ($branchurl =~ / ^$rooturl (.*)/ ) {
633
+ fatal " commit $parent git-svn metadata changed mid-run!" ;
634
+ }
635
+ my $branchpath = $1 ;
636
+
637
+ my $ra = Git::SVN::Ra-> new($branchurl );
638
+ my (undef , undef , $props ) =
639
+ $ra -> get_dir(canonicalize_path(" ." ), $svnrev );
640
+ my $par_mergeinfo = $props -> {' svn:mergeinfo' };
641
+ unless (defined $par_mergeinfo ) {
642
+ $par_mergeinfo = ' ' ;
643
+ }
644
+ # Merge previous mergeinfo values
645
+ $aggregate_mergeinfo =
646
+ merge_merge_info($aggregate_mergeinfo ,
647
+ $par_mergeinfo , 0);
648
+
649
+ next if $parent eq $parents [0]; # Skip first parent
650
+ # Add new changes being placed in tree by merge
651
+ my @cmd = (qw/ rev-list --reverse/ ,
652
+ $parent , qw/ --not/ );
653
+ foreach my $par (@parents ) {
654
+ unless ($par eq $parent ) {
655
+ push @cmd , $par ;
656
+ }
657
+ }
658
+ my @revsin = ();
659
+ my ($revlist , $ctx ) = command_output_pipe(@cmd );
660
+ while (<$revlist >) {
661
+ my $irev = $_ ;
662
+ chomp $irev ;
663
+ my (undef , $csvnrev , undef ) =
664
+ cmt_metadata($irev );
665
+ unless (defined $csvnrev ) {
666
+ # A child is missing SVN annotations...
667
+ # this might be OK, or might not be.
668
+ warn " W:child $irev is merged into revision "
669
+ ." $d but does not have git-svn metadata. "
670
+ ." This means git-svn cannot determine the "
671
+ ." svn revision numbers to place into the "
672
+ ." svn:mergeinfo property. You must ensure "
673
+ ." a branch is entirely committed to "
674
+ ." SVN before merging it in order for "
675
+ ." svn:mergeinfo population to function "
676
+ ." properly" ;
677
+ }
678
+ push @revsin , $csvnrev ;
679
+ }
680
+ command_close_pipe($revlist , $ctx );
681
+
682
+ last unless $all_parents_ok ;
683
+
684
+ # We now have a list of all SVN revnos which are
685
+ # merged by this particular parent. Integrate them.
686
+ next if $#revsin == -1;
687
+ my $newmergeinfo = " $branchpath :" . join (' ,' , @revsin );
688
+ $aggregate_mergeinfo =
689
+ merge_merge_info($aggregate_mergeinfo ,
690
+ $newmergeinfo , 1);
691
+ }
692
+ if ($all_parents_ok and $aggregate_mergeinfo ) {
693
+ return $aggregate_mergeinfo ;
694
+ }
695
+ }
696
+
697
+ return undef ;
698
+ }
699
+
511
700
sub cmd_dcommit {
512
701
my $head = shift ;
513
702
command_noisy(qw/ update-index --refresh/ );
@@ -558,6 +747,62 @@ sub cmd_dcommit {
558
747
" without --no-rebase may be required."
559
748
}
560
749
my $expect_url = $url ;
750
+
751
+ my $push_merge_info = eval {
752
+ command_oneline(qw/ config --get svn.pushmergeinfo/ )
753
+ };
754
+ if (not defined ($push_merge_info )
755
+ or $push_merge_info eq " false"
756
+ or $push_merge_info eq " no"
757
+ or $push_merge_info eq " never" ) {
758
+ $push_merge_info = 0;
759
+ }
760
+
761
+ unless (defined ($_merge_info) || ! $push_merge_info ) {
762
+ # Preflight check of changes to ensure no issues with mergeinfo
763
+ # This includes check for uncommitted-to-SVN parents
764
+ # (other than the first parent, which we will handle),
765
+ # information from different SVN repos, and paths
766
+ # which are not underneath this repository root.
767
+ my $rooturl = $gs -> repos_root;
768
+ foreach my $d (@$linear_refs ) {
769
+ my %parentshash ;
770
+ read_commit_parents(\%parentshash , $d );
771
+ my @realparents = @{$parentshash {$d }};
772
+ if ($#realparents > 0) {
773
+ # Merge commit
774
+ shift @realparents ; # Remove/ignore first parent
775
+ foreach my $parent (@realparents ) {
776
+ my ($branchurl , $svnrev , $paruuid ) = cmt_metadata($parent );
777
+ unless (defined $paruuid ) {
778
+ # A parent is missing SVN annotations...
779
+ # abort the whole operation.
780
+ fatal " $parent is merged into revision $d , "
781
+ ." but does not have git-svn metadata. "
782
+ ." Either dcommit the branch or use a "
783
+ ." local cherry-pick, FF merge, or rebase "
784
+ ." instead of an explicit merge commit." ;
785
+ }
786
+
787
+ unless ($paruuid eq $uuid ) {
788
+ # Parent has SVN metadata from different repository
789
+ fatal " merge parent $parent for change $d has "
790
+ ." git-svn uuid $paruuid , while current change "
791
+ ." has uuid $uuid !" ;
792
+ }
793
+
794
+ unless ($branchurl =~ / ^$rooturl (.*)/ ) {
795
+ # This branch is very strange indeed.
796
+ fatal " merge parent $parent for $d is on branch "
797
+ ." $branchurl , which is not under the "
798
+ ." git-svn root $rooturl !" ;
799
+ }
800
+ }
801
+ }
802
+ }
803
+ }
804
+
805
+ my $rewritten_parent ;
561
806
Git::SVN::remove_username($expect_url );
562
807
if (defined ($_merge_info)) {
563
808
$_merge_info =~ tr { }{\n};
@@ -575,6 +820,14 @@ sub cmd_dcommit {
575
820
print " diff-tree $d ~1 $d \n " ;
576
821
} else {
577
822
my $cmt_rev ;
823
+
824
+ unless (defined ($_merge_info) || ! $push_merge_info ) {
825
+ $_merge_info = populate_merge_info($d , $gs ,
826
+ $uuid ,
827
+ $linear_refs ,
828
+ $rewritten_parent );
829
+ }
830
+
578
831
my %ed_opts = ( r => $last_rev ,
579
832
log => get_commit_entry($d )-> {log },
580
833
ra => Git::SVN::Ra-> new($url ),
@@ -617,6 +870,9 @@ sub cmd_dcommit {
617
870
@finish = qw/ reset --mixed/ ;
618
871
}
619
872
command_noisy(@finish , $gs -> refname);
873
+
874
+ $rewritten_parent = command_oneline(qw/ rev-parse HEAD/ );
875
+
620
876
if (@diff ) {
621
877
@refs = ();
622
878
my ($url_ , $rev_ , $uuid_ , $gs_ ) =
0 commit comments