]> git.ipfire.org Git - thirdparty/git.git/blame - git-archimport.perl
[PATCH] archimport documentation tidyup
[thirdparty/git.git] / git-archimport.perl
CommitLineData
d3968363
ML
1#!/usr/bin/perl -w
2#
3# This tool is copyright (c) 2005, Martin Langhoff.
4# It is released under the Gnu Public License, version 2.
5#
6# The basic idea is to walk the output of tla abrowse,
7# fetch the changesets and apply them.
8#
9=head1 Invocation
10
215a7ad1 11 git-archimport -i <archive>/<branch> [<archive>/<branch>]
d3968363
ML
12 [ <archive>/<branch> ]
13
14 The script expects you to provide the key roots where it can start the
15 import from an 'initial import' or 'tag' type of Arch commit. It will
16 then follow all the branching and tagging within the provided roots.
17
18 It will die if it sees branches that have different roots.
19
20=head2 TODO
21
22 - keep track of merged patches, and mark a git merge when it happens
23 - smarter rules to parse the archive history "up" and "down"
24 - be able to continue an import where we left off
25 - audit shell-escaping of filenames
26
27=head1 Devel tricks
28
29Add print in front of the shell commands invoked via backticks.
30
31=cut
32
33use strict;
34use warnings;
35use Getopt::Std;
36use File::Spec;
37use File::Temp qw(tempfile);
38use File::Path qw(mkpath);
39use File::Basename qw(basename dirname);
40use String::ShellQuote;
41use Time::Local;
42use IO::Socket;
43use IO::Pipe;
44use POSIX qw(strftime dup2);
45use Data::Dumper qw/ Dumper /;
46use IPC::Open2;
47
48$SIG{'PIPE'}="IGNORE";
49$ENV{'TZ'}="UTC";
50
51our($opt_h,$opt_v, $opt_T,
3292ae47 52 $opt_C,$opt_t);
d3968363
ML
53
54sub usage() {
55 print STDERR <<END;
56Usage: ${\basename $0} # fetch/update GIT from Arch
3292ae47 57 [ -h ] [ -v ] [ -T ]
d3968363
ML
58 [ -C GIT_repository ] [ -t tempdir ]
59 repository/arch-branch [ repository/arch-branch] ...
60END
61 exit(1);
62}
63
b779d5f0 64getopts("ThviC:t:") or usage();
d3968363
ML
65usage if $opt_h;
66
67@ARGV >= 1 or usage();
68my @arch_roots = @ARGV;
69
70my $tmp = $opt_t;
71$tmp ||= '/tmp';
72$tmp .= '/git-archimport/';
73
74my $git_tree = $opt_C;
75$git_tree ||= ".";
76
77
78my @psets = (); # the collection
b779d5f0
ML
79my %psets = (); # the collection, by name
80
81my %rptags = (); # my reverse private tags
82 # to map a SHA1 to a commitid
d3968363
ML
83
84foreach my $root (@arch_roots) {
85 my ($arepo, $abranch) = split(m!/!, $root);
86 open ABROWSE, "tla abrowse -f -A $arepo --desc --merges $abranch |"
87 or die "Problems with tla abrowse: $!";
88
89 my %ps = (); # the current one
90 my $mode = '';
91 my $lastseen = '';
92
93 while (<ABROWSE>) {
94 chomp;
95
96 # first record padded w 8 spaces
97 if (s/^\s{8}\b//) {
98
99 # store the record we just captured
100 if (%ps) {
101 my %temp = %ps; # break references
102 push (@psets, \%temp);
b779d5f0 103 $psets{$temp{id}} = \%temp;
d3968363
ML
104 %ps = ();
105 }
106
107 my ($id, $type) = split(m/\s{3}/, $_);
108 $ps{id} = $id;
109 $ps{repo} = $arepo;
110
111 # deal with types
112 if ($type =~ m/^\(simple changeset\)/) {
113 $ps{type} = 's';
114 } elsif ($type eq '(initial import)') {
115 $ps{type} = 'i';
116 } elsif ($type =~ m/^\(tag revision of (.+)\)/) {
117 $ps{type} = 't';
118 $ps{tag} = $1;
119 } else {
120 warn "Unknown type $type";
121 }
122 $lastseen = 'id';
123 }
124
125 if (s/^\s{10}//) {
126 # 10 leading spaces or more
127 # indicate commit metadata
128
129 # date & author
130 if ($lastseen eq 'id' && m/^\d{4}-\d{2}-\d{2}/) {
131
132 my ($date, $authoremail) = split(m/\s{2,}/, $_);
133 $ps{date} = $date;
134 $ps{date} =~ s/\bGMT$//; # strip off trailign GMT
135 if ($ps{date} =~ m/\b\w+$/) {
136 warn 'Arch dates not in GMT?! - imported dates will be wrong';
137 }
138
139 $authoremail =~ m/^(.+)\s(\S+)$/;
140 $ps{author} = $1;
141 $ps{email} = $2;
142
143 $lastseen = 'date';
144
145 } elsif ($lastseen eq 'date') {
146 # the only hint is position
147 # subject is after date
148 $ps{subj} = $_;
149 $lastseen = 'subj';
150
151 } elsif ($lastseen eq 'subj' && $_ eq 'merges in:') {
152 $ps{merges} = [];
153 $lastseen = 'merges';
154
155 } elsif ($lastseen eq 'merges' && s/^\s{2}//) {
156 push (@{$ps{merges}}, $_);
157 } else {
158 warn 'more metadata after merges!?';
159 }
160
161 }
162 }
163
164 if (%ps) {
165 my %temp = %ps; # break references
b779d5f0
ML
166 push (@psets, \%temp);
167 $psets{ $temp{id} } = \%temp;
d3968363
ML
168 %ps = ();
169 }
170 close ABROWSE;
171} # end foreach $root
172
173## Order patches by time
174@psets = sort {$a->{date}.$b->{id} cmp $b->{date}.$b->{id}} @psets;
175
176#print Dumper \@psets;
177
178##
179## TODO cleanup irrelevant patches
180## and put an initial import
181## or a full tag
3292ae47
ML
182my $import = 0;
183unless (-d '.git') { # initial import
d3968363
ML
184 if ($psets[0]{type} eq 'i' || $psets[0]{type} eq 't') {
185 print "Starting import from $psets[0]{id}\n";
3292ae47
ML
186 `git-init-db`;
187 die $! if $?;
188 $import = 1;
d3968363
ML
189 } else {
190 die "Need to start from an import or a tag -- cannot use $psets[0]{id}";
191 }
b779d5f0
ML
192} else { # progressing an import
193 # load the rptags
194 opendir(DIR, ".git/archimport/tags")
195 || die "can't opendir: $!";
196 while (my $file = readdir(DIR)) {
197 # skip non-interesting-files
198 next unless -f ".git/archimport/tags/$file";
199 next if $file =~ m/--base-0$/; # don't care for base-0
200 my $sha = ptag($file);
201 chomp $sha;
202 # reconvert the 3rd '--' sequence from the end
203 # into a slash
204 # $file = reverse $file;
205 # $file =~ s!^(.+?--.+?--.+?--.+?)--(.+)$!$1/$2!;
206 # $file = reverse $file;
207 $rptags{$sha} = $file;
208 }
209 closedir DIR;
d3968363
ML
210}
211
3292ae47 212# process patchsets
d3968363
ML
213foreach my $ps (@psets) {
214
215 $ps->{branch} = branchname($ps->{id});
216
217 #
218 # ensure we have a clean state
219 #
220 if (`git diff-files`) {
221 die "Unclean tree when about to process $ps->{id} " .
222 " - did we fail to commit cleanly before?";
223 }
224 die $! if $?;
225
3292ae47
ML
226 #
227 # skip commits already in repo
228 #
229 if (ptag($ps->{id})) {
230 $opt_v && print "Skipping already imported: $ps->{id}\n";
231 next;
232 }
233
d3968363
ML
234 #
235 # create the branch if needed
236 #
3292ae47
ML
237 if ($ps->{type} eq 'i' && !$import) {
238 die "Should not have more than one 'Initial import' per GIT import: $ps->{id}";
d3968363
ML
239 }
240
3292ae47 241 unless ($import) { # skip for import
d3968363
ML
242 if ( -e ".git/refs/heads/$ps->{branch}") {
243 # we know about this branch
244 `git checkout $ps->{branch}`;
245 } else {
246 # new branch! we need to verify a few things
247 die "Branch on a non-tag!" unless $ps->{type} eq 't';
248 my $branchpoint = ptag($ps->{tag});
249 die "Tagging from unknown id unsupported: $ps->{tag}"
250 unless $branchpoint;
251
252 # find where we are supposed to branch from
253 `git checkout -b $ps->{branch} $branchpoint`;
52586ecb
ML
254
255 # If we trust Arch with the fact that this is just
256 # a tag, and it does not affect the state of the tree
257 # then we just tag and move on
258 tag($ps->{id}, $branchpoint);
259 ptag($ps->{id}, $branchpoint);
260 print " * Tagged $ps->{id} at $branchpoint\n";
261 next;
d3968363
ML
262 }
263 die $! if $?;
264 }
265
d3968363
ML
266 #
267 # Apply the import/changeset/merge into the working tree
268 #
269 if ($ps->{type} eq 'i' || $ps->{type} eq 't') {
d3968363 270 apply_import($ps) or die $!;
3292ae47 271 $import=0;
d3968363
ML
272 } elsif ($ps->{type} eq 's') {
273 apply_cset($ps);
274 }
275
276 #
277 # prepare update git's index, based on what arch knows
278 # about the pset, resolve parents, etc
279 #
280 my $tree;
281
282 my $commitlog = `tla cat-archive-log -A $ps->{repo} $ps->{id}`;
283 die "Error in cat-archive-log: $!" if $?;
284
285 # parselog will git-add/rm files
286 # and generally prepare things for the commit
287 # NOTE: parselog will shell-quote filenames!
288 my ($sum, $msg, $add, $del, $mod, $ren) = parselog($commitlog);
289 my $logmessage = "$sum\n$msg";
290
291
292 # imports don't give us good info
293 # on added files. Shame on them
294 if ($ps->{type} eq 'i' || $ps->{type} eq 't') {
215a7ad1
JH
295 `find . -type f -print0 | grep -zv '^./.git' | xargs -0 -l100 git-update-index --add`;
296 `git-ls-files --deleted -z | xargs --no-run-if-empty -0 -l100 git-update-index --remove`;
d3968363
ML
297 }
298
299 if (@$add) {
300 while (@$add) {
301 my @slice = splice(@$add, 0, 100);
302 my $slice = join(' ', @slice);
215a7ad1
JH
303 `git-update-index --add $slice`;
304 die "Error in git-update-index --add: $!" if $?;
d3968363
ML
305 }
306 }
307 if (@$del) {
308 foreach my $file (@$del) {
309 unlink $file or die "Problems deleting $file : $!";
310 }
311 while (@$del) {
312 my @slice = splice(@$del, 0, 100);
313 my $slice = join(' ', @slice);
215a7ad1
JH
314 `git-update-index --remove $slice`;
315 die "Error in git-update-index --remove: $!" if $?;
d3968363
ML
316 }
317 }
318 if (@$ren) { # renamed
319 if (@$ren % 2) {
320 die "Odd number of entries in rename!?";
321 }
322 ;
323 while (@$ren) {
324 my $from = pop @$ren;
325 my $to = pop @$ren;
326
327 unless (-d dirname($to)) {
328 mkpath(dirname($to)); # will die on err
329 }
330 #print "moving $from $to";
331 `mv $from $to`;
332 die "Error renaming $from $to : $!" if $?;
215a7ad1
JH
333 `git-update-index --remove $from`;
334 die "Error in git-update-index --remove: $!" if $?;
335 `git-update-index --add $to`;
336 die "Error in git-update-index --add: $!" if $?;
d3968363
ML
337 }
338
339 }
340 if (@$mod) { # must be _after_ renames
341 while (@$mod) {
342 my @slice = splice(@$mod, 0, 100);
343 my $slice = join(' ', @slice);
215a7ad1
JH
344 `git-update-index $slice`;
345 die "Error in git-update-index: $!" if $?;
d3968363
ML
346 }
347 }
348
215a7ad1 349 # warn "errors when running git-update-index! $!";
d3968363
ML
350 $tree = `git-write-tree`;
351 die "cannot write tree $!" if $?;
352 chomp $tree;
353
354
355 #
356 # Who's your daddy?
357 #
358 my @par;
359 if ( -e ".git/refs/heads/$ps->{branch}") {
360 if (open HEAD, "<.git/refs/heads/$ps->{branch}") {
361 my $p = <HEAD>;
362 close HEAD;
363 chomp $p;
364 push @par, '-p', $p;
365 } else {
366 if ($ps->{type} eq 's') {
367 warn "Could not find the right head for the branch $ps->{branch}";
368 }
369 }
370 }
371
b779d5f0
ML
372 if ($ps->{merges}) {
373 push @par, find_parents($ps);
374 }
d3968363
ML
375 my $par = join (' ', @par);
376
377 #
378 # Commit, tag and clean state
379 #
380 $ENV{TZ} = 'GMT';
381 $ENV{GIT_AUTHOR_NAME} = $ps->{author};
382 $ENV{GIT_AUTHOR_EMAIL} = $ps->{email};
383 $ENV{GIT_AUTHOR_DATE} = $ps->{date};
384 $ENV{GIT_COMMITTER_NAME} = $ps->{author};
385 $ENV{GIT_COMMITTER_EMAIL} = $ps->{email};
386 $ENV{GIT_COMMITTER_DATE} = $ps->{date};
387
388 my ($pid, $commit_rh, $commit_wh);
389 $commit_rh = 'commit_rh';
390 $commit_wh = 'commit_wh';
391
392 $pid = open2(*READER, *WRITER, "git-commit-tree $tree $par")
393 or die $!;
394 print WRITER $logmessage; # write
395 close WRITER;
396 my $commitid = <READER>; # read
397 chomp $commitid;
398 close READER;
399 waitpid $pid,0; # close;
400
401 if (length $commitid != 40) {
402 die "Something went wrong with the commit! $! $commitid";
403 }
404 #
405 # Update the branch
406 #
407 open HEAD, ">.git/refs/heads/$ps->{branch}";
408 print HEAD $commitid;
409 close HEAD;
410 unlink ('.git/HEAD');
411 symlink("refs/heads/$ps->{branch}",".git/HEAD");
412
413 # tag accordingly
414 ptag($ps->{id}, $commitid); # private tag
415 if ($opt_T || $ps->{type} eq 't' || $ps->{type} eq 'i') {
416 tag($ps->{id}, $commitid);
417 }
418 print " * Committed $ps->{id}\n";
419 print " + tree $tree\n";
420 print " + commit $commitid\n";
b779d5f0
ML
421 $opt_v && print " + commit date is $ps->{date} \n";
422 $opt_v && print " + parents: $par \n";
d3968363
ML
423}
424
425sub branchname {
426 my $id = shift;
427 $id =~ s#^.+?/##;
428 my @parts = split(m/--/, $id);
429 return join('--', @parts[0..1]);
430}
431
432sub apply_import {
433 my $ps = shift;
434 my $bname = branchname($ps->{id});
435
436 `mkdir -p $tmp`;
437
438 `tla get -s --no-pristine -A $ps->{repo} $ps->{id} $tmp/import`;
439 die "Cannot get import: $!" if $?;
440 `rsync -v --archive --delete --exclude '.git' --exclude '.arch-ids' --exclude '{arch}' $tmp/import/* ./`;
441 die "Cannot rsync import:$!" if $?;
442
443 `rm -fr $tmp/import`;
444 die "Cannot remove tempdir: $!" if $?;
445
446
447 return 1;
448}
449
450sub apply_cset {
451 my $ps = shift;
452
453 `mkdir -p $tmp`;
454
455 # get the changeset
456 `tla get-changeset -A $ps->{repo} $ps->{id} $tmp/changeset`;
457 die "Cannot get changeset: $!" if $?;
458
459 # apply patches
460 if (`find $tmp/changeset/patches -type f -name '*.patch'`) {
461 # this can be sped up considerably by doing
462 # (find | xargs cat) | patch
463 # but that cna get mucked up by patches
464 # with missing trailing newlines or the standard
465 # 'missing newline' flag in the patch - possibly
466 # produced with an old/buggy diff.
467 # slow and safe, we invoke patch once per patchfile
468 `find $tmp/changeset/patches -type f -name '*.patch' -print0 | grep -zv '{arch}' | xargs -iFILE -0 --no-run-if-empty patch -p1 --forward -iFILE`;
469 die "Problem applying patches! $!" if $?;
470 }
471
472 # apply changed binary files
473 if (my @modified = `find $tmp/changeset/patches -type f -name '*.modified'`) {
474 foreach my $mod (@modified) {
475 chomp $mod;
476 my $orig = $mod;
477 $orig =~ s/\.modified$//; # lazy
478 $orig =~ s!^\Q$tmp\E/changeset/patches/!!;
479 #print "rsync -p '$mod' '$orig'";
480 `rsync -p $mod ./$orig`;
481 die "Problem applying binary changes! $!" if $?;
482 }
483 }
484
485 # bring in new files
486 `rsync --archive --exclude '.git' --exclude '.arch-ids' --exclude '{arch}' $tmp/changeset/new-files-archive/* ./`;
487
488 # deleted files are hinted from the commitlog processing
489
490 `rm -fr $tmp/changeset`;
491}
492
493
494# =for reference
495# A log entry looks like
496# Revision: moodle-org--moodle--1.3.3--patch-15
497# Archive: arch-eduforge@catalyst.net.nz--2004
498# Creator: Penny Leach <penny@catalyst.net.nz>
499# Date: Wed May 25 14:15:34 NZST 2005
500# Standard-date: 2005-05-25 02:15:34 GMT
501# New-files: lang/de/.arch-ids/block_glossary_random.php.id
502# lang/de/.arch-ids/block_html.php.id
503# New-directories: lang/de/help/questionnaire
504# lang/de/help/questionnaire/.arch-ids
505# Renamed-files: .arch-ids/db_sears.sql.id db/.arch-ids/db_sears.sql.id
506# db_sears.sql db/db_sears.sql
507# Removed-files: lang/be/docs/.arch-ids/release.html.id
508# lang/be/docs/.arch-ids/releaseold.html.id
509# Modified-files: admin/cron.php admin/delete.php
510# admin/editor.html backup/lib.php backup/restore.php
511# New-patches: arch-eduforge@catalyst.net.nz--2004/moodle-org--moodle--1.3.3--patch-15
512# Summary: Updating to latest from MOODLE_14_STABLE (1.4.5+)
513# Keywords:
514#
515# Updating yadda tadda tadda madda
516sub parselog {
517 my $log = shift;
518 #print $log;
519
520 my (@add, @del, @mod, @ren, @kw, $sum, $msg );
521
522 if ($log =~ m/(?:\n|^)New-files:(.*?)(?=\n\w)/s ) {
523 my $files = $1;
524 @add = split(m/\s+/s, $files);
525 }
526
527 if ($log =~ m/(?:\n|^)Removed-files:(.*?)(?=\n\w)/s ) {
528 my $files = $1;
529 @del = split(m/\s+/s, $files);
530 }
531
532 if ($log =~ m/(?:\n|^)Modified-files:(.*?)(?=\n\w)/s ) {
533 my $files = $1;
534 @mod = split(m/\s+/s, $files);
535 }
536
537 if ($log =~ m/(?:\n|^)Renamed-files:(.*?)(?=\n\w)/s ) {
538 my $files = $1;
539 @ren = split(m/\s+/s, $files);
540 }
541
542 $sum ='';
543 if ($log =~ m/^Summary:(.+?)$/m ) {
544 $sum = $1;
545 $sum =~ s/^\s+//;
546 $sum =~ s/\s+$//;
547 }
548
549 $msg = '';
550 if ($log =~ m/\n\n(.+)$/s) {
551 $msg = $1;
552 $msg =~ s/^\s+//;
553 $msg =~ s/\s+$//;
554 }
555
556
557 # cleanup the arrays
558 foreach my $ref ( (\@add, \@del, \@mod, \@ren) ) {
559 my @tmp = ();
560 while (my $t = pop @$ref) {
561 next unless length ($t);
562 next if $t =~ m!\{arch\}/!;
563 next if $t =~ m!\.arch-ids/!;
564 next if $t =~ m!\.arch-inventory$!;
565 push (@tmp, shell_quote($t));
566 }
567 @$ref = @tmp;
568 }
569
570 #print Dumper [$sum, $msg, \@add, \@del, \@mod, \@ren];
571 return ($sum, $msg, \@add, \@del, \@mod, \@ren);
572}
573
574# write/read a tag
575sub tag {
576 my ($tag, $commit) = @_;
577 $tag =~ s|/|--|g;
578 $tag = shell_quote($tag);
579
580 if ($commit) {
581 open(C,">.git/refs/tags/$tag")
582 or die "Cannot create tag $tag: $!\n";
583 print C "$commit\n"
584 or die "Cannot write tag $tag: $!\n";
585 close(C)
586 or die "Cannot write tag $tag: $!\n";
b779d5f0 587 print " * Created tag ' $tag' on '$commit'\n" if $opt_v;
d3968363
ML
588 } else { # read
589 open(C,"<.git/refs/tags/$tag")
590 or die "Cannot read tag $tag: $!\n";
591 $commit = <C>;
592 chomp $commit;
593 die "Error reading tag $tag: $!\n" unless length $commit == 40;
594 close(C)
595 or die "Cannot read tag $tag: $!\n";
596 return $commit;
597 }
598}
599
600# write/read a private tag
601# reads fail softly if the tag isn't there
602sub ptag {
603 my ($tag, $commit) = @_;
604 $tag =~ s|/|--|g;
605 $tag = shell_quote($tag);
606
607 unless (-d '.git/archimport/tags') {
608 mkpath('.git/archimport/tags');
609 }
610
611 if ($commit) { # write
612 open(C,">.git/archimport/tags/$tag")
613 or die "Cannot create tag $tag: $!\n";
614 print C "$commit\n"
615 or die "Cannot write tag $tag: $!\n";
616 close(C)
617 or die "Cannot write tag $tag: $!\n";
b779d5f0
ML
618 $rptags{$commit} = $tag
619 unless $tag =~ m/--base-0$/;
d3968363
ML
620 } else { # read
621 # if the tag isn't there, return 0
622 unless ( -s ".git/archimport/tags/$tag") {
d3968363
ML
623 return 0;
624 }
625 open(C,"<.git/archimport/tags/$tag")
626 or die "Cannot read tag $tag: $!\n";
627 $commit = <C>;
628 chomp $commit;
629 die "Error reading tag $tag: $!\n" unless length $commit == 40;
630 close(C)
631 or die "Cannot read tag $tag: $!\n";
b779d5f0
ML
632 unless (defined $rptags{$commit}) {
633 $rptags{$commit} = $tag;
634 }
d3968363
ML
635 return $commit;
636 }
637}
b779d5f0
ML
638
639sub find_parents {
640 #
641 # Identify what branches are merging into me
642 # and whether we are fully merged
643 # git-merge-base <headsha> <headsha> should tell
644 # me what the base of the merge should be
645 #
646 my $ps = shift;
647
648 my %branches; # holds an arrayref per branch
649 # the arrayref contains a list of
650 # merged patches between the base
651 # of the merge and the current head
652
653 my @parents; # parents found for this commit
654
655 # simple loop to split the merges
656 # per branch
657 foreach my $merge (@{$ps->{merges}}) {
658 my $branch = branchname($merge);
659 unless (defined $branches{$branch} ){
660 $branches{$branch} = [];
661 }
662 push @{$branches{$branch}}, $merge;
663 }
664
665 #
666 # foreach branch find a merge base and walk it to the
667 # head where we are, collecting the merged patchsets that
668 # Arch has recorded. Keep that in @have
669 # Compare that with the commits on the other branch
670 # between merge-base and the tip of the branch (@need)
671 # and see if we have a series of consecutive patches
672 # starting from the merge base. The tip of the series
673 # of consecutive patches merged is our new parent for
674 # that branch.
675 #
676 foreach my $branch (keys %branches) {
677 my $mergebase = `git-merge-base $branch $ps->{branch}`;
678 die "Cannot find merge base for $branch and $ps->{branch}" if $?;
679 chomp $mergebase;
680
681 # now walk up to the mergepoint collecting what patches we have
682 my $branchtip = git_rev_parse($ps->{branch});
683 my @ancestors = `git-rev-list --merge-order $branchtip ^$mergebase`;
684 my %have; # collected merges this branch has
685 foreach my $merge (@{$ps->{merges}}) {
686 $have{$merge} = 1;
687 }
688 my %ancestorshave;
689 foreach my $par (@ancestors) {
690 $par = commitid2pset($par);
691 if (defined $par->{merges}) {
692 foreach my $merge (@{$par->{merges}}) {
693 $ancestorshave{$merge}=1;
694 }
695 }
696 }
697 # print "++++ Merges in $ps->{id} are....\n";
698 # my @have = sort keys %have; print Dumper(\@have);
699
700 # merge what we have with what ancestors have
701 %have = (%have, %ancestorshave);
702
703 # see what the remote branch has - these are the merges we
704 # will want to have in a consecutive series from the mergebase
705 my $otherbranchtip = git_rev_parse($branch);
706 my @needraw = `git-rev-list --merge-order $otherbranchtip ^$mergebase`;
707 my @need;
708 foreach my $needps (@needraw) { # get the psets
709 $needps = commitid2pset($needps);
710 # git-rev-list will also
711 # list commits merged in via earlier
712 # merges. we are only interested in commits
713 # from the branch we're looking at
714 if ($branch eq $needps->{branch}) {
715 push @need, $needps->{id};
716 }
717 }
718
719 # print "++++ Merges from $branch we want are....\n";
720 # print Dumper(\@need);
721
722 my $newparent;
723 while (my $needed_commit = pop @need) {
724 if ($have{$needed_commit}) {
725 $newparent = $needed_commit;
726 } else {
727 last; # break out of the while
728 }
729 }
730 if ($newparent) {
731 push @parents, $newparent;
732 }
733
734
735 } # end foreach branch
736
737 # prune redundant parents
738 my %parents;
739 foreach my $p (@parents) {
740 $parents{$p} = 1;
741 }
742 foreach my $p (@parents) {
743 next unless exists $psets{$p}{merges};
744 next unless ref $psets{$p}{merges};
745 my @merges = @{$psets{$p}{merges}};
746 foreach my $merge (@merges) {
747 if ($parents{$merge}) {
748 delete $parents{$merge};
749 }
750 }
751 }
752 @parents = keys %parents;
753 @parents = map { " -p " . ptag($_) } @parents;
754 return @parents;
755}
756
757sub git_rev_parse {
758 my $name = shift;
759 my $val = `git-rev-parse $name`;
760 die "Error: git-rev-parse $name" if $?;
761 chomp $val;
762 return $val;
763}
764
765# resolve a SHA1 to a known patchset
766sub commitid2pset {
767 my $commitid = shift;
768 chomp $commitid;
769 my $name = $rptags{$commitid}
770 || die "Cannot find reverse tag mapping for $commitid";
771 # the keys in %rptag are slightly munged; unmunge
772 # reconvert the 3rd '--' sequence from the end
773 # into a slash
774 $name = reverse $name;
775 $name =~ s!^(.+?--.+?--.+?--.+?)--(.+)$!$1/$2!;
776 $name = reverse $name;
777 my $ps = $psets{$name}
778 || (print Dumper(sort keys %psets)) && die "Cannot find patchset for $name";
779 return $ps;
780}