Skip to content

Commit a6cc73c

Browse files
pks-tgitster
authored andcommitted
builtin/history: replace "--ref-action=print" with "--dry-run"
The git-history(1) command has the ability to perform a dry-run that will not end up modifying any references. Instead, we'll only print any ref updates that would happen as a consequence of performing the operation. This mode is somewhat hidden though behind the "--ref-action=print" option. This command line option has its origin in git-replay(1), where it's probably an okayish interface as this command is sitting more on the plumbing side of tools. But git-history(1) is a user-facing tool, and this way of achieving a dry-run is way too technical and thus not very discoverable. Besides usability issues, it also has another issue: the dry-run mode will always operate as if the user wanted to rewrite all branches. But in fact, the user also has the option to only update the HEAD reference, and they might want to perform a dry-run of such an operation, too. We could of course introduce "--ref-actoin=print-head", but that would become even less ergonomic. Replace "--ref-action=print" with a new "--dry-run" toggle. This new toggle works with both "--ref-action={head,branches}" and is way more discoverable. Add a test to verify that both "--ref-action=" values behave as expected. Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>
1 parent ab0e1d1 commit a6cc73c

3 files changed

Lines changed: 96 additions & 78 deletions

File tree

Documentation/git-history.adoc

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ git-history - EXPERIMENTAL: Rewrite history
88
SYNOPSIS
99
--------
1010
[synopsis]
11-
git history reword <commit> [--ref-action=(branches|head|print)]
11+
git history reword <commit> [--dry-run] [--ref-action=(branches|head)]
1212

1313
DESCRIPTION
1414
-----------
@@ -60,13 +60,15 @@ The following commands are available to rewrite history in different ways:
6060
OPTIONS
6161
-------
6262

63-
`--ref-action=(branches|head|print)`::
63+
`--dry-run`::
64+
Do not update any references, but instead print any ref updates in a
65+
format that can be consumed by linkgit:git-update-ref[1].
66+
67+
`--ref-action=(branches|head)`::
6468
Control which references will be updated by the command, if any. With
6569
`branches`, all local branches that point to commits which are
6670
descendants of the original commit will be rewritten. With `head`, only
67-
the current `HEAD` reference will be rewritten. With `print`, all
68-
updates as they would be performed with `branches` are printed in a
69-
format that can be consumed by linkgit:git-update-ref[1].
71+
the current `HEAD` reference will be rewritten.
7072

7173
GIT
7274
---

builtin/history.c

Lines changed: 79 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
#include "wt-status.h"
1919

2020
#define GIT_HISTORY_REWORD_USAGE \
21-
N_("git history reword <commit> [--ref-action=(branches|head|print)]")
21+
N_("git history reword <commit> [--dry-run] [--ref-action=(branches|head)]")
2222

2323
static void change_data_free(void *util, const char *str UNUSED)
2424
{
@@ -155,7 +155,6 @@ enum ref_action {
155155
REF_ACTION_DEFAULT,
156156
REF_ACTION_BRANCHES,
157157
REF_ACTION_HEAD,
158-
REF_ACTION_PRINT,
159158
};
160159

161160
static int parse_ref_action(const struct option *opt, const char *value, int unset)
@@ -167,10 +166,8 @@ static int parse_ref_action(const struct option *opt, const char *value, int uns
167166
*action = REF_ACTION_BRANCHES;
168167
} else if (!strcmp(value, "head")) {
169168
*action = REF_ACTION_HEAD;
170-
} else if (!strcmp(value, "print")) {
171-
*action = REF_ACTION_PRINT;
172169
} else {
173-
return error(_("%s expects one of 'branches', 'head' or 'print'"),
170+
return error(_("%s expects one of 'branches' or 'head'"),
174171
opt->long_name);
175172
}
176173

@@ -287,11 +284,29 @@ static int setup_revwalk(struct repository *repo,
287284
return ret;
288285
}
289286

287+
static int handle_ref_update(struct ref_transaction *transaction,
288+
const char *refname,
289+
const struct object_id *new_oid,
290+
const struct object_id *old_oid,
291+
const char *reflog_msg,
292+
struct strbuf *err)
293+
{
294+
if (!transaction) {
295+
printf("update %s %s %s\n",
296+
refname, oid_to_hex(new_oid), oid_to_hex(old_oid));
297+
return 0;
298+
}
299+
300+
return ref_transaction_update(transaction, refname, new_oid, old_oid,
301+
NULL, NULL, 0, reflog_msg, err);
302+
}
303+
290304
static int handle_reference_updates(struct rev_info *revs,
291305
enum ref_action action,
292306
struct commit *original,
293307
struct commit *rewritten,
294-
const char *reflog_msg)
308+
const char *reflog_msg,
309+
int dry_run)
295310
{
296311
const struct name_decoration *decoration;
297312
struct replay_revisions_options opts = { 0 };
@@ -313,82 +328,72 @@ static int handle_reference_updates(struct rev_info *revs,
313328
if (ret)
314329
goto out;
315330

316-
switch (action) {
317-
case REF_ACTION_BRANCHES:
318-
case REF_ACTION_HEAD:
331+
if (action != REF_ACTION_BRANCHES && action != REF_ACTION_HEAD)
332+
BUG("unsupported ref action %d", action);
333+
334+
if (!dry_run) {
319335
transaction = ref_store_transaction_begin(get_main_ref_store(revs->repo), 0, &err);
320336
if (!transaction) {
321337
ret = error(_("failed to begin ref transaction: %s"), err.buf);
322338
goto out;
323339
}
340+
}
324341

325-
for (size_t i = 0; i < result.updates_nr; i++) {
326-
ret = ref_transaction_update(transaction,
327-
result.updates[i].refname,
328-
&result.updates[i].new_oid,
329-
&result.updates[i].old_oid,
330-
NULL, NULL, 0, reflog_msg, &err);
331-
if (ret) {
332-
ret = error(_("failed to update ref '%s': %s"),
333-
result.updates[i].refname, err.buf);
334-
goto out;
335-
}
342+
for (size_t i = 0; i < result.updates_nr; i++) {
343+
ret = handle_ref_update(transaction,
344+
result.updates[i].refname,
345+
&result.updates[i].new_oid,
346+
&result.updates[i].old_oid,
347+
reflog_msg, &err);
348+
if (ret) {
349+
ret = error(_("failed to update ref '%s': %s"),
350+
result.updates[i].refname, err.buf);
351+
goto out;
336352
}
353+
}
354+
355+
/*
356+
* `replay_revisions()` only updates references that are
357+
* ancestors of `rewritten`, so we need to manually
358+
* handle updating references that point to `original`.
359+
*/
360+
for (decoration = get_name_decoration(&original->object);
361+
decoration;
362+
decoration = decoration->next)
363+
{
364+
if (decoration->type != DECORATION_REF_LOCAL &&
365+
decoration->type != DECORATION_REF_HEAD)
366+
continue;
367+
368+
if (action == REF_ACTION_HEAD &&
369+
decoration->type != DECORATION_REF_HEAD)
370+
continue;
337371

338372
/*
339-
* `replay_revisions()` only updates references that are
340-
* ancestors of `rewritten`, so we need to manually
341-
* handle updating references that point to `original`.
373+
* We only need to update HEAD separately in case it's
374+
* detached. If it's not we'd already update the branch
375+
* it is pointing to.
342376
*/
343-
for (decoration = get_name_decoration(&original->object);
344-
decoration;
345-
decoration = decoration->next)
346-
{
347-
if (decoration->type != DECORATION_REF_LOCAL &&
348-
decoration->type != DECORATION_REF_HEAD)
349-
continue;
350-
351-
if (action == REF_ACTION_HEAD &&
352-
decoration->type != DECORATION_REF_HEAD)
353-
continue;
354-
355-
/*
356-
* We only need to update HEAD separately in case it's
357-
* detached. If it's not we'd already update the branch
358-
* it is pointing to.
359-
*/
360-
if (action == REF_ACTION_BRANCHES &&
361-
decoration->type == DECORATION_REF_HEAD &&
362-
!detached_head)
363-
continue;
364-
365-
ret = ref_transaction_update(transaction,
366-
decoration->name,
367-
&rewritten->object.oid,
368-
&original->object.oid,
369-
NULL, NULL, 0, reflog_msg, &err);
370-
if (ret) {
371-
ret = error(_("failed to update ref '%s': %s"),
372-
decoration->name, err.buf);
373-
goto out;
374-
}
375-
}
376-
377-
if (ref_transaction_commit(transaction, &err)) {
378-
ret = error(_("failed to commit ref transaction: %s"), err.buf);
377+
if (action == REF_ACTION_BRANCHES &&
378+
decoration->type == DECORATION_REF_HEAD &&
379+
!detached_head)
380+
continue;
381+
382+
ret = handle_ref_update(transaction,
383+
decoration->name,
384+
&rewritten->object.oid,
385+
&original->object.oid,
386+
reflog_msg, &err);
387+
if (ret) {
388+
ret = error(_("failed to update ref '%s': %s"),
389+
decoration->name, err.buf);
379390
goto out;
380391
}
392+
}
381393

382-
break;
383-
case REF_ACTION_PRINT:
384-
for (size_t i = 0; i < result.updates_nr; i++)
385-
printf("update %s %s %s\n",
386-
result.updates[i].refname,
387-
oid_to_hex(&result.updates[i].new_oid),
388-
oid_to_hex(&result.updates[i].old_oid));
389-
break;
390-
default:
391-
BUG("unsupported ref action %d", action);
394+
if (transaction && ref_transaction_commit(transaction, &err)) {
395+
ret = error(_("failed to commit ref transaction: %s"), err.buf);
396+
goto out;
392397
}
393398

394399
ret = 0;
@@ -410,10 +415,13 @@ static int cmd_history_reword(int argc,
410415
NULL,
411416
};
412417
enum ref_action action = REF_ACTION_DEFAULT;
418+
int dry_run = 0;
413419
struct option options[] = {
414420
OPT_CALLBACK_F(0, "ref-action", &action, N_("<action>"),
415-
N_("control ref update behavior (branches|head|print)"),
421+
N_("control ref update behavior (branches|head)"),
416422
PARSE_OPT_NONEG, parse_ref_action),
423+
OPT_BOOL('n', "dry-run", &dry_run,
424+
N_("perform a dry-run without updating any refs")),
417425
OPT_END(),
418426
};
419427
struct strbuf reflog_msg = STRBUF_INIT;
@@ -450,7 +458,7 @@ static int cmd_history_reword(int argc,
450458
strbuf_addf(&reflog_msg, "reword: updating %s", argv[0]);
451459

452460
ret = handle_reference_updates(&revs, action, original, rewritten,
453-
reflog_msg.buf);
461+
reflog_msg.buf, dry_run);
454462
if (ret < 0) {
455463
ret = error(_("failed replaying descendants"));
456464
goto out;

t/t3451-history-reword.sh

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ test_expect_success 'can reword a merge commit' '
221221
)
222222
'
223223

224-
test_expect_success '--ref-action=print prints ref updates without modifying repo' '
224+
test_expect_success '--dry-run prints ref updates without modifying repo' '
225225
test_when_finished "rm -rf repo" &&
226226
git init repo --initial-branch=main &&
227227
(
@@ -233,7 +233,15 @@ test_expect_success '--ref-action=print prints ref updates without modifying rep
233233
test_commit theirs &&
234234
235235
git refs list >refs-expect &&
236-
reword_with_message --ref-action=print base >updates <<-\EOF &&
236+
reword_with_message --dry-run --ref-action=head base >updates <<-\EOF &&
237+
reworded commit
238+
EOF
239+
git refs list >refs-actual &&
240+
test_cmp refs-expect refs-actual &&
241+
test_grep "update refs/heads/branch" updates &&
242+
test_grep ! "update refs/heads/main" updates &&
243+
244+
reword_with_message --dry-run base >updates <<-\EOF &&
237245
reworded commit
238246
EOF
239247
git refs list >refs-actual &&

0 commit comments

Comments
 (0)