Merge branch 'en/ps-history-some' into seen

* en/ps-history-some:
  history: fix detached HEAD handling
  SQUASH ME: Fixups
This commit is contained in:
Junio C Hamano 2026-01-11 10:32:20 -08:00
commit 7cfdc50b1b
6 changed files with 87 additions and 52 deletions

View File

@ -63,7 +63,7 @@ OPTIONS
`--ref-action=(branches|head|print)`::
Control which references will be updated by the command, if any. With
`branches`, all local branches that point to commits which are
decendants of the original commit will be rewritten. With `head`, only
descendants of the original commit will be rewritten. With `head`, only
the current `HEAD` reference will be rewritten. With `print`, all
updates as they would be performed with `branches` are printed in a
format that can be consumed by linkgit:git-update-ref[1].

View File

@ -178,17 +178,23 @@ static int handle_reference_updates(enum ref_action action,
{
const struct name_decoration *decoration;
struct replay_revisions_options opts = { 0 };
struct replay_result result = {
.final_oid = rewritten->object.oid,
};
struct replay_result result = { 0 };
struct ref_transaction *transaction = NULL;
struct strvec args = STRVEC_INIT;
struct strbuf err = STRBUF_INIT;
struct commit *head = NULL;
char *head_ref = NULL;
bool detached_head = false;
struct rev_info revs;
char hex[GIT_MAX_HEXSZ + 1];
int ret;
head_ref = refs_resolve_refdup(get_main_ref_store(repo), "HEAD",
RESOLVE_REF_READING, NULL, NULL);
if (!strcmp(head_ref, "HEAD"))
detached_head = true;
free(head_ref);
repo_init_revisions(repo, &revs, NULL);
strvec_push(&args, "ignored");
strvec_push(&args, "--reverse");
@ -233,9 +239,10 @@ static int handle_reference_updates(enum ref_action action,
goto out;
}
strvec_push(&args, oid_to_hex(&head->object.oid));
strvec_push(&args, "HEAD");
} else {
strvec_push(&args, "--branches");
strvec_push(&args, "HEAD");
}
setup_revisions_from_strvec(&args, &revs, NULL);
@ -244,13 +251,14 @@ static int handle_reference_updates(enum ref_action action,
opts.onto = oid_to_hex_r(hex, &rewritten->object.oid);
ret = replay_revisions(repo, &revs, &opts, &result);
ret = replay_revisions(&revs, &opts, &result);
if (ret)
goto out;
switch (action) {
case REF_ACTION_DEFAULT:
case REF_ACTION_BRANCHES:
case REF_ACTION_HEAD:
transaction = ref_store_transaction_begin(get_main_ref_store(repo), 0, &err);
if (!transaction) {
ret = error(_("failed to begin ref transaction: %s"), err.buf);
@ -279,9 +287,11 @@ static int handle_reference_updates(enum ref_action action,
decoration;
decoration = decoration->next)
{
if (decoration->type != DECORATION_REF_LOCAL)
if ((decoration->type != DECORATION_REF_HEAD ||
(action != REF_ACTION_HEAD && !detached_head)) &&
(decoration->type != DECORATION_REF_LOCAL ||
action == REF_ACTION_HEAD))
continue;
ret = ref_transaction_update(transaction,
decoration->name,
&rewritten->object.oid,
@ -300,13 +310,6 @@ static int handle_reference_updates(enum ref_action action,
}
break;
case REF_ACTION_HEAD:
ret = refs_update_ref(get_main_ref_store(repo), reflog_msg, "HEAD",
&result.final_oid, &head->object.oid, 0,
UPDATE_REFS_MSG_ON_ERR);
if (ret)
goto out;
break;
case REF_ACTION_PRINT:
for (size_t i = 0; i < result.updates_nr; i++)
printf("update %s %s %s\n",

View File

@ -167,7 +167,7 @@ int cmd_replay(int argc,
revs.simplify_history = 0;
}
ret = replay_revisions(repo, &revs, &opts, &result);
ret = replay_revisions(&revs, &opts, &result);
if (ret)
goto cleanup;
@ -220,11 +220,5 @@ cleanup:
strbuf_release(&reflog_msg);
release_revisions(&revs);
if (ret) {
if (result.merge_conflict)
return 1;
return 128;
}
return 0;
return ret;
}

View File

@ -5,11 +5,11 @@
#include "hex.h"
#include "merge-ort.h"
#include "object-name.h"
#include "oidset.h"
#include "parse-options.h"
#include "refs.h"
#include "replay.h"
#include "revision.h"
#include "strmap.h"
#include "tree.h"
static const char *short_commit_name(struct repository *repo,
@ -151,11 +151,20 @@ static void get_ref_information(struct repository *repo,
static void set_up_replay_mode(struct repository *repo,
struct rev_cmdline_info *cmd_info,
const char *onto_name,
bool *detached_head,
char **advance_name,
struct commit **onto,
struct strset **update_refs)
{
struct ref_info rinfo;
char *head_ref;
*detached_head = false;
head_ref = refs_resolve_refdup(get_main_ref_store(repo), "HEAD",
RESOLVE_REF_READING, NULL, NULL);
if (!strcmp(head_ref, "HEAD"))
*detached_head = true;
free(head_ref);
get_ref_information(repo, cmd_info, &rinfo);
if (!rinfo.positive_refexprs)
@ -256,7 +265,7 @@ static void replay_result_queue_update(struct replay_result *result,
result->updates_nr++;
}
int replay_revisions(struct repository *repo, struct rev_info *revs,
int replay_revisions(struct rev_info *revs,
struct replay_revisions_options *opts,
struct replay_result *out)
{
@ -265,16 +274,18 @@ int replay_revisions(struct repository *repo, struct rev_info *revs,
struct commit *last_commit = NULL;
struct commit *commit;
struct commit *onto = NULL;
struct repository *repo = revs->repo;
struct merge_options merge_opt;
struct merge_result result = {
.clean = 1,
};
char *advance;
bool detached_head;
int ret;
advance = xstrdup_or_null(opts->advance);
set_up_replay_mode(repo, &revs->cmdline, opts->onto, &advance,
&onto, &update_refs);
set_up_replay_mode(repo, &revs->cmdline, opts->onto,
&detached_head, &advance, &onto, &update_refs);
/* FIXME: Should allow replaying commits with the first as a root commit */
@ -316,7 +327,9 @@ int replay_revisions(struct repository *repo, struct rev_info *revs,
if (!decoration)
continue;
while (decoration) {
if (decoration->type == DECORATION_REF_LOCAL &&
if ((decoration->type == DECORATION_REF_LOCAL ||
(decoration->type == DECORATION_REF_HEAD &&
detached_head)) &&
(opts->contained || strset_contains(update_refs,
decoration->name))) {
replay_result_queue_update(out, decoration->name,
@ -328,8 +341,7 @@ int replay_revisions(struct repository *repo, struct rev_info *revs,
}
if (!result.clean) {
out->merge_conflict = true;
ret = -1;
ret = 1;
goto out;
}
@ -339,8 +351,6 @@ int replay_revisions(struct repository *repo, struct rev_info *revs,
&onto->object.oid,
&last_commit->object.oid);
out->final_oid = last_commit->object.oid;
ret = 0;
out:

View File

@ -43,25 +43,6 @@ struct replay_result {
struct object_id new_oid;
} *updates;
size_t updates_nr, updates_alloc;
/* Set to true in case the replay failed with a merge conflict. */
bool merge_conflict;
/*
* The final object ID that was rewritten. Note that this field has
* somewhat special semantics and may or may not be what you want:
*
* - If no commits were rewritten it will remain uninitialized.
*
* - If a thicket of branches is rewritten it is undefined in which
* order those branches will be rewritten, and thus the final object
* ID may point to a different commit than you'd expect.
*
* That being said, this field can still be useful when you know that
* you only replay a single strand of commits. In that case, the final
* commit will point to the tip of the rewritten strand of commits.
*/
struct object_id final_oid;
};
void replay_result_release(struct replay_result *result);
@ -73,7 +54,7 @@ void replay_result_release(struct replay_result *result);
*
* Returns 0 on success, a negative error code otherwise.
*/
int replay_revisions(struct repository *repo, struct rev_info *revs,
int replay_revisions(struct rev_info *revs,
struct replay_revisions_options *opts,
struct replay_result *out);

View File

@ -77,6 +77,53 @@ test_expect_success 'can reword commit in the middle' '
)
'
test_expect_success 'can reword commit in the middle even on detached head' '
test_when_finished "rm -rf repo" &&
git init repo &&
(
cd repo &&
test_commit first &&
test_commit second &&
test_commit third_on_main &&
git checkout --detach HEAD^ &&
test_commit third_on_head &&
reword_with_message HEAD~ <<-EOF &&
second reworded
EOF
expect_log HEAD --branches --graph <<-\EOF
* third_on_head
| * third_on_main
|/
* second reworded
* first
EOF
)
'
test_expect_success 'can reword the detached head' '
test_when_finished "rm -rf repo" &&
git init repo &&
(
cd repo &&
test_commit first &&
test_commit second &&
git checkout --detach HEAD &&
test_commit third &&
reword_with_message HEAD <<-EOF &&
third reworded
EOF
expect_log <<-\EOF
third reworded
second
first
EOF
)
'
test_expect_success 'can reword root commit' '
test_when_finished "rm -rf repo" &&
git init repo &&