Skip to content

Commit

Permalink
Merge branch 'dt/refs-pseudo'
Browse files Browse the repository at this point in the history
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
  • Loading branch information
gitster committed Aug 25, 2015
2 parents 32561f5 + 2c3aed1 commit 080cc64
Show file tree
Hide file tree
Showing 5 changed files with 163 additions and 56 deletions.
21 changes: 21 additions & 0 deletions Documentation/glossary-content.txt
Expand Up @@ -411,6 +411,27 @@ exclude;;
core Git. Porcelains expose more of a <<def_SCM,SCM>>
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::
Pulling a <<def_branch,branch>> means to <<def_fetch,fetch>> it and
<<def_merge,merge>> it. See also linkgit:git-pull[1].
Expand Down
37 changes: 8 additions & 29 deletions bisect.c
Expand Up @@ -19,7 +19,6 @@ static struct object_id *current_bad_oid;

static const char *argv_checkout[] = {"checkout", "-q", 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_good;
Expand Down Expand Up @@ -678,34 +677,16 @@ static int is_expected_rev(const struct object_id *oid)
return res;
}

static void mark_expected_rev(char *bisect_rev_hex)
{
int len = strlen(bisect_rev_hex);
const char *filename = git_path("BISECT_EXPECTED_REV");
int fd = open(filename, O_CREAT | O_TRUNC | O_WRONLY, 0600);

if (fd < 0)
die_errno("could not create file '%s'", filename);

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)
static int bisect_checkout(const unsigned char *bisect_rev, int no_checkout)
{
char bisect_rev_hex[GIT_SHA1_HEXSZ + 1];

mark_expected_rev(bisect_rev_hex);
memcpy(bisect_rev_hex, sha1_to_hex(bisect_rev), GIT_SHA1_HEXSZ + 1);
update_ref(NULL, "BISECT_EXPECTED_REV", bisect_rev, NULL, 0, UPDATE_REFS_DIE_ON_ERR);

argv_checkout[2] = bisect_rev_hex;
if (no_checkout) {
argv_update_ref[3] = bisect_rev_hex;
if (run_command_v_opt(argv_update_ref, RUN_GIT_CMD))
die("update-ref --no-deref HEAD failed on %s",
bisect_rev_hex);
update_ref(NULL, "BISECT_HEAD", bisect_rev, NULL, 0, UPDATE_REFS_DIE_ON_ERR);
} else {
int res;
res = run_command_v_opt(argv_checkout, RUN_GIT_CMD);
Expand Down Expand Up @@ -807,7 +788,7 @@ static void check_merge_bases(int no_checkout)
handle_skipped_merge_base(mb);
} else {
printf("Bisecting: a merge base must be tested\n");
exit(bisect_checkout(sha1_to_hex(mb), no_checkout));
exit(bisect_checkout(mb, no_checkout));
}
}

Expand Down Expand Up @@ -951,7 +932,6 @@ int bisect_next_all(const char *prefix, int no_checkout)
struct commit_list *tried;
int reaches = 0, all = 0, nr, steps;
const unsigned char *bisect_rev;
char bisect_rev_hex[GIT_SHA1_HEXSZ + 1];

read_bisect_terms(&term_bad, &term_good);
if (read_bisect_refs())
Expand Down Expand Up @@ -989,11 +969,10 @@ int bisect_next_all(const char *prefix, int no_checkout)
}

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)) {
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);
show_diff_tree(prefix, revs.commits->item);
/* This means the bisection process succeeded. */
Expand All @@ -1006,7 +985,7 @@ int bisect_next_all(const char *prefix, int no_checkout)
"(roughly %d step%s)\n", nr, (nr == 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)
Expand Down
130 changes: 122 additions & 8 deletions refs.c
Expand Up @@ -2854,12 +2854,117 @@ static int delete_ref_loose(struct ref_lock *lock, int flag, struct strbuf *err)
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,
unsigned int flags)
{
struct ref_transaction *transaction;
struct strbuf err = STRBUF_INIT;

if (ref_type(refname) == REF_TYPE_PSEUDOREF)
return delete_pseudoref(refname, old_sha1);

transaction = ref_transaction_begin(&err);
if (!transaction ||
ref_transaction_delete(transaction, refname, old_sha1,
Expand Down Expand Up @@ -3961,17 +4066,25 @@ int update_ref(const char *msg, const char *refname,
const unsigned char *new_sha1, const unsigned char *old_sha1,
unsigned int flags, enum action_on_err onerr)
{
struct ref_transaction *t;
struct ref_transaction *t = NULL;
struct strbuf err = STRBUF_INIT;
int ret = 0;

t = ref_transaction_begin(&err);
if (!t ||
ref_transaction_update(t, refname, new_sha1, old_sha1,
flags, msg, &err) ||
ref_transaction_commit(t, &err)) {
if (ref_type(refname) == REF_TYPE_PSEUDOREF) {
ret = write_pseudoref(refname, new_sha1, old_sha1, &err);
} else {
t = ref_transaction_begin(&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";

ref_transaction_free(t);
switch (onerr) {
case UPDATE_REFS_MSG_ON_ERR:
error(str, refname, err.buf);
Expand All @@ -3986,7 +4099,8 @@ int update_ref(const char *msg, const char *refname,
return 1;
}
strbuf_release(&err);
ref_transaction_free(t);
if (t)
ref_transaction_free(t);
return 0;
}

Expand Down
8 changes: 8 additions & 0 deletions refs.h
Expand Up @@ -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 *);

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 {
EXPIRE_REFLOGS_DRY_RUN = 1 << 0,
EXPIRE_REFLOGS_UPDATE_REF = 1 << 1,
Expand Down
23 changes: 4 additions & 19 deletions sequencer.c
Expand Up @@ -163,23 +163,6 @@ static void free_message(struct commit *commit, struct commit_message *msg)
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)
{
char *msg = getenv("GIT_CHERRY_PICK_HELP");
Expand Down Expand Up @@ -609,9 +592,11 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
* write it at all.
*/
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))
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) {
error(opts->action == REPLAY_REVERT
Expand Down

0 comments on commit 080cc64

Please sign in to comment.