* [PATCH v3 1/8] refs: accept symref values in `ref_transaction[_add]_update`
2024-04-23 21:28 ` [PATCH v3 0/8] refs: add symref support to 'git-update-ref' Karthik Nayak
@ 2024-04-23 21:28 ` Karthik Nayak
2024-04-23 21:28 ` [PATCH v3 2/8] update-ref: support parsing ref targets in `parse_next_oid` Karthik Nayak
` (9 subsequent siblings)
10 siblings, 0 replies; 194+ messages in thread
From: Karthik Nayak @ 2024-04-23 21:28 UTC (permalink / raw)
To: karthik.188; +Cc: chris.torek, git, gitster, ps
From: Karthik Nayak <karthik.188@gmail.com>
The `ref_transaction[_add]_update` functions obtain ref information and
flags to create a `ref_update` and add it to the transaction at hand.
To extend symref support in transactions, we need to also accept the
old and new ref targets and process it. In this commit, let's add the
required parameters to the function and modify all call sites.
The two parameters added are `new_target` and `old_target`. The
`new_target` is used to denote what the reference should point to when
the transaction is applied. Some functions allow this parameter to be
NULL, meaning that the reference is not changed.
The `old_target` denotes the value the reference must have before the
update. Some functions allow this parameter to be NULL, meaning that the
old value of the reference is not checked. A copy of this value is made
in the transaction.
The handling logic of these parameters will be added in consequent
commits as we add symref support to the existing 'git-update-ref'
commands.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
branch.c | 2 +-
builtin/fast-import.c | 5 +++--
builtin/fetch.c | 2 +-
builtin/receive-pack.c | 1 +
builtin/replace.c | 2 +-
builtin/tag.c | 1 +
builtin/update-ref.c | 1 +
refs.c | 22 +++++++++++++++++-----
refs.h | 17 ++++++++++++++++-
refs/files-backend.c | 12 ++++++------
refs/refs-internal.h | 13 +++++++++++++
refs/reftable-backend.c | 4 ++--
sequencer.c | 9 +++++----
walker.c | 2 +-
14 files changed, 69 insertions(+), 24 deletions(-)
diff --git a/branch.c b/branch.c
index e4a738fc7b..48af4c3ceb 100644
--- a/branch.c
+++ b/branch.c
@@ -627,7 +627,7 @@ void create_branch(struct repository *r,
if (!transaction ||
ref_transaction_update(transaction, ref.buf,
&oid, forcing ? NULL : null_oid(),
- 0, msg, &err) ||
+ NULL, NULL, 0, msg, &err) ||
ref_transaction_commit(transaction, &err))
die("%s", err.buf);
ref_transaction_free(transaction);
diff --git a/builtin/fast-import.c b/builtin/fast-import.c
index dc5a9d32dd..297dfb91a1 100644
--- a/builtin/fast-import.c
+++ b/builtin/fast-import.c
@@ -1634,7 +1634,7 @@ static int update_branch(struct branch *b)
transaction = ref_transaction_begin(&err);
if (!transaction ||
ref_transaction_update(transaction, b->name, &b->oid, &old_oid,
- 0, msg, &err) ||
+ NULL, NULL, 0, msg, &err) ||
ref_transaction_commit(transaction, &err)) {
ref_transaction_free(transaction);
error("%s", err.buf);
@@ -1675,7 +1675,8 @@ static void dump_tags(void)
strbuf_addf(&ref_name, "refs/tags/%s", t->name);
if (ref_transaction_update(transaction, ref_name.buf,
- &t->oid, NULL, 0, msg, &err)) {
+ &t->oid, NULL, NULL, NULL,
+ 0, msg, &err)) {
failure |= error("%s", err.buf);
goto cleanup;
}
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 5857d860db..66840b7c5b 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -668,7 +668,7 @@ static int s_update_ref(const char *action,
ret = ref_transaction_update(transaction, ref->name, &ref->new_oid,
check_old ? &ref->old_oid : NULL,
- 0, msg, &err);
+ NULL, NULL, 0, msg, &err);
if (ret) {
ret = STORE_REF_ERROR_OTHER;
goto out;
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index e8d7df14b6..b150ef39a8 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -1595,6 +1595,7 @@ static const char *update(struct command *cmd, struct shallow_info *si)
if (ref_transaction_update(transaction,
namespaced_name,
new_oid, old_oid,
+ NULL, NULL,
0, "push",
&err)) {
rp_error("%s", err.buf);
diff --git a/builtin/replace.c b/builtin/replace.c
index da59600ad2..7690687b0e 100644
--- a/builtin/replace.c
+++ b/builtin/replace.c
@@ -201,7 +201,7 @@ static int replace_object_oid(const char *object_ref,
transaction = ref_transaction_begin(&err);
if (!transaction ||
ref_transaction_update(transaction, ref.buf, repl, &prev,
- 0, NULL, &err) ||
+ NULL, NULL, 0, NULL, &err) ||
ref_transaction_commit(transaction, &err))
res = error("%s", err.buf);
diff --git a/builtin/tag.c b/builtin/tag.c
index 9a33cb50b4..40a65fdebc 100644
--- a/builtin/tag.c
+++ b/builtin/tag.c
@@ -660,6 +660,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
transaction = ref_transaction_begin(&err);
if (!transaction ||
ref_transaction_update(transaction, ref.buf, &object, &prev,
+ NULL, NULL,
create_reflog ? REF_FORCE_CREATE_REFLOG : 0,
reflog_msg.buf, &err) ||
ref_transaction_commit(transaction, &err)) {
diff --git a/builtin/update-ref.c b/builtin/update-ref.c
index e46afbc46d..21fdbf6ac8 100644
--- a/builtin/update-ref.c
+++ b/builtin/update-ref.c
@@ -204,6 +204,7 @@ static void parse_cmd_update(struct ref_transaction *transaction,
if (ref_transaction_update(transaction, refname,
&new_oid, have_old ? &old_oid : NULL,
+ NULL, NULL,
update_flags | create_reflog_flag,
msg, &err))
die("%s", err.buf);
diff --git a/refs.c b/refs.c
index 55d2e0b2cb..060a31616d 100644
--- a/refs.c
+++ b/refs.c
@@ -1228,6 +1228,7 @@ struct ref_update *ref_transaction_add_update(
const char *refname, unsigned int flags,
const struct object_id *new_oid,
const struct object_id *old_oid,
+ const char *new_target, const char *old_target,
const char *msg)
{
struct ref_update *update;
@@ -1235,6 +1236,11 @@ struct ref_update *ref_transaction_add_update(
if (transaction->state != REF_TRANSACTION_OPEN)
BUG("update called for transaction that is not open");
+ if (old_oid && !is_null_oid(old_oid) && old_target)
+ BUG("Only one of old_oid and old_target should be non NULL");
+ if (new_oid && !is_null_oid(new_oid) && new_target)
+ BUG("Only one of new_oid and new_target should be non NULL");
+
FLEX_ALLOC_STR(update, refname, refname);
ALLOC_GROW(transaction->updates, transaction->nr + 1, transaction->alloc);
transaction->updates[transaction->nr++] = update;
@@ -1253,6 +1259,8 @@ int ref_transaction_update(struct ref_transaction *transaction,
const char *refname,
const struct object_id *new_oid,
const struct object_id *old_oid,
+ const char *new_target,
+ const char *old_target,
unsigned int flags, const char *msg,
struct strbuf *err)
{
@@ -1280,7 +1288,8 @@ int ref_transaction_update(struct ref_transaction *transaction,
flags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0);
ref_transaction_add_update(transaction, refname, flags,
- new_oid, old_oid, msg);
+ new_oid, old_oid, new_target,
+ old_target, msg);
return 0;
}
@@ -1295,7 +1304,8 @@ int ref_transaction_create(struct ref_transaction *transaction,
return 1;
}
return ref_transaction_update(transaction, refname, new_oid,
- null_oid(), flags, msg, err);
+ null_oid(), NULL, NULL, flags,
+ msg, err);
}
int ref_transaction_delete(struct ref_transaction *transaction,
@@ -1308,7 +1318,8 @@ int ref_transaction_delete(struct ref_transaction *transaction,
BUG("delete called with old_oid set to zeros");
return ref_transaction_update(transaction, refname,
null_oid(), old_oid,
- flags, msg, err);
+ NULL, NULL, flags,
+ msg, err);
}
int ref_transaction_verify(struct ref_transaction *transaction,
@@ -1321,6 +1332,7 @@ int ref_transaction_verify(struct ref_transaction *transaction,
BUG("verify called with old_oid set to NULL");
return ref_transaction_update(transaction, refname,
NULL, old_oid,
+ NULL, NULL,
flags, NULL, err);
}
@@ -1335,8 +1347,8 @@ int refs_update_ref(struct ref_store *refs, const char *msg,
t = ref_store_transaction_begin(refs, &err);
if (!t ||
- ref_transaction_update(t, refname, new_oid, old_oid, flags, msg,
- &err) ||
+ ref_transaction_update(t, refname, new_oid, old_oid, NULL, NULL,
+ flags, msg, &err) ||
ref_transaction_commit(t, &err)) {
ret = 1;
ref_transaction_free(t);
diff --git a/refs.h b/refs.h
index d278775e08..c792e13a64 100644
--- a/refs.h
+++ b/refs.h
@@ -648,6 +648,15 @@ struct ref_transaction *ref_transaction_begin(struct strbuf *err);
* before the update. A copy of this value is made in the
* transaction.
*
+ * new_target -- the target reference that the reference will be
+ * update to point to. This takes precedence over new_oid when
+ * set. If the reference is a regular reference, it will be
+ * converted to a symbolic reference.
+ *
+ * old_target -- the reference that the reference must be pointing to.
+ * Will only be taken into account when the reference is a symbolic
+ * reference.
+ *
* flags -- flags affecting the update, passed to
* update_ref_lock(). Possible flags: REF_NO_DEREF,
* REF_FORCE_CREATE_REFLOG. See those constants for more
@@ -713,7 +722,11 @@ struct ref_transaction *ref_transaction_begin(struct strbuf *err);
* beforehand. The old value is checked after the lock is taken to
* prevent races. If the old value doesn't agree with old_oid, the
* whole transaction fails. If old_oid is NULL, then the previous
- * value is not checked.
+ * value is not checked. If `old_target` is not NULL, treat the reference
+ * as a symbolic ref and validate that its target before the update is
+ * `old_target`. If the `new_target` is not NULL, then the reference
+ * will be updated to a symbolic ref which targets `new_target`.
+ * Together, these allow us to update between regular refs and symrefs.
*
* See the above comment "Reference transaction updates" for more
* information.
@@ -722,6 +735,8 @@ int ref_transaction_update(struct ref_transaction *transaction,
const char *refname,
const struct object_id *new_oid,
const struct object_id *old_oid,
+ const char *new_target,
+ const char *old_target,
unsigned int flags, const char *msg,
struct strbuf *err);
diff --git a/refs/files-backend.c b/refs/files-backend.c
index a098d14ea0..e4d0aa3d41 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -1198,7 +1198,7 @@ static void prune_ref(struct files_ref_store *refs, struct ref_to_prune *r)
ref_transaction_add_update(
transaction, r->name,
REF_NO_DEREF | REF_HAVE_NEW | REF_HAVE_OLD | REF_IS_PRUNING,
- null_oid(), &r->oid, NULL);
+ null_oid(), &r->oid, NULL, NULL, NULL);
if (ref_transaction_commit(transaction, &err))
goto cleanup;
@@ -1292,7 +1292,7 @@ static int files_pack_refs(struct ref_store *ref_store,
* packed-refs transaction:
*/
if (ref_transaction_update(transaction, iter->refname,
- iter->oid, NULL,
+ iter->oid, NULL, NULL, NULL,
REF_NO_DEREF, NULL, &err))
die("failure preparing to create packed reference %s: %s",
iter->refname, err.buf);
@@ -2309,7 +2309,7 @@ static int split_head_update(struct ref_update *update,
transaction, "HEAD",
update->flags | REF_LOG_ONLY | REF_NO_DEREF,
&update->new_oid, &update->old_oid,
- update->msg);
+ NULL, NULL, update->msg);
/*
* Add "HEAD". This insertion is O(N) in the transaction
@@ -2372,7 +2372,7 @@ static int split_symref_update(struct ref_update *update,
new_update = ref_transaction_add_update(
transaction, referent, new_flags,
&update->new_oid, &update->old_oid,
- update->msg);
+ NULL, NULL, update->msg);
new_update->parent_update = update;
@@ -2763,7 +2763,7 @@ static int files_transaction_prepare(struct ref_store *ref_store,
packed_transaction, update->refname,
REF_HAVE_NEW | REF_NO_DEREF,
&update->new_oid, NULL,
- NULL);
+ NULL, NULL, NULL);
}
}
@@ -3048,7 +3048,7 @@ static int files_initial_transaction_commit(struct ref_store *ref_store,
ref_transaction_add_update(packed_transaction, update->refname,
update->flags & ~REF_HAVE_OLD,
&update->new_oid, &update->old_oid,
- NULL);
+ NULL, NULL, NULL);
}
if (packed_refs_lock(refs->packed_ref_store, 0, err)) {
diff --git a/refs/refs-internal.h b/refs/refs-internal.h
index 56641aa57a..3040d4797c 100644
--- a/refs/refs-internal.h
+++ b/refs/refs-internal.h
@@ -124,6 +124,18 @@ struct ref_update {
*/
struct object_id old_oid;
+ /*
+ * If set, point the reference to this value. This can also be
+ * used to convert regular references to become symbolic refs.
+ */
+ const char *new_target;
+
+ /*
+ * If set and the reference is a symbolic ref, check that the
+ * reference previously pointed to this value.
+ */
+ const char *old_target;
+
/*
* One or more of REF_NO_DEREF, REF_FORCE_CREATE_REFLOG,
* REF_HAVE_NEW, REF_HAVE_OLD, or backend-specific flags.
@@ -173,6 +185,7 @@ struct ref_update *ref_transaction_add_update(
const char *refname, unsigned int flags,
const struct object_id *new_oid,
const struct object_id *old_oid,
+ const char *new_target, const char *old_target,
const char *msg);
/*
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index 1cda48c504..6104471199 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -829,7 +829,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
new_update = ref_transaction_add_update(
transaction, "HEAD",
u->flags | REF_LOG_ONLY | REF_NO_DEREF,
- &u->new_oid, &u->old_oid, u->msg);
+ &u->new_oid, &u->old_oid, NULL, NULL, u->msg);
string_list_insert(&affected_refnames, new_update->refname);
}
@@ -908,7 +908,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
*/
new_update = ref_transaction_add_update(
transaction, referent.buf, new_flags,
- &u->new_oid, &u->old_oid, u->msg);
+ &u->new_oid, &u->old_oid, NULL, NULL, u->msg);
new_update->parent_update = u;
/*
diff --git a/sequencer.c b/sequencer.c
index 2c19846385..af1b25692b 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -616,7 +616,7 @@ static int fast_forward_to(struct repository *r,
if (!transaction ||
ref_transaction_update(transaction, "HEAD",
to, unborn && !is_rebase_i(opts) ?
- null_oid() : from,
+ null_oid() : from, NULL, NULL,
0, sb.buf, &err) ||
ref_transaction_commit(transaction, &err)) {
ref_transaction_free(transaction);
@@ -1248,7 +1248,7 @@ int update_head_with_reflog(const struct commit *old_head,
if (!transaction ||
ref_transaction_update(transaction, "HEAD", new_head,
old_head ? &old_head->object.oid : null_oid(),
- 0, sb.buf, err) ||
+ NULL, NULL, 0, sb.buf, err) ||
ref_transaction_commit(transaction, err)) {
ret = -1;
}
@@ -3764,8 +3764,9 @@ static int do_label(struct repository *r, const char *name, int len)
} else if (repo_get_oid(r, "HEAD", &head_oid)) {
error(_("could not read HEAD"));
ret = -1;
- } else if (ref_transaction_update(transaction, ref_name.buf, &head_oid,
- NULL, 0, msg.buf, &err) < 0 ||
+ } else if (ref_transaction_update(transaction, ref_name.buf,
+ &head_oid, NULL, NULL, NULL,
+ 0, msg.buf, &err) < 0 ||
ref_transaction_commit(transaction, &err)) {
error("%s", err.buf);
ret = -1;
diff --git a/walker.c b/walker.c
index c0fd632d92..1b3df43906 100644
--- a/walker.c
+++ b/walker.c
@@ -324,7 +324,7 @@ int walker_fetch(struct walker *walker, int targets, char **target,
strbuf_reset(&refname);
strbuf_addf(&refname, "refs/%s", write_ref[i]);
if (ref_transaction_update(transaction, refname.buf,
- oids + i, NULL, 0,
+ oids + i, NULL, NULL, NULL, 0,
msg ? msg : "fetch (unknown)",
&err)) {
error("%s", err.buf);
--
2.43.GIT
^ permalink raw reply related [flat|nested] 194+ messages in thread
* [PATCH v3 2/8] update-ref: support parsing ref targets in `parse_next_oid`
2024-04-23 21:28 ` [PATCH v3 0/8] refs: add symref support to 'git-update-ref' Karthik Nayak
2024-04-23 21:28 ` [PATCH v3 1/8] refs: accept symref values in `ref_transaction[_add]_update` Karthik Nayak
@ 2024-04-23 21:28 ` Karthik Nayak
2024-04-23 21:28 ` [PATCH v3 3/8] files-backend: extract out `create_symref_lock` Karthik Nayak
` (8 subsequent siblings)
10 siblings, 0 replies; 194+ messages in thread
From: Karthik Nayak @ 2024-04-23 21:28 UTC (permalink / raw)
To: karthik.188; +Cc: chris.torek, git, gitster, ps
From: Karthik Nayak <karthik.188@gmail.com>
The `parse_next_oid` is used for parsing the next oid present in the
input buffer. Extend this function to also parse refnames in the form
`ref:<ref_target>`. This will be used in the upcoming commits to add
symref support to the existing update-ref commands.
Since `parse_next_oid` now also parses refs apart from oids, we rename
the function to a more apt name `parse_next_arg`.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
builtin/update-ref.c | 58 ++++++++++++++++++++++++++++++++------------
1 file changed, 42 insertions(+), 16 deletions(-)
diff --git a/builtin/update-ref.c b/builtin/update-ref.c
index 21fdbf6ac8..98ec356394 100644
--- a/builtin/update-ref.c
+++ b/builtin/update-ref.c
@@ -88,6 +88,11 @@ static char *parse_refname(const char **next)
*/
#define PARSE_SHA1_ALLOW_EMPTY 0x02
+/*
+ * Parse refname targets using the ref:<ref_target> format.
+ */
+#define PARSE_REFNAME_TARGETS 0x04
+
/*
* Parse an argument separator followed by the next argument, if any.
* If there is an argument, convert it to a SHA-1, write it to sha1,
@@ -95,10 +100,13 @@ static char *parse_refname(const char **next)
* return 0. If there is no argument at all (not even the empty
* string), return 1 and leave *next unchanged. If the value is
* provided but cannot be converted to a SHA-1, die. flags can
- * include PARSE_SHA1_OLD and/or PARSE_SHA1_ALLOW_EMPTY.
+ * include PARSE_SHA1_OLD and/or PARSE_SHA1_ALLOW_EMPTY and/or
+ * PARSE_REFNAME_TARGETS. When PARSE_REFNAME_TARGETS is set, parse
+ * the argument as `ref:<refname>` and store the refname into
+ * the target strbuf.
*/
-static int parse_next_oid(const char **next, const char *end,
- struct object_id *oid,
+static int parse_next_arg(const char **next, const char *end,
+ struct object_id *oid, struct strbuf *target,
const char *command, const char *refname,
int flags)
{
@@ -118,8 +126,17 @@ static int parse_next_oid(const char **next, const char *end,
(*next)++;
*next = parse_arg(*next, &arg);
if (arg.len) {
- if (repo_get_oid(the_repository, arg.buf, oid))
- goto invalid;
+ if (repo_get_oid(the_repository, arg.buf, oid)) {
+ const char *value;
+ if (flags & PARSE_REFNAME_TARGETS &&
+ skip_prefix(arg.buf, "ref:", &value)) {
+ if (check_refname_format(value, REFNAME_ALLOW_ONELEVEL))
+ die("invalid ref format: %s", value);
+ strbuf_addstr(target, value);
+ } else {
+ goto invalid;
+ }
+ }
} else {
/* Without -z, an empty value means all zeros: */
oidclr(oid);
@@ -136,8 +153,17 @@ static int parse_next_oid(const char **next, const char *end,
*next += arg.len;
if (arg.len) {
- if (repo_get_oid(the_repository, arg.buf, oid))
- goto invalid;
+ if (repo_get_oid(the_repository, arg.buf, oid)) {
+ const char *value;
+ if (flags & PARSE_REFNAME_TARGETS &&
+ skip_prefix(arg.buf, "ref:", &value)) {
+ if (check_refname_format(value, REFNAME_ALLOW_ONELEVEL))
+ die("invalid ref format: %s", value);
+ strbuf_addstr(target, value);
+ } else {
+ goto invalid;
+ }
+ }
} else if (flags & PARSE_SHA1_ALLOW_EMPTY) {
/* With -z, treat an empty value as all zeros: */
warning("%s %s: missing <new-oid>, treating as zero",
@@ -192,12 +218,12 @@ static void parse_cmd_update(struct ref_transaction *transaction,
if (!refname)
die("update: missing <ref>");
- if (parse_next_oid(&next, end, &new_oid, "update", refname,
- PARSE_SHA1_ALLOW_EMPTY))
+ if (parse_next_arg(&next, end, &new_oid, NULL,
+ "update", refname, PARSE_SHA1_ALLOW_EMPTY))
die("update %s: missing <new-oid>", refname);
- have_old = !parse_next_oid(&next, end, &old_oid, "update", refname,
- PARSE_SHA1_OLD);
+ have_old = !parse_next_arg(&next, end, &old_oid, NULL,
+ "update", refname, PARSE_SHA1_OLD);
if (*next != line_termination)
die("update %s: extra input: %s", refname, next);
@@ -225,7 +251,7 @@ static void parse_cmd_create(struct ref_transaction *transaction,
if (!refname)
die("create: missing <ref>");
- if (parse_next_oid(&next, end, &new_oid, "create", refname, 0))
+ if (parse_next_arg(&next, end, &new_oid, NULL, "create", refname, 0))
die("create %s: missing <new-oid>", refname);
if (is_null_oid(&new_oid))
@@ -256,8 +282,8 @@ static void parse_cmd_delete(struct ref_transaction *transaction,
if (!refname)
die("delete: missing <ref>");
- if (parse_next_oid(&next, end, &old_oid, "delete", refname,
- PARSE_SHA1_OLD)) {
+ if (parse_next_arg(&next, end, &old_oid, NULL,
+ "delete", refname, PARSE_SHA1_OLD)) {
have_old = 0;
} else {
if (is_null_oid(&old_oid))
@@ -289,8 +315,8 @@ static void parse_cmd_verify(struct ref_transaction *transaction,
if (!refname)
die("verify: missing <ref>");
- if (parse_next_oid(&next, end, &old_oid, "verify", refname,
- PARSE_SHA1_OLD))
+ if (parse_next_arg(&next, end, &old_oid, NULL,
+ "verify", refname, PARSE_SHA1_OLD))
oidclr(&old_oid);
if (*next != line_termination)
--
2.43.GIT
^ permalink raw reply related [flat|nested] 194+ messages in thread
* [PATCH v3 3/8] files-backend: extract out `create_symref_lock`
2024-04-23 21:28 ` [PATCH v3 0/8] refs: add symref support to 'git-update-ref' Karthik Nayak
2024-04-23 21:28 ` [PATCH v3 1/8] refs: accept symref values in `ref_transaction[_add]_update` Karthik Nayak
2024-04-23 21:28 ` [PATCH v3 2/8] update-ref: support parsing ref targets in `parse_next_oid` Karthik Nayak
@ 2024-04-23 21:28 ` Karthik Nayak
2024-04-23 21:28 ` [PATCH v3 4/8] update-ref: support symrefs in the verify command Karthik Nayak
` (7 subsequent siblings)
10 siblings, 0 replies; 194+ messages in thread
From: Karthik Nayak @ 2024-04-23 21:28 UTC (permalink / raw)
To: karthik.188; +Cc: chris.torek, git, gitster, ps
From: Karthik Nayak <karthik.188@gmail.com>
The function `create_symref_locked` creates a symref by creating a
'<symref>.lock' file and then committing the symref lock, which creates
the final symref.
Split this into two individual functions `create_and_commit_symref` and
`create_symref_locked`. This way we can create the symref lock and
commit it at different times. This will be used to provide symref
support in `git-update-ref(1)`.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
refs/files-backend.c | 40 +++++++++++++++++++++++++++-------------
1 file changed, 27 insertions(+), 13 deletions(-)
diff --git a/refs/files-backend.c b/refs/files-backend.c
index e4d0aa3d41..2420dac2aa 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -1920,26 +1920,39 @@ static void update_symref_reflog(struct files_ref_store *refs,
}
}
-static int create_symref_locked(struct files_ref_store *refs,
- struct ref_lock *lock, const char *refname,
- const char *target, const char *logmsg)
+static int create_symref_lock(struct files_ref_store *refs,
+ struct ref_lock *lock, const char *refname,
+ const char *target)
{
+ if (!fdopen_lock_file(&lock->lk, "w"))
+ return error("unable to fdopen %s: %s",
+ get_lock_file_path(&lock->lk), strerror(errno));
+
+ /* no error check; commit_ref will check ferror */
+ fprintf(get_lock_file_fp(&lock->lk), "ref: %s\n", target);
+ return 0;
+}
+
+static int create_and_commit_symref(struct files_ref_store *refs,
+ struct ref_lock *lock, const char *refname,
+ const char *target, const char *logmsg)
+{
+ int ret;
+
if (prefer_symlink_refs && !create_ref_symlink(lock, target)) {
update_symref_reflog(refs, lock, refname, target, logmsg);
return 0;
}
- if (!fdopen_lock_file(&lock->lk, "w"))
- return error("unable to fdopen %s: %s",
- get_lock_file_path(&lock->lk), strerror(errno));
+ ret = create_symref_lock(refs, lock, refname, target);
+ if (!ret) {
+ update_symref_reflog(refs, lock, refname, target, logmsg);
- update_symref_reflog(refs, lock, refname, target, logmsg);
+ if (commit_ref(lock) < 0)
+ return error("unable to write symref for %s: %s", refname,
+ strerror(errno));
+ }
- /* no error check; commit_ref will check ferror */
- fprintf(get_lock_file_fp(&lock->lk), "ref: %s\n", target);
- if (commit_ref(lock) < 0)
- return error("unable to write symref for %s: %s", refname,
- strerror(errno));
return 0;
}
@@ -1960,7 +1973,8 @@ static int files_create_symref(struct ref_store *ref_store,
return -1;
}
- ret = create_symref_locked(refs, lock, refname, target, logmsg);
+ ret = create_and_commit_symref(refs, lock, refname, target, logmsg);
+
unlock_ref(lock);
return ret;
}
--
2.43.GIT
^ permalink raw reply related [flat|nested] 194+ messages in thread
* [PATCH v3 4/8] update-ref: support symrefs in the verify command
2024-04-23 21:28 ` [PATCH v3 0/8] refs: add symref support to 'git-update-ref' Karthik Nayak
` (2 preceding siblings ...)
2024-04-23 21:28 ` [PATCH v3 3/8] files-backend: extract out `create_symref_lock` Karthik Nayak
@ 2024-04-23 21:28 ` Karthik Nayak
2024-04-23 21:28 ` [PATCH v3 5/8] update-ref: support symrefs in the delete command Karthik Nayak
` (6 subsequent siblings)
10 siblings, 0 replies; 194+ messages in thread
From: Karthik Nayak @ 2024-04-23 21:28 UTC (permalink / raw)
To: karthik.188; +Cc: chris.torek, git, gitster, ps
From: Karthik Nayak <karthik.188@gmail.com>
In the previous commits, we added the required base for adding symref
support to the transaction commands provided by 'git-update-ref(1)'.
Using them, extend the 'verify' command to support symrefs.
The 'verify' command allows users to verify if a provided symbolic
reference `<ref>` contains the provided `<old-oid>` without changing the
`<ref>`. Now we alternatively allow users to provide a
`ref:<old-target>` instead to verify if a symref targets the provided
target. Since we're checking for symbolic refs, this will only work with
the 'no-deref' mode. This is because any dereferenced symbolic ref will
point to an object and not a ref.
Add and use `null_new_value`, a helper function which is used to check
if there is a new_value in a reference update. The new value could
either be a symref target `new_target` or a OID `new_oid`. We also add
tests to test the command in both the regular stdin mode and also with
the '-z' flag.
We also disable the reference-transaction hook for symref-updates which
will be tackled in its own commit.
Add required tests for symref support in 'verify' while also adding
reflog checks for the pre-existing 'verify' tests.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
Documentation/git-update-ref.txt | 13 ++++--
builtin/update-ref.c | 14 ++++--
refs.c | 30 ++++++++++--
refs.h | 1 +
refs/files-backend.c | 43 +++++++++++++++++
refs/refs-internal.h | 7 +++
refs/reftable-backend.c | 21 ++++++++-
t/t1400-update-ref.sh | 79 +++++++++++++++++++++++++++++++-
8 files changed, 194 insertions(+), 14 deletions(-)
diff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt
index 374a2ebd2b..9f8c059944 100644
--- a/Documentation/git-update-ref.txt
+++ b/Documentation/git-update-ref.txt
@@ -64,7 +64,7 @@ performs all modifications together. Specify commands of the form:
update SP <ref> SP <new-oid> [SP <old-oid>] LF
create SP <ref> SP <new-oid> LF
delete SP <ref> [SP <old-oid>] LF
- verify SP <ref> [SP <old-oid>] LF
+ verify SP <ref> [SP (<old-oid> | ref:<old-target>)] LF
option SP <opt> LF
start LF
prepare LF
@@ -85,7 +85,7 @@ quoting:
update SP <ref> NUL <new-oid> NUL [<old-oid>] NUL
create SP <ref> NUL <new-oid> NUL
delete SP <ref> NUL [<old-oid>] NUL
- verify SP <ref> NUL [<old-oid>] NUL
+ verify SP <ref> NUL [(<old-oid> | ref:<old-target>)] NUL
option SP <opt> NUL
start NUL
prepare NUL
@@ -95,6 +95,12 @@ quoting:
In this format, use 40 "0" to specify a zero value, and use the empty
string to specify a missing value.
+For commands which support it, substituting the <old-oid> value with
+ref:<old-target> will ensure that the <ref> targets the specified
+old-target before the update. Similarly, substituting the <new-oid>
+with ref:<new-target> will ensure that the <ref> is a symbolic ref
+targeting the new-target after the update.
+
In either format, values can be specified in any form that Git
recognizes as an object name. Commands in any other format or a
repeated <ref> produce an error. Command meanings are:
@@ -115,7 +121,8 @@ delete::
verify::
Verify <ref> against <old-oid> but do not change it. If
- <old-oid> is zero or missing, the ref must not exist.
+ <old-oid> is zero or missing, the ref must not exist. For
+ verifying symbolic refs, provide ref:<old-target>.
option::
Modify the behavior of the next command naming a <ref>.
diff --git a/builtin/update-ref.c b/builtin/update-ref.c
index 98ec356394..246167e835 100644
--- a/builtin/update-ref.c
+++ b/builtin/update-ref.c
@@ -308,6 +308,7 @@ static void parse_cmd_verify(struct ref_transaction *transaction,
const char *next, const char *end)
{
struct strbuf err = STRBUF_INIT;
+ struct strbuf old_target = STRBUF_INIT;
char *refname;
struct object_id old_oid;
@@ -315,20 +316,27 @@ static void parse_cmd_verify(struct ref_transaction *transaction,
if (!refname)
die("verify: missing <ref>");
- if (parse_next_arg(&next, end, &old_oid, NULL,
- "verify", refname, PARSE_SHA1_OLD))
+ if (parse_next_arg(&next, end, &old_oid, &old_target,
+ "verify", refname,
+ PARSE_SHA1_OLD | PARSE_REFNAME_TARGETS))
oidclr(&old_oid);
+ if (old_target.len && !(update_flags & REF_NO_DEREF))
+ die("verify %s: cannot operate on symrefs in deref mode", refname);
+
if (*next != line_termination)
die("verify %s: extra input: %s", refname, next);
- if (ref_transaction_verify(transaction, refname, &old_oid,
+ if (ref_transaction_verify(transaction, refname,
+ old_target.len ? NULL : &old_oid,
+ old_target.len ? old_target.buf : NULL,
update_flags, &err))
die("%s", err.buf);
update_flags = default_flags;
free(refname);
strbuf_release(&err);
+ strbuf_release(&old_target);
}
static void report_ok(const char *command)
diff --git a/refs.c b/refs.c
index 060a31616d..0e1013b5ab 100644
--- a/refs.c
+++ b/refs.c
@@ -1217,6 +1217,8 @@ void ref_transaction_free(struct ref_transaction *transaction)
for (i = 0; i < transaction->nr; i++) {
free(transaction->updates[i]->msg);
+ free((void *)transaction->updates[i]->old_target);
+ free((void *)transaction->updates[i]->new_target);
free(transaction->updates[i]);
}
free(transaction->updates);
@@ -1247,9 +1249,13 @@ struct ref_update *ref_transaction_add_update(
update->flags = flags;
- if (flags & REF_HAVE_NEW)
+ if (new_target)
+ update->new_target = xstrdup(new_target);
+ if (old_target)
+ update->old_target = xstrdup(old_target);
+ if (new_oid && flags & REF_HAVE_NEW)
oidcpy(&update->new_oid, new_oid);
- if (flags & REF_HAVE_OLD)
+ if (old_oid && flags & REF_HAVE_OLD)
oidcpy(&update->old_oid, old_oid);
update->msg = normalize_reflog_message(msg);
return update;
@@ -1286,6 +1292,7 @@ int ref_transaction_update(struct ref_transaction *transaction,
flags &= REF_TRANSACTION_UPDATE_ALLOWED_FLAGS;
flags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0);
+ flags |= (new_target ? REF_HAVE_NEW : 0) | (old_target ? REF_HAVE_OLD : 0);
ref_transaction_add_update(transaction, refname, flags,
new_oid, old_oid, new_target,
@@ -1325,14 +1332,17 @@ int ref_transaction_delete(struct ref_transaction *transaction,
int ref_transaction_verify(struct ref_transaction *transaction,
const char *refname,
const struct object_id *old_oid,
+ const char *old_target,
unsigned int flags,
struct strbuf *err)
{
- if (!old_oid)
- BUG("verify called with old_oid set to NULL");
+ if (!old_target && !old_oid)
+ BUG("verify called with old_oid and old_target set to NULL");
+ if (old_target && !(flags & REF_NO_DEREF))
+ BUG("verify cannot operate on symrefs with deref mode");
return ref_transaction_update(transaction, refname,
NULL, old_oid,
- NULL, NULL,
+ NULL, old_target,
flags, NULL, err);
}
@@ -2349,6 +2359,12 @@ static int run_transaction_hook(struct ref_transaction *transaction,
for (i = 0; i < transaction->nr; i++) {
struct ref_update *update = transaction->updates[i];
+ /*
+ * Skip reference transaction for symbolic refs.
+ */
+ if (update->new_target || update->old_target)
+ continue;
+
strbuf_reset(&buf);
strbuf_addf(&buf, "%s %s %s\n",
oid_to_hex(&update->old_oid),
@@ -2802,3 +2818,7 @@ int copy_existing_ref(const char *oldref, const char *newref, const char *logmsg
{
return refs_copy_existing_ref(get_main_ref_store(the_repository), oldref, newref, logmsg);
}
+
+int ref_update_is_null_new_value(struct ref_update *update) {
+ return !update->new_target && is_null_oid(&update->new_oid);
+}
diff --git a/refs.h b/refs.h
index c792e13a64..27b9aeaf54 100644
--- a/refs.h
+++ b/refs.h
@@ -780,6 +780,7 @@ int ref_transaction_delete(struct ref_transaction *transaction,
int ref_transaction_verify(struct ref_transaction *transaction,
const char *refname,
const struct object_id *old_oid,
+ const char *old_target,
unsigned int flags,
struct strbuf *err);
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 2420dac2aa..53197fa3af 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -2425,6 +2425,37 @@ static const char *original_update_refname(struct ref_update *update)
return update->refname;
}
+/*
+ * Check whether the REF_HAVE_OLD and old_target values stored in
+ * update are consistent with ref, which is the symbolic reference's
+ * current value. If everything is OK, return 0; otherwise, write an
+ * error message to err and return -1.
+ */
+static int check_old_target(struct ref_update *update, char *ref,
+ struct strbuf *err)
+{
+ if (!(update->flags & REF_HAVE_OLD) ||
+ !strcmp(update->old_target, ref))
+ return 0;
+
+ if (!strcmp(update->old_target, ""))
+ strbuf_addf(err, "cannot lock ref '%s': "
+ "reference already exists",
+ original_update_refname(update));
+ else if (!strcmp(ref, ""))
+ strbuf_addf(err, "cannot lock ref '%s': "
+ "reference is missing but expected %s",
+ original_update_refname(update),
+ update->old_target);
+ else
+ strbuf_addf(err, "cannot lock ref '%s': "
+ "is at %s but expected %s",
+ original_update_refname(update),
+ ref, update->old_target);
+
+ return -1;
+}
+
/*
* Check whether the REF_HAVE_OLD and old_oid values stored in update
* are consistent with oid, which is the reference's current value. If
@@ -2528,6 +2559,18 @@ static int lock_ref_for_update(struct files_ref_store *refs,
ret = TRANSACTION_GENERIC_ERROR;
goto out;
}
+ }
+
+ /*
+ * For symref verification, we need to check the reference value
+ * rather than the oid. If we're dealing with regular refs or we're
+ * verifying a dereferenced symref, we then check the oid.
+ */
+ if (update->old_target) {
+ if (check_old_target(update, referent.buf, err)) {
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto out;
+ }
} else if (check_old_oid(update, &lock->old_oid, err)) {
ret = TRANSACTION_GENERIC_ERROR;
goto out;
diff --git a/refs/refs-internal.h b/refs/refs-internal.h
index 3040d4797c..23e65f65e8 100644
--- a/refs/refs-internal.h
+++ b/refs/refs-internal.h
@@ -748,4 +748,11 @@ void base_ref_store_init(struct ref_store *refs, struct repository *repo,
*/
struct ref_store *maybe_debug_wrap_ref_store(const char *gitdir, struct ref_store *store);
+/*
+ * Helper function to check if the new value is null, this
+ * takes into consideration that the update could be a regular
+ * ref or a symbolic ref.
+ */
+int ref_update_is_null_new_value(struct ref_update *update);
+
#endif /* REFS_REFS_INTERNAL_H */
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index 6104471199..a2474245aa 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -938,7 +938,26 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
* individual refs. But the error messages match what the files
* backend returns, which keeps our tests happy.
*/
- if (u->flags & REF_HAVE_OLD && !oideq(¤t_oid, &u->old_oid)) {
+ if ((u->flags & REF_HAVE_OLD) && u->old_target) {
+ if (strcmp(referent.buf, u->old_target)) {
+ if (!strcmp(u->old_target, ""))
+ strbuf_addf(err, "verifying symref target: '%s': "
+ "provided target is empty",
+ original_update_refname(u));
+ else if (!strcmp(referent.buf, ""))
+ strbuf_addf(err, "verifying symref target: '%s': "
+ "reference is missing but expected %s",
+ original_update_refname(u),
+ u->old_target);
+ else
+ strbuf_addf(err, "verifying symref target: '%s': "
+ "is at %s but expected %s",
+ original_update_refname(u),
+ referent.buf, u->old_target);
+ ret = -1;
+ goto done;
+ }
+ } else if (u->flags & REF_HAVE_OLD && !oideq(¤t_oid, &u->old_oid)) {
if (is_null_oid(&u->old_oid))
strbuf_addf(err, _("cannot lock ref '%s': "
"reference already exists"),
diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh
index ec3443cc87..1f2b63755a 100755
--- a/t/t1400-update-ref.sh
+++ b/t/t1400-update-ref.sh
@@ -890,17 +890,23 @@ test_expect_success 'stdin update/create/verify combination works' '
'
test_expect_success 'stdin verify succeeds for correct value' '
+ test-tool ref-store main for-each-reflog-ent $m >before &&
git rev-parse $m >expect &&
echo "verify $m $m" >stdin &&
git update-ref --stdin <stdin &&
git rev-parse $m >actual &&
- test_cmp expect actual
+ test_cmp expect actual &&
+ test-tool ref-store main for-each-reflog-ent $m >after &&
+ test_cmp before after
'
test_expect_success 'stdin verify succeeds for missing reference' '
+ test-tool ref-store main for-each-reflog-ent $m >before &&
echo "verify refs/heads/missing $Z" >stdin &&
git update-ref --stdin <stdin &&
- test_must_fail git rev-parse --verify -q refs/heads/missing
+ test_must_fail git rev-parse --verify -q refs/heads/missing &&
+ test-tool ref-store main for-each-reflog-ent $m >after &&
+ test_cmp before after
'
test_expect_success 'stdin verify treats no value as missing' '
@@ -1641,4 +1647,73 @@ test_expect_success PIPE 'transaction flushes status updates' '
test_cmp expected actual
'
+create_stdin_buf () {
+ if test "$1" = "-z"
+ then
+ shift
+ printf "$F" "$@" >stdin
+ else
+ echo "$@" >stdin
+ fi
+}
+
+for type in "" "-z"
+do
+
+ test_expect_success "stdin ${type} verify symref fails without --no-deref" '
+ git symbolic-ref refs/heads/symref $a &&
+ create_stdin_buf ${type} "verify refs/heads/symref" "ref:$a" &&
+ test_must_fail git update-ref --stdin ${type} <stdin 2>err &&
+ grep "fatal: verify refs/heads/symref: cannot operate on symrefs in deref mode" err
+ '
+
+ test_expect_success "stdin ${type} verify symref fails with too many arguments" '
+ create_stdin_buf ${type} "verify refs/heads/symref" "ref:$a" "ref:$a" &&
+ test_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&
+ if test "$type" = "-z"
+ then
+ grep "fatal: unknown command: ref:$a" err
+ else
+ grep "fatal: verify refs/heads/symref: extra input: ref:$a" err
+ fi
+ '
+
+ test_expect_success "stdin ${type} verify symref succeeds for correct value" '
+ git symbolic-ref refs/heads/symref >expect &&
+ test-tool ref-store main for-each-reflog-ent refs/heads/symref >before &&
+ create_stdin_buf ${type} "verify refs/heads/symref" "ref:$a" &&
+ git update-ref --stdin ${type} --no-deref <stdin &&
+ git symbolic-ref refs/heads/symref >actual &&
+ test_cmp expect actual &&
+ test-tool ref-store main for-each-reflog-ent refs/heads/symref >after &&
+ test_cmp before after
+ '
+
+ test_expect_success "stdin ${type} verify symref succeeds for missing reference" '
+ test-tool ref-store main for-each-reflog-ent refs/heads/symref >before &&
+ create_stdin_buf ${type} "verify refs/heads/missing" "$Z" &&
+ git update-ref --stdin ${type} --no-deref <stdin &&
+ test_must_fail git rev-parse --verify -q refs/heads/missing &&
+ test-tool ref-store main for-each-reflog-ent refs/heads/symref >after &&
+ test_cmp before after
+ '
+
+ test_expect_success "stdin ${type} verify symref fails for wrong value" '
+ git symbolic-ref refs/heads/symref >expect &&
+ create_stdin_buf ${type} "verify refs/heads/symref" "$b" &&
+ test_must_fail git update-ref --stdin ${type} --no-deref <stdin &&
+ git symbolic-ref refs/heads/symref >actual &&
+ test_cmp expect actual
+ '
+
+ test_expect_success "stdin ${type} verify symref fails for mistaken null value" '
+ git symbolic-ref refs/heads/symref >expect &&
+ create_stdin_buf ${type} "verify refs/heads/symref" "$Z" &&
+ test_must_fail git update-ref --stdin ${type} --no-deref <stdin &&
+ git symbolic-ref refs/heads/symref >actual &&
+ test_cmp expect actual
+ '
+
+done
+
test_done
--
2.43.GIT
^ permalink raw reply related [flat|nested] 194+ messages in thread
* [PATCH v3 5/8] update-ref: support symrefs in the delete command
2024-04-23 21:28 ` [PATCH v3 0/8] refs: add symref support to 'git-update-ref' Karthik Nayak
` (3 preceding siblings ...)
2024-04-23 21:28 ` [PATCH v3 4/8] update-ref: support symrefs in the verify command Karthik Nayak
@ 2024-04-23 21:28 ` Karthik Nayak
2024-04-23 21:28 ` [PATCH v3 6/8] update-ref: support symrefs in the create command Karthik Nayak
` (5 subsequent siblings)
10 siblings, 0 replies; 194+ messages in thread
From: Karthik Nayak @ 2024-04-23 21:28 UTC (permalink / raw)
To: karthik.188; +Cc: chris.torek, git, gitster, ps
From: Karthik Nayak <karthik.188@gmail.com>
The 'delete' command in 'git-update-ref' allows users to delete `<ref>`
after verifying it exists with `<old-oid>`, if given. Extend this command
to alternatively take in `ref:<old-target>` which is used to verify if
the symbolic ref targets the provided `<old-target>` before deletion.
This will only work when used with the 'no-deref' mode as it doesn't
make sense to deref a symref during deletion.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
Documentation/git-update-ref.txt | 10 +++++---
builtin/fetch.c | 2 +-
builtin/receive-pack.c | 3 ++-
builtin/update-ref.c | 18 +++++++++----
refs.c | 12 ++++++---
refs.h | 4 ++-
refs/files-backend.c | 2 +-
refs/reftable-backend.c | 2 +-
t/t1400-update-ref.sh | 44 ++++++++++++++++++++++++++++++++
9 files changed, 79 insertions(+), 18 deletions(-)
diff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt
index 9f8c059944..f28b026cd7 100644
--- a/Documentation/git-update-ref.txt
+++ b/Documentation/git-update-ref.txt
@@ -63,7 +63,7 @@ performs all modifications together. Specify commands of the form:
update SP <ref> SP <new-oid> [SP <old-oid>] LF
create SP <ref> SP <new-oid> LF
- delete SP <ref> [SP <old-oid>] LF
+ delete SP <ref> [SP (<old-oid> | ref:<old-target>)] LF
verify SP <ref> [SP (<old-oid> | ref:<old-target>)] LF
option SP <opt> LF
start LF
@@ -84,7 +84,7 @@ quoting:
update SP <ref> NUL <new-oid> NUL [<old-oid>] NUL
create SP <ref> NUL <new-oid> NUL
- delete SP <ref> NUL [<old-oid>] NUL
+ delete SP <ref> NUL [(<old-oid> | ref:<old-target>)] NUL
verify SP <ref> NUL [(<old-oid> | ref:<old-target>)] NUL
option SP <opt> NUL
start NUL
@@ -116,8 +116,10 @@ create::
exist. The given <new-oid> may not be zero.
delete::
- Delete <ref> after verifying it exists with <old-oid>, if
- given. If given, <old-oid> may not be zero.
+ Delete <ref> after verifying it exists with <old-oid>, if given.
+ If given, <old-oid> may not be zero. If instead, ref:<old-target>
+ is provided, verify that the symbolic ref <ref> targets
+ <old-target> before deleting it.
verify::
Verify <ref> against <old-oid> but do not change it. If
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 66840b7c5b..d02592efca 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -1383,7 +1383,7 @@ static int prune_refs(struct display_state *display_state,
if (transaction) {
for (ref = stale_refs; ref; ref = ref->next) {
result = ref_transaction_delete(transaction, ref->name, NULL, 0,
- "fetch: prune", &err);
+ NULL, "fetch: prune", &err);
if (result)
goto cleanup;
}
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index b150ef39a8..9a4667d57d 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -1576,7 +1576,8 @@ static const char *update(struct command *cmd, struct shallow_info *si)
if (ref_transaction_delete(transaction,
namespaced_name,
old_oid,
- 0, "push", &err)) {
+ 0, NULL,
+ "push", &err)) {
rp_error("%s", err.buf);
ret = "failed to delete";
} else {
diff --git a/builtin/update-ref.c b/builtin/update-ref.c
index 246167e835..cee7a5ebc0 100644
--- a/builtin/update-ref.c
+++ b/builtin/update-ref.c
@@ -274,6 +274,7 @@ static void parse_cmd_delete(struct ref_transaction *transaction,
const char *next, const char *end)
{
struct strbuf err = STRBUF_INIT;
+ struct strbuf old_target = STRBUF_INIT;
char *refname;
struct object_id old_oid;
int have_old;
@@ -282,26 +283,33 @@ static void parse_cmd_delete(struct ref_transaction *transaction,
if (!refname)
die("delete: missing <ref>");
- if (parse_next_arg(&next, end, &old_oid, NULL,
- "delete", refname, PARSE_SHA1_OLD)) {
+ if (parse_next_arg(&next, end, &old_oid, &old_target,
+ "delete", refname, PARSE_SHA1_OLD |
+ PARSE_REFNAME_TARGETS)) {
have_old = 0;
} else {
- if (is_null_oid(&old_oid))
+ if (!old_target.len && is_null_oid(&old_oid))
die("delete %s: zero <old-oid>", refname);
- have_old = 1;
+ have_old = 1 && !old_target.len;
}
+ if (old_target.len && !(update_flags & REF_NO_DEREF))
+ die("delete %s: cannot operate on symrefs in deref mode", refname);
+
if (*next != line_termination)
die("delete %s: extra input: %s", refname, next);
if (ref_transaction_delete(transaction, refname,
have_old ? &old_oid : NULL,
- update_flags, msg, &err))
+ update_flags,
+ old_target.len ? old_target.buf : NULL,
+ msg, &err))
die("%s", err.buf);
update_flags = default_flags;
free(refname);
strbuf_release(&err);
+ strbuf_release(&old_target);
}
static void parse_cmd_verify(struct ref_transaction *transaction,
diff --git a/refs.c b/refs.c
index 0e1013b5ab..6b7c46bfd8 100644
--- a/refs.c
+++ b/refs.c
@@ -979,7 +979,7 @@ int refs_delete_ref(struct ref_store *refs, const char *msg,
transaction = ref_store_transaction_begin(refs, &err);
if (!transaction ||
ref_transaction_delete(transaction, refname, old_oid,
- flags, msg, &err) ||
+ flags, NULL, msg, &err) ||
ref_transaction_commit(transaction, &err)) {
error("%s", err.buf);
ref_transaction_free(transaction);
@@ -1318,14 +1318,18 @@ int ref_transaction_create(struct ref_transaction *transaction,
int ref_transaction_delete(struct ref_transaction *transaction,
const char *refname,
const struct object_id *old_oid,
- unsigned int flags, const char *msg,
+ unsigned int flags,
+ const char *old_target,
+ const char *msg,
struct strbuf *err)
{
if (old_oid && is_null_oid(old_oid))
BUG("delete called with old_oid set to zeros");
+ if (old_target && !(flags & REF_NO_DEREF))
+ BUG("delete cannot operate on symrefs with deref mode");
return ref_transaction_update(transaction, refname,
null_oid(), old_oid,
- NULL, NULL, flags,
+ NULL, old_target, flags,
msg, err);
}
@@ -2752,7 +2756,7 @@ int refs_delete_refs(struct ref_store *refs, const char *logmsg,
for_each_string_list_item(item, refnames) {
ret = ref_transaction_delete(transaction, item->string,
- NULL, flags, msg, &err);
+ NULL, flags, NULL, msg, &err);
if (ret) {
warning(_("could not delete reference %s: %s"),
item->string, err.buf);
diff --git a/refs.h b/refs.h
index 27b9aeaf54..4be4930f04 100644
--- a/refs.h
+++ b/refs.h
@@ -766,7 +766,9 @@ int ref_transaction_create(struct ref_transaction *transaction,
int ref_transaction_delete(struct ref_transaction *transaction,
const char *refname,
const struct object_id *old_oid,
- unsigned int flags, const char *msg,
+ unsigned int flags,
+ const char *old_target,
+ const char *msg,
struct strbuf *err);
/*
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 53197fa3af..fc5037fe5a 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -2516,7 +2516,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,
files_assert_main_repository(refs, "lock_ref_for_update");
- if ((update->flags & REF_HAVE_NEW) && is_null_oid(&update->new_oid))
+ if ((update->flags & REF_HAVE_NEW) && ref_update_is_null_new_value(update))
update->flags |= REF_DELETING;
if (head_ref) {
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index a2474245aa..2b2cbca8c0 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -1120,7 +1120,7 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data
if (u->flags & REF_LOG_ONLY)
continue;
- if (u->flags & REF_HAVE_NEW && is_null_oid(&u->new_oid)) {
+ if (u->flags & REF_HAVE_NEW && ref_update_is_null_new_value(u)) {
struct reftable_ref_record ref = {
.refname = (char *)u->refname,
.update_index = ts,
diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh
index 1f2b63755a..cd1ad0d2ec 100755
--- a/t/t1400-update-ref.sh
+++ b/t/t1400-update-ref.sh
@@ -1714,6 +1714,50 @@ do
test_cmp expect actual
'
+ test_expect_success "stdin ${type} delete symref fails without --no-deref" '
+ git symbolic-ref refs/heads/symref $a &&
+ create_stdin_buf ${type} "delete refs/heads/symref" "ref:$a" &&
+ test_must_fail git update-ref --stdin ${type} <stdin 2>err &&
+ grep "fatal: delete refs/heads/symref: cannot operate on symrefs in deref mode" err
+ '
+
+ test_expect_success "stdin ${type} delete symref fails with no ref" '
+ create_stdin_buf ${type} "delete " &&
+ test_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&
+ grep "fatal: delete: missing <ref>" err
+ '
+
+ test_expect_success "stdin ${type} delete symref fails with too many arguments" '
+ create_stdin_buf ${type} "delete refs/heads/symref" "ref:$a" "ref:$a" &&
+ test_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&
+ if test "$type" = "-z"
+ then
+ grep "fatal: unknown command: ref:$a" err
+ else
+ grep "fatal: delete refs/heads/symref: extra input: ref:$a" err
+ fi
+ '
+
+ test_expect_success "stdin ${type} delete symref fails with wrong old value" '
+ create_stdin_buf ${type} "delete refs/heads/symref" "ref:$m" &&
+ test_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&
+ if test_have_prereq REFTABLE
+ then
+ grep "fatal: verifying symref target: ${SQ}refs/heads/symref${SQ}: is at $a but expected refs/heads/main" err
+ else
+ grep "fatal: cannot lock ref ${SQ}refs/heads/symref${SQ}" err
+ fi &&
+ git symbolic-ref refs/heads/symref >expect &&
+ echo $a >actual &&
+ test_cmp expect actual
+ '
+
+ test_expect_success "stdin ${type} delete symref works with right old value" '
+ create_stdin_buf ${type} "delete refs/heads/symref" "ref:$a" &&
+ git update-ref --stdin ${type} --no-deref <stdin &&
+ test_must_fail git rev-parse --verify -q $b
+ '
+
done
test_done
--
2.43.GIT
^ permalink raw reply related [flat|nested] 194+ messages in thread
* [PATCH v3 6/8] update-ref: support symrefs in the create command
2024-04-23 21:28 ` [PATCH v3 0/8] refs: add symref support to 'git-update-ref' Karthik Nayak
` (4 preceding siblings ...)
2024-04-23 21:28 ` [PATCH v3 5/8] update-ref: support symrefs in the delete command Karthik Nayak
@ 2024-04-23 21:28 ` Karthik Nayak
2024-04-23 21:28 ` [PATCH v3 7/8] update-ref: support symrefs in the update command Karthik Nayak
` (4 subsequent siblings)
10 siblings, 0 replies; 194+ messages in thread
From: Karthik Nayak @ 2024-04-23 21:28 UTC (permalink / raw)
To: karthik.188; +Cc: chris.torek, git, gitster, ps
From: Karthik Nayak <karthik.188@gmail.com>
The 'create' commands in 'git-update-ref' allows users to create `<ref>`
with `<new-oid>` after verifying it does not exist. Extend this command
to alternatively take in `ref:<new-target>` which is used to create a
symref with `<new-target>` as its target.
Also, support the 'core.prefersymlinkrefs' config, wherein if the config
is set and the filesystem supports symlinks, we create the symbolic ref
as a symlink. We fallback to creating a regular symref if creating the
symlink is unsuccessful.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
Documentation/git-update-ref.txt | 8 +++--
builtin/clone.c | 2 +-
builtin/update-ref.c | 14 ++++++--
refs.c | 9 ++++--
refs.h | 1 +
refs/files-backend.c | 42 ++++++++++++++++++++++++
refs/reftable-backend.c | 23 +++++++++++--
t/t0600-reffiles-backend.sh | 32 +++++++++++++++++++
t/t1400-update-ref.sh | 55 ++++++++++++++++++++++++++++++++
9 files changed, 173 insertions(+), 13 deletions(-)
diff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt
index f28b026cd7..1202769178 100644
--- a/Documentation/git-update-ref.txt
+++ b/Documentation/git-update-ref.txt
@@ -62,7 +62,7 @@ With `--stdin`, update-ref reads instructions from standard input and
performs all modifications together. Specify commands of the form:
update SP <ref> SP <new-oid> [SP <old-oid>] LF
- create SP <ref> SP <new-oid> LF
+ create SP <ref> SP (<new-oid> | ref:<new-target>) LF
delete SP <ref> [SP (<old-oid> | ref:<old-target>)] LF
verify SP <ref> [SP (<old-oid> | ref:<old-target>)] LF
option SP <opt> LF
@@ -83,7 +83,7 @@ Alternatively, use `-z` to specify in NUL-terminated format, without
quoting:
update SP <ref> NUL <new-oid> NUL [<old-oid>] NUL
- create SP <ref> NUL <new-oid> NUL
+ create SP <ref> NUL (<new-oid> | ref:<new-target>) NUL
delete SP <ref> NUL [(<old-oid> | ref:<old-target>)] NUL
verify SP <ref> NUL [(<old-oid> | ref:<old-target>)] NUL
option SP <opt> NUL
@@ -113,7 +113,9 @@ update::
create::
Create <ref> with <new-oid> after verifying it does not
- exist. The given <new-oid> may not be zero.
+ exist. The given <new-oid> may not be zero. If instead
+ ref:<new-target> is provided, a symbolic ref is created
+ which targets <new-target>.
delete::
Delete <ref> after verifying it exists with <old-oid>, if given.
diff --git a/builtin/clone.c b/builtin/clone.c
index 74ec14542e..c0eed8e795 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -547,7 +547,7 @@ static void write_remote_refs(const struct ref *local_refs)
if (!r->peer_ref)
continue;
if (ref_transaction_create(t, r->peer_ref->name, &r->old_oid,
- 0, NULL, &err))
+ NULL, 0, NULL, &err))
die("%s", err.buf);
}
diff --git a/builtin/update-ref.c b/builtin/update-ref.c
index cee7a5ebc0..afab706cd7 100644
--- a/builtin/update-ref.c
+++ b/builtin/update-ref.c
@@ -244,6 +244,7 @@ static void parse_cmd_create(struct ref_transaction *transaction,
const char *next, const char *end)
{
struct strbuf err = STRBUF_INIT;
+ struct strbuf new_target = STRBUF_INIT;
char *refname;
struct object_id new_oid;
@@ -251,16 +252,22 @@ static void parse_cmd_create(struct ref_transaction *transaction,
if (!refname)
die("create: missing <ref>");
- if (parse_next_arg(&next, end, &new_oid, NULL, "create", refname, 0))
+ if (parse_next_arg(&next, end, &new_oid, &new_target,
+ "create", refname, PARSE_REFNAME_TARGETS))
die("create %s: missing <new-oid>", refname);
- if (is_null_oid(&new_oid))
+ if (!new_target.len && is_null_oid(&new_oid))
die("create %s: zero <new-oid>", refname);
+ if (new_target.len && !(update_flags & REF_NO_DEREF))
+ die("create %s: cannot create symrefs in deref mode", refname);
+
if (*next != line_termination)
die("create %s: extra input: %s", refname, next);
- if (ref_transaction_create(transaction, refname, &new_oid,
+ if (ref_transaction_create(transaction, refname,
+ new_target.len ? NULL : &new_oid ,
+ new_target.len ? new_target.buf : NULL,
update_flags | create_reflog_flag,
msg, &err))
die("%s", err.buf);
@@ -268,6 +275,7 @@ static void parse_cmd_create(struct ref_transaction *transaction,
update_flags = default_flags;
free(refname);
strbuf_release(&err);
+ strbuf_release(&new_target);
}
static void parse_cmd_delete(struct ref_transaction *transaction,
diff --git a/refs.c b/refs.c
index 6b7c46bfd8..42cb4126a7 100644
--- a/refs.c
+++ b/refs.c
@@ -1303,15 +1303,18 @@ int ref_transaction_update(struct ref_transaction *transaction,
int ref_transaction_create(struct ref_transaction *transaction,
const char *refname,
const struct object_id *new_oid,
+ const char *new_target,
unsigned int flags, const char *msg,
struct strbuf *err)
{
- if (!new_oid || is_null_oid(new_oid)) {
- strbuf_addf(err, "'%s' has a null OID", refname);
+ if ((!new_oid || is_null_oid(new_oid)) && !new_target) {
+ strbuf_addf(err, "'%s' has a null OID or no new target", refname);
return 1;
}
+ if (new_target && !(flags & REF_NO_DEREF))
+ BUG("create cannot operate on symrefs with deref mode");
return ref_transaction_update(transaction, refname, new_oid,
- null_oid(), NULL, NULL, flags,
+ null_oid(), new_target, NULL, flags,
msg, err);
}
diff --git a/refs.h b/refs.h
index 4be4930f04..bde8606213 100644
--- a/refs.h
+++ b/refs.h
@@ -752,6 +752,7 @@ int ref_transaction_update(struct ref_transaction *transaction,
int ref_transaction_create(struct ref_transaction *transaction,
const char *refname,
const struct object_id *new_oid,
+ const char *new_target,
unsigned int flags, const char *msg,
struct strbuf *err);
diff --git a/refs/files-backend.c b/refs/files-backend.c
index fc5037fe5a..f5e271a442 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -2610,6 +2610,27 @@ static int lock_ref_for_update(struct files_ref_store *refs,
}
}
+ if (update->new_target) {
+ if (create_symref_lock(refs, lock, update->refname, update->new_target)) {
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto out;
+ }
+
+ if (close_ref_gently(lock)) {
+ strbuf_addf(err, "couldn't close '%s.lock'",
+ update->refname);
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto out;
+ }
+
+ /*
+ * Once we have created the symref lock, the commit
+ * phase of the transaction only needs to commit the lock.
+ */
+ update->flags |= REF_NEEDS_COMMIT;
+ }
+
+
if ((update->flags & REF_HAVE_NEW) &&
!(update->flags & REF_DELETING) &&
!(update->flags & REF_LOG_ONLY)) {
@@ -2905,6 +2926,18 @@ static int files_transaction_finish(struct ref_store *ref_store,
if (update->flags & REF_NEEDS_COMMIT ||
update->flags & REF_LOG_ONLY) {
+ if (update->new_target) {
+ /*
+ * We want to get the resolved OID for the target, to ensure
+ * that the correct value is added to the reflog.
+ */
+ if (!refs_resolve_ref_unsafe(&refs->base, update->new_target,
+ RESOLVE_REF_READING, &update->new_oid, NULL)) {
+ /* for dangling symrefs we gracefully set the oid to zero */
+ update->new_oid = *null_oid();
+ }
+ }
+
if (files_log_ref_write(refs,
lock->ref_name,
&lock->old_oid,
@@ -2922,6 +2955,15 @@ static int files_transaction_finish(struct ref_store *ref_store,
goto cleanup;
}
}
+
+ /*
+ * We try creating a symlink, if that succeeds we continue to the
+ * next updated. If not, we try and create a regular symref.
+ */
+ if (update->new_target && prefer_symlink_refs)
+ if (!create_ref_symlink(lock, update->new_target))
+ continue;
+
if (update->flags & REF_NEEDS_COMMIT) {
clear_loose_ref_cache(refs);
if (commit_ref(lock)) {
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index 2b2cbca8c0..e203c697f2 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -856,7 +856,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
* There is no need to write the reference deletion
* when the reference in question doesn't exist.
*/
- if (u->flags & REF_HAVE_NEW && !is_null_oid(&u->new_oid)) {
+ if (u->flags & REF_HAVE_NEW && !ref_update_is_null_new_value(u)) {
ret = queue_transaction_update(refs, tx_data, u,
¤t_oid, err);
if (ret)
@@ -1062,7 +1062,7 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data
* - `core.logAllRefUpdates` tells us to create the reflog for
* the given ref.
*/
- if (u->flags & REF_HAVE_NEW && !(u->type & REF_ISSYMREF) && is_null_oid(&u->new_oid)) {
+ if (u->flags & REF_HAVE_NEW && !(u->type & REF_ISSYMREF) && ref_update_is_null_new_value(u)) {
struct reftable_log_record log = {0};
struct reftable_iterator it = {0};
@@ -1104,6 +1104,12 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data
should_write_log(&arg->refs->base, u->refname))) {
struct reftable_log_record *log;
+ if (u->new_target)
+ if (!refs_resolve_ref_unsafe(&arg->refs->base, u->new_target,
+ RESOLVE_REF_READING, &u->new_oid, NULL))
+ /* for dangling symrefs we gracefully set the oid to zero */
+ u->new_oid = *null_oid();
+
ALLOC_GROW(logs, logs_nr + 1, logs_alloc);
log = &logs[logs_nr++];
memset(log, 0, sizeof(*log));
@@ -1120,7 +1126,18 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data
if (u->flags & REF_LOG_ONLY)
continue;
- if (u->flags & REF_HAVE_NEW && ref_update_is_null_new_value(u)) {
+ if (u->flags & REF_HAVE_NEW && u->new_target) {
+ struct reftable_ref_record ref = {
+ .refname = (char *)u->refname,
+ .value_type = REFTABLE_REF_SYMREF,
+ .value.symref = (char *)u->new_target,
+ .update_index = ts,
+ };
+
+ ret = reftable_writer_add_ref(writer, &ref);
+ if (ret < 0)
+ goto done;
+ } else if (u->flags & REF_HAVE_NEW && ref_update_is_null_new_value(u)) {
struct reftable_ref_record ref = {
.refname = (char *)u->refname,
.update_index = ts,
diff --git a/t/t0600-reffiles-backend.sh b/t/t0600-reffiles-backend.sh
index 64214340e7..1291242940 100755
--- a/t/t0600-reffiles-backend.sh
+++ b/t/t0600-reffiles-backend.sh
@@ -472,4 +472,36 @@ test_expect_success POSIXPERM 'git reflog expire honors core.sharedRepository' '
esac
'
+test_expect_success SYMLINKS 'symref transaction supports symlinks' '
+ test_when_finished "git symbolic-ref -d TESTSYMREFONE" &&
+ git update-ref refs/heads/new @ &&
+ test_config core.prefersymlinkrefs true &&
+ cat >stdin <<-EOF &&
+ start
+ create TESTSYMREFONE ref:refs/heads/new
+ prepare
+ commit
+ EOF
+ git update-ref --no-deref --stdin <stdin &&
+ test_path_is_symlink .git/TESTSYMREFONE &&
+ test "$(test_readlink .git/TESTSYMREFONE)" = refs/heads/new
+'
+
+test_expect_success 'symref transaction supports false symlink config' '
+ test_when_finished "git symbolic-ref -d TESTSYMREFONE" &&
+ git update-ref refs/heads/new @ &&
+ test_config core.prefersymlinkrefs false &&
+ cat >stdin <<-EOF &&
+ start
+ create TESTSYMREFONE ref:refs/heads/new
+ prepare
+ commit
+ EOF
+ git update-ref --no-deref --stdin <stdin &&
+ test_path_is_file .git/TESTSYMREFONE &&
+ git symbolic-ref TESTSYMREFONE >actual &&
+ echo refs/heads/new >expect &&
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh
index cd1ad0d2ec..e85d08ce5c 100755
--- a/t/t1400-update-ref.sh
+++ b/t/t1400-update-ref.sh
@@ -1758,6 +1758,61 @@ do
test_must_fail git rev-parse --verify -q $b
'
+ test_expect_success "stdin ${type} create symref fails without --no-deref" '
+ create_stdin_buf ${type} "create refs/heads/symref" "ref:$a" &&
+ test_must_fail git update-ref --stdin ${type} <stdin 2>err &&
+ grep "fatal: create refs/heads/symref: cannot create symrefs in deref mode" err
+ '
+
+ test_expect_success "stdin ${type} create symref fails with too many arguments" '
+ create_stdin_buf ${type} "create refs/heads/symref" "ref:$a" "ref:$a" >stdin &&
+ test_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&
+ if test "$type" = "-z"
+ then
+ grep "fatal: unknown command: ref:$a" err
+ else
+ grep "fatal: create refs/heads/symref: extra input: ref:$a" err
+ fi
+ '
+
+ test_expect_success "stdin ${type} create symref ref works" '
+ test_when_finished "git symbolic-ref -d refs/heads/symref" &&
+ create_stdin_buf ${type} "create refs/heads/symref" "ref:$a" >stdin &&
+ git update-ref --stdin ${type} --no-deref <stdin &&
+ git symbolic-ref refs/heads/symref >expect &&
+ echo $a >actual &&
+ test_cmp expect actual
+ '
+
+ test_expect_success "stdin ${type} create dangling symref ref works" '
+ test_when_finished "git symbolic-ref -d refs/heads/symref" &&
+ create_stdin_buf ${type} "create refs/heads/symref" "ref:refs/heads/unkown" >stdin &&
+ git update-ref --stdin ${type} --no-deref <stdin &&
+ git symbolic-ref refs/heads/symref >expect &&
+ echo refs/heads/unkown >actual &&
+ test_cmp expect actual
+ '
+
+ test_expect_success "stdin ${type} create symref does not create reflogs by default" '
+ test_when_finished "git symbolic-ref -d refs/symref" &&
+ create_stdin_buf ${type} "create refs/symref" "ref:$a" >stdin &&
+ git update-ref --stdin ${type} --no-deref <stdin &&
+ git symbolic-ref refs/symref >expect &&
+ echo $a >actual &&
+ test_cmp expect actual &&
+ test_must_fail git reflog exists refs/symref
+ '
+
+ test_expect_success "stdin ${type} create symref reflogs with --create-reflog" '
+ test_when_finished "git symbolic-ref -d refs/heads/symref" &&
+ create_stdin_buf ${type} "create refs/heads/symref" "ref:$a" >stdin &&
+ git update-ref --create-reflog --stdin ${type} --no-deref <stdin &&
+ git symbolic-ref refs/heads/symref >expect &&
+ echo $a >actual &&
+ test_cmp expect actual &&
+ git reflog exists refs/heads/symref
+ '
+
done
test_done
--
2.43.GIT
^ permalink raw reply related [flat|nested] 194+ messages in thread
* [PATCH v3 7/8] update-ref: support symrefs in the update command
2024-04-23 21:28 ` [PATCH v3 0/8] refs: add symref support to 'git-update-ref' Karthik Nayak
` (5 preceding siblings ...)
2024-04-23 21:28 ` [PATCH v3 6/8] update-ref: support symrefs in the create command Karthik Nayak
@ 2024-04-23 21:28 ` Karthik Nayak
2024-04-23 21:28 ` [PATCH v3 8/8] ref: support symrefs in 'reference-transaction' hook Karthik Nayak
` (3 subsequent siblings)
10 siblings, 0 replies; 194+ messages in thread
From: Karthik Nayak @ 2024-04-23 21:28 UTC (permalink / raw)
To: karthik.188; +Cc: chris.torek, git, gitster, ps
From: Karthik Nayak <karthik.188@gmail.com>
The 'update' command in 'git-update-ref' allows users to set `<ref>` to
`<new-oid>` after verifying `<old-oid>`, if given. Extend this command
to alternatively take in `ref:<new-target>` which is used to update to/a
symref with `<new-target>` as its target. And take in `ref:<old-target>`
which is used to verify that the symref points to `<old-target>` before
the update.
With this the 'update' command can also now be used to:
- create symrefs
- change a regular ref to a symref
- change a symref to a regular ref
This command will also support deref mode, to ensure that we can update
dereferenced regular refs to symrefs.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
Documentation/git-update-ref.txt | 10 +-
builtin/update-ref.c | 21 ++--
refs/files-backend.c | 14 ++-
refs/reftable-backend.c | 3 +-
t/t1400-update-ref.sh | 168 +++++++++++++++++++++++++++++++
5 files changed, 198 insertions(+), 18 deletions(-)
diff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt
index 1202769178..79e29fead6 100644
--- a/Documentation/git-update-ref.txt
+++ b/Documentation/git-update-ref.txt
@@ -61,7 +61,7 @@ still contains <old-oid>.
With `--stdin`, update-ref reads instructions from standard input and
performs all modifications together. Specify commands of the form:
- update SP <ref> SP <new-oid> [SP <old-oid>] LF
+ update SP <ref> SP (<new-oid> | ref:<new-target>) [SP (<old-oid> | ref:<old-target>)] LF
create SP <ref> SP (<new-oid> | ref:<new-target>) LF
delete SP <ref> [SP (<old-oid> | ref:<old-target>)] LF
verify SP <ref> [SP (<old-oid> | ref:<old-target>)] LF
@@ -82,7 +82,7 @@ specify a missing value, omit the value and its preceding SP entirely.
Alternatively, use `-z` to specify in NUL-terminated format, without
quoting:
- update SP <ref> NUL <new-oid> NUL [<old-oid>] NUL
+ update SP <ref> NUL (<new-oid> | ref:<new-target>) NUL [(<old-oid> | ref:<old-target>)] NUL
create SP <ref> NUL (<new-oid> | ref:<new-target>) NUL
delete SP <ref> NUL [(<old-oid> | ref:<old-target>)] NUL
verify SP <ref> NUL [(<old-oid> | ref:<old-target>)] NUL
@@ -109,7 +109,11 @@ update::
Set <ref> to <new-oid> after verifying <old-oid>, if given.
Specify a zero <new-oid> to ensure the ref does not exist
after the update and/or a zero <old-oid> to make sure the
- ref does not exist before the update.
+ ref does not exist before the update. If ref:<old-target>
+ is provided, we verify that the <ref> is an existing symbolic
+ ref which targets <old-target>. If ref:<new-target> is given,
+ the update ensures <ref> is a symbolic ref which targets
+ <new-target>.
create::
Create <ref> with <new-oid> after verifying it does not
diff --git a/builtin/update-ref.c b/builtin/update-ref.c
index afab706cd7..175579148f 100644
--- a/builtin/update-ref.c
+++ b/builtin/update-ref.c
@@ -210,6 +210,8 @@ static void parse_cmd_update(struct ref_transaction *transaction,
const char *next, const char *end)
{
struct strbuf err = STRBUF_INIT;
+ struct strbuf new_target = STRBUF_INIT;
+ struct strbuf old_target = STRBUF_INIT;
char *refname;
struct object_id new_oid, old_oid;
int have_old;
@@ -218,19 +220,24 @@ static void parse_cmd_update(struct ref_transaction *transaction,
if (!refname)
die("update: missing <ref>");
- if (parse_next_arg(&next, end, &new_oid, NULL,
- "update", refname, PARSE_SHA1_ALLOW_EMPTY))
+ if (parse_next_arg(&next, end, &new_oid,
+ &new_target, "update", refname,
+ PARSE_SHA1_ALLOW_EMPTY | PARSE_REFNAME_TARGETS))
die("update %s: missing <new-oid>", refname);
- have_old = !parse_next_arg(&next, end, &old_oid, NULL,
- "update", refname, PARSE_SHA1_OLD);
+ have_old = !parse_next_arg(&next, end, &old_oid,
+ &old_target, "update", refname,
+ PARSE_SHA1_OLD | PARSE_REFNAME_TARGETS);
+ have_old = have_old & !old_target.len;
if (*next != line_termination)
die("update %s: extra input: %s", refname, next);
if (ref_transaction_update(transaction, refname,
- &new_oid, have_old ? &old_oid : NULL,
- NULL, NULL,
+ new_target.len ? NULL : &new_oid,
+ have_old ? &old_oid : NULL,
+ new_target.len ? new_target.buf : NULL,
+ old_target.len ? old_target.buf : NULL,
update_flags | create_reflog_flag,
msg, &err))
die("%s", err.buf);
@@ -238,6 +245,8 @@ static void parse_cmd_update(struct ref_transaction *transaction,
update_flags = default_flags;
free(refname);
strbuf_release(&err);
+ strbuf_release(&old_target);
+ strbuf_release(&new_target);
}
static void parse_cmd_create(struct ref_transaction *transaction,
diff --git a/refs/files-backend.c b/refs/files-backend.c
index f5e271a442..59d1ab3eeb 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -2386,7 +2386,8 @@ static int split_symref_update(struct ref_update *update,
new_update = ref_transaction_add_update(
transaction, referent, new_flags,
&update->new_oid, &update->old_oid,
- NULL, NULL, update->msg);
+ update->new_target, update->old_target,
+ update->msg);
new_update->parent_update = update;
@@ -2610,7 +2611,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,
}
}
- if (update->new_target) {
+ if (update->new_target && !(update->flags & REF_LOG_ONLY)) {
if (create_symref_lock(refs, lock, update->refname, update->new_target)) {
ret = TRANSACTION_GENERIC_ERROR;
goto out;
@@ -2628,12 +2629,9 @@ static int lock_ref_for_update(struct files_ref_store *refs,
* phase of the transaction only needs to commit the lock.
*/
update->flags |= REF_NEEDS_COMMIT;
- }
-
-
- if ((update->flags & REF_HAVE_NEW) &&
- !(update->flags & REF_DELETING) &&
- !(update->flags & REF_LOG_ONLY)) {
+ } else if ((update->flags & REF_HAVE_NEW) &&
+ !(update->flags & REF_DELETING) &&
+ !(update->flags & REF_LOG_ONLY)) {
if (!(update->type & REF_ISSYMREF) &&
oideq(&lock->old_oid, &update->new_oid)) {
/*
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index e203c697f2..a00f55802a 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -908,7 +908,8 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
*/
new_update = ref_transaction_add_update(
transaction, referent.buf, new_flags,
- &u->new_oid, &u->old_oid, NULL, NULL, u->msg);
+ &u->new_oid, &u->old_oid, u->new_target,
+ u->old_target, u->msg);
new_update->parent_update = u;
/*
diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh
index e85d08ce5c..5b2d23da37 100755
--- a/t/t1400-update-ref.sh
+++ b/t/t1400-update-ref.sh
@@ -1360,6 +1360,7 @@ test_expect_success 'fails with duplicate HEAD update' '
'
test_expect_success 'fails with duplicate ref update via symref' '
+ test_when_finished "git symbolic-ref -d refs/heads/symref2" &&
git branch target2 $A &&
git symbolic-ref refs/heads/symref2 refs/heads/target2 &&
cat >stdin <<-EOF &&
@@ -1813,6 +1814,173 @@ do
git reflog exists refs/heads/symref
'
+ test_expect_success "stdin ${type} update symref fails with too many arguments" '
+ create_stdin_buf ${type} "update refs/heads/symref" "ref:$a" "ref:$a" "ref:$a" >stdin &&
+ test_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&
+ if test "$type" = "-z"
+ then
+ grep "fatal: unknown command: ref:$a" err
+ else
+ grep "fatal: update refs/heads/symref: extra input: ref:$a" err
+ fi
+ '
+
+ test_expect_success "stdin ${type} update creates symref with zero old value" '
+ test_when_finished "git symbolic-ref -d refs/heads/symref" &&
+ create_stdin_buf ${type} "update refs/heads/symref" "ref:$a" "$Z" >stdin &&
+ git update-ref --stdin ${type} --no-deref <stdin &&
+ echo $a >expect &&
+ git symbolic-ref refs/heads/symref >actual &&
+ test_cmp expect actual
+ '
+
+ test_expect_success "stdin ${type} update creates symref with empty old value" '
+ test_when_finished "git symbolic-ref -d refs/heads/symref" &&
+ create_stdin_buf ${type} "update refs/heads/symref" "ref:$a" "" >stdin &&
+ git update-ref --stdin ${type} --no-deref <stdin &&
+ echo $a >expect &&
+ git symbolic-ref refs/heads/symref >actual &&
+ test_cmp expect actual
+ '
+
+ test_expect_success "stdin ${type} update symref fails with wrong old value" '
+ test_when_finished "git symbolic-ref -d refs/heads/symref" &&
+ git symbolic-ref refs/heads/symref $a &&
+ create_stdin_buf ${type} "update refs/heads/symref" "ref:$m" "ref:$b" >stdin &&
+ test_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&
+ if test_have_prereq REFTABLE
+ then
+ grep "fatal: verifying symref target: ${SQ}refs/heads/symref${SQ}: is at $a but expected $b" err
+ else
+ grep "fatal: cannot lock ref ${SQ}refs/heads/symref${SQ}: is at $a but expected $b" err
+ fi &&
+ test_must_fail git rev-parse --verify -q $c
+ '
+
+ test_expect_success "stdin ${type} update symref works with right old value" '
+ test_when_finished "git symbolic-ref -d refs/heads/symref" &&
+ git symbolic-ref refs/heads/symref $a &&
+ create_stdin_buf ${type} "update refs/heads/symref" "ref:$m" "ref:$a" >stdin &&
+ git update-ref --stdin ${type} --no-deref <stdin &&
+ echo $m >expect &&
+ git symbolic-ref refs/heads/symref >actual &&
+ test_cmp expect actual
+ '
+
+ test_expect_success "stdin ${type} update creates symref (with deref)" '
+ test_when_finished "git symbolic-ref -d refs/heads/symref" &&
+ create_stdin_buf ${type} "update refs/heads/symref" "ref:$a" "" >stdin &&
+ git update-ref --stdin ${type} <stdin &&
+ echo $a >expect &&
+ git symbolic-ref --no-recurse refs/heads/symref >actual &&
+ test_cmp expect actual &&
+ test-tool ref-store main for-each-reflog-ent refs/heads/symref >actual &&
+ grep "$Z $(git rev-parse $a)" actual
+ '
+
+ test_expect_success "stdin ${type} update regular ref to symref with correct old-oid" '
+ test_when_finished "git symbolic-ref -d --no-recurse refs/heads/regularref" &&
+ git update-ref --no-deref refs/heads/regularref $a &&
+ create_stdin_buf ${type} "update refs/heads/regularref" "ref:$a" "$(git rev-parse $a)" >stdin &&
+ git update-ref --stdin ${type} <stdin &&
+ echo $a >expect &&
+ git symbolic-ref --no-recurse refs/heads/regularref >actual &&
+ test_cmp expect actual &&
+ test-tool ref-store main for-each-reflog-ent refs/heads/regularref >actual &&
+ grep "$(git rev-parse $a) $(git rev-parse $a)" actual
+ '
+
+ test_expect_success "stdin ${type} update regular ref to symref fails with wrong old-oid" '
+ test_when_finished "git update-ref -d refs/heads/regularref" &&
+ git update-ref --no-deref refs/heads/regularref $a &&
+ create_stdin_buf ${type} "update refs/heads/regularref" "ref:$a" "$(git rev-parse refs/heads/target2)" >stdin &&
+ test_must_fail git update-ref --stdin ${type} <stdin 2>err &&
+ echo $(git rev-parse $a) >expect &&
+ git rev-parse refs/heads/regularref >actual &&
+ test_cmp expect actual
+ '
+
+ test_expect_success "stdin ${type} update existing symref with zero old-oid" '
+ test_when_finished "git symbolic-ref -d --no-recurse refs/heads/symref" &&
+ git symbolic-ref refs/heads/symref refs/heads/target2 &&
+ create_stdin_buf ${type} "update refs/heads/symref" "ref:$a" "$Z" >stdin &&
+ test_must_fail git update-ref --stdin ${type} <stdin 2>err &&
+ grep "fatal: cannot lock ref ${SQ}refs/heads/symref${SQ}: reference already exists" err &&
+ echo refs/heads/target2 >expect &&
+ git symbolic-ref refs/heads/symref >actual &&
+ test_cmp expect actual
+ '
+
+ test_expect_success "stdin ${type} update existing symref to regular ref" '
+ test_when_finished "git update-ref -d refs/heads/symref" &&
+ git symbolic-ref refs/heads/symref refs/heads/target2 &&
+ create_stdin_buf ${type} "update refs/heads/symref" "$(git rev-parse $a)" "ref:refs/heads/target2" >stdin &&
+ git update-ref --stdin ${type} --no-deref <stdin &&
+ echo $(git rev-parse $a) >expect &&
+ git rev-parse refs/heads/symref >actual &&
+ test_cmp expect actual &&
+ test-tool ref-store main for-each-reflog-ent refs/heads/symref >actual &&
+ grep "$(git rev-parse refs/heads/target2) $(git rev-parse $a)" actual
+ '
+
done
+test_expect_success "stdin update symref (with deref)" '
+ test_when_finished "git symbolic-ref -d refs/heads/symref" &&
+ test_when_finished "git update-ref -d --no-deref refs/heads/symref2" &&
+ git update-ref refs/heads/symref2 $a &&
+ git symbolic-ref --no-recurse refs/heads/symref refs/heads/symref2 &&
+ echo "update refs/heads/symref" "ref:$a" >stdin &&
+ git update-ref --stdin <stdin &&
+ echo $a >expect &&
+ git symbolic-ref --no-recurse refs/heads/symref2 >actual &&
+ test_cmp expect actual &&
+ echo refs/heads/symref2 >expect &&
+ git symbolic-ref --no-recurse refs/heads/symref >actual &&
+ test_cmp expect actual &&
+ test-tool ref-store main for-each-reflog-ent refs/heads/symref >actual &&
+ grep "$(git rev-parse $a) $(git rev-parse $a)" actual
+'
+
+test_expect_success "stdin -z update symref (with deref)" '
+ test_when_finished "git symbolic-ref -d refs/heads/symref" &&
+ test_when_finished "git update-ref -d --no-deref refs/heads/symref2" &&
+ git update-ref refs/heads/symref2 $a &&
+ git symbolic-ref --no-recurse refs/heads/symref refs/heads/symref2 &&
+ printf "$F" "update refs/heads/symref" "ref:$a" "" >stdin &&
+ git update-ref --stdin -z <stdin &&
+ echo $a >expect &&
+ git symbolic-ref --no-recurse refs/heads/symref2 >actual &&
+ test_cmp expect actual &&
+ echo refs/heads/symref2 >expect &&
+ git symbolic-ref --no-recurse refs/heads/symref >actual &&
+ test_cmp expect actual &&
+ test-tool ref-store main for-each-reflog-ent refs/heads/symref >actual &&
+ grep "$(git rev-parse $a) $(git rev-parse $a)" actual
+'
+
+test_expect_success "stdin update regular ref to symref" '
+ test_when_finished "git symbolic-ref -d --no-recurse refs/heads/regularref" &&
+ git update-ref --no-deref refs/heads/regularref $a &&
+ echo "update refs/heads/regularref" "ref:$a" >stdin &&
+ git update-ref --stdin <stdin &&
+ echo $a >expect &&
+ git symbolic-ref --no-recurse refs/heads/regularref >actual &&
+ test_cmp expect actual &&
+ test-tool ref-store main for-each-reflog-ent refs/heads/regularref >actual &&
+ grep "$(git rev-parse $a) $(git rev-parse $a)" actual
+'
+
+test_expect_success "stdin -z update regular ref to symref" '
+ test_when_finished "git symbolic-ref -d --no-recurse refs/heads/regularref" &&
+ git update-ref --no-deref refs/heads/regularref $a &&
+ printf "$F" "update refs/heads/regularref" "ref:$a" "" >stdin &&
+ git update-ref --stdin -z <stdin &&
+ echo $a >expect &&
+ git symbolic-ref --no-recurse refs/heads/regularref >actual &&
+ test_cmp expect actual &&
+ test-tool ref-store main for-each-reflog-ent refs/heads/regularref >actual &&
+ grep "$(git rev-parse $a) $(git rev-parse $a)" actual
+'
+
test_done
--
2.43.GIT
^ permalink raw reply related [flat|nested] 194+ messages in thread
* [PATCH v3 8/8] ref: support symrefs in 'reference-transaction' hook
2024-04-23 21:28 ` [PATCH v3 0/8] refs: add symref support to 'git-update-ref' Karthik Nayak
` (6 preceding siblings ...)
2024-04-23 21:28 ` [PATCH v3 7/8] update-ref: support symrefs in the update command Karthik Nayak
@ 2024-04-23 21:28 ` Karthik Nayak
2024-04-23 22:03 ` [PATCH v3 0/8] refs: add symref support to 'git-update-ref' Jeff King
` (2 subsequent siblings)
10 siblings, 0 replies; 194+ messages in thread
From: Karthik Nayak @ 2024-04-23 21:28 UTC (permalink / raw)
To: karthik.188; +Cc: chris.torek, git, gitster, ps
From: Karthik Nayak <karthik.188@gmail.com>
The 'reference-transaction' hook runs whenever a reference update is
made to the system. In the previous commits, we added symref support for
various commands in `git-update-ref`. While it allowed us to now
manipulate symbolic refs via `git-update-ref`, it didn't activate the
'reference-transaction' hook.
Let's activate the hook for symbolic reference updates too. There is no
new format described for this and we stick to the existing format of:
<old-value> SP <new-value> SP <ref-name> LF
but now, <old-value> and <new-value> could also denote references
instead of objects, where the format is similar to that in
'git-update-ref', i.e. 'ref:<ref-target>'.
While this seems to be backward incompatible, it is okay, since the only
way the `reference-transaction` hook has refs in its output is when
`git-update-ref` is used to manipulate symrefs. Also the documentation
for reference-transaction hook always stated that support for symbolic
references may be added in the future.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
Documentation/githooks.txt | 14 +++++++----
refs.c | 21 ++++++++--------
t/t1416-ref-transaction-hooks.sh | 41 ++++++++++++++++++++++++++++++++
3 files changed, 61 insertions(+), 15 deletions(-)
diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
index ee9b92c90d..0bf8ca87a6 100644
--- a/Documentation/githooks.txt
+++ b/Documentation/githooks.txt
@@ -486,7 +486,7 @@ reference-transaction
This hook is invoked by any Git command that performs reference
updates. It executes whenever a reference transaction is prepared,
committed or aborted and may thus get called multiple times. The hook
-does not cover symbolic references (but that may change in the future).
+also cover symbolic references.
The hook takes exactly one argument, which is the current state the
given reference transaction is in:
@@ -503,16 +503,20 @@ given reference transaction is in:
For each reference update that was added to the transaction, the hook
receives on standard input a line of the format:
- <old-oid> SP <new-oid> SP <ref-name> LF
+ <old-value> SP <new-value> SP <ref-name> LF
-where `<old-oid>` is the old object name passed into the reference
-transaction, `<new-oid>` is the new object name to be stored in the
+where `<old-value>` is the old object name passed into the reference
+transaction, `<new-value>` is the new object name to be stored in the
ref and `<ref-name>` is the full name of the ref. When force updating
the reference regardless of its current value or when the reference is
-to be created anew, `<old-oid>` is the all-zeroes object name. To
+to be created anew, `<old-value>` is the all-zeroes object name. To
distinguish these cases, you can inspect the current value of
`<ref-name>` via `git rev-parse`.
+For symbolic reference updates the `<old_value>` and `<new-value>`
+fields could denote references instead of objects, denoted via the
+`ref:<ref-target>` format.
+
The exit status of the hook is ignored for any state except for the
"prepared" state. In the "prepared" state, a non-zero exit status will
cause the transaction to be aborted. The hook will not be called with
diff --git a/refs.c b/refs.c
index 42cb4126a7..9a510744a7 100644
--- a/refs.c
+++ b/refs.c
@@ -2365,18 +2365,19 @@ static int run_transaction_hook(struct ref_transaction *transaction,
for (i = 0; i < transaction->nr; i++) {
struct ref_update *update = transaction->updates[i];
+ strbuf_reset(&buf);
- /*
- * Skip reference transaction for symbolic refs.
- */
- if (update->new_target || update->old_target)
- continue;
+ if (update->flags & REF_HAVE_OLD && update->old_target)
+ strbuf_addf(&buf, "ref:%s ", update->old_target);
+ else
+ strbuf_addf(&buf, "%s ", oid_to_hex(&update->old_oid));
- strbuf_reset(&buf);
- strbuf_addf(&buf, "%s %s %s\n",
- oid_to_hex(&update->old_oid),
- oid_to_hex(&update->new_oid),
- update->refname);
+ if (update->flags & REF_HAVE_NEW && update->new_target)
+ strbuf_addf(&buf, "ref:%s ", update->new_target);
+ else
+ strbuf_addf(&buf, "%s ", oid_to_hex(&update->new_oid));
+
+ strbuf_addf(&buf, "%s\n", update->refname);
if (write_in_full(proc.in, buf.buf, buf.len) < 0) {
if (errno != EPIPE) {
diff --git a/t/t1416-ref-transaction-hooks.sh b/t/t1416-ref-transaction-hooks.sh
index 2092488090..abd4777819 100755
--- a/t/t1416-ref-transaction-hooks.sh
+++ b/t/t1416-ref-transaction-hooks.sh
@@ -134,4 +134,45 @@ test_expect_success 'interleaving hook calls succeed' '
test_cmp expect target-repo.git/actual
'
+# This test doesn't add a check for symref 'delete' since there is a
+# variation between the ref backends WRT 'delete'. In the files backend,
+# 'delete' also triggers an additional transaction update on the
+# packed-refs backend, which constitutes additional reflog entries.
+test_expect_success 'hook gets all queued symref updates' '
+ test_when_finished "rm actual" &&
+
+ git update-ref refs/heads/branch $POST_OID &&
+ git symbolic-ref refs/heads/symref refs/heads/main &&
+ git symbolic-ref refs/heads/symrefu refs/heads/main &&
+
+ test_hook reference-transaction <<-\EOF &&
+ echo "$*" >>actual
+ while read -r line
+ do
+ printf "%s\n" "$line"
+ done >>actual
+ EOF
+
+ cat >expect <<-EOF &&
+ prepared
+ ref:refs/heads/main $ZERO_OID refs/heads/symref
+ $ZERO_OID ref:refs/heads/main refs/heads/symrefc
+ ref:refs/heads/main ref:refs/heads/branch refs/heads/symrefu
+ committed
+ ref:refs/heads/main $ZERO_OID refs/heads/symref
+ $ZERO_OID ref:refs/heads/main refs/heads/symrefc
+ ref:refs/heads/main ref:refs/heads/branch refs/heads/symrefu
+ EOF
+
+ git update-ref --no-deref --stdin <<-EOF &&
+ start
+ verify refs/heads/symref ref:refs/heads/main
+ create refs/heads/symrefc ref:refs/heads/main
+ update refs/heads/symrefu ref:refs/heads/branch ref:refs/heads/main
+ prepare
+ commit
+ EOF
+ test_cmp expect actual
+'
+
test_done
--
2.43.GIT
^ permalink raw reply related [flat|nested] 194+ messages in thread
* Re: [PATCH v3 0/8] refs: add symref support to 'git-update-ref'
2024-04-23 21:28 ` [PATCH v3 0/8] refs: add symref support to 'git-update-ref' Karthik Nayak
` (7 preceding siblings ...)
2024-04-23 21:28 ` [PATCH v3 8/8] ref: support symrefs in 'reference-transaction' hook Karthik Nayak
@ 2024-04-23 22:03 ` Jeff King
2024-04-24 1:17 ` Junio C Hamano
2024-04-24 16:25 ` Karthik Nayak
2024-04-25 17:09 ` Junio C Hamano
2024-04-26 15:24 ` [PATCH v4 0/7] add symref-* commands to 'git-update-ref --stdin' Karthik Nayak
10 siblings, 2 replies; 194+ messages in thread
From: Jeff King @ 2024-04-23 22:03 UTC (permalink / raw)
To: Karthik Nayak; +Cc: chris.torek, git, gitster, ps
On Tue, Apr 23, 2024 at 11:28:10PM +0200, Karthik Nayak wrote:
> Changes from v2:
> 1. We no longer have separate commands for symrefs, instead the regular
> commands learn to parse 'ref:<target>' as symref targets. This reduces
> the code in this series. Thanks Patrick for the suggestion.
Hmm. I can see how this makes things a lot simpler, but it introduces an
ambiguity, since you can pass full ref expressions to "update-ref" (like
"ref:foo" to find the "foo" entry in ref^{tree}). I see that you only
kick in the symref "ref:" handling if the regular oid lookup failed, so
there's no backwards-compatibility issue (anything that used to work
will still take precedence, and the new code only runs when the old code
would have reported an error).
But I wonder if it would let somebody cause mischief in a repository
they can push to, but which may get updates from other sources. For
example, imagine a forge like GitLab runs the equivalent of:
echo "create refs/whatever ref:refs/heads/main" |
git update-ref --stdin
as part of some system process. Now if I push up "refs/heads/ref" that
contains the path "refs/heads/main" in its tree, that will take
precedence, causing the system process to do something it did not
expect.
I think you'd have to pile on a lot of assumptions to get any kind of
security problem. Something like:
1. The system has a hidden ref namespace like refs/gitlab that normal
remote push/fetch users are not allowed to read/write to.
2. The system tries to make a symlink within that namespace. Say,
"refs/gitlab/metadata/HEAD" to point to
"refs/gitlab/metadata/branches/main" or something.
3. The user pushes up "refs/heads/ref" with a tree that contains
"refs/gitlab/metadata/branches/main". Now when (2) happens, the
hidden ref points to user-controlled data.
That's pretty convoluted. But we can avoid it entirely if there's no
ambiguity in the protocol at all.
-Peff
^ permalink raw reply [flat|nested] 194+ messages in thread
* Re: [PATCH v3 0/8] refs: add symref support to 'git-update-ref'
2024-04-23 22:03 ` [PATCH v3 0/8] refs: add symref support to 'git-update-ref' Jeff King
@ 2024-04-24 1:17 ` Junio C Hamano
2024-04-24 16:25 ` Karthik Nayak
1 sibling, 0 replies; 194+ messages in thread
From: Junio C Hamano @ 2024-04-24 1:17 UTC (permalink / raw)
To: Jeff King; +Cc: Karthik Nayak, chris.torek, git, ps
Jeff King <peff@peff.net> writes:
> That's pretty convoluted. But we can avoid it entirely if there's no
> ambiguity in the protocol at all.
;-).
^ permalink raw reply [flat|nested] 194+ messages in thread
* Re: [PATCH v3 0/8] refs: add symref support to 'git-update-ref'
2024-04-23 22:03 ` [PATCH v3 0/8] refs: add symref support to 'git-update-ref' Jeff King
2024-04-24 1:17 ` Junio C Hamano
@ 2024-04-24 16:25 ` Karthik Nayak
2024-04-25 6:40 ` Patrick Steinhardt
` (2 more replies)
1 sibling, 3 replies; 194+ messages in thread
From: Karthik Nayak @ 2024-04-24 16:25 UTC (permalink / raw)
To: Jeff King; +Cc: chris.torek, git, gitster, ps
[-- Attachment #1: Type: text/plain, Size: 4177 bytes --]
Jeff King <peff@peff.net> writes:
> On Tue, Apr 23, 2024 at 11:28:10PM +0200, Karthik Nayak wrote:
>
>> Changes from v2:
>> 1. We no longer have separate commands for symrefs, instead the regular
>> commands learn to parse 'ref:<target>' as symref targets. This reduces
>> the code in this series. Thanks Patrick for the suggestion.
>
> Hmm. I can see how this makes things a lot simpler, but it introduces an
> ambiguity, since you can pass full ref expressions to "update-ref" (like
> "ref:foo" to find the "foo" entry in ref^{tree}). I see that you only
> kick in the symref "ref:" handling if the regular oid lookup failed, so
> there's no backwards-compatibility issue (anything that used to work
> will still take precedence, and the new code only runs when the old code
> would have reported an error).
>
> But I wonder if it would let somebody cause mischief in a repository
> they can push to, but which may get updates from other sources. For
> example, imagine a forge like GitLab runs the equivalent of:
>
> echo "create refs/whatever ref:refs/heads/main" |
> git update-ref --stdin
>
> as part of some system process. Now if I push up "refs/heads/ref" that
> contains the path "refs/heads/main" in its tree, that will take
> precedence, causing the system process to do something it did not
> expect.
>
> I think you'd have to pile on a lot of assumptions to get any kind of
> security problem. Something like:
>
> 1. The system has a hidden ref namespace like refs/gitlab that normal
> remote push/fetch users are not allowed to read/write to.
>
> 2. The system tries to make a symlink within that namespace. Say,
> "refs/gitlab/metadata/HEAD" to point to
> "refs/gitlab/metadata/branches/main" or something.
>
> 3. The user pushes up "refs/heads/ref" with a tree that contains
> "refs/gitlab/metadata/branches/main". Now when (2) happens, the
> hidden ref points to user-controlled data.
>
> That's pretty convoluted. But we can avoid it entirely if there's no
> ambiguity in the protocol at all.
>
> -Peff
Thanks Peff, that is indeed something I totally missed thinking about.
This also brings light onto the previous versions we were considering:
symref-update SP <ref> SP <new-target> [SP (<old-target> | <old-oid>)] LF
There is also some ambiguity here which we missed, especially when we
support dangling refs. If we're updating a dangling ref <ref>, and we
provide an old value. Then there is uncertainty around whether the
provided value is actually a <old-target> or if it's an <old-oid>.
For non dangling ref symref, we first parse it as an <old-target> and
since the <old-target> would exist, we can move on.
So I see two ways to go about this,
1. In the symref-update function, we need to parse and see if <ref> is a
regular ref or a symref, if it is symref, we simply set the provided old
value as <old-target>, if not, we set it as <old-oid>. This seems clunky
because we'll be parsing the ref and trying to understand its type in
'update-ref.c', before the actual update.
2. We change the syntax to something like
symref-update SP <ref> SP <new-ref> [SP (ref <old-target> | oid
<old-oid>)] LF
this would remove any ambiguity since the user specifies the data type
they're providing.
Overall, I think it is best to discard this version and I will push v4
with the older schematics of introducing new commands.
I'm currently considering going ahead with the [2], but will wait for a
day or two to consider other opinions.
Also on a sidenote, it's worth considering that with the direction of
[2], we could also extrapolate to introduce {verify, update, create,
delete} v2, which support both symrefs and regular refs. But require
explicit types from the user:
update-v2 SP <ref> NUL (oid <new-oid> | ref <new-target>) NUL
[(oid <old-oid> | ref <old-target>)] NUL
create-v2 SP <ref> NUL (oid <new-oid> | ref <new-target>) NUL
delete-v2 SP <ref> NUL [(oid <old-oid> | ref <old-target>)] NUL
verify-v2 SP <ref> NUL [(oid <old-oid> | ref <old-target>)] NUL
This is similar to the v3 patches I've currently sent out, in that it
would also allow cross operations between regular refs and symrefs.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 194+ messages in thread
* Re: [PATCH v3 0/8] refs: add symref support to 'git-update-ref'
2024-04-24 16:25 ` Karthik Nayak
@ 2024-04-25 6:40 ` Patrick Steinhardt
2024-04-25 21:12 ` Karthik Nayak
2024-04-25 18:01 ` Junio C Hamano
2024-04-26 20:41 ` Jeff King
2 siblings, 1 reply; 194+ messages in thread
From: Patrick Steinhardt @ 2024-04-25 6:40 UTC (permalink / raw)
To: Karthik Nayak; +Cc: Jeff King, chris.torek, git, gitster
[-- Attachment #1: Type: text/plain, Size: 1080 bytes --]
On Wed, Apr 24, 2024 at 09:25:27AM -0700, Karthik Nayak wrote:
> Jeff King <peff@peff.net> writes:
> > On Tue, Apr 23, 2024 at 11:28:10PM +0200, Karthik Nayak wrote:
[snip]
> Also on a sidenote, it's worth considering that with the direction of
> [2], we could also extrapolate to introduce {verify, update, create,
> delete} v2, which support both symrefs and regular refs. But require
> explicit types from the user:
>
> update-v2 SP <ref> NUL (oid <new-oid> | ref <new-target>) NUL
> [(oid <old-oid> | ref <old-target>)] NUL
> create-v2 SP <ref> NUL (oid <new-oid> | ref <new-target>) NUL
> delete-v2 SP <ref> NUL [(oid <old-oid> | ref <old-target>)] NUL
> verify-v2 SP <ref> NUL [(oid <old-oid> | ref <old-target>)] NUL
>
> This is similar to the v3 patches I've currently sent out, in that it
> would also allow cross operations between regular refs and symrefs.
One could put this new syntax behind a feature flag to avoid the "-v2"
suffixes, e.g. `git update-ref --with-symrefs`. But I'm not sure whether
this would be beneficial.
Patrick
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 194+ messages in thread
* Re: [PATCH v3 0/8] refs: add symref support to 'git-update-ref'
2024-04-25 6:40 ` Patrick Steinhardt
@ 2024-04-25 21:12 ` Karthik Nayak
0 siblings, 0 replies; 194+ messages in thread
From: Karthik Nayak @ 2024-04-25 21:12 UTC (permalink / raw)
To: Patrick Steinhardt; +Cc: Jeff King, chris.torek, git, gitster
[-- Attachment #1: Type: text/plain, Size: 1428 bytes --]
Patrick Steinhardt <ps@pks.im> writes:
> On Wed, Apr 24, 2024 at 09:25:27AM -0700, Karthik Nayak wrote:
>> Jeff King <peff@peff.net> writes:
>> > On Tue, Apr 23, 2024 at 11:28:10PM +0200, Karthik Nayak wrote:
> [snip]
>> Also on a sidenote, it's worth considering that with the direction of
>> [2], we could also extrapolate to introduce {verify, update, create,
>> delete} v2, which support both symrefs and regular refs. But require
>> explicit types from the user:
>>
>> update-v2 SP <ref> NUL (oid <new-oid> | ref <new-target>) NUL
>> [(oid <old-oid> | ref <old-target>)] NUL
>> create-v2 SP <ref> NUL (oid <new-oid> | ref <new-target>) NUL
>> delete-v2 SP <ref> NUL [(oid <old-oid> | ref <old-target>)] NUL
>> verify-v2 SP <ref> NUL [(oid <old-oid> | ref <old-target>)] NUL
>>
>> This is similar to the v3 patches I've currently sent out, in that it
>> would also allow cross operations between regular refs and symrefs.
>
> One could put this new syntax behind a feature flag to avoid the "-v2"
> suffixes, e.g. `git update-ref --with-symrefs`. But I'm not sure whether
> this would be beneficial.
>
Yeah, me neither. I mean it doesn't provide any new functionality. The
only usecase we're missing currently (with old + symref-* commands), is
that we have no way to convert a symref to a regular ref while checking
the old_value. This could however simply be solved by introducing a new
`symref-convert` command.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 194+ messages in thread
* Re: [PATCH v3 0/8] refs: add symref support to 'git-update-ref'
2024-04-24 16:25 ` Karthik Nayak
2024-04-25 6:40 ` Patrick Steinhardt
@ 2024-04-25 18:01 ` Junio C Hamano
2024-04-25 21:14 ` Karthik Nayak
2024-04-26 20:41 ` Jeff King
2 siblings, 1 reply; 194+ messages in thread
From: Junio C Hamano @ 2024-04-25 18:01 UTC (permalink / raw)
To: Karthik Nayak; +Cc: Jeff King, chris.torek, git, ps
Karthik Nayak <karthik.188@gmail.com> writes:
> 2. We change the syntax to something like
>
> symref-update SP <ref> SP <new-ref> [SP (ref <old-target> | oid
> <old-oid>)] LF
>
> this would remove any ambiguity since the user specifies the data type
> they're providing.
Yup. Being explicit helps, especially if you are only dealing with
programs that do not complain "that's too many keystrokes" like
pesky humans ;-).
When the topic's overall title is to add support for symbolic refs
to the update-ref command, a natural expectation is that in a far
enough future everything that can be done with "git symbolic-ref"
can be done with "git update-ref" and we can, if we wanted to,
depreate "git symbolic-ref". Is that really what is going on here?
IOW, we should add support for operation modes other than "--stdin"
as well, shouldn't we?
Thanks.
^ permalink raw reply [flat|nested] 194+ messages in thread
* Re: [PATCH v3 0/8] refs: add symref support to 'git-update-ref'
2024-04-25 18:01 ` Junio C Hamano
@ 2024-04-25 21:14 ` Karthik Nayak
2024-04-25 21:55 ` Junio C Hamano
0 siblings, 1 reply; 194+ messages in thread
From: Karthik Nayak @ 2024-04-25 21:14 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Jeff King, chris.torek, git, ps
[-- Attachment #1: Type: text/plain, Size: 1218 bytes --]
Junio C Hamano <gitster@pobox.com> writes:
> Karthik Nayak <karthik.188@gmail.com> writes:
>
>> 2. We change the syntax to something like
>>
>> symref-update SP <ref> SP <new-ref> [SP (ref <old-target> | oid
>> <old-oid>)] LF
>>
>> this would remove any ambiguity since the user specifies the data type
>> they're providing.
>
> Yup. Being explicit helps, especially if you are only dealing with
> programs that do not complain "that's too many keystrokes" like
> pesky humans ;-).
>
> When the topic's overall title is to add support for symbolic refs
> to the update-ref command, a natural expectation is that in a far
> enough future everything that can be done with "git symbolic-ref"
> can be done with "git update-ref" and we can, if we wanted to,
> depreate "git symbolic-ref". Is that really what is going on here?
>
> IOW, we should add support for operation modes other than "--stdin"
> as well, shouldn't we?
>
> Thanks.
That's a good point, I was thinking of something like this too, but
didn't want to bloat this series. Would it make sense to add this
functionality in a follow up series with the cleanup that Patrick
mentioned [1] too?
[1]: https://lore.kernel.org/git/ZidXrdi7hXdAnDhy@tanuki/
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 194+ messages in thread
* Re: [PATCH v3 0/8] refs: add symref support to 'git-update-ref'
2024-04-25 21:14 ` Karthik Nayak
@ 2024-04-25 21:55 ` Junio C Hamano
2024-04-26 12:48 ` Karthik Nayak
0 siblings, 1 reply; 194+ messages in thread
From: Junio C Hamano @ 2024-04-25 21:55 UTC (permalink / raw)
To: Karthik Nayak; +Cc: Jeff King, chris.torek, git, ps
Karthik Nayak <karthik.188@gmail.com> writes:
>> IOW, we should add support for operation modes other than "--stdin"
>> as well, shouldn't we?
>>
>> Thanks.
>
> That's a good point, I was thinking of something like this too, but
> didn't want to bloat this series. Would it make sense to add this
> functionality in a follow up series with the cleanup that Patrick
> mentioned [1] too?
Going multi-step is usually a preferred approach but we have to be
sure that we do not have to retract the way earlier steps gave our
users when we move to later steps. "Earlier we said that your
commands should look like this, but now you have to give your
commands differently" is absolutely what we want to avoid.
So, "in this iteration, you can use this enhanced feature only via
the "--stdin" mode, but we promise we will make the same available
on the command line" is perfectly fine. You may need to retitle the
topic to clarify the scope of each iteration, though.
Thansk.
^ permalink raw reply [flat|nested] 194+ messages in thread
* Re: [PATCH v3 0/8] refs: add symref support to 'git-update-ref'
2024-04-25 21:55 ` Junio C Hamano
@ 2024-04-26 12:48 ` Karthik Nayak
0 siblings, 0 replies; 194+ messages in thread
From: Karthik Nayak @ 2024-04-26 12:48 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Jeff King, chris.torek, git, ps
[-- Attachment #1: Type: text/plain, Size: 1214 bytes --]
Junio C Hamano <gitster@pobox.com> writes:
> Karthik Nayak <karthik.188@gmail.com> writes:
>
>>> IOW, we should add support for operation modes other than "--stdin"
>>> as well, shouldn't we?
>>>
>>> Thanks.
>>
>> That's a good point, I was thinking of something like this too, but
>> didn't want to bloat this series. Would it make sense to add this
>> functionality in a follow up series with the cleanup that Patrick
>> mentioned [1] too?
>
> Going multi-step is usually a preferred approach but we have to be
> sure that we do not have to retract the way earlier steps gave our
> users when we move to later steps. "Earlier we said that your
> commands should look like this, but now you have to give your
> commands differently" is absolutely what we want to avoid.
>
> So, "in this iteration, you can use this enhanced feature only via
> the "--stdin" mode, but we promise we will make the same available
> on the command line" is perfectly fine. You may need to retitle the
> topic to clarify the scope of each iteration, though.
>
> Thansk.
Yeah, will do that. Possibly also some of the commits too, where we
could mention that this is being added to the `--stdin` mode and can be
extended further on.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 194+ messages in thread
* Re: [PATCH v3 0/8] refs: add symref support to 'git-update-ref'
2024-04-24 16:25 ` Karthik Nayak
2024-04-25 6:40 ` Patrick Steinhardt
2024-04-25 18:01 ` Junio C Hamano
@ 2024-04-26 20:41 ` Jeff King
2 siblings, 0 replies; 194+ messages in thread
From: Jeff King @ 2024-04-26 20:41 UTC (permalink / raw)
To: Karthik Nayak; +Cc: chris.torek, git, gitster, ps
On Wed, Apr 24, 2024 at 09:25:27AM -0700, Karthik Nayak wrote:
> This also brings light onto the previous versions we were considering:
>
> symref-update SP <ref> SP <new-target> [SP (<old-target> | <old-oid>)] LF
>
> There is also some ambiguity here which we missed, especially when we
> support dangling refs. If we're updating a dangling ref <ref>, and we
> provide an old value. Then there is uncertainty around whether the
> provided value is actually a <old-target> or if it's an <old-oid>.
>
> For non dangling ref symref, we first parse it as an <old-target> and
> since the <old-target> would exist, we can move on.
>
> So I see two ways to go about this,
>
> 1. In the symref-update function, we need to parse and see if <ref> is a
> regular ref or a symref, if it is symref, we simply set the provided old
> value as <old-target>, if not, we set it as <old-oid>. This seems clunky
> because we'll be parsing the ref and trying to understand its type in
> 'update-ref.c', before the actual update.
I think this avoids the "mischief" case I mentioned because it is about
looking at what is in <ref> currently, not spooky action from a
possibly-unrelated "refs/heads/ref". But in general, I think it is a
good thing if we can tell what the caller is asking for based only on
the syntax of the request, without taking into account repository state.
It just makes things easier to reason about.
> 2. We change the syntax to something like
>
> symref-update SP <ref> SP <new-ref> [SP (ref <old-target> | oid
> <old-oid>)] LF
>
> this would remove any ambiguity since the user specifies the data type
> they're providing.
Yeah, I was going to suggest that it could be resolved with any syntax
that would not be a valid oid name. Certainly check-ref-format places
some restrictions there (e.g., no "^") but name resolution relies on
that, too (so foo^{tree} is a valid name). Probably something like
"^foo" is unambiguous, but it's ugly and hard to explain. ;)
But I think your "ref <old-target>" solves that. Resolved names can have
spaces in them, but only after a "^" (e.g., "foo^{/some regex}") or ":"
(e.g., "foo:path with spaces"). So seeing just "ref" by itself, followed
by a space, I think is unambiguous. And it looks pretty.
It gets a little tricky when the field delimiter is also space, and the
item in question is not the final field. See below.
> Also on a sidenote, it's worth considering that with the direction of
> [2], we could also extrapolate to introduce {verify, update, create,
> delete} v2, which support both symrefs and regular refs. But require
> explicit types from the user:
>
> update-v2 SP <ref> NUL (oid <new-oid> | ref <new-target>) NUL
> [(oid <old-oid> | ref <old-target>)] NUL
> create-v2 SP <ref> NUL (oid <new-oid> | ref <new-target>) NUL
> delete-v2 SP <ref> NUL [(oid <old-oid> | ref <old-target>)] NUL
> verify-v2 SP <ref> NUL [(oid <old-oid> | ref <old-target>)] NUL
>
> This is similar to the v3 patches I've currently sent out, in that it
> would also allow cross operations between regular refs and symrefs.
So I _think_ that "<oid> | ref <symref-target>" is unambiguous. In which
case you could just have:
update SP <ref> NUL (<new-oid> | ref <new-target>) NUL [(<old-oid> | ref <old-target>)]
which is backwards-compatible.
With the NUL separator it's easy to parse, because "ref " with a
trailing space always means a ref, even in the "new" field. But with
spaces instead, it gets weird. If you have:
update refs/heads/foo refs refs/heads/bar
it can either be creating a symref to "bar" (with no "old" specifier) or
it could be pointing it at the resolved-name "refs", which the old value
coming from "bar".
I guess one option would be to only allow "ref" syntax in "-z" mode, but
that is probably getting to be a bit weird and hard to explain.
-Peff
^ permalink raw reply [flat|nested] 194+ messages in thread
* Re: [PATCH v3 0/8] refs: add symref support to 'git-update-ref'
2024-04-23 21:28 ` [PATCH v3 0/8] refs: add symref support to 'git-update-ref' Karthik Nayak
` (8 preceding siblings ...)
2024-04-23 22:03 ` [PATCH v3 0/8] refs: add symref support to 'git-update-ref' Jeff King
@ 2024-04-25 17:09 ` Junio C Hamano
2024-04-25 21:07 ` Karthik Nayak
2024-04-26 15:24 ` [PATCH v4 0/7] add symref-* commands to 'git-update-ref --stdin' Karthik Nayak
10 siblings, 1 reply; 194+ messages in thread
From: Junio C Hamano @ 2024-04-25 17:09 UTC (permalink / raw)
To: Karthik Nayak; +Cc: chris.torek, git, ps
You'd want something like this squashed into an appropriate step to
avoid breaking "make sparse".
diff --git a/builtin/update-ref.c b/builtin/update-ref.c
index 175579148f..1cdafc33f3 100644
--- a/builtin/update-ref.c
+++ b/builtin/update-ref.c
@@ -228,7 +228,7 @@ static void parse_cmd_update(struct ref_transaction *transaction,
have_old = !parse_next_arg(&next, end, &old_oid,
&old_target, "update", refname,
PARSE_SHA1_OLD | PARSE_REFNAME_TARGETS);
- have_old = have_old & !old_target.len;
+ have_old = have_old && !old_target.len;
if (*next != line_termination)
die("update %s: extra input: %s", refname, next);
^ permalink raw reply related [flat|nested] 194+ messages in thread
* Re: [PATCH v3 0/8] refs: add symref support to 'git-update-ref'
2024-04-25 17:09 ` Junio C Hamano
@ 2024-04-25 21:07 ` Karthik Nayak
0 siblings, 0 replies; 194+ messages in thread
From: Karthik Nayak @ 2024-04-25 21:07 UTC (permalink / raw)
To: Junio C Hamano; +Cc: chris.torek, git, ps
[-- Attachment #1: Type: text/plain, Size: 756 bytes --]
Junio C Hamano <gitster@pobox.com> writes:
> You'd want something like this squashed into an appropriate step to
> avoid breaking "make sparse".
>
> diff --git a/builtin/update-ref.c b/builtin/update-ref.c
> index 175579148f..1cdafc33f3 100644
> --- a/builtin/update-ref.c
> +++ b/builtin/update-ref.c
> @@ -228,7 +228,7 @@ static void parse_cmd_update(struct ref_transaction *transaction,
> have_old = !parse_next_arg(&next, end, &old_oid,
> &old_target, "update", refname,
> PARSE_SHA1_OLD | PARSE_REFNAME_TARGETS);
> - have_old = have_old & !old_target.len;
> + have_old = have_old && !old_target.len;
>
> if (*next != line_termination)
> die("update %s: extra input: %s", refname, next);
Thanks, will check and squash this in.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 194+ messages in thread
* [PATCH v4 0/7] add symref-* commands to 'git-update-ref --stdin'
2024-04-23 21:28 ` [PATCH v3 0/8] refs: add symref support to 'git-update-ref' Karthik Nayak
` (9 preceding siblings ...)
2024-04-25 17:09 ` Junio C Hamano
@ 2024-04-26 15:24 ` Karthik Nayak
2024-04-26 15:24 ` [PATCH v4 1/7] refs: accept symref values in `ref_transaction[_add]_update` Karthik Nayak
` (8 more replies)
10 siblings, 9 replies; 194+ messages in thread
From: Karthik Nayak @ 2024-04-26 15:24 UTC (permalink / raw)
To: karthik.188; +Cc: christian.couder, git, gitster, ps
From: Karthik Nayak <karthik.188@gmail.com>
The 'git-update-ref(1)' command allows transactional reference updates.
But currently only supports regular reference updates. Meaning, if one
wants to update HEAD (symbolic ref) in a transaction, there is no tool
to do so.
One option to obtain transactional updates for the HEAD ref is to
manually create the HEAD.lock file and commit. This is intrusive, where
the user needs to mimic internal git behavior. Also, this only works
when using the files backend.
At GitLab, we've been using the manual process till date, to allow users
to set and change their default branch. But with the introduction of
reftables as a reference backend, this becomes a necessity to be solved
within git.
This patch series goes about introducing a set of commands
symref-{create,verify,delete,update} to work with symrefs complimenting
the existing commands for the regular refs in the '--stdin' mode of
'git-update-ref'.
The 'symref-verify' command can be used to verify if a symref exists and
its existing value.
The 'symref-create' command can be used to create a new symref.
The 'symref-delete' command can be used to delete an existing symref while
optionally checking its existing value.
The 'symref-update' command can be used to update a symref, create a symref,
delete a symref or even convert an existing regular ref to a symref. Wherein
like the regular 'update' command, the zero OID can be used to create/delete
a symref.
While this series adds the commands and the required ground work, it only
is accessile within the '--stdin' mode of 'git-update-ref'. However, it makes
it easy to extend it further to the command line too, which will be present
in a follow up series.
Previous versions:
V1: https://lore.kernel.org/git/20240330224623.579457-1-knayak@gitlab.com/
V2: https://lore.kernel.org/git/20240412095908.1134387-1-knayak@gitlab.com/
V3: https://lore.kernel.org/git/20240423212818.574123-1-knayak@gitlab.com/
V3 took a different approach of incorporating changes into the existing commands,
of 'git-update-ref --stdin' but we realized that there was some ambiguity in how
these commands are parsed [1]. In that sense it makes more sense to compare this
version with v2 instead.
Changes over v2 are:
- Rename (old|new)_ref to (old|new)_target, to avoid confusion.
- Better assertions around the input data.
- Removing the REF_SYMREF_UPDATE flag.
- Filled in missing/new documentation.
- For symref-update, realized that there was some ambiguity on how the
old_oid and old_target was parsed, and now the command requires the
user to explicitly input the data type.
- Support dangling refs in all operations.
- More test cases around empty values.
- Removed unecessary header includes.
- Fixed whitespace issues with the previous series.
- Other review comments.
[1]: https://lore.kernel.org/git/20240423220308.GC1172807@coredump.intra.peff.net/
Range diff (against v2):
1: 3269d0e91e ! 1: 4a56e3ede4 refs: accept symref values in `ref_transaction[_add]_update`
@@ Commit message
flags to create a `ref_update` and add it to the transaction at hand.
To extend symref support in transactions, we need to also accept the
- old and new ref values and process it. In this commit, let's add the
- required paramaters to the function and modify all call sites.
+ old and new ref targets and process it. In this commit, let's add the
+ required parameters to the function and modify all call sites.
- The two paramaters added are `new_ref` and `old_ref`. The `new_ref` is
- used to denote what the reference should point to when the transaction
- is applied. Some functions allow this parameter to be NULL, meaning that
- the reference is not changed, or `""`, meaning that the reference should
- be deleted.
+ The two parameters added are `new_target` and `old_target`. The
+ `new_target` is used to denote what the reference should point to when
+ the transaction is applied.
- The `old_ref` denotes the value of that the reference must have before
- the update. Some functions allow this parameter to be NULL, meaning that
- the old value of the reference is not checked, or `""`, meaning that the
- reference must not exist before the update. A copy of this value is made
- in the transaction.
+ The `old_target` denotes the value the reference must have before the
+ update. Some functions allow this parameter to be NULL, meaning that the
+ old value of the reference is not checked.
The handling logic of these parameters will be added in consequent
- commits as we implement symref-{create, update, delete, verify}.
+ commits as we add symref commands to the '--stdin' mode of
+ 'git-update-ref'.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
@@ refs.c: struct ref_update *ref_transaction_add_update(
const char *refname, unsigned int flags,
const struct object_id *new_oid,
const struct object_id *old_oid,
-+ const char *new_ref, const char *old_ref,
++ const char *new_target, const char *old_target,
const char *msg)
{
struct ref_update *update;
+@@ refs.c: struct ref_update *ref_transaction_add_update(
+ if (transaction->state != REF_TRANSACTION_OPEN)
+ BUG("update called for transaction that is not open");
+
++ if (old_oid && !is_null_oid(old_oid) && old_target)
++ BUG("Only one of old_oid and old_target should be non NULL");
++ if (new_oid && !is_null_oid(new_oid) && new_target)
++ BUG("Only one of new_oid and new_target should be non NULL");
++
+ FLEX_ALLOC_STR(update, refname, refname);
+ ALLOC_GROW(transaction->updates, transaction->nr + 1, transaction->alloc);
+ transaction->updates[transaction->nr++] = update;
@@ refs.c: int ref_transaction_update(struct ref_transaction *transaction,
const char *refname,
const struct object_id *new_oid,
const struct object_id *old_oid,
-+ const char *new_ref, const char *old_ref,
++ const char *new_target,
++ const char *old_target,
unsigned int flags, const char *msg,
struct strbuf *err)
{
@@ refs.c: int ref_transaction_update(struct ref_transaction *transaction,
ref_transaction_add_update(transaction, refname, flags,
- new_oid, old_oid, msg);
-+ new_oid, old_oid, new_ref, old_ref, msg);
++ new_oid, old_oid, new_target,
++ old_target, msg);
return 0;
}
@@ refs.c: int refs_update_ref(struct ref_store *refs, const char *msg,
## refs.h ##
@@ refs.h: struct ref_transaction *ref_transaction_begin(struct strbuf *err);
- */
- #define REF_SKIP_REFNAME_VERIFICATION (1 << 11)
-
-+/*
-+ * The reference update is considered to be done on a symbolic reference. This
-+ * ensures that we verify, delete, create and update the ref correspondingly.
-+ */
-+#define REF_SYMREF_UPDATE (1 << 12)
-+
- /*
- * Bitmask of all of the flags that are allowed to be passed in to
- * ref_transaction_update() and friends:
- */
- #define REF_TRANSACTION_UPDATE_ALLOWED_FLAGS \
- (REF_NO_DEREF | REF_FORCE_CREATE_REFLOG | REF_SKIP_OID_VERIFICATION | \
-- REF_SKIP_REFNAME_VERIFICATION)
-+ REF_SKIP_REFNAME_VERIFICATION | REF_SYMREF_UPDATE )
-
- /*
- * Add a reference update to transaction. `new_oid` is the value that
+ * before the update. A copy of this value is made in the
+ * transaction.
+ *
++ * new_target -- the target reference that the reference will be
++ * update to point to. This takes precedence over new_oid when
++ * set. If the reference is a regular reference, it will be
++ * converted to a symbolic reference.
++ *
++ * old_target -- the reference that the reference must be pointing to.
++ * Will only be taken into account when the reference is a symbolic
++ * reference.
++ *
+ * flags -- flags affecting the update, passed to
+ * update_ref_lock(). Possible flags: REF_NO_DEREF,
+ * REF_FORCE_CREATE_REFLOG. See those constants for more
+@@ refs.h: struct ref_transaction *ref_transaction_begin(struct strbuf *err);
+ * beforehand. The old value is checked after the lock is taken to
+ * prevent races. If the old value doesn't agree with old_oid, the
+ * whole transaction fails. If old_oid is NULL, then the previous
+- * value is not checked.
++ * value is not checked. If `old_target` is not NULL, treat the reference
++ * as a symbolic ref and validate that its target before the update is
++ * `old_target`. If the `new_target` is not NULL, then the reference
++ * will be updated to a symbolic ref which targets `new_target`.
++ * Together, these allow us to update between regular refs and symrefs.
+ *
+ * See the above comment "Reference transaction updates" for more
+ * information.
@@ refs.h: int ref_transaction_update(struct ref_transaction *transaction,
const char *refname,
const struct object_id *new_oid,
const struct object_id *old_oid,
-+ const char *new_ref, const char *old_ref,
++ const char *new_target,
++ const char *old_target,
unsigned int flags, const char *msg,
struct strbuf *err);
@@ refs/refs-internal.h: struct ref_update {
struct object_id old_oid;
+ /*
-+ * If (flags & REF_SYMREF_UPDATE), set the reference to this
-+ * value (or delete it, if `new_ref` is an empty string).
++ * If set, point the reference to this value. This can also be
++ * used to convert regular references to become symbolic refs.
+ */
-+ const char *new_ref;
++ const char *new_target;
+
+ /*
-+ * If (type & REF_SYMREF_UPDATE), check that the reference
-+ * previously had this value (or didn't previously exist,
-+ * if `old_ref` is an empty string).
++ * If set and the reference is a symbolic ref, check that the
++ * reference previously pointed to this value.
+ */
-+ const char *old_ref;
++ const char *old_target;
+
/*
* One or more of REF_NO_DEREF, REF_FORCE_CREATE_REFLOG,
@@ refs/refs-internal.h: struct ref_update *ref_transaction_add_update(
const char *refname, unsigned int flags,
const struct object_id *new_oid,
const struct object_id *old_oid,
-+ const char *new_ref, const char *old_ref,
++ const char *new_target, const char *old_target,
const char *msg);
/*
4: 53fdb408ef = 2: 496bf14f28 files-backend: extract out `create_symref_lock`
2: a8cb0e0a1d ! 3: 6337859cbb update-ref: add support for symref-verify
@@ Metadata
Author: Karthik Nayak <karthik.188@gmail.com>
## Commit message ##
- update-ref: add support for symref-verify
+ update-ref: add support for 'symref-verify' command
- In the previous commit, we added the required base for adding symref
- support in transactions provided by the 'git-update-ref(1)'. This commit
- introduces the 'symref-verify' command which is similar to the existing
- 'verify' command for regular refs.
+ In the previous commits, we added the required base for adding symref
+ commands to the '--stdin' mode provided by 'git-update-ref(1)'. Using
+ them, add a new 'symref-verify' command to verify symrefs.
The 'symref-verify' command allows users to verify if a provided <ref>
- contains the provided <old-ref> without changing the <ref>. If <old-ref>
- is not provided, the command will verify that the <ref> doesn't exist.
- Since we're checking for symbolic refs, this command will only work with
- the 'no-deref' mode. This is because any dereferenced symbolic ref will
- point to an object and not a ref and the regular 'verify' command can be
- used in such situations.
+ contains the provided <old-target> without changing the <ref>. If
+ <old-target> is not provided, the command will verify that the <ref>
+ doesn't exist. Since we're checking for symbolic refs, this command will
+ only work with the 'no-deref' mode. This is because any dereferenced
+ symbolic ref will point to an object and not a ref and the regular
+ 'verify' command can be used in such situations.
- This commit adds all required helper functions required to also
- introduce the other symref commands, namely create, delete, and update.
+ Add and use `ref_update_is_null_new_value`, a helper function which is
+ used to check if there is a new_value in a reference update. The new
+ value could either be a symref target `new_target` or a OID `new_oid`.
We also add tests to test the command in both the regular stdin mode and
also with the '-z' flag.
- When the user doesn't provide a <old-ref> we need to check that the
- provided <ref> doesn't exist. And to do this, we take over the existing
- understanding that <old-oid> when set to its zero value, it refers to
- the ref not existing. While this seems like a mix of contexts between
- using <*-ref> and <*-oid>, this actually works really well, especially
- considering the fact that we want to eventually also introduce
-
- symref-update SP <ref> SP <new-ref> [SP (<old-oid> | <old-rev>)] LF
-
- and here, we'd allow the user to update a regular <ref> to a symref and
- use <old-oid> to check the <ref>'s oid. This can be extrapolated to the
- user using this to create a symref when provided a zero <old-oid>. Which
- will work given how we're setting it up.
-
We also disable the reference-transaction hook for symref-updates which
will be tackled in its own commit.
- Add required tests for 'symref-verify' while also adding reflog checks for
- the pre-existing 'verify' tests.
+ Add required tests for symref support in 'verify' while also adding
+ reflog checks for the pre-existing 'verify' tests.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
@@ Documentation/git-update-ref.txt: performs all modifications together. Specify
create SP <ref> SP <new-oid> LF
delete SP <ref> [SP <old-oid>] LF
verify SP <ref> [SP <old-oid>] LF
-+ symref-verify SP <ref> [SP <old-ref>] LF
++ symref-verify SP <ref> [SP <old-target>] LF
option SP <opt> LF
start LF
prepare LF
@@ Documentation/git-update-ref.txt: quoting:
create SP <ref> NUL <new-oid> NUL
delete SP <ref> NUL [<old-oid>] NUL
verify SP <ref> NUL [<old-oid>] NUL
-+ symref-verify SP <ref> [NUL <old-ref>] NUL
++ symref-verify SP <ref> [NUL <old-target>] NUL
option SP <opt> NUL
start NUL
prepare NUL
@@ Documentation/git-update-ref.txt: verify::
<old-oid> is zero or missing, the ref must not exist.
+symref-verify::
-+ Verify symbolic <ref> against <old-ref> but do not change it.
-+ If <old-ref> is missing, the ref must not exist. Can only be
++ Verify symbolic <ref> against <old-target> but do not change it.
++ If <old-target> is missing, the ref must not exist. Can only be
+ used in `no-deref` mode.
+
option::
@@ builtin/update-ref.c: static char *parse_refname(const char **next)
return strbuf_detach(&ref, NULL);
}
-+
-+
+/*
+ * Wrapper around parse_refname which skips the next delimiter.
+ */
+static char *parse_next_refname(const char **next)
+{
-+ if (line_termination) {
-+ /* Without -z, consume SP and use next argument */
-+ if (!**next || **next == line_termination)
-+ return NULL;
-+ if (**next != ' ')
-+ die("expected SP but got: %s", *next);
-+ } else {
-+ /* With -z, read the next NUL-terminated line */
-+ if (**next)
-+ return NULL;
-+ }
-+ /* Skip the delimiter */
-+ (*next)++;
++ if (line_termination) {
++ /* Without -z, consume SP and use next argument */
++ if (!**next || **next == line_termination)
++ return NULL;
++ if (**next != ' ')
++ die("expected SP but got: %s", *next);
++ } else {
++ /* With -z, read the next NUL-terminated line */
++ if (**next)
++ return NULL;
++ }
++ /* Skip the delimiter */
++ (*next)++;
+
-+ return parse_refname(next);
++ return parse_refname(next);
+}
++
+
/*
* The value being parsed is <old-oid> (as opposed to <new-oid>; the
@@ builtin/update-ref.c: static void parse_cmd_verify(struct ref_transaction *trans
+}
+
+static void parse_cmd_symref_verify(struct ref_transaction *transaction,
-+ const char *next, const char *end)
++ const char *next, const char *end)
+{
+ struct strbuf err = STRBUF_INIT;
+ struct object_id old_oid;
-+ char *refname, *old_ref;
++ char *refname, *old_target;
+
+ if (!(update_flags & REF_NO_DEREF))
+ die("symref-verify: cannot operate with deref mode");
@@ builtin/update-ref.c: static void parse_cmd_verify(struct ref_transaction *trans
+ * old_ref is optional, but we want to differentiate between
+ * a NULL and zero value.
+ */
-+ old_ref = parse_next_refname(&next);
-+ if (!old_ref)
++ old_target = parse_next_refname(&next);
++ if (!old_target)
+ old_oid = *null_oid();
-+ else if (read_ref(old_ref, NULL))
-+ die("symref-verify %s: invalid <old-ref>", refname);
+
+ if (*next != line_termination)
+ die("symref-verify %s: extra input: %s", refname, next);
+
-+ if (ref_transaction_verify(transaction, refname, old_ref ? NULL : &old_oid,
-+ old_ref, update_flags | REF_SYMREF_UPDATE, &err))
++ if (ref_transaction_verify(transaction, refname,
++ old_target ? NULL : &old_oid,
++ old_target, update_flags, &err))
die("%s", err.buf);
update_flags = default_flags;
free(refname);
-+ free(old_ref);
++ free(old_target);
strbuf_release(&err);
}
@@ builtin/update-ref.c: static const struct parse_cmd {
static void update_refs_stdin(void)
## refs.c ##
-@@
- #include "object-store-ll.h"
- #include "object.h"
- #include "path.h"
-+#include "string.h"
- #include "tag.h"
- #include "submodule.h"
- #include "worktree.h"
-@@
- #include "date.h"
- #include "commit.h"
- #include "wildmatch.h"
-+#include "wrapper.h"
-
- /*
- * List of all available backends
@@ refs.c: void ref_transaction_free(struct ref_transaction *transaction)
for (i = 0; i < transaction->nr; i++) {
free(transaction->updates[i]->msg);
-+ free((void *)transaction->updates[i]->old_ref);
++ free((void *)transaction->updates[i]->old_target);
++ free((void *)transaction->updates[i]->new_target);
free(transaction->updates[i]);
}
free(transaction->updates);
@@ refs.c: struct ref_update *ref_transaction_add_update(
update->flags = flags;
- if (flags & REF_HAVE_NEW)
-- oidcpy(&update->new_oid, new_oid);
++ if (new_target)
++ update->new_target = xstrdup(new_target);
++ if (old_target)
++ update->old_target = xstrdup(old_target);
++ if (new_oid && flags & REF_HAVE_NEW)
+ oidcpy(&update->new_oid, new_oid);
- if (flags & REF_HAVE_OLD)
-- oidcpy(&update->old_oid, old_oid);
-+ /*
-+ * The ref values are to be considered over the oid values when we're
-+ * doing symref operations.
-+ */
-+ if (update->flags & REF_SYMREF_UPDATE) {
-+ if (old_ref)
-+ update->old_ref = xstrdup(old_ref);
-+ } else {
-+ if (flags & REF_HAVE_NEW)
-+ oidcpy(&update->new_oid, new_oid);
-+ if (flags & REF_HAVE_OLD)
-+ oidcpy(&update->old_oid, old_oid);
-+ }
++ if (old_oid && flags & REF_HAVE_OLD)
+ oidcpy(&update->old_oid, old_oid);
update->msg = normalize_reflog_message(msg);
return update;
- }
@@ refs.c: int ref_transaction_update(struct ref_transaction *transaction,
flags &= REF_TRANSACTION_UPDATE_ALLOWED_FLAGS;
flags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0);
-+ flags |= (new_ref ? REF_HAVE_NEW : 0) | (old_ref ? REF_HAVE_OLD : 0);
++ flags |= (new_target ? REF_HAVE_NEW : 0) | (old_target ? REF_HAVE_OLD : 0);
ref_transaction_add_update(transaction, refname, flags,
- new_oid, old_oid, new_ref, old_ref, msg);
+ new_oid, old_oid, new_target,
@@ refs.c: int ref_transaction_delete(struct ref_transaction *transaction,
int ref_transaction_verify(struct ref_transaction *transaction,
const char *refname,
const struct object_id *old_oid,
-+ const char *old_ref,
++ const char *old_target,
unsigned int flags,
struct strbuf *err)
{
- if (!old_oid)
-+ if (flags & REF_SYMREF_UPDATE && !old_ref && !old_oid)
-+ BUG("verify called with old_ref set to NULL");
-+ if (!(flags & REF_SYMREF_UPDATE) && !old_oid)
- BUG("verify called with old_oid set to NULL");
+- BUG("verify called with old_oid set to NULL");
++ if (!old_target && !old_oid)
++ BUG("verify called with old_oid and old_target set to NULL");
++ if (old_target && !(flags & REF_NO_DEREF))
++ BUG("verify cannot operate on symrefs with deref mode");
return ref_transaction_update(transaction, refname,
NULL, old_oid,
- NULL, NULL,
-+ NULL, old_ref,
++ NULL, old_target,
flags, NULL, err);
}
@@ refs.c: static int run_transaction_hook(struct ref_transaction *transaction,
for (i = 0; i < transaction->nr; i++) {
struct ref_update *update = transaction->updates[i];
-+ if (update->flags & REF_SYMREF_UPDATE)
++ /*
++ * Skip reference transaction for symbolic refs.
++ */
++ if (update->new_target || update->old_target)
+ continue;
+
strbuf_reset(&buf);
@@ refs.c: int copy_existing_ref(const char *oldref, const char *newref, const char
return refs_copy_existing_ref(get_main_ref_store(the_repository), oldref, newref, logmsg);
}
+
-+int null_new_value(struct ref_update *update) {
-+ if (update->flags & REF_SYMREF_UPDATE && update->new_ref)
-+ return 0;
-+ return is_null_oid(&update->new_oid);
++int ref_update_is_null_new_value(struct ref_update *update) {
++ return !update->new_target && is_null_oid(&update->new_oid);
+}
## refs.h ##
@@ refs.h: int ref_transaction_delete(struct ref_transaction *transaction,
int ref_transaction_verify(struct ref_transaction *transaction,
const char *refname,
const struct object_id *old_oid,
-+ const char *old_ref,
++ const char *old_target,
unsigned int flags,
struct strbuf *err);
@@ refs/files-backend.c: static const char *original_update_refname(struct ref_upda
}
+/*
-+ * Check whether the REF_HAVE_OLD and old_ref values stored in update
-+ * are consistent with ref, which is the symbolic reference's current
-+ * value. If everything is OK, return 0; otherwise, write an error
-+ * message to err and return -1.
++ * Check whether the REF_HAVE_OLD and old_target values stored in
++ * update are consistent with ref, which is the symbolic reference's
++ * current value. If everything is OK, return 0; otherwise, write an
++ * error message to err and return -1.
+ */
-+static int check_old_ref(struct ref_update *update, char *ref,
-+ struct strbuf *err)
++static int check_old_target(struct ref_update *update, char *ref,
++ struct strbuf *err)
+{
+ if (!(update->flags & REF_HAVE_OLD) ||
-+ !strcmp(update->old_ref, ref))
++ !strcmp(update->old_target, ref))
+ return 0;
+
-+ if (!strcmp(update->old_ref, ""))
++ if (!strcmp(update->old_target, ""))
+ strbuf_addf(err, "cannot lock ref '%s': "
+ "reference already exists",
+ original_update_refname(update));
@@ refs/files-backend.c: static const char *original_update_refname(struct ref_upda
+ strbuf_addf(err, "cannot lock ref '%s': "
+ "reference is missing but expected %s",
+ original_update_refname(update),
-+ update->old_ref);
++ update->old_target);
+ else
+ strbuf_addf(err, "cannot lock ref '%s': "
+ "is at %s but expected %s",
+ original_update_refname(update),
-+ ref, update->old_ref);
++ ref, update->old_target);
+
+ return -1;
+}
@@ refs/files-backend.c: static const char *original_update_refname(struct ref_upda
/*
* Check whether the REF_HAVE_OLD and old_oid values stored in update
* are consistent with oid, which is the reference's current value. If
-@@ refs/files-backend.c: static int lock_ref_for_update(struct files_ref_store *refs,
- struct strbuf *err)
- {
- struct strbuf referent = STRBUF_INIT;
-- int mustexist = (update->flags & REF_HAVE_OLD) &&
-- !is_null_oid(&update->old_oid);
-+ int mustexist = (update->flags & REF_HAVE_OLD) && !is_null_oid(&update->old_oid);
- int ret = 0;
- struct ref_lock *lock;
-
@@ refs/files-backend.c: static int lock_ref_for_update(struct files_ref_store *refs,
ret = TRANSACTION_GENERIC_ERROR;
goto out;
@@ refs/files-backend.c: static int lock_ref_for_update(struct files_ref_store *ref
+ }
+
+ /*
-+ * For symref verification, we need to check the referent value
++ * For symref verification, we need to check the reference value
+ * rather than the oid. If we're dealing with regular refs or we're
+ * verifying a dereferenced symref, we then check the oid.
+ */
-+ if (update->flags & REF_SYMREF_UPDATE && update->old_ref) {
-+ if (check_old_ref(update, referent.buf, err)) {
++ if (update->old_target) {
++ if (check_old_target(update, referent.buf, err)) {
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto out;
+ }
@@ refs/refs-internal.h: void base_ref_store_init(struct ref_store *refs, struct re
+ * takes into consideration that the update could be a regular
+ * ref or a symbolic ref.
+ */
-+int null_new_value(struct ref_update *update);
++int ref_update_is_null_new_value(struct ref_update *update);
+
#endif /* REFS_REFS_INTERNAL_H */
@@ refs/reftable-backend.c: static int reftable_be_transaction_prepare(struct ref_s
* backend returns, which keeps our tests happy.
*/
- if (u->flags & REF_HAVE_OLD && !oideq(¤t_oid, &u->old_oid)) {
-+ if ((u->flags & REF_HAVE_OLD) &&
-+ (u->flags & REF_SYMREF_UPDATE) &&
-+ u->old_ref) {
-+ if (strcmp(referent.buf, u->old_ref)) {
-+ if (!strcmp(u->old_ref, ""))
-+ strbuf_addf(err, "cannot lock ref '%s': "
-+ "reference already exists",
++ if ((u->flags & REF_HAVE_OLD) && u->old_target) {
++ if (strcmp(referent.buf, u->old_target)) {
++ if (!strcmp(u->old_target, ""))
++ strbuf_addf(err, "verifying symref target: '%s': "
++ "provided target is empty",
+ original_update_refname(u));
+ else if (!strcmp(referent.buf, ""))
-+ strbuf_addf(err, "cannot lock ref '%s': "
++ strbuf_addf(err, "verifying symref target: '%s': "
+ "reference is missing but expected %s",
+ original_update_refname(u),
-+ u->old_ref);
++ u->old_target);
+ else
-+ strbuf_addf(err, "cannot lock ref '%s': "
++ strbuf_addf(err, "verifying symref target: '%s': "
+ "is at %s but expected %s",
+ original_update_refname(u),
-+ referent.buf, u->old_ref);
++ referent.buf, u->old_target);
+ ret = -1;
+ goto done;
+ }
@@ t/t1400-update-ref.sh: test_expect_success PIPE 'transaction flushes status upda
test_cmp expected actual
'
-+create_stdin_buf ()
-+{
++create_stdin_buf () {
+ if test "$1" = "-z"
+ then
+ shift
@@ t/t1400-update-ref.sh: test_expect_success PIPE 'transaction flushes status upda
+for type in "" "-z"
+do
+
-+test_expect_success "stdin ${type} symref-verify fails without --no-deref" '
-+ git symbolic-ref refs/heads/symref $a &&
-+ create_stdin_buf ${type} "symref-verify refs/heads/symref" "$a" &&
-+ test_must_fail git update-ref --stdin ${type} <stdin 2>err &&
-+ grep "fatal: symref-verify: cannot operate with deref mode" err
-+'
++ test_expect_success "stdin ${type} symref-verify fails without --no-deref" '
++ git symbolic-ref refs/heads/symref $a &&
++ create_stdin_buf ${type} "symref-verify refs/heads/symref" "$a" &&
++ test_must_fail git update-ref --stdin ${type} <stdin 2>err &&
++ grep "fatal: symref-verify: cannot operate with deref mode" err
++ '
+
-+test_expect_success "stdin ${type} symref-verify fails with too many arguments" '
-+ create_stdin_buf ${type} "symref-verify refs/heads/symref" "$a" "$a" &&
-+ test_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&
-+ if test "$type" = "-z"
-+ then
-+ grep "fatal: unknown command: $a" err
-+ else
-+ grep "fatal: symref-verify refs/heads/symref: extra input: $a" err
-+ fi
-+'
++ test_expect_success "stdin ${type} symref-verify fails with too many arguments" '
++ create_stdin_buf ${type} "symref-verify refs/heads/symref" "$a" "$a" &&
++ test_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&
++ if test "$type" = "-z"
++ then
++ grep "fatal: unknown command: $a" err
++ else
++ grep "fatal: symref-verify refs/heads/symref: extra input: $a" err
++ fi
++ '
+
-+test_expect_success "stdin ${type} symref-verify succeeds for correct value" '
-+ git symbolic-ref refs/heads/symref >expect &&
-+ test-tool ref-store main for-each-reflog-ent refs/heads/symref >before &&
-+ create_stdin_buf ${type} "symref-verify refs/heads/symref" "$a" &&
-+ git update-ref --stdin ${type} --no-deref <stdin &&
-+ git symbolic-ref refs/heads/symref >actual &&
-+ test_cmp expect actual &&
-+ test-tool ref-store main for-each-reflog-ent refs/heads/symref >after &&
-+ test_cmp before after
-+'
++ test_expect_success "stdin ${type} symref-verify succeeds for correct value" '
++ git symbolic-ref refs/heads/symref >expect &&
++ test-tool ref-store main for-each-reflog-ent refs/heads/symref >before &&
++ create_stdin_buf ${type} "symref-verify refs/heads/symref" "$a" &&
++ git update-ref --stdin ${type} --no-deref <stdin &&
++ git symbolic-ref refs/heads/symref >actual &&
++ test_cmp expect actual &&
++ test-tool ref-store main for-each-reflog-ent refs/heads/symref >after &&
++ test_cmp before after
++ '
+
-+test_expect_success "stdin ${type} symref-verify succeeds for missing reference" '
-+ test-tool ref-store main for-each-reflog-ent refs/heads/symref >before &&
-+ create_stdin_buf ${type} "symref-verify refs/heads/missing" &&
-+ git update-ref --stdin ${type} --no-deref <stdin &&
-+ test_must_fail git rev-parse --verify -q refs/heads/missing &&
-+ test-tool ref-store main for-each-reflog-ent refs/heads/symref >after &&
-+ test_cmp before after
-+'
++ test_expect_success "stdin ${type} symref-verify no value is treated as zero value" '
++ git symbolic-ref refs/heads/symref >expect &&
++ create_stdin_buf ${type} "symref-verify refs/heads/symref" "" &&
++ test_must_fail git update-ref --stdin ${type} --no-deref <stdin
++ '
++
++ test_expect_success "stdin ${type} symref-verify succeeds for dangling reference" '
++ test_when_finished "git symbolic-ref -d refs/heads/symref2" &&
++ test_must_fail git symbolic-ref refs/heads/nonexistent &&
++ git symbolic-ref refs/heads/symref2 refs/heads/nonexistent &&
++ create_stdin_buf ${type} "symref-verify refs/heads/symref2" "refs/heads/nonexistent" &&
++ git update-ref --stdin ${type} --no-deref <stdin
++ '
++
++ test_expect_success "stdin ${type} symref-verify succeeds for missing reference" '
++ test-tool ref-store main for-each-reflog-ent refs/heads/symref >before &&
++ create_stdin_buf ${type} "symref-verify refs/heads/missing" "$Z" &&
++ git update-ref --stdin ${type} --no-deref <stdin &&
++ test_must_fail git rev-parse --verify -q refs/heads/missing &&
++ test-tool ref-store main for-each-reflog-ent refs/heads/symref >after &&
++ test_cmp before after
++ '
+
-+test_expect_success "stdin ${type} symref-verify fails for wrong value" '
-+ git symbolic-ref refs/heads/symref >expect &&
-+ create_stdin_buf ${type} "symref-verify refs/heads/symref" "$b" &&
-+ test_must_fail git update-ref --stdin ${type} --no-deref <stdin &&
-+ git symbolic-ref refs/heads/symref >actual &&
-+ test_cmp expect actual
-+'
++ test_expect_success "stdin ${type} symref-verify fails for wrong value" '
++ git symbolic-ref refs/heads/symref >expect &&
++ create_stdin_buf ${type} "symref-verify refs/heads/symref" "$b" &&
++ test_must_fail git update-ref --stdin ${type} --no-deref <stdin &&
++ git symbolic-ref refs/heads/symref >actual &&
++ test_cmp expect actual
++ '
+
-+test_expect_success "stdin ${type} symref-verify fails for mistaken null value" '
-+ git symbolic-ref refs/heads/symref >expect &&
-+ create_stdin_buf ${type} "symref-verify refs/heads/symref" &&
-+ test_must_fail git update-ref --stdin ${type} --no-deref <stdin &&
-+ git symbolic-ref refs/heads/symref >actual &&
-+ test_cmp expect actual
-+'
++ test_expect_success "stdin ${type} symref-verify fails for mistaken null value" '
++ git symbolic-ref refs/heads/symref >expect &&
++ create_stdin_buf ${type} "symref-verify refs/heads/symref" "$Z" &&
++ test_must_fail git update-ref --stdin ${type} --no-deref <stdin &&
++ git symbolic-ref refs/heads/symref >actual &&
++ test_cmp expect actual
++ '
+
+done
+
3: 37c3e006da ! 4: e611cb5a8c update-ref: add support for symref-delete
@@ Metadata
Author: Karthik Nayak <karthik.188@gmail.com>
## Commit message ##
- update-ref: add support for symref-delete
+ update-ref: add support for 'symref-delete' command
- Similar to the previous commit, add 'symref-delete' to allow deletions
- of symbolic refs in a transaction via the 'git-update-ref' command. The
- 'symref-delete' command can when given with an <old-ref>, deletes the
- provided <ref> only when it points to <old-ref>.
+ Add a new command 'symref-delete' to allow deletions of symbolic refs in
+ a transaction via the '--stdin' mode of the 'git-update-ref' command.
+ The 'symref-delete' command can, when given an <old-target>, delete the
+ provided <ref> only when it points to <old-target>. This will only work
+ when used with the 'no-deref' mode as it doesn't make sense to deref a
+ symref during deletion.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
@@ Documentation/git-update-ref.txt: performs all modifications together. Specify
create SP <ref> SP <new-oid> LF
delete SP <ref> [SP <old-oid>] LF
verify SP <ref> [SP <old-oid>] LF
-+ symref-delete SP <ref> [SP <old-ref>] LF
- symref-verify SP <ref> [SP <old-ref>] LF
++ symref-delete SP <ref> [SP <old-target>] LF
+ symref-verify SP <ref> [SP <old-target>] LF
option SP <opt> LF
start LF
@@ Documentation/git-update-ref.txt: quoting:
create SP <ref> NUL <new-oid> NUL
delete SP <ref> NUL [<old-oid>] NUL
verify SP <ref> NUL [<old-oid>] NUL
-+ symref-delete SP <ref> [NUL <old-ref>] NUL
- symref-verify SP <ref> [NUL <old-ref>] NUL
++ symref-delete SP <ref> [NUL <old-target>] NUL
+ symref-verify SP <ref> [NUL <old-target>] NUL
option SP <opt> NUL
start NUL
-@@ Documentation/git-update-ref.txt: verify::
+@@ Documentation/git-update-ref.txt: create::
+ exist. The given <new-oid> may not be zero.
+
+ delete::
+- Delete <ref> after verifying it exists with <old-oid>, if
+- given. If given, <old-oid> may not be zero.
++ Delete <ref> after verifying it exists with <old-oid>, if given.
++ If given, <old-oid> may not be zero. If instead, ref:<old-target>
++ is provided, verify that the symbolic ref <ref> targets
++ <old-target> before deleting it.
+
+ verify::
Verify <ref> against <old-oid> but do not change it. If
<old-oid> is zero or missing, the ref must not exist.
+symref-delete::
-+ Delete <ref> after verifying it exists with <old-ref>, if
-+ given.
++ Delete <ref> after verifying it exists with <old-target>, if given.
+
symref-verify::
- Verify symbolic <ref> against <old-ref> but do not change it.
- If <old-ref> is missing, the ref must not exist. Can only be
+ Verify symbolic <ref> against <old-target> but do not change it.
+ If <old-target> is missing, the ref must not exist. Can only be
## builtin/fetch.c ##
@@ builtin/fetch.c: static int prune_refs(struct display_state *display_state,
@@ builtin/update-ref.c: static void parse_cmd_delete(struct ref_transaction *trans
strbuf_release(&err);
}
++
+static void parse_cmd_symref_delete(struct ref_transaction *transaction,
+ const char *next, const char *end)
+{
+ struct strbuf err = STRBUF_INIT;
-+ char *refname, *old_ref;
++ char *refname, *old_target;
+
+ if (!(update_flags & REF_NO_DEREF))
-+ die("symref-delete: cannot operate with deref mode");
++ die("symref-delete: cannot operate with deref mode");
+
+ refname = parse_refname(&next);
+ if (!refname)
+ die("symref-delete: missing <ref>");
+
-+ old_ref = parse_next_refname(&next);
-+ if (old_ref && read_ref(old_ref, NULL))
-+ die("symref-delete %s: invalid <old-ref>", refname);
++ old_target = parse_next_refname(&next);
+
+ if (*next != line_termination)
+ die("symref-delete %s: extra input: %s", refname, next);
+
+ if (ref_transaction_delete(transaction, refname, NULL,
-+ update_flags | REF_SYMREF_UPDATE,
-+ old_ref, msg, &err))
++ update_flags, old_target, msg, &err))
+ die("%s", err.buf);
+
+ update_flags = default_flags;
+ free(refname);
-+ free(old_ref);
++ free(old_target);
+ strbuf_release(&err);
+}
++
+
static void parse_cmd_verify(struct ref_transaction *transaction,
const char *next, const char *end)
@@ refs.c: int refs_delete_ref(struct ref_store *refs, const char *msg,
ref_transaction_commit(transaction, &err)) {
error("%s", err.buf);
ref_transaction_free(transaction);
-@@ refs.c: void ref_transaction_free(struct ref_transaction *transaction)
- for (i = 0; i < transaction->nr; i++) {
- free(transaction->updates[i]->msg);
- free((void *)transaction->updates[i]->old_ref);
-+ free((void *)transaction->updates[i]->new_ref);
- free(transaction->updates[i]);
- }
- free(transaction->updates);
-@@ refs.c: struct ref_update *ref_transaction_add_update(
- if (update->flags & REF_SYMREF_UPDATE) {
- if (old_ref)
- update->old_ref = xstrdup(old_ref);
-+ if (new_ref)
-+ update->new_ref = xstrdup(new_ref);
- } else {
- if (flags & REF_HAVE_NEW)
- oidcpy(&update->new_oid, new_oid);
@@ refs.c: int ref_transaction_create(struct ref_transaction *transaction,
int ref_transaction_delete(struct ref_transaction *transaction,
const char *refname,
const struct object_id *old_oid,
- unsigned int flags, const char *msg,
+ unsigned int flags,
-+ const char *old_ref,
++ const char *old_target,
+ const char *msg,
struct strbuf *err)
{
-- if (old_oid && is_null_oid(old_oid))
-+ if (!(flags & REF_SYMREF_UPDATE) && old_oid &&
-+ is_null_oid(old_oid))
+ if (old_oid && is_null_oid(old_oid))
BUG("delete called with old_oid set to zeros");
++ if (old_target && !(flags & REF_NO_DEREF))
++ BUG("delete cannot operate on symrefs with deref mode");
return ref_transaction_update(transaction, refname,
null_oid(), old_oid,
- NULL, NULL, flags,
-+ NULL, old_ref, flags,
++ NULL, old_target, flags,
msg, err);
}
@@ refs.c: int refs_delete_refs(struct ref_store *refs, const char *logmsg,
for_each_string_list_item(item, refnames) {
ret = ref_transaction_delete(transaction, item->string,
- NULL, flags, msg, &err);
-+ NULL, flags, 0, msg, &err);
++ NULL, flags, NULL, msg, &err);
if (ret) {
warning(_("could not delete reference %s: %s"),
item->string, err.buf);
@@ refs.h: int ref_transaction_create(struct ref_transaction *transaction,
const struct object_id *old_oid,
- unsigned int flags, const char *msg,
+ unsigned int flags,
-+ const char *old_ref,
++ const char *old_target,
+ const char *msg,
struct strbuf *err);
@@ refs/files-backend.c: static int lock_ref_for_update(struct files_ref_store *ref
files_assert_main_repository(refs, "lock_ref_for_update");
- if ((update->flags & REF_HAVE_NEW) && is_null_oid(&update->new_oid))
-+ if ((update->flags & REF_HAVE_NEW) && null_new_value(update))
++ if ((update->flags & REF_HAVE_NEW) && ref_update_is_null_new_value(update))
update->flags |= REF_DELETING;
if (head_ref) {
@@ refs/reftable-backend.c: static int write_transaction_table(struct reftable_writ
continue;
- if (u->flags & REF_HAVE_NEW && is_null_oid(&u->new_oid)) {
-+ if (u->flags & REF_HAVE_NEW && null_new_value(u)) {
++ if (u->flags & REF_HAVE_NEW && ref_update_is_null_new_value(u)) {
struct reftable_ref_record ref = {
.refname = (char *)u->refname,
.update_index = ts,
## t/t1400-update-ref.sh ##
-@@ t/t1400-update-ref.sh: test_expect_success "stdin ${type} symref-verify fails for mistaken null value"
- test_cmp expect actual
- '
+@@ t/t1400-update-ref.sh: do
+ test_cmp before after
+ '
-+test_expect_success "stdin ${type} symref-delete fails without --no-deref" '
-+ git symbolic-ref refs/heads/symref $a &&
-+ create_stdin_buf ${type} "symref-delete refs/heads/symref" "$a" &&
-+ test_must_fail git update-ref --stdin ${type} <stdin 2>err &&
-+ grep "fatal: symref-delete: cannot operate with deref mode" err
-+'
+- test_expect_success "stdin ${type} symref-verify no value is treated as zero value" '
++ test_expect_success "stdin ${type} symref-verify fails with no value" '
+ git symbolic-ref refs/heads/symref >expect &&
+ create_stdin_buf ${type} "symref-verify refs/heads/symref" "" &&
+ test_must_fail git update-ref --stdin ${type} --no-deref <stdin
+@@ t/t1400-update-ref.sh: do
+ test_cmp expect actual
+ '
+
++ test_expect_success "stdin ${type} symref-delete fails without --no-deref" '
++ git symbolic-ref refs/heads/symref $a &&
++ create_stdin_buf ${type} "symref-delete refs/heads/symref" "$a" &&
++ test_must_fail git update-ref --stdin ${type} <stdin 2>err &&
++ grep "fatal: symref-delete: cannot operate with deref mode" err
++ '
++
++ test_expect_success "stdin ${type} symref-delete fails with no ref" '
++ create_stdin_buf ${type} "symref-delete " &&
++ test_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&
++ grep "fatal: symref-delete: missing <ref>" err
++ '
++
++ test_expect_success "stdin ${type} symref-delete fails with too many arguments" '
++ create_stdin_buf ${type} "symref-delete refs/heads/symref" "$a" "$a" &&
++ test_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&
++ if test "$type" = "-z"
++ then
++ grep "fatal: unknown command: $a" err
++ else
++ grep "fatal: symref-delete refs/heads/symref: extra input: $a" err
++ fi
++ '
+
-+test_expect_success "stdin ${type} fails symref-delete with no ref" '
-+ create_stdin_buf ${type} "symref-delete " &&
-+ test_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&
-+ grep "fatal: symref-delete: missing <ref>" err
-+'
++ test_expect_success "stdin ${type} symref-delete fails with wrong old value" '
++ create_stdin_buf ${type} "symref-delete refs/heads/symref" "$m" &&
++ test_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&
++ if test_have_prereq REFTABLE
++ then
++ grep "fatal: verifying symref target: ${SQ}refs/heads/symref${SQ}: is at $a but expected refs/heads/main" err
++ else
++ grep "fatal: cannot lock ref ${SQ}refs/heads/symref${SQ}" err
++ fi &&
++ git symbolic-ref refs/heads/symref >expect &&
++ echo $a >actual &&
++ test_cmp expect actual
++ '
+
-+test_expect_success "stdin ${type} fails symref-delete with too many arguments" '
-+ create_stdin_buf ${type} "symref-delete refs/heads/symref" "$a" "$a" &&
-+ test_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&
-+ if test "$type" = "-z"
-+ then
-+ grep "fatal: unknown command: $a" err
-+ else
-+ grep "fatal: symref-delete refs/heads/symref: extra input: $a" err
-+ fi
-+'
++ test_expect_success "stdin ${type} symref-delete works with right old value" '
++ create_stdin_buf ${type} "symref-delete refs/heads/symref" "$a" &&
++ git update-ref --stdin ${type} --no-deref <stdin &&
++ test_must_fail git rev-parse --verify -q refs/heads/symref
++ '
+
-+test_expect_success "stdin ${type} symref-delete ref fails with wrong old value" '
-+ create_stdin_buf ${type} "symref-delete refs/heads/symref" "$m" &&
-+ test_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&
-+ grep "fatal: cannot lock ref '"'"'refs/heads/symref'"'"'" err &&
-+ git symbolic-ref refs/heads/symref >expect &&
-+ echo $a >actual &&
-+ test_cmp expect actual
-+'
++ test_expect_success "stdin ${type} symref-delete works with empty old value" '
++ git symbolic-ref refs/heads/symref $a &&
++ create_stdin_buf ${type} "symref-delete refs/heads/symref" "" &&
++ git update-ref --stdin ${type} --no-deref <stdin &&
++ test_must_fail git rev-parse --verify -q $b
++ '
+
-+test_expect_success "stdin ${type} symref-delete ref works with right old value" '
-+ create_stdin_buf ${type} "symref-delete refs/heads/symref" "$a" &&
-+ git update-ref --stdin ${type} --no-deref <stdin &&
-+ test_must_fail git rev-parse --verify -q $b
-+'
++ test_expect_success "stdin ${type} symref-delete succeeds for dangling reference" '
++ test_must_fail git symbolic-ref refs/heads/nonexistent &&
++ git symbolic-ref refs/heads/symref2 refs/heads/nonexistent &&
++ create_stdin_buf ${type} "symref-delete refs/heads/symref2" "refs/heads/nonexistent" &&
++ git update-ref --stdin ${type} --no-deref <stdin &&
++ test_must_fail git symbolic-ref -d refs/heads/symref2
++ '
+
done
5: 8fa0151f94 ! 5: 37f8be2f3f update-ref: add support for symref-create
@@ Metadata
Author: Karthik Nayak <karthik.188@gmail.com>
## Commit message ##
- update-ref: add support for symref-create
+ update-ref: add support for 'symref-create' command
- Add 'symref-create' to allow creation of symbolic refs in a transaction
- via the 'git-update-ref' command. The 'symref-create' command takes in a
- <new-ref>, which the created <ref> will point to.
+ Add 'symref-create' command to the '--stdin' mode 'git-update-ref' to
+ allow creation of symbolic refs in a transaction. The 'symref-create'
+ command takes in a <new-target>, which the created <ref> will point to.
- We also support the 'core.prefersymlinkrefs', wherein if the flag is set
- and the filesystem supports symlinks, we create the symbolic ref as a
- symlink.
+ Also, support the 'core.prefersymlinkrefs' config, wherein if the config
+ is set and the filesystem supports symlinks, we create the symbolic ref
+ as a symlink. We fallback to creating a regular symref if creating the
+ symlink is unsuccessful.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
@@ Documentation/git-update-ref.txt: performs all modifications together. Specify
create SP <ref> SP <new-oid> LF
delete SP <ref> [SP <old-oid>] LF
verify SP <ref> [SP <old-oid>] LF
-+ symref-create SP <ref> SP <new-ref> LF
- symref-delete SP <ref> [SP <old-ref>] LF
- symref-verify SP <ref> [SP <old-ref>] LF
++ symref-create SP <ref> SP <new-target> LF
+ symref-delete SP <ref> [SP <old-target>] LF
+ symref-verify SP <ref> [SP <old-target>] LF
option SP <opt> LF
@@ Documentation/git-update-ref.txt: quoting:
create SP <ref> NUL <new-oid> NUL
delete SP <ref> NUL [<old-oid>] NUL
verify SP <ref> NUL [<old-oid>] NUL
-+ symref-create SP <ref> NUL <new-ref> NUL
- symref-delete SP <ref> [NUL <old-ref>] NUL
- symref-verify SP <ref> [NUL <old-ref>] NUL
++ symref-create SP <ref> NUL <new-target> NUL
+ symref-delete SP <ref> [NUL <old-target>] NUL
+ symref-verify SP <ref> [NUL <old-target>] NUL
option SP <opt> NUL
+@@ Documentation/git-update-ref.txt: update::
+
+ create::
+ Create <ref> with <new-oid> after verifying it does not
+- exist. The given <new-oid> may not be zero.
++ exist. The given <new-oid> may not be zero. If instead
++ ref:<new-target> is provided, a symbolic ref is created
++ which targets <new-target>.
+
+ delete::
+ Delete <ref> after verifying it exists with <old-oid>, if given.
@@ Documentation/git-update-ref.txt: verify::
Verify <ref> against <old-oid> but do not change it. If
<old-oid> is zero or missing, the ref must not exist.
+symref-create::
-+ Create symbolic ref <ref> with <new-ref> after verifying
++ Create symbolic ref <ref> with <new-target> after verifying
+ it does not exist. Can only be used in `no-deref` mode.
+
symref-delete::
- Delete <ref> after verifying it exists with <old-ref>, if
- given.
+ Delete <ref> after verifying it exists with <old-target>, if given.
+
## builtin/clone.c ##
@@ builtin/clone.c: static void write_remote_refs(const struct ref *local_refs)
@@ builtin/update-ref.c: static void parse_cmd_create(struct ref_transaction *trans
strbuf_release(&err);
}
++
+static void parse_cmd_symref_create(struct ref_transaction *transaction,
+ const char *next, const char *end)
+{
+ struct strbuf err = STRBUF_INIT;
-+ char *refname, *new_ref;
++ char *refname, *new_target;
+
+ if (!(update_flags & REF_NO_DEREF))
-+ die("symref-create: cannot operate with deref mode");
++ die("symref-create: cannot operate with deref mode");
+
+ refname = parse_refname(&next);
+ if (!refname)
+ die("symref-create: missing <ref>");
+
-+ new_ref = parse_next_refname(&next);
-+ if (!new_ref)
-+ die("symref-create %s: missing <new-ref>", refname);
-+ if (read_ref(new_ref, NULL))
-+ die("symref-create %s: invalid <new-ref>", refname);
++ new_target = parse_next_refname(&next);
++ if (!new_target)
++ die("symref-create %s: missing <new-target>", refname);
+
+ if (*next != line_termination)
+ die("symref-create %s: extra input: %s", refname, next);
+
-+ if (ref_transaction_create(transaction, refname, NULL, new_ref,
-+ update_flags | create_reflog_flag |
-+ REF_SYMREF_UPDATE, msg, &err))
++ if (ref_transaction_create(transaction, refname, NULL, new_target,
++ update_flags | create_reflog_flag,
++ msg, &err))
+ die("%s", err.buf);
+
+ update_flags = default_flags;
+ free(refname);
-+ free(new_ref);
++ free(new_target);
+ strbuf_release(&err);
+}
+
@@ refs.c: int ref_transaction_update(struct ref_transaction *transaction,
int ref_transaction_create(struct ref_transaction *transaction,
const char *refname,
const struct object_id *new_oid,
-+ const char *new_ref,
++ const char *new_target,
unsigned int flags, const char *msg,
struct strbuf *err)
{
- if (!new_oid || is_null_oid(new_oid)) {
-+ if ((flags & REF_SYMREF_UPDATE) && !new_ref) {
-+ strbuf_addf(err, "'%s' has a no new ref", refname);
-+ return 1;
-+ }
-+ if (!(flags & REF_SYMREF_UPDATE) && (!new_oid || is_null_oid(new_oid))) {
- strbuf_addf(err, "'%s' has a null OID", refname);
+- strbuf_addf(err, "'%s' has a null OID", refname);
++ if ((!new_oid || is_null_oid(new_oid)) && !new_target) {
++ strbuf_addf(err, "'%s' has a null OID or no new target", refname);
return 1;
}
++ if (new_target && !(flags & REF_NO_DEREF))
++ BUG("create cannot operate on symrefs with deref mode");
return ref_transaction_update(transaction, refname, new_oid,
- null_oid(), NULL, NULL, flags,
-+ null_oid(), new_ref, NULL, flags,
++ null_oid(), new_target, NULL, flags,
msg, err);
}
@@ refs.h: int ref_transaction_update(struct ref_transaction *transaction,
int ref_transaction_create(struct ref_transaction *transaction,
const char *refname,
const struct object_id *new_oid,
-+ const char *new_ref,
++ const char *new_target,
unsigned int flags, const char *msg,
struct strbuf *err);
@@ refs/files-backend.c: static int lock_ref_for_update(struct files_ref_store *ref
}
}
-+ if (update->flags & REF_SYMREF_UPDATE && update->new_ref) {
-+ if (create_symref_lock(refs, lock, update->refname, update->new_ref)) {
++ if (update->new_target) {
++ if (create_symref_lock(refs, lock, update->refname, update->new_target)) {
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto out;
+ }
@@ refs/files-backend.c: static int files_transaction_finish(struct ref_store *ref_
if (update->flags & REF_NEEDS_COMMIT ||
update->flags & REF_LOG_ONLY) {
-+ if (update->flags & REF_SYMREF_UPDATE && update->new_ref) {
-+ /* for dangling symrefs we gracefully set the oid to zero */
-+ if (!refs_resolve_ref_unsafe(&refs->base, update->new_ref,
++ if (update->new_target) {
++ /*
++ * We want to get the resolved OID for the target, to ensure
++ * that the correct value is added to the reflog.
++ */
++ if (!refs_resolve_ref_unsafe(&refs->base, update->new_target,
+ RESOLVE_REF_READING, &update->new_oid, NULL)) {
++ /* for dangling symrefs we gracefully set the oid to zero */
+ update->new_oid = *null_oid();
+ }
+ }
@@ refs/files-backend.c: static int files_transaction_finish(struct ref_store *ref_
+ * We try creating a symlink, if that succeeds we continue to the
+ * next updated. If not, we try and create a regular symref.
+ */
-+ if (update->flags & REF_SYMREF_UPDATE && prefer_symlink_refs)
-+ if (!create_ref_symlink(lock, update->new_ref))
++ if (update->new_target && prefer_symlink_refs)
++ if (!create_ref_symlink(lock, update->new_target))
+ continue;
+
if (update->flags & REF_NEEDS_COMMIT) {
@@ refs/reftable-backend.c: static int reftable_be_transaction_prepare(struct ref_s
* when the reference in question doesn't exist.
*/
- if (u->flags & REF_HAVE_NEW && !is_null_oid(&u->new_oid)) {
-+ if (u->flags & REF_HAVE_NEW && !null_new_value(u)) {
++ if (u->flags & REF_HAVE_NEW && !ref_update_is_null_new_value(u)) {
ret = queue_transaction_update(refs, tx_data, u,
¤t_oid, err);
if (ret)
@@ refs/reftable-backend.c: static int write_transaction_table(struct reftable_writ
* the given ref.
*/
- if (u->flags & REF_HAVE_NEW && !(u->type & REF_ISSYMREF) && is_null_oid(&u->new_oid)) {
-+ if (u->flags & REF_HAVE_NEW && !(u->type & REF_ISSYMREF) && null_new_value(u)) {
++ if (u->flags & REF_HAVE_NEW && !(u->type & REF_ISSYMREF) && ref_update_is_null_new_value(u)) {
struct reftable_log_record log = {0};
struct reftable_iterator it = {0};
+@@ refs/reftable-backend.c: static int write_transaction_table(struct reftable_writer *writer, void *cb_data
+ should_write_log(&arg->refs->base, u->refname))) {
+ struct reftable_log_record *log;
+
++ if (u->new_target)
++ if (!refs_resolve_ref_unsafe(&arg->refs->base, u->new_target,
++ RESOLVE_REF_READING, &u->new_oid, NULL))
++ /* for dangling symrefs we gracefully set the oid to zero */
++ u->new_oid = *null_oid();
++
+ ALLOC_GROW(logs, logs_nr + 1, logs_alloc);
+ log = &logs[logs_nr++];
+ memset(log, 0, sizeof(*log));
@@ refs/reftable-backend.c: static int write_transaction_table(struct reftable_writer *writer, void *cb_data
if (u->flags & REF_LOG_ONLY)
continue;
-- if (u->flags & REF_HAVE_NEW && null_new_value(u)) {
-+ if (u->flags & REF_SYMREF_UPDATE &&
-+ u->flags & REF_HAVE_NEW &&
-+ !null_new_value(u)) {
+- if (u->flags & REF_HAVE_NEW && ref_update_is_null_new_value(u)) {
++ if (u->flags & REF_HAVE_NEW && u->new_target) {
+ struct reftable_ref_record ref = {
+ .refname = (char *)u->refname,
+ .value_type = REFTABLE_REF_SYMREF,
-+ .value.symref = (char *)u->new_ref,
++ .value.symref = (char *)u->new_target,
+ .update_index = ts,
+ };
+
+ ret = reftable_writer_add_ref(writer, &ref);
+ if (ret < 0)
+ goto done;
-+ } else if (u->flags & REF_HAVE_NEW && null_new_value(u)) {
++ } else if (u->flags & REF_HAVE_NEW && ref_update_is_null_new_value(u)) {
struct reftable_ref_record ref = {
.refname = (char *)u->refname,
.update_index = ts,
@@ t/t0600-reffiles-backend.sh: test_expect_success POSIXPERM 'git reflog expire ho
test_done
## t/t1400-update-ref.sh ##
-@@ t/t1400-update-ref.sh: test_expect_success "stdin ${type} symref-delete ref works with right old value"
- test_must_fail git rev-parse --verify -q $b
- '
+@@ t/t1400-update-ref.sh: do
+ test_must_fail git symbolic-ref -d refs/heads/symref2
+ '
-+test_expect_success "stdin ${type} symref-create fails without --no-deref" '
-+ create_stdin_buf ${type} "symref-create refs/heads/symref" "$a" &&
-+ test_must_fail git update-ref --stdin ${type} <stdin 2>err &&
-+ grep "fatal: symref-create: cannot operate with deref mode" err
-+'
++ test_expect_success "stdin ${type} symref-create fails without --no-deref" '
++ create_stdin_buf ${type} "symref-create refs/heads/symref" "$a" &&
++ test_must_fail git update-ref --stdin ${type} <stdin 2>err &&
++ grep "fatal: symref-create: cannot operate with deref mode" err
++ '
+
-+test_expect_success "stdin ${type} fails symref-create with no ref" '
-+ create_stdin_buf ${type} "symref-create " >stdin &&
-+ test_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&
-+ grep "fatal: symref-create: missing <ref>" err
-+'
++ test_expect_success "stdin ${type} symref-create fails with too many arguments" '
++ create_stdin_buf ${type} "symref-create refs/heads/symref" "$a" "$a" >stdin &&
++ test_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&
++ if test "$type" = "-z"
++ then
++ grep "fatal: unknown command: $a" err
++ else
++ grep "fatal: symref-create refs/heads/symref: extra input: $a" err
++ fi
++ '
+
-+test_expect_success "stdin ${type} fails symref-create with no new value" '
-+ create_stdin_buf ${type} "symref-create refs/heads/symref" >stdin &&
-+ test_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&
-+ grep "fatal: symref-create refs/heads/symref: missing <new-ref>" err
-+'
++ test_expect_success "stdin ${type} symref-create fails with no target" '
++ create_stdin_buf ${type} "symref-create refs/heads/symref" >stdin &&
++ test_must_fail git update-ref --stdin ${type} --no-deref <stdin
++ '
+
-+test_expect_success "stdin ${type} fails symref-create with too many arguments" '
-+ create_stdin_buf ${type} "symref-create refs/heads/symref" "$a" "$a" >stdin &&
-+ test_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&
-+ if test "$type" = "-z"
-+ then
-+ grep "fatal: unknown command: $a" err
-+ else
-+ grep "fatal: symref-create refs/heads/symref: extra input: $a" err
-+ fi
-+'
++ test_expect_success "stdin ${type} symref-create fails with empty target" '
++ create_stdin_buf ${type} "symref-create refs/heads/symref" "" >stdin &&
++ test_must_fail git update-ref --stdin ${type} --no-deref <stdin
++ '
+
-+test_expect_success "stdin ${type} symref-create ref works" '
-+ test_when_finished "git symbolic-ref -d refs/heads/symref" &&
-+ create_stdin_buf ${type} "symref-create refs/heads/symref" "$a" >stdin &&
-+ git update-ref --stdin ${type} --no-deref <stdin &&
-+ git symbolic-ref refs/heads/symref >expect &&
-+ echo $a >actual &&
-+ test_cmp expect actual
-+'
++ test_expect_success "stdin ${type} symref-create works" '
++ test_when_finished "git symbolic-ref -d refs/heads/symref" &&
++ create_stdin_buf ${type} "symref-create refs/heads/symref" "$a" >stdin &&
++ git update-ref --stdin ${type} --no-deref <stdin &&
++ git symbolic-ref refs/heads/symref >expect &&
++ echo $a >actual &&
++ test_cmp expect actual
++ '
+
-+test_expect_success "stdin ${type} symref-create does not create reflogs by default" '
-+ test_when_finished "git symbolic-ref -d refs/symref" &&
-+ create_stdin_buf ${type} "symref-create refs/symref" "$a" >stdin &&
-+ git update-ref --stdin ${type} --no-deref <stdin &&
-+ git symbolic-ref refs/symref >expect &&
-+ echo $a >actual &&
-+ test_cmp expect actual &&
-+ test_must_fail git reflog exists refs/symref
-+'
++ test_expect_success "stdin ${type} create dangling symref ref works" '
++ test_when_finished "git symbolic-ref -d refs/heads/symref" &&
++ create_stdin_buf ${type} "symref-create refs/heads/symref" "refs/heads/unkown" >stdin &&
++ git update-ref --stdin ${type} --no-deref <stdin &&
++ git symbolic-ref refs/heads/symref >expect &&
++ echo refs/heads/unkown >actual &&
++ test_cmp expect actual
++ '
+
-+test_expect_success "stdin ${type} symref-create reflogs with --create-reflog" '
-+ test_when_finished "git symbolic-ref -d refs/heads/symref" &&
-+ create_stdin_buf ${type} "symref-create refs/heads/symref" "$a" >stdin &&
-+ git update-ref --create-reflog --stdin ${type} --no-deref <stdin &&
-+ git symbolic-ref refs/heads/symref >expect &&
-+ echo $a >actual &&
-+ test_cmp expect actual &&
-+ git reflog exists refs/heads/symref
-+'
++ test_expect_success "stdin ${type} symref-create does not create reflogs by default" '
++ test_when_finished "git symbolic-ref -d refs/symref" &&
++ create_stdin_buf ${type} "symref-create refs/symref" "$a" >stdin &&
++ git update-ref --stdin ${type} --no-deref <stdin &&
++ git symbolic-ref refs/symref >expect &&
++ echo $a >actual &&
++ test_cmp expect actual &&
++ test_must_fail git reflog exists refs/symref
++ '
++
++ test_expect_success "stdin ${type} symref-create reflogs with --create-reflog" '
++ test_when_finished "git symbolic-ref -d refs/heads/symref" &&
++ create_stdin_buf ${type} "symref-create refs/heads/symref" "$a" >stdin &&
++ git update-ref --create-reflog --stdin ${type} --no-deref <stdin &&
++ git symbolic-ref refs/heads/symref >expect &&
++ echo $a >actual &&
++ test_cmp expect actual &&
++ git reflog exists refs/heads/symref
++ '
+
done
6: 714492ede3 < -: ---------- update-ref: add support for symref-update
-: ---------- > 6: b385f4d0d7 update-ref: add support for 'symref-update' command
7: c483104562 ! 7: ef335e47d1 refs: support symrefs in 'reference-transaction' hook
@@ Metadata
Author: Karthik Nayak <karthik.188@gmail.com>
## Commit message ##
- refs: support symrefs in 'reference-transaction' hook
+ ref: support symrefs in 'reference-transaction' hook
The 'reference-transaction' hook runs whenever a reference update is
- made to the system. In the previous commits, we added support for
- various symref commands in `git-update-ref`. While it allowed us to now
+ made to the system. In the previous commits, we added symref support for
+ various commands in `git-update-ref`. While it allowed us to now
manipulate symbolic refs via `git-update-ref`, it didn't activate the
'reference-transaction' hook.
@@ Commit message
new format described for this and we stick to the existing format of:
<old-value> SP <new-value> SP <ref-name> LF
but now, <old-value> and <new-value> could also denote references
- instead of objects.
+ instead of objects, where the format is similar to that in
+ 'git-update-ref', i.e. 'ref:<ref-target>'.
While this seems to be backward incompatible, it is okay, since the only
way the `reference-transaction` hook has refs in its output is when
- `git-update-ref` is used with `update-symref` command. Also the
- documentation for reference-transaction hook always stated that support
- for symbolic references may be added in the future.
+ `git-update-ref` is used to manipulate symrefs. Also the documentation
+ for reference-transaction hook always stated that support for symbolic
+ references may be added in the future.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
@@ Documentation/githooks.txt: given reference transaction is in:
`<ref-name>` via `git rev-parse`.
+For symbolic reference updates the `<old_value>` and `<new-value>`
-+fields could denote references instead of objects.
++fields could denote references instead of objects, denoted via the
++`ref:<ref-target>` format.
+
The exit status of the hook is ignored for any state except for the
"prepared" state. In the "prepared" state, a non-zero exit status will
@@ refs.c: static int run_transaction_hook(struct ref_transaction *transaction,
for (i = 0; i < transaction->nr; i++) {
struct ref_update *update = transaction->updates[i];
-+ const char *new_value, *old_value;
++ strbuf_reset(&buf);
-- if (update->flags & REF_SYMREF_UPDATE)
+- /*
+- * Skip reference transaction for symbolic refs.
+- */
+- if (update->new_target || update->old_target)
- continue;
-+ new_value = oid_to_hex(&update->new_oid);
-+ old_value = oid_to_hex(&update->old_oid);
-+
-+ if (update->flags & REF_SYMREF_UPDATE) {
-+ if (update->flags & REF_HAVE_NEW && !null_new_value(update))
-+ new_value = update->new_ref;
-+ if (update->flags & REF_HAVE_OLD && update->old_ref)
-+ old_value = update->old_ref;
-+ }
++ if (update->flags & REF_HAVE_OLD && update->old_target)
++ strbuf_addf(&buf, "ref:%s ", update->old_target);
++ else
++ strbuf_addf(&buf, "%s ", oid_to_hex(&update->old_oid));
- strbuf_reset(&buf);
+- strbuf_reset(&buf);
- strbuf_addf(&buf, "%s %s %s\n",
- oid_to_hex(&update->old_oid),
- oid_to_hex(&update->new_oid),
- update->refname);
-+ strbuf_addf(&buf, "%s %s %s\n", old_value, new_value, update->refname);
++ if (update->flags & REF_HAVE_NEW && update->new_target)
++ strbuf_addf(&buf, "ref:%s ", update->new_target);
++ else
++ strbuf_addf(&buf, "%s ", oid_to_hex(&update->new_oid));
++
++ strbuf_addf(&buf, "%s\n", update->refname);
if (write_in_full(proc.in, buf.buf, buf.len) < 0) {
if (errno != EPIPE) {
## t/t1416-ref-transaction-hooks.sh ##
-@@ t/t1416-ref-transaction-hooks.sh: test_expect_success 'hook gets all queued updates in aborted state' '
- test_cmp expect actual
+@@ t/t1416-ref-transaction-hooks.sh: test_expect_success 'interleaving hook calls succeed' '
+ test_cmp expect target-repo.git/actual
'
-+# This test doesn't add a check for 'symref-delete' since there is a
++# This test doesn't add a check for symref 'delete' since there is a
+# variation between the ref backends WRT 'delete'. In the files backend,
+# 'delete' also triggers an additional transaction update on the
+# packed-refs backend, which constitutes additional reflog entries.
- test_expect_success 'interleaving hook calls succeed' '
- test_when_finished "rm -r target-repo.git" &&
-
-@@ t/t1416-ref-transaction-hooks.sh: test_expect_success 'interleaving hook calls succeed' '
- test_cmp expect target-repo.git/actual
- '
-
+test_expect_success 'hook gets all queued symref updates' '
+ test_when_finished "rm actual" &&
+
@@ t/t1416-ref-transaction-hooks.sh: test_expect_success 'interleaving hook calls s
+
+ cat >expect <<-EOF &&
+ prepared
-+ refs/heads/main $ZERO_OID refs/heads/symref
-+ $ZERO_OID refs/heads/main refs/heads/symrefc
-+ refs/heads/main refs/heads/branch refs/heads/symrefu
++ ref:refs/heads/main $ZERO_OID refs/heads/symref
++ $ZERO_OID ref:refs/heads/main refs/heads/symrefc
++ ref:refs/heads/main ref:refs/heads/branch refs/heads/symrefu
+ committed
-+ refs/heads/main $ZERO_OID refs/heads/symref
-+ $ZERO_OID refs/heads/main refs/heads/symrefc
-+ refs/heads/main refs/heads/branch refs/heads/symrefu
++ ref:refs/heads/main $ZERO_OID refs/heads/symref
++ $ZERO_OID ref:refs/heads/main refs/heads/symrefc
++ ref:refs/heads/main ref:refs/heads/branch refs/heads/symrefu
+ EOF
+
+ git update-ref --no-deref --stdin <<-EOF &&
+ start
+ symref-verify refs/heads/symref refs/heads/main
+ symref-create refs/heads/symrefc refs/heads/main
-+ symref-update refs/heads/symrefu refs/heads/branch refs/heads/main
++ symref-update refs/heads/symrefu refs/heads/branch ref refs/heads/main
+ prepare
+ commit
+ EOF
Karthik Nayak (7):
refs: accept symref values in `ref_transaction[_add]_update`
files-backend: extract out `create_symref_lock`
update-ref: add support for 'symref-verify' command
update-ref: add support for 'symref-delete' command
update-ref: add support for 'symref-create' command
update-ref: add support for 'symref-update' command
ref: support symrefs in 'reference-transaction' hook
Documentation/git-update-ref.txt | 34 ++-
Documentation/githooks.txt | 14 +-
branch.c | 2 +-
builtin/clone.c | 2 +-
builtin/fast-import.c | 5 +-
builtin/fetch.c | 4 +-
builtin/receive-pack.c | 4 +-
builtin/replace.c | 2 +-
builtin/tag.c | 1 +
builtin/update-ref.c | 240 ++++++++++++++++--
refs.c | 78 ++++--
refs.h | 23 +-
refs/files-backend.c | 141 +++++++++--
refs/refs-internal.h | 20 ++
refs/reftable-backend.c | 49 +++-
sequencer.c | 9 +-
t/t0600-reffiles-backend.sh | 32 +++
t/t1400-update-ref.sh | 413 ++++++++++++++++++++++++++++++-
t/t1416-ref-transaction-hooks.sh | 41 +++
walker.c | 2 +-
20 files changed, 1030 insertions(+), 86 deletions(-)
--
2.43.GIT
^ permalink raw reply [flat|nested] 194+ messages in thread
* [PATCH v4 1/7] refs: accept symref values in `ref_transaction[_add]_update`
2024-04-26 15:24 ` [PATCH v4 0/7] add symref-* commands to 'git-update-ref --stdin' Karthik Nayak
@ 2024-04-26 15:24 ` Karthik Nayak
2024-04-26 19:31 ` Junio C Hamano
2024-04-29 13:38 ` Phillip Wood
2024-04-26 15:24 ` [PATCH v4 2/7] files-backend: extract out `create_symref_lock` Karthik Nayak
` (7 subsequent siblings)
8 siblings, 2 replies; 194+ messages in thread
From: Karthik Nayak @ 2024-04-26 15:24 UTC (permalink / raw)
To: karthik.188; +Cc: christian.couder, git, gitster, ps
From: Karthik Nayak <karthik.188@gmail.com>
The `ref_transaction[_add]_update` functions obtain ref information and
flags to create a `ref_update` and add it to the transaction at hand.
To extend symref support in transactions, we need to also accept the
old and new ref targets and process it. In this commit, let's add the
required parameters to the function and modify all call sites.
The two parameters added are `new_target` and `old_target`. The
`new_target` is used to denote what the reference should point to when
the transaction is applied.
The `old_target` denotes the value the reference must have before the
update. Some functions allow this parameter to be NULL, meaning that the
old value of the reference is not checked.
The handling logic of these parameters will be added in consequent
commits as we add symref commands to the '--stdin' mode of
'git-update-ref'.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
branch.c | 2 +-
builtin/fast-import.c | 5 +++--
builtin/fetch.c | 2 +-
builtin/receive-pack.c | 1 +
builtin/replace.c | 2 +-
builtin/tag.c | 1 +
builtin/update-ref.c | 1 +
refs.c | 22 +++++++++++++++++-----
refs.h | 17 ++++++++++++++++-
refs/files-backend.c | 12 ++++++------
refs/refs-internal.h | 13 +++++++++++++
refs/reftable-backend.c | 4 ++--
sequencer.c | 9 +++++----
walker.c | 2 +-
14 files changed, 69 insertions(+), 24 deletions(-)
diff --git a/branch.c b/branch.c
index e4a738fc7b..48af4c3ceb 100644
--- a/branch.c
+++ b/branch.c
@@ -627,7 +627,7 @@ void create_branch(struct repository *r,
if (!transaction ||
ref_transaction_update(transaction, ref.buf,
&oid, forcing ? NULL : null_oid(),
- 0, msg, &err) ||
+ NULL, NULL, 0, msg, &err) ||
ref_transaction_commit(transaction, &err))
die("%s", err.buf);
ref_transaction_free(transaction);
diff --git a/builtin/fast-import.c b/builtin/fast-import.c
index dc5a9d32dd..297dfb91a1 100644
--- a/builtin/fast-import.c
+++ b/builtin/fast-import.c
@@ -1634,7 +1634,7 @@ static int update_branch(struct branch *b)
transaction = ref_transaction_begin(&err);
if (!transaction ||
ref_transaction_update(transaction, b->name, &b->oid, &old_oid,
- 0, msg, &err) ||
+ NULL, NULL, 0, msg, &err) ||
ref_transaction_commit(transaction, &err)) {
ref_transaction_free(transaction);
error("%s", err.buf);
@@ -1675,7 +1675,8 @@ static void dump_tags(void)
strbuf_addf(&ref_name, "refs/tags/%s", t->name);
if (ref_transaction_update(transaction, ref_name.buf,
- &t->oid, NULL, 0, msg, &err)) {
+ &t->oid, NULL, NULL, NULL,
+ 0, msg, &err)) {
failure |= error("%s", err.buf);
goto cleanup;
}
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 5857d860db..66840b7c5b 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -668,7 +668,7 @@ static int s_update_ref(const char *action,
ret = ref_transaction_update(transaction, ref->name, &ref->new_oid,
check_old ? &ref->old_oid : NULL,
- 0, msg, &err);
+ NULL, NULL, 0, msg, &err);
if (ret) {
ret = STORE_REF_ERROR_OTHER;
goto out;
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index e8d7df14b6..b150ef39a8 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -1595,6 +1595,7 @@ static const char *update(struct command *cmd, struct shallow_info *si)
if (ref_transaction_update(transaction,
namespaced_name,
new_oid, old_oid,
+ NULL, NULL,
0, "push",
&err)) {
rp_error("%s", err.buf);
diff --git a/builtin/replace.c b/builtin/replace.c
index da59600ad2..7690687b0e 100644
--- a/builtin/replace.c
+++ b/builtin/replace.c
@@ -201,7 +201,7 @@ static int replace_object_oid(const char *object_ref,
transaction = ref_transaction_begin(&err);
if (!transaction ||
ref_transaction_update(transaction, ref.buf, repl, &prev,
- 0, NULL, &err) ||
+ NULL, NULL, 0, NULL, &err) ||
ref_transaction_commit(transaction, &err))
res = error("%s", err.buf);
diff --git a/builtin/tag.c b/builtin/tag.c
index 9a33cb50b4..40a65fdebc 100644
--- a/builtin/tag.c
+++ b/builtin/tag.c
@@ -660,6 +660,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
transaction = ref_transaction_begin(&err);
if (!transaction ||
ref_transaction_update(transaction, ref.buf, &object, &prev,
+ NULL, NULL,
create_reflog ? REF_FORCE_CREATE_REFLOG : 0,
reflog_msg.buf, &err) ||
ref_transaction_commit(transaction, &err)) {
diff --git a/builtin/update-ref.c b/builtin/update-ref.c
index e46afbc46d..21fdbf6ac8 100644
--- a/builtin/update-ref.c
+++ b/builtin/update-ref.c
@@ -204,6 +204,7 @@ static void parse_cmd_update(struct ref_transaction *transaction,
if (ref_transaction_update(transaction, refname,
&new_oid, have_old ? &old_oid : NULL,
+ NULL, NULL,
update_flags | create_reflog_flag,
msg, &err))
die("%s", err.buf);
diff --git a/refs.c b/refs.c
index 55d2e0b2cb..060a31616d 100644
--- a/refs.c
+++ b/refs.c
@@ -1228,6 +1228,7 @@ struct ref_update *ref_transaction_add_update(
const char *refname, unsigned int flags,
const struct object_id *new_oid,
const struct object_id *old_oid,
+ const char *new_target, const char *old_target,
const char *msg)
{
struct ref_update *update;
@@ -1235,6 +1236,11 @@ struct ref_update *ref_transaction_add_update(
if (transaction->state != REF_TRANSACTION_OPEN)
BUG("update called for transaction that is not open");
+ if (old_oid && !is_null_oid(old_oid) && old_target)
+ BUG("Only one of old_oid and old_target should be non NULL");
+ if (new_oid && !is_null_oid(new_oid) && new_target)
+ BUG("Only one of new_oid and new_target should be non NULL");
+
FLEX_ALLOC_STR(update, refname, refname);
ALLOC_GROW(transaction->updates, transaction->nr + 1, transaction->alloc);
transaction->updates[transaction->nr++] = update;
@@ -1253,6 +1259,8 @@ int ref_transaction_update(struct ref_transaction *transaction,
const char *refname,
const struct object_id *new_oid,
const struct object_id *old_oid,
+ const char *new_target,
+ const char *old_target,
unsigned int flags, const char *msg,
struct strbuf *err)
{
@@ -1280,7 +1288,8 @@ int ref_transaction_update(struct ref_transaction *transaction,
flags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0);
ref_transaction_add_update(transaction, refname, flags,
- new_oid, old_oid, msg);
+ new_oid, old_oid, new_target,
+ old_target, msg);
return 0;
}
@@ -1295,7 +1304,8 @@ int ref_transaction_create(struct ref_transaction *transaction,
return 1;
}
return ref_transaction_update(transaction, refname, new_oid,
- null_oid(), flags, msg, err);
+ null_oid(), NULL, NULL, flags,
+ msg, err);
}
int ref_transaction_delete(struct ref_transaction *transaction,
@@ -1308,7 +1318,8 @@ int ref_transaction_delete(struct ref_transaction *transaction,
BUG("delete called with old_oid set to zeros");
return ref_transaction_update(transaction, refname,
null_oid(), old_oid,
- flags, msg, err);
+ NULL, NULL, flags,
+ msg, err);
}
int ref_transaction_verify(struct ref_transaction *transaction,
@@ -1321,6 +1332,7 @@ int ref_transaction_verify(struct ref_transaction *transaction,
BUG("verify called with old_oid set to NULL");
return ref_transaction_update(transaction, refname,
NULL, old_oid,
+ NULL, NULL,
flags, NULL, err);
}
@@ -1335,8 +1347,8 @@ int refs_update_ref(struct ref_store *refs, const char *msg,
t = ref_store_transaction_begin(refs, &err);
if (!t ||
- ref_transaction_update(t, refname, new_oid, old_oid, flags, msg,
- &err) ||
+ ref_transaction_update(t, refname, new_oid, old_oid, NULL, NULL,
+ flags, msg, &err) ||
ref_transaction_commit(t, &err)) {
ret = 1;
ref_transaction_free(t);
diff --git a/refs.h b/refs.h
index d278775e08..c792e13a64 100644
--- a/refs.h
+++ b/refs.h
@@ -648,6 +648,15 @@ struct ref_transaction *ref_transaction_begin(struct strbuf *err);
* before the update. A copy of this value is made in the
* transaction.
*
+ * new_target -- the target reference that the reference will be
+ * update to point to. This takes precedence over new_oid when
+ * set. If the reference is a regular reference, it will be
+ * converted to a symbolic reference.
+ *
+ * old_target -- the reference that the reference must be pointing to.
+ * Will only be taken into account when the reference is a symbolic
+ * reference.
+ *
* flags -- flags affecting the update, passed to
* update_ref_lock(). Possible flags: REF_NO_DEREF,
* REF_FORCE_CREATE_REFLOG. See those constants for more
@@ -713,7 +722,11 @@ struct ref_transaction *ref_transaction_begin(struct strbuf *err);
* beforehand. The old value is checked after the lock is taken to
* prevent races. If the old value doesn't agree with old_oid, the
* whole transaction fails. If old_oid is NULL, then the previous
- * value is not checked.
+ * value is not checked. If `old_target` is not NULL, treat the reference
+ * as a symbolic ref and validate that its target before the update is
+ * `old_target`. If the `new_target` is not NULL, then the reference
+ * will be updated to a symbolic ref which targets `new_target`.
+ * Together, these allow us to update between regular refs and symrefs.
*
* See the above comment "Reference transaction updates" for more
* information.
@@ -722,6 +735,8 @@ int ref_transaction_update(struct ref_transaction *transaction,
const char *refname,
const struct object_id *new_oid,
const struct object_id *old_oid,
+ const char *new_target,
+ const char *old_target,
unsigned int flags, const char *msg,
struct strbuf *err);
diff --git a/refs/files-backend.c b/refs/files-backend.c
index a098d14ea0..e4d0aa3d41 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -1198,7 +1198,7 @@ static void prune_ref(struct files_ref_store *refs, struct ref_to_prune *r)
ref_transaction_add_update(
transaction, r->name,
REF_NO_DEREF | REF_HAVE_NEW | REF_HAVE_OLD | REF_IS_PRUNING,
- null_oid(), &r->oid, NULL);
+ null_oid(), &r->oid, NULL, NULL, NULL);
if (ref_transaction_commit(transaction, &err))
goto cleanup;
@@ -1292,7 +1292,7 @@ static int files_pack_refs(struct ref_store *ref_store,
* packed-refs transaction:
*/
if (ref_transaction_update(transaction, iter->refname,
- iter->oid, NULL,
+ iter->oid, NULL, NULL, NULL,
REF_NO_DEREF, NULL, &err))
die("failure preparing to create packed reference %s: %s",
iter->refname, err.buf);
@@ -2309,7 +2309,7 @@ static int split_head_update(struct ref_update *update,
transaction, "HEAD",
update->flags | REF_LOG_ONLY | REF_NO_DEREF,
&update->new_oid, &update->old_oid,
- update->msg);
+ NULL, NULL, update->msg);
/*
* Add "HEAD". This insertion is O(N) in the transaction
@@ -2372,7 +2372,7 @@ static int split_symref_update(struct ref_update *update,
new_update = ref_transaction_add_update(
transaction, referent, new_flags,
&update->new_oid, &update->old_oid,
- update->msg);
+ NULL, NULL, update->msg);
new_update->parent_update = update;
@@ -2763,7 +2763,7 @@ static int files_transaction_prepare(struct ref_store *ref_store,
packed_transaction, update->refname,
REF_HAVE_NEW | REF_NO_DEREF,
&update->new_oid, NULL,
- NULL);
+ NULL, NULL, NULL);
}
}
@@ -3048,7 +3048,7 @@ static int files_initial_transaction_commit(struct ref_store *ref_store,
ref_transaction_add_update(packed_transaction, update->refname,
update->flags & ~REF_HAVE_OLD,
&update->new_oid, &update->old_oid,
- NULL);
+ NULL, NULL, NULL);
}
if (packed_refs_lock(refs->packed_ref_store, 0, err)) {
diff --git a/refs/refs-internal.h b/refs/refs-internal.h
index 56641aa57a..3040d4797c 100644
--- a/refs/refs-internal.h
+++ b/refs/refs-internal.h
@@ -124,6 +124,18 @@ struct ref_update {
*/
struct object_id old_oid;
+ /*
+ * If set, point the reference to this value. This can also be
+ * used to convert regular references to become symbolic refs.
+ */
+ const char *new_target;
+
+ /*
+ * If set and the reference is a symbolic ref, check that the
+ * reference previously pointed to this value.
+ */
+ const char *old_target;
+
/*
* One or more of REF_NO_DEREF, REF_FORCE_CREATE_REFLOG,
* REF_HAVE_NEW, REF_HAVE_OLD, or backend-specific flags.
@@ -173,6 +185,7 @@ struct ref_update *ref_transaction_add_update(
const char *refname, unsigned int flags,
const struct object_id *new_oid,
const struct object_id *old_oid,
+ const char *new_target, const char *old_target,
const char *msg);
/*
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index 1cda48c504..6104471199 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -829,7 +829,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
new_update = ref_transaction_add_update(
transaction, "HEAD",
u->flags | REF_LOG_ONLY | REF_NO_DEREF,
- &u->new_oid, &u->old_oid, u->msg);
+ &u->new_oid, &u->old_oid, NULL, NULL, u->msg);
string_list_insert(&affected_refnames, new_update->refname);
}
@@ -908,7 +908,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
*/
new_update = ref_transaction_add_update(
transaction, referent.buf, new_flags,
- &u->new_oid, &u->old_oid, u->msg);
+ &u->new_oid, &u->old_oid, NULL, NULL, u->msg);
new_update->parent_update = u;
/*
diff --git a/sequencer.c b/sequencer.c
index 2c19846385..af1b25692b 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -616,7 +616,7 @@ static int fast_forward_to(struct repository *r,
if (!transaction ||
ref_transaction_update(transaction, "HEAD",
to, unborn && !is_rebase_i(opts) ?
- null_oid() : from,
+ null_oid() : from, NULL, NULL,
0, sb.buf, &err) ||
ref_transaction_commit(transaction, &err)) {
ref_transaction_free(transaction);
@@ -1248,7 +1248,7 @@ int update_head_with_reflog(const struct commit *old_head,
if (!transaction ||
ref_transaction_update(transaction, "HEAD", new_head,
old_head ? &old_head->object.oid : null_oid(),
- 0, sb.buf, err) ||
+ NULL, NULL, 0, sb.buf, err) ||
ref_transaction_commit(transaction, err)) {
ret = -1;
}
@@ -3764,8 +3764,9 @@ static int do_label(struct repository *r, const char *name, int len)
} else if (repo_get_oid(r, "HEAD", &head_oid)) {
error(_("could not read HEAD"));
ret = -1;
- } else if (ref_transaction_update(transaction, ref_name.buf, &head_oid,
- NULL, 0, msg.buf, &err) < 0 ||
+ } else if (ref_transaction_update(transaction, ref_name.buf,
+ &head_oid, NULL, NULL, NULL,
+ 0, msg.buf, &err) < 0 ||
ref_transaction_commit(transaction, &err)) {
error("%s", err.buf);
ret = -1;
diff --git a/walker.c b/walker.c
index c0fd632d92..1b3df43906 100644
--- a/walker.c
+++ b/walker.c
@@ -324,7 +324,7 @@ int walker_fetch(struct walker *walker, int targets, char **target,
strbuf_reset(&refname);
strbuf_addf(&refname, "refs/%s", write_ref[i]);
if (ref_transaction_update(transaction, refname.buf,
- oids + i, NULL, 0,
+ oids + i, NULL, NULL, NULL, 0,
msg ? msg : "fetch (unknown)",
&err)) {
error("%s", err.buf);
--
2.43.GIT
^ permalink raw reply related [flat|nested] 194+ messages in thread
* Re: [PATCH v4 1/7] refs: accept symref values in `ref_transaction[_add]_update`
2024-04-26 15:24 ` [PATCH v4 1/7] refs: accept symref values in `ref_transaction[_add]_update` Karthik Nayak
@ 2024-04-26 19:31 ` Junio C Hamano
2024-04-26 21:15 ` Jeff King
2024-04-28 19:36 ` Karthik Nayak
2024-04-29 13:38 ` Phillip Wood
1 sibling, 2 replies; 194+ messages in thread
From: Junio C Hamano @ 2024-04-26 19:31 UTC (permalink / raw)
To: Karthik Nayak; +Cc: christian.couder, git, ps
Karthik Nayak <karthik.188@gmail.com> writes:
> From: Karthik Nayak <karthik.188@gmail.com>
> Subject: Re: [PATCH v4 1/7] refs: accept symref values in `ref_transaction[_add]_update`
>
> The `ref_transaction[_add]_update` functions obtain ref information and
> flags to create a `ref_update` and add it to the transaction at hand.
Just a very minor irritation, but ref_transaction_add_update() is a
function used internally in the ref subsystem and is exported only
because its visibility needs to cross file boundaries between refs.c
and refs/*backend.c files.
It would be better to only mention ref_transaction_update() in the
title, and talk about the need to make matching adjustment to
ref_transaction_add_update(), which is an internal function, in the
body of the log message.
This is an unrelated #leftoverbits tangent, but while trying to find
out the reason why "[_add]" in the title looked irritating to me, I
noticed that builtin/show-ref.c includes <refs/refs-internal.h>. I
do not know what it uses from the "internal" implementation detail,
but the API may have to be cleaned up so that a random "client"
caller do not have to do so.
The patch itself looked good. Thanks.
^ permalink raw reply [flat|nested] 194+ messages in thread
* Re: [PATCH v4 1/7] refs: accept symref values in `ref_transaction[_add]_update`
2024-04-26 19:31 ` Junio C Hamano
@ 2024-04-26 21:15 ` Jeff King
2024-04-29 7:02 ` Patrick Steinhardt
2024-04-29 9:32 ` phillip.wood123
2024-04-28 19:36 ` Karthik Nayak
1 sibling, 2 replies; 194+ messages in thread
From: Jeff King @ 2024-04-26 21:15 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Phillip Wood, Karthik Nayak, christian.couder, git, ps
On Fri, Apr 26, 2024 at 12:31:36PM -0700, Junio C Hamano wrote:
> This is an unrelated #leftoverbits tangent, but while trying to find
> out the reason why "[_add]" in the title looked irritating to me, I
> noticed that builtin/show-ref.c includes <refs/refs-internal.h>. I
> do not know what it uses from the "internal" implementation detail,
> but the API may have to be cleaned up so that a random "client"
> caller do not have to do so.
There are two issues. One is the use of refs_read_raw_ref(), added by
Patrick's 9080a7f178 (builtin/show-ref: add new mode to check for
reference existence, 2023-10-31). And it argues there why the regular
API is unsufficient (mostly because it does not protect errno).
But the more interesting one is a call to refname_is_safe(), added
recently by Phillip's 1dbe401563 (show-ref --verify: accept pseudorefs,
2024-02-07). Looking at that commit, the intent was to allow pseudo-refs
by loosening the conditional that checked "HEAD" to allow "FOO_BAR" but
not "foobar" outside of "refs/". We enforce the all-caps pseudoref
syntax in is_refname_safe().
The proper API there is I think check_ref_format() with ALLOW_ONELEVEL.
But you shouldn't need to do that, because the refs code should be
checking the names itself (using check_ref_format() usually, but falling
back to refname_is_safe() if the ALLOW_BAD_NAME flag is passed).
And I think there _is_ a bug there. The idea of those two functions is
that check_ref_format() would allow a subset of what refname_is_safe()
does. We'd fall back to the latter when deleting, but not otherwise
allow creation or updates.
However, it looks like check_ref_format() doesn't enforce the pseudo-ref
syntax. It will happily resolve this:
git rev-parse HEAD >.git/foo
git rev-parse foo
and even update it:
git update-ref foo HEAD
though curiously we will refuse to delete it:
$ git update-ref -d foo
error: refusing to update ref with bad name 'foo'
since that sets the ALLOW_BAD_NAME flag!
IMHO these should _all_ be forbidden, because we only want to allow the
more limited pseudoref names everywhere (and never mischievous ones like
"config" or whatever). And once we are doing that, then show-ref has no
need to check the format. It can just call read_ref() and it either gets
an answer or doesn't.
I don't know if that is a #leftoverbit though. It perhaps more
complicated than that.
-Peff
^ permalink raw reply [flat|nested] 194+ messages in thread
* Re: [PATCH v4 1/7] refs: accept symref values in `ref_transaction[_add]_update`
2024-04-26 21:15 ` Jeff King
@ 2024-04-29 7:02 ` Patrick Steinhardt
2024-04-29 7:55 ` Jeff King
2024-04-29 9:32 ` phillip.wood123
1 sibling, 1 reply; 194+ messages in thread
From: Patrick Steinhardt @ 2024-04-29 7:02 UTC (permalink / raw)
To: Jeff King
Cc: Junio C Hamano, Phillip Wood, Karthik Nayak, christian.couder, git
[-- Attachment #1: Type: text/plain, Size: 3305 bytes --]
On Fri, Apr 26, 2024 at 05:15:29PM -0400, Jeff King wrote:
> On Fri, Apr 26, 2024 at 12:31:36PM -0700, Junio C Hamano wrote:
>
> > This is an unrelated #leftoverbits tangent, but while trying to find
> > out the reason why "[_add]" in the title looked irritating to me, I
> > noticed that builtin/show-ref.c includes <refs/refs-internal.h>. I
> > do not know what it uses from the "internal" implementation detail,
> > but the API may have to be cleaned up so that a random "client"
> > caller do not have to do so.
>
> There are two issues. One is the use of refs_read_raw_ref(), added by
> Patrick's 9080a7f178 (builtin/show-ref: add new mode to check for
> reference existence, 2023-10-31). And it argues there why the regular
> API is unsufficient (mostly because it does not protect errno).
>
> But the more interesting one is a call to refname_is_safe(), added
> recently by Phillip's 1dbe401563 (show-ref --verify: accept pseudorefs,
> 2024-02-07). Looking at that commit, the intent was to allow pseudo-refs
> by loosening the conditional that checked "HEAD" to allow "FOO_BAR" but
> not "foobar" outside of "refs/". We enforce the all-caps pseudoref
> syntax in is_refname_safe().
>
> The proper API there is I think check_ref_format() with ALLOW_ONELEVEL.
> But you shouldn't need to do that, because the refs code should be
> checking the names itself (using check_ref_format() usually, but falling
> back to refname_is_safe() if the ALLOW_BAD_NAME flag is passed).
>
> And I think there _is_ a bug there. The idea of those two functions is
> that check_ref_format() would allow a subset of what refname_is_safe()
> does. We'd fall back to the latter when deleting, but not otherwise
> allow creation or updates.
>
> However, it looks like check_ref_format() doesn't enforce the pseudo-ref
> syntax. It will happily resolve this:
>
> git rev-parse HEAD >.git/foo
> git rev-parse foo
>
> and even update it:
>
> git update-ref foo HEAD
>
> though curiously we will refuse to delete it:
>
> $ git update-ref -d foo
> error: refusing to update ref with bad name 'foo'
>
> since that sets the ALLOW_BAD_NAME flag!
>
> IMHO these should _all_ be forbidden, because we only want to allow the
> more limited pseudoref names everywhere (and never mischievous ones like
> "config" or whatever). And once we are doing that, then show-ref has no
> need to check the format. It can just call read_ref() and it either gets
> an answer or doesn't.
>
> I don't know if that is a #leftoverbit though. It perhaps more
> complicated than that.
Yeah, this is something that I've repeatedly stumbled over myself. If I
remember correctly, the plan was to clean up and consolidate all these
different functions we have for checking ref names such that they become
easier to use and hopefully lead to more consistent behaviour.
In any case, I very much agree that git-update-ref(1) should refuse to
write refs with names that are known-bad. There should probably be an
escape hatch though that at least allows you to _delete_ those, or
otherwise there is no way to remove such a ref in the reftable repo.
Well, except for meddling with the binary format, but I doubt that
anybody would really want to do that.
Patrick
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 194+ messages in thread
* Re: [PATCH v4 1/7] refs: accept symref values in `ref_transaction[_add]_update`
2024-04-29 7:02 ` Patrick Steinhardt
@ 2024-04-29 7:55 ` Jeff King
2024-04-29 9:29 ` phillip.wood123
0 siblings, 1 reply; 194+ messages in thread
From: Jeff King @ 2024-04-29 7:55 UTC (permalink / raw)
To: Patrick Steinhardt
Cc: Junio C Hamano, Phillip Wood, Karthik Nayak, christian.couder, git
On Mon, Apr 29, 2024 at 09:02:56AM +0200, Patrick Steinhardt wrote:
> > IMHO these should _all_ be forbidden, because we only want to allow the
> > more limited pseudoref names everywhere (and never mischievous ones like
> > "config" or whatever). And once we are doing that, then show-ref has no
> > need to check the format. It can just call read_ref() and it either gets
> > an answer or doesn't.
> >
> > I don't know if that is a #leftoverbit though. It perhaps more
> > complicated than that.
>
> Yeah, this is something that I've repeatedly stumbled over myself. If I
> remember correctly, the plan was to clean up and consolidate all these
> different functions we have for checking ref names such that they become
> easier to use and hopefully lead to more consistent behaviour.
I think the code changes here aren't too bad (modulo some
head-scratching I had to do around per-worktree refs). I'll post a
series in a moment (split off from here since we're far off topic from
the original thread).
> In any case, I very much agree that git-update-ref(1) should refuse to
> write refs with names that are known-bad. There should probably be an
> escape hatch though that at least allows you to _delete_ those, or
> otherwise there is no way to remove such a ref in the reftable repo.
> Well, except for meddling with the binary format, but I doubt that
> anybody would really want to do that.
Yeah, ironically deleting is the one thing you cannot do with them even
right now. ;) That is because the supposedly-looser refname_is_safe() is
actually tighter.
It would be tough to loosen it safely, since you don't want to allow
deleting arbitrary files in .git. Or worse, escaping .git via clever
paths, symlinks, etc. So I think at most you'd want some kind of "trust
me, for this command don't bother with ref format checks" flag.
Or alternatively, refname_is_safe() should become a per-backend thing.
And then reftables can say "everything is safe", because the ref names
are never used as paths (I think, anyway; I didn't follow all of the
discussion around per-worktree refs, pseudorefs, etc).
-Peff
^ permalink raw reply [flat|nested] 194+ messages in thread
* Re: [PATCH v4 1/7] refs: accept symref values in `ref_transaction[_add]_update`
2024-04-29 7:55 ` Jeff King
@ 2024-04-29 9:29 ` phillip.wood123
0 siblings, 0 replies; 194+ messages in thread
From: phillip.wood123 @ 2024-04-29 9:29 UTC (permalink / raw)
To: Jeff King, Patrick Steinhardt
Cc: Junio C Hamano, Phillip Wood, Karthik Nayak, christian.couder, git
On 29/04/2024 08:55, Jeff King wrote:
> On Mon, Apr 29, 2024 at 09:02:56AM +0200, Patrick Steinhardt wrote:
>
>> In any case, I very much agree that git-update-ref(1) should refuse to
>> write refs with names that are known-bad. There should probably be an
>> escape hatch though that at least allows you to _delete_ those, or
>> otherwise there is no way to remove such a ref in the reftable repo.
>> Well, except for meddling with the binary format, but I doubt that
>> anybody would really want to do that.
>
> Yeah, ironically deleting is the one thing you cannot do with them even
> right now. ;) That is because the supposedly-looser refname_is_safe() is
> actually tighter.
>
> It would be tough to loosen it safely, since you don't want to allow
> deleting arbitrary files in .git. Or worse, escaping .git via clever
> paths, symlinks, etc. So I think at most you'd want some kind of "trust
> me, for this command don't bother with ref format checks" flag.
Currently "git update-ref config HEAD" fails because the config file
does not look like a ref - could we do similar check when deleting
references at least for the files backend for arguments outside refs/?
Best Wishes
Phillip
^ permalink raw reply [flat|nested] 194+ messages in thread
* Re: [PATCH v4 1/7] refs: accept symref values in `ref_transaction[_add]_update`
2024-04-26 21:15 ` Jeff King
2024-04-29 7:02 ` Patrick Steinhardt
@ 2024-04-29 9:32 ` phillip.wood123
2024-04-29 16:18 ` Junio C Hamano
2024-04-30 10:30 ` Jeff King
1 sibling, 2 replies; 194+ messages in thread
From: phillip.wood123 @ 2024-04-29 9:32 UTC (permalink / raw)
To: Jeff King, Junio C Hamano
Cc: Phillip Wood, Karthik Nayak, christian.couder, git, ps
Hi Peff
On 26/04/2024 22:15, Jeff King wrote:
> On Fri, Apr 26, 2024 at 12:31:36PM -0700, Junio C Hamano wrote:
>
> But the more interesting one is a call to refname_is_safe(), added
> recently by Phillip's 1dbe401563 (show-ref --verify: accept pseudorefs,
> 2024-02-07). Looking at that commit, the intent was to allow pseudo-refs
> by loosening the conditional that checked "HEAD" to allow "FOO_BAR" but
> not "foobar" outside of "refs/". We enforce the all-caps pseudoref
> syntax in is_refname_safe().
>
> The proper API there is I think check_ref_format() with ALLOW_ONELEVEL.
ALLOW_ONELEVEL just disables the check that the refname contains a '/'
and I think it is aimed at checking branch and tag names without a
refs/{heads,tags} prefix. If we want to move away from using
refname_is_safe() perhaps we could add an ALLOW_PSEUDOREF flag that only
allows the name to contain '[A-Z_]' if there is no '/'.
Best Wishes
Phillip
^ permalink raw reply [flat|nested] 194+ messages in thread
* Re: [PATCH v4 1/7] refs: accept symref values in `ref_transaction[_add]_update`
2024-04-29 9:32 ` phillip.wood123
@ 2024-04-29 16:18 ` Junio C Hamano
2024-04-30 10:33 ` Jeff King
2024-04-30 10:30 ` Jeff King
1 sibling, 1 reply; 194+ messages in thread
From: Junio C Hamano @ 2024-04-29 16:18 UTC (permalink / raw)
To: phillip.wood123
Cc: Jeff King, Phillip Wood, Karthik Nayak, christian.couder, git, ps
phillip.wood123@gmail.com writes:
> ALLOW_ONELEVEL just disables the check that the refname contains a '/'
> and I think it is aimed at checking branch and tag names without a
> refs/{heads,tags} prefix. If we want to move away from using
> refname_is_safe() perhaps we could add an ALLOW_PSEUDOREF flag that
> only allows the name to contain '[A-Z_]' if there is no '/'.
Makes sense.
I wonder if we eventually can get rid of ALLOW_ONELEVEL, though. If
all callers that use ALLOW_ONELEVEL know under which prefix they
plan to hang the refname they are checking (if the refname passed
the check), we can force the check to be performed always on the
full refname, and it will become easier to make the check more
consistent---as the check will have full context information.
For example, with ALLOW_ONELEVEL the check may say "HEAD" is OK, but
if we get rid of ALLOW_ONELEVEL and force the callers to always test
the full refname, we may say "refs/heads/HEAD" is not acceptable,
neither is "refs/tags/HEAD", but "refs/remotes/origin/HEAD" is good.
^ permalink raw reply [flat|nested] 194+ messages in thread
* Re: [PATCH v4 1/7] refs: accept symref values in `ref_transaction[_add]_update`
2024-04-29 16:18 ` Junio C Hamano
@ 2024-04-30 10:33 ` Jeff King
0 siblings, 0 replies; 194+ messages in thread
From: Jeff King @ 2024-04-30 10:33 UTC (permalink / raw)
To: Junio C Hamano
Cc: phillip.wood123, Phillip Wood, Karthik Nayak, christian.couder, git, ps
On Mon, Apr 29, 2024 at 09:18:47AM -0700, Junio C Hamano wrote:
> phillip.wood123@gmail.com writes:
>
> > ALLOW_ONELEVEL just disables the check that the refname contains a '/'
> > and I think it is aimed at checking branch and tag names without a
> > refs/{heads,tags} prefix. If we want to move away from using
> > refname_is_safe() perhaps we could add an ALLOW_PSEUDOREF flag that
> > only allows the name to contain '[A-Z_]' if there is no '/'.
>
> Makes sense.
>
> I wonder if we eventually can get rid of ALLOW_ONELEVEL, though. If
> all callers that use ALLOW_ONELEVEL know under which prefix they
> plan to hang the refname they are checking (if the refname passed
> the check), we can force the check to be performed always on the
> full refname, and it will become easier to make the check more
> consistent---as the check will have full context information.
>
> For example, with ALLOW_ONELEVEL the check may say "HEAD" is OK, but
> if we get rid of ALLOW_ONELEVEL and force the callers to always test
> the full refname, we may say "refs/heads/HEAD" is not acceptable,
> neither is "refs/tags/HEAD", but "refs/remotes/origin/HEAD" is good.
One case I ran into while working on my series is refspec parsing, where
we feed the left and right halves to check_refname_format(), albeit with
a special flag that allows the "*" wildcard character. And there we are
OK with one-level names because they will be passed through the
dwim_ref() lookup.
I don't know if there are other spots that do something similar. Most of
them would, I imagine, just take any input and leave it to the ref code
to enforce syntax after the dwim-ref has happened.
-Peff
^ permalink raw reply [flat|nested] 194+ messages in thread
* Re: [PATCH v4 1/7] refs: accept symref values in `ref_transaction[_add]_update`
2024-04-29 9:32 ` phillip.wood123
2024-04-29 16:18 ` Junio C Hamano
@ 2024-04-30 10:30 ` Jeff King
1 sibling, 0 replies; 194+ messages in thread
From: Jeff King @ 2024-04-30 10:30 UTC (permalink / raw)
To: phillip.wood; +Cc: Junio C Hamano, Karthik Nayak, christian.couder, git, ps
On Mon, Apr 29, 2024 at 10:32:58AM +0100, phillip.wood123@gmail.com wrote:
> On 26/04/2024 22:15, Jeff King wrote:
> > On Fri, Apr 26, 2024 at 12:31:36PM -0700, Junio C Hamano wrote:
> >
> > But the more interesting one is a call to refname_is_safe(), added
> > recently by Phillip's 1dbe401563 (show-ref --verify: accept pseudorefs,
> > 2024-02-07). Looking at that commit, the intent was to allow pseudo-refs
> > by loosening the conditional that checked "HEAD" to allow "FOO_BAR" but
> > not "foobar" outside of "refs/". We enforce the all-caps pseudoref
> > syntax in is_refname_safe().
> >
> > The proper API there is I think check_ref_format() with ALLOW_ONELEVEL.
>
> ALLOW_ONELEVEL just disables the check that the refname contains a '/' and I
> think it is aimed at checking branch and tag names without a
> refs/{heads,tags} prefix. If we want to move away from using
> refname_is_safe() perhaps we could add an ALLOW_PSEUDOREF flag that only
> allows the name to contain '[A-Z_]' if there is no '/'.
Right, I think that was the original reason for ALLOW_ONELEVEL. But then
lots of callers ended up using it because it was the only way to allow
"HEAD" or "FETCH_HEAD" to work alongside regular refs.
See the series I posted elsewhere which adds a new flag to cover that
case.
-Peff
^ permalink raw reply [flat|nested] 194+ messages in thread
* Re: [PATCH v4 1/7] refs: accept symref values in `ref_transaction[_add]_update`
2024-04-26 19:31 ` Junio C Hamano
2024-04-26 21:15 ` Jeff King
@ 2024-04-28 19:36 ` Karthik Nayak
1 sibling, 0 replies; 194+ messages in thread
From: Karthik Nayak @ 2024-04-28 19:36 UTC (permalink / raw)
To: Junio C Hamano; +Cc: christian.couder, git, ps
[-- Attachment #1: Type: text/plain, Size: 689 bytes --]
Junio C Hamano <gitster@pobox.com> writes:
> Karthik Nayak <karthik.188@gmail.com> writes:
>
>> From: Karthik Nayak <karthik.188@gmail.com>
>> Subject: Re: [PATCH v4 1/7] refs: accept symref values in `ref_transaction[_add]_update`
>>
>> The `ref_transaction[_add]_update` functions obtain ref information and
>> flags to create a `ref_update` and add it to the transaction at hand.
>
> Just a very minor irritation, but ref_transaction_add_update() is a
> function used internally in the ref subsystem and is exported only
> because its visibility needs to cross file boundaries between refs.c
> and refs/*backend.c files.
Yes that is true. I'll amend this for the next version. Thanks
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 194+ messages in thread
* Re: [PATCH v4 1/7] refs: accept symref values in `ref_transaction[_add]_update`
2024-04-26 15:24 ` [PATCH v4 1/7] refs: accept symref values in `ref_transaction[_add]_update` Karthik Nayak
2024-04-26 19:31 ` Junio C Hamano
@ 2024-04-29 13:38 ` Phillip Wood
2024-04-29 14:01 ` Karthik Nayak
1 sibling, 1 reply; 194+ messages in thread
From: Phillip Wood @ 2024-04-29 13:38 UTC (permalink / raw)
To: Karthik Nayak; +Cc: christian.couder, git, gitster, ps
Hi Karthik
On 26/04/2024 16:24, Karthik Nayak wrote:
> From: Karthik Nayak <karthik.188@gmail.com>
>
> The `ref_transaction[_add]_update` functions obtain ref information and
> flags to create a `ref_update` and add it to the transaction at hand.
>
> To extend symref support in transactions, we need to also accept the
> old and new ref targets and process it.
s/it/them/
> In this commit, let's add the
This commit adds?
> required parameters to the function and modify all call sites.
>
> The two parameters added are `new_target` and `old_target`. The
> `new_target` is used to denote what the reference should point to when
> the transaction is applied.
>
> The `old_target` denotes the value the reference must have before the
> update. Some functions allow this parameter to be NULL, meaning that the
> old value of the reference is not checked.
>
> The handling logic of these parameters will be added in consequent
> commits as we add symref commands to the '--stdin' mode of
> 'git-update-ref'.
>
> Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
Thanks for updating the documentation, I've left a couple of comments below
> diff --git a/refs.h b/refs.h
> index d278775e08..c792e13a64 100644
> --- a/refs.h
> +++ b/refs.h
> @@ -648,6 +648,15 @@ struct ref_transaction *ref_transaction_begin(struct strbuf *err);
> * before the update. A copy of this value is made in the
> * transaction.
> *
> + * new_target -- the target reference that the reference will be
> + * update to point to.
s/update/updated/
> This takes precedence over new_oid when set.
I thought it was a bug to set both new_oid and new_target.
> If the reference is a regular reference, it will be
> + * converted to a symbolic reference.
> + *
> + * old_target -- the reference that the reference must be pointing to.
> + * Will only be taken into account when the reference is a symbolic
> + * reference.
Does this last sentence mean it is not possible to assert that it is
currently a symbolic reference? I thought the point of being able to
specify the old value of a ref when updating was to ensure it hadn't
changed since it was read. This contradicts the documentation in the
next hunk and the description in the commit message.
> * flags -- flags affecting the update, passed to
> * update_ref_lock(). Possible flags: REF_NO_DEREF,
> * REF_FORCE_CREATE_REFLOG. See those constants for more
> @@ -713,7 +722,11 @@ struct ref_transaction *ref_transaction_begin(struct strbuf *err);
> * beforehand. The old value is checked after the lock is taken to
> * prevent races. If the old value doesn't agree with old_oid, the
> * whole transaction fails. If old_oid is NULL, then the previous
> - * value is not checked.
> + * value is not checked. If `old_target` is not NULL, treat the reference
> + * as a symbolic ref and validate that its target before the update is
> + * `old_target`. If the `new_target` is not NULL, then the reference
> + * will be updated to a symbolic ref which targets `new_target`.
This looks good and describes the behavior I'd expected to see.
Best Wishes
Phillip
^ permalink raw reply [flat|nested] 194+ messages in thread
* Re: [PATCH v4 1/7] refs: accept symref values in `ref_transaction[_add]_update`
2024-04-29 13:38 ` Phillip Wood
@ 2024-04-29 14:01 ` Karthik Nayak
0 siblings, 0 replies; 194+ messages in thread
From: Karthik Nayak @ 2024-04-29 14:01 UTC (permalink / raw)
To: phillip.wood; +Cc: christian.couder, git, gitster, ps
[-- Attachment #1: Type: text/plain, Size: 2753 bytes --]
Phillip Wood <phillip.wood123@gmail.com> writes:
> Hi Karthik
>
> On 26/04/2024 16:24, Karthik Nayak wrote:
>> From: Karthik Nayak <karthik.188@gmail.com>
>>
>> The `ref_transaction[_add]_update` functions obtain ref information and
>> flags to create a `ref_update` and add it to the transaction at hand.
>>
>> To extend symref support in transactions, we need to also accept the
>> old and new ref targets and process it.
>
> s/it/them/
>
>> In this commit, let's add the
>
> This commit adds?
>
Thanks, will add both the above.
>> required parameters to the function and modify all call sites.
>>
>> The two parameters added are `new_target` and `old_target`. The
>> `new_target` is used to denote what the reference should point to when
>> the transaction is applied.
>>
>> The `old_target` denotes the value the reference must have before the
>> update. Some functions allow this parameter to be NULL, meaning that the
>> old value of the reference is not checked.
>>
>> The handling logic of these parameters will be added in consequent
>> commits as we add symref commands to the '--stdin' mode of
>> 'git-update-ref'.
>>
>> Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
>
> Thanks for updating the documentation, I've left a couple of comments below
>
Thanks for the review.
>> diff --git a/refs.h b/refs.h
>> index d278775e08..c792e13a64 100644
>> --- a/refs.h
>> +++ b/refs.h
>> @@ -648,6 +648,15 @@ struct ref_transaction *ref_transaction_begin(struct strbuf *err);
>> * before the update. A copy of this value is made in the
>> * transaction.
>> *
>> + * new_target -- the target reference that the reference will be
>> + * update to point to.
>
> s/update/updated/
>
>> This takes precedence over new_oid when set.
>
> I thought it was a bug to set both new_oid and new_target.
>
Yup. fixed both of these.
>
>> If the reference is a regular reference, it will be
>> + * converted to a symbolic reference.
> > + *
>> + * old_target -- the reference that the reference must be pointing to.
>> + * Will only be taken into account when the reference is a symbolic
>> + * reference.
>
> Does this last sentence mean it is not possible to assert that it is
> currently a symbolic reference? I thought the point of being able to
> specify the old value of a ref when updating was to ensure it hadn't
> changed since it was read. This contradicts the documentation in the
> next hunk and the description in the commit message.
>
I see how this is vague, I was trying to imply that the old_target is
used to check a symref's old_value. But actually, if this is set and the
ref is a regular ref, we do fail the check. So this is wrong. Let me
just strip the last time.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 194+ messages in thread
* [PATCH v4 2/7] files-backend: extract out `create_symref_lock`
2024-04-26 15:24 ` [PATCH v4 0/7] add symref-* commands to 'git-update-ref --stdin' Karthik Nayak
2024-04-26 15:24 ` [PATCH v4 1/7] refs: accept symref values in `ref_transaction[_add]_update` Karthik Nayak
@ 2024-04-26 15:24 ` Karthik Nayak
2024-04-26 21:39 ` Junio C Hamano
2024-04-26 15:24 ` [PATCH v4 3/7] update-ref: add support for 'symref-verify' command Karthik Nayak
` (6 subsequent siblings)
8 siblings, 1 reply; 194+ messages in thread
From: Karthik Nayak @ 2024-04-26 15:24 UTC (permalink / raw)
To: karthik.188; +Cc: christian.couder, git, gitster, ps
From: Karthik Nayak <karthik.188@gmail.com>
The function `create_symref_locked` creates a symref by creating a
'<symref>.lock' file and then committing the symref lock, which creates
the final symref.
Split this into two individual functions `create_and_commit_symref` and
`create_symref_locked`. This way we can create the symref lock and
commit it at different times. This will be used to provide symref
support in `git-update-ref(1)`.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
refs/files-backend.c | 40 +++++++++++++++++++++++++++-------------
1 file changed, 27 insertions(+), 13 deletions(-)
diff --git a/refs/files-backend.c b/refs/files-backend.c
index e4d0aa3d41..2420dac2aa 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -1920,26 +1920,39 @@ static void update_symref_reflog(struct files_ref_store *refs,
}
}
-static int create_symref_locked(struct files_ref_store *refs,
- struct ref_lock *lock, const char *refname,
- const char *target, const char *logmsg)
+static int create_symref_lock(struct files_ref_store *refs,
+ struct ref_lock *lock, const char *refname,
+ const char *target)
{
+ if (!fdopen_lock_file(&lock->lk, "w"))
+ return error("unable to fdopen %s: %s",
+ get_lock_file_path(&lock->lk), strerror(errno));
+
+ /* no error check; commit_ref will check ferror */
+ fprintf(get_lock_file_fp(&lock->lk), "ref: %s\n", target);
+ return 0;
+}
+
+static int create_and_commit_symref(struct files_ref_store *refs,
+ struct ref_lock *lock, const char *refname,
+ const char *target, const char *logmsg)
+{
+ int ret;
+
if (prefer_symlink_refs && !create_ref_symlink(lock, target)) {
update_symref_reflog(refs, lock, refname, target, logmsg);
return 0;
}
- if (!fdopen_lock_file(&lock->lk, "w"))
- return error("unable to fdopen %s: %s",
- get_lock_file_path(&lock->lk), strerror(errno));
+ ret = create_symref_lock(refs, lock, refname, target);
+ if (!ret) {
+ update_symref_reflog(refs, lock, refname, target, logmsg);
- update_symref_reflog(refs, lock, refname, target, logmsg);
+ if (commit_ref(lock) < 0)
+ return error("unable to write symref for %s: %s", refname,
+ strerror(errno));
+ }
- /* no error check; commit_ref will check ferror */
- fprintf(get_lock_file_fp(&lock->lk), "ref: %s\n", target);
- if (commit_ref(lock) < 0)
- return error("unable to write symref for %s: %s", refname,
- strerror(errno));
return 0;
}
@@ -1960,7 +1973,8 @@ static int files_create_symref(struct ref_store *ref_store,
return -1;
}
- ret = create_symref_locked(refs, lock, refname, target, logmsg);
+ ret = create_and_commit_symref(refs, lock, refname, target, logmsg);
+
unlock_ref(lock);
return ret;
}
--
2.43.GIT
^ permalink raw reply related [flat|nested] 194+ messages in thread
* Re: [PATCH v4 2/7] files-backend: extract out `create_symref_lock`
2024-04-26 15:24 ` [PATCH v4 2/7] files-backend: extract out `create_symref_lock` Karthik Nayak
@ 2024-04-26 21:39 ` Junio C Hamano
2024-04-28 19:57 ` Karthik Nayak
0 siblings, 1 reply; 194+ messages in thread
From: Junio C Hamano @ 2024-04-26 21:39 UTC (permalink / raw)
To: Karthik Nayak; +Cc: christian.couder, git, ps
Karthik Nayak <karthik.188@gmail.com> writes:
> From: Karthik Nayak <karthik.188@gmail.com>
>
> The function `create_symref_locked` creates a symref by creating a
> '<symref>.lock' file and then committing the symref lock, which creates
> the final symref.
>
> Split this into two individual functions `create_and_commit_symref` and
> `create_symref_locked`. This way we can create the symref lock and
> commit it at different times. This will be used to provide symref
> support in `git-update-ref(1)`.
It is a confusing way to describe what this patch did, though. If
you truly splitted create_symref_locked() into two, you would have
functions A and B, and existing callers of create_symref_locked()
would be changed to call A() and then B(). I do not think such a
split would make sense in this case, but the above description gives
an impression that it was what you did.
In reality, an early part of create_symref_locked() was made into a
separate helper function that can be called from callers other than
create_symref_locked(), and because the helper got a name too
similar to the original, you had to rename create_symref_locked() to
create_and_commit_symref(). The existing callers of it are not
affected, modulo the name change.
Perhaps
Split the early half of create_symref_locked() into a new helper
funciton create_symref_lock(). Because the name of the new
function is too similar to the original, rename the original to
create_and_commit_symref() to avoid confusion.
The new helper will be used to ...
or something?
> -static int create_symref_locked(struct files_ref_store *refs,
> - struct ref_lock *lock, const char *refname,
> - const char *target, const char *logmsg)
> +static int create_symref_lock(struct files_ref_store *refs,
> + struct ref_lock *lock, const char *refname,
> + const char *target)
> {
> + if (!fdopen_lock_file(&lock->lk, "w"))
> + return error("unable to fdopen %s: %s",
> + get_lock_file_path(&lock->lk), strerror(errno));
> +
> + /* no error check; commit_ref will check ferror */
> + fprintf(get_lock_file_fp(&lock->lk), "ref: %s\n", target);
This was a bit puzzling (see below).
> + return 0;
> +}
> +
> +static int create_and_commit_symref(struct files_ref_store *refs,
> + struct ref_lock *lock, const char *refname,
> + const char *target, const char *logmsg)
> +{
> + int ret;
> +
> if (prefer_symlink_refs && !create_ref_symlink(lock, target)) {
> update_symref_reflog(refs, lock, refname, target, logmsg);
> return 0;
> }
Offtopic: we might want to start planning to deprecate creation
of "symlink refs". Linus originally used a symlink for
.git/HEAD, but 9f0bb90d (core.prefersymlinkrefs: use symlinks
for .git/HEAD, 2006-05-02) made it default not to use of
symbolic links. As long as we preserve the ability to work on a
repository whose HEAD still uses a symbolic link, I'd hope
nothing would break (#leftoverbits).
Let me rearrange this hunk to show the original first:
> - if (!fdopen_lock_file(&lock->lk, "w"))
> - return error("unable to fdopen %s: %s",
> - get_lock_file_path(&lock->lk), strerror(errno));
> - update_symref_reflog(refs, lock, refname, target, logmsg);
> - /* no error check; commit_ref will check ferror */
> - fprintf(get_lock_file_fp(&lock->lk), "ref: %s\n", target);
> - if (commit_ref(lock) < 0)
> - return error("unable to write symref for %s: %s", refname,
> - strerror(errno));
The original in create_symref_locked() created a lockfile, called
update_symref_reflog(), and called commit_ref() to commit the thing.
The "no error check" comment is about detecting an error while
writing into the lock file. It came from 370e5ad6 (create_symref:
use existing ref-lock code, 2015-12-29). Because the fprintf() call
was immediately followed by commit_ref(), and the code assumed that
commit_ref() will check ferror(), we do not bother checking if the
fprintf() call fails to write the contents correctly.
> + ret = create_symref_lock(refs, lock, refname, target);
> + if (!ret) {
> + update_symref_reflog(refs, lock, refname, target, logmsg);
>
> + if (commit_ref(lock) < 0)
> + return error("unable to write symref for %s: %s", refname,
> + strerror(errno));
> + }
The new code lets create_symref_lock() to create a lockfile, and
does the rest here. commit_ref() does call commit_lock_file(),
which eventually passes the control to close_tempfile() and a
write error can be detected there.
But the point of this patch is that the creation of the locked
symref file PLUS writing its new contents (which is done by
create_symref_lock()) can be done way ahead of the remainder that
eventually does commit_ref(). So it smells a bit[*] dubious that we
still leave the error from fprintf() ignored in the "early half" in
the rearranged code.
Side note: it is a "bit", as it is unlikely that we will do
something to clear the ferror() from the (FILE *) in the
meantime.
> @@ -1960,7 +1973,8 @@ static int files_create_symref(struct ref_store *ref_store,
> return -1;
> }
>
> - ret = create_symref_locked(refs, lock, refname, target, logmsg);
> + ret = create_and_commit_symref(refs, lock, refname, target, logmsg);
> +
> unlock_ref(lock);
> return ret;
> }
This hunk shows the "original function was renamed; there is no
other changes visible to the caller" nature of this rearrangement.
The extra blank line is probably a nice touch.
^ permalink raw reply [flat|nested] 194+ messages in thread
* Re: [PATCH v4 2/7] files-backend: extract out `create_symref_lock`
2024-04-26 21:39 ` Junio C Hamano
@ 2024-04-28 19:57 ` Karthik Nayak
0 siblings, 0 replies; 194+ messages in thread
From: Karthik Nayak @ 2024-04-28 19:57 UTC (permalink / raw)
To: Junio C Hamano; +Cc: christian.couder, git, ps
[-- Attachment #1: Type: text/plain, Size: 6127 bytes --]
Junio C Hamano <gitster@pobox.com> writes:
> Karthik Nayak <karthik.188@gmail.com> writes:
>
>> From: Karthik Nayak <karthik.188@gmail.com>
>>
>> The function `create_symref_locked` creates a symref by creating a
>> '<symref>.lock' file and then committing the symref lock, which creates
>> the final symref.
>>
>> Split this into two individual functions `create_and_commit_symref` and
>> `create_symref_locked`. This way we can create the symref lock and
>> commit it at different times. This will be used to provide symref
>> support in `git-update-ref(1)`.
>
> It is a confusing way to describe what this patch did, though. If
> you truly splitted create_symref_locked() into two, you would have
> functions A and B, and existing callers of create_symref_locked()
> would be changed to call A() and then B(). I do not think such a
> split would make sense in this case, but the above description gives
> an impression that it was what you did.
>
> In reality, an early part of create_symref_locked() was made into a
> separate helper function that can be called from callers other than
> create_symref_locked(), and because the helper got a name too
> similar to the original, you had to rename create_symref_locked() to
> create_and_commit_symref(). The existing callers of it are not
> affected, modulo the name change.
>
> Perhaps
>
> Split the early half of create_symref_locked() into a new helper
> funciton create_symref_lock(). Because the name of the new
> function is too similar to the original, rename the original to
> create_and_commit_symref() to avoid confusion.
>
> The new helper will be used to ...
>
> or something?
>
Thanks. I agree with what you're saying. I would also s/Split/Extract
perhaps because it drives the point better.
>> -static int create_symref_locked(struct files_ref_store *refs,
>> - struct ref_lock *lock, const char *refname,
>> - const char *target, const char *logmsg)
>> +static int create_symref_lock(struct files_ref_store *refs,
>> + struct ref_lock *lock, const char *refname,
>> + const char *target)
>> {
>> + if (!fdopen_lock_file(&lock->lk, "w"))
>> + return error("unable to fdopen %s: %s",
>> + get_lock_file_path(&lock->lk), strerror(errno));
>> +
>> + /* no error check; commit_ref will check ferror */
>> + fprintf(get_lock_file_fp(&lock->lk), "ref: %s\n", target);
>
> This was a bit puzzling (see below).
>
>> + return 0;
>> +}
>> +
>> +static int create_and_commit_symref(struct files_ref_store *refs,
>> + struct ref_lock *lock, const char *refname,
>> + const char *target, const char *logmsg)
>> +{
>> + int ret;
>> +
>> if (prefer_symlink_refs && !create_ref_symlink(lock, target)) {
>> update_symref_reflog(refs, lock, refname, target, logmsg);
>> return 0;
>> }
>
> Offtopic: we might want to start planning to deprecate creation
> of "symlink refs". Linus originally used a symlink for
> .git/HEAD, but 9f0bb90d (core.prefersymlinkrefs: use symlinks
> for .git/HEAD, 2006-05-02) made it default not to use of
> symbolic links. As long as we preserve the ability to work on a
> repository whose HEAD still uses a symbolic link, I'd hope
> nothing would break (#leftoverbits).
>
> Let me rearrange this hunk to show the original first:
>
>> - if (!fdopen_lock_file(&lock->lk, "w"))
>> - return error("unable to fdopen %s: %s",
>> - get_lock_file_path(&lock->lk), strerror(errno));
>> - update_symref_reflog(refs, lock, refname, target, logmsg);
>> - /* no error check; commit_ref will check ferror */
>> - fprintf(get_lock_file_fp(&lock->lk), "ref: %s\n", target);
>> - if (commit_ref(lock) < 0)
>> - return error("unable to write symref for %s: %s", refname,
>> - strerror(errno));
>
> The original in create_symref_locked() created a lockfile, called
> update_symref_reflog(), and called commit_ref() to commit the thing.
>
> The "no error check" comment is about detecting an error while
> writing into the lock file. It came from 370e5ad6 (create_symref:
> use existing ref-lock code, 2015-12-29). Because the fprintf() call
> was immediately followed by commit_ref(), and the code assumed that
> commit_ref() will check ferror(), we do not bother checking if the
> fprintf() call fails to write the contents correctly.
>
>> + ret = create_symref_lock(refs, lock, refname, target);
>> + if (!ret) {
>> + update_symref_reflog(refs, lock, refname, target, logmsg);
>>
>> + if (commit_ref(lock) < 0)
>> + return error("unable to write symref for %s: %s", refname,
>> + strerror(errno));
>> + }
>
> The new code lets create_symref_lock() to create a lockfile, and
> does the rest here. commit_ref() does call commit_lock_file(),
> which eventually passes the control to close_tempfile() and a
> write error can be detected there.
>
> But the point of this patch is that the creation of the locked
> symref file PLUS writing its new contents (which is done by
> create_symref_lock()) can be done way ahead of the remainder that
> eventually does commit_ref(). So it smells a bit[*] dubious that we
> still leave the error from fprintf() ignored in the "early half" in
> the rearranged code.
>
> Side note: it is a "bit", as it is unlikely that we will do
> something to clear the ferror() from the (FILE *) in the
> meantime.
>
You're right. I would say that perhaps it is a bit more than a 'bit
dubious' since `commit_ref()` being called after, is no longer a
guarantee. I'll go ahead and add error handling here.
>> @@ -1960,7 +1973,8 @@ static int files_create_symref(struct ref_store *ref_store,
>> return -1;
>> }
>>
>> - ret = create_symref_locked(refs, lock, refname, target, logmsg);
>> + ret = create_and_commit_symref(refs, lock, refname, target, logmsg);
>> +
>> unlock_ref(lock);
>> return ret;
>> }
>
> This hunk shows the "original function was renamed; there is no
> other changes visible to the caller" nature of this rearrangement.
>
> The extra blank line is probably a nice touch.
Thanks. I'm sure it's not the best idea to introduce whitespace, but this
felt more readable here.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 194+ messages in thread
* [PATCH v4 3/7] update-ref: add support for 'symref-verify' command
2024-04-26 15:24 ` [PATCH v4 0/7] add symref-* commands to 'git-update-ref --stdin' Karthik Nayak
2024-04-26 15:24 ` [PATCH v4 1/7] refs: accept symref values in `ref_transaction[_add]_update` Karthik Nayak
2024-04-26 15:24 ` [PATCH v4 2/7] files-backend: extract out `create_symref_lock` Karthik Nayak
@ 2024-04-26 15:24 ` Karthik Nayak
2024-04-26 22:51 ` Junio C Hamano
2024-04-26 15:24 ` [PATCH v4 4/7] update-ref: add support for 'symref-delete' command Karthik Nayak
` (5 subsequent siblings)
8 siblings, 1 reply; 194+ messages in thread
From: Karthik Nayak @ 2024-04-26 15:24 UTC (permalink / raw)
To: karthik.188; +Cc: christian.couder, git, gitster, ps
From: Karthik Nayak <karthik.188@gmail.com>
In the previous commits, we added the required base for adding symref
commands to the '--stdin' mode provided by 'git-update-ref(1)'. Using
them, add a new 'symref-verify' command to verify symrefs.
The 'symref-verify' command allows users to verify if a provided <ref>
contains the provided <old-target> without changing the <ref>. If
<old-target> is not provided, the command will verify that the <ref>
doesn't exist. Since we're checking for symbolic refs, this command will
only work with the 'no-deref' mode. This is because any dereferenced
symbolic ref will point to an object and not a ref and the regular
'verify' command can be used in such situations.
Add and use `ref_update_is_null_new_value`, a helper function which is
used to check if there is a new_value in a reference update. The new
value could either be a symref target `new_target` or a OID `new_oid`.
We also add tests to test the command in both the regular stdin mode and
also with the '-z' flag.
We also disable the reference-transaction hook for symref-updates which
will be tackled in its own commit.
Add required tests for symref support in 'verify' while also adding
reflog checks for the pre-existing 'verify' tests.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
Documentation/git-update-ref.txt | 7 +++
builtin/update-ref.c | 80 +++++++++++++++++++++++----
refs.c | 30 +++++++++--
refs.h | 1 +
refs/files-backend.c | 43 +++++++++++++++
refs/refs-internal.h | 7 +++
refs/reftable-backend.c | 21 +++++++-
t/t1400-update-ref.sh | 93 +++++++++++++++++++++++++++++++-
8 files changed, 264 insertions(+), 18 deletions(-)
diff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt
index 374a2ebd2b..9fe78b3501 100644
--- a/Documentation/git-update-ref.txt
+++ b/Documentation/git-update-ref.txt
@@ -65,6 +65,7 @@ performs all modifications together. Specify commands of the form:
create SP <ref> SP <new-oid> LF
delete SP <ref> [SP <old-oid>] LF
verify SP <ref> [SP <old-oid>] LF
+ symref-verify SP <ref> [SP <old-target>] LF
option SP <opt> LF
start LF
prepare LF
@@ -86,6 +87,7 @@ quoting:
create SP <ref> NUL <new-oid> NUL
delete SP <ref> NUL [<old-oid>] NUL
verify SP <ref> NUL [<old-oid>] NUL
+ symref-verify SP <ref> [NUL <old-target>] NUL
option SP <opt> NUL
start NUL
prepare NUL
@@ -117,6 +119,11 @@ verify::
Verify <ref> against <old-oid> but do not change it. If
<old-oid> is zero or missing, the ref must not exist.
+symref-verify::
+ Verify symbolic <ref> against <old-target> but do not change it.
+ If <old-target> is missing, the ref must not exist. Can only be
+ used in `no-deref` mode.
+
option::
Modify the behavior of the next command naming a <ref>.
The only valid option is `no-deref` to avoid dereferencing
diff --git a/builtin/update-ref.c b/builtin/update-ref.c
index 21fdbf6ac8..419b28169b 100644
--- a/builtin/update-ref.c
+++ b/builtin/update-ref.c
@@ -76,6 +76,29 @@ static char *parse_refname(const char **next)
return strbuf_detach(&ref, NULL);
}
+/*
+ * Wrapper around parse_refname which skips the next delimiter.
+ */
+static char *parse_next_refname(const char **next)
+{
+ if (line_termination) {
+ /* Without -z, consume SP and use next argument */
+ if (!**next || **next == line_termination)
+ return NULL;
+ if (**next != ' ')
+ die("expected SP but got: %s", *next);
+ } else {
+ /* With -z, read the next NUL-terminated line */
+ if (**next)
+ return NULL;
+ }
+ /* Skip the delimiter */
+ (*next)++;
+
+ return parse_refname(next);
+}
+
+
/*
* The value being parsed is <old-oid> (as opposed to <new-oid>; the
* difference affects which error messages are generated):
@@ -297,11 +320,47 @@ static void parse_cmd_verify(struct ref_transaction *transaction,
die("verify %s: extra input: %s", refname, next);
if (ref_transaction_verify(transaction, refname, &old_oid,
- update_flags, &err))
+ NULL, update_flags, &err))
+ die("%s", err.buf);
+
+ update_flags = default_flags;
+ free(refname);
+ strbuf_release(&err);
+}
+
+static void parse_cmd_symref_verify(struct ref_transaction *transaction,
+ const char *next, const char *end)
+{
+ struct strbuf err = STRBUF_INIT;
+ struct object_id old_oid;
+ char *refname, *old_target;
+
+ if (!(update_flags & REF_NO_DEREF))
+ die("symref-verify: cannot operate with deref mode");
+
+ refname = parse_refname(&next);
+ if (!refname)
+ die("symref-verify: missing <ref>");
+
+ /*
+ * old_ref is optional, but we want to differentiate between
+ * a NULL and zero value.
+ */
+ old_target = parse_next_refname(&next);
+ if (!old_target)
+ old_oid = *null_oid();
+
+ if (*next != line_termination)
+ die("symref-verify %s: extra input: %s", refname, next);
+
+ if (ref_transaction_verify(transaction, refname,
+ old_target ? NULL : &old_oid,
+ old_target, update_flags, &err))
die("%s", err.buf);
update_flags = default_flags;
free(refname);
+ free(old_target);
strbuf_release(&err);
}
@@ -380,15 +439,16 @@ static const struct parse_cmd {
unsigned args;
enum update_refs_state state;
} command[] = {
- { "update", parse_cmd_update, 3, UPDATE_REFS_OPEN },
- { "create", parse_cmd_create, 2, UPDATE_REFS_OPEN },
- { "delete", parse_cmd_delete, 2, UPDATE_REFS_OPEN },
- { "verify", parse_cmd_verify, 2, UPDATE_REFS_OPEN },
- { "option", parse_cmd_option, 1, UPDATE_REFS_OPEN },
- { "start", parse_cmd_start, 0, UPDATE_REFS_STARTED },
- { "prepare", parse_cmd_prepare, 0, UPDATE_REFS_PREPARED },
- { "abort", parse_cmd_abort, 0, UPDATE_REFS_CLOSED },
- { "commit", parse_cmd_commit, 0, UPDATE_REFS_CLOSED },
+ { "update", parse_cmd_update, 3, UPDATE_REFS_OPEN },
+ { "create", parse_cmd_create, 2, UPDATE_REFS_OPEN },
+ { "delete", parse_cmd_delete, 2, UPDATE_REFS_OPEN },
+ { "verify", parse_cmd_verify, 2, UPDATE_REFS_OPEN },
+ { "symref-verify", parse_cmd_symref_verify, 2, UPDATE_REFS_OPEN },
+ { "option", parse_cmd_option, 1, UPDATE_REFS_OPEN },
+ { "start", parse_cmd_start, 0, UPDATE_REFS_STARTED },
+ { "prepare", parse_cmd_prepare, 0, UPDATE_REFS_PREPARED },
+ { "abort", parse_cmd_abort, 0, UPDATE_REFS_CLOSED },
+ { "commit", parse_cmd_commit, 0, UPDATE_REFS_CLOSED },
};
static void update_refs_stdin(void)
diff --git a/refs.c b/refs.c
index 060a31616d..0e1013b5ab 100644
--- a/refs.c
+++ b/refs.c
@@ -1217,6 +1217,8 @@ void ref_transaction_free(struct ref_transaction *transaction)
for (i = 0; i < transaction->nr; i++) {
free(transaction->updates[i]->msg);
+ free((void *)transaction->updates[i]->old_target);
+ free((void *)transaction->updates[i]->new_target);
free(transaction->updates[i]);
}
free(transaction->updates);
@@ -1247,9 +1249,13 @@ struct ref_update *ref_transaction_add_update(
update->flags = flags;
- if (flags & REF_HAVE_NEW)
+ if (new_target)
+ update->new_target = xstrdup(new_target);
+ if (old_target)
+ update->old_target = xstrdup(old_target);
+ if (new_oid && flags & REF_HAVE_NEW)
oidcpy(&update->new_oid, new_oid);
- if (flags & REF_HAVE_OLD)
+ if (old_oid && flags & REF_HAVE_OLD)
oidcpy(&update->old_oid, old_oid);
update->msg = normalize_reflog_message(msg);
return update;
@@ -1286,6 +1292,7 @@ int ref_transaction_update(struct ref_transaction *transaction,
flags &= REF_TRANSACTION_UPDATE_ALLOWED_FLAGS;
flags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0);
+ flags |= (new_target ? REF_HAVE_NEW : 0) | (old_target ? REF_HAVE_OLD : 0);
ref_transaction_add_update(transaction, refname, flags,
new_oid, old_oid, new_target,
@@ -1325,14 +1332,17 @@ int ref_transaction_delete(struct ref_transaction *transaction,
int ref_transaction_verify(struct ref_transaction *transaction,
const char *refname,
const struct object_id *old_oid,
+ const char *old_target,
unsigned int flags,
struct strbuf *err)
{
- if (!old_oid)
- BUG("verify called with old_oid set to NULL");
+ if (!old_target && !old_oid)
+ BUG("verify called with old_oid and old_target set to NULL");
+ if (old_target && !(flags & REF_NO_DEREF))
+ BUG("verify cannot operate on symrefs with deref mode");
return ref_transaction_update(transaction, refname,
NULL, old_oid,
- NULL, NULL,
+ NULL, old_target,
flags, NULL, err);
}
@@ -2349,6 +2359,12 @@ static int run_transaction_hook(struct ref_transaction *transaction,
for (i = 0; i < transaction->nr; i++) {
struct ref_update *update = transaction->updates[i];
+ /*
+ * Skip reference transaction for symbolic refs.
+ */
+ if (update->new_target || update->old_target)
+ continue;
+
strbuf_reset(&buf);
strbuf_addf(&buf, "%s %s %s\n",
oid_to_hex(&update->old_oid),
@@ -2802,3 +2818,7 @@ int copy_existing_ref(const char *oldref, const char *newref, const char *logmsg
{
return refs_copy_existing_ref(get_main_ref_store(the_repository), oldref, newref, logmsg);
}
+
+int ref_update_is_null_new_value(struct ref_update *update) {
+ return !update->new_target && is_null_oid(&update->new_oid);
+}
diff --git a/refs.h b/refs.h
index c792e13a64..27b9aeaf54 100644
--- a/refs.h
+++ b/refs.h
@@ -780,6 +780,7 @@ int ref_transaction_delete(struct ref_transaction *transaction,
int ref_transaction_verify(struct ref_transaction *transaction,
const char *refname,
const struct object_id *old_oid,
+ const char *old_target,
unsigned int flags,
struct strbuf *err);
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 2420dac2aa..53197fa3af 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -2425,6 +2425,37 @@ static const char *original_update_refname(struct ref_update *update)
return update->refname;
}
+/*
+ * Check whether the REF_HAVE_OLD and old_target values stored in
+ * update are consistent with ref, which is the symbolic reference's
+ * current value. If everything is OK, return 0; otherwise, write an
+ * error message to err and return -1.
+ */
+static int check_old_target(struct ref_update *update, char *ref,
+ struct strbuf *err)
+{
+ if (!(update->flags & REF_HAVE_OLD) ||
+ !strcmp(update->old_target, ref))
+ return 0;
+
+ if (!strcmp(update->old_target, ""))
+ strbuf_addf(err, "cannot lock ref '%s': "
+ "reference already exists",
+ original_update_refname(update));
+ else if (!strcmp(ref, ""))
+ strbuf_addf(err, "cannot lock ref '%s': "
+ "reference is missing but expected %s",
+ original_update_refname(update),
+ update->old_target);
+ else
+ strbuf_addf(err, "cannot lock ref '%s': "
+ "is at %s but expected %s",
+ original_update_refname(update),
+ ref, update->old_target);
+
+ return -1;
+}
+
/*
* Check whether the REF_HAVE_OLD and old_oid values stored in update
* are consistent with oid, which is the reference's current value. If
@@ -2528,6 +2559,18 @@ static int lock_ref_for_update(struct files_ref_store *refs,
ret = TRANSACTION_GENERIC_ERROR;
goto out;
}
+ }
+
+ /*
+ * For symref verification, we need to check the reference value
+ * rather than the oid. If we're dealing with regular refs or we're
+ * verifying a dereferenced symref, we then check the oid.
+ */
+ if (update->old_target) {
+ if (check_old_target(update, referent.buf, err)) {
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto out;
+ }
} else if (check_old_oid(update, &lock->old_oid, err)) {
ret = TRANSACTION_GENERIC_ERROR;
goto out;
diff --git a/refs/refs-internal.h b/refs/refs-internal.h
index 3040d4797c..23e65f65e8 100644
--- a/refs/refs-internal.h
+++ b/refs/refs-internal.h
@@ -748,4 +748,11 @@ void base_ref_store_init(struct ref_store *refs, struct repository *repo,
*/
struct ref_store *maybe_debug_wrap_ref_store(const char *gitdir, struct ref_store *store);
+/*
+ * Helper function to check if the new value is null, this
+ * takes into consideration that the update could be a regular
+ * ref or a symbolic ref.
+ */
+int ref_update_is_null_new_value(struct ref_update *update);
+
#endif /* REFS_REFS_INTERNAL_H */
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index 6104471199..a2474245aa 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -938,7 +938,26 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
* individual refs. But the error messages match what the files
* backend returns, which keeps our tests happy.
*/
- if (u->flags & REF_HAVE_OLD && !oideq(¤t_oid, &u->old_oid)) {
+ if ((u->flags & REF_HAVE_OLD) && u->old_target) {
+ if (strcmp(referent.buf, u->old_target)) {
+ if (!strcmp(u->old_target, ""))
+ strbuf_addf(err, "verifying symref target: '%s': "
+ "provided target is empty",
+ original_update_refname(u));
+ else if (!strcmp(referent.buf, ""))
+ strbuf_addf(err, "verifying symref target: '%s': "
+ "reference is missing but expected %s",
+ original_update_refname(u),
+ u->old_target);
+ else
+ strbuf_addf(err, "verifying symref target: '%s': "
+ "is at %s but expected %s",
+ original_update_refname(u),
+ referent.buf, u->old_target);
+ ret = -1;
+ goto done;
+ }
+ } else if (u->flags & REF_HAVE_OLD && !oideq(¤t_oid, &u->old_oid)) {
if (is_null_oid(&u->old_oid))
strbuf_addf(err, _("cannot lock ref '%s': "
"reference already exists"),
diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh
index ec3443cc87..34b29eeac8 100755
--- a/t/t1400-update-ref.sh
+++ b/t/t1400-update-ref.sh
@@ -890,17 +890,23 @@ test_expect_success 'stdin update/create/verify combination works' '
'
test_expect_success 'stdin verify succeeds for correct value' '
+ test-tool ref-store main for-each-reflog-ent $m >before &&
git rev-parse $m >expect &&
echo "verify $m $m" >stdin &&
git update-ref --stdin <stdin &&
git rev-parse $m >actual &&
- test_cmp expect actual
+ test_cmp expect actual &&
+ test-tool ref-store main for-each-reflog-ent $m >after &&
+ test_cmp before after
'
test_expect_success 'stdin verify succeeds for missing reference' '
+ test-tool ref-store main for-each-reflog-ent $m >before &&
echo "verify refs/heads/missing $Z" >stdin &&
git update-ref --stdin <stdin &&
- test_must_fail git rev-parse --verify -q refs/heads/missing
+ test_must_fail git rev-parse --verify -q refs/heads/missing &&
+ test-tool ref-store main for-each-reflog-ent $m >after &&
+ test_cmp before after
'
test_expect_success 'stdin verify treats no value as missing' '
@@ -1641,4 +1647,87 @@ test_expect_success PIPE 'transaction flushes status updates' '
test_cmp expected actual
'
+create_stdin_buf () {
+ if test "$1" = "-z"
+ then
+ shift
+ printf "$F" "$@" >stdin
+ else
+ echo "$@" >stdin
+ fi
+}
+
+for type in "" "-z"
+do
+
+ test_expect_success "stdin ${type} symref-verify fails without --no-deref" '
+ git symbolic-ref refs/heads/symref $a &&
+ create_stdin_buf ${type} "symref-verify refs/heads/symref" "$a" &&
+ test_must_fail git update-ref --stdin ${type} <stdin 2>err &&
+ grep "fatal: symref-verify: cannot operate with deref mode" err
+ '
+
+ test_expect_success "stdin ${type} symref-verify fails with too many arguments" '
+ create_stdin_buf ${type} "symref-verify refs/heads/symref" "$a" "$a" &&
+ test_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&
+ if test "$type" = "-z"
+ then
+ grep "fatal: unknown command: $a" err
+ else
+ grep "fatal: symref-verify refs/heads/symref: extra input: $a" err
+ fi
+ '
+
+ test_expect_success "stdin ${type} symref-verify succeeds for correct value" '
+ git symbolic-ref refs/heads/symref >expect &&
+ test-tool ref-store main for-each-reflog-ent refs/heads/symref >before &&
+ create_stdin_buf ${type} "symref-verify refs/heads/symref" "$a" &&
+ git update-ref --stdin ${type} --no-deref <stdin &&
+ git symbolic-ref refs/heads/symref >actual &&
+ test_cmp expect actual &&
+ test-tool ref-store main for-each-reflog-ent refs/heads/symref >after &&
+ test_cmp before after
+ '
+
+ test_expect_success "stdin ${type} symref-verify no value is treated as zero value" '
+ git symbolic-ref refs/heads/symref >expect &&
+ create_stdin_buf ${type} "symref-verify refs/heads/symref" "" &&
+ test_must_fail git update-ref --stdin ${type} --no-deref <stdin
+ '
+
+ test_expect_success "stdin ${type} symref-verify succeeds for dangling reference" '
+ test_when_finished "git symbolic-ref -d refs/heads/symref2" &&
+ test_must_fail git symbolic-ref refs/heads/nonexistent &&
+ git symbolic-ref refs/heads/symref2 refs/heads/nonexistent &&
+ create_stdin_buf ${type} "symref-verify refs/heads/symref2" "refs/heads/nonexistent" &&
+ git update-ref --stdin ${type} --no-deref <stdin
+ '
+
+ test_expect_success "stdin ${type} symref-verify succeeds for missing reference" '
+ test-tool ref-store main for-each-reflog-ent refs/heads/symref >before &&
+ create_stdin_buf ${type} "symref-verify refs/heads/missing" "$Z" &&
+ git update-ref --stdin ${type} --no-deref <stdin &&
+ test_must_fail git rev-parse --verify -q refs/heads/missing &&
+ test-tool ref-store main for-each-reflog-ent refs/heads/symref >after &&
+ test_cmp before after
+ '
+
+ test_expect_success "stdin ${type} symref-verify fails for wrong value" '
+ git symbolic-ref refs/heads/symref >expect &&
+ create_stdin_buf ${type} "symref-verify refs/heads/symref" "$b" &&
+ test_must_fail git update-ref --stdin ${type} --no-deref <stdin &&
+ git symbolic-ref refs/heads/symref >actual &&
+ test_cmp expect actual
+ '
+
+ test_expect_success "stdin ${type} symref-verify fails for mistaken null value" '
+ git symbolic-ref refs/heads/symref >expect &&
+ create_stdin_buf ${type} "symref-verify refs/heads/symref" "$Z" &&
+ test_must_fail git update-ref --stdin ${type} --no-deref <stdin &&
+ git symbolic-ref refs/heads/symref >actual &&
+ test_cmp expect actual
+ '
+
+done
+
test_done
--
2.43.GIT
^ permalink raw reply related [flat|nested] 194+ messages in thread
* Re: [PATCH v4 3/7] update-ref: add support for 'symref-verify' command
2024-04-26 15:24 ` [PATCH v4 3/7] update-ref: add support for 'symref-verify' command Karthik Nayak
@ 2024-04-26 22:51 ` Junio C Hamano
2024-04-28 22:28 ` Karthik Nayak
0 siblings, 1 reply; 194+ messages in thread
From: Junio C Hamano @ 2024-04-26 22:51 UTC (permalink / raw)
To: Karthik Nayak; +Cc: christian.couder, git, ps
Karthik Nayak <karthik.188@gmail.com> writes:
> From: Karthik Nayak <karthik.188@gmail.com>
>
> In the previous commits, we added the required base for adding symref
> commands to the '--stdin' mode provided by 'git-update-ref(1)'. Using
> them, add a new 'symref-verify' command to verify symrefs.
>
> The 'symref-verify' command allows users to verify if a provided <ref>
> contains the provided <old-target> without changing the <ref>. If
> <old-target> is not provided, the command will verify that the <ref>
> doesn't exist. Since we're checking for symbolic refs, this command will
> only work with the 'no-deref' mode. This is because any dereferenced
> symbolic ref will point to an object and not a ref and the regular
> 'verify' command can be used in such situations.
All makes sense, but a naïve reader may find it helpful if you
explained why having "verify" command is a good idea in the first
place ("I can just do 'git symoblic-ref' to read the current value,
and see if it is what I expect"). Presumably the value of "verify"
is that you can have it in a transaction and fail other operations
in the same transaction if the symref moved from what you expected
it to point at?
> Add and use `ref_update_is_null_new_value`, a helper function which is
> used to check if there is a new_value in a reference update. The new
> value could either be a symref target `new_target` or a OID `new_oid`.
> We also add tests to test the command in both the regular stdin mode and
> also with the '-z' flag.
This looks out of place, primarily because the helper function is
*NOT* used in this step. Without any actual user, and with the name
that says only what it checks without hinting why a caller may want
to check the condition it checks, it is hard to guess if it is a
good idea to have such a helper.
"If a ref_update object specifies no new-oid and no new-target, it
is not about updating but just validating" is how the callers are
expected to use it, then instead of is_null_new_value that says
what it checks, something like is_verify_only that says what the
caller may want to use it for would be a more friendly name for
readers and future developers.
> @@ -297,11 +320,47 @@ static void parse_cmd_verify(struct ref_transaction *transaction,
> die("verify %s: extra input: %s", refname, next);
>
> if (ref_transaction_verify(transaction, refname, &old_oid,
> - update_flags, &err))
> + NULL, update_flags, &err))
>
> update_flags = default_flags;
> free(refname);
> strbuf_release(&err);
> }
The only damage by this patch to parse_cmd_verify() is that
ref_transaction_verify() gained another parameter NULL, but with the
default "--diff-algorithm=myers" algorithm, it is very hard to see.
The "--patience" algorithm does a much beter job on this hunk.
And the following function is entirely new.
> +static void parse_cmd_symref_verify(struct ref_transaction *transaction,
> + const char *next, const char *end)
> +{
> + struct strbuf err = STRBUF_INIT;
> + struct object_id old_oid;
> + char *refname, *old_target;
> +
> + if (!(update_flags & REF_NO_DEREF))
> + die("symref-verify: cannot operate with deref mode");
> +
> + refname = parse_refname(&next);
> + if (!refname)
> + die("symref-verify: missing <ref>");
> +
> + /*
> + * old_ref is optional, but we want to differentiate between
> + * a NULL and zero value.
> + */
> + old_target = parse_next_refname(&next);
> + if (!old_target)
> + old_oid = *null_oid();
In many existing code paths, we do not do structure assignment like
this. Instead we do
oidcpy(&old_oid, null_oid());
We can see an existing example in a common context in a hunk for
refs.c in this patch.
> + if (*next != line_termination)
> + die("symref-verify %s: extra input: %s", refname, next);
> +
> + if (ref_transaction_verify(transaction, refname,
> + old_target ? NULL : &old_oid,
> + old_target, update_flags, &err))
> + die("%s", err.buf);
Are static analyzers smart enough to notice that we will not be
using old_oid uninitialized here? Just wondering.
Anyway. This ensures ref_transaction_verify() gets either
old_target or old_oid, but never both at the same time. The caller
to ref_transaction_verify() in the previous function passed NULL for
old_target but it always had a non-NULL old_oid so that is perfectly
fine.
> + update_flags = default_flags;
> + free(refname);
> + free(old_target);
> + strbuf_release(&err);
> +}
> diff --git a/refs.c b/refs.c
> index 060a31616d..0e1013b5ab 100644
> --- a/refs.c
> +++ b/refs.c
> @@ -1217,6 +1217,8 @@ void ref_transaction_free(struct ref_transaction *transaction)
>
> for (i = 0; i < transaction->nr; i++) {
> free(transaction->updates[i]->msg);
> + free((void *)transaction->updates[i]->old_target);
> + free((void *)transaction->updates[i]->new_target);
> free(transaction->updates[i]);
> }
> free(transaction->updates);
> @@ -1247,9 +1249,13 @@ struct ref_update *ref_transaction_add_update(
>
> update->flags = flags;
>
> - if (flags & REF_HAVE_NEW)
> + if (new_target)
> + update->new_target = xstrdup(new_target);
> + if (old_target)
> + update->old_target = xstrdup(old_target);
Presumably "update" structure, when freshly initialized, has NULL in
both of these _target members? Otherwise ref_transaction_free()
would get in trouble, so double checking.
> + if (new_oid && flags & REF_HAVE_NEW)
> oidcpy(&update->new_oid, new_oid);
> - if (flags & REF_HAVE_OLD)
> + if (old_oid && flags & REF_HAVE_OLD)
> oidcpy(&update->old_oid, old_oid);
Since we can ask to work on a symbolic ref, new_oid / old_oid can be
NULL when REF_HAVE_NEW / REF_HAVE_OLD bit is on for _target members.
Makes me wonder if the code becomes easier to follow if the flag
bits are split into four (_NEW -> _NEW_OID + _NEW_TARGET), but let's
not worry about that for now.
> @@ -1286,6 +1292,7 @@ int ref_transaction_update(struct ref_transaction *transaction,
> flags &= REF_TRANSACTION_UPDATE_ALLOWED_FLAGS;
>
> flags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0);
> + flags |= (new_target ? REF_HAVE_NEW : 0) | (old_target ? REF_HAVE_OLD : 0);
> @@ -1325,14 +1332,17 @@ int ref_transaction_delete(struct ref_transaction *transaction,
> int ref_transaction_verify(struct ref_transaction *transaction,
> const char *refname,
> const struct object_id *old_oid,
> + const char *old_target,
> unsigned int flags,
> struct strbuf *err)
> {
> - if (!old_oid)
> - BUG("verify called with old_oid set to NULL");
> + if (!old_target && !old_oid)
> + BUG("verify called with old_oid and old_target set to NULL");
Is it normal if you get _both_ set, or is it equally a BUG()?
The parse_*_verify() codepaths we saw earlier both made sure
only one of the two is non-NULL, and it is unclear what should
happen if both are non-NULL.
> + if (old_target && !(flags & REF_NO_DEREF))
> + BUG("verify cannot operate on symrefs with deref mode");
> return ref_transaction_update(transaction, refname,
> NULL, old_oid,
> - NULL, NULL,
> + NULL, old_target,
> flags, NULL, err);
> }
So this queues an ref_update object whose .new_oid and .new_target
are NULL, and .old_oid and .old_target are what the caller gave us
to check. The NULLs in .new* members hopefully do not mean "delete
this thing" ;-)
> @@ -2349,6 +2359,12 @@ static int run_transaction_hook(struct ref_transaction *transaction,
> for (i = 0; i < transaction->nr; i++) {
> struct ref_update *update = transaction->updates[i];
>
> + /*
> + * Skip reference transaction for symbolic refs.
> + */
> + if (update->new_target || update->old_target)
> + continue;
Is that a final design, or will the hooks have a chance to interfere?
> diff --git a/refs/files-backend.c b/refs/files-backend.c
> index 2420dac2aa..53197fa3af 100644
> --- a/refs/files-backend.c
> +++ b/refs/files-backend.c
> @@ -2425,6 +2425,37 @@ static const char *original_update_refname(struct ref_update *update)
> return update->refname;
> }
>
> +/*
> + * Check whether the REF_HAVE_OLD and old_target values stored in
> + * update are consistent with ref, which is the symbolic reference's
> + * current value. If everything is OK, return 0; otherwise, write an
> + * error message to err and return -1.
> + */
> +static int check_old_target(struct ref_update *update, char *ref,
> + struct strbuf *err)
> +{
> + if (!(update->flags & REF_HAVE_OLD) ||
> + !strcmp(update->old_target, ref))
> + return 0;
Earlier on the assignment side for "update" structure we saw above,
the guard was (old_target && flags & REF_HAVE_OLD), but here we
assume old_target is valid, which feels a bit asymmetric.
Yes, I can see that the caller does not call us when !old_target,
but still... Perhaps
if ((update->flags & REF_HAVE_OLD) && !update->old_target)
BUG(...);
or something? Or alternatively, perhaps !!update->old_target should
be the only thing we should check and ignore REF_HAVE_OLD bit? I am
not sure, but it smells like that the non-NULL-ness of old_target is
the only thing that matters (if it is not NULL, very early in the
control flow somebody would have set REF_HAVE_OLD bit to flags, no?).
It brings me back to my earlier question. Does REF_HAVE_OLD bit
serve a useful purpose in this code?
> + if (!strcmp(update->old_target, ""))
> + strbuf_addf(err, "cannot lock ref '%s': "
> + "reference already exists",
> + original_update_refname(update));
> + else if (!strcmp(ref, ""))
> + strbuf_addf(err, "cannot lock ref '%s': "
> + "reference is missing but expected %s",
> + original_update_refname(update),
> + update->old_target);
So... for old_target and ref, an empty string is a special value?
How? Shouldn't that be documented in the comment before the
function?
> + else
> + strbuf_addf(err, "cannot lock ref '%s': "
> + "is at %s but expected %s",
> + original_update_refname(update),
> + ref, update->old_target);
> +
> + return -1;
> +}
> +
> /*
> * Check whether the REF_HAVE_OLD and old_oid values stored in update
> * are consistent with oid, which is the reference's current value. If
> @@ -2528,6 +2559,18 @@ static int lock_ref_for_update(struct files_ref_store *refs,
> ret = TRANSACTION_GENERIC_ERROR;
> goto out;
> }
> + }
> +
> + /*
> + * For symref verification, we need to check the reference value
> + * rather than the oid. If we're dealing with regular refs or we're
> + * verifying a dereferenced symref, we then check the oid.
> + */
> + if (update->old_target) {
> + if (check_old_target(update, referent.buf, err)) {
> + ret = TRANSACTION_GENERIC_ERROR;
> + goto out;
> + }
We come here only when update->type has REF_ISSYMREF bit on (we
learned that value by calling lock_raw_ref()), and know referent.buf
has the current "target" value. That is consumed as "ref" parameter
to check_old_target() we just saw. OK.
> diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
> index 6104471199..a2474245aa 100644
> --- a/refs/reftable-backend.c
> +++ b/refs/reftable-backend.c
> @@ -938,7 +938,26 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
> * individual refs. But the error messages match what the files
> * backend returns, which keeps our tests happy.
> */
> - if (u->flags & REF_HAVE_OLD && !oideq(¤t_oid, &u->old_oid)) {
> + if ((u->flags & REF_HAVE_OLD) && u->old_target) {
> + if (strcmp(referent.buf, u->old_target)) {
> + if (!strcmp(u->old_target, ""))
> + strbuf_addf(err, "verifying symref target: '%s': "
> + "provided target is empty",
> + original_update_refname(u));
> + else if (!strcmp(referent.buf, ""))
> + strbuf_addf(err, "verifying symref target: '%s': "
> + "reference is missing but expected %s",
> + original_update_refname(u),
> + u->old_target);
> + else
> + strbuf_addf(err, "verifying symref target: '%s': "
> + "is at %s but expected %s",
> + original_update_refname(u),
> + referent.buf, u->old_target);
> + ret = -1;
> + goto done;
> + }
Again, the puzzling "empty string"s are handled here.
^ permalink raw reply [flat|nested] 194+ messages in thread
* Re: [PATCH v4 3/7] update-ref: add support for 'symref-verify' command
2024-04-26 22:51 ` Junio C Hamano
@ 2024-04-28 22:28 ` Karthik Nayak
0 siblings, 0 replies; 194+ messages in thread
From: Karthik Nayak @ 2024-04-28 22:28 UTC (permalink / raw)
To: Junio C Hamano; +Cc: christian.couder, git, ps
[-- Attachment #1: Type: text/plain, Size: 14893 bytes --]
Junio C Hamano <gitster@pobox.com> writes:
> Karthik Nayak <karthik.188@gmail.com> writes:
>
>> From: Karthik Nayak <karthik.188@gmail.com>
>>
>> In the previous commits, we added the required base for adding symref
>> commands to the '--stdin' mode provided by 'git-update-ref(1)'. Using
>> them, add a new 'symref-verify' command to verify symrefs.
>>
>> The 'symref-verify' command allows users to verify if a provided <ref>
>> contains the provided <old-target> without changing the <ref>. If
>> <old-target> is not provided, the command will verify that the <ref>
>> doesn't exist. Since we're checking for symbolic refs, this command will
>> only work with the 'no-deref' mode. This is because any dereferenced
>> symbolic ref will point to an object and not a ref and the regular
>> 'verify' command can be used in such situations.
>
> All makes sense, but a naïve reader may find it helpful if you
> explained why having "verify" command is a good idea in the first
> place ("I can just do 'git symoblic-ref' to read the current value,
> and see if it is what I expect"). Presumably the value of "verify"
> is that you can have it in a transaction and fail other operations
> in the same transaction if the symref moved from what you expected
> it to point at?
>
I would say none of the commits drive this point, and I would go ahead
and add something on these lines to each of them. I think it would add
good value to readers.
>> Add and use `ref_update_is_null_new_value`, a helper function which is
>> used to check if there is a new_value in a reference update. The new
>> value could either be a symref target `new_target` or a OID `new_oid`.
>> We also add tests to test the command in both the regular stdin mode and
>> also with the '-z' flag.
>
> This looks out of place, primarily because the helper function is
> *NOT* used in this step. Without any actual user, and with the name
> that says only what it checks without hinting why a caller may want
> to check the condition it checks, it is hard to guess if it is a
> good idea to have such a helper.
>
I think over the revision, its usage from this commit was removed. It
makes sense to move it to a commit where its used, I'll do that.
> "If a ref_update object specifies no new-oid and no new-target, it
> is not about updating but just validating" is how the callers are
> expected to use it, then instead of is_null_new_value that says
> what it checks, something like is_verify_only that says what the
> caller may want to use it for would be a more friendly name for
> readers and future developers.
This is true for the old-oid and old-target. That is, when they are set
to null, we're validating.
With the new-oid and new-target, if they're null, it usually signifies
deletion. We could rename it to 'is_delete_only', but that would also
need checking the 'REF_HAVE_NEW' flag. So we could ideally change it to
```
int ref_update_is_delete_only(struct ref_update *update) {
return (update->flags & REF_HAVE_NEW) && !update->new_target &&
is_null_oid(&update->new_oid);
}
```
I'm okay with making this change.
>> @@ -297,11 +320,47 @@ static void parse_cmd_verify(struct ref_transaction *transaction,
>> die("verify %s: extra input: %s", refname, next);
>>
>> if (ref_transaction_verify(transaction, refname, &old_oid,
>> - update_flags, &err))
>> + NULL, update_flags, &err))
>>
>> update_flags = default_flags;
>> free(refname);
>> strbuf_release(&err);
>> }
>
> The only damage by this patch to parse_cmd_verify() is that
> ref_transaction_verify() gained another parameter NULL, but with the
> default "--diff-algorithm=myers" algorithm, it is very hard to see.
>
> The "--patience" algorithm does a much beter job on this hunk.
>
> And the following function is entirely new.
>
>> +static void parse_cmd_symref_verify(struct ref_transaction *transaction,
>> + const char *next, const char *end)
>> +{
>> + struct strbuf err = STRBUF_INIT;
>> + struct object_id old_oid;
>> + char *refname, *old_target;
>> +
>> + if (!(update_flags & REF_NO_DEREF))
>> + die("symref-verify: cannot operate with deref mode");
>> +
>> + refname = parse_refname(&next);
>> + if (!refname)
>> + die("symref-verify: missing <ref>");
>> +
>> + /*
>> + * old_ref is optional, but we want to differentiate between
>> + * a NULL and zero value.
>> + */
>> + old_target = parse_next_refname(&next);
>> + if (!old_target)
>> + old_oid = *null_oid();
>
> In many existing code paths, we do not do structure assignment like
> this. Instead we do
>
> oidcpy(&old_oid, null_oid());
>
> We can see an existing example in a common context in a hunk for
> refs.c in this patch.
>
Yeah, makes sense to switch this. Will do.
>> + if (*next != line_termination)
>> + die("symref-verify %s: extra input: %s", refname, next);
>> +
>> + if (ref_transaction_verify(transaction, refname,
>> + old_target ? NULL : &old_oid,
>> + old_target, update_flags, &err))
>> + die("%s", err.buf);
>
> Are static analyzers smart enough to notice that we will not be
> using old_oid uninitialized here? Just wondering.
Yup, at least the clang LSP server seems to detect and not bug me about
it.
> Anyway. This ensures ref_transaction_verify() gets either
> old_target or old_oid, but never both at the same time. The caller
> to ref_transaction_verify() in the previous function passed NULL for
> old_target but it always had a non-NULL old_oid so that is perfectly
> fine.
>
>> + update_flags = default_flags;
>> + free(refname);
>> + free(old_target);
>> + strbuf_release(&err);
>> +}
>
>> diff --git a/refs.c b/refs.c
>> index 060a31616d..0e1013b5ab 100644
>> --- a/refs.c
>> +++ b/refs.c
>> @@ -1217,6 +1217,8 @@ void ref_transaction_free(struct ref_transaction *transaction)
>>
>> for (i = 0; i < transaction->nr; i++) {
>> free(transaction->updates[i]->msg);
>> + free((void *)transaction->updates[i]->old_target);
>> + free((void *)transaction->updates[i]->new_target);
>> free(transaction->updates[i]);
>> }
>> free(transaction->updates);
>> @@ -1247,9 +1249,13 @@ struct ref_update *ref_transaction_add_update(
>>
>> update->flags = flags;
>>
>> - if (flags & REF_HAVE_NEW)
>> + if (new_target)
>> + update->new_target = xstrdup(new_target);
>> + if (old_target)
>> + update->old_target = xstrdup(old_target);
>
> Presumably "update" structure, when freshly initialized, has NULL in
> both of these _target members? Otherwise ref_transaction_free()
> would get in trouble, so double checking.
>
This is a good point. My understanding was that FLEX_ALLOC_MEM should
set everything to 0.
>> + if (new_oid && flags & REF_HAVE_NEW)
>> oidcpy(&update->new_oid, new_oid);
>> - if (flags & REF_HAVE_OLD)
>> + if (old_oid && flags & REF_HAVE_OLD)
>> oidcpy(&update->old_oid, old_oid);
>
> Since we can ask to work on a symbolic ref, new_oid / old_oid can be
> NULL when REF_HAVE_NEW / REF_HAVE_OLD bit is on for _target members.
>
> Makes me wonder if the code becomes easier to follow if the flag
> bits are split into four (_NEW -> _NEW_OID + _NEW_TARGET), but let's
> not worry about that for now.
>
The intersection of this is quite low currently, so I'm not really sure
if there's added benefit. I did start that way before, but perhaps with
the iterations in the last few version, maybe it makes the code simpler.
>> @@ -1286,6 +1292,7 @@ int ref_transaction_update(struct ref_transaction *transaction,
>> flags &= REF_TRANSACTION_UPDATE_ALLOWED_FLAGS;
>>
>> flags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0);
>> + flags |= (new_target ? REF_HAVE_NEW : 0) | (old_target ? REF_HAVE_OLD : 0);
>
>> @@ -1325,14 +1332,17 @@ int ref_transaction_delete(struct ref_transaction *transaction,
>> int ref_transaction_verify(struct ref_transaction *transaction,
>> const char *refname,
>> const struct object_id *old_oid,
>> + const char *old_target,
>> unsigned int flags,
>> struct strbuf *err)
>> {
>> - if (!old_oid)
>> - BUG("verify called with old_oid set to NULL");
>> + if (!old_target && !old_oid)
>> + BUG("verify called with old_oid and old_target set to NULL");
>
> Is it normal if you get _both_ set, or is it equally a BUG()?
> The parse_*_verify() codepaths we saw earlier both made sure
> only one of the two is non-NULL, and it is unclear what should
> happen if both are non-NULL.
>
It is a bug and this is caught in `ref_transaction_add_update`.
Introduced in the first patch of the series.
>> + if (old_target && !(flags & REF_NO_DEREF))
>> + BUG("verify cannot operate on symrefs with deref mode");
>> return ref_transaction_update(transaction, refname,
>> NULL, old_oid,
>> - NULL, NULL,
>> + NULL, old_target,
>> flags, NULL, err);
>> }
>
> So this queues an ref_update object whose .new_oid and .new_target
> are NULL, and .old_oid and .old_target are what the caller gave us
> to check. The NULLs in .new* members hopefully do not mean "delete
> this thing" ;-)
>
So the 'new_oid' being set to zero should be the delete this thing
queue.
>> @@ -2349,6 +2359,12 @@ static int run_transaction_hook(struct ref_transaction *transaction,
>> for (i = 0; i < transaction->nr; i++) {
>> struct ref_update *update = transaction->updates[i];
>>
>> + /*
>> + * Skip reference transaction for symbolic refs.
>> + */
>> + if (update->new_target || update->old_target)
>> + continue;
>
> Is that a final design, or will the hooks have a chance to interfere?
>
The last patch adds hook support.
>> diff --git a/refs/files-backend.c b/refs/files-backend.c
>> index 2420dac2aa..53197fa3af 100644
>> --- a/refs/files-backend.c
>> +++ b/refs/files-backend.c
>> @@ -2425,6 +2425,37 @@ static const char *original_update_refname(struct ref_update *update)
>> return update->refname;
>> }
>>
>> +/*
>> + * Check whether the REF_HAVE_OLD and old_target values stored in
>> + * update are consistent with ref, which is the symbolic reference's
>> + * current value. If everything is OK, return 0; otherwise, write an
>> + * error message to err and return -1.
>> + */
>> +static int check_old_target(struct ref_update *update, char *ref,
>> + struct strbuf *err)
>> +{
>> + if (!(update->flags & REF_HAVE_OLD) ||
>> + !strcmp(update->old_target, ref))
>> + return 0;
>
> Earlier on the assignment side for "update" structure we saw above,
> the guard was (old_target && flags & REF_HAVE_OLD), but here we
> assume old_target is valid, which feels a bit asymmetric.
>
> Yes, I can see that the caller does not call us when !old_target,
> but still... Perhaps
>
> if ((update->flags & REF_HAVE_OLD) && !update->old_target)
> BUG(...);
>
I will add something like this.
> or something? Or alternatively, perhaps !!update->old_target should
> be the only thing we should check and ignore REF_HAVE_OLD bit? I am
> not sure, but it smells like that the non-NULL-ness of old_target is
> the only thing that matters (if it is not NULL, very early in the
> control flow somebody would have set REF_HAVE_OLD bit to flags, no?).
>
> It brings me back to my earlier question. Does REF_HAVE_OLD bit
> serve a useful purpose in this code?
>
I checked and it doesn't, it can be removed from usage in this code.
Will cleanup this part.
>> + if (!strcmp(update->old_target, ""))
>> + strbuf_addf(err, "cannot lock ref '%s': "
>> + "reference already exists",
>> + original_update_refname(update));
>> + else if (!strcmp(ref, ""))
>> + strbuf_addf(err, "cannot lock ref '%s': "
>> + "reference is missing but expected %s",
>> + original_update_refname(update),
>> + update->old_target);
>
> So... for old_target and ref, an empty string is a special value?
> How? Shouldn't that be documented in the comment before the
> function?
>
>> + else
>> + strbuf_addf(err, "cannot lock ref '%s': "
>> + "is at %s but expected %s",
>> + original_update_refname(update),
>> + ref, update->old_target);
>> +
>> + return -1;
>> +}
>> +
>> /*
>> * Check whether the REF_HAVE_OLD and old_oid values stored in update
>> * are consistent with oid, which is the reference's current value. If
>> @@ -2528,6 +2559,18 @@ static int lock_ref_for_update(struct files_ref_store *refs,
>> ret = TRANSACTION_GENERIC_ERROR;
>> goto out;
>> }
>> + }
>> +
>> + /*
>> + * For symref verification, we need to check the reference value
>> + * rather than the oid. If we're dealing with regular refs or we're
>> + * verifying a dereferenced symref, we then check the oid.
>> + */
>> + if (update->old_target) {
>> + if (check_old_target(update, referent.buf, err)) {
>> + ret = TRANSACTION_GENERIC_ERROR;
>> + goto out;
>> + }
>
> We come here only when update->type has REF_ISSYMREF bit on (we
> learned that value by calling lock_raw_ref()), and know referent.buf
> has the current "target" value. That is consumed as "ref" parameter
> to check_old_target() we just saw. OK.
>
>> diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
>> index 6104471199..a2474245aa 100644
>> --- a/refs/reftable-backend.c
>> +++ b/refs/reftable-backend.c
>> @@ -938,7 +938,26 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
>> * individual refs. But the error messages match what the files
>> * backend returns, which keeps our tests happy.
>> */
>> - if (u->flags & REF_HAVE_OLD && !oideq(¤t_oid, &u->old_oid)) {
>> + if ((u->flags & REF_HAVE_OLD) && u->old_target) {
>> + if (strcmp(referent.buf, u->old_target)) {
>> + if (!strcmp(u->old_target, ""))
>> + strbuf_addf(err, "verifying symref target: '%s': "
>> + "provided target is empty",
>> + original_update_refname(u));
>> + else if (!strcmp(referent.buf, ""))
>> + strbuf_addf(err, "verifying symref target: '%s': "
>> + "reference is missing but expected %s",
>> + original_update_refname(u),
>> + u->old_target);
>> + else
>> + strbuf_addf(err, "verifying symref target: '%s': "
>> + "is at %s but expected %s",
>> + original_update_refname(u),
>> + referent.buf, u->old_target);
>> + ret = -1;
>> + goto done;
>> + }
>
> Again, the puzzling "empty string"s are handled here.
For here and above, this too is dead code and no longer needed,
old_target being empty string is left over code from before we decided
to use zero_oid for deleting. I'll remove it. Thanks.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 194+ messages in thread
* [PATCH v4 4/7] update-ref: add support for 'symref-delete' command
2024-04-26 15:24 ` [PATCH v4 0/7] add symref-* commands to 'git-update-ref --stdin' Karthik Nayak
` (2 preceding siblings ...)
2024-04-26 15:24 ` [PATCH v4 3/7] update-ref: add support for 'symref-verify' command Karthik Nayak
@ 2024-04-26 15:24 ` Karthik Nayak
2024-04-26 15:24 ` [PATCH v4 5/7] update-ref: add support for 'symref-create' command Karthik Nayak
` (4 subsequent siblings)
8 siblings, 0 replies; 194+ messages in thread
From: Karthik Nayak @ 2024-04-26 15:24 UTC (permalink / raw)
To: karthik.188; +Cc: christian.couder, git, gitster, ps
From: Karthik Nayak <karthik.188@gmail.com>
Add a new command 'symref-delete' to allow deletions of symbolic refs in
a transaction via the '--stdin' mode of the 'git-update-ref' command.
The 'symref-delete' command can, when given an <old-target>, delete the
provided <ref> only when it points to <old-target>. This will only work
when used with the 'no-deref' mode as it doesn't make sense to deref a
symref during deletion.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
Documentation/git-update-ref.txt | 11 ++++--
builtin/fetch.c | 2 +-
builtin/receive-pack.c | 3 +-
builtin/update-ref.c | 33 ++++++++++++++++-
refs.c | 12 ++++---
refs.h | 4 ++-
refs/files-backend.c | 2 +-
refs/reftable-backend.c | 2 +-
t/t1400-update-ref.sh | 61 +++++++++++++++++++++++++++++++-
9 files changed, 117 insertions(+), 13 deletions(-)
diff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt
index 9fe78b3501..2924b9437e 100644
--- a/Documentation/git-update-ref.txt
+++ b/Documentation/git-update-ref.txt
@@ -65,6 +65,7 @@ performs all modifications together. Specify commands of the form:
create SP <ref> SP <new-oid> LF
delete SP <ref> [SP <old-oid>] LF
verify SP <ref> [SP <old-oid>] LF
+ symref-delete SP <ref> [SP <old-target>] LF
symref-verify SP <ref> [SP <old-target>] LF
option SP <opt> LF
start LF
@@ -87,6 +88,7 @@ quoting:
create SP <ref> NUL <new-oid> NUL
delete SP <ref> NUL [<old-oid>] NUL
verify SP <ref> NUL [<old-oid>] NUL
+ symref-delete SP <ref> [NUL <old-target>] NUL
symref-verify SP <ref> [NUL <old-target>] NUL
option SP <opt> NUL
start NUL
@@ -112,13 +114,18 @@ create::
exist. The given <new-oid> may not be zero.
delete::
- Delete <ref> after verifying it exists with <old-oid>, if
- given. If given, <old-oid> may not be zero.
+ Delete <ref> after verifying it exists with <old-oid>, if given.
+ If given, <old-oid> may not be zero. If instead, ref:<old-target>
+ is provided, verify that the symbolic ref <ref> targets
+ <old-target> before deleting it.
verify::
Verify <ref> against <old-oid> but do not change it. If
<old-oid> is zero or missing, the ref must not exist.
+symref-delete::
+ Delete <ref> after verifying it exists with <old-target>, if given.
+
symref-verify::
Verify symbolic <ref> against <old-target> but do not change it.
If <old-target> is missing, the ref must not exist. Can only be
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 66840b7c5b..d02592efca 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -1383,7 +1383,7 @@ static int prune_refs(struct display_state *display_state,
if (transaction) {
for (ref = stale_refs; ref; ref = ref->next) {
result = ref_transaction_delete(transaction, ref->name, NULL, 0,
- "fetch: prune", &err);
+ NULL, "fetch: prune", &err);
if (result)
goto cleanup;
}
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index b150ef39a8..9a4667d57d 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -1576,7 +1576,8 @@ static const char *update(struct command *cmd, struct shallow_info *si)
if (ref_transaction_delete(transaction,
namespaced_name,
old_oid,
- 0, "push", &err)) {
+ 0, NULL,
+ "push", &err)) {
rp_error("%s", err.buf);
ret = "failed to delete";
} else {
diff --git a/builtin/update-ref.c b/builtin/update-ref.c
index 419b28169b..8fef3aed0a 100644
--- a/builtin/update-ref.c
+++ b/builtin/update-ref.c
@@ -293,7 +293,7 @@ static void parse_cmd_delete(struct ref_transaction *transaction,
if (ref_transaction_delete(transaction, refname,
have_old ? &old_oid : NULL,
- update_flags, msg, &err))
+ update_flags, NULL, msg, &err))
die("%s", err.buf);
update_flags = default_flags;
@@ -301,6 +301,36 @@ static void parse_cmd_delete(struct ref_transaction *transaction,
strbuf_release(&err);
}
+
+static void parse_cmd_symref_delete(struct ref_transaction *transaction,
+ const char *next, const char *end)
+{
+ struct strbuf err = STRBUF_INIT;
+ char *refname, *old_target;
+
+ if (!(update_flags & REF_NO_DEREF))
+ die("symref-delete: cannot operate with deref mode");
+
+ refname = parse_refname(&next);
+ if (!refname)
+ die("symref-delete: missing <ref>");
+
+ old_target = parse_next_refname(&next);
+
+ if (*next != line_termination)
+ die("symref-delete %s: extra input: %s", refname, next);
+
+ if (ref_transaction_delete(transaction, refname, NULL,
+ update_flags, old_target, msg, &err))
+ die("%s", err.buf);
+
+ update_flags = default_flags;
+ free(refname);
+ free(old_target);
+ strbuf_release(&err);
+}
+
+
static void parse_cmd_verify(struct ref_transaction *transaction,
const char *next, const char *end)
{
@@ -443,6 +473,7 @@ static const struct parse_cmd {
{ "create", parse_cmd_create, 2, UPDATE_REFS_OPEN },
{ "delete", parse_cmd_delete, 2, UPDATE_REFS_OPEN },
{ "verify", parse_cmd_verify, 2, UPDATE_REFS_OPEN },
+ { "symref-delete", parse_cmd_symref_delete, 2, UPDATE_REFS_OPEN },
{ "symref-verify", parse_cmd_symref_verify, 2, UPDATE_REFS_OPEN },
{ "option", parse_cmd_option, 1, UPDATE_REFS_OPEN },
{ "start", parse_cmd_start, 0, UPDATE_REFS_STARTED },
diff --git a/refs.c b/refs.c
index 0e1013b5ab..6b7c46bfd8 100644
--- a/refs.c
+++ b/refs.c
@@ -979,7 +979,7 @@ int refs_delete_ref(struct ref_store *refs, const char *msg,
transaction = ref_store_transaction_begin(refs, &err);
if (!transaction ||
ref_transaction_delete(transaction, refname, old_oid,
- flags, msg, &err) ||
+ flags, NULL, msg, &err) ||
ref_transaction_commit(transaction, &err)) {
error("%s", err.buf);
ref_transaction_free(transaction);
@@ -1318,14 +1318,18 @@ int ref_transaction_create(struct ref_transaction *transaction,
int ref_transaction_delete(struct ref_transaction *transaction,
const char *refname,
const struct object_id *old_oid,
- unsigned int flags, const char *msg,
+ unsigned int flags,
+ const char *old_target,
+ const char *msg,
struct strbuf *err)
{
if (old_oid && is_null_oid(old_oid))
BUG("delete called with old_oid set to zeros");
+ if (old_target && !(flags & REF_NO_DEREF))
+ BUG("delete cannot operate on symrefs with deref mode");
return ref_transaction_update(transaction, refname,
null_oid(), old_oid,
- NULL, NULL, flags,
+ NULL, old_target, flags,
msg, err);
}
@@ -2752,7 +2756,7 @@ int refs_delete_refs(struct ref_store *refs, const char *logmsg,
for_each_string_list_item(item, refnames) {
ret = ref_transaction_delete(transaction, item->string,
- NULL, flags, msg, &err);
+ NULL, flags, NULL, msg, &err);
if (ret) {
warning(_("could not delete reference %s: %s"),
item->string, err.buf);
diff --git a/refs.h b/refs.h
index 27b9aeaf54..4be4930f04 100644
--- a/refs.h
+++ b/refs.h
@@ -766,7 +766,9 @@ int ref_transaction_create(struct ref_transaction *transaction,
int ref_transaction_delete(struct ref_transaction *transaction,
const char *refname,
const struct object_id *old_oid,
- unsigned int flags, const char *msg,
+ unsigned int flags,
+ const char *old_target,
+ const char *msg,
struct strbuf *err);
/*
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 53197fa3af..fc5037fe5a 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -2516,7 +2516,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,
files_assert_main_repository(refs, "lock_ref_for_update");
- if ((update->flags & REF_HAVE_NEW) && is_null_oid(&update->new_oid))
+ if ((update->flags & REF_HAVE_NEW) && ref_update_is_null_new_value(update))
update->flags |= REF_DELETING;
if (head_ref) {
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index a2474245aa..2b2cbca8c0 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -1120,7 +1120,7 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data
if (u->flags & REF_LOG_ONLY)
continue;
- if (u->flags & REF_HAVE_NEW && is_null_oid(&u->new_oid)) {
+ if (u->flags & REF_HAVE_NEW && ref_update_is_null_new_value(u)) {
struct reftable_ref_record ref = {
.refname = (char *)u->refname,
.update_index = ts,
diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh
index 34b29eeac8..8efddac013 100755
--- a/t/t1400-update-ref.sh
+++ b/t/t1400-update-ref.sh
@@ -1689,7 +1689,7 @@ do
test_cmp before after
'
- test_expect_success "stdin ${type} symref-verify no value is treated as zero value" '
+ test_expect_success "stdin ${type} symref-verify fails with no value" '
git symbolic-ref refs/heads/symref >expect &&
create_stdin_buf ${type} "symref-verify refs/heads/symref" "" &&
test_must_fail git update-ref --stdin ${type} --no-deref <stdin
@@ -1728,6 +1728,65 @@ do
test_cmp expect actual
'
+ test_expect_success "stdin ${type} symref-delete fails without --no-deref" '
+ git symbolic-ref refs/heads/symref $a &&
+ create_stdin_buf ${type} "symref-delete refs/heads/symref" "$a" &&
+ test_must_fail git update-ref --stdin ${type} <stdin 2>err &&
+ grep "fatal: symref-delete: cannot operate with deref mode" err
+ '
+
+ test_expect_success "stdin ${type} symref-delete fails with no ref" '
+ create_stdin_buf ${type} "symref-delete " &&
+ test_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&
+ grep "fatal: symref-delete: missing <ref>" err
+ '
+
+ test_expect_success "stdin ${type} symref-delete fails with too many arguments" '
+ create_stdin_buf ${type} "symref-delete refs/heads/symref" "$a" "$a" &&
+ test_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&
+ if test "$type" = "-z"
+ then
+ grep "fatal: unknown command: $a" err
+ else
+ grep "fatal: symref-delete refs/heads/symref: extra input: $a" err
+ fi
+ '
+
+ test_expect_success "stdin ${type} symref-delete fails with wrong old value" '
+ create_stdin_buf ${type} "symref-delete refs/heads/symref" "$m" &&
+ test_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&
+ if test_have_prereq REFTABLE
+ then
+ grep "fatal: verifying symref target: ${SQ}refs/heads/symref${SQ}: is at $a but expected refs/heads/main" err
+ else
+ grep "fatal: cannot lock ref ${SQ}refs/heads/symref${SQ}" err
+ fi &&
+ git symbolic-ref refs/heads/symref >expect &&
+ echo $a >actual &&
+ test_cmp expect actual
+ '
+
+ test_expect_success "stdin ${type} symref-delete works with right old value" '
+ create_stdin_buf ${type} "symref-delete refs/heads/symref" "$a" &&
+ git update-ref --stdin ${type} --no-deref <stdin &&
+ test_must_fail git rev-parse --verify -q refs/heads/symref
+ '
+
+ test_expect_success "stdin ${type} symref-delete works with empty old value" '
+ git symbolic-ref refs/heads/symref $a &&
+ create_stdin_buf ${type} "symref-delete refs/heads/symref" "" &&
+ git update-ref --stdin ${type} --no-deref <stdin &&
+ test_must_fail git rev-parse --verify -q $b
+ '
+
+ test_expect_success "stdin ${type} symref-delete succeeds for dangling reference" '
+ test_must_fail git symbolic-ref refs/heads/nonexistent &&
+ git symbolic-ref refs/heads/symref2 refs/heads/nonexistent &&
+ create_stdin_buf ${type} "symref-delete refs/heads/symref2" "refs/heads/nonexistent" &&
+ git update-ref --stdin ${type} --no-deref <stdin &&
+ test_must_fail git symbolic-ref -d refs/heads/symref2
+ '
+
done
test_done
--
2.43.GIT
^ permalink raw reply related [flat|nested] 194+ messages in thread
* [PATCH v4 5/7] update-ref: add support for 'symref-create' command
2024-04-26 15:24 ` [PATCH v4 0/7] add symref-* commands to 'git-update-ref --stdin' Karthik Nayak
` (3 preceding siblings ...)
2024-04-26 15:24 ` [PATCH v4 4/7] update-ref: add support for 'symref-delete' command Karthik Nayak
@ 2024-04-26 15:24 ` Karthik Nayak
2024-04-26 15:24 ` [PATCH v4 6/7] update-ref: add support for 'symref-update' command Karthik Nayak
` (3 subsequent siblings)
8 siblings, 0 replies; 194+ messages in thread
From: Karthik Nayak @ 2024-04-26 15:24 UTC (permalink / raw)
To: karthik.188; +Cc: christian.couder, git, gitster, ps
From: Karthik Nayak <karthik.188@gmail.com>
Add 'symref-create' command to the '--stdin' mode 'git-update-ref' to
allow creation of symbolic refs in a transaction. The 'symref-create'
command takes in a <new-target>, which the created <ref> will point to.
Also, support the 'core.prefersymlinkrefs' config, wherein if the config
is set and the filesystem supports symlinks, we create the symbolic ref
as a symlink. We fallback to creating a regular symref if creating the
symlink is unsuccessful.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
Documentation/git-update-ref.txt | 10 ++++-
builtin/clone.c | 2 +-
builtin/update-ref.c | 35 ++++++++++++++++-
refs.c | 9 +++--
refs.h | 1 +
refs/files-backend.c | 42 +++++++++++++++++++++
refs/reftable-backend.c | 23 +++++++++--
t/t0600-reffiles-backend.sh | 32 ++++++++++++++++
t/t1400-update-ref.sh | 65 ++++++++++++++++++++++++++++++++
9 files changed, 210 insertions(+), 9 deletions(-)
diff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt
index 2924b9437e..7a33f70767 100644
--- a/Documentation/git-update-ref.txt
+++ b/Documentation/git-update-ref.txt
@@ -65,6 +65,7 @@ performs all modifications together. Specify commands of the form:
create SP <ref> SP <new-oid> LF
delete SP <ref> [SP <old-oid>] LF
verify SP <ref> [SP <old-oid>] LF
+ symref-create SP <ref> SP <new-target> LF
symref-delete SP <ref> [SP <old-target>] LF
symref-verify SP <ref> [SP <old-target>] LF
option SP <opt> LF
@@ -88,6 +89,7 @@ quoting:
create SP <ref> NUL <new-oid> NUL
delete SP <ref> NUL [<old-oid>] NUL
verify SP <ref> NUL [<old-oid>] NUL
+ symref-create SP <ref> NUL <new-target> NUL
symref-delete SP <ref> [NUL <old-target>] NUL
symref-verify SP <ref> [NUL <old-target>] NUL
option SP <opt> NUL
@@ -111,7 +113,9 @@ update::
create::
Create <ref> with <new-oid> after verifying it does not
- exist. The given <new-oid> may not be zero.
+ exist. The given <new-oid> may not be zero. If instead
+ ref:<new-target> is provided, a symbolic ref is created
+ which targets <new-target>.
delete::
Delete <ref> after verifying it exists with <old-oid>, if given.
@@ -123,6 +127,10 @@ verify::
Verify <ref> against <old-oid> but do not change it. If
<old-oid> is zero or missing, the ref must not exist.
+symref-create::
+ Create symbolic ref <ref> with <new-target> after verifying
+ it does not exist. Can only be used in `no-deref` mode.
+
symref-delete::
Delete <ref> after verifying it exists with <old-target>, if given.
diff --git a/builtin/clone.c b/builtin/clone.c
index 74ec14542e..c0eed8e795 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -547,7 +547,7 @@ static void write_remote_refs(const struct ref *local_refs)
if (!r->peer_ref)
continue;
if (ref_transaction_create(t, r->peer_ref->name, &r->old_oid,
- 0, NULL, &err))
+ NULL, 0, NULL, &err))
die("%s", err.buf);
}
diff --git a/builtin/update-ref.c b/builtin/update-ref.c
index 8fef3aed0a..ae68ffde5e 100644
--- a/builtin/update-ref.c
+++ b/builtin/update-ref.c
@@ -257,7 +257,7 @@ static void parse_cmd_create(struct ref_transaction *transaction,
if (*next != line_termination)
die("create %s: extra input: %s", refname, next);
- if (ref_transaction_create(transaction, refname, &new_oid,
+ if (ref_transaction_create(transaction, refname, &new_oid, NULL,
update_flags | create_reflog_flag,
msg, &err))
die("%s", err.buf);
@@ -267,6 +267,38 @@ static void parse_cmd_create(struct ref_transaction *transaction,
strbuf_release(&err);
}
+
+static void parse_cmd_symref_create(struct ref_transaction *transaction,
+ const char *next, const char *end)
+{
+ struct strbuf err = STRBUF_INIT;
+ char *refname, *new_target;
+
+ if (!(update_flags & REF_NO_DEREF))
+ die("symref-create: cannot operate with deref mode");
+
+ refname = parse_refname(&next);
+ if (!refname)
+ die("symref-create: missing <ref>");
+
+ new_target = parse_next_refname(&next);
+ if (!new_target)
+ die("symref-create %s: missing <new-target>", refname);
+
+ if (*next != line_termination)
+ die("symref-create %s: extra input: %s", refname, next);
+
+ if (ref_transaction_create(transaction, refname, NULL, new_target,
+ update_flags | create_reflog_flag,
+ msg, &err))
+ die("%s", err.buf);
+
+ update_flags = default_flags;
+ free(refname);
+ free(new_target);
+ strbuf_release(&err);
+}
+
static void parse_cmd_delete(struct ref_transaction *transaction,
const char *next, const char *end)
{
@@ -473,6 +505,7 @@ static const struct parse_cmd {
{ "create", parse_cmd_create, 2, UPDATE_REFS_OPEN },
{ "delete", parse_cmd_delete, 2, UPDATE_REFS_OPEN },
{ "verify", parse_cmd_verify, 2, UPDATE_REFS_OPEN },
+ { "symref-create", parse_cmd_symref_create, 2, UPDATE_REFS_OPEN },
{ "symref-delete", parse_cmd_symref_delete, 2, UPDATE_REFS_OPEN },
{ "symref-verify", parse_cmd_symref_verify, 2, UPDATE_REFS_OPEN },
{ "option", parse_cmd_option, 1, UPDATE_REFS_OPEN },
diff --git a/refs.c b/refs.c
index 6b7c46bfd8..42cb4126a7 100644
--- a/refs.c
+++ b/refs.c
@@ -1303,15 +1303,18 @@ int ref_transaction_update(struct ref_transaction *transaction,
int ref_transaction_create(struct ref_transaction *transaction,
const char *refname,
const struct object_id *new_oid,
+ const char *new_target,
unsigned int flags, const char *msg,
struct strbuf *err)
{
- if (!new_oid || is_null_oid(new_oid)) {
- strbuf_addf(err, "'%s' has a null OID", refname);
+ if ((!new_oid || is_null_oid(new_oid)) && !new_target) {
+ strbuf_addf(err, "'%s' has a null OID or no new target", refname);
return 1;
}
+ if (new_target && !(flags & REF_NO_DEREF))
+ BUG("create cannot operate on symrefs with deref mode");
return ref_transaction_update(transaction, refname, new_oid,
- null_oid(), NULL, NULL, flags,
+ null_oid(), new_target, NULL, flags,
msg, err);
}
diff --git a/refs.h b/refs.h
index 4be4930f04..bde8606213 100644
--- a/refs.h
+++ b/refs.h
@@ -752,6 +752,7 @@ int ref_transaction_update(struct ref_transaction *transaction,
int ref_transaction_create(struct ref_transaction *transaction,
const char *refname,
const struct object_id *new_oid,
+ const char *new_target,
unsigned int flags, const char *msg,
struct strbuf *err);
diff --git a/refs/files-backend.c b/refs/files-backend.c
index fc5037fe5a..f5e271a442 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -2610,6 +2610,27 @@ static int lock_ref_for_update(struct files_ref_store *refs,
}
}
+ if (update->new_target) {
+ if (create_symref_lock(refs, lock, update->refname, update->new_target)) {
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto out;
+ }
+
+ if (close_ref_gently(lock)) {
+ strbuf_addf(err, "couldn't close '%s.lock'",
+ update->refname);
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto out;
+ }
+
+ /*
+ * Once we have created the symref lock, the commit
+ * phase of the transaction only needs to commit the lock.
+ */
+ update->flags |= REF_NEEDS_COMMIT;
+ }
+
+
if ((update->flags & REF_HAVE_NEW) &&
!(update->flags & REF_DELETING) &&
!(update->flags & REF_LOG_ONLY)) {
@@ -2905,6 +2926,18 @@ static int files_transaction_finish(struct ref_store *ref_store,
if (update->flags & REF_NEEDS_COMMIT ||
update->flags & REF_LOG_ONLY) {
+ if (update->new_target) {
+ /*
+ * We want to get the resolved OID for the target, to ensure
+ * that the correct value is added to the reflog.
+ */
+ if (!refs_resolve_ref_unsafe(&refs->base, update->new_target,
+ RESOLVE_REF_READING, &update->new_oid, NULL)) {
+ /* for dangling symrefs we gracefully set the oid to zero */
+ update->new_oid = *null_oid();
+ }
+ }
+
if (files_log_ref_write(refs,
lock->ref_name,
&lock->old_oid,
@@ -2922,6 +2955,15 @@ static int files_transaction_finish(struct ref_store *ref_store,
goto cleanup;
}
}
+
+ /*
+ * We try creating a symlink, if that succeeds we continue to the
+ * next updated. If not, we try and create a regular symref.
+ */
+ if (update->new_target && prefer_symlink_refs)
+ if (!create_ref_symlink(lock, update->new_target))
+ continue;
+
if (update->flags & REF_NEEDS_COMMIT) {
clear_loose_ref_cache(refs);
if (commit_ref(lock)) {
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index 2b2cbca8c0..e203c697f2 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -856,7 +856,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
* There is no need to write the reference deletion
* when the reference in question doesn't exist.
*/
- if (u->flags & REF_HAVE_NEW && !is_null_oid(&u->new_oid)) {
+ if (u->flags & REF_HAVE_NEW && !ref_update_is_null_new_value(u)) {
ret = queue_transaction_update(refs, tx_data, u,
¤t_oid, err);
if (ret)
@@ -1062,7 +1062,7 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data
* - `core.logAllRefUpdates` tells us to create the reflog for
* the given ref.
*/
- if (u->flags & REF_HAVE_NEW && !(u->type & REF_ISSYMREF) && is_null_oid(&u->new_oid)) {
+ if (u->flags & REF_HAVE_NEW && !(u->type & REF_ISSYMREF) && ref_update_is_null_new_value(u)) {
struct reftable_log_record log = {0};
struct reftable_iterator it = {0};
@@ -1104,6 +1104,12 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data
should_write_log(&arg->refs->base, u->refname))) {
struct reftable_log_record *log;
+ if (u->new_target)
+ if (!refs_resolve_ref_unsafe(&arg->refs->base, u->new_target,
+ RESOLVE_REF_READING, &u->new_oid, NULL))
+ /* for dangling symrefs we gracefully set the oid to zero */
+ u->new_oid = *null_oid();
+
ALLOC_GROW(logs, logs_nr + 1, logs_alloc);
log = &logs[logs_nr++];
memset(log, 0, sizeof(*log));
@@ -1120,7 +1126,18 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data
if (u->flags & REF_LOG_ONLY)
continue;
- if (u->flags & REF_HAVE_NEW && ref_update_is_null_new_value(u)) {
+ if (u->flags & REF_HAVE_NEW && u->new_target) {
+ struct reftable_ref_record ref = {
+ .refname = (char *)u->refname,
+ .value_type = REFTABLE_REF_SYMREF,
+ .value.symref = (char *)u->new_target,
+ .update_index = ts,
+ };
+
+ ret = reftable_writer_add_ref(writer, &ref);
+ if (ret < 0)
+ goto done;
+ } else if (u->flags & REF_HAVE_NEW && ref_update_is_null_new_value(u)) {
struct reftable_ref_record ref = {
.refname = (char *)u->refname,
.update_index = ts,
diff --git a/t/t0600-reffiles-backend.sh b/t/t0600-reffiles-backend.sh
index 64214340e7..c5061c26cf 100755
--- a/t/t0600-reffiles-backend.sh
+++ b/t/t0600-reffiles-backend.sh
@@ -472,4 +472,36 @@ test_expect_success POSIXPERM 'git reflog expire honors core.sharedRepository' '
esac
'
+test_expect_success SYMLINKS 'symref transaction supports symlinks' '
+ test_when_finished "git symbolic-ref -d TESTSYMREFONE" &&
+ git update-ref refs/heads/new @ &&
+ test_config core.prefersymlinkrefs true &&
+ cat >stdin <<-EOF &&
+ start
+ symref-create TESTSYMREFONE refs/heads/new
+ prepare
+ commit
+ EOF
+ git update-ref --no-deref --stdin <stdin &&
+ test_path_is_symlink .git/TESTSYMREFONE &&
+ test "$(test_readlink .git/TESTSYMREFONE)" = refs/heads/new
+'
+
+test_expect_success 'symref transaction supports false symlink config' '
+ test_when_finished "git symbolic-ref -d TESTSYMREFONE" &&
+ git update-ref refs/heads/new @ &&
+ test_config core.prefersymlinkrefs false &&
+ cat >stdin <<-EOF &&
+ start
+ symref-create TESTSYMREFONE refs/heads/new
+ prepare
+ commit
+ EOF
+ git update-ref --no-deref --stdin <stdin &&
+ test_path_is_file .git/TESTSYMREFONE &&
+ git symbolic-ref TESTSYMREFONE >actual &&
+ echo refs/heads/new >expect &&
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh
index 8efddac013..452fc1da50 100755
--- a/t/t1400-update-ref.sh
+++ b/t/t1400-update-ref.sh
@@ -1787,6 +1787,71 @@ do
test_must_fail git symbolic-ref -d refs/heads/symref2
'
+ test_expect_success "stdin ${type} symref-create fails without --no-deref" '
+ create_stdin_buf ${type} "symref-create refs/heads/symref" "$a" &&
+ test_must_fail git update-ref --stdin ${type} <stdin 2>err &&
+ grep "fatal: symref-create: cannot operate with deref mode" err
+ '
+
+ test_expect_success "stdin ${type} symref-create fails with too many arguments" '
+ create_stdin_buf ${type} "symref-create refs/heads/symref" "$a" "$a" >stdin &&
+ test_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&
+ if test "$type" = "-z"
+ then
+ grep "fatal: unknown command: $a" err
+ else
+ grep "fatal: symref-create refs/heads/symref: extra input: $a" err
+ fi
+ '
+
+ test_expect_success "stdin ${type} symref-create fails with no target" '
+ create_stdin_buf ${type} "symref-create refs/heads/symref" >stdin &&
+ test_must_fail git update-ref --stdin ${type} --no-deref <stdin
+ '
+
+ test_expect_success "stdin ${type} symref-create fails with empty target" '
+ create_stdin_buf ${type} "symref-create refs/heads/symref" "" >stdin &&
+ test_must_fail git update-ref --stdin ${type} --no-deref <stdin
+ '
+
+ test_expect_success "stdin ${type} symref-create works" '
+ test_when_finished "git symbolic-ref -d refs/heads/symref" &&
+ create_stdin_buf ${type} "symref-create refs/heads/symref" "$a" >stdin &&
+ git update-ref --stdin ${type} --no-deref <stdin &&
+ git symbolic-ref refs/heads/symref >expect &&
+ echo $a >actual &&
+ test_cmp expect actual
+ '
+
+ test_expect_success "stdin ${type} create dangling symref ref works" '
+ test_when_finished "git symbolic-ref -d refs/heads/symref" &&
+ create_stdin_buf ${type} "symref-create refs/heads/symref" "refs/heads/unkown" >stdin &&
+ git update-ref --stdin ${type} --no-deref <stdin &&
+ git symbolic-ref refs/heads/symref >expect &&
+ echo refs/heads/unkown >actual &&
+ test_cmp expect actual
+ '
+
+ test_expect_success "stdin ${type} symref-create does not create reflogs by default" '
+ test_when_finished "git symbolic-ref -d refs/symref" &&
+ create_stdin_buf ${type} "symref-create refs/symref" "$a" >stdin &&
+ git update-ref --stdin ${type} --no-deref <stdin &&
+ git symbolic-ref refs/symref >expect &&
+ echo $a >actual &&
+ test_cmp expect actual &&
+ test_must_fail git reflog exists refs/symref
+ '
+
+ test_expect_success "stdin ${type} symref-create reflogs with --create-reflog" '
+ test_when_finished "git symbolic-ref -d refs/heads/symref" &&
+ create_stdin_buf ${type} "symref-create refs/heads/symref" "$a" >stdin &&
+ git update-ref --create-reflog --stdin ${type} --no-deref <stdin &&
+ git symbolic-ref refs/heads/symref >expect &&
+ echo $a >actual &&
+ test_cmp expect actual &&
+ git reflog exists refs/heads/symref
+ '
+
done
test_done
--
2.43.GIT
^ permalink raw reply related [flat|nested] 194+ messages in thread
* [PATCH v4 6/7] update-ref: add support for 'symref-update' command
2024-04-26 15:24 ` [PATCH v4 0/7] add symref-* commands to 'git-update-ref --stdin' Karthik Nayak
` (4 preceding siblings ...)
2024-04-26 15:24 ` [PATCH v4 5/7] update-ref: add support for 'symref-create' command Karthik Nayak
@ 2024-04-26 15:24 ` Karthik Nayak
2024-04-26 15:24 ` [PATCH v4 7/7] ref: support symrefs in 'reference-transaction' hook Karthik Nayak
` (2 subsequent siblings)
8 siblings, 0 replies; 194+ messages in thread
From: Karthik Nayak @ 2024-04-26 15:24 UTC (permalink / raw)
To: karthik.188; +Cc: christian.couder, git, gitster, ps
From: Karthik Nayak <karthik.188@gmail.com>
Add 'symref-update' command to the '--stdin' mode of 'git-update-ref' to
allow updates of symbolic refs. The 'symref-update' command takes in a
<new-target>, which the <ref> will be updated to. If the <ref> doesn't
exist it will be created.
It also optionally takes either an `ref <old-target>` or `oid
<old-oid>`. If the <old-target> is provided, it checks to see if the
<ref> targets the <old-target> before the update. If <old-oid> is provided
it checks <ref> to ensure that it is a regular ref and <old-oid> is the
OID before the update. This by extension also means that this when a
zero <old-oid> is provided, it ensures that the ref didn't exist before.
This command will also support deref mode, to ensure that we can update
dereferenced regular refs to symrefs.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
Documentation/git-update-ref.txt | 6 +
builtin/update-ref.c | 91 +++++++++++++-
refs/files-backend.c | 14 +--
refs/reftable-backend.c | 3 +-
t/t1400-update-ref.sh | 196 +++++++++++++++++++++++++++++++
5 files changed, 299 insertions(+), 11 deletions(-)
diff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt
index 7a33f70767..0cd4c37820 100644
--- a/Documentation/git-update-ref.txt
+++ b/Documentation/git-update-ref.txt
@@ -65,6 +65,7 @@ performs all modifications together. Specify commands of the form:
create SP <ref> SP <new-oid> LF
delete SP <ref> [SP <old-oid>] LF
verify SP <ref> [SP <old-oid>] LF
+ symref-update SP <ref> SP <new-target> [SP (ref SP <old-target> | oid SP <old-oid>)] LF
symref-create SP <ref> SP <new-target> LF
symref-delete SP <ref> [SP <old-target>] LF
symref-verify SP <ref> [SP <old-target>] LF
@@ -89,6 +90,7 @@ quoting:
create SP <ref> NUL <new-oid> NUL
delete SP <ref> NUL [<old-oid>] NUL
verify SP <ref> NUL [<old-oid>] NUL
+ symref-update SP <ref> NUL <new-target> [NUL (ref NUL <old-target> | oid NUL <old-oid>)] NUL
symref-create SP <ref> NUL <new-target> NUL
symref-delete SP <ref> [NUL <old-target>] NUL
symref-verify SP <ref> [NUL <old-target>] NUL
@@ -123,6 +125,10 @@ delete::
is provided, verify that the symbolic ref <ref> targets
<old-target> before deleting it.
+symref-update::
+ Set <ref> to <new-target> after verifying <old-target> or <old-oid>,
+ if given. Can be used to delete or create symrefs too.
+
verify::
Verify <ref> against <old-oid> but do not change it. If
<old-oid> is zero or missing, the ref must not exist.
diff --git a/builtin/update-ref.c b/builtin/update-ref.c
index ae68ffde5e..5001d80ae4 100644
--- a/builtin/update-ref.c
+++ b/builtin/update-ref.c
@@ -98,6 +98,41 @@ static char *parse_next_refname(const char **next)
return parse_refname(next);
}
+/*
+ * Wrapper around parse_arg which skips the next delimiter.
+ */
+static char *parse_next_arg(const char **next)
+{
+ struct strbuf arg = STRBUF_INIT;
+
+ if (line_termination) {
+ /* Without -z, consume SP and use next argument */
+ if (!**next || **next == line_termination)
+ return NULL;
+ if (**next != ' ')
+ die("expected SP but got: %s", *next);
+ } else {
+ /* With -z, read the next NUL-terminated line */
+ if (**next)
+ return NULL;
+ }
+ /* Skip the delimiter */
+ (*next)++;
+
+ if (line_termination) {
+ /* Without -z, use the next argument */
+ *next = parse_arg(*next, &arg);
+ } else {
+ /* With -z, use everything up to the next NUL */
+ strbuf_addstr(&arg, *next);
+ *next += arg.len;
+ }
+
+ if (arg.len)
+ return strbuf_detach(&arg, NULL);
+ return NULL;
+}
+
/*
* The value being parsed is <old-oid> (as opposed to <new-oid>; the
@@ -225,8 +260,8 @@ static void parse_cmd_update(struct ref_transaction *transaction,
if (*next != line_termination)
die("update %s: extra input: %s", refname, next);
- if (ref_transaction_update(transaction, refname,
- &new_oid, have_old ? &old_oid : NULL,
+ if (ref_transaction_update(transaction, refname, &new_oid,
+ have_old ? &old_oid : NULL,
NULL, NULL,
update_flags | create_reflog_flag,
msg, &err))
@@ -237,6 +272,57 @@ static void parse_cmd_update(struct ref_transaction *transaction,
strbuf_release(&err);
}
+static void parse_cmd_symref_update(struct ref_transaction *transaction,
+ const char *next, const char *end)
+{
+ char *refname, *new_target, *old_arg;
+ char *old_target = NULL;
+ struct strbuf err = STRBUF_INIT;
+ struct object_id old_oid;
+ int have_old = 0;
+
+ refname = parse_refname(&next);
+ if (!refname)
+ die("symref-update: missing <ref>");
+
+ new_target = parse_next_refname(&next);
+ if (!new_target)
+ die("symref-update %s: missing <new-target>", refname);
+
+ old_arg = parse_next_arg(&next);
+ if (old_arg) {
+ old_target = parse_next_refname(&next);
+ if (!old_target)
+ die("symref-update %s: expected old value", refname);
+
+ if (!strcmp(old_arg, "oid") &&
+ !repo_get_oid(the_repository, old_target, &old_oid)) {
+ old_target = NULL;
+ have_old = 1;
+ } else if (strcmp(old_arg, "ref"))
+ die("symref-update %s: invalid arg '%s' for old value", refname, old_arg);
+ }
+
+
+ if (*next != line_termination)
+ die("symref-update %s: extra input: %s", refname, next);
+
+
+ if (ref_transaction_update(transaction, refname, NULL,
+ have_old ? &old_oid : NULL,
+ new_target, old_target,
+ update_flags |= create_reflog_flag,
+ msg, &err))
+ die("%s", err.buf);
+
+ update_flags = default_flags;
+ free(refname);
+ free(old_arg);
+ free(old_target);
+ free(new_target);
+ strbuf_release(&err);
+}
+
static void parse_cmd_create(struct ref_transaction *transaction,
const char *next, const char *end)
{
@@ -505,6 +591,7 @@ static const struct parse_cmd {
{ "create", parse_cmd_create, 2, UPDATE_REFS_OPEN },
{ "delete", parse_cmd_delete, 2, UPDATE_REFS_OPEN },
{ "verify", parse_cmd_verify, 2, UPDATE_REFS_OPEN },
+ { "symref-update", parse_cmd_symref_update, 4, UPDATE_REFS_OPEN },
{ "symref-create", parse_cmd_symref_create, 2, UPDATE_REFS_OPEN },
{ "symref-delete", parse_cmd_symref_delete, 2, UPDATE_REFS_OPEN },
{ "symref-verify", parse_cmd_symref_verify, 2, UPDATE_REFS_OPEN },
diff --git a/refs/files-backend.c b/refs/files-backend.c
index f5e271a442..59d1ab3eeb 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -2386,7 +2386,8 @@ static int split_symref_update(struct ref_update *update,
new_update = ref_transaction_add_update(
transaction, referent, new_flags,
&update->new_oid, &update->old_oid,
- NULL, NULL, update->msg);
+ update->new_target, update->old_target,
+ update->msg);
new_update->parent_update = update;
@@ -2610,7 +2611,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,
}
}
- if (update->new_target) {
+ if (update->new_target && !(update->flags & REF_LOG_ONLY)) {
if (create_symref_lock(refs, lock, update->refname, update->new_target)) {
ret = TRANSACTION_GENERIC_ERROR;
goto out;
@@ -2628,12 +2629,9 @@ static int lock_ref_for_update(struct files_ref_store *refs,
* phase of the transaction only needs to commit the lock.
*/
update->flags |= REF_NEEDS_COMMIT;
- }
-
-
- if ((update->flags & REF_HAVE_NEW) &&
- !(update->flags & REF_DELETING) &&
- !(update->flags & REF_LOG_ONLY)) {
+ } else if ((update->flags & REF_HAVE_NEW) &&
+ !(update->flags & REF_DELETING) &&
+ !(update->flags & REF_LOG_ONLY)) {
if (!(update->type & REF_ISSYMREF) &&
oideq(&lock->old_oid, &update->new_oid)) {
/*
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index e203c697f2..a00f55802a 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -908,7 +908,8 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
*/
new_update = ref_transaction_add_update(
transaction, referent.buf, new_flags,
- &u->new_oid, &u->old_oid, NULL, NULL, u->msg);
+ &u->new_oid, &u->old_oid, u->new_target,
+ u->old_target, u->msg);
new_update->parent_update = u;
/*
diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh
index 452fc1da50..3cfef9fbe3 100755
--- a/t/t1400-update-ref.sh
+++ b/t/t1400-update-ref.sh
@@ -1360,6 +1360,7 @@ test_expect_success 'fails with duplicate HEAD update' '
'
test_expect_success 'fails with duplicate ref update via symref' '
+ test_when_finished "git symbolic-ref -d refs/heads/symref2" &&
git branch target2 $A &&
git symbolic-ref refs/heads/symref2 refs/heads/target2 &&
cat >stdin <<-EOF &&
@@ -1852,6 +1853,201 @@ do
git reflog exists refs/heads/symref
'
+ test_expect_success "stdin ${type} symref-update fails with too many arguments" '
+ create_stdin_buf ${type} "symref-update refs/heads/symref" "$a" "ref" "$a" "$a" >stdin &&
+ test_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&
+ if test "$type" = "-z"
+ then
+ grep "fatal: unknown command: $a" err
+ else
+ grep "fatal: symref-update refs/heads/symref: extra input: $a" err
+ fi
+ '
+
+ test_expect_success "stdin ${type} symref-update fails with wrong old value argument" '
+ create_stdin_buf ${type} "symref-update refs/heads/symref" "$a" "foo" "$a" "$a" >stdin &&
+ test_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&
+ grep "fatal: symref-update refs/heads/symref: invalid arg ${SQ}foo${SQ} for old value" err
+ '
+
+ test_expect_success "stdin ${type} symref-update creates with zero old value" '
+ test_when_finished "git symbolic-ref -d refs/heads/symref" &&
+ create_stdin_buf ${type} "symref-update refs/heads/symref" "$a" "oid" "$Z" >stdin &&
+ git update-ref --stdin ${type} --no-deref <stdin &&
+ echo $a >expect &&
+ git symbolic-ref refs/heads/symref >actual &&
+ test_cmp expect actual
+ '
+
+ test_expect_success "stdin ${type} symref-update creates with no old value" '
+ test_when_finished "git symbolic-ref -d refs/heads/symref" &&
+ create_stdin_buf ${type} "symref-update refs/heads/symref" "$a" >stdin &&
+ git update-ref --stdin ${type} --no-deref <stdin &&
+ echo $a >expect &&
+ git symbolic-ref refs/heads/symref >actual &&
+ test_cmp expect actual
+ '
+
+ test_expect_success "stdin ${type} symref-update creates dangling" '
+ test_when_finished "git symbolic-ref -d refs/heads/symref" &&
+ test_must_fail git rev-parse refs/heads/nonexistent &&
+ create_stdin_buf ${type} "symref-update refs/heads/symref" "refs/heads/nonexistent" >stdin &&
+ git update-ref --stdin ${type} --no-deref <stdin &&
+ echo refs/heads/nonexistent >expect &&
+ git symbolic-ref refs/heads/symref >actual &&
+ test_cmp expect actual
+ '
+
+ test_expect_success "stdin ${type} symref-update fails with wrong old value" '
+ test_when_finished "git symbolic-ref -d refs/heads/symref" &&
+ git symbolic-ref refs/heads/symref $a &&
+ create_stdin_buf ${type} "symref-update refs/heads/symref" "$m" "ref" "$b" >stdin &&
+ test_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&
+ if test_have_prereq REFTABLE
+ then
+ grep "fatal: verifying symref target: ${SQ}refs/heads/symref${SQ}: is at $a but expected $b" err
+ else
+ grep "fatal: cannot lock ref ${SQ}refs/heads/symref${SQ}: is at $a but expected $b" err
+ fi &&
+ test_must_fail git rev-parse --verify -q $c
+ '
+
+ test_expect_success "stdin ${type} symref-update updates dangling ref" '
+ test_when_finished "git symbolic-ref -d refs/heads/symref" &&
+ test_must_fail git rev-parse refs/heads/nonexistent &&
+ git symbolic-ref refs/heads/symref refs/heads/nonexistent &&
+ create_stdin_buf ${type} "symref-update refs/heads/symref" "$a" >stdin &&
+ git update-ref --stdin ${type} --no-deref <stdin &&
+ echo $a >expect &&
+ git symbolic-ref refs/heads/symref >actual &&
+ test_cmp expect actual
+ '
+
+ test_expect_success "stdin ${type} symref-update updates dangling ref with old value" '
+ test_when_finished "git symbolic-ref -d refs/heads/symref" &&
+ test_must_fail git rev-parse refs/heads/nonexistent &&
+ git symbolic-ref refs/heads/symref refs/heads/nonexistent &&
+ create_stdin_buf ${type} "symref-update refs/heads/symref" "$a" "ref" "refs/heads/nonexistent" >stdin &&
+ git update-ref --stdin ${type} --no-deref <stdin &&
+ echo $a >expect &&
+ git symbolic-ref refs/heads/symref >actual &&
+ test_cmp expect actual
+ '
+
+ test_expect_success "stdin ${type} symref-update fails update dangling ref with wrong old value" '
+ test_when_finished "git symbolic-ref -d refs/heads/symref" &&
+ test_must_fail git rev-parse refs/heads/nonexistent &&
+ git symbolic-ref refs/heads/symref refs/heads/nonexistent &&
+ create_stdin_buf ${type} "symref-update refs/heads/symref" "$a" "ref" "refs/heads/wrongref" >stdin &&
+ test_must_fail git update-ref --stdin ${type} --no-deref <stdin &&
+ echo refs/heads/nonexistent >expect &&
+ git symbolic-ref refs/heads/symref >actual &&
+ test_cmp expect actual
+ '
+
+ test_expect_success "stdin ${type} symref-update works with right old value" '
+ test_when_finished "git symbolic-ref -d refs/heads/symref" &&
+ git symbolic-ref refs/heads/symref $a &&
+ create_stdin_buf ${type} "symref-update refs/heads/symref" "$m" "ref" "$a" >stdin &&
+ git update-ref --stdin ${type} --no-deref <stdin &&
+ echo $m >expect &&
+ git symbolic-ref refs/heads/symref >actual &&
+ test_cmp expect actual
+ '
+
+ test_expect_success "stdin ${type} symref-update works with no old value" '
+ test_when_finished "git symbolic-ref -d refs/heads/symref" &&
+ git symbolic-ref refs/heads/symref $a &&
+ create_stdin_buf ${type} "symref-update refs/heads/symref" "$m" >stdin &&
+ git update-ref --stdin ${type} --no-deref <stdin &&
+ echo $m >expect &&
+ git symbolic-ref refs/heads/symref >actual &&
+ test_cmp expect actual
+ '
+
+ test_expect_success "stdin ${type} symref-update fails with empty old ref-target" '
+ test_when_finished "git symbolic-ref -d refs/heads/symref" &&
+ git symbolic-ref refs/heads/symref $a &&
+ create_stdin_buf ${type} "symref-update refs/heads/symref" "$m" "ref" "" >stdin &&
+ test_must_fail git update-ref --stdin ${type} --no-deref <stdin &&
+ echo $a >expect &&
+ git symbolic-ref refs/heads/symref >actual &&
+ test_cmp expect actual
+ '
+
+ test_expect_success "stdin ${type} symref-update creates (with deref)" '
+ test_when_finished "git symbolic-ref -d refs/heads/symref" &&
+ create_stdin_buf ${type} "symref-update refs/heads/symref" "$a" >stdin &&
+ git update-ref --stdin ${type} <stdin &&
+ echo $a >expect &&
+ git symbolic-ref --no-recurse refs/heads/symref >actual &&
+ test_cmp expect actual &&
+ test-tool ref-store main for-each-reflog-ent refs/heads/symref >actual &&
+ grep "$Z $(git rev-parse $a)" actual
+ '
+
+ test_expect_success "stdin ${type} symref-update regular ref to symref with correct old-oid" '
+ test_when_finished "git symbolic-ref -d --no-recurse refs/heads/regularref" &&
+ git update-ref --no-deref refs/heads/regularref $a &&
+ create_stdin_buf ${type} "symref-update refs/heads/regularref" "$a" "oid" "$(git rev-parse $a)" >stdin &&
+ git update-ref --stdin ${type} <stdin &&
+ echo $a >expect &&
+ git symbolic-ref --no-recurse refs/heads/regularref >actual &&
+ test_cmp expect actual &&
+ test-tool ref-store main for-each-reflog-ent refs/heads/regularref >actual &&
+ grep "$(git rev-parse $a) $(git rev-parse $a)" actual
+ '
+
+ test_expect_success "stdin ${type} symref-update regular ref to symref fails with wrong old-oid" '
+ test_when_finished "git update-ref -d refs/heads/regularref" &&
+ git update-ref --no-deref refs/heads/regularref $a &&
+ create_stdin_buf ${type} "symref-update refs/heads/regularref" "$a" "oid" "$(git rev-parse refs/heads/target2)" >stdin &&
+ test_must_fail git update-ref --stdin ${type} <stdin 2>err &&
+ echo $(git rev-parse $a) >expect &&
+ git rev-parse refs/heads/regularref >actual &&
+ test_cmp expect actual
+ '
+
+ test_expect_success "stdin ${type} symref-update existing symref with zero old-oid" '
+ test_when_finished "git symbolic-ref -d --no-recurse refs/heads/symref" &&
+ git symbolic-ref refs/heads/symref refs/heads/target2 &&
+ create_stdin_buf ${type} "symref-update refs/heads/symref" "$a" "oid" "$Z" >stdin &&
+ test_must_fail git update-ref --stdin ${type} <stdin 2>err &&
+ grep "fatal: cannot lock ref ${SQ}refs/heads/symref${SQ}: reference already exists" err &&
+ echo refs/heads/target2 >expect &&
+ git symbolic-ref refs/heads/symref >actual &&
+ test_cmp expect actual
+ '
+
+ test_expect_success "stdin ${type} symref-update regular ref to symref (with deref)" '
+ test_when_finished "git symbolic-ref -d refs/heads/symref" &&
+ test_when_finished "git update-ref -d --no-deref refs/heads/symref2" &&
+ git update-ref refs/heads/symref2 $a &&
+ git symbolic-ref --no-recurse refs/heads/symref refs/heads/symref2 &&
+ create_stdin_buf ${type} "symref-update refs/heads/symref" "$a" >stdin &&
+ git update-ref ${type} --stdin <stdin &&
+ echo $a >expect &&
+ git symbolic-ref --no-recurse refs/heads/symref2 >actual &&
+ test_cmp expect actual &&
+ echo refs/heads/symref2 >expect &&
+ git symbolic-ref --no-recurse refs/heads/symref >actual &&
+ test_cmp expect actual &&
+ test-tool ref-store main for-each-reflog-ent refs/heads/symref >actual &&
+ grep "$(git rev-parse $a) $(git rev-parse $a)" actual
+ '
+
+ test_expect_success "stdin ${type} symref-update regular ref to symref" '
+ test_when_finished "git symbolic-ref -d --no-recurse refs/heads/regularref" &&
+ git update-ref --no-deref refs/heads/regularref $a &&
+ create_stdin_buf ${type} "symref-update refs/heads/regularref" "$a" >stdin &&
+ git update-ref ${type} --stdin <stdin &&
+ echo $a >expect &&
+ git symbolic-ref --no-recurse refs/heads/regularref >actual &&
+ test_cmp expect actual &&
+ test-tool ref-store main for-each-reflog-ent refs/heads/regularref >actual &&
+ grep "$(git rev-parse $a) $(git rev-parse $a)" actual
+ '
+
done
test_done
--
2.43.GIT
^ permalink raw reply related [flat|nested] 194+ messages in thread
* [PATCH v4 7/7] ref: support symrefs in 'reference-transaction' hook
2024-04-26 15:24 ` [PATCH v4 0/7] add symref-* commands to 'git-update-ref --stdin' Karthik Nayak
` (5 preceding siblings ...)
2024-04-26 15:24 ` [PATCH v4 6/7] update-ref: add support for 'symref-update' command Karthik Nayak
@ 2024-04-26 15:24 ` Karthik Nayak
2024-04-30 10:14 ` [PATCH v4 0/7] add symref-* commands to 'git-update-ref --stdin' Karthik Nayak
2024-05-01 20:22 ` [PATCH v5 0/7] refs: add support for transactional symref updates Karthik Nayak
8 siblings, 0 replies; 194+ messages in thread
From: Karthik Nayak @ 2024-04-26 15:24 UTC (permalink / raw)
To: karthik.188; +Cc: christian.couder, git, gitster, ps
From: Karthik Nayak <karthik.188@gmail.com>
The 'reference-transaction' hook runs whenever a reference update is
made to the system. In the previous commits, we added symref support for
various commands in `git-update-ref`. While it allowed us to now
manipulate symbolic refs via `git-update-ref`, it didn't activate the
'reference-transaction' hook.
Let's activate the hook for symbolic reference updates too. There is no
new format described for this and we stick to the existing format of:
<old-value> SP <new-value> SP <ref-name> LF
but now, <old-value> and <new-value> could also denote references
instead of objects, where the format is similar to that in
'git-update-ref', i.e. 'ref:<ref-target>'.
While this seems to be backward incompatible, it is okay, since the only
way the `reference-transaction` hook has refs in its output is when
`git-update-ref` is used to manipulate symrefs. Also the documentation
for reference-transaction hook always stated that support for symbolic
references may be added in the future.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
Documentation/githooks.txt | 14 +++++++----
refs.c | 21 ++++++++--------
t/t1416-ref-transaction-hooks.sh | 41 ++++++++++++++++++++++++++++++++
3 files changed, 61 insertions(+), 15 deletions(-)
diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
index ee9b92c90d..0bf8ca87a6 100644
--- a/Documentation/githooks.txt
+++ b/Documentation/githooks.txt
@@ -486,7 +486,7 @@ reference-transaction
This hook is invoked by any Git command that performs reference
updates. It executes whenever a reference transaction is prepared,
committed or aborted and may thus get called multiple times. The hook
-does not cover symbolic references (but that may change in the future).
+also cover symbolic references.
The hook takes exactly one argument, which is the current state the
given reference transaction is in:
@@ -503,16 +503,20 @@ given reference transaction is in:
For each reference update that was added to the transaction, the hook
receives on standard input a line of the format:
- <old-oid> SP <new-oid> SP <ref-name> LF
+ <old-value> SP <new-value> SP <ref-name> LF
-where `<old-oid>` is the old object name passed into the reference
-transaction, `<new-oid>` is the new object name to be stored in the
+where `<old-value>` is the old object name passed into the reference
+transaction, `<new-value>` is the new object name to be stored in the
ref and `<ref-name>` is the full name of the ref. When force updating
the reference regardless of its current value or when the reference is
-to be created anew, `<old-oid>` is the all-zeroes object name. To
+to be created anew, `<old-value>` is the all-zeroes object name. To
distinguish these cases, you can inspect the current value of
`<ref-name>` via `git rev-parse`.
+For symbolic reference updates the `<old_value>` and `<new-value>`
+fields could denote references instead of objects, denoted via the
+`ref:<ref-target>` format.
+
The exit status of the hook is ignored for any state except for the
"prepared" state. In the "prepared" state, a non-zero exit status will
cause the transaction to be aborted. The hook will not be called with
diff --git a/refs.c b/refs.c
index 42cb4126a7..9a510744a7 100644
--- a/refs.c
+++ b/refs.c
@@ -2365,18 +2365,19 @@ static int run_transaction_hook(struct ref_transaction *transaction,
for (i = 0; i < transaction->nr; i++) {
struct ref_update *update = transaction->updates[i];
+ strbuf_reset(&buf);
- /*
- * Skip reference transaction for symbolic refs.
- */
- if (update->new_target || update->old_target)
- continue;
+ if (update->flags & REF_HAVE_OLD && update->old_target)
+ strbuf_addf(&buf, "ref:%s ", update->old_target);
+ else
+ strbuf_addf(&buf, "%s ", oid_to_hex(&update->old_oid));
- strbuf_reset(&buf);
- strbuf_addf(&buf, "%s %s %s\n",
- oid_to_hex(&update->old_oid),
- oid_to_hex(&update->new_oid),
- update->refname);
+ if (update->flags & REF_HAVE_NEW && update->new_target)
+ strbuf_addf(&buf, "ref:%s ", update->new_target);
+ else
+ strbuf_addf(&buf, "%s ", oid_to_hex(&update->new_oid));
+
+ strbuf_addf(&buf, "%s\n", update->refname);
if (write_in_full(proc.in, buf.buf, buf.len) < 0) {
if (errno != EPIPE) {
diff --git a/t/t1416-ref-transaction-hooks.sh b/t/t1416-ref-transaction-hooks.sh
index 2092488090..0a7e86062e 100755
--- a/t/t1416-ref-transaction-hooks.sh
+++ b/t/t1416-ref-transaction-hooks.sh
@@ -134,4 +134,45 @@ test_expect_success 'interleaving hook calls succeed' '
test_cmp expect target-repo.git/actual
'
+# This test doesn't add a check for symref 'delete' since there is a
+# variation between the ref backends WRT 'delete'. In the files backend,
+# 'delete' also triggers an additional transaction update on the
+# packed-refs backend, which constitutes additional reflog entries.
+test_expect_success 'hook gets all queued symref updates' '
+ test_when_finished "rm actual" &&
+
+ git update-ref refs/heads/branch $POST_OID &&
+ git symbolic-ref refs/heads/symref refs/heads/main &&
+ git symbolic-ref refs/heads/symrefu refs/heads/main &&
+
+ test_hook reference-transaction <<-\EOF &&
+ echo "$*" >>actual
+ while read -r line
+ do
+ printf "%s\n" "$line"
+ done >>actual
+ EOF
+
+ cat >expect <<-EOF &&
+ prepared
+ ref:refs/heads/main $ZERO_OID refs/heads/symref
+ $ZERO_OID ref:refs/heads/main refs/heads/symrefc
+ ref:refs/heads/main ref:refs/heads/branch refs/heads/symrefu
+ committed
+ ref:refs/heads/main $ZERO_OID refs/heads/symref
+ $ZERO_OID ref:refs/heads/main refs/heads/symrefc
+ ref:refs/heads/main ref:refs/heads/branch refs/heads/symrefu
+ EOF
+
+ git update-ref --no-deref --stdin <<-EOF &&
+ start
+ symref-verify refs/heads/symref refs/heads/main
+ symref-create refs/heads/symrefc refs/heads/main
+ symref-update refs/heads/symrefu refs/heads/branch ref refs/heads/main
+ prepare
+ commit
+ EOF
+ test_cmp expect actual
+'
+
test_done
--
2.43.GIT
^ permalink raw reply related [flat|nested] 194+ messages in thread
* Re: [PATCH v4 0/7] add symref-* commands to 'git-update-ref --stdin'
2024-04-26 15:24 ` [PATCH v4 0/7] add symref-* commands to 'git-update-ref --stdin' Karthik Nayak
` (6 preceding siblings ...)
2024-04-26 15:24 ` [PATCH v4 7/7] ref: support symrefs in 'reference-transaction' hook Karthik Nayak
@ 2024-04-30 10:14 ` Karthik Nayak
2024-05-01 20:22 ` [PATCH v5 0/7] refs: add support for transactional symref updates Karthik Nayak
8 siblings, 0 replies; 194+ messages in thread
From: Karthik Nayak @ 2024-04-30 10:14 UTC (permalink / raw)
Cc: christian.couder, git, gitster, ps
[-- Attachment #1: Type: text/plain, Size: 2148 bytes --]
Karthik Nayak <karthik.188@gmail.com> writes:
> From: Karthik Nayak <karthik.188@gmail.com>
>
> The 'git-update-ref(1)' command allows transactional reference updates.
> But currently only supports regular reference updates. Meaning, if one
> wants to update HEAD (symbolic ref) in a transaction, there is no tool
> to do so.
>
> One option to obtain transactional updates for the HEAD ref is to
> manually create the HEAD.lock file and commit. This is intrusive, where
> the user needs to mimic internal git behavior. Also, this only works
> when using the files backend.
>
> At GitLab, we've been using the manual process till date, to allow users
> to set and change their default branch. But with the introduction of
> reftables as a reference backend, this becomes a necessity to be solved
> within git.
>
> This patch series goes about introducing a set of commands
> symref-{create,verify,delete,update} to work with symrefs complimenting
> the existing commands for the regular refs in the '--stdin' mode of
> 'git-update-ref'.
>
> The 'symref-verify' command can be used to verify if a symref exists and
> its existing value.
>
> The 'symref-create' command can be used to create a new symref.
>
> The 'symref-delete' command can be used to delete an existing symref while
> optionally checking its existing value.
>
> The 'symref-update' command can be used to update a symref, create a symref,
> delete a symref or even convert an existing regular ref to a symref. Wherein
> like the regular 'update' command, the zero OID can be used to create/delete
> a symref.
>
> While this series adds the commands and the required ground work, it only
> is accessile within the '--stdin' mode of 'git-update-ref'. However, it makes
> it easy to extend it further to the command line too, which will be present
> in a follow up series.
>
Also, I'll be pushing a new version with reduced scope soon. The idea
being that we can review/merge the part which adds the symref support to
the refs library first and then we can have a followup for exposing the
same to the users via git-update-ref. This should make it also easier
for reviewers.
[snip]
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 194+ messages in thread
* [PATCH v5 0/7] refs: add support for transactional symref updates
2024-04-26 15:24 ` [PATCH v4 0/7] add symref-* commands to 'git-update-ref --stdin' Karthik Nayak
` (7 preceding siblings ...)
2024-04-30 10:14 ` [PATCH v4 0/7] add symref-* commands to 'git-update-ref --stdin' Karthik Nayak
@ 2024-05-01 20:22 ` Karthik Nayak
2024-05-01 20:22 ` [PATCH v5 1/7] refs: accept symref values in `ref_transaction_update()` Karthik Nayak
` (8 more replies)
8 siblings, 9 replies; 194+ messages in thread
From: Karthik Nayak @ 2024-05-01 20:22 UTC (permalink / raw)
To: karthik.188; +Cc: christian.couder, git, gitster, ps
From: Karthik Nayak <karthik.188@gmail.com>
The patch series takes over from the existing patch series, wherein we
introduced symref-* commands to git-update-ref. Since there was a ton of
discussions on the UX of the patch series and its application, I thought it
would be best to shorten the series and split it into multiple smaller series.
This series adds transactional support for symrefs in the reference db. Then
we switch refs_create_symref() to start using transactions for symref updates.
This allows us to deprecate the create_symref code in the ref_storage_be
interface and remove all associated code which is no longer used.
The split was primarily done so we can merge the non-user facing parts of the
previous series. While pertaining the user facing components into another set
of patches wherein deeper discussion on the UX can be held without worrying
about the internal implementation. Also by using this new functionality in a
pre-existing command, we can leverage the existing tests to catch any
inconsistencies. One of which was how 'git-symbolic-ref' doesn't add reflog for
dangling symrefs, which I've modified my patch to do the same.
We also modify the reference transaction hook to support symrefs. For any symref
update the reference transaction hook will output the value with a 'ref:' prefix.
Previous versions:
V1: https://lore.kernel.org/git/20240330224623.579457-1-knayak@gitlab.com/
V2: https://lore.kernel.org/git/20240412095908.1134387-1-knayak@gitlab.com/
V3: https://lore.kernel.org/git/20240423212818.574123-1-knayak@gitlab.com/
V4: https://lore.kernel.org/r/20240426152449.228860-1-knayak@gitlab.com
Changes over v4 are:
- Dropped the patches for adding support in git-update-ref.
- Added changes to use transaction in 'refs_create_symref()' and
deprecate 'create_symref'.
- Cleaned up the commit messages, documentation and comments.
- Added better handling, like if 'old_target' is set, the value of the
reference is checked, irrelevant of its type.
Range diff:
1: 4a56e3ede4 ! 1: a354190905 refs: accept symref values in `ref_transaction[_add]_update`
@@ Metadata
Author: Karthik Nayak <karthik.188@gmail.com>
## Commit message ##
- refs: accept symref values in `ref_transaction[_add]_update`
+ refs: accept symref values in `ref_transaction_update()`
- The `ref_transaction[_add]_update` functions obtain ref information and
- flags to create a `ref_update` and add it to the transaction at hand.
+ The function `ref_transaction_update()` obtains ref information and
+ flags to create a `ref_update` and add them to the transaction at hand.
To extend symref support in transactions, we need to also accept the
- old and new ref targets and process it. In this commit, let's add the
- required parameters to the function and modify all call sites.
+ old and new ref targets and process it. This commit adds the required
+ parameters to the function and modifies all call sites.
The two parameters added are `new_target` and `old_target`. The
`new_target` is used to denote what the reference should point to when
- the transaction is applied.
+ the transaction is applied. Some functions allow this parameter to be
+ NULL, meaning that the reference is not changed.
The `old_target` denotes the value the reference must have before the
update. Some functions allow this parameter to be NULL, meaning that the
old value of the reference is not checked.
- The handling logic of these parameters will be added in consequent
- commits as we add symref commands to the '--stdin' mode of
- 'git-update-ref'.
+ We also update the internal function `ref_transaction_add_update()`
+ similarly to take the two new parameters.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
@@ refs.c: struct ref_update *ref_transaction_add_update(
BUG("update called for transaction that is not open");
+ if (old_oid && !is_null_oid(old_oid) && old_target)
-+ BUG("Only one of old_oid and old_target should be non NULL");
++ BUG("only one of old_oid and old_target should be non NULL");
+ if (new_oid && !is_null_oid(new_oid) && new_target)
-+ BUG("Only one of new_oid and new_target should be non NULL");
++ BUG("only one of new_oid and new_target should be non NULL");
+
FLEX_ALLOC_STR(update, refname, refname);
ALLOC_GROW(transaction->updates, transaction->nr + 1, transaction->alloc);
@@ refs.h: struct ref_transaction *ref_transaction_begin(struct strbuf *err);
* transaction.
*
+ * new_target -- the target reference that the reference will be
-+ * update to point to. This takes precedence over new_oid when
-+ * set. If the reference is a regular reference, it will be
-+ * converted to a symbolic reference.
++ * updated to point to. If the reference is a regular reference,
++ * it will be converted to a symbolic reference. Cannot be set
++ * together with `new_oid`. A copy of this value is made in the
++ * transaction.
+ *
+ * old_target -- the reference that the reference must be pointing to.
-+ * Will only be taken into account when the reference is a symbolic
-+ * reference.
++ * Canont be set together with `old_oid`. A copy of this value is
++ * made in the transaction.
+ *
* flags -- flags affecting the update, passed to
* update_ref_lock(). Possible flags: REF_NO_DEREF,
@@ refs/refs-internal.h: struct ref_update {
+ /*
+ * If set, point the reference to this value. This can also be
+ * used to convert regular references to become symbolic refs.
++ * Cannot be set together with `new_oid`.
+ */
+ const char *new_target;
+
+ /*
-+ * If set and the reference is a symbolic ref, check that the
-+ * reference previously pointed to this value.
++ * If set, check that the reference previously pointed to this
++ * value. Cannot be set together with `old_oid`.
+ */
+ const char *old_target;
+
2: 496bf14f28 ! 2: 7dff21dbef files-backend: extract out `create_symref_lock`
@@ Metadata
Author: Karthik Nayak <karthik.188@gmail.com>
## Commit message ##
- files-backend: extract out `create_symref_lock`
+ files-backend: extract out `create_symref_lock()`
- The function `create_symref_locked` creates a symref by creating a
+ The function `create_symref_locked()` creates a symref by creating a
'<symref>.lock' file and then committing the symref lock, which creates
the final symref.
- Split this into two individual functions `create_and_commit_symref` and
- `create_symref_locked`. This way we can create the symref lock and
- commit it at different times. This will be used to provide symref
- support in `git-update-ref(1)`.
+ Extract the early half of `create_symref_locked()` into a new helper
+ function `create_symref_lock()`. Because the name of the new function is
+ too similar to the original, rename the original to
+ `create_and_commit_symref()` to avoid confusion.
+
+ The new function `create_symref_locked()` can be used to create the
+ symref lock in a separate step from that of committing it. This allows
+ to add transactional support for symrefs, where the lock would be
+ created in the preparation step and the lock would be committed in the
+ finish step.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
@@ refs/files-backend.c: static void update_symref_reflog(struct files_ref_store *r
+ return error("unable to fdopen %s: %s",
+ get_lock_file_path(&lock->lk), strerror(errno));
+
-+ /* no error check; commit_ref will check ferror */
-+ fprintf(get_lock_file_fp(&lock->lk), "ref: %s\n", target);
++ if (fprintf(get_lock_file_fp(&lock->lk), "ref: %s\n", target) < 0)
++ return error("unable to fprintf %s: %s",
++ get_lock_file_path(&lock->lk), strerror(errno));
+ return 0;
+}
+
@@ refs/files-backend.c: static void update_symref_reflog(struct files_ref_store *r
- if (commit_ref(lock) < 0)
- return error("unable to write symref for %s: %s", refname,
- strerror(errno));
- return 0;
+- return 0;
++ return ret;
}
+ static int files_create_symref(struct ref_store *ref_store,
@@ refs/files-backend.c: static int files_create_symref(struct ref_store *ref_store,
return -1;
}
3: 6337859cbb < -: ---------- update-ref: add support for 'symref-verify' command
4: e611cb5a8c < -: ---------- update-ref: add support for 'symref-delete' command
5: 37f8be2f3f < -: ---------- update-ref: add support for 'symref-create' command
6: b385f4d0d7 < -: ---------- update-ref: add support for 'symref-update' command
7: ef335e47d1 < -: ---------- ref: support symrefs in 'reference-transaction' hook
-: ---------- > 3: 901a586683 refs: support symrefs in 'reference-transaction' hook
-: ---------- > 4: 6c97f6a660 refs: add support for transactional symref updates
-: ---------- > 5: 5b55406430 refs: use transaction in `refs_create_symref()`
-: ---------- > 6: 9e25816e68 refs: rename `refs_create_symref()` to `refs_update_symref()`
-: ---------- > 7: 3836e25932 refs: remove `create_symref` and associated dead code
Karthik Nayak (7):
refs: accept symref values in `ref_transaction_update()`
files-backend: extract out `create_symref_lock()`
refs: support symrefs in 'reference-transaction' hook
refs: add support for transactional symref updates
refs: use transaction in `refs_create_symref()`
refs: rename `refs_create_symref()` to `refs_update_symref()`
refs: remove `create_symref` and associated dead code
Documentation/githooks.txt | 14 ++-
branch.c | 2 +-
builtin/branch.c | 2 +-
builtin/fast-import.c | 5 +-
builtin/fetch.c | 2 +-
builtin/receive-pack.c | 1 +
builtin/replace.c | 2 +-
builtin/tag.c | 1 +
builtin/update-ref.c | 1 +
builtin/worktree.c | 2 +-
refs.c | 87 +++++++++++----
refs.h | 20 +++-
refs/debug.c | 13 ---
refs/files-backend.c | 185 +++++++++++++++++++------------
refs/packed-backend.c | 1 -
refs/refs-internal.h | 26 ++++-
refs/reftable-backend.c | 159 +++++++++-----------------
sequencer.c | 9 +-
t/helper/test-ref-store.c | 2 +-
t/t0610-reftable-basics.sh | 2 +-
t/t1416-ref-transaction-hooks.sh | 23 ++++
walker.c | 2 +-
22 files changed, 321 insertions(+), 240 deletions(-)
--
2.43.GIT
^ permalink raw reply [flat|nested] 194+ messages in thread
* [PATCH v5 1/7] refs: accept symref values in `ref_transaction_update()`
2024-05-01 20:22 ` [PATCH v5 0/7] refs: add support for transactional symref updates Karthik Nayak
@ 2024-05-01 20:22 ` Karthik Nayak
2024-05-01 20:22 ` [PATCH v5 2/7] files-backend: extract out `create_symref_lock()` Karthik Nayak
` (7 subsequent siblings)
8 siblings, 0 replies; 194+ messages in thread
From: Karthik Nayak @ 2024-05-01 20:22 UTC (permalink / raw)
To: karthik.188; +Cc: christian.couder, git, gitster, ps
From: Karthik Nayak <karthik.188@gmail.com>
The function `ref_transaction_update()` obtains ref information and
flags to create a `ref_update` and add them to the transaction at hand.
To extend symref support in transactions, we need to also accept the
old and new ref targets and process it. This commit adds the required
parameters to the function and modifies all call sites.
The two parameters added are `new_target` and `old_target`. The
`new_target` is used to denote what the reference should point to when
the transaction is applied. Some functions allow this parameter to be
NULL, meaning that the reference is not changed.
The `old_target` denotes the value the reference must have before the
update. Some functions allow this parameter to be NULL, meaning that the
old value of the reference is not checked.
We also update the internal function `ref_transaction_add_update()`
similarly to take the two new parameters.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
branch.c | 2 +-
builtin/fast-import.c | 5 +++--
builtin/fetch.c | 2 +-
builtin/receive-pack.c | 1 +
builtin/replace.c | 2 +-
builtin/tag.c | 1 +
builtin/update-ref.c | 1 +
refs.c | 22 +++++++++++++++++-----
refs.h | 18 +++++++++++++++++-
refs/files-backend.c | 12 ++++++------
refs/refs-internal.h | 14 ++++++++++++++
refs/reftable-backend.c | 4 ++--
sequencer.c | 9 +++++----
walker.c | 2 +-
14 files changed, 71 insertions(+), 24 deletions(-)
diff --git a/branch.c b/branch.c
index e4a738fc7b..48af4c3ceb 100644
--- a/branch.c
+++ b/branch.c
@@ -627,7 +627,7 @@ void create_branch(struct repository *r,
if (!transaction ||
ref_transaction_update(transaction, ref.buf,
&oid, forcing ? NULL : null_oid(),
- 0, msg, &err) ||
+ NULL, NULL, 0, msg, &err) ||
ref_transaction_commit(transaction, &err))
die("%s", err.buf);
ref_transaction_free(transaction);
diff --git a/builtin/fast-import.c b/builtin/fast-import.c
index dc5a9d32dd..297dfb91a1 100644
--- a/builtin/fast-import.c
+++ b/builtin/fast-import.c
@@ -1634,7 +1634,7 @@ static int update_branch(struct branch *b)
transaction = ref_transaction_begin(&err);
if (!transaction ||
ref_transaction_update(transaction, b->name, &b->oid, &old_oid,
- 0, msg, &err) ||
+ NULL, NULL, 0, msg, &err) ||
ref_transaction_commit(transaction, &err)) {
ref_transaction_free(transaction);
error("%s", err.buf);
@@ -1675,7 +1675,8 @@ static void dump_tags(void)
strbuf_addf(&ref_name, "refs/tags/%s", t->name);
if (ref_transaction_update(transaction, ref_name.buf,
- &t->oid, NULL, 0, msg, &err)) {
+ &t->oid, NULL, NULL, NULL,
+ 0, msg, &err)) {
failure |= error("%s", err.buf);
goto cleanup;
}
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 5857d860db..66840b7c5b 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -668,7 +668,7 @@ static int s_update_ref(const char *action,
ret = ref_transaction_update(transaction, ref->name, &ref->new_oid,
check_old ? &ref->old_oid : NULL,
- 0, msg, &err);
+ NULL, NULL, 0, msg, &err);
if (ret) {
ret = STORE_REF_ERROR_OTHER;
goto out;
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index e8d7df14b6..b150ef39a8 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -1595,6 +1595,7 @@ static const char *update(struct command *cmd, struct shallow_info *si)
if (ref_transaction_update(transaction,
namespaced_name,
new_oid, old_oid,
+ NULL, NULL,
0, "push",
&err)) {
rp_error("%s", err.buf);
diff --git a/builtin/replace.c b/builtin/replace.c
index da59600ad2..7690687b0e 100644
--- a/builtin/replace.c
+++ b/builtin/replace.c
@@ -201,7 +201,7 @@ static int replace_object_oid(const char *object_ref,
transaction = ref_transaction_begin(&err);
if (!transaction ||
ref_transaction_update(transaction, ref.buf, repl, &prev,
- 0, NULL, &err) ||
+ NULL, NULL, 0, NULL, &err) ||
ref_transaction_commit(transaction, &err))
res = error("%s", err.buf);
diff --git a/builtin/tag.c b/builtin/tag.c
index 9a33cb50b4..40a65fdebc 100644
--- a/builtin/tag.c
+++ b/builtin/tag.c
@@ -660,6 +660,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
transaction = ref_transaction_begin(&err);
if (!transaction ||
ref_transaction_update(transaction, ref.buf, &object, &prev,
+ NULL, NULL,
create_reflog ? REF_FORCE_CREATE_REFLOG : 0,
reflog_msg.buf, &err) ||
ref_transaction_commit(transaction, &err)) {
diff --git a/builtin/update-ref.c b/builtin/update-ref.c
index e46afbc46d..21fdbf6ac8 100644
--- a/builtin/update-ref.c
+++ b/builtin/update-ref.c
@@ -204,6 +204,7 @@ static void parse_cmd_update(struct ref_transaction *transaction,
if (ref_transaction_update(transaction, refname,
&new_oid, have_old ? &old_oid : NULL,
+ NULL, NULL,
update_flags | create_reflog_flag,
msg, &err))
die("%s", err.buf);
diff --git a/refs.c b/refs.c
index 55d2e0b2cb..47bc9dd103 100644
--- a/refs.c
+++ b/refs.c
@@ -1228,6 +1228,7 @@ struct ref_update *ref_transaction_add_update(
const char *refname, unsigned int flags,
const struct object_id *new_oid,
const struct object_id *old_oid,
+ const char *new_target, const char *old_target,
const char *msg)
{
struct ref_update *update;
@@ -1235,6 +1236,11 @@ struct ref_update *ref_transaction_add_update(
if (transaction->state != REF_TRANSACTION_OPEN)
BUG("update called for transaction that is not open");
+ if (old_oid && !is_null_oid(old_oid) && old_target)
+ BUG("only one of old_oid and old_target should be non NULL");
+ if (new_oid && !is_null_oid(new_oid) && new_target)
+ BUG("only one of new_oid and new_target should be non NULL");
+
FLEX_ALLOC_STR(update, refname, refname);
ALLOC_GROW(transaction->updates, transaction->nr + 1, transaction->alloc);
transaction->updates[transaction->nr++] = update;
@@ -1253,6 +1259,8 @@ int ref_transaction_update(struct ref_transaction *transaction,
const char *refname,
const struct object_id *new_oid,
const struct object_id *old_oid,
+ const char *new_target,
+ const char *old_target,
unsigned int flags, const char *msg,
struct strbuf *err)
{
@@ -1280,7 +1288,8 @@ int ref_transaction_update(struct ref_transaction *transaction,
flags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0);
ref_transaction_add_update(transaction, refname, flags,
- new_oid, old_oid, msg);
+ new_oid, old_oid, new_target,
+ old_target, msg);
return 0;
}
@@ -1295,7 +1304,8 @@ int ref_transaction_create(struct ref_transaction *transaction,
return 1;
}
return ref_transaction_update(transaction, refname, new_oid,
- null_oid(), flags, msg, err);
+ null_oid(), NULL, NULL, flags,
+ msg, err);
}
int ref_transaction_delete(struct ref_transaction *transaction,
@@ -1308,7 +1318,8 @@ int ref_transaction_delete(struct ref_transaction *transaction,
BUG("delete called with old_oid set to zeros");
return ref_transaction_update(transaction, refname,
null_oid(), old_oid,
- flags, msg, err);
+ NULL, NULL, flags,
+ msg, err);
}
int ref_transaction_verify(struct ref_transaction *transaction,
@@ -1321,6 +1332,7 @@ int ref_transaction_verify(struct ref_transaction *transaction,
BUG("verify called with old_oid set to NULL");
return ref_transaction_update(transaction, refname,
NULL, old_oid,
+ NULL, NULL,
flags, NULL, err);
}
@@ -1335,8 +1347,8 @@ int refs_update_ref(struct ref_store *refs, const char *msg,
t = ref_store_transaction_begin(refs, &err);
if (!t ||
- ref_transaction_update(t, refname, new_oid, old_oid, flags, msg,
- &err) ||
+ ref_transaction_update(t, refname, new_oid, old_oid, NULL, NULL,
+ flags, msg, &err) ||
ref_transaction_commit(t, &err)) {
ret = 1;
ref_transaction_free(t);
diff --git a/refs.h b/refs.h
index d278775e08..c7851bf587 100644
--- a/refs.h
+++ b/refs.h
@@ -648,6 +648,16 @@ struct ref_transaction *ref_transaction_begin(struct strbuf *err);
* before the update. A copy of this value is made in the
* transaction.
*
+ * new_target -- the target reference that the reference will be
+ * updated to point to. If the reference is a regular reference,
+ * it will be converted to a symbolic reference. Cannot be set
+ * together with `new_oid`. A copy of this value is made in the
+ * transaction.
+ *
+ * old_target -- the reference that the reference must be pointing to.
+ * Canont be set together with `old_oid`. A copy of this value is
+ * made in the transaction.
+ *
* flags -- flags affecting the update, passed to
* update_ref_lock(). Possible flags: REF_NO_DEREF,
* REF_FORCE_CREATE_REFLOG. See those constants for more
@@ -713,7 +723,11 @@ struct ref_transaction *ref_transaction_begin(struct strbuf *err);
* beforehand. The old value is checked after the lock is taken to
* prevent races. If the old value doesn't agree with old_oid, the
* whole transaction fails. If old_oid is NULL, then the previous
- * value is not checked.
+ * value is not checked. If `old_target` is not NULL, treat the reference
+ * as a symbolic ref and validate that its target before the update is
+ * `old_target`. If the `new_target` is not NULL, then the reference
+ * will be updated to a symbolic ref which targets `new_target`.
+ * Together, these allow us to update between regular refs and symrefs.
*
* See the above comment "Reference transaction updates" for more
* information.
@@ -722,6 +736,8 @@ int ref_transaction_update(struct ref_transaction *transaction,
const char *refname,
const struct object_id *new_oid,
const struct object_id *old_oid,
+ const char *new_target,
+ const char *old_target,
unsigned int flags, const char *msg,
struct strbuf *err);
diff --git a/refs/files-backend.c b/refs/files-backend.c
index a098d14ea0..e4d0aa3d41 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -1198,7 +1198,7 @@ static void prune_ref(struct files_ref_store *refs, struct ref_to_prune *r)
ref_transaction_add_update(
transaction, r->name,
REF_NO_DEREF | REF_HAVE_NEW | REF_HAVE_OLD | REF_IS_PRUNING,
- null_oid(), &r->oid, NULL);
+ null_oid(), &r->oid, NULL, NULL, NULL);
if (ref_transaction_commit(transaction, &err))
goto cleanup;
@@ -1292,7 +1292,7 @@ static int files_pack_refs(struct ref_store *ref_store,
* packed-refs transaction:
*/
if (ref_transaction_update(transaction, iter->refname,
- iter->oid, NULL,
+ iter->oid, NULL, NULL, NULL,
REF_NO_DEREF, NULL, &err))
die("failure preparing to create packed reference %s: %s",
iter->refname, err.buf);
@@ -2309,7 +2309,7 @@ static int split_head_update(struct ref_update *update,
transaction, "HEAD",
update->flags | REF_LOG_ONLY | REF_NO_DEREF,
&update->new_oid, &update->old_oid,
- update->msg);
+ NULL, NULL, update->msg);
/*
* Add "HEAD". This insertion is O(N) in the transaction
@@ -2372,7 +2372,7 @@ static int split_symref_update(struct ref_update *update,
new_update = ref_transaction_add_update(
transaction, referent, new_flags,
&update->new_oid, &update->old_oid,
- update->msg);
+ NULL, NULL, update->msg);
new_update->parent_update = update;
@@ -2763,7 +2763,7 @@ static int files_transaction_prepare(struct ref_store *ref_store,
packed_transaction, update->refname,
REF_HAVE_NEW | REF_NO_DEREF,
&update->new_oid, NULL,
- NULL);
+ NULL, NULL, NULL);
}
}
@@ -3048,7 +3048,7 @@ static int files_initial_transaction_commit(struct ref_store *ref_store,
ref_transaction_add_update(packed_transaction, update->refname,
update->flags & ~REF_HAVE_OLD,
&update->new_oid, &update->old_oid,
- NULL);
+ NULL, NULL, NULL);
}
if (packed_refs_lock(refs->packed_ref_store, 0, err)) {
diff --git a/refs/refs-internal.h b/refs/refs-internal.h
index 56641aa57a..108f4ec419 100644
--- a/refs/refs-internal.h
+++ b/refs/refs-internal.h
@@ -124,6 +124,19 @@ struct ref_update {
*/
struct object_id old_oid;
+ /*
+ * If set, point the reference to this value. This can also be
+ * used to convert regular references to become symbolic refs.
+ * Cannot be set together with `new_oid`.
+ */
+ const char *new_target;
+
+ /*
+ * If set, check that the reference previously pointed to this
+ * value. Cannot be set together with `old_oid`.
+ */
+ const char *old_target;
+
/*
* One or more of REF_NO_DEREF, REF_FORCE_CREATE_REFLOG,
* REF_HAVE_NEW, REF_HAVE_OLD, or backend-specific flags.
@@ -173,6 +186,7 @@ struct ref_update *ref_transaction_add_update(
const char *refname, unsigned int flags,
const struct object_id *new_oid,
const struct object_id *old_oid,
+ const char *new_target, const char *old_target,
const char *msg);
/*
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index 1cda48c504..6104471199 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -829,7 +829,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
new_update = ref_transaction_add_update(
transaction, "HEAD",
u->flags | REF_LOG_ONLY | REF_NO_DEREF,
- &u->new_oid, &u->old_oid, u->msg);
+ &u->new_oid, &u->old_oid, NULL, NULL, u->msg);
string_list_insert(&affected_refnames, new_update->refname);
}
@@ -908,7 +908,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
*/
new_update = ref_transaction_add_update(
transaction, referent.buf, new_flags,
- &u->new_oid, &u->old_oid, u->msg);
+ &u->new_oid, &u->old_oid, NULL, NULL, u->msg);
new_update->parent_update = u;
/*
diff --git a/sequencer.c b/sequencer.c
index 88de4dc20f..61e007d85f 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -665,7 +665,7 @@ static int fast_forward_to(struct repository *r,
if (!transaction ||
ref_transaction_update(transaction, "HEAD",
to, unborn && !is_rebase_i(opts) ?
- null_oid() : from,
+ null_oid() : from, NULL, NULL,
0, sb.buf, &err) ||
ref_transaction_commit(transaction, &err)) {
ref_transaction_free(transaction);
@@ -1298,7 +1298,7 @@ int update_head_with_reflog(const struct commit *old_head,
if (!transaction ||
ref_transaction_update(transaction, "HEAD", new_head,
old_head ? &old_head->object.oid : null_oid(),
- 0, sb.buf, err) ||
+ NULL, NULL, 0, sb.buf, err) ||
ref_transaction_commit(transaction, err)) {
ret = -1;
}
@@ -3832,8 +3832,9 @@ static int do_label(struct repository *r, const char *name, int len)
} else if (repo_get_oid(r, "HEAD", &head_oid)) {
error(_("could not read HEAD"));
ret = -1;
- } else if (ref_transaction_update(transaction, ref_name.buf, &head_oid,
- NULL, 0, msg.buf, &err) < 0 ||
+ } else if (ref_transaction_update(transaction, ref_name.buf,
+ &head_oid, NULL, NULL, NULL,
+ 0, msg.buf, &err) < 0 ||
ref_transaction_commit(transaction, &err)) {
error("%s", err.buf);
ret = -1;
diff --git a/walker.c b/walker.c
index c0fd632d92..1b3df43906 100644
--- a/walker.c
+++ b/walker.c
@@ -324,7 +324,7 @@ int walker_fetch(struct walker *walker, int targets, char **target,
strbuf_reset(&refname);
strbuf_addf(&refname, "refs/%s", write_ref[i]);
if (ref_transaction_update(transaction, refname.buf,
- oids + i, NULL, 0,
+ oids + i, NULL, NULL, NULL, 0,
msg ? msg : "fetch (unknown)",
&err)) {
error("%s", err.buf);
--
2.43.GIT
^ permalink raw reply related [flat|nested] 194+ messages in thread
* [PATCH v5 2/7] files-backend: extract out `create_symref_lock()`
2024-05-01 20:22 ` [PATCH v5 0/7] refs: add support for transactional symref updates Karthik Nayak
2024-05-01 20:22 ` [PATCH v5 1/7] refs: accept symref values in `ref_transaction_update()` Karthik Nayak
@ 2024-05-01 20:22 ` Karthik Nayak
2024-05-01 22:06 ` Junio C Hamano
2024-05-01 20:22 ` [PATCH v5 3/7] refs: support symrefs in 'reference-transaction' hook Karthik Nayak
` (6 subsequent siblings)
8 siblings, 1 reply; 194+ messages in thread
From: Karthik Nayak @ 2024-05-01 20:22 UTC (permalink / raw)
To: karthik.188; +Cc: christian.couder, git, gitster, ps
From: Karthik Nayak <karthik.188@gmail.com>
The function `create_symref_locked()` creates a symref by creating a
'<symref>.lock' file and then committing the symref lock, which creates
the final symref.
Extract the early half of `create_symref_locked()` into a new helper
function `create_symref_lock()`. Because the name of the new function is
too similar to the original, rename the original to
`create_and_commit_symref()` to avoid confusion.
The new function `create_symref_locked()` can be used to create the
symref lock in a separate step from that of committing it. This allows
to add transactional support for symrefs, where the lock would be
created in the preparation step and the lock would be committed in the
finish step.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
refs/files-backend.c | 43 +++++++++++++++++++++++++++++--------------
1 file changed, 29 insertions(+), 14 deletions(-)
diff --git a/refs/files-backend.c b/refs/files-backend.c
index e4d0aa3d41..878601ced0 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -1920,27 +1920,41 @@ static void update_symref_reflog(struct files_ref_store *refs,
}
}
-static int create_symref_locked(struct files_ref_store *refs,
- struct ref_lock *lock, const char *refname,
- const char *target, const char *logmsg)
+static int create_symref_lock(struct files_ref_store *refs,
+ struct ref_lock *lock, const char *refname,
+ const char *target)
{
+ if (!fdopen_lock_file(&lock->lk, "w"))
+ return error("unable to fdopen %s: %s",
+ get_lock_file_path(&lock->lk), strerror(errno));
+
+ if (fprintf(get_lock_file_fp(&lock->lk), "ref: %s\n", target) < 0)
+ return error("unable to fprintf %s: %s",
+ get_lock_file_path(&lock->lk), strerror(errno));
+ return 0;
+}
+
+static int create_and_commit_symref(struct files_ref_store *refs,
+ struct ref_lock *lock, const char *refname,
+ const char *target, const char *logmsg)
+{
+ int ret;
+
if (prefer_symlink_refs && !create_ref_symlink(lock, target)) {
update_symref_reflog(refs, lock, refname, target, logmsg);
return 0;
}
- if (!fdopen_lock_file(&lock->lk, "w"))
- return error("unable to fdopen %s: %s",
- get_lock_file_path(&lock->lk), strerror(errno));
+ ret = create_symref_lock(refs, lock, refname, target);
+ if (!ret) {
+ update_symref_reflog(refs, lock, refname, target, logmsg);
- update_symref_reflog(refs, lock, refname, target, logmsg);
+ if (commit_ref(lock) < 0)
+ return error("unable to write symref for %s: %s", refname,
+ strerror(errno));
+ }
- /* no error check; commit_ref will check ferror */
- fprintf(get_lock_file_fp(&lock->lk), "ref: %s\n", target);
- if (commit_ref(lock) < 0)
- return error("unable to write symref for %s: %s", refname,
- strerror(errno));
- return 0;
+ return ret;
}
static int files_create_symref(struct ref_store *ref_store,
@@ -1960,7 +1974,8 @@ static int files_create_symref(struct ref_store *ref_store,
return -1;
}
- ret = create_symref_locked(refs, lock, refname, target, logmsg);
+ ret = create_and_commit_symref(refs, lock, refname, target, logmsg);
+
unlock_ref(lock);
return ret;
}
--
2.43.GIT
^ permalink raw reply related [flat|nested] 194+ messages in thread
* Re: [PATCH v5 2/7] files-backend: extract out `create_symref_lock()`
2024-05-01 20:22 ` [PATCH v5 2/7] files-backend: extract out `create_symref_lock()` Karthik Nayak
@ 2024-05-01 22:06 ` Junio C Hamano
2024-05-02 7:47 ` Patrick Steinhardt
0 siblings, 1 reply; 194+ messages in thread
From: Junio C Hamano @ 2024-05-01 22:06 UTC (permalink / raw)
To: Karthik Nayak; +Cc: christian.couder, git, ps
Karthik Nayak <karthik.188@gmail.com> writes:
> + if (!fdopen_lock_file(&lock->lk, "w"))
> + return error("unable to fdopen %s: %s",
> + get_lock_file_path(&lock->lk), strerror(errno));
> +
> + if (fprintf(get_lock_file_fp(&lock->lk), "ref: %s\n", target) < 0)
> + return error("unable to fprintf %s: %s",
> + get_lock_file_path(&lock->lk), strerror(errno));
error() is end-user facing, so "fprintf" is probably a bit too
precise? "fprintf" -> "write to"
Also we may want to make them (not just this new message but other
error() messages in related code paths) localizable but that is
probably beyond the scope of this topic.
^ permalink raw reply [flat|nested] 194+ messages in thread
* Re: [PATCH v5 2/7] files-backend: extract out `create_symref_lock()`
2024-05-01 22:06 ` Junio C Hamano
@ 2024-05-02 7:47 ` Patrick Steinhardt
2024-05-02 11:05 ` Karthik Nayak
2024-05-02 16:49 ` Junio C Hamano
0 siblings, 2 replies; 194+ messages in thread
From: Patrick Steinhardt @ 2024-05-02 7:47 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Karthik Nayak, christian.couder, git
[-- Attachment #1: Type: text/plain, Size: 980 bytes --]
On Wed, May 01, 2024 at 03:06:19PM -0700, Junio C Hamano wrote:
> Karthik Nayak <karthik.188@gmail.com> writes:
>
> > + if (!fdopen_lock_file(&lock->lk, "w"))
> > + return error("unable to fdopen %s: %s",
> > + get_lock_file_path(&lock->lk), strerror(errno));
> > +
> > + if (fprintf(get_lock_file_fp(&lock->lk), "ref: %s\n", target) < 0)
> > + return error("unable to fprintf %s: %s",
> > + get_lock_file_path(&lock->lk), strerror(errno));
>
> error() is end-user facing, so "fprintf" is probably a bit too
> precise? "fprintf" -> "write to"
>
> Also we may want to make them (not just this new message but other
> error() messages in related code paths) localizable but that is
> probably beyond the scope of this topic.
It only occurred to me now, but shouldn't we also support passing in a
`struct strbuf *err` here? The transactional code doesn't want us to
print error messages to `stderr`, but always supplies a buffer.
Patrick
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 194+ messages in thread
* Re: [PATCH v5 2/7] files-backend: extract out `create_symref_lock()`
2024-05-02 7:47 ` Patrick Steinhardt
@ 2024-05-02 11:05 ` Karthik Nayak
2024-05-02 16:49 ` Junio C Hamano
1 sibling, 0 replies; 194+ messages in thread
From: Karthik Nayak @ 2024-05-02 11:05 UTC (permalink / raw)
To: Patrick Steinhardt, Junio C Hamano; +Cc: christian.couder, git
[-- Attachment #1: Type: text/plain, Size: 1093 bytes --]
Patrick Steinhardt <ps@pks.im> writes:
> On Wed, May 01, 2024 at 03:06:19PM -0700, Junio C Hamano wrote:
>> Karthik Nayak <karthik.188@gmail.com> writes:
>>
>> > + if (!fdopen_lock_file(&lock->lk, "w"))
>> > + return error("unable to fdopen %s: %s",
>> > + get_lock_file_path(&lock->lk), strerror(errno));
>> > +
>> > + if (fprintf(get_lock_file_fp(&lock->lk), "ref: %s\n", target) < 0)
>> > + return error("unable to fprintf %s: %s",
>> > + get_lock_file_path(&lock->lk), strerror(errno));
>>
>> error() is end-user facing, so "fprintf" is probably a bit too
>> precise? "fprintf" -> "write to"
>>
>> Also we may want to make them (not just this new message but other
>> error() messages in related code paths) localizable but that is
>> probably beyond the scope of this topic.
>
> It only occurred to me now, but shouldn't we also support passing in a
> `struct strbuf *err` here? The transactional code doesn't want us to
> print error messages to `stderr`, but always supplies a buffer.
>
> Patrick
Yes I think that would fit better with the existing transaction code.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 194+ messages in thread
* Re: [PATCH v5 2/7] files-backend: extract out `create_symref_lock()`
2024-05-02 7:47 ` Patrick Steinhardt
2024-05-02 11:05 ` Karthik Nayak
@ 2024-05-02 16:49 ` Junio C Hamano
1 sibling, 0 replies; 194+ messages in thread
From: Junio C Hamano @ 2024-05-02 16:49 UTC (permalink / raw)
To: Patrick Steinhardt; +Cc: Karthik Nayak, christian.couder, git
Patrick Steinhardt <ps@pks.im> writes:
> On Wed, May 01, 2024 at 03:06:19PM -0700, Junio C Hamano wrote:
>> Karthik Nayak <karthik.188@gmail.com> writes:
>>
>> > + if (!fdopen_lock_file(&lock->lk, "w"))
>> > + return error("unable to fdopen %s: %s",
>> > + get_lock_file_path(&lock->lk), strerror(errno));
>> > +
>> > + if (fprintf(get_lock_file_fp(&lock->lk), "ref: %s\n", target) < 0)
>> > + return error("unable to fprintf %s: %s",
>> > + get_lock_file_path(&lock->lk), strerror(errno));
>>
>> error() is end-user facing, so "fprintf" is probably a bit too
>> precise? "fprintf" -> "write to"
>>
>> Also we may want to make them (not just this new message but other
>> error() messages in related code paths) localizable but that is
>> probably beyond the scope of this topic.
>
> It only occurred to me now, but shouldn't we also support passing in a
> `struct strbuf *err` here? The transactional code doesn't want us to
> print error messages to `stderr`, but always supplies a buffer.
Sounds sensible. Thanks.
^ permalink raw reply [flat|nested] 194+ messages in thread
* [PATCH v5 3/7] refs: support symrefs in 'reference-transaction' hook
2024-05-01 20:22 ` [PATCH v5 0/7] refs: add support for transactional symref updates Karthik Nayak
2024-05-01 20:22 ` [PATCH v5 1/7] refs: accept symref values in `ref_transaction_update()` Karthik Nayak
2024-05-01 20:22 ` [PATCH v5 2/7] files-backend: extract out `create_symref_lock()` Karthik Nayak
@ 2024-05-01 20:22 ` Karthik Nayak
2024-05-01 23:05 ` Junio C Hamano
2024-05-01 20:22 ` [PATCH v5 4/7] refs: add support for transactional symref updates Karthik Nayak
` (5 subsequent siblings)
8 siblings, 1 reply; 194+ messages in thread
From: Karthik Nayak @ 2024-05-01 20:22 UTC (permalink / raw)
To: karthik.188; +Cc: christian.couder, git, gitster, ps
From: Karthik Nayak <karthik.188@gmail.com>
The 'reference-transaction' hook runs whenever a reference update is
made to the system. In a previous commit, we added the `old_target` and
`new_target` fields to the `reference_transaction_update()`. In
following commits we'll also add the code to handle symref's in the
reference backends.
Support symrefs also in the 'reference-transaction' hook, by modifying
the current format:
<old-oid> SP <new-oid> SP <ref-name> LF
to be be:
<old-value> SP <new-value> SP <ref-name> LF
where for regular refs the output would not change and remain the same.
But when either 'old-value' or 'new-value' is a symref, we print the ref
as 'ref:<ref-target>'.
This does break backward compatibility, but the 'reference-transaction'
hook's documentation always stated that support for symbolic references
may be added in the future.
We do not add any tests in this commit since there is no git command
which activates this flow, in an upcoming commit, we'll start using
transaction based symref updates as the default, we'll add tests there
for the hook too.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
Documentation/githooks.txt | 14 +++++++++-----
refs.c | 16 ++++++++++++----
2 files changed, 21 insertions(+), 9 deletions(-)
diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
index ee9b92c90d..06e997131b 100644
--- a/Documentation/githooks.txt
+++ b/Documentation/githooks.txt
@@ -486,7 +486,7 @@ reference-transaction
This hook is invoked by any Git command that performs reference
updates. It executes whenever a reference transaction is prepared,
committed or aborted and may thus get called multiple times. The hook
-does not cover symbolic references (but that may change in the future).
+also supports symbolic reference updates.
The hook takes exactly one argument, which is the current state the
given reference transaction is in:
@@ -503,16 +503,20 @@ given reference transaction is in:
For each reference update that was added to the transaction, the hook
receives on standard input a line of the format:
- <old-oid> SP <new-oid> SP <ref-name> LF
+ <old-value> SP <new-value> SP <ref-name> LF
-where `<old-oid>` is the old object name passed into the reference
-transaction, `<new-oid>` is the new object name to be stored in the
+where `<old-value>` is the old object name passed into the reference
+transaction, `<new-value>` is the new object name to be stored in the
ref and `<ref-name>` is the full name of the ref. When force updating
the reference regardless of its current value or when the reference is
-to be created anew, `<old-oid>` is the all-zeroes object name. To
+to be created anew, `<old-value>` is the all-zeroes object name. To
distinguish these cases, you can inspect the current value of
`<ref-name>` via `git rev-parse`.
+For symbolic reference updates the `<old_value>` and `<new-value>`
+fields could denote references instead of objects. A reference will be
+denoted with a 'ref:' prefix, like `ref:<ref-target>`.
+
The exit status of the hook is ignored for any state except for the
"prepared" state. In the "prepared" state, a non-zero exit status will
cause the transaction to be aborted. The hook will not be called with
diff --git a/refs.c b/refs.c
index 47bc9dd103..5dfe93060a 100644
--- a/refs.c
+++ b/refs.c
@@ -2350,10 +2350,18 @@ static int run_transaction_hook(struct ref_transaction *transaction,
struct ref_update *update = transaction->updates[i];
strbuf_reset(&buf);
- strbuf_addf(&buf, "%s %s %s\n",
- oid_to_hex(&update->old_oid),
- oid_to_hex(&update->new_oid),
- update->refname);
+
+ if (update->flags & REF_HAVE_OLD && update->old_target)
+ strbuf_addf(&buf, "ref:%s ", update->old_target);
+ else
+ strbuf_addf(&buf, "%s ", oid_to_hex(&update->old_oid));
+
+ if (update->flags & REF_HAVE_NEW && update->new_target)
+ strbuf_addf(&buf, "ref:%s ", update->new_target);
+ else
+ strbuf_addf(&buf, "%s ", oid_to_hex(&update->new_oid));
+
+ strbuf_addf(&buf, "%s\n", update->refname);
if (write_in_full(proc.in, buf.buf, buf.len) < 0) {
if (errno != EPIPE) {
--
2.43.GIT
^ permalink raw reply related [flat|nested] 194+ messages in thread
* Re: [PATCH v5 3/7] refs: support symrefs in 'reference-transaction' hook
2024-05-01 20:22 ` [PATCH v5 3/7] refs: support symrefs in 'reference-transaction' hook Karthik Nayak
@ 2024-05-01 23:05 ` Junio C Hamano
2024-05-02 5:32 ` Karthik Nayak
0 siblings, 1 reply; 194+ messages in thread
From: Junio C Hamano @ 2024-05-01 23:05 UTC (permalink / raw)
To: Karthik Nayak; +Cc: christian.couder, git, ps
Karthik Nayak <karthik.188@gmail.com> writes:
> + if (update->flags & REF_HAVE_OLD && update->old_target)
Although the precedence rule does not require it,
if ((update->flags & REF_HAVE_OLD) && update_old_target)
is probably easier to read.
> + strbuf_addf(&buf, "ref:%s ", update->old_target);
> + else
> + strbuf_addf(&buf, "%s ", oid_to_hex(&update->old_oid));
So the promise this code assumes is that .old_target member is
non-NULL if and only if the ref originally is a symbolic ref?
And if the "we do not care what the original value is, whether it is
a normal ref or a symbolic one" case, .old_oid would be all '\0' and
REF_HAVE_OLD bit is not set?
If we can write it like so:
if (!(update->flags & REF_HAVE_OLD))
strbuf_addf(&buf, "%s ", oid_to_hex(null_oid()));
else if (update->old_target)
strbuf_addf(&buf, "ref:%s ", update->old_target);
else
strbuf_addf(&buf, "ref:%s ", oid_to_hex(update->old_oid));
it may make the intent of the code a lot more clear. If we are
operating in "!HAVE_OLD" mode, we show 0{40}. Otherwise, old_target
is non-NULL when the thing is symbolic, and if old_target is NULL,
it is not symbolic and has its own value.
The same comment applies to the other side.
> + if (update->flags & REF_HAVE_NEW && update->new_target)
> + strbuf_addf(&buf, "ref:%s ", update->new_target);
> + else
> + strbuf_addf(&buf, "%s ", oid_to_hex(&update->new_oid));
> + strbuf_addf(&buf, "%s\n", update->refname);
>
> if (write_in_full(proc.in, buf.buf, buf.len) < 0) {
> if (errno != EPIPE) {
^ permalink raw reply [flat|nested] 194+ messages in thread
* Re: [PATCH v5 3/7] refs: support symrefs in 'reference-transaction' hook
2024-05-01 23:05 ` Junio C Hamano
@ 2024-05-02 5:32 ` Karthik Nayak
0 siblings, 0 replies; 194+ messages in thread
From: Karthik Nayak @ 2024-05-02 5:32 UTC (permalink / raw)
To: Junio C Hamano; +Cc: christian.couder, git, ps
[-- Attachment #1: Type: text/plain, Size: 1958 bytes --]
Junio C Hamano <gitster@pobox.com> writes:
> Karthik Nayak <karthik.188@gmail.com> writes:
>
>> + if (update->flags & REF_HAVE_OLD && update->old_target)
>
> Although the precedence rule does not require it,
>
> if ((update->flags & REF_HAVE_OLD) && update_old_target)
>
> is probably easier to read.
>
Will add.
>> + strbuf_addf(&buf, "ref:%s ", update->old_target);
>> + else
>> + strbuf_addf(&buf, "%s ", oid_to_hex(&update->old_oid));
>
> So the promise this code assumes is that .old_target member is
> non-NULL if and only if the ref originally is a symbolic ref?
>
Yes, for old_target this is correct. new_target could be set for a ref
to convert it to a symbolic ref.
> And if the "we do not care what the original value is, whether it is
> a normal ref or a symbolic one" case, .old_oid would be all '\0' and
> REF_HAVE_OLD bit is not set?
>
Yup that's accurate.
> If we can write it like so:
>
> if (!(update->flags & REF_HAVE_OLD))
> strbuf_addf(&buf, "%s ", oid_to_hex(null_oid()));
> else if (update->old_target)
> strbuf_addf(&buf, "ref:%s ", update->old_target);
> else
> strbuf_addf(&buf, "ref:%s ", oid_to_hex(update->old_oid));
>
> it may make the intent of the code a lot more clear. If we are
> operating in "!HAVE_OLD" mode, we show 0{40}. Otherwise, old_target
> is non-NULL when the thing is symbolic, and if old_target is NULL,
> it is not symbolic and has its own value.
>
> The same comment applies to the other side.
>
I see how it makes it clearer, but I think the intent with the existing
code was clear too. I'll add this change to my local for the next
version.
>> + if (update->flags & REF_HAVE_NEW && update->new_target)
>> + strbuf_addf(&buf, "ref:%s ", update->new_target);
>> + else
>> + strbuf_addf(&buf, "%s ", oid_to_hex(&update->new_oid));
>
>
>> + strbuf_addf(&buf, "%s\n", update->refname);
>>
>> if (write_in_full(proc.in, buf.buf, buf.len) < 0) {
>> if (errno != EPIPE) {
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 194+ messages in thread
* [PATCH v5 4/7] refs: add support for transactional symref updates
2024-05-01 20:22 ` [PATCH v5 0/7] refs: add support for transactional symref updates Karthik Nayak
` (2 preceding siblings ...)
2024-05-01 20:22 ` [PATCH v5 3/7] refs: support symrefs in 'reference-transaction' hook Karthik Nayak
@ 2024-05-01 20:22 ` Karthik Nayak
2024-05-01 23:52 ` Junio C Hamano
2024-05-02 17:53 ` Junio C Hamano
2024-05-01 20:22 ` [PATCH v5 5/7] refs: use transaction in `refs_create_symref()` Karthik Nayak
` (4 subsequent siblings)
8 siblings, 2 replies; 194+ messages in thread
From: Karthik Nayak @ 2024-05-01 20:22 UTC (permalink / raw)
To: karthik.188; +Cc: christian.couder, git, gitster, ps
From: Karthik Nayak <karthik.188@gmail.com>
The reference backends currently support transactional reference
updates. While this is exposed to users via 'git-update-ref' and its
'--stdin' mode, it is also used internally within various commands.
However, we never supported transactional updates of symrefs. Let's add
support for symrefs in both the 'files' and the 'reftable' backend.
Here, we add and use `ref_update_is_null_new_value()`, a helper function
which is used to check if there is a new_value in a reference update.
The new value could either be a symref target `new_target` or a OID
`new_oid`.
With this, now transactional updates (verify, create, delete, update)
can be used for:
- regular refs
- symbolic refs
- conversion of regular to symbolic refs and vice versa
This also allows us to expose this to users via new commands in
'git-update-ref' in the future.
We do not add reflog for dangling symref updates, because currently
'git-symbolic-ref' doesn't add reflog for dangling symref updates and it
would be best to keep this behavior consistent as we would move it to
start using transaction based updates in the following commit.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
refs.c | 17 +++++-
refs/files-backend.c | 112 +++++++++++++++++++++++++++++++++++-----
refs/refs-internal.h | 7 +++
refs/reftable-backend.c | 71 +++++++++++++++++++------
4 files changed, 176 insertions(+), 31 deletions(-)
diff --git a/refs.c b/refs.c
index 5dfe93060a..a4dca08244 100644
--- a/refs.c
+++ b/refs.c
@@ -1217,6 +1217,8 @@ void ref_transaction_free(struct ref_transaction *transaction)
for (i = 0; i < transaction->nr; i++) {
free(transaction->updates[i]->msg);
+ free((char *)transaction->updates[i]->new_target);
+ free((char *)transaction->updates[i]->old_target);
free(transaction->updates[i]);
}
free(transaction->updates);
@@ -1247,10 +1249,15 @@ struct ref_update *ref_transaction_add_update(
update->flags = flags;
- if (flags & REF_HAVE_NEW)
+ if (new_target)
+ update->new_target = xstrdup(new_target);
+ if (old_target)
+ update->old_target = xstrdup(old_target);
+ if (new_oid && flags & REF_HAVE_NEW)
oidcpy(&update->new_oid, new_oid);
- if (flags & REF_HAVE_OLD)
+ if (old_oid && flags & REF_HAVE_OLD)
oidcpy(&update->old_oid, old_oid);
+
update->msg = normalize_reflog_message(msg);
return update;
}
@@ -1286,6 +1293,7 @@ int ref_transaction_update(struct ref_transaction *transaction,
flags &= REF_TRANSACTION_UPDATE_ALLOWED_FLAGS;
flags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0);
+ flags |= (new_target ? REF_HAVE_NEW : 0) | (old_target ? REF_HAVE_OLD : 0);
ref_transaction_add_update(transaction, refname, flags,
new_oid, old_oid, new_target,
@@ -2810,3 +2818,8 @@ int copy_existing_ref(const char *oldref, const char *newref, const char *logmsg
{
return refs_copy_existing_ref(get_main_ref_store(the_repository), oldref, newref, logmsg);
}
+
+int ref_update_is_null_new_value(struct ref_update *update)
+{
+ return !update->new_target && is_null_oid(&update->new_oid);
+}
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 878601ced0..85c4af7e89 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -2426,6 +2426,36 @@ static const char *original_update_refname(struct ref_update *update)
return update->refname;
}
+/*
+ * Check whether the old_target values stored in update are consistent
+ * with current_target, which is the symbolic reference's current value.
+ * If everything is OK, return 0; otherwise, write an error message to
+ * err and return -1.
+ */
+static int check_old_target(struct ref_update *update,
+ const char *current_target,
+ struct strbuf *err)
+{
+ if (!update->old_target)
+ BUG("called without old_target set");
+
+ if (!strcmp(update->old_target, current_target))
+ return 0;
+
+ if (!strcmp(current_target, ""))
+ strbuf_addf(err, "cannot lock ref '%s': "
+ "reference is missing but expected %s",
+ original_update_refname(update),
+ update->old_target);
+ else
+ strbuf_addf(err, "cannot lock ref '%s': "
+ "is at %s but expected %s",
+ original_update_refname(update),
+ current_target, update->old_target);
+
+ return -1;
+}
+
/*
* Check whether the REF_HAVE_OLD and old_oid values stored in update
* are consistent with oid, which is the reference's current value. If
@@ -2486,7 +2516,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,
files_assert_main_repository(refs, "lock_ref_for_update");
- if ((update->flags & REF_HAVE_NEW) && is_null_oid(&update->new_oid))
+ if ((update->flags & REF_HAVE_NEW) && ref_update_is_null_new_value(update))
update->flags |= REF_DELETING;
if (head_ref) {
@@ -2529,7 +2559,14 @@ static int lock_ref_for_update(struct files_ref_store *refs,
ret = TRANSACTION_GENERIC_ERROR;
goto out;
}
- } else if (check_old_oid(update, &lock->old_oid, err)) {
+ }
+
+ if (update->old_target) {
+ if (check_old_target(update, referent.buf, err)) {
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto out;
+ }
+ } else if (check_old_oid(update, &lock->old_oid, err)) {
ret = TRANSACTION_GENERIC_ERROR;
goto out;
}
@@ -2550,7 +2587,17 @@ static int lock_ref_for_update(struct files_ref_store *refs,
} else {
struct ref_update *parent_update;
- if (check_old_oid(update, &lock->old_oid, err)) {
+ /*
+ * Even if the ref is a regular ref, if `old_target` is set, we
+ * check the referent value. Ideally `old_target` should only
+ * be set for symrefs, but we're strict about its usage.
+ */
+ if (update->old_target) {
+ if (check_old_target(update, referent.buf, err)) {
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto out;
+ }
+ } else if (check_old_oid(update, &lock->old_oid, err)) {
ret = TRANSACTION_GENERIC_ERROR;
goto out;
}
@@ -2568,9 +2615,27 @@ static int lock_ref_for_update(struct files_ref_store *refs,
}
}
- if ((update->flags & REF_HAVE_NEW) &&
- !(update->flags & REF_DELETING) &&
- !(update->flags & REF_LOG_ONLY)) {
+ if (update->new_target && !(update->flags & REF_LOG_ONLY)) {
+ if (create_symref_lock(refs, lock, update->refname, update->new_target)) {
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto out;
+ }
+
+ if (close_ref_gently(lock)) {
+ strbuf_addf(err, "couldn't close '%s.lock'",
+ update->refname);
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto out;
+ }
+
+ /*
+ * Once we have created the symref lock, the commit
+ * phase of the transaction only needs to commit the lock.
+ */
+ update->flags |= REF_NEEDS_COMMIT;
+ } else if ((update->flags & REF_HAVE_NEW) &&
+ !(update->flags & REF_DELETING) &&
+ !(update->flags & REF_LOG_ONLY)) {
if (!(update->type & REF_ISSYMREF) &&
oideq(&lock->old_oid, &update->new_oid)) {
/*
@@ -2863,12 +2928,26 @@ static int files_transaction_finish(struct ref_store *ref_store,
if (update->flags & REF_NEEDS_COMMIT ||
update->flags & REF_LOG_ONLY) {
- if (files_log_ref_write(refs,
- lock->ref_name,
- &lock->old_oid,
- &update->new_oid,
- update->msg, update->flags,
- err)) {
+ int create_reflog = 1;
+
+ if (update->new_target) {
+ /*
+ * We want to get the resolved OID for the target, to ensure
+ * that the correct value is added to the reflog.
+ */
+ if (!refs_resolve_ref_unsafe(&refs->base, update->new_target,
+ RESOLVE_REF_READING, &update->new_oid, NULL)) {
+ /* for dangling symrefs we skip creating a reflog entry. */
+ create_reflog = 0;
+ }
+ }
+
+ if (create_reflog && files_log_ref_write(refs,
+ lock->ref_name,
+ &lock->old_oid,
+ &update->new_oid,
+ update->msg, update->flags,
+ err)) {
char *old_msg = strbuf_detach(err, NULL);
strbuf_addf(err, "cannot update the ref '%s': %s",
@@ -2880,6 +2959,15 @@ static int files_transaction_finish(struct ref_store *ref_store,
goto cleanup;
}
}
+
+ /*
+ * We try creating a symlink, if that succeeds we continue to the
+ * next update. If not, we try and create a regular symref.
+ */
+ if (update->new_target && prefer_symlink_refs)
+ if (!create_ref_symlink(lock, update->new_target))
+ continue;
+
if (update->flags & REF_NEEDS_COMMIT) {
clear_loose_ref_cache(refs);
if (commit_ref(lock)) {
diff --git a/refs/refs-internal.h b/refs/refs-internal.h
index 108f4ec419..9578665243 100644
--- a/refs/refs-internal.h
+++ b/refs/refs-internal.h
@@ -749,4 +749,11 @@ void base_ref_store_init(struct ref_store *refs, struct repository *repo,
*/
struct ref_store *maybe_debug_wrap_ref_store(const char *gitdir, struct ref_store *store);
+/*
+ * Helper function to check if the new value is null, this
+ * takes into consideration that the update could be a regular
+ * ref or a symbolic ref.
+ */
+int ref_update_is_null_new_value(struct ref_update *update);
+
#endif /* REFS_REFS_INTERNAL_H */
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index 6104471199..5e8a696d40 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -856,7 +856,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
* There is no need to write the reference deletion
* when the reference in question doesn't exist.
*/
- if (u->flags & REF_HAVE_NEW && !is_null_oid(&u->new_oid)) {
+ if (u->flags & REF_HAVE_NEW && !ref_update_is_null_new_value(u)) {
ret = queue_transaction_update(refs, tx_data, u,
¤t_oid, err);
if (ret)
@@ -907,8 +907,10 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
* intertwined with the locking in files-backend.c.
*/
new_update = ref_transaction_add_update(
- transaction, referent.buf, new_flags,
- &u->new_oid, &u->old_oid, NULL, NULL, u->msg);
+ transaction, referent.buf, new_flags,
+ &u->new_oid, &u->old_oid, u->new_target,
+ u->old_target, u->msg);
+
new_update->parent_update = u;
/*
@@ -938,7 +940,22 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
* individual refs. But the error messages match what the files
* backend returns, which keeps our tests happy.
*/
- if (u->flags & REF_HAVE_OLD && !oideq(¤t_oid, &u->old_oid)) {
+ if (u->old_target) {
+ if (strcmp(referent.buf, u->old_target)) {
+ if (!strcmp(referent.buf, ""))
+ strbuf_addf(err, "verifying symref target: '%s': "
+ "reference is missing but expected %s",
+ original_update_refname(u),
+ u->old_target);
+ else
+ strbuf_addf(err, "verifying symref target: '%s': "
+ "is at %s but expected %s",
+ original_update_refname(u),
+ referent.buf, u->old_target);
+ ret = -1;
+ goto done;
+ }
+ } else if (u->flags & REF_HAVE_OLD && !oideq(¤t_oid, &u->old_oid)) {
if (is_null_oid(&u->old_oid))
strbuf_addf(err, _("cannot lock ref '%s': "
"reference already exists"),
@@ -1043,7 +1060,7 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data
* - `core.logAllRefUpdates` tells us to create the reflog for
* the given ref.
*/
- if (u->flags & REF_HAVE_NEW && !(u->type & REF_ISSYMREF) && is_null_oid(&u->new_oid)) {
+ if (u->flags & REF_HAVE_NEW && !(u->type & REF_ISSYMREF) && ref_update_is_null_new_value(u)) {
struct reftable_log_record log = {0};
struct reftable_iterator it = {0};
@@ -1084,24 +1101,44 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data
(u->flags & REF_FORCE_CREATE_REFLOG ||
should_write_log(&arg->refs->base, u->refname))) {
struct reftable_log_record *log;
+ int create_reflog = 1;
- ALLOC_GROW(logs, logs_nr + 1, logs_alloc);
- log = &logs[logs_nr++];
- memset(log, 0, sizeof(*log));
-
- fill_reftable_log_record(log);
- log->update_index = ts;
- log->refname = xstrdup(u->refname);
- memcpy(log->value.update.new_hash, u->new_oid.hash, GIT_MAX_RAWSZ);
- memcpy(log->value.update.old_hash, tx_update->current_oid.hash, GIT_MAX_RAWSZ);
- log->value.update.message =
- xstrndup(u->msg, arg->refs->write_options.block_size / 2);
+ if (u->new_target)
+ if (!refs_resolve_ref_unsafe(&arg->refs->base, u->new_target,
+ RESOLVE_REF_READING, &u->new_oid, NULL))
+ /* for dangling symrefs we skip creating reflog */
+ create_reflog = 0;
+
+ if (create_reflog) {
+ ALLOC_GROW(logs, logs_nr + 1, logs_alloc);
+ log = &logs[logs_nr++];
+ memset(log, 0, sizeof(*log));
+
+ fill_reftable_log_record(log);
+ log->update_index = ts;
+ log->refname = xstrdup(u->refname);
+ memcpy(log->value.update.new_hash, u->new_oid.hash, GIT_MAX_RAWSZ);
+ memcpy(log->value.update.old_hash, tx_update->current_oid.hash, GIT_MAX_RAWSZ);
+ log->value.update.message =
+ xstrndup(u->msg, arg->refs->write_options.block_size / 2);
+ }
}
if (u->flags & REF_LOG_ONLY)
continue;
- if (u->flags & REF_HAVE_NEW && is_null_oid(&u->new_oid)) {
+ if (u->flags & REF_HAVE_NEW && u->new_target) {
+ struct reftable_ref_record ref = {
+ .refname = (char *)u->refname,
+ .value_type = REFTABLE_REF_SYMREF,
+ .value.symref = (char *)u->new_target,
+ .update_index = ts,
+ };
+
+ ret = reftable_writer_add_ref(writer, &ref);
+ if (ret < 0)
+ goto done;
+ } else if (u->flags & REF_HAVE_NEW && ref_update_is_null_new_value(u)) {
struct reftable_ref_record ref = {
.refname = (char *)u->refname,
.update_index = ts,
--
2.43.GIT
^ permalink raw reply related [flat|nested] 194+ messages in thread
* Re: [PATCH v5 4/7] refs: add support for transactional symref updates
2024-05-01 20:22 ` [PATCH v5 4/7] refs: add support for transactional symref updates Karthik Nayak
@ 2024-05-01 23:52 ` Junio C Hamano
2024-05-02 5:50 ` Karthik Nayak
2024-05-02 17:53 ` Junio C Hamano
1 sibling, 1 reply; 194+ messages in thread
From: Junio C Hamano @ 2024-05-01 23:52 UTC (permalink / raw)
To: Karthik Nayak; +Cc: christian.couder, git, ps
Karthik Nayak <karthik.188@gmail.com> writes:
> From: Karthik Nayak <karthik.188@gmail.com>
>
> The reference backends currently support transactional reference
> updates. While this is exposed to users via 'git-update-ref' and its
> '--stdin' mode, it is also used internally within various commands.
>
> However, we never supported transactional updates of symrefs. Let's add
> support for symrefs in both the 'files' and the 'reftable' backend.
>
> Here, we add and use `ref_update_is_null_new_value()`, a helper function
> which is used to check if there is a new_value in a reference update.
I know you want to express a condition where you answer yes to "Is
the new value specified in this ref update NULL?", but "is" at that
position in the name somehow sounds awkward. Any of
ref_update_has_null_new_value
ref_update_with_no_new_value
ref_update_without_new_value
might be nicer to ears.
> We do not add reflog for dangling symref updates, because currently
> 'git-symbolic-ref' doesn't add reflog for dangling symref updates and it
> would be best to keep this behavior consistent as we would move it to
> start using transaction based updates in the following commit.
If we are not changing the behaviour, does it deserve a four-line
paragraph? It is not like we describe every no changes (i.e. "we
could break the behaviour by introducing this and that bugs, but we
did not" is not something we usually say in proposed log messages).
At most, if you want to highlight that behaviour, I would expect a
brief mention like:
Note that a dangling symref update does not record a new reflog
entry, which is unchanged before and after this commit.
As a reflog entry records name of the object that is pointed by the
ref (either directly or indirectly) before and after an operation,
an operation that involve a dangling reflog that does not point at
any object cannot be expressed in a reflog, no? It is way too late
to change this, but it would have been interesting if the design of
reflog had a room to log the change of symbolic ref target as well
as object names. It would have allowed us to say "HEAD at time T
pointed at refs/heads/main (which did not exist)", "HEAD at time T+1
directly pointed at commit X (detached)", "HEAD at time T+2 pointed
at refs/heads/next", etc. and allowed us to much more cleanly
support "previous branch".
> @@ -1247,10 +1249,15 @@ struct ref_update *ref_transaction_add_update(
>
> update->flags = flags;
>
> - if (flags & REF_HAVE_NEW)
> + if (new_target)
> + update->new_target = xstrdup(new_target);
> + if (old_target)
> + update->old_target = xstrdup(old_target);
"Is the assumption that *update is 0-initialized?" was the first
question that came to my mind.
Doing an unconditional
update->new_target = xstrdup_or_null(new_target);
update->old_target = xstrdup_or_null(old_target);
would convey the intention much more clearly without having readers
guess the answer to the above question.
> + if (new_oid && flags & REF_HAVE_NEW)
Even though syntactically not required,
if (new_oid && (flags & REF_HAVE_NEW))
or better yet
if ((flags & REF_HAVE_NEW) && new_oid)
would be easier to see.
> oidcpy(&update->new_oid, new_oid);
Again is the expectation that update->new_oid is initialized to
all-0? I am wondering if we want an else clause, i.e.
if (!(flags & REF_HAVE_NEW))
oidcpy(&update->new_oid, null_oid());
else
oidcpy(&update->new_oid, new_oid ? new_oid : null_oid());
to clarify the intention of the code, since the way you wrote the
consumer of thes two members and REF_HAVE_NEW bit in the previous
step implied that the new_oid member gets used even when REF_HAVE_*
bit is off, only for its null_oid() value.
I'll stop here for now.
Thanks.
^ permalink raw reply [flat|nested] 194+ messages in thread
* Re: [PATCH v5 4/7] refs: add support for transactional symref updates
2024-05-01 23:52 ` Junio C Hamano
@ 2024-05-02 5:50 ` Karthik Nayak
2024-05-02 7:47 ` Patrick Steinhardt
2024-05-02 16:00 ` Junio C Hamano
0 siblings, 2 replies; 194+ messages in thread
From: Karthik Nayak @ 2024-05-02 5:50 UTC (permalink / raw)
To: Junio C Hamano; +Cc: christian.couder, git, ps
[-- Attachment #1: Type: text/plain, Size: 5013 bytes --]
Junio C Hamano <gitster@pobox.com> writes:
> Karthik Nayak <karthik.188@gmail.com> writes:
>
>> From: Karthik Nayak <karthik.188@gmail.com>
>>
>> The reference backends currently support transactional reference
>> updates. While this is exposed to users via 'git-update-ref' and its
>> '--stdin' mode, it is also used internally within various commands.
>>
>> However, we never supported transactional updates of symrefs. Let's add
>> support for symrefs in both the 'files' and the 'reftable' backend.
>>
>> Here, we add and use `ref_update_is_null_new_value()`, a helper function
>> which is used to check if there is a new_value in a reference update.
>
> I know you want to express a condition where you answer yes to "Is
> the new value specified in this ref update NULL?", but "is" at that
> position in the name somehow sounds awkward. Any of
>
> ref_update_has_null_new_value
> ref_update_with_no_new_value
> ref_update_without_new_value
>
> might be nicer to ears.
>
Yes, thanks with this, I do agree that `ref_update_has_null_new_value`
sounds better.
>> We do not add reflog for dangling symref updates, because currently
>> 'git-symbolic-ref' doesn't add reflog for dangling symref updates and it
>> would be best to keep this behavior consistent as we would move it to
>> start using transaction based updates in the following commit.
>
> If we are not changing the behaviour, does it deserve a four-line
> paragraph? It is not like we describe every no changes (i.e. "we
> could break the behaviour by introducing this and that bugs, but we
> did not" is not something we usually say in proposed log messages).
>
> At most, if you want to highlight that behaviour, I would expect a
> brief mention like:
>
> Note that a dangling symref update does not record a new reflog
> entry, which is unchanged before and after this commit.
>
> As a reflog entry records name of the object that is pointed by the
> ref (either directly or indirectly) before and after an operation,
> an operation that involve a dangling reflog that does not point at
> any object cannot be expressed in a reflog, no? It is way too late
> to change this, but it would have been interesting if the design of
> reflog had a room to log the change of symbolic ref target as well
> as object names. It would have allowed us to say "HEAD at time T
> pointed at refs/heads/main (which did not exist)", "HEAD at time T+1
> directly pointed at commit X (detached)", "HEAD at time T+2 pointed
> at refs/heads/next", etc. and allowed us to much more cleanly
> support "previous branch".
>
While I agree that four lines may seem excessive, I think it is indeed
an important point to note. Mostly because this shows that when doing
dangling symref updates, there is no record of this update. The best
situation would be like you mentioned, to record the symref target
changes. But even with the current design, it would have been nice to at
least acknowledge that there was some update done to the symref. By
having zero-oid for the new and old value in the reflog. But seems like
we can't do that either.
>> @@ -1247,10 +1249,15 @@ struct ref_update *ref_transaction_add_update(
>>
>> update->flags = flags;
>>
>> - if (flags & REF_HAVE_NEW)
>> + if (new_target)
>> + update->new_target = xstrdup(new_target);
>> + if (old_target)
>> + update->old_target = xstrdup(old_target);
>
> "Is the assumption that *update is 0-initialized?" was the first
> question that came to my mind.
>
> Doing an unconditional
>
> update->new_target = xstrdup_or_null(new_target);
> update->old_target = xstrdup_or_null(old_target);
>
> would convey the intention much more clearly without having readers
> guess the answer to the above question.
>
Right, I didn't catch the nuance last time, thanks for the explanation.
>> + if (new_oid && flags & REF_HAVE_NEW)
>
> Even though syntactically not required,
>
> if (new_oid && (flags & REF_HAVE_NEW))
>
> or better yet
>
> if ((flags & REF_HAVE_NEW) && new_oid)
>
> would be easier to see.
>
>> oidcpy(&update->new_oid, new_oid);
>
> Again is the expectation that update->new_oid is initialized to
> all-0? I am wondering if we want an else clause, i.e.
>
> if (!(flags & REF_HAVE_NEW))
> oidcpy(&update->new_oid, null_oid());
> else
> oidcpy(&update->new_oid, new_oid ? new_oid : null_oid());
>
> to clarify the intention of the code, since the way you wrote the
> consumer of thes two members and REF_HAVE_NEW bit in the previous
> step implied that the new_oid member gets used even when REF_HAVE_*
> bit is off, only for its null_oid() value.
>
Yes I understand what you're saying, but since we're doing a
`FLEX_ALLOC_MEM` right above this code, I thought the fact that the
'update' struct is 0 initialized is known. With this, and the fact that
here `update->new_oid` is a static variable, while `new_oid` is a
pointer. I think being too verbose is not required.
> I'll stop here for now.
>
> Thanks.
>
Thanks Junio for taking the time to review.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 194+ messages in thread
* Re: [PATCH v5 4/7] refs: add support for transactional symref updates
2024-05-02 5:50 ` Karthik Nayak
@ 2024-05-02 7:47 ` Patrick Steinhardt
2024-05-02 11:10 ` Karthik Nayak
2024-05-02 16:51 ` Junio C Hamano
2024-05-02 16:00 ` Junio C Hamano
1 sibling, 2 replies; 194+ messages in thread
From: Patrick Steinhardt @ 2024-05-02 7:47 UTC (permalink / raw)
To: Karthik Nayak; +Cc: Junio C Hamano, christian.couder, git
[-- Attachment #1: Type: text/plain, Size: 2947 bytes --]
On Thu, May 02, 2024 at 05:50:47AM +0000, Karthik Nayak wrote:
> Junio C Hamano <gitster@pobox.com> writes:
> > Karthik Nayak <karthik.188@gmail.com> writes:
> >> From: Karthik Nayak <karthik.188@gmail.com>
> >> We do not add reflog for dangling symref updates, because currently
> >> 'git-symbolic-ref' doesn't add reflog for dangling symref updates and it
> >> would be best to keep this behavior consistent as we would move it to
> >> start using transaction based updates in the following commit.
> >
> > If we are not changing the behaviour, does it deserve a four-line
> > paragraph? It is not like we describe every no changes (i.e. "we
> > could break the behaviour by introducing this and that bugs, but we
> > did not" is not something we usually say in proposed log messages).
> >
> > At most, if you want to highlight that behaviour, I would expect a
> > brief mention like:
> >
> > Note that a dangling symref update does not record a new reflog
> > entry, which is unchanged before and after this commit.
> >
> > As a reflog entry records name of the object that is pointed by the
> > ref (either directly or indirectly) before and after an operation,
> > an operation that involve a dangling reflog that does not point at
> > any object cannot be expressed in a reflog, no? It is way too late
> > to change this, but it would have been interesting if the design of
> > reflog had a room to log the change of symbolic ref target as well
> > as object names. It would have allowed us to say "HEAD at time T
> > pointed at refs/heads/main (which did not exist)", "HEAD at time T+1
> > directly pointed at commit X (detached)", "HEAD at time T+2 pointed
> > at refs/heads/next", etc. and allowed us to much more cleanly
> > support "previous branch".
> >
>
> While I agree that four lines may seem excessive, I think it is indeed
> an important point to note. Mostly because this shows that when doing
> dangling symref updates, there is no record of this update. The best
> situation would be like you mentioned, to record the symref target
> changes. But even with the current design, it would have been nice to at
> least acknowledge that there was some update done to the symref. By
> having zero-oid for the new and old value in the reflog. But seems like
> we can't do that either.
I wouldn't say we can't do that. We already do log when symrefs become
dangling when updating references via HEAD by logging a zero OID as new
OID. That is, if we have "HEAD -> refs/heads/foo" and you delete the
latter, then we create a new reflog message for "HEAD" with zero OID as
new OID.
I would claim that the current behaviour where we don't create a reflog
entry when updating a ref to become dangling is a mere bug. I think it's
fair to declare this a #leftoverbit and handle it in a follow-up patch
series. But it would be nice to say so in an in-code comment.
Patrick
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 194+ messages in thread
* Re: [PATCH v5 4/7] refs: add support for transactional symref updates
2024-05-02 7:47 ` Patrick Steinhardt
@ 2024-05-02 11:10 ` Karthik Nayak
2024-05-02 16:51 ` Junio C Hamano
1 sibling, 0 replies; 194+ messages in thread
From: Karthik Nayak @ 2024-05-02 11:10 UTC (permalink / raw)
To: Patrick Steinhardt; +Cc: Junio C Hamano, christian.couder, git
[-- Attachment #1: Type: text/plain, Size: 3298 bytes --]
Patrick Steinhardt <ps@pks.im> writes:
> On Thu, May 02, 2024 at 05:50:47AM +0000, Karthik Nayak wrote:
>> Junio C Hamano <gitster@pobox.com> writes:
>> > Karthik Nayak <karthik.188@gmail.com> writes:
>> >> From: Karthik Nayak <karthik.188@gmail.com>
>> >> We do not add reflog for dangling symref updates, because currently
>> >> 'git-symbolic-ref' doesn't add reflog for dangling symref updates and it
>> >> would be best to keep this behavior consistent as we would move it to
>> >> start using transaction based updates in the following commit.
>> >
>> > If we are not changing the behaviour, does it deserve a four-line
>> > paragraph? It is not like we describe every no changes (i.e. "we
>> > could break the behaviour by introducing this and that bugs, but we
>> > did not" is not something we usually say in proposed log messages).
>> >
>> > At most, if you want to highlight that behaviour, I would expect a
>> > brief mention like:
>> >
>> > Note that a dangling symref update does not record a new reflog
>> > entry, which is unchanged before and after this commit.
>> >
>> > As a reflog entry records name of the object that is pointed by the
>> > ref (either directly or indirectly) before and after an operation,
>> > an operation that involve a dangling reflog that does not point at
>> > any object cannot be expressed in a reflog, no? It is way too late
>> > to change this, but it would have been interesting if the design of
>> > reflog had a room to log the change of symbolic ref target as well
>> > as object names. It would have allowed us to say "HEAD at time T
>> > pointed at refs/heads/main (which did not exist)", "HEAD at time T+1
>> > directly pointed at commit X (detached)", "HEAD at time T+2 pointed
>> > at refs/heads/next", etc. and allowed us to much more cleanly
>> > support "previous branch".
>> >
>>
>> While I agree that four lines may seem excessive, I think it is indeed
>> an important point to note. Mostly because this shows that when doing
>> dangling symref updates, there is no record of this update. The best
>> situation would be like you mentioned, to record the symref target
>> changes. But even with the current design, it would have been nice to at
>> least acknowledge that there was some update done to the symref. By
>> having zero-oid for the new and old value in the reflog. But seems like
>> we can't do that either.
>
> I wouldn't say we can't do that. We already do log when symrefs become
> dangling when updating references via HEAD by logging a zero OID as new
> OID. That is, if we have "HEAD -> refs/heads/foo" and you delete the
> latter, then we create a new reflog message for "HEAD" with zero OID as
> new OID.
>
> I would claim that the current behaviour where we don't create a reflog
> entry when updating a ref to become dangling is a mere bug. I think it's
> fair to declare this a #leftoverbit and handle it in a follow-up patch
> series. But it would be nice to say so in an in-code comment.
>
I think _can't_ wasn't the best terminology. My previous series actually
added a reflog, but I noticed a bunch of tests were failing and I think
it made sense to keep the existing behaviour.
But addressing it as a bug would definitely be a good way to go and fix
this, I'll add a comment in the code for now.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 194+ messages in thread
* Re: [PATCH v5 4/7] refs: add support for transactional symref updates
2024-05-02 7:47 ` Patrick Steinhardt
2024-05-02 11:10 ` Karthik Nayak
@ 2024-05-02 16:51 ` Junio C Hamano
1 sibling, 0 replies; 194+ messages in thread
From: Junio C Hamano @ 2024-05-02 16:51 UTC (permalink / raw)
To: Patrick Steinhardt; +Cc: Karthik Nayak, christian.couder, git
Patrick Steinhardt <ps@pks.im> writes:
> I wouldn't say we can't do that. We already do log when symrefs become
> dangling when updating references via HEAD by logging a zero OID as new
> OID. That is, if we have "HEAD -> refs/heads/foo" and you delete the
> latter, then we create a new reflog message for "HEAD" with zero OID as
> new OID.
>
> I would claim that the current behaviour where we don't create a reflog
> entry when updating a ref to become dangling is a mere bug. I think it's
> fair to declare this a #leftoverbit and handle it in a follow-up patch
> series. But it would be nice to say so in an in-code comment.
I like that. Thanks.
^ permalink raw reply [flat|nested] 194+ messages in thread
* Re: [PATCH v5 4/7] refs: add support for transactional symref updates
2024-05-02 5:50 ` Karthik Nayak
2024-05-02 7:47 ` Patrick Steinhardt
@ 2024-05-02 16:00 ` Junio C Hamano
1 sibling, 0 replies; 194+ messages in thread
From: Junio C Hamano @ 2024-05-02 16:00 UTC (permalink / raw)
To: Karthik Nayak; +Cc: christian.couder, git, ps
Karthik Nayak <karthik.188@gmail.com> writes:
> ... By
> having zero-oid for the new and old value in the reflog. But seems like
> we can't do that either.
Even if you could, the transition from refs/heads/main to refs/heads/next
would not be captured.
In any case, that is not something this step changes. My assumption
is that for most of developers it would be unexpected for the theme
to "allow symbolic updates to be transactional" to change it, hence
"we do not explain what we did not change".
Thanks.
^ permalink raw reply [flat|nested] 194+ messages in thread
* Re: [PATCH v5 4/7] refs: add support for transactional symref updates
2024-05-01 20:22 ` [PATCH v5 4/7] refs: add support for transactional symref updates Karthik Nayak
2024-05-01 23:52 ` Junio C Hamano
@ 2024-05-02 17:53 ` Junio C Hamano
1 sibling, 0 replies; 194+ messages in thread
From: Junio C Hamano @ 2024-05-02 17:53 UTC (permalink / raw)
To: Karthik Nayak; +Cc: christian.couder, git, ps
Karthik Nayak <karthik.188@gmail.com> writes:
> @@ -2863,12 +2928,26 @@ static int files_transaction_finish(struct ref_store *ref_store,
>
> if (update->flags & REF_NEEDS_COMMIT ||
> update->flags & REF_LOG_ONLY) {
> - if (files_log_ref_write(refs,
> - lock->ref_name,
> - &lock->old_oid,
> - &update->new_oid,
> - update->msg, update->flags,
> - err)) {
> + int create_reflog = 1;
> +
> + if (update->new_target) {
> + /*
> + * We want to get the resolved OID for the target, to ensure
> + * that the correct value is added to the reflog.
> + */
> + if (!refs_resolve_ref_unsafe(&refs->base, update->new_target,
> + RESOLVE_REF_READING, &update->new_oid, NULL)) {
> + /* for dangling symrefs we skip creating a reflog entry. */
> + create_reflog = 0;
> + }
> + }
> +
> + if (create_reflog && files_log_ref_write(refs,
> + lock->ref_name,
> + &lock->old_oid,
> + &update->new_oid,
> + update->msg, update->flags,
> + err)) {
> char *old_msg = strbuf_detach(err, NULL);
>
> strbuf_addf(err, "cannot update the ref '%s': %s",
This hunk is overly wide.
You could of course fix it mechanically (e.g., by rewrapping overly
wide comment block, wrapping an expression after &&-), but a code
path that is too deeply indented may be a sign that the function may
want to be split into calls to a smaller helper function for
readability.
^ permalink raw reply [flat|nested] 194+ messages in thread
* [PATCH v5 5/7] refs: use transaction in `refs_create_symref()`
2024-05-01 20:22 ` [PATCH v5 0/7] refs: add support for transactional symref updates Karthik Nayak
` (3 preceding siblings ...)
2024-05-01 20:22 ` [PATCH v5 4/7] refs: add support for transactional symref updates Karthik Nayak
@ 2024-05-01 20:22 ` Karthik Nayak
2024-05-02 7:47 ` Patrick Steinhardt
2024-05-01 20:22 ` [PATCH v5 6/7] refs: rename `refs_create_symref()` to `refs_update_symref()` Karthik Nayak
` (3 subsequent siblings)
8 siblings, 1 reply; 194+ messages in thread
From: Karthik Nayak @ 2024-05-01 20:22 UTC (permalink / raw)
To: karthik.188; +Cc: christian.couder, git, gitster, ps
From: Karthik Nayak <karthik.188@gmail.com>
The `refs_create_symref()` function updates a symref to a given new
target. To do this, it uses a ref-backend specific function
`create_symref()`.
In this previous commit, we introduce symref support in transactions.
This means we can now use transactions to perform symref updates and not
have to resort to `create_symref()`. Doing this allows us to remove and
cleanup `create_symref()`, which we will do in the following commit.
Modify the expected error message for a test in
't/t0610-reftable-basics.sh', since the error is now thrown from the
'refs.c'. This is because in transactional updates, F/D conflicts are
caught before we're in the reference backend.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
refs.c | 24 +++++++++++++++++-------
t/t0610-reftable-basics.sh | 2 +-
t/t1416-ref-transaction-hooks.sh | 23 +++++++++++++++++++++++
3 files changed, 41 insertions(+), 8 deletions(-)
diff --git a/refs.c b/refs.c
index a4dca08244..1b67c87f47 100644
--- a/refs.c
+++ b/refs.c
@@ -2291,14 +2291,24 @@ int refs_create_symref(struct ref_store *refs,
const char *refs_heads_master,
const char *logmsg)
{
- char *msg;
- int retval;
+ struct ref_transaction *transaction;
+ struct strbuf err = STRBUF_INIT;
+ int ret = 0;
- msg = normalize_reflog_message(logmsg);
- retval = refs->be->create_symref(refs, ref_target, refs_heads_master,
- msg);
- free(msg);
- return retval;
+ transaction = ref_store_transaction_begin(refs, &err);
+ if (!transaction ||
+ ref_transaction_update(transaction, ref_target, NULL, NULL,
+ refs_heads_master, NULL, REF_NO_DEREF,
+ logmsg, &err) ||
+ ref_transaction_commit(transaction, &err)) {
+ ret = error("%s", err.buf);
+ }
+
+ strbuf_release(&err);
+ if (transaction)
+ ref_transaction_free(transaction);
+
+ return ret;
}
int create_symref(const char *ref_target, const char *refs_heads_master,
diff --git a/t/t0610-reftable-basics.sh b/t/t0610-reftable-basics.sh
index 178791e086..9e8d22bcbd 100755
--- a/t/t0610-reftable-basics.sh
+++ b/t/t0610-reftable-basics.sh
@@ -286,7 +286,7 @@ test_expect_success 'ref transaction: creating symbolic ref fails with F/D confl
git init repo &&
test_commit -C repo A &&
cat >expect <<-EOF &&
- error: unable to write symref for refs/heads: file/directory conflict
+ error: ${SQ}refs/heads/main${SQ} exists; cannot create ${SQ}refs/heads${SQ}
EOF
test_must_fail git -C repo symbolic-ref refs/heads refs/heads/foo 2>err &&
test_cmp expect err
diff --git a/t/t1416-ref-transaction-hooks.sh b/t/t1416-ref-transaction-hooks.sh
index 2092488090..4433ac2177 100755
--- a/t/t1416-ref-transaction-hooks.sh
+++ b/t/t1416-ref-transaction-hooks.sh
@@ -134,4 +134,27 @@ test_expect_success 'interleaving hook calls succeed' '
test_cmp expect target-repo.git/actual
'
+test_expect_success 'hook captures git-symbolic-ref updates' '
+ test_when_finished "rm actual" &&
+
+ test_hook reference-transaction <<-\EOF &&
+ echo "$*" >>actual
+ while read -r line
+ do
+ printf "%s\n" "$line"
+ done >>actual
+ EOF
+
+ git symbolic-ref refs/heads/symref refs/heads/main &&
+
+ cat >expect <<-EOF &&
+ prepared
+ $ZERO_OID ref:refs/heads/main refs/heads/symref
+ committed
+ $ZERO_OID ref:refs/heads/main refs/heads/symref
+ EOF
+
+ test_cmp expect actual
+'
+
test_done
--
2.43.GIT
^ permalink raw reply related [flat|nested] 194+ messages in thread
* Re: [PATCH v5 5/7] refs: use transaction in `refs_create_symref()`
2024-05-01 20:22 ` [PATCH v5 5/7] refs: use transaction in `refs_create_symref()` Karthik Nayak
@ 2024-05-02 7:47 ` Patrick Steinhardt
0 siblings, 0 replies; 194+ messages in thread
From: Patrick Steinhardt @ 2024-05-02 7:47 UTC (permalink / raw)
To: Karthik Nayak; +Cc: christian.couder, git, gitster
[-- Attachment #1: Type: text/plain, Size: 2226 bytes --]
On Wed, May 01, 2024 at 10:22:27PM +0200, Karthik Nayak wrote:
> From: Karthik Nayak <karthik.188@gmail.com>
>
> The `refs_create_symref()` function updates a symref to a given new
> target. To do this, it uses a ref-backend specific function
> `create_symref()`.
>
> In this previous commit, we introduce symref support in transactions.
> This means we can now use transactions to perform symref updates and not
> have to resort to `create_symref()`. Doing this allows us to remove and
Nit: "not have to" -> "don't have to"
[snip]
> diff --git a/t/t0610-reftable-basics.sh b/t/t0610-reftable-basics.sh
> index 178791e086..9e8d22bcbd 100755
> --- a/t/t0610-reftable-basics.sh
> +++ b/t/t0610-reftable-basics.sh
> @@ -286,7 +286,7 @@ test_expect_success 'ref transaction: creating symbolic ref fails with F/D confl
> git init repo &&
> test_commit -C repo A &&
> cat >expect <<-EOF &&
> - error: unable to write symref for refs/heads: file/directory conflict
> + error: ${SQ}refs/heads/main${SQ} exists; cannot create ${SQ}refs/heads${SQ}
> EOF
> test_must_fail git -C repo symbolic-ref refs/heads refs/heads/foo 2>err &&
> test_cmp expect err
Nice. Not only do we have less code to worry about, but the error
message is better, too.
> diff --git a/t/t1416-ref-transaction-hooks.sh b/t/t1416-ref-transaction-hooks.sh
> index 2092488090..4433ac2177 100755
> --- a/t/t1416-ref-transaction-hooks.sh
> +++ b/t/t1416-ref-transaction-hooks.sh
> @@ -134,4 +134,27 @@ test_expect_success 'interleaving hook calls succeed' '
> test_cmp expect target-repo.git/actual
> '
>
> +test_expect_success 'hook captures git-symbolic-ref updates' '
> + test_when_finished "rm actual" &&
> +
> + test_hook reference-transaction <<-\EOF &&
> + echo "$*" >>actual
> + while read -r line
> + do
> + printf "%s\n" "$line"
> + done >>actual
> + EOF
> +
> + git symbolic-ref refs/heads/symref refs/heads/main &&
> +
> + cat >expect <<-EOF &&
> + prepared
> + $ZERO_OID ref:refs/heads/main refs/heads/symref
> + committed
> + $ZERO_OID ref:refs/heads/main refs/heads/symref
> + EOF
Nit: the contents of the heredoc should be indented one level less.
Patrick
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 194+ messages in thread
* [PATCH v5 6/7] refs: rename `refs_create_symref()` to `refs_update_symref()`
2024-05-01 20:22 ` [PATCH v5 0/7] refs: add support for transactional symref updates Karthik Nayak
` (4 preceding siblings ...)
2024-05-01 20:22 ` [PATCH v5 5/7] refs: use transaction in `refs_create_symref()` Karthik Nayak
@ 2024-05-01 20:22 ` Karthik Nayak
2024-05-02 7:47 ` Patrick Steinhardt
2024-05-01 20:22 ` [PATCH v5 7/7] refs: remove `create_symref` and associated dead code Karthik Nayak
` (2 subsequent siblings)
8 siblings, 1 reply; 194+ messages in thread
From: Karthik Nayak @ 2024-05-01 20:22 UTC (permalink / raw)
To: karthik.188; +Cc: christian.couder, git, gitster, ps
From: Karthik Nayak <karthik.188@gmail.com>
The `refs_create_symref()` function is used to update/create a symref.
But it doesn't check the old target of the symref, if existing. It force
updates the symref. In this regard, the name `refs_create_symref()` is a
bit misleading. So let's rename it to `refs_update_symref()`. This is
akin to how 'git-update-ref(1)' also allows us to create apart from
update.
While we're here, rename the arguments in the function to clarify what
they actually signify and reduce confusion.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
builtin/branch.c | 2 +-
builtin/worktree.c | 2 +-
refs.c | 12 +++++-------
refs.h | 2 +-
t/helper/test-ref-store.c | 2 +-
5 files changed, 9 insertions(+), 11 deletions(-)
diff --git a/builtin/branch.c b/builtin/branch.c
index dd3e3a7dc0..4491f7a20c 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -555,7 +555,7 @@ static int replace_each_worktree_head_symref(struct worktree **worktrees,
continue;
refs = get_worktree_ref_store(worktrees[i]);
- if (refs_create_symref(refs, "HEAD", newref, logmsg))
+ if (refs_update_symref(refs, "HEAD", newref, logmsg))
ret = error(_("HEAD of working tree %s is not updated"),
worktrees[i]->path);
}
diff --git a/builtin/worktree.c b/builtin/worktree.c
index 7c6c72536b..480202c517 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -517,7 +517,7 @@ static int add_worktree(const char *path, const char *refname,
ret = refs_update_ref(wt_refs, NULL, "HEAD", &commit->object.oid,
NULL, 0, UPDATE_REFS_MSG_ON_ERR);
else
- ret = refs_create_symref(wt_refs, "HEAD", symref.buf, NULL);
+ ret = refs_update_symref(wt_refs, "HEAD", symref.buf, NULL);
if (ret)
goto done;
diff --git a/refs.c b/refs.c
index 1b67c87f47..1215b0fae2 100644
--- a/refs.c
+++ b/refs.c
@@ -2286,10 +2286,8 @@ int peel_iterated_oid(const struct object_id *base, struct object_id *peeled)
return peel_object(base, peeled) ? -1 : 0;
}
-int refs_create_symref(struct ref_store *refs,
- const char *ref_target,
- const char *refs_heads_master,
- const char *logmsg)
+int refs_update_symref(struct ref_store *refs, const char *ref,
+ const char *target, const char *logmsg)
{
struct ref_transaction *transaction;
struct strbuf err = STRBUF_INIT;
@@ -2297,8 +2295,8 @@ int refs_create_symref(struct ref_store *refs,
transaction = ref_store_transaction_begin(refs, &err);
if (!transaction ||
- ref_transaction_update(transaction, ref_target, NULL, NULL,
- refs_heads_master, NULL, REF_NO_DEREF,
+ ref_transaction_update(transaction, ref, NULL, NULL,
+ target, NULL, REF_NO_DEREF,
logmsg, &err) ||
ref_transaction_commit(transaction, &err)) {
ret = error("%s", err.buf);
@@ -2314,7 +2312,7 @@ int refs_create_symref(struct ref_store *refs,
int create_symref(const char *ref_target, const char *refs_heads_master,
const char *logmsg)
{
- return refs_create_symref(get_main_ref_store(the_repository), ref_target,
+ return refs_update_symref(get_main_ref_store(the_repository), ref_target,
refs_heads_master, logmsg);
}
diff --git a/refs.h b/refs.h
index c7851bf587..71cc1c58e0 100644
--- a/refs.h
+++ b/refs.h
@@ -606,7 +606,7 @@ int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,
int copy_existing_ref(const char *oldref, const char *newref,
const char *logmsg);
-int refs_create_symref(struct ref_store *refs, const char *refname,
+int refs_update_symref(struct ref_store *refs, const char *refname,
const char *target, const char *logmsg);
int create_symref(const char *refname, const char *target, const char *logmsg);
diff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c
index 82bbf6e2e6..4651e4ced7 100644
--- a/t/helper/test-ref-store.c
+++ b/t/helper/test-ref-store.c
@@ -118,7 +118,7 @@ static int cmd_create_symref(struct ref_store *refs, const char **argv)
const char *target = notnull(*argv++, "target");
const char *logmsg = *argv++;
- return refs_create_symref(refs, refname, target, logmsg);
+ return refs_update_symref(refs, refname, target, logmsg);
}
static struct flag_definition transaction_flags[] = {
--
2.43.GIT
^ permalink raw reply related [flat|nested] 194+ messages in thread
* Re: [PATCH v5 6/7] refs: rename `refs_create_symref()` to `refs_update_symref()`
2024-05-01 20:22 ` [PATCH v5 6/7] refs: rename `refs_create_symref()` to `refs_update_symref()` Karthik Nayak
@ 2024-05-02 7:47 ` Patrick Steinhardt
2024-05-02 11:34 ` Karthik Nayak
0 siblings, 1 reply; 194+ messages in thread
From: Patrick Steinhardt @ 2024-05-02 7:47 UTC (permalink / raw)
To: Karthik Nayak; +Cc: christian.couder, git, gitster
[-- Attachment #1: Type: text/plain, Size: 998 bytes --]
On Wed, May 01, 2024 at 10:22:28PM +0200, Karthik Nayak wrote:
> From: Karthik Nayak <karthik.188@gmail.com>
>
> The `refs_create_symref()` function is used to update/create a symref.
> But it doesn't check the old target of the symref, if existing. It force
> updates the symref. In this regard, the name `refs_create_symref()` is a
> bit misleading. So let's rename it to `refs_update_symref()`. This is
> akin to how 'git-update-ref(1)' also allows us to create apart from
> update.
Arguably, as we are already updating all callsites anyway, I don't see a
reason why we shouldn't also update the function to accept the old OID
or old target so that callers can make raceless updates.
The only problem is that we don't have any users yet, and consequently
we have no way to verify that it works as intended. So maybe this is
better left for a future patch series, unless we have places where we
can reasonably update the callers to pass in the old value, as well.
Patrick
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 194+ messages in thread
* Re: [PATCH v5 6/7] refs: rename `refs_create_symref()` to `refs_update_symref()`
2024-05-02 7:47 ` Patrick Steinhardt
@ 2024-05-02 11:34 ` Karthik Nayak
0 siblings, 0 replies; 194+ messages in thread
From: Karthik Nayak @ 2024-05-02 11:34 UTC (permalink / raw)
To: Patrick Steinhardt; +Cc: christian.couder, git, gitster
[-- Attachment #1: Type: text/plain, Size: 1193 bytes --]
Patrick Steinhardt <ps@pks.im> writes:
> On Wed, May 01, 2024 at 10:22:28PM +0200, Karthik Nayak wrote:
>> From: Karthik Nayak <karthik.188@gmail.com>
>>
>> The `refs_create_symref()` function is used to update/create a symref.
>> But it doesn't check the old target of the symref, if existing. It force
>> updates the symref. In this regard, the name `refs_create_symref()` is a
>> bit misleading. So let's rename it to `refs_update_symref()`. This is
>> akin to how 'git-update-ref(1)' also allows us to create apart from
>> update.
>
> Arguably, as we are already updating all callsites anyway, I don't see a
> reason why we shouldn't also update the function to accept the old OID
> or old target so that callers can make raceless updates.
>
> The only problem is that we don't have any users yet, and consequently
> we have no way to verify that it works as intended. So maybe this is
> better left for a future patch series, unless we have places where we
> can reasonably update the callers to pass in the old value, as well.
>
> Patrick
Yeah that did run through my mind too, but without a usecase, its hard
to justify a change and also like you mentioned harder to write tests
for.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 194+ messages in thread
* [PATCH v5 7/7] refs: remove `create_symref` and associated dead code
2024-05-01 20:22 ` [PATCH v5 0/7] refs: add support for transactional symref updates Karthik Nayak
` (5 preceding siblings ...)
2024-05-01 20:22 ` [PATCH v5 6/7] refs: rename `refs_create_symref()` to `refs_update_symref()` Karthik Nayak
@ 2024-05-01 20:22 ` Karthik Nayak
2024-05-02 7:47 ` Patrick Steinhardt
2024-05-02 0:20 ` [PATCH v5 0/7] refs: add support for transactional symref updates Junio C Hamano
2024-05-03 12:41 ` [PATCH v6 " Karthik Nayak
8 siblings, 1 reply; 194+ messages in thread
From: Karthik Nayak @ 2024-05-01 20:22 UTC (permalink / raw)
To: karthik.188; +Cc: christian.couder, git, gitster, ps
From: Karthik Nayak <karthik.188@gmail.com>
In the previous commits, we converted `refs_create_symref()` to utilize
transactions to perform symref updates. Earlier `refs_create_symref()`
used `create_symref()` to do the same.
This means, we can now remove `create_symref()` and any code associated
with it which is no longer used. We remove `create_symref()` code from
all the reference backends and also remove it entirely from the
`ref_storage_be` struct.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
refs/debug.c | 13 -------
refs/files-backend.c | 64 ------------------------------
refs/packed-backend.c | 1 -
refs/refs-internal.h | 5 ---
refs/reftable-backend.c | 86 -----------------------------------------
5 files changed, 169 deletions(-)
diff --git a/refs/debug.c b/refs/debug.c
index c7531b17f0..8be316bb67 100644
--- a/refs/debug.c
+++ b/refs/debug.c
@@ -131,18 +131,6 @@ static int debug_pack_refs(struct ref_store *ref_store, struct pack_refs_opts *o
return res;
}
-static int debug_create_symref(struct ref_store *ref_store,
- const char *ref_name, const char *target,
- const char *logmsg)
-{
- struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store;
- int res = drefs->refs->be->create_symref(drefs->refs, ref_name, target,
- logmsg);
- trace_printf_key(&trace_refs, "create_symref: %s -> %s \"%s\": %d\n", ref_name,
- target, logmsg, res);
- return res;
-}
-
static int debug_rename_ref(struct ref_store *ref_store, const char *oldref,
const char *newref, const char *logmsg)
{
@@ -441,7 +429,6 @@ struct ref_storage_be refs_be_debug = {
.initial_transaction_commit = debug_initial_transaction_commit,
.pack_refs = debug_pack_refs,
- .create_symref = debug_create_symref,
.rename_ref = debug_rename_ref,
.copy_ref = debug_copy_ref,
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 85c4af7e89..7597760f63 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -1903,23 +1903,6 @@ static int create_ref_symlink(struct ref_lock *lock, const char *target)
return ret;
}
-static void update_symref_reflog(struct files_ref_store *refs,
- struct ref_lock *lock, const char *refname,
- const char *target, const char *logmsg)
-{
- struct strbuf err = STRBUF_INIT;
- struct object_id new_oid;
-
- if (logmsg &&
- refs_resolve_ref_unsafe(&refs->base, target,
- RESOLVE_REF_READING, &new_oid, NULL) &&
- files_log_ref_write(refs, refname, &lock->old_oid,
- &new_oid, logmsg, 0, &err)) {
- error("%s", err.buf);
- strbuf_release(&err);
- }
-}
-
static int create_symref_lock(struct files_ref_store *refs,
struct ref_lock *lock, const char *refname,
const char *target)
@@ -1934,52 +1917,6 @@ static int create_symref_lock(struct files_ref_store *refs,
return 0;
}
-static int create_and_commit_symref(struct files_ref_store *refs,
- struct ref_lock *lock, const char *refname,
- const char *target, const char *logmsg)
-{
- int ret;
-
- if (prefer_symlink_refs && !create_ref_symlink(lock, target)) {
- update_symref_reflog(refs, lock, refname, target, logmsg);
- return 0;
- }
-
- ret = create_symref_lock(refs, lock, refname, target);
- if (!ret) {
- update_symref_reflog(refs, lock, refname, target, logmsg);
-
- if (commit_ref(lock) < 0)
- return error("unable to write symref for %s: %s", refname,
- strerror(errno));
- }
-
- return ret;
-}
-
-static int files_create_symref(struct ref_store *ref_store,
- const char *refname, const char *target,
- const char *logmsg)
-{
- struct files_ref_store *refs =
- files_downcast(ref_store, REF_STORE_WRITE, "create_symref");
- struct strbuf err = STRBUF_INIT;
- struct ref_lock *lock;
- int ret;
-
- lock = lock_ref_oid_basic(refs, refname, &err);
- if (!lock) {
- error("%s", err.buf);
- strbuf_release(&err);
- return -1;
- }
-
- ret = create_and_commit_symref(refs, lock, refname, target, logmsg);
-
- unlock_ref(lock);
- return ret;
-}
-
static int files_reflog_exists(struct ref_store *ref_store,
const char *refname)
{
@@ -3394,7 +3331,6 @@ struct ref_storage_be refs_be_files = {
.initial_transaction_commit = files_initial_transaction_commit,
.pack_refs = files_pack_refs,
- .create_symref = files_create_symref,
.rename_ref = files_rename_ref,
.copy_ref = files_copy_ref,
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index 4e826c05ff..a937e7dbfc 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -1714,7 +1714,6 @@ struct ref_storage_be refs_be_packed = {
.initial_transaction_commit = packed_initial_transaction_commit,
.pack_refs = packed_pack_refs,
- .create_symref = NULL,
.rename_ref = NULL,
.copy_ref = NULL,
diff --git a/refs/refs-internal.h b/refs/refs-internal.h
index 9578665243..fa46548a2f 100644
--- a/refs/refs-internal.h
+++ b/refs/refs-internal.h
@@ -566,10 +566,6 @@ typedef int ref_transaction_commit_fn(struct ref_store *refs,
typedef int pack_refs_fn(struct ref_store *ref_store,
struct pack_refs_opts *opts);
-typedef int create_symref_fn(struct ref_store *ref_store,
- const char *ref_target,
- const char *refs_heads_master,
- const char *logmsg);
typedef int rename_ref_fn(struct ref_store *ref_store,
const char *oldref, const char *newref,
const char *logmsg);
@@ -690,7 +686,6 @@ struct ref_storage_be {
ref_transaction_commit_fn *initial_transaction_commit;
pack_refs_fn *pack_refs;
- create_symref_fn *create_symref;
rename_ref_fn *rename_ref;
copy_ref_fn *copy_ref;
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index 5e8a696d40..d4221898e4 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -1269,91 +1269,6 @@ struct write_create_symref_arg {
const char *logmsg;
};
-static int write_create_symref_table(struct reftable_writer *writer, void *cb_data)
-{
- struct write_create_symref_arg *create = cb_data;
- uint64_t ts = reftable_stack_next_update_index(create->stack);
- struct reftable_ref_record ref = {
- .refname = (char *)create->refname,
- .value_type = REFTABLE_REF_SYMREF,
- .value.symref = (char *)create->target,
- .update_index = ts,
- };
- struct reftable_log_record log = {0};
- struct object_id new_oid;
- struct object_id old_oid;
- int ret;
-
- reftable_writer_set_limits(writer, ts, ts);
-
- ret = reftable_writer_add_ref(writer, &ref);
- if (ret)
- return ret;
-
- /*
- * Note that it is important to try and resolve the reference before we
- * write the log entry. This is because `should_write_log()` will munge
- * `core.logAllRefUpdates`, which is undesirable when we create a new
- * repository because it would be written into the config. As HEAD will
- * not resolve for new repositories this ordering will ensure that this
- * never happens.
- */
- if (!create->logmsg ||
- !refs_resolve_ref_unsafe(&create->refs->base, create->target,
- RESOLVE_REF_READING, &new_oid, NULL) ||
- !should_write_log(&create->refs->base, create->refname))
- return 0;
-
- fill_reftable_log_record(&log);
- log.refname = xstrdup(create->refname);
- log.update_index = ts;
- log.value.update.message = xstrndup(create->logmsg,
- create->refs->write_options.block_size / 2);
- memcpy(log.value.update.new_hash, new_oid.hash, GIT_MAX_RAWSZ);
- if (refs_resolve_ref_unsafe(&create->refs->base, create->refname,
- RESOLVE_REF_READING, &old_oid, NULL))
- memcpy(log.value.update.old_hash, old_oid.hash, GIT_MAX_RAWSZ);
-
- ret = reftable_writer_add_log(writer, &log);
- reftable_log_record_release(&log);
- return ret;
-}
-
-static int reftable_be_create_symref(struct ref_store *ref_store,
- const char *refname,
- const char *target,
- const char *logmsg)
-{
- struct reftable_ref_store *refs =
- reftable_be_downcast(ref_store, REF_STORE_WRITE, "create_symref");
- struct reftable_stack *stack = stack_for(refs, refname, &refname);
- struct write_create_symref_arg arg = {
- .refs = refs,
- .stack = stack,
- .refname = refname,
- .target = target,
- .logmsg = logmsg,
- };
- int ret;
-
- ret = refs->err;
- if (ret < 0)
- goto done;
-
- ret = reftable_stack_reload(stack);
- if (ret)
- goto done;
-
- ret = reftable_stack_add(stack, &write_create_symref_table, &arg);
-
-done:
- assert(ret != REFTABLE_API_ERROR);
- if (ret)
- error("unable to write symref for %s: %s", refname,
- reftable_error_str(ret));
- return ret;
-}
-
struct write_copy_arg {
struct reftable_ref_store *refs;
struct reftable_stack *stack;
@@ -2261,7 +2176,6 @@ struct ref_storage_be refs_be_reftable = {
.initial_transaction_commit = reftable_be_initial_transaction_commit,
.pack_refs = reftable_be_pack_refs,
- .create_symref = reftable_be_create_symref,
.rename_ref = reftable_be_rename_ref,
.copy_ref = reftable_be_copy_ref,
--
2.43.GIT
^ permalink raw reply related [flat|nested] 194+ messages in thread
* Re: [PATCH v5 7/7] refs: remove `create_symref` and associated dead code
2024-05-01 20:22 ` [PATCH v5 7/7] refs: remove `create_symref` and associated dead code Karthik Nayak
@ 2024-05-02 7:47 ` Patrick Steinhardt
2024-05-02 16:53 ` Junio C Hamano
0 siblings, 1 reply; 194+ messages in thread
From: Patrick Steinhardt @ 2024-05-02 7:47 UTC (permalink / raw)
To: Karthik Nayak; +Cc: christian.couder, git, gitster
[-- Attachment #1: Type: text/plain, Size: 579 bytes --]
On Wed, May 01, 2024 at 10:22:29PM +0200, Karthik Nayak wrote:
> From: Karthik Nayak <karthik.188@gmail.com>
>
> In the previous commits, we converted `refs_create_symref()` to utilize
> transactions to perform symref updates. Earlier `refs_create_symref()`
> used `create_symref()` to do the same.
>
> This means, we can now remove `create_symref()` and any code associated
> with it which is no longer used. We remove `create_symref()` code from
> all the reference backends and also remove it entirely from the
> `ref_storage_be` struct.
Very nice.
Patrick
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 194+ messages in thread
* Re: [PATCH v5 7/7] refs: remove `create_symref` and associated dead code
2024-05-02 7:47 ` Patrick Steinhardt
@ 2024-05-02 16:53 ` Junio C Hamano
0 siblings, 0 replies; 194+ messages in thread
From: Junio C Hamano @ 2024-05-02 16:53 UTC (permalink / raw)
To: Patrick Steinhardt; +Cc: Karthik Nayak, christian.couder, git
Patrick Steinhardt <ps@pks.im> writes:
> On Wed, May 01, 2024 at 10:22:29PM +0200, Karthik Nayak wrote:
>> From: Karthik Nayak <karthik.188@gmail.com>
>>
>> In the previous commits, we converted `refs_create_symref()` to utilize
>> transactions to perform symref updates. Earlier `refs_create_symref()`
>> used `create_symref()` to do the same.
>>
>> This means, we can now remove `create_symref()` and any code associated
>> with it which is no longer used. We remove `create_symref()` code from
>> all the reference backends and also remove it entirely from the
>> `ref_storage_be` struct.
>
> Very nice.
Indeed.
The last paragraph reads well without "This means, ", by the way.
^ permalink raw reply [flat|nested] 194+ messages in thread
* Re: [PATCH v5 0/7] refs: add support for transactional symref updates
2024-05-01 20:22 ` [PATCH v5 0/7] refs: add support for transactional symref updates Karthik Nayak
` (6 preceding siblings ...)
2024-05-01 20:22 ` [PATCH v5 7/7] refs: remove `create_symref` and associated dead code Karthik Nayak
@ 2024-05-02 0:20 ` Junio C Hamano
2024-05-02 5:53 ` Karthik Nayak
2024-05-03 12:41 ` [PATCH v6 " Karthik Nayak
8 siblings, 1 reply; 194+ messages in thread
From: Junio C Hamano @ 2024-05-02 0:20 UTC (permalink / raw)
To: Karthik Nayak; +Cc: christian.couder, git, ps
Karthik Nayak <karthik.188@gmail.com> writes:
> From: Karthik Nayak <karthik.188@gmail.com>
>
> The patch series takes over from the existing patch series, wherein we
> introduced symref-* commands to git-update-ref. Since there was a ton of
> discussions on the UX of the patch series and its application, I thought it
> would be best to shorten the series and split it into multiple smaller series.
>
> This series adds transactional support for symrefs in the reference db. Then
> we switch refs_create_symref() to start using transactions for symref updates.
> This allows us to deprecate the create_symref code in the ref_storage_be
> interface and remove all associated code which is no longer used.
>
> The split was primarily done so we can merge the non-user facing parts of the
> previous series. While pertaining the user facing components into another set
> of patches wherein deeper discussion on the UX can be held without worrying
> about the internal implementation.
This split probably makes sense in the context of the evolution of
this series. If this were without any prior discussion, a change to
the internal mechanism, without showing how the end-user facing half
would use it fully, would have been hard to evaluate, but now that
we know where the new mechanism wants to take us, we can fairly
evaluate it alone without the end-user facing part.
I've read only the earlier half of the series but so far the pieces
make sense to me.
^ permalink raw reply [flat|nested] 194+ messages in thread
* Re: [PATCH v5 0/7] refs: add support for transactional symref updates
2024-05-02 0:20 ` [PATCH v5 0/7] refs: add support for transactional symref updates Junio C Hamano
@ 2024-05-02 5:53 ` Karthik Nayak
0 siblings, 0 replies; 194+ messages in thread
From: Karthik Nayak @ 2024-05-02 5:53 UTC (permalink / raw)
To: Junio C Hamano; +Cc: christian.couder, git, ps
[-- Attachment #1: Type: text/plain, Size: 1723 bytes --]
Junio C Hamano <gitster@pobox.com> writes:
> Karthik Nayak <karthik.188@gmail.com> writes:
>
>> From: Karthik Nayak <karthik.188@gmail.com>
>>
>> The patch series takes over from the existing patch series, wherein we
>> introduced symref-* commands to git-update-ref. Since there was a ton of
>> discussions on the UX of the patch series and its application, I thought it
>> would be best to shorten the series and split it into multiple smaller series.
>>
>> This series adds transactional support for symrefs in the reference db. Then
>> we switch refs_create_symref() to start using transactions for symref updates.
>> This allows us to deprecate the create_symref code in the ref_storage_be
>> interface and remove all associated code which is no longer used.
>>
>> The split was primarily done so we can merge the non-user facing parts of the
>> previous series. While pertaining the user facing components into another set
>> of patches wherein deeper discussion on the UX can be held without worrying
>> about the internal implementation.
>
> This split probably makes sense in the context of the evolution of
> this series. If this were without any prior discussion, a change to
> the internal mechanism, without showing how the end-user facing half
> would use it fully, would have been hard to evaluate, but now that
> we know where the new mechanism wants to take us, we can fairly
> evaluate it alone without the end-user facing part.
>
> I've read only the earlier half of the series but so far the pieces
> make sense to me.
Yes I agree, I think it's also nice to see how a bunch of code can be
removed to use this generic functionality.
I'll wait for a day/two for other reviews. Thanks for your review.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 194+ messages in thread
* [PATCH v6 0/7] refs: add support for transactional symref updates
2024-05-01 20:22 ` [PATCH v5 0/7] refs: add support for transactional symref updates Karthik Nayak
` (7 preceding siblings ...)
2024-05-02 0:20 ` [PATCH v5 0/7] refs: add support for transactional symref updates Junio C Hamano
@ 2024-05-03 12:41 ` Karthik Nayak
2024-05-03 12:41 ` [PATCH v6 1/7] refs: accept symref values in `ref_transaction_update()` Karthik Nayak
` (9 more replies)
8 siblings, 10 replies; 194+ messages in thread
From: Karthik Nayak @ 2024-05-03 12:41 UTC (permalink / raw)
To: karthik.188; +Cc: christian.couder, git, gitster, ps
From: Karthik Nayak <karthik.188@gmail.com>
The patch series takes over from the existing patch series, wherein we
introduced symref-* commands to git-update-ref. Since there was a ton of
discussions on the UX of the patch series and its application, I thought it
would be best to shorten the series and split it into multiple smaller series.
This series adds transactional support for symrefs in the reference db. Then
we switch refs_create_symref() to start using transactions for symref updates.
This allows us to deprecate the create_symref code in the ref_storage_be
interface and remove all associated code which is no longer used.
The split was primarily done so we can merge the non-user facing parts of the
previous series. While pertaining the user facing components into another set
of patches wherein deeper discussion on the UX can be held without worrying
about the internal implementation. Also by using this new functionality in a
pre-existing command, we can leverage the existing tests to catch any
inconsistencies. One of which was how 'git-symbolic-ref' doesn't add reflog for
dangling symrefs, which I've modified my patch to do the same.
We also modify the reference transaction hook to support symrefs. For any symref
update the reference transaction hook will output the value with a 'ref:' prefix.
Previous versions:
V1: https://lore.kernel.org/git/20240330224623.579457-1-knayak@gitlab.com/
V2: https://lore.kernel.org/git/20240412095908.1134387-1-knayak@gitlab.com/
V3: https://lore.kernel.org/git/20240423212818.574123-1-knayak@gitlab.com/
V4: https://lore.kernel.org/r/20240426152449.228860-1-knayak@gitlab.com
V5: https://lore.kernel.org/r/20240501202229.2695774-1-knayak@gitlab.com
Changes over v5 are:
- More user friendly error messages.
- `create_symref_lock` now writes to an err buf, instead of directly to stderr.
- Refactor code to make it easier to read around logical operations.
- Cleanup commit message and fix typos.
Thanks to all reviewers!
Range diff:
1: a354190905 = 1: a354190905 refs: accept symref values in `ref_transaction_update()`
2: 7dff21dbef ! 2: 0d9c5b9804 files-backend: extract out `create_symref_lock()`
@@ refs/files-backend.c: static void update_symref_reflog(struct files_ref_store *r
- const char *target, const char *logmsg)
+static int create_symref_lock(struct files_ref_store *refs,
+ struct ref_lock *lock, const char *refname,
-+ const char *target)
++ const char *target, struct strbuf *err)
{
-+ if (!fdopen_lock_file(&lock->lk, "w"))
-+ return error("unable to fdopen %s: %s",
++ if (!fdopen_lock_file(&lock->lk, "w")) {
++ strbuf_addf(err, "unable to fdopen %s: %s",
+ get_lock_file_path(&lock->lk), strerror(errno));
++ return -1;
++ }
+
-+ if (fprintf(get_lock_file_fp(&lock->lk), "ref: %s\n", target) < 0)
-+ return error("unable to fprintf %s: %s",
++ if (fprintf(get_lock_file_fp(&lock->lk), "ref: %s\n", target) < 0) {
++ strbuf_addf(err, "unable to write to %s: %s",
+ get_lock_file_path(&lock->lk), strerror(errno));
++ return -1;
++ }
++
+ return 0;
+}
+
@@ refs/files-backend.c: static void update_symref_reflog(struct files_ref_store *r
+ struct ref_lock *lock, const char *refname,
+ const char *target, const char *logmsg)
+{
++ struct strbuf err = STRBUF_INIT;
+ int ret;
+
if (prefer_symlink_refs && !create_ref_symlink(lock, target)) {
@@ refs/files-backend.c: static void update_symref_reflog(struct files_ref_store *r
- if (!fdopen_lock_file(&lock->lk, "w"))
- return error("unable to fdopen %s: %s",
- get_lock_file_path(&lock->lk), strerror(errno));
-+ ret = create_symref_lock(refs, lock, refname, target);
++ ret = create_symref_lock(refs, lock, refname, target, &err);
+ if (!ret) {
+ update_symref_reflog(refs, lock, refname, target, logmsg);
@@ refs/files-backend.c: static void update_symref_reflog(struct files_ref_store *r
+ if (commit_ref(lock) < 0)
+ return error("unable to write symref for %s: %s", refname,
+ strerror(errno));
++ } else {
++ return error("%s", err.buf);
+ }
- /* no error check; commit_ref will check ferror */
3: 901a586683 ! 3: e0219ffd31 refs: support symrefs in 'reference-transaction' hook
@@ refs.c: static int run_transaction_hook(struct ref_transaction *transaction,
- oid_to_hex(&update->new_oid),
- update->refname);
+
-+ if (update->flags & REF_HAVE_OLD && update->old_target)
++ if (!(update->flags & REF_HAVE_OLD))
++ strbuf_addf(&buf, "%s ", oid_to_hex(null_oid()));
++ else if (update->old_target)
+ strbuf_addf(&buf, "ref:%s ", update->old_target);
+ else
+ strbuf_addf(&buf, "%s ", oid_to_hex(&update->old_oid));
+
-+ if (update->flags & REF_HAVE_NEW && update->new_target)
++ if (!(update->flags & REF_HAVE_NEW))
++ strbuf_addf(&buf, "%s ", oid_to_hex(null_oid()));
++ else if (update->new_target)
+ strbuf_addf(&buf, "ref:%s ", update->new_target);
+ else
+ strbuf_addf(&buf, "%s ", oid_to_hex(&update->new_oid));
4: 6c97f6a660 ! 4: b22c59c722 refs: add support for transactional symref updates
@@ Commit message
However, we never supported transactional updates of symrefs. Let's add
support for symrefs in both the 'files' and the 'reftable' backend.
- Here, we add and use `ref_update_is_null_new_value()`, a helper function
- which is used to check if there is a new_value in a reference update.
- The new value could either be a symref target `new_target` or a OID
- `new_oid`.
+ Here, we add and use `ref_update_has_null_new_value()`, a helper
+ function which is used to check if there is a new_value in a reference
+ update. The new value could either be a symref target `new_target` or a
+ OID `new_oid`.
With this, now transactional updates (verify, create, delete, update)
can be used for:
@@ Commit message
This also allows us to expose this to users via new commands in
'git-update-ref' in the future.
- We do not add reflog for dangling symref updates, because currently
- 'git-symbolic-ref' doesn't add reflog for dangling symref updates and it
- would be best to keep this behavior consistent as we would move it to
- start using transaction based updates in the following commit.
+ Note that a dangling symref update does not record a new reflog entry,
+ which is unchanged before and after this commit.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
@@ refs.c: struct ref_update *ref_transaction_add_update(
update->flags = flags;
- if (flags & REF_HAVE_NEW)
-+ if (new_target)
-+ update->new_target = xstrdup(new_target);
-+ if (old_target)
-+ update->old_target = xstrdup(old_target);
-+ if (new_oid && flags & REF_HAVE_NEW)
++ update->new_target = xstrdup_or_null(new_target);
++ update->old_target = xstrdup_or_null(old_target);
++ if ((flags & REF_HAVE_NEW) && new_oid)
oidcpy(&update->new_oid, new_oid);
- if (flags & REF_HAVE_OLD)
-+ if (old_oid && flags & REF_HAVE_OLD)
++ if ((flags & REF_HAVE_OLD) && old_oid)
oidcpy(&update->old_oid, old_oid);
+
update->msg = normalize_reflog_message(msg);
@@ refs.c: int copy_existing_ref(const char *oldref, const char *newref, const char
return refs_copy_existing_ref(get_main_ref_store(the_repository), oldref, newref, logmsg);
}
+
-+int ref_update_is_null_new_value(struct ref_update *update)
++int ref_update_has_null_new_value(struct ref_update *update)
+{
+ return !update->new_target && is_null_oid(&update->new_oid);
+}
@@ refs/files-backend.c: static int lock_ref_for_update(struct files_ref_store *ref
files_assert_main_repository(refs, "lock_ref_for_update");
- if ((update->flags & REF_HAVE_NEW) && is_null_oid(&update->new_oid))
-+ if ((update->flags & REF_HAVE_NEW) && ref_update_is_null_new_value(update))
++ if ((update->flags & REF_HAVE_NEW) && ref_update_has_null_new_value(update))
update->flags |= REF_DELETING;
if (head_ref) {
@@ refs/files-backend.c: static int lock_ref_for_update(struct files_ref_store *ref
- !(update->flags & REF_DELETING) &&
- !(update->flags & REF_LOG_ONLY)) {
+ if (update->new_target && !(update->flags & REF_LOG_ONLY)) {
-+ if (create_symref_lock(refs, lock, update->refname, update->new_target)) {
++ if (create_symref_lock(refs, lock, update->refname, update->new_target, err)) {
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto out;
+ }
@@ refs/files-backend.c: static int lock_ref_for_update(struct files_ref_store *ref
if (!(update->type & REF_ISSYMREF) &&
oideq(&lock->old_oid, &update->new_oid)) {
/*
+@@ refs/files-backend.c: static int files_transaction_prepare(struct ref_store *ref_store,
+ return ret;
+ }
+
++static int parse_and_write_reflog(struct files_ref_store *refs,
++ struct ref_update *update,
++ struct ref_lock *lock,
++ struct strbuf *err)
++{
++ if (update->new_target) {
++ /*
++ * We want to get the resolved OID for the target, to ensure
++ * that the correct value is added to the reflog.
++ */
++ if (!refs_resolve_ref_unsafe(&refs->base, update->new_target,
++ RESOLVE_REF_READING,
++ &update->new_oid, NULL)) {
++ /*
++ * TODO: currently we skip creating reflogs for dangling
++ * symref updates. It would be nice to capture this as
++ * zero oid updates however.
++ */
++ return 0;
++ }
++ }
++
++ if (files_log_ref_write(refs, lock->ref_name, &lock->old_oid,
++ &update->new_oid, update->msg, update->flags, err)) {
++ char *old_msg = strbuf_detach(err, NULL);
++
++ strbuf_addf(err, "cannot update the ref '%s': %s",
++ lock->ref_name, old_msg);
++ free(old_msg);
++ unlock_ref(lock);
++ update->backend_data = NULL;
++ return -1;
++ }
++
++ return 0;
++}
++
+ static int files_transaction_finish(struct ref_store *ref_store,
+ struct ref_transaction *transaction,
+ struct strbuf *err)
@@ refs/files-backend.c: static int files_transaction_finish(struct ref_store *ref_store,
if (update->flags & REF_NEEDS_COMMIT ||
@@ refs/files-backend.c: static int files_transaction_finish(struct ref_store *ref_
- &update->new_oid,
- update->msg, update->flags,
- err)) {
-+ int create_reflog = 1;
-+
-+ if (update->new_target) {
-+ /*
-+ * We want to get the resolved OID for the target, to ensure
-+ * that the correct value is added to the reflog.
-+ */
-+ if (!refs_resolve_ref_unsafe(&refs->base, update->new_target,
-+ RESOLVE_REF_READING, &update->new_oid, NULL)) {
-+ /* for dangling symrefs we skip creating a reflog entry. */
-+ create_reflog = 0;
-+ }
-+ }
-+
-+ if (create_reflog && files_log_ref_write(refs,
-+ lock->ref_name,
-+ &lock->old_oid,
-+ &update->new_oid,
-+ update->msg, update->flags,
-+ err)) {
- char *old_msg = strbuf_detach(err, NULL);
-
- strbuf_addf(err, "cannot update the ref '%s': %s",
-@@ refs/files-backend.c: static int files_transaction_finish(struct ref_store *ref_store,
+- char *old_msg = strbuf_detach(err, NULL);
+-
+- strbuf_addf(err, "cannot update the ref '%s': %s",
+- lock->ref_name, old_msg);
+- free(old_msg);
+- unlock_ref(lock);
+- update->backend_data = NULL;
++ if (parse_and_write_reflog(refs, update, lock, err)) {
+ ret = TRANSACTION_GENERIC_ERROR;
goto cleanup;
}
}
@@ refs/refs-internal.h: void base_ref_store_init(struct ref_store *refs, struct re
+ * takes into consideration that the update could be a regular
+ * ref or a symbolic ref.
+ */
-+int ref_update_is_null_new_value(struct ref_update *update);
++int ref_update_has_null_new_value(struct ref_update *update);
+
#endif /* REFS_REFS_INTERNAL_H */
@@ refs/reftable-backend.c: static int reftable_be_transaction_prepare(struct ref_s
* when the reference in question doesn't exist.
*/
- if (u->flags & REF_HAVE_NEW && !is_null_oid(&u->new_oid)) {
-+ if (u->flags & REF_HAVE_NEW && !ref_update_is_null_new_value(u)) {
++ if ((u->flags & REF_HAVE_NEW) && !ref_update_has_null_new_value(u)) {
ret = queue_transaction_update(refs, tx_data, u,
¤t_oid, err);
if (ret)
@@ refs/reftable-backend.c: static int reftable_be_transaction_prepare(struct ref_s
+ ret = -1;
+ goto done;
+ }
-+ } else if (u->flags & REF_HAVE_OLD && !oideq(¤t_oid, &u->old_oid)) {
++ } else if ((u->flags & REF_HAVE_OLD) && !oideq(¤t_oid, &u->old_oid)) {
if (is_null_oid(&u->old_oid))
strbuf_addf(err, _("cannot lock ref '%s': "
"reference already exists"),
@@ refs/reftable-backend.c: static int write_transaction_table(struct reftable_writ
* the given ref.
*/
- if (u->flags & REF_HAVE_NEW && !(u->type & REF_ISSYMREF) && is_null_oid(&u->new_oid)) {
-+ if (u->flags & REF_HAVE_NEW && !(u->type & REF_ISSYMREF) && ref_update_is_null_new_value(u)) {
++ if ((u->flags & REF_HAVE_NEW) && !(u->type & REF_ISSYMREF) && ref_update_has_null_new_value(u)) {
struct reftable_log_record log = {0};
struct reftable_iterator it = {0};
@@ refs/reftable-backend.c: static int write_transaction_table(struct reftable_writ
should_write_log(&arg->refs->base, u->refname))) {
struct reftable_log_record *log;
+ int create_reflog = 1;
++
++ if (u->new_target) {
++ if (!refs_resolve_ref_unsafe(&arg->refs->base, u->new_target,
++ RESOLVE_REF_READING, &u->new_oid, NULL)) {
++ /*
++ * TODO: currently we skip creating reflogs for dangling
++ * symref updates. It would be nice to capture this as
++ * zero oid updates however.
++ */
++ create_reflog = 0;
++ }
++ }
- ALLOC_GROW(logs, logs_nr + 1, logs_alloc);
- log = &logs[logs_nr++];
@@ refs/reftable-backend.c: static int write_transaction_table(struct reftable_writ
- memcpy(log->value.update.old_hash, tx_update->current_oid.hash, GIT_MAX_RAWSZ);
- log->value.update.message =
- xstrndup(u->msg, arg->refs->write_options.block_size / 2);
-+ if (u->new_target)
-+ if (!refs_resolve_ref_unsafe(&arg->refs->base, u->new_target,
-+ RESOLVE_REF_READING, &u->new_oid, NULL))
-+ /* for dangling symrefs we skip creating reflog */
-+ create_reflog = 0;
-+
+ if (create_reflog) {
+ ALLOC_GROW(logs, logs_nr + 1, logs_alloc);
+ log = &logs[logs_nr++];
@@ refs/reftable-backend.c: static int write_transaction_table(struct reftable_writ
continue;
- if (u->flags & REF_HAVE_NEW && is_null_oid(&u->new_oid)) {
-+ if (u->flags & REF_HAVE_NEW && u->new_target) {
++ if (u->new_target) {
+ struct reftable_ref_record ref = {
+ .refname = (char *)u->refname,
+ .value_type = REFTABLE_REF_SYMREF,
@@ refs/reftable-backend.c: static int write_transaction_table(struct reftable_writ
+ ret = reftable_writer_add_ref(writer, &ref);
+ if (ret < 0)
+ goto done;
-+ } else if (u->flags & REF_HAVE_NEW && ref_update_is_null_new_value(u)) {
++ } else if ((u->flags & REF_HAVE_NEW) && ref_update_has_null_new_value(u)) {
struct reftable_ref_record ref = {
.refname = (char *)u->refname,
.update_index = ts,
5: 5b55406430 ! 5: 636bf5ce98 refs: use transaction in `refs_create_symref()`
@@ Commit message
target. To do this, it uses a ref-backend specific function
`create_symref()`.
- In this previous commit, we introduce symref support in transactions.
- This means we can now use transactions to perform symref updates and not
- have to resort to `create_symref()`. Doing this allows us to remove and
- cleanup `create_symref()`, which we will do in the following commit.
+ In the previous commits, we introduced symref support in transactions.
+ This means we can now use transactions to perform symref updates and
+ don't have to resort to `create_symref()`. Doing this allows us to
+ remove and cleanup `create_symref()`, which we will do in the following
+ commit.
Modify the expected error message for a test in
- 't/t0610-reftable-basics.sh', since the error is now thrown from the
+ 't/t0610-reftable-basics.sh', since the error is now thrown from
'refs.c'. This is because in transactional updates, F/D conflicts are
caught before we're in the reference backend.
@@ t/t1416-ref-transaction-hooks.sh: test_expect_success 'interleaving hook calls s
+ git symbolic-ref refs/heads/symref refs/heads/main &&
+
+ cat >expect <<-EOF &&
-+ prepared
-+ $ZERO_OID ref:refs/heads/main refs/heads/symref
-+ committed
-+ $ZERO_OID ref:refs/heads/main refs/heads/symref
++ prepared
++ $ZERO_OID ref:refs/heads/main refs/heads/symref
++ committed
++ $ZERO_OID ref:refs/heads/main refs/heads/symref
+ EOF
+
+ test_cmp expect actual
6: 9e25816e68 = 6: 07fb23374f refs: rename `refs_create_symref()` to `refs_update_symref()`
7: 3836e25932 ! 7: 5c05813bcc refs: remove `create_symref` and associated dead code
@@ Commit message
transactions to perform symref updates. Earlier `refs_create_symref()`
used `create_symref()` to do the same.
- This means, we can now remove `create_symref()` and any code associated
- with it which is no longer used. We remove `create_symref()` code from
- all the reference backends and also remove it entirely from the
- `ref_storage_be` struct.
+ We can now remove `create_symref()` and any code associated with it
+ which is no longer used. We remove `create_symref()` code from all the
+ reference backends and also remove it entirely from the `ref_storage_be`
+ struct.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
@@ refs/files-backend.c: static int create_ref_symlink(struct ref_lock *lock, const
-
static int create_symref_lock(struct files_ref_store *refs,
struct ref_lock *lock, const char *refname,
- const char *target)
+ const char *target, struct strbuf *err)
@@ refs/files-backend.c: static int create_symref_lock(struct files_ref_store *refs,
return 0;
}
@@ refs/files-backend.c: static int create_symref_lock(struct files_ref_store *refs
- struct ref_lock *lock, const char *refname,
- const char *target, const char *logmsg)
-{
+- struct strbuf err = STRBUF_INIT;
- int ret;
-
- if (prefer_symlink_refs && !create_ref_symlink(lock, target)) {
@@ refs/files-backend.c: static int create_symref_lock(struct files_ref_store *refs
- return 0;
- }
-
-- ret = create_symref_lock(refs, lock, refname, target);
+- ret = create_symref_lock(refs, lock, refname, target, &err);
- if (!ret) {
- update_symref_reflog(refs, lock, refname, target, logmsg);
-
- if (commit_ref(lock) < 0)
- return error("unable to write symref for %s: %s", refname,
- strerror(errno));
+- } else {
+- return error("%s", err.buf);
- }
-
- return ret;
Karthik Nayak (7):
refs: accept symref values in `ref_transaction_update()`
files-backend: extract out `create_symref_lock()`
refs: support symrefs in 'reference-transaction' hook
refs: add support for transactional symref updates
refs: use transaction in `refs_create_symref()`
refs: rename `refs_create_symref()` to `refs_update_symref()`
refs: remove `create_symref` and associated dead code
Documentation/githooks.txt | 14 +-
branch.c | 2 +-
builtin/branch.c | 2 +-
builtin/fast-import.c | 5 +-
builtin/fetch.c | 2 +-
builtin/receive-pack.c | 1 +
builtin/replace.c | 2 +-
builtin/tag.c | 1 +
builtin/update-ref.c | 1 +
builtin/worktree.c | 2 +-
refs.c | 89 +++++++++----
refs.h | 20 ++-
refs/debug.c | 13 --
refs/files-backend.c | 213 +++++++++++++++++++------------
refs/packed-backend.c | 1 -
refs/refs-internal.h | 26 +++-
refs/reftable-backend.c | 165 +++++++++---------------
sequencer.c | 9 +-
t/helper/test-ref-store.c | 2 +-
t/t0610-reftable-basics.sh | 2 +-
t/t1416-ref-transaction-hooks.sh | 23 ++++
walker.c | 2 +-
22 files changed, 351 insertions(+), 246 deletions(-)
--
2.43.GIT
^ permalink raw reply [flat|nested] 194+ messages in thread
* [PATCH v6 1/7] refs: accept symref values in `ref_transaction_update()`
2024-05-03 12:41 ` [PATCH v6 " Karthik Nayak
@ 2024-05-03 12:41 ` Karthik Nayak
2024-05-04 15:18 ` Phillip Wood
2024-05-03 12:41 ` [PATCH v6 2/7] files-backend: extract out `create_symref_lock()` Karthik Nayak
` (8 subsequent siblings)
9 siblings, 1 reply; 194+ messages in thread
From: Karthik Nayak @ 2024-05-03 12:41 UTC (permalink / raw)
To: karthik.188; +Cc: christian.couder, git, gitster, ps
From: Karthik Nayak <karthik.188@gmail.com>
The function `ref_transaction_update()` obtains ref information and
flags to create a `ref_update` and add them to the transaction at hand.
To extend symref support in transactions, we need to also accept the
old and new ref targets and process it. This commit adds the required
parameters to the function and modifies all call sites.
The two parameters added are `new_target` and `old_target`. The
`new_target` is used to denote what the reference should point to when
the transaction is applied. Some functions allow this parameter to be
NULL, meaning that the reference is not changed.
The `old_target` denotes the value the reference must have before the
update. Some functions allow this parameter to be NULL, meaning that the
old value of the reference is not checked.
We also update the internal function `ref_transaction_add_update()`
similarly to take the two new parameters.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
branch.c | 2 +-
builtin/fast-import.c | 5 +++--
builtin/fetch.c | 2 +-
builtin/receive-pack.c | 1 +
builtin/replace.c | 2 +-
builtin/tag.c | 1 +
builtin/update-ref.c | 1 +
refs.c | 22 +++++++++++++++++-----
refs.h | 18 +++++++++++++++++-
refs/files-backend.c | 12 ++++++------
refs/refs-internal.h | 14 ++++++++++++++
refs/reftable-backend.c | 4 ++--
sequencer.c | 9 +++++----
walker.c | 2 +-
14 files changed, 71 insertions(+), 24 deletions(-)
diff --git a/branch.c b/branch.c
index e4a738fc7b..48af4c3ceb 100644
--- a/branch.c
+++ b/branch.c
@@ -627,7 +627,7 @@ void create_branch(struct repository *r,
if (!transaction ||
ref_transaction_update(transaction, ref.buf,
&oid, forcing ? NULL : null_oid(),
- 0, msg, &err) ||
+ NULL, NULL, 0, msg, &err) ||
ref_transaction_commit(transaction, &err))
die("%s", err.buf);
ref_transaction_free(transaction);
diff --git a/builtin/fast-import.c b/builtin/fast-import.c
index dc5a9d32dd..297dfb91a1 100644
--- a/builtin/fast-import.c
+++ b/builtin/fast-import.c
@@ -1634,7 +1634,7 @@ static int update_branch(struct branch *b)
transaction = ref_transaction_begin(&err);
if (!transaction ||
ref_transaction_update(transaction, b->name, &b->oid, &old_oid,
- 0, msg, &err) ||
+ NULL, NULL, 0, msg, &err) ||
ref_transaction_commit(transaction, &err)) {
ref_transaction_free(transaction);
error("%s", err.buf);
@@ -1675,7 +1675,8 @@ static void dump_tags(void)
strbuf_addf(&ref_name, "refs/tags/%s", t->name);
if (ref_transaction_update(transaction, ref_name.buf,
- &t->oid, NULL, 0, msg, &err)) {
+ &t->oid, NULL, NULL, NULL,
+ 0, msg, &err)) {
failure |= error("%s", err.buf);
goto cleanup;
}
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 5857d860db..66840b7c5b 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -668,7 +668,7 @@ static int s_update_ref(const char *action,
ret = ref_transaction_update(transaction, ref->name, &ref->new_oid,
check_old ? &ref->old_oid : NULL,
- 0, msg, &err);
+ NULL, NULL, 0, msg, &err);
if (ret) {
ret = STORE_REF_ERROR_OTHER;
goto out;
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index e8d7df14b6..b150ef39a8 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -1595,6 +1595,7 @@ static const char *update(struct command *cmd, struct shallow_info *si)
if (ref_transaction_update(transaction,
namespaced_name,
new_oid, old_oid,
+ NULL, NULL,
0, "push",
&err)) {
rp_error("%s", err.buf);
diff --git a/builtin/replace.c b/builtin/replace.c
index da59600ad2..7690687b0e 100644
--- a/builtin/replace.c
+++ b/builtin/replace.c
@@ -201,7 +201,7 @@ static int replace_object_oid(const char *object_ref,
transaction = ref_transaction_begin(&err);
if (!transaction ||
ref_transaction_update(transaction, ref.buf, repl, &prev,
- 0, NULL, &err) ||
+ NULL, NULL, 0, NULL, &err) ||
ref_transaction_commit(transaction, &err))
res = error("%s", err.buf);
diff --git a/builtin/tag.c b/builtin/tag.c
index 9a33cb50b4..40a65fdebc 100644
--- a/builtin/tag.c
+++ b/builtin/tag.c
@@ -660,6 +660,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
transaction = ref_transaction_begin(&err);
if (!transaction ||
ref_transaction_update(transaction, ref.buf, &object, &prev,
+ NULL, NULL,
create_reflog ? REF_FORCE_CREATE_REFLOG : 0,
reflog_msg.buf, &err) ||
ref_transaction_commit(transaction, &err)) {
diff --git a/builtin/update-ref.c b/builtin/update-ref.c
index e46afbc46d..21fdbf6ac8 100644
--- a/builtin/update-ref.c
+++ b/builtin/update-ref.c
@@ -204,6 +204,7 @@ static void parse_cmd_update(struct ref_transaction *transaction,
if (ref_transaction_update(transaction, refname,
&new_oid, have_old ? &old_oid : NULL,
+ NULL, NULL,
update_flags | create_reflog_flag,
msg, &err))
die("%s", err.buf);
diff --git a/refs.c b/refs.c
index 55d2e0b2cb..47bc9dd103 100644
--- a/refs.c
+++ b/refs.c
@@ -1228,6 +1228,7 @@ struct ref_update *ref_transaction_add_update(
const char *refname, unsigned int flags,
const struct object_id *new_oid,
const struct object_id *old_oid,
+ const char *new_target, const char *old_target,
const char *msg)
{
struct ref_update *update;
@@ -1235,6 +1236,11 @@ struct ref_update *ref_transaction_add_update(
if (transaction->state != REF_TRANSACTION_OPEN)
BUG("update called for transaction that is not open");
+ if (old_oid && !is_null_oid(old_oid) && old_target)
+ BUG("only one of old_oid and old_target should be non NULL");
+ if (new_oid && !is_null_oid(new_oid) && new_target)
+ BUG("only one of new_oid and new_target should be non NULL");
+
FLEX_ALLOC_STR(update, refname, refname);
ALLOC_GROW(transaction->updates, transaction->nr + 1, transaction->alloc);
transaction->updates[transaction->nr++] = update;
@@ -1253,6 +1259,8 @@ int ref_transaction_update(struct ref_transaction *transaction,
const char *refname,
const struct object_id *new_oid,
const struct object_id *old_oid,
+ const char *new_target,
+ const char *old_target,
unsigned int flags, const char *msg,
struct strbuf *err)
{
@@ -1280,7 +1288,8 @@ int ref_transaction_update(struct ref_transaction *transaction,
flags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0);
ref_transaction_add_update(transaction, refname, flags,
- new_oid, old_oid, msg);
+ new_oid, old_oid, new_target,
+ old_target, msg);
return 0;
}
@@ -1295,7 +1304,8 @@ int ref_transaction_create(struct ref_transaction *transaction,
return 1;
}
return ref_transaction_update(transaction, refname, new_oid,
- null_oid(), flags, msg, err);
+ null_oid(), NULL, NULL, flags,
+ msg, err);
}
int ref_transaction_delete(struct ref_transaction *transaction,
@@ -1308,7 +1318,8 @@ int ref_transaction_delete(struct ref_transaction *transaction,
BUG("delete called with old_oid set to zeros");
return ref_transaction_update(transaction, refname,
null_oid(), old_oid,
- flags, msg, err);
+ NULL, NULL, flags,
+ msg, err);
}
int ref_transaction_verify(struct ref_transaction *transaction,
@@ -1321,6 +1332,7 @@ int ref_transaction_verify(struct ref_transaction *transaction,
BUG("verify called with old_oid set to NULL");
return ref_transaction_update(transaction, refname,
NULL, old_oid,
+ NULL, NULL,
flags, NULL, err);
}
@@ -1335,8 +1347,8 @@ int refs_update_ref(struct ref_store *refs, const char *msg,
t = ref_store_transaction_begin(refs, &err);
if (!t ||
- ref_transaction_update(t, refname, new_oid, old_oid, flags, msg,
- &err) ||
+ ref_transaction_update(t, refname, new_oid, old_oid, NULL, NULL,
+ flags, msg, &err) ||
ref_transaction_commit(t, &err)) {
ret = 1;
ref_transaction_free(t);
diff --git a/refs.h b/refs.h
index d278775e08..c7851bf587 100644
--- a/refs.h
+++ b/refs.h
@@ -648,6 +648,16 @@ struct ref_transaction *ref_transaction_begin(struct strbuf *err);
* before the update. A copy of this value is made in the
* transaction.
*
+ * new_target -- the target reference that the reference will be
+ * updated to point to. If the reference is a regular reference,
+ * it will be converted to a symbolic reference. Cannot be set
+ * together with `new_oid`. A copy of this value is made in the
+ * transaction.
+ *
+ * old_target -- the reference that the reference must be pointing to.
+ * Canont be set together with `old_oid`. A copy of this value is
+ * made in the transaction.
+ *
* flags -- flags affecting the update, passed to
* update_ref_lock(). Possible flags: REF_NO_DEREF,
* REF_FORCE_CREATE_REFLOG. See those constants for more
@@ -713,7 +723,11 @@ struct ref_transaction *ref_transaction_begin(struct strbuf *err);
* beforehand. The old value is checked after the lock is taken to
* prevent races. If the old value doesn't agree with old_oid, the
* whole transaction fails. If old_oid is NULL, then the previous
- * value is not checked.
+ * value is not checked. If `old_target` is not NULL, treat the reference
+ * as a symbolic ref and validate that its target before the update is
+ * `old_target`. If the `new_target` is not NULL, then the reference
+ * will be updated to a symbolic ref which targets `new_target`.
+ * Together, these allow us to update between regular refs and symrefs.
*
* See the above comment "Reference transaction updates" for more
* information.
@@ -722,6 +736,8 @@ int ref_transaction_update(struct ref_transaction *transaction,
const char *refname,
const struct object_id *new_oid,
const struct object_id *old_oid,
+ const char *new_target,
+ const char *old_target,
unsigned int flags, const char *msg,
struct strbuf *err);
diff --git a/refs/files-backend.c b/refs/files-backend.c
index a098d14ea0..e4d0aa3d41 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -1198,7 +1198,7 @@ static void prune_ref(struct files_ref_store *refs, struct ref_to_prune *r)
ref_transaction_add_update(
transaction, r->name,
REF_NO_DEREF | REF_HAVE_NEW | REF_HAVE_OLD | REF_IS_PRUNING,
- null_oid(), &r->oid, NULL);
+ null_oid(), &r->oid, NULL, NULL, NULL);
if (ref_transaction_commit(transaction, &err))
goto cleanup;
@@ -1292,7 +1292,7 @@ static int files_pack_refs(struct ref_store *ref_store,
* packed-refs transaction:
*/
if (ref_transaction_update(transaction, iter->refname,
- iter->oid, NULL,
+ iter->oid, NULL, NULL, NULL,
REF_NO_DEREF, NULL, &err))
die("failure preparing to create packed reference %s: %s",
iter->refname, err.buf);
@@ -2309,7 +2309,7 @@ static int split_head_update(struct ref_update *update,
transaction, "HEAD",
update->flags | REF_LOG_ONLY | REF_NO_DEREF,
&update->new_oid, &update->old_oid,
- update->msg);
+ NULL, NULL, update->msg);
/*
* Add "HEAD". This insertion is O(N) in the transaction
@@ -2372,7 +2372,7 @@ static int split_symref_update(struct ref_update *update,
new_update = ref_transaction_add_update(
transaction, referent, new_flags,
&update->new_oid, &update->old_oid,
- update->msg);
+ NULL, NULL, update->msg);
new_update->parent_update = update;
@@ -2763,7 +2763,7 @@ static int files_transaction_prepare(struct ref_store *ref_store,
packed_transaction, update->refname,
REF_HAVE_NEW | REF_NO_DEREF,
&update->new_oid, NULL,
- NULL);
+ NULL, NULL, NULL);
}
}
@@ -3048,7 +3048,7 @@ static int files_initial_transaction_commit(struct ref_store *ref_store,
ref_transaction_add_update(packed_transaction, update->refname,
update->flags & ~REF_HAVE_OLD,
&update->new_oid, &update->old_oid,
- NULL);
+ NULL, NULL, NULL);
}
if (packed_refs_lock(refs->packed_ref_store, 0, err)) {
diff --git a/refs/refs-internal.h b/refs/refs-internal.h
index 56641aa57a..108f4ec419 100644
--- a/refs/refs-internal.h
+++ b/refs/refs-internal.h
@@ -124,6 +124,19 @@ struct ref_update {
*/
struct object_id old_oid;
+ /*
+ * If set, point the reference to this value. This can also be
+ * used to convert regular references to become symbolic refs.
+ * Cannot be set together with `new_oid`.
+ */
+ const char *new_target;
+
+ /*
+ * If set, check that the reference previously pointed to this
+ * value. Cannot be set together with `old_oid`.
+ */
+ const char *old_target;
+
/*
* One or more of REF_NO_DEREF, REF_FORCE_CREATE_REFLOG,
* REF_HAVE_NEW, REF_HAVE_OLD, or backend-specific flags.
@@ -173,6 +186,7 @@ struct ref_update *ref_transaction_add_update(
const char *refname, unsigned int flags,
const struct object_id *new_oid,
const struct object_id *old_oid,
+ const char *new_target, const char *old_target,
const char *msg);
/*
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index 1cda48c504..6104471199 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -829,7 +829,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
new_update = ref_transaction_add_update(
transaction, "HEAD",
u->flags | REF_LOG_ONLY | REF_NO_DEREF,
- &u->new_oid, &u->old_oid, u->msg);
+ &u->new_oid, &u->old_oid, NULL, NULL, u->msg);
string_list_insert(&affected_refnames, new_update->refname);
}
@@ -908,7 +908,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
*/
new_update = ref_transaction_add_update(
transaction, referent.buf, new_flags,
- &u->new_oid, &u->old_oid, u->msg);
+ &u->new_oid, &u->old_oid, NULL, NULL, u->msg);
new_update->parent_update = u;
/*
diff --git a/sequencer.c b/sequencer.c
index 88de4dc20f..61e007d85f 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -665,7 +665,7 @@ static int fast_forward_to(struct repository *r,
if (!transaction ||
ref_transaction_update(transaction, "HEAD",
to, unborn && !is_rebase_i(opts) ?
- null_oid() : from,
+ null_oid() : from, NULL, NULL,
0, sb.buf, &err) ||
ref_transaction_commit(transaction, &err)) {
ref_transaction_free(transaction);
@@ -1298,7 +1298,7 @@ int update_head_with_reflog(const struct commit *old_head,
if (!transaction ||
ref_transaction_update(transaction, "HEAD", new_head,
old_head ? &old_head->object.oid : null_oid(),
- 0, sb.buf, err) ||
+ NULL, NULL, 0, sb.buf, err) ||
ref_transaction_commit(transaction, err)) {
ret = -1;
}
@@ -3832,8 +3832,9 @@ static int do_label(struct repository *r, const char *name, int len)
} else if (repo_get_oid(r, "HEAD", &head_oid)) {
error(_("could not read HEAD"));
ret = -1;
- } else if (ref_transaction_update(transaction, ref_name.buf, &head_oid,
- NULL, 0, msg.buf, &err) < 0 ||
+ } else if (ref_transaction_update(transaction, ref_name.buf,
+ &head_oid, NULL, NULL, NULL,
+ 0, msg.buf, &err) < 0 ||
ref_transaction_commit(transaction, &err)) {
error("%s", err.buf);
ret = -1;
diff --git a/walker.c b/walker.c
index c0fd632d92..1b3df43906 100644
--- a/walker.c
+++ b/walker.c
@@ -324,7 +324,7 @@ int walker_fetch(struct walker *walker, int targets, char **target,
strbuf_reset(&refname);
strbuf_addf(&refname, "refs/%s", write_ref[i]);
if (ref_transaction_update(transaction, refname.buf,
- oids + i, NULL, 0,
+ oids + i, NULL, NULL, NULL, 0,
msg ? msg : "fetch (unknown)",
&err)) {
error("%s", err.buf);
--
2.43.GIT
^ permalink raw reply related [flat|nested] 194+ messages in thread
* Re: [PATCH v6 1/7] refs: accept symref values in `ref_transaction_update()`
2024-05-03 12:41 ` [PATCH v6 1/7] refs: accept symref values in `ref_transaction_update()` Karthik Nayak
@ 2024-05-04 15:18 ` Phillip Wood
2024-05-05 15:10 ` Karthik Nayak
0 siblings, 1 reply; 194+ messages in thread
From: Phillip Wood @ 2024-05-04 15:18 UTC (permalink / raw)
To: Karthik Nayak; +Cc: christian.couder, git, gitster, ps
Hi Karthik
On 03/05/2024 13:41, Karthik Nayak wrote:
> From: Karthik Nayak <karthik.188@gmail.com>
>
> The function `ref_transaction_update()` obtains ref information and
> flags to create a `ref_update` and add them to the transaction at hand.
>
> To extend symref support in transactions, we need to also accept the
> old and new ref targets and process it. This commit adds the required
> parameters to the function and modifies all call sites.
>
> The two parameters added are `new_target` and `old_target`. The
> `new_target` is used to denote what the reference should point to when
> the transaction is applied. Some functions allow this parameter to be
> NULL, meaning that the reference is not changed.
>
> The `old_target` denotes the value the reference must have before the
> update. Some functions allow this parameter to be NULL, meaning that the
> old value of the reference is not checked.
>
> We also update the internal function `ref_transaction_add_update()`
> similarly to take the two new parameters.
The documentation for the new parameters looks good to me now - thanks
for updating it. I'm confused about the assertions though as I mentioned
in my other message [1].
Best Wishes
Phillip
[1]
https://www.lore.kernel.org/git/7ca8c2c4-a9cc-4bec-b13c-95d7854b664b@gmail.com
> Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
> ---
> branch.c | 2 +-
> builtin/fast-import.c | 5 +++--
> builtin/fetch.c | 2 +-
> builtin/receive-pack.c | 1 +
> builtin/replace.c | 2 +-
> builtin/tag.c | 1 +
> builtin/update-ref.c | 1 +
> refs.c | 22 +++++++++++++++++-----
> refs.h | 18 +++++++++++++++++-
> refs/files-backend.c | 12 ++++++------
> refs/refs-internal.h | 14 ++++++++++++++
> refs/reftable-backend.c | 4 ++--
> sequencer.c | 9 +++++----
> walker.c | 2 +-
> 14 files changed, 71 insertions(+), 24 deletions(-)
>
> diff --git a/branch.c b/branch.c
> index e4a738fc7b..48af4c3ceb 100644
> --- a/branch.c
> +++ b/branch.c
> @@ -627,7 +627,7 @@ void create_branch(struct repository *r,
> if (!transaction ||
> ref_transaction_update(transaction, ref.buf,
> &oid, forcing ? NULL : null_oid(),
> - 0, msg, &err) ||
> + NULL, NULL, 0, msg, &err) ||
> ref_transaction_commit(transaction, &err))
> die("%s", err.buf);
> ref_transaction_free(transaction);
> diff --git a/builtin/fast-import.c b/builtin/fast-import.c
> index dc5a9d32dd..297dfb91a1 100644
> --- a/builtin/fast-import.c
> +++ b/builtin/fast-import.c
> @@ -1634,7 +1634,7 @@ static int update_branch(struct branch *b)
> transaction = ref_transaction_begin(&err);
> if (!transaction ||
> ref_transaction_update(transaction, b->name, &b->oid, &old_oid,
> - 0, msg, &err) ||
> + NULL, NULL, 0, msg, &err) ||
> ref_transaction_commit(transaction, &err)) {
> ref_transaction_free(transaction);
> error("%s", err.buf);
> @@ -1675,7 +1675,8 @@ static void dump_tags(void)
> strbuf_addf(&ref_name, "refs/tags/%s", t->name);
>
> if (ref_transaction_update(transaction, ref_name.buf,
> - &t->oid, NULL, 0, msg, &err)) {
> + &t->oid, NULL, NULL, NULL,
> + 0, msg, &err)) {
> failure |= error("%s", err.buf);
> goto cleanup;
> }
> diff --git a/builtin/fetch.c b/builtin/fetch.c
> index 5857d860db..66840b7c5b 100644
> --- a/builtin/fetch.c
> +++ b/builtin/fetch.c
> @@ -668,7 +668,7 @@ static int s_update_ref(const char *action,
>
> ret = ref_transaction_update(transaction, ref->name, &ref->new_oid,
> check_old ? &ref->old_oid : NULL,
> - 0, msg, &err);
> + NULL, NULL, 0, msg, &err);
> if (ret) {
> ret = STORE_REF_ERROR_OTHER;
> goto out;
> diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
> index e8d7df14b6..b150ef39a8 100644
> --- a/builtin/receive-pack.c
> +++ b/builtin/receive-pack.c
> @@ -1595,6 +1595,7 @@ static const char *update(struct command *cmd, struct shallow_info *si)
> if (ref_transaction_update(transaction,
> namespaced_name,
> new_oid, old_oid,
> + NULL, NULL,
> 0, "push",
> &err)) {
> rp_error("%s", err.buf);
> diff --git a/builtin/replace.c b/builtin/replace.c
> index da59600ad2..7690687b0e 100644
> --- a/builtin/replace.c
> +++ b/builtin/replace.c
> @@ -201,7 +201,7 @@ static int replace_object_oid(const char *object_ref,
> transaction = ref_transaction_begin(&err);
> if (!transaction ||
> ref_transaction_update(transaction, ref.buf, repl, &prev,
> - 0, NULL, &err) ||
> + NULL, NULL, 0, NULL, &err) ||
> ref_transaction_commit(transaction, &err))
> res = error("%s", err.buf);
>
> diff --git a/builtin/tag.c b/builtin/tag.c
> index 9a33cb50b4..40a65fdebc 100644
> --- a/builtin/tag.c
> +++ b/builtin/tag.c
> @@ -660,6 +660,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
> transaction = ref_transaction_begin(&err);
> if (!transaction ||
> ref_transaction_update(transaction, ref.buf, &object, &prev,
> + NULL, NULL,
> create_reflog ? REF_FORCE_CREATE_REFLOG : 0,
> reflog_msg.buf, &err) ||
> ref_transaction_commit(transaction, &err)) {
> diff --git a/builtin/update-ref.c b/builtin/update-ref.c
> index e46afbc46d..21fdbf6ac8 100644
> --- a/builtin/update-ref.c
> +++ b/builtin/update-ref.c
> @@ -204,6 +204,7 @@ static void parse_cmd_update(struct ref_transaction *transaction,
>
> if (ref_transaction_update(transaction, refname,
> &new_oid, have_old ? &old_oid : NULL,
> + NULL, NULL,
> update_flags | create_reflog_flag,
> msg, &err))
> die("%s", err.buf);
> diff --git a/refs.c b/refs.c
> index 55d2e0b2cb..47bc9dd103 100644
> --- a/refs.c
> +++ b/refs.c
> @@ -1228,6 +1228,7 @@ struct ref_update *ref_transaction_add_update(
> const char *refname, unsigned int flags,
> const struct object_id *new_oid,
> const struct object_id *old_oid,
> + const char *new_target, const char *old_target,
> const char *msg)
> {
> struct ref_update *update;
> @@ -1235,6 +1236,11 @@ struct ref_update *ref_transaction_add_update(
> if (transaction->state != REF_TRANSACTION_OPEN)
> BUG("update called for transaction that is not open");
>
> + if (old_oid && !is_null_oid(old_oid) && old_target)
> + BUG("only one of old_oid and old_target should be non NULL");
> + if (new_oid && !is_null_oid(new_oid) && new_target)
> + BUG("only one of new_oid and new_target should be non NULL");
> +
> FLEX_ALLOC_STR(update, refname, refname);
> ALLOC_GROW(transaction->updates, transaction->nr + 1, transaction->alloc);
> transaction->updates[transaction->nr++] = update;
> @@ -1253,6 +1259,8 @@ int ref_transaction_update(struct ref_transaction *transaction,
> const char *refname,
> const struct object_id *new_oid,
> const struct object_id *old_oid,
> + const char *new_target,
> + const char *old_target,
> unsigned int flags, const char *msg,
> struct strbuf *err)
> {
> @@ -1280,7 +1288,8 @@ int ref_transaction_update(struct ref_transaction *transaction,
> flags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0);
>
> ref_transaction_add_update(transaction, refname, flags,
> - new_oid, old_oid, msg);
> + new_oid, old_oid, new_target,
> + old_target, msg);
> return 0;
> }
>
> @@ -1295,7 +1304,8 @@ int ref_transaction_create(struct ref_transaction *transaction,
> return 1;
> }
> return ref_transaction_update(transaction, refname, new_oid,
> - null_oid(), flags, msg, err);
> + null_oid(), NULL, NULL, flags,
> + msg, err);
> }
>
> int ref_transaction_delete(struct ref_transaction *transaction,
> @@ -1308,7 +1318,8 @@ int ref_transaction_delete(struct ref_transaction *transaction,
> BUG("delete called with old_oid set to zeros");
> return ref_transaction_update(transaction, refname,
> null_oid(), old_oid,
> - flags, msg, err);
> + NULL, NULL, flags,
> + msg, err);
> }
>
> int ref_transaction_verify(struct ref_transaction *transaction,
> @@ -1321,6 +1332,7 @@ int ref_transaction_verify(struct ref_transaction *transaction,
> BUG("verify called with old_oid set to NULL");
> return ref_transaction_update(transaction, refname,
> NULL, old_oid,
> + NULL, NULL,
> flags, NULL, err);
> }
>
> @@ -1335,8 +1347,8 @@ int refs_update_ref(struct ref_store *refs, const char *msg,
>
> t = ref_store_transaction_begin(refs, &err);
> if (!t ||
> - ref_transaction_update(t, refname, new_oid, old_oid, flags, msg,
> - &err) ||
> + ref_transaction_update(t, refname, new_oid, old_oid, NULL, NULL,
> + flags, msg, &err) ||
> ref_transaction_commit(t, &err)) {
> ret = 1;
> ref_transaction_free(t);
> diff --git a/refs.h b/refs.h
> index d278775e08..c7851bf587 100644
> --- a/refs.h
> +++ b/refs.h
> @@ -648,6 +648,16 @@ struct ref_transaction *ref_transaction_begin(struct strbuf *err);
> * before the update. A copy of this value is made in the
> * transaction.
> *
> + * new_target -- the target reference that the reference will be
> + * updated to point to. If the reference is a regular reference,
> + * it will be converted to a symbolic reference. Cannot be set
> + * together with `new_oid`. A copy of this value is made in the
> + * transaction.
> + *
> + * old_target -- the reference that the reference must be pointing to.
> + * Canont be set together with `old_oid`. A copy of this value is
> + * made in the transaction.
> + *
> * flags -- flags affecting the update, passed to
> * update_ref_lock(). Possible flags: REF_NO_DEREF,
> * REF_FORCE_CREATE_REFLOG. See those constants for more
> @@ -713,7 +723,11 @@ struct ref_transaction *ref_transaction_begin(struct strbuf *err);
> * beforehand. The old value is checked after the lock is taken to
> * prevent races. If the old value doesn't agree with old_oid, the
> * whole transaction fails. If old_oid is NULL, then the previous
> - * value is not checked.
> + * value is not checked. If `old_target` is not NULL, treat the reference
> + * as a symbolic ref and validate that its target before the update is
> + * `old_target`. If the `new_target` is not NULL, then the reference
> + * will be updated to a symbolic ref which targets `new_target`.
> + * Together, these allow us to update between regular refs and symrefs.
> *
> * See the above comment "Reference transaction updates" for more
> * information.
> @@ -722,6 +736,8 @@ int ref_transaction_update(struct ref_transaction *transaction,
> const char *refname,
> const struct object_id *new_oid,
> const struct object_id *old_oid,
> + const char *new_target,
> + const char *old_target,
> unsigned int flags, const char *msg,
> struct strbuf *err);
>
> diff --git a/refs/files-backend.c b/refs/files-backend.c
> index a098d14ea0..e4d0aa3d41 100644
> --- a/refs/files-backend.c
> +++ b/refs/files-backend.c
> @@ -1198,7 +1198,7 @@ static void prune_ref(struct files_ref_store *refs, struct ref_to_prune *r)
> ref_transaction_add_update(
> transaction, r->name,
> REF_NO_DEREF | REF_HAVE_NEW | REF_HAVE_OLD | REF_IS_PRUNING,
> - null_oid(), &r->oid, NULL);
> + null_oid(), &r->oid, NULL, NULL, NULL);
> if (ref_transaction_commit(transaction, &err))
> goto cleanup;
>
> @@ -1292,7 +1292,7 @@ static int files_pack_refs(struct ref_store *ref_store,
> * packed-refs transaction:
> */
> if (ref_transaction_update(transaction, iter->refname,
> - iter->oid, NULL,
> + iter->oid, NULL, NULL, NULL,
> REF_NO_DEREF, NULL, &err))
> die("failure preparing to create packed reference %s: %s",
> iter->refname, err.buf);
> @@ -2309,7 +2309,7 @@ static int split_head_update(struct ref_update *update,
> transaction, "HEAD",
> update->flags | REF_LOG_ONLY | REF_NO_DEREF,
> &update->new_oid, &update->old_oid,
> - update->msg);
> + NULL, NULL, update->msg);
>
> /*
> * Add "HEAD". This insertion is O(N) in the transaction
> @@ -2372,7 +2372,7 @@ static int split_symref_update(struct ref_update *update,
> new_update = ref_transaction_add_update(
> transaction, referent, new_flags,
> &update->new_oid, &update->old_oid,
> - update->msg);
> + NULL, NULL, update->msg);
>
> new_update->parent_update = update;
>
> @@ -2763,7 +2763,7 @@ static int files_transaction_prepare(struct ref_store *ref_store,
> packed_transaction, update->refname,
> REF_HAVE_NEW | REF_NO_DEREF,
> &update->new_oid, NULL,
> - NULL);
> + NULL, NULL, NULL);
> }
> }
>
> @@ -3048,7 +3048,7 @@ static int files_initial_transaction_commit(struct ref_store *ref_store,
> ref_transaction_add_update(packed_transaction, update->refname,
> update->flags & ~REF_HAVE_OLD,
> &update->new_oid, &update->old_oid,
> - NULL);
> + NULL, NULL, NULL);
> }
>
> if (packed_refs_lock(refs->packed_ref_store, 0, err)) {
> diff --git a/refs/refs-internal.h b/refs/refs-internal.h
> index 56641aa57a..108f4ec419 100644
> --- a/refs/refs-internal.h
> +++ b/refs/refs-internal.h
> @@ -124,6 +124,19 @@ struct ref_update {
> */
> struct object_id old_oid;
>
> + /*
> + * If set, point the reference to this value. This can also be
> + * used to convert regular references to become symbolic refs.
> + * Cannot be set together with `new_oid`.
> + */
> + const char *new_target;
> +
> + /*
> + * If set, check that the reference previously pointed to this
> + * value. Cannot be set together with `old_oid`.
> + */
> + const char *old_target;
> +
> /*
> * One or more of REF_NO_DEREF, REF_FORCE_CREATE_REFLOG,
> * REF_HAVE_NEW, REF_HAVE_OLD, or backend-specific flags.
> @@ -173,6 +186,7 @@ struct ref_update *ref_transaction_add_update(
> const char *refname, unsigned int flags,
> const struct object_id *new_oid,
> const struct object_id *old_oid,
> + const char *new_target, const char *old_target,
> const char *msg);
>
> /*
> diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
> index 1cda48c504..6104471199 100644
> --- a/refs/reftable-backend.c
> +++ b/refs/reftable-backend.c
> @@ -829,7 +829,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
> new_update = ref_transaction_add_update(
> transaction, "HEAD",
> u->flags | REF_LOG_ONLY | REF_NO_DEREF,
> - &u->new_oid, &u->old_oid, u->msg);
> + &u->new_oid, &u->old_oid, NULL, NULL, u->msg);
> string_list_insert(&affected_refnames, new_update->refname);
> }
>
> @@ -908,7 +908,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
> */
> new_update = ref_transaction_add_update(
> transaction, referent.buf, new_flags,
> - &u->new_oid, &u->old_oid, u->msg);
> + &u->new_oid, &u->old_oid, NULL, NULL, u->msg);
> new_update->parent_update = u;
>
> /*
> diff --git a/sequencer.c b/sequencer.c
> index 88de4dc20f..61e007d85f 100644
> --- a/sequencer.c
> +++ b/sequencer.c
> @@ -665,7 +665,7 @@ static int fast_forward_to(struct repository *r,
> if (!transaction ||
> ref_transaction_update(transaction, "HEAD",
> to, unborn && !is_rebase_i(opts) ?
> - null_oid() : from,
> + null_oid() : from, NULL, NULL,
> 0, sb.buf, &err) ||
> ref_transaction_commit(transaction, &err)) {
> ref_transaction_free(transaction);
> @@ -1298,7 +1298,7 @@ int update_head_with_reflog(const struct commit *old_head,
> if (!transaction ||
> ref_transaction_update(transaction, "HEAD", new_head,
> old_head ? &old_head->object.oid : null_oid(),
> - 0, sb.buf, err) ||
> + NULL, NULL, 0, sb.buf, err) ||
> ref_transaction_commit(transaction, err)) {
> ret = -1;
> }
> @@ -3832,8 +3832,9 @@ static int do_label(struct repository *r, const char *name, int len)
> } else if (repo_get_oid(r, "HEAD", &head_oid)) {
> error(_("could not read HEAD"));
> ret = -1;
> - } else if (ref_transaction_update(transaction, ref_name.buf, &head_oid,
> - NULL, 0, msg.buf, &err) < 0 ||
> + } else if (ref_transaction_update(transaction, ref_name.buf,
> + &head_oid, NULL, NULL, NULL,
> + 0, msg.buf, &err) < 0 ||
> ref_transaction_commit(transaction, &err)) {
> error("%s", err.buf);
> ret = -1;
> diff --git a/walker.c b/walker.c
> index c0fd632d92..1b3df43906 100644
> --- a/walker.c
> +++ b/walker.c
> @@ -324,7 +324,7 @@ int walker_fetch(struct walker *walker, int targets, char **target,
> strbuf_reset(&refname);
> strbuf_addf(&refname, "refs/%s", write_ref[i]);
> if (ref_transaction_update(transaction, refname.buf,
> - oids + i, NULL, 0,
> + oids + i, NULL, NULL, NULL, 0,
> msg ? msg : "fetch (unknown)",
> &err)) {
> error("%s", err.buf);
^ permalink raw reply [flat|nested] 194+ messages in thread
* Re: [PATCH v6 1/7] refs: accept symref values in `ref_transaction_update()`
2024-05-04 15:18 ` Phillip Wood
@ 2024-05-05 15:10 ` Karthik Nayak
2024-05-05 15:19 ` phillip.wood123
0 siblings, 1 reply; 194+ messages in thread
From: Karthik Nayak @ 2024-05-05 15:10 UTC (permalink / raw)
To: phillip.wood; +Cc: christian.couder, git, gitster, ps
[-- Attachment #1: Type: text/plain, Size: 3324 bytes --]
Hey Phillip,
Phillip Wood <phillip.wood123@gmail.com> writes:
> Hi Karthik
>
> On 03/05/2024 13:41, Karthik Nayak wrote:
>> From: Karthik Nayak <karthik.188@gmail.com>
>>
>> The function `ref_transaction_update()` obtains ref information and
>> flags to create a `ref_update` and add them to the transaction at hand.
>>
>> To extend symref support in transactions, we need to also accept the
>> old and new ref targets and process it. This commit adds the required
>> parameters to the function and modifies all call sites.
>>
>> The two parameters added are `new_target` and `old_target`. The
>> `new_target` is used to denote what the reference should point to when
>> the transaction is applied. Some functions allow this parameter to be
>> NULL, meaning that the reference is not changed.
>>
>> The `old_target` denotes the value the reference must have before the
>> update. Some functions allow this parameter to be NULL, meaning that the
>> old value of the reference is not checked.
>>
>> We also update the internal function `ref_transaction_add_update()`
>> similarly to take the two new parameters.
>
> The documentation for the new parameters looks good to me now - thanks
> for updating it. I'm confused about the assertions though as I mentioned
> in my other message [1].
>
> Best Wishes
>
> Phillip
>
> [1]
> https://www.lore.kernel.org/git/7ca8c2c4-a9cc-4bec-b13c-95d7854b664b@gmail.com
>
Responding here since this is a newer thread.
This is done because in files-backend we split symref updates (see
`split_symref_update`) and add a new one for the target reference. Here
we pass along the update struct. This update struct is memset to 0 and
this is after the checks we do. So the 'new_oid' here would be set to 0
(null oid) even if the 'new_target' value is set. This made more sense
in the earlier set of patches, but probably a diff like this should work
for this series and can be amended later as needed (in the series which
adds the symref-* commands).
diff --git a/refs.c b/refs.c
index 3645b805c1..20d26da372 100644
--- a/refs.c
+++ b/refs.c
@@ -1238,9 +1238,9 @@ struct ref_update *ref_transaction_add_update(
if (transaction->state != REF_TRANSACTION_OPEN)
BUG("update called for transaction that is not open");
- if (old_oid && !is_null_oid(old_oid) && old_target)
+ if (old_oid && old_target)
BUG("only one of old_oid and old_target should be non NULL");
- if (new_oid && !is_null_oid(new_oid) && new_target)
+ if (new_oid && new_target)
BUG("only one of new_oid and new_target should be non NULL");
FLEX_ALLOC_STR(update, refname, refname);
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 3ce260d07d..a718164798 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -2328,8 +2328,9 @@ static int split_symref_update(struct ref_update *update,
new_update = ref_transaction_add_update(
transaction, referent, new_flags,
- &update->new_oid, &update->old_oid,
- NULL, NULL, update->msg);
+ update->new_target ? NULL : &update->new_oid,
+ update->old_target ? NULL : &update->old_oid,
+ update->new_target, update->old_target, update->msg);
new_update->parent_update = update;
I think it makes sense to make it fool proof and add this, I'll wait for
more reviews and re-roll in a day or so.
Thanks for following through.
[snip]
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply related [flat|nested] 194+ messages in thread
* Re: [PATCH v6 1/7] refs: accept symref values in `ref_transaction_update()`
2024-05-05 15:10 ` Karthik Nayak
@ 2024-05-05 15:19 ` phillip.wood123
0 siblings, 0 replies; 194+ messages in thread
From: phillip.wood123 @ 2024-05-05 15:19 UTC (permalink / raw)
To: Karthik Nayak, phillip.wood; +Cc: christian.couder, git, gitster, ps
Hi Karthik
On 05/05/2024 16:10, Karthik Nayak wrote:
> Hey Phillip,
>
> Phillip Wood <phillip.wood123@gmail.com> writes:
>
>> Hi Karthik
>>
>> On 03/05/2024 13:41, Karthik Nayak wrote:
>>> From: Karthik Nayak <karthik.188@gmail.com>
>>>
>>> The function `ref_transaction_update()` obtains ref information and
>>> flags to create a `ref_update` and add them to the transaction at hand.
>>>
>>> To extend symref support in transactions, we need to also accept the
>>> old and new ref targets and process it. This commit adds the required
>>> parameters to the function and modifies all call sites.
>>>
>>> The two parameters added are `new_target` and `old_target`. The
>>> `new_target` is used to denote what the reference should point to when
>>> the transaction is applied. Some functions allow this parameter to be
>>> NULL, meaning that the reference is not changed.
>>>
>>> The `old_target` denotes the value the reference must have before the
>>> update. Some functions allow this parameter to be NULL, meaning that the
>>> old value of the reference is not checked.
>>>
>>> We also update the internal function `ref_transaction_add_update()`
>>> similarly to take the two new parameters.
>>
>> The documentation for the new parameters looks good to me now - thanks
>> for updating it. I'm confused about the assertions though as I mentioned
>> in my other message [1].
>>
>> Best Wishes
>>
>> Phillip
>>
>> [1]
>> https://www.lore.kernel.org/git/7ca8c2c4-a9cc-4bec-b13c-95d7854b664b@gmail.com
>>
>
> Responding here since this is a newer thread.
>
> This is done because in files-backend we split symref updates (see
> `split_symref_update`) and add a new one for the target reference. Here
> we pass along the update struct. This update struct is memset to 0 and
> this is after the checks we do. So the 'new_oid' here would be set to 0
> (null oid) even if the 'new_target' value is set. This made more sense
> in the earlier set of patches, but probably a diff like this should work
> for this series and can be amended later as needed (in the series which
> adds the symref-* commands).
Thanks for the explanation - it would be good to fix this because the
assertions don't catch misuses of ref_transaction_update() at the moment
when old_oid points to the null oid and old_target is also set.
Best Wishes
Phillip
> diff --git a/refs.c b/refs.c
> index 3645b805c1..20d26da372 100644
> --- a/refs.c
> +++ b/refs.c
> @@ -1238,9 +1238,9 @@ struct ref_update *ref_transaction_add_update(
> if (transaction->state != REF_TRANSACTION_OPEN)
> BUG("update called for transaction that is not open");
>
> - if (old_oid && !is_null_oid(old_oid) && old_target)
> + if (old_oid && old_target)
> BUG("only one of old_oid and old_target should be non NULL");
> - if (new_oid && !is_null_oid(new_oid) && new_target)
> + if (new_oid && new_target)
> BUG("only one of new_oid and new_target should be non NULL");
>
> FLEX_ALLOC_STR(update, refname, refname);
> diff --git a/refs/files-backend.c b/refs/files-backend.c
> index 3ce260d07d..a718164798 100644
> --- a/refs/files-backend.c
> +++ b/refs/files-backend.c
> @@ -2328,8 +2328,9 @@ static int split_symref_update(struct ref_update *update,
>
> new_update = ref_transaction_add_update(
> transaction, referent, new_flags,
> - &update->new_oid, &update->old_oid,
> - NULL, NULL, update->msg);
> + update->new_target ? NULL : &update->new_oid,
> + update->old_target ? NULL : &update->old_oid,
> + update->new_target, update->old_target, update->msg);
>
> new_update->parent_update = update;
>
> I think it makes sense to make it fool proof and add this, I'll wait for
> more reviews and re-roll in a day or so.
>
> Thanks for following through.
>
> [snip]
^ permalink raw reply [flat|nested] 194+ messages in thread
* [PATCH v6 2/7] files-backend: extract out `create_symref_lock()`
2024-05-03 12:41 ` [PATCH v6 " Karthik Nayak
2024-05-03 12:41 ` [PATCH v6 1/7] refs: accept symref values in `ref_transaction_update()` Karthik Nayak
@ 2024-05-03 12:41 ` Karthik Nayak
2024-05-03 12:41 ` [PATCH v6 3/7] refs: support symrefs in 'reference-transaction' hook Karthik Nayak
` (7 subsequent siblings)
9 siblings, 0 replies; 194+ messages in thread
From: Karthik Nayak @ 2024-05-03 12:41 UTC (permalink / raw)
To: karthik.188; +Cc: christian.couder, git, gitster, ps
From: Karthik Nayak <karthik.188@gmail.com>
The function `create_symref_locked()` creates a symref by creating a
'<symref>.lock' file and then committing the symref lock, which creates
the final symref.
Extract the early half of `create_symref_locked()` into a new helper
function `create_symref_lock()`. Because the name of the new function is
too similar to the original, rename the original to
`create_and_commit_symref()` to avoid confusion.
The new function `create_symref_locked()` can be used to create the
symref lock in a separate step from that of committing it. This allows
to add transactional support for symrefs, where the lock would be
created in the preparation step and the lock would be committed in the
finish step.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
refs/files-backend.c | 51 ++++++++++++++++++++++++++++++++------------
1 file changed, 37 insertions(+), 14 deletions(-)
diff --git a/refs/files-backend.c b/refs/files-backend.c
index e4d0aa3d41..74a713090c 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -1920,27 +1920,49 @@ static void update_symref_reflog(struct files_ref_store *refs,
}
}
-static int create_symref_locked(struct files_ref_store *refs,
- struct ref_lock *lock, const char *refname,
- const char *target, const char *logmsg)
+static int create_symref_lock(struct files_ref_store *refs,
+ struct ref_lock *lock, const char *refname,
+ const char *target, struct strbuf *err)
{
+ if (!fdopen_lock_file(&lock->lk, "w")) {
+ strbuf_addf(err, "unable to fdopen %s: %s",
+ get_lock_file_path(&lock->lk), strerror(errno));
+ return -1;
+ }
+
+ if (fprintf(get_lock_file_fp(&lock->lk), "ref: %s\n", target) < 0) {
+ strbuf_addf(err, "unable to write to %s: %s",
+ get_lock_file_path(&lock->lk), strerror(errno));
+ return -1;
+ }
+
+ return 0;
+}
+
+static int create_and_commit_symref(struct files_ref_store *refs,
+ struct ref_lock *lock, const char *refname,
+ const char *target, const char *logmsg)
+{
+ struct strbuf err = STRBUF_INIT;
+ int ret;
+
if (prefer_symlink_refs && !create_ref_symlink(lock, target)) {
update_symref_reflog(refs, lock, refname, target, logmsg);
return 0;
}
- if (!fdopen_lock_file(&lock->lk, "w"))
- return error("unable to fdopen %s: %s",
- get_lock_file_path(&lock->lk), strerror(errno));
+ ret = create_symref_lock(refs, lock, refname, target, &err);
+ if (!ret) {
+ update_symref_reflog(refs, lock, refname, target, logmsg);
- update_symref_reflog(refs, lock, refname, target, logmsg);
+ if (commit_ref(lock) < 0)
+ return error("unable to write symref for %s: %s", refname,
+ strerror(errno));
+ } else {
+ return error("%s", err.buf);
+ }
- /* no error check; commit_ref will check ferror */
- fprintf(get_lock_file_fp(&lock->lk), "ref: %s\n", target);
- if (commit_ref(lock) < 0)
- return error("unable to write symref for %s: %s", refname,
- strerror(errno));
- return 0;
+ return ret;
}
static int files_create_symref(struct ref_store *ref_store,
@@ -1960,7 +1982,8 @@ static int files_create_symref(struct ref_store *ref_store,
return -1;
}
- ret = create_symref_locked(refs, lock, refname, target, logmsg);
+ ret = create_and_commit_symref(refs, lock, refname, target, logmsg);
+
unlock_ref(lock);
return ret;
}
--
2.43.GIT
^ permalink raw reply related [flat|nested] 194+ messages in thread
* [PATCH v6 3/7] refs: support symrefs in 'reference-transaction' hook
2024-05-03 12:41 ` [PATCH v6 " Karthik Nayak
2024-05-03 12:41 ` [PATCH v6 1/7] refs: accept symref values in `ref_transaction_update()` Karthik Nayak
2024-05-03 12:41 ` [PATCH v6 2/7] files-backend: extract out `create_symref_lock()` Karthik Nayak
@ 2024-05-03 12:41 ` Karthik Nayak
2024-05-03 12:41 ` [PATCH v6 4/7] refs: add support for transactional symref updates Karthik Nayak
` (6 subsequent siblings)
9 siblings, 0 replies; 194+ messages in thread
From: Karthik Nayak @ 2024-05-03 12:41 UTC (permalink / raw)
To: karthik.188; +Cc: christian.couder, git, gitster, ps
From: Karthik Nayak <karthik.188@gmail.com>
The 'reference-transaction' hook runs whenever a reference update is
made to the system. In a previous commit, we added the `old_target` and
`new_target` fields to the `reference_transaction_update()`. In
following commits we'll also add the code to handle symref's in the
reference backends.
Support symrefs also in the 'reference-transaction' hook, by modifying
the current format:
<old-oid> SP <new-oid> SP <ref-name> LF
to be be:
<old-value> SP <new-value> SP <ref-name> LF
where for regular refs the output would not change and remain the same.
But when either 'old-value' or 'new-value' is a symref, we print the ref
as 'ref:<ref-target>'.
This does break backward compatibility, but the 'reference-transaction'
hook's documentation always stated that support for symbolic references
may be added in the future.
We do not add any tests in this commit since there is no git command
which activates this flow, in an upcoming commit, we'll start using
transaction based symref updates as the default, we'll add tests there
for the hook too.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
Documentation/githooks.txt | 14 +++++++++-----
refs.c | 20 ++++++++++++++++----
2 files changed, 25 insertions(+), 9 deletions(-)
diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
index ee9b92c90d..06e997131b 100644
--- a/Documentation/githooks.txt
+++ b/Documentation/githooks.txt
@@ -486,7 +486,7 @@ reference-transaction
This hook is invoked by any Git command that performs reference
updates. It executes whenever a reference transaction is prepared,
committed or aborted and may thus get called multiple times. The hook
-does not cover symbolic references (but that may change in the future).
+also supports symbolic reference updates.
The hook takes exactly one argument, which is the current state the
given reference transaction is in:
@@ -503,16 +503,20 @@ given reference transaction is in:
For each reference update that was added to the transaction, the hook
receives on standard input a line of the format:
- <old-oid> SP <new-oid> SP <ref-name> LF
+ <old-value> SP <new-value> SP <ref-name> LF
-where `<old-oid>` is the old object name passed into the reference
-transaction, `<new-oid>` is the new object name to be stored in the
+where `<old-value>` is the old object name passed into the reference
+transaction, `<new-value>` is the new object name to be stored in the
ref and `<ref-name>` is the full name of the ref. When force updating
the reference regardless of its current value or when the reference is
-to be created anew, `<old-oid>` is the all-zeroes object name. To
+to be created anew, `<old-value>` is the all-zeroes object name. To
distinguish these cases, you can inspect the current value of
`<ref-name>` via `git rev-parse`.
+For symbolic reference updates the `<old_value>` and `<new-value>`
+fields could denote references instead of objects. A reference will be
+denoted with a 'ref:' prefix, like `ref:<ref-target>`.
+
The exit status of the hook is ignored for any state except for the
"prepared" state. In the "prepared" state, a non-zero exit status will
cause the transaction to be aborted. The hook will not be called with
diff --git a/refs.c b/refs.c
index 47bc9dd103..203a101988 100644
--- a/refs.c
+++ b/refs.c
@@ -2350,10 +2350,22 @@ static int run_transaction_hook(struct ref_transaction *transaction,
struct ref_update *update = transaction->updates[i];
strbuf_reset(&buf);
- strbuf_addf(&buf, "%s %s %s\n",
- oid_to_hex(&update->old_oid),
- oid_to_hex(&update->new_oid),
- update->refname);
+
+ if (!(update->flags & REF_HAVE_OLD))
+ strbuf_addf(&buf, "%s ", oid_to_hex(null_oid()));
+ else if (update->old_target)
+ strbuf_addf(&buf, "ref:%s ", update->old_target);
+ else
+ strbuf_addf(&buf, "%s ", oid_to_hex(&update->old_oid));
+
+ if (!(update->flags & REF_HAVE_NEW))
+ strbuf_addf(&buf, "%s ", oid_to_hex(null_oid()));
+ else if (update->new_target)
+ strbuf_addf(&buf, "ref:%s ", update->new_target);
+ else
+ strbuf_addf(&buf, "%s ", oid_to_hex(&update->new_oid));
+
+ strbuf_addf(&buf, "%s\n", update->refname);
if (write_in_full(proc.in, buf.buf, buf.len) < 0) {
if (errno != EPIPE) {
--
2.43.GIT
^ permalink raw reply related [flat|nested] 194+ messages in thread
* [PATCH v6 4/7] refs: add support for transactional symref updates
2024-05-03 12:41 ` [PATCH v6 " Karthik Nayak
` (2 preceding siblings ...)
2024-05-03 12:41 ` [PATCH v6 3/7] refs: support symrefs in 'reference-transaction' hook Karthik Nayak
@ 2024-05-03 12:41 ` Karthik Nayak
2024-05-05 14:09 ` Phillip Wood
2024-05-03 12:41 ` [PATCH v6 5/7] refs: use transaction in `refs_create_symref()` Karthik Nayak
` (5 subsequent siblings)
9 siblings, 1 reply; 194+ messages in thread
From: Karthik Nayak @ 2024-05-03 12:41 UTC (permalink / raw)
To: karthik.188; +Cc: christian.couder, git, gitster, ps
From: Karthik Nayak <karthik.188@gmail.com>
The reference backends currently support transactional reference
updates. While this is exposed to users via 'git-update-ref' and its
'--stdin' mode, it is also used internally within various commands.
However, we never supported transactional updates of symrefs. Let's add
support for symrefs in both the 'files' and the 'reftable' backend.
Here, we add and use `ref_update_has_null_new_value()`, a helper
function which is used to check if there is a new_value in a reference
update. The new value could either be a symref target `new_target` or a
OID `new_oid`.
With this, now transactional updates (verify, create, delete, update)
can be used for:
- regular refs
- symbolic refs
- conversion of regular to symbolic refs and vice versa
This also allows us to expose this to users via new commands in
'git-update-ref' in the future.
Note that a dangling symref update does not record a new reflog entry,
which is unchanged before and after this commit.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
refs.c | 15 ++++-
refs/files-backend.c | 137 ++++++++++++++++++++++++++++++++++------
refs/refs-internal.h | 7 ++
refs/reftable-backend.c | 77 +++++++++++++++++-----
4 files changed, 198 insertions(+), 38 deletions(-)
diff --git a/refs.c b/refs.c
index 203a101988..acb153b6f8 100644
--- a/refs.c
+++ b/refs.c
@@ -1217,6 +1217,8 @@ void ref_transaction_free(struct ref_transaction *transaction)
for (i = 0; i < transaction->nr; i++) {
free(transaction->updates[i]->msg);
+ free((char *)transaction->updates[i]->new_target);
+ free((char *)transaction->updates[i]->old_target);
free(transaction->updates[i]);
}
free(transaction->updates);
@@ -1247,10 +1249,13 @@ struct ref_update *ref_transaction_add_update(
update->flags = flags;
- if (flags & REF_HAVE_NEW)
+ update->new_target = xstrdup_or_null(new_target);
+ update->old_target = xstrdup_or_null(old_target);
+ if ((flags & REF_HAVE_NEW) && new_oid)
oidcpy(&update->new_oid, new_oid);
- if (flags & REF_HAVE_OLD)
+ if ((flags & REF_HAVE_OLD) && old_oid)
oidcpy(&update->old_oid, old_oid);
+
update->msg = normalize_reflog_message(msg);
return update;
}
@@ -1286,6 +1291,7 @@ int ref_transaction_update(struct ref_transaction *transaction,
flags &= REF_TRANSACTION_UPDATE_ALLOWED_FLAGS;
flags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0);
+ flags |= (new_target ? REF_HAVE_NEW : 0) | (old_target ? REF_HAVE_OLD : 0);
ref_transaction_add_update(transaction, refname, flags,
new_oid, old_oid, new_target,
@@ -2814,3 +2820,8 @@ int copy_existing_ref(const char *oldref, const char *newref, const char *logmsg
{
return refs_copy_existing_ref(get_main_ref_store(the_repository), oldref, newref, logmsg);
}
+
+int ref_update_has_null_new_value(struct ref_update *update)
+{
+ return !update->new_target && is_null_oid(&update->new_oid);
+}
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 74a713090c..e1f0ca74c0 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -2434,6 +2434,36 @@ static const char *original_update_refname(struct ref_update *update)
return update->refname;
}
+/*
+ * Check whether the old_target values stored in update are consistent
+ * with current_target, which is the symbolic reference's current value.
+ * If everything is OK, return 0; otherwise, write an error message to
+ * err and return -1.
+ */
+static int check_old_target(struct ref_update *update,
+ const char *current_target,
+ struct strbuf *err)
+{
+ if (!update->old_target)
+ BUG("called without old_target set");
+
+ if (!strcmp(update->old_target, current_target))
+ return 0;
+
+ if (!strcmp(current_target, ""))
+ strbuf_addf(err, "cannot lock ref '%s': "
+ "reference is missing but expected %s",
+ original_update_refname(update),
+ update->old_target);
+ else
+ strbuf_addf(err, "cannot lock ref '%s': "
+ "is at %s but expected %s",
+ original_update_refname(update),
+ current_target, update->old_target);
+
+ return -1;
+}
+
/*
* Check whether the REF_HAVE_OLD and old_oid values stored in update
* are consistent with oid, which is the reference's current value. If
@@ -2494,7 +2524,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,
files_assert_main_repository(refs, "lock_ref_for_update");
- if ((update->flags & REF_HAVE_NEW) && is_null_oid(&update->new_oid))
+ if ((update->flags & REF_HAVE_NEW) && ref_update_has_null_new_value(update))
update->flags |= REF_DELETING;
if (head_ref) {
@@ -2537,7 +2567,14 @@ static int lock_ref_for_update(struct files_ref_store *refs,
ret = TRANSACTION_GENERIC_ERROR;
goto out;
}
- } else if (check_old_oid(update, &lock->old_oid, err)) {
+ }
+
+ if (update->old_target) {
+ if (check_old_target(update, referent.buf, err)) {
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto out;
+ }
+ } else if (check_old_oid(update, &lock->old_oid, err)) {
ret = TRANSACTION_GENERIC_ERROR;
goto out;
}
@@ -2558,7 +2595,17 @@ static int lock_ref_for_update(struct files_ref_store *refs,
} else {
struct ref_update *parent_update;
- if (check_old_oid(update, &lock->old_oid, err)) {
+ /*
+ * Even if the ref is a regular ref, if `old_target` is set, we
+ * check the referent value. Ideally `old_target` should only
+ * be set for symrefs, but we're strict about its usage.
+ */
+ if (update->old_target) {
+ if (check_old_target(update, referent.buf, err)) {
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto out;
+ }
+ } else if (check_old_oid(update, &lock->old_oid, err)) {
ret = TRANSACTION_GENERIC_ERROR;
goto out;
}
@@ -2576,9 +2623,27 @@ static int lock_ref_for_update(struct files_ref_store *refs,
}
}
- if ((update->flags & REF_HAVE_NEW) &&
- !(update->flags & REF_DELETING) &&
- !(update->flags & REF_LOG_ONLY)) {
+ if (update->new_target && !(update->flags & REF_LOG_ONLY)) {
+ if (create_symref_lock(refs, lock, update->refname, update->new_target, err)) {
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto out;
+ }
+
+ if (close_ref_gently(lock)) {
+ strbuf_addf(err, "couldn't close '%s.lock'",
+ update->refname);
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto out;
+ }
+
+ /*
+ * Once we have created the symref lock, the commit
+ * phase of the transaction only needs to commit the lock.
+ */
+ update->flags |= REF_NEEDS_COMMIT;
+ } else if ((update->flags & REF_HAVE_NEW) &&
+ !(update->flags & REF_DELETING) &&
+ !(update->flags & REF_LOG_ONLY)) {
if (!(update->type & REF_ISSYMREF) &&
oideq(&lock->old_oid, &update->new_oid)) {
/*
@@ -2841,6 +2906,43 @@ static int files_transaction_prepare(struct ref_store *ref_store,
return ret;
}
+static int parse_and_write_reflog(struct files_ref_store *refs,
+ struct ref_update *update,
+ struct ref_lock *lock,
+ struct strbuf *err)
+{
+ if (update->new_target) {
+ /*
+ * We want to get the resolved OID for the target, to ensure
+ * that the correct value is added to the reflog.
+ */
+ if (!refs_resolve_ref_unsafe(&refs->base, update->new_target,
+ RESOLVE_REF_READING,
+ &update->new_oid, NULL)) {
+ /*
+ * TODO: currently we skip creating reflogs for dangling
+ * symref updates. It would be nice to capture this as
+ * zero oid updates however.
+ */
+ return 0;
+ }
+ }
+
+ if (files_log_ref_write(refs, lock->ref_name, &lock->old_oid,
+ &update->new_oid, update->msg, update->flags, err)) {
+ char *old_msg = strbuf_detach(err, NULL);
+
+ strbuf_addf(err, "cannot update the ref '%s': %s",
+ lock->ref_name, old_msg);
+ free(old_msg);
+ unlock_ref(lock);
+ update->backend_data = NULL;
+ return -1;
+ }
+
+ return 0;
+}
+
static int files_transaction_finish(struct ref_store *ref_store,
struct ref_transaction *transaction,
struct strbuf *err)
@@ -2871,23 +2973,20 @@ static int files_transaction_finish(struct ref_store *ref_store,
if (update->flags & REF_NEEDS_COMMIT ||
update->flags & REF_LOG_ONLY) {
- if (files_log_ref_write(refs,
- lock->ref_name,
- &lock->old_oid,
- &update->new_oid,
- update->msg, update->flags,
- err)) {
- char *old_msg = strbuf_detach(err, NULL);
-
- strbuf_addf(err, "cannot update the ref '%s': %s",
- lock->ref_name, old_msg);
- free(old_msg);
- unlock_ref(lock);
- update->backend_data = NULL;
+ if (parse_and_write_reflog(refs, update, lock, err)) {
ret = TRANSACTION_GENERIC_ERROR;
goto cleanup;
}
}
+
+ /*
+ * We try creating a symlink, if that succeeds we continue to the
+ * next update. If not, we try and create a regular symref.
+ */
+ if (update->new_target && prefer_symlink_refs)
+ if (!create_ref_symlink(lock, update->new_target))
+ continue;
+
if (update->flags & REF_NEEDS_COMMIT) {
clear_loose_ref_cache(refs);
if (commit_ref(lock)) {
diff --git a/refs/refs-internal.h b/refs/refs-internal.h
index 108f4ec419..03eda70ea4 100644
--- a/refs/refs-internal.h
+++ b/refs/refs-internal.h
@@ -749,4 +749,11 @@ void base_ref_store_init(struct ref_store *refs, struct repository *repo,
*/
struct ref_store *maybe_debug_wrap_ref_store(const char *gitdir, struct ref_store *store);
+/*
+ * Helper function to check if the new value is null, this
+ * takes into consideration that the update could be a regular
+ * ref or a symbolic ref.
+ */
+int ref_update_has_null_new_value(struct ref_update *update);
+
#endif /* REFS_REFS_INTERNAL_H */
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index 6104471199..54c4d8b771 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -856,7 +856,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
* There is no need to write the reference deletion
* when the reference in question doesn't exist.
*/
- if (u->flags & REF_HAVE_NEW && !is_null_oid(&u->new_oid)) {
+ if ((u->flags & REF_HAVE_NEW) && !ref_update_has_null_new_value(u)) {
ret = queue_transaction_update(refs, tx_data, u,
¤t_oid, err);
if (ret)
@@ -907,8 +907,10 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
* intertwined with the locking in files-backend.c.
*/
new_update = ref_transaction_add_update(
- transaction, referent.buf, new_flags,
- &u->new_oid, &u->old_oid, NULL, NULL, u->msg);
+ transaction, referent.buf, new_flags,
+ &u->new_oid, &u->old_oid, u->new_target,
+ u->old_target, u->msg);
+
new_update->parent_update = u;
/*
@@ -938,7 +940,22 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
* individual refs. But the error messages match what the files
* backend returns, which keeps our tests happy.
*/
- if (u->flags & REF_HAVE_OLD && !oideq(¤t_oid, &u->old_oid)) {
+ if (u->old_target) {
+ if (strcmp(referent.buf, u->old_target)) {
+ if (!strcmp(referent.buf, ""))
+ strbuf_addf(err, "verifying symref target: '%s': "
+ "reference is missing but expected %s",
+ original_update_refname(u),
+ u->old_target);
+ else
+ strbuf_addf(err, "verifying symref target: '%s': "
+ "is at %s but expected %s",
+ original_update_refname(u),
+ referent.buf, u->old_target);
+ ret = -1;
+ goto done;
+ }
+ } else if ((u->flags & REF_HAVE_OLD) && !oideq(¤t_oid, &u->old_oid)) {
if (is_null_oid(&u->old_oid))
strbuf_addf(err, _("cannot lock ref '%s': "
"reference already exists"),
@@ -1043,7 +1060,7 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data
* - `core.logAllRefUpdates` tells us to create the reflog for
* the given ref.
*/
- if (u->flags & REF_HAVE_NEW && !(u->type & REF_ISSYMREF) && is_null_oid(&u->new_oid)) {
+ if ((u->flags & REF_HAVE_NEW) && !(u->type & REF_ISSYMREF) && ref_update_has_null_new_value(u)) {
struct reftable_log_record log = {0};
struct reftable_iterator it = {0};
@@ -1084,24 +1101,50 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data
(u->flags & REF_FORCE_CREATE_REFLOG ||
should_write_log(&arg->refs->base, u->refname))) {
struct reftable_log_record *log;
+ int create_reflog = 1;
+
+ if (u->new_target) {
+ if (!refs_resolve_ref_unsafe(&arg->refs->base, u->new_target,
+ RESOLVE_REF_READING, &u->new_oid, NULL)) {
+ /*
+ * TODO: currently we skip creating reflogs for dangling
+ * symref updates. It would be nice to capture this as
+ * zero oid updates however.
+ */
+ create_reflog = 0;
+ }
+ }
- ALLOC_GROW(logs, logs_nr + 1, logs_alloc);
- log = &logs[logs_nr++];
- memset(log, 0, sizeof(*log));
-
- fill_reftable_log_record(log);
- log->update_index = ts;
- log->refname = xstrdup(u->refname);
- memcpy(log->value.update.new_hash, u->new_oid.hash, GIT_MAX_RAWSZ);
- memcpy(log->value.update.old_hash, tx_update->current_oid.hash, GIT_MAX_RAWSZ);
- log->value.update.message =
- xstrndup(u->msg, arg->refs->write_options.block_size / 2);
+ if (create_reflog) {
+ ALLOC_GROW(logs, logs_nr + 1, logs_alloc);
+ log = &logs[logs_nr++];
+ memset(log, 0, sizeof(*log));
+
+ fill_reftable_log_record(log);
+ log->update_index = ts;
+ log->refname = xstrdup(u->refname);
+ memcpy(log->value.update.new_hash, u->new_oid.hash, GIT_MAX_RAWSZ);
+ memcpy(log->value.update.old_hash, tx_update->current_oid.hash, GIT_MAX_RAWSZ);
+ log->value.update.message =
+ xstrndup(u->msg, arg->refs->write_options.block_size / 2);
+ }
}
if (u->flags & REF_LOG_ONLY)
continue;
- if (u->flags & REF_HAVE_NEW && is_null_oid(&u->new_oid)) {
+ if (u->new_target) {
+ struct reftable_ref_record ref = {
+ .refname = (char *)u->refname,
+ .value_type = REFTABLE_REF_SYMREF,
+ .value.symref = (char *)u->new_target,
+ .update_index = ts,
+ };
+
+ ret = reftable_writer_add_ref(writer, &ref);
+ if (ret < 0)
+ goto done;
+ } else if ((u->flags & REF_HAVE_NEW) && ref_update_has_null_new_value(u)) {
struct reftable_ref_record ref = {
.refname = (char *)u->refname,
.update_index = ts,
--
2.43.GIT
^ permalink raw reply related [flat|nested] 194+ messages in thread
* Re: [PATCH v6 4/7] refs: add support for transactional symref updates
2024-05-03 12:41 ` [PATCH v6 4/7] refs: add support for transactional symref updates Karthik Nayak
@ 2024-05-05 14:09 ` Phillip Wood
2024-05-05 16:09 ` Karthik Nayak
0 siblings, 1 reply; 194+ messages in thread
From: Phillip Wood @ 2024-05-05 14:09 UTC (permalink / raw)
To: Karthik Nayak; +Cc: christian.couder, git, gitster, ps
Hi Karthik
I've left a few comments below - the most important one is about the
error messages in the reftable backend, non of the others are worth
re-rolling for on their own.
On 03/05/2024 13:41, Karthik Nayak wrote:
> From: Karthik Nayak <karthik.188@gmail.com>
>
> The reference backends currently support transactional reference
> updates. While this is exposed to users via 'git-update-ref' and its
> '--stdin' mode, it is also used internally within various commands.
>
> However, we never supported transactional updates of symrefs. Let's adds
s/we never supported/we do not support/
s/Let's/This commit/
> support for symrefs in both the 'files' and the 'reftable' backend.
s/backend/backends/
> Here, we add and use `ref_update_has_null_new_value()`, a helper
> function which is used to check if there is a new_value in a reference
> update. The new value could either be a symref target `new_target` or a
> OID `new_oid`.
>
> With this, now transactional updates (verify, create, delete, update)
s/With this, //
> can be used for:
> - regular refs
> - symbolic refs
> - conversion of regular to symbolic refs and vice versa
Excellent
> This also allows us to expose this to users via new commands in
> 'git-update-ref' in the future.
I'm slightly concerned that splitting out the update-ref changes means
we don't have any test coverage of the new code beyond the part that is
used by refs_create_symref()
> Note that a dangling symref update does not record a new reflog entry,
> which is unchanged before and after this commit.
>
> +/*
> + * Check whether the old_target values stored in update are consistent
> + * with current_target, which is the symbolic reference's current value.
> + * If everything is OK, return 0; otherwise, write an error message to
> + * err and return -1.
> + */
> +static int check_old_target(struct ref_update *update,
> + const char *current_target,
> + struct strbuf *err)
> +{
> + if (!update->old_target)
> + BUG("called without old_target set");
> +
> + if (!strcmp(update->old_target, current_target))
> + return 0;
> +
> + if (!strcmp(current_target, ""))
> + strbuf_addf(err, "cannot lock ref '%s': "
> + "reference is missing but expected %s",
> + original_update_refname(update),
> + update->old_target);
> + else
> + strbuf_addf(err, "cannot lock ref '%s': "
> + "is at %s but expected %s",
> + original_update_refname(update),
> + current_target, update->old_target);
> +
> + return -1;
> +}
> @@ -2576,9 +2623,27 @@ static int lock_ref_for_update(struct files_ref_store *refs,
> }
> }
>
> - if ((update->flags & REF_HAVE_NEW) &&
> - !(update->flags & REF_DELETING) &&
> - !(update->flags & REF_LOG_ONLY)) {
> + if (update->new_target && !(update->flags & REF_LOG_ONLY)) {
> + if (create_symref_lock(refs, lock, update->refname, update->new_target, err)) {
This line looks quite long
> --- a/refs/reftable-backend.c
> +++ b/refs/reftable-backend.c
> @@ -938,7 +940,22 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
> * individual refs. But the error messages match what the files
> * backend returns, which keeps our tests happy.
> */
> - if (u->flags & REF_HAVE_OLD && !oideq(¤t_oid, &u->old_oid)) {
> + if (u->old_target) {
> + if (strcmp(referent.buf, u->old_target)) {
> + if (!strcmp(referent.buf, ""))
> + strbuf_addf(err, "verifying symref target: '%s': "
> + "reference is missing but expected %s",
> + original_update_refname(u),
> + u->old_target);
> + else
> + strbuf_addf(err, "verifying symref target: '%s': "
> + "is at %s but expected %s",
> + original_update_refname(u),
> + referent.buf, u->old_target);
The messages in this function differ from the equivalent messages in
check_old_target() from the files backend above. This is potentially
confusing to users, creates more work for translators and makes it hard
to write tests that are independent of the backend. Can we export
check_old_target() so it can be reused here. If not we should reword
these messages to match the other messages all of which talk about not
being able to lock the ref.
> + ret = -1;
> + goto done;
> + }
> + } else if ((u->flags & REF_HAVE_OLD) && !oideq(¤t_oid, &u->old_oid)) {
> if (is_null_oid(&u->old_oid))
> strbuf_addf(err, _("cannot lock ref '%s': "
> "reference already exists"),
> @@ -1043,7 +1060,7 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data
> * - `core.logAllRefUpdates` tells us to create the reflog for
> * the given ref.
> */
> - if (u->flags & REF_HAVE_NEW && !(u->type & REF_ISSYMREF) && is_null_oid(&u->new_oid)) {
> + if ((u->flags & REF_HAVE_NEW) && !(u->type & REF_ISSYMREF) && ref_update_has_null_new_value(u)) {
The old line was already quite long and the new one is even longer -
perhaps we could break it after the second "&&"
> + if (create_reflog) {
> + ALLOC_GROW(logs, logs_nr + 1, logs_alloc);
> + log = &logs[logs_nr++];
> + memset(log, 0, sizeof(*log));
> +
> + fill_reftable_log_record(log);
> + log->update_index = ts;
> + log->refname = xstrdup(u->refname);
> + memcpy(log->value.update.new_hash, u->new_oid.hash, GIT_MAX_RAWSZ);
> + memcpy(log->value.update.old_hash, tx_update->current_oid.hash, GIT_MAX_RAWSZ);
Both these lines would benefit from being folded
Best Wishes
Phillip
^ permalink raw reply [flat|nested] 194+ messages in thread
* Re: [PATCH v6 4/7] refs: add support for transactional symref updates
2024-05-05 14:09 ` Phillip Wood
@ 2024-05-05 16:09 ` Karthik Nayak
2024-05-06 9:35 ` Phillip Wood
2024-05-06 9:54 ` Phillip Wood
0 siblings, 2 replies; 194+ messages in thread
From: Karthik Nayak @ 2024-05-05 16:09 UTC (permalink / raw)
To: phillip.wood; +Cc: christian.couder, git, gitster, ps
[-- Attachment #1: Type: text/plain, Size: 6563 bytes --]
Hello Phillip,
Phillip Wood <phillip.wood123@gmail.com> writes:
> Hi Karthik
>
> I've left a few comments below - the most important one is about the
> error messages in the reftable backend, non of the others are worth
> re-rolling for on their own.
>
> On 03/05/2024 13:41, Karthik Nayak wrote:
>> From: Karthik Nayak <karthik.188@gmail.com>
>>
>> The reference backends currently support transactional reference
>> updates. While this is exposed to users via 'git-update-ref' and its
>> '--stdin' mode, it is also used internally within various commands.
>>
>> However, we never supported transactional updates of symrefs. Let's adds
>
> s/we never supported/we do not support/
>
> s/Let's/This commit/
>
>> support for symrefs in both the 'files' and the 'reftable' backend.
>
> s/backend/backends/
>
>> Here, we add and use `ref_update_has_null_new_value()`, a helper
>> function which is used to check if there is a new_value in a reference
>> update. The new value could either be a symref target `new_target` or a
>> OID `new_oid`.
>>
>> With this, now transactional updates (verify, create, delete, update)
>
> s/With this, //
>
Thanks, will make the above changes.
>> can be used for:
>> - regular refs
>> - symbolic refs
>> - conversion of regular to symbolic refs and vice versa
>
> Excellent
>
>> This also allows us to expose this to users via new commands in
>> 'git-update-ref' in the future.
>
> I'm slightly concerned that splitting out the update-ref changes means
> we don't have any test coverage of the new code beyond the part that is
> used by refs_create_symref()
>
This is definitely true. But I also caught a bunch of edge cases this
way because the tests which indirectly use 'refs_create_symref()' are
quite intensive.
>> Note that a dangling symref update does not record a new reflog entry,
>> which is unchanged before and after this commit.
>>
>> +/*
>> + * Check whether the old_target values stored in update are consistent
>> + * with current_target, which is the symbolic reference's current value.
>> + * If everything is OK, return 0; otherwise, write an error message to
>> + * err and return -1.
>> + */
>> +static int check_old_target(struct ref_update *update,
>> + const char *current_target,
>> + struct strbuf *err)
>> +{
>> + if (!update->old_target)
>> + BUG("called without old_target set");
>> +
>> + if (!strcmp(update->old_target, current_target))
>> + return 0;
>> +
>> + if (!strcmp(current_target, ""))
>> + strbuf_addf(err, "cannot lock ref '%s': "
>> + "reference is missing but expected %s",
>> + original_update_refname(update),
>> + update->old_target);
>> + else
>> + strbuf_addf(err, "cannot lock ref '%s': "
>> + "is at %s but expected %s",
>> + original_update_refname(update),
>> + current_target, update->old_target);
>> +
>> + return -1;
>> +}
>> @@ -2576,9 +2623,27 @@ static int lock_ref_for_update(struct files_ref_store *refs,
>> }
>> }
>>
>> - if ((update->flags & REF_HAVE_NEW) &&
>> - !(update->flags & REF_DELETING) &&
>> - !(update->flags & REF_LOG_ONLY)) {
>> + if (update->new_target && !(update->flags & REF_LOG_ONLY)) {
>> + if (create_symref_lock(refs, lock, update->refname, update->new_target, err)) {
>
> This line looks quite long
>
Somehow I find it much easier to read these longer lines, unless there
is a logical operator, but I'll split the line mid way with the
arguments.
>> --- a/refs/reftable-backend.c
>> +++ b/refs/reftable-backend.c
>> @@ -938,7 +940,22 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
>> * individual refs. But the error messages match what the files
>> * backend returns, which keeps our tests happy.
>> */
>> - if (u->flags & REF_HAVE_OLD && !oideq(¤t_oid, &u->old_oid)) {
>> + if (u->old_target) {
>> + if (strcmp(referent.buf, u->old_target)) {
>> + if (!strcmp(referent.buf, ""))
>> + strbuf_addf(err, "verifying symref target: '%s': "
>> + "reference is missing but expected %s",
>> + original_update_refname(u),
>> + u->old_target);
>> + else
>> + strbuf_addf(err, "verifying symref target: '%s': "
>> + "is at %s but expected %s",
>> + original_update_refname(u),
>> + referent.buf, u->old_target);
>
> The messages in this function differ from the equivalent messages in
> check_old_target() from the files backend above. This is potentially
> confusing to users, creates more work for translators and makes it hard
> to write tests that are independent of the backend. Can we export
> check_old_target() so it can be reused here. If not we should reword
> these messages to match the other messages all of which talk about not
> being able to lock the ref.
>
This is very intentional, the way the backends work at this point are
quite different and while in the files backend, we talk about locking a
particular ref. In the reftable backend we do not lock single refs. We
lock tables. So keeping it consistent doesn't make sense here.
However, we could make the files backend similar to this one, I would be
okay doing that.
>> + ret = -1;
>> + goto done;
>> + }
>> + } else if ((u->flags & REF_HAVE_OLD) && !oideq(¤t_oid, &u->old_oid)) {
>> if (is_null_oid(&u->old_oid))
>> strbuf_addf(err, _("cannot lock ref '%s': "
>> "reference already exists"),
>> @@ -1043,7 +1060,7 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data
>> * - `core.logAllRefUpdates` tells us to create the reflog for
>> * the given ref.
>> */
>> - if (u->flags & REF_HAVE_NEW && !(u->type & REF_ISSYMREF) && is_null_oid(&u->new_oid)) {
>> + if ((u->flags & REF_HAVE_NEW) && !(u->type & REF_ISSYMREF) && ref_update_has_null_new_value(u)) {
>
> The old line was already quite long and the new one is even longer -
> perhaps we could break it after the second "&&"
>
I will break it both the && I think that is easier on the eyes.
>> + if (create_reflog) {
>> + ALLOC_GROW(logs, logs_nr + 1, logs_alloc);
>> + log = &logs[logs_nr++];
>> + memset(log, 0, sizeof(*log));
>> +
>> + fill_reftable_log_record(log);
>> + log->update_index = ts;
>> + log->refname = xstrdup(u->refname);
>> + memcpy(log->value.update.new_hash, u->new_oid.hash, GIT_MAX_RAWSZ);
>> + memcpy(log->value.update.old_hash, tx_update->current_oid.hash, GIT_MAX_RAWSZ);
>
> Both these lines would benefit from being folded
>
> Best Wishes
>
> Phillip
Thanks for the review.
Karthik
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 194+ messages in thread
* Re: [PATCH v6 4/7] refs: add support for transactional symref updates
2024-05-05 16:09 ` Karthik Nayak
@ 2024-05-06 9:35 ` Phillip Wood
2024-05-06 11:19 ` Karthik Nayak
2024-05-06 9:54 ` Phillip Wood
1 sibling, 1 reply; 194+ messages in thread
From: Phillip Wood @ 2024-05-06 9:35 UTC (permalink / raw)
To: Karthik Nayak, phillip.wood; +Cc: christian.couder, git, gitster, ps
Hi Karthik
On 05/05/2024 17:09, Karthik Nayak wrote:
> Phillip Wood <phillip.wood123@gmail.com> writes:
>> On 03/05/2024 13:41, Karthik Nayak wrote:
>>> --- a/refs/reftable-backend.c
>>> +++ b/refs/reftable-backend.c
>>> @@ -938,7 +940,22 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
>>> * individual refs. But the error messages match what the files
>>> * backend returns, which keeps our tests happy.
>>> */
>>> - if (u->flags & REF_HAVE_OLD && !oideq(¤t_oid, &u->old_oid)) {
>>> + if (u->old_target) {
>>> + if (strcmp(referent.buf, u->old_target)) {
>>> + if (!strcmp(referent.buf, ""))
>>> + strbuf_addf(err, "verifying symref target: '%s': "
>>> + "reference is missing but expected %s",
>>> + original_update_refname(u),
>>> + u->old_target);
>>> + else
>>> + strbuf_addf(err, "verifying symref target: '%s': "
>>> + "is at %s but expected %s",
>>> + original_update_refname(u),
>>> + referent.buf, u->old_target);
>>
>> The messages in this function differ from the equivalent messages in
>> check_old_target() from the files backend above. This is potentially
>> confusing to users, creates more work for translators and makes it hard
>> to write tests that are independent of the backend. Can we export
>> check_old_target() so it can be reused here. If not we should reword
>> these messages to match the other messages all of which talk about not
>> being able to lock the ref.
>>
>
> This is very intentional, the way the backends work at this point are
> quite different and while in the files backend, we talk about locking a
> particular ref.
I agree that the existing messages could be improved - these messages
are returned when checking the old value of the ref so talking about
being unable to lock the ref is not helpful as the important information
is that the old value does not match the expected value. However that is
not dependent on the backend or on whether the expected value is a
symref or an oid so it feels a bit random to make these two messages
different.
> In the reftable backend we do not lock single refs. We
> lock tables. So keeping it consistent doesn't make sense here.
Where an update is prevented by another process holding a lock I think
that the granularity of the lock that prevents the ref from being
updated is not particularly relevant as far as the user is concerned. As
far as I can see the existing error messages in the reftable backend try
to be consistent with the messages in the files backend.
> However, we could make the files backend similar to this one, I would be
> okay doing that.
I would be very happy to see the messages improved for both backends
when the old value does not match the expected (oid or symref) value. I
do think we should have consistent error messages in this case that are
essentially independent of the backend and type of the expected value.
Best Wishes
Phillip
^ permalink raw reply [flat|nested] 194+ messages in thread
* Re: [PATCH v6 4/7] refs: add support for transactional symref updates
2024-05-06 9:35 ` Phillip Wood
@ 2024-05-06 11:19 ` Karthik Nayak
2024-05-06 13:19 ` Phillip Wood
0 siblings, 1 reply; 194+ messages in thread
From: Karthik Nayak @ 2024-05-06 11:19 UTC (permalink / raw)
To: Phillip Wood, phillip.wood; +Cc: christian.couder, git, gitster, ps
[-- Attachment #1: Type: text/plain, Size: 3771 bytes --]
Phillip Wood <phillip.wood123@gmail.com> writes:
> Hi Karthik
>
> On 05/05/2024 17:09, Karthik Nayak wrote:
>> Phillip Wood <phillip.wood123@gmail.com> writes:
>>> On 03/05/2024 13:41, Karthik Nayak wrote:
>>>> --- a/refs/reftable-backend.c
>>>> +++ b/refs/reftable-backend.c
>>>> @@ -938,7 +940,22 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
>>>> * individual refs. But the error messages match what the files
>>>> * backend returns, which keeps our tests happy.
>>>> */
>>>> - if (u->flags & REF_HAVE_OLD && !oideq(¤t_oid, &u->old_oid)) {
>>>> + if (u->old_target) {
>>>> + if (strcmp(referent.buf, u->old_target)) {
>>>> + if (!strcmp(referent.buf, ""))
>>>> + strbuf_addf(err, "verifying symref target: '%s': "
>>>> + "reference is missing but expected %s",
>>>> + original_update_refname(u),
>>>> + u->old_target);
>>>> + else
>>>> + strbuf_addf(err, "verifying symref target: '%s': "
>>>> + "is at %s but expected %s",
>>>> + original_update_refname(u),
>>>> + referent.buf, u->old_target);
>>>
>>> The messages in this function differ from the equivalent messages in
>>> check_old_target() from the files backend above. This is potentially
>>> confusing to users, creates more work for translators and makes it hard
>>> to write tests that are independent of the backend. Can we export
>>> check_old_target() so it can be reused here. If not we should reword
>>> these messages to match the other messages all of which talk about not
>>> being able to lock the ref.
>>>
>>
>> This is very intentional, the way the backends work at this point are
>> quite different and while in the files backend, we talk about locking a
>> particular ref.
>
> I agree that the existing messages could be improved - these messages
> are returned when checking the old value of the ref so talking about
> being unable to lock the ref is not helpful as the important information
> is that the old value does not match the expected value. However that is
> not dependent on the backend or on whether the expected value is a
> symref or an oid so it feels a bit random to make these two messages
> different.
>
>> In the reftable backend we do not lock single refs. We
>> lock tables. So keeping it consistent doesn't make sense here.
>
> Where an update is prevented by another process holding a lock I think
> that the granularity of the lock that prevents the ref from being
> updated is not particularly relevant as far as the user is concerned. As
> far as I can see the existing error messages in the reftable backend try
> to be consistent with the messages in the files backend.
>
Well, I agree. But we do have to note, that the files backend always had
`check_old_oid` which has messages along the lines of 'cannot lock ref
...' and now the new `check_old_target` will not be consistent with
those messages. Which is OK, since, like you mentioned, these messages
could be improved.
>> However, we could make the files backend similar to this one, I would be
>> okay doing that.
>
> I would be very happy to see the messages improved for both backends
> when the old value does not match the expected (oid or symref) value. I
> do think we should have consistent error messages in this case that are
> essentially independent of the backend and type of the expected value.
>
> Best Wishes
>
> Phillip
Overall, I think we're reaching the same consensus, i.e. to export this
functionality to a generic function and shared between the both
backends. I will make this change. I think the message used in the
reftable backend along the lines of 'verifying symref target ...' is
verbose yet generic enough, so I'll keep those messages.
Thanks
Karthik
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 194+ messages in thread
* Re: [PATCH v6 4/7] refs: add support for transactional symref updates
2024-05-06 11:19 ` Karthik Nayak
@ 2024-05-06 13:19 ` Phillip Wood
0 siblings, 0 replies; 194+ messages in thread
From: Phillip Wood @ 2024-05-06 13:19 UTC (permalink / raw)
To: Karthik Nayak, phillip.wood; +Cc: christian.couder, git, gitster, ps
Hi Karthik
On 06/05/2024 12:19, Karthik Nayak wrote:
>
> Overall, I think we're reaching the same consensus, i.e. to export this
> functionality to a generic function and shared between the both
> backends. I will make this change. I think the message used in the
> reftable backend along the lines of 'verifying symref target ...' is
> verbose yet generic enough, so I'll keep those messages.
That sounds good to me
Thanks
Phillip
^ permalink raw reply [flat|nested] 194+ messages in thread
* Re: [PATCH v6 4/7] refs: add support for transactional symref updates
2024-05-05 16:09 ` Karthik Nayak
2024-05-06 9:35 ` Phillip Wood
@ 2024-05-06 9:54 ` Phillip Wood
2024-05-06 11:22 ` Karthik Nayak
1 sibling, 1 reply; 194+ messages in thread
From: Phillip Wood @ 2024-05-06 9:54 UTC (permalink / raw)
To: Karthik Nayak, phillip.wood; +Cc: christian.couder, git, gitster, ps
Hi Karthik
On 05/05/2024 17:09, Karthik Nayak wrote:
> Phillip Wood <phillip.wood123@gmail.com> writes:
>> I'm slightly concerned that splitting out the update-ref changes means
>> we don't have any test coverage of the new code beyond the part that is
>> used by refs_create_symref()
>>
>
> This is definitely true. But I also caught a bunch of edge cases this
> way because the tests which indirectly use 'refs_create_symref()' are
> quite intensive.
I forgot to say in my last mail that this is good to know. So it sounds
like the only new code that isn't being exercised by the tests is the
check for the old value?
Best Wishes
Phillip
^ permalink raw reply [flat|nested] 194+ messages in thread
* Re: [PATCH v6 4/7] refs: add support for transactional symref updates
2024-05-06 9:54 ` Phillip Wood
@ 2024-05-06 11:22 ` Karthik Nayak
2024-05-06 13:17 ` Phillip Wood
0 siblings, 1 reply; 194+ messages in thread
From: Karthik Nayak @ 2024-05-06 11:22 UTC (permalink / raw)
To: Phillip Wood, phillip.wood; +Cc: christian.couder, git, gitster, ps
[-- Attachment #1: Type: text/plain, Size: 1076 bytes --]
Hello,
Phillip Wood <phillip.wood123@gmail.com> writes:
> Hi Karthik
>
> On 05/05/2024 17:09, Karthik Nayak wrote:
>> Phillip Wood <phillip.wood123@gmail.com> writes:
>>> I'm slightly concerned that splitting out the update-ref changes means
>>> we don't have any test coverage of the new code beyond the part that is
>>> used by refs_create_symref()
>>>
>>
>> This is definitely true. But I also caught a bunch of edge cases this
>> way because the tests which indirectly use 'refs_create_symref()' are
>> quite intensive.
>
> I forgot to say in my last mail that this is good to know. So it sounds
> like the only new code that isn't being exercised by the tests is the
> check for the old value?
>
> Best Wishes
>
> Phillip
That's correct. I think testing that _currently_ would require us to
probably expose and test via the unit testing library. I plan to follow
this patch series soon with the symref-* ones. While that's not the best
argument for not having full test coverage, I hope it is an OK state to
be since that path has no users as of this point.
Karthik
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 194+ messages in thread
* Re: [PATCH v6 4/7] refs: add support for transactional symref updates
2024-05-06 11:22 ` Karthik Nayak
@ 2024-05-06 13:17 ` Phillip Wood
0 siblings, 0 replies; 194+ messages in thread
From: Phillip Wood @ 2024-05-06 13:17 UTC (permalink / raw)
To: Karthik Nayak, phillip.wood; +Cc: christian.couder, git, gitster, ps
Hi Karthik
On 06/05/2024 12:22, Karthik Nayak wrote:
>
> That's correct. I think testing that _currently_ would require us to
> probably expose and test via the unit testing library. I plan to follow
> this patch series soon with the symref-* ones. While that's not the best
> argument for not having full test coverage, I hope it is an OK state to
> be since that path has no users as of this point.
That seems reasonable - we have good test coverage for the code we're
currently using and you're clearly committed to adding tests for the
rest when you extend "git update-ref" to accommodate symrefs.
Thanks
Phillip
^ permalink raw reply [flat|nested] 194+ messages in thread
* [PATCH v6 5/7] refs: use transaction in `refs_create_symref()`
2024-05-03 12:41 ` [PATCH v6 " Karthik Nayak
` (3 preceding siblings ...)
2024-05-03 12:41 ` [PATCH v6 4/7] refs: add support for transactional symref updates Karthik Nayak
@ 2024-05-03 12:41 ` Karthik Nayak
2024-05-03 12:41 ` [PATCH v6 6/7] refs: rename `refs_create_symref()` to `refs_update_symref()` Karthik Nayak
` (4 subsequent siblings)
9 siblings, 0 replies; 194+ messages in thread
From: Karthik Nayak @ 2024-05-03 12:41 UTC (permalink / raw)
To: karthik.188; +Cc: christian.couder, git, gitster, ps
From: Karthik Nayak <karthik.188@gmail.com>
The `refs_create_symref()` function updates a symref to a given new
target. To do this, it uses a ref-backend specific function
`create_symref()`.
In the previous commits, we introduced symref support in transactions.
This means we can now use transactions to perform symref updates and
don't have to resort to `create_symref()`. Doing this allows us to
remove and cleanup `create_symref()`, which we will do in the following
commit.
Modify the expected error message for a test in
't/t0610-reftable-basics.sh', since the error is now thrown from
'refs.c'. This is because in transactional updates, F/D conflicts are
caught before we're in the reference backend.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
refs.c | 24 +++++++++++++++++-------
t/t0610-reftable-basics.sh | 2 +-
t/t1416-ref-transaction-hooks.sh | 23 +++++++++++++++++++++++
3 files changed, 41 insertions(+), 8 deletions(-)
diff --git a/refs.c b/refs.c
index acb153b6f8..195861b09b 100644
--- a/refs.c
+++ b/refs.c
@@ -2289,14 +2289,24 @@ int refs_create_symref(struct ref_store *refs,
const char *refs_heads_master,
const char *logmsg)
{
- char *msg;
- int retval;
+ struct ref_transaction *transaction;
+ struct strbuf err = STRBUF_INIT;
+ int ret = 0;
- msg = normalize_reflog_message(logmsg);
- retval = refs->be->create_symref(refs, ref_target, refs_heads_master,
- msg);
- free(msg);
- return retval;
+ transaction = ref_store_transaction_begin(refs, &err);
+ if (!transaction ||
+ ref_transaction_update(transaction, ref_target, NULL, NULL,
+ refs_heads_master, NULL, REF_NO_DEREF,
+ logmsg, &err) ||
+ ref_transaction_commit(transaction, &err)) {
+ ret = error("%s", err.buf);
+ }
+
+ strbuf_release(&err);
+ if (transaction)
+ ref_transaction_free(transaction);
+
+ return ret;
}
int create_symref(const char *ref_target, const char *refs_heads_master,
diff --git a/t/t0610-reftable-basics.sh b/t/t0610-reftable-basics.sh
index 178791e086..9e8d22bcbd 100755
--- a/t/t0610-reftable-basics.sh
+++ b/t/t0610-reftable-basics.sh
@@ -286,7 +286,7 @@ test_expect_success 'ref transaction: creating symbolic ref fails with F/D confl
git init repo &&
test_commit -C repo A &&
cat >expect <<-EOF &&
- error: unable to write symref for refs/heads: file/directory conflict
+ error: ${SQ}refs/heads/main${SQ} exists; cannot create ${SQ}refs/heads${SQ}
EOF
test_must_fail git -C repo symbolic-ref refs/heads refs/heads/foo 2>err &&
test_cmp expect err
diff --git a/t/t1416-ref-transaction-hooks.sh b/t/t1416-ref-transaction-hooks.sh
index 2092488090..067fd57290 100755
--- a/t/t1416-ref-transaction-hooks.sh
+++ b/t/t1416-ref-transaction-hooks.sh
@@ -134,4 +134,27 @@ test_expect_success 'interleaving hook calls succeed' '
test_cmp expect target-repo.git/actual
'
+test_expect_success 'hook captures git-symbolic-ref updates' '
+ test_when_finished "rm actual" &&
+
+ test_hook reference-transaction <<-\EOF &&
+ echo "$*" >>actual
+ while read -r line
+ do
+ printf "%s\n" "$line"
+ done >>actual
+ EOF
+
+ git symbolic-ref refs/heads/symref refs/heads/main &&
+
+ cat >expect <<-EOF &&
+ prepared
+ $ZERO_OID ref:refs/heads/main refs/heads/symref
+ committed
+ $ZERO_OID ref:refs/heads/main refs/heads/symref
+ EOF
+
+ test_cmp expect actual
+'
+
test_done
--
2.43.GIT
^ permalink raw reply related [flat|nested] 194+ messages in thread
* [PATCH v6 6/7] refs: rename `refs_create_symref()` to `refs_update_symref()`
2024-05-03 12:41 ` [PATCH v6 " Karthik Nayak
` (4 preceding siblings ...)
2024-05-03 12:41 ` [PATCH v6 5/7] refs: use transaction in `refs_create_symref()` Karthik Nayak
@ 2024-05-03 12:41 ` Karthik Nayak
2024-05-03 12:41 ` [PATCH v6 7/7] refs: remove `create_symref` and associated dead code Karthik Nayak
` (3 subsequent siblings)
9 siblings, 0 replies; 194+ messages in thread
From: Karthik Nayak @ 2024-05-03 12:41 UTC (permalink / raw)
To: karthik.188; +Cc: christian.couder, git, gitster, ps
From: Karthik Nayak <karthik.188@gmail.com>
The `refs_create_symref()` function is used to update/create a symref.
But it doesn't check the old target of the symref, if existing. It force
updates the symref. In this regard, the name `refs_create_symref()` is a
bit misleading. So let's rename it to `refs_update_symref()`. This is
akin to how 'git-update-ref(1)' also allows us to create apart from
update.
While we're here, rename the arguments in the function to clarify what
they actually signify and reduce confusion.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
builtin/branch.c | 2 +-
builtin/worktree.c | 2 +-
refs.c | 12 +++++-------
refs.h | 2 +-
t/helper/test-ref-store.c | 2 +-
5 files changed, 9 insertions(+), 11 deletions(-)
diff --git a/builtin/branch.c b/builtin/branch.c
index dd3e3a7dc0..4491f7a20c 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -555,7 +555,7 @@ static int replace_each_worktree_head_symref(struct worktree **worktrees,
continue;
refs = get_worktree_ref_store(worktrees[i]);
- if (refs_create_symref(refs, "HEAD", newref, logmsg))
+ if (refs_update_symref(refs, "HEAD", newref, logmsg))
ret = error(_("HEAD of working tree %s is not updated"),
worktrees[i]->path);
}
diff --git a/builtin/worktree.c b/builtin/worktree.c
index 7c6c72536b..480202c517 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -517,7 +517,7 @@ static int add_worktree(const char *path, const char *refname,
ret = refs_update_ref(wt_refs, NULL, "HEAD", &commit->object.oid,
NULL, 0, UPDATE_REFS_MSG_ON_ERR);
else
- ret = refs_create_symref(wt_refs, "HEAD", symref.buf, NULL);
+ ret = refs_update_symref(wt_refs, "HEAD", symref.buf, NULL);
if (ret)
goto done;
diff --git a/refs.c b/refs.c
index 195861b09b..3645b805c1 100644
--- a/refs.c
+++ b/refs.c
@@ -2284,10 +2284,8 @@ int peel_iterated_oid(const struct object_id *base, struct object_id *peeled)
return peel_object(base, peeled) ? -1 : 0;
}
-int refs_create_symref(struct ref_store *refs,
- const char *ref_target,
- const char *refs_heads_master,
- const char *logmsg)
+int refs_update_symref(struct ref_store *refs, const char *ref,
+ const char *target, const char *logmsg)
{
struct ref_transaction *transaction;
struct strbuf err = STRBUF_INIT;
@@ -2295,8 +2293,8 @@ int refs_create_symref(struct ref_store *refs,
transaction = ref_store_transaction_begin(refs, &err);
if (!transaction ||
- ref_transaction_update(transaction, ref_target, NULL, NULL,
- refs_heads_master, NULL, REF_NO_DEREF,
+ ref_transaction_update(transaction, ref, NULL, NULL,
+ target, NULL, REF_NO_DEREF,
logmsg, &err) ||
ref_transaction_commit(transaction, &err)) {
ret = error("%s", err.buf);
@@ -2312,7 +2310,7 @@ int refs_create_symref(struct ref_store *refs,
int create_symref(const char *ref_target, const char *refs_heads_master,
const char *logmsg)
{
- return refs_create_symref(get_main_ref_store(the_repository), ref_target,
+ return refs_update_symref(get_main_ref_store(the_repository), ref_target,
refs_heads_master, logmsg);
}
diff --git a/refs.h b/refs.h
index c7851bf587..71cc1c58e0 100644
--- a/refs.h
+++ b/refs.h
@@ -606,7 +606,7 @@ int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,
int copy_existing_ref(const char *oldref, const char *newref,
const char *logmsg);
-int refs_create_symref(struct ref_store *refs, const char *refname,
+int refs_update_symref(struct ref_store *refs, const char *refname,
const char *target, const char *logmsg);
int create_symref(const char *refname, const char *target, const char *logmsg);
diff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c
index 82bbf6e2e6..4651e4ced7 100644
--- a/t/helper/test-ref-store.c
+++ b/t/helper/test-ref-store.c
@@ -118,7 +118,7 @@ static int cmd_create_symref(struct ref_store *refs, const char **argv)
const char *target = notnull(*argv++, "target");
const char *logmsg = *argv++;
- return refs_create_symref(refs, refname, target, logmsg);
+ return refs_update_symref(refs, refname, target, logmsg);
}
static struct flag_definition transaction_flags[] = {
--
2.43.GIT
^ permalink raw reply related [flat|nested] 194+ messages in thread
* [PATCH v6 7/7] refs: remove `create_symref` and associated dead code
2024-05-03 12:41 ` [PATCH v6 " Karthik Nayak
` (5 preceding siblings ...)
2024-05-03 12:41 ` [PATCH v6 6/7] refs: rename `refs_create_symref()` to `refs_update_symref()` Karthik Nayak
@ 2024-05-03 12:41 ` Karthik Nayak
2024-05-03 23:09 ` Junio C Hamano
2024-05-03 16:45 ` [PATCH v6 0/7] refs: add support for transactional symref updates Junio C Hamano
` (2 subsequent siblings)
9 siblings, 1 reply; 194+ messages in thread
From: Karthik Nayak @ 2024-05-03 12:41 UTC (permalink / raw)
To: karthik.188; +Cc: christian.couder, git, gitster, ps
From: Karthik Nayak <karthik.188@gmail.com>
In the previous commits, we converted `refs_create_symref()` to utilize
transactions to perform symref updates. Earlier `refs_create_symref()`
used `create_symref()` to do the same.
We can now remove `create_symref()` and any code associated with it
which is no longer used. We remove `create_symref()` code from all the
reference backends and also remove it entirely from the `ref_storage_be`
struct.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
refs/debug.c | 13 -------
refs/files-backend.c | 67 --------------------------------
refs/packed-backend.c | 1 -
refs/refs-internal.h | 5 ---
refs/reftable-backend.c | 86 -----------------------------------------
5 files changed, 172 deletions(-)
diff --git a/refs/debug.c b/refs/debug.c
index c7531b17f0..8be316bb67 100644
--- a/refs/debug.c
+++ b/refs/debug.c
@@ -131,18 +131,6 @@ static int debug_pack_refs(struct ref_store *ref_store, struct pack_refs_opts *o
return res;
}
-static int debug_create_symref(struct ref_store *ref_store,
- const char *ref_name, const char *target,
- const char *logmsg)
-{
- struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store;
- int res = drefs->refs->be->create_symref(drefs->refs, ref_name, target,
- logmsg);
- trace_printf_key(&trace_refs, "create_symref: %s -> %s \"%s\": %d\n", ref_name,
- target, logmsg, res);
- return res;
-}
-
static int debug_rename_ref(struct ref_store *ref_store, const char *oldref,
const char *newref, const char *logmsg)
{
@@ -441,7 +429,6 @@ struct ref_storage_be refs_be_debug = {
.initial_transaction_commit = debug_initial_transaction_commit,
.pack_refs = debug_pack_refs,
- .create_symref = debug_create_symref,
.rename_ref = debug_rename_ref,
.copy_ref = debug_copy_ref,
diff --git a/refs/files-backend.c b/refs/files-backend.c
index e1f0ca74c0..3ce260d07d 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -1903,23 +1903,6 @@ static int create_ref_symlink(struct ref_lock *lock, const char *target)
return ret;
}
-static void update_symref_reflog(struct files_ref_store *refs,
- struct ref_lock *lock, const char *refname,
- const char *target, const char *logmsg)
-{
- struct strbuf err = STRBUF_INIT;
- struct object_id new_oid;
-
- if (logmsg &&
- refs_resolve_ref_unsafe(&refs->base, target,
- RESOLVE_REF_READING, &new_oid, NULL) &&
- files_log_ref_write(refs, refname, &lock->old_oid,
- &new_oid, logmsg, 0, &err)) {
- error("%s", err.buf);
- strbuf_release(&err);
- }
-}
-
static int create_symref_lock(struct files_ref_store *refs,
struct ref_lock *lock, const char *refname,
const char *target, struct strbuf *err)
@@ -1939,55 +1922,6 @@ static int create_symref_lock(struct files_ref_store *refs,
return 0;
}
-static int create_and_commit_symref(struct files_ref_store *refs,
- struct ref_lock *lock, const char *refname,
- const char *target, const char *logmsg)
-{
- struct strbuf err = STRBUF_INIT;
- int ret;
-
- if (prefer_symlink_refs && !create_ref_symlink(lock, target)) {
- update_symref_reflog(refs, lock, refname, target, logmsg);
- return 0;
- }
-
- ret = create_symref_lock(refs, lock, refname, target, &err);
- if (!ret) {
- update_symref_reflog(refs, lock, refname, target, logmsg);
-
- if (commit_ref(lock) < 0)
- return error("unable to write symref for %s: %s", refname,
- strerror(errno));
- } else {
- return error("%s", err.buf);
- }
-
- return ret;
-}
-
-static int files_create_symref(struct ref_store *ref_store,
- const char *refname, const char *target,
- const char *logmsg)
-{
- struct files_ref_store *refs =
- files_downcast(ref_store, REF_STORE_WRITE, "create_symref");
- struct strbuf err = STRBUF_INIT;
- struct ref_lock *lock;
- int ret;
-
- lock = lock_ref_oid_basic(refs, refname, &err);
- if (!lock) {
- error("%s", err.buf);
- strbuf_release(&err);
- return -1;
- }
-
- ret = create_and_commit_symref(refs, lock, refname, target, logmsg);
-
- unlock_ref(lock);
- return ret;
-}
-
static int files_reflog_exists(struct ref_store *ref_store,
const char *refname)
{
@@ -3413,7 +3347,6 @@ struct ref_storage_be refs_be_files = {
.initial_transaction_commit = files_initial_transaction_commit,
.pack_refs = files_pack_refs,
- .create_symref = files_create_symref,
.rename_ref = files_rename_ref,
.copy_ref = files_copy_ref,
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index 4e826c05ff..a937e7dbfc 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -1714,7 +1714,6 @@ struct ref_storage_be refs_be_packed = {
.initial_transaction_commit = packed_initial_transaction_commit,
.pack_refs = packed_pack_refs,
- .create_symref = NULL,
.rename_ref = NULL,
.copy_ref = NULL,
diff --git a/refs/refs-internal.h b/refs/refs-internal.h
index 03eda70ea4..0c620d4bce 100644
--- a/refs/refs-internal.h
+++ b/refs/refs-internal.h
@@ -566,10 +566,6 @@ typedef int ref_transaction_commit_fn(struct ref_store *refs,
typedef int pack_refs_fn(struct ref_store *ref_store,
struct pack_refs_opts *opts);
-typedef int create_symref_fn(struct ref_store *ref_store,
- const char *ref_target,
- const char *refs_heads_master,
- const char *logmsg);
typedef int rename_ref_fn(struct ref_store *ref_store,
const char *oldref, const char *newref,
const char *logmsg);
@@ -690,7 +686,6 @@ struct ref_storage_be {
ref_transaction_commit_fn *initial_transaction_commit;
pack_refs_fn *pack_refs;
- create_symref_fn *create_symref;
rename_ref_fn *rename_ref;
copy_ref_fn *copy_ref;
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index 54c4d8b771..999b5bca21 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -1275,91 +1275,6 @@ struct write_create_symref_arg {
const char *logmsg;
};
-static int write_create_symref_table(struct reftable_writer *writer, void *cb_data)
-{
- struct write_create_symref_arg *create = cb_data;
- uint64_t ts = reftable_stack_next_update_index(create->stack);
- struct reftable_ref_record ref = {
- .refname = (char *)create->refname,
- .value_type = REFTABLE_REF_SYMREF,
- .value.symref = (char *)create->target,
- .update_index = ts,
- };
- struct reftable_log_record log = {0};
- struct object_id new_oid;
- struct object_id old_oid;
- int ret;
-
- reftable_writer_set_limits(writer, ts, ts);
-
- ret = reftable_writer_add_ref(writer, &ref);
- if (ret)
- return ret;
-
- /*
- * Note that it is important to try and resolve the reference before we
- * write the log entry. This is because `should_write_log()` will munge
- * `core.logAllRefUpdates`, which is undesirable when we create a new
- * repository because it would be written into the config. As HEAD will
- * not resolve for new repositories this ordering will ensure that this
- * never happens.
- */
- if (!create->logmsg ||
- !refs_resolve_ref_unsafe(&create->refs->base, create->target,
- RESOLVE_REF_READING, &new_oid, NULL) ||
- !should_write_log(&create->refs->base, create->refname))
- return 0;
-
- fill_reftable_log_record(&log);
- log.refname = xstrdup(create->refname);
- log.update_index = ts;
- log.value.update.message = xstrndup(create->logmsg,
- create->refs->write_options.block_size / 2);
- memcpy(log.value.update.new_hash, new_oid.hash, GIT_MAX_RAWSZ);
- if (refs_resolve_ref_unsafe(&create->refs->base, create->refname,
- RESOLVE_REF_READING, &old_oid, NULL))
- memcpy(log.value.update.old_hash, old_oid.hash, GIT_MAX_RAWSZ);
-
- ret = reftable_writer_add_log(writer, &log);
- reftable_log_record_release(&log);
- return ret;
-}
-
-static int reftable_be_create_symref(struct ref_store *ref_store,
- const char *refname,
- const char *target,
- const char *logmsg)
-{
- struct reftable_ref_store *refs =
- reftable_be_downcast(ref_store, REF_STORE_WRITE, "create_symref");
- struct reftable_stack *stack = stack_for(refs, refname, &refname);
- struct write_create_symref_arg arg = {
- .refs = refs,
- .stack = stack,
- .refname = refname,
- .target = target,
- .logmsg = logmsg,
- };
- int ret;
-
- ret = refs->err;
- if (ret < 0)
- goto done;
-
- ret = reftable_stack_reload(stack);
- if (ret)
- goto done;
-
- ret = reftable_stack_add(stack, &write_create_symref_table, &arg);
-
-done:
- assert(ret != REFTABLE_API_ERROR);
- if (ret)
- error("unable to write symref for %s: %s", refname,
- reftable_error_str(ret));
- return ret;
-}
-
struct write_copy_arg {
struct reftable_ref_store *refs;
struct reftable_stack *stack;
@@ -2267,7 +2182,6 @@ struct ref_storage_be refs_be_reftable = {
.initial_transaction_commit = reftable_be_initial_transaction_commit,
.pack_refs = reftable_be_pack_refs,
- .create_symref = reftable_be_create_symref,
.rename_ref = reftable_be_rename_ref,
.copy_ref = reftable_be_copy_ref,
--
2.43.GIT
^ permalink raw reply related [flat|nested] 194+ messages in thread
* Re: [PATCH v6 7/7] refs: remove `create_symref` and associated dead code
2024-05-03 12:41 ` [PATCH v6 7/7] refs: remove `create_symref` and associated dead code Karthik Nayak
@ 2024-05-03 23:09 ` Junio C Hamano
2024-05-04 9:30 ` Karthik Nayak
0 siblings, 1 reply; 194+ messages in thread
From: Junio C Hamano @ 2024-05-03 23:09 UTC (permalink / raw)
To: Karthik Nayak; +Cc: christian.couder, git, ps
Karthik Nayak <karthik.188@gmail.com> writes:
> From: Karthik Nayak <karthik.188@gmail.com>
>
> In the previous commits, we converted `refs_create_symref()` to utilize
> transactions to perform symref updates. Earlier `refs_create_symref()`
> used `create_symref()` to do the same.
>
> We can now remove `create_symref()` and any code associated with it
> which is no longer used. We remove `create_symref()` code from all the
> reference backends and also remove it entirely from the `ref_storage_be`
> struct.
>
> Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
> ---
This has serious semantic conflicts with in-flight topics. I think
I resolved them all correctly while merging it in 'seen', but please
double check the result after I push it out.
This comment equally applies to the "force all callers to use
get_main_ref_store() on the_repository and remove functions that
implicitly used the main ref store of the_repository" topic by
Patrick, but we really should devise a bit more smoother way to cope
with out of tree and in-flight topics. For example, as the new
refs_update_symref() function works exactly like the existing
refs_create_symref() function, after renaming all the in-base (i.e.,
in-tree at the point this topic forks from) users to call the new
function, instead of just removing the original one, it would have
been nice to guide authors of other in-flight topics by (1) causing
build failure and at the same time (2) telling them what they need
to do to adjust to the new world order. This patch does only (1)
but does a poor job for (2). We may want to establish a better
convention than just outright removing and breaking others' topics.
Thanks.
^ permalink raw reply [flat|nested] 194+ messages in thread
* Re: [PATCH v6 7/7] refs: remove `create_symref` and associated dead code
2024-05-03 23:09 ` Junio C Hamano
@ 2024-05-04 9:30 ` Karthik Nayak
0 siblings, 0 replies; 194+ messages in thread
From: Karthik Nayak @ 2024-05-04 9:30 UTC (permalink / raw)
To: Junio C Hamano; +Cc: christian.couder, git, ps
[-- Attachment #1: Type: text/plain, Size: 1944 bytes --]
Junio C Hamano <gitster@pobox.com> writes:
> Karthik Nayak <karthik.188@gmail.com> writes:
>
>> From: Karthik Nayak <karthik.188@gmail.com>
>>
>> In the previous commits, we converted `refs_create_symref()` to utilize
>> transactions to perform symref updates. Earlier `refs_create_symref()`
>> used `create_symref()` to do the same.
>>
>> We can now remove `create_symref()` and any code associated with it
>> which is no longer used. We remove `create_symref()` code from all the
>> reference backends and also remove it entirely from the `ref_storage_be`
>> struct.
>>
>> Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
>> ---
>
> This has serious semantic conflicts with in-flight topics. I think
> I resolved them all correctly while merging it in 'seen', but please
> double check the result after I push it out.
>
I had a look at the commit pushed to 'seen', looked good to me. Thanks.
> This comment equally applies to the "force all callers to use
> get_main_ref_store() on the_repository and remove functions that
> implicitly used the main ref store of the_repository" topic by
> Patrick, but we really should devise a bit more smoother way to cope
> with out of tree and in-flight topics. For example, as the new
> refs_update_symref() function works exactly like the existing
> refs_create_symref() function, after renaming all the in-base (i.e.,
> in-tree at the point this topic forks from) users to call the new
> function, instead of just removing the original one, it would have
> been nice to guide authors of other in-flight topics by (1) causing
> build failure and at the same time (2) telling them what they need
> to do to adjust to the new world order. This patch does only (1)
> but does a poor job for (2). We may want to establish a better
> convention than just outright removing and breaking others' topics.
>
> Thanks.
Just so I can do better next time, what is your suggestion for doing (2)
better?
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 194+ messages in thread
* Re: [PATCH v6 0/7] refs: add support for transactional symref updates
2024-05-03 12:41 ` [PATCH v6 " Karthik Nayak
` (6 preceding siblings ...)
2024-05-03 12:41 ` [PATCH v6 7/7] refs: remove `create_symref` and associated dead code Karthik Nayak
@ 2024-05-03 16:45 ` Junio C Hamano
2024-05-06 7:36 ` Patrick Steinhardt
2024-05-07 6:00 ` [PATCH v7 0/8] " Karthik Nayak
9 siblings, 0 replies; 194+ messages in thread
From: Junio C Hamano @ 2024-05-03 16:45 UTC (permalink / raw)
To: Karthik Nayak; +Cc: christian.couder, git, ps
Karthik Nayak <karthik.188@gmail.com> writes:
> Previous versions:
> V1: https://lore.kernel.org/git/20240330224623.579457-1-knayak@gitlab.com/
> V2: https://lore.kernel.org/git/20240412095908.1134387-1-knayak@gitlab.com/
> V3: https://lore.kernel.org/git/20240423212818.574123-1-knayak@gitlab.com/
> V4: https://lore.kernel.org/r/20240426152449.228860-1-knayak@gitlab.com
> V5: https://lore.kernel.org/r/20240501202229.2695774-1-knayak@gitlab.com
>
> Changes over v5 are:
> - More user friendly error messages.
> - `create_symref_lock` now writes to an err buf, instead of directly to stderr.
> - Refactor code to make it easier to read around logical operations.
All changes I saw in the range-diff and diff output with v5 looked
sensible improvements.
Will wait for comments and then hopefully we can mark it for 'next'
sometime next week, if nothing happens.
Thanks.
^ permalink raw reply [flat|nested] 194+ messages in thread
* Re: [PATCH v6 0/7] refs: add support for transactional symref updates
2024-05-03 12:41 ` [PATCH v6 " Karthik Nayak
` (7 preceding siblings ...)
2024-05-03 16:45 ` [PATCH v6 0/7] refs: add support for transactional symref updates Junio C Hamano
@ 2024-05-06 7:36 ` Patrick Steinhardt
2024-05-07 6:00 ` [PATCH v7 0/8] " Karthik Nayak
9 siblings, 0 replies; 194+ messages in thread
From: Patrick Steinhardt @ 2024-05-06 7:36 UTC (permalink / raw)
To: Karthik Nayak; +Cc: christian.couder, git, gitster
[-- Attachment #1: Type: text/plain, Size: 2265 bytes --]
On Fri, May 03, 2024 at 02:41:08PM +0200, Karthik Nayak wrote:
> From: Karthik Nayak <karthik.188@gmail.com>
>
> The patch series takes over from the existing patch series, wherein we
> introduced symref-* commands to git-update-ref. Since there was a ton of
> discussions on the UX of the patch series and its application, I thought it
> would be best to shorten the series and split it into multiple smaller series.
>
> This series adds transactional support for symrefs in the reference db. Then
> we switch refs_create_symref() to start using transactions for symref updates.
> This allows us to deprecate the create_symref code in the ref_storage_be
> interface and remove all associated code which is no longer used.
>
> The split was primarily done so we can merge the non-user facing parts of the
> previous series. While pertaining the user facing components into another set
> of patches wherein deeper discussion on the UX can be held without worrying
> about the internal implementation. Also by using this new functionality in a
> pre-existing command, we can leverage the existing tests to catch any
> inconsistencies. One of which was how 'git-symbolic-ref' doesn't add reflog for
> dangling symrefs, which I've modified my patch to do the same.
>
> We also modify the reference transaction hook to support symrefs. For any symref
> update the reference transaction hook will output the value with a 'ref:' prefix.
>
> Previous versions:
> V1: https://lore.kernel.org/git/20240330224623.579457-1-knayak@gitlab.com/
> V2: https://lore.kernel.org/git/20240412095908.1134387-1-knayak@gitlab.com/
> V3: https://lore.kernel.org/git/20240423212818.574123-1-knayak@gitlab.com/
> V4: https://lore.kernel.org/r/20240426152449.228860-1-knayak@gitlab.com
> V5: https://lore.kernel.org/r/20240501202229.2695774-1-knayak@gitlab.com
>
> Changes over v5 are:
> - More user friendly error messages.
> - `create_symref_lock` now writes to an err buf, instead of directly to stderr.
> - Refactor code to make it easier to read around logical operations.
> - Cleanup commit message and fix typos.
Just for the record, I don't really have much to add to this series
besides what has already been said.
Thanks!
Patrick
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 194+ messages in thread
* [PATCH v7 0/8] refs: add support for transactional symref updates
2024-05-03 12:41 ` [PATCH v6 " Karthik Nayak
` (8 preceding siblings ...)
2024-05-06 7:36 ` Patrick Steinhardt
@ 2024-05-07 6:00 ` Karthik Nayak
2024-05-07 6:00 ` [PATCH v7 1/8] refs: accept symref values in `ref_transaction_update()` Karthik Nayak
` (9 more replies)
9 siblings, 10 replies; 194+ messages in thread
From: Karthik Nayak @ 2024-05-07 6:00 UTC (permalink / raw)
To: karthik.188; +Cc: christian.couder, git, gitster, ps, phillip.wood123
From: Karthik Nayak <karthik.188@gmail.com>
The patch series takes over from the existing patch series, wherein we
introduced symref-* commands to git-update-ref. Since there was a ton of
discussions on the UX of the patch series and its application, I thought it
would be best to shorten the series and split it into multiple smaller series.
This series adds transactional support for symrefs in the reference db. Then
we switch refs_create_symref() to start using transactions for symref updates.
This allows us to deprecate the create_symref code in the ref_storage_be
interface and remove all associated code which is no longer used.
The split was primarily done so we can merge the non-user facing parts of the
previous series. While pertaining the user facing components into another set
of patches wherein deeper discussion on the UX can be held without worrying
about the internal implementation. Also by using this new functionality in a
pre-existing command, we can leverage the existing tests to catch any
inconsistencies. One of which was how 'git-symbolic-ref' doesn't add reflog for
dangling symrefs, which I've modified my patch to do the same.
We also modify the reference transaction hook to support symrefs. For any symref
update the reference transaction hook will output the value with a 'ref:' prefix.
Previous versions:
V1: https://lore.kernel.org/git/20240330224623.579457-1-knayak@gitlab.com
V2: https://lore.kernel.org/git/20240412095908.1134387-1-knayak@gitlab.com
V3: https://lore.kernel.org/git/20240423212818.574123-1-knayak@gitlab.com
V4: https://lore.kernel.org/r/20240426152449.228860-1-knayak@gitlab.com
V5: https://lore.kernel.org/r/20240501202229.2695774-1-knayak@gitlab.com
V6: https://lore.kernel.org/r/20240503124115.252413-1-knayak@gitlab.com
Thanks for all the reviews!
Changes since v6:
* Made the check for old/new oid & target more stricter by removing
the exception for null oid's.
* Extracted `ref_update_check_old_target` and `original_update_refname`
to refs.c
* ^This allowed us to generalize the error messages for non-matching
old_target values between files and reftable backend.
* Better line wrapping in code.
* Fixed some grammar in commit messages.
Junio, I've rebased my patches on top of, not sure if there is something else I
missed here:
- ps/ci-python-2-deprecation (2a686e00d2)
- ps/reftable-write-options (f5545b173b)
- ps/pseudo-ref-terminology (ad302a68fd)
Range diff against v6:
1: a354190905 ! 1: 2a09350661 refs: accept symref values in `ref_transaction_update()`
@@ refs.c: struct ref_update *ref_transaction_add_update(
if (transaction->state != REF_TRANSACTION_OPEN)
BUG("update called for transaction that is not open");
-+ if (old_oid && !is_null_oid(old_oid) && old_target)
++ if (old_oid && old_target)
+ BUG("only one of old_oid and old_target should be non NULL");
-+ if (new_oid && !is_null_oid(new_oid) && new_target)
++ if (new_oid && new_target)
+ BUG("only one of new_oid and new_target should be non NULL");
+
FLEX_ALLOC_STR(update, refname, refname);
2: 0d9c5b9804 = 2: 9e76b768b9 files-backend: extract out `create_symref_lock()`
3: e0219ffd31 = 3: c1d1c5a651 refs: support symrefs in 'reference-transaction' hook
-: ---------- > 4: d7d455c9b6 refs: move `original_update_refname` to 'refs.c'
4: b22c59c722 ! 5: 1c00cd69c6 refs: add support for transactional symref updates
@@ Commit message
updates. While this is exposed to users via 'git-update-ref' and its
'--stdin' mode, it is also used internally within various commands.
- However, we never supported transactional updates of symrefs. Let's add
- support for symrefs in both the 'files' and the 'reftable' backend.
+ However, we do not support transactional updates of symrefs. This commit
+ adds support for symrefs in both the 'files' and the 'reftable' backend.
Here, we add and use `ref_update_has_null_new_value()`, a helper
function which is used to check if there is a new_value in a reference
update. The new value could either be a symref target `new_target` or a
OID `new_oid`.
- With this, now transactional updates (verify, create, delete, update)
- can be used for:
+ We also add another common function `ref_update_check_old_target` which
+ will be used to check if the update's old_target corresponds to a
+ reference's current target.
+
+ Now transactional updates (verify, create, delete, update) can be used
+ for:
- regular refs
- symbolic refs
- conversion of regular to symbolic refs and vice versa
@@ refs.c: int ref_transaction_update(struct ref_transaction *transaction,
ref_transaction_add_update(transaction, refname, flags,
new_oid, old_oid, new_target,
-@@ refs.c: int copy_existing_ref(const char *oldref, const char *newref, const char *logmsg
- {
- return refs_copy_existing_ref(get_main_ref_store(the_repository), oldref, newref, logmsg);
+@@ refs.c: const char *ref_update_original_update_refname(struct ref_update *update)
+ return update->refname;
}
-+
+
+int ref_update_has_null_new_value(struct ref_update *update)
+{
+ return !update->new_target && is_null_oid(&update->new_oid);
+}
-
- ## refs/files-backend.c ##
-@@ refs/files-backend.c: static const char *original_update_refname(struct ref_update *update)
- return update->refname;
- }
-
-+/*
-+ * Check whether the old_target values stored in update are consistent
-+ * with current_target, which is the symbolic reference's current value.
-+ * If everything is OK, return 0; otherwise, write an error message to
-+ * err and return -1.
-+ */
-+static int check_old_target(struct ref_update *update,
-+ const char *current_target,
-+ struct strbuf *err)
++
++int ref_update_check_old_target(const char *referent, struct ref_update *update,
++ struct strbuf *err)
+{
+ if (!update->old_target)
+ BUG("called without old_target set");
+
-+ if (!strcmp(update->old_target, current_target))
++ if (!strcmp(referent, update->old_target))
+ return 0;
+
-+ if (!strcmp(current_target, ""))
-+ strbuf_addf(err, "cannot lock ref '%s': "
++ if (!strcmp(referent, ""))
++ strbuf_addf(err, "verifying symref target: '%s': "
+ "reference is missing but expected %s",
-+ original_update_refname(update),
++ ref_update_original_update_refname(update),
+ update->old_target);
+ else
-+ strbuf_addf(err, "cannot lock ref '%s': "
++ strbuf_addf(err, "verifying symref target: '%s': "
+ "is at %s but expected %s",
-+ original_update_refname(update),
-+ current_target, update->old_target);
-+
++ ref_update_original_update_refname(update),
++ referent, update->old_target);
+ return -1;
+}
-+
- /*
- * Check whether the REF_HAVE_OLD and old_oid values stored in update
- * are consistent with oid, which is the reference's current value. If
+
+ ## refs/files-backend.c ##
+@@ refs/files-backend.c: static int split_symref_update(struct ref_update *update,
+
+ new_update = ref_transaction_add_update(
+ transaction, referent, new_flags,
+- &update->new_oid, &update->old_oid,
+- NULL, NULL, update->msg);
++ update->new_target ? NULL : &update->new_oid,
++ update->old_target ? NULL : &update->old_oid,
++ update->new_target, update->old_target, update->msg);
+
+ new_update->parent_update = update;
+
@@ refs/files-backend.c: static int lock_ref_for_update(struct files_ref_store *refs,
files_assert_main_repository(refs, "lock_ref_for_update");
@@ refs/files-backend.c: static int lock_ref_for_update(struct files_ref_store *ref
+ }
+
+ if (update->old_target) {
-+ if (check_old_target(update, referent.buf, err)) {
++ if (ref_update_check_old_target(referent.buf, update, err)) {
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto out;
+ }
@@ refs/files-backend.c: static int lock_ref_for_update(struct files_ref_store *ref
+ * be set for symrefs, but we're strict about its usage.
+ */
+ if (update->old_target) {
-+ if (check_old_target(update, referent.buf, err)) {
++ if (ref_update_check_old_target(referent.buf, update, err)) {
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto out;
+ }
@@ refs/files-backend.c: static int lock_ref_for_update(struct files_ref_store *ref
- !(update->flags & REF_DELETING) &&
- !(update->flags & REF_LOG_ONLY)) {
+ if (update->new_target && !(update->flags & REF_LOG_ONLY)) {
-+ if (create_symref_lock(refs, lock, update->refname, update->new_target, err)) {
++ if (create_symref_lock(refs, lock, update->refname,
++ update->new_target, err)) {
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto out;
+ }
@@ refs/files-backend.c: static int files_transaction_finish(struct ref_store *ref_
if (commit_ref(lock)) {
## refs/refs-internal.h ##
-@@ refs/refs-internal.h: void base_ref_store_init(struct ref_store *refs, struct repository *repo,
+@@ refs/refs-internal.h: struct ref_store *maybe_debug_wrap_ref_store(const char *gitdir, struct ref_stor
*/
- struct ref_store *maybe_debug_wrap_ref_store(const char *gitdir, struct ref_store *store);
+ const char *ref_update_original_update_refname(struct ref_update *update);
+/*
+ * Helper function to check if the new value is null, this
@@ refs/refs-internal.h: void base_ref_store_init(struct ref_store *refs, struct re
+ * ref or a symbolic ref.
+ */
+int ref_update_has_null_new_value(struct ref_update *update);
++
++/*
++ * Check whether the old_target values stored in update are consistent
++ * with the referent, which is the symbolic reference's current value.
++ * If everything is OK, return 0; otherwise, write an error message to
++ * err and return -1.
++ */
++int ref_update_check_old_target(const char *referent, struct ref_update *update,
++ struct strbuf *err);
+
#endif /* REFS_REFS_INTERNAL_H */
@@ refs/reftable-backend.c: static int reftable_be_transaction_prepare(struct ref_s
*/
- if (u->flags & REF_HAVE_OLD && !oideq(¤t_oid, &u->old_oid)) {
+ if (u->old_target) {
-+ if (strcmp(referent.buf, u->old_target)) {
-+ if (!strcmp(referent.buf, ""))
-+ strbuf_addf(err, "verifying symref target: '%s': "
-+ "reference is missing but expected %s",
-+ original_update_refname(u),
-+ u->old_target);
-+ else
-+ strbuf_addf(err, "verifying symref target: '%s': "
-+ "is at %s but expected %s",
-+ original_update_refname(u),
-+ referent.buf, u->old_target);
++ if (ref_update_check_old_target(referent.buf, u, err)) {
+ ret = -1;
+ goto done;
+ }
+ } else if ((u->flags & REF_HAVE_OLD) && !oideq(¤t_oid, &u->old_oid)) {
if (is_null_oid(&u->old_oid))
strbuf_addf(err, _("cannot lock ref '%s': "
- "reference already exists"),
+ "reference already exists"),
@@ refs/reftable-backend.c: static int write_transaction_table(struct reftable_writer *writer, void *cb_data
* - `core.logAllRefUpdates` tells us to create the reflog for
* the given ref.
*/
- if (u->flags & REF_HAVE_NEW && !(u->type & REF_ISSYMREF) && is_null_oid(&u->new_oid)) {
-+ if ((u->flags & REF_HAVE_NEW) && !(u->type & REF_ISSYMREF) && ref_update_has_null_new_value(u)) {
++ if ((u->flags & REF_HAVE_NEW) &&
++ !(u->type & REF_ISSYMREF) &&
++ ref_update_has_null_new_value(u)) {
struct reftable_log_record log = {0};
struct reftable_iterator it = {0};
@@ refs/reftable-backend.c: static int write_transaction_table(struct reftable_writ
- log = &logs[logs_nr++];
- memset(log, 0, sizeof(*log));
-
-- fill_reftable_log_record(log);
+- fill_reftable_log_record(log, &committer_ident);
- log->update_index = ts;
- log->refname = xstrdup(u->refname);
- memcpy(log->value.update.new_hash, u->new_oid.hash, GIT_MAX_RAWSZ);
@@ refs/reftable-backend.c: static int write_transaction_table(struct reftable_writ
+ log = &logs[logs_nr++];
+ memset(log, 0, sizeof(*log));
+
-+ fill_reftable_log_record(log);
++ fill_reftable_log_record(log, &committer_ident);
+ log->update_index = ts;
+ log->refname = xstrdup(u->refname);
-+ memcpy(log->value.update.new_hash, u->new_oid.hash, GIT_MAX_RAWSZ);
-+ memcpy(log->value.update.old_hash, tx_update->current_oid.hash, GIT_MAX_RAWSZ);
++ memcpy(log->value.update.new_hash,
++ u->new_oid.hash, GIT_MAX_RAWSZ);
++ memcpy(log->value.update.old_hash,
++ tx_update->current_oid.hash, GIT_MAX_RAWSZ);
+ log->value.update.message =
+ xstrndup(u->msg, arg->refs->write_options.block_size / 2);
+ }
5: 636bf5ce98 ! 6: 027106cfe6 refs: use transaction in `refs_create_symref()`
@@ refs.c: int refs_create_symref(struct ref_store *refs,
int create_symref(const char *ref_target, const char *refs_heads_master,
- ## t/t0610-reftable-basics.sh ##
-@@ t/t0610-reftable-basics.sh: test_expect_success 'ref transaction: creating symbolic ref fails with F/D confl
- git init repo &&
- test_commit -C repo A &&
- cat >expect <<-EOF &&
-- error: unable to write symref for refs/heads: file/directory conflict
-+ error: ${SQ}refs/heads/main${SQ} exists; cannot create ${SQ}refs/heads${SQ}
- EOF
- test_must_fail git -C repo symbolic-ref refs/heads refs/heads/foo 2>err &&
- test_cmp expect err
-
## t/t1416-ref-transaction-hooks.sh ##
@@ t/t1416-ref-transaction-hooks.sh: test_expect_success 'interleaving hook calls succeed' '
test_cmp expect target-repo.git/actual
6: 07fb23374f = 7: 6939406143 refs: rename `refs_create_symref()` to `refs_update_symref()`
7: 5c05813bcc ! 8: bc787b903b refs: remove `create_symref` and associated dead code
@@ refs/reftable-backend.c: struct write_create_symref_arg {
- .value.symref = (char *)create->target,
- .update_index = ts,
- };
+- struct ident_split committer_ident = {0};
- struct reftable_log_record log = {0};
- struct object_id new_oid;
- struct object_id old_oid;
+- const char *committer_info;
- int ret;
-
- reftable_writer_set_limits(writer, ts, ts);
-
+- ret = refs_verify_refname_available(&create->refs->base, create->refname,
+- NULL, NULL, create->err);
+- if (ret < 0)
+- return ret;
+-
- ret = reftable_writer_add_ref(writer, &ref);
- if (ret)
- return ret;
@@ refs/reftable-backend.c: struct write_create_symref_arg {
- !should_write_log(&create->refs->base, create->refname))
- return 0;
-
-- fill_reftable_log_record(&log);
+- committer_info = git_committer_info(0);
+- if (split_ident_line(&committer_ident, committer_info, strlen(committer_info)))
+- BUG("failed splitting committer info");
+-
+- fill_reftable_log_record(&log, &committer_ident);
- log.refname = xstrdup(create->refname);
- log.update_index = ts;
- log.value.update.message = xstrndup(create->logmsg,
@@ refs/reftable-backend.c: struct write_create_symref_arg {
- struct reftable_ref_store *refs =
- reftable_be_downcast(ref_store, REF_STORE_WRITE, "create_symref");
- struct reftable_stack *stack = stack_for(refs, refname, &refname);
+- struct strbuf err = STRBUF_INIT;
- struct write_create_symref_arg arg = {
- .refs = refs,
- .stack = stack,
- .refname = refname,
- .target = target,
- .logmsg = logmsg,
+- .err = &err,
- };
- int ret;
-
@@ refs/reftable-backend.c: struct write_create_symref_arg {
-
-done:
- assert(ret != REFTABLE_API_ERROR);
-- if (ret)
-- error("unable to write symref for %s: %s", refname,
-- reftable_error_str(ret));
+- if (ret) {
+- if (err.len)
+- error("%s", err.buf);
+- else
+- error("unable to write symref for %s: %s", refname,
+- reftable_error_str(ret));
+- }
+-
+- strbuf_release(&err);
- return ret;
-}
-
Karthik Nayak (8):
refs: accept symref values in `ref_transaction_update()`
files-backend: extract out `create_symref_lock()`
refs: support symrefs in 'reference-transaction' hook
refs: move `original_update_refname` to 'refs.c'
refs: add support for transactional symref updates
refs: use transaction in `refs_create_symref()`
refs: rename `refs_create_symref()` to `refs_update_symref()`
refs: remove `create_symref` and associated dead code
Documentation/githooks.txt | 14 ++-
branch.c | 2 +-
builtin/branch.c | 2 +-
builtin/fast-import.c | 5 +-
builtin/fetch.c | 2 +-
builtin/receive-pack.c | 1 +
builtin/replace.c | 2 +-
builtin/tag.c | 1 +
builtin/update-ref.c | 1 +
builtin/worktree.c | 2 +-
refs.c | 119 ++++++++++++++----
refs.h | 20 ++-
refs/debug.c | 13 --
refs/files-backend.c | 208 +++++++++++++++++--------------
refs/packed-backend.c | 1 -
refs/refs-internal.h | 40 +++++-
refs/reftable-backend.c | 202 +++++++++---------------------
sequencer.c | 9 +-
t/helper/test-ref-store.c | 2 +-
t/t1416-ref-transaction-hooks.sh | 23 ++++
walker.c | 2 +-
21 files changed, 373 insertions(+), 298 deletions(-)
--
2.43.GIT
^ permalink raw reply [flat|nested] 194+ messages in thread
* [PATCH v7 1/8] refs: accept symref values in `ref_transaction_update()`
2024-05-07 6:00 ` [PATCH v7 0/8] " Karthik Nayak
@ 2024-05-07 6:00 ` Karthik Nayak
2024-05-07 6:00 ` [PATCH v7 2/8] files-backend: extract out `create_symref_lock()` Karthik Nayak
` (8 subsequent siblings)
9 siblings, 0 replies; 194+ messages in thread
From: Karthik Nayak @ 2024-05-07 6:00 UTC (permalink / raw)
To: karthik.188; +Cc: christian.couder, git, gitster, ps, phillip.wood123
From: Karthik Nayak <karthik.188@gmail.com>
The function `ref_transaction_update()` obtains ref information and
flags to create a `ref_update` and add them to the transaction at hand.
To extend symref support in transactions, we need to also accept the
old and new ref targets and process it. This commit adds the required
parameters to the function and modifies all call sites.
The two parameters added are `new_target` and `old_target`. The
`new_target` is used to denote what the reference should point to when
the transaction is applied. Some functions allow this parameter to be
NULL, meaning that the reference is not changed.
The `old_target` denotes the value the reference must have before the
update. Some functions allow this parameter to be NULL, meaning that the
old value of the reference is not checked.
We also update the internal function `ref_transaction_add_update()`
similarly to take the two new parameters.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
branch.c | 2 +-
builtin/fast-import.c | 5 +++--
builtin/fetch.c | 2 +-
builtin/receive-pack.c | 1 +
builtin/replace.c | 2 +-
builtin/tag.c | 1 +
builtin/update-ref.c | 1 +
refs.c | 22 +++++++++++++++++-----
refs.h | 18 +++++++++++++++++-
refs/files-backend.c | 12 ++++++------
refs/refs-internal.h | 14 ++++++++++++++
refs/reftable-backend.c | 4 ++--
sequencer.c | 9 +++++----
walker.c | 2 +-
14 files changed, 71 insertions(+), 24 deletions(-)
diff --git a/branch.c b/branch.c
index e4a738fc7b..48af4c3ceb 100644
--- a/branch.c
+++ b/branch.c
@@ -627,7 +627,7 @@ void create_branch(struct repository *r,
if (!transaction ||
ref_transaction_update(transaction, ref.buf,
&oid, forcing ? NULL : null_oid(),
- 0, msg, &err) ||
+ NULL, NULL, 0, msg, &err) ||
ref_transaction_commit(transaction, &err))
die("%s", err.buf);
ref_transaction_free(transaction);
diff --git a/builtin/fast-import.c b/builtin/fast-import.c
index dc5a9d32dd..297dfb91a1 100644
--- a/builtin/fast-import.c
+++ b/builtin/fast-import.c
@@ -1634,7 +1634,7 @@ static int update_branch(struct branch *b)
transaction = ref_transaction_begin(&err);
if (!transaction ||
ref_transaction_update(transaction, b->name, &b->oid, &old_oid,
- 0, msg, &err) ||
+ NULL, NULL, 0, msg, &err) ||
ref_transaction_commit(transaction, &err)) {
ref_transaction_free(transaction);
error("%s", err.buf);
@@ -1675,7 +1675,8 @@ static void dump_tags(void)
strbuf_addf(&ref_name, "refs/tags/%s", t->name);
if (ref_transaction_update(transaction, ref_name.buf,
- &t->oid, NULL, 0, msg, &err)) {
+ &t->oid, NULL, NULL, NULL,
+ 0, msg, &err)) {
failure |= error("%s", err.buf);
goto cleanup;
}
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 5857d860db..66840b7c5b 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -668,7 +668,7 @@ static int s_update_ref(const char *action,
ret = ref_transaction_update(transaction, ref->name, &ref->new_oid,
check_old ? &ref->old_oid : NULL,
- 0, msg, &err);
+ NULL, NULL, 0, msg, &err);
if (ret) {
ret = STORE_REF_ERROR_OTHER;
goto out;
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index e8d7df14b6..b150ef39a8 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -1595,6 +1595,7 @@ static const char *update(struct command *cmd, struct shallow_info *si)
if (ref_transaction_update(transaction,
namespaced_name,
new_oid, old_oid,
+ NULL, NULL,
0, "push",
&err)) {
rp_error("%s", err.buf);
diff --git a/builtin/replace.c b/builtin/replace.c
index da59600ad2..7690687b0e 100644
--- a/builtin/replace.c
+++ b/builtin/replace.c
@@ -201,7 +201,7 @@ static int replace_object_oid(const char *object_ref,
transaction = ref_transaction_begin(&err);
if (!transaction ||
ref_transaction_update(transaction, ref.buf, repl, &prev,
- 0, NULL, &err) ||
+ NULL, NULL, 0, NULL, &err) ||
ref_transaction_commit(transaction, &err))
res = error("%s", err.buf);
diff --git a/builtin/tag.c b/builtin/tag.c
index 9a33cb50b4..40a65fdebc 100644
--- a/builtin/tag.c
+++ b/builtin/tag.c
@@ -660,6 +660,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
transaction = ref_transaction_begin(&err);
if (!transaction ||
ref_transaction_update(transaction, ref.buf, &object, &prev,
+ NULL, NULL,
create_reflog ? REF_FORCE_CREATE_REFLOG : 0,
reflog_msg.buf, &err) ||
ref_transaction_commit(transaction, &err)) {
diff --git a/builtin/update-ref.c b/builtin/update-ref.c
index e46afbc46d..21fdbf6ac8 100644
--- a/builtin/update-ref.c
+++ b/builtin/update-ref.c
@@ -204,6 +204,7 @@ static void parse_cmd_update(struct ref_transaction *transaction,
if (ref_transaction_update(transaction, refname,
&new_oid, have_old ? &old_oid : NULL,
+ NULL, NULL,
update_flags | create_reflog_flag,
msg, &err))
die("%s", err.buf);
diff --git a/refs.c b/refs.c
index 7c3c7465a4..d7df137904 100644
--- a/refs.c
+++ b/refs.c
@@ -1268,6 +1268,7 @@ struct ref_update *ref_transaction_add_update(
const char *refname, unsigned int flags,
const struct object_id *new_oid,
const struct object_id *old_oid,
+ const char *new_target, const char *old_target,
const char *msg)
{
struct ref_update *update;
@@ -1275,6 +1276,11 @@ struct ref_update *ref_transaction_add_update(
if (transaction->state != REF_TRANSACTION_OPEN)
BUG("update called for transaction that is not open");
+ if (old_oid && old_target)
+ BUG("only one of old_oid and old_target should be non NULL");
+ if (new_oid && new_target)
+ BUG("only one of new_oid and new_target should be non NULL");
+
FLEX_ALLOC_STR(update, refname, refname);
ALLOC_GROW(transaction->updates, transaction->nr + 1, transaction->alloc);
transaction->updates[transaction->nr++] = update;
@@ -1293,6 +1299,8 @@ int ref_transaction_update(struct ref_transaction *transaction,
const char *refname,
const struct object_id *new_oid,
const struct object_id *old_oid,
+ const char *new_target,
+ const char *old_target,
unsigned int flags, const char *msg,
struct strbuf *err)
{
@@ -1327,7 +1335,8 @@ int ref_transaction_update(struct ref_transaction *transaction,
flags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0);
ref_transaction_add_update(transaction, refname, flags,
- new_oid, old_oid, msg);
+ new_oid, old_oid, new_target,
+ old_target, msg);
return 0;
}
@@ -1342,7 +1351,8 @@ int ref_transaction_create(struct ref_transaction *transaction,
return 1;
}
return ref_transaction_update(transaction, refname, new_oid,
- null_oid(), flags, msg, err);
+ null_oid(), NULL, NULL, flags,
+ msg, err);
}
int ref_transaction_delete(struct ref_transaction *transaction,
@@ -1355,7 +1365,8 @@ int ref_transaction_delete(struct ref_transaction *transaction,
BUG("delete called with old_oid set to zeros");
return ref_transaction_update(transaction, refname,
null_oid(), old_oid,
- flags, msg, err);
+ NULL, NULL, flags,
+ msg, err);
}
int ref_transaction_verify(struct ref_transaction *transaction,
@@ -1368,6 +1379,7 @@ int ref_transaction_verify(struct ref_transaction *transaction,
BUG("verify called with old_oid set to NULL");
return ref_transaction_update(transaction, refname,
NULL, old_oid,
+ NULL, NULL,
flags, NULL, err);
}
@@ -1382,8 +1394,8 @@ int refs_update_ref(struct ref_store *refs, const char *msg,
t = ref_store_transaction_begin(refs, &err);
if (!t ||
- ref_transaction_update(t, refname, new_oid, old_oid, flags, msg,
- &err) ||
+ ref_transaction_update(t, refname, new_oid, old_oid, NULL, NULL,
+ flags, msg, &err) ||
ref_transaction_commit(t, &err)) {
ret = 1;
ref_transaction_free(t);
diff --git a/refs.h b/refs.h
index 8255989e7e..9abbfd35a2 100644
--- a/refs.h
+++ b/refs.h
@@ -648,6 +648,16 @@ struct ref_transaction *ref_transaction_begin(struct strbuf *err);
* before the update. A copy of this value is made in the
* transaction.
*
+ * new_target -- the target reference that the reference will be
+ * updated to point to. If the reference is a regular reference,
+ * it will be converted to a symbolic reference. Cannot be set
+ * together with `new_oid`. A copy of this value is made in the
+ * transaction.
+ *
+ * old_target -- the reference that the reference must be pointing to.
+ * Canont be set together with `old_oid`. A copy of this value is
+ * made in the transaction.
+ *
* flags -- flags affecting the update, passed to
* update_ref_lock(). Possible flags: REF_NO_DEREF,
* REF_FORCE_CREATE_REFLOG. See those constants for more
@@ -713,7 +723,11 @@ struct ref_transaction *ref_transaction_begin(struct strbuf *err);
* beforehand. The old value is checked after the lock is taken to
* prevent races. If the old value doesn't agree with old_oid, the
* whole transaction fails. If old_oid is NULL, then the previous
- * value is not checked.
+ * value is not checked. If `old_target` is not NULL, treat the reference
+ * as a symbolic ref and validate that its target before the update is
+ * `old_target`. If the `new_target` is not NULL, then the reference
+ * will be updated to a symbolic ref which targets `new_target`.
+ * Together, these allow us to update between regular refs and symrefs.
*
* See the above comment "Reference transaction updates" for more
* information.
@@ -722,6 +736,8 @@ int ref_transaction_update(struct ref_transaction *transaction,
const char *refname,
const struct object_id *new_oid,
const struct object_id *old_oid,
+ const char *new_target,
+ const char *old_target,
unsigned int flags, const char *msg,
struct strbuf *err);
diff --git a/refs/files-backend.c b/refs/files-backend.c
index ea927c516d..7e432cf026 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -1197,7 +1197,7 @@ static void prune_ref(struct files_ref_store *refs, struct ref_to_prune *r)
ref_transaction_add_update(
transaction, r->name,
REF_NO_DEREF | REF_HAVE_NEW | REF_HAVE_OLD | REF_IS_PRUNING,
- null_oid(), &r->oid, NULL);
+ null_oid(), &r->oid, NULL, NULL, NULL);
if (ref_transaction_commit(transaction, &err))
goto cleanup;
@@ -1291,7 +1291,7 @@ static int files_pack_refs(struct ref_store *ref_store,
* packed-refs transaction:
*/
if (ref_transaction_update(transaction, iter->refname,
- iter->oid, NULL,
+ iter->oid, NULL, NULL, NULL,
REF_NO_DEREF, NULL, &err))
die("failure preparing to create packed reference %s: %s",
iter->refname, err.buf);
@@ -2308,7 +2308,7 @@ static int split_head_update(struct ref_update *update,
transaction, "HEAD",
update->flags | REF_LOG_ONLY | REF_NO_DEREF,
&update->new_oid, &update->old_oid,
- update->msg);
+ NULL, NULL, update->msg);
/*
* Add "HEAD". This insertion is O(N) in the transaction
@@ -2371,7 +2371,7 @@ static int split_symref_update(struct ref_update *update,
new_update = ref_transaction_add_update(
transaction, referent, new_flags,
&update->new_oid, &update->old_oid,
- update->msg);
+ NULL, NULL, update->msg);
new_update->parent_update = update;
@@ -2762,7 +2762,7 @@ static int files_transaction_prepare(struct ref_store *ref_store,
packed_transaction, update->refname,
REF_HAVE_NEW | REF_NO_DEREF,
&update->new_oid, NULL,
- NULL);
+ NULL, NULL, NULL);
}
}
@@ -3047,7 +3047,7 @@ static int files_initial_transaction_commit(struct ref_store *ref_store,
ref_transaction_add_update(packed_transaction, update->refname,
update->flags & ~REF_HAVE_OLD,
&update->new_oid, &update->old_oid,
- NULL);
+ NULL, NULL, NULL);
}
if (packed_refs_lock(refs->packed_ref_store, 0, err)) {
diff --git a/refs/refs-internal.h b/refs/refs-internal.h
index 56641aa57a..108f4ec419 100644
--- a/refs/refs-internal.h
+++ b/refs/refs-internal.h
@@ -124,6 +124,19 @@ struct ref_update {
*/
struct object_id old_oid;
+ /*
+ * If set, point the reference to this value. This can also be
+ * used to convert regular references to become symbolic refs.
+ * Cannot be set together with `new_oid`.
+ */
+ const char *new_target;
+
+ /*
+ * If set, check that the reference previously pointed to this
+ * value. Cannot be set together with `old_oid`.
+ */
+ const char *old_target;
+
/*
* One or more of REF_NO_DEREF, REF_FORCE_CREATE_REFLOG,
* REF_HAVE_NEW, REF_HAVE_OLD, or backend-specific flags.
@@ -173,6 +186,7 @@ struct ref_update *ref_transaction_add_update(
const char *refname, unsigned int flags,
const struct object_id *new_oid,
const struct object_id *old_oid,
+ const char *new_target, const char *old_target,
const char *msg);
/*
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index 10ee92e1f7..f48ab5ad22 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -870,7 +870,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
new_update = ref_transaction_add_update(
transaction, "HEAD",
u->flags | REF_LOG_ONLY | REF_NO_DEREF,
- &u->new_oid, &u->old_oid, u->msg);
+ &u->new_oid, &u->old_oid, NULL, NULL, u->msg);
string_list_insert(&affected_refnames, new_update->refname);
}
@@ -949,7 +949,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
*/
new_update = ref_transaction_add_update(
transaction, referent.buf, new_flags,
- &u->new_oid, &u->old_oid, u->msg);
+ &u->new_oid, &u->old_oid, NULL, NULL, u->msg);
new_update->parent_update = u;
/*
diff --git a/sequencer.c b/sequencer.c
index 88de4dc20f..61e007d85f 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -665,7 +665,7 @@ static int fast_forward_to(struct repository *r,
if (!transaction ||
ref_transaction_update(transaction, "HEAD",
to, unborn && !is_rebase_i(opts) ?
- null_oid() : from,
+ null_oid() : from, NULL, NULL,
0, sb.buf, &err) ||
ref_transaction_commit(transaction, &err)) {
ref_transaction_free(transaction);
@@ -1298,7 +1298,7 @@ int update_head_with_reflog(const struct commit *old_head,
if (!transaction ||
ref_transaction_update(transaction, "HEAD", new_head,
old_head ? &old_head->object.oid : null_oid(),
- 0, sb.buf, err) ||
+ NULL, NULL, 0, sb.buf, err) ||
ref_transaction_commit(transaction, err)) {
ret = -1;
}
@@ -3832,8 +3832,9 @@ static int do_label(struct repository *r, const char *name, int len)
} else if (repo_get_oid(r, "HEAD", &head_oid)) {
error(_("could not read HEAD"));
ret = -1;
- } else if (ref_transaction_update(transaction, ref_name.buf, &head_oid,
- NULL, 0, msg.buf, &err) < 0 ||
+ } else if (ref_transaction_update(transaction, ref_name.buf,
+ &head_oid, NULL, NULL, NULL,
+ 0, msg.buf, &err) < 0 ||
ref_transaction_commit(transaction, &err)) {
error("%s", err.buf);
ret = -1;
diff --git a/walker.c b/walker.c
index c0fd632d92..1b3df43906 100644
--- a/walker.c
+++ b/walker.c
@@ -324,7 +324,7 @@ int walker_fetch(struct walker *walker, int targets, char **target,
strbuf_reset(&refname);
strbuf_addf(&refname, "refs/%s", write_ref[i]);
if (ref_transaction_update(transaction, refname.buf,
- oids + i, NULL, 0,
+ oids + i, NULL, NULL, NULL, 0,
msg ? msg : "fetch (unknown)",
&err)) {
error("%s", err.buf);
--
2.43.GIT
^ permalink raw reply related [flat|nested] 194+ messages in thread
* [PATCH v7 2/8] files-backend: extract out `create_symref_lock()`
2024-05-07 6:00 ` [PATCH v7 0/8] " Karthik Nayak
2024-05-07 6:00 ` [PATCH v7 1/8] refs: accept symref values in `ref_transaction_update()` Karthik Nayak
@ 2024-05-07 6:00 ` Karthik Nayak
2024-05-07 6:00 ` [PATCH v7 3/8] refs: support symrefs in 'reference-transaction' hook Karthik Nayak
` (7 subsequent siblings)
9 siblings, 0 replies; 194+ messages in thread
From: Karthik Nayak @ 2024-05-07 6:00 UTC (permalink / raw)
To: karthik.188; +Cc: christian.couder, git, gitster, ps, phillip.wood123
From: Karthik Nayak <karthik.188@gmail.com>
The function `create_symref_locked()` creates a symref by creating a
'<symref>.lock' file and then committing the symref lock, which creates
the final symref.
Extract the early half of `create_symref_locked()` into a new helper
function `create_symref_lock()`. Because the name of the new function is
too similar to the original, rename the original to
`create_and_commit_symref()` to avoid confusion.
The new function `create_symref_locked()` can be used to create the
symref lock in a separate step from that of committing it. This allows
to add transactional support for symrefs, where the lock would be
created in the preparation step and the lock would be committed in the
finish step.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
refs/files-backend.c | 51 ++++++++++++++++++++++++++++++++------------
1 file changed, 37 insertions(+), 14 deletions(-)
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 7e432cf026..40cc715ea7 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -1919,27 +1919,49 @@ static void update_symref_reflog(struct files_ref_store *refs,
}
}
-static int create_symref_locked(struct files_ref_store *refs,
- struct ref_lock *lock, const char *refname,
- const char *target, const char *logmsg)
+static int create_symref_lock(struct files_ref_store *refs,
+ struct ref_lock *lock, const char *refname,
+ const char *target, struct strbuf *err)
{
+ if (!fdopen_lock_file(&lock->lk, "w")) {
+ strbuf_addf(err, "unable to fdopen %s: %s",
+ get_lock_file_path(&lock->lk), strerror(errno));
+ return -1;
+ }
+
+ if (fprintf(get_lock_file_fp(&lock->lk), "ref: %s\n", target) < 0) {
+ strbuf_addf(err, "unable to write to %s: %s",
+ get_lock_file_path(&lock->lk), strerror(errno));
+ return -1;
+ }
+
+ return 0;
+}
+
+static int create_and_commit_symref(struct files_ref_store *refs,
+ struct ref_lock *lock, const char *refname,
+ const char *target, const char *logmsg)
+{
+ struct strbuf err = STRBUF_INIT;
+ int ret;
+
if (prefer_symlink_refs && !create_ref_symlink(lock, target)) {
update_symref_reflog(refs, lock, refname, target, logmsg);
return 0;
}
- if (!fdopen_lock_file(&lock->lk, "w"))
- return error("unable to fdopen %s: %s",
- get_lock_file_path(&lock->lk), strerror(errno));
+ ret = create_symref_lock(refs, lock, refname, target, &err);
+ if (!ret) {
+ update_symref_reflog(refs, lock, refname, target, logmsg);
- update_symref_reflog(refs, lock, refname, target, logmsg);
+ if (commit_ref(lock) < 0)
+ return error("unable to write symref for %s: %s", refname,
+ strerror(errno));
+ } else {
+ return error("%s", err.buf);
+ }
- /* no error check; commit_ref will check ferror */
- fprintf(get_lock_file_fp(&lock->lk), "ref: %s\n", target);
- if (commit_ref(lock) < 0)
- return error("unable to write symref for %s: %s", refname,
- strerror(errno));
- return 0;
+ return ret;
}
static int files_create_symref(struct ref_store *ref_store,
@@ -1959,7 +1981,8 @@ static int files_create_symref(struct ref_store *ref_store,
return -1;
}
- ret = create_symref_locked(refs, lock, refname, target, logmsg);
+ ret = create_and_commit_symref(refs, lock, refname, target, logmsg);
+
unlock_ref(lock);
return ret;
}
--
2.43.GIT
^ permalink raw reply related [flat|nested] 194+ messages in thread
* [PATCH v7 3/8] refs: support symrefs in 'reference-transaction' hook
2024-05-07 6:00 ` [PATCH v7 0/8] " Karthik Nayak
2024-05-07 6:00 ` [PATCH v7 1/8] refs: accept symref values in `ref_transaction_update()` Karthik Nayak
2024-05-07 6:00 ` [PATCH v7 2/8] files-backend: extract out `create_symref_lock()` Karthik Nayak
@ 2024-05-07 6:00 ` Karthik Nayak
2024-05-07 6:00 ` [PATCH v7 4/8] refs: move `original_update_refname` to 'refs.c' Karthik Nayak
` (6 subsequent siblings)
9 siblings, 0 replies; 194+ messages in thread
From: Karthik Nayak @ 2024-05-07 6:00 UTC (permalink / raw)
To: karthik.188; +Cc: christian.couder, git, gitster, ps, phillip.wood123
From: Karthik Nayak <karthik.188@gmail.com>
The 'reference-transaction' hook runs whenever a reference update is
made to the system. In a previous commit, we added the `old_target` and
`new_target` fields to the `reference_transaction_update()`. In
following commits we'll also add the code to handle symref's in the
reference backends.
Support symrefs also in the 'reference-transaction' hook, by modifying
the current format:
<old-oid> SP <new-oid> SP <ref-name> LF
to be be:
<old-value> SP <new-value> SP <ref-name> LF
where for regular refs the output would not change and remain the same.
But when either 'old-value' or 'new-value' is a symref, we print the ref
as 'ref:<ref-target>'.
This does break backward compatibility, but the 'reference-transaction'
hook's documentation always stated that support for symbolic references
may be added in the future.
We do not add any tests in this commit since there is no git command
which activates this flow, in an upcoming commit, we'll start using
transaction based symref updates as the default, we'll add tests there
for the hook too.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
Documentation/githooks.txt | 14 +++++++++-----
refs.c | 20 ++++++++++++++++----
2 files changed, 25 insertions(+), 9 deletions(-)
diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
index ee9b92c90d..06e997131b 100644
--- a/Documentation/githooks.txt
+++ b/Documentation/githooks.txt
@@ -486,7 +486,7 @@ reference-transaction
This hook is invoked by any Git command that performs reference
updates. It executes whenever a reference transaction is prepared,
committed or aborted and may thus get called multiple times. The hook
-does not cover symbolic references (but that may change in the future).
+also supports symbolic reference updates.
The hook takes exactly one argument, which is the current state the
given reference transaction is in:
@@ -503,16 +503,20 @@ given reference transaction is in:
For each reference update that was added to the transaction, the hook
receives on standard input a line of the format:
- <old-oid> SP <new-oid> SP <ref-name> LF
+ <old-value> SP <new-value> SP <ref-name> LF
-where `<old-oid>` is the old object name passed into the reference
-transaction, `<new-oid>` is the new object name to be stored in the
+where `<old-value>` is the old object name passed into the reference
+transaction, `<new-value>` is the new object name to be stored in the
ref and `<ref-name>` is the full name of the ref. When force updating
the reference regardless of its current value or when the reference is
-to be created anew, `<old-oid>` is the all-zeroes object name. To
+to be created anew, `<old-value>` is the all-zeroes object name. To
distinguish these cases, you can inspect the current value of
`<ref-name>` via `git rev-parse`.
+For symbolic reference updates the `<old_value>` and `<new-value>`
+fields could denote references instead of objects. A reference will be
+denoted with a 'ref:' prefix, like `ref:<ref-target>`.
+
The exit status of the hook is ignored for any state except for the
"prepared" state. In the "prepared" state, a non-zero exit status will
cause the transaction to be aborted. The hook will not be called with
diff --git a/refs.c b/refs.c
index d7df137904..2bb3e09425 100644
--- a/refs.c
+++ b/refs.c
@@ -2366,10 +2366,22 @@ static int run_transaction_hook(struct ref_transaction *transaction,
struct ref_update *update = transaction->updates[i];
strbuf_reset(&buf);
- strbuf_addf(&buf, "%s %s %s\n",
- oid_to_hex(&update->old_oid),
- oid_to_hex(&update->new_oid),
- update->refname);
+
+ if (!(update->flags & REF_HAVE_OLD))
+ strbuf_addf(&buf, "%s ", oid_to_hex(null_oid()));
+ else if (update->old_target)
+ strbuf_addf(&buf, "ref:%s ", update->old_target);
+ else
+ strbuf_addf(&buf, "%s ", oid_to_hex(&update->old_oid));
+
+ if (!(update->flags & REF_HAVE_NEW))
+ strbuf_addf(&buf, "%s ", oid_to_hex(null_oid()));
+ else if (update->new_target)
+ strbuf_addf(&buf, "ref:%s ", update->new_target);
+ else
+ strbuf_addf(&buf, "%s ", oid_to_hex(&update->new_oid));
+
+ strbuf_addf(&buf, "%s\n", update->refname);
if (write_in_full(proc.in, buf.buf, buf.len) < 0) {
if (errno != EPIPE) {
--
2.43.GIT
^ permalink raw reply related [flat|nested] 194+ messages in thread
* [PATCH v7 4/8] refs: move `original_update_refname` to 'refs.c'
2024-05-07 6:00 ` [PATCH v7 0/8] " Karthik Nayak
` (2 preceding siblings ...)
2024-05-07 6:00 ` [PATCH v7 3/8] refs: support symrefs in 'reference-transaction' hook Karthik Nayak
@ 2024-05-07 6:00 ` Karthik Nayak
2024-05-07 6:00 ` [PATCH v7 5/8] refs: add support for transactional symref updates Karthik Nayak
` (5 subsequent siblings)
9 siblings, 0 replies; 194+ messages in thread
From: Karthik Nayak @ 2024-05-07 6:00 UTC (permalink / raw)
To: karthik.188; +Cc: christian.couder, git, gitster, ps, phillip.wood123
From: Karthik Nayak <karthik.188@gmail.com>
The files backend and the reftable backend implement
`original_update_refname` to obtain the original refname of the update.
Move it out to 'refs.c' and only expose it internally to the refs
library. This will be used in an upcoming commit to also introduce
another common functionality for the two backends.
We also rename the function to `ref_update_original_update_refname` to
keep it consistent with the upcoming other 'ref_update_*' functions
that'll be introduced.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
refs.c | 9 +++++++++
refs/files-backend.c | 21 +++++----------------
refs/refs-internal.h | 5 +++++
refs/reftable-backend.c | 24 +++++++-----------------
4 files changed, 26 insertions(+), 33 deletions(-)
diff --git a/refs.c b/refs.c
index 2bb3e09425..d65e4cc41e 100644
--- a/refs.c
+++ b/refs.c
@@ -2830,3 +2830,12 @@ int copy_existing_ref(const char *oldref, const char *newref, const char *logmsg
{
return refs_copy_existing_ref(get_main_ref_store(the_repository), oldref, newref, logmsg);
}
+
+const char *ref_update_original_update_refname(struct ref_update *update)
+{
+ while (update->parent_update)
+ update = update->parent_update;
+
+ return update->refname;
+}
+
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 40cc715ea7..64d2a50e97 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -2422,17 +2422,6 @@ static int split_symref_update(struct ref_update *update,
return 0;
}
-/*
- * Return the refname under which update was originally requested.
- */
-static const char *original_update_refname(struct ref_update *update)
-{
- while (update->parent_update)
- update = update->parent_update;
-
- return update->refname;
-}
-
/*
* Check whether the REF_HAVE_OLD and old_oid values stored in update
* are consistent with oid, which is the reference's current value. If
@@ -2449,16 +2438,16 @@ static int check_old_oid(struct ref_update *update, struct object_id *oid,
if (is_null_oid(&update->old_oid))
strbuf_addf(err, "cannot lock ref '%s': "
"reference already exists",
- original_update_refname(update));
+ ref_update_original_update_refname(update));
else if (is_null_oid(oid))
strbuf_addf(err, "cannot lock ref '%s': "
"reference is missing but expected %s",
- original_update_refname(update),
+ ref_update_original_update_refname(update),
oid_to_hex(&update->old_oid));
else
strbuf_addf(err, "cannot lock ref '%s': "
"is at %s but expected %s",
- original_update_refname(update),
+ ref_update_original_update_refname(update),
oid_to_hex(oid),
oid_to_hex(&update->old_oid));
@@ -2512,7 +2501,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,
reason = strbuf_detach(err, NULL);
strbuf_addf(err, "cannot lock ref '%s': %s",
- original_update_refname(update), reason);
+ ref_update_original_update_refname(update), reason);
free(reason);
goto out;
}
@@ -2532,7 +2521,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,
if (update->flags & REF_HAVE_OLD) {
strbuf_addf(err, "cannot lock ref '%s': "
"error reading reference",
- original_update_refname(update));
+ ref_update_original_update_refname(update));
ret = TRANSACTION_GENERIC_ERROR;
goto out;
}
diff --git a/refs/refs-internal.h b/refs/refs-internal.h
index 108f4ec419..617b93a6c8 100644
--- a/refs/refs-internal.h
+++ b/refs/refs-internal.h
@@ -749,4 +749,9 @@ void base_ref_store_init(struct ref_store *refs, struct repository *repo,
*/
struct ref_store *maybe_debug_wrap_ref_store(const char *gitdir, struct ref_store *store);
+/*
+ * Return the refname under which update was originally requested.
+ */
+const char *ref_update_original_update_refname(struct ref_update *update);
+
#endif /* REFS_REFS_INTERNAL_H */
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index f48ab5ad22..0988f59ed4 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -622,16 +622,6 @@ static int reftable_be_read_symbolic_ref(struct ref_store *ref_store,
return ret;
}
-/*
- * Return the refname under which update was originally requested.
- */
-static const char *original_update_refname(struct ref_update *update)
-{
- while (update->parent_update)
- update = update->parent_update;
- return update->refname;
-}
-
struct reftable_transaction_update {
struct ref_update *update;
struct object_id current_oid;
@@ -910,7 +900,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
/* The reference does not exist, but we expected it to. */
strbuf_addf(err, _("cannot lock ref '%s': "
"unable to resolve reference '%s'"),
- original_update_refname(u), u->refname);
+ ref_update_original_update_refname(u), u->refname);
ret = -1;
goto done;
}
@@ -982,17 +972,17 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
if (u->flags & REF_HAVE_OLD && !oideq(¤t_oid, &u->old_oid)) {
if (is_null_oid(&u->old_oid))
strbuf_addf(err, _("cannot lock ref '%s': "
- "reference already exists"),
- original_update_refname(u));
+ "reference already exists"),
+ ref_update_original_update_refname(u));
else if (is_null_oid(¤t_oid))
strbuf_addf(err, _("cannot lock ref '%s': "
- "reference is missing but expected %s"),
- original_update_refname(u),
+ "reference is missing but expected %s"),
+ ref_update_original_update_refname(u),
oid_to_hex(&u->old_oid));
else
strbuf_addf(err, _("cannot lock ref '%s': "
- "is at %s but expected %s"),
- original_update_refname(u),
+ "is at %s but expected %s"),
+ ref_update_original_update_refname(u),
oid_to_hex(¤t_oid),
oid_to_hex(&u->old_oid));
ret = -1;
--
2.43.GIT
^ permalink raw reply related [flat|nested] 194+ messages in thread
* [PATCH v7 5/8] refs: add support for transactional symref updates
2024-05-07 6:00 ` [PATCH v7 0/8] " Karthik Nayak
` (3 preceding siblings ...)
2024-05-07 6:00 ` [PATCH v7 4/8] refs: move `original_update_refname` to 'refs.c' Karthik Nayak
@ 2024-05-07 6:00 ` Karthik Nayak
2024-05-07 6:00 ` [PATCH v7 6/8] refs: use transaction in `refs_create_symref()` Karthik Nayak
` (4 subsequent siblings)
9 siblings, 0 replies; 194+ messages in thread
From: Karthik Nayak @ 2024-05-07 6:00 UTC (permalink / raw)
To: karthik.188; +Cc: christian.couder, git, gitster, ps, phillip.wood123
From: Karthik Nayak <karthik.188@gmail.com>
The reference backends currently support transactional reference
updates. While this is exposed to users via 'git-update-ref' and its
'--stdin' mode, it is also used internally within various commands.
However, we do not support transactional updates of symrefs. This commit
adds support for symrefs in both the 'files' and the 'reftable' backend.
Here, we add and use `ref_update_has_null_new_value()`, a helper
function which is used to check if there is a new_value in a reference
update. The new value could either be a symref target `new_target` or a
OID `new_oid`.
We also add another common function `ref_update_check_old_target` which
will be used to check if the update's old_target corresponds to a
reference's current target.
Now transactional updates (verify, create, delete, update) can be used
for:
- regular refs
- symbolic refs
- conversion of regular to symbolic refs and vice versa
This also allows us to expose this to users via new commands in
'git-update-ref' in the future.
Note that a dangling symref update does not record a new reflog entry,
which is unchanged before and after this commit.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
refs.c | 36 ++++++++++++-
refs/files-backend.c | 113 ++++++++++++++++++++++++++++++++--------
refs/refs-internal.h | 16 ++++++
refs/reftable-backend.c | 71 +++++++++++++++++++------
4 files changed, 196 insertions(+), 40 deletions(-)
diff --git a/refs.c b/refs.c
index d65e4cc41e..f3d6b3b8e3 100644
--- a/refs.c
+++ b/refs.c
@@ -1257,6 +1257,8 @@ void ref_transaction_free(struct ref_transaction *transaction)
for (i = 0; i < transaction->nr; i++) {
free(transaction->updates[i]->msg);
+ free((char *)transaction->updates[i]->new_target);
+ free((char *)transaction->updates[i]->old_target);
free(transaction->updates[i]);
}
free(transaction->updates);
@@ -1287,10 +1289,13 @@ struct ref_update *ref_transaction_add_update(
update->flags = flags;
- if (flags & REF_HAVE_NEW)
+ update->new_target = xstrdup_or_null(new_target);
+ update->old_target = xstrdup_or_null(old_target);
+ if ((flags & REF_HAVE_NEW) && new_oid)
oidcpy(&update->new_oid, new_oid);
- if (flags & REF_HAVE_OLD)
+ if ((flags & REF_HAVE_OLD) && old_oid)
oidcpy(&update->old_oid, old_oid);
+
update->msg = normalize_reflog_message(msg);
return update;
}
@@ -1333,6 +1338,7 @@ int ref_transaction_update(struct ref_transaction *transaction,
flags &= REF_TRANSACTION_UPDATE_ALLOWED_FLAGS;
flags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0);
+ flags |= (new_target ? REF_HAVE_NEW : 0) | (old_target ? REF_HAVE_OLD : 0);
ref_transaction_add_update(transaction, refname, flags,
new_oid, old_oid, new_target,
@@ -2839,3 +2845,29 @@ const char *ref_update_original_update_refname(struct ref_update *update)
return update->refname;
}
+int ref_update_has_null_new_value(struct ref_update *update)
+{
+ return !update->new_target && is_null_oid(&update->new_oid);
+}
+
+int ref_update_check_old_target(const char *referent, struct ref_update *update,
+ struct strbuf *err)
+{
+ if (!update->old_target)
+ BUG("called without old_target set");
+
+ if (!strcmp(referent, update->old_target))
+ return 0;
+
+ if (!strcmp(referent, ""))
+ strbuf_addf(err, "verifying symref target: '%s': "
+ "reference is missing but expected %s",
+ ref_update_original_update_refname(update),
+ update->old_target);
+ else
+ strbuf_addf(err, "verifying symref target: '%s': "
+ "is at %s but expected %s",
+ ref_update_original_update_refname(update),
+ referent, update->old_target);
+ return -1;
+}
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 64d2a50e97..1d4650b7cb 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -2393,8 +2393,9 @@ static int split_symref_update(struct ref_update *update,
new_update = ref_transaction_add_update(
transaction, referent, new_flags,
- &update->new_oid, &update->old_oid,
- NULL, NULL, update->msg);
+ update->new_target ? NULL : &update->new_oid,
+ update->old_target ? NULL : &update->old_oid,
+ update->new_target, update->old_target, update->msg);
new_update->parent_update = update;
@@ -2482,7 +2483,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,
files_assert_main_repository(refs, "lock_ref_for_update");
- if ((update->flags & REF_HAVE_NEW) && is_null_oid(&update->new_oid))
+ if ((update->flags & REF_HAVE_NEW) && ref_update_has_null_new_value(update))
update->flags |= REF_DELETING;
if (head_ref) {
@@ -2525,7 +2526,14 @@ static int lock_ref_for_update(struct files_ref_store *refs,
ret = TRANSACTION_GENERIC_ERROR;
goto out;
}
- } else if (check_old_oid(update, &lock->old_oid, err)) {
+ }
+
+ if (update->old_target) {
+ if (ref_update_check_old_target(referent.buf, update, err)) {
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto out;
+ }
+ } else if (check_old_oid(update, &lock->old_oid, err)) {
ret = TRANSACTION_GENERIC_ERROR;
goto out;
}
@@ -2546,7 +2554,17 @@ static int lock_ref_for_update(struct files_ref_store *refs,
} else {
struct ref_update *parent_update;
- if (check_old_oid(update, &lock->old_oid, err)) {
+ /*
+ * Even if the ref is a regular ref, if `old_target` is set, we
+ * check the referent value. Ideally `old_target` should only
+ * be set for symrefs, but we're strict about its usage.
+ */
+ if (update->old_target) {
+ if (ref_update_check_old_target(referent.buf, update, err)) {
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto out;
+ }
+ } else if (check_old_oid(update, &lock->old_oid, err)) {
ret = TRANSACTION_GENERIC_ERROR;
goto out;
}
@@ -2564,9 +2582,28 @@ static int lock_ref_for_update(struct files_ref_store *refs,
}
}
- if ((update->flags & REF_HAVE_NEW) &&
- !(update->flags & REF_DELETING) &&
- !(update->flags & REF_LOG_ONLY)) {
+ if (update->new_target && !(update->flags & REF_LOG_ONLY)) {
+ if (create_symref_lock(refs, lock, update->refname,
+ update->new_target, err)) {
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto out;
+ }
+
+ if (close_ref_gently(lock)) {
+ strbuf_addf(err, "couldn't close '%s.lock'",
+ update->refname);
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto out;
+ }
+
+ /*
+ * Once we have created the symref lock, the commit
+ * phase of the transaction only needs to commit the lock.
+ */
+ update->flags |= REF_NEEDS_COMMIT;
+ } else if ((update->flags & REF_HAVE_NEW) &&
+ !(update->flags & REF_DELETING) &&
+ !(update->flags & REF_LOG_ONLY)) {
if (!(update->type & REF_ISSYMREF) &&
oideq(&lock->old_oid, &update->new_oid)) {
/*
@@ -2829,6 +2866,43 @@ static int files_transaction_prepare(struct ref_store *ref_store,
return ret;
}
+static int parse_and_write_reflog(struct files_ref_store *refs,
+ struct ref_update *update,
+ struct ref_lock *lock,
+ struct strbuf *err)
+{
+ if (update->new_target) {
+ /*
+ * We want to get the resolved OID for the target, to ensure
+ * that the correct value is added to the reflog.
+ */
+ if (!refs_resolve_ref_unsafe(&refs->base, update->new_target,
+ RESOLVE_REF_READING,
+ &update->new_oid, NULL)) {
+ /*
+ * TODO: currently we skip creating reflogs for dangling
+ * symref updates. It would be nice to capture this as
+ * zero oid updates however.
+ */
+ return 0;
+ }
+ }
+
+ if (files_log_ref_write(refs, lock->ref_name, &lock->old_oid,
+ &update->new_oid, update->msg, update->flags, err)) {
+ char *old_msg = strbuf_detach(err, NULL);
+
+ strbuf_addf(err, "cannot update the ref '%s': %s",
+ lock->ref_name, old_msg);
+ free(old_msg);
+ unlock_ref(lock);
+ update->backend_data = NULL;
+ return -1;
+ }
+
+ return 0;
+}
+
static int files_transaction_finish(struct ref_store *ref_store,
struct ref_transaction *transaction,
struct strbuf *err)
@@ -2859,23 +2933,20 @@ static int files_transaction_finish(struct ref_store *ref_store,
if (update->flags & REF_NEEDS_COMMIT ||
update->flags & REF_LOG_ONLY) {
- if (files_log_ref_write(refs,
- lock->ref_name,
- &lock->old_oid,
- &update->new_oid,
- update->msg, update->flags,
- err)) {
- char *old_msg = strbuf_detach(err, NULL);
-
- strbuf_addf(err, "cannot update the ref '%s': %s",
- lock->ref_name, old_msg);
- free(old_msg);
- unlock_ref(lock);
- update->backend_data = NULL;
+ if (parse_and_write_reflog(refs, update, lock, err)) {
ret = TRANSACTION_GENERIC_ERROR;
goto cleanup;
}
}
+
+ /*
+ * We try creating a symlink, if that succeeds we continue to the
+ * next update. If not, we try and create a regular symref.
+ */
+ if (update->new_target && prefer_symlink_refs)
+ if (!create_ref_symlink(lock, update->new_target))
+ continue;
+
if (update->flags & REF_NEEDS_COMMIT) {
clear_loose_ref_cache(refs);
if (commit_ref(lock)) {
diff --git a/refs/refs-internal.h b/refs/refs-internal.h
index 617b93a6c8..819157256e 100644
--- a/refs/refs-internal.h
+++ b/refs/refs-internal.h
@@ -754,4 +754,20 @@ struct ref_store *maybe_debug_wrap_ref_store(const char *gitdir, struct ref_stor
*/
const char *ref_update_original_update_refname(struct ref_update *update);
+/*
+ * Helper function to check if the new value is null, this
+ * takes into consideration that the update could be a regular
+ * ref or a symbolic ref.
+ */
+int ref_update_has_null_new_value(struct ref_update *update);
+
+/*
+ * Check whether the old_target values stored in update are consistent
+ * with the referent, which is the symbolic reference's current value.
+ * If everything is OK, return 0; otherwise, write an error message to
+ * err and return -1.
+ */
+int ref_update_check_old_target(const char *referent, struct ref_update *update,
+ struct strbuf *err);
+
#endif /* REFS_REFS_INTERNAL_H */
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index 0988f59ed4..4817dc2f0b 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -887,7 +887,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
* There is no need to write the reference deletion
* when the reference in question doesn't exist.
*/
- if (u->flags & REF_HAVE_NEW && !is_null_oid(&u->new_oid)) {
+ if ((u->flags & REF_HAVE_NEW) && !ref_update_has_null_new_value(u)) {
ret = queue_transaction_update(refs, tx_data, u,
¤t_oid, err);
if (ret)
@@ -938,8 +938,10 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
* intertwined with the locking in files-backend.c.
*/
new_update = ref_transaction_add_update(
- transaction, referent.buf, new_flags,
- &u->new_oid, &u->old_oid, NULL, NULL, u->msg);
+ transaction, referent.buf, new_flags,
+ &u->new_oid, &u->old_oid, u->new_target,
+ u->old_target, u->msg);
+
new_update->parent_update = u;
/*
@@ -969,7 +971,12 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
* individual refs. But the error messages match what the files
* backend returns, which keeps our tests happy.
*/
- if (u->flags & REF_HAVE_OLD && !oideq(¤t_oid, &u->old_oid)) {
+ if (u->old_target) {
+ if (ref_update_check_old_target(referent.buf, u, err)) {
+ ret = -1;
+ goto done;
+ }
+ } else if ((u->flags & REF_HAVE_OLD) && !oideq(¤t_oid, &u->old_oid)) {
if (is_null_oid(&u->old_oid))
strbuf_addf(err, _("cannot lock ref '%s': "
"reference already exists"),
@@ -1080,7 +1087,9 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data
* - `core.logAllRefUpdates` tells us to create the reflog for
* the given ref.
*/
- if (u->flags & REF_HAVE_NEW && !(u->type & REF_ISSYMREF) && is_null_oid(&u->new_oid)) {
+ if ((u->flags & REF_HAVE_NEW) &&
+ !(u->type & REF_ISSYMREF) &&
+ ref_update_has_null_new_value(u)) {
struct reftable_log_record log = {0};
struct reftable_iterator it = {0};
@@ -1121,24 +1130,52 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data
(u->flags & REF_FORCE_CREATE_REFLOG ||
should_write_log(&arg->refs->base, u->refname))) {
struct reftable_log_record *log;
+ int create_reflog = 1;
+
+ if (u->new_target) {
+ if (!refs_resolve_ref_unsafe(&arg->refs->base, u->new_target,
+ RESOLVE_REF_READING, &u->new_oid, NULL)) {
+ /*
+ * TODO: currently we skip creating reflogs for dangling
+ * symref updates. It would be nice to capture this as
+ * zero oid updates however.
+ */
+ create_reflog = 0;
+ }
+ }
- ALLOC_GROW(logs, logs_nr + 1, logs_alloc);
- log = &logs[logs_nr++];
- memset(log, 0, sizeof(*log));
-
- fill_reftable_log_record(log, &committer_ident);
- log->update_index = ts;
- log->refname = xstrdup(u->refname);
- memcpy(log->value.update.new_hash, u->new_oid.hash, GIT_MAX_RAWSZ);
- memcpy(log->value.update.old_hash, tx_update->current_oid.hash, GIT_MAX_RAWSZ);
- log->value.update.message =
- xstrndup(u->msg, arg->refs->write_options.block_size / 2);
+ if (create_reflog) {
+ ALLOC_GROW(logs, logs_nr + 1, logs_alloc);
+ log = &logs[logs_nr++];
+ memset(log, 0, sizeof(*log));
+
+ fill_reftable_log_record(log, &committer_ident);
+ log->update_index = ts;
+ log->refname = xstrdup(u->refname);
+ memcpy(log->value.update.new_hash,
+ u->new_oid.hash, GIT_MAX_RAWSZ);
+ memcpy(log->value.update.old_hash,
+ tx_update->current_oid.hash, GIT_MAX_RAWSZ);
+ log->value.update.message =
+ xstrndup(u->msg, arg->refs->write_options.block_size / 2);
+ }
}
if (u->flags & REF_LOG_ONLY)
continue;
- if (u->flags & REF_HAVE_NEW && is_null_oid(&u->new_oid)) {
+ if (u->new_target) {
+ struct reftable_ref_record ref = {
+ .refname = (char *)u->refname,
+ .value_type = REFTABLE_REF_SYMREF,
+ .value.symref = (char *)u->new_target,
+ .update_index = ts,
+ };
+
+ ret = reftable_writer_add_ref(writer, &ref);
+ if (ret < 0)
+ goto done;
+ } else if ((u->flags & REF_HAVE_NEW) && ref_update_has_null_new_value(u)) {
struct reftable_ref_record ref = {
.refname = (char *)u->refname,
.update_index = ts,
--
2.43.GIT
^ permalink raw reply related [flat|nested] 194+ messages in thread
* [PATCH v7 6/8] refs: use transaction in `refs_create_symref()`
2024-05-07 6:00 ` [PATCH v7 0/8] " Karthik Nayak
` (4 preceding siblings ...)
2024-05-07 6:00 ` [PATCH v7 5/8] refs: add support for transactional symref updates Karthik Nayak
@ 2024-05-07 6:00 ` Karthik Nayak
2024-05-07 6:00 ` [PATCH v7 7/8] refs: rename `refs_create_symref()` to `refs_update_symref()` Karthik Nayak
` (3 subsequent siblings)
9 siblings, 0 replies; 194+ messages in thread
From: Karthik Nayak @ 2024-05-07 6:00 UTC (permalink / raw)
To: karthik.188; +Cc: christian.couder, git, gitster, ps, phillip.wood123
From: Karthik Nayak <karthik.188@gmail.com>
The `refs_create_symref()` function updates a symref to a given new
target. To do this, it uses a ref-backend specific function
`create_symref()`.
In the previous commits, we introduced symref support in transactions.
This means we can now use transactions to perform symref updates and
don't have to resort to `create_symref()`. Doing this allows us to
remove and cleanup `create_symref()`, which we will do in the following
commit.
Modify the expected error message for a test in
't/t0610-reftable-basics.sh', since the error is now thrown from
'refs.c'. This is because in transactional updates, F/D conflicts are
caught before we're in the reference backend.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
refs.c | 24 +++++++++++++++++-------
t/t1416-ref-transaction-hooks.sh | 23 +++++++++++++++++++++++
2 files changed, 40 insertions(+), 7 deletions(-)
diff --git a/refs.c b/refs.c
index f3d6b3b8e3..7a693dbcfb 100644
--- a/refs.c
+++ b/refs.c
@@ -2305,14 +2305,24 @@ int refs_create_symref(struct ref_store *refs,
const char *refs_heads_master,
const char *logmsg)
{
- char *msg;
- int retval;
+ struct ref_transaction *transaction;
+ struct strbuf err = STRBUF_INIT;
+ int ret = 0;
- msg = normalize_reflog_message(logmsg);
- retval = refs->be->create_symref(refs, ref_target, refs_heads_master,
- msg);
- free(msg);
- return retval;
+ transaction = ref_store_transaction_begin(refs, &err);
+ if (!transaction ||
+ ref_transaction_update(transaction, ref_target, NULL, NULL,
+ refs_heads_master, NULL, REF_NO_DEREF,
+ logmsg, &err) ||
+ ref_transaction_commit(transaction, &err)) {
+ ret = error("%s", err.buf);
+ }
+
+ strbuf_release(&err);
+ if (transaction)
+ ref_transaction_free(transaction);
+
+ return ret;
}
int create_symref(const char *ref_target, const char *refs_heads_master,
diff --git a/t/t1416-ref-transaction-hooks.sh b/t/t1416-ref-transaction-hooks.sh
index 2092488090..067fd57290 100755
--- a/t/t1416-ref-transaction-hooks.sh
+++ b/t/t1416-ref-transaction-hooks.sh
@@ -134,4 +134,27 @@ test_expect_success 'interleaving hook calls succeed' '
test_cmp expect target-repo.git/actual
'
+test_expect_success 'hook captures git-symbolic-ref updates' '
+ test_when_finished "rm actual" &&
+
+ test_hook reference-transaction <<-\EOF &&
+ echo "$*" >>actual
+ while read -r line
+ do
+ printf "%s\n" "$line"
+ done >>actual
+ EOF
+
+ git symbolic-ref refs/heads/symref refs/heads/main &&
+
+ cat >expect <<-EOF &&
+ prepared
+ $ZERO_OID ref:refs/heads/main refs/heads/symref
+ committed
+ $ZERO_OID ref:refs/heads/main refs/heads/symref
+ EOF
+
+ test_cmp expect actual
+'
+
test_done
--
2.43.GIT
^ permalink raw reply related [flat|nested] 194+ messages in thread
* [PATCH v7 7/8] refs: rename `refs_create_symref()` to `refs_update_symref()`
2024-05-07 6:00 ` [PATCH v7 0/8] " Karthik Nayak
` (5 preceding siblings ...)
2024-05-07 6:00 ` [PATCH v7 6/8] refs: use transaction in `refs_create_symref()` Karthik Nayak
@ 2024-05-07 6:00 ` Karthik Nayak
2024-05-07 6:00 ` [PATCH v7 8/8] refs: remove `create_symref` and associated dead code Karthik Nayak
` (2 subsequent siblings)
9 siblings, 0 replies; 194+ messages in thread
From: Karthik Nayak @ 2024-05-07 6:00 UTC (permalink / raw)
To: karthik.188; +Cc: christian.couder, git, gitster, ps, phillip.wood123
From: Karthik Nayak <karthik.188@gmail.com>
The `refs_create_symref()` function is used to update/create a symref.
But it doesn't check the old target of the symref, if existing. It force
updates the symref. In this regard, the name `refs_create_symref()` is a
bit misleading. So let's rename it to `refs_update_symref()`. This is
akin to how 'git-update-ref(1)' also allows us to create apart from
update.
While we're here, rename the arguments in the function to clarify what
they actually signify and reduce confusion.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
builtin/branch.c | 2 +-
builtin/worktree.c | 2 +-
refs.c | 12 +++++-------
refs.h | 2 +-
t/helper/test-ref-store.c | 2 +-
5 files changed, 9 insertions(+), 11 deletions(-)
diff --git a/builtin/branch.c b/builtin/branch.c
index dd3e3a7dc0..4491f7a20c 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -555,7 +555,7 @@ static int replace_each_worktree_head_symref(struct worktree **worktrees,
continue;
refs = get_worktree_ref_store(worktrees[i]);
- if (refs_create_symref(refs, "HEAD", newref, logmsg))
+ if (refs_update_symref(refs, "HEAD", newref, logmsg))
ret = error(_("HEAD of working tree %s is not updated"),
worktrees[i]->path);
}
diff --git a/builtin/worktree.c b/builtin/worktree.c
index 7c6c72536b..480202c517 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -517,7 +517,7 @@ static int add_worktree(const char *path, const char *refname,
ret = refs_update_ref(wt_refs, NULL, "HEAD", &commit->object.oid,
NULL, 0, UPDATE_REFS_MSG_ON_ERR);
else
- ret = refs_create_symref(wt_refs, "HEAD", symref.buf, NULL);
+ ret = refs_update_symref(wt_refs, "HEAD", symref.buf, NULL);
if (ret)
goto done;
diff --git a/refs.c b/refs.c
index 7a693dbcfb..85f53a2cbe 100644
--- a/refs.c
+++ b/refs.c
@@ -2300,10 +2300,8 @@ int peel_iterated_oid(const struct object_id *base, struct object_id *peeled)
return peel_object(base, peeled) ? -1 : 0;
}
-int refs_create_symref(struct ref_store *refs,
- const char *ref_target,
- const char *refs_heads_master,
- const char *logmsg)
+int refs_update_symref(struct ref_store *refs, const char *ref,
+ const char *target, const char *logmsg)
{
struct ref_transaction *transaction;
struct strbuf err = STRBUF_INIT;
@@ -2311,8 +2309,8 @@ int refs_create_symref(struct ref_store *refs,
transaction = ref_store_transaction_begin(refs, &err);
if (!transaction ||
- ref_transaction_update(transaction, ref_target, NULL, NULL,
- refs_heads_master, NULL, REF_NO_DEREF,
+ ref_transaction_update(transaction, ref, NULL, NULL,
+ target, NULL, REF_NO_DEREF,
logmsg, &err) ||
ref_transaction_commit(transaction, &err)) {
ret = error("%s", err.buf);
@@ -2328,7 +2326,7 @@ int refs_create_symref(struct ref_store *refs,
int create_symref(const char *ref_target, const char *refs_heads_master,
const char *logmsg)
{
- return refs_create_symref(get_main_ref_store(the_repository), ref_target,
+ return refs_update_symref(get_main_ref_store(the_repository), ref_target,
refs_heads_master, logmsg);
}
diff --git a/refs.h b/refs.h
index 9abbfd35a2..7c69e8cc0d 100644
--- a/refs.h
+++ b/refs.h
@@ -606,7 +606,7 @@ int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,
int copy_existing_ref(const char *oldref, const char *newref,
const char *logmsg);
-int refs_create_symref(struct ref_store *refs, const char *refname,
+int refs_update_symref(struct ref_store *refs, const char *refname,
const char *target, const char *logmsg);
int create_symref(const char *refname, const char *target, const char *logmsg);
diff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c
index 82bbf6e2e6..4651e4ced7 100644
--- a/t/helper/test-ref-store.c
+++ b/t/helper/test-ref-store.c
@@ -118,7 +118,7 @@ static int cmd_create_symref(struct ref_store *refs, const char **argv)
const char *target = notnull(*argv++, "target");
const char *logmsg = *argv++;
- return refs_create_symref(refs, refname, target, logmsg);
+ return refs_update_symref(refs, refname, target, logmsg);
}
static struct flag_definition transaction_flags[] = {
--
2.43.GIT
^ permalink raw reply related [flat|nested] 194+ messages in thread
* [PATCH v7 8/8] refs: remove `create_symref` and associated dead code
2024-05-07 6:00 ` [PATCH v7 0/8] " Karthik Nayak
` (6 preceding siblings ...)
2024-05-07 6:00 ` [PATCH v7 7/8] refs: rename `refs_create_symref()` to `refs_update_symref()` Karthik Nayak
@ 2024-05-07 6:00 ` Karthik Nayak
2024-05-07 6:25 ` [PATCH v7 0/8] refs: add support for transactional symref updates Junio C Hamano
2024-05-07 12:58 ` [PATCH v8 " Karthik Nayak
9 siblings, 0 replies; 194+ messages in thread
From: Karthik Nayak @ 2024-05-07 6:00 UTC (permalink / raw)
To: karthik.188; +Cc: christian.couder, git, gitster, ps, phillip.wood123
From: Karthik Nayak <karthik.188@gmail.com>
In the previous commits, we converted `refs_create_symref()` to utilize
transactions to perform symref updates. Earlier `refs_create_symref()`
used `create_symref()` to do the same.
We can now remove `create_symref()` and any code associated with it
which is no longer used. We remove `create_symref()` code from all the
reference backends and also remove it entirely from the `ref_storage_be`
struct.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
refs/debug.c | 13 -----
refs/files-backend.c | 67 -------------------------
refs/packed-backend.c | 1 -
refs/refs-internal.h | 5 --
refs/reftable-backend.c | 105 ----------------------------------------
5 files changed, 191 deletions(-)
diff --git a/refs/debug.c b/refs/debug.c
index c7531b17f0..8be316bb67 100644
--- a/refs/debug.c
+++ b/refs/debug.c
@@ -131,18 +131,6 @@ static int debug_pack_refs(struct ref_store *ref_store, struct pack_refs_opts *o
return res;
}
-static int debug_create_symref(struct ref_store *ref_store,
- const char *ref_name, const char *target,
- const char *logmsg)
-{
- struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store;
- int res = drefs->refs->be->create_symref(drefs->refs, ref_name, target,
- logmsg);
- trace_printf_key(&trace_refs, "create_symref: %s -> %s \"%s\": %d\n", ref_name,
- target, logmsg, res);
- return res;
-}
-
static int debug_rename_ref(struct ref_store *ref_store, const char *oldref,
const char *newref, const char *logmsg)
{
@@ -441,7 +429,6 @@ struct ref_storage_be refs_be_debug = {
.initial_transaction_commit = debug_initial_transaction_commit,
.pack_refs = debug_pack_refs,
- .create_symref = debug_create_symref,
.rename_ref = debug_rename_ref,
.copy_ref = debug_copy_ref,
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 1d4650b7cb..dc0490f0db 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -1902,23 +1902,6 @@ static int create_ref_symlink(struct ref_lock *lock, const char *target)
return ret;
}
-static void update_symref_reflog(struct files_ref_store *refs,
- struct ref_lock *lock, const char *refname,
- const char *target, const char *logmsg)
-{
- struct strbuf err = STRBUF_INIT;
- struct object_id new_oid;
-
- if (logmsg &&
- refs_resolve_ref_unsafe(&refs->base, target,
- RESOLVE_REF_READING, &new_oid, NULL) &&
- files_log_ref_write(refs, refname, &lock->old_oid,
- &new_oid, logmsg, 0, &err)) {
- error("%s", err.buf);
- strbuf_release(&err);
- }
-}
-
static int create_symref_lock(struct files_ref_store *refs,
struct ref_lock *lock, const char *refname,
const char *target, struct strbuf *err)
@@ -1938,55 +1921,6 @@ static int create_symref_lock(struct files_ref_store *refs,
return 0;
}
-static int create_and_commit_symref(struct files_ref_store *refs,
- struct ref_lock *lock, const char *refname,
- const char *target, const char *logmsg)
-{
- struct strbuf err = STRBUF_INIT;
- int ret;
-
- if (prefer_symlink_refs && !create_ref_symlink(lock, target)) {
- update_symref_reflog(refs, lock, refname, target, logmsg);
- return 0;
- }
-
- ret = create_symref_lock(refs, lock, refname, target, &err);
- if (!ret) {
- update_symref_reflog(refs, lock, refname, target, logmsg);
-
- if (commit_ref(lock) < 0)
- return error("unable to write symref for %s: %s", refname,
- strerror(errno));
- } else {
- return error("%s", err.buf);
- }
-
- return ret;
-}
-
-static int files_create_symref(struct ref_store *ref_store,
- const char *refname, const char *target,
- const char *logmsg)
-{
- struct files_ref_store *refs =
- files_downcast(ref_store, REF_STORE_WRITE, "create_symref");
- struct strbuf err = STRBUF_INIT;
- struct ref_lock *lock;
- int ret;
-
- lock = lock_ref_oid_basic(refs, refname, &err);
- if (!lock) {
- error("%s", err.buf);
- strbuf_release(&err);
- return -1;
- }
-
- ret = create_and_commit_symref(refs, lock, refname, target, logmsg);
-
- unlock_ref(lock);
- return ret;
-}
-
static int files_reflog_exists(struct ref_store *ref_store,
const char *refname)
{
@@ -3373,7 +3307,6 @@ struct ref_storage_be refs_be_files = {
.initial_transaction_commit = files_initial_transaction_commit,
.pack_refs = files_pack_refs,
- .create_symref = files_create_symref,
.rename_ref = files_rename_ref,
.copy_ref = files_copy_ref,
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index 4e826c05ff..a937e7dbfc 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -1714,7 +1714,6 @@ struct ref_storage_be refs_be_packed = {
.initial_transaction_commit = packed_initial_transaction_commit,
.pack_refs = packed_pack_refs,
- .create_symref = NULL,
.rename_ref = NULL,
.copy_ref = NULL,
diff --git a/refs/refs-internal.h b/refs/refs-internal.h
index 819157256e..53a6c5d842 100644
--- a/refs/refs-internal.h
+++ b/refs/refs-internal.h
@@ -566,10 +566,6 @@ typedef int ref_transaction_commit_fn(struct ref_store *refs,
typedef int pack_refs_fn(struct ref_store *ref_store,
struct pack_refs_opts *opts);
-typedef int create_symref_fn(struct ref_store *ref_store,
- const char *ref_target,
- const char *refs_heads_master,
- const char *logmsg);
typedef int rename_ref_fn(struct ref_store *ref_store,
const char *oldref, const char *newref,
const char *logmsg);
@@ -690,7 +686,6 @@ struct ref_storage_be {
ref_transaction_commit_fn *initial_transaction_commit;
pack_refs_fn *pack_refs;
- create_symref_fn *create_symref;
rename_ref_fn *rename_ref;
copy_ref_fn *copy_ref;
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index 4817dc2f0b..d8e06767a4 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -1307,110 +1307,6 @@ struct write_create_symref_arg {
const char *logmsg;
};
-static int write_create_symref_table(struct reftable_writer *writer, void *cb_data)
-{
- struct write_create_symref_arg *create = cb_data;
- uint64_t ts = reftable_stack_next_update_index(create->stack);
- struct reftable_ref_record ref = {
- .refname = (char *)create->refname,
- .value_type = REFTABLE_REF_SYMREF,
- .value.symref = (char *)create->target,
- .update_index = ts,
- };
- struct ident_split committer_ident = {0};
- struct reftable_log_record log = {0};
- struct object_id new_oid;
- struct object_id old_oid;
- const char *committer_info;
- int ret;
-
- reftable_writer_set_limits(writer, ts, ts);
-
- ret = refs_verify_refname_available(&create->refs->base, create->refname,
- NULL, NULL, create->err);
- if (ret < 0)
- return ret;
-
- ret = reftable_writer_add_ref(writer, &ref);
- if (ret)
- return ret;
-
- /*
- * Note that it is important to try and resolve the reference before we
- * write the log entry. This is because `should_write_log()` will munge
- * `core.logAllRefUpdates`, which is undesirable when we create a new
- * repository because it would be written into the config. As HEAD will
- * not resolve for new repositories this ordering will ensure that this
- * never happens.
- */
- if (!create->logmsg ||
- !refs_resolve_ref_unsafe(&create->refs->base, create->target,
- RESOLVE_REF_READING, &new_oid, NULL) ||
- !should_write_log(&create->refs->base, create->refname))
- return 0;
-
- committer_info = git_committer_info(0);
- if (split_ident_line(&committer_ident, committer_info, strlen(committer_info)))
- BUG("failed splitting committer info");
-
- fill_reftable_log_record(&log, &committer_ident);
- log.refname = xstrdup(create->refname);
- log.update_index = ts;
- log.value.update.message = xstrndup(create->logmsg,
- create->refs->write_options.block_size / 2);
- memcpy(log.value.update.new_hash, new_oid.hash, GIT_MAX_RAWSZ);
- if (refs_resolve_ref_unsafe(&create->refs->base, create->refname,
- RESOLVE_REF_READING, &old_oid, NULL))
- memcpy(log.value.update.old_hash, old_oid.hash, GIT_MAX_RAWSZ);
-
- ret = reftable_writer_add_log(writer, &log);
- reftable_log_record_release(&log);
- return ret;
-}
-
-static int reftable_be_create_symref(struct ref_store *ref_store,
- const char *refname,
- const char *target,
- const char *logmsg)
-{
- struct reftable_ref_store *refs =
- reftable_be_downcast(ref_store, REF_STORE_WRITE, "create_symref");
- struct reftable_stack *stack = stack_for(refs, refname, &refname);
- struct strbuf err = STRBUF_INIT;
- struct write_create_symref_arg arg = {
- .refs = refs,
- .stack = stack,
- .refname = refname,
- .target = target,
- .logmsg = logmsg,
- .err = &err,
- };
- int ret;
-
- ret = refs->err;
- if (ret < 0)
- goto done;
-
- ret = reftable_stack_reload(stack);
- if (ret)
- goto done;
-
- ret = reftable_stack_add(stack, &write_create_symref_table, &arg);
-
-done:
- assert(ret != REFTABLE_API_ERROR);
- if (ret) {
- if (err.len)
- error("%s", err.buf);
- else
- error("unable to write symref for %s: %s", refname,
- reftable_error_str(ret));
- }
-
- strbuf_release(&err);
- return ret;
-}
-
struct write_copy_arg {
struct reftable_ref_store *refs;
struct reftable_stack *stack;
@@ -2325,7 +2221,6 @@ struct ref_storage_be refs_be_reftable = {
.initial_transaction_commit = reftable_be_initial_transaction_commit,
.pack_refs = reftable_be_pack_refs,
- .create_symref = reftable_be_create_symref,
.rename_ref = reftable_be_rename_ref,
.copy_ref = reftable_be_copy_ref,
--
2.43.GIT
^ permalink raw reply related [flat|nested] 194+ messages in thread
* Re: [PATCH v7 0/8] refs: add support for transactional symref updates
2024-05-07 6:00 ` [PATCH v7 0/8] " Karthik Nayak
` (7 preceding siblings ...)
2024-05-07 6:00 ` [PATCH v7 8/8] refs: remove `create_symref` and associated dead code Karthik Nayak
@ 2024-05-07 6:25 ` Junio C Hamano
2024-05-07 6:31 ` Junio C Hamano
2024-05-07 12:58 ` [PATCH v8 " Karthik Nayak
9 siblings, 1 reply; 194+ messages in thread
From: Junio C Hamano @ 2024-05-07 6:25 UTC (permalink / raw)
To: Karthik Nayak; +Cc: christian.couder, git, ps, phillip.wood123
Karthik Nayak <karthik.188@gmail.com> writes:
> Junio, I've rebased my patches on top of, not sure if there is something else I
> missed here:
> - ps/ci-python-2-deprecation (2a686e00d2)
> - ps/reftable-write-options (f5545b173b)
> - ps/pseudo-ref-terminology (ad302a68fd)
This is a somewhat puzzling set of topics. You are not touching
ci/lib.sh at all so I do not see the point of including the
ci-python one, for example.
^ permalink raw reply [flat|nested] 194+ messages in thread
* Re: [PATCH v7 0/8] refs: add support for transactional symref updates
2024-05-07 6:25 ` [PATCH v7 0/8] refs: add support for transactional symref updates Junio C Hamano
@ 2024-05-07 6:31 ` Junio C Hamano
0 siblings, 0 replies; 194+ messages in thread
From: Junio C Hamano @ 2024-05-07 6:31 UTC (permalink / raw)
To: Karthik Nayak; +Cc: christian.couder, git, ps, phillip.wood123
Junio C Hamano <gitster@pobox.com> writes:
> Karthik Nayak <karthik.188@gmail.com> writes:
>
>> Junio, I've rebased my patches on top of, not sure if there is something else I
>> missed here:
>> - ps/ci-python-2-deprecation (2a686e00d2)
>> - ps/reftable-write-options (f5545b173b)
>> - ps/pseudo-ref-terminology (ad302a68fd)
>
> This is a somewhat puzzling set of topics. You are not touching
> ci/lib.sh at all so I do not see the point of including the
> ci-python one, for example.
Oh, it is even worse. 2a686e00d2 is not the ci-python thing at all.
It is a random point between 'master' and 'seen' that happens to be
a merge of that topic, so you basically based your series on top of
random collection of many topics that are not even in 'next'. More
than 30 of them.
2a686e00d2 Merge branch 'ps/ci-python-2-deprecation' into seen
cfae85760b Merge branch 'ow/refspec-glossary-update' into seen
0ac6476973 Merge branch 'ps/ci-enable-minimal-fuzzers-at-gitlab' into seen
2e6fe8d39b Merge branch 'jp/tag-trailer' into seen
2954339080 Merge branch 'rs/external-diff-with-exit-code' into seen
b16fe0b6f5 Merge branch 'it/refs-name-conflict' into seen
bdec3a9a17 Merge branch 'ps/refs-without-the-repository' into seen
b30d6b985f Merge branch 'kn/ref-transaction-symref' into seen
33c1562652 Merge branch 'ds/doc-config-reflow' into seen
053736edd8 Merge branch 'ie/config-includeif-hostname' into seen
db453e610c Merge branch 'cw/git-std-lib' into seen
6858402556 Merge branch 'bk/complete-dirname-for-am-and-format-patch' into seen
2ec214595a Merge branch 'bk/complete-send-email' into seen
0810be3417 Merge branch 'jc/rerere-cleanup' into seen
a3d2847041 Merge branch 'ds/send-email-per-message-block' into seen
919b280123 Merge branch 'tb/attr-limits' into jch
27f675fb6d Merge branch 'ps/config-subcommands' into jch
f5545b173b Merge branch 'ps/reftable-write-options' into jch
c86de5db47 Merge branch 'ew/khash-to-khashl' into jch
ef2bdd9348 Merge branch 'jc/test-workaround-broken-mv' into jch
fb794ba19c Merge branch 'jc/rev-parse-fatal-doc' into jch
96aa517d85 Merge branch 'jt/port-ci-whitespace-check-to-gitlab' into jch
941a03e7c9 Merge branch 'jl/git-no-advice' into jch
56c4ee21a1 Merge branch 'ma/win32-unix-domain-socket' into jch
9d2ea80011 Merge branch 'tb/pseudo-merge-reachability-bitmap' into jch
ad302a68fd Merge branch 'ps/pseudo-ref-terminology' into jch
2754234e89 Merge branch 'ds/scalar-reconfigure-all-fix' into jch
c0340717db Merge branch 'pw/rebase-i-error-message' into jch
89b67e5799 Merge branch 'tb/path-filter-fix' into jch
f40942e3c0 Merge branch 'la/hide-trailer-info' into jch
7b725fb75c Merge branch 'js/cmake-with-test-tool' into jch
1ddb41fd10 Merge branch 'js/unit-test-suite-runner' into jch
e95b4f5420 Merge branch 'jc/no-default-attr-tree-in-bare' into jch
ed26589653 ### match next
I'll not look at this round, as I know you know better than building
a series deliberately on top of these random topics and this was an
accidental mistake you'd rather want to correct before asking folks
to take a look at the resulting series.
Thanks.
^ permalink raw reply [flat|nested] 194+ messages in thread
* [PATCH v8 0/8] refs: add support for transactional symref updates
2024-05-07 6:00 ` [PATCH v7 0/8] " Karthik Nayak
` (8 preceding siblings ...)
2024-05-07 6:25 ` [PATCH v7 0/8] refs: add support for transactional symref updates Junio C Hamano
@ 2024-05-07 12:58 ` Karthik Nayak
2024-05-07 12:58 ` [PATCH v8 1/8] refs: accept symref values in `ref_transaction_update()` Karthik Nayak
` (9 more replies)
9 siblings, 10 replies; 194+ messages in thread
From: Karthik Nayak @ 2024-05-07 12:58 UTC (permalink / raw)
To: karthik.188; +Cc: git, gitster, ps, phillip.wood123
From: Karthik Nayak <karthik.188@gmail.com>
The patch series takes over from the existing patch series, wherein we
introduced symref-* commands to git-update-ref. Since there was a ton of
discussions on the UX of the patch series and its application, I thought it
would be best to shorten the series and split it into multiple smaller series.
This series adds transactional support for symrefs in the reference db. Then
we switch refs_create_symref() to start using transactions for symref updates.
This allows us to deprecate the create_symref code in the ref_storage_be
interface and remove all associated code which is no longer used.
The split was primarily done so we can merge the non-user facing parts of the
previous series. While pertaining the user facing components into another set
of patches wherein deeper discussion on the UX can be held without worrying
about the internal implementation. Also by using this new functionality in a
pre-existing command, we can leverage the existing tests to catch any
inconsistencies. One of which was how 'git-symbolic-ref' doesn't add reflog for
dangling symrefs, which I've modified my patch to do the same.
We also modify the reference transaction hook to support symrefs. For any symref
update the reference transaction hook will output the value with a 'ref:' prefix.
Previous versions:
V1: https://lore.kernel.org/git/20240330224623.579457-1-knayak@gitlab.com
V2: https://lore.kernel.org/git/20240412095908.1134387-1-knayak@gitlab.com
V3: https://lore.kernel.org/git/20240423212818.574123-1-knayak@gitlab.com
V4: https://lore.kernel.org/r/20240426152449.228860-1-knayak@gitlab.com
V5: https://lore.kernel.org/r/20240501202229.2695774-1-knayak@gitlab.com
V6: https://lore.kernel.org/r/20240503124115.252413-1-knayak@gitlab.com
V7: https://lore.kernel.org/r/20240507060035.28602-1-knayak@gitlab.com
Thanks for all the reviews!
Changes since v7:
* I had rebased v7 on next. I've rebased v8 on master. That's the only difference
between the two versions.
Junio, this might cause conflicts when merging, I think you resolved them for
v6 and hope its the same now. Let me know if I can help otherwise somehow.
Changes since v6:
* Made the check for old/new oid & target more stricter by removing
the exception for null oid's.
* Extracted `ref_update_check_old_target` and `original_update_refname`
to refs.c
* ^This allowed us to generalize the error messages for non-matching
old_target values between files and reftable backend.
* Better line wrapping in code.
* Fixed some grammar in commit messages.
Range diff against v6:
1: a354190905 ! 1: defc1b3521 refs: accept symref values in `ref_transaction_update()`
@@ refs.c: struct ref_update *ref_transaction_add_update(
if (transaction->state != REF_TRANSACTION_OPEN)
BUG("update called for transaction that is not open");
-+ if (old_oid && !is_null_oid(old_oid) && old_target)
++ if (old_oid && old_target)
+ BUG("only one of old_oid and old_target should be non NULL");
-+ if (new_oid && !is_null_oid(new_oid) && new_target)
++ if (new_oid && new_target)
+ BUG("only one of new_oid and new_target should be non NULL");
+
FLEX_ALLOC_STR(update, refname, refname);
2: 0d9c5b9804 = 2: 54bb78a27c files-backend: extract out `create_symref_lock()`
3: e0219ffd31 = 3: c16b7c5da0 refs: support symrefs in 'reference-transaction' hook
-: ---------- > 4: 2d268f12cc refs: move `original_update_refname` to 'refs.c'
4: b22c59c722 ! 5: 7db3a2245f refs: add support for transactional symref updates
@@ Commit message
updates. While this is exposed to users via 'git-update-ref' and its
'--stdin' mode, it is also used internally within various commands.
- However, we never supported transactional updates of symrefs. Let's add
- support for symrefs in both the 'files' and the 'reftable' backend.
+ However, we do not support transactional updates of symrefs. This commit
+ adds support for symrefs in both the 'files' and the 'reftable' backend.
Here, we add and use `ref_update_has_null_new_value()`, a helper
function which is used to check if there is a new_value in a reference
update. The new value could either be a symref target `new_target` or a
OID `new_oid`.
- With this, now transactional updates (verify, create, delete, update)
- can be used for:
+ We also add another common function `ref_update_check_old_target` which
+ will be used to check if the update's old_target corresponds to a
+ reference's current target.
+
+ Now transactional updates (verify, create, delete, update) can be used
+ for:
- regular refs
- symbolic refs
- conversion of regular to symbolic refs and vice versa
@@ refs.c: int ref_transaction_update(struct ref_transaction *transaction,
ref_transaction_add_update(transaction, refname, flags,
new_oid, old_oid, new_target,
-@@ refs.c: int copy_existing_ref(const char *oldref, const char *newref, const char *logmsg
- {
- return refs_copy_existing_ref(get_main_ref_store(the_repository), oldref, newref, logmsg);
+@@ refs.c: const char *ref_update_original_update_refname(struct ref_update *update)
+ return update->refname;
}
-+
+
+int ref_update_has_null_new_value(struct ref_update *update)
+{
+ return !update->new_target && is_null_oid(&update->new_oid);
+}
-
- ## refs/files-backend.c ##
-@@ refs/files-backend.c: static const char *original_update_refname(struct ref_update *update)
- return update->refname;
- }
-
-+/*
-+ * Check whether the old_target values stored in update are consistent
-+ * with current_target, which is the symbolic reference's current value.
-+ * If everything is OK, return 0; otherwise, write an error message to
-+ * err and return -1.
-+ */
-+static int check_old_target(struct ref_update *update,
-+ const char *current_target,
-+ struct strbuf *err)
++
++int ref_update_check_old_target(const char *referent, struct ref_update *update,
++ struct strbuf *err)
+{
+ if (!update->old_target)
+ BUG("called without old_target set");
+
-+ if (!strcmp(update->old_target, current_target))
++ if (!strcmp(referent, update->old_target))
+ return 0;
+
-+ if (!strcmp(current_target, ""))
-+ strbuf_addf(err, "cannot lock ref '%s': "
++ if (!strcmp(referent, ""))
++ strbuf_addf(err, "verifying symref target: '%s': "
+ "reference is missing but expected %s",
-+ original_update_refname(update),
++ ref_update_original_update_refname(update),
+ update->old_target);
+ else
-+ strbuf_addf(err, "cannot lock ref '%s': "
++ strbuf_addf(err, "verifying symref target: '%s': "
+ "is at %s but expected %s",
-+ original_update_refname(update),
-+ current_target, update->old_target);
-+
++ ref_update_original_update_refname(update),
++ referent, update->old_target);
+ return -1;
+}
-+
- /*
- * Check whether the REF_HAVE_OLD and old_oid values stored in update
- * are consistent with oid, which is the reference's current value. If
+
+ ## refs/files-backend.c ##
+@@ refs/files-backend.c: static int split_symref_update(struct ref_update *update,
+
+ new_update = ref_transaction_add_update(
+ transaction, referent, new_flags,
+- &update->new_oid, &update->old_oid,
+- NULL, NULL, update->msg);
++ update->new_target ? NULL : &update->new_oid,
++ update->old_target ? NULL : &update->old_oid,
++ update->new_target, update->old_target, update->msg);
+
+ new_update->parent_update = update;
+
@@ refs/files-backend.c: static int lock_ref_for_update(struct files_ref_store *refs,
files_assert_main_repository(refs, "lock_ref_for_update");
@@ refs/files-backend.c: static int lock_ref_for_update(struct files_ref_store *ref
+ }
+
+ if (update->old_target) {
-+ if (check_old_target(update, referent.buf, err)) {
++ if (ref_update_check_old_target(referent.buf, update, err)) {
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto out;
+ }
@@ refs/files-backend.c: static int lock_ref_for_update(struct files_ref_store *ref
+ * be set for symrefs, but we're strict about its usage.
+ */
+ if (update->old_target) {
-+ if (check_old_target(update, referent.buf, err)) {
++ if (ref_update_check_old_target(referent.buf, update, err)) {
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto out;
+ }
@@ refs/files-backend.c: static int lock_ref_for_update(struct files_ref_store *ref
- !(update->flags & REF_DELETING) &&
- !(update->flags & REF_LOG_ONLY)) {
+ if (update->new_target && !(update->flags & REF_LOG_ONLY)) {
-+ if (create_symref_lock(refs, lock, update->refname, update->new_target, err)) {
++ if (create_symref_lock(refs, lock, update->refname,
++ update->new_target, err)) {
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto out;
+ }
@@ refs/files-backend.c: static int files_transaction_finish(struct ref_store *ref_
if (commit_ref(lock)) {
## refs/refs-internal.h ##
-@@ refs/refs-internal.h: void base_ref_store_init(struct ref_store *refs, struct repository *repo,
+@@ refs/refs-internal.h: struct ref_store *maybe_debug_wrap_ref_store(const char *gitdir, struct ref_stor
*/
- struct ref_store *maybe_debug_wrap_ref_store(const char *gitdir, struct ref_store *store);
+ const char *ref_update_original_update_refname(struct ref_update *update);
+/*
+ * Helper function to check if the new value is null, this
@@ refs/refs-internal.h: void base_ref_store_init(struct ref_store *refs, struct re
+ * ref or a symbolic ref.
+ */
+int ref_update_has_null_new_value(struct ref_update *update);
++
++/*
++ * Check whether the old_target values stored in update are consistent
++ * with the referent, which is the symbolic reference's current value.
++ * If everything is OK, return 0; otherwise, write an error message to
++ * err and return -1.
++ */
++int ref_update_check_old_target(const char *referent, struct ref_update *update,
++ struct strbuf *err);
+
#endif /* REFS_REFS_INTERNAL_H */
@@ refs/reftable-backend.c: static int reftable_be_transaction_prepare(struct ref_s
*/
- if (u->flags & REF_HAVE_OLD && !oideq(¤t_oid, &u->old_oid)) {
+ if (u->old_target) {
-+ if (strcmp(referent.buf, u->old_target)) {
-+ if (!strcmp(referent.buf, ""))
-+ strbuf_addf(err, "verifying symref target: '%s': "
-+ "reference is missing but expected %s",
-+ original_update_refname(u),
-+ u->old_target);
-+ else
-+ strbuf_addf(err, "verifying symref target: '%s': "
-+ "is at %s but expected %s",
-+ original_update_refname(u),
-+ referent.buf, u->old_target);
++ if (ref_update_check_old_target(referent.buf, u, err)) {
+ ret = -1;
+ goto done;
+ }
+ } else if ((u->flags & REF_HAVE_OLD) && !oideq(¤t_oid, &u->old_oid)) {
if (is_null_oid(&u->old_oid))
strbuf_addf(err, _("cannot lock ref '%s': "
- "reference already exists"),
+ "reference already exists"),
@@ refs/reftable-backend.c: static int write_transaction_table(struct reftable_writer *writer, void *cb_data
* - `core.logAllRefUpdates` tells us to create the reflog for
* the given ref.
*/
- if (u->flags & REF_HAVE_NEW && !(u->type & REF_ISSYMREF) && is_null_oid(&u->new_oid)) {
-+ if ((u->flags & REF_HAVE_NEW) && !(u->type & REF_ISSYMREF) && ref_update_has_null_new_value(u)) {
++ if ((u->flags & REF_HAVE_NEW) &&
++ !(u->type & REF_ISSYMREF) &&
++ ref_update_has_null_new_value(u)) {
struct reftable_log_record log = {0};
struct reftable_iterator it = {0};
@@ refs/reftable-backend.c: static int write_transaction_table(struct reftable_writ
+ fill_reftable_log_record(log);
+ log->update_index = ts;
+ log->refname = xstrdup(u->refname);
-+ memcpy(log->value.update.new_hash, u->new_oid.hash, GIT_MAX_RAWSZ);
-+ memcpy(log->value.update.old_hash, tx_update->current_oid.hash, GIT_MAX_RAWSZ);
++ memcpy(log->value.update.new_hash,
++ u->new_oid.hash, GIT_MAX_RAWSZ);
++ memcpy(log->value.update.old_hash,
++ tx_update->current_oid.hash, GIT_MAX_RAWSZ);
+ log->value.update.message =
+ xstrndup(u->msg, arg->refs->write_options.block_size / 2);
+ }
5: 636bf5ce98 = 6: 354ebbe17f refs: use transaction in `refs_create_symref()`
6: 07fb23374f = 7: c8a23b3454 refs: rename `refs_create_symref()` to `refs_update_symref()`
7: 5c05813bcc = 8: fa1b8f445b refs: remove `create_symref` and associated dead code
Karthik Nayak (8):
refs: accept symref values in `ref_transaction_update()`
files-backend: extract out `create_symref_lock()`
refs: support symrefs in 'reference-transaction' hook
refs: move `original_update_refname` to 'refs.c'
refs: add support for transactional symref updates
refs: use transaction in `refs_create_symref()`
refs: rename `refs_create_symref()` to `refs_update_symref()`
refs: remove `create_symref` and associated dead code
Documentation/githooks.txt | 14 ++-
branch.c | 2 +-
builtin/branch.c | 2 +-
builtin/fast-import.c | 5 +-
builtin/fetch.c | 2 +-
builtin/receive-pack.c | 1 +
builtin/replace.c | 2 +-
builtin/tag.c | 1 +
builtin/update-ref.c | 1 +
builtin/worktree.c | 2 +-
refs.c | 119 ++++++++++++++----
refs.h | 20 ++-
refs/debug.c | 13 --
refs/files-backend.c | 208 +++++++++++++++++--------------
refs/packed-backend.c | 1 -
refs/refs-internal.h | 40 +++++-
refs/reftable-backend.c | 183 +++++++++------------------
sequencer.c | 9 +-
t/helper/test-ref-store.c | 2 +-
t/t0610-reftable-basics.sh | 2 +-
t/t1416-ref-transaction-hooks.sh | 23 ++++
walker.c | 2 +-
22 files changed, 374 insertions(+), 280 deletions(-)
--
2.43.GIT
^ permalink raw reply [flat|nested] 194+ messages in thread
* [PATCH v8 1/8] refs: accept symref values in `ref_transaction_update()`
2024-05-07 12:58 ` [PATCH v8 " Karthik Nayak
@ 2024-05-07 12:58 ` Karthik Nayak
2024-05-07 12:58 ` [PATCH v8 2/8] files-backend: extract out `create_symref_lock()` Karthik Nayak
` (8 subsequent siblings)
9 siblings, 0 replies; 194+ messages in thread
From: Karthik Nayak @ 2024-05-07 12:58 UTC (permalink / raw)
To: karthik.188; +Cc: git, gitster, ps, phillip.wood123
From: Karthik Nayak <karthik.188@gmail.com>
The function `ref_transaction_update()` obtains ref information and
flags to create a `ref_update` and add them to the transaction at hand.
To extend symref support in transactions, we need to also accept the
old and new ref targets and process it. This commit adds the required
parameters to the function and modifies all call sites.
The two parameters added are `new_target` and `old_target`. The
`new_target` is used to denote what the reference should point to when
the transaction is applied. Some functions allow this parameter to be
NULL, meaning that the reference is not changed.
The `old_target` denotes the value the reference must have before the
update. Some functions allow this parameter to be NULL, meaning that the
old value of the reference is not checked.
We also update the internal function `ref_transaction_add_update()`
similarly to take the two new parameters.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
branch.c | 2 +-
builtin/fast-import.c | 5 +++--
builtin/fetch.c | 2 +-
builtin/receive-pack.c | 1 +
builtin/replace.c | 2 +-
builtin/tag.c | 1 +
builtin/update-ref.c | 1 +
refs.c | 22 +++++++++++++++++-----
refs.h | 18 +++++++++++++++++-
refs/files-backend.c | 12 ++++++------
refs/refs-internal.h | 14 ++++++++++++++
refs/reftable-backend.c | 4 ++--
sequencer.c | 9 +++++----
walker.c | 2 +-
14 files changed, 71 insertions(+), 24 deletions(-)
diff --git a/branch.c b/branch.c
index e4a738fc7b..48af4c3ceb 100644
--- a/branch.c
+++ b/branch.c
@@ -627,7 +627,7 @@ void create_branch(struct repository *r,
if (!transaction ||
ref_transaction_update(transaction, ref.buf,
&oid, forcing ? NULL : null_oid(),
- 0, msg, &err) ||
+ NULL, NULL, 0, msg, &err) ||
ref_transaction_commit(transaction, &err))
die("%s", err.buf);
ref_transaction_free(transaction);
diff --git a/builtin/fast-import.c b/builtin/fast-import.c
index dc5a9d32dd..297dfb91a1 100644
--- a/builtin/fast-import.c
+++ b/builtin/fast-import.c
@@ -1634,7 +1634,7 @@ static int update_branch(struct branch *b)
transaction = ref_transaction_begin(&err);
if (!transaction ||
ref_transaction_update(transaction, b->name, &b->oid, &old_oid,
- 0, msg, &err) ||
+ NULL, NULL, 0, msg, &err) ||
ref_transaction_commit(transaction, &err)) {
ref_transaction_free(transaction);
error("%s", err.buf);
@@ -1675,7 +1675,8 @@ static void dump_tags(void)
strbuf_addf(&ref_name, "refs/tags/%s", t->name);
if (ref_transaction_update(transaction, ref_name.buf,
- &t->oid, NULL, 0, msg, &err)) {
+ &t->oid, NULL, NULL, NULL,
+ 0, msg, &err)) {
failure |= error("%s", err.buf);
goto cleanup;
}
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 5857d860db..66840b7c5b 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -668,7 +668,7 @@ static int s_update_ref(const char *action,
ret = ref_transaction_update(transaction, ref->name, &ref->new_oid,
check_old ? &ref->old_oid : NULL,
- 0, msg, &err);
+ NULL, NULL, 0, msg, &err);
if (ret) {
ret = STORE_REF_ERROR_OTHER;
goto out;
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index e8d7df14b6..b150ef39a8 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -1595,6 +1595,7 @@ static const char *update(struct command *cmd, struct shallow_info *si)
if (ref_transaction_update(transaction,
namespaced_name,
new_oid, old_oid,
+ NULL, NULL,
0, "push",
&err)) {
rp_error("%s", err.buf);
diff --git a/builtin/replace.c b/builtin/replace.c
index da59600ad2..7690687b0e 100644
--- a/builtin/replace.c
+++ b/builtin/replace.c
@@ -201,7 +201,7 @@ static int replace_object_oid(const char *object_ref,
transaction = ref_transaction_begin(&err);
if (!transaction ||
ref_transaction_update(transaction, ref.buf, repl, &prev,
- 0, NULL, &err) ||
+ NULL, NULL, 0, NULL, &err) ||
ref_transaction_commit(transaction, &err))
res = error("%s", err.buf);
diff --git a/builtin/tag.c b/builtin/tag.c
index 9a33cb50b4..40a65fdebc 100644
--- a/builtin/tag.c
+++ b/builtin/tag.c
@@ -660,6 +660,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
transaction = ref_transaction_begin(&err);
if (!transaction ||
ref_transaction_update(transaction, ref.buf, &object, &prev,
+ NULL, NULL,
create_reflog ? REF_FORCE_CREATE_REFLOG : 0,
reflog_msg.buf, &err) ||
ref_transaction_commit(transaction, &err)) {
diff --git a/builtin/update-ref.c b/builtin/update-ref.c
index e46afbc46d..21fdbf6ac8 100644
--- a/builtin/update-ref.c
+++ b/builtin/update-ref.c
@@ -204,6 +204,7 @@ static void parse_cmd_update(struct ref_transaction *transaction,
if (ref_transaction_update(transaction, refname,
&new_oid, have_old ? &old_oid : NULL,
+ NULL, NULL,
update_flags | create_reflog_flag,
msg, &err))
die("%s", err.buf);
diff --git a/refs.c b/refs.c
index 55d2e0b2cb..e7b7c48d92 100644
--- a/refs.c
+++ b/refs.c
@@ -1228,6 +1228,7 @@ struct ref_update *ref_transaction_add_update(
const char *refname, unsigned int flags,
const struct object_id *new_oid,
const struct object_id *old_oid,
+ const char *new_target, const char *old_target,
const char *msg)
{
struct ref_update *update;
@@ -1235,6 +1236,11 @@ struct ref_update *ref_transaction_add_update(
if (transaction->state != REF_TRANSACTION_OPEN)
BUG("update called for transaction that is not open");
+ if (old_oid && old_target)
+ BUG("only one of old_oid and old_target should be non NULL");
+ if (new_oid && new_target)
+ BUG("only one of new_oid and new_target should be non NULL");
+
FLEX_ALLOC_STR(update, refname, refname);
ALLOC_GROW(transaction->updates, transaction->nr + 1, transaction->alloc);
transaction->updates[transaction->nr++] = update;
@@ -1253,6 +1259,8 @@ int ref_transaction_update(struct ref_transaction *transaction,
const char *refname,
const struct object_id *new_oid,
const struct object_id *old_oid,
+ const char *new_target,
+ const char *old_target,
unsigned int flags, const char *msg,
struct strbuf *err)
{
@@ -1280,7 +1288,8 @@ int ref_transaction_update(struct ref_transaction *transaction,
flags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0);
ref_transaction_add_update(transaction, refname, flags,
- new_oid, old_oid, msg);
+ new_oid, old_oid, new_target,
+ old_target, msg);
return 0;
}
@@ -1295,7 +1304,8 @@ int ref_transaction_create(struct ref_transaction *transaction,
return 1;
}
return ref_transaction_update(transaction, refname, new_oid,
- null_oid(), flags, msg, err);
+ null_oid(), NULL, NULL, flags,
+ msg, err);
}
int ref_transaction_delete(struct ref_transaction *transaction,
@@ -1308,7 +1318,8 @@ int ref_transaction_delete(struct ref_transaction *transaction,
BUG("delete called with old_oid set to zeros");
return ref_transaction_update(transaction, refname,
null_oid(), old_oid,
- flags, msg, err);
+ NULL, NULL, flags,
+ msg, err);
}
int ref_transaction_verify(struct ref_transaction *transaction,
@@ -1321,6 +1332,7 @@ int ref_transaction_verify(struct ref_transaction *transaction,
BUG("verify called with old_oid set to NULL");
return ref_transaction_update(transaction, refname,
NULL, old_oid,
+ NULL, NULL,
flags, NULL, err);
}
@@ -1335,8 +1347,8 @@ int refs_update_ref(struct ref_store *refs, const char *msg,
t = ref_store_transaction_begin(refs, &err);
if (!t ||
- ref_transaction_update(t, refname, new_oid, old_oid, flags, msg,
- &err) ||
+ ref_transaction_update(t, refname, new_oid, old_oid, NULL, NULL,
+ flags, msg, &err) ||
ref_transaction_commit(t, &err)) {
ret = 1;
ref_transaction_free(t);
diff --git a/refs.h b/refs.h
index d278775e08..c7851bf587 100644
--- a/refs.h
+++ b/refs.h
@@ -648,6 +648,16 @@ struct ref_transaction *ref_transaction_begin(struct strbuf *err);
* before the update. A copy of this value is made in the
* transaction.
*
+ * new_target -- the target reference that the reference will be
+ * updated to point to. If the reference is a regular reference,
+ * it will be converted to a symbolic reference. Cannot be set
+ * together with `new_oid`. A copy of this value is made in the
+ * transaction.
+ *
+ * old_target -- the reference that the reference must be pointing to.
+ * Canont be set together with `old_oid`. A copy of this value is
+ * made in the transaction.
+ *
* flags -- flags affecting the update, passed to
* update_ref_lock(). Possible flags: REF_NO_DEREF,
* REF_FORCE_CREATE_REFLOG. See those constants for more
@@ -713,7 +723,11 @@ struct ref_transaction *ref_transaction_begin(struct strbuf *err);
* beforehand. The old value is checked after the lock is taken to
* prevent races. If the old value doesn't agree with old_oid, the
* whole transaction fails. If old_oid is NULL, then the previous
- * value is not checked.
+ * value is not checked. If `old_target` is not NULL, treat the reference
+ * as a symbolic ref and validate that its target before the update is
+ * `old_target`. If the `new_target` is not NULL, then the reference
+ * will be updated to a symbolic ref which targets `new_target`.
+ * Together, these allow us to update between regular refs and symrefs.
*
* See the above comment "Reference transaction updates" for more
* information.
@@ -722,6 +736,8 @@ int ref_transaction_update(struct ref_transaction *transaction,
const char *refname,
const struct object_id *new_oid,
const struct object_id *old_oid,
+ const char *new_target,
+ const char *old_target,
unsigned int flags, const char *msg,
struct strbuf *err);
diff --git a/refs/files-backend.c b/refs/files-backend.c
index a098d14ea0..e4d0aa3d41 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -1198,7 +1198,7 @@ static void prune_ref(struct files_ref_store *refs, struct ref_to_prune *r)
ref_transaction_add_update(
transaction, r->name,
REF_NO_DEREF | REF_HAVE_NEW | REF_HAVE_OLD | REF_IS_PRUNING,
- null_oid(), &r->oid, NULL);
+ null_oid(), &r->oid, NULL, NULL, NULL);
if (ref_transaction_commit(transaction, &err))
goto cleanup;
@@ -1292,7 +1292,7 @@ static int files_pack_refs(struct ref_store *ref_store,
* packed-refs transaction:
*/
if (ref_transaction_update(transaction, iter->refname,
- iter->oid, NULL,
+ iter->oid, NULL, NULL, NULL,
REF_NO_DEREF, NULL, &err))
die("failure preparing to create packed reference %s: %s",
iter->refname, err.buf);
@@ -2309,7 +2309,7 @@ static int split_head_update(struct ref_update *update,
transaction, "HEAD",
update->flags | REF_LOG_ONLY | REF_NO_DEREF,
&update->new_oid, &update->old_oid,
- update->msg);
+ NULL, NULL, update->msg);
/*
* Add "HEAD". This insertion is O(N) in the transaction
@@ -2372,7 +2372,7 @@ static int split_symref_update(struct ref_update *update,
new_update = ref_transaction_add_update(
transaction, referent, new_flags,
&update->new_oid, &update->old_oid,
- update->msg);
+ NULL, NULL, update->msg);
new_update->parent_update = update;
@@ -2763,7 +2763,7 @@ static int files_transaction_prepare(struct ref_store *ref_store,
packed_transaction, update->refname,
REF_HAVE_NEW | REF_NO_DEREF,
&update->new_oid, NULL,
- NULL);
+ NULL, NULL, NULL);
}
}
@@ -3048,7 +3048,7 @@ static int files_initial_transaction_commit(struct ref_store *ref_store,
ref_transaction_add_update(packed_transaction, update->refname,
update->flags & ~REF_HAVE_OLD,
&update->new_oid, &update->old_oid,
- NULL);
+ NULL, NULL, NULL);
}
if (packed_refs_lock(refs->packed_ref_store, 0, err)) {
diff --git a/refs/refs-internal.h b/refs/refs-internal.h
index 56641aa57a..108f4ec419 100644
--- a/refs/refs-internal.h
+++ b/refs/refs-internal.h
@@ -124,6 +124,19 @@ struct ref_update {
*/
struct object_id old_oid;
+ /*
+ * If set, point the reference to this value. This can also be
+ * used to convert regular references to become symbolic refs.
+ * Cannot be set together with `new_oid`.
+ */
+ const char *new_target;
+
+ /*
+ * If set, check that the reference previously pointed to this
+ * value. Cannot be set together with `old_oid`.
+ */
+ const char *old_target;
+
/*
* One or more of REF_NO_DEREF, REF_FORCE_CREATE_REFLOG,
* REF_HAVE_NEW, REF_HAVE_OLD, or backend-specific flags.
@@ -173,6 +186,7 @@ struct ref_update *ref_transaction_add_update(
const char *refname, unsigned int flags,
const struct object_id *new_oid,
const struct object_id *old_oid,
+ const char *new_target, const char *old_target,
const char *msg);
/*
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index 1cda48c504..6104471199 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -829,7 +829,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
new_update = ref_transaction_add_update(
transaction, "HEAD",
u->flags | REF_LOG_ONLY | REF_NO_DEREF,
- &u->new_oid, &u->old_oid, u->msg);
+ &u->new_oid, &u->old_oid, NULL, NULL, u->msg);
string_list_insert(&affected_refnames, new_update->refname);
}
@@ -908,7 +908,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
*/
new_update = ref_transaction_add_update(
transaction, referent.buf, new_flags,
- &u->new_oid, &u->old_oid, u->msg);
+ &u->new_oid, &u->old_oid, NULL, NULL, u->msg);
new_update->parent_update = u;
/*
diff --git a/sequencer.c b/sequencer.c
index 88de4dc20f..61e007d85f 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -665,7 +665,7 @@ static int fast_forward_to(struct repository *r,
if (!transaction ||
ref_transaction_update(transaction, "HEAD",
to, unborn && !is_rebase_i(opts) ?
- null_oid() : from,
+ null_oid() : from, NULL, NULL,
0, sb.buf, &err) ||
ref_transaction_commit(transaction, &err)) {
ref_transaction_free(transaction);
@@ -1298,7 +1298,7 @@ int update_head_with_reflog(const struct commit *old_head,
if (!transaction ||
ref_transaction_update(transaction, "HEAD", new_head,
old_head ? &old_head->object.oid : null_oid(),
- 0, sb.buf, err) ||
+ NULL, NULL, 0, sb.buf, err) ||
ref_transaction_commit(transaction, err)) {
ret = -1;
}
@@ -3832,8 +3832,9 @@ static int do_label(struct repository *r, const char *name, int len)
} else if (repo_get_oid(r, "HEAD", &head_oid)) {
error(_("could not read HEAD"));
ret = -1;
- } else if (ref_transaction_update(transaction, ref_name.buf, &head_oid,
- NULL, 0, msg.buf, &err) < 0 ||
+ } else if (ref_transaction_update(transaction, ref_name.buf,
+ &head_oid, NULL, NULL, NULL,
+ 0, msg.buf, &err) < 0 ||
ref_transaction_commit(transaction, &err)) {
error("%s", err.buf);
ret = -1;
diff --git a/walker.c b/walker.c
index c0fd632d92..1b3df43906 100644
--- a/walker.c
+++ b/walker.c
@@ -324,7 +324,7 @@ int walker_fetch(struct walker *walker, int targets, char **target,
strbuf_reset(&refname);
strbuf_addf(&refname, "refs/%s", write_ref[i]);
if (ref_transaction_update(transaction, refname.buf,
- oids + i, NULL, 0,
+ oids + i, NULL, NULL, NULL, 0,
msg ? msg : "fetch (unknown)",
&err)) {
error("%s", err.buf);
--
2.43.GIT
^ permalink raw reply related [flat|nested] 194+ messages in thread
* [PATCH v8 2/8] files-backend: extract out `create_symref_lock()`
2024-05-07 12:58 ` [PATCH v8 " Karthik Nayak
2024-05-07 12:58 ` [PATCH v8 1/8] refs: accept symref values in `ref_transaction_update()` Karthik Nayak
@ 2024-05-07 12:58 ` Karthik Nayak
2024-05-07 12:58 ` [PATCH v8 3/8] refs: support symrefs in 'reference-transaction' hook Karthik Nayak
` (7 subsequent siblings)
9 siblings, 0 replies; 194+ messages in thread
From: Karthik Nayak @ 2024-05-07 12:58 UTC (permalink / raw)
To: karthik.188; +Cc: git, gitster, ps, phillip.wood123
From: Karthik Nayak <karthik.188@gmail.com>
The function `create_symref_locked()` creates a symref by creating a
'<symref>.lock' file and then committing the symref lock, which creates
the final symref.
Extract the early half of `create_symref_locked()` into a new helper
function `create_symref_lock()`. Because the name of the new function is
too similar to the original, rename the original to
`create_and_commit_symref()` to avoid confusion.
The new function `create_symref_locked()` can be used to create the
symref lock in a separate step from that of committing it. This allows
to add transactional support for symrefs, where the lock would be
created in the preparation step and the lock would be committed in the
finish step.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
refs/files-backend.c | 51 ++++++++++++++++++++++++++++++++------------
1 file changed, 37 insertions(+), 14 deletions(-)
diff --git a/refs/files-backend.c b/refs/files-backend.c
index e4d0aa3d41..74a713090c 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -1920,27 +1920,49 @@ static void update_symref_reflog(struct files_ref_store *refs,
}
}
-static int create_symref_locked(struct files_ref_store *refs,
- struct ref_lock *lock, const char *refname,
- const char *target, const char *logmsg)
+static int create_symref_lock(struct files_ref_store *refs,
+ struct ref_lock *lock, const char *refname,
+ const char *target, struct strbuf *err)
{
+ if (!fdopen_lock_file(&lock->lk, "w")) {
+ strbuf_addf(err, "unable to fdopen %s: %s",
+ get_lock_file_path(&lock->lk), strerror(errno));
+ return -1;
+ }
+
+ if (fprintf(get_lock_file_fp(&lock->lk), "ref: %s\n", target) < 0) {
+ strbuf_addf(err, "unable to write to %s: %s",
+ get_lock_file_path(&lock->lk), strerror(errno));
+ return -1;
+ }
+
+ return 0;
+}
+
+static int create_and_commit_symref(struct files_ref_store *refs,
+ struct ref_lock *lock, const char *refname,
+ const char *target, const char *logmsg)
+{
+ struct strbuf err = STRBUF_INIT;
+ int ret;
+
if (prefer_symlink_refs && !create_ref_symlink(lock, target)) {
update_symref_reflog(refs, lock, refname, target, logmsg);
return 0;
}
- if (!fdopen_lock_file(&lock->lk, "w"))
- return error("unable to fdopen %s: %s",
- get_lock_file_path(&lock->lk), strerror(errno));
+ ret = create_symref_lock(refs, lock, refname, target, &err);
+ if (!ret) {
+ update_symref_reflog(refs, lock, refname, target, logmsg);
- update_symref_reflog(refs, lock, refname, target, logmsg);
+ if (commit_ref(lock) < 0)
+ return error("unable to write symref for %s: %s", refname,
+ strerror(errno));
+ } else {
+ return error("%s", err.buf);
+ }
- /* no error check; commit_ref will check ferror */
- fprintf(get_lock_file_fp(&lock->lk), "ref: %s\n", target);
- if (commit_ref(lock) < 0)
- return error("unable to write symref for %s: %s", refname,
- strerror(errno));
- return 0;
+ return ret;
}
static int files_create_symref(struct ref_store *ref_store,
@@ -1960,7 +1982,8 @@ static int files_create_symref(struct ref_store *ref_store,
return -1;
}
- ret = create_symref_locked(refs, lock, refname, target, logmsg);
+ ret = create_and_commit_symref(refs, lock, refname, target, logmsg);
+
unlock_ref(lock);
return ret;
}
--
2.43.GIT
^ permalink raw reply related [flat|nested] 194+ messages in thread
* [PATCH v8 3/8] refs: support symrefs in 'reference-transaction' hook
2024-05-07 12:58 ` [PATCH v8 " Karthik Nayak
2024-05-07 12:58 ` [PATCH v8 1/8] refs: accept symref values in `ref_transaction_update()` Karthik Nayak
2024-05-07 12:58 ` [PATCH v8 2/8] files-backend: extract out `create_symref_lock()` Karthik Nayak
@ 2024-05-07 12:58 ` Karthik Nayak
2024-05-07 12:58 ` [PATCH v8 4/8] refs: move `original_update_refname` to 'refs.c' Karthik Nayak
` (6 subsequent siblings)
9 siblings, 0 replies; 194+ messages in thread
From: Karthik Nayak @ 2024-05-07 12:58 UTC (permalink / raw)
To: karthik.188; +Cc: git, gitster, ps, phillip.wood123
From: Karthik Nayak <karthik.188@gmail.com>
The 'reference-transaction' hook runs whenever a reference update is
made to the system. In a previous commit, we added the `old_target` and
`new_target` fields to the `reference_transaction_update()`. In
following commits we'll also add the code to handle symref's in the
reference backends.
Support symrefs also in the 'reference-transaction' hook, by modifying
the current format:
<old-oid> SP <new-oid> SP <ref-name> LF
to be be:
<old-value> SP <new-value> SP <ref-name> LF
where for regular refs the output would not change and remain the same.
But when either 'old-value' or 'new-value' is a symref, we print the ref
as 'ref:<ref-target>'.
This does break backward compatibility, but the 'reference-transaction'
hook's documentation always stated that support for symbolic references
may be added in the future.
We do not add any tests in this commit since there is no git command
which activates this flow, in an upcoming commit, we'll start using
transaction based symref updates as the default, we'll add tests there
for the hook too.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
Documentation/githooks.txt | 14 +++++++++-----
refs.c | 20 ++++++++++++++++----
2 files changed, 25 insertions(+), 9 deletions(-)
diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
index ee9b92c90d..06e997131b 100644
--- a/Documentation/githooks.txt
+++ b/Documentation/githooks.txt
@@ -486,7 +486,7 @@ reference-transaction
This hook is invoked by any Git command that performs reference
updates. It executes whenever a reference transaction is prepared,
committed or aborted and may thus get called multiple times. The hook
-does not cover symbolic references (but that may change in the future).
+also supports symbolic reference updates.
The hook takes exactly one argument, which is the current state the
given reference transaction is in:
@@ -503,16 +503,20 @@ given reference transaction is in:
For each reference update that was added to the transaction, the hook
receives on standard input a line of the format:
- <old-oid> SP <new-oid> SP <ref-name> LF
+ <old-value> SP <new-value> SP <ref-name> LF
-where `<old-oid>` is the old object name passed into the reference
-transaction, `<new-oid>` is the new object name to be stored in the
+where `<old-value>` is the old object name passed into the reference
+transaction, `<new-value>` is the new object name to be stored in the
ref and `<ref-name>` is the full name of the ref. When force updating
the reference regardless of its current value or when the reference is
-to be created anew, `<old-oid>` is the all-zeroes object name. To
+to be created anew, `<old-value>` is the all-zeroes object name. To
distinguish these cases, you can inspect the current value of
`<ref-name>` via `git rev-parse`.
+For symbolic reference updates the `<old_value>` and `<new-value>`
+fields could denote references instead of objects. A reference will be
+denoted with a 'ref:' prefix, like `ref:<ref-target>`.
+
The exit status of the hook is ignored for any state except for the
"prepared" state. In the "prepared" state, a non-zero exit status will
cause the transaction to be aborted. The hook will not be called with
diff --git a/refs.c b/refs.c
index e7b7c48d92..9d722d798a 100644
--- a/refs.c
+++ b/refs.c
@@ -2350,10 +2350,22 @@ static int run_transaction_hook(struct ref_transaction *transaction,
struct ref_update *update = transaction->updates[i];
strbuf_reset(&buf);
- strbuf_addf(&buf, "%s %s %s\n",
- oid_to_hex(&update->old_oid),
- oid_to_hex(&update->new_oid),
- update->refname);
+
+ if (!(update->flags & REF_HAVE_OLD))
+ strbuf_addf(&buf, "%s ", oid_to_hex(null_oid()));
+ else if (update->old_target)
+ strbuf_addf(&buf, "ref:%s ", update->old_target);
+ else
+ strbuf_addf(&buf, "%s ", oid_to_hex(&update->old_oid));
+
+ if (!(update->flags & REF_HAVE_NEW))
+ strbuf_addf(&buf, "%s ", oid_to_hex(null_oid()));
+ else if (update->new_target)
+ strbuf_addf(&buf, "ref:%s ", update->new_target);
+ else
+ strbuf_addf(&buf, "%s ", oid_to_hex(&update->new_oid));
+
+ strbuf_addf(&buf, "%s\n", update->refname);
if (write_in_full(proc.in, buf.buf, buf.len) < 0) {
if (errno != EPIPE) {
--
2.43.GIT
^ permalink raw reply related [flat|nested] 194+ messages in thread
* [PATCH v8 4/8] refs: move `original_update_refname` to 'refs.c'
2024-05-07 12:58 ` [PATCH v8 " Karthik Nayak
` (2 preceding siblings ...)
2024-05-07 12:58 ` [PATCH v8 3/8] refs: support symrefs in 'reference-transaction' hook Karthik Nayak
@ 2024-05-07 12:58 ` Karthik Nayak
2024-05-07 12:58 ` [PATCH v8 5/8] refs: add support for transactional symref updates Karthik Nayak
` (5 subsequent siblings)
9 siblings, 0 replies; 194+ messages in thread
From: Karthik Nayak @ 2024-05-07 12:58 UTC (permalink / raw)
To: karthik.188; +Cc: git, gitster, ps, phillip.wood123
From: Karthik Nayak <karthik.188@gmail.com>
The files backend and the reftable backend implement
`original_update_refname` to obtain the original refname of the update.
Move it out to 'refs.c' and only expose it internally to the refs
library. This will be used in an upcoming commit to also introduce
another common functionality for the two backends.
We also rename the function to `ref_update_original_update_refname` to
keep it consistent with the upcoming other 'ref_update_*' functions
that'll be introduced.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
refs.c | 9 +++++++++
refs/files-backend.c | 21 +++++----------------
refs/refs-internal.h | 5 +++++
refs/reftable-backend.c | 24 +++++++-----------------
4 files changed, 26 insertions(+), 33 deletions(-)
diff --git a/refs.c b/refs.c
index 9d722d798a..8de43dc0fc 100644
--- a/refs.c
+++ b/refs.c
@@ -2814,3 +2814,12 @@ int copy_existing_ref(const char *oldref, const char *newref, const char *logmsg
{
return refs_copy_existing_ref(get_main_ref_store(the_repository), oldref, newref, logmsg);
}
+
+const char *ref_update_original_update_refname(struct ref_update *update)
+{
+ while (update->parent_update)
+ update = update->parent_update;
+
+ return update->refname;
+}
+
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 74a713090c..25e5d03496 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -2423,17 +2423,6 @@ static int split_symref_update(struct ref_update *update,
return 0;
}
-/*
- * Return the refname under which update was originally requested.
- */
-static const char *original_update_refname(struct ref_update *update)
-{
- while (update->parent_update)
- update = update->parent_update;
-
- return update->refname;
-}
-
/*
* Check whether the REF_HAVE_OLD and old_oid values stored in update
* are consistent with oid, which is the reference's current value. If
@@ -2450,16 +2439,16 @@ static int check_old_oid(struct ref_update *update, struct object_id *oid,
if (is_null_oid(&update->old_oid))
strbuf_addf(err, "cannot lock ref '%s': "
"reference already exists",
- original_update_refname(update));
+ ref_update_original_update_refname(update));
else if (is_null_oid(oid))
strbuf_addf(err, "cannot lock ref '%s': "
"reference is missing but expected %s",
- original_update_refname(update),
+ ref_update_original_update_refname(update),
oid_to_hex(&update->old_oid));
else
strbuf_addf(err, "cannot lock ref '%s': "
"is at %s but expected %s",
- original_update_refname(update),
+ ref_update_original_update_refname(update),
oid_to_hex(oid),
oid_to_hex(&update->old_oid));
@@ -2513,7 +2502,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,
reason = strbuf_detach(err, NULL);
strbuf_addf(err, "cannot lock ref '%s': %s",
- original_update_refname(update), reason);
+ ref_update_original_update_refname(update), reason);
free(reason);
goto out;
}
@@ -2533,7 +2522,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,
if (update->flags & REF_HAVE_OLD) {
strbuf_addf(err, "cannot lock ref '%s': "
"error reading reference",
- original_update_refname(update));
+ ref_update_original_update_refname(update));
ret = TRANSACTION_GENERIC_ERROR;
goto out;
}
diff --git a/refs/refs-internal.h b/refs/refs-internal.h
index 108f4ec419..617b93a6c8 100644
--- a/refs/refs-internal.h
+++ b/refs/refs-internal.h
@@ -749,4 +749,9 @@ void base_ref_store_init(struct ref_store *refs, struct repository *repo,
*/
struct ref_store *maybe_debug_wrap_ref_store(const char *gitdir, struct ref_store *store);
+/*
+ * Return the refname under which update was originally requested.
+ */
+const char *ref_update_original_update_refname(struct ref_update *update);
+
#endif /* REFS_REFS_INTERNAL_H */
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index 6104471199..8bba5d5096 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -581,16 +581,6 @@ static int reftable_be_read_symbolic_ref(struct ref_store *ref_store,
return ret;
}
-/*
- * Return the refname under which update was originally requested.
- */
-static const char *original_update_refname(struct ref_update *update)
-{
- while (update->parent_update)
- update = update->parent_update;
- return update->refname;
-}
-
struct reftable_transaction_update {
struct ref_update *update;
struct object_id current_oid;
@@ -869,7 +859,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
/* The reference does not exist, but we expected it to. */
strbuf_addf(err, _("cannot lock ref '%s': "
"unable to resolve reference '%s'"),
- original_update_refname(u), u->refname);
+ ref_update_original_update_refname(u), u->refname);
ret = -1;
goto done;
}
@@ -941,17 +931,17 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
if (u->flags & REF_HAVE_OLD && !oideq(¤t_oid, &u->old_oid)) {
if (is_null_oid(&u->old_oid))
strbuf_addf(err, _("cannot lock ref '%s': "
- "reference already exists"),
- original_update_refname(u));
+ "reference already exists"),
+ ref_update_original_update_refname(u));
else if (is_null_oid(¤t_oid))
strbuf_addf(err, _("cannot lock ref '%s': "
- "reference is missing but expected %s"),
- original_update_refname(u),
+ "reference is missing but expected %s"),
+ ref_update_original_update_refname(u),
oid_to_hex(&u->old_oid));
else
strbuf_addf(err, _("cannot lock ref '%s': "
- "is at %s but expected %s"),
- original_update_refname(u),
+ "is at %s but expected %s"),
+ ref_update_original_update_refname(u),
oid_to_hex(¤t_oid),
oid_to_hex(&u->old_oid));
ret = -1;
--
2.43.GIT
^ permalink raw reply related [flat|nested] 194+ messages in thread
* [PATCH v8 5/8] refs: add support for transactional symref updates
2024-05-07 12:58 ` [PATCH v8 " Karthik Nayak
` (3 preceding siblings ...)
2024-05-07 12:58 ` [PATCH v8 4/8] refs: move `original_update_refname` to 'refs.c' Karthik Nayak
@ 2024-05-07 12:58 ` Karthik Nayak
2024-05-07 12:58 ` [PATCH v8 6/8] refs: use transaction in `refs_create_symref()` Karthik Nayak
` (4 subsequent siblings)
9 siblings, 0 replies; 194+ messages in thread
From: Karthik Nayak @ 2024-05-07 12:58 UTC (permalink / raw)
To: karthik.188; +Cc: git, gitster, ps, phillip.wood123
From: Karthik Nayak <karthik.188@gmail.com>
The reference backends currently support transactional reference
updates. While this is exposed to users via 'git-update-ref' and its
'--stdin' mode, it is also used internally within various commands.
However, we do not support transactional updates of symrefs. This commit
adds support for symrefs in both the 'files' and the 'reftable' backend.
Here, we add and use `ref_update_has_null_new_value()`, a helper
function which is used to check if there is a new_value in a reference
update. The new value could either be a symref target `new_target` or a
OID `new_oid`.
We also add another common function `ref_update_check_old_target` which
will be used to check if the update's old_target corresponds to a
reference's current target.
Now transactional updates (verify, create, delete, update) can be used
for:
- regular refs
- symbolic refs
- conversion of regular to symbolic refs and vice versa
This also allows us to expose this to users via new commands in
'git-update-ref' in the future.
Note that a dangling symref update does not record a new reflog entry,
which is unchanged before and after this commit.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
refs.c | 36 ++++++++++++-
refs/files-backend.c | 113 ++++++++++++++++++++++++++++++++--------
refs/refs-internal.h | 16 ++++++
refs/reftable-backend.c | 71 +++++++++++++++++++------
4 files changed, 196 insertions(+), 40 deletions(-)
diff --git a/refs.c b/refs.c
index 8de43dc0fc..d0ea7573d8 100644
--- a/refs.c
+++ b/refs.c
@@ -1217,6 +1217,8 @@ void ref_transaction_free(struct ref_transaction *transaction)
for (i = 0; i < transaction->nr; i++) {
free(transaction->updates[i]->msg);
+ free((char *)transaction->updates[i]->new_target);
+ free((char *)transaction->updates[i]->old_target);
free(transaction->updates[i]);
}
free(transaction->updates);
@@ -1247,10 +1249,13 @@ struct ref_update *ref_transaction_add_update(
update->flags = flags;
- if (flags & REF_HAVE_NEW)
+ update->new_target = xstrdup_or_null(new_target);
+ update->old_target = xstrdup_or_null(old_target);
+ if ((flags & REF_HAVE_NEW) && new_oid)
oidcpy(&update->new_oid, new_oid);
- if (flags & REF_HAVE_OLD)
+ if ((flags & REF_HAVE_OLD) && old_oid)
oidcpy(&update->old_oid, old_oid);
+
update->msg = normalize_reflog_message(msg);
return update;
}
@@ -1286,6 +1291,7 @@ int ref_transaction_update(struct ref_transaction *transaction,
flags &= REF_TRANSACTION_UPDATE_ALLOWED_FLAGS;
flags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0);
+ flags |= (new_target ? REF_HAVE_NEW : 0) | (old_target ? REF_HAVE_OLD : 0);
ref_transaction_add_update(transaction, refname, flags,
new_oid, old_oid, new_target,
@@ -2823,3 +2829,29 @@ const char *ref_update_original_update_refname(struct ref_update *update)
return update->refname;
}
+int ref_update_has_null_new_value(struct ref_update *update)
+{
+ return !update->new_target && is_null_oid(&update->new_oid);
+}
+
+int ref_update_check_old_target(const char *referent, struct ref_update *update,
+ struct strbuf *err)
+{
+ if (!update->old_target)
+ BUG("called without old_target set");
+
+ if (!strcmp(referent, update->old_target))
+ return 0;
+
+ if (!strcmp(referent, ""))
+ strbuf_addf(err, "verifying symref target: '%s': "
+ "reference is missing but expected %s",
+ ref_update_original_update_refname(update),
+ update->old_target);
+ else
+ strbuf_addf(err, "verifying symref target: '%s': "
+ "is at %s but expected %s",
+ ref_update_original_update_refname(update),
+ referent, update->old_target);
+ return -1;
+}
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 25e5d03496..2d1525b240 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -2394,8 +2394,9 @@ static int split_symref_update(struct ref_update *update,
new_update = ref_transaction_add_update(
transaction, referent, new_flags,
- &update->new_oid, &update->old_oid,
- NULL, NULL, update->msg);
+ update->new_target ? NULL : &update->new_oid,
+ update->old_target ? NULL : &update->old_oid,
+ update->new_target, update->old_target, update->msg);
new_update->parent_update = update;
@@ -2483,7 +2484,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,
files_assert_main_repository(refs, "lock_ref_for_update");
- if ((update->flags & REF_HAVE_NEW) && is_null_oid(&update->new_oid))
+ if ((update->flags & REF_HAVE_NEW) && ref_update_has_null_new_value(update))
update->flags |= REF_DELETING;
if (head_ref) {
@@ -2526,7 +2527,14 @@ static int lock_ref_for_update(struct files_ref_store *refs,
ret = TRANSACTION_GENERIC_ERROR;
goto out;
}
- } else if (check_old_oid(update, &lock->old_oid, err)) {
+ }
+
+ if (update->old_target) {
+ if (ref_update_check_old_target(referent.buf, update, err)) {
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto out;
+ }
+ } else if (check_old_oid(update, &lock->old_oid, err)) {
ret = TRANSACTION_GENERIC_ERROR;
goto out;
}
@@ -2547,7 +2555,17 @@ static int lock_ref_for_update(struct files_ref_store *refs,
} else {
struct ref_update *parent_update;
- if (check_old_oid(update, &lock->old_oid, err)) {
+ /*
+ * Even if the ref is a regular ref, if `old_target` is set, we
+ * check the referent value. Ideally `old_target` should only
+ * be set for symrefs, but we're strict about its usage.
+ */
+ if (update->old_target) {
+ if (ref_update_check_old_target(referent.buf, update, err)) {
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto out;
+ }
+ } else if (check_old_oid(update, &lock->old_oid, err)) {
ret = TRANSACTION_GENERIC_ERROR;
goto out;
}
@@ -2565,9 +2583,28 @@ static int lock_ref_for_update(struct files_ref_store *refs,
}
}
- if ((update->flags & REF_HAVE_NEW) &&
- !(update->flags & REF_DELETING) &&
- !(update->flags & REF_LOG_ONLY)) {
+ if (update->new_target && !(update->flags & REF_LOG_ONLY)) {
+ if (create_symref_lock(refs, lock, update->refname,
+ update->new_target, err)) {
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto out;
+ }
+
+ if (close_ref_gently(lock)) {
+ strbuf_addf(err, "couldn't close '%s.lock'",
+ update->refname);
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto out;
+ }
+
+ /*
+ * Once we have created the symref lock, the commit
+ * phase of the transaction only needs to commit the lock.
+ */
+ update->flags |= REF_NEEDS_COMMIT;
+ } else if ((update->flags & REF_HAVE_NEW) &&
+ !(update->flags & REF_DELETING) &&
+ !(update->flags & REF_LOG_ONLY)) {
if (!(update->type & REF_ISSYMREF) &&
oideq(&lock->old_oid, &update->new_oid)) {
/*
@@ -2830,6 +2867,43 @@ static int files_transaction_prepare(struct ref_store *ref_store,
return ret;
}
+static int parse_and_write_reflog(struct files_ref_store *refs,
+ struct ref_update *update,
+ struct ref_lock *lock,
+ struct strbuf *err)
+{
+ if (update->new_target) {
+ /*
+ * We want to get the resolved OID for the target, to ensure
+ * that the correct value is added to the reflog.
+ */
+ if (!refs_resolve_ref_unsafe(&refs->base, update->new_target,
+ RESOLVE_REF_READING,
+ &update->new_oid, NULL)) {
+ /*
+ * TODO: currently we skip creating reflogs for dangling
+ * symref updates. It would be nice to capture this as
+ * zero oid updates however.
+ */
+ return 0;
+ }
+ }
+
+ if (files_log_ref_write(refs, lock->ref_name, &lock->old_oid,
+ &update->new_oid, update->msg, update->flags, err)) {
+ char *old_msg = strbuf_detach(err, NULL);
+
+ strbuf_addf(err, "cannot update the ref '%s': %s",
+ lock->ref_name, old_msg);
+ free(old_msg);
+ unlock_ref(lock);
+ update->backend_data = NULL;
+ return -1;
+ }
+
+ return 0;
+}
+
static int files_transaction_finish(struct ref_store *ref_store,
struct ref_transaction *transaction,
struct strbuf *err)
@@ -2860,23 +2934,20 @@ static int files_transaction_finish(struct ref_store *ref_store,
if (update->flags & REF_NEEDS_COMMIT ||
update->flags & REF_LOG_ONLY) {
- if (files_log_ref_write(refs,
- lock->ref_name,
- &lock->old_oid,
- &update->new_oid,
- update->msg, update->flags,
- err)) {
- char *old_msg = strbuf_detach(err, NULL);
-
- strbuf_addf(err, "cannot update the ref '%s': %s",
- lock->ref_name, old_msg);
- free(old_msg);
- unlock_ref(lock);
- update->backend_data = NULL;
+ if (parse_and_write_reflog(refs, update, lock, err)) {
ret = TRANSACTION_GENERIC_ERROR;
goto cleanup;
}
}
+
+ /*
+ * We try creating a symlink, if that succeeds we continue to the
+ * next update. If not, we try and create a regular symref.
+ */
+ if (update->new_target && prefer_symlink_refs)
+ if (!create_ref_symlink(lock, update->new_target))
+ continue;
+
if (update->flags & REF_NEEDS_COMMIT) {
clear_loose_ref_cache(refs);
if (commit_ref(lock)) {
diff --git a/refs/refs-internal.h b/refs/refs-internal.h
index 617b93a6c8..819157256e 100644
--- a/refs/refs-internal.h
+++ b/refs/refs-internal.h
@@ -754,4 +754,20 @@ struct ref_store *maybe_debug_wrap_ref_store(const char *gitdir, struct ref_stor
*/
const char *ref_update_original_update_refname(struct ref_update *update);
+/*
+ * Helper function to check if the new value is null, this
+ * takes into consideration that the update could be a regular
+ * ref or a symbolic ref.
+ */
+int ref_update_has_null_new_value(struct ref_update *update);
+
+/*
+ * Check whether the old_target values stored in update are consistent
+ * with the referent, which is the symbolic reference's current value.
+ * If everything is OK, return 0; otherwise, write an error message to
+ * err and return -1.
+ */
+int ref_update_check_old_target(const char *referent, struct ref_update *update,
+ struct strbuf *err);
+
#endif /* REFS_REFS_INTERNAL_H */
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index 8bba5d5096..3eab57cf95 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -846,7 +846,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
* There is no need to write the reference deletion
* when the reference in question doesn't exist.
*/
- if (u->flags & REF_HAVE_NEW && !is_null_oid(&u->new_oid)) {
+ if ((u->flags & REF_HAVE_NEW) && !ref_update_has_null_new_value(u)) {
ret = queue_transaction_update(refs, tx_data, u,
¤t_oid, err);
if (ret)
@@ -897,8 +897,10 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
* intertwined with the locking in files-backend.c.
*/
new_update = ref_transaction_add_update(
- transaction, referent.buf, new_flags,
- &u->new_oid, &u->old_oid, NULL, NULL, u->msg);
+ transaction, referent.buf, new_flags,
+ &u->new_oid, &u->old_oid, u->new_target,
+ u->old_target, u->msg);
+
new_update->parent_update = u;
/*
@@ -928,7 +930,12 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
* individual refs. But the error messages match what the files
* backend returns, which keeps our tests happy.
*/
- if (u->flags & REF_HAVE_OLD && !oideq(¤t_oid, &u->old_oid)) {
+ if (u->old_target) {
+ if (ref_update_check_old_target(referent.buf, u, err)) {
+ ret = -1;
+ goto done;
+ }
+ } else if ((u->flags & REF_HAVE_OLD) && !oideq(¤t_oid, &u->old_oid)) {
if (is_null_oid(&u->old_oid))
strbuf_addf(err, _("cannot lock ref '%s': "
"reference already exists"),
@@ -1033,7 +1040,9 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data
* - `core.logAllRefUpdates` tells us to create the reflog for
* the given ref.
*/
- if (u->flags & REF_HAVE_NEW && !(u->type & REF_ISSYMREF) && is_null_oid(&u->new_oid)) {
+ if ((u->flags & REF_HAVE_NEW) &&
+ !(u->type & REF_ISSYMREF) &&
+ ref_update_has_null_new_value(u)) {
struct reftable_log_record log = {0};
struct reftable_iterator it = {0};
@@ -1074,24 +1083,52 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data
(u->flags & REF_FORCE_CREATE_REFLOG ||
should_write_log(&arg->refs->base, u->refname))) {
struct reftable_log_record *log;
+ int create_reflog = 1;
+
+ if (u->new_target) {
+ if (!refs_resolve_ref_unsafe(&arg->refs->base, u->new_target,
+ RESOLVE_REF_READING, &u->new_oid, NULL)) {
+ /*
+ * TODO: currently we skip creating reflogs for dangling
+ * symref updates. It would be nice to capture this as
+ * zero oid updates however.
+ */
+ create_reflog = 0;
+ }
+ }
- ALLOC_GROW(logs, logs_nr + 1, logs_alloc);
- log = &logs[logs_nr++];
- memset(log, 0, sizeof(*log));
-
- fill_reftable_log_record(log);
- log->update_index = ts;
- log->refname = xstrdup(u->refname);
- memcpy(log->value.update.new_hash, u->new_oid.hash, GIT_MAX_RAWSZ);
- memcpy(log->value.update.old_hash, tx_update->current_oid.hash, GIT_MAX_RAWSZ);
- log->value.update.message =
- xstrndup(u->msg, arg->refs->write_options.block_size / 2);
+ if (create_reflog) {
+ ALLOC_GROW(logs, logs_nr + 1, logs_alloc);
+ log = &logs[logs_nr++];
+ memset(log, 0, sizeof(*log));
+
+ fill_reftable_log_record(log);
+ log->update_index = ts;
+ log->refname = xstrdup(u->refname);
+ memcpy(log->value.update.new_hash,
+ u->new_oid.hash, GIT_MAX_RAWSZ);
+ memcpy(log->value.update.old_hash,
+ tx_update->current_oid.hash, GIT_MAX_RAWSZ);
+ log->value.update.message =
+ xstrndup(u->msg, arg->refs->write_options.block_size / 2);
+ }
}
if (u->flags & REF_LOG_ONLY)
continue;
- if (u->flags & REF_HAVE_NEW && is_null_oid(&u->new_oid)) {
+ if (u->new_target) {
+ struct reftable_ref_record ref = {
+ .refname = (char *)u->refname,
+ .value_type = REFTABLE_REF_SYMREF,
+ .value.symref = (char *)u->new_target,
+ .update_index = ts,
+ };
+
+ ret = reftable_writer_add_ref(writer, &ref);
+ if (ret < 0)
+ goto done;
+ } else if ((u->flags & REF_HAVE_NEW) && ref_update_has_null_new_value(u)) {
struct reftable_ref_record ref = {
.refname = (char *)u->refname,
.update_index = ts,
--
2.43.GIT
^ permalink raw reply related [flat|nested] 194+ messages in thread
* [PATCH v8 6/8] refs: use transaction in `refs_create_symref()`
2024-05-07 12:58 ` [PATCH v8 " Karthik Nayak
` (4 preceding siblings ...)
2024-05-07 12:58 ` [PATCH v8 5/8] refs: add support for transactional symref updates Karthik Nayak
@ 2024-05-07 12:58 ` Karthik Nayak
2024-05-07 12:58 ` [PATCH v8 7/8] refs: rename `refs_create_symref()` to `refs_update_symref()` Karthik Nayak
` (3 subsequent siblings)
9 siblings, 0 replies; 194+ messages in thread
From: Karthik Nayak @ 2024-05-07 12:58 UTC (permalink / raw)
To: karthik.188; +Cc: git, gitster, ps, phillip.wood123
From: Karthik Nayak <karthik.188@gmail.com>
The `refs_create_symref()` function updates a symref to a given new
target. To do this, it uses a ref-backend specific function
`create_symref()`.
In the previous commits, we introduced symref support in transactions.
This means we can now use transactions to perform symref updates and
don't have to resort to `create_symref()`. Doing this allows us to
remove and cleanup `create_symref()`, which we will do in the following
commit.
Modify the expected error message for a test in
't/t0610-reftable-basics.sh', since the error is now thrown from
'refs.c'. This is because in transactional updates, F/D conflicts are
caught before we're in the reference backend.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
refs.c | 24 +++++++++++++++++-------
t/t0610-reftable-basics.sh | 2 +-
t/t1416-ref-transaction-hooks.sh | 23 +++++++++++++++++++++++
3 files changed, 41 insertions(+), 8 deletions(-)
diff --git a/refs.c b/refs.c
index d0ea7573d8..507f5f0525 100644
--- a/refs.c
+++ b/refs.c
@@ -2289,14 +2289,24 @@ int refs_create_symref(struct ref_store *refs,
const char *refs_heads_master,
const char *logmsg)
{
- char *msg;
- int retval;
+ struct ref_transaction *transaction;
+ struct strbuf err = STRBUF_INIT;
+ int ret = 0;
- msg = normalize_reflog_message(logmsg);
- retval = refs->be->create_symref(refs, ref_target, refs_heads_master,
- msg);
- free(msg);
- return retval;
+ transaction = ref_store_transaction_begin(refs, &err);
+ if (!transaction ||
+ ref_transaction_update(transaction, ref_target, NULL, NULL,
+ refs_heads_master, NULL, REF_NO_DEREF,
+ logmsg, &err) ||
+ ref_transaction_commit(transaction, &err)) {
+ ret = error("%s", err.buf);
+ }
+
+ strbuf_release(&err);
+ if (transaction)
+ ref_transaction_free(transaction);
+
+ return ret;
}
int create_symref(const char *ref_target, const char *refs_heads_master,
diff --git a/t/t0610-reftable-basics.sh b/t/t0610-reftable-basics.sh
index 178791e086..9e8d22bcbd 100755
--- a/t/t0610-reftable-basics.sh
+++ b/t/t0610-reftable-basics.sh
@@ -286,7 +286,7 @@ test_expect_success 'ref transaction: creating symbolic ref fails with F/D confl
git init repo &&
test_commit -C repo A &&
cat >expect <<-EOF &&
- error: unable to write symref for refs/heads: file/directory conflict
+ error: ${SQ}refs/heads/main${SQ} exists; cannot create ${SQ}refs/heads${SQ}
EOF
test_must_fail git -C repo symbolic-ref refs/heads refs/heads/foo 2>err &&
test_cmp expect err
diff --git a/t/t1416-ref-transaction-hooks.sh b/t/t1416-ref-transaction-hooks.sh
index 2092488090..067fd57290 100755
--- a/t/t1416-ref-transaction-hooks.sh
+++ b/t/t1416-ref-transaction-hooks.sh
@@ -134,4 +134,27 @@ test_expect_success 'interleaving hook calls succeed' '
test_cmp expect target-repo.git/actual
'
+test_expect_success 'hook captures git-symbolic-ref updates' '
+ test_when_finished "rm actual" &&
+
+ test_hook reference-transaction <<-\EOF &&
+ echo "$*" >>actual
+ while read -r line
+ do
+ printf "%s\n" "$line"
+ done >>actual
+ EOF
+
+ git symbolic-ref refs/heads/symref refs/heads/main &&
+
+ cat >expect <<-EOF &&
+ prepared
+ $ZERO_OID ref:refs/heads/main refs/heads/symref
+ committed
+ $ZERO_OID ref:refs/heads/main refs/heads/symref
+ EOF
+
+ test_cmp expect actual
+'
+
test_done
--
2.43.GIT
^ permalink raw reply related [flat|nested] 194+ messages in thread
* [PATCH v8 7/8] refs: rename `refs_create_symref()` to `refs_update_symref()`
2024-05-07 12:58 ` [PATCH v8 " Karthik Nayak
` (5 preceding siblings ...)
2024-05-07 12:58 ` [PATCH v8 6/8] refs: use transaction in `refs_create_symref()` Karthik Nayak
@ 2024-05-07 12:58 ` Karthik Nayak
2024-05-07 12:58 ` [PATCH v8 8/8] refs: remove `create_symref` and associated dead code Karthik Nayak
` (2 subsequent siblings)
9 siblings, 0 replies; 194+ messages in thread
From: Karthik Nayak @ 2024-05-07 12:58 UTC (permalink / raw)
To: karthik.188; +Cc: git, gitster, ps, phillip.wood123
From: Karthik Nayak <karthik.188@gmail.com>
The `refs_create_symref()` function is used to update/create a symref.
But it doesn't check the old target of the symref, if existing. It force
updates the symref. In this regard, the name `refs_create_symref()` is a
bit misleading. So let's rename it to `refs_update_symref()`. This is
akin to how 'git-update-ref(1)' also allows us to create apart from
update.
While we're here, rename the arguments in the function to clarify what
they actually signify and reduce confusion.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
builtin/branch.c | 2 +-
builtin/worktree.c | 2 +-
refs.c | 12 +++++-------
refs.h | 2 +-
t/helper/test-ref-store.c | 2 +-
5 files changed, 9 insertions(+), 11 deletions(-)
diff --git a/builtin/branch.c b/builtin/branch.c
index dd3e3a7dc0..4491f7a20c 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -555,7 +555,7 @@ static int replace_each_worktree_head_symref(struct worktree **worktrees,
continue;
refs = get_worktree_ref_store(worktrees[i]);
- if (refs_create_symref(refs, "HEAD", newref, logmsg))
+ if (refs_update_symref(refs, "HEAD", newref, logmsg))
ret = error(_("HEAD of working tree %s is not updated"),
worktrees[i]->path);
}
diff --git a/builtin/worktree.c b/builtin/worktree.c
index 7c6c72536b..480202c517 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -517,7 +517,7 @@ static int add_worktree(const char *path, const char *refname,
ret = refs_update_ref(wt_refs, NULL, "HEAD", &commit->object.oid,
NULL, 0, UPDATE_REFS_MSG_ON_ERR);
else
- ret = refs_create_symref(wt_refs, "HEAD", symref.buf, NULL);
+ ret = refs_update_symref(wt_refs, "HEAD", symref.buf, NULL);
if (ret)
goto done;
diff --git a/refs.c b/refs.c
index 507f5f0525..fa5471d219 100644
--- a/refs.c
+++ b/refs.c
@@ -2284,10 +2284,8 @@ int peel_iterated_oid(const struct object_id *base, struct object_id *peeled)
return peel_object(base, peeled) ? -1 : 0;
}
-int refs_create_symref(struct ref_store *refs,
- const char *ref_target,
- const char *refs_heads_master,
- const char *logmsg)
+int refs_update_symref(struct ref_store *refs, const char *ref,
+ const char *target, const char *logmsg)
{
struct ref_transaction *transaction;
struct strbuf err = STRBUF_INIT;
@@ -2295,8 +2293,8 @@ int refs_create_symref(struct ref_store *refs,
transaction = ref_store_transaction_begin(refs, &err);
if (!transaction ||
- ref_transaction_update(transaction, ref_target, NULL, NULL,
- refs_heads_master, NULL, REF_NO_DEREF,
+ ref_transaction_update(transaction, ref, NULL, NULL,
+ target, NULL, REF_NO_DEREF,
logmsg, &err) ||
ref_transaction_commit(transaction, &err)) {
ret = error("%s", err.buf);
@@ -2312,7 +2310,7 @@ int refs_create_symref(struct ref_store *refs,
int create_symref(const char *ref_target, const char *refs_heads_master,
const char *logmsg)
{
- return refs_create_symref(get_main_ref_store(the_repository), ref_target,
+ return refs_update_symref(get_main_ref_store(the_repository), ref_target,
refs_heads_master, logmsg);
}
diff --git a/refs.h b/refs.h
index c7851bf587..71cc1c58e0 100644
--- a/refs.h
+++ b/refs.h
@@ -606,7 +606,7 @@ int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,
int copy_existing_ref(const char *oldref, const char *newref,
const char *logmsg);
-int refs_create_symref(struct ref_store *refs, const char *refname,
+int refs_update_symref(struct ref_store *refs, const char *refname,
const char *target, const char *logmsg);
int create_symref(const char *refname, const char *target, const char *logmsg);
diff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c
index 82bbf6e2e6..4651e4ced7 100644
--- a/t/helper/test-ref-store.c
+++ b/t/helper/test-ref-store.c
@@ -118,7 +118,7 @@ static int cmd_create_symref(struct ref_store *refs, const char **argv)
const char *target = notnull(*argv++, "target");
const char *logmsg = *argv++;
- return refs_create_symref(refs, refname, target, logmsg);
+ return refs_update_symref(refs, refname, target, logmsg);
}
static struct flag_definition transaction_flags[] = {
--
2.43.GIT
^ permalink raw reply related [flat|nested] 194+ messages in thread
* [PATCH v8 8/8] refs: remove `create_symref` and associated dead code
2024-05-07 12:58 ` [PATCH v8 " Karthik Nayak
` (6 preceding siblings ...)
2024-05-07 12:58 ` [PATCH v8 7/8] refs: rename `refs_create_symref()` to `refs_update_symref()` Karthik Nayak
@ 2024-05-07 12:58 ` Karthik Nayak
2024-05-07 15:50 ` [PATCH v8 0/8] refs: add support for transactional symref updates phillip.wood123
2024-05-07 16:32 ` Junio C Hamano
9 siblings, 0 replies; 194+ messages in thread
From: Karthik Nayak @ 2024-05-07 12:58 UTC (permalink / raw)
To: karthik.188; +Cc: git, gitster, ps, phillip.wood123
From: Karthik Nayak <karthik.188@gmail.com>
In the previous commits, we converted `refs_create_symref()` to utilize
transactions to perform symref updates. Earlier `refs_create_symref()`
used `create_symref()` to do the same.
We can now remove `create_symref()` and any code associated with it
which is no longer used. We remove `create_symref()` code from all the
reference backends and also remove it entirely from the `ref_storage_be`
struct.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
refs/debug.c | 13 -------
refs/files-backend.c | 67 --------------------------------
refs/packed-backend.c | 1 -
refs/refs-internal.h | 5 ---
refs/reftable-backend.c | 86 -----------------------------------------
5 files changed, 172 deletions(-)
diff --git a/refs/debug.c b/refs/debug.c
index c7531b17f0..8be316bb67 100644
--- a/refs/debug.c
+++ b/refs/debug.c
@@ -131,18 +131,6 @@ static int debug_pack_refs(struct ref_store *ref_store, struct pack_refs_opts *o
return res;
}
-static int debug_create_symref(struct ref_store *ref_store,
- const char *ref_name, const char *target,
- const char *logmsg)
-{
- struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store;
- int res = drefs->refs->be->create_symref(drefs->refs, ref_name, target,
- logmsg);
- trace_printf_key(&trace_refs, "create_symref: %s -> %s \"%s\": %d\n", ref_name,
- target, logmsg, res);
- return res;
-}
-
static int debug_rename_ref(struct ref_store *ref_store, const char *oldref,
const char *newref, const char *logmsg)
{
@@ -441,7 +429,6 @@ struct ref_storage_be refs_be_debug = {
.initial_transaction_commit = debug_initial_transaction_commit,
.pack_refs = debug_pack_refs,
- .create_symref = debug_create_symref,
.rename_ref = debug_rename_ref,
.copy_ref = debug_copy_ref,
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 2d1525b240..3957bfa579 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -1903,23 +1903,6 @@ static int create_ref_symlink(struct ref_lock *lock, const char *target)
return ret;
}
-static void update_symref_reflog(struct files_ref_store *refs,
- struct ref_lock *lock, const char *refname,
- const char *target, const char *logmsg)
-{
- struct strbuf err = STRBUF_INIT;
- struct object_id new_oid;
-
- if (logmsg &&
- refs_resolve_ref_unsafe(&refs->base, target,
- RESOLVE_REF_READING, &new_oid, NULL) &&
- files_log_ref_write(refs, refname, &lock->old_oid,
- &new_oid, logmsg, 0, &err)) {
- error("%s", err.buf);
- strbuf_release(&err);
- }
-}
-
static int create_symref_lock(struct files_ref_store *refs,
struct ref_lock *lock, const char *refname,
const char *target, struct strbuf *err)
@@ -1939,55 +1922,6 @@ static int create_symref_lock(struct files_ref_store *refs,
return 0;
}
-static int create_and_commit_symref(struct files_ref_store *refs,
- struct ref_lock *lock, const char *refname,
- const char *target, const char *logmsg)
-{
- struct strbuf err = STRBUF_INIT;
- int ret;
-
- if (prefer_symlink_refs && !create_ref_symlink(lock, target)) {
- update_symref_reflog(refs, lock, refname, target, logmsg);
- return 0;
- }
-
- ret = create_symref_lock(refs, lock, refname, target, &err);
- if (!ret) {
- update_symref_reflog(refs, lock, refname, target, logmsg);
-
- if (commit_ref(lock) < 0)
- return error("unable to write symref for %s: %s", refname,
- strerror(errno));
- } else {
- return error("%s", err.buf);
- }
-
- return ret;
-}
-
-static int files_create_symref(struct ref_store *ref_store,
- const char *refname, const char *target,
- const char *logmsg)
-{
- struct files_ref_store *refs =
- files_downcast(ref_store, REF_STORE_WRITE, "create_symref");
- struct strbuf err = STRBUF_INIT;
- struct ref_lock *lock;
- int ret;
-
- lock = lock_ref_oid_basic(refs, refname, &err);
- if (!lock) {
- error("%s", err.buf);
- strbuf_release(&err);
- return -1;
- }
-
- ret = create_and_commit_symref(refs, lock, refname, target, logmsg);
-
- unlock_ref(lock);
- return ret;
-}
-
static int files_reflog_exists(struct ref_store *ref_store,
const char *refname)
{
@@ -3374,7 +3308,6 @@ struct ref_storage_be refs_be_files = {
.initial_transaction_commit = files_initial_transaction_commit,
.pack_refs = files_pack_refs,
- .create_symref = files_create_symref,
.rename_ref = files_rename_ref,
.copy_ref = files_copy_ref,
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index 4e826c05ff..a937e7dbfc 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -1714,7 +1714,6 @@ struct ref_storage_be refs_be_packed = {
.initial_transaction_commit = packed_initial_transaction_commit,
.pack_refs = packed_pack_refs,
- .create_symref = NULL,
.rename_ref = NULL,
.copy_ref = NULL,
diff --git a/refs/refs-internal.h b/refs/refs-internal.h
index 819157256e..53a6c5d842 100644
--- a/refs/refs-internal.h
+++ b/refs/refs-internal.h
@@ -566,10 +566,6 @@ typedef int ref_transaction_commit_fn(struct ref_store *refs,
typedef int pack_refs_fn(struct ref_store *ref_store,
struct pack_refs_opts *opts);
-typedef int create_symref_fn(struct ref_store *ref_store,
- const char *ref_target,
- const char *refs_heads_master,
- const char *logmsg);
typedef int rename_ref_fn(struct ref_store *ref_store,
const char *oldref, const char *newref,
const char *logmsg);
@@ -690,7 +686,6 @@ struct ref_storage_be {
ref_transaction_commit_fn *initial_transaction_commit;
pack_refs_fn *pack_refs;
- create_symref_fn *create_symref;
rename_ref_fn *rename_ref;
copy_ref_fn *copy_ref;
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index 3eab57cf95..1b92c396b6 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -1259,91 +1259,6 @@ struct write_create_symref_arg {
const char *logmsg;
};
-static int write_create_symref_table(struct reftable_writer *writer, void *cb_data)
-{
- struct write_create_symref_arg *create = cb_data;
- uint64_t ts = reftable_stack_next_update_index(create->stack);
- struct reftable_ref_record ref = {
- .refname = (char *)create->refname,
- .value_type = REFTABLE_REF_SYMREF,
- .value.symref = (char *)create->target,
- .update_index = ts,
- };
- struct reftable_log_record log = {0};
- struct object_id new_oid;
- struct object_id old_oid;
- int ret;
-
- reftable_writer_set_limits(writer, ts, ts);
-
- ret = reftable_writer_add_ref(writer, &ref);
- if (ret)
- return ret;
-
- /*
- * Note that it is important to try and resolve the reference before we
- * write the log entry. This is because `should_write_log()` will munge
- * `core.logAllRefUpdates`, which is undesirable when we create a new
- * repository because it would be written into the config. As HEAD will
- * not resolve for new repositories this ordering will ensure that this
- * never happens.
- */
- if (!create->logmsg ||
- !refs_resolve_ref_unsafe(&create->refs->base, create->target,
- RESOLVE_REF_READING, &new_oid, NULL) ||
- !should_write_log(&create->refs->base, create->refname))
- return 0;
-
- fill_reftable_log_record(&log);
- log.refname = xstrdup(create->refname);
- log.update_index = ts;
- log.value.update.message = xstrndup(create->logmsg,
- create->refs->write_options.block_size / 2);
- memcpy(log.value.update.new_hash, new_oid.hash, GIT_MAX_RAWSZ);
- if (refs_resolve_ref_unsafe(&create->refs->base, create->refname,
- RESOLVE_REF_READING, &old_oid, NULL))
- memcpy(log.value.update.old_hash, old_oid.hash, GIT_MAX_RAWSZ);
-
- ret = reftable_writer_add_log(writer, &log);
- reftable_log_record_release(&log);
- return ret;
-}
-
-static int reftable_be_create_symref(struct ref_store *ref_store,
- const char *refname,
- const char *target,
- const char *logmsg)
-{
- struct reftable_ref_store *refs =
- reftable_be_downcast(ref_store, REF_STORE_WRITE, "create_symref");
- struct reftable_stack *stack = stack_for(refs, refname, &refname);
- struct write_create_symref_arg arg = {
- .refs = refs,
- .stack = stack,
- .refname = refname,
- .target = target,
- .logmsg = logmsg,
- };
- int ret;
-
- ret = refs->err;
- if (ret < 0)
- goto done;
-
- ret = reftable_stack_reload(stack);
- if (ret)
- goto done;
-
- ret = reftable_stack_add(stack, &write_create_symref_table, &arg);
-
-done:
- assert(ret != REFTABLE_API_ERROR);
- if (ret)
- error("unable to write symref for %s: %s", refname,
- reftable_error_str(ret));
- return ret;
-}
-
struct write_copy_arg {
struct reftable_ref_store *refs;
struct reftable_stack *stack;
@@ -2251,7 +2166,6 @@ struct ref_storage_be refs_be_reftable = {
.initial_transaction_commit = reftable_be_initial_transaction_commit,
.pack_refs = reftable_be_pack_refs,
- .create_symref = reftable_be_create_symref,
.rename_ref = reftable_be_rename_ref,
.copy_ref = reftable_be_copy_ref,
--
2.43.GIT
^ permalink raw reply related [flat|nested] 194+ messages in thread
* Re: [PATCH v8 0/8] refs: add support for transactional symref updates
2024-05-07 12:58 ` [PATCH v8 " Karthik Nayak
` (7 preceding siblings ...)
2024-05-07 12:58 ` [PATCH v8 8/8] refs: remove `create_symref` and associated dead code Karthik Nayak
@ 2024-05-07 15:50 ` phillip.wood123
2024-05-07 16:32 ` Junio C Hamano
9 siblings, 0 replies; 194+ messages in thread
From: phillip.wood123 @ 2024-05-07 15:50 UTC (permalink / raw)
To: Karthik Nayak; +Cc: git, gitster, ps
Hi Karthik
On 07/05/2024 13:58, Karthik Nayak wrote:
> Changes since v6:
> * Made the check for old/new oid & target more stricter by removing
> the exception for null oid's.
> * Extracted `ref_update_check_old_target` and `original_update_refname`
> to refs.c
> * ^This allowed us to generalize the error messages for non-matching
> old_target values between files and reftable backend.
> * Better line wrapping in code.
> * Fixed some grammar in commit messages.
Thanks for re-rolling - these changes address all of my comments on v6
and the range-diff below looks good.
Best Wishes
Phillip
> Range diff against v6:
>
> 1: a354190905 ! 1: defc1b3521 refs: accept symref values in `ref_transaction_update()`
> @@ refs.c: struct ref_update *ref_transaction_add_update(
> if (transaction->state != REF_TRANSACTION_OPEN)
> BUG("update called for transaction that is not open");
>
> -+ if (old_oid && !is_null_oid(old_oid) && old_target)
> ++ if (old_oid && old_target)
> + BUG("only one of old_oid and old_target should be non NULL");
> -+ if (new_oid && !is_null_oid(new_oid) && new_target)
> ++ if (new_oid && new_target)
> + BUG("only one of new_oid and new_target should be non NULL");
> +
> FLEX_ALLOC_STR(update, refname, refname);
> 2: 0d9c5b9804 = 2: 54bb78a27c files-backend: extract out `create_symref_lock()`
> 3: e0219ffd31 = 3: c16b7c5da0 refs: support symrefs in 'reference-transaction' hook
> -: ---------- > 4: 2d268f12cc refs: move `original_update_refname` to 'refs.c'
> 4: b22c59c722 ! 5: 7db3a2245f refs: add support for transactional symref updates
> @@ Commit message
> updates. While this is exposed to users via 'git-update-ref' and its
> '--stdin' mode, it is also used internally within various commands.
>
> - However, we never supported transactional updates of symrefs. Let's add
> - support for symrefs in both the 'files' and the 'reftable' backend.
> + However, we do not support transactional updates of symrefs. This commit
> + adds support for symrefs in both the 'files' and the 'reftable' backend.
>
> Here, we add and use `ref_update_has_null_new_value()`, a helper
> function which is used to check if there is a new_value in a reference
> update. The new value could either be a symref target `new_target` or a
> OID `new_oid`.
>
> - With this, now transactional updates (verify, create, delete, update)
> - can be used for:
> + We also add another common function `ref_update_check_old_target` which
> + will be used to check if the update's old_target corresponds to a
> + reference's current target.
> +
> + Now transactional updates (verify, create, delete, update) can be used
> + for:
> - regular refs
> - symbolic refs
> - conversion of regular to symbolic refs and vice versa
> @@ refs.c: int ref_transaction_update(struct ref_transaction *transaction,
>
> ref_transaction_add_update(transaction, refname, flags,
> new_oid, old_oid, new_target,
> -@@ refs.c: int copy_existing_ref(const char *oldref, const char *newref, const char *logmsg
> - {
> - return refs_copy_existing_ref(get_main_ref_store(the_repository), oldref, newref, logmsg);
> +@@ refs.c: const char *ref_update_original_update_refname(struct ref_update *update)
> + return update->refname;
> }
> -+
> +
> +int ref_update_has_null_new_value(struct ref_update *update)
> +{
> + return !update->new_target && is_null_oid(&update->new_oid);
> +}
> -
> - ## refs/files-backend.c ##
> -@@ refs/files-backend.c: static const char *original_update_refname(struct ref_update *update)
> - return update->refname;
> - }
> -
> -+/*
> -+ * Check whether the old_target values stored in update are consistent
> -+ * with current_target, which is the symbolic reference's current value.
> -+ * If everything is OK, return 0; otherwise, write an error message to
> -+ * err and return -1.
> -+ */
> -+static int check_old_target(struct ref_update *update,
> -+ const char *current_target,
> -+ struct strbuf *err)
> ++
> ++int ref_update_check_old_target(const char *referent, struct ref_update *update,
> ++ struct strbuf *err)
> +{
> + if (!update->old_target)
> + BUG("called without old_target set");
> +
> -+ if (!strcmp(update->old_target, current_target))
> ++ if (!strcmp(referent, update->old_target))
> + return 0;
> +
> -+ if (!strcmp(current_target, ""))
> -+ strbuf_addf(err, "cannot lock ref '%s': "
> ++ if (!strcmp(referent, ""))
> ++ strbuf_addf(err, "verifying symref target: '%s': "
> + "reference is missing but expected %s",
> -+ original_update_refname(update),
> ++ ref_update_original_update_refname(update),
> + update->old_target);
> + else
> -+ strbuf_addf(err, "cannot lock ref '%s': "
> ++ strbuf_addf(err, "verifying symref target: '%s': "
> + "is at %s but expected %s",
> -+ original_update_refname(update),
> -+ current_target, update->old_target);
> -+
> ++ ref_update_original_update_refname(update),
> ++ referent, update->old_target);
> + return -1;
> +}
> -+
> - /*
> - * Check whether the REF_HAVE_OLD and old_oid values stored in update
> - * are consistent with oid, which is the reference's current value. If
> +
> + ## refs/files-backend.c ##
> +@@ refs/files-backend.c: static int split_symref_update(struct ref_update *update,
> +
> + new_update = ref_transaction_add_update(
> + transaction, referent, new_flags,
> +- &update->new_oid, &update->old_oid,
> +- NULL, NULL, update->msg);
> ++ update->new_target ? NULL : &update->new_oid,
> ++ update->old_target ? NULL : &update->old_oid,
> ++ update->new_target, update->old_target, update->msg);
> +
> + new_update->parent_update = update;
> +
> @@ refs/files-backend.c: static int lock_ref_for_update(struct files_ref_store *refs,
>
> files_assert_main_repository(refs, "lock_ref_for_update");
> @@ refs/files-backend.c: static int lock_ref_for_update(struct files_ref_store *ref
> + }
> +
> + if (update->old_target) {
> -+ if (check_old_target(update, referent.buf, err)) {
> ++ if (ref_update_check_old_target(referent.buf, update, err)) {
> + ret = TRANSACTION_GENERIC_ERROR;
> + goto out;
> + }
> @@ refs/files-backend.c: static int lock_ref_for_update(struct files_ref_store *ref
> + * be set for symrefs, but we're strict about its usage.
> + */
> + if (update->old_target) {
> -+ if (check_old_target(update, referent.buf, err)) {
> ++ if (ref_update_check_old_target(referent.buf, update, err)) {
> + ret = TRANSACTION_GENERIC_ERROR;
> + goto out;
> + }
> @@ refs/files-backend.c: static int lock_ref_for_update(struct files_ref_store *ref
> - !(update->flags & REF_DELETING) &&
> - !(update->flags & REF_LOG_ONLY)) {
> + if (update->new_target && !(update->flags & REF_LOG_ONLY)) {
> -+ if (create_symref_lock(refs, lock, update->refname, update->new_target, err)) {
> ++ if (create_symref_lock(refs, lock, update->refname,
> ++ update->new_target, err)) {
> + ret = TRANSACTION_GENERIC_ERROR;
> + goto out;
> + }
> @@ refs/files-backend.c: static int files_transaction_finish(struct ref_store *ref_
> if (commit_ref(lock)) {
>
> ## refs/refs-internal.h ##
> -@@ refs/refs-internal.h: void base_ref_store_init(struct ref_store *refs, struct repository *repo,
> +@@ refs/refs-internal.h: struct ref_store *maybe_debug_wrap_ref_store(const char *gitdir, struct ref_stor
> */
> - struct ref_store *maybe_debug_wrap_ref_store(const char *gitdir, struct ref_store *store);
> + const char *ref_update_original_update_refname(struct ref_update *update);
>
> +/*
> + * Helper function to check if the new value is null, this
> @@ refs/refs-internal.h: void base_ref_store_init(struct ref_store *refs, struct re
> + * ref or a symbolic ref.
> + */
> +int ref_update_has_null_new_value(struct ref_update *update);
> ++
> ++/*
> ++ * Check whether the old_target values stored in update are consistent
> ++ * with the referent, which is the symbolic reference's current value.
> ++ * If everything is OK, return 0; otherwise, write an error message to
> ++ * err and return -1.
> ++ */
> ++int ref_update_check_old_target(const char *referent, struct ref_update *update,
> ++ struct strbuf *err);
> +
> #endif /* REFS_REFS_INTERNAL_H */
>
> @@ refs/reftable-backend.c: static int reftable_be_transaction_prepare(struct ref_s
> */
> - if (u->flags & REF_HAVE_OLD && !oideq(¤t_oid, &u->old_oid)) {
> + if (u->old_target) {
> -+ if (strcmp(referent.buf, u->old_target)) {
> -+ if (!strcmp(referent.buf, ""))
> -+ strbuf_addf(err, "verifying symref target: '%s': "
> -+ "reference is missing but expected %s",
> -+ original_update_refname(u),
> -+ u->old_target);
> -+ else
> -+ strbuf_addf(err, "verifying symref target: '%s': "
> -+ "is at %s but expected %s",
> -+ original_update_refname(u),
> -+ referent.buf, u->old_target);
> ++ if (ref_update_check_old_target(referent.buf, u, err)) {
> + ret = -1;
> + goto done;
> + }
> + } else if ((u->flags & REF_HAVE_OLD) && !oideq(¤t_oid, &u->old_oid)) {
> if (is_null_oid(&u->old_oid))
> strbuf_addf(err, _("cannot lock ref '%s': "
> - "reference already exists"),
> + "reference already exists"),
> @@ refs/reftable-backend.c: static int write_transaction_table(struct reftable_writer *writer, void *cb_data
> * - `core.logAllRefUpdates` tells us to create the reflog for
> * the given ref.
> */
> - if (u->flags & REF_HAVE_NEW && !(u->type & REF_ISSYMREF) && is_null_oid(&u->new_oid)) {
> -+ if ((u->flags & REF_HAVE_NEW) && !(u->type & REF_ISSYMREF) && ref_update_has_null_new_value(u)) {
> ++ if ((u->flags & REF_HAVE_NEW) &&
> ++ !(u->type & REF_ISSYMREF) &&
> ++ ref_update_has_null_new_value(u)) {
> struct reftable_log_record log = {0};
> struct reftable_iterator it = {0};
>
> @@ refs/reftable-backend.c: static int write_transaction_table(struct reftable_writ
> + fill_reftable_log_record(log);
> + log->update_index = ts;
> + log->refname = xstrdup(u->refname);
> -+ memcpy(log->value.update.new_hash, u->new_oid.hash, GIT_MAX_RAWSZ);
> -+ memcpy(log->value.update.old_hash, tx_update->current_oid.hash, GIT_MAX_RAWSZ);
> ++ memcpy(log->value.update.new_hash,
> ++ u->new_oid.hash, GIT_MAX_RAWSZ);
> ++ memcpy(log->value.update.old_hash,
> ++ tx_update->current_oid.hash, GIT_MAX_RAWSZ);
> + log->value.update.message =
> + xstrndup(u->msg, arg->refs->write_options.block_size / 2);
> + }
> 5: 636bf5ce98 = 6: 354ebbe17f refs: use transaction in `refs_create_symref()`
> 6: 07fb23374f = 7: c8a23b3454 refs: rename `refs_create_symref()` to `refs_update_symref()`
> 7: 5c05813bcc = 8: fa1b8f445b refs: remove `create_symref` and associated dead code
>
>
> Karthik Nayak (8):
> refs: accept symref values in `ref_transaction_update()`
> files-backend: extract out `create_symref_lock()`
> refs: support symrefs in 'reference-transaction' hook
> refs: move `original_update_refname` to 'refs.c'
> refs: add support for transactional symref updates
> refs: use transaction in `refs_create_symref()`
> refs: rename `refs_create_symref()` to `refs_update_symref()`
> refs: remove `create_symref` and associated dead code
>
> Documentation/githooks.txt | 14 ++-
> branch.c | 2 +-
> builtin/branch.c | 2 +-
> builtin/fast-import.c | 5 +-
> builtin/fetch.c | 2 +-
> builtin/receive-pack.c | 1 +
> builtin/replace.c | 2 +-
> builtin/tag.c | 1 +
> builtin/update-ref.c | 1 +
> builtin/worktree.c | 2 +-
> refs.c | 119 ++++++++++++++----
> refs.h | 20 ++-
> refs/debug.c | 13 --
> refs/files-backend.c | 208 +++++++++++++++++--------------
> refs/packed-backend.c | 1 -
> refs/refs-internal.h | 40 +++++-
> refs/reftable-backend.c | 183 +++++++++------------------
> sequencer.c | 9 +-
> t/helper/test-ref-store.c | 2 +-
> t/t0610-reftable-basics.sh | 2 +-
> t/t1416-ref-transaction-hooks.sh | 23 ++++
> walker.c | 2 +-
> 22 files changed, 374 insertions(+), 280 deletions(-)
>
^ permalink raw reply [flat|nested] 194+ messages in thread
* Re: [PATCH v8 0/8] refs: add support for transactional symref updates
2024-05-07 12:58 ` [PATCH v8 " Karthik Nayak
` (8 preceding siblings ...)
2024-05-07 15:50 ` [PATCH v8 0/8] refs: add support for transactional symref updates phillip.wood123
@ 2024-05-07 16:32 ` Junio C Hamano
2024-05-12 17:17 ` Karthik Nayak
9 siblings, 1 reply; 194+ messages in thread
From: Junio C Hamano @ 2024-05-07 16:32 UTC (permalink / raw)
To: Karthik Nayak; +Cc: git, ps, phillip.wood123
Karthik Nayak <karthik.188@gmail.com> writes:
> Changes since v7:
> * I had rebased v7 on next. I've rebased v8 on master. That's the only difference
> between the two versions.
I've applied them to the same base as used to queue the previous
round, which I think is "436d4e5b14 The seventeenth batch". It went
without conflicts, and tests fine in isolation. I'll see if it plays
well with other topics in 'seen' later in the day but not now.
Thanks.
> Junio, this might cause conflicts when merging, I think you resolved them for
> v6 and hope its the same now. Let me know if I can help otherwise somehow.
The easiest for both of us would be to do this:
(1) Build on whatever base you want, and format-patch the series.
If you are doing "rebase -i" in-place to update from the
previous round, this will reuse the previous base so (2) and
(3) may become trivial.
(2) Find the base of where the last round was queued, something like
$ mine='kn/ref-transaction-symref'
$ git checkout "origin/seen^{/^Merge branch '$mine'}...master"
(3) Apply your format-patch result. There are three cases
(3)-1. Things apply cleanly and tests fine. Go to (4).
(3)-2. Things apply cleanly but does not build or test fails,
or things do not apply cleanly.
In the latter case, you have textual or semantic conflicts
coming from the difference between the old base and the base
you used to build in (1). Identify what caused the breakages
(e.g., a topic or two may have merged since the base used by
(2) until the base used by (1)).
Check out the latest 'origin/master' (which may be newer than
the base used by (2)), "merge --no-ff" the topics you newly
depend on in there, and use the result of the merge(s) as the
base, rebuild the series and test again. Run format-patch from
the last such merges to the tip of your topic. If you did
$ git checkout origin/master
$ git merge --no-ff --into kn/ref-transaction-symref fo/obar
$ git merge --no-ff --into kn/ref-transaction-symref ba/zqux
... rebuild the topic ...
Then you'd just format your topic above these "preparing the
ground" merges, e.g.
$ git format-patch "HEAD^{/^Merge branch 'ba/zqux'}"..HEAD
Do not forget to write in the cover letter you did this,
including the topics you have in your base on top of 'master'.
Then go to (4).
(4) Make a trial merge of your topic into 'next' and 'seen', e.g.
$ git checkout --detach 'origin/seen' &&
git revert -m 1 <the merge of the previous iteration into seen> &&
git merge kn/ref-transaction-symref
The "revert" is needed if the previous iteration of your topic
is already in 'seen' (like in this case). You could choose to
rebuild master..origin/seen from scratch while excluding your
previous iteration, which may emulate what happens on my end
more closely.
This trial merge may conflict. It is primarily to see what
conflicts _other_ topics may have with your topic. In other
words, you do not have to depend on to make your topic work on
'master'. It may become the job of the other topic owners to
resolve conflicts if your topic goes to 'next' before theirs.
Make a note on what conflict you saw in the cover letter. You
do not necessarily have to resolve them, but it would be a good
opportunity to learn what others are doing in an related area.
$ git checkout --detach 'origin/next' &&
git merge kn/ref-transaction-symref
This is to see what conflicts your topic has with other topics
that are already cooking. This should not conflict if (3)-2
prepared a base on top of updated master plus dependent topics
taken from 'next'. Unless the context is severe (one way to
tell is try the same trial merge with your old iteration, which
may conflict in a similar way), expect that it will be handled
on my end (if it gets unmanageable, I'll ask to rebase when I
receive your patches).
Something like the above should be added to the SubmittingPatches
document (or its successor to cover more advanced topics, perhaps).
Thanks.
^ permalink raw reply [flat|nested] 194+ messages in thread
* Re: [PATCH v8 0/8] refs: add support for transactional symref updates
2024-05-07 16:32 ` Junio C Hamano
@ 2024-05-12 17:17 ` Karthik Nayak
2024-05-13 17:15 ` Junio C Hamano
0 siblings, 1 reply; 194+ messages in thread
From: Karthik Nayak @ 2024-05-12 17:17 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git, ps, phillip.wood123
[-- Attachment #1: Type: text/plain, Size: 5083 bytes --]
Junio C Hamano <gitster@pobox.com> writes:
> Karthik Nayak <karthik.188@gmail.com> writes:
>
>> Changes since v7:
>> * I had rebased v7 on next. I've rebased v8 on master. That's the only difference
>> between the two versions.
>
> I've applied them to the same base as used to queue the previous
> round, which I think is "436d4e5b14 The seventeenth batch". It went
> without conflicts, and tests fine in isolation. I'll see if it plays
> well with other topics in 'seen' later in the day but not now.
>
> Thanks.
>
>> Junio, this might cause conflicts when merging, I think you resolved them for
>> v6 and hope its the same now. Let me know if I can help otherwise somehow.
>
> The easiest for both of us would be to do this:
>
> (1) Build on whatever base you want, and format-patch the series.
> If you are doing "rebase -i" in-place to update from the
> previous round, this will reuse the previous base so (2) and
> (3) may become trivial.
>
> (2) Find the base of where the last round was queued, something like
>
> $ mine='kn/ref-transaction-symref'
> $ git checkout "origin/seen^{/^Merge branch '$mine'}...master"
>
I find the '...' always so confusing, I would say suggesting to use
'git-merge-base' would be much nicer here.
> (3) Apply your format-patch result. There are three cases
>
> (3)-1. Things apply cleanly and tests fine. Go to (4).
>
> (3)-2. Things apply cleanly but does not build or test fails,
> or things do not apply cleanly.
>
> In the latter case, you have textual or semantic conflicts
> coming from the difference between the old base and the base
> you used to build in (1). Identify what caused the breakages
> (e.g., a topic or two may have merged since the base used by
> (2) until the base used by (1)).
>
> Check out the latest 'origin/master' (which may be newer than
> the base used by (2)), "merge --no-ff" the topics you newly
For my own understanding, even if we use '--ff' the end result should be
the same, but using '--no-ff' would ensure that the changes and
conflicts are isolated to the merge commit, right?
> depend on in there, and use the result of the merge(s) as the
> base, rebuild the series and test again. Run format-patch from
> the last such merges to the tip of your topic. If you did
>
> $ git checkout origin/master
> $ git merge --no-ff --into kn/ref-transaction-symref fo/obar
> $ git merge --no-ff --into kn/ref-transaction-symref ba/zqux
> ... rebuild the topic ...
>
I guess you mean '--into-name' here? I would skip mentioning this since
it doesn't have any real effect and is perhaps confusing.
> Then you'd just format your topic above these "preparing the
> ground" merges, e.g.
>
> $ git format-patch "HEAD^{/^Merge branch 'ba/zqux'}"..HEAD
>
> Do not forget to write in the cover letter you did this,
> including the topics you have in your base on top of 'master'.
> Then go to (4).
>
> (4) Make a trial merge of your topic into 'next' and 'seen', e.g.
>
> $ git checkout --detach 'origin/seen' &&
> git revert -m 1 <the merge of the previous iteration into seen> &&
> git merge kn/ref-transaction-symref
>
> The "revert" is needed if the previous iteration of your topic
> is already in 'seen' (like in this case). You could choose to
> rebuild master..origin/seen from scratch while excluding your
> previous iteration, which may emulate what happens on my end
> more closely.
>
> This trial merge may conflict. It is primarily to see what
> conflicts _other_ topics may have with your topic. In other
> words, you do not have to depend on to make your topic work on
> 'master'. It may become the job of the other topic owners to
> resolve conflicts if your topic goes to 'next' before theirs.
>
> Make a note on what conflict you saw in the cover letter. You
> do not necessarily have to resolve them, but it would be a good
> opportunity to learn what others are doing in an related area.
>
> $ git checkout --detach 'origin/next' &&
> git merge kn/ref-transaction-symref
>
> This is to see what conflicts your topic has with other topics
> that are already cooking. This should not conflict if (3)-2
> prepared a base on top of updated master plus dependent topics
> taken from 'next'. Unless the context is severe (one way to
> tell is try the same trial merge with your old iteration, which
> may conflict in a similar way), expect that it will be handled
> on my end (if it gets unmanageable, I'll ask to rebase when I
> receive your patches).
>
> Something like the above should be added to the SubmittingPatches
> document (or its successor to cover more advanced topics, perhaps).
>
> Thanks.
The rest of this looks good, I'll cleanup add the appropriate syntax,
merge in your patches [1] and send something soon!
[1]: https://lore.kernel.org/all/20240510165526.1412338-1-gitster@pobox.com/#t
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 194+ messages in thread
* Re: [PATCH v8 0/8] refs: add support for transactional symref updates
2024-05-12 17:17 ` Karthik Nayak
@ 2024-05-13 17:15 ` Junio C Hamano
0 siblings, 0 replies; 194+ messages in thread
From: Junio C Hamano @ 2024-05-13 17:15 UTC (permalink / raw)
To: Karthik Nayak; +Cc: git, ps, phillip.wood123
Karthik Nayak <karthik.188@gmail.com> writes:
> I find the '...' always so confusing, I would say suggesting to use
> 'git-merge-base' would be much nicer here.
They are equivalent, except that "..." in
git checkout --detach A...B
is internal as opposed to
git checkout --detach $(merge-base A B)
that uses one extra process. I thought people loath spawning
processes?
>> Check out the latest 'origin/master' (which may be newer than
>> the base used by (2)), "merge --no-ff" the topics you newly
>
> For my own understanding, even if we use '--ff' the end result should be
> the same, but using '--no-ff' would ensure that the changes and
> conflicts are isolated to the merge commit, right?
It will make it easy for you to look at
git log --first-parent --oneline master..
by hiding the base commits you depend on behind a single merge
commit.
>> $ git merge --no-ff --into kn/ref-transaction-symref ba/zqux
>> ... rebuild the topic ...
>
> I guess you mean '--into-name' here? I would skip mentioning this since
> it doesn't have any real effect and is perhaps confusing.
Again this is to help that "one liner first-parent chain" output.
^ permalink raw reply [flat|nested] 194+ messages in thread