summaryrefslogtreecommitdiffstats
path: root/bin
diff options
context:
space:
mode:
authorOswald Buddenhagen <oswald.buddenhagen@gmx.de>2023-04-01 08:51:48 +0200
committerOswald Buddenhagen <oswald.buddenhagen@gmx.de>2024-01-09 16:39:48 +0000
commitf38c1da03852bd6abc26c381bf06f0892373571f (patch)
tree1237f71047d0f1fe8eabbd52b30798d183ea4580 /bin
parent5d515561adc7e56dbedf4bf9217b73fba11e1cc9 (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-xbin/git-gpush114
-rw-r--r--bin/git_gpush.pm27
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,