Skip to content

Commit 1073fa1

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-action=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. This patch is best viewed with "--ignore-space-change". Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>
1 parent 0f2a0c5 commit 1073fa1

3 files changed

Lines changed: 98 additions & 78 deletions

File tree

Documentation/git-history.adoc

Lines changed: 9 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,17 @@ 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]. Necessary new
66+
objects will be written into the repository, so applying these printed
67+
ref updates is generally safe.
68+
69+
`--ref-action=(branches|head)`::
6470
Control which references will be updated by the command, if any. With
6571
`branches`, all local branches that point to commits which are
6672
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].
73+
the current `HEAD` reference will be rewritten.
7074

7175
GIT
7276
---

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

@@ -286,11 +283,29 @@ static int setup_revwalk(struct repository *repo,
286283
return ret;
287284
}
288285

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

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

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

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

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

393398
ret = 0;
@@ -409,10 +414,13 @@ static int cmd_history_reword(int argc,
409414
NULL,
410415
};
411416
enum ref_action action = REF_ACTION_DEFAULT;
417+
int dry_run = 0;
412418
struct option options[] = {
413419
OPT_CALLBACK_F(0, "ref-action", &action, N_("<action>"),
414-
N_("control ref update behavior (branches|head|print)"),
420+
N_("control ref update behavior (branches|head)"),
415421
PARSE_OPT_NONEG, parse_ref_action),
422+
OPT_BOOL('n', "dry-run", &dry_run,
423+
N_("perform a dry-run without updating any refs")),
416424
OPT_END(),
417425
};
418426
struct strbuf reflog_msg = STRBUF_INIT;
@@ -449,7 +457,7 @@ static int cmd_history_reword(int argc,
449457
strbuf_addf(&reflog_msg, "reword: updating %s", argv[0]);
450458

451459
ret = handle_reference_updates(&revs, action, original, rewritten,
452-
reflog_msg.buf);
460+
reflog_msg.buf, dry_run);
453461
if (ret < 0) {
454462
ret = error(_("failed replaying descendants"));
455463
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)