{"thread":{"id":"39559","subject":"[PATCH 01/13] delete_ref(): move declaration to refs.h","startedAt":"2015-06-08T11:45:34Z","lastAt":"2015-06-13T14:30:50Z","messageCount":30,"participants":["Michael Haggerty","Stefan Beller","Jeff King","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":13},"messages":[{"id":"263194","messageId":"cover.1433763494.git.mhagger@alum.mit.edu","threadId":"39559","inReplyTo":null,"subject":"[PATCH 00/13] Improve \"refs\" module encapsulation","fromName":"Michael Haggerty","fromEmail":"mhagger@alum.mit.edu","sentAt":"2015-06-08T11:45:34Z","receivedAt":"2015-06-08T11:45:34Z","isPatch":true,"sender":{"key":"mhagger@alum.mit.edu","avatar":"https://avatars.githubusercontent.com/u/119718?v=4"},"body":"Add functions to the reference API to\n\n* Delete a bunch of references at once, but *without* failing the\n  whole transaction if one of the deletions fails. This functionality\n  is used by `git remote remove` and `git remote prune`.\n\n* Create initial references during `git clone`. (During clone,\n  references are written directly to the `packed-refs` file without\n  any locking.)\n\nAlso move the remaining \"refs\" function declarations from `cache.h` to\n`refs.h`.\n\nThis improves the encapsulation of the refs module. Especially, it\nmeans that code outside of the refs module should no longer need to\ncare about the distinction between loose and packed references.\n\nThese patches are also available from my GitHub account [1] as branch\n\"init-delete-refs-api\".\n\n[1] https://github.com/mhagger/git\n\nMichael Haggerty (13):\n  delete_ref(): move declaration to refs.h\n  remove_branches(): remove temporary\n  delete_ref(): handle special case more explicitly\n  delete_refs(): new function for the refs API\n  delete_refs(): improve error message\n  delete_refs(): convert error message to lower case\n  prune_remote(): use delete_refs()\n  repack_without_refs(): make function private\n  initial_ref_transaction_commit(): function for initial ref creation\n  refs: remove some functions from the module's public interface\n  initial_ref_transaction_commit(): check for duplicate refs\n  initial_ref_transaction_commit(): check for ref D/F conflicts\n  refs: move the remaining ref module declarations to refs.h\n\n archive.c               |   1 +\n builtin/blame.c         |   1 +\n builtin/clone.c         |  19 ++++-\n builtin/fast-export.c   |   1 +\n builtin/fmt-merge-msg.c |   1 +\n builtin/init-db.c       |   1 +\n builtin/log.c           |   1 +\n builtin/remote.c        |  33 +-------\n cache.h                 |  68 ----------------\n refs.c                  | 167 +++++++++++++++++++++++++++++++++++---\n refs.h                  | 210 +++++++++++++++++++++++++++++++-----------------\n remote-testsvn.c        |   1 +\n 12 files changed, 316 insertions(+), 188 deletions(-)\n\n-- \n2.1.4\n"},{"id":"263189","messageId":"2eea274d60e355e4944ca9ed400e6c2132b4ed32.1433763494.git.mhagger@alum.mit.edu","threadId":"39559","inReplyTo":"cover.1433763494.git.mhagger@alum.mit.edu","subject":"[PATCH 01/13] delete_ref(): move declaration to refs.h","fromName":"Michael Haggerty","fromEmail":"mhagger@alum.mit.edu","sentAt":"2015-06-08T11:45:35Z","receivedAt":"2015-06-08T11:45:35Z","isPatch":true,"sender":{"key":"mhagger@alum.mit.edu","avatar":"https://avatars.githubusercontent.com/u/119718?v=4"},"body":"Also\n\n* Add a docstring\n\n* Rename the second parameter to \"old_sha1\", to be consistent with the\n  convention used elsewhere in the refs module\n\nSigned-off-by: Michael Haggerty <mhagger@alum.mit.edu>\n---\n cache.h | 2 --\n refs.c  | 5 +++--\n refs.h  | 9 +++++++++\n 3 files changed, 12 insertions(+), 4 deletions(-)\n\ndiff --git a/cache.h b/cache.h\nindex 571c98f..be92121 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -585,8 +585,6 @@ extern void update_index_if_able(struct index_state *, struct lock_file *);\n extern int hold_locked_index(struct lock_file *, int);\n extern void set_alternate_index_output(const char *);\n \n-extern int delete_ref(const char *, const unsigned char *sha1, unsigned int flags);\n-\n /* Environment bits from configuration mechanism */\n extern int trust_executable_bit;\n extern int trust_ctime;\ndiff --git a/refs.c b/refs.c\nindex a742d79..b575bb8 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2789,7 +2789,8 @@ static int delete_ref_loose(struct ref_lock *lock, int flag, struct strbuf *err)\n \treturn 0;\n }\n \n-int delete_ref(const char *refname, const unsigned char *sha1, unsigned int flags)\n+int delete_ref(const char *refname, const unsigned char *old_sha1,\n+\t       unsigned int flags)\n {\n \tstruct ref_transaction *transaction;\n \tstruct strbuf err = STRBUF_INIT;\n@@ -2797,7 +2798,7 @@ int delete_ref(const char *refname, const unsigned char *sha1, unsigned int flag\n \ttransaction = ref_transaction_begin(&err);\n \tif (!transaction ||\n \t    ref_transaction_delete(transaction, refname,\n-\t\t\t\t   (sha1 && !is_null_sha1(sha1)) ? sha1 : NULL,\n+\t\t\t\t   (old_sha1 && !is_null_sha1(old_sha1)) ? old_sha1 : NULL,\n \t\t\t\t   flags, NULL, &err) ||\n \t    ref_transaction_commit(transaction, &err)) {\n \t\terror(\"%s\", err.buf);\ndiff --git a/refs.h b/refs.h\nindex 8c3d433..8785bca 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -202,6 +202,15 @@ extern int read_ref_at(const char *refname, unsigned int flags,\n /** Check if a particular reflog exists */\n extern int reflog_exists(const char *refname);\n \n+/*\n+ * Delete the specified reference. If old_sha1 is non-NULL and not\n+ * NULL_SHA1, then verify that the current value of the reference is\n+ * old_sha1 before deleting it. flags is passed to\n+ * ref_transaction_delete().\n+ */\n+extern int delete_ref(const char *refname, const unsigned char *old_sha1,\n+\t\t      unsigned int flags);\n+\n /** Delete a reflog */\n extern int delete_reflog(const char *refname);\n \n-- \n2.1.4\n"},{"id":"263191","messageId":"77713838b598b95c5973a1fe73569635f5f31ee9.1433763494.git.mhagger@alum.mit.edu","threadId":"39559","inReplyTo":"cover.1433763494.git.mhagger@alum.mit.edu","subject":"[PATCH 02/13] remove_branches(): remove temporary","fromName":"Michael Haggerty","fromEmail":"mhagger@alum.mit.edu","sentAt":"2015-06-08T11:45:36Z","receivedAt":"2015-06-08T11:45:36Z","isPatch":true,"sender":{"key":"mhagger@alum.mit.edu","avatar":"https://avatars.githubusercontent.com/u/119718?v=4"},"body":"Signed-off-by: Michael Haggerty <mhagger@alum.mit.edu>\n---\n builtin/remote.c | 3 +--\n 1 file changed, 1 insertion(+), 2 deletions(-)\n\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex f4a6ec9..53b8e13 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -756,8 +756,7 @@ static int remove_branches(struct string_list *branches)\n \tstrbuf_release(&err);\n \n \tfor (i = 0; i < branches->nr; i++) {\n-\t\tstruct string_list_item *item = branches->items + i;\n-\t\tconst char *refname = item->string;\n+\t\tconst char *refname = branches->items[i].string;\n \n \t\tif (delete_ref(refname, NULL, 0))\n \t\t\tresult |= error(_(\"Could not remove branch %s\"), refname);\n-- \n2.1.4\n"},{"id":"263190","messageId":"1a8670c63b4e8b3b2f99d0e1acbb18162111f166.1433763494.git.mhagger@alum.mit.edu","threadId":"39559","inReplyTo":"cover.1433763494.git.mhagger@alum.mit.edu","subject":"[PATCH 03/13] delete_ref(): handle special case more explicitly","fromName":"Michael Haggerty","fromEmail":"mhagger@alum.mit.edu","sentAt":"2015-06-08T11:45:37Z","receivedAt":"2015-06-08T11:45:37Z","isPatch":true,"sender":{"key":"mhagger@alum.mit.edu","avatar":"https://avatars.githubusercontent.com/u/119718?v=4"},"body":"delete_ref() uses a different convention for its old_sha1 parameter\nthan, say, ref_transaction_delete(): NULL_SHA1 means not to check the\nold value. Make this fact a little bit clearer in the code by handling\nit in explicit, commented code rather than burying it in a conditional\nexpression.\n\nSigned-off-by: Michael Haggerty <mhagger@alum.mit.edu>\n---\n refs.c | 7 +++++--\n 1 file changed, 5 insertions(+), 2 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex b575bb8..f9d87b6 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2795,10 +2795,13 @@ int delete_ref(const char *refname, const unsigned char *old_sha1,\n \tstruct ref_transaction *transaction;\n \tstruct strbuf err = STRBUF_INIT;\n \n+\t/* Treat NULL_SHA1 as \"don't care\" */\n+\tif (old_sha1 && is_null_sha1(old_sha1))\n+\t\told_sha1 = NULL;\n+\n \ttransaction = ref_transaction_begin(&err);\n \tif (!transaction ||\n-\t    ref_transaction_delete(transaction, refname,\n-\t\t\t\t   (old_sha1 && !is_null_sha1(old_sha1)) ? old_sha1 : NULL,\n+\t    ref_transaction_delete(transaction, refname, old_sha1,\n \t\t\t\t   flags, NULL, &err) ||\n \t    ref_transaction_commit(transaction, &err)) {\n \t\terror(\"%s\", err.buf);\n-- \n2.1.4\n"},{"id":"263198","messageId":"e6897bee0aba656cf9d287c4c8eae5a1ed52e4b3.1433763494.git.mhagger@alum.mit.edu","threadId":"39559","inReplyTo":"cover.1433763494.git.mhagger@alum.mit.edu","subject":"[PATCH 04/13] delete_refs(): new function for the refs API","fromName":"Michael Haggerty","fromEmail":"mhagger@alum.mit.edu","sentAt":"2015-06-08T11:45:38Z","receivedAt":"2015-06-08T11:45:38Z","isPatch":true,"sender":{"key":"mhagger@alum.mit.edu","avatar":"https://avatars.githubusercontent.com/u/119718?v=4"},"body":"Move the function remove_branches() from builtin/remote.c to refs.c,\nrename it to delete_refs(), and make it public.\n\nSigned-off-by: Michael Haggerty <mhagger@alum.mit.edu>\n---\n builtin/remote.c | 21 +--------------------\n refs.c           | 19 +++++++++++++++++++\n refs.h           |  7 +++++++\n 3 files changed, 27 insertions(+), 20 deletions(-)\n\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 53b8e13..c8dc724 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -746,25 +746,6 @@ static int mv(int argc, const char **argv)\n \treturn 0;\n }\n \n-static int remove_branches(struct string_list *branches)\n-{\n-\tstruct strbuf err = STRBUF_INIT;\n-\tint i, result = 0;\n-\n-\tif (repack_without_refs(branches, &err))\n-\t\tresult |= error(\"%s\", err.buf);\n-\tstrbuf_release(&err);\n-\n-\tfor (i = 0; i < branches->nr; i++) {\n-\t\tconst char *refname = branches->items[i].string;\n-\n-\t\tif (delete_ref(refname, NULL, 0))\n-\t\t\tresult |= error(_(\"Could not remove branch %s\"), refname);\n-\t}\n-\n-\treturn result;\n-}\n-\n static int rm(int argc, const char **argv)\n {\n \tstruct option options[] = {\n@@ -821,7 +802,7 @@ static int rm(int argc, const char **argv)\n \tstrbuf_release(&buf);\n \n \tif (!result)\n-\t\tresult = remove_branches(&branches);\n+\t\tresult = delete_refs(&branches);\n \tstring_list_clear(&branches, 0);\n \n \tif (skipped.nr) {\ndiff --git a/refs.c b/refs.c\nindex f9d87b6..c413282 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2814,6 +2814,25 @@ int delete_ref(const char *refname, const unsigned char *old_sha1,\n \treturn 0;\n }\n \n+int delete_refs(struct string_list *refnames)\n+{\n+\tstruct strbuf err = STRBUF_INIT;\n+\tint i, result = 0;\n+\n+\tif (repack_without_refs(refnames, &err))\n+\t\tresult |= error(\"%s\", err.buf);\n+\tstrbuf_release(&err);\n+\n+\tfor (i = 0; i < refnames->nr; i++) {\n+\t\tconst char *refname = refnames->items[i].string;\n+\n+\t\tif (delete_ref(refname, NULL, 0))\n+\t\t\tresult |= error(_(\"Could not remove branch %s\"), refname);\n+\t}\n+\n+\treturn result;\n+}\n+\n /*\n  * People using contrib's git-new-workdir have .git/logs/refs ->\n  * /some/other/path/.git/logs/refs, and that may live on another device.\ndiff --git a/refs.h b/refs.h\nindex 8785bca..9b75b9f 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -211,6 +211,13 @@ extern int reflog_exists(const char *refname);\n extern int delete_ref(const char *refname, const unsigned char *old_sha1,\n \t\t      unsigned int flags);\n \n+/*\n+ * Delete the specified references. If there are any problems, emit\n+ * errors but attempt to keep going (i.e., the deletes are not done in\n+ * an all-or-nothing transaction).\n+ */\n+extern int delete_refs(struct string_list *refnames);\n+\n /** Delete a reflog */\n extern int delete_reflog(const char *refname);\n \n-- \n2.1.4\n"},{"id":"263199","messageId":"9f6f3432467c2b12329c16a46c52670f772993e5.1433763494.git.mhagger@alum.mit.edu","threadId":"39559","inReplyTo":"cover.1433763494.git.mhagger@alum.mit.edu","subject":"[PATCH 05/13] delete_refs(): improve error message","fromName":"Michael Haggerty","fromEmail":"mhagger@alum.mit.edu","sentAt":"2015-06-08T11:45:39Z","receivedAt":"2015-06-08T11:45:39Z","isPatch":true,"sender":{"key":"mhagger@alum.mit.edu","avatar":"https://avatars.githubusercontent.com/u/119718?v=4"},"body":"Change the error message from\n\n    Could not remove branch %s\n\nto\n\n    Could not remove reference %s\n\nThis change makes sense even for the existing caller, which uses the\nfunction to delete remote-tracking branches.\n\nSigned-off-by: Michael Haggerty <mhagger@alum.mit.edu>\n---\n refs.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/refs.c b/refs.c\nindex c413282..2a2a06d 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2827,7 +2827,7 @@ int delete_refs(struct string_list *refnames)\n \t\tconst char *refname = refnames->items[i].string;\n \n \t\tif (delete_ref(refname, NULL, 0))\n-\t\t\tresult |= error(_(\"Could not remove branch %s\"), refname);\n+\t\t\tresult |= error(_(\"Could not remove reference %s\"), refname);\n \t}\n \n \treturn result;\n-- \n2.1.4\n"},{"id":"263192","messageId":"49bd60449c003c8a7fd060095fe743bb9efaba99.1433763494.git.mhagger@alum.mit.edu","threadId":"39559","inReplyTo":"cover.1433763494.git.mhagger@alum.mit.edu","subject":"[PATCH 06/13] delete_refs(): convert error message to lower case","fromName":"Michael Haggerty","fromEmail":"mhagger@alum.mit.edu","sentAt":"2015-06-08T11:45:40Z","receivedAt":"2015-06-08T11:45:40Z","isPatch":true,"sender":{"key":"mhagger@alum.mit.edu","avatar":"https://avatars.githubusercontent.com/u/119718?v=4"},"body":"This string is going to have to be re-internationalized anyway because\nof the previous commit. So while we're at it, we might as well convert\nit to lower case as per our usual practice.\n\nSigned-off-by: Michael Haggerty <mhagger@alum.mit.edu>\n---\n refs.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/refs.c b/refs.c\nindex 2a2a06d..a10aba8 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2827,7 +2827,7 @@ int delete_refs(struct string_list *refnames)\n \t\tconst char *refname = refnames->items[i].string;\n \n \t\tif (delete_ref(refname, NULL, 0))\n-\t\t\tresult |= error(_(\"Could not remove reference %s\"), refname);\n+\t\t\tresult |= error(_(\"could not remove reference %s\"), refname);\n \t}\n \n \treturn result;\n-- \n2.1.4\n"},{"id":"263193","messageId":"2ce5b2c64d002ce649c2192bacdf505c4921caa6.1433763494.git.mhagger@alum.mit.edu","threadId":"39559","inReplyTo":"cover.1433763494.git.mhagger@alum.mit.edu","subject":"[PATCH 07/13] prune_remote(): use delete_refs()","fromName":"Michael Haggerty","fromEmail":"mhagger@alum.mit.edu","sentAt":"2015-06-08T11:45:41Z","receivedAt":"2015-06-08T11:45:41Z","isPatch":true,"sender":{"key":"mhagger@alum.mit.edu","avatar":"https://avatars.githubusercontent.com/u/119718?v=4"},"body":"This will result in errors being emitted for references that can't be\ndeleted, but that is a good thing.\n\nSigned-off-by: Michael Haggerty <mhagger@alum.mit.edu>\n---\n builtin/remote.c | 11 ++---------\n 1 file changed, 2 insertions(+), 9 deletions(-)\n\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex c8dc724..cc3c741 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -1314,19 +1314,12 @@ static int prune_remote(const char *remote, int dry_run)\n \t\tstring_list_append(&refs_to_prune, item->util);\n \tstring_list_sort(&refs_to_prune);\n \n-\tif (!dry_run) {\n-\t\tstruct strbuf err = STRBUF_INIT;\n-\t\tif (repack_without_refs(&refs_to_prune, &err))\n-\t\t\tresult |= error(\"%s\", err.buf);\n-\t\tstrbuf_release(&err);\n-\t}\n+\tif (!dry_run)\n+\t\tresult |= delete_refs(&refs_to_prune);\n \n \tfor_each_string_list_item(item, &states.stale) {\n \t\tconst char *refname = item->util;\n \n-\t\tif (!dry_run)\n-\t\t\tresult |= delete_ref(refname, NULL, 0);\n-\n \t\tif (dry_run)\n \t\t\tprintf_ln(_(\" * [would prune] %s\"),\n \t\t\t       abbrev_ref(refname, \"refs/remotes/\"));\n-- \n2.1.4\n"},{"id":"263202","messageId":"7140f87b1c1d42108e875163344bdec668d221fa.1433763494.git.mhagger@alum.mit.edu","threadId":"39559","inReplyTo":"cover.1433763494.git.mhagger@alum.mit.edu","subject":"[PATCH 08/13] repack_without_refs(): make function private","fromName":"Michael Haggerty","fromEmail":"mhagger@alum.mit.edu","sentAt":"2015-06-08T11:45:42Z","receivedAt":"2015-06-08T11:45:42Z","isPatch":true,"sender":{"key":"mhagger@alum.mit.edu","avatar":"https://avatars.githubusercontent.com/u/119718?v=4"},"body":"It is no longer called from outside of the refs module. Also move its\ndocstring and change it to imperative voice.\n\nSigned-off-by: Michael Haggerty <mhagger@alum.mit.edu>\n---\n refs.c |  9 ++++++++-\n refs.h | 11 -----------\n 2 files changed, 8 insertions(+), 12 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex a10aba8..dc17984 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2724,7 +2724,14 @@ int pack_refs(unsigned int flags)\n \treturn 0;\n }\n \n-int repack_without_refs(struct string_list *refnames, struct strbuf *err)\n+/*\n+ * Rewrite the packed-refs file, omitting any refs listed in\n+ * 'refnames'. On error, leave packed-refs unchanged, write an error\n+ * message to 'err', and return a nonzero value.\n+ *\n+ * The refs in 'refnames' needn't be sorted. `err` must not be NULL.\n+ */\n+static int repack_without_refs(struct string_list *refnames, struct strbuf *err)\n {\n \tstruct ref_dir *packed;\n \tstruct string_list_item *refname;\ndiff --git a/refs.h b/refs.h\nindex 9b75b9f..3420c98 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -154,17 +154,6 @@ extern void rollback_packed_refs(void);\n  */\n int pack_refs(unsigned int flags);\n \n-/*\n- * Rewrite the packed-refs file, omitting any refs listed in\n- * 'refnames'. On error, packed-refs will be unchanged, the return\n- * value is nonzero, and a message about the error is written to the\n- * 'err' strbuf.\n- *\n- * The refs in 'refnames' needn't be sorted. `err` must not be NULL.\n- */\n-extern int repack_without_refs(struct string_list *refnames,\n-\t\t\t       struct strbuf *err);\n-\n extern int ref_exists(const char *);\n \n extern int is_branch(const char *refname);\n-- \n2.1.4\n"},{"id":"263195","messageId":"7d0f71d1d15f6811658f32cad5a9e74dbd94e8c6.1433763494.git.mhagger@alum.mit.edu","threadId":"39559","inReplyTo":"cover.1433763494.git.mhagger@alum.mit.edu","subject":"[PATCH 09/13] initial_ref_transaction_commit(): function for initial ref creation","fromName":"Michael Haggerty","fromEmail":"mhagger@alum.mit.edu","sentAt":"2015-06-08T11:45:43Z","receivedAt":"2015-06-08T11:45:43Z","isPatch":true,"sender":{"key":"mhagger@alum.mit.edu","avatar":"https://avatars.githubusercontent.com/u/119718?v=4"},"body":"\"git clone\" uses shortcuts when creating the initial set of\nreferences:\n\n* It writes them directly to packed-refs.\n\n* It doesn't lock the individual references (though it does lock the\n  packed-refs file).\n\n* It doesn't check for refname conflicts between two new references or\n  between one new reference and any hypothetical old ones.\n\n* It doesn't create reflog entries for the reference creations.\n\nThis functionality was implemented in builtin/clone.c. But really that\nfile shouldn't have such intimate knowledge of how references are\nstored. So provide a new function in the refs API,\ninitial_ref_transaction_commit(), which can be used for initial\nreference creation. The new function is based on the ref_transaction\ninterface.\n\nThis means that we can make some other functions private to the refs\nmodule. That will be done in a followup commit.\n\nIt would seem to make sense to add a test here that there are no\nexisting references, because that is how the function *should* be\nused. But in fact, the \"testgit\" remote helper appears to call it\n*after* having set up refs/remotes/<name>/HEAD and\nrefs/remotes/<name>/master, so we can't be so strict. For now, the\nfunction trusts its caller to only call it when it makes sense. Future\ncommits will add some more limited sanity checks.\n\nSigned-off-by: Michael Haggerty <mhagger@alum.mit.edu>\n---\n builtin/clone.c | 19 +++++++++++++++----\n refs.c          | 47 +++++++++++++++++++++++++++++++++++++++++++++++\n refs.h          | 14 ++++++++++++++\n 3 files changed, 76 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex b878252..bd2a50a 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -500,16 +500,27 @@ static void write_remote_refs(const struct ref *local_refs)\n {\n \tconst struct ref *r;\n \n-\tlock_packed_refs(LOCK_DIE_ON_ERROR);\n+\tstruct ref_transaction *t;\n+\tstruct strbuf err = STRBUF_INIT;\n+\n+\tt = ref_transaction_begin(&err);\n+\tif (!t)\n+\t\tdie(err.buf);\n \n \tfor (r = local_refs; r; r = r->next) {\n \t\tif (!r->peer_ref)\n \t\t\tcontinue;\n-\t\tadd_packed_ref(r->peer_ref->name, r->old_sha1);\n+\t\tif (ref_transaction_create(t, r->peer_ref->name, r->old_sha1,\n+\t\t\t\t\t   0, NULL, &err))\n+\t\t\tdie(err.buf);\n+\t}\n+\n+\tif (initial_ref_transaction_commit(t, &err)) {\n+\t\tdie(err.buf);\n \t}\n \n-\tif (commit_packed_refs())\n-\t\tdie_errno(\"unable to overwrite old ref-pack file\");\n+\tstrbuf_release(&err);\n+\tref_transaction_free(t);\n }\n \n static void write_followtags(const struct ref *refs, const char *msg)\ndiff --git a/refs.c b/refs.c\nindex dc17984..ec4b8a0 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -4041,6 +4041,53 @@ cleanup:\n \treturn ret;\n }\n \n+int initial_ref_transaction_commit(struct ref_transaction *transaction,\n+\t\t\t\t   struct strbuf *err)\n+{\n+\tint ret = 0, i;\n+\tint n = transaction->nr;\n+\tstruct ref_update **updates = transaction->updates;\n+\n+\tassert(err);\n+\n+\tif (transaction->state != REF_TRANSACTION_OPEN)\n+\t\tdie(\"BUG: commit called for transaction that is not open\");\n+\n+\tfor (i = 0; i < n; i++) {\n+\t\tstruct ref_update *update = updates[i];\n+\n+\t\tif ((update->flags & REF_HAVE_OLD) &&\n+\t\t    !is_null_sha1(update->old_sha1))\n+\t\t\tdie(\"BUG: initial ref transaction with old_sha1 set\");\n+\t}\n+\n+\tif (lock_packed_refs(0)) {\n+\t\tstrbuf_addf(err, \"unable to lock packed-refs file: %s\",\n+\t\t\t    strerror(errno));\n+\t\tret = TRANSACTION_GENERIC_ERROR;\n+\t\tgoto cleanup;\n+\t}\n+\n+\tfor (i = 0; i < n; i++) {\n+\t\tstruct ref_update *update = updates[i];\n+\n+\t\tif ((update->flags & REF_HAVE_NEW) &&\n+\t\t    !is_null_sha1(update->new_sha1))\n+\t\t\tadd_packed_ref(update->refname, update->new_sha1);\n+\t}\n+\n+\tif (commit_packed_refs()) {\n+\t\tstrbuf_addf(err, \"unable to commit packed-refs file: %s\",\n+\t\t\t    strerror(errno));\n+\t\tret = TRANSACTION_GENERIC_ERROR;\n+\t\tgoto cleanup;\n+\t}\n+\n+cleanup:\n+\ttransaction->state = REF_TRANSACTION_CLOSED;\n+\treturn ret;\n+}\n+\n char *shorten_unambiguous_ref(const char *refname, int strict)\n {\n \tint i;\ndiff --git a/refs.h b/refs.h\nindex 3420c98..fa600b5 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -365,6 +365,20 @@ int ref_transaction_commit(struct ref_transaction *transaction,\n \t\t\t   struct strbuf *err);\n \n /*\n+ * Like ref_transaction_commit(), but optimized for creating\n+ * references when originally initializing a repository (e.g., by \"git\n+ * clone\"). It writes the new references directly to packed-refs\n+ * without locking the individual references.\n+ *\n+ * It is a bug to call this function when there might be other\n+ * processes accessing the repository or if there are existing\n+ * references that might conflict with the ones being created. All\n+ * old_sha1 values must either be absent or NULL_SHA1.\n+ */\n+int initial_ref_transaction_commit(struct ref_transaction *transaction,\n+\t\t\t\t   struct strbuf *err);\n+\n+/*\n  * Free an existing transaction and all associated data.\n  */\n void ref_transaction_free(struct ref_transaction *transaction);\n-- \n2.1.4\n"},{"id":"263196","messageId":"399429406fc6ec7a7e1a148c0a3a4b126240e59a.1433763494.git.mhagger@alum.mit.edu","threadId":"39559","inReplyTo":"cover.1433763494.git.mhagger@alum.mit.edu","subject":"[PATCH 10/13] refs: remove some functions from the module's public interface","fromName":"Michael Haggerty","fromEmail":"mhagger@alum.mit.edu","sentAt":"2015-06-08T11:45:44Z","receivedAt":"2015-06-08T11:45:44Z","isPatch":true,"sender":{"key":"mhagger@alum.mit.edu","avatar":"https://avatars.githubusercontent.com/u/119718?v=4"},"body":"The following functions are no longer used from outside the refs\nmodule:\n\n* lock_packed_refs()\n* add_packed_ref()\n* commit_packed_refs()\n* rollback_packed_refs()\n\nSo make these functions private.\n\nThis is an important step, because it means that nobody outside of the\nrefs module needs to know the difference between loose and packed\nreferences.\n\nSigned-off-by: Michael Haggerty <mhagger@alum.mit.edu>\n---\n refs.c | 31 ++++++++++++++++++++++++-------\n refs.h | 30 ------------------------------\n 2 files changed, 24 insertions(+), 37 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex ec4b8a0..b103a4b 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1314,7 +1314,13 @@ static struct ref_dir *get_packed_refs(struct ref_cache *refs)\n \treturn get_packed_ref_dir(get_packed_ref_cache(refs));\n }\n \n-void add_packed_ref(const char *refname, const unsigned char *sha1)\n+/*\n+ * Add a reference to the in-memory packed reference cache.  This may\n+ * only be called while the packed-refs file is locked (see\n+ * lock_packed_refs()).  To actually write the packed-refs file, call\n+ * commit_packed_refs().\n+ */\n+static void add_packed_ref(const char *refname, const unsigned char *sha1)\n {\n \tstruct packed_ref_cache *packed_ref_cache =\n \t\tget_packed_ref_cache(&ref_cache);\n@@ -2503,8 +2509,12 @@ static int write_packed_entry_fn(struct ref_entry *entry, void *cb_data)\n \treturn 0;\n }\n \n-/* This should return a meaningful errno on failure */\n-int lock_packed_refs(int flags)\n+/*\n+ * Lock the packed-refs file for writing. Flags is passed to\n+ * hold_lock_file_for_update(). Return 0 on success. On errors, set\n+ * errno appropriately and return a nonzero value.\n+ */\n+static int lock_packed_refs(int flags)\n {\n \tstatic int timeout_configured = 0;\n \tstatic int timeout_value = 1000;\n@@ -2534,10 +2544,12 @@ int lock_packed_refs(int flags)\n }\n \n /*\n- * Commit the packed refs changes.\n- * On error we must make sure that errno contains a meaningful value.\n+ * Write the current version of the packed refs cache from memory to\n+ * disk. The packed-refs file must already be locked for writing (see\n+ * lock_packed_refs()). Return zero on success. On errors, set errno\n+ * and return a nonzero value\n  */\n-int commit_packed_refs(void)\n+static int commit_packed_refs(void)\n {\n \tstruct packed_ref_cache *packed_ref_cache =\n \t\tget_packed_ref_cache(&ref_cache);\n@@ -2566,7 +2578,12 @@ int commit_packed_refs(void)\n \treturn error;\n }\n \n-void rollback_packed_refs(void)\n+/*\n+ * Rollback the lockfile for the packed-refs file, and discard the\n+ * in-memory packed reference cache.  (The packed-refs file will be\n+ * read anew if it is needed again after this function is called.)\n+ */\n+static void rollback_packed_refs(void)\n {\n \tstruct packed_ref_cache *packed_ref_cache =\n \t\tget_packed_ref_cache(&ref_cache);\ndiff --git a/refs.h b/refs.h\nindex fa600b5..cb56662 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -111,36 +111,6 @@ extern void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refn\n extern void warn_dangling_symrefs(FILE *fp, const char *msg_fmt, const struct string_list *refnames);\n \n /*\n- * Lock the packed-refs file for writing.  Flags is passed to\n- * hold_lock_file_for_update().  Return 0 on success.\n- * Errno is set to something meaningful on error.\n- */\n-extern int lock_packed_refs(int flags);\n-\n-/*\n- * Add a reference to the in-memory packed reference cache.  This may\n- * only be called while the packed-refs file is locked (see\n- * lock_packed_refs()).  To actually write the packed-refs file, call\n- * commit_packed_refs().\n- */\n-extern void add_packed_ref(const char *refname, const unsigned char *sha1);\n-\n-/*\n- * Write the current version of the packed refs cache from memory to\n- * disk.  The packed-refs file must already be locked for writing (see\n- * lock_packed_refs()).  Return zero on success.\n- * Sets errno to something meaningful on error.\n- */\n-extern int commit_packed_refs(void);\n-\n-/*\n- * Rollback the lockfile for the packed-refs file, and discard the\n- * in-memory packed reference cache.  (The packed-refs file will be\n- * read anew if it is needed again after this function is called.)\n- */\n-extern void rollback_packed_refs(void);\n-\n-/*\n  * Flags for controlling behaviour of pack_refs()\n  * PACK_REFS_PRUNE: Prune loose refs after packing\n  * PACK_REFS_ALL:   Pack _all_ refs, not just tags and already packed refs\n-- \n2.1.4\n"},{"id":"263197","messageId":"e8bc4ba5053b6b6b06805bb3e84724e052c82dff.1433763494.git.mhagger@alum.mit.edu","threadId":"39559","inReplyTo":"cover.1433763494.git.mhagger@alum.mit.edu","subject":"[PATCH 11/13] initial_ref_transaction_commit(): check for duplicate refs","fromName":"Michael Haggerty","fromEmail":"mhagger@alum.mit.edu","sentAt":"2015-06-08T11:45:45Z","receivedAt":"2015-06-08T11:45:45Z","isPatch":true,"sender":{"key":"mhagger@alum.mit.edu","avatar":"https://avatars.githubusercontent.com/u/119718?v=4"},"body":"Error out if the ref_transaction includes more than one update for any\nrefname.\n\nSigned-off-by: Michael Haggerty <mhagger@alum.mit.edu>\n---\n refs.c | 11 +++++++++++\n 1 file changed, 11 insertions(+)\n\ndiff --git a/refs.c b/refs.c\nindex b103a4b..ced4e53 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -4064,12 +4064,22 @@ int initial_ref_transaction_commit(struct ref_transaction *transaction,\n \tint ret = 0, i;\n \tint n = transaction->nr;\n \tstruct ref_update **updates = transaction->updates;\n+\tstruct string_list affected_refnames = STRING_LIST_INIT_NODUP;\n \n \tassert(err);\n \n \tif (transaction->state != REF_TRANSACTION_OPEN)\n \t\tdie(\"BUG: commit called for transaction that is not open\");\n \n+\t/* Fail if a refname appears more than once in the transaction: */\n+\tfor (i = 0; i < n; i++)\n+\t\tstring_list_append(&affected_refnames, updates[i]->refname);\n+\tstring_list_sort(&affected_refnames);\n+\tif (ref_update_reject_duplicates(&affected_refnames, err)) {\n+\t\tret = TRANSACTION_GENERIC_ERROR;\n+\t\tgoto cleanup;\n+\t}\n+\n \tfor (i = 0; i < n; i++) {\n \t\tstruct ref_update *update = updates[i];\n \n@@ -4102,6 +4112,7 @@ int initial_ref_transaction_commit(struct ref_transaction *transaction,\n \n cleanup:\n \ttransaction->state = REF_TRANSACTION_CLOSED;\n+\tstring_list_clear(&affected_refnames, 0);\n \treturn ret;\n }\n \n-- \n2.1.4\n"},{"id":"263201","messageId":"bfce44d80a716a076e743ab406265dad9a8a8e88.1433763494.git.mhagger@alum.mit.edu","threadId":"39559","inReplyTo":"cover.1433763494.git.mhagger@alum.mit.edu","subject":"[PATCH 12/13] initial_ref_transaction_commit(): check for ref D/F conflicts","fromName":"Michael Haggerty","fromEmail":"mhagger@alum.mit.edu","sentAt":"2015-06-08T11:45:46Z","receivedAt":"2015-06-08T11:45:46Z","isPatch":true,"sender":{"key":"mhagger@alum.mit.edu","avatar":"https://avatars.githubusercontent.com/u/119718?v=4"},"body":"In initial_ref_transaction_commit(), check for D/F conflicts (i.e.,\nthe type of conflict that exists between \"refs/foo\" and\n\"refs/foo/bar\") among the references being created and between the\nreferences being created and any hypothetical existing references.\n\nIdeally, there shouldn't *be* any existing references when this\nfunction is called. But, at least in the case of the \"testgit\" remote\nhelper, \"clone\" can be called after the remote-tracking \"HEAD\" and\n\"master\" branches have already been created. So let's just do the\nfull-blown check.\n\nSigned-off-by: Michael Haggerty <mhagger@alum.mit.edu>\n---\n\nNotes (discussion):\n    I'm a bit torn on this commit. Part of me says that we should avoid\n    the overhead of the extra checks against loose_refs and packed_refs\n    because there shouldn't be any references there anyway. Instead, the\n    \"testgit\" remote helper should be fixed.\n    \n    But these tests should be pretty cheap, and we would want to check for\n    ref D/F conflicts among the new references in any case, so it seems\n    safer to include these checks. Also, our documentation suggests that\n    users refer to git-remote-testgit(1) as an example when writing their\n    own remote helpers, so it could be that there is other code out there\n    that has imitated its light misuse of this functionality.\n\n refs.c | 34 ++++++++++++++++++++++++++++++++++\n 1 file changed, 34 insertions(+)\n\ndiff --git a/refs.c b/refs.c\nindex ced4e53..2c4d9ab 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -4058,9 +4058,19 @@ cleanup:\n \treturn ret;\n }\n \n+static int ref_present(const char *refname,\n+\t\t       const struct object_id *oid, int flags, void *cb_data)\n+{\n+\tstruct string_list *affected_refnames = cb_data;\n+\n+\treturn string_list_has_string(affected_refnames, refname);\n+}\n+\n int initial_ref_transaction_commit(struct ref_transaction *transaction,\n \t\t\t\t   struct strbuf *err)\n {\n+\tstruct ref_dir *loose_refs = get_loose_refs(&ref_cache);\n+\tstruct ref_dir *packed_refs = get_packed_refs(&ref_cache);\n \tint ret = 0, i;\n \tint n = transaction->nr;\n \tstruct ref_update **updates = transaction->updates;\n@@ -4080,12 +4090,36 @@ int initial_ref_transaction_commit(struct ref_transaction *transaction,\n \t\tgoto cleanup;\n \t}\n \n+\t/*\n+\t * It's really undefined to call this function in an active\n+\t * repository or when there are existing references: we are\n+\t * only locking and changing packed-refs, so (1) any\n+\t * simultaneous processes might try to change a reference at\n+\t * the same time we do, and (2) any existing loose versions of\n+\t * the references that we are setting would have precedence\n+\t * over our values. But some remote helpers create the remote\n+\t * \"HEAD\" and \"master\" branches before calling this function,\n+\t * so here we really only check that none of the references\n+\t * that we are creating already exists.\n+\t */\n+\tif (for_each_rawref(ref_present, &affected_refnames))\n+\t\tdie(\"BUG: initial ref transaction called with existing refs\");\n+\n \tfor (i = 0; i < n; i++) {\n \t\tstruct ref_update *update = updates[i];\n \n \t\tif ((update->flags & REF_HAVE_OLD) &&\n \t\t    !is_null_sha1(update->old_sha1))\n \t\t\tdie(\"BUG: initial ref transaction with old_sha1 set\");\n+\t\tif (verify_refname_available(update->refname,\n+\t\t\t\t\t     &affected_refnames, NULL,\n+\t\t\t\t\t     loose_refs, err) ||\n+\t\t    verify_refname_available(update->refname,\n+\t\t\t\t\t     &affected_refnames, NULL,\n+\t\t\t\t\t     packed_refs, err)) {\n+\t\t\tret = TRANSACTION_NAME_CONFLICT;\n+\t\t\tgoto cleanup;\n+\t\t}\n \t}\n \n \tif (lock_packed_refs(0)) {\n-- \n2.1.4\n"},{"id":"263200","messageId":"2f18cd5c2fdce062a319a116affc7a42ed21a3c4.1433763494.git.mhagger@alum.mit.edu","threadId":"39559","inReplyTo":"cover.1433763494.git.mhagger@alum.mit.edu","subject":"[PATCH 13/13] refs: move the remaining ref module declarations to refs.h","fromName":"Michael Haggerty","fromEmail":"mhagger@alum.mit.edu","sentAt":"2015-06-08T11:45:47Z","receivedAt":"2015-06-08T11:45:47Z","isPatch":true,"sender":{"key":"mhagger@alum.mit.edu","avatar":"https://avatars.githubusercontent.com/u/119718?v=4"},"body":"Some functions from the refs module were still declared in cache.h.\nMove them to refs.h.\n\nAdd some parameter names where they were missing.\n\nSigned-off-by: Michael Haggerty <mhagger@alum.mit.edu>\n---\n archive.c               |   1 +\n builtin/blame.c         |   1 +\n builtin/fast-export.c   |   1 +\n builtin/fmt-merge-msg.c |   1 +\n builtin/init-db.c       |   1 +\n builtin/log.c           |   1 +\n cache.h                 |  66 -----------------------\n refs.c                  |   6 ++-\n refs.h                  | 139 +++++++++++++++++++++++++++++++++++++-----------\n remote-testsvn.c        |   1 +\n 10 files changed, 118 insertions(+), 100 deletions(-)\n\ndiff --git a/archive.c b/archive.c\nindex d37c41d..936a594 100644\n--- a/archive.c\n+++ b/archive.c\n@@ -1,4 +1,5 @@\n #include \"cache.h\"\n+#include \"refs.h\"\n #include \"commit.h\"\n #include \"tree-walk.h\"\n #include \"attr.h\"\ndiff --git a/builtin/blame.c b/builtin/blame.c\nindex b3e948e..1c998cb 100644\n--- a/builtin/blame.c\n+++ b/builtin/blame.c\n@@ -6,6 +6,7 @@\n  */\n \n #include \"cache.h\"\n+#include \"refs.h\"\n #include \"builtin.h\"\n #include \"blob.h\"\n #include \"commit.h\"\ndiff --git a/builtin/fast-export.c b/builtin/fast-export.c\nindex b8182c2..d23f3be 100644\n--- a/builtin/fast-export.c\n+++ b/builtin/fast-export.c\n@@ -5,6 +5,7 @@\n  */\n #include \"builtin.h\"\n #include \"cache.h\"\n+#include \"refs.h\"\n #include \"commit.h\"\n #include \"object.h\"\n #include \"tag.h\"\ndiff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c\nindex 05f4c26..4ba7f28 100644\n--- a/builtin/fmt-merge-msg.c\n+++ b/builtin/fmt-merge-msg.c\n@@ -1,5 +1,6 @@\n #include \"builtin.h\"\n #include \"cache.h\"\n+#include \"refs.h\"\n #include \"commit.h\"\n #include \"diff.h\"\n #include \"revision.h\"\ndiff --git a/builtin/init-db.c b/builtin/init-db.c\nindex 4335738..49df78d 100644\n--- a/builtin/init-db.c\n+++ b/builtin/init-db.c\n@@ -4,6 +4,7 @@\n  * Copyright (C) Linus Torvalds, 2005\n  */\n #include \"cache.h\"\n+#include \"refs.h\"\n #include \"builtin.h\"\n #include \"exec_cmd.h\"\n #include \"parse-options.h\"\ndiff --git a/builtin/log.c b/builtin/log.c\nindex e67671e..3caa917 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -5,6 +5,7 @@\n  *\t\t 2006 Junio Hamano\n  */\n #include \"cache.h\"\n+#include \"refs.h\"\n #include \"color.h\"\n #include \"commit.h\"\n #include \"diff.h\"\ndiff --git a/cache.h b/cache.h\nindex be92121..1c00098 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1009,76 +1009,10 @@ extern int get_oid_hex(const char *hex, struct object_id *sha1);\n \n extern char *sha1_to_hex(const unsigned char *sha1);\t/* static buffer result! */\n extern char *oid_to_hex(const struct object_id *oid);\t/* same static buffer as sha1_to_hex */\n-extern int read_ref_full(const char *refname, int resolve_flags,\n-\t\t\t unsigned char *sha1, int *flags);\n-extern int read_ref(const char *refname, unsigned char *sha1);\n \n-/*\n- * Resolve a reference, recursively following symbolic refererences.\n- *\n- * Store the referred-to object's name in sha1 and return the name of\n- * the non-symbolic reference that ultimately pointed at it.  The\n- * return value, if not NULL, is a pointer into either a static buffer\n- * or the input ref.\n- *\n- * If the reference cannot be resolved to an object, the behavior\n- * depends on the RESOLVE_REF_READING flag:\n- *\n- * - If RESOLVE_REF_READING is set, return NULL.\n- *\n- * - If RESOLVE_REF_READING is not set, clear sha1 and return the name of\n- *   the last reference name in the chain, which will either be a non-symbolic\n- *   reference or an undefined reference.  If this is a prelude to\n- *   \"writing\" to the ref, the return value is the name of the ref\n- *   that will actually be created or changed.\n- *\n- * If the RESOLVE_REF_NO_RECURSE flag is passed, only resolves one\n- * level of symbolic reference.  The value stored in sha1 for a symbolic\n- * reference will always be null_sha1 in this case, and the return\n- * value is the reference that the symref refers to directly.\n- *\n- * If flags is non-NULL, set the value that it points to the\n- * combination of REF_ISPACKED (if the reference was found among the\n- * packed references), REF_ISSYMREF (if the initial reference was a\n- * symbolic reference), REF_BAD_NAME (if the reference name is ill\n- * formed --- see RESOLVE_REF_ALLOW_BAD_NAME below), and REF_ISBROKEN\n- * (if the ref is malformed or has a bad name). See refs.h for more detail\n- * on each flag.\n- *\n- * If ref is not a properly-formatted, normalized reference, return\n- * NULL.  If more than MAXDEPTH recursive symbolic lookups are needed,\n- * give up and return NULL.\n- *\n- * RESOLVE_REF_ALLOW_BAD_NAME allows resolving refs even when their\n- * name is invalid according to git-check-ref-format(1).  If the name\n- * is bad then the value stored in sha1 will be null_sha1 and the two\n- * flags REF_ISBROKEN and REF_BAD_NAME will be set.\n- *\n- * Even with RESOLVE_REF_ALLOW_BAD_NAME, names that escape the refs/\n- * directory and do not consist of all caps and underscores cannot be\n- * resolved. The function returns NULL for such ref names.\n- * Caps and underscores refers to the special refs, such as HEAD,\n- * FETCH_HEAD and friends, that all live outside of the refs/ directory.\n- */\n-#define RESOLVE_REF_READING 0x01\n-#define RESOLVE_REF_NO_RECURSE 0x02\n-#define RESOLVE_REF_ALLOW_BAD_NAME 0x04\n-extern const char *resolve_ref_unsafe(const char *ref, int resolve_flags, unsigned char *sha1, int *flags);\n-extern char *resolve_refdup(const char *ref, int resolve_flags, unsigned char *sha1, int *flags);\n-\n-extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref);\n-extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref);\n extern int interpret_branch_name(const char *str, int len, struct strbuf *);\n extern int get_sha1_mb(const char *str, unsigned char *sha1);\n \n-/*\n- * Return true iff abbrev_name is a possible abbreviation for\n- * full_name according to the rules defined by ref_rev_parse_rules in\n- * refs.c.\n- */\n-extern int refname_match(const char *abbrev_name, const char *full_name);\n-\n-extern int create_symref(const char *ref, const char *refs_heads_master, const char *logmsg);\n extern int validate_headref(const char *ref);\n \n extern int base_name_compare(const char *name1, int len1, int mode1, const char *name2, int len2, int mode2);\ndiff --git a/refs.c b/refs.c\nindex 2c4d9ab..30aa2a5 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1732,9 +1732,11 @@ const char *resolve_ref_unsafe(const char *refname, int resolve_flags,\n \treturn ret;\n }\n \n-char *resolve_refdup(const char *ref, int resolve_flags, unsigned char *sha1, int *flags)\n+char *resolve_refdup(const char *refname, int resolve_flags,\n+\t\t     unsigned char *sha1, int *flags)\n {\n-\treturn xstrdup_or_null(resolve_ref_unsafe(ref, resolve_flags, sha1, flags));\n+\treturn xstrdup_or_null(resolve_ref_unsafe(refname, resolve_flags,\n+\t\t\t\t\t\t  sha1, flags));\n }\n \n /* The argument to filter_refs */\ndiff --git a/refs.h b/refs.h\nindex cb56662..33c2f76 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -2,6 +2,98 @@\n #define REFS_H\n \n /*\n+ * Resolve a reference, recursively following symbolic refererences.\n+ *\n+ * Store the referred-to object's name in sha1 and return the name of\n+ * the non-symbolic reference that ultimately pointed at it.  The\n+ * return value, if not NULL, is a pointer into either a static buffer\n+ * or the input ref.\n+ *\n+ * If the reference cannot be resolved to an object, the behavior\n+ * depends on the RESOLVE_REF_READING flag:\n+ *\n+ * - If RESOLVE_REF_READING is set, return NULL.\n+ *\n+ * - If RESOLVE_REF_READING is not set, clear sha1 and return the name of\n+ *   the last reference name in the chain, which will either be a non-symbolic\n+ *   reference or an undefined reference.  If this is a prelude to\n+ *   \"writing\" to the ref, the return value is the name of the ref\n+ *   that will actually be created or changed.\n+ *\n+ * If the RESOLVE_REF_NO_RECURSE flag is passed, only resolves one\n+ * level of symbolic reference.  The value stored in sha1 for a symbolic\n+ * reference will always be null_sha1 in this case, and the return\n+ * value is the reference that the symref refers to directly.\n+ *\n+ * If flags is non-NULL, set the value that it points to the\n+ * combination of REF_ISPACKED (if the reference was found among the\n+ * packed references), REF_ISSYMREF (if the initial reference was a\n+ * symbolic reference), REF_BAD_NAME (if the reference name is ill\n+ * formed --- see RESOLVE_REF_ALLOW_BAD_NAME below), and REF_ISBROKEN\n+ * (if the ref is malformed or has a bad name). See refs.h for more detail\n+ * on each flag.\n+ *\n+ * If ref is not a properly-formatted, normalized reference, return\n+ * NULL.  If more than MAXDEPTH recursive symbolic lookups are needed,\n+ * give up and return NULL.\n+ *\n+ * RESOLVE_REF_ALLOW_BAD_NAME allows resolving refs even when their\n+ * name is invalid according to git-check-ref-format(1).  If the name\n+ * is bad then the value stored in sha1 will be null_sha1 and the two\n+ * flags REF_ISBROKEN and REF_BAD_NAME will be set.\n+ *\n+ * Even with RESOLVE_REF_ALLOW_BAD_NAME, names that escape the refs/\n+ * directory and do not consist of all caps and underscores cannot be\n+ * resolved. The function returns NULL for such ref names.\n+ * Caps and underscores refers to the special refs, such as HEAD,\n+ * FETCH_HEAD and friends, that all live outside of the refs/ directory.\n+ */\n+#define RESOLVE_REF_READING 0x01\n+#define RESOLVE_REF_NO_RECURSE 0x02\n+#define RESOLVE_REF_ALLOW_BAD_NAME 0x04\n+\n+extern const char *resolve_ref_unsafe(const char *refname, int resolve_flags,\n+\t\t\t\t      unsigned char *sha1, int *flags);\n+\n+extern char *resolve_refdup(const char *refname, int resolve_flags,\n+\t\t\t    unsigned char *sha1, int *flags);\n+\n+extern int read_ref_full(const char *refname, int resolve_flags,\n+\t\t\t unsigned char *sha1, int *flags);\n+extern int read_ref(const char *refname, unsigned char *sha1);\n+\n+extern int ref_exists(const char *refname);\n+\n+extern int is_branch(const char *refname);\n+\n+/*\n+ * If refname is a non-symbolic reference that refers to a tag object,\n+ * and the tag can be (recursively) dereferenced to a non-tag object,\n+ * store the SHA1 of the referred-to object to sha1 and return 0.  If\n+ * any of these conditions are not met, return a non-zero value.\n+ * Symbolic references are considered unpeelable, even if they\n+ * ultimately resolve to a peelable tag.\n+ */\n+extern int peel_ref(const char *refname, unsigned char *sha1);\n+\n+/**\n+ * Resolve refname in the nested \"gitlink\" repository that is located\n+ * at path.  If the resolution is successful, return 0 and set sha1 to\n+ * the name of the object; otherwise, return a non-zero value.\n+ */\n+extern int resolve_gitlink_ref(const char *path, const char *refname, unsigned char *sha1);\n+\n+/*\n+ * Return true iff abbrev_name is a possible abbreviation for\n+ * full_name according to the rules defined by ref_rev_parse_rules in\n+ * refs.c.\n+ */\n+extern int refname_match(const char *abbrev_name, const char *full_name);\n+\n+extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref);\n+extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref);\n+\n+/*\n  * A ref_transaction represents a collection of ref updates\n  * that should succeed or fail together.\n  *\n@@ -78,15 +170,15 @@ typedef int each_ref_fn(const char *refname,\n  * modifies the reference also returns a nonzero value to immediately\n  * stop the iteration.\n  */\n-extern int head_ref(each_ref_fn, void *);\n-extern int for_each_ref(each_ref_fn, void *);\n-extern int for_each_ref_in(const char *, each_ref_fn, void *);\n-extern int for_each_tag_ref(each_ref_fn, void *);\n-extern int for_each_branch_ref(each_ref_fn, void *);\n-extern int for_each_remote_ref(each_ref_fn, void *);\n-extern int for_each_replace_ref(each_ref_fn, void *);\n-extern int for_each_glob_ref(each_ref_fn, const char *pattern, void *);\n-extern int for_each_glob_ref_in(each_ref_fn, const char *pattern, const char* prefix, void *);\n+extern int head_ref(each_ref_fn fn, void *cb_data);\n+extern int for_each_ref(each_ref_fn fn, void *cb_data);\n+extern int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data);\n+extern int for_each_tag_ref(each_ref_fn fn, void *cb_data);\n+extern int for_each_branch_ref(each_ref_fn fn, void *cb_data);\n+extern int for_each_remote_ref(each_ref_fn fn, void *cb_data);\n+extern int for_each_replace_ref(each_ref_fn fn, void *cb_data);\n+extern int for_each_glob_ref(each_ref_fn fn, const char *pattern, void *cb_data);\n+extern int for_each_glob_ref_in(each_ref_fn fn, const char *pattern, const char* prefix, void *cb_data);\n \n extern int head_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);\n extern int for_each_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);\n@@ -99,14 +191,14 @@ extern int for_each_remote_ref_submodule(const char *submodule, each_ref_fn fn,\n extern int head_ref_namespaced(each_ref_fn fn, void *cb_data);\n extern int for_each_namespaced_ref(each_ref_fn fn, void *cb_data);\n \n+/* can be used to learn about broken ref and symref */\n+extern int for_each_rawref(each_ref_fn fn, void *cb_data);\n+\n static inline const char *has_glob_specials(const char *pattern)\n {\n \treturn strpbrk(pattern, \"?*[\");\n }\n \n-/* can be used to learn about broken ref and symref */\n-extern int for_each_rawref(each_ref_fn, void *);\n-\n extern void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname);\n extern void warn_dangling_symrefs(FILE *fp, const char *msg_fmt, const struct string_list *refnames);\n \n@@ -124,20 +216,6 @@ extern void warn_dangling_symrefs(FILE *fp, const char *msg_fmt, const struct st\n  */\n int pack_refs(unsigned int flags);\n \n-extern int ref_exists(const char *);\n-\n-extern int is_branch(const char *refname);\n-\n-/*\n- * If refname is a non-symbolic reference that refers to a tag object,\n- * and the tag can be (recursively) dereferenced to a non-tag object,\n- * store the SHA1 of the referred-to object to sha1 and return 0.  If\n- * any of these conditions are not met, return a non-zero value.\n- * Symbolic references are considered unpeelable, even if they\n- * ultimately resolve to a peelable tag.\n- */\n-extern int peel_ref(const char *refname, unsigned char *sha1);\n-\n /*\n  * Flags controlling ref_transaction_update(), ref_transaction_create(), etc.\n  * REF_NODEREF: act on the ref directly, instead of dereferencing\n@@ -205,17 +283,13 @@ extern int for_each_reflog(each_ref_fn, void *);\n extern int check_refname_format(const char *refname, int flags);\n \n extern const char *prettify_refname(const char *refname);\n+\n extern char *shorten_unambiguous_ref(const char *refname, int strict);\n \n /** rename ref, return 0 on success **/\n extern int rename_ref(const char *oldref, const char *newref, const char *logmsg);\n \n-/**\n- * Resolve refname in the nested \"gitlink\" repository that is located\n- * at path.  If the resolution is successful, return 0 and set sha1 to\n- * the name of the object; otherwise, return a non-zero value.\n- */\n-extern int resolve_gitlink_ref(const char *path, const char *refname, unsigned char *sha1);\n+extern int create_symref(const char *ref, const char *refs_heads_master, const char *logmsg);\n \n enum action_on_err {\n \tUPDATE_REFS_MSG_ON_ERR,\n@@ -366,6 +440,7 @@ int update_ref(const char *msg, const char *refname,\n \t       unsigned int flags, enum action_on_err onerr);\n \n extern int parse_hide_refs_config(const char *var, const char *value, const char *);\n+\n extern int ref_is_hidden(const char *);\n \n enum expire_reflog_flags {\ndiff --git a/remote-testsvn.c b/remote-testsvn.c\nindex 48bf6eb..f599c37 100644\n--- a/remote-testsvn.c\n+++ b/remote-testsvn.c\n@@ -1,4 +1,5 @@\n #include \"cache.h\"\n+#include \"refs.h\"\n #include \"remote.h\"\n #include \"strbuf.h\"\n #include \"url.h\"\n-- \n2.1.4\n"},{"id":"263252","messageId":"CAGZ79kYy3zUezRzAG3Uo7bDbLgJ7srj3F1k-ama1C44ZdbHr=Q@mail.gmail.com","threadId":"39559","inReplyTo":"2eea274d60e355e4944ca9ed400e6c2132b4ed32.1433763494.git.mhagger@alum.mit.edu","subject":"Re: [PATCH 01/13] delete_ref(): move declaration to refs.h","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2015-06-08T16:43:44Z","receivedAt":"2015-06-08T16:43:44Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Mon, Jun 8, 2015 at 4:45 AM, Michael Haggerty <mhagger@alum.mit.edu> wrote:\n> Also\n>\n> * Add a docstring\n>\n> * Rename the second parameter to \"old_sha1\", to be consistent with the\n>   convention used elsewhere in the refs module\n>\n> Signed-off-by: Michael Haggerty <mhagger@alum.mit.edu>\n> ---\n>  cache.h | 2 --\n>  refs.c  | 5 +++--\n>  refs.h  | 9 +++++++++\n>  3 files changed, 12 insertions(+), 4 deletions(-)\n>\n> diff --git a/cache.h b/cache.h\n> index 571c98f..be92121 100644\n> --- a/cache.h\n> +++ b/cache.h\n> @@ -585,8 +585,6 @@ extern void update_index_if_able(struct index_state *, struct lock_file *);\n>  extern int hold_locked_index(struct lock_file *, int);\n>  extern void set_alternate_index_output(const char *);\n>\n> -extern int delete_ref(const char *, const unsigned char *sha1, unsigned int flags);\n> -\n>  /* Environment bits from configuration mechanism */\n>  extern int trust_executable_bit;\n>  extern int trust_ctime;\n> diff --git a/refs.c b/refs.c\n> index a742d79..b575bb8 100644\n> --- a/refs.c\n> +++ b/refs.c\n> @@ -2789,7 +2789,8 @@ static int delete_ref_loose(struct ref_lock *lock, int flag, struct strbuf *err)\n>         return 0;\n>  }\n>\n> -int delete_ref(const char *refname, const unsigned char *sha1, unsigned int flags)\n> +int delete_ref(const char *refname, const unsigned char *old_sha1,\n> +              unsigned int flags)\n>  {\n>         struct ref_transaction *transaction;\n>         struct strbuf err = STRBUF_INIT;\n> @@ -2797,7 +2798,7 @@ int delete_ref(const char *refname, const unsigned char *sha1, unsigned int flag\n>         transaction = ref_transaction_begin(&err);\n>         if (!transaction ||\n>             ref_transaction_delete(transaction, refname,\n> -                                  (sha1 && !is_null_sha1(sha1)) ? sha1 : NULL,\n> +                                  (old_sha1 && !is_null_sha1(old_sha1)) ? old_sha1 : NULL,\n>                                    flags, NULL, &err) ||\n>             ref_transaction_commit(transaction, &err)) {\n>                 error(\"%s\", err.buf);\n> diff --git a/refs.h b/refs.h\n> index 8c3d433..8785bca 100644\n> --- a/refs.h\n> +++ b/refs.h\n> @@ -202,6 +202,15 @@ extern int read_ref_at(const char *refname, unsigned int flags,\n>  /** Check if a particular reflog exists */\n>  extern int reflog_exists(const char *refname);\n>\n> +/*\n> + * Delete the specified reference. If old_sha1 is non-NULL and not\n> + * NULL_SHA1, then verify that the current value of the reference is\n> + * old_sha1 before deleting it.\n\nAnd here I wondered what the distinction between NULL and non-NULL,\nbut NULL_SHA1\nis and digging into the code, there is none. (As you can also see in\nthis patch above with\n    (old_sha1 && !is_null_sha1(old_sha1)) ? old_sha1 : NULL,\nbut when digging deeper, the !is_null_sha1(old_sha1) is an arbitrary\nlimitation (i.e.\nref_transaction_delete and ref_transaction_update don't differ between\nthose two cases.)\n\nThe distinction comes in at lock_ref_sha1_basic, where I think we may\nwant to get rid of\nthe is_null_sha1 check and depend on NULL/non-NULL as the difference\nfor valid and invalid\nsha1's?\n\nThat said, do we want to add another sentence to the doc here saying\nnon-NULL and not\nNULL_SHA1 are treated the same or is it clear enough?\nWith or without this question addressed:\nReviewed-by: Stefan Beller <sbeller@google.com>\n\n> flags is passed to\n> + * ref_transaction_delete().\n> + */\n> +extern int delete_ref(const char *refname, const unsigned char *old_sha1,\n> +                     unsigned int flags);\n> +\n>  /** Delete a reflog */\n>  extern int delete_reflog(const char *refname);\n>\n> --\n> 2.1.4\n>\n"},{"id":"263249","messageId":"CAGZ79kaW-jT5x2SNLjL6xtY=Qe_U86G6+nh8y2sP9Sv=CHc5Kg@mail.gmail.com","threadId":"39559","inReplyTo":"77713838b598b95c5973a1fe73569635f5f31ee9.1433763494.git.mhagger@alum.mit.edu","subject":"Re: [PATCH 02/13] remove_branches(): remove temporary","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2015-06-08T16:45:34Z","receivedAt":"2015-06-08T16:45:34Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Mon, Jun 8, 2015 at 4:45 AM, Michael Haggerty <mhagger@alum.mit.edu> wrote:\n> Signed-off-by: Michael Haggerty <mhagger@alum.mit.edu>\n\nFound-to-be-Obviously-Correct-by: Stefan Beller <sbeller@google.com> ;)\n> ---\n>  builtin/remote.c | 3 +--\n>  1 file changed, 1 insertion(+), 2 deletions(-)\n>\n> diff --git a/builtin/remote.c b/builtin/remote.c\n> index f4a6ec9..53b8e13 100644\n> --- a/builtin/remote.c\n> +++ b/builtin/remote.c\n> @@ -756,8 +756,7 @@ static int remove_branches(struct string_list *branches)\n>         strbuf_release(&err);\n>\n>         for (i = 0; i < branches->nr; i++) {\n> -               struct string_list_item *item = branches->items + i;\n> -               const char *refname = item->string;\n> +               const char *refname = branches->items[i].string;\n>\n>                 if (delete_ref(refname, NULL, 0))\n>                         result |= error(_(\"Could not remove branch %s\"), refname);\n> --\n> 2.1.4\n>\n"},{"id":"263253","messageId":"CAGZ79kZvmf0WWEAHb5Hq6o5ayu7ADxQoAS4xsewoc==VEHKoPw@mail.gmail.com","threadId":"39559","inReplyTo":"1a8670c63b4e8b3b2f99d0e1acbb18162111f166.1433763494.git.mhagger@alum.mit.edu","subject":"Re: [PATCH 03/13] delete_ref(): handle special case more explicitly","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2015-06-08T16:48:23Z","receivedAt":"2015-06-08T16:48:23Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Mon, Jun 8, 2015 at 4:45 AM, Michael Haggerty <mhagger@alum.mit.edu> wrote:\n> delete_ref() uses a different convention for its old_sha1 parameter\n> than, say, ref_transaction_delete(): NULL_SHA1 means not to check the\n> old value. Make this fact a little bit clearer in the code by handling\n> it in explicit, commented code rather than burying it in a conditional\n> expression.\n>\n> Signed-off-by: Michael Haggerty <mhagger@alum.mit.edu>\n> ---\n>  refs.c | 7 +++++--\n>  1 file changed, 5 insertions(+), 2 deletions(-)\n>\n> diff --git a/refs.c b/refs.c\n> index b575bb8..f9d87b6 100644\n> --- a/refs.c\n> +++ b/refs.c\n> @@ -2795,10 +2795,13 @@ int delete_ref(const char *refname, const unsigned char *old_sha1,\n>         struct ref_transaction *transaction;\n>         struct strbuf err = STRBUF_INIT;\n>\n> +       /* Treat NULL_SHA1 as \"don't care\" */\n\nand by \"don't care\" you mean we still care about getting it deleted,\nthe part we don't care about is the particular sha1 (could be a bogus ref).\n\n> +       if (old_sha1 && is_null_sha1(old_sha1))\n> +               old_sha1 = NULL;\n> +\n>         transaction = ref_transaction_begin(&err);\n>         if (!transaction ||\n> -           ref_transaction_delete(transaction, refname,\n> -                                  (old_sha1 && !is_null_sha1(old_sha1)) ? old_sha1 : NULL,\n> +           ref_transaction_delete(transaction, refname, old_sha1,\n>                                    flags, NULL, &err) ||\n>             ref_transaction_commit(transaction, &err)) {\n>                 error(\"%s\", err.buf);\n> --\n> 2.1.4\n>\n"},{"id":"263251","messageId":"CAGZ79kbT3x=CxKwwxk8qsfNh5ee-0i2Toe4sY584CkjbadDGjA@mail.gmail.com","threadId":"39559","inReplyTo":"49bd60449c003c8a7fd060095fe743bb9efaba99.1433763494.git.mhagger@alum.mit.edu","subject":"Re: [PATCH 06/13] delete_refs(): convert error message to lower case","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2015-06-08T16:51:42Z","receivedAt":"2015-06-08T16:51:42Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Mon, Jun 8, 2015 at 4:45 AM, Michael Haggerty <mhagger@alum.mit.edu> wrote:\n> This string is going to have to be re-internationalized anyway because\n> of the previous commit. So while we're at it, we might as well convert\n> it to lower case as per our usual practice.\n\nAlthough the previous patch and this are addressing two slightly\ndifferent things,\nwe may want to squash this into the previous without dropping any of\nthe commit message?\n(It might make reviewing easier, I'd assume)\n\n>\n> Signed-off-by: Michael Haggerty <mhagger@alum.mit.edu>\n> ---\n>  refs.c | 2 +-\n>  1 file changed, 1 insertion(+), 1 deletion(-)\n>\n> diff --git a/refs.c b/refs.c\n> index 2a2a06d..a10aba8 100644\n> --- a/refs.c\n> +++ b/refs.c\n> @@ -2827,7 +2827,7 @@ int delete_refs(struct string_list *refnames)\n>                 const char *refname = refnames->items[i].string;\n>\n>                 if (delete_ref(refname, NULL, 0))\n> -                       result |= error(_(\"Could not remove reference %s\"), refname);\n> +                       result |= error(_(\"could not remove reference %s\"), refname);\n>         }\n>\n>         return result;\n> --\n> 2.1.4\n>\n"},{"id":"263255","messageId":"CAGZ79kYcO95M6DsPa71uckOcOKs-mkz2P+NtEKx5qYfOcDw99g@mail.gmail.com","threadId":"39559","inReplyTo":"2ce5b2c64d002ce649c2192bacdf505c4921caa6.1433763494.git.mhagger@alum.mit.edu","subject":"Re: [PATCH 07/13] prune_remote(): use delete_refs()","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2015-06-08T16:57:04Z","receivedAt":"2015-06-08T16:57:04Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Mon, Jun 8, 2015 at 4:45 AM, Michael Haggerty <mhagger@alum.mit.edu> wrote:\n> This will result in errors being emitted for references that can't be\n> deleted, but that is a good thing.\n\nThis sounds a bit like hand-waving to me. \"Trust me, I'm an engineer!\".\n\n\n>\n> Signed-off-by: Michael Haggerty <mhagger@alum.mit.edu>\n> ---\n>  builtin/remote.c | 11 ++---------\n>  1 file changed, 2 insertions(+), 9 deletions(-)\n>\n> diff --git a/builtin/remote.c b/builtin/remote.c\n> index c8dc724..cc3c741 100644\n> --- a/builtin/remote.c\n> +++ b/builtin/remote.c\n> @@ -1314,19 +1314,12 @@ static int prune_remote(const char *remote, int dry_run)\n>                 string_list_append(&refs_to_prune, item->util);\n>         string_list_sort(&refs_to_prune);\n>\n> -       if (!dry_run) {\n> -               struct strbuf err = STRBUF_INIT;\n> -               if (repack_without_refs(&refs_to_prune, &err))\n> -                       result |= error(\"%s\", err.buf);\n> -               strbuf_release(&err);\n> -       }\n> +       if (!dry_run)\n> +               result |= delete_refs(&refs_to_prune);\n>\n>         for_each_string_list_item(item, &states.stale) {\n>                 const char *refname = item->util;\n>\n> -               if (!dry_run)\n> -                       result |= delete_ref(refname, NULL, 0);\n> -\n>                 if (dry_run)\n>                         printf_ln(_(\" * [would prune] %s\"),\n>                                abbrev_ref(refname, \"refs/remotes/\"));\n> --\n> 2.1.4\n>\n"},{"id":"263256","messageId":"CAGZ79kbEzrAB+Jmt8vcv2FfTsPNKY0a343ARpM8o9y0+tMiQ7g@mail.gmail.com","threadId":"39559","inReplyTo":"cover.1433763494.git.mhagger@alum.mit.edu","subject":"Re: [PATCH 00/13] Improve \"refs\" module encapsulation","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2015-06-08T17:03:39Z","receivedAt":"2015-06-08T17:03:39Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Mon, Jun 8, 2015 at 4:45 AM, Michael Haggerty <mhagger@alum.mit.edu> wrote:\n> Add functions to the reference API to\n>\n> * Delete a bunch of references at once, but *without* failing the\n>   whole transaction if one of the deletions fails. This functionality\n>   is used by `git remote remove` and `git remote prune`.\n>\n> * Create initial references during `git clone`. (During clone,\n>   references are written directly to the `packed-refs` file without\n>   any locking.)\n>\n> Also move the remaining \"refs\" function declarations from `cache.h` to\n> `refs.h`.\n>\n> This improves the encapsulation of the refs module. Especially, it\n> means that code outside of the refs module should no longer need to\n> care about the distinction between loose and packed references.\n>\n> These patches are also available from my GitHub account [1] as branch\n> \"init-delete-refs-api\".\n>\n> [1] https://github.com/mhagger/git\n\nThw whole series looks good to me.\n>\n> Michael Haggerty (13):\n>   delete_ref(): move declaration to refs.h\n>   remove_branches(): remove temporary\n>   delete_ref(): handle special case more explicitly\n>   delete_refs(): new function for the refs API\n>   delete_refs(): improve error message\n>   delete_refs(): convert error message to lower case\n>   prune_remote(): use delete_refs()\n>   repack_without_refs(): make function private\n>   initial_ref_transaction_commit(): function for initial ref creation\n>   refs: remove some functions from the module's public interface\n>   initial_ref_transaction_commit(): check for duplicate refs\n>   initial_ref_transaction_commit(): check for ref D/F conflicts\n>   refs: move the remaining ref module declarations to refs.h\n>\n>  archive.c               |   1 +\n>  builtin/blame.c         |   1 +\n>  builtin/clone.c         |  19 ++++-\n>  builtin/fast-export.c   |   1 +\n>  builtin/fmt-merge-msg.c |   1 +\n>  builtin/init-db.c       |   1 +\n>  builtin/log.c           |   1 +\n>  builtin/remote.c        |  33 +-------\n>  cache.h                 |  68 ----------------\n>  refs.c                  | 167 +++++++++++++++++++++++++++++++++++---\n>  refs.h                  | 210 +++++++++++++++++++++++++++++++-----------------\n>  remote-testsvn.c        |   1 +\n>  12 files changed, 316 insertions(+), 188 deletions(-)\n>\n> --\n> 2.1.4\n>\n"},{"id":"263261","messageId":"20150608171202.GB6863@peff.net","threadId":"39559","inReplyTo":"CAGZ79kYcO95M6DsPa71uckOcOKs-mkz2P+NtEKx5qYfOcDw99g@mail.gmail.com","subject":"Re: [PATCH 07/13] prune_remote(): use delete_refs()","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2015-06-08T17:12:03Z","receivedAt":"2015-06-08T17:12:03Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Jun 08, 2015 at 09:57:04AM -0700, Stefan Beller wrote:\n\n> On Mon, Jun 8, 2015 at 4:45 AM, Michael Haggerty <mhagger@alum.mit.edu> wrote:\n> > This will result in errors being emitted for references that can't be\n> > deleted, but that is a good thing.\n> \n> This sounds a bit like hand-waving to me. \"Trust me, I'm an engineer!\".\n\nI think the argument is \"we failed to do that the user asked for, but\nnever reported the reason why\".\n\nBut I don't see how that is the case. We already complain if\nrepack_without_refs fail, and AFAICT the original call to delete_ref()\nwould emit an error, as well.\n\n-Peff\n"},{"id":"263337","messageId":"5576BBB1.5020903@alum.mit.edu","threadId":"39559","inReplyTo":"CAGZ79kYy3zUezRzAG3Uo7bDbLgJ7srj3F1k-ama1C44ZdbHr=Q@mail.gmail.com","subject":"Re: [PATCH 01/13] delete_ref(): move declaration to refs.h","fromName":"Michael Haggerty","fromEmail":"mhagger@alum.mit.edu","sentAt":"2015-06-09T10:10:57Z","receivedAt":"2015-06-09T10:10:57Z","isPatch":true,"sender":{"key":"mhagger@alum.mit.edu","avatar":"https://avatars.githubusercontent.com/u/119718?v=4"},"body":"On 06/08/2015 06:43 PM, Stefan Beller wrote:\n> On Mon, Jun 8, 2015 at 4:45 AM, Michael Haggerty <mhagger@alum.mit.edu> wrote:\n>> [...]\n>> +/*\n>> + * Delete the specified reference. If old_sha1 is non-NULL and not\n>> + * NULL_SHA1, then verify that the current value of the reference is\n>> + * old_sha1 before deleting it.\n> \n> And here I wondered what the distinction between NULL and non-NULL,\n> but NULL_SHA1\n> is and digging into the code, there is none. (As you can also see in\n> this patch above with\n>     (old_sha1 && !is_null_sha1(old_sha1)) ? old_sha1 : NULL,\n> but when digging deeper, the !is_null_sha1(old_sha1) is an arbitrary\n> limitation (i.e.\n> ref_transaction_delete and ref_transaction_update don't differ between\n> those two cases.)\n> \n> The distinction comes in at lock_ref_sha1_basic, where I think we may\n> want to get rid of\n> the is_null_sha1 check and depend on NULL/non-NULL as the difference\n> for valid and invalid\n> sha1's?\n\nI'm having a little trouble understanding your comment.\n\nThe convention for ref_transaction_update() and friends is that\n\n* old_sha1 == NULL\n\n  We don't care whether the reference existed prior to the\n  update, nor what its value was.\n\n* *old_sha1 is NULL_SHA1\n\n  (by which I mean that old_sha1 points at 20 zero bytes; I hope\n  that's clear even though NULL_SHA1 is not actually defined\n  anywhere): The reference must *not* have existed prior to the\n  update.\n\n* old_sha1 has some other value\n\n  The reference must have had that value prior to the update.\n\nlock_ref_sha1_basic() distinguishes between { NULL vs. NULL_SHA1 vs.\nother values } in the same way that ref_transaction_update() does.\n\nThe delete_ref() function doesn't follow the same convention. It treats\nNULL and NULL_SHA1 identically, as \"don't care\".\n\nIt probably makes sense to change delete_ref() use the same convention\nas ref_transaction_update(), but there are quite a few callers and I\ndidn't have the energy to review them all as part of this patch series.\nSo I left it unchanged and just documented the status quo better.\n\n> That said, do we want to add another sentence to the doc here saying\n> non-NULL and not\n> NULL_SHA1 are treated the same or is it clear enough?\n> With or without this question addressed:\n> Reviewed-by: Stefan Beller <sbeller@google.com>\n\nIn set notation,\n\n    \"non-NULL\" =\n        \"non-NULL and not NULL_SHA1\" ∪\n        \"non-NULL and equal to NULL_SHA1\"\n\nThe latter two are *not* treated the same, so I don't see how we can\nclaim that \"non-NULL\" and \"not NULL_SHA1\" are treated the same. I must\nbe misunderstanding you.\n\nWould it help if I changed the comment to\n\n    Delete the specified reference. If old_sha1 is non-NULL and not\n    NULL_SHA1, then verify that the current value of the reference is\n    old_sha1 before deleting it. If old_sha1 is NULL or NULL_SHA1,\n    delete the reference it it exists, regardless of its old value.\n\n?\n\nMichael\n\n-- \nMichael Haggerty\nmhagger@alum.mit.edu\n"},{"id":"263338","messageId":"5576BD3B.1060005@alum.mit.edu","threadId":"39559","inReplyTo":"CAGZ79kZvmf0WWEAHb5Hq6o5ayu7ADxQoAS4xsewoc==VEHKoPw@mail.gmail.com","subject":"Re: [PATCH 03/13] delete_ref(): handle special case more explicitly","fromName":"Michael Haggerty","fromEmail":"mhagger@alum.mit.edu","sentAt":"2015-06-09T10:17:31Z","receivedAt":"2015-06-09T10:17:31Z","isPatch":true,"sender":{"key":"mhagger@alum.mit.edu","avatar":"https://avatars.githubusercontent.com/u/119718?v=4"},"body":"On 06/08/2015 06:48 PM, Stefan Beller wrote:\n> On Mon, Jun 8, 2015 at 4:45 AM, Michael Haggerty <mhagger@alum.mit.edu> wrote:\n>> delete_ref() uses a different convention for its old_sha1 parameter\n>> than, say, ref_transaction_delete(): NULL_SHA1 means not to check the\n>> old value. Make this fact a little bit clearer in the code by handling\n>> it in explicit, commented code rather than burying it in a conditional\n>> expression.\n>>\n>> Signed-off-by: Michael Haggerty <mhagger@alum.mit.edu>\n>> ---\n>>  refs.c | 7 +++++--\n>>  1 file changed, 5 insertions(+), 2 deletions(-)\n>>\n>> diff --git a/refs.c b/refs.c\n>> index b575bb8..f9d87b6 100644\n>> --- a/refs.c\n>> +++ b/refs.c\n>> @@ -2795,10 +2795,13 @@ int delete_ref(const char *refname, const unsigned char *old_sha1,\n>>         struct ref_transaction *transaction;\n>>         struct strbuf err = STRBUF_INIT;\n>>\n>> +       /* Treat NULL_SHA1 as \"don't care\" */\n> \n> and by \"don't care\" you mean we still care about getting it deleted,\n> the part we don't care about is the particular sha1 (could be a bogus ref).\n\nCorrect. I will to change the comment to\n\n\t/*\n\t * Treat NULL_SHA1 and NULL alike, to mean \"we don't care what\n\t * the old value of the reference was (or even if it didn't\n\t * exist)\":\n\t */\n\nto make that clearer.\n\nMichael\n\n-- \nMichael Haggerty\nmhagger@alum.mit.edu\n"},{"id":"263339","messageId":"5576BE90.8050307@alum.mit.edu","threadId":"39559","inReplyTo":"CAGZ79kbT3x=CxKwwxk8qsfNh5ee-0i2Toe4sY584CkjbadDGjA@mail.gmail.com","subject":"Re: [PATCH 06/13] delete_refs(): convert error message to lower case","fromName":"Michael Haggerty","fromEmail":"mhagger@alum.mit.edu","sentAt":"2015-06-09T10:23:12Z","receivedAt":"2015-06-09T10:23:12Z","isPatch":true,"sender":{"key":"mhagger@alum.mit.edu","avatar":"https://avatars.githubusercontent.com/u/119718?v=4"},"body":"On 06/08/2015 06:51 PM, Stefan Beller wrote:\n> On Mon, Jun 8, 2015 at 4:45 AM, Michael Haggerty <mhagger@alum.mit.edu> wrote:\n>> This string is going to have to be re-internationalized anyway because\n>> of the previous commit. So while we're at it, we might as well convert\n>> it to lower case as per our usual practice.\n> \n> Although the previous patch and this are addressing two slightly\n> different things,\n> we may want to squash this into the previous without dropping any of\n> the commit message?\n> (It might make reviewing easier, I'd assume)\n\nOK, I will squash them together.\n\nMichael\n\n-- \nMichael Haggerty\nmhagger@alum.mit.edu\n"},{"id":"263341","messageId":"5576C4E5.4080107@alum.mit.edu","threadId":"39559","inReplyTo":"20150608171202.GB6863@peff.net","subject":"Re: [PATCH 07/13] prune_remote(): use delete_refs()","fromName":"Michael Haggerty","fromEmail":"mhagger@alum.mit.edu","sentAt":"2015-06-09T10:50:13Z","receivedAt":"2015-06-09T10:50:13Z","isPatch":true,"sender":{"key":"mhagger@alum.mit.edu","avatar":"https://avatars.githubusercontent.com/u/119718?v=4"},"body":"On 06/08/2015 07:12 PM, Jeff King wrote:\n> On Mon, Jun 08, 2015 at 09:57:04AM -0700, Stefan Beller wrote:\n> \n>> On Mon, Jun 8, 2015 at 4:45 AM, Michael Haggerty <mhagger@alum.mit.edu> wrote:\n>>> This will result in errors being emitted for references that can't be\n>>> deleted, but that is a good thing.\n>>\n>> This sounds a bit like hand-waving to me. \"Trust me, I'm an engineer!\".\n> \n> I think the argument is \"we failed to do that the user asked for, but\n> never reported the reason why\".\n> \n> But I don't see how that is the case. We already complain if\n> repack_without_refs fail, and AFAICT the original call to delete_ref()\n> would emit an error, as well.\n\nThe old and new code report errors that come from repack_without_refs()\nthe same way. The difference is how they report errors within delete_ref().\n\nThe old code only allowed delete_ref() to emit its error.\n\nThe new code (in delete_refs()) allows delete_ref() to emit its error,\nbut then follows it up with\n\n    error(_(\"could not remove reference %s\"), refname)\n\nThe \"could not remove reference\" error originally came from a similar\nmessage in remove_branches() (from builtin/remote.c).\n\nI *think* this is an improvement, because the error from delete_ref()\n(which usually comes from ref_transaction_commit()) can be pretty\nlow-level, like\n\n    Cannot lock ref '%s': unable to resolve reference %s: %s\n\nwhere the last \"%s\" is the original strerror.\n\nI would be happy to change the behavior if somebody has a concrete wish.\nAt the same time I don't think we need to sweat the details too much,\nbecause these errors should only ever be seen in the case of a broken\nrepository or a race between two processes; i.e., only in pretty rare\nand anomalous situations.\n\nMichael\n\n-- \nMichael Haggerty\nmhagger@alum.mit.edu\n"},{"id":"263343","messageId":"20150609115343.GA2777@peff.net","threadId":"39559","inReplyTo":"5576C4E5.4080107@alum.mit.edu","subject":"Re: [PATCH 07/13] prune_remote(): use delete_refs()","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2015-06-09T11:53:46Z","receivedAt":"2015-06-09T11:53:46Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Jun 09, 2015 at 12:50:13PM +0200, Michael Haggerty wrote:\n\n> The new code (in delete_refs()) allows delete_ref() to emit its error,\n> but then follows it up with\n> \n>     error(_(\"could not remove reference %s\"), refname)\n> \n> The \"could not remove reference\" error originally came from a similar\n> message in remove_branches() (from builtin/remote.c).\n> \n> I *think* this is an improvement, because the error from delete_ref()\n> (which usually comes from ref_transaction_commit()) can be pretty\n> low-level, like\n> \n>     Cannot lock ref '%s': unable to resolve reference %s: %s\n> \n> where the last \"%s\" is the original strerror.\n> \n> I would be happy to change the behavior if somebody has a concrete wish.\n> At the same time I don't think we need to sweat the details too much,\n> because these errors should only ever be seen in the case of a broken\n> repository or a race between two processes; i.e., only in pretty rare\n> and anomalous situations.\n\nThanks for the explanation. I agree it probably doesn't matter much\neither way.\n\n-Peff\n"},{"id":"263378","messageId":"CAGZ79kagxZLxFdf4QmiQr7qP6bPm_vZC8FbhmndC6O=AARrrNw@mail.gmail.com","threadId":"39559","inReplyTo":"5576BBB1.5020903@alum.mit.edu","subject":"Re: [PATCH 01/13] delete_ref(): move declaration to refs.h","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2015-06-09T16:42:15Z","receivedAt":"2015-06-09T16:42:15Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Tue, Jun 9, 2015 at 3:10 AM, Michael Haggerty <mhagger@alum.mit.edu> wrote:\n> On 06/08/2015 06:43 PM, Stefan Beller wrote:\n>> On Mon, Jun 8, 2015 at 4:45 AM, Michael Haggerty <mhagger@alum.mit.edu> wrote:\n>>> [...]\n>>> +/*\n>>> + * Delete the specified reference. If old_sha1 is non-NULL and not\n>>> + * NULL_SHA1, then verify that the current value of the reference is\n>>> + * old_sha1 before deleting it.\n>>\n>> And here I wondered what the distinction between NULL and non-NULL,\n>> but NULL_SHA1\n>> is and digging into the code, there is none. (As you can also see in\n>> this patch above with\n>>     (old_sha1 && !is_null_sha1(old_sha1)) ? old_sha1 : NULL,\n>> but when digging deeper, the !is_null_sha1(old_sha1) is an arbitrary\n>> limitation (i.e.\n>> ref_transaction_delete and ref_transaction_update don't differ between\n>> those two cases.)\n>>\n>> The distinction comes in at lock_ref_sha1_basic, where I think we may\n>> want to get rid of\n>> the is_null_sha1 check and depend on NULL/non-NULL as the difference\n>> for valid and invalid\n>> sha1's?\n>\n> I'm having a little trouble understanding your comment.\n>\n> The convention for ref_transaction_update() and friends is that\n>\n> * old_sha1 == NULL\n>\n>   We don't care whether the reference existed prior to the\n>   update, nor what its value was.\n>\n> * *old_sha1 is NULL_SHA1\n>\n>   (by which I mean that old_sha1 points at 20 zero bytes; I hope\n>   that's clear even though NULL_SHA1 is not actually defined\n>   anywhere): The reference must *not* have existed prior to the\n>   update.\n\nOk that's what I was missing.\n\n>\n> * old_sha1 has some other value\n>\n>   The reference must have had that value prior to the update.\n>\n> lock_ref_sha1_basic() distinguishes between { NULL vs. NULL_SHA1 vs.\n> other values } in the same way that ref_transaction_update() does.\n>\n> The delete_ref() function doesn't follow the same convention. It treats\n> NULL and NULL_SHA1 identically, as \"don't care\".\n>\n> It probably makes sense to change delete_ref() use the same convention\n> as ref_transaction_update(), but there are quite a few callers and I\n> didn't have the energy to review them all as part of this patch series.\n> So I left it unchanged and just documented the status quo better.\n>\n>> That said, do we want to add another sentence to the doc here saying\n>> non-NULL and not\n>> NULL_SHA1 are treated the same or is it clear enough?\n>> With or without this question addressed:\n>> Reviewed-by: Stefan Beller <sbeller@google.com>\n>\n> In set notation,\n>\n>     \"non-NULL\" =\n>         \"non-NULL and not NULL_SHA1\" ∪\n>         \"non-NULL and equal to NULL_SHA1\"\n>\n> The latter two are *not* treated the same, so I don't see how we can\n> claim that \"non-NULL\" and \"not NULL_SHA1\" are treated the same. I must\n> be misunderstanding you.\n>\n> Would it help if I changed the comment to\n>\n>     Delete the specified reference. If old_sha1 is non-NULL and not\n>     NULL_SHA1, then verify that the current value of the reference is\n>     old_sha1 before deleting it. If old_sha1 is NULL or NULL_SHA1,\n>     delete the reference it it exists, regardless of its old value.\n>\n> ?\n\nThis is very clear to me.\n\n>\n> Michael\n>\n> --\n> Michael Haggerty\n> mhagger@alum.mit.edu\n>\n"},{"id":"263408","messageId":"xmqqa8w87m7d.fsf@gitster.dls.corp.google.com","threadId":"39559","inReplyTo":"cover.1433763494.git.mhagger@alum.mit.edu","subject":"Re: [PATCH 00/13] Improve \"refs\" module encapsulation","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2015-06-09T18:47:50Z","receivedAt":"2015-06-09T18:47:50Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Michael Haggerty <mhagger@alum.mit.edu> writes:\n\n> Add functions to the reference API to\n>\n> * Delete a bunch of references at once, but *without* failing the\n>   whole transaction if one of the deletions fails. This functionality\n>   is used by `git remote remove` and `git remote prune`.\n>\n> * Create initial references during `git clone`. (During clone,\n>   references are written directly to the `packed-refs` file without\n>   any locking.)\n>\n> Also move the remaining \"refs\" function declarations from `cache.h` to\n> `refs.h`.\n>\n> This improves the encapsulation of the refs module. Especially, it\n> means that code outside of the refs module should no longer need to\n> care about the distinction between loose and packed references.\n\nYay.  The subject of the series matches this \"primary value\" of the\ntopic, even though the cover text makes it sound as if it was just a\n\"while we are at adding two functions\" with \"Also move...\" ;-)\n"},{"id":"263401","messageId":"xmqq38207m77.fsf@gitster.dls.corp.google.com","threadId":"39559","inReplyTo":"9f6f3432467c2b12329c16a46c52670f772993e5.1433763494.git.mhagger@alum.mit.edu","subject":"Re: [PATCH 05/13] delete_refs(): improve error message","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2015-06-09T18:47:56Z","receivedAt":"2015-06-09T18:47:56Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Michael Haggerty <mhagger@alum.mit.edu> writes:\n\n> Change the error message from\n>\n>     Could not remove branch %s\n>\n> to\n>\n>     Could not remove reference %s\n>\n> This change makes sense even for the existing caller, which uses the\n> function to delete remote-tracking branches.\n\nI am 80% convinced ;-)\n\nThe existing caller never used this for removing tags, so 'could not\nremove branch' was equally correct for it and was more specific than\n'could not remove reference'.  If you change it to 'could not remove\nthat thing %s', it would still be correct for the existing caller;\nit would be even less specific for them, though ;-)\n\nThe new callers you will add in later patch of course cannot live\nwith 'could not remove branch', so I think that this is an\nacceptable compromise we can live with.  If somebody later wants to\nmake the message more specific, they can add code that switches on\nthe prefix of the ref when coming up with the error message (and use\nthat code consistently in other error messages e.g. 'could not add\nreference').\n\n>\n> Signed-off-by: Michael Haggerty <mhagger@alum.mit.edu>\n> ---\n>  refs.c | 2 +-\n>  1 file changed, 1 insertion(+), 1 deletion(-)\n>\n> diff --git a/refs.c b/refs.c\n> index c413282..2a2a06d 100644\n> --- a/refs.c\n> +++ b/refs.c\n> @@ -2827,7 +2827,7 @@ int delete_refs(struct string_list *refnames)\n>  \t\tconst char *refname = refnames->items[i].string;\n>  \n>  \t\tif (delete_ref(refname, NULL, 0))\n> -\t\t\tresult |= error(_(\"Could not remove branch %s\"), refname);\n> +\t\t\tresult |= error(_(\"Could not remove reference %s\"), refname);\n>  \t}\n>  \n>  \treturn result;\n"},{"id":"263721","messageId":"557C3E9A.6090000@alum.mit.edu","threadId":"39559","inReplyTo":"CAGZ79kagxZLxFdf4QmiQr7qP6bPm_vZC8FbhmndC6O=AARrrNw@mail.gmail.com","subject":"Re: [PATCH 01/13] delete_ref(): move declaration to refs.h","fromName":"Michael Haggerty","fromEmail":"mhagger@alum.mit.edu","sentAt":"2015-06-13T14:30:50Z","receivedAt":"2015-06-13T14:30:50Z","isPatch":true,"sender":{"key":"mhagger@alum.mit.edu","avatar":"https://avatars.githubusercontent.com/u/119718?v=4"},"body":"On 06/09/2015 06:42 PM, Stefan Beller wrote:\n> On Tue, Jun 9, 2015 at 3:10 AM, Michael Haggerty <mhagger@alum.mit.edu> wrote:\n>> In set notation,\n>>\n>>     \"non-NULL\" =\n>>         \"non-NULL and not NULL_SHA1\" ∪\n>>         \"non-NULL and equal to NULL_SHA1\"\n>>\n>> The latter two are *not* treated the same, so I don't see how we can\n>> claim that \"non-NULL\" and \"not NULL_SHA1\" are treated the same. I must\n>> be misunderstanding you.\n>>\n>> Would it help if I changed the comment to\n>>\n>>     Delete the specified reference. If old_sha1 is non-NULL and not\n>>     NULL_SHA1, then verify that the current value of the reference is\n>>     old_sha1 before deleting it. If old_sha1 is NULL or NULL_SHA1,\n>>     delete the reference it it exists, regardless of its old value.\n>>\n>> ?\n> \n> This is very clear to me.\n\nOK, I will make the change in v2 (with s/it it/if it/).\n\nMichael\n\n-- \nMichael Haggerty\nmhagger@alum.mit.edu\n"}]}