summaryrefslogtreecommitdiff
path: root/bin
diff options
context:
space:
mode:
authorOswald Buddenhagen <oswald.buddenhagen@gmx.de>2019-10-04 14:55:21 +0200
committerOswald Buddenhagen <oswald.buddenhagen@gmx.de>2020-03-05 18:08:12 +0000
commita97cc116977942327bf0b96a36fe4800f4d40f9a (patch)
tree8bde9c19a43eab9f166685ac1cb576c01a09301b /bin
parentd252cf0eb99dff19dc095f5359398ff3207ba50c (diff)
downloadqtrepotools-a97cc116977942327bf0b96a36fe4800f4d40f9a.tar.gz
gpick: add support for resolving easy conflicts
Change-Id: I703e26fdf3527ef13b5f4702b6efae52b20f6eba Reviewed-by: Oswald Buddenhagen <oswald.buddenhagen@gmx.de>
Diffstat (limited to 'bin')
-rwxr-xr-xbin/git-gpick103
-rw-r--r--bin/git_gpush.pm23
2 files changed, 115 insertions, 11 deletions
diff --git a/bin/git-gpick b/bin/git-gpick
index 5e07140..433ee6a 100755
--- a/bin/git-gpick
+++ b/bin/git-gpick
@@ -72,6 +72,13 @@ Description:
follow the usual instructions on screen.
Options:
+ -m, --merge
+ Attempt to merge concurrent local and remote modifications to
+ the same Changes. Use only once you are sure that there are no
+ logical conflicts.
+ This currently works only when each side touches different parts
+ of the Change (author, commit message, or diff).
+
-f, --force
Replace or merge the local commits even in case of conflicting
modifications.
@@ -162,6 +169,7 @@ use constant {
my $branch;
my $upstream_branch;
+my $merge = 0;
my $force = 0;
my $ignore = 0;
my @commit_specs;
@@ -186,6 +194,8 @@ sub parse_arguments(@)
} elsif ($arg eq "-b" || $arg eq "--branch") {
fail("--branch needs an argument.\n") if (!@_ || ($_[0] =~ /^-/));
$branch = shift @_;
+ } elsif ($arg eq "-m" || $arg eq "--merge") {
+ $merge = 1;
} elsif ($arg eq "-f" || $arg eq "--force") {
$force = 1;
} elsif ($arg eq "-i" || $arg eq "--ignore") {
@@ -203,8 +213,8 @@ sub parse_arguments(@)
fail("--quiet and --verbose/--debug are mutually exclusive.\n")
if ($quiet && $verbose);
- fail("--force and --ignore are mutually exclusive.\n")
- if ($force && $ignore);
+ fail("--merge/--force and --ignore are mutually exclusive.\n")
+ if (($merge || $force) && $ignore);
}
sub determine_local_branch()
@@ -1171,10 +1181,10 @@ sub verify_delete($$$$$$$)
return (UPD_DROP, $pfx, $annot);
}
-sub verify_update($$$$$$$)
+sub verify_update($$$$$$$$)
{
my ($reports, $lcl_commit, $rmt_commit, $rmt_ps, $pushed_commit, $pushed_ps,
- $need_force_repl) = @_;
+ $need_force_repl, $can_merge) = @_;
my ($pfx, $annot, $forcing) = (undef, "", 0);
# First make a direct comparison. This should be the common case (the user
@@ -1226,6 +1236,35 @@ sub verify_update($$$$$$$)
if ($lcl_parts) {
# The current local commit differs from the previously pushed one,
# and we already know that the current remote commit also differs.
+ # However, individual parts may be unmodified on either side.
+ if (!$fc && $merge) {
+ # No textual conflict, as each side modified different parts.
+ # We do not merge without being asked to, as the modifications
+ # might still conflict logically.
+ $pfx = "Merge " if (!$quiet);
+ return (UPD_PUSHED | UPD_META | UPD_INFO, $rmt_parts, $pfx, $annot);
+ }
+ if (!$force) {
+ my $pfx;
+ if (!$fc) {
+ $$can_merge = 1;
+ $pfx = "(Merge) ";
+ } elsif ($fl) {
+ $$can_merge = 1 if (!$merge);
+ $$need_force_repl = 1;
+ $pfx = "(MERGE) ";
+ } else {
+ # TODO: We could still attempt 3-way merges here.
+ $$need_force_repl = 1;
+ $pfx = "(REPLACE) ";
+ }
+ return (UPD_INFO, NO_PARTS, $pfx, $annot);
+ }
+ if ($fl && $merge) {
+ # Retain the non-conflicting local modifications.
+ $pfx = "MERGE " if (!$quiet);
+ return (UPD_PUSHED | UPD_META | UPD_INFO, $rmt_parts, $pfx, $annot);
+ }
$forcing = 1;
} elsif ($pushed_ps eq "?") {
# The previously pushed commit disappeared from Gerrit.
@@ -1251,6 +1290,33 @@ sub verify_update($$$$$$$)
return (UPD_PUSHED | UPD_META | UPD_INFO, ALL_PARTS, $pfx, $annot);
}
+# Create a new commit by replacing named parts of the
+# source commit with these from the reference commit.
+sub merge_commits($$$)
+{
+ my ($commit, $ref_commit, $parts) = @_;
+
+ my ($parents, $tree, $message, $author, $committer) =
+ ($$commit{parents}, $$commit{tree}, $$commit{message},
+ $$commit{author}, $$commit{committer});
+ if ($parts & AUTHOR_PART) {
+ $author = $$ref_commit{author};
+ }
+ if ($parts & MESSAGE_PART) {
+ # This is probably the most common case: commit message edited
+ # directly on Gerrit, while amending the diff locally.
+ $message = $$ref_commit{message};
+ }
+ if ($parts & DIFF_PART) {
+ # To get the correct diff, we need to replace both the parent(s)
+ # and the tree. The implicit rebasing does not matter, as the
+ # result is going to be cherry-picked anyway.
+ $parents = $$ref_commit{parents};
+ $tree = $$ref_commit{tree};
+ }
+ return create_commit($parents, $tree, $message, $author, $committer);
+}
+
# Turn the list of local Changes into the final list of commits the
# local branch should be reconstructed from.
sub do_adjust_changes($)
@@ -1259,7 +1325,7 @@ sub do_adjust_changes($)
print "Adjusting Changes ...\n" if ($debug);
- my ($need_force_del, $need_force_repl);
+ my ($need_force_del, $need_force_repl, $can_merge);
my ($any_missing, $any_crossed, $any_merged);
my (@commits, @reports);
foreach my $pair (@$pairs) {
@@ -1309,7 +1375,7 @@ sub do_adjust_changes($)
} else {
($upd, $parts, $pfx, $annot) =
verify_update(\@reports, $lcl_commit, $rmt_commit, $rmt_ps,
- $pushed_commit, $pushed_ps, \$need_force_repl);
+ $pushed_commit, $pushed_ps, \$need_force_repl, \$can_merge);
}
report_update(\@reports, $pfx, $annot, $lcl_commit, $ginfo) if (defined($pfx));
# Note: we don't clear the diff cache here, so entries filled
@@ -1325,6 +1391,8 @@ sub do_adjust_changes($)
my $commit;
if ($parts == NO_PARTS) {
$commit = $lcl_commit;
+ } elsif ($parts != ALL_PARTS) {
+ $commit = merge_commits($lcl_commit, $rmt_commit, $parts);
} else {
$commit = $rmt_commit;
}
@@ -1369,15 +1437,26 @@ sub do_adjust_changes($)
$any_msg = 1;
}
- if ($need_force_repl || $need_force_del) {
+ if ($can_merge || $need_force_repl || $need_force_del) {
my $err;
if ($need_force_del) {
$err .= "\nLocal modifications found in Change(s) scheduled for deletion;"
." add --force to proceed nonetheless.";
}
- if ($need_force_repl) {
- $err .= "\nConflicting local modifications found in Change(s) scheduled for update;"
- ." use --force to overwrite them, or --ignore to disregard any new PatchSet(s).";
+ if ($can_merge || $need_force_repl) {
+ my $lmt = " local modifications found in Change(s) scheduled for update;";
+ my $ipt = " or --ignore to disregard any new PatchSet(s).";
+ if ($can_merge) {
+ my $mmt = " add --merge after verifying compatibility,";
+ if ($need_force_repl) {
+ $err .= "\nMergeable and conflicting".$lmt.$mmt
+ ." and/or --force to overwrite unmerged modifications,".$ipt;
+ } else {
+ $err .= "\nMergeable".$lmt.$mmt." --force to overwrite them,".$ipt;
+ }
+ } else {
+ $err .= "\nConflicting".$lmt." use --force to overwrite them,".$ipt;
+ }
}
nwfail($err."\n");
}
@@ -1479,7 +1558,9 @@ sub invoke_rebase($$)
$ENV{GPICK_DEBUG} = $debug;
$ENV{GIT_EDITOR} = $script;
- my @gitcmd = ('git', 'rebase', '-i', $base);
+ # Make sure that every commit is picked, otherwise the state todo might
+ # not match if we constructed a first commit that needs no rebasing.
+ my @gitcmd = ('git', 'rebase', '-i', '--no-ff', $base);
push @gitcmd, '-q' if ($quiet);
if (!$dry_run) {
print "+ @gitcmd\n" if ($debug);
diff --git a/bin/git_gpush.pm b/bin/git_gpush.pm
index d349db4..b6f9b1c 100644
--- a/bin/git_gpush.pm
+++ b/bin/git_gpush.pm
@@ -1343,6 +1343,29 @@ sub apply_diff($$$)
return ($curr_tree, undef);
}
+# Create a commit object from the specified metadata.
+sub create_commit($$$$$)
+{
+ my ($parents, $tree, $commit_msg, $author, $committer) = @_;
+
+ ($ENV{GIT_AUTHOR_NAME}, $ENV{GIT_AUTHOR_EMAIL}, $ENV{GIT_AUTHOR_DATE}) = @$author;
+ ($ENV{GIT_COMMITTER_NAME}, $ENV{GIT_COMMITTER_EMAIL}, $ENV{GIT_COMMITTER_DATE}) = @$committer;
+ my @pargs = map { ('-p', $_) } @$parents;
+ my $proc = open_process(USE_STDIN | SILENT_STDIN | USE_STDOUT | FWD_STDERR,
+ 'git', 'commit-tree', $tree, @pargs);
+ write_process($proc, $commit_msg);
+ my $sha1 = read_process($proc);
+ close_process($proc);
+
+ my $commit = {
+ id => $sha1,
+ parents => $parents,
+ tree => $tree
+ };
+ init_commit($commit);
+ return $commit;
+}
+
###################
# branch tracking #
###################