Merge branch 'dt/refs-pseudo'
To prepare for allowing a different "ref" backend to be plugged in to the system, update_ref()/delete_ref() have been taught about ref-like things like MERGE_HEAD that are per-worktree (they will always be written to the filesystem inside $GIT_DIR). * dt/refs-pseudo: pseudoref: check return values from read_ref() sequencer: replace write_cherry_pick_head with update_ref bisect: use update_ref pseudorefs: create and use pseudoref update and delete functions refs: add ref_type function refs: introduce pseudoref and per-worktree ref concepts
This commit is contained in:
commit
080cc64663
@ -411,6 +411,27 @@ exclude;;
|
|||||||
core Git. Porcelains expose more of a <<def_SCM,SCM>>
|
core Git. Porcelains expose more of a <<def_SCM,SCM>>
|
||||||
interface than the <<def_plumbing,plumbing>>.
|
interface than the <<def_plumbing,plumbing>>.
|
||||||
|
|
||||||
|
[[def_per_worktree_ref]]per-worktree ref::
|
||||||
|
Refs that are per-<<def_working_tree,worktree>>, rather than
|
||||||
|
global. This is presently only <<def_HEAD,HEAD>>, but might
|
||||||
|
later include other unusual refs.
|
||||||
|
|
||||||
|
[[def_pseudoref]]pseudoref::
|
||||||
|
Pseudorefs are a class of files under `$GIT_DIR` which behave
|
||||||
|
like refs for the purposes of rev-parse, but which are treated
|
||||||
|
specially by git. Pseudorefs both have names that are all-caps,
|
||||||
|
and always start with a line consisting of a
|
||||||
|
<<def_SHA1,SHA-1>> followed by whitespace. So, HEAD is not a
|
||||||
|
pseudoref, because it is sometimes a symbolic ref. They might
|
||||||
|
optionally contain some additional data. `MERGE_HEAD` and
|
||||||
|
`CHERRY_PICK_HEAD` are examples. Unlike
|
||||||
|
<<def_per_worktree_ref,per-worktree refs>>, these files cannot
|
||||||
|
be symbolic refs, and never have reflogs. They also cannot be
|
||||||
|
updated through the normal ref update machinery. Instead,
|
||||||
|
they are updated by directly writing to the files. However,
|
||||||
|
they can be read as if they were refs, so `git rev-parse
|
||||||
|
MERGE_HEAD` will work.
|
||||||
|
|
||||||
[[def_pull]]pull::
|
[[def_pull]]pull::
|
||||||
Pulling a <<def_branch,branch>> means to <<def_fetch,fetch>> it and
|
Pulling a <<def_branch,branch>> means to <<def_fetch,fetch>> it and
|
||||||
<<def_merge,merge>> it. See also linkgit:git-pull[1].
|
<<def_merge,merge>> it. See also linkgit:git-pull[1].
|
||||||
|
37
bisect.c
37
bisect.c
@ -19,7 +19,6 @@ static struct object_id *current_bad_oid;
|
|||||||
|
|
||||||
static const char *argv_checkout[] = {"checkout", "-q", NULL, "--", NULL};
|
static const char *argv_checkout[] = {"checkout", "-q", NULL, "--", NULL};
|
||||||
static const char *argv_show_branch[] = {"show-branch", NULL, NULL};
|
static const char *argv_show_branch[] = {"show-branch", NULL, NULL};
|
||||||
static const char *argv_update_ref[] = {"update-ref", "--no-deref", "BISECT_HEAD", NULL, NULL};
|
|
||||||
|
|
||||||
static const char *term_bad;
|
static const char *term_bad;
|
||||||
static const char *term_good;
|
static const char *term_good;
|
||||||
@ -678,34 +677,16 @@ static int is_expected_rev(const struct object_id *oid)
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void mark_expected_rev(char *bisect_rev_hex)
|
static int bisect_checkout(const unsigned char *bisect_rev, int no_checkout)
|
||||||
{
|
{
|
||||||
int len = strlen(bisect_rev_hex);
|
char bisect_rev_hex[GIT_SHA1_HEXSZ + 1];
|
||||||
const char *filename = git_path("BISECT_EXPECTED_REV");
|
|
||||||
int fd = open(filename, O_CREAT | O_TRUNC | O_WRONLY, 0600);
|
|
||||||
|
|
||||||
if (fd < 0)
|
memcpy(bisect_rev_hex, sha1_to_hex(bisect_rev), GIT_SHA1_HEXSZ + 1);
|
||||||
die_errno("could not create file '%s'", filename);
|
update_ref(NULL, "BISECT_EXPECTED_REV", bisect_rev, NULL, 0, UPDATE_REFS_DIE_ON_ERR);
|
||||||
|
|
||||||
bisect_rev_hex[len] = '\n';
|
|
||||||
write_or_die(fd, bisect_rev_hex, len + 1);
|
|
||||||
bisect_rev_hex[len] = '\0';
|
|
||||||
|
|
||||||
if (close(fd) < 0)
|
|
||||||
die("closing file %s: %s", filename, strerror(errno));
|
|
||||||
}
|
|
||||||
|
|
||||||
static int bisect_checkout(char *bisect_rev_hex, int no_checkout)
|
|
||||||
{
|
|
||||||
|
|
||||||
mark_expected_rev(bisect_rev_hex);
|
|
||||||
|
|
||||||
argv_checkout[2] = bisect_rev_hex;
|
argv_checkout[2] = bisect_rev_hex;
|
||||||
if (no_checkout) {
|
if (no_checkout) {
|
||||||
argv_update_ref[3] = bisect_rev_hex;
|
update_ref(NULL, "BISECT_HEAD", bisect_rev, NULL, 0, UPDATE_REFS_DIE_ON_ERR);
|
||||||
if (run_command_v_opt(argv_update_ref, RUN_GIT_CMD))
|
|
||||||
die("update-ref --no-deref HEAD failed on %s",
|
|
||||||
bisect_rev_hex);
|
|
||||||
} else {
|
} else {
|
||||||
int res;
|
int res;
|
||||||
res = run_command_v_opt(argv_checkout, RUN_GIT_CMD);
|
res = run_command_v_opt(argv_checkout, RUN_GIT_CMD);
|
||||||
@ -807,7 +788,7 @@ static void check_merge_bases(int no_checkout)
|
|||||||
handle_skipped_merge_base(mb);
|
handle_skipped_merge_base(mb);
|
||||||
} else {
|
} else {
|
||||||
printf("Bisecting: a merge base must be tested\n");
|
printf("Bisecting: a merge base must be tested\n");
|
||||||
exit(bisect_checkout(sha1_to_hex(mb), no_checkout));
|
exit(bisect_checkout(mb, no_checkout));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -951,7 +932,6 @@ int bisect_next_all(const char *prefix, int no_checkout)
|
|||||||
struct commit_list *tried;
|
struct commit_list *tried;
|
||||||
int reaches = 0, all = 0, nr, steps;
|
int reaches = 0, all = 0, nr, steps;
|
||||||
const unsigned char *bisect_rev;
|
const unsigned char *bisect_rev;
|
||||||
char bisect_rev_hex[GIT_SHA1_HEXSZ + 1];
|
|
||||||
|
|
||||||
read_bisect_terms(&term_bad, &term_good);
|
read_bisect_terms(&term_bad, &term_good);
|
||||||
if (read_bisect_refs())
|
if (read_bisect_refs())
|
||||||
@ -989,11 +969,10 @@ int bisect_next_all(const char *prefix, int no_checkout)
|
|||||||
}
|
}
|
||||||
|
|
||||||
bisect_rev = revs.commits->item->object.sha1;
|
bisect_rev = revs.commits->item->object.sha1;
|
||||||
memcpy(bisect_rev_hex, sha1_to_hex(bisect_rev), GIT_SHA1_HEXSZ + 1);
|
|
||||||
|
|
||||||
if (!hashcmp(bisect_rev, current_bad_oid->hash)) {
|
if (!hashcmp(bisect_rev, current_bad_oid->hash)) {
|
||||||
exit_if_skipped_commits(tried, current_bad_oid);
|
exit_if_skipped_commits(tried, current_bad_oid);
|
||||||
printf("%s is the first %s commit\n", bisect_rev_hex,
|
printf("%s is the first %s commit\n", sha1_to_hex(bisect_rev),
|
||||||
term_bad);
|
term_bad);
|
||||||
show_diff_tree(prefix, revs.commits->item);
|
show_diff_tree(prefix, revs.commits->item);
|
||||||
/* This means the bisection process succeeded. */
|
/* This means the bisection process succeeded. */
|
||||||
@ -1006,7 +985,7 @@ int bisect_next_all(const char *prefix, int no_checkout)
|
|||||||
"(roughly %d step%s)\n", nr, (nr == 1 ? "" : "s"),
|
"(roughly %d step%s)\n", nr, (nr == 1 ? "" : "s"),
|
||||||
steps, (steps == 1 ? "" : "s"));
|
steps, (steps == 1 ? "" : "s"));
|
||||||
|
|
||||||
return bisect_checkout(bisect_rev_hex, no_checkout);
|
return bisect_checkout(bisect_rev, no_checkout);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline int log2i(int n)
|
static inline int log2i(int n)
|
||||||
|
130
refs.c
130
refs.c
@ -2854,12 +2854,117 @@ static int delete_ref_loose(struct ref_lock *lock, int flag, struct strbuf *err)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int is_per_worktree_ref(const char *refname)
|
||||||
|
{
|
||||||
|
return !strcmp(refname, "HEAD");
|
||||||
|
}
|
||||||
|
|
||||||
|
static int is_pseudoref_syntax(const char *refname)
|
||||||
|
{
|
||||||
|
const char *c;
|
||||||
|
|
||||||
|
for (c = refname; *c; c++) {
|
||||||
|
if (!isupper(*c) && *c != '-' && *c != '_')
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ref_type ref_type(const char *refname)
|
||||||
|
{
|
||||||
|
if (is_per_worktree_ref(refname))
|
||||||
|
return REF_TYPE_PER_WORKTREE;
|
||||||
|
if (is_pseudoref_syntax(refname))
|
||||||
|
return REF_TYPE_PSEUDOREF;
|
||||||
|
return REF_TYPE_NORMAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int write_pseudoref(const char *pseudoref, const unsigned char *sha1,
|
||||||
|
const unsigned char *old_sha1, struct strbuf *err)
|
||||||
|
{
|
||||||
|
const char *filename;
|
||||||
|
int fd;
|
||||||
|
static struct lock_file lock;
|
||||||
|
struct strbuf buf = STRBUF_INIT;
|
||||||
|
int ret = -1;
|
||||||
|
|
||||||
|
strbuf_addf(&buf, "%s\n", sha1_to_hex(sha1));
|
||||||
|
|
||||||
|
filename = git_path("%s", pseudoref);
|
||||||
|
fd = hold_lock_file_for_update(&lock, filename, LOCK_DIE_ON_ERROR);
|
||||||
|
if (fd < 0) {
|
||||||
|
strbuf_addf(err, "Could not open '%s' for writing: %s",
|
||||||
|
filename, strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (old_sha1) {
|
||||||
|
unsigned char actual_old_sha1[20];
|
||||||
|
|
||||||
|
if (read_ref(pseudoref, actual_old_sha1))
|
||||||
|
die("could not read ref '%s'", pseudoref);
|
||||||
|
if (hashcmp(actual_old_sha1, old_sha1)) {
|
||||||
|
strbuf_addf(err, "Unexpected sha1 when writing %s", pseudoref);
|
||||||
|
rollback_lock_file(&lock);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (write_in_full(fd, buf.buf, buf.len) != buf.len) {
|
||||||
|
strbuf_addf(err, "Could not write to '%s'", filename);
|
||||||
|
rollback_lock_file(&lock);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
commit_lock_file(&lock);
|
||||||
|
ret = 0;
|
||||||
|
done:
|
||||||
|
strbuf_release(&buf);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int delete_pseudoref(const char *pseudoref, const unsigned char *old_sha1)
|
||||||
|
{
|
||||||
|
static struct lock_file lock;
|
||||||
|
const char *filename;
|
||||||
|
|
||||||
|
filename = git_path("%s", pseudoref);
|
||||||
|
|
||||||
|
if (old_sha1 && !is_null_sha1(old_sha1)) {
|
||||||
|
int fd;
|
||||||
|
unsigned char actual_old_sha1[20];
|
||||||
|
|
||||||
|
fd = hold_lock_file_for_update(&lock, filename,
|
||||||
|
LOCK_DIE_ON_ERROR);
|
||||||
|
if (fd < 0)
|
||||||
|
die_errno(_("Could not open '%s' for writing"), filename);
|
||||||
|
if (read_ref(pseudoref, actual_old_sha1))
|
||||||
|
die("could not read ref '%s'", pseudoref);
|
||||||
|
if (hashcmp(actual_old_sha1, old_sha1)) {
|
||||||
|
warning("Unexpected sha1 when deleting %s", pseudoref);
|
||||||
|
rollback_lock_file(&lock);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
unlink(filename);
|
||||||
|
rollback_lock_file(&lock);
|
||||||
|
} else {
|
||||||
|
unlink(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
int delete_ref(const char *refname, const unsigned char *old_sha1,
|
int delete_ref(const char *refname, const unsigned char *old_sha1,
|
||||||
unsigned int flags)
|
unsigned int flags)
|
||||||
{
|
{
|
||||||
struct ref_transaction *transaction;
|
struct ref_transaction *transaction;
|
||||||
struct strbuf err = STRBUF_INIT;
|
struct strbuf err = STRBUF_INIT;
|
||||||
|
|
||||||
|
if (ref_type(refname) == REF_TYPE_PSEUDOREF)
|
||||||
|
return delete_pseudoref(refname, old_sha1);
|
||||||
|
|
||||||
transaction = ref_transaction_begin(&err);
|
transaction = ref_transaction_begin(&err);
|
||||||
if (!transaction ||
|
if (!transaction ||
|
||||||
ref_transaction_delete(transaction, refname, old_sha1,
|
ref_transaction_delete(transaction, refname, old_sha1,
|
||||||
@ -3961,17 +4066,25 @@ int update_ref(const char *msg, const char *refname,
|
|||||||
const unsigned char *new_sha1, const unsigned char *old_sha1,
|
const unsigned char *new_sha1, const unsigned char *old_sha1,
|
||||||
unsigned int flags, enum action_on_err onerr)
|
unsigned int flags, enum action_on_err onerr)
|
||||||
{
|
{
|
||||||
struct ref_transaction *t;
|
struct ref_transaction *t = NULL;
|
||||||
struct strbuf err = STRBUF_INIT;
|
struct strbuf err = STRBUF_INIT;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
t = ref_transaction_begin(&err);
|
if (ref_type(refname) == REF_TYPE_PSEUDOREF) {
|
||||||
if (!t ||
|
ret = write_pseudoref(refname, new_sha1, old_sha1, &err);
|
||||||
ref_transaction_update(t, refname, new_sha1, old_sha1,
|
} else {
|
||||||
flags, msg, &err) ||
|
t = ref_transaction_begin(&err);
|
||||||
ref_transaction_commit(t, &err)) {
|
if (!t ||
|
||||||
|
ref_transaction_update(t, refname, new_sha1, old_sha1,
|
||||||
|
flags, msg, &err) ||
|
||||||
|
ref_transaction_commit(t, &err)) {
|
||||||
|
ret = 1;
|
||||||
|
ref_transaction_free(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ret) {
|
||||||
const char *str = "update_ref failed for ref '%s': %s";
|
const char *str = "update_ref failed for ref '%s': %s";
|
||||||
|
|
||||||
ref_transaction_free(t);
|
|
||||||
switch (onerr) {
|
switch (onerr) {
|
||||||
case UPDATE_REFS_MSG_ON_ERR:
|
case UPDATE_REFS_MSG_ON_ERR:
|
||||||
error(str, refname, err.buf);
|
error(str, refname, err.buf);
|
||||||
@ -3986,7 +4099,8 @@ int update_ref(const char *msg, const char *refname,
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
strbuf_release(&err);
|
strbuf_release(&err);
|
||||||
ref_transaction_free(t);
|
if (t)
|
||||||
|
ref_transaction_free(t);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
8
refs.h
8
refs.h
@ -445,6 +445,14 @@ extern int parse_hide_refs_config(const char *var, const char *value, const char
|
|||||||
|
|
||||||
extern int ref_is_hidden(const char *);
|
extern int ref_is_hidden(const char *);
|
||||||
|
|
||||||
|
enum ref_type {
|
||||||
|
REF_TYPE_PER_WORKTREE,
|
||||||
|
REF_TYPE_PSEUDOREF,
|
||||||
|
REF_TYPE_NORMAL,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum ref_type ref_type(const char *refname);
|
||||||
|
|
||||||
enum expire_reflog_flags {
|
enum expire_reflog_flags {
|
||||||
EXPIRE_REFLOGS_DRY_RUN = 1 << 0,
|
EXPIRE_REFLOGS_DRY_RUN = 1 << 0,
|
||||||
EXPIRE_REFLOGS_UPDATE_REF = 1 << 1,
|
EXPIRE_REFLOGS_UPDATE_REF = 1 << 1,
|
||||||
|
23
sequencer.c
23
sequencer.c
@ -163,23 +163,6 @@ static void free_message(struct commit *commit, struct commit_message *msg)
|
|||||||
unuse_commit_buffer(commit, msg->message);
|
unuse_commit_buffer(commit, msg->message);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void write_cherry_pick_head(struct commit *commit, const char *pseudoref)
|
|
||||||
{
|
|
||||||
const char *filename;
|
|
||||||
int fd;
|
|
||||||
struct strbuf buf = STRBUF_INIT;
|
|
||||||
|
|
||||||
strbuf_addf(&buf, "%s\n", sha1_to_hex(commit->object.sha1));
|
|
||||||
|
|
||||||
filename = git_path("%s", pseudoref);
|
|
||||||
fd = open(filename, O_WRONLY | O_CREAT, 0666);
|
|
||||||
if (fd < 0)
|
|
||||||
die_errno(_("Could not open '%s' for writing"), filename);
|
|
||||||
if (write_in_full(fd, buf.buf, buf.len) != buf.len || close(fd))
|
|
||||||
die_errno(_("Could not write to '%s'"), filename);
|
|
||||||
strbuf_release(&buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void print_advice(int show_hint, struct replay_opts *opts)
|
static void print_advice(int show_hint, struct replay_opts *opts)
|
||||||
{
|
{
|
||||||
char *msg = getenv("GIT_CHERRY_PICK_HELP");
|
char *msg = getenv("GIT_CHERRY_PICK_HELP");
|
||||||
@ -609,9 +592,11 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
|
|||||||
* write it at all.
|
* write it at all.
|
||||||
*/
|
*/
|
||||||
if (opts->action == REPLAY_PICK && !opts->no_commit && (res == 0 || res == 1))
|
if (opts->action == REPLAY_PICK && !opts->no_commit && (res == 0 || res == 1))
|
||||||
write_cherry_pick_head(commit, "CHERRY_PICK_HEAD");
|
update_ref(NULL, "CHERRY_PICK_HEAD", commit->object.sha1, NULL,
|
||||||
|
REF_NODEREF, UPDATE_REFS_DIE_ON_ERR);
|
||||||
if (opts->action == REPLAY_REVERT && ((opts->no_commit && res == 0) || res == 1))
|
if (opts->action == REPLAY_REVERT && ((opts->no_commit && res == 0) || res == 1))
|
||||||
write_cherry_pick_head(commit, "REVERT_HEAD");
|
update_ref(NULL, "REVERT_HEAD", commit->object.sha1, NULL,
|
||||||
|
REF_NODEREF, UPDATE_REFS_DIE_ON_ERR);
|
||||||
|
|
||||||
if (res) {
|
if (res) {
|
||||||
error(opts->action == REPLAY_REVERT
|
error(opts->action == REPLAY_REVERT
|
||||||
|
Loading…
Reference in New Issue
Block a user