{"thread":{"id":"41190","subject":"[PATCH v3 00/20] refs backend rebase on pu","startedAt":"2016-01-14T16:25:57Z","lastAt":"2016-02-04T22:44:03Z","messageCount":49,"participants":["David Turner","SZEDER Gábor","Thomas Gummerer","Junio C Hamano","Ramsay Jones","Jeff King","Duy Nguyen"],"isPatch":true,"patchVersion":3,"patchTotal":20},"messages":[{"id":"276024","messageId":"1452788777-24954-1-git-send-email-dturner@twopensource.com","threadId":"41190","inReplyTo":null,"subject":"[PATCH v3 00/20] refs backend rebase on pu","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2016-01-14T16:25:57Z","receivedAt":"2016-01-14T16:25:57Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"I rebased this on top of 20fabf9b194c4099d329582c734e433f9f6586a3 (the\ncommit before the previous version of this series).\n\nThis entailed removing Michael Haggerty's patch to builtin/clone.c,\nsince a patch by Stefan already did approximately the same thing.\n\nThere was a somewhat hairy merge of \"resolve symbolic refs first\", but\nI think the new one is fine (the same tests all pass except for the\none TODO noted in the lmdb code).\n\nDavid Turner (17):\n  refs: add do_for_each_per_worktree_ref\n  refs: add methods for reflog\n  refs: add method for initial ref transaction commit\n  refs: add method for delete_refs\n  refs: add methods to init refs db\n  refs: add method to rename refs\n  refs: make lock generic\n  refs: move duplicate check to common code\n  refs: allow log-only updates\n  refs: resolve symbolic refs first\n  refs: always handle non-normal refs in files backend\n  init: allow alternate backends to be set for new repos\n  refs: check submodules ref storage config\n  refs: allow ref backend to be set for clone\n  svn: learn ref-storage argument\n  refs: add LMDB refs backend\n  refs: tests for lmdb backend\n\nRonnie Sahlberg (3):\n  refs: add a backend method structure with transaction functions\n  refs: add methods for misc ref operations\n  refs: add methods for the ref iterators\n\n .gitignore                                     |    1 +\n Documentation/config.txt                       |    7 +\n Documentation/git-clone.txt                    |    6 +\n Documentation/git-init-db.txt                  |    2 +-\n Documentation/git-init.txt                     |    7 +-\n Documentation/technical/refs-lmdb-backend.txt  |   52 +\n Documentation/technical/repository-version.txt |    5 +\n Makefile                                       |   12 +\n builtin/clone.c                                |    5 +\n builtin/init-db.c                              |   40 +-\n builtin/submodule--helper.c                    |    2 +-\n cache.h                                        |    2 +\n config.c                                       |   29 +\n configure.ac                                   |   33 +\n contrib/workdir/git-new-workdir                |    3 +\n git-submodule.sh                               |   13 +\n git-svn.perl                                   |    6 +-\n path.c                                         |   29 +-\n refs.c                                         |  451 +++++-\n refs.h                                         |   17 +\n refs/files-backend.c                           |  397 +++--\n refs/lmdb-backend.c                            | 2051 ++++++++++++++++++++++++\n refs/refs-internal.h                           |  128 +-\n setup.c                                        |   23 +-\n t/t0001-init.sh                                |   24 +\n t/t1460-refs-lmdb-backend.sh                   | 1109 +++++++++++++\n t/t1470-refs-lmdb-backend-reflog.sh            |  359 +++++\n t/t1480-refs-lmdb-submodule.sh                 |   85 +\n t/test-lib.sh                                  |    1 +\n test-refs-lmdb-backend.c                       |   64 +\n transport.c                                    |    7 +-\n 31 files changed, 4767 insertions(+), 203 deletions(-)\n create mode 100644 Documentation/technical/refs-lmdb-backend.txt\n create mode 100644 refs/lmdb-backend.c\n create mode 100755 t/t1460-refs-lmdb-backend.sh\n create mode 100755 t/t1470-refs-lmdb-backend-reflog.sh\n create mode 100755 t/t1480-refs-lmdb-submodule.sh\n create mode 100644 test-refs-lmdb-backend.c\n\n-- \n2.4.2.749.g730654d-twtrsrc\n"},{"id":"276043","messageId":"1452788777-24954-2-git-send-email-dturner@twopensource.com","threadId":"41190","inReplyTo":"1452788777-24954-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 01/20] refs: add a backend method structure with transaction functions","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2016-01-14T16:25:58Z","receivedAt":"2016-01-14T16:25:58Z","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 storage backend methods. Start by adding a\nmethod pointer for the transaction commit function.\n\nAdd a function set_refs_backend to switch between storage\nbackends. The files based storage backend is the default.\n\nSigned-off-by: Ronnie Sahlberg <sahlberg@google.com>\nSigned-off-by: David Turner <dturner@twopensource.com>\n---\n refs.c               | 29 +++++++++++++++++++++++++++++\n refs.h               |  5 +++++\n refs/files-backend.c | 10 ++++++++--\n refs/refs-internal.h | 10 ++++++++++\n 4 files changed, 52 insertions(+), 2 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex e2d34b2..485b905 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -10,6 +10,28 @@\n #include \"tag.h\"\n \n /*\n+ * We always have a files backend and it is the default.\n+ */\n+struct ref_storage_be refs_be_files;\n+static struct ref_storage_be *the_refs_backend = &refs_be_files;\n+/*\n+ * List of all available backends\n+ */\n+static struct ref_storage_be *refs_backends = &refs_be_files;\n+\n+int set_ref_storage_backend(const char *name)\n+{\n+\tstruct ref_storage_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+/*\n  * How to handle various characters in refnames:\n  * 0: An acceptable character for refs\n  * 1: End-of-component\n@@ -1082,3 +1104,10 @@ int rename_ref_available(const char *oldname, const char *newname)\n \tstrbuf_release(&err);\n \treturn ret;\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 3c3da29..c831b8e 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -508,4 +508,9 @@ 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+ * Switch to an alternate ref storage backend.\n+ */\n+int set_ref_storage_backend(const char *name);\n+\n #endif /* REFS_H */\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 81c92b4..69708e6 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -3128,8 +3128,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@@ -3515,3 +3515,9 @@ int reflog_expire(const char *refname, const unsigned char *sha1,\n \tunlock_ref(lock);\n \treturn -1;\n }\n+\n+struct ref_storage_be refs_be_files = {\n+\tNULL,\n+\t\"files\",\n+\tfiles_transaction_commit,\n+};\ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex c7dded3..b110c77 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -197,4 +197,14 @@ const char *find_descendant_ref(const char *dirname,\n \n int rename_ref_available(const char *oldname, const char *newname);\n \n+/* refs backends */\n+typedef int ref_transaction_commit_fn(struct ref_transaction *transaction,\n+\t\t\t\t      struct strbuf *err);\n+\n+struct ref_storage_be {\n+\tstruct ref_storage_be *next;\n+\tconst char *name;\n+\tref_transaction_commit_fn *transaction_commit;\n+};\n+\n #endif /* REFS_REFS_INTERNAL_H */\n-- \n2.4.2.749.g730654d-twtrsrc\n"},{"id":"276044","messageId":"1452788777-24954-3-git-send-email-dturner@twopensource.com","threadId":"41190","inReplyTo":"1452788777-24954-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 02/20] refs: add methods for misc ref operations","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2016-01-14T16:25:59Z","receivedAt":"2016-01-14T16:25:59Z","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@twopensource.com>\n---\n builtin/init-db.c    |  1 +\n refs.c               | 36 ++++++++++++++++++++++++++++++++++++\n refs/files-backend.c | 33 +++++++++++++++++++++++----------\n refs/refs-internal.h | 23 +++++++++++++++++++++++\n 4 files changed, 83 insertions(+), 10 deletions(-)\n\ndiff --git a/builtin/init-db.c b/builtin/init-db.c\nindex 07229d6..26e1cc3 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/refs.c b/refs.c\nindex 485b905..7103781 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1111,3 +1111,39 @@ 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+\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+\t\t\tunsigned char *sha1)\n+{\n+\treturn the_refs_backend->resolve_gitlink_ref(path, refname, sha1);\n+}\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 69708e6..783eb03 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -1333,7 +1333,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@@ -1565,8 +1566,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@@ -1615,7 +1618,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@@ -2252,7 +2255,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@@ -2443,10 +2446,10 @@ out:\n \treturn ret;\n }\n \n-int verify_refname_available(const char *newname,\n-\t\t\t     struct string_list *extras,\n-\t\t\t     struct string_list *skip,\n-\t\t\t     struct strbuf *err)\n+static int files_verify_refname_available(const char *newname,\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 \tstruct ref_dir *packed_refs = get_packed_refs(&ref_cache);\n \tstruct ref_dir *loose_refs = get_loose_refs(&ref_cache);\n@@ -2866,7 +2869,9 @@ static int create_symref_locked(struct ref_lock *lock, const char *refname,\n \treturn 0;\n }\n \n-int create_symref(const char *refname, const char *target, const char *logmsg)\n+static int files_create_symref(const char *refname,\n+\t\t\t       const char *target,\n+\t\t\t       const char *logmsg)\n {\n \tstruct strbuf err = STRBUF_INIT;\n \tstruct ref_lock *lock;\n@@ -3520,4 +3525,12 @@ struct ref_storage_be refs_be_files = {\n \tNULL,\n \t\"files\",\n \tfiles_transaction_commit,\n+\n+\tfiles_pack_refs,\n+\tfiles_peel_ref,\n+\tfiles_create_symref,\n+\n+\tfiles_resolve_ref_unsafe,\n+\tfiles_verify_refname_available,\n+\tfiles_resolve_gitlink_ref,\n };\ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex b110c77..0528239 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -201,10 +201,33 @@ int rename_ref_available(const char *oldname, const char *newname);\n typedef int ref_transaction_commit_fn(struct ref_transaction *transaction,\n \t\t\t\t      struct strbuf *err);\n \n+/* misc methods */\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(const char *ref_target,\n+\t\t\t     const char *refs_heads_master,\n+\t\t\t     const char *logmsg);\n+\n+/* resolution methods */\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 resolve_gitlink_ref_fn(const char *path, const char *refname,\n+\t\t\t\t   unsigned char *sha1);\n+\n struct ref_storage_be {\n \tstruct ref_storage_be *next;\n \tconst char *name;\n \tref_transaction_commit_fn *transaction_commit;\n+\n+\tpack_refs_fn *pack_refs;\n+\tpeel_ref_fn *peel_ref;\n+\tcreate_symref_fn *create_symref;\n+\n+\tresolve_ref_unsafe_fn *resolve_ref_unsafe;\n+\tverify_refname_available_fn *verify_refname_available;\n+\tresolve_gitlink_ref_fn *resolve_gitlink_ref;\n };\n \n #endif /* REFS_REFS_INTERNAL_H */\n-- \n2.4.2.749.g730654d-twtrsrc\n"},{"id":"276026","messageId":"1452788777-24954-4-git-send-email-dturner@twopensource.com","threadId":"41190","inReplyTo":"1452788777-24954-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 03/20] refs: add methods for the ref iterators","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2016-01-14T16:26:00Z","receivedAt":"2016-01-14T16:26:00Z","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.c               | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++\n refs/files-backend.c | 41 +++++++++++++++++++++++++++------------\n refs/refs-internal.h | 29 ++++++++++++++++++++++++++++\n 3 files changed, 112 insertions(+), 12 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 7103781..9c2caa7 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1147,3 +1147,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/files-backend.c b/refs/files-backend.c\nindex 783eb03..dcaf9eb 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -1756,32 +1756,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@@ -1790,19 +1794,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 \n-int for_each_replace_ref(each_ref_fn fn, void *cb_data)\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@@ -1812,7 +1818,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@@ -3533,4 +3539,15 @@ struct ref_storage_be refs_be_files = {\n \tfiles_resolve_ref_unsafe,\n \tfiles_verify_refname_available,\n \tfiles_resolve_gitlink_ref,\n+\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/refs-internal.h b/refs/refs-internal.h\nindex 0528239..cd338f9 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -216,6 +216,24 @@ typedef int verify_refname_available_fn(const char *refname, struct string_list\n typedef int resolve_gitlink_ref_fn(const char *path, const char *refname,\n \t\t\t\t   unsigned char *sha1);\n \n+/* iteration methods */\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_storage_be {\n \tstruct ref_storage_be *next;\n \tconst char *name;\n@@ -228,6 +246,17 @@ struct ref_storage_be {\n \tresolve_ref_unsafe_fn *resolve_ref_unsafe;\n \tverify_refname_available_fn *verify_refname_available;\n \tresolve_gitlink_ref_fn *resolve_gitlink_ref;\n+\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 #endif /* REFS_REFS_INTERNAL_H */\n-- \n2.4.2.749.g730654d-twtrsrc\n"},{"id":"276042","messageId":"1452788777-24954-5-git-send-email-dturner@twopensource.com","threadId":"41190","inReplyTo":"1452788777-24954-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 04/20] refs: add do_for_each_per_worktree_ref","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2016-01-14T16:26:01Z","receivedAt":"2016-01-14T16:26:01Z","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/files-backend.c | 15 ++++++++++++---\n refs/refs-internal.h | 10 ++++++++++\n 2 files changed, 22 insertions(+), 3 deletions(-)\n\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex dcaf9eb..68f310e 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -518,9 +518,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@@ -568,6 +565,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@@ -1738,6 +1739,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/refs-internal.h b/refs/refs-internal.h\nindex cd338f9..d3cc8d0 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -57,6 +57,16 @@\n  */\n int refname_is_safe(const char *refname);\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+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 enum peel_status {\n \t/* object was peeled successfully: */\n \tPEEL_PEELED = 0,\n-- \n2.4.2.749.g730654d-twtrsrc\n"},{"id":"276040","messageId":"1452788777-24954-6-git-send-email-dturner@twopensource.com","threadId":"41190","inReplyTo":"1452788777-24954-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 05/20] refs: add methods for reflog","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2016-01-14T16:26:02Z","receivedAt":"2016-01-14T16:26:02Z","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.c               | 46 ++++++++++++++++++++++++++++++++++++++++++++++\n refs/files-backend.c | 36 ++++++++++++++++++++++++------------\n refs/refs-internal.h | 27 +++++++++++++++++++++++++++\n 3 files changed, 97 insertions(+), 12 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 9c2caa7..36b4666 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1201,3 +1201,49 @@ 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_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/files-backend.c b/refs/files-backend.c\nindex 68f310e..f13b111 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -2649,7 +2649,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@@ -2905,7 +2906,7 @@ static int files_create_symref(const char *refname,\n \treturn ret;\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@@ -2913,7 +2914,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@@ -2957,7 +2958,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@@ -3059,7 +3062,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@@ -3121,7 +3125,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@@ -3431,12 +3435,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@@ -3541,6 +3545,14 @@ struct ref_storage_be refs_be_files = {\n \t\"files\",\n \tfiles_transaction_commit,\n \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+\n \tfiles_pack_refs,\n \tfiles_peel_ref,\n \tfiles_create_symref,\ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex d3cc8d0..87f2274 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -211,6 +211,25 @@ int rename_ref_available(const char *oldname, const char *newname);\n typedef int ref_transaction_commit_fn(struct ref_transaction *transaction,\n \t\t\t\t      struct strbuf *err);\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 /* misc methods */\n typedef int pack_refs_fn(unsigned int flags);\n typedef int peel_ref_fn(const char *refname, unsigned char *sha1);\n@@ -249,6 +268,14 @@ struct ref_storage_be {\n \tconst char *name;\n \tref_transaction_commit_fn *transaction_commit;\n \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+\n \tpack_refs_fn *pack_refs;\n \tpeel_ref_fn *peel_ref;\n \tcreate_symref_fn *create_symref;\n-- \n2.4.2.749.g730654d-twtrsrc\n"},{"id":"276039","messageId":"1452788777-24954-7-git-send-email-dturner@twopensource.com","threadId":"41190","inReplyTo":"1452788777-24954-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 06/20] refs: add method for initial ref transaction commit","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2016-01-14T16:26:03Z","receivedAt":"2016-01-14T16:26:03Z","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.c               | 6 ++++++\n refs/files-backend.c | 5 +++--\n refs/refs-internal.h | 1 +\n 3 files changed, 10 insertions(+), 2 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 36b4666..f492fa3 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1247,3 +1247,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/files-backend.c b/refs/files-backend.c\nindex f13b111..748993f 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -3319,8 +3319,8 @@ static int ref_present(const char *refname,\n \treturn string_list_has_string(affected_refnames, refname);\n }\n \n-int initial_ref_transaction_commit(struct ref_transaction *transaction,\n-\t\t\t\t   struct strbuf *err)\n+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@@ -3544,6 +3544,7 @@ struct ref_storage_be refs_be_files = {\n \tNULL,\n \t\"files\",\n \tfiles_transaction_commit,\n+\tfiles_initial_transaction_commit,\n \n \tfiles_for_each_reflog_ent,\n \tfiles_for_each_reflog_ent_reverse,\ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex 87f2274..26b707e 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -267,6 +267,7 @@ struct ref_storage_be {\n \tstruct ref_storage_be *next;\n \tconst char *name;\n \tref_transaction_commit_fn *transaction_commit;\n+\tref_transaction_commit_fn *initial_transaction_commit;\n \n \tfor_each_reflog_ent_fn *for_each_reflog_ent;\n \tfor_each_reflog_ent_reverse_fn *for_each_reflog_ent_reverse;\n-- \n2.4.2.749.g730654d-twtrsrc\n"},{"id":"276028","messageId":"1452788777-24954-8-git-send-email-dturner@twopensource.com","threadId":"41190","inReplyTo":"1452788777-24954-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 07/20] refs: add method for delete_refs","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2016-01-14T16:26:04Z","receivedAt":"2016-01-14T16:26:04Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"In the file-based backend, delete_refs has some special optimization\nto deal with packed refs.  In other backends, we might be able to make\nref deletion faster by putting all deletions into a single\ntransaction.  So we need a special backend function for this.\n\nSigned-off-by: David Turner <dturner@twopensource.com>\n---\n refs.c               | 5 +++++\n refs/files-backend.c | 3 ++-\n refs/refs-internal.h | 2 ++\n 3 files changed, 9 insertions(+), 1 deletion(-)\n\ndiff --git a/refs.c b/refs.c\nindex f492fa3..f1c0601 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1112,6 +1112,11 @@ int ref_transaction_commit(struct ref_transaction *transaction,\n \treturn the_refs_backend->transaction_commit(transaction, err);\n }\n \n+int delete_refs(struct string_list *refnames)\n+{\n+\treturn the_refs_backend->delete_refs(refnames);\n+}\n+\n const char *resolve_ref_unsafe(const char *ref, int resolve_flags,\n \t\t\t       unsigned char *sha1, int *flags)\n {\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 748993f..f1cb4c9 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -2362,7 +2362,7 @@ static int delete_ref_loose(struct ref_lock *lock, int flag, struct strbuf *err)\n \treturn 0;\n }\n \n-int delete_refs(struct string_list *refnames)\n+static int files_delete_refs(struct string_list *refnames)\n {\n \tstruct strbuf err = STRBUF_INIT;\n \tint i, result = 0;\n@@ -3557,6 +3557,7 @@ struct ref_storage_be refs_be_files = {\n \tfiles_pack_refs,\n \tfiles_peel_ref,\n \tfiles_create_symref,\n+\tfiles_delete_refs,\n \n \tfiles_resolve_ref_unsafe,\n \tfiles_verify_refname_available,\ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex 26b707e..b9c9b1d 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -236,6 +236,7 @@ typedef int peel_ref_fn(const char *refname, unsigned char *sha1);\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 delete_refs_fn(struct string_list *refnames);\n \n /* resolution methods */\n typedef const char *resolve_ref_unsafe_fn(const char *ref,\n@@ -280,6 +281,7 @@ struct ref_storage_be {\n \tpack_refs_fn *pack_refs;\n \tpeel_ref_fn *peel_ref;\n \tcreate_symref_fn *create_symref;\n+\tdelete_refs_fn *delete_refs;\n \n \tresolve_ref_unsafe_fn *resolve_ref_unsafe;\n \tverify_refname_available_fn *verify_refname_available;\n-- \n2.4.2.749.g730654d-twtrsrc\n"},{"id":"276027","messageId":"1452788777-24954-9-git-send-email-dturner@twopensource.com","threadId":"41190","inReplyTo":"1452788777-24954-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 08/20] refs: add methods to init refs db","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2016-01-14T16:26:05Z","receivedAt":"2016-01-14T16:26:05Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"Alternate refs backends might not need the refs/heads directory and so\non, so we make ref db initialization part of the backend.\n\nSigned-off-by: David Turner <dturner@twopensource.com>\n---\n builtin/init-db.c    | 14 ++++----------\n refs.c               |  5 +++++\n refs.h               |  2 ++\n refs/files-backend.c | 17 +++++++++++++++++\n refs/refs-internal.h |  2 ++\n 5 files changed, 30 insertions(+), 10 deletions(-)\n\ndiff --git a/builtin/init-db.c b/builtin/init-db.c\nindex 26e1cc3..4771e7e 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_init_db(&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.c b/refs.c\nindex f1c0601..b43d9ea 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1106,6 +1106,11 @@ int rename_ref_available(const char *oldname, const char *newname)\n }\n \n /* backend functions */\n+int refs_init_db(struct strbuf *err, int shared)\n+{\n+\treturn the_refs_backend->init_db(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 c831b8e..4d18886 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -66,6 +66,8 @@ extern int ref_exists(const char *refname);\n \n extern int is_branch(const char *refname);\n \n+extern int refs_init_db(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,\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex f1cb4c9..7f8c3d1 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -3540,9 +3540,26 @@ static int files_reflog_expire(const char *refname, const unsigned char *sha1,\n \treturn -1;\n }\n \n+static int files_init_db(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_storage_be refs_be_files = {\n \tNULL,\n \t\"files\",\n+\tfiles_init_db,\n \tfiles_transaction_commit,\n \tfiles_initial_transaction_commit,\n \ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex b9c9b1d..1373c02 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -208,6 +208,7 @@ const char *find_descendant_ref(const char *dirname,\n int rename_ref_available(const char *oldname, const char *newname);\n \n /* refs backends */\n+typedef int ref_init_db_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 \n@@ -267,6 +268,7 @@ typedef int for_each_replace_ref_fn(each_ref_fn fn, void *cb_data);\n struct ref_storage_be {\n \tstruct ref_storage_be *next;\n \tconst char *name;\n+\tref_init_db_fn *init_db;\n \tref_transaction_commit_fn *transaction_commit;\n \tref_transaction_commit_fn *initial_transaction_commit;\n \n-- \n2.4.2.749.g730654d-twtrsrc\n"},{"id":"276025","messageId":"1452788777-24954-10-git-send-email-dturner@twopensource.com","threadId":"41190","inReplyTo":"1452788777-24954-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 09/20] refs: add method to rename refs","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2016-01-14T16:26:06Z","receivedAt":"2016-01-14T16:26:06Z","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.c               | 5 +++++\n refs/files-backend.c | 4 +++-\n refs/refs-internal.h | 9 +++++++++\n 3 files changed, 17 insertions(+), 1 deletion(-)\n\ndiff --git a/refs.c b/refs.c\nindex b43d9ea..deeb7cf 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1122,6 +1122,11 @@ int delete_refs(struct string_list *refnames)\n \treturn the_refs_backend->delete_refs(refnames);\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 {\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 7f8c3d1..f14b6f9 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -2484,7 +2484,8 @@ 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@@ -3575,6 +3576,7 @@ struct ref_storage_be refs_be_files = {\n \tfiles_peel_ref,\n \tfiles_create_symref,\n \tfiles_delete_refs,\n+\tfiles_rename_ref,\n \n \tfiles_resolve_ref_unsafe,\n \tfiles_verify_refname_available,\ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex 1373c02..36a5bf8 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -67,6 +67,13 @@ 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+/*\n+ * Check if the new name does not conflict with any existing refs\n+ * (other than possibly the old ref).  Return 0 if the ref can be\n+ * renamed to the new name.\n+ */\n+int rename_ref_available(const char *oldname, const char *newname);\n+\n enum peel_status {\n \t/* object was peeled successfully: */\n \tPEEL_PEELED = 0,\n@@ -246,6 +253,7 @@ typedef const char *resolve_ref_unsafe_fn(const char *ref,\n typedef int verify_refname_available_fn(const char *refname, struct string_list *extra, struct string_list *skip, struct strbuf *err);\n typedef int resolve_gitlink_ref_fn(const char *path, const char *refname,\n \t\t\t\t   unsigned char *sha1);\n+typedef int rename_ref_fn(const char *oldref, const char *newref, const char *logmsg);\n \n /* iteration methods */\n typedef int head_ref_fn(each_ref_fn fn, void *cb_data);\n@@ -284,6 +292,7 @@ struct ref_storage_be {\n \tpeel_ref_fn *peel_ref;\n \tcreate_symref_fn *create_symref;\n \tdelete_refs_fn *delete_refs;\n+\trename_ref_fn *rename_ref;\n \n \tresolve_ref_unsafe_fn *resolve_ref_unsafe;\n \tverify_refname_available_fn *verify_refname_available;\n-- \n2.4.2.749.g730654d-twtrsrc\n"},{"id":"276041","messageId":"1452788777-24954-11-git-send-email-dturner@twopensource.com","threadId":"41190","inReplyTo":"1452788777-24954-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 10/20] refs: make lock generic","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2016-01-14T16:26:07Z","receivedAt":"2016-01-14T16:26:07Z","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/files-backend.c | 29 ++++++++++++++++-------------\n refs/refs-internal.h |  2 +-\n 2 files changed, 17 insertions(+), 14 deletions(-)\n\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex f14b6f9..9ae06a6 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -3190,11 +3190,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@@ -3202,7 +3203,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@@ -3220,12 +3222,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@@ -3234,7 +3236,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@@ -3250,7 +3252,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@@ -3263,16 +3265,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@@ -3280,16 +3282,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@@ -3305,8 +3308,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/refs-internal.h b/refs/refs-internal.h\nindex 36a5bf8..7390247 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -161,7 +161,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.749.g730654d-twtrsrc\n"},{"id":"276037","messageId":"1452788777-24954-12-git-send-email-dturner@twopensource.com","threadId":"41190","inReplyTo":"1452788777-24954-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 11/20] refs: move duplicate check to common code","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2016-01-14T16:26:08Z","receivedAt":"2016-01-14T16:26:08Z","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.c               | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++--\n refs/files-backend.c | 57 ++++---------------------------------------\n refs/refs-internal.h |  1 +\n 3 files changed, 73 insertions(+), 54 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex deeb7cf..d9cdacf 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1105,6 +1105,36 @@ int rename_ref_available(const char *oldname, const char *newname)\n \treturn ret;\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+static int get_affected_refnames(struct ref_transaction *transaction,\n+\t\t\t\t struct string_list *refnames,\n+\t\t\t\t struct 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_init_db(struct strbuf *err, int shared)\n {\n@@ -1114,7 +1144,29 @@ int refs_init_db(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 (get_affected_refnames(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 delete_refs(struct string_list *refnames)\n@@ -1266,5 +1318,18 @@ 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+\tstruct string_list affected_refnames = STRING_LIST_INIT_NODUP;\n+\tint ret;\n+\n+\tif (get_affected_refnames(transaction,\n+\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/files-backend.c b/refs/files-backend.c\nindex 9ae06a6..6dc9257 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -3136,24 +3136,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@@ -3161,26 +3145,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@@ -3199,7 +3163,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@@ -3311,7 +3275,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@@ -3324,27 +3287,18 @@ static int ref_present(const char *refname,\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@@ -3357,7 +3311,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@@ -3367,7 +3321,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@@ -3398,7 +3352,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/refs-internal.h b/refs/refs-internal.h\nindex 7390247..0dbb13d 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -217,6 +217,7 @@ int rename_ref_available(const char *oldname, const char *newname);\n /* refs backends */\n typedef int ref_init_db_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 \n /* reflog functions */\n-- \n2.4.2.749.g730654d-twtrsrc\n"},{"id":"276036","messageId":"1452788777-24954-13-git-send-email-dturner@twopensource.com","threadId":"41190","inReplyTo":"1452788777-24954-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 12/20] refs: allow log-only updates","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2016-01-14T16:26:09Z","receivedAt":"2016-01-14T16:26:09Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"The refs infrastructure learns about log-only ref updates, which only\nupdate the reflog.  Later, we will use this to separate symbolic\nreference resolution from ref updating.\n\nSigned-off-by: David Turner <dturner@twopensource.com>\n---\n refs/files-backend.c | 15 ++++++++++-----\n refs/refs-internal.h |  2 ++\n 2 files changed, 12 insertions(+), 5 deletions(-)\n\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 6dc9257..3a61d1e 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -2827,7 +2827,7 @@ static int commit_ref_update(struct ref_lock *lock,\n \t\t\t}\n \t\t}\n \t}\n-\tif (commit_ref(lock)) {\n+\tif (!(flags & REF_LOG_ONLY) && commit_ref(lock)) {\n \t\terror(\"Couldn't set %s\", lock->ref_name);\n \t\tunlock_ref(lock);\n \t\treturn -1;\n@@ -3181,7 +3181,8 @@ static int files_transaction_commit(struct ref_transaction *transaction,\n \t\t\tgoto cleanup;\n \t\t}\n \t\tif ((update->flags & REF_HAVE_NEW) &&\n-\t\t    !(update->flags & REF_DELETING)) {\n+\t\t    !(update->flags & REF_DELETING) &&\n+\t\t    !(update->flags & REF_LOG_ONLY)) {\n \t\t\tint overwriting_symref = ((update->type & REF_ISSYMREF) &&\n \t\t\t\t\t\t  (update->flags & REF_NODEREF));\n \n@@ -3211,7 +3212,9 @@ static int files_transaction_commit(struct ref_transaction *transaction,\n \t\t\t\tupdate->flags |= REF_NEEDS_COMMIT;\n \t\t\t}\n \t\t}\n-\t\tif (!(update->flags & REF_NEEDS_COMMIT)) {\n+\n+\t\tif (!(update->flags & REF_LOG_ONLY) &&\n+\t\t    !(update->flags & REF_NEEDS_COMMIT)) {\n \t\t\t/*\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@@ -3228,7 +3231,8 @@ static int files_transaction_commit(struct ref_transaction *transaction,\n \tfor (i = 0; i < n; i++) {\n \t\tstruct ref_update *update = updates[i];\n \n-\t\tif (update->flags & REF_NEEDS_COMMIT) {\n+\t\tif (update->flags & REF_NEEDS_COMMIT ||\n+\t\t    update->flags & REF_LOG_ONLY) {\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@@ -3248,7 +3252,8 @@ static int files_transaction_commit(struct ref_transaction *transaction,\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\tif (update->flags & REF_DELETING &&\n+\t\t    !(update->flags & REF_LOG_ONLY)) {\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;\ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex 0dbb13d..e8b1865 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -42,6 +42,8 @@\n  * value to ref_update::flags\n  */\n \n+#define REF_LOG_ONLY 0x80\n+\n /*\n  * Return true iff refname is minimally safe. \"Safe\" here means that\n  * deleting a loose reference by this name will not do any damage, for\n-- \n2.4.2.749.g730654d-twtrsrc\n"},{"id":"276031","messageId":"1452788777-24954-14-git-send-email-dturner@twopensource.com","threadId":"41190","inReplyTo":"1452788777-24954-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 13/20] refs: resolve symbolic refs first","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2016-01-14T16:26:10Z","receivedAt":"2016-01-14T16:26:10Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"Before committing ref updates, split symbolic ref updates into two\nparts: an update to the underlying ref, and a log-only update to the\nsymbolic ref.  This ensures that both references are locked correctly\nwhile their reflogs are updated.\n\nIt is still possible to confuse git by concurrent updates, since the\nsplitting of symbolic refs does not happen under lock. So a symbolic ref\ncould be replaced by a plain ref in the middle of this operation, which\nwould lead to reflog discontinuities and missed old-ref checks.\n\nSigned-off-by: David Turner <dturner@twopensource.com>\n---\n refs.c               |  69 +++++++++++++++++++++++++++\n refs/files-backend.c | 132 ++++++++++++++++++++++++++-------------------------\n refs/refs-internal.h |   8 ++++\n 3 files changed, 145 insertions(+), 64 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex d9cdacf..11cd5d2 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1141,6 +1141,71 @@ int refs_init_db(struct strbuf *err, int shared)\n \treturn the_refs_backend->init_db(err, shared);\n }\n \n+/*\n+ * Special case for symbolic refs when REF_NODEREF is not turned on.\n+ * Dereference them here, mark them REF_LOG_ONLY, and add an update\n+ * for the underlying ref.\n+ */\n+static int dereference_symrefs(struct ref_transaction *transaction,\n+\t\t\t       struct strbuf *err)\n+{\n+\tint i;\n+\tint nr = transaction->nr;\n+\n+\tfor (i = 0; i < nr; i++) {\n+\t\tstruct ref_update *update = transaction->updates[i];\n+\t\tconst char *resolved;\n+\t\tunsigned char sha1[20];\n+\t\tint resolve_flags = 0;\n+\t\tint mustexist = (update->old_sha1 &&\n+\t\t\t\t !is_null_sha1(update->old_sha1));\n+\t\tint deleting = (update->flags & REF_HAVE_NEW) &&\n+\t\t\tis_null_sha1(update->new_sha1);\n+\n+\t\tif (mustexist)\n+\t\t\tresolve_flags |= RESOLVE_REF_READING;\n+\t\tif (deleting)\n+\t\t\tresolve_flags |= RESOLVE_REF_ALLOW_BAD_NAME |\n+\t\t\t\tRESOLVE_REF_NO_RECURSE;\n+\n+\t\tif (strcmp(update->refname, \"HEAD\"))\n+\t\t\tupdate->flags |= REF_IS_NOT_HEAD;\n+\n+\t\tresolved = resolve_ref_unsafe(update->refname, resolve_flags,\n+\t\t\t\t\t      sha1, &update->type);\n+\t\tif (!resolved) {\n+\t\t\t/*\n+\t\t\t * We may notice this breakage later and die\n+\t\t\t * with a sensible error message\n+\t\t\t */\n+\t\t\tupdate->type |= REF_ISBROKEN;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\thashcpy(update->read_sha1, sha1);\n+\n+\t\tif (update->flags & REF_NODEREF ||\n+\t\t    !(update->type & REF_ISSYMREF))\n+\t\t\tcontinue;\n+\n+\t\t/* Create a new transaction for the underlying ref */\n+\t\tif (ref_transaction_update(transaction,\n+\t\t\t\t\t   resolved,\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 & ~REF_IS_NOT_HEAD,\n+\t\t\t\t\t   update->msg, err))\n+\t\t\treturn -1;\n+\n+\t\t/* Make the symbolic ref update non-recursive */\n+\t\tupdate->flags |= REF_LOG_ONLY | REF_NODEREF;\n+\t\tupdate->flags &= ~REF_HAVE_OLD;\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@@ -1157,6 +1222,10 @@ int ref_transaction_commit(struct ref_transaction *transaction,\n \t\treturn 0;\n \t}\n \n+\tret = dereference_symrefs(transaction, err);\n+\tif (ret)\n+\t\tgoto done;\n+\n \tif (get_affected_refnames(transaction, &affected_refnames, err)) {\n \t\tret = TRANSACTION_GENERIC_ERROR;\n \t\tgoto done;\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 3a61d1e..3b9061e 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -7,7 +7,6 @@\n \n struct ref_lock {\n \tchar *ref_name;\n-\tchar *orig_ref_name;\n \tstruct lock_file *lk;\n \tstruct object_id old_oid;\n };\n@@ -1839,7 +1838,6 @@ static void unlock_ref(struct ref_lock *lock)\n \tif (lock->lk)\n \t\trollback_lock_file(lock->lk);\n \tfree(lock->ref_name);\n-\tfree(lock->orig_ref_name);\n \tfree(lock);\n }\n \n@@ -1895,6 +1893,7 @@ static int remove_empty_directories(struct strbuf *path)\n  */\n static struct ref_lock *lock_ref_sha1_basic(const char *refname,\n \t\t\t\t\t    const unsigned char *old_sha1,\n+\t\t\t\t\t    const unsigned char *read_sha1,\n \t\t\t\t\t    const struct string_list *extras,\n \t\t\t\t\t    const struct string_list *skip,\n \t\t\t\t\t    unsigned int flags, int *type_p,\n@@ -1902,14 +1901,14 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,\n {\n \tstruct strbuf ref_file = STRBUF_INIT;\n \tstruct strbuf orig_ref_file = STRBUF_INIT;\n-\tconst char *orig_refname = refname;\n \tstruct ref_lock *lock;\n \tint last_errno = 0;\n \tint type;\n \tint lflags = 0;\n \tint mustexist = (old_sha1 && !is_null_sha1(old_sha1));\n-\tint resolve_flags = 0;\n+\tint resolve_flags = RESOLVE_REF_NO_RECURSE;\n \tint attempts_remaining = 3;\n+\tint resolved;\n \n \tassert(err);\n \n@@ -1919,65 +1918,65 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,\n \t\tresolve_flags |= RESOLVE_REF_READING;\n \tif (flags & REF_DELETING)\n \t\tresolve_flags |= RESOLVE_REF_ALLOW_BAD_NAME;\n-\tif (flags & REF_NODEREF) {\n-\t\tresolve_flags |= RESOLVE_REF_NO_RECURSE;\n+\tif (flags & REF_NODEREF)\n \t\tlflags |= LOCK_NO_DEREF;\n-\t}\n \n-\trefname = resolve_ref_unsafe(refname, resolve_flags,\n-\t\t\t\t     lock->old_oid.hash, &type);\n-\tif (!refname && errno == EISDIR) {\n-\t\t/*\n-\t\t * we are trying to lock foo but we used to\n-\t\t * have foo/bar which now does not exist;\n-\t\t * it is normal for the empty directory 'foo'\n-\t\t * to remain.\n-\t\t */\n-\t\tstrbuf_git_path(&orig_ref_file, \"%s\", orig_refname);\n-\t\tif (remove_empty_directories(&orig_ref_file)) {\n+\tif (type_p && *type_p & REF_ISSYMREF) {\n+\t\thashcpy(lock->old_oid.hash, read_sha1);\n+\t} else {\n+\t\tresolved = !!resolve_ref_unsafe(refname, resolve_flags,\n+\t\t\t\t\t\tlock->old_oid.hash, &type);\n+\t\tif (!resolved && errno == EISDIR) {\n+\t\t\t/*\n+\t\t\t * we are trying to lock foo but we used to\n+\t\t\t * have foo/bar which now does not exist;\n+\t\t\t * it is normal for the empty directory 'foo'\n+\t\t\t * to remain.\n+\t\t\t */\n+\t\t\tstrbuf_git_path(&orig_ref_file, \"%s\", refname);\n+\t\t\tif (remove_empty_directories(&orig_ref_file)) {\n+\t\t\t\tstruct ref_dir *loose_refs;\n+\t\t\t\tloose_refs = get_loose_refs(&ref_cache);\n+\t\t\t\tlast_errno = errno;\n+\t\t\t\tif (!verify_refname_available_dir(refname, extras, skip,\n+\t\t\t\t\t\t\t\t  loose_refs, err))\n+\t\t\t\t\tstrbuf_addf(err, \"there are still refs under '%s'\",\n+\t\t\t\t\t\t    refname);\n+\t\t\t\tgoto error_return;\n+\t\t\t}\n+\t\t\tresolved = !!resolve_ref_unsafe(refname, resolve_flags,\n+\t\t\t\t\t\t\tlock->old_oid.hash, &type);\n+\t\t}\n+\n+\t\tif (type_p)\n+\t\t\t*type_p = type;\n+\t\tif (!resolved) {\n \t\t\tlast_errno = errno;\n-\t\t\tif (!verify_refname_available_dir(orig_refname, extras, skip,\n+\t\t\tif (last_errno != ENOTDIR ||\n+\t\t\t    !verify_refname_available_dir(refname, extras, skip,\n \t\t\t\t\t\t\t  get_loose_refs(&ref_cache), 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\t\tstrbuf_addf(err,\n+\t\t\t\t\t    \"unable to resolve reference %s: %s\",\n+\t\t\t\t\t    refname, strerror(last_errno));\n+\n+\t\t\tgoto error_return;\n+\t\t}\n+\t\t/*\n+\t\t * If the ref did not exist and we are creating it, make sure\n+\t\t * there is no existing packed ref whose name begins with our\n+\t\t * refname, nor a packed ref whose name is a proper prefix of\n+\t\t * our refname.\n+\t\t */\n+\t\tif (is_null_oid(&lock->old_oid) &&\n+\t\t    verify_refname_available_dir(refname, extras, skip,\n+\t\t\t\t\t\t get_packed_refs(&ref_cache), err)) {\n+\t\t\tlast_errno = ENOTDIR;\n \t\t\tgoto error_return;\n \t\t}\n-\t\trefname = resolve_ref_unsafe(orig_refname, resolve_flags,\n-\t\t\t\t\t     lock->old_oid.hash, &type);\n-\t}\n-\tif (type_p)\n-\t    *type_p = type;\n-\tif (!refname) {\n-\t\tlast_errno = errno;\n-\t\tif (last_errno != ENOTDIR ||\n-\t\t    !verify_refname_available_dir(orig_refname, extras, skip,\n-\t\t\t\t\t\t  get_loose_refs(&ref_cache), 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-\t\tgoto error_return;\n-\t}\n-\n-\tif (flags & REF_NODEREF)\n-\t\trefname = orig_refname;\n-\n-\t/*\n-\t * If the ref did not exist and we are creating it, make sure\n-\t * there is no existing packed ref whose name begins with our\n-\t * refname, nor a packed ref whose name is a proper prefix of\n-\t * our refname.\n-\t */\n-\tif (is_null_oid(&lock->old_oid) &&\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-\n \tlock->lk = xcalloc(1, sizeof(struct lock_file));\n \n \tlock->ref_name = xstrdup(refname);\n-\tlock->orig_ref_name = xstrdup(orig_refname);\n \tstrbuf_git_path(&ref_file, \"%s\", refname);\n \n  retry:\n@@ -2009,7 +2008,13 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,\n \t\t\tgoto error_return;\n \t\t}\n \t}\n-\tif (verify_lock(lock, old_sha1, mustexist, err)) {\n+\n+\tif (type_p && *type_p & REF_ISSYMREF && !(*type_p & REF_ISBROKEN)) {\n+\t\t/*\n+\t\t * Old hash verification for symrefs happens on their\n+\t\t * base ref.\n+\t\t */\n+\t} else if (verify_lock(lock, old_sha1, mustexist, err)) {\n \t\tlast_errno = errno;\n \t\tgoto error_return;\n \t}\n@@ -2543,7 +2548,7 @@ static int files_rename_ref(const char *oldrefname, const char *newrefname,\n \n \tlogmoved = log;\n \n-\tlock = lock_ref_sha1_basic(newrefname, NULL, NULL, NULL, 0, NULL, &err);\n+\tlock = lock_ref_sha1_basic(newrefname, NULL, NULL, NULL, NULL, 0, NULL, &err);\n \tif (!lock) {\n \t\terror(\"unable to rename '%s' to '%s': %s\", oldrefname, newrefname, err.buf);\n \t\tstrbuf_release(&err);\n@@ -2561,7 +2566,7 @@ static int files_rename_ref(const char *oldrefname, const char *newrefname,\n \treturn 0;\n \n  rollback:\n-\tlock = lock_ref_sha1_basic(oldrefname, NULL, NULL, NULL, 0, NULL, &err);\n+\tlock = lock_ref_sha1_basic(oldrefname, NULL, NULL, NULL, NULL, 0, NULL, &err);\n \tif (!lock) {\n \t\terror(\"unable to lock %s for rollback: %s\", oldrefname, err.buf);\n \t\tstrbuf_release(&err);\n@@ -2789,9 +2794,7 @@ static int commit_ref_update(struct ref_lock *lock,\n \t\t\t     int flags, struct strbuf *err)\n {\n \tclear_loose_ref_cache(&ref_cache);\n-\tif (log_ref_write(lock->ref_name, lock->old_oid.hash, sha1, logmsg, flags, err) < 0 ||\n-\t    (strcmp(lock->ref_name, lock->orig_ref_name) &&\n-\t     log_ref_write(lock->orig_ref_name, lock->old_oid.hash, sha1, logmsg, flags, err) < 0)) {\n+\tif (log_ref_write(lock->ref_name, lock->old_oid.hash, sha1, logmsg, flags, err) < 0) {\n \t\tchar *old_msg = strbuf_detach(err, NULL);\n \t\tstrbuf_addf(err, \"Cannot update the ref '%s': %s\",\n \t\t\t    lock->ref_name, old_msg);\n@@ -2799,7 +2802,7 @@ static int commit_ref_update(struct ref_lock *lock,\n \t\tunlock_ref(lock);\n \t\treturn -1;\n \t}\n-\tif (strcmp(lock->orig_ref_name, \"HEAD\") != 0) {\n+\tif (flags & REF_IS_NOT_HEAD) {\n \t\t/*\n \t\t * Special hack: If a branch is updated directly and HEAD\n \t\t * points to it (may happen on the remote side of a push\n@@ -2894,8 +2897,8 @@ static int files_create_symref(const char *refname,\n \tstruct ref_lock *lock;\n \tint ret;\n \n-\tlock = lock_ref_sha1_basic(refname, NULL, NULL, NULL, REF_NODEREF, NULL,\n-\t\t\t\t   &err);\n+\tlock = lock_ref_sha1_basic(refname, NULL, NULL, NULL, NULL, REF_NODEREF,\n+\t\t\t\t   NULL, &err);\n \tif (!lock) {\n \t\terror(\"%s\", err.buf);\n \t\tstrbuf_release(&err);\n@@ -3163,6 +3166,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\tupdate->read_sha1,\n \t\t\t\taffected_refnames, NULL,\n \t\t\t\tupdate->flags,\n \t\t\t\t&update->type,\n@@ -3409,7 +3413,7 @@ static int files_reflog_expire(const char *refname, const unsigned char *sha1,\n \tstruct ref_lock *lock;\n \tchar *log_file;\n \tint status = 0;\n-\tint type;\n+\tint type = 0;\n \tstruct strbuf err = STRBUF_INIT;\n \n \tmemset(&cb, 0, sizeof(cb));\n@@ -3422,7 +3426,7 @@ static int files_reflog_expire(const char *refname, const unsigned char *sha1,\n \t * reference itself, plus we might need to update the\n \t * reference if --updateref was specified:\n \t */\n-\tlock = lock_ref_sha1_basic(refname, sha1, NULL, NULL, 0, &type, &err);\n+\tlock = lock_ref_sha1_basic(refname, sha1, NULL, NULL, NULL, 0, &type, &err);\n \tif (!lock) {\n \t\terror(\"cannot lock ref '%s': %s\", refname, err.buf);\n \t\tstrbuf_release(&err);\ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex e8b1865..ed6fb22 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -44,6 +44,8 @@\n \n #define REF_LOG_ONLY 0x80\n \n+#define REF_IS_NOT_HEAD 0x100\n+\n /*\n  * Return true iff refname is minimally safe. \"Safe\" here means that\n  * deleting a loose reference by this name will not do any damage, for\n@@ -159,6 +161,12 @@ struct ref_update {\n \t */\n \tunsigned char old_sha1[20];\n \t/*\n+\t * During the symbolic ref split stage, we resolve refs.\n+\t * We'll re-resolve non-symbolic refs once they are locked,\n+\t * but we store this to avoid re-resolving symbolic refs.\n+\t */\n+\tunsigned char read_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-- \n2.4.2.749.g730654d-twtrsrc\n"},{"id":"276033","messageId":"1452788777-24954-15-git-send-email-dturner@twopensource.com","threadId":"41190","inReplyTo":"1452788777-24954-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 14/20] refs: always handle non-normal refs in files backend","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2016-01-14T16:26:11Z","receivedAt":"2016-01-14T16:26:11Z","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\nUpdates to normal refs continue to go through the chosen backend.\n\nUpdates to non-normal refs are moved to a separate files backend\ntransaction.\n\nSigned-off-by: David Turner <dturner@twopensource.com>\n---\n refs.c               | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++--\n refs/refs-internal.h |  2 ++\n 2 files changed, 81 insertions(+), 2 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 11cd5d2..545823b 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -9,6 +9,11 @@\n #include \"object.h\"\n #include \"tag.h\"\n \n+static 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@@ -780,6 +785,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@@ -787,8 +799,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@@ -1206,11 +1217,38 @@ static int dereference_symrefs(struct ref_transaction *transaction,\n \treturn 0;\n }\n \n+/*\n+ * Move all non-normal ref updates 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 strbuf *err)\n+{\n+\tint i;\n+\n+\tfor (i = 0; i < transaction->nr; i++) {\n+\t\tint last;\n+\t\tstruct ref_update *update = transaction->updates[i];\n+\n+\t\tif (ref_type(update->refname) == REF_TYPE_NORMAL)\n+\t\t\tcontinue;\n+\n+\t\tlast = --transaction->nr;\n+\t\ttransaction->updates[i] = transaction->updates[last];\n+\t\tadd_update_obj(files_transaction, update);\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 ref_transaction *files_transaction = NULL;\n \n \tassert(err);\n \n@@ -1226,6 +1264,26 @@ int ref_transaction_commit(struct ref_transaction *transaction,\n \tif (ret)\n \t\tgoto done;\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\tgoto done;\n+\n+\t\tret = move_abnormal_ref_updates(transaction, files_transaction,\n+\t\t\t\t\t\terr);\n+\t\tif (ret)\n+\t\t\tgoto done;\n+\n+\t\t/* files backend commit */\n+\t\tif (get_affected_refnames(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 (get_affected_refnames(transaction, &affected_refnames, err)) {\n \t\tret = TRANSACTION_GENERIC_ERROR;\n \t\tgoto done;\n@@ -1233,8 +1291,24 @@ 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 (files_transaction) {\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+\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 \treturn ret;\n }\n \n@@ -1274,6 +1348,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 }\ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex ed6fb22..75b4389 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -321,4 +321,6 @@ struct ref_storage_be {\n \tfor_each_replace_ref_fn *for_each_replace_ref;\n };\n \n+extern struct ref_storage_be refs_be_files;\n+\n #endif /* REFS_REFS_INTERNAL_H */\n-- \n2.4.2.749.g730654d-twtrsrc\n"},{"id":"276038","messageId":"1452788777-24954-16-git-send-email-dturner@twopensource.com","threadId":"41190","inReplyTo":"1452788777-24954-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 15/20] init: allow alternate backends to be set for new repos","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2016-01-14T16:26:12Z","receivedAt":"2016-01-14T16:26:12Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"git init learns a new argument --ref-storage.  Presently, only\n\"files\" is supported, but later we will add other backends.\n\nWhen this argument is used, the repository's extensions.refStorage\nconfiguration value is set (as well as core.repositoryformatversion),\nand the refs backend's initdb function is used to set up the ref\ndatabase.\n\nSigned-off-by: David Turner <dturner@twopensource.com>\n---\n Documentation/git-init-db.txt |  2 +-\n Documentation/git-init.txt    |  7 ++++++-\n builtin/init-db.c             | 31 ++++++++++++++++++++++++++-----\n cache.h                       |  2 ++\n path.c                        | 29 +++++++++++++++++++++++++++--\n refs.c                        |  8 ++++++++\n refs.h                        |  8 ++++++++\n setup.c                       | 12 ++++++++++++\n t/t0001-init.sh               | 24 ++++++++++++++++++++++++\n 9 files changed, 114 insertions(+), 9 deletions(-)\n\ndiff --git a/Documentation/git-init-db.txt b/Documentation/git-init-db.txt\nindex 648a6cd..d03fb69 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>]] [--ref-storage=<name>]\n \n \n DESCRIPTION\ndiff --git a/Documentation/git-init.txt b/Documentation/git-init.txt\nindex 8174d27..d2b150f 100644\n--- a/Documentation/git-init.txt\n+++ b/Documentation/git-init.txt\n@@ -12,7 +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-\n+\t  [--ref-storage=<name>]\n \n DESCRIPTION\n -----------\n@@ -113,6 +113,11 @@ does not exist, it will be created.\n \n --\n \n+--ref-storage=<name>::\n+Type of refs storage backend. Default is to use the original \"files\"\n+storage, which stores ref data in files in .git/refs and\n+.git/packed-refs.\n+\n TEMPLATE DIRECTORY\n ------------------\n \ndiff --git a/builtin/init-db.c b/builtin/init-db.c\nindex 4771e7e..ebc747c 100644\n--- a/builtin/init-db.c\n+++ b/builtin/init-db.c\n@@ -24,6 +24,7 @@ static int init_is_bare_repository = 0;\n static int init_shared_repository = -1;\n static const char *init_db_template_dir;\n static const char *git_link;\n+static char *requested_ref_storage_backend;\n \n static void copy_templates_1(struct strbuf *path, struct strbuf *template,\n \t\t\t     DIR *dir)\n@@ -179,6 +180,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,9 +206,6 @@ static int create_default_files(const char *template_path)\n \t\tadjust_shared_perm(get_git_dir());\n \t}\n \n-\tif (refs_init_db(&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.\n@@ -214,14 +213,34 @@ static int create_default_files(const char *template_path)\n \tpath = git_path_buf(&buf, \"HEAD\");\n \treinit = (!access(path, R_OK)\n \t\t  || readlink(path, junk, sizeof(junk)-1) != -1);\n-\tif (!reinit) {\n+\tif (reinit) {\n+\t\tif (requested_ref_storage_backend &&\n+\t\t    strcmp(ref_storage_backend, requested_ref_storage_backend))\n+\t\t\tdie(\"You can't change the refs storage type (was %s; you requested %s)\",\n+\t\t\t    ref_storage_backend, requested_ref_storage_backend);\n+\t} else {\n \t\tif (create_symref(\"HEAD\", \"refs/heads/master\", NULL) < 0)\n \t\t\texit(1);\n \t}\n \n+\tif (requested_ref_storage_backend)\n+\t\tref_storage_backend = requested_ref_storage_backend;\n+\tif (strcmp(ref_storage_backend, \"files\")) {\n+\t\tgit_config_set(\"extensions.refStorage\", ref_storage_backend);\n+\t\tgit_config_set(\"core.repositoryformatversion\", ref_storage_backend);\n+#ifdef USE_LIBLMDB\n+\t\tregister_ref_storage_backend(&refs_be_lmdb);\n+#endif\n+\t\tset_ref_storage_backend(ref_storage_backend);\n+\t\trepo_version = 1;\n+\t}\n+\n+\tif (refs_init_db(&err, shared_repository))\n+\t\tdie(\"failed to set up refs db: %s\", err.buf);\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 +488,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, \"ref-storage\", &requested_ref_storage_backend,\n+\t\t\t   N_(\"name\"), N_(\"name of backend type to use\")),\n \t\tOPT_END()\n \t};\n \ndiff --git a/cache.h b/cache.h\nindex aee1d51..4fb5ba2 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -716,6 +716,8 @@ extern enum object_creation_mode object_creation_mode;\n \n extern char *notes_ref_name;\n \n+extern const char *ref_storage_backend;\n+\n extern int grafts_replace_parents;\n \n /*\ndiff --git a/path.c b/path.c\nindex 8b7e168..2e67a2b 100644\n--- a/path.c\n+++ b/path.c\n@@ -2,6 +2,7 @@\n  * Utilities for paths and pathnames\n  */\n #include \"cache.h\"\n+#include \"refs.h\"\n #include \"strbuf.h\"\n #include \"string-list.h\"\n #include \"dir.h\"\n@@ -511,8 +512,32 @@ int validate_headref(const char *path)\n \tint fd;\n \tssize_t len;\n \n-\tif (lstat(path, &st) < 0)\n-\t\treturn -1;\n+\tif (lstat(path, &st) < 0) {\n+\t\tint backend_type_set;\n+\t\tstruct strbuf config_path = STRBUF_INIT;\n+\t\tchar *pathdup = xstrdup(path);\n+\t\tchar *git_dir = dirname(pathdup);\n+\t\tchar *storage = NULL;\n+\n+\t\tstrbuf_addf(&config_path, \"%s/%s\", git_dir, \"config\");\n+\t\tfree(pathdup);\n+\n+\t\tif (git_config_from_file(ref_storage_backend_config,\n+\t\t\t\t\t config_path.buf, &storage)) {\n+\t\t\tstrbuf_release(&config_path);\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\tbackend_type_set = !!storage;\n+\t\tfree((void *)storage);\n+\t\tstrbuf_release(&config_path);\n+\t\t/*\n+\t\t * Alternate backends are assumed to keep HEAD\n+\t\t * in a valid state, so there's no need to do\n+\t\t * further validation.\n+\t\t */\n+\t\treturn backend_type_set ? 0 : -1;\n+\t}\n \n \t/* Make sure it is a \"refs/..\" symlink */\n \tif (S_ISLNK(st.st_mode)) {\ndiff --git a/refs.c b/refs.c\nindex 545823b..9f946d0 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -24,6 +24,14 @@ static struct ref_storage_be *the_refs_backend = &refs_be_files;\n  */\n static struct ref_storage_be *refs_backends = &refs_be_files;\n \n+const char *ref_storage_backend = \"files\";\n+\n+void register_ref_storage_backend(struct ref_storage_be *be)\n+{\n+\tbe->next = refs_backends;\n+\trefs_backends = be;\n+}\n+\n int set_ref_storage_backend(const char *name)\n {\n \tstruct ref_storage_be *be;\ndiff --git a/refs.h b/refs.h\nindex 4d18886..a916a6b 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -511,8 +511,16 @@ extern int reflog_expire(const char *refname, const unsigned char *sha1,\n \t\t\t void *policy_cb_data);\n \n /*\n+ * Read the refdb storage backend name out of the config file\n+ */\n+int ref_storage_backend_config(const char *var, const char *value, void *ptr);\n+\n+struct ref_storage_be;\n+/*\n  * Switch to an alternate ref storage backend.\n  */\n int set_ref_storage_backend(const char *name);\n \n+void register_ref_storage_backend(struct ref_storage_be *be);\n+\n #endif /* REFS_H */\ndiff --git a/setup.c b/setup.c\nindex d343725..a329a51 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,6 +264,15 @@ int get_common_dir_noenv(struct strbuf *sb, const char *gitdir)\n \treturn ret;\n }\n \n+int ref_storage_backend_config(const char *var, const char *value, void *ptr)\n+{\n+\tchar **cdata = ptr;\n+\n+\tif (!strcmp(var, \"extensions.refstorage\"))\n+\t\t*cdata = xstrdup(value);\n+\treturn 0;\n+}\n+\n /*\n  * Test if it looks like we're at a git directory.\n  * We want to see:\n@@ -373,6 +383,8 @@ static int check_repo_format(const char *var, const char *value, void *cb)\n \t\t\t;\n \t\telse if (!strcmp(ext, \"preciousobjects\"))\n \t\t\trepository_format_precious_objects = git_config_bool(var, value);\n+\t\telse if (!strcmp(ext, \"refstorage\"))\n+\t\t\tref_storage_backend = xstrdup(value);\n \t\telse\n \t\t\tstring_list_append(&unknown_extensions, ext);\n \t}\ndiff --git a/t/t0001-init.sh b/t/t0001-init.sh\nindex 295aa59..5e77994 100755\n--- a/t/t0001-init.sh\n+++ b/t/t0001-init.sh\n@@ -174,6 +174,30 @@ test_expect_success 'reinit' '\n \ttest_i18ncmp again/empty again/err2\n '\n \n+test_expect_success 'init with bogus storage backend fails' '\n+\n+\t(\n+\t\tmkdir again2 &&\n+\t\tcd again2 &&\n+\t\tgit init --ref-storage=test >out2 2>err2\n+\t)\n+'\n+\n+test_expect_failure 'reinit with changed storage backend fails' '\n+\n+\t(\n+\t\tmkdir again3 &&\n+\t\tcd again3 &&\n+\t\tgit init >out1 2>err1 &&\n+\t\tgit init --ref-storage=test >out2 2>err2\n+\t) &&\n+\ttest_i18ngrep \"Initialized empty\" again3/out1 &&\n+\ttest_i18ngrep \"Reinitialized existing\" again3/out2 &&\n+\t>again3/empty &&\n+\ttest_i18ncmp again3/empty again3/err1 &&\n+\ttest_i18ncmp again3/empty again3/err2\n+'\n+\n test_expect_success 'init with --template' '\n \tmkdir template-source &&\n \techo content >template-source/file &&\n-- \n2.4.2.749.g730654d-twtrsrc\n"},{"id":"276029","messageId":"1452788777-24954-17-git-send-email-dturner@twopensource.com","threadId":"41190","inReplyTo":"1452788777-24954-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 16/20] refs: check submodules ref storage config","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2016-01-14T16:26:13Z","receivedAt":"2016-01-14T16:26:13Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"All submodules must have the same ref storage (for now).  Confirm that\nthis is so before attempting to do anything with submodule refs.\n\nSigned-off-by: David Turner <dturner@twopensource.com>\n---\n refs.c | 42 ++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 42 insertions(+)\n\ndiff --git a/refs.c b/refs.c\nindex 9f946d0..3d4c0a6 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -297,8 +297,44 @@ int for_each_tag_ref(each_ref_fn fn, void *cb_data)\n \treturn for_each_ref_in(\"refs/tags/\", fn, cb_data);\n }\n \n+static int submodule_backend(const char *key, const char *value, void *data)\n+{\n+\tchar **path = data;\n+\tif (!strcmp(key, \"extensions.refstorage\"))\n+\t\t*path = xstrdup(value);\n+\treturn 0;\n+}\n+\n+static void check_submodule_backend(const char *submodule)\n+{\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tchar *submodule_storage_backend = xstrdup(\"files\");\n+\n+\tif (!submodule)\n+\t\tgoto done;\n+\n+\tstrbuf_git_path_submodule(&sb, submodule, \"%s\", \"\");\n+\n+\tif (!is_git_directory(sb.buf))\n+\t\tgoto done;\n+\n+\tstrbuf_reset(&sb);\n+\tstrbuf_git_path_submodule(&sb, submodule, \"config\");\n+\n+\tgit_config_from_file(submodule_backend, sb.buf,\n+\t\t\t     &submodule_storage_backend);\n+\tif (strcmp(submodule_storage_backend, ref_storage_backend))\n+\t\tdie(\"Ref storage '%s' for submodule '%s' does not match our storage, '%s'\",\n+\t\t    submodule_storage_backend, submodule, ref_storage_backend);\n+\n+done:\n+\tfree(submodule_storage_backend);\n+\tstrbuf_release(&sb);\n+}\n+\n int for_each_tag_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)\n {\n+\tcheck_submodule_backend(submodule);\n \treturn for_each_ref_in_submodule(submodule, \"refs/tags/\", fn, cb_data);\n }\n \n@@ -309,6 +345,7 @@ int for_each_branch_ref(each_ref_fn fn, void *cb_data)\n \n int for_each_branch_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)\n {\n+\tcheck_submodule_backend(submodule);\n \treturn for_each_ref_in_submodule(submodule, \"refs/heads/\", fn, cb_data);\n }\n \n@@ -319,6 +356,7 @@ int for_each_remote_ref(each_ref_fn fn, void *cb_data)\n \n int for_each_remote_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)\n {\n+\tcheck_submodule_backend(submodule);\n \treturn for_each_ref_in_submodule(submodule, \"refs/remotes/\", fn, cb_data);\n }\n \n@@ -1366,6 +1404,7 @@ int create_symref(const char *ref_target, const char *refs_heads_master,\n int resolve_gitlink_ref(const char *path, const char *refname,\n \t\t\tunsigned char *sha1)\n {\n+\tcheck_submodule_backend(path);\n \treturn the_refs_backend->resolve_gitlink_ref(path, refname, sha1);\n }\n \n@@ -1376,6 +1415,7 @@ int head_ref(each_ref_fn fn, void *cb_data)\n \n int head_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)\n {\n+\tcheck_submodule_backend(submodule);\n \treturn the_refs_backend->head_ref_submodule(submodule, fn, cb_data);\n }\n \n@@ -1386,6 +1426,7 @@ int for_each_ref(each_ref_fn fn, void *cb_data)\n \n int for_each_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)\n {\n+\tcheck_submodule_backend(submodule);\n \treturn the_refs_backend->for_each_ref_submodule(submodule, fn, cb_data);\n }\n \n@@ -1404,6 +1445,7 @@ int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data,\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+\tcheck_submodule_backend(submodule);\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-- \n2.4.2.749.g730654d-twtrsrc\n"},{"id":"276030","messageId":"1452788777-24954-18-git-send-email-dturner@twopensource.com","threadId":"41190","inReplyTo":"1452788777-24954-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 17/20] refs: allow ref backend to be set for clone","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2016-01-14T16:26:14Z","receivedAt":"2016-01-14T16:26:14Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"Add a new option, --ref-storage, 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 --ref-storage option option when cloning\nsubmodules.\n\nSigned-off-by: David Turner <dturner@twopensource.com>\n---\n Documentation/git-clone.txt |  5 +++++\n builtin/clone.c             |  5 +++++\n builtin/submodule--helper.c |  2 +-\n git-submodule.sh            | 13 +++++++++++++\n 4 files changed, 24 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt\nindex cb7966a..ffaf5b0 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  [--ref-storage=<name>]\n \t  [--recursive | --recurse-submodules] [--jobs <n>] [--] <repository>\n \t  [<directory>]\n \n@@ -225,6 +226,10 @@ objects from the source repository into a pack in the cloned repository.\n \tThe number of submodules fetched at the same time.\n \tDefaults to the `submodule.fetchJobs` option.\n \n+--ref-storage=<name>::\n+\tType of ref storage backend. Default is to use the original files\n+\tbased ref storage.\n+\n <repository>::\n \tThe (possibly remote) repository to clone from.  See the\n \t<<URLS,URLS>> section below for more information on specifying\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex dd26f57..a4c6396 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -95,6 +95,8 @@ 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, \"ref-storage\", &ref_storage_backend,\n+\t\t   N_(\"name\"), N_(\"name of backend type to use\")),\n \tOPT_END()\n };\n \n@@ -733,6 +735,9 @@ static int checkout(void)\n \t\tif (max_jobs != -1)\n \t\t\targv_array_pushf(&args, \"--jobs=%d\", max_jobs);\n \n+\t\targv_array_pushl(&args, \"--ref-storage\",\n+\t\t\t\t ref_storage_backend, NULL);\n+\n \t\terr = run_command_v_opt(args.argv, RUN_GIT_CMD);\n \t\targv_array_clear(&args);\n \t}\ndiff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\nindex 8002187..849da47 100644\n--- a/builtin/submodule--helper.c\n+++ b/builtin/submodule--helper.c\n@@ -140,7 +140,7 @@ 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+\targv_array_pushl(&cp.args, \"--ref-storage\", ref_storage_backend, NULL);\n \targv_array_push(&cp.args, url);\n \targv_array_push(&cp.args, path);\n \ndiff --git a/git-submodule.sh b/git-submodule.sh\nindex 10c5af9..022902b 100755\n--- a/git-submodule.sh\n+++ b/git-submodule.sh\n@@ -637,6 +637,14 @@ cmd_update()\n \t\t--checkout)\n \t\t\tupdate=\"checkout\"\n \t\t\t;;\n+\t\t--ref-storage=*)\n+\t\t\tref_storage=\"$1\"\n+\t\t\t;;\n+\t\t--ref-storage)\n+\t\t\tcase \"$2\" in '') usage ;; esac\n+\t\t\tref_storage=\"$2\"\n+\t\t\tshift\n+\t\t\t;;\n \t\t--depth)\n \t\t\tcase \"$2\" in '') usage ;; esac\n \t\t\tdepth=\"--depth=$2\"\n@@ -670,6 +678,11 @@ cmd_update()\n \tif test -n \"$init\"\n \tthen\n \t\tcmd_init \"--\" \"$@\" || return\n+\telse\n+\t\tif test -n \"$ref_storage\"\n+\t\tthen\n+\t\t\tusage\n+\t\tfi\n \tfi\n \n \tgit submodule--helper update-clone ${GIT_QUIET:+--quiet} \\\n-- \n2.4.2.749.g730654d-twtrsrc\n"},{"id":"276032","messageId":"1452788777-24954-19-git-send-email-dturner@twopensource.com","threadId":"41190","inReplyTo":"1452788777-24954-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 18/20] svn: learn ref-storage argument","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2016-01-14T16:26:15Z","receivedAt":"2016-01-14T16:26:15Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"git svn learns to pass the ref-storage command-line argument (to init\nand clone) through to git init.\n\nSigned-off-by: David Turner <dturner@twopensource.com>\n---\n git-svn.perl | 6 +++++-\n 1 file changed, 5 insertions(+), 1 deletion(-)\n\ndiff --git a/git-svn.perl b/git-svn.perl\nindex fa5f253..15d1544 100755\n--- a/git-svn.perl\n+++ b/git-svn.perl\n@@ -141,7 +141,7 @@ my %fc_opts = ( 'follow-parent|follow!' => \\$Git::SVN::_follow_parent,\n \t\t'localtime' => \\$Git::SVN::_localtime,\n \t\t%remote_opts );\n \n-my ($_trunk, @_tags, @_branches, $_stdlayout);\n+my ($_trunk, @_tags, @_branches, $_stdlayout, $_ref_storage);\n my %icv;\n my %init_opts = ( 'template=s' => \\$_template, 'shared:s' => \\$_shared,\n                   'trunk|T=s' => \\$_trunk, 'tags|t=s@' => \\@_tags,\n@@ -153,6 +153,7 @@ my %init_opts = ( 'template=s' => \\$_template, 'shared:s' => \\$_shared,\n \t\t  'use-svnsync-props' => sub { $icv{useSvnsyncProps} = 1 },\n \t\t  'rewrite-root=s' => sub { $icv{rewriteRoot} = $_[1] },\n \t\t  'rewrite-uuid=s' => sub { $icv{rewriteUUID} = $_[1] },\n+\t\t  'ref-storage=s' => \\$_ref_storage,\n                   %remote_opts );\n my %cmt_opts = ( 'edit|e' => \\$_edit,\n \t\t'rmdir' => \\$Git::SVN::Editor::_rmdir,\n@@ -469,6 +470,9 @@ sub do_git_init_db {\n \t\t\t\tpush @init_db, \"--shared\";\n \t\t\t}\n \t\t}\n+\t\tif (defined $_ref_storage) {\n+\t\t    push @init_db, \"--ref-storage=\" . $_ref_storage;\n+\t\t}\n \t\tcommand_noisy(@init_db);\n \t\t$_repository = Git->repository(Repository => \".git\");\n \t}\n-- \n2.4.2.749.g730654d-twtrsrc\n"},{"id":"276034","messageId":"1452788777-24954-20-git-send-email-dturner@twopensource.com","threadId":"41190","inReplyTo":"1452788777-24954-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 19/20] refs: add LMDB refs backend","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2016-01-14T16:26:16Z","receivedAt":"2016-01-14T16:26:16Z","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 --ref-storage to git init.  If those tests are\nchanged to use the update-ref machinery or test-refs-lmdb-backend (or,\nin the case of packed-refs, corrupt refs, and dumb fetch tests, are\nskipped), the only remaining failing tests are the git-new-workdir\ntests and the gitweb tests.\n\nSigned-off-by: David Turner <dturner@twopensource.com>\n---\n .gitignore                                     |    1 +\n Documentation/config.txt                       |    7 +\n Documentation/git-clone.txt                    |    3 +-\n Documentation/git-init.txt                     |    2 +-\n Documentation/technical/refs-lmdb-backend.txt  |   52 +\n Documentation/technical/repository-version.txt |    5 +\n Makefile                                       |   12 +\n config.c                                       |   29 +\n configure.ac                                   |   33 +\n contrib/workdir/git-new-workdir                |    3 +\n refs.h                                         |    2 +\n refs/lmdb-backend.c                            | 2051 ++++++++++++++++++++++++\n setup.c                                        |   11 +-\n test-refs-lmdb-backend.c                       |   64 +\n transport.c                                    |    7 +-\n 15 files changed, 2275 insertions(+), 7 deletions(-)\n create mode 100644 Documentation/technical/refs-lmdb-backend.txt\n create mode 100644 refs/lmdb-backend.c\n create mode 100644 test-refs-lmdb-backend.c\n\ndiff --git a/.gitignore b/.gitignore\nindex 1c2f832..87d45a2 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-lmdb-backend\n /test-regex\n /test-revision-walking\n /test-run-command\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex c374b0e..7289f10 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -1142,6 +1142,13 @@ difftool.<tool>.cmd::\n difftool.prompt::\n \tPrompt before each invocation of the diff tool.\n \n+extensions.refsStorage::\n+\tType of refs storage backend. Default is to use the original\n+\tfiles based ref storage.  When set to \"lmdb\", refs are stored in\n+\tthe lmdb database backend.  This setting reflects the refs\n+\tbackend that is currently in use; it is not possible to change\n+\tthe backend by changing this setting.\n+\n fetch.recurseSubmodules::\n \tThis option can be either set to a boolean value or to 'on-demand'.\n \tSetting it to a boolean changes the behavior of fetch and pull to\ndiff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt\nindex ffaf5b0..52a8204 100644\n--- a/Documentation/git-clone.txt\n+++ b/Documentation/git-clone.txt\n@@ -228,7 +228,8 @@ objects from the source repository into a pack in the cloned repository.\n \n --ref-storage=<name>::\n \tType of ref storage backend. Default is to use the original files\n-\tbased ref storage.\n+\tbased ref storage. Set to \"lmdb\" to store refs in the lmdb database\n+\tbackend.\n \n <repository>::\n \tThe (possibly remote) repository to clone from.  See the\ndiff --git a/Documentation/git-init.txt b/Documentation/git-init.txt\nindex d2b150f..3b30bf3 100644\n--- a/Documentation/git-init.txt\n+++ b/Documentation/git-init.txt\n@@ -116,7 +116,7 @@ does not exist, it will be created.\n --ref-storage=<name>::\n Type of refs storage backend. Default is to use the original \"files\"\n storage, which stores ref data in files in .git/refs and\n-.git/packed-refs.\n+.git/packed-refs. Set to \"lmdb\" to activate the lmdb storage backend.\n \n TEMPLATE DIRECTORY\n ------------------\ndiff --git a/Documentation/technical/refs-lmdb-backend.txt b/Documentation/technical/refs-lmdb-backend.txt\nnew file mode 100644\nindex 0000000..c328bfc\n--- /dev/null\n+++ b/Documentation/technical/refs-lmdb-backend.txt\n@@ -0,0 +1,52 @@\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 stored 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 update.  The\n+key format is logs/[refname]\\0[timestamp].  The timestamp is a 64-bit\n+unsigned integer number of nanoseconds since 1/1/1970, stored in\n+network byte order.  This means that reflog entries are\n+chronologically ordered.  Because LMDB is a btree database, we can\n+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, including the trailing LF. The date in the reflog value\n+matches the date in the timestamp field.\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 00ad379..fca5ecd 100644\n--- a/Documentation/technical/repository-version.txt\n+++ b/Documentation/technical/repository-version.txt\n@@ -86,3 +86,8 @@ for testing format-1 compatibility.\n When the config key `extensions.preciousObjects` is set to `true`,\n objects in the repository MUST NOT be deleted (e.g., by `git-prune` or\n `git repack -d`).\n+\n+`refStorage`\n+~~~~~~~~~~~~\n+This extension allows the use of alternate ref storage backends.  The\n+only defined value is `lmdb`.\ndiff --git a/Makefile b/Makefile\nindex fc2f1ab..f3325de 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1041,6 +1041,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/lmdb-backend.o\n+\tTEST_PROGRAMS_NEED_X += test-refs-lmdb-backend\n+endif\n+\n ifdef HAVE_ALLOCA_H\n \tBASIC_CFLAGS += -DHAVE_ALLOCA_H\n endif\n@@ -2139,6 +2150,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/config.c b/config.c\nindex 86a5eb2..1daf25a 100644\n--- a/config.c\n+++ b/config.c\n@@ -11,6 +11,7 @@\n #include \"strbuf.h\"\n #include \"quote.h\"\n #include \"hashmap.h\"\n+#include \"refs.h\"\n #include \"string-list.h\"\n #include \"utf8.h\"\n \n@@ -1207,6 +1208,34 @@ 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\tchar *storage = NULL;\n+\n+\t\t/*\n+\t\t * make sure we always read the ref storage config\n+\t\t * from the extensions section on startup\n+\t\t */\n+\t\tret += git_config_from_file(ref_storage_backend_config,\n+\t\t\t\t\t    repo_config, &storage);\n+\n+\t\tif (!storage)\n+\t\t\tstorage = xstrdup(\"\");\n+\n+\t\tif ((!*storage) ||\n+\t\t    (!strcmp(storage, \"files\"))) {\n+\t\t\t/* default backend, nothing to do */\n+\t\t\tfree(storage);\n+\t\t} else if (!strcmp(storage, \"lmdb\")) {\n+#ifdef USE_LIBLMDB\n+\t\t\tref_storage_backend = storage;\n+\t\t\tregister_ref_storage_backend(&refs_be_lmdb);\n+\t\t\tset_ref_storage_backend(ref_storage_backend);\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'\", storage);\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 89e2590..3853bec 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..c035a43 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 extensions.refstorage)\" = \"lmdb\" && die \"git-new-workdir is incompatible with the refs lmdb storage\"\n+\n case \"$git_dir\" in\n .git)\n \tgit_dir=\"$orig_git/.git\"\ndiff --git a/refs.h b/refs.h\nindex a916a6b..f75d695 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -516,6 +516,8 @@ extern int reflog_expire(const char *refname, const unsigned char *sha1,\n int ref_storage_backend_config(const char *var, const char *value, void *ptr);\n \n struct ref_storage_be;\n+\n+extern struct ref_storage_be refs_be_lmdb;\n /*\n  * Switch to an alternate ref storage backend.\n  */\ndiff --git a/refs/lmdb-backend.c b/refs/lmdb-backend.c\nnew file mode 100644\nindex 0000000..470c79f\n--- /dev/null\n+++ b/refs/lmdb-backend.c\n@@ -0,0 +1,2051 @@\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 \"refs-internal.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+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+\tassert(path);\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_init_db(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+\tif (!db_path)\n+\t\tdb_path = xstrdup(real_path(get_refdb_path(get_git_common_dir())));\n+\n+\tif (mkdir(db_path, 0775) && errno != EEXIST) {\n+\t\tstrbuf_addf(err, \"%s\", strerror(errno));\n+\t\treturn -1;\n+\t}\n+\n+\treturn 0;\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, int *needs_free)\n+{\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tstruct strbuf path = STRBUF_INIT;\n+\tstruct stat st;\n+\tint ret = -1;\n+\n+\tsubmodule_path(&path, submodule, refname);\n+\n+#ifndef NO_SYMLINK_HEAD\n+\tif (lstat(path.buf, &st)) {\n+\t\tif (errno == ENOENT)\n+\t\t\tret = MDB_NOTFOUND;\n+\t\tgoto done;\n+\t}\n+\tif (S_ISLNK(st.st_mode)) {\n+\t\tstrbuf_readlink(&sb, path.buf, 0);\n+\t\tif (starts_with(sb.buf, \"refs/\") &&\n+\t\t    !check_refname_format(sb.buf, 0)) {\n+\t\t\tval->mv_data = xmalloc(sb.len + 6);\n+\t\t\tval->mv_size = sprintf(val->mv_data, \"ref: %s\",\n+\t\t\t\t\t       sb.buf) + 1;\n+\t\t\tret = 0;\n+\t\t} else {\n+\t\t\tret = MDB_NOTFOUND;\n+\t\t}\n+\t\tstrbuf_release(&sb);\n+\t\tgoto done;\n+\t}\n+#endif\n+\n+\tif (strbuf_read_file(&sb, path.buf, 200) < 0) {\n+\t\tstrbuf_release(&sb);\n+\t\tif (errno == ENOENT)\n+\t\t\tret = MDB_NOTFOUND;\n+\t\tgoto done;\n+\t}\n+\tstrbuf_rtrim(&sb);\n+\n+\tval->mv_data = strbuf_detach(&sb, &val->mv_size);\n+\tval->mv_size++;\n+\n+\tret = 0;\n+done:\n+\tstrbuf_release(&path);\n+\t*needs_free = !ret;\n+\treturn ret;\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+/*\n+ * Read a ref.  If the ref is a per-worktree ref, read it from disk.\n+ * Otherwise, read it from LMDB.  LMDB manages its own memory, so the\n+ * data returned in *val will ordinarily not need to be freed.  But\n+ * when a per-worktree ref is (successfully) read, non-LMDB memory is\n+ * allocated.  In this case, *needs_free is set so that the caller can\n+ * free the memory when it is done with it.\n+ */\n+static int mdb_get_or_die(struct lmdb_transaction *transaction, MDB_val *key,\n+\t\t\t  MDB_val *val, int *needs_free)\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, needs_free);\n+\n+\t*needs_free = 0;\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+\tif (!db_path)\n+\t\tdb_path = xstrdup(real_path(get_refdb_path(get_git_common_dir())));\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+\tint needs_free = 0;\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, &needs_free)) {\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\tif (needs_free)\n+\t\t\tfree(val.mv_data);\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+\tint needs_free = 0;\n+\tconst char *ret;\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, &needs_free)) {\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+\tret = parse_ref_data(transaction, refname, ref_data, sha1,\n+\t\t\t     resolve_flags, flags, bad_name);\n+\tif (needs_free)\n+\t\tfree(ref_data);\n+\treturn ret;\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 sha) new (raw sha) 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\treturn 0; /* corrupt? */\n+\n+\ttimestamp = strtoul(email_end + 2, &message, 10);\n+\n+\tif (!timestamp ||\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/* if no reflog exists, we're done */\n+\tif (mdb_cursor_get_or_die(cursor, &key, &val, MDB_SET_RANGE) ||\n+\t    strcmp(key.mv_data, log_key))\n+\t\tgoto done;\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+\tstrbuf_release(&buf);\n+\n+done:\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\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 = xstrdup(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 refs_be_files.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) ||\n+\t\t    ((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(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 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_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(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(transaction.txn);\n+\t\t\tret = -1;\n+\t\t\tgoto cleanup;\n+\t\t}\n+\n+\t}\n+\tret = mdb_transaction_commit(&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_delete_refs(struct string_list *refnames)\n+{\n+\tint i;\n+\tstruct strbuf err = STRBUF_INIT;\n+\tint result = 0;\n+\n+\tif (!refnames->nr)\n+\t\treturn 0;\n+\n+\tlmdb_transaction_begin_flags_or_die(0);\n+\n+\tfor (i = 0; i < refnames->nr; i++) {\n+\t\tconst char *refname = refnames->items[i].string;\n+\n+\t\tif (lmdb_transaction_update(refname, null_sha1, NULL,\n+\t\t\t\t\t    REF_DELETING, NULL, &err))\n+\t\t\tresult |= error(_(\"could not remove reference %s: %s\"),\n+\t\t\t\t\trefname, err.buf);\n+\t}\n+\n+\tresult |= mdb_transaction_commit(&transaction, &err);\n+\tstrbuf_release(&err);\n+\treturn 0;\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 refs_be_files.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 refs_be_files.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 refs_be_files.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 refs_be_files.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+\t/* TODO: Don't create ref d/f conflicts */\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+\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+\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_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+\tint needs_free = 0;\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, &needs_free);\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+\tif (needs_free)\n+\t\tfree(val.mv_data);\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, '\\t');\n+\tif (msg)\n+\t\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+\tstruct strbuf sb = 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 entry here, because this is\n+\t * just for tests, so it's OK to be a bit inefficient */\n+\n+\twhile (strbuf_getline(&input, stdin, '\\n') != EOF) {\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+\tstrbuf_release(&input);\n+\tstrbuf_release(&sb);\n+\tassert(mdb_transaction_commit(&transaction, NULL) == 0);\n+\tfree(key.mv_data);\n+}\n+\n+struct ref_storage_be refs_be_lmdb = {\n+\tNULL,\n+\t\"lmdb\",\n+\tlmdb_init_db,\n+\tlmdb_transaction_commit,\n+\tlmdb_transaction_commit, /* initial commit */\n+\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+\n+\tlmdb_pack_refs,\n+\tlmdb_peel_ref,\n+\tlmdb_create_symref,\n+\tlmdb_delete_refs,\n+\tlmdb_rename_ref,\n+\n+\tlmdb_resolve_ref_unsafe,\n+\tlmdb_verify_refname_available,\n+\tlmdb_resolve_gitlink_ref,\n+\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+};\ndiff --git a/setup.c b/setup.c\nindex a329a51..9613bcc 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -279,7 +279,7 @@ int ref_storage_backend_config(const char *var, const char *value, void *ptr)\n  *\n  *  - either an objects/ directory _or_ the proper\n  *    GIT_OBJECT_DIRECTORY environment variable\n- *  - a refs/ directory\n+ *  - a refdb/ directory or 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@@ -313,8 +313,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:\ndiff --git a/test-refs-lmdb-backend.c b/test-refs-lmdb-backend.c\nnew file mode 100644\nindex 0000000..5cf61e6\n--- /dev/null\n+++ b/test-refs-lmdb-backend.c\n@@ -0,0 +1,64 @@\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-lmdb-backend <key>\",\n+\t\"git test-refs-lmdb-backend <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+\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+\tregister_ref_storage_backend(&refs_be_lmdb);\n+\tset_ref_storage_backend(\"lmdb\");\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+}\ndiff --git a/transport.c b/transport.c\nindex 67f3666..7288093 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -1418,8 +1418,11 @@ static int refs_from_alternate_cb(struct alternate_object_database *e,\n \t\tgoto out;\n \t/* Is this a git repository with refs? */\n \tmemcpy(other + len - 8, \"/refs\", 6);\n-\tif (!is_directory(other))\n-\t\tgoto out;\n+\tif (!is_directory(other)) {\n+\t\tmemcpy(other + len - 8, \"/refdb\", 7);\n+\t\tif (!is_directory(other))\n+\t\t\tgoto out;\n+\t}\n \tother[len - 8] = '\\0';\n \tremote = remote_get(other);\n \ttransport = transport_get(remote, other);\n-- \n2.4.2.749.g730654d-twtrsrc\n"},{"id":"276035","messageId":"1452788777-24954-21-git-send-email-dturner@twopensource.com","threadId":"41190","inReplyTo":"1452788777-24954-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 20/20] refs: tests for lmdb backend","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2016-01-14T16:26:17Z","receivedAt":"2016-01-14T16:26:17Z","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-lmdb-backend.sh        | 1109 +++++++++++++++++++++++++++++++++++\n t/t1470-refs-lmdb-backend-reflog.sh |  359 ++++++++++++\n t/t1480-refs-lmdb-submodule.sh      |   85 +++\n t/test-lib.sh                       |    1 +\n 4 files changed, 1554 insertions(+)\n create mode 100755 t/t1460-refs-lmdb-backend.sh\n create mode 100755 t/t1470-refs-lmdb-backend-reflog.sh\n create mode 100755 t/t1480-refs-lmdb-submodule.sh\n\ndiff --git a/t/t1460-refs-lmdb-backend.sh b/t/t1460-refs-lmdb-backend.sh\nnew file mode 100755\nindex 0000000..31442e7\n--- /dev/null\n+++ b/t/t1460-refs-lmdb-backend.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-lmdb-backend \"$1\"\n+}\n+\n+delete_ref() {\n+\ttest-refs-lmdb-backend -d \"$1\"\n+}\n+\n+write_ref() {\n+\ttest-refs-lmdb-backend \"$1\" \"$2\"\n+}\n+\n+raw_reflog() {\n+\ttest-refs-lmdb-backend -l \"$1\"\n+}\n+\n+delete_all_reflogs() {\n+\ttest-refs-lmdb-backend -c\n+}\n+\n+append_reflog() {\n+\ttest-refs-lmdb-backend -a \"$1\"\n+}\n+\n+Z=$_z40\n+\n+test_expect_success setup '\n+\tgit init --ref-storage=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-lmdb-backend 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-lmdb-backend-reflog.sh b/t/t1470-refs-lmdb-backend-reflog.sh\nnew file mode 100755\nindex 0000000..83bfba0\n--- /dev/null\n+++ b/t/t1470-refs-lmdb-backend-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-lmdb-backend -l \"$1\"\n+}\n+\n+append_reflog() {\n+\ttest-refs-lmdb-backend -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 --ref-storage=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/t1480-refs-lmdb-submodule.sh b/t/t1480-refs-lmdb-submodule.sh\nnew file mode 100755\nindex 0000000..c7d911a\n--- /dev/null\n+++ b/t/t1480-refs-lmdb-submodule.sh\n@@ -0,0 +1,85 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2016 Twitter, Inc\n+# Based on t5531-deep-submodule-push.sh\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+test_expect_success setup '\n+\tmkdir pub.git &&\n+\tGIT_DIR=pub.git git init --bare &&\n+\tGIT_DIR=pub.git git config receive.fsckobjects true &&\n+\tmkdir work &&\n+\t(\n+\t\tcd work &&\n+\t\tgit init --ref-storage=lmdb &&\n+\t\tgit config push.default matching &&\n+\t\tmkdir -p gar/bage &&\n+\t\t(\n+\t\t\tcd gar/bage &&\n+\t\t\tgit init --ref-storage=lmdb  &&\n+\t\t\tgit config push.default matching &&\n+\t\t\t>junk &&\n+\t\t\tgit add junk &&\n+\t\t\tgit commit -m \"Initial junk\"\n+\t\t) &&\n+\t\tgit add gar/bage &&\n+\t\tgit commit -m \"Initial superproject\"\n+\t)\n+'\n+\n+test_expect_success 'submodules have same ref storage' '\n+\tgit init --ref-storage=lmdb test &&\n+\t(\n+\t\tcd test &&\n+\t\tgit submodule add ../work/gar/bage w\n+\t) &&\n+\t(\n+\t\tcd test/w &&\n+\t\tgit config extensions.refstorage >cfg &&\n+\t\techo lmdb >expect &&\n+\t\ttest_cmp cfg expect\n+\t)\n+'\n+\n+test_expect_success 'push with correct backend' '\n+\t(\n+\t\tcd work/gar/bage &&\n+\t\t>junk2 &&\n+\t\tgit add junk2 &&\n+\t\tgit commit -m \"Second junk\"\n+\t) &&\n+\t(\n+\t\tcd work &&\n+\t\tgit add gar/bage &&\n+\t\tgit commit -m \"Second commit for gar/bage\" &&\n+\t\tgit push --recurse-submodules=check ../pub.git master\n+\t)\n+'\n+\n+test_expect_success 'commit with different backend fails' '\n+\t(\n+\t\tcd work/gar/bage &&\n+\t\ttest_commit junk3 &&\n+\t\t# manually convert to files-backend\n+\t\tgitdir=\"$(git rev-parse --git-dir)\" &&\n+\t\tmkdir -p \"$gitdir/refs/heads\" &&\n+\t\tgit rev-parse HEAD >\"$gitdir/refs/heads/master\" &&\n+\t\tgit config --local --unset extensions.refStorage &&\n+\t\trm -r \"$gitdir/refdb\"\n+\t) &&\n+\t(\n+\t\tcd work &&\n+\t\ttest_must_fail git add gar/bage\n+\t)\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.749.g730654d-twtrsrc\n"},{"id":"276147","messageId":"1452857550-25887-1-git-send-email-szeder@ira.uka.de","threadId":"41190","inReplyTo":"1452788777-24954-18-git-send-email-dturner@twopensource.com","subject":"Re: [PATCH v3 17/20] refs: allow ref backend to be set for clone","fromName":"SZEDER Gábor","fromEmail":"szeder@ira.uka.de","sentAt":"2016-01-15T11:32:30Z","receivedAt":"2016-01-15T11:32:30Z","isPatch":true,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"Hi,\n\nThis change is more about 'git clone' than about refs, therefore\nI think the subject line would be better as:\n\n  clone: allow setting alternate ref backend\n\nCould you please squash this in to keep the completion script up to date?\nIs there or will there be a way to list available ref backends, so we\ncould complete possible options for --ref-storage=<TAB>, too?\n\n------ >8 ------\n\nSubject: completion: git clone --ref-storage=\n---\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex ab4da7f97917..c970d3c0d0a3 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -1092,6 +1092,7 @@ _git_clone ()\n \t\t\t--depth\n \t\t\t--single-branch\n \t\t\t--branch\n+\t\t\t--ref-storage=\n \t\t\t\"\n \t\treturn\n \t\t;;\n"},{"id":"276148","messageId":"1452857638-25956-1-git-send-email-szeder@ira.uka.de","threadId":"41190","inReplyTo":"1452788777-24954-16-git-send-email-dturner@twopensource.com","subject":"Re: [PATCH v3 15/20] init: allow alternate backends to be set for new repos","fromName":"SZEDER Gábor","fromEmail":"szeder@ira.uka.de","sentAt":"2016-01-15T11:33:58Z","receivedAt":"2016-01-15T11:33:58Z","isPatch":true,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"Hi,\n\nPerhaps the subject line could benefit from a s/backends/ref backends/.\nOr even 'init: allow setting alternate ref backends'\n\nAnyway, could you please squash this in to keep the completion script\nup to date?\n\n------ >8 ------\n\nSubject: completion: git init --ref-storage=\n---\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex c970d3c0d0a3..743d8509a69f 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -1363,7 +1363,8 @@ _git_init ()\n \t\treturn\n \t\t;;\n \t--*)\n-\t\t__gitcomp \"--quiet --bare --template= --shared --shared=\"\n+\t\t__gitcomp \"--quiet --bare --template= --shared --shared=\n+\t\t\t--ref-storage=\"\n \t\treturn\n \t\t;;\n \tesac\n"},{"id":"276149","messageId":"1452857660-26026-1-git-send-email-szeder@ira.uka.de","threadId":"41190","inReplyTo":"1452788777-24954-19-git-send-email-dturner@twopensource.com","subject":"Re: [PATCH v3 18/20] svn: learn ref-storage argument","fromName":"SZEDER Gábor","fromEmail":"szeder@ira.uka.de","sentAt":"2016-01-15T11:34:20Z","receivedAt":"2016-01-15T11:34:20Z","isPatch":true,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"Hi,\n\nThe third and last \"squash request\" for the completion script.\n\n------ >8 ------\n\nSubject: completion: git svn (init|clone) --ref-storage=\n---\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 743d8509a69f..9e48d069f07a 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -2461,7 +2461,7 @@ _git_svn ()\n \t\t\t--branches= --stdlayout --minimize-url\n \t\t\t--no-metadata --use-svm-props --use-svnsync-props\n \t\t\t--rewrite-root= --prefix= --use-log-author\n-\t\t\t--add-author-from $remote_opts\n+\t\t\t--add-author-from --ref-storage= $remote_opts\n \t\t\t\"\n \t\tlocal cmt_opts=\"\n \t\t\t--edit --rmdir --find-copies-harder --copy-similarity=\n"},{"id":"276151","messageId":"20160115125139.GI10612@hank","threadId":"41190","inReplyTo":"1452788777-24954-16-git-send-email-dturner@twopensource.com","subject":"Re: [PATCH v3 15/20] init: allow alternate backends to be set for new repos","fromName":"Thomas Gummerer","fromEmail":"t.gummerer@gmail.com","sentAt":"2016-01-15T12:51:39Z","receivedAt":"2016-01-15T12:51:39Z","isPatch":true,"sender":{"key":"t.gummerer@gmail.com","avatar":"https://avatars.githubusercontent.com/u/191004?v=4"},"body":"On 01/14, David Turner wrote:\n> git init learns a new argument --ref-storage.  Presently, only\n> \"files\" is supported, but later we will add other backends.\n>\n> When this argument is used, the repository's extensions.refStorage\n> configuration value is set (as well as core.repositoryformatversion),\n> and the refs backend's initdb function is used to set up the ref\n> database.\n>\n> Signed-off-by: David Turner <dturner@twopensource.com>\n> ---\n>  Documentation/git-init-db.txt |  2 +-\n>  Documentation/git-init.txt    |  7 ++++++-\n>  builtin/init-db.c             | 31 ++++++++++++++++++++++++++-----\n>  cache.h                       |  2 ++\n>  path.c                        | 29 +++++++++++++++++++++++++++--\n>  refs.c                        |  8 ++++++++\n>  refs.h                        |  8 ++++++++\n>  setup.c                       | 12 ++++++++++++\n>  t/t0001-init.sh               | 24 ++++++++++++++++++++++++\n>  9 files changed, 114 insertions(+), 9 deletions(-)\n>\n> diff --git a/Documentation/git-init-db.txt b/Documentation/git-init-db.txt\n> index 648a6cd..d03fb69 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>]] [--ref-storage=<name>]\n>\n>\n>  DESCRIPTION\n> diff --git a/Documentation/git-init.txt b/Documentation/git-init.txt\n> index 8174d27..d2b150f 100644\n> --- a/Documentation/git-init.txt\n> +++ b/Documentation/git-init.txt\n> @@ -12,7 +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> -\n> +\t  [--ref-storage=<name>]\n>\n>  DESCRIPTION\n>  -----------\n> @@ -113,6 +113,11 @@ does not exist, it will be created.\n>\n>  --\n>\n> +--ref-storage=<name>::\n> +Type of refs storage backend. Default is to use the original \"files\"\n> +storage, which stores ref data in files in .git/refs and\n> +.git/packed-refs.\n> +\n>  TEMPLATE DIRECTORY\n>  ------------------\n>\n> diff --git a/builtin/init-db.c b/builtin/init-db.c\n> index 4771e7e..ebc747c 100644\n> --- a/builtin/init-db.c\n> +++ b/builtin/init-db.c\n> @@ -24,6 +24,7 @@ static int init_is_bare_repository = 0;\n>  static int init_shared_repository = -1;\n>  static const char *init_db_template_dir;\n>  static const char *git_link;\n> +static char *requested_ref_storage_backend;\n>\n>  static void copy_templates_1(struct strbuf *path, struct strbuf *template,\n>  \t\t\t     DIR *dir)\n> @@ -179,6 +180,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,9 +206,6 @@ static int create_default_files(const char *template_path)\n>  \t\tadjust_shared_perm(get_git_dir());\n>  \t}\n>\n> -\tif (refs_init_db(&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.\n> @@ -214,14 +213,34 @@ static int create_default_files(const char *template_path)\n>  \tpath = git_path_buf(&buf, \"HEAD\");\n>  \treinit = (!access(path, R_OK)\n>  \t\t  || readlink(path, junk, sizeof(junk)-1) != -1);\n> -\tif (!reinit) {\n> +\tif (reinit) {\n> +\t\tif (requested_ref_storage_backend &&\n> +\t\t    strcmp(ref_storage_backend, requested_ref_storage_backend))\n> +\t\t\tdie(\"You can't change the refs storage type (was %s; you requested %s)\",\n> +\t\t\t    ref_storage_backend, requested_ref_storage_backend);\n> +\t} else {\n>  \t\tif (create_symref(\"HEAD\", \"refs/heads/master\", NULL) < 0)\n>  \t\t\texit(1);\n>  \t}\n>\n> +\tif (requested_ref_storage_backend)\n> +\t\tref_storage_backend = requested_ref_storage_backend;\n> +\tif (strcmp(ref_storage_backend, \"files\")) {\n> +\t\tgit_config_set(\"extensions.refStorage\", ref_storage_backend);\n> +\t\tgit_config_set(\"core.repositoryformatversion\", ref_storage_backend);\n> +#ifdef USE_LIBLMDB\n> +\t\tregister_ref_storage_backend(&refs_be_lmdb);\n\nrefs_be_lmdb is not yet defined at this point in the patch series.  It\ndoesn't break anything, because USE_LIBLMDB doesn't leak through the\nmakefile yet, but I still think it would make more sense to have the\nifdef in 19/20.\n\n> +#endif\n> +\t\tset_ref_storage_backend(ref_storage_backend);\n\nMore importantly, there is no check whether setting the ref storage\nbackend succeeds.  If a user accidentally sets an invalid value for\nthe ref backend, a broken repository will be created without even\nwarning the user.\n\nWhile the repository will still work fine at this point in the series,\ngit will die with an invalid ref backend in the config after 19/20.\nIt can be fixed by setting extensions.refStorage to files in the\nconfig, because the backend is initialized to the default files\nbackend below when the ref backend cannot be set.\n\nI think it would be best to die() here, if setting the ref backend\ndoesn't succeed, and clean up the files that were already written,\ninstead of leaving the user with a broken repository.\n\n> +\t\trepo_version = 1;\n> +\t}\n> +\n> +\tif (refs_init_db(&err, shared_repository))\n> +\t\tdie(\"failed to set up refs db: %s\", err.buf);\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 +488,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, \"ref-storage\", &requested_ref_storage_backend,\n> +\t\t\t   N_(\"name\"), N_(\"name of backend type to use\")),\n>  \t\tOPT_END()\n>  \t};\n>\n\n[...]\n\n> +test_expect_success 'init with bogus storage backend fails' '\n> +\n> +\t(\n> +\t\tmkdir again2 &&\n> +\t\tcd again2 &&\n> +\t\tgit init --ref-storage=test >out2 2>err2\n> +\t)\n> +'\n\nI noticed the above mainly because of this test, which doesn't seem to\ntest what it claims to test.  It only seems to test that git init\nsucceeds, and writes something to out2 and err2, it's missing the\nchecks that the contents are actually what they're supposed to be.\n\n> +\n> +test_expect_failure 'reinit with changed storage backend fails' '\n> +\n> +\t(\n> +\t\tmkdir again3 &&\n> +\t\tcd again3 &&\n> +\t\tgit init >out1 2>err1 &&\n> +\t\tgit init --ref-storage=test >out2 2>err2\n> +\t) &&\n> +\ttest_i18ngrep \"Initialized empty\" again3/out1 &&\n> +\ttest_i18ngrep \"Reinitialized existing\" again3/out2 &&\n> +\t>again3/empty &&\n> +\ttest_i18ncmp again3/empty again3/err1 &&\n> +\ttest_i18ncmp again3/empty again3/err2\n> +'\n> +\n>  test_expect_success 'init with --template' '\n>  \tmkdir template-source &&\n>  \techo content >template-source/file &&\n> --\n> 2.4.2.749.g730654d-twtrsrc\n>\n> --\n> To unsubscribe from this list: send the line \"unsubscribe git\" in\n> the body of a message to majordomo@vger.kernel.org\n> More majordomo info at  http://vger.kernel.org/majordomo-info.html\n"},{"id":"276157","messageId":"20160115133317.GJ10612@hank","threadId":"41190","inReplyTo":"1452788777-24954-20-git-send-email-dturner@twopensource.com","subject":"Re: [PATCH v3 19/20] refs: add LMDB refs backend","fromName":"Thomas Gummerer","fromEmail":"t.gummerer@gmail.com","sentAt":"2016-01-15T13:33:17Z","receivedAt":"2016-01-15T13:33:17Z","isPatch":true,"sender":{"key":"t.gummerer@gmail.com","avatar":"https://avatars.githubusercontent.com/u/191004?v=4"},"body":"[I don't know too much about the refs code, but was still interested\nin this patch series, so below are the few things that I noticed while\nreading through it.]\n\nOn 01/14, David Turner wrote:\n> Add a database backend for refs using LMDB.  This backend runs git\n> for-each-ref about 30% faster than the files backend with fully-packed\n> refs on a repo with ~120k refs.  It's also about 4x faster than using\n> fully-unpacked refs.  In addition, and perhaps more importantly, it\n> avoids case-conflict issues on OS X.\n>\n> LMDB has a few features that make it suitable for usage in git:\n>\n> 1. It is relatively lightweight; it requires only one header file, and\n> the library code takes under 64k at runtime.\n>\n> 2. It is well-tested: it's been used in OpenLDAP for years.\n>\n> 3. It's very fast.  LMDB's benchmarks show that it is among\n> the fastest key-value stores.\n>\n> 4. It has a relatively simple concurrency story; readers don't\n> block writers and writers don't block readers.\n>\n> Ronnie Sahlberg's original version of this patchset used tdb.  The\n> major disadvantage of tdb is that tdb is hard to build on OS X.  It's\n> also not in homebrew.  So lmdb seemed simpler.\n>\n> To test this backend's correctness, I hacked test-lib.sh and\n> test-lib-functions.sh to run all tests under the refs backend. Dozens\n> of tests use manual ref/reflog reading/writing, or create submodules\n> without passing --ref-storage to git init.  If those tests are\n> changed to use the update-ref machinery or test-refs-lmdb-backend (or,\n> in the case of packed-refs, corrupt refs, and dumb fetch tests, are\n> skipped), the only remaining failing tests are the git-new-workdir\n> tests and the gitweb tests.\n>\n> Signed-off-by: David Turner <dturner@twopensource.com>\n> ---\n>  .gitignore                                     |    1 +\n>  Documentation/config.txt                       |    7 +\n>  Documentation/git-clone.txt                    |    3 +-\n>  Documentation/git-init.txt                     |    2 +-\n>  Documentation/technical/refs-lmdb-backend.txt  |   52 +\n>  Documentation/technical/repository-version.txt |    5 +\n>  Makefile                                       |   12 +\n>  config.c                                       |   29 +\n>  configure.ac                                   |   33 +\n>  contrib/workdir/git-new-workdir                |    3 +\n>  refs.h                                         |    2 +\n>  refs/lmdb-backend.c                            | 2051 ++++++++++++++++++++++++\n>  setup.c                                        |   11 +-\n>  test-refs-lmdb-backend.c                       |   64 +\n>  transport.c                                    |    7 +-\n>  15 files changed, 2275 insertions(+), 7 deletions(-)\n>  create mode 100644 Documentation/technical/refs-lmdb-backend.txt\n>  create mode 100644 refs/lmdb-backend.c\n>  create mode 100644 test-refs-lmdb-backend.c\n>\n\n[...]\n\n> diff --git a/Documentation/technical/refs-lmdb-backend.txt b/Documentation/technical/refs-lmdb-backend.txt\n> new file mode 100644\n> index 0000000..c328bfc\n> --- /dev/null\n> +++ b/Documentation/technical/refs-lmdb-backend.txt\n> @@ -0,0 +1,52 @@\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 stored 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 update.  The\n> +key format is logs/[refname]\\0[timestamp].  The timestamp is a 64-bit\n> +unsigned integer number of nanoseconds since 1/1/1970, stored in\n> +network byte order.  This means that reflog entries are\n> +chronologically ordered.  Because LMDB is a btree database, we can\n> +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\ns/timeztamp/timestamp/\n\n> +\n> +Reflog values are in the same format as the original files-based\n> +reflog, including the trailing LF. The date in the reflog value\n> +matches the date in the timestamp field.\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.\n\n[...]\n\n> diff --git a/contrib/workdir/git-new-workdir b/contrib/workdir/git-new-workdir\n> index 888c34a..c035a43 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 extensions.refstorage)\" = \"lmdb\" && die \"git-new-workdir is incompatible with the refs lmdb storage\"\n> +\n\nIs it expected that other potential ref backends are compatible with\ngit-new-workdir?  Otherwise I think it would make more sense to\nwhitelist the files backend here instead of blacklisting the lmdb\nbackend, so we don't risk forgetting about this when adding another\nbackend.\n\n>  case \"$git_dir\" in\n>  .git)\n>  \tgit_dir=\"$orig_git/.git\"\n\n[...]\n\n> +static int lmdb_init_db(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> +\tif (!db_path)\n> +\t\tdb_path = xstrdup(real_path(get_refdb_path(get_git_common_dir())));\n\nI think we're leaking some memory from get_refdb_path() here.\nget_refdb_path() uses strbuf_detach(), which according to its docstring\nmakes its caller take care of the memory of the returned string.\nreal_path() then uses strbuf_addstr() to add the string to its\ninternal strbuf, but leaves the string we get from get_refdb_path()\nalone, so it leaks.\n\n> +\n> +\tif (mkdir(db_path, 0775) && errno != EEXIST) {\n> +\t\tstrbuf_addf(err, \"%s\", strerror(errno));\n> +\t\treturn -1;\n> +\t}\n> +\n> +\treturn 0;\n> +}\n> +\n\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> +\tif (!db_path)\n> +\t\tdb_path = xstrdup(real_path(get_refdb_path(get_git_common_dir())));\n\nSame comment about leaking memory from get_refdb_path() as above applies.\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[...]\n"},{"id":"276346","messageId":"1453223161.16226.24.camel@twopensource.com","threadId":"41190","inReplyTo":"1452857550-25887-1-git-send-email-szeder@ira.uka.de","subject":"Re: [PATCH v3 17/20] refs: allow ref backend to be set for clone","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2016-01-19T17:06:01Z","receivedAt":"2016-01-19T17:06:01Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"Thanks for the suggestions.\n\nWith your permission, I will add:\n\nSigned-off-by: SZEDER Gábor <szeder@ira.uka.de>\n\nto all three of these patches post-squash.  Is that OK?\n\nOn Fri, 2016-01-15 at 12:32 +0100, SZEDER Gábor wrote:\n> Hi,\n> \n> This change is more about 'git clone' than about refs, therefore\n> I think the subject line would be better as:\n> \n>   clone: allow setting alternate ref backend\n> \n> Could you please squash this in to keep the completion script up to\n> date?\n> Is there or will there be a way to list available ref backends, so we\n> could complete possible options for --ref-storage=<TAB>, too?\n> \n> ------ >8 ------\n> \n> Subject: completion: git clone --ref-storage=\n> ---\n> diff --git a/contrib/completion/git-completion.bash\n> b/contrib/completion/git-completion.bash\n> index ab4da7f97917..c970d3c0d0a3 100644\n> --- a/contrib/completion/git-completion.bash\n> +++ b/contrib/completion/git-completion.bash\n> @@ -1092,6 +1092,7 @@ _git_clone ()\n>  \t\t\t--depth\n>  \t\t\t--single-branch\n>  \t\t\t--branch\n> +\t\t\t--ref-storage=\n>  \t\t\t\"\n>  \t\treturn\n>  \t\t;;\n> \n"},{"id":"276357","messageId":"1453229733.16226.33.camel@twopensource.com","threadId":"41190","inReplyTo":"20160115133317.GJ10612@hank","subject":"Re: [PATCH v3 19/20] refs: add LMDB refs backend","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2016-01-19T18:55:33Z","receivedAt":"2016-01-19T18:55:33Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"On Fri, 2016-01-15 at 14:33 +0100, Thomas Gummerer wrote:\n> [I don't know too much about the refs code, but was still interested\n> in this patch series, so below are the few things that I noticed\n> while\n> reading through it.]\n\nThanks for taking a look.\n\n> On 01/14, David Turner wrote:\n\n> > +exists.  The header has the same format as an ordinary reflog, but\n> > with\n> > +a timeztamp of all zeros and an empty value.\n> \n> s/timeztamp/timestamp/\n\nFixed, thanks.\n\n> > +test \"$(git config extensions.refstorage)\" = \"lmdb\" && die \"git\n> > -new-workdir is incompatible with the refs lmdb storage\"\n> > +\n> \n> Is it expected that other potential ref backends are compatible with\n> git-new-workdir?  Otherwise I think it would make more sense to\n> whitelist the files backend here instead of blacklisting the lmdb\n> backend, so we don't risk forgetting about this when adding another\n> backend.\n\nI no longer remember what the exact issue was, but we can be safe and\nrestrict git-new-workdir to the files backend (since it looks likely\nthat worktrees will replace it anyway).\n\n> >  case \"$git_dir\" in\n> >  .git)\n> >  \tgit_dir=\"$orig_git/.git\"\n> \n> [...]\n> \n> > +static int lmdb_init_db(struct strbuf *err, int shared)\n> > +{\n> > +\t/*\n> > +\t * To create a db, all we need to do is make a directory\n> > for\n> > +\t * it to live in; lmdb will do the rest.\n> > +\t */\n> > +\n> > +\tif (!db_path)\n> > +\t\tdb_path =\n> > xstrdup(real_path(get_refdb_path(get_git_common_dir())));\n> \n> I think we're leaking some memory from get_refdb_path() here.\n> get_refdb_path() uses strbuf_detach(), which according to its\n> docstring\n> makes its caller take care of the memory of the returned string.\n> real_path() then uses strbuf_addstr() to add the string to its\n> internal strbuf, but leaves the string we get from get_refdb_path()\n> alone, so it leaks.\n\nThat only happens once per run, since db_path is set afterwards, but\nI'll change get_refdb_path() to return a pointer to a statically\nallocated string (as many similar funcs do).  That will be easier to\nread.\n"},{"id":"276359","messageId":"1453230748.16226.36.camel@twopensource.com","threadId":"41190","inReplyTo":"20160115125139.GI10612@hank","subject":"Re: [PATCH v3 15/20] init: allow alternate backends to be set for new repos","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2016-01-19T19:12:28Z","receivedAt":"2016-01-19T19:12:28Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"On Fri, 2016-01-15 at 13:51 +0100, Thomas Gummerer wrote:\n> On 01/14, David Turner wrote:\n> > +\tif (requested_ref_storage_backend)\n> > +\t\tref_storage_backend =\n> > requested_ref_storage_backend;\n> > +\tif (strcmp(ref_storage_backend, \"files\")) {\n> > +\t\tgit_config_set(\"extensions.refStorage\",\n> > ref_storage_backend);\n> > +\t\tgit_config_set(\"core.repositoryformatversion\",\n> > ref_storage_backend);\n> > +#ifdef USE_LIBLMDB\n> > +\t\tregister_ref_storage_backend(&refs_be_lmdb);\n> \n> refs_be_lmdb is not yet defined at this point in the patch series. \n>  It\n> doesn't break anything, because USE_LIBLMDB doesn't leak through the\n> makefile yet, but I still think it would make more sense to have the\n> ifdef in 19/20.\n\nThanks, fixed.\n\n> > +#endif\n> > +\t\tset_ref_storage_backend(ref_storage_backend);\n> \n> More importantly, there is no check whether setting the ref storage\n> backend succeeds.  If a user accidentally sets an invalid value for\n> the ref backend, a broken repository will be created without even\n> warning the user.\n> \n> While the repository will still work fine at this point in the\n> series,\n> git will die with an invalid ref backend in the config after 19/20.\n> It can be fixed by setting extensions.refStorage to files in the\n> config, because the backend is initialized to the default files\n> backend below when the ref backend cannot be set.\n> \n> I think it would be best to die() here, if setting the ref backend\n> doesn't succeed, and clean up the files that were already written,\n> instead of leaving the user with a broken repository.\n\nI've rearranged this code. \n\n> > +test_expect_success 'init with bogus storage backend fails' '\n> > +\n> > +\t(\n> > +\t\tmkdir again2 &&\n> > +\t\tcd again2 &&\n> > +\t\tgit init --ref-storage=test >out2 2>err2\n> > +\t)\n> > +'\n> \n> I noticed the above mainly because of this test, which doesn't seem\n> to\n> test what it claims to test.  It only seems to test that git init\n> succeeds, and writes something to out2 and err2, it's missing the\n> checks that the contents are actually what they're supposed to be.\n\nFixed, thanks.\n"},{"id":"276490","messageId":"20160121100820.Horde.5-2HMBAP-1P3hWXbxgxeBsX@webmail.informatik.kit.edu","threadId":"41190","inReplyTo":"1453223161.16226.24.camel@twopensource.com","subject":"Re: [PATCH v3 17/20] refs: allow ref backend to be set for clone","fromName":"SZEDER Gábor","fromEmail":"szeder@ira.uka.de","sentAt":"2016-01-21T09:08:20Z","receivedAt":"2016-01-21T09:08:20Z","isPatch":true,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"\nQuoting David Turner <dturner@twopensource.com>:\n\n> Thanks for the suggestions.\n>\n> With your permission, I will add:\n>\n> Signed-off-by: SZEDER Gábor <szeder@ira.uka.de>\n>\n> to all three of these patches post-squash.  Is that OK?\n\nSure...\n\n(But does such a trivial one-liner need a sign-off at all, when it's  \nnot a standalone patch?)\n\n\n> On Fri, 2016-01-15 at 12:32 +0100, SZEDER Gábor wrote:\n>> Hi,\n>>\n>> This change is more about 'git clone' than about refs, therefore\n>> I think the subject line would be better as:\n>>\n>>   clone: allow setting alternate ref backend\n>>\n>> Could you please squash this in to keep the completion script up to\n>> date?\n>> Is there or will there be a way to list available ref backends, so we\n>> could complete possible options for --ref-storage=<TAB>, too?\n>>\n>> ------ >8 ------\n>>\n>> Subject: completion: git clone --ref-storage=\n>> ---\n>> diff --git a/contrib/completion/git-completion.bash\n>> b/contrib/completion/git-completion.bash\n>> index ab4da7f97917..c970d3c0d0a3 100644\n>> --- a/contrib/completion/git-completion.bash\n>> +++ b/contrib/completion/git-completion.bash\n>> @@ -1092,6 +1092,7 @@ _git_clone ()\n>>  \t\t\t--depth\n>>  \t\t\t--single-branch\n>>  \t\t\t--branch\n>> +\t\t\t--ref-storage=\n>>  \t\t\t\"\n>>  \t\treturn\n>>  \t\t;;\n>>\n"},{"id":"277271","messageId":"1454443734.5545.1.camel@twopensource.com","threadId":"41190","inReplyTo":"1452788777-24954-1-git-send-email-dturner@twopensource.com","subject":"Re: [PATCH v3 00/20] refs backend rebase on pu","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2016-02-02T20:08:54Z","receivedAt":"2016-02-02T20:08:54Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"Are there any more reviews on this?  I do have some changes from this\nset, but they're pretty minor so I don't want to post a new one (unless\nfolks would rather see those changes before reviewing).  Let me know.\n\nThanks.\n\nOn Thu, 2016-01-14 at 11:25 -0500, David Turner wrote:\n> I rebased this on top of 20fabf9b194c4099d329582c734e433f9f6586a3\n> (the\n> commit before the previous version of this series).\n> \n> This entailed removing Michael Haggerty's patch to builtin/clone.c,\n> since a patch by Stefan already did approximately the same thing.\n> \n> There was a somewhat hairy merge of \"resolve symbolic refs first\",\n> but\n> I think the new one is fine (the same tests all pass except for the\n> one TODO noted in the lmdb code).\n> \n> David Turner (17):\n>   refs: add do_for_each_per_worktree_ref\n>   refs: add methods for reflog\n>   refs: add method for initial ref transaction commit\n>   refs: add method for delete_refs\n>   refs: add methods to init refs db\n>   refs: add method to rename refs\n>   refs: make lock generic\n>   refs: move duplicate check to common code\n>   refs: allow log-only updates\n>   refs: resolve symbolic refs first\n>   refs: always handle non-normal refs in files backend\n>   init: allow alternate backends to be set for new repos\n>   refs: check submodules ref storage config\n>   refs: allow ref backend to be set for clone\n>   svn: learn ref-storage argument\n>   refs: add LMDB refs backend\n>   refs: tests for lmdb backend\n> \n> Ronnie Sahlberg (3):\n>   refs: add a backend method structure with transaction functions\n>   refs: add methods for misc ref operations\n>   refs: add methods for the ref iterators\n> \n>  .gitignore                                     |    1 +\n>  Documentation/config.txt                       |    7 +\n>  Documentation/git-clone.txt                    |    6 +\n>  Documentation/git-init-db.txt                  |    2 +-\n>  Documentation/git-init.txt                     |    7 +-\n>  Documentation/technical/refs-lmdb-backend.txt  |   52 +\n>  Documentation/technical/repository-version.txt |    5 +\n>  Makefile                                       |   12 +\n>  builtin/clone.c                                |    5 +\n>  builtin/init-db.c                              |   40 +-\n>  builtin/submodule--helper.c                    |    2 +-\n>  cache.h                                        |    2 +\n>  config.c                                       |   29 +\n>  configure.ac                                   |   33 +\n>  contrib/workdir/git-new-workdir                |    3 +\n>  git-submodule.sh                               |   13 +\n>  git-svn.perl                                   |    6 +-\n>  path.c                                         |   29 +-\n>  refs.c                                         |  451 +++++-\n>  refs.h                                         |   17 +\n>  refs/files-backend.c                           |  397 +++--\n>  refs/lmdb-backend.c                            | 2051\n> ++++++++++++++++++++++++\n>  refs/refs-internal.h                           |  128 +-\n>  setup.c                                        |   23 +-\n>  t/t0001-init.sh                                |   24 +\n>  t/t1460-refs-lmdb-backend.sh                   | 1109 +++++++++++++\n>  t/t1470-refs-lmdb-backend-reflog.sh            |  359 +++++\n>  t/t1480-refs-lmdb-submodule.sh                 |   85 +\n>  t/test-lib.sh                                  |    1 +\n>  test-refs-lmdb-backend.c                       |   64 +\n>  transport.c                                    |    7 +-\n>  31 files changed, 4767 insertions(+), 203 deletions(-)\n>  create mode 100644 Documentation/technical/refs-lmdb-backend.txt\n>  create mode 100644 refs/lmdb-backend.c\n>  create mode 100755 t/t1460-refs-lmdb-backend.sh\n>  create mode 100755 t/t1470-refs-lmdb-backend-reflog.sh\n>  create mode 100755 t/t1480-refs-lmdb-submodule.sh\n>  create mode 100644 test-refs-lmdb-backend.c\n> \n"},{"id":"277281","messageId":"xmqq7fimrcab.fsf@gitster.mtv.corp.google.com","threadId":"41190","inReplyTo":"1454443734.5545.1.camel@twopensource.com","subject":"Re: [PATCH v3 00/20] refs backend rebase on pu","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-02-02T22:13:32Z","receivedAt":"2016-02-02T22:13:32Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"David Turner <dturner@twopensource.com> writes:\n\n> Are there any more reviews on this?  I do have some changes from this\n> set, but they're pretty minor so I don't want to post a new one (unless\n> folks would rather see those changes before reviewing).  Let me know.\n\nThanks for pinging.  As this is a rather wide-ranging topic, it was\nnot practical to intergrate with the rest of the topics in flight\nback then, but now it seems that this needs only one topic that\nstill is in flight.  I'll queue this on top of a merge between\n'master' and the tip of 'sb/submodule-parallel-update' and include\nin the daily integration cycle to make it easy for people to view\nthe changes in wider context as necessary.\n"},{"id":"277353","messageId":"xmqqmvrhjpzc.fsf@gitster.mtv.corp.google.com","threadId":"41190","inReplyTo":"xmqq7fimrcab.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH v3 00/20] refs backend rebase on pu","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-02-04T00:09:27Z","receivedAt":"2016-02-04T00:09:27Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> David Turner <dturner@twopensource.com> writes:\n>\n>> Are there any more reviews on this?  I do have some changes from this\n>> set, but they're pretty minor so I don't want to post a new one (unless\n>> folks would rather see those changes before reviewing).  Let me know.\n>\n> Thanks for pinging.  As this is a rather wide-ranging topic, it was\n> not practical to intergrate with the rest of the topics in flight\n> back then, but now it seems that this needs only one topic that\n> still is in flight.  I'll queue this on top of a merge between\n> 'master' and the tip of 'sb/submodule-parallel-update' and include\n> in the daily integration cycle to make it easy for people to view\n> the changes in wider context as necessary.\n\nI've re-applied the patches to rebuild the topic; when merged to\n'pu' it seemed to break some tests, but I didn't look too deeply\ninto it.\n\nThanks.\n"},{"id":"277356","messageId":"1454548352.5545.2.camel@twopensource.com","threadId":"41190","inReplyTo":"xmqqmvrhjpzc.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH v3 00/20] refs backend rebase on pu","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2016-02-04T01:12:32Z","receivedAt":"2016-02-04T01:12:32Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"On Wed, 2016-02-03 at 16:09 -0800, Junio C Hamano wrote:\n> Junio C Hamano <gitster@pobox.com> writes:\n> \n> > David Turner <dturner@twopensource.com> writes:\n> > \n> > > Are there any more reviews on this?  I do have some changes from\n> > > this\n> > > set, but they're pretty minor so I don't want to post a new one\n> > > (unless\n> > > folks would rather see those changes before reviewing).  Let me\n> > > know.\n> > \n> > Thanks for pinging.  As this is a rather wide-ranging topic, it was\n> > not practical to intergrate with the rest of the topics in flight\n> > back then, but now it seems that this needs only one topic that\n> > still is in flight.  I'll queue this on top of a merge between\n> > 'master' and the tip of 'sb/submodule-parallel-update' and include\n> > in the daily integration cycle to make it easy for people to view\n> > the changes in wider context as necessary.\n> \n> I've re-applied the patches to rebuild the topic; when merged to\n> 'pu' it seemed to break some tests, but I didn't look too deeply\n> into it.\n\nThey were working for me as-of the time I sent them.  I guess something\nmust have broken since.  I'll rebase, test, and send a new series.\n"},{"id":"277358","messageId":"56B2AF64.5020502@ramsayjones.plus.com","threadId":"41190","inReplyTo":"1454548352.5545.2.camel@twopensource.com","subject":"Re: [PATCH v3 00/20] refs backend rebase on pu","fromName":"Ramsay Jones","fromEmail":"ramsay@ramsayjones.plus.com","sentAt":"2016-02-04T01:54:44Z","receivedAt":"2016-02-04T01:54:44Z","isPatch":true,"sender":{"key":"ramsay@ramsayjones.plus.com","avatar":"https://avatars.githubusercontent.com/u/33702710?v=4"},"body":"\n\nOn 04/02/16 01:12, David Turner wrote:\n> On Wed, 2016-02-03 at 16:09 -0800, Junio C Hamano wrote:\n>> Junio C Hamano <gitster@pobox.com> writes:\n>>\n>>> David Turner <dturner@twopensource.com> writes:\n>>>\n>>>> Are there any more reviews on this?  I do have some changes from\n>>>> this\n>>>> set, but they're pretty minor so I don't want to post a new one\n>>>> (unless\n>>>> folks would rather see those changes before reviewing).  Let me\n>>>> know.\n>>>\n>>> Thanks for pinging.  As this is a rather wide-ranging topic, it was\n>>> not practical to intergrate with the rest of the topics in flight\n>>> back then, but now it seems that this needs only one topic that\n>>> still is in flight.  I'll queue this on top of a merge between\n>>> 'master' and the tip of 'sb/submodule-parallel-update' and include\n>>> in the daily integration cycle to make it easy for people to view\n>>> the changes in wider context as necessary.\n>>\n>> I've re-applied the patches to rebuild the topic; when merged to\n>> 'pu' it seemed to break some tests, but I didn't look too deeply\n>> into it.\n> \n> They were working for me as-of the time I sent them.  I guess something\n> must have broken since.  I'll rebase, test, and send a new series.\n\nI didn't spend too long looking at it, but I think this interacts with\nJeff's patch a2d5156c (\"resolve_gitlink_ref: ignore non-repository paths\",\n22-01-2016) which introduces the new test in 't3000-ls-files-others.sh'\nwhich fails for me.\n\nThe change which Jeff made to resolve_gitlink_ref() is effectively side-stepped\nby the call to check_submodule_backend() in the new resolve_gitlink_ref().\n(Jeff's change is now in the 'files' backend version of resolve_gitlink_ref()).\n\nIn the debugger, when the die() breakpoint is taken, the bt looks like:\n\n(gdb) bt\n#0  die (err=err@entry=0x57b38a \"Invalid gitfile format: %s\") at usage.c:99\n#1  0x000000000050db64 in read_gitfile_gently (\n    path=0x7ee770 \"not-a-submodule/.git\", \n    return_error_code=return_error_code@entry=0x0) at setup.c:558\n#2  0x00000000004dfce8 in do_submodule_path (buf=0x7fffffffd220, \n    path=0x7ee750 \"not-a-submodule/\", fmt=0x5771d0 \"%s\", \n    args=args@entry=0x7fffffffd138) at path.c:469\n#3  0x00000000004e05c2 in strbuf_git_path_submodule (\n    buf=buf@entry=0x7fffffffd220, path=path@entry=0x7ee750 \"not-a-submodule/\", \n    fmt=fmt@entry=0x5771d0 \"%s\") at path.c:503\n#4  0x00000000004edfdc in check_submodule_backend (\n    submodule=submodule@entry=0x7ee750 \"not-a-submodule/\") at refs.c:316\n#5  0x00000000004f01c2 in resolve_gitlink_ref (\n    path=0x7ee750 \"not-a-submodule/\", refname=0x565040 \"HEAD\", \n    sha1=0x7fffffffd2a0 \"\\n\") at refs.c:1407\n#6  0x00000000004b0a07 in treat_directory (simplify=0x0, exclude=0, baselen=0, \n    len=16, dirname=0x7ee750 \"not-a-submodule/\", untracked=0x0, \n    dir=0x7fffffffdc10) at dir.c:1305\n#7  treat_one_path (dir=dir@entry=0x7fffffffdc10, \n    untracked=untracked@entry=0x0, path=path@entry=0x7fffffffd330, \n    baselen=baselen@entry=0, simplify=simplify@entry=0x0, dtype=4, \n    de=de@entry=0x7eef60) at dir.c:1489\n#8  0x00000000004b027e in treat_path (simplify=0x0, baselen=0, \n    path=0x7fffffffd330, cdir=0x7fffffffd350, untracked=0x0, \n    dir=0x7fffffffdc10) at dir.c:1550\n#9  read_directory_recursive (dir=dir@entry=0x7fffffffdc10, \n    base=base@entry=0x54744a \"\", baselen=baselen@entry=0, untracked=0x0, \n    check_only=check_only@entry=0, simplify=simplify@entry=0x0) at dir.c:1697\n#10 0x00000000004b0b67 in read_directory (dir=dir@entry=0x7fffffffdc10, \n    path=0x54744a \"\", len=len@entry=0, \n    pathspec=pathspec@entry=0x7c83c0 <pathspec>) at dir.c:2023\n---Type <return> to continue, or q <return> to quit---\n\nNote that the error code return pointer is NULL in the call to\nread_gitfile_gently(). Also, note the call frames #4 and #5.\n\nI have to go now ... Hope that helps.\n\nATB,\nRamsay Jones\n"},{"id":"277359","messageId":"20160204025821.GA21105@sigill.intra.peff.net","threadId":"41190","inReplyTo":"56B2AF64.5020502@ramsayjones.plus.com","subject":"Re: [PATCH v3 00/20] refs backend rebase on pu","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2016-02-04T02:58:21Z","receivedAt":"2016-02-04T02:58:21Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Thu, Feb 04, 2016 at 01:54:44AM +0000, Ramsay Jones wrote:\n\n> > They were working for me as-of the time I sent them.  I guess something\n> > must have broken since.  I'll rebase, test, and send a new series.\n> \n> I didn't spend too long looking at it, but I think this interacts with\n> Jeff's patch a2d5156c (\"resolve_gitlink_ref: ignore non-repository paths\",\n> 22-01-2016) which introduces the new test in 't3000-ls-files-others.sh'\n> which fails for me.\n> \n> The change which Jeff made to resolve_gitlink_ref() is effectively side-stepped\n> by the call to check_submodule_backend() in the new resolve_gitlink_ref().\n> (Jeff's change is now in the 'files' backend version of resolve_gitlink_ref()).\n\nYeah. The check_submodule_backend() function calls\nstrbuf_git_path_submodule(), which unconditionally requires that the\npath be an actual submodule (the irony being that we are using it to\nfind out whether we have a submodule!). So I don't think there's a\nconflict between our code, so much as that the new code has the same bug\nI fixed in a2d5156c (and we didn't notice earlier, because there was no\ntest).\n\nThe solution in a2d5156 is to use is_nonbare_repository_dir() before\nassuming we have a submodule. I think check_submodule_backend() would\nwant to do the same thing. This is the minimal fix:\n\ndiff --git a/refs.c b/refs.c\nindex 3d4c0a6..7f86c49 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -313,9 +313,8 @@ static void check_submodule_backend(const char *submodule)\n \tif (!submodule)\n \t\tgoto done;\n \n-\tstrbuf_git_path_submodule(&sb, submodule, \"%s\", \"\");\n-\n-\tif (!is_git_directory(sb.buf))\n+\tstrbuf_addstr(&sb, submodule);\n+\tif (!is_nonbare_repository_dir(&sb))\n \t\tgoto done;\n \n \tstrbuf_reset(&sb);\n\n\nThat gets the test passing. But I noticed a few other things, some\nrelated and some unrelated, while looking at this function:\n\n  - in files_resolve_gitlink_ref, if we do find a submodule, we cache\n    the result with the ref_cache code. But here, we would read the .git\n    file repeatedly (and in fact, it happens twice per call, as\n    submodule_git_path has to read it itself).\n\n    I don't know if it would be worth adding any kind of caching at this\n    layer.  It may be that we don't typically resolve more than one ref\n    (or do more than one for_each_ref iteration) for a submodule, so the\n    cache is pointless. I didn't implement it specifically in a2d5156,\n    it just came for free with the existing ref_cache code.\n\n  - check_submodule_backend knows whether we have a submodule at all and\n    is worth proceeding, but does not tell its callers. So we'll end up\n    in the backend files_resolve_gitlink_ref and make the same check.\n    It's probably worth moving this logic to the outer layer so each\n    backend doesn't have to implement it (and then the check in\n    files_resolve_gitlink_ref can actually go away).\n\n  - for the common case of submodule==NULL (i.e., the main repository),\n    check_submodule_backend should be a noop, but it allocates and frees\n    the submodule_storage_backend string. Probably not a huge deal, but\n    it can be easily bumped down, and the first \"goto done\" turned into\n    a \"return\".\n\n  - if the submodule does have a backend configured, we leak the memory\n    for the default string. I think the submodule_backend() config\n    callback needs to free() the previous value.\n\n  - the config callback unconditionally dereferences \"value\", which will\n    segfault if the submodule's extensions.refstorage is a pure boolean\n    like:\n\n        [extensions]\n\trefstorage\n\n    That should never happen, of course, but we should be checking and\n    dying for \"!value\" rather than segfaulting. Using\n    git_config_string() will do this for you.\n\nHope that helps.\n\n-Peff\n"},{"id":"277368","messageId":"20160204073742.GA20162@sigill.intra.peff.net","threadId":"41190","inReplyTo":"1452788777-24954-14-git-send-email-dturner@twopensource.com","subject":"Re: [PATCH v3 13/20] refs: resolve symbolic refs first","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2016-02-04T07:37:42Z","receivedAt":"2016-02-04T07:37:42Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Thu, Jan 14, 2016 at 11:26:10AM -0500, David Turner wrote:\n\n> +static int dereference_symrefs(struct ref_transaction *transaction,\n> +\t\t\t       struct strbuf *err)\n> +{\n> +\tint i;\n> +\tint nr = transaction->nr;\n> +\n> +\tfor (i = 0; i < nr; i++) {\n> +\t\tstruct ref_update *update = transaction->updates[i];\n> +\t\tconst char *resolved;\n> +\t\tunsigned char sha1[20];\n> +\t\tint resolve_flags = 0;\n> +\t\tint mustexist = (update->old_sha1 &&\n> +\t\t\t\t !is_null_sha1(update->old_sha1));\n\nCoverity complains about this last line, as \"update->old_sha1\" is an\narray. I think you want to check \"update->flags & REF_HAVE_OLD\" instead?\n\n-Peff\n"},{"id":"277412","messageId":"CACsJy8DUpOGtZmBr2F2R+8xvzvHOxLc_YOSnF8eQRvHYq15ttA@mail.gmail.com","threadId":"41190","inReplyTo":"1452788777-24954-16-git-send-email-dturner@twopensource.com","subject":"Re: [PATCH v3 15/20] init: allow alternate backends to be set for new repos","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2016-02-04T09:48:36Z","receivedAt":"2016-02-04T09:48:36Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"(picking a random series version because I didn't follow it closely)\n\nOn Thu, Jan 14, 2016 at 11:26 PM, David Turner <dturner@twopensource.com> wrote:\n> +       if (requested_ref_storage_backend)\n> +               ref_storage_backend = requested_ref_storage_backend;\n> +       if (strcmp(ref_storage_backend, \"files\")) {\n> +               git_config_set(\"extensions.refStorage\", ref_storage_backend);\n> +               git_config_set(\"core.repositoryformatversion\", ref_storage_backend);\n> +#ifdef USE_LIBLMDB\n> +               register_ref_storage_backend(&refs_be_lmdb);\n> +#endif\n> +               set_ref_storage_backend(ref_storage_backend);\n> +               repo_version = 1;\n> +       }\n> +\n> +       if (refs_init_db(&err, shared_repository))\n> +               die(\"failed to set up refs db: %s\", err.buf);\n> +\n\nI was surprised that \"git init --ref-storage=lmdb abc\" ran\nsuccessfully even on non-lmdb build. Of course running any commands in\nthe new repo will fail, suggesting to rebuild with lmdb. But should\n\"git init\" fail in the first place? I see Thomas' comment about this\nblock, but not sure why it still passes for me. It does not catch\nunrecognized backend names either.\n\nAlso it would be nice if we could hide #ifdef USE_LIBLMDB (here and in\nconfig.c) away, maybe in refs directory. I imagine a new backend would\nneed the same set of #ifdef. Spreading them across files does not\nsound ideal.\n-- \nDuy\n"},{"id":"277413","messageId":"CACsJy8Ai93QWnoB8CMFchvO_fdJk+PXgDcBhuPbuQWGNaSdpyw@mail.gmail.com","threadId":"41190","inReplyTo":"1452788777-24954-20-git-send-email-dturner@twopensource.com","subject":"Re: [PATCH v3 19/20] refs: add LMDB refs backend","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2016-02-04T09:58:31Z","receivedAt":"2016-02-04T09:58:31Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Thu, Jan 14, 2016 at 11:26 PM, David Turner <dturner@twopensource.com> wrote:\n> diff --git a/refs/lmdb-backend.c b/refs/lmdb-backend.c\n> new file mode 100644\n> index 0000000..470c79f\n> --- /dev/null\n> +++ b/refs/lmdb-backend.c\n> @@ -0,0 +1,2051 @@\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 \"refs-internal.h\"\n> +#include \"../tag.h\"\n> +#include \"../lockfile.h\"\n> +\n> +static struct trace_key db_trace = TRACE_KEY_INIT(LMDB);\n\nSuper nit:\n\n    CC refs/lmdb-backend.o\nrefs/lmdb-backend.c:22:25: cảnh báo: ‘db_trace’ defined but not used\n[-Wunused-variable]\n-- \nDuy\n"},{"id":"277414","messageId":"CACsJy8Cw9CM+EBw85KM1RcDAsRY8LbTFWFUq0dLSHWY0NLPZMA@mail.gmail.com","threadId":"41190","inReplyTo":"1454443734.5545.1.camel@twopensource.com","subject":"Re: [PATCH v3 00/20] refs backend rebase on pu","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2016-02-04T10:09:48Z","receivedAt":"2016-02-04T10:09:48Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Wed, Feb 3, 2016 at 3:08 AM, David Turner <dturner@twopensource.com> wrote:\n> Are there any more reviews on this?  I do have some changes from this\n> set, but they're pretty minor so I don't want to post a new one (unless\n> folks would rather see those changes before reviewing).  Let me know.\n\nI think you need to keep \"refs\" directory back somehow. Without it\n(e.g. \"git init --ref-storage=lmdb\" does not create \"refs\"),\nis_git_directory() from _old_ git versions fails to recognize this is\na good repository. So instead of dying on finding an unsupported\nrepository, setup code keeps on looking for another repository in\nparent directories. If I accidentally run \"git add something\" with an\nold binary, \"something\" could be added to a wrong repository. Very\nconfusing.\n-- \nDuy\n"},{"id":"277420","messageId":"CACsJy8Cau0mpz8zVjvz7RPt-s=xmaCCmz0p8OCup9-Q1MnwWCQ@mail.gmail.com","threadId":"41190","inReplyTo":"1454443734.5545.1.camel@twopensource.com","subject":"Re: [PATCH v3 00/20] refs backend rebase on pu","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2016-02-04T11:42:56Z","receivedAt":"2016-02-04T11:42:56Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Wed, Feb 3, 2016 at 3:08 AM, David Turner <dturner@twopensource.com> wrote:\n> Are there any more reviews on this?  I do have some changes from this\n> set, but they're pretty minor so I don't want to post a new one (unless\n> folks would rather see those changes before reviewing).  Let me know.\n\nLast note. Since this is new code, maybe you can wrap translatable\nstrings with _(), basically any visible string that machines do not\nneed to recognize.\n-- \nDuy\n"},{"id":"277447","messageId":"1454613883.5545.4.camel@twopensource.com","threadId":"41190","inReplyTo":"20160204073742.GA20162@sigill.intra.peff.net","subject":"Re: [PATCH v3 13/20] refs: resolve symbolic refs first","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2016-02-04T19:24:43Z","receivedAt":"2016-02-04T19:24:43Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"On Thu, 2016-02-04 at 02:37 -0500, Jeff King wrote:\n> On Thu, Jan 14, 2016 at 11:26:10AM -0500, David Turner wrote:\n> \n> > +static int dereference_symrefs(struct ref_transaction\n> > *transaction,\n> > +\t\t\t       struct strbuf *err)\n> > +{\n> > +\tint i;\n> > +\tint nr = transaction->nr;\n> > +\n> > +\tfor (i = 0; i < nr; i++) {\n> > +\t\tstruct ref_update *update = transaction\n> > ->updates[i];\n> > +\t\tconst char *resolved;\n> > +\t\tunsigned char sha1[20];\n> > +\t\tint resolve_flags = 0;\n> > +\t\tint mustexist = (update->old_sha1 &&\n> > +\t\t\t\t !is_null_sha1(update->old_sha1));\n> \n> Coverity complains about this last line, as \"update->old_sha1\" is an\n> array. I think you want to check \"update->flags & REF_HAVE_OLD\"\n> instead?\n\nYeah, that's right.  Thanks.\n"},{"id":"277448","messageId":"1454614385.5545.5.camel@twopensource.com","threadId":"41190","inReplyTo":"CACsJy8Ai93QWnoB8CMFchvO_fdJk+PXgDcBhuPbuQWGNaSdpyw@mail.gmail.com","subject":"Re: [PATCH v3 19/20] refs: add LMDB refs backend","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2016-02-04T19:33:05Z","receivedAt":"2016-02-04T19:33:05Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"On Thu, 2016-02-04 at 16:58 +0700, Duy Nguyen wrote:\n> On Thu, Jan 14, 2016 at 11:26 PM, David Turner <\n> dturner@twopensource.com> wrote:\n> > diff --git a/refs/lmdb-backend.c b/refs/lmdb-backend.c\n> > new file mode 100644\n> > index 0000000..470c79f\n> > --- /dev/null\n> > +++ b/refs/lmdb-backend.c\n> > @@ -0,0 +1,2051 @@\n> > +/*\n> > + * This file implements a lmdb backend for refs.\n> > + *\n> > + * The design of this backend relies on lmdb's write lock -- that\n> > is, any\n> > + * write transaction blocks all other writers.  Thus, as soon as a\n> > 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\n> > 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 \"refs-internal.h\"\n> > +#include \"../tag.h\"\n> > +#include \"../lockfile.h\"\n> > +\n> > +static struct trace_key db_trace = TRACE_KEY_INIT(LMDB);\n> \n> Super nit:\n> \n>     CC refs/lmdb-backend.o\n> refs/lmdb-backend.c:22:25: cảnh báo: ‘db_trace’ defined but not used\n> [-Wunused-variable]\n\nFixed, thanks.\n"},{"id":"277455","messageId":"1454616308.5545.9.camel@twopensource.com","threadId":"41190","inReplyTo":"CACsJy8DUpOGtZmBr2F2R+8xvzvHOxLc_YOSnF8eQRvHYq15ttA@mail.gmail.com","subject":"Re: [PATCH v3 15/20] init: allow alternate backends to be set for new repos","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2016-02-04T20:05:08Z","receivedAt":"2016-02-04T20:05:08Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"On Thu, 2016-02-04 at 16:48 +0700, Duy Nguyen wrote:\n> (picking a random series version because I didn't follow it closely)\n> \n> On Thu, Jan 14, 2016 at 11:26 PM, David Turner <\n> dturner@twopensource.com> wrote:\n> > +       if (requested_ref_storage_backend)\n> > +               ref_storage_backend =\n> > requested_ref_storage_backend;\n> > +       if (strcmp(ref_storage_backend, \"files\")) {\n> > +               git_config_set(\"extensions.refStorage\",\n> > ref_storage_backend);\n> > +               git_config_set(\"core.repositoryformatversion\",\n> > ref_storage_backend);\n> > +#ifdef USE_LIBLMDB\n> > +               register_ref_storage_backend(&refs_be_lmdb);\n> > +#endif\n> > +               set_ref_storage_backend(ref_storage_backend);\n> > +               repo_version = 1;\n> > +       }\n> > +\n> > +       if (refs_init_db(&err, shared_repository))\n> > +               die(\"failed to set up refs db: %s\", err.buf);\n> > +\n> \n> I was surprised that \"git init --ref-storage=lmdb abc\" ran\n> successfully even on non-lmdb build. Of course running any commands\n> in\n> the new repo will fail, suggesting to rebuild with lmdb. But should\n> \"git init\" fail in the first place? I see Thomas' comment about this\n> block, but not sure why it still passes for me. It does not catch\n> unrecognized backend names either.\n\nThis was broken in this patchset and is fixed in the set I've got in\n-progress (with a test).\n\n> Also it would be nice if we could hide #ifdef USE_LIBLMDB (here and\n> in\n> config.c) away, maybe in refs directory. I imagine a new backend\n> would\n> need the same set of #ifdef. Spreading them across files does not\n> sound ideal.\n\nGood point. Moved to a single one in refs.c.  Thanks.\n"},{"id":"277458","messageId":"1454617535.5545.10.camel@twopensource.com","threadId":"41190","inReplyTo":"CACsJy8Cau0mpz8zVjvz7RPt-s=xmaCCmz0p8OCup9-Q1MnwWCQ@mail.gmail.com","subject":"Re: [PATCH v3 00/20] refs backend rebase on pu","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2016-02-04T20:25:35Z","receivedAt":"2016-02-04T20:25:35Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"On Thu, 2016-02-04 at 18:42 +0700, Duy Nguyen wrote:\n> On Wed, Feb 3, 2016 at 3:08 AM, David Turner <\n> dturner@twopensource.com> wrote:\n> > Are there any more reviews on this?  I do have some changes from\n> > this\n> > set, but they're pretty minor so I don't want to post a new one\n> > (unless\n> > folks would rather see those changes before reviewing).  Let me\n> > know.\n> \n> Last note. Since this is new code, maybe you can wrap translatable\n> strings with _(), basically any visible string that machines do not\n> need to recognize.\n\nFixed, thanks. \n"},{"id":"277460","messageId":"56B3B71C.1000907@ramsayjones.plus.com","threadId":"41190","inReplyTo":"1454617535.5545.10.camel@twopensource.com","subject":"Re: [PATCH v3 00/20] refs backend rebase on pu","fromName":"Ramsay Jones","fromEmail":"ramsay@ramsayjones.plus.com","sentAt":"2016-02-04T20:39:56Z","receivedAt":"2016-02-04T20:39:56Z","isPatch":true,"sender":{"key":"ramsay@ramsayjones.plus.com","avatar":"https://avatars.githubusercontent.com/u/33702710?v=4"},"body":"\n\nOn 04/02/16 20:25, David Turner wrote:\n> On Thu, 2016-02-04 at 18:42 +0700, Duy Nguyen wrote:\n>> On Wed, Feb 3, 2016 at 3:08 AM, David Turner <\n>> dturner@twopensource.com> wrote:\n>>> Are there any more reviews on this?  I do have some changes from\n>>> this\n>>> set, but they're pretty minor so I don't want to post a new one\n>>> (unless\n>>> folks would rather see those changes before reviewing).  Let me\n>>> know.\n>>\n>> Last note. Since this is new code, maybe you can wrap translatable\n>> strings with _(), basically any visible string that machines do not\n>> need to recognize.\n> \n> Fixed, thanks. \n\nAnother minor point, could you please squash this in:\n\ndiff --git a/refs.c b/refs.c\nindex 3d4c0a6..4858d94 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -17,7 +17,6 @@ static const char split_transaction_fail_warning[] =\n /*\n  * We always have a files backend and it is the default.\n  */\n-struct ref_storage_be refs_be_files;\n static struct ref_storage_be *the_refs_backend = &refs_be_files;\n /*\n  * List of all available backends\n\nThe above (on Linux, anyway) is a 'tentative definition' of the\nrefs_be_files variable and so a common symbol definition is issued\nin refs.o. This then gets 'combined' with the *actual* symbol\ndefinition in  refs/files-backend.c. So everything 'works', except\nthat I have used some unix (let alone non-unix) systems which would\nnot output a common symbol for the above and would fail to link\nwith a 'multiple symbol definition' error.\n\n[Also note that an external declaration is already in effect from\nthe refs/refs-internal.h header file! ;-) ]\n\nATB,\nRamsay Jones\n"},{"id":"277461","messageId":"1454620998.5545.11.camel@twopensource.com","threadId":"41190","inReplyTo":"56B3B71C.1000907@ramsayjones.plus.com","subject":"Re: [PATCH v3 00/20] refs backend rebase on pu","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2016-02-04T21:23:18Z","receivedAt":"2016-02-04T21:23:18Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"On Thu, 2016-02-04 at 20:39 +0000, Ramsay Jones wrote:\n> \n> On 04/02/16 20:25, David Turner wrote:\n> > On Thu, 2016-02-04 at 18:42 +0700, Duy Nguyen wrote:\n> > > On Wed, Feb 3, 2016 at 3:08 AM, David Turner <\n> > > dturner@twopensource.com> wrote:\n> > > > Are there any more reviews on this?  I do have some changes\n> > > > from\n> > > > this\n> > > > set, but they're pretty minor so I don't want to post a new one\n> > > > (unless\n> > > > folks would rather see those changes before reviewing).  Let me\n> > > > know.\n> > > \n> > > Last note. Since this is new code, maybe you can wrap\n> > > translatable\n> > > strings with _(), basically any visible string that machines do\n> > > not\n> > > need to recognize.\n> > \n> > Fixed, thanks. \n> \n> Another minor point, could you please squash this in:\n> \n> diff --git a/refs.c b/refs.c\n> index 3d4c0a6..4858d94 100644\n> --- a/refs.c\n> +++ b/refs.c\n> @@ -17,7 +17,6 @@ static const char split_transaction_fail_warning[]\n> =\n>  /*\n>   * We always have a files backend and it is the default.\n>   */\n> -struct ref_storage_be refs_be_files;\n>  static struct ref_storage_be *the_refs_backend = &refs_be_files;\n>  /*\n>   * List of all available backends\n> \n> The above (on Linux, anyway) is a 'tentative definition' of the\n> refs_be_files variable and so a common symbol definition is issued\n> in refs.o. This then gets 'combined' with the *actual* symbol\n> definition in  refs/files-backend.c. So everything 'works', except\n> that I have used some unix (let alone non-unix) systems which would\n> not output a common symbol for the above and would fail to link\n> with a 'multiple symbol definition' error.\n> \n> [Also note that an external declaration is already in effect from\n> the refs/refs-internal.h header file! ;-) ]\n> \n> ATB,\n> Ramsay Jones\n\nFixed, thanks.\n"},{"id":"277463","messageId":"1454621969.5545.14.camel@twopensource.com","threadId":"41190","inReplyTo":"CACsJy8Cw9CM+EBw85KM1RcDAsRY8LbTFWFUq0dLSHWY0NLPZMA@mail.gmail.com","subject":"Re: [PATCH v3 00/20] refs backend rebase on pu","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2016-02-04T21:39:29Z","receivedAt":"2016-02-04T21:39:29Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"On Thu, 2016-02-04 at 17:09 +0700, Duy Nguyen wrote:\n> On Wed, Feb 3, 2016 at 3:08 AM, David Turner <\n> dturner@twopensource.com> wrote:\n> > Are there any more reviews on this?  I do have some changes from\n> > this\n> > set, but they're pretty minor so I don't want to post a new one\n> > (unless\n> > folks would rather see those changes before reviewing).  Let me\n> > know.\n> \n> I think you need to keep \"refs\" directory back somehow. Without it\n> (e.g. \"git init --ref-storage=lmdb\" does not create \"refs\"),\n> is_git_directory() from _old_ git versions fails to recognize this is\n> a good repository. So instead of dying on finding an unsupported\n> repository, setup code keeps on looking for another repository in\n> parent directories. If I accidentally run \"git add something\" with an\n> old binary, \"something\" could be added to a wrong repository. Very\n> confusing.\n\nI went ahead and changed this.  Doing so seems to cause old versions of\ngit to recognize this as a git repo with an unknown version, which is\nwhat we want.  Thanks.\n"},{"id":"277480","messageId":"1454625843.5545.20.camel@twopensource.com","threadId":"41190","inReplyTo":"20160204025821.GA21105@sigill.intra.peff.net","subject":"Re: [PATCH v3 00/20] refs backend rebase on pu","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2016-02-04T22:44:03Z","receivedAt":"2016-02-04T22:44:03Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"On Wed, 2016-02-03 at 21:58 -0500, Jeff King wrote:\n> On Thu, Feb 04, 2016 at 01:54:44AM +0000, Ramsay Jones wrote:\n> \n> > > They were working for me as-of the time I sent them.  I guess\n> > > something\n> > > must have broken since.  I'll rebase, test, and send a new\n> > > series.\n> > \n> > I didn't spend too long looking at it, but I think this interacts\n> > with\n> > Jeff's patch a2d5156c (\"resolve_gitlink_ref: ignore non-repository\n> > paths\",\n> > 22-01-2016) which introduces the new test in 't3000-ls-files\n> > -others.sh'\n> > which fails for me.\n> > \n> > The change which Jeff made to resolve_gitlink_ref() is effectively\n> > side-stepped\n> > by the call to check_submodule_backend() in the new\n> > resolve_gitlink_ref().\n> > (Jeff's change is now in the 'files' backend version of\n> > resolve_gitlink_ref()).\n> \n> Yeah. The check_submodule_backend() function calls\n> strbuf_git_path_submodule(), which unconditionally requires that the\n> path be an actual submodule (the irony being that we are using it to\n> find out whether we have a submodule!). So I don't think there's a\n> conflict between our code, so much as that the new code has the same\n> bug\n> I fixed in a2d5156c (and we didn't notice earlier, because there was\n> no\n> test).\n> \n> The solution in a2d5156 is to use is_nonbare_repository_dir() before\n> assuming we have a submodule. I think check_submodule_backend() would\n> want to do the same thing. This is the minimal fix:\n> \n> diff --git a/refs.c b/refs.c\n> index 3d4c0a6..7f86c49 100644\n> --- a/refs.c\n> +++ b/refs.c\n> @@ -313,9 +313,8 @@ static void check_submodule_backend(const char\n> *submodule)\n>  \tif (!submodule)\n>  \t\tgoto done;\n>  \n> -\tstrbuf_git_path_submodule(&sb, submodule, \"%s\", \"\");\n> -\n> -\tif (!is_git_directory(sb.buf))\n> +\tstrbuf_addstr(&sb, submodule);\n> +\tif (!is_nonbare_repository_dir(&sb))\n>  \t\tgoto done;\n>  \n>  \tstrbuf_reset(&sb);\n\n+ one other place, yeah, that does fix it.\n\n> That gets the test passing. But I noticed a few other things, some\n> related and some unrelated, while looking at this function:\n> \n>   - in files_resolve_gitlink_ref, if we do find a submodule, we cache\n>     the result with the ref_cache code. But here, we would read the\n> .git\n>     file repeatedly (and in fact, it happens twice per call, as\n>     submodule_git_path has to read it itself).\n> \n>     I don't know if it would be worth adding any kind of caching at\n> this\n>     layer.  It may be that we don't typically resolve more than one\n> ref\n>     (or do more than one for_each_ref iteration) for a submodule, so\n> the\n>     cache is pointless. I didn't implement it specifically in\n> a2d5156,\n>     it just came for free with the existing ref_cache code.\n\nI'm going to punt on this for now; we can fix it if it becomes a\nproblem.\n\n>   - check_submodule_backend knows whether we have a submodule at all\n> and\n>     is worth proceeding, but does not tell its callers. So we'll end\n> up\n>     in the backend files_resolve_gitlink_ref and make the same check.\n>     It's probably worth moving this logic to the outer layer so each\n>     backend doesn't have to implement it (and then the check in\n>     files_resolve_gitlink_ref can actually go away).\n\nFixed, thanks.\n\n  - for the common case of submodule==NULL (i.e., the main repository),\n>     check_submodule_backend should be a noop, but it allocates and\n> frees\n>     the submodule_storage_backend string. Probably not a huge deal,\n> but\n>     it can be easily bumped down, and the first \"goto done\" turned\n> into\n>     a \"return\".\n\nFixed, thanks.\n\n>   - if the submodule does have a backend configured, we leak the\n> memory\n>     for the default string. I think the submodule_backend() config\n>     callback needs to free() the previous value.\n\nFixed.\n\n>   - the config callback unconditionally dereferences \"value\", which\n> will\n>     segfault if the submodule's extensions.refstorage is a pure\n> boolean\n>     like:\n> \n>         [extensions]\n> \trefstorage\n\nFixed.\n\n>     That should never happen, of course, but we should be checking\n> and\n>     dying for \"!value\" rather than segfaulting. Using\n>     git_config_string() will do this for you.\n> \n> Hope that helps.\n\nThanks!\n"}]}