diff options
author | Oswald Buddenhagen <oswald.buddenhagen@gmx.de> | 2019-10-04 14:55:21 +0200 |
---|---|---|
committer | Oswald Buddenhagen <oswald.buddenhagen@gmx.de> | 2020-03-05 18:08:12 +0000 |
commit | a97cc116977942327bf0b96a36fe4800f4d40f9a (patch) | |
tree | 8bde9c19a43eab9f166685ac1cb576c01a09301b /bin/git-gpick | |
parent | d252cf0eb99dff19dc095f5359398ff3207ba50c (diff) | |
download | qtrepotools-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/git-gpick')
-rwxr-xr-x | bin/git-gpick | 103 |
1 files changed, 92 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); |