3 # This tool is copyright (c) 2005, Martin Langhoff.
4 # It is released under the Gnu Public License, version 2.
6 # The basic idea is to walk the output of tla abrowse,
7 # fetch the changesets and apply them.
12 git-archimport [ -h ] [ -v ] [ -o ] [ -a ] [ -f ] [ -T ]
13 [ -D depth] [ -t tempdir ] <archive>/<branch> [ <archive>/<branch> ]
15 Imports a project from one or more Arch repositories. It will follow branches
16 and repositories within the namespaces defined by the <archive/branch>
17 parameters suppplied. If it cannot find the remote branch a merge comes from
18 it will just import it as a regular commit. If it can find it, it will mark it
19 as a merge whenever possible.
21 See man (1) git-archimport for more details.
25 - create tag objects instead of ref tags
26 - audit shell-escaping of filenames
27 - hide our private tags somewhere smarter
28 - find a way to make "cat *patches | patch" safe even when patchfiles are missing newlines
29 - sort and apply patches by graphing ancestry relations instead of just
30 relying in dates supplied in the changeset itself.
31 tla ancestry-graph -m could be helpful here...
35 Add print in front of the shell commands invoked via backticks.
39 There are several places where Arch and git terminology are intermixed
40 and potentially confused.
42 The notion of a "branch" in git is approximately equivalent to
43 a "archive/category--branch--version" in Arch. Also, it should be noted
44 that the "--branch" portion of "archive/category--branch--version" is really
45 optional in Arch although not many people (nor tools!) seem to know this.
46 This means that "archive/category--version" is also a valid "branch"
49 We always refer to Arch names by their fully qualified variant (which
50 means the "archive" name is prefixed.
52 For people unfamiliar with Arch, an "archive" is the term for "repository",
53 and can contain multiple, unrelated branches.
60 use File
::Temp
qw(tempdir);
61 use File
::Path
qw(mkpath rmtree);
62 use File
::Basename
qw(basename dirname);
63 use Data
::Dumper qw
/ Dumper /;
66 $SIG{'PIPE'}="IGNORE";
69 my $git_dir = $ENV{"GIT_DIR"} || ".git";
70 $ENV{"GIT_DIR"} = $git_dir;
71 my $ptag_dir = "$git_dir/archimport/tags";
73 our($opt_h,$opt_f,$opt_v,$opt_T,$opt_t,$opt_D,$opt_a,$opt_o);
77 Usage: ${\basename $0} # fetch/update GIT from Arch
78 [ -h ] [ -v ] [ -o ] [ -a ] [ -f ] [ -T ] [ -D depth ] [ -t tempdir ]
79 repository/arch-branch [ repository/arch-branch] ...
84 getopts
("fThvat:D:") or usage
();
87 @ARGV >= 1 or usage
();
89 # values associated with keys:
90 # =1 - Arch version / git 'branch' detected via abrowse on a limit
91 # >1 - Arch version / git 'branch' of an auxilliary branch we've merged
92 my %arch_branches = map { $_ => 1 } @ARGV;
94 $ENV{'TMPDIR'} = $opt_t if $opt_t; # $ENV{TMPDIR} will affect tempdir() calls:
95 my $tmp = tempdir
('git-archimport-XXXXXX', TMPDIR
=> 1, CLEANUP
=> 1);
96 $opt_v && print "+ Using $tmp as temporary directory\n";
98 my %reachable = (); # Arch repositories we can access
99 my %unreachable = (); # Arch repositories we can't access :<
100 my @psets = (); # the collection
101 my %psets = (); # the collection, by name
102 my %stats = ( # Track which strategy we used to import:
103 get_tag
=> 0, replay
=> 0, get_new
=> 0, get_delta
=> 0,
104 simple_changeset
=> 0, import_or_tag
=> 0
107 my %rptags = (); # my reverse private tags
108 # to map a SHA1 to a commitid
109 my $TLA = $ENV{'ARCH_CLIENT'} || 'tla';
113 while (my ($limit, $level) = each %arch_branches) {
114 next unless $level == $stage;
116 open ABROWSE
, "$TLA abrowse -fkD --merges $limit |"
117 or die "Problems with tla abrowse: $!";
119 my %ps = (); # the current one
125 # first record padded w 8 spaces
127 my ($id, $type) = split(m/\s+/, $_, 2);
130 # store the record we just captured
131 if (%ps && !exists $psets{ $ps{id
} }) {
132 %last_ps = %ps; # break references
133 push (@psets, \
%last_ps);
134 $psets{ $last_ps{id
} } = \
%last_ps;
137 my $branch = extract_versionname
($id);
138 %ps = ( id
=> $id, branch
=> $branch );
139 if (%last_ps && ($last_ps{branch
} eq $branch)) {
140 $ps{parent_id
} = $last_ps{id
};
143 $arch_branches{$branch} = 1;
146 # deal with types (should work with baz or tla):
147 if ($type =~ m/\(.*changeset\)/) {
149 } elsif ($type =~ /\(.*import\)/) {
151 } elsif ($type =~ m/\(tag.*?(\S+\@\S+).*?\)/) {
153 # read which revision we've tagged when we parse the log
156 warn "Unknown type $type";
159 $arch_branches{$branch} = 1;
161 } elsif (s/^\s{10}//) {
162 # 10 leading spaces or more
163 # indicate commit metadata
166 if ($lastseen eq 'id' && m/^(\d{4}-\d\d-\d\d \d\d:\d\d:\d\d)/){
169 } elsif ($_ eq 'merges in:') {
171 $lastseen = 'merges';
172 } elsif ($lastseen eq 'merges' && s/^\s{2}//) {
174 push (@
{$ps{merges
}}, $id);
176 # aggressive branch finding:
178 my $branch = extract_versionname
($id);
179 my $repo = extract_reponame
($branch);
181 if (archive_reachable
($repo) &&
182 !defined $arch_branches{$branch}) {
183 $arch_branches{$branch} = $stage + 1;
187 warn "more metadata after merges!?: $_\n" unless /^\s*$/;
192 if (%ps && !exists $psets{ $ps{id
} }) {
193 my %temp = %ps; # break references
194 if (@psets && $psets[$#psets]{branch
} eq $ps{branch
}) {
195 $temp{parent_id
} = $psets[$#psets]{id
};
197 push (@psets, \
%temp);
198 $psets{ $temp{id
} } = \
%temp;
201 close ABROWSE
or die "$TLA abrowse failed on $limit\n";
203 } # end foreach $root
208 while ($depth <= $opt_D) {
213 ## Order patches by time
214 # FIXME see if we can find a more optimal way to do this by graphing
215 # the ancestry data and walking it, that way we won't have to rely on
216 # client-supplied dates
217 @psets = sort {$a->{date
}.$b->{id
} cmp $b->{date
}.$b->{id
}} @psets;
219 #print Dumper \@psets;
222 ## TODO cleanup irrelevant patches
223 ## and put an initial import
226 unless (-d
$git_dir) { # initial import
227 if ($psets[0]{type
} eq 'i' || $psets[0]{type
} eq 't') {
228 print "Starting import from $psets[0]{id}\n";
233 die "Need to start from an import or a tag -- cannot use $psets[0]{id}";
235 } else { # progressing an import
237 opendir(DIR
, $ptag_dir)
238 || die "can't opendir: $!";
239 while (my $file = readdir(DIR
)) {
240 # skip non-interesting-files
241 next unless -f
"$ptag_dir/$file";
243 # convert first '--' to '/' from old git-archimport to use
244 # as an archivename/c--b--v private tag
248 print STDERR
"converting old tag $oldfile to $file\n";
249 rename("$ptag_dir/$oldfile", "$ptag_dir/$file") or die $!;
251 my $sha = ptag
($file);
253 $rptags{$sha} = $file;
259 # extract the Arch repository name (Arch "archive" in Arch-speak)
260 sub extract_reponame
{
261 my $fq_cvbr = shift; # archivename/[[[[category]branch]version]revision]
262 return (split(/\//, $fq_cvbr))[0];
265 sub extract_versionname
{
267 $name =~ s/--(?:patch|version(?:fix)?|base)-\d+$//;
271 # convert a fully-qualified revision or version to a unique dirname:
272 # normalperson@yhbt.net-05/mpd--uclinux--1--patch-2
273 # becomes: normalperson@yhbt.net-05,mpd--uclinux--1
275 # the git notion of a branch is closer to
276 # archive/category--branch--version than archive/category--branch, so we
277 # use this to convert to git branch names.
278 # Also, keep archive names but replace '/' with ',' since it won't require
279 # subdirectories, and is safer than swapping '--' which could confuse
280 # reverse-mapping when dealing with bastard branches that
281 # are just archive/category--version (no --branch)
283 my $revision = shift;
284 my $name = extract_versionname
($revision);
289 # old versions of git-archimport just use the <category--branch> part:
290 sub old_style_branchname
{
292 my $ret = safe_pipe_capture
($TLA,'parse-package-name','-p',$id);
297 *git_branchname
= $opt_o ?
*old_style_branchname
: *tree_dirname
;
299 sub process_patchset_accurate
{
302 # switch to that branch if we're not already in that branch:
303 if (-e
"$git_dir/refs/heads/$ps->{branch}") {
304 system('git-checkout','-f',$ps->{branch
}) == 0 or die "$! $?\n";
306 # remove any old stuff that got leftover:
307 my $rm = safe_pipe_capture
('git-ls-files','--others','-z');
308 rmtree
(split(/\0/,$rm)) if $rm;
311 # Apply the import/changeset/merge into the working tree
312 my $dir = sync_to_ps
($ps);
313 # read the new log entry:
314 my @commitlog = safe_pipe_capture
($TLA,'cat-log','-d',$dir,$ps->{id
});
315 die "Error in cat-log: $!" if $?
;
318 # grab variables we want from the log, new fields get added to $ps:
319 # (author, date, email, summary, message body ...)
320 parselog
($ps, \
@commitlog);
322 if ($ps->{id
} =~ /--base-0$/ && $ps->{id
} ne $psets[0]{id
}) {
323 # this should work when importing continuations
324 if ($ps->{tag
} && (my $branchpoint = eval { ptag
($ps->{tag
}) })) {
326 # find where we are supposed to branch from
327 system('git-checkout','-f','-b',$ps->{branch
},
328 $branchpoint) == 0 or die "$! $?\n";
330 # remove any old stuff that got leftover:
331 my $rm = safe_pipe_capture
('git-ls-files','--others','-z');
332 rmtree
(split(/\0/,$rm)) if $rm;
334 # If we trust Arch with the fact that this is just
335 # a tag, and it does not affect the state of the tree
336 # then we just tag and move on
337 tag
($ps->{id
}, $branchpoint);
338 ptag
($ps->{id
}, $branchpoint);
339 print " * Tagged $ps->{id} at $branchpoint\n";
342 warn "Tagging from unknown id unsupported\n" if $ps->{tag
};
344 # allow multiple bases/imports here since Arch supports cherry-picks
345 # from unrelated trees
348 # update the index with all the changes we got
349 system('git-ls-files --others -z | '.
350 'git-update-index --add -z --stdin') == 0 or die "$! $?\n";
351 system('git-ls-files --deleted -z | '.
352 'git-update-index --remove -z --stdin') == 0 or die "$! $?\n";
353 system('git-ls-files -z | '.
354 'git-update-index -z --stdin') == 0 or die "$! $?\n";
358 # the native changeset processing strategy. This is very fast, but
359 # does not handle permissions or any renames involving directories
360 sub process_patchset_fast
{
363 # create the branch if needed
365 if ($ps->{type
} eq 'i' && !$import) {
366 die "Should not have more than one 'Initial import' per GIT import: $ps->{id}";
369 unless ($import) { # skip for import
370 if ( -e
"$git_dir/refs/heads/$ps->{branch}") {
371 # we know about this branch
372 system('git-checkout',$ps->{branch
});
374 # new branch! we need to verify a few things
375 die "Branch on a non-tag!" unless $ps->{type
} eq 't';
376 my $branchpoint = ptag
($ps->{tag
});
377 die "Tagging from unknown id unsupported: $ps->{tag}"
380 # find where we are supposed to branch from
381 system('git-checkout','-b',$ps->{branch
},$branchpoint);
383 # If we trust Arch with the fact that this is just
384 # a tag, and it does not affect the state of the tree
385 # then we just tag and move on
386 tag
($ps->{id
}, $branchpoint);
387 ptag
($ps->{id
}, $branchpoint);
388 print " * Tagged $ps->{id} at $branchpoint\n";
395 # Apply the import/changeset/merge into the working tree
397 if ($ps->{type
} eq 'i' || $ps->{type
} eq 't') {
398 apply_import
($ps) or die $!;
399 $stats{import_or_tag
}++;
401 } elsif ($ps->{type
} eq 's') {
403 $stats{simple_changeset
}++;
407 # prepare update git's index, based on what arch knows
408 # about the pset, resolve parents, etc
411 my @commitlog = safe_pipe_capture
($TLA,'cat-archive-log',$ps->{id
});
412 die "Error in cat-archive-log: $!" if $?
;
414 parselog
($ps,\
@commitlog);
416 # imports don't give us good info
417 # on added files. Shame on them
418 if ($ps->{type
} eq 'i' || $ps->{type
} eq 't') {
419 system('git-ls-files --others -z | '.
420 'git-update-index --add -z --stdin') == 0 or die "$! $?\n";
421 system('git-ls-files --deleted -z | '.
422 'git-update-index --remove -z --stdin') == 0 or die "$! $?\n";
425 # TODO: handle removed_directories and renamed_directories:
427 if (my $add = $ps->{new_files
}) {
429 my @slice = splice(@
$add, 0, 100);
430 system('git-update-index','--add','--',@slice) == 0 or
431 die "Error in git-update-index --add: $! $?\n";
435 if (my $del = $ps->{removed_files
}) {
438 my @slice = splice(@
$del, 0, 100);
439 system('git-update-index','--remove','--',@slice) == 0 or
440 die "Error in git-update-index --remove: $! $?\n";
444 if (my $ren = $ps->{renamed_files
}) { # renamed
446 die "Odd number of entries in rename!?";
450 my $from = shift @
$ren;
451 my $to = shift @
$ren;
453 unless (-d dirname
($to)) {
454 mkpath
(dirname
($to)); # will die on err
456 # print "moving $from $to";
457 rename($from, $to) or die "Error renaming '$from' '$to': $!\n";
458 system('git-update-index','--remove','--',$from) == 0 or
459 die "Error in git-update-index --remove: $! $?\n";
460 system('git-update-index','--add','--',$to) == 0 or
461 die "Error in git-update-index --add: $! $?\n";
465 if (my $mod = $ps->{modified_files
}) {
467 my @slice = splice(@
$mod, 0, 100);
468 system('git-update-index','--',@slice) == 0 or
469 die "Error in git-update-index: $! $?\n";
472 return 1; # we successfully applied the changeset
476 print "Will import patchsets using the fast strategy\n",
477 "Renamed directories and permission changes will be missed\n";
478 *process_patchset
= *process_patchset_fast
;
480 print "Using the default (accurate) import strategy.\n",
481 "Things may be a bit slow\n";
482 *process_patchset
= *process_patchset_accurate
;
485 foreach my $ps (@psets) {
487 $ps->{branch
} = git_branchname
($ps->{id
});
490 # ensure we have a clean state
492 if (my $dirty = `git-diff-files`) {
493 die "Unclean tree when about to process $ps->{id} " .
494 " - did we fail to commit cleanly before?\n$dirty";
499 # skip commits already in repo
501 if (ptag
($ps->{id
})) {
502 $opt_v && print " * Skipping already imported: $ps->{id}\n";
506 print " * Starting to work on $ps->{id}\n";
508 process_patchset
($ps) or next;
510 # warn "errors when running git-update-index! $!";
511 my $tree = `git-write-tree`;
512 die "cannot write tree $!" if $?
;
519 if ( -e
"$git_dir/refs/heads/$ps->{branch}") {
520 if (open HEAD
, "<","$git_dir/refs/heads/$ps->{branch}") {
526 if ($ps->{type
} eq 's') {
527 warn "Could not find the right head for the branch $ps->{branch}";
533 push @par, find_parents
($ps);
537 # Commit, tag and clean state
540 $ENV{GIT_AUTHOR_NAME
} = $ps->{author
};
541 $ENV{GIT_AUTHOR_EMAIL
} = $ps->{email
};
542 $ENV{GIT_AUTHOR_DATE
} = $ps->{date
};
543 $ENV{GIT_COMMITTER_NAME
} = $ps->{author
};
544 $ENV{GIT_COMMITTER_EMAIL
} = $ps->{email
};
545 $ENV{GIT_COMMITTER_DATE
} = $ps->{date
};
547 my $pid = open2
(*READER
, *WRITER
,'git-commit-tree',$tree,@par)
549 print WRITER
$ps->{summary
},"\n";
550 print WRITER
$ps->{message
},"\n";
552 # make it easy to backtrack and figure out which Arch revision this was:
553 print WRITER
'git-archimport-id: ',$ps->{id
},"\n";
556 my $commitid = <READER
>; # read
559 waitpid $pid,0; # close;
561 if (length $commitid != 40) {
562 die "Something went wrong with the commit! $! $commitid";
567 open HEAD
, ">","$git_dir/refs/heads/$ps->{branch}";
568 print HEAD
$commitid;
570 system('git-update-ref', 'HEAD', "$ps->{branch}");
573 ptag
($ps->{id
}, $commitid); # private tag
574 if ($opt_T || $ps->{type
} eq 't' || $ps->{type
} eq 'i') {
575 tag
($ps->{id
}, $commitid);
577 print " * Committed $ps->{id}\n";
578 print " + tree $tree\n";
579 print " + commit $commitid\n";
580 $opt_v && print " + commit date is $ps->{date} \n";
581 $opt_v && print " + parents: ",join(' ',@par),"\n";
585 foreach (sort keys %stats) {
586 print" $_: $stats{$_}\n";
591 # used by the accurate strategy:
594 my $tree_dir = $tmp.'/'.tree_dirname
($ps->{id
});
596 $opt_v && print "sync_to_ps($ps->{id}) method: ";
599 if ($ps->{type
} eq 't') {
600 $opt_v && print "get (tag)\n";
601 # looks like a tag-only or (worse,) a mixed tags/changeset branch,
602 # can't rely on replay to work correctly on these
604 safe_pipe_capture
($TLA,'get','--no-pristine',$ps->{id
},$tree_dir);
607 my $tree_id = arch_tree_id
($tree_dir);
608 if ($ps->{parent_id
} && ($ps->{parent_id
} eq $tree_id)) {
609 # the common case (hopefully)
610 $opt_v && print "replay\n";
611 safe_pipe_capture
($TLA,'replay','-d',$tree_dir,$ps->{id
});
614 # getting one tree is usually faster than getting two trees
615 # and applying the delta ...
617 $opt_v && print "apply-delta\n";
618 safe_pipe_capture
($TLA,'get','--no-pristine',
619 $ps->{id
},$tree_dir);
625 $opt_v && print "get (new tree)\n";
626 safe_pipe_capture
($TLA,'get','--no-pristine',$ps->{id
},$tree_dir);
630 # added -I flag to rsync since we're going to fast! AIEEEEE!!!!
631 system('rsync','-aI','--delete','--exclude',$git_dir,
632 # '--exclude','.arch-inventory',
633 '--exclude','.arch-ids','--exclude','{arch}',
634 '--exclude','+*','--exclude',',*',
635 "$tree_dir/",'./') == 0 or die "Cannot rsync $tree_dir: $! $?";
641 my $bname = git_branchname
($ps->{id
});
645 safe_pipe_capture
($TLA,'get','-s','--no-pristine',$ps->{id
},"$tmp/import");
646 die "Cannot get import: $!" if $?
;
647 system('rsync','-aI','--delete', '--exclude',$git_dir,
648 '--exclude','.arch-ids','--exclude','{arch}',
649 "$tmp/import/", './');
650 die "Cannot rsync import:$!" if $?
;
652 rmtree
("$tmp/import");
653 die "Cannot remove tempdir: $!" if $?
;
665 safe_pipe_capture
($TLA,'get-changeset',$ps->{id
},"$tmp/changeset");
666 die "Cannot get changeset: $!" if $?
;
669 if (`find $tmp/changeset/patches -type f -name '*.patch'`) {
670 # this can be sped up considerably by doing
671 # (find | xargs cat) | patch
672 # but that cna get mucked up by patches
673 # with missing trailing newlines or the standard
674 # 'missing newline' flag in the patch - possibly
675 # produced with an old/buggy diff.
676 # slow and safe, we invoke patch once per patchfile
677 `find $tmp/changeset/patches -type f -name '*.patch' -print0 | grep -zv '{arch}' | xargs -iFILE -0 --no-run-if-empty patch -p1 --forward -iFILE`;
678 die "Problem applying patches! $!" if $?
;
681 # apply changed binary files
682 if (my @modified = `find $tmp/changeset/patches -type f -name '*.modified'`) {
683 foreach my $mod (@modified) {
686 $orig =~ s/\.modified$//; # lazy
687 $orig =~ s!^\Q$tmp\E/changeset/patches/!!;
688 #print "rsync -p '$mod' '$orig'";
689 system('rsync','-p',$mod,"./$orig");
690 die "Problem applying binary changes! $!" if $?
;
695 system('rsync','-aI','--exclude',$git_dir,
696 '--exclude','.arch-ids',
697 '--exclude', '{arch}',
698 "$tmp/changeset/new-files-archive/",'./');
700 # deleted files are hinted from the commitlog processing
702 rmtree
("$tmp/changeset");
707 # notes: *-files/-directories keys cannot have spaces, they're always
708 # pika-escaped. Everything after the first newline
709 # A log entry looks like:
710 # Revision: moodle-org--moodle--1.3.3--patch-15
711 # Archive: arch-eduforge@catalyst.net.nz--2004
712 # Creator: Penny Leach <penny@catalyst.net.nz>
713 # Date: Wed May 25 14:15:34 NZST 2005
714 # Standard-date: 2005-05-25 02:15:34 GMT
715 # New-files: lang/de/.arch-ids/block_glossary_random.php.id
716 # lang/de/.arch-ids/block_html.php.id
717 # New-directories: lang/de/help/questionnaire
718 # lang/de/help/questionnaire/.arch-ids
719 # Renamed-files: .arch-ids/db_sears.sql.id db/.arch-ids/db_sears.sql.id
720 # db_sears.sql db/db_sears.sql
721 # Removed-files: lang/be/docs/.arch-ids/release.html.id
722 # lang/be/docs/.arch-ids/releaseold.html.id
723 # Modified-files: admin/cron.php admin/delete.php
724 # admin/editor.html backup/lib.php backup/restore.php
725 # New-patches: arch-eduforge@catalyst.net.nz--2004/moodle-org--moodle--1.3.3--patch-15
726 # Summary: Updating to latest from MOODLE_14_STABLE (1.4.5+)
727 # summary can be multiline with a leading space just like the above fields
730 # Updating yadda tadda tadda madda
735 # headers we want that contain filenames:
740 renamed_directories
=> 1,
742 removed_directories
=> 1,
746 while ($_ = shift @
$log) {
747 if (/^Continuation-of:\s*(.*)/) {
750 } elsif (/^Summary:\s*(.*)$/ ) {
751 # summary can be multiline as long as it has a leading space
752 $ps->{summary
} = [ $1 ];
754 } elsif (/^Creator: (.*)\s*<([^\>]+)>/) {
758 # any *-files or *-directories can be read here:
759 } elsif (/^([A-Z][a-z\-]+):\s*(.*)$/) {
762 $key =~ tr/-/_/; # too lazy to quote :P
763 if ($want_headers{$key}) {
764 push @
{$ps->{$key}}, split(/\s+/, $val);
769 last; # remainder of @$log that didn't get shifted off is message
772 if ($key eq 'summary') {
773 push @
{$ps->{$key}}, $1;
774 } else { # files/directories:
775 push @
{$ps->{$key}}, split(/\s+/, $1);
784 $ps->{summary
} = join("\n",@
{$ps->{summary
}})."\n";
785 $ps->{message
} = join("\n",@
$log);
787 # skip Arch control files, unescape pika-escaped files
788 foreach my $k (keys %want_headers) {
789 next unless (defined $ps->{$k});
791 foreach my $t (@
{$ps->{$k}}) {
792 next unless length ($t);
793 next if $t =~ m!\{arch\}/!;
794 next if $t =~ m!\.arch-ids/!;
795 # should we skip this?
796 next if $t =~ m!\.arch-inventory$!;
797 # tla cat-archive-log will give us filenames with spaces as file\(sp)name - why?
798 # we can assume that any filename with \ indicates some pika escaping that we want to get rid of.
800 $t = (safe_pipe_capture
($TLA,'escape','--unescaped',$t))[0];
810 my ($tag, $commit) = @_;
815 # don't use subdirs for tags yet, it could screw up other porcelains
820 open(C
,">","$git_dir/refs/tags/$tag")
821 or die "Cannot create tag $tag: $!\n";
823 or die "Cannot write tag $tag: $!\n";
825 or die "Cannot write tag $tag: $!\n";
826 print " * Created tag '$tag' on '$commit'\n" if $opt_v;
828 open(C
,"<","$git_dir/refs/tags/$tag")
829 or die "Cannot read tag $tag: $!\n";
832 die "Error reading tag $tag: $!\n" unless length $commit == 40;
834 or die "Cannot read tag $tag: $!\n";
839 # write/read a private tag
840 # reads fail softly if the tag isn't there
842 my ($tag, $commit) = @_;
844 # don't use subdirs for tags yet, it could screw up other porcelains
847 my $tag_file = "$ptag_dir/$tag";
848 my $tag_branch_dir = dirname
($tag_file);
849 mkpath
($tag_branch_dir) unless (-d
$tag_branch_dir);
851 if ($commit) { # write
852 open(C
,">",$tag_file)
853 or die "Cannot create tag $tag: $!\n";
855 or die "Cannot write tag $tag: $!\n";
857 or die "Cannot write tag $tag: $!\n";
858 $rptags{$commit} = $tag
859 unless $tag =~ m/--base-0$/;
861 # if the tag isn't there, return 0
862 unless ( -s
$tag_file) {
865 open(C
,"<",$tag_file)
866 or die "Cannot read tag $tag: $!\n";
869 die "Error reading tag $tag: $!\n" unless length $commit == 40;
871 or die "Cannot read tag $tag: $!\n";
872 unless (defined $rptags{$commit}) {
873 $rptags{$commit} = $tag;
881 # Identify what branches are merging into me
882 # and whether we are fully merged
883 # git-merge-base <headsha> <headsha> should tell
884 # me what the base of the merge should be
888 my %branches; # holds an arrayref per branch
889 # the arrayref contains a list of
890 # merged patches between the base
891 # of the merge and the current head
893 my @parents; # parents found for this commit
895 # simple loop to split the merges
897 foreach my $merge (@
{$ps->{merges
}}) {
898 my $branch = git_branchname
($merge);
899 unless (defined $branches{$branch} ){
900 $branches{$branch} = [];
902 push @
{$branches{$branch}}, $merge;
906 # foreach branch find a merge base and walk it to the
907 # head where we are, collecting the merged patchsets that
908 # Arch has recorded. Keep that in @have
909 # Compare that with the commits on the other branch
910 # between merge-base and the tip of the branch (@need)
911 # and see if we have a series of consecutive patches
912 # starting from the merge base. The tip of the series
913 # of consecutive patches merged is our new parent for
916 foreach my $branch (keys %branches) {
918 # check that we actually know about the branch
919 next unless -e
"$git_dir/refs/heads/$branch";
921 my $mergebase = `git-merge-base $branch $ps->{branch}`;
923 # Don't die here, Arch supports one-way cherry-picking
924 # between branches with no common base (or any relationship
926 warn "Cannot find merge base for $branch and $ps->{branch}";
931 # now walk up to the mergepoint collecting what patches we have
932 my $branchtip = git_rev_parse
($ps->{branch
});
933 my @ancestors = `git-rev-list --merge-order $branchtip ^$mergebase`;
934 my %have; # collected merges this branch has
935 foreach my $merge (@
{$ps->{merges
}}) {
939 foreach my $par (@ancestors) {
940 $par = commitid2pset
($par);
941 if (defined $par->{merges
}) {
942 foreach my $merge (@
{$par->{merges
}}) {
943 $ancestorshave{$merge}=1;
947 # print "++++ Merges in $ps->{id} are....\n";
948 # my @have = sort keys %have; print Dumper(\@have);
950 # merge what we have with what ancestors have
951 %have = (%have, %ancestorshave);
953 # see what the remote branch has - these are the merges we
954 # will want to have in a consecutive series from the mergebase
955 my $otherbranchtip = git_rev_parse
($branch);
956 my @needraw = `git-rev-list --merge-order $otherbranchtip ^$mergebase`;
958 foreach my $needps (@needraw) { # get the psets
959 $needps = commitid2pset
($needps);
960 # git-rev-list will also
961 # list commits merged in via earlier
962 # merges. we are only interested in commits
963 # from the branch we're looking at
964 if ($branch eq $needps->{branch
}) {
965 push @need, $needps->{id
};
969 # print "++++ Merges from $branch we want are....\n";
970 # print Dumper(\@need);
973 while (my $needed_commit = pop @need) {
974 if ($have{$needed_commit}) {
975 $newparent = $needed_commit;
977 last; # break out of the while
981 push @parents, $newparent;
985 } # end foreach branch
987 # prune redundant parents
989 foreach my $p (@parents) {
992 foreach my $p (@parents) {
993 next unless exists $psets{$p}{merges
};
994 next unless ref $psets{$p}{merges
};
995 my @merges = @
{$psets{$p}{merges
}};
996 foreach my $merge (@merges) {
997 if ($parents{$merge}) {
998 delete $parents{$merge};
1004 foreach (keys %parents) {
1005 push @parents, '-p', ptag
($_);
1012 my $val = `git-rev-parse $name`;
1013 die "Error: git-rev-parse $name" if $?
;
1018 # resolve a SHA1 to a known patchset
1020 my $commitid = shift;
1022 my $name = $rptags{$commitid}
1023 || die "Cannot find reverse tag mapping for $commitid";
1025 my $ps = $psets{$name}
1026 || (print Dumper
(sort keys %psets)) && die "Cannot find patchset for $name";
1031 # an alterative to `command` that allows input to be passed as an array
1032 # to work around shell problems with weird characters in arguments
1033 sub safe_pipe_capture
{
1035 if (my $pid = open my $child, '-|') {
1036 @output = (<$child>);
1037 close $child or die join(' ',@_).": $! $?";
1039 exec(@_) or die "$! $?"; # exec() can fail the executable can't be found
1041 return wantarray ?
@output : join('',@output);
1044 # `tla logs -rf -d <dir> | head -n1` or `baz tree-id <dir>`
1047 chomp( my $ret = (safe_pipe_capture
($TLA,'logs','-rf','-d',$dir))[0] );
1051 sub archive_reachable
{
1052 my $archive = shift;
1053 return 1 if $reachable{$archive};
1054 return 0 if $unreachable{$archive};
1056 if (system "$TLA whereis-archive $archive >/dev/null") {
1057 if ($opt_a && (system($TLA,'register-archive',
1058 "http://mirrors.sourcecontrol.net/$archive") == 0)) {
1059 $reachable{$archive} = 1;
1062 print STDERR
"Archive is unreachable: $archive\n";
1063 $unreachable{$archive} = 1;
1066 $reachable{$archive} = 1;