diff options
author | Oswald Buddenhagen <oswald.buddenhagen@gmx.de> | 2023-04-01 08:51:48 +0200 |
---|---|---|
committer | Oswald Buddenhagen <oswald.buddenhagen@gmx.de> | 2024-01-09 16:39:48 +0000 |
commit | f38c1da03852bd6abc26c381bf06f0892373571f (patch) | |
tree | 1237f71047d0f1fe8eabbd52b30798d183ea4580 /bin | |
parent | 5d515561adc7e56dbedf4bf9217b73fba11e1cc9 (diff) |
gpush: introduce option to add missing Change-Id footers
it's not exactly rare to try to push something to Gerrit, only to
discover that the commit-msg hook wasn't installed. of course one can
fix that and then simply run 'git rebase -i' and 'reword' each commit,
but that gets a bit tedious with a lot of Changes ...
Change-Id: I5a47f615452cc929dacbe621cc3116d2d8cc7b2d
Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
Diffstat (limited to 'bin')
-rwxr-xr-x | bin/git-gpush | 114 | ||||
-rw-r--r-- | bin/git_gpush.pm | 27 |
2 files changed, 129 insertions, 12 deletions
diff --git a/bin/git-gpush b/bin/git-gpush index fbfa19a..5425649 100755 --- a/bin/git-gpush +++ b/bin/git-gpush @@ -258,6 +258,14 @@ Options: In mail mode, send out the patches right away using git send-email, rather than merely preparing them using git format-patch. + -I, --add-ids + Add Change-Id footers to local commits that miss them. Use this + to fix up a branch whose commits were authored while Gerrit's + commit-msg hook was not installed yet. + + This option does not prevent other actions in the same run; + only the default of pushing 'HEAD' is suppressed. + --aliases Report all registered aliases and quit. @@ -409,6 +417,7 @@ my $exclude = 0; my $list_only = 0; my $list_rebase = 0; my $list_online = 0; +my $add_ids = 0; my $send_email = 0; my $keep_version = 0; @@ -510,6 +519,8 @@ sub parse_arguments(@) print "$key = $aliases{$key}\n"; } exit 0; + } elsif ($arg eq "-I" || $arg eq "--add-ids") { + $add_ids = 1; } elsif ($arg eq "-?" || $arg eq "--?" || $arg eq "-h" || $arg eq "--help") { usage(); exit 0; @@ -555,7 +566,11 @@ sub parse_arguments(@) if (!defined($from)) { $from = "HEAD"; - $push_all = 1 if ($list_only); + if ($list_only) { + $push_all = 1; + } else { + $add_ids = -$add_ids; + } } else { fail("--all is incompatible with specifying commits.\n") if ($push_all); @@ -915,13 +930,16 @@ sub determine_local_branch($$$) { my ($source, $core_source, $flags) = @_; + my $src_type; # First, try to extract a branch name directly. $local_branch = resolve_head($core_source); $local_branch =~ s,^refs/heads/,,; my $tip = $local_refs{$local_branch}; - if (!defined($tip)) { + if (defined($tip)) { + $src_type = SRC_BRANCH; + } else { # Next, try to deduce a branch from the commit. - my ($branch, $src_type) = branch_for_commit($source, $flags); + (my $branch, $src_type) = branch_for_commit($source, $flags); return if (!defined($src_type)); # Cannot use the branch's tip mid-rebase, because the # referenced commits may have been rewritten already. @@ -937,7 +955,7 @@ sub determine_local_branch($$$) printf("Extracted tip %s (%s) from %s.\n", $tip, $local_branch ? "branch '$local_branch'" : "no branch", $source) if ($debug); - return $tip; + return ($tip, $src_type); } # Determine the target branch for the given series. @@ -1165,6 +1183,83 @@ sub visit_pushed_changes($) visit_local_commits(\@commits, $mail_mode); } +sub do_add_change_ids($) +{ + my ($commits) = @_; + + printf("Adding Change-Ids to %s..%s.\n", + format_id($local_base), format_id($$commits[-1]{id})) + if ($verbose); + my $base = $local_base; + my ($any, $added) = (0, 0); + foreach my $commit (@$commits) { + my $message = $$commit{message}; + if (defined($$commit{changeid})) { + if (!$any) { + printf("Skipping %s\n", $$commit{id}) if ($debug); + $base = $$commit{id}; + next; + } + printf("Rebasing %s\n", $$commit{id}) if ($debug); + } else { + printf("Amending %s\n", $$commit{id}) if ($debug); + $message =~ s/^#.*\n//gsm; + $message .= "\n" + if ($message !~ /(\n[A-Z]+[-A-Za-z0-9]+: [^\n]+)+\n*$/); + $message .= "Change-Id: I".$$commit{id}."\n"; + $added++; + } + my $parents = $$commit{parents}; + my $new_parents = + ($base eq 'ROOT') ? [] : [ $base, @$parents[1..$#$parents] ]; + $base = create_commit_raw( + $new_parents, $$commit{tree}, $message, + $$commit{author}, $$commit{committer}); + $old2new{$$commit{id}} = $base; + $any = 1; + } + if ($any) { + printf("Added Change-Ids to %d commit(s).\n", $added) + if (!$quiet); + return $base; + } + print "Notice: All local commits already have Change-Ids.\n" + if (!$quiet); + return undef; +} + +sub add_change_ids($$) +{ + my ($commits, $src_type) = @_; + + # We refuse to add Change-Ids to detached HEADs (including mid-rebase + # states), as this would disproportionately contribute to creating + # duplicate Changes. This is of no concern for mail mode, so don't + # restrict it. + # We also reject "free-floating" commits, as the rewritten commits + # would be "lost". + fail("Adding Change-Ids requires a branch.\n") + if (($src_type == SRC_FLOATING) || + (($src_type == SRC_HEAD) && !$mail_mode)); + my ($target, $update_ref, $update_head); + if ($src_type == SRC_BRANCH) { + $target = "refs/heads/".$local_branch; + $update_head = ($target eq ($head_branch // "")); + $update_ref = 1; + } else { + $target = "HEAD"; + $update_head = 1; + } + my $newtip = with_local_git_index(\&do_add_change_ids, $commits); + return if (!defined($newtip)); + run_process(FWD_OUTPUT | DRY_RUN, + 'git', 'update-ref', '-m', "Added Change-Ids", + $target, $newtip); + $local_refs{$local_branch} = $newtip if ($update_ref); + $head_commit = $newtip if ($update_head); + return $newtip; +} + sub initialize_get_changes() { # Get the pool of Changes to push from, and possibly search in @@ -1178,7 +1273,7 @@ sub initialize_get_changes() # needs the full range. my ($source, $core_source) = ($from, $from =~ s/^([^~^]*+).*$/$1/r); # Try to derive the branch from the given tip. - my $tip = determine_local_branch($source, $core_source, SOFT_FAIL); + my ($tip, $src_type) = determine_local_branch($source, $core_source, SOFT_FAIL); if (!defined($tip)) { # That didn't work, so maybe the rev-spec refers to a Change-Id. # We need a place to start from. We try only the current local branch - @@ -1186,14 +1281,19 @@ sub initialize_get_changes() # (as the same Change-Id can exist multiple times), and probably not # very useful to start with. $source = $core_source = 'HEAD'; - $tip = determine_local_branch($source, $core_source, FWD_STDERR); + ($tip, $src_type) = determine_local_branch($source, $core_source, FWD_STDERR); } setup_remotes($source); set_gerrit_config($remote) if (!$mail_mode); source_map_validate(); - my $commits = visit_local_branch($tip); + my $commits = visit_local_branch($tip, $add_ids); fail("No local Changes (from $core_source).\n") if (!@$commits); + if ($add_ids) { + $tip = add_change_ids($commits, $src_type); + exit(0) if ($add_ids < 0); + $commits = visit_local_branch($tip) if (defined($tip)); + } analyze_local_branch($commits); return parse_local_rev($from, SPEC_TIP); } diff --git a/bin/git_gpush.pm b/bin/git_gpush.pm index 67aac0f..b470208 100644 --- a/bin/git_gpush.pm +++ b/bin/git_gpush.pm @@ -1327,7 +1327,9 @@ sub visit_commits_raw($$;$) my @cids = ($message =~ /^Change-Id: (.+)$/mg); my $changeid; if (!@cids) { - fail(format_subject($id, $subject, -18)." has no Change-Id.\n") + fail(format_subject($id, $subject, -18)." has no Change-Id.\n" + ."Install the Gerrit commit-msg hook and amend affected Changes,\n" + ."or fix the issue directly by running git gpush --add-ids.\n") if (!$cid_opt); } else { # Gerrit uses the last Change-Id if multiple are present. @@ -1410,13 +1412,13 @@ sub source_map_assign($$); sub source_map_traverse(); sub _source_map_finish_initial(); -sub visit_local_branch($) +sub visit_local_branch($;$) { - my ($tip) = @_; + my ($tip, $cid_opt) = @_; # Get the revs ... print "Enumerating local Changes ...\n" if ($debug); - my $raw_commits = visit_local_commits([ $tip ]); + my $raw_commits = visit_local_commits([ $tip ], $cid_opt); return [] if (!@$raw_commits); # Traverse along 1st parents only, unlike visit_local_commits(). @@ -1549,6 +1551,9 @@ use constant { SPEC_PARENT => 2 }; +# Mapping of SHA1s rewritten by --add-ids. +our %old2new; + # Parse a revision specification referring to a commit in the local branch. sub parse_local_rev($$) { @@ -1578,6 +1583,11 @@ sub parse_local_rev($$) ($out // $core).$rest."^{commit}") if (!defined($out) || length($rest)); fail("$rev is not a valid rev-spec.\n") if (!defined($out)); + my $new = $old2new{$out}; + if (defined($new)) { + print "Remapping resolved $out => $new.\n" if ($debug); + $out = $new; + } # There is no point in restricting the base here - subsequent # use will find out soon enough if it is not reachable. return $out if ($scope == SPEC_BASE); @@ -1726,7 +1736,7 @@ sub apply_diff($$$) } # Create a commit object from the specified metadata. -sub create_commit($$$$$) +sub create_commit_raw($$$$$) { my ($parents, $tree, $commit_msg, $author, $committer) = @_; @@ -1738,7 +1748,14 @@ sub create_commit($$$$$) write_process($proc, $commit_msg); my $sha1 = read_process($proc); close_process($proc); + return $sha1; +} + +sub create_commit($$$$$) +{ + my ($parents, $tree, $commit_msg, $author, $committer) = @_; + my $sha1 = create_commit_raw($parents, $tree, $commit_msg, $author, $committer); my $commit = { id => $sha1, parents => $parents, |