508
unlink $gs->{index};
509
}
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
+
700
sub cmd_dcommit {
701
my $head = shift;
702
command_noisy(qw/update-index --refresh/);
747
"without --no-rebase may be required."
748
}
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;
806
Git::SVN::remove_username($expect_url);
807
if (defined($_merge_info)) {
808
$_merge_info =~ tr{ }{\n};
820
print "diff-tree $d~1 $d\n";
821
} else {
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
+
831
my %ed_opts = ( r => $last_rev,
832
log => get_commit_entry($d)->{log},
833
ra => Git::SVN::Ra->new($url),
870
@finish = qw/reset --mixed/;
871
}
872
command_noisy(@finish, $gs->refname);
873
+
874
+ $rewritten_parent = command_oneline(qw/rev-parse HEAD/);
875
+
876
if (@diff) {
877
@refs = ();
878
my ($url_, $rev_, $uuid_, $gs_) =