Skip to content

Commit 080cc64

Browse files
committed
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
2 parents 32561f5 + 2c3aed1 commit 080cc64

File tree

5 files changed

+163
-56
lines changed

5 files changed

+163
-56
lines changed

Documentation/glossary-content.txt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,27 @@ exclude;;
411411
core Git. Porcelains expose more of a <<def_SCM,SCM>>
412412
interface than the <<def_plumbing,plumbing>>.
413413

414+
[[def_per_worktree_ref]]per-worktree ref::
415+
Refs that are per-<<def_working_tree,worktree>>, rather than
416+
global. This is presently only <<def_HEAD,HEAD>>, but might
417+
later include other unusual refs.
418+
419+
[[def_pseudoref]]pseudoref::
420+
Pseudorefs are a class of files under `$GIT_DIR` which behave
421+
like refs for the purposes of rev-parse, but which are treated
422+
specially by git. Pseudorefs both have names that are all-caps,
423+
and always start with a line consisting of a
424+
<<def_SHA1,SHA-1>> followed by whitespace. So, HEAD is not a
425+
pseudoref, because it is sometimes a symbolic ref. They might
426+
optionally contain some additional data. `MERGE_HEAD` and
427+
`CHERRY_PICK_HEAD` are examples. Unlike
428+
<<def_per_worktree_ref,per-worktree refs>>, these files cannot
429+
be symbolic refs, and never have reflogs. They also cannot be
430+
updated through the normal ref update machinery. Instead,
431+
they are updated by directly writing to the files. However,
432+
they can be read as if they were refs, so `git rev-parse
433+
MERGE_HEAD` will work.
434+
414435
[[def_pull]]pull::
415436
Pulling a <<def_branch,branch>> means to <<def_fetch,fetch>> it and
416437
<<def_merge,merge>> it. See also linkgit:git-pull[1].

bisect.c

Lines changed: 8 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ static struct object_id *current_bad_oid;
1919

2020
static const char *argv_checkout[] = {"checkout", "-q", NULL, "--", NULL};
2121
static const char *argv_show_branch[] = {"show-branch", NULL, NULL};
22-
static const char *argv_update_ref[] = {"update-ref", "--no-deref", "BISECT_HEAD", NULL, NULL};
2322

2423
static const char *term_bad;
2524
static const char *term_good;
@@ -678,34 +677,16 @@ static int is_expected_rev(const struct object_id *oid)
678677
return res;
679678
}
680679

681-
static void mark_expected_rev(char *bisect_rev_hex)
682-
{
683-
int len = strlen(bisect_rev_hex);
684-
const char *filename = git_path("BISECT_EXPECTED_REV");
685-
int fd = open(filename, O_CREAT | O_TRUNC | O_WRONLY, 0600);
686-
687-
if (fd < 0)
688-
die_errno("could not create file '%s'", filename);
689-
690-
bisect_rev_hex[len] = '\n';
691-
write_or_die(fd, bisect_rev_hex, len + 1);
692-
bisect_rev_hex[len] = '\0';
693-
694-
if (close(fd) < 0)
695-
die("closing file %s: %s", filename, strerror(errno));
696-
}
697-
698-
static int bisect_checkout(char *bisect_rev_hex, int no_checkout)
680+
static int bisect_checkout(const unsigned char *bisect_rev, int no_checkout)
699681
{
682+
char bisect_rev_hex[GIT_SHA1_HEXSZ + 1];
700683

701-
mark_expected_rev(bisect_rev_hex);
684+
memcpy(bisect_rev_hex, sha1_to_hex(bisect_rev), GIT_SHA1_HEXSZ + 1);
685+
update_ref(NULL, "BISECT_EXPECTED_REV", bisect_rev, NULL, 0, UPDATE_REFS_DIE_ON_ERR);
702686

703687
argv_checkout[2] = bisect_rev_hex;
704688
if (no_checkout) {
705-
argv_update_ref[3] = bisect_rev_hex;
706-
if (run_command_v_opt(argv_update_ref, RUN_GIT_CMD))
707-
die("update-ref --no-deref HEAD failed on %s",
708-
bisect_rev_hex);
689+
update_ref(NULL, "BISECT_HEAD", bisect_rev, NULL, 0, UPDATE_REFS_DIE_ON_ERR);
709690
} else {
710691
int res;
711692
res = run_command_v_opt(argv_checkout, RUN_GIT_CMD);
@@ -807,7 +788,7 @@ static void check_merge_bases(int no_checkout)
807788
handle_skipped_merge_base(mb);
808789
} else {
809790
printf("Bisecting: a merge base must be tested\n");
810-
exit(bisect_checkout(sha1_to_hex(mb), no_checkout));
791+
exit(bisect_checkout(mb, no_checkout));
811792
}
812793
}
813794

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

956936
read_bisect_terms(&term_bad, &term_good);
957937
if (read_bisect_refs())
@@ -989,11 +969,10 @@ int bisect_next_all(const char *prefix, int no_checkout)
989969
}
990970

991971
bisect_rev = revs.commits->item->object.sha1;
992-
memcpy(bisect_rev_hex, sha1_to_hex(bisect_rev), GIT_SHA1_HEXSZ + 1);
993972

994973
if (!hashcmp(bisect_rev, current_bad_oid->hash)) {
995974
exit_if_skipped_commits(tried, current_bad_oid);
996-
printf("%s is the first %s commit\n", bisect_rev_hex,
975+
printf("%s is the first %s commit\n", sha1_to_hex(bisect_rev),
997976
term_bad);
998977
show_diff_tree(prefix, revs.commits->item);
999978
/* This means the bisection process succeeded. */
@@ -1006,7 +985,7 @@ int bisect_next_all(const char *prefix, int no_checkout)
1006985
"(roughly %d step%s)\n", nr, (nr == 1 ? "" : "s"),
1007986
steps, (steps == 1 ? "" : "s"));
1008987

1009-
return bisect_checkout(bisect_rev_hex, no_checkout);
988+
return bisect_checkout(bisect_rev, no_checkout);
1010989
}
1011990

1012991
static inline int log2i(int n)

refs.c

Lines changed: 122 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2854,12 +2854,117 @@ static int delete_ref_loose(struct ref_lock *lock, int flag, struct strbuf *err)
28542854
return 0;
28552855
}
28562856

2857+
static int is_per_worktree_ref(const char *refname)
2858+
{
2859+
return !strcmp(refname, "HEAD");
2860+
}
2861+
2862+
static int is_pseudoref_syntax(const char *refname)
2863+
{
2864+
const char *c;
2865+
2866+
for (c = refname; *c; c++) {
2867+
if (!isupper(*c) && *c != '-' && *c != '_')
2868+
return 0;
2869+
}
2870+
2871+
return 1;
2872+
}
2873+
2874+
enum ref_type ref_type(const char *refname)
2875+
{
2876+
if (is_per_worktree_ref(refname))
2877+
return REF_TYPE_PER_WORKTREE;
2878+
if (is_pseudoref_syntax(refname))
2879+
return REF_TYPE_PSEUDOREF;
2880+
return REF_TYPE_NORMAL;
2881+
}
2882+
2883+
static int write_pseudoref(const char *pseudoref, const unsigned char *sha1,
2884+
const unsigned char *old_sha1, struct strbuf *err)
2885+
{
2886+
const char *filename;
2887+
int fd;
2888+
static struct lock_file lock;
2889+
struct strbuf buf = STRBUF_INIT;
2890+
int ret = -1;
2891+
2892+
strbuf_addf(&buf, "%s\n", sha1_to_hex(sha1));
2893+
2894+
filename = git_path("%s", pseudoref);
2895+
fd = hold_lock_file_for_update(&lock, filename, LOCK_DIE_ON_ERROR);
2896+
if (fd < 0) {
2897+
strbuf_addf(err, "Could not open '%s' for writing: %s",
2898+
filename, strerror(errno));
2899+
return -1;
2900+
}
2901+
2902+
if (old_sha1) {
2903+
unsigned char actual_old_sha1[20];
2904+
2905+
if (read_ref(pseudoref, actual_old_sha1))
2906+
die("could not read ref '%s'", pseudoref);
2907+
if (hashcmp(actual_old_sha1, old_sha1)) {
2908+
strbuf_addf(err, "Unexpected sha1 when writing %s", pseudoref);
2909+
rollback_lock_file(&lock);
2910+
goto done;
2911+
}
2912+
}
2913+
2914+
if (write_in_full(fd, buf.buf, buf.len) != buf.len) {
2915+
strbuf_addf(err, "Could not write to '%s'", filename);
2916+
rollback_lock_file(&lock);
2917+
goto done;
2918+
}
2919+
2920+
commit_lock_file(&lock);
2921+
ret = 0;
2922+
done:
2923+
strbuf_release(&buf);
2924+
return ret;
2925+
}
2926+
2927+
static int delete_pseudoref(const char *pseudoref, const unsigned char *old_sha1)
2928+
{
2929+
static struct lock_file lock;
2930+
const char *filename;
2931+
2932+
filename = git_path("%s", pseudoref);
2933+
2934+
if (old_sha1 && !is_null_sha1(old_sha1)) {
2935+
int fd;
2936+
unsigned char actual_old_sha1[20];
2937+
2938+
fd = hold_lock_file_for_update(&lock, filename,
2939+
LOCK_DIE_ON_ERROR);
2940+
if (fd < 0)
2941+
die_errno(_("Could not open '%s' for writing"), filename);
2942+
if (read_ref(pseudoref, actual_old_sha1))
2943+
die("could not read ref '%s'", pseudoref);
2944+
if (hashcmp(actual_old_sha1, old_sha1)) {
2945+
warning("Unexpected sha1 when deleting %s", pseudoref);
2946+
rollback_lock_file(&lock);
2947+
return -1;
2948+
}
2949+
2950+
unlink(filename);
2951+
rollback_lock_file(&lock);
2952+
} else {
2953+
unlink(filename);
2954+
}
2955+
2956+
return 0;
2957+
}
2958+
28572959
int delete_ref(const char *refname, const unsigned char *old_sha1,
28582960
unsigned int flags)
28592961
{
28602962
struct ref_transaction *transaction;
28612963
struct strbuf err = STRBUF_INIT;
28622964

2965+
if (ref_type(refname) == REF_TYPE_PSEUDOREF)
2966+
return delete_pseudoref(refname, old_sha1);
2967+
28632968
transaction = ref_transaction_begin(&err);
28642969
if (!transaction ||
28652970
ref_transaction_delete(transaction, refname, old_sha1,
@@ -3961,17 +4066,25 @@ int update_ref(const char *msg, const char *refname,
39614066
const unsigned char *new_sha1, const unsigned char *old_sha1,
39624067
unsigned int flags, enum action_on_err onerr)
39634068
{
3964-
struct ref_transaction *t;
4069+
struct ref_transaction *t = NULL;
39654070
struct strbuf err = STRBUF_INIT;
4071+
int ret = 0;
39664072

3967-
t = ref_transaction_begin(&err);
3968-
if (!t ||
3969-
ref_transaction_update(t, refname, new_sha1, old_sha1,
3970-
flags, msg, &err) ||
3971-
ref_transaction_commit(t, &err)) {
4073+
if (ref_type(refname) == REF_TYPE_PSEUDOREF) {
4074+
ret = write_pseudoref(refname, new_sha1, old_sha1, &err);
4075+
} else {
4076+
t = ref_transaction_begin(&err);
4077+
if (!t ||
4078+
ref_transaction_update(t, refname, new_sha1, old_sha1,
4079+
flags, msg, &err) ||
4080+
ref_transaction_commit(t, &err)) {
4081+
ret = 1;
4082+
ref_transaction_free(t);
4083+
}
4084+
}
4085+
if (ret) {
39724086
const char *str = "update_ref failed for ref '%s': %s";
39734087

3974-
ref_transaction_free(t);
39754088
switch (onerr) {
39764089
case UPDATE_REFS_MSG_ON_ERR:
39774090
error(str, refname, err.buf);
@@ -3986,7 +4099,8 @@ int update_ref(const char *msg, const char *refname,
39864099
return 1;
39874100
}
39884101
strbuf_release(&err);
3989-
ref_transaction_free(t);
4102+
if (t)
4103+
ref_transaction_free(t);
39904104
return 0;
39914105
}
39924106

refs.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,14 @@ extern int parse_hide_refs_config(const char *var, const char *value, const char
445445

446446
extern int ref_is_hidden(const char *);
447447

448+
enum ref_type {
449+
REF_TYPE_PER_WORKTREE,
450+
REF_TYPE_PSEUDOREF,
451+
REF_TYPE_NORMAL,
452+
};
453+
454+
enum ref_type ref_type(const char *refname);
455+
448456
enum expire_reflog_flags {
449457
EXPIRE_REFLOGS_DRY_RUN = 1 << 0,
450458
EXPIRE_REFLOGS_UPDATE_REF = 1 << 1,

sequencer.c

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -163,23 +163,6 @@ static void free_message(struct commit *commit, struct commit_message *msg)
163163
unuse_commit_buffer(commit, msg->message);
164164
}
165165

166-
static void write_cherry_pick_head(struct commit *commit, const char *pseudoref)
167-
{
168-
const char *filename;
169-
int fd;
170-
struct strbuf buf = STRBUF_INIT;
171-
172-
strbuf_addf(&buf, "%s\n", sha1_to_hex(commit->object.sha1));
173-
174-
filename = git_path("%s", pseudoref);
175-
fd = open(filename, O_WRONLY | O_CREAT, 0666);
176-
if (fd < 0)
177-
die_errno(_("Could not open '%s' for writing"), filename);
178-
if (write_in_full(fd, buf.buf, buf.len) != buf.len || close(fd))
179-
die_errno(_("Could not write to '%s'"), filename);
180-
strbuf_release(&buf);
181-
}
182-
183166
static void print_advice(int show_hint, struct replay_opts *opts)
184167
{
185168
char *msg = getenv("GIT_CHERRY_PICK_HELP");
@@ -609,9 +592,11 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
609592
* write it at all.
610593
*/
611594
if (opts->action == REPLAY_PICK && !opts->no_commit && (res == 0 || res == 1))
612-
write_cherry_pick_head(commit, "CHERRY_PICK_HEAD");
595+
update_ref(NULL, "CHERRY_PICK_HEAD", commit->object.sha1, NULL,
596+
REF_NODEREF, UPDATE_REFS_DIE_ON_ERR);
613597
if (opts->action == REPLAY_REVERT && ((opts->no_commit && res == 0) || res == 1))
614-
write_cherry_pick_head(commit, "REVERT_HEAD");
598+
update_ref(NULL, "REVERT_HEAD", commit->object.sha1, NULL,
599+
REF_NODEREF, UPDATE_REFS_DIE_ON_ERR);
615600

616601
if (res) {
617602
error(opts->action == REPLAY_REVERT

0 commit comments

Comments
 (0)