diff --git a/branch.c b/branch.c index 6719a181bd1f03af21b92d8be71a93142ef700e7..dd64f506694b1ff6706a3535775e0194b38ea40b 100644 --- a/branch.c +++ b/branch.c @@ -623,7 +623,7 @@ void create_branch(struct repository *r, if (!transaction || ref_transaction_update(transaction, ref.buf, &oid, forcing ? NULL : null_oid(), - 0, msg, &err) || + 0, msg, NULL, &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 92eda20683ca2d07c24a03f5903d1e604ce54763..525e7a4872e476bab2508d61ef66571d3c220050 100644 --- a/builtin/fast-import.c +++ b/builtin/fast-import.c @@ -1644,7 +1644,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) || + 0, msg, NULL, &err) || ref_transaction_commit(transaction, &err)) { ref_transaction_free(transaction); error("%s", err.buf); @@ -1684,8 +1684,8 @@ static void dump_tags(void) strbuf_reset(&ref_name); strbuf_addf(&ref_name, "refs/tags/%s", t->name); - if (ref_transaction_update(transaction, ref_name.buf, - &t->oid, NULL, 0, msg, &err)) { + if (ref_transaction_update(transaction, ref_name.buf, &t->oid, + NULL, 0, msg, NULL, &err)) { failure |= error("%s", err.buf); goto cleanup; } diff --git a/builtin/fetch.c b/builtin/fetch.c index 0a7a1a34765483e9f207ec26c5ca408df88c9dfc..0e7033cba9fcebe8744a256fc7e649778afcc878 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -667,7 +667,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); + 0, msg, NULL, &err); if (ret) { ret = STORE_REF_ERROR_OTHER; goto out; diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index db656074857e765c8b5d5c91ab1b872de0bad66d..19f5db2650b8370da68921aca96fa69547f7a6cb 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -1592,7 +1592,7 @@ static const char *update(struct command *cmd, struct shallow_info *si) namespaced_name, new_oid, old_oid, 0, "push", - &err)) { + NULL, &err)) { rp_error("%s", err.buf); ret = "failed to update ref"; } else { diff --git a/builtin/replace.c b/builtin/replace.c index da59600ad22fda16155a278a210478fb55d0d94e..b3281758d09fb0419aa22f1b418cfabe9073ae17 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) || + 0, NULL, NULL, &err) || ref_transaction_commit(transaction, &err)) res = error("%s", err.buf); diff --git a/builtin/tag.c b/builtin/tag.c index 19a7e06bf414ccf811c93f0ac941a142a7fabef2..8de32535de7dbc178b9e8264fa7b3dfded111d2f 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -624,7 +624,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) if (!transaction || ref_transaction_update(transaction, ref.buf, &object, &prev, create_reflog ? REF_FORCE_CREATE_REFLOG : 0, - reflog_msg.buf, &err) || + reflog_msg.buf, NULL, &err) || ref_transaction_commit(transaction, &err)) { if (path) fprintf(stderr, diff --git a/builtin/update-ref.c b/builtin/update-ref.c index 61338a01ecfc0ee4f029215149651bb9ac03da1e..d5c08306c03339a25ad79cb3e8399993180b6e61 100644 --- a/builtin/update-ref.c +++ b/builtin/update-ref.c @@ -205,10 +205,52 @@ static void parse_cmd_update(struct ref_transaction *transaction, if (ref_transaction_update(transaction, refname, &new_oid, have_old ? &old_oid : NULL, update_flags | create_reflog_flag, - msg, &err)) + msg, NULL, &err)) + die("%s", err.buf); + + update_flags = default_flags; + free(refname); + strbuf_release(&err); +} + +static void parse_cmd_update_symref(struct ref_transaction *transaction, + const char *next, const char *end) +{ + struct strbuf err = STRBUF_INIT; + char *refname, *symref; + + refname = parse_refname(&next); + if (!refname) + die("update-symbolic: missing "); + + if (line_termination) { + /* Without -z, consume SP and use next argument */ + if (!*next || *next == line_termination) + die("update-symbolic %s: missing ", refname); + if (*next != ' ') + die("update-symbolic %s: expected SP but got: %s", + refname, next); + } else { + /* With -z, read the next NUL-terminated line */ + if (*next) + die("update-symbolic %s: missing ", refname); + } + next++; + + symref = parse_refname(&next); + if (!symref) + die("update-symbolic %s: missing ", refname); + + if (*next != line_termination) + die("update-symbolic %s: extra input: %s", refname, next); + + if (ref_transaction_update(transaction, refname, NULL, NULL, + update_flags | create_reflog_flag | REF_UPDATE_SYMREF, + msg, symref, &err)) die("%s", err.buf); update_flags = default_flags; + free(symref); free(refname); strbuf_release(&err); } @@ -379,15 +421,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 }, + { "update-symref", parse_cmd_update_symref, 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 }, }; static void update_refs_stdin(void) diff --git a/refs.c b/refs.c index 55d2e0b2cb9e959443e98eb329fdf97eff9073a9..a049809dc7a9b63ac5fc8d6c957aef8c62ab6171 100644 --- a/refs.c +++ b/refs.c @@ -29,6 +29,7 @@ #include "date.h" #include "commit.h" #include "wildmatch.h" +#include "wrapper.h" /* * List of all available backends @@ -1216,6 +1217,7 @@ void ref_transaction_free(struct ref_transaction *transaction) } for (i = 0; i < transaction->nr; i++) { + free(transaction->updates[i]->symref_target); free(transaction->updates[i]->msg); free(transaction->updates[i]); } @@ -1228,7 +1230,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 *msg) + const char *msg, const char *symref) { struct ref_update *update; @@ -1245,6 +1247,9 @@ struct ref_update *ref_transaction_add_update( oidcpy(&update->new_oid, new_oid); if (flags & REF_HAVE_OLD) oidcpy(&update->old_oid, old_oid); + if (flags & REF_UPDATE_SYMREF) + update->symref_target = xstrdup(symref); + update->msg = normalize_reflog_message(msg); return update; } @@ -1254,7 +1259,7 @@ int ref_transaction_update(struct ref_transaction *transaction, const struct object_id *new_oid, const struct object_id *old_oid, unsigned int flags, const char *msg, - struct strbuf *err) + const char *symref, struct strbuf *err) { assert(err); @@ -1280,7 +1285,7 @@ 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, msg, symref); return 0; } @@ -1295,7 +1300,7 @@ 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(), flags, msg, NULL, err); } int ref_transaction_delete(struct ref_transaction *transaction, @@ -1308,7 +1313,7 @@ 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); + flags, msg, NULL, err); } int ref_transaction_verify(struct ref_transaction *transaction, @@ -1320,8 +1325,8 @@ int ref_transaction_verify(struct ref_transaction *transaction, if (!old_oid) BUG("verify called with old_oid set to NULL"); return ref_transaction_update(transaction, refname, - NULL, old_oid, - flags, NULL, err); + NULL, old_oid, flags, + NULL, NULL, err); } int refs_update_ref(struct ref_store *refs, const char *msg, @@ -1336,7 +1341,7 @@ 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) || + NULL, &err) || ref_transaction_commit(t, &err)) { ret = 1; ref_transaction_free(t); @@ -2337,6 +2342,10 @@ static int run_transaction_hook(struct ref_transaction *transaction, for (i = 0; i < transaction->nr; i++) { struct ref_update *update = transaction->updates[i]; + // Reference transaction does not support symbolic updates. + if (update->flags & REF_UPDATE_SYMREF) + continue; + strbuf_reset(&buf); strbuf_addf(&buf, "%s %s %s\n", oid_to_hex(&update->old_oid), diff --git a/refs.h b/refs.h index 298caf6c6184cc3a23acf78d1a0e3dc8c7d8614c..073653d1a47b262abc14fffd5f4b69fb26e373ee 100644 --- a/refs.h +++ b/refs.h @@ -694,13 +694,18 @@ struct ref_transaction *ref_transaction_begin(struct strbuf *err); */ #define REF_SKIP_REFNAME_VERIFICATION (1 << 11) +/* + * Used to denote a symbolic reference update. + */ +#define REF_UPDATE_SYMREF (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_UPDATE_SYMREF) /* * Add a reference update to transaction. `new_oid` is the value that @@ -721,7 +726,7 @@ int ref_transaction_update(struct ref_transaction *transaction, const struct object_id *new_oid, const struct object_id *old_oid, unsigned int flags, const char *msg, - struct strbuf *err); + const char *symref, struct strbuf *err); /* * Add a reference creation to transaction. new_oid is the value that diff --git a/refs/files-backend.c b/refs/files-backend.c index a098d14ea00ed6db449e5d3cc8dff28594228977..c2d9e4409e18eb976d1b037acefdea2ba57809d0 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); if (ref_transaction_commit(transaction, &err)) goto cleanup; @@ -1292,8 +1292,8 @@ static int files_pack_refs(struct ref_store *ref_store, * packed-refs transaction: */ if (ref_transaction_update(transaction, iter->refname, - iter->oid, NULL, - REF_NO_DEREF, NULL, &err)) + iter->oid, NULL, REF_NO_DEREF, + NULL, NULL, &err)) die("failure preparing to create packed reference %s: %s", iter->refname, err.buf); @@ -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; } @@ -2309,7 +2323,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); + update->msg, update->symref_target); /* * Add "HEAD". This insertion is O(N) in the transaction @@ -2372,7 +2386,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); + update->msg, update->symref_target); new_update->parent_update = update; @@ -2383,6 +2397,7 @@ static int split_symref_update(struct ref_update *update, */ update->flags |= REF_LOG_ONLY | REF_NO_DEREF; update->flags &= ~REF_HAVE_OLD; + update->flags &= ~REF_UPDATE_SYMREF; /* * Add the referent. This insertion is O(N) in the transaction @@ -2553,6 +2568,9 @@ static int lock_ref_for_update(struct files_ref_store *refs, } } + if (update->flags & REF_UPDATE_SYMREF) + ret = create_symref_lock(refs, lock, update->refname, update->symref_target); + if ((update->flags & REF_HAVE_NEW) && !(update->flags & REF_DELETING) && !(update->flags & REF_LOG_ONLY)) { @@ -2583,6 +2601,7 @@ static int lock_ref_for_update(struct files_ref_store *refs, update->flags |= REF_NEEDS_COMMIT; } } + if (!(update->flags & REF_NEEDS_COMMIT)) { /* * We didn't call write_ref_to_lockfile(), so @@ -2595,6 +2614,9 @@ static int lock_ref_for_update(struct files_ref_store *refs, ret = TRANSACTION_GENERIC_ERROR; goto out; } + + if (update->flags & REF_UPDATE_SYMREF) + update->flags |= REF_NEEDS_COMMIT; } out: @@ -2763,7 +2785,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); } } @@ -3048,7 +3070,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); } if (packed_refs_lock(refs->packed_ref_store, 0, err)) { diff --git a/refs/refs-internal.h b/refs/refs-internal.h index 56641aa57a138da17037307d37e1ca28baa2a1ee..3fccf784d498be1d513c7615a75fab1f79488d20 100644 --- a/refs/refs-internal.h +++ b/refs/refs-internal.h @@ -134,6 +134,12 @@ struct ref_update { unsigned int type; char *msg; + /* + * If (flags & REF_UPDATE_SYMREF), we update the reference to be a + * symbolic reference and the value is taken from this field. + */ + char *symref_target; + /* * If this ref_update was split off of a symref update via * split_symref_update(), then this member points at that @@ -164,8 +170,8 @@ int ref_update_reject_duplicates(struct string_list *refnames, /* * Add a ref_update with the specified properties to transaction, and * return a pointer to the new object. This function does not verify - * that refname is well-formed. new_oid and old_oid are only - * dereferenced if the REF_HAVE_NEW and REF_HAVE_OLD bits, + * that refname is well-formed. new_oid, old_oid, symref are only + * dereferenced if the REF_HAVE_NEW, REF_HAVE_OLD and REF_UPDATE_SYMREF bits, * respectively, are set in flags. */ struct ref_update *ref_transaction_add_update( @@ -173,7 +179,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 *msg); + const char *msg, const char *symref); /* * Transaction states. diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c index eaa82967ee9ae347af9e0de590d09e7b424fe60d..6208457273b990354b5165beacd144174343de00 100644 --- a/refs/reftable-backend.c +++ b/refs/reftable-backend.c @@ -840,7 +840,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, u->msg, NULL); string_list_insert(&affected_refnames, new_update->refname); } @@ -919,7 +919,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, u->msg, NULL); new_update->parent_update = u; /* diff --git a/sequencer.c b/sequencer.c index f49a871ac0666b10393331ed3c9b611921f1039e..bfe1d24bb03b689ff3ba9cdd317163546e65b475 100644 --- a/sequencer.c +++ b/sequencer.c @@ -602,7 +602,7 @@ static int fast_forward_to(struct repository *r, ref_transaction_update(transaction, "HEAD", to, unborn && !is_rebase_i(opts) ? null_oid() : from, - 0, sb.buf, &err) || + 0, sb.buf, NULL, &err) || ref_transaction_commit(transaction, &err)) { ref_transaction_free(transaction); error("%s", err.buf); @@ -1218,7 +1218,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) || + 0, sb.buf, NULL, err) || ref_transaction_commit(transaction, err)) { ret = -1; } @@ -3736,7 +3736,7 @@ static int do_label(struct repository *r, const char *name, int len) error(_("could not read HEAD")); ret = -1; } else if (ref_transaction_update(transaction, ref_name.buf, &head_oid, - NULL, 0, msg.buf, &err) < 0 || + NULL, 0, msg.buf, NULL, &err) < 0 || ref_transaction_commit(transaction, &err)) { error("%s", err.buf); ret = -1; diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh index 6ebc3ef9453b71dbc7d90a052834af482dfd48ec..5366a178c5a6edc01a1820ee6c5b1604034f33ad 100755 --- a/t/t1400-update-ref.sh +++ b/t/t1400-update-ref.sh @@ -868,6 +868,39 @@ test_expect_success 'stdin delete symref works flag --no-deref' ' test_cmp expect actual ' +test_expect_success 'stdin update-symref works' ' + git update-ref TESTSYMREFONE $a && + git symbolic-ref TESTSYMREFTWO TESTSYMREFONE && + cat >stdin <<-EOF && + update-symref TESTSYMREFTWO $b + EOF + git update-ref --stdin expect && + git symbolic-ref --no-recurse TESTSYMREFTWO >actual && + test_cmp expect actual && + git rev-parse TESTSYMREFONE >actual && + git rev-parse $b >expect && + test_cmp expect actual && + git symbolic-ref TESTSYMREFONE >actual && + echo $b >expect && + test_cmp expect actual +' + +test_expect_success 'stdin update-symref works flag --no-deref' ' + git update-ref TESTREFONE $a && + git symbolic-ref TESTSYMREFTWO TESTSYMREFONE && + cat >stdin <<-EOF && + update-symref TESTSYMREFTWO $b + EOF + git update-ref --no-deref --stdin expect && + git rev-parse TESTREFONE >actual && + test_cmp expect actual && + echo $b >expect && + git symbolic-ref --no-recurse TESTSYMREFTWO >actual && + test_cmp expect actual +' + test_expect_success 'stdin delete ref works with right old value' ' echo "delete $b $m~1" >stdin && git update-ref --stdin stdin <<-EOF && + start + update-symref TESTSYMREFONE $b + prepare + commit + EOF + git update-ref --no-deref --stdin expect && + git symbolic-ref TESTSYMREFONE >actual && + test_cmp expect actual +' + +test_expect_success 'transaction can abort symref update' ' + git symbolic-ref TESTSYMREFONE $a && + cat >stdin <<-EOF && + start + update-symref TESTSYMREFONE $b + prepare + abort + EOF + git update-ref --no-deref --stdin expect && + git symbolic-ref TESTSYMREFONE >actual && + test_cmp expect actual +' + test_done diff --git a/walker.c b/walker.c index 65002a7220adc2e60aafe6a29555df1cee167ad0..33eef703cecd9dd6d3c47491e68c7db8fe7dda11 100644 --- a/walker.c +++ b/walker.c @@ -326,7 +326,7 @@ int walker_fetch(struct walker *walker, int targets, char **target, if (ref_transaction_update(transaction, refname.buf, oids + i, NULL, 0, msg ? msg : "fetch (unknown)", - &err)) { + NULL, &err)) { error("%s", err.buf); goto done; }