{"thread":{"id":"34797","subject":"[PATCH/RFC 3/7] refs: factor update_ref steps into helpers","startedAt":"2013-08-29T14:11:48Z","lastAt":"2013-09-11T16:07:56Z","messageCount":106,"participants":["Brad King","Martin Fick","Junio C Hamano","Michael Haggerty","Eric Sunshine","Ramkumar Ramachandra"],"isPatch":true,"patchVersion":1,"patchTotal":7},"messages":[{"id":"226161","messageId":"cover.1377784597.git.brad.king@kitware.com","threadId":"34797","inReplyTo":null,"subject":"[PATCH/RFC 0/7] Multiple simultaneously locked ref updates","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-08-29T14:11:48Z","receivedAt":"2013-08-29T14:11:48Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"Hi Folks,\n\nWhile thinking about some how some server-side branch management\nservices might work, I came across a need to update multiple refs\nlocked with verified old values simultaneously.  For example, to\ntransfer ownership of some commits by rewinding a branch and creating\na new branch at the original head, one must lock both refs.\nOtherwise, depending on the order of updates another process could\ncreate the new branch after we've rewound the original, or add commits\nto the original after we've created the new branch.\n\nThis series teaches update-ref a new --stdin option to read update and\ndelete instructions from lines of standard input, lock all refs up\nfront with verified old values, and then perform the modifications.\nThis is still work in progress, but it is ready for comments and\nfeedback.  The series is based on master as of v1.8.4.\n\nNotable unfinished work:\n\n* I propose a format for stdin lines in the last commit of the series\n  as a proof-of-concept but I invite suggestions of better formats.\n  The format must be able to specify updates and deletes with optional\n  old values and optional no-deref.\n\n* No tests for new features, though existing tests pass for me.\n\n* No check for duplicate refs in input.  Currently a duplicate ref\n  will result in a failure message like:\n\n   fatal: Unable to create '....lock': File exists.\n   If no other git process is currently running, this probably means a\n   git process crashed in this repository earlier. Make sure no other git\n   process is running and remove the file manually to continue.\n\n  Instead we should reject duplicate ref names up front.  I would\n  appreciate suggestions about an efficient data structure already\n  available in Git to perform this lookup.\n\nI welcome feedback on the approach, interface, and implementation.\n\nThanks,\n-Brad\n\nBrad King (7):\n  reset: rename update_refs to reset_refs\n  refs: report ref type from lock_any_ref_for_update\n  refs: factor update_ref steps into helpers\n  refs: factor delete_ref loose ref step into a helper\n  refs: add function to repack without multiple refs\n  refs: add update_refs for multiple simultaneous updates\n  update-ref: support multiple simultaneous updates\n\n Documentation/git-update-ref.txt |   19 ++++-\n branch.c                         |    2 +-\n builtin/commit.c                 |    2 +-\n builtin/fetch.c                  |    2 +-\n builtin/receive-pack.c           |    2 +-\n builtin/reflog.c                 |    2 +-\n builtin/replace.c                |    2 +-\n builtin/reset.c                  |    4 +-\n builtin/tag.c                    |    2 +-\n builtin/update-ref.c             |   93 ++++++++++++++++++++++-\n fast-import.c                    |    2 +-\n refs.c                           |  150 ++++++++++++++++++++++++++++++++------\n refs.h                           |   13 +++-\n sequencer.c                      |    2 +-\n 14 files changed, 262 insertions(+), 35 deletions(-)\n\n-- \n1.7.10.4\n"},{"id":"226162","messageId":"e2d48324726825844c209e4ca544b5c20ffeb11b.1377784597.git.brad.king@kitware.com","threadId":"34797","inReplyTo":"cover.1377784597.git.brad.king@kitware.com","subject":"[PATCH/RFC 1/7] reset: rename update_refs to reset_refs","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-08-29T14:11:49Z","receivedAt":"2013-08-29T14:11:49Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"Get it out of the way for a future refs.h function.\n\nSigned-off-by: Brad King <brad.king@kitware.com>\n---\n builtin/reset.c |    4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/reset.c b/builtin/reset.c\nindex afa6e02..789ee48 100644\n--- a/builtin/reset.c\n+++ b/builtin/reset.c\n@@ -219,7 +219,7 @@ static const char **parse_args(const char **argv, const char *prefix, const char\n \treturn argv[0] ? get_pathspec(prefix, argv) : NULL;\n }\n \n-static int update_refs(const char *rev, const unsigned char *sha1)\n+static int reset_refs(const char *rev, const unsigned char *sha1)\n {\n \tint update_ref_status;\n \tstruct strbuf msg = STRBUF_INIT;\n@@ -350,7 +350,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)\n \tif (!pathspec && !unborn) {\n \t\t/* Any resets without paths update HEAD to the head being\n \t\t * switched to, saving the previous head in ORIG_HEAD before. */\n-\t\tupdate_ref_status = update_refs(rev, sha1);\n+\t\tupdate_ref_status = reset_refs(rev, sha1);\n \n \t\tif (reset_type == HARD && !update_ref_status && !quiet)\n \t\t\tprint_new_head_line(lookup_commit_reference(sha1));\n-- \n1.7.10.4\n"},{"id":"226159","messageId":"80d72f6f4bf04a8fef8ebfcc02a463e769cea3d4.1377784597.git.brad.king@kitware.com","threadId":"34797","inReplyTo":"cover.1377784597.git.brad.king@kitware.com","subject":"[PATCH/RFC 2/7] refs: report ref type from lock_any_ref_for_update","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-08-29T14:11:50Z","receivedAt":"2013-08-29T14:11:50Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"Expose lock_ref_sha1_basic's type_p argument to callers of\nlock_any_ref_for_update.  Update all call sites to ignore it; we will\nuse it later.\n\nSigned-off-by: Brad King <brad.king@kitware.com>\n---\n branch.c               |    2 +-\n builtin/commit.c       |    2 +-\n builtin/fetch.c        |    2 +-\n builtin/receive-pack.c |    2 +-\n builtin/reflog.c       |    2 +-\n builtin/replace.c      |    2 +-\n builtin/tag.c          |    2 +-\n fast-import.c          |    2 +-\n refs.c                 |    7 ++++---\n refs.h                 |    2 +-\n sequencer.c            |    2 +-\n 11 files changed, 14 insertions(+), 13 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex c5c6984..c244483 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -291,7 +291,7 @@ void create_branch(const char *head,\n \thashcpy(sha1, commit->object.sha1);\n \n \tif (!dont_change_ref) {\n-\t\tlock = lock_any_ref_for_update(ref.buf, NULL, 0);\n+\t\tlock = lock_any_ref_for_update(ref.buf, NULL, 0, 0);\n \t\tif (!lock)\n \t\t\tdie_errno(_(\"Failed to lock ref for update\"));\n \t}\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 10acc53..78d773f 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -1618,7 +1618,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \t\t\t\t\t   !current_head\n \t\t\t\t\t   ? NULL\n \t\t\t\t\t   : current_head->object.sha1,\n-\t\t\t\t\t   0);\n+\t\t\t\t\t   0, 0);\n \n \tnl = strchr(sb.buf, '\\n');\n \tif (nl)\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex d784b2e..34903ef 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -246,7 +246,7 @@ static int s_update_ref(const char *action,\n \t\trla = default_rla.buf;\n \tsnprintf(msg, sizeof(msg), \"%s: %s\", rla, action);\n \tlock = lock_any_ref_for_update(ref->name,\n-\t\t\t\t       check_old ? ref->old_sha1 : NULL, 0);\n+\t\t\t\t       check_old ? ref->old_sha1 : NULL, 0, 0);\n \tif (!lock)\n \t\treturn errno == ENOTDIR ? STORE_REF_ERROR_DF_CONFLICT :\n \t\t\t\t\t  STORE_REF_ERROR_OTHER;\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex e3eb5fc..dd61234 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -524,7 +524,7 @@ static const char *update(struct command *cmd)\n \t\treturn NULL; /* good */\n \t}\n \telse {\n-\t\tlock = lock_any_ref_for_update(namespaced_name, old_sha1, 0);\n+\t\tlock = lock_any_ref_for_update(namespaced_name, old_sha1, 0, 0);\n \t\tif (!lock) {\n \t\t\trp_error(\"failed to lock %s\", name);\n \t\t\treturn \"failed to lock\";\ndiff --git a/builtin/reflog.c b/builtin/reflog.c\nindex 54184b3..11b30f9 100644\n--- a/builtin/reflog.c\n+++ b/builtin/reflog.c\n@@ -366,7 +366,7 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused,\n \t * we take the lock for the ref itself to prevent it from\n \t * getting updated.\n \t */\n-\tlock = lock_any_ref_for_update(ref, sha1, 0);\n+\tlock = lock_any_ref_for_update(ref, sha1, 0, 0);\n \tif (!lock)\n \t\treturn error(\"cannot lock ref '%s'\", ref);\n \tlog_file = git_pathdup(\"logs/%s\", ref);\ndiff --git a/builtin/replace.c b/builtin/replace.c\nindex 59d3115..e2e2002 100644\n--- a/builtin/replace.c\n+++ b/builtin/replace.c\n@@ -105,7 +105,7 @@ static int replace_object(const char *object_ref, const char *replace_ref,\n \telse if (!force)\n \t\tdie(\"replace ref '%s' already exists\", ref);\n \n-\tlock = lock_any_ref_for_update(ref, prev, 0);\n+\tlock = lock_any_ref_for_update(ref, prev, 0, 0);\n \tif (!lock)\n \t\tdie(\"%s: cannot lock the ref\", ref);\n \tif (write_ref_sha1(lock, repl, NULL) < 0)\ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex af3af3f..c261469 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -577,7 +577,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \tif (annotate)\n \t\tcreate_tag(object, tag, &buf, &opt, prev, object);\n \n-\tlock = lock_any_ref_for_update(ref.buf, prev, 0);\n+\tlock = lock_any_ref_for_update(ref.buf, prev, 0, 0);\n \tif (!lock)\n \t\tdie(_(\"%s: cannot lock the ref\"), ref.buf);\n \tif (write_ref_sha1(lock, object, NULL) < 0)\ndiff --git a/fast-import.c b/fast-import.c\nindex 23f625f..5f7ef82 100644\n--- a/fast-import.c\n+++ b/fast-import.c\n@@ -1678,7 +1678,7 @@ static int update_branch(struct branch *b)\n \t\treturn 0;\n \tif (read_ref(b->name, old_sha1))\n \t\thashclr(old_sha1);\n-\tlock = lock_any_ref_for_update(b->name, old_sha1, 0);\n+\tlock = lock_any_ref_for_update(b->name, old_sha1, 0, 0);\n \tif (!lock)\n \t\treturn error(\"Unable to lock %s\", b->name);\n \tif (!force_update && !is_null_sha1(old_sha1)) {\ndiff --git a/refs.c b/refs.c\nindex 7922261..3a7b597 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2121,11 +2121,12 @@ struct ref_lock *lock_ref_sha1(const char *refname, const unsigned char *old_sha\n }\n \n struct ref_lock *lock_any_ref_for_update(const char *refname,\n-\t\t\t\t\t const unsigned char *old_sha1, int flags)\n+\t\t\t\t\t const unsigned char *old_sha1,\n+\t\t\t\t\t int flags, int *type_p)\n {\n \tif (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL))\n \t\treturn NULL;\n-\treturn lock_ref_sha1_basic(refname, old_sha1, flags, NULL);\n+\treturn lock_ref_sha1_basic(refname, old_sha1, flags, type_p);\n }\n \n /*\n@@ -3174,7 +3175,7 @@ int update_ref(const char *action, const char *refname,\n \t\tint flags, enum action_on_err onerr)\n {\n \tstatic struct ref_lock *lock;\n-\tlock = lock_any_ref_for_update(refname, oldval, flags);\n+\tlock = lock_any_ref_for_update(refname, oldval, flags, 0);\n \tif (!lock) {\n \t\tconst char *str = \"Cannot lock the ref '%s'.\";\n \t\tswitch (onerr) {\ndiff --git a/refs.h b/refs.h\nindex 9e5db3a..2cd307a 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -137,7 +137,7 @@ extern struct ref_lock *lock_ref_sha1(const char *refname, const unsigned char *\n #define REF_NODEREF\t0x01\n extern struct ref_lock *lock_any_ref_for_update(const char *refname,\n \t\t\t\t\t\tconst unsigned char *old_sha1,\n-\t\t\t\t\t\tint flags);\n+\t\t\t\t\t\tint flags, int *type_p);\n \n /** Close the file descriptor owned by a lock and return the status */\n extern int close_ref(struct ref_lock *lock);\ndiff --git a/sequencer.c b/sequencer.c\nindex 351548f..4180d58 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -279,7 +279,7 @@ static int fast_forward_to(const unsigned char *to, const unsigned char *from,\n \tread_cache();\n \tif (checkout_fast_forward(from, to, 1))\n \t\texit(1); /* the callee should have complained already */\n-\tref_lock = lock_any_ref_for_update(\"HEAD\", unborn ? null_sha1 : from, 0);\n+\tref_lock = lock_any_ref_for_update(\"HEAD\", unborn ? null_sha1 : from, 0, 0);\n \tstrbuf_addf(&sb, \"%s: fast-forward\", action_name(opts));\n \tret = write_ref_sha1(ref_lock, to, sb.buf);\n \tstrbuf_release(&sb);\n-- \n1.7.10.4\n"},{"id":"226158","messageId":"85ddf42be97e92e33ca883bc23cafca818a3df85.1377784597.git.brad.king@kitware.com","threadId":"34797","inReplyTo":"cover.1377784597.git.brad.king@kitware.com","subject":"[PATCH/RFC 3/7] refs: factor update_ref steps into helpers","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-08-29T14:11:51Z","receivedAt":"2013-08-29T14:11:51Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"Factor the lock and write steps and error handling into helper functions\nupdate_ref_lock and update_ref_write to allow later use elsewhere.\nExpose lock_any_ref_for_update's type_p to update_ref_lock callers.\n\nSigned-off-by: Brad King <brad.king@kitware.com>\n---\n refs.c |   28 +++++++++++++++++++++++-----\n 1 file changed, 23 insertions(+), 5 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 3a7b597..2e755b4 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -3170,12 +3170,13 @@ int for_each_reflog(each_ref_fn fn, void *cb_data)\n \treturn retval;\n }\n \n-int update_ref(const char *action, const char *refname,\n-\t\tconst unsigned char *sha1, const unsigned char *oldval,\n-\t\tint flags, enum action_on_err onerr)\n+static struct ref_lock *update_ref_lock(const char *refname,\n+\t\t\t\t\tconst unsigned char *oldval,\n+\t\t\t\t\tint flags, int *type_p,\n+\t\t\t\t\tenum action_on_err onerr)\n {\n \tstatic struct ref_lock *lock;\n-\tlock = lock_any_ref_for_update(refname, oldval, flags, 0);\n+\tlock = lock_any_ref_for_update(refname, oldval, flags, type_p);\n \tif (!lock) {\n \t\tconst char *str = \"Cannot lock the ref '%s'.\";\n \t\tswitch (onerr) {\n@@ -3183,8 +3184,14 @@ int update_ref(const char *action, const char *refname,\n \t\tcase DIE_ON_ERR: die(str, refname); break;\n \t\tcase QUIET_ON_ERR: break;\n \t\t}\n-\t\treturn 1;\n \t}\n+\treturn lock;\n+}\n+\n+static int update_ref_write(const char *action, const char *refname,\n+\t\t\t    const unsigned char *sha1, struct ref_lock *lock,\n+\t\t\t    enum action_on_err onerr)\n+{\n \tif (write_ref_sha1(lock, sha1, action) < 0) {\n \t\tconst char *str = \"Cannot update the ref '%s'.\";\n \t\tswitch (onerr) {\n@@ -3197,6 +3204,17 @@ int update_ref(const char *action, const char *refname,\n \treturn 0;\n }\n \n+int update_ref(const char *action, const char *refname,\n+\t       const unsigned char *sha1, const unsigned char *oldval,\n+\t       int flags, enum action_on_err onerr)\n+{\n+\tstatic struct ref_lock *lock;\n+\tlock = update_ref_lock(refname, oldval, flags, 0, onerr);\n+\tif (!lock)\n+\t\treturn 1;\n+\treturn update_ref_write(action, refname, sha1, lock, onerr);\n+}\n+\n struct ref *find_ref_by_name(const struct ref *list, const char *name)\n {\n \tfor ( ; list; list = list->next)\n-- \n1.7.10.4\n"},{"id":"226163","messageId":"f5a6f3552b16519b6568a7c745ab61c26bc4a072.1377784597.git.brad.king@kitware.com","threadId":"34797","inReplyTo":"cover.1377784597.git.brad.king@kitware.com","subject":"[PATCH/RFC 4/7] refs: factor delete_ref loose ref step into a helper","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-08-29T14:11:52Z","receivedAt":"2013-08-29T14:11:52Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"Factor loose ref deletion into helper function delete_ref_loose to allow\nlater use elsewhere.  While at it, rename local names 'flag => type' and\n'delopt => flags' for consistency with callers and called functions.\n\nSigned-off-by: Brad King <brad.king@kitware.com>\n---\n refs.c |   24 ++++++++++++++++--------\n 1 file changed, 16 insertions(+), 8 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 2e755b4..5908648 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2450,15 +2450,10 @@ static int repack_without_ref(const char *refname)\n \treturn commit_packed_refs();\n }\n \n-int delete_ref(const char *refname, const unsigned char *sha1, int delopt)\n+static int delete_ref_loose(struct ref_lock *lock, int type)\n {\n-\tstruct ref_lock *lock;\n-\tint err, i = 0, ret = 0, flag = 0;\n-\n-\tlock = lock_ref_sha1_basic(refname, sha1, delopt, &flag);\n-\tif (!lock)\n-\t\treturn 1;\n-\tif (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) {\n+\tint err, i, ret = 0;\n+\tif (!(type & REF_ISPACKED) || type & REF_ISSYMREF) {\n \t\t/* loose */\n \t\ti = strlen(lock->lk->filename) - 5; /* .lock */\n \t\tlock->lk->filename[i] = 0;\n@@ -2468,6 +2463,19 @@ int delete_ref(const char *refname, const unsigned char *sha1, int delopt)\n \n \t\tlock->lk->filename[i] = '.';\n \t}\n+\treturn ret;\n+}\n+\n+int delete_ref(const char *refname, const unsigned char *sha1, int flags)\n+{\n+\tstruct ref_lock *lock;\n+\tint ret = 0, type = 0;\n+\n+\tlock = lock_ref_sha1_basic(refname, sha1, flags, &type);\n+\tif (!lock)\n+\t\treturn 1;\n+\tret |= delete_ref_loose(lock, type);\n+\n \t/* removing the loose one could have resurrected an earlier\n \t * packed one.  Also, if it was not loose we need to repack\n \t * without it.\n-- \n1.7.10.4\n"},{"id":"226160","messageId":"85ef4bbb02b7648560d016a5b7ec62df9d394bdb.1377784597.git.brad.king@kitware.com","threadId":"34797","inReplyTo":"cover.1377784597.git.brad.king@kitware.com","subject":"[PATCH/RFC 5/7] refs: add function to repack without multiple refs","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-08-29T14:11:53Z","receivedAt":"2013-08-29T14:11:53Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"Generalize repack_without_ref as repack_without_refs to support a list\nof refs and implement the former in terms of the latter.\n\nSigned-off-by: Brad King <brad.king@kitware.com>\n---\n refs.c |   29 ++++++++++++++++++++++-------\n 1 file changed, 22 insertions(+), 7 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 5908648..5a6c14e 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2414,25 +2414,35 @@ static int curate_packed_ref_fn(struct ref_entry *entry, void *cb_data)\n \treturn 0;\n }\n \n-static int repack_without_ref(const char *refname)\n+static int repack_without_refs(const char **refnames, int n)\n {\n \tstruct ref_dir *packed;\n \tstruct string_list refs_to_delete = STRING_LIST_INIT_DUP;\n \tstruct string_list_item *ref_to_delete;\n+\tint i, removed = 0;\n+\n+\t/* Look for a packed ref: */\n+\tfor (i = 0; i < n; ++i)\n+\t\tif (get_packed_ref(refnames[i]))\n+\t\t\tbreak;\n \n-\tif (!get_packed_ref(refname))\n-\t\treturn 0; /* refname does not exist in packed refs */\n+\t/* Avoid locking if we have nothing to do: */\n+\tif(i == n)\n+\t\treturn 0; /* no refname exists in packed refs */\n \n \tif (lock_packed_refs(0)) {\n \t\tunable_to_lock_error(git_path(\"packed-refs\"), errno);\n-\t\treturn error(\"cannot delete '%s' from packed refs\", refname);\n+\t\treturn error(\"cannot delete '%s' from packed refs\", refnames[i]);\n \t}\n \tpacked = get_packed_refs(&ref_cache);\n \n-\t/* Remove refname from the cache: */\n-\tif (remove_entry(packed, refname) == -1) {\n+\t/* Remove refnames from the cache: */\n+\tfor (i = 0; i < n; ++i)\n+\t\tif (remove_entry(packed, refnames[i]) != -1)\n+\t\t\tremoved = 1;\n+\tif (!removed) {\n \t\t/*\n-\t\t * The packed entry disappeared while we were\n+\t\t * All packed entries disappeared while we were\n \t\t * acquiring the lock.\n \t\t */\n \t\trollback_packed_refs();\n@@ -2450,6 +2460,11 @@ static int repack_without_ref(const char *refname)\n \treturn commit_packed_refs();\n }\n \n+static int repack_without_ref(const char *refname)\n+{\n+\treturn repack_without_refs(&refname, 1);\n+}\n+\n static int delete_ref_loose(struct ref_lock *lock, int type)\n {\n \tint err, i, ret = 0;\n-- \n1.7.10.4\n"},{"id":"226164","messageId":"518ba77096664a679e4c7212e4cc4d496c6b38d3.1377784597.git.brad.king@kitware.com","threadId":"34797","inReplyTo":"cover.1377784597.git.brad.king@kitware.com","subject":"[PATCH/RFC 6/7] refs: add update_refs for multiple simultaneous updates","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-08-29T14:11:54Z","receivedAt":"2013-08-29T14:11:54Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"Add 'struct ref_update' to encode the information needed to update or\ndelete a ref (name, new sha1, optional old sha1, no-deref flag).  Add\nfunction 'update_refs' accepting an array of updates to perform.  First\nacquire locks on all refs with verified old values.  Then update or\ndelete all refs accordingly.  Fail if any one lock cannot be obtained or\nany one old value does not match.\n\nThough the refs themeselves cannot be modified together in a single\natomic transaction, this function does enable some useful semantics.\nFor example, a caller may create a new branch starting from the head of\nanother branch and rewind the original branch at the same time.  This\ntransfers ownership of commits between branches without risk of losing\ncommits added to the original branch by a concurrent process, or risk of\na concurrent process creating the new branch first.\n\nSigned-off-by: Brad King <brad.king@kitware.com>\n---\n refs.c |   66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n refs.h |   11 +++++++++++\n 2 files changed, 77 insertions(+)\n\ndiff --git a/refs.c b/refs.c\nindex 5a6c14e..0a0c19e 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -3238,6 +3238,72 @@ int update_ref(const char *action, const char *refname,\n \treturn update_ref_write(action, refname, sha1, lock, onerr);\n }\n \n+int update_refs(const char *action, struct ref_update *updates,\n+\t\tint n, enum action_on_err onerr)\n+{\n+\tint ret = 0, delnum = 0, i;\n+\tint *types;\n+\tstruct ref_lock **locks;\n+\tconst char **delnames;\n+\n+\tif (!updates || !n)\n+\t\treturn 0;\n+\n+\t/* Allocate work space: */\n+\ttypes = xmalloc(sizeof(int)*n);\n+\tlocks = xmalloc(sizeof(struct ref_lock*)*n);\n+\tdelnames = xmalloc(sizeof(const char*)*n);\n+\n+\t/* Acquire all locks while verifying old values: */\n+\tfor (i=0; i < n; ++i) {\n+\t\tlocks[i] = update_ref_lock(updates[i].ref_name,\n+\t\t\t\t\t   updates[i].old_sha1,\n+\t\t\t\t\t   updates[i].flags,\n+\t\t\t\t\t   &types[i], onerr);\n+\t\tif (!locks[i])\n+\t\t\tbreak;\n+\t}\n+\n+\t/* Abort if we did not get all locks: */\n+\tif (i < n) {\n+\t\twhile (--i >= 0)\n+\t\t\tunlock_ref(locks[i]);\n+\t\tfree(types);\n+\t\tfree(locks);\n+\t\tfree(delnames);\n+\t\treturn 1;\n+\t}\n+\n+\t/* Perform updates first so live commits remain referenced: */\n+\tfor (i=0; i < n; ++i)\n+\t\tif (!is_null_sha1(updates[i].new_sha1)) {\n+\t\t\tret |= update_ref_write(action,\n+\t\t\t\t\t\tupdates[i].ref_name,\n+\t\t\t\t\t\tupdates[i].new_sha1,\n+\t\t\t\t\t\tlocks[i], onerr);\n+\t\t\tlocks[i] = 0; /* freed by update_ref_write */\n+\t\t}\n+\n+\t/* Perform deletes now that updates are safely completed: */\n+\tfor (i=0; i < n; ++i)\n+\t\tif (locks[i]) {\n+\t\t\tdelnames[delnum++] = locks[i]->ref_name;\n+\t\t\tret |= delete_ref_loose(locks[i], types[i]);\n+\t\t}\n+\tret |= repack_without_refs(delnames, delnum);\n+\tfor (i=0; i < delnum; ++i)\n+\t\tunlink_or_warn(git_path(\"logs/%s\", delnames[i]));\n+\tclear_loose_ref_cache(&ref_cache);\n+\tfor (i=0; i < n; ++i)\n+\t\tif (locks[i])\n+\t\t\tunlock_ref(locks[i]);\n+\n+\tfree(types);\n+\tfree(locks);\n+\tfree(delnames);\n+\treturn ret;\n+}\n+\n struct ref *find_ref_by_name(const struct ref *list, const char *name)\n {\n \tfor ( ; list; list = list->next)\ndiff --git a/refs.h b/refs.h\nindex 2cd307a..5763f3a 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -214,6 +214,17 @@ int update_ref(const char *action, const char *refname,\n \t\tconst unsigned char *sha1, const unsigned char *oldval,\n \t\tint flags, enum action_on_err onerr);\n \n+struct ref_update {\n+\tconst char *ref_name;\n+\tunsigned char new_sha1[20];\n+\tunsigned char *old_sha1;\n+\tint flags;\n+};\n+\n+/** lock all refs and then write all of them */\n+int update_refs(const char *action, struct ref_update *updates,\n+\t\tint n, enum action_on_err onerr);\n+\n extern int parse_hide_refs_config(const char *var, const char *value, const char *);\n extern int ref_is_hidden(const char *);\n \n-- \n1.7.10.4\n"},{"id":"226165","messageId":"8d323b9c2a71a9bafa8b48caf1d85c1035549b16.1377784597.git.brad.king@kitware.com","threadId":"34797","inReplyTo":"cover.1377784597.git.brad.king@kitware.com","subject":"[PATCH/RFC 7/7] update-ref: support multiple simultaneous updates","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-08-29T14:11:55Z","receivedAt":"2013-08-29T14:11:55Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"Add a --stdin signature to read update instructions from standard input\nand apply multiple ref updates and deletes together.\n\nSigned-off-by: Brad King <brad.king@kitware.com>\n---\n Documentation/git-update-ref.txt |   19 +++++++-\n builtin/update-ref.c             |   93 +++++++++++++++++++++++++++++++++++++-\n 2 files changed, 110 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt\nindex 0df13ff..a79afe8 100644\n--- a/Documentation/git-update-ref.txt\n+++ b/Documentation/git-update-ref.txt\n@@ -8,7 +8,7 @@ git-update-ref - Update the object name stored in a ref safely\n SYNOPSIS\n --------\n [verse]\n-'git update-ref' [-m <reason>] (-d <ref> [<oldvalue>] | [--no-deref] <ref> <newvalue> [<oldvalue>])\n+'git update-ref' [-m <reason>] (-d <ref> [<oldvalue>] | [--no-deref] <ref> <newvalue> [<oldvalue>] | --stdin)\n \n DESCRIPTION\n -----------\n@@ -58,6 +58,23 @@ archive by creating a symlink tree).\n With `-d` flag, it deletes the named <ref> after verifying it\n still contains <oldvalue>.\n \n+With `--stdin`, update-ref reads instructions from standard input\n+and performs all modifications together.  Specify updates with\n+lines of the form:\n+\n+\t[ --no-deref SP ] <ref> SP <newvalue> [ SP <oldvalue> ] LF\n+\n+and deletes with lines of the form:\n+\n+\t[ --no-deref SP ] -d SP <ref> [ SP <oldvalue> ] LF\n+\n+or as updates with 40 \"0\" as <newvalue>.  Blank lines are ignored.\n+Lines of any other format or a repeated <ref> produce an error.\n+If all <ref>s can be locked with matching <oldvalue>s\n+simultaneously all modifications are performed.  Otherwise, no\n+modifications are performed.  Note that while each individual\n+<ref> is updated or deleted atomically, a concurrent reader may\n+still see a subset of the modifications.\n \n Logging Updates\n ---------------\ndiff --git a/builtin/update-ref.c b/builtin/update-ref.c\nindex 51d2684..2f0d34c 100644\n--- a/builtin/update-ref.c\n+++ b/builtin/update-ref.c\n@@ -6,19 +6,102 @@\n static const char * const git_update_ref_usage[] = {\n \tN_(\"git update-ref [options] -d <refname> [<oldval>]\"),\n \tN_(\"git update-ref [options]    <refname> <newval> [<oldval>]\"),\n+\tN_(\"git update-ref [options] --stdin\"),\n \tNULL\n };\n \n+static const char blank[] = \" \\t\\r\\n\";\n+\n+static int updates_size;\n+static int updates_count;\n+static struct ref_update *updates;\n+\n+static void update_refs_stdin(const char *line)\n+{\n+\tint delete = 0, i;\n+\tconst char *c, *s, *oldvalue, *value[2] = {0,0};\n+\tstruct ref_update *update;\n+\tc = line;\n+\n+\t/* Skip blank lines: */\n+\tif (*c == '\\n')\n+\t\treturn;\n+\n+\t/* Allocate a ref_update struct: */\n+\tif (updates_count == updates_size) {\n+\t\tupdates_size = updates_size? updates_size*2 : 16;\n+\t\tupdates = xrealloc(updates, sizeof(*updates)*updates_size);\n+\t\tmemset(updates + updates_count, 0,\n+\t\t       sizeof(*updates)*(updates_size-updates_count));\n+\t}\n+\tupdate = &updates[updates_count++];\n+\n+\t/* --no-deref SP */\n+\tif (strncmp(c, \"--no-deref \", 11) == 0) {\n+\t\tc += 11;\n+\t\tupdate->flags |= REF_NODEREF;\n+\t}\n+\n+\t/* -d SP */\n+\tif (strncmp(c, \"-d \", 3) == 0) {\n+\t\tc += 3;\n+\t\tdelete = 1;\n+\t}\n+\n+\t/* <ref> */\n+\ts = c;\n+\tc = s + strcspn(s, blank);\n+\tupdate->ref_name = xstrndup(s, c-s);\n+\n+\t/* [ SP <value> ]... */\n+\tfor (i=0; i < 2; ++i) {\n+\t\tif (*c != ' ')\n+\t\t\tbreak;\n+\t\t++c;\n+\t\ts = c;\n+\t\tc = s + strcspn(s, blank);\n+\t\tvalue[i] = xstrndup(s, c-s);\n+\t}\n+\n+\tif (*c && *c != '\\n')\n+\t\tdie(\"unrecognized input line: %s\", line);\n+\n+\tif (check_refname_format(update->ref_name, REFNAME_ALLOW_ONELEVEL))\n+\t\tdie(\"invalid <ref> format on input line: %s\", line);\n+\n+\tif (delete) {\n+\t\thashclr(update->new_sha1);\n+\t\toldvalue = value[0];\n+\t\tif (value[1])\n+\t\t\tdie(\"both <newvalue> and <oldvalue> on delete line: %s\",\n+\t\t\t    line);\n+\t} else {\n+\t\tif (!value[0])\n+\t\t\tdie(\"missing <newvalue> on update line: %s\", line);\n+\t\tif (get_sha1(value[0], update->new_sha1))\n+\t\t\tdie(\"invalid <newvalue> on update line: %s\", line);\n+\t\toldvalue = value[1];\n+\t}\n+\tif (oldvalue) {\n+\t\tupdate->old_sha1 = xmalloc(20);\n+\t\tif (get_sha1(oldvalue, update->old_sha1))\n+\t\t\tdie(\"invalid <oldvalue> on %s line: %s\",\n+\t\t\t    delete? \"delete\":\"update\", line);\n+\t}\n+}\n+\n int cmd_update_ref(int argc, const char **argv, const char *prefix)\n {\n \tconst char *refname, *oldval, *msg = NULL;\n \tunsigned char sha1[20], oldsha1[20];\n-\tint delete = 0, no_deref = 0, flags = 0;\n+\tint delete = 0, no_deref = 0, read_stdin = 0, flags = 0;\n+\tchar line[1000];\n \tstruct option options[] = {\n \t\tOPT_STRING( 'm', NULL, &msg, N_(\"reason\"), N_(\"reason of the update\")),\n \t\tOPT_BOOLEAN('d', NULL, &delete, N_(\"delete the reference\")),\n \t\tOPT_BOOLEAN( 0 , \"no-deref\", &no_deref,\n \t\t\t\t\tN_(\"update <refname> not the one it points to\")),\n+\t\tOPT_BOOLEAN( 0 , \"stdin\", &read_stdin, N_(\"read updates from stdin\")),\n \t\tOPT_END(),\n \t};\n \n@@ -28,6 +111,14 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)\n \tif (msg && !*msg)\n \t\tdie(\"Refusing to perform update with empty message.\");\n \n+\tif (read_stdin) {\n+\t\tif (delete || no_deref || argc > 0)\n+\t\t\tusage_with_options(git_update_ref_usage, options);\n+\t\twhile (fgets(line, sizeof(line), stdin))\n+\t\t\tupdate_refs_stdin(line);\n+\t\treturn update_refs(msg, updates, updates_count, DIE_ON_ERR);\n+\t}\n+\n \tif (delete) {\n \t\tif (argc < 1 || argc > 2)\n \t\t\tusage_with_options(git_update_ref_usage, options);\n-- \n1.7.10.4\n"},{"id":"226178","messageId":"201308290932.18199.mfick@codeaurora.org","threadId":"34797","inReplyTo":"cover.1377784597.git.brad.king@kitware.com","subject":"Re: [PATCH/RFC 0/7] Multiple simultaneously locked ref updates","fromName":"Martin Fick","fromEmail":"mfick@codeaurora.org","sentAt":"2013-08-29T15:32:18Z","receivedAt":"2013-08-29T15:32:18Z","isPatch":true,"sender":{"key":"mfick@codeaurora.org","avatar":null},"body":"On Thursday, August 29, 2013 08:11:48 am Brad King wrote:\n> \n>    fatal: Unable to create '....lock': File exists.\n>    If no other git process is currently running, this\n> probably means a git process crashed in this repository\n> earlier. Make sure no other git process is running and\n> remove the file manually to continue.\n\nI don't believe git currently tries to do any form of stale \nlock recovery since it is racy and unreliable (both single \nserver or on a multi-server shared repo),\n\n\n-Martin\n"},{"id":"226181","messageId":"521F6CF0.6040905@kitware.com","threadId":"34797","inReplyTo":"201308290932.18199.mfick@codeaurora.org","subject":"Re: [PATCH/RFC 0/7] Multiple simultaneously locked ref updates","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-08-29T15:46:56Z","receivedAt":"2013-08-29T15:46:56Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"On 08/29/2013 11:32 AM, Martin Fick wrote:\n> On Thursday, August 29, 2013 08:11:48 am Brad King wrote:\n>>\n>>    fatal: Unable to create '....lock': File exists.\n>>    If no other git process is currently running, this\n>> probably means a git process crashed in this repository\n>> earlier. Make sure no other git process is running and\n>> remove the file manually to continue.\n> \n> I don't believe git currently tries to do any form of stale \n> lock recovery since it is racy and unreliable (both single \n> server or on a multi-server shared repo),\n\nNor should it in this case.  I was saying that the front-end\nneeds to reject duplicate ref names from the stdin lines before\ntrying to lock the ref twice to avoid this message.  I'm asking\nfor a suggestion for existing data structure capabilities in\nGit's source to efficiently detect the duplicate name.\n\n-Brad\n"},{"id":"226185","messageId":"xmqq7gf479jm.fsf@gitster.dls.corp.google.com","threadId":"34797","inReplyTo":"521F6CF0.6040905@kitware.com","subject":"Re: [PATCH/RFC 0/7] Multiple simultaneously locked ref updates","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-08-29T16:21:01Z","receivedAt":"2013-08-29T16:21:01Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Brad King <brad.king@kitware.com> writes:\n\n> Nor should it in this case.  I was saying that the front-end\n> needs to reject duplicate ref names from the stdin lines before\n> trying to lock the ref twice to avoid this message.\n\nHow about trying not to feed duplicates?\n"},{"id":"226188","messageId":"521F8033.6070302@kitware.com","threadId":"34797","inReplyTo":"xmqq7gf479jm.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH/RFC 0/7] Multiple simultaneously locked ref updates","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-08-29T17:09:07Z","receivedAt":"2013-08-29T17:09:07Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"On 08/29/2013 12:21 PM, Junio C Hamano wrote:\n> Brad King <brad.king@kitware.com> writes:\n>> needs to reject duplicate ref names from the stdin lines before\n>> trying to lock the ref twice to avoid this message.\n> \n> How about trying not to feed duplicates?\n\nSure, perhaps it is simplest to push the responsibility on the user\nto avoid duplicates.  However, the error message will need to be\nre-worded to distinguish this case from a stale lock or competing\nprocess since both locks may come from the same update-ref process.\n\nWithout checking the input for duplicates ourselves we cannot\ndistinguish these cases to provide a more informative error message.\nHowever, such a check would add runtime overhead even for valid input.\nIf we prefer to avoid input validation then here is proposed new\nwording for the lock failure message:\n\n--------------------------------------------------------------------\nfatal: Unable to create '....lock': File exists.\n\nThe lock file may exist because:\n- another running git process already has the lock, or\n- this process already has the lock because it was asked to\n  update the same file multiple times simultaneously, or\n- a stale lock is left from a git process that crashed earlier.\nIn the last case, make sure no other git process is running and\nremove the file manually to continue.\n--------------------------------------------------------------------\n\nIIUC the message cannot say anything about a 'ref' because it is\nused for other file type lock failures too.\n\nComments?\n-Brad\n"},{"id":"226189","messageId":"xmqqy57k5sca.fsf@gitster.dls.corp.google.com","threadId":"34797","inReplyTo":"e2d48324726825844c209e4ca544b5c20ffeb11b.1377784597.git.brad.king@kitware.com","subject":"Re: [PATCH/RFC 1/7] reset: rename update_refs to reset_refs","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-08-29T17:17:57Z","receivedAt":"2013-08-29T17:17:57Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Brad King <brad.king@kitware.com> writes:\n\n> Get it out of the way for a future refs.h function.\n\nReaders do not know if \"update_refs()\" is a good name for that\nfuture refs.h function at this point, so \"evict squatter\" is not a\nvery good justification by itself.  I do agree that this static\nfunction is resetting a ref, not doing an arbitrary update, and the\nnew name is a better match than the old one for what it does, though.\n\n> Signed-off-by: Brad King <brad.king@kitware.com>\n> ---\n>  builtin/reset.c |    4 ++--\n>  1 file changed, 2 insertions(+), 2 deletions(-)\n>\n> diff --git a/builtin/reset.c b/builtin/reset.c\n> index afa6e02..789ee48 100644\n> --- a/builtin/reset.c\n> +++ b/builtin/reset.c\n> @@ -219,7 +219,7 @@ static const char **parse_args(const char **argv, const char *prefix, const char\n>  \treturn argv[0] ? get_pathspec(prefix, argv) : NULL;\n>  }\n>  \n> -static int update_refs(const char *rev, const unsigned char *sha1)\n> +static int reset_refs(const char *rev, const unsigned char *sha1)\n>  {\n>  \tint update_ref_status;\n>  \tstruct strbuf msg = STRBUF_INIT;\n> @@ -350,7 +350,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)\n>  \tif (!pathspec && !unborn) {\n>  \t\t/* Any resets without paths update HEAD to the head being\n>  \t\t * switched to, saving the previous head in ORIG_HEAD before. */\n> -\t\tupdate_ref_status = update_refs(rev, sha1);\n> +\t\tupdate_ref_status = reset_refs(rev, sha1);\n>  \n>  \t\tif (reset_type == HARD && !update_ref_status && !quiet)\n>  \t\t\tprint_new_head_line(lookup_commit_reference(sha1));\n"},{"id":"226190","messageId":"xmqqtxi85s50.fsf@gitster.dls.corp.google.com","threadId":"34797","inReplyTo":"80d72f6f4bf04a8fef8ebfcc02a463e769cea3d4.1377784597.git.brad.king@kitware.com","subject":"Re: [PATCH/RFC 2/7] refs: report ref type from lock_any_ref_for_update","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-08-29T17:22:19Z","receivedAt":"2013-08-29T17:22:19Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Brad King <brad.king@kitware.com> writes:\n\n> Expose lock_ref_sha1_basic's type_p argument to callers of\n> lock_any_ref_for_update.  Update all call sites to ignore it; we will\n> use it later.\n> ...\n> diff --git a/branch.c b/branch.c\n> index c5c6984..c244483 100644\n> --- a/branch.c\n> +++ b/branch.c\n> @@ -291,7 +291,7 @@ void create_branch(const char *head,\n>  \thashcpy(sha1, commit->object.sha1);\n>  \n>  \tif (!dont_change_ref) {\n> -\t\tlock = lock_any_ref_for_update(ref.buf, NULL, 0);\n> +\t\tlock = lock_any_ref_for_update(ref.buf, NULL, 0, 0);\n\nIf you are passing an NULL as a new parameter, please spell it\n\"NULL\", not \"0\".\n"},{"id":"226191","messageId":"xmqqppsw5rv8.fsf@gitster.dls.corp.google.com","threadId":"34797","inReplyTo":"f5a6f3552b16519b6568a7c745ab61c26bc4a072.1377784597.git.brad.king@kitware.com","subject":"Re: [PATCH/RFC 4/7] refs: factor delete_ref loose ref step into a helper","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-08-29T17:28:11Z","receivedAt":"2013-08-29T17:28:11Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Brad King <brad.king@kitware.com> writes:\n\n> Factor loose ref deletion into helper function delete_ref_loose to allow\n> later use elsewhere.  While at it, rename local names 'flag => type' and\n> 'delopt => flags' for consistency with callers and called functions.\n>\n> Signed-off-by: Brad King <brad.king@kitware.com>\n> ---\n>  refs.c |   24 ++++++++++++++++--------\n>  1 file changed, 16 insertions(+), 8 deletions(-)\n>\n> diff --git a/refs.c b/refs.c\n> index 2e755b4..5908648 100644\n> --- a/refs.c\n> +++ b/refs.c\n> @@ -2450,15 +2450,10 @@ static int repack_without_ref(const char *refname)\n>  \treturn commit_packed_refs();\n>  }\n>  \n> -int delete_ref(const char *refname, const unsigned char *sha1, int delopt)\n> +static int delete_ref_loose(struct ref_lock *lock, int type)\n>  {\n> -\tstruct ref_lock *lock;\n> -\tint err, i = 0, ret = 0, flag = 0;\n> -\n> -\tlock = lock_ref_sha1_basic(refname, sha1, delopt, &flag);\n> -\tif (!lock)\n> -\t\treturn 1;\n> -\tif (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) {\n> +\tint err, i, ret = 0;\n> +\tif (!(type & REF_ISPACKED) || type & REF_ISSYMREF) {\n\nHits from \"git grep REF_IS\" tell me that all users of REF_IS* symbol\nthat check if a bit is on in a word does so against \"flag\" (either a\nvariable called \"flag\", \"flags\", or a structure member \".flag\").\n\nThis change is making things less consistent, not more, I am afraid.\n\n>  \t\t/* loose */\n>  \t\ti = strlen(lock->lk->filename) - 5; /* .lock */\n>  \t\tlock->lk->filename[i] = 0;\n> @@ -2468,6 +2463,19 @@ int delete_ref(const char *refname, const unsigned char *sha1, int delopt)\n>  \n>  \t\tlock->lk->filename[i] = '.';\n>  \t}\n> +\treturn ret;\n> +}\n> +\n> +int delete_ref(const char *refname, const unsigned char *sha1, int flags)\n> +{\n> +\tstruct ref_lock *lock;\n> +\tint ret = 0, type = 0;\n> +\n> +\tlock = lock_ref_sha1_basic(refname, sha1, flags, &type);\n> +\tif (!lock)\n> +\t\treturn 1;\n> +\tret |= delete_ref_loose(lock, type);\n> +\n>  \t/* removing the loose one could have resurrected an earlier\n>  \t * packed one.  Also, if it was not loose we need to repack\n>  \t * without it.\n"},{"id":"226192","messageId":"xmqqli3k5rjx.fsf@gitster.dls.corp.google.com","threadId":"34797","inReplyTo":"85ef4bbb02b7648560d016a5b7ec62df9d394bdb.1377784597.git.brad.king@kitware.com","subject":"Re: [PATCH/RFC 5/7] refs: add function to repack without multiple refs","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-08-29T17:34:58Z","receivedAt":"2013-08-29T17:34:58Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Brad King <brad.king@kitware.com> writes:\n\n> Generalize repack_without_ref as repack_without_refs to support a list\n> of refs and implement the former in terms of the latter.\n>\n> Signed-off-by: Brad King <brad.king@kitware.com>\n> ---\n>  refs.c |   29 ++++++++++++++++++++++-------\n>  1 file changed, 22 insertions(+), 7 deletions(-)\n>\n> diff --git a/refs.c b/refs.c\n> index 5908648..5a6c14e 100644\n> --- a/refs.c\n> +++ b/refs.c\n> @@ -2414,25 +2414,35 @@ static int curate_packed_ref_fn(struct ref_entry *entry, void *cb_data)\n>  \treturn 0;\n>  }\n>  \n> -static int repack_without_ref(const char *refname)\n> +static int repack_without_refs(const char **refnames, int n)\n>  {\n>  \tstruct ref_dir *packed;\n>  \tstruct string_list refs_to_delete = STRING_LIST_INIT_DUP;\n>  \tstruct string_list_item *ref_to_delete;\n> +\tint i, removed = 0;\n> +\n> +\t/* Look for a packed ref: */\n> +\tfor (i = 0; i < n; ++i)\n> +\t\tif (get_packed_ref(refnames[i]))\n> +\t\t\tbreak;\n>  \n> -\tif (!get_packed_ref(refname))\n> -\t\treturn 0; /* refname does not exist in packed refs */\n> +\t/* Avoid locking if we have nothing to do: */\n> +\tif(i == n)\n\nStyle:\n\tif (i == n)\n\n> +\t\treturn 0; /* no refname exists in packed refs */\n>  \n>  \tif (lock_packed_refs(0)) {\n>  \t\tunable_to_lock_error(git_path(\"packed-refs\"), errno);\n> -\t\treturn error(\"cannot delete '%s' from packed refs\", refname);\n> +\t\treturn error(\"cannot delete '%s' from packed refs\", refnames[i]);\n>  \t}\n>  \tpacked = get_packed_refs(&ref_cache);\n>  \n> -\t/* Remove refname from the cache: */\n> -\tif (remove_entry(packed, refname) == -1) {\n> +\t/* Remove refnames from the cache: */\n> +\tfor (i = 0; i < n; ++i)\n> +\t\tif (remove_entry(packed, refnames[i]) != -1)\n> +\t\t\tremoved = 1;\n> +\tif (!removed) {\n>  \t\t/*\n> -\t\t * The packed entry disappeared while we were\n> +\t\t * All packed entries disappeared while we were\n>  \t\t * acquiring the lock.\n>  \t\t */\n>  \t\trollback_packed_refs();\n\n... and this is not an error; somebody else did the work we wanted\nto do for us, which is good ;-)\n\n> @@ -2450,6 +2460,11 @@ static int repack_without_ref(const char *refname)\n>  \treturn commit_packed_refs();\n>  }\n>  \n> +static int repack_without_ref(const char *refname)\n> +{\n> +\treturn repack_without_refs(&refname, 1);\n> +}\n> +\n>  static int delete_ref_loose(struct ref_lock *lock, int type)\n>  {\n>  \tint err, i, ret = 0;\n"},{"id":"226193","messageId":"xmqqhae85rbl.fsf@gitster.dls.corp.google.com","threadId":"34797","inReplyTo":"518ba77096664a679e4c7212e4cc4d496c6b38d3.1377784597.git.brad.king@kitware.com","subject":"Re: [PATCH/RFC 6/7] refs: add update_refs for multiple simultaneous updates","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-08-29T17:39:58Z","receivedAt":"2013-08-29T17:39:58Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Brad King <brad.king@kitware.com> writes:\n\n> Add 'struct ref_update' to encode the information needed to update or\n> delete a ref (name, new sha1, optional old sha1, no-deref flag).  Add\n> function 'update_refs' accepting an array of updates to perform.  First\n> acquire locks on all refs with verified old values.  Then update or\n> delete all refs accordingly.  Fail if any one lock cannot be obtained or\n> any one old value does not match.\n>\n> Though the refs themeselves cannot be modified together in a single\n> atomic transaction, this function does enable some useful semantics.\n> For example, a caller may create a new branch starting from the head of\n> another branch and rewind the original branch at the same time.  This\n> transfers ownership of commits between branches without risk of losing\n> commits added to the original branch by a concurrent process, or risk of\n> a concurrent process creating the new branch first.\n>\n> Signed-off-by: Brad King <brad.king@kitware.com>\n> ---\n>  refs.c |   66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n>  refs.h |   11 +++++++++++\n>  2 files changed, 77 insertions(+)\n>\n> diff --git a/refs.c b/refs.c\n> index 5a6c14e..0a0c19e 100644\n> --- a/refs.c\n> +++ b/refs.c\n> @@ -3238,6 +3238,72 @@ int update_ref(const char *action, const char *refname,\n>  \treturn update_ref_write(action, refname, sha1, lock, onerr);\n>  }\n>  \n> +int update_refs(const char *action, struct ref_update *updates,\n> +\t\tint n, enum action_on_err onerr)\n> +{\n> +\tint ret = 0, delnum = 0, i;\n> +\tint *types;\n> +\tstruct ref_lock **locks;\n> +\tconst char **delnames;\n> +\n> +\tif (!updates || !n)\n> +\t\treturn 0;\n> +\n> +\t/* Allocate work space: */\n> +\ttypes = xmalloc(sizeof(int)*n);\n> +\tlocks = xmalloc(sizeof(struct ref_lock*)*n);\n> +\tdelnames = xmalloc(sizeof(const char*)*n);\n> +\n> +\t/* Acquire all locks while verifying old values: */\n> +\tfor (i=0; i < n; ++i) {\n\nStyle:\n\n\tfor (i = 0; i < n; i++) {\n\n> +\t\tlocks[i] = update_ref_lock(updates[i].ref_name,\n> +\t\t\t\t\t   updates[i].old_sha1,\n> +\t\t\t\t\t   updates[i].flags,\n> +\t\t\t\t\t   &types[i], onerr);\n> +\t\tif (!locks[i])\n> +\t\t\tbreak;\n> +\t}\n\nIs it asking for AB-BA deadlock?  If so, is the caller responsible\nfor avoiding it?\n\n> +\t/* Abort if we did not get all locks: */\n> +\tif (i < n) {\n> +\t\twhile (--i >= 0)\n> +\t\t\tunlock_ref(locks[i]);\n> +\t\tfree(types);\n> +\t\tfree(locks);\n> +\t\tfree(delnames);\n> +\t\treturn 1;\n> +\t}\n> +\n> +\t/* Perform updates first so live commits remain referenced: */\n> +\tfor (i=0; i < n; ++i)\n> +\t\tif (!is_null_sha1(updates[i].new_sha1)) {\n> +\t\t\tret |= update_ref_write(action,\n> +\t\t\t\t\t\tupdates[i].ref_name,\n> +\t\t\t\t\t\tupdates[i].new_sha1,\n> +\t\t\t\t\t\tlocks[i], onerr);\n> +\t\t\tlocks[i] = 0; /* freed by update_ref_write */\n> +\t\t}\n> +\n> +\t/* Perform deletes now that updates are safely completed: */\n> +\tfor (i=0; i < n; ++i)\n> +\t\tif (locks[i]) {\n> +\t\t\tdelnames[delnum++] = locks[i]->ref_name;\n> +\t\t\tret |= delete_ref_loose(locks[i], types[i]);\n> +\t\t}\n> +\tret |= repack_without_refs(delnames, delnum);\n> +\tfor (i=0; i < delnum; ++i)\n> +\t\tunlink_or_warn(git_path(\"logs/%s\", delnames[i]));\n> +\tclear_loose_ref_cache(&ref_cache);\n> +\tfor (i=0; i < n; ++i)\n> +\t\tif (locks[i])\n> +\t\t\tunlock_ref(locks[i]);\n> +\n> +\tfree(types);\n> +\tfree(locks);\n> +\tfree(delnames);\n> +\treturn ret;\n> +}\n> +\n>  struct ref *find_ref_by_name(const struct ref *list, const char *name)\n>  {\n>  \tfor ( ; list; list = list->next)\n> diff --git a/refs.h b/refs.h\n> index 2cd307a..5763f3a 100644\n> --- a/refs.h\n> +++ b/refs.h\n> @@ -214,6 +214,17 @@ int update_ref(const char *action, const char *refname,\n>  \t\tconst unsigned char *sha1, const unsigned char *oldval,\n>  \t\tint flags, enum action_on_err onerr);\n>  \n> +struct ref_update {\n> +\tconst char *ref_name;\n> +\tunsigned char new_sha1[20];\n> +\tunsigned char *old_sha1;\n\nThis asymmetry is rather curious and will later become problematic\nwhen we have more users of this API.  Maybe your callers happen have\nstatic storage to keep old_sha1[] outside this structure but do not\nhave it for new_sha1[] for some unknown reason, which may not apply\nto later callers we may want to add.\n\n> +\tint flags;\n> +};\n> +\n> +/** lock all refs and then write all of them */\n> +int update_refs(const char *action, struct ref_update *updates,\n> +\t\tint n, enum action_on_err onerr);\n> +\n>  extern int parse_hide_refs_config(const char *var, const char *value, const char *);\n>  extern int ref_is_hidden(const char *);\n"},{"id":"226197","messageId":"xmqqd2ow5q1h.fsf@gitster.dls.corp.google.com","threadId":"34797","inReplyTo":"521F8033.6070302@kitware.com","subject":"Re: [PATCH/RFC 0/7] Multiple simultaneously locked ref updates","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-08-29T18:07:38Z","receivedAt":"2013-08-29T18:07:38Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Brad King <brad.king@kitware.com> writes:\n\n> On 08/29/2013 12:21 PM, Junio C Hamano wrote:\n>> Brad King <brad.king@kitware.com> writes:\n>>> needs to reject duplicate ref names from the stdin lines before\n>>> trying to lock the ref twice to avoid this message.\n>> \n>> How about trying not to feed duplicates?\n>\n> Sure, perhaps it is simplest to push the responsibility on the user\n> to avoid duplicates.\n\nI didn't mean to force the caller of new \"update-ref --stdin\"; the\nnew code you wrote for it is what feeds the input to update_refs()\nfunction, and that is one place you can make sure you do not lock\nyourself out.\n\nBesides, if you get two updates to the same ref from --stdin, you\nwould need to verify these are identical (i.e. updating to the same\nnew object name from the same old object name; otherwise the requests\nare conflicting and do not make sense), so the code to collect the\nrequests from stdin needs to match potential duplicates anyway.\n\nOne clean way to do so may be to put an update request (old and new\nsha1) in a structure, and use a string_list to hold list of refs,\nwith the util field pointing at the update request data.\n\n> The lock file may exist because:\n> - another running git process already has the lock, or\n> - this process already has the lock because it was asked to\n>   update the same file multiple times simultaneously, or\n> - a stale lock is left from a git process that crashed earlier.\n> In the last case, make sure no other git process is running and\n> remove the file manually to continue.\n\nThe second case is like left hand not knowing what right hand is\ndoing, no?  It should not happen if we code it right.\n\n> --------------------------------------------------------------------\n>\n> IIUC the message cannot say anything about a 'ref' because it is\n> used for other file type lock failures too.\n>\n> Comments?\n> -Brad\n"},{"id":"226198","messageId":"521F8DFC.9090506@kitware.com","threadId":"34797","inReplyTo":"xmqqy57k5sca.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH/RFC 1/7] reset: rename update_refs to reset_refs","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-08-29T18:07:56Z","receivedAt":"2013-08-29T18:07:56Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"On 08/29/2013 01:17 PM, Junio C Hamano wrote:\n> Brad King <brad.king@kitware.com> writes:\n> \n>> Get it out of the way for a future refs.h function.\n> \n> Readers do not know if \"update_refs()\" is a good name for that\n> future refs.h function at this point, so \"evict squatter\" is not a\n> very good justification by itself.  I do agree that this static\n> function is resetting a ref, not doing an arbitrary update, and the\n> new name is a better match than the old one for what it does, though.\n\nOkay, I've revised the commit message for the next revision.\n\n-Brad\n"},{"id":"226199","messageId":"521F8E0F.5030606@kitware.com","threadId":"34797","inReplyTo":"xmqqtxi85s50.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH/RFC 2/7] refs: report ref type from lock_any_ref_for_update","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-08-29T18:08:15Z","receivedAt":"2013-08-29T18:08:15Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"On 08/29/2013 01:22 PM, Junio C Hamano wrote:\n> If you are passing an NULL as a new parameter, please spell it\n> \"NULL\", not \"0\".\n\nFixed at all updated call sites.\n\n-Brad\n"},{"id":"226200","messageId":"521F8E33.4060906@kitware.com","threadId":"34797","inReplyTo":"xmqqppsw5rv8.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH/RFC 4/7] refs: factor delete_ref loose ref step into a helper","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-08-29T18:08:51Z","receivedAt":"2013-08-29T18:08:51Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"On 08/29/2013 01:28 PM, Junio C Hamano wrote:\n> Brad King <brad.king@kitware.com> writes:\n>> -\tif (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) {\n>> +\tif (!(type & REF_ISPACKED) || type & REF_ISSYMREF) {\n> \n> Hits from \"git grep REF_IS\" tell me that all users of REF_IS* symbol\n> that check if a bit is on in a word does so against \"flag\" (either a\n> variable called \"flag\", \"flags\", or a structure member \".flag\").\n> \n> This change is making things less consistent, not more, I am afraid.\n\nOkay, I removed this part of the change.  It makes the commit\nsimpler anyway.\n\n-Brad\n"},{"id":"226201","messageId":"521F8E52.3030904@kitware.com","threadId":"34797","inReplyTo":"xmqqli3k5rjx.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH/RFC 5/7] refs: add function to repack without multiple refs","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-08-29T18:09:22Z","receivedAt":"2013-08-29T18:09:22Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"On 08/29/2013 01:34 PM, Junio C Hamano wrote:\n> Brad King <brad.king@kitware.com> writes:\n>> +\tif(i == n)\n> \n> Style:\n> \tif (i == n)\n\nFixed in next revision.\n\n-Brad\n"},{"id":"226220","messageId":"521F90EC.6040208@kitware.com","threadId":"34797","inReplyTo":"xmqqhae85rbl.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH/RFC 6/7] refs: add update_refs for multiple simultaneous updates","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-08-29T18:20:28Z","receivedAt":"2013-08-29T18:20:28Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"On 08/29/2013 01:39 PM, Junio C Hamano wrote:\n> Brad King <brad.king@kitware.com> writes:\n>> +\tfor (i=0; i < n; ++i) {\n> \n> Style:\n> \n> \tfor (i = 0; i < n; i++) {\n\nFixed.\n\n> Is it asking for AB-BA deadlock?  If so, is the caller responsible\n> for avoiding it?\n\nSince we don't actually block waiting for locks we won't really\ndeadlock.  However, if two competing callers keep repeating they\nmight never finish.  In order to avoid this one must sort the refs\nso that locks are always acquired in a consistent order.\n\nFor Git's internal API I think we can document this in a comment so\nthat update_refs does not have to sort.  Then we can add a new\nref_update_sort function to sort an array of struct ref_update.\nThe user-facing \"update-ref --stdin\" can then use ref_update_sort.\n\nThe sort logic may subsume duplicate ref update detection too.\nAfter sorting a simple linear-time scan can detect duplicates.\n\n>> +\tunsigned char new_sha1[20];\n>> +\tunsigned char *old_sha1;\n> \n> This asymmetry is rather curious and will later become problematic\n> when we have more users of this API.  Maybe your callers happen have\n> static storage to keep old_sha1[] outside this structure but do not\n> have it for new_sha1[] for some unknown reason, which may not apply\n> to later callers we may want to add.\n\nI wasn't happy with the asymmetry either but forgot to raise it in\nthe cover letter.  We need a way to represent \"old value not given\"\nas different from \"old value is_null_sha1\".\n\nOne approach is to use a bit in the \"flags\" member that already\nstores REF_NODEREF.  However, that will be inconsistent with the\nother ref update APIs that check whether old_sha1 is NULL.  We could\nstill do it and document the bit to mean \"ignore old_sha1 member of\nstruct ref_update\".\n\nAnother approach is to add a dedicated member to struct ref_update.\n\nComments?\n-Brad\n"},{"id":"226225","messageId":"521F91B9.5030801@kitware.com","threadId":"34797","inReplyTo":"xmqqd2ow5q1h.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH/RFC 0/7] Multiple simultaneously locked ref updates","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-08-29T18:23:53Z","receivedAt":"2013-08-29T18:23:53Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"On 08/29/2013 02:07 PM, Junio C Hamano wrote:\n> I didn't mean to force the caller of new \"update-ref --stdin\"; the\n> new code you wrote for it is what feeds the input to update_refs()\n> function, and that is one place you can make sure you do not lock\n> yourself out.\n> \n> Besides, if you get two updates to the same ref from --stdin, you\n> would need to verify these are identical (i.e. updating to the same\n> new object name from the same old object name; otherwise the requests\n> are conflicting and do not make sense), so the code to collect the\n> requests from stdin needs to match potential duplicates anyway.\n> \n> One clean way to do so may be to put an update request (old and new\n> sha1) in a structure, and use a string_list to hold list of refs,\n> with the util field pointing at the update request data.\n> \n>> - this process already has the lock because it was asked to\n>>   update the same file multiple times simultaneously, or\n> \n> The second case is like left hand not knowing what right hand is\n> doing, no?  It should not happen if we code it right.\n\nYes.  All of the above is what I originally intended when asking\nthe question in the cover letter.  Using string_list and its util\nfield may be useful.  However, see also my response at\n$gmane/233260 about how it may fold into sorting.\n\nThanks for the reviews!\n-Brad\n"},{"id":"226226","messageId":"xmqqmwo04ac2.fsf@gitster.dls.corp.google.com","threadId":"34797","inReplyTo":"521F90EC.6040208@kitware.com","subject":"Re: [PATCH/RFC 6/7] refs: add update_refs for multiple simultaneous updates","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-08-29T18:32:13Z","receivedAt":"2013-08-29T18:32:13Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Brad King <brad.king@kitware.com> writes:\n\n> On 08/29/2013 01:39 PM, Junio C Hamano wrote:\n>> Brad King <brad.king@kitware.com> writes:\n>>> +\tfor (i=0; i < n; ++i) {\n>> \n>> Style:\n>> \n>> \tfor (i = 0; i < n; i++) {\n>\n> Fixed.\n>\n>> Is it asking for AB-BA deadlock?  If so, is the caller responsible\n>> for avoiding it?\n>\n> Since we don't actually block waiting for locks we won't really\n> deadlock.\n\nAhh, OK.\n\n> For Git's internal API I think we can document this in a comment so\n> that update_refs does not have to sort.  Then we can add a new\n> ref_update_sort function to sort an array of struct ref_update.\n> The user-facing \"update-ref --stdin\" can then use ref_update_sort.\n\nMy immediate reaction was \"is there a case where the caller knows\nthat it already has a sorted collection?\".  The single caller you\nare envisioning could collect the proposed updates to a string list\nand dedup, I think, and the resulting list would then be already\nsorted.\n\nBut it may not be a bad idea to keep the callers dumb and have this\nfunction always sort, dedup, *and* fail inconsistent request.  Then\nyour original caller that just collects --stdin input can pass\npossibly unsorted, duplicated and/or inconsistent request to the\nfunction and have it do the sanity check.\n"},{"id":"226227","messageId":"xmqqioyo4a7w.fsf@gitster.dls.corp.google.com","threadId":"34797","inReplyTo":"8d323b9c2a71a9bafa8b48caf1d85c1035549b16.1377784597.git.brad.king@kitware.com","subject":"Re: [PATCH/RFC 7/7] update-ref: support multiple simultaneous updates","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-08-29T18:34:43Z","receivedAt":"2013-08-29T18:34:43Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Brad King <brad.king@kitware.com> writes:\n\n> +\tconst char *c, *s, *oldvalue, *value[2] = {0,0};\n\nThis patch has many style issues of the same kind, lack of a SP at\nplaces where there should be between operators and after comma.\n"},{"id":"226232","messageId":"521F953A.9090309@kitware.com","threadId":"34797","inReplyTo":"xmqqmwo04ac2.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH/RFC 6/7] refs: add update_refs for multiple simultaneous updates","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-08-29T18:38:50Z","receivedAt":"2013-08-29T18:38:50Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"On 08/29/2013 02:32 PM, Junio C Hamano wrote:\n> But it may not be a bad idea to keep the callers dumb and have this\n> function always sort, dedup, *and* fail inconsistent request.\n\nI agree.  I was just starting to write the comment for update_refs\nand it basically would have said \"always use ref_update_sort and\ncheck for duplicates first\".  We might was well build it into the\nfunction.  If some call site in the future wants to optimize a case\nknown to be sorted it can be refactored later.\n\nThanks,\n-Brad\n"},{"id":"226233","messageId":"521F961F.3090801@kitware.com","threadId":"34797","inReplyTo":"xmqqioyo4a7w.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH/RFC 7/7] update-ref: support multiple simultaneous updates","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-08-29T18:42:39Z","receivedAt":"2013-08-29T18:42:39Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"On 08/29/2013 02:34 PM, Junio C Hamano wrote:\n> Brad King <brad.king@kitware.com> writes:\n> \n>> +\tconst char *c, *s, *oldvalue, *value[2] = {0,0};\n> \n> This patch has many style issues of the same kind, lack of a SP at\n> places where there should be between operators and after comma.\n\nOkay, I can fix those.  However, for this patch I'm particularly\ninterested in suggestions for the proposed stdin format.  Right\nnow it just looks like the command line, but it feels strange to\nparse \"-options\" from a formatted input stream that is not an\noptions response file.\n\nThanks,\n-Brad\n"},{"id":"226250","messageId":"521FA154.8050105@kitware.com","threadId":"34797","inReplyTo":"521F90EC.6040208@kitware.com","subject":"Re: [PATCH/RFC 6/7] refs: add update_refs for multiple simultaneous updates","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-08-29T19:30:28Z","receivedAt":"2013-08-29T19:30:28Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"On 08/29/2013 02:20 PM, Brad King wrote:\n> I wasn't happy with the asymmetry either but forgot to raise it in\n> the cover letter.  We need a way to represent \"old value not given\"\n> as different from \"old value is_null_sha1\".\n[snip]\n> Another approach is to add a dedicated member to struct ref_update.\n\nFYI, I chose the latter approach for simplicity.  The member\ncan be folded into the flags by future refactoring if needed.\n\n-Brad\n"},{"id":"226358","messageId":"cover.1377885441.git.brad.king@kitware.com","threadId":"34797","inReplyTo":"cover.1377784597.git.brad.king@kitware.com","subject":"[PATCH v2 0/8] Multiple simultaneously locked ref updates","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-08-30T18:11:58Z","receivedAt":"2013-08-30T18:11:58Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"Hi Folks,\n\nHere is the second revision of a series to support locking multiple\nrefs at the same time to update all of them consistently.  The first\nseries can be found at $gmane/233260.  This revision is ready to\nconsider for integration.\n\nUpdates since the previous revision of the series:\n\n* Incorporated style fixes and cleanups suggested by Junio.\n\n* In patch 6, the new update_refs function now sorts the updates\n  so that locks are acquired in a consistent order by competing\n  processes.  Then it uses a simple linear scan to reject input\n  containing duplicate refs (which are adjacent after sorting).\n  Also, struct ref_update now has a symmetric representation\n  for new_sha1 and old_sha1.\n\n* In patch 7, I propose a new format for instructions read from\n  standard input that is much more robust and extensible.\n\n* Patch 8 is new and adds test cases covering new features\n  and error cases.\n\n-Brad\n\nBrad King (8):\n  reset: rename update_refs to reset_refs\n  refs: report ref type from lock_any_ref_for_update\n  refs: factor update_ref steps into helpers\n  refs: factor delete_ref loose ref step into a helper\n  refs: add function to repack without multiple refs\n  refs: add update_refs for multiple simultaneous updates\n  update-ref: support multiple simultaneous updates\n  update-ref: add test cases covering --stdin signature\n\n Documentation/git-update-ref.txt |   21 +++-\n branch.c                         |    2 +-\n builtin/commit.c                 |    2 +-\n builtin/fetch.c                  |    3 +-\n builtin/receive-pack.c           |    3 +-\n builtin/reflog.c                 |    2 +-\n builtin/replace.c                |    2 +-\n builtin/reset.c                  |    4 +-\n builtin/tag.c                    |    2 +-\n builtin/update-ref.c             |  121 +++++++++++++++++++++-\n fast-import.c                    |    2 +-\n refs.c                           |  203 +++++++++++++++++++++++++++++++++----\n refs.h                           |   16 ++-\n sequencer.c                      |    3 +-\n t/t1400-update-ref.sh            |  206 ++++++++++++++++++++++++++++++++++++++\n 15 files changed, 558 insertions(+), 34 deletions(-)\n\n-- \n1.7.10.4\n"},{"id":"226352","messageId":"ce57e08b2fe53b23ba3627bc8a9e0cc44a7683e2.1377885441.git.brad.king@kitware.com","threadId":"34797","inReplyTo":"cover.1377885441.git.brad.king@kitware.com","subject":"[PATCH v2 1/8] reset: rename update_refs to reset_refs","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-08-30T18:11:59Z","receivedAt":"2013-08-30T18:11:59Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"The function resets refs rather than doing arbitrary updates.\nRename it to allow a future general-purpose update_refs function\nto be added.\n\nSigned-off-by: Brad King <brad.king@kitware.com>\n---\n builtin/reset.c |    4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/reset.c b/builtin/reset.c\nindex afa6e02..789ee48 100644\n--- a/builtin/reset.c\n+++ b/builtin/reset.c\n@@ -219,7 +219,7 @@ static const char **parse_args(const char **argv, const char *prefix, const char\n \treturn argv[0] ? get_pathspec(prefix, argv) : NULL;\n }\n \n-static int update_refs(const char *rev, const unsigned char *sha1)\n+static int reset_refs(const char *rev, const unsigned char *sha1)\n {\n \tint update_ref_status;\n \tstruct strbuf msg = STRBUF_INIT;\n@@ -350,7 +350,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)\n \tif (!pathspec && !unborn) {\n \t\t/* Any resets without paths update HEAD to the head being\n \t\t * switched to, saving the previous head in ORIG_HEAD before. */\n-\t\tupdate_ref_status = update_refs(rev, sha1);\n+\t\tupdate_ref_status = reset_refs(rev, sha1);\n \n \t\tif (reset_type == HARD && !update_ref_status && !quiet)\n \t\t\tprint_new_head_line(lookup_commit_reference(sha1));\n-- \n1.7.10.4\n"},{"id":"226359","messageId":"b06a2a1878e1140949f4bce1760e6ac3a5633d4c.1377885441.git.brad.king@kitware.com","threadId":"34797","inReplyTo":"cover.1377885441.git.brad.king@kitware.com","subject":"[PATCH v2 2/8] refs: report ref type from lock_any_ref_for_update","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-08-30T18:12:00Z","receivedAt":"2013-08-30T18:12:00Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"Expose lock_ref_sha1_basic's type_p argument to callers of\nlock_any_ref_for_update.  Update all call sites to ignore it by passing\nNULL for now.\n\nSigned-off-by: Brad King <brad.king@kitware.com>\n---\n branch.c               |    2 +-\n builtin/commit.c       |    2 +-\n builtin/fetch.c        |    3 ++-\n builtin/receive-pack.c |    3 ++-\n builtin/reflog.c       |    2 +-\n builtin/replace.c      |    2 +-\n builtin/tag.c          |    2 +-\n fast-import.c          |    2 +-\n refs.c                 |    7 ++++---\n refs.h                 |    2 +-\n sequencer.c            |    3 ++-\n 11 files changed, 17 insertions(+), 13 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex c5c6984..f2d383f 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -291,7 +291,7 @@ void create_branch(const char *head,\n \thashcpy(sha1, commit->object.sha1);\n \n \tif (!dont_change_ref) {\n-\t\tlock = lock_any_ref_for_update(ref.buf, NULL, 0);\n+\t\tlock = lock_any_ref_for_update(ref.buf, NULL, 0, NULL);\n \t\tif (!lock)\n \t\t\tdie_errno(_(\"Failed to lock ref for update\"));\n \t}\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 10acc53..be08f41 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -1618,7 +1618,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \t\t\t\t\t   !current_head\n \t\t\t\t\t   ? NULL\n \t\t\t\t\t   : current_head->object.sha1,\n-\t\t\t\t\t   0);\n+\t\t\t\t\t   0, NULL);\n \n \tnl = strchr(sb.buf, '\\n');\n \tif (nl)\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex d784b2e..28e4025 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -246,7 +246,8 @@ static int s_update_ref(const char *action,\n \t\trla = default_rla.buf;\n \tsnprintf(msg, sizeof(msg), \"%s: %s\", rla, action);\n \tlock = lock_any_ref_for_update(ref->name,\n-\t\t\t\t       check_old ? ref->old_sha1 : NULL, 0);\n+\t\t\t\t       check_old ? ref->old_sha1 : NULL,\n+\t\t\t\t       0, NULL);\n \tif (!lock)\n \t\treturn errno == ENOTDIR ? STORE_REF_ERROR_DF_CONFLICT :\n \t\t\t\t\t  STORE_REF_ERROR_OTHER;\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex e3eb5fc..a323070 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -524,7 +524,8 @@ static const char *update(struct command *cmd)\n \t\treturn NULL; /* good */\n \t}\n \telse {\n-\t\tlock = lock_any_ref_for_update(namespaced_name, old_sha1, 0);\n+\t\tlock = lock_any_ref_for_update(namespaced_name, old_sha1,\n+\t\t\t\t\t       0, NULL);\n \t\tif (!lock) {\n \t\t\trp_error(\"failed to lock %s\", name);\n \t\t\treturn \"failed to lock\";\ndiff --git a/builtin/reflog.c b/builtin/reflog.c\nindex 54184b3..28d756a 100644\n--- a/builtin/reflog.c\n+++ b/builtin/reflog.c\n@@ -366,7 +366,7 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused,\n \t * we take the lock for the ref itself to prevent it from\n \t * getting updated.\n \t */\n-\tlock = lock_any_ref_for_update(ref, sha1, 0);\n+\tlock = lock_any_ref_for_update(ref, sha1, 0, NULL);\n \tif (!lock)\n \t\treturn error(\"cannot lock ref '%s'\", ref);\n \tlog_file = git_pathdup(\"logs/%s\", ref);\ndiff --git a/builtin/replace.c b/builtin/replace.c\nindex 59d3115..1ecae8d 100644\n--- a/builtin/replace.c\n+++ b/builtin/replace.c\n@@ -105,7 +105,7 @@ static int replace_object(const char *object_ref, const char *replace_ref,\n \telse if (!force)\n \t\tdie(\"replace ref '%s' already exists\", ref);\n \n-\tlock = lock_any_ref_for_update(ref, prev, 0);\n+\tlock = lock_any_ref_for_update(ref, prev, 0, NULL);\n \tif (!lock)\n \t\tdie(\"%s: cannot lock the ref\", ref);\n \tif (write_ref_sha1(lock, repl, NULL) < 0)\ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex af3af3f..2c867d2 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -577,7 +577,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \tif (annotate)\n \t\tcreate_tag(object, tag, &buf, &opt, prev, object);\n \n-\tlock = lock_any_ref_for_update(ref.buf, prev, 0);\n+\tlock = lock_any_ref_for_update(ref.buf, prev, 0, NULL);\n \tif (!lock)\n \t\tdie(_(\"%s: cannot lock the ref\"), ref.buf);\n \tif (write_ref_sha1(lock, object, NULL) < 0)\ndiff --git a/fast-import.c b/fast-import.c\nindex 23f625f..5c329f6 100644\n--- a/fast-import.c\n+++ b/fast-import.c\n@@ -1678,7 +1678,7 @@ static int update_branch(struct branch *b)\n \t\treturn 0;\n \tif (read_ref(b->name, old_sha1))\n \t\thashclr(old_sha1);\n-\tlock = lock_any_ref_for_update(b->name, old_sha1, 0);\n+\tlock = lock_any_ref_for_update(b->name, old_sha1, 0, NULL);\n \tif (!lock)\n \t\treturn error(\"Unable to lock %s\", b->name);\n \tif (!force_update && !is_null_sha1(old_sha1)) {\ndiff --git a/refs.c b/refs.c\nindex 7922261..c69fd68 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2121,11 +2121,12 @@ struct ref_lock *lock_ref_sha1(const char *refname, const unsigned char *old_sha\n }\n \n struct ref_lock *lock_any_ref_for_update(const char *refname,\n-\t\t\t\t\t const unsigned char *old_sha1, int flags)\n+\t\t\t\t\t const unsigned char *old_sha1,\n+\t\t\t\t\t int flags, int *type_p)\n {\n \tif (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL))\n \t\treturn NULL;\n-\treturn lock_ref_sha1_basic(refname, old_sha1, flags, NULL);\n+\treturn lock_ref_sha1_basic(refname, old_sha1, flags, type_p);\n }\n \n /*\n@@ -3174,7 +3175,7 @@ int update_ref(const char *action, const char *refname,\n \t\tint flags, enum action_on_err onerr)\n {\n \tstatic struct ref_lock *lock;\n-\tlock = lock_any_ref_for_update(refname, oldval, flags);\n+\tlock = lock_any_ref_for_update(refname, oldval, flags, NULL);\n \tif (!lock) {\n \t\tconst char *str = \"Cannot lock the ref '%s'.\";\n \t\tswitch (onerr) {\ndiff --git a/refs.h b/refs.h\nindex 9e5db3a..2cd307a 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -137,7 +137,7 @@ extern struct ref_lock *lock_ref_sha1(const char *refname, const unsigned char *\n #define REF_NODEREF\t0x01\n extern struct ref_lock *lock_any_ref_for_update(const char *refname,\n \t\t\t\t\t\tconst unsigned char *old_sha1,\n-\t\t\t\t\t\tint flags);\n+\t\t\t\t\t\tint flags, int *type_p);\n \n /** Close the file descriptor owned by a lock and return the status */\n extern int close_ref(struct ref_lock *lock);\ndiff --git a/sequencer.c b/sequencer.c\nindex 351548f..06e52b4 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -279,7 +279,8 @@ static int fast_forward_to(const unsigned char *to, const unsigned char *from,\n \tread_cache();\n \tif (checkout_fast_forward(from, to, 1))\n \t\texit(1); /* the callee should have complained already */\n-\tref_lock = lock_any_ref_for_update(\"HEAD\", unborn ? null_sha1 : from, 0);\n+\tref_lock = lock_any_ref_for_update(\"HEAD\", unborn ? null_sha1 : from,\n+\t\t\t\t\t   0, NULL);\n \tstrbuf_addf(&sb, \"%s: fast-forward\", action_name(opts));\n \tret = write_ref_sha1(ref_lock, to, sb.buf);\n \tstrbuf_release(&sb);\n-- \n1.7.10.4\n"},{"id":"226356","messageId":"d327c12d90c0c2500e51fb07c06f516f94438ff0.1377885441.git.brad.king@kitware.com","threadId":"34797","inReplyTo":"cover.1377885441.git.brad.king@kitware.com","subject":"[PATCH v2 3/8] refs: factor update_ref steps into helpers","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-08-30T18:12:01Z","receivedAt":"2013-08-30T18:12:01Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"Factor the lock and write steps and error handling into helper functions\nupdate_ref_lock and update_ref_write to allow later use elsewhere.\nExpose lock_any_ref_for_update's type_p to update_ref_lock callers.\n\nSigned-off-by: Brad King <brad.king@kitware.com>\n---\n refs.c |   28 +++++++++++++++++++++++-----\n 1 file changed, 23 insertions(+), 5 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex c69fd68..2e755b4 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -3170,12 +3170,13 @@ int for_each_reflog(each_ref_fn fn, void *cb_data)\n \treturn retval;\n }\n \n-int update_ref(const char *action, const char *refname,\n-\t\tconst unsigned char *sha1, const unsigned char *oldval,\n-\t\tint flags, enum action_on_err onerr)\n+static struct ref_lock *update_ref_lock(const char *refname,\n+\t\t\t\t\tconst unsigned char *oldval,\n+\t\t\t\t\tint flags, int *type_p,\n+\t\t\t\t\tenum action_on_err onerr)\n {\n \tstatic struct ref_lock *lock;\n-\tlock = lock_any_ref_for_update(refname, oldval, flags, NULL);\n+\tlock = lock_any_ref_for_update(refname, oldval, flags, type_p);\n \tif (!lock) {\n \t\tconst char *str = \"Cannot lock the ref '%s'.\";\n \t\tswitch (onerr) {\n@@ -3183,8 +3184,14 @@ int update_ref(const char *action, const char *refname,\n \t\tcase DIE_ON_ERR: die(str, refname); break;\n \t\tcase QUIET_ON_ERR: break;\n \t\t}\n-\t\treturn 1;\n \t}\n+\treturn lock;\n+}\n+\n+static int update_ref_write(const char *action, const char *refname,\n+\t\t\t    const unsigned char *sha1, struct ref_lock *lock,\n+\t\t\t    enum action_on_err onerr)\n+{\n \tif (write_ref_sha1(lock, sha1, action) < 0) {\n \t\tconst char *str = \"Cannot update the ref '%s'.\";\n \t\tswitch (onerr) {\n@@ -3197,6 +3204,17 @@ int update_ref(const char *action, const char *refname,\n \treturn 0;\n }\n \n+int update_ref(const char *action, const char *refname,\n+\t       const unsigned char *sha1, const unsigned char *oldval,\n+\t       int flags, enum action_on_err onerr)\n+{\n+\tstatic struct ref_lock *lock;\n+\tlock = update_ref_lock(refname, oldval, flags, 0, onerr);\n+\tif (!lock)\n+\t\treturn 1;\n+\treturn update_ref_write(action, refname, sha1, lock, onerr);\n+}\n+\n struct ref *find_ref_by_name(const struct ref *list, const char *name)\n {\n \tfor ( ; list; list = list->next)\n-- \n1.7.10.4\n"},{"id":"226351","messageId":"aa8dc7fb089894b43fdf25e2e0e144229db544a6.1377885441.git.brad.king@kitware.com","threadId":"34797","inReplyTo":"cover.1377885441.git.brad.king@kitware.com","subject":"[PATCH v2 4/8] refs: factor delete_ref loose ref step into a helper","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-08-30T18:12:02Z","receivedAt":"2013-08-30T18:12:02Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"Factor loose ref deletion into helper function delete_ref_loose to allow\nlater use elsewhere.\n\nSigned-off-by: Brad King <brad.king@kitware.com>\n---\n refs.c |   22 +++++++++++++++-------\n 1 file changed, 15 insertions(+), 7 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 2e755b4..5dd86ee 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2450,14 +2450,9 @@ static int repack_without_ref(const char *refname)\n \treturn commit_packed_refs();\n }\n \n-int delete_ref(const char *refname, const unsigned char *sha1, int delopt)\n+static int delete_ref_loose(struct ref_lock *lock, int flag)\n {\n-\tstruct ref_lock *lock;\n-\tint err, i = 0, ret = 0, flag = 0;\n-\n-\tlock = lock_ref_sha1_basic(refname, sha1, delopt, &flag);\n-\tif (!lock)\n-\t\treturn 1;\n+\tint err, i, ret = 0;\n \tif (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) {\n \t\t/* loose */\n \t\ti = strlen(lock->lk->filename) - 5; /* .lock */\n@@ -2468,6 +2463,19 @@ int delete_ref(const char *refname, const unsigned char *sha1, int delopt)\n \n \t\tlock->lk->filename[i] = '.';\n \t}\n+\treturn ret;\n+}\n+\n+int delete_ref(const char *refname, const unsigned char *sha1, int delopt)\n+{\n+\tstruct ref_lock *lock;\n+\tint ret = 0, flag = 0;\n+\n+\tlock = lock_ref_sha1_basic(refname, sha1, delopt, &flag);\n+\tif (!lock)\n+\t\treturn 1;\n+\tret |= delete_ref_loose(lock, flag);\n+\n \t/* removing the loose one could have resurrected an earlier\n \t * packed one.  Also, if it was not loose we need to repack\n \t * without it.\n-- \n1.7.10.4\n"},{"id":"226357","messageId":"d968768528a9f588f15468623489ce115a1a0fb9.1377885441.git.brad.king@kitware.com","threadId":"34797","inReplyTo":"cover.1377885441.git.brad.king@kitware.com","subject":"[PATCH v2 5/8] refs: add function to repack without multiple refs","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-08-30T18:12:03Z","receivedAt":"2013-08-30T18:12:03Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"Generalize repack_without_ref as repack_without_refs to support a list\nof refs and implement the former in terms of the latter.\n\nSigned-off-by: Brad King <brad.king@kitware.com>\n---\n refs.c |   29 ++++++++++++++++++++++-------\n 1 file changed, 22 insertions(+), 7 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 5dd86ee..3bcd26e 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2414,25 +2414,35 @@ static int curate_packed_ref_fn(struct ref_entry *entry, void *cb_data)\n \treturn 0;\n }\n \n-static int repack_without_ref(const char *refname)\n+static int repack_without_refs(const char **refnames, int n)\n {\n \tstruct ref_dir *packed;\n \tstruct string_list refs_to_delete = STRING_LIST_INIT_DUP;\n \tstruct string_list_item *ref_to_delete;\n+\tint i, removed = 0;\n+\n+\t/* Look for a packed ref: */\n+\tfor (i = 0; i < n; ++i)\n+\t\tif (get_packed_ref(refnames[i]))\n+\t\t\tbreak;\n \n-\tif (!get_packed_ref(refname))\n-\t\treturn 0; /* refname does not exist in packed refs */\n+\t/* Avoid locking if we have nothing to do: */\n+\tif (i == n)\n+\t\treturn 0; /* no refname exists in packed refs */\n \n \tif (lock_packed_refs(0)) {\n \t\tunable_to_lock_error(git_path(\"packed-refs\"), errno);\n-\t\treturn error(\"cannot delete '%s' from packed refs\", refname);\n+\t\treturn error(\"cannot delete '%s' from packed refs\", refnames[i]);\n \t}\n \tpacked = get_packed_refs(&ref_cache);\n \n-\t/* Remove refname from the cache: */\n-\tif (remove_entry(packed, refname) == -1) {\n+\t/* Remove refnames from the cache: */\n+\tfor (i = 0; i < n; ++i)\n+\t\tif (remove_entry(packed, refnames[i]) != -1)\n+\t\t\tremoved = 1;\n+\tif (!removed) {\n \t\t/*\n-\t\t * The packed entry disappeared while we were\n+\t\t * All packed entries disappeared while we were\n \t\t * acquiring the lock.\n \t\t */\n \t\trollback_packed_refs();\n@@ -2450,6 +2460,11 @@ static int repack_without_ref(const char *refname)\n \treturn commit_packed_refs();\n }\n \n+static int repack_without_ref(const char *refname)\n+{\n+\treturn repack_without_refs(&refname, 1);\n+}\n+\n static int delete_ref_loose(struct ref_lock *lock, int flag)\n {\n \tint err, i, ret = 0;\n-- \n1.7.10.4\n"},{"id":"226353","messageId":"edaddbd4e303866f789f1a4f755a9da77590aeef.1377885441.git.brad.king@kitware.com","threadId":"34797","inReplyTo":"cover.1377885441.git.brad.king@kitware.com","subject":"[PATCH v2 6/8] refs: add update_refs for multiple simultaneous updates","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-08-30T18:12:04Z","receivedAt":"2013-08-30T18:12:04Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"Add 'struct ref_update' to encode the information needed to update or\ndelete a ref (name, new sha1, optional old sha1, no-deref flag).  Add\nfunction 'update_refs' accepting an array of updates to perform.  First\nsort the input array to order locks consistently everywhere and reject\nmultiple updates to the same ref.  Then acquire locks on all refs with\nverified old values.  Then update or delete all refs accordingly.  Fail\nif any one lock cannot be obtained or any one old value does not match.\n\nThough the refs themeselves cannot be modified together in a single\natomic transaction, this function does enable some useful semantics.\nFor example, a caller may create a new branch starting from the head of\nanother branch and rewind the original branch at the same time.  This\ntransfers ownership of commits between branches without risk of losing\ncommits added to the original branch by a concurrent process, or risk of\na concurrent process creating the new branch first.\n\nSigned-off-by: Brad King <brad.king@kitware.com>\n---\n refs.c |  121 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n refs.h |   14 ++++++++\n 2 files changed, 135 insertions(+)\n\ndiff --git a/refs.c b/refs.c\nindex 3bcd26e..901a38a 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -3238,6 +3238,127 @@ int update_ref(const char *action, const char *refname,\n \treturn update_ref_write(action, refname, sha1, lock, onerr);\n }\n \n+static int ref_update_compare(const void *r1, const void *r2)\n+{\n+\tstruct ref_update *u1 = (struct ref_update *)(r1);\n+\tstruct ref_update *u2 = (struct ref_update *)(r2);\n+\tint ret;\n+\tret = strcmp(u1->ref_name, u2->ref_name);\n+\tif (ret)\n+\t\treturn ret;\n+\tret = hashcmp(u1->new_sha1, u2->new_sha1);\n+\tif (ret)\n+\t\treturn ret;\n+\tret = hashcmp(u1->old_sha1, u2->old_sha1);\n+\tif (ret)\n+\t\treturn ret;\n+\tret = u1->flags - u2->flags;\n+\tif (ret)\n+\t\treturn ret;\n+\treturn u1->have_old - u2->have_old;\n+}\n+\n+static int ref_update_reject_duplicates(struct ref_update *updates, int n,\n+\t\t\t\t\tenum action_on_err onerr)\n+{\n+\tint i;\n+\tfor (i = 1; i < n; ++i)\n+\t\tif (!strcmp(updates[i - 1].ref_name, updates[i].ref_name))\n+\t\t\tbreak;\n+\tif (i < n) {\n+\t\tconst char *str = \"Multiple updates for ref '%s' not allowed.\";\n+\t\tswitch (onerr) {\n+\t\tcase MSG_ON_ERR: error(str, updates[i].ref_name); break;\n+\t\tcase DIE_ON_ERR: die(str, updates[i].ref_name); break;\n+\t\tcase QUIET_ON_ERR: break;\n+\t\t}\n+\t\treturn 1;\n+\t}\n+\treturn 0;\n+}\n+\n+int update_refs(const char *action, const struct ref_update *updates_orig,\n+\t\tint n, enum action_on_err onerr)\n+{\n+\tint ret = 0, delnum = 0, i;\n+\tstruct ref_update *updates;\n+\tint *types;\n+\tstruct ref_lock **locks;\n+\tconst char **delnames;\n+\n+\tif (!updates_orig || !n)\n+\t\treturn 0;\n+\n+\t/* Allocate work space: */\n+\tupdates = xmalloc(sizeof(struct ref_update) * n);\n+\ttypes = xmalloc(sizeof(int) * n);\n+\tlocks = xmalloc(sizeof(struct ref_lock *) * n);\n+\tdelnames = xmalloc(sizeof(const char *) * n);\n+\n+\t/* Copy, sort, and reject duplicate refs: */\n+\tmemcpy(updates, updates_orig, sizeof(struct ref_update) * n);\n+\tqsort(updates, n, sizeof(struct ref_update), ref_update_compare);\n+\tif (ref_update_reject_duplicates(updates, n, onerr)) {\n+\t\tfree(updates);\n+\t\tfree(types);\n+\t\tfree(locks);\n+\t\tfree(delnames);\n+\t\treturn 1;\n+\t}\n+\n+\t/* Acquire all locks while verifying old values: */\n+\tfor (i = 0; i < n; ++i) {\n+\t\tlocks[i] = update_ref_lock(updates[i].ref_name,\n+\t\t\t\t\t   (updates[i].have_old ?\n+\t\t\t\t\t    updates[i].old_sha1 : NULL),\n+\t\t\t\t\t   updates[i].flags,\n+\t\t\t\t\t   &types[i], onerr);\n+\t\tif (!locks[i])\n+\t\t\tbreak;\n+\t}\n+\n+\t/* Abort if we did not get all locks: */\n+\tif (i < n) {\n+\t\twhile (--i >= 0)\n+\t\t\tunlock_ref(locks[i]);\n+\t\tfree(updates);\n+\t\tfree(types);\n+\t\tfree(locks);\n+\t\tfree(delnames);\n+\t\treturn 1;\n+\t}\n+\n+\t/* Perform updates first so live commits remain referenced: */\n+\tfor (i = 0; i < n; ++i)\n+\t\tif (!is_null_sha1(updates[i].new_sha1)) {\n+\t\t\tret |= update_ref_write(action,\n+\t\t\t\t\t\tupdates[i].ref_name,\n+\t\t\t\t\t\tupdates[i].new_sha1,\n+\t\t\t\t\t\tlocks[i], onerr);\n+\t\t\tlocks[i] = 0; /* freed by update_ref_write */\n+\t\t}\n+\n+\t/* Perform deletes now that updates are safely completed: */\n+\tfor (i = 0; i < n; ++i)\n+\t\tif (locks[i]) {\n+\t\t\tdelnames[delnum++] = locks[i]->ref_name;\n+\t\t\tret |= delete_ref_loose(locks[i], types[i]);\n+\t\t}\n+\tret |= repack_without_refs(delnames, delnum);\n+\tfor (i = 0; i < delnum; ++i)\n+\t\tunlink_or_warn(git_path(\"logs/%s\", delnames[i]));\n+\tclear_loose_ref_cache(&ref_cache);\n+\tfor (i = 0; i < n; ++i)\n+\t\tif (locks[i])\n+\t\t\tunlock_ref(locks[i]);\n+\n+\tfree(updates);\n+\tfree(types);\n+\tfree(locks);\n+\tfree(delnames);\n+\treturn ret;\n+}\n+\n struct ref *find_ref_by_name(const struct ref *list, const char *name)\n {\n \tfor ( ; list; list = list->next)\ndiff --git a/refs.h b/refs.h\nindex 2cd307a..a8a7cc6 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -214,6 +214,20 @@ int update_ref(const char *action, const char *refname,\n \t\tconst unsigned char *sha1, const unsigned char *oldval,\n \t\tint flags, enum action_on_err onerr);\n \n+struct ref_update {\n+\tconst char *ref_name;\n+\tunsigned char new_sha1[20];\n+\tunsigned char old_sha1[20];\n+\tint flags;\n+\tint have_old;\n+};\n+\n+/**\n+ * Lock all refs and then perform all modifications.\n+ */\n+int update_refs(const char *action, const struct ref_update *updates,\n+\t\tint n, enum action_on_err onerr);\n+\n extern int parse_hide_refs_config(const char *var, const char *value, const char *);\n extern int ref_is_hidden(const char *);\n \n-- \n1.7.10.4\n"},{"id":"226355","messageId":"ba564b6566b54d780a24355ca893294d814d8d24.1377885441.git.brad.king@kitware.com","threadId":"34797","inReplyTo":"cover.1377885441.git.brad.king@kitware.com","subject":"[PATCH v2 7/8] update-ref: support multiple simultaneous updates","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-08-30T18:12:05Z","receivedAt":"2013-08-30T18:12:05Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"Add a --stdin signature to read update instructions from standard input\nand apply multiple ref updates together.  Use an input format that\nsupports any update that could be specified via the command-line,\nincluding object names like 'branch:path with space'.\n\nSigned-off-by: Brad King <brad.king@kitware.com>\n---\n Documentation/git-update-ref.txt |   21 ++++++-\n builtin/update-ref.c             |  121 +++++++++++++++++++++++++++++++++++++-\n 2 files changed, 140 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt\nindex 0df13ff..295d0bb 100644\n--- a/Documentation/git-update-ref.txt\n+++ b/Documentation/git-update-ref.txt\n@@ -8,7 +8,7 @@ git-update-ref - Update the object name stored in a ref safely\n SYNOPSIS\n --------\n [verse]\n-'git update-ref' [-m <reason>] (-d <ref> [<oldvalue>] | [--no-deref] <ref> <newvalue> [<oldvalue>])\n+'git update-ref' [-m <reason>] (-d <ref> [<oldvalue>] | [--no-deref] <ref> <newvalue> [<oldvalue>] | --stdin)\n \n DESCRIPTION\n -----------\n@@ -58,6 +58,25 @@ archive by creating a symlink tree).\n With `-d` flag, it deletes the named <ref> after verifying it\n still contains <oldvalue>.\n \n+With `--stdin`, update-ref reads instructions from standard input and\n+performs all modifications together.  Empty lines are ignored.\n+Each non-empty line is parsed as whitespace-separated arguments.\n+Use single-quotes to enclose whitespace and backslashes and an\n+unquoted backslash to escape a single quote.  Specify updates with\n+lines of the form:\n+\n+\t[--no-deref] [--] <ref> <newvalue> [<oldvalue>]\n+\n+Lines of any other format or a repeated <ref> produce an error.\n+Specify a zero <newvalue> to delete a ref and/or a zero <oldvalue>\n+to make sure that a ref not exist.  Use either 40 \"0\" or the\n+empty string (written as '') to specify a zero value.\n+\n+If all <ref>s can be locked with matching <oldvalue>s\n+simultaneously all modifications are performed.  Otherwise, no\n+modifications are performed.  Note that while each individual\n+<ref> is updated or deleted atomically, a concurrent reader may\n+still see a subset of the modifications.\n \n Logging Updates\n ---------------\ndiff --git a/builtin/update-ref.c b/builtin/update-ref.c\nindex 51d2684..eb8db85 100644\n--- a/builtin/update-ref.c\n+++ b/builtin/update-ref.c\n@@ -6,19 +6,129 @@\n static const char * const git_update_ref_usage[] = {\n \tN_(\"git update-ref [options] -d <refname> [<oldval>]\"),\n \tN_(\"git update-ref [options]    <refname> <newval> [<oldval>]\"),\n+\tN_(\"git update-ref [options] --stdin\"),\n \tNULL\n };\n \n+static const char blank[] = \" \\t\\r\\n\";\n+\n+static int updates_size;\n+static int updates_count;\n+static struct ref_update *updates;\n+\n+static const char* update_refs_stdin_next_arg(const char* next,\n+\t\t\t\t\t      struct strbuf *arg)\n+{\n+\t/* Skip leading whitespace: */\n+\twhile (isspace(*next))\n+\t\t++next;\n+\n+\t/* Return NULL when no argument is found: */\n+\tif (!*next)\n+\t\treturn NULL;\n+\n+\t/* Parse the argument: */\n+\tstrbuf_reset(arg);\n+\tfor (;;) {\n+\t\tchar c = *next;\n+\t\tif (!c || isspace(c))\n+\t\t\tbreak;\n+\t\t++next;\n+\t\tif (c == '\\'') {\n+\t\t\tsize_t len = strcspn(next, \"'\");\n+\t\t\tif (!next[len])\n+\t\t\t\tdie(\"unterminated single-quote: '%s\", next);\n+\t\t\tstrbuf_add(arg, next, len);\n+\t\t\tnext += len + 1;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (c == '\\\\') {\n+\t\t\tif (*next == '\\'')\n+\t\t\t\tc = *next++;\n+\t\t\telse\n+\t\t\t\tdie(\"unquoted backslash not escaping \"\n+\t\t\t\t    \"single-quote: \\\\%s\", next);\n+\t\t}\n+\t\tstrbuf_addch(arg, c);\n+\t}\n+\treturn next;\n+}\n+\n+static void update_refs_stdin(const char *line)\n+{\n+\tint options = 1, flags = 0, argc = 0;\n+\tchar *argv[3] = {0, 0, 0};\n+\tstruct strbuf arg = STRBUF_INIT;\n+\tstruct ref_update *update;\n+\tconst char *next = line;\n+\n+\t/* Skip blank lines: */\n+\tif (!line[0])\n+\t\treturn;\n+\n+\t/* Parse arguments on this line: */\n+\twhile ((next = update_refs_stdin_next_arg(next, &arg)) != NULL) {\n+\t\tif (options && arg.buf[0] == '-')\n+\t\t\tif (!strcmp(arg.buf, \"--no-deref\"))\n+\t\t\t\tflags |= REF_NODEREF;\n+\t\t\telse if (!strcmp(arg.buf, \"--\"))\n+\t\t\t\toptions = 0;\n+\t\t\telse\n+\t\t\t\tdie(\"unknown option %s\", arg.buf);\n+\t\telse if (argc >= 3)\n+\t\t\tdie(\"too many arguments on line: %s\", line);\n+\t\telse {\n+\t\t\targv[argc++] = xstrdup(arg.buf);\n+\t\t\toptions = 0;\n+\t\t}\n+\t}\n+\tstrbuf_release(&arg);\n+\n+\t/* Allocate and zero-init a struct ref_update: */\n+\tif (updates_count == updates_size) {\n+\t\tupdates_size = updates_size ? (updates_size * 2) : 16;\n+\t\tupdates = xrealloc(updates, sizeof(*updates) * updates_size);\n+\t\tmemset(updates + updates_count, 0,\n+\t\t       sizeof(*updates) * (updates_size - updates_count));\n+\t}\n+\tupdate = &updates[updates_count++];\n+\tupdate->flags = flags;\n+\n+\t/* Set the update ref_name: */\n+\tif (!argv[0])\n+\t\tdie(\"no ref on line: %s\", line);\n+\tif (check_refname_format(argv[0], REFNAME_ALLOW_ONELEVEL))\n+\t\tdie(\"invalid ref format on line: %s\", line);\n+\tupdate->ref_name = argv[0];\n+\targv[0] = 0;\n+\n+\t/* Set the update new_sha1 and, if specified, old_sha1: */\n+\tif (!argv[1])\n+\t\tdie(\"missing new value on line: %s\", line);\n+\tif (*argv[1] && get_sha1(argv[1], update->new_sha1))\n+\t\tdie(\"invalid new value on line: %s\", line);\n+\tif (argv[2]) {\n+\t\tupdate->have_old = 1;\n+\t\tif (*argv[2] && get_sha1(argv[2], update->old_sha1))\n+\t\t\tdie(\"invalid old value on line: %s\", line);\n+\t}\n+\n+\twhile (argc > 0)\n+\t\tfree(argv[--argc]);\n+}\n+\n int cmd_update_ref(int argc, const char **argv, const char *prefix)\n {\n \tconst char *refname, *oldval, *msg = NULL;\n \tunsigned char sha1[20], oldsha1[20];\n-\tint delete = 0, no_deref = 0, flags = 0;\n+\tint delete = 0, no_deref = 0, read_stdin = 0, flags = 0;\n+\tstruct strbuf line = STRBUF_INIT;\n \tstruct option options[] = {\n \t\tOPT_STRING( 'm', NULL, &msg, N_(\"reason\"), N_(\"reason of the update\")),\n \t\tOPT_BOOLEAN('d', NULL, &delete, N_(\"delete the reference\")),\n \t\tOPT_BOOLEAN( 0 , \"no-deref\", &no_deref,\n \t\t\t\t\tN_(\"update <refname> not the one it points to\")),\n+\t\tOPT_BOOLEAN( 0 , \"stdin\", &read_stdin, N_(\"read updates from stdin\")),\n \t\tOPT_END(),\n \t};\n \n@@ -28,6 +138,15 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)\n \tif (msg && !*msg)\n \t\tdie(\"Refusing to perform update with empty message.\");\n \n+\tif (read_stdin) {\n+\t\tif (delete || no_deref || argc > 0)\n+\t\t\tusage_with_options(git_update_ref_usage, options);\n+\t\twhile (strbuf_getline(&line, stdin, '\\n') != EOF)\n+\t\t\tupdate_refs_stdin(line.buf);\n+\t\tstrbuf_release(&line);\n+\t\treturn update_refs(msg, updates, updates_count, DIE_ON_ERR);\n+\t}\n+\n \tif (delete) {\n \t\tif (argc < 1 || argc > 2)\n \t\t\tusage_with_options(git_update_ref_usage, options);\n-- \n1.7.10.4\n"},{"id":"226354","messageId":"944b2af0ff064577734fd13cf40039fa4ebc10ec.1377885441.git.brad.king@kitware.com","threadId":"34797","inReplyTo":"cover.1377885441.git.brad.king@kitware.com","subject":"[PATCH v2 8/8] update-ref: add test cases covering --stdin signature","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-08-30T18:12:06Z","receivedAt":"2013-08-30T18:12:06Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"Extend t/t1400-update-ref.sh to cover cases using the --stdin option.\n\nSigned-off-by: Brad King <brad.king@kitware.com>\n---\n t/t1400-update-ref.sh |  206 +++++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 206 insertions(+)\n\ndiff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh\nindex e415ee0..9fd03fc 100755\n--- a/t/t1400-update-ref.sh\n+++ b/t/t1400-update-ref.sh\n@@ -302,4 +302,210 @@ test_expect_success \\\n \t'git cat-file blob master@{2005-05-26 23:42}:F (expect OTHER)' \\\n \t'test OTHER = $(git cat-file blob \"master@{2005-05-26 23:42}:F\")'\n \n+a=refs/heads/a\n+b=refs/heads/b\n+c=refs/heads/c\n+z=0000000000000000000000000000000000000000\n+e=\"''\"\n+\n+test_expect_success 'stdin works with no input' '\n+\trm -f stdin &&\n+\ttouch stdin &&\n+\tgit update-ref --stdin < stdin &&\n+\tgit rev-parse --verify -q $m\n+'\n+\n+test_expect_success 'stdin fails with bad line lines' '\n+\techo \" \" > stdin &&\n+\ttest_must_fail git update-ref --stdin < stdin 2> err &&\n+\tgrep \"fatal: no ref on line:  \" err &&\n+\techo \"--\" > stdin &&\n+\ttest_must_fail git update-ref --stdin < stdin 2> err &&\n+\tgrep \"fatal: no ref on line: --\" err &&\n+\techo \"--bad-option\" > stdin &&\n+\ttest_must_fail git update-ref --stdin < stdin 2> err &&\n+\tgrep \"fatal: unknown option --bad-option\" err &&\n+\techo \"-\\'\"'\"' $a $m\" > stdin &&\n+\ttest_must_fail git update-ref --stdin < stdin 2> err &&\n+\tgrep \"fatal: unknown option -'\"'\"'\" err &&\n+\techo \"~a $m\" > stdin &&\n+\ttest_must_fail git update-ref --stdin < stdin 2> err &&\n+\tgrep \"fatal: invalid ref format on line: ~a $m\" err &&\n+\techo \"$a '\"'\"'master\" > stdin &&\n+\ttest_must_fail git update-ref --stdin < stdin 2> err &&\n+\tgrep \"fatal: unterminated single-quote: '\"'\"'master\" err &&\n+\techo \"$a \\master\" > stdin &&\n+\ttest_must_fail git update-ref --stdin < stdin 2> err &&\n+\tgrep \"fatal: unquoted backslash not escaping single-quote: \\\\\\\\master\" err &&\n+\techo \"$a $m $m $m\" > stdin &&\n+\ttest_must_fail git update-ref --stdin < stdin 2> err &&\n+\tgrep \"fatal: too many arguments on line: $a $m $m $m\" err &&\n+\techo \"$a\" > stdin &&\n+\ttest_must_fail git update-ref --stdin < stdin 2> err &&\n+\tgrep \"fatal: missing new value on line: $a\" err\n+'\n+\n+test_expect_success 'stdin fails with duplicate refs' '\n+\techo \"$a $m\" > stdin &&\n+\techo \"$b $m\" >> stdin &&\n+\techo \"$a $m\" >> stdin &&\n+\ttest_must_fail git update-ref --stdin < stdin 2> err &&\n+\tgrep \"fatal: Multiple updates for ref '\"'\"'$a'\"'\"' not allowed.\" err\n+'\n+\n+test_expect_success 'stdin create ref works with no old value' '\n+\techo \"$a $m\" > stdin &&\n+\tgit update-ref --stdin < stdin &&\n+\tgit rev-parse $m > expect &&\n+\tgit rev-parse $a > actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin create ref works with zero old value' '\n+\techo \"$b $m $z\" > stdin &&\n+\tgit update-ref --stdin < stdin &&\n+\tgit rev-parse $m > expect &&\n+\tgit rev-parse $b > actual &&\n+\ttest_cmp expect actual &&\n+\tgit update-ref -d $b &&\n+\techo \"$b $m $e\" > stdin &&\n+\tgit update-ref --stdin < stdin &&\n+\tgit rev-parse $m > expect &&\n+\tgit rev-parse $b > actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin create ref fails with wrong old value' '\n+\techo \"$c $m $m~1\" > stdin &&\n+\ttest_must_fail git update-ref --stdin < stdin 2> err &&\n+\tgrep \"fatal: Cannot lock the ref '\"'\"'$c'\"'\"'\" err &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin create ref fails with bad old value' '\n+\techo \"$c $m does-not-exist\" > stdin &&\n+\ttest_must_fail git update-ref --stdin < stdin 2> err &&\n+\tgrep \"fatal: invalid old value on line: $c $m does-not-exist\" err &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin create ref fails with bad new value' '\n+\techo \"$c does-not-exist\" > stdin &&\n+\ttest_must_fail git update-ref --stdin < stdin 2> err &&\n+\tgrep \"fatal: invalid new value on line: $c does-not-exist\" err &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin update ref works with right old value' '\n+\techo \"$b $m~1 $m\" > stdin &&\n+\tgit update-ref --stdin < stdin &&\n+\tgit rev-parse $m~1 > expect &&\n+\tgit rev-parse $b > actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin update ref fails with wrong old value' '\n+\techo \"$b $m~1 $m\" > stdin &&\n+\ttest_must_fail git update-ref --stdin < stdin 2> err &&\n+\tgrep \"fatal: Cannot lock the ref '\"'\"'$b'\"'\"'\" err &&\n+\tgit rev-parse $m~1 > expect &&\n+\tgit rev-parse $b > actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin delete ref fails with wrong old value' '\n+\techo \"$a $e $m~1\" > stdin &&\n+\ttest_must_fail git update-ref --stdin < stdin 2> err &&\n+\tgrep \"fatal: Cannot lock the ref '\"'\"'$a'\"'\"'\" err &&\n+\tgit rev-parse $m > expect &&\n+\tgit rev-parse $a > actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin update symref works with --no-deref' '\n+\tgit symbolic-ref TESTSYMREF $b &&\n+\techo \"--no-deref TESTSYMREF $a $b\" > stdin &&\n+\tgit update-ref --stdin < stdin &&\n+\tgit rev-parse TESTSYMREF > expect &&\n+\tgit rev-parse $a > actual &&\n+\ttest_cmp expect actual &&\n+\tgit rev-parse $m~1 > expect &&\n+\tgit rev-parse $b > actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin delete symref works with --no-deref' '\n+\tgit symbolic-ref TESTSYMREF $b &&\n+\techo \"--no-deref TESTSYMREF $e $b\" > stdin &&\n+\tgit update-ref --stdin < stdin &&\n+\ttest_must_fail git rev-parse --verify -q TESTSYMREF &&\n+\tgit rev-parse $m~1 > expect &&\n+\tgit rev-parse $b > actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin delete ref works with right old value' '\n+\techo \"$b $e $m~1\" > stdin &&\n+\tgit update-ref --stdin < stdin &&\n+\ttest_must_fail git rev-parse --verify -q $b\n+'\n+\n+test_expect_success 'stdin create refs works with some old values' '\n+\techo \"$a $m\" > stdin &&\n+\techo \"$b $m $z\" >> stdin &&\n+\techo \"$c $z $z\" >> stdin &&\n+\tgit update-ref --stdin < stdin &&\n+\tgit rev-parse $m > expect &&\n+\tgit rev-parse $a > actual &&\n+\ttest_cmp expect actual &&\n+\tgit rev-parse $b > actual &&\n+\ttest_cmp expect actual &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin update refs works with identity updates' '\n+\techo \"\" > stdin && # also test blank lines\n+\techo \"$a $m $m\" >> stdin &&\n+\techo \"\" >> stdin &&\n+\techo \" '\"'\"'$b'\"'\"'  $m $m \" >> stdin &&\n+\techo \"\" >> stdin &&\n+\techo \"-- $c  $z  $e  \" >> stdin &&\n+\techo \"\" >> stdin &&\n+\tgit update-ref --stdin < stdin &&\n+\tgit rev-parse $m > expect &&\n+\tgit rev-parse $a > actual &&\n+\ttest_cmp expect actual &&\n+\tgit rev-parse $b > actual &&\n+\ttest_cmp expect actual &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin update refs fails with wrong old value' '\n+\tgit update-ref $c $m &&\n+\techo \"$a $m $m\" > stdin &&\n+\techo \"$b $m $m\" >> stdin &&\n+\techo \"$c $e $e\" >> stdin &&\n+\ttest_must_fail git update-ref --stdin < stdin 2> err &&\n+\tgrep \"fatal: Cannot lock the ref '\"'\"'$c'\"'\"'\" err &&\n+\tgit rev-parse $m > expect &&\n+\tgit rev-parse $a > actual &&\n+\ttest_cmp expect actual &&\n+\tgit rev-parse $b > actual &&\n+\ttest_cmp expect actual &&\n+\tgit rev-parse $c > actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin delete refs works with packed and loose refs' '\n+\tgit pack-refs --all &&\n+\tgit update-ref $c $m~1 &&\n+\techo \"$a $z $m\" > stdin &&\n+\techo \"$b $z $m\" >> stdin &&\n+\techo \"$c $e $m~1\" >> stdin &&\n+\tgit update-ref --stdin < stdin &&\n+\ttest_must_fail git rev-parse --verify -q $a &&\n+\ttest_must_fail git rev-parse --verify -q $b &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n test_done\n-- \n1.7.10.4\n"},{"id":"226379","messageId":"xmqqbo4eyeps.fsf@gitster.dls.corp.google.com","threadId":"34797","inReplyTo":"ba564b6566b54d780a24355ca893294d814d8d24.1377885441.git.brad.king@kitware.com","subject":"Re: [PATCH v2 7/8] update-ref: support multiple simultaneous updates","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-08-30T22:51:43Z","receivedAt":"2013-08-30T22:51:43Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Brad King <brad.king@kitware.com> writes:\n\n> Add a --stdin signature to read update instructions from standard input\n> and apply multiple ref updates together.  Use an input format that\n> supports any update that could be specified via the command-line,\n> including object names like 'branch:path with space'.\n>\n> Signed-off-by: Brad King <brad.king@kitware.com>\n> ---\n>  Documentation/git-update-ref.txt |   21 ++++++-\n>  builtin/update-ref.c             |  121 +++++++++++++++++++++++++++++++++++++-\n>  2 files changed, 140 insertions(+), 2 deletions(-)\n>\n> diff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt\n> index 0df13ff..295d0bb 100644\n> --- a/Documentation/git-update-ref.txt\n> +++ b/Documentation/git-update-ref.txt\n> @@ -8,7 +8,7 @@ git-update-ref - Update the object name stored in a ref safely\n>  SYNOPSIS\n>  --------\n>  [verse]\n> -'git update-ref' [-m <reason>] (-d <ref> [<oldvalue>] | [--no-deref] <ref> <newvalue> [<oldvalue>])\n> +'git update-ref' [-m <reason>] (-d <ref> [<oldvalue>] | [--no-deref] <ref> <newvalue> [<oldvalue>] | --stdin)\n>  \n>  DESCRIPTION\n>  -----------\n> @@ -58,6 +58,25 @@ archive by creating a symlink tree).\n>  With `-d` flag, it deletes the named <ref> after verifying it\n>  still contains <oldvalue>.\n>  \n> +With `--stdin`, update-ref reads instructions from standard input and\n> +performs all modifications together.  Empty lines are ignored.\n> +Each non-empty line is parsed as whitespace-separated arguments.\n> +Use single-quotes to enclose whitespace and backslashes and an\n> +unquoted backslash to escape a single quote.\n\nThat is somewhat unusual.\n\nWhen we need to deal with arbitrary strings (like pathnames), other\nparts of the system usually give the user two interfaces, --stdin\nwith and without -z, and the strings are C-quoted when run without\nthe -z option, and terminated with NUL when run with the -z option.\n\n> +Specify updates with\n> +lines of the form:\n> +\n> +\t[--no-deref] [--] <ref> <newvalue> [<oldvalue>]\n\nWhat is -- doing here?  refs given to update-ref begin with refs/\n(otherwise it is HEAD), no?\n"},{"id":"226421","messageId":"52221A15.20804@alum.mit.edu","threadId":"34797","inReplyTo":"aa8dc7fb089894b43fdf25e2e0e144229db544a6.1377885441.git.brad.king@kitware.com","subject":"Re: [PATCH v2 4/8] refs: factor delete_ref loose ref step into a helper","fromName":"Michael Haggerty","fromEmail":"mhagger@alum.mit.edu","sentAt":"2013-08-31T16:30:13Z","receivedAt":"2013-08-31T16:30:13Z","isPatch":true,"sender":{"key":"mhagger@alum.mit.edu","avatar":"https://avatars.githubusercontent.com/u/119718?v=4"},"body":"On 08/30/2013 08:12 PM, Brad King wrote:\n> Factor loose ref deletion into helper function delete_ref_loose to allow\n> later use elsewhere.\n> \n> Signed-off-by: Brad King <brad.king@kitware.com>\n> ---\n>  refs.c |   22 +++++++++++++++-------\n>  1 file changed, 15 insertions(+), 7 deletions(-)\n> \n> diff --git a/refs.c b/refs.c\n> index 2e755b4..5dd86ee 100644\n> --- a/refs.c\n> +++ b/refs.c\n> @@ -2450,14 +2450,9 @@ static int repack_without_ref(const char *refname)\n>  \treturn commit_packed_refs();\n>  }\n>  \n> -int delete_ref(const char *refname, const unsigned char *sha1, int delopt)\n> +static int delete_ref_loose(struct ref_lock *lock, int flag)\n>  {\n> -\tstruct ref_lock *lock;\n> -\tint err, i = 0, ret = 0, flag = 0;\n> -\n> -\tlock = lock_ref_sha1_basic(refname, sha1, delopt, &flag);\n> -\tif (!lock)\n> -\t\treturn 1;\n> +\tint err, i, ret = 0;\n>  \tif (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) {\n>  \t\t/* loose */\n>  \t\ti = strlen(lock->lk->filename) - 5; /* .lock */\n> @@ -2468,6 +2463,19 @@ int delete_ref(const char *refname, const unsigned char *sha1, int delopt)\n>  \n>  \t\tlock->lk->filename[i] = '.';\n>  \t}\n> +\treturn ret;\n> +}\n> +\n\n\nAt first glance it is odd that delete_ref_loose() takes a (struct\nref_lock *) argument but only actually uses lock->lk->filename.  But I\nguess that the function is so specific to the contents of struct\nref_lock and indeed struct lock_file that it wouldn't make sense to pass\nit only the filename attribute.  So OK.\n\nGiven that ret is only returned, you could restore the filename before\nthe if statement and replace the ret variable with an immediate return\nstatement:\n\nstatic int delete_ref_loose(struct ref_lock *lock, int flag)\n{\n\tif (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) {\n\t\t/* loose */\n\t\tint err, i = strlen(lock->lk->filename) - 5; /* .lock */\n\n\t\tlock->lk->filename[i] = 0;\n\t\terr = unlink_or_warn(lock->lk->filename);\n\t\tlock->lk->filename[i] = '.';\n\t\tif (err && errno != ENOENT)\n\t\t\treturn 1;\n\t}\n\treturn 0;\n}\n\n> +int delete_ref(const char *refname, const unsigned char *sha1, int delopt)\n> +{\n> +\tstruct ref_lock *lock;\n> +\tint ret = 0, flag = 0;\n> +\n> +\tlock = lock_ref_sha1_basic(refname, sha1, delopt, &flag);\n> +\tif (!lock)\n> +\t\treturn 1;\n> +\tret |= delete_ref_loose(lock, flag);\n> +\n>  \t/* removing the loose one could have resurrected an earlier\n>  \t * packed one.  Also, if it was not loose we need to repack\n>  \t * without it.\n> \n\nOtherwise looks good.\n\nMichael\n\n-- \nMichael Haggerty\nmhagger@alum.mit.edu\nhttp://softwareswirl.blogspot.com/\n"},{"id":"226428","messageId":"52223398.2080109@alum.mit.edu","threadId":"34797","inReplyTo":"edaddbd4e303866f789f1a4f755a9da77590aeef.1377885441.git.brad.king@kitware.com","subject":"Re: [PATCH v2 6/8] refs: add update_refs for multiple simultaneous updates","fromName":"Michael Haggerty","fromEmail":"mhagger@alum.mit.edu","sentAt":"2013-08-31T18:19:04Z","receivedAt":"2013-08-31T18:19:04Z","isPatch":true,"sender":{"key":"mhagger@alum.mit.edu","avatar":"https://avatars.githubusercontent.com/u/119718?v=4"},"body":"On 08/30/2013 08:12 PM, Brad King wrote:\n> Add 'struct ref_update' to encode the information needed to update or\n> delete a ref (name, new sha1, optional old sha1, no-deref flag).  Add\n> function 'update_refs' accepting an array of updates to perform.  First\n> sort the input array to order locks consistently everywhere and reject\n> multiple updates to the same ref.  Then acquire locks on all refs with\n> verified old values.  Then update or delete all refs accordingly.  Fail\n> if any one lock cannot be obtained or any one old value does not match.\n> \n> Though the refs themeselves cannot be modified together in a single\n\ns/themeselves/themselves/\n\n> atomic transaction, this function does enable some useful semantics.\n> For example, a caller may create a new branch starting from the head of\n> another branch and rewind the original branch at the same time.  This\n> transfers ownership of commits between branches without risk of losing\n> commits added to the original branch by a concurrent process, or risk of\n> a concurrent process creating the new branch first.\n> \n> Signed-off-by: Brad King <brad.king@kitware.com>\n> ---\n>  refs.c |  121 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n>  refs.h |   14 ++++++++\n>  2 files changed, 135 insertions(+)\n> \n> diff --git a/refs.c b/refs.c\n> index 3bcd26e..901a38a 100644\n> --- a/refs.c\n> +++ b/refs.c\n> @@ -3238,6 +3238,127 @@ int update_ref(const char *action, const char *refname,\n>  \treturn update_ref_write(action, refname, sha1, lock, onerr);\n>  }\n>  \n> +static int ref_update_compare(const void *r1, const void *r2)\n> +{\n> +\tstruct ref_update *u1 = (struct ref_update *)(r1);\n> +\tstruct ref_update *u2 = (struct ref_update *)(r2);\n\nIf you declare u1 and u2 to be \"const struct ref_update *\" (i.e., add\n\"const\"), then you have const correctness and don't need the explicit\ncasts.  (And the parentheses around r1 and r2 are superfluous in any case.)\n\n> +\tint ret;\n\nStyle: we usually put a blank line between variable declarations and the\nfirst line of code.\n\n> +\tret = strcmp(u1->ref_name, u2->ref_name);\n> +\tif (ret)\n> +\t\treturn ret;\n> +\tret = hashcmp(u1->new_sha1, u2->new_sha1);\n> +\tif (ret)\n> +\t\treturn ret;\n> +\tret = hashcmp(u1->old_sha1, u2->old_sha1);\n> +\tif (ret)\n> +\t\treturn ret;\n> +\tret = u1->flags - u2->flags;\n> +\tif (ret)\n> +\t\treturn ret;\n> +\treturn u1->have_old - u2->have_old;\n> +}\n\nIs there a need to compare more than ref_name?  If two entries are found\nwith the same name, then ref_update_reject_duplicates() will error out\nanyway.  So the relative order among entries with the same name is\nirrelevant.  I think it would be OK to return 0 for any entries with the\nsame ref_name, even if they differ in other fields.\n\n> +\n> +static int ref_update_reject_duplicates(struct ref_update *updates, int n,\n> +\t\t\t\t\tenum action_on_err onerr)\n> +{\n> +\tint i;\n> +\tfor (i = 1; i < n; ++i)\n> +\t\tif (!strcmp(updates[i - 1].ref_name, updates[i].ref_name))\n> +\t\t\tbreak;\n\nThe error handling code could be right here instead of the \"break\"\nstatement, removing the need for the \"if\" conditional.\n\n> +\tif (i < n) {\n> +\t\tconst char *str = \"Multiple updates for ref '%s' not allowed.\";\n> +\t\tswitch (onerr) {\n> +\t\tcase MSG_ON_ERR: error(str, updates[i].ref_name); break;\n> +\t\tcase DIE_ON_ERR: die(str, updates[i].ref_name); break;\n> +\t\tcase QUIET_ON_ERR: break;\n> +\t\t}\n> +\t\treturn 1;\n> +\t}\n> +\treturn 0;\n> +}\n> +\n> +int update_refs(const char *action, const struct ref_update *updates_orig,\n> +\t\tint n, enum action_on_err onerr)\n> +{\n> +\tint ret = 0, delnum = 0, i;\n> +\tstruct ref_update *updates;\n> +\tint *types;\n> +\tstruct ref_lock **locks;\n> +\tconst char **delnames;\n> +\n> +\tif (!updates_orig || !n)\n> +\t\treturn 0;\n> +\n> +\t/* Allocate work space: */\n> +\tupdates = xmalloc(sizeof(struct ref_update) * n);\n\nIt seems preferred here to write\n\n\tupdates = xmalloc(sizeof(*updates) * n);\n\nas this will continue to work if the type of updates is ever changed.\nSimilarly for the next lines.\n\n> +\ttypes = xmalloc(sizeof(int) * n);\n> +\tlocks = xmalloc(sizeof(struct ref_lock *) * n);\n> +\tdelnames = xmalloc(sizeof(const char *) * n);\n\nAn alternative to managing separate arrays to hold types and locks would\nbe to include the scratch space in struct ref_update and document it\n\"for internal use only; need not be initialized by caller\".  On the one\nhand it's ugly to cruft up the \"interface\" with internal implementation\ndetails; on the other hand there is precedent for this sort of thing\n(e.g., ref_lock::force_write or lock_file::on_list) and it would\nsimplify the code.\n\n> +\n> +\t/* Copy, sort, and reject duplicate refs: */\n> +\tmemcpy(updates, updates_orig, sizeof(struct ref_update) * n);\n> +\tqsort(updates, n, sizeof(struct ref_update), ref_update_compare);\n\nYou could save some space and memory shuffling (during memcpy() and\nqsort()) if you would declare \"updates\" to be an array of pointers to\n\"struct ref_update\" rather than an array of structs.  Sorting could then\nbe done by moving pointers around instead of moving the structs.  This\nwould also make it easier for update_refs() to pass information about\nthe references back to its caller, should that ever be needed.\n\nBut I suppose that n will usually be small, so this suggestion can be\nconsidered optional.\n\n> +\tif (ref_update_reject_duplicates(updates, n, onerr)) {\n> +\t\tfree(updates);\n> +\t\tfree(types);\n> +\t\tfree(locks);\n> +\t\tfree(delnames);\n> +\t\treturn 1;\n> +\t}\n> +\n> +\t/* Acquire all locks while verifying old values: */\n> +\tfor (i = 0; i < n; ++i) {\n> +\t\tlocks[i] = update_ref_lock(updates[i].ref_name,\n> +\t\t\t\t\t   (updates[i].have_old ?\n> +\t\t\t\t\t    updates[i].old_sha1 : NULL),\n> +\t\t\t\t\t   updates[i].flags,\n> +\t\t\t\t\t   &types[i], onerr);\n> +\t\tif (!locks[i])\n> +\t\t\tbreak;\n\nThe error handling code could go here in place of the \"break\",\nespecially if it's only \"ret = 1; goto label;\" as suggested below.\n\n> +\t}\n> +\n> +\t/* Abort if we did not get all locks: */\n> +\tif (i < n) {\n> +\t\twhile (--i >= 0)\n> +\t\t\tunlock_ref(locks[i]);\n> +\t\tfree(updates);\n> +\t\tfree(types);\n> +\t\tfree(locks);\n> +\t\tfree(delnames);\n> +\t\treturn 1;\n> +\t}\n> +\n> +\t/* Perform updates first so live commits remain referenced: */\n> +\tfor (i = 0; i < n; ++i)\n> +\t\tif (!is_null_sha1(updates[i].new_sha1)) {\n> +\t\t\tret |= update_ref_write(action,\n> +\t\t\t\t\t\tupdates[i].ref_name,\n> +\t\t\t\t\t\tupdates[i].new_sha1,\n> +\t\t\t\t\t\tlocks[i], onerr);\n> +\t\t\tlocks[i] = 0; /* freed by update_ref_write */\n> +\t\t}\n> +\n\nHmmm, if one of the calls to update_ref_write() fails, would it be safer\nto abort the rest of the work (especially the reference deletions)?\n\n> +\t/* Perform deletes now that updates are safely completed: */\n> +\tfor (i = 0; i < n; ++i)\n> +\t\tif (locks[i]) {\n> +\t\t\tdelnames[delnum++] = locks[i]->ref_name;\n> +\t\t\tret |= delete_ref_loose(locks[i], types[i]);\n> +\t\t}\n> +\tret |= repack_without_refs(delnames, delnum);\n> +\tfor (i = 0; i < delnum; ++i)\n> +\t\tunlink_or_warn(git_path(\"logs/%s\", delnames[i]));\n> +\tclear_loose_ref_cache(&ref_cache);\n> +\tfor (i = 0; i < n; ++i)\n> +\t\tif (locks[i])\n> +\t\t\tunlock_ref(locks[i]);\n> +\n> +\tfree(updates);\n> +\tfree(types);\n> +\tfree(locks);\n> +\tfree(delnames);\n> +\treturn ret;\n> +}\n\nThere's a lot of duplicated cleanup code in the function.  If you put a\nlabel before the final for loop, and if you initialize the locks array\nto zeros (e.g., by using xcalloc()), then the three exits could all\nshare the same code \"ret = 1; goto cleanup;\".\n\n> +\n>  struct ref *find_ref_by_name(const struct ref *list, const char *name)\n>  {\n>  \tfor ( ; list; list = list->next)\n> diff --git a/refs.h b/refs.h\n> index 2cd307a..a8a7cc6 100644\n> --- a/refs.h\n> +++ b/refs.h\n> @@ -214,6 +214,20 @@ int update_ref(const char *action, const char *refname,\n>  \t\tconst unsigned char *sha1, const unsigned char *oldval,\n>  \t\tint flags, enum action_on_err onerr);\n>  \n> +struct ref_update {\n> +\tconst char *ref_name;\n> +\tunsigned char new_sha1[20];\n> +\tunsigned char old_sha1[20];\n> +\tint flags;\n> +\tint have_old;\n> +};\n\nPlease document this structure, especially the relationship between\nhave_old and old_sha1.\n\n> +\n> +/**\n> + * Lock all refs and then perform all modifications.\n> + */\n> +int update_refs(const char *action, const struct ref_update *updates,\n> +\t\tint n, enum action_on_err onerr);\n> +\n>  extern int parse_hide_refs_config(const char *var, const char *value, const char *);\n>  extern int ref_is_hidden(const char *);\n>  \n> \n\nOverall, thanks; it looks good.  I think this change is useful by itself\nand is also a good start towards implementing true reference\ntransactions (which I think will be necessary pretty soon, at least for\nsome applications).  I will write another email discussing how your\nchanges are related to some changes that I have been working on lately.\n\nMichael\n\n-- \nMichael Haggerty\nmhagger@alum.mit.edu\nhttp://softwareswirl.blogspot.com/\n"},{"id":"226429","messageId":"52223918.6040603@alum.mit.edu","threadId":"34797","inReplyTo":"ba564b6566b54d780a24355ca893294d814d8d24.1377885441.git.brad.king@kitware.com","subject":"Re: [PATCH v2 7/8] update-ref: support multiple simultaneous updates","fromName":"Michael Haggerty","fromEmail":"mhagger@alum.mit.edu","sentAt":"2013-08-31T18:42:32Z","receivedAt":"2013-08-31T18:42:32Z","isPatch":true,"sender":{"key":"mhagger@alum.mit.edu","avatar":"https://avatars.githubusercontent.com/u/119718?v=4"},"body":"On 08/30/2013 08:12 PM, Brad King wrote:\n> Add a --stdin signature to read update instructions from standard input\n> and apply multiple ref updates together.  Use an input format that\n> supports any update that could be specified via the command-line,\n> including object names like 'branch:path with space'.\n> \n> Signed-off-by: Brad King <brad.king@kitware.com>\n> ---\n>  Documentation/git-update-ref.txt |   21 ++++++-\n>  builtin/update-ref.c             |  121 +++++++++++++++++++++++++++++++++++++-\n>  2 files changed, 140 insertions(+), 2 deletions(-)\n> \n> diff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt\n> index 0df13ff..295d0bb 100644\n> --- a/Documentation/git-update-ref.txt\n> +++ b/Documentation/git-update-ref.txt\n> @@ -8,7 +8,7 @@ git-update-ref - Update the object name stored in a ref safely\n>  SYNOPSIS\n>  --------\n>  [verse]\n> -'git update-ref' [-m <reason>] (-d <ref> [<oldvalue>] | [--no-deref] <ref> <newvalue> [<oldvalue>])\n> +'git update-ref' [-m <reason>] (-d <ref> [<oldvalue>] | [--no-deref] <ref> <newvalue> [<oldvalue>] | --stdin)\n>  \n>  DESCRIPTION\n>  -----------\n> @@ -58,6 +58,25 @@ archive by creating a symlink tree).\n>  With `-d` flag, it deletes the named <ref> after verifying it\n>  still contains <oldvalue>.\n>  \n> +With `--stdin`, update-ref reads instructions from standard input and\n> +performs all modifications together.  Empty lines are ignored.\n> +Each non-empty line is parsed as whitespace-separated arguments.\n> +Use single-quotes to enclose whitespace and backslashes and an\n> +unquoted backslash to escape a single quote.  Specify updates with\n> +lines of the form:\n> +\n> +\t[--no-deref] [--] <ref> <newvalue> [<oldvalue>]\n> +\n> +Lines of any other format or a repeated <ref> produce an error.\n> +Specify a zero <newvalue> to delete a ref and/or a zero <oldvalue>\n> +to make sure that a ref not exist.  Use either 40 \"0\" or the\n> +empty string (written as '') to specify a zero value.\n> +\n> +If all <ref>s can be locked with matching <oldvalue>s\n> +simultaneously all modifications are performed.  Otherwise, no\n\nComma after \"simultaneously\".\n\n> +modifications are performed.  Note that while each individual\n> +<ref> is updated or deleted atomically, a concurrent reader may\n> +still see a subset of the modifications.\n>  \n>  Logging Updates\n>  ---------------\n> diff --git a/builtin/update-ref.c b/builtin/update-ref.c\n> index 51d2684..eb8db85 100644\n> --- a/builtin/update-ref.c\n> +++ b/builtin/update-ref.c\n> @@ -6,19 +6,129 @@\n>  static const char * const git_update_ref_usage[] = {\n>  \tN_(\"git update-ref [options] -d <refname> [<oldval>]\"),\n>  \tN_(\"git update-ref [options]    <refname> <newval> [<oldval>]\"),\n> +\tN_(\"git update-ref [options] --stdin\"),\n>  \tNULL\n>  };\n>  \n> +static const char blank[] = \" \\t\\r\\n\";\n> +\n> +static int updates_size;\n> +static int updates_count;\n> +static struct ref_update *updates;\n> +\n> +static const char* update_refs_stdin_next_arg(const char* next,\n> +\t\t\t\t\t      struct strbuf *arg)\n> +{\n> +\t/* Skip leading whitespace: */\n> +\twhile (isspace(*next))\n> +\t\t++next;\n> +\n> +\t/* Return NULL when no argument is found: */\n> +\tif (!*next)\n> +\t\treturn NULL;\n> +\n> +\t/* Parse the argument: */\n> +\tstrbuf_reset(arg);\n> +\tfor (;;) {\n> +\t\tchar c = *next;\n> +\t\tif (!c || isspace(c))\n> +\t\t\tbreak;\n> +\t\t++next;\n> +\t\tif (c == '\\'') {\n> +\t\t\tsize_t len = strcspn(next, \"'\");\n\nI agree with Junio that your quoting rules are peculiar.\n\n> +\t\t\tif (!next[len])\n> +\t\t\t\tdie(\"unterminated single-quote: '%s\", next);\n> +\t\t\tstrbuf_add(arg, next, len);\n> +\t\t\tnext += len + 1;\n> +\t\t\tcontinue;\n> +\t\t}\n> +\t\tif (c == '\\\\') {\n> +\t\t\tif (*next == '\\'')\n> +\t\t\t\tc = *next++;\n> +\t\t\telse\n> +\t\t\t\tdie(\"unquoted backslash not escaping \"\n> +\t\t\t\t    \"single-quote: \\\\%s\", next);\n> +\t\t}\n> +\t\tstrbuf_addch(arg, c);\n> +\t}\n> +\treturn next;\n> +}\n> +\n> +static void update_refs_stdin(const char *line)\n> +{\n> +\tint options = 1, flags = 0, argc = 0;\n> +\tchar *argv[3] = {0, 0, 0};\n> +\tstruct strbuf arg = STRBUF_INIT;\n> +\tstruct ref_update *update;\n> +\tconst char *next = line;\n> +\n> +\t/* Skip blank lines: */\n> +\tif (!line[0])\n> +\t\treturn;\n> +\n> +\t/* Parse arguments on this line: */\n> +\twhile ((next = update_refs_stdin_next_arg(next, &arg)) != NULL) {\n> +\t\tif (options && arg.buf[0] == '-')\n> +\t\t\tif (!strcmp(arg.buf, \"--no-deref\"))\n> +\t\t\t\tflags |= REF_NODEREF;\n> +\t\t\telse if (!strcmp(arg.buf, \"--\"))\n> +\t\t\t\toptions = 0;\n> +\t\t\telse\n> +\t\t\t\tdie(\"unknown option %s\", arg.buf);\n> +\t\telse if (argc >= 3)\n> +\t\t\tdie(\"too many arguments on line: %s\", line);\n> +\t\telse {\n> +\t\t\targv[argc++] = xstrdup(arg.buf);\n> +\t\t\toptions = 0;\n> +\t\t}\n> +\t}\n> +\tstrbuf_release(&arg);\n> +\n> +\t/* Allocate and zero-init a struct ref_update: */\n> +\tif (updates_count == updates_size) {\n> +\t\tupdates_size = updates_size ? (updates_size * 2) : 16;\n> +\t\tupdates = xrealloc(updates, sizeof(*updates) * updates_size);\n> +\t\tmemset(updates + updates_count, 0,\n> +\t\t       sizeof(*updates) * (updates_size - updates_count));\n> +\t}\n\nHere you can use ARRAY_GROW().  See\n\n    Documentation/technical/api-allocation-growing.txt\n\n> +\tupdate = &updates[updates_count++];\n> +\tupdate->flags = flags;\n> +\n> +\t/* Set the update ref_name: */\n> +\tif (!argv[0])\n> +\t\tdie(\"no ref on line: %s\", line);\n> +\tif (check_refname_format(argv[0], REFNAME_ALLOW_ONELEVEL))\n> +\t\tdie(\"invalid ref format on line: %s\", line);\n> +\tupdate->ref_name = argv[0];\n> +\targv[0] = 0;\n> +\n> +\t/* Set the update new_sha1 and, if specified, old_sha1: */\n> +\tif (!argv[1])\n> +\t\tdie(\"missing new value on line: %s\", line);\n> +\tif (*argv[1] && get_sha1(argv[1], update->new_sha1))\n> +\t\tdie(\"invalid new value on line: %s\", line);\n> +\tif (argv[2]) {\n> +\t\tupdate->have_old = 1;\n> +\t\tif (*argv[2] && get_sha1(argv[2], update->old_sha1))\n> +\t\t\tdie(\"invalid old value on line: %s\", line);\n> +\t}\n> +\n> +\twhile (argc > 0)\n> +\t\tfree(argv[--argc]);\n> +}\n> +\n>  int cmd_update_ref(int argc, const char **argv, const char *prefix)\n>  {\n>  \tconst char *refname, *oldval, *msg = NULL;\n>  \tunsigned char sha1[20], oldsha1[20];\n> -\tint delete = 0, no_deref = 0, flags = 0;\n> +\tint delete = 0, no_deref = 0, read_stdin = 0, flags = 0;\n> +\tstruct strbuf line = STRBUF_INIT;\n>  \tstruct option options[] = {\n>  \t\tOPT_STRING( 'm', NULL, &msg, N_(\"reason\"), N_(\"reason of the update\")),\n>  \t\tOPT_BOOLEAN('d', NULL, &delete, N_(\"delete the reference\")),\n>  \t\tOPT_BOOLEAN( 0 , \"no-deref\", &no_deref,\n>  \t\t\t\t\tN_(\"update <refname> not the one it points to\")),\n> +\t\tOPT_BOOLEAN( 0 , \"stdin\", &read_stdin, N_(\"read updates from stdin\")),\n>  \t\tOPT_END(),\n>  \t};\n>  \n> @@ -28,6 +138,15 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)\n>  \tif (msg && !*msg)\n>  \t\tdie(\"Refusing to perform update with empty message.\");\n>  \n> +\tif (read_stdin) {\n> +\t\tif (delete || no_deref || argc > 0)\n> +\t\t\tusage_with_options(git_update_ref_usage, options);\n> +\t\twhile (strbuf_getline(&line, stdin, '\\n') != EOF)\n> +\t\t\tupdate_refs_stdin(line.buf);\n> +\t\tstrbuf_release(&line);\n> +\t\treturn update_refs(msg, updates, updates_count, DIE_ON_ERR);\n> +\t}\n> +\n>  \tif (delete) {\n>  \t\tif (argc < 1 || argc > 2)\n>  \t\t\tusage_with_options(git_update_ref_usage, options);\n> \n\n\n-- \nMichael Haggerty\nmhagger@alum.mit.edu\nhttp://softwareswirl.blogspot.com/\n"},{"id":"226430","messageId":"52223DD6.3070303@alum.mit.edu","threadId":"34797","inReplyTo":"cover.1377885441.git.brad.king@kitware.com","subject":"Re: [PATCH v2 0/8] Multiple simultaneously locked ref updates","fromName":"Michael Haggerty","fromEmail":"mhagger@alum.mit.edu","sentAt":"2013-08-31T19:02:46Z","receivedAt":"2013-08-31T19:02:46Z","isPatch":true,"sender":{"key":"mhagger@alum.mit.edu","avatar":"https://avatars.githubusercontent.com/u/119718?v=4"},"body":"On 08/30/2013 08:11 PM, Brad King wrote:\n> Here is the second revision of a series to support locking multiple\n> refs at the same time to update all of them consistently.  The first\n> series can be found at $gmane/233260.  This revision is ready to\n> consider for integration.\n\nI'm very interested in this area and I regret that I have been so\ninvisible lately.  I definitely like the way your changes are going.\n\nI have been doing some work in the same neighborhood and our work\noverlaps somewhat.  Namely, it is incorrect that delete_ref() currently\ndeletes the loose ref before rewriting packed-refs, because it can cause\na simultaneous reader to see the packed value for a moment in time, and\nthe packed value might not even point to a valid object anymore.  In\nfact, the current version is even worse: it deletes the loose reference\nbefore even locking the packed-refs file, so if the packed refs file\ncannot be rewritten (e.g., because it is locked by another process) then\nthe reference is permanently left in a corrupt state.\n\nOn the other hand, writing the packed-refs file first would also be\nincorrect, because a pack-refs process could jump in before the loose\nrefs file was deleted, read the loose value, and write it to the new\npacked-refs file.  The result would be that the first process would\nthink that it had deleted the reference but it would still exist (not\nsuch a catastrophe, but incorrect nevertheless).\n\nThe solution that I have been working on is to first lock *both* the\nloose and packed refs files, then rewrite the packed-refs file *but\nretain a lock on it*, then rewrite the loose-ref file and release its\nlock, then release the lock on the packed-refs file.  By retaining the\nlock on the packed-refs file during the whole \"transaction\", we prevent\nanother process from trying to pack the refs before our reference is\ncompletely deleted.  This requires the file-locking API to be enhanced\nto allow a file to be replaced by its new version while still retaining\na lock on the file.\n\nYour code has the same bug as the original (it's not your fault!) so I\nthink it will eventually have to be fixed to look something like\n\n    acquire lock on packed-refs\n\n    for reference in ref_updates:\n        lock reference\n        if old sha1 known:\n            verify old sha1 is still current\n\n    for reference in ref_updates:\n        if reference should be created/modified:\n            modify reference\n            release lock on reference\n\n    delete references from packed-refs file and activate new\n            version of the file *but retain a lock on the\n            packed-refs file*\n    for reference in ref_updates:\n        if reference should be deleted:\n            delete loose version of reference\n            release lock on reference\n\n    release lock on packed-refs file\n\nThis is really all just for your information; there is certainly no\nobligation for you to fix this pre-existing problem.  And I'm working on\nit anyway; if you happen to be interested you can view my current\nwork-in-progress on GitHub (though it still doesn't work!):\n\n    https://github.com/mhagger/git/tree/WIP-delete-ref-locking\n\nFeedback would of course be welcome.\n\nMichael\n\n-- \nMichael Haggerty\nmhagger@alum.mit.edu\nhttp://softwareswirl.blogspot.com/\n"},{"id":"226458","messageId":"CAPig+cQhH+t6unkea=0O2neJm8gOFaiKzPdSnuBaRy=Sdruu=g@mail.gmail.com","threadId":"34797","inReplyTo":"944b2af0ff064577734fd13cf40039fa4ebc10ec.1377885441.git.brad.king@kitware.com","subject":"Re: [PATCH v2 8/8] update-ref: add test cases covering --stdin signature","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2013-09-01T03:41:45Z","receivedAt":"2013-09-01T03:41:45Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Fri, Aug 30, 2013 at 2:12 PM, Brad King <brad.king@kitware.com> wrote:\n> Extend t/t1400-update-ref.sh to cover cases using the --stdin option.\n>\n> Signed-off-by: Brad King <brad.king@kitware.com>\n> ---\n>  t/t1400-update-ref.sh |  206 +++++++++++++++++++++++++++++++++++++++++++++++++\n>  1 file changed, 206 insertions(+)\n>\n> diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh\n> index e415ee0..9fd03fc 100755\n> --- a/t/t1400-update-ref.sh\n> +++ b/t/t1400-update-ref.sh\n> @@ -302,4 +302,210 @@ test_expect_success \\\n>         'git cat-file blob master@{2005-05-26 23:42}:F (expect OTHER)' \\\n>         'test OTHER = $(git cat-file blob \"master@{2005-05-26 23:42}:F\")'\n>\n> +a=refs/heads/a\n> +b=refs/heads/b\n> +c=refs/heads/c\n> +z=0000000000000000000000000000000000000000\n> +e=\"''\"\n> +\n> +test_expect_success 'stdin works with no input' '\n> +       rm -f stdin &&\n> +       touch stdin &&\n\nUnless the timestamp of 'stdin' has particular significance, modern\ngit tests avoid 'touch' in favor of creating the empty file like this\n\n    >stdin &&\n\n> +       git update-ref --stdin < stdin &&\n\nStyle: Git test scripts omit whitespace following <, >, <<, and >>.\n\n> +       git rev-parse --verify -q $m\n> +'\n> +\n> +test_expect_success 'stdin fails with bad line lines' '\n> +       echo \" \" > stdin &&\n> +       test_must_fail git update-ref --stdin < stdin 2> err &&\n> +       grep \"fatal: no ref on line:  \" err &&\n> +       echo \"--\" > stdin &&\n> +       test_must_fail git update-ref --stdin < stdin 2> err &&\n> +       grep \"fatal: no ref on line: --\" err &&\n> +       echo \"--bad-option\" > stdin &&\n> +       test_must_fail git update-ref --stdin < stdin 2> err &&\n> +       grep \"fatal: unknown option --bad-option\" err &&\n> +       echo \"-\\'\"'\"' $a $m\" > stdin &&\n> +       test_must_fail git update-ref --stdin < stdin 2> err &&\n> +       grep \"fatal: unknown option -'\"'\"'\" err &&\n> +       echo \"~a $m\" > stdin &&\n> +       test_must_fail git update-ref --stdin < stdin 2> err &&\n> +       grep \"fatal: invalid ref format on line: ~a $m\" err &&\n> +       echo \"$a '\"'\"'master\" > stdin &&\n> +       test_must_fail git update-ref --stdin < stdin 2> err &&\n> +       grep \"fatal: unterminated single-quote: '\"'\"'master\" err &&\n> +       echo \"$a \\master\" > stdin &&\n> +       test_must_fail git update-ref --stdin < stdin 2> err &&\n> +       grep \"fatal: unquoted backslash not escaping single-quote: \\\\\\\\master\" err &&\n> +       echo \"$a $m $m $m\" > stdin &&\n> +       test_must_fail git update-ref --stdin < stdin 2> err &&\n> +       grep \"fatal: too many arguments on line: $a $m $m $m\" err &&\n> +       echo \"$a\" > stdin &&\n> +       test_must_fail git update-ref --stdin < stdin 2> err &&\n> +       grep \"fatal: missing new value on line: $a\" err\n> +'\n\nDespite the semantic relationship between all these cases, if there is\na regression in one case, the person reading the verbose output has to\nstudy it carefully to determine the offending case. If you decompose\nthis monolith so that each case is in its own test_expect_success,\nthen the regressed case becomes immediately obvious.\n\n> +test_expect_success 'stdin fails with duplicate refs' '\n> +       echo \"$a $m\" > stdin &&\n> +       echo \"$b $m\" >> stdin &&\n> +       echo \"$a $m\" >> stdin &&\n\nThese multi-line preparations of 'stdin' might be more readable with a heredoc:\n\n    cat >stdin <<-EOF &&\n    $a $m\n    $b $m\n    $a $m\n    EOF\n\n> +       test_must_fail git update-ref --stdin < stdin 2> err &&\n> +       grep \"fatal: Multiple updates for ref '\"'\"'$a'\"'\"' not allowed.\" err\n> +'\n"},{"id":"226463","messageId":"xmqqvc2lul9g.fsf@gitster.dls.corp.google.com","threadId":"34797","inReplyTo":"d327c12d90c0c2500e51fb07c06f516f94438ff0.1377885441.git.brad.king@kitware.com","subject":"Re: [PATCH v2 3/8] refs: factor update_ref steps into helpers","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-09-01T06:08:27Z","receivedAt":"2013-09-01T06:08:27Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Brad King <brad.king@kitware.com> writes:\n\n> Factor the lock and write steps and error handling into helper functions\n> update_ref_lock and update_ref_write to allow later use elsewhere.\n> Expose lock_any_ref_for_update's type_p to update_ref_lock callers.\n>\n> Signed-off-by: Brad King <brad.king@kitware.com>\n> ---\n>  refs.c |   28 +++++++++++++++++++++++-----\n>  1 file changed, 23 insertions(+), 5 deletions(-)\n>\n> diff --git a/refs.c b/refs.c\n> index c69fd68..2e755b4 100644\n> --- a/refs.c\n> +++ b/refs.c\n> @@ -3170,12 +3170,13 @@ int for_each_reflog(each_ref_fn fn, void *cb_data)\n>  \treturn retval;\n>  }\n>  \n> -int update_ref(const char *action, const char *refname,\n> -\t\tconst unsigned char *sha1, const unsigned char *oldval,\n> -\t\tint flags, enum action_on_err onerr)\n> +static struct ref_lock *update_ref_lock(const char *refname,\n> +\t\t\t\t\tconst unsigned char *oldval,\n> +\t\t\t\t\tint flags, int *type_p,\n> +\t\t\t\t\tenum action_on_err onerr)\n>  {\n>  \tstatic struct ref_lock *lock;\n\nNot the fault of this patch, as the original update_ref() had it\nthis way, but it is not necessary to keep the value of this variable\nacross invocations.  Let's drop \"static\" from here, and also the\ncorresponding variable in the new update_ref().\n\nWill locally tweak while queuing.\n\n> -\tlock = lock_any_ref_for_update(refname, oldval, flags, NULL);\n> +\tlock = lock_any_ref_for_update(refname, oldval, flags, type_p);\n>  \tif (!lock) {\n>  \t\tconst char *str = \"Cannot lock the ref '%s'.\";\n>  \t\tswitch (onerr) {\n> @@ -3183,8 +3184,14 @@ int update_ref(const char *action, const char *refname,\n>  \t\tcase DIE_ON_ERR: die(str, refname); break;\n>  \t\tcase QUIET_ON_ERR: break;\n>  \t\t}\n> -\t\treturn 1;\n>  \t}\n> +\treturn lock;\n> +}\n> +\n> +static int update_ref_write(const char *action, const char *refname,\n> +\t\t\t    const unsigned char *sha1, struct ref_lock *lock,\n> +\t\t\t    enum action_on_err onerr)\n> +{\n>  \tif (write_ref_sha1(lock, sha1, action) < 0) {\n>  \t\tconst char *str = \"Cannot update the ref '%s'.\";\n>  \t\tswitch (onerr) {\n> @@ -3197,6 +3204,17 @@ int update_ref(const char *action, const char *refname,\n>  \treturn 0;\n>  }\n>  \n> +int update_ref(const char *action, const char *refname,\n> +\t       const unsigned char *sha1, const unsigned char *oldval,\n> +\t       int flags, enum action_on_err onerr)\n> +{\n> +\tstatic struct ref_lock *lock;\n> +\tlock = update_ref_lock(refname, oldval, flags, 0, onerr);\n> +\tif (!lock)\n> +\t\treturn 1;\n> +\treturn update_ref_write(action, refname, sha1, lock, onerr);\n> +}\n> +\n>  struct ref *find_ref_by_name(const struct ref *list, const char *name)\n>  {\n>  \tfor ( ; list; list = list->next)\n"},{"id":"226464","messageId":"xmqqob8dul99.fsf@gitster.dls.corp.google.com","threadId":"34797","inReplyTo":"edaddbd4e303866f789f1a4f755a9da77590aeef.1377885441.git.brad.king@kitware.com","subject":"Re: [PATCH v2 6/8] refs: add update_refs for multiple simultaneous updates","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-09-01T06:08:34Z","receivedAt":"2013-09-01T06:08:34Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Brad King <brad.king@kitware.com> writes:\n\n> Add 'struct ref_update' to encode the information needed to update or\n> delete a ref (name, new sha1, optional old sha1, no-deref flag).  Add\n> function 'update_refs' accepting an array of updates to perform.  First\n> sort the input array to order locks consistently everywhere and reject\n> multiple updates to the same ref.  Then acquire locks on all refs with\n> verified old values.  Then update or delete all refs accordingly.  Fail\n> if any one lock cannot be obtained or any one old value does not match.\n\nOK.  The code releases the locks it acquired so far when it fails,\nwhich is good.\n\n> Though the refs themeselves cannot be modified together in a single\n\n\"themselves\".\n\n> atomic transaction, this function does enable some useful semantics.\n> For example, a caller may create a new branch starting from the head of\n> another branch and rewind the original branch at the same time.  This\n> transfers ownership of commits between branches without risk of losing\n> commits added to the original branch by a concurrent process, or risk of\n> a concurrent process creating the new branch first.\n\n> +static int ref_update_compare(const void *r1, const void *r2)\n> +{\n> +\tstruct ref_update *u1 = (struct ref_update *)(r1);\n> +\tstruct ref_update *u2 = (struct ref_update *)(r2);\n> +\tint ret;\n\nLet's have a blank line between the end of decls and the beginning\nof the body here.\n\n> +\tret = strcmp(u1->ref_name, u2->ref_name);\n> +\tif (ret)\n> +\t\treturn ret;\n> +\tret = hashcmp(u1->new_sha1, u2->new_sha1);\n> +\tif (ret)\n> +\t\treturn ret;\n> +\tret = hashcmp(u1->old_sha1, u2->old_sha1);\n> +\tif (ret)\n> +\t\treturn ret;\n> +\tret = u1->flags - u2->flags;\n> +\tif (ret)\n> +\t\treturn ret;\n> +\treturn u1->have_old - u2->have_old;\n> +}\n\nI notice that we are using an array of structures and letting qsort\nswap 50~64 bytes of data, instead of sorting an array of pointers,\neach element of which points at a structure.  This may not matter\nunless we are asked to update thousands at once, so I think it is OK\nfor now.\n\n> +static int ref_update_reject_duplicates(struct ref_update *updates, int n,\n> +\t\t\t\t\tenum action_on_err onerr)\n> +{\n> +\tint i;\n> +\tfor (i = 1; i < n; ++i)\n> +\t\tif (!strcmp(updates[i - 1].ref_name, updates[i].ref_name))\n> +\t\t\tbreak;\n\nOptionally we could silently dedup multiple identical updates and\nnot fail it in ref-update-reject-duplicates.  But that does not have\nto be done until we find people's script would benefit from such a\nnicety.\n\nBy the way, unless there is a strong reason not to do so,\npost-increment \"i++\" (and pre-decrement \"--i\", if you use it) is the\nnorm around here.  Especially in places like the third part of a\nfor(;;) loop where people are used to see \"i++\", breaking the idiom\nmakes readers wonder if there is something else going on.\n\n> +\t/* Perform updates first so live commits remain referenced: */\n> +\tfor (i = 0; i < n; ++i)\n> +\t\tif (!is_null_sha1(updates[i].new_sha1)) {\n> +\t\t\tret |= update_ref_write(action,\n> +\t\t\t\t\t\tupdates[i].ref_name,\n> +\t\t\t\t\t\tupdates[i].new_sha1,\n> +\t\t\t\t\t\tlocks[i], onerr);\n> +\t\t\tlocks[i] = 0; /* freed by update_ref_write */\n\nI think what is assigned here is a NULL pointer.\n\nWill locally tweak while queuing.  Thanks.\n"},{"id":"226533","messageId":"5224C89D.3050002@kitware.com","threadId":"34797","inReplyTo":"xmqqvc2lul9g.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH v2 3/8] refs: factor update_ref steps into helpers","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-09-02T17:19:25Z","receivedAt":"2013-09-02T17:19:25Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"On 09/01/2013 02:08 AM, Junio C Hamano wrote:\n> Brad King <brad.king@kitware.com> writes:\n>>  \tstatic struct ref_lock *lock;\n> \n> Not the fault of this patch, as the original update_ref() had it\n> this way, but it is not necessary to keep the value of this variable\n> across invocations.  Let's drop \"static\" from here, and also the\n> corresponding variable in the new update_ref().\n\nFixed in next revision.\n\nThanks,\n-Brad\n"},{"id":"226534","messageId":"5224C8B0.20206@kitware.com","threadId":"34797","inReplyTo":"52221A15.20804@alum.mit.edu","subject":"Re: [PATCH v2 4/8] refs: factor delete_ref loose ref step into a helper","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-09-02T17:19:44Z","receivedAt":"2013-09-02T17:19:44Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"On 08/31/2013 12:30 PM, Michael Haggerty wrote:\n> Given that ret is only returned, you could restore the filename before\n> the if statement and replace the ret variable with an immediate return\n> statement:\n\nGood idea.  Fixed in next revision.\n\nThanks,\n-Brad\n"},{"id":"226535","messageId":"5224C8C6.4050500@kitware.com","threadId":"34797","inReplyTo":"xmqqob8dul99.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH v2 6/8] refs: add update_refs for multiple simultaneous updates","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-09-02T17:20:06Z","receivedAt":"2013-09-02T17:20:06Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"On 09/01/2013 02:08 AM, Junio C Hamano wrote:\n>> Though the refs themeselves cannot be modified together in a single\n> \n> \"themselves\".\n\nFixed.\n\n> I notice that we are using an array of structures and letting qsort\n> swap 50~64 bytes of data\n\nMichael suggested this too, so fixed.\n\n> Optionally we could silently dedup multiple identical updates and\n> not fail it in ref-update-reject-duplicates.  But that does not have\n> to be done until we find people's script would benefit from such a\n> nicety.\n\nWe can always be less strict about input later, so I'd like to keep\nthe implementation simpler for now.\n\n> By the way, unless there is a strong reason not to do so,\n> post-increment \"i++\" (and pre-decrement \"--i\", if you use it) is the\n> norm around here.\n\nOkay, fixed in entire series.\n\n>> +\t\t\tlocks[i] = 0; /* freed by update_ref_write */\n> \n> I think what is assigned here is a NULL pointer.\n\nYes, fixed.\n\nThanks,\n-Brad\n"},{"id":"226536","messageId":"5224C8E5.1080908@kitware.com","threadId":"34797","inReplyTo":"52223398.2080109@alum.mit.edu","subject":"Re: [PATCH v2 6/8] refs: add update_refs for multiple simultaneous updates","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-09-02T17:20:37Z","receivedAt":"2013-09-02T17:20:37Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"On 08/31/2013 02:19 PM, Michael Haggerty wrote:\n> s/themeselves/themselves/\n\nFixed.\n\n>> +\tstruct ref_update *u1 = (struct ref_update *)(r1);\n>> +\tstruct ref_update *u2 = (struct ref_update *)(r2);\n> \n> If you declare u1 and u2 to be \"const struct ref_update *\" (i.e., add\n> \"const\"), then you have const correctness and don't need the explicit\n> casts.  (And the parentheses around r1 and r2 are superfluous in any case.)\n\nFixed.\n\n>> +\tret = strcmp(u1->ref_name, u2->ref_name);\n> \n> Is there a need to compare more than ref_name?  If two entries are found\n> with the same name, then ref_update_reject_duplicates() will error out\n\nJunio mentioned possibility of auto-combining identical entries which would\nneed full ordering.  I think that can be added later so for now we can sort\nonly by ref name.  Thanks.\n\n>> +\t\tif (!strcmp(updates[i - 1].ref_name, updates[i].ref_name))\n>> +\t\t\tbreak;\n> \n> The error handling code could be right here instead of the \"break\"\n> statement, removing the need for the \"if\" conditional.\n\nFixed.\n\n>> +\t/* Allocate work space: */\n>> +\tupdates = xmalloc(sizeof(struct ref_update) * n);\n> \n> It seems preferred here to write\n> \n> \tupdates = xmalloc(sizeof(*updates) * n);\n> \n> as this will continue to work if the type of updates is ever changed.\n\nYes, thanks.\n\n> Similarly for the next lines.\n> \n>> +\ttypes = xmalloc(sizeof(int) * n);\n>> +\tlocks = xmalloc(sizeof(struct ref_lock *) * n);\n>> +\tdelnames = xmalloc(sizeof(const char *) * n);\n> \n> An alternative to managing separate arrays to hold types and locks would\n> be to include the scratch space in struct ref_update and document it\n> \"for internal use only; need not be initialized by caller\".  On the one\n> hand it's ugly to cruft up the \"interface\" with internal implementation\n> details; on the other hand there is precedent for this sort of thing\n> (e.g., ref_lock::force_write or lock_file::on_list) and it would\n> simplify the code.\n\nI think the \"goto cleanup\" reorganization simplifies the code enough\nto not need this.  After changing \"updates\" to an array of pointers\nit needs to be separate so we can sort.  Also \"delnames\" needs to be\na separate array to pass to repack_without_refs.\n\n>> +\t/* Copy, sort, and reject duplicate refs: */\n>> +\tmemcpy(updates, updates_orig, sizeof(struct ref_update) * n);\n>> +\tqsort(updates, n, sizeof(struct ref_update), ref_update_compare);\n> \n> You could save some space and memory shuffling (during memcpy() and\n> qsort()) if you would declare \"updates\" to be an array of pointers to\n> \"struct ref_update\" rather than an array of structs.  Sorting could then\n> be done by moving pointers around instead of moving the structs.  This\n> would also make it easier for update_refs() to pass information about\n> the references back to its caller, should that ever be needed.\n\nGood idea.  Changed in next revision.\n\n>> +\t\t\tret |= update_ref_write(action,\n>> +\t\t\t\t\t\tupdates[i].ref_name,\n>> +\t\t\t\t\t\tupdates[i].new_sha1,\n>> +\t\t\t\t\t\tlocks[i], onerr);\n>> +\t\t\tlocks[i] = 0; /* freed by update_ref_write */\n>> +\t\t}\n>> +\n> \n> Hmmm, if one of the calls to update_ref_write() fails, would it be safer\n> to abort the rest of the work (especially the reference deletions)?\n\nYes.  Since we already have the lock at this point something must be\ngoing pretty wrong if this fails so it is best to abort altogether.\n\n>> +\tfree(updates);\n>> +\tfree(types);\n>> +\tfree(locks);\n>> +\tfree(delnames);\n>> +\treturn ret;\n>> +}\n> \n> There's a lot of duplicated cleanup code in the function.  If you put a\n> label before the final for loop, and if you initialize the locks array\n> to zeros (e.g., by using xcalloc()), then the three exits could all\n> share the same code \"ret = 1; goto cleanup;\".\n\nDone, thanks.\n\n>> +struct ref_update {\n> \n> Please document this structure, especially the relationship between\n> have_old and old_sha1.\n\nDone.  I also moved it to the top of the header just under ref_lock\nso it can be used by other APIs later.\n\nThanks,\n-Brad\n"},{"id":"226537","messageId":"5224C902.6060408@kitware.com","threadId":"34797","inReplyTo":"52223918.6040603@alum.mit.edu","subject":"Re: [PATCH v2 7/8] update-ref: support multiple simultaneous updates","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-09-02T17:21:06Z","receivedAt":"2013-09-02T17:21:06Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"On 08/31/2013 02:42 PM, Michael Haggerty wrote:\n> On 08/30/2013 08:12 PM, Brad King wrote:\n>> +If all <ref>s can be locked with matching <oldvalue>s\n>> +simultaneously all modifications are performed.  Otherwise, no\n> \n> Comma after \"simultaneously\".\n\nFixed.\n\n> I agree with Junio that your quoting rules are peculiar.\n\nI won't disagree.  That's why I asked for suggestions in the\noriginal PATCH/RFC cover letter ;)\n\n>> +\t/* Allocate and zero-init a struct ref_update: */\n> \n> Here you can use ARRAY_GROW().  See\n> \n>     Documentation/technical/api-allocation-growing.txt\n\nFixed.\n\nThanks,\n-Brad\n"},{"id":"226538","messageId":"5224C990.8070701@kitware.com","threadId":"34797","inReplyTo":"xmqqbo4eyeps.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH v2 7/8] update-ref: support multiple simultaneous updates","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-09-02T17:23:28Z","receivedAt":"2013-09-02T17:23:28Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"On 08/30/2013 06:51 PM, Junio C Hamano wrote:\n> Brad King <brad.king@kitware.com> writes:\n>> +With `--stdin`, update-ref reads instructions from standard input and\n>> +performs all modifications together.  Empty lines are ignored.\n>> +Each non-empty line is parsed as whitespace-separated arguments.\n>> +Use single-quotes to enclose whitespace and backslashes and an\n>> +unquoted backslash to escape a single quote.\n> \n> That is somewhat unusual.\n> \n> When we need to deal with arbitrary strings (like pathnames), other\n> parts of the system usually give the user two interfaces, --stdin\n> with and without -z, and the strings are C-quoted when run without\n> the -z option, and terminated with NUL when run with the -z option.\n\nGreat, this was the kind of suggestion I was looking for in the original\nPATCH/RFC cover letter.  Thanks.  I'll start with the C-quoted version\nand think about adding -z once we've agreed on that format.\n\n>> +Specify updates with\n>> +lines of the form:\n>> +\n>> +\t[--no-deref] [--] <ref> <newvalue> [<oldvalue>]\n> \n> What is -- doing here?  refs given to update-ref begin with refs/\n> (otherwise it is HEAD), no?\n\nThe existing update-ref command line can be used to create all kinds\nof refs, even starting in \"-\".  I didn't want to add any restriction\nin the stdin format.  I'm not opposed to it though.\n\nThanks,\n-Brad\n"},{"id":"226539","messageId":"5224C99E.3050209@kitware.com","threadId":"34797","inReplyTo":"CAPig+cQhH+t6unkea=0O2neJm8gOFaiKzPdSnuBaRy=Sdruu=g@mail.gmail.com","subject":"Re: [PATCH v2 8/8] update-ref: add test cases covering --stdin signature","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-09-02T17:23:42Z","receivedAt":"2013-09-02T17:23:42Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"On 08/31/2013 11:41 PM, Eric Sunshine wrote:\n>> +       rm -f stdin &&\n>> +       touch stdin &&\n> \n> Unless the timestamp of 'stdin' has particular significance, modern\n> git tests avoid 'touch' in favor of creating the empty file like this\n> \n>     >stdin &&\n\nFixed.\n\n>> +       git update-ref --stdin < stdin &&\n> \n> Style: Git test scripts omit whitespace following <, >, <<, and >>.\n\nFixed.\n\n>> +test_expect_success 'stdin fails with bad line lines' '\n> \n> Despite the semantic relationship between all these cases, if there is\n> a regression in one case, the person reading the verbose output has to\n> study it carefully to determine the offending case. If you decompose\n> this monolith so that each case is in its own test_expect_success,\n> then the regressed case becomes immediately obvious.\n\nYes, of course.  Fixed.\n\n> multi-line preparations of 'stdin' might be more readable with a heredoc:\n> \n>     cat >stdin <<-EOF &&\n>     $a $m\n>     $b $m\n>     $a $m\n>     EOF\n\nFixed.\n\nThanks,\n-Brad\n"},{"id":"226543","messageId":"cover.1378142795.git.brad.king@kitware.com","threadId":"34797","inReplyTo":"cover.1377885441.git.brad.king@kitware.com","subject":"[PATCH v3 0/8] Multiple simultaneously locked ref updates","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-09-02T17:48:48Z","receivedAt":"2013-09-02T17:48:48Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"Hi Folks,\n\nHere is the third revision of a series to support locking multiple\nrefs at the same time to update all of them consistently.  The\nprevious revisions of the series can be found at $gmane/233260 and\n$gmane/233458.\n\nUpdates since the previous revision of the series:\n\n* Incorporated style fixes suggested in patches 6-8.\n\n* In patch 3, the local \"lock\" variables in update_ref_lock and\n  update_ref now drop the existing \"static\" declaration.\n\n* In patch 4, delete_ref_loose internals have been cleaned up\n  as Michael suggested.\n\n* In patch 6:\n\n  - struct ref_update has been documented\n  - update_refs now takes an array of pointers to struct ref_update\n    as Michael and Junio both suggested\n  - update_refs return cases were simplified with a label and goto\n  - update_refs now stops immediately if any ref write fails\n  - ref_update_compare now compares only the ref name\n\n* In patch 7, another new input format is proposed.  It now uses\n  quoting based on unquote_c_style.\n\n* In patch 8, more new test cases have been added.  Failure cases\n  are now covered in separate steps to simplify diagnosis.\n\n-Brad\n\nBrad King (8):\n  reset: rename update_refs to reset_refs\n  refs: report ref type from lock_any_ref_for_update\n  refs: factor update_ref steps into helpers\n  refs: factor delete_ref loose ref step into a helper\n  refs: add function to repack without multiple refs\n  refs: add update_refs for multiple simultaneous updates\n  update-ref: support multiple simultaneous updates\n  update-ref: add test cases covering --stdin signature\n\n Documentation/git-update-ref.txt |   20 ++-\n branch.c                         |    2 +-\n builtin/commit.c                 |    2 +-\n builtin/fetch.c                  |    3 +-\n builtin/receive-pack.c           |    3 +-\n builtin/reflog.c                 |    2 +-\n builtin/replace.c                |    2 +-\n builtin/reset.c                  |    4 +-\n builtin/tag.c                    |    2 +-\n builtin/update-ref.c             |  103 ++++++++++++++-\n fast-import.c                    |    2 +-\n refs.c                           |  191 ++++++++++++++++++++++++----\n refs.h                           |   22 +++-\n sequencer.c                      |    3 +-\n t/t1400-update-ref.sh            |  256 ++++++++++++++++++++++++++++++++++++++\n 15 files changed, 578 insertions(+), 39 deletions(-)\n\n-- \n1.7.10.4\n"},{"id":"226540","messageId":"ce57e08b2fe53b23ba3627bc8a9e0cc44a7683e2.1378142796.git.brad.king@kitware.com","threadId":"34797","inReplyTo":"cover.1378142795.git.brad.king@kitware.com","subject":"[PATCH v3 1/8] reset: rename update_refs to reset_refs","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-09-02T17:48:49Z","receivedAt":"2013-09-02T17:48:49Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"The function resets refs rather than doing arbitrary updates.\nRename it to allow a future general-purpose update_refs function\nto be added.\n\nSigned-off-by: Brad King <brad.king@kitware.com>\n---\n builtin/reset.c |    4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/reset.c b/builtin/reset.c\nindex afa6e02..789ee48 100644\n--- a/builtin/reset.c\n+++ b/builtin/reset.c\n@@ -219,7 +219,7 @@ static const char **parse_args(const char **argv, const char *prefix, const char\n \treturn argv[0] ? get_pathspec(prefix, argv) : NULL;\n }\n \n-static int update_refs(const char *rev, const unsigned char *sha1)\n+static int reset_refs(const char *rev, const unsigned char *sha1)\n {\n \tint update_ref_status;\n \tstruct strbuf msg = STRBUF_INIT;\n@@ -350,7 +350,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)\n \tif (!pathspec && !unborn) {\n \t\t/* Any resets without paths update HEAD to the head being\n \t\t * switched to, saving the previous head in ORIG_HEAD before. */\n-\t\tupdate_ref_status = update_refs(rev, sha1);\n+\t\tupdate_ref_status = reset_refs(rev, sha1);\n \n \t\tif (reset_type == HARD && !update_ref_status && !quiet)\n \t\t\tprint_new_head_line(lookup_commit_reference(sha1));\n-- \n1.7.10.4\n"},{"id":"226545","messageId":"b06a2a1878e1140949f4bce1760e6ac3a5633d4c.1378142796.git.brad.king@kitware.com","threadId":"34797","inReplyTo":"cover.1378142795.git.brad.king@kitware.com","subject":"[PATCH v3 2/8] refs: report ref type from lock_any_ref_for_update","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-09-02T17:48:50Z","receivedAt":"2013-09-02T17:48:50Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"Expose lock_ref_sha1_basic's type_p argument to callers of\nlock_any_ref_for_update.  Update all call sites to ignore it by passing\nNULL for now.\n\nSigned-off-by: Brad King <brad.king@kitware.com>\n---\n branch.c               |    2 +-\n builtin/commit.c       |    2 +-\n builtin/fetch.c        |    3 ++-\n builtin/receive-pack.c |    3 ++-\n builtin/reflog.c       |    2 +-\n builtin/replace.c      |    2 +-\n builtin/tag.c          |    2 +-\n fast-import.c          |    2 +-\n refs.c                 |    7 ++++---\n refs.h                 |    2 +-\n sequencer.c            |    3 ++-\n 11 files changed, 17 insertions(+), 13 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex c5c6984..f2d383f 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -291,7 +291,7 @@ void create_branch(const char *head,\n \thashcpy(sha1, commit->object.sha1);\n \n \tif (!dont_change_ref) {\n-\t\tlock = lock_any_ref_for_update(ref.buf, NULL, 0);\n+\t\tlock = lock_any_ref_for_update(ref.buf, NULL, 0, NULL);\n \t\tif (!lock)\n \t\t\tdie_errno(_(\"Failed to lock ref for update\"));\n \t}\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 10acc53..be08f41 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -1618,7 +1618,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \t\t\t\t\t   !current_head\n \t\t\t\t\t   ? NULL\n \t\t\t\t\t   : current_head->object.sha1,\n-\t\t\t\t\t   0);\n+\t\t\t\t\t   0, NULL);\n \n \tnl = strchr(sb.buf, '\\n');\n \tif (nl)\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex d784b2e..28e4025 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -246,7 +246,8 @@ static int s_update_ref(const char *action,\n \t\trla = default_rla.buf;\n \tsnprintf(msg, sizeof(msg), \"%s: %s\", rla, action);\n \tlock = lock_any_ref_for_update(ref->name,\n-\t\t\t\t       check_old ? ref->old_sha1 : NULL, 0);\n+\t\t\t\t       check_old ? ref->old_sha1 : NULL,\n+\t\t\t\t       0, NULL);\n \tif (!lock)\n \t\treturn errno == ENOTDIR ? STORE_REF_ERROR_DF_CONFLICT :\n \t\t\t\t\t  STORE_REF_ERROR_OTHER;\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex e3eb5fc..a323070 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -524,7 +524,8 @@ static const char *update(struct command *cmd)\n \t\treturn NULL; /* good */\n \t}\n \telse {\n-\t\tlock = lock_any_ref_for_update(namespaced_name, old_sha1, 0);\n+\t\tlock = lock_any_ref_for_update(namespaced_name, old_sha1,\n+\t\t\t\t\t       0, NULL);\n \t\tif (!lock) {\n \t\t\trp_error(\"failed to lock %s\", name);\n \t\t\treturn \"failed to lock\";\ndiff --git a/builtin/reflog.c b/builtin/reflog.c\nindex 54184b3..28d756a 100644\n--- a/builtin/reflog.c\n+++ b/builtin/reflog.c\n@@ -366,7 +366,7 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused,\n \t * we take the lock for the ref itself to prevent it from\n \t * getting updated.\n \t */\n-\tlock = lock_any_ref_for_update(ref, sha1, 0);\n+\tlock = lock_any_ref_for_update(ref, sha1, 0, NULL);\n \tif (!lock)\n \t\treturn error(\"cannot lock ref '%s'\", ref);\n \tlog_file = git_pathdup(\"logs/%s\", ref);\ndiff --git a/builtin/replace.c b/builtin/replace.c\nindex 59d3115..1ecae8d 100644\n--- a/builtin/replace.c\n+++ b/builtin/replace.c\n@@ -105,7 +105,7 @@ static int replace_object(const char *object_ref, const char *replace_ref,\n \telse if (!force)\n \t\tdie(\"replace ref '%s' already exists\", ref);\n \n-\tlock = lock_any_ref_for_update(ref, prev, 0);\n+\tlock = lock_any_ref_for_update(ref, prev, 0, NULL);\n \tif (!lock)\n \t\tdie(\"%s: cannot lock the ref\", ref);\n \tif (write_ref_sha1(lock, repl, NULL) < 0)\ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex af3af3f..2c867d2 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -577,7 +577,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \tif (annotate)\n \t\tcreate_tag(object, tag, &buf, &opt, prev, object);\n \n-\tlock = lock_any_ref_for_update(ref.buf, prev, 0);\n+\tlock = lock_any_ref_for_update(ref.buf, prev, 0, NULL);\n \tif (!lock)\n \t\tdie(_(\"%s: cannot lock the ref\"), ref.buf);\n \tif (write_ref_sha1(lock, object, NULL) < 0)\ndiff --git a/fast-import.c b/fast-import.c\nindex 23f625f..5c329f6 100644\n--- a/fast-import.c\n+++ b/fast-import.c\n@@ -1678,7 +1678,7 @@ static int update_branch(struct branch *b)\n \t\treturn 0;\n \tif (read_ref(b->name, old_sha1))\n \t\thashclr(old_sha1);\n-\tlock = lock_any_ref_for_update(b->name, old_sha1, 0);\n+\tlock = lock_any_ref_for_update(b->name, old_sha1, 0, NULL);\n \tif (!lock)\n \t\treturn error(\"Unable to lock %s\", b->name);\n \tif (!force_update && !is_null_sha1(old_sha1)) {\ndiff --git a/refs.c b/refs.c\nindex 7922261..c69fd68 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2121,11 +2121,12 @@ struct ref_lock *lock_ref_sha1(const char *refname, const unsigned char *old_sha\n }\n \n struct ref_lock *lock_any_ref_for_update(const char *refname,\n-\t\t\t\t\t const unsigned char *old_sha1, int flags)\n+\t\t\t\t\t const unsigned char *old_sha1,\n+\t\t\t\t\t int flags, int *type_p)\n {\n \tif (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL))\n \t\treturn NULL;\n-\treturn lock_ref_sha1_basic(refname, old_sha1, flags, NULL);\n+\treturn lock_ref_sha1_basic(refname, old_sha1, flags, type_p);\n }\n \n /*\n@@ -3174,7 +3175,7 @@ int update_ref(const char *action, const char *refname,\n \t\tint flags, enum action_on_err onerr)\n {\n \tstatic struct ref_lock *lock;\n-\tlock = lock_any_ref_for_update(refname, oldval, flags);\n+\tlock = lock_any_ref_for_update(refname, oldval, flags, NULL);\n \tif (!lock) {\n \t\tconst char *str = \"Cannot lock the ref '%s'.\";\n \t\tswitch (onerr) {\ndiff --git a/refs.h b/refs.h\nindex 9e5db3a..2cd307a 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -137,7 +137,7 @@ extern struct ref_lock *lock_ref_sha1(const char *refname, const unsigned char *\n #define REF_NODEREF\t0x01\n extern struct ref_lock *lock_any_ref_for_update(const char *refname,\n \t\t\t\t\t\tconst unsigned char *old_sha1,\n-\t\t\t\t\t\tint flags);\n+\t\t\t\t\t\tint flags, int *type_p);\n \n /** Close the file descriptor owned by a lock and return the status */\n extern int close_ref(struct ref_lock *lock);\ndiff --git a/sequencer.c b/sequencer.c\nindex 351548f..06e52b4 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -279,7 +279,8 @@ static int fast_forward_to(const unsigned char *to, const unsigned char *from,\n \tread_cache();\n \tif (checkout_fast_forward(from, to, 1))\n \t\texit(1); /* the callee should have complained already */\n-\tref_lock = lock_any_ref_for_update(\"HEAD\", unborn ? null_sha1 : from, 0);\n+\tref_lock = lock_any_ref_for_update(\"HEAD\", unborn ? null_sha1 : from,\n+\t\t\t\t\t   0, NULL);\n \tstrbuf_addf(&sb, \"%s: fast-forward\", action_name(opts));\n \tret = write_ref_sha1(ref_lock, to, sb.buf);\n \tstrbuf_release(&sb);\n-- \n1.7.10.4\n"},{"id":"226546","messageId":"36826922e62f2940a9592096ea55de009e234381.1378142796.git.brad.king@kitware.com","threadId":"34797","inReplyTo":"cover.1378142795.git.brad.king@kitware.com","subject":"[PATCH v3 3/8] refs: factor update_ref steps into helpers","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-09-02T17:48:51Z","receivedAt":"2013-09-02T17:48:51Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"Factor the lock and write steps and error handling into helper functions\nupdate_ref_lock and update_ref_write to allow later use elsewhere.\nExpose lock_any_ref_for_update's type_p to update_ref_lock callers.\n\nWhile at it, drop \"static\" from the local \"lock\" variable as it is not\nnecessary to keep across invocations.\n\nSigned-off-by: Brad King <brad.king@kitware.com>\n---\n refs.c |   30 ++++++++++++++++++++++++------\n 1 file changed, 24 insertions(+), 6 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex c69fd68..4347826 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -3170,12 +3170,13 @@ int for_each_reflog(each_ref_fn fn, void *cb_data)\n \treturn retval;\n }\n \n-int update_ref(const char *action, const char *refname,\n-\t\tconst unsigned char *sha1, const unsigned char *oldval,\n-\t\tint flags, enum action_on_err onerr)\n+static struct ref_lock *update_ref_lock(const char *refname,\n+\t\t\t\t\tconst unsigned char *oldval,\n+\t\t\t\t\tint flags, int *type_p,\n+\t\t\t\t\tenum action_on_err onerr)\n {\n-\tstatic struct ref_lock *lock;\n-\tlock = lock_any_ref_for_update(refname, oldval, flags, NULL);\n+\tstruct ref_lock *lock;\n+\tlock = lock_any_ref_for_update(refname, oldval, flags, type_p);\n \tif (!lock) {\n \t\tconst char *str = \"Cannot lock the ref '%s'.\";\n \t\tswitch (onerr) {\n@@ -3183,8 +3184,14 @@ int update_ref(const char *action, const char *refname,\n \t\tcase DIE_ON_ERR: die(str, refname); break;\n \t\tcase QUIET_ON_ERR: break;\n \t\t}\n-\t\treturn 1;\n \t}\n+\treturn lock;\n+}\n+\n+static int update_ref_write(const char *action, const char *refname,\n+\t\t\t    const unsigned char *sha1, struct ref_lock *lock,\n+\t\t\t    enum action_on_err onerr)\n+{\n \tif (write_ref_sha1(lock, sha1, action) < 0) {\n \t\tconst char *str = \"Cannot update the ref '%s'.\";\n \t\tswitch (onerr) {\n@@ -3197,6 +3204,17 @@ int update_ref(const char *action, const char *refname,\n \treturn 0;\n }\n \n+int update_ref(const char *action, const char *refname,\n+\t       const unsigned char *sha1, const unsigned char *oldval,\n+\t       int flags, enum action_on_err onerr)\n+{\n+\tstruct ref_lock *lock;\n+\tlock = update_ref_lock(refname, oldval, flags, 0, onerr);\n+\tif (!lock)\n+\t\treturn 1;\n+\treturn update_ref_write(action, refname, sha1, lock, onerr);\n+}\n+\n struct ref *find_ref_by_name(const struct ref *list, const char *name)\n {\n \tfor ( ; list; list = list->next)\n-- \n1.7.10.4\n"},{"id":"226542","messageId":"d699bcba91dc16500b7762759ce53b6ea4eb9939.1378142796.git.brad.king@kitware.com","threadId":"34797","inReplyTo":"cover.1378142795.git.brad.king@kitware.com","subject":"[PATCH v3 4/8] refs: factor delete_ref loose ref step into a helper","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-09-02T17:48:52Z","receivedAt":"2013-09-02T17:48:52Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"Factor loose ref deletion into helper function delete_ref_loose to allow\nlater use elsewhere.\n\nSigned-off-by: Brad King <brad.king@kitware.com>\n---\n refs.c |   27 +++++++++++++++++----------\n 1 file changed, 17 insertions(+), 10 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 4347826..ab9d22e 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2450,24 +2450,31 @@ static int repack_without_ref(const char *refname)\n \treturn commit_packed_refs();\n }\n \n+static int delete_ref_loose(struct ref_lock *lock, int flag)\n+{\n+\tif (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) {\n+\t\t/* loose */\n+\t\tint err, i = strlen(lock->lk->filename) - 5; /* .lock */\n+\n+\t\tlock->lk->filename[i] = 0;\n+\t\terr = unlink_or_warn(lock->lk->filename);\n+\t\tlock->lk->filename[i] = '.';\n+\t\tif (err && errno != ENOENT)\n+\t\t\treturn 1;\n+\t}\n+\treturn 0;\n+}\n+\n int delete_ref(const char *refname, const unsigned char *sha1, int delopt)\n {\n \tstruct ref_lock *lock;\n-\tint err, i = 0, ret = 0, flag = 0;\n+\tint ret = 0, flag = 0;\n \n \tlock = lock_ref_sha1_basic(refname, sha1, delopt, &flag);\n \tif (!lock)\n \t\treturn 1;\n-\tif (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) {\n-\t\t/* loose */\n-\t\ti = strlen(lock->lk->filename) - 5; /* .lock */\n-\t\tlock->lk->filename[i] = 0;\n-\t\terr = unlink_or_warn(lock->lk->filename);\n-\t\tif (err && errno != ENOENT)\n-\t\t\tret = 1;\n+\tret |= delete_ref_loose(lock, flag);\n \n-\t\tlock->lk->filename[i] = '.';\n-\t}\n \t/* removing the loose one could have resurrected an earlier\n \t * packed one.  Also, if it was not loose we need to repack\n \t * without it.\n-- \n1.7.10.4\n"},{"id":"226544","messageId":"75675cc2760d5bb806e1931cda8b335ec7121bf9.1378142796.git.brad.king@kitware.com","threadId":"34797","inReplyTo":"cover.1378142795.git.brad.king@kitware.com","subject":"[PATCH v3 5/8] refs: add function to repack without multiple refs","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-09-02T17:48:53Z","receivedAt":"2013-09-02T17:48:53Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"Generalize repack_without_ref as repack_without_refs to support a list\nof refs and implement the former in terms of the latter.\n\nSigned-off-by: Brad King <brad.king@kitware.com>\n---\n refs.c |   29 ++++++++++++++++++++++-------\n 1 file changed, 22 insertions(+), 7 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex ab9d22e..599504b 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2414,25 +2414,35 @@ static int curate_packed_ref_fn(struct ref_entry *entry, void *cb_data)\n \treturn 0;\n }\n \n-static int repack_without_ref(const char *refname)\n+static int repack_without_refs(const char **refnames, int n)\n {\n \tstruct ref_dir *packed;\n \tstruct string_list refs_to_delete = STRING_LIST_INIT_DUP;\n \tstruct string_list_item *ref_to_delete;\n+\tint i, removed = 0;\n+\n+\t/* Look for a packed ref: */\n+\tfor (i = 0; i < n; i++)\n+\t\tif (get_packed_ref(refnames[i]))\n+\t\t\tbreak;\n \n-\tif (!get_packed_ref(refname))\n-\t\treturn 0; /* refname does not exist in packed refs */\n+\t/* Avoid locking if we have nothing to do: */\n+\tif (i == n)\n+\t\treturn 0; /* no refname exists in packed refs */\n \n \tif (lock_packed_refs(0)) {\n \t\tunable_to_lock_error(git_path(\"packed-refs\"), errno);\n-\t\treturn error(\"cannot delete '%s' from packed refs\", refname);\n+\t\treturn error(\"cannot delete '%s' from packed refs\", refnames[i]);\n \t}\n \tpacked = get_packed_refs(&ref_cache);\n \n-\t/* Remove refname from the cache: */\n-\tif (remove_entry(packed, refname) == -1) {\n+\t/* Remove refnames from the cache: */\n+\tfor (i = 0; i < n; i++)\n+\t\tif (remove_entry(packed, refnames[i]) != -1)\n+\t\t\tremoved = 1;\n+\tif (!removed) {\n \t\t/*\n-\t\t * The packed entry disappeared while we were\n+\t\t * All packed entries disappeared while we were\n \t\t * acquiring the lock.\n \t\t */\n \t\trollback_packed_refs();\n@@ -2450,6 +2460,11 @@ static int repack_without_ref(const char *refname)\n \treturn commit_packed_refs();\n }\n \n+static int repack_without_ref(const char *refname)\n+{\n+\treturn repack_without_refs(&refname, 1);\n+}\n+\n static int delete_ref_loose(struct ref_lock *lock, int flag)\n {\n \tif (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) {\n-- \n1.7.10.4\n"},{"id":"226548","messageId":"ba16eca38ff0577710089de2f626abca26fdc2c1.1378142796.git.brad.king@kitware.com","threadId":"34797","inReplyTo":"cover.1378142795.git.brad.king@kitware.com","subject":"[PATCH v3 6/8] refs: add update_refs for multiple simultaneous updates","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-09-02T17:48:54Z","receivedAt":"2013-09-02T17:48:54Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"Add 'struct ref_update' to encode the information needed to update or\ndelete a ref (name, new sha1, optional old sha1, no-deref flag).  Add\nfunction 'update_refs' accepting an array of updates to perform.  First\nsort the input array to order locks consistently everywhere and reject\nmultiple updates to the same ref.  Then acquire locks on all refs with\nverified old values.  Then update or delete all refs accordingly.  Fail\nif any one lock cannot be obtained or any one old value does not match.\n\nThough the refs themselves cannot be modified together in a single\natomic transaction, this function does enable some useful semantics.\nFor example, a caller may create a new branch starting from the head of\nanother branch and rewind the original branch at the same time.  This\ntransfers ownership of commits between branches without risk of losing\ncommits added to the original branch by a concurrent process, or risk of\na concurrent process creating the new branch first.\n\nSigned-off-by: Brad King <brad.king@kitware.com>\n---\n refs.c |  100 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n refs.h |   20 +++++++++++++\n 2 files changed, 120 insertions(+)\n\ndiff --git a/refs.c b/refs.c\nindex 599504b..53e8774 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -3237,6 +3237,106 @@ int update_ref(const char *action, const char *refname,\n \treturn update_ref_write(action, refname, sha1, lock, onerr);\n }\n \n+static int ref_update_compare(const void *r1, const void *r2)\n+{\n+\tconst struct ref_update * const *u1 = r1;\n+\tconst struct ref_update * const *u2 = r2;\n+\treturn strcmp((*u1)->ref_name, (*u2)->ref_name);\n+}\n+\n+static int ref_update_reject_duplicates(struct ref_update **updates, int n,\n+\t\t\t\t\tenum action_on_err onerr)\n+{\n+\tint i;\n+\tfor (i = 1; i < n; i++)\n+\t\tif (!strcmp(updates[i - 1]->ref_name, updates[i]->ref_name)) {\n+\t\t\tconst char *str =\n+\t\t\t\t\"Multiple updates for ref '%s' not allowed.\";\n+\t\t\tswitch (onerr) {\n+\t\t\tcase MSG_ON_ERR:\n+\t\t\t\terror(str, updates[i]->ref_name); break;\n+\t\t\tcase DIE_ON_ERR:\n+\t\t\t\tdie(str, updates[i]->ref_name); break;\n+\t\t\tcase QUIET_ON_ERR:\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t\treturn 1;\n+\t\t}\n+\treturn 0;\n+}\n+\n+int update_refs(const char *action, const struct ref_update **updates_orig,\n+\t\tint n, enum action_on_err onerr)\n+{\n+\tint ret = 0, delnum = 0, i;\n+\tstruct ref_update **updates;\n+\tint *types;\n+\tstruct ref_lock **locks;\n+\tconst char **delnames;\n+\n+\tif (!updates_orig || !n)\n+\t\treturn 0;\n+\n+\t/* Allocate work space: */\n+\tupdates = xmalloc(sizeof(*updates) * n);\n+\ttypes = xmalloc(sizeof(*types) * n);\n+\tlocks = xcalloc(n, sizeof(*locks));\n+\tdelnames = xmalloc(sizeof(*delnames) * n);\n+\n+\t/* Copy, sort, and reject duplicate refs: */\n+\tmemcpy(updates, updates_orig, sizeof(*updates) * n);\n+\tqsort(updates, n, sizeof(*updates), ref_update_compare);\n+\tret = ref_update_reject_duplicates(updates, n, onerr);\n+\tif (ret)\n+\t\tgoto cleanup;\n+\n+\t/* Acquire all locks while verifying old values: */\n+\tfor (i = 0; i < n; i++) {\n+\t\tlocks[i] = update_ref_lock(updates[i]->ref_name,\n+\t\t\t\t\t   (updates[i]->have_old ?\n+\t\t\t\t\t    updates[i]->old_sha1 : NULL),\n+\t\t\t\t\t   updates[i]->flags,\n+\t\t\t\t\t   &types[i], onerr);\n+\t\tif (!locks[i]) {\n+\t\t\tret = 1;\n+\t\t\tgoto cleanup;\n+\t\t}\n+\t}\n+\n+\t/* Perform updates first so live commits remain referenced: */\n+\tfor (i = 0; i < n; i++)\n+\t\tif (!is_null_sha1(updates[i]->new_sha1)) {\n+\t\t\tret = update_ref_write(action,\n+\t\t\t\t\t       updates[i]->ref_name,\n+\t\t\t\t\t       updates[i]->new_sha1,\n+\t\t\t\t\t       locks[i], onerr);\n+\t\t\tlocks[i] = NULL; /* freed by update_ref_write */\n+\t\t\tif (ret)\n+\t\t\t\tgoto cleanup;\n+\t\t}\n+\n+\t/* Perform deletes now that updates are safely completed: */\n+\tfor (i = 0; i < n; i++)\n+\t\tif (locks[i]) {\n+\t\t\tdelnames[delnum++] = locks[i]->ref_name;\n+\t\t\tret |= delete_ref_loose(locks[i], types[i]);\n+\t\t}\n+\tret |= repack_without_refs(delnames, delnum);\n+\tfor (i = 0; i < delnum; i++)\n+\t\tunlink_or_warn(git_path(\"logs/%s\", delnames[i]));\n+\tclear_loose_ref_cache(&ref_cache);\n+\n+cleanup:\n+\tfor (i = 0; i < n; i++)\n+\t\tif (locks[i])\n+\t\t\tunlock_ref(locks[i]);\n+\tfree(updates);\n+\tfree(types);\n+\tfree(locks);\n+\tfree(delnames);\n+\treturn ret;\n+}\n+\n struct ref *find_ref_by_name(const struct ref *list, const char *name)\n {\n \tfor ( ; list; list = list->next)\ndiff --git a/refs.h b/refs.h\nindex 2cd307a..b113377 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -10,6 +10,20 @@ struct ref_lock {\n \tint force_write;\n };\n \n+/**\n+ * Information needed for a single ref update.  Set new_sha1 to the\n+ * new value or to zero to delete the ref.  To check the old value\n+ * while locking the ref, set have_old to 1 and set old_sha1 to the\n+ * value or to zero to ensure the ref does not exist before update.\n+ */\n+struct ref_update {\n+\tconst char *ref_name;\n+\tunsigned char new_sha1[20];\n+\tunsigned char old_sha1[20];\n+\tint flags; /* REF_NODEREF? */\n+\tint have_old; /* 1 if old_sha1 is valid, 0 otherwise */\n+};\n+\n /*\n  * Bit values set in the flags argument passed to each_ref_fn():\n  */\n@@ -214,6 +228,12 @@ int update_ref(const char *action, const char *refname,\n \t\tconst unsigned char *sha1, const unsigned char *oldval,\n \t\tint flags, enum action_on_err onerr);\n \n+/**\n+ * Lock all refs and then perform all modifications.\n+ */\n+int update_refs(const char *action, const struct ref_update **updates,\n+\t\tint n, enum action_on_err onerr);\n+\n extern int parse_hide_refs_config(const char *var, const char *value, const char *);\n extern int ref_is_hidden(const char *);\n \n-- \n1.7.10.4\n"},{"id":"226547","messageId":"93efe58835dc2e569fe1e398a4cba5ec4d4b3771.1378142796.git.brad.king@kitware.com","threadId":"34797","inReplyTo":"cover.1378142795.git.brad.king@kitware.com","subject":"[PATCH v3 7/8] update-ref: support multiple simultaneous updates","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-09-02T17:48:55Z","receivedAt":"2013-09-02T17:48:55Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"Add a --stdin signature to read update instructions from standard input\nand apply multiple ref updates together.  Use an input format that\nsupports any update that could be specified via the command-line,\nincluding object names like \"branch:path with space\".\n\nSigned-off-by: Brad King <brad.king@kitware.com>\n---\n Documentation/git-update-ref.txt |   20 +++++++-\n builtin/update-ref.c             |  103 +++++++++++++++++++++++++++++++++++++-\n 2 files changed, 121 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt\nindex 0df13ff..01019f1 100644\n--- a/Documentation/git-update-ref.txt\n+++ b/Documentation/git-update-ref.txt\n@@ -8,7 +8,7 @@ git-update-ref - Update the object name stored in a ref safely\n SYNOPSIS\n --------\n [verse]\n-'git update-ref' [-m <reason>] (-d <ref> [<oldvalue>] | [--no-deref] <ref> <newvalue> [<oldvalue>])\n+'git update-ref' [-m <reason>] (-d <ref> [<oldvalue>] | [--no-deref] <ref> <newvalue> [<oldvalue>] | --stdin)\n \n DESCRIPTION\n -----------\n@@ -58,6 +58,24 @@ archive by creating a symlink tree).\n With `-d` flag, it deletes the named <ref> after verifying it\n still contains <oldvalue>.\n \n+With `--stdin`, update-ref reads instructions from standard input and\n+performs all modifications together.  Empty lines are ignored.\n+Each non-empty line is parsed as whitespace-separated arguments.\n+Quote arguments containing whitespace as if in C source code.\n+Specify updates with lines of the form:\n+\n+\t[--no-deref] [--] <ref> <newvalue> [<oldvalue>]\n+\n+Lines of any other format or a repeated <ref> produce an error.\n+Specify a zero <newvalue> to delete a ref and/or a zero <oldvalue>\n+to make sure that a ref not exist.  Use either 40 \"0\" or the\n+empty string (written as \"\") to specify a zero value.\n+\n+If all <ref>s can be locked with matching <oldvalue>s\n+simultaneously, all modifications are performed.  Otherwise, no\n+modifications are performed.  Note that while each individual\n+<ref> is updated or deleted atomically, a concurrent reader may\n+still see a subset of the modifications.\n \n Logging Updates\n ---------------\ndiff --git a/builtin/update-ref.c b/builtin/update-ref.c\nindex 51d2684..12a3c76 100644\n--- a/builtin/update-ref.c\n+++ b/builtin/update-ref.c\n@@ -2,23 +2,115 @@\n #include \"refs.h\"\n #include \"builtin.h\"\n #include \"parse-options.h\"\n+#include \"quote.h\"\n \n static const char * const git_update_ref_usage[] = {\n \tN_(\"git update-ref [options] -d <refname> [<oldval>]\"),\n \tN_(\"git update-ref [options]    <refname> <newval> [<oldval>]\"),\n+\tN_(\"git update-ref [options] --stdin\"),\n \tNULL\n };\n \n+static int updates_alloc;\n+static int updates_count;\n+static const struct ref_update **updates;\n+\n+static const char* update_refs_stdin_next_arg(const char* next,\n+\t\t\t\t\t      struct strbuf *arg)\n+{\n+\t/* Skip leading whitespace: */\n+\twhile (isspace(*next))\n+\t\t++next;\n+\n+\t/* Return NULL when no argument is found: */\n+\tif (!*next)\n+\t\treturn NULL;\n+\n+\t/* Parse the argument: */\n+\tstrbuf_reset(arg);\n+\tif (*next == '\"') {\n+\t\tif (unquote_c_style(arg, next, &next))\n+\t\t\tdie(\"badly quoted argument: %s\", next);\n+\t\treturn next;\n+\t}\n+\twhile (*next && !isspace(*next))\n+\t\tstrbuf_addch(arg, *next++);\n+\treturn next;\n+}\n+\n+static void update_refs_stdin(const char *line)\n+{\n+\tint options = 1, flags = 0, argc = 0;\n+\tchar *argv[3] = {0, 0, 0};\n+\tstruct strbuf arg = STRBUF_INIT;\n+\tstruct ref_update *update;\n+\tconst char *next = line;\n+\n+\t/* Skip blank lines: */\n+\tif (!line[0])\n+\t\treturn;\n+\n+\t/* Parse arguments on this line: */\n+\twhile ((next = update_refs_stdin_next_arg(next, &arg)) != NULL) {\n+\t\tif (options && arg.buf[0] == '-')\n+\t\t\tif (!strcmp(arg.buf, \"--no-deref\"))\n+\t\t\t\tflags |= REF_NODEREF;\n+\t\t\telse if (!strcmp(arg.buf, \"--\"))\n+\t\t\t\toptions = 0;\n+\t\t\telse\n+\t\t\t\tdie(\"unknown option %s\", arg.buf);\n+\t\telse if (argc >= 3)\n+\t\t\tdie(\"too many arguments on line: %s\", line);\n+\t\telse {\n+\t\t\targv[argc++] = xstrdup(arg.buf);\n+\t\t\toptions = 0;\n+\t\t}\n+\t}\n+\tstrbuf_release(&arg);\n+\n+\t/* Allocate and zero-init a struct ref_update: */\n+\tupdate = xcalloc(1, sizeof(*update));\n+\tALLOC_GROW(updates, updates_count+1, updates_alloc);\n+\tupdates[updates_count++] = update;\n+\n+\t/* Set the update ref_name: */\n+\tif (!argv[0])\n+\t\tdie(\"no ref on line: %s\", line);\n+\tif (check_refname_format(argv[0], REFNAME_ALLOW_ONELEVEL))\n+\t\tdie(\"invalid ref format on line: %s\", line);\n+\tupdate->ref_name = argv[0];\n+\targv[0] = 0;\n+\n+\t/* Set the update new_sha1 and, if specified, old_sha1: */\n+\tif (!argv[1])\n+\t\tdie(\"missing new value on line: %s\", line);\n+\tif (*argv[1] && get_sha1(argv[1], update->new_sha1))\n+\t\tdie(\"invalid new value on line: %s\", line);\n+\tif (argv[2]) {\n+\t\tupdate->have_old = 1;\n+\t\tif (*argv[2] && get_sha1(argv[2], update->old_sha1))\n+\t\t\tdie(\"invalid old value on line: %s\", line);\n+\t}\n+\n+\t/* Set the update flags: */\n+\tupdate->flags = flags;\n+\n+\twhile (argc > 0)\n+\t\tfree(argv[--argc]);\n+}\n+\n int cmd_update_ref(int argc, const char **argv, const char *prefix)\n {\n \tconst char *refname, *oldval, *msg = NULL;\n \tunsigned char sha1[20], oldsha1[20];\n-\tint delete = 0, no_deref = 0, flags = 0;\n+\tint delete = 0, no_deref = 0, read_stdin = 0, flags = 0;\n+\tstruct strbuf line = STRBUF_INIT;\n \tstruct option options[] = {\n \t\tOPT_STRING( 'm', NULL, &msg, N_(\"reason\"), N_(\"reason of the update\")),\n \t\tOPT_BOOLEAN('d', NULL, &delete, N_(\"delete the reference\")),\n \t\tOPT_BOOLEAN( 0 , \"no-deref\", &no_deref,\n \t\t\t\t\tN_(\"update <refname> not the one it points to\")),\n+\t\tOPT_BOOLEAN( 0 , \"stdin\", &read_stdin, N_(\"read updates from stdin\")),\n \t\tOPT_END(),\n \t};\n \n@@ -28,6 +120,15 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)\n \tif (msg && !*msg)\n \t\tdie(\"Refusing to perform update with empty message.\");\n \n+\tif (read_stdin) {\n+\t\tif (delete || no_deref || argc > 0)\n+\t\t\tusage_with_options(git_update_ref_usage, options);\n+\t\twhile (strbuf_getline(&line, stdin, '\\n') != EOF)\n+\t\t\tupdate_refs_stdin(line.buf);\n+\t\tstrbuf_release(&line);\n+\t\treturn update_refs(msg, updates, updates_count, DIE_ON_ERR);\n+\t}\n+\n \tif (delete) {\n \t\tif (argc < 1 || argc > 2)\n \t\t\tusage_with_options(git_update_ref_usage, options);\n-- \n1.7.10.4\n"},{"id":"226541","messageId":"9457f5c046a0fb420f4fd730c7b02c2a75ee7e0d.1378142796.git.brad.king@kitware.com","threadId":"34797","inReplyTo":"cover.1378142795.git.brad.king@kitware.com","subject":"[PATCH v3 8/8] update-ref: add test cases covering --stdin signature","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-09-02T17:48:56Z","receivedAt":"2013-09-02T17:48:56Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"Extend t/t1400-update-ref.sh to cover cases using the --stdin option.\n\nSigned-off-by: Brad King <brad.king@kitware.com>\n---\n t/t1400-update-ref.sh |  256 +++++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 256 insertions(+)\n\ndiff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh\nindex e415ee0..b6d7dfa 100755\n--- a/t/t1400-update-ref.sh\n+++ b/t/t1400-update-ref.sh\n@@ -302,4 +302,260 @@ test_expect_success \\\n \t'git cat-file blob master@{2005-05-26 23:42}:F (expect OTHER)' \\\n \t'test OTHER = $(git cat-file blob \"master@{2005-05-26 23:42}:F\")'\n \n+a=refs/heads/a\n+b=refs/heads/b\n+c=refs/heads/c\n+z=0000000000000000000000000000000000000000\n+e='\"\"'\n+pws='path with space'\n+\n+test_expect_success 'stdin test setup' '\n+\techo \"$pws\" >\"$pws\" &&\n+\tgit add -- \"$pws\" &&\n+\tgit commit -m \"$pws\"\n+'\n+\n+test_expect_success 'stdin works with no input' '\n+\t>stdin &&\n+\tgit update-ref --stdin <stdin &&\n+\tgit rev-parse --verify -q $m\n+'\n+\n+test_expect_success 'stdin fails on bad input line with only whitespace' '\n+\techo \" \" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: no ref on line:  \" err\n+'\n+\n+test_expect_success 'stdin fails on bad input line with only --' '\n+\techo \"--\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: no ref on line: --\" err\n+'\n+\n+test_expect_success 'stdin fails on bad input line with only --bad-option' '\n+\techo \"--bad-option\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: unknown option --bad-option\" err\n+'\n+\n+test_expect_success 'stdin fails on bad ref name' '\n+\techo \"~a $m\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: invalid ref format on line: ~a $m\" err\n+'\n+\n+test_expect_success 'stdin fails on badly quoted input' '\n+\techo \"$a \\\"master\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: badly quoted argument: \\\\\\\"master\" err\n+'\n+\n+test_expect_success 'stdin fails on bad input line with too many arguments' '\n+\techo \"$a $m $m $m\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: too many arguments on line: $a $m $m $m\" err\n+'\n+\n+test_expect_success 'stdin fails on bad input line with too few arguments' '\n+\techo \"$a\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: missing new value on line: $a\" err\n+'\n+\n+test_expect_success 'stdin fails with duplicate refs' '\n+\tcat >stdin <<-EOF &&\n+$a $m\n+$b $m\n+$a $m\n+EOF\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: Multiple updates for ref '\"'\"'$a'\"'\"' not allowed.\" err\n+'\n+\n+test_expect_success 'stdin create ref works with no old value' '\n+\techo \"$a $m\" >stdin &&\n+\tgit update-ref --stdin <stdin &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin create ref works with zero old value' '\n+\techo \"$b $m $z\" >stdin &&\n+\tgit update-ref --stdin <stdin &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual &&\n+\tgit update-ref -d $b &&\n+\techo \"$b $m $e\" >stdin &&\n+\tgit update-ref --stdin <stdin &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin create ref works with path with space to blob' '\n+\techo \"refs/blobs/pws \\\"$m:$pws\\\"\" >stdin &&\n+\tgit update-ref --stdin <stdin &&\n+\tgit rev-parse \"$m:$pws\" >expect &&\n+\tgit rev-parse refs/blobs/pws >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin create ref fails with wrong old value' '\n+\techo \"$c $m $m~1\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: Cannot lock the ref '\"'\"'$c'\"'\"'\" err &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin create ref fails with bad old value' '\n+\techo \"$c $m does-not-exist\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: invalid old value on line: $c $m does-not-exist\" err &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin create ref fails with bad new value' '\n+\techo \"$c does-not-exist\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: invalid new value on line: $c does-not-exist\" err &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin update ref works with right old value' '\n+\techo \"$b $m~1 $m\" >stdin &&\n+\tgit update-ref --stdin <stdin &&\n+\tgit rev-parse $m~1 >expect &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin update ref fails with wrong old value' '\n+\techo \"$b $m~1 $m\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: Cannot lock the ref '\"'\"'$b'\"'\"'\" err &&\n+\tgit rev-parse $m~1 >expect &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin delete ref fails with wrong old value' '\n+\techo \"$a $e $m~1\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: Cannot lock the ref '\"'\"'$a'\"'\"'\" err &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin update symref works with --no-deref' '\n+\tgit symbolic-ref TESTSYMREF $b &&\n+\techo \"--no-deref TESTSYMREF $a $b\" >stdin &&\n+\tgit update-ref --stdin <stdin &&\n+\tgit rev-parse TESTSYMREF >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual &&\n+\tgit rev-parse $m~1 >expect &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin delete symref works with --no-deref' '\n+\tgit symbolic-ref TESTSYMREF $b &&\n+\techo \"--no-deref TESTSYMREF $e $b\" >stdin &&\n+\tgit update-ref --stdin <stdin &&\n+\ttest_must_fail git rev-parse --verify -q TESTSYMREF &&\n+\tgit rev-parse $m~1 >expect &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin delete ref works with right old value' '\n+\techo \"$b $e $m~1\" >stdin &&\n+\tgit update-ref --stdin <stdin &&\n+\ttest_must_fail git rev-parse --verify -q $b\n+'\n+\n+test_expect_success 'stdin create refs works with some old values' '\n+\tcat >stdin <<-EOF &&\n+$a $m\n+$b $m $z\n+$c $z $z\n+EOF\n+\tgit update-ref --stdin <stdin &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin update refs works with identity updates' '\n+\tcat >stdin <<-EOF &&\n+$a $m $m\n+$b $m $m\n+$c $z $e\n+EOF\n+\tgit update-ref --stdin <stdin &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin update refs works with extra whitespace' '\n+\tcat >stdin <<-EOF &&\n+\n+$a $m $m\n+\n+ \"$b\"  $m $m ''\n+\n+-- $c  $z  $e  ''\n+EOF\n+\tgit update-ref --stdin <stdin &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin update refs fails with wrong old value' '\n+\tgit update-ref $c $m &&\n+\tcat >stdin <<-EOF &&\n+$a $m $m\n+$b $m $m\n+$c $e $e\n+EOF\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: Cannot lock the ref '\"'\"'$c'\"'\"'\" err &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual &&\n+\tgit rev-parse $c >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin delete refs works with packed and loose refs' '\n+\tgit pack-refs --all &&\n+\tgit update-ref $c $m~1 &&\n+\tcat >stdin <<-EOF &&\n+$a $z $m\n+$b $z $m\n+$c $e $m~1\n+EOF\n+\tgit update-ref --stdin <stdin &&\n+\ttest_must_fail git rev-parse --verify -q $a &&\n+\ttest_must_fail git rev-parse --verify -q $b &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n test_done\n-- \n1.7.10.4\n"},{"id":"226549","messageId":"5224DAFA.9000807@kitware.com","threadId":"34797","inReplyTo":"93efe58835dc2e569fe1e398a4cba5ec4d4b3771.1378142796.git.brad.king@kitware.com","subject":"Re: [PATCH v3 7/8] update-ref: support multiple simultaneous updates","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-09-02T18:37:46Z","receivedAt":"2013-09-02T18:37:46Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"On 09/02/2013 01:48 PM, Brad King wrote:\n> +\t/* Parse the argument: */\n> +\tstrbuf_reset(arg);\n> +\tif (*next == '\"') {\n> +\t\tif (unquote_c_style(arg, next, &next))\n> +\t\t\tdie(\"badly quoted argument: %s\", next);\n> +\t\treturn next;\n> +\t}\n> +\twhile (*next && !isspace(*next))\n> +\t\tstrbuf_addch(arg, *next++);\n> +\treturn next;\n\nThis quoting proposal was written in response to $gmane/233479:\n\nOn 08/30/2013 06:51 PM, Junio C Hamano wrote:\n> When we need to deal with arbitrary strings (like pathnames), other\n> parts of the system usually give the user two interfaces, --stdin\n> with and without -z, and the strings are C-quoted when run without\n> the -z option, and terminated with NUL when run with the -z option.\n\n1. Do we want to allow arbitrary non-space characters in unquoted\narguments (while loop above) or reserve some syntax for future use?\n\n2. Thinking about how the -z variation might work, I ran:\n\n$ git grep '\\[0\\] == '\"'\"'\"' -- '*.c'\nbuiltin/check-attr.c:           if (line_termination && buf.buf[0] == '\"') {\nbuiltin/check-ignore.c:         if (line_termination && buf.buf[0] == '\"') {\nbuiltin/checkout-index.c:                       if (line_termination && buf.buf[0] == '\"') {\nbuiltin/hash-object.c:          if (buf.buf[0] == '\"') {\nbuiltin/mktree.c:       if (line_termination && path[0] == '\"') {\nbuiltin/update-index.c:         if (line_termination && path_name[0] == '\"') {\nbuiltin/update-index.c:                 if (line_termination && buf.buf[0] == '\"') {\n\nAll of these support quoting only in the non-z mode (the hash-object.c\nline follows a getline using hard-coded '\\n').  However, they are\nall in cases looking for one value on a line or at the end of a line\nso their -z option allows NUL-terminated lines containing LF.\n\nWhat distinguishes the \"update-ref --stdin\" case is that we want to\nrepresent multiple arguments on one line, each allowing arbitrary\ncharacters or an empty string.  From a brief search a couple places\nI found that do something related are:\n\n* apply: Read multiple paths from a diff header, using unquote_c_style\n  for quoted paths and separated by spaces.  There is no -z input mode.\n\n* config: Output keyword=value\\n becomes keyword\\nvalue\\0 in -z mode.\n  This works because the first piece (keyword) cannot have a LF\n  and there is at most one value so all LFs belong to it.\n\n* quote.c: sq_dequote_to_argv handles single quotes like a shell\n  would but allows only one space between arguments.  No -z mode.\n  This is similar to my v2 proposal.\n\nIf we use unquote_c_style and spaces to divide LF-terminated lines,\nhow shall we divide arguments on NUL-terminated lines?\n\nThanks,\n-Brad\n"},{"id":"226575","messageId":"522568D9.3080808@alum.mit.edu","threadId":"34797","inReplyTo":"5224C8C6.4050500@kitware.com","subject":"Re: [PATCH v2 6/8] refs: add update_refs for multiple simultaneous updates","fromName":"Michael Haggerty","fromEmail":"mhagger@alum.mit.edu","sentAt":"2013-09-03T04:43:05Z","receivedAt":"2013-09-03T04:43:05Z","isPatch":true,"sender":{"key":"mhagger@alum.mit.edu","avatar":"https://avatars.githubusercontent.com/u/119718?v=4"},"body":"On 09/02/2013 07:20 PM, Brad King wrote:\n> On 09/01/2013 02:08 AM, Junio C Hamano wrote:\n>>> Though the refs themeselves cannot be modified together in a single\n>>\n>> \"themselves\".\n> \n> Fixed.\n> \n>> I notice that we are using an array of structures and letting qsort\n>> swap 50~64 bytes of data\n> \n> Michael suggested this too, so fixed.\n\nHmmm, I see that you changed the signature of update_refs() to take an\narray of pointers.  My suggestion was unclear, but I didn't mean that\nthe function signature had to be changed.  Rather, I meant that *within*\nthe function, you could have created an array of pointers to the\nstructures in the input array and thereafter accessed it via the pointers:\n\nint update_refs(const char *action, const struct ref_update *updates_orig,\n\t\tint n, enum action_on_err onerr)\n{\n\t[...]\n\tstruct ref_update **updates;\n\n\t[...]\n\tupdates = xcalloc(n, sizeof(*updates));\n\tfor (i = 0; i < n; i++)\n\t\tupdates[i] = &updates_orig[i];\n\t[...]\n}\n\nHowever, your approach is also fine.  It will typically involve more\nmalloc()s but smaller memcpy()s (i.e., via ALLOC_GROW()) at the caller,\nand since usually the number of ref_updates being done at one time will\nbe limited anyway, I don't see a reason to prefer one version over the\nother.\n\nThanks for making the change.\n\nMichael\n\n-- \nMichael Haggerty\nmhagger@alum.mit.edu\nhttp://softwareswirl.blogspot.com/\n"},{"id":"226604","messageId":"CAPig+cRZFAaa1WVUa9V3jC9Oiy+ucSvyRAc0jdgwQASmhZm6eA@mail.gmail.com","threadId":"34797","inReplyTo":"9457f5c046a0fb420f4fd730c7b02c2a75ee7e0d.1378142796.git.brad.king@kitware.com","subject":"Re: [PATCH v3 8/8] update-ref: add test cases covering --stdin signature","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2013-09-03T08:16:46Z","receivedAt":"2013-09-03T08:16:46Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Mon, Sep 2, 2013 at 1:48 PM, Brad King <brad.king@kitware.com> wrote:\n> Extend t/t1400-update-ref.sh to cover cases using the --stdin option.\n>\n> Signed-off-by: Brad King <brad.king@kitware.com>\n> ---\n>  t/t1400-update-ref.sh |  256 +++++++++++++++++++++++++++++++++++++++++++++++++\n>  1 file changed, 256 insertions(+)\n>\n> diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh\n> index e415ee0..b6d7dfa 100755\n> --- a/t/t1400-update-ref.sh\n> +++ b/t/t1400-update-ref.sh\n> @@ -302,4 +302,260 @@ test_expect_success \\\n>         'git cat-file blob master@{2005-05-26 23:42}:F (expect OTHER)' \\\n>         'test OTHER = $(git cat-file blob \"master@{2005-05-26 23:42}:F\")'\n>\n> +a=refs/heads/a\n> +b=refs/heads/b\n> +c=refs/heads/c\n> +z=0000000000000000000000000000000000000000\n> +e='\"\"'\n> +pws='path with space'\n> +\n> +test_expect_success 'stdin fails on bad input line with only whitespace' '\n> +       echo \" \" >stdin &&\n> +       test_must_fail git update-ref --stdin <stdin 2>err &&\n> +       grep \"fatal: no ref on line:  \" err\n> +'\n> +\n> +test_expect_success 'stdin fails on bad input line with only --' '\n> +       echo \"--\" >stdin &&\n> +       test_must_fail git update-ref --stdin <stdin 2>err &&\n> +       grep \"fatal: no ref on line: --\" err\n> +'\n> +\n> +test_expect_success 'stdin fails on bad input line with only --bad-option' '\n> +       echo \"--bad-option\" >stdin &&\n> +       test_must_fail git update-ref --stdin <stdin 2>err &&\n> +       grep \"fatal: unknown option --bad-option\" err\n> +'\n\nWhen you decomposed the monolithic test from v1 into individual tests,\nyou dropped a couple cases (\"fatal: unknown option'\" and \"fatal:\nunterminated single-quote\"). Was this intentional?\n\n> +test_expect_success 'stdin fails with duplicate refs' '\n> +       cat >stdin <<-EOF &&\n> +$a $m\n> +$b $m\n> +$a $m\n> +EOF\n> +       test_must_fail git update-ref --stdin <stdin 2>err &&\n> +       grep \"fatal: Multiple updates for ref '\"'\"'$a'\"'\"' not allowed.\" err\n> +'\n\nThe leading '-' on '-EOF' allows you to indent the content of the\nheredoc and the terminating EOF, which makes the test read nicely:\n\n    test_expect_success 'stdin fails with duplicate refs' '\n        cat >stdin <<-EOF &&\n        $a $m\n        $b $m\n        $a $m\n        EOF\n        test_must_fail git update-ref ...\n    '\n\n> +\n> +test_expect_success 'stdin create ref works with no old value' '\n> +       echo \"$a $m\" >stdin &&\n> +       git update-ref --stdin <stdin &&\n> +       git rev-parse $m >expect &&\n> +       git rev-parse $a >actual &&\n> +       test_cmp expect actual\n> +'\n"},{"id":"226615","messageId":"5225CF35.2040209@kitware.com","threadId":"34797","inReplyTo":"522568D9.3080808@alum.mit.edu","subject":"Re: [PATCH v2 6/8] refs: add update_refs for multiple simultaneous updates","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-09-03T11:59:49Z","receivedAt":"2013-09-03T11:59:49Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"On 09/03/2013 12:43 AM, Michael Haggerty wrote:\n> Hmmm, I see that you changed the signature of update_refs() to take an\n> array of pointers.  My suggestion was unclear, but I didn't mean that\n> the function signature had to be changed.\n[snip]\n> However, your approach is also fine.\n\nOkay.  Thanks for reviewing!\n\n-Brad\n"},{"id":"226616","messageId":"5225D2F9.8000908@kitware.com","threadId":"34797","inReplyTo":"CAPig+cRZFAaa1WVUa9V3jC9Oiy+ucSvyRAc0jdgwQASmhZm6eA@mail.gmail.com","subject":"Re: [PATCH v3 8/8] update-ref: add test cases covering --stdin signature","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-09-03T12:15:53Z","receivedAt":"2013-09-03T12:15:53Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"On 09/03/2013 04:16 AM, Eric Sunshine wrote:\n> When you decomposed the monolithic test from v1 into individual tests,\n> you dropped a couple cases (\"fatal: unknown option'\" and \"fatal:\n> unterminated single-quote\"). Was this intentional?\n\nYes.  The v3 patch 7 changed the set of error messages to be covered.\n\n> The leading '-' on '-EOF' allows you to indent the content of the\n> heredoc and the terminating EOF, which makes the test read nicely:\n\nVery nice.  Fixed for next iteration.\n\nThanks,\n-Brad\n"},{"id":"226732","messageId":"cover.1378307529.git.brad.king@kitware.com","threadId":"34797","inReplyTo":"cover.1378142795.git.brad.king@kitware.com","subject":"[PATCH v4 0/8] Multiple simultaneously locked ref updates","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-09-04T15:22:37Z","receivedAt":"2013-09-04T15:22:37Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"Hi Folks,\n\nHere is the fourth revision of a series to support locking multiple\nrefs at the same time to update all of them consistently.  The\nprevious revisions of the series can be found at $gmane/233260,\n$gmane/233458, and $gmane/233647.\n\nUpdates since the previous revision of the series:\n\n* Patches 1-4 are identical\n\n* Patches 5-7 no longer have \":\" at the end of comments, a style I\n  \"learned\" from the context of patch 5 but that I saw Junio squash\n  out of patch 5 v2 when he queued it as 53237ae4.\n\n* Patch 7 has a re-organized parser and now defines a -z format for\n  stdin that terminates arguments with NUL and lines with LF NUL:\n\n   ... <ref> NUL <newvalue> NUL [ <oldvalue> NUL ] LF NUL\n\n* Patch 8 now has test cases for -z mode and updated error cases for\n  the re-organized parser.\n\nNote to maintainer:\n\n* Patch 4 needs to be re-queued to replace c7c80f49 due to the\n  tweak suggested in $gmane/233521 and made in v3 and kept in v4.\n\nThanks,\n-Brad\n\nBrad King (8):\n  reset: rename update_refs to reset_refs\n  refs: report ref type from lock_any_ref_for_update\n  refs: factor update_ref steps into helpers\n  refs: factor delete_ref loose ref step into a helper\n  refs: add function to repack without multiple refs\n  refs: add update_refs for multiple simultaneous updates\n  update-ref: support multiple simultaneous updates\n  update-ref: add test cases covering --stdin signature\n\n Documentation/git-update-ref.txt |  22 +-\n branch.c                         |   2 +-\n builtin/commit.c                 |   2 +-\n builtin/fetch.c                  |   3 +-\n builtin/receive-pack.c           |   3 +-\n builtin/reflog.c                 |   2 +-\n builtin/replace.c                |   2 +-\n builtin/reset.c                  |   4 +-\n builtin/tag.c                    |   2 +-\n builtin/update-ref.c             | 144 ++++++++++++-\n fast-import.c                    |   2 +-\n refs.c                           | 195 ++++++++++++++---\n refs.h                           |  22 +-\n sequencer.c                      |   3 +-\n t/t1400-update-ref.sh            | 445 +++++++++++++++++++++++++++++++++++++++\n 15 files changed, 812 insertions(+), 41 deletions(-)\n\n-- \n1.8.4.rc3\n"},{"id":"226738","messageId":"ce57e08b2fe53b23ba3627bc8a9e0cc44a7683e2.1378307529.git.brad.king@kitware.com","threadId":"34797","inReplyTo":"cover.1378307529.git.brad.king@kitware.com","subject":"[PATCH v4 1/8] reset: rename update_refs to reset_refs","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-09-04T15:22:38Z","receivedAt":"2013-09-04T15:22:38Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"The function resets refs rather than doing arbitrary updates.\nRename it to allow a future general-purpose update_refs function\nto be added.\n\nSigned-off-by: Brad King <brad.king@kitware.com>\n---\n builtin/reset.c | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/reset.c b/builtin/reset.c\nindex afa6e02..789ee48 100644\n--- a/builtin/reset.c\n+++ b/builtin/reset.c\n@@ -219,7 +219,7 @@ static const char **parse_args(const char **argv, const char *prefix, const char\n \treturn argv[0] ? get_pathspec(prefix, argv) : NULL;\n }\n \n-static int update_refs(const char *rev, const unsigned char *sha1)\n+static int reset_refs(const char *rev, const unsigned char *sha1)\n {\n \tint update_ref_status;\n \tstruct strbuf msg = STRBUF_INIT;\n@@ -350,7 +350,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)\n \tif (!pathspec && !unborn) {\n \t\t/* Any resets without paths update HEAD to the head being\n \t\t * switched to, saving the previous head in ORIG_HEAD before. */\n-\t\tupdate_ref_status = update_refs(rev, sha1);\n+\t\tupdate_ref_status = reset_refs(rev, sha1);\n \n \t\tif (reset_type == HARD && !update_ref_status && !quiet)\n \t\t\tprint_new_head_line(lookup_commit_reference(sha1));\n-- \n1.8.4.rc3\n"},{"id":"226736","messageId":"b06a2a1878e1140949f4bce1760e6ac3a5633d4c.1378307529.git.brad.king@kitware.com","threadId":"34797","inReplyTo":"cover.1378307529.git.brad.king@kitware.com","subject":"[PATCH v4 2/8] refs: report ref type from lock_any_ref_for_update","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-09-04T15:22:39Z","receivedAt":"2013-09-04T15:22:39Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"Expose lock_ref_sha1_basic's type_p argument to callers of\nlock_any_ref_for_update.  Update all call sites to ignore it by passing\nNULL for now.\n\nSigned-off-by: Brad King <brad.king@kitware.com>\n---\n branch.c               | 2 +-\n builtin/commit.c       | 2 +-\n builtin/fetch.c        | 3 ++-\n builtin/receive-pack.c | 3 ++-\n builtin/reflog.c       | 2 +-\n builtin/replace.c      | 2 +-\n builtin/tag.c          | 2 +-\n fast-import.c          | 2 +-\n refs.c                 | 7 ++++---\n refs.h                 | 2 +-\n sequencer.c            | 3 ++-\n 11 files changed, 17 insertions(+), 13 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex c5c6984..f2d383f 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -291,7 +291,7 @@ void create_branch(const char *head,\n \thashcpy(sha1, commit->object.sha1);\n \n \tif (!dont_change_ref) {\n-\t\tlock = lock_any_ref_for_update(ref.buf, NULL, 0);\n+\t\tlock = lock_any_ref_for_update(ref.buf, NULL, 0, NULL);\n \t\tif (!lock)\n \t\t\tdie_errno(_(\"Failed to lock ref for update\"));\n \t}\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 10acc53..be08f41 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -1618,7 +1618,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \t\t\t\t\t   !current_head\n \t\t\t\t\t   ? NULL\n \t\t\t\t\t   : current_head->object.sha1,\n-\t\t\t\t\t   0);\n+\t\t\t\t\t   0, NULL);\n \n \tnl = strchr(sb.buf, '\\n');\n \tif (nl)\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex d784b2e..28e4025 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -246,7 +246,8 @@ static int s_update_ref(const char *action,\n \t\trla = default_rla.buf;\n \tsnprintf(msg, sizeof(msg), \"%s: %s\", rla, action);\n \tlock = lock_any_ref_for_update(ref->name,\n-\t\t\t\t       check_old ? ref->old_sha1 : NULL, 0);\n+\t\t\t\t       check_old ? ref->old_sha1 : NULL,\n+\t\t\t\t       0, NULL);\n \tif (!lock)\n \t\treturn errno == ENOTDIR ? STORE_REF_ERROR_DF_CONFLICT :\n \t\t\t\t\t  STORE_REF_ERROR_OTHER;\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex e3eb5fc..a323070 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -524,7 +524,8 @@ static const char *update(struct command *cmd)\n \t\treturn NULL; /* good */\n \t}\n \telse {\n-\t\tlock = lock_any_ref_for_update(namespaced_name, old_sha1, 0);\n+\t\tlock = lock_any_ref_for_update(namespaced_name, old_sha1,\n+\t\t\t\t\t       0, NULL);\n \t\tif (!lock) {\n \t\t\trp_error(\"failed to lock %s\", name);\n \t\t\treturn \"failed to lock\";\ndiff --git a/builtin/reflog.c b/builtin/reflog.c\nindex 54184b3..28d756a 100644\n--- a/builtin/reflog.c\n+++ b/builtin/reflog.c\n@@ -366,7 +366,7 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused,\n \t * we take the lock for the ref itself to prevent it from\n \t * getting updated.\n \t */\n-\tlock = lock_any_ref_for_update(ref, sha1, 0);\n+\tlock = lock_any_ref_for_update(ref, sha1, 0, NULL);\n \tif (!lock)\n \t\treturn error(\"cannot lock ref '%s'\", ref);\n \tlog_file = git_pathdup(\"logs/%s\", ref);\ndiff --git a/builtin/replace.c b/builtin/replace.c\nindex 59d3115..1ecae8d 100644\n--- a/builtin/replace.c\n+++ b/builtin/replace.c\n@@ -105,7 +105,7 @@ static int replace_object(const char *object_ref, const char *replace_ref,\n \telse if (!force)\n \t\tdie(\"replace ref '%s' already exists\", ref);\n \n-\tlock = lock_any_ref_for_update(ref, prev, 0);\n+\tlock = lock_any_ref_for_update(ref, prev, 0, NULL);\n \tif (!lock)\n \t\tdie(\"%s: cannot lock the ref\", ref);\n \tif (write_ref_sha1(lock, repl, NULL) < 0)\ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex af3af3f..2c867d2 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -577,7 +577,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \tif (annotate)\n \t\tcreate_tag(object, tag, &buf, &opt, prev, object);\n \n-\tlock = lock_any_ref_for_update(ref.buf, prev, 0);\n+\tlock = lock_any_ref_for_update(ref.buf, prev, 0, NULL);\n \tif (!lock)\n \t\tdie(_(\"%s: cannot lock the ref\"), ref.buf);\n \tif (write_ref_sha1(lock, object, NULL) < 0)\ndiff --git a/fast-import.c b/fast-import.c\nindex 23f625f..5c329f6 100644\n--- a/fast-import.c\n+++ b/fast-import.c\n@@ -1678,7 +1678,7 @@ static int update_branch(struct branch *b)\n \t\treturn 0;\n \tif (read_ref(b->name, old_sha1))\n \t\thashclr(old_sha1);\n-\tlock = lock_any_ref_for_update(b->name, old_sha1, 0);\n+\tlock = lock_any_ref_for_update(b->name, old_sha1, 0, NULL);\n \tif (!lock)\n \t\treturn error(\"Unable to lock %s\", b->name);\n \tif (!force_update && !is_null_sha1(old_sha1)) {\ndiff --git a/refs.c b/refs.c\nindex 7922261..c69fd68 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2121,11 +2121,12 @@ struct ref_lock *lock_ref_sha1(const char *refname, const unsigned char *old_sha\n }\n \n struct ref_lock *lock_any_ref_for_update(const char *refname,\n-\t\t\t\t\t const unsigned char *old_sha1, int flags)\n+\t\t\t\t\t const unsigned char *old_sha1,\n+\t\t\t\t\t int flags, int *type_p)\n {\n \tif (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL))\n \t\treturn NULL;\n-\treturn lock_ref_sha1_basic(refname, old_sha1, flags, NULL);\n+\treturn lock_ref_sha1_basic(refname, old_sha1, flags, type_p);\n }\n \n /*\n@@ -3174,7 +3175,7 @@ int update_ref(const char *action, const char *refname,\n \t\tint flags, enum action_on_err onerr)\n {\n \tstatic struct ref_lock *lock;\n-\tlock = lock_any_ref_for_update(refname, oldval, flags);\n+\tlock = lock_any_ref_for_update(refname, oldval, flags, NULL);\n \tif (!lock) {\n \t\tconst char *str = \"Cannot lock the ref '%s'.\";\n \t\tswitch (onerr) {\ndiff --git a/refs.h b/refs.h\nindex 9e5db3a..2cd307a 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -137,7 +137,7 @@ extern struct ref_lock *lock_ref_sha1(const char *refname, const unsigned char *\n #define REF_NODEREF\t0x01\n extern struct ref_lock *lock_any_ref_for_update(const char *refname,\n \t\t\t\t\t\tconst unsigned char *old_sha1,\n-\t\t\t\t\t\tint flags);\n+\t\t\t\t\t\tint flags, int *type_p);\n \n /** Close the file descriptor owned by a lock and return the status */\n extern int close_ref(struct ref_lock *lock);\ndiff --git a/sequencer.c b/sequencer.c\nindex 351548f..06e52b4 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -279,7 +279,8 @@ static int fast_forward_to(const unsigned char *to, const unsigned char *from,\n \tread_cache();\n \tif (checkout_fast_forward(from, to, 1))\n \t\texit(1); /* the callee should have complained already */\n-\tref_lock = lock_any_ref_for_update(\"HEAD\", unborn ? null_sha1 : from, 0);\n+\tref_lock = lock_any_ref_for_update(\"HEAD\", unborn ? null_sha1 : from,\n+\t\t\t\t\t   0, NULL);\n \tstrbuf_addf(&sb, \"%s: fast-forward\", action_name(opts));\n \tret = write_ref_sha1(ref_lock, to, sb.buf);\n \tstrbuf_release(&sb);\n-- \n1.8.4.rc3\n"},{"id":"226735","messageId":"36826922e62f2940a9592096ea55de009e234381.1378307529.git.brad.king@kitware.com","threadId":"34797","inReplyTo":"cover.1378307529.git.brad.king@kitware.com","subject":"[PATCH v4 3/8] refs: factor update_ref steps into helpers","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-09-04T15:22:40Z","receivedAt":"2013-09-04T15:22:40Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"Factor the lock and write steps and error handling into helper functions\nupdate_ref_lock and update_ref_write to allow later use elsewhere.\nExpose lock_any_ref_for_update's type_p to update_ref_lock callers.\n\nWhile at it, drop \"static\" from the local \"lock\" variable as it is not\nnecessary to keep across invocations.\n\nSigned-off-by: Brad King <brad.king@kitware.com>\n---\n refs.c | 30 ++++++++++++++++++++++++------\n 1 file changed, 24 insertions(+), 6 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex c69fd68..4347826 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -3170,12 +3170,13 @@ int for_each_reflog(each_ref_fn fn, void *cb_data)\n \treturn retval;\n }\n \n-int update_ref(const char *action, const char *refname,\n-\t\tconst unsigned char *sha1, const unsigned char *oldval,\n-\t\tint flags, enum action_on_err onerr)\n+static struct ref_lock *update_ref_lock(const char *refname,\n+\t\t\t\t\tconst unsigned char *oldval,\n+\t\t\t\t\tint flags, int *type_p,\n+\t\t\t\t\tenum action_on_err onerr)\n {\n-\tstatic struct ref_lock *lock;\n-\tlock = lock_any_ref_for_update(refname, oldval, flags, NULL);\n+\tstruct ref_lock *lock;\n+\tlock = lock_any_ref_for_update(refname, oldval, flags, type_p);\n \tif (!lock) {\n \t\tconst char *str = \"Cannot lock the ref '%s'.\";\n \t\tswitch (onerr) {\n@@ -3183,8 +3184,14 @@ int update_ref(const char *action, const char *refname,\n \t\tcase DIE_ON_ERR: die(str, refname); break;\n \t\tcase QUIET_ON_ERR: break;\n \t\t}\n-\t\treturn 1;\n \t}\n+\treturn lock;\n+}\n+\n+static int update_ref_write(const char *action, const char *refname,\n+\t\t\t    const unsigned char *sha1, struct ref_lock *lock,\n+\t\t\t    enum action_on_err onerr)\n+{\n \tif (write_ref_sha1(lock, sha1, action) < 0) {\n \t\tconst char *str = \"Cannot update the ref '%s'.\";\n \t\tswitch (onerr) {\n@@ -3197,6 +3204,17 @@ int update_ref(const char *action, const char *refname,\n \treturn 0;\n }\n \n+int update_ref(const char *action, const char *refname,\n+\t       const unsigned char *sha1, const unsigned char *oldval,\n+\t       int flags, enum action_on_err onerr)\n+{\n+\tstruct ref_lock *lock;\n+\tlock = update_ref_lock(refname, oldval, flags, 0, onerr);\n+\tif (!lock)\n+\t\treturn 1;\n+\treturn update_ref_write(action, refname, sha1, lock, onerr);\n+}\n+\n struct ref *find_ref_by_name(const struct ref *list, const char *name)\n {\n \tfor ( ; list; list = list->next)\n-- \n1.8.4.rc3\n"},{"id":"226731","messageId":"d699bcba91dc16500b7762759ce53b6ea4eb9939.1378307529.git.brad.king@kitware.com","threadId":"34797","inReplyTo":"cover.1378307529.git.brad.king@kitware.com","subject":"[PATCH v4 4/8] refs: factor delete_ref loose ref step into a helper","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-09-04T15:22:41Z","receivedAt":"2013-09-04T15:22:41Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"Factor loose ref deletion into helper function delete_ref_loose to allow\nlater use elsewhere.\n\nSigned-off-by: Brad King <brad.king@kitware.com>\n---\n refs.c | 27 +++++++++++++++++----------\n 1 file changed, 17 insertions(+), 10 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 4347826..ab9d22e 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2450,24 +2450,31 @@ static int repack_without_ref(const char *refname)\n \treturn commit_packed_refs();\n }\n \n+static int delete_ref_loose(struct ref_lock *lock, int flag)\n+{\n+\tif (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) {\n+\t\t/* loose */\n+\t\tint err, i = strlen(lock->lk->filename) - 5; /* .lock */\n+\n+\t\tlock->lk->filename[i] = 0;\n+\t\terr = unlink_or_warn(lock->lk->filename);\n+\t\tlock->lk->filename[i] = '.';\n+\t\tif (err && errno != ENOENT)\n+\t\t\treturn 1;\n+\t}\n+\treturn 0;\n+}\n+\n int delete_ref(const char *refname, const unsigned char *sha1, int delopt)\n {\n \tstruct ref_lock *lock;\n-\tint err, i = 0, ret = 0, flag = 0;\n+\tint ret = 0, flag = 0;\n \n \tlock = lock_ref_sha1_basic(refname, sha1, delopt, &flag);\n \tif (!lock)\n \t\treturn 1;\n-\tif (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) {\n-\t\t/* loose */\n-\t\ti = strlen(lock->lk->filename) - 5; /* .lock */\n-\t\tlock->lk->filename[i] = 0;\n-\t\terr = unlink_or_warn(lock->lk->filename);\n-\t\tif (err && errno != ENOENT)\n-\t\t\tret = 1;\n+\tret |= delete_ref_loose(lock, flag);\n \n-\t\tlock->lk->filename[i] = '.';\n-\t}\n \t/* removing the loose one could have resurrected an earlier\n \t * packed one.  Also, if it was not loose we need to repack\n \t * without it.\n-- \n1.8.4.rc3\n"},{"id":"226737","messageId":"4d330225743473df43ef6145632e731aa1ba95f3.1378307529.git.brad.king@kitware.com","threadId":"34797","inReplyTo":"cover.1378307529.git.brad.king@kitware.com","subject":"[PATCH v4 5/8] refs: add function to repack without multiple refs","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-09-04T15:22:42Z","receivedAt":"2013-09-04T15:22:42Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"Generalize repack_without_ref as repack_without_refs to support a list\nof refs and implement the former in terms of the latter.\n\nSigned-off-by: Brad King <brad.king@kitware.com>\n---\n refs.c | 33 ++++++++++++++++++++++++---------\n 1 file changed, 24 insertions(+), 9 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex ab9d22e..92d801c 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2414,42 +2414,57 @@ static int curate_packed_ref_fn(struct ref_entry *entry, void *cb_data)\n \treturn 0;\n }\n \n-static int repack_without_ref(const char *refname)\n+static int repack_without_refs(const char **refnames, int n)\n {\n \tstruct ref_dir *packed;\n \tstruct string_list refs_to_delete = STRING_LIST_INIT_DUP;\n \tstruct string_list_item *ref_to_delete;\n+\tint i, removed = 0;\n+\n+\t/* Look for a packed ref */\n+\tfor (i = 0; i < n; i++)\n+\t\tif (get_packed_ref(refnames[i]))\n+\t\t\tbreak;\n \n-\tif (!get_packed_ref(refname))\n-\t\treturn 0; /* refname does not exist in packed refs */\n+\t/* Avoid locking if we have nothing to do */\n+\tif (i == n)\n+\t\treturn 0; /* no refname exists in packed refs */\n \n \tif (lock_packed_refs(0)) {\n \t\tunable_to_lock_error(git_path(\"packed-refs\"), errno);\n-\t\treturn error(\"cannot delete '%s' from packed refs\", refname);\n+\t\treturn error(\"cannot delete '%s' from packed refs\", refnames[i]);\n \t}\n \tpacked = get_packed_refs(&ref_cache);\n \n-\t/* Remove refname from the cache: */\n-\tif (remove_entry(packed, refname) == -1) {\n+\t/* Remove refnames from the cache */\n+\tfor (i = 0; i < n; i++)\n+\t\tif (remove_entry(packed, refnames[i]) != -1)\n+\t\t\tremoved = 1;\n+\tif (!removed) {\n \t\t/*\n-\t\t * The packed entry disappeared while we were\n+\t\t * All packed entries disappeared while we were\n \t\t * acquiring the lock.\n \t\t */\n \t\trollback_packed_refs();\n \t\treturn 0;\n \t}\n \n-\t/* Remove any other accumulated cruft: */\n+\t/* Remove any other accumulated cruft */\n \tdo_for_each_entry_in_dir(packed, 0, curate_packed_ref_fn, &refs_to_delete);\n \tfor_each_string_list_item(ref_to_delete, &refs_to_delete) {\n \t\tif (remove_entry(packed, ref_to_delete->string) == -1)\n \t\t\tdie(\"internal error\");\n \t}\n \n-\t/* Write what remains: */\n+\t/* Write what remains */\n \treturn commit_packed_refs();\n }\n \n+static int repack_without_ref(const char *refname)\n+{\n+\treturn repack_without_refs(&refname, 1);\n+}\n+\n static int delete_ref_loose(struct ref_lock *lock, int flag)\n {\n \tif (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) {\n-- \n1.8.4.rc3\n"},{"id":"226733","messageId":"c613338b545a9759ffc69558e08445ea32059b7d.1378307529.git.brad.king@kitware.com","threadId":"34797","inReplyTo":"cover.1378307529.git.brad.king@kitware.com","subject":"[PATCH v4 6/8] refs: add update_refs for multiple simultaneous updates","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-09-04T15:22:43Z","receivedAt":"2013-09-04T15:22:43Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"Add 'struct ref_update' to encode the information needed to update or\ndelete a ref (name, new sha1, optional old sha1, no-deref flag).  Add\nfunction 'update_refs' accepting an array of updates to perform.  First\nsort the input array to order locks consistently everywhere and reject\nmultiple updates to the same ref.  Then acquire locks on all refs with\nverified old values.  Then update or delete all refs accordingly.  Fail\nif any one lock cannot be obtained or any one old value does not match.\n\nThough the refs themselves cannot be modified together in a single\natomic transaction, this function does enable some useful semantics.\nFor example, a caller may create a new branch starting from the head of\nanother branch and rewind the original branch at the same time.  This\ntransfers ownership of commits between branches without risk of losing\ncommits added to the original branch by a concurrent process, or risk of\na concurrent process creating the new branch first.\n\nSigned-off-by: Brad King <brad.king@kitware.com>\n---\n refs.c | 100 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n refs.h |  20 +++++++++++++\n 2 files changed, 120 insertions(+)\n\ndiff --git a/refs.c b/refs.c\nindex 92d801c..46177ad 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -3237,6 +3237,106 @@ int update_ref(const char *action, const char *refname,\n \treturn update_ref_write(action, refname, sha1, lock, onerr);\n }\n \n+static int ref_update_compare(const void *r1, const void *r2)\n+{\n+\tconst struct ref_update * const *u1 = r1;\n+\tconst struct ref_update * const *u2 = r2;\n+\treturn strcmp((*u1)->ref_name, (*u2)->ref_name);\n+}\n+\n+static int ref_update_reject_duplicates(struct ref_update **updates, int n,\n+\t\t\t\t\tenum action_on_err onerr)\n+{\n+\tint i;\n+\tfor (i = 1; i < n; i++)\n+\t\tif (!strcmp(updates[i - 1]->ref_name, updates[i]->ref_name)) {\n+\t\t\tconst char *str =\n+\t\t\t\t\"Multiple updates for ref '%s' not allowed.\";\n+\t\t\tswitch (onerr) {\n+\t\t\tcase MSG_ON_ERR:\n+\t\t\t\terror(str, updates[i]->ref_name); break;\n+\t\t\tcase DIE_ON_ERR:\n+\t\t\t\tdie(str, updates[i]->ref_name); break;\n+\t\t\tcase QUIET_ON_ERR:\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t\treturn 1;\n+\t\t}\n+\treturn 0;\n+}\n+\n+int update_refs(const char *action, const struct ref_update **updates_orig,\n+\t\tint n, enum action_on_err onerr)\n+{\n+\tint ret = 0, delnum = 0, i;\n+\tstruct ref_update **updates;\n+\tint *types;\n+\tstruct ref_lock **locks;\n+\tconst char **delnames;\n+\n+\tif (!updates_orig || !n)\n+\t\treturn 0;\n+\n+\t/* Allocate work space */\n+\tupdates = xmalloc(sizeof(*updates) * n);\n+\ttypes = xmalloc(sizeof(*types) * n);\n+\tlocks = xcalloc(n, sizeof(*locks));\n+\tdelnames = xmalloc(sizeof(*delnames) * n);\n+\n+\t/* Copy, sort, and reject duplicate refs */\n+\tmemcpy(updates, updates_orig, sizeof(*updates) * n);\n+\tqsort(updates, n, sizeof(*updates), ref_update_compare);\n+\tret = ref_update_reject_duplicates(updates, n, onerr);\n+\tif (ret)\n+\t\tgoto cleanup;\n+\n+\t/* Acquire all locks while verifying old values */\n+\tfor (i = 0; i < n; i++) {\n+\t\tlocks[i] = update_ref_lock(updates[i]->ref_name,\n+\t\t\t\t\t   (updates[i]->have_old ?\n+\t\t\t\t\t    updates[i]->old_sha1 : NULL),\n+\t\t\t\t\t   updates[i]->flags,\n+\t\t\t\t\t   &types[i], onerr);\n+\t\tif (!locks[i]) {\n+\t\t\tret = 1;\n+\t\t\tgoto cleanup;\n+\t\t}\n+\t}\n+\n+\t/* Perform updates first so live commits remain referenced */\n+\tfor (i = 0; i < n; i++)\n+\t\tif (!is_null_sha1(updates[i]->new_sha1)) {\n+\t\t\tret = update_ref_write(action,\n+\t\t\t\t\t       updates[i]->ref_name,\n+\t\t\t\t\t       updates[i]->new_sha1,\n+\t\t\t\t\t       locks[i], onerr);\n+\t\t\tlocks[i] = NULL; /* freed by update_ref_write */\n+\t\t\tif (ret)\n+\t\t\t\tgoto cleanup;\n+\t\t}\n+\n+\t/* Perform deletes now that updates are safely completed */\n+\tfor (i = 0; i < n; i++)\n+\t\tif (locks[i]) {\n+\t\t\tdelnames[delnum++] = locks[i]->ref_name;\n+\t\t\tret |= delete_ref_loose(locks[i], types[i]);\n+\t\t}\n+\tret |= repack_without_refs(delnames, delnum);\n+\tfor (i = 0; i < delnum; i++)\n+\t\tunlink_or_warn(git_path(\"logs/%s\", delnames[i]));\n+\tclear_loose_ref_cache(&ref_cache);\n+\n+cleanup:\n+\tfor (i = 0; i < n; i++)\n+\t\tif (locks[i])\n+\t\t\tunlock_ref(locks[i]);\n+\tfree(updates);\n+\tfree(types);\n+\tfree(locks);\n+\tfree(delnames);\n+\treturn ret;\n+}\n+\n struct ref *find_ref_by_name(const struct ref *list, const char *name)\n {\n \tfor ( ; list; list = list->next)\ndiff --git a/refs.h b/refs.h\nindex 2cd307a..b113377 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -10,6 +10,20 @@ struct ref_lock {\n \tint force_write;\n };\n \n+/**\n+ * Information needed for a single ref update.  Set new_sha1 to the\n+ * new value or to zero to delete the ref.  To check the old value\n+ * while locking the ref, set have_old to 1 and set old_sha1 to the\n+ * value or to zero to ensure the ref does not exist before update.\n+ */\n+struct ref_update {\n+\tconst char *ref_name;\n+\tunsigned char new_sha1[20];\n+\tunsigned char old_sha1[20];\n+\tint flags; /* REF_NODEREF? */\n+\tint have_old; /* 1 if old_sha1 is valid, 0 otherwise */\n+};\n+\n /*\n  * Bit values set in the flags argument passed to each_ref_fn():\n  */\n@@ -214,6 +228,12 @@ int update_ref(const char *action, const char *refname,\n \t\tconst unsigned char *sha1, const unsigned char *oldval,\n \t\tint flags, enum action_on_err onerr);\n \n+/**\n+ * Lock all refs and then perform all modifications.\n+ */\n+int update_refs(const char *action, const struct ref_update **updates,\n+\t\tint n, enum action_on_err onerr);\n+\n extern int parse_hide_refs_config(const char *var, const char *value, const char *);\n extern int ref_is_hidden(const char *);\n \n-- \n1.8.4.rc3\n"},{"id":"226734","messageId":"ad27a96b70198e837ac61995a1328960e10072bc.1378307529.git.brad.king@kitware.com","threadId":"34797","inReplyTo":"cover.1378307529.git.brad.king@kitware.com","subject":"[PATCH v4 7/8] update-ref: support multiple simultaneous updates","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-09-04T15:22:44Z","receivedAt":"2013-09-04T15:22:44Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"Add a --stdin signature to read update instructions from standard input\nand apply multiple ref updates together.  Use an input format that\nsupports any update that could be specified via the command-line,\nincluding object names like \"branch:path with space\".\n\nSigned-off-by: Brad King <brad.king@kitware.com>\n---\n Documentation/git-update-ref.txt |  22 +++++-\n builtin/update-ref.c             | 144 ++++++++++++++++++++++++++++++++++++++-\n 2 files changed, 164 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt\nindex 0df13ff..11dd9d3 100644\n--- a/Documentation/git-update-ref.txt\n+++ b/Documentation/git-update-ref.txt\n@@ -8,7 +8,7 @@ git-update-ref - Update the object name stored in a ref safely\n SYNOPSIS\n --------\n [verse]\n-'git update-ref' [-m <reason>] (-d <ref> [<oldvalue>] | [--no-deref] <ref> <newvalue> [<oldvalue>])\n+'git update-ref' [-m <reason>] (-d <ref> [<oldvalue>] | [--no-deref] <ref> <newvalue> [<oldvalue>] | --stdin)\n \n DESCRIPTION\n -----------\n@@ -58,6 +58,26 @@ archive by creating a symlink tree).\n With `-d` flag, it deletes the named <ref> after verifying it\n still contains <oldvalue>.\n \n+With `--stdin`, update-ref reads instructions from standard input and\n+performs all modifications together.  Empty lines are ignored.\n+Each non-empty line is parsed as whitespace-separated arguments.\n+Quote arguments containing whitespace as if in C source code.\n+Specify updates with lines of the form:\n+\n+\t[--no-deref] [--] <ref> <newvalue> [<oldvalue>]\n+\n+Lines of any other format or a repeated <ref> produce an error.\n+Specify a zero <newvalue> to delete a ref and/or a zero <oldvalue>\n+to make sure that a ref not exist.  Use either 40 \"0\" or the\n+empty string (written as \"\") to specify a zero value.  Use `-z`\n+to specify input with no whitespace, quoting, or escaping, and\n+terminate each argument by NUL and each line by LF NUL.\n+\n+If all <ref>s can be locked with matching <oldvalue>s\n+simultaneously, all modifications are performed.  Otherwise, no\n+modifications are performed.  Note that while each individual\n+<ref> is updated or deleted atomically, a concurrent reader may\n+still see a subset of the modifications.\n \n Logging Updates\n ---------------\ndiff --git a/builtin/update-ref.c b/builtin/update-ref.c\nindex 51d2684..9c348fb 100644\n--- a/builtin/update-ref.c\n+++ b/builtin/update-ref.c\n@@ -2,23 +2,152 @@\n #include \"refs.h\"\n #include \"builtin.h\"\n #include \"parse-options.h\"\n+#include \"quote.h\"\n+#include \"argv-array.h\"\n \n static const char * const git_update_ref_usage[] = {\n \tN_(\"git update-ref [options] -d <refname> [<oldval>]\"),\n \tN_(\"git update-ref [options]    <refname> <newval> [<oldval>]\"),\n+\tN_(\"git update-ref [options] --stdin [-z]\"),\n \tNULL\n };\n \n+static int updates_alloc;\n+static int updates_count;\n+static const struct ref_update **updates;\n+\n+static void update_refs_stdin(int argc, const char **argv)\n+{\n+\tstruct ref_update *update;\n+\n+\t/* Skip blank lines */\n+\tif (!argc)\n+\t\treturn;\n+\n+\t/* Allocate and zero-init a struct ref_update */\n+\tupdate = xcalloc(1, sizeof(*update));\n+\tALLOC_GROW(updates, updates_count+1, updates_alloc);\n+\tupdates[updates_count++] = update;\n+\n+\t/* Process options */\n+\twhile (argc > 0 && argv[0][0] == '-') {\n+\t\tconst char *arg = argv[0];\n+\t\t--argc;\n+\t\t++argv;\n+\t\tif (!strcmp(arg, \"--no-deref\"))\n+\t\t\tupdate->flags |= REF_NODEREF;\n+\t\telse if (!strcmp(arg, \"--\"))\n+\t\t\tbreak;\n+\t\telse\n+\t\t\tdie(\"unknown option %s\", arg);\n+\t}\n+\n+\t/* Set the update ref_name */\n+\tif (argc < 1)\n+\t\tdie(\"input line with no ref!\");\n+\tif (check_refname_format(argv[0], REFNAME_ALLOW_ONELEVEL))\n+\t\tdie(\"invalid ref format: %s\", argv[0]);\n+\tupdate->ref_name = xstrdup(argv[0]);\n+\n+\t/* Set the update new_sha1 and, if specified, old_sha1 */\n+\tif (argc < 2)\n+\t\tdie(\"missing new value for ref %s\", update->ref_name);\n+\tif (*argv[1] && get_sha1(argv[1], update->new_sha1))\n+\t\tdie(\"invalid new value for ref %s: %s\",\n+\t\t    update->ref_name, argv[1]);\n+\tif (argc >= 3) {\n+\t\tupdate->have_old = 1;\n+\t\tif (*argv[2] && get_sha1(argv[2], update->old_sha1))\n+\t\t\tdie(\"invalid old value for ref %s: %s\",\n+\t\t\t    update->ref_name, argv[2]);\n+\t}\n+\n+\tif (argc > 3)\n+\t\tdie(\"too many arguments for ref %s\", update->ref_name);\n+}\n+\n+static const char *update_refs_stdin_parse_arg(const char *next,\n+\t\t\t\t\t       struct strbuf *arg)\n+{\n+\t/* Skip leading whitespace */\n+\twhile (isspace(*next))\n+\t\t++next;\n+\n+\t/* Return NULL when no argument is found */\n+\tif (!*next)\n+\t\treturn NULL;\n+\n+\t/* Parse the argument */\n+\tstrbuf_reset(arg);\n+\tif (*next == '\"') {\n+\t\tif (unquote_c_style(arg, next, &next))\n+\t\t\tdie(\"badly quoted argument: %s\", next);\n+\t\treturn next;\n+\t}\n+\twhile (*next && !isspace(*next))\n+\t\tstrbuf_addch(arg, *next++);\n+\treturn next;\n+}\n+\n+static void update_refs_stdin_parse_line(const char *next)\n+{\n+\tstruct strbuf arg = STRBUF_INIT;\n+\tstatic struct argv_array args = ARGV_ARRAY_INIT;\n+\n+\t/* Parse arguments on this line */\n+\twhile ((next = update_refs_stdin_parse_arg(next, &arg)) != NULL)\n+\t\targv_array_push(&args, arg.buf);\n+\n+\t/* Process this command */\n+\tupdate_refs_stdin(args.argc, args.argv);\n+\n+\targv_array_clear(&args);\n+\tstrbuf_release(&arg);\n+}\n+\n+static void update_refs_stdin_read_n()\n+{\n+\tstruct strbuf line = STRBUF_INIT;\n+\n+\twhile (strbuf_getline(&line, stdin, '\\n') != EOF)\n+\t\tupdate_refs_stdin_parse_line(line.buf);\n+\n+\tstrbuf_release(&line);\n+}\n+\n+static void update_refs_stdin_read_z()\n+{\n+\tstruct strbuf arg = STRBUF_INIT;\n+\tstatic struct argv_array args = ARGV_ARRAY_INIT;\n+\n+\t/* Process NUL-terminated arguments with commands ending in LF */\n+\twhile (strbuf_getline(&arg, stdin, '\\0') != EOF) {\n+\t\tif (!strcmp(arg.buf, \"\\n\")) {\n+\t\t\tupdate_refs_stdin(args.argc, args.argv);\n+\t\t\targv_array_clear(&args);\n+\t\t} else {\n+\t\t\targv_array_push(&args, arg.buf);\n+\t\t}\n+\t}\n+\n+\tif (args.argc > 0)\n+\t\tdie(\"unterminated -z input sequence\");\n+\n+\tstrbuf_release(&arg);\n+}\n+\n int cmd_update_ref(int argc, const char **argv, const char *prefix)\n {\n \tconst char *refname, *oldval, *msg = NULL;\n \tunsigned char sha1[20], oldsha1[20];\n-\tint delete = 0, no_deref = 0, flags = 0;\n+\tint delete = 0, no_deref = 0, read_stdin = 0, end_null = 0, flags = 0;\n \tstruct option options[] = {\n \t\tOPT_STRING( 'm', NULL, &msg, N_(\"reason\"), N_(\"reason of the update\")),\n \t\tOPT_BOOLEAN('d', NULL, &delete, N_(\"delete the reference\")),\n+\t\tOPT_BOOLEAN('z', NULL, &end_null, N_(\"stdin has NUL-terminated arguments\")),\n \t\tOPT_BOOLEAN( 0 , \"no-deref\", &no_deref,\n \t\t\t\t\tN_(\"update <refname> not the one it points to\")),\n+\t\tOPT_BOOLEAN( 0 , \"stdin\", &read_stdin, N_(\"read updates from stdin\")),\n \t\tOPT_END(),\n \t};\n \n@@ -28,6 +157,19 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)\n \tif (msg && !*msg)\n \t\tdie(\"Refusing to perform update with empty message.\");\n \n+\tif (read_stdin) {\n+\t\tif (delete || no_deref || argc > 0)\n+\t\t\tusage_with_options(git_update_ref_usage, options);\n+\t\tif (end_null)\n+\t\t\tupdate_refs_stdin_read_z();\n+\t\telse\n+\t\t\tupdate_refs_stdin_read_n();\n+\t\treturn update_refs(msg, updates, updates_count, DIE_ON_ERR);\n+\t}\n+\n+\tif (end_null)\n+\t\tusage_with_options(git_update_ref_usage, options);\n+\n \tif (delete) {\n \t\tif (argc < 1 || argc > 2)\n \t\t\tusage_with_options(git_update_ref_usage, options);\n-- \n1.8.4.rc3\n"},{"id":"226730","messageId":"80fd3ec9c847a0dbf505da3a539d690d524972e2.1378307529.git.brad.king@kitware.com","threadId":"34797","inReplyTo":"cover.1378307529.git.brad.king@kitware.com","subject":"[PATCH v4 8/8] update-ref: add test cases covering --stdin signature","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-09-04T15:22:45Z","receivedAt":"2013-09-04T15:22:45Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"Extend t/t1400-update-ref.sh to cover cases using the --stdin option.\n\nSigned-off-by: Brad King <brad.king@kitware.com>\n---\n t/t1400-update-ref.sh | 445 ++++++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 445 insertions(+)\n\ndiff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh\nindex e415ee0..e8ba0d2 100755\n--- a/t/t1400-update-ref.sh\n+++ b/t/t1400-update-ref.sh\n@@ -302,4 +302,449 @@ test_expect_success \\\n \t'git cat-file blob master@{2005-05-26 23:42}:F (expect OTHER)' \\\n \t'test OTHER = $(git cat-file blob \"master@{2005-05-26 23:42}:F\")'\n \n+a=refs/heads/a\n+b=refs/heads/b\n+c=refs/heads/c\n+E='\"\"'\n+pws='path with space'\n+\n+print_nul() {\n+\twhile test $# -gt 0; do\n+\t\tprintf -- \"$1\" &&\n+\t\tprintf -- \"Q\" | q_to_nul &&\n+\t\tshift || return\n+\tdone\n+}\n+\n+test_expect_success '-z fails without --stdin' '\n+\ttest_must_fail git update-ref -z $m $m $m 2>err &&\n+\tgrep \"usage: git update-ref\" err\n+'\n+\n+test_expect_success 'stdin test setup' '\n+\techo \"$pws\" >\"$pws\" &&\n+\tgit add -- \"$pws\" &&\n+\tgit commit -m \"$pws\"\n+'\n+\n+test_expect_success 'stdin works with no input' '\n+\t>stdin &&\n+\tgit update-ref --stdin <stdin &&\n+\tgit rev-parse --verify -q $m\n+'\n+\n+test_expect_success 'stdin works with whitespace-only input' '\n+\techo \" \" >stdin &&\n+\tgit update-ref --stdin <stdin 2>err &&\n+\tgit rev-parse --verify -q $m\n+'\n+\n+test_expect_success 'stdin fails on bad input line with only --' '\n+\techo \"--\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: input line with no ref\" err\n+'\n+\n+test_expect_success 'stdin fails on bad input line with only --bad-option' '\n+\techo \"--bad-option\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: unknown option --bad-option\" err\n+'\n+\n+test_expect_success 'stdin fails on bad ref name' '\n+\techo \"~a $m\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: invalid ref format: ~a\" err\n+'\n+\n+test_expect_success 'stdin fails on badly quoted input' '\n+\techo \"$a \\\"master\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: badly quoted argument: \\\\\\\"master\" err\n+'\n+\n+test_expect_success 'stdin fails on bad input line with too many arguments' '\n+\techo \"$a $m $m $m\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: too many arguments for ref $a\" err\n+'\n+\n+test_expect_success 'stdin fails on bad input line with too few arguments' '\n+\techo \"$a\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: missing new value for ref $a\" err\n+'\n+\n+test_expect_success 'stdin fails with duplicate refs' '\n+\tcat >stdin <<-EOF &&\n+\t$a $m\n+\t$b $m\n+\t$a $m\n+\tEOF\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: Multiple updates for ref '\"'\"'$a'\"'\"' not allowed.\" err\n+'\n+\n+test_expect_success 'stdin create ref works with no old value' '\n+\techo \"$a $m\" >stdin &&\n+\tgit update-ref --stdin <stdin &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin create ref works with zero old value' '\n+\techo \"$b $m $Z\" >stdin &&\n+\tgit update-ref --stdin <stdin &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual &&\n+\tgit update-ref -d $b\n+'\n+\n+test_expect_success 'stdin create ref works with empty old value' '\n+\techo \"$b $m $E\" >stdin &&\n+\tgit update-ref --stdin <stdin &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin create ref works with path with space to blob' '\n+\techo \"refs/blobs/pws \\\"$m:$pws\\\"\" >stdin &&\n+\tgit update-ref --stdin <stdin &&\n+\tgit rev-parse \"$m:$pws\" >expect &&\n+\tgit rev-parse refs/blobs/pws >actual &&\n+\ttest_cmp expect actual &&\n+\tgit update-ref -d refs/blobs/pws\n+'\n+\n+test_expect_success 'stdin create ref fails with wrong old value' '\n+\techo \"$c $m $m~1\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: Cannot lock the ref '\"'\"'$c'\"'\"'\" err &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin create ref fails with bad old value' '\n+\techo \"$c $m does-not-exist\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: invalid old value for ref $c: does-not-exist\" err &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin create ref fails with bad new value' '\n+\techo \"$c does-not-exist\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: invalid new value for ref $c: does-not-exist\" err &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin update ref works with right old value' '\n+\techo \"$b $m~1 $m\" >stdin &&\n+\tgit update-ref --stdin <stdin &&\n+\tgit rev-parse $m~1 >expect &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin update ref fails with wrong old value' '\n+\techo \"$b $m~1 $m\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: Cannot lock the ref '\"'\"'$b'\"'\"'\" err &&\n+\tgit rev-parse $m~1 >expect &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin delete ref fails with wrong old value' '\n+\techo \"$a $E $m~1\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: Cannot lock the ref '\"'\"'$a'\"'\"'\" err &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin update symref works with --no-deref' '\n+\tgit symbolic-ref TESTSYMREF $b &&\n+\techo \"--no-deref TESTSYMREF $a $b\" >stdin &&\n+\tgit update-ref --stdin <stdin &&\n+\tgit rev-parse TESTSYMREF >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual &&\n+\tgit rev-parse $m~1 >expect &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin delete symref works with --no-deref' '\n+\tgit symbolic-ref TESTSYMREF $b &&\n+\techo \"--no-deref TESTSYMREF $E $b\" >stdin &&\n+\tgit update-ref --stdin <stdin &&\n+\ttest_must_fail git rev-parse --verify -q TESTSYMREF &&\n+\tgit rev-parse $m~1 >expect &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin delete ref works with right old value' '\n+\techo \"$b $E $m~1\" >stdin &&\n+\tgit update-ref --stdin <stdin &&\n+\ttest_must_fail git rev-parse --verify -q $b\n+'\n+\n+test_expect_success 'stdin create refs works with some old values' '\n+\tcat >stdin <<-EOF &&\n+\t$a $m\n+\t$b $m $Z\n+\t$c $Z $Z\n+\tEOF\n+\tgit update-ref --stdin <stdin &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin update refs works with identity updates' '\n+\tcat >stdin <<-EOF &&\n+\t$a $m $m\n+\t$b $m $m\n+\t$c $Z $E\n+\tEOF\n+\tgit update-ref --stdin <stdin &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin update refs works with extra whitespace' '\n+\tcat >stdin <<-EOF &&\n+\t''\n+\t$a $m $m\n+\t''\n+\t \"$b\"  $m $m ''\n+\t''\n+\t-- $c  $Z  $E  ''\n+\tEOF\n+\tgit update-ref --stdin <stdin &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin update refs fails with wrong old value' '\n+\tgit update-ref $c $m &&\n+\tcat >stdin <<-EOF &&\n+\t$a $m $m\n+\t$b $m $m\n+\t$c $E $E\n+\tEOF\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: Cannot lock the ref '\"'\"'$c'\"'\"'\" err &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual &&\n+\tgit rev-parse $c >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin delete refs works with packed and loose refs' '\n+\tgit pack-refs --all &&\n+\tgit update-ref $c $m~1 &&\n+\tcat >stdin <<-EOF &&\n+\t$a $Z $m\n+\t$b $Z $m\n+\t$c $E $m~1\n+\tEOF\n+\tgit update-ref --stdin <stdin &&\n+\ttest_must_fail git rev-parse --verify -q $a &&\n+\ttest_must_fail git rev-parse --verify -q $b &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin -z works on empty input' '\n+\t>stdin &&\n+\tgit update-ref -z --stdin <stdin &&\n+\tgit rev-parse --verify -q $m\n+'\n+\n+test_expect_success 'stdin -z works on empty input sequence' '\n+\tprint_nul \"\\n\" >stdin &&\n+\tgit update-ref -z --stdin <stdin &&\n+\tgit rev-parse --verify -q $m\n+'\n+\n+test_expect_success 'stdin -z fails on unterminated input sequence' '\n+\tprint_nul \"$a\" \"$m\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: unterminated -z input sequence\" err\n+'\n+\n+test_expect_success 'stdin -z create ref works with no old value' '\n+\tprint_nul \"$a\" \"$m\" \"\\n\" >stdin &&\n+\tgit update-ref -z --stdin <stdin &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin -z create ref works with zero old value' '\n+\tprint_nul \"$b\" \"$m\" \"$Z\" \"\\n\" >stdin &&\n+\tgit update-ref -z --stdin <stdin &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual &&\n+\tgit update-ref -d $b\n+'\n+\n+test_expect_success 'stdin -z create ref works with empty old value' '\n+\tprint_nul \"$b\" \"$m\" \"\" \"\\n\" >stdin &&\n+\tgit update-ref -z --stdin <stdin &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin -z create ref works with path with space to blob' '\n+\tprint_nul \"refs/blobs/pws\" \"$m:$pws\" \"\\n\" >stdin &&\n+\tgit update-ref -z --stdin <stdin &&\n+\tgit rev-parse \"$m:$pws\" >expect &&\n+\tgit rev-parse refs/blobs/pws >actual &&\n+\ttest_cmp expect actual &&\n+\tgit update-ref -d refs/blobs/pws\n+'\n+\n+test_expect_success 'stdin -z create ref fails with wrong old value' '\n+\tprint_nul \"$c\" \"$m\" \"$m~1\" \"\\n\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: Cannot lock the ref '\"'\"'$c'\"'\"'\" err &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin -z create ref fails with bad old value' '\n+\tprint_nul \"$c\" \"$m\" \"does-not-exist\" \"\\n\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: invalid old value for ref $c: does-not-exist\" err &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin -z create ref fails with bad new value' '\n+\tprint_nul \"$c\" \"does-not-exist\" \"\\n\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: invalid new value for ref $c: does-not-exist\" err &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin -z update ref works with right old value' '\n+\tprint_nul \"$b\" \"$m~1\" \"$m\" \"\\n\" >stdin &&\n+\tgit update-ref -z --stdin <stdin &&\n+\tgit rev-parse $m~1 >expect &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin -z update ref fails with wrong old value' '\n+\tprint_nul \"$b\" \"$m~1\" \"$m\" \"\\n\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: Cannot lock the ref '\"'\"'$b'\"'\"'\" err &&\n+\tgit rev-parse $m~1 >expect &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin -z delete ref fails with wrong old value' '\n+\tprint_nul \"$a\" \"\" \"$m~1\" \"\\n\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: Cannot lock the ref '\"'\"'$a'\"'\"'\" err &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin -z update symref works with --no-deref' '\n+\tgit symbolic-ref TESTSYMREF $b &&\n+\tprint_nul \"--no-deref\" \"TESTSYMREF\" \"$a\" \"$b\" \"\\n\" >stdin &&\n+\tgit update-ref -z --stdin <stdin &&\n+\tgit rev-parse TESTSYMREF >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual &&\n+\tgit rev-parse $m~1 >expect &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin -z delete symref works with --no-deref' '\n+\tgit symbolic-ref TESTSYMREF $b &&\n+\tprint_nul \"--no-deref\" \"TESTSYMREF\" \"\" \"$b\" \"\\n\" >stdin &&\n+\tgit update-ref -z --stdin <stdin &&\n+\ttest_must_fail git rev-parse --verify -q TESTSYMREF &&\n+\tgit rev-parse $m~1 >expect &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin -z delete ref works with right old value' '\n+\tprint_nul \"$b\" \"\" \"$m~1\" \"\\n\" >stdin &&\n+\tgit update-ref -z --stdin <stdin &&\n+\ttest_must_fail git rev-parse --verify -q $b\n+'\n+\n+test_expect_success 'stdin -z create refs works with some old values' '\n+\tprint_nul \"$a\" \"$m\" \"\\n\" \"$b\" \"$m\" \"$Z\" \"\\n\" \"$c\" \"$Z\" \"$Z\" \"\\n\" >stdin &&\n+\tgit update-ref -z --stdin <stdin &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin -z update refs works with identity updates' '\n+\tprint_nul \"$a\" \"$m\" \"$m\" \"\\n\" \"$b\" \"$m\" \"$m\" \"\\n\" \"$c\" \"$Z\" \"\" \"\\n\" >stdin &&\n+\tgit update-ref -z --stdin <stdin &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin -z update refs fails with wrong old value' '\n+\tgit update-ref $c $m &&\n+\tprint_nul \"$a\" \"$m\" \"$m\" \"\\n\" \"$b\" \"$m\" \"$m\" \"\\n\" \"$c\" \"\" \"\" \"\\n\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: Cannot lock the ref '\"'\"'$c'\"'\"'\" err &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual &&\n+\tgit rev-parse $c >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin -z delete refs works with packed and loose refs' '\n+\tgit pack-refs --all &&\n+\tgit update-ref $c $m~1 &&\n+\tprint_nul \"$a\" \"$Z\" \"$m\" \"\\n\" \"$b\" \"$Z\" \"$m\" \"\\n\" \"$c\" \"\" \"$m~1\" \"\\n\" >stdin &&\n+\tgit update-ref -z --stdin <stdin &&\n+\ttest_must_fail git rev-parse --verify -q $a &&\n+\ttest_must_fail git rev-parse --verify -q $b &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n test_done\n-- \n1.8.4.rc3\n"},{"id":"226746","messageId":"xmqqsixkmonq.fsf@gitster.dls.corp.google.com","threadId":"34797","inReplyTo":"ad27a96b70198e837ac61995a1328960e10072bc.1378307529.git.brad.king@kitware.com","subject":"Re: [PATCH v4 7/8] update-ref: support multiple simultaneous updates","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-09-04T18:23:37Z","receivedAt":"2013-09-04T18:23:37Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Brad King <brad.king@kitware.com> writes:\n\n> +With `--stdin`, update-ref reads instructions from standard input and\n> +performs all modifications together.  Empty lines are ignored.\n> +Each non-empty line is parsed as whitespace-separated arguments.\n\n\"whitespace-separated\" implies that we may allow fields separated\nwith not a single SP, but with double SPs or even HTs between them.\nI personally do not think we should be so loose; it is not like we\nare allowing the user to type these from the terminal, so limiting\n\"separated by a single SP\" may be perfectly adequate, and make it\nless error prone (e.g. there will not be a confusion \"Is 'A SP SP B'\nA followed by B, or A followed by an empty string followed by B?\").\n\n> +Quote arguments containing whitespace as if in C source code.\n\nProbably \"as if they were strings in C source code\".\n\n> +Specify updates with lines of the form:\n> +\n> +\t[--no-deref] [--] <ref> <newvalue> [<oldvalue>]\n> +\n> +Lines of any other format or a repeated <ref> produce an error.\n> +Specify a zero <newvalue> to delete a ref and/or a zero <oldvalue>\n> +to make sure that a ref not exist.  Use either 40 \"0\" or the\n> +empty string (written as \"\") to specify a zero value.  Use `-z`\n> +to specify input with no whitespace, quoting, or escaping, and\n> +terminate each argument by NUL and each line by LF NUL.\n\nThis is a somewhat interesting choice of the record terminator. Do\nwe have a precedent to use LF NUL elsewhere?  If this is the first\nsuch case where we need to express variable number of NUL-separated\nfields in a record, I think I am fine with LF NUL (but I am sure\nother people would give us a better convention if we ask them\npolitely ;-)), but I just want to make sure we do it the same way as\nother codepaths, if exist, that have to handle this kind of thing.\n"},{"id":"226758","messageId":"52278729.2060404@kitware.com","threadId":"34797","inReplyTo":"xmqqk3iwmm6l.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH v4 7/8] update-ref: support multiple simultaneous updates","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-09-04T19:16:57Z","receivedAt":"2013-09-04T19:16:57Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"On 09/04/2013 03:17 PM, Junio C Hamano wrote:\n> Brad King <brad.king@kitware.com> writes:\n>> +static void update_refs_stdin_read_n()\n>> +static void update_refs_stdin_read_z()\n> \n> These need to be defined with explicit (void) argument list.\n\nOops, fixed.\n\nThanks,\n-Brad\n"},{"id":"226757","messageId":"xmqqk3iwmm6l.fsf@gitster.dls.corp.google.com","threadId":"34797","inReplyTo":"ad27a96b70198e837ac61995a1328960e10072bc.1378307529.git.brad.king@kitware.com","subject":"Re: [PATCH v4 7/8] update-ref: support multiple simultaneous updates","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-09-04T19:17:06Z","receivedAt":"2013-09-04T19:17:06Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Brad King <brad.king@kitware.com> writes:\n\n> +static void update_refs_stdin_read_n()\n> +{\n> +\tstruct strbuf line = STRBUF_INIT;\n> +\n> +\twhile (strbuf_getline(&line, stdin, '\\n') != EOF)\n> +\t\tupdate_refs_stdin_parse_line(line.buf);\n> +\n> +\tstrbuf_release(&line);\n> +}\n> +\n> +static void update_refs_stdin_read_z()\n> +{\n\nThese need to be defined with explicit (void) argument list.\n\n\tstatic void foo(void)\n        {\n        \t...\n"},{"id":"226760","messageId":"52279107.2070205@kitware.com","threadId":"34797","inReplyTo":"xmqqsixkmonq.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH v4 7/8] update-ref: support multiple simultaneous updates","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-09-04T19:59:03Z","receivedAt":"2013-09-04T19:59:03Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"On 09/04/2013 02:23 PM, Junio C Hamano wrote:\n> \"whitespace-separated\" implies that we may allow fields separated with not a single SP, but with double SPs or even HTs between them. I personally do not think we should be so loose\n\nOkay, I will look at making it more strict.  See proposed format\nbelow.\n\n>> +Quote arguments containing whitespace as if in C source code.\n> \n> Probably \"as if they were strings in C source code\".\n\nFixed.\n\n>> +terminate each argument by NUL and each line by LF NUL.\n> \n> This is a somewhat interesting choice of the record terminator. Do we have a precedent to use LF NUL elsewhere?  If this is the first such case where we need to express variable number of NUL-separated fields in a record, I think I am fine with LF NUL (but I am sure other people would give us a\n> better convention if we ask them politely ;-)), but I just want to make sure we do it the same way as other codepaths, if exist, that have to handle this kind of thing.\n\nNothing else uses LF NUL.  I chose it as a starting point for\nthis very discussion, which I asked about in $gmane/233653.\nIn this particular use case we know the last field will never\nbe LF but that may not be so for future cases.  There is no way\nto represent sentinel-terminated arbitrary variable-width records\nof NUL-terminated fields without some kind of escaping for the\nsentinel value, but the whole point of -z is to avoid escaping.\n\nBelow is a survey of all mentions of NUL and \\0 in documented\nformats as of v1.8.4.  The summary is that most are fixed-width\nrecords but a few have variable width allowing n or n+1 fields.\nIn all variable-width cases there is structured information in\nthe first field that indicates the number of NUL-terminated\nfields to expect.\n\nIn the motivating case here, we could use a --no-old or\n--have-old option to indicate in one field how many more to\nexpect in the record, but that will be quite verbose.\n\nSide note: I'd like to reserve room for the leading options to\ninclude things like \"-m NUL <reason> NUL\" so we cannot keep them\nall in in a single NUL-terminated, SP-separated field.\n\nAnother approach is to introduce a way to represent \"not here\"\nfor the <oldvalue> argument that is not an otherwise valid value.\nThis would make the non-option part of the record have a fixed\nwidth of 3 fields.  For example, we could use SP in -z mode:\n\n [-<opt> NUL]... <ref> NUL <new> NUL (<old>|SP) NUL\n\nand the last field can be optional in non-z mode anyway:\n\n [-<opt> SP]... <ref> SP <new> [SP <old>] LF\n\nOr we could use a character like \"~\" (other ideas?):\n\n [-<opt> NUL]... <ref> NUL <new> NUL (<old>|~) NUL\n\nand make it available in non-z mode too:\n\n [-<opt> SP]... <ref> SP <new> [SP (<old>|~)] LF\n\nThoughts?\n-Brad\n\n\nSurvey of NUL in documented formats:\n------------------------------------------------------------------------\n* Documentation/diff-format.txt: The -z mode for --numstat prints\n  NUL-terminated lines but there is exactly one path at the end of\n  each entry and the earlier fields are separated by TAB because\n  they are structured.\n\n* Documentation/diff-options.txt: The -z mode for diff-tree output\n  prints structured SP/TAB-separated fields in a NUL-terminated\n  field followed by either one or two NUL-terminated paths.  This\n  is variable width but the first field tells us how wide.\n\n* Documentation/git-apply.txt: The -z mode forwards to --numstat\n  diff options.\n\n* Documentation/git-check-attr.txt: The -z mode for stdin reads\n  NUL-terminated paths.\n\n* Documentation/git-check-ignore.txt: The -z mode for stdin reads\n  NUL-terminated paths.  The -z mode for output prints a fixed-width\n  table with every group of four NUL-terminated fields forming a row.\n\n* Documentation/git-checkout-index.txt: The -z mode reads\n  NUL-terminated paths.\n\n* Documentation/git-commit.txt: The -z mode forwards to git-status.\n\n* Documentation/git-grep.txt: The -z mode separates file names from\n  the matched line by a NUL.  Therefore NUL divides LF-terminated\n  lines into two pieces.\n\n* Documentation/git-ls-files.txt: The -z mode prints NUL-terminated\n  lines but there is exactly one path at the end of each entry and the\n  earlier fields are separated by SP and TAB because they are\n  structured.\n\n* Documentation/git-ls-tree.txt: The -z mode prints NUL-terminated\n  lines but there is exactly one path at the end of each entry and the\n  earlier fields are separated by SP and TAB because they are\n  structured.\n\n* Documentation/git-mktree.txt: The -z mode reads NUL-terminated lines\n  as output by ls-tree -z.\n\n* Documentation/git-status.txt: The -z mode of --porcelain separates\n  a variable number of entries by NUL.  The beginning of each entry\n  allows one to know the number of NUL-terminated fields to expect\n  (A = 1 total NUL, R = 2 total NULs, etc.).\n\n* Documentation/git-update-index.txt: The -z mode of --stdin separates\n  paths by NUL.  The -z mode of --index-info separates entries by NUL\n  but there is exactly one path at the end of each entry and the earlier\n  fields are separated by SP and TAB because they are structured.\n\n* Documentation/rev-list-options.txt: The --header option prints\n  commits separated by NUL but they are never empty.\n------------------------------------------------------------------------\n"},{"id":"226771","messageId":"xmqqd2ool1ks.fsf@gitster.dls.corp.google.com","threadId":"34797","inReplyTo":"52279107.2070205@kitware.com","subject":"Re: [PATCH v4 7/8] update-ref: support multiple simultaneous updates","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-09-04T21:27:31Z","receivedAt":"2013-09-04T21:27:31Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Brad King <brad.king@kitware.com> writes:\n\n> Nothing else uses LF NUL.  I chose it as a starting point for\n> this very discussion, which I asked about in $gmane/233653.\n\nThe primary reason why LF raised my eyebrow was because the reason\nwhy many subcommands use \"-z\" (and NUL) is often because the payload\nmay have LF in a record and LF cannot be used as a record separator\nwithout escaping.  And they use NUL knowing that the payload data in\nfields cannot contain a NUL.  If we used LF as a signal to define\nthe structure of the record, it pretty much defeats the whole point\nof defining \"-z\" format.  The -m reason string will be made into a\nsingle liner deep in the codepath but it _can_ contain LF.\n\nI would have been more receptive to, say, double-NUL as a record\nterminator, while using a NUL as a field terminator, or something,\nbut then we would need to have a way to express an empty field.\n\n> In this particular use case we know the last field will never\n> be LF but that may not be so for future cases.  There is no way\n> to represent sentinel-terminated arbitrary variable-width records\n> of NUL-terminated fields without some kind of escaping for the\n> sentinel value, but the whole point of -z is to avoid escaping.\n\nIndeed, but one escape hatch we have is that payload will not\ncontain NUL anywhere, so whenever we see a NUL, we can trust that it\ndefines the structure of the record, and is not a part of the\npayload.\n\nStepping back a bit, here are some observations on the arguments\nupdate-ref can take:\n\n * \"-m <reason>\" is a reason given for this entire update. As the\n   point of this new feature is to give an all-or-none update to one\n   or more refs, I think we should not have to accept more than one\n   reason (more specifically, the -m option does _not_ belong to a\n   specific record that describes what happens to a single ref).\n\n * \"-d <ref> <oldvalue>\" is a way to delete a ref. <oldvalue> may be\n   missing.\n\n * \"--no-deref <ref> <newvalue> <oldvalue>\" and \"<ref> <newvalue>\n   <oldvalue>\" are ways to update or create a ref. Again <oldvalue>\n   may be missing.\n\nSo it looks to me that one possible format that is easy to generate\nby machine without ambiguity may be:\n\n    * The first record could be\n\n      m NUL <reason strong> NUL\n\n      but it is optional. The reason string may contain LF but just\n      like invocation from the command line, LF will eventually\n      cleaned up into a SP.\n\n    * Then a series of records of different kinds follow.\n\n      - A delete record looks like this:\n\n        d NUL <ref> NUL <oldvalue> NUL\n\n        If you want to delete the ref without \"oldvalue\" protection,\n        just say\n\n        d NUL <ref> NUL NUL\n\n      - A create/update record looks like one of these:\n\n        u NUL <ref> NUL <newvalue> NUL <oldvalue> NUL\n        n NUL <ref> NUL <newvalue> NUL <oldvalue> NUL\n\n        Again, if you want to delete the ref without \"oldvalue\"\n        protection, just say\n\n        u NUL <ref> NUL <newvalue> NUL NUL\n        n NUL <ref> NUL <newvalue> NUL NUL\n\n     * EOF signals the end of the request.\n\nI am not saying the above is the best format, but the point is that\nthe mode of the operation defines the structure, so unlike parsing\nxml or json where you first parse the structure and then interpret\nwhat each element means, you can define a simple format where the\nkind of element comes upfront to allow the parser/interpreter know\nwhat is expected to follow it.\n"},{"id":"226880","messageId":"5228EA62.7030606@kitware.com","threadId":"34797","inReplyTo":"xmqqd2ool1ks.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH v4 7/8] update-ref: support multiple simultaneous updates","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-09-05T20:32:34Z","receivedAt":"2013-09-05T20:32:34Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"On 09/04/2013 05:27 PM, Junio C Hamano wrote:\n> I am not saying the above is the best format, but the point is that\n> the mode of the operation defines the structure\n\nGreat, thanks for your comments.  Based on that I've prototyped a\nnew format.  Rather than jumping straight to the patch, here is my\nproposed format documentation for review:\n\n-------------------------------------------------------------------------\nWith `--stdin`, update-ref reads instructions from standard input and\nperforms all modifications together.  Specify commands of the form:\n\n\tcreate SP <ref> SP <newvalue> LF\n\tupdate SP <ref> SP <newvalue> [SP <oldvalue>] LF\n\tdelete SP <ref> [SP <oldvalue>] LF\n\tverify SP <ref> [SP <oldvalue>] LF\n\toption SP <opt> LF\n\nQuote fields containing whitespace as if they were strings in C source\ncode.  Alternatively, use `-z` to specify commands without quoting:\n\n\tcreate SP <ref> NUL <newvalue> NUL\n\tupdate SP <ref> NUL <newvalue> NUL [<oldvalue>] NUL\n\tdelete SP <ref> NUL [<oldvalue>] NUL\n\tverify SP <ref> NUL [<oldvalue>] NUL\n\toption SP <opt> NUL\n\nLines of any other format or a repeated <ref> produce an error.\nCommand meanings are:\n\ncreate::\n\tCreate <ref> with <newvalue> only if it does not exist.\n\nupdate::\n\tUpdate <ref> to be <newvalue>, verifying <oldvalue> if given.\n\tSpecify a zero <newvalue> to delete a ref and/or a zero\n\t<oldvalue> to make sure that a ref does not exist.\n\ndelete::\n\tDelete <ref>, verifying <oldvalue> if given.\n\nverify::\n\tVerify <ref> against <oldvalue> but do not change it.  If\n\t<oldvalue> zero or missing, the ref must not exist.\n\noption::\n\tSpecify an option to take effect for following commands.\n\tValid options are `deref` and `no-deref` to specify whether\n\tto dereference symbolic refs.\n\nUse 40 \"0\" or the empty string to specify a zero value, except that\nwith `-z` an empty <oldvalue> is considered missing.\n\nIf all <ref>s can be locked with matching <oldvalue>s\nsimultaneously, all modifications are performed.  Otherwise, no\nmodifications are performed.  Note that while each individual\n<ref> is updated or deleted atomically, a concurrent reader may\nstill see a subset of the modifications.\n-------------------------------------------------------------------------\n\nThanks,\n-Brad\n"},{"id":"226883","messageId":"xmqqk3ivgdxz.fsf@gitster.dls.corp.google.com","threadId":"34797","inReplyTo":"5228EA62.7030606@kitware.com","subject":"Re: [PATCH v4 7/8] update-ref: support multiple simultaneous updates","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-09-05T21:23:52Z","receivedAt":"2013-09-05T21:23:52Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Brad King <brad.king@kitware.com> writes:\n\n> On 09/04/2013 05:27 PM, Junio C Hamano wrote:\n>> I am not saying the above is the best format, but the point is that\n>> the mode of the operation defines the structure\n>\n> Great, thanks for your comments.  Based on that I've prototyped a\n> new format.  Rather than jumping straight to the patch, here is my\n> proposed format documentation for review:\n>\n> -------------------------------------------------------------------------\n> With `--stdin`, update-ref reads instructions from standard input and\n> performs all modifications together.  Specify commands of the form:\n>\n> \tcreate SP <ref> SP <newvalue> LF\n> \tupdate SP <ref> SP <newvalue> [SP <oldvalue>] LF\n> \tdelete SP <ref> [SP <oldvalue>] LF\n> \tverify SP <ref> [SP <oldvalue>] LF\n> \toption SP <opt> LF\n>\n> Quote fields containing whitespace as if they were strings in C source\n> code.  Alternatively, use `-z` to specify commands without quoting:\n>\n> \tcreate SP <ref> NUL <newvalue> NUL\n> \tupdate SP <ref> NUL <newvalue> NUL [<oldvalue>] NUL\n> \tdelete SP <ref> NUL [<oldvalue>] NUL\n> \tverify SP <ref> NUL [<oldvalue>] NUL\n> \toption SP <opt> NUL\n\nThat SP in '-z' format looks strange.  Was there a reason why NUL\nwas inappropriate?\n\n> Lines of any other format or a repeated <ref> produce an error.\n> Command meanings are:\n>\n> create::\n> \tCreate <ref> with <newvalue> only if it does not exist.\n>\n> update::\n> \tUpdate <ref> to be <newvalue>, verifying <oldvalue> if given.\n> \tSpecify a zero <newvalue> to delete a ref and/or a zero\n> \t<oldvalue> to make sure that a ref does not exist.\n>\n> delete::\n> \tDelete <ref>, verifying <oldvalue> if given.\n>\n> verify::\n> \tVerify <ref> against <oldvalue> but do not change it.  If\n> \t<oldvalue> zero or missing, the ref must not exist.\n>\n> option::\n> \tSpecify an option to take effect for following commands.\n> \tValid options are `deref` and `no-deref` to specify whether\n> \tto dereference symbolic refs.\n\nThis last one is somewhat peculiar, especially because it says\n\"following command*s*\".\n\nHow would I request to update refs HEAD and next in an all-or-none\nfashion, while applying 'no-deref' only to HEAD but not next?\n\n\tupdate refs/heads/next <newvalue>\n\toption --no-deref\n        update HEAD <newvalue>\n\nsounds somewhat convoluted.\n\nWhen I said \"create or update\" in the message you are responding to,\nI did not mean that we should have two separate commands.  The\nregular command line does create-or-update; if it exists already, it\nis an update, and if it does not, it is a create.\n\nIf we were to have two, I would say we should have:\n\n\tcreate-or-update <ref> <newvalue> [<oldvalue>]\n\tcreate-or-update-no-deref <ref> <newvalue> [<oldvalue>]\n        \nAn old value of 0{40} would mean \"I should be the one creating; if\nsomebody else created it while I was preparing this request, please\nfail\".\n\nSimilarly for delete:\n\n\tdelete <ref> [<oldvalue>]\n        delete-no-deref <ref> [<oldvalue>]\n\nAlso how would one set the reflog message for the proposed update?\n"},{"id":"226895","messageId":"5229175A.4070203@kitware.com","threadId":"34797","inReplyTo":"xmqqk3ivgdxz.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH v4 7/8] update-ref: support multiple simultaneous updates","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-09-05T23:44:26Z","receivedAt":"2013-09-05T23:44:26Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"On 9/5/2013 5:23 PM, Junio C Hamano wrote:\n> Brad King <brad.king@kitware.com> writes:\n>> \tcreate SP <ref> NUL <newvalue> NUL\n>> \tupdate SP <ref> NUL <newvalue> NUL [<oldvalue>] NUL\n> \n> That SP in '-z' format looks strange.  Was there a reason why NUL\n> was inappropriate?\n\nThe precedent I saw in the -z survey I posted is that NUL is used\nto terminate fields that can contain arbitrary text but otherwise\nSP or TAB is still used to terminate simple fields.  I recall no\ncases that use NUL after fields that only contain simple values.\n\n>> option::\n>> \tSpecify an option to take effect for following commands.\n> \n> This last one is somewhat peculiar, especially because it says\n> \"following command*s*\".\n> \n> How would I request to update refs HEAD and next in an all-or-none\n> fashion, while applying 'no-deref' only to HEAD but not next?\n\nYou're right.  It will be simpler for clients to generate each\nsequence of options followed by a <ref> command without worrying\nabout options possibly generated for preceding <ref>s.  So:\n\noption::\n\tModify behavior of the next command naming a <ref>.\n\tThe only valid option is `no-deref` to avoid dereferencing\n\ta symbolic ref.\n\n> When I said \"create or update\" in the message you are responding to,\n> I did not mean that we should have two separate commands.\n\nThe nice thing about \"create\" is that it implies oldvalue=zero, just\nas \"delete\" implies newvalue=zero.  The symmetry feels clean.  Also,\nin -z mode we need to treat an empty string <oldvalue> as missing\nrather than zero.  The only way to specify create with the \"update\"\ncommand is with literal 40 \"0\" as <oldvalue>.\n\n> The regular command line does create-or-update; if it exists already,\n> it is an update, and if it does not, it is a create.\n\nThe proposed update command can be used for create, update, delete,\nor verify with proper arguments and use of 40 \"0\".  The other <ref>\ncommands are for convenience, shorter input, and statement of intent.\n\n> \tcreate-or-update-no-deref <ref> <newvalue> [<oldvalue>]\n>         delete-no-deref <ref> [<oldvalue>]\n> \n> Also how would one set the reflog message for the proposed update?\n\nThis is why I proposed a separate option command.  It can be used\nboth for no-deref and for other options we want to add later e.g.\n\n  option SP message SP <reason> LF\n\nand with -z:\n\n  option SP message SP <reason> NUL\n\nFor now I think it is sufficient for the -m <reason> command-line\noption to set the same message for all updates.  The option command\nleaves room to add a per-ref message later.\n\nBTW, the reasons I propose message as an option rather than its own\ncommand are:\n\n* It is simpler to document the reach of the one option command\n  (affects next <ref>) in one place rather than separately for\n  each option-like command.\n\n* It reduces the number of commands that do not take a <ref>.\n\n-Brad\n"},{"id":"227210","messageId":"cover.1378732710.git.brad.king@kitware.com","threadId":"34797","inReplyTo":"cover.1378307529.git.brad.king@kitware.com","subject":"[PATCH v5 0/8] Multiple simultaneously locked ref updates","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-09-09T13:22:31Z","receivedAt":"2013-09-09T13:22:31Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"Hi Folks,\n\nHere is the fifth revision of a series to support locking multiple\nrefs at the same time to update all of them consistently.  The\nprevious revisions of the series can be found at $gmane/233260,\n$gmane/233458, $gmane/233647, and $gmane/233840.\n\nUpdates since the previous revision of the series:\n\n* Patches 1-6 are identical to v4 so are not re-sent here.\n\n* Patch 7 and 8 now implement and test the input format proposed and\n  discussed at $gmane/233990.\n\n-Brad\n\nBrad King (2):\n  update-ref: support multiple simultaneous updates\n  update-ref: add test cases covering --stdin signature\n\n Documentation/git-update-ref.txt |  54 +++-\n builtin/update-ref.c             | 252 ++++++++++++++-\n t/t1400-update-ref.sh            | 639 +++++++++++++++++++++++++++++++++++++++\n 3 files changed, 943 insertions(+), 2 deletions(-)\n\n-- \n1.8.4.rc3\n"},{"id":"227211","messageId":"8cda356bce181bd94dcb9e1625929693874161f6.1378732710.git.brad.king@kitware.com","threadId":"34797","inReplyTo":"cover.1378732710.git.brad.king@kitware.com","subject":"[PATCH v5 7/8] update-ref: support multiple simultaneous updates","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-09-09T13:22:32Z","receivedAt":"2013-09-09T13:22:32Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"Add a --stdin signature to read update instructions from standard input\nand apply multiple ref updates together.  Use an input format that\nsupports any update that could be specified via the command-line,\nincluding object names like \"branch:path with space\".\n\nSigned-off-by: Brad King <brad.king@kitware.com>\n---\n Documentation/git-update-ref.txt |  54 ++++++++-\n builtin/update-ref.c             | 252 ++++++++++++++++++++++++++++++++++++++-\n 2 files changed, 304 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt\nindex 0df13ff..0a0a551 100644\n--- a/Documentation/git-update-ref.txt\n+++ b/Documentation/git-update-ref.txt\n@@ -8,7 +8,7 @@ git-update-ref - Update the object name stored in a ref safely\n SYNOPSIS\n --------\n [verse]\n-'git update-ref' [-m <reason>] (-d <ref> [<oldvalue>] | [--no-deref] <ref> <newvalue> [<oldvalue>])\n+'git update-ref' [-m <reason>] (-d <ref> [<oldvalue>] | [--no-deref] <ref> <newvalue> [<oldvalue>] | --stdin [-z])\n \n DESCRIPTION\n -----------\n@@ -58,6 +58,58 @@ archive by creating a symlink tree).\n With `-d` flag, it deletes the named <ref> after verifying it\n still contains <oldvalue>.\n \n+With `--stdin`, update-ref reads instructions from standard input and\n+performs all modifications together.  Specify commands of the form:\n+\n+\tupdate SP <ref> SP <newvalue> [SP <oldvalue>] LF\n+\tcreate SP <ref> SP <newvalue> LF\n+\tdelete SP <ref> [SP <oldvalue>] LF\n+\tverify SP <ref> [SP <oldvalue>] LF\n+\toption SP <opt> LF\n+\n+Quote fields containing whitespace as if they were strings in C source\n+code.  Alternatively, use `-z` to specify commands without quoting:\n+\n+\tupdate SP <ref> NUL <newvalue> NUL [<oldvalue>] NUL\n+\tcreate SP <ref> NUL <newvalue> NUL\n+\tdelete SP <ref> NUL [<oldvalue>] NUL\n+\tverify SP <ref> NUL [<oldvalue>] NUL\n+\toption SP <opt> NUL\n+\n+Lines of any other format or a repeated <ref> produce an error.\n+Command meanings are:\n+\n+update::\n+\tSet <ref> to <newvalue> after verifying <oldvalue>, if given.\n+\tSpecify a zero <newvalue> to ensure the ref does not exist\n+\tafter the update and/or a zero <oldvalue> to make sure the\n+\tref does not exist before the update.\n+\n+create::\n+\tCreate <ref> with <newvalue> after verifying it does not\n+\texist.  The given <newvalue> may not be zero.\n+\n+delete::\n+\tDelete <ref> after verifying it exists with <oldvalue>, if\n+\tgiven.  If given, <oldvalue> may not be zero.\n+\n+verify::\n+\tVerify <ref> against <oldvalue> but do not change it.  If\n+\t<oldvalue> zero or missing, the ref must not exist.\n+\n+option::\n+\tModify behavior of the next command naming a <ref>.\n+\tThe only valid option is `no-deref` to avoid dereferencing\n+\ta symbolic ref.\n+\n+Use 40 \"0\" or the empty string to specify a zero value, except that\n+with `-z` an empty <oldvalue> is considered missing.\n+\n+If all <ref>s can be locked with matching <oldvalue>s\n+simultaneously, all modifications are performed.  Otherwise, no\n+modifications are performed.  Note that while each individual\n+<ref> is updated or deleted atomically, a concurrent reader may\n+still see a subset of the modifications.\n \n Logging Updates\n ---------------\ndiff --git a/builtin/update-ref.c b/builtin/update-ref.c\nindex 51d2684..894f16b 100644\n--- a/builtin/update-ref.c\n+++ b/builtin/update-ref.c\n@@ -2,23 +2,261 @@\n #include \"refs.h\"\n #include \"builtin.h\"\n #include \"parse-options.h\"\n+#include \"quote.h\"\n+#include \"argv-array.h\"\n \n static const char * const git_update_ref_usage[] = {\n \tN_(\"git update-ref [options] -d <refname> [<oldval>]\"),\n \tN_(\"git update-ref [options]    <refname> <newval> [<oldval>]\"),\n+\tN_(\"git update-ref [options] --stdin [-z]\"),\n \tNULL\n };\n \n+static int updates_alloc;\n+static int updates_count;\n+static const struct ref_update **updates;\n+\n+static char line_termination = '\\n';\n+static int update_flags;\n+\n+static struct ref_update *update_alloc(void)\n+{\n+\tstruct ref_update *update;\n+\n+\t/* Allocate and zero-init a struct ref_update */\n+\tupdate = xcalloc(1, sizeof(*update));\n+\tALLOC_GROW(updates, updates_count + 1, updates_alloc);\n+\tupdates[updates_count++] = update;\n+\n+\t/* Store and reset accumulated options */\n+\tupdate->flags = update_flags;\n+\tupdate_flags = 0;\n+\n+\treturn update;\n+}\n+\n+static void update_store_ref_name(struct ref_update *update,\n+\t\t\t\t  const char *ref_name)\n+{\n+\tif (check_refname_format(ref_name, REFNAME_ALLOW_ONELEVEL))\n+\t\tdie(\"invalid ref format: %s\", ref_name);\n+\tupdate->ref_name = xstrdup(ref_name);\n+}\n+\n+static void update_store_new_sha1(struct ref_update *update,\n+\t\t\t\t  const char *newvalue)\n+{\n+\tif (*newvalue && get_sha1(newvalue, update->new_sha1))\n+\t\tdie(\"invalid new value for ref %s: %s\",\n+\t\t    update->ref_name, newvalue);\n+}\n+\n+static void update_store_old_sha1(struct ref_update *update,\n+\t\t\t\t  const char *oldvalue)\n+{\n+\tif (*oldvalue && get_sha1(oldvalue, update->old_sha1))\n+\t\tdie(\"invalid old value for ref %s: %s\",\n+\t\t    update->ref_name, oldvalue);\n+\n+\t/* We have an old value if non-empty, or if empty without -z */\n+\tupdate->have_old = *oldvalue || line_termination;\n+}\n+\n+static const char *parse_arg(const char *next, struct strbuf *arg)\n+{\n+\t/* Parse SP-terminated, possibly C-quoted argument */\n+\tif (*next != '\"')\n+\t\twhile (*next && !isspace(*next))\n+\t\t\tstrbuf_addch(arg, *next++);\n+\telse if (unquote_c_style(arg, next, &next))\n+\t\tdie(\"badly quoted argument: %s\", next);\n+\n+\t/* Return position after the argument */\n+\treturn next;\n+}\n+\n+static const char *parse_first_arg(const char *next, struct strbuf *arg)\n+{\n+\t/* Parse argument immediately after \"command SP\" */\n+\tstrbuf_reset(arg);\n+\tif (line_termination) {\n+\t\t/* Without -z, use the next argument */\n+\t\tnext = parse_arg(next, arg);\n+\t} else {\n+\t\t/* With -z, use rest of first NUL-terminated line */\n+\t\tstrbuf_addstr(arg, next);\n+\t\tnext = next + arg->len;\n+\t}\n+\treturn next;\n+}\n+\n+static const char *parse_next_arg(const char *next, struct strbuf *arg)\n+{\n+\t/* Parse next SP-terminated or NUL-terminated argument, if any */\n+\tstrbuf_reset(arg);\n+\tif (line_termination) {\n+\t\t/* Without -z, consume SP and use next argument */\n+\t\tif (!*next)\n+\t\t\treturn NULL;\n+\t\tif (*next != ' ')\n+\t\t\tdie(\"expected SP but got: %s\", next);\n+\t\tnext = parse_arg(next + 1, arg);\n+\t} else {\n+\t\t/* With -z, read the next NUL-terminated line */\n+\t\tif (*next)\n+\t\t\tdie(\"expected NUL but got: %s\", next);\n+\t\tif (strbuf_getline(arg, stdin, '\\0') == EOF)\n+\t\t\treturn NULL;\n+\t\tnext = arg->buf + arg->len;\n+\t}\n+\treturn next;\n+}\n+\n+static void parse_cmd_update(const char *next)\n+{\n+\tstruct strbuf ref = STRBUF_INIT;\n+\tstruct strbuf newvalue = STRBUF_INIT;\n+\tstruct strbuf oldvalue = STRBUF_INIT;\n+\tstruct ref_update *update;\n+\n+\tupdate = update_alloc();\n+\n+\tif ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0])\n+\t\tupdate_store_ref_name(update, ref.buf);\n+\telse\n+\t\tdie(\"update line missing <ref>\");\n+\n+\tif ((next = parse_next_arg(next, &newvalue)) != NULL)\n+\t\tupdate_store_new_sha1(update, newvalue.buf);\n+\telse\n+\t\tdie(\"update %s missing <newvalue>\", ref.buf);\n+\n+\tif ((next = parse_next_arg(next, &oldvalue)) != NULL)\n+\t\tupdate_store_old_sha1(update, oldvalue.buf);\n+\telse if(!line_termination)\n+\t\tdie(\"update %s missing [<oldvalue>] NUL\", ref.buf);\n+\n+\tif (next && *next)\n+\t\tdie(\"update %s has extra input: %s\", ref.buf, next);\n+}\n+\n+static void parse_cmd_create(const char *next)\n+{\n+\tstruct strbuf ref = STRBUF_INIT;\n+\tstruct strbuf newvalue = STRBUF_INIT;\n+\tstruct ref_update *update;\n+\n+\tupdate = update_alloc();\n+\n+\tif ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0])\n+\t\tupdate_store_ref_name(update, ref.buf);\n+\telse\n+\t\tdie(\"create line missing <ref>\");\n+\n+\tif ((next = parse_next_arg(next, &newvalue)) != NULL)\n+\t\tupdate_store_new_sha1(update, newvalue.buf);\n+\telse\n+\t\tdie(\"create %s missing <newvalue>\", ref.buf);\n+\tif (is_null_sha1(update->new_sha1))\n+\t\tdie(\"create %s given zero new value\", ref.buf);\n+\n+\tif (next && *next)\n+\t\tdie(\"create %s has extra input: %s\", ref.buf, next);\n+}\n+\n+static void parse_cmd_delete(const char *next)\n+{\n+\tstruct strbuf ref = STRBUF_INIT;\n+\tstruct strbuf oldvalue = STRBUF_INIT;\n+\tstruct ref_update *update;\n+\n+\tupdate = update_alloc();\n+\n+\tif ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0])\n+\t\tupdate_store_ref_name(update, ref.buf);\n+\telse\n+\t\tdie(\"delete line missing <ref>\");\n+\n+\tif ((next = parse_next_arg(next, &oldvalue)) != NULL)\n+\t\tupdate_store_old_sha1(update, oldvalue.buf);\n+\telse if(!line_termination)\n+\t\tdie(\"delete %s missing [<oldvalue>] NUL\", ref.buf);\n+\tif (update->have_old && is_null_sha1(update->old_sha1))\n+\t\tdie(\"delete %s given zero old value\", ref.buf);\n+\n+\tif (next && *next)\n+\t\tdie(\"delete %s has extra input: %s\", ref.buf, next);\n+}\n+\n+static void parse_cmd_verify(const char *next)\n+{\n+\tstruct strbuf ref = STRBUF_INIT;\n+\tstruct strbuf value = STRBUF_INIT;\n+\tstruct ref_update *update;\n+\n+\tupdate = update_alloc();\n+\n+\tif ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0])\n+\t\tupdate_store_ref_name(update, ref.buf);\n+\telse\n+\t\tdie(\"verify line missing <ref>\");\n+\n+\tif ((next = parse_next_arg(next, &value)) != NULL) {\n+\t\tupdate_store_old_sha1(update, value.buf);\n+\t\tupdate_store_new_sha1(update, value.buf);\n+\t} else if(!line_termination)\n+\t\tdie(\"verify %s missing [<oldvalue>] NUL\", ref.buf);\n+\n+\tif (next && *next)\n+\t\tdie(\"verify %s has extra input: %s\", ref.buf, next);\n+}\n+\n+static void parse_cmd_option(const char *next)\n+{\n+\tif (!strcmp(next, \"no-deref\"))\n+\t\tupdate_flags |= REF_NODEREF;\n+\telse\n+\t\tdie(\"option unknown: %s\", next);\n+}\n+\n+static void update_refs_stdin(void)\n+{\n+\tstruct strbuf cmd = STRBUF_INIT;\n+\n+\t/* Read each line dispatch its command */\n+\twhile (strbuf_getline(&cmd, stdin, line_termination) != EOF)\n+\t\tif (!cmd.buf[0])\n+\t\t\tdie(\"empty command in input\");\n+\t\telse if (isspace(*cmd.buf))\n+\t\t\tdie(\"whitespace before command: %s\", cmd.buf);\n+\t\telse if (!prefixcmp(cmd.buf, \"update \"))\n+\t\t\tparse_cmd_update(cmd.buf + 7);\n+\t\telse if (!prefixcmp(cmd.buf, \"create \"))\n+\t\t\tparse_cmd_create(cmd.buf + 7);\n+\t\telse if (!prefixcmp(cmd.buf, \"delete \"))\n+\t\t\tparse_cmd_delete(cmd.buf + 7);\n+\t\telse if (!prefixcmp(cmd.buf, \"verify \"))\n+\t\t\tparse_cmd_verify(cmd.buf + 7);\n+\t\telse if (!prefixcmp(cmd.buf, \"option \"))\n+\t\t\tparse_cmd_option(cmd.buf + 7);\n+\t\telse\n+\t\t\tdie(\"unknown command: %s\", cmd.buf);\n+\n+\tstrbuf_release(&cmd);\n+}\n+\n int cmd_update_ref(int argc, const char **argv, const char *prefix)\n {\n \tconst char *refname, *oldval, *msg = NULL;\n \tunsigned char sha1[20], oldsha1[20];\n-\tint delete = 0, no_deref = 0, flags = 0;\n+\tint delete = 0, no_deref = 0, read_stdin = 0, end_null = 0, flags = 0;\n \tstruct option options[] = {\n \t\tOPT_STRING( 'm', NULL, &msg, N_(\"reason\"), N_(\"reason of the update\")),\n \t\tOPT_BOOLEAN('d', NULL, &delete, N_(\"delete the reference\")),\n+\t\tOPT_BOOLEAN('z', NULL, &end_null, N_(\"stdin has NUL-terminated arguments\")),\n \t\tOPT_BOOLEAN( 0 , \"no-deref\", &no_deref,\n \t\t\t\t\tN_(\"update <refname> not the one it points to\")),\n+\t\tOPT_BOOLEAN( 0 , \"stdin\", &read_stdin, N_(\"read updates from stdin\")),\n \t\tOPT_END(),\n \t};\n \n@@ -28,6 +266,18 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)\n \tif (msg && !*msg)\n \t\tdie(\"Refusing to perform update with empty message.\");\n \n+\tif (read_stdin) {\n+\t\tif (delete || no_deref || argc > 0)\n+\t\t\tusage_with_options(git_update_ref_usage, options);\n+\t\tif (end_null)\n+\t\t\tline_termination = '\\0';\n+\t\tupdate_refs_stdin();\n+\t\treturn update_refs(msg, updates, updates_count, DIE_ON_ERR);\n+\t}\n+\n+\tif (end_null)\n+\t\tusage_with_options(git_update_ref_usage, options);\n+\n \tif (delete) {\n \t\tif (argc < 1 || argc > 2)\n \t\t\tusage_with_options(git_update_ref_usage, options);\n-- \n1.8.4.rc3\n"},{"id":"227212","messageId":"dfa21c5d3a9ee9671e8c6040d52177cbdcf9f21e.1378732710.git.brad.king@kitware.com","threadId":"34797","inReplyTo":"cover.1378732710.git.brad.king@kitware.com","subject":"[PATCH v5 8/8] update-ref: add test cases covering --stdin signature","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-09-09T13:22:33Z","receivedAt":"2013-09-09T13:22:33Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"Extend t/t1400-update-ref.sh to cover cases using the --stdin option.\n\nSigned-off-by: Brad King <brad.king@kitware.com>\n---\n t/t1400-update-ref.sh | 639 ++++++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 639 insertions(+)\n\ndiff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh\nindex e415ee0..a510500 100755\n--- a/t/t1400-update-ref.sh\n+++ b/t/t1400-update-ref.sh\n@@ -302,4 +302,643 @@ test_expect_success \\\n \t'git cat-file blob master@{2005-05-26 23:42}:F (expect OTHER)' \\\n \t'test OTHER = $(git cat-file blob \"master@{2005-05-26 23:42}:F\")'\n \n+a=refs/heads/a\n+b=refs/heads/b\n+c=refs/heads/c\n+E='\"\"'\n+pws='path with space'\n+\n+print_nul() {\n+\twhile test $# -gt 0; do\n+\t\tprintf -- \"$1\" &&\n+\t\tprintf -- \"Q\" | q_to_nul &&\n+\t\tshift || return\n+\tdone\n+}\n+\n+test_expect_success 'stdin test setup' '\n+\techo \"$pws\" >\"$pws\" &&\n+\tgit add -- \"$pws\" &&\n+\tgit commit -m \"$pws\"\n+'\n+\n+test_expect_success '-z fails without --stdin' '\n+\ttest_must_fail git update-ref -z $m $m $m 2>err &&\n+\tgrep \"usage: git update-ref\" err\n+'\n+\n+test_expect_success 'stdin works with no input' '\n+\t>stdin &&\n+\tgit update-ref --stdin <stdin &&\n+\tgit rev-parse --verify -q $m\n+'\n+\n+test_expect_success 'stdin fails on empty line' '\n+\techo \"\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: empty command in input\" err\n+'\n+\n+test_expect_success 'stdin fails on only whitespace' '\n+\techo \" \" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: whitespace before command:  \" err\n+'\n+\n+test_expect_success 'stdin fails on leading whitespace' '\n+\techo \" create $a $m\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: whitespace before command:  create $a $m\" err\n+'\n+\n+test_expect_success 'stdin fails on unknown command' '\n+\techo \"unknown $a\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: unknown command: unknown $a\" err\n+'\n+\n+test_expect_success 'stdin fails on badly quoted input' '\n+\techo \"create $a \\\"master\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: badly quoted argument: \\\\\\\"master\" err\n+'\n+\n+test_expect_success 'stdin fails on arguments not separated by space' '\n+\techo \"create \\\"$a\\\"master\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: expected SP but got: master\" err\n+'\n+\n+test_expect_success 'stdin fails create with no ref' '\n+\techo \"create \" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: create line missing <ref>\" err\n+'\n+\n+test_expect_success 'stdin fails create with bad ref name' '\n+\techo \"create ~a $m\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: invalid ref format: ~a\" err\n+'\n+\n+test_expect_success 'stdin fails create with no new value' '\n+\techo \"create $a\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: create $a missing <newvalue>\" err\n+'\n+\n+test_expect_success 'stdin fails create with too many arguments' '\n+\techo \"create $a $m $m\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: create $a has extra input:  $m\" err\n+'\n+\n+test_expect_success 'stdin fails update with no ref' '\n+\techo \"update \" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: update line missing <ref>\" err\n+'\n+\n+test_expect_success 'stdin fails update with bad ref name' '\n+\techo \"update ~a $m\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: invalid ref format: ~a\" err\n+'\n+\n+test_expect_success 'stdin fails update with no new value' '\n+\techo \"update $a\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: update $a missing <newvalue>\" err\n+'\n+\n+test_expect_success 'stdin fails update with too many arguments' '\n+\techo \"update $a $m $m $m\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: update $a has extra input:  $m\" err\n+'\n+\n+test_expect_success 'stdin fails delete with no ref' '\n+\techo \"delete \" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: delete line missing <ref>\" err\n+'\n+\n+test_expect_success 'stdin fails delete with bad ref name' '\n+\techo \"delete ~a $m\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: invalid ref format: ~a\" err\n+'\n+\n+test_expect_success 'stdin fails delete with too many arguments' '\n+\techo \"delete $a $m $m\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: delete $a has extra input:  $m\" err\n+'\n+\n+test_expect_success 'stdin fails verify with too many arguments' '\n+\techo \"verify $a $m $m\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: verify $a has extra input:  $m\" err\n+'\n+\n+test_expect_success 'stdin fails option with unknown name' '\n+\techo \"option unknown\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: option unknown: unknown\" err\n+'\n+\n+test_expect_success 'stdin fails with duplicate refs' '\n+\tcat >stdin <<-EOF &&\n+\tcreate $a $m\n+\tcreate $b $m\n+\tcreate $a $m\n+\tEOF\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: Multiple updates for ref '\"'\"'$a'\"'\"' not allowed.\" err\n+'\n+\n+test_expect_success 'stdin create ref works' '\n+\techo \"create $a $m\" >stdin &&\n+\tgit update-ref --stdin <stdin &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin update ref creates with zero old value' '\n+\techo \"update $b $m $Z\" >stdin &&\n+\tgit update-ref --stdin <stdin &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual &&\n+\tgit update-ref -d $b\n+'\n+\n+test_expect_success 'stdin update ref creates with empty old value' '\n+\techo \"update $b $m $E\" >stdin &&\n+\tgit update-ref --stdin <stdin &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin create ref works with path with space to blob' '\n+\techo \"create refs/blobs/pws \\\"$m:$pws\\\"\" >stdin &&\n+\tgit update-ref --stdin <stdin &&\n+\tgit rev-parse \"$m:$pws\" >expect &&\n+\tgit rev-parse refs/blobs/pws >actual &&\n+\ttest_cmp expect actual &&\n+\tgit update-ref -d refs/blobs/pws\n+'\n+\n+test_expect_success 'stdin update ref fails with wrong old value' '\n+\techo \"update $c $m $m~1\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: Cannot lock the ref '\"'\"'$c'\"'\"'\" err &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin update ref fails with bad old value' '\n+\techo \"update $c $m does-not-exist\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: invalid old value for ref $c: does-not-exist\" err &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin create ref fails with bad new value' '\n+\techo \"create $c does-not-exist\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: invalid new value for ref $c: does-not-exist\" err &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin create ref fails with zero new value' '\n+\techo \"create $c \" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: create $c given zero new value\" err &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin update ref works with right old value' '\n+\techo \"update $b $m~1 $m\" >stdin &&\n+\tgit update-ref --stdin <stdin &&\n+\tgit rev-parse $m~1 >expect &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin delete ref fails with wrong old value' '\n+\techo \"delete $a $m~1\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: Cannot lock the ref '\"'\"'$a'\"'\"'\" err &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin delete ref fails with zero old value' '\n+\techo \"delete $a \" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: delete $a given zero old value\" err &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin update symref works option no-deref' '\n+\tgit symbolic-ref TESTSYMREF $b &&\n+\tcat >stdin <<-EOF &&\n+\toption no-deref\n+\tupdate TESTSYMREF $a $b\n+\tEOF\n+\tgit update-ref --stdin <stdin &&\n+\tgit rev-parse TESTSYMREF >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual &&\n+\tgit rev-parse $m~1 >expect &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin delete symref works option no-deref' '\n+\tgit symbolic-ref TESTSYMREF $b &&\n+\tcat >stdin <<-EOF &&\n+\toption no-deref\n+\tdelete TESTSYMREF $b\n+\tEOF\n+\tgit update-ref --stdin <stdin &&\n+\ttest_must_fail git rev-parse --verify -q TESTSYMREF &&\n+\tgit rev-parse $m~1 >expect &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin delete ref works with right old value' '\n+\techo \"delete $b $m~1\" >stdin &&\n+\tgit update-ref --stdin <stdin &&\n+\ttest_must_fail git rev-parse --verify -q $b\n+'\n+\n+test_expect_success 'stdin update/create/verify combination works' '\n+\tcat >stdin <<-EOF &&\n+\tupdate $a $m\n+\tcreate $b $m\n+\tverify $c\n+\tEOF\n+\tgit update-ref --stdin <stdin &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin update refs works with identity updates' '\n+\tcat >stdin <<-EOF &&\n+\tupdate $a $m $m\n+\tupdate $b $m $m\n+\tupdate $c $Z $E\n+\tEOF\n+\tgit update-ref --stdin <stdin &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin update refs fails with wrong old value' '\n+\tgit update-ref $c $m &&\n+\tcat >stdin <<-EOF &&\n+\tupdate $a $m $m\n+\tupdate $b $m $m\n+\tupdate $c  ''\n+\tEOF\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: Cannot lock the ref '\"'\"'$c'\"'\"'\" err &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual &&\n+\tgit rev-parse $c >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin delete refs works with packed and loose refs' '\n+\tgit pack-refs --all &&\n+\tgit update-ref $c $m~1 &&\n+\tcat >stdin <<-EOF &&\n+\tdelete $a $m\n+\tupdate $b $Z $m\n+\tupdate $c $E $m~1\n+\tEOF\n+\tgit update-ref --stdin <stdin &&\n+\ttest_must_fail git rev-parse --verify -q $a &&\n+\ttest_must_fail git rev-parse --verify -q $b &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin -z works on empty input' '\n+\t>stdin &&\n+\tgit update-ref -z --stdin <stdin &&\n+\tgit rev-parse --verify -q $m\n+'\n+\n+test_expect_success 'stdin -z fails on empty line' '\n+\techo \"\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: whitespace before command: \" err\n+'\n+\n+test_expect_success 'stdin -z fails on empty command' '\n+\tprint_nul \"\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: empty command in input\" err\n+'\n+\n+test_expect_success 'stdin -z fails on only whitespace' '\n+\tprint_nul \" \" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: whitespace before command:  \" err\n+'\n+\n+test_expect_success 'stdin -z fails on leading whitespace' '\n+\tprint_nul \" create $a\" \"$m\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: whitespace before command:  create $a\" err\n+'\n+\n+test_expect_success 'stdin -z fails on unknown command' '\n+\tprint_nul \"unknown $a\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: unknown command: unknown $a\" err\n+'\n+\n+test_expect_success 'stdin -z fails create with no ref' '\n+\tprint_nul \"create \" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: create line missing <ref>\" err\n+'\n+\n+test_expect_success 'stdin -z fails create with bad ref name' '\n+\tprint_nul \"create ~a \" \"$m\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: invalid ref format: ~a \" err\n+'\n+\n+test_expect_success 'stdin -z fails create with no new value' '\n+\tprint_nul \"create $a\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: create $a missing <newvalue>\" err\n+'\n+\n+test_expect_success 'stdin -z fails create with too many arguments' '\n+\tprint_nul \"create $a\" \"$m\" \"$m\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: unknown command: $m\" err\n+'\n+\n+test_expect_success 'stdin -z fails update with no ref' '\n+\tprint_nul \"update \" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: update line missing <ref>\" err\n+'\n+\n+test_expect_success 'stdin -z fails update with bad ref name' '\n+\tprint_nul \"update ~a\" \"$m\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: invalid ref format: ~a\" err\n+'\n+\n+test_expect_success 'stdin -z fails update with no new value' '\n+\tprint_nul \"update $a\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: update $a missing <newvalue>\" err\n+'\n+\n+test_expect_success 'stdin -z fails update with no old value' '\n+\tprint_nul \"update $a\" \"$m\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: update $a missing \\\\[<oldvalue>\\\\] NUL\" err\n+'\n+\n+test_expect_success 'stdin -z fails update with too many arguments' '\n+\tprint_nul \"update $a\" \"$m\" \"$m\" \"$m\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: unknown command: $m\" err\n+'\n+\n+test_expect_success 'stdin -z fails delete with no ref' '\n+\tprint_nul \"delete \" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: delete line missing <ref>\" err\n+'\n+\n+test_expect_success 'stdin -z fails delete with bad ref name' '\n+\tprint_nul \"delete ~a\" \"$m\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: invalid ref format: ~a\" err\n+'\n+\n+test_expect_success 'stdin -z fails delete with no old value' '\n+\tprint_nul \"delete $a\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: delete $a missing \\\\[<oldvalue>\\\\] NUL\" err\n+'\n+\n+test_expect_success 'stdin -z fails delete with too many arguments' '\n+\tprint_nul \"delete $a\" \"$m\" \"$m\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: unknown command: $m\" err\n+'\n+\n+test_expect_success 'stdin -z fails verify with too many arguments' '\n+\tprint_nul \"verify $a\" \"$m\" \"$m\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: unknown command: $m\" err\n+'\n+\n+test_expect_success 'stdin -z fails verify with no old value' '\n+\tprint_nul \"verify $a\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: verify $a missing \\\\[<oldvalue>\\\\] NUL\" err\n+'\n+\n+test_expect_success 'stdin -z fails option with unknown name' '\n+\tprint_nul \"option unknown\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: option unknown: unknown\" err\n+'\n+\n+test_expect_success 'stdin -z fails with duplicate refs' '\n+\tprint_nul \"create $a\" \"$m\" \"create $b\" \"$m\" \"create $a\" \"$m\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: Multiple updates for ref '\"'\"'$a'\"'\"' not allowed.\" err\n+'\n+\n+test_expect_success 'stdin -z create ref works' '\n+\tprint_nul \"create $a\" \"$m\" >stdin &&\n+\tgit update-ref -z --stdin <stdin &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin -z update ref creates with zero old value' '\n+\tprint_nul \"update $b\" \"$m\" \"$Z\" >stdin &&\n+\tgit update-ref -z --stdin <stdin &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual &&\n+\tgit update-ref -d $b\n+'\n+\n+test_expect_success 'stdin -z update ref creates with empty old value' '\n+\tprint_nul \"update $b\" \"$m\" \"\" >stdin &&\n+\tgit update-ref -z --stdin <stdin &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin -z create ref works with path with space to blob' '\n+\tprint_nul \"create refs/blobs/pws\" \"$m:$pws\" >stdin &&\n+\tgit update-ref -z --stdin <stdin &&\n+\tgit rev-parse \"$m:$pws\" >expect &&\n+\tgit rev-parse refs/blobs/pws >actual &&\n+\ttest_cmp expect actual &&\n+\tgit update-ref -d refs/blobs/pws\n+'\n+\n+test_expect_success 'stdin -z update ref fails with wrong old value' '\n+\tprint_nul \"update $c\" \"$m\" \"$m~1\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: Cannot lock the ref '\"'\"'$c'\"'\"'\" err &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin -z update ref fails with bad old value' '\n+\tprint_nul \"update $c\" \"$m\" \"does-not-exist\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: invalid old value for ref $c: does-not-exist\" err &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin -z create ref fails with bad new value' '\n+\tprint_nul \"create $c\" \"does-not-exist\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: invalid new value for ref $c: does-not-exist\" err &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin -z create ref fails with zero new value' '\n+\tprint_nul \"create $c\" \"\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: create $c given zero new value\" err &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin -z update ref works with right old value' '\n+\tprint_nul \"update $b\" \"$m~1\" \"$m\" >stdin &&\n+\tgit update-ref -z --stdin <stdin &&\n+\tgit rev-parse $m~1 >expect &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin -z delete ref fails with wrong old value' '\n+\tprint_nul \"delete $a\" \"$m~1\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: Cannot lock the ref '\"'\"'$a'\"'\"'\" err &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin -z delete ref fails with zero old value' '\n+\tprint_nul \"delete $a\" \"$Z\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: delete $a given zero old value\" err &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin -z update symref works option no-deref' '\n+\tgit symbolic-ref TESTSYMREF $b &&\n+\tprint_nul \"option no-deref\" \"update TESTSYMREF\" \"$a\" \"$b\" >stdin &&\n+\tgit update-ref -z --stdin <stdin &&\n+\tgit rev-parse TESTSYMREF >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual &&\n+\tgit rev-parse $m~1 >expect &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin -z delete symref works option no-deref' '\n+\tgit symbolic-ref TESTSYMREF $b &&\n+\tprint_nul \"option no-deref\" \"delete TESTSYMREF\" \"$b\" >stdin &&\n+\tgit update-ref -z --stdin <stdin &&\n+\ttest_must_fail git rev-parse --verify -q TESTSYMREF &&\n+\tgit rev-parse $m~1 >expect &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin -z delete ref works with right old value' '\n+\tprint_nul \"delete $b\" \"$m~1\" >stdin &&\n+\tgit update-ref -z --stdin <stdin &&\n+\ttest_must_fail git rev-parse --verify -q $b\n+'\n+\n+test_expect_success 'stdin -z update/create/verify combination works' '\n+\tprint_nul \"update $a\" \"$m\" \"\" \"create $b\" \"$m\" \"verify $c\" \"\" >stdin &&\n+\tgit update-ref -z --stdin <stdin &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin -z update refs works with identity updates' '\n+\tprint_nul \"update $a\" \"$m\" \"$m\" \"update $b\" \"$m\" \"$m\" \"update $c\" \"$Z\" \"\" >stdin &&\n+\tgit update-ref -z --stdin <stdin &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin -z update refs fails with wrong old value' '\n+\tgit update-ref $c $m &&\n+\tprint_nul \"update $a\" \"$m\" \"$m\" \"update $b\" \"$m\" \"$m\" \"update $c\" \"\" \"$Z\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: Cannot lock the ref '\"'\"'$c'\"'\"'\" err &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual &&\n+\tgit rev-parse $c >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin -z delete refs works with packed and loose refs' '\n+\tgit pack-refs --all &&\n+\tgit update-ref $c $m~1 &&\n+\tprint_nul \"delete $a\" \"$m\" \"update $b\" \"$Z\" \"$m\" \"update $c\" \"\" \"$m~1\" >stdin &&\n+\tgit update-ref -z --stdin <stdin &&\n+\ttest_must_fail git rev-parse --verify -q $a &&\n+\ttest_must_fail git rev-parse --verify -q $b &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n test_done\n-- \n1.8.4.rc3\n"},{"id":"227291","messageId":"cover.1378773895.git.brad.king@kitware.com","threadId":"34797","inReplyTo":"cover.1378732710.git.brad.king@kitware.com","subject":"[PATCH v6 0/8] Multiple simultaneously locked ref updates","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-09-10T00:57:38Z","receivedAt":"2013-09-10T00:57:38Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"Hi Folks,\n\nHere is the sixth revision of a series to support locking multiple\nrefs at the same time to update all of them consistently.  The\nprevious revisions of the series can be found at $gmane/233260,\n$gmane/233458, $gmane/233647, $gmane/233840, and $gmane/234324.\n\nUpdates since the previous revision of the series:\n\n* The entire series was rebased on master at bb80ee09; it was\n  previously based on v1.8.4.\n\n* A conflict in refs.c with 47a59185 was resolved by preserving\n  the elimination of find_ref_by_name while adding our new content.\n\n* A conflict in builtin/update-ref.c with d5d09d47 (Replace deprecated\n  OPT_BOOLEAN by OPT_BOOL, 2013-08-03) was resolved by integrating\n  both changes.  The new options added in patch 7 now use OPT_BOOL.\n\n-Brad\n\nBrad King (8):\n  reset: rename update_refs to reset_refs\n  refs: report ref type from lock_any_ref_for_update\n  refs: factor update_ref steps into helpers\n  refs: factor delete_ref loose ref step into a helper\n  refs: add function to repack without multiple refs\n  refs: add update_refs for multiple simultaneous updates\n  update-ref: support multiple simultaneous updates\n  update-ref: add test cases covering --stdin signature\n\n Documentation/git-update-ref.txt |  54 +++-\n branch.c                         |   2 +-\n builtin/commit.c                 |   2 +-\n builtin/fetch.c                  |   3 +-\n builtin/receive-pack.c           |   3 +-\n builtin/reflog.c                 |   2 +-\n builtin/replace.c                |   2 +-\n builtin/reset.c                  |   4 +-\n builtin/tag.c                    |   2 +-\n builtin/update-ref.c             | 252 ++++++++++++++-\n fast-import.c                    |   2 +-\n refs.c                           | 195 ++++++++++--\n refs.h                           |  22 +-\n sequencer.c                      |   3 +-\n t/t1400-update-ref.sh            | 639 +++++++++++++++++++++++++++++++++++++++\n 15 files changed, 1146 insertions(+), 41 deletions(-)\n\n-- \n1.8.4.rc3\n"},{"id":"227293","messageId":"caa29a576f30be135956073ccab6534d0877a418.1378773895.git.brad.king@kitware.com","threadId":"34797","inReplyTo":"cover.1378773895.git.brad.king@kitware.com","subject":"[PATCH v6 1/8] reset: rename update_refs to reset_refs","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-09-10T00:57:39Z","receivedAt":"2013-09-10T00:57:39Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"The function resets refs rather than doing arbitrary updates.\nRename it to allow a future general-purpose update_refs function\nto be added.\n\nSigned-off-by: Brad King <brad.king@kitware.com>\n---\n builtin/reset.c | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/reset.c b/builtin/reset.c\nindex 5e4c551..88b4fde 100644\n--- a/builtin/reset.c\n+++ b/builtin/reset.c\n@@ -226,7 +226,7 @@ static void parse_args(struct pathspec *pathspec,\n \t\t       prefix, argv);\n }\n \n-static int update_refs(const char *rev, const unsigned char *sha1)\n+static int reset_refs(const char *rev, const unsigned char *sha1)\n {\n \tint update_ref_status;\n \tstruct strbuf msg = STRBUF_INIT;\n@@ -357,7 +357,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)\n \tif (!pathspec.nr && !unborn) {\n \t\t/* Any resets without paths update HEAD to the head being\n \t\t * switched to, saving the previous head in ORIG_HEAD before. */\n-\t\tupdate_ref_status = update_refs(rev, sha1);\n+\t\tupdate_ref_status = reset_refs(rev, sha1);\n \n \t\tif (reset_type == HARD && !update_ref_status && !quiet)\n \t\t\tprint_new_head_line(lookup_commit_reference(sha1));\n-- \n1.8.4.rc3\n"},{"id":"227295","messageId":"2e0e4e9736bc0f879120597db4ea2850d0c989e6.1378773895.git.brad.king@kitware.com","threadId":"34797","inReplyTo":"cover.1378773895.git.brad.king@kitware.com","subject":"[PATCH v6 2/8] refs: report ref type from lock_any_ref_for_update","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-09-10T00:57:40Z","receivedAt":"2013-09-10T00:57:40Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"Expose lock_ref_sha1_basic's type_p argument to callers of\nlock_any_ref_for_update.  Update all call sites to ignore it by passing\nNULL for now.\n\nSigned-off-by: Brad King <brad.king@kitware.com>\n---\n branch.c               | 2 +-\n builtin/commit.c       | 2 +-\n builtin/fetch.c        | 3 ++-\n builtin/receive-pack.c | 3 ++-\n builtin/reflog.c       | 2 +-\n builtin/replace.c      | 2 +-\n builtin/tag.c          | 2 +-\n fast-import.c          | 2 +-\n refs.c                 | 7 ++++---\n refs.h                 | 2 +-\n sequencer.c            | 3 ++-\n 11 files changed, 17 insertions(+), 13 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex c5c6984..f2d383f 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -291,7 +291,7 @@ void create_branch(const char *head,\n \thashcpy(sha1, commit->object.sha1);\n \n \tif (!dont_change_ref) {\n-\t\tlock = lock_any_ref_for_update(ref.buf, NULL, 0);\n+\t\tlock = lock_any_ref_for_update(ref.buf, NULL, 0, NULL);\n \t\tif (!lock)\n \t\t\tdie_errno(_(\"Failed to lock ref for update\"));\n \t}\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 80d886a..33d6f5a 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -1615,7 +1615,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \t\t\t\t\t   !current_head\n \t\t\t\t\t   ? NULL\n \t\t\t\t\t   : current_head->object.sha1,\n-\t\t\t\t\t   0);\n+\t\t\t\t\t   0, NULL);\n \n \tnl = strchr(sb.buf, '\\n');\n \tif (nl)\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex 9e654ef..bd7a101 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -262,7 +262,8 @@ static int s_update_ref(const char *action,\n \t\trla = default_rla.buf;\n \tsnprintf(msg, sizeof(msg), \"%s: %s\", rla, action);\n \tlock = lock_any_ref_for_update(ref->name,\n-\t\t\t\t       check_old ? ref->old_sha1 : NULL, 0);\n+\t\t\t\t       check_old ? ref->old_sha1 : NULL,\n+\t\t\t\t       0, NULL);\n \tif (!lock)\n \t\treturn errno == ENOTDIR ? STORE_REF_ERROR_DF_CONFLICT :\n \t\t\t\t\t  STORE_REF_ERROR_OTHER;\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 7434d9b..d8b074f 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -525,7 +525,8 @@ static const char *update(struct command *cmd)\n \t\treturn NULL; /* good */\n \t}\n \telse {\n-\t\tlock = lock_any_ref_for_update(namespaced_name, old_sha1, 0);\n+\t\tlock = lock_any_ref_for_update(namespaced_name, old_sha1,\n+\t\t\t\t\t       0, NULL);\n \t\tif (!lock) {\n \t\t\trp_error(\"failed to lock %s\", name);\n \t\t\treturn \"failed to lock\";\ndiff --git a/builtin/reflog.c b/builtin/reflog.c\nindex 54184b3..28d756a 100644\n--- a/builtin/reflog.c\n+++ b/builtin/reflog.c\n@@ -366,7 +366,7 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused,\n \t * we take the lock for the ref itself to prevent it from\n \t * getting updated.\n \t */\n-\tlock = lock_any_ref_for_update(ref, sha1, 0);\n+\tlock = lock_any_ref_for_update(ref, sha1, 0, NULL);\n \tif (!lock)\n \t\treturn error(\"cannot lock ref '%s'\", ref);\n \tlog_file = git_pathdup(\"logs/%s\", ref);\ndiff --git a/builtin/replace.c b/builtin/replace.c\nindex 11b0a55..301b45c 100644\n--- a/builtin/replace.c\n+++ b/builtin/replace.c\n@@ -105,7 +105,7 @@ static int replace_object(const char *object_ref, const char *replace_ref,\n \telse if (!force)\n \t\tdie(\"replace ref '%s' already exists\", ref);\n \n-\tlock = lock_any_ref_for_update(ref, prev, 0);\n+\tlock = lock_any_ref_for_update(ref, prev, 0, NULL);\n \tif (!lock)\n \t\tdie(\"%s: cannot lock the ref\", ref);\n \tif (write_ref_sha1(lock, repl, NULL) < 0)\ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex b577af5..ea55f1d 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -574,7 +574,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \tif (annotate)\n \t\tcreate_tag(object, tag, &buf, &opt, prev, object);\n \n-\tlock = lock_any_ref_for_update(ref.buf, prev, 0);\n+\tlock = lock_any_ref_for_update(ref.buf, prev, 0, NULL);\n \tif (!lock)\n \t\tdie(_(\"%s: cannot lock the ref\"), ref.buf);\n \tif (write_ref_sha1(lock, object, NULL) < 0)\ndiff --git a/fast-import.c b/fast-import.c\nindex 21db3fc..8036cc4 100644\n--- a/fast-import.c\n+++ b/fast-import.c\n@@ -1694,7 +1694,7 @@ static int update_branch(struct branch *b)\n \t\treturn 0;\n \tif (read_ref(b->name, old_sha1))\n \t\thashclr(old_sha1);\n-\tlock = lock_any_ref_for_update(b->name, old_sha1, 0);\n+\tlock = lock_any_ref_for_update(b->name, old_sha1, 0, NULL);\n \tif (!lock)\n \t\treturn error(\"Unable to lock %s\", b->name);\n \tif (!force_update && !is_null_sha1(old_sha1)) {\ndiff --git a/refs.c b/refs.c\nindex d78860c..5832a8f 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2121,11 +2121,12 @@ struct ref_lock *lock_ref_sha1(const char *refname, const unsigned char *old_sha\n }\n \n struct ref_lock *lock_any_ref_for_update(const char *refname,\n-\t\t\t\t\t const unsigned char *old_sha1, int flags)\n+\t\t\t\t\t const unsigned char *old_sha1,\n+\t\t\t\t\t int flags, int *type_p)\n {\n \tif (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL))\n \t\treturn NULL;\n-\treturn lock_ref_sha1_basic(refname, old_sha1, flags, NULL);\n+\treturn lock_ref_sha1_basic(refname, old_sha1, flags, type_p);\n }\n \n /*\n@@ -3174,7 +3175,7 @@ int update_ref(const char *action, const char *refname,\n \t\tint flags, enum action_on_err onerr)\n {\n \tstatic struct ref_lock *lock;\n-\tlock = lock_any_ref_for_update(refname, oldval, flags);\n+\tlock = lock_any_ref_for_update(refname, oldval, flags, NULL);\n \tif (!lock) {\n \t\tconst char *str = \"Cannot lock the ref '%s'.\";\n \t\tswitch (onerr) {\ndiff --git a/refs.h b/refs.h\nindex 9e5db3a..2cd307a 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -137,7 +137,7 @@ extern struct ref_lock *lock_ref_sha1(const char *refname, const unsigned char *\n #define REF_NODEREF\t0x01\n extern struct ref_lock *lock_any_ref_for_update(const char *refname,\n \t\t\t\t\t\tconst unsigned char *old_sha1,\n-\t\t\t\t\t\tint flags);\n+\t\t\t\t\t\tint flags, int *type_p);\n \n /** Close the file descriptor owned by a lock and return the status */\n extern int close_ref(struct ref_lock *lock);\ndiff --git a/sequencer.c b/sequencer.c\nindex 351548f..06e52b4 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -279,7 +279,8 @@ static int fast_forward_to(const unsigned char *to, const unsigned char *from,\n \tread_cache();\n \tif (checkout_fast_forward(from, to, 1))\n \t\texit(1); /* the callee should have complained already */\n-\tref_lock = lock_any_ref_for_update(\"HEAD\", unborn ? null_sha1 : from, 0);\n+\tref_lock = lock_any_ref_for_update(\"HEAD\", unborn ? null_sha1 : from,\n+\t\t\t\t\t   0, NULL);\n \tstrbuf_addf(&sb, \"%s: fast-forward\", action_name(opts));\n \tret = write_ref_sha1(ref_lock, to, sb.buf);\n \tstrbuf_release(&sb);\n-- \n1.8.4.rc3\n"},{"id":"227294","messageId":"6021464d478d95d0a5763e4421762efae30d67b7.1378773895.git.brad.king@kitware.com","threadId":"34797","inReplyTo":"cover.1378773895.git.brad.king@kitware.com","subject":"[PATCH v6 3/8] refs: factor update_ref steps into helpers","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-09-10T00:57:41Z","receivedAt":"2013-09-10T00:57:41Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"Factor the lock and write steps and error handling into helper functions\nupdate_ref_lock and update_ref_write to allow later use elsewhere.\nExpose lock_any_ref_for_update's type_p to update_ref_lock callers.\n\nWhile at it, drop \"static\" from the local \"lock\" variable as it is not\nnecessary to keep across invocations.\n\nSigned-off-by: Brad King <brad.king@kitware.com>\n---\n refs.c | 30 ++++++++++++++++++++++++------\n 1 file changed, 24 insertions(+), 6 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 5832a8f..f7d3c09 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -3170,12 +3170,13 @@ int for_each_reflog(each_ref_fn fn, void *cb_data)\n \treturn retval;\n }\n \n-int update_ref(const char *action, const char *refname,\n-\t\tconst unsigned char *sha1, const unsigned char *oldval,\n-\t\tint flags, enum action_on_err onerr)\n+static struct ref_lock *update_ref_lock(const char *refname,\n+\t\t\t\t\tconst unsigned char *oldval,\n+\t\t\t\t\tint flags, int *type_p,\n+\t\t\t\t\tenum action_on_err onerr)\n {\n-\tstatic struct ref_lock *lock;\n-\tlock = lock_any_ref_for_update(refname, oldval, flags, NULL);\n+\tstruct ref_lock *lock;\n+\tlock = lock_any_ref_for_update(refname, oldval, flags, type_p);\n \tif (!lock) {\n \t\tconst char *str = \"Cannot lock the ref '%s'.\";\n \t\tswitch (onerr) {\n@@ -3183,8 +3184,14 @@ int update_ref(const char *action, const char *refname,\n \t\tcase DIE_ON_ERR: die(str, refname); break;\n \t\tcase QUIET_ON_ERR: break;\n \t\t}\n-\t\treturn 1;\n \t}\n+\treturn lock;\n+}\n+\n+static int update_ref_write(const char *action, const char *refname,\n+\t\t\t    const unsigned char *sha1, struct ref_lock *lock,\n+\t\t\t    enum action_on_err onerr)\n+{\n \tif (write_ref_sha1(lock, sha1, action) < 0) {\n \t\tconst char *str = \"Cannot update the ref '%s'.\";\n \t\tswitch (onerr) {\n@@ -3197,6 +3204,17 @@ int update_ref(const char *action, const char *refname,\n \treturn 0;\n }\n \n+int update_ref(const char *action, const char *refname,\n+\t       const unsigned char *sha1, const unsigned char *oldval,\n+\t       int flags, enum action_on_err onerr)\n+{\n+\tstruct ref_lock *lock;\n+\tlock = update_ref_lock(refname, oldval, flags, 0, onerr);\n+\tif (!lock)\n+\t\treturn 1;\n+\treturn update_ref_write(action, refname, sha1, lock, onerr);\n+}\n+\n /*\n  * generate a format suitable for scanf from a ref_rev_parse_rules\n  * rule, that is replace the \"%.*s\" spec with a \"%s\" spec\n-- \n1.8.4.rc3\n"},{"id":"227296","messageId":"3795a3f88051f01fb2ced6f4bef1f91a6f812a9b.1378773895.git.brad.king@kitware.com","threadId":"34797","inReplyTo":"cover.1378773895.git.brad.king@kitware.com","subject":"[PATCH v6 4/8] refs: factor delete_ref loose ref step into a helper","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-09-10T00:57:42Z","receivedAt":"2013-09-10T00:57:42Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"Factor loose ref deletion into helper function delete_ref_loose to allow\nlater use elsewhere.\n\nSigned-off-by: Brad King <brad.king@kitware.com>\n---\n refs.c | 27 +++++++++++++++++----------\n 1 file changed, 17 insertions(+), 10 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex f7d3c09..b14f59b 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2450,24 +2450,31 @@ static int repack_without_ref(const char *refname)\n \treturn commit_packed_refs();\n }\n \n+static int delete_ref_loose(struct ref_lock *lock, int flag)\n+{\n+\tif (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) {\n+\t\t/* loose */\n+\t\tint err, i = strlen(lock->lk->filename) - 5; /* .lock */\n+\n+\t\tlock->lk->filename[i] = 0;\n+\t\terr = unlink_or_warn(lock->lk->filename);\n+\t\tlock->lk->filename[i] = '.';\n+\t\tif (err && errno != ENOENT)\n+\t\t\treturn 1;\n+\t}\n+\treturn 0;\n+}\n+\n int delete_ref(const char *refname, const unsigned char *sha1, int delopt)\n {\n \tstruct ref_lock *lock;\n-\tint err, i = 0, ret = 0, flag = 0;\n+\tint ret = 0, flag = 0;\n \n \tlock = lock_ref_sha1_basic(refname, sha1, delopt, &flag);\n \tif (!lock)\n \t\treturn 1;\n-\tif (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) {\n-\t\t/* loose */\n-\t\ti = strlen(lock->lk->filename) - 5; /* .lock */\n-\t\tlock->lk->filename[i] = 0;\n-\t\terr = unlink_or_warn(lock->lk->filename);\n-\t\tif (err && errno != ENOENT)\n-\t\t\tret = 1;\n+\tret |= delete_ref_loose(lock, flag);\n \n-\t\tlock->lk->filename[i] = '.';\n-\t}\n \t/* removing the loose one could have resurrected an earlier\n \t * packed one.  Also, if it was not loose we need to repack\n \t * without it.\n-- \n1.8.4.rc3\n"},{"id":"227299","messageId":"451f8c9ea63078b75a759cb825a1826242672632.1378773895.git.brad.king@kitware.com","threadId":"34797","inReplyTo":"cover.1378773895.git.brad.king@kitware.com","subject":"[PATCH v6 5/8] refs: add function to repack without multiple refs","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-09-10T00:57:43Z","receivedAt":"2013-09-10T00:57:43Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"Generalize repack_without_ref as repack_without_refs to support a list\nof refs and implement the former in terms of the latter.\n\nSigned-off-by: Brad King <brad.king@kitware.com>\n---\n refs.c | 33 ++++++++++++++++++++++++---------\n 1 file changed, 24 insertions(+), 9 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex b14f59b..4a63404 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2414,42 +2414,57 @@ static int curate_packed_ref_fn(struct ref_entry *entry, void *cb_data)\n \treturn 0;\n }\n \n-static int repack_without_ref(const char *refname)\n+static int repack_without_refs(const char **refnames, int n)\n {\n \tstruct ref_dir *packed;\n \tstruct string_list refs_to_delete = STRING_LIST_INIT_DUP;\n \tstruct string_list_item *ref_to_delete;\n+\tint i, removed = 0;\n+\n+\t/* Look for a packed ref */\n+\tfor (i = 0; i < n; i++)\n+\t\tif (get_packed_ref(refnames[i]))\n+\t\t\tbreak;\n \n-\tif (!get_packed_ref(refname))\n-\t\treturn 0; /* refname does not exist in packed refs */\n+\t/* Avoid locking if we have nothing to do */\n+\tif (i == n)\n+\t\treturn 0; /* no refname exists in packed refs */\n \n \tif (lock_packed_refs(0)) {\n \t\tunable_to_lock_error(git_path(\"packed-refs\"), errno);\n-\t\treturn error(\"cannot delete '%s' from packed refs\", refname);\n+\t\treturn error(\"cannot delete '%s' from packed refs\", refnames[i]);\n \t}\n \tpacked = get_packed_refs(&ref_cache);\n \n-\t/* Remove refname from the cache: */\n-\tif (remove_entry(packed, refname) == -1) {\n+\t/* Remove refnames from the cache */\n+\tfor (i = 0; i < n; i++)\n+\t\tif (remove_entry(packed, refnames[i]) != -1)\n+\t\t\tremoved = 1;\n+\tif (!removed) {\n \t\t/*\n-\t\t * The packed entry disappeared while we were\n+\t\t * All packed entries disappeared while we were\n \t\t * acquiring the lock.\n \t\t */\n \t\trollback_packed_refs();\n \t\treturn 0;\n \t}\n \n-\t/* Remove any other accumulated cruft: */\n+\t/* Remove any other accumulated cruft */\n \tdo_for_each_entry_in_dir(packed, 0, curate_packed_ref_fn, &refs_to_delete);\n \tfor_each_string_list_item(ref_to_delete, &refs_to_delete) {\n \t\tif (remove_entry(packed, ref_to_delete->string) == -1)\n \t\t\tdie(\"internal error\");\n \t}\n \n-\t/* Write what remains: */\n+\t/* Write what remains */\n \treturn commit_packed_refs();\n }\n \n+static int repack_without_ref(const char *refname)\n+{\n+\treturn repack_without_refs(&refname, 1);\n+}\n+\n static int delete_ref_loose(struct ref_lock *lock, int flag)\n {\n \tif (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) {\n-- \n1.8.4.rc3\n"},{"id":"227298","messageId":"016681d5c6c9c1c0905b1836e7374dad4db5d326.1378773895.git.brad.king@kitware.com","threadId":"34797","inReplyTo":"cover.1378773895.git.brad.king@kitware.com","subject":"[PATCH v6 6/8] refs: add update_refs for multiple simultaneous updates","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-09-10T00:57:44Z","receivedAt":"2013-09-10T00:57:44Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"Add 'struct ref_update' to encode the information needed to update or\ndelete a ref (name, new sha1, optional old sha1, no-deref flag).  Add\nfunction 'update_refs' accepting an array of updates to perform.  First\nsort the input array to order locks consistently everywhere and reject\nmultiple updates to the same ref.  Then acquire locks on all refs with\nverified old values.  Then update or delete all refs accordingly.  Fail\nif any one lock cannot be obtained or any one old value does not match.\n\nThough the refs themselves cannot be modified together in a single\natomic transaction, this function does enable some useful semantics.\nFor example, a caller may create a new branch starting from the head of\nanother branch and rewind the original branch at the same time.  This\ntransfers ownership of commits between branches without risk of losing\ncommits added to the original branch by a concurrent process, or risk of\na concurrent process creating the new branch first.\n\nSigned-off-by: Brad King <brad.king@kitware.com>\n---\n refs.c | 100 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n refs.h |  20 +++++++++++++\n 2 files changed, 120 insertions(+)\n\ndiff --git a/refs.c b/refs.c\nindex 4a63404..6383813 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -3237,6 +3237,106 @@ int update_ref(const char *action, const char *refname,\n \treturn update_ref_write(action, refname, sha1, lock, onerr);\n }\n \n+static int ref_update_compare(const void *r1, const void *r2)\n+{\n+\tconst struct ref_update * const *u1 = r1;\n+\tconst struct ref_update * const *u2 = r2;\n+\treturn strcmp((*u1)->ref_name, (*u2)->ref_name);\n+}\n+\n+static int ref_update_reject_duplicates(struct ref_update **updates, int n,\n+\t\t\t\t\tenum action_on_err onerr)\n+{\n+\tint i;\n+\tfor (i = 1; i < n; i++)\n+\t\tif (!strcmp(updates[i - 1]->ref_name, updates[i]->ref_name)) {\n+\t\t\tconst char *str =\n+\t\t\t\t\"Multiple updates for ref '%s' not allowed.\";\n+\t\t\tswitch (onerr) {\n+\t\t\tcase MSG_ON_ERR:\n+\t\t\t\terror(str, updates[i]->ref_name); break;\n+\t\t\tcase DIE_ON_ERR:\n+\t\t\t\tdie(str, updates[i]->ref_name); break;\n+\t\t\tcase QUIET_ON_ERR:\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t\treturn 1;\n+\t\t}\n+\treturn 0;\n+}\n+\n+int update_refs(const char *action, const struct ref_update **updates_orig,\n+\t\tint n, enum action_on_err onerr)\n+{\n+\tint ret = 0, delnum = 0, i;\n+\tstruct ref_update **updates;\n+\tint *types;\n+\tstruct ref_lock **locks;\n+\tconst char **delnames;\n+\n+\tif (!updates_orig || !n)\n+\t\treturn 0;\n+\n+\t/* Allocate work space */\n+\tupdates = xmalloc(sizeof(*updates) * n);\n+\ttypes = xmalloc(sizeof(*types) * n);\n+\tlocks = xcalloc(n, sizeof(*locks));\n+\tdelnames = xmalloc(sizeof(*delnames) * n);\n+\n+\t/* Copy, sort, and reject duplicate refs */\n+\tmemcpy(updates, updates_orig, sizeof(*updates) * n);\n+\tqsort(updates, n, sizeof(*updates), ref_update_compare);\n+\tret = ref_update_reject_duplicates(updates, n, onerr);\n+\tif (ret)\n+\t\tgoto cleanup;\n+\n+\t/* Acquire all locks while verifying old values */\n+\tfor (i = 0; i < n; i++) {\n+\t\tlocks[i] = update_ref_lock(updates[i]->ref_name,\n+\t\t\t\t\t   (updates[i]->have_old ?\n+\t\t\t\t\t    updates[i]->old_sha1 : NULL),\n+\t\t\t\t\t   updates[i]->flags,\n+\t\t\t\t\t   &types[i], onerr);\n+\t\tif (!locks[i]) {\n+\t\t\tret = 1;\n+\t\t\tgoto cleanup;\n+\t\t}\n+\t}\n+\n+\t/* Perform updates first so live commits remain referenced */\n+\tfor (i = 0; i < n; i++)\n+\t\tif (!is_null_sha1(updates[i]->new_sha1)) {\n+\t\t\tret = update_ref_write(action,\n+\t\t\t\t\t       updates[i]->ref_name,\n+\t\t\t\t\t       updates[i]->new_sha1,\n+\t\t\t\t\t       locks[i], onerr);\n+\t\t\tlocks[i] = NULL; /* freed by update_ref_write */\n+\t\t\tif (ret)\n+\t\t\t\tgoto cleanup;\n+\t\t}\n+\n+\t/* Perform deletes now that updates are safely completed */\n+\tfor (i = 0; i < n; i++)\n+\t\tif (locks[i]) {\n+\t\t\tdelnames[delnum++] = locks[i]->ref_name;\n+\t\t\tret |= delete_ref_loose(locks[i], types[i]);\n+\t\t}\n+\tret |= repack_without_refs(delnames, delnum);\n+\tfor (i = 0; i < delnum; i++)\n+\t\tunlink_or_warn(git_path(\"logs/%s\", delnames[i]));\n+\tclear_loose_ref_cache(&ref_cache);\n+\n+cleanup:\n+\tfor (i = 0; i < n; i++)\n+\t\tif (locks[i])\n+\t\t\tunlock_ref(locks[i]);\n+\tfree(updates);\n+\tfree(types);\n+\tfree(locks);\n+\tfree(delnames);\n+\treturn ret;\n+}\n+\n /*\n  * generate a format suitable for scanf from a ref_rev_parse_rules\n  * rule, that is replace the \"%.*s\" spec with a \"%s\" spec\ndiff --git a/refs.h b/refs.h\nindex 2cd307a..b113377 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -10,6 +10,20 @@ struct ref_lock {\n \tint force_write;\n };\n \n+/**\n+ * Information needed for a single ref update.  Set new_sha1 to the\n+ * new value or to zero to delete the ref.  To check the old value\n+ * while locking the ref, set have_old to 1 and set old_sha1 to the\n+ * value or to zero to ensure the ref does not exist before update.\n+ */\n+struct ref_update {\n+\tconst char *ref_name;\n+\tunsigned char new_sha1[20];\n+\tunsigned char old_sha1[20];\n+\tint flags; /* REF_NODEREF? */\n+\tint have_old; /* 1 if old_sha1 is valid, 0 otherwise */\n+};\n+\n /*\n  * Bit values set in the flags argument passed to each_ref_fn():\n  */\n@@ -214,6 +228,12 @@ int update_ref(const char *action, const char *refname,\n \t\tconst unsigned char *sha1, const unsigned char *oldval,\n \t\tint flags, enum action_on_err onerr);\n \n+/**\n+ * Lock all refs and then perform all modifications.\n+ */\n+int update_refs(const char *action, const struct ref_update **updates,\n+\t\tint n, enum action_on_err onerr);\n+\n extern int parse_hide_refs_config(const char *var, const char *value, const char *);\n extern int ref_is_hidden(const char *);\n \n-- \n1.8.4.rc3\n"},{"id":"227297","messageId":"74c081c4004f3a8afb38ab15aff7d3178de4a1f8.1378773895.git.brad.king@kitware.com","threadId":"34797","inReplyTo":"cover.1378773895.git.brad.king@kitware.com","subject":"[PATCH v6 7/8] update-ref: support multiple simultaneous updates","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-09-10T00:57:45Z","receivedAt":"2013-09-10T00:57:45Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"Add a --stdin signature to read update instructions from standard input\nand apply multiple ref updates together.  Use an input format that\nsupports any update that could be specified via the command-line,\nincluding object names like \"branch:path with space\".\n\nSigned-off-by: Brad King <brad.king@kitware.com>\n---\n Documentation/git-update-ref.txt |  54 ++++++++-\n builtin/update-ref.c             | 252 ++++++++++++++++++++++++++++++++++++++-\n 2 files changed, 304 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt\nindex 0df13ff..0a0a551 100644\n--- a/Documentation/git-update-ref.txt\n+++ b/Documentation/git-update-ref.txt\n@@ -8,7 +8,7 @@ git-update-ref - Update the object name stored in a ref safely\n SYNOPSIS\n --------\n [verse]\n-'git update-ref' [-m <reason>] (-d <ref> [<oldvalue>] | [--no-deref] <ref> <newvalue> [<oldvalue>])\n+'git update-ref' [-m <reason>] (-d <ref> [<oldvalue>] | [--no-deref] <ref> <newvalue> [<oldvalue>] | --stdin [-z])\n \n DESCRIPTION\n -----------\n@@ -58,6 +58,58 @@ archive by creating a symlink tree).\n With `-d` flag, it deletes the named <ref> after verifying it\n still contains <oldvalue>.\n \n+With `--stdin`, update-ref reads instructions from standard input and\n+performs all modifications together.  Specify commands of the form:\n+\n+\tupdate SP <ref> SP <newvalue> [SP <oldvalue>] LF\n+\tcreate SP <ref> SP <newvalue> LF\n+\tdelete SP <ref> [SP <oldvalue>] LF\n+\tverify SP <ref> [SP <oldvalue>] LF\n+\toption SP <opt> LF\n+\n+Quote fields containing whitespace as if they were strings in C source\n+code.  Alternatively, use `-z` to specify commands without quoting:\n+\n+\tupdate SP <ref> NUL <newvalue> NUL [<oldvalue>] NUL\n+\tcreate SP <ref> NUL <newvalue> NUL\n+\tdelete SP <ref> NUL [<oldvalue>] NUL\n+\tverify SP <ref> NUL [<oldvalue>] NUL\n+\toption SP <opt> NUL\n+\n+Lines of any other format or a repeated <ref> produce an error.\n+Command meanings are:\n+\n+update::\n+\tSet <ref> to <newvalue> after verifying <oldvalue>, if given.\n+\tSpecify a zero <newvalue> to ensure the ref does not exist\n+\tafter the update and/or a zero <oldvalue> to make sure the\n+\tref does not exist before the update.\n+\n+create::\n+\tCreate <ref> with <newvalue> after verifying it does not\n+\texist.  The given <newvalue> may not be zero.\n+\n+delete::\n+\tDelete <ref> after verifying it exists with <oldvalue>, if\n+\tgiven.  If given, <oldvalue> may not be zero.\n+\n+verify::\n+\tVerify <ref> against <oldvalue> but do not change it.  If\n+\t<oldvalue> zero or missing, the ref must not exist.\n+\n+option::\n+\tModify behavior of the next command naming a <ref>.\n+\tThe only valid option is `no-deref` to avoid dereferencing\n+\ta symbolic ref.\n+\n+Use 40 \"0\" or the empty string to specify a zero value, except that\n+with `-z` an empty <oldvalue> is considered missing.\n+\n+If all <ref>s can be locked with matching <oldvalue>s\n+simultaneously, all modifications are performed.  Otherwise, no\n+modifications are performed.  Note that while each individual\n+<ref> is updated or deleted atomically, a concurrent reader may\n+still see a subset of the modifications.\n \n Logging Updates\n ---------------\ndiff --git a/builtin/update-ref.c b/builtin/update-ref.c\nindex 7484d36..b5479e2 100644\n--- a/builtin/update-ref.c\n+++ b/builtin/update-ref.c\n@@ -2,23 +2,261 @@\n #include \"refs.h\"\n #include \"builtin.h\"\n #include \"parse-options.h\"\n+#include \"quote.h\"\n+#include \"argv-array.h\"\n \n static const char * const git_update_ref_usage[] = {\n \tN_(\"git update-ref [options] -d <refname> [<oldval>]\"),\n \tN_(\"git update-ref [options]    <refname> <newval> [<oldval>]\"),\n+\tN_(\"git update-ref [options] --stdin [-z]\"),\n \tNULL\n };\n \n+static int updates_alloc;\n+static int updates_count;\n+static const struct ref_update **updates;\n+\n+static char line_termination = '\\n';\n+static int update_flags;\n+\n+static struct ref_update *update_alloc(void)\n+{\n+\tstruct ref_update *update;\n+\n+\t/* Allocate and zero-init a struct ref_update */\n+\tupdate = xcalloc(1, sizeof(*update));\n+\tALLOC_GROW(updates, updates_count + 1, updates_alloc);\n+\tupdates[updates_count++] = update;\n+\n+\t/* Store and reset accumulated options */\n+\tupdate->flags = update_flags;\n+\tupdate_flags = 0;\n+\n+\treturn update;\n+}\n+\n+static void update_store_ref_name(struct ref_update *update,\n+\t\t\t\t  const char *ref_name)\n+{\n+\tif (check_refname_format(ref_name, REFNAME_ALLOW_ONELEVEL))\n+\t\tdie(\"invalid ref format: %s\", ref_name);\n+\tupdate->ref_name = xstrdup(ref_name);\n+}\n+\n+static void update_store_new_sha1(struct ref_update *update,\n+\t\t\t\t  const char *newvalue)\n+{\n+\tif (*newvalue && get_sha1(newvalue, update->new_sha1))\n+\t\tdie(\"invalid new value for ref %s: %s\",\n+\t\t    update->ref_name, newvalue);\n+}\n+\n+static void update_store_old_sha1(struct ref_update *update,\n+\t\t\t\t  const char *oldvalue)\n+{\n+\tif (*oldvalue && get_sha1(oldvalue, update->old_sha1))\n+\t\tdie(\"invalid old value for ref %s: %s\",\n+\t\t    update->ref_name, oldvalue);\n+\n+\t/* We have an old value if non-empty, or if empty without -z */\n+\tupdate->have_old = *oldvalue || line_termination;\n+}\n+\n+static const char *parse_arg(const char *next, struct strbuf *arg)\n+{\n+\t/* Parse SP-terminated, possibly C-quoted argument */\n+\tif (*next != '\"')\n+\t\twhile (*next && !isspace(*next))\n+\t\t\tstrbuf_addch(arg, *next++);\n+\telse if (unquote_c_style(arg, next, &next))\n+\t\tdie(\"badly quoted argument: %s\", next);\n+\n+\t/* Return position after the argument */\n+\treturn next;\n+}\n+\n+static const char *parse_first_arg(const char *next, struct strbuf *arg)\n+{\n+\t/* Parse argument immediately after \"command SP\" */\n+\tstrbuf_reset(arg);\n+\tif (line_termination) {\n+\t\t/* Without -z, use the next argument */\n+\t\tnext = parse_arg(next, arg);\n+\t} else {\n+\t\t/* With -z, use rest of first NUL-terminated line */\n+\t\tstrbuf_addstr(arg, next);\n+\t\tnext = next + arg->len;\n+\t}\n+\treturn next;\n+}\n+\n+static const char *parse_next_arg(const char *next, struct strbuf *arg)\n+{\n+\t/* Parse next SP-terminated or NUL-terminated argument, if any */\n+\tstrbuf_reset(arg);\n+\tif (line_termination) {\n+\t\t/* Without -z, consume SP and use next argument */\n+\t\tif (!*next)\n+\t\t\treturn NULL;\n+\t\tif (*next != ' ')\n+\t\t\tdie(\"expected SP but got: %s\", next);\n+\t\tnext = parse_arg(next + 1, arg);\n+\t} else {\n+\t\t/* With -z, read the next NUL-terminated line */\n+\t\tif (*next)\n+\t\t\tdie(\"expected NUL but got: %s\", next);\n+\t\tif (strbuf_getline(arg, stdin, '\\0') == EOF)\n+\t\t\treturn NULL;\n+\t\tnext = arg->buf + arg->len;\n+\t}\n+\treturn next;\n+}\n+\n+static void parse_cmd_update(const char *next)\n+{\n+\tstruct strbuf ref = STRBUF_INIT;\n+\tstruct strbuf newvalue = STRBUF_INIT;\n+\tstruct strbuf oldvalue = STRBUF_INIT;\n+\tstruct ref_update *update;\n+\n+\tupdate = update_alloc();\n+\n+\tif ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0])\n+\t\tupdate_store_ref_name(update, ref.buf);\n+\telse\n+\t\tdie(\"update line missing <ref>\");\n+\n+\tif ((next = parse_next_arg(next, &newvalue)) != NULL)\n+\t\tupdate_store_new_sha1(update, newvalue.buf);\n+\telse\n+\t\tdie(\"update %s missing <newvalue>\", ref.buf);\n+\n+\tif ((next = parse_next_arg(next, &oldvalue)) != NULL)\n+\t\tupdate_store_old_sha1(update, oldvalue.buf);\n+\telse if(!line_termination)\n+\t\tdie(\"update %s missing [<oldvalue>] NUL\", ref.buf);\n+\n+\tif (next && *next)\n+\t\tdie(\"update %s has extra input: %s\", ref.buf, next);\n+}\n+\n+static void parse_cmd_create(const char *next)\n+{\n+\tstruct strbuf ref = STRBUF_INIT;\n+\tstruct strbuf newvalue = STRBUF_INIT;\n+\tstruct ref_update *update;\n+\n+\tupdate = update_alloc();\n+\n+\tif ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0])\n+\t\tupdate_store_ref_name(update, ref.buf);\n+\telse\n+\t\tdie(\"create line missing <ref>\");\n+\n+\tif ((next = parse_next_arg(next, &newvalue)) != NULL)\n+\t\tupdate_store_new_sha1(update, newvalue.buf);\n+\telse\n+\t\tdie(\"create %s missing <newvalue>\", ref.buf);\n+\tif (is_null_sha1(update->new_sha1))\n+\t\tdie(\"create %s given zero new value\", ref.buf);\n+\n+\tif (next && *next)\n+\t\tdie(\"create %s has extra input: %s\", ref.buf, next);\n+}\n+\n+static void parse_cmd_delete(const char *next)\n+{\n+\tstruct strbuf ref = STRBUF_INIT;\n+\tstruct strbuf oldvalue = STRBUF_INIT;\n+\tstruct ref_update *update;\n+\n+\tupdate = update_alloc();\n+\n+\tif ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0])\n+\t\tupdate_store_ref_name(update, ref.buf);\n+\telse\n+\t\tdie(\"delete line missing <ref>\");\n+\n+\tif ((next = parse_next_arg(next, &oldvalue)) != NULL)\n+\t\tupdate_store_old_sha1(update, oldvalue.buf);\n+\telse if(!line_termination)\n+\t\tdie(\"delete %s missing [<oldvalue>] NUL\", ref.buf);\n+\tif (update->have_old && is_null_sha1(update->old_sha1))\n+\t\tdie(\"delete %s given zero old value\", ref.buf);\n+\n+\tif (next && *next)\n+\t\tdie(\"delete %s has extra input: %s\", ref.buf, next);\n+}\n+\n+static void parse_cmd_verify(const char *next)\n+{\n+\tstruct strbuf ref = STRBUF_INIT;\n+\tstruct strbuf value = STRBUF_INIT;\n+\tstruct ref_update *update;\n+\n+\tupdate = update_alloc();\n+\n+\tif ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0])\n+\t\tupdate_store_ref_name(update, ref.buf);\n+\telse\n+\t\tdie(\"verify line missing <ref>\");\n+\n+\tif ((next = parse_next_arg(next, &value)) != NULL) {\n+\t\tupdate_store_old_sha1(update, value.buf);\n+\t\tupdate_store_new_sha1(update, value.buf);\n+\t} else if(!line_termination)\n+\t\tdie(\"verify %s missing [<oldvalue>] NUL\", ref.buf);\n+\n+\tif (next && *next)\n+\t\tdie(\"verify %s has extra input: %s\", ref.buf, next);\n+}\n+\n+static void parse_cmd_option(const char *next)\n+{\n+\tif (!strcmp(next, \"no-deref\"))\n+\t\tupdate_flags |= REF_NODEREF;\n+\telse\n+\t\tdie(\"option unknown: %s\", next);\n+}\n+\n+static void update_refs_stdin(void)\n+{\n+\tstruct strbuf cmd = STRBUF_INIT;\n+\n+\t/* Read each line dispatch its command */\n+\twhile (strbuf_getline(&cmd, stdin, line_termination) != EOF)\n+\t\tif (!cmd.buf[0])\n+\t\t\tdie(\"empty command in input\");\n+\t\telse if (isspace(*cmd.buf))\n+\t\t\tdie(\"whitespace before command: %s\", cmd.buf);\n+\t\telse if (!prefixcmp(cmd.buf, \"update \"))\n+\t\t\tparse_cmd_update(cmd.buf + 7);\n+\t\telse if (!prefixcmp(cmd.buf, \"create \"))\n+\t\t\tparse_cmd_create(cmd.buf + 7);\n+\t\telse if (!prefixcmp(cmd.buf, \"delete \"))\n+\t\t\tparse_cmd_delete(cmd.buf + 7);\n+\t\telse if (!prefixcmp(cmd.buf, \"verify \"))\n+\t\t\tparse_cmd_verify(cmd.buf + 7);\n+\t\telse if (!prefixcmp(cmd.buf, \"option \"))\n+\t\t\tparse_cmd_option(cmd.buf + 7);\n+\t\telse\n+\t\t\tdie(\"unknown command: %s\", cmd.buf);\n+\n+\tstrbuf_release(&cmd);\n+}\n+\n int cmd_update_ref(int argc, const char **argv, const char *prefix)\n {\n \tconst char *refname, *oldval, *msg = NULL;\n \tunsigned char sha1[20], oldsha1[20];\n-\tint delete = 0, no_deref = 0, flags = 0;\n+\tint delete = 0, no_deref = 0, read_stdin = 0, end_null = 0, flags = 0;\n \tstruct option options[] = {\n \t\tOPT_STRING( 'm', NULL, &msg, N_(\"reason\"), N_(\"reason of the update\")),\n \t\tOPT_BOOL('d', NULL, &delete, N_(\"delete the reference\")),\n+\t\tOPT_BOOL('z', NULL, &end_null, N_(\"stdin has NUL-terminated arguments\")),\n \t\tOPT_BOOL( 0 , \"no-deref\", &no_deref,\n \t\t\t\t\tN_(\"update <refname> not the one it points to\")),\n+\t\tOPT_BOOL( 0 , \"stdin\", &read_stdin, N_(\"read updates from stdin\")),\n \t\tOPT_END(),\n \t};\n \n@@ -28,6 +266,18 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)\n \tif (msg && !*msg)\n \t\tdie(\"Refusing to perform update with empty message.\");\n \n+\tif (read_stdin) {\n+\t\tif (delete || no_deref || argc > 0)\n+\t\t\tusage_with_options(git_update_ref_usage, options);\n+\t\tif (end_null)\n+\t\t\tline_termination = '\\0';\n+\t\tupdate_refs_stdin();\n+\t\treturn update_refs(msg, updates, updates_count, DIE_ON_ERR);\n+\t}\n+\n+\tif (end_null)\n+\t\tusage_with_options(git_update_ref_usage, options);\n+\n \tif (delete) {\n \t\tif (argc < 1 || argc > 2)\n \t\t\tusage_with_options(git_update_ref_usage, options);\n-- \n1.8.4.rc3\n"},{"id":"227292","messageId":"9083b451c8bdb7c7e04541deee1b96ae81f910b4.1378773895.git.brad.king@kitware.com","threadId":"34797","inReplyTo":"cover.1378773895.git.brad.king@kitware.com","subject":"[PATCH v6 8/8] update-ref: add test cases covering --stdin signature","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-09-10T00:57:46Z","receivedAt":"2013-09-10T00:57:46Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"Extend t/t1400-update-ref.sh to cover cases using the --stdin option.\n\nSigned-off-by: Brad King <brad.king@kitware.com>\n---\n t/t1400-update-ref.sh | 639 ++++++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 639 insertions(+)\n\ndiff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh\nindex e415ee0..a510500 100755\n--- a/t/t1400-update-ref.sh\n+++ b/t/t1400-update-ref.sh\n@@ -302,4 +302,643 @@ test_expect_success \\\n \t'git cat-file blob master@{2005-05-26 23:42}:F (expect OTHER)' \\\n \t'test OTHER = $(git cat-file blob \"master@{2005-05-26 23:42}:F\")'\n \n+a=refs/heads/a\n+b=refs/heads/b\n+c=refs/heads/c\n+E='\"\"'\n+pws='path with space'\n+\n+print_nul() {\n+\twhile test $# -gt 0; do\n+\t\tprintf -- \"$1\" &&\n+\t\tprintf -- \"Q\" | q_to_nul &&\n+\t\tshift || return\n+\tdone\n+}\n+\n+test_expect_success 'stdin test setup' '\n+\techo \"$pws\" >\"$pws\" &&\n+\tgit add -- \"$pws\" &&\n+\tgit commit -m \"$pws\"\n+'\n+\n+test_expect_success '-z fails without --stdin' '\n+\ttest_must_fail git update-ref -z $m $m $m 2>err &&\n+\tgrep \"usage: git update-ref\" err\n+'\n+\n+test_expect_success 'stdin works with no input' '\n+\t>stdin &&\n+\tgit update-ref --stdin <stdin &&\n+\tgit rev-parse --verify -q $m\n+'\n+\n+test_expect_success 'stdin fails on empty line' '\n+\techo \"\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: empty command in input\" err\n+'\n+\n+test_expect_success 'stdin fails on only whitespace' '\n+\techo \" \" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: whitespace before command:  \" err\n+'\n+\n+test_expect_success 'stdin fails on leading whitespace' '\n+\techo \" create $a $m\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: whitespace before command:  create $a $m\" err\n+'\n+\n+test_expect_success 'stdin fails on unknown command' '\n+\techo \"unknown $a\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: unknown command: unknown $a\" err\n+'\n+\n+test_expect_success 'stdin fails on badly quoted input' '\n+\techo \"create $a \\\"master\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: badly quoted argument: \\\\\\\"master\" err\n+'\n+\n+test_expect_success 'stdin fails on arguments not separated by space' '\n+\techo \"create \\\"$a\\\"master\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: expected SP but got: master\" err\n+'\n+\n+test_expect_success 'stdin fails create with no ref' '\n+\techo \"create \" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: create line missing <ref>\" err\n+'\n+\n+test_expect_success 'stdin fails create with bad ref name' '\n+\techo \"create ~a $m\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: invalid ref format: ~a\" err\n+'\n+\n+test_expect_success 'stdin fails create with no new value' '\n+\techo \"create $a\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: create $a missing <newvalue>\" err\n+'\n+\n+test_expect_success 'stdin fails create with too many arguments' '\n+\techo \"create $a $m $m\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: create $a has extra input:  $m\" err\n+'\n+\n+test_expect_success 'stdin fails update with no ref' '\n+\techo \"update \" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: update line missing <ref>\" err\n+'\n+\n+test_expect_success 'stdin fails update with bad ref name' '\n+\techo \"update ~a $m\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: invalid ref format: ~a\" err\n+'\n+\n+test_expect_success 'stdin fails update with no new value' '\n+\techo \"update $a\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: update $a missing <newvalue>\" err\n+'\n+\n+test_expect_success 'stdin fails update with too many arguments' '\n+\techo \"update $a $m $m $m\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: update $a has extra input:  $m\" err\n+'\n+\n+test_expect_success 'stdin fails delete with no ref' '\n+\techo \"delete \" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: delete line missing <ref>\" err\n+'\n+\n+test_expect_success 'stdin fails delete with bad ref name' '\n+\techo \"delete ~a $m\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: invalid ref format: ~a\" err\n+'\n+\n+test_expect_success 'stdin fails delete with too many arguments' '\n+\techo \"delete $a $m $m\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: delete $a has extra input:  $m\" err\n+'\n+\n+test_expect_success 'stdin fails verify with too many arguments' '\n+\techo \"verify $a $m $m\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: verify $a has extra input:  $m\" err\n+'\n+\n+test_expect_success 'stdin fails option with unknown name' '\n+\techo \"option unknown\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: option unknown: unknown\" err\n+'\n+\n+test_expect_success 'stdin fails with duplicate refs' '\n+\tcat >stdin <<-EOF &&\n+\tcreate $a $m\n+\tcreate $b $m\n+\tcreate $a $m\n+\tEOF\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: Multiple updates for ref '\"'\"'$a'\"'\"' not allowed.\" err\n+'\n+\n+test_expect_success 'stdin create ref works' '\n+\techo \"create $a $m\" >stdin &&\n+\tgit update-ref --stdin <stdin &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin update ref creates with zero old value' '\n+\techo \"update $b $m $Z\" >stdin &&\n+\tgit update-ref --stdin <stdin &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual &&\n+\tgit update-ref -d $b\n+'\n+\n+test_expect_success 'stdin update ref creates with empty old value' '\n+\techo \"update $b $m $E\" >stdin &&\n+\tgit update-ref --stdin <stdin &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin create ref works with path with space to blob' '\n+\techo \"create refs/blobs/pws \\\"$m:$pws\\\"\" >stdin &&\n+\tgit update-ref --stdin <stdin &&\n+\tgit rev-parse \"$m:$pws\" >expect &&\n+\tgit rev-parse refs/blobs/pws >actual &&\n+\ttest_cmp expect actual &&\n+\tgit update-ref -d refs/blobs/pws\n+'\n+\n+test_expect_success 'stdin update ref fails with wrong old value' '\n+\techo \"update $c $m $m~1\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: Cannot lock the ref '\"'\"'$c'\"'\"'\" err &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin update ref fails with bad old value' '\n+\techo \"update $c $m does-not-exist\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: invalid old value for ref $c: does-not-exist\" err &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin create ref fails with bad new value' '\n+\techo \"create $c does-not-exist\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: invalid new value for ref $c: does-not-exist\" err &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin create ref fails with zero new value' '\n+\techo \"create $c \" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: create $c given zero new value\" err &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin update ref works with right old value' '\n+\techo \"update $b $m~1 $m\" >stdin &&\n+\tgit update-ref --stdin <stdin &&\n+\tgit rev-parse $m~1 >expect &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin delete ref fails with wrong old value' '\n+\techo \"delete $a $m~1\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: Cannot lock the ref '\"'\"'$a'\"'\"'\" err &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin delete ref fails with zero old value' '\n+\techo \"delete $a \" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: delete $a given zero old value\" err &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin update symref works option no-deref' '\n+\tgit symbolic-ref TESTSYMREF $b &&\n+\tcat >stdin <<-EOF &&\n+\toption no-deref\n+\tupdate TESTSYMREF $a $b\n+\tEOF\n+\tgit update-ref --stdin <stdin &&\n+\tgit rev-parse TESTSYMREF >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual &&\n+\tgit rev-parse $m~1 >expect &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin delete symref works option no-deref' '\n+\tgit symbolic-ref TESTSYMREF $b &&\n+\tcat >stdin <<-EOF &&\n+\toption no-deref\n+\tdelete TESTSYMREF $b\n+\tEOF\n+\tgit update-ref --stdin <stdin &&\n+\ttest_must_fail git rev-parse --verify -q TESTSYMREF &&\n+\tgit rev-parse $m~1 >expect &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin delete ref works with right old value' '\n+\techo \"delete $b $m~1\" >stdin &&\n+\tgit update-ref --stdin <stdin &&\n+\ttest_must_fail git rev-parse --verify -q $b\n+'\n+\n+test_expect_success 'stdin update/create/verify combination works' '\n+\tcat >stdin <<-EOF &&\n+\tupdate $a $m\n+\tcreate $b $m\n+\tverify $c\n+\tEOF\n+\tgit update-ref --stdin <stdin &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin update refs works with identity updates' '\n+\tcat >stdin <<-EOF &&\n+\tupdate $a $m $m\n+\tupdate $b $m $m\n+\tupdate $c $Z $E\n+\tEOF\n+\tgit update-ref --stdin <stdin &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin update refs fails with wrong old value' '\n+\tgit update-ref $c $m &&\n+\tcat >stdin <<-EOF &&\n+\tupdate $a $m $m\n+\tupdate $b $m $m\n+\tupdate $c  ''\n+\tEOF\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: Cannot lock the ref '\"'\"'$c'\"'\"'\" err &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual &&\n+\tgit rev-parse $c >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin delete refs works with packed and loose refs' '\n+\tgit pack-refs --all &&\n+\tgit update-ref $c $m~1 &&\n+\tcat >stdin <<-EOF &&\n+\tdelete $a $m\n+\tupdate $b $Z $m\n+\tupdate $c $E $m~1\n+\tEOF\n+\tgit update-ref --stdin <stdin &&\n+\ttest_must_fail git rev-parse --verify -q $a &&\n+\ttest_must_fail git rev-parse --verify -q $b &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin -z works on empty input' '\n+\t>stdin &&\n+\tgit update-ref -z --stdin <stdin &&\n+\tgit rev-parse --verify -q $m\n+'\n+\n+test_expect_success 'stdin -z fails on empty line' '\n+\techo \"\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: whitespace before command: \" err\n+'\n+\n+test_expect_success 'stdin -z fails on empty command' '\n+\tprint_nul \"\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: empty command in input\" err\n+'\n+\n+test_expect_success 'stdin -z fails on only whitespace' '\n+\tprint_nul \" \" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: whitespace before command:  \" err\n+'\n+\n+test_expect_success 'stdin -z fails on leading whitespace' '\n+\tprint_nul \" create $a\" \"$m\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: whitespace before command:  create $a\" err\n+'\n+\n+test_expect_success 'stdin -z fails on unknown command' '\n+\tprint_nul \"unknown $a\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: unknown command: unknown $a\" err\n+'\n+\n+test_expect_success 'stdin -z fails create with no ref' '\n+\tprint_nul \"create \" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: create line missing <ref>\" err\n+'\n+\n+test_expect_success 'stdin -z fails create with bad ref name' '\n+\tprint_nul \"create ~a \" \"$m\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: invalid ref format: ~a \" err\n+'\n+\n+test_expect_success 'stdin -z fails create with no new value' '\n+\tprint_nul \"create $a\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: create $a missing <newvalue>\" err\n+'\n+\n+test_expect_success 'stdin -z fails create with too many arguments' '\n+\tprint_nul \"create $a\" \"$m\" \"$m\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: unknown command: $m\" err\n+'\n+\n+test_expect_success 'stdin -z fails update with no ref' '\n+\tprint_nul \"update \" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: update line missing <ref>\" err\n+'\n+\n+test_expect_success 'stdin -z fails update with bad ref name' '\n+\tprint_nul \"update ~a\" \"$m\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: invalid ref format: ~a\" err\n+'\n+\n+test_expect_success 'stdin -z fails update with no new value' '\n+\tprint_nul \"update $a\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: update $a missing <newvalue>\" err\n+'\n+\n+test_expect_success 'stdin -z fails update with no old value' '\n+\tprint_nul \"update $a\" \"$m\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: update $a missing \\\\[<oldvalue>\\\\] NUL\" err\n+'\n+\n+test_expect_success 'stdin -z fails update with too many arguments' '\n+\tprint_nul \"update $a\" \"$m\" \"$m\" \"$m\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: unknown command: $m\" err\n+'\n+\n+test_expect_success 'stdin -z fails delete with no ref' '\n+\tprint_nul \"delete \" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: delete line missing <ref>\" err\n+'\n+\n+test_expect_success 'stdin -z fails delete with bad ref name' '\n+\tprint_nul \"delete ~a\" \"$m\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: invalid ref format: ~a\" err\n+'\n+\n+test_expect_success 'stdin -z fails delete with no old value' '\n+\tprint_nul \"delete $a\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: delete $a missing \\\\[<oldvalue>\\\\] NUL\" err\n+'\n+\n+test_expect_success 'stdin -z fails delete with too many arguments' '\n+\tprint_nul \"delete $a\" \"$m\" \"$m\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: unknown command: $m\" err\n+'\n+\n+test_expect_success 'stdin -z fails verify with too many arguments' '\n+\tprint_nul \"verify $a\" \"$m\" \"$m\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: unknown command: $m\" err\n+'\n+\n+test_expect_success 'stdin -z fails verify with no old value' '\n+\tprint_nul \"verify $a\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: verify $a missing \\\\[<oldvalue>\\\\] NUL\" err\n+'\n+\n+test_expect_success 'stdin -z fails option with unknown name' '\n+\tprint_nul \"option unknown\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: option unknown: unknown\" err\n+'\n+\n+test_expect_success 'stdin -z fails with duplicate refs' '\n+\tprint_nul \"create $a\" \"$m\" \"create $b\" \"$m\" \"create $a\" \"$m\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: Multiple updates for ref '\"'\"'$a'\"'\"' not allowed.\" err\n+'\n+\n+test_expect_success 'stdin -z create ref works' '\n+\tprint_nul \"create $a\" \"$m\" >stdin &&\n+\tgit update-ref -z --stdin <stdin &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin -z update ref creates with zero old value' '\n+\tprint_nul \"update $b\" \"$m\" \"$Z\" >stdin &&\n+\tgit update-ref -z --stdin <stdin &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual &&\n+\tgit update-ref -d $b\n+'\n+\n+test_expect_success 'stdin -z update ref creates with empty old value' '\n+\tprint_nul \"update $b\" \"$m\" \"\" >stdin &&\n+\tgit update-ref -z --stdin <stdin &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin -z create ref works with path with space to blob' '\n+\tprint_nul \"create refs/blobs/pws\" \"$m:$pws\" >stdin &&\n+\tgit update-ref -z --stdin <stdin &&\n+\tgit rev-parse \"$m:$pws\" >expect &&\n+\tgit rev-parse refs/blobs/pws >actual &&\n+\ttest_cmp expect actual &&\n+\tgit update-ref -d refs/blobs/pws\n+'\n+\n+test_expect_success 'stdin -z update ref fails with wrong old value' '\n+\tprint_nul \"update $c\" \"$m\" \"$m~1\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: Cannot lock the ref '\"'\"'$c'\"'\"'\" err &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin -z update ref fails with bad old value' '\n+\tprint_nul \"update $c\" \"$m\" \"does-not-exist\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: invalid old value for ref $c: does-not-exist\" err &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin -z create ref fails with bad new value' '\n+\tprint_nul \"create $c\" \"does-not-exist\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: invalid new value for ref $c: does-not-exist\" err &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin -z create ref fails with zero new value' '\n+\tprint_nul \"create $c\" \"\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: create $c given zero new value\" err &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin -z update ref works with right old value' '\n+\tprint_nul \"update $b\" \"$m~1\" \"$m\" >stdin &&\n+\tgit update-ref -z --stdin <stdin &&\n+\tgit rev-parse $m~1 >expect &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin -z delete ref fails with wrong old value' '\n+\tprint_nul \"delete $a\" \"$m~1\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: Cannot lock the ref '\"'\"'$a'\"'\"'\" err &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin -z delete ref fails with zero old value' '\n+\tprint_nul \"delete $a\" \"$Z\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: delete $a given zero old value\" err &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin -z update symref works option no-deref' '\n+\tgit symbolic-ref TESTSYMREF $b &&\n+\tprint_nul \"option no-deref\" \"update TESTSYMREF\" \"$a\" \"$b\" >stdin &&\n+\tgit update-ref -z --stdin <stdin &&\n+\tgit rev-parse TESTSYMREF >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual &&\n+\tgit rev-parse $m~1 >expect &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin -z delete symref works option no-deref' '\n+\tgit symbolic-ref TESTSYMREF $b &&\n+\tprint_nul \"option no-deref\" \"delete TESTSYMREF\" \"$b\" >stdin &&\n+\tgit update-ref -z --stdin <stdin &&\n+\ttest_must_fail git rev-parse --verify -q TESTSYMREF &&\n+\tgit rev-parse $m~1 >expect &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin -z delete ref works with right old value' '\n+\tprint_nul \"delete $b\" \"$m~1\" >stdin &&\n+\tgit update-ref -z --stdin <stdin &&\n+\ttest_must_fail git rev-parse --verify -q $b\n+'\n+\n+test_expect_success 'stdin -z update/create/verify combination works' '\n+\tprint_nul \"update $a\" \"$m\" \"\" \"create $b\" \"$m\" \"verify $c\" \"\" >stdin &&\n+\tgit update-ref -z --stdin <stdin &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin -z update refs works with identity updates' '\n+\tprint_nul \"update $a\" \"$m\" \"$m\" \"update $b\" \"$m\" \"$m\" \"update $c\" \"$Z\" \"\" >stdin &&\n+\tgit update-ref -z --stdin <stdin &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin -z update refs fails with wrong old value' '\n+\tgit update-ref $c $m &&\n+\tprint_nul \"update $a\" \"$m\" \"$m\" \"update $b\" \"$m\" \"$m\" \"update $c\" \"\" \"$Z\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: Cannot lock the ref '\"'\"'$c'\"'\"'\" err &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual &&\n+\tgit rev-parse $c >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin -z delete refs works with packed and loose refs' '\n+\tgit pack-refs --all &&\n+\tgit update-ref $c $m~1 &&\n+\tprint_nul \"delete $a\" \"$m\" \"update $b\" \"$Z\" \"$m\" \"update $c\" \"\" \"$m~1\" >stdin &&\n+\tgit update-ref -z --stdin <stdin &&\n+\ttest_must_fail git rev-parse --verify -q $a &&\n+\ttest_must_fail git rev-parse --verify -q $b &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n test_done\n-- \n1.8.4.rc3\n"},{"id":"227302","messageId":"CALkWK0kC-jwONA2iZ+KYSe7iJAEq=SFNdUEmuxWUpKuJ+r_AKQ@mail.gmail.com","threadId":"34797","inReplyTo":"caa29a576f30be135956073ccab6534d0877a418.1378773895.git.brad.king@kitware.com","subject":"Re: [PATCH v6 1/8] reset: rename update_refs to reset_refs","fromName":"Ramkumar Ramachandra","fromEmail":"artagnon@gmail.com","sentAt":"2013-09-10T03:43:16Z","receivedAt":"2013-09-10T03:43:16Z","isPatch":true,"sender":{"key":"r@artagnon.com","avatar":"https://avatars.githubusercontent.com/u/37226?v=4"},"body":"Brad King wrote:\n> The function resets refs rather than doing arbitrary updates.\n> Rename it to allow a future general-purpose update_refs function\n> to be added.\n\nMakes sense; thanks.\n"},{"id":"227326","messageId":"xmqqob80zlju.fsf@gitster.dls.corp.google.com","threadId":"34797","inReplyTo":"cover.1378773895.git.brad.king@kitware.com","subject":"Re: [PATCH v6 0/8] Multiple simultaneously locked ref updates","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-09-10T16:30:29Z","receivedAt":"2013-09-10T16:30:29Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Brad King <brad.king@kitware.com> writes:\n\n> Updates since the previous revision of the series:\n>\n> * The entire series was rebased on master at bb80ee09; it was\n>   previously based on v1.8.4.\n>\n> * A conflict in refs.c with 47a59185 was resolved by preserving\n>   the elimination of find_ref_by_name while adding our new content.\n>\n> * A conflict in builtin/update-ref.c with d5d09d47 (Replace deprecated\n>   OPT_BOOLEAN by OPT_BOOL, 2013-08-03) was resolved by integrating\n>   both changes.  The new options added in patch 7 now use OPT_BOOL.\n\nI just test-applied these on top of bb80ee09 (Update draft release\nnotes to 1.8.5 for the second batch of topics, 2013-09-09), and\ncompared the result with the result of merging the tip of the\nprevious round 511910e1 (update-ref: add test cases covering --stdin\nsignature, 2013-09-09) with bb80ee09, and they more-or-less match\n(the order of options[] array elements may differ in\nupdate-index.c), which validates that existing merge conflict\nresolution matches your expectation.\n\nThanks.  I am not sure if I should rewind and rebuild the series\nwith these patches, though.  This is a new feature and does not have\nto be merged to 'maint', so rebasing is perfectly fine, but it is\nnot strictly necessary, either.\n"},{"id":"227329","messageId":"522F4EDE.1010104@kitware.com","threadId":"34797","inReplyTo":"xmqqob80zlju.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH v6 0/8] Multiple simultaneously locked ref updates","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-09-10T16:54:54Z","receivedAt":"2013-09-10T16:54:54Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"On 09/10/2013 12:30 PM, Junio C Hamano wrote:\n> Thanks.  I am not sure if I should rewind and rebuild the series\n> with these patches, though.  This is a new feature and does not have\n> to be merged to 'maint', so rebasing is perfectly fine, but it is\n> not strictly necessary, either.\n\nI just thought I'd help out with the conflict resolution.  If you're\nhappy with resolving the conflicts in v5 then there is no reason to\nuse v6.\n\nThanks,\n-Brad\n"},{"id":"227364","messageId":"xmqq7geoxwfl.fsf@gitster.dls.corp.google.com","threadId":"34797","inReplyTo":"522F4EDE.1010104@kitware.com","subject":"Re: [PATCH v6 0/8] Multiple simultaneously locked ref updates","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-09-10T20:18:22Z","receivedAt":"2013-09-10T20:18:22Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Brad King <brad.king@kitware.com> writes:\n\n> On 09/10/2013 12:30 PM, Junio C Hamano wrote:\n>> Thanks.  I am not sure if I should rewind and rebuild the series\n>> with these patches, though.  This is a new feature and does not have\n>> to be merged to 'maint', so rebasing is perfectly fine, but it is\n>> not strictly necessary, either.\n>\n> I just thought I'd help out with the conflict resolution.\n\nYeah, such an independent confirmation of the conflict resolution is\nvery much appreciated.\n"},{"id":"227384","messageId":"CAPig+cQNrtznw1jc+dVOZhqBV7me0gC6Vx-k5siMPeyCzxmBSQ@mail.gmail.com","threadId":"34797","inReplyTo":"9083b451c8bdb7c7e04541deee1b96ae81f910b4.1378773895.git.brad.king@kitware.com","subject":"Re: [PATCH v6 8/8] update-ref: add test cases covering --stdin signature","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2013-09-10T22:46:05Z","receivedAt":"2013-09-10T22:46:05Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Mon, Sep 9, 2013 at 8:57 PM, Brad King <brad.king@kitware.com> wrote:\n> Extend t/t1400-update-ref.sh to cover cases using the --stdin option.\n>\n> Signed-off-by: Brad King <brad.king@kitware.com>\n> ---\n>  t/t1400-update-ref.sh | 639 ++++++++++++++++++++++++++++++++++++++++++++++++++\n>  1 file changed, 639 insertions(+)\n>\n> diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh\n> index e415ee0..a510500 100755\n> --- a/t/t1400-update-ref.sh\n> +++ b/t/t1400-update-ref.sh\n> @@ -302,4 +302,643 @@ test_expect_success \\\n>         'git cat-file blob master@{2005-05-26 23:42}:F (expect OTHER)' \\\n>         'test OTHER = $(git cat-file blob \"master@{2005-05-26 23:42}:F\")'\n>\n> +a=refs/heads/a\n> +b=refs/heads/b\n> +c=refs/heads/c\n> +E='\"\"'\n> +pws='path with space'\n> +\n> +print_nul() {\n> +       while test $# -gt 0; do\n> +               printf -- \"$1\" &&\n> +               printf -- \"Q\" | q_to_nul &&\n> +               shift || return\n> +       done\n> +}\n\nI believe that current fashion in git test scripts is to add a space\nbefore () in the function declaration. Likewise, the 'do' should be on\nthe line following 'while' and aligned with 'while' (and drop the\nsemicolon).\n\nThe '--' option to printf is not likely portable. POSIX [1] certainly\ndoes not mention it.\n\nYou can get printf to emit a NUL more naturally via \\0, so q_to_nul is\nunnecessary.\n\nFinally, printf reuses its 'format' argument as many times as needed\nto output all arguments, so the while loop is unneeded.\n\nThus printf provides all the functionality you require, and\nprint_nul() function can be dropped. So:\n\n    printf '%s\\0' foo bar baz\n\nis equivalent to:\n\n    print_null foo bar baz\n\n[1]: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/printf.html\n"},{"id":"227385","messageId":"CAPig+cQ=PSQbF4+YfZC-Ps2Te47VNTRm_yDTVY48vGh4G2UFgQ@mail.gmail.com","threadId":"34797","inReplyTo":"74c081c4004f3a8afb38ab15aff7d3178de4a1f8.1378773895.git.brad.king@kitware.com","subject":"Re: [PATCH v6 7/8] update-ref: support multiple simultaneous updates","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2013-09-10T22:51:28Z","receivedAt":"2013-09-10T22:51:28Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Mon, Sep 9, 2013 at 8:57 PM, Brad King <brad.king@kitware.com> wrote:\n> diff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt\n> index 0df13ff..0a0a551 100644\n> --- a/Documentation/git-update-ref.txt\n> +++ b/Documentation/git-update-ref.txt\n> @@ -58,6 +58,58 @@ archive by creating a symlink tree).\n> +option::\n> +       Modify behavior of the next command naming a <ref>.\n> +       The only valid option is `no-deref` to avoid dereferencing\n> +       a symbolic ref.\n> +\n> +Use 40 \"0\" or the empty string to specify a zero value, except that\n\nDid you want an 's' after the \"0\"?\n\n0's\n\"0\"s\n\"0\"'s\nzeros\nzeroes\n\n> +with `-z` an empty <oldvalue> is considered missing.\n> +\n> +If all <ref>s can be locked with matching <oldvalue>s\n> +simultaneously, all modifications are performed.  Otherwise, no\n> +modifications are performed.  Note that while each individual\n> +<ref> is updated or deleted atomically, a concurrent reader may\n> +still see a subset of the modifications.\n"},{"id":"227386","messageId":"xmqqppsguw1o.fsf@gitster.dls.corp.google.com","threadId":"34797","inReplyTo":"CAPig+cQNrtznw1jc+dVOZhqBV7me0gC6Vx-k5siMPeyCzxmBSQ@mail.gmail.com","subject":"Re: [PATCH v6 8/8] update-ref: add test cases covering --stdin signature","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-09-10T22:54:59Z","receivedAt":"2013-09-10T22:54:59Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Eric Sunshine <sunshine@sunshineco.com> writes:\n\n> Thus printf provides all the functionality you require, and\n> print_nul() function can be dropped. So:\n>\n>     printf '%s\\0' foo bar baz\n>\n> is equivalent to:\n>\n>     print_null foo bar baz\n\nGood eyes.  Thanks, I missed them when I looked at the patches.\n"},{"id":"227446","messageId":"523063D7.7000305@kitware.com","threadId":"34797","inReplyTo":"CAPig+cQ=PSQbF4+YfZC-Ps2Te47VNTRm_yDTVY48vGh4G2UFgQ@mail.gmail.com","subject":"Re: [PATCH v6 7/8] update-ref: support multiple simultaneous updates","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-09-11T12:36:39Z","receivedAt":"2013-09-11T12:36:39Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"On 09/10/2013 06:51 PM, Eric Sunshine wrote:\n> On Mon, Sep 9, 2013 at 8:57 PM, Brad King <brad.king@kitware.com> wrote:\n>> +Use 40 \"0\" or the empty string to specify a zero value, except that\n> \n> Did you want an 's' after the \"0\"?\n\nThe same description without 's' already appears in git-update-ref.txt\nabove this location in the existing documentation of the command-line\noption behavior.  I see 0{40} in git-receive-pack.txt and also in\nhowto/update-hook-example.txt.  Perhaps a follow-up change can be made\nto choose a consistent way to describe 40 0s.\n\n-Brad\n"},{"id":"227447","messageId":"6e43bee0d5d8f3ba5e1e41ed8f9faa2f10654591.1378903086.git.brad.king@kitware.com","threadId":"34797","inReplyTo":"CAPig+cQNrtznw1jc+dVOZhqBV7me0gC6Vx-k5siMPeyCzxmBSQ@mail.gmail.com","subject":"[PATCH v6.1 8/8] update-ref: add test cases covering --stdin signature","fromName":"Brad King","fromEmail":"brad.king@kitware.com","sentAt":"2013-09-11T12:46:18Z","receivedAt":"2013-09-11T12:46:18Z","isPatch":true,"sender":{"key":"brad.king@kitware.com","avatar":"https://avatars.githubusercontent.com/u/87268?v=4"},"body":"Extend t/t1400-update-ref.sh to cover cases using the --stdin option.\n\nSigned-off-by: Brad King <brad.king@kitware.com>\n---\n\nOn 09/10/2013 06:46 PM, Eric Sunshine wrote:\n> Thus printf provides all the functionality you require, and\n> print_nul() function can be dropped. So:\n> \n>     printf '%s\\0' foo bar baz\n\nWonderful, thanks!  The single-quotes do not fit easily inside\ntest code blocks that are themselves single-quoted, so I packaged\nthe format up in a variable.  Here is a revised patch.\n\n-Brad\n\n t/t1400-update-ref.sh | 632 ++++++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 632 insertions(+)\n\ndiff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh\nindex e415ee0..6ffd82f 100755\n--- a/t/t1400-update-ref.sh\n+++ b/t/t1400-update-ref.sh\n@@ -302,4 +302,636 @@ test_expect_success \\\n \t'git cat-file blob master@{2005-05-26 23:42}:F (expect OTHER)' \\\n \t'test OTHER = $(git cat-file blob \"master@{2005-05-26 23:42}:F\")'\n \n+a=refs/heads/a\n+b=refs/heads/b\n+c=refs/heads/c\n+E='\"\"'\n+F='%s\\0'\n+pws='path with space'\n+\n+test_expect_success 'stdin test setup' '\n+\techo \"$pws\" >\"$pws\" &&\n+\tgit add -- \"$pws\" &&\n+\tgit commit -m \"$pws\"\n+'\n+\n+test_expect_success '-z fails without --stdin' '\n+\ttest_must_fail git update-ref -z $m $m $m 2>err &&\n+\tgrep \"usage: git update-ref\" err\n+'\n+\n+test_expect_success 'stdin works with no input' '\n+\t>stdin &&\n+\tgit update-ref --stdin <stdin &&\n+\tgit rev-parse --verify -q $m\n+'\n+\n+test_expect_success 'stdin fails on empty line' '\n+\techo \"\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: empty command in input\" err\n+'\n+\n+test_expect_success 'stdin fails on only whitespace' '\n+\techo \" \" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: whitespace before command:  \" err\n+'\n+\n+test_expect_success 'stdin fails on leading whitespace' '\n+\techo \" create $a $m\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: whitespace before command:  create $a $m\" err\n+'\n+\n+test_expect_success 'stdin fails on unknown command' '\n+\techo \"unknown $a\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: unknown command: unknown $a\" err\n+'\n+\n+test_expect_success 'stdin fails on badly quoted input' '\n+\techo \"create $a \\\"master\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: badly quoted argument: \\\\\\\"master\" err\n+'\n+\n+test_expect_success 'stdin fails on arguments not separated by space' '\n+\techo \"create \\\"$a\\\"master\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: expected SP but got: master\" err\n+'\n+\n+test_expect_success 'stdin fails create with no ref' '\n+\techo \"create \" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: create line missing <ref>\" err\n+'\n+\n+test_expect_success 'stdin fails create with bad ref name' '\n+\techo \"create ~a $m\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: invalid ref format: ~a\" err\n+'\n+\n+test_expect_success 'stdin fails create with no new value' '\n+\techo \"create $a\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: create $a missing <newvalue>\" err\n+'\n+\n+test_expect_success 'stdin fails create with too many arguments' '\n+\techo \"create $a $m $m\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: create $a has extra input:  $m\" err\n+'\n+\n+test_expect_success 'stdin fails update with no ref' '\n+\techo \"update \" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: update line missing <ref>\" err\n+'\n+\n+test_expect_success 'stdin fails update with bad ref name' '\n+\techo \"update ~a $m\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: invalid ref format: ~a\" err\n+'\n+\n+test_expect_success 'stdin fails update with no new value' '\n+\techo \"update $a\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: update $a missing <newvalue>\" err\n+'\n+\n+test_expect_success 'stdin fails update with too many arguments' '\n+\techo \"update $a $m $m $m\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: update $a has extra input:  $m\" err\n+'\n+\n+test_expect_success 'stdin fails delete with no ref' '\n+\techo \"delete \" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: delete line missing <ref>\" err\n+'\n+\n+test_expect_success 'stdin fails delete with bad ref name' '\n+\techo \"delete ~a $m\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: invalid ref format: ~a\" err\n+'\n+\n+test_expect_success 'stdin fails delete with too many arguments' '\n+\techo \"delete $a $m $m\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: delete $a has extra input:  $m\" err\n+'\n+\n+test_expect_success 'stdin fails verify with too many arguments' '\n+\techo \"verify $a $m $m\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: verify $a has extra input:  $m\" err\n+'\n+\n+test_expect_success 'stdin fails option with unknown name' '\n+\techo \"option unknown\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: option unknown: unknown\" err\n+'\n+\n+test_expect_success 'stdin fails with duplicate refs' '\n+\tcat >stdin <<-EOF &&\n+\tcreate $a $m\n+\tcreate $b $m\n+\tcreate $a $m\n+\tEOF\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: Multiple updates for ref '\"'\"'$a'\"'\"' not allowed.\" err\n+'\n+\n+test_expect_success 'stdin create ref works' '\n+\techo \"create $a $m\" >stdin &&\n+\tgit update-ref --stdin <stdin &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin update ref creates with zero old value' '\n+\techo \"update $b $m $Z\" >stdin &&\n+\tgit update-ref --stdin <stdin &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual &&\n+\tgit update-ref -d $b\n+'\n+\n+test_expect_success 'stdin update ref creates with empty old value' '\n+\techo \"update $b $m $E\" >stdin &&\n+\tgit update-ref --stdin <stdin &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin create ref works with path with space to blob' '\n+\techo \"create refs/blobs/pws \\\"$m:$pws\\\"\" >stdin &&\n+\tgit update-ref --stdin <stdin &&\n+\tgit rev-parse \"$m:$pws\" >expect &&\n+\tgit rev-parse refs/blobs/pws >actual &&\n+\ttest_cmp expect actual &&\n+\tgit update-ref -d refs/blobs/pws\n+'\n+\n+test_expect_success 'stdin update ref fails with wrong old value' '\n+\techo \"update $c $m $m~1\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: Cannot lock the ref '\"'\"'$c'\"'\"'\" err &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin update ref fails with bad old value' '\n+\techo \"update $c $m does-not-exist\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: invalid old value for ref $c: does-not-exist\" err &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin create ref fails with bad new value' '\n+\techo \"create $c does-not-exist\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: invalid new value for ref $c: does-not-exist\" err &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin create ref fails with zero new value' '\n+\techo \"create $c \" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: create $c given zero new value\" err &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin update ref works with right old value' '\n+\techo \"update $b $m~1 $m\" >stdin &&\n+\tgit update-ref --stdin <stdin &&\n+\tgit rev-parse $m~1 >expect &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin delete ref fails with wrong old value' '\n+\techo \"delete $a $m~1\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: Cannot lock the ref '\"'\"'$a'\"'\"'\" err &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin delete ref fails with zero old value' '\n+\techo \"delete $a \" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: delete $a given zero old value\" err &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin update symref works option no-deref' '\n+\tgit symbolic-ref TESTSYMREF $b &&\n+\tcat >stdin <<-EOF &&\n+\toption no-deref\n+\tupdate TESTSYMREF $a $b\n+\tEOF\n+\tgit update-ref --stdin <stdin &&\n+\tgit rev-parse TESTSYMREF >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual &&\n+\tgit rev-parse $m~1 >expect &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin delete symref works option no-deref' '\n+\tgit symbolic-ref TESTSYMREF $b &&\n+\tcat >stdin <<-EOF &&\n+\toption no-deref\n+\tdelete TESTSYMREF $b\n+\tEOF\n+\tgit update-ref --stdin <stdin &&\n+\ttest_must_fail git rev-parse --verify -q TESTSYMREF &&\n+\tgit rev-parse $m~1 >expect &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin delete ref works with right old value' '\n+\techo \"delete $b $m~1\" >stdin &&\n+\tgit update-ref --stdin <stdin &&\n+\ttest_must_fail git rev-parse --verify -q $b\n+'\n+\n+test_expect_success 'stdin update/create/verify combination works' '\n+\tcat >stdin <<-EOF &&\n+\tupdate $a $m\n+\tcreate $b $m\n+\tverify $c\n+\tEOF\n+\tgit update-ref --stdin <stdin &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin update refs works with identity updates' '\n+\tcat >stdin <<-EOF &&\n+\tupdate $a $m $m\n+\tupdate $b $m $m\n+\tupdate $c $Z $E\n+\tEOF\n+\tgit update-ref --stdin <stdin &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin update refs fails with wrong old value' '\n+\tgit update-ref $c $m &&\n+\tcat >stdin <<-EOF &&\n+\tupdate $a $m $m\n+\tupdate $b $m $m\n+\tupdate $c  ''\n+\tEOF\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: Cannot lock the ref '\"'\"'$c'\"'\"'\" err &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual &&\n+\tgit rev-parse $c >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin delete refs works with packed and loose refs' '\n+\tgit pack-refs --all &&\n+\tgit update-ref $c $m~1 &&\n+\tcat >stdin <<-EOF &&\n+\tdelete $a $m\n+\tupdate $b $Z $m\n+\tupdate $c $E $m~1\n+\tEOF\n+\tgit update-ref --stdin <stdin &&\n+\ttest_must_fail git rev-parse --verify -q $a &&\n+\ttest_must_fail git rev-parse --verify -q $b &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin -z works on empty input' '\n+\t>stdin &&\n+\tgit update-ref -z --stdin <stdin &&\n+\tgit rev-parse --verify -q $m\n+'\n+\n+test_expect_success 'stdin -z fails on empty line' '\n+\techo \"\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: whitespace before command: \" err\n+'\n+\n+test_expect_success 'stdin -z fails on empty command' '\n+\tprintf $F \"\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: empty command in input\" err\n+'\n+\n+test_expect_success 'stdin -z fails on only whitespace' '\n+\tprintf $F \" \" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: whitespace before command:  \" err\n+'\n+\n+test_expect_success 'stdin -z fails on leading whitespace' '\n+\tprintf $F \" create $a\" \"$m\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: whitespace before command:  create $a\" err\n+'\n+\n+test_expect_success 'stdin -z fails on unknown command' '\n+\tprintf $F \"unknown $a\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: unknown command: unknown $a\" err\n+'\n+\n+test_expect_success 'stdin -z fails create with no ref' '\n+\tprintf $F \"create \" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: create line missing <ref>\" err\n+'\n+\n+test_expect_success 'stdin -z fails create with bad ref name' '\n+\tprintf $F \"create ~a \" \"$m\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: invalid ref format: ~a \" err\n+'\n+\n+test_expect_success 'stdin -z fails create with no new value' '\n+\tprintf $F \"create $a\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: create $a missing <newvalue>\" err\n+'\n+\n+test_expect_success 'stdin -z fails create with too many arguments' '\n+\tprintf $F \"create $a\" \"$m\" \"$m\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: unknown command: $m\" err\n+'\n+\n+test_expect_success 'stdin -z fails update with no ref' '\n+\tprintf $F \"update \" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: update line missing <ref>\" err\n+'\n+\n+test_expect_success 'stdin -z fails update with bad ref name' '\n+\tprintf $F \"update ~a\" \"$m\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: invalid ref format: ~a\" err\n+'\n+\n+test_expect_success 'stdin -z fails update with no new value' '\n+\tprintf $F \"update $a\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: update $a missing <newvalue>\" err\n+'\n+\n+test_expect_success 'stdin -z fails update with no old value' '\n+\tprintf $F \"update $a\" \"$m\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: update $a missing \\\\[<oldvalue>\\\\] NUL\" err\n+'\n+\n+test_expect_success 'stdin -z fails update with too many arguments' '\n+\tprintf $F \"update $a\" \"$m\" \"$m\" \"$m\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: unknown command: $m\" err\n+'\n+\n+test_expect_success 'stdin -z fails delete with no ref' '\n+\tprintf $F \"delete \" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: delete line missing <ref>\" err\n+'\n+\n+test_expect_success 'stdin -z fails delete with bad ref name' '\n+\tprintf $F \"delete ~a\" \"$m\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: invalid ref format: ~a\" err\n+'\n+\n+test_expect_success 'stdin -z fails delete with no old value' '\n+\tprintf $F \"delete $a\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: delete $a missing \\\\[<oldvalue>\\\\] NUL\" err\n+'\n+\n+test_expect_success 'stdin -z fails delete with too many arguments' '\n+\tprintf $F \"delete $a\" \"$m\" \"$m\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: unknown command: $m\" err\n+'\n+\n+test_expect_success 'stdin -z fails verify with too many arguments' '\n+\tprintf $F \"verify $a\" \"$m\" \"$m\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: unknown command: $m\" err\n+'\n+\n+test_expect_success 'stdin -z fails verify with no old value' '\n+\tprintf $F \"verify $a\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: verify $a missing \\\\[<oldvalue>\\\\] NUL\" err\n+'\n+\n+test_expect_success 'stdin -z fails option with unknown name' '\n+\tprintf $F \"option unknown\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: option unknown: unknown\" err\n+'\n+\n+test_expect_success 'stdin -z fails with duplicate refs' '\n+\tprintf $F \"create $a\" \"$m\" \"create $b\" \"$m\" \"create $a\" \"$m\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: Multiple updates for ref '\"'\"'$a'\"'\"' not allowed.\" err\n+'\n+\n+test_expect_success 'stdin -z create ref works' '\n+\tprintf $F \"create $a\" \"$m\" >stdin &&\n+\tgit update-ref -z --stdin <stdin &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin -z update ref creates with zero old value' '\n+\tprintf $F \"update $b\" \"$m\" \"$Z\" >stdin &&\n+\tgit update-ref -z --stdin <stdin &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual &&\n+\tgit update-ref -d $b\n+'\n+\n+test_expect_success 'stdin -z update ref creates with empty old value' '\n+\tprintf $F \"update $b\" \"$m\" \"\" >stdin &&\n+\tgit update-ref -z --stdin <stdin &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin -z create ref works with path with space to blob' '\n+\tprintf $F \"create refs/blobs/pws\" \"$m:$pws\" >stdin &&\n+\tgit update-ref -z --stdin <stdin &&\n+\tgit rev-parse \"$m:$pws\" >expect &&\n+\tgit rev-parse refs/blobs/pws >actual &&\n+\ttest_cmp expect actual &&\n+\tgit update-ref -d refs/blobs/pws\n+'\n+\n+test_expect_success 'stdin -z update ref fails with wrong old value' '\n+\tprintf $F \"update $c\" \"$m\" \"$m~1\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: Cannot lock the ref '\"'\"'$c'\"'\"'\" err &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin -z update ref fails with bad old value' '\n+\tprintf $F \"update $c\" \"$m\" \"does-not-exist\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: invalid old value for ref $c: does-not-exist\" err &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin -z create ref fails with bad new value' '\n+\tprintf $F \"create $c\" \"does-not-exist\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: invalid new value for ref $c: does-not-exist\" err &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin -z create ref fails with zero new value' '\n+\tprintf $F \"create $c\" \"\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: create $c given zero new value\" err &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin -z update ref works with right old value' '\n+\tprintf $F \"update $b\" \"$m~1\" \"$m\" >stdin &&\n+\tgit update-ref -z --stdin <stdin &&\n+\tgit rev-parse $m~1 >expect &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin -z delete ref fails with wrong old value' '\n+\tprintf $F \"delete $a\" \"$m~1\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: Cannot lock the ref '\"'\"'$a'\"'\"'\" err &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin -z delete ref fails with zero old value' '\n+\tprintf $F \"delete $a\" \"$Z\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: delete $a given zero old value\" err &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin -z update symref works option no-deref' '\n+\tgit symbolic-ref TESTSYMREF $b &&\n+\tprintf $F \"option no-deref\" \"update TESTSYMREF\" \"$a\" \"$b\" >stdin &&\n+\tgit update-ref -z --stdin <stdin &&\n+\tgit rev-parse TESTSYMREF >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual &&\n+\tgit rev-parse $m~1 >expect &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin -z delete symref works option no-deref' '\n+\tgit symbolic-ref TESTSYMREF $b &&\n+\tprintf $F \"option no-deref\" \"delete TESTSYMREF\" \"$b\" >stdin &&\n+\tgit update-ref -z --stdin <stdin &&\n+\ttest_must_fail git rev-parse --verify -q TESTSYMREF &&\n+\tgit rev-parse $m~1 >expect &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin -z delete ref works with right old value' '\n+\tprintf $F \"delete $b\" \"$m~1\" >stdin &&\n+\tgit update-ref -z --stdin <stdin &&\n+\ttest_must_fail git rev-parse --verify -q $b\n+'\n+\n+test_expect_success 'stdin -z update/create/verify combination works' '\n+\tprintf $F \"update $a\" \"$m\" \"\" \"create $b\" \"$m\" \"verify $c\" \"\" >stdin &&\n+\tgit update-ref -z --stdin <stdin &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin -z update refs works with identity updates' '\n+\tprintf $F \"update $a\" \"$m\" \"$m\" \"update $b\" \"$m\" \"$m\" \"update $c\" \"$Z\" \"\" >stdin &&\n+\tgit update-ref -z --stdin <stdin &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success 'stdin -z update refs fails with wrong old value' '\n+\tgit update-ref $c $m &&\n+\tprintf $F \"update $a\" \"$m\" \"$m\" \"update $b\" \"$m\" \"$m\" \"update $c\" \"\" \"$Z\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: Cannot lock the ref '\"'\"'$c'\"'\"'\" err &&\n+\tgit rev-parse $m >expect &&\n+\tgit rev-parse $a >actual &&\n+\ttest_cmp expect actual &&\n+\tgit rev-parse $b >actual &&\n+\ttest_cmp expect actual &&\n+\tgit rev-parse $c >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin -z delete refs works with packed and loose refs' '\n+\tgit pack-refs --all &&\n+\tgit update-ref $c $m~1 &&\n+\tprintf $F \"delete $a\" \"$m\" \"update $b\" \"$Z\" \"$m\" \"update $c\" \"\" \"$m~1\" >stdin &&\n+\tgit update-ref -z --stdin <stdin &&\n+\ttest_must_fail git rev-parse --verify -q $a &&\n+\ttest_must_fail git rev-parse --verify -q $b &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n test_done\n-- \n1.8.4.rc3\n"},{"id":"227456","messageId":"CAPig+cT6XmcpQLQWyC_mU0sHRGNb87QUVpzNG2HBhmOCx12KTg@mail.gmail.com","threadId":"34797","inReplyTo":"523063D7.7000305@kitware.com","subject":"Re: [PATCH v6 7/8] update-ref: support multiple simultaneous updates","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2013-09-11T16:07:56Z","receivedAt":"2013-09-11T16:07:56Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Wed, Sep 11, 2013 at 8:36 AM, Brad King <brad.king@kitware.com> wrote:\n> On 09/10/2013 06:51 PM, Eric Sunshine wrote:\n>> On Mon, Sep 9, 2013 at 8:57 PM, Brad King <brad.king@kitware.com> wrote:\n>>> +Use 40 \"0\" or the empty string to specify a zero value, except that\n>>\n>> Did you want an 's' after the \"0\"?\n>\n> The same description without 's' already appears in git-update-ref.txt\n> above this location in the existing documentation of the command-line\n\nThanks for the explanation. (I could have checked the surrounding text\nbut didn't think to do so.)\n\n> option behavior.  I see 0{40} in git-receive-pack.txt and also in\n> howto/update-hook-example.txt.  Perhaps a follow-up change can be made\n> to choose a consistent way to describe 40 0s.\n>\n> -Brad\n"}]}