diff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt index ab18f4baca..8c6478b2f2 100644 --- a/Documentation/glossary-content.txt +++ b/Documentation/glossary-content.txt @@ -411,6 +411,27 @@ exclude;; core Git. Porcelains expose more of a <> interface than the <>. +[[def_per_worktree_ref]]per-worktree ref:: + Refs that are per-<>, rather than + global. This is presently only <>, 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 + <> 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 + <>, 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 <> means to <> it and <> it. See also linkgit:git-pull[1]. diff --git a/bisect.c b/bisect.c index f9da9d1346..041a13d093 100644 --- a/bisect.c +++ b/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_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; @@ -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) +static int bisect_checkout(const unsigned char *bisect_rev, int no_checkout) { - 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); + char bisect_rev_hex[GIT_SHA1_HEXSZ + 1]; - 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) -{ - - 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); @@ -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)); } } @@ -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()) @@ -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. */ @@ -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) diff --git a/refs.c b/refs.c index 835801905b..b0e0fe560c 100644 --- a/refs.c +++ b/refs.c @@ -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, @@ -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); @@ -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; } diff --git a/refs.h b/refs.h index 6a3fa6d41d..e9a5f3230a 100644 --- a/refs.h +++ b/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 *); +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, diff --git a/sequencer.c b/sequencer.c index 147adfac81..a0600aebca 100644 --- a/sequencer.c +++ b/sequencer.c @@ -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"); @@ -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