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); 59use File::Basename qw(basename dirname); 60use String::ShellQuote; 61use Time::Local; 62use IO::Socket; 63use IO::Pipe; 64use POSIX qw(strftime dup2); 65use Data::Dumper qw/ Dumper /; 66use IPC::Open2; 67 68$SIG{'PIPE'}="IGNORE"; 69$ENV{'TZ'}="UTC"; 70 71my$git_dir=$ENV{"GIT_DIR"} ||".git"; 72$ENV{"GIT_DIR"} =$git_dir; 73my$ptag_dir="$git_dir/archimport/tags"; 74 75our($opt_h,$opt_v,$opt_T,$opt_t,$opt_o); 76 77sub usage() { 78print STDERR <<END; 79Usage: ${\basename$0} # fetch/update GIT from Arch 80 [ -o ] [ -h ] [ -v ] [ -T ] [ -t tempdir ] 81 repository/arch-branch [ repository/arch-branch] ... 82END 83exit(1); 84} 85 86getopts("Thvt:")or usage(); 87usage if$opt_h; 88 89@ARGV>=1or usage(); 90my@arch_roots=@ARGV; 91 92my($tmpdir,$tmpdirname) = tempdir('git-archimport-XXXXXX', TMPDIR =>1, CLEANUP =>1); 93my$tmp=$opt_t||1; 94$tmp= tempdir('git-archimport-XXXXXX', TMPDIR =>1, CLEANUP =>1); 95$opt_v&&print"+ Using$tmpas temporary directory\n"; 96 97my@psets= ();# the collection 98my%psets= ();# the collection, by name 99 100my%rptags= ();# my reverse private tags 101# to map a SHA1 to a commitid 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 308`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 317`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); 324 print " * Tagged$ps->{id} at$branchpoint\n"; 325 next; 326 } 327 die$!if$?; 328 } 329 330 # 331 # Apply the import/changeset/merge into the working tree 332 # 333 if ($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 # 344 my$tree; 345 346 my$commitlog= `tla cat-archive-log -A $ps->{repo}$ps->{id}`; 347 die "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! 352 my ($sum,$msg,$add,$del,$mod,$ren) = parselog($commitlog); 353 my$logmessage= "$sum\n$msg"; 354 355 356 # imports don't give us good info 357 # on added files. Shame on them 358 if ($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 363 if (@$add) { 364 while (@$add) { 365 my@slice= splice(@$add, 0, 100); 366 my$slice= join(' ',@slice); 367 `git-update-index --add $slice`; 368die"Error in git-update-index --add:$!"if$?; 369} 370} 371if(@$del) { 372foreachmy$file(@$del) { 373unlink$fileor die"Problems deleting$file:$!"; 374} 375while(@$del) { 376my@slice=splice(@$del,0,100); 377my$slice=join(' ',@slice); 378`git-update-index --remove$slice`; 379 die "Error in git-update-index --remove:$!" if$?; 380 } 381 } 382 if (@$ren) { # renamed 383 if (@$ren% 2) { 384 die "Odd number of entries in rename!?"; 385 } 386 ; 387 while (@$ren) { 388 my$from= pop@$ren; 389 my$to= pop@$ren; 390 391 unless (-d dirname($to)) { 392 mkpath(dirname($to)); # will die on err 393 } 394 #print "moving$from$to"; 395 `mv $from $to`; 396die"Error renaming$from$to:$!"if$?; 397`git-update-index --remove$from`; 398 die "Error in git-update-index --remove:$!" if$?; 399 `git-update-index --add $to`; 400die"Error in git-update-index --add:$!"if$?; 401} 402 403} 404if(@$mod) {# must be _after_ renames 405while(@$mod) { 406my@slice=splice(@$mod,0,100); 407my$slice=join(' ',@slice); 408`git-update-index$slice`; 409 die "Error in git-update-index:$!" if$?; 410 } 411 } 412 413 # warn "errors when running git-update-index!$!"; 414$tree= `git-write-tree`; 415 die "cannot write tree$!" if$?; 416 chomp$tree; 417 418 419 # 420 # Who's your daddy? 421 # 422 my@par; 423 if ( -e "$git_dir/refs/heads/$ps->{branch}") { 424 if (open HEAD, "<$git_dir/refs/heads/$ps->{branch}") { 425 my$p= <HEAD>; 426 close HEAD; 427 chomp$p; 428 push@par, '-p',$p; 429 } else { 430 if ($ps->{type} eq 's') { 431 warn "Could not find the right head for the branch$ps->{branch}"; 432 } 433 } 434 } 435 436 if ($ps->{merges}) { 437 push@par, find_parents($ps); 438 } 439 my$par= join (' ',@par); 440 441 # 442 # Commit, tag and clean state 443 # 444$ENV{TZ} = 'GMT'; 445$ENV{GIT_AUTHOR_NAME} =$ps->{author}; 446$ENV{GIT_AUTHOR_EMAIL} =$ps->{email}; 447$ENV{GIT_AUTHOR_DATE} =$ps->{date}; 448$ENV{GIT_COMMITTER_NAME} =$ps->{author}; 449$ENV{GIT_COMMITTER_EMAIL} =$ps->{email}; 450$ENV{GIT_COMMITTER_DATE} =$ps->{date}; 451 452 my ($pid,$commit_rh,$commit_wh); 453$commit_rh= 'commit_rh'; 454$commit_wh= 'commit_wh'; 455 456$pid= open2(*READER, *WRITER, "git-commit-tree$tree$par") 457 or die$!; 458 print WRITER$logmessage; # write 459 close WRITER; 460 my$commitid= <READER>; # read 461 chomp$commitid; 462 close READER; 463 waitpid$pid,0; # close; 464 465 if (length$commitid!= 40) { 466 die "Something went wrong with the commit!$!$commitid"; 467 } 468 # 469 # Update the branch 470 # 471 open HEAD, ">$git_dir/refs/heads/$ps->{branch}"; 472 print HEAD$commitid; 473 close HEAD; 474 system('git-update-ref', 'HEAD', "$ps->{branch}"); 475 476 # tag accordingly 477 ptag($ps->{id},$commitid); # private tag 478 if ($opt_T||$ps->{type} eq 't' ||$ps->{type} eq 'i') { 479 tag($ps->{id},$commitid); 480 } 481 print " * Committed$ps->{id}\n"; 482 print " + tree$tree\n"; 483 print " + commit$commitid\n"; 484$opt_v&& print " + commit date is$ps->{date}\n"; 485$opt_v&& print " + parents:$par\n"; 486} 487 488sub apply_import { 489 my$ps= shift; 490 my$bname= git_branchname($ps->{id}); 491 492 `mkdir-p $tmp`; 493 494`tla get -s --no-pristine -A$ps->{repo}$ps->{id}$tmp/import`; 495 die "Cannot get import:$!" if$?; 496 `rsync -v --archive --delete--exclude '$git_dir'--exclude '.arch-ids'--exclude '{arch}'$tmp/import/*./`; 497 die "Cannot rsync import:$!" if$?; 498 499 `rm -fr $tmp/import`; 500die"Cannot remove tempdir:$!"if$?; 501 502 503return1; 504} 505 506sub apply_cset { 507my$ps=shift; 508 509`mkdir -p$tmp`; 510 511 # get the changeset 512 `tla get-changeset -A $ps->{repo}$ps->{id}$tmp/changeset`; 513die"Cannot get changeset:$!"if$?; 514 515# apply patches 516if(`find$tmp/changeset/patches-type f -name '*.patch'`) { 517# this can be sped up considerably by doing 518# (find | xargs cat) | patch 519# but that cna get mucked up by patches 520# with missing trailing newlines or the standard 521# 'missing newline' flag in the patch - possibly 522# produced with an old/buggy diff. 523# slow and safe, we invoke patch once per patchfile 524`find$tmp/changeset/patches-type f -name '*.patch' -print0 | grep -zv '{arch}' | xargs -iFILE -0 --no-run-if-empty patch -p1 --forward -iFILE`; 525die"Problem applying patches!$!"if$?; 526} 527 528# apply changed binary files 529if(my@modified=`find$tmp/changeset/patches-type f -name '*.modified'`) { 530foreachmy$mod(@modified) { 531chomp$mod; 532my$orig=$mod; 533$orig=~s/\.modified$//;# lazy 534$orig=~s!^\Q$tmp\E/changeset/patches/!!; 535#print "rsync -p '$mod' '$orig'"; 536`rsync -p$mod./$orig`; 537 die "Problem applying binary changes!$!" if$?; 538 } 539 } 540 541 # bring in new files 542 `rsync --archive --exclude '$git_dir'--exclude '.arch-ids'--exclude '{arch}'$tmp/changeset/new-files-archive/* ./`; 543 544 # deleted files are hinted from the commitlog processing 545 546 `rm -fr $tmp/changeset`; 547} 548 549 550# =for reference 551# A log entry looks like 552# Revision: moodle-org--moodle--1.3.3--patch-15 553# Archive: arch-eduforge@catalyst.net.nz--2004 554# Creator: Penny Leach <penny@catalyst.net.nz> 555# Date: Wed May 25 14:15:34 NZST 2005 556# Standard-date: 2005-05-25 02:15:34 GMT 557# New-files: lang/de/.arch-ids/block_glossary_random.php.id 558# lang/de/.arch-ids/block_html.php.id 559# New-directories: lang/de/help/questionnaire 560# lang/de/help/questionnaire/.arch-ids 561# Renamed-files: .arch-ids/db_sears.sql.id db/.arch-ids/db_sears.sql.id 562# db_sears.sql db/db_sears.sql 563# Removed-files: lang/be/docs/.arch-ids/release.html.id 564# lang/be/docs/.arch-ids/releaseold.html.id 565# Modified-files: admin/cron.php admin/delete.php 566# admin/editor.html backup/lib.php backup/restore.php 567# New-patches: arch-eduforge@catalyst.net.nz--2004/moodle-org--moodle--1.3.3--patch-15 568# Summary: Updating to latest from MOODLE_14_STABLE (1.4.5+) 569# Keywords: 570# 571# Updating yadda tadda tadda madda 572sub parselog { 573my$log=shift; 574#print $log; 575 576my(@add,@del,@mod,@ren,@kw,$sum,$msg); 577 578if($log=~m/(?:\n|^)New-files:(.*?)(?=\n\w)/s) { 579my$files=$1; 580@add=split(m/\s+/s,$files); 581} 582 583if($log=~m/(?:\n|^)Removed-files:(.*?)(?=\n\w)/s) { 584my$files=$1; 585@del=split(m/\s+/s,$files); 586} 587 588if($log=~m/(?:\n|^)Modified-files:(.*?)(?=\n\w)/s) { 589my$files=$1; 590@mod=split(m/\s+/s,$files); 591} 592 593if($log=~m/(?:\n|^)Renamed-files:(.*?)(?=\n\w)/s) { 594my$files=$1; 595@ren=split(m/\s+/s,$files); 596} 597 598$sum=''; 599if($log=~m/^Summary:(.+?)$/m) { 600$sum=$1; 601$sum=~s/^\s+//; 602$sum=~s/\s+$//; 603} 604 605$msg=''; 606if($log=~m/\n\n(.+)$/s) { 607$msg=$1; 608$msg=~s/^\s+//; 609$msg=~s/\s+$//; 610} 611 612 613# cleanup the arrays 614foreachmy$ref( (\@add, \@del, \@mod, \@ren) ) { 615my@tmp= (); 616while(my$t=pop@$ref) { 617next unlesslength($t); 618next if$t=~m!\{arch\}/!; 619next if$t=~m!\.arch-ids/!; 620next if$t=~m!\.arch-inventory$!; 621# tla cat-archive-log will give us filenames with spaces as file\(sp)name - why? 622# we can assume that any filename with \ indicates some pika escaping that we want to get rid of. 623if($t=~/\\/){ 624$t=`tla escape --unescaped '$t'`; 625} 626push(@tmp, shell_quote($t)); 627} 628@$ref=@tmp; 629} 630 631#print Dumper [$sum, $msg, \@add, \@del, \@mod, \@ren]; 632return($sum,$msg, \@add, \@del, \@mod, \@ren); 633} 634 635# write/read a tag 636sub tag { 637my($tag,$commit) =@_; 638 639if($opt_o) { 640$tag=~ s|/|--|g; 641}else{ 642# don't use subdirs for tags yet, it could screw up other porcelains 643$tag=~ s|/|,|g; 644} 645 646if($commit) { 647open(C,">","$git_dir/refs/tags/$tag") 648or die"Cannot create tag$tag:$!\n"; 649print C "$commit\n" 650or die"Cannot write tag$tag:$!\n"; 651close(C) 652or die"Cannot write tag$tag:$!\n"; 653print" * Created tag '$tag' on '$commit'\n"if$opt_v; 654}else{# read 655open(C,"<","$git_dir/refs/tags/$tag") 656or die"Cannot read tag$tag:$!\n"; 657$commit= <C>; 658chomp$commit; 659die"Error reading tag$tag:$!\n"unlesslength$commit==40; 660close(C) 661or die"Cannot read tag$tag:$!\n"; 662return$commit; 663} 664} 665 666# write/read a private tag 667# reads fail softly if the tag isn't there 668sub ptag { 669my($tag,$commit) =@_; 670 671# don't use subdirs for tags yet, it could screw up other porcelains 672$tag=~ s|/|,|g; 673 674my$tag_file="$ptag_dir/$tag"; 675my$tag_branch_dir= dirname($tag_file); 676 mkpath($tag_branch_dir)unless(-d $tag_branch_dir); 677 678if($commit) {# write 679open(C,">",$tag_file) 680or die"Cannot create tag$tag:$!\n"; 681print C "$commit\n" 682or die"Cannot write tag$tag:$!\n"; 683close(C) 684or die"Cannot write tag$tag:$!\n"; 685$rptags{$commit} =$tag 686unless$tag=~m/--base-0$/; 687}else{# read 688# if the tag isn't there, return 0 689unless( -s $tag_file) { 690return0; 691} 692open(C,"<",$tag_file) 693or die"Cannot read tag$tag:$!\n"; 694$commit= <C>; 695chomp$commit; 696die"Error reading tag$tag:$!\n"unlesslength$commit==40; 697close(C) 698or die"Cannot read tag$tag:$!\n"; 699unless(defined$rptags{$commit}) { 700$rptags{$commit} =$tag; 701} 702return$commit; 703} 704} 705 706sub find_parents { 707# 708# Identify what branches are merging into me 709# and whether we are fully merged 710# git-merge-base <headsha> <headsha> should tell 711# me what the base of the merge should be 712# 713my$ps=shift; 714 715my%branches;# holds an arrayref per branch 716# the arrayref contains a list of 717# merged patches between the base 718# of the merge and the current head 719 720my@parents;# parents found for this commit 721 722# simple loop to split the merges 723# per branch 724foreachmy$merge(@{$ps->{merges}}) { 725my$branch= git_branchname($merge); 726unless(defined$branches{$branch} ){ 727$branches{$branch} = []; 728} 729push@{$branches{$branch}},$merge; 730} 731 732# 733# foreach branch find a merge base and walk it to the 734# head where we are, collecting the merged patchsets that 735# Arch has recorded. Keep that in @have 736# Compare that with the commits on the other branch 737# between merge-base and the tip of the branch (@need) 738# and see if we have a series of consecutive patches 739# starting from the merge base. The tip of the series 740# of consecutive patches merged is our new parent for 741# that branch. 742# 743foreachmy$branch(keys%branches) { 744 745# check that we actually know about the branch 746next unless-e "$git_dir/refs/heads/$branch"; 747 748my$mergebase=`git-merge-base$branch$ps->{branch}`; 749if($?) { 750# Don't die here, Arch supports one-way cherry-picking 751# between branches with no common base (or any relationship 752# at all beforehand) 753warn"Cannot find merge base for$branchand$ps->{branch}"; 754next; 755} 756chomp$mergebase; 757 758# now walk up to the mergepoint collecting what patches we have 759my$branchtip= git_rev_parse($ps->{branch}); 760my@ancestors=`git-rev-list --merge-order$branchtip^$mergebase`; 761 my%have; # collected merges this branch has 762 foreach my$merge(@{$ps->{merges}}) { 763$have{$merge} = 1; 764 } 765 my%ancestorshave; 766 foreach my$par(@ancestors) { 767$par= commitid2pset($par); 768 if (defined$par->{merges}) { 769 foreach my$merge(@{$par->{merges}}) { 770$ancestorshave{$merge}=1; 771 } 772 } 773 } 774 # print "++++ Merges in$ps->{id} are....\n"; 775 # my@have= sort keys%have; print Dumper(\@have); 776 777 # merge what we have with what ancestors have 778%have= (%have,%ancestorshave); 779 780 # see what the remote branch has - these are the merges we 781 # will want to have in a consecutive series from the mergebase 782 my$otherbranchtip= git_rev_parse($branch); 783 my@needraw= `git-rev-list --merge-order $otherbranchtip^$mergebase`; 784my@need; 785foreachmy$needps(@needraw) {# get the psets 786$needps= commitid2pset($needps); 787# git-rev-list will also 788# list commits merged in via earlier 789# merges. we are only interested in commits 790# from the branch we're looking at 791if($brancheq$needps->{branch}) { 792push@need,$needps->{id}; 793} 794} 795 796# print "++++ Merges from $branch we want are....\n"; 797# print Dumper(\@need); 798 799my$newparent; 800while(my$needed_commit=pop@need) { 801if($have{$needed_commit}) { 802$newparent=$needed_commit; 803}else{ 804last;# break out of the while 805} 806} 807if($newparent) { 808push@parents,$newparent; 809} 810 811 812}# end foreach branch 813 814# prune redundant parents 815my%parents; 816foreachmy$p(@parents) { 817$parents{$p} =1; 818} 819foreachmy$p(@parents) { 820next unlessexists$psets{$p}{merges}; 821next unlessref$psets{$p}{merges}; 822my@merges= @{$psets{$p}{merges}}; 823foreachmy$merge(@merges) { 824if($parents{$merge}) { 825delete$parents{$merge}; 826} 827} 828} 829@parents=keys%parents; 830@parents=map{" -p ". ptag($_) }@parents; 831return@parents; 832} 833 834sub git_rev_parse { 835my$name=shift; 836my$val=`git-rev-parse$name`; 837 die "Error: git-rev-parse$name" if$?; 838 chomp$val; 839 return$val; 840} 841 842# resolve a SHA1 to a known patchset 843sub commitid2pset { 844 my$commitid= shift; 845 chomp$commitid; 846 my$name=$rptags{$commitid} 847 || die "Cannot find reverse tag mapping for$commitid"; 848$name=~ s|,|/|; 849 my$ps=$psets{$name} 850 || (print Dumper(sort keys%psets)) && die "Cannot find patchset for$name"; 851 return$ps; 852}