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:
Junio C Hamano 2015-08-25 14:57:08 -07:00
Родитель 32561f5dd3 2c3aed1381
Коммит 080cc64663
5 изменённых файлов: 163 добавлений и 56 удалений

Просмотреть файл

@ -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].

Просмотреть файл

@ -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
Просмотреть файл

@ -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
Просмотреть файл

@ -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,

Просмотреть файл

@ -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