@@ -508,6 +508,195 @@ sub cmd_set_tree {
508508 unlink $gs -> {index };
509509}
510510
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+
511700sub cmd_dcommit {
512701 my $head = shift ;
513702 command_noisy(qw/ update-index --refresh/ );
@@ -558,6 +747,62 @@ sub cmd_dcommit {
558747 " without --no-rebase may be required."
559748 }
560749 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 ;
561806 Git::SVN::remove_username($expect_url );
562807 if (defined ($_merge_info)) {
563808 $_merge_info =~ tr { }{\n};
@@ -575,6 +820,14 @@ sub cmd_dcommit {
575820 print " diff-tree $d ~1 $d \n " ;
576821 } else {
577822 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+
578831 my %ed_opts = ( r => $last_rev ,
579832 log => get_commit_entry($d )-> {log },
580833 ra => Git::SVN::Ra-> new($url ),
@@ -617,6 +870,9 @@ sub cmd_dcommit {
617870 @finish = qw/ reset --mixed/ ;
618871 }
619872 command_noisy(@finish , $gs -> refname);
873+
874+ $rewritten_parent = command_oneline(qw/ rev-parse HEAD/ );
875+
620876 if (@diff ) {
621877 @refs = ();
622878 my ($url_ , $rev_ , $uuid_ , $gs_ ) =
0 commit comments