{"thread":{"id":"40543","subject":"[PATCH v3 04/44] refs.c: add a new refs.c file to hold all common refs code","startedAt":"2015-10-12T21:51:20Z","lastAt":"2015-10-14T01:01:56Z","messageCount":71,"participants":["David Turner","Michael Haggerty"],"isPatch":true,"patchVersion":3,"patchTotal":44},"messages":[{"id":"271497","messageId":"1444686725-27660-1-git-send-email-dturner@twopensource.com","threadId":"40543","inReplyTo":null,"subject":"[PATCH v3 00/44] alternate refs backends","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-10-12T21:51:20Z","receivedAt":"2015-10-12T21:51:20Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"This version of the patch set includes the following changes:\n1. Numerous formatting and style updates.  Thanks for Michael Haggerty to\npointing these out; remaining errors are of course my own.\n\n2. Fixed warnings.  Thanks to Michael Haggerty, Michael Blume, and\nTorsten Bögershausen for pointing these out.\n\n3. I reworded some commit messages.\n\n4. Made tests work with LMDB turned off. Thanks to Dennis Kaarsemaker.\n\n5. Added Jeff King's core.repositoryformatversion patch.\n\n6. After discussion with Michael Haggerty and Junio, redesigned the\nbackend structure.  Now the generic code builds ref transactions, and\nbackends apply these transactions.  The ref transaction backend patch\nchanged as a result of this, and I added three more patches:\n\nrefs: make lock generic (to allow ref backends to use a generic ref_update\nstruct to handle their own locking)\n\nrefs: move duplicate check to common code (to simplify the implementation\nof ref backends, and to simplify the next patch)\n\nrefs: always handle non-normal refs in files backend (cross-backend\ntransactions)\n\n\nDavid Turner (24):\n  refs: make repack_without_refs and is_branch public\n  refs: move transaction functions into common code\n  refs-be-files.c: add method for for_each_reftype_...\n  refs-be-files.c: add do_for_each_per_worktree_ref\n  refs.c: move refname_is_safe to the common code\n  refs.h: document make refname_is_safe and add it to header\n  refs.c: move copy_msg to the common code\n  refs.c: move peel_object to the common code\n  refs.c: move should_autocreate_reflog to common code\n  refs.c: add ref backend init function\n  refs.c: add methods for reflog\n  refs.c: add method for initial ref transaction commit\n  initdb: move safe_create_dir into common code\n  refs.c: add method for initializing refs db\n  refs-be-files.c: add method to rename refs\n  refs: make lock generic\n  refs: make files_log_ref_write functions public\n  refs: break out a ref conflict check\n  refs: move duplicate check to common code\n  refs: always handle non-normal refs in files backend\n  refs: allow ref backend to be set for clone\n  refs: add register_refs_backend\n  refs: add LMDB refs backend\n  refs: tests for db backend\n\nJeff King (1):\n  introduce \"extensions\" form of core.repositoryformatversion\n\nRonnie Sahlberg (19):\n  refs.c: create a public version of verify_refname_available\n  refs-be-files.c: rename refs to refs-be-files\n  refs.c: add a new refs.c file to hold all common refs code\n  refs.c: move update_ref to refs.c\n  refs.c: move delete_ref and delete_refs to the common code\n  refs.c: move read_ref_at to the common refs file\n  refs.c: move the hidden refs functions to the common code\n  refs.c: move dwim and friend functions to the common refs code\n  refs.c: move warn_if_dangling_symref* to the common code\n  refs.c: move read_ref, read_ref_full and ref_exists to the common code\n  refs.c: move resolve_refdup to common\n  refs.c: move check_refname_format to the common code\n  refs.c: move is_branch to the common code\n  refs.c: move prettify_refname to the common code\n  refs.c: move ref iterators to the common code\n  refs.c: move head_ref_namespaced to the common code\n  refs: add a backend method structure with transaction functions\n  refs-be-files.c: add methods for misc ref operations\n  refs-be-files.c: add methods for the ref iterators\n\n .gitignore                                     |    1 +\n Documentation/config.txt                       |    7 +\n Documentation/git-clone.txt                    |    4 +\n Documentation/git-init-db.txt                  |    2 +-\n Documentation/git-init.txt                     |    6 +\n Documentation/technical/refs-be-lmdb.txt       |   50 +\n Documentation/technical/repository-version.txt |   86 +\n Makefile                                       |   13 +\n builtin/clone.c                                |   27 +-\n builtin/init-db.c                              |   47 +-\n builtin/submodule--helper.c                    |    5 +-\n cache.h                                        |   19 +\n config.c                                       |   34 +\n configure.ac                                   |   33 +\n contrib/workdir/git-new-workdir                |    3 +\n environment.c                                  |    1 +\n path.c                                         |   12 +\n refs-be-files.c                                | 3521 ++++++++++++++++\n refs-be-lmdb.c                                 | 1997 +++++++++\n refs.c                                         | 5322 +++++-------------------\n refs.h                                         |  306 +-\n setup.c                                        |   69 +-\n t/t1302-repo-version.sh                        |   38 +\n t/t1460-refs-be-db.sh                          | 1109 +++++\n t/t1470-refs-be-db-reflog.sh                   |  359 ++\n t/test-lib.sh                                  |    1 +\n test-refs-be-lmdb.c                            |   68 +\n 27 files changed, 8849 insertions(+), 4291 deletions(-)\n create mode 100644 Documentation/technical/refs-be-lmdb.txt\n create mode 100644 Documentation/technical/repository-version.txt\n create mode 100644 refs-be-files.c\n create mode 100644 refs-be-lmdb.c\n create mode 100755 t/t1460-refs-be-db.sh\n create mode 100755 t/t1470-refs-be-db-reflog.sh\n create mode 100644 test-refs-be-lmdb.c\n\n-- \n2.4.2.644.g97b850b-twtrsrc\n"},{"id":"271541","messageId":"1444686725-27660-2-git-send-email-dturner@twopensource.com","threadId":"40543","inReplyTo":"1444686725-27660-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 01/44] refs.c: create a public version of verify_refname_available","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-10-12T21:51:21Z","receivedAt":"2015-10-12T21:51:21Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\n\nCreate a public version of verify_refname_available that backends can\nprovide.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: David Turner <dturner@twopensource.com>\n---\n refs.c | 65 ++++++++++++++++++++++++++++++-----------------------------------\n refs.h | 17 +++++++++++++++++\n 2 files changed, 47 insertions(+), 35 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 132eff5..fe71ea0 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -279,7 +279,7 @@ struct ref_dir {\n  * presence of an empty subdirectory does not block the creation of a\n  * similarly-named reference.  (The fact that reference names with the\n  * same leading components can conflict *with each other* is a\n- * separate issue that is regulated by verify_refname_available().)\n+ * separate issue that is regulated by verify_refname_available_dir().)\n  *\n  * Please note that the name field contains the fully-qualified\n  * reference (or subdirectory) name.  Space could be saved by only\n@@ -897,25 +897,13 @@ static int nonmatching_ref_fn(struct ref_entry *entry, void *vdata)\n /*\n  * Return 0 if a reference named refname could be created without\n  * conflicting with the name of an existing reference in dir.\n- * Otherwise, return a negative value and write an explanation to err.\n- * If extras is non-NULL, it is a list of additional refnames with\n- * which refname is not allowed to conflict. If skip is non-NULL,\n- * ignore potential conflicts with refs in skip (e.g., because they\n- * are scheduled for deletion in the same operation). Behavior is\n- * undefined if the same name is listed in both extras and skip.\n- *\n- * Two reference names conflict if one of them exactly matches the\n- * leading components of the other; e.g., \"refs/foo/bar\" conflicts\n- * with both \"refs/foo\" and with \"refs/foo/bar/baz\" but not with\n- * \"refs/foo/bar\" or \"refs/foo/barbados\".\n- *\n- * extras and skip must be sorted.\n+ * See verify_refname_available for details.\n  */\n-static int verify_refname_available(const char *refname,\n-\t\t\t\t    const struct string_list *extras,\n-\t\t\t\t    const struct string_list *skip,\n-\t\t\t\t    struct ref_dir *dir,\n-\t\t\t\t    struct strbuf *err)\n+static int verify_refname_available_dir(const char *refname,\n+\t\t\t\t\tconst struct string_list *extras,\n+\t\t\t\t\tconst struct string_list *skip,\n+\t\t\t\t\tstruct ref_dir *dir,\n+\t\t\t\t\tstruct strbuf *err)\n {\n \tconst char *slash;\n \tint pos;\n@@ -2464,9 +2452,12 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,\n \t\t */\n \t\tstrbuf_git_path(&orig_ref_file, \"%s\", orig_refname);\n \t\tif (remove_empty_directories(&orig_ref_file)) {\n+\t\t\tstruct ref_dir *loose_refs;\n+\t\t\tloose_refs = get_loose_refs(&ref_cache);\n \t\t\tlast_errno = errno;\n-\t\t\tif (!verify_refname_available(orig_refname, extras, skip,\n-\t\t\t\t\t\t      get_loose_refs(&ref_cache), err))\n+\t\t\tif (!verify_refname_available_dir(orig_refname, extras,\n+\t\t\t\t\t\t\t  skip, loose_refs,\n+\t\t\t\t\t\t\t  err))\n \t\t\t\tstrbuf_addf(err, \"there are still refs under '%s'\",\n \t\t\t\t\t    orig_refname);\n \t\t\tgoto error_return;\n@@ -2479,8 +2470,9 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,\n \tif (!refname) {\n \t\tlast_errno = errno;\n \t\tif (last_errno != ENOTDIR ||\n-\t\t    !verify_refname_available(orig_refname, extras, skip,\n-\t\t\t\t\t      get_loose_refs(&ref_cache), err))\n+\t\t    !verify_refname_available_dir(orig_refname, extras, skip,\n+\t\t\t\t\t\t  get_loose_refs(&ref_cache),\n+\t\t\t\t\t\t  err))\n \t\t\tstrbuf_addf(err, \"unable to resolve reference %s: %s\",\n \t\t\t\t    orig_refname, strerror(last_errno));\n \n@@ -2493,8 +2485,8 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,\n \t * our refname.\n \t */\n \tif (is_null_oid(&lock->old_oid) &&\n-\t    verify_refname_available(refname, extras, skip,\n-\t\t\t\t     get_packed_refs(&ref_cache), err)) {\n+\t    verify_refname_available_dir(refname, extras, skip,\n+\t\t\t\t\t get_packed_refs(&ref_cache), err)) {\n \t\tlast_errno = ENOTDIR;\n \t\tgoto error_return;\n \t}\n@@ -3127,10 +3119,7 @@ static int rename_ref_available(const char *oldname, const char *newname)\n \tint ret;\n \n \tstring_list_insert(&skip, oldname);\n-\tret = !verify_refname_available(newname, NULL, &skip,\n-\t\t\t\t\tget_packed_refs(&ref_cache), &err)\n-\t\t&& !verify_refname_available(newname, NULL, &skip,\n-\t\t\t\t\t     get_loose_refs(&ref_cache), &err);\n+\tret = !verify_refname_available(newname, NULL, &skip, &err);\n \tif (!ret)\n \t\terror(\"%s\", err.buf);\n \n@@ -3299,6 +3288,17 @@ static int should_autocreate_reflog(const char *refname)\n \t\t!strcmp(refname, \"HEAD\");\n }\n \n+int verify_refname_available(const char *newname, struct string_list *extra,\n+\t\t\t     struct string_list *skip, struct strbuf *err)\n+{\n+\tstruct ref_dir *packed_refs = get_packed_refs(&ref_cache);\n+\tstruct ref_dir *loose_refs = get_loose_refs(&ref_cache);\n+\treturn verify_refname_available_dir(newname, extra, skip,\n+\t\t\t\t\t    packed_refs, err) ||\n+\t\tverify_refname_available_dir(newname, extra, skip,\n+\t\t\t\t\t     loose_refs, err);\n+}\n+\n /*\n  * Create a reflog for a ref.  If force_create = 0, the reflog will\n  * only be created for certain refs (those for which\n@@ -4334,8 +4334,6 @@ static int ref_present(const char *refname,\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@@ -4378,10 +4376,7 @@ int initial_ref_transaction_commit(struct ref_transaction *transaction,\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\t\t\t     err)) {\n \t\t\tret = TRANSACTION_NAME_CONFLICT;\n \t\t\tgoto cleanup;\n \t\t}\ndiff --git a/refs.h b/refs.h\nindex 6d30c98..79ea220 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -218,6 +218,23 @@ extern void warn_dangling_symrefs(FILE *fp, const char *msg_fmt, const struct st\n int pack_refs(unsigned int flags);\n \n /*\n+ * Return true iff a reference named refname could be created without\n+ * conflicting with the name of an existing reference.  If\n+ * skip is non-NULL, ignore potential conflicts with refs in skip\n+ * (e.g., because they are scheduled for deletion in the same\n+ * operation).\n+ *\n+ * Two reference names conflict if one of them exactly matches the\n+ * leading components of the other; e.g., \"foo/bar\" conflicts with\n+ * both \"foo\" and with \"foo/bar/baz\" but not with \"foo/bar\" or\n+ * \"foo/barbados\".\n+ *\n+ * skip must be sorted.\n+ */\n+int verify_refname_available(const char *newname, struct string_list *extra,\n+\t\t\t     struct string_list *skip, struct strbuf *err);\n+\n+/*\n  * Flags controlling ref_transaction_update(), ref_transaction_create(), etc.\n  * REF_NODEREF: act on the ref directly, instead of dereferencing\n  *              symbolic references.\n-- \n2.4.2.644.g97b850b-twtrsrc\n"},{"id":"271495","messageId":"1444686725-27660-3-git-send-email-dturner@twopensource.com","threadId":"40543","inReplyTo":"1444686725-27660-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 02/44] refs: make repack_without_refs and is_branch public","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-10-12T21:51:22Z","receivedAt":"2015-10-12T21:51:22Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"is_branch was already non-static, but this patch declares it in the\nheader.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: David Turner <dturner@twopensource.com>\n---\n refs.c |  9 +--------\n refs.h | 13 +++++++++++++\n 2 files changed, 14 insertions(+), 8 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex fe71ea0..d0dfdfc 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2814,14 +2814,7 @@ int pack_refs(unsigned int flags)\n \treturn 0;\n }\n \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+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 79ea220..729bc3c 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -218,6 +218,19 @@ extern void warn_dangling_symrefs(FILE *fp, const char *msg_fmt, const struct st\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 is_branch(const char *refname);\n+\n+/*\n  * Return true iff a reference named refname could be created without\n  * conflicting with the name of an existing reference.  If\n  * skip is non-NULL, ignore potential conflicts with refs in skip\n-- \n2.4.2.644.g97b850b-twtrsrc\n"},{"id":"271535","messageId":"1444686725-27660-4-git-send-email-dturner@twopensource.com","threadId":"40543","inReplyTo":"1444686725-27660-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 03/44] refs-be-files.c: rename refs to refs-be-files","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-10-12T21:51:23Z","receivedAt":"2015-10-12T21:51:23Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\n\nRename refs.c to refs-be-files.c to indicate that this file now\nholds the implementation for the files based refs backend.\nA smaller portion of the code in this file is backend agnostic and will\nbe moved to a a new refs.c file that will hold all the common refs code\nthat is shared across all backends.\n\nA second reason for first moving all the code to the new file and then\nmove the backend agnostic code back to refs.c instead of the other way\naround is because the code that will eventually remain in this new\nrefs-be-files.c file is so entangled that it would then be very\ndifficult to break the split up into small independent patches/chunks.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\n---\n Makefile                  | 2 +-\n refs.c => refs-be-files.c | 0\n 2 files changed, 1 insertion(+), 1 deletion(-)\n rename refs.c => refs-be-files.c (100%)\n\ndiff --git a/Makefile b/Makefile\nindex 46d0ca7..19036de 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -766,7 +766,7 @@ LIB_OBJS += quote.o\n LIB_OBJS += reachable.o\n LIB_OBJS += read-cache.o\n LIB_OBJS += reflog-walk.o\n-LIB_OBJS += refs.o\n+LIB_OBJS += refs-be-files.o\n LIB_OBJS += ref-filter.o\n LIB_OBJS += remote.o\n LIB_OBJS += replace_object.o\ndiff --git a/refs.c b/refs-be-files.c\nsimilarity index 100%\nrename from refs.c\nrename to refs-be-files.c\n-- \n2.4.2.644.g97b850b-twtrsrc\n"},{"id":"271494","messageId":"1444686725-27660-5-git-send-email-dturner@twopensource.com","threadId":"40543","inReplyTo":"1444686725-27660-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 04/44] refs.c: add a new refs.c file to hold all common refs code","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-10-12T21:51:24Z","receivedAt":"2015-10-12T21:51:24Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\n\nCreate a new refs.c file that will be used to hold all the refs\ncode that is backend agnostic and will be shared across all backends.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\n---\n Makefile | 1 +\n refs.c   | 3 +++\n 2 files changed, 4 insertions(+)\n create mode 100644 refs.c\n\ndiff --git a/Makefile b/Makefile\nindex 19036de..43ceab0 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -767,6 +767,7 @@ LIB_OBJS += reachable.o\n LIB_OBJS += read-cache.o\n LIB_OBJS += reflog-walk.o\n LIB_OBJS += refs-be-files.o\n+LIB_OBJS += refs.o\n LIB_OBJS += ref-filter.o\n LIB_OBJS += remote.o\n LIB_OBJS += replace_object.o\ndiff --git a/refs.c b/refs.c\nnew file mode 100644\nindex 0000000..77492ff\n--- /dev/null\n+++ b/refs.c\n@@ -0,0 +1,3 @@\n+/*\n+ * Common refs code for all backends.\n+ */\n-- \n2.4.2.644.g97b850b-twtrsrc\n"},{"id":"271540","messageId":"1444686725-27660-6-git-send-email-dturner@twopensource.com","threadId":"40543","inReplyTo":"1444686725-27660-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 05/44] refs.c: move update_ref to refs.c","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-10-12T21:51:25Z","receivedAt":"2015-10-12T21:51:25Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\n\nMove update_ref() to the refs.c file since this function does not\ncontain any backend specific code.  Move the ref classifier functions\nas well, since update_ref depends on them.\n\nBased on Ronnie Sahlberg's patch\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: David Turner <dturner@twopensource.com>\n---\n refs-be-files.c | 117 +-------------------------------------------------------\n refs.c          | 115 +++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 116 insertions(+), 116 deletions(-)\n\ndiff --git a/refs-be-files.c b/refs-be-files.c\nindex d0dfdfc..7fe4931 100644\n--- a/refs-be-files.c\n+++ b/refs-be-files.c\n@@ -2675,8 +2675,6 @@ struct pack_refs_cb_data {\n \tstruct ref_to_prune *ref_to_prune;\n };\n \n-static int is_per_worktree_ref(const char *refname);\n-\n /*\n  * An each_ref_entry_fn that is run over loose references only.  If\n  * the loose reference can be packed, add an entry in the packed ref\n@@ -2691,7 +2689,7 @@ static int pack_if_possible_fn(struct ref_entry *entry, void *cb_data)\n \tint is_tag_ref = starts_with(entry->name, \"refs/tags/\");\n \n \t/* Do not pack per-worktree refs: */\n-\tif (is_per_worktree_ref(entry->name))\n+\tif (ref_type(entry->name) == REF_TYPE_PER_WORKTREE)\n \t\treturn 0;\n \n \t/* ALWAYS pack tags */\n@@ -2879,77 +2877,6 @@ static int delete_ref_loose(struct ref_lock *lock, int flag, struct strbuf *err)\n \treturn 0;\n }\n \n-static int is_per_worktree_ref(const char *refname)\n-{\n-\treturn !strcmp(refname, \"HEAD\") ||\n-\t\tstarts_with(refname, \"refs/bisect/\");\n-}\n-\n-static int is_pseudoref_syntax(const char *refname)\n-{\n-\tconst char *c;\n-\n-\tfor (c = refname; *c; c++) {\n-\t\tif (!isupper(*c) && *c != '-' && *c != '_')\n-\t\t\treturn 0;\n-\t}\n-\n-\treturn 1;\n-}\n-\n-enum ref_type ref_type(const char *refname)\n-{\n-\tif (is_per_worktree_ref(refname))\n-\t\treturn REF_TYPE_PER_WORKTREE;\n-\tif (is_pseudoref_syntax(refname))\n-\t\treturn REF_TYPE_PSEUDOREF;\n-       return REF_TYPE_NORMAL;\n-}\n-\n-static int write_pseudoref(const char *pseudoref, const unsigned char *sha1,\n-\t\t\t   const unsigned char *old_sha1, struct strbuf *err)\n-{\n-\tconst char *filename;\n-\tint fd;\n-\tstatic struct lock_file lock;\n-\tstruct strbuf buf = STRBUF_INIT;\n-\tint ret = -1;\n-\n-\tstrbuf_addf(&buf, \"%s\\n\", sha1_to_hex(sha1));\n-\n-\tfilename = git_path(\"%s\", pseudoref);\n-\tfd = hold_lock_file_for_update(&lock, filename, LOCK_DIE_ON_ERROR);\n-\tif (fd < 0) {\n-\t\tstrbuf_addf(err, \"Could not open '%s' for writing: %s\",\n-\t\t\t    filename, strerror(errno));\n-\t\treturn -1;\n-\t}\n-\n-\tif (old_sha1) {\n-\t\tunsigned char actual_old_sha1[20];\n-\n-\t\tif (read_ref(pseudoref, actual_old_sha1))\n-\t\t\tdie(\"could not read ref '%s'\", pseudoref);\n-\t\tif (hashcmp(actual_old_sha1, old_sha1)) {\n-\t\t\tstrbuf_addf(err, \"Unexpected sha1 when writing %s\", pseudoref);\n-\t\t\trollback_lock_file(&lock);\n-\t\t\tgoto done;\n-\t\t}\n-\t}\n-\n-\tif (write_in_full(fd, buf.buf, buf.len) != buf.len) {\n-\t\tstrbuf_addf(err, \"Could not write to '%s'\", filename);\n-\t\trollback_lock_file(&lock);\n-\t\tgoto done;\n-\t}\n-\n-\tcommit_lock_file(&lock);\n-\tret = 0;\n-done:\n-\tstrbuf_release(&buf);\n-\treturn ret;\n-}\n-\n static int delete_pseudoref(const char *pseudoref, const unsigned char *old_sha1)\n {\n \tstatic struct lock_file lock;\n@@ -4098,48 +4025,6 @@ int ref_transaction_verify(struct ref_transaction *transaction,\n \t\t\t\t      flags, NULL, err);\n }\n \n-int update_ref(const char *msg, const char *refname,\n-\t       const unsigned char *new_sha1, const unsigned char *old_sha1,\n-\t       unsigned int flags, enum action_on_err onerr)\n-{\n-\tstruct ref_transaction *t = NULL;\n-\tstruct strbuf err = STRBUF_INIT;\n-\tint ret = 0;\n-\n-\tif (ref_type(refname) == REF_TYPE_PSEUDOREF) {\n-\t\tret = write_pseudoref(refname, new_sha1, old_sha1, &err);\n-\t} else {\n-\t\tt = ref_transaction_begin(&err);\n-\t\tif (!t ||\n-\t\t    ref_transaction_update(t, refname, new_sha1, old_sha1,\n-\t\t\t\t\t   flags, msg, &err) ||\n-\t\t    ref_transaction_commit(t, &err)) {\n-\t\t\tret = 1;\n-\t\t\tref_transaction_free(t);\n-\t\t}\n-\t}\n-\tif (ret) {\n-\t\tconst char *str = \"update_ref failed for ref '%s': %s\";\n-\n-\t\tswitch (onerr) {\n-\t\tcase UPDATE_REFS_MSG_ON_ERR:\n-\t\t\terror(str, refname, err.buf);\n-\t\t\tbreak;\n-\t\tcase UPDATE_REFS_DIE_ON_ERR:\n-\t\t\tdie(str, refname, err.buf);\n-\t\t\tbreak;\n-\t\tcase UPDATE_REFS_QUIET_ON_ERR:\n-\t\t\tbreak;\n-\t\t}\n-\t\tstrbuf_release(&err);\n-\t\treturn 1;\n-\t}\n-\tstrbuf_release(&err);\n-\tif (t)\n-\t\tref_transaction_free(t);\n-\treturn 0;\n-}\n-\n static int ref_update_reject_duplicates(struct string_list *refnames,\n \t\t\t\t\tstruct strbuf *err)\n {\ndiff --git a/refs.c b/refs.c\nindex 77492ff..2d10708 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1,3 +1,118 @@\n /*\n  * Common refs code for all backends.\n  */\n+#include \"cache.h\"\n+#include \"refs.h\"\n+#include \"lockfile.h\"\n+\n+static int is_per_worktree_ref(const char *refname)\n+{\n+\treturn !strcmp(refname, \"HEAD\") ||\n+\t\tstarts_with(refname, \"refs/bisect/\");\n+}\n+\n+static int is_pseudoref_syntax(const char *refname)\n+{\n+\tconst char *c;\n+\n+\tfor (c = refname; *c; c++) {\n+\t\tif (!isupper(*c) && *c != '-' && *c != '_')\n+\t\t\treturn 0;\n+\t}\n+\n+\treturn 1;\n+}\n+\n+enum ref_type ref_type(const char *refname)\n+{\n+\tif (is_per_worktree_ref(refname))\n+\t\treturn REF_TYPE_PER_WORKTREE;\n+\tif (is_pseudoref_syntax(refname))\n+\t\treturn REF_TYPE_PSEUDOREF;\n+       return REF_TYPE_NORMAL;\n+}\n+\n+static int write_pseudoref(const char *pseudoref, const unsigned char *sha1,\n+\t\t\t   const unsigned char *old_sha1, struct strbuf *err)\n+{\n+\tconst char *filename;\n+\tint fd;\n+\tstatic struct lock_file lock;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint ret = -1;\n+\n+\tstrbuf_addf(&buf, \"%s\\n\", sha1_to_hex(sha1));\n+\n+\tfilename = git_path(\"%s\", pseudoref);\n+\tfd = hold_lock_file_for_update(&lock, filename, LOCK_DIE_ON_ERROR);\n+\tif (fd < 0) {\n+\t\tstrbuf_addf(err, \"Could not open '%s' for writing: %s\",\n+\t\t\t    filename, strerror(errno));\n+\t\treturn -1;\n+\t}\n+\n+\tif (old_sha1) {\n+\t\tunsigned char actual_old_sha1[20];\n+\t\tread_ref(pseudoref, actual_old_sha1);\n+\t\tif (hashcmp(actual_old_sha1, old_sha1)) {\n+\t\t\tstrbuf_addf(err, \"Unexpected sha1 when writing %s\", pseudoref);\n+\t\t\trollback_lock_file(&lock);\n+\t\t\tgoto done;\n+\t\t}\n+\t}\n+\n+\tif (write_in_full(fd, buf.buf, buf.len) != buf.len) {\n+\t\tstrbuf_addf(err, \"Could not write to '%s'\", filename);\n+\t\trollback_lock_file(&lock);\n+\t\tgoto done;\n+\t}\n+\n+\tcommit_lock_file(&lock);\n+\tret = 0;\n+done:\n+\tstrbuf_release(&buf);\n+\treturn ret;\n+}\n+\n+int update_ref(const char *msg, const char *refname,\n+\t       const unsigned char *new_sha1, const unsigned char *old_sha1,\n+\t       unsigned int flags, enum action_on_err onerr)\n+{\n+\tstruct ref_transaction *t = NULL;\n+\tstruct strbuf err = STRBUF_INIT;\n+\tint ret = 0;\n+\n+\tif (ref_type(refname) == REF_TYPE_PSEUDOREF) {\n+\t\tret = write_pseudoref(refname, new_sha1, old_sha1, &err);\n+\t} else {\n+\t\tt = ref_transaction_begin(&err);\n+\t\tif (!t ||\n+\t\t    ref_transaction_update(t, refname, new_sha1, old_sha1,\n+\t\t\t\t\t   flags, msg, &err) ||\n+\t\t    ref_transaction_commit(t, &err)) {\n+\t\t\tret = 1;\n+\t\t\tref_transaction_free(t);\n+\t\t}\n+\t}\n+\n+\tif (ret) {\n+\t\tconst char *str = \"update_ref failed for ref '%s': %s\";\n+\n+\t\tswitch (onerr) {\n+\t\tcase UPDATE_REFS_MSG_ON_ERR:\n+\t\t\terror(str, refname, err.buf);\n+\t\t\tbreak;\n+\t\tcase UPDATE_REFS_DIE_ON_ERR:\n+\t\t\tdie(str, refname, err.buf);\n+\t\t\tbreak;\n+\t\tcase UPDATE_REFS_QUIET_ON_ERR:\n+\t\t\tbreak;\n+\t\t}\n+\t\tstrbuf_release(&err);\n+\t\treturn 1;\n+\t}\n+\tstrbuf_release(&err);\n+\tif (t)\n+\t\tref_transaction_free(t);\n+\treturn 0;\n+}\n-- \n2.4.2.644.g97b850b-twtrsrc\n"},{"id":"271537","messageId":"1444686725-27660-7-git-send-email-dturner@twopensource.com","threadId":"40543","inReplyTo":"1444686725-27660-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 06/44] refs.c: move delete_ref and delete_refs to the common code","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-10-12T21:51:26Z","receivedAt":"2015-10-12T21:51:26Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\n\nMove delete_pseudoref, delete_ref() and delete_refs() to the refs.c\nfile since these functions do not contain any backend specific code.\n\nBased on a patch by Ronnie Sahlberg.\n\nSigned-off-by: David Turner <dturner@twopensource.com>\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\n---\n refs-be-files.c | 94 ---------------------------------------------------------\n refs.c          | 94 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 94 insertions(+), 94 deletions(-)\n\ndiff --git a/refs-be-files.c b/refs-be-files.c\nindex 7fe4931..099df75 100644\n--- a/refs-be-files.c\n+++ b/refs-be-files.c\n@@ -2877,100 +2877,6 @@ static int delete_ref_loose(struct ref_lock *lock, int flag, struct strbuf *err)\n \treturn 0;\n }\n \n-static int delete_pseudoref(const char *pseudoref, const unsigned char *old_sha1)\n-{\n-\tstatic struct lock_file lock;\n-\tconst char *filename;\n-\n-\tfilename = git_path(\"%s\", pseudoref);\n-\n-\tif (old_sha1 && !is_null_sha1(old_sha1)) {\n-\t\tint fd;\n-\t\tunsigned char actual_old_sha1[20];\n-\n-\t\tfd = hold_lock_file_for_update(&lock, filename,\n-\t\t\t\t\t       LOCK_DIE_ON_ERROR);\n-\t\tif (fd < 0)\n-\t\t\tdie_errno(_(\"Could not open '%s' for writing\"), filename);\n-\t\tif (read_ref(pseudoref, actual_old_sha1))\n-\t\t\tdie(\"could not read ref '%s'\", pseudoref);\n-\t\tif (hashcmp(actual_old_sha1, old_sha1)) {\n-\t\t\twarning(\"Unexpected sha1 when deleting %s\", pseudoref);\n-\t\t\trollback_lock_file(&lock);\n-\t\t\treturn -1;\n-\t\t}\n-\n-\t\tunlink(filename);\n-\t\trollback_lock_file(&lock);\n-\t} else {\n-\t\tunlink(filename);\n-\t}\n-\n-\treturn 0;\n-}\n-\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-\n-\tif (ref_type(refname) == REF_TYPE_PSEUDOREF)\n-\t\treturn delete_pseudoref(refname, old_sha1);\n-\n-\ttransaction = ref_transaction_begin(&err);\n-\tif (!transaction ||\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-\t\tref_transaction_free(transaction);\n-\t\tstrbuf_release(&err);\n-\t\treturn 1;\n-\t}\n-\tref_transaction_free(transaction);\n-\tstrbuf_release(&err);\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 (!refnames->nr)\n-\t\treturn 0;\n-\n-\tresult = repack_without_refs(refnames, &err);\n-\tif (result) {\n-\t\t/*\n-\t\t * If we failed to rewrite the packed-refs file, then\n-\t\t * it is unsafe to try to remove loose refs, because\n-\t\t * doing so might expose an obsolete packed value for\n-\t\t * a reference that might even point at an object that\n-\t\t * has been garbage collected.\n-\t\t */\n-\t\tif (refnames->nr == 1)\n-\t\t\terror(_(\"could not delete reference %s: %s\"),\n-\t\t\t      refnames->items[0].string, err.buf);\n-\t\telse\n-\t\t\terror(_(\"could not delete references: %s\"), err.buf);\n-\n-\t\tgoto out;\n-\t}\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 reference %s\"), refname);\n-\t}\n-\n-out:\n-\tstrbuf_release(&err);\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.c b/refs.c\nindex 2d10708..205a899 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -116,3 +116,97 @@ int update_ref(const char *msg, const char *refname,\n \t\tref_transaction_free(t);\n \treturn 0;\n }\n+\n+\n+static int delete_pseudoref(const char *pseudoref, const unsigned char *old_sha1)\n+{\n+\tstatic struct lock_file lock;\n+\tconst char *filename;\n+\n+\tfilename = git_path(\"%s\", pseudoref);\n+\n+\tif (old_sha1 && !is_null_sha1(old_sha1)) {\n+\t\tint fd;\n+\t\tunsigned char actual_old_sha1[20];\n+\n+\t\tfd = hold_lock_file_for_update(&lock, filename,\n+\t\t\t\t\t       LOCK_DIE_ON_ERROR);\n+\t\tif (fd < 0)\n+\t\t\tdie_errno(_(\"Could not open '%s' for writing\"), filename);\n+\t\tread_ref(pseudoref, actual_old_sha1);\n+\t\tif (hashcmp(actual_old_sha1, old_sha1)) {\n+\t\t\twarning(\"Unexpected sha1 when deleting %s\", pseudoref);\n+\t\t\trollback_lock_file(&lock);\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\tunlink(filename);\n+\t\trollback_lock_file(&lock);\n+\t} else {\n+\t\tunlink(filename);\n+\t}\n+\n+\treturn 0;\n+}\n+\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+\n+\tif (ref_type(refname) == REF_TYPE_PSEUDOREF)\n+\t\treturn delete_pseudoref(refname, old_sha1);\n+\n+\ttransaction = ref_transaction_begin(&err);\n+\tif (!transaction ||\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+\t\tref_transaction_free(transaction);\n+\t\tstrbuf_release(&err);\n+\t\treturn 1;\n+\t}\n+\tref_transaction_free(transaction);\n+\tstrbuf_release(&err);\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 (!refnames->nr)\n+\t\treturn 0;\n+\n+\tresult = repack_without_refs(refnames, &err);\n+\tif (result) {\n+\t\t/*\n+\t\t * If we failed to rewrite the packed-refs file, then\n+\t\t * it is unsafe to try to remove loose refs, because\n+\t\t * doing so might expose an obsolete packed value for\n+\t\t * a reference that might even point at an object that\n+\t\t * has been garbage collected.\n+\t\t */\n+\t\tif (refnames->nr == 1)\n+\t\t\terror(_(\"could not delete reference %s: %s\"),\n+\t\t\t      refnames->items[0].string, err.buf);\n+\t\telse\n+\t\t\terror(_(\"could not delete references: %s\"), err.buf);\n+\n+\t\tgoto out;\n+\t}\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 reference %s\"), refname);\n+\t}\n+\n+out:\n+\tstrbuf_release(&err);\n+\treturn result;\n+}\n-- \n2.4.2.644.g97b850b-twtrsrc\n"},{"id":"271536","messageId":"1444686725-27660-8-git-send-email-dturner@twopensource.com","threadId":"40543","inReplyTo":"1444686725-27660-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 07/44] refs.c: move read_ref_at to the common refs file","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-10-12T21:51:27Z","receivedAt":"2015-10-12T21:51:27Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\n\nMove read_ref_at() to the refs.c file since this function does not\ncontain any backend specific code.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: David Turner <dturner@twopensource.com>\n---\n refs-be-files.c | 118 --------------------------------------------------------\n refs.c          | 118 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 118 insertions(+), 118 deletions(-)\n\ndiff --git a/refs-be-files.c b/refs-be-files.c\nindex 099df75..4e7e05e 100644\n--- a/refs-be-files.c\n+++ b/refs-be-files.c\n@@ -3424,124 +3424,6 @@ int create_symref(const char *ref_target, const char *refs_heads_master,\n \treturn 0;\n }\n \n-struct read_ref_at_cb {\n-\tconst char *refname;\n-\tunsigned long at_time;\n-\tint cnt;\n-\tint reccnt;\n-\tunsigned char *sha1;\n-\tint found_it;\n-\n-\tunsigned char osha1[20];\n-\tunsigned char nsha1[20];\n-\tint tz;\n-\tunsigned long date;\n-\tchar **msg;\n-\tunsigned long *cutoff_time;\n-\tint *cutoff_tz;\n-\tint *cutoff_cnt;\n-};\n-\n-static int read_ref_at_ent(unsigned char *osha1, unsigned char *nsha1,\n-\t\tconst char *email, unsigned long timestamp, int tz,\n-\t\tconst char *message, void *cb_data)\n-{\n-\tstruct read_ref_at_cb *cb = cb_data;\n-\n-\tcb->reccnt++;\n-\tcb->tz = tz;\n-\tcb->date = timestamp;\n-\n-\tif (timestamp <= cb->at_time || cb->cnt == 0) {\n-\t\tif (cb->msg)\n-\t\t\t*cb->msg = xstrdup(message);\n-\t\tif (cb->cutoff_time)\n-\t\t\t*cb->cutoff_time = timestamp;\n-\t\tif (cb->cutoff_tz)\n-\t\t\t*cb->cutoff_tz = tz;\n-\t\tif (cb->cutoff_cnt)\n-\t\t\t*cb->cutoff_cnt = cb->reccnt - 1;\n-\t\t/*\n-\t\t * we have not yet updated cb->[n|o]sha1 so they still\n-\t\t * hold the values for the previous record.\n-\t\t */\n-\t\tif (!is_null_sha1(cb->osha1)) {\n-\t\t\thashcpy(cb->sha1, nsha1);\n-\t\t\tif (hashcmp(cb->osha1, nsha1))\n-\t\t\t\twarning(\"Log for ref %s has gap after %s.\",\n-\t\t\t\t\tcb->refname, show_date(cb->date, cb->tz, DATE_MODE(RFC2822)));\n-\t\t}\n-\t\telse if (cb->date == cb->at_time)\n-\t\t\thashcpy(cb->sha1, nsha1);\n-\t\telse if (hashcmp(nsha1, cb->sha1))\n-\t\t\twarning(\"Log for ref %s unexpectedly ended on %s.\",\n-\t\t\t\tcb->refname, show_date(cb->date, cb->tz,\n-\t\t\t\t\t\t       DATE_MODE(RFC2822)));\n-\t\thashcpy(cb->osha1, osha1);\n-\t\thashcpy(cb->nsha1, nsha1);\n-\t\tcb->found_it = 1;\n-\t\treturn 1;\n-\t}\n-\thashcpy(cb->osha1, osha1);\n-\thashcpy(cb->nsha1, nsha1);\n-\tif (cb->cnt > 0)\n-\t\tcb->cnt--;\n-\treturn 0;\n-}\n-\n-static int read_ref_at_ent_oldest(unsigned char *osha1, unsigned char *nsha1,\n-\t\t\t\t  const char *email, unsigned long timestamp,\n-\t\t\t\t  int tz, const char *message, void *cb_data)\n-{\n-\tstruct read_ref_at_cb *cb = cb_data;\n-\n-\tif (cb->msg)\n-\t\t*cb->msg = xstrdup(message);\n-\tif (cb->cutoff_time)\n-\t\t*cb->cutoff_time = timestamp;\n-\tif (cb->cutoff_tz)\n-\t\t*cb->cutoff_tz = tz;\n-\tif (cb->cutoff_cnt)\n-\t\t*cb->cutoff_cnt = cb->reccnt;\n-\thashcpy(cb->sha1, osha1);\n-\tif (is_null_sha1(cb->sha1))\n-\t\thashcpy(cb->sha1, nsha1);\n-\t/* We just want the first entry */\n-\treturn 1;\n-}\n-\n-int read_ref_at(const char *refname, unsigned int flags, unsigned long at_time, int cnt,\n-\t\tunsigned char *sha1, char **msg,\n-\t\tunsigned long *cutoff_time, int *cutoff_tz, int *cutoff_cnt)\n-{\n-\tstruct read_ref_at_cb cb;\n-\n-\tmemset(&cb, 0, sizeof(cb));\n-\tcb.refname = refname;\n-\tcb.at_time = at_time;\n-\tcb.cnt = cnt;\n-\tcb.msg = msg;\n-\tcb.cutoff_time = cutoff_time;\n-\tcb.cutoff_tz = cutoff_tz;\n-\tcb.cutoff_cnt = cutoff_cnt;\n-\tcb.sha1 = sha1;\n-\n-\tfor_each_reflog_ent_reverse(refname, read_ref_at_ent, &cb);\n-\n-\tif (!cb.reccnt) {\n-\t\tif (flags & GET_SHA1_QUIETLY)\n-\t\t\texit(128);\n-\t\telse\n-\t\t\tdie(\"Log for %s is empty.\", refname);\n-\t}\n-\tif (cb.found_it)\n-\t\treturn 0;\n-\n-\tfor_each_reflog_ent(refname, read_ref_at_ent_oldest, &cb);\n-\n-\treturn 1;\n-}\n-\n int reflog_exists(const char *refname)\n {\n \tstruct stat st;\ndiff --git a/refs.c b/refs.c\nindex 205a899..5a8ef0c 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -210,3 +210,121 @@ out:\n \tstrbuf_release(&err);\n \treturn result;\n }\n+\n+struct read_ref_at_cb {\n+\tconst char *refname;\n+\tunsigned long at_time;\n+\tint cnt;\n+\tint reccnt;\n+\tunsigned char *sha1;\n+\tint found_it;\n+\n+\tunsigned char osha1[20];\n+\tunsigned char nsha1[20];\n+\tint tz;\n+\tunsigned long date;\n+\tchar **msg;\n+\tunsigned long *cutoff_time;\n+\tint *cutoff_tz;\n+\tint *cutoff_cnt;\n+};\n+\n+static int read_ref_at_ent(unsigned char *osha1, unsigned char *nsha1,\n+\t\tconst char *email, unsigned long timestamp, int tz,\n+\t\tconst char *message, void *cb_data)\n+{\n+\tstruct read_ref_at_cb *cb = cb_data;\n+\n+\tcb->reccnt++;\n+\tcb->tz = tz;\n+\tcb->date = timestamp;\n+\n+\tif (timestamp <= cb->at_time || cb->cnt == 0) {\n+\t\tif (cb->msg)\n+\t\t\t*cb->msg = xstrdup(message);\n+\t\tif (cb->cutoff_time)\n+\t\t\t*cb->cutoff_time = timestamp;\n+\t\tif (cb->cutoff_tz)\n+\t\t\t*cb->cutoff_tz = tz;\n+\t\tif (cb->cutoff_cnt)\n+\t\t\t*cb->cutoff_cnt = cb->reccnt - 1;\n+\t\t/*\n+\t\t * we have not yet updated cb->[n|o]sha1 so they still\n+\t\t * hold the values for the previous record.\n+\t\t */\n+\t\tif (!is_null_sha1(cb->osha1)) {\n+\t\t\thashcpy(cb->sha1, nsha1);\n+\t\t\tif (hashcmp(cb->osha1, nsha1))\n+\t\t\t\twarning(\"Log for ref %s has gap after %s.\",\n+\t\t\t\t\tcb->refname, show_date(cb->date, cb->tz, DATE_MODE(RFC2822)));\n+\t\t}\n+\t\telse if (cb->date == cb->at_time)\n+\t\t\thashcpy(cb->sha1, nsha1);\n+\t\telse if (hashcmp(nsha1, cb->sha1))\n+\t\t\twarning(\"Log for ref %s unexpectedly ended on %s.\",\n+\t\t\t\tcb->refname, show_date(cb->date, cb->tz,\n+\t\t\t\t\t\t       DATE_MODE(RFC2822)));\n+\t\thashcpy(cb->osha1, osha1);\n+\t\thashcpy(cb->nsha1, nsha1);\n+\t\tcb->found_it = 1;\n+\t\treturn 1;\n+\t}\n+\thashcpy(cb->osha1, osha1);\n+\thashcpy(cb->nsha1, nsha1);\n+\tif (cb->cnt > 0)\n+\t\tcb->cnt--;\n+\treturn 0;\n+}\n+\n+static int read_ref_at_ent_oldest(unsigned char *osha1, unsigned char *nsha1,\n+\t\t\t\t  const char *email, unsigned long timestamp,\n+\t\t\t\t  int tz, const char *message, void *cb_data)\n+{\n+\tstruct read_ref_at_cb *cb = cb_data;\n+\n+\tif (cb->msg)\n+\t\t*cb->msg = xstrdup(message);\n+\tif (cb->cutoff_time)\n+\t\t*cb->cutoff_time = timestamp;\n+\tif (cb->cutoff_tz)\n+\t\t*cb->cutoff_tz = tz;\n+\tif (cb->cutoff_cnt)\n+\t\t*cb->cutoff_cnt = cb->reccnt;\n+\thashcpy(cb->sha1, osha1);\n+\tif (is_null_sha1(cb->sha1))\n+\t\thashcpy(cb->sha1, nsha1);\n+\t/* We just want the first entry */\n+\treturn 1;\n+}\n+\n+int read_ref_at(const char *refname, unsigned int flags, unsigned long at_time, int cnt,\n+\t\tunsigned char *sha1, char **msg,\n+\t\tunsigned long *cutoff_time, int *cutoff_tz, int *cutoff_cnt)\n+{\n+\tstruct read_ref_at_cb cb;\n+\n+\tmemset(&cb, 0, sizeof(cb));\n+\tcb.refname = refname;\n+\tcb.at_time = at_time;\n+\tcb.cnt = cnt;\n+\tcb.msg = msg;\n+\tcb.cutoff_time = cutoff_time;\n+\tcb.cutoff_tz = cutoff_tz;\n+\tcb.cutoff_cnt = cutoff_cnt;\n+\tcb.sha1 = sha1;\n+\n+\tfor_each_reflog_ent_reverse(refname, read_ref_at_ent, &cb);\n+\n+\tif (!cb.reccnt) {\n+\t\tif (flags & GET_SHA1_QUIETLY)\n+\t\t\texit(128);\n+\t\telse\n+\t\t\tdie(\"Log for %s is empty.\", refname);\n+\t}\n+\tif (cb.found_it)\n+\t\treturn 0;\n+\n+\tfor_each_reflog_ent(refname, read_ref_at_ent_oldest, &cb);\n+\n+\treturn 1;\n+}\n-- \n2.4.2.644.g97b850b-twtrsrc\n"},{"id":"271496","messageId":"1444686725-27660-9-git-send-email-dturner@twopensource.com","threadId":"40543","inReplyTo":"1444686725-27660-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 08/44] refs.c: move the hidden refs functions to the common code","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-10-12T21:51:28Z","receivedAt":"2015-10-12T21:51:28Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\n\nMove the hidden refs functions to the refs.c file since these\nfunctions do not contain any backend specific code.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: David Turner <dturner@twopensource.com>\n---\n refs-be-files.c | 51 ---------------------------------------------------\n refs.c          | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 50 insertions(+), 51 deletions(-)\n\ndiff --git a/refs-be-files.c b/refs-be-files.c\nindex 4e7e05e..d110bf8 100644\n--- a/refs-be-files.c\n+++ b/refs-be-files.c\n@@ -4169,57 +4169,6 @@ char *shorten_unambiguous_ref(const char *refname, int strict)\n \treturn xstrdup(refname);\n }\n \n-static struct string_list *hide_refs;\n-\n-int parse_hide_refs_config(const char *var, const char *value, const char *section)\n-{\n-\tif (!strcmp(\"transfer.hiderefs\", var) ||\n-\t    /* NEEDSWORK: use parse_config_key() once both are merged */\n-\t    (starts_with(var, section) && var[strlen(section)] == '.' &&\n-\t     !strcmp(var + strlen(section), \".hiderefs\"))) {\n-\t\tchar *ref;\n-\t\tint len;\n-\n-\t\tif (!value)\n-\t\t\treturn config_error_nonbool(var);\n-\t\tref = xstrdup(value);\n-\t\tlen = strlen(ref);\n-\t\twhile (len && ref[len - 1] == '/')\n-\t\t\tref[--len] = '\\0';\n-\t\tif (!hide_refs) {\n-\t\t\thide_refs = xcalloc(1, sizeof(*hide_refs));\n-\t\t\thide_refs->strdup_strings = 1;\n-\t\t}\n-\t\tstring_list_append(hide_refs, ref);\n-\t}\n-\treturn 0;\n-}\n-\n-int ref_is_hidden(const char *refname)\n-{\n-\tint i;\n-\n-\tif (!hide_refs)\n-\t\treturn 0;\n-\tfor (i = hide_refs->nr - 1; i >= 0; i--) {\n-\t\tconst char *match = hide_refs->items[i].string;\n-\t\tint neg = 0;\n-\t\tint len;\n-\n-\t\tif (*match == '!') {\n-\t\t\tneg = 1;\n-\t\t\tmatch++;\n-\t\t}\n-\n-\t\tif (!starts_with(refname, match))\n-\t\t\tcontinue;\n-\t\tlen = strlen(match);\n-\t\tif (!refname[len] || refname[len] == '/')\n-\t\t\treturn !neg;\n-\t}\n-\treturn 0;\n-}\n-\n struct expire_reflog_cb {\n \tunsigned int flags;\n \treflog_expiry_should_prune_fn *should_prune_fn;\ndiff --git a/refs.c b/refs.c\nindex 5a8ef0c..6b2fc39 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -328,3 +328,53 @@ int read_ref_at(const char *refname, unsigned int flags, unsigned long at_time,\n \n \treturn 1;\n }\n+\n+static struct string_list *hide_refs;\n+\n+int parse_hide_refs_config(const char *var, const char *value, const char *section)\n+{\n+\tif (!strcmp(\"transfer.hiderefs\", var) ||\n+\t    /* NEEDSWORK: use parse_config_key() once both are merged */\n+\t    (starts_with(var, section) && var[strlen(section)] == '.' &&\n+\t     !strcmp(var + strlen(section), \".hiderefs\"))) {\n+\t\tchar *ref;\n+\t\tint len;\n+\n+\t\tif (!value)\n+\t\t\treturn config_error_nonbool(var);\n+\t\tref = xstrdup(value);\n+\t\tlen = strlen(ref);\n+\t\twhile (len && ref[len - 1] == '/')\n+\t\t\tref[--len] = '\\0';\n+\t\tif (!hide_refs) {\n+\t\t\thide_refs = xcalloc(1, sizeof(*hide_refs));\n+\t\t\thide_refs->strdup_strings = 1;\n+\t\t}\n+\t\tstring_list_append(hide_refs, ref);\n+\t}\n+\treturn 0;\n+}\n+\n+int ref_is_hidden(const char *refname)\n+{\n+\tint i;\n+\n+\tif (!hide_refs)\n+\t\treturn 0;\n+\tfor (i = hide_refs->nr - 1; i >= 0; i--) {\n+\t\tconst char *match = hide_refs->items[i].string;\n+\t\tint neg = 0;\n+\t\tint len;\n+\t\tif (*match == '!') {\n+\t\t\tneg = 1;\n+\t\t\tmatch++;\n+\t\t}\n+\n+\t\tif (!starts_with(refname, match))\n+\t\t\tcontinue;\n+\t\tlen = strlen(match);\n+\t\tif (!refname[len] || refname[len] == '/')\n+\t\t\treturn !neg;\n+\t}\n+\treturn 0;\n+}\n-- \n2.4.2.644.g97b850b-twtrsrc\n"},{"id":"271498","messageId":"1444686725-27660-10-git-send-email-dturner@twopensource.com","threadId":"40543","inReplyTo":"1444686725-27660-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 09/44] refs.c: move dwim and friend functions to the common refs code","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-10-12T21:51:29Z","receivedAt":"2015-10-12T21:51:29Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\n\nThese functions do not contain any backend specific code so we move\nthem to the common code and share across all backends.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: David Turner <dturner@twopensource.com>\n---\n refs-be-files.c | 203 --------------------------------------------------------\n refs.c          | 203 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 203 insertions(+), 203 deletions(-)\n\ndiff --git a/refs-be-files.c b/refs-be-files.c\nindex d110bf8..37772a7 100644\n--- a/refs-be-files.c\n+++ b/refs-be-files.c\n@@ -2247,30 +2247,6 @@ const char *prettify_refname(const char *name)\n \t\t0);\n }\n \n-static const char *ref_rev_parse_rules[] = {\n-\t\"%.*s\",\n-\t\"refs/%.*s\",\n-\t\"refs/tags/%.*s\",\n-\t\"refs/heads/%.*s\",\n-\t\"refs/remotes/%.*s\",\n-\t\"refs/remotes/%.*s/HEAD\",\n-\tNULL\n-};\n-\n-int refname_match(const char *abbrev_name, const char *full_name)\n-{\n-\tconst char **p;\n-\tconst int abbrev_name_len = strlen(abbrev_name);\n-\n-\tfor (p = ref_rev_parse_rules; *p; p++) {\n-\t\tif (!strcmp(full_name, mkpath(*p, abbrev_name_len, abbrev_name))) {\n-\t\t\treturn 1;\n-\t\t}\n-\t}\n-\n-\treturn 0;\n-}\n-\n static void unlock_ref(struct ref_lock *lock)\n {\n \t/* Do not free lock->lk -- atexit() still looks at them */\n@@ -2323,92 +2299,6 @@ static int remove_empty_directories(struct strbuf *path)\n }\n \n /*\n- * *string and *len will only be substituted, and *string returned (for\n- * later free()ing) if the string passed in is a magic short-hand form\n- * to name a branch.\n- */\n-static char *substitute_branch_name(const char **string, int *len)\n-{\n-\tstruct strbuf buf = STRBUF_INIT;\n-\tint ret = interpret_branch_name(*string, *len, &buf);\n-\n-\tif (ret == *len) {\n-\t\tsize_t size;\n-\t\t*string = strbuf_detach(&buf, &size);\n-\t\t*len = size;\n-\t\treturn (char *)*string;\n-\t}\n-\n-\treturn NULL;\n-}\n-\n-int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref)\n-{\n-\tchar *last_branch = substitute_branch_name(&str, &len);\n-\tconst char **p, *r;\n-\tint refs_found = 0;\n-\n-\t*ref = NULL;\n-\tfor (p = ref_rev_parse_rules; *p; p++) {\n-\t\tchar fullref[PATH_MAX];\n-\t\tunsigned char sha1_from_ref[20];\n-\t\tunsigned char *this_result;\n-\t\tint flag;\n-\n-\t\tthis_result = refs_found ? sha1_from_ref : sha1;\n-\t\tmksnpath(fullref, sizeof(fullref), *p, len, str);\n-\t\tr = resolve_ref_unsafe(fullref, RESOLVE_REF_READING,\n-\t\t\t\t       this_result, &flag);\n-\t\tif (r) {\n-\t\t\tif (!refs_found++)\n-\t\t\t\t*ref = xstrdup(r);\n-\t\t\tif (!warn_ambiguous_refs)\n-\t\t\t\tbreak;\n-\t\t} else if ((flag & REF_ISSYMREF) && strcmp(fullref, \"HEAD\")) {\n-\t\t\twarning(\"ignoring dangling symref %s.\", fullref);\n-\t\t} else if ((flag & REF_ISBROKEN) && strchr(fullref, '/')) {\n-\t\t\twarning(\"ignoring broken ref %s.\", fullref);\n-\t\t}\n-\t}\n-\tfree(last_branch);\n-\treturn refs_found;\n-}\n-\n-int dwim_log(const char *str, int len, unsigned char *sha1, char **log)\n-{\n-\tchar *last_branch = substitute_branch_name(&str, &len);\n-\tconst char **p;\n-\tint logs_found = 0;\n-\n-\t*log = NULL;\n-\tfor (p = ref_rev_parse_rules; *p; p++) {\n-\t\tunsigned char hash[20];\n-\t\tchar path[PATH_MAX];\n-\t\tconst char *ref, *it;\n-\n-\t\tmksnpath(path, sizeof(path), *p, len, str);\n-\t\tref = resolve_ref_unsafe(path, RESOLVE_REF_READING,\n-\t\t\t\t\t hash, NULL);\n-\t\tif (!ref)\n-\t\t\tcontinue;\n-\t\tif (reflog_exists(path))\n-\t\t\tit = path;\n-\t\telse if (strcmp(ref, path) && reflog_exists(ref))\n-\t\t\tit = ref;\n-\t\telse\n-\t\t\tcontinue;\n-\t\tif (!logs_found++) {\n-\t\t\t*log = xstrdup(it);\n-\t\t\thashcpy(sha1, hash);\n-\t\t}\n-\t\tif (!warn_ambiguous_refs)\n-\t\t\tbreak;\n-\t}\n-\tfree(last_branch);\n-\treturn logs_found;\n-}\n-\n-/*\n  * Locks a ref returning the lock on success and NULL on failure.\n  * On failure errno is set to something meaningful.\n  */\n@@ -4076,99 +3966,6 @@ cleanup:\n \treturn ret;\n }\n \n-char *shorten_unambiguous_ref(const char *refname, int strict)\n-{\n-\tint i;\n-\tstatic char **scanf_fmts;\n-\tstatic int nr_rules;\n-\tchar *short_name;\n-\n-\tif (!nr_rules) {\n-\t\t/*\n-\t\t * Pre-generate scanf formats from ref_rev_parse_rules[].\n-\t\t * Generate a format suitable for scanf from a\n-\t\t * ref_rev_parse_rules rule by interpolating \"%s\" at the\n-\t\t * location of the \"%.*s\".\n-\t\t */\n-\t\tsize_t total_len = 0;\n-\t\tsize_t offset = 0;\n-\n-\t\t/* the rule list is NULL terminated, count them first */\n-\t\tfor (nr_rules = 0; ref_rev_parse_rules[nr_rules]; nr_rules++)\n-\t\t\t/* -2 for strlen(\"%.*s\") - strlen(\"%s\"); +1 for NUL */\n-\t\t\ttotal_len += strlen(ref_rev_parse_rules[nr_rules]) - 2 + 1;\n-\n-\t\tscanf_fmts = xmalloc(nr_rules * sizeof(char *) + total_len);\n-\n-\t\toffset = 0;\n-\t\tfor (i = 0; i < nr_rules; i++) {\n-\t\t\tassert(offset < total_len);\n-\t\t\tscanf_fmts[i] = (char *)&scanf_fmts[nr_rules] + offset;\n-\t\t\toffset += snprintf(scanf_fmts[i], total_len - offset,\n-\t\t\t\t\t   ref_rev_parse_rules[i], 2, \"%s\") + 1;\n-\t\t}\n-\t}\n-\n-\t/* bail out if there are no rules */\n-\tif (!nr_rules)\n-\t\treturn xstrdup(refname);\n-\n-\t/* buffer for scanf result, at most refname must fit */\n-\tshort_name = xstrdup(refname);\n-\n-\t/* skip first rule, it will always match */\n-\tfor (i = nr_rules - 1; i > 0 ; --i) {\n-\t\tint j;\n-\t\tint rules_to_fail = i;\n-\t\tint short_name_len;\n-\n-\t\tif (1 != sscanf(refname, scanf_fmts[i], short_name))\n-\t\t\tcontinue;\n-\n-\t\tshort_name_len = strlen(short_name);\n-\n-\t\t/*\n-\t\t * in strict mode, all (except the matched one) rules\n-\t\t * must fail to resolve to a valid non-ambiguous ref\n-\t\t */\n-\t\tif (strict)\n-\t\t\trules_to_fail = nr_rules;\n-\n-\t\t/*\n-\t\t * check if the short name resolves to a valid ref,\n-\t\t * but use only rules prior to the matched one\n-\t\t */\n-\t\tfor (j = 0; j < rules_to_fail; j++) {\n-\t\t\tconst char *rule = ref_rev_parse_rules[j];\n-\t\t\tchar refname[PATH_MAX];\n-\n-\t\t\t/* skip matched rule */\n-\t\t\tif (i == j)\n-\t\t\t\tcontinue;\n-\n-\t\t\t/*\n-\t\t\t * the short name is ambiguous, if it resolves\n-\t\t\t * (with this previous rule) to a valid ref\n-\t\t\t * read_ref() returns 0 on success\n-\t\t\t */\n-\t\t\tmksnpath(refname, sizeof(refname),\n-\t\t\t\t rule, short_name_len, short_name);\n-\t\t\tif (ref_exists(refname))\n-\t\t\t\tbreak;\n-\t\t}\n-\n-\t\t/*\n-\t\t * short name is non-ambiguous if all previous rules\n-\t\t * haven't resolved to a valid ref\n-\t\t */\n-\t\tif (j == rules_to_fail)\n-\t\t\treturn short_name;\n-\t}\n-\n-\tfree(short_name);\n-\treturn xstrdup(refname);\n-}\n-\n struct expire_reflog_cb {\n \tunsigned int flags;\n \treflog_expiry_should_prune_fn *should_prune_fn;\ndiff --git a/refs.c b/refs.c\nindex 6b2fc39..310b7f5 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -378,3 +378,206 @@ int ref_is_hidden(const char *refname)\n \t}\n \treturn 0;\n }\n+\n+static const char *ref_rev_parse_rules[] = {\n+\t\"%.*s\",\n+\t\"refs/%.*s\",\n+\t\"refs/tags/%.*s\",\n+\t\"refs/heads/%.*s\",\n+\t\"refs/remotes/%.*s\",\n+\t\"refs/remotes/%.*s/HEAD\",\n+\tNULL\n+};\n+\n+int refname_match(const char *abbrev_name, const char *full_name)\n+{\n+\tconst char **p;\n+\tconst int abbrev_name_len = strlen(abbrev_name);\n+\n+\tfor (p = ref_rev_parse_rules; *p; p++) {\n+\t\tif (!strcmp(full_name, mkpath(*p, abbrev_name_len, abbrev_name))) {\n+\t\t\treturn 1;\n+\t\t}\n+\t}\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * *string and *len will only be substituted, and *string returned (for\n+ * later free()ing) if the string passed in is a magic short-hand form\n+ * to name a branch.\n+ */\n+static char *substitute_branch_name(const char **string, int *len)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint ret = interpret_branch_name(*string, *len, &buf);\n+\n+\tif (ret == *len) {\n+\t\tsize_t size;\n+\t\t*string = strbuf_detach(&buf, &size);\n+\t\t*len = size;\n+\t\treturn (char *)*string;\n+\t}\n+\n+\treturn NULL;\n+}\n+\n+int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref)\n+{\n+\tchar *last_branch = substitute_branch_name(&str, &len);\n+\tconst char **p, *r;\n+\tint refs_found = 0;\n+\n+\t*ref = NULL;\n+\tfor (p = ref_rev_parse_rules; *p; p++) {\n+\t\tchar fullref[PATH_MAX];\n+\t\tunsigned char sha1_from_ref[20];\n+\t\tunsigned char *this_result;\n+\t\tint flag;\n+\n+\t\tthis_result = refs_found ? sha1_from_ref : sha1;\n+\t\tmksnpath(fullref, sizeof(fullref), *p, len, str);\n+\t\tr = resolve_ref_unsafe(fullref, RESOLVE_REF_READING,\n+\t\t\t\t       this_result, &flag);\n+\t\tif (r) {\n+\t\t\tif (!refs_found++)\n+\t\t\t\t*ref = xstrdup(r);\n+\t\t\tif (!warn_ambiguous_refs)\n+\t\t\t\tbreak;\n+\t\t} else if ((flag & REF_ISSYMREF) && strcmp(fullref, \"HEAD\")) {\n+\t\t\twarning(\"ignoring dangling symref %s.\", fullref);\n+\t\t} else if ((flag & REF_ISBROKEN) && strchr(fullref, '/')) {\n+\t\t\twarning(\"ignoring broken ref %s.\", fullref);\n+\t\t}\n+\t}\n+\tfree(last_branch);\n+\treturn refs_found;\n+}\n+\n+int dwim_log(const char *str, int len, unsigned char *sha1, char **log)\n+{\n+\tchar *last_branch = substitute_branch_name(&str, &len);\n+\tconst char **p;\n+\tint logs_found = 0;\n+\n+\t*log = NULL;\n+\tfor (p = ref_rev_parse_rules; *p; p++) {\n+\t\tunsigned char hash[20];\n+\t\tchar path[PATH_MAX];\n+\t\tconst char *ref, *it;\n+\n+\t\tmksnpath(path, sizeof(path), *p, len, str);\n+\t\tref = resolve_ref_unsafe(path, RESOLVE_REF_READING,\n+\t\t\t\t\t hash, NULL);\n+\t\tif (!ref)\n+\t\t\tcontinue;\n+\t\tif (reflog_exists(path))\n+\t\t\tit = path;\n+\t\telse if (strcmp(ref, path) && reflog_exists(ref))\n+\t\t\tit = ref;\n+\t\telse\n+\t\t\tcontinue;\n+\t\tif (!logs_found++) {\n+\t\t\t*log = xstrdup(it);\n+\t\t\thashcpy(sha1, hash);\n+\t\t}\n+\t\tif (!warn_ambiguous_refs)\n+\t\t\tbreak;\n+\t}\n+\tfree(last_branch);\n+\treturn logs_found;\n+}\n+\n+char *shorten_unambiguous_ref(const char *refname, int strict)\n+{\n+\tint i;\n+\tstatic char **scanf_fmts;\n+\tstatic int nr_rules;\n+\tchar *short_name;\n+\n+\tif (!nr_rules) {\n+\t\t/*\n+\t\t * Pre-generate scanf formats from ref_rev_parse_rules[].\n+\t\t * Generate a format suitable for scanf from a\n+\t\t * ref_rev_parse_rules rule by interpolating \"%s\" at the\n+\t\t * location of the \"%.*s\".\n+\t\t */\n+\t\tsize_t total_len = 0;\n+\t\tsize_t offset = 0;\n+\n+\t\t/* the rule list is NULL terminated, count them first */\n+\t\tfor (nr_rules = 0; ref_rev_parse_rules[nr_rules]; nr_rules++)\n+\t\t\t/* -2 for strlen(\"%.*s\") - strlen(\"%s\"); +1 for NUL */\n+\t\t\ttotal_len += strlen(ref_rev_parse_rules[nr_rules]) - 2 + 1;\n+\n+\t\tscanf_fmts = xmalloc(nr_rules * sizeof(char *) + total_len);\n+\n+\t\toffset = 0;\n+\t\tfor (i = 0; i < nr_rules; i++) {\n+\t\t\tassert(offset < total_len);\n+\t\t\tscanf_fmts[i] = (char *)&scanf_fmts[nr_rules] + offset;\n+\t\t\toffset += snprintf(scanf_fmts[i], total_len - offset,\n+\t\t\t\t\t   ref_rev_parse_rules[i], 2, \"%s\") + 1;\n+\t\t}\n+\t}\n+\n+\t/* bail out if there are no rules */\n+\tif (!nr_rules)\n+\t\treturn xstrdup(refname);\n+\n+\t/* buffer for scanf result, at most refname must fit */\n+\tshort_name = xstrdup(refname);\n+\n+\t/* skip first rule, it will always match */\n+\tfor (i = nr_rules - 1; i > 0 ; --i) {\n+\t\tint j;\n+\t\tint rules_to_fail = i;\n+\t\tint short_name_len;\n+\n+\t\tif (1 != sscanf(refname, scanf_fmts[i], short_name))\n+\t\t\tcontinue;\n+\n+\t\tshort_name_len = strlen(short_name);\n+\n+\t\t/*\n+\t\t * in strict mode, all (except the matched one) rules\n+\t\t * must fail to resolve to a valid non-ambiguous ref\n+\t\t */\n+\t\tif (strict)\n+\t\t\trules_to_fail = nr_rules;\n+\n+\t\t/*\n+\t\t * check if the short name resolves to a valid ref,\n+\t\t * but use only rules prior to the matched one\n+\t\t */\n+\t\tfor (j = 0; j < rules_to_fail; j++) {\n+\t\t\tconst char *rule = ref_rev_parse_rules[j];\n+\t\t\tchar refname[PATH_MAX];\n+\n+\t\t\t/* skip matched rule */\n+\t\t\tif (i == j)\n+\t\t\t\tcontinue;\n+\n+\t\t\t/*\n+\t\t\t * the short name is ambiguous, if it resolves\n+\t\t\t * (with this previous rule) to a valid ref\n+\t\t\t * read_ref() returns 0 on success\n+\t\t\t */\n+\t\t\tmksnpath(refname, sizeof(refname),\n+\t\t\t\t rule, short_name_len, short_name);\n+\t\t\tif (ref_exists(refname))\n+\t\t\t\tbreak;\n+\t\t}\n+\n+\t\t/*\n+\t\t * short name is non-ambiguous if all previous rules\n+\t\t * haven't resolved to a valid ref\n+\t\t */\n+\t\tif (j == rules_to_fail)\n+\t\t\treturn short_name;\n+\t}\n+\n+\tfree(short_name);\n+\treturn xstrdup(refname);\n+}\n-- \n2.4.2.644.g97b850b-twtrsrc\n"},{"id":"271533","messageId":"1444686725-27660-11-git-send-email-dturner@twopensource.com","threadId":"40543","inReplyTo":"1444686725-27660-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 10/44] refs.c: move warn_if_dangling_symref* to the common code","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-10-12T21:51:30Z","receivedAt":"2015-10-12T21:51:30Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\n\nThese functions do not use any backend specific code so we move\nthem to the common code.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: David Turner <dturner@twopensource.com>\n---\n refs-be-files.c | 52 ----------------------------------------------------\n refs.c          | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 52 insertions(+), 52 deletions(-)\n\ndiff --git a/refs-be-files.c b/refs-be-files.c\nindex 37772a7..7d82864 100644\n--- a/refs-be-files.c\n+++ b/refs-be-files.c\n@@ -1944,58 +1944,6 @@ int peel_ref(const char *refname, unsigned char *sha1)\n \treturn peel_object(base, sha1);\n }\n \n-struct warn_if_dangling_data {\n-\tFILE *fp;\n-\tconst char *refname;\n-\tconst struct string_list *refnames;\n-\tconst char *msg_fmt;\n-};\n-\n-static int warn_if_dangling_symref(const char *refname, const struct object_id *oid,\n-\t\t\t\t   int flags, void *cb_data)\n-{\n-\tstruct warn_if_dangling_data *d = cb_data;\n-\tconst char *resolves_to;\n-\tstruct object_id junk;\n-\n-\tif (!(flags & REF_ISSYMREF))\n-\t\treturn 0;\n-\n-\tresolves_to = resolve_ref_unsafe(refname, 0, junk.hash, NULL);\n-\tif (!resolves_to\n-\t    || (d->refname\n-\t\t? strcmp(resolves_to, d->refname)\n-\t\t: !string_list_has_string(d->refnames, resolves_to))) {\n-\t\treturn 0;\n-\t}\n-\n-\tfprintf(d->fp, d->msg_fmt, refname);\n-\tfputc('\\n', d->fp);\n-\treturn 0;\n-}\n-\n-void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname)\n-{\n-\tstruct warn_if_dangling_data data;\n-\n-\tdata.fp = fp;\n-\tdata.refname = refname;\n-\tdata.refnames = NULL;\n-\tdata.msg_fmt = msg_fmt;\n-\tfor_each_rawref(warn_if_dangling_symref, &data);\n-}\n-\n-void warn_dangling_symrefs(FILE *fp, const char *msg_fmt, const struct string_list *refnames)\n-{\n-\tstruct warn_if_dangling_data data;\n-\n-\tdata.fp = fp;\n-\tdata.refname = NULL;\n-\tdata.refnames = refnames;\n-\tdata.msg_fmt = msg_fmt;\n-\tfor_each_rawref(warn_if_dangling_symref, &data);\n-}\n-\n /*\n  * Call fn for each reference in the specified ref_cache, omitting\n  * references not in the containing_dir of base.  fn is called for all\ndiff --git a/refs.c b/refs.c\nindex 310b7f5..24d5e28 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -581,3 +581,55 @@ char *shorten_unambiguous_ref(const char *refname, int strict)\n \tfree(short_name);\n \treturn xstrdup(refname);\n }\n+\n+struct warn_if_dangling_data {\n+\tFILE *fp;\n+\tconst char *refname;\n+\tconst struct string_list *refnames;\n+\tconst char *msg_fmt;\n+};\n+\n+static int warn_if_dangling_symref(const char *refname, const struct object_id *oid,\n+\t\t\t\t   int flags, void *cb_data)\n+{\n+\tstruct warn_if_dangling_data *d = cb_data;\n+\tconst char *resolves_to;\n+\tstruct object_id junk;\n+\n+\tif (!(flags & REF_ISSYMREF))\n+\t\treturn 0;\n+\n+\tresolves_to = resolve_ref_unsafe(refname, 0, junk.hash, NULL);\n+\tif (!resolves_to\n+\t    || (d->refname\n+\t\t? strcmp(resolves_to, d->refname)\n+\t\t: !string_list_has_string(d->refnames, resolves_to))) {\n+\t\treturn 0;\n+\t}\n+\n+\tfprintf(d->fp, d->msg_fmt, refname);\n+\tfputc('\\n', d->fp);\n+\treturn 0;\n+}\n+\n+void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname)\n+{\n+\tstruct warn_if_dangling_data data;\n+\n+\tdata.fp = fp;\n+\tdata.refname = refname;\n+\tdata.refnames = NULL;\n+\tdata.msg_fmt = msg_fmt;\n+\tfor_each_rawref(warn_if_dangling_symref, &data);\n+}\n+\n+void warn_dangling_symrefs(FILE *fp, const char *msg_fmt, const struct string_list *refnames)\n+{\n+\tstruct warn_if_dangling_data data;\n+\n+\tdata.fp = fp;\n+\tdata.refname = NULL;\n+\tdata.refnames = refnames;\n+\tdata.msg_fmt = msg_fmt;\n+\tfor_each_rawref(warn_if_dangling_symref, &data);\n+}\n-- \n2.4.2.644.g97b850b-twtrsrc\n"},{"id":"271534","messageId":"1444686725-27660-12-git-send-email-dturner@twopensource.com","threadId":"40543","inReplyTo":"1444686725-27660-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 11/44] refs.c: move read_ref, read_ref_full and ref_exists to the common code","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-10-12T21:51:31Z","receivedAt":"2015-10-12T21:51:31Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\n\nThese functions do not depend on the backend implementation so we\nmove them to the common code.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: David Turner <dturner@twopensource.com>\n---\n refs-be-files.c | 18 ------------------\n refs.c          | 18 ++++++++++++++++++\n 2 files changed, 18 insertions(+), 18 deletions(-)\n\ndiff --git a/refs-be-files.c b/refs-be-files.c\nindex 7d82864..bd0dbb5 100644\n--- a/refs-be-files.c\n+++ b/refs-be-files.c\n@@ -1791,24 +1791,6 @@ struct ref_filter {\n \tvoid *cb_data;\n };\n \n-int read_ref_full(const char *refname, int resolve_flags, unsigned char *sha1, int *flags)\n-{\n-\tif (resolve_ref_unsafe(refname, resolve_flags, sha1, flags))\n-\t\treturn 0;\n-\treturn -1;\n-}\n-\n-int read_ref(const char *refname, unsigned char *sha1)\n-{\n-\treturn read_ref_full(refname, RESOLVE_REF_READING, sha1, NULL);\n-}\n-\n-int ref_exists(const char *refname)\n-{\n-\tunsigned char sha1[20];\n-\treturn !!resolve_ref_unsafe(refname, RESOLVE_REF_READING, sha1, NULL);\n-}\n-\n static int filter_refs(const char *refname, const struct object_id *oid,\n \t\t\t   int flags, void *data)\n {\ndiff --git a/refs.c b/refs.c\nindex 24d5e28..242f66d 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -633,3 +633,21 @@ void warn_dangling_symrefs(FILE *fp, const char *msg_fmt, const struct string_li\n \tdata.msg_fmt = msg_fmt;\n \tfor_each_rawref(warn_if_dangling_symref, &data);\n }\n+\n+int read_ref_full(const char *refname, int resolve_flags, unsigned char *sha1, int *flags)\n+{\n+\tif (resolve_ref_unsafe(refname, resolve_flags, sha1, flags))\n+\t\treturn 0;\n+\treturn -1;\n+}\n+\n+int read_ref(const char *refname, unsigned char *sha1)\n+{\n+\treturn read_ref_full(refname, RESOLVE_REF_READING, sha1, NULL);\n+}\n+\n+int ref_exists(const char *refname)\n+{\n+\tunsigned char sha1[20];\n+\treturn !!resolve_ref_unsafe(refname, RESOLVE_REF_READING, sha1, NULL);\n+}\n-- \n2.4.2.644.g97b850b-twtrsrc\n"},{"id":"271501","messageId":"1444686725-27660-13-git-send-email-dturner@twopensource.com","threadId":"40543","inReplyTo":"1444686725-27660-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 12/44] refs.c: move resolve_refdup to common","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-10-12T21:51:32Z","receivedAt":"2015-10-12T21:51:32Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\n\nThis function can be shared across all refs backends so move it\nto the common code.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: David Turner <dturner@twopensource.com>\n---\n refs-be-files.c | 7 -------\n refs.c          | 7 +++++++\n 2 files changed, 7 insertions(+), 7 deletions(-)\n\ndiff --git a/refs-be-files.c b/refs-be-files.c\nindex bd0dbb5..045bf09 100644\n--- a/refs-be-files.c\n+++ b/refs-be-files.c\n@@ -1777,13 +1777,6 @@ const char *resolve_ref_unsafe(const char *refname, int resolve_flags,\n \treturn ret;\n }\n \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(refname, resolve_flags,\n-\t\t\t\t\t\t  sha1, flags));\n-}\n-\n /* The argument to filter_refs */\n struct ref_filter {\n \tconst char *pattern;\ndiff --git a/refs.c b/refs.c\nindex 242f66d..039e5c0 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -651,3 +651,10 @@ int ref_exists(const char *refname)\n \tunsigned char sha1[20];\n \treturn !!resolve_ref_unsafe(refname, RESOLVE_REF_READING, sha1, NULL);\n }\n+\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(refname, resolve_flags,\n+\t\t\t\t\t\t  sha1, flags));\n+}\n-- \n2.4.2.644.g97b850b-twtrsrc\n"},{"id":"271500","messageId":"1444686725-27660-14-git-send-email-dturner@twopensource.com","threadId":"40543","inReplyTo":"1444686725-27660-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 13/44] refs.c: move check_refname_format to the common code","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-10-12T21:51:33Z","receivedAt":"2015-10-12T21:51:33Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\n\nThis function does not contain any backend specific code so we\nmove it to the common code.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: David Turner <dturner@twopensource.com>\n---\n refs-be-files.c | 109 --------------------------------------------------------\n refs.c          | 109 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 109 insertions(+), 109 deletions(-)\n\ndiff --git a/refs-be-files.c b/refs-be-files.c\nindex 045bf09..9c57dcc 100644\n--- a/refs-be-files.c\n+++ b/refs-be-files.c\n@@ -14,27 +14,6 @@ struct ref_lock {\n };\n \n /*\n- * How to handle various characters in refnames:\n- * 0: An acceptable character for refs\n- * 1: End-of-component\n- * 2: ., look for a preceding . to reject .. in refs\n- * 3: {, look for a preceding @ to reject @{ in refs\n- * 4: A bad character: ASCII control characters, and\n- *    \":\", \"?\", \"[\", \"\\\", \"^\", \"~\", SP, or TAB\n- * 5: *, reject unless REFNAME_REFSPEC_PATTERN is set\n- */\n-static unsigned char refname_disposition[256] = {\n-\t1, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,\n-\t4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,\n-\t4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 2, 1,\n-\t0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 4,\n-\t0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n-\t0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 0, 4, 0,\n-\t0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n-\t0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 4, 4\n-};\n-\n-/*\n  * Flag passed to lock_ref_sha1_basic() telling it to tolerate broken\n  * refs (i.e., because the reference is about to be deleted anyway).\n  */\n@@ -69,94 +48,6 @@ static unsigned char refname_disposition[256] = {\n  * value to ref_update::flags\n  */\n \n-/*\n- * Try to read one refname component from the front of refname.\n- * Return the length of the component found, or -1 if the component is\n- * not legal.  It is legal if it is something reasonable to have under\n- * \".git/refs/\"; We do not like it if:\n- *\n- * - any path component of it begins with \".\", or\n- * - it has double dots \"..\", or\n- * - it has ASCII control characters, or\n- * - it has \":\", \"?\", \"[\", \"\\\", \"^\", \"~\", SP, or TAB anywhere, or\n- * - it has \"*\" anywhere unless REFNAME_REFSPEC_PATTERN is set, or\n- * - it ends with a \"/\", or\n- * - it ends with \".lock\", or\n- * - it contains a \"@{\" portion\n- */\n-static int check_refname_component(const char *refname, int *flags)\n-{\n-\tconst char *cp;\n-\tchar last = '\\0';\n-\n-\tfor (cp = refname; ; cp++) {\n-\t\tint ch = *cp & 255;\n-\t\tunsigned char disp = refname_disposition[ch];\n-\t\tswitch (disp) {\n-\t\tcase 1:\n-\t\t\tgoto out;\n-\t\tcase 2:\n-\t\t\tif (last == '.')\n-\t\t\t\treturn -1; /* Refname contains \"..\". */\n-\t\t\tbreak;\n-\t\tcase 3:\n-\t\t\tif (last == '@')\n-\t\t\t\treturn -1; /* Refname contains \"@{\". */\n-\t\t\tbreak;\n-\t\tcase 4:\n-\t\t\treturn -1;\n-\t\tcase 5:\n-\t\t\tif (!(*flags & REFNAME_REFSPEC_PATTERN))\n-\t\t\t\treturn -1; /* refspec can't be a pattern */\n-\n-\t\t\t/*\n-\t\t\t * Unset the pattern flag so that we only accept\n-\t\t\t * a single asterisk for one side of refspec.\n-\t\t\t */\n-\t\t\t*flags &= ~ REFNAME_REFSPEC_PATTERN;\n-\t\t\tbreak;\n-\t\t}\n-\t\tlast = ch;\n-\t}\n-out:\n-\tif (cp == refname)\n-\t\treturn 0; /* Component has zero length. */\n-\tif (refname[0] == '.')\n-\t\treturn -1; /* Component starts with '.'. */\n-\tif (cp - refname >= LOCK_SUFFIX_LEN &&\n-\t    !memcmp(cp - LOCK_SUFFIX_LEN, LOCK_SUFFIX, LOCK_SUFFIX_LEN))\n-\t\treturn -1; /* Refname ends with \".lock\". */\n-\treturn cp - refname;\n-}\n-\n-int check_refname_format(const char *refname, int flags)\n-{\n-\tint component_len, component_count = 0;\n-\n-\tif (!strcmp(refname, \"@\"))\n-\t\t/* Refname is a single character '@'. */\n-\t\treturn -1;\n-\n-\twhile (1) {\n-\t\t/* We are at the start of a path component. */\n-\t\tcomponent_len = check_refname_component(refname, &flags);\n-\t\tif (component_len <= 0)\n-\t\t\treturn -1;\n-\n-\t\tcomponent_count++;\n-\t\tif (refname[component_len] == '\\0')\n-\t\t\tbreak;\n-\t\t/* Skip to next component. */\n-\t\trefname += component_len + 1;\n-\t}\n-\n-\tif (refname[component_len - 1] == '.')\n-\t\treturn -1; /* Refname ends with '.'. */\n-\tif (!(flags & REFNAME_ALLOW_ONELEVEL) && component_count < 2)\n-\t\treturn -1; /* Refname has only one component. */\n-\treturn 0;\n-}\n-\n struct ref_entry;\n \n /*\ndiff --git a/refs.c b/refs.c\nindex 039e5c0..e9cc2d4 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -658,3 +658,112 @@ char *resolve_refdup(const char *refname, int resolve_flags,\n \treturn xstrdup_or_null(resolve_ref_unsafe(refname, resolve_flags,\n \t\t\t\t\t\t  sha1, flags));\n }\n+\n+/*\n+ * How to handle various characters in refnames:\n+ * 0: An acceptable character for refs\n+ * 1: End-of-component\n+ * 2: ., look for a preceding . to reject .. in refs\n+ * 3: {, look for a preceding @ to reject @{ in refs\n+ * 4: A bad character: ASCII control characters, and\n+ *    \":\", \"?\", \"[\", \"\\\", \"^\", \"~\", SP, or TAB\n+ * 5: *, reject unless REFNAME_REFSPEC_PATTERN is set\n+ */\n+static unsigned char refname_disposition[256] = {\n+\t1, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,\n+\t4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,\n+\t4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 2, 1,\n+\t0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 4,\n+\t0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n+\t0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 0, 4, 0,\n+\t0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n+\t0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 4, 4\n+};\n+\n+/*\n+ * Try to read one refname component from the front of refname.\n+ * Return the length of the component found, or -1 if the component is\n+ * not legal.  It is legal if it is something reasonable to have under\n+ * \".git/refs/\"; We do not like it if:\n+ *\n+ * - any path component of it begins with \".\", or\n+ * - it has double dots \"..\", or\n+ * - it has ASCII control characters, or\n+ * - it has \":\", \"?\", \"[\", \"\\\", \"^\", \"~\", SP, or TAB anywhere, or\n+ * - it has \"*\" anywhere unless REFNAME_REFSPEC_PATTERN is set, or\n+ * - it ends with a \"/\", or\n+ * - it ends with \".lock\", or\n+ * - it contains a \"@{\" portion\n+ */\n+static int check_refname_component(const char *refname, int *flags)\n+{\n+\tconst char *cp;\n+\tchar last = '\\0';\n+\n+\tfor (cp = refname; ; cp++) {\n+\t\tint ch = *cp & 255;\n+\t\tunsigned char disp = refname_disposition[ch];\n+\t\tswitch (disp) {\n+\t\tcase 1:\n+\t\t\tgoto out;\n+\t\tcase 2:\n+\t\t\tif (last == '.')\n+\t\t\t\treturn -1; /* Refname contains \"..\". */\n+\t\t\tbreak;\n+\t\tcase 3:\n+\t\t\tif (last == '@')\n+\t\t\t\treturn -1; /* Refname contains \"@{\". */\n+\t\t\tbreak;\n+\t\tcase 4:\n+\t\t\treturn -1;\n+\t\tcase 5:\n+\t\t\tif (!(*flags & REFNAME_REFSPEC_PATTERN))\n+\t\t\t\treturn -1; /* refspec can't be a pattern */\n+\n+\t\t\t/*\n+\t\t\t * Unset the pattern flag so that we only accept\n+\t\t\t * a single asterisk for one side of refspec.\n+\t\t\t */\n+\t\t\t*flags &= ~ REFNAME_REFSPEC_PATTERN;\n+\t\t\tbreak;\n+\t\t}\n+\t\tlast = ch;\n+\t}\n+out:\n+\tif (cp == refname)\n+\t\treturn 0; /* Component has zero length. */\n+\tif (refname[0] == '.')\n+\t\treturn -1; /* Component starts with '.'. */\n+\tif (cp - refname >= LOCK_SUFFIX_LEN &&\n+\t    !memcmp(cp - LOCK_SUFFIX_LEN, LOCK_SUFFIX, LOCK_SUFFIX_LEN))\n+\t\treturn -1; /* Refname ends with \".lock\". */\n+\treturn cp - refname;\n+}\n+\n+int check_refname_format(const char *refname, int flags)\n+{\n+\tint component_len, component_count = 0;\n+\n+\tif (!strcmp(refname, \"@\"))\n+\t\t/* Refname is a single character '@'. */\n+\t\treturn -1;\n+\n+\twhile (1) {\n+\t\t/* We are at the start of a path component. */\n+\t\tcomponent_len = check_refname_component(refname, &flags);\n+\t\tif (component_len <= 0)\n+\t\t\treturn -1;\n+\n+\t\tcomponent_count++;\n+\t\tif (refname[component_len] == '\\0')\n+\t\t\tbreak;\n+\t\t/* Skip to next component. */\n+\t\trefname += component_len + 1;\n+\t}\n+\n+\tif (refname[component_len - 1] == '.')\n+\t\treturn -1; /* Refname ends with '.'. */\n+\tif (!(flags & REFNAME_ALLOW_ONELEVEL) && component_count < 2)\n+\t\treturn -1; /* Refname has only one component. */\n+\treturn 0;\n+}\n-- \n2.4.2.644.g97b850b-twtrsrc\n"},{"id":"271532","messageId":"1444686725-27660-15-git-send-email-dturner@twopensource.com","threadId":"40543","inReplyTo":"1444686725-27660-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 14/44] refs.c: move is_branch to the common code","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-10-12T21:51:34Z","receivedAt":"2015-10-12T21:51:34Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: David Turner <dturner@twopensource.com>\n---\n refs-be-files.c | 5 -----\n refs.c          | 5 +++++\n 2 files changed, 5 insertions(+), 5 deletions(-)\n\ndiff --git a/refs-be-files.c b/refs-be-files.c\nindex 9c57dcc..1214d9e 100644\n--- a/refs-be-files.c\n+++ b/refs-be-files.c\n@@ -2957,11 +2957,6 @@ static int log_ref_write(const char *refname, const unsigned char *old_sha1,\n \treturn ret;\n }\n \n-int is_branch(const char *refname)\n-{\n-\treturn !strcmp(refname, \"HEAD\") || starts_with(refname, \"refs/heads/\");\n-}\n-\n /*\n  * Write sha1 into the open lockfile, then close the lockfile. On\n  * errors, rollback the lockfile, fill in *err and\ndiff --git a/refs.c b/refs.c\nindex e9cc2d4..bc8750c 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -767,3 +767,8 @@ int check_refname_format(const char *refname, int flags)\n \t\treturn -1; /* Refname has only one component. */\n \treturn 0;\n }\n+\n+int is_branch(const char *refname)\n+{\n+\treturn !strcmp(refname, \"HEAD\") || starts_with(refname, \"refs/heads/\");\n+}\n-- \n2.4.2.644.g97b850b-twtrsrc\n"},{"id":"271502","messageId":"1444686725-27660-16-git-send-email-dturner@twopensource.com","threadId":"40543","inReplyTo":"1444686725-27660-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 15/44] refs.c: move prettify_refname to the common code","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-10-12T21:51:35Z","receivedAt":"2015-10-12T21:51:35Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\n---\n refs-be-files.c | 9 ---------\n refs.c          | 9 +++++++++\n 2 files changed, 9 insertions(+), 9 deletions(-)\n\ndiff --git a/refs-be-files.c b/refs-be-files.c\nindex 1214d9e..ecf2b33 100644\n--- a/refs-be-files.c\n+++ b/refs-be-files.c\n@@ -2052,15 +2052,6 @@ int for_each_rawref(each_ref_fn fn, void *cb_data)\n \t\t\t       DO_FOR_EACH_INCLUDE_BROKEN, cb_data);\n }\n \n-const char *prettify_refname(const char *name)\n-{\n-\treturn name + (\n-\t\tstarts_with(name, \"refs/heads/\") ? 11 :\n-\t\tstarts_with(name, \"refs/tags/\") ? 10 :\n-\t\tstarts_with(name, \"refs/remotes/\") ? 13 :\n-\t\t0);\n-}\n-\n static void unlock_ref(struct ref_lock *lock)\n {\n \t/* Do not free lock->lk -- atexit() still looks at them */\ndiff --git a/refs.c b/refs.c\nindex bc8750c..44ee4f4 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -772,3 +772,12 @@ int is_branch(const char *refname)\n {\n \treturn !strcmp(refname, \"HEAD\") || starts_with(refname, \"refs/heads/\");\n }\n+\n+const char *prettify_refname(const char *name)\n+{\n+\treturn name + (\n+\t\tstarts_with(name, \"refs/heads/\") ? 11 :\n+\t\tstarts_with(name, \"refs/tags/\") ? 10 :\n+\t\tstarts_with(name, \"refs/remotes/\") ? 13 :\n+\t\t0);\n+}\n-- \n2.4.2.644.g97b850b-twtrsrc\n"},{"id":"271499","messageId":"1444686725-27660-17-git-send-email-dturner@twopensource.com","threadId":"40543","inReplyTo":"1444686725-27660-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 16/44] refs.c: move ref iterators to the common code","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-10-12T21:51:36Z","receivedAt":"2015-10-12T21:51:36Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\n---\n refs-be-files.c | 82 ---------------------------------------------------------\n refs.c          | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 81 insertions(+), 82 deletions(-)\n\ndiff --git a/refs-be-files.c b/refs-be-files.c\nindex ecf2b33..46fa43c 100644\n--- a/refs-be-files.c\n+++ b/refs-be-files.c\n@@ -1668,23 +1668,6 @@ const char *resolve_ref_unsafe(const char *refname, int resolve_flags,\n \treturn ret;\n }\n \n-/* The argument to filter_refs */\n-struct ref_filter {\n-\tconst char *pattern;\n-\teach_ref_fn *fn;\n-\tvoid *cb_data;\n-};\n-\n-static int filter_refs(const char *refname, const struct object_id *oid,\n-\t\t\t   int flags, void *data)\n-{\n-\tstruct ref_filter *filter = (struct ref_filter *)data;\n-\n-\tif (wildmatch(filter->pattern, refname, 0, NULL))\n-\t\treturn 0;\n-\treturn filter->fn(refname, oid, flags, filter->cb_data);\n-}\n-\n enum peel_status {\n \t/* object was peeled successfully: */\n \tPEEL_PEELED = 0,\n@@ -1950,37 +1933,6 @@ int for_each_ref_in_submodule(const char *submodule, const char *prefix,\n {\n \treturn do_for_each_ref(get_ref_cache(submodule), prefix, fn, strlen(prefix), 0, cb_data);\n }\n-\n-int for_each_tag_ref(each_ref_fn fn, void *cb_data)\n-{\n-\treturn for_each_ref_in(\"refs/tags/\", fn, cb_data);\n-}\n-\n-int for_each_tag_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)\n-{\n-\treturn for_each_ref_in_submodule(submodule, \"refs/tags/\", fn, cb_data);\n-}\n-\n-int for_each_branch_ref(each_ref_fn fn, void *cb_data)\n-{\n-\treturn for_each_ref_in(\"refs/heads/\", fn, cb_data);\n-}\n-\n-int for_each_branch_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)\n-{\n-\treturn for_each_ref_in_submodule(submodule, \"refs/heads/\", fn, cb_data);\n-}\n-\n-int for_each_remote_ref(each_ref_fn fn, void *cb_data)\n-{\n-\treturn for_each_ref_in(\"refs/remotes/\", fn, cb_data);\n-}\n-\n-int for_each_remote_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)\n-{\n-\treturn for_each_ref_in_submodule(submodule, \"refs/remotes/\", fn, cb_data);\n-}\n-\n int for_each_replace_ref(each_ref_fn fn, void *cb_data)\n {\n \treturn do_for_each_ref(&ref_cache, git_replace_ref_base, fn,\n@@ -2012,40 +1964,6 @@ int for_each_namespaced_ref(each_ref_fn fn, void *cb_data)\n \treturn ret;\n }\n \n-int for_each_glob_ref_in(each_ref_fn fn, const char *pattern,\n-\tconst char *prefix, void *cb_data)\n-{\n-\tstruct strbuf real_pattern = STRBUF_INIT;\n-\tstruct ref_filter filter;\n-\tint ret;\n-\n-\tif (!prefix && !starts_with(pattern, \"refs/\"))\n-\t\tstrbuf_addstr(&real_pattern, \"refs/\");\n-\telse if (prefix)\n-\t\tstrbuf_addstr(&real_pattern, prefix);\n-\tstrbuf_addstr(&real_pattern, pattern);\n-\n-\tif (!has_glob_specials(pattern)) {\n-\t\t/* Append implied '/' '*' if not present. */\n-\t\tstrbuf_complete(&real_pattern, '/');\n-\t\t/* No need to check for '*', there is none. */\n-\t\tstrbuf_addch(&real_pattern, '*');\n-\t}\n-\n-\tfilter.pattern = real_pattern.buf;\n-\tfilter.fn = fn;\n-\tfilter.cb_data = cb_data;\n-\tret = for_each_ref(filter_refs, &filter);\n-\n-\tstrbuf_release(&real_pattern);\n-\treturn ret;\n-}\n-\n-int for_each_glob_ref(each_ref_fn fn, const char *pattern, void *cb_data)\n-{\n-\treturn for_each_glob_ref_in(fn, pattern, NULL, cb_data);\n-}\n-\n int for_each_rawref(each_ref_fn fn, void *cb_data)\n {\n \treturn do_for_each_ref(&ref_cache, \"\", fn, 0,\ndiff --git a/refs.c b/refs.c\nindex 44ee4f4..7714dad 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -781,3 +781,84 @@ const char *prettify_refname(const char *name)\n \t\tstarts_with(name, \"refs/remotes/\") ? 13 :\n \t\t0);\n }\n+\n+/* The argument to filter_refs */\n+struct ref_filter {\n+\tconst char *pattern;\n+\teach_ref_fn *fn;\n+\tvoid *cb_data;\n+};\n+\n+static int filter_refs(const char *refname, const struct object_id *oid, int flags,\n+\t\t       void *data)\n+{\n+\tstruct ref_filter *filter = (struct ref_filter *)data;\n+\tif (wildmatch(filter->pattern, refname, 0, NULL))\n+\t\treturn 0;\n+\treturn filter->fn(refname, oid, flags, filter->cb_data);\n+}\n+\n+int for_each_glob_ref_in(each_ref_fn fn, const char *pattern,\n+\tconst char *prefix, void *cb_data)\n+{\n+\tstruct strbuf real_pattern = STRBUF_INIT;\n+\tstruct ref_filter filter;\n+\tint ret;\n+\n+\tif (!prefix && !starts_with(pattern, \"refs/\"))\n+\t\tstrbuf_addstr(&real_pattern, \"refs/\");\n+\telse if (prefix)\n+\t\tstrbuf_addstr(&real_pattern, prefix);\n+\tstrbuf_addstr(&real_pattern, pattern);\n+\n+\tif (!has_glob_specials(pattern)) {\n+\t\t/* Append implied '/' '*' if not present. */\n+\t\tif (real_pattern.buf[real_pattern.len - 1] != '/')\n+\t\t\tstrbuf_addch(&real_pattern, '/');\n+\t\t/* No need to check for '*', there is none. */\n+\t\tstrbuf_addch(&real_pattern, '*');\n+\t}\n+\n+\tfilter.pattern = real_pattern.buf;\n+\tfilter.fn = fn;\n+\tfilter.cb_data = cb_data;\n+\tret = for_each_ref(filter_refs, &filter);\n+\n+\tstrbuf_release(&real_pattern);\n+\treturn ret;\n+}\n+\n+int for_each_glob_ref(each_ref_fn fn, const char *pattern, void *cb_data)\n+{\n+\treturn for_each_glob_ref_in(fn, pattern, NULL, cb_data);\n+}\n+\n+int for_each_tag_ref(each_ref_fn fn, void *cb_data)\n+{\n+\treturn for_each_ref_in(\"refs/tags/\", fn, cb_data);\n+}\n+\n+int for_each_tag_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)\n+{\n+\treturn for_each_ref_in_submodule(submodule, \"refs/tags/\", fn, cb_data);\n+}\n+\n+int for_each_branch_ref(each_ref_fn fn, void *cb_data)\n+{\n+\treturn for_each_ref_in(\"refs/heads/\", fn, cb_data);\n+}\n+\n+int for_each_branch_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)\n+{\n+\treturn for_each_ref_in_submodule(submodule, \"refs/heads/\", fn, cb_data);\n+}\n+\n+int for_each_remote_ref(each_ref_fn fn, void *cb_data)\n+{\n+\treturn for_each_ref_in(\"refs/remotes/\", fn, cb_data);\n+}\n+\n+int for_each_remote_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)\n+{\n+\treturn for_each_ref_in_submodule(submodule, \"refs/remotes/\", fn, cb_data);\n+}\n-- \n2.4.2.644.g97b850b-twtrsrc\n"},{"id":"271503","messageId":"1444686725-27660-18-git-send-email-dturner@twopensource.com","threadId":"40543","inReplyTo":"1444686725-27660-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 17/44] refs.c: move head_ref_namespaced to the common code","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-10-12T21:51:37Z","receivedAt":"2015-10-12T21:51:37Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\n---\n refs-be-files.c | 15 ---------------\n refs.c          | 15 +++++++++++++++\n 2 files changed, 15 insertions(+), 15 deletions(-)\n\ndiff --git a/refs-be-files.c b/refs-be-files.c\nindex 46fa43c..34ed220 100644\n--- a/refs-be-files.c\n+++ b/refs-be-files.c\n@@ -1939,21 +1939,6 @@ int for_each_replace_ref(each_ref_fn fn, void *cb_data)\n \t\t\t       strlen(git_replace_ref_base), 0, cb_data);\n }\n \n-int head_ref_namespaced(each_ref_fn fn, void *cb_data)\n-{\n-\tstruct strbuf buf = STRBUF_INIT;\n-\tint ret = 0;\n-\tstruct object_id oid;\n-\tint flag;\n-\n-\tstrbuf_addf(&buf, \"%sHEAD\", get_git_namespace());\n-\tif (!read_ref_full(buf.buf, RESOLVE_REF_READING, oid.hash, &flag))\n-\t\tret = fn(buf.buf, &oid, flag, cb_data);\n-\tstrbuf_release(&buf);\n-\n-\treturn ret;\n-}\n-\n int for_each_namespaced_ref(each_ref_fn fn, void *cb_data)\n {\n \tstruct strbuf buf = STRBUF_INIT;\ndiff --git a/refs.c b/refs.c\nindex 7714dad..0f4e19a 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -862,3 +862,18 @@ int for_each_remote_ref_submodule(const char *submodule, each_ref_fn fn, void *c\n {\n \treturn for_each_ref_in_submodule(submodule, \"refs/remotes/\", fn, cb_data);\n }\n+\n+int head_ref_namespaced(each_ref_fn fn, void *cb_data)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint ret = 0;\n+\tstruct object_id oid;\n+\tint flag;\n+\n+\tstrbuf_addf(&buf, \"%sHEAD\", get_git_namespace());\n+\tif (!read_ref_full(buf.buf, RESOLVE_REF_READING, oid.hash, &flag))\n+\t\tret = fn(buf.buf, &oid, flag, cb_data);\n+\tstrbuf_release(&buf);\n+\n+\treturn ret;\n+}\n-- \n2.4.2.644.g97b850b-twtrsrc\n"},{"id":"271526","messageId":"1444686725-27660-19-git-send-email-dturner@twopensource.com","threadId":"40543","inReplyTo":"1444686725-27660-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 18/44] refs: move transaction functions into common code","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-10-12T21:51:38Z","receivedAt":"2015-10-12T21:51:38Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"The common ref code will build up a ref transaction.  Backends will\nthen commit it.  So the transaction creation and update functions should\nbe in the common code.  We also need to move the ref structs into\nthe common code so that alternate backends can access them.\n\nLater, we will modify struct ref_update to support alternate backends.\n\nSigned-off-by: David Turner <dturner@twopensource.com>\n---\n refs-be-files.c | 198 --------------------------------------------------------\n refs.c          | 108 +++++++++++++++++++++++++++++++\n refs.h          |  91 +++++++++++++++++++++++++-\n 3 files changed, 198 insertions(+), 199 deletions(-)\n\ndiff --git a/refs-be-files.c b/refs-be-files.c\nindex 34ed220..1308955 100644\n--- a/refs-be-files.c\n+++ b/refs-be-files.c\n@@ -13,41 +13,6 @@ struct ref_lock {\n \tstruct object_id old_oid;\n };\n \n-/*\n- * Flag passed to lock_ref_sha1_basic() telling it to tolerate broken\n- * refs (i.e., because the reference is about to be deleted anyway).\n- */\n-#define REF_DELETING\t0x02\n-\n-/*\n- * Used as a flag in ref_update::flags when a loose ref is being\n- * pruned.\n- */\n-#define REF_ISPRUNING\t0x04\n-\n-/*\n- * Used as a flag in ref_update::flags when the reference should be\n- * updated to new_sha1.\n- */\n-#define REF_HAVE_NEW\t0x08\n-\n-/*\n- * Used as a flag in ref_update::flags when old_sha1 should be\n- * checked.\n- */\n-#define REF_HAVE_OLD\t0x10\n-\n-/*\n- * Used as a flag in ref_update::flags when the lockfile needs to be\n- * committed.\n- */\n-#define REF_NEEDS_COMMIT 0x20\n-\n-/*\n- * 0x40 is REF_FORCE_CREATE_REFLOG, so skip it if you're adding a\n- * value to ref_update::flags\n- */\n-\n struct ref_entry;\n \n /*\n@@ -3243,169 +3208,6 @@ int for_each_reflog(each_ref_fn fn, void *cb_data)\n \treturn retval;\n }\n \n-/**\n- * Information needed for a single ref update. Set new_sha1 to the new\n- * value or to null_sha1 to delete the ref. To check the old value\n- * while the ref is locked, set (flags & REF_HAVE_OLD) and set\n- * old_sha1 to the old value, or to null_sha1 to ensure the ref does\n- * not exist before update.\n- */\n-struct ref_update {\n-\t/*\n-\t * If (flags & REF_HAVE_NEW), set the reference to this value:\n-\t */\n-\tunsigned char new_sha1[20];\n-\t/*\n-\t * If (flags & REF_HAVE_OLD), check that the reference\n-\t * previously had this value:\n-\t */\n-\tunsigned char old_sha1[20];\n-\t/*\n-\t * One or more of REF_HAVE_NEW, REF_HAVE_OLD, REF_NODEREF,\n-\t * REF_DELETING, and REF_ISPRUNING:\n-\t */\n-\tunsigned int flags;\n-\tstruct ref_lock *lock;\n-\tint type;\n-\tchar *msg;\n-\tconst char refname[FLEX_ARRAY];\n-};\n-\n-/*\n- * Transaction states.\n- * OPEN:   The transaction is in a valid state and can accept new updates.\n- *         An OPEN transaction can be committed.\n- * CLOSED: A closed transaction is no longer active and no other operations\n- *         than free can be used on it in this state.\n- *         A transaction can either become closed by successfully committing\n- *         an active transaction or if there is a failure while building\n- *         the transaction thus rendering it failed/inactive.\n- */\n-enum ref_transaction_state {\n-\tREF_TRANSACTION_OPEN   = 0,\n-\tREF_TRANSACTION_CLOSED = 1\n-};\n-\n-/*\n- * Data structure for holding a reference transaction, which can\n- * consist of checks and updates to multiple references, carried out\n- * as atomically as possible.  This structure is opaque to callers.\n- */\n-struct ref_transaction {\n-\tstruct ref_update **updates;\n-\tsize_t alloc;\n-\tsize_t nr;\n-\tenum ref_transaction_state state;\n-};\n-\n-struct ref_transaction *ref_transaction_begin(struct strbuf *err)\n-{\n-\tassert(err);\n-\n-\treturn xcalloc(1, sizeof(struct ref_transaction));\n-}\n-\n-void ref_transaction_free(struct ref_transaction *transaction)\n-{\n-\tint i;\n-\n-\tif (!transaction)\n-\t\treturn;\n-\n-\tfor (i = 0; i < transaction->nr; i++) {\n-\t\tfree(transaction->updates[i]->msg);\n-\t\tfree(transaction->updates[i]);\n-\t}\n-\tfree(transaction->updates);\n-\tfree(transaction);\n-}\n-\n-static struct ref_update *add_update(struct ref_transaction *transaction,\n-\t\t\t\t     const char *refname)\n-{\n-\tsize_t len = strlen(refname) + 1;\n-\tstruct ref_update *update = xcalloc(1, sizeof(*update) + len);\n-\n-\tmemcpy((char *)update->refname, refname, len); /* includes NUL */\n-\tALLOC_GROW(transaction->updates, transaction->nr + 1, transaction->alloc);\n-\ttransaction->updates[transaction->nr++] = update;\n-\treturn update;\n-}\n-\n-int ref_transaction_update(struct ref_transaction *transaction,\n-\t\t\t   const char *refname,\n-\t\t\t   const unsigned char *new_sha1,\n-\t\t\t   const unsigned char *old_sha1,\n-\t\t\t   unsigned int flags, const char *msg,\n-\t\t\t   struct strbuf *err)\n-{\n-\tstruct ref_update *update;\n-\n-\tassert(err);\n-\n-\tif (transaction->state != REF_TRANSACTION_OPEN)\n-\t\tdie(\"BUG: update called for transaction that is not open\");\n-\n-\tif (new_sha1 && !is_null_sha1(new_sha1) &&\n-\t    check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {\n-\t\tstrbuf_addf(err, \"refusing to update ref with bad name %s\",\n-\t\t\t    refname);\n-\t\treturn -1;\n-\t}\n-\n-\tupdate = add_update(transaction, refname);\n-\tif (new_sha1) {\n-\t\thashcpy(update->new_sha1, new_sha1);\n-\t\tflags |= REF_HAVE_NEW;\n-\t}\n-\tif (old_sha1) {\n-\t\thashcpy(update->old_sha1, old_sha1);\n-\t\tflags |= REF_HAVE_OLD;\n-\t}\n-\tupdate->flags = flags;\n-\tif (msg)\n-\t\tupdate->msg = xstrdup(msg);\n-\treturn 0;\n-}\n-\n-int ref_transaction_create(struct ref_transaction *transaction,\n-\t\t\t   const char *refname,\n-\t\t\t   const unsigned char *new_sha1,\n-\t\t\t   unsigned int flags, const char *msg,\n-\t\t\t   struct strbuf *err)\n-{\n-\tif (!new_sha1 || is_null_sha1(new_sha1))\n-\t\tdie(\"BUG: create called without valid new_sha1\");\n-\treturn ref_transaction_update(transaction, refname, new_sha1,\n-\t\t\t\t      null_sha1, flags, msg, err);\n-}\n-\n-int ref_transaction_delete(struct ref_transaction *transaction,\n-\t\t\t   const char *refname,\n-\t\t\t   const unsigned char *old_sha1,\n-\t\t\t   unsigned int flags, const char *msg,\n-\t\t\t   struct strbuf *err)\n-{\n-\tif (old_sha1 && is_null_sha1(old_sha1))\n-\t\tdie(\"BUG: delete called with old_sha1 set to zeros\");\n-\treturn ref_transaction_update(transaction, refname,\n-\t\t\t\t      null_sha1, old_sha1,\n-\t\t\t\t      flags, msg, err);\n-}\n-\n-int ref_transaction_verify(struct ref_transaction *transaction,\n-\t\t\t   const char *refname,\n-\t\t\t   const unsigned char *old_sha1,\n-\t\t\t   unsigned int flags,\n-\t\t\t   struct strbuf *err)\n-{\n-\tif (!old_sha1)\n-\t\tdie(\"BUG: verify called with old_sha1 set to NULL\");\n-\treturn ref_transaction_update(transaction, refname,\n-\t\t\t\t      NULL, old_sha1,\n-\t\t\t\t      flags, NULL, err);\n-}\n-\n static int ref_update_reject_duplicates(struct string_list *refnames,\n \t\t\t\t\tstruct strbuf *err)\n {\ndiff --git a/refs.c b/refs.c\nindex 0f4e19a..25ad3b5 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -877,3 +877,111 @@ int head_ref_namespaced(each_ref_fn fn, void *cb_data)\n \n \treturn ret;\n }\n+\n+struct ref_transaction *ref_transaction_begin(struct strbuf *err)\n+{\n+\tassert(err);\n+\n+\treturn xcalloc(1, sizeof(struct ref_transaction));\n+}\n+\n+void ref_transaction_free(struct ref_transaction *transaction)\n+{\n+\tint i;\n+\n+\tif (!transaction)\n+\t\treturn;\n+\n+\tfor (i = 0; i < transaction->nr; i++) {\n+\t\tfree(transaction->updates[i]->msg);\n+\t\tfree(transaction->updates[i]);\n+\t}\n+\tfree(transaction->updates);\n+\tfree(transaction);\n+}\n+\n+static struct ref_update *add_update(struct ref_transaction *transaction,\n+\t\t\t\t     const char *refname)\n+{\n+\tsize_t len = strlen(refname) + 1;\n+\tstruct ref_update *update = xcalloc(1, sizeof(*update) + len);\n+\n+\tmemcpy((char *)update->refname, refname, len); /* includes NUL */\n+\tALLOC_GROW(transaction->updates, transaction->nr + 1, transaction->alloc);\n+\ttransaction->updates[transaction->nr++] = update;\n+\treturn update;\n+}\n+\n+int ref_transaction_update(struct ref_transaction *transaction,\n+\t\t\t   const char *refname,\n+\t\t\t   const unsigned char *new_sha1,\n+\t\t\t   const unsigned char *old_sha1,\n+\t\t\t   unsigned int flags, const char *msg,\n+\t\t\t   struct strbuf *err)\n+{\n+\tstruct ref_update *update;\n+\n+\tassert(err);\n+\n+\tif (transaction->state != REF_TRANSACTION_OPEN)\n+\t\tdie(\"BUG: update called for transaction that is not open\");\n+\n+\tif (new_sha1 && !is_null_sha1(new_sha1) &&\n+\t    check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {\n+\t\tstrbuf_addf(err, \"refusing to update ref with bad name %s\",\n+\t\t\t    refname);\n+\t\treturn -1;\n+\t}\n+\n+\tupdate = add_update(transaction, refname);\n+\tif (new_sha1) {\n+\t\thashcpy(update->new_sha1, new_sha1);\n+\t\tflags |= REF_HAVE_NEW;\n+\t}\n+\tif (old_sha1) {\n+\t\thashcpy(update->old_sha1, old_sha1);\n+\t\tflags |= REF_HAVE_OLD;\n+\t}\n+\tupdate->flags = flags;\n+\tif (msg)\n+\t\tupdate->msg = xstrdup(msg);\n+\treturn 0;\n+}\n+\n+int ref_transaction_create(struct ref_transaction *transaction,\n+\t\t\t   const char *refname,\n+\t\t\t   const unsigned char *new_sha1,\n+\t\t\t   unsigned int flags, const char *msg,\n+\t\t\t   struct strbuf *err)\n+{\n+\tif (!new_sha1 || is_null_sha1(new_sha1))\n+\t\tdie(\"BUG: create called without valid new_sha1\");\n+\treturn ref_transaction_update(transaction, refname, new_sha1,\n+\t\t\t\t      null_sha1, flags, msg, err);\n+}\n+\n+int ref_transaction_delete(struct ref_transaction *transaction,\n+\t\t\t   const char *refname,\n+\t\t\t   const unsigned char *old_sha1,\n+\t\t\t   unsigned int flags, const char *msg,\n+\t\t\t   struct strbuf *err)\n+{\n+\tif (old_sha1 && is_null_sha1(old_sha1))\n+\t\tdie(\"BUG: delete called with old_sha1 set to zeros\");\n+\treturn ref_transaction_update(transaction, refname,\n+\t\t\t\t      null_sha1, old_sha1,\n+\t\t\t\t      flags, msg, err);\n+}\n+\n+int ref_transaction_verify(struct ref_transaction *transaction,\n+\t\t\t   const char *refname,\n+\t\t\t   const unsigned char *old_sha1,\n+\t\t\t   unsigned int flags,\n+\t\t\t   struct strbuf *err)\n+{\n+\tif (!old_sha1)\n+\t\tdie(\"BUG: verify called with old_sha1 set to NULL\");\n+\treturn ref_transaction_update(transaction, refname,\n+\t\t\t\t      NULL, old_sha1,\n+\t\t\t\t      flags, NULL, err);\n+}\ndiff --git a/refs.h b/refs.h\nindex 729bc3c..4940ae9 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -94,6 +94,84 @@ 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+ * Transaction states.\n+ * OPEN:   The transaction is in a valid state and can accept new updates.\n+ *         An OPEN transaction can be committed.\n+ * CLOSED: A closed transaction is no longer active and no other operations\n+ *         than free can be used on it in this state.\n+ *         A transaction can either become closed by successfully committing\n+ *         an active transaction or if there is a failure while building\n+ *         the transaction thus rendering it failed/inactive.\n+ */\n+enum ref_transaction_state {\n+\tREF_TRANSACTION_OPEN   = 0,\n+\tREF_TRANSACTION_CLOSED = 1\n+};\n+\n+/*\n+ * Flag passed to lock_ref_sha1_basic() telling it to tolerate broken\n+ * refs (i.e., because the reference is about to be deleted anyway).\n+ */\n+#define REF_DELETING\t0x02\n+\n+/*\n+ * Used as a flag in ref_update::flags when a loose ref is being\n+ * pruned.\n+ */\n+#define REF_ISPRUNING\t0x04\n+\n+/*\n+ * Used as a flag in ref_update::flags when the reference should be\n+ * updated to new_sha1.\n+ */\n+#define REF_HAVE_NEW\t0x08\n+\n+/*\n+ * Used as a flag in ref_update::flags when old_sha1 should be\n+ * checked.\n+ */\n+#define REF_HAVE_OLD\t0x10\n+\n+/*\n+ * Used as a flag in ref_update::flags when the lockfile needs to be\n+ * committed.\n+ */\n+#define REF_NEEDS_COMMIT 0x20\n+\n+/*\n+ * 0x40 is REF_FORCE_CREATE_REFLOG, so skip it if you're adding a\n+ * value to ref_update::flags\n+ */\n+\n+/**\n+ * Information needed for a single ref update. Set new_sha1 to the new\n+ * value or to null_sha1 to delete the ref. To check the old value\n+ * while the ref is locked, set (flags & REF_HAVE_OLD) and set\n+ * old_sha1 to the old value, or to null_sha1 to ensure the ref does\n+ * not exist before update.\n+ */\n+struct ref_update {\n+\t/*\n+\t * If (flags & REF_HAVE_NEW), set the reference to this value:\n+\t */\n+\tunsigned char new_sha1[20];\n+\t/*\n+\t * If (flags & REF_HAVE_OLD), check that the reference\n+\t * previously had this value:\n+\t */\n+\tunsigned char old_sha1[20];\n+\t/*\n+\t * One or more of REF_HAVE_NEW, REF_HAVE_OLD, REF_NODEREF,\n+\t * REF_DELETING, and REF_ISPRUNING:\n+\t */\n+\tunsigned int flags;\n+\tstruct ref_lock *lock;\n+\tint type;\n+\tchar *msg;\n+\tconst char refname[FLEX_ARRAY];\n+};\n+\n+/*\n  * A ref_transaction represents a collection of ref updates\n  * that should succeed or fail together.\n  *\n@@ -125,7 +203,18 @@ extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref);\n  * The message is appended to err without first clearing err.\n  * err will not be '\\n' terminated.\n  */\n-struct ref_transaction;\n+\n+/*\n+ * Data structure for holding a reference transaction, which can\n+ * consist of checks and updates to multiple references, carried out\n+ * as atomically as possible.  This structure is opaque to callers.\n+ */\n+struct ref_transaction {\n+\tstruct ref_update **updates;\n+\tsize_t alloc;\n+\tsize_t nr;\n+\tenum ref_transaction_state state;\n+};\n \n /*\n  * Bit values set in the flags argument passed to each_ref_fn():\n-- \n2.4.2.644.g97b850b-twtrsrc\n"},{"id":"271530","messageId":"1444686725-27660-20-git-send-email-dturner@twopensource.com","threadId":"40543","inReplyTo":"1444686725-27660-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 19/44] refs: add a backend method structure with transaction functions","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-10-12T21:51:39Z","receivedAt":"2015-10-12T21:51:39Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\n\nAdd a ref structure for backend methods. Start by adding a method pointer\nfor the transaction commit function.\n\nAdd a function set_refs_backend to switch between backends. The files\nbased backend is the default.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: David Turner <dturner@twopensource.com>\n---\n refs-be-files.c | 10 ++++++++--\n refs.c          | 30 ++++++++++++++++++++++++++++++\n refs.h          | 15 +++++++++++++++\n 3 files changed, 53 insertions(+), 2 deletions(-)\n\ndiff --git a/refs-be-files.c b/refs-be-files.c\nindex 1308955..3050f1d 100644\n--- a/refs-be-files.c\n+++ b/refs-be-files.c\n@@ -3225,8 +3225,8 @@ static int ref_update_reject_duplicates(struct string_list *refnames,\n \treturn 0;\n }\n \n-int ref_transaction_commit(struct ref_transaction *transaction,\n-\t\t\t   struct strbuf *err)\n+static int files_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@@ -3612,3 +3612,9 @@ int reflog_expire(const char *refname, const unsigned char *sha1,\n \tunlock_ref(lock);\n \treturn -1;\n }\n+\n+struct ref_be refs_be_files = {\n+\tNULL,\n+\t\"files\",\n+\tfiles_transaction_commit,\n+};\ndiff --git a/refs.c b/refs.c\nindex 25ad3b5..f930fe0 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -4,6 +4,29 @@\n #include \"cache.h\"\n #include \"refs.h\"\n #include \"lockfile.h\"\n+/*\n+ * We always have a files backend and it is the default.\n+ */\n+struct ref_be *the_refs_backend = &refs_be_files;\n+/*\n+ * List of all available backends\n+ */\n+struct ref_be *refs_backends = &refs_be_files;\n+\n+/*\n+ * This function is used to switch to an alternate backend.\n+ */\n+int set_refs_backend(const char *name)\n+{\n+\tstruct ref_be *be;\n+\n+\tfor (be = refs_backends; be; be = be->next)\n+\t\tif (!strcmp(be->name, name)) {\n+\t\t\tthe_refs_backend = be;\n+\t\t\treturn 0;\n+\t\t}\n+\treturn 1;\n+}\n \n static int is_per_worktree_ref(const char *refname)\n {\n@@ -985,3 +1008,10 @@ int ref_transaction_verify(struct ref_transaction *transaction,\n \t\t\t\t      NULL, old_sha1,\n \t\t\t\t      flags, NULL, err);\n }\n+\n+/* backend functions */\n+int ref_transaction_commit(struct ref_transaction *transaction,\n+\t\t\t   struct strbuf *err)\n+{\n+\treturn the_refs_backend->transaction_commit(transaction, err);\n+}\ndiff --git a/refs.h b/refs.h\nindex 4940ae9..419abf4 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -619,4 +619,19 @@ extern int reflog_expire(const char *refname, const unsigned char *sha1,\n \t\t\t reflog_expiry_cleanup_fn cleanup_fn,\n \t\t\t void *policy_cb_data);\n \n+/* refs backends */\n+typedef int ref_transaction_commit_fn(struct ref_transaction *transaction,\n+\t\t\t\t      struct strbuf *err);\n+typedef void ref_transaction_free_fn(struct ref_transaction *transaction);\n+\n+struct ref_be {\n+\tstruct ref_be *next;\n+\tconst char *name;\n+\tref_transaction_commit_fn *transaction_commit;\n+};\n+\n+\n+extern struct ref_be refs_be_files;\n+int set_refs_backend(const char *name);\n+\n #endif /* REFS_H */\n-- \n2.4.2.644.g97b850b-twtrsrc\n"},{"id":"271529","messageId":"1444686725-27660-21-git-send-email-dturner@twopensource.com","threadId":"40543","inReplyTo":"1444686725-27660-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 19/44] refs-be-files.c: add a backend method structure with transaction functions","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-10-12T21:51:40Z","receivedAt":"2015-10-12T21:51:40Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\n\nAdd a ref structure for backend methods. Start by adding a method pointer\nfor the transaction commit function.\n\nAdd a function set_refs_backend to switch between backends. The files\nbased backend is the default.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: David Turner <dturner@twopensource.com>\n---\n refs-be-files.c | 10 ++++++++--\n refs.c          | 30 ++++++++++++++++++++++++++++++\n refs.h          | 15 +++++++++++++++\n 3 files changed, 53 insertions(+), 2 deletions(-)\n\ndiff --git a/refs-be-files.c b/refs-be-files.c\nindex 1308955..3050f1d 100644\n--- a/refs-be-files.c\n+++ b/refs-be-files.c\n@@ -3225,8 +3225,8 @@ static int ref_update_reject_duplicates(struct string_list *refnames,\n \treturn 0;\n }\n \n-int ref_transaction_commit(struct ref_transaction *transaction,\n-\t\t\t   struct strbuf *err)\n+static int files_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@@ -3612,3 +3612,9 @@ int reflog_expire(const char *refname, const unsigned char *sha1,\n \tunlock_ref(lock);\n \treturn -1;\n }\n+\n+struct ref_be refs_be_files = {\n+\tNULL,\n+\t\"files\",\n+\tfiles_transaction_commit,\n+};\ndiff --git a/refs.c b/refs.c\nindex 25ad3b5..f930fe0 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -4,6 +4,29 @@\n #include \"cache.h\"\n #include \"refs.h\"\n #include \"lockfile.h\"\n+/*\n+ * We always have a files backend and it is the default.\n+ */\n+struct ref_be *the_refs_backend = &refs_be_files;\n+/*\n+ * List of all available backends\n+ */\n+struct ref_be *refs_backends = &refs_be_files;\n+\n+/*\n+ * This function is used to switch to an alternate backend.\n+ */\n+int set_refs_backend(const char *name)\n+{\n+\tstruct ref_be *be;\n+\n+\tfor (be = refs_backends; be; be = be->next)\n+\t\tif (!strcmp(be->name, name)) {\n+\t\t\tthe_refs_backend = be;\n+\t\t\treturn 0;\n+\t\t}\n+\treturn 1;\n+}\n \n static int is_per_worktree_ref(const char *refname)\n {\n@@ -985,3 +1008,10 @@ int ref_transaction_verify(struct ref_transaction *transaction,\n \t\t\t\t      NULL, old_sha1,\n \t\t\t\t      flags, NULL, err);\n }\n+\n+/* backend functions */\n+int ref_transaction_commit(struct ref_transaction *transaction,\n+\t\t\t   struct strbuf *err)\n+{\n+\treturn the_refs_backend->transaction_commit(transaction, err);\n+}\ndiff --git a/refs.h b/refs.h\nindex 4940ae9..419abf4 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -619,4 +619,19 @@ extern int reflog_expire(const char *refname, const unsigned char *sha1,\n \t\t\t reflog_expiry_cleanup_fn cleanup_fn,\n \t\t\t void *policy_cb_data);\n \n+/* refs backends */\n+typedef int ref_transaction_commit_fn(struct ref_transaction *transaction,\n+\t\t\t\t      struct strbuf *err);\n+typedef void ref_transaction_free_fn(struct ref_transaction *transaction);\n+\n+struct ref_be {\n+\tstruct ref_be *next;\n+\tconst char *name;\n+\tref_transaction_commit_fn *transaction_commit;\n+};\n+\n+\n+extern struct ref_be refs_be_files;\n+int set_refs_backend(const char *name);\n+\n #endif /* REFS_H */\n-- \n2.4.2.644.g97b850b-twtrsrc\n"},{"id":"271528","messageId":"1444686725-27660-22-git-send-email-dturner@twopensource.com","threadId":"40543","inReplyTo":"1444686725-27660-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 20/44] refs-be-files.c: add methods for misc ref operations","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-10-12T21:51:41Z","receivedAt":"2015-10-12T21:51:41Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\n\nAdd ref backend methods for:\nresolve_ref_unsafe, verify_refname_available, pack_refs, peel_ref,\ncreate_symref, resolve_gitlink_ref.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: David Turner <dturner@twitter.com>\n---\n builtin/init-db.c |  1 +\n cache.h           |  7 +++++++\n refs-be-files.c   | 36 +++++++++++++++++++++++++++---------\n refs.c            | 47 +++++++++++++++++++++++++++++++++++++++++++++++\n refs.h            | 18 ++++++++++++++++++\n 5 files changed, 100 insertions(+), 9 deletions(-)\n\ndiff --git a/builtin/init-db.c b/builtin/init-db.c\nindex cf6a3c8..313d13f 100644\n--- a/builtin/init-db.c\n+++ b/builtin/init-db.c\n@@ -8,6 +8,7 @@\n #include \"builtin.h\"\n #include \"exec_cmd.h\"\n #include \"parse-options.h\"\n+#include \"refs.h\"\n \n #ifndef DEFAULT_GIT_TEMPLATE_DIR\n #define DEFAULT_GIT_TEMPLATE_DIR \"/usr/share/git-core/templates\"\ndiff --git a/cache.h b/cache.h\nindex 497d296..1c45b62 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1104,6 +1104,13 @@ extern char *oid_to_hex(const struct object_id *oid);\t/* same static buffer as s\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 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-be-files.c b/refs-be-files.c\nindex 3050f1d..116d72d 100644\n--- a/refs-be-files.c\n+++ b/refs-be-files.c\n@@ -1386,7 +1386,8 @@ static int resolve_gitlink_ref_recursive(struct ref_cache *refs,\n \treturn resolve_gitlink_ref_recursive(refs, p, sha1, recursion+1);\n }\n \n-int resolve_gitlink_ref(const char *path, const char *refname, unsigned char *sha1)\n+static int files_resolve_gitlink_ref(const char *path, const char *refname,\n+\t\t\t\t     unsigned char *sha1)\n {\n \tint len = strlen(path), retval;\n \tchar *submodule;\n@@ -1618,8 +1619,10 @@ static const char *resolve_ref_1(const char *refname,\n \t}\n }\n \n-const char *resolve_ref_unsafe(const char *refname, int resolve_flags,\n-\t\t\t       unsigned char *sha1, int *flags)\n+static const char *files_resolve_ref_unsafe(const char *refname,\n+\t\t\t\t\t    int resolve_flags,\n+\t\t\t\t\t    unsigned char *sha1,\n+\t\t\t\t\t    int *flags)\n {\n \tstatic struct strbuf sb_refname = STRBUF_INIT;\n \tstruct strbuf sb_contents = STRBUF_INIT;\n@@ -1721,7 +1724,7 @@ static enum peel_status peel_entry(struct ref_entry *entry, int repeel)\n \treturn status;\n }\n \n-int peel_ref(const char *refname, unsigned char *sha1)\n+static int files_peel_ref(const char *refname, unsigned char *sha1)\n {\n \tint flag;\n \tunsigned char base[20];\n@@ -2355,7 +2358,7 @@ static void prune_refs(struct ref_to_prune *r)\n \t}\n }\n \n-int pack_refs(unsigned int flags)\n+static int files_pack_refs(unsigned int flags)\n {\n \tstruct pack_refs_cb_data cbdata;\n \n@@ -2677,8 +2680,16 @@ static int should_autocreate_reflog(const char *refname)\n \t\t!strcmp(refname, \"HEAD\");\n }\n \n-int verify_refname_available(const char *newname, struct string_list *extra,\n-\t\t\t     struct string_list *skip, struct strbuf *err)\n+/*\n+ * Create a reflog for a ref.  If force_create = 0, the reflog will\n+ * only be created for certain refs (those for which\n+ * should_autocreate_reflog returns non-zero.  Otherwise, create it\n+ * regardless of the ref name.  Fill in *err and return -1 on failure.\n+ */\n+static int files_verify_refname_available(const char *newname,\n+\t\t\t\t\t  struct string_list *extra,\n+\t\t\t\t\t  struct string_list *skip,\n+\t\t\t\t\t  struct strbuf *err)\n {\n \tstruct ref_dir *packed_refs = get_packed_refs(&ref_cache);\n \tstruct ref_dir *loose_refs = get_loose_refs(&ref_cache);\n@@ -2913,8 +2924,9 @@ static int commit_ref_update(struct ref_lock *lock,\n \treturn 0;\n }\n \n-int create_symref(const char *ref_target, const char *refs_heads_master,\n-\t\t  const char *logmsg)\n+static int files_create_symref(const char *ref_target,\n+\t\t\t       const char *refs_heads_master,\n+\t\t\t       const char *logmsg)\n {\n \tchar *lockpath = NULL;\n \tchar ref[1000];\n@@ -3617,4 +3629,10 @@ struct ref_be refs_be_files = {\n \tNULL,\n \t\"files\",\n \tfiles_transaction_commit,\n+\tfiles_resolve_ref_unsafe,\n+\tfiles_verify_refname_available,\n+\tfiles_pack_refs,\n+\tfiles_peel_ref,\n+\tfiles_create_symref,\n+\tfiles_resolve_gitlink_ref,\n };\ndiff --git a/refs.c b/refs.c\nindex f930fe0..61d90f5 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1015,3 +1015,50 @@ int ref_transaction_commit(struct ref_transaction *transaction,\n {\n \treturn the_refs_backend->transaction_commit(transaction, err);\n }\n+\n+const char *resolve_ref_unsafe(const char *ref, int resolve_flags,\n+\t\t\t       unsigned char *sha1, int *flags)\n+{\n+\treturn the_refs_backend->resolve_ref_unsafe(ref, resolve_flags, sha1,\n+\t\t\t\t\t\t    flags);\n+}\n+\n+int verify_refname_available(const char *refname, struct string_list *extra,\n+\t\t\t     struct string_list *skip, struct strbuf *err)\n+{\n+\treturn the_refs_backend->verify_refname_available(refname, extra, skip, err);\n+}\n+\n+int pack_refs(unsigned int flags)\n+{\n+\treturn the_refs_backend->pack_refs(flags);\n+}\n+\n+int peel_ref(const char *refname, unsigned char *sha1)\n+{\n+\treturn the_refs_backend->peel_ref(refname, sha1);\n+}\n+\n+int create_symref(const char *ref_target, const char *refs_heads_master,\n+\t\t  const char *logmsg)\n+{\n+\tstruct strbuf err = STRBUF_INIT;\n+\tstruct ref_transaction *transaction = ref_transaction_begin(&err);\n+\tif (!transaction)\n+\t\treturn -1;\n+\n+\tif (the_refs_backend->create_symref(transaction, ref_target,\n+\t\t\t\t\t    refs_heads_master, logmsg))\n+\t\treturn -1;\n+\n+\tif (ref_transaction_commit(transaction, &err))\n+\t\treturn -1;\n+\n+\treturn 0;\n+}\n+\n+int resolve_gitlink_ref(const char *path, const char *refname,\n+\t\t\tunsigned char *sha1)\n+{\n+\treturn the_refs_backend->resolve_gitlink_ref(path, refname, sha1);\n+}\ndiff --git a/refs.h b/refs.h\nindex 419abf4..ee06a20 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -623,11 +623,29 @@ extern int reflog_expire(const char *refname, const unsigned char *sha1,\n typedef int ref_transaction_commit_fn(struct ref_transaction *transaction,\n \t\t\t\t      struct strbuf *err);\n typedef void ref_transaction_free_fn(struct ref_transaction *transaction);\n+typedef const char *resolve_ref_unsafe_fn(const char *ref,\n+\t\t\t\t\t  int resolve_flags,\n+\t\t\t\t\t  unsigned char *sha1, int *flags);\n+typedef int verify_refname_available_fn(const char *refname, struct string_list *extra, struct string_list *skip, struct strbuf *err);\n+typedef int pack_refs_fn(unsigned int flags);\n+typedef int peel_ref_fn(const char *refname, unsigned char *sha1);\n+typedef int create_symref_fn(struct ref_transaction *transaction,\n+\t\t\t     const char *ref_target,\n+\t\t\t     const char *refs_heads_master,\n+\t\t\t     const char *logmsg);\n+typedef int resolve_gitlink_ref_fn(const char *path, const char *refname,\n+\t\t\t\t   unsigned char *sha1);\n \n struct ref_be {\n \tstruct ref_be *next;\n \tconst char *name;\n \tref_transaction_commit_fn *transaction_commit;\n+\tresolve_ref_unsafe_fn *resolve_ref_unsafe;\n+\tverify_refname_available_fn *verify_refname_available;\n+\tpack_refs_fn *pack_refs;\n+\tpeel_ref_fn *peel_ref;\n+\tcreate_symref_fn *create_symref;\n+\tresolve_gitlink_ref_fn *resolve_gitlink_ref;\n };\n \n \n-- \n2.4.2.644.g97b850b-twtrsrc\n"},{"id":"271531","messageId":"1444686725-27660-23-git-send-email-dturner@twopensource.com","threadId":"40543","inReplyTo":"1444686725-27660-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 21/44] refs-be-files.c: add methods for the ref iterators","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-10-12T21:51:42Z","receivedAt":"2015-10-12T21:51:42Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"From: Ronnie Sahlberg <sahlberg@google.com>\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: David Turner <dturner@twopensource.com>\n---\n refs-be-files.c | 41 ++++++++++++++++++++++++----------\n refs.c          | 69 ++++++++++++++++++++++++++++++++++++++++++++++-----------\n refs.h          | 34 +++++++++++++++++++++++++---\n 3 files changed, 116 insertions(+), 28 deletions(-)\n\ndiff --git a/refs-be-files.c b/refs-be-files.c\nindex 116d72d..2e2399e 100644\n--- a/refs-be-files.c\n+++ b/refs-be-files.c\n@@ -1862,32 +1862,36 @@ static int do_head_ref(const char *submodule, each_ref_fn fn, void *cb_data)\n \treturn 0;\n }\n \n-int head_ref(each_ref_fn fn, void *cb_data)\n+static int files_head_ref(each_ref_fn fn, void *cb_data)\n {\n \treturn do_head_ref(NULL, fn, cb_data);\n }\n \n-int head_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)\n+static int files_head_ref_submodule(const char *submodule, each_ref_fn fn,\n+\t\t\t\t    void *cb_data)\n {\n \treturn do_head_ref(submodule, fn, cb_data);\n }\n \n-int for_each_ref(each_ref_fn fn, void *cb_data)\n+static int files_for_each_ref(each_ref_fn fn, void *cb_data)\n {\n \treturn do_for_each_ref(&ref_cache, \"\", fn, 0, 0, cb_data);\n }\n \n-int for_each_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)\n+static int files_for_each_ref_submodule(const char *submodule, each_ref_fn fn,\n+\t\t\t\t\tvoid *cb_data)\n {\n \treturn do_for_each_ref(get_ref_cache(submodule), \"\", fn, 0, 0, cb_data);\n }\n \n-int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data)\n+static int files_for_each_ref_in(const char *prefix, each_ref_fn fn,\n+\t\t\t\t void *cb_data)\n {\n \treturn do_for_each_ref(&ref_cache, prefix, fn, strlen(prefix), 0, cb_data);\n }\n \n-int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data, unsigned int broken)\n+static int files_for_each_fullref_in(const char *prefix, each_ref_fn fn,\n+\t\t\t\t     void *cb_data, unsigned int broken)\n {\n \tunsigned int flag = 0;\n \n@@ -1896,18 +1900,21 @@ int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data, unsig\n \treturn do_for_each_ref(&ref_cache, prefix, fn, 0, flag, cb_data);\n }\n \n-int for_each_ref_in_submodule(const char *submodule, const char *prefix,\n-\t\teach_ref_fn fn, void *cb_data)\n+static int files_for_each_ref_in_submodule(const char *submodule,\n+\t\t\t\t\t   const char *prefix,\n+\t\t\t\t\t   each_ref_fn fn, void *cb_data)\n {\n-\treturn do_for_each_ref(get_ref_cache(submodule), prefix, fn, strlen(prefix), 0, cb_data);\n+\treturn do_for_each_ref(get_ref_cache(submodule), prefix, fn,\n+\t\t\t       strlen(prefix), 0, cb_data);\n }\n-int for_each_replace_ref(each_ref_fn fn, void *cb_data)\n+\n+static int files_for_each_replace_ref(each_ref_fn fn, void *cb_data)\n {\n \treturn do_for_each_ref(&ref_cache, git_replace_ref_base, fn,\n \t\t\t       strlen(git_replace_ref_base), 0, cb_data);\n }\n \n-int for_each_namespaced_ref(each_ref_fn fn, void *cb_data)\n+static int files_for_each_namespaced_ref(each_ref_fn fn, void *cb_data)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n \tint ret;\n@@ -1917,7 +1924,7 @@ int for_each_namespaced_ref(each_ref_fn fn, void *cb_data)\n \treturn ret;\n }\n \n-int for_each_rawref(each_ref_fn fn, void *cb_data)\n+static int files_for_each_rawref(each_ref_fn fn, void *cb_data)\n {\n \treturn do_for_each_ref(&ref_cache, \"\", fn, 0,\n \t\t\t       DO_FOR_EACH_INCLUDE_BROKEN, cb_data);\n@@ -3635,4 +3642,14 @@ struct ref_be refs_be_files = {\n \tfiles_peel_ref,\n \tfiles_create_symref,\n \tfiles_resolve_gitlink_ref,\n+\tfiles_head_ref,\n+\tfiles_head_ref_submodule,\n+\tfiles_for_each_ref,\n+\tfiles_for_each_ref_submodule,\n+\tfiles_for_each_ref_in,\n+\tfiles_for_each_fullref_in,\n+\tfiles_for_each_ref_in_submodule,\n+\tfiles_for_each_rawref,\n+\tfiles_for_each_namespaced_ref,\n+\tfiles_for_each_replace_ref,\n };\ndiff --git a/refs.c b/refs.c\nindex 61d90f5..657ab73 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1042,19 +1042,8 @@ int peel_ref(const char *refname, unsigned char *sha1)\n int create_symref(const char *ref_target, const char *refs_heads_master,\n \t\t  const char *logmsg)\n {\n-\tstruct strbuf err = STRBUF_INIT;\n-\tstruct ref_transaction *transaction = ref_transaction_begin(&err);\n-\tif (!transaction)\n-\t\treturn -1;\n-\n-\tif (the_refs_backend->create_symref(transaction, ref_target,\n-\t\t\t\t\t    refs_heads_master, logmsg))\n-\t\treturn -1;\n-\n-\tif (ref_transaction_commit(transaction, &err))\n-\t\treturn -1;\n-\n-\treturn 0;\n+\treturn the_refs_backend->create_symref(ref_target, refs_heads_master,\n+\t\t\t\t\t       logmsg);\n }\n \n int resolve_gitlink_ref(const char *path, const char *refname,\n@@ -1062,3 +1051,57 @@ int resolve_gitlink_ref(const char *path, const char *refname,\n {\n \treturn the_refs_backend->resolve_gitlink_ref(path, refname, sha1);\n }\n+\n+int head_ref(each_ref_fn fn, void *cb_data)\n+{\n+\treturn the_refs_backend->head_ref(fn, cb_data);\n+}\n+\n+int head_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)\n+{\n+\treturn the_refs_backend->head_ref_submodule(submodule, fn, cb_data);\n+}\n+\n+int for_each_ref(each_ref_fn fn, void *cb_data)\n+{\n+\treturn the_refs_backend->for_each_ref(fn, cb_data);\n+}\n+\n+int for_each_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)\n+{\n+\treturn the_refs_backend->for_each_ref_submodule(submodule, fn, cb_data);\n+}\n+\n+int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data)\n+{\n+\treturn the_refs_backend->for_each_ref_in(prefix, fn, cb_data);\n+}\n+\n+int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data,\n+\t\t\tunsigned int broken)\n+{\n+\treturn the_refs_backend->for_each_fullref_in(prefix, fn, cb_data,\n+\t\t\t\t\t\t     broken);\n+}\n+\n+int for_each_ref_in_submodule(const char *submodule, const char *prefix,\n+\t\t\t      each_ref_fn fn, void *cb_data)\n+{\n+\treturn the_refs_backend->for_each_ref_in_submodule(submodule, prefix,\n+\t\t\t\t\t\t\t   fn, cb_data);\n+}\n+\n+int for_each_rawref(each_ref_fn fn, void *cb_data)\n+{\n+\treturn the_refs_backend->for_each_rawref(fn, cb_data);\n+}\n+\n+int for_each_namespaced_ref(each_ref_fn fn, void *cb_data)\n+{\n+\treturn the_refs_backend->for_each_namespaced_ref(fn, cb_data);\n+}\n+\n+int for_each_replace_ref(each_ref_fn fn, void *cb_data)\n+{\n+\treturn the_refs_backend->for_each_replace_ref(fn, cb_data);\n+}\ndiff --git a/refs.h b/refs.h\nindex ee06a20..6dec0da 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -626,15 +626,33 @@ typedef void ref_transaction_free_fn(struct ref_transaction *transaction);\n typedef const char *resolve_ref_unsafe_fn(const char *ref,\n \t\t\t\t\t  int resolve_flags,\n \t\t\t\t\t  unsigned char *sha1, int *flags);\n-typedef int verify_refname_available_fn(const char *refname, struct string_list *extra, struct string_list *skip, struct strbuf *err);\n+typedef int verify_refname_available_fn(const char *refname,\n+\t\t\t\t\tstruct string_list *extra,\n+\t\t\t\t\tstruct string_list *skip,\n+\t\t\t\t\tstruct strbuf *err);\n typedef int pack_refs_fn(unsigned int flags);\n typedef int peel_ref_fn(const char *refname, unsigned char *sha1);\n-typedef int create_symref_fn(struct ref_transaction *transaction,\n-\t\t\t     const char *ref_target,\n+typedef int create_symref_fn(const char *ref_target,\n \t\t\t     const char *refs_heads_master,\n \t\t\t     const char *logmsg);\n typedef int resolve_gitlink_ref_fn(const char *path, const char *refname,\n \t\t\t\t   unsigned char *sha1);\n+typedef int head_ref_fn(each_ref_fn fn, void *cb_data);\n+typedef int head_ref_submodule_fn(const char *submodule, each_ref_fn fn,\n+\t\t\t\t  void *cb_data);\n+typedef int for_each_ref_fn(each_ref_fn fn, void *cb_data);\n+typedef int for_each_ref_submodule_fn(const char *submodule, each_ref_fn fn,\n+\t\t\t\t      void *cb_data);\n+typedef int for_each_ref_in_fn(const char *prefix, each_ref_fn fn,\n+\t\t\t       void *cb_data);\n+typedef int for_each_fullref_in_fn(const char *prefix, each_ref_fn fn,\n+\t\t\t\t   void *cb_data, unsigned int broken);\n+typedef int for_each_ref_in_submodule_fn(const char *submodule,\n+\t\t\t\t\t const char *prefix,\n+\t\t\t\t\t each_ref_fn fn, void *cb_data);\n+typedef int for_each_rawref_fn(each_ref_fn fn, void *cb_data);\n+typedef int for_each_namespaced_ref_fn(each_ref_fn fn, void *cb_data);\n+typedef int for_each_replace_ref_fn(each_ref_fn fn, void *cb_data);\n \n struct ref_be {\n \tstruct ref_be *next;\n@@ -646,6 +664,16 @@ struct ref_be {\n \tpeel_ref_fn *peel_ref;\n \tcreate_symref_fn *create_symref;\n \tresolve_gitlink_ref_fn *resolve_gitlink_ref;\n+\thead_ref_fn *head_ref;\n+\thead_ref_submodule_fn *head_ref_submodule;\n+\tfor_each_ref_fn *for_each_ref;\n+\tfor_each_ref_submodule_fn *for_each_ref_submodule;\n+\tfor_each_ref_in_fn *for_each_ref_in;\n+\tfor_each_fullref_in_fn *for_each_fullref_in;\n+\tfor_each_ref_in_submodule_fn *for_each_ref_in_submodule;\n+\tfor_each_rawref_fn *for_each_rawref;\n+\tfor_each_namespaced_ref_fn *for_each_namespaced_ref;\n+\tfor_each_replace_ref_fn *for_each_replace_ref;\n };\n \n \n-- \n2.4.2.644.g97b850b-twtrsrc\n"},{"id":"271504","messageId":"1444686725-27660-24-git-send-email-dturner@twopensource.com","threadId":"40543","inReplyTo":"1444686725-27660-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 22/44] refs-be-files.c: add method for for_each_reftype_...","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-10-12T21:51:43Z","receivedAt":"2015-10-12T21:51:43Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"Add method for for_each_reftype_fullpath.\n\nSigned-off-by: David Turner <dturner@twopensource.com>\n---\n refs-be-files.c | 11 +++++++++++\n refs.c          |  7 +++++++\n refs.h          |  3 +++\n 3 files changed, 21 insertions(+)\n\ndiff --git a/refs-be-files.c b/refs-be-files.c\nindex 2e2399e..c99319f 100644\n--- a/refs-be-files.c\n+++ b/refs-be-files.c\n@@ -1914,6 +1914,16 @@ static int files_for_each_replace_ref(each_ref_fn fn, void *cb_data)\n \t\t\t       strlen(git_replace_ref_base), 0, cb_data);\n }\n \n+static int files_for_each_reftype_fullpath(each_ref_fn fn, char *type,\n+\t\t\t\t\t   unsigned int broken, void *cb_data)\n+{\n+\tunsigned int flag = 0;\n+\n+\tif (broken)\n+\t\tflag = DO_FOR_EACH_INCLUDE_BROKEN;\n+\treturn do_for_each_ref(&ref_cache, type, fn, 0, flag, cb_data);\n+}\n+\n static int files_for_each_namespaced_ref(each_ref_fn fn, void *cb_data)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n@@ -3652,4 +3662,5 @@ struct ref_be refs_be_files = {\n \tfiles_for_each_rawref,\n \tfiles_for_each_namespaced_ref,\n \tfiles_for_each_replace_ref,\n+\tfiles_for_each_reftype_fullpath,\n };\ndiff --git a/refs.c b/refs.c\nindex 657ab73..e627d74 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1105,3 +1105,10 @@ int for_each_replace_ref(each_ref_fn fn, void *cb_data)\n {\n \treturn the_refs_backend->for_each_replace_ref(fn, cb_data);\n }\n+\n+int for_each_reftype_fullpath(each_ref_fn fn, char *type, unsigned int broken,\n+\t\t\t      void *cb_data)\n+{\n+\treturn the_refs_backend->for_each_reftype_fullpath(fn, type, broken,\n+\t\t\t\t\t\t\t   cb_data);\n+}\ndiff --git a/refs.h b/refs.h\nindex 6dec0da..491aa1d 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -653,6 +653,8 @@ typedef int for_each_ref_in_submodule_fn(const char *submodule,\n typedef int for_each_rawref_fn(each_ref_fn fn, void *cb_data);\n typedef int for_each_namespaced_ref_fn(each_ref_fn fn, void *cb_data);\n typedef int for_each_replace_ref_fn(each_ref_fn fn, void *cb_data);\n+typedef int for_each_reftype_fullpath_fn(each_ref_fn fn, char *type,\n+\t\t\t\t\t unsigned int broken, void *cb_data);\n \n struct ref_be {\n \tstruct ref_be *next;\n@@ -674,6 +676,7 @@ struct ref_be {\n \tfor_each_rawref_fn *for_each_rawref;\n \tfor_each_namespaced_ref_fn *for_each_namespaced_ref;\n \tfor_each_replace_ref_fn *for_each_replace_ref;\n+\tfor_each_reftype_fullpath_fn *for_each_reftype_fullpath;\n };\n \n \n-- \n2.4.2.644.g97b850b-twtrsrc\n"},{"id":"271527","messageId":"1444686725-27660-25-git-send-email-dturner@twopensource.com","threadId":"40543","inReplyTo":"1444686725-27660-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 23/44] refs-be-files.c: add do_for_each_per_worktree_ref","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-10-12T21:51:44Z","receivedAt":"2015-10-12T21:51:44Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"Alternate refs backends might still use files to store per-worktree\nrefs.  So the files backend's ref-loading infrastructure should be\navailable to those backends, just for use on per-worktree refs.  Add\ndo_for_each_per_worktree_ref, which iterates over per-worktree refs.\n\nSigned-off-by: David Turner <dturner@twopensource.com>\n---\n refs-be-files.c | 15 ++++++++++++---\n refs.h          | 11 +++++++++++\n 2 files changed, 23 insertions(+), 3 deletions(-)\n\ndiff --git a/refs-be-files.c b/refs-be-files.c\nindex c99319f..9548fb3 100644\n--- a/refs-be-files.c\n+++ b/refs-be-files.c\n@@ -552,9 +552,6 @@ static void sort_ref_dir(struct ref_dir *dir)\n \tdir->sorted = dir->nr = i;\n }\n \n-/* Include broken references in a do_for_each_ref*() iteration: */\n-#define DO_FOR_EACH_INCLUDE_BROKEN 0x01\n-\n /*\n  * Return true iff the reference described by entry can be resolved to\n  * an object in the database.  Emit a warning if the referred-to\n@@ -602,6 +599,10 @@ static int do_one_ref(struct ref_entry *entry, void *cb_data)\n \tstruct ref_entry *old_current_ref;\n \tint retval;\n \n+\tif (data->flags & DO_FOR_EACH_PER_WORKTREE_ONLY &&\n+\t    ref_type(entry->name) != REF_TYPE_PER_WORKTREE)\n+\t\treturn 0;\n+\n \tif (!starts_with(entry->name, data->base))\n \t\treturn 0;\n \n@@ -1844,6 +1845,14 @@ static int do_for_each_ref(struct ref_cache *refs, const char *base,\n \treturn do_for_each_entry(refs, base, do_one_ref, &data);\n }\n \n+int do_for_each_per_worktree_ref(const char *submodule, const char *base,\n+\t\t\t\t each_ref_fn fn, int trim, int flags,\n+\t\t\t\t void *cb_data)\n+{\n+\treturn do_for_each_ref(get_ref_cache(submodule), base, fn, trim,\n+\t\t\t       flags | DO_FOR_EACH_PER_WORKTREE_ONLY, cb_data);\n+}\n+\n static int do_head_ref(const char *submodule, each_ref_fn fn, void *cb_data)\n {\n \tstruct object_id oid;\ndiff --git a/refs.h b/refs.h\nindex 491aa1d..e2ba725 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -241,6 +241,12 @@ struct ref_transaction {\n  */\n #define REF_BAD_NAME 0x08\n \n+/* Include broken references in a do_for_each_ref*() iteration */\n+#define DO_FOR_EACH_INCLUDE_BROKEN 0x01\n+\n+/* Only include per-worktree refs in a do_for_each_ref*() iteration */\n+#define DO_FOR_EACH_PER_WORKTREE_ONLY 0x02\n+\n /*\n  * The signature for the callback function for the for_each_*()\n  * functions below.  The memory pointed to by the refname and sha1\n@@ -284,6 +290,11 @@ extern int for_each_namespaced_ref(each_ref_fn fn, void *cb_data);\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+\n+int do_for_each_per_worktree_ref(const char *submodule, const char *base,\n+\t\t\t\t each_ref_fn fn, int trim, int flags,\n+\t\t\t\t void *cb_data);\n+\n static inline const char *has_glob_specials(const char *pattern)\n {\n \treturn strpbrk(pattern, \"?*[\");\n-- \n2.4.2.644.g97b850b-twtrsrc\n"},{"id":"271505","messageId":"1444686725-27660-26-git-send-email-dturner@twopensource.com","threadId":"40543","inReplyTo":"1444686725-27660-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 24/44] refs.c: move refname_is_safe to the common code","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-10-12T21:51:45Z","receivedAt":"2015-10-12T21:51:45Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"This function does not contain any backend specific code so we\nmove it to the common code.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: David Turner <dturner@twopensource.com>\n---\n refs-be-files.c | 33 ---------------------------------\n refs.c          | 24 ++++++++++++++++++++++++\n refs.h          |  2 ++\n 3 files changed, 26 insertions(+), 33 deletions(-)\n\ndiff --git a/refs-be-files.c b/refs-be-files.c\nindex 9548fb3..346429e 100644\n--- a/refs-be-files.c\n+++ b/refs-be-files.c\n@@ -196,39 +196,6 @@ static struct ref_dir *get_ref_dir(struct ref_entry *entry)\n \treturn dir;\n }\n \n-/*\n- * Check if a refname is safe.\n- * For refs that start with \"refs/\" we consider it safe as long they do\n- * not try to resolve to outside of refs/.\n- *\n- * For all other refs we only consider them safe iff they only contain\n- * upper case characters and '_' (like \"HEAD\" AND \"MERGE_HEAD\", and not like\n- * \"config\").\n- */\n-static int refname_is_safe(const char *refname)\n-{\n-\tif (starts_with(refname, \"refs/\")) {\n-\t\tchar *buf;\n-\t\tint result;\n-\n-\t\tbuf = xmalloc(strlen(refname) + 1);\n-\t\t/*\n-\t\t * Does the refname try to escape refs/?\n-\t\t * For example: refs/foo/../bar is safe but refs/foo/../../bar\n-\t\t * is not.\n-\t\t */\n-\t\tresult = !normalize_path_copy(buf, refname + strlen(\"refs/\"));\n-\t\tfree(buf);\n-\t\treturn result;\n-\t}\n-\twhile (*refname) {\n-\t\tif (!isupper(*refname) && *refname != '_')\n-\t\t\treturn 0;\n-\t\trefname++;\n-\t}\n-\treturn 1;\n-}\n-\n static struct ref_entry *create_ref_entry(const char *refname,\n \t\t\t\t\t  const unsigned char *sha1, int flag,\n \t\t\t\t\t  int check_name)\ndiff --git a/refs.c b/refs.c\nindex e627d74..2515f6e 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1009,6 +1009,30 @@ int ref_transaction_verify(struct ref_transaction *transaction,\n \t\t\t\t      flags, NULL, err);\n }\n \n+int refname_is_safe(const char *refname)\n+{\n+\tif (starts_with(refname, \"refs/\")) {\n+\t\tchar *buf;\n+\t\tint result;\n+\n+\t\tbuf = xmalloc(strlen(refname) + 1);\n+\t\t/*\n+\t\t * Does the refname try to escape refs/?\n+\t\t * For example: refs/foo/../bar is safe but refs/foo/../../bar\n+\t\t * is not.\n+\t\t */\n+\t\tresult = !normalize_path_copy(buf, refname + strlen(\"refs/\"));\n+\t\tfree(buf);\n+\t\treturn result;\n+\t}\n+\twhile (*refname) {\n+\t\tif (!isupper(*refname) && *refname != '_')\n+\t\t\treturn 0;\n+\t\trefname++;\n+\t}\n+\treturn 1;\n+}\n+\n /* backend functions */\n int ref_transaction_commit(struct ref_transaction *transaction,\n \t\t\t   struct strbuf *err)\ndiff --git a/refs.h b/refs.h\nindex e2ba725..fc8a748 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -415,6 +415,8 @@ extern int for_each_reflog(each_ref_fn, void *);\n  */\n extern int check_refname_format(const char *refname, int flags);\n \n+extern int refname_is_safe(const char *refname);\n+\n extern const char *prettify_refname(const char *refname);\n \n extern char *shorten_unambiguous_ref(const char *refname, int strict);\n-- \n2.4.2.644.g97b850b-twtrsrc\n"},{"id":"271507","messageId":"1444686725-27660-27-git-send-email-dturner@twopensource.com","threadId":"40543","inReplyTo":"1444686725-27660-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 25/44] refs.h: document make refname_is_safe and add it to header","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-10-12T21:51:46Z","receivedAt":"2015-10-12T21:51:46Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"This function might be used by other refs backends\n\nSigned-off-by: David Turner <dturner@twopensource.com>\n---\n refs.h | 11 +++++++++++\n 1 file changed, 11 insertions(+)\n\ndiff --git a/refs.h b/refs.h\nindex fc8a748..7a936e2 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -348,6 +348,17 @@ int verify_refname_available(const char *newname, struct string_list *extra,\n \t\t\t     struct string_list *skip, struct strbuf *err);\n \n /*\n+ * Check if a refname is safe.\n+ * For refs that start with \"refs/\" we consider it safe as long they do\n+ * not try to resolve to outside of refs/.\n+ *\n+ * For all other refs we only consider them safe iff they only contain\n+ * upper case characters and '_' (like \"HEAD\" AND \"MERGE_HEAD\", and not like\n+ * \"config\").\n+ */\n+int refname_is_safe(const char *refname);\n+\n+/*\n  * Flags controlling ref_transaction_update(), ref_transaction_create(), etc.\n  * REF_NODEREF: act on the ref directly, instead of dereferencing\n  *              symbolic references.\n-- \n2.4.2.644.g97b850b-twtrsrc\n"},{"id":"271522","messageId":"1444686725-27660-28-git-send-email-dturner@twopensource.com","threadId":"40543","inReplyTo":"1444686725-27660-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 26/44] refs.c: move copy_msg to the common code","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-10-12T21:51:47Z","receivedAt":"2015-10-12T21:51:47Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"Rename copy_msg to copy_reflog_msg and make it public.\n\nSigned-off-by: David Turner <dturner@twopensource.com>\n---\n refs-be-files.c | 28 +---------------------------\n refs.c          | 26 ++++++++++++++++++++++++++\n refs.h          |  2 ++\n 3 files changed, 29 insertions(+), 27 deletions(-)\n\ndiff --git a/refs-be-files.c b/refs-be-files.c\nindex 346429e..830b5a1 100644\n--- a/refs-be-files.c\n+++ b/refs-be-files.c\n@@ -2637,32 +2637,6 @@ static int commit_ref(struct ref_lock *lock)\n \treturn 0;\n }\n \n-/*\n- * copy the reflog message msg to buf, which has been allocated sufficiently\n- * large, while cleaning up the whitespaces.  Especially, convert LF to space,\n- * because reflog file is one line per entry.\n- */\n-static int copy_msg(char *buf, const char *msg)\n-{\n-\tchar *cp = buf;\n-\tchar c;\n-\tint wasspace = 1;\n-\n-\t*cp++ = '\\t';\n-\twhile ((c = *msg++)) {\n-\t\tif (wasspace && isspace(c))\n-\t\t\tcontinue;\n-\t\twasspace = isspace(c);\n-\t\tif (wasspace)\n-\t\t\tc = ' ';\n-\t\t*cp++ = c;\n-\t}\n-\twhile (buf < cp && isspace(cp[-1]))\n-\t\tcp--;\n-\t*cp++ = '\\n';\n-\treturn cp - buf;\n-}\n-\n static int should_autocreate_reflog(const char *refname)\n {\n \tif (!log_all_ref_updates)\n@@ -2765,7 +2739,7 @@ static int log_ref_write_fd(int fd, const unsigned char *old_sha1,\n \t\t\tsha1_to_hex(new_sha1),\n \t\t\tcommitter);\n \tif (msglen)\n-\t\tlen += copy_msg(logrec + len - 1, msg) - 1;\n+\t\tlen += copy_reflog_msg(logrec + len - 1, msg) - 1;\n \n \twritten = len <= maxlen ? write_in_full(fd, logrec, len) : -1;\n \tfree(logrec);\ndiff --git a/refs.c b/refs.c\nindex 2515f6e..bd8c71b 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -886,6 +886,32 @@ int for_each_remote_ref_submodule(const char *submodule, each_ref_fn fn, void *c\n \treturn for_each_ref_in_submodule(submodule, \"refs/remotes/\", fn, cb_data);\n }\n \n+/*\n+ * copy the reflog message msg to buf, which has been allocated sufficiently\n+ * large, while cleaning up the whitespaces.  Especially, convert LF to space,\n+ * because reflog file is one line per entry.\n+ */\n+int copy_reflog_msg(char *buf, const char *msg)\n+{\n+\tchar *cp = buf;\n+\tchar c;\n+\tint wasspace = 1;\n+\n+\t*cp++ = '\\t';\n+\twhile ((c = *msg++)) {\n+\t\tif (wasspace && isspace(c))\n+\t\t\tcontinue;\n+\t\twasspace = isspace(c);\n+\t\tif (wasspace)\n+\t\t\tc = ' ';\n+\t\t*cp++ = c;\n+\t}\n+\twhile (buf < cp && isspace(cp[-1]))\n+\t\tcp--;\n+\t*cp++ = '\\n';\n+\treturn cp - buf;\n+}\n+\n int head_ref_namespaced(each_ref_fn fn, void *cb_data)\n {\n \tstruct strbuf buf = STRBUF_INIT;\ndiff --git a/refs.h b/refs.h\nindex 7a936e2..3da5d09 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -597,6 +597,8 @@ enum ref_type {\n \n enum ref_type ref_type(const char *refname);\n \n+int copy_reflog_msg(char *buf, const char *msg);\n+\n enum expire_reflog_flags {\n \tEXPIRE_REFLOGS_DRY_RUN = 1 << 0,\n \tEXPIRE_REFLOGS_UPDATE_REF = 1 << 1,\n-- \n2.4.2.644.g97b850b-twtrsrc\n"},{"id":"271523","messageId":"1444686725-27660-29-git-send-email-dturner@twopensource.com","threadId":"40543","inReplyTo":"1444686725-27660-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 27/44] refs.c: move peel_object to the common code","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-10-12T21:51:48Z","receivedAt":"2015-10-12T21:51:48Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"This function does not contain any backend specific code so we\nmove it to the common code.\n\nSigned-off-by: David Turner <dturner@twopensource.com>\n---\n refs-be-files.c | 53 -----------------------------------------------------\n refs.c          | 31 +++++++++++++++++++++++++++++++\n refs.h          | 27 +++++++++++++++++++++++++++\n 3 files changed, 58 insertions(+), 53 deletions(-)\n\ndiff --git a/refs-be-files.c b/refs-be-files.c\nindex 830b5a1..bf2fd7a 100644\n--- a/refs-be-files.c\n+++ b/refs-be-files.c\n@@ -1604,59 +1604,6 @@ static const char *files_resolve_ref_unsafe(const char *refname,\n \treturn ret;\n }\n \n-enum peel_status {\n-\t/* object was peeled successfully: */\n-\tPEEL_PEELED = 0,\n-\n-\t/*\n-\t * object cannot be peeled because the named object (or an\n-\t * object referred to by a tag in the peel chain), does not\n-\t * exist.\n-\t */\n-\tPEEL_INVALID = -1,\n-\n-\t/* object cannot be peeled because it is not a tag: */\n-\tPEEL_NON_TAG = -2,\n-\n-\t/* ref_entry contains no peeled value because it is a symref: */\n-\tPEEL_IS_SYMREF = -3,\n-\n-\t/*\n-\t * ref_entry cannot be peeled because it is broken (i.e., the\n-\t * symbolic reference cannot even be resolved to an object\n-\t * name):\n-\t */\n-\tPEEL_BROKEN = -4\n-};\n-\n-/*\n- * Peel the named object; i.e., if the object is a tag, resolve the\n- * tag recursively until a non-tag is found.  If successful, store the\n- * result to sha1 and return PEEL_PEELED.  If the object is not a tag\n- * or is not valid, return PEEL_NON_TAG or PEEL_INVALID, respectively,\n- * and leave sha1 unchanged.\n- */\n-static enum peel_status peel_object(const unsigned char *name, unsigned char *sha1)\n-{\n-\tstruct object *o = lookup_unknown_object(name);\n-\n-\tif (o->type == OBJ_NONE) {\n-\t\tint type = sha1_object_info(name, NULL);\n-\t\tif (type < 0 || !object_as_type(o, type, 0))\n-\t\t\treturn PEEL_INVALID;\n-\t}\n-\n-\tif (o->type != OBJ_TAG)\n-\t\treturn PEEL_NON_TAG;\n-\n-\to = deref_tag_noverify(o);\n-\tif (!o)\n-\t\treturn PEEL_INVALID;\n-\n-\thashcpy(sha1, o->sha1);\n-\treturn PEEL_PEELED;\n-}\n-\n /*\n  * Peel the entry (if possible) and return its new peel_status.  If\n  * repeel is true, re-peel the entry even if there is an old peeled\ndiff --git a/refs.c b/refs.c\nindex bd8c71b..99b31f6 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -4,6 +4,9 @@\n #include \"cache.h\"\n #include \"refs.h\"\n #include \"lockfile.h\"\n+#include \"object.h\"\n+#include \"tag.h\"\n+\n /*\n  * We always have a files backend and it is the default.\n  */\n@@ -1059,6 +1062,34 @@ int refname_is_safe(const char *refname)\n \treturn 1;\n }\n \n+/*\n+ * Peel the named object; i.e., if the object is a tag, resolve the\n+ * tag recursively until a non-tag is found.  If successful, store the\n+ * result to sha1 and return PEEL_PEELED.  If the object is not a tag\n+ * or is not valid, return PEEL_NON_TAG or PEEL_INVALID, respectively,\n+ * and leave sha1 unchanged.\n+ */\n+enum peel_status peel_object(const unsigned char *name, unsigned char *sha1)\n+{\n+\tstruct object *o = lookup_unknown_object(name);\n+\n+\tif (o->type == OBJ_NONE) {\n+\t\tint type = sha1_object_info(name, NULL);\n+\t\tif (type < 0 || !object_as_type(o, type, 0))\n+\t\t\treturn PEEL_INVALID;\n+\t}\n+\n+\tif (o->type != OBJ_TAG)\n+\t\treturn PEEL_NON_TAG;\n+\n+\to = deref_tag_noverify(o);\n+\tif (!o)\n+\t\treturn PEEL_INVALID;\n+\n+\thashcpy(sha1, o->sha1);\n+\treturn PEEL_PEELED;\n+}\n+\n /* backend functions */\n int ref_transaction_commit(struct ref_transaction *transaction,\n \t\t\t   struct strbuf *err)\ndiff --git a/refs.h b/refs.h\nindex 3da5d09..636f959 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -76,6 +76,33 @@ extern int is_branch(const char *refname);\n  */\n extern int peel_ref(const char *refname, unsigned char *sha1);\n \n+enum peel_status {\n+\t/* object was peeled successfully: */\n+\tPEEL_PEELED = 0,\n+\n+\t/*\n+\t * object cannot be peeled because the named object (or an\n+\t * object referred to by a tag in the peel chain), does not\n+\t * exist.\n+\t */\n+\tPEEL_INVALID = -1,\n+\n+\t/* object cannot be peeled because it is not a tag: */\n+\tPEEL_NON_TAG = -2,\n+\n+\t/* ref_entry contains no peeled value because it is a symref: */\n+\tPEEL_IS_SYMREF = -3,\n+\n+\t/*\n+\t * ref_entry cannot be peeled because it is broken (i.e., the\n+\t * symbolic reference cannot even be resolved to an object\n+\t * name):\n+\t */\n+\tPEEL_BROKEN = -4\n+};\n+\n+enum peel_status peel_object(const unsigned char *name, 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-- \n2.4.2.644.g97b850b-twtrsrc\n"},{"id":"271525","messageId":"1444686725-27660-30-git-send-email-dturner@twopensource.com","threadId":"40543","inReplyTo":"1444686725-27660-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 28/44] refs.c: move should_autocreate_reflog to common code","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-10-12T21:51:49Z","receivedAt":"2015-10-12T21:51:49Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"Signed-off-by: David Turner <dturner@twopensource.com>\n---\n refs-be-files.c | 10 ----------\n refs.c          | 10 ++++++++++\n refs.h          |  2 ++\n 3 files changed, 12 insertions(+), 10 deletions(-)\n\ndiff --git a/refs-be-files.c b/refs-be-files.c\nindex bf2fd7a..73111e7 100644\n--- a/refs-be-files.c\n+++ b/refs-be-files.c\n@@ -2584,16 +2584,6 @@ static int commit_ref(struct ref_lock *lock)\n \treturn 0;\n }\n \n-static int should_autocreate_reflog(const char *refname)\n-{\n-\tif (!log_all_ref_updates)\n-\t\treturn 0;\n-\treturn starts_with(refname, \"refs/heads/\") ||\n-\t\tstarts_with(refname, \"refs/remotes/\") ||\n-\t\tstarts_with(refname, \"refs/notes/\") ||\n-\t\t!strcmp(refname, \"HEAD\");\n-}\n-\n /*\n  * Create a reflog for a ref.  If force_create = 0, the reflog will\n  * only be created for certain refs (those for which\ndiff --git a/refs.c b/refs.c\nindex 99b31f6..1de8529 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -685,6 +685,16 @@ char *resolve_refdup(const char *refname, int resolve_flags,\n \t\t\t\t\t\t  sha1, flags));\n }\n \n+int should_autocreate_reflog(const char *refname)\n+{\n+\tif (!log_all_ref_updates)\n+\t\treturn 0;\n+\treturn starts_with(refname, \"refs/heads/\") ||\n+\t\tstarts_with(refname, \"refs/remotes/\") ||\n+\t\tstarts_with(refname, \"refs/notes/\") ||\n+\t\t!strcmp(refname, \"HEAD\");\n+}\n+\n /*\n  * How to handle various characters in refnames:\n  * 0: An acceptable character for refs\ndiff --git a/refs.h b/refs.h\nindex 636f959..6d284f5 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -58,6 +58,8 @@ extern const char *resolve_ref_unsafe(const char *refname, int resolve_flags,\n extern char *resolve_refdup(const char *refname, int resolve_flags,\n \t\t\t    unsigned char *sha1, int *flags);\n \n+extern int should_autocreate_reflog(const char *refname);\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-- \n2.4.2.644.g97b850b-twtrsrc\n"},{"id":"271506","messageId":"1444686725-27660-31-git-send-email-dturner@twopensource.com","threadId":"40543","inReplyTo":"1444686725-27660-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 29/44] refs.c: add ref backend init function","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-10-12T21:51:50Z","receivedAt":"2015-10-12T21:51:50Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"The file backend doesn't need this function, but other backends might.\n\nSigned-off-by: David Turner <dturner@twopensource.com>\nSigned-off-by: Ronnie Sahlberg <rsahlberg@google.com>\n---\n refs-be-files.c | 6 ++++++\n refs.c          | 3 ++-\n refs.h          | 4 +++-\n 3 files changed, 11 insertions(+), 2 deletions(-)\n\ndiff --git a/refs-be-files.c b/refs-be-files.c\nindex 73111e7..f123459 100644\n--- a/refs-be-files.c\n+++ b/refs-be-files.c\n@@ -3307,6 +3307,11 @@ static int ref_present(const char *refname,\n \n \treturn string_list_has_string(affected_refnames, refname);\n }\n+ \n+void files_init_backend(void *data)\n+{\n+\t/* do nothing */\n+}\n \n int initial_ref_transaction_commit(struct ref_transaction *transaction,\n \t\t\t\t   struct strbuf *err)\n@@ -3532,6 +3537,7 @@ int reflog_expire(const char *refname, const unsigned char *sha1,\n struct ref_be refs_be_files = {\n \tNULL,\n \t\"files\",\n+\tfiles_init_backend,\n \tfiles_transaction_commit,\n \tfiles_resolve_ref_unsafe,\n \tfiles_verify_refname_available,\ndiff --git a/refs.c b/refs.c\nindex 1de8529..6370ace 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -19,13 +19,14 @@ struct ref_be *refs_backends = &refs_be_files;\n /*\n  * This function is used to switch to an alternate backend.\n  */\n-int set_refs_backend(const char *name)\n+int set_refs_backend(const char *name, void *init_data)\n {\n \tstruct ref_be *be;\n \n \tfor (be = refs_backends; be; be = be->next)\n \t\tif (!strcmp(be->name, name)) {\n \t\t\tthe_refs_backend = be;\n+\t\t\tbe->init_backend(init_data);\n \t\t\treturn 0;\n \t\t}\n \treturn 1;\ndiff --git a/refs.h b/refs.h\nindex 6d284f5..e76553a 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -675,6 +675,7 @@ extern int reflog_expire(const char *refname, const unsigned char *sha1,\n \t\t\t void *policy_cb_data);\n \n /* refs backends */\n+typedef void ref_backend_init_fn(void *data);\n typedef int ref_transaction_commit_fn(struct ref_transaction *transaction,\n \t\t\t\t      struct strbuf *err);\n typedef void ref_transaction_free_fn(struct ref_transaction *transaction);\n@@ -714,6 +715,7 @@ typedef int for_each_reftype_fullpath_fn(each_ref_fn fn, char *type,\n struct ref_be {\n \tstruct ref_be *next;\n \tconst char *name;\n+\tref_backend_init_fn *init_backend;\n \tref_transaction_commit_fn *transaction_commit;\n \tresolve_ref_unsafe_fn *resolve_ref_unsafe;\n \tverify_refname_available_fn *verify_refname_available;\n@@ -736,6 +738,6 @@ struct ref_be {\n \n \n extern struct ref_be refs_be_files;\n-int set_refs_backend(const char *name);\n+int set_refs_backend(const char *name, void *init_data);\n \n #endif /* REFS_H */\n-- \n2.4.2.644.g97b850b-twtrsrc\n"},{"id":"271524","messageId":"1444686725-27660-32-git-send-email-dturner@twopensource.com","threadId":"40543","inReplyTo":"1444686725-27660-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 30/44] refs.c: add methods for reflog","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-10-12T21:51:51Z","receivedAt":"2015-10-12T21:51:51Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"In the file-based backend, the reflog piggybacks on the ref lock.\nSince other backends won't have the same sort of ref lock, ref backends\nmust also handle reflogs.\n\nSigned-off-by: Ronnie Sahlberg <rsahlberg@google.com>\nSigned-off-by: David Turner <dturner@twopensource.com>\n---\n refs-be-files.c | 35 +++++++++++++++++++++++------------\n refs.c          | 46 ++++++++++++++++++++++++++++++++++++++++++++++\n refs.h          | 28 ++++++++++++++++++++++++++++\n 3 files changed, 97 insertions(+), 12 deletions(-)\n\ndiff --git a/refs-be-files.c b/refs-be-files.c\nindex f123459..104e90f 100644\n--- a/refs-be-files.c\n+++ b/refs-be-files.c\n@@ -2650,7 +2650,8 @@ static int log_ref_setup(const char *refname, struct strbuf *logfile, struct str\n }\n \n \n-int safe_create_reflog(const char *refname, int force_create, struct strbuf *err)\n+static int files_create_reflog(const char *refname, int force_create,\n+\t\t\t       struct strbuf *err)\n {\n \tint ret;\n \tstruct strbuf sb = STRBUF_INIT;\n@@ -2898,7 +2899,7 @@ static int files_create_symref(const char *ref_target,\n \treturn 0;\n }\n \n-int reflog_exists(const char *refname)\n+static int files_reflog_exists(const char *refname)\n {\n \tstruct stat st;\n \n@@ -2906,7 +2907,7 @@ int reflog_exists(const char *refname)\n \t\tS_ISREG(st.st_mode);\n }\n \n-int delete_reflog(const char *refname)\n+static int files_delete_reflog(const char *refname)\n {\n \treturn remove_path(git_path(\"logs/%s\", refname));\n }\n@@ -2950,7 +2951,9 @@ static char *find_beginning_of_line(char *bob, char *scan)\n \treturn scan;\n }\n \n-int for_each_reflog_ent_reverse(const char *refname, each_reflog_ent_fn fn, void *cb_data)\n+static int files_for_each_reflog_ent_reverse(const char *refname,\n+\t\t\t\t\t     each_reflog_ent_fn fn,\n+\t\t\t\t\t     void *cb_data)\n {\n \tstruct strbuf sb = STRBUF_INIT;\n \tFILE *logfp;\n@@ -3052,7 +3055,8 @@ int for_each_reflog_ent_reverse(const char *refname, each_reflog_ent_fn fn, void\n \treturn ret;\n }\n \n-int for_each_reflog_ent(const char *refname, each_reflog_ent_fn fn, void *cb_data)\n+static int files_for_each_reflog_ent(const char *refname,\n+\t\t\t\t     each_reflog_ent_fn fn, void *cb_data)\n {\n \tFILE *logfp;\n \tstruct strbuf sb = STRBUF_INIT;\n@@ -3114,7 +3118,7 @@ static int do_for_each_reflog(struct strbuf *name, each_ref_fn fn, void *cb_data\n \treturn retval;\n }\n \n-int for_each_reflog(each_ref_fn fn, void *cb_data)\n+static int files_for_each_reflog(each_ref_fn fn, void *cb_data)\n {\n \tint retval;\n \tstruct strbuf name;\n@@ -3429,12 +3433,12 @@ static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1,\n \treturn 0;\n }\n \n-int reflog_expire(const char *refname, const unsigned char *sha1,\n-\t\t unsigned int flags,\n-\t\t reflog_expiry_prepare_fn prepare_fn,\n-\t\t reflog_expiry_should_prune_fn should_prune_fn,\n-\t\t reflog_expiry_cleanup_fn cleanup_fn,\n-\t\t void *policy_cb_data)\n+static int files_reflog_expire(const char *refname, const unsigned char *sha1,\n+\t\t\t       unsigned int flags,\n+\t\t\t       reflog_expiry_prepare_fn prepare_fn,\n+\t\t\t       reflog_expiry_should_prune_fn should_prune_fn,\n+\t\t\t       reflog_expiry_cleanup_fn cleanup_fn,\n+\t\t\t       void *policy_cb_data)\n {\n \tstatic struct lock_file reflog_lock;\n \tstruct expire_reflog_cb cb;\n@@ -3539,6 +3543,13 @@ struct ref_be refs_be_files = {\n \t\"files\",\n \tfiles_init_backend,\n \tfiles_transaction_commit,\n+\tfiles_for_each_reflog_ent,\n+\tfiles_for_each_reflog_ent_reverse,\n+\tfiles_for_each_reflog,\n+\tfiles_reflog_exists,\n+\tfiles_create_reflog,\n+\tfiles_delete_reflog,\n+\tfiles_reflog_expire,\n \tfiles_resolve_ref_unsafe,\n \tfiles_verify_refname_available,\n \tfiles_pack_refs,\ndiff --git a/refs.c b/refs.c\nindex 6370ace..304fead 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1204,3 +1204,49 @@ int for_each_reftype_fullpath(each_ref_fn fn, char *type, unsigned int broken,\n \treturn the_refs_backend->for_each_reftype_fullpath(fn, type, broken,\n \t\t\t\t\t\t\t   cb_data);\n }\n+\n+int for_each_reflog_ent_reverse(const char *refname, each_reflog_ent_fn fn,\n+\t\t\t\tvoid *cb_data)\n+{\n+\treturn the_refs_backend->for_each_reflog_ent_reverse(refname, fn,\n+\t\t\t\t\t\t\t     cb_data);\n+}\n+\n+int for_each_reflog_ent(const char *refname, each_reflog_ent_fn fn,\n+\t\t\tvoid *cb_data)\n+{\n+\treturn the_refs_backend->for_each_reflog_ent(refname, fn, cb_data);\n+}\n+\n+int for_each_reflog(each_ref_fn fn, void *cb_data)\n+{\n+\treturn the_refs_backend->for_each_reflog(fn, cb_data);\n+}\n+\n+int reflog_exists(const char *refname)\n+{\n+\treturn the_refs_backend->reflog_exists(refname);\n+}\n+\n+int safe_create_reflog(const char *refname, int force_create,\n+\t\t       struct strbuf *err)\n+{\n+\treturn the_refs_backend->create_reflog(refname, force_create, err);\n+}\n+\n+int delete_reflog(const char *refname)\n+{\n+\treturn the_refs_backend->delete_reflog(refname);\n+}\n+\n+int reflog_expire(const char *refname, const unsigned char *sha1,\n+\t\t  unsigned int flags,\n+\t\t  reflog_expiry_prepare_fn prepare_fn,\n+\t\t  reflog_expiry_should_prune_fn should_prune_fn,\n+\t\t  reflog_expiry_cleanup_fn cleanup_fn,\n+\t\t  void *policy_cb_data)\n+{\n+\treturn the_refs_backend->reflog_expire(refname, sha1, flags,\n+\t\t\t\t\t       prepare_fn, should_prune_fn,\n+\t\t\t\t\t       cleanup_fn, policy_cb_data);\n+}\ndiff --git a/refs.h b/refs.h\nindex e76553a..5888877 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -679,6 +679,27 @@ typedef void ref_backend_init_fn(void *data);\n typedef int ref_transaction_commit_fn(struct ref_transaction *transaction,\n \t\t\t\t      struct strbuf *err);\n typedef void ref_transaction_free_fn(struct ref_transaction *transaction);\n+\n+/* reflog functions */\n+typedef int for_each_reflog_ent_fn(const char *refname,\n+\t\t\t\t   each_reflog_ent_fn fn,\n+\t\t\t\t   void *cb_data);\n+typedef int for_each_reflog_ent_reverse_fn(const char *refname,\n+\t\t\t\t\t   each_reflog_ent_fn fn,\n+\t\t\t\t\t   void *cb_data);\n+typedef int for_each_reflog_fn(each_ref_fn fn, void *cb_data);\n+typedef int reflog_exists_fn(const char *refname);\n+typedef int create_reflog_fn(const char *refname, int force_create,\n+\t\t\t     struct strbuf *err);\n+typedef int delete_reflog_fn(const char *refname);\n+typedef int reflog_expire_fn(const char *refname, const unsigned char *sha1,\n+\t\t\t     unsigned int flags,\n+\t\t\t     reflog_expiry_prepare_fn prepare_fn,\n+\t\t\t     reflog_expiry_should_prune_fn should_prune_fn,\n+\t\t\t     reflog_expiry_cleanup_fn cleanup_fn,\n+\t\t\t     void *policy_cb_data);\n+\n+/* resolution functions */\n typedef const char *resolve_ref_unsafe_fn(const char *ref,\n \t\t\t\t\t  int resolve_flags,\n \t\t\t\t\t  unsigned char *sha1, int *flags);\n@@ -717,6 +738,13 @@ struct ref_be {\n \tconst char *name;\n \tref_backend_init_fn *init_backend;\n \tref_transaction_commit_fn *transaction_commit;\n+\tfor_each_reflog_ent_fn *for_each_reflog_ent;\n+\tfor_each_reflog_ent_reverse_fn *for_each_reflog_ent_reverse;\n+\tfor_each_reflog_fn *for_each_reflog;\n+\treflog_exists_fn *reflog_exists;\n+\tcreate_reflog_fn *create_reflog;\n+\tdelete_reflog_fn *delete_reflog;\n+\treflog_expire_fn *reflog_expire;\n \tresolve_ref_unsafe_fn *resolve_ref_unsafe;\n \tverify_refname_available_fn *verify_refname_available;\n \tpack_refs_fn *pack_refs;\n-- \n2.4.2.644.g97b850b-twtrsrc\n"},{"id":"271508","messageId":"1444686725-27660-33-git-send-email-dturner@twopensource.com","threadId":"40543","inReplyTo":"1444686725-27660-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 31/44] refs.c: add method for initial ref transaction commit","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-10-12T21:51:52Z","receivedAt":"2015-10-12T21:51:52Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"Signed-off-by: Ronnie Sahlberg <rsahlberg@google.com>\nSigned-off-by: David Turner <dturner@twopensource.com>\n---\n refs-be-files.c | 5 +++--\n refs.c          | 6 ++++++\n refs.h          | 1 +\n 3 files changed, 10 insertions(+), 2 deletions(-)\n\ndiff --git a/refs-be-files.c b/refs-be-files.c\nindex 104e90f..7560a55 100644\n--- a/refs-be-files.c\n+++ b/refs-be-files.c\n@@ -3317,8 +3317,8 @@ void files_init_backend(void *data)\n \t/* do nothing */\n }\n \n-int initial_ref_transaction_commit(struct ref_transaction *transaction,\n-\t\t\t\t   struct strbuf *err)\n+static int files_initial_transaction_commit(struct ref_transaction *transaction,\n+\t\t\t\t\t    struct strbuf *err)\n {\n \tint ret = 0, i;\n \tint n = transaction->nr;\n@@ -3543,6 +3543,7 @@ struct ref_be refs_be_files = {\n \t\"files\",\n \tfiles_init_backend,\n \tfiles_transaction_commit,\n+\tfiles_initial_transaction_commit,\n \tfiles_for_each_reflog_ent,\n \tfiles_for_each_reflog_ent_reverse,\n \tfiles_for_each_reflog,\ndiff --git a/refs.c b/refs.c\nindex 304fead..6e41ed8 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1250,3 +1250,9 @@ int reflog_expire(const char *refname, const unsigned char *sha1,\n \t\t\t\t\t       prepare_fn, should_prune_fn,\n \t\t\t\t\t       cleanup_fn, policy_cb_data);\n }\n+\n+int initial_ref_transaction_commit(struct ref_transaction *transaction,\n+\t\t\t\t   struct strbuf *err)\n+{\n+\treturn the_refs_backend->initial_transaction_commit(transaction, err);\n+}\ndiff --git a/refs.h b/refs.h\nindex 5888877..e3136ee 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -738,6 +738,7 @@ struct ref_be {\n \tconst char *name;\n \tref_backend_init_fn *init_backend;\n \tref_transaction_commit_fn *transaction_commit;\n+\tref_transaction_commit_fn *initial_transaction_commit;\n \tfor_each_reflog_ent_fn *for_each_reflog_ent;\n \tfor_each_reflog_ent_reverse_fn *for_each_reflog_ent_reverse;\n \tfor_each_reflog_fn *for_each_reflog;\n-- \n2.4.2.644.g97b850b-twtrsrc\n"},{"id":"271520","messageId":"1444686725-27660-34-git-send-email-dturner@twopensource.com","threadId":"40543","inReplyTo":"1444686725-27660-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 32/44] initdb: move safe_create_dir into common code","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-10-12T21:51:53Z","receivedAt":"2015-10-12T21:51:53Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"In a moment, we'll create initdb functions for ref backends, and code\nfrom initdb that calls this function needs to move into the files\nbackend.  So this function needs to be public.\n\nSigned-off-by: David Turner <dturner@twopensource.com>\n---\n builtin/init-db.c | 12 ------------\n cache.h           |  5 +++++\n path.c            | 12 ++++++++++++\n 3 files changed, 17 insertions(+), 12 deletions(-)\n\ndiff --git a/builtin/init-db.c b/builtin/init-db.c\nindex 313d13f..f6f7259 100644\n--- a/builtin/init-db.c\n+++ b/builtin/init-db.c\n@@ -25,18 +25,6 @@ static int init_shared_repository = -1;\n static const char *init_db_template_dir;\n static const char *git_link;\n \n-static void safe_create_dir(const char *dir, int share)\n-{\n-\tif (mkdir(dir, 0777) < 0) {\n-\t\tif (errno != EEXIST) {\n-\t\t\tperror(dir);\n-\t\t\texit(1);\n-\t\t}\n-\t}\n-\telse if (share && adjust_shared_perm(dir))\n-\t\tdie(_(\"Could not make %s writable by group\"), dir);\n-}\n-\n static void copy_templates_1(struct strbuf *path, struct strbuf *template,\n \t\t\t     DIR *dir)\n {\ndiff --git a/cache.h b/cache.h\nindex 1c45b62..cc817dc 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1749,4 +1749,9 @@ void stat_validity_update(struct stat_validity *sv, int fd);\n int versioncmp(const char *s1, const char *s2);\n void sleep_millisec(int millisec);\n \n+/*\n+ * Create a directory and (if share is nonzero) adjust its permissions\n+ * according to the shared_repository setting.\n+ */\n+void safe_create_dir(const char *dir, int share);\n #endif /* CACHE_H */\ndiff --git a/path.c b/path.c\nindex 48bd252..9ab2710 100644\n--- a/path.c\n+++ b/path.c\n@@ -740,6 +740,18 @@ int adjust_shared_perm(const char *path)\n \treturn 0;\n }\n \n+void safe_create_dir(const char *dir, int share)\n+{\n+\tif (mkdir(dir, 0777) < 0) {\n+\t\tif (errno != EEXIST) {\n+\t\t\tperror(dir);\n+\t\t\texit(1);\n+\t\t}\n+\t}\n+\telse if (share && adjust_shared_perm(dir))\n+\t\tdie(_(\"Could not make %s writable by group\"), dir);\n+}\n+\n static int have_same_root(const char *path1, const char *path2)\n {\n \tint is_abs1, is_abs2;\n-- \n2.4.2.644.g97b850b-twtrsrc\n"},{"id":"271509","messageId":"1444686725-27660-35-git-send-email-dturner@twopensource.com","threadId":"40543","inReplyTo":"1444686725-27660-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 33/44] refs.c: add method for initializing refs db","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-10-12T21:51:54Z","receivedAt":"2015-10-12T21:51:54Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"Alternate refs backends might not need refs/heads and so on, so we make\nref db initialization part of the backend.\n\nSigned-off-by: David Turner <dturner@twopensource.com>\n---\n builtin/init-db.c | 14 ++++----------\n refs-be-files.c   | 17 +++++++++++++++++\n refs.c            |  5 +++++\n refs.h            |  4 ++++\n 4 files changed, 30 insertions(+), 10 deletions(-)\n\ndiff --git a/builtin/init-db.c b/builtin/init-db.c\nindex f6f7259..2705739 100644\n--- a/builtin/init-db.c\n+++ b/builtin/init-db.c\n@@ -178,13 +178,7 @@ static int create_default_files(const char *template_path)\n \tchar junk[2];\n \tint reinit;\n \tint filemode;\n-\n-\t/*\n-\t * Create .git/refs/{heads,tags}\n-\t */\n-\tsafe_create_dir(git_path_buf(&buf, \"refs\"), 1);\n-\tsafe_create_dir(git_path_buf(&buf, \"refs/heads\"), 1);\n-\tsafe_create_dir(git_path_buf(&buf, \"refs/tags\"), 1);\n+\tstruct strbuf err = STRBUF_INIT;\n \n \t/* Just look for `init.templatedir` */\n \tgit_config(git_init_db_config, NULL);\n@@ -208,11 +202,11 @@ static int create_default_files(const char *template_path)\n \t */\n \tif (shared_repository) {\n \t\tadjust_shared_perm(get_git_dir());\n-\t\tadjust_shared_perm(git_path_buf(&buf, \"refs\"));\n-\t\tadjust_shared_perm(git_path_buf(&buf, \"refs/heads\"));\n-\t\tadjust_shared_perm(git_path_buf(&buf, \"refs/tags\"));\n \t}\n \n+\tif (refs_initdb(&err, shared_repository))\n+\t\tdie(\"failed to set up refs db: %s\", err.buf);\n+\n \t/*\n \t * Create the default symlink from \".git/HEAD\" to the \"master\"\n \t * branch, if it does not exist yet.\ndiff --git a/refs-be-files.c b/refs-be-files.c\nindex 7560a55..5f2602c 100644\n--- a/refs-be-files.c\n+++ b/refs-be-files.c\n@@ -3538,10 +3538,27 @@ static int files_reflog_expire(const char *refname, const unsigned char *sha1,\n \treturn -1;\n }\n \n+static int files_initdb(struct strbuf *err, int shared)\n+{\n+\t/*\n+\t * Create .git/refs/{heads,tags}\n+\t */\n+\tsafe_create_dir(git_path(\"refs\"), 1);\n+\tsafe_create_dir(git_path(\"refs/heads\"), 1);\n+\tsafe_create_dir(git_path(\"refs/tags\"), 1);\n+\tif (shared) {\n+\t\tadjust_shared_perm(git_path(\"refs\"));\n+\t\tadjust_shared_perm(git_path(\"refs/heads\"));\n+\t\tadjust_shared_perm(git_path(\"refs/tags\"));\n+\t}\n+\treturn 0;\n+}\n+\n struct ref_be refs_be_files = {\n \tNULL,\n \t\"files\",\n \tfiles_init_backend,\n+\tfiles_initdb,\n \tfiles_transaction_commit,\n \tfiles_initial_transaction_commit,\n \tfiles_for_each_reflog_ent,\ndiff --git a/refs.c b/refs.c\nindex 6e41ed8..5fc8eb7 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1102,6 +1102,11 @@ enum peel_status peel_object(const unsigned char *name, unsigned char *sha1)\n }\n \n /* backend functions */\n+int refs_initdb(struct strbuf *err, int shared)\n+{\n+\treturn the_refs_backend->initdb(err, shared);\n+}\n+\n int ref_transaction_commit(struct ref_transaction *transaction,\n \t\t\t   struct strbuf *err)\n {\ndiff --git a/refs.h b/refs.h\nindex e3136ee..ce71d33 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -68,6 +68,8 @@ extern int ref_exists(const char *refname);\n \n extern int is_branch(const char *refname);\n \n+extern int refs_initdb(struct strbuf *err, int shared);\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@@ -676,6 +678,7 @@ extern int reflog_expire(const char *refname, const unsigned char *sha1,\n \n /* refs backends */\n typedef void ref_backend_init_fn(void *data);\n+typedef int ref_backend_initdb_fn(struct strbuf *err, int shared);\n typedef int ref_transaction_commit_fn(struct ref_transaction *transaction,\n \t\t\t\t      struct strbuf *err);\n typedef void ref_transaction_free_fn(struct ref_transaction *transaction);\n@@ -737,6 +740,7 @@ struct ref_be {\n \tstruct ref_be *next;\n \tconst char *name;\n \tref_backend_init_fn *init_backend;\n+\tref_backend_initdb_fn *initdb;\n \tref_transaction_commit_fn *transaction_commit;\n \tref_transaction_commit_fn *initial_transaction_commit;\n \tfor_each_reflog_ent_fn *for_each_reflog_ent;\n-- \n2.4.2.644.g97b850b-twtrsrc\n"},{"id":"271521","messageId":"1444686725-27660-36-git-send-email-dturner@twopensource.com","threadId":"40543","inReplyTo":"1444686725-27660-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 34/44] refs-be-files.c: add method to rename refs","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-10-12T21:51:55Z","receivedAt":"2015-10-12T21:51:55Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"We also have to make rename_ref_available public, since alternate\nbackends for rename_ref will need it.\n\nSigned-off-by: David Turner <dturner@twopensource.com>\n---\n refs-be-files.c | 20 +++-----------------\n refs.c          | 21 +++++++++++++++++++++\n refs.h          |  7 ++++++-\n 3 files changed, 30 insertions(+), 18 deletions(-)\n\ndiff --git a/refs-be-files.c b/refs-be-files.c\nindex 5f2602c..4e6f032 100644\n--- a/refs-be-files.c\n+++ b/refs-be-files.c\n@@ -2444,29 +2444,14 @@ out:\n \treturn ret;\n }\n \n-static int rename_ref_available(const char *oldname, const char *newname)\n-{\n-\tstruct string_list skip = STRING_LIST_INIT_NODUP;\n-\tstruct strbuf err = STRBUF_INIT;\n-\tint ret;\n-\n-\tstring_list_insert(&skip, oldname);\n-\tret = !verify_refname_available(newname, NULL, &skip, &err);\n-\tif (!ret)\n-\t\terror(\"%s\", err.buf);\n-\n-\tstring_list_clear(&skip, 0);\n-\tstrbuf_release(&err);\n-\treturn ret;\n-}\n-\n static int write_ref_to_lockfile(struct ref_lock *lock,\n \t\t\t\t const unsigned char *sha1, struct strbuf *err);\n static int commit_ref_update(struct ref_lock *lock,\n \t\t\t     const unsigned char *sha1, const char *logmsg,\n \t\t\t     int flags, struct strbuf *err);\n \n-int rename_ref(const char *oldrefname, const char *newrefname, const char *logmsg)\n+static int files_rename_ref(const char *oldrefname, const char *newrefname,\n+\t\t\t    const char *logmsg)\n {\n \tunsigned char sha1[20], orig_sha1[20];\n \tint flag = 0, logmoved = 0;\n@@ -3561,6 +3546,7 @@ struct ref_be refs_be_files = {\n \tfiles_initdb,\n \tfiles_transaction_commit,\n \tfiles_initial_transaction_commit,\n+\tfiles_rename_ref,\n \tfiles_for_each_reflog_ent,\n \tfiles_for_each_reflog_ent_reverse,\n \tfiles_for_each_reflog,\ndiff --git a/refs.c b/refs.c\nindex 5fc8eb7..5a3125d 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1113,6 +1113,11 @@ int ref_transaction_commit(struct ref_transaction *transaction,\n \treturn the_refs_backend->transaction_commit(transaction, err);\n }\n \n+int rename_ref(const char *oldref, const char *newref, const char *logmsg)\n+{\n+\treturn the_refs_backend->rename_ref(oldref, newref, logmsg);\n+}\n+\n const char *resolve_ref_unsafe(const char *ref, int resolve_flags,\n \t\t\t       unsigned char *sha1, int *flags)\n {\n@@ -1126,6 +1131,22 @@ int verify_refname_available(const char *refname, struct string_list *extra,\n \treturn the_refs_backend->verify_refname_available(refname, extra, skip, err);\n }\n \n+int rename_ref_available(const char *oldname, const char *newname)\n+{\n+\tstruct string_list skip = STRING_LIST_INIT_NODUP;\n+\tstruct strbuf err = STRBUF_INIT;\n+\tint ret;\n+\n+\tstring_list_insert(&skip, oldname);\n+\tret = !verify_refname_available(newname, NULL, &skip, &err);\n+\tif (!ret)\n+\t\terror(\"%s\", err.buf);\n+\n+\tstring_list_clear(&skip, 0);\n+\tstrbuf_release(&err);\n+\treturn ret;\n+}\n+\n int pack_refs(unsigned int flags)\n {\n \treturn the_refs_backend->pack_refs(flags);\ndiff --git a/refs.h b/refs.h\nindex ce71d33..87d5801 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -464,10 +464,13 @@ extern const char *prettify_refname(const char *refname);\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+extern int rename_ref(const char *oldref, const char *newref,\n+\t\t      const char *logmsg);\n \n extern int create_symref(const char *ref, const char *refs_heads_master, const char *logmsg);\n \n+int rename_ref_available(const char *oldname, const char *newname);\n+\n enum action_on_err {\n \tUPDATE_REFS_MSG_ON_ERR,\n \tUPDATE_REFS_DIE_ON_ERR,\n@@ -703,6 +706,7 @@ typedef int reflog_expire_fn(const char *refname, const unsigned char *sha1,\n \t\t\t     void *policy_cb_data);\n \n /* resolution functions */\n+typedef int rename_ref_fn(const char *oldref, const char *newref, const char *logmsg);\n typedef const char *resolve_ref_unsafe_fn(const char *ref,\n \t\t\t\t\t  int resolve_flags,\n \t\t\t\t\t  unsigned char *sha1, int *flags);\n@@ -743,6 +747,7 @@ struct ref_be {\n \tref_backend_initdb_fn *initdb;\n \tref_transaction_commit_fn *transaction_commit;\n \tref_transaction_commit_fn *initial_transaction_commit;\n+\trename_ref_fn *rename_ref;\n \tfor_each_reflog_ent_fn *for_each_reflog_ent;\n \tfor_each_reflog_ent_reverse_fn *for_each_reflog_ent_reverse;\n \tfor_each_reflog_fn *for_each_reflog;\n-- \n2.4.2.644.g97b850b-twtrsrc\n"},{"id":"271510","messageId":"1444686725-27660-37-git-send-email-dturner@twopensource.com","threadId":"40543","inReplyTo":"1444686725-27660-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 35/44] refs: make lock generic","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-10-12T21:51:56Z","receivedAt":"2015-10-12T21:51:56Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"Instead of using a files-backend-specific struct ref_lock, the generic\nref_transaction struct should provide a void pointer that backends can use\nfor their own lock data.\n\nSigned-off-by: David Turner <dturner@twopensource.com>\n---\n refs-be-files.c | 29 ++++++++++++++++-------------\n refs.h          |  2 +-\n 2 files changed, 17 insertions(+), 14 deletions(-)\n\ndiff --git a/refs-be-files.c b/refs-be-files.c\nindex 4e6f032..798a995 100644\n--- a/refs-be-files.c\n+++ b/refs-be-files.c\n@@ -3167,11 +3167,12 @@ static int files_transaction_commit(struct ref_transaction *transaction,\n \t */\n \tfor (i = 0; i < n; i++) {\n \t\tstruct ref_update *update = updates[i];\n+\t\tstruct ref_lock *lock;\n \n \t\tif ((update->flags & REF_HAVE_NEW) &&\n \t\t    is_null_sha1(update->new_sha1))\n \t\t\tupdate->flags |= REF_DELETING;\n-\t\tupdate->lock = lock_ref_sha1_basic(\n+\t\tlock = lock_ref_sha1_basic(\n \t\t\t\tupdate->refname,\n \t\t\t\t((update->flags & REF_HAVE_OLD) ?\n \t\t\t\t update->old_sha1 : NULL),\n@@ -3179,7 +3180,8 @@ static int files_transaction_commit(struct ref_transaction *transaction,\n \t\t\t\tupdate->flags,\n \t\t\t\t&update->type,\n \t\t\t\terr);\n-\t\tif (!update->lock) {\n+\t\tupdate->backend_data = lock;\n+\t\tif (!lock) {\n \t\t\tchar *reason;\n \n \t\t\tret = (errno == ENOTDIR)\n@@ -3197,12 +3199,12 @@ static int files_transaction_commit(struct ref_transaction *transaction,\n \t\t\t\t\t\t  (update->flags & REF_NODEREF));\n \n \t\t\tif (!overwriting_symref &&\n-\t\t\t    !hashcmp(update->lock->old_oid.hash, update->new_sha1)) {\n+\t\t\t    !hashcmp(lock->old_oid.hash, update->new_sha1)) {\n \t\t\t\t/*\n \t\t\t\t * The reference already has the desired\n \t\t\t\t * value, so we don't need to write it.\n \t\t\t\t */\n-\t\t\t} else if (write_ref_to_lockfile(update->lock,\n+\t\t\t} else if (write_ref_to_lockfile(lock,\n \t\t\t\t\t\t\t update->new_sha1,\n \t\t\t\t\t\t\t err)) {\n \t\t\t\tchar *write_err = strbuf_detach(err, NULL);\n@@ -3211,7 +3213,7 @@ static int files_transaction_commit(struct ref_transaction *transaction,\n \t\t\t\t * The lock was freed upon failure of\n \t\t\t\t * write_ref_to_lockfile():\n \t\t\t\t */\n-\t\t\t\tupdate->lock = NULL;\n+\t\t\t\tupdate->backend_data = NULL;\n \t\t\t\tstrbuf_addf(err,\n \t\t\t\t\t    \"cannot update the ref '%s': %s\",\n \t\t\t\t\t    update->refname, write_err);\n@@ -3227,7 +3229,7 @@ static int files_transaction_commit(struct ref_transaction *transaction,\n \t\t\t * We didn't have to write anything to the lockfile.\n \t\t\t * Close it to free up the file descriptor:\n \t\t\t */\n-\t\t\tif (close_ref(update->lock)) {\n+\t\t\tif (close_ref(lock)) {\n \t\t\t\tstrbuf_addf(err, \"Couldn't close %s.lock\",\n \t\t\t\t\t    update->refname);\n \t\t\t\tgoto cleanup;\n@@ -3240,16 +3242,16 @@ static int files_transaction_commit(struct ref_transaction *transaction,\n \t\tstruct ref_update *update = updates[i];\n \n \t\tif (update->flags & REF_NEEDS_COMMIT) {\n-\t\t\tif (commit_ref_update(update->lock,\n+\t\t\tif (commit_ref_update(update->backend_data,\n \t\t\t\t\t      update->new_sha1, update->msg,\n \t\t\t\t\t      update->flags, err)) {\n \t\t\t\t/* freed by commit_ref_update(): */\n-\t\t\t\tupdate->lock = NULL;\n+\t\t\t\tupdate->backend_data = NULL;\n \t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\t\t\tgoto cleanup;\n \t\t\t} else {\n \t\t\t\t/* freed by commit_ref_update(): */\n-\t\t\t\tupdate->lock = NULL;\n+\t\t\t\tupdate->backend_data = NULL;\n \t\t\t}\n \t\t}\n \t}\n@@ -3257,16 +3259,17 @@ static int files_transaction_commit(struct ref_transaction *transaction,\n \t/* Perform deletes now that updates are safely completed */\n \tfor (i = 0; i < n; i++) {\n \t\tstruct ref_update *update = updates[i];\n+\t\tstruct ref_lock *lock = update->backend_data;\n \n \t\tif (update->flags & REF_DELETING) {\n-\t\t\tif (delete_ref_loose(update->lock, update->type, err)) {\n+\t\t\tif (delete_ref_loose(lock, update->type, err)) {\n \t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\t\t\tgoto cleanup;\n \t\t\t}\n \n \t\t\tif (!(update->flags & REF_ISPRUNING))\n \t\t\t\tstring_list_append(&refs_to_delete,\n-\t\t\t\t\t\t   update->lock->ref_name);\n+\t\t\t\t\t\t   lock->ref_name);\n \t\t}\n \t}\n \n@@ -3282,8 +3285,8 @@ cleanup:\n \ttransaction->state = REF_TRANSACTION_CLOSED;\n \n \tfor (i = 0; i < n; i++)\n-\t\tif (updates[i]->lock)\n-\t\t\tunlock_ref(updates[i]->lock);\n+\t\tif (updates[i]->backend_data)\n+\t\t\tunlock_ref(updates[i]->backend_data);\n \tstring_list_clear(&refs_to_delete, 0);\n \tstring_list_clear(&affected_refnames, 0);\n \treturn ret;\ndiff --git a/refs.h b/refs.h\nindex 87d5801..4d8b3bb 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -196,7 +196,7 @@ struct ref_update {\n \t * REF_DELETING, and REF_ISPRUNING:\n \t */\n \tunsigned int flags;\n-\tstruct ref_lock *lock;\n+\tvoid *backend_data;\n \tint type;\n \tchar *msg;\n \tconst char refname[FLEX_ARRAY];\n-- \n2.4.2.644.g97b850b-twtrsrc\n"},{"id":"271519","messageId":"1444686725-27660-38-git-send-email-dturner@twopensource.com","threadId":"40543","inReplyTo":"1444686725-27660-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 36/44] refs: make files_log_ref_write functions public","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-10-12T21:51:57Z","receivedAt":"2015-10-12T21:51:57Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"Because HEAD and stash are per-worktree, refs.c needs to go through\nthe files backend to write these refs.\n\nIn this patch, we make one files backend internal functions\npublic. Later, we will use this to handle reflog updates for\nper-worktree symbolic refs (HEAD).\n\nSigned-off-by: David Turner <dturner@twopensource.com>\n---\n refs-be-files.c | 8 ++++++++\n refs.h          | 5 +++++\n 2 files changed, 13 insertions(+)\n\ndiff --git a/refs-be-files.c b/refs-be-files.c\nindex 798a995..8d1ffce 100644\n--- a/refs-be-files.c\n+++ b/refs-be-files.c\n@@ -2710,6 +2710,14 @@ static int log_ref_write(const char *refname, const unsigned char *old_sha1,\n \t\t\t const unsigned char *new_sha1, const char *msg,\n \t\t\t int flags, struct strbuf *err)\n {\n+\treturn files_log_ref_write(refname, old_sha1, new_sha1, msg, flags,\n+\t\t\t\t   err);\n+}\n+\n+int files_log_ref_write(const char *refname, const unsigned char *old_sha1,\n+\t\t\tconst unsigned char *new_sha1, const char *msg,\n+\t\t\tint flags, struct strbuf *err)\n+{\n \tstruct strbuf sb = STRBUF_INIT;\n \tint ret = log_ref_write_1(refname, old_sha1, new_sha1, msg, &sb, flags,\n \t\t\t\t  err);\ndiff --git a/refs.h b/refs.h\nindex 4d8b3bb..3aad3b8 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -633,6 +633,11 @@ enum ref_type ref_type(const char *refname);\n \n int copy_reflog_msg(char *buf, const char *msg);\n \n+int files_log_ref_write(const char *refname, const unsigned char *old_sha1,\n+\t\t\tconst unsigned char *new_sha1, const char *msg,\n+\t\t\tint flags, struct strbuf *err);\n+\n+\n enum expire_reflog_flags {\n \tEXPIRE_REFLOGS_DRY_RUN = 1 << 0,\n \tEXPIRE_REFLOGS_UPDATE_REF = 1 << 1,\n-- \n2.4.2.644.g97b850b-twtrsrc\n"},{"id":"271516","messageId":"1444686725-27660-39-git-send-email-dturner@twopensource.com","threadId":"40543","inReplyTo":"1444686725-27660-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 37/44] refs: break out a ref conflict check","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-10-12T21:51:58Z","receivedAt":"2015-10-12T21:51:58Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"Create new function verify_no_descendants, to hold one of the ref\nconflict checks used in verify_refname_available.  Multiple backends\nwill need this function, so it goes in the common code.\n\nSigned-off-by: David Turner <dturner@twopensource.com>\n---\n refs-be-files.c | 33 ++++++++-------------------------\n refs.c          | 22 ++++++++++++++++++++++\n refs.h          |  7 +++++++\n 3 files changed, 37 insertions(+), 25 deletions(-)\n\ndiff --git a/refs-be-files.c b/refs-be-files.c\nindex 8d1ffce..dd2c42e 100644\n--- a/refs-be-files.c\n+++ b/refs-be-files.c\n@@ -730,6 +730,7 @@ static int verify_refname_available_dir(const char *refname,\n \t\t\t\t\tstruct strbuf *err)\n {\n \tconst char *slash;\n+\tconst char *extra_refname;\n \tint pos;\n \tstruct strbuf dirname = STRBUF_INIT;\n \tint ret = -1;\n@@ -835,33 +836,15 @@ static int verify_refname_available_dir(const char *refname,\n \t\t}\n \t}\n \n-\tif (extras) {\n-\t\t/*\n-\t\t * Check for entries in extras that start with\n-\t\t * \"$refname/\". We do that by looking for the place\n-\t\t * where \"$refname/\" would be inserted in extras. If\n-\t\t * there is an entry at that position that starts with\n-\t\t * \"$refname/\" and is not in skip, then we have a\n-\t\t * conflict.\n-\t\t */\n-\t\tfor (pos = string_list_find_insert_index(extras, dirname.buf, 0);\n-\t\t     pos < extras->nr; pos++) {\n-\t\t\tconst char *extra_refname = extras->items[pos].string;\n-\n-\t\t\tif (!starts_with(extra_refname, dirname.buf))\n-\t\t\t\tbreak;\n-\n-\t\t\tif (!skip || !string_list_has_string(skip, extra_refname)) {\n-\t\t\t\tstrbuf_addf(err, \"cannot process '%s' and '%s' at the same time\",\n-\t\t\t\t\t    refname, extra_refname);\n-\t\t\t\tgoto cleanup;\n-\t\t\t}\n-\t\t}\n+\textra_refname = find_descendant_ref(dirname.buf, extras, skip);\n+\tif (extra_refname) {\n+\t\tstrbuf_addf(err,\n+\t\t\t    \"cannot process '%s' and '%s' at the same time\",\n+\t\t\t    refname, extra_refname);\n+\t} else {\n+\t\tret = 0;\n \t}\n \n-\t/* No conflicts were found */\n-\tret = 0;\n-\n cleanup:\n \tstrbuf_release(&dirname);\n \treturn ret;\ndiff --git a/refs.c b/refs.c\nindex 5a3125d..3ae0274 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1101,6 +1101,28 @@ enum peel_status peel_object(const unsigned char *name, unsigned char *sha1)\n \treturn PEEL_PEELED;\n }\n \n+const char *find_descendant_ref(const char *refname,\n+\t\t\t\tconst struct string_list *extras,\n+\t\t\t\tconst struct string_list *skip)\n+{\n+\tint pos;\n+\tif (!extras)\n+\t\treturn NULL;\n+\n+\t/* Look for the place where \"$refname/\" would be inserted in extras. */\n+\tfor (pos = string_list_find_insert_index(extras, refname, 0);\n+\t     pos < extras->nr; pos++) {\n+\t\tconst char *extra_refname = extras->items[pos].string;\n+\n+\t\tif (!starts_with(extra_refname, refname))\n+\t\t\tbreak;\n+\n+\t\tif (!skip || !string_list_has_string(skip, extra_refname))\n+\t\t\treturn extra_refname;\n+\t}\n+\treturn NULL;\n+}\n+\n /* backend functions */\n int refs_initdb(struct strbuf *err, int shared)\n {\ndiff --git a/refs.h b/refs.h\nindex 3aad3b8..f8becea 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -637,6 +637,13 @@ int files_log_ref_write(const char *refname, const unsigned char *old_sha1,\n \t\t\tconst unsigned char *new_sha1, const char *msg,\n \t\t\tint flags, struct strbuf *err);\n \n+/*\n+ * Check for entries in extras that start with \"$refname/\", ignoring\n+ * those in skip. If there is such an entry, then we have a conflict.\n+ */\n+const char *find_descendant_ref(const char *refname,\n+\t\t\t\tconst struct string_list *extras,\n+\t\t\t\tconst struct string_list *skip);\n \n enum expire_reflog_flags {\n \tEXPIRE_REFLOGS_DRY_RUN = 1 << 0,\n-- \n2.4.2.644.g97b850b-twtrsrc\n"},{"id":"271512","messageId":"1444686725-27660-40-git-send-email-dturner@twopensource.com","threadId":"40543","inReplyTo":"1444686725-27660-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 38/44] refs: move duplicate check to common code","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-10-12T21:51:59Z","receivedAt":"2015-10-12T21:51:59Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"The check for duplicate refnames in a transaction is needed for\nall backends, so move it to the common code.\n\nref_transaction_commit_fn gains a new argument, the sorted\nstring_list of affected refnames.\n\nSigned-off-by: David Turner <dturner@twopensource.com>\n---\n refs-be-files.c | 57 ++++-----------------------------------------\n refs.c          | 71 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--\n refs.h          |  1 +\n 3 files changed, 75 insertions(+), 54 deletions(-)\n\ndiff --git a/refs-be-files.c b/refs-be-files.c\nindex dd2c42e..b89bd5c 100644\n--- a/refs-be-files.c\n+++ b/refs-be-files.c\n@@ -3104,24 +3104,8 @@ static int files_for_each_reflog(each_ref_fn fn, void *cb_data)\n \treturn retval;\n }\n \n-static int ref_update_reject_duplicates(struct string_list *refnames,\n-\t\t\t\t\tstruct strbuf *err)\n-{\n-\tint i, n = refnames->nr;\n-\n-\tassert(err);\n-\n-\tfor (i = 1; i < n; i++)\n-\t\tif (!strcmp(refnames->items[i - 1].string, refnames->items[i].string)) {\n-\t\t\tstrbuf_addf(err,\n-\t\t\t\t    \"Multiple updates for ref '%s' not allowed.\",\n-\t\t\t\t    refnames->items[i].string);\n-\t\t\treturn 1;\n-\t\t}\n-\treturn 0;\n-}\n-\n static int files_transaction_commit(struct ref_transaction *transaction,\n+\t\t\t\t    struct string_list *affected_refnames,\n \t\t\t\t    struct strbuf *err)\n {\n \tint ret = 0, i;\n@@ -3129,26 +3113,6 @@ static int files_transaction_commit(struct ref_transaction *transaction,\n \tstruct ref_update **updates = transaction->updates;\n \tstruct string_list refs_to_delete = STRING_LIST_INIT_NODUP;\n \tstruct string_list_item *ref_to_delete;\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-\tif (!n) {\n-\t\ttransaction->state = REF_TRANSACTION_CLOSED;\n-\t\treturn 0;\n-\t}\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 \t/*\n \t * Acquire all locks, verify old values if provided, check\n@@ -3167,7 +3131,7 @@ static int files_transaction_commit(struct ref_transaction *transaction,\n \t\t\t\tupdate->refname,\n \t\t\t\t((update->flags & REF_HAVE_OLD) ?\n \t\t\t\t update->old_sha1 : NULL),\n-\t\t\t\t&affected_refnames, NULL,\n+\t\t\t\taffected_refnames, NULL,\n \t\t\t\tupdate->flags,\n \t\t\t\t&update->type,\n \t\t\t\terr);\n@@ -3279,7 +3243,6 @@ cleanup:\n \t\tif (updates[i]->backend_data)\n \t\t\tunlock_ref(updates[i]->backend_data);\n \tstring_list_clear(&refs_to_delete, 0);\n-\tstring_list_clear(&affected_refnames, 0);\n \treturn ret;\n }\n \n@@ -3297,27 +3260,18 @@ void files_init_backend(void *data)\n }\n \n static int files_initial_transaction_commit(struct ref_transaction *transaction,\n+\t\t\t\t\t    struct string_list *affected_refnames,\n \t\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-\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 \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@@ -3330,7 +3284,7 @@ static int files_initial_transaction_commit(struct ref_transaction *transaction,\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+\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@@ -3340,7 +3294,7 @@ static int files_initial_transaction_commit(struct ref_transaction *transaction,\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     affected_refnames, NULL,\n \t\t\t\t\t     err)) {\n \t\t\tret = TRANSACTION_NAME_CONFLICT;\n \t\t\tgoto cleanup;\n@@ -3371,7 +3325,6 @@ static int files_initial_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 \ndiff --git a/refs.c b/refs.c\nindex 3ae0274..b00f910 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1123,6 +1123,37 @@ const char *find_descendant_ref(const char *refname,\n \treturn NULL;\n }\n \n+/*\n+ * Return 1 if there are any duplicate refnames in the updates in\n+ * `transaction`, and fill in err with an appropriate error message.\n+ * Fill in `refnames` with the refnames from the transaction.\n+ */\n+\n+static int ref_update_reject_duplicates(struct ref_transaction *transaction,\n+\t\t\t\t\tstruct string_list *refnames,\n+\t\t\t\t\tstruct strbuf *err)\n+{\n+\tint i, n = transaction->nr;\n+\tstruct ref_update **updates;\n+\n+\tassert(err);\n+\n+\tupdates = transaction->updates;\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(refnames, updates[i]->refname);\n+\tstring_list_sort(refnames);\n+\n+\tfor (i = 1; i < n; i++)\n+\t\tif (!strcmp(refnames->items[i - 1].string, refnames->items[i].string)) {\n+\t\t\tstrbuf_addf(err,\n+\t\t\t\t    \"Multiple updates for ref '%s' not allowed.\",\n+\t\t\t\t    refnames->items[i].string);\n+\t\t\treturn 1;\n+\t\t}\n+\treturn 0;\n+}\n+\n /* backend functions */\n int refs_initdb(struct strbuf *err, int shared)\n {\n@@ -1132,7 +1163,29 @@ int refs_initdb(struct strbuf *err, int shared)\n int ref_transaction_commit(struct ref_transaction *transaction,\n \t\t\t   struct strbuf *err)\n {\n-\treturn the_refs_backend->transaction_commit(transaction, err);\n+\tint ret = -1;\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+\tif (!transaction->nr) {\n+\t\ttransaction->state = REF_TRANSACTION_CLOSED;\n+\t\treturn 0;\n+\t}\n+\n+\tif (ref_update_reject_duplicates(transaction, &affected_refnames, err)) {\n+\t\tret = TRANSACTION_GENERIC_ERROR;\n+\t\tgoto done;\n+\t}\n+\n+\tret = the_refs_backend->transaction_commit(transaction,\n+\t\t\t\t\t\t   &affected_refnames, err);\n+done:\n+\tstring_list_clear(&affected_refnames, 0);\n+\treturn ret;\n }\n \n int rename_ref(const char *oldref, const char *newref, const char *logmsg)\n@@ -1302,5 +1355,19 @@ int reflog_expire(const char *refname, const unsigned char *sha1,\n int initial_ref_transaction_commit(struct ref_transaction *transaction,\n \t\t\t\t   struct strbuf *err)\n {\n-\treturn the_refs_backend->initial_transaction_commit(transaction, err);\n+\n+\tstruct string_list affected_refnames = STRING_LIST_INIT_NODUP;\n+\tint ret;\n+\n+\tif (ref_update_reject_duplicates(transaction,\n+\t\t\t\t\t &affected_refnames, err)) {\n+\t\tret = TRANSACTION_GENERIC_ERROR;\n+\t\tgoto done;\n+\t}\n+\tret = the_refs_backend->initial_transaction_commit(transaction,\n+\t\t\t\t\t\t\t   &affected_refnames,\n+\t\t\t\t\t\t\t   err);\n+done:\n+\tstring_list_clear(&affected_refnames, 0);\n+\treturn ret;\n }\ndiff --git a/refs.h b/refs.h\nindex f8becea..a9fdbf0 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -695,6 +695,7 @@ extern int reflog_expire(const char *refname, const unsigned char *sha1,\n typedef void ref_backend_init_fn(void *data);\n typedef int ref_backend_initdb_fn(struct strbuf *err, int shared);\n typedef int ref_transaction_commit_fn(struct ref_transaction *transaction,\n+\t\t\t\t      struct string_list *affected_refnames,\n \t\t\t\t      struct strbuf *err);\n typedef void ref_transaction_free_fn(struct ref_transaction *transaction);\n \n-- \n2.4.2.644.g97b850b-twtrsrc\n"},{"id":"271514","messageId":"1444686725-27660-41-git-send-email-dturner@twopensource.com","threadId":"40543","inReplyTo":"1444686725-27660-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 39/44] refs: always handle non-normal refs in files backend","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-10-12T21:52:00Z","receivedAt":"2015-10-12T21:52:00Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"Always handle non-normal (per-worktree or pseudo) refs in the files\nbackend instead of alternate backends.\n\nSometimes a ref transaction will update both a per-worktree ref and a\nnormal ref.  For instance, an ordinary commit might update\nrefs/heads/master and HEAD (or at least HEAD's reflog).\n\nWe handle three cases here:\n\n1. updates to normal refs continue to go through the chosen backend\n\n2. updates to non-normal refs with REF_NODEREF or to non-symbolic refs\nare moved to a separate files backend transaction.\n\n3. updates to symbolic refs are dereferenced to their base ref.  The\nupdate to the base ref then goes through the ordinary backend, while\nthe files backend is directly called to update the symref's reflog.\n\nSigned-off-by: David Turner <dturner@twopensource.com>\n---\n refs.c | 141 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-\n 1 file changed, 139 insertions(+), 2 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex b00f910..025cc08 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -7,6 +7,11 @@\n #include \"object.h\"\n #include \"tag.h\"\n \n+const char split_transaction_fail_warning[] =\n+\t\"A ref transaction was split across two refs backends.  Part of the \"\n+\t\"transaction succeeded, but then the update to the per-worktree refs \"\n+\t\"failed.  Your repository may be in an inconsistent state.\";\n+\n /*\n  * We always have a files backend and it is the default.\n  */\n@@ -963,6 +968,13 @@ void ref_transaction_free(struct ref_transaction *transaction)\n \tfree(transaction);\n }\n \n+static void add_update_obj(struct ref_transaction *transaction,\n+\t\t\t   struct ref_update *update)\n+{\n+\tALLOC_GROW(transaction->updates, transaction->nr + 1, transaction->alloc);\n+\ttransaction->updates[transaction->nr++] = update;\n+}\n+\n static struct ref_update *add_update(struct ref_transaction *transaction,\n \t\t\t\t     const char *refname)\n {\n@@ -970,8 +982,7 @@ static struct ref_update *add_update(struct ref_transaction *transaction,\n \tstruct ref_update *update = xcalloc(1, sizeof(*update) + len);\n \n \tmemcpy((char *)update->refname, refname, len); /* includes NUL */\n-\tALLOC_GROW(transaction->updates, transaction->nr + 1, transaction->alloc);\n-\ttransaction->updates[transaction->nr++] = update;\n+\tadd_update_obj(transaction, update);\n \treturn update;\n }\n \n@@ -1160,11 +1171,87 @@ int refs_initdb(struct strbuf *err, int shared)\n \treturn the_refs_backend->initdb(err, shared);\n }\n \n+/*\n+ * Special case for non-normal refs.  For symbolic-refs when\n+ * REF_NODEREF is not turned on, we dereference them here and replace\n+ * updates to the symbolic refs with updates to the underlying ref.\n+ * Then we do our own reflogging for the symbolic ref.\n+ *\n+ * We move other non-normal ref updates with into a specially-created\n+ * files-backend transaction\n+ */\n+static int move_abnormal_ref_updates(struct ref_transaction *transaction,\n+\t\t\t\t     struct ref_transaction *files_transaction,\n+\t\t\t\t     struct string_list *symrefs)\n+{\n+\tint i;\n+\n+\tfor (i = 0; i < transaction->nr; i++) {\n+\t\tstruct ref_update *update = transaction->updates[i];\n+\t\tconst char *resolved;\n+\t\tint flags = 0;\n+\t\tunsigned char sha1[20];\n+\n+\t\tif (ref_type(update->refname) == REF_TYPE_NORMAL)\n+\t\t\tcontinue;\n+\n+\t\tresolved = resolve_ref_unsafe(update->refname, 0, sha1, &flags);\n+\n+\t\tif (update->flags & REF_NODEREF || !(flags & REF_ISSYMREF)) {\n+\t\t\tint last;\n+\n+\t\t\tadd_update_obj(files_transaction, update);\n+\t\t\t/*\n+\t\t\t * Replace this transaction with the\n+\t\t\t * last transaction, removing it from\n+\t\t\t * the list of backend transactions\n+\t\t\t */\n+\t\t\tlast = --transaction->nr;\n+\t\t\ttransaction->updates[i] = transaction->updates[last];\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (resolved) {\n+\t\t\tstruct ref_update *new_update;\n+\t\t\tstruct string_list_item *item;\n+\n+\t\t\tif (ref_type(resolved) != REF_TYPE_NORMAL)\n+\t\t\t\tdie(\"Non-normal symbolic ref `%s` points to non-normal ref `%s`\", update->refname, resolved);\n+\n+\t\t\tnew_update = xmalloc(sizeof(*new_update) +\n+\t\t\t\t\t     strlen(resolved) + 1);\n+\t\t\tmemcpy(new_update, update, sizeof(*update));\n+\n+\t\t\tif (update->flags & REF_HAVE_OLD &&\n+\t\t\t    hashcmp(sha1, update->old_sha1)) {\n+\t\t\t\t/* consistency check failed */\n+\t\t\t\tfree(new_update);\n+\t\t\t\treturn -1;\n+\t\t\t} else {\n+\t\t\t\thashcpy(update->old_sha1, sha1);\n+\t\t\t}\n+\n+\t\t\tstrcpy((char *)new_update->refname, resolved);\n+\t\t\ttransaction->updates[i] = new_update;\n+\n+\t\t\titem = string_list_append(symrefs, update->refname);\n+\t\t\titem->util = new_update;\n+\t\t\tfree(update);\n+\t\t}\n+\t}\n+\n+\treturn 0;\n+}\n+\n int ref_transaction_commit(struct ref_transaction *transaction,\n \t\t\t   struct strbuf *err)\n {\n \tint ret = -1;\n \tstruct string_list affected_refnames = STRING_LIST_INIT_NODUP;\n+\tstruct string_list files_affected_refnames = STRING_LIST_INIT_NODUP;\n+\tstruct string_list symrefs = STRING_LIST_INIT_DUP;\n+\tstruct string_list_item *item;\n+\tstruct ref_transaction *files_transaction = NULL;\n \n \tassert(err);\n \n@@ -1176,6 +1263,26 @@ int ref_transaction_commit(struct ref_transaction *transaction,\n \t\treturn 0;\n \t}\n \n+\tif (the_refs_backend != &refs_be_files) {\n+\t\tfiles_transaction = ref_transaction_begin(err);\n+\t\tif (!files_transaction)\n+\t\t\tdie(\"%s\", err->buf);\n+\n+\t\tret = move_abnormal_ref_updates(transaction, files_transaction,\n+\t\t\t\t\t\t&symrefs);\n+\t\tif (ret)\n+\t\t\tgoto done;\n+\n+\t\t/* files backend commit */\n+\t\tif (ref_update_reject_duplicates(files_transaction,\n+\t\t\t\t\t\t &files_affected_refnames,\n+\t\t\t\t\t\t err)) {\n+\t\t\tret = TRANSACTION_GENERIC_ERROR;\n+\t\t\tgoto done;\n+\t\t}\n+\t}\n+\n+\t/* main backend commit */\n \tif (ref_update_reject_duplicates(transaction, &affected_refnames, err)) {\n \t\tret = TRANSACTION_GENERIC_ERROR;\n \t\tgoto done;\n@@ -1183,8 +1290,35 @@ int ref_transaction_commit(struct ref_transaction *transaction,\n \n \tret = the_refs_backend->transaction_commit(transaction,\n \t\t\t\t\t\t   &affected_refnames, err);\n+\tif (ret)\n+\t\tgoto done;\n+\n+\tif (the_refs_backend != &refs_be_files) {\n+\t\tret = refs_be_files.transaction_commit(files_transaction,\n+\t\t\t\t\t\t       &files_affected_refnames,\n+\t\t\t\t\t\t       err);\n+\t\tif (ret) {\n+\t\t\twarning(split_transaction_fail_warning);\n+\t\t\tgoto done;\n+\t\t}\n+\n+\t\t/* reflogging for dereferenced symbolic refs */\n+\t\tfor_each_string_list_item(item, &symrefs) {\n+\t\t\tstruct ref_update *update = item->util;\n+\t\t\tif (files_log_ref_write(item->string, update->old_sha1,\n+\t\t\t\t\t\tupdate->new_sha1,\n+\t\t\t\t\t\tupdate->msg, update->flags, err))\n+\t\t\t\twarning(\"failed to log ref update for symref %s\",\n+\t\t\t\t\titem->string);\n+\t\t}\n+\t}\n+\n done:\n \tstring_list_clear(&affected_refnames, 0);\n+\tstring_list_clear(&files_affected_refnames, 0);\n+\tif (files_transaction)\n+\t\tref_transaction_free(files_transaction);\n+\tstring_list_clear(&symrefs, 0);\n \treturn ret;\n }\n \n@@ -1235,6 +1369,9 @@ int peel_ref(const char *refname, unsigned char *sha1)\n int create_symref(const char *ref_target, const char *refs_heads_master,\n \t\t  const char *logmsg)\n {\n+\tif (ref_type(ref_target) != REF_TYPE_NORMAL)\n+\t\treturn refs_be_files.create_symref(ref_target, refs_heads_master,\n+\t\t\t\t\t\t   logmsg);\n \treturn the_refs_backend->create_symref(ref_target, refs_heads_master,\n \t\t\t\t\t       logmsg);\n }\n-- \n2.4.2.644.g97b850b-twtrsrc\n"},{"id":"271518","messageId":"1444686725-27660-42-git-send-email-dturner@twopensource.com","threadId":"40543","inReplyTo":"1444686725-27660-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 40/44] refs: allow ref backend to be set for clone","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-10-12T21:52:01Z","receivedAt":"2015-10-12T21:52:01Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"Add a new option, --refs-backend-type, to allow the ref backend type to\nbe set on new clones.\n\nSubmodules must use the same ref backend as the parent repository, so\nwe also pass the --refs-backend-type option option when cloning\nsubmodules.\n\nSigned-off-by: David Turner <dturner@twopensource.com>\n---\n Documentation/git-clone.txt |  4 ++++\n builtin/clone.c             | 27 +++++++++++++++++++++++++--\n builtin/submodule--helper.c |  5 ++++-\n cache.h                     |  1 +\n refs.c                      |  2 ++\n 5 files changed, 36 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt\nindex f1f2a3f..d7a4cb0 100644\n--- a/Documentation/git-clone.txt\n+++ b/Documentation/git-clone.txt\n@@ -14,6 +14,7 @@ SYNOPSIS\n \t  [-o <name>] [-b <name>] [-u <upload-pack>] [--reference <repository>]\n \t  [--dissociate] [--separate-git-dir <git dir>]\n \t  [--depth <depth>] [--[no-]single-branch]\n+\t  [--refs-backend-type=<name>]\n \t  [--recursive | --recurse-submodules] [--] <repository>\n \t  [<directory>]\n \n@@ -216,6 +217,9 @@ objects from the source repository into a pack in the cloned repository.\n \tThe result is Git repository can be separated from working\n \ttree.\n \n+--refs-backend-type=<name>::\n+\tType of refs backend. Default is to use the original files based\n+\tbackend.\n \n <repository>::\n \tThe (possibly remote) repository to clone from.  See the\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 3e14491..d489a87 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -92,11 +92,13 @@ static struct option builtin_clone_options[] = {\n \t\t   N_(\"separate git dir from working tree\")),\n \tOPT_STRING_LIST('c', \"config\", &option_config, N_(\"key=value\"),\n \t\t\tN_(\"set config inside the new repository\")),\n+\tOPT_STRING(0, \"refs-backend-type\", &refs_backend_type,\n+\t\t   N_(\"name\"), N_(\"name of backend type to use\")),\n \tOPT_END()\n };\n \n static const char *argv_submodule[] = {\n-\t\"submodule\", \"update\", \"--init\", \"--recursive\", NULL\n+\t\"submodule\", \"update\", \"--init\", \"--recursive\", NULL, NULL\n };\n \n static const char *get_repo_path_1(struct strbuf *path, int *is_bundle)\n@@ -724,8 +726,24 @@ static int checkout(void)\n \terr |= run_hook_le(NULL, \"post-checkout\", sha1_to_hex(null_sha1),\n \t\t\t   sha1_to_hex(sha1), \"1\", NULL);\n \n-\tif (!err && option_recursive)\n+\tif (!err && option_recursive) {\n+\t\tconst char **backend_arg = argv_submodule;\n+\t\tchar *new_backend_arg = NULL;\n+\t\tif (refs_backend_type) {\n+\t\t\twhile (*backend_arg)\n+\t\t\t\t++backend_arg;\n+\n+\t\t\tnew_backend_arg = xmalloc(21 + strlen(refs_backend_type));\n+\t\t\tsprintf(new_backend_arg, \"--refs-backend-type=%s\",\n+\t\t\t\trefs_backend_type);\n+\t\t\t*backend_arg = new_backend_arg;\n+\t\t}\n \t\terr = run_command_v_opt(argv_submodule, RUN_GIT_CMD);\n+\t\tif (refs_backend_type) {\n+\t\t\tfree(new_backend_arg);\n+\t\t\t*backend_arg = NULL;\n+\t\t}\n+\t}\n \n \treturn err;\n }\n@@ -744,6 +762,11 @@ static void write_config(struct string_list *config)\n \t\t\t\t\t       write_one_config, NULL) < 0)\n \t\t\tdie(\"unable to write parameters to config file\");\n \t}\n+\n+\tif (refs_backend_type &&\n+\t    write_one_config(\"core.refs-backend-type\",\n+\t\t\t     refs_backend_type, NULL) < 0)\n+\t\t\tdie(\"unable to write backend parameter to config file\");\n }\n \n static void write_refspec_config(const char *src_ref_prefix,\ndiff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\nindex f4c3eff..5c9ca4e 100644\n--- a/builtin/submodule--helper.c\n+++ b/builtin/submodule--helper.c\n@@ -140,7 +140,10 @@ static int clone_submodule(const char *path, const char *gitdir, const char *url\n \t\targv_array_pushl(&cp.args, \"--reference\", reference, NULL);\n \tif (gitdir && *gitdir)\n \t\targv_array_pushl(&cp.args, \"--separate-git-dir\", gitdir, NULL);\n-\n+\tif (refs_backend_type && *refs_backend_type) {\n+\t\targv_array_push(&cp.args, \"--refs-backend-type\");\n+\t\targv_array_push(&cp.args, refs_backend_type);\n+\t}\n \targv_array_push(&cp.args, url);\n \targv_array_push(&cp.args, path);\n \ndiff --git a/cache.h b/cache.h\nindex cc817dc..692cfd3 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -695,6 +695,7 @@ enum object_creation_mode {\n extern enum object_creation_mode object_creation_mode;\n \n extern char *notes_ref_name;\n+extern const char *refs_backend_type;\n \n extern int grafts_replace_parents;\n \ndiff --git a/refs.c b/refs.c\nindex 025cc08..901558d 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -21,6 +21,8 @@ struct ref_be *the_refs_backend = &refs_be_files;\n  */\n struct ref_be *refs_backends = &refs_be_files;\n \n+const char *refs_backend_type;\n+\n /*\n  * This function is used to switch to an alternate backend.\n  */\n-- \n2.4.2.644.g97b850b-twtrsrc\n"},{"id":"271511","messageId":"1444686725-27660-43-git-send-email-dturner@twopensource.com","threadId":"40543","inReplyTo":"1444686725-27660-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 41/44] refs: add register_refs_backend","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-10-12T21:52:02Z","receivedAt":"2015-10-12T21:52:02Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"Add register_refs_backend, to allow refs backends to be registered.\n\nSigned-off-by: David Turner <dturner@twopensource.com>\n---\n refs.c | 6 ++++++\n refs.h | 1 +\n 2 files changed, 7 insertions(+)\n\ndiff --git a/refs.c b/refs.c\nindex 901558d..534d12e 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -23,6 +23,12 @@ struct ref_be *refs_backends = &refs_be_files;\n \n const char *refs_backend_type;\n \n+void register_refs_backend(struct ref_be *be)\n+{\n+\tbe->next = refs_backends;\n+\trefs_backends = be;\n+}\n+\n /*\n  * This function is used to switch to an alternate backend.\n  */\ndiff --git a/refs.h b/refs.h\nindex a9fdbf0..45615e8 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -790,5 +790,6 @@ struct ref_be {\n \n extern struct ref_be refs_be_files;\n int set_refs_backend(const char *name, void *init_data);\n+void register_refs_backend(struct ref_be *be);\n \n #endif /* REFS_H */\n-- \n2.4.2.644.g97b850b-twtrsrc\n"},{"id":"271513","messageId":"1444686725-27660-44-git-send-email-dturner@twopensource.com","threadId":"40543","inReplyTo":"1444686725-27660-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 42/44] introduce \"extensions\" form of core.repositoryformatversion","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-10-12T21:52:03Z","receivedAt":"2015-10-12T21:52:03Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"From: Jeff King <peff@peff.net>\n\nNormally we try to avoid bumps of the whole-repository\ncore.repositoryformatversion field. However, it is\nunavoidable if we want to safely change certain aspects of\ngit in a backwards-incompatible way (e.g., modifying the set\nof ref tips that we must traverse to generate a list of\nunreachable, safe-to-prune objects).\n\nIf we were to bump the repository version for every such\nchange, then any implementation understanding version `X`\nwould also have to understand `X-1`, `X-2`, and so forth,\neven though the incompatibilities may be in orthogonal parts\nof the system, and there is otherwise no reason we cannot\nimplement one without the other (or more importantly, that\nthe user cannot choose to use one feature without the other,\nweighing the tradeoff in compatibility only for that\nparticular feature).\n\nThis patch documents the existing repositoryformatversion\nstrategy and introduces a new format, \"1\", which lets a\nrepository specify that it must run with an arbitrary set of\nextensions. This can be used, for example:\n\n - to inform git that the objects should not be pruned based\n   only on the reachability of the ref tips (e.g, because it\n   has \"clone --shared\" children)\n\n - that the refs are stored in a format besides the usual\n   \"refs\" and \"packed-refs\" directories\n\nBecause we bump to format \"1\", and because format \"1\"\nrequires that a running git knows about any extensions\nmentioned, we know that older versions of the code will not\ndo something dangerous when confronted with these new\nformats.\n\nFor example, if the user chooses to use database storage for\nrefs, they may set the \"extensions.refbackend\" config to\n\"db\". Older versions of git will not understand format \"1\"\nand bail. Versions of git which understand \"1\" but do not\nknow about \"refbackend\", or which know about \"refbackend\"\nbut not about the \"db\" backend, will refuse to run. This is\nannoying, of course, but much better than the alternative of\nclaiming that there are no refs in the repository, or\nwriting to a location that other implementations will not\nread.\n\nNote that we are only defining the rules for format 1 here.\nWe do not ever write format 1 ourselves; it is a tool that\nis meant to be used by users and future extensions to\nprovide safety with older implementations.\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\n Documentation/technical/repository-version.txt | 81 ++++++++++++++++++++++++++\n cache.h                                        |  6 ++\n setup.c                                        | 37 +++++++++++-\n t/t1302-repo-version.sh                        | 38 ++++++++++++\n 4 files changed, 159 insertions(+), 3 deletions(-)\n create mode 100644 Documentation/technical/repository-version.txt\n\ndiff --git a/Documentation/technical/repository-version.txt b/Documentation/technical/repository-version.txt\nnew file mode 100644\nindex 0000000..3d7106d\n--- /dev/null\n+++ b/Documentation/technical/repository-version.txt\n@@ -0,0 +1,81 @@\n+Git Repository Format Versions\n+==============================\n+\n+Every git repository is marked with a numeric version in the\n+`core.repositoryformatversion` key of its `config` file. This version\n+specifies the rules for operating on the on-disk repository data. An\n+implementation of git which does not understand a particular version\n+advertised by an on-disk repository MUST NOT operate on that repository;\n+doing so risks not only producing wrong results, but actually losing\n+data.\n+\n+Because of this rule, version bumps should be kept to an absolute\n+minimum. Instead, we generally prefer these strategies:\n+\n+  - bumping format version numbers of individual data files (e.g.,\n+    index, packfiles, etc). This restricts the incompatibilities only to\n+    those files.\n+\n+  - introducing new data that gracefully degrades when used by older\n+    clients (e.g., pack bitmap files are ignored by older clients, which\n+    simply do not take advantage of the optimization they provide).\n+\n+A whole-repository format version bump should only be part of a change\n+that cannot be independently versioned. For instance, if one were to\n+change the reachability rules for objects, or the rules for locking\n+refs, that would require a bump of the repository format version.\n+\n+Note that this applies only to accessing the repository's disk contents\n+directly. An older client which understands only format `0` may still\n+connect via `git://` to a repository using format `1`, as long as the\n+server process understands format `1`.\n+\n+The preferred strategy for rolling out a version bump (whether whole\n+repository or for a single file) is to teach git to read the new format,\n+and allow writing the new format with a config switch or command line\n+option (for experimentation or for those who do not care about backwards\n+compatibility with older gits). Then after a long period to allow the\n+reading capability to become common, we may switch to writing the new\n+format by default.\n+\n+The currently defined format versions are:\n+\n+Version `0`\n+-----------\n+\n+This is the format defined by the initial version of git, including but\n+not limited to the format of the repository directory, the repository\n+configuration file, and the object and ref storage. Specifying the\n+complete behavior of git is beyond the scope of this document.\n+\n+Version `1`\n+-----------\n+\n+This format is identical to version `0`, with the following exceptions:\n+\n+  1. When reading the `core.repositoryformatversion` variable, a git\n+     implementation which supports version 1 MUST also read any\n+     configuration keys found in the `extensions` section of the\n+     configuration file.\n+\n+  2. If a version-1 repository specifies any `extensions.*` keys that\n+     the running git has not implemented, the operation MUST NOT\n+     proceed. Similarly, if the value of any known key is not understood\n+     by the implementation, the operation MUST NOT proceed.\n+\n+Note that if no extensions are specified in the config file, then\n+`core.repositoryformatversion` SHOULD be set to `0` (setting it to `1`\n+provides no benefit, and makes the repository incompatible with older\n+implementations of git).\n+\n+This document will serve as the master list for extensions. Any\n+implementation wishing to define a new extension should make a note of\n+it here, in order to claim the name.\n+\n+The defined extensions are:\n+\n+`noop`\n+~~~~~~\n+\n+This extension does not change git's behavior at all. It is useful only\n+for testing format-1 compatibility.\ndiff --git a/cache.h b/cache.h\nindex 692cfd3..edfe7fa 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -699,7 +699,13 @@ extern const char *refs_backend_type;\n \n extern int grafts_replace_parents;\n \n+/*\n+ * GIT_REPO_VERSION is the version we write by default. The\n+ * _READ variant is the highest number we know how to\n+ * handle.\n+ */\n #define GIT_REPO_VERSION 0\n+#define GIT_REPO_VERSION_READ 1\n extern int repository_format_version;\n extern int check_repository_format(void);\n \ndiff --git a/setup.c b/setup.c\nindex b264471..7433eed 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -5,6 +5,7 @@\n static int inside_git_dir = -1;\n static int inside_work_tree = -1;\n static int work_tree_config_is_bogus;\n+static struct string_list unknown_extensions = STRING_LIST_INIT_DUP;\n \n /*\n  * The input parameter must contain an absolute path, and it must already be\n@@ -356,10 +357,23 @@ void setup_work_tree(void)\n \n static int check_repo_format(const char *var, const char *value, void *cb)\n {\n+\tconst char *ext;\n+\n \tif (strcmp(var, \"core.repositoryformatversion\") == 0)\n \t\trepository_format_version = git_config_int(var, value);\n \telse if (strcmp(var, \"core.sharedrepository\") == 0)\n \t\tshared_repository = git_config_perm(var, value);\n+\telse if (skip_prefix(var, \"extensions.\", &ext)) {\n+\t\t/*\n+\t\t * record any known extensions here; otherwise,\n+\t\t * we fall through to recording it as unknown, and\n+\t\t * check_repository_format will complain\n+\t\t */\n+\t\tif (!strcmp(ext, \"noop\"))\n+\t\t\t;\n+\t\telse\n+\t\t\tstring_list_append(&unknown_extensions, ext);\n+\t}\n \treturn 0;\n }\n \n@@ -370,6 +384,8 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok)\n \tconfig_fn_t fn;\n \tint ret = 0;\n \n+\tstring_list_clear(&unknown_extensions, 0);\n+\n \tif (get_common_dir(&sb, gitdir))\n \t\tfn = check_repo_format;\n \telse\n@@ -387,16 +403,31 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok)\n \t * is a good one.\n \t */\n \tgit_config_early(fn, NULL, repo_config);\n-\tif (GIT_REPO_VERSION < repository_format_version) {\n+\tif (GIT_REPO_VERSION_READ < repository_format_version) {\n \t\tif (!nongit_ok)\n \t\t\tdie (\"Expected git repo version <= %d, found %d\",\n-\t\t\t     GIT_REPO_VERSION, repository_format_version);\n+\t\t\t     GIT_REPO_VERSION_READ, repository_format_version);\n \t\twarning(\"Expected git repo version <= %d, found %d\",\n-\t\t\tGIT_REPO_VERSION, repository_format_version);\n+\t\t\tGIT_REPO_VERSION_READ, repository_format_version);\n \t\twarning(\"Please upgrade Git\");\n \t\t*nongit_ok = -1;\n \t\tret = -1;\n \t}\n+\n+\tif (repository_format_version >= 1 && unknown_extensions.nr) {\n+\t\tint i;\n+\n+\t\tif (!nongit_ok)\n+\t\t\tdie(\"unknown repository extension: %s\",\n+\t\t\t    unknown_extensions.items[0].string);\n+\n+\t\tfor (i = 0; i < unknown_extensions.nr; i++)\n+\t\t\twarning(\"unknown repository extension: %s\",\n+\t\t\t\tunknown_extensions.items[i].string);\n+\t\t*nongit_ok = -1;\n+\t\tret = -1;\n+\t}\n+\n \tstrbuf_release(&sb);\n \treturn ret;\n }\ndiff --git a/t/t1302-repo-version.sh b/t/t1302-repo-version.sh\nindex 0d9388a..8dd6fd7 100755\n--- a/t/t1302-repo-version.sh\n+++ b/t/t1302-repo-version.sh\n@@ -67,4 +67,42 @@ test_expect_success 'gitdir required mode' '\n \t)\n '\n \n+check_allow () {\n+\tgit rev-parse --git-dir >actual &&\n+\techo .git >expect &&\n+\ttest_cmp expect actual\n+}\n+\n+check_abort () {\n+\ttest_must_fail git rev-parse --git-dir\n+}\n+\n+# avoid git-config, since it cannot be trusted to run\n+# in a repository with a broken version\n+mkconfig () {\n+\techo '[core]' &&\n+\techo \"repositoryformatversion = $1\" &&\n+\tshift &&\n+\n+\tif test $# -gt 0; then\n+\t\techo '[extensions]' &&\n+\t\tfor i in \"$@\"; do\n+\t\t\techo \"$i\"\n+\t\tdone\n+\tfi\n+}\n+\n+while read outcome version extensions; do\n+\ttest_expect_success \"$outcome version=$version $extensions\" \"\n+\t\tmkconfig $version $extensions >.git/config &&\n+\t\tcheck_${outcome}\n+\t\"\n+done <<\\EOF\n+allow 0\n+allow 1\n+allow 1 noop\n+abort 1 no-such-extension\n+allow 0 no-such-extension\n+EOF\n+\n test_done\n-- \n2.4.2.644.g97b850b-twtrsrc\n"},{"id":"271517","messageId":"1444686725-27660-45-git-send-email-dturner@twopensource.com","threadId":"40543","inReplyTo":"1444686725-27660-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 43/44] refs: add LMDB refs backend","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-10-12T21:52:04Z","receivedAt":"2015-10-12T21:52:04Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"Add a database backend for refs using LMDB.  This backend runs git\nfor-each-ref about 30% faster than the files backend with fully-packed\nrefs on a repo with ~120k refs.  It's also about 4x faster than using\nfully-unpacked refs.  In addition, and perhaps more importantly , it\navoids case-conflict issues on OS X.\n\nLMDB has a few features that make it suitable for usage in git:\n\n1. It is relatively lightweight; it requires only one header file, and\nthe library code takes under 64k at runtime.\n\n2. It is well-tested: it's been used in OpenLDAP for years.\n\n3. It's very fast.  LMDB's benchmarks show that it is among\nthe fastest key-value stores.\n\n4. It has a relatively simple concurrency story; readers don't\nblock writers and writers don't block readers.\n\nRonnie Sahlberg's original version of this patchset used tdb.  The\nmajor disadvantage of tdb is that tdb is hard to build on OS X.  It's\nalso not in homebrew.  So lmdb seemed simpler.\n\nTo test this backend's correctness, I hacked test-lib.sh and\ntest-lib-functions.sh to run all tests under the refs backend. Dozens\nof tests use manual ref/reflog reading/writing, or create submodules\nwithout passing --refs-backend-type to git init.  If those tests are\nchanged to use the update-ref machinery or test-refs-be-db (or, in the\ncase of packed-refs, corrupt refs, and dumb fetch tests, are skipped),\nthe only remaining failing tests are the git-new-workdir tests and the\ngitweb tests.\n\nSigned-off-by: David Turner <dturner@twopensource.com>\n---\n .gitignore                                     |    1 +\n Documentation/config.txt                       |    7 +\n Documentation/git-clone.txt                    |    2 +-\n Documentation/git-init-db.txt                  |    2 +-\n Documentation/git-init.txt                     |    6 +\n Documentation/technical/refs-be-lmdb.txt       |   50 +\n Documentation/technical/repository-version.txt |    5 +\n Makefile                                       |   12 +\n builtin/clone.c                                |    2 +-\n builtin/init-db.c                              |   20 +-\n config.c                                       |   34 +\n configure.ac                                   |   33 +\n contrib/workdir/git-new-workdir                |    3 +\n environment.c                                  |    1 +\n refs-be-lmdb.c                                 | 1997 ++++++++++++++++++++++++\n refs.h                                         |   10 +\n setup.c                                        |   32 +-\n test-refs-be-lmdb.c                            |   68 +\n 18 files changed, 2275 insertions(+), 10 deletions(-)\n create mode 100644 Documentation/technical/refs-be-lmdb.txt\n create mode 100644 refs-be-lmdb.c\n create mode 100644 test-refs-be-lmdb.c\n\ndiff --git a/.gitignore b/.gitignore\nindex 1c2f832..7decc2f 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -199,6 +199,7 @@\n /test-path-utils\n /test-prio-queue\n /test-read-cache\n+/test-refs-be-lmdb\n /test-regex\n /test-revision-walking\n /test-run-command\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex ab57276..eccfdb6 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -496,6 +496,13 @@ core.repositoryFormatVersion::\n \tInternal variable identifying the repository format and layout\n \tversion.\n \n+core.refsBackendType::\n+\tType of refs backend. Default is to use the original files\n+\tbased backend. Set to 'lmdb' to activate the lmdb database\n+\tbackend.  If you use the lmdb backend,\n+\tcore.repositoryFormatVersion must be set to 1, and\n+\textensions.refBackend must be set to 'lmdb'.\n+\n core.sharedRepository::\n \tWhen 'group' (or 'true'), the repository is made shareable between\n \tseveral users in a group (making sure all the files and objects are\ndiff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt\nindex d7a4cb0..6a4cf28 100644\n--- a/Documentation/git-clone.txt\n+++ b/Documentation/git-clone.txt\n@@ -219,7 +219,7 @@ objects from the source repository into a pack in the cloned repository.\n \n --refs-backend-type=<name>::\n \tType of refs backend. Default is to use the original files based\n-\tbackend.\n+\tbackend. Set to \"lmdb\" to activate the lmdb database backend.\n \n <repository>::\n \tThe (possibly remote) repository to clone from.  See the\ndiff --git a/Documentation/git-init-db.txt b/Documentation/git-init-db.txt\nindex 648a6cd..72fbd71 100644\n--- a/Documentation/git-init-db.txt\n+++ b/Documentation/git-init-db.txt\n@@ -9,7 +9,7 @@ git-init-db - Creates an empty Git repository\n SYNOPSIS\n --------\n [verse]\n-'git init-db' [-q | --quiet] [--bare] [--template=<template_directory>] [--separate-git-dir <git dir>] [--shared[=<permissions>]]\n+'git init-db' [-q | --quiet] [--bare] [--template=<template_directory>] [--separate-git-dir <git dir>] [--shared[=<permissions>]] [--refs-backend-type=<name>]\n \n \n DESCRIPTION\ndiff --git a/Documentation/git-init.txt b/Documentation/git-init.txt\nindex 8174d27..d352788 100644\n--- a/Documentation/git-init.txt\n+++ b/Documentation/git-init.txt\n@@ -12,6 +12,7 @@ SYNOPSIS\n 'git init' [-q | --quiet] [--bare] [--template=<template_directory>]\n \t  [--separate-git-dir <git dir>]\n \t  [--shared[=<permissions>]] [directory]\n+\t  [--refs-backend-type=<name>]\n \n \n DESCRIPTION\n@@ -113,6 +114,11 @@ does not exist, it will be created.\n \n --\n \n+--refs-backend-type=<name>::\n+Type of refs backend. Default is to use the original \"files\" backend,\n+which stores ref data in files in .git/refs and .git/packed-refs.  Set\n+to \"lmdb\" to activate the lmdb database backend.\n+\n TEMPLATE DIRECTORY\n ------------------\n \ndiff --git a/Documentation/technical/refs-be-lmdb.txt b/Documentation/technical/refs-be-lmdb.txt\nnew file mode 100644\nindex 0000000..c497ffc\n--- /dev/null\n+++ b/Documentation/technical/refs-be-lmdb.txt\n@@ -0,0 +1,50 @@\n+Notes on the LMDB refs backend\n+==============================\n+\n+Design:\n+------\n+\n+Refs and reflogs are stored in a lmdb database in .git/refdb.  All\n+keys and values are \\0-terminated.\n+\n+Keys for refs are the name of the ref (e.g. refs/heads/master).\n+Values are the value of the ref, in hex\n+(e.g. 61f23eb0f81357c19fa91e2b8c6f3906c3a8f9b0).\n+\n+All per-worktree refs (refs/bisect/* and HEAD) are store using\n+the traditional files-based backend.\n+\n+Reflogs are stored as a series of database entries.\n+\n+For non-empty reflogs, there is one entry per logged ref\n+update.  The key format is logs/[refname]\\0[timestamp].  The timestamp\n+is a 64-bit unsigned integer number of nanoseconds since 1/1/1970.\n+This means that reflog entries are chronologically ordered.  Because\n+LMDB is a btree database, we can efficiently iterate over these keys.\n+\n+For an empty reflog, there is a \"header\" entry to show that a reflog\n+exists.  The header has the same format as an ordinary reflog, but with\n+a timeztamp of all zeros and an empty value.\n+\n+Reflog values are in the same format as the original files-based\n+reflog.\n+\n+Weaknesses:\n+-----------\n+\n+The reflog format is somewhat inefficient: a binary format could store\n+reflog date/time information in somewhat less space.\n+\n+The rsync and file:// transports don't work yet, because they\n+don't use the refs API.\n+\n+git new-workdir is incompatible with the lmdb backend.  Fortunately,\n+git new-workdir is deprecated, and worktrees work fine.\n+\n+LMDB locks the entire database for write.  Any other writer waits\n+until the first writer is done before beginning.  Readers do not wait\n+for writers, and writers do not wait for readers.  The underlying\n+scheme is approximately MVCC; each reader's queries see the state of\n+the database as-of the time that the reader acquired its read lock.\n+This is not too far off from the files backend, which loads all refs\n+into memory when one is requested.\ndiff --git a/Documentation/technical/repository-version.txt b/Documentation/technical/repository-version.txt\nindex 3d7106d..04d457d 100644\n--- a/Documentation/technical/repository-version.txt\n+++ b/Documentation/technical/repository-version.txt\n@@ -79,3 +79,8 @@ The defined extensions are:\n \n This extension does not change git's behavior at all. It is useful only\n for testing format-1 compatibility.\n+\n+`refbackend`\n+\n+This extension allows the user of alternate ref backends.  The only\n+defined value is `lmdb`.\ndiff --git a/Makefile b/Makefile\nindex 43ceab0..e96f84b 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1036,6 +1036,17 @@ ifdef USE_LIBPCRE\n \tEXTLIBS += -lpcre\n endif\n \n+ifdef USE_LIBLMDB\n+\tBASIC_CFLAGS += -DUSE_LIBLMDB\n+\tifdef LIBLMDBDIR\n+\t\tBASIC_CFLAGS += -I$(LIBLMDBDIR)/include\n+\t\tEXTLIBS += -L$(LIBLMDBDIR)/$(lib) $(CC_LD_DYNPATH)$(LIBLMDBDIR)/$(lib)\n+\tendif\n+\tEXTLIBS += -llmdb\n+\tLIB_OBJS += refs-be-lmdb.o\n+\tTEST_PROGRAMS_NEED_X += test-refs-be-lmdb\n+endif\n+\n ifdef HAVE_ALLOCA_H\n \tBASIC_CFLAGS += -DHAVE_ALLOCA_H\n endif\n@@ -2122,6 +2133,7 @@ GIT-BUILD-OPTIONS: FORCE\n \t@echo NO_CURL=\\''$(subst ','\\'',$(subst ','\\'',$(NO_CURL)))'\\' >>$@+\n \t@echo NO_EXPAT=\\''$(subst ','\\'',$(subst ','\\'',$(NO_EXPAT)))'\\' >>$@+\n \t@echo USE_LIBPCRE=\\''$(subst ','\\'',$(subst ','\\'',$(USE_LIBPCRE)))'\\' >>$@+\n+\t@echo USE_LIBLMDB=\\''$(subst ','\\'',$(subst ','\\'',$(USE_LIBLMDB)))'\\' >>$@+\n \t@echo NO_PERL=\\''$(subst ','\\'',$(subst ','\\'',$(NO_PERL)))'\\' >>$@+\n \t@echo NO_PYTHON=\\''$(subst ','\\'',$(subst ','\\'',$(NO_PYTHON)))'\\' >>$@+\n \t@echo NO_UNIX_SOCKETS=\\''$(subst ','\\'',$(subst ','\\'',$(NO_UNIX_SOCKETS)))'\\' >>$@+\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex d489a87..c0f482c 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -764,7 +764,7 @@ static void write_config(struct string_list *config)\n \t}\n \n \tif (refs_backend_type &&\n-\t    write_one_config(\"core.refs-backend-type\",\n+\t    write_one_config(\"core.refsBackendType\",\n \t\t\t     refs_backend_type, NULL) < 0)\n \t\t\tdie(\"unable to write backend parameter to config file\");\n }\ndiff --git a/builtin/init-db.c b/builtin/init-db.c\nindex 2705739..0483e2d 100644\n--- a/builtin/init-db.c\n+++ b/builtin/init-db.c\n@@ -179,6 +179,7 @@ static int create_default_files(const char *template_path)\n \tint reinit;\n \tint filemode;\n \tstruct strbuf err = STRBUF_INIT;\n+\tint repo_version = 0;\n \n \t/* Just look for `init.templatedir` */\n \tgit_config(git_init_db_config, NULL);\n@@ -204,6 +205,21 @@ static int create_default_files(const char *template_path)\n \t\tadjust_shared_perm(get_git_dir());\n \t}\n \n+\tif (refs_backend_type) {\n+\t\tstruct refdb_config_data config_data = {NULL};\n+\t\tgit_config_set(\"core.refsBackendType\", refs_backend_type);\n+\t\tconfig_data.refs_backend_type = refs_backend_type;\n+\t\tconfig_data.refs_base = get_git_dir();\n+#ifdef USE_LIBLMDB\n+\t\tregister_refs_backend(&refs_be_lmdb);\n+#endif\n+\t\tset_refs_backend(refs_backend_type, &config_data);\n+\t\tif (!strcmp(refs_backend_type, \"lmdb\")) {\n+\t\t\tgit_config_set(\"extensions.refbackend\", \"lmdb\");\n+\t\t\trepo_version = 1;\n+\t\t}\n+\t}\n+\n \tif (refs_initdb(&err, shared_repository))\n \t\tdie(\"failed to set up refs db: %s\", err.buf);\n \n@@ -221,7 +237,7 @@ static int create_default_files(const char *template_path)\n \n \t/* This forces creation of new config file */\n \txsnprintf(repo_version_string, sizeof(repo_version_string),\n-\t\t  \"%d\", GIT_REPO_VERSION);\n+\t\t  \"%d\", repo_version);\n \tgit_config_set(\"core.repositoryformatversion\", repo_version_string);\n \n \t/* Check filemode trustability */\n@@ -469,6 +485,8 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)\n \t\tOPT_BIT('q', \"quiet\", &flags, N_(\"be quiet\"), INIT_DB_QUIET),\n \t\tOPT_STRING(0, \"separate-git-dir\", &real_git_dir, N_(\"gitdir\"),\n \t\t\t   N_(\"separate git dir from working tree\")),\n+\t\tOPT_STRING(0, \"refs-backend-type\", &refs_backend_type,\n+\t\t\t   N_(\"name\"), N_(\"name of backend type to use\")),\n \t\tOPT_END()\n \t};\n \ndiff --git a/config.c b/config.c\nindex 248a21a..c17c7b9 100644\n--- a/config.c\n+++ b/config.c\n@@ -10,6 +10,7 @@\n #include \"exec_cmd.h\"\n #include \"strbuf.h\"\n #include \"quote.h\"\n+#include \"refs.h\"\n #include \"hashmap.h\"\n #include \"string-list.h\"\n #include \"utf8.h\"\n@@ -1207,6 +1208,39 @@ int git_config_early(config_fn_t fn, void *data, const char *repo_config)\n \t}\n \n \tif (repo_config && !access_or_die(repo_config, R_OK, 0)) {\n+\t\tstruct refdb_config_data refdb_data = {NULL};\n+\t\tchar *repo_config_copy;\n+\n+\t\t/*\n+\t\t * make sure we always read the backend config from the\n+\t\t * core section on startup\n+\t\t */\n+\t\tret += git_config_from_file(refdb_config, repo_config,\n+\t\t\t\t\t    &refdb_data);\n+\n+\t\trepo_config_copy = xstrdup(repo_config);\n+\t\trefdb_data.refs_base = strdup(dirname(repo_config_copy));\n+\t\tfree(repo_config_copy);\n+\n+\t\tif (!refdb_data.refs_backend_type)\n+\t\t\trefdb_data.refs_backend_type = \"\";\n+\n+\t\tif ((!*refdb_data.refs_backend_type) ||\n+\t\t    (!strcmp(refdb_data.refs_backend_type, \"files\"))) {\n+\t\t\t/* default backend, nothing to do */\n+\t\t} else if (!strcmp(refdb_data.refs_backend_type, \"lmdb\")) {\n+\n+#ifdef USE_LIBLMDB\n+\t\t\trefs_backend_type = refdb_data.refs_backend_type;\n+\t\t\tregister_refs_backend(&refs_be_lmdb);\n+\t\t\tset_refs_backend(refs_backend_type, &refdb_data);\n+#else\n+\t\t\tdie(\"Git was not built with USE_LIBLMDB, so the db refs backend is not available\");\n+#endif\n+\t\t} else {\n+\t\t\tdie(\"Unknown ref backend type '%s'\", refdb_data.refs_backend_type);\n+\t\t}\n+\n \t\tret += git_config_from_file(fn, repo_config, data);\n \t\tfound += 1;\n \t}\ndiff --git a/configure.ac b/configure.ac\nindex 63b7244..ea8e93d 100644\n--- a/configure.ac\n+++ b/configure.ac\n@@ -271,6 +271,24 @@ AS_HELP_STRING([],           [ARG can be also prefix for libpcre library and hea\n         dnl it yet.\n \tGIT_CONF_SUBST([LIBPCREDIR])\n     fi)\n+\n+USE_LIBLMDB=YesPlease\n+AC_ARG_WITH(liblmdb,\n+AS_HELP_STRING([--with-liblmdb],[support lmdb (default is YES])\n+AS_HELP_STRING([],           [ARG can be also prefix for liblmdb library and headers]),\n+    if test \"$withval\" = \"no\"; then\n+\tUSE_LIBLMDB=\n+    elif test \"$withval\" = \"yes\"; then\n+\tUSE_LIBLMDB=YesPlease\n+    else\n+\tUSE_LIBLMDB=YesPlease\n+\tLIBLMDBDIR=$withval\n+\tAC_MSG_NOTICE([Setting LIBLMDBDIR to $LIBLMDBDIR])\n+        dnl USE_LIBLMDB can still be modified below, so don't substitute\n+        dnl it yet.\n+\tGIT_CONF_SUBST([LIBLMDBDIR])\n+    fi)\n+\n #\n # Define HAVE_ALLOCA_H if you have working alloca(3) defined in that header.\n AC_FUNC_ALLOCA\n@@ -510,6 +528,21 @@ GIT_CONF_SUBST([USE_LIBPCRE])\n \n fi\n \n+if test -n \"$USE_LIBLMDB\"; then\n+\n+GIT_STASH_FLAGS($LIBLMDBDIR)\n+\n+AC_CHECK_LIB([lmdb], [mdb_env_open],\n+[USE_LIBLMDB=YesPlease],\n+[USE_LIBLMDB=])\n+\n+GIT_UNSTASH_FLAGS($LIBLMDBDIR)\n+\n+GIT_CONF_SUBST([USE_LIBLMDB])\n+\n+fi\n+\n+\n #\n # Define NO_CURL if you do not have libcurl installed.  git-http-pull and\n # git-http-push are not built, and you cannot use http:// and https://\ndiff --git a/contrib/workdir/git-new-workdir b/contrib/workdir/git-new-workdir\nindex 888c34a..66b7ecf 100755\n--- a/contrib/workdir/git-new-workdir\n+++ b/contrib/workdir/git-new-workdir\n@@ -28,6 +28,9 @@ git_dir=$(cd \"$orig_git\" 2>/dev/null &&\n   git rev-parse --git-dir 2>/dev/null) ||\n   die \"Not a git repository: \\\"$orig_git\\\"\"\n \n+\n+test \"$(git config core.refsbackendtype)\" = \"lmdb\" && die \"git-new-workdir is incompatible with the refs lmdb backend\"\n+\n case \"$git_dir\" in\n .git)\n \tgit_dir=\"$orig_git/.git\"\ndiff --git a/environment.c b/environment.c\nindex c5b65f5..6a2f639 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -65,6 +65,7 @@ int merge_log_config = -1;\n int precomposed_unicode = -1; /* see probe_utf8_pathname_composition() */\n struct startup_info *startup_info;\n unsigned long pack_size_limit_cfg;\n+const char *refs_backend_type;\n \n #ifndef PROTECT_HFS_DEFAULT\n #define PROTECT_HFS_DEFAULT 0\ndiff --git a/refs-be-lmdb.c b/refs-be-lmdb.c\nnew file mode 100644\nindex 0000000..3c41206\n--- /dev/null\n+++ b/refs-be-lmdb.c\n@@ -0,0 +1,1997 @@\n+/*\n+ * This file implements a lmdb backend for refs.\n+ *\n+ * The design of this backend relies on lmdb's write lock -- that is, any\n+ * write transaction blocks all other writers.  Thus, as soon as a ref\n+ * transaction is opened, we know that any values we read won't\n+ * change out from under us, and we have a fully-consistent view of the\n+ * database.\n+ *\n+ * We store the content of refs including the trailing \\0 so that\n+ * standard C string functions can handle them.  Just like struct\n+ * strbuf.\n+ */\n+#include \"cache.h\"\n+#include <lmdb.h>\n+#include \"object.h\"\n+#include \"refs.h\"\n+#include \"tag.h\"\n+#include \"lockfile.h\"\n+\n+static struct trace_key db_trace = TRACE_KEY_INIT(LMDB);\n+\n+static MDB_env *env;\n+\n+static char *db_path;\n+\n+static struct ref_be *files_be;\n+\n+struct lmdb_transaction {\n+\tMDB_txn *txn;\n+\tMDB_dbi dbi;\n+\tMDB_cursor *cursor;\n+\tconst char *submodule;\n+\tint flags;\n+};\n+\n+struct lmdb_transaction transaction;\n+\n+static char *get_refdb_path(const char *base)\n+{\n+\tstruct strbuf path_buf = STRBUF_INIT;\n+\tstrbuf_addf(&path_buf, \"%s/refdb\", base);\n+\treturn strbuf_detach(&path_buf, NULL);\n+}\n+\n+static int in_write_transaction(void)\n+{\n+\treturn transaction.txn && !(transaction.flags & MDB_RDONLY);\n+}\n+\n+static void init_env(MDB_env **env, const char *path)\n+{\n+\tint ret;\n+\tif (*env)\n+\t\treturn;\n+\n+\tret = mdb_env_create(env);\n+\tif (ret)\n+\t\tdie(\"mdb_env_create failed: %s\", mdb_strerror(ret));\n+\tret = mdb_env_set_maxreaders(*env, 1000);\n+\tif (ret)\n+\t\tdie(\"BUG: mdb_env_set_maxreaders failed: %s\", mdb_strerror(ret));\n+\tret = mdb_env_set_mapsize(*env, (1<<30));\n+\tif (ret)\n+\t\tdie(\"BUG: mdb_set_mapsize failed: %s\", mdb_strerror(ret));\n+\tret = mdb_env_open(*env, path, 0 , 0664);\n+\tif (ret)\n+\t\tdie(\"BUG: mdb_env_open (%s) failed: %s\", path,\n+\t\t    mdb_strerror(ret));\n+}\n+\n+static int lmdb_initdb(struct strbuf *err, int shared)\n+{\n+\t/*\n+\t * To create a db, all we need to do is make a directory for\n+\t * it to live in; lmdb will do the rest.\n+\t */\n+\n+\tassert(db_path);\n+\tif (mkdir(db_path, 0775) && errno != EEXIST) {\n+\t\tstrbuf_addf(err, \"%s\", strerror(errno));\n+\t\treturn -1;\n+\t}\n+\treturn files_be->initdb(err, shared);\n+}\n+\n+static void lmdb_init_backend(void *cbdata)\n+{\n+\tstruct refdb_config_data *data = (struct refdb_config_data *)cbdata;\n+\n+\tif (db_path)\n+\t\treturn;\n+\n+\tdb_path = xstrdup(real_path(get_refdb_path(data->refs_base)));\n+\n+\tfiles_be = &refs_be_files;\n+\tfiles_be->init_backend(NULL);\n+\ttrace_printf_key(&db_trace, \"Init backend\\n\");\n+}\n+\n+static void mdb_cursor_open_or_die(struct lmdb_transaction *transaction,\n+\t\t\t\t   MDB_cursor **cursor)\n+{\n+\tint ret = mdb_cursor_open(transaction->txn, transaction->dbi, cursor);\n+\tif (ret)\n+\t\tdie(\"mdb_cursor_open failed: %s\", mdb_strerror(ret));\n+}\n+\n+static void submodule_path(struct strbuf *sb, const char *submodule,\n+\t\t\t   const char *refname)\n+{\n+\tif (submodule)\n+\t\tstrbuf_git_path_submodule(sb, submodule, \"%s\", refname);\n+\telse\n+\t\tstrbuf_git_path(sb, \"%s\", refname);\n+}\n+\n+static int read_per_worktree_ref(const char *submodule, const char *refname,\n+\t\t\t\t struct MDB_val *val)\n+{\n+\tstatic struct strbuf sb = STRBUF_INIT;\n+\tstatic struct strbuf path = STRBUF_INIT;\n+\n+\tsubmodule_path(&path, submodule, refname);\n+\n+\tif (strbuf_read_file(&sb, path.buf, 200) < 0) {\n+\t\tstrbuf_release(&sb);\n+\t\tstrbuf_release(&path);\n+\t\tif (errno == ENOENT)\n+\t\t\treturn MDB_NOTFOUND;\n+\t\treturn -1;\n+\t}\n+\tstrbuf_setlen(&sb, sb.len - 1);\n+\n+\tval->mv_data = sb.buf;\n+\tval->mv_size = sb.len + 1;\n+\n+\tstrbuf_detach(&sb, NULL);\n+\tstrbuf_release(&path);\n+\treturn 0;\n+}\n+\n+static void write_per_worktree_ref(const char *submodule, const char *refname,\n+\t\t\t\t   MDB_val *val)\n+{\n+\tstatic struct lock_file lock;\n+\tint fd;\n+\tint len = val->mv_size - 1;\n+\tstruct strbuf path = STRBUF_INIT;\n+\n+\tsubmodule_path(&path, submodule, refname);\n+\tsafe_create_leading_directories(path.buf);\n+\n+\tfd = hold_lock_file_for_update(&lock, path.buf, LOCK_DIE_ON_ERROR);\n+\tstrbuf_release(&path);\n+\n+\tif (write_in_full(fd, val->mv_data, len) != len ||\n+\t    write_in_full(fd, \"\\n\", 1) != 1)\n+\t\tdie_errno(\"failed to write new HEAD\");\n+\n+\tif (commit_lock_file(&lock))\n+\t\tdie_errno(\"failed to write new HEAD\");\n+}\n+\n+static int del_per_worktree_ref(const char *submodule, const char *refname,\n+\t\t\t\tMDB_val *val)\n+{\n+\tstruct strbuf path = STRBUF_INIT;\n+\tint result;\n+\n+\t/*\n+\t * Returning deleted ref data is not yet implemented, but no\n+\t * callers need it.\n+\t */\n+\tassert(val == NULL);\n+\n+\tsubmodule_path(&path, submodule, refname);\n+\n+\tresult = unlink(path.buf);\n+\tstrbuf_release(&path);\n+\tif (result && errno != ENOENT)\n+\t\treturn 1;\n+\n+\treturn 0;\n+}\n+\n+static int mdb_get_or_die(struct lmdb_transaction *transaction, MDB_val *key,\n+\t\t\t  MDB_val *val)\n+{\n+\tint ret;\n+\n+\tif (ref_type(key->mv_data) != REF_TYPE_NORMAL)\n+\t\treturn read_per_worktree_ref(transaction->submodule,\n+\t\t\t\t\t     key->mv_data, val);\n+\n+\tret = mdb_get(transaction->txn, transaction->dbi, key, val);\n+\tif (ret) {\n+\t\tif (ret != MDB_NOTFOUND)\n+\t\t\tdie(\"mdb_get failed: %s\", mdb_strerror(ret));\n+\t\treturn ret;\n+\t}\n+\treturn 0;\n+}\n+\n+static int mdb_del_or_die(struct lmdb_transaction *transaction, MDB_val *key,\n+\t\t\t  MDB_val *val)\n+{\n+\tint ret;\n+\n+\tif (ref_type(key->mv_data) != REF_TYPE_NORMAL)\n+\t\tdie(\"BUG: this backend should only try to delete normal refs\");\n+\n+\tret = mdb_del(transaction->txn, transaction->dbi, key, val);\n+\tif (ret) {\n+\t\tif (ret != MDB_NOTFOUND)\n+\t\t\tdie(\"mdb_del failed: %s\", mdb_strerror(ret));\n+\t\treturn ret;\n+\t}\n+\treturn 0;\n+}\n+\n+static void mdb_put_or_die(struct lmdb_transaction *transaction, MDB_val *key,\n+\t\t\t   MDB_val *val, int mode)\n+{\n+\tint ret;\n+\n+\tif (ref_type(key->mv_data) != REF_TYPE_NORMAL)\n+\t\tdie(\"BUG: this backend should only try to write normal refs\");\n+\n+\tret = mdb_put(transaction->txn, transaction->dbi, key, val, mode);\n+\tif (ret) {\n+\t\tif (ret == MDB_BAD_VALSIZE)\n+\t\t\tdie(\"Ref name %s too long (max size is %d)\",\n+\t\t\t    (const char *)key->mv_data,\n+\t\t\t    mdb_env_get_maxkeysize(env));\n+\t\telse\n+\t\t\tdie(\"mdb_put failed: (%s -> %s) %s\",\n+\t\t\t    (const char *)key->mv_data,\n+\t\t\t    (const char *)val->mv_data, mdb_strerror(ret));\n+\t}\n+}\n+\n+static int mdb_cursor_get_or_die(MDB_cursor *cursor, MDB_val *key, MDB_val *val, int mode)\n+{\n+\tint ret;\n+\n+\tret = mdb_cursor_get(cursor, key, val, mode);\n+\tif (ret) {\n+\t\tif (ret != MDB_NOTFOUND)\n+\t\t\tdie(\"mdb_cursor_get failed: %s\", mdb_strerror(ret));\n+\t\treturn ret;\n+\t}\n+\tassert(((char *)val->mv_data)[val->mv_size - 1] == 0);\n+\treturn 0;\n+}\n+\n+static int mdb_cursor_del_or_die(MDB_cursor *cursor, int flags)\n+{\n+\tint ret = mdb_cursor_del(cursor, flags);\n+\tif (ret) {\n+\t\tif (ret != MDB_NOTFOUND)\n+\t\t\tdie(\"mdb_cursor_del failed: %s\", mdb_strerror(ret));\n+\t\treturn ret;\n+\t}\n+\treturn 0;\n+}\n+\n+/*\n+ * Begin a transaction. Because only one transaction per thread is\n+ * permitted, we use a global transaction object.  If a read-write\n+ * transaction is presently already in-progress, and a read-only\n+ * transaction is requested, the read-write transaction will be\n+ * returned instead.  If a read-write transaction is requested and a\n+ * read-only transaction is open, the read-only transaction will be\n+ * closed.\n+ *\n+ * It is a bug to request a read-write transaction during another\n+ * read-write transaction.\n+ *\n+ * As a result, it is unsafe to retain read-only transactions past the\n+ * point where a read-write transaction might be needed.  For\n+ * instance, any call that has callbacks outside this module must\n+ * conclude all of its reads from the database before calling those\n+ * callbacks, or must reacquire the transaction after its callbacks\n+ * are completed.\n+ */\n+int lmdb_transaction_begin_flags(struct strbuf *err, unsigned int flags)\n+{\n+\tint ret;\n+\tMDB_txn *txn;\n+\tstatic size_t last_txnid = 0;\n+\tint force_restart = 0;\n+\tMDB_envinfo stat;\n+\n+\tinit_env(&env, db_path);\n+\n+\t/*\n+\t * Since each transaction sees a consistent view of the db,\n+\t * downstream processes that write the db won't be seen in\n+\t * this transaction.  We can check if the last transaction id\n+\t * has changed since this read transaction was started, and if\n+\t * so, we want to reopen the transaction.\n+\t */\n+\n+\tmdb_env_info(env, &stat);\n+\tif (stat.me_last_txnid != last_txnid) {\n+\t\tforce_restart = 1;\n+\t\tlast_txnid = stat.me_last_txnid;\n+\t}\n+\n+\tif (!transaction.txn) {\n+\t\tret = mdb_txn_begin(env, NULL, flags, &txn);\n+\t\tif (ret) {\n+\t\t\tstrbuf_addf(err, \"mdb_txn_begin failed: %s\",\n+\t\t\t\t    mdb_strerror(ret));\n+\t\t\treturn -1;\n+\t\t}\n+\t\tret = mdb_dbi_open(txn, NULL, 0, &transaction.dbi);\n+\t\tif (ret) {\n+\t\t\tstrbuf_addf(err, \"mdb_txn_open failed: %s\",\n+\t\t\t\t    mdb_strerror(ret));\n+\t\t\treturn -1;\n+\t\t}\n+\t\ttransaction.txn = txn;\n+\t\ttransaction.flags = flags;\n+\t\treturn 0;\n+\t}\n+\n+\tif (transaction.flags == flags && !(flags & MDB_RDONLY))\n+\t\tdie(\"BUG: rw transaction started during another rw txn\");\n+\n+\tif (force_restart || (transaction.flags != flags && transaction.flags & MDB_RDONLY)) {\n+\t\t/*\n+\t\t * RO -> RW, or forced restart due to possible changes\n+\t\t * from downstream processes.\n+\t\t */\n+\t\tmdb_txn_abort(transaction.txn);\n+\t\tret = mdb_txn_begin(env, NULL, flags, &txn);\n+\t\tif (ret) {\n+\t\t\tstrbuf_addf(err, \"restarting txn: mdb_txn_begin failed: %s\",\n+\t\t\t\t    mdb_strerror(ret));\n+\t\t\treturn -1;\n+\t\t}\n+\t\tret = mdb_dbi_open(txn, NULL, 0, &transaction.dbi);\n+\t\tif (ret) {\n+\t\t\tstrbuf_addf(err, \"mdb_txn_open failed: %s\",\n+\t\t\t\t    mdb_strerror(ret));\n+\t\t\treturn -1;\n+\t\t}\n+\t\ttransaction.txn = txn;\n+\t\ttransaction.flags = flags;\n+\t}\n+\t/* RW -> RO just keeps the RW txn */\n+\treturn 0;\n+}\n+\n+static struct lmdb_transaction *lmdb_transaction_begin_flags_or_die(int flags)\n+{\n+\tstruct strbuf err = STRBUF_INIT;\n+\tif (lmdb_transaction_begin_flags(&err, flags))\n+\t\tdie(\"%s\", err.buf);\n+\treturn &transaction;\n+}\n+\n+#define MAXDEPTH 5\n+\n+static const char *parse_ref_data(struct lmdb_transaction *transaction,\n+\t\t\t\t  const char *refname, const char *ref_data,\n+\t\t\t\t  unsigned char *sha1, int resolve_flags,\n+\t\t\t\t  int *flags, int bad_name)\n+{\n+\tint depth = MAXDEPTH;\n+\tconst char *buf;\n+\tstatic struct strbuf refname_buffer = STRBUF_INIT;\n+\tstatic struct strbuf refdata_buffer = STRBUF_INIT;\n+\tMDB_val key, val;\n+\n+\tfor (;;) {\n+\t\tif (--depth < 0)\n+\t\t\treturn NULL;\n+\n+\t\tif (!starts_with(ref_data, \"ref:\")) {\n+\t\t\tif (get_sha1_hex(ref_data, sha1) ||\n+\t\t\t    (ref_data[40] != '\\0' && !isspace(ref_data[40]))) {\n+\t\t\t\tif (flags)\n+\t\t\t\t\t*flags |= REF_ISBROKEN;\n+\t\t\t\terrno = EINVAL;\n+\t\t\t\treturn NULL;\n+\t\t\t}\n+\n+\t\t\tif (bad_name) {\n+\t\t\t\thashclr(sha1);\n+\t\t\t\tif (flags)\n+\t\t\t\t\t*flags |= REF_ISBROKEN;\n+\t\t\t} else if (is_null_sha1(sha1)) {\n+\t\t\t\tif (flags)\n+\t\t\t\t\t*flags |= REF_ISBROKEN;\n+\t\t\t}\n+\t\t\treturn refname;\n+\t\t}\n+\t\tif (flags)\n+\t\t\t*flags |= REF_ISSYMREF;\n+\t\tbuf = ref_data + 4;\n+\t\twhile (isspace(*buf))\n+\t\t\tbuf++;\n+\t\tstrbuf_reset(&refname_buffer);\n+\t\tstrbuf_addstr(&refname_buffer, buf);\n+\t\trefname = refname_buffer.buf;\n+\t\tif (resolve_flags & RESOLVE_REF_NO_RECURSE) {\n+\t\t\thashclr(sha1);\n+\t\t\treturn refname;\n+\t\t}\n+\t\tif (check_refname_format(buf, REFNAME_ALLOW_ONELEVEL)) {\n+\t\t\tif (flags)\n+\t\t\t\t*flags |= REF_ISBROKEN;\n+\n+\t\t\tif (!(resolve_flags & RESOLVE_REF_ALLOW_BAD_NAME) ||\n+\t\t\t    !refname_is_safe(buf)) {\n+\t\t\t\terrno = EINVAL;\n+\t\t\t\treturn NULL;\n+\t\t\t}\n+\t\t\tbad_name = 1;\n+\t\t}\n+\n+\t\tkey.mv_data = (char *)refname;\n+\t\tkey.mv_size = strlen(refname) + 1;\n+\t\tif (mdb_get_or_die(transaction, &key, &val)) {\n+\t\t\thashclr(sha1);\n+\t\t\tif (bad_name) {\n+\t\t\t\tif (flags)\n+\t\t\t\t\t*flags |= REF_ISBROKEN;\n+\t\t\t}\n+\t\t\tif (resolve_flags & RESOLVE_REF_READING)\n+\t\t\t\treturn NULL;\n+\n+\t\t\treturn refname;\n+\t\t}\n+\t\tstrbuf_reset(&refdata_buffer);\n+\t\tstrbuf_add(&refdata_buffer, val.mv_data, val.mv_size);\n+\t\tref_data = refdata_buffer.buf;\n+\t}\n+\treturn refname;\n+}\n+\n+static int verify_refname_available_txn(struct lmdb_transaction *transaction,\n+\t\t\t\t\tconst char *refname,\n+\t\t\t\t\tstruct string_list *extras,\n+\t\t\t\t\tstruct string_list *skip,\n+\t\t\t\t\tstruct strbuf *err)\n+{\n+\tMDB_cursor *cursor;\n+\tMDB_val key;\n+\tMDB_val val;\n+\tint mdb_ret;\n+\tsize_t refname_len;\n+\tchar *search_key;\n+\tconst char *extra_refname;\n+\tint ret = 1;\n+\tsize_t i;\n+\n+\tmdb_cursor_open_or_die(transaction, &cursor);\n+\n+\trefname_len = strlen(refname) + 2;\n+\tkey.mv_size = refname_len;\n+\tsearch_key = xmalloc(refname_len);\n+\tmemcpy(search_key, refname, refname_len - 2);\n+\tsearch_key[refname_len - 2] = '/';\n+\tsearch_key[refname_len - 1] = 0;\n+\tkey.mv_data = search_key;\n+\n+\t/* Check for subdirs of refname: we start at refname/ */\n+\tmdb_ret = mdb_cursor_get_or_die(cursor, &key, &val, MDB_SET_RANGE);\n+\n+\twhile (!mdb_ret) {\n+\t\tif (starts_with(key.mv_data, refname) &&\n+\t\t    ((char*)key.mv_data)[refname_len - 2] == '/') {\n+\t\t\tif (skip && string_list_has_string(skip, key.mv_data))\n+\t\t\t\tgoto next;\n+\n+\t\t\tstrbuf_addf(err, \"'%s' exists; cannot create '%s'\", (char *)key.mv_data, refname);\n+\t\t\tgoto done;\n+\t\t}\n+\t\tbreak;\n+\tnext:\n+\t\tmdb_ret = mdb_cursor_get_or_die(cursor, &key, &val, MDB_NEXT);\n+\t}\n+\n+\t/* Check for parent dirs of refname. */\n+\tfor (i = 0; i < refname_len - 2; i++) {\n+\t\tif (search_key[i] == '/') {\n+\t\t\tsearch_key[i] = 0;\n+\t\t\tif (skip && string_list_has_string(skip, search_key)) {\n+\t\t\t\tsearch_key[i] = '/';\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\n+\t\t\tif (extras && string_list_has_string(extras, search_key)) {\n+\t\t\t\tstrbuf_addf(err, \"cannot process '%s' and '%s' at the same time\",\n+\t\t\t\t\t    refname, search_key);\n+\t\t\t\tgoto done;\n+\t\t\t}\n+\n+\t\t\tkey.mv_data = search_key;\n+\t\t\tkey.mv_size = i + 1;\n+\t\t\tif (!mdb_cursor_get_or_die(cursor, &key, &val, MDB_SET)) {\n+\t\t\t\tstrbuf_addf(err, \"'%s' exists; cannot create '%s'\", (char *)key.mv_data, refname);\n+\t\t\t\tgoto done;\n+\t\t\t}\n+\t\t\tsearch_key[i] = '/';\n+\t\t}\n+\t}\n+\n+\textra_refname = find_descendant_ref(refname, extras, skip);\n+\tif (extra_refname) {\n+\t\tstrbuf_addf(err,\n+\t\t\t    \"cannot process '%s' and '%s' at the same time\",\n+\t\t\t    refname, extra_refname);\n+\t\tret = 1;\n+\t} else {\n+\t\tret = 0;\n+\t}\n+done:\n+\tmdb_cursor_close(cursor);\n+\tfree(search_key);\n+\treturn ret;\n+}\n+\n+static const char *resolve_ref_unsafe_txn(struct lmdb_transaction *transaction,\n+\t\t\t\t\t  const char *refname,\n+\t\t\t\t\t  int resolve_flags,\n+\t\t\t\t\t  unsigned char *sha1,\n+\t\t\t\t\t  int *flags)\n+{\n+\tint bad_name = 0;\n+\tchar *ref_data;\n+\tstruct MDB_val key, val;\n+\tstruct strbuf err = STRBUF_INIT;\n+\n+\tval.mv_size = 0;\n+\tval.mv_data = NULL;\n+\n+\tif (flags)\n+\t\t*flags = 0;\n+\n+\tif (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {\n+\t\tif (flags)\n+\t\t\t*flags |= REF_BAD_NAME;\n+\n+\t\tif (!(resolve_flags & RESOLVE_REF_ALLOW_BAD_NAME) ||\n+\t\t    !refname_is_safe(refname)) {\n+\t\t\terrno = EINVAL;\n+\t\t\treturn NULL;\n+\t\t}\n+\t\t/*\n+\t\t * dwim_ref() uses REF_ISBROKEN to distinguish between\n+\t\t * missing refs and refs that were present but invalid,\n+\t\t * to complain about the latter to stderr.\n+\t\t *\n+\t\t * We don't know whether the ref exists, so don't set\n+\t\t * REF_ISBROKEN yet.\n+\t\t */\n+\t\tbad_name = 1;\n+\t}\n+\n+\tkey.mv_data = (void *)refname;\n+\tkey.mv_size = strlen(refname) + 1;\n+\tif (mdb_get_or_die(transaction, &key, &val)) {\n+\t\tif (bad_name) {\n+\t\t\thashclr(sha1);\n+\t\t\tif (flags)\n+\t\t\t\t*flags |= REF_ISBROKEN;\n+\t\t}\n+\n+\t\tif (resolve_flags & RESOLVE_REF_READING)\n+\t\t\treturn NULL;\n+\n+\t\tif (verify_refname_available_txn(transaction, refname, NULL, NULL, &err)) {\n+\t\t\terror(\"%s\", err.buf);\n+\t\t\tstrbuf_release(&err);\n+\t\t\treturn NULL;\n+\t\t}\n+\n+\t\thashclr(sha1);\n+\t\treturn refname;\n+\t}\n+\n+\tref_data = val.mv_data;\n+\tassert(ref_data[val.mv_size - 1] == 0);\n+\n+\treturn parse_ref_data(transaction, refname, ref_data, sha1, resolve_flags,\n+\t\t\t      flags, bad_name);\n+}\n+\n+static const char *lmdb_resolve_ref_unsafe(const char *refname, int resolve_flags,\n+\t\t\t\t\t   unsigned char *sha1, int *flags)\n+{\n+\tlmdb_transaction_begin_flags_or_die(MDB_RDONLY);\n+\treturn resolve_ref_unsafe_txn(&transaction, refname,\n+\t\t\t\t      resolve_flags, sha1, flags);\n+}\n+\n+static void write_u64(char *buf, uint64_t number)\n+{\n+\tint i;\n+\n+\tfor (i = 0; i < 8; i++)\n+\t\tbuf[i] = (number >> (i * 8)) & 0xff;\n+}\n+\n+static int show_one_reflog_ent(struct strbuf *sb, each_reflog_ent_fn fn, void *cb_data)\n+{\n+\tunsigned char osha1[20], nsha1[20];\n+\tchar *email_end, *message;\n+\tunsigned long timestamp;\n+\tint tz;\n+\n+\t/* old (raw) new (raw) name <email> SP time TAB msg LF */\n+\tif (sb->len < 41 || sb->buf[sb->len - 1] != '\\n' ||\n+\t    !(email_end = strchr(sb->buf + 40, '>')) ||\n+\t    email_end[1] != ' ' ||\n+\t    !(timestamp = strtoul(email_end + 2, &message, 10)) ||\n+\t    !message || message[0] != ' ' ||\n+\t    (message[1] != '+' && message[1] != '-') ||\n+\t    !isdigit(message[2]) || !isdigit(message[3]) ||\n+\t    !isdigit(message[4]) || !isdigit(message[5]))\n+\t\treturn 0; /* corrupt? */\n+\n+\thashcpy(osha1, (const unsigned char *)sb->buf);\n+\thashcpy(nsha1, (const unsigned char *)sb->buf + 20);\n+\n+\temail_end[1] = '\\0';\n+\ttz = strtol(message + 1, NULL, 10);\n+\tif (message[6] != '\\t')\n+\t\tmessage += 6;\n+\telse\n+\t\tmessage += 7;\n+\treturn fn(osha1, nsha1, sb->buf + 40, timestamp, tz, message, cb_data);\n+}\n+\n+static void format_reflog_entry(struct strbuf *buf,\n+\t\t\t\tconst unsigned char *old_sha1,\n+\t\t\t\tconst unsigned char *new_sha1,\n+\t\t\t\tconst char *committer, const char *msg)\n+{\n+\tint len;\n+\tint msglen;\n+\n+\tassert(buf->len == 0);\n+\tstrbuf_add(buf, old_sha1, 20);\n+\tstrbuf_add(buf, new_sha1, 20);\n+\tstrbuf_addstr(buf, committer);\n+\tstrbuf_addch(buf, '\\n');\n+\n+\tlen = buf->len;\n+\tmsglen = msg ? strlen(msg) : 0;\n+\tif (msglen) {\n+\t\tint copied;\n+\t\tstrbuf_grow(buf, msglen + 1);\n+\t\tcopied = copy_reflog_msg(buf->buf + 40 + strlen(committer), msg) - 1;\n+\t\tbuf->len = len + copied;\n+\t\tbuf->buf[buf->len] = 0;\n+\t}\n+}\n+\n+static int log_ref_write(const char *refname,\n+\t\t\t const unsigned char *old_sha1,\n+\t\t\t const unsigned char *new_sha1,\n+\t\t\t const char *msg,\n+\t\t\t int flags,\n+\t\t\t struct strbuf *err)\n+{\n+\tMDB_val key, val;\n+\tuint64_t now = getnanotime();\n+\tint result;\n+\tchar *log_key;\n+\tint refname_len;\n+\tMDB_cursor *cursor;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tconst char *timestamp;\n+\n+\tif (log_all_ref_updates < 0)\n+\t\tlog_all_ref_updates = !is_bare_repository();\n+\n+\t/* it is assumed that we are in a ref transaction here */\n+\tassert(transaction.txn);\n+\n+\tresult = safe_create_reflog(refname, flags & REF_FORCE_CREATE_REFLOG, err);\n+\tif (result)\n+\t\treturn result;\n+\n+\t/* \"logs/\" + refname + \\0 + 8-byte timestamp for sorting and expiry. */\n+\trefname_len = strlen(refname);\n+\tkey.mv_size = refname_len + 14;\n+\tlog_key = xcalloc(1, key.mv_size);\n+\tsprintf(log_key, \"logs/%s\", refname);\n+\tkey.mv_data = log_key;\n+\n+\tmdb_cursor_open_or_die(&transaction, &cursor);\n+\n+\t/* check that a reflog exists */\n+\tif (mdb_cursor_get_or_die(cursor, &key, &val, MDB_SET_RANGE) ||\n+\t    strcmp(key.mv_data, log_key)) {\n+\t\tfree(log_key);\n+\t\tmdb_cursor_close(cursor);\n+\t\treturn 0;\n+\t}\n+\n+\t/* Is this a header?  We only need the header for empty reflogs */\n+\ttimestamp = (const char *)key.mv_data + refname_len + 6;\n+\tif (ntohll(*(uint64_t *)timestamp) == 0)\n+\t\tmdb_cursor_del_or_die(cursor, 0);\n+\n+\tkey.mv_data = log_key;\n+\n+\twrite_u64((char *)key.mv_data + refname_len + 6, htonll(now));\n+\n+\tformat_reflog_entry(&buf, old_sha1, new_sha1,\n+\t\t\t    git_committer_info(0), msg);\n+\tassert(buf.len >= 42);\n+\tval.mv_data = buf.buf;\n+\tval.mv_size = buf.len + 1;\n+\n+\tmdb_put_or_die(&transaction, &key, &val, 0);\n+\n+\tstrbuf_release(&buf);\n+\tfree(log_key);\n+\tmdb_cursor_close(cursor);\n+\treturn 0;\n+}\n+\n+static int lmdb_verify_refname_available(const char *refname,\n+\t\t\t\t\t struct string_list *extras,\n+\t\t\t\t\t struct string_list *skip,\n+\t\t\t\t\t struct strbuf *err)\n+{\n+\tlmdb_transaction_begin_flags_or_die(MDB_RDONLY);\n+\treturn verify_refname_available_txn(&transaction, refname, extras, skip, err);\n+}\n+\n+/*\n+ * Attempt to resolve `refname` to `old_sha1` (if old_sha1 is\n+ * non-null).  The return value is a pointer to a newly-allocated\n+ * string containing the next ref name that this resolves to.  So if\n+ * HEAD is a symbolic ref to refs/heads/example, which is itself a\n+ * symbolic ref to refs/heads/foo, return refs/heads/example,\n+ * and fill in resolved_sha1 with the sha of refs/heads/foo.\n+ */\n+static char *check_ref(MDB_txn *txn, const char *refname,\n+\t\t       const unsigned char *old_sha1,\n+\t\t       unsigned char *resolved_sha1, int flags,\n+\t\t       int *type_p)\n+{\n+\tint mustexist = (old_sha1 && !is_null_sha1(old_sha1));\n+\tint resolve_flags = 0;\n+\tint type;\n+\tchar *resolved_refname;\n+\n+\tif (mustexist)\n+\t\tresolve_flags |= RESOLVE_REF_READING;\n+\tif (flags & REF_DELETING) {\n+\t\tresolve_flags |= RESOLVE_REF_ALLOW_BAD_NAME;\n+\t\tif (flags & REF_NODEREF)\n+\t\t\tresolve_flags |= RESOLVE_REF_NO_RECURSE;\n+\t}\n+\n+\t/*\n+\t * The first time we resolve the refname, we're just trying to\n+\t * see if there is any ref at all by this name, even if it is\n+\t * a broken symref.\n+\t */\n+\trefname = resolve_ref_unsafe(refname, resolve_flags,\n+\t\t\t\t     resolved_sha1, &type);\n+\tif (type_p)\n+\t    *type_p = type;\n+\n+\tif (!refname)\n+\t\treturn NULL;\n+\n+\t/*\n+\t * Need to copy refname here because the resolve_ref_unsafe\n+\t * returns a pointer to a static buffer that could get mangled\n+\t * by the second call.\n+\t */\n+\tresolved_refname = strdup(refname);\n+\n+\tif (old_sha1) {\n+\t\tif (flags & REF_NODEREF) {\n+\t\t\tresolve_flags &= ~RESOLVE_REF_NO_RECURSE;\n+\n+\t\t\tresolve_ref_unsafe(refname, resolve_flags,\n+\t\t\t\t\t   resolved_sha1, &type);\n+\t\t}\n+\t\tif (hashcmp(old_sha1, resolved_sha1)) {\n+\t\t\terror(\"ref %s is at %s but expected %s\", refname,\n+\t\t\t      sha1_to_hex(resolved_sha1), sha1_to_hex(old_sha1));\n+\n+\t\t\treturn NULL;\n+\t\t}\n+\t}\n+\treturn resolved_refname;\n+}\n+\n+static int mdb_transaction_commit(struct lmdb_transaction *transaction,\n+\t\t\t\t  struct strbuf *err)\n+{\n+\tint result;\n+\n+\tresult = mdb_txn_commit(transaction->txn);\n+\tif (result && err)\n+\t\tstrbuf_addstr(err, mdb_strerror(result));\n+\n+\ttransaction->txn = NULL;\n+\treturn result;\n+}\n+\n+static int lmdb_delete_reflog(const char *refname)\n+{\n+\tMDB_val key, val;\n+\tchar *log_path;\n+\tint len;\n+\tMDB_cursor *cursor;\n+\tint ret = 0;\n+\tint mdb_ret;\n+\tstruct strbuf err = STRBUF_INIT;\n+\tint in_transaction;\n+\n+\tif (ref_type(refname) != REF_TYPE_NORMAL)\n+\t\treturn files_be->delete_reflog(refname);\n+\n+\tin_transaction = in_write_transaction();\n+\n+\tlen = strlen(refname) + 6;\n+\tlog_path = xmalloc(len);\n+\tsprintf(log_path, \"logs/%s\", refname);\n+\n+\tkey.mv_data = log_path;\n+\tkey.mv_size = len;\n+\n+\tif (!in_transaction)\n+\t\tlmdb_transaction_begin_flags_or_die(0);\n+\n+\tmdb_cursor_open_or_die(&transaction, &cursor);\n+\n+\tmdb_ret = mdb_cursor_get_or_die(cursor, &key, &val, MDB_SET_RANGE);\n+\n+\twhile (!mdb_ret) {\n+\t\tif (key.mv_size < len)\n+\t\t\tbreak;\n+\n+\t\tif (!starts_with(key.mv_data, log_path) || ((char*)key.mv_data)[len - 1] != 0)\n+\t\t\tbreak;\n+\n+\t\tmdb_cursor_del_or_die(cursor, 0);\n+\t\tmdb_ret = mdb_cursor_get_or_die(cursor, &key, &val, MDB_NEXT);\n+\t}\n+\n+\tfree(log_path);\n+\tmdb_cursor_close(cursor);\n+\ttransaction.cursor = NULL;\n+\n+\tif (!in_transaction && mdb_transaction_commit(&transaction, &err)) {\n+\t\twarning(\"%s\", err.buf);\n+\t\tret = 01;\n+\t}\n+\tstrbuf_release(&err);\n+\treturn ret;\n+}\n+\n+#define REF_NO_REFLOG 0x8000\n+\n+static int lmdb_transaction_update(struct lmdb_transaction *transaction,\n+\t\t\t\t   const char *refname,\n+\t\t\t\t   const unsigned char *new_sha1,\n+\t\t\t\t   const unsigned char *old_sha1,\n+\t\t\t\t   unsigned int flags, const char *msg,\n+\t\t\t\t   struct strbuf *err)\n+{\n+\tconst char *orig_refname = refname;\n+\tMDB_val key, val;\n+\tunsigned char resolved_sha1[20];\n+\tint type;\n+\tint ret = -1;\n+\n+\tif ((flags & REF_HAVE_NEW) && is_null_sha1(new_sha1))\n+\t\tflags |= REF_DELETING;\n+\n+\tif (new_sha1 && !is_null_sha1(new_sha1) &&\n+\t    check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {\n+\t\tstrbuf_addf(err, \"refusing to update ref with bad name %s\",\n+\t\t\t    refname);\n+\t\treturn TRANSACTION_GENERIC_ERROR;\n+\t}\n+\n+\trefname = check_ref(transaction->txn, orig_refname, old_sha1,\n+\t\t\t    resolved_sha1, flags, &type);\n+\tif (refname == NULL) {\n+\t\tstrbuf_addf(err, \"cannot lock the ref '%s'\", orig_refname);\n+\t\treturn TRANSACTION_GENERIC_ERROR;\n+\t}\n+\n+\tif (!(flags & REF_DELETING) && is_null_sha1(resolved_sha1) &&\n+\t    verify_refname_available_txn(transaction, refname, NULL, NULL, err))\n+\t\treturn TRANSACTION_NAME_CONFLICT;\n+\n+\tif (flags & REF_NODEREF) {\n+\t\tfree((void *)refname);\n+\t\trefname = orig_refname;\n+\t}\n+\n+\tkey.mv_size = strlen(refname) + 1;\n+\tkey.mv_data = (void *)refname;\n+\n+\tif ((flags & REF_HAVE_NEW) && !is_null_sha1(new_sha1)) {\n+\t\tint overwriting_symref = ((type & REF_ISSYMREF) &&\n+\t\t\t\t\t  (flags & REF_NODEREF));\n+\n+\t\tstruct object *o = parse_object(new_sha1);\n+\t\tif (!o) {\n+\t\t\tstrbuf_addf(err,\n+\t\t\t\t    \"Trying to write ref %s with nonexistent object %s\",\n+\t\t\t\t    refname, sha1_to_hex(new_sha1));\n+\t\t\tgoto done;\n+\t\t}\n+\t\tif (o->type != OBJ_COMMIT && is_branch(refname)) {\n+\t\t\tstrbuf_addf(err,\n+\t\t\t\t    \"Trying to write non-commit object %s to branch %s\",\n+\t\t\t\t    sha1_to_hex(new_sha1), refname);\n+\t\t\tgoto done;\n+\t\t}\n+\n+\t\tif (!overwriting_symref\n+\t\t    && !hashcmp(resolved_sha1, new_sha1)) {\n+\t\t\t/*\n+\t\t\t * The reference already has the desired\n+\t\t\t * value, so we don't need to write it.\n+\t\t\t */\n+\t\t\tflags |= REF_NO_REFLOG;\n+\t\t} else {\n+\t\t\tval.mv_size = 41;\n+\t\t\tif (new_sha1)\n+\t\t\t\tval.mv_data = sha1_to_hex(new_sha1);\n+\t\t\telse\n+\t\t\t\tval.mv_data = sha1_to_hex(null_sha1);\n+\t\t\tmdb_put_or_die(transaction, &key, &val, 0);\n+\t\t}\n+\t}\n+\n+\tif (flags & REF_DELETING) {\n+\t\tif (mdb_del_or_die(transaction, &key, NULL)) {\n+\t\t\tif (old_sha1 && !is_null_sha1(old_sha1)) {\n+\t\t\t\tstrbuf_addf(err, \"No such ref %s\", refname);\n+\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n+\t\t\t\tgoto done;\n+\t\t\t}\n+\t\t}\n+\t\tlmdb_delete_reflog(orig_refname);\n+\t} else if (!(flags & REF_NO_REFLOG)) {\n+\t\tif (!new_sha1)\n+\t\t\tnew_sha1 = null_sha1;\n+\t\tif (log_ref_write(orig_refname, resolved_sha1,\n+\t\t\t\t  new_sha1, msg, flags, err) < 0)\n+\t\t\tgoto done;\n+\t\tif (strcmp (refname, orig_refname) &&\n+\t\t    log_ref_write(refname, resolved_sha1,\n+\t\t\t\t  new_sha1, msg, flags, err) < 0)\n+\t\t\tgoto done;\n+\t}\n+\n+\tret = 0;\n+done:\n+\tif (refname != orig_refname)\n+\t\tfree((void *) refname);\n+\treturn ret;\n+}\n+\n+static int lmdb_transaction_commit(struct ref_transaction *ref_transaction,\n+\t\t\t\t   struct string_list *affected_refnames,\n+\t\t\t\t   struct strbuf *err)\n+{\n+\tint ret = 0, i;\n+\tint n = ref_transaction->nr;\n+\tstruct lmdb_transaction *lmdb_transaction;\n+\tstruct ref_update **updates = ref_transaction->updates;\n+\n+\t/*\n+\t * We might already be in a write transaction, because some\n+\t * lmdb backend functionality is implemented in terms of\n+\t * (other stuff) + ref_transaction_commit\n+\t */\n+\tif (in_write_transaction())\n+\t\tlmdb_transaction = &transaction;\n+\telse\n+\t\tlmdb_transaction = lmdb_transaction_begin_flags_or_die(0);\n+\n+\tfor (i = 0; i < n; i++) {\n+\t\tstruct ref_update *update = updates[i];\n+\n+\t\tif (lmdb_transaction_update(lmdb_transaction,\n+\t\t\t\t\t    update->refname,\n+\t\t\t\t\t    update->new_sha1,\n+\t\t\t\t\t    (update->flags & REF_HAVE_OLD) ?\n+\t\t\t\t\t     update->old_sha1 : NULL,\n+\t\t\t\t\t    update->flags,\n+\t\t\t\t\t    update->msg,\n+\t\t\t\t\t    err)) {\n+\t\t\tmdb_txn_abort(lmdb_transaction->txn);\n+\t\t\tret = -1;\n+\t\t\tgoto cleanup;\n+\t\t}\n+\n+\t}\n+\tret = mdb_transaction_commit(lmdb_transaction, err);\n+\n+cleanup:\n+\tref_transaction->state = REF_TRANSACTION_CLOSED;\n+\treturn ret;\n+}\n+\n+static int rename_reflog_ent(unsigned char *osha1, unsigned char *nsha1,\n+\t\t\t     const char *email, unsigned long timestamp, int tz,\n+\t\t\t     const char *message, void *cb_data)\n+{\n+\n+\tconst char *newrefname = cb_data;\n+\tMDB_val key, new_key, val;\n+\n+\tassert(transaction.cursor);\n+\n+\tif (mdb_cursor_get_or_die(transaction.cursor, &key, &val, MDB_GET_CURRENT))\n+\t\tdie(\"renaming ref: mdb_cursor_get failed to get current\");\n+\n+\tnew_key.mv_size = strlen(newrefname) + 5 + 1 + 8;\n+\tnew_key.mv_data = xmalloc(new_key.mv_size);\n+\tstrcpy(new_key.mv_data, \"logs/\");\n+\tstrcpy((char *)new_key.mv_data + 5, newrefname);\n+\tmemcpy((char *)new_key.mv_data + new_key.mv_size - 8,\n+\t       (const char *)key.mv_data + key.mv_size - 8, 8);\n+\tmdb_put_or_die(&transaction, &new_key, &val, 0);\n+\tmdb_cursor_del_or_die(transaction.cursor, 0);\n+\tfree(new_key.mv_data);\n+\treturn 0;\n+}\n+\n+static int lmdb_rename_ref(const char *oldref, const char *newref, const char *logmsg)\n+{\n+\tunsigned char orig_sha1[20];\n+\tint flag = 0;\n+\tint log = reflog_exists(oldref);\n+\tconst char *symref = NULL;\n+\tstruct strbuf err = STRBUF_INIT;\n+\tstruct ref_transaction *ref_transaction;\n+\n+\tif (!strcmp(oldref, newref))\n+\t\treturn 0;\n+\n+\tlmdb_transaction_begin_flags_or_die(0);\n+\n+\tref_transaction = ref_transaction_begin(&err);\n+\tif (!ref_transaction)\n+\t\tdie(\"%s\", err.buf);\n+\n+\tsymref = resolve_ref_unsafe(oldref, RESOLVE_REF_READING,\n+\t\t\t\t    orig_sha1, &flag);\n+\tif (flag & REF_ISSYMREF) {\n+\t\terror(\"refname %s is a symbolic ref, renaming it is not supported\",\n+\t\t      oldref);\n+\t\tgoto fail;\n+\t}\n+\tif (!symref) {\n+\t\tmdb_txn_abort(transaction.txn);\n+\t\terror(\"refname %s not found\", oldref);\n+\t\tgoto fail;\n+\t}\n+\tif (!rename_ref_available(oldref, newref))\n+\t\tgoto fail;\n+\n+\t/* Copy the reflog from the old to the new */\n+\tif (log) {\n+\t\tstruct strbuf old_log_sentinel = STRBUF_INIT;\n+\t\tMDB_val key;\n+\t\tint log_all;\n+\n+\t\tlog_all = log_all_ref_updates;\n+\t\tlog_all_ref_updates = 1;\n+\t\tif (safe_create_reflog(newref, 0, &err)) {\n+\t\t\terror(\"can't create reflog for %s: %s\", newref, err.buf);\n+\t\t\tstrbuf_release(&err);\n+\t\t\tgoto fail;\n+\t\t}\n+\t\tlog_all_ref_updates = log_all;\n+\n+\t\tfor_each_reflog_ent(oldref, rename_reflog_ent, (void *)newref);\n+\t\tstrbuf_addf(&old_log_sentinel, \"logs/%sxxxxxxxx\", oldref);\n+\t\tmemset(old_log_sentinel.buf + old_log_sentinel.len - 8, 0, 8);\n+\n+\t\tkey.mv_size = old_log_sentinel.len;\n+\t\tkey.mv_data = old_log_sentinel.buf;\n+\n+\t\t/* It's OK if the old reflog is missing */\n+\t\tmdb_del_or_die(&transaction, &key, NULL);\n+\t\tstrbuf_release(&old_log_sentinel);\n+\t}\n+\n+\tif (ref_transaction_delete(ref_transaction, oldref,\n+\t\t\t\t   orig_sha1, REF_NODEREF, NULL, &err)) {\n+\t\terror(\"unable to delete old %s\", oldref);\n+\t\tgoto fail;\n+\t}\n+\n+\tif (ref_transaction_update(ref_transaction, newref, orig_sha1, NULL,\n+\t\t\t\t    0, logmsg, &err)) {\n+\t\terror(\"%s\", err.buf);\n+\t\tgoto fail;\n+\t}\n+\n+\tif (ref_transaction_commit(ref_transaction, &err)) {\n+\t\terror(\"%s\", err.buf);\n+\t\tgoto fail;\n+\t}\n+\n+\treturn 0;\n+\n+fail:\n+\tref_transaction_free(ref_transaction);\n+\tstrbuf_release(&err);\n+\tmdb_txn_abort(transaction.txn);\n+\treturn 1;\n+}\n+\n+static int lmdb_for_each_reflog_ent_order(const char *refname,\n+\t\t\t\t\t  each_reflog_ent_fn fn,\n+\t\t\t\t\t  void *cb_data, int reverse)\n+{\n+\tMDB_val key, val;\n+\tchar *search_key;\n+\tchar *log_path;\n+\tint len;\n+\tMDB_cursor *cursor;\n+\tint ret = 0;\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tenum MDB_cursor_op direction = reverse ? MDB_PREV : MDB_NEXT;\n+\tuint64_t zero = 0ULL;\n+\n+\tlen = strlen(refname) + 6;\n+\tlog_path = xmalloc(len);\n+\tsearch_key = xmalloc(len + 1);\n+\tsprintf(log_path, \"logs/%s\", refname);\n+\tstrcpy(search_key, log_path);\n+\n+\tif (reverse) {\n+\t\t/*\n+\t\t * For a reverse search, start at the key\n+\t\t * lexicographically after the searched-for key.\n+\t\t * That's the one with \\001 appended to the key.\n+\t\t */\n+\n+\t\tsearch_key[len - 1] = 1;\n+\t\tsearch_key[len] = 0;\n+\t\tkey.mv_size = len + 1;\n+\t} else {\n+\t\tkey.mv_size = len;\n+\t}\n+\n+\tkey.mv_data = search_key;\n+\n+\tlmdb_transaction_begin_flags_or_die(MDB_RDONLY);\n+\n+\tmdb_cursor_open_or_die(&transaction, &cursor);\n+\n+\ttransaction.cursor = cursor;\n+\n+\t/*\n+\t * MDB's cursor API requires that the first mdb_cursor_get be\n+\t * called with MDB_SET_RANGE.  For reverse searches, this will\n+\t * give us the entry one-past the entry we're looking for, so\n+\t * we should jump back using MDB_PREV.\n+\t */\n+\tmdb_cursor_get_or_die(cursor, &key, &val, MDB_SET_RANGE);\n+\tif (direction == MDB_PREV)\n+\t\tmdb_cursor_get_or_die(cursor, &key, &val, direction);\n+\n+\tdo {\n+\t\tif (key.mv_size < len)\n+\t\t\tbreak;\n+\n+\t\tif (!starts_with(key.mv_data, log_path) || ((char *)key.mv_data)[len - 1] != 0)\n+\t\t\tbreak;\n+\n+\t\tif (!memcmp(&zero, ((char *)key.mv_data) + key.mv_size - 8, 8))\n+\t\t\tcontinue;\n+\n+\t\tassert(val.mv_size != 0);\n+\n+\t\tstrbuf_add(&sb, val.mv_data, val.mv_size - 1);\n+\t\tret = show_one_reflog_ent(&sb, fn, cb_data);\n+\t\tif (ret)\n+\t\t\tbreak;\n+\n+\t\tstrbuf_reset(&sb);\n+\t} while (!mdb_cursor_get_or_die(cursor, &key, &val, direction));\n+\n+\tstrbuf_release(&sb);\n+\tfree(log_path);\n+\tfree(search_key);\n+\tmdb_cursor_close(cursor);\n+\treturn ret;\n+}\n+\n+static int lmdb_for_each_reflog_ent(const char *refname,\n+\t\t\t\t    each_reflog_ent_fn fn,\n+\t\t\t\t    void *cb_data)\n+{\n+\tif (ref_type(refname) != REF_TYPE_NORMAL)\n+\t\treturn files_be->for_each_reflog_ent(refname, fn, cb_data);\n+\treturn lmdb_for_each_reflog_ent_order(refname, fn, cb_data, 0);\n+}\n+\n+static int lmdb_for_each_reflog_ent_reverse(const char *refname,\n+\t\t\t\t\t    each_reflog_ent_fn fn,\n+\t\t\t\t\t    void *cb_data)\n+{\n+\tif (ref_type(refname) != REF_TYPE_NORMAL)\n+\t\treturn files_be->for_each_reflog_ent_reverse(refname, fn, cb_data);\n+\treturn lmdb_for_each_reflog_ent_order(refname, fn, cb_data, 1);\n+}\n+\n+static int lmdb_reflog_exists(const char *refname)\n+{\n+\tMDB_val key, val;\n+\tchar *log_path;\n+\tint len;\n+\tMDB_cursor *cursor;\n+\tint ret = 1;\n+\n+\tif (ref_type(refname) != REF_TYPE_NORMAL)\n+\t\treturn files_be->reflog_exists(refname);\n+\n+\tlen = strlen(refname) + 6;\n+\tlog_path = xmalloc(len);\n+\tsprintf(log_path, \"logs/%s\", refname);\n+\n+\tkey.mv_data = log_path;\n+\tkey.mv_size = len;\n+\n+\tlmdb_transaction_begin_flags_or_die(MDB_RDONLY);\n+\tmdb_cursor_open_or_die(&transaction, &cursor);\n+\n+\tif (mdb_cursor_get_or_die(cursor, &key, &val, MDB_SET_RANGE) ||\n+\t    !starts_with(key.mv_data, log_path))\n+\t\tret = 0;\n+\n+\tfree(log_path);\n+\tmdb_cursor_close(cursor);\n+\n+\treturn ret;\n+}\n+\n+struct wrapped_each_ref_fn {\n+\teach_ref_fn *fn;\n+\tvoid *cb_data;\n+};\n+\n+static int check_reflog(const char *refname,\n+\t\t\tconst struct object_id *oid, int flags, void *cb_data)\n+{\n+\tstruct wrapped_each_ref_fn *wrapped = cb_data;\n+\n+\tif (reflog_exists(refname))\n+\t\treturn wrapped->fn(refname, oid, 0, wrapped->cb_data);\n+\n+\treturn 0;\n+}\n+\n+static int lmdb_for_each_reflog(each_ref_fn fn, void *cb_data)\n+{\n+\tstruct wrapped_each_ref_fn wrapped = {fn, cb_data};\n+\tint result = head_ref(fn, cb_data);\n+\tif (result)\n+\t\treturn result;\n+\treturn for_each_ref(check_reflog, &wrapped);\n+}\n+\n+static int lmdb_create_reflog(const char *refname, int force_create, struct strbuf *err)\n+{\n+\t/*\n+\t * We mark that there is a reflog by creating a key of the\n+\t * form logs/$refname followed by nine \\0 (one for\n+\t * string-termination, 8 in lieu of a timestamp), with an empty\n+\t * value.\n+\t */\n+\n+\tint in_transaction = in_write_transaction();\n+\tMDB_val key, val;\n+\n+\tif (!force_create && !should_autocreate_reflog(refname))\n+\t\treturn 0;\n+\n+\tif (!in_transaction)\n+\t\tlmdb_transaction_begin_flags_or_die(0);\n+\n+\tkey.mv_size = strlen(refname) + 5 + 1 + 8;\n+\tkey.mv_data = xcalloc(1, key.mv_size);\n+\tsprintf((char *)key.mv_data, \"logs/%s\", refname);\n+\tval.mv_size = 0;\n+\tval.mv_data = NULL;\n+\tmdb_put_or_die(&transaction, &key, &val, 0);\n+\n+\tfree(key.mv_data);\n+\tif (!in_transaction)\n+\t\treturn mdb_transaction_commit(&transaction, err);\n+\treturn 0;\n+}\n+\n+struct expire_reflog_cb {\n+\tunsigned int flags;\n+\treflog_expiry_should_prune_fn *should_prune_fn;\n+\tvoid *policy_cb;\n+\tunsigned char last_kept_sha1[20];\n+};\n+\n+static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1,\n+\t\t\t     const char *email, unsigned long timestamp, int tz,\n+\t\t\t     const char *message, void *cb_data)\n+{\n+\tstruct expire_reflog_cb *cb = cb_data;\n+\tstruct expire_reflog_policy_cb *policy_cb = cb->policy_cb;\n+\n+\tif (cb->flags & EXPIRE_REFLOGS_REWRITE)\n+\t\tosha1 = cb->last_kept_sha1;\n+\n+\tif ((*cb->should_prune_fn)(osha1, nsha1, email, timestamp, tz,\n+\t\t\t\t   message, policy_cb)) {\n+\t\tif (cb->flags & EXPIRE_REFLOGS_DRY_RUN)\n+\t\t\tprintf(\"would prune %s\", message);\n+\t\telse {\n+\t\t\tif (cb->flags & EXPIRE_REFLOGS_VERBOSE)\n+\t\t\t\tprintf(\"prune %s\", message);\n+\n+\t\t\tmdb_cursor_del_or_die(transaction.cursor, 0);\n+\t\t}\n+\t} else {\n+\t\thashcpy(cb->last_kept_sha1, nsha1);\n+\t\tif (cb->flags & EXPIRE_REFLOGS_VERBOSE)\n+\t\t\tprintf(\"keep %s\", message);\n+\t}\n+\treturn 0;\n+}\n+\n+static int write_ref(const char *refname, const unsigned char *sha1)\n+{\n+\tstruct strbuf err = STRBUF_INIT;\n+\tstruct ref_transaction *transaction;\n+\n+\ttransaction = ref_transaction_begin(&err);\n+\tif (!transaction) {\n+\t\terror(\"%s\", err.buf);\n+\t\tstrbuf_release(&err);\n+\t\treturn -1;\n+\t}\n+\n+\tif (ref_transaction_update(transaction, refname, sha1, NULL,\n+\t\t\t\t   REF_NO_REFLOG, NULL, &err)) {\n+\t\terror(\"%s\", err.buf);\n+\t\tstrbuf_release(&err);\n+\t\treturn -1;\n+\t}\n+\n+\tif (ref_transaction_commit(transaction, &err)) {\n+\t\terror(\"%s\", err.buf);\n+\t\tstrbuf_release(&err);\n+\t\treturn -1;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+int lmdb_reflog_expire(const char *refname, const unsigned char *sha1,\n+\t\t       unsigned int flags,\n+\t\t       reflog_expiry_prepare_fn prepare_fn,\n+\t\t       reflog_expiry_should_prune_fn should_prune_fn,\n+\t\t       reflog_expiry_cleanup_fn cleanup_fn,\n+\t\t       void *policy_cb_data)\n+{\n+\tstruct expire_reflog_cb cb;\n+\tint dry_run = flags & EXPIRE_REFLOGS_DRY_RUN;\n+\tint status = 0;\n+\tstruct strbuf err = STRBUF_INIT;\n+\tunsigned char resolved_sha1[20];\n+\tint type;\n+\tchar *resolved;\n+\n+\tif (ref_type(refname) != REF_TYPE_NORMAL)\n+\t\treturn files_be->reflog_expire(refname, sha1, flags, prepare_fn,\n+\t\t\t\t\t       should_prune_fn, cleanup_fn,\n+\t\t\t\t\t       policy_cb_data);\n+\n+\tmemset(&cb, 0, sizeof(cb));\n+\tcb.flags = flags;\n+\tcb.policy_cb = policy_cb_data;\n+\tcb.should_prune_fn = should_prune_fn;\n+\n+\tlmdb_transaction_begin_flags_or_die(dry_run ? MDB_RDONLY : 0);\n+\n+\tresolved = check_ref(transaction.txn, refname, sha1,\n+\t\t\t     resolved_sha1, 0, &type);\n+\tif (!resolved)\n+\t\tdie(\"Failed to resolve %s\", refname);\n+\tfree(resolved);\n+\n+\t(*prepare_fn)(refname, sha1, cb.policy_cb);\n+\tlmdb_for_each_reflog_ent(refname, expire_reflog_ent, &cb);\n+\t(*cleanup_fn)(cb.policy_cb);\n+\n+\tif (!dry_run) {\n+\t\t/*\n+\t\t * It doesn't make sense to adjust a reference pointed\n+\t\t * to by a symbolic ref based on expiring entries in\n+\t\t * the symbolic reference's reflog. Nor can we update\n+\t\t * a reference if there are no remaining reflog\n+\t\t * entries.\n+\t\t */\n+\t\tint update = (flags & EXPIRE_REFLOGS_UPDATE_REF) &&\n+\t\t\t!(type & REF_ISSYMREF) &&\n+\t\t\t!is_null_sha1(cb.last_kept_sha1);\n+\n+\t\tif (mdb_transaction_commit(&transaction, &err)) {\n+\t\t\tstatus |= error(\"couldn't write logs/%s: %s\", refname,\n+\t\t\t\t\terr.buf);\n+\t\t\tstrbuf_release(&err);\n+\t\t} else if (update &&\n+\t\t\t   write_ref(refname, cb.last_kept_sha1)) {\n+\t\t\tstatus |= error(\"couldn't set %s\",\n+\t\t\t\t\trefname);\n+\t\t}\n+\t}\n+\treturn status;\n+}\n+\n+static int lmdb_pack_refs(unsigned int flags)\n+{\n+\t/* This concept does not exist in this backend. */\n+\treturn 0;\n+}\n+\n+static int lmdb_peel_ref(const char *refname, unsigned char *sha1)\n+{\n+\tint flag;\n+\tunsigned char base[20];\n+\n+\tif (read_ref_full(refname, RESOLVE_REF_READING, base, &flag))\n+\t\treturn -1;\n+\n+\treturn peel_object(base, sha1);\n+}\n+\n+static int lmdb_create_symref(const char *ref_target,\n+\t\t\t      const char *refs_heads_master,\n+\t\t\t      const char *logmsg)\n+{\n+\n+\tstruct strbuf err = STRBUF_INIT;\n+\tunsigned char old_sha1[20], new_sha1[20];\n+\tMDB_val key, val;\n+\tchar *valdata;\n+\tint ret = 0;\n+\tint in_transaction;\n+\n+\tin_transaction = in_write_transaction();\n+\n+\tif (logmsg && read_ref(ref_target, old_sha1))\n+\t\thashclr(old_sha1);\n+\n+\tkey.mv_size = strlen(ref_target) + 1;\n+\tkey.mv_data = xstrdup(ref_target);\n+\n+\tval.mv_size = strlen(refs_heads_master) + 1 + 5;\n+\tvaldata = xmalloc(val.mv_size);\n+\tsprintf(valdata, \"ref: %s\", refs_heads_master);\n+\tval.mv_data = valdata;\n+\n+\tif (!in_transaction)\n+\t\tlmdb_transaction_begin_flags_or_die(0);\n+\n+\tmdb_put_or_die(&transaction, &key, &val, 0);\n+\n+\tif (logmsg && !read_ref(refs_heads_master, new_sha1) &&\n+\t    log_ref_write(ref_target, old_sha1, new_sha1, logmsg, 0, &err)) {\n+\t\terror(\"create_symref: log_ref_write failed: %s\", err.buf);\n+\t\tret = -1;\n+\t\tgoto done;\n+\t}\n+\n+\tif (!in_transaction && mdb_transaction_commit(&transaction, &err)) {\n+\t\terror(\"create_symref: commit failed: %s\", err.buf);\n+\t\tret = -1;\n+\t}\n+\n+done:\n+\tstrbuf_release(&err);\n+\tfree(key.mv_data);\n+\tfree(valdata);\n+\n+\treturn ret;\n+}\n+\n+MDB_env *submodule_txn_begin(struct lmdb_transaction *transaction)\n+{\n+\tint ret;\n+\tMDB_env *submodule_env = NULL;\n+\tstruct strbuf path = STRBUF_INIT;\n+\n+\tstrbuf_git_path_submodule(&path, transaction->submodule, \"refdb\");\n+\n+\tif (!is_directory(path.buf))\n+\t\tgoto done;\n+\n+\tmkdir(path.buf, 0775);\n+\n+\tinit_env(&submodule_env, path.buf);\n+\n+\tret = mdb_txn_begin(submodule_env, NULL, MDB_RDONLY, &transaction->txn);\n+\tif (ret) {\n+\t\tdie(\"mdb_txn_begin failed: %s\", mdb_strerror(ret));\n+\n+\t}\n+\tret = mdb_dbi_open(transaction->txn, NULL, 0, &transaction->dbi);\n+\tif (ret) {\n+\t\tdie(\"mdb_txn_open failed: %s\", mdb_strerror(ret));\n+\t}\n+\n+done:\n+\tstrbuf_release(&path);\n+\treturn submodule_env;\n+}\n+\n+static int lmdb_resolve_gitlink_ref(const char *submodule, const char *refname,\n+\t\t\t\t    unsigned char *sha1)\n+{\n+\tstruct lmdb_transaction transaction;\n+\tMDB_env *submodule_env;\n+\tint result;\n+\n+\ttransaction.txn = NULL;\n+\ttransaction.submodule = submodule;\n+\tsubmodule_env = submodule_txn_begin(&transaction);\n+\tif (!submodule_env)\n+\t\treturn -1;\n+\tresult = !resolve_ref_unsafe_txn(&transaction, refname,\n+\t\t\t\t\t RESOLVE_REF_READING, sha1, NULL);\n+\n+\tmdb_txn_abort(transaction.txn);\n+\tmdb_env_close(submodule_env);\n+\treturn result ? -1 : 0;\n+}\n+\n+static int do_head_ref(const char *submodule, each_ref_fn fn, void *cb_data)\n+{\n+\tstruct object_id oid;\n+\tint flag;\n+\n+\tif (submodule) {\n+\t\tif (resolve_gitlink_ref(submodule, \"HEAD\", oid.hash) == 0)\n+\t\t\treturn fn(\"HEAD\", &oid, 0, cb_data);\n+\n+\t\treturn 0;\n+\t}\n+\n+\tif (!read_ref_full(\"HEAD\", RESOLVE_REF_READING, oid.hash, &flag))\n+\t\treturn fn(\"HEAD\", &oid, flag, cb_data);\n+\n+\treturn 0;\n+}\n+\n+static int lmdb_head_ref(each_ref_fn fn, void *cb_data)\n+{\n+\treturn do_head_ref(NULL, fn, cb_data);\n+}\n+\n+static int lmdb_head_ref_submodule(const char *submodule, each_ref_fn fn,\n+\t\t\t\t   void *cb_data)\n+{\n+\treturn do_head_ref(submodule, fn, cb_data);\n+}\n+\n+/*\n+ * Call fn for each reference for which the refname begins with base.\n+ * If trim is non-zero, then trim that many characters off the\n+ * beginning of each refname before passing the refname to fn.  flags\n+ * can be DO_FOR_EACH_INCLUDE_BROKEN to include broken references in\n+ * the iteration.  If fn ever returns a non-zero value, stop the\n+ * iteration and return that value; otherwise, return 0.\n+ */\n+static int do_for_each_ref(struct lmdb_transaction *transaction,\n+\t\t\t   const char *base, each_ref_fn fn, int trim,\n+\t\t\t   int flags, void *cb_data)\n+{\n+\n+\tMDB_val key, val;\n+\tMDB_cursor *cursor;\n+\tint baselen;\n+\tchar *search_key;\n+\tint retval;\n+\tint mdb_ret;\n+\n+\tretval = do_for_each_per_worktree_ref(transaction->submodule, base, fn,\n+\t\t\t\t\t      trim, flags, cb_data);\n+\tif (retval)\n+\t\treturn retval;\n+\n+\tif (ref_paranoia < 0)\n+\t\tref_paranoia = git_env_bool(\"GIT_REF_PARANOIA\", 0);\n+\tif (ref_paranoia)\n+\t\tflags |= DO_FOR_EACH_INCLUDE_BROKEN;\n+\n+\tif (!base || !*base) {\n+\t\tbase = \"refs/\";\n+\t\ttrim = 0;\n+\t}\n+\n+\tbaselen = strlen(base);\n+\tsearch_key = xmalloc(baselen + 1);\n+\tstrcpy(search_key, base);\n+\tkey.mv_size = baselen + 1;\n+\tkey.mv_data = search_key;\n+\n+\tmdb_cursor_open_or_die(transaction, &cursor);\n+\n+\tmdb_ret = mdb_cursor_get_or_die(cursor, &key, &val, MDB_SET_RANGE);\n+\n+\twhile (!mdb_ret) {\n+\t\tstruct object_id oid;\n+\t\tint parsed_flags = 0;\n+\n+\t\tif (memcmp(key.mv_data, base, baselen))\n+\t\t\tbreak;\n+\n+\t\tparse_ref_data(transaction, (const char *)key.mv_data + (trim ? baselen : 0),\n+\t\t\t       val.mv_data, oid.hash, 0, &parsed_flags, 0);\n+\n+\t\tif (flags & DO_FOR_EACH_INCLUDE_BROKEN ||\n+\t\t    (!(parsed_flags & REF_ISBROKEN) &&\n+\t\t     has_sha1_file(oid.hash))) {\n+\t\t\tretval = fn((const char *)key.mv_data + (trim ? baselen : 0), &oid, parsed_flags, cb_data);\n+\t\t\tif (retval)\n+\t\t\t\tbreak;\n+\t\t}\n+\n+\t\tmdb_ret = mdb_cursor_get_or_die(cursor, &key, &val, MDB_NEXT);\n+\t}\n+\n+\tmdb_cursor_close(cursor);\n+\tfree(search_key);\n+\n+\treturn retval;\n+}\n+\n+static int lmdb_for_each_ref(each_ref_fn fn, void *cb_data)\n+{\n+\tlmdb_transaction_begin_flags_or_die(MDB_RDONLY);\n+\treturn do_for_each_ref(&transaction, \"\", fn, 0, 0, cb_data);\n+}\n+\n+static int lmdb_for_each_ref_submodule(const char *submodule, each_ref_fn fn,\n+\t\t\t\t       void *cb_data)\n+{\n+\tstruct lmdb_transaction transaction;\n+\tMDB_env *submodule_env;\n+\tint result;\n+\n+\tif (!submodule)\n+\t\treturn for_each_ref(fn, cb_data);\n+\n+\ttransaction.txn = NULL;\n+\ttransaction.submodule = submodule;\n+\n+\tsubmodule_env = submodule_txn_begin(&transaction);\n+\tif (!submodule_env)\n+\t\treturn 0;\n+\tresult = do_for_each_ref(&transaction, \"\", fn, 0, 0, cb_data);\n+\tmdb_txn_abort(transaction.txn);\n+\tmdb_env_close(submodule_env);\n+\treturn result;\n+}\n+\n+static int lmdb_for_each_ref_in(const char *prefix, each_ref_fn fn,\n+\t\t\t\t void *cb_data)\n+{\n+\tlmdb_transaction_begin_flags_or_die(MDB_RDONLY);\n+\treturn do_for_each_ref(&transaction, prefix, fn, strlen(prefix),\n+\t\t\t       0, cb_data);\n+}\n+\n+static int lmdb_for_each_fullref_in(const char *prefix, each_ref_fn fn,\n+\t\t\t\t    void *cb_data, unsigned int broken)\n+{\n+\tunsigned int flag = 0;\n+\n+\tif (broken)\n+\t\tflag = DO_FOR_EACH_INCLUDE_BROKEN;\n+\tlmdb_transaction_begin_flags_or_die(MDB_RDONLY);\n+\treturn do_for_each_ref(&transaction, prefix, fn, 0, flag, cb_data);\n+}\n+\n+static int lmdb_for_each_ref_in_submodule(const char *submodule,\n+\t\t\t\t\t  const char *prefix,\n+\t\t\t\t\t  each_ref_fn fn, void *cb_data)\n+{\n+\tstruct lmdb_transaction transaction = {NULL};\n+\tMDB_env *submodule_env;\n+\tint result;\n+\n+\tif (!submodule)\n+\t\treturn for_each_ref_in(prefix, fn, cb_data);\n+\n+\ttransaction.submodule = submodule;\n+\tsubmodule_env = submodule_txn_begin(&transaction);\n+\tif (!submodule_env)\n+\t\treturn 0;\n+\tresult = do_for_each_ref(&transaction, prefix, fn,\n+\t\t\t\t strlen(prefix), 0, cb_data);\n+\tmdb_txn_abort(transaction.txn);\n+\tmdb_env_close(submodule_env);\n+\treturn result;\n+}\n+\n+static int lmdb_for_each_replace_ref(each_ref_fn fn, void *cb_data)\n+{\n+\tlmdb_transaction_begin_flags_or_die(MDB_RDONLY);\n+\treturn do_for_each_ref(&transaction, git_replace_ref_base, fn,\n+\t\t\t       strlen(git_replace_ref_base), 0, cb_data);\n+}\n+\n+static int lmdb_for_each_reftype_fullpath(each_ref_fn fn, char *type, unsigned int broken, void *cb_data)\n+{\n+\tunsigned int flag = 0;\n+\n+\tif (broken)\n+\t\tflag = DO_FOR_EACH_INCLUDE_BROKEN;\n+\tlmdb_transaction_begin_flags_or_die(MDB_RDONLY);\n+\treturn do_for_each_ref(&transaction, type, fn, 0, flag, cb_data);\n+}\n+\n+static int lmdb_for_each_namespaced_ref(each_ref_fn fn, void *cb_data)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint ret;\n+\n+\tstrbuf_addf(&buf, \"%srefs/\", get_git_namespace());\n+\tlmdb_transaction_begin_flags_or_die(MDB_RDONLY);\n+\tret = do_for_each_ref(&transaction, buf.buf, fn, 0, 0, cb_data);\n+\tstrbuf_release(&buf);\n+\treturn ret;\n+}\n+\n+static int lmdb_for_each_rawref(each_ref_fn fn, void *cb_data)\n+{\n+\tlmdb_transaction_begin_flags_or_die(MDB_RDONLY);\n+\treturn do_for_each_ref(&transaction, \"\", fn, 0,\n+\t\t\t       DO_FOR_EACH_INCLUDE_BROKEN, cb_data);\n+}\n+\n+/* For testing only! */\n+int test_refdb_raw_read(const char *key)\n+{\n+\tMDB_val key_val, val;\n+\tchar *keydup;\n+\tint ret;\n+\n+\tlmdb_transaction_begin_flags_or_die(MDB_RDONLY);\n+\tkeydup = xstrdup(key);\n+\tkey_val.mv_data = keydup;\n+\tkey_val.mv_size = strlen(key) + 1;\n+\n+\tret = mdb_get_or_die(&transaction, &key_val, &val);\n+\tfree(keydup);\n+\tswitch (ret) {\n+\tcase 0:\n+\t\tprintf(\"%s\\n\", (char *)val.mv_data);\n+\t\treturn 0;\n+\tcase MDB_NOTFOUND:\n+\t\tfprintf(stderr, \"%s not found\\n\", key);\n+\t\treturn 1;\n+\tdefault:\n+\t\treturn 2;\n+\t}\n+}\n+\n+/* For testing only! */\n+void test_refdb_raw_write(const char *key, const char *value)\n+{\n+\tMDB_val key_val, val;\n+\tchar *keydup, *valdup;\n+\n+\tif (ref_type(key) != REF_TYPE_NORMAL) {\n+\t\tval.mv_data = (void *)value;\n+\t\tval.mv_size = strlen(value) + 1;\n+\t\twrite_per_worktree_ref(NULL, key, &val);\n+\t\treturn;\n+\t}\n+\n+\tlmdb_transaction_begin_flags_or_die(0);\n+\n+\tkeydup = xstrdup(key);\n+\tkey_val.mv_data = keydup;\n+\tkey_val.mv_size = strlen(key) + 1;\n+\n+\tvaldup = xstrdup(value);\n+\tval.mv_data = valdup;\n+\tval.mv_size = strlen(value) + 1;\n+\n+\tmdb_put_or_die(&transaction, &key_val, &val, 0);\n+\tassert(mdb_transaction_commit(&transaction, NULL) == 0);\n+\n+\tfree(keydup);\n+\tfree(valdup);\n+}\n+\n+/* For testing only! */\n+int test_refdb_raw_delete(const char *key)\n+{\n+\tMDB_val key_val;\n+\tchar *keydup;\n+\tint ret;\n+\n+\tif (ref_type(key) != REF_TYPE_NORMAL)\n+\t\treturn del_per_worktree_ref(NULL, key, NULL);\n+\n+\tlmdb_transaction_begin_flags_or_die(0);\n+\tkeydup = xstrdup(key);\n+\tkey_val.mv_data = keydup;\n+\tkey_val.mv_size = strlen(key) + 1;\n+\n+\tret = mdb_del_or_die(&transaction, &key_val, NULL);\n+\n+\tassert(mdb_transaction_commit(&transaction, NULL) == 0);\n+\n+\tfree(keydup);\n+\treturn ret;\n+}\n+\n+static int print_raw_reflog_ent(unsigned char *osha1, unsigned char *nsha1,\n+\t\t\t\tconst char *email, unsigned long timestamp,\n+\t\t\t\tint tz, const char *message, void *cb_data)\n+{\n+\tint *any = cb_data;\n+\t*any = 1;\n+\n+\tif (*message != '\\n')\n+\t\tprintf(\"%s %s %s %lu %+05d\\t%s\", sha1_to_hex(osha1),\n+\t\t       sha1_to_hex(nsha1),\n+\t\t       email, timestamp, tz, message);\n+\telse\n+\t\tprintf(\"%s %s %s %lu %+05d\\n\", sha1_to_hex(osha1),\n+\t\t       sha1_to_hex(nsha1),\n+\t\t       email, timestamp, tz);\n+\treturn 0;\n+}\n+\n+/* For testing only! */\n+int test_refdb_raw_reflog(const char *refname)\n+{\n+\tint any = 0;\n+\n+\tfor_each_reflog_ent(refname, print_raw_reflog_ent, &any);\n+\n+\treturn !any;\n+}\n+\n+/* For testing only! */\n+void test_refdb_raw_delete_reflog(char *refname)\n+{\n+\tMDB_val key, val;\n+\tint mdb_ret;\n+\tchar *search_key;\n+\tMDB_cursor *cursor;\n+\tint len;\n+\n+\tif (refname) {\n+\t\tlen = strlen(refname) + 5 + 1; /* logs/ + 0*/\n+\t\tsearch_key = xmalloc(len);\n+\t\tsprintf(search_key, \"logs/%s\", refname);\n+\t} else {\n+\t\tlen = 6; /* logs/ + 0*/\n+\t\tsearch_key = xstrdup(\"logs/\");\n+\t}\n+\tkey.mv_data = search_key;\n+\tkey.mv_size = len;\n+\n+\tlmdb_transaction_begin_flags_or_die(0);\n+\n+\tmdb_cursor_open_or_die(&transaction, &cursor);\n+\n+\tmdb_ret = mdb_cursor_get_or_die(cursor, &key, &val, MDB_SET_RANGE);\n+\twhile (!mdb_ret) {\n+\t\tif (!starts_with(key.mv_data, search_key))\n+\t\t\tbreak;\n+\t\tif (refname && ((char *)val.mv_data)[len - 1] == 0)\n+\t\t\tbreak;\n+\n+\t\tmdb_cursor_del_or_die(cursor, 0);\n+\t\tmdb_ret = mdb_cursor_get_or_die(cursor, &key, &val, MDB_NEXT);\n+\t}\n+\n+\tfree(search_key);\n+\tmdb_cursor_close(cursor);\n+\n+\tassert(mdb_transaction_commit(&transaction, NULL) == 0);\n+\treturn;\n+}\n+\n+static void format_lmdb_reflog_ent(struct strbuf *dst, struct strbuf *src)\n+{\n+\tunsigned char osha1[20], nsha1[20];\n+\tconst char *msg;\n+\n+\tget_sha1_hex(src->buf, osha1);\n+\tget_sha1_hex(src->buf + 41, nsha1);\n+\n+\tmsg = strchr(src->buf + 82, '\\n');\n+\tassert(msg);\n+\tmsg += 1;\n+\n+\tformat_reflog_entry(dst, osha1, nsha1, src->buf + 82, msg);\n+}\n+\n+/* For testing only! */\n+void test_refdb_raw_append_reflog(const char *refname)\n+{\n+\tstruct strbuf input = STRBUF_INIT;\n+\tuint64_t now = getnanotime();\n+\tMDB_val key, val;\n+\n+\tkey.mv_size = strlen(refname) + 14;\n+\tkey.mv_data = xcalloc(1, key.mv_size);\n+\tsprintf(key.mv_data, \"logs/%s\", refname);\n+\n+\tlmdb_transaction_begin_flags_or_die(0);\n+\n+\t/* We do not remove the header here, because this is just for\n+\t * tests, so it's OK to be a bit inefficient */\n+\n+\twhile (strbuf_getwholeline(&input, stdin, '\\n') != EOF) {\n+\t\tstruct strbuf sb = STRBUF_INIT;\n+\n+\t\t/* \"logs/\" + \\0 + 8-byte timestamp for sorting and expiry */\n+\t\twrite_u64((char *)key.mv_data + key.mv_size - 8, htonll(now++));\n+\n+\t\t/*\n+\t\t * Convert the input from files-reflog format to\n+\t\t * lmdb-reflog-format\n+\t\t */\n+\n+\t\tformat_lmdb_reflog_ent(&sb, &input);\n+\t\tval.mv_data = sb.buf;\n+\t\tval.mv_size = sb.len + 1;\n+\t\tmdb_put_or_die(&transaction, &key, &val, 0);\n+\t\tstrbuf_reset(&sb);\n+\t\tinput.len = 0;\n+\t}\n+\n+\tassert(mdb_transaction_commit(&transaction, NULL) == 0);\n+\tfree(key.mv_data);\n+}\n+\n+struct ref_be refs_be_lmdb = {\n+\tNULL,\n+\t\"lmdb\",\n+\tlmdb_init_backend,\n+\tlmdb_initdb,\n+\tlmdb_transaction_commit,\n+\tlmdb_transaction_commit, /* initial commit */\n+\tlmdb_rename_ref,\n+\tlmdb_for_each_reflog_ent,\n+\tlmdb_for_each_reflog_ent_reverse,\n+\tlmdb_for_each_reflog,\n+\tlmdb_reflog_exists,\n+\tlmdb_create_reflog,\n+\tlmdb_delete_reflog,\n+\tlmdb_reflog_expire,\n+\tlmdb_resolve_ref_unsafe,\n+\tlmdb_verify_refname_available,\n+\tlmdb_pack_refs,\n+\tlmdb_peel_ref,\n+\tlmdb_create_symref,\n+\tlmdb_resolve_gitlink_ref,\n+\tlmdb_head_ref,\n+\tlmdb_head_ref_submodule,\n+\tlmdb_for_each_ref,\n+\tlmdb_for_each_ref_submodule,\n+\tlmdb_for_each_ref_in,\n+\tlmdb_for_each_fullref_in,\n+\tlmdb_for_each_ref_in_submodule,\n+\tlmdb_for_each_rawref,\n+\tlmdb_for_each_namespaced_ref,\n+\tlmdb_for_each_replace_ref,\n+\tlmdb_for_each_reftype_fullpath,\n+};\ndiff --git a/refs.h b/refs.h\nindex 45615e8..8d85a6e 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -691,6 +691,15 @@ extern int reflog_expire(const char *refname, const unsigned char *sha1,\n \t\t\t reflog_expiry_cleanup_fn cleanup_fn,\n \t\t\t void *policy_cb_data);\n \n+/*\n+ * Read the refdb configuration data out of the config file\n+ */\n+struct refdb_config_data {\n+\tconst char *refs_backend_type;\n+\tconst char *refs_base;\n+};\n+int refdb_config(const char *var, const char *value, void *ptr);\n+\n /* refs backends */\n typedef void ref_backend_init_fn(void *data);\n typedef int ref_backend_initdb_fn(struct strbuf *err, int shared);\n@@ -789,6 +798,7 @@ struct ref_be {\n \n \n extern struct ref_be refs_be_files;\n+extern struct ref_be refs_be_lmdb;\n int set_refs_backend(const char *name, void *init_data);\n void register_refs_backend(struct ref_be *be);\n \ndiff --git a/setup.c b/setup.c\nindex 7433eed..c8ed245 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -1,5 +1,6 @@\n #include \"cache.h\"\n #include \"dir.h\"\n+#include \"refs.h\"\n #include \"string-list.h\"\n \n static int inside_git_dir = -1;\n@@ -263,16 +264,26 @@ int get_common_dir_noenv(struct strbuf *sb, const char *gitdir)\n \treturn ret;\n }\n \n+int refdb_config(const char *var, const char *value, void *ptr)\n+{\n+       struct refdb_config_data *cdata = ptr;\n+\n+       if (!strcmp(var, \"core.refsbackendtype\"))\n+\t       cdata->refs_backend_type = strdup((char *)value);\n+       return 0;\n+}\n+\n /*\n  * Test if it looks like we're at a git directory.\n  * We want to see:\n  *\n  *  - either an objects/ directory _or_ the proper\n  *    GIT_OBJECT_DIRECTORY environment variable\n- *  - a refs/ directory\n- *  - either a HEAD symlink or a HEAD file that is formatted as\n- *    a proper \"ref:\", or a regular file HEAD that has a properly\n- *    formatted sha1 object name.\n+ *  - a refdb/ directory or\n+ *    - a refs/ directory\n+ *    - either a HEAD symlink or a HEAD file that is formatted as\n+ *      a proper \"ref:\", or a regular file HEAD that has a properly\n+ *      formatted sha1 object name.\n  */\n int is_git_directory(const char *suspect)\n {\n@@ -303,8 +314,13 @@ int is_git_directory(const char *suspect)\n \n \tstrbuf_setlen(&path, len);\n \tstrbuf_addstr(&path, \"/refs\");\n-\tif (access(path.buf, X_OK))\n-\t\tgoto done;\n+\n+\tif (access(path.buf, X_OK)) {\n+\t\tstrbuf_setlen(&path, len);\n+\t\tstrbuf_addstr(&path, \"/refdb\");\n+\t\tif (access(path.buf, X_OK))\n+\t\t\tgoto done;\n+\t}\n \n \tret = 1;\n done:\n@@ -371,6 +387,10 @@ static int check_repo_format(const char *var, const char *value, void *cb)\n \t\t */\n \t\tif (!strcmp(ext, \"noop\"))\n \t\t\t;\n+#ifdef USE_LIBLMDB\n+\t\telse if (!strcmp(ext, \"refbackend\") && !strcmp(value, \"lmdb\"))\n+\t\t\t;\n+#endif\n \t\telse\n \t\t\tstring_list_append(&unknown_extensions, ext);\n \t}\ndiff --git a/test-refs-be-lmdb.c b/test-refs-be-lmdb.c\nnew file mode 100644\nindex 0000000..bb875ff\n--- /dev/null\n+++ b/test-refs-be-lmdb.c\n@@ -0,0 +1,68 @@\n+#include \"cache.h\"\n+#include \"string-list.h\"\n+#include \"parse-options.h\"\n+#include \"refs.h\"\n+\n+static const char * const test_refs_be_lmdb_usage[] = {\n+\t\"git test-refs-be-lmdb <key>\",\n+\t\"git test-refs-be-lmdb <key> <value>\",\n+\tNULL,\n+};\n+\n+int test_refdb_raw_read(const char *key);\n+void test_refdb_raw_write(const char *key, const char *value);\n+int test_refdb_raw_reflog(const char *refname);\n+int test_refdb_raw_delete(const char *key);\n+void test_refdb_raw_delete_reflog(const char *refname);\n+void test_refdb_raw_append_reflog(const char *refname);\n+\n+int main(int argc, const char **argv)\n+{\n+\tconst char *delete = NULL;\n+\tconst char *reflog = NULL;\n+\tconst char *append_reflog = NULL;\n+\tint delete_missing_error = 0;\n+\tint clear_reflog = 0;\n+\tstruct refdb_config_data config_data = {NULL};\n+\n+\tstruct option options[] = {\n+\t\tOPT_STRING('d', NULL, &delete, \"branch\", \"delete refdb entry\"),\n+\t\tOPT_STRING('l', NULL, &reflog, \"branch\", \"show reflog\"),\n+\t\tOPT_STRING('a', NULL, &append_reflog, \"branch\", \"append to reflog\"),\n+\t\tOPT_BOOL('c', NULL, &clear_reflog, \"delete reflog. If a branch is provided, the reflog for that branch will be deleted; else all reflogs will be deleted.\"),\n+\t\tOPT_BOOL('x', NULL, &delete_missing_error,\n+\t\t\t \"deleting a missing key is an error\"),\n+\t\tOPT_END(),\n+\t};\n+\n+\targc = parse_options(argc, argv, \"\", options, test_refs_be_lmdb_usage,\n+\t\t\t     0);\n+\n+\tif (!append_reflog && !clear_reflog && !delete && !reflog && argc != 1 && argc != 2)\n+\t\tusage_with_options(test_refs_be_lmdb_usage,\n+\t\t\t\t   options);\n+\n+\tgit_config(git_default_config, NULL);\n+\n+\tconfig_data.refs_backend_type = \"lmdb\";\n+\tconfig_data.refs_base = get_git_dir();\n+\n+\tregister_refs_backend(&refs_be_lmdb);\n+\tset_refs_backend(\"lmdb\", &config_data);\n+\n+\tif (clear_reflog) {\n+\t\ttest_refdb_raw_delete_reflog(argv[0]);\n+\t} else if (append_reflog) {\n+\t\ttest_refdb_raw_append_reflog(append_reflog);\n+\t} else if (reflog) {\n+\t\treturn test_refdb_raw_reflog(reflog);\n+\t} else if (delete) {\n+\t\tif (test_refdb_raw_delete(delete) && delete_missing_error)\n+\t\t\treturn 1;\n+\t} else if (argc == 1) {\n+\t\treturn test_refdb_raw_read(argv[0]);\n+\t} else {\n+\t\ttest_refdb_raw_write(argv[0], argv[1]);\n+\t}\n+\treturn 0;\n+}\n-- \n2.4.2.644.g97b850b-twtrsrc\n"},{"id":"271515","messageId":"1444686725-27660-46-git-send-email-dturner@twopensource.com","threadId":"40543","inReplyTo":"1444686725-27660-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 44/44] refs: tests for db backend","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-10-12T21:52:05Z","receivedAt":"2015-10-12T21:52:05Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"Add tests for the database backend.\n\nSigned-off-by: David Turner <dturner@twopensource.com>\nHelped-by: Dennis Kaarsemaker <dennis@kaarsemaker.net>\n---\n t/t1460-refs-be-db.sh        | 1109 ++++++++++++++++++++++++++++++++++++++++++\n t/t1470-refs-be-db-reflog.sh |  359 ++++++++++++++\n t/test-lib.sh                |    1 +\n 3 files changed, 1469 insertions(+)\n create mode 100755 t/t1460-refs-be-db.sh\n create mode 100755 t/t1470-refs-be-db-reflog.sh\n\ndiff --git a/t/t1460-refs-be-db.sh b/t/t1460-refs-be-db.sh\nnew file mode 100755\nindex 0000000..56d2d2b\n--- /dev/null\n+++ b/t/t1460-refs-be-db.sh\n@@ -0,0 +1,1109 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2015 Twitter, Inc\n+# Copyright (c) 2006 Shawn Pearce\n+# This test is based on t1400-update-ref.sh\n+#\n+\n+test_description='Test lmdb refs backend'\n+TEST_NO_CREATE_REPO=1\n+. ./test-lib.sh\n+\n+if ! test_have_prereq LMDB\n+then\n+\tskip_all=\"Skipping lmdb refs backend tests, lmdb backend not built\"\n+\ttest_done\n+fi\n+\n+raw_ref() {\n+\ttest-refs-be-lmdb \"$1\"\n+}\n+\n+delete_ref() {\n+\ttest-refs-be-lmdb -d \"$1\"\n+}\n+\n+write_ref() {\n+\ttest-refs-be-lmdb \"$1\" \"$2\"\n+}\n+\n+raw_reflog() {\n+\ttest-refs-be-lmdb -l \"$1\"\n+}\n+\n+delete_all_reflogs() {\n+\ttest-refs-be-lmdb -c\n+}\n+\n+append_reflog() {\n+\ttest-refs-be-lmdb -a \"$1\"\n+}\n+\n+Z=$_z40\n+\n+test_expect_success setup '\n+\tgit init --refs-backend-type=lmdb &&\n+\tfor name in A B C D E F\n+\tdo\n+\t\ttest_tick &&\n+\t\tT=$(git write-tree) &&\n+\t\tsha1=$(echo $name | git commit-tree $T) &&\n+\t\teval $name=$sha1\n+\tdone\n+'\n+\n+m=refs/heads/master\n+n_dir=refs/heads/gu\n+n=$n_dir/fixes\n+\n+test_expect_success \\\n+\t\"create $m\" \\\n+\t\"git update-ref $m $A &&\n+\t test $A\"' = $(raw_ref '\"$m\"')'\n+test_expect_success \\\n+\t\"create $m\" \\\n+\t\"git update-ref $m $B $A &&\n+\t test $B\"' = $(raw_ref '\"$m\"')'\n+test_expect_success \"fail to delete $m with stale ref\" '\n+\ttest_must_fail git update-ref -d $m $A &&\n+\ttest $B = \"$(raw_ref $m)\"\n+'\n+test_expect_success \"delete $m\" '\n+\tgit update-ref -d $m $B &&\n+\t! raw_ref $m\n+'\n+delete_ref $m\n+\n+test_expect_success \"delete $m without oldvalue verification\" \"\n+\tgit update-ref $m $A &&\n+\ttest $A = \\$(raw_ref $m) &&\n+\tgit update-ref -d $m &&\n+\t! raw_ref $m\n+\"\n+delete_ref $m\n+\n+test_expect_success \\\n+\t\"fail to create $n\" \\\n+\t\"git update-ref $n_dir $A &&\n+\t test_must_fail git update-ref $n $A >out 2>err\"\n+\n+delete_ref $n_dir\n+rm -f out err\n+\n+test_expect_success \\\n+\t\"create $m (by HEAD)\" \\\n+\t\"git update-ref HEAD $A &&\n+\t test $A\"' = $(raw_ref '\"$m\"')'\n+test_expect_success \\\n+\t\"create $m (by HEAD)\" \\\n+\t\"git update-ref HEAD $B $A &&\n+\t test $B\"' = $(raw_ref '\"$m\"')'\n+test_expect_success \"fail to delete $m (by HEAD) with stale ref\" '\n+\ttest_must_fail git update-ref -d HEAD $A &&\n+\ttest $B = $(raw_ref '\"$m\"')\n+'\n+test_expect_success \"delete $m (by HEAD)\" '\n+\tgit update-ref -d HEAD $B &&\n+\t! raw_ref $m\n+'\n+delete_ref $m\n+\n+test_expect_success \\\n+\t\"create $m (by HEAD)\" \\\n+\t\"git update-ref HEAD $A &&\n+\t test $A\"' = $(raw_ref '\"$m\"')'\n+test_expect_success \\\n+\t\"pack refs\" \\\n+\t\"git pack-refs --all\"\n+test_expect_success \\\n+\t\"move $m (by HEAD)\" \\\n+\t\"git update-ref HEAD $B $A &&\n+\t test $B\"' = $(raw_ref '\"$m\"')'\n+test_expect_success \"delete $m (by HEAD) should remove both packed and loose $m\" '\n+\tgit update-ref -d HEAD $B &&\n+\t! raw_ref $m\n+'\n+delete_ref $m\n+\n+OLD_HEAD=$(raw_ref HEAD)\n+test_expect_success \"delete symref without dereference\" '\n+\tgit update-ref --no-deref -d HEAD &&\n+\t! raw_ref HEAD\n+'\n+write_ref HEAD \"$OLD_HEAD\"\n+\n+test_expect_success \"delete symref without dereference when the referred ref is packed\" '\n+\techo foo >foo.c &&\n+\tgit add foo.c &&\n+\tgit commit -m foo &&\n+\tgit pack-refs --all &&\n+\tgit update-ref --no-deref -d HEAD &&\n+\t! raw_ref HEAD\n+'\n+write_ref HEAD \"$OLD_HEAD\"\n+delete_ref $m\n+\n+test_expect_success 'update-ref -d is not confused by self-reference' '\n+\tgit symbolic-ref refs/heads/self refs/heads/self &&\n+\ttest_when_finished \"delete_ref refs/heads/self\" &&\n+\ttest_must_fail git update-ref -d refs/heads/self\n+'\n+\n+test_expect_success 'update-ref --no-deref -d can delete self-reference' '\n+\tgit symbolic-ref refs/heads/self refs/heads/self &&\n+\ttest_when_finished \"delete_ref refs/heads/self\" &&\n+\tgit update-ref --no-deref -d refs/heads/self\n+'\n+\n+test_expect_success 'update-ref --no-deref -d can delete reference to bad ref' '\n+\ttest-refs-be-lmdb refs/heads/bad \"\" &&\n+\ttest_when_finished \"delete_ref refs/heads/bad\" &&\n+\tgit symbolic-ref refs/heads/ref-to-bad refs/heads/bad &&\n+\ttest_when_finished \"delete_ref refs/heads/ref-to-bad\" &&\n+\traw_ref refs/heads/ref-to-bad &&\n+\tgit update-ref --no-deref -d refs/heads/ref-to-bad &&\n+\t! raw_ref refs/heads/ref-to-bad\n+'\n+\n+test_expect_success '(not) create HEAD with old sha1' \"\n+\ttest_must_fail git update-ref HEAD $A $B\n+\"\n+test_expect_success \"(not) prior created .git/$m\" \"\n+\t! raw_ref $m\n+\"\n+delete_ref $m\n+\n+test_expect_success \\\n+\t\"create HEAD\" \\\n+\t\"git update-ref HEAD $A\"\n+test_expect_success '(not) change HEAD with wrong SHA1' \"\n+\ttest_must_fail git update-ref HEAD $B $Z\n+\"\n+test_expect_success \"(not) changed .git/$m\" \"\n+\t! test $B\"' = $(raw_ref '\"$m\"')\n+'\n+\n+: a repository with working tree always has reflog these days...\n+delete_all_reflogs\n+: | append_reflog $m\n+delete_ref $m\n+\n+test_expect_success \\\n+\t\"create $m (logged by touch)\" \\\n+\t'GIT_COMMITTER_DATE=\"2005-05-26 23:30\" \\\n+\t git update-ref HEAD '\"$A\"' -m \"Initial Creation\" &&\n+\t test '\"$A\"' = $(raw_ref '\"$m\"')'\n+test_expect_success \\\n+\t\"update $m (logged by touch)\" \\\n+\t'GIT_COMMITTER_DATE=\"2005-05-26 23:31\" \\\n+\t git update-ref HEAD'\" $B $A \"'-m \"Switch\" &&\n+\t test '\"$B\"' = $(raw_ref '\"$m\"')'\n+test_expect_success \\\n+\t\"set $m (logged by touch)\" \\\n+\t'GIT_COMMITTER_DATE=\"2005-05-26 23:41\" \\\n+\t git update-ref HEAD'\" $A &&\n+\t test $A\"' = $(raw_ref '\"$m\"')'\n+\n+cat >expect <<EOF\n+$Z $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000\tInitial Creation\n+$A $B $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150260 +0000\tSwitch\n+$B $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150860 +0000\n+EOF\n+test_expect_success \\\n+\t\"verifying $m's log\" \\\n+\t\"raw_reflog $m >actual &&\n+\t test_cmp expect actual\"\n+delete_ref $m\n+delete_all_reflogs\n+: | append_reflog $m\n+rm -f actual expect\n+\n+test_expect_success \\\n+\t'enable core.logAllRefUpdates' \\\n+\t'git config core.logAllRefUpdates true &&\n+\t test true = $(git config --bool --get core.logAllRefUpdates)'\n+\n+test_expect_success \\\n+\t\"create $m (logged by config)\" \\\n+\t'GIT_COMMITTER_DATE=\"2005-05-26 23:32\" \\\n+\t git update-ref HEAD'\" $A \"'-m \"Initial Creation\" &&\n+\t test '\"$A\"' = $(raw_ref '\"$m\"')'\n+test_expect_success \\\n+\t\"update $m (logged by config)\" \\\n+\t'GIT_COMMITTER_DATE=\"2005-05-26 23:33\" \\\n+\t git update-ref HEAD'\" $B $A \"'-m \"Switch\" &&\n+\t test '\"$B\"' = $(raw_ref '\"$m\"')'\n+test_expect_success \\\n+\t\"set $m (logged by config)\" \\\n+\t'GIT_COMMITTER_DATE=\"2005-05-26 23:43\" \\\n+\t git update-ref HEAD '\"$A &&\n+\t test $A\"' = $(raw_ref '\"$m\"')'\n+\n+cat >expect <<EOF\n+$Z $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150320 +0000\tInitial Creation\n+$A $B $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150380 +0000\tSwitch\n+$B $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150980 +0000\n+EOF\n+test_expect_success \\\n+\t\"verifying $m's log\" \\\n+\t'raw_reflog $m >actual &&\n+\ttest_cmp expect actual'\n+delete_ref $m\n+rm -f expect\n+\n+git update-ref $m $D\n+git reflog expire --expire=all $m\n+\n+append_reflog $m <<EOF\n+0000000000000000000000000000000000000000 $C $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150320 -0500\n+$C $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150350 -0500\n+$A $B $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150380 -0500\n+$F $Z $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150680 -0500\n+$Z $E $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150980 -0500\n+EOF\n+\n+ed=\"Thu, 26 May 2005 18:32:00 -0500\"\n+gd=\"Thu, 26 May 2005 18:33:00 -0500\"\n+ld=\"Thu, 26 May 2005 18:43:00 -0500\"\n+test_expect_success \\\n+\t'Query \"master@{May 25 2005}\" (before history)' \\\n+\t'rm -f o e &&\n+\t git rev-parse --verify \"master@{May 25 2005}\" >o 2>e &&\n+\t test '\"$C\"' = $(cat o) &&\n+\t test \"warning: Log for '\\'master\\'' only goes back to $ed.\" = \"$(cat e)\"'\n+test_expect_success \\\n+\t\"Query master@{2005-05-25} (before history)\" \\\n+\t'rm -f o e &&\n+\t git rev-parse --verify master@{2005-05-25} >o 2>e &&\n+\t test '\"$C\"' = $(cat o) &&\n+\t echo test \"warning: Log for '\\'master\\'' only goes back to $ed.\" = \"$(cat e)\"'\n+test_expect_success \\\n+\t'Query \"master@{May 26 2005 23:31:59}\" (1 second before history)' \\\n+\t'rm -f o e &&\n+\t git rev-parse --verify \"master@{May 26 2005 23:31:59}\" >o 2>e &&\n+\t test '\"$C\"' = $(cat o) &&\n+\t test \"warning: Log for '\\''master'\\'' only goes back to $ed.\" = \"$(cat e)\"'\n+test_expect_success \\\n+\t'Query \"master@{May 26 2005 23:32:00}\" (exactly history start)' \\\n+\t'rm -f o e &&\n+\t git rev-parse --verify \"master@{May 26 2005 23:32:00}\" >o 2>e &&\n+\t test '\"$C\"' = $(cat o) &&\n+\t test \"\" = \"$(cat e)\"'\n+test_expect_success \\\n+\t'Query \"master@{May 26 2005 23:32:30}\" (first non-creation change)' \\\n+\t'rm -f o e &&\n+\t git rev-parse --verify \"master@{May 26 2005 23:32:30}\" >o 2>e &&\n+\t test '\"$A\"' = $(cat o) &&\n+\t test \"\" = \"$(cat e)\"'\n+test_expect_success \\\n+\t'Query \"master@{2005-05-26 23:33:01}\" (middle of history with gap)' \\\n+\t'rm -f o e &&\n+\t git rev-parse --verify \"master@{2005-05-26 23:33:01}\" >o 2>e &&\n+\t test '\"$B\"' = $(cat o) &&\n+\t test \"warning: Log for ref '\"$m has gap after $gd\"'.\" = \"$(cat e)\"'\n+test_expect_success \\\n+\t'Query \"master@{2005-05-26 23:38:00}\" (middle of history)' \\\n+\t'rm -f o e &&\n+\t git rev-parse --verify \"master@{2005-05-26 23:38:00}\" >o 2>e &&\n+\t test '\"$Z\"' = $(cat o) &&\n+\t test \"\" = \"$(cat e)\"'\n+test_expect_success \\\n+\t'Query \"master@{2005-05-26 23:43:00}\" (exact end of history)' \\\n+\t'rm -f o e &&\n+\t git rev-parse --verify \"master@{2005-05-26 23:43:00}\" >o 2>e &&\n+\t test '\"$E\"' = $(cat o) &&\n+\t test \"\" = \"$(cat e)\"'\n+test_expect_success \\\n+\t'Query \"master@{2005-05-28}\" (past end of history)' \\\n+\t'rm -f o e &&\n+\t git rev-parse --verify \"master@{2005-05-28}\" >o 2>e &&\n+\t test '\"$D\"' = $(cat o) &&\n+\t test \"warning: Log for ref '\"$m unexpectedly ended on $ld\"'.\" = \"$(cat e)\"'\n+\n+\n+git reflog expire --expire=all $m\n+delete_ref $m\n+\n+test_expect_success \\\n+    'creating initial files' \\\n+    'echo TEST >F &&\n+     git add F &&\n+\t GIT_AUTHOR_DATE=\"2005-05-26 23:30\" \\\n+\t GIT_COMMITTER_DATE=\"2005-05-26 23:30\" git commit -m add -a &&\n+\t h_TEST=$(git rev-parse --verify HEAD) &&\n+\t echo The other day this did not work. >M &&\n+\t echo And then Bob told me how to fix it. >>M &&\n+\t echo OTHER >F &&\n+\t GIT_AUTHOR_DATE=\"2005-05-26 23:41\" \\\n+\t GIT_COMMITTER_DATE=\"2005-05-26 23:41\" git commit -F M -a &&\n+\t h_OTHER=$(git rev-parse --verify HEAD) &&\n+\t GIT_AUTHOR_DATE=\"2005-05-26 23:44\" \\\n+\t GIT_COMMITTER_DATE=\"2005-05-26 23:44\" git commit --amend &&\n+\t h_FIXED=$(git rev-parse --verify HEAD) &&\n+\t echo Merged initial commit and a later commit. >M &&\n+\t echo $h_TEST >.git/MERGE_HEAD &&\n+\t GIT_AUTHOR_DATE=\"2005-05-26 23:45\" \\\n+\t GIT_COMMITTER_DATE=\"2005-05-26 23:45\" git commit -F M &&\n+\t h_MERGED=$(git rev-parse --verify HEAD) &&\n+\t rm -f M'\n+\n+cat >expect <<EOF\n+$Z $h_TEST $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000\tcommit (initial): add\n+$h_TEST $h_OTHER $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150860 +0000\tcommit: The other day this did not work.\n+$h_OTHER $h_FIXED $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117151040 +0000\tcommit (amend): The other day this did not work.\n+$h_FIXED $h_MERGED $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117151100 +0000\tcommit (merge): Merged initial commit and a later commit.\n+EOF\n+test_expect_success \\\n+\t'git commit logged updates' \\\n+\t\"raw_reflog $m >actual &&\n+\ttest_cmp expect actual\"\n+unset h_TEST h_OTHER h_FIXED h_MERGED\n+\n+test_expect_success \\\n+\t'git cat-file blob master:F (expect OTHER)' \\\n+\t'test OTHER = $(git cat-file blob master:F)'\n+test_expect_success \\\n+\t'git cat-file blob master@{2005-05-26 23:30}:F (expect TEST)' \\\n+\t'test TEST = $(git cat-file blob \"master@{2005-05-26 23:30}:F\")'\n+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 unbalanced quotes' '\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 invalid escape' '\n+\techo \"create $a \\\"ma\\zter\\\"\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: badly quoted argument: \\\\\\\"ma\\\\\\\\zter\\\\\\\"\" err\n+'\n+\n+test_expect_success 'stdin fails on junk after quoted argument' '\n+\techo \"create \\\"$a\\\"master\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin 2>err &&\n+\tgrep \"fatal: unexpected character after quoted argument: \\\\\\\"$a\\\\\\\"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: missing <ref>\" 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: 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: missing <ref>\" 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: 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: missing <ref>\" 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: 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: 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 succeeds with quoted argument' '\n+\tgit update-ref -d $a &&\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 succeeds with escaped character' '\n+\tgit update-ref -d $a &&\n+\techo \"create $a \\\"ma\\\\163ter\\\"\" >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: update $c: invalid <oldvalue>: 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: create $c: invalid <newvalue>: 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: zero <newvalue>\" 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: zero <oldvalue>\" 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 refs/TESTSYMREF $b &&\n+\tcat >stdin <<-EOF &&\n+\toption no-deref\n+\tupdate refs/TESTSYMREF $a $b\n+\tEOF\n+\tgit update-ref --stdin <stdin &&\n+\tgit rev-parse refs/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 refs/TESTSYMREF $b &&\n+\tcat >stdin <<-EOF &&\n+\toption no-deref\n+\tdelete refs/TESTSYMREF $b\n+\tEOF\n+\tgit update-ref --stdin <stdin &&\n+\ttest_must_fail git rev-parse --verify -q refs/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 verify succeeds for correct value' '\n+\tgit rev-parse $m >expect &&\n+\techo \"verify $m $m\" >stdin &&\n+\tgit update-ref --stdin <stdin &&\n+\tgit rev-parse $m >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin verify succeeds for missing reference' '\n+\techo \"verify refs/heads/missing $Z\" >stdin &&\n+\tgit update-ref --stdin <stdin &&\n+\ttest_must_fail git rev-parse --verify -q refs/heads/missing\n+'\n+\n+test_expect_success 'stdin verify treats no value as missing' '\n+\techo \"verify refs/heads/missing\" >stdin &&\n+\tgit update-ref --stdin <stdin &&\n+\ttest_must_fail git rev-parse --verify -q refs/heads/missing\n+'\n+\n+test_expect_success 'stdin verify fails for wrong value' '\n+\tgit rev-parse $m >expect &&\n+\techo \"verify $m $m~1\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin &&\n+\tgit rev-parse $m >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin verify fails for mistaken null value' '\n+\tgit rev-parse $m >expect &&\n+\techo \"verify $m $Z\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin &&\n+\tgit rev-parse $m >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin verify fails for mistaken empty value' '\n+\tM=$(git rev-parse $m) &&\n+\ttest_when_finished \"git update-ref $m $M\" &&\n+\tgit rev-parse $m >expect &&\n+\techo \"verify $m\" >stdin &&\n+\ttest_must_fail git update-ref --stdin <stdin &&\n+\tgit rev-parse $m >actual &&\n+\ttest_cmp expect actual\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: missing <ref>\" 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: unexpected end of input when reading <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: missing <ref>\" err\n+'\n+\n+test_expect_success 'stdin -z fails update with too few args' '\n+\tprintf $F \"update $a\" \"$m\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: update $a: unexpected end of input when reading <oldvalue>\" err\n+'\n+\n+test_expect_success 'stdin -z emits warning with empty new value' '\n+\tgit update-ref $a $m &&\n+\tprintf $F \"update $a\" \"\" \"\" >stdin &&\n+\tgit update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"warning: update $a: missing <newvalue>, treating as zero\" err &&\n+\ttest_must_fail git rev-parse --verify -q $a\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: unexpected end of input when reading <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: unexpected end of input when reading <oldvalue>\" err\n+'\n+\n+test_expect_success 'stdin -z fails update with too many arguments' '\n+\tprintf $F \"update $m\" \"$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: missing <ref>\" 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: unexpected end of input when reading <oldvalue>\" err\n+'\n+\n+test_expect_success 'stdin -z fails delete with too many arguments' '\n+\tprintf $F \"delete $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 verify with too many arguments' '\n+\tprintf $F \"verify $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 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: unexpected end of input when reading <oldvalue>\" 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: update $c: invalid <oldvalue>: 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 when ref exists' '\n+\tgit update-ref $c $m &&\n+\tgit rev-parse \"$c\" >expect &&\n+\tprintf $F \"create $c\" \"$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+\tgit rev-parse \"$c\" >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin -z create ref fails with bad new value' '\n+\tgit update-ref -d \"$c\" &&\n+\tprintf $F \"create $c\" \"does-not-exist\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin 2>err &&\n+\tgrep \"fatal: create $c: invalid <newvalue>: 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 empty 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: missing <newvalue>\" 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: zero <oldvalue>\" 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 refs/TESTSYMREF $b &&\n+\tprintf $F \"option no-deref\" \"update refs/TESTSYMREF\" \"$a\" \"$b\" >stdin &&\n+\tgit update-ref -z --stdin <stdin &&\n+\tgit rev-parse refs/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 refs/TESTSYMREF $b &&\n+\tprintf $F \"option no-deref\" \"delete refs/TESTSYMREF\" \"$b\" >stdin &&\n+\tgit update-ref -z --stdin <stdin &&\n+\ttest_must_fail git rev-parse --verify -q refs/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 verify succeeds for correct value' '\n+\tgit rev-parse $m >expect &&\n+\tprintf $F \"verify $m\" \"$m\" >stdin &&\n+\tgit update-ref -z --stdin <stdin &&\n+\tgit rev-parse $m >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin -z verify succeeds for missing reference' '\n+\tprintf $F \"verify refs/heads/missing\" \"$Z\" >stdin &&\n+\tgit update-ref -z --stdin <stdin &&\n+\ttest_must_fail git rev-parse --verify -q refs/heads/missing\n+'\n+\n+test_expect_success 'stdin -z verify treats no value as missing' '\n+\tprintf $F \"verify refs/heads/missing\" \"\" >stdin &&\n+\tgit update-ref -z --stdin <stdin &&\n+\ttest_must_fail git rev-parse --verify -q refs/heads/missing\n+'\n+\n+test_expect_success 'stdin -z verify fails for wrong value' '\n+\tgit rev-parse $m >expect &&\n+\tprintf $F \"verify $m\" \"$m~1\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin &&\n+\tgit rev-parse $m >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin -z verify fails for mistaken null value' '\n+\tgit rev-parse $m >expect &&\n+\tprintf $F \"verify $m\" \"$Z\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin &&\n+\tgit rev-parse $m >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stdin -z verify fails for mistaken empty value' '\n+\tM=$(git rev-parse $m) &&\n+\ttest_when_finished \"git update-ref $m $M\" &&\n+\tgit rev-parse $m >expect &&\n+\tprintf $F \"verify $m\" \"\" >stdin &&\n+\ttest_must_fail git update-ref -z --stdin <stdin &&\n+\tgit rev-parse $m >actual &&\n+\ttest_cmp expect actual\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\" \"$m\" \"$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\ndiff --git a/t/t1470-refs-be-db-reflog.sh b/t/t1470-refs-be-db-reflog.sh\nnew file mode 100755\nindex 0000000..2c6b867\n--- /dev/null\n+++ b/t/t1470-refs-be-db-reflog.sh\n@@ -0,0 +1,359 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2015 Twitter, Inc\n+# Copyright (c) 2007 Junio C Hamano\n+#\n+\n+test_description='Test prune and reflog expiration'\n+TEST_NO_CREATE_REPO=1\n+. ./test-lib.sh\n+\n+if ! test_have_prereq LMDB\n+then\n+\tskip_all=\"Skipping lmdb refs backend tests, lmdb backend not built\"\n+\ttest_done\n+fi\n+\n+raw_reflog() {\n+\tcat .git/logs/$1 2>/dev/null || test-refs-be-lmdb -l \"$1\"\n+}\n+\n+append_reflog() {\n+\ttest-refs-be-lmdb -a \"$1\"\n+}\n+\n+check_have () {\n+\tgaah= &&\n+\tfor N in \"$@\"\n+\tdo\n+\t\teval \"o=\\$$N\" && git cat-file -t $o || {\n+\t\t\techo Gaah $N\n+\t\t\tgaah=$N\n+\t\t\tbreak\n+\t\t}\n+\tdone &&\n+\ttest -z \"$gaah\"\n+}\n+\n+check_fsck () {\n+\toutput=$(git fsck --full)\n+\tcase \"$1\" in\n+\t'')\n+\t\ttest -z \"$output\" ;;\n+\t*)\n+\t\techo \"$output\" | grep \"$1\" ;;\n+\tesac\n+}\n+\n+corrupt () {\n+\taa=${1%??????????????????????????????????????} zz=${1#??}\n+\tmv .git/objects/$aa/$zz .git/$aa$zz\n+}\n+\n+recover () {\n+\taa=${1%??????????????????????????????????????} zz=${1#??}\n+\tmkdir -p .git/objects/$aa\n+\tmv .git/$aa$zz .git/objects/$aa/$zz\n+}\n+\n+check_dont_have () {\n+\tgaah= &&\n+\tfor N in \"$@\"\n+\tdo\n+\t\teval \"o=\\$$N\"\n+\t\tgit cat-file -t $o && {\n+\t\t\techo Gaah $N\n+\t\t\tgaah=$N\n+\t\t\tbreak\n+\t\t}\n+\tdone\n+\ttest -z \"$gaah\"\n+}\n+\n+test_expect_success setup '\n+\tgit init --refs-backend-type=lmdb &&\n+\tmkdir -p A/B &&\n+\techo rat >C &&\n+\techo ox >A/D &&\n+\techo tiger >A/B/E &&\n+\tgit add . &&\n+\n+\ttest_tick && git commit -m rabbit &&\n+\tH=`git rev-parse --verify HEAD` &&\n+\tA=`git rev-parse --verify HEAD:A` &&\n+\tB=`git rev-parse --verify HEAD:A/B` &&\n+\tC=`git rev-parse --verify HEAD:C` &&\n+\tD=`git rev-parse --verify HEAD:A/D` &&\n+\tE=`git rev-parse --verify HEAD:A/B/E` &&\n+\tcheck_fsck &&\n+\n+\ttest_chmod +x C &&\n+\tgit add C &&\n+\ttest_tick && git commit -m dragon &&\n+\tL=`git rev-parse --verify HEAD` &&\n+\tcheck_fsck &&\n+\n+\trm -f C A/B/E &&\n+\techo snake >F &&\n+\techo horse >A/G &&\n+\tgit add F A/G &&\n+\ttest_tick && git commit -a -m sheep &&\n+\tF=`git rev-parse --verify HEAD:F` &&\n+\tG=`git rev-parse --verify HEAD:A/G` &&\n+\tI=`git rev-parse --verify HEAD:A` &&\n+\tJ=`git rev-parse --verify HEAD` &&\n+\tcheck_fsck &&\n+\n+\trm -f A/G &&\n+\ttest_tick && git commit -a -m monkey &&\n+\tK=`git rev-parse --verify HEAD` &&\n+\tcheck_fsck &&\n+\n+\tcheck_have A B C D E F G H I J K L &&\n+\n+\tgit prune &&\n+\n+\tcheck_have A B C D E F G H I J K L &&\n+\n+\tcheck_fsck &&\n+\n+\traw_reflog refs/heads/master >reflog &&\n+\ttest_when_finished rm -f reflog &&\n+\ttest_line_count = 4 reflog\n+'\n+\n+test_expect_success rewind '\n+\ttest_tick && git reset --hard HEAD~2 &&\n+\ttest -f C &&\n+\ttest -f A/B/E &&\n+\t! test -f F &&\n+\t! test -f A/G &&\n+\n+\tcheck_have A B C D E F G H I J K L &&\n+\n+\tgit prune &&\n+\n+\tcheck_have A B C D E F G H I J K L &&\n+\n+\traw_reflog refs/heads/master >reflog &&\n+\ttest_when_finished rm -f reflog &&\n+\ttest_line_count = 5 reflog\n+'\n+\n+test_expect_success 'corrupt and check' '\n+\n+\tcorrupt $F &&\n+\tcheck_fsck \"missing blob $F\"\n+\n+'\n+\n+test_expect_success 'reflog expire --dry-run should not touch reflog' '\n+\n+\tgit reflog expire --dry-run \\\n+\t\t--expire=$(($test_tick - 10000)) \\\n+\t\t--expire-unreachable=$(($test_tick - 10000)) \\\n+\t\t--stale-fix \\\n+\t\t--all &&\n+\n+\traw_reflog refs/heads/master >reflog &&\n+\ttest_when_finished rm -f reflog &&\n+\ttest_line_count = 5 reflog &&\n+\n+\tcheck_fsck \"missing blob $F\"\n+'\n+\n+test_expect_success 'reflog expire' '\n+\n+\tgit reflog expire --verbose \\\n+\t\t--expire=$(($test_tick - 10000)) \\\n+\t\t--expire-unreachable=$(($test_tick - 10000)) \\\n+\t\t--stale-fix \\\n+\t\t--all &&\n+\n+\techo git reflog expire --verbose \\\n+\t\t--expire=$(($test_tick - 10000)) \\\n+\t\t--expire-unreachable=$(($test_tick - 10000)) \\\n+\t\t--stale-fix \\\n+\t\t--all &&\n+\n+\traw_reflog refs/heads/master >reflog &&\n+\ttest_when_finished rm -f reflog &&\n+\ttest_line_count = 2 reflog &&\n+\n+\tcheck_fsck \"dangling commit $K\"\n+'\n+\n+test_expect_success 'prune and fsck' '\n+\n+\tgit prune &&\n+\tcheck_fsck &&\n+\n+\tcheck_have A B C D E H L &&\n+\tcheck_dont_have F G I J K\n+\n+'\n+\n+test_expect_success 'recover and check' '\n+\n+\trecover $F &&\n+\tcheck_fsck \"dangling blob $F\"\n+\n+'\n+\n+test_expect_success 'delete' '\n+\techo 1 > C &&\n+\ttest_tick &&\n+\tgit commit -m rat C &&\n+\n+\techo 2 > C &&\n+\ttest_tick &&\n+\tgit commit -m ox C &&\n+\n+\techo 3 > C &&\n+\ttest_tick &&\n+\tgit commit -m tiger C &&\n+\n+\tHEAD_entry_count=$(git reflog | wc -l) &&\n+\tmaster_entry_count=$(git reflog show master | wc -l) &&\n+\n+\ttest $HEAD_entry_count = 5 &&\n+\ttest $master_entry_count = 5 &&\n+\n+\n+\tgit reflog delete master@{1} &&\n+\tgit reflog show master > output &&\n+\ttest $(($master_entry_count - 1)) = $(wc -l < output) &&\n+\ttest $HEAD_entry_count = $(git reflog | wc -l) &&\n+\t! grep ox < output &&\n+\n+\tmaster_entry_count=$(wc -l < output) &&\n+\n+\tgit reflog delete HEAD@{1} &&\n+\ttest $(($HEAD_entry_count -1)) = $(git reflog | wc -l) &&\n+\ttest $master_entry_count = $(git reflog show master | wc -l) &&\n+\n+\tHEAD_entry_count=$(git reflog | wc -l) &&\n+\n+\tgit reflog delete master@{07.04.2005.15:15:00.-0700} &&\n+\tgit reflog show master > output &&\n+\ttest $(($master_entry_count - 1)) = $(wc -l < output) &&\n+\t! grep dragon < output\n+\n+'\n+\n+test_expect_success 'rewind2' '\n+\n+\ttest_tick && git reset --hard HEAD~2 &&\n+\traw_reflog refs/heads/master >reflog &&\n+\ttest_when_finished rm -f reflog &&\n+\ttest_line_count = 4 reflog\n+'\n+\n+test_expect_success '--expire=never' '\n+\n+\tgit reflog expire --verbose \\\n+\t\t--expire=never \\\n+\t\t--expire-unreachable=never \\\n+\t\t--all &&\n+\traw_reflog refs/heads/master >reflog &&\n+\ttest_when_finished rm -f reflog &&\n+\ttest_line_count = 4 reflog\n+'\n+\n+test_expect_success 'gc.reflogexpire=never' '\n+\n+\tgit config gc.reflogexpire never &&\n+\tgit config gc.reflogexpireunreachable never &&\n+\tgit reflog expire --verbose --all &&\n+\traw_reflog refs/heads/master >reflog &&\n+\ttest_when_finished rm -f reflog &&\n+\ttest_line_count = 4 reflog\n+'\n+\n+test_expect_success 'gc.reflogexpire=false' '\n+\n+\tgit config gc.reflogexpire false &&\n+\tgit config gc.reflogexpireunreachable false &&\n+\tgit reflog expire --verbose --all &&\n+\traw_reflog refs/heads/master >reflog &&\n+\ttest_when_finished rm -f reflog &&\n+\ttest_line_count = 4 reflog &&\n+\n+\tgit config --unset gc.reflogexpire &&\n+\tgit config --unset gc.reflogexpireunreachable\n+\n+'\n+\n+test_expect_success 'checkout should not delete log for packed ref' '\n+\ttest $(git reflog master | wc -l) = 4 &&\n+\tgit branch foo &&\n+\tgit pack-refs --all &&\n+\tgit checkout foo &&\n+\ttest $(git reflog master | wc -l) = 4\n+'\n+\n+test_expect_success 'stale dirs do not cause d/f conflicts (reflogs on)' '\n+\ttest_when_finished \"git branch -d one || git branch -d one/two\" &&\n+\n+\tgit branch one/two master &&\n+\techo \"one/two@{0} branch: Created from master\" >expect &&\n+\tgit log -g --format=\"%gd %gs\" one/two >actual &&\n+\ttest_cmp expect actual &&\n+\tgit branch -d one/two &&\n+\n+\t# now logs/refs/heads/one is a stale directory, but\n+\t# we should move it out of the way to create \"one\" reflog\n+\tgit branch one master &&\n+\techo \"one@{0} branch: Created from master\" >expect &&\n+\tgit log -g --format=\"%gd %gs\" one >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'stale dirs do not cause d/f conflicts (reflogs off)' '\n+\ttest_when_finished \"git branch -d one || git branch -d one/two\" &&\n+\n+\tgit branch one/two master &&\n+\techo \"one/two@{0} branch: Created from master\" >expect &&\n+\tgit log -g --format=\"%gd %gs\" one/two >actual &&\n+\ttest_cmp expect actual &&\n+\tgit branch -d one/two &&\n+\n+\t# same as before, but we only create a reflog for \"one\" if\n+\t# it already exists, which it does not\n+\tgit -c core.logallrefupdates=false branch one master &&\n+\t: >expect &&\n+\tgit log -g --format=\"%gd %gs\" one >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+# Triggering the bug detected by this test requires a newline to fall\n+# exactly BUFSIZ-1 bytes from the end of the file. We don't know\n+# what that value is, since it's platform dependent. However, if\n+# we choose some value N, we also catch any D which divides N evenly\n+# (since we will read backwards in chunks of D). So we choose 8K,\n+# which catches glibc (with an 8K BUFSIZ) and *BSD (1K).\n+#\n+# Each line is 114 characters, so we need 75 to still have a few before the\n+# last 8K. The 89-character padding on the final entry lines up our\n+# newline exactly.\n+test_expect_success 'parsing reverse reflogs at BUFSIZ boundaries' '\n+\tgit checkout -b reflogskip &&\n+\tz38=00000000000000000000000000000000000000 &&\n+\tident=\"abc <xyz> 0000000001 +0000\" &&\n+\tfor i in $(test_seq 1 75); do\n+\t\tprintf \"$z38%02d $z38%02d %s\\t\" $i $(($i+1)) \"$ident\" &&\n+\t\tif test $i = 75; then\n+\t\t\tfor j in $(test_seq 1 89); do\n+\t\t\t\tprintf X\n+\t\t\tdone\n+\t\telse\n+\t\t\tprintf X\n+\t\tfi &&\n+\t\tprintf \"\\n\"\n+\tdone | append_reflog refs/heads/reflogskip &&\n+\tgit rev-parse reflogskip@{73} >actual &&\n+\techo ${z38}03 >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_done\ndiff --git a/t/test-lib.sh b/t/test-lib.sh\nindex 16c4d7b..c6ba541 100644\n--- a/t/test-lib.sh\n+++ b/t/test-lib.sh\n@@ -958,6 +958,7 @@ test -z \"$NO_PERL\" && test_set_prereq PERL\n test -z \"$NO_PYTHON\" && test_set_prereq PYTHON\n test -n \"$USE_LIBPCRE\" && test_set_prereq LIBPCRE\n test -z \"$NO_GETTEXT\" && test_set_prereq GETTEXT\n+test -n \"$USE_LIBLMDB\" && test_set_prereq LMDB\n \n # Can we rely on git's output in the C locale?\n if test -n \"$GETTEXT_POISON\"\n-- \n2.4.2.644.g97b850b-twtrsrc\n"},{"id":"271542","messageId":"1444688706.7234.1.camel@twopensource.com","threadId":"40543","inReplyTo":"1444686725-27660-21-git-send-email-dturner@twopensource.com","subject":"Re: [PATCH v3 19/44] refs-be-files.c: add a backend method structure with transaction functions","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-10-12T22:25:06Z","receivedAt":"2015-10-12T22:25:06Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"Please disregard this one; I rewrorded the commit message and forgot to\ndelete this patch.  \n\nOn Mon, 2015-10-12 at 17:51 -0400, David Turner wrote:\n> From: Ronnie Sahlberg <sahlberg@google.com>\n> \n> Add a ref structure for backend methods. Start by adding a method pointer\n> for the transaction commit function.\n> \n> Add a function set_refs_backend to switch between backends. The files\n> based backend is the default.\n> \n> Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>\n> Signed-off-by: David Turner <dturner@twopensource.com>\n> ---\n>  refs-be-files.c | 10 ++++++++--\n>  refs.c          | 30 ++++++++++++++++++++++++++++++\n>  refs.h          | 15 +++++++++++++++\n>  3 files changed, 53 insertions(+), 2 deletions(-)\n> \n> diff --git a/refs-be-files.c b/refs-be-files.c\n> index 1308955..3050f1d 100644\n> --- a/refs-be-files.c\n> +++ b/refs-be-files.c\n> @@ -3225,8 +3225,8 @@ static int ref_update_reject_duplicates(struct string_list *refnames,\n>  \treturn 0;\n>  }\n>  \n> -int ref_transaction_commit(struct ref_transaction *transaction,\n> -\t\t\t   struct strbuf *err)\n> +static int files_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> @@ -3612,3 +3612,9 @@ int reflog_expire(const char *refname, const unsigned char *sha1,\n>  \tunlock_ref(lock);\n>  \treturn -1;\n>  }\n> +\n> +struct ref_be refs_be_files = {\n> +\tNULL,\n> +\t\"files\",\n> +\tfiles_transaction_commit,\n> +};\n> diff --git a/refs.c b/refs.c\n> index 25ad3b5..f930fe0 100644\n> --- a/refs.c\n> +++ b/refs.c\n> @@ -4,6 +4,29 @@\n>  #include \"cache.h\"\n>  #include \"refs.h\"\n>  #include \"lockfile.h\"\n> +/*\n> + * We always have a files backend and it is the default.\n> + */\n> +struct ref_be *the_refs_backend = &refs_be_files;\n> +/*\n> + * List of all available backends\n> + */\n> +struct ref_be *refs_backends = &refs_be_files;\n> +\n> +/*\n> + * This function is used to switch to an alternate backend.\n> + */\n> +int set_refs_backend(const char *name)\n> +{\n> +\tstruct ref_be *be;\n> +\n> +\tfor (be = refs_backends; be; be = be->next)\n> +\t\tif (!strcmp(be->name, name)) {\n> +\t\t\tthe_refs_backend = be;\n> +\t\t\treturn 0;\n> +\t\t}\n> +\treturn 1;\n> +}\n>  \n>  static int is_per_worktree_ref(const char *refname)\n>  {\n> @@ -985,3 +1008,10 @@ int ref_transaction_verify(struct ref_transaction *transaction,\n>  \t\t\t\t      NULL, old_sha1,\n>  \t\t\t\t      flags, NULL, err);\n>  }\n> +\n> +/* backend functions */\n> +int ref_transaction_commit(struct ref_transaction *transaction,\n> +\t\t\t   struct strbuf *err)\n> +{\n> +\treturn the_refs_backend->transaction_commit(transaction, err);\n> +}\n> diff --git a/refs.h b/refs.h\n> index 4940ae9..419abf4 100644\n> --- a/refs.h\n> +++ b/refs.h\n> @@ -619,4 +619,19 @@ extern int reflog_expire(const char *refname, const unsigned char *sha1,\n>  \t\t\t reflog_expiry_cleanup_fn cleanup_fn,\n>  \t\t\t void *policy_cb_data);\n>  \n> +/* refs backends */\n> +typedef int ref_transaction_commit_fn(struct ref_transaction *transaction,\n> +\t\t\t\t      struct strbuf *err);\n> +typedef void ref_transaction_free_fn(struct ref_transaction *transaction);\n> +\n> +struct ref_be {\n> +\tstruct ref_be *next;\n> +\tconst char *name;\n> +\tref_transaction_commit_fn *transaction_commit;\n> +};\n> +\n> +\n> +extern struct ref_be refs_be_files;\n> +int set_refs_backend(const char *name);\n> +\n>  #endif /* REFS_H */\n"},{"id":"271549","messageId":"561C6C47.7090108@alum.mit.edu","threadId":"40543","inReplyTo":"1444686725-27660-2-git-send-email-dturner@twopensource.com","subject":"Re: [PATCH v3 01/44] refs.c: create a public version of verify_refname_available","fromName":"Michael Haggerty","fromEmail":"mhagger@alum.mit.edu","sentAt":"2015-10-13T02:28:23Z","receivedAt":"2015-10-13T02:28:23Z","isPatch":true,"sender":{"key":"mhagger@alum.mit.edu","avatar":"https://avatars.githubusercontent.com/u/119718?v=4"},"body":"On 10/12/2015 11:51 PM, David Turner wrote:\n> From: Ronnie Sahlberg <sahlberg@google.com>\n> \n> Create a public version of verify_refname_available that backends can\n> provide.\n> \n> Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>\n> Signed-off-by: David Turner <dturner@twopensource.com>\n> ---\n>  refs.c | 65 ++++++++++++++++++++++++++++++-----------------------------------\n>  refs.h | 17 +++++++++++++++++\n>  2 files changed, 47 insertions(+), 35 deletions(-)\n> \n> diff --git a/refs.c b/refs.c\n> index 132eff5..fe71ea0 100644\n> --- a/refs.c\n> +++ b/refs.c\n> @@ -279,7 +279,7 @@ struct ref_dir {\n>   * presence of an empty subdirectory does not block the creation of a\n>   * similarly-named reference.  (The fact that reference names with the\n>   * same leading components can conflict *with each other* is a\n> - * separate issue that is regulated by verify_refname_available().)\n> + * separate issue that is regulated by verify_refname_available_dir().)\n>   *\n>   * Please note that the name field contains the fully-qualified\n>   * reference (or subdirectory) name.  Space could be saved by only\n> @@ -897,25 +897,13 @@ static int nonmatching_ref_fn(struct ref_entry *entry, void *vdata)\n>  /*\n>   * Return 0 if a reference named refname could be created without\n>   * conflicting with the name of an existing reference in dir.\n> - * Otherwise, return a negative value and write an explanation to err.\n> - * If extras is non-NULL, it is a list of additional refnames with\n> - * which refname is not allowed to conflict. If skip is non-NULL,\n> - * ignore potential conflicts with refs in skip (e.g., because they\n> - * are scheduled for deletion in the same operation). Behavior is\n> - * undefined if the same name is listed in both extras and skip.\n> - *\n> - * Two reference names conflict if one of them exactly matches the\n> - * leading components of the other; e.g., \"refs/foo/bar\" conflicts\n> - * with both \"refs/foo\" and with \"refs/foo/bar/baz\" but not with\n> - * \"refs/foo/bar\" or \"refs/foo/barbados\".\n> - *\n> - * extras and skip must be sorted.\n> + * See verify_refname_available for details.\n>   */\n> -static int verify_refname_available(const char *refname,\n> -\t\t\t\t    const struct string_list *extras,\n> -\t\t\t\t    const struct string_list *skip,\n> -\t\t\t\t    struct ref_dir *dir,\n> -\t\t\t\t    struct strbuf *err)\n> +static int verify_refname_available_dir(const char *refname,\n> +\t\t\t\t\tconst struct string_list *extras,\n> +\t\t\t\t\tconst struct string_list *skip,\n> +\t\t\t\t\tstruct ref_dir *dir,\n> +\t\t\t\t\tstruct strbuf *err)\n>  {\n>  \tconst char *slash;\n>  \tint pos;\n> @@ -2464,9 +2452,12 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,\n>  \t\t */\n>  \t\tstrbuf_git_path(&orig_ref_file, \"%s\", orig_refname);\n>  \t\tif (remove_empty_directories(&orig_ref_file)) {\n> +\t\t\tstruct ref_dir *loose_refs;\n> +\t\t\tloose_refs = get_loose_refs(&ref_cache);\n\nIt's OK to initialize `loose_refs` on the line where it is declared.\n\n>  \t\t\tlast_errno = errno;\n> -\t\t\tif (!verify_refname_available(orig_refname, extras, skip,\n> -\t\t\t\t\t\t      get_loose_refs(&ref_cache), err))\n> +\t\t\tif (!verify_refname_available_dir(orig_refname, extras,\n> +\t\t\t\t\t\t\t  skip, loose_refs,\n> +\t\t\t\t\t\t\t  err))\n>  \t\t\t\tstrbuf_addf(err, \"there are still refs under '%s'\",\n>  \t\t\t\t\t    orig_refname);\n>  \t\t\tgoto error_return;\n> [...]\n> diff --git a/refs.h b/refs.h\n> index 6d30c98..79ea220 100644\n> --- a/refs.h\n> +++ b/refs.h\n> @@ -218,6 +218,23 @@ extern void warn_dangling_symrefs(FILE *fp, const char *msg_fmt, const struct st\n>  int pack_refs(unsigned int flags);\n>  \n>  /*\n> + * Return true iff a reference named refname could be created without\n> + * conflicting with the name of an existing reference.  If\n> + * skip is non-NULL, ignore potential conflicts with refs in skip\n> + * (e.g., because they are scheduled for deletion in the same\n> + * operation).\n> + *\n> + * Two reference names conflict if one of them exactly matches the\n> + * leading components of the other; e.g., \"foo/bar\" conflicts with\n> + * both \"foo\" and with \"foo/bar/baz\" but not with \"foo/bar\" or\n> + * \"foo/barbados\".\n> + *\n> + * skip must be sorted.\n> + */\n> +int verify_refname_available(const char *newname, struct string_list *extra,\n> +\t\t\t     struct string_list *skip, struct strbuf *err);\n\nThis function has an `extra` parameter but the description of this\nparameter disappeared as the docstring was moved from refs.c to here.\n\n> +\n> +/*\n>   * Flags controlling ref_transaction_update(), ref_transaction_create(), etc.\n>   * REF_NODEREF: act on the ref directly, instead of dereferencing\n>   *              symbolic references.\n> \n\nMichael\n\n-- \nMichael Haggerty\nmhagger@alum.mit.edu\n"},{"id":"271550","messageId":"561C6EE8.3000301@alum.mit.edu","threadId":"40543","inReplyTo":"1444686725-27660-3-git-send-email-dturner@twopensource.com","subject":"Re: [PATCH v3 02/44] refs: make repack_without_refs and is_branch public","fromName":"Michael Haggerty","fromEmail":"mhagger@alum.mit.edu","sentAt":"2015-10-13T02:39:36Z","receivedAt":"2015-10-13T02:39:36Z","isPatch":true,"sender":{"key":"mhagger@alum.mit.edu","avatar":"https://avatars.githubusercontent.com/u/119718?v=4"},"body":"On 10/12/2015 11:51 PM, David Turner wrote:\n> is_branch was already non-static, but this patch declares it in the\n> header.\n> \n> Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>\n> Signed-off-by: David Turner <dturner@twopensource.com>\n> ---\n> [...]\n\nIt seems odd that repack_without_refs() should be made public (and\nultimately end up in refs.h) given that it intrinsically only has to do\nwith file-based references. But I will read on...\n\nMichael\n\n-- \nMichael Haggerty\nmhagger@alum.mit.edu\n"},{"id":"271551","messageId":"561C7D7B.8020807@alum.mit.edu","threadId":"40543","inReplyTo":"1444686725-27660-6-git-send-email-dturner@twopensource.com","subject":"Re: [PATCH v3 05/44] refs.c: move update_ref to refs.c","fromName":"Michael Haggerty","fromEmail":"mhagger@alum.mit.edu","sentAt":"2015-10-13T03:41:47Z","receivedAt":"2015-10-13T03:41:47Z","isPatch":true,"sender":{"key":"mhagger@alum.mit.edu","avatar":"https://avatars.githubusercontent.com/u/119718?v=4"},"body":"On 10/12/2015 11:51 PM, David Turner wrote:\n> From: Ronnie Sahlberg <sahlberg@google.com>\n> \n> Move update_ref() to the refs.c file since this function does not\n> contain any backend specific code.  Move the ref classifier functions\n> as well, since update_ref depends on them.\n> \n> Based on Ronnie Sahlberg's patch\n> \n> Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>\n> Signed-off-by: David Turner <dturner@twopensource.com>\n> ---\n>  refs-be-files.c | 117 +-------------------------------------------------------\n>  refs.c          | 115 +++++++++++++++++++++++++++++++++++++++++++++++++++++++\n>  2 files changed, 116 insertions(+), 116 deletions(-)\n> \n> diff --git a/refs-be-files.c b/refs-be-files.c\n> index d0dfdfc..7fe4931 100644\n> --- a/refs-be-files.c\n> +++ b/refs-be-files.c\n> [...]\n> @@ -2879,77 +2877,6 @@ static int delete_ref_loose(struct ref_lock *lock, int flag, struct strbuf *err)\n> [...]\n> -static int write_pseudoref(const char *pseudoref, const unsigned char *sha1,\n> -\t\t\t   const unsigned char *old_sha1, struct strbuf *err)\n> -{\n> -\tconst char *filename;\n> -\tint fd;\n> -\tstatic struct lock_file lock;\n> -\tstruct strbuf buf = STRBUF_INIT;\n> -\tint ret = -1;\n> -\n> -\tstrbuf_addf(&buf, \"%s\\n\", sha1_to_hex(sha1));\n> -\n> -\tfilename = git_path(\"%s\", pseudoref);\n> -\tfd = hold_lock_file_for_update(&lock, filename, LOCK_DIE_ON_ERROR);\n> -\tif (fd < 0) {\n> -\t\tstrbuf_addf(err, \"Could not open '%s' for writing: %s\",\n> -\t\t\t    filename, strerror(errno));\n> -\t\treturn -1;\n> -\t}\n> -\n> -\tif (old_sha1) {\n> -\t\tunsigned char actual_old_sha1[20];\n> -\n> -\t\tif (read_ref(pseudoref, actual_old_sha1))\n> -\t\t\tdie(\"could not read ref '%s'\", pseudoref);\n> -\t\tif (hashcmp(actual_old_sha1, old_sha1)) {\n> -\t\t\tstrbuf_addf(err, \"Unexpected sha1 when writing %s\", pseudoref);\n> -\t\t\trollback_lock_file(&lock);\n> -\t\t\tgoto done;\n> -\t\t}\n> -\t}\n> -\n> -\tif (write_in_full(fd, buf.buf, buf.len) != buf.len) {\n> -\t\tstrbuf_addf(err, \"Could not write to '%s'\", filename);\n> -\t\trollback_lock_file(&lock);\n> -\t\tgoto done;\n> -\t}\n> -\n> -\tcommit_lock_file(&lock);\n> -\tret = 0;\n> -done:\n> -\tstrbuf_release(&buf);\n> -\treturn ret;\n> -}\n> -\n> [...]\n> diff --git a/refs.c b/refs.c\n> index 77492ff..2d10708 100644\n> --- a/refs.c\n> +++ b/refs.c\n> @@ -1,3 +1,118 @@\n> [...]\n> +static int write_pseudoref(const char *pseudoref, const unsigned char *sha1,\n> +\t\t\t   const unsigned char *old_sha1, struct strbuf *err)\n> +{\n> +\tconst char *filename;\n> +\tint fd;\n> +\tstatic struct lock_file lock;\n> +\tstruct strbuf buf = STRBUF_INIT;\n> +\tint ret = -1;\n> +\n> +\tstrbuf_addf(&buf, \"%s\\n\", sha1_to_hex(sha1));\n> +\n> +\tfilename = git_path(\"%s\", pseudoref);\n> +\tfd = hold_lock_file_for_update(&lock, filename, LOCK_DIE_ON_ERROR);\n> +\tif (fd < 0) {\n> +\t\tstrbuf_addf(err, \"Could not open '%s' for writing: %s\",\n> +\t\t\t    filename, strerror(errno));\n> +\t\treturn -1;\n> +\t}\n> +\n> +\tif (old_sha1) {\n> +\t\tunsigned char actual_old_sha1[20];\n> +\t\tread_ref(pseudoref, actual_old_sha1);\n\nThe original read\n\n\t\tif (read_ref(pseudoref, actual_old_sha1))\n\t\t\tdie(\"could not read ref '%s'\", pseudoref);\n\nThis seems like an important test. What happened to it?\n\nIf its removal was intentional, it deserves a careful explanation (and\nshould probably be done as a separate commit). If it was an accident,\nplease consider how this accident arose and try to think about whether\nsimilar accidents might have happened elsewhere in this series.\n\n> [...]\n\nMichael\n\n-- \nMichael Haggerty\nmhagger@alum.mit.edu\n"},{"id":"271552","messageId":"561C7FAE.5010500@alum.mit.edu","threadId":"40543","inReplyTo":"1444686725-27660-7-git-send-email-dturner@twopensource.com","subject":"Re: [PATCH v3 06/44] refs.c: move delete_ref and delete_refs to the common code","fromName":"Michael Haggerty","fromEmail":"mhagger@alum.mit.edu","sentAt":"2015-10-13T03:51:10Z","receivedAt":"2015-10-13T03:51:10Z","isPatch":true,"sender":{"key":"mhagger@alum.mit.edu","avatar":"https://avatars.githubusercontent.com/u/119718?v=4"},"body":"On 10/12/2015 11:51 PM, David Turner wrote:\n> From: Ronnie Sahlberg <sahlberg@google.com>\n> \n> Move delete_pseudoref, delete_ref() and delete_refs() to the refs.c\n> file since these functions do not contain any backend specific code.\n> \n> Based on a patch by Ronnie Sahlberg.\n> \n> Signed-off-by: David Turner <dturner@twopensource.com>\n> Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>\n> ---\n>  refs-be-files.c | 94 ---------------------------------------------------------\n>  refs.c          | 94 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n>  2 files changed, 94 insertions(+), 94 deletions(-)\n> \n> diff --git a/refs-be-files.c b/refs-be-files.c\n> index 7fe4931..099df75 100644\n> --- a/refs-be-files.c\n> +++ b/refs-be-files.c\n> @@ -2877,100 +2877,6 @@ static int delete_ref_loose(struct ref_lock *lock, int flag, struct strbuf *err)\n>  \treturn 0;\n>  }\n>  \n> -static int delete_pseudoref(const char *pseudoref, const unsigned char *old_sha1)\n> -{\n> -\tstatic struct lock_file lock;\n> -\tconst char *filename;\n> -\n> -\tfilename = git_path(\"%s\", pseudoref);\n> -\n> -\tif (old_sha1 && !is_null_sha1(old_sha1)) {\n> -\t\tint fd;\n> -\t\tunsigned char actual_old_sha1[20];\n> -\n> -\t\tfd = hold_lock_file_for_update(&lock, filename,\n> -\t\t\t\t\t       LOCK_DIE_ON_ERROR);\n> -\t\tif (fd < 0)\n> -\t\t\tdie_errno(_(\"Could not open '%s' for writing\"), filename);\n> -\t\tif (read_ref(pseudoref, actual_old_sha1))\n> -\t\t\tdie(\"could not read ref '%s'\", pseudoref);\n> -\t\tif (hashcmp(actual_old_sha1, old_sha1)) {\n> -\t\t\twarning(\"Unexpected sha1 when deleting %s\", pseudoref);\n> -\t\t\trollback_lock_file(&lock);\n> -\t\t\treturn -1;\n> -\t\t}\n> -\n> -\t\tunlink(filename);\n> -\t\trollback_lock_file(&lock);\n> -\t} else {\n> -\t\tunlink(filename);\n> -\t}\n> -\n> -\treturn 0;\n> -}\n> -\n> [...]\n> diff --git a/refs.c b/refs.c\n> index 2d10708..205a899 100644\n> --- a/refs.c\n> +++ b/refs.c\n> @@ -116,3 +116,97 @@ int update_ref(const char *msg, const char *refname,\n>  \t\tref_transaction_free(t);\n>  \treturn 0;\n>  }\n> +\n> +\n> +static int delete_pseudoref(const char *pseudoref, const unsigned char *old_sha1)\n> +{\n> +\tstatic struct lock_file lock;\n> +\tconst char *filename;\n> +\n> +\tfilename = git_path(\"%s\", pseudoref);\n> +\n> +\tif (old_sha1 && !is_null_sha1(old_sha1)) {\n> +\t\tint fd;\n> +\t\tunsigned char actual_old_sha1[20];\n> +\n> +\t\tfd = hold_lock_file_for_update(&lock, filename,\n> +\t\t\t\t\t       LOCK_DIE_ON_ERROR);\n> +\t\tif (fd < 0)\n> +\t\t\tdie_errno(_(\"Could not open '%s' for writing\"), filename);\n> +\t\tread_ref(pseudoref, actual_old_sha1);\n\nThe same test vanishes here as in the previous patch. The original was\n\n\t\tif (read_ref(pseudoref, actual_old_sha1))\n\t\t\tdie(\"could not read ref '%s'\", pseudoref);\n\n> +\t\tif (hashcmp(actual_old_sha1, old_sha1)) {\n> +\t\t\twarning(\"Unexpected sha1 when deleting %s\", pseudoref);\n> +\t\t\trollback_lock_file(&lock);\n> +\t\t\treturn -1;\n> +\t\t}\n> +\n> +\t\tunlink(filename);\n> +\t\trollback_lock_file(&lock);\n> +\t} else {\n> +\t\tunlink(filename);\n> +\t}\n> +\n> +\treturn 0;\n> +}\n> +\n> [...]\n\nMichael\n\n-- \nMichael Haggerty\nmhagger@alum.mit.edu\n"},{"id":"271553","messageId":"561C84BA.7000001@alum.mit.edu","threadId":"40543","inReplyTo":"1444686725-27660-17-git-send-email-dturner@twopensource.com","subject":"Re: [PATCH v3 16/44] refs.c: move ref iterators to the common code","fromName":"Michael Haggerty","fromEmail":"mhagger@alum.mit.edu","sentAt":"2015-10-13T04:12:42Z","receivedAt":"2015-10-13T04:12:42Z","isPatch":true,"sender":{"key":"mhagger@alum.mit.edu","avatar":"https://avatars.githubusercontent.com/u/119718?v=4"},"body":"On 10/12/2015 11:51 PM, David Turner wrote:\n> From: Ronnie Sahlberg <sahlberg@google.com>\n> \n> Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>\n> ---\n>  refs-be-files.c | 82 ---------------------------------------------------------\n>  refs.c          | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n>  2 files changed, 81 insertions(+), 82 deletions(-)\n> \n> diff --git a/refs-be-files.c b/refs-be-files.c\n> index ecf2b33..46fa43c 100644\n> --- a/refs-be-files.c\n> +++ b/refs-be-files.c\n> [...]\n> @@ -2012,40 +1964,6 @@ int for_each_namespaced_ref(each_ref_fn fn, void *cb_data)\n>  \treturn ret;\n>  }\n>  \n> -int for_each_glob_ref_in(each_ref_fn fn, const char *pattern,\n> -\tconst char *prefix, void *cb_data)\n> -{\n> -\tstruct strbuf real_pattern = STRBUF_INIT;\n> -\tstruct ref_filter filter;\n> -\tint ret;\n> -\n> -\tif (!prefix && !starts_with(pattern, \"refs/\"))\n> -\t\tstrbuf_addstr(&real_pattern, \"refs/\");\n> -\telse if (prefix)\n> -\t\tstrbuf_addstr(&real_pattern, prefix);\n> -\tstrbuf_addstr(&real_pattern, pattern);\n> -\n> -\tif (!has_glob_specials(pattern)) {\n> -\t\t/* Append implied '/' '*' if not present. */\n> -\t\tstrbuf_complete(&real_pattern, '/');\n> -\t\t/* No need to check for '*', there is none. */\n> -\t\tstrbuf_addch(&real_pattern, '*');\n> -\t}\n> -\n> -\tfilter.pattern = real_pattern.buf;\n> -\tfilter.fn = fn;\n> -\tfilter.cb_data = cb_data;\n> -\tret = for_each_ref(filter_refs, &filter);\n> -\n> -\tstrbuf_release(&real_pattern);\n> -\treturn ret;\n> -}\n> -\n> [...]\n> diff --git a/refs.c b/refs.c\n> index 44ee4f4..7714dad 100644\n> --- a/refs.c\n> +++ b/refs.c\n> @@ -781,3 +781,84 @@ const char *prettify_refname(const char *name)\n>  \t\tstarts_with(name, \"refs/remotes/\") ? 13 :\n>  \t\t0);\n>  }\n> [...]\n> +int for_each_glob_ref_in(each_ref_fn fn, const char *pattern,\n> +\tconst char *prefix, void *cb_data)\n> +{\n> +\tstruct strbuf real_pattern = STRBUF_INIT;\n> +\tstruct ref_filter filter;\n> +\tint ret;\n> +\n> +\tif (!prefix && !starts_with(pattern, \"refs/\"))\n> +\t\tstrbuf_addstr(&real_pattern, \"refs/\");\n> +\telse if (prefix)\n> +\t\tstrbuf_addstr(&real_pattern, prefix);\n> +\tstrbuf_addstr(&real_pattern, pattern);\n> +\n> +\tif (!has_glob_specials(pattern)) {\n> +\t\t/* Append implied '/' '*' if not present. */\n> +\t\tif (real_pattern.buf[real_pattern.len - 1] != '/')\n> +\t\t\tstrbuf_addch(&real_pattern, '/');\n\nThe original has\n\n\t\tstrbuf_complete(&real_pattern, '/');\n\nin place of the above two lines. I think you made a mistake when\nrebasing on top of\n\n    00b6c178c3 use strbuf_complete to conditionally append slash\n\n> +\t\t/* No need to check for '*', there is none. */\n> +\t\tstrbuf_addch(&real_pattern, '*');\n> +\t}\n> +\n> +\tfilter.pattern = real_pattern.buf;\n> +\tfilter.fn = fn;\n> +\tfilter.cb_data = cb_data;\n> +\tret = for_each_ref(filter_refs, &filter);\n> +\n> +\tstrbuf_release(&real_pattern);\n> +\treturn ret;\n> +}\n> +\n> [...]\n\nMichael\n\n-- \nMichael Haggerty\nmhagger@alum.mit.edu\n"},{"id":"271554","messageId":"561CA19D.4050404@alum.mit.edu","threadId":"40543","inReplyTo":"1444686725-27660-19-git-send-email-dturner@twopensource.com","subject":"Re: [PATCH v3 18/44] refs: move transaction functions into common code","fromName":"Michael Haggerty","fromEmail":"mhagger@alum.mit.edu","sentAt":"2015-10-13T06:15:57Z","receivedAt":"2015-10-13T06:15:57Z","isPatch":true,"sender":{"key":"mhagger@alum.mit.edu","avatar":"https://avatars.githubusercontent.com/u/119718?v=4"},"body":"On 10/12/2015 11:51 PM, David Turner wrote:\n> The common ref code will build up a ref transaction.  Backends will\n> then commit it.  So the transaction creation and update functions should\n> be in the common code.  We also need to move the ref structs into\n> the common code so that alternate backends can access them.\n> \n> Later, we will modify struct ref_update to support alternate backends.\n\nThis patch leaks internal implementation details into the public refs\ninterface. I want to make sure that code elsewhere in Git treats these\nstructures as opaque. That is why I suggested creating an extra module\nto hold \"protected\" code for the reference backend \"class hierarchy\" [1].\n\nFor a sketch of what my suggestion would look like, see the two commits\nat the tip of branch \"refs-be-common\" on my GitHub repo [2].\n\nMichael\n\n[1] http://article.gmane.org/gmane.comp.version-control.git/279049\n[2] https://github.com/mhagger/git\n\n-- \nMichael Haggerty\nmhagger@alum.mit.edu\n"},{"id":"271557","messageId":"561CB188.2090508@alum.mit.edu","threadId":"40543","inReplyTo":"561C6EE8.3000301@alum.mit.edu","subject":"Re: [PATCH v3 02/44] refs: make repack_without_refs and is_branch public","fromName":"Michael Haggerty","fromEmail":"mhagger@alum.mit.edu","sentAt":"2015-10-13T07:23:52Z","receivedAt":"2015-10-13T07:23:52Z","isPatch":true,"sender":{"key":"mhagger@alum.mit.edu","avatar":"https://avatars.githubusercontent.com/u/119718?v=4"},"body":"On 10/13/2015 04:39 AM, Michael Haggerty wrote:\n> On 10/12/2015 11:51 PM, David Turner wrote:\n>> is_branch was already non-static, but this patch declares it in the\n>> header.\n>>\n>> Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>\n>> Signed-off-by: David Turner <dturner@twopensource.com>\n>> ---\n>> [...]\n> \n> It seems odd that repack_without_refs() should be made public (and\n> ultimately end up in refs.h) given that it intrinsically only has to do\n> with file-based references. But I will read on...\n\nI think the reason you needed to do this was because you wanted to move\ndelete_refs() to the common code. It is true that delete_ref() can be\nmoved to the common code. And most of the code in delete_refs() is just\na loop calling delete_ref(). But delete_refs() also does the very\nfiles-specific optimization of calling repack_without_refs() before the\nloop. *That* call shouldn't be in the common code.\n\nSo my suggestion is that you write a common_delete_refs() function that\nonly includes the loop over delete_ref(), and a files_delete_refs()\nfunction that is basically\n\n    {\n            result = repack_without_refs(refnames, &err);\n            if (result) {\n                    ...report error...\n                    return result;\n            }\n            return common_delete_refs(...);\n    }\n\nMichael\n\n-- \nMichael Haggerty\nmhagger@alum.mit.edu\n"},{"id":"271559","messageId":"561CB3C4.1010208@alum.mit.edu","threadId":"40543","inReplyTo":"1444686725-27660-27-git-send-email-dturner@twopensource.com","subject":"Re: [PATCH v3 25/44] refs.h: document make refname_is_safe and add it to header","fromName":"Michael Haggerty","fromEmail":"mhagger@alum.mit.edu","sentAt":"2015-10-13T07:33:24Z","receivedAt":"2015-10-13T07:33:24Z","isPatch":true,"sender":{"key":"mhagger@alum.mit.edu","avatar":"https://avatars.githubusercontent.com/u/119718?v=4"},"body":"On 10/12/2015 11:51 PM, David Turner wrote:\n> This function might be used by other refs backends\n> \n> Signed-off-by: David Turner <dturner@twopensource.com>\n> ---\n>  refs.h | 11 +++++++++++\n>  1 file changed, 11 insertions(+)\n> \n> diff --git a/refs.h b/refs.h\n> index fc8a748..7a936e2 100644\n> --- a/refs.h\n> +++ b/refs.h\n> @@ -348,6 +348,17 @@ int verify_refname_available(const char *newname, struct string_list *extra,\n>  \t\t\t     struct string_list *skip, struct strbuf *err);\n>  \n>  /*\n> + * Check if a refname is safe.\n> + * For refs that start with \"refs/\" we consider it safe as long they do\n> + * not try to resolve to outside of refs/.\n> + *\n> + * For all other refs we only consider them safe iff they only contain\n> + * upper case characters and '_' (like \"HEAD\" AND \"MERGE_HEAD\", and not like\n> + * \"config\").\n> + */\n> +int refname_is_safe(const char *refname);\n> +\n> +/*\n>   * Flags controlling ref_transaction_update(), ref_transaction_create(), etc.\n>   * REF_NODEREF: act on the ref directly, instead of dereferencing\n>   *              symbolic references.\n> \n\nThe previous commit deleted this comment from where it previously\nappeared in refs-be-files.c. It would make more sense to squash this\ncommit onto that one so it's clear that you are moving the comment\nrather than creating a new comment out of thin air.\n\nMichael\n\n-- \nMichael Haggerty\nmhagger@alum.mit.edu\n"},{"id":"271561","messageId":"561CB65C.4040902@alum.mit.edu","threadId":"40543","inReplyTo":"1444686725-27660-22-git-send-email-dturner@twopensource.com","subject":"Re: [PATCH v3 20/44] refs-be-files.c: add methods for misc ref operations","fromName":"Michael Haggerty","fromEmail":"mhagger@alum.mit.edu","sentAt":"2015-10-13T07:44:28Z","receivedAt":"2015-10-13T07:44:28Z","isPatch":true,"sender":{"key":"mhagger@alum.mit.edu","avatar":"https://avatars.githubusercontent.com/u/119718?v=4"},"body":"On 10/12/2015 11:51 PM, David Turner wrote:\n> From: Ronnie Sahlberg <sahlberg@google.com>\n> \n> Add ref backend methods for:\n> resolve_ref_unsafe, verify_refname_available, pack_refs, peel_ref,\n> create_symref, resolve_gitlink_ref.\n> \n> Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>\n> Signed-off-by: David Turner <dturner@twitter.com>\n> ---\n>  builtin/init-db.c |  1 +\n>  cache.h           |  7 +++++++\n>  refs-be-files.c   | 36 +++++++++++++++++++++++++++---------\n>  refs.c            | 47 +++++++++++++++++++++++++++++++++++++++++++++++\n>  refs.h            | 18 ++++++++++++++++++\n>  5 files changed, 100 insertions(+), 9 deletions(-)\n\nThis commit doesn't build:\n\nrefs-be-files.c:3636:2: error: initialization from incompatible pointer\ntype [-Werror]\n  files_create_symref,\n  ^\nrefs-be-files.c:3636:2: error: (near initialization for\n‘refs_be_files.create_symref’) [-Werror]\n\nApparently one of the hunks from the subsequent commit is needed in this\ncommit.\n\nMichael\n\n-- \nMichael Haggerty\nmhagger@alum.mit.edu\n"},{"id":"271562","messageId":"561CB919.8000801@alum.mit.edu","threadId":"40543","inReplyTo":"1444686725-27660-21-git-send-email-dturner@twopensource.com","subject":"Re: [PATCH v3 19/44] refs-be-files.c: add a backend method structure with transaction functions","fromName":"Michael Haggerty","fromEmail":"mhagger@alum.mit.edu","sentAt":"2015-10-13T07:56:09Z","receivedAt":"2015-10-13T07:56:09Z","isPatch":true,"sender":{"key":"mhagger@alum.mit.edu","avatar":"https://avatars.githubusercontent.com/u/119718?v=4"},"body":"On 10/12/2015 11:51 PM, David Turner wrote:\n> From: Ronnie Sahlberg <sahlberg@google.com>\n> \n> Add a ref structure for backend methods. Start by adding a method pointer\n> for the transaction commit function.\n> \n> Add a function set_refs_backend to switch between backends. The files\n> based backend is the default.\n> \n> Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>\n> Signed-off-by: David Turner <dturner@twopensource.com>\n> ---\n>  refs-be-files.c | 10 ++++++++--\n>  refs.c          | 30 ++++++++++++++++++++++++++++++\n>  refs.h          | 15 +++++++++++++++\n>  3 files changed, 53 insertions(+), 2 deletions(-)\n> \n> [...]\n> diff --git a/refs.h b/refs.h\n> index 4940ae9..419abf4 100644\n> --- a/refs.h\n> +++ b/refs.h\n> @@ -619,4 +619,19 @@ extern int reflog_expire(const char *refname, const unsigned char *sha1,\n>  \t\t\t reflog_expiry_cleanup_fn cleanup_fn,\n>  \t\t\t void *policy_cb_data);\n>  \n> +/* refs backends */\n> +typedef int ref_transaction_commit_fn(struct ref_transaction *transaction,\n> +\t\t\t\t      struct strbuf *err);\n> +typedef void ref_transaction_free_fn(struct ref_transaction *transaction);\n\nThe ref_transaction_free_fn typedef isn't used anywhere.\n\n> +struct ref_be {\n> +\tstruct ref_be *next;\n> +\tconst char *name;\n> +\tref_transaction_commit_fn *transaction_commit;\n> +};\n> +\n> +\n> +extern struct ref_be refs_be_files;\n\nI don't think that refs_be_files is needed in the public interface.\n\n> +int set_refs_backend(const char *name);\n> +\n>  #endif /* REFS_H */\n\nMichael\n\n-- \nMichael Haggerty\nmhagger@alum.mit.edu\n"},{"id":"271564","messageId":"561CCC63.8050604@alum.mit.edu","threadId":"40543","inReplyTo":"561CB3C4.1010208@alum.mit.edu","subject":"Re: [PATCH v3 25/44] refs.h: document make refname_is_safe and add it to header","fromName":"Michael Haggerty","fromEmail":"mhagger@alum.mit.edu","sentAt":"2015-10-13T09:18:27Z","receivedAt":"2015-10-13T09:18:27Z","isPatch":true,"sender":{"key":"mhagger@alum.mit.edu","avatar":"https://avatars.githubusercontent.com/u/119718?v=4"},"body":"On 10/13/2015 09:33 AM, Michael Haggerty wrote:\n> On 10/12/2015 11:51 PM, David Turner wrote:\n>> This function might be used by other refs backends\n>>\n>> Signed-off-by: David Turner <dturner@twopensource.com>\n>> ---\n>>  refs.h | 11 +++++++++++\n>>  1 file changed, 11 insertions(+)\n>>\n>> diff --git a/refs.h b/refs.h\n>> index fc8a748..7a936e2 100644\n>> --- a/refs.h\n>> +++ b/refs.h\n>> @@ -348,6 +348,17 @@ int verify_refname_available(const char *newname, struct string_list *extra,\n>>  \t\t\t     struct string_list *skip, struct strbuf *err);\n>>  \n>>  /*\n>> + * Check if a refname is safe.\n>> + * For refs that start with \"refs/\" we consider it safe as long they do\n>> + * not try to resolve to outside of refs/.\n>> + *\n>> + * For all other refs we only consider them safe iff they only contain\n>> + * upper case characters and '_' (like \"HEAD\" AND \"MERGE_HEAD\", and not like\n>> + * \"config\").\n>> + */\n>> +int refname_is_safe(const char *refname);\n>> +\n>> +/*\n>>   * Flags controlling ref_transaction_update(), ref_transaction_create(), etc.\n>>   * REF_NODEREF: act on the ref directly, instead of dereferencing\n>>   *              symbolic references.\n>>\n> \n> The previous commit deleted this comment from where it previously\n> appeared in refs-be-files.c. It would make more sense to squash this\n> commit onto that one so it's clear that you are moving the comment\n> rather than creating a new comment out of thin air.\n\nAlso, after this commit the prototype for this function appears twice in\nrefs.h.\n\nMichael\n\n-- \nMichael Haggerty\nmhagger@alum.mit.edu\n"},{"id":"271565","messageId":"561CCDFD.7040103@alum.mit.edu","threadId":"40543","inReplyTo":"1444686725-27660-28-git-send-email-dturner@twopensource.com","subject":"Re: [PATCH v3 26/44] refs.c: move copy_msg to the common code","fromName":"Michael Haggerty","fromEmail":"mhagger@alum.mit.edu","sentAt":"2015-10-13T09:25:17Z","receivedAt":"2015-10-13T09:25:17Z","isPatch":true,"sender":{"key":"mhagger@alum.mit.edu","avatar":"https://avatars.githubusercontent.com/u/119718?v=4"},"body":"On 10/12/2015 11:51 PM, David Turner wrote:\n> Rename copy_msg to copy_reflog_msg and make it public.\n> \n> Signed-off-by: David Turner <dturner@twopensource.com>\n> ---\n>  refs-be-files.c | 28 +---------------------------\n>  refs.c          | 26 ++++++++++++++++++++++++++\n>  refs.h          |  2 ++\n>  3 files changed, 29 insertions(+), 27 deletions(-)\n> \n> [...]\n> diff --git a/refs.c b/refs.c\n> index 2515f6e..bd8c71b 100644\n> --- a/refs.c\n> +++ b/refs.c\n> @@ -886,6 +886,32 @@ int for_each_remote_ref_submodule(const char *submodule, each_ref_fn fn, void *c\n>  \treturn for_each_ref_in_submodule(submodule, \"refs/remotes/\", fn, cb_data);\n>  }\n>  \n> +/*\n> + * copy the reflog message msg to buf, which has been allocated sufficiently\n> + * large, while cleaning up the whitespaces.  Especially, convert LF to space,\n> + * because reflog file is one line per entry.\n> + */\n\nPlease put the docstring by the declaration in the header file.\n\n> [...]\n> diff --git a/refs.h b/refs.h\n> index 7a936e2..3da5d09 100644\n> --- a/refs.h\n> +++ b/refs.h\n> @@ -597,6 +597,8 @@ enum ref_type {\n>  \n>  enum ref_type ref_type(const char *refname);\n>  \n> +int copy_reflog_msg(char *buf, const char *msg);\n> +\n>  enum expire_reflog_flags {\n>  \tEXPIRE_REFLOGS_DRY_RUN = 1 << 0,\n>  \tEXPIRE_REFLOGS_UPDATE_REF = 1 << 1,\n> \n\nMichael\n\n-- \nMichael Haggerty\nmhagger@alum.mit.edu\n"},{"id":"271566","messageId":"561CD03B.2080506@alum.mit.edu","threadId":"40543","inReplyTo":"1444686725-27660-29-git-send-email-dturner@twopensource.com","subject":"Re: [PATCH v3 27/44] refs.c: move peel_object to the common code","fromName":"Michael Haggerty","fromEmail":"mhagger@alum.mit.edu","sentAt":"2015-10-13T09:34:51Z","receivedAt":"2015-10-13T09:34:51Z","isPatch":true,"sender":{"key":"mhagger@alum.mit.edu","avatar":"https://avatars.githubusercontent.com/u/119718?v=4"},"body":"On 10/12/2015 11:51 PM, David Turner wrote:\n> This function does not contain any backend specific code so we\n> move it to the common code.\n> \n> Signed-off-by: David Turner <dturner@twopensource.com>\n> ---\n>  refs-be-files.c | 53 -----------------------------------------------------\n>  refs.c          | 31 +++++++++++++++++++++++++++++++\n>  refs.h          | 27 +++++++++++++++++++++++++++\n>  3 files changed, 58 insertions(+), 53 deletions(-)\n> \n> [...]\n> diff --git a/refs.c b/refs.c\n> index bd8c71b..99b31f6 100644\n> --- a/refs.c\n> +++ b/refs.c\n> @@ -4,6 +4,9 @@\n>  #include \"cache.h\"\n>  #include \"refs.h\"\n>  #include \"lockfile.h\"\n> +#include \"object.h\"\n> +#include \"tag.h\"\n> +\n>  /*\n>   * We always have a files backend and it is the default.\n>   */\n> @@ -1059,6 +1062,34 @@ int refname_is_safe(const char *refname)\n>  \treturn 1;\n>  }\n>  \n> +/*\n> + * Peel the named object; i.e., if the object is a tag, resolve the\n> + * tag recursively until a non-tag is found.  If successful, store the\n> + * result to sha1 and return PEEL_PEELED.  If the object is not a tag\n> + * or is not valid, return PEEL_NON_TAG or PEEL_INVALID, respectively,\n> + * and leave sha1 unchanged.\n> + */\n\nPlease move the docstring to the header file.\n\n> [...]\n> diff --git a/refs.h b/refs.h\n> index 3da5d09..636f959 100644\n> --- a/refs.h\n> +++ b/refs.h\n> @@ -76,6 +76,33 @@ extern int is_branch(const char *refname);\n>   */\n>  extern int peel_ref(const char *refname, unsigned char *sha1);\n>  \n> +enum peel_status {\n> +\t/* object was peeled successfully: */\n> +\tPEEL_PEELED = 0,\n> +\n> +\t/*\n> +\t * object cannot be peeled because the named object (or an\n> +\t * object referred to by a tag in the peel chain), does not\n> +\t * exist.\n> +\t */\n> +\tPEEL_INVALID = -1,\n> +\n> +\t/* object cannot be peeled because it is not a tag: */\n> +\tPEEL_NON_TAG = -2,\n> +\n> +\t/* ref_entry contains no peeled value because it is a symref: */\n> +\tPEEL_IS_SYMREF = -3,\n> +\n> +\t/*\n> +\t * ref_entry cannot be peeled because it is broken (i.e., the\n> +\t * symbolic reference cannot even be resolved to an object\n> +\t * name):\n> +\t */\n> +\tPEEL_BROKEN = -4\n> +};\n> +\n> +enum peel_status peel_object(const unsigned char *name, 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> \n\nMichael\n\n-- \nMichael Haggerty\nmhagger@alum.mit.edu\n"},{"id":"271568","messageId":"561CDC2E.8060804@alum.mit.edu","threadId":"40543","inReplyTo":"1444686725-27660-39-git-send-email-dturner@twopensource.com","subject":"Re: [PATCH v3 37/44] refs: break out a ref conflict check","fromName":"Michael Haggerty","fromEmail":"mhagger@alum.mit.edu","sentAt":"2015-10-13T10:25:50Z","receivedAt":"2015-10-13T10:25:50Z","isPatch":true,"sender":{"key":"mhagger@alum.mit.edu","avatar":"https://avatars.githubusercontent.com/u/119718?v=4"},"body":"On 10/12/2015 11:51 PM, David Turner wrote:\n> Create new function verify_no_descendants, to hold one of the ref\n> conflict checks used in verify_refname_available.  Multiple backends\n> will need this function, so it goes in the common code.\n> \n> Signed-off-by: David Turner <dturner@twopensource.com>\n> ---\n>  refs-be-files.c | 33 ++++++++-------------------------\n>  refs.c          | 22 ++++++++++++++++++++++\n>  refs.h          |  7 +++++++\n>  3 files changed, 37 insertions(+), 25 deletions(-)\n> \n> [...]\n> diff --git a/refs.c b/refs.c\n> index 5a3125d..3ae0274 100644\n> --- a/refs.c\n> +++ b/refs.c\n> @@ -1101,6 +1101,28 @@ enum peel_status peel_object(const unsigned char *name, unsigned char *sha1)\n>  \treturn PEEL_PEELED;\n>  }\n>  \n> +const char *find_descendant_ref(const char *refname,\n> +\t\t\t\tconst struct string_list *extras,\n> +\t\t\t\tconst struct string_list *skip)\n> +{\n> +\tint pos;\n> +\tif (!extras)\n> +\t\treturn NULL;\n> +\n> +\t/* Look for the place where \"$refname/\" would be inserted in extras. */\n\nThe comment above is misleading. See my note at the bottom of this email\nfor an explanation.\n\n> +\tfor (pos = string_list_find_insert_index(extras, refname, 0);\n> +\t     pos < extras->nr; pos++) {\n> +\t\tconst char *extra_refname = extras->items[pos].string;\n> +\n> +\t\tif (!starts_with(extra_refname, refname))\n> +\t\t\tbreak;\n> +\n> +\t\tif (!skip || !string_list_has_string(skip, extra_refname))\n> +\t\t\treturn extra_refname;\n> +\t}\n> +\treturn NULL;\n> +}\n> +\n>  /* backend functions */\n>  int refs_initdb(struct strbuf *err, int shared)\n>  {\n> diff --git a/refs.h b/refs.h\n> index 3aad3b8..f8becea 100644\n> --- a/refs.h\n> +++ b/refs.h\n> @@ -637,6 +637,13 @@ int files_log_ref_write(const char *refname, const unsigned char *old_sha1,\n>  \t\t\tconst unsigned char *new_sha1, const char *msg,\n>  \t\t\tint flags, struct strbuf *err);\n>  \n> +/*\n> + * Check for entries in extras that start with \"$refname/\", ignoring\n> + * those in skip. If there is such an entry, then we have a conflict.\n> + */\n> +const char *find_descendant_ref(const char *refname,\n> +\t\t\t\tconst struct string_list *extras,\n> +\t\t\t\tconst struct string_list *skip);\n\nThe documentation is is not correct. As written, the function needs not\nthe refname as argument but the refname followed by '/'. Without the\ntrailing slash, \"refs/heads/foo\" would be blocked by \"refs/heads/foobar\".\n\n>From the point of view of simplicity, it would be nice if this function\ncould take a refname (without the trailing slash) as argument. But then\nit would basically be forced to allocate a new string, copy refname to\nit, append a '/', then free the string again when it's done.\nAlternatively, it could accept its refname argument in a strbuf and\ntemporarily append the '/'.\n\nBut neither one of these alternatives is so elegant, so maybe it's OK if\nthis function is documented to take a \"directory name, including the\ntrailing '/'\" as argument and rename the argument (e.g., to \"dirname\").\n\nPlease also document that \"skip\" and \"extras\" can be NULL, but if not\nNULL they need to be sorted lists.\n\n> [...]\n\nMichael\n\n-- \nMichael Haggerty\nmhagger@alum.mit.edu\n"},{"id":"271570","messageId":"561CEB28.3090907@alum.mit.edu","threadId":"40543","inReplyTo":"1444686725-27660-19-git-send-email-dturner@twopensource.com","subject":"Re: [PATCH v3 18/44] refs: move transaction functions into common code","fromName":"Michael Haggerty","fromEmail":"mhagger@alum.mit.edu","sentAt":"2015-10-13T11:29:44Z","receivedAt":"2015-10-13T11:29:44Z","isPatch":true,"sender":{"key":"mhagger@alum.mit.edu","avatar":"https://avatars.githubusercontent.com/u/119718?v=4"},"body":"I reviewed the patches up to here pretty carefully, and aside from the\ncomments I already sent, they look good.\n\nI like the new approach where the ref_transaction-building code is\nshared across backends.\n\nIt seems to me that a good breaking point for the first batch of patches\nwould be here, just before you start creating the VTABLE in commit\n[19/44]. The patches before this point all have to do with moving code\naround and a little bit of light refactoring. They cause a lot of code\nchurn that will soon conflict with other people's work (e.g., [1]), but\nI think they are pretty uncontroversial.\n\nAfter this you start making a few important design decisions that\n*could* be controversial. Therefore, by making a cut, you can maximize\nthe chance that the earlier patches can be merged to master relatively\nquickly, after which the cross section for future conflicts will be much\nsmaller.\n\nIdeally, you would include a few later patches in the \"pre-VTABLE\" patch\nseries. It looks like the following patches also mostly have to do with\nmoving code around, and would fit logically with the \"pre-VTABLE\" changes:\n\n[24] refs.c: move refname_is_safe to the common code\n[25] refs.h: document make refname_is_safe and add it to header\n[26] refs.c: move copy_msg to the common code\n[27] refs.c: move peel_object to the common code\n[28] refs.c: move should_autocreate_reflog to common code\n[32] initdb: move safe_create_dir into common code\n[36] refs: make files_log_ref_write functions public\n[37] refs: break out a ref conflict check\n\nI tried rebasing those commits on top of your patch 18 and it wasn't too\nbad. The result is on branch \"refs-backend-pre-vtable\" on my GitHub repo\n[2], including my suggested changes to those eight patches (which\ntherefore became seven because I squashed the first two together).\n\nMichael\n\n[1] http://article.gmane.org/gmane.comp.version-control.git/279286\n[2] https://github.com/mhagger/git\n\n-- \nMichael Haggerty\nmhagger@alum.mit.edu\n"},{"id":"271584","messageId":"1444760911.7234.6.camel@twopensource.com","threadId":"40543","inReplyTo":"561CB919.8000801@alum.mit.edu","subject":"Re: [PATCH v3 19/44] refs-be-files.c: add a backend method structure with transaction functions","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-10-13T18:28:31Z","receivedAt":"2015-10-13T18:28:31Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"On Tue, 2015-10-13 at 09:56 +0200, Michael Haggerty wrote:\n> On 10/12/2015 11:51 PM, David Turner wrote:\n> > From: Ronnie Sahlberg <sahlberg@google.com>\n> > \n> > Add a ref structure for backend methods. Start by adding a method pointer\n> > for the transaction commit function.\n> > \n> > Add a function set_refs_backend to switch between backends. The files\n> > based backend is the default.\n> > \n> > Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>\n> > Signed-off-by: David Turner <dturner@twopensource.com>\n> > ---\n> >  refs-be-files.c | 10 ++++++++--\n> >  refs.c          | 30 ++++++++++++++++++++++++++++++\n> >  refs.h          | 15 +++++++++++++++\n> >  3 files changed, 53 insertions(+), 2 deletions(-)\n> > \n> > [...]\n> > diff --git a/refs.h b/refs.h\n> > index 4940ae9..419abf4 100644\n> > --- a/refs.h\n> > +++ b/refs.h\n> > @@ -619,4 +619,19 @@ extern int reflog_expire(const char *refname, const unsigned char *sha1,\n> >  \t\t\t reflog_expiry_cleanup_fn cleanup_fn,\n> >  \t\t\t void *policy_cb_data);\n> >  \n> > +/* refs backends */\n> > +typedef int ref_transaction_commit_fn(struct ref_transaction *transaction,\n> > +\t\t\t\t      struct strbuf *err);\n> > +typedef void ref_transaction_free_fn(struct ref_transaction *transaction);\n> \n> The ref_transaction_free_fn typedef isn't used anywhere.\n\nWill fix, thanks.\n\n> > +struct ref_be {\n> > +\tstruct ref_be *next;\n> > +\tconst char *name;\n> > +\tref_transaction_commit_fn *transaction_commit;\n> > +};\n> > +\n> > +\n> > +extern struct ref_be refs_be_files;\n> \n> I don't think that refs_be_files is needed in the public interface.\n\nWe use refs_be_lmdb in a few other places to set up the lmdb backend, so\nI thought I would put refs_be_files in refs.h too.  But I can remove\nrefs_be_files and just stick it in the places it's needed.\n"},{"id":"271585","messageId":"1444761161.7234.7.camel@twopensource.com","threadId":"40543","inReplyTo":"561CCC63.8050604@alum.mit.edu","subject":"Re: [PATCH v3 25/44] refs.h: document make refname_is_safe and add it to header","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-10-13T18:32:41Z","receivedAt":"2015-10-13T18:32:41Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"On Tue, 2015-10-13 at 11:18 +0200, Michael Haggerty wrote:\n> On 10/13/2015 09:33 AM, Michael Haggerty wrote:\n> > On 10/12/2015 11:51 PM, David Turner wrote:\n> >> This function might be used by other refs backends\n> >>\n> >> Signed-off-by: David Turner <dturner@twopensource.com>\n> >> ---\n> >>  refs.h | 11 +++++++++++\n> >>  1 file changed, 11 insertions(+)\n> >>\n> >> diff --git a/refs.h b/refs.h\n> >> index fc8a748..7a936e2 100644\n> >> --- a/refs.h\n> >> +++ b/refs.h\n> >> @@ -348,6 +348,17 @@ int verify_refname_available(const char *newname, struct string_list *extra,\n> >>  \t\t\t     struct string_list *skip, struct strbuf *err);\n> >>  \n> >>  /*\n> >> + * Check if a refname is safe.\n> >> + * For refs that start with \"refs/\" we consider it safe as long they do\n> >> + * not try to resolve to outside of refs/.\n> >> + *\n> >> + * For all other refs we only consider them safe iff they only contain\n> >> + * upper case characters and '_' (like \"HEAD\" AND \"MERGE_HEAD\", and not like\n> >> + * \"config\").\n> >> + */\n> >> +int refname_is_safe(const char *refname);\n> >> +\n> >> +/*\n> >>   * Flags controlling ref_transaction_update(), ref_transaction_create(), etc.\n> >>   * REF_NODEREF: act on the ref directly, instead of dereferencing\n> >>   *              symbolic references.\n> >>\n> > \n> > The previous commit deleted this comment from where it previously\n> > appeared in refs-be-files.c. It would make more sense to squash this\n> > commit onto that one so it's clear that you are moving the comment\n> > rather than creating a new comment out of thin air.\n> \n> Also, after this commit the prototype for this function appears twice in\n> refs.h.\n\nWill squash and fix.\n"},{"id":"271586","messageId":"1444761991.7234.8.camel@twopensource.com","threadId":"40543","inReplyTo":"561CDC2E.8060804@alum.mit.edu","subject":"Re: [PATCH v3 37/44] refs: break out a ref conflict check","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-10-13T18:46:31Z","receivedAt":"2015-10-13T18:46:31Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"On Tue, 2015-10-13 at 12:25 +0200, Michael Haggerty wrote:\n> On 10/12/2015 11:51 PM, David Turner wrote:\n> > Create new function verify_no_descendants, to hold one of the ref\n> > conflict checks used in verify_refname_available.  Multiple backends\n> > will need this function, so it goes in the common code.\n> > \n> > Signed-off-by: David Turner <dturner@twopensource.com>\n> > ---\n> >  refs-be-files.c | 33 ++++++++-------------------------\n> >  refs.c          | 22 ++++++++++++++++++++++\n> >  refs.h          |  7 +++++++\n> >  3 files changed, 37 insertions(+), 25 deletions(-)\n> > \n> > [...]\n> > diff --git a/refs.c b/refs.c\n> > index 5a3125d..3ae0274 100644\n> > --- a/refs.c\n> > +++ b/refs.c\n> > @@ -1101,6 +1101,28 @@ enum peel_status peel_object(const unsigned char *name, unsigned char *sha1)\n> >  \treturn PEEL_PEELED;\n> >  }\n> >  \n> > +const char *find_descendant_ref(const char *refname,\n> > +\t\t\t\tconst struct string_list *extras,\n> > +\t\t\t\tconst struct string_list *skip)\n> > +{\n> > +\tint pos;\n> > +\tif (!extras)\n> > +\t\treturn NULL;\n> > +\n> > +\t/* Look for the place where \"$refname/\" would be inserted in extras. */\n> \n> The comment above is misleading. See my note at the bottom of this email\n> for an explanation.\n> \n> > +\tfor (pos = string_list_find_insert_index(extras, refname, 0);\n> > +\t     pos < extras->nr; pos++) {\n> > +\t\tconst char *extra_refname = extras->items[pos].string;\n> > +\n> > +\t\tif (!starts_with(extra_refname, refname))\n> > +\t\t\tbreak;\n> > +\n> > +\t\tif (!skip || !string_list_has_string(skip, extra_refname))\n> > +\t\t\treturn extra_refname;\n> > +\t}\n> > +\treturn NULL;\n> > +}\n> > +\n> >  /* backend functions */\n> >  int refs_initdb(struct strbuf *err, int shared)\n> >  {\n> > diff --git a/refs.h b/refs.h\n> > index 3aad3b8..f8becea 100644\n> > --- a/refs.h\n> > +++ b/refs.h\n> > @@ -637,6 +637,13 @@ int files_log_ref_write(const char *refname, const unsigned char *old_sha1,\n> >  \t\t\tconst unsigned char *new_sha1, const char *msg,\n> >  \t\t\tint flags, struct strbuf *err);\n> >  \n> > +/*\n> > + * Check for entries in extras that start with \"$refname/\", ignoring\n> > + * those in skip. If there is such an entry, then we have a conflict.\n> > + */\n> > +const char *find_descendant_ref(const char *refname,\n> > +\t\t\t\tconst struct string_list *extras,\n> > +\t\t\t\tconst struct string_list *skip);\n> \n> The documentation is is not correct. As written, the function needs not\n> the refname as argument but the refname followed by '/'. Without the\n> trailing slash, \"refs/heads/foo\" would be blocked by \"refs/heads/foobar\".\n> \n> From the point of view of simplicity, it would be nice if this function\n> could take a refname (without the trailing slash) as argument. But then\n> it would basically be forced to allocate a new string, copy refname to\n> it, append a '/', then free the string again when it's done.\n> Alternatively, it could accept its refname argument in a strbuf and\n> temporarily append the '/'.\n> \n> But neither one of these alternatives is so elegant, so maybe it's OK if\n> this function is documented to take a \"directory name, including the\n> trailing '/'\" as argument and rename the argument (e.g., to \"dirname\").\n> \n> Please also document that \"skip\" and \"extras\" can be NULL, but if not\n> NULL they need to be sorted lists.\n\nI think `extras` may not be NULL.  But I've otherwise fixed this.\nThanks.\n"},{"id":"271592","messageId":"1444768817.7234.17.camel@twopensource.com","threadId":"40543","inReplyTo":"561C7D7B.8020807@alum.mit.edu","subject":"Re: [PATCH v3 05/44] refs.c: move update_ref to refs.c","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-10-13T20:40:17Z","receivedAt":"2015-10-13T20:40:17Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"On Tue, 2015-10-13 at 05:41 +0200, Michael Haggerty wrote:\n\n> If its removal was intentional, it deserves a careful explanation (and\n> should probably be done as a separate commit). If it was an accident,\n> please consider how this accident arose and try to think about whether\n> similar accidents might have happened elsewhere in this series.\n\nThis was an accident. I think it must have happened when I\nforward-ported Ronnie's changes over my change that introduced that\ncheck.  Usually, when there were conflicts during this process\n(indicating that the moved code had changed in the meantime), I did the\nmove by copy-pasting the code (rather than by choosing the old version).\nApparently, I missed this one.\n\nWill fix.\n\n> > [...]\n> \n> Michael\n> \n"},{"id":"271596","messageId":"561D73C4.2040403@alum.mit.edu","threadId":"40543","inReplyTo":"1444760911.7234.6.camel@twopensource.com","subject":"Re: [PATCH v3 19/44] refs-be-files.c: add a backend method structure with transaction functions","fromName":"Michael Haggerty","fromEmail":"mhagger@alum.mit.edu","sentAt":"2015-10-13T21:12:36Z","receivedAt":"2015-10-13T21:12:36Z","isPatch":true,"sender":{"key":"mhagger@alum.mit.edu","avatar":"https://avatars.githubusercontent.com/u/119718?v=4"},"body":"On 10/13/2015 08:28 PM, David Turner wrote:\n> On Tue, 2015-10-13 at 09:56 +0200, Michael Haggerty wrote:\n>> On 10/12/2015 11:51 PM, David Turner wrote:\n>>> [...]\n>>> +extern struct ref_be refs_be_files;\n>>\n>> I don't think that refs_be_files is needed in the public interface.\n> \n> We use refs_be_lmdb in a few other places to set up the lmdb backend, so\n> I thought I would put refs_be_files in refs.h too.  But I can remove\n> refs_be_files and just stick it in the places it's needed.\n\nIt's OK then. It doesn't hurt to leave it for consistency's sake.\n\nMichael\n\n-- \nMichael Haggerty\nmhagger@alum.mit.edu\n"},{"id":"271597","messageId":"1444771096.18742.3.camel@twopensource.com","threadId":"40543","inReplyTo":"561CB188.2090508@alum.mit.edu","subject":"Re: [PATCH v3 02/44] refs: make repack_without_refs and is_branch public","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-10-13T21:18:16Z","receivedAt":"2015-10-13T21:18:16Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"On Tue, 2015-10-13 at 09:23 +0200, Michael Haggerty wrote:\n> On 10/13/2015 04:39 AM, Michael Haggerty wrote:\n> > On 10/12/2015 11:51 PM, David Turner wrote:\n> >> is_branch was already non-static, but this patch declares it in the\n> >> header.\n> >>\n> >> Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>\n> >> Signed-off-by: David Turner <dturner@twopensource.com>\n> >> ---\n> >> [...]\n> > \n> > It seems odd that repack_without_refs() should be made public (and\n> > ultimately end up in refs.h) given that it intrinsically only has to do\n> > with file-based references. But I will read on...\n> \n> I think the reason you needed to do this was because you wanted to move\n> delete_refs() to the common code. It is true that delete_ref() can be\n> moved to the common code. And most of the code in delete_refs() is just\n> a loop calling delete_ref(). But delete_refs() also does the very\n> files-specific optimization of calling repack_without_refs() before the\n> loop. *That* call shouldn't be in the common code.\n> \n> So my suggestion is that you write a common_delete_refs() function that\n> only includes the loop over delete_ref(), and a files_delete_refs()\n> function that is basically\n> \n>     {\n>             result = repack_without_refs(refnames, &err);\n>             if (result) {\n>                     ...report error...\n>                     return result;\n>             }\n>             return common_delete_refs(...);\n>     }\n\nOK, I can do that.  That will have to be part of the backends work, so\nI'll exclude it from the refs-backend-pre-vtable patch set.\n"},{"id":"271599","messageId":"1444774901.18742.6.camel@twopensource.com","threadId":"40543","inReplyTo":"561CEB28.3090907@alum.mit.edu","subject":"Re: [PATCH v3 18/44] refs: move transaction functions into common code","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-10-13T22:21:41Z","receivedAt":"2015-10-13T22:21:41Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"On Tue, 2015-10-13 at 13:29 +0200, Michael Haggerty wrote:\n> I reviewed the patches up to here pretty carefully, and aside from the\n> comments I already sent, they look good.\n> \n> I like the new approach where the ref_transaction-building code is\n> shared across backends.\n> \n> It seems to me that a good breaking point for the first batch of patches\n> would be here, just before you start creating the VTABLE in commit\n> [19/44]. The patches before this point all have to do with moving code\n> around and a little bit of light refactoring. They cause a lot of code\n> churn that will soon conflict with other people's work (e.g., [1]), but\n> I think they are pretty uncontroversial.\n> \n> After this you start making a few important design decisions that\n> *could* be controversial. Therefore, by making a cut, you can maximize\n> the chance that the earlier patches can be merged to master relatively\n> quickly, after which the cross section for future conflicts will be much\n> smaller.\n> \n> Ideally, you would include a few later patches in the \"pre-VTABLE\" patch\n> series. It looks like the following patches also mostly have to do with\n> moving code around, and would fit logically with the \"pre-VTABLE\" changes:\n> \n> [24] refs.c: move refname_is_safe to the common code\n> [25] refs.h: document make refname_is_safe and add it to header\n> [26] refs.c: move copy_msg to the common code\n> [27] refs.c: move peel_object to the common code\n> [28] refs.c: move should_autocreate_reflog to common code\n> [32] initdb: move safe_create_dir into common code\n> [36] refs: make files_log_ref_write functions public\n> [37] refs: break out a ref conflict check\n> \n> I tried rebasing those commits on top of your patch 18 and it wasn't too\n> bad. The result is on branch \"refs-backend-pre-vtable\" on my GitHub repo\n> [2], including my suggested changes to those eight patches (which\n> therefore became seven because I squashed the first two together).\n\nThanks.  I started from that, and made the changes that you suggested \nin reviews that were not yet in there.\n\nI also added Jeff's extension patch, since it seems uncontroversial to\nme, and since we'll need it sooner or later anyway.\n\nI put the result on my github at:\nhttps://github.com/dturner-tw/git\non the refs-backend-pre-vtable branch \n"},{"id":"271601","messageId":"1444777525.18742.7.camel@twopensource.com","threadId":"40543","inReplyTo":"1444774901.18742.6.camel@twopensource.com","subject":"Re: [PATCH v3 18/44] refs: move transaction functions into common code","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-10-13T23:05:25Z","receivedAt":"2015-10-13T23:05:25Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"On Tue, 2015-10-13 at 18:21 -0400, David Turner wrote:\n> On Tue, 2015-10-13 at 13:29 +0200, Michael Haggerty wrote:\n> > I reviewed the patches up to here pretty carefully, and aside from the\n> > comments I already sent, they look good.\n> > \n> > I like the new approach where the ref_transaction-building code is\n> > shared across backends.\n> > \n> > It seems to me that a good breaking point for the first batch of patches\n> > would be here, just before you start creating the VTABLE in commit\n> > [19/44]. The patches before this point all have to do with moving code\n> > around and a little bit of light refactoring. They cause a lot of code\n> > churn that will soon conflict with other people's work (e.g., [1]), but\n> > I think they are pretty uncontroversial.\n> > \n> > After this you start making a few important design decisions that\n> > *could* be controversial. Therefore, by making a cut, you can maximize\n> > the chance that the earlier patches can be merged to master relatively\n> > quickly, after which the cross section for future conflicts will be much\n> > smaller.\n> > \n> > Ideally, you would include a few later patches in the \"pre-VTABLE\" patch\n> > series. It looks like the following patches also mostly have to do with\n> > moving code around, and would fit logically with the \"pre-VTABLE\" changes:\n> > \n> > [24] refs.c: move refname_is_safe to the common code\n> > [25] refs.h: document make refname_is_safe and add it to header\n> > [26] refs.c: move copy_msg to the common code\n> > [27] refs.c: move peel_object to the common code\n> > [28] refs.c: move should_autocreate_reflog to common code\n> > [32] initdb: move safe_create_dir into common code\n> > [36] refs: make files_log_ref_write functions public\n> > [37] refs: break out a ref conflict check\n> > \n> > I tried rebasing those commits on top of your patch 18 and it wasn't too\n> > bad. The result is on branch \"refs-backend-pre-vtable\" on my GitHub repo\n> > [2], including my suggested changes to those eight patches (which\n> > therefore became seven because I squashed the first two together).\n> \n> Thanks.  I started from that, and made the changes that you suggested \n> in reviews that were not yet in there.\n> \n> I also added Jeff's extension patch, since it seems uncontroversial to\n> me, and since we'll need it sooner or later anyway.\n> \n> I put the result on my github at:\n> https://github.com/dturner-tw/git\n> on the refs-backend-pre-vtable branch \n\nWhile rebasing the rest of the series on this, I noticed an issue in one\nof the patches, which I have now fixed.  I re-pushed.\n"},{"id":"271631","messageId":"1444784516.18742.12.camel@twopensource.com","threadId":"40543","inReplyTo":"561C7D7B.8020807@alum.mit.edu","subject":"Re: [PATCH v3 05/44] refs.c: move update_ref to refs.c","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-10-14T01:01:56Z","receivedAt":"2015-10-14T01:01:56Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"On Tue, 2015-10-13 at 05:41 +0200, Michael Haggerty wrote:\n\n> The original read\n> \n> \t\tif (read_ref(pseudoref, actual_old_sha1))\n> \t\t\tdie(\"could not read ref '%s'\", pseudoref);\n> \n> This seems like an important test. What happened to it?\n> \n> If its removal was intentional, it deserves a careful explanation (and\n> should probably be done as a separate commit). If it was an accident,\n> please consider how this accident arose and try to think about whether\n> similar accidents might have happened elsewhere in this series.\n\nI went ahead and manually rechecked all of the patches to ensure that\nnothing else was screwed up.  While doing so, I found two or three\nspurious whitespace changes (moving newlines around), which I fixed (in\nrefs-backend-pre-vtable, which I'll send to the list tomorrow-ish).\n"}]}