#! /usr/bin/perl # Copyright (C) 2015 The Qt Company Ltd. # Contact: http://www.qt.io/licensing/ # # You may use this file under the terms of the 3-clause BSD license. # See the file LICENSE from this package for details. # use warnings; use strict; my @repos = ( [ "projects.pro", "qt" ], [ "qlalr.pro", "qt5/qlalr" ], [ "qtactiveqt.pro", "qt5/qtactiveqt" ], [ "qtbase.pro", "qt5/qtbase" ], [ "qtdeclarative.pro", "qt5/qtdeclarative" ], [ "qtdoc.pro", "qt5/qtdoc" ], [ "qtphonon.pro", "qt5/qtphonon" ], [ "qtquick1.pro", "qt5/qtquick1" ], [ "qtscript.pro", "qt5/qtscript" ], [ "qtsvg.pro", "qt5/qtsvg" ], [ "qttools.pro", "qt5/qttools" ], [ "qttranslations.pro", "qt5/qttranslations" ], [ "qtxmlpatterns.pro", "qt5/qtxmlpatterns" ], [ "qtcreator.pro", "qtcreator" ], [ "git-hooks/sanitize-commit", "qt5/qtrepotools" ], [ "shell/sanitize-commit", "devtools" ], ); my @mappings = ( [ [ "qt", "qt5/qlalr" ], [ [ 'util/qlalr//[^/]+$', 'src//[^/]+$' ], [ 'util/qlalr', '' ] ] ], [ [ "qt", "qt5/qtactiveqt" ], [ [ 'tools/activeqt', 'tools' ], [ 'doc/src/examples/activeqt', 'doc/src/examples' ], [ 'doc/src/development//[^/]+$', 'doc/src//[^/]+$' ] ] ], [ [ "qt", "qt5/qtbase" ], [ [ 'doc/src/development', 'qmake/doc/src' ], ] ], [ [ "qt", "qt5/qtdeclarative" ], [ [ 'tools/qml', 'tools/qmlviewer' ] ] ], [ [ "qt", "qt5/qtdoc" ], [ [ 'tools/qdoc3/test', 'doc/config' ] ] ], [ [ "qt", "qt5/qtphonon" ], [ ] ], [ [ "qt", "qt5/qtquick1" ], [ ] ], [ [ "qt", "qt5/qtscript" ], [ ] ], [ [ "qt", "qt5/qtsvg" ], [ ] ], [ [ "qt", "qt5/qttools" ], [ [ 'tools', 'src' ] ] ], [ [ "qt", "qt5/qttranslations" ], [ ] ], [ [ "qt", "qt5/qtxmlpatterns" ], [ ] ], [ [ "devtools", "qt5/qtrepotools" ], [ [ 'shell/git-', 'bin/git-' ], [ 'shell/git_post_commit_hook', 'git-hooks/git_post_commit_hook' ], [ 'shell/sanitize-commit', 'git-hooks/sanitize-commit' ] ] ], [ [ "qtcreator", "qt5/qttools" ], [ [ 'src/shared/proparser', 'src/linguist/shared' ], ], 1 ], [ [ "qtcreator", "qt5/qtbase" ], [ [ 'src/shared/proparser', 'qmake/library' ], ], 1 ], [ [ "qt5/qtbase", "qt5/qttools" ], [ [ 'qmake/library', 'src/linguist/shared' ], ], 1 ], [ [ "qt5/qtbase", "qt5/qtdoc" ], [ [ 'qmake/doc/src/snippets', 'doc/src/snippets' ], [ 'qmake/doc/src', 'doc/src/development' ], ], 1 ] ); my ($cand, $inv); sub map_name($) { my ($fn) = @_; for my $ptha (@{$$cand[1]}) { my ($pthi, $ptho) = ($$ptha[$inv], $$ptha[1 - $inv]); $pthi .= "(.*)" if (!($pthi =~ s,//(.*),/($1),)); $ptho =~ s,//.*,/,; last if ($fn =~ s/^$pthi/$ptho$1/); } return $fn; } sub get_arg() { if (@ARGV < 2) { die $ARGV[0]." requires an argument.\n"; } shift @ARGV; return $ARGV[0]; } sub get_repo_arg() { my $arg = $ARGV[0]; my $val = get_arg(); for my $rp (@repos) { if ($$rp[1] eq $val) { return $val; } if ($$rp[1] =~ /\/\Q$val\E$/) { return $$rp[1]; } } die "Fatal: Unknown repository passed to $arg.\n"; } my ($src_repo, $dst_repo, $other_path) = ("", "", ""); my @commits = (); my $push = 0; my $noop = 0; while (@ARGV) { my $arg = $ARGV[0]; if ($arg eq '-h' or $arg eq '-help' or $arg eq '--help') { print < Specify repo to pull from instead of guessing it. -s, --push [] Push [to specified repo] instead of pulling. -p, --path Specify path of other repo instead of guessing it. -n, --dry-run Dump patch to stdout instead of applying it. -- Only SHA1s follow this option (use with -s without repo). By default, the HEAD commit of the source repo is cherry-picked. EOF exit 0; } elsif ($arg eq '-l' or $arg eq '--pull') { $src_repo = get_repo_arg(); } elsif ($arg eq '-s' or $arg eq '--push') { $dst_repo = get_repo_arg() if (@ARGV >= 2 && $ARGV[1] !~ /^-/); $push = 1; } elsif ($arg eq '-p' or $arg eq '--path') { $other_path = get_arg(); } elsif ($arg eq '-n' or $arg eq '--dry-run') { $noop = 1; } elsif ($arg eq "--") { shift @ARGV; push @commits, @ARGV; last; } elsif ($arg =~ /^-/) { die "Unrecognized option $arg.\n"; } else { push @commits, $arg; } shift @ARGV; } die "-s and -l are mutually exclusive.\n" if ($src_repo ne "" and $dst_repo ne ""); push @commits, "HEAD" if (!@commits); my $cdup = `git rev-parse --show-cdup` or exit 1; chomp $cdup; if (length($cdup)) { chdir $cdup or die "Cannot enter $cdup: $!\n"; } my $this_repo = ""; for my $rp (@repos) { if (-f $$rp[0]) { $this_repo = $$rp[1]; last; } } die "Fatal: This Git repository is not known to this script.\n" if ($this_repo eq ""); if (!$push) { $dst_repo = $this_repo; } else { $src_repo = $this_repo; } die "Fatal: source and target repository are the same.\n" if ($src_repo eq $dst_repo); my @candidates = (); my $mincand = 10; for my $cand (@mappings) { my $mcand = $$cand[2] // 0; if ($mcand <= $mincand) { for my $inv (0, 1) { if (("" eq $src_repo or $$cand[0][$inv] eq $src_repo) and ("" eq $dst_repo or $$cand[0][1 - $inv] eq $dst_repo)) { if ($mcand < $mincand) { $mincand = $mcand; @candidates = (); } push @candidates, [ $cand, $inv ]; last; } } } } if (@candidates == 0) { die "Fatal: found no mapping between source and target repository.\n"; } elsif (@candidates > 1) { die "Fatal: found multiple mappings between source and target repository. Try -l/-p.\n"; } ($cand, $inv) = @{$candidates[0]}; print "Porting from ".$$cand[0][$inv]." to ".$$cand[0][1 - $inv]."\n"; if ($other_path eq "") { my $this_path = $$cand[0][1 - ($push ^ $inv)]; my $cnt = 1; while ($this_path =~ /\//g) { $cnt++ } $other_path = "../"x$cnt . $$cand[0][$push ^ $inv]; } print "Other path is ".$other_path."\n"; my $cd_other = "cd \"".$other_path."\" && "; my ($src_cd, $dst_cd) = ("", ""); if (!$push) { $src_cd = $cd_other; } else { $dst_cd = $cd_other; } my $short_src = $$cand[0][$inv]; $short_src =~ s,^.*/,,; my $needid = -1; for my $commit (@commits) { my @revs; if ($commit =~ /\.\./) { @revs = `${src_cd}git rev-list --reverse $commit`; } else { @revs = ($commit); } for my $rev (@revs) { my ($first, $empty, $sha1, $patch, $haveid) = (1, 0, "", "", 0); open DIFF, $src_cd."git format-patch --stdout -1 --src-prefix=\@old\@/ --dst-prefix=\@new\@/ ".$rev." |" or die "cannot run git: $!"; while () { if ($first) { $first = 0; /^From (.{40}) / and $sha1 = $1; } elsif ($_ eq "\n" || /^[-\w]+: /) { $haveid = 1 if (/^Change-Id:/); $empty = 1; } else { if (s/^(diff --git \@old\@\/)(.*)( \@new\@\/)(.*)\n/$1.map_name($2).$3.map_name($4)."\n"/e) { } elsif (s/^(--- \@old\@|\+\+\+ \@new\@)\/([^\n]+)/$1."\/".map_name($2)/e) { } elsif ($_ eq "---\n") { $patch .= "\n" if (!$empty); $patch .= "(cherry picked from ".$short_src."/".$sha1.")\n"; if (!$haveid) { $needid = int(grep /codereview/, `${dst_cd}git remote -v`) if ($needid < 0); $patch .= "Change-Id: I".$sha1."\n" if ($needid); } } $empty = 0; } $patch .= $_; } close DIFF or die "Aborting at ".$rev."\n"; if ($noop) { print $patch; } else { open PATCH, "| ".$dst_cd."git am -3" or die "cannot run git: $!"; print PATCH $patch; close PATCH or die "Aborting at ".$rev."\n"; } } } exit 0;