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 10=head1 Invocation 11 12 git-archimport [ -h ] [ -v ] [ -T ] [ -t tempdir ] <archive>/<branch> [ <archive>/<branch> ] 13 14Imports a project from one or more Arch repositories. It will follow branches 15and repositories within the namespaces defined by the <archive/branch> 16parameters suppplied. If it cannot find the remote branch a merge comes from 17it will just import it as a regular commit. If it can find it, it will mark it 18as a merge whenever possible. 19 20See man (1) git-archimport for more details. 21 22=head1 TODO 23 24 - create tag objects instead of ref tags 25 - audit shell-escaping of filenames 26 - hide our private tags somewhere smarter 27 - find a way to make "cat *patches | patch" safe even when patchfiles are missing newlines 28 29=head1 Devel tricks 30 31Add print in front of the shell commands invoked via backticks. 32 33=head1 Devel Notes 34 35There are several places where Arch and git terminology are intermixed 36and potentially confused. 37 38The notion of a "branch" in git is approximately equivalent to 39a "archive/category--branch--version" in Arch. Also, it should be noted 40that the "--branch" portion of "archive/category--branch--version" is really 41optional in Arch although not many people (nor tools!) seem to know this. 42This means that "archive/category--version" is also a valid "branch" 43in git terms. 44 45We always refer to Arch names by their fully qualified variant (which 46means the "archive" name is prefixed. 47 48For people unfamiliar with Arch, an "archive" is the term for "repository", 49and can contain multiple, unrelated branches. 50 51=cut 52 53use strict; 54use warnings; 55use Getopt::Std; 56use File::Spec; 57use File::Temp qw(tempfile tempdir); 58use File::Path qw(mkpath rmtree); 59use File::Basename qw(basename dirname); 60use Time::Local; 61use IO::Socket; 62use IO::Pipe; 63use POSIX qw(strftime dup2); 64use Data::Dumper qw/ Dumper /; 65use IPC::Open2; 66 67$SIG{'PIPE'}="IGNORE"; 68$ENV{'TZ'}="UTC"; 69 70my$git_dir=$ENV{"GIT_DIR"} ||".git"; 71$ENV{"GIT_DIR"} =$git_dir; 72my$ptag_dir="$git_dir/archimport/tags"; 73 74our($opt_h,$opt_v,$opt_T,$opt_t,$opt_o); 75 76sub usage() { 77print STDERR <<END; 78Usage: ${\basename$0} # fetch/update GIT from Arch 79 [ -o ] [ -h ] [ -v ] [ -T ] [ -t tempdir ] 80 repository/arch-branch [ repository/arch-branch] ... 81END 82exit(1); 83} 84 85getopts("Thvt:")or usage(); 86usage if$opt_h; 87 88@ARGV>=1or usage(); 89my@arch_roots=@ARGV; 90 91my($tmpdir,$tmpdirname) = tempdir('git-archimport-XXXXXX', TMPDIR =>1, CLEANUP =>1); 92my$tmp=$opt_t||1; 93$tmp= tempdir('git-archimport-XXXXXX', TMPDIR =>1, CLEANUP =>1); 94$opt_v&&print"+ Using$tmpas temporary directory\n"; 95 96my@psets= ();# the collection 97my%psets= ();# the collection, by name 98 99my%rptags= ();# my reverse private tags 100# to map a SHA1 to a commitid 101my$TLA=$ENV{'ARCH_CLIENT'} ||'tla'; 102 103foreachmy$root(@arch_roots) { 104my($arepo,$abranch) =split(m!/!,$root); 105open ABROWSE,"tla abrowse -f -A$arepo--desc --merges$abranch|" 106or die"Problems with tla abrowse:$!"; 107 108my%ps= ();# the current one 109my$mode=''; 110my$lastseen=''; 111 112while(<ABROWSE>) { 113chomp; 114 115# first record padded w 8 spaces 116if(s/^\s{8}\b//) { 117 118# store the record we just captured 119if(%ps) { 120my%temp=%ps;# break references 121push(@psets, \%temp); 122$psets{$temp{id}} = \%temp; 123%ps= (); 124} 125 126my($id,$type) =split(m/\s{3}/,$_); 127$ps{id} =$id; 128$ps{repo} =$arepo; 129 130# deal with types 131if($type=~m/^\(simple changeset\)/) { 132$ps{type} ='s'; 133}elsif($typeeq'(initial import)') { 134$ps{type} ='i'; 135}elsif($type=~m/^\(tag revision of (.+)\)/) { 136$ps{type} ='t'; 137$ps{tag} =$1; 138}else{ 139warn"Unknown type$type"; 140} 141$lastseen='id'; 142} 143 144if(s/^\s{10}//) { 145# 10 leading spaces or more 146# indicate commit metadata 147 148# date & author 149if($lastseeneq'id'&&m/^\d{4}-\d{2}-\d{2}/) { 150 151my($date,$authoremail) =split(m/\s{2,}/,$_); 152$ps{date} =$date; 153$ps{date} =~s/\bGMT$//;# strip off trailign GMT 154if($ps{date} =~m/\b\w+$/) { 155warn'Arch dates not in GMT?! - imported dates will be wrong'; 156} 157 158$authoremail=~m/^(.+)\s(\S+)$/; 159$ps{author} =$1; 160$ps{email} =$2; 161 162$lastseen='date'; 163 164}elsif($lastseeneq'date') { 165# the only hint is position 166# subject is after date 167$ps{subj} =$_; 168$lastseen='subj'; 169 170}elsif($lastseeneq'subj'&&$_eq'merges in:') { 171$ps{merges} = []; 172$lastseen='merges'; 173 174}elsif($lastseeneq'merges'&&s/^\s{2}//) { 175push(@{$ps{merges}},$_); 176}else{ 177warn'more metadata after merges!?'; 178} 179 180} 181} 182 183if(%ps) { 184my%temp=%ps;# break references 185push(@psets, \%temp); 186$psets{$temp{id} } = \%temp; 187%ps= (); 188} 189close ABROWSE; 190}# end foreach $root 191 192## Order patches by time 193@psets=sort{$a->{date}.$b->{id}cmp$b->{date}.$b->{id}}@psets; 194 195#print Dumper \@psets; 196 197## 198## TODO cleanup irrelevant patches 199## and put an initial import 200## or a full tag 201my$import=0; 202unless(-d $git_dir) {# initial import 203if($psets[0]{type}eq'i'||$psets[0]{type}eq't') { 204print"Starting import from$psets[0]{id}\n"; 205`git-init-db`; 206die$!if$?; 207$import=1; 208}else{ 209die"Need to start from an import or a tag -- cannot use$psets[0]{id}"; 210} 211}else{# progressing an import 212# load the rptags 213opendir(DIR,"$git_dir/archimport/tags") 214||die"can't opendir:$!"; 215while(my$file=readdir(DIR)) { 216# skip non-interesting-files 217next unless-f "$ptag_dir/$file"; 218 219# convert first '--' to '/' from old git-archimport to use 220# as an archivename/c--b--v private tag 221if($file!~m!,!) { 222my$oldfile=$file; 223$file=~s!--!,!; 224print STDERR "converting old tag$oldfileto$file\n"; 225rename("$ptag_dir/$oldfile","$ptag_dir/$file")or die$!; 226} 227my$sha= ptag($file); 228chomp$sha; 229$rptags{$sha} =$file; 230} 231closedir DIR; 232} 233 234# process patchsets 235# extract the Arch repository name (Arch "archive" in Arch-speak) 236sub extract_reponame { 237my$fq_cvbr=shift;# archivename/[[[[category]branch]version]revision] 238return(split(/\//,$fq_cvbr))[0]; 239} 240 241sub extract_versionname { 242my$name=shift; 243$name=~s/--(?:patch|version(?:fix)?|base)-\d+$//; 244return$name; 245} 246 247# convert a fully-qualified revision or version to a unique dirname: 248# normalperson@yhbt.net-05/mpd--uclinux--1--patch-2 249# becomes: normalperson@yhbt.net-05,mpd--uclinux--1 250# 251# the git notion of a branch is closer to 252# archive/category--branch--version than archive/category--branch, so we 253# use this to convert to git branch names. 254# Also, keep archive names but replace '/' with ',' since it won't require 255# subdirectories, and is safer than swapping '--' which could confuse 256# reverse-mapping when dealing with bastard branches that 257# are just archive/category--version (no --branch) 258sub tree_dirname { 259my$revision=shift; 260my$name= extract_versionname($revision); 261$name=~ s#/#,#; 262return$name; 263} 264 265# old versions of git-archimport just use the <category--branch> part: 266sub old_style_branchname { 267my$id=shift; 268my$ret= safe_pipe_capture($TLA,'parse-package-name','-p',$id); 269chomp$ret; 270return$ret; 271} 272 273*git_branchname =$opt_o? *old_style_branchname : *tree_dirname; 274 275# process patchsets 276foreachmy$ps(@psets) { 277$ps->{branch} = git_branchname($ps->{id}); 278 279# 280# ensure we have a clean state 281# 282if(`git diff-files`) { 283die"Unclean tree when about to process$ps->{id} ". 284" - did we fail to commit cleanly before?"; 285} 286die$!if$?; 287 288# 289# skip commits already in repo 290# 291if(ptag($ps->{id})) { 292$opt_v&&print" * Skipping already imported:$ps->{id}\n"; 293next; 294} 295 296print" * Starting to work on$ps->{id}\n"; 297 298# 299# create the branch if needed 300# 301if($ps->{type}eq'i'&& !$import) { 302die"Should not have more than one 'Initial import' per GIT import:$ps->{id}"; 303} 304 305unless($import) {# skip for import 306if( -e "$git_dir/refs/heads/$ps->{branch}") { 307# we know about this branch 308system('git-checkout',$ps->{branch}); 309}else{ 310# new branch! we need to verify a few things 311die"Branch on a non-tag!"unless$ps->{type}eq't'; 312my$branchpoint= ptag($ps->{tag}); 313die"Tagging from unknown id unsupported:$ps->{tag}" 314unless$branchpoint; 315 316# find where we are supposed to branch from 317system('git-checkout','-b',$ps->{branch},$branchpoint); 318 319# If we trust Arch with the fact that this is just 320# a tag, and it does not affect the state of the tree 321# then we just tag and move on 322 tag($ps->{id},$branchpoint); 323 ptag($ps->{id},$branchpoint); 324print" * Tagged$ps->{id} at$branchpoint\n"; 325next; 326} 327die$!if$?; 328} 329 330# 331# Apply the import/changeset/merge into the working tree 332# 333if($ps->{type}eq'i'||$ps->{type}eq't') { 334 apply_import($ps)or die$!; 335$import=0; 336}elsif($ps->{type}eq's') { 337 apply_cset($ps); 338} 339 340# 341# prepare update git's index, based on what arch knows 342# about the pset, resolve parents, etc 343# 344my$tree; 345 346my$commitlog= safe_pipe_capture($TLA,'cat-archive-log',$ps->{id}); 347die"Error in cat-archive-log:$!"if$?; 348 349# parselog will git-add/rm files 350# and generally prepare things for the commit 351# NOTE: parselog will shell-quote filenames! 352my($sum,$msg,$add,$del,$mod,$ren) = parselog($commitlog); 353my$logmessage="$sum\n$msg"; 354 355 356# imports don't give us good info 357# on added files. Shame on them 358if($ps->{type}eq'i'||$ps->{type}eq't') { 359`find . -type f -print0 | grep -zv '^./$git_dir' | xargs -0 -l100 git-update-index --add`; 360`git-ls-files --deleted -z | xargs --no-run-if-empty -0 -l100 git-update-index --remove`; 361} 362 363if(@$add) { 364while(@$add) { 365my@slice=splice(@$add,0,100); 366my$slice=join(' ',@slice); 367`git-update-index --add$slice`; 368 die "Error in git-update-index --add:$!" if$?; 369 } 370 } 371 if (@$del) { 372 foreach my$file(@$del) { 373 unlink$fileor die "Problems deleting$file:$!"; 374 } 375 while (@$del) { 376 my@slice= splice(@$del, 0, 100); 377 my$slice= join(' ',@slice); 378 `git-update-index --remove $slice`; 379die"Error in git-update-index --remove:$!"if$?; 380} 381} 382if(@$ren) {# renamed 383if(@$ren%2) { 384die"Odd number of entries in rename!?"; 385} 386; 387while(@$ren) { 388my$from=pop@$ren; 389my$to=pop@$ren; 390 391unless(-d dirname($to)) { 392 mkpath(dirname($to));# will die on err 393} 394#print "moving $from $to"; 395`mv$from$to`; 396 die "Error renaming$from$to:$!" if$?; 397 `git-update-index --remove $from`; 398die"Error in git-update-index --remove:$!"if$?; 399`git-update-index --add$to`; 400 die "Error in git-update-index --add:$!" if$?; 401 } 402 403 } 404 if (@$mod) { # must be _after_ renames 405 while (@$mod) { 406 my@slice= splice(@$mod, 0, 100); 407 my$slice= join(' ',@slice); 408 `git-update-index $slice`; 409die"Error in git-update-index:$!"if$?; 410} 411} 412 413# warn "errors when running git-update-index! $!"; 414$tree=`git-write-tree`; 415die"cannot write tree$!"if$?; 416chomp$tree; 417 418 419# 420# Who's your daddy? 421# 422my@par; 423if( -e "$git_dir/refs/heads/$ps->{branch}") { 424if(open HEAD,"<","$git_dir/refs/heads/$ps->{branch}") { 425my$p= <HEAD>; 426close HEAD; 427chomp$p; 428push@par,'-p',$p; 429}else{ 430if($ps->{type}eq's') { 431warn"Could not find the right head for the branch$ps->{branch}"; 432} 433} 434} 435 436if($ps->{merges}) { 437push@par, find_parents($ps); 438} 439 440# 441# Commit, tag and clean state 442# 443$ENV{TZ} ='GMT'; 444$ENV{GIT_AUTHOR_NAME} =$ps->{author}; 445$ENV{GIT_AUTHOR_EMAIL} =$ps->{email}; 446$ENV{GIT_AUTHOR_DATE} =$ps->{date}; 447$ENV{GIT_COMMITTER_NAME} =$ps->{author}; 448$ENV{GIT_COMMITTER_EMAIL} =$ps->{email}; 449$ENV{GIT_COMMITTER_DATE} =$ps->{date}; 450 451my($pid,$commit_rh,$commit_wh); 452$commit_rh='commit_rh'; 453$commit_wh='commit_wh'; 454 455$pid= open2(*READER, *WRITER,'git-commit-tree',$tree,@par) 456or die$!; 457print WRITER $logmessage;# write 458close WRITER; 459my$commitid= <READER>;# read 460chomp$commitid; 461close READER; 462waitpid$pid,0;# close; 463 464if(length$commitid!=40) { 465die"Something went wrong with the commit!$!$commitid"; 466} 467# 468# Update the branch 469# 470open HEAD,">","$git_dir/refs/heads/$ps->{branch}"; 471print HEAD $commitid; 472close HEAD; 473system('git-update-ref','HEAD',"$ps->{branch}"); 474 475# tag accordingly 476 ptag($ps->{id},$commitid);# private tag 477if($opt_T||$ps->{type}eq't'||$ps->{type}eq'i') { 478 tag($ps->{id},$commitid); 479} 480print" * Committed$ps->{id}\n"; 481print" + tree$tree\n"; 482print" + commit$commitid\n"; 483$opt_v&&print" + commit date is$ps->{date}\n"; 484$opt_v&&print" + parents: ",join(' ',@par),"\n"; 485} 486 487sub apply_import { 488my$ps=shift; 489my$bname= git_branchname($ps->{id}); 490 491 mkpath($tmp); 492 493 safe_pipe_capture($TLA,'get','-s','--no-pristine',$ps->{id},"$tmp/import"); 494die"Cannot get import:$!"if$?; 495system('rsync','-aI','--delete','--exclude',$git_dir, 496'--exclude','.arch-ids','--exclude','{arch}', 497"$tmp/import/",'./'); 498die"Cannot rsync import:$!"if$?; 499 500 rmtree("$tmp/import"); 501die"Cannot remove tempdir:$!"if$?; 502 503 504return1; 505} 506 507sub apply_cset { 508my$ps=shift; 509 510 mkpath($tmp); 511 512# get the changeset 513 safe_pipe_capture($TLA,'get-changeset',$ps->{id},"$tmp/changeset"); 514die"Cannot get changeset:$!"if$?; 515 516# apply patches 517if(`find$tmp/changeset/patches-type f -name '*.patch'`) { 518# this can be sped up considerably by doing 519# (find | xargs cat) | patch 520# but that cna get mucked up by patches 521# with missing trailing newlines or the standard 522# 'missing newline' flag in the patch - possibly 523# produced with an old/buggy diff. 524# slow and safe, we invoke patch once per patchfile 525`find$tmp/changeset/patches-type f -name '*.patch' -print0 | grep -zv '{arch}' | xargs -iFILE -0 --no-run-if-empty patch -p1 --forward -iFILE`; 526die"Problem applying patches!$!"if$?; 527} 528 529# apply changed binary files 530if(my@modified=`find$tmp/changeset/patches-type f -name '*.modified'`) { 531foreachmy$mod(@modified) { 532chomp$mod; 533my$orig=$mod; 534$orig=~s/\.modified$//;# lazy 535$orig=~s!^\Q$tmp\E/changeset/patches/!!; 536#print "rsync -p '$mod' '$orig'"; 537system('rsync','-p',$mod,"./$orig"); 538die"Problem applying binary changes!$!"if$?; 539} 540} 541 542# bring in new files 543system('rsync','-aI','--exclude',$git_dir, 544'--exclude','.arch-ids', 545'--exclude','{arch}', 546"$tmp/changeset/new-files-archive/",'./'); 547 548# deleted files are hinted from the commitlog processing 549 550 rmtree("$tmp/changeset"); 551} 552 553 554# =for reference 555# A log entry looks like 556# Revision: moodle-org--moodle--1.3.3--patch-15 557# Archive: arch-eduforge@catalyst.net.nz--2004 558# Creator: Penny Leach <penny@catalyst.net.nz> 559# Date: Wed May 25 14:15:34 NZST 2005 560# Standard-date: 2005-05-25 02:15:34 GMT 561# New-files: lang/de/.arch-ids/block_glossary_random.php.id 562# lang/de/.arch-ids/block_html.php.id 563# New-directories: lang/de/help/questionnaire 564# lang/de/help/questionnaire/.arch-ids 565# Renamed-files: .arch-ids/db_sears.sql.id db/.arch-ids/db_sears.sql.id 566# db_sears.sql db/db_sears.sql 567# Removed-files: lang/be/docs/.arch-ids/release.html.id 568# lang/be/docs/.arch-ids/releaseold.html.id 569# Modified-files: admin/cron.php admin/delete.php 570# admin/editor.html backup/lib.php backup/restore.php 571# New-patches: arch-eduforge@catalyst.net.nz--2004/moodle-org--moodle--1.3.3--patch-15 572# Summary: Updating to latest from MOODLE_14_STABLE (1.4.5+) 573# Keywords: 574# 575# Updating yadda tadda tadda madda 576sub parselog { 577my$log=shift; 578#print $log; 579 580my(@add,@del,@mod,@ren,@kw,$sum,$msg); 581 582if($log=~m/(?:\n|^)New-files:(.*?)(?=\n\w)/s) { 583my$files=$1; 584@add=split(m/\s+/s,$files); 585} 586 587if($log=~m/(?:\n|^)Removed-files:(.*?)(?=\n\w)/s) { 588my$files=$1; 589@del=split(m/\s+/s,$files); 590} 591 592if($log=~m/(?:\n|^)Modified-files:(.*?)(?=\n\w)/s) { 593my$files=$1; 594@mod=split(m/\s+/s,$files); 595} 596 597if($log=~m/(?:\n|^)Renamed-files:(.*?)(?=\n\w)/s) { 598my$files=$1; 599@ren=split(m/\s+/s,$files); 600} 601 602$sum=''; 603if($log=~m/^Summary:(.+?)$/m) { 604$sum=$1; 605$sum=~s/^\s+//; 606$sum=~s/\s+$//; 607} 608 609$msg=''; 610if($log=~m/\n\n(.+)$/s) { 611$msg=$1; 612$msg=~s/^\s+//; 613$msg=~s/\s+$//; 614} 615 616 617# cleanup the arrays 618foreachmy$ref( (\@add, \@del, \@mod, \@ren) ) { 619my@tmp= (); 620while(my$t=pop@$ref) { 621next unlesslength($t); 622next if$t=~m!\{arch\}/!; 623next if$t=~m!\.arch-ids/!; 624next if$t=~m!\.arch-inventory$!; 625# tla cat-archive-log will give us filenames with spaces as file\(sp)name - why? 626# we can assume that any filename with \ indicates some pika escaping that we want to get rid of. 627if($t=~/\\/){ 628$t= (safe_pipe_capture($TLA,'escape','--unescaped',$t))[0]; 629} 630push(@tmp,$t); 631} 632@$ref=@tmp; 633} 634 635#print Dumper [$sum, $msg, \@add, \@del, \@mod, \@ren]; 636return($sum,$msg, \@add, \@del, \@mod, \@ren); 637} 638 639# write/read a tag 640sub tag { 641my($tag,$commit) =@_; 642 643if($opt_o) { 644$tag=~ s|/|--|g; 645}else{ 646# don't use subdirs for tags yet, it could screw up other porcelains 647$tag=~ s|/|,|g; 648} 649 650if($commit) { 651open(C,">","$git_dir/refs/tags/$tag") 652or die"Cannot create tag$tag:$!\n"; 653print C "$commit\n" 654or die"Cannot write tag$tag:$!\n"; 655close(C) 656or die"Cannot write tag$tag:$!\n"; 657print" * Created tag '$tag' on '$commit'\n"if$opt_v; 658}else{# read 659open(C,"<","$git_dir/refs/tags/$tag") 660or die"Cannot read tag$tag:$!\n"; 661$commit= <C>; 662chomp$commit; 663die"Error reading tag$tag:$!\n"unlesslength$commit==40; 664close(C) 665or die"Cannot read tag$tag:$!\n"; 666return$commit; 667} 668} 669 670# write/read a private tag 671# reads fail softly if the tag isn't there 672sub ptag { 673my($tag,$commit) =@_; 674 675# don't use subdirs for tags yet, it could screw up other porcelains 676$tag=~ s|/|,|g; 677 678my$tag_file="$ptag_dir/$tag"; 679my$tag_branch_dir= dirname($tag_file); 680 mkpath($tag_branch_dir)unless(-d $tag_branch_dir); 681 682if($commit) {# write 683open(C,">",$tag_file) 684or die"Cannot create tag$tag:$!\n"; 685print C "$commit\n" 686or die"Cannot write tag$tag:$!\n"; 687close(C) 688or die"Cannot write tag$tag:$!\n"; 689$rptags{$commit} =$tag 690unless$tag=~m/--base-0$/; 691}else{# read 692# if the tag isn't there, return 0 693unless( -s $tag_file) { 694return0; 695} 696open(C,"<",$tag_file) 697or die"Cannot read tag$tag:$!\n"; 698$commit= <C>; 699chomp$commit; 700die"Error reading tag$tag:$!\n"unlesslength$commit==40; 701close(C) 702or die"Cannot read tag$tag:$!\n"; 703unless(defined$rptags{$commit}) { 704$rptags{$commit} =$tag; 705} 706return$commit; 707} 708} 709 710sub find_parents { 711# 712# Identify what branches are merging into me 713# and whether we are fully merged 714# git-merge-base <headsha> <headsha> should tell 715# me what the base of the merge should be 716# 717my$ps=shift; 718 719my%branches;# holds an arrayref per branch 720# the arrayref contains a list of 721# merged patches between the base 722# of the merge and the current head 723 724my@parents;# parents found for this commit 725 726# simple loop to split the merges 727# per branch 728foreachmy$merge(@{$ps->{merges}}) { 729my$branch= git_branchname($merge); 730unless(defined$branches{$branch} ){ 731$branches{$branch} = []; 732} 733push@{$branches{$branch}},$merge; 734} 735 736# 737# foreach branch find a merge base and walk it to the 738# head where we are, collecting the merged patchsets that 739# Arch has recorded. Keep that in @have 740# Compare that with the commits on the other branch 741# between merge-base and the tip of the branch (@need) 742# and see if we have a series of consecutive patches 743# starting from the merge base. The tip of the series 744# of consecutive patches merged is our new parent for 745# that branch. 746# 747foreachmy$branch(keys%branches) { 748 749# check that we actually know about the branch 750next unless-e "$git_dir/refs/heads/$branch"; 751 752my$mergebase=`git-merge-base$branch$ps->{branch}`; 753if($?) { 754# Don't die here, Arch supports one-way cherry-picking 755# between branches with no common base (or any relationship 756# at all beforehand) 757warn"Cannot find merge base for$branchand$ps->{branch}"; 758next; 759} 760chomp$mergebase; 761 762# now walk up to the mergepoint collecting what patches we have 763my$branchtip= git_rev_parse($ps->{branch}); 764my@ancestors=`git-rev-list --merge-order$branchtip^$mergebase`; 765 my%have; # collected merges this branch has 766 foreach my$merge(@{$ps->{merges}}) { 767$have{$merge} = 1; 768 } 769 my%ancestorshave; 770 foreach my$par(@ancestors) { 771$par= commitid2pset($par); 772 if (defined$par->{merges}) { 773 foreach my$merge(@{$par->{merges}}) { 774$ancestorshave{$merge}=1; 775 } 776 } 777 } 778 # print "++++ Merges in$ps->{id} are....\n"; 779 # my@have= sort keys%have; print Dumper(\@have); 780 781 # merge what we have with what ancestors have 782%have= (%have,%ancestorshave); 783 784 # see what the remote branch has - these are the merges we 785 # will want to have in a consecutive series from the mergebase 786 my$otherbranchtip= git_rev_parse($branch); 787 my@needraw= `git-rev-list --merge-order $otherbranchtip^$mergebase`; 788my@need; 789foreachmy$needps(@needraw) {# get the psets 790$needps= commitid2pset($needps); 791# git-rev-list will also 792# list commits merged in via earlier 793# merges. we are only interested in commits 794# from the branch we're looking at 795if($brancheq$needps->{branch}) { 796push@need,$needps->{id}; 797} 798} 799 800# print "++++ Merges from $branch we want are....\n"; 801# print Dumper(\@need); 802 803my$newparent; 804while(my$needed_commit=pop@need) { 805if($have{$needed_commit}) { 806$newparent=$needed_commit; 807}else{ 808last;# break out of the while 809} 810} 811if($newparent) { 812push@parents,$newparent; 813} 814 815 816}# end foreach branch 817 818# prune redundant parents 819my%parents; 820foreachmy$p(@parents) { 821$parents{$p} =1; 822} 823foreachmy$p(@parents) { 824next unlessexists$psets{$p}{merges}; 825next unlessref$psets{$p}{merges}; 826my@merges= @{$psets{$p}{merges}}; 827foreachmy$merge(@merges) { 828if($parents{$merge}) { 829delete$parents{$merge}; 830} 831} 832} 833@parents= (); 834foreach(keys%parents) { 835push@parents,'-p', ptag($_); 836} 837return@parents; 838} 839 840sub git_rev_parse { 841my$name=shift; 842my$val=`git-rev-parse$name`; 843 die "Error: git-rev-parse$name" if$?; 844 chomp$val; 845 return$val; 846} 847 848# resolve a SHA1 to a known patchset 849sub commitid2pset { 850 my$commitid= shift; 851 chomp$commitid; 852 my$name=$rptags{$commitid} 853 || die "Cannot find reverse tag mapping for$commitid"; 854$name=~ s|,|/|; 855 my$ps=$psets{$name} 856 || (print Dumper(sort keys%psets)) && die "Cannot find patchset for$name"; 857 return$ps; 858} 859 860# an alterative to `command` that allows input to be passed as an array 861# to work around shell problems with weird characters in arguments 862sub safe_pipe_capture { 863 my@output; 864 if (my$pid= open my$child, '-|') { 865@output= (<$child>); 866 close$childor die join(' ',@_).":$!$?"; 867 } else { 868 exec(@_) or die$?; # exec() can fail the executable can't be found 869 } 870 return wantarray ?@output: join('',@output); 871} 872 873