Merge branch 'ps/not-so-many-refs-are-special'

Define "special ref" as a very narrow set that consists of
FETCH_HEAD and MERGE_HEAD, and clarify everything else that used to
be classified as such are actually just pseudorefs.

* ps/not-so-many-refs-are-special:
  Documentation: add "special refs" to the glossary
  refs: redefine special refs
  refs: convert MERGE_AUTOSTASH to become a normal pseudo-ref
  sequencer: introduce functions to handle autostashes via refs
  refs: convert AUTO_MERGE to become a normal pseudo-ref
  sequencer: delete REBASE_HEAD in correct repo when picking commits
  sequencer: clean up pseudo refs with REF_NO_DEREF
maint
Junio C Hamano 2024-01-29 16:03:00 -08:00
commit 8282f95928
13 changed files with 132 additions and 78 deletions

View File

@ -638,6 +638,20 @@ The most notable example is `HEAD`.
An <<def_object,object>> used to temporarily store the contents of a
<<def_dirty,dirty>> working directory and the index for future reuse.

[[def_special_ref]]special ref::
A ref that has different semantics than normal refs. These refs can be
accessed via normal Git commands but may not behave the same as a
normal ref in some cases.
+
The following special refs are known to Git:

- "`FETCH_HEAD`" is written by linkgit:git-fetch[1] or linkgit:git-pull[1]. It
may refer to multiple object IDs. Each object ID is annotated with metadata
indicating where it was fetched from and its fetch status.

- "`MERGE_HEAD`" is written by linkgit:git-merge[1] when resolving merge
conflicts. It contains all commit IDs which are being merged.

[[def_submodule]]submodule::
A <<def_repository,repository>> that holds the history of a
separate project inside another repository (the latter of

View File

@ -817,8 +817,9 @@ void remove_merge_branch_state(struct repository *r)
unlink(git_path_merge_rr(r));
unlink(git_path_merge_msg(r));
unlink(git_path_merge_mode(r));
unlink(git_path_auto_merge(r));
save_autostash(git_path_merge_autostash(r));
refs_delete_ref(get_main_ref_store(r), "", "AUTO_MERGE",
NULL, REF_NO_DEREF);
save_autostash_ref(r, "MERGE_AUTOSTASH");
}

void remove_branch_state(struct repository *r, int verbose)

View File

@ -1877,7 +1877,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
&oid, flags);
}

apply_autostash(git_path_merge_autostash(the_repository));
apply_autostash_ref(the_repository, "MERGE_AUTOSTASH");

cleanup:
strbuf_release(&author_ident);

View File

@ -476,7 +476,7 @@ static void finish(struct commit *head_commit,
run_hooks_l("post-merge", squash ? "1" : "0", NULL);

if (new_head)
apply_autostash(git_path_merge_autostash(the_repository));
apply_autostash_ref(the_repository, "MERGE_AUTOSTASH");
strbuf_release(&reflog_message);
}

@ -1315,7 +1315,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
if (abort_current_merge) {
int nargc = 2;
const char *nargv[] = {"reset", "--merge", NULL};
struct strbuf stash_oid = STRBUF_INIT;
char stash_oid_hex[GIT_MAX_HEXSZ + 1];
struct object_id stash_oid = {0};

if (orig_argc != 2)
usage_msg_opt(_("--abort expects no arguments"),
@ -1324,17 +1325,17 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
if (!file_exists(git_path_merge_head(the_repository)))
die(_("There is no merge to abort (MERGE_HEAD missing)."));

if (read_oneliner(&stash_oid, git_path_merge_autostash(the_repository),
READ_ONELINER_SKIP_IF_EMPTY))
unlink(git_path_merge_autostash(the_repository));
if (!read_ref("MERGE_AUTOSTASH", &stash_oid))
delete_ref("", "MERGE_AUTOSTASH", &stash_oid, REF_NO_DEREF);

/* Invoke 'git reset --merge' */
ret = cmd_reset(nargc, nargv, prefix);

if (stash_oid.len)
apply_autostash_oid(stash_oid.buf);
if (!is_null_oid(&stash_oid)) {
oid_to_hex_r(stash_oid_hex, &stash_oid);
apply_autostash_oid(stash_oid_hex);
}

strbuf_release(&stash_oid);
goto done;
}

@ -1563,13 +1564,12 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
}

if (autostash)
create_autostash(the_repository,
git_path_merge_autostash(the_repository));
create_autostash_ref(the_repository, "MERGE_AUTOSTASH");
if (checkout_fast_forward(the_repository,
&head_commit->object.oid,
&commit->object.oid,
overwrite_ignore)) {
apply_autostash(git_path_merge_autostash(the_repository));
apply_autostash_ref(the_repository, "MERGE_AUTOSTASH");
ret = 1;
goto done;
}
@ -1655,8 +1655,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
die_ff_impossible();

if (autostash)
create_autostash(the_repository,
git_path_merge_autostash(the_repository));
create_autostash_ref(the_repository, "MERGE_AUTOSTASH");

/* We are going to make a new commit. */
git_committer_info(IDENT_STRICT);
@ -1741,7 +1740,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
else
fprintf(stderr, _("Merge with strategy %s failed.\n"),
use_strategies[0]->name);
apply_autostash(git_path_merge_autostash(the_repository));
apply_autostash_ref(the_repository, "MERGE_AUTOSTASH");
ret = 2;
goto done;
} else if (best_strategy == wt_strategy)

View File

@ -515,7 +515,7 @@ static int finish_rebase(struct rebase_options *opts)
int ret = 0;

delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF);
unlink(git_path_auto_merge(the_repository));
delete_ref(NULL, "AUTO_MERGE", NULL, REF_NO_DEREF);
apply_autostash(state_dir_path("autostash", opts));
/*
* We ignore errors in 'git maintenance run --auto', since the

View File

@ -38,6 +38,7 @@
#include "path.h"
#include "promisor-remote.h"
#include "read-cache-ll.h"
#include "refs.h"
#include "revision.h"
#include "sparse-index.h"
#include "strmap.h"
@ -4659,9 +4660,6 @@ void merge_switch_to_result(struct merge_options *opt,
{
assert(opt->priv == NULL);
if (result->clean >= 0 && update_worktree_and_index) {
const char *filename;
FILE *fp;

trace2_region_enter("merge", "checkout", opt->repo);
if (checkout(opt, head, result->tree)) {
/* failure to function */
@ -4687,10 +4685,17 @@ void merge_switch_to_result(struct merge_options *opt,
trace2_region_leave("merge", "record_conflicted", opt->repo);

trace2_region_enter("merge", "write_auto_merge", opt->repo);
filename = git_path_auto_merge(opt->repo);
fp = xfopen(filename, "w");
fprintf(fp, "%s\n", oid_to_hex(&result->tree->object.oid));
fclose(fp);
if (refs_update_ref(get_main_ref_store(opt->repo), "", "AUTO_MERGE",
&result->tree->object.oid, NULL, REF_NO_DEREF,
UPDATE_REFS_MSG_ON_ERR)) {
/* failure to function */
opt->priv = NULL;
result->clean = -1;
merge_finalize(opt, result);
trace2_region_leave("merge", "write_auto_merge",
opt->repo);
return;
}
trace2_region_leave("merge", "write_auto_merge", opt->repo);
}
if (display_update_msgs)

2
path.c
View File

@ -1588,7 +1588,5 @@ REPO_GIT_PATH_FUNC(merge_msg, "MERGE_MSG")
REPO_GIT_PATH_FUNC(merge_rr, "MERGE_RR")
REPO_GIT_PATH_FUNC(merge_mode, "MERGE_MODE")
REPO_GIT_PATH_FUNC(merge_head, "MERGE_HEAD")
REPO_GIT_PATH_FUNC(merge_autostash, "MERGE_AUTOSTASH")
REPO_GIT_PATH_FUNC(auto_merge, "AUTO_MERGE")
REPO_GIT_PATH_FUNC(fetch_head, "FETCH_HEAD")
REPO_GIT_PATH_FUNC(shallow, "shallow")

2
path.h
View File

@ -175,8 +175,6 @@ const char *git_path_merge_msg(struct repository *r);
const char *git_path_merge_rr(struct repository *r);
const char *git_path_merge_mode(struct repository *r);
const char *git_path_merge_head(struct repository *r);
const char *git_path_merge_autostash(struct repository *r);
const char *git_path_auto_merge(struct repository *r);
const char *git_path_fetch_head(struct repository *r);
const char *git_path_shallow(struct repository *r);


35
refs.c
View File

@ -1839,13 +1839,10 @@ done:
static int is_special_ref(const char *refname)
{
/*
* Special references get written and read directly via the filesystem
* by the subsystems that create them. Thus, they must not go through
* the reference backend but must instead be read directly. It is
* arguable whether this behaviour is sensible, or whether it's simply
* a leaky abstraction enabled by us only having a single reference
* backend implementation. But at least for a subset of references it
* indeed does make sense to treat them specially:
* Special references are refs that have different semantics compared
* to "normal" refs. These refs can thus not be stored in the ref
* backend, but must always be accessed via the filesystem. The
* following refs are special:
*
* - FETCH_HEAD may contain multiple object IDs, and each one of them
* carries additional metadata like where it came from.
@ -1853,30 +1850,12 @@ static int is_special_ref(const char *refname)
* - MERGE_HEAD may contain multiple object IDs when merging multiple
* heads.
*
* There are some exceptions that you might expect to see on this list
* but which are handled exclusively via the reference backend:
*
* - BISECT_EXPECTED_REV
*
* - CHERRY_PICK_HEAD
*
* - HEAD
*
* - ORIG_HEAD
*
* - "rebase-apply/" and "rebase-merge/" contain all of the state for
* rebases, including some reference-like files. These are
* exclusively read and written via the filesystem and never go
* through the refdb.
*
* Writing or deleting references must consistently go either through
* the filesystem (special refs) or through the reference backend
* (normal ones).
* Reading, writing or deleting references must consistently go either
* through the filesystem (special refs) or through the reference
* backend (normal ones).
*/
static const char * const special_refs[] = {
"AUTO_MERGE",
"FETCH_HEAD",
"MERGE_AUTOSTASH",
"MERGE_HEAD",
};
size_t i;

View File

@ -262,8 +262,6 @@ static void repo_clear_path_cache(struct repo_path_cache *cache)
FREE_AND_NULL(cache->merge_rr);
FREE_AND_NULL(cache->merge_mode);
FREE_AND_NULL(cache->merge_head);
FREE_AND_NULL(cache->merge_autostash);
FREE_AND_NULL(cache->auto_merge);
FREE_AND_NULL(cache->fetch_head);
FREE_AND_NULL(cache->shallow);
}

View File

@ -67,8 +67,6 @@ struct repo_path_cache {
char *merge_rr;
char *merge_mode;
char *merge_head;
char *merge_autostash;
char *auto_merge;
char *fetch_head;
char *shallow;
};

View File

@ -474,7 +474,7 @@ static void print_advice(struct repository *r, int show_hint,
* of the commit itself so remove CHERRY_PICK_HEAD
*/
refs_delete_ref(get_main_ref_store(r), "", "CHERRY_PICK_HEAD",
NULL, 0);
NULL, REF_NO_DEREF);
return;
}

@ -1667,7 +1667,7 @@ static int do_commit(struct repository *r,
strbuf_release(&sb);
if (!res) {
refs_delete_ref(get_main_ref_store(r), "",
"CHERRY_PICK_HEAD", NULL, 0);
"CHERRY_PICK_HEAD", NULL, REF_NO_DEREF);
unlink(git_path_merge_msg(r));
if (!is_rebase_i(opts))
print_commit_summary(r, NULL, &oid,
@ -2406,9 +2406,10 @@ static int do_pick_commit(struct repository *r,
} else if (allow == 2) {
drop_commit = 1;
refs_delete_ref(get_main_ref_store(r), "", "CHERRY_PICK_HEAD",
NULL, 0);
NULL, REF_NO_DEREF);
unlink(git_path_merge_msg(r));
unlink(git_path_auto_merge(r));
refs_delete_ref(get_main_ref_store(r), "", "AUTO_MERGE",
NULL, REF_NO_DEREF);
fprintf(stderr,
_("dropping %s %s -- patch contents already upstream\n"),
oid_to_hex(&commit->object.oid), msg.subject);
@ -2802,7 +2803,7 @@ void sequencer_post_commit_cleanup(struct repository *r, int verbose)

if (refs_ref_exists(get_main_ref_store(r), "CHERRY_PICK_HEAD")) {
if (!refs_delete_ref(get_main_ref_store(r), "",
"CHERRY_PICK_HEAD", NULL, 0) &&
"CHERRY_PICK_HEAD", NULL, REF_NO_DEREF) &&
verbose)
warning(_("cancelling a cherry picking in progress"));
opts.action = REPLAY_PICK;
@ -2811,14 +2812,15 @@ void sequencer_post_commit_cleanup(struct repository *r, int verbose)

if (refs_ref_exists(get_main_ref_store(r), "REVERT_HEAD")) {
if (!refs_delete_ref(get_main_ref_store(r), "", "REVERT_HEAD",
NULL, 0) &&
NULL, REF_NO_DEREF) &&
verbose)
warning(_("cancelling a revert in progress"));
opts.action = REPLAY_REVERT;
need_cleanup = 1;
}

unlink(git_path_auto_merge(r));
refs_delete_ref(get_main_ref_store(r), "", "AUTO_MERGE",
NULL, REF_NO_DEREF);

if (!need_cleanup)
return;
@ -4116,7 +4118,7 @@ static int do_merge(struct repository *r,

strbuf_release(&ref_name);
refs_delete_ref(get_main_ref_store(r), "", "CHERRY_PICK_HEAD",
NULL, 0);
NULL, REF_NO_DEREF);
rollback_lock_file(&lock);

ret = run_command(&cmd);
@ -4461,12 +4463,17 @@ static enum todo_command peek_command(struct todo_list *todo_list, int offset)
return -1;
}

void create_autostash(struct repository *r, const char *path)
static void create_autostash_internal(struct repository *r,
const char *path,
const char *refname)
{
struct strbuf buf = STRBUF_INIT;
struct lock_file lock_file = LOCK_INIT;
int fd;

if (path && refname)
BUG("can only pass path or refname");

fd = repo_hold_locked_index(r, &lock_file, 0);
refresh_index(r->index, REFRESH_QUIET, NULL, NULL, NULL);
if (0 <= fd)
@ -4493,10 +4500,16 @@ void create_autostash(struct repository *r, const char *path)
strbuf_reset(&buf);
strbuf_add_unique_abbrev(&buf, &oid, DEFAULT_ABBREV);

if (safe_create_leading_directories_const(path))
die(_("Could not create directory for '%s'"),
path);
write_file(path, "%s", oid_to_hex(&oid));
if (path) {
if (safe_create_leading_directories_const(path))
die(_("Could not create directory for '%s'"),
path);
write_file(path, "%s", oid_to_hex(&oid));
} else {
refs_update_ref(get_main_ref_store(r), "", refname,
&oid, null_oid(), 0, UPDATE_REFS_DIE_ON_ERR);
}

printf(_("Created autostash: %s\n"), buf.buf);
if (reset_head(r, &ropts) < 0)
die(_("could not reset --hard"));
@ -4507,6 +4520,16 @@ void create_autostash(struct repository *r, const char *path)
strbuf_release(&buf);
}

void create_autostash(struct repository *r, const char *path)
{
create_autostash_internal(r, path, NULL);
}

void create_autostash_ref(struct repository *r, const char *refname)
{
create_autostash_internal(r, NULL, refname);
}

static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)
{
struct child_process child = CHILD_PROCESS_INIT;
@ -4584,6 +4607,41 @@ int apply_autostash_oid(const char *stash_oid)
return apply_save_autostash_oid(stash_oid, 1);
}

static int apply_save_autostash_ref(struct repository *r, const char *refname,
int attempt_apply)
{
struct object_id stash_oid;
char stash_oid_hex[GIT_MAX_HEXSZ + 1];
int flag, ret;

if (!refs_ref_exists(get_main_ref_store(r), refname))
return 0;

if (!refs_resolve_ref_unsafe(get_main_ref_store(r), refname,
RESOLVE_REF_READING, &stash_oid, &flag))
return -1;
if (flag & REF_ISSYMREF)
return error(_("autostash reference is a symref"));

oid_to_hex_r(stash_oid_hex, &stash_oid);
ret = apply_save_autostash_oid(stash_oid_hex, attempt_apply);

refs_delete_ref(get_main_ref_store(r), "", refname,
&stash_oid, REF_NO_DEREF);

return ret;
}

int save_autostash_ref(struct repository *r, const char *refname)
{
return apply_save_autostash_ref(r, refname, 0);
}

int apply_autostash_ref(struct repository *r, const char *refname)
{
return apply_save_autostash_ref(r, refname, 1);
}

static int checkout_onto(struct repository *r, struct replay_opts *opts,
const char *onto_name, const struct object_id *onto,
const struct object_id *orig_head)
@ -4766,8 +4824,10 @@ static int pick_commits(struct repository *r,
}
unlink(rebase_path_author_script());
unlink(git_path_merge_head(r));
unlink(git_path_auto_merge(r));
delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF);
refs_delete_ref(get_main_ref_store(r), "", "AUTO_MERGE",
NULL, REF_NO_DEREF);
refs_delete_ref(get_main_ref_store(r), "", "REBASE_HEAD",
NULL, REF_NO_DEREF);

if (item->command == TODO_BREAK) {
if (!opts->verbose)
@ -5108,7 +5168,7 @@ static int commit_staged_changes(struct repository *r,
if (refs_ref_exists(get_main_ref_store(r),
"CHERRY_PICK_HEAD") &&
refs_delete_ref(get_main_ref_store(r), "",
"CHERRY_PICK_HEAD", NULL, 0))
"CHERRY_PICK_HEAD", NULL, REF_NO_DEREF))
return error(_("could not remove CHERRY_PICK_HEAD"));
if (unlink(git_path_merge_msg(r)) && errno != ENOENT)
return error_errno(_("could not remove '%s'"),
@ -5122,7 +5182,8 @@ static int commit_staged_changes(struct repository *r,
return error(_("could not commit staged changes."));
unlink(rebase_path_amend());
unlink(git_path_merge_head(r));
unlink(git_path_auto_merge(r));
refs_delete_ref(get_main_ref_store(r), "", "AUTO_MERGE",
NULL, REF_NO_DEREF);
if (final_fixup) {
unlink(rebase_path_fixup_msg());
unlink(rebase_path_squash_msg());

View File

@ -225,9 +225,12 @@ void commit_post_rewrite(struct repository *r,
const struct object_id *new_head);

void create_autostash(struct repository *r, const char *path);
void create_autostash_ref(struct repository *r, const char *refname);
int save_autostash(const char *path);
int save_autostash_ref(struct repository *r, const char *refname);
int apply_autostash(const char *path);
int apply_autostash_oid(const char *stash_oid);
int apply_autostash_ref(struct repository *r, const char *refname);

#define SUMMARY_INITIAL_COMMIT (1 << 0)
#define SUMMARY_SHOW_AUTHOR_DATE (1 << 1)