threads / patch / 40445

v2, 43 partsrefs-be-files.c: rename refs to refs-be-files

Subject: [PATCH v2 03/43] refs-be-files.c: rename refs to refs-be-files

## tl;dr

90 messages between Sep 28, 2015 and Oct 12, 2015. Diffs are folded; open one to read it.

replies: 89people: 8as markdown or json

David Turner· Sep 28, 2015, 22:01 UTC · lore

[PATCH v2 00/43] lmdb ref backend

This version rebases on top of pu. Mainly, this just moves a couple of methods to the backend.

I added support for long symrefs to the lmdb backend. Peff added this to the files backend in pu, and there's a test for it.

I cleaned up some formatting (long lines), and moved one patch to put it closer to its related patches. I added a comment to a bit I found confusing when re-reading the patch (in the lmdb backend patch).

I re-ran the tests under lmdb, and everything that passed last time passes this time.

David Turner (24):
  refs: make repack_without_refs and is_branch public
  refs-be-files.c: add method for for_each_reftype_...
  refs-be-files.c: add do_for_each_per_worktree_ref
  refs.c: move refname_is_safe to the common code
  refs.h: document make refname_is_safe and add it to header
  refs.c: move copy_msg to the common code
  refs.c: move peel_object to the common code
  refs.c: move should_autocreate_reflog to common code
  refs.c: add ref backend init function
  refs.c: add methods for reflog
  refs-be-files.c: add method to expire reflogs
  refs.c: add method for initial ref transaction commit
  initdb: move safe_create_dir into common code
  refs.c: add method for initializing refs db
  refs.c: make struct ref_transaction generic
  refs-be-files.c: add method to rename refs
  run-command: track total number of commands run
  refs: move some defines from refs-be-files.c to refs.h
  refs: make some files backend functions public
  refs: break out a ref conflict check
  refs: allow ref backend to be set for clone
  refs: add register_refs_backend
  refs: add LMDB refs backend
  refs: tests for db backend
Ronnie Sahlberg (19):
  refs.c: create a public version of verify_refname_available
  refs-be-files.c: rename refs to refs-be-files
  refs.c: add a new refs.c file to hold all common refs code
  refs.c: move update_ref to refs.c
  refs.c: move delete_ref and delete_refs to the common code
  refs.c: move read_ref_at to the common refs file
  refs.c: move the hidden refs functions to the common code
  refs.c: move dwim and friend functions to the common refs code
  refs.c: move warn_if_dangling_symref* to the common code
  refs.c: move read_ref, read_ref_full and ref_exists to the common code
  refs.c: move resolve_refdup to common
  refs.c: move check_refname_format to the common code
  refs.c: move is_branch to the common code
  refs.c: move prettify_refname to the common code
  refs.c: move ref iterators to the common code
  refs.c: move head_ref_namespaced to the common code
  refs-be-files.c: add a backend method structure with transaction
    functions
  refs-be-files.c: add methods for misc ref operations
  refs-be-files.c: add methods for the ref iterators
 .gitignore                               |    1 +
 Documentation/git-clone.txt              |    4 +
 Documentation/git-init-db.txt            |    2 +-
 Documentation/git-init.txt               |    6 +
 Documentation/technical/refs-be-lmdb.txt |   39 +
 Makefile                                 |   13 +
 builtin/clone.c                          |   27 +-
 builtin/init-db.c                        |   39 +-
 builtin/submodule--helper.c              |    5 +-
 cache.h                                  |   13 +
 config.c                                 |   27 +
 configure.ac                             |   33 +
 contrib/workdir/git-new-workdir          |    2 +
 environment.c                            |    1 +
 path.c                                   |   12 +
 refs-be-files.c                          | 3757 +++++++++++++++++++++
 refs-be-lmdb.c                           | 1984 +++++++++++
 refs.c                                   | 5329 ++++++------------------------
 refs.h                                   |  282 +-
 run-command.c                            |    2 +
 run-command.h                            |    1 +
 setup.c                                  |   28 +-
 t/t1460-refs-be-db.sh                    | 1103 +++++++
 t/t1470-refs-be-db-reflog.sh             |  353 ++
 test-refs-be-lmdb.c                      |   68 +
 25 files changed, 8716 insertions(+), 4415 deletions(-)
 create mode 100644 Documentation/technical/refs-be-lmdb.txt
 create mode 100644 refs-be-files.c
 create mode 100644 refs-be-lmdb.c
 create mode 100755 t/t1460-refs-be-db.sh
 create mode 100755 t/t1470-refs-be-db-reflog.sh
 create mode 100644 test-refs-be-lmdb.c
-- 
2.4.2.644.g97b850b-twtrsrc
David Turner· Sep 28, 2015, 22:01 UTC · re: David Turner · lore

[PATCH v2 01/43] refs.c: create a public version of verify_refname_available

From: Ronnie Sahlberg <sahlberg@google.com>

Create a public version of verify_refname_available that backends can provide.

Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>
Signed-off-by: David Turner <dturner@twopensource.com>
---
 refs.c | 51 +++++++++++++++++++++++++++++----------------------
 refs.h | 17 +++++++++++++++++
 2 files changed, 46 insertions(+), 22 deletions(-)
Show changes to 2 files +46 −22

refs.c, refs.h

diff --git a/refs.c b/refs.c
index 132eff5..ce551e9 100644
--- a/refs.c
+++ b/refs.c
@@ -279,7 +279,7 @@ struct ref_dir {
  * presence of an empty subdirectory does not block the creation of a
  * similarly-named reference.  (The fact that reference names with the
  * same leading components can conflict *with each other* is a
- * separate issue that is regulated by verify_refname_available().)
+ * separate issue that is regulated by verify_refname_available_dir().)
  *
  * Please note that the name field contains the fully-qualified
  * reference (or subdirectory) name.  Space could be saved by only
@@ -911,11 +911,11 @@ static int nonmatching_ref_fn(struct ref_entry *entry, void *vdata)
  *
  * extras and skip must be sorted.
  */
-static int verify_refname_available(const char *refname,
-				    const struct string_list *extras,
-				    const struct string_list *skip,
-				    struct ref_dir *dir,
-				    struct strbuf *err)
+static int verify_refname_available_dir(const char *refname,
+					const struct string_list *extras,
+					const struct string_list *skip,
+					struct ref_dir *dir,
+					struct strbuf *err)
 {
 	const char *slash;
 	int pos;
@@ -2464,9 +2464,12 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
 		 */
 		strbuf_git_path(&orig_ref_file, "%s", orig_refname);
 		if (remove_empty_directories(&orig_ref_file)) {
+			struct ref_dir *loose_refs;
+			loose_refs = get_loose_refs(&ref_cache);
 			last_errno = errno;
-			if (!verify_refname_available(orig_refname, extras, skip,
-						      get_loose_refs(&ref_cache), err))
+			if (!verify_refname_available_dir(orig_refname, extras,
+							  skip, loose_refs,
+							  err))
 				strbuf_addf(err, "there are still refs under '%s'",
 					    orig_refname);
 			goto error_return;
@@ -2479,8 +2482,9 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
 	if (!refname) {
 		last_errno = errno;
 		if (last_errno != ENOTDIR ||
-		    !verify_refname_available(orig_refname, extras, skip,
-					      get_loose_refs(&ref_cache), err))
+		    !verify_refname_available_dir(orig_refname, extras, skip,
+						  get_loose_refs(&ref_cache),
+						  err))
 			strbuf_addf(err, "unable to resolve reference %s: %s",
 				    orig_refname, strerror(last_errno));
 
@@ -2493,8 +2497,8 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
 	 * our refname.
 	 */
 	if (is_null_oid(&lock->old_oid) &&
-	    verify_refname_available(refname, extras, skip,
-				     get_packed_refs(&ref_cache), err)) {
+	    verify_refname_available_dir(refname, extras, skip,
+					 get_packed_refs(&ref_cache), err)) {
 		last_errno = ENOTDIR;
 		goto error_return;
 	}
@@ -3127,10 +3131,7 @@ static int rename_ref_available(const char *oldname, const char *newname)
 	int ret;
 
 	string_list_insert(&skip, oldname);
-	ret = !verify_refname_available(newname, NULL, &skip,
-					get_packed_refs(&ref_cache), &err)
-		&& !verify_refname_available(newname, NULL, &skip,
-					     get_loose_refs(&ref_cache), &err);
+	ret = !verify_refname_available(newname, NULL, &skip, &err);
 	if (!ret)
 		error("%s", err.buf);
 
@@ -3299,6 +3300,17 @@ static int should_autocreate_reflog(const char *refname)
 		!strcmp(refname, "HEAD");
 }
 
+int verify_refname_available(const char *newname, struct string_list *extra,
+			     struct string_list *skip, struct strbuf *err)
+{
+	struct ref_dir *packed_refs = get_packed_refs(&ref_cache);
+	struct ref_dir *loose_refs = get_loose_refs(&ref_cache);
+	return verify_refname_available_dir(newname, extra, skip,
+					    packed_refs, err) ||
+		verify_refname_available_dir(newname, extra, skip,
+					     loose_refs, err);
+}
+
 /*
  * Create a reflog for a ref.  If force_create = 0, the reflog will
  * only be created for certain refs (those for which
@@ -4334,8 +4346,6 @@ static int ref_present(const char *refname,
 int initial_ref_transaction_commit(struct ref_transaction *transaction,
 				   struct strbuf *err)
 {
-	struct ref_dir *loose_refs = get_loose_refs(&ref_cache);
-	struct ref_dir *packed_refs = get_packed_refs(&ref_cache);
 	int ret = 0, i;
 	int n = transaction->nr;
 	struct ref_update **updates = transaction->updates;
@@ -4378,10 +4388,7 @@ int initial_ref_transaction_commit(struct ref_transaction *transaction,
 			die("BUG: initial ref transaction with old_sha1 set");
 		if (verify_refname_available(update->refname,
 					     &affected_refnames, NULL,
-					     loose_refs, err) ||
-		    verify_refname_available(update->refname,
-					     &affected_refnames, NULL,
-					     packed_refs, err)) {
+					     err)) {
 			ret = TRANSACTION_NAME_CONFLICT;
 			goto cleanup;
 		}
diff --git a/refs.h b/refs.h
index 6d30c98..79ea220 100644
--- a/refs.h
+++ b/refs.h
@@ -218,6 +218,23 @@ extern void warn_dangling_symrefs(FILE *fp, const char *msg_fmt, const struct st
 int pack_refs(unsigned int flags);
 
 /*
+ * Return true iff a reference named refname could be created without
+ * conflicting with the name of an existing reference.  If
+ * skip is non-NULL, ignore potential conflicts with refs in skip
+ * (e.g., because they are scheduled for deletion in the same
+ * operation).
+ *
+ * Two reference names conflict if one of them exactly matches the
+ * leading components of the other; e.g., "foo/bar" conflicts with
+ * both "foo" and with "foo/bar/baz" but not with "foo/bar" or
+ * "foo/barbados".
+ *
+ * skip must be sorted.
+ */
+int verify_refname_available(const char *newname, struct string_list *extra,
+			     struct string_list *skip, struct strbuf *err);
+
+/*
  * Flags controlling ref_transaction_update(), ref_transaction_create(), etc.
  * REF_NODEREF: act on the ref directly, instead of dereferencing
  *              symbolic references.
-- 
2.4.2.644.g97b850b-twtrsrc
Torsten Bögershausen· Oct 3, 2015, 05:02 UTC · re: David Turner · lore

Re: [PATCH v2 01/43] refs.c: create a public version of verify_refname_available

On 29.09.15 00:01, David Turner wrote:
>
(Not sure if this is the right thread to report on)
In file included from builtin/commit.c:20:
./refs.h:695:16: warning: redefinition of typedef 'ref_transaction_free_fn' is a C11 feature
      [-Wtypedef-redefinition]
typedef void (*ref_transaction_free_fn)(struct ref_transaction *transaction);
               ^
David Turner· Oct 3, 2015, 16:50 UTC · re: Torsten Bögershausen · lore

Re: [PATCH v2 01/43] refs.c: create a public version of verify_refname_available

On Sat, 2015-10-03 at 07:02 +0200, Torsten Bögershausen wrote:
Show 9 quoted lines
> On 29.09.15 00:01, David Turner wrote:
> >
> (Not sure if this is the right thread to report on)
> 
> In file included from builtin/commit.c:20:
> ./refs.h:695:16: warning: redefinition of typedef 'ref_transaction_free_fn' is a C11 feature
>       [-Wtypedef-redefinition]
> typedef void (*ref_transaction_free_fn)(struct ref_transaction *transaction);
>                ^
Fixed, thanks.
What compiler flag did you turn on to see that warning?
Torsten Bögershausen· Oct 3, 2015, 21:07 UTC · re: David Turner · lore

Re: [PATCH v2 01/43] refs.c: create a public version of verify_refname_available

On 03.10.15 18:50, David Turner wrote:
Show 15 quoted lines
> On Sat, 2015-10-03 at 07:02 +0200, Torsten Bögershausen wrote:
>> On 29.09.15 00:01, David Turner wrote:
>>>
>> (Not sure if this is the right thread to report on)
>>
>> In file included from builtin/commit.c:20:
>> ./refs.h:695:16: warning: redefinition of typedef 'ref_transaction_free_fn' is a C11 feature
>>       [-Wtypedef-redefinition]
>> typedef void (*ref_transaction_free_fn)(struct ref_transaction *transaction);
>>                ^
> 
> 
> Fixed, thanks.
> 
> What compiler flag did you turn on to see that warning?
Mac OS X, 10.9:
 gcc --version
Configured with: --prefix=/Library/Developer/CommandLineTools/usr --with-gxx-include-dir=/usr/include/c++/4.2.1
Apple LLVM version 6.0 (clang-600.0.57) (based on LLVM 3.5svn)
Target: x86_64-apple-darwin13.4.0
Thread model: posix
Torsten Bögershausen· Oct 4, 2015, 06:07 UTC · re: David Turner · lore

Re: [PATCH v2 01/43] refs.c: create a public version of verify_refname_available

On 2015-10-03 18.50, David Turner wrote:
Show 13 quoted lines
> On Sat, 2015-10-03 at 07:02 +0200, Torsten Bögershausen wrote:
>> On 29.09.15 00:01, David Turner wrote:
>> (Not sure if this is the right thread to report on)
>>
>> In file included from builtin/commit.c:20:
>> ./refs.h:695:16: warning: redefinition of typedef 'ref_transaction_free_fn' is a C11 feature
>>       [-Wtypedef-redefinition]
>> typedef void (*ref_transaction_free_fn)(struct ref_transaction *transaction);
>>                ^
>
> Fixed, thanks.
>
> What compiler flag did you turn on to see that warning?

This compiler reported it as an error, not a warning: gcc --version i686-apple-darwin10-gcc-4.2.1 (GCC) 4.2.1 (Apple Inc. build 5666) (dot 3) Copyright (C) 2007 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Michael Haggerty· Oct 5, 2015, 04:29 UTC · re: David Turner · lore

Re: [PATCH v2 01/43] refs.c: create a public version of verify_refname_available

On 09/29/2015 12:01 AM, David Turner wrote:
Show 64 quoted lines
> From: Ronnie Sahlberg <sahlberg@google.com>
> 
> Create a public version of verify_refname_available that backends can
> provide.
> 
> Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>
> Signed-off-by: David Turner <dturner@twopensource.com>
> ---
>  refs.c | 51 +++++++++++++++++++++++++++++----------------------
>  refs.h | 17 +++++++++++++++++
>  2 files changed, 46 insertions(+), 22 deletions(-)
> 
> diff --git a/refs.c b/refs.c
> index 132eff5..ce551e9 100644
> --- a/refs.c
> +++ b/refs.c
> @@ -279,7 +279,7 @@ struct ref_dir {
>   * presence of an empty subdirectory does not block the creation of a
>   * similarly-named reference.  (The fact that reference names with the
>   * same leading components can conflict *with each other* is a
> - * separate issue that is regulated by verify_refname_available().)
> + * separate issue that is regulated by verify_refname_available_dir().)
>   *
>   * Please note that the name field contains the fully-qualified
>   * reference (or subdirectory) name.  Space could be saved by only
> @@ -911,11 +911,11 @@ static int nonmatching_ref_fn(struct ref_entry *entry, void *vdata)
>   *
>   * extras and skip must be sorted.
>   */
> -static int verify_refname_available(const char *refname,
> -				    const struct string_list *extras,
> -				    const struct string_list *skip,
> -				    struct ref_dir *dir,
> -				    struct strbuf *err)
> +static int verify_refname_available_dir(const char *refname,
> +					const struct string_list *extras,
> +					const struct string_list *skip,
> +					struct ref_dir *dir,
> +					struct strbuf *err)
>  {
>  	const char *slash;
>  	int pos;
> [...]
> diff --git a/refs.h b/refs.h
> index 6d30c98..79ea220 100644
> --- a/refs.h
> +++ b/refs.h
> @@ -218,6 +218,23 @@ extern void warn_dangling_symrefs(FILE *fp, const char *msg_fmt, const struct st
>  int pack_refs(unsigned int flags);
>  
>  /*
> + * Return true iff a reference named refname could be created without
> + * conflicting with the name of an existing reference.  If
> + * skip is non-NULL, ignore potential conflicts with refs in skip
> + * (e.g., because they are scheduled for deletion in the same
> + * operation).
> + *
> + * Two reference names conflict if one of them exactly matches the
> + * leading components of the other; e.g., "foo/bar" conflicts with
> + * both "foo" and with "foo/bar/baz" but not with "foo/bar" or
> + * "foo/barbados".
> + *
> + * skip must be sorted.
> + */

This comment is approximately a copy of the comment for verify_refname_available_dir(). It seems unnecessary to keep both of them (and is also a small maintenance burden). I suggest you shorten the comment at verify_refname_available_dir() and make it refer to the comment for this function for the details.

Show 8 quoted lines
> +int verify_refname_available(const char *newname, struct string_list *extra,
> +			     struct string_list *skip, struct strbuf *err);
> +
> +/*
>   * Flags controlling ref_transaction_update(), ref_transaction_create(), etc.
>   * REF_NODEREF: act on the ref directly, instead of dereferencing
>   *              symbolic references.
> 
Michael
-- 
Michael Haggerty
mhagger@alum.mit.edu
David Turner· Oct 5, 2015, 20:23 UTC · re: Michael Haggerty · lore

Re: [PATCH v2 01/43] refs.c: create a public version of verify_refname_available

On Mon, 2015-10-05 at 06:29 +0200, Michael Haggerty wrote:
Show 9 quoted lines
> + * skip must be sorted.
> > + */
> 
> This comment is approximately a copy of the comment for
> verify_refname_available_dir(). It seems unnecessary to keep both of
> them (and is also a small maintenance burden). I suggest you shorten 
> the
> comment at verify_refname_available_dir() and make it refer to the
> comment for this function for the details.
Will do, thanks.
David Turner· Sep 28, 2015, 22:01 UTC · re: David Turner · lore

[PATCH v2 02/43] refs: make repack_without_refs and is_branch public

Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>
Signed-off-by: David Turner <dturner@twopensource.com>
---
 refs.c |  9 +--------
 refs.h | 13 +++++++++++++
 2 files changed, 14 insertions(+), 8 deletions(-)
Show changes to 2 files +14 −8

refs.c, refs.h

diff --git a/refs.c b/refs.c
index ce551e9..2741cc5 100644
--- a/refs.c
+++ b/refs.c
@@ -2826,14 +2826,7 @@ int pack_refs(unsigned int flags)
 	return 0;
 }
 
-/*
- * Rewrite the packed-refs file, omitting any refs listed in
- * 'refnames'. On error, leave packed-refs unchanged, write an error
- * message to 'err', and return a nonzero value.
- *
- * The refs in 'refnames' needn't be sorted. `err` must not be NULL.
- */
-static int repack_without_refs(struct string_list *refnames, struct strbuf *err)
+int repack_without_refs(struct string_list *refnames, struct strbuf *err)
 {
 	struct ref_dir *packed;
 	struct string_list_item *refname;
diff --git a/refs.h b/refs.h
index 79ea220..729bc3c 100644
--- a/refs.h
+++ b/refs.h
@@ -218,6 +218,19 @@ extern void warn_dangling_symrefs(FILE *fp, const char *msg_fmt, const struct st
 int pack_refs(unsigned int flags);
 
 /*
+ * Rewrite the packed-refs file, omitting any refs listed in
+ * 'refnames'. On error, packed-refs will be unchanged, the return
+ * value is nonzero, and a message about the error is written to the
+ * 'err' strbuf.
+ *
+ * The refs in 'refnames' needn't be sorted. `err` must not be NULL.
+ */
+extern int repack_without_refs(struct string_list *refnames,
+			       struct strbuf *err);
+
+extern int is_branch(const char *refname);
+
+/*
  * Return true iff a reference named refname could be created without
  * conflicting with the name of an existing reference.  If
  * skip is non-NULL, ignore potential conflicts with refs in skip
-- 
2.4.2.644.g97b850b-twtrsrc
Michael Haggerty· Oct 5, 2015, 04:34 UTC · re: David Turner · lore

Re: [PATCH v2 02/43] refs: make repack_without_refs and is_branch public

On 09/29/2015 12:01 AM, David Turner wrote:
Show 24 quoted lines
> Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>
> Signed-off-by: David Turner <dturner@twopensource.com>
> ---
>  refs.c |  9 +--------
>  refs.h | 13 +++++++++++++
>  2 files changed, 14 insertions(+), 8 deletions(-)
> 
> diff --git a/refs.c b/refs.c
> index ce551e9..2741cc5 100644
> --- a/refs.c
> +++ b/refs.c
> @@ -2826,14 +2826,7 @@ int pack_refs(unsigned int flags)
>  	return 0;
>  }
>  
> -/*
> - * Rewrite the packed-refs file, omitting any refs listed in
> - * 'refnames'. On error, leave packed-refs unchanged, write an error
> - * message to 'err', and return a nonzero value.
> - *
> - * The refs in 'refnames' needn't be sorted. `err` must not be NULL.
> - */
> -static int repack_without_refs(struct string_list *refnames, struct strbuf *err)
> +int repack_without_refs(struct string_list *refnames, struct strbuf *err)

I looked for the corresponding change to remove `static` from is_branch(). Apparently that function already had external linkage, even though it was not listed in the header file. As a convenience to readers you might note that peculiarity in the commit message.

> [...]
Michael
-- 
Michael Haggerty
mhagger@alum.mit.edu
David Turner· Oct 5, 2015, 20:26 UTC · re: Michael Haggerty · lore

Re: [PATCH v2 02/43] refs: make repack_without_refs and is_branch public

On Mon, 2015-10-05 at 06:34 +0200, Michael Haggerty wrote:
Show 30 quoted lines
> On 09/29/2015 12:01 AM, David Turner wrote:
> > Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>
> > Signed-off-by: David Turner <dturner@twopensource.com>
> > ---
> >  refs.c |  9 +--------
> >  refs.h | 13 +++++++++++++
> >  2 files changed, 14 insertions(+), 8 deletions(-)
> > 
> > diff --git a/refs.c b/refs.c
> > index ce551e9..2741cc5 100644
> > --- a/refs.c
> > +++ b/refs.c
> > @@ -2826,14 +2826,7 @@ int pack_refs(unsigned int flags)
> >  	return 0;
> >  }
> >  
> > -/*
> > - * Rewrite the packed-refs file, omitting any refs listed in
> > - * 'refnames'. On error, leave packed-refs unchanged, write an error
> > - * message to 'err', and return a nonzero value.
> > - *
> > - * The refs in 'refnames' needn't be sorted. `err` must not be NULL.
> > - */
> > -static int repack_without_refs(struct string_list *refnames, struct strbuf *err)
> > +int repack_without_refs(struct string_list *refnames, struct strbuf *err)
> 
> I looked for the corresponding change to remove `static` from
> is_branch(). Apparently that function already had external linkage, even
> though it was not listed in the header file. As a convenience to readers
> you might note that peculiarity in the commit message.
Will fix, thnaks.
David Turner· Sep 28, 2015, 22:01 UTC · re: David Turner · lore
From: Ronnie Sahlberg <sahlberg@google.com>

Rename refs.c to refs-be-files.c to indicate that this file now holds the implementation for the files based refs backend. A smaller portion of the code in this file is backend agnostic and will be moved to a a new refs.c file that will hold all the common refs code that is shared across all backends.

A second reason for first moving all the code to the new file and then move the backend agnostic code back to refs.c instead of the other way around is because the code that will eventually remain in this new refs-be-files.c file is so entangled that it would then be very difficult to break the split up into small independent patches/chunks.

Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>
---
 Makefile                  | 2 +-
 refs.c => refs-be-files.c | 0
 2 files changed, 1 insertion(+), 1 deletion(-)
 rename refs.c => refs-be-files.c (100%)
Show changes to 2 files +1 −1

Makefile, refs.c

diff --git a/Makefile b/Makefile
index 46d0ca7..19036de 100644
--- a/Makefile
+++ b/Makefile
@@ -766,7 +766,7 @@ LIB_OBJS += quote.o
 LIB_OBJS += reachable.o
 LIB_OBJS += read-cache.o
 LIB_OBJS += reflog-walk.o
-LIB_OBJS += refs.o
+LIB_OBJS += refs-be-files.o
 LIB_OBJS += ref-filter.o
 LIB_OBJS += remote.o
 LIB_OBJS += replace_object.o
diff --git a/refs.c b/refs-be-files.c
similarity index 100%
rename from refs.c
rename to refs-be-files.c
-- 
2.4.2.644.g97b850b-twtrsrc
David Turner· Sep 28, 2015, 22:01 UTC · re: David Turner · lore

[PATCH v2 04/43] refs.c: add a new refs.c file to hold all common refs code

From: Ronnie Sahlberg <sahlberg@google.com>

Create a new refs.c file that will be used to hold all the refs code that is backend agnostic and will be shared across all backends.

Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>
---
 Makefile | 1 +
 refs.c   | 3 +++
 2 files changed, 4 insertions(+)
 create mode 100644 refs.c
Show changes to 2 files +4 −0

Makefile, refs.c

diff --git a/Makefile b/Makefile
index 19036de..43ceab0 100644
--- a/Makefile
+++ b/Makefile
@@ -767,6 +767,7 @@ LIB_OBJS += reachable.o
 LIB_OBJS += read-cache.o
 LIB_OBJS += reflog-walk.o
 LIB_OBJS += refs-be-files.o
+LIB_OBJS += refs.o
 LIB_OBJS += ref-filter.o
 LIB_OBJS += remote.o
 LIB_OBJS += replace_object.o
diff --git a/refs.c b/refs.c
new file mode 100644
index 0000000..77492ff
--- /dev/null
+++ b/refs.c
@@ -0,0 +1,3 @@
+/*
+ * Common refs code for all backends.
+ */
-- 
2.4.2.644.g97b850b-twtrsrc
David Turner· Sep 28, 2015, 22:01 UTC · re: David Turner · lore

[PATCH v2 05/43] refs.c: move update_ref to refs.c

From: Ronnie Sahlberg <sahlberg@google.com>

Move update_ref() to the refs.c file since this function does not contain any backend specific code. Move the ref classifier functions as well, since update_ref depends on them.

Based on Ronnie Sahlberg's patch
Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>
Signed-off-by: David Turner <dturner@twopensource.com>
---
 refs-be-files.c | 117 +-------------------------------------------------------
 refs.c          | 115 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 116 insertions(+), 116 deletions(-)
Show changes to 2 files +116 −116

refs-be-files.c, refs.c

diff --git a/refs-be-files.c b/refs-be-files.c
index 2741cc5..2e8079e 100644
--- a/refs-be-files.c
+++ b/refs-be-files.c
@@ -2687,8 +2687,6 @@ struct pack_refs_cb_data {
 	struct ref_to_prune *ref_to_prune;
 };
 
-static int is_per_worktree_ref(const char *refname);
-
 /*
  * An each_ref_entry_fn that is run over loose references only.  If
  * the loose reference can be packed, add an entry in the packed ref
@@ -2703,7 +2701,7 @@ static int pack_if_possible_fn(struct ref_entry *entry, void *cb_data)
 	int is_tag_ref = starts_with(entry->name, "refs/tags/");
 
 	/* Do not pack per-worktree refs: */
-	if (is_per_worktree_ref(entry->name))
+	if (ref_type(entry->name) == REF_TYPE_PER_WORKTREE)
 		return 0;
 
 	/* ALWAYS pack tags */
@@ -2891,77 +2889,6 @@ static int delete_ref_loose(struct ref_lock *lock, int flag, struct strbuf *err)
 	return 0;
 }
 
-static int is_per_worktree_ref(const char *refname)
-{
-	return !strcmp(refname, "HEAD") ||
-		starts_with(refname, "refs/bisect/");
-}
-
-static int is_pseudoref_syntax(const char *refname)
-{
-	const char *c;
-
-	for (c = refname; *c; c++) {
-		if (!isupper(*c) && *c != '-' && *c != '_')
-			return 0;
-	}
-
-	return 1;
-}
-
-enum ref_type ref_type(const char *refname)
-{
-	if (is_per_worktree_ref(refname))
-		return REF_TYPE_PER_WORKTREE;
-	if (is_pseudoref_syntax(refname))
-		return REF_TYPE_PSEUDOREF;
-       return REF_TYPE_NORMAL;
-}
-
-static int write_pseudoref(const char *pseudoref, const unsigned char *sha1,
-			   const unsigned char *old_sha1, struct strbuf *err)
-{
-	const char *filename;
-	int fd;
-	static struct lock_file lock;
-	struct strbuf buf = STRBUF_INIT;
-	int ret = -1;
-
-	strbuf_addf(&buf, "%s\n", sha1_to_hex(sha1));
-
-	filename = git_path("%s", pseudoref);
-	fd = hold_lock_file_for_update(&lock, filename, LOCK_DIE_ON_ERROR);
-	if (fd < 0) {
-		strbuf_addf(err, "Could not open '%s' for writing: %s",
-			    filename, strerror(errno));
-		return -1;
-	}
-
-	if (old_sha1) {
-		unsigned char actual_old_sha1[20];
-
-		if (read_ref(pseudoref, actual_old_sha1))
-			die("could not read ref '%s'", pseudoref);
-		if (hashcmp(actual_old_sha1, old_sha1)) {
-			strbuf_addf(err, "Unexpected sha1 when writing %s", pseudoref);
-			rollback_lock_file(&lock);
-			goto done;
-		}
-	}
-
-	if (write_in_full(fd, buf.buf, buf.len) != buf.len) {
-		strbuf_addf(err, "Could not write to '%s'", filename);
-		rollback_lock_file(&lock);
-		goto done;
-	}
-
-	commit_lock_file(&lock);
-	ret = 0;
-done:
-	strbuf_release(&buf);
-	return ret;
-}
-
 static int delete_pseudoref(const char *pseudoref, const unsigned char *old_sha1)
 {
 	static struct lock_file lock;
@@ -4110,48 +4037,6 @@ int ref_transaction_verify(struct ref_transaction *transaction,
 				      flags, NULL, err);
 }
 
-int update_ref(const char *msg, const char *refname,
-	       const unsigned char *new_sha1, const unsigned char *old_sha1,
-	       unsigned int flags, enum action_on_err onerr)
-{
-	struct ref_transaction *t = NULL;
-	struct strbuf err = STRBUF_INIT;
-	int ret = 0;
-
-	if (ref_type(refname) == REF_TYPE_PSEUDOREF) {
-		ret = write_pseudoref(refname, new_sha1, old_sha1, &err);
-	} else {
-		t = ref_transaction_begin(&err);
-		if (!t ||
-		    ref_transaction_update(t, refname, new_sha1, old_sha1,
-					   flags, msg, &err) ||
-		    ref_transaction_commit(t, &err)) {
-			ret = 1;
-			ref_transaction_free(t);
-		}
-	}
-	if (ret) {
-		const char *str = "update_ref failed for ref '%s': %s";
-
-		switch (onerr) {
-		case UPDATE_REFS_MSG_ON_ERR:
-			error(str, refname, err.buf);
-			break;
-		case UPDATE_REFS_DIE_ON_ERR:
-			die(str, refname, err.buf);
-			break;
-		case UPDATE_REFS_QUIET_ON_ERR:
-			break;
-		}
-		strbuf_release(&err);
-		return 1;
-	}
-	strbuf_release(&err);
-	if (t)
-		ref_transaction_free(t);
-	return 0;
-}
-
 static int ref_update_reject_duplicates(struct string_list *refnames,
 					struct strbuf *err)
 {
diff --git a/refs.c b/refs.c
index 77492ff..2d10708 100644
--- a/refs.c
+++ b/refs.c
@@ -1,3 +1,118 @@
 /*
  * Common refs code for all backends.
  */
+#include "cache.h"
+#include "refs.h"
+#include "lockfile.h"
+
+static int is_per_worktree_ref(const char *refname)
+{
+	return !strcmp(refname, "HEAD") ||
+		starts_with(refname, "refs/bisect/");
+}
+
+static int is_pseudoref_syntax(const char *refname)
+{
+	const char *c;
+
+	for (c = refname; *c; c++) {
+		if (!isupper(*c) && *c != '-' && *c != '_')
+			return 0;
+	}
+
+	return 1;
+}
+
+enum ref_type ref_type(const char *refname)
+{
+	if (is_per_worktree_ref(refname))
+		return REF_TYPE_PER_WORKTREE;
+	if (is_pseudoref_syntax(refname))
+		return REF_TYPE_PSEUDOREF;
+       return REF_TYPE_NORMAL;
+}
+
+static int write_pseudoref(const char *pseudoref, const unsigned char *sha1,
+			   const unsigned char *old_sha1, struct strbuf *err)
+{
+	const char *filename;
+	int fd;
+	static struct lock_file lock;
+	struct strbuf buf = STRBUF_INIT;
+	int ret = -1;
+
+	strbuf_addf(&buf, "%s\n", sha1_to_hex(sha1));
+
+	filename = git_path("%s", pseudoref);
+	fd = hold_lock_file_for_update(&lock, filename, LOCK_DIE_ON_ERROR);
+	if (fd < 0) {
+		strbuf_addf(err, "Could not open '%s' for writing: %s",
+			    filename, strerror(errno));
+		return -1;
+	}
+
+	if (old_sha1) {
+		unsigned char actual_old_sha1[20];
+		read_ref(pseudoref, actual_old_sha1);
+		if (hashcmp(actual_old_sha1, old_sha1)) {
+			strbuf_addf(err, "Unexpected sha1 when writing %s", pseudoref);
+			rollback_lock_file(&lock);
+			goto done;
+		}
+	}
+
+	if (write_in_full(fd, buf.buf, buf.len) != buf.len) {
+		strbuf_addf(err, "Could not write to '%s'", filename);
+		rollback_lock_file(&lock);
+		goto done;
+	}
+
+	commit_lock_file(&lock);
+	ret = 0;
+done:
+	strbuf_release(&buf);
+	return ret;
+}
+
+int update_ref(const char *msg, const char *refname,
+	       const unsigned char *new_sha1, const unsigned char *old_sha1,
+	       unsigned int flags, enum action_on_err onerr)
+{
+	struct ref_transaction *t = NULL;
+	struct strbuf err = STRBUF_INIT;
+	int ret = 0;
+
+	if (ref_type(refname) == REF_TYPE_PSEUDOREF) {
+		ret = write_pseudoref(refname, new_sha1, old_sha1, &err);
+	} else {
+		t = ref_transaction_begin(&err);
+		if (!t ||
+		    ref_transaction_update(t, refname, new_sha1, old_sha1,
+					   flags, msg, &err) ||
+		    ref_transaction_commit(t, &err)) {
+			ret = 1;
+			ref_transaction_free(t);
+		}
+	}
+
+	if (ret) {
+		const char *str = "update_ref failed for ref '%s': %s";
+
+		switch (onerr) {
+		case UPDATE_REFS_MSG_ON_ERR:
+			error(str, refname, err.buf);
+			break;
+		case UPDATE_REFS_DIE_ON_ERR:
+			die(str, refname, err.buf);
+			break;
+		case UPDATE_REFS_QUIET_ON_ERR:
+			break;
+		}
+		strbuf_release(&err);
+		return 1;
+	}
+	strbuf_release(&err);
+	if (t)
+		ref_transaction_free(t);
+	return 0;
+}
-- 
2.4.2.644.g97b850b-twtrsrc
David Turner· Sep 28, 2015, 22:01 UTC · re: David Turner · lore

[PATCH v2 06/43] refs.c: move delete_ref and delete_refs to the common code

From: Ronnie Sahlberg <sahlberg@google.com>

Move delete_pseudoref, delete_ref() and delete_refs() to the refs.c file since these functions do not contain any backend specific code.

Based on a patch by Ronnie Sahlberg.
Signed-off-by: David Turner <dturner@twopensource.com>
Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>
---
 refs-be-files.c | 94 ---------------------------------------------------------
 refs.c          | 94 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 94 insertions(+), 94 deletions(-)
Show changes to 2 files +94 −94

refs-be-files.c, refs.c

diff --git a/refs-be-files.c b/refs-be-files.c
index 2e8079e..666ba7f 100644
--- a/refs-be-files.c
+++ b/refs-be-files.c
@@ -2889,100 +2889,6 @@ static int delete_ref_loose(struct ref_lock *lock, int flag, struct strbuf *err)
 	return 0;
 }
 
-static int delete_pseudoref(const char *pseudoref, const unsigned char *old_sha1)
-{
-	static struct lock_file lock;
-	const char *filename;
-
-	filename = git_path("%s", pseudoref);
-
-	if (old_sha1 && !is_null_sha1(old_sha1)) {
-		int fd;
-		unsigned char actual_old_sha1[20];
-
-		fd = hold_lock_file_for_update(&lock, filename,
-					       LOCK_DIE_ON_ERROR);
-		if (fd < 0)
-			die_errno(_("Could not open '%s' for writing"), filename);
-		if (read_ref(pseudoref, actual_old_sha1))
-			die("could not read ref '%s'", pseudoref);
-		if (hashcmp(actual_old_sha1, old_sha1)) {
-			warning("Unexpected sha1 when deleting %s", pseudoref);
-			rollback_lock_file(&lock);
-			return -1;
-		}
-
-		unlink(filename);
-		rollback_lock_file(&lock);
-	} else {
-		unlink(filename);
-	}
-
-	return 0;
-}
-
-int delete_ref(const char *refname, const unsigned char *old_sha1,
-	       unsigned int flags)
-{
-	struct ref_transaction *transaction;
-	struct strbuf err = STRBUF_INIT;
-
-	if (ref_type(refname) == REF_TYPE_PSEUDOREF)
-		return delete_pseudoref(refname, old_sha1);
-
-	transaction = ref_transaction_begin(&err);
-	if (!transaction ||
-	    ref_transaction_delete(transaction, refname, old_sha1,
-				   flags, NULL, &err) ||
-	    ref_transaction_commit(transaction, &err)) {
-		error("%s", err.buf);
-		ref_transaction_free(transaction);
-		strbuf_release(&err);
-		return 1;
-	}
-	ref_transaction_free(transaction);
-	strbuf_release(&err);
-	return 0;
-}
-
-int delete_refs(struct string_list *refnames)
-{
-	struct strbuf err = STRBUF_INIT;
-	int i, result = 0;
-
-	if (!refnames->nr)
-		return 0;
-
-	result = repack_without_refs(refnames, &err);
-	if (result) {
-		/*
-		 * If we failed to rewrite the packed-refs file, then
-		 * it is unsafe to try to remove loose refs, because
-		 * doing so might expose an obsolete packed value for
-		 * a reference that might even point at an object that
-		 * has been garbage collected.
-		 */
-		if (refnames->nr == 1)
-			error(_("could not delete reference %s: %s"),
-			      refnames->items[0].string, err.buf);
-		else
-			error(_("could not delete references: %s"), err.buf);
-
-		goto out;
-	}
-
-	for (i = 0; i < refnames->nr; i++) {
-		const char *refname = refnames->items[i].string;
-
-		if (delete_ref(refname, NULL, 0))
-			result |= error(_("could not remove reference %s"), refname);
-	}
-
-out:
-	strbuf_release(&err);
-	return result;
-}
-
 /*
  * People using contrib's git-new-workdir have .git/logs/refs ->
  * /some/other/path/.git/logs/refs, and that may live on another device.
diff --git a/refs.c b/refs.c
index 2d10708..205a899 100644
--- a/refs.c
+++ b/refs.c
@@ -116,3 +116,97 @@ int update_ref(const char *msg, const char *refname,
 		ref_transaction_free(t);
 	return 0;
 }
+
+
+static int delete_pseudoref(const char *pseudoref, const unsigned char *old_sha1)
+{
+	static struct lock_file lock;
+	const char *filename;
+
+	filename = git_path("%s", pseudoref);
+
+	if (old_sha1 && !is_null_sha1(old_sha1)) {
+		int fd;
+		unsigned char actual_old_sha1[20];
+
+		fd = hold_lock_file_for_update(&lock, filename,
+					       LOCK_DIE_ON_ERROR);
+		if (fd < 0)
+			die_errno(_("Could not open '%s' for writing"), filename);
+		read_ref(pseudoref, actual_old_sha1);
+		if (hashcmp(actual_old_sha1, old_sha1)) {
+			warning("Unexpected sha1 when deleting %s", pseudoref);
+			rollback_lock_file(&lock);
+			return -1;
+		}
+
+		unlink(filename);
+		rollback_lock_file(&lock);
+	} else {
+		unlink(filename);
+	}
+
+	return 0;
+}
+
+int delete_ref(const char *refname, const unsigned char *old_sha1,
+	       unsigned int flags)
+{
+	struct ref_transaction *transaction;
+	struct strbuf err = STRBUF_INIT;
+
+	if (ref_type(refname) == REF_TYPE_PSEUDOREF)
+		return delete_pseudoref(refname, old_sha1);
+
+	transaction = ref_transaction_begin(&err);
+	if (!transaction ||
+	    ref_transaction_delete(transaction, refname, old_sha1,
+				   flags, NULL, &err) ||
+	    ref_transaction_commit(transaction, &err)) {
+		error("%s", err.buf);
+		ref_transaction_free(transaction);
+		strbuf_release(&err);
+		return 1;
+	}
+	ref_transaction_free(transaction);
+	strbuf_release(&err);
+	return 0;
+}
+
+int delete_refs(struct string_list *refnames)
+{
+	struct strbuf err = STRBUF_INIT;
+	int i, result = 0;
+
+	if (!refnames->nr)
+		return 0;
+
+	result = repack_without_refs(refnames, &err);
+	if (result) {
+		/*
+		 * If we failed to rewrite the packed-refs file, then
+		 * it is unsafe to try to remove loose refs, because
+		 * doing so might expose an obsolete packed value for
+		 * a reference that might even point at an object that
+		 * has been garbage collected.
+		 */
+		if (refnames->nr == 1)
+			error(_("could not delete reference %s: %s"),
+			      refnames->items[0].string, err.buf);
+		else
+			error(_("could not delete references: %s"), err.buf);
+
+		goto out;
+	}
+
+	for (i = 0; i < refnames->nr; i++) {
+		const char *refname = refnames->items[i].string;
+
+		if (delete_ref(refname, NULL, 0))
+			result |= error(_("could not remove reference %s"), refname);
+	}
+
+out:
+	strbuf_release(&err);
+	return result;
+}
-- 
2.4.2.644.g97b850b-twtrsrc
David Turner· Sep 28, 2015, 22:01 UTC · re: David Turner · lore

[PATCH v2 07/43] refs.c: move read_ref_at to the common refs file

From: Ronnie Sahlberg <sahlberg@google.com>

Move read_ref_at() to the refs.c file since this function does not contain any backend specific code.

Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>
Signed-off-by: David Turner <dturner@twopensource.com>
---
 refs-be-files.c | 118 --------------------------------------------------------
 refs.c          | 118 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 118 insertions(+), 118 deletions(-)
Show changes to 2 files +118 −118

refs-be-files.c, refs.c

diff --git a/refs-be-files.c b/refs-be-files.c
index 666ba7f..f6cdea9 100644
--- a/refs-be-files.c
+++ b/refs-be-files.c
@@ -3436,124 +3436,6 @@ int create_symref(const char *ref_target, const char *refs_heads_master,
 	return 0;
 }
 
-struct read_ref_at_cb {
-	const char *refname;
-	unsigned long at_time;
-	int cnt;
-	int reccnt;
-	unsigned char *sha1;
-	int found_it;
-
-	unsigned char osha1[20];
-	unsigned char nsha1[20];
-	int tz;
-	unsigned long date;
-	char **msg;
-	unsigned long *cutoff_time;
-	int *cutoff_tz;
-	int *cutoff_cnt;
-};
-
-static int read_ref_at_ent(unsigned char *osha1, unsigned char *nsha1,
-		const char *email, unsigned long timestamp, int tz,
-		const char *message, void *cb_data)
-{
-	struct read_ref_at_cb *cb = cb_data;
-
-	cb->reccnt++;
-	cb->tz = tz;
-	cb->date = timestamp;
-
-	if (timestamp <= cb->at_time || cb->cnt == 0) {
-		if (cb->msg)
-			*cb->msg = xstrdup(message);
-		if (cb->cutoff_time)
-			*cb->cutoff_time = timestamp;
-		if (cb->cutoff_tz)
-			*cb->cutoff_tz = tz;
-		if (cb->cutoff_cnt)
-			*cb->cutoff_cnt = cb->reccnt - 1;
-		/*
-		 * we have not yet updated cb->[n|o]sha1 so they still
-		 * hold the values for the previous record.
-		 */
-		if (!is_null_sha1(cb->osha1)) {
-			hashcpy(cb->sha1, nsha1);
-			if (hashcmp(cb->osha1, nsha1))
-				warning("Log for ref %s has gap after %s.",
-					cb->refname, show_date(cb->date, cb->tz, DATE_MODE(RFC2822)));
-		}
-		else if (cb->date == cb->at_time)
-			hashcpy(cb->sha1, nsha1);
-		else if (hashcmp(nsha1, cb->sha1))
-			warning("Log for ref %s unexpectedly ended on %s.",
-				cb->refname, show_date(cb->date, cb->tz,
-						       DATE_MODE(RFC2822)));
-		hashcpy(cb->osha1, osha1);
-		hashcpy(cb->nsha1, nsha1);
-		cb->found_it = 1;
-		return 1;
-	}
-	hashcpy(cb->osha1, osha1);
-	hashcpy(cb->nsha1, nsha1);
-	if (cb->cnt > 0)
-		cb->cnt--;
-	return 0;
-}
-
-static int read_ref_at_ent_oldest(unsigned char *osha1, unsigned char *nsha1,
-				  const char *email, unsigned long timestamp,
-				  int tz, const char *message, void *cb_data)
-{
-	struct read_ref_at_cb *cb = cb_data;
-
-	if (cb->msg)
-		*cb->msg = xstrdup(message);
-	if (cb->cutoff_time)
-		*cb->cutoff_time = timestamp;
-	if (cb->cutoff_tz)
-		*cb->cutoff_tz = tz;
-	if (cb->cutoff_cnt)
-		*cb->cutoff_cnt = cb->reccnt;
-	hashcpy(cb->sha1, osha1);
-	if (is_null_sha1(cb->sha1))
-		hashcpy(cb->sha1, nsha1);
-	/* We just want the first entry */
-	return 1;
-}
-
-int read_ref_at(const char *refname, unsigned int flags, unsigned long at_time, int cnt,
-		unsigned char *sha1, char **msg,
-		unsigned long *cutoff_time, int *cutoff_tz, int *cutoff_cnt)
-{
-	struct read_ref_at_cb cb;
-
-	memset(&cb, 0, sizeof(cb));
-	cb.refname = refname;
-	cb.at_time = at_time;
-	cb.cnt = cnt;
-	cb.msg = msg;
-	cb.cutoff_time = cutoff_time;
-	cb.cutoff_tz = cutoff_tz;
-	cb.cutoff_cnt = cutoff_cnt;
-	cb.sha1 = sha1;
-
-	for_each_reflog_ent_reverse(refname, read_ref_at_ent, &cb);
-
-	if (!cb.reccnt) {
-		if (flags & GET_SHA1_QUIETLY)
-			exit(128);
-		else
-			die("Log for %s is empty.", refname);
-	}
-	if (cb.found_it)
-		return 0;
-
-	for_each_reflog_ent(refname, read_ref_at_ent_oldest, &cb);
-
-	return 1;
-}
-
 int reflog_exists(const char *refname)
 {
 	struct stat st;
diff --git a/refs.c b/refs.c
index 205a899..5a8ef0c 100644
--- a/refs.c
+++ b/refs.c
@@ -210,3 +210,121 @@ out:
 	strbuf_release(&err);
 	return result;
 }
+
+struct read_ref_at_cb {
+	const char *refname;
+	unsigned long at_time;
+	int cnt;
+	int reccnt;
+	unsigned char *sha1;
+	int found_it;
+
+	unsigned char osha1[20];
+	unsigned char nsha1[20];
+	int tz;
+	unsigned long date;
+	char **msg;
+	unsigned long *cutoff_time;
+	int *cutoff_tz;
+	int *cutoff_cnt;
+};
+
+static int read_ref_at_ent(unsigned char *osha1, unsigned char *nsha1,
+		const char *email, unsigned long timestamp, int tz,
+		const char *message, void *cb_data)
+{
+	struct read_ref_at_cb *cb = cb_data;
+
+	cb->reccnt++;
+	cb->tz = tz;
+	cb->date = timestamp;
+
+	if (timestamp <= cb->at_time || cb->cnt == 0) {
+		if (cb->msg)
+			*cb->msg = xstrdup(message);
+		if (cb->cutoff_time)
+			*cb->cutoff_time = timestamp;
+		if (cb->cutoff_tz)
+			*cb->cutoff_tz = tz;
+		if (cb->cutoff_cnt)
+			*cb->cutoff_cnt = cb->reccnt - 1;
+		/*
+		 * we have not yet updated cb->[n|o]sha1 so they still
+		 * hold the values for the previous record.
+		 */
+		if (!is_null_sha1(cb->osha1)) {
+			hashcpy(cb->sha1, nsha1);
+			if (hashcmp(cb->osha1, nsha1))
+				warning("Log for ref %s has gap after %s.",
+					cb->refname, show_date(cb->date, cb->tz, DATE_MODE(RFC2822)));
+		}
+		else if (cb->date == cb->at_time)
+			hashcpy(cb->sha1, nsha1);
+		else if (hashcmp(nsha1, cb->sha1))
+			warning("Log for ref %s unexpectedly ended on %s.",
+				cb->refname, show_date(cb->date, cb->tz,
+						       DATE_MODE(RFC2822)));
+		hashcpy(cb->osha1, osha1);
+		hashcpy(cb->nsha1, nsha1);
+		cb->found_it = 1;
+		return 1;
+	}
+	hashcpy(cb->osha1, osha1);
+	hashcpy(cb->nsha1, nsha1);
+	if (cb->cnt > 0)
+		cb->cnt--;
+	return 0;
+}
+
+static int read_ref_at_ent_oldest(unsigned char *osha1, unsigned char *nsha1,
+				  const char *email, unsigned long timestamp,
+				  int tz, const char *message, void *cb_data)
+{
+	struct read_ref_at_cb *cb = cb_data;
+
+	if (cb->msg)
+		*cb->msg = xstrdup(message);
+	if (cb->cutoff_time)
+		*cb->cutoff_time = timestamp;
+	if (cb->cutoff_tz)
+		*cb->cutoff_tz = tz;
+	if (cb->cutoff_cnt)
+		*cb->cutoff_cnt = cb->reccnt;
+	hashcpy(cb->sha1, osha1);
+	if (is_null_sha1(cb->sha1))
+		hashcpy(cb->sha1, nsha1);
+	/* We just want the first entry */
+	return 1;
+}
+
+int read_ref_at(const char *refname, unsigned int flags, unsigned long at_time, int cnt,
+		unsigned char *sha1, char **msg,
+		unsigned long *cutoff_time, int *cutoff_tz, int *cutoff_cnt)
+{
+	struct read_ref_at_cb cb;
+
+	memset(&cb, 0, sizeof(cb));
+	cb.refname = refname;
+	cb.at_time = at_time;
+	cb.cnt = cnt;
+	cb.msg = msg;
+	cb.cutoff_time = cutoff_time;
+	cb.cutoff_tz = cutoff_tz;
+	cb.cutoff_cnt = cutoff_cnt;
+	cb.sha1 = sha1;
+
+	for_each_reflog_ent_reverse(refname, read_ref_at_ent, &cb);
+
+	if (!cb.reccnt) {
+		if (flags & GET_SHA1_QUIETLY)
+			exit(128);
+		else
+			die("Log for %s is empty.", refname);
+	}
+	if (cb.found_it)
+		return 0;
+
+	for_each_reflog_ent(refname, read_ref_at_ent_oldest, &cb);
+
+	return 1;
+}
-- 
2.4.2.644.g97b850b-twtrsrc
David Turner· Sep 28, 2015, 22:01 UTC · re: David Turner · lore

[PATCH v2 08/43] refs.c: move the hidden refs functions to the common code

From: Ronnie Sahlberg <sahlberg@google.com>

Move the hidden refs functions to the refs.c file since these functions do not contain any backend specific code.

Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>
Signed-off-by: David Turner <dturner@twopensource.com>
---
 refs-be-files.c | 51 ---------------------------------------------------
 refs.c          | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 50 insertions(+), 51 deletions(-)
Show changes to 2 files +50 −51

refs-be-files.c, refs.c

diff --git a/refs-be-files.c b/refs-be-files.c
index f6cdea9..3609d02 100644
--- a/refs-be-files.c
+++ b/refs-be-files.c
@@ -4181,57 +4181,6 @@ char *shorten_unambiguous_ref(const char *refname, int strict)
 	return xstrdup(refname);
 }
 
-static struct string_list *hide_refs;
-
-int parse_hide_refs_config(const char *var, const char *value, const char *section)
-{
-	if (!strcmp("transfer.hiderefs", var) ||
-	    /* NEEDSWORK: use parse_config_key() once both are merged */
-	    (starts_with(var, section) && var[strlen(section)] == '.' &&
-	     !strcmp(var + strlen(section), ".hiderefs"))) {
-		char *ref;
-		int len;
-
-		if (!value)
-			return config_error_nonbool(var);
-		ref = xstrdup(value);
-		len = strlen(ref);
-		while (len && ref[len - 1] == '/')
-			ref[--len] = '\0';
-		if (!hide_refs) {
-			hide_refs = xcalloc(1, sizeof(*hide_refs));
-			hide_refs->strdup_strings = 1;
-		}
-		string_list_append(hide_refs, ref);
-	}
-	return 0;
-}
-
-int ref_is_hidden(const char *refname)
-{
-	int i;
-
-	if (!hide_refs)
-		return 0;
-	for (i = hide_refs->nr - 1; i >= 0; i--) {
-		const char *match = hide_refs->items[i].string;
-		int neg = 0;
-		int len;
-
-		if (*match == '!') {
-			neg = 1;
-			match++;
-		}
-
-		if (!starts_with(refname, match))
-			continue;
-		len = strlen(match);
-		if (!refname[len] || refname[len] == '/')
-			return !neg;
-	}
-	return 0;
-}
-
 struct expire_reflog_cb {
 	unsigned int flags;
 	reflog_expiry_should_prune_fn *should_prune_fn;
diff --git a/refs.c b/refs.c
index 5a8ef0c..6b2fc39 100644
--- a/refs.c
+++ b/refs.c
@@ -328,3 +328,53 @@ int read_ref_at(const char *refname, unsigned int flags, unsigned long at_time,
 
 	return 1;
 }
+
+static struct string_list *hide_refs;
+
+int parse_hide_refs_config(const char *var, const char *value, const char *section)
+{
+	if (!strcmp("transfer.hiderefs", var) ||
+	    /* NEEDSWORK: use parse_config_key() once both are merged */
+	    (starts_with(var, section) && var[strlen(section)] == '.' &&
+	     !strcmp(var + strlen(section), ".hiderefs"))) {
+		char *ref;
+		int len;
+
+		if (!value)
+			return config_error_nonbool(var);
+		ref = xstrdup(value);
+		len = strlen(ref);
+		while (len && ref[len - 1] == '/')
+			ref[--len] = '\0';
+		if (!hide_refs) {
+			hide_refs = xcalloc(1, sizeof(*hide_refs));
+			hide_refs->strdup_strings = 1;
+		}
+		string_list_append(hide_refs, ref);
+	}
+	return 0;
+}
+
+int ref_is_hidden(const char *refname)
+{
+	int i;
+
+	if (!hide_refs)
+		return 0;
+	for (i = hide_refs->nr - 1; i >= 0; i--) {
+		const char *match = hide_refs->items[i].string;
+		int neg = 0;
+		int len;
+		if (*match == '!') {
+			neg = 1;
+			match++;
+		}
+
+		if (!starts_with(refname, match))
+			continue;
+		len = strlen(match);
+		if (!refname[len] || refname[len] == '/')
+			return !neg;
+	}
+	return 0;
+}
-- 
2.4.2.644.g97b850b-twtrsrc
David Turner· Sep 28, 2015, 22:01 UTC · re: David Turner · lore

[PATCH v2 09/43] refs.c: move dwim and friend functions to the common refs code

From: Ronnie Sahlberg <sahlberg@google.com>

These functions do not contain any backend specific code so we move them to the common code and share across all backends.

Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>
Signed-off-by: David Turner <dturner@twopensource.com>
---
 refs-be-files.c | 203 --------------------------------------------------------
 refs.c          | 203 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 203 insertions(+), 203 deletions(-)
Show changes to 2 files +203 −203

refs-be-files.c, refs.c

diff --git a/refs-be-files.c b/refs-be-files.c
index 3609d02..eb660bb 100644
--- a/refs-be-files.c
+++ b/refs-be-files.c
@@ -2259,30 +2259,6 @@ const char *prettify_refname(const char *name)
 		0);
 }
 
-static const char *ref_rev_parse_rules[] = {
-	"%.*s",
-	"refs/%.*s",
-	"refs/tags/%.*s",
-	"refs/heads/%.*s",
-	"refs/remotes/%.*s",
-	"refs/remotes/%.*s/HEAD",
-	NULL
-};
-
-int refname_match(const char *abbrev_name, const char *full_name)
-{
-	const char **p;
-	const int abbrev_name_len = strlen(abbrev_name);
-
-	for (p = ref_rev_parse_rules; *p; p++) {
-		if (!strcmp(full_name, mkpath(*p, abbrev_name_len, abbrev_name))) {
-			return 1;
-		}
-	}
-
-	return 0;
-}
-
 static void unlock_ref(struct ref_lock *lock)
 {
 	/* Do not free lock->lk -- atexit() still looks at them */
@@ -2335,92 +2311,6 @@ static int remove_empty_directories(struct strbuf *path)
 }
 
 /*
- * *string and *len will only be substituted, and *string returned (for
- * later free()ing) if the string passed in is a magic short-hand form
- * to name a branch.
- */
-static char *substitute_branch_name(const char **string, int *len)
-{
-	struct strbuf buf = STRBUF_INIT;
-	int ret = interpret_branch_name(*string, *len, &buf);
-
-	if (ret == *len) {
-		size_t size;
-		*string = strbuf_detach(&buf, &size);
-		*len = size;
-		return (char *)*string;
-	}
-
-	return NULL;
-}
-
-int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref)
-{
-	char *last_branch = substitute_branch_name(&str, &len);
-	const char **p, *r;
-	int refs_found = 0;
-
-	*ref = NULL;
-	for (p = ref_rev_parse_rules; *p; p++) {
-		char fullref[PATH_MAX];
-		unsigned char sha1_from_ref[20];
-		unsigned char *this_result;
-		int flag;
-
-		this_result = refs_found ? sha1_from_ref : sha1;
-		mksnpath(fullref, sizeof(fullref), *p, len, str);
-		r = resolve_ref_unsafe(fullref, RESOLVE_REF_READING,
-				       this_result, &flag);
-		if (r) {
-			if (!refs_found++)
-				*ref = xstrdup(r);
-			if (!warn_ambiguous_refs)
-				break;
-		} else if ((flag & REF_ISSYMREF) && strcmp(fullref, "HEAD")) {
-			warning("ignoring dangling symref %s.", fullref);
-		} else if ((flag & REF_ISBROKEN) && strchr(fullref, '/')) {
-			warning("ignoring broken ref %s.", fullref);
-		}
-	}
-	free(last_branch);
-	return refs_found;
-}
-
-int dwim_log(const char *str, int len, unsigned char *sha1, char **log)
-{
-	char *last_branch = substitute_branch_name(&str, &len);
-	const char **p;
-	int logs_found = 0;
-
-	*log = NULL;
-	for (p = ref_rev_parse_rules; *p; p++) {
-		unsigned char hash[20];
-		char path[PATH_MAX];
-		const char *ref, *it;
-
-		mksnpath(path, sizeof(path), *p, len, str);
-		ref = resolve_ref_unsafe(path, RESOLVE_REF_READING,
-					 hash, NULL);
-		if (!ref)
-			continue;
-		if (reflog_exists(path))
-			it = path;
-		else if (strcmp(ref, path) && reflog_exists(ref))
-			it = ref;
-		else
-			continue;
-		if (!logs_found++) {
-			*log = xstrdup(it);
-			hashcpy(sha1, hash);
-		}
-		if (!warn_ambiguous_refs)
-			break;
-	}
-	free(last_branch);
-	return logs_found;
-}
-
-/*
  * Locks a ref returning the lock on success and NULL on failure.
  * On failure errno is set to something meaningful.
  */
@@ -4088,99 +3978,6 @@ cleanup:
 	return ret;
 }
 
-char *shorten_unambiguous_ref(const char *refname, int strict)
-{
-	int i;
-	static char **scanf_fmts;
-	static int nr_rules;
-	char *short_name;
-
-	if (!nr_rules) {
-		/*
-		 * Pre-generate scanf formats from ref_rev_parse_rules[].
-		 * Generate a format suitable for scanf from a
-		 * ref_rev_parse_rules rule by interpolating "%s" at the
-		 * location of the "%.*s".
-		 */
-		size_t total_len = 0;
-		size_t offset = 0;
-
-		/* the rule list is NULL terminated, count them first */
-		for (nr_rules = 0; ref_rev_parse_rules[nr_rules]; nr_rules++)
-			/* -2 for strlen("%.*s") - strlen("%s"); +1 for NUL */
-			total_len += strlen(ref_rev_parse_rules[nr_rules]) - 2 + 1;
-
-		scanf_fmts = xmalloc(nr_rules * sizeof(char *) + total_len);
-
-		offset = 0;
-		for (i = 0; i < nr_rules; i++) {
-			assert(offset < total_len);
-			scanf_fmts[i] = (char *)&scanf_fmts[nr_rules] + offset;
-			offset += snprintf(scanf_fmts[i], total_len - offset,
-					   ref_rev_parse_rules[i], 2, "%s") + 1;
-		}
-	}
-
-	/* bail out if there are no rules */
-	if (!nr_rules)
-		return xstrdup(refname);
-
-	/* buffer for scanf result, at most refname must fit */
-	short_name = xstrdup(refname);
-
-	/* skip first rule, it will always match */
-	for (i = nr_rules - 1; i > 0 ; --i) {
-		int j;
-		int rules_to_fail = i;
-		int short_name_len;
-
-		if (1 != sscanf(refname, scanf_fmts[i], short_name))
-			continue;
-
-		short_name_len = strlen(short_name);
-
-		/*
-		 * in strict mode, all (except the matched one) rules
-		 * must fail to resolve to a valid non-ambiguous ref
-		 */
-		if (strict)
-			rules_to_fail = nr_rules;
-
-		/*
-		 * check if the short name resolves to a valid ref,
-		 * but use only rules prior to the matched one
-		 */
-		for (j = 0; j < rules_to_fail; j++) {
-			const char *rule = ref_rev_parse_rules[j];
-			char refname[PATH_MAX];
-
-			/* skip matched rule */
-			if (i == j)
-				continue;
-
-			/*
-			 * the short name is ambiguous, if it resolves
-			 * (with this previous rule) to a valid ref
-			 * read_ref() returns 0 on success
-			 */
-			mksnpath(refname, sizeof(refname),
-				 rule, short_name_len, short_name);
-			if (ref_exists(refname))
-				break;
-		}
-
-		/*
-		 * short name is non-ambiguous if all previous rules
-		 * haven't resolved to a valid ref
-		 */
-		if (j == rules_to_fail)
-			return short_name;
-	}
-
-	free(short_name);
-	return xstrdup(refname);
-}
-
 struct expire_reflog_cb {
 	unsigned int flags;
 	reflog_expiry_should_prune_fn *should_prune_fn;
diff --git a/refs.c b/refs.c
index 6b2fc39..310b7f5 100644
--- a/refs.c
+++ b/refs.c
@@ -378,3 +378,206 @@ int ref_is_hidden(const char *refname)
 	}
 	return 0;
 }
+
+static const char *ref_rev_parse_rules[] = {
+	"%.*s",
+	"refs/%.*s",
+	"refs/tags/%.*s",
+	"refs/heads/%.*s",
+	"refs/remotes/%.*s",
+	"refs/remotes/%.*s/HEAD",
+	NULL
+};
+
+int refname_match(const char *abbrev_name, const char *full_name)
+{
+	const char **p;
+	const int abbrev_name_len = strlen(abbrev_name);
+
+	for (p = ref_rev_parse_rules; *p; p++) {
+		if (!strcmp(full_name, mkpath(*p, abbrev_name_len, abbrev_name))) {
+			return 1;
+		}
+	}
+
+	return 0;
+}
+
+/*
+ * *string and *len will only be substituted, and *string returned (for
+ * later free()ing) if the string passed in is a magic short-hand form
+ * to name a branch.
+ */
+static char *substitute_branch_name(const char **string, int *len)
+{
+	struct strbuf buf = STRBUF_INIT;
+	int ret = interpret_branch_name(*string, *len, &buf);
+
+	if (ret == *len) {
+		size_t size;
+		*string = strbuf_detach(&buf, &size);
+		*len = size;
+		return (char *)*string;
+	}
+
+	return NULL;
+}
+
+int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref)
+{
+	char *last_branch = substitute_branch_name(&str, &len);
+	const char **p, *r;
+	int refs_found = 0;
+
+	*ref = NULL;
+	for (p = ref_rev_parse_rules; *p; p++) {
+		char fullref[PATH_MAX];
+		unsigned char sha1_from_ref[20];
+		unsigned char *this_result;
+		int flag;
+
+		this_result = refs_found ? sha1_from_ref : sha1;
+		mksnpath(fullref, sizeof(fullref), *p, len, str);
+		r = resolve_ref_unsafe(fullref, RESOLVE_REF_READING,
+				       this_result, &flag);
+		if (r) {
+			if (!refs_found++)
+				*ref = xstrdup(r);
+			if (!warn_ambiguous_refs)
+				break;
+		} else if ((flag & REF_ISSYMREF) && strcmp(fullref, "HEAD")) {
+			warning("ignoring dangling symref %s.", fullref);
+		} else if ((flag & REF_ISBROKEN) && strchr(fullref, '/')) {
+			warning("ignoring broken ref %s.", fullref);
+		}
+	}
+	free(last_branch);
+	return refs_found;
+}
+
+int dwim_log(const char *str, int len, unsigned char *sha1, char **log)
+{
+	char *last_branch = substitute_branch_name(&str, &len);
+	const char **p;
+	int logs_found = 0;
+
+	*log = NULL;
+	for (p = ref_rev_parse_rules; *p; p++) {
+		unsigned char hash[20];
+		char path[PATH_MAX];
+		const char *ref, *it;
+
+		mksnpath(path, sizeof(path), *p, len, str);
+		ref = resolve_ref_unsafe(path, RESOLVE_REF_READING,
+					 hash, NULL);
+		if (!ref)
+			continue;
+		if (reflog_exists(path))
+			it = path;
+		else if (strcmp(ref, path) && reflog_exists(ref))
+			it = ref;
+		else
+			continue;
+		if (!logs_found++) {
+			*log = xstrdup(it);
+			hashcpy(sha1, hash);
+		}
+		if (!warn_ambiguous_refs)
+			break;
+	}
+	free(last_branch);
+	return logs_found;
+}
+
+char *shorten_unambiguous_ref(const char *refname, int strict)
+{
+	int i;
+	static char **scanf_fmts;
+	static int nr_rules;
+	char *short_name;
+
+	if (!nr_rules) {
+		/*
+		 * Pre-generate scanf formats from ref_rev_parse_rules[].
+		 * Generate a format suitable for scanf from a
+		 * ref_rev_parse_rules rule by interpolating "%s" at the
+		 * location of the "%.*s".
+		 */
+		size_t total_len = 0;
+		size_t offset = 0;
+
+		/* the rule list is NULL terminated, count them first */
+		for (nr_rules = 0; ref_rev_parse_rules[nr_rules]; nr_rules++)
+			/* -2 for strlen("%.*s") - strlen("%s"); +1 for NUL */
+			total_len += strlen(ref_rev_parse_rules[nr_rules]) - 2 + 1;
+
+		scanf_fmts = xmalloc(nr_rules * sizeof(char *) + total_len);
+
+		offset = 0;
+		for (i = 0; i < nr_rules; i++) {
+			assert(offset < total_len);
+			scanf_fmts[i] = (char *)&scanf_fmts[nr_rules] + offset;
+			offset += snprintf(scanf_fmts[i], total_len - offset,
+					   ref_rev_parse_rules[i], 2, "%s") + 1;
+		}
+	}
+
+	/* bail out if there are no rules */
+	if (!nr_rules)
+		return xstrdup(refname);
+
+	/* buffer for scanf result, at most refname must fit */
+	short_name = xstrdup(refname);
+
+	/* skip first rule, it will always match */
+	for (i = nr_rules - 1; i > 0 ; --i) {
+		int j;
+		int rules_to_fail = i;
+		int short_name_len;
+
+		if (1 != sscanf(refname, scanf_fmts[i], short_name))
+			continue;
+
+		short_name_len = strlen(short_name);
+
+		/*
+		 * in strict mode, all (except the matched one) rules
+		 * must fail to resolve to a valid non-ambiguous ref
+		 */
+		if (strict)
+			rules_to_fail = nr_rules;
+
+		/*
+		 * check if the short name resolves to a valid ref,
+		 * but use only rules prior to the matched one
+		 */
+		for (j = 0; j < rules_to_fail; j++) {
+			const char *rule = ref_rev_parse_rules[j];
+			char refname[PATH_MAX];
+
+			/* skip matched rule */
+			if (i == j)
+				continue;
+
+			/*
+			 * the short name is ambiguous, if it resolves
+			 * (with this previous rule) to a valid ref
+			 * read_ref() returns 0 on success
+			 */
+			mksnpath(refname, sizeof(refname),
+				 rule, short_name_len, short_name);
+			if (ref_exists(refname))
+				break;
+		}
+
+		/*
+		 * short name is non-ambiguous if all previous rules
+		 * haven't resolved to a valid ref
+		 */
+		if (j == rules_to_fail)
+			return short_name;
+	}
+
+	free(short_name);
+	return xstrdup(refname);
+}
-- 
2.4.2.644.g97b850b-twtrsrc
David Turner· Sep 28, 2015, 22:01 UTC · re: David Turner · lore

[PATCH v2 10/43] refs.c: move warn_if_dangling_symref* to the common code

From: Ronnie Sahlberg <sahlberg@google.com>

These functions do not use any backend specific code so we move them to the common code.

Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>
Signed-off-by: David Turner <dturner@twopensource.com>
---
 refs-be-files.c | 52 ----------------------------------------------------
 refs.c          | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 52 insertions(+), 52 deletions(-)
Show changes to 2 files +52 −52

refs-be-files.c, refs.c

diff --git a/refs-be-files.c b/refs-be-files.c
index eb660bb..2495d42 100644
--- a/refs-be-files.c
+++ b/refs-be-files.c
@@ -1956,58 +1956,6 @@ int peel_ref(const char *refname, unsigned char *sha1)
 	return peel_object(base, sha1);
 }
 
-struct warn_if_dangling_data {
-	FILE *fp;
-	const char *refname;
-	const struct string_list *refnames;
-	const char *msg_fmt;
-};
-
-static int warn_if_dangling_symref(const char *refname, const struct object_id *oid,
-				   int flags, void *cb_data)
-{
-	struct warn_if_dangling_data *d = cb_data;
-	const char *resolves_to;
-	struct object_id junk;
-
-	if (!(flags & REF_ISSYMREF))
-		return 0;
-
-	resolves_to = resolve_ref_unsafe(refname, 0, junk.hash, NULL);
-	if (!resolves_to
-	    || (d->refname
-		? strcmp(resolves_to, d->refname)
-		: !string_list_has_string(d->refnames, resolves_to))) {
-		return 0;
-	}
-
-	fprintf(d->fp, d->msg_fmt, refname);
-	fputc('\n', d->fp);
-	return 0;
-}
-
-void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname)
-{
-	struct warn_if_dangling_data data;
-
-	data.fp = fp;
-	data.refname = refname;
-	data.refnames = NULL;
-	data.msg_fmt = msg_fmt;
-	for_each_rawref(warn_if_dangling_symref, &data);
-}
-
-void warn_dangling_symrefs(FILE *fp, const char *msg_fmt, const struct string_list *refnames)
-{
-	struct warn_if_dangling_data data;
-
-	data.fp = fp;
-	data.refname = NULL;
-	data.refnames = refnames;
-	data.msg_fmt = msg_fmt;
-	for_each_rawref(warn_if_dangling_symref, &data);
-}
-
 /*
  * Call fn for each reference in the specified ref_cache, omitting
  * references not in the containing_dir of base.  fn is called for all
diff --git a/refs.c b/refs.c
index 310b7f5..24d5e28 100644
--- a/refs.c
+++ b/refs.c
@@ -581,3 +581,55 @@ char *shorten_unambiguous_ref(const char *refname, int strict)
 	free(short_name);
 	return xstrdup(refname);
 }
+
+struct warn_if_dangling_data {
+	FILE *fp;
+	const char *refname;
+	const struct string_list *refnames;
+	const char *msg_fmt;
+};
+
+static int warn_if_dangling_symref(const char *refname, const struct object_id *oid,
+				   int flags, void *cb_data)
+{
+	struct warn_if_dangling_data *d = cb_data;
+	const char *resolves_to;
+	struct object_id junk;
+
+	if (!(flags & REF_ISSYMREF))
+		return 0;
+
+	resolves_to = resolve_ref_unsafe(refname, 0, junk.hash, NULL);
+	if (!resolves_to
+	    || (d->refname
+		? strcmp(resolves_to, d->refname)
+		: !string_list_has_string(d->refnames, resolves_to))) {
+		return 0;
+	}
+
+	fprintf(d->fp, d->msg_fmt, refname);
+	fputc('\n', d->fp);
+	return 0;
+}
+
+void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname)
+{
+	struct warn_if_dangling_data data;
+
+	data.fp = fp;
+	data.refname = refname;
+	data.refnames = NULL;
+	data.msg_fmt = msg_fmt;
+	for_each_rawref(warn_if_dangling_symref, &data);
+}
+
+void warn_dangling_symrefs(FILE *fp, const char *msg_fmt, const struct string_list *refnames)
+{
+	struct warn_if_dangling_data data;
+
+	data.fp = fp;
+	data.refname = NULL;
+	data.refnames = refnames;
+	data.msg_fmt = msg_fmt;
+	for_each_rawref(warn_if_dangling_symref, &data);
+}
-- 
2.4.2.644.g97b850b-twtrsrc
David Turner· Sep 28, 2015, 22:01 UTC · re: David Turner · lore

[PATCH v2 11/43] refs.c: move read_ref, read_ref_full and ref_exists to the common code

From: Ronnie Sahlberg <sahlberg@google.com>

These functions do not depend on the backend implementation so we move them to the common code.

Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>
Signed-off-by: David Turner <dturner@twopensource.com>
---
 refs-be-files.c | 18 ------------------
 refs.c          | 18 ++++++++++++++++++
 2 files changed, 18 insertions(+), 18 deletions(-)
Show changes to 2 files +18 −18

refs-be-files.c, refs.c

diff --git a/refs-be-files.c b/refs-be-files.c
index 2495d42..d46474a 100644
--- a/refs-be-files.c
+++ b/refs-be-files.c
@@ -1803,24 +1803,6 @@ struct ref_filter {
 	void *cb_data;
 };
 
-int read_ref_full(const char *refname, int resolve_flags, unsigned char *sha1, int *flags)
-{
-	if (resolve_ref_unsafe(refname, resolve_flags, sha1, flags))
-		return 0;
-	return -1;
-}
-
-int read_ref(const char *refname, unsigned char *sha1)
-{
-	return read_ref_full(refname, RESOLVE_REF_READING, sha1, NULL);
-}
-
-int ref_exists(const char *refname)
-{
-	unsigned char sha1[20];
-	return !!resolve_ref_unsafe(refname, RESOLVE_REF_READING, sha1, NULL);
-}
-
 static int filter_refs(const char *refname, const struct object_id *oid,
 			   int flags, void *data)
 {
diff --git a/refs.c b/refs.c
index 24d5e28..242f66d 100644
--- a/refs.c
+++ b/refs.c
@@ -633,3 +633,21 @@ void warn_dangling_symrefs(FILE *fp, const char *msg_fmt, const struct string_li
 	data.msg_fmt = msg_fmt;
 	for_each_rawref(warn_if_dangling_symref, &data);
 }
+
+int read_ref_full(const char *refname, int resolve_flags, unsigned char *sha1, int *flags)
+{
+	if (resolve_ref_unsafe(refname, resolve_flags, sha1, flags))
+		return 0;
+	return -1;
+}
+
+int read_ref(const char *refname, unsigned char *sha1)
+{
+	return read_ref_full(refname, RESOLVE_REF_READING, sha1, NULL);
+}
+
+int ref_exists(const char *refname)
+{
+	unsigned char sha1[20];
+	return !!resolve_ref_unsafe(refname, RESOLVE_REF_READING, sha1, NULL);
+}
-- 
2.4.2.644.g97b850b-twtrsrc
David Turner· Sep 28, 2015, 22:01 UTC · re: David Turner · lore

[PATCH v2 12/43] refs.c: move resolve_refdup to common

From: Ronnie Sahlberg <sahlberg@google.com>

This function can be shared across all refs backends so move it to the common code.

Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>
Signed-off-by: David Turner <dturner@twopensource.com>
---
 refs-be-files.c | 7 -------
 refs.c          | 7 +++++++
 2 files changed, 7 insertions(+), 7 deletions(-)
Show changes to 2 files +7 −7

refs-be-files.c, refs.c

diff --git a/refs-be-files.c b/refs-be-files.c
index d46474a..e3f7b1d 100644
--- a/refs-be-files.c
+++ b/refs-be-files.c
@@ -1789,13 +1789,6 @@ const char *resolve_ref_unsafe(const char *refname, int resolve_flags,
 	return ret;
 }
 
-char *resolve_refdup(const char *refname, int resolve_flags,
-		     unsigned char *sha1, int *flags)
-{
-	return xstrdup_or_null(resolve_ref_unsafe(refname, resolve_flags,
-						  sha1, flags));
-}
-
 /* The argument to filter_refs */
 struct ref_filter {
 	const char *pattern;
diff --git a/refs.c b/refs.c
index 242f66d..039e5c0 100644
--- a/refs.c
+++ b/refs.c
@@ -651,3 +651,10 @@ int ref_exists(const char *refname)
 	unsigned char sha1[20];
 	return !!resolve_ref_unsafe(refname, RESOLVE_REF_READING, sha1, NULL);
 }
+
+char *resolve_refdup(const char *refname, int resolve_flags,
+		     unsigned char *sha1, int *flags)
+{
+	return xstrdup_or_null(resolve_ref_unsafe(refname, resolve_flags,
+						  sha1, flags));
+}
-- 
2.4.2.644.g97b850b-twtrsrc
David Turner· Sep 28, 2015, 22:01 UTC · re: David Turner · lore

[PATCH v2 13/43] refs.c: move check_refname_format to the common code

From: Ronnie Sahlberg <sahlberg@google.com>

This function does not contain any backend specific code so we move it to the common code.

Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>
Signed-off-by: David Turner <dturner@twopensource.com>
---
 refs-be-files.c | 109 --------------------------------------------------------
 refs.c          | 109 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 109 insertions(+), 109 deletions(-)
Show changes to 2 files +109 −109

refs-be-files.c, refs.c

diff --git a/refs-be-files.c b/refs-be-files.c
index e3f7b1d..f920b65 100644
--- a/refs-be-files.c
+++ b/refs-be-files.c
@@ -14,27 +14,6 @@ struct ref_lock {
 };
 
 /*
- * How to handle various characters in refnames:
- * 0: An acceptable character for refs
- * 1: End-of-component
- * 2: ., look for a preceding . to reject .. in refs
- * 3: {, look for a preceding @ to reject @{ in refs
- * 4: A bad character: ASCII control characters, and
- *    ":", "?", "[", "\", "^", "~", SP, or TAB
- * 5: *, reject unless REFNAME_REFSPEC_PATTERN is set
- */
-static unsigned char refname_disposition[256] = {
-	1, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
-	4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
-	4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 2, 1,
-	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 4,
-	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 0, 4, 0,
-	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 4, 4
-};
-
-/*
  * Flag passed to lock_ref_sha1_basic() telling it to tolerate broken
  * refs (i.e., because the reference is about to be deleted anyway).
  */
@@ -69,94 +48,6 @@ static unsigned char refname_disposition[256] = {
  * value to ref_update::flags
  */
 
-/*
- * Try to read one refname component from the front of refname.
- * Return the length of the component found, or -1 if the component is
- * not legal.  It is legal if it is something reasonable to have under
- * ".git/refs/"; We do not like it if:
- *
- * - any path component of it begins with ".", or
- * - it has double dots "..", or
- * - it has ASCII control characters, or
- * - it has ":", "?", "[", "\", "^", "~", SP, or TAB anywhere, or
- * - it has "*" anywhere unless REFNAME_REFSPEC_PATTERN is set, or
- * - it ends with a "/", or
- * - it ends with ".lock", or
- * - it contains a "@{" portion
- */
-static int check_refname_component(const char *refname, int *flags)
-{
-	const char *cp;
-	char last = '\0';
-
-	for (cp = refname; ; cp++) {
-		int ch = *cp & 255;
-		unsigned char disp = refname_disposition[ch];
-		switch (disp) {
-		case 1:
-			goto out;
-		case 2:
-			if (last == '.')
-				return -1; /* Refname contains "..". */
-			break;
-		case 3:
-			if (last == '@')
-				return -1; /* Refname contains "@{". */
-			break;
-		case 4:
-			return -1;
-		case 5:
-			if (!(*flags & REFNAME_REFSPEC_PATTERN))
-				return -1; /* refspec can't be a pattern */
-
-			/*
-			 * Unset the pattern flag so that we only accept
-			 * a single asterisk for one side of refspec.
-			 */
-			*flags &= ~ REFNAME_REFSPEC_PATTERN;
-			break;
-		}
-		last = ch;
-	}
-out:
-	if (cp == refname)
-		return 0; /* Component has zero length. */
-	if (refname[0] == '.')
-		return -1; /* Component starts with '.'. */
-	if (cp - refname >= LOCK_SUFFIX_LEN &&
-	    !memcmp(cp - LOCK_SUFFIX_LEN, LOCK_SUFFIX, LOCK_SUFFIX_LEN))
-		return -1; /* Refname ends with ".lock". */
-	return cp - refname;
-}
-
-int check_refname_format(const char *refname, int flags)
-{
-	int component_len, component_count = 0;
-
-	if (!strcmp(refname, "@"))
-		/* Refname is a single character '@'. */
-		return -1;
-
-	while (1) {
-		/* We are at the start of a path component. */
-		component_len = check_refname_component(refname, &flags);
-		if (component_len <= 0)
-			return -1;
-
-		component_count++;
-		if (refname[component_len] == '\0')
-			break;
-		/* Skip to next component. */
-		refname += component_len + 1;
-	}
-
-	if (refname[component_len - 1] == '.')
-		return -1; /* Refname ends with '.'. */
-	if (!(flags & REFNAME_ALLOW_ONELEVEL) && component_count < 2)
-		return -1; /* Refname has only one component. */
-	return 0;
-}
-
 struct ref_entry;
 
 /*
diff --git a/refs.c b/refs.c
index 039e5c0..e9cc2d4 100644
--- a/refs.c
+++ b/refs.c
@@ -658,3 +658,112 @@ char *resolve_refdup(const char *refname, int resolve_flags,
 	return xstrdup_or_null(resolve_ref_unsafe(refname, resolve_flags,
 						  sha1, flags));
 }
+
+/*
+ * How to handle various characters in refnames:
+ * 0: An acceptable character for refs
+ * 1: End-of-component
+ * 2: ., look for a preceding . to reject .. in refs
+ * 3: {, look for a preceding @ to reject @{ in refs
+ * 4: A bad character: ASCII control characters, and
+ *    ":", "?", "[", "\", "^", "~", SP, or TAB
+ * 5: *, reject unless REFNAME_REFSPEC_PATTERN is set
+ */
+static unsigned char refname_disposition[256] = {
+	1, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+	4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+	4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 2, 1,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 4,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 0, 4, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 4, 4
+};
+
+/*
+ * Try to read one refname component from the front of refname.
+ * Return the length of the component found, or -1 if the component is
+ * not legal.  It is legal if it is something reasonable to have under
+ * ".git/refs/"; We do not like it if:
+ *
+ * - any path component of it begins with ".", or
+ * - it has double dots "..", or
+ * - it has ASCII control characters, or
+ * - it has ":", "?", "[", "\", "^", "~", SP, or TAB anywhere, or
+ * - it has "*" anywhere unless REFNAME_REFSPEC_PATTERN is set, or
+ * - it ends with a "/", or
+ * - it ends with ".lock", or
+ * - it contains a "@{" portion
+ */
+static int check_refname_component(const char *refname, int *flags)
+{
+	const char *cp;
+	char last = '\0';
+
+	for (cp = refname; ; cp++) {
+		int ch = *cp & 255;
+		unsigned char disp = refname_disposition[ch];
+		switch (disp) {
+		case 1:
+			goto out;
+		case 2:
+			if (last == '.')
+				return -1; /* Refname contains "..". */
+			break;
+		case 3:
+			if (last == '@')
+				return -1; /* Refname contains "@{". */
+			break;
+		case 4:
+			return -1;
+		case 5:
+			if (!(*flags & REFNAME_REFSPEC_PATTERN))
+				return -1; /* refspec can't be a pattern */
+
+			/*
+			 * Unset the pattern flag so that we only accept
+			 * a single asterisk for one side of refspec.
+			 */
+			*flags &= ~ REFNAME_REFSPEC_PATTERN;
+			break;
+		}
+		last = ch;
+	}
+out:
+	if (cp == refname)
+		return 0; /* Component has zero length. */
+	if (refname[0] == '.')
+		return -1; /* Component starts with '.'. */
+	if (cp - refname >= LOCK_SUFFIX_LEN &&
+	    !memcmp(cp - LOCK_SUFFIX_LEN, LOCK_SUFFIX, LOCK_SUFFIX_LEN))
+		return -1; /* Refname ends with ".lock". */
+	return cp - refname;
+}
+
+int check_refname_format(const char *refname, int flags)
+{
+	int component_len, component_count = 0;
+
+	if (!strcmp(refname, "@"))
+		/* Refname is a single character '@'. */
+		return -1;
+
+	while (1) {
+		/* We are at the start of a path component. */
+		component_len = check_refname_component(refname, &flags);
+		if (component_len <= 0)
+			return -1;
+
+		component_count++;
+		if (refname[component_len] == '\0')
+			break;
+		/* Skip to next component. */
+		refname += component_len + 1;
+	}
+
+	if (refname[component_len - 1] == '.')
+		return -1; /* Refname ends with '.'. */
+	if (!(flags & REFNAME_ALLOW_ONELEVEL) && component_count < 2)
+		return -1; /* Refname has only one component. */
+	return 0;
+}
-- 
2.4.2.644.g97b850b-twtrsrc
David Turner· Sep 28, 2015, 22:01 UTC · re: David Turner · lore

[PATCH v2 14/43] refs.c: move is_branch to the common code

From: Ronnie Sahlberg <sahlberg@google.com>
Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>
Signed-off-by: David Turner <dturner@twopensource.com>
---
 refs-be-files.c | 5 -----
 refs.c          | 5 +++++
 2 files changed, 5 insertions(+), 5 deletions(-)
Show changes to 2 files +5 −5

refs-be-files.c, refs.c

diff --git a/refs-be-files.c b/refs-be-files.c
index f920b65..58ff453 100644
--- a/refs-be-files.c
+++ b/refs-be-files.c
@@ -2969,11 +2969,6 @@ static int log_ref_write(const char *refname, const unsigned char *old_sha1,
 	return ret;
 }
 
-int is_branch(const char *refname)
-{
-	return !strcmp(refname, "HEAD") || starts_with(refname, "refs/heads/");
-}
-
 /*
  * Write sha1 into the open lockfile, then close the lockfile. On
  * errors, rollback the lockfile, fill in *err and
diff --git a/refs.c b/refs.c
index e9cc2d4..bc8750c 100644
--- a/refs.c
+++ b/refs.c
@@ -767,3 +767,8 @@ int check_refname_format(const char *refname, int flags)
 		return -1; /* Refname has only one component. */
 	return 0;
 }
+
+int is_branch(const char *refname)
+{
+	return !strcmp(refname, "HEAD") || starts_with(refname, "refs/heads/");
+}
-- 
2.4.2.644.g97b850b-twtrsrc
David Turner· Sep 28, 2015, 22:01 UTC · re: David Turner · lore

[PATCH v2 15/43] refs.c: move prettify_refname to the common code

From: Ronnie Sahlberg <sahlberg@google.com>
Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>
---
 refs-be-files.c | 9 ---------
 refs.c          | 9 +++++++++
 2 files changed, 9 insertions(+), 9 deletions(-)
Show changes to 2 files +9 −9

refs-be-files.c, refs.c

diff --git a/refs-be-files.c b/refs-be-files.c
index 58ff453..9c27851 100644
--- a/refs-be-files.c
+++ b/refs-be-files.c
@@ -2064,15 +2064,6 @@ int for_each_rawref(each_ref_fn fn, void *cb_data)
 			       DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
 }
 
-const char *prettify_refname(const char *name)
-{
-	return name + (
-		starts_with(name, "refs/heads/") ? 11 :
-		starts_with(name, "refs/tags/") ? 10 :
-		starts_with(name, "refs/remotes/") ? 13 :
-		0);
-}
-
 static void unlock_ref(struct ref_lock *lock)
 {
 	/* Do not free lock->lk -- atexit() still looks at them */
diff --git a/refs.c b/refs.c
index bc8750c..44ee4f4 100644
--- a/refs.c
+++ b/refs.c
@@ -772,3 +772,12 @@ int is_branch(const char *refname)
 {
 	return !strcmp(refname, "HEAD") || starts_with(refname, "refs/heads/");
 }
+
+const char *prettify_refname(const char *name)
+{
+	return name + (
+		starts_with(name, "refs/heads/") ? 11 :
+		starts_with(name, "refs/tags/") ? 10 :
+		starts_with(name, "refs/remotes/") ? 13 :
+		0);
+}
-- 
2.4.2.644.g97b850b-twtrsrc
David Turner· Sep 28, 2015, 22:01 UTC · re: David Turner · lore

[PATCH v2 16/43] refs.c: move ref iterators to the common code

From: Ronnie Sahlberg <sahlberg@google.com>
Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>
---
 refs-be-files.c | 82 ---------------------------------------------------------
 refs.c          | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 81 insertions(+), 82 deletions(-)
Show changes to 2 files +81 −82

refs-be-files.c, refs.c

diff --git a/refs-be-files.c b/refs-be-files.c
index 9c27851..5a7e406 100644
--- a/refs-be-files.c
+++ b/refs-be-files.c
@@ -1680,23 +1680,6 @@ const char *resolve_ref_unsafe(const char *refname, int resolve_flags,
 	return ret;
 }
 
-/* The argument to filter_refs */
-struct ref_filter {
-	const char *pattern;
-	each_ref_fn *fn;
-	void *cb_data;
-};
-
-static int filter_refs(const char *refname, const struct object_id *oid,
-			   int flags, void *data)
-{
-	struct ref_filter *filter = (struct ref_filter *)data;
-
-	if (wildmatch(filter->pattern, refname, 0, NULL))
-		return 0;
-	return filter->fn(refname, oid, flags, filter->cb_data);
-}
-
 enum peel_status {
 	/* object was peeled successfully: */
 	PEEL_PEELED = 0,
@@ -1962,37 +1945,6 @@ int for_each_ref_in_submodule(const char *submodule, const char *prefix,
 {
 	return do_for_each_ref(get_ref_cache(submodule), prefix, fn, strlen(prefix), 0, cb_data);
 }
-
-int for_each_tag_ref(each_ref_fn fn, void *cb_data)
-{
-	return for_each_ref_in("refs/tags/", fn, cb_data);
-}
-
-int for_each_tag_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)
-{
-	return for_each_ref_in_submodule(submodule, "refs/tags/", fn, cb_data);
-}
-
-int for_each_branch_ref(each_ref_fn fn, void *cb_data)
-{
-	return for_each_ref_in("refs/heads/", fn, cb_data);
-}
-
-int for_each_branch_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)
-{
-	return for_each_ref_in_submodule(submodule, "refs/heads/", fn, cb_data);
-}
-
-int for_each_remote_ref(each_ref_fn fn, void *cb_data)
-{
-	return for_each_ref_in("refs/remotes/", fn, cb_data);
-}
-
-int for_each_remote_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)
-{
-	return for_each_ref_in_submodule(submodule, "refs/remotes/", fn, cb_data);
-}
-
 int for_each_replace_ref(each_ref_fn fn, void *cb_data)
 {
 	return do_for_each_ref(&ref_cache, git_replace_ref_base, fn,
@@ -2024,40 +1976,6 @@ int for_each_namespaced_ref(each_ref_fn fn, void *cb_data)
 	return ret;
 }
 
-int for_each_glob_ref_in(each_ref_fn fn, const char *pattern,
-	const char *prefix, void *cb_data)
-{
-	struct strbuf real_pattern = STRBUF_INIT;
-	struct ref_filter filter;
-	int ret;
-
-	if (!prefix && !starts_with(pattern, "refs/"))
-		strbuf_addstr(&real_pattern, "refs/");
-	else if (prefix)
-		strbuf_addstr(&real_pattern, prefix);
-	strbuf_addstr(&real_pattern, pattern);
-
-	if (!has_glob_specials(pattern)) {
-		/* Append implied '/' '*' if not present. */
-		strbuf_complete(&real_pattern, '/');
-		/* No need to check for '*', there is none. */
-		strbuf_addch(&real_pattern, '*');
-	}
-
-	filter.pattern = real_pattern.buf;
-	filter.fn = fn;
-	filter.cb_data = cb_data;
-	ret = for_each_ref(filter_refs, &filter);
-
-	strbuf_release(&real_pattern);
-	return ret;
-}
-
-int for_each_glob_ref(each_ref_fn fn, const char *pattern, void *cb_data)
-{
-	return for_each_glob_ref_in(fn, pattern, NULL, cb_data);
-}
-
 int for_each_rawref(each_ref_fn fn, void *cb_data)
 {
 	return do_for_each_ref(&ref_cache, "", fn, 0,
diff --git a/refs.c b/refs.c
index 44ee4f4..7714dad 100644
--- a/refs.c
+++ b/refs.c
@@ -781,3 +781,84 @@ const char *prettify_refname(const char *name)
 		starts_with(name, "refs/remotes/") ? 13 :
 		0);
 }
+
+/* The argument to filter_refs */
+struct ref_filter {
+	const char *pattern;
+	each_ref_fn *fn;
+	void *cb_data;
+};
+
+static int filter_refs(const char *refname, const struct object_id *oid, int flags,
+		       void *data)
+{
+	struct ref_filter *filter = (struct ref_filter *)data;
+	if (wildmatch(filter->pattern, refname, 0, NULL))
+		return 0;
+	return filter->fn(refname, oid, flags, filter->cb_data);
+}
+
+int for_each_glob_ref_in(each_ref_fn fn, const char *pattern,
+	const char *prefix, void *cb_data)
+{
+	struct strbuf real_pattern = STRBUF_INIT;
+	struct ref_filter filter;
+	int ret;
+
+	if (!prefix && !starts_with(pattern, "refs/"))
+		strbuf_addstr(&real_pattern, "refs/");
+	else if (prefix)
+		strbuf_addstr(&real_pattern, prefix);
+	strbuf_addstr(&real_pattern, pattern);
+
+	if (!has_glob_specials(pattern)) {
+		/* Append implied '/' '*' if not present. */
+		if (real_pattern.buf[real_pattern.len - 1] != '/')
+			strbuf_addch(&real_pattern, '/');
+		/* No need to check for '*', there is none. */
+		strbuf_addch(&real_pattern, '*');
+	}
+
+	filter.pattern = real_pattern.buf;
+	filter.fn = fn;
+	filter.cb_data = cb_data;
+	ret = for_each_ref(filter_refs, &filter);
+
+	strbuf_release(&real_pattern);
+	return ret;
+}
+
+int for_each_glob_ref(each_ref_fn fn, const char *pattern, void *cb_data)
+{
+	return for_each_glob_ref_in(fn, pattern, NULL, cb_data);
+}
+
+int for_each_tag_ref(each_ref_fn fn, void *cb_data)
+{
+	return for_each_ref_in("refs/tags/", fn, cb_data);
+}
+
+int for_each_tag_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)
+{
+	return for_each_ref_in_submodule(submodule, "refs/tags/", fn, cb_data);
+}
+
+int for_each_branch_ref(each_ref_fn fn, void *cb_data)
+{
+	return for_each_ref_in("refs/heads/", fn, cb_data);
+}
+
+int for_each_branch_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)
+{
+	return for_each_ref_in_submodule(submodule, "refs/heads/", fn, cb_data);
+}
+
+int for_each_remote_ref(each_ref_fn fn, void *cb_data)
+{
+	return for_each_ref_in("refs/remotes/", fn, cb_data);
+}
+
+int for_each_remote_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)
+{
+	return for_each_ref_in_submodule(submodule, "refs/remotes/", fn, cb_data);
+}
-- 
2.4.2.644.g97b850b-twtrsrc
David Turner· Sep 28, 2015, 22:01 UTC · re: David Turner · lore

[PATCH v2 17/43] refs.c: move head_ref_namespaced to the common code

From: Ronnie Sahlberg <sahlberg@google.com>
Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>
---
 refs-be-files.c | 15 ---------------
 refs.c          | 15 +++++++++++++++
 2 files changed, 15 insertions(+), 15 deletions(-)
Show changes to 2 files +15 −15

refs-be-files.c, refs.c

diff --git a/refs-be-files.c b/refs-be-files.c
index 5a7e406..05a4b88 100644
--- a/refs-be-files.c
+++ b/refs-be-files.c
@@ -1951,21 +1951,6 @@ int for_each_replace_ref(each_ref_fn fn, void *cb_data)
 			       strlen(git_replace_ref_base), 0, cb_data);
 }
 
-int head_ref_namespaced(each_ref_fn fn, void *cb_data)
-{
-	struct strbuf buf = STRBUF_INIT;
-	int ret = 0;
-	struct object_id oid;
-	int flag;
-
-	strbuf_addf(&buf, "%sHEAD", get_git_namespace());
-	if (!read_ref_full(buf.buf, RESOLVE_REF_READING, oid.hash, &flag))
-		ret = fn(buf.buf, &oid, flag, cb_data);
-	strbuf_release(&buf);
-
-	return ret;
-}
-
 int for_each_namespaced_ref(each_ref_fn fn, void *cb_data)
 {
 	struct strbuf buf = STRBUF_INIT;
diff --git a/refs.c b/refs.c
index 7714dad..0f4e19a 100644
--- a/refs.c
+++ b/refs.c
@@ -862,3 +862,18 @@ int for_each_remote_ref_submodule(const char *submodule, each_ref_fn fn, void *c
 {
 	return for_each_ref_in_submodule(submodule, "refs/remotes/", fn, cb_data);
 }
+
+int head_ref_namespaced(each_ref_fn fn, void *cb_data)
+{
+	struct strbuf buf = STRBUF_INIT;
+	int ret = 0;
+	struct object_id oid;
+	int flag;
+
+	strbuf_addf(&buf, "%sHEAD", get_git_namespace());
+	if (!read_ref_full(buf.buf, RESOLVE_REF_READING, oid.hash, &flag))
+		ret = fn(buf.buf, &oid, flag, cb_data);
+	strbuf_release(&buf);
+
+	return ret;
+}
-- 
2.4.2.644.g97b850b-twtrsrc
David Turner· Sep 28, 2015, 22:01 UTC · re: David Turner · lore

[PATCH v2 18/43] refs-be-files.c: add a backend method structure with transaction functions

From: Ronnie Sahlberg <sahlberg@google.com>

Add a ref structure for backend methods. Start by adding method pointers for the transaction functions.

Add a function set_refs_backend to switch between backends. The files based backend is the default.

Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>
Signed-off-by: David Turner <dturner@twopensource.com>
---
 refs-be-files.c | 62 +++++++++++++++++++++++++++-------------------
 refs.c          | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 refs.h          | 36 +++++++++++++++++++++++++++
 3 files changed, 150 insertions(+), 25 deletions(-)
Show changes to 3 files +150 −25

refs-be-files.c, refs.c, refs.h

diff --git a/refs-be-files.c b/refs-be-files.c
index 05a4b88..6e6f092 100644
--- a/refs-be-files.c
+++ b/refs-be-files.c
@@ -3310,14 +3310,14 @@ struct ref_transaction {
 	enum ref_transaction_state state;
 };
 
-struct ref_transaction *ref_transaction_begin(struct strbuf *err)
+static struct ref_transaction *files_transaction_begin(struct strbuf *err)
 {
 	assert(err);
 
 	return xcalloc(1, sizeof(struct ref_transaction));
 }
 
-void ref_transaction_free(struct ref_transaction *transaction)
+static void files_transaction_free(struct ref_transaction *transaction)
 {
 	int i;
 
@@ -3344,12 +3344,12 @@ static struct ref_update *add_update(struct ref_transaction *transaction,
 	return update;
 }
 
-int ref_transaction_update(struct ref_transaction *transaction,
-			   const char *refname,
-			   const unsigned char *new_sha1,
-			   const unsigned char *old_sha1,
-			   unsigned int flags, const char *msg,
-			   struct strbuf *err)
+static int files_transaction_update(struct ref_transaction *transaction,
+				  const char *refname,
+				  const unsigned char *new_sha1,
+				  const unsigned char *old_sha1,
+				  unsigned int flags, const char *msg,
+				  struct strbuf *err)
 {
 	struct ref_update *update;
 
@@ -3380,11 +3380,11 @@ int ref_transaction_update(struct ref_transaction *transaction,
 	return 0;
 }
 
-int ref_transaction_create(struct ref_transaction *transaction,
-			   const char *refname,
-			   const unsigned char *new_sha1,
-			   unsigned int flags, const char *msg,
-			   struct strbuf *err)
+static int files_transaction_create(struct ref_transaction *transaction,
+				  const char *refname,
+				  const unsigned char *new_sha1,
+				  unsigned int flags, const char *msg,
+				  struct strbuf *err)
 {
 	if (!new_sha1 || is_null_sha1(new_sha1))
 		die("BUG: create called without valid new_sha1");
@@ -3392,11 +3392,11 @@ int ref_transaction_create(struct ref_transaction *transaction,
 				      null_sha1, flags, msg, err);
 }
 
-int ref_transaction_delete(struct ref_transaction *transaction,
-			   const char *refname,
-			   const unsigned char *old_sha1,
-			   unsigned int flags, const char *msg,
-			   struct strbuf *err)
+static int files_transaction_delete(struct ref_transaction *transaction,
+				  const char *refname,
+				  const unsigned char *old_sha1,
+				  unsigned int flags, const char *msg,
+				  struct strbuf *err)
 {
 	if (old_sha1 && is_null_sha1(old_sha1))
 		die("BUG: delete called with old_sha1 set to zeros");
@@ -3405,11 +3405,11 @@ int ref_transaction_delete(struct ref_transaction *transaction,
 				      flags, msg, err);
 }
 
-int ref_transaction_verify(struct ref_transaction *transaction,
-			   const char *refname,
-			   const unsigned char *old_sha1,
-			   unsigned int flags,
-			   struct strbuf *err)
+int files_transaction_verify(struct ref_transaction *transaction,
+			     const char *refname,
+			     const unsigned char *old_sha1,
+			     unsigned int flags,
+			     struct strbuf *err)
 {
 	if (!old_sha1)
 		die("BUG: verify called with old_sha1 set to NULL");
@@ -3435,8 +3435,8 @@ static int ref_update_reject_duplicates(struct string_list *refnames,
 	return 0;
 }
 
-int ref_transaction_commit(struct ref_transaction *transaction,
-			   struct strbuf *err)
+static int files_transaction_commit(struct ref_transaction *transaction,
+				  struct strbuf *err)
 {
 	int ret = 0, i;
 	int n = transaction->nr;
@@ -3822,3 +3822,15 @@ int reflog_expire(const char *refname, const unsigned char *sha1,
 	unlock_ref(lock);
 	return -1;
 }
+
+struct ref_be refs_be_files = {
+	NULL,
+	"files",
+	files_transaction_begin,
+	files_transaction_update,
+	files_transaction_create,
+	files_transaction_delete,
+	files_transaction_verify,
+	files_transaction_commit,
+	files_transaction_free,
+};
diff --git a/refs.c b/refs.c
index 0f4e19a..98aa357 100644
--- a/refs.c
+++ b/refs.c
@@ -4,6 +4,29 @@
 #include "cache.h"
 #include "refs.h"
 #include "lockfile.h"
+/*
+ * We always have a files backend and it is the default.
+ */
+struct ref_be *the_refs_backend = &refs_be_files;
+/*
+ * List of all available backends
+ */
+struct ref_be *refs_backends = &refs_be_files;
+
+/*
+ * This function is used to switch to an alternate backend.
+ */
+int set_refs_backend(const char *name)
+{
+	struct ref_be *be;
+
+	for (be = refs_backends; be; be = be->next)
+		if (!strcmp(be->name, name)) {
+			the_refs_backend = be;
+			return 0;
+		}
+	return 1;
+}
 
 static int is_per_worktree_ref(const char *refname)
 {
@@ -877,3 +900,57 @@ int head_ref_namespaced(each_ref_fn fn, void *cb_data)
 
 	return ret;
 }
+
+
+/* backend functions */
+struct ref_transaction *ref_transaction_begin(struct strbuf *err)
+{
+	return the_refs_backend->transaction_begin(err);
+}
+
+
+int ref_transaction_update(struct ref_transaction *transaction,
+			   const char *refname, const unsigned char *new_sha1,
+			   const unsigned char *old_sha1, unsigned int flags,
+			   const char *msg, struct strbuf *err)
+{
+	return the_refs_backend->transaction_update(transaction,
+			refname, new_sha1, old_sha1, flags, msg, err);
+}
+
+int ref_transaction_create(struct ref_transaction *transaction,
+			   const char *refname, const unsigned char *new_sha1,
+			   unsigned int flags, const char *msg,
+			   struct strbuf *err)
+{
+	return the_refs_backend->transaction_create(transaction,
+			refname, new_sha1, flags, msg, err);
+}
+int ref_transaction_delete(struct ref_transaction *transaction,
+			   const char *refname, const unsigned char *old_sha1,
+			   unsigned int flags, const char *msg,
+			   struct strbuf *err)
+{
+	return the_refs_backend->transaction_delete(transaction,
+			refname, old_sha1, flags, msg, err);
+}
+
+int ref_transaction_verify(struct ref_transaction *transaction,
+			   const char *refname, const unsigned char *old_sha1,
+			   unsigned int flags,
+			   struct strbuf *err)
+{
+	return the_refs_backend->transaction_verify(transaction,
+			refname, old_sha1, flags, err);
+}
+
+int ref_transaction_commit(struct ref_transaction *transaction,
+			   struct strbuf *err)
+{
+	return the_refs_backend->transaction_commit(transaction, err);
+}
+
+void ref_transaction_free(struct ref_transaction *transaction)
+{
+	return the_refs_backend->transaction_free(transaction);
+}
diff --git a/refs.h b/refs.h
index 729bc3c..a1db3ef 100644
--- a/refs.h
+++ b/refs.h
@@ -530,4 +530,40 @@ extern int reflog_expire(const char *refname, const unsigned char *sha1,
 			 reflog_expiry_cleanup_fn cleanup_fn,
 			 void *policy_cb_data);
 
+/* refs backends */
+typedef struct ref_transaction *(*ref_transaction_begin_fn)(struct strbuf *err);
+typedef int (*ref_transaction_update_fn)(struct ref_transaction *transaction,
+		const char *refname, const unsigned char *new_sha1,
+		const unsigned char *old_sha1, unsigned int flags,
+		const char *msg, struct strbuf *err);
+typedef int (*ref_transaction_create_fn)(
+		struct ref_transaction *transaction,
+		const char *refname, const unsigned char *new_sha1,
+		unsigned int flags, const char *msg, struct strbuf *err);
+typedef int (*ref_transaction_delete_fn)(struct ref_transaction *transaction,
+		const char *refname, const unsigned char *old_sha1,
+		unsigned int flags, const char *msg, struct strbuf *err);
+typedef int (*ref_transaction_verify_fn)(struct ref_transaction *transaction,
+		const char *refname, const unsigned char *old_sha1,
+		unsigned int flags, struct strbuf *err);
+typedef int (*ref_transaction_commit_fn)(struct ref_transaction *transaction,
+				     struct strbuf *err);
+typedef void (*ref_transaction_free_fn)(struct ref_transaction *transaction);
+
+struct ref_be {
+	struct ref_be *next;
+	const char *name;
+	ref_transaction_begin_fn transaction_begin;
+	ref_transaction_update_fn transaction_update;
+	ref_transaction_create_fn transaction_create;
+	ref_transaction_delete_fn transaction_delete;
+	ref_transaction_verify_fn transaction_verify;
+	ref_transaction_commit_fn transaction_commit;
+	ref_transaction_free_fn transaction_free;
+};
+
+
+extern struct ref_be refs_be_files;
+int set_refs_backend(const char *name);
+
 #endif /* REFS_H */
-- 
2.4.2.644.g97b850b-twtrsrc
Michael Haggerty· Oct 5, 2015, 08:03 UTC · re: David Turner · lore

Re: [PATCH v2 18/43] refs-be-files.c: add a backend method structure with transaction functions

On 09/29/2015 12:01 AM, David Turner wrote:
Show 27 quoted lines
> From: Ronnie Sahlberg <sahlberg@google.com>
> 
> Add a ref structure for backend methods. Start by adding method pointers
> for the transaction functions.
> 
> Add a function set_refs_backend to switch between backends. The files
> based backend is the default.
> 
> Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>
> Signed-off-by: David Turner <dturner@twopensource.com>
> ---
>  refs-be-files.c | 62 +++++++++++++++++++++++++++-------------------
>  refs.c          | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  refs.h          | 36 +++++++++++++++++++++++++++
>  3 files changed, 150 insertions(+), 25 deletions(-)
> 
> [...]
> diff --git a/refs.h b/refs.h
> index 729bc3c..a1db3ef 100644
> --- a/refs.h
> +++ b/refs.h
> @@ -530,4 +530,40 @@ extern int reflog_expire(const char *refname, const unsigned char *sha1,
>  			 reflog_expiry_cleanup_fn cleanup_fn,
>  			 void *policy_cb_data);
>  
> +/* refs backends */
> +typedef struct ref_transaction *(*ref_transaction_begin_fn)(struct strbuf *err);

Hmmm, I thought our convention was to define typedefs for functions themselves, not for the pointer-to-function; e.g.,

    typedef struct ref_transaction *ref_transaction_begin_fn(struct
strbuf *err);
(which would require `struct ref_be` to be changed to
        ref_transaction_begin_fn *transaction_begin;

etc.) But now as I grep through the code it looks like both conventions are used. So never mind :-)

Show 36 quoted lines
> +typedef int (*ref_transaction_update_fn)(struct ref_transaction *transaction,
> +		const char *refname, const unsigned char *new_sha1,
> +		const unsigned char *old_sha1, unsigned int flags,
> +		const char *msg, struct strbuf *err);
> +typedef int (*ref_transaction_create_fn)(
> +		struct ref_transaction *transaction,
> +		const char *refname, const unsigned char *new_sha1,
> +		unsigned int flags, const char *msg, struct strbuf *err);
> +typedef int (*ref_transaction_delete_fn)(struct ref_transaction *transaction,
> +		const char *refname, const unsigned char *old_sha1,
> +		unsigned int flags, const char *msg, struct strbuf *err);
> +typedef int (*ref_transaction_verify_fn)(struct ref_transaction *transaction,
> +		const char *refname, const unsigned char *old_sha1,
> +		unsigned int flags, struct strbuf *err);
> +typedef int (*ref_transaction_commit_fn)(struct ref_transaction *transaction,
> +				     struct strbuf *err);
> +typedef void (*ref_transaction_free_fn)(struct ref_transaction *transaction);
> +
> +struct ref_be {
> +	struct ref_be *next;
> +	const char *name;
> +	ref_transaction_begin_fn transaction_begin;
> +	ref_transaction_update_fn transaction_update;
> +	ref_transaction_create_fn transaction_create;
> +	ref_transaction_delete_fn transaction_delete;
> +	ref_transaction_verify_fn transaction_verify;
> +	ref_transaction_commit_fn transaction_commit;
> +	ref_transaction_free_fn transaction_free;
> +};
> +
> +
> +extern struct ref_be refs_be_files;
> +int set_refs_backend(const char *name);
> +
>  #endif /* REFS_H */
> 
Michael
-- 
Michael Haggerty
mhagger@alum.mit.edu
Junio C Hamano· Oct 5, 2015, 17:25 UTC · re: Michael Haggerty · lore

Re: [PATCH v2 18/43] refs-be-files.c: add a backend method structure with transaction functions

Michael Haggerty <mhagger@alum.mit.edu> writes:
Show 15 quoted lines
>> +/* refs backends */
>> +typedef struct ref_transaction *(*ref_transaction_begin_fn)(struct strbuf *err);
>
> Hmmm, I thought our convention was to define typedefs for functions
> themselves, not for the pointer-to-function; e.g.,
>
>     typedef struct ref_transaction *ref_transaction_begin_fn(struct
> strbuf *err);
>
> (which would require `struct ref_be` to be changed to
>
>         ref_transaction_begin_fn *transaction_begin;
>
> etc.) But now as I grep through the code it looks like both conventions
> are used. So never mind :-)

Well spotted. My recollection is the same and we do prefer the latter (I think all early function typedefs by Linus were done that way). It would be better to correct existing mistakes we added over time and certainly not add more of them in new code.

Thanks.
David Turner· Oct 6, 2015, 00:20 UTC · re: Junio C Hamano · lore

Re: [PATCH v2 18/43] refs-be-files.c: add a backend method structure with transaction functions

On Mon, 2015-10-05 at 10:25 -0700, Junio C Hamano wrote:
Show 22 quoted lines
> Michael Haggerty <mhagger@alum.mit.edu> writes:
> 
> >> +/* refs backends */
> >> +typedef struct ref_transaction *(*ref_transaction_begin_fn)(struct strbuf *err);
> >
> > Hmmm, I thought our convention was to define typedefs for functions
> > themselves, not for the pointer-to-function; e.g.,
> >
> >     typedef struct ref_transaction *ref_transaction_begin_fn(struct
> > strbuf *err);
> >
> > (which would require `struct ref_be` to be changed to
> >
> >         ref_transaction_begin_fn *transaction_begin;
> >
> > etc.) But now as I grep through the code it looks like both conventions
> > are used. So never mind :-)
> 
> Well spotted.  My recollection is the same and we do prefer the
> latter (I think all early function typedefs by Linus were done that
> way).  It would be better to correct existing mistakes we added over
> time and certainly not add more of them in new code.
Will fix, thanks.
David Turner· Sep 28, 2015, 22:01 UTC · re: David Turner · lore

[PATCH v2 19/43] refs-be-files.c: add methods for misc ref operations

From: Ronnie Sahlberg <sahlberg@google.com>

Add ref backend methods for: resolve_ref_unsafe, verify_refname_available, pack_refs, peel_ref, create_symref, resolve_gitlink_ref.

Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>
Signed-off-by: David Turner <dturner@twopensource.com>
---
 cache.h         |  7 +++++++
 refs-be-files.c | 31 ++++++++++++++++++++++---------
 refs.c          | 48 ++++++++++++++++++++++++++++++++++++++++++++++++
 refs.h          | 21 +++++++++++++++++++++
 4 files changed, 98 insertions(+), 9 deletions(-)
Show changes to 4 files +98 −9

cache.h, refs-be-files.c, refs.c, refs.h

diff --git a/cache.h b/cache.h
index 497d296..1c45b62 100644
--- a/cache.h
+++ b/cache.h
@@ -1104,6 +1104,13 @@ extern char *oid_to_hex(const struct object_id *oid);	/* same static buffer as s
 extern int interpret_branch_name(const char *str, int len, struct strbuf *);
 extern int get_sha1_mb(const char *str, unsigned char *sha1);
 
+/*
+ * Return true iff abbrev_name is a possible abbreviation for
+ * full_name according to the rules defined by ref_rev_parse_rules in
+ * refs.c.
+ */
+extern int refname_match(const char *abbrev_name, const char *full_name);
+
 extern int validate_headref(const char *ref);
 
 extern int base_name_compare(const char *name1, int len1, int mode1, const char *name2, int len2, int mode2);
diff --git a/refs-be-files.c b/refs-be-files.c
index 6e6f092..7101e31 100644
--- a/refs-be-files.c
+++ b/refs-be-files.c
@@ -1433,7 +1433,8 @@ static int resolve_gitlink_ref_recursive(struct ref_cache *refs,
 	return resolve_gitlink_ref_recursive(refs, p, sha1, recursion+1);
 }
 
-int resolve_gitlink_ref(const char *path, const char *refname, unsigned char *sha1)
+static int files_resolve_gitlink_ref(const char *path, const char *refname,
+				     unsigned char *sha1)
 {
 	int len = strlen(path), retval;
 	char *submodule;
@@ -1665,8 +1666,10 @@ static const char *resolve_ref_1(const char *refname,
 	}
 }
 
-const char *resolve_ref_unsafe(const char *refname, int resolve_flags,
-			       unsigned char *sha1, int *flags)
+static const char *files_resolve_ref_unsafe(const char *refname,
+					    int resolve_flags,
+					    unsigned char *sha1,
+					    int *flags)
 {
 	static struct strbuf sb_refname = STRBUF_INIT;
 	struct strbuf sb_contents = STRBUF_INIT;
@@ -1768,7 +1771,7 @@ static enum peel_status peel_entry(struct ref_entry *entry, int repeel)
 	return status;
 }
 
-int peel_ref(const char *refname, unsigned char *sha1)
+static int files_peel_ref(const char *refname, unsigned char *sha1)
 {
 	int flag;
 	unsigned char base[20];
@@ -2402,7 +2405,7 @@ static void prune_refs(struct ref_to_prune *r)
 	}
 }
 
-int pack_refs(unsigned int flags)
+static int files_pack_refs(unsigned int flags)
 {
 	struct pack_refs_cb_data cbdata;
 
@@ -2724,8 +2727,10 @@ static int should_autocreate_reflog(const char *refname)
 		!strcmp(refname, "HEAD");
 }
 
-int verify_refname_available(const char *newname, struct string_list *extra,
-			     struct string_list *skip, struct strbuf *err)
+static int files_verify_refname_available(const char *newname,
+					  struct string_list *extra,
+					  struct string_list *skip,
+					  struct strbuf *err)
 {
 	struct ref_dir *packed_refs = get_packed_refs(&ref_cache);
 	struct ref_dir *loose_refs = get_loose_refs(&ref_cache);
@@ -2960,8 +2965,10 @@ static int commit_ref_update(struct ref_lock *lock,
 	return 0;
 }
 
-int create_symref(const char *ref_target, const char *refs_heads_master,
-		  const char *logmsg)
+static int files_create_symref(struct ref_transaction *trans,
+			       const char *ref_target,
+			       const char *refs_heads_master,
+			       const char *logmsg)
 {
 	char *lockpath = NULL;
 	char ref[1000];
@@ -3833,4 +3840,10 @@ struct ref_be refs_be_files = {
 	files_transaction_verify,
 	files_transaction_commit,
 	files_transaction_free,
+	files_resolve_ref_unsafe,
+	files_verify_refname_available,
+	files_pack_refs,
+	files_peel_ref,
+	files_create_symref,
+	files_resolve_gitlink_ref,
 };
diff --git a/refs.c b/refs.c
index 98aa357..a9901f3 100644
--- a/refs.c
+++ b/refs.c
@@ -954,3 +954,51 @@ void ref_transaction_free(struct ref_transaction *transaction)
 {
 	return the_refs_backend->transaction_free(transaction);
 }
+
+const char *resolve_ref_unsafe(const char *ref, int resolve_flags,
+			       unsigned char *sha1, int *flags)
+{
+	return the_refs_backend->resolve_ref_unsafe(ref, resolve_flags, sha1,
+						    flags);
+}
+
+int verify_refname_available(const char *refname, struct string_list *extra,
+			     struct string_list *skip, struct strbuf *err)
+{
+	return the_refs_backend->verify_refname_available(refname, extra, skip,
+							  err);
+}
+
+int pack_refs(unsigned int flags)
+{
+	return the_refs_backend->pack_refs(flags);
+}
+
+int peel_ref(const char *refname, unsigned char *sha1)
+{
+	return the_refs_backend->peel_ref(refname, sha1);
+}
+
+int create_symref(const char *ref_target, const char *refs_heads_master,
+		  const char *logmsg)
+{
+	struct strbuf err = STRBUF_INIT;
+	struct ref_transaction *transaction = ref_transaction_begin(&err);
+	if (!transaction)
+		return -1;
+
+	if (the_refs_backend->create_symref(transaction, ref_target,
+					    refs_heads_master, logmsg))
+		return -1;
+
+	if (ref_transaction_commit(transaction, &err))
+		return -1;
+
+	return 0;
+}
+
+int resolve_gitlink_ref(const char *path, const char *refname,
+			unsigned char *sha1)
+{
+	return the_refs_backend->resolve_gitlink_ref(path, refname, sha1);
+}
diff --git a/refs.h b/refs.h
index a1db3ef..6e88909 100644
--- a/refs.h
+++ b/refs.h
@@ -549,6 +549,21 @@ typedef int (*ref_transaction_verify_fn)(struct ref_transaction *transaction,
 typedef int (*ref_transaction_commit_fn)(struct ref_transaction *transaction,
 				     struct strbuf *err);
 typedef void (*ref_transaction_free_fn)(struct ref_transaction *transaction);
+typedef const char *(*resolve_ref_unsafe_fn)(const char *ref,
+					     int resolve_flags,
+					     unsigned char *sha1, int *flags);
+typedef int (*verify_refname_available_fn)(const char *refname,
+					   struct string_list *extra,
+					   struct string_list *skip,
+					   struct strbuf *err);
+typedef int (*pack_refs_fn)(unsigned int flags);
+typedef int (*peel_ref_fn)(const char *refname, unsigned char *sha1);
+typedef int (*create_symref_fn)(struct ref_transaction *transaction,
+				const char *ref_target,
+				const char *refs_heads_master,
+				const char *logmsg);
+typedef int (*resolve_gitlink_ref_fn)(const char *path, const char *refname,
+				      unsigned char *sha1);
 
 struct ref_be {
 	struct ref_be *next;
@@ -560,6 +575,12 @@ struct ref_be {
 	ref_transaction_verify_fn transaction_verify;
 	ref_transaction_commit_fn transaction_commit;
 	ref_transaction_free_fn transaction_free;
+	resolve_ref_unsafe_fn resolve_ref_unsafe;
+	verify_refname_available_fn verify_refname_available;
+	pack_refs_fn pack_refs;
+	peel_ref_fn peel_ref;
+	create_symref_fn create_symref;
+	resolve_gitlink_ref_fn resolve_gitlink_ref;
 };
 
 
-- 
2.4.2.644.g97b850b-twtrsrc
David Turner· Sep 28, 2015, 22:01 UTC · re: David Turner · lore

[PATCH v2 20/43] refs-be-files.c: add methods for the ref iterators

From: Ronnie Sahlberg <sahlberg@google.com>
Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>
Signed-off-by: David Turner <dturner@twopensource.com>
---
 refs-be-files.c | 41 +++++++++++++++++++++++++++++------------
 refs.c          | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 refs.h          | 26 ++++++++++++++++++++++++++
 3 files changed, 109 insertions(+), 12 deletions(-)
Show changes to 3 files +109 −12

refs-be-files.c, refs.c, refs.h

diff --git a/refs-be-files.c b/refs-be-files.c
index 7101e31..9b45468 100644
--- a/refs-be-files.c
+++ b/refs-be-files.c
@@ -1909,32 +1909,36 @@ static int do_head_ref(const char *submodule, each_ref_fn fn, void *cb_data)
 	return 0;
 }
 
-int head_ref(each_ref_fn fn, void *cb_data)
+static int files_head_ref(each_ref_fn fn, void *cb_data)
 {
 	return do_head_ref(NULL, fn, cb_data);
 }
 
-int head_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)
+static int files_head_ref_submodule(const char *submodule, each_ref_fn fn,
+				    void *cb_data)
 {
 	return do_head_ref(submodule, fn, cb_data);
 }
 
-int for_each_ref(each_ref_fn fn, void *cb_data)
+static int files_for_each_ref(each_ref_fn fn, void *cb_data)
 {
 	return do_for_each_ref(&ref_cache, "", fn, 0, 0, cb_data);
 }
 
-int for_each_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)
+static int files_for_each_ref_submodule(const char *submodule, each_ref_fn fn,
+					void *cb_data)
 {
 	return do_for_each_ref(get_ref_cache(submodule), "", fn, 0, 0, cb_data);
 }
 
-int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data)
+static int files_for_each_ref_in(const char *prefix, each_ref_fn fn,
+				 void *cb_data)
 {
 	return do_for_each_ref(&ref_cache, prefix, fn, strlen(prefix), 0, cb_data);
 }
 
-int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data, unsigned int broken)
+static int files_for_each_fullref_in(const char *prefix, each_ref_fn fn,
+				     void *cb_data, unsigned int broken)
 {
 	unsigned int flag = 0;
 
@@ -1943,18 +1947,21 @@ int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data, unsig
 	return do_for_each_ref(&ref_cache, prefix, fn, 0, flag, cb_data);
 }
 
-int for_each_ref_in_submodule(const char *submodule, const char *prefix,
-		each_ref_fn fn, void *cb_data)
+static int files_for_each_ref_in_submodule(const char *submodule,
+					   const char *prefix,
+					   each_ref_fn fn, void *cb_data)
 {
-	return do_for_each_ref(get_ref_cache(submodule), prefix, fn, strlen(prefix), 0, cb_data);
+	return do_for_each_ref(get_ref_cache(submodule), prefix, fn,
+			       strlen(prefix), 0, cb_data);
 }
-int for_each_replace_ref(each_ref_fn fn, void *cb_data)
+
+static int files_for_each_replace_ref(each_ref_fn fn, void *cb_data)
 {
 	return do_for_each_ref(&ref_cache, git_replace_ref_base, fn,
 			       strlen(git_replace_ref_base), 0, cb_data);
 }
 
-int for_each_namespaced_ref(each_ref_fn fn, void *cb_data)
+static int files_for_each_namespaced_ref(each_ref_fn fn, void *cb_data)
 {
 	struct strbuf buf = STRBUF_INIT;
 	int ret;
@@ -1964,7 +1971,7 @@ int for_each_namespaced_ref(each_ref_fn fn, void *cb_data)
 	return ret;
 }
 
-int for_each_rawref(each_ref_fn fn, void *cb_data)
+static int files_for_each_rawref(each_ref_fn fn, void *cb_data)
 {
 	return do_for_each_ref(&ref_cache, "", fn, 0,
 			       DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
@@ -3846,4 +3853,14 @@ struct ref_be refs_be_files = {
 	files_peel_ref,
 	files_create_symref,
 	files_resolve_gitlink_ref,
+	files_head_ref,
+	files_head_ref_submodule,
+	files_for_each_ref,
+	files_for_each_ref_submodule,
+	files_for_each_ref_in,
+	files_for_each_fullref_in,
+	files_for_each_ref_in_submodule,
+	files_for_each_rawref,
+	files_for_each_namespaced_ref,
+	files_for_each_replace_ref,
 };
diff --git a/refs.c b/refs.c
index a9901f3..0375bb5 100644
--- a/refs.c
+++ b/refs.c
@@ -1002,3 +1002,57 @@ int resolve_gitlink_ref(const char *path, const char *refname,
 {
 	return the_refs_backend->resolve_gitlink_ref(path, refname, sha1);
 }
+
+int head_ref(each_ref_fn fn, void *cb_data)
+{
+	return the_refs_backend->head_ref(fn, cb_data);
+}
+
+int head_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)
+{
+	return the_refs_backend->head_ref_submodule(submodule, fn, cb_data);
+}
+
+int for_each_ref(each_ref_fn fn, void *cb_data)
+{
+	return the_refs_backend->for_each_ref(fn, cb_data);
+}
+
+int for_each_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)
+{
+	return the_refs_backend->for_each_ref_submodule(submodule, fn, cb_data);
+}
+
+int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data)
+{
+	return the_refs_backend->for_each_ref_in(prefix, fn, cb_data);
+}
+
+int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data,
+			unsigned int broken)
+{
+	return the_refs_backend->for_each_fullref_in(prefix, fn, cb_data,
+						     broken);
+}
+
+int for_each_ref_in_submodule(const char *submodule, const char *prefix,
+			      each_ref_fn fn, void *cb_data)
+{
+	return the_refs_backend->for_each_ref_in_submodule(submodule, prefix,
+							   fn, cb_data);
+}
+
+int for_each_rawref(each_ref_fn fn, void *cb_data)
+{
+	return the_refs_backend->for_each_rawref(fn, cb_data);
+}
+
+int for_each_namespaced_ref(each_ref_fn fn, void *cb_data)
+{
+	return the_refs_backend->for_each_namespaced_ref(fn, cb_data);
+}
+
+int for_each_replace_ref(each_ref_fn fn, void *cb_data)
+{
+	return the_refs_backend->for_each_replace_ref(fn, cb_data);
+}
diff --git a/refs.h b/refs.h
index 6e88909..b26cffc 100644
--- a/refs.h
+++ b/refs.h
@@ -564,6 +564,22 @@ typedef int (*create_symref_fn)(struct ref_transaction *transaction,
 				const char *logmsg);
 typedef int (*resolve_gitlink_ref_fn)(const char *path, const char *refname,
 				      unsigned char *sha1);
+typedef int (*head_ref_fn)(each_ref_fn fn, void *cb_data);
+typedef int (*head_ref_submodule_fn)(const char *submodule, each_ref_fn fn,
+				     void *cb_data);
+typedef int (*for_each_ref_fn)(each_ref_fn fn, void *cb_data);
+typedef int (*for_each_ref_submodule_fn)(const char *submodule, each_ref_fn fn,
+					 void *cb_data);
+typedef int (*for_each_ref_in_fn)(const char *prefix, each_ref_fn fn,
+				  void *cb_data);
+typedef int (*for_each_fullref_in_fn)(const char *prefix, each_ref_fn fn,
+				      void *cb_data, unsigned int broken);
+typedef int (*for_each_ref_in_submodule_fn)(const char *submodule,
+					    const char *prefix,
+					    each_ref_fn fn, void *cb_data);
+typedef int (*for_each_rawref_fn)(each_ref_fn fn, void *cb_data);
+typedef int (*for_each_namespaced_ref_fn)(each_ref_fn fn, void *cb_data);
+typedef int (*for_each_replace_ref_fn)(each_ref_fn fn, void *cb_data);
 
 struct ref_be {
 	struct ref_be *next;
@@ -581,6 +597,16 @@ struct ref_be {
 	peel_ref_fn peel_ref;
 	create_symref_fn create_symref;
 	resolve_gitlink_ref_fn resolve_gitlink_ref;
+	head_ref_fn head_ref;
+	head_ref_submodule_fn head_ref_submodule;
+	for_each_ref_fn for_each_ref;
+	for_each_ref_submodule_fn for_each_ref_submodule;
+	for_each_ref_in_fn for_each_ref_in;
+	for_each_fullref_in_fn for_each_fullref_in;
+	for_each_ref_in_submodule_fn for_each_ref_in_submodule;
+	for_each_rawref_fn for_each_rawref;
+	for_each_namespaced_ref_fn for_each_namespaced_ref;
+	for_each_replace_ref_fn for_each_replace_ref;
 };
 
 
-- 
2.4.2.644.g97b850b-twtrsrc
David Turner· Sep 28, 2015, 22:01 UTC · re: David Turner · lore

[PATCH v2 21/43] refs-be-files.c: add method for for_each_reftype_...

Add method for for_each_reftype_fullpath.
Signed-off-by: David Turner <dturner@twopensource.com>
---
 refs-be-files.c | 11 +++++++++++
 refs.c          |  7 +++++++
 refs.h          |  3 +++
 3 files changed, 21 insertions(+)
Show changes to 3 files +21 −0

refs-be-files.c, refs.c, refs.h

diff --git a/refs-be-files.c b/refs-be-files.c
index 9b45468..eb18a20 100644
--- a/refs-be-files.c
+++ b/refs-be-files.c
@@ -1961,6 +1961,16 @@ static int files_for_each_replace_ref(each_ref_fn fn, void *cb_data)
 			       strlen(git_replace_ref_base), 0, cb_data);
 }
 
+static int files_for_each_reftype_fullpath(each_ref_fn fn, char *type,
+					   unsigned int broken, void *cb_data)
+{
+	unsigned int flag = 0;
+
+	if (broken)
+		flag = DO_FOR_EACH_INCLUDE_BROKEN;
+	return do_for_each_ref(&ref_cache, type, fn, 0, flag, cb_data);
+}
+
 static int files_for_each_namespaced_ref(each_ref_fn fn, void *cb_data)
 {
 	struct strbuf buf = STRBUF_INIT;
@@ -3863,4 +3873,5 @@ struct ref_be refs_be_files = {
 	files_for_each_rawref,
 	files_for_each_namespaced_ref,
 	files_for_each_replace_ref,
+	files_for_each_reftype_fullpath,
 };
diff --git a/refs.c b/refs.c
index 0375bb5..c6112ac 100644
--- a/refs.c
+++ b/refs.c
@@ -1056,3 +1056,10 @@ int for_each_replace_ref(each_ref_fn fn, void *cb_data)
 {
 	return the_refs_backend->for_each_replace_ref(fn, cb_data);
 }
+
+int for_each_reftype_fullpath(each_ref_fn fn, char *type, unsigned int broken,
+			      void *cb_data)
+{
+	return the_refs_backend->for_each_reftype_fullpath(fn, type, broken,
+							   cb_data);
+}
diff --git a/refs.h b/refs.h
index b26cffc..5875fe5 100644
--- a/refs.h
+++ b/refs.h
@@ -580,6 +580,8 @@ typedef int (*for_each_ref_in_submodule_fn)(const char *submodule,
 typedef int (*for_each_rawref_fn)(each_ref_fn fn, void *cb_data);
 typedef int (*for_each_namespaced_ref_fn)(each_ref_fn fn, void *cb_data);
 typedef int (*for_each_replace_ref_fn)(each_ref_fn fn, void *cb_data);
+typedef int (*for_each_reftype_fullpath_fn)(each_ref_fn fn, char *type,
+					    unsigned int broken, void *cb_data);
 
 struct ref_be {
 	struct ref_be *next;
@@ -607,6 +609,7 @@ struct ref_be {
 	for_each_rawref_fn for_each_rawref;
 	for_each_namespaced_ref_fn for_each_namespaced_ref;
 	for_each_replace_ref_fn for_each_replace_ref;
+	for_each_reftype_fullpath_fn for_each_reftype_fullpath;
 };
 
 
-- 
2.4.2.644.g97b850b-twtrsrc
David Turner· Sep 28, 2015, 22:01 UTC · re: David Turner · lore

[PATCH v2 22/43] refs-be-files.c: add do_for_each_per_worktree_ref

Alternate refs backends might still use files to store per-worktree refs. So the files backend's ref-loading infrastructure should be available to those backends, just for use on per-worktree refs. Add do_for_each_per_worktree_ref, which iterates over per-worktree refs.

Signed-off-by: David Turner <dturner@twopensource.com>
---
 refs-be-files.c | 15 ++++++++++++---
 refs.h          | 11 +++++++++++
 2 files changed, 23 insertions(+), 3 deletions(-)
Show changes to 2 files +23 −3

refs-be-files.c, refs.h

diff --git a/refs-be-files.c b/refs-be-files.c
index eb18a20..dc89289 100644
--- a/refs-be-files.c
+++ b/refs-be-files.c
@@ -587,9 +587,6 @@ static void sort_ref_dir(struct ref_dir *dir)
 	dir->sorted = dir->nr = i;
 }
 
-/* Include broken references in a do_for_each_ref*() iteration: */
-#define DO_FOR_EACH_INCLUDE_BROKEN 0x01
-
 /*
  * Return true iff the reference described by entry can be resolved to
  * an object in the database.  Emit a warning if the referred-to
@@ -637,6 +634,10 @@ static int do_one_ref(struct ref_entry *entry, void *cb_data)
 	struct ref_entry *old_current_ref;
 	int retval;
 
+	if (data->flags & DO_FOR_EACH_PER_WORKTREE_ONLY &&
+	    ref_type(entry->name) != REF_TYPE_PER_WORKTREE)
+		return 0;
+
 	if (!starts_with(entry->name, data->base))
 		return 0;
 
@@ -1891,6 +1892,14 @@ static int do_for_each_ref(struct ref_cache *refs, const char *base,
 	return do_for_each_entry(refs, base, do_one_ref, &data);
 }
 
+int do_for_each_per_worktree_ref(const char *submodule, const char *base,
+				 each_ref_fn fn, int trim, int flags,
+				 void *cb_data)
+{
+	return do_for_each_ref(get_ref_cache(submodule), base, fn, trim,
+			       flags | DO_FOR_EACH_PER_WORKTREE_ONLY, cb_data);
+}
+
 static int do_head_ref(const char *submodule, each_ref_fn fn, void *cb_data)
 {
 	struct object_id oid;
diff --git a/refs.h b/refs.h
index 5875fe5..09d140d 100644
--- a/refs.h
+++ b/refs.h
@@ -152,6 +152,12 @@ struct ref_transaction;
  */
 #define REF_BAD_NAME 0x08
 
+/* Include broken references in a do_for_each_ref*() iteration */
+#define DO_FOR_EACH_INCLUDE_BROKEN 0x01
+
+/* Only include per-worktree refs in a do_for_each_ref*() iteration */
+#define DO_FOR_EACH_PER_WORKTREE_ONLY 0x02
+
 /*
  * The signature for the callback function for the for_each_*()
  * functions below.  The memory pointed to by the refname and sha1
@@ -195,6 +201,11 @@ extern int for_each_namespaced_ref(each_ref_fn fn, void *cb_data);
 /* can be used to learn about broken ref and symref */
 extern int for_each_rawref(each_ref_fn fn, void *cb_data);
 
+
+int do_for_each_per_worktree_ref(const char *submodule, const char *base,
+				 each_ref_fn fn, int trim, int flags,
+				 void *cb_data);
+
 static inline const char *has_glob_specials(const char *pattern)
 {
 	return strpbrk(pattern, "?*[");
-- 
2.4.2.644.g97b850b-twtrsrc
Michael Haggerty· Oct 5, 2015, 08:19 UTC · re: David Turner · lore

Re: [PATCH v2 22/43] refs-be-files.c: add do_for_each_per_worktree_ref

On 09/29/2015 12:01 AM, David Turner wrote:
Show 36 quoted lines
> Alternate refs backends might still use files to store per-worktree
> refs.  So the files backend's ref-loading infrastructure should be
> available to those backends, just for use on per-worktree refs.  Add
> do_for_each_per_worktree_ref, which iterates over per-worktree refs.
> 
> Signed-off-by: David Turner <dturner@twopensource.com>
> ---
>  refs-be-files.c | 15 ++++++++++++---
>  refs.h          | 11 +++++++++++
>  2 files changed, 23 insertions(+), 3 deletions(-)
> 
> diff --git a/refs-be-files.c b/refs-be-files.c
> index eb18a20..dc89289 100644
> --- a/refs-be-files.c
> +++ b/refs-be-files.c
> @@ -587,9 +587,6 @@ static void sort_ref_dir(struct ref_dir *dir)
>  	dir->sorted = dir->nr = i;
>  }
>  
> -/* Include broken references in a do_for_each_ref*() iteration: */
> -#define DO_FOR_EACH_INCLUDE_BROKEN 0x01
> -
>  /*
>   * Return true iff the reference described by entry can be resolved to
>   * an object in the database.  Emit a warning if the referred-to
> [...]
> diff --git a/refs.h b/refs.h
> index 5875fe5..09d140d 100644
> --- a/refs.h
> +++ b/refs.h
> @@ -152,6 +152,12 @@ struct ref_transaction;
>   */
>  #define REF_BAD_NAME 0x08
>  
> +/* Include broken references in a do_for_each_ref*() iteration */
> +#define DO_FOR_EACH_INCLUDE_BROKEN 0x01
Why do you move this definition from refs-be-files.c?
> +
> +/* Only include per-worktree refs in a do_for_each_ref*() iteration */
> +#define DO_FOR_EACH_PER_WORKTREE_ONLY 0x02
And why do you define this one here instead of in refs-be-files.c?
> [...]
Michael
-- 
Michael Haggerty
mhagger@alum.mit.edu
David Turner· Oct 5, 2015, 20:14 UTC · re: Michael Haggerty · lore

Re: [PATCH v2 22/43] refs-be-files.c: add do_for_each_per_worktree_ref

On Mon, 2015-10-05 at 10:19 +0200, Michael Haggerty wrote:
Show 39 quoted lines
> On 09/29/2015 12:01 AM, David Turner wrote:
> > Alternate refs backends might still use files to store per-worktree
> > refs.  So the files backend's ref-loading infrastructure should be
> > available to those backends, just for use on per-worktree refs.  Add
> > do_for_each_per_worktree_ref, which iterates over per-worktree refs.
> > 
> > Signed-off-by: David Turner <dturner@twopensource.com>
> > ---
> >  refs-be-files.c | 15 ++++++++++++---
> >  refs.h          | 11 +++++++++++
> >  2 files changed, 23 insertions(+), 3 deletions(-)
> > 
> > diff --git a/refs-be-files.c b/refs-be-files.c
> > index eb18a20..dc89289 100644
> > --- a/refs-be-files.c
> > +++ b/refs-be-files.c
> > @@ -587,9 +587,6 @@ static void sort_ref_dir(struct ref_dir *dir)
> >  	dir->sorted = dir->nr = i;
> >  }
> >  
> > -/* Include broken references in a do_for_each_ref*() iteration: */
> > -#define DO_FOR_EACH_INCLUDE_BROKEN 0x01
> > -
> >  /*
> >   * Return true iff the reference described by entry can be resolved to
> >   * an object in the database.  Emit a warning if the referred-to
> > [...]
> > diff --git a/refs.h b/refs.h
> > index 5875fe5..09d140d 100644
> > --- a/refs.h
> > +++ b/refs.h
> > @@ -152,6 +152,12 @@ struct ref_transaction;
> >   */
> >  #define REF_BAD_NAME 0x08
> >  
> > +/* Include broken references in a do_for_each_ref*() iteration */
> > +#define DO_FOR_EACH_INCLUDE_BROKEN 0x01
> 
> Why do you move this definition from refs-be-files.c?
Because the lmdb backend will need it.
Show 5 quoted lines
> > +
> > +/* Only include per-worktree refs in a do_for_each_ref*() iteration */
> > +#define DO_FOR_EACH_PER_WORKTREE_ONLY 0x02
> 
> And why do you define this one here instead of in refs-be-files.c?

Because other backends might need it (even though at present lmdb doesn't).

David Turner· Sep 28, 2015, 22:01 UTC · re: David Turner · lore

[PATCH v2 23/43] refs.c: move refname_is_safe to the common code

This function does not contain any backend specific code so we move it to the common code.

Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>
Signed-off-by: David Turner <dturner@twopensource.com>
---
 refs-be-files.c | 33 ---------------------------------
 refs.c          | 23 +++++++++++++++++++++++
 refs.h          |  2 ++
 3 files changed, 25 insertions(+), 33 deletions(-)
Show changes to 3 files +25 −33

refs-be-files.c, refs.c, refs.h

diff --git a/refs-be-files.c b/refs-be-files.c
index dc89289..bb5a3ad 100644
--- a/refs-be-files.c
+++ b/refs-be-files.c
@@ -231,39 +231,6 @@ static struct ref_dir *get_ref_dir(struct ref_entry *entry)
 	return dir;
 }
 
-/*
- * Check if a refname is safe.
- * For refs that start with "refs/" we consider it safe as long they do
- * not try to resolve to outside of refs/.
- *
- * For all other refs we only consider them safe iff they only contain
- * upper case characters and '_' (like "HEAD" AND "MERGE_HEAD", and not like
- * "config").
- */
-static int refname_is_safe(const char *refname)
-{
-	if (starts_with(refname, "refs/")) {
-		char *buf;
-		int result;
-
-		buf = xmalloc(strlen(refname) + 1);
-		/*
-		 * Does the refname try to escape refs/?
-		 * For example: refs/foo/../bar is safe but refs/foo/../../bar
-		 * is not.
-		 */
-		result = !normalize_path_copy(buf, refname + strlen("refs/"));
-		free(buf);
-		return result;
-	}
-	while (*refname) {
-		if (!isupper(*refname) && *refname != '_')
-			return 0;
-		refname++;
-	}
-	return 1;
-}
-
 static struct ref_entry *create_ref_entry(const char *refname,
 					  const unsigned char *sha1, int flag,
 					  int check_name)
diff --git a/refs.c b/refs.c
index c6112ac..aa49db5 100644
--- a/refs.c
+++ b/refs.c
@@ -901,6 +901,29 @@ int head_ref_namespaced(each_ref_fn fn, void *cb_data)
 	return ret;
 }
 
+int refname_is_safe(const char *refname)
+{
+	if (starts_with(refname, "refs/")) {
+		char *buf;
+		int result;
+
+		buf = xmalloc(strlen(refname) + 1);
+		/*
+		 * Does the refname try to escape refs/?
+		 * For example: refs/foo/../bar is safe but refs/foo/../../bar
+		 * is not.
+		 */
+		result = !normalize_path_copy(buf, refname + strlen("refs/"));
+		free(buf);
+		return result;
+	}
+	while (*refname) {
+		if (!isupper(*refname) && *refname != '_')
+			return 0;
+		refname++;
+	}
+	return 1;
+}
 
 /* backend functions */
 struct ref_transaction *ref_transaction_begin(struct strbuf *err)
diff --git a/refs.h b/refs.h
index 09d140d..2bafa55 100644
--- a/refs.h
+++ b/refs.h
@@ -326,6 +326,8 @@ extern int for_each_reflog(each_ref_fn, void *);
  */
 extern int check_refname_format(const char *refname, int flags);
 
+extern int refname_is_safe(const char *refname);
+
 extern const char *prettify_refname(const char *refname);
 
 extern char *shorten_unambiguous_ref(const char *refname, int strict);
-- 
2.4.2.644.g97b850b-twtrsrc
David Turner· Sep 28, 2015, 22:01 UTC · re: David Turner · lore

[PATCH v2 24/43] refs.h: document make refname_is_safe and add it to header

This function might be used by other refs backends
Signed-off-by: David Turner <dturner@twopensource.com>
---
 refs.h | 11 +++++++++++
 1 file changed, 11 insertions(+)
Show changes to refs.h +11 −0
diff --git a/refs.h b/refs.h
index 2bafa55..db60bfe 100644
--- a/refs.h
+++ b/refs.h
@@ -259,6 +259,17 @@ int verify_refname_available(const char *newname, struct string_list *extra,
 			     struct string_list *skip, struct strbuf *err);
 
 /*
+ * Check if a refname is safe.
+ * For refs that start with "refs/" we consider it safe as long they do
+ * not try to resolve to outside of refs/.
+ *
+ * For all other refs we only consider them safe iff they only contain
+ * upper case characters and '_' (like "HEAD" AND "MERGE_HEAD", and not like
+ * "config").
+ */
+int refname_is_safe(const char *refname);
+
+/*
  * Flags controlling ref_transaction_update(), ref_transaction_create(), etc.
  * REF_NODEREF: act on the ref directly, instead of dereferencing
  *              symbolic references.
-- 
2.4.2.644.g97b850b-twtrsrc
David Turner· Sep 28, 2015, 22:02 UTC · re: David Turner · lore

[PATCH v2 25/43] refs.c: move copy_msg to the common code

Rename copy_msg to copy_reflog_msg and make it public.
Signed-off-by: David Turner <dturner@twopensource.com>
---
 refs-be-files.c | 28 +---------------------------
 refs.c          | 26 ++++++++++++++++++++++++++
 refs.h          |  2 ++
 3 files changed, 29 insertions(+), 27 deletions(-)
Show changes to 3 files +29 −27

refs-be-files.c, refs.c, refs.h

diff --git a/refs-be-files.c b/refs-be-files.c
index bb5a3ad..8898e86 100644
--- a/refs-be-files.c
+++ b/refs-be-files.c
@@ -2684,32 +2684,6 @@ static int commit_ref(struct ref_lock *lock)
 	return 0;
 }
 
-/*
- * copy the reflog message msg to buf, which has been allocated sufficiently
- * large, while cleaning up the whitespaces.  Especially, convert LF to space,
- * because reflog file is one line per entry.
- */
-static int copy_msg(char *buf, const char *msg)
-{
-	char *cp = buf;
-	char c;
-	int wasspace = 1;
-
-	*cp++ = '\t';
-	while ((c = *msg++)) {
-		if (wasspace && isspace(c))
-			continue;
-		wasspace = isspace(c);
-		if (wasspace)
-			c = ' ';
-		*cp++ = c;
-	}
-	while (buf < cp && isspace(cp[-1]))
-		cp--;
-	*cp++ = '\n';
-	return cp - buf;
-}
-
 static int should_autocreate_reflog(const char *refname)
 {
 	if (!log_all_ref_updates)
@@ -2806,7 +2780,7 @@ static int log_ref_write_fd(int fd, const unsigned char *old_sha1,
 			sha1_to_hex(new_sha1),
 			committer);
 	if (msglen)
-		len += copy_msg(logrec + len - 1, msg) - 1;
+		len += copy_reflog_msg(logrec + len - 1, msg) - 1;
 
 	written = len <= maxlen ? write_in_full(fd, logrec, len) : -1;
 	free(logrec);
diff --git a/refs.c b/refs.c
index aa49db5..db650b6 100644
--- a/refs.c
+++ b/refs.c
@@ -886,6 +886,32 @@ int for_each_remote_ref_submodule(const char *submodule, each_ref_fn fn, void *c
 	return for_each_ref_in_submodule(submodule, "refs/remotes/", fn, cb_data);
 }
 
+/*
+ * copy the reflog message msg to buf, which has been allocated sufficiently
+ * large, while cleaning up the whitespaces.  Especially, convert LF to space,
+ * because reflog file is one line per entry.
+ */
+int copy_reflog_msg(char *buf, const char *msg)
+{
+	char *cp = buf;
+	char c;
+	int wasspace = 1;
+
+	*cp++ = '\t';
+	while ((c = *msg++)) {
+		if (wasspace && isspace(c))
+			continue;
+		wasspace = isspace(c);
+		if (wasspace)
+			c = ' ';
+		*cp++ = c;
+	}
+	while (buf < cp && isspace(cp[-1]))
+		cp--;
+	*cp++ = '\n';
+	return cp - buf;
+}
+
 int head_ref_namespaced(each_ref_fn fn, void *cb_data)
 {
 	struct strbuf buf = STRBUF_INIT;
diff --git a/refs.h b/refs.h
index db60bfe..c224f0e 100644
--- a/refs.h
+++ b/refs.h
@@ -508,6 +508,8 @@ enum ref_type {
 
 enum ref_type ref_type(const char *refname);
 
+int copy_reflog_msg(char *buf, const char *msg);
+
 enum expire_reflog_flags {
 	EXPIRE_REFLOGS_DRY_RUN = 1 << 0,
 	EXPIRE_REFLOGS_UPDATE_REF = 1 << 1,
-- 
2.4.2.644.g97b850b-twtrsrc
David Turner· Sep 28, 2015, 22:02 UTC · re: David Turner · lore

[PATCH v2 26/43] refs.c: move peel_object to the common code

This function does not contain any backend specific code so we move it to the common code.

Signed-off-by: David Turner <dturner@twopensource.com>
---
 refs-be-files.c | 53 -----------------------------------------------------
 refs.c          | 31 +++++++++++++++++++++++++++++++
 refs.h          | 27 +++++++++++++++++++++++++++
 3 files changed, 58 insertions(+), 53 deletions(-)
Show changes to 3 files +58 −53

refs-be-files.c, refs.c, refs.h

diff --git a/refs-be-files.c b/refs-be-files.c
index 8898e86..5971526 100644
--- a/refs-be-files.c
+++ b/refs-be-files.c
@@ -1651,59 +1651,6 @@ static const char *files_resolve_ref_unsafe(const char *refname,
 	return ret;
 }
 
-enum peel_status {
-	/* object was peeled successfully: */
-	PEEL_PEELED = 0,
-
-	/*
-	 * object cannot be peeled because the named object (or an
-	 * object referred to by a tag in the peel chain), does not
-	 * exist.
-	 */
-	PEEL_INVALID = -1,
-
-	/* object cannot be peeled because it is not a tag: */
-	PEEL_NON_TAG = -2,
-
-	/* ref_entry contains no peeled value because it is a symref: */
-	PEEL_IS_SYMREF = -3,
-
-	/*
-	 * ref_entry cannot be peeled because it is broken (i.e., the
-	 * symbolic reference cannot even be resolved to an object
-	 * name):
-	 */
-	PEEL_BROKEN = -4
-};
-
-/*
- * Peel the named object; i.e., if the object is a tag, resolve the
- * tag recursively until a non-tag is found.  If successful, store the
- * result to sha1 and return PEEL_PEELED.  If the object is not a tag
- * or is not valid, return PEEL_NON_TAG or PEEL_INVALID, respectively,
- * and leave sha1 unchanged.
- */
-static enum peel_status peel_object(const unsigned char *name, unsigned char *sha1)
-{
-	struct object *o = lookup_unknown_object(name);
-
-	if (o->type == OBJ_NONE) {
-		int type = sha1_object_info(name, NULL);
-		if (type < 0 || !object_as_type(o, type, 0))
-			return PEEL_INVALID;
-	}
-
-	if (o->type != OBJ_TAG)
-		return PEEL_NON_TAG;
-
-	o = deref_tag_noverify(o);
-	if (!o)
-		return PEEL_INVALID;
-
-	hashcpy(sha1, o->sha1);
-	return PEEL_PEELED;
-}
-
 /*
  * Peel the entry (if possible) and return its new peel_status.  If
  * repeel is true, re-peel the entry even if there is an old peeled
diff --git a/refs.c b/refs.c
index db650b6..d13b854 100644
--- a/refs.c
+++ b/refs.c
@@ -4,6 +4,9 @@
 #include "cache.h"
 #include "refs.h"
 #include "lockfile.h"
+#include "object.h"
+#include "tag.h"
+
 /*
  * We always have a files backend and it is the default.
  */
@@ -951,6 +954,34 @@ int refname_is_safe(const char *refname)
 	return 1;
 }
 
+/*
+ * Peel the named object; i.e., if the object is a tag, resolve the
+ * tag recursively until a non-tag is found.  If successful, store the
+ * result to sha1 and return PEEL_PEELED.  If the object is not a tag
+ * or is not valid, return PEEL_NON_TAG or PEEL_INVALID, respectively,
+ * and leave sha1 unchanged.
+ */
+enum peel_status peel_object(const unsigned char *name, unsigned char *sha1)
+{
+	struct object *o = lookup_unknown_object(name);
+
+	if (o->type == OBJ_NONE) {
+		int type = sha1_object_info(name, NULL);
+		if (type < 0 || !object_as_type(o, type, 0))
+			return PEEL_INVALID;
+	}
+
+	if (o->type != OBJ_TAG)
+		return PEEL_NON_TAG;
+
+	o = deref_tag_noverify(o);
+	if (!o)
+		return PEEL_INVALID;
+
+	hashcpy(sha1, o->sha1);
+	return PEEL_PEELED;
+}
+
 /* backend functions */
 struct ref_transaction *ref_transaction_begin(struct strbuf *err)
 {
diff --git a/refs.h b/refs.h
index c224f0e..60a98cf 100644
--- a/refs.h
+++ b/refs.h
@@ -76,6 +76,33 @@ extern int is_branch(const char *refname);
  */
 extern int peel_ref(const char *refname, unsigned char *sha1);
 
+enum peel_status {
+	/* object was peeled successfully: */
+	PEEL_PEELED = 0,
+
+	/*
+	 * object cannot be peeled because the named object (or an
+	 * object referred to by a tag in the peel chain), does not
+	 * exist.
+	 */
+	PEEL_INVALID = -1,
+
+	/* object cannot be peeled because it is not a tag: */
+	PEEL_NON_TAG = -2,
+
+	/* ref_entry contains no peeled value because it is a symref: */
+	PEEL_IS_SYMREF = -3,
+
+	/*
+	 * ref_entry cannot be peeled because it is broken (i.e., the
+	 * symbolic reference cannot even be resolved to an object
+	 * name):
+	 */
+	PEEL_BROKEN = -4
+};
+
+enum peel_status peel_object(const unsigned char *name, unsigned char *sha1);
+
 /**
  * Resolve refname in the nested "gitlink" repository that is located
  * at path.  If the resolution is successful, return 0 and set sha1 to
-- 
2.4.2.644.g97b850b-twtrsrc
David Turner· Sep 28, 2015, 22:02 UTC · re: David Turner · lore

[PATCH v2 27/43] refs.c: move should_autocreate_reflog to common code

Signed-off-by: David Turner <dturner@twopensource.com>
---
 refs-be-files.c | 10 ----------
 refs.c          | 10 ++++++++++
 refs.h          |  2 ++
 3 files changed, 12 insertions(+), 10 deletions(-)
Show changes to 3 files +12 −10

refs-be-files.c, refs.c, refs.h

diff --git a/refs-be-files.c b/refs-be-files.c
index 5971526..37e244a 100644
--- a/refs-be-files.c
+++ b/refs-be-files.c
@@ -2631,16 +2631,6 @@ static int commit_ref(struct ref_lock *lock)
 	return 0;
 }
 
-static int should_autocreate_reflog(const char *refname)
-{
-	if (!log_all_ref_updates)
-		return 0;
-	return starts_with(refname, "refs/heads/") ||
-		starts_with(refname, "refs/remotes/") ||
-		starts_with(refname, "refs/notes/") ||
-		!strcmp(refname, "HEAD");
-}
-
 static int files_verify_refname_available(const char *newname,
 					  struct string_list *extra,
 					  struct string_list *skip,
diff --git a/refs.c b/refs.c
index d13b854..769574d 100644
--- a/refs.c
+++ b/refs.c
@@ -685,6 +685,16 @@ char *resolve_refdup(const char *refname, int resolve_flags,
 						  sha1, flags));
 }
 
+int should_autocreate_reflog(const char *refname)
+{
+	if (!log_all_ref_updates)
+		return 0;
+	return starts_with(refname, "refs/heads/") ||
+		starts_with(refname, "refs/remotes/") ||
+		starts_with(refname, "refs/notes/") ||
+		!strcmp(refname, "HEAD");
+}
+
 /*
  * How to handle various characters in refnames:
  * 0: An acceptable character for refs
diff --git a/refs.h b/refs.h
index 60a98cf..0b407b2 100644
--- a/refs.h
+++ b/refs.h
@@ -58,6 +58,8 @@ extern const char *resolve_ref_unsafe(const char *refname, int resolve_flags,
 extern char *resolve_refdup(const char *refname, int resolve_flags,
 			    unsigned char *sha1, int *flags);
 
+extern int should_autocreate_reflog(const char *refname);
+
 extern int read_ref_full(const char *refname, int resolve_flags,
 			 unsigned char *sha1, int *flags);
 extern int read_ref(const char *refname, unsigned char *sha1);
-- 
2.4.2.644.g97b850b-twtrsrc
David Turner· Sep 28, 2015, 22:02 UTC · re: David Turner · lore

[PATCH v2 28/43] refs.c: add ref backend init function

The file backend doesn't need this function, but other backends might.
Signed-off-by: David Turner <dturner@twopensource.com>
Signed-off-by: Ronnie Sahlberg <rsahlberg@google.com>
---
 refs-be-files.c | 1 +
 refs.c          | 4 +++-
 refs.h          | 4 +++-
 3 files changed, 7 insertions(+), 2 deletions(-)
Show changes to 3 files +7 −2

refs-be-files.c, refs.c, refs.h

diff --git a/refs-be-files.c b/refs-be-files.c
index 37e244a..eaa74b6 100644
--- a/refs-be-files.c
+++ b/refs-be-files.c
@@ -3737,6 +3737,7 @@ int reflog_expire(const char *refname, const unsigned char *sha1,
 struct ref_be refs_be_files = {
 	NULL,
 	"files",
+	NULL,
 	files_transaction_begin,
 	files_transaction_update,
 	files_transaction_create,
diff --git a/refs.c b/refs.c
index 769574d..9ce10b7 100644
--- a/refs.c
+++ b/refs.c
@@ -19,13 +19,15 @@ struct ref_be *refs_backends = &refs_be_files;
 /*
  * This function is used to switch to an alternate backend.
  */
-int set_refs_backend(const char *name)
+int set_refs_backend(const char *name, void *init_data)
 {
 	struct ref_be *be;
 
 	for (be = refs_backends; be; be = be->next)
 		if (!strcmp(be->name, name)) {
 			the_refs_backend = be;
+			if (be->init_backend)
+				be->init_backend(init_data);
 			return 0;
 		}
 	return 1;
diff --git a/refs.h b/refs.h
index 0b407b2..0dc626e 100644
--- a/refs.h
+++ b/refs.h
@@ -586,6 +586,7 @@ extern int reflog_expire(const char *refname, const unsigned char *sha1,
 			 void *policy_cb_data);
 
 /* refs backends */
+typedef void (*ref_backend_init_fn)(void *data);
 typedef struct ref_transaction *(*ref_transaction_begin_fn)(struct strbuf *err);
 typedef int (*ref_transaction_update_fn)(struct ref_transaction *transaction,
 		const char *refname, const unsigned char *new_sha1,
@@ -641,6 +642,7 @@ typedef int (*for_each_reftype_fullpath_fn)(each_ref_fn fn, char *type,
 struct ref_be {
 	struct ref_be *next;
 	const char *name;
+	ref_backend_init_fn init_backend;
 	ref_transaction_begin_fn transaction_begin;
 	ref_transaction_update_fn transaction_update;
 	ref_transaction_create_fn transaction_create;
@@ -669,6 +671,6 @@ struct ref_be {
 
 
 extern struct ref_be refs_be_files;
-int set_refs_backend(const char *name);
+int set_refs_backend(const char *name, void *init_data);
 
 #endif /* REFS_H */
-- 
2.4.2.644.g97b850b-twtrsrc
Michael Haggerty· Oct 5, 2015, 08:37 UTC · re: David Turner · lore

Re: [PATCH v2 28/43] refs.c: add ref backend init function

On 09/29/2015 12:02 AM, David Turner wrote:
Show 40 quoted lines
> The file backend doesn't need this function, but other backends might.
> 
> Signed-off-by: David Turner <dturner@twopensource.com>
> Signed-off-by: Ronnie Sahlberg <rsahlberg@google.com>
> ---
>  refs-be-files.c | 1 +
>  refs.c          | 4 +++-
>  refs.h          | 4 +++-
>  3 files changed, 7 insertions(+), 2 deletions(-)
> 
> diff --git a/refs-be-files.c b/refs-be-files.c
> index 37e244a..eaa74b6 100644
> --- a/refs-be-files.c
> +++ b/refs-be-files.c
> @@ -3737,6 +3737,7 @@ int reflog_expire(const char *refname, const unsigned char *sha1,
>  struct ref_be refs_be_files = {
>  	NULL,
>  	"files",
> +	NULL,
>  	files_transaction_begin,
>  	files_transaction_update,
>  	files_transaction_create,
> diff --git a/refs.c b/refs.c
> index 769574d..9ce10b7 100644
> --- a/refs.c
> +++ b/refs.c
> @@ -19,13 +19,15 @@ struct ref_be *refs_backends = &refs_be_files;
>  /*
>   * This function is used to switch to an alternate backend.
>   */
> -int set_refs_backend(const char *name)
> +int set_refs_backend(const char *name, void *init_data)
>  {
>  	struct ref_be *be;
>  
>  	for (be = refs_backends; be; be = be->next)
>  		if (!strcmp(be->name, name)) {
>  			the_refs_backend = be;
> +			if (be->init_backend)
> +				be->init_backend(init_data);

I don't like that this virtual function, alone among all of them introduced so far, is allowed to be NULL. That seems non-obvious and something extra that devs have to remember.

I think it would be better for the files backend to define a do-nothing function that can be stuck in this slot.

If you are opposed to that for some reason, then please at least add a comment where this function pointer is added to `struct ref_be`, warning that it can be NULL.

Show 32 quoted lines
>  			return 0;
>  		}
>  	return 1;
> diff --git a/refs.h b/refs.h
> index 0b407b2..0dc626e 100644
> --- a/refs.h
> +++ b/refs.h
> @@ -586,6 +586,7 @@ extern int reflog_expire(const char *refname, const unsigned char *sha1,
>  			 void *policy_cb_data);
>  
>  /* refs backends */
> +typedef void (*ref_backend_init_fn)(void *data);
>  typedef struct ref_transaction *(*ref_transaction_begin_fn)(struct strbuf *err);
>  typedef int (*ref_transaction_update_fn)(struct ref_transaction *transaction,
>  		const char *refname, const unsigned char *new_sha1,
> @@ -641,6 +642,7 @@ typedef int (*for_each_reftype_fullpath_fn)(each_ref_fn fn, char *type,
>  struct ref_be {
>  	struct ref_be *next;
>  	const char *name;
> +	ref_backend_init_fn init_backend;
>  	ref_transaction_begin_fn transaction_begin;
>  	ref_transaction_update_fn transaction_update;
>  	ref_transaction_create_fn transaction_create;
> @@ -669,6 +671,6 @@ struct ref_be {
>  
>  
>  extern struct ref_be refs_be_files;
> -int set_refs_backend(const char *name);
> +int set_refs_backend(const char *name, void *init_data);
>  
>  #endif /* REFS_H */
> 
Michael
-- 
Michael Haggerty
mhagger@alum.mit.edu
David Turner· Oct 5, 2015, 20:37 UTC · re: Michael Haggerty · lore

Re: [PATCH v2 28/43] refs.c: add ref backend init function

On Mon, 2015-10-05 at 10:37 +0200, Michael Haggerty wrote:
Show 48 quoted lines
> On 09/29/2015 12:02 AM, David Turner wrote:
> > The file backend doesn't need this function, but other backends might.
> > 
> > Signed-off-by: David Turner <dturner@twopensource.com>
> > Signed-off-by: Ronnie Sahlberg <rsahlberg@google.com>
> > ---
> >  refs-be-files.c | 1 +
> >  refs.c          | 4 +++-
> >  refs.h          | 4 +++-
> >  3 files changed, 7 insertions(+), 2 deletions(-)
> > 
> > diff --git a/refs-be-files.c b/refs-be-files.c
> > index 37e244a..eaa74b6 100644
> > --- a/refs-be-files.c
> > +++ b/refs-be-files.c
> > @@ -3737,6 +3737,7 @@ int reflog_expire(const char *refname, const unsigned char *sha1,
> >  struct ref_be refs_be_files = {
> >  	NULL,
> >  	"files",
> > +	NULL,
> >  	files_transaction_begin,
> >  	files_transaction_update,
> >  	files_transaction_create,
> > diff --git a/refs.c b/refs.c
> > index 769574d..9ce10b7 100644
> > --- a/refs.c
> > +++ b/refs.c
> > @@ -19,13 +19,15 @@ struct ref_be *refs_backends = &refs_be_files;
> >  /*
> >   * This function is used to switch to an alternate backend.
> >   */
> > -int set_refs_backend(const char *name)
> > +int set_refs_backend(const char *name, void *init_data)
> >  {
> >  	struct ref_be *be;
> >  
> >  	for (be = refs_backends; be; be = be->next)
> >  		if (!strcmp(be->name, name)) {
> >  			the_refs_backend = be;
> > +			if (be->init_backend)
> > +				be->init_backend(init_data);
> 
> I don't like that this virtual function, alone among all of them
> introduced so far, is allowed to be NULL. That seems non-obvious and
> something extra that devs have to remember.
> 
> I think it would be better for the files backend to define a do-nothing
> function that can be stuck in this slot.
Will change, thanks.
David Turner· Sep 28, 2015, 22:02 UTC · re: David Turner · lore

[PATCH v2 29/43] refs.c: add methods for reflog

In the file-based backend, the reflog piggybacks on the ref lock. Since other backends won't have the same sort of ref lock, ref backends must also handle reflogs.

Signed-off-by: Ronnie Sahlberg <rsahlberg@google.com>
Signed-off-by: David Turner <dturner@twopensource.com>
---
 refs-be-files.c | 32 +++++++++++++++++++++-----------
 refs.c          | 34 ++++++++++++++++++++++++++++++++++
 refs.h          | 23 +++++++++++++++++++++++
 3 files changed, 78 insertions(+), 11 deletions(-)
Show changes to 3 files +78 −11

refs-be-files.c, refs.c, refs.h

diff --git a/refs-be-files.c b/refs-be-files.c
index eaa74b6..fdb47e0 100644
--- a/refs-be-files.c
+++ b/refs-be-files.c
@@ -2691,7 +2691,8 @@ static int log_ref_setup(const char *refname, struct strbuf *logfile, struct str
 }
 
 
-int safe_create_reflog(const char *refname, int force_create, struct strbuf *err)
+static int files_create_reflog(const char *refname, int force_create,
+			       struct strbuf *err)
 {
 	int ret;
 	struct strbuf sb = STRBUF_INIT;
@@ -2940,7 +2941,7 @@ static int files_create_symref(struct ref_transaction *trans,
 	return 0;
 }
 
-int reflog_exists(const char *refname)
+static int files_reflog_exists(const char *refname)
 {
 	struct stat st;
 
@@ -2948,7 +2949,7 @@ int reflog_exists(const char *refname)
 		S_ISREG(st.st_mode);
 }
 
-int delete_reflog(const char *refname)
+static int files_delete_reflog(const char *refname)
 {
 	return remove_path(git_path("logs/%s", refname));
 }
@@ -2992,7 +2993,9 @@ static char *find_beginning_of_line(char *bob, char *scan)
 	return scan;
 }
 
-int for_each_reflog_ent_reverse(const char *refname, each_reflog_ent_fn fn, void *cb_data)
+static int files_for_each_reflog_ent_reverse(const char *refname,
+					     each_reflog_ent_fn fn,
+					     void *cb_data)
 {
 	struct strbuf sb = STRBUF_INIT;
 	FILE *logfp;
@@ -3094,7 +3097,8 @@ int for_each_reflog_ent_reverse(const char *refname, each_reflog_ent_fn fn, void
 	return ret;
 }
 
-int for_each_reflog_ent(const char *refname, each_reflog_ent_fn fn, void *cb_data)
+static int files_for_each_reflog_ent(const char *refname,
+				     each_reflog_ent_fn fn, void *cb_data)
 {
 	FILE *logfp;
 	struct strbuf sb = STRBUF_INIT;
@@ -3156,7 +3160,7 @@ static int do_for_each_reflog(struct strbuf *name, each_ref_fn fn, void *cb_data
 	return retval;
 }
 
-int for_each_reflog(each_ref_fn fn, void *cb_data)
+static int files_for_each_reflog(each_ref_fn fn, void *cb_data)
 {
 	int retval;
 	struct strbuf name;
@@ -3630,11 +3634,11 @@ static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
 }
 
 int reflog_expire(const char *refname, const unsigned char *sha1,
-		 unsigned int flags,
-		 reflog_expiry_prepare_fn prepare_fn,
-		 reflog_expiry_should_prune_fn should_prune_fn,
-		 reflog_expiry_cleanup_fn cleanup_fn,
-		 void *policy_cb_data)
+		  unsigned int flags,
+		  reflog_expiry_prepare_fn prepare_fn,
+		  reflog_expiry_should_prune_fn should_prune_fn,
+		  reflog_expiry_cleanup_fn cleanup_fn,
+		  void *policy_cb_data)
 {
 	static struct lock_file reflog_lock;
 	struct expire_reflog_cb cb;
@@ -3745,6 +3749,12 @@ struct ref_be refs_be_files = {
 	files_transaction_verify,
 	files_transaction_commit,
 	files_transaction_free,
+	files_for_each_reflog_ent,
+	files_for_each_reflog_ent_reverse,
+	files_for_each_reflog,
+	files_reflog_exists,
+	files_create_reflog,
+	files_delete_reflog,
 	files_resolve_ref_unsafe,
 	files_verify_refname_available,
 	files_pack_refs,
diff --git a/refs.c b/refs.c
index 9ce10b7..7b2a47b 100644
--- a/refs.c
+++ b/refs.c
@@ -1155,3 +1155,37 @@ int for_each_reftype_fullpath(each_ref_fn fn, char *type, unsigned int broken,
 	return the_refs_backend->for_each_reftype_fullpath(fn, type, broken,
 							   cb_data);
 }
+
+int for_each_reflog_ent_reverse(const char *refname, each_reflog_ent_fn fn,
+				void *cb_data)
+{
+	return the_refs_backend->for_each_reflog_ent_reverse(refname, fn,
+							     cb_data);
+}
+
+int for_each_reflog_ent(const char *refname, each_reflog_ent_fn fn,
+			void *cb_data)
+{
+	return the_refs_backend->for_each_reflog_ent(refname, fn, cb_data);
+}
+
+int for_each_reflog(each_ref_fn fn, void *cb_data)
+{
+	return the_refs_backend->for_each_reflog(fn, cb_data);
+}
+
+int reflog_exists(const char *refname)
+{
+	return the_refs_backend->reflog_exists(refname);
+}
+
+int safe_create_reflog(const char *refname, int force_create,
+		       struct strbuf *err)
+{
+	return the_refs_backend->create_reflog(refname, force_create, err);
+}
+
+int delete_reflog(const char *refname)
+{
+	return the_refs_backend->delete_reflog(refname);
+}
diff --git a/refs.h b/refs.h
index 0dc626e..494f8c8 100644
--- a/refs.h
+++ b/refs.h
@@ -605,6 +605,21 @@ typedef int (*ref_transaction_verify_fn)(struct ref_transaction *transaction,
 typedef int (*ref_transaction_commit_fn)(struct ref_transaction *transaction,
 				     struct strbuf *err);
 typedef void (*ref_transaction_free_fn)(struct ref_transaction *transaction);
+
+/* reflog functions */
+typedef int (*for_each_reflog_ent_fn)(const char *refname,
+				      each_reflog_ent_fn fn,
+				      void *cb_data);
+typedef int (*for_each_reflog_ent_reverse_fn)(const char *refname,
+					      each_reflog_ent_fn fn,
+					      void *cb_data);
+typedef int (*for_each_reflog_fn)(each_ref_fn fn, void *cb_data);
+typedef int (*reflog_exists_fn)(const char *refname);
+typedef int (*create_reflog_fn)(const char *refname, int force_create,
+				struct strbuf *err);
+typedef int (*delete_reflog_fn)(const char *refname);
+
+/* resolution functions */
 typedef const char *(*resolve_ref_unsafe_fn)(const char *ref,
 					     int resolve_flags,
 					     unsigned char *sha1, int *flags);
@@ -620,6 +635,8 @@ typedef int (*create_symref_fn)(struct ref_transaction *transaction,
 				const char *logmsg);
 typedef int (*resolve_gitlink_ref_fn)(const char *path, const char *refname,
 				      unsigned char *sha1);
+
+/* iteration functions */
 typedef int (*head_ref_fn)(each_ref_fn fn, void *cb_data);
 typedef int (*head_ref_submodule_fn)(const char *submodule, each_ref_fn fn,
 				     void *cb_data);
@@ -650,6 +667,12 @@ struct ref_be {
 	ref_transaction_verify_fn transaction_verify;
 	ref_transaction_commit_fn transaction_commit;
 	ref_transaction_free_fn transaction_free;
+	for_each_reflog_ent_fn for_each_reflog_ent;
+	for_each_reflog_ent_reverse_fn for_each_reflog_ent_reverse;
+	for_each_reflog_fn for_each_reflog;
+	reflog_exists_fn reflog_exists;
+	create_reflog_fn create_reflog;
+	delete_reflog_fn delete_reflog;
 	resolve_ref_unsafe_fn resolve_ref_unsafe;
 	verify_refname_available_fn verify_refname_available;
 	pack_refs_fn pack_refs;
-- 
2.4.2.644.g97b850b-twtrsrc
David Turner· Sep 28, 2015, 22:02 UTC · re: David Turner · lore

[PATCH v2 30/43] refs-be-files.c: add method to expire reflogs

Signed-off-by: David Turner <dturner@twopensource.com>
---
 refs-be-files.c | 13 +++++++------
 refs.c          | 12 ++++++++++++
 refs.h          |  7 +++++++
 3 files changed, 26 insertions(+), 6 deletions(-)
Show changes to 3 files +26 −6

refs-be-files.c, refs.c, refs.h

diff --git a/refs-be-files.c b/refs-be-files.c
index fdb47e0..93a5a33 100644
--- a/refs-be-files.c
+++ b/refs-be-files.c
@@ -3633,12 +3633,12 @@ static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
 	return 0;
 }
 
-int reflog_expire(const char *refname, const unsigned char *sha1,
-		  unsigned int flags,
-		  reflog_expiry_prepare_fn prepare_fn,
-		  reflog_expiry_should_prune_fn should_prune_fn,
-		  reflog_expiry_cleanup_fn cleanup_fn,
-		  void *policy_cb_data)
+static int files_reflog_expire(const char *refname, const unsigned char *sha1,
+			       unsigned int flags,
+			       reflog_expiry_prepare_fn prepare_fn,
+			       reflog_expiry_should_prune_fn should_prune_fn,
+			       reflog_expiry_cleanup_fn cleanup_fn,
+			       void *policy_cb_data)
 {
 	static struct lock_file reflog_lock;
 	struct expire_reflog_cb cb;
@@ -3755,6 +3755,7 @@ struct ref_be refs_be_files = {
 	files_reflog_exists,
 	files_create_reflog,
 	files_delete_reflog,
+	files_reflog_expire,
 	files_resolve_ref_unsafe,
 	files_verify_refname_available,
 	files_pack_refs,
diff --git a/refs.c b/refs.c
index 7b2a47b..96fa699 100644
--- a/refs.c
+++ b/refs.c
@@ -1189,3 +1189,15 @@ int delete_reflog(const char *refname)
 {
 	return the_refs_backend->delete_reflog(refname);
 }
+
+int reflog_expire(const char *refname, const unsigned char *sha1,
+		  unsigned int flags,
+		  reflog_expiry_prepare_fn prepare_fn,
+		  reflog_expiry_should_prune_fn should_prune_fn,
+		  reflog_expiry_cleanup_fn cleanup_fn,
+		  void *policy_cb_data)
+{
+	return the_refs_backend->reflog_expire(refname, sha1, flags,
+					       prepare_fn, should_prune_fn,
+					       cleanup_fn, policy_cb_data);
+}
diff --git a/refs.h b/refs.h
index 494f8c8..06089f8 100644
--- a/refs.h
+++ b/refs.h
@@ -618,6 +618,12 @@ typedef int (*reflog_exists_fn)(const char *refname);
 typedef int (*create_reflog_fn)(const char *refname, int force_create,
 				struct strbuf *err);
 typedef int (*delete_reflog_fn)(const char *refname);
+typedef int (*reflog_expire_fn)(const char *refname, const unsigned char *sha1,
+				unsigned int flags,
+				reflog_expiry_prepare_fn prepare_fn,
+				reflog_expiry_should_prune_fn should_prune_fn,
+				reflog_expiry_cleanup_fn cleanup_fn,
+				void *policy_cb_data);
 
 /* resolution functions */
 typedef const char *(*resolve_ref_unsafe_fn)(const char *ref,
@@ -673,6 +679,7 @@ struct ref_be {
 	reflog_exists_fn reflog_exists;
 	create_reflog_fn create_reflog;
 	delete_reflog_fn delete_reflog;
+	reflog_expire_fn reflog_expire;
 	resolve_ref_unsafe_fn resolve_ref_unsafe;
 	verify_refname_available_fn verify_refname_available;
 	pack_refs_fn pack_refs;
-- 
2.4.2.644.g97b850b-twtrsrc
Michael Haggerty· Oct 5, 2015, 08:41 UTC · re: David Turner · lore

Re: [PATCH v2 30/43] refs-be-files.c: add method to expire reflogs

On 09/29/2015 12:02 AM, David Turner wrote:
Show 7 quoted lines
> Signed-off-by: David Turner <dturner@twopensource.com>
> ---
>  refs-be-files.c | 13 +++++++------
>  refs.c          | 12 ++++++++++++
>  refs.h          |  7 +++++++
>  3 files changed, 26 insertions(+), 6 deletions(-)
> [...]
I think it would be natural to squash this into the previous patch.
Michael
-- 
Michael Haggerty
mhagger@alum.mit.edu
David Turner· Sep 28, 2015, 22:02 UTC · re: David Turner · lore

[PATCH v2 31/43] refs.c: add method for initial ref transaction commit

Signed-off-by: Ronnie Sahlberg <rsahlberg@google.com>
Signed-off-by: David Turner <dturner@twopensource.com>
---
 refs-be-files.c | 5 +++--
 refs.c          | 6 ++++++
 refs.h          | 1 +
 3 files changed, 10 insertions(+), 2 deletions(-)
Show changes to 3 files +10 −2

refs-be-files.c, refs.c, refs.h

diff --git a/refs-be-files.c b/refs-be-files.c
index 93a5a33..ea91afd 100644
--- a/refs-be-files.c
+++ b/refs-be-files.c
@@ -3517,8 +3517,8 @@ static int ref_present(const char *refname,
 	return string_list_has_string(affected_refnames, refname);
 }
 
-int initial_ref_transaction_commit(struct ref_transaction *transaction,
-				   struct strbuf *err)
+static int files_initial_transaction_commit(struct ref_transaction *transaction,
+					    struct strbuf *err)
 {
 	int ret = 0, i;
 	int n = transaction->nr;
@@ -3748,6 +3748,7 @@ struct ref_be refs_be_files = {
 	files_transaction_delete,
 	files_transaction_verify,
 	files_transaction_commit,
+	files_initial_transaction_commit,
 	files_transaction_free,
 	files_for_each_reflog_ent,
 	files_for_each_reflog_ent_reverse,
diff --git a/refs.c b/refs.c
index 96fa699..530edcf 100644
--- a/refs.c
+++ b/refs.c
@@ -1201,3 +1201,9 @@ int reflog_expire(const char *refname, const unsigned char *sha1,
 					       prepare_fn, should_prune_fn,
 					       cleanup_fn, policy_cb_data);
 }
+
+int initial_ref_transaction_commit(struct ref_transaction *transaction,
+				   struct strbuf *err)
+{
+	return the_refs_backend->initial_transaction_commit(transaction, err);
+}
diff --git a/refs.h b/refs.h
index 06089f8..bd46d0f 100644
--- a/refs.h
+++ b/refs.h
@@ -672,6 +672,7 @@ struct ref_be {
 	ref_transaction_delete_fn transaction_delete;
 	ref_transaction_verify_fn transaction_verify;
 	ref_transaction_commit_fn transaction_commit;
+	ref_transaction_commit_fn initial_transaction_commit;
 	ref_transaction_free_fn transaction_free;
 	for_each_reflog_ent_fn for_each_reflog_ent;
 	for_each_reflog_ent_reverse_fn for_each_reflog_ent_reverse;
-- 
2.4.2.644.g97b850b-twtrsrc
David Turner· Sep 28, 2015, 22:02 UTC · re: David Turner · lore

[PATCH v2 32/43] initdb: move safe_create_dir into common code

In a moment, we'll create initdb functions for ref backends, and code from initdb that calls this function needs to move into the files backend. So this function needs to be public.

Signed-off-by: David Turner <dturner@twopensource.com>
---
 builtin/init-db.c | 12 ------------
 cache.h           |  5 +++++
 path.c            | 12 ++++++++++++
 3 files changed, 17 insertions(+), 12 deletions(-)
Show changes to 3 files +17 −12

builtin/init-db.c, cache.h, path.c

diff --git a/builtin/init-db.c b/builtin/init-db.c
index cf6a3c8..f0b095c 100644
--- a/builtin/init-db.c
+++ b/builtin/init-db.c
@@ -24,18 +24,6 @@ static int init_shared_repository = -1;
 static const char *init_db_template_dir;
 static const char *git_link;
 
-static void safe_create_dir(const char *dir, int share)
-{
-	if (mkdir(dir, 0777) < 0) {
-		if (errno != EEXIST) {
-			perror(dir);
-			exit(1);
-		}
-	}
-	else if (share && adjust_shared_perm(dir))
-		die(_("Could not make %s writable by group"), dir);
-}
-
 static void copy_templates_1(struct strbuf *path, struct strbuf *template,
 			     DIR *dir)
 {
diff --git a/cache.h b/cache.h
index 1c45b62..cc817dc 100644
--- a/cache.h
+++ b/cache.h
@@ -1749,4 +1749,9 @@ void stat_validity_update(struct stat_validity *sv, int fd);
 int versioncmp(const char *s1, const char *s2);
 void sleep_millisec(int millisec);
 
+/*
+ * Create a directory and (if share is nonzero) adjust its permissions
+ * according to the shared_repository setting.
+ */
+void safe_create_dir(const char *dir, int share);
 #endif /* CACHE_H */
diff --git a/path.c b/path.c
index 48bd252..9ab2710 100644
--- a/path.c
+++ b/path.c
@@ -740,6 +740,18 @@ int adjust_shared_perm(const char *path)
 	return 0;
 }
 
+void safe_create_dir(const char *dir, int share)
+{
+	if (mkdir(dir, 0777) < 0) {
+		if (errno != EEXIST) {
+			perror(dir);
+			exit(1);
+		}
+	}
+	else if (share && adjust_shared_perm(dir))
+		die(_("Could not make %s writable by group"), dir);
+}
+
 static int have_same_root(const char *path1, const char *path2)
 {
 	int is_abs1, is_abs2;
-- 
2.4.2.644.g97b850b-twtrsrc
David Turner· Sep 28, 2015, 22:02 UTC · re: David Turner · lore

[PATCH v2 33/43] refs.c: add method for initializing refs db

Alternate refs backends might not need refs/heads and so on, so we make ref db initialization part of the backend.

Signed-off-by: David Turner <dturner@twopensource.com>
---
 builtin/init-db.c | 14 ++++----------
 refs-be-files.c   | 17 +++++++++++++++++
 refs.c            |  5 +++++
 refs.h            |  4 ++++
 4 files changed, 30 insertions(+), 10 deletions(-)
Show changes to 4 files +30 −10

builtin/init-db.c, refs-be-files.c, refs.c, refs.h

diff --git a/builtin/init-db.c b/builtin/init-db.c
index f0b095c..504a2dc 100644
--- a/builtin/init-db.c
+++ b/builtin/init-db.c
@@ -177,13 +177,7 @@ static int create_default_files(const char *template_path)
 	char junk[2];
 	int reinit;
 	int filemode;
-
-	/*
-	 * Create .git/refs/{heads,tags}
-	 */
-	safe_create_dir(git_path_buf(&buf, "refs"), 1);
-	safe_create_dir(git_path_buf(&buf, "refs/heads"), 1);
-	safe_create_dir(git_path_buf(&buf, "refs/tags"), 1);
+	struct strbuf err = STRBUF_INIT;
 
 	/* Just look for `init.templatedir` */
 	git_config(git_init_db_config, NULL);
@@ -207,11 +201,11 @@ static int create_default_files(const char *template_path)
 	 */
 	if (shared_repository) {
 		adjust_shared_perm(get_git_dir());
-		adjust_shared_perm(git_path_buf(&buf, "refs"));
-		adjust_shared_perm(git_path_buf(&buf, "refs/heads"));
-		adjust_shared_perm(git_path_buf(&buf, "refs/tags"));
 	}
 
+	if (refs_initdb(&err, shared_repository))
+		die("failed to set up refs db: %s", err.buf);
+
 	/*
 	 * Create the default symlink from ".git/HEAD" to the "master"
 	 * branch, if it does not exist yet.
diff --git a/refs-be-files.c b/refs-be-files.c
index ea91afd..0a76c8e 100644
--- a/refs-be-files.c
+++ b/refs-be-files.c
@@ -3738,10 +3738,27 @@ static int files_reflog_expire(const char *refname, const unsigned char *sha1,
 	return -1;
 }
 
+static int files_initdb(struct strbuf *err, int shared)
+{
+	/*
+	 * Create .git/refs/{heads,tags}
+	 */
+	safe_create_dir(git_path("refs"), 1);
+	safe_create_dir(git_path("refs/heads"), 1);
+	safe_create_dir(git_path("refs/tags"), 1);
+	if (shared) {
+		adjust_shared_perm(git_path("refs"));
+		adjust_shared_perm(git_path("refs/heads"));
+		adjust_shared_perm(git_path("refs/tags"));
+	}
+	return 0;
+}
+
 struct ref_be refs_be_files = {
 	NULL,
 	"files",
 	NULL,
+	files_initdb,
 	files_transaction_begin,
 	files_transaction_update,
 	files_transaction_create,
diff --git a/refs.c b/refs.c
index 530edcf..930c85c 100644
--- a/refs.c
+++ b/refs.c
@@ -995,6 +995,11 @@ enum peel_status peel_object(const unsigned char *name, unsigned char *sha1)
 }
 
 /* backend functions */
+int refs_initdb(struct strbuf *err, int shared)
+{
+	return the_refs_backend->initdb(err, shared);
+}
+
 struct ref_transaction *ref_transaction_begin(struct strbuf *err)
 {
 	return the_refs_backend->transaction_begin(err);
diff --git a/refs.h b/refs.h
index bd46d0f..64dba64 100644
--- a/refs.h
+++ b/refs.h
@@ -68,6 +68,8 @@ extern int ref_exists(const char *refname);
 
 extern int is_branch(const char *refname);
 
+extern int refs_initdb(struct strbuf *err, int shared);
+
 /*
  * If refname is a non-symbolic reference that refers to a tag object,
  * and the tag can be (recursively) dereferenced to a non-tag object,
@@ -587,6 +589,7 @@ extern int reflog_expire(const char *refname, const unsigned char *sha1,
 
 /* refs backends */
 typedef void (*ref_backend_init_fn)(void *data);
+typedef int (*ref_backend_initdb_fn)(struct strbuf *err, int shared);
 typedef struct ref_transaction *(*ref_transaction_begin_fn)(struct strbuf *err);
 typedef int (*ref_transaction_update_fn)(struct ref_transaction *transaction,
 		const char *refname, const unsigned char *new_sha1,
@@ -666,6 +669,7 @@ struct ref_be {
 	struct ref_be *next;
 	const char *name;
 	ref_backend_init_fn init_backend;
+	ref_backend_initdb_fn initdb;
 	ref_transaction_begin_fn transaction_begin;
 	ref_transaction_update_fn transaction_update;
 	ref_transaction_create_fn transaction_create;
-- 
2.4.2.644.g97b850b-twtrsrc
David Turner· Sep 28, 2015, 22:02 UTC · re: David Turner · lore

[PATCH v2 34/43] refs.c: make struct ref_transaction generic

Alternate ref backends might need different data for transactions. Make struct ref_transaction an empty struct, and let backends define their own structs which extend it.

Signed-off-by: David Turner <dturner@twopensource.com>
---
 refs-be-files.c | 24 +++++++++++++++++-------
 refs.h          |  8 ++++++--
 2 files changed, 23 insertions(+), 9 deletions(-)
Show changes to 2 files +23 −9

refs-be-files.c, refs.h

diff --git a/refs-be-files.c b/refs-be-files.c
index 0a76c8e..3f2d194 100644
--- a/refs-be-files.c
+++ b/refs-be-files.c
@@ -3218,7 +3218,8 @@ enum ref_transaction_state {
  * consist of checks and updates to multiple references, carried out
  * as atomically as possible.  This structure is opaque to callers.
  */
-struct ref_transaction {
+struct files_ref_transaction {
+	struct ref_transaction base;
 	struct ref_update **updates;
 	size_t alloc;
 	size_t nr;
@@ -3229,13 +3230,16 @@ static struct ref_transaction *files_transaction_begin(struct strbuf *err)
 {
 	assert(err);
 
-	return xcalloc(1, sizeof(struct ref_transaction));
+	return xcalloc(1, sizeof(struct files_ref_transaction));
 }
 
-static void files_transaction_free(struct ref_transaction *transaction)
+static void files_transaction_free(struct ref_transaction *trans)
 {
 	int i;
 
+	struct files_ref_transaction *transaction =
+		(struct files_ref_transaction *)trans;
+
 	if (!transaction)
 		return;
 
@@ -3247,7 +3251,7 @@ static void files_transaction_free(struct ref_transaction *transaction)
 	free(transaction);
 }
 
-static struct ref_update *add_update(struct ref_transaction *transaction,
+static struct ref_update *add_update(struct files_ref_transaction *transaction,
 				     const char *refname)
 {
 	size_t len = strlen(refname) + 1;
@@ -3259,7 +3263,7 @@ static struct ref_update *add_update(struct ref_transaction *transaction,
 	return update;
 }
 
-static int files_transaction_update(struct ref_transaction *transaction,
+static int files_transaction_update(struct ref_transaction *trans,
 				  const char *refname,
 				  const unsigned char *new_sha1,
 				  const unsigned char *old_sha1,
@@ -3267,6 +3271,8 @@ static int files_transaction_update(struct ref_transaction *transaction,
 				  struct strbuf *err)
 {
 	struct ref_update *update;
+	struct files_ref_transaction *transaction =
+		(struct files_ref_transaction *)trans;
 
 	assert(err);
 
@@ -3350,10 +3356,12 @@ static int ref_update_reject_duplicates(struct string_list *refnames,
 	return 0;
 }
 
-static int files_transaction_commit(struct ref_transaction *transaction,
+static int files_transaction_commit(struct ref_transaction *trans,
 				  struct strbuf *err)
 {
 	int ret = 0, i;
+	struct files_ref_transaction *transaction =
+		(struct files_ref_transaction *)trans;
 	int n = transaction->nr;
 	struct ref_update **updates = transaction->updates;
 	struct string_list refs_to_delete = STRING_LIST_INIT_NODUP;
@@ -3517,10 +3525,12 @@ static int ref_present(const char *refname,
 	return string_list_has_string(affected_refnames, refname);
 }
 
-static int files_initial_transaction_commit(struct ref_transaction *transaction,
+static int files_initial_transaction_commit(struct ref_transaction *trans,
 					    struct strbuf *err)
 {
 	int ret = 0, i;
+	struct files_ref_transaction *transaction =
+		(struct files_ref_transaction *)trans;
 	int n = transaction->nr;
 	struct ref_update **updates = transaction->updates;
 	struct string_list affected_refnames = STRING_LIST_INIT_NODUP;
diff --git a/refs.h b/refs.h
index 64dba64..02001ef 100644
--- a/refs.h
+++ b/refs.h
@@ -130,7 +130,7 @@ extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref);
  *
  * Calling sequence
  * ----------------
- * - Allocate and initialize a `struct ref_transaction` by calling
+ * - Allocate and initialize a transaction by calling
  *   `ref_transaction_begin()`.
  *
  * - List intended ref updates by calling functions like
@@ -156,7 +156,10 @@ extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref);
  * The message is appended to err without first clearing err.
  * err will not be '\n' terminated.
  */
-struct ref_transaction;
+
+struct ref_transaction {
+	/* ref backends should extend this */
+};
 
 /*
  * Bit values set in the flags argument passed to each_ref_fn():
@@ -629,6 +632,7 @@ typedef int (*reflog_expire_fn)(const char *refname, const unsigned char *sha1,
 				void *policy_cb_data);
 
 /* resolution functions */
+typedef void (*ref_transaction_free_fn)(struct ref_transaction *transaction);
 typedef const char *(*resolve_ref_unsafe_fn)(const char *ref,
 					     int resolve_flags,
 					     unsigned char *sha1, int *flags);
-- 
2.4.2.644.g97b850b-twtrsrc
Michael Blume· Oct 6, 2015, 17:43 UTC · re: David Turner · lore

Re: [PATCH v2 34/43] refs.c: make struct ref_transaction generic

This triggers a lot of build warnings on my mac, basically in every file that uses refs.h:

    CC archive.o
In file included from archive.c:2:
./refs.h:635:16: warning: redefinition of typedef
'ref_transaction_free_fn' is a C11 feature [-Wtypedef-redefinition]
typedef void (*ref_transaction_free_fn)(struct ref_transaction *transaction);
               ^
./refs.h:613:16: note: previous definition is here
typedef void (*ref_transaction_free_fn)(struct ref_transaction *transaction);
               ^
1 warning generated.
On Mon, Sep 28, 2015 at 3:02 PM, David Turner <dturner@twopensource.com> wrote:
Show 138 quoted lines
> Alternate ref backends might need different data for transactions.  Make
> struct ref_transaction an empty struct, and let backends define their
> own structs which extend it.
>
> Signed-off-by: David Turner <dturner@twopensource.com>
> ---
>  refs-be-files.c | 24 +++++++++++++++++-------
>  refs.h          |  8 ++++++--
>  2 files changed, 23 insertions(+), 9 deletions(-)
>
> diff --git a/refs-be-files.c b/refs-be-files.c
> index 0a76c8e..3f2d194 100644
> --- a/refs-be-files.c
> +++ b/refs-be-files.c
> @@ -3218,7 +3218,8 @@ enum ref_transaction_state {
>   * consist of checks and updates to multiple references, carried out
>   * as atomically as possible.  This structure is opaque to callers.
>   */
> -struct ref_transaction {
> +struct files_ref_transaction {
> +       struct ref_transaction base;
>         struct ref_update **updates;
>         size_t alloc;
>         size_t nr;
> @@ -3229,13 +3230,16 @@ static struct ref_transaction *files_transaction_begin(struct strbuf *err)
>  {
>         assert(err);
>
> -       return xcalloc(1, sizeof(struct ref_transaction));
> +       return xcalloc(1, sizeof(struct files_ref_transaction));
>  }
>
> -static void files_transaction_free(struct ref_transaction *transaction)
> +static void files_transaction_free(struct ref_transaction *trans)
>  {
>         int i;
>
> +       struct files_ref_transaction *transaction =
> +               (struct files_ref_transaction *)trans;
> +
>         if (!transaction)
>                 return;
>
> @@ -3247,7 +3251,7 @@ static void files_transaction_free(struct ref_transaction *transaction)
>         free(transaction);
>  }
>
> -static struct ref_update *add_update(struct ref_transaction *transaction,
> +static struct ref_update *add_update(struct files_ref_transaction *transaction,
>                                      const char *refname)
>  {
>         size_t len = strlen(refname) + 1;
> @@ -3259,7 +3263,7 @@ static struct ref_update *add_update(struct ref_transaction *transaction,
>         return update;
>  }
>
> -static int files_transaction_update(struct ref_transaction *transaction,
> +static int files_transaction_update(struct ref_transaction *trans,
>                                   const char *refname,
>                                   const unsigned char *new_sha1,
>                                   const unsigned char *old_sha1,
> @@ -3267,6 +3271,8 @@ static int files_transaction_update(struct ref_transaction *transaction,
>                                   struct strbuf *err)
>  {
>         struct ref_update *update;
> +       struct files_ref_transaction *transaction =
> +               (struct files_ref_transaction *)trans;
>
>         assert(err);
>
> @@ -3350,10 +3356,12 @@ static int ref_update_reject_duplicates(struct string_list *refnames,
>         return 0;
>  }
>
> -static int files_transaction_commit(struct ref_transaction *transaction,
> +static int files_transaction_commit(struct ref_transaction *trans,
>                                   struct strbuf *err)
>  {
>         int ret = 0, i;
> +       struct files_ref_transaction *transaction =
> +               (struct files_ref_transaction *)trans;
>         int n = transaction->nr;
>         struct ref_update **updates = transaction->updates;
>         struct string_list refs_to_delete = STRING_LIST_INIT_NODUP;
> @@ -3517,10 +3525,12 @@ static int ref_present(const char *refname,
>         return string_list_has_string(affected_refnames, refname);
>  }
>
> -static int files_initial_transaction_commit(struct ref_transaction *transaction,
> +static int files_initial_transaction_commit(struct ref_transaction *trans,
>                                             struct strbuf *err)
>  {
>         int ret = 0, i;
> +       struct files_ref_transaction *transaction =
> +               (struct files_ref_transaction *)trans;
>         int n = transaction->nr;
>         struct ref_update **updates = transaction->updates;
>         struct string_list affected_refnames = STRING_LIST_INIT_NODUP;
> diff --git a/refs.h b/refs.h
> index 64dba64..02001ef 100644
> --- a/refs.h
> +++ b/refs.h
> @@ -130,7 +130,7 @@ extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref);
>   *
>   * Calling sequence
>   * ----------------
> - * - Allocate and initialize a `struct ref_transaction` by calling
> + * - Allocate and initialize a transaction by calling
>   *   `ref_transaction_begin()`.
>   *
>   * - List intended ref updates by calling functions like
> @@ -156,7 +156,10 @@ extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref);
>   * The message is appended to err without first clearing err.
>   * err will not be '\n' terminated.
>   */
> -struct ref_transaction;
> +
> +struct ref_transaction {
> +       /* ref backends should extend this */
> +};
>
>  /*
>   * Bit values set in the flags argument passed to each_ref_fn():
> @@ -629,6 +632,7 @@ typedef int (*reflog_expire_fn)(const char *refname, const unsigned char *sha1,
>                                 void *policy_cb_data);
>
>  /* resolution functions */
> +typedef void (*ref_transaction_free_fn)(struct ref_transaction *transaction);
>  typedef const char *(*resolve_ref_unsafe_fn)(const char *ref,
>                                              int resolve_flags,
>                                              unsigned char *sha1, int *flags);
> --
> 2.4.2.644.g97b850b-twtrsrc
>
> --
> To unsubscribe from this list: send the line "unsubscribe git" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
David Turner· Oct 6, 2015, 17:53 UTC · re: Michael Blume · lore

Re: [PATCH v2 34/43] refs.c: make struct ref_transaction generic

On Tue, 2015-10-06 at 10:43 -0700, Michael Blume wrote:
Show 13 quoted lines
> This triggers a lot of build warnings on my mac, basically in every
> file that uses refs.h:
> 
>     CC archive.o
> In file included from archive.c:2:
> ./refs.h:635:16: warning: redefinition of typedef
> 'ref_transaction_free_fn' is a C11 feature [-Wtypedef-redefinition]
> typedef void (*ref_transaction_free_fn)(struct ref_transaction *transaction);
>                ^
> ./refs.h:613:16: note: previous definition is here
> typedef void (*ref_transaction_free_fn)(struct ref_transaction *transaction);
>                ^
> 1 warning generated.
Thanks. I'll fix that when I reroll.
David Turner· Sep 28, 2015, 22:02 UTC · re: David Turner · lore

[PATCH v2 35/43] refs-be-files.c: add method to rename refs

We also have to make rename_ref_available public, since alternate backends for rename_ref will need it.

Signed-off-by: David Turner <dturner@twopensource.com>
---
 refs-be-files.c | 20 +++-----------------
 refs.c          | 21 +++++++++++++++++++++
 refs.h          |  7 ++++++-
 3 files changed, 30 insertions(+), 18 deletions(-)
Show changes to 3 files +30 −18

refs-be-files.c, refs.c, refs.h

diff --git a/refs-be-files.c b/refs-be-files.c
index 3f2d194..3bcfab3 100644
--- a/refs-be-files.c
+++ b/refs-be-files.c
@@ -2491,29 +2491,14 @@ out:
 	return ret;
 }
 
-static int rename_ref_available(const char *oldname, const char *newname)
-{
-	struct string_list skip = STRING_LIST_INIT_NODUP;
-	struct strbuf err = STRBUF_INIT;
-	int ret;
-
-	string_list_insert(&skip, oldname);
-	ret = !verify_refname_available(newname, NULL, &skip, &err);
-	if (!ret)
-		error("%s", err.buf);
-
-	string_list_clear(&skip, 0);
-	strbuf_release(&err);
-	return ret;
-}
-
 static int write_ref_to_lockfile(struct ref_lock *lock,
 				 const unsigned char *sha1, struct strbuf *err);
 static int commit_ref_update(struct ref_lock *lock,
 			     const unsigned char *sha1, const char *logmsg,
 			     int flags, struct strbuf *err);
 
-int rename_ref(const char *oldrefname, const char *newrefname, const char *logmsg)
+static int files_rename_ref(const char *oldrefname, const char *newrefname,
+			    const char *logmsg)
 {
 	unsigned char sha1[20], orig_sha1[20];
 	int flag = 0, logmoved = 0;
@@ -3777,6 +3762,7 @@ struct ref_be refs_be_files = {
 	files_transaction_commit,
 	files_initial_transaction_commit,
 	files_transaction_free,
+	files_rename_ref,
 	files_for_each_reflog_ent,
 	files_for_each_reflog_ent_reverse,
 	files_for_each_reflog,
diff --git a/refs.c b/refs.c
index 930c85c..1c2dd79 100644
--- a/refs.c
+++ b/refs.c
@@ -1052,6 +1052,11 @@ void ref_transaction_free(struct ref_transaction *transaction)
 	return the_refs_backend->transaction_free(transaction);
 }
 
+int rename_ref(const char *oldref, const char *newref, const char *logmsg)
+{
+	return the_refs_backend->rename_ref(oldref, newref, logmsg);
+}
+
 const char *resolve_ref_unsafe(const char *ref, int resolve_flags,
 			       unsigned char *sha1, int *flags)
 {
@@ -1066,6 +1071,22 @@ int verify_refname_available(const char *refname, struct string_list *extra,
 							  err);
 }
 
+int rename_ref_available(const char *oldname, const char *newname)
+{
+	struct string_list skip = STRING_LIST_INIT_NODUP;
+	struct strbuf err = STRBUF_INIT;
+	int ret;
+
+	string_list_insert(&skip, oldname);
+	ret = !verify_refname_available(newname, NULL, &skip, &err);
+	if (!ret)
+		error("%s", err.buf);
+
+	string_list_clear(&skip, 0);
+	strbuf_release(&err);
+	return ret;
+}
+
 int pack_refs(unsigned int flags)
 {
 	return the_refs_backend->pack_refs(flags);
diff --git a/refs.h b/refs.h
index 02001ef..823983b 100644
--- a/refs.h
+++ b/refs.h
@@ -378,10 +378,13 @@ extern const char *prettify_refname(const char *refname);
 extern char *shorten_unambiguous_ref(const char *refname, int strict);
 
 /** rename ref, return 0 on success **/
-extern int rename_ref(const char *oldref, const char *newref, const char *logmsg);
+extern int rename_ref(const char *oldref, const char *newref,
+		      const char *logmsg);
 
 extern int create_symref(const char *ref, const char *refs_heads_master, const char *logmsg);
 
+int rename_ref_available(const char *oldname, const char *newname);
+
 enum action_on_err {
 	UPDATE_REFS_MSG_ON_ERR,
 	UPDATE_REFS_DIE_ON_ERR,
@@ -633,6 +636,7 @@ typedef int (*reflog_expire_fn)(const char *refname, const unsigned char *sha1,
 
 /* resolution functions */
 typedef void (*ref_transaction_free_fn)(struct ref_transaction *transaction);
+typedef int (*rename_ref_fn)(const char *oldref, const char *newref, const char *logmsg);
 typedef const char *(*resolve_ref_unsafe_fn)(const char *ref,
 					     int resolve_flags,
 					     unsigned char *sha1, int *flags);
@@ -682,6 +686,7 @@ struct ref_be {
 	ref_transaction_commit_fn transaction_commit;
 	ref_transaction_commit_fn initial_transaction_commit;
 	ref_transaction_free_fn transaction_free;
+	rename_ref_fn rename_ref;
 	for_each_reflog_ent_fn for_each_reflog_ent;
 	for_each_reflog_ent_reverse_fn for_each_reflog_ent_reverse;
 	for_each_reflog_fn for_each_reflog;
-- 
2.4.2.644.g97b850b-twtrsrc
David Turner· Sep 28, 2015, 22:02 UTC · re: David Turner · lore

[PATCH v2 36/43] run-command: track total number of commands run

Soon, the db refs backend will use this to restart read transactions after subcommands which might have modified the refs database.

Signed-off-by: David Turner <dturner@twopensource.com>
---
 run-command.c | 2 ++
 run-command.h | 1 +
 2 files changed, 3 insertions(+)
Show changes to 2 files +3 −0

run-command.c, run-command.h

diff --git a/run-command.c b/run-command.c
index ceee5d1..9ee6c85 100644
--- a/run-command.c
+++ b/run-command.c
@@ -19,6 +19,7 @@ struct child_to_clean {
 };
 static struct child_to_clean *children_to_clean;
 static int installed_child_cleanup_handler;
+int total_commands_run = 0;
 
 static void cleanup_children(int sig, int in_signal)
 {
@@ -349,6 +350,7 @@ fail_pipe:
 		cmd->err = fderr[0];
 	}
 
+	total_commands_run++;
 	trace_argv_printf(cmd->argv, "trace: run_command:");
 	fflush(NULL);
 
diff --git a/run-command.h b/run-command.h
index aa2a528..ef7ae7a 100644
--- a/run-command.h
+++ b/run-command.h
@@ -158,4 +158,5 @@ int run_processes_parallel(int n, void *data,
 			   start_failure_fn,
 			   return_value_fn);
 
+extern int total_commands_run;
 #endif
-- 
2.4.2.644.g97b850b-twtrsrc
David Turner· Sep 28, 2015, 22:02 UTC · re: David Turner · lore

[PATCH v2 37/43] refs: move some defines from refs-be-files.c to refs.h

This allows them to be used by other ref backends.
Signed-off-by: David Turner <dturner@twopensource.com>
---
 refs-be-files.c | 24 ------------------------
 refs.h          | 24 ++++++++++++++++++++++++
 2 files changed, 24 insertions(+), 24 deletions(-)
Show changes to 2 files +24 −24

refs-be-files.c, refs.h

diff --git a/refs-be-files.c b/refs-be-files.c
index 3bcfab3..2727943 100644
--- a/refs-be-files.c
+++ b/refs-be-files.c
@@ -14,30 +14,6 @@ struct ref_lock {
 };
 
 /*
- * Flag passed to lock_ref_sha1_basic() telling it to tolerate broken
- * refs (i.e., because the reference is about to be deleted anyway).
- */
-#define REF_DELETING	0x02
-
-/*
- * Used as a flag in ref_update::flags when a loose ref is being
- * pruned.
- */
-#define REF_ISPRUNING	0x04
-
-/*
- * Used as a flag in ref_update::flags when the reference should be
- * updated to new_sha1.
- */
-#define REF_HAVE_NEW	0x08
-
-/*
- * Used as a flag in ref_update::flags when old_sha1 should be
- * checked.
- */
-#define REF_HAVE_OLD	0x10
-
-/*
  * Used as a flag in ref_update::flags when the lockfile needs to be
  * committed.
  */
diff --git a/refs.h b/refs.h
index 823983b..d8ae9af 100644
--- a/refs.h
+++ b/refs.h
@@ -193,6 +193,30 @@ struct ref_transaction {
 #define DO_FOR_EACH_PER_WORKTREE_ONLY 0x02
 
 /*
+ * Flag passed to lock_ref_sha1_basic() telling it to tolerate broken
+ * refs (i.e., because the reference is about to be deleted anyway).
+ */
+#define REF_DELETING	0x02
+
+/*
+ * Used as a flag in ref_update::flags when a loose ref is being
+ * pruned.
+ */
+#define REF_ISPRUNING	0x04
+
+/*
+ * Used as a flag in ref_update::flags when the reference should be
+ * updated to new_sha1.
+ */
+#define REF_HAVE_NEW	0x08
+
+/*
+ * Used as a flag in ref_update::flags when old_sha1 should be
+ * checked.
+ */
+#define REF_HAVE_OLD	0x10
+
+/*
  * The signature for the callback function for the for_each_*()
  * functions below.  The memory pointed to by the refname and sha1
  * arguments is only guaranteed to be valid for the duration of a
-- 
2.4.2.644.g97b850b-twtrsrc
Michael Haggerty· Oct 5, 2015, 08:57 UTC · re: David Turner · lore

Re: [PATCH v2 37/43] refs: move some defines from refs-be-files.c to refs.h

On 09/29/2015 12:02 AM, David Turner wrote:
Show 34 quoted lines
> This allows them to be used by other ref backends.
> 
> Signed-off-by: David Turner <dturner@twopensource.com>
> ---
>  refs-be-files.c | 24 ------------------------
>  refs.h          | 24 ++++++++++++++++++++++++
>  2 files changed, 24 insertions(+), 24 deletions(-)
> 
> diff --git a/refs-be-files.c b/refs-be-files.c
> index 3bcfab3..2727943 100644
> --- a/refs-be-files.c
> +++ b/refs-be-files.c
> @@ -14,30 +14,6 @@ struct ref_lock {
>  };
>  
>  /*
> - * Flag passed to lock_ref_sha1_basic() telling it to tolerate broken
> - * refs (i.e., because the reference is about to be deleted anyway).
> - */
> -#define REF_DELETING	0x02
> [...]
> diff --git a/refs.h b/refs.h
> index 823983b..d8ae9af 100644
> --- a/refs.h
> +++ b/refs.h
> @@ -193,6 +193,30 @@ struct ref_transaction {
>  #define DO_FOR_EACH_PER_WORKTREE_ONLY 0x02
>  
>  /*
> + * Flag passed to lock_ref_sha1_basic() telling it to tolerate broken
> + * refs (i.e., because the reference is about to be deleted anyway).
> + */
> +#define REF_DELETING	0x02
> [...]

The fact that these constants might be used by multiple reference backends is not a reason to put them in the ref API's public interface. We don't want random code using these contants, right?

Maybe there should be a new file pair, refs-be-shared.{c,h}, for "protected" constants and functions for the internal use of the reference backends. In fact, maybe `struct ref_be` and the associated typedefs could even be moved there.

Probably some of the functions that you have made public earlier in this series could also go into the "protected" area.

Michael
-- 
Michael Haggerty
mhagger@alum.mit.edu
David Turner· Sep 28, 2015, 22:02 UTC · re: David Turner · lore

[PATCH v2 38/43] refs: make some files backend functions public

Because HEAD and stash are per-worktree, other backends need to go through the files backend to manage these refs and their reflogs.

To enable this, we make some files backend functions public.
Signed-off-by: David Turner <dturner@twopensource.com>
---
 refs-be-files.c | 34 +++++++++++++++++++++-------------
 refs.h          | 20 ++++++++++++++++++++
 2 files changed, 41 insertions(+), 13 deletions(-)
Show changes to 2 files +41 −13

refs-be-files.c, refs.h

diff --git a/refs-be-files.c b/refs-be-files.c
index 2727943..943604c 100644
--- a/refs-be-files.c
+++ b/refs-be-files.c
@@ -2727,6 +2727,14 @@ static int log_ref_write(const char *refname, const unsigned char *old_sha1,
 			 const unsigned char *new_sha1, const char *msg,
 			 int flags, struct strbuf *err)
 {
+	return files_log_ref_write(refname, old_sha1, new_sha1, msg, flags,
+				   err);
+}
+
+int files_log_ref_write(const char *refname, const unsigned char *old_sha1,
+			const unsigned char *new_sha1, const char *msg,
+			int flags, struct strbuf *err)
+{
 	struct strbuf sb = STRBUF_INIT;
 	int ret = log_ref_write_1(refname, old_sha1, new_sha1, msg, &sb, flags,
 				  err);
@@ -2902,7 +2910,7 @@ static int files_create_symref(struct ref_transaction *trans,
 	return 0;
 }
 
-static int files_reflog_exists(const char *refname)
+int files_reflog_exists(const char *refname)
 {
 	struct stat st;
 
@@ -2910,7 +2918,7 @@ static int files_reflog_exists(const char *refname)
 		S_ISREG(st.st_mode);
 }
 
-static int files_delete_reflog(const char *refname)
+int files_delete_reflog(const char *refname)
 {
 	return remove_path(git_path("logs/%s", refname));
 }
@@ -2954,9 +2962,9 @@ static char *find_beginning_of_line(char *bob, char *scan)
 	return scan;
 }
 
-static int files_for_each_reflog_ent_reverse(const char *refname,
-					     each_reflog_ent_fn fn,
-					     void *cb_data)
+int files_for_each_reflog_ent_reverse(const char *refname,
+				      each_reflog_ent_fn fn,
+				      void *cb_data)
 {
 	struct strbuf sb = STRBUF_INIT;
 	FILE *logfp;
@@ -3058,8 +3066,8 @@ static int files_for_each_reflog_ent_reverse(const char *refname,
 	return ret;
 }
 
-static int files_for_each_reflog_ent(const char *refname,
-				     each_reflog_ent_fn fn, void *cb_data)
+int files_for_each_reflog_ent(const char *refname,
+			      each_reflog_ent_fn fn, void *cb_data)
 {
 	FILE *logfp;
 	struct strbuf sb = STRBUF_INIT;
@@ -3604,12 +3612,12 @@ static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
 	return 0;
 }
 
-static int files_reflog_expire(const char *refname, const unsigned char *sha1,
-			       unsigned int flags,
-			       reflog_expiry_prepare_fn prepare_fn,
-			       reflog_expiry_should_prune_fn should_prune_fn,
-			       reflog_expiry_cleanup_fn cleanup_fn,
-			       void *policy_cb_data)
+int files_reflog_expire(const char *refname, const unsigned char *sha1,
+			unsigned int flags,
+			reflog_expiry_prepare_fn prepare_fn,
+			reflog_expiry_should_prune_fn should_prune_fn,
+			reflog_expiry_cleanup_fn cleanup_fn,
+			void *policy_cb_data)
 {
 	static struct lock_file reflog_lock;
 	struct expire_reflog_cb cb;
diff --git a/refs.h b/refs.h
index d8ae9af..da29232 100644
--- a/refs.h
+++ b/refs.h
@@ -571,6 +571,10 @@ enum ref_type ref_type(const char *refname);
 
 int copy_reflog_msg(char *buf, const char *msg);
 
+int files_log_ref_write(const char *refname, const unsigned char *old_sha1,
+			const unsigned char *new_sha1, const char *msg,
+			int flags, struct strbuf *err);
+
 enum expire_reflog_flags {
 	EXPIRE_REFLOGS_DRY_RUN = 1 << 0,
 	EXPIRE_REFLOGS_UPDATE_REF = 1 << 1,
@@ -604,6 +608,22 @@ typedef int reflog_expiry_should_prune_fn(unsigned char *osha1,
 					  const char *message, void *cb_data);
 typedef void reflog_expiry_cleanup_fn(void *cb_data);
 
+int files_reflog_expire(const char *refname, const unsigned char *sha1,
+			unsigned int flags,
+			reflog_expiry_prepare_fn prepare_fn,
+			reflog_expiry_should_prune_fn should_prune_fn,
+			reflog_expiry_cleanup_fn cleanup_fn,
+			void *policy_cb_data);
+
+int files_for_each_reflog_ent(const char *refname,
+			      each_reflog_ent_fn fn, void *cb_data);
+
+int files_for_each_reflog_ent_reverse(const char *refname,
+				      each_reflog_ent_fn fn, void *cb_data);
+
+int files_delete_reflog(const char *refname);
+int files_reflog_exists(const char *refname);
+
 /*
  * Expire reflog entries for the specified reference. sha1 is the old
  * value of the reference. flags is a combination of the constants in
-- 
2.4.2.644.g97b850b-twtrsrc
Michael Haggerty· Oct 5, 2015, 09:03 UTC · re: David Turner · lore

Re: [PATCH v2 38/43] refs: make some files backend functions public

On 09/29/2015 12:02 AM, David Turner wrote:
> Because HEAD and stash are per-worktree, other backends need to
> go through the files backend to manage these refs and their reflogs.
> 
> To enable this, we make some files backend functions public.
I have a bad feeling about this change.

Naively I would expect a reference backend that cannot handle its own (e.g.) stash to instantiate internally a files backend object and to delegate stash-related calls to that object. That way neither class's interface has to be changed.

Here you are adding a separate interface to the files backend. That seems like a more complicated and less flexible design. But I'm open to be persuaded otherwise...

Michael
-- 
Michael Haggerty
mhagger@alum.mit.edu
David Turner· Oct 6, 2015, 01:24 UTC · re: Michael Haggerty · lore

Re: [PATCH v2 38/43] refs: make some files backend functions public

On Mon, 2015-10-05 at 11:03 +0200, Michael Haggerty wrote:
Show 16 quoted lines
> On 09/29/2015 12:02 AM, David Turner wrote:
> > Because HEAD and stash are per-worktree, other backends need to
> > go through the files backend to manage these refs and their reflogs.
> > 
> > To enable this, we make some files backend functions public.
> 
> I have a bad feeling about this change.
> 
> Naively I would expect a reference backend that cannot handle its own
> (e.g.) stash to instantiate internally a files backend object and to
> delegate stash-related calls to that object. That way neither class's
> interface has to be changed.
> 
> Here you are adding a separate interface to the files backend. That
> seems like a more complicated and less flexible design. But I'm open to
> be persuaded otherwise...
OK, I've switched over most of these to use an internal files backend.

While doing so, I thought I needed to keep files_log_ref_write, because we need to handle refs that cross over between the lmdb backend and the files backend.

But on reflection, I have realized that I'm doing cross-backend refs wrong anyway (I'm prematurely committing the files backend ones, and doing it without locks). So I think I need to rethink this a bit. Probably I can split out a files transaction using REF_NODEREF or something.

Will do something about this.
David Turner· Oct 7, 2015, 01:25 UTC · re: Michael Haggerty · lore

Re: [PATCH v2 38/43] refs: make some files backend functions public

On Mon, 2015-10-05 at 11:03 +0200, Michael Haggerty wrote:
Show 16 quoted lines
> On 09/29/2015 12:02 AM, David Turner wrote:
> > Because HEAD and stash are per-worktree, other backends need to
> > go through the files backend to manage these refs and their reflogs.
> > 
> > To enable this, we make some files backend functions public.
> 
> I have a bad feeling about this change.
> 
> Naively I would expect a reference backend that cannot handle its own
> (e.g.) stash to instantiate internally a files backend object and to
> delegate stash-related calls to that object. That way neither class's
> interface has to be changed.
> 
> Here you are adding a separate interface to the files backend. That
> seems like a more complicated and less flexible design. But I'm open to
> be persuaded otherwise...
After some thought, here's a summary of the problem:

Some writes are cross-backend writes. For example, if HEAD is symref to refs/head/master, a commit is a cross-backend write (HEAD itself is not updated, but its reflog is). Ronnie's design of the ref backend structure did not account for cross-backend writes, because we didn't have per-worktree refs at the time (there was only HEAD, and there was only one copy of it).

Cross-backend writes are complicated because there is no way to tell a backend to do only part of a ref update -- for instance, to tell the files backend to update HEAD and HEAD's reflog but not refs/heads/master. Maybe we could set a flag that would do this, but the synchronization would be fairly complicated. For instance, an update to HEAD might need to confirm the old sha for HEAD, meaning that we couldn't do the db write first. But if we move the db write second, then when the db code goes to do its check of the HEAD sha, it might see a new value. Perhaps there's a way to make it work, but it seems fragile/complex.

Right now, for cross-backend reads/writes, the lmdb code cheats. It simply does the write directly and immediately. This means that these portions of transactions cannot be rolled back. That's clearly bad.

The simplest solution would be for the lmdb code to simply acquire locks, and write to lock files, and then commit those lock files just before the db transaction commits. Then the lmdb code would handle all of the orchestration without the files backend having to be rewritten to handle this case.

We would still like to use files_log_ref_write, since that handles the formatting details of writing to file-based reflogs. If you want, we could break that (and its callees) out to separate refs-be-common.[ch] files, as you suggested for some #defines. (The calls to files_log_ref_write would also have to move to transaction_commit as well, but I think that's straightforward).

Does this seem reasonable to you?
Michael Haggerty· Oct 7, 2015, 16:00 UTC · re: David Turner · lore

Re: [PATCH v2 38/43] refs: make some files backend functions public

On 10/07/2015 03:25 AM, David Turner wrote:
Show 41 quoted lines
> On Mon, 2015-10-05 at 11:03 +0200, Michael Haggerty wrote:
>> On 09/29/2015 12:02 AM, David Turner wrote:
>>> Because HEAD and stash are per-worktree, other backends need to
>>> go through the files backend to manage these refs and their reflogs.
>>>
>>> To enable this, we make some files backend functions public.
>>
>> I have a bad feeling about this change.
>>
>> Naively I would expect a reference backend that cannot handle its own
>> (e.g.) stash to instantiate internally a files backend object and to
>> delegate stash-related calls to that object. That way neither class's
>> interface has to be changed.
>>
>> Here you are adding a separate interface to the files backend. That
>> seems like a more complicated and less flexible design. But I'm open to
>> be persuaded otherwise...
> 
> After some thought, here's a summary of the problem:
> 
> Some writes are cross-backend writes.  For example, if HEAD is symref to
> refs/head/master, a commit is a cross-backend write (HEAD itself is not
> updated, but its reflog is).  Ronnie's design of the ref backend
> structure did not account for cross-backend writes, because we didn't
> have per-worktree refs at the time (there was only HEAD, and there was
> only one copy of it).
> 
> Cross-backend writes are complicated because there is no way to tell a
> backend to do only part of a ref update -- for instance, to tell the
> files backend to update HEAD and HEAD's reflog but not
> refs/heads/master.  Maybe we could set a flag that would do this, but
> the synchronization would be fairly complicated.  For instance, an
> update to HEAD might need to confirm the old sha for HEAD, meaning that
> we couldn't do the db write first.  But if we move the db write second,
> then when the db code goes to do its check of the HEAD sha, it might see
> a new value.  Perhaps there's a way to make it work, but it seems
> fragile/complex.
> 
> Right now, for cross-backend reads/writes, the lmdb code cheats. It
> simply does the write directly and immediately.  This means that these
> portions of transactions cannot be rolled back.  That's clearly bad. 
That's a really good point.

I hate to break it to you, but the handling of symrefs in Git is already a mess. HEAD is the only symref that I would really trust to work correctly all the time. So I think that changes needn't be judged on whether they handle symrefs perfectly. They should just not break them in any dramatic new ways.

So, you pointed out the problem that HEAD (a per-worktree reference) can be a symref that points at a shared reference. In fact, I think when HEAD is symbolic it is only allowed to point at a branch under refs/heads, so this particular problem is pretty well-constrained.

Are there other cases of cross-backend writes? I suppose there could be a symref elsewhere among the per-worktree references that points at a shared reference. But I can't think of any cases where this is done by standard Git. Not that it is forbidden; I just don't think it is done by any of the standard tools.

Or there could be a symref among the shared references that points at a per-worktree reference. But AFAIK the only other symrefs that are in common use are the refs/remotes/*/HEAD symrefs, and they always point at references within the same (shared) namespace.

If everything that I've said is correct, then my opinion is that it would be perfectly adequate if your code would handle the specific case of HEAD (by hook or by crook), and if there are any other cross-backend symrefs, just die with a message stating that such usage is unsupported. Junio, do you think that would be acceptable?

Show 5 quoted lines
> The simplest solution would be for the lmdb code to simply acquire
> locks, and write to lock files, and then commit those lock files just
> before the db transaction commits. Then the lmdb code would handle all
> of the orchestration without the files backend having to be rewritten to
> handle this case.

Wouldn't that essentially be re-implementing the files backend? I must be missing something.

> [...]

BTW I just realized that if one backend should delegate to another, then the primary backend should be the per-worktree backend and it should delegate to the common backend. I think I described things the other way around in my earlier message. This makes more sense because it is acceptable for per-worktree references to refer to common references but not vice versa.

Michael
-- 
Michael Haggerty
mhagger@alum.mit.edu
Junio C Hamano· Oct 7, 2015, 17:20 UTC · re: Michael Haggerty · lore

Re: [PATCH v2 38/43] refs: make some files backend functions public

Michael Haggerty <mhagger@alum.mit.edu> writes:
Show 17 quoted lines
>> Right now, for cross-backend reads/writes, the lmdb code cheats. It
>> simply does the write directly and immediately.  This means that these
>> portions of transactions cannot be rolled back.  That's clearly bad. 
>
> That's a really good point.
>
> I hate to break it to you, but the handling of symrefs in Git is already
> a mess. HEAD is the only symref that I would really trust to work
> correctly all the time. So I think that changes needn't be judged on
> whether they handle symrefs perfectly. They should just not break them
> in any dramatic new ways.
> ...
> If everything that I've said is correct, then my opinion is that it
> would be perfectly adequate if your code would handle the specific case
> of HEAD (by hook or by crook), and if there are any other cross-backend
> symrefs, just die with a message stating that such usage is unsupported.
> Junio, do you think that would be acceptable?
I find it a very sensible approach.
Show 6 quoted lines
> BTW I just realized that if one backend should delegate to another, then
> the primary backend should be the per-worktree backend and it should
> delegate to the common backend. I think I described things the other way
> around in my earlier message. This makes more sense because it is
> acceptable for per-worktree references to refer to common references but
> not vice versa.
Sounds good.
Thanks.
David Turner· Oct 7, 2015, 20:55 UTC · re: Michael Haggerty · lore

Re: [PATCH v2 38/43] refs: make some files backend functions public

On Wed, 2015-10-07 at 18:00 +0200, Michael Haggerty wrote:
Show 61 quoted lines
> On 10/07/2015 03:25 AM, David Turner wrote:
> > On Mon, 2015-10-05 at 11:03 +0200, Michael Haggerty wrote:
> >> On 09/29/2015 12:02 AM, David Turner wrote:
> >>> Because HEAD and stash are per-worktree, other backends need to
> >>> go through the files backend to manage these refs and their reflogs.
> >>>
> >>> To enable this, we make some files backend functions public.
> >>
> >> I have a bad feeling about this change.
> >>
> >> Naively I would expect a reference backend that cannot handle its own
> >> (e.g.) stash to instantiate internally a files backend object and to
> >> delegate stash-related calls to that object. That way neither class's
> >> interface has to be changed.
> >>
> >> Here you are adding a separate interface to the files backend. That
> >> seems like a more complicated and less flexible design. But I'm open to
> >> be persuaded otherwise...
> > 
> > After some thought, here's a summary of the problem:
> > 
> > Some writes are cross-backend writes.  For example, if HEAD is symref to
> > refs/head/master, a commit is a cross-backend write (HEAD itself is not
> > updated, but its reflog is).  Ronnie's design of the ref backend
> > structure did not account for cross-backend writes, because we didn't
> > have per-worktree refs at the time (there was only HEAD, and there was
> > only one copy of it).
> > 
> > Cross-backend writes are complicated because there is no way to tell a
> > backend to do only part of a ref update -- for instance, to tell the
> > files backend to update HEAD and HEAD's reflog but not
> > refs/heads/master.  Maybe we could set a flag that would do this, but
> > the synchronization would be fairly complicated.  For instance, an
> > update to HEAD might need to confirm the old sha for HEAD, meaning that
> > we couldn't do the db write first.  But if we move the db write second,
> > then when the db code goes to do its check of the HEAD sha, it might see
> > a new value.  Perhaps there's a way to make it work, but it seems
> > fragile/complex.
> > 
> > Right now, for cross-backend reads/writes, the lmdb code cheats. It
> > simply does the write directly and immediately.  This means that these
> > portions of transactions cannot be rolled back.  That's clearly bad. 
> 
> That's a really good point.
> 
> I hate to break it to you, but the handling of symrefs in Git is already
> a mess. HEAD is the only symref that I would really trust to work
> correctly all the time. So I think that changes needn't be judged on
> whether they handle symrefs perfectly. They should just not break them
> in any dramatic new ways.
> 
> So, you pointed out the problem that HEAD (a per-worktree reference) can
> be a symref that points at a shared reference. In fact, I think when
> HEAD is symbolic it is only allowed to point at a branch under
> refs/heads, so this particular problem is pretty well-constrained.
> 
> Are there other cases of cross-backend writes? I suppose there could be
> a symref elsewhere among the per-worktree references that points at a
> shared reference. But I can't think of any cases where this is done by
> standard Git. Not that it is forbidden; I just don't think it is done by
> any of the standard tools.

Another case would be an update-ref command that updates both refs/bisect/something and refs/heads/something.

I don't think git ever does this by default, but anyone can issue a weird update-ref command if they feel like it.

Show 10 quoted lines
> Or there could be a symref among the shared references that points at a
> per-worktree reference. But AFAIK the only other symrefs that are in
> common use are the refs/remotes/*/HEAD symrefs, and they always point at
> references within the same (shared) namespace.
> 
> If everything that I've said is correct, then my opinion is that it
> would be perfectly adequate if your code would handle the specific case
> of HEAD (by hook or by crook), and if there are any other cross-backend
> symrefs, just die with a message stating that such usage is unsupported.
> Junio, do you think that would be acceptable?

Hm. I don't think it's significantly easier to handle just HEAD than it would be to handle all cases. But I'll see what happens as I write the code.

Show 8 quoted lines
> > The simplest solution would be for the lmdb code to simply acquire
> > locks, and write to lock files, and then commit those lock files just
> > before the db transaction commits. Then the lmdb code would handle all
> > of the orchestration without the files backend having to be rewritten to
> > handle this case.
> 
> Wouldn't that essentially be re-implementing the files backend? I must
> be missing something.

There would be some amount of reimplementation, yes. But if we assume that the number of per-worktree refs is relatively small, we could make some simplification. But actually, see below.

Show 8 quoted lines
> > [...]
> 
> BTW I just realized that if one backend should delegate to another, then
> the primary backend should be the per-worktree backend and it should
> delegate to the common backend. I think I described things the other way
> around in my earlier message. This makes more sense because it is
> acceptable for per-worktree references to refer to common references but
> not vice versa.
I think I might have a good way to deal with this:

If we're going to switch the lmdb transaction code over to accumulate updates and then do them as one batch, then probably all other backends will work the same way. So maybe there is no need for all of these backend functions:

	ref_transaction_begin_fn *transaction_begin;
	ref_transaction_update_fn *transaction_update;
	ref_transaction_create_fn *transaction_create;
	ref_transaction_delete_fn *transaction_delete;
	ref_transaction_verify_fn *transaction_verify;

Instead, the generic refs code will accumulate updates in a struct ref_update. Instead of a lock, the ref_update struct will have a void pointer that backends can use for per-update data (such as the lock). The generic code can also handle rejecting duplicate ref updates.

The per-backend transaction_commit method will just take a struct ref_transaction (that is, what the current patchset calls a files_ref_transaction) -- basically, a list of ref_updates -- and attempt to apply it.

While we're doing this, the generic ref code can detect an update to HEAD, and replace it with an update to whatever HEAD points to (if HEAD is a symref). Then it can call files_log_ref_write to write to HEAD's reflog, if the main transaction commits successfully. If HEAD is not a symref, the generic code can just move the HEAD update over to the files backend.

Does this make sense?
Michael Haggerty· Oct 7, 2015, 22:47 UTC · re: David Turner · lore

Re: [PATCH v2 38/43] refs: make some files backend functions public

On 10/07/2015 10:55 PM, David Turner wrote:
Show 26 quoted lines
> On Wed, 2015-10-07 at 18:00 +0200, Michael Haggerty wrote:
>>
>> That's a really good point.
>>
>> I hate to break it to you, but the handling of symrefs in Git is already
>> a mess. HEAD is the only symref that I would really trust to work
>> correctly all the time. So I think that changes needn't be judged on
>> whether they handle symrefs perfectly. They should just not break them
>> in any dramatic new ways.
>>
>> So, you pointed out the problem that HEAD (a per-worktree reference) can
>> be a symref that points at a shared reference. In fact, I think when
>> HEAD is symbolic it is only allowed to point at a branch under
>> refs/heads, so this particular problem is pretty well-constrained.
>>
>> Are there other cases of cross-backend writes? I suppose there could be
>> a symref elsewhere among the per-worktree references that points at a
>> shared reference. But I can't think of any cases where this is done by
>> standard Git. Not that it is forbidden; I just don't think it is done by
>> any of the standard tools.
> 
> Another case would be an update-ref command that updates both
> refs/bisect/something and refs/heads/something.  
> 
> I don't think git ever does this by default, but anyone can issue a
> weird update-ref command if they feel like it.

Oh I was mostly worried about symbolic refs reaching across the divide. If a transaction includes *non-symbolic* refs from both sides I think all you have to do is sort them into two piles and do one transaction for each pile. You would probably have to sacrifice atomicity across the dividing line, but it's a bit unfair to expect atomic updates that span two possibly completely different backends.

OK, you don't know for 100% sure that a reference is a symref before you have locked it. But we've agreed that cross-repo symref support doesn't have to be perfect (except for HEAD).

Show 14 quoted lines
>> Or there could be a symref among the shared references that points at a
>> per-worktree reference. But AFAIK the only other symrefs that are in
>> common use are the refs/remotes/*/HEAD symrefs, and they always point at
>> references within the same (shared) namespace.
>>
>> If everything that I've said is correct, then my opinion is that it
>> would be perfectly adequate if your code would handle the specific case
>> of HEAD (by hook or by crook), and if there are any other cross-backend
>> symrefs, just die with a message stating that such usage is unsupported.
>> Junio, do you think that would be acceptable?
> 
> Hm.  I don't think it's significantly  easier to handle just HEAD than
> it would be to handle all cases.  But I'll see what happens as I write
> the code.

I think the main simplification is having license not to worry about shared symrefs that could theoretically point at per-worktree references. Though I guess that's nonsensical anyway, so maybe it was already obvious that you don't have to handle it.

Show 53 quoted lines
>>> The simplest solution would be for the lmdb code to simply acquire
>>> locks, and write to lock files, and then commit those lock files just
>>> before the db transaction commits. Then the lmdb code would handle all
>>> of the orchestration without the files backend having to be rewritten to
>>> handle this case.
>>
>> Wouldn't that essentially be re-implementing the files backend? I must
>> be missing something.
> 
> There would be some amount of reimplementation, yes.  But if we assume
> that the number of per-worktree refs is relatively small, we could make
> some simplification.  But actually, see below.
> 
>>> [...]
>>
>> BTW I just realized that if one backend should delegate to another, then
>> the primary backend should be the per-worktree backend and it should
>> delegate to the common backend. I think I described things the other way
>> around in my earlier message. This makes more sense because it is
>> acceptable for per-worktree references to refer to common references but
>> not vice versa.
> 
> I think I might have a good way to deal with this:
> 
> If we're going to switch the lmdb transaction code over to accumulate
> updates and then do them as one batch, then probably all other
> backends will work the same way.  So maybe there is no need for all of
> these backend functions:
> 
> 	ref_transaction_begin_fn *transaction_begin;
> 	ref_transaction_update_fn *transaction_update;
> 	ref_transaction_create_fn *transaction_create;
> 	ref_transaction_delete_fn *transaction_delete;
> 	ref_transaction_verify_fn *transaction_verify;
> 
> Instead, the generic refs code will accumulate updates in a struct
> ref_update.  Instead of a lock, the ref_update struct will have a void
> pointer that backends can use for per-update data (such as the lock).
> The generic code can also handle rejecting duplicate ref updates.
> 
> The per-backend transaction_commit method will just take a struct
> ref_transaction (that is, what the current patchset calls a
> files_ref_transaction) -- basically, a list of ref_updates -- and
> attempt to apply it.
> 
> While we're doing this, the generic ref code can detect an update to
> HEAD, and replace it with an update to whatever HEAD points to (if HEAD
> is a symref).  Then it can call files_log_ref_write to write to HEAD's
> reflog, if the main transaction commits successfully.  If HEAD is not a
> symref, the generic code can just move the HEAD update over to the files
> backend.
> 
> Does this make sense?
That makes a lot of sense. I like it.
Michael
-- 
Michael Haggerty
mhagger@alum.mit.edu
David Turner· Sep 28, 2015, 22:02 UTC · re: David Turner · lore

[PATCH v2 39/43] refs: break out a ref conflict check

Create new function verify_no_descendants, to hold one of the ref conflict checks used in verify_refname_available. Multiple backends will need this function, so it goes in the common code.

Signed-off-by: David Turner <dturner@twopensource.com>
---
 refs-be-files.c | 33 ++++++++-------------------------
 refs.c          | 28 ++++++++++++++++++++++++++++
 refs.h          |  4 ++++
 3 files changed, 40 insertions(+), 25 deletions(-)
Show changes to 3 files +40 −25

refs-be-files.c, refs.c, refs.h

diff --git a/refs-be-files.c b/refs-be-files.c
index 943604c..8815111 100644
--- a/refs-be-files.c
+++ b/refs-be-files.c
@@ -753,6 +753,7 @@ static int verify_refname_available_dir(const char *refname,
 					struct strbuf *err)
 {
 	const char *slash;
+	const char *extra_refname;
 	int pos;
 	struct strbuf dirname = STRBUF_INIT;
 	int ret = -1;
@@ -858,33 +859,15 @@ static int verify_refname_available_dir(const char *refname,
 		}
 	}
 
-	if (extras) {
-		/*
-		 * Check for entries in extras that start with
-		 * "$refname/". We do that by looking for the place
-		 * where "$refname/" would be inserted in extras. If
-		 * there is an entry at that position that starts with
-		 * "$refname/" and is not in skip, then we have a
-		 * conflict.
-		 */
-		for (pos = string_list_find_insert_index(extras, dirname.buf, 0);
-		     pos < extras->nr; pos++) {
-			const char *extra_refname = extras->items[pos].string;
-
-			if (!starts_with(extra_refname, dirname.buf))
-				break;
-
-			if (!skip || !string_list_has_string(skip, extra_refname)) {
-				strbuf_addf(err, "cannot process '%s' and '%s' at the same time",
-					    refname, extra_refname);
-				goto cleanup;
-			}
-		}
+	extra_refname = find_descendant_ref(dirname.buf, extras, skip);
+	if (extra_refname) {
+		strbuf_addf(err,
+			    "cannot process '%s' and '%s' at the same time",
+			    refname, extra_refname);
+	} else {
+		ret = 0;
 	}
 
-	/* No conflicts were found */
-	ret = 0;
-
 cleanup:
 	strbuf_release(&dirname);
 	return ret;
diff --git a/refs.c b/refs.c
index 1c2dd79..17a364a 100644
--- a/refs.c
+++ b/refs.c
@@ -994,6 +994,34 @@ enum peel_status peel_object(const unsigned char *name, unsigned char *sha1)
 	return PEEL_PEELED;
 }
 
+const char *find_descendant_ref(const char *refname,
+				const struct string_list *extras,
+				const struct string_list *skip)
+{
+	int pos;
+	if (!extras)
+		return NULL;
+
+	/*
+	 * Check for entries in extras that start with "$refname/". We
+	 * do that by looking for the place where "$refname/" would be
+	 * inserted in extras. If there is an entry at that position
+	 * that starts with "$refname/" and is not in skip, then we
+	 * have a conflict.
+	 */
+	for (pos = string_list_find_insert_index(extras, refname, 0);
+	     pos < extras->nr; pos++) {
+		const char *extra_refname = extras->items[pos].string;
+
+		if (!starts_with(extra_refname, refname))
+			break;
+
+		if (!skip || !string_list_has_string(skip, extra_refname))
+			return extra_refname;
+	}
+	return NULL;
+}
+
 /* backend functions */
 int refs_initdb(struct strbuf *err, int shared)
 {
diff --git a/refs.h b/refs.h
index da29232..cf1780e 100644
--- a/refs.h
+++ b/refs.h
@@ -571,6 +571,10 @@ enum ref_type ref_type(const char *refname);
 
 int copy_reflog_msg(char *buf, const char *msg);
 
+const char *find_descendant_ref(const char *refname,
+				const struct string_list *extras,
+				const struct string_list *skip);
+
 int files_log_ref_write(const char *refname, const unsigned char *old_sha1,
 			const unsigned char *new_sha1, const char *msg,
 			int flags, struct strbuf *err);
-- 
2.4.2.644.g97b850b-twtrsrc
Michael Haggerty· Oct 5, 2015, 09:06 UTC · re: David Turner · lore

Re: [PATCH v2 39/43] refs: break out a ref conflict check

On 09/29/2015 12:02 AM, David Turner wrote:
Show 87 quoted lines
> Create new function verify_no_descendants, to hold one of the ref
> conflict checks used in verify_refname_available.  Multiple backends
> will need this function, so it goes in the common code.
> 
> Signed-off-by: David Turner <dturner@twopensource.com>
> ---
>  refs-be-files.c | 33 ++++++++-------------------------
>  refs.c          | 28 ++++++++++++++++++++++++++++
>  refs.h          |  4 ++++
>  3 files changed, 40 insertions(+), 25 deletions(-)
> 
> diff --git a/refs-be-files.c b/refs-be-files.c
> index 943604c..8815111 100644
> --- a/refs-be-files.c
> +++ b/refs-be-files.c
> @@ -753,6 +753,7 @@ static int verify_refname_available_dir(const char *refname,
>  					struct strbuf *err)
>  {
>  	const char *slash;
> +	const char *extra_refname;
>  	int pos;
>  	struct strbuf dirname = STRBUF_INIT;
>  	int ret = -1;
> @@ -858,33 +859,15 @@ static int verify_refname_available_dir(const char *refname,
>  		}
>  	}
>  
> -	if (extras) {
> -		/*
> -		 * Check for entries in extras that start with
> -		 * "$refname/". We do that by looking for the place
> -		 * where "$refname/" would be inserted in extras. If
> -		 * there is an entry at that position that starts with
> -		 * "$refname/" and is not in skip, then we have a
> -		 * conflict.
> -		 */
> -		for (pos = string_list_find_insert_index(extras, dirname.buf, 0);
> -		     pos < extras->nr; pos++) {
> -			const char *extra_refname = extras->items[pos].string;
> -
> -			if (!starts_with(extra_refname, dirname.buf))
> -				break;
> -
> -			if (!skip || !string_list_has_string(skip, extra_refname)) {
> -				strbuf_addf(err, "cannot process '%s' and '%s' at the same time",
> -					    refname, extra_refname);
> -				goto cleanup;
> -			}
> -		}
> +	extra_refname = find_descendant_ref(dirname.buf, extras, skip);
> +	if (extra_refname) {
> +		strbuf_addf(err,
> +			    "cannot process '%s' and '%s' at the same time",
> +			    refname, extra_refname);
> +	} else {
> +		ret = 0;
>  	}
>  
> -	/* No conflicts were found */
> -	ret = 0;
> -
>  cleanup:
>  	strbuf_release(&dirname);
>  	return ret;
> diff --git a/refs.c b/refs.c
> index 1c2dd79..17a364a 100644
> --- a/refs.c
> +++ b/refs.c
> @@ -994,6 +994,34 @@ enum peel_status peel_object(const unsigned char *name, unsigned char *sha1)
>  	return PEEL_PEELED;
>  }
>  
> +const char *find_descendant_ref(const char *refname,
> +				const struct string_list *extras,
> +				const struct string_list *skip)
> +{
> +	int pos;
> +	if (!extras)
> +		return NULL;
> +
> +	/*
> +	 * Check for entries in extras that start with "$refname/". We
> +	 * do that by looking for the place where "$refname/" would be
> +	 * inserted in extras. If there is an entry at that position
> +	 * that starts with "$refname/" and is not in skip, then we
> +	 * have a conflict.
> +	 */

Would you please turn this comment (or something like it) into a docstring for this function in refs.h?

Show 32 quoted lines
> +	for (pos = string_list_find_insert_index(extras, refname, 0);
> +	     pos < extras->nr; pos++) {
> +		const char *extra_refname = extras->items[pos].string;
> +
> +		if (!starts_with(extra_refname, refname))
> +			break;
> +
> +		if (!skip || !string_list_has_string(skip, extra_refname))
> +			return extra_refname;
> +	}
> +	return NULL;
> +}
> +
>  /* backend functions */
>  int refs_initdb(struct strbuf *err, int shared)
>  {
> diff --git a/refs.h b/refs.h
> index da29232..cf1780e 100644
> --- a/refs.h
> +++ b/refs.h
> @@ -571,6 +571,10 @@ enum ref_type ref_type(const char *refname);
>  
>  int copy_reflog_msg(char *buf, const char *msg);
>  
> +const char *find_descendant_ref(const char *refname,
> +				const struct string_list *extras,
> +				const struct string_list *skip);
> +
>  int files_log_ref_write(const char *refname, const unsigned char *old_sha1,
>  			const unsigned char *new_sha1, const char *msg,
>  			int flags, struct strbuf *err);
> 
Michael
-- 
Michael Haggerty
mhagger@alum.mit.edu
David Turner· Oct 6, 2015, 00:28 UTC · re: Michael Haggerty · lore

Re: [PATCH v2 39/43] refs: break out a ref conflict check

On Mon, 2015-10-05 at 11:06 +0200, Michael Haggerty wrote:
Show 10 quoted lines
> > +	/*
> > +	 * Check for entries in extras that start with "$refname/". We
> > +	 * do that by looking for the place where "$refname/" would be
> > +	 * inserted in extras. If there is an entry at that position
> > +	 * that starts with "$refname/" and is not in skip, then we
> > +	 * have a conflict.
> > +	 */
> 
> Would you please turn this comment (or something like it) into a
> docstring for this function in refs.h?
Yes, thanks.
David Turner· Sep 28, 2015, 22:02 UTC · re: David Turner · lore

[PATCH v2 40/43] refs: allow ref backend to be set for clone

Add a new option, --refs-backend-type, to allow the ref backend type to be set on new clones.

Submodules must use the same ref backend as the parent repository, so we also pass the --refs-backend-type option option when cloning submodules.

Signed-off-by: David Turner <dturner@twopensource.com>
---
 Documentation/git-clone.txt |  4 ++++
 builtin/clone.c             | 27 +++++++++++++++++++++++++--
 builtin/submodule--helper.c |  5 ++++-
 cache.h                     |  1 +
 refs.c                      |  2 ++
 5 files changed, 36 insertions(+), 3 deletions(-)
Show changes to 5 files +36 −3

Documentation/git-clone.txt, builtin/clone.c, builtin/submodule--helper.c, cache.h, refs.c

diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt
index f1f2a3f..d7a4cb0 100644
--- a/Documentation/git-clone.txt
+++ b/Documentation/git-clone.txt
@@ -14,6 +14,7 @@ SYNOPSIS
 	  [-o <name>] [-b <name>] [-u <upload-pack>] [--reference <repository>]
 	  [--dissociate] [--separate-git-dir <git dir>]
 	  [--depth <depth>] [--[no-]single-branch]
+	  [--refs-backend-type=<name>]
 	  [--recursive | --recurse-submodules] [--] <repository>
 	  [<directory>]
 
@@ -216,6 +217,9 @@ objects from the source repository into a pack in the cloned repository.
 	The result is Git repository can be separated from working
 	tree.
 
+--refs-backend-type=<name>::
+	Type of refs backend. Default is to use the original files based
+	backend.
 
 <repository>::
 	The (possibly remote) repository to clone from.  See the
diff --git a/builtin/clone.c b/builtin/clone.c
index 3e14491..d489a87 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -92,11 +92,13 @@ static struct option builtin_clone_options[] = {
 		   N_("separate git dir from working tree")),
 	OPT_STRING_LIST('c', "config", &option_config, N_("key=value"),
 			N_("set config inside the new repository")),
+	OPT_STRING(0, "refs-backend-type", &refs_backend_type,
+		   N_("name"), N_("name of backend type to use")),
 	OPT_END()
 };
 
 static const char *argv_submodule[] = {
-	"submodule", "update", "--init", "--recursive", NULL
+	"submodule", "update", "--init", "--recursive", NULL, NULL
 };
 
 static const char *get_repo_path_1(struct strbuf *path, int *is_bundle)
@@ -724,8 +726,24 @@ static int checkout(void)
 	err |= run_hook_le(NULL, "post-checkout", sha1_to_hex(null_sha1),
 			   sha1_to_hex(sha1), "1", NULL);
 
-	if (!err && option_recursive)
+	if (!err && option_recursive) {
+		const char **backend_arg = argv_submodule;
+		char *new_backend_arg = NULL;
+		if (refs_backend_type) {
+			while (*backend_arg)
+				++backend_arg;
+
+			new_backend_arg = xmalloc(21 + strlen(refs_backend_type));
+			sprintf(new_backend_arg, "--refs-backend-type=%s",
+				refs_backend_type);
+			*backend_arg = new_backend_arg;
+		}
 		err = run_command_v_opt(argv_submodule, RUN_GIT_CMD);
+		if (refs_backend_type) {
+			free(new_backend_arg);
+			*backend_arg = NULL;
+		}
+	}
 
 	return err;
 }
@@ -744,6 +762,11 @@ static void write_config(struct string_list *config)
 					       write_one_config, NULL) < 0)
 			die("unable to write parameters to config file");
 	}
+
+	if (refs_backend_type &&
+	    write_one_config("core.refs-backend-type",
+			     refs_backend_type, NULL) < 0)
+			die("unable to write backend parameter to config file");
 }
 
 static void write_refspec_config(const char *src_ref_prefix,
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index f4c3eff..5c9ca4e 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -140,7 +140,10 @@ static int clone_submodule(const char *path, const char *gitdir, const char *url
 		argv_array_pushl(&cp.args, "--reference", reference, NULL);
 	if (gitdir && *gitdir)
 		argv_array_pushl(&cp.args, "--separate-git-dir", gitdir, NULL);
-
+	if (refs_backend_type && *refs_backend_type) {
+		argv_array_push(&cp.args, "--refs-backend-type");
+		argv_array_push(&cp.args, refs_backend_type);
+	}
 	argv_array_push(&cp.args, url);
 	argv_array_push(&cp.args, path);
 
diff --git a/cache.h b/cache.h
index cc817dc..692cfd3 100644
--- a/cache.h
+++ b/cache.h
@@ -695,6 +695,7 @@ enum object_creation_mode {
 extern enum object_creation_mode object_creation_mode;
 
 extern char *notes_ref_name;
+extern const char *refs_backend_type;
 
 extern int grafts_replace_parents;
 
diff --git a/refs.c b/refs.c
index 17a364a..4f2ab25 100644
--- a/refs.c
+++ b/refs.c
@@ -16,6 +16,8 @@ struct ref_be *the_refs_backend = &refs_be_files;
  */
 struct ref_be *refs_backends = &refs_be_files;
 
+const char *refs_backend_type;
+
 /*
  * This function is used to switch to an alternate backend.
  */
-- 
2.4.2.644.g97b850b-twtrsrc
Michael Haggerty· Oct 5, 2015, 09:32 UTC · re: David Turner · lore

Re: [PATCH v2 40/43] refs: allow ref backend to be set for clone

On 09/29/2015 12:02 AM, David Turner wrote:
Show 6 quoted lines
> Add a new option, --refs-backend-type, to allow the ref backend type to
> be set on new clones.
> 
> Submodules must use the same ref backend as the parent repository, so
> we also pass the --refs-backend-type option option when cloning
> submodules.

If I'm reading this correctly, you propose to add a new configuration setting,

    core.refs-backend-type
to record which reference backend the repository is using.

(By the way, please document any new config settings in Documentation/config.txt.)

This does not provide adequate backwards compatibility. Suppose I create a repository with a DB backend, then later try to access the repository using an older version of Git (or JGit or libgit2 or ...) that doesn't know about this configuration setting. It will think it is a normal repository that only has a couple of references (HEAD, stash, etc). If I run `git gc` using the older version of git, *poof* all of the objects referred to by other branches will be garbage-collected.

Therefore, I don't think this can be merged without a bump to core.repositoryformatversion. Such a bump will tell well-behaved older Git clients keep their hands off the repository. (Of course repositories that use the files backend can continue using core.repositoryformatversion 0.)

I thought Peff proposed a way to do such a bump, including a way to extend repositories one by one with new features. But that was something that we were chatting about off-list.

I haven't reviewed the actual code in this patch yet but I wanted to get the above comment on your radar.

Michael
-- 
Michael Haggerty
mhagger@alum.mit.edu
David Turner· Oct 6, 2015, 01:29 UTC · re: Michael Haggerty · lore

Re: [PATCH v2 40/43] refs: allow ref backend to be set for clone

On Mon, 2015-10-05 at 11:32 +0200, Michael Haggerty wrote:
Show 17 quoted lines
> On 09/29/2015 12:02 AM, David Turner wrote:
> > Add a new option, --refs-backend-type, to allow the ref backend type to
> > be set on new clones.
> > 
> > Submodules must use the same ref backend as the parent repository, so
> > we also pass the --refs-backend-type option option when cloning
> > submodules.
> 
> If I'm reading this correctly, you propose to add a new configuration
> setting,
> 
>     core.refs-backend-type
> 
> to record which reference backend the repository is using.
> 
> (By the way, please document any new config settings in
> Documentation/config.txt.)
Fixed, thanks.
Show 22 quoted lines
> This does not provide adequate backwards compatibility. Suppose I create
> a repository with a DB backend, then later try to access the repository
> using an older version of Git (or JGit or libgit2 or ...) that doesn't
> know about this configuration setting. It will think it is a normal
> repository that only has a couple of references (HEAD, stash, etc). If I
> run `git gc` using the older version of git, *poof* all of the objects
> referred to by other branches will be garbage-collected.
> 
> Therefore, I don't think this can be merged without a bump to
> core.repositoryformatversion. Such a bump will tell well-behaved older
> Git clients keep their hands off the repository. (Of course repositories
> that use the files backend can continue using
> core.repositoryformatversion 0.)
> 
> I thought Peff proposed a way to do such a bump, including a way to
> extend repositories one by one with new features. But that was something
> that we were chatting about off-list.
> 
> I haven't reviewed the actual code in this patch yet but I wanted to get
> the above comment on your radar.
> 
> Michael

I'll fix this to upgrade to v=1 when the lmdb refs backend is in use, and to give sensible error messages in a v1 repo if built without LMDB.

Jeff King· Oct 6, 2015, 01:58 UTC · re: David Turner · lore

Re: [PATCH v2 40/43] refs: allow ref backend to be set for clone

On Mon, Oct 05, 2015 at 09:29:37PM -0400, David Turner wrote:
Show 17 quoted lines
> > Therefore, I don't think this can be merged without a bump to
> > core.repositoryformatversion. Such a bump will tell well-behaved older
> > Git clients keep their hands off the repository. (Of course repositories
> > that use the files backend can continue using
> > core.repositoryformatversion 0.)
> > 
> > I thought Peff proposed a way to do such a bump, including a way to
> > extend repositories one by one with new features. But that was something
> > that we were chatting about off-list.
> > 
> > I haven't reviewed the actual code in this patch yet but I wanted to get
> > the above comment on your radar.
> > 
> > Michael
> 
> I'll fix this to upgrade to v=1 when the lmdb refs backend is in use,
> and to give sensible error messages in a v1 repo if built without LMDB.
I think the relevant series is:
  http://article.gmane.org/gmane.comp.version-control.git/272447

It did not seem too controversial, but it mostly got dropped amidst the release, and I haven't reposted it yet.

-Peff
David Turner· Oct 6, 2015, 18:09 UTC · re: Jeff King · lore

Re: [PATCH v2 40/43] refs: allow ref backend to be set for clone

On Mon, 2015-10-05 at 21:58 -0400, Jeff King wrote:
Show 26 quoted lines
> On Mon, Oct 05, 2015 at 09:29:37PM -0400, David Turner wrote:
> 
> > > Therefore, I don't think this can be merged without a bump to
> > > core.repositoryformatversion. Such a bump will tell well-behaved older
> > > Git clients keep their hands off the repository. (Of course repositories
> > > that use the files backend can continue using
> > > core.repositoryformatversion 0.)
> > > 
> > > I thought Peff proposed a way to do such a bump, including a way to
> > > extend repositories one by one with new features. But that was something
> > > that we were chatting about off-list.
> > > 
> > > I haven't reviewed the actual code in this patch yet but I wanted to get
> > > the above comment on your radar.
> > > 
> > > Michael
> > 
> > I'll fix this to upgrade to v=1 when the lmdb refs backend is in use,
> > and to give sensible error messages in a v1 repo if built without LMDB.
> 
> I think the relevant series is:
> 
>   http://article.gmane.org/gmane.comp.version-control.git/272447
> 
> It did not seem too controversial, but it mostly got dropped amidst the
> release, and I haven't reposted it yet.

That patch will work perfectly for this use case. I'll add it to my series when I reroll, and set an extension.

Carlos Martín Nieto· Oct 12, 2015, 09:00 UTC · re: David Turner · lore

Re: [PATCH v2 40/43] refs: allow ref backend to be set for clone

On Tue, 2015-10-06 at 14:09 -0400, David Turner wrote:
Show 38 quoted lines
> On Mon, 2015-10-05 at 21:58 -0400, Jeff King wrote:
> > On Mon, Oct 05, 2015 at 09:29:37PM -0400, David Turner wrote:
> > 
> > > > Therefore, I don't think this can be merged without a bump to
> > > > core.repositoryformatversion. Such a bump will tell well-
> > > > behaved older
> > > > Git clients keep their hands off the repository. (Of course
> > > > repositories
> > > > that use the files backend can continue using
> > > > core.repositoryformatversion 0.)
> > > > 
> > > > I thought Peff proposed a way to do such a bump, including a
> > > > way to
> > > > extend repositories one by one with new features. But that was
> > > > something
> > > > that we were chatting about off-list.
> > > > 
> > > > I haven't reviewed the actual code in this patch yet but I
> > > > wanted to get
> > > > the above comment on your radar.
> > > > 
> > > > Michael
> > > 
> > > I'll fix this to upgrade to v=1 when the lmdb refs backend is in
> > > use,
> > > and to give sensible error messages in a v1 repo if built without
> > > LMDB.
> > 
> > I think the relevant series is:
> > 
> >   http://article.gmane.org/gmane.comp.version-control.git/272447
> > 
> > It did not seem too controversial, but it mostly got dropped amidst
> > the
> > release, and I haven't reposted it yet.
> 
> That patch will work perfectly for this use case.  I'll add it to my
> series when I reroll, and set an extension.

This is something I'm working on right now for libgit2 as well; not lmdb specifically but allowing user-provided backends, which would allow built-in ones as well.

Did we ever decide on the format for these extensions? The series mentioned above has a couple of examples, but doesn't have any testing for backend stuff. Do we have a concrete proposal for this? I was going to go for something like

extensions.refbackend = "lmdb" extensions.odbbackend = "psql"

and have backends register themselves by the "lmdb", "psql" or whatever format, but if we have already decided something else which I missed, I'd swap over to that.

Cheers,
   cmn
Michael Haggerty· Oct 5, 2015, 11:55 UTC · re: David Turner · lore

Re: [PATCH v2 40/43] refs: allow ref backend to be set for clone

On 09/29/2015 12:02 AM, David Turner wrote:
Show 32 quoted lines
> Add a new option, --refs-backend-type, to allow the ref backend type to
> be set on new clones.
> 
> Submodules must use the same ref backend as the parent repository, so
> we also pass the --refs-backend-type option option when cloning
> submodules.
> 
> Signed-off-by: David Turner <dturner@twopensource.com>
> ---
>  Documentation/git-clone.txt |  4 ++++
>  builtin/clone.c             | 27 +++++++++++++++++++++++++--
>  builtin/submodule--helper.c |  5 ++++-
>  cache.h                     |  1 +
>  refs.c                      |  2 ++
>  5 files changed, 36 insertions(+), 3 deletions(-)
> 
> [...]
> diff --git a/builtin/clone.c b/builtin/clone.c
> index 3e14491..d489a87 100644
> --- a/builtin/clone.c
> +++ b/builtin/clone.c
> [...]
> @@ -744,6 +762,11 @@ static void write_config(struct string_list *config)
>  					       write_one_config, NULL) < 0)
>  			die("unable to write parameters to config file");
>  	}
> +
> +	if (refs_backend_type &&
> +	    write_one_config("core.refs-backend-type",
> +			     refs_backend_type, NULL) < 0)
> +			die("unable to write backend parameter to config file");
>  }

Our config variable names don't use separated-with-hyphens but rather wordsruntogether (though in documentation this would be written core.refsBackendType, because the actual names are case-insensitive).

> [...]
Michael
-- 
Michael Haggerty
mhagger@alum.mit.edu
David Turner· Oct 6, 2015, 01:16 UTC · re: Michael Haggerty · lore

Re: [PATCH v2 40/43] refs: allow ref backend to be set for clone

On Mon, 2015-10-05 at 13:55 +0200, Michael Haggerty wrote:
Show 37 quoted lines
> On 09/29/2015 12:02 AM, David Turner wrote:
> > Add a new option, --refs-backend-type, to allow the ref backend type to
> > be set on new clones.
> > 
> > Submodules must use the same ref backend as the parent repository, so
> > we also pass the --refs-backend-type option option when cloning
> > submodules.
> > 
> > Signed-off-by: David Turner <dturner@twopensource.com>
> > ---
> >  Documentation/git-clone.txt |  4 ++++
> >  builtin/clone.c             | 27 +++++++++++++++++++++++++--
> >  builtin/submodule--helper.c |  5 ++++-
> >  cache.h                     |  1 +
> >  refs.c                      |  2 ++
> >  5 files changed, 36 insertions(+), 3 deletions(-)
> > 
> > [...]
> > diff --git a/builtin/clone.c b/builtin/clone.c
> > index 3e14491..d489a87 100644
> > --- a/builtin/clone.c
> > +++ b/builtin/clone.c
> > [...]
> > @@ -744,6 +762,11 @@ static void write_config(struct string_list *config)
> >  					       write_one_config, NULL) < 0)
> >  			die("unable to write parameters to config file");
> >  	}
> > +
> > +	if (refs_backend_type &&
> > +	    write_one_config("core.refs-backend-type",
> > +			     refs_backend_type, NULL) < 0)
> > +			die("unable to write backend parameter to config file");
> >  }
> 
> Our config variable names don't use separated-with-hyphens but rather
> wordsruntogether (though in documentation this would be written
> core.refsBackendType, because the actual names are case-insensitive).
Will fix, thanks.
David Turner· Sep 28, 2015, 22:02 UTC · re: David Turner · lore

[PATCH v2 41/43] refs: add register_refs_backend

Add register_refs_backend, to allow refs backends to be registered.
Signed-off-by: David Turner <dturner@twopensource.com>
---
 refs.c | 6 ++++++
 refs.h | 1 +
 2 files changed, 7 insertions(+)
Show changes to 2 files +7 −0

refs.c, refs.h

diff --git a/refs.c b/refs.c
index 4f2ab25..4195657 100644
--- a/refs.c
+++ b/refs.c
@@ -18,6 +18,12 @@ struct ref_be *refs_backends = &refs_be_files;
 
 const char *refs_backend_type;
 
+void register_refs_backend(struct ref_be *be)
+{
+	be->next = refs_backends;
+	refs_backends = be;
+}
+
 /*
  * This function is used to switch to an alternate backend.
  */
diff --git a/refs.h b/refs.h
index cf1780e..ac88371 100644
--- a/refs.h
+++ b/refs.h
@@ -764,5 +764,6 @@ struct ref_be {
 
 extern struct ref_be refs_be_files;
 int set_refs_backend(const char *name, void *init_data);
+void register_refs_backend(struct ref_be *be);
 
 #endif /* REFS_H */
-- 
2.4.2.644.g97b850b-twtrsrc
David Turner· Sep 28, 2015, 22:02 UTC · re: David Turner · lore

[PATCH v2 42/43] refs: add LMDB refs backend

Add a database backend for refs using LMDB. This backend runs git for-each-ref about 30% faster than the files backend with fully-packed refs on a repo with ~120k refs. It's also about 4x faster than using fully-unpacked refs. In addition, and perhaps more importantly , it avoids case-conflict issues on OS X.

LMDB has a few features that make it suitable for usage in git:
1. It is relatively lightweight; it requires only one header file, and
the library code takes under 64k at runtime.
2. It is well-tested: it's been used in OpenLDAP for years.
3. It's very fast.  LMDB's benchmarks show that it is among
the fastest key-value stores.
4. It has a relatively simple concurrency story; readers don't
block writers and writers don't block readers.

Ronnie Sahlberg's original version of this patchset used tdb. The major disadvantage of tdb is that tdb is hard to build on OS X. It's also not in homebrew. So lmdb seemed simpler.

To test this backend's correctness, I hacked test-lib.sh and test-lib-functions.sh to run all tests under the refs backend. Dozens of tests use manual ref/reflog reading/writing, or create submodules without passing --refs-backend-type to git init. If those tests are changed to use the update-ref machinery or test-refs-be-db (or, in the case of packed-refs, corrupt refs, and dumb fetch tests, are skipped), the only remaining failing tests are the git-new-workdir tests and the gitweb tests.

Signed-off-by: David Turner <dturner@twopensource.com>
---
 .gitignore                               |    1 +
 Documentation/git-clone.txt              |    2 +-
 Documentation/git-init-db.txt            |    2 +-
 Documentation/git-init.txt               |    6 +
 Documentation/technical/refs-be-lmdb.txt |   39 +
 Makefile                                 |   12 +
 builtin/init-db.c                        |   13 +
 config.c                                 |   27 +
 configure.ac                             |   33 +
 contrib/workdir/git-new-workdir          |    2 +
 environment.c                            |    1 +
 refs-be-lmdb.c                           | 2003 ++++++++++++++++++++++++++++++
 refs.h                                   |   10 +
 setup.c                                  |   28 +-
 test-refs-be-lmdb.c                      |   68 +
 15 files changed, 2239 insertions(+), 8 deletions(-)
 create mode 100644 Documentation/technical/refs-be-lmdb.txt
 create mode 100644 refs-be-lmdb.c
 create mode 100644 test-refs-be-lmdb.c
Show changes to 15 files +2239 −8

.gitignore, Documentation/git-clone.txt, Documentation/git-init-db.txt, Documentation/git-init.txt, Documentation/technical/refs-be-lmdb.txt, Makefile, builtin/init-db.c, config.c, configure.ac, contrib/workdir/git-new-workdir, environment.c, refs-be-lmdb.c, refs.h, setup.c, test-refs-be-lmdb.c

diff --git a/.gitignore b/.gitignore
index 1c2f832..7decc2f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -199,6 +199,7 @@
 /test-path-utils
 /test-prio-queue
 /test-read-cache
+/test-refs-be-lmdb
 /test-regex
 /test-revision-walking
 /test-run-command
diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt
index d7a4cb0..6a4cf28 100644
--- a/Documentation/git-clone.txt
+++ b/Documentation/git-clone.txt
@@ -219,7 +219,7 @@ objects from the source repository into a pack in the cloned repository.
 
 --refs-backend-type=<name>::
 	Type of refs backend. Default is to use the original files based
-	backend.
+	backend. Set to "lmdb" to activate the lmdb database backend.
 
 <repository>::
 	The (possibly remote) repository to clone from.  See the
diff --git a/Documentation/git-init-db.txt b/Documentation/git-init-db.txt
index 648a6cd..72fbd71 100644
--- a/Documentation/git-init-db.txt
+++ b/Documentation/git-init-db.txt
@@ -9,7 +9,7 @@ git-init-db - Creates an empty Git repository
 SYNOPSIS
 --------
 [verse]
-'git init-db' [-q | --quiet] [--bare] [--template=<template_directory>] [--separate-git-dir <git dir>] [--shared[=<permissions>]]
+'git init-db' [-q | --quiet] [--bare] [--template=<template_directory>] [--separate-git-dir <git dir>] [--shared[=<permissions>]] [--refs-backend-type=<name>]
 
 
 DESCRIPTION
diff --git a/Documentation/git-init.txt b/Documentation/git-init.txt
index 8174d27..d352788 100644
--- a/Documentation/git-init.txt
+++ b/Documentation/git-init.txt
@@ -12,6 +12,7 @@ SYNOPSIS
 'git init' [-q | --quiet] [--bare] [--template=<template_directory>]
 	  [--separate-git-dir <git dir>]
 	  [--shared[=<permissions>]] [directory]
+	  [--refs-backend-type=<name>]
 
 
 DESCRIPTION
@@ -113,6 +114,11 @@ does not exist, it will be created.
 
 --
 
+--refs-backend-type=<name>::
+Type of refs backend. Default is to use the original "files" backend,
+which stores ref data in files in .git/refs and .git/packed-refs.  Set
+to "lmdb" to activate the lmdb database backend.
+
 TEMPLATE DIRECTORY
 ------------------
 
diff --git a/Documentation/technical/refs-be-lmdb.txt b/Documentation/technical/refs-be-lmdb.txt
new file mode 100644
index 0000000..98c13ec
--- /dev/null
+++ b/Documentation/technical/refs-be-lmdb.txt
@@ -0,0 +1,39 @@
+Notes on the LMDB refs backend
+==============================
+
+Design:
+------
+
+Refs and reflogs are stored in a lmdb database in .git/refdb.  All
+keys and values are \0-terminated.
+
+Keys for refs are the name of the ref (e.g. refs/heads/master).
+Values are the value of the ref
+(e.g. 61f23eb0f81357c19fa91e2b8c6f3906c3a8f9b0).
+
+All per-worktree refs (refs/bisect/* and HEAD) are store using
+the traditional files-based backend.
+
+Reflogs are stored as a series of database entries.
+
+For an empty reflog, there is a "header" entry to show that a reflog
+exists.  For non-empty reflogs, there is one entry per logged ref
+update.  The key format is logs/[refname]\0[timestamp].  The timestamp
+is a 64-bit unsigned integer number of nanoseconds since 1/1/1970.
+This means that reflog entries are chronologically ordered.  Because
+LMDB is a btree database, we can efficiently iterate over these keys.
+
+Reflog values are in the same format as the original files-based
+reflog.
+
+Weaknesses:
+-----------
+
+The reflog format is somewhat inefficient: a binary format could store
+reflog date/time information in somewhat less space.
+
+The rsync and file:// transports don't work yet, because they
+don't use the refs API.
+
+git new-workdir is incompatible with the lmdb backend.  Fortunately,
+git new-workdir is deprecated, and worktrees work fine.
diff --git a/Makefile b/Makefile
index 43ceab0..e96f84b 100644
--- a/Makefile
+++ b/Makefile
@@ -1036,6 +1036,17 @@ ifdef USE_LIBPCRE
 	EXTLIBS += -lpcre
 endif
 
+ifdef USE_LIBLMDB
+	BASIC_CFLAGS += -DUSE_LIBLMDB
+	ifdef LIBLMDBDIR
+		BASIC_CFLAGS += -I$(LIBLMDBDIR)/include
+		EXTLIBS += -L$(LIBLMDBDIR)/$(lib) $(CC_LD_DYNPATH)$(LIBLMDBDIR)/$(lib)
+	endif
+	EXTLIBS += -llmdb
+	LIB_OBJS += refs-be-lmdb.o
+	TEST_PROGRAMS_NEED_X += test-refs-be-lmdb
+endif
+
 ifdef HAVE_ALLOCA_H
 	BASIC_CFLAGS += -DHAVE_ALLOCA_H
 endif
@@ -2122,6 +2133,7 @@ GIT-BUILD-OPTIONS: FORCE
 	@echo NO_CURL=\''$(subst ','\'',$(subst ','\'',$(NO_CURL)))'\' >>$@+
 	@echo NO_EXPAT=\''$(subst ','\'',$(subst ','\'',$(NO_EXPAT)))'\' >>$@+
 	@echo USE_LIBPCRE=\''$(subst ','\'',$(subst ','\'',$(USE_LIBPCRE)))'\' >>$@+
+	@echo USE_LIBLMDB=\''$(subst ','\'',$(subst ','\'',$(USE_LIBLMDB)))'\' >>$@+
 	@echo NO_PERL=\''$(subst ','\'',$(subst ','\'',$(NO_PERL)))'\' >>$@+
 	@echo NO_PYTHON=\''$(subst ','\'',$(subst ','\'',$(NO_PYTHON)))'\' >>$@+
 	@echo NO_UNIX_SOCKETS=\''$(subst ','\'',$(subst ','\'',$(NO_UNIX_SOCKETS)))'\' >>$@+
diff --git a/builtin/init-db.c b/builtin/init-db.c
index 504a2dc..89f2c05 100644
--- a/builtin/init-db.c
+++ b/builtin/init-db.c
@@ -203,6 +203,17 @@ static int create_default_files(const char *template_path)
 		adjust_shared_perm(get_git_dir());
 	}
 
+	if (refs_backend_type) {
+		struct refdb_config_data config_data = {NULL};
+		git_config_set("core.refs-backend-type", refs_backend_type);
+		config_data.refs_backend_type = refs_backend_type;
+		config_data.refs_base = get_git_dir();
+#ifdef USE_LIBLMDB
+		register_refs_backend(&refs_be_lmdb);
+#endif
+		set_refs_backend(refs_backend_type, &config_data);
+	}
+
 	if (refs_initdb(&err, shared_repository))
 		die("failed to set up refs db: %s", err.buf);
 
@@ -468,6 +479,8 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
 		OPT_BIT('q', "quiet", &flags, N_("be quiet"), INIT_DB_QUIET),
 		OPT_STRING(0, "separate-git-dir", &real_git_dir, N_("gitdir"),
 			   N_("separate git dir from working tree")),
+		OPT_STRING(0, "refs-backend-type", &refs_backend_type,
+			   N_("name"), N_("name of backend type to use")),
 		OPT_END()
 	};
 
diff --git a/config.c b/config.c
index 248a21a..9ce2ed2 100644
--- a/config.c
+++ b/config.c
@@ -10,6 +10,7 @@
 #include "exec_cmd.h"
 #include "strbuf.h"
 #include "quote.h"
+#include "refs.h"
 #include "hashmap.h"
 #include "string-list.h"
 #include "utf8.h"
@@ -1207,6 +1208,32 @@ int git_config_early(config_fn_t fn, void *data, const char *repo_config)
 	}
 
 	if (repo_config && !access_or_die(repo_config, R_OK, 0)) {
+		struct refdb_config_data refdb_data = {NULL};
+		char *repo_config_copy;
+
+		/*
+		 * make sure we always read the backend config from the
+		 * core section on startup
+		 */
+		ret += git_config_from_file(refdb_config, repo_config,
+					    &refdb_data);
+
+		repo_config_copy = xstrdup(repo_config);
+		refdb_data.refs_base = strdup(dirname(repo_config_copy));
+		free(repo_config_copy);
+
+		if (refdb_data.refs_backend_type &&
+		    !strcmp(refdb_data.refs_backend_type, "lmdb")) {
+
+#ifdef USE_LIBLMDB
+			refs_backend_type = refdb_data.refs_backend_type;
+			register_refs_backend(&refs_be_lmdb);
+			set_refs_backend(refs_backend_type, &refdb_data);
+#else
+			die("Git was not built with USE_LIBLMDB, so the db refs backend is not available");
+#endif
+		}
+
 		ret += git_config_from_file(fn, repo_config, data);
 		found += 1;
 	}
diff --git a/configure.ac b/configure.ac
index 63b7244..ea8e93d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -271,6 +271,24 @@ AS_HELP_STRING([],           [ARG can be also prefix for libpcre library and hea
         dnl it yet.
 	GIT_CONF_SUBST([LIBPCREDIR])
     fi)
+
+USE_LIBLMDB=YesPlease
+AC_ARG_WITH(liblmdb,
+AS_HELP_STRING([--with-liblmdb],[support lmdb (default is YES])
+AS_HELP_STRING([],           [ARG can be also prefix for liblmdb library and headers]),
+    if test "$withval" = "no"; then
+	USE_LIBLMDB=
+    elif test "$withval" = "yes"; then
+	USE_LIBLMDB=YesPlease
+    else
+	USE_LIBLMDB=YesPlease
+	LIBLMDBDIR=$withval
+	AC_MSG_NOTICE([Setting LIBLMDBDIR to $LIBLMDBDIR])
+        dnl USE_LIBLMDB can still be modified below, so don't substitute
+        dnl it yet.
+	GIT_CONF_SUBST([LIBLMDBDIR])
+    fi)
+
 #
 # Define HAVE_ALLOCA_H if you have working alloca(3) defined in that header.
 AC_FUNC_ALLOCA
@@ -510,6 +528,21 @@ GIT_CONF_SUBST([USE_LIBPCRE])
 
 fi
 
+if test -n "$USE_LIBLMDB"; then
+
+GIT_STASH_FLAGS($LIBLMDBDIR)
+
+AC_CHECK_LIB([lmdb], [mdb_env_open],
+[USE_LIBLMDB=YesPlease],
+[USE_LIBLMDB=])
+
+GIT_UNSTASH_FLAGS($LIBLMDBDIR)
+
+GIT_CONF_SUBST([USE_LIBLMDB])
+
+fi
+
+
 #
 # Define NO_CURL if you do not have libcurl installed.  git-http-pull and
 # git-http-push are not built, and you cannot use http:// and https://
diff --git a/contrib/workdir/git-new-workdir b/contrib/workdir/git-new-workdir
index 888c34a..9761a1a 100755
--- a/contrib/workdir/git-new-workdir
+++ b/contrib/workdir/git-new-workdir
@@ -28,6 +28,8 @@ git_dir=$(cd "$orig_git" 2>/dev/null &&
   git rev-parse --git-dir 2>/dev/null) ||
   die "Not a git repository: \"$orig_git\""
 
+test -d $git_dir/refdb && die "git-new-workdir is incompatible with the refs db backend"
+
 case "$git_dir" in
 .git)
 	git_dir="$orig_git/.git"
diff --git a/environment.c b/environment.c
index c5b65f5..6a2f639 100644
--- a/environment.c
+++ b/environment.c
@@ -65,6 +65,7 @@ int merge_log_config = -1;
 int precomposed_unicode = -1; /* see probe_utf8_pathname_composition() */
 struct startup_info *startup_info;
 unsigned long pack_size_limit_cfg;
+const char *refs_backend_type;
 
 #ifndef PROTECT_HFS_DEFAULT
 #define PROTECT_HFS_DEFAULT 0
diff --git a/refs-be-lmdb.c b/refs-be-lmdb.c
new file mode 100644
index 0000000..99cbd29
--- /dev/null
+++ b/refs-be-lmdb.c
@@ -0,0 +1,2003 @@
+/*
+ * This file implements a lmdb backend for refs.
+ *
+ * The design of this backend relies on lmdb's write lock -- that is, any
+ * write transaction blocks all other writers.  Thus, as soon as a ref
+ * transaction is opened, we know that any values we read won't
+ * change out from under us, and we have a fully-consistent view of the
+ * database.
+ *
+ * We store the content of refs including the trailing \0 so that
+ * standard C string functions can handle them.  Just like struct
+ * strbuf.
+ */
+#include <lmdb.h>
+#include <sys/uio.h>
+#include "cache.h"
+#include "object.h"
+#include "refs.h"
+#include "tag.h"
+#include "lockfile.h"
+#include "run-command.h"
+
+static struct trace_key db_trace = TRACE_KEY_INIT(LMDB);
+
+static MDB_env *env;
+
+static char *db_path;
+
+struct lmdb_transaction_info {
+	MDB_txn *txn;
+	MDB_dbi dbi;
+	const char *submodule;
+};
+
+struct lmdb_transaction {
+	struct ref_transaction base;
+	MDB_cursor *cursor;
+	struct lmdb_transaction_info info;
+	struct hashmap updated_refs;
+	unsigned int flags;
+};
+
+static struct lmdb_transaction transaction = {{}, NULL};
+
+struct ref_update {
+	struct hashmap_entry ent;  /* must be first */
+	size_t len;
+	char refname[FLEX_ARRAY];
+};
+
+static char *get_refdb_path(const char *base)
+{
+	struct strbuf path_buf = STRBUF_INIT;
+	strbuf_addf(&path_buf, "%s/refdb", base);
+	return strbuf_detach(&path_buf, NULL);
+}
+
+static int in_write_transaction(void)
+{
+	return transaction.info.txn && !(transaction.flags & MDB_RDONLY);
+}
+
+static void init_env(MDB_env **env, const char *path)
+{
+	int ret;
+	if (*env)
+		return;
+
+	if ((ret = mdb_env_create(env)) != MDB_SUCCESS)
+		die("mdb_env_create failed: %s", mdb_strerror(ret));
+	if ((ret = mdb_env_set_maxreaders(*env, 1000)) != MDB_SUCCESS)
+		die("BUG: mdb_env_set_maxreaders failed: %s", mdb_strerror(ret));
+	if ((ret = mdb_env_set_mapsize(*env, (1<<30))) != MDB_SUCCESS)
+		die("BUG: mdb_set_mapsize failed: %s", mdb_strerror(ret));
+	if ((ret = mdb_env_open(*env, path, 0 , 0664)) != MDB_SUCCESS)
+		die("BUG: mdb_env_open (%s) failed: %s", path, mdb_strerror(ret));
+}
+
+static int lmdb_initdb(struct strbuf *err, int shared)
+{
+	/* To create a db, all we need to do is make a directory for
+	   it to live in; lmdb will do the rest. */
+
+	assert(db_path);
+	if (mkdir(db_path, 0775)) {
+		if (errno != EEXIST) {
+			strbuf_addf(err, "%s", strerror(errno));
+			return -1;
+		}
+	}
+	return 0;
+}
+
+static void lmdb_init_backend(struct refdb_config_data *data)
+{
+	if (db_path)
+		return;
+
+	db_path = xstrdup(real_path(get_refdb_path(data->refs_base)));
+	trace_printf_key(&db_trace, "Init backend\n");
+}
+
+int ref_update_cmp(const void *entry, const void *entry_or_key, const void *keydata)
+{
+
+	const struct ref_update *existing = entry;
+	const struct ref_update *incoming = entry_or_key;
+
+	return existing->len != incoming->len ||
+		memcmp(existing->refname, incoming->refname, existing->len);
+}
+
+static void mdb_cursor_open_or_die(struct lmdb_transaction_info *info,
+				   MDB_cursor **cursor)
+{
+	int ret = mdb_cursor_open(info->txn, info->dbi, cursor);
+	if (ret)
+		die("mdb_cursor_open failed: %s", mdb_strerror(ret));
+}
+
+static void submodule_path(struct strbuf *sb, const char *submodule,
+			   const char *refname)
+{
+	if (submodule)
+		strbuf_git_path_submodule(sb, submodule, "%s", refname);
+	else
+		strbuf_git_path(sb, "%s", refname);
+}
+
+static int read_per_worktree_ref(const char *submodule, const char *refname,
+				 struct MDB_val *val)
+{
+	static struct strbuf sb = STRBUF_INIT;
+	static struct strbuf path = STRBUF_INIT;
+
+	submodule_path(&path, submodule, refname);
+
+	if (strbuf_read_file(&sb, path.buf, 200) < 0) {
+		strbuf_release(&sb);
+		strbuf_release(&path);
+		if (errno == ENOENT)
+			return MDB_NOTFOUND;
+		return -1;
+	}
+	strbuf_setlen(&sb, sb.len - 1);
+
+	val->mv_data = sb.buf;
+	val->mv_size = sb.len + 1;
+
+	strbuf_detach(&sb, NULL);
+	strbuf_release(&path);
+	return 0;
+}
+
+static void write_per_worktree_ref(const char *submodule, const char *refname,
+				   MDB_val *val)
+{
+	static struct lock_file lock;
+	int fd;
+	int len = val->mv_size - 1;
+	struct strbuf path = STRBUF_INIT;
+
+	submodule_path(&path, submodule, refname);
+	safe_create_leading_directories(path.buf);
+
+	fd = hold_lock_file_for_update(&lock, path.buf, LOCK_DIE_ON_ERROR);
+	strbuf_release(&path);
+
+	if (write_in_full(fd, val->mv_data, len) != len ||
+	    write_in_full(fd, "\n", 1) != 1)
+		die_errno("failed to write new HEAD");
+
+	if (commit_lock_file(&lock))
+		die_errno("failed to write new HEAD");
+}
+
+static int del_per_worktree_ref(const char *submodule, const char *refname,
+				MDB_val *val)
+{
+	struct strbuf path = STRBUF_INIT;
+	int result;
+
+	/*
+	 * Returning deleted ref data is not yet implemented, but no
+	 * callers need it.
+	 */
+	assert(val == NULL);
+
+	submodule_path(&path, submodule, refname);
+
+	result = unlink(path.buf);
+	strbuf_release(&path);
+	if (result) {
+		if (errno == ENOENT)
+			return 0;
+		return 1;
+	}
+
+	return 0;
+}
+
+static int mdb_get_or_die(struct lmdb_transaction_info *info, MDB_val *key, MDB_val *val)
+{
+	int ret;
+
+	if (ref_type(key->mv_data) != REF_TYPE_NORMAL)
+		return read_per_worktree_ref(info->submodule, key->mv_data, val);
+
+	ret = mdb_get(info->txn, info->dbi, key, val);
+	if (ret) {
+		if (ret != MDB_NOTFOUND)
+			die("mdb_get failed: %s", mdb_strerror(ret));
+		return ret;
+	}
+	return 0;
+}
+
+static int mdb_del_or_die(struct lmdb_transaction_info *info, MDB_val *key, MDB_val *val)
+{
+	int ret;
+
+	if (ref_type(key->mv_data) != REF_TYPE_NORMAL)
+		return del_per_worktree_ref(info->submodule, key->mv_data, val);
+
+	ret = mdb_del(info->txn, info->dbi, key, val);
+	if (ret) {
+		if (ret != MDB_NOTFOUND)
+			die("mdb_del failed: %s", mdb_strerror(ret));
+		return ret;
+	}
+	return 0;
+}
+
+static void mdb_put_or_die(struct lmdb_transaction_info *info, MDB_val *key, MDB_val *val, int mode)
+{
+	int ret;
+
+	if (ref_type(key->mv_data) != REF_TYPE_NORMAL) {
+		write_per_worktree_ref(info->submodule, key->mv_data, val);
+		return;
+	}
+	assert(val->mv_size == 0 || ((char *)val->mv_data)[val->mv_size - 1] == 0);
+
+	ret = mdb_put(info->txn, info->dbi, key, val, mode);
+	if (ret) {
+		if (ret == MDB_BAD_VALSIZE)
+			die("Ref name %s too long (max size is %d)",
+			    (const char *)key->mv_data,
+			    mdb_env_get_maxkeysize(env));
+		else
+			die("mdb_put failed: %s", mdb_strerror(ret));
+	}
+}
+
+static int mdb_cursor_get_or_die(MDB_cursor *cursor, MDB_val *key, MDB_val *val, int mode)
+{
+	int ret;
+
+	ret = mdb_cursor_get(cursor, key, val, mode);
+	if (ret) {
+		if (ret != MDB_NOTFOUND)
+			die("mdb_cursor_get failed: %s", mdb_strerror(ret));
+		return ret;
+	}
+	assert(((char *)val->mv_data)[val->mv_size - 1] == 0);
+	return 0;
+}
+
+static int mdb_cursor_del_or_die(MDB_cursor *cursor, int flags)
+{
+	int ret = mdb_cursor_del(cursor, flags);
+	if (ret) {
+		if (ret != MDB_NOTFOUND)
+			die("mdb_cursor_del failed: %s", mdb_strerror(ret));
+		return ret;
+	}
+	return 0;
+}
+
+/*
+ * Begin a transaction. Because only one transaction per thread is
+ * permitted, we use a global transaction object.  If a read-write
+ * transaction is presently already in-progress, and a read-only
+ * transaction is requested, the read-write transaction will be
+ * returned instead.  If a read-write transaction is requested and a
+ * read-only transaction is open, the read-only transaction will be
+ * closed.
+ *
+ * It is a bug to request a read-write transaction during another
+ * read-write transaction.
+ *
+ * As a result, it is unsafe to retain read-only transactions past the
+ * point where a read-write transaction might be needed.  For
+ * instance, any call that has callbacks outside this module must
+ * conclude all of its reads from the database before calling those
+ * callbacks, or must reacquire the transaction after its callbacks
+ * are completed.
+ */
+int lmdb_transaction_begin_flags(struct strbuf *err, unsigned int flags)
+{
+	int ret;
+	MDB_txn *txn;
+	static int last_commands_run = 0;
+	int force_restart = 0;
+
+	init_env(&env, db_path);
+
+	if (total_commands_run != last_commands_run) {
+		/*
+		 * Since each transaction sees a consistent view of
+		 * the db, downstream processes that write the db
+		 * won't be seen in this transaction.  We don't know
+		 * whether any given downstream process has made any
+		 * writes, so if there have been any downstream processes,
+		 * we had better reopen the transaction.
+		 */
+		force_restart = 1;
+		last_commands_run = total_commands_run;
+	}
+
+	if (!transaction.info.txn) {
+		hashmap_init(&transaction.updated_refs, ref_update_cmp, 0);
+		if ((ret = mdb_txn_begin(env, NULL, flags, &txn)) != MDB_SUCCESS) {
+			strbuf_addf(err, "mdb_txn_begin failed: %s",
+				    mdb_strerror(ret));
+			return -1;
+		}
+		if ((ret = mdb_dbi_open(txn, NULL, 0, &transaction.info.dbi)) != MDB_SUCCESS) {
+			strbuf_addf(err, "mdb_txn_open failed: %s",
+				    mdb_strerror(ret));
+			return -1;
+		}
+		transaction.info.txn = txn;
+		transaction.flags = flags;
+		return 0;
+	}
+
+	if (transaction.flags == flags && !(flags & MDB_RDONLY)) {
+		die("BUG: rw transaction started during another rw txn");
+	}
+
+	if (force_restart || (transaction.flags != flags && transaction.flags & MDB_RDONLY)) {
+		/*
+		 * RO -> RW, or forced restart due to possible changes
+		 * from downstream processes.
+		 */
+		mdb_txn_abort(transaction.info.txn);
+		if ((ret = mdb_txn_begin(env, NULL, flags, &txn)) != MDB_SUCCESS) {
+			strbuf_addf(err, "restarting txn: mdb_txn_begin failed: %s",
+				    mdb_strerror(ret));
+			return -1;
+		}
+		if ((ret = mdb_dbi_open(txn, NULL, 0, &transaction.info.dbi)) != MDB_SUCCESS) {
+			strbuf_addf(err, "mdb_txn_open failed: %s",
+				    mdb_strerror(ret));
+			return -1;
+		}
+		transaction.info.txn = txn;
+		transaction.flags = flags;
+	}
+	/* RW -> RO just keeps the RW txn */
+	return 0;
+}
+
+static struct ref_transaction *lmdb_transaction_begin_flags_or_die(int flags)
+{
+	struct strbuf err = STRBUF_INIT;
+	if (lmdb_transaction_begin_flags(&err, flags))
+		die("%s", err.buf);
+	return (struct ref_transaction *)&transaction;
+}
+
+static struct ref_transaction *lmdb_transaction_begin(struct strbuf *err)
+{
+	if (lmdb_transaction_begin_flags(err, 0))
+		return NULL;
+	return (struct ref_transaction *)&transaction;
+}
+
+#define MAXDEPTH 5
+
+static const char *parse_ref_data(struct lmdb_transaction_info *info,
+				  const char *refname, const char *ref_data,
+				  unsigned char *sha1, int resolve_flags,
+				  int *flags, int bad_name)
+{
+	int depth = MAXDEPTH;
+	const char *buf;
+	static struct strbuf refname_buffer = STRBUF_INIT;
+	static struct strbuf refdata_buffer = STRBUF_INIT;
+	MDB_val key, val;
+	const char *ret;
+
+	strbuf_reset(&refname_buffer);
+	strbuf_reset(&refdata_buffer);
+
+	for (;;) {
+		if (--depth < 0) {
+			return NULL;
+		}
+
+		if (!starts_with(ref_data, "ref:")) {
+			if (get_sha1_hex(ref_data, sha1) ||
+			    (ref_data[40] != '\0' && !isspace(ref_data[40]))) {
+				if (flags)
+					*flags |= REF_ISBROKEN;
+				errno = EINVAL;
+				return NULL;
+			}
+
+			if (bad_name) {
+				hashclr(sha1);
+				if (flags)
+					*flags |= REF_ISBROKEN;
+			} else if (is_null_sha1(sha1)) {
+				if (flags)
+					*flags |= REF_ISBROKEN;
+			}
+			return refname;
+		}
+		if (flags)
+			*flags |= REF_ISSYMREF;
+		buf = ref_data + 4;
+		while (isspace(*buf))
+			buf++;
+		strbuf_addf(&refname_buffer, buf);
+		refname = refname_buffer.buf;
+		if (resolve_flags & RESOLVE_REF_NO_RECURSE) {
+			hashclr(sha1);
+			return refname;
+		}
+		if (check_refname_format(buf, REFNAME_ALLOW_ONELEVEL)) {
+			if (flags)
+				*flags |= REF_ISBROKEN;
+
+			if (!(resolve_flags & RESOLVE_REF_ALLOW_BAD_NAME) ||
+			    !refname_is_safe(buf)) {
+				errno = EINVAL;
+				return NULL;
+			}
+			bad_name = 1;
+		}
+
+		key.mv_data = (char *)refname;
+		key.mv_size = strlen(refname) + 1;
+		if (mdb_get_or_die(info, &key, &val)) {
+			hashclr(sha1);
+			if (bad_name) {
+				if (flags)
+					*flags |= REF_ISBROKEN;
+			}
+			if (resolve_flags & RESOLVE_REF_READING)
+				return NULL;
+
+			return refname;
+		}
+		strbuf_reset(&refdata_buffer);
+		strbuf_add(&refdata_buffer, val.mv_data, val.mv_size);
+		ref_data = refdata_buffer.buf;
+	}
+	return refname;
+}
+
+static int verify_refname_available_txn(struct lmdb_transaction_info *info,
+					const char *refname,
+					struct string_list *extras,
+					struct string_list *skip,
+					struct strbuf *err)
+{
+	MDB_cursor *cursor;
+	MDB_val key;
+	MDB_val val;
+	int mdb_ret;
+	size_t refname_len;
+	char *search_key;
+	const char *extra_refname;
+	int ret = 1;
+	size_t i;
+
+	mdb_cursor_open_or_die(info, &cursor);
+
+	refname_len = strlen(refname) + 2;
+	key.mv_size = refname_len;
+	search_key = xmalloc(refname_len);
+	memcpy(search_key, refname, refname_len - 2);
+	search_key[refname_len - 2] = '/';
+	search_key[refname_len - 1] = 0;
+	key.mv_data = search_key;
+
+	/*
+	 * Check for subdirs of refname: we start at refname/
+	 */
+	mdb_ret = mdb_cursor_get_or_die(cursor, &key, &val, MDB_SET_RANGE);
+
+	while (!mdb_ret) {
+		if (starts_with(key.mv_data, refname) &&
+		    ((char*)key.mv_data)[refname_len - 2] == '/') {
+			if (skip && string_list_has_string(skip, key.mv_data))
+				goto next;
+
+			strbuf_addf(err, "'%s' exists; cannot create '%s'", (char *)key.mv_data, refname);
+			goto done;
+		}
+		break;
+	next:
+		mdb_ret = mdb_cursor_get_or_die(cursor, &key, &val, MDB_NEXT);
+	}
+
+	/* Check for parent dirs of refname. */
+	for (i = 0; i < refname_len - 2; ++i) {
+		if (search_key[i] == '/') {
+			search_key[i] = 0;
+			if (skip && string_list_has_string(skip, search_key)) {
+				search_key[i] = '/';
+				continue;
+			}
+
+			if (extras && string_list_has_string(extras, search_key)) {
+				strbuf_addf(err, "cannot process '%s' and '%s' at the same time",
+					    refname, search_key);
+				goto done;
+			}
+
+			key.mv_data = search_key;
+			key.mv_size = i + 1;
+			if (!mdb_cursor_get_or_die(cursor, &key, &val, MDB_SET)) {
+				strbuf_addf(err, "'%s' exists; cannot create '%s'", (char *)key.mv_data, refname);
+				goto done;
+			}
+			search_key[i] = '/';
+		}
+	}
+
+	extra_refname = find_descendant_ref(refname, extras, skip);
+	if (extra_refname) {
+		strbuf_addf(err,
+			    "cannot process '%s' and '%s' at the same time",
+			    refname, extra_refname);
+		ret = 1;
+	} else {
+		ret = 0;
+	}
+done:
+	mdb_cursor_close(cursor);
+	free(search_key);
+	return ret;
+}
+
+static const char *resolve_ref_unsafe_txn(struct lmdb_transaction_info *info,
+					  const char *refname,
+					  int resolve_flags,
+					  unsigned char *sha1,
+					  int *flags)
+{
+	int bad_name = 0;
+	char *ref_data;
+	struct MDB_val key, val;
+	struct strbuf err = STRBUF_INIT;
+
+	val.mv_size = 0;
+	val.mv_data = NULL;
+
+	if (flags)
+		*flags = 0;
+
+	if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
+		if (flags)
+			*flags |= REF_BAD_NAME;
+
+		if (!(resolve_flags & RESOLVE_REF_ALLOW_BAD_NAME) ||
+		    !refname_is_safe(refname)) {
+			errno = EINVAL;
+			return NULL;
+		}
+		/*
+		 * dwim_ref() uses REF_ISBROKEN to distinguish between
+		 * missing refs and refs that were present but invalid,
+		 * to complain about the latter to stderr.
+		 *
+		 * We don't know whether the ref exists, so don't set
+		 * REF_ISBROKEN yet.
+		 */
+		bad_name = 1;
+	}
+
+	key.mv_data = (void *)refname;
+	key.mv_size = strlen(refname) + 1;
+	if (mdb_get_or_die(info, &key, &val)) {
+		if (bad_name) {
+			hashclr(sha1);
+			if (flags)
+				*flags |= REF_ISBROKEN;
+		}
+
+		if (resolve_flags & RESOLVE_REF_READING)
+			return NULL;
+
+		if (verify_refname_available_txn(info, refname, NULL, NULL, &err)) {
+			error("%s", err.buf);
+			strbuf_release(&err);
+			return NULL;
+		}
+
+		hashclr(sha1);
+		return refname;
+	}
+
+	ref_data = val.mv_data;
+	assert(ref_data[val.mv_size - 1] == 0);
+
+	return parse_ref_data(info, refname, ref_data, sha1, resolve_flags,
+			      flags, bad_name);
+}
+
+static const char *lmdb_resolve_ref_unsafe(const char *refname, int resolve_flags,
+					   unsigned char *sha1, int *flags)
+{
+	lmdb_transaction_begin_flags_or_die(MDB_RDONLY);
+	return resolve_ref_unsafe_txn(&transaction.info, refname,
+				      resolve_flags, sha1, flags);
+}
+
+static void write_u64(char *buf, uint64_t number)
+{
+	int i;
+
+	for (i = 0; i < 8; ++i)
+		buf[i] = (number >> (i * 8)) & 0xff;
+}
+
+static int show_one_reflog_ent(struct strbuf *sb, each_reflog_ent_fn fn, void *cb_data)
+{
+	unsigned char osha1[20], nsha1[20];
+	char *email_end, *message;
+	unsigned long timestamp;
+	int tz;
+
+	/* old (raw) new (raw) name <email> SP time TAB msg LF */
+	if (sb->len < 41 || sb->buf[sb->len - 1] != '\n' ||
+	    !(email_end = strchr(sb->buf + 40, '>')) ||
+	    email_end[1] != ' ' ||
+	    !(timestamp = strtoul(email_end + 2, &message, 10)) ||
+	    !message || message[0] != ' ' ||
+	    (message[1] != '+' && message[1] != '-') ||
+	    !isdigit(message[2]) || !isdigit(message[3]) ||
+	    !isdigit(message[4]) || !isdigit(message[5]))
+		return 0; /* corrupt? */
+
+	hashcpy(osha1, (const unsigned char *)sb->buf);
+	hashcpy(nsha1, (const unsigned char *)sb->buf + 20);
+
+	email_end[1] = '\0';
+	tz = strtol(message + 1, NULL, 10);
+	if (message[6] != '\t')
+		message += 6;
+	else
+		message += 7;
+	return fn(osha1, nsha1, sb->buf + 40, timestamp, tz, message, cb_data);
+}
+
+static void format_reflog_entry(struct strbuf *buf,
+				const unsigned char *old_sha1,
+				const unsigned char *new_sha1,
+				const char *committer, const char *msg)
+{
+	int len;
+	int msglen;
+
+	assert(buf->len == 0);
+	strbuf_add(buf, old_sha1, 20);
+	strbuf_add(buf, new_sha1, 20);
+	strbuf_addstr(buf, committer);
+	strbuf_addch(buf, '\n');
+
+	len = buf->len;
+	msglen = msg ? strlen(msg) : 0;
+	if (msglen) {
+		strbuf_grow(buf, msglen + 1);
+		int copied = copy_reflog_msg(buf->buf + 40 + strlen(committer), msg) - 1;
+		buf->len = len + copied;
+		buf->buf[buf->len] = 0;
+	}
+}
+
+static int log_ref_write(const char *refname,
+			 const unsigned char *old_sha1,
+			 const unsigned char *new_sha1,
+			 const char *msg,
+			 int flags,
+			 struct strbuf *err)
+{
+	MDB_val key, val;
+	uint64_t now = getnanotime();
+	int result;
+	char *log_key;
+	int refname_len;
+	MDB_cursor *cursor;
+	struct strbuf buf = STRBUF_INIT;
+	const char *timestamp;
+
+	if (log_all_ref_updates < 0)
+		log_all_ref_updates = !is_bare_repository();
+
+	if (ref_type(refname) != REF_TYPE_NORMAL) {
+		return files_log_ref_write(refname, old_sha1, new_sha1,
+					   msg, flags, err);
+	}
+
+	/* it is assumed that we are in a ref transaction here */
+	assert(transaction.info.txn);
+
+	result = safe_create_reflog(refname, flags & REF_FORCE_CREATE_REFLOG, err);
+	if (result)
+		return result;
+
+	/* "logs/" + refname + \0 + 8-byte timestamp for sorting and expiry. */
+	refname_len = strlen(refname);
+	key.mv_size = refname_len + 14;
+	log_key = xcalloc(1, key.mv_size);
+	sprintf(log_key, "logs/%s", refname);
+	key.mv_data = log_key;
+
+	mdb_cursor_open_or_die(&transaction.info, &cursor);
+
+	/* check that a reflog exists */
+	if (mdb_cursor_get_or_die(cursor, &key, &val, MDB_SET_RANGE) ||
+	    strcmp(key.mv_data, log_key)) {
+		free(log_key);
+		mdb_cursor_close(cursor);
+		return 0;
+	}
+
+	/* Is this a header?  We only need the header for empty reflogs */
+	timestamp = (const char *)key.mv_data + refname_len + 6;
+	if (ntohll(*(uint64_t *)timestamp) == 0)
+		mdb_cursor_del_or_die(cursor, 0);
+
+	key.mv_data = log_key;
+
+	write_u64(key.mv_data + refname_len + 6, htonll(now));
+
+	format_reflog_entry(&buf, old_sha1, new_sha1,
+			    git_committer_info(0), msg);
+	assert(buf.len >= 42);
+	val.mv_data = buf.buf;
+	val.mv_size = buf.len + 1;
+
+	mdb_put_or_die(&transaction.info, &key, &val, 0);
+
+	strbuf_release(&buf);
+	free(log_key);
+	mdb_cursor_close(cursor);
+	return 0;
+}
+
+static int lmdb_verify_refname_available(const char *refname,
+					 struct string_list *extras,
+					 struct string_list *skip,
+					 struct strbuf *err)
+{
+	lmdb_transaction_begin_flags_or_die(MDB_RDONLY);
+	return verify_refname_available_txn(&transaction.info, refname, extras, skip, err);
+}
+
+static const char *check_ref(MDB_txn *txn, const char *refname,
+			     const unsigned char *old_sha1,
+			     unsigned char *resolved_sha1, int flags,
+			     int *type_p)
+{
+	int mustexist = (old_sha1 && !is_null_sha1(old_sha1));
+	int resolve_flags = 0;
+	int type;
+
+	if (mustexist)
+		resolve_flags |= RESOLVE_REF_READING;
+	if (flags & REF_DELETING) {
+		resolve_flags |= RESOLVE_REF_ALLOW_BAD_NAME;
+		if (flags & REF_NODEREF)
+			resolve_flags |= RESOLVE_REF_NO_RECURSE;
+	}
+
+	refname = resolve_ref_unsafe(refname, resolve_flags,
+				     resolved_sha1, &type);
+	if (type_p)
+	    *type_p = type;
+	if (!refname) {
+		return NULL;
+	}
+
+	if (old_sha1) {
+		if (flags & REF_NODEREF) {
+			resolve_flags &= ~RESOLVE_REF_NO_RECURSE;
+			resolve_ref_unsafe(refname, resolve_flags,
+					   resolved_sha1, &type);
+		}
+		if (hashcmp(old_sha1, resolved_sha1)) {
+			error("Ref %s is at %s but expected %s", refname,
+			      sha1_to_hex(resolved_sha1), sha1_to_hex(old_sha1));
+
+			return NULL;
+		}
+	}
+	return refname;
+}
+
+int lmdb_transaction_create(struct ref_transaction *transaction,
+			    const char *refname,
+			    const unsigned char *new_sha1,
+			    unsigned int flags, const char *msg,
+			    struct strbuf *err)
+{
+	if (!new_sha1 || is_null_sha1(new_sha1))
+		die("BUG: create called without valid new_sha1");
+	return ref_transaction_update(transaction, refname, new_sha1,
+				      null_sha1, flags, msg, err);
+}
+
+static int lmdb_transaction_delete(struct ref_transaction *transaction,
+				   const char *refname,
+				   const unsigned char *old_sha1,
+				   unsigned int flags, const char *msg,
+				   struct strbuf *err)
+{
+		if (old_sha1 && is_null_sha1(old_sha1))
+		die("BUG: delete called with old_sha1 set to zeros");
+	return ref_transaction_update(transaction, refname,
+				      null_sha1, old_sha1,
+				      flags, msg, err);
+}
+
+static int lmdb_transaction_verify(struct ref_transaction *transaction,
+				  const char *refname,
+				  const unsigned char *old_sha1,
+				  unsigned int flags,
+				  struct strbuf *err)
+{
+		if (!old_sha1)
+		die("BUG: verify called with old_sha1 set to NULL");
+	return ref_transaction_update(transaction, refname,
+				      NULL, old_sha1,
+				      flags, NULL, err);
+}
+
+static void lmdb_transaction_free_1(struct lmdb_transaction *transaction)
+{
+	hashmap_free(&transaction->updated_refs, 1);
+	transaction->info.txn = NULL;
+}
+
+static void lmdb_transaction_free(struct ref_transaction *trans)
+{
+	struct lmdb_transaction *transaction = (struct lmdb_transaction *)trans;
+	if (!transaction->info.txn)
+		return;
+
+	mdb_txn_abort(transaction->info.txn);
+	lmdb_transaction_free_1(transaction);
+	return;
+}
+
+static int lmdb_transaction_commit(struct ref_transaction *trans,
+				   struct strbuf *err)
+{
+	struct lmdb_transaction *transaction = (struct lmdb_transaction *)trans;
+	int result;
+
+	result = mdb_txn_commit(transaction->info.txn);
+	lmdb_transaction_free_1(transaction);
+	return result;
+}
+
+static int lmdb_delete_reflog(const char *refname)
+{
+	MDB_val key, val;
+	char *log_path;
+	int len;
+	MDB_cursor *cursor;
+	int ret = 0;
+	int mdb_ret;
+	struct strbuf err = STRBUF_INIT;
+	int in_transaction;
+
+	if (ref_type(refname) != REF_TYPE_NORMAL)
+		return files_delete_reflog(refname);
+
+	in_transaction = in_write_transaction();
+
+	len = strlen(refname) + 6;
+	log_path = xmalloc(len);
+	sprintf(log_path, "logs/%s", refname);
+
+	key.mv_data = log_path;
+	key.mv_size = len;
+
+	if (!in_transaction)
+		lmdb_transaction_begin_flags_or_die(0);
+
+	mdb_cursor_open_or_die(&transaction.info, &cursor);
+
+	mdb_ret = mdb_cursor_get_or_die(cursor, &key, &val, MDB_SET_RANGE);
+
+	while (!mdb_ret) {
+		if (key.mv_size < len)
+			break;
+
+		if (!starts_with(key.mv_data, log_path) || ((char*)key.mv_data)[len - 1] != 0)
+			break;
+
+		mdb_cursor_del_or_die(cursor, 0);
+		mdb_ret = mdb_cursor_get_or_die(cursor, &key, &val, MDB_NEXT);
+	}
+
+	free(log_path);
+	mdb_cursor_close(cursor);
+	transaction.cursor = NULL;
+
+	if (!in_transaction)
+		lmdb_transaction_commit((struct ref_transaction *)&transaction, &err);
+	strbuf_release(&err);
+	return ret;
+}
+
+#define REF_NO_REFLOG 0x8000
+
+static int lmdb_transaction_update(struct ref_transaction *trans,
+				   const char *refname,
+				   const unsigned char *new_sha1,
+				   const unsigned char *old_sha1,
+				   unsigned int flags, const char *msg,
+				   struct strbuf *err)
+{
+	struct lmdb_transaction *transaction = (struct lmdb_transaction *)trans;
+	const char *orig_refname = refname;
+	MDB_val key, val;
+	struct ref_update *update, *old;
+	unsigned char resolved_sha1[20];
+	size_t len;
+	int type;
+
+	if (new_sha1)
+		flags |= REF_HAVE_NEW;
+	if (old_sha1)
+		flags |= REF_HAVE_OLD;
+
+	if ((flags & REF_HAVE_NEW) && is_null_sha1(new_sha1))
+		flags |= REF_DELETING;
+
+	if (new_sha1 && !is_null_sha1(new_sha1) &&
+	    check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
+		strbuf_addf(err, "refusing to update ref with bad name %s",
+			    refname);
+		return TRANSACTION_GENERIC_ERROR;
+	}
+
+	len = strlen(orig_refname);
+	update = xmalloc(sizeof(*update) + len + 1);
+	update->len = len;
+	strcpy(update->refname, orig_refname);
+	hashmap_entry_init(update, memhash(update->refname, len));
+	old = hashmap_put(&(transaction->updated_refs), update);
+	if (old) {
+		strbuf_addf(err, "Multiple updates for ref '%s' not allowed.",
+			    orig_refname);
+		free(old);
+		return TRANSACTION_GENERIC_ERROR;
+	}
+
+	refname = check_ref(transaction->info.txn, orig_refname, old_sha1,
+			    resolved_sha1, flags, &type);
+
+	if (refname == NULL) {
+		strbuf_addf(err, "cannot lock the ref '%s'", orig_refname);
+		return TRANSACTION_GENERIC_ERROR;
+	}
+
+	if (!(flags & REF_DELETING) && is_null_sha1(resolved_sha1) &&
+	    verify_refname_available_txn(&transaction->info, refname, NULL, NULL, err))
+		return TRANSACTION_NAME_CONFLICT;
+
+	if (flags & REF_NODEREF)
+		refname = orig_refname;
+
+	key.mv_size = strlen(refname) + 1;
+	key.mv_data = (void *)refname;
+
+	if ((flags & REF_HAVE_NEW) && !is_null_sha1(new_sha1)) {
+		int overwriting_symref = ((type & REF_ISSYMREF) &&
+					  (flags & REF_NODEREF));
+
+		struct object *o = parse_object(new_sha1);
+		if (!o) {
+			strbuf_addf(err,
+				    "Trying to write ref %s with nonexistent object %s",
+				    refname, sha1_to_hex(new_sha1));
+			return -1;
+		}
+		if (o->type != OBJ_COMMIT && is_branch(refname)) {
+			strbuf_addf(err,
+				    "Trying to write non-commit object %s to branch %s",
+				    sha1_to_hex(new_sha1), refname);
+			return -1;
+		}
+
+		if (!overwriting_symref
+		    && !hashcmp(resolved_sha1, new_sha1)) {
+			/*
+			 * The reference already has the desired
+			 * value, so we don't need to write it.
+			 */
+			flags |= REF_NO_REFLOG;
+		} else {
+			val.mv_size = 41;
+			if (new_sha1)
+				val.mv_data  = sha1_to_hex(new_sha1);
+			else
+				val.mv_data = sha1_to_hex(null_sha1);
+			mdb_put_or_die(&transaction->info, &key, &val, 0);
+		}
+	}
+
+	if (flags & REF_DELETING) {
+		if (mdb_del_or_die(&transaction->info, &key, NULL)) {
+			if (old_sha1 && !is_null_sha1(old_sha1)) {
+				strbuf_addf(err, "No such ref %s", refname);
+				return TRANSACTION_GENERIC_ERROR;
+			}
+		}
+		lmdb_delete_reflog(orig_refname);
+	} else if (!(flags & REF_NO_REFLOG)) {
+		if (log_ref_write(orig_refname, resolved_sha1,
+				  new_sha1 ? new_sha1 : null_sha1,
+				  msg, flags, err) < 0)
+			return -1;
+		if (strcmp (refname, orig_refname) &&
+		    log_ref_write(refname, resolved_sha1,
+				  new_sha1 ? new_sha1 : null_sha1,
+				  msg, flags, err) < 0)
+			return -1;
+	}
+
+	return 0;
+}
+
+static int rename_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
+			     const char *email, unsigned long timestamp, int tz,
+			     const char *message, void *cb_data)
+{
+
+	const char *newrefname = cb_data;
+	MDB_val key, new_key, val;
+
+	assert(transaction.cursor);
+
+	if (mdb_cursor_get_or_die(transaction.cursor, &key, &val, MDB_GET_CURRENT))
+		die("renaming ref: mdb_cursor_get failed to get current");
+
+	new_key.mv_size = strlen(newrefname) + 5 + 1 + 8;
+	new_key.mv_data = xmalloc(new_key.mv_size);
+	strcpy(new_key.mv_data, "logs/");
+	strcpy(new_key.mv_data + 5, newrefname);
+	memcpy(new_key.mv_data + new_key.mv_size - 8, key.mv_data + key.mv_size - 8, 8);
+	mdb_put_or_die(&transaction.info, &new_key, &val, 0);
+	mdb_cursor_del_or_die(transaction.cursor, 0);
+	free(new_key.mv_data);
+	return 0;
+}
+
+static int lmdb_rename_ref(const char *oldref, const char *newref, const char *logmsg)
+{
+	unsigned char orig_sha1[20];
+	int flag = 0;
+	int log = reflog_exists(oldref);
+	const char *symref = NULL;
+	struct strbuf err = STRBUF_INIT;
+	struct ref_transaction *ref_transaction;
+
+	assert(!in_write_transaction());
+
+	if (!strcmp(oldref, newref))
+		return 0;
+
+	ref_transaction = lmdb_transaction_begin_flags_or_die(0);
+
+	symref = resolve_ref_unsafe(oldref, RESOLVE_REF_READING,
+				    orig_sha1, &flag);
+	if (flag & REF_ISSYMREF)
+		return error("refname %s is a symbolic ref, renaming it is not supported",
+			oldref);
+	if (!symref)
+		return error("refname %s not found", oldref);
+
+	if (!rename_ref_available(oldref, newref))
+		return 1;
+
+	if (log) {
+		struct strbuf old_log_sentinel = STRBUF_INIT;
+		MDB_val key;
+		int log_all;
+
+		log_all = log_all_ref_updates;
+		log_all_ref_updates = 1;
+		if (safe_create_reflog(newref, 0, &err)) {
+			error("can't create reflog for %s: %s", newref, err.buf);
+			strbuf_release(&err);
+			return 1;
+		}
+		log_all_ref_updates = log_all;
+
+		for_each_reflog_ent(oldref, rename_reflog_ent, (void *)newref);
+		strbuf_addf(&old_log_sentinel, "logs/%sxxxxxxxx", oldref);
+		memset(old_log_sentinel.buf + old_log_sentinel.len - 8, 0, 8);
+
+		key.mv_size = old_log_sentinel.len;
+		key.mv_data = old_log_sentinel.buf;
+		/* It's OK if the old reflog is missing */
+		mdb_del_or_die(&transaction.info, &key, NULL);
+
+		strbuf_release(&old_log_sentinel);
+	}
+
+	if (ref_transaction_delete(ref_transaction, oldref,
+		orig_sha1, REF_NODEREF, NULL, &err)) {
+		error("unable to delete old %s", oldref);
+		return 1;
+	}
+
+	if (lmdb_transaction_update(ref_transaction, newref, orig_sha1, NULL,
+				    0, logmsg, &err)) {
+		error("%s", err.buf);
+		strbuf_release(&err);
+		return 1;
+	}
+
+	if (lmdb_transaction_commit(ref_transaction, &err)) {
+		error("%s", err.buf);
+		strbuf_release(&err);
+		return 1;
+	}
+
+	return 0;
+
+}
+
+static int lmdb_for_each_reflog_ent_order(const char *refname,
+					  each_reflog_ent_fn fn,
+					  void *cb_data, int reverse)
+{
+	MDB_val key, val;
+	char *search_key;
+	char *log_path;
+	int len;
+	MDB_cursor *cursor;
+	int ret = 0;
+	struct strbuf sb = STRBUF_INIT;
+	enum MDB_cursor_op direction = reverse ? MDB_PREV : MDB_NEXT;
+	uint64_t zero = 0ULL;
+
+	len = strlen(refname) + 6;
+	log_path = xmalloc(len);
+	search_key = xmalloc(len + 1);
+	sprintf(log_path, "logs/%s", refname);
+	strcpy(search_key, log_path);
+
+	if (reverse) {
+		/*
+		 * For a reverse search, start at the key
+		 * lexicographically after the searched-for key.
+		 * That's the one with \001 appended to the key.
+		 */
+
+		search_key[len - 1] = 1;
+		search_key[len] = 0;
+		key.mv_size = len + 1;
+	} else {
+		key.mv_size = len;
+	}
+
+	key.mv_data = search_key;
+
+	lmdb_transaction_begin_flags_or_die(MDB_RDONLY);
+
+	mdb_cursor_open_or_die(&transaction.info, &cursor);
+
+	transaction.cursor = cursor;
+
+	/*
+	 * MDB's cursor API requires that the first mdb_cursor_get be
+	 * called with MDB_SET_RANGE.  For reverse searches, this will
+	 * give us the entry one-past the entry we're looking for, so
+	 * we should jump back using MDB_PREV.
+	 */
+	mdb_cursor_get_or_die(cursor, &key, &val, MDB_SET_RANGE);
+	if (direction == MDB_PREV)
+		mdb_cursor_get_or_die(cursor, &key, &val, direction);
+
+	do {
+		if (key.mv_size < len)
+			break;
+
+		if (!starts_with(key.mv_data, log_path) || ((char *)key.mv_data)[len - 1] != 0)
+			break;
+
+		if (!memcmp(&zero, ((char *)key.mv_data) + key.mv_size - 8, 8))
+			continue;
+
+		assert(val.mv_size != 0);
+
+		strbuf_add(&sb, val.mv_data, val.mv_size - 1);
+		ret = show_one_reflog_ent(&sb, fn, cb_data);
+		if (ret)
+			break;
+
+		strbuf_reset(&sb);
+	} while (!mdb_cursor_get_or_die(cursor, &key, &val, direction));
+
+	strbuf_release(&sb);
+	free(log_path);
+	free(search_key);
+	mdb_cursor_close(cursor);
+	return ret;
+}
+
+static int lmdb_for_each_reflog_ent(const char *refname,
+				    each_reflog_ent_fn fn,
+				    void *cb_data)
+{
+	if (ref_type(refname) != REF_TYPE_NORMAL)
+		return files_for_each_reflog_ent(refname, fn, cb_data);
+	return lmdb_for_each_reflog_ent_order(refname, fn, cb_data, 0);
+}
+
+static int lmdb_for_each_reflog_ent_reverse(const char *refname,
+					    each_reflog_ent_fn fn,
+					    void *cb_data)
+{
+	if (ref_type(refname) != REF_TYPE_NORMAL)
+		return files_for_each_reflog_ent_reverse(refname, fn, cb_data);
+	return lmdb_for_each_reflog_ent_order(refname, fn, cb_data, 1);
+}
+
+static int lmdb_reflog_exists(const char *refname)
+{
+	MDB_val key, val;
+	char *log_path;
+	int len;
+	MDB_cursor *cursor;
+	int ret = 1;
+
+	if (ref_type(refname) != REF_TYPE_NORMAL)
+		return files_reflog_exists(refname);
+
+	len = strlen(refname) + 6;
+	log_path = xmalloc(len);
+	sprintf(log_path, "logs/%s", refname);
+
+	key.mv_data = log_path;
+	key.mv_size = len;
+
+	lmdb_transaction_begin_flags_or_die(MDB_RDONLY);
+	mdb_cursor_open_or_die(&transaction.info, &cursor);
+
+	if (mdb_cursor_get_or_die(cursor, &key, &val, MDB_SET_RANGE)) {
+		ret = 0;
+	} else if (!starts_with(key.mv_data, log_path)) {
+		ret = 0;
+	}
+
+	free(log_path);
+	mdb_cursor_close(cursor);
+
+	return ret;
+}
+
+struct wrapped_each_ref_fn {
+	each_ref_fn *fn;
+	void *cb_data;
+};
+
+static int check_reflog(const char *refname,
+			const struct object_id *oid, int flags, void *cb_data)
+{
+	struct wrapped_each_ref_fn *wrapped = cb_data;
+
+	if (reflog_exists(refname))
+		return wrapped->fn(refname, oid, 0, wrapped->cb_data);
+
+	return 0;
+}
+
+static int lmdb_for_each_reflog(each_ref_fn fn, void *cb_data)
+{
+	struct wrapped_each_ref_fn wrapped = {fn, cb_data};
+	int result = head_ref(fn, cb_data);
+	if (result)
+	    return result;
+	return for_each_ref(check_reflog, &wrapped);
+}
+
+static int lmdb_create_reflog(const char *refname, int force_create, struct strbuf *err)
+{
+	/*
+	 * We mark that there is a reflog by creating a key of the
+	 * form logs/$refname followed by nine \0 (one for
+	 * string-termination, 8 in lieu of a timestamp), with an empty
+	 * value.
+	 */
+
+	int in_transaction = in_write_transaction();
+	MDB_val key, val;
+
+	if (!force_create && !should_autocreate_reflog(refname))
+		return 0;
+
+	if (!in_transaction)
+		lmdb_transaction_begin_flags_or_die(0);
+
+	key.mv_size = strlen(refname) + 5 + 1 + 8;
+	key.mv_data = xcalloc(1, key.mv_size);
+	sprintf((char *)key.mv_data, "logs/%s", refname);
+	val.mv_size = 0;
+	val.mv_data = NULL;
+	mdb_put_or_die(&transaction.info, &key, &val, 0);
+
+	free(key.mv_data);
+	if (!in_transaction)
+		return lmdb_transaction_commit(
+			(struct ref_transaction *)&transaction, err);
+	return 0;
+}
+
+struct expire_reflog_cb {
+	unsigned int flags;
+	reflog_expiry_should_prune_fn *should_prune_fn;
+	void *policy_cb;
+	unsigned char last_kept_sha1[20];
+};
+
+static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
+			     const char *email, unsigned long timestamp, int tz,
+			     const char *message, void *cb_data)
+{
+	struct expire_reflog_cb *cb = cb_data;
+	struct expire_reflog_policy_cb *policy_cb = cb->policy_cb;
+
+	if (cb->flags & EXPIRE_REFLOGS_REWRITE)
+		osha1 = cb->last_kept_sha1;
+
+	if ((*cb->should_prune_fn)(osha1, nsha1, email, timestamp, tz,
+				   message, policy_cb)) {
+		if (cb->flags & EXPIRE_REFLOGS_DRY_RUN)
+			printf("would prune %s", message);
+		else {
+			if (cb->flags & EXPIRE_REFLOGS_VERBOSE)
+				printf("prune %s", message);
+
+			mdb_cursor_del_or_die(transaction.cursor, 0);
+		}
+	} else {
+		hashcpy(cb->last_kept_sha1, nsha1);
+		if (cb->flags & EXPIRE_REFLOGS_VERBOSE)
+			printf("keep %s", message);
+	}
+	return 0;
+}
+
+static int write_ref(const char *refname, const unsigned char *sha1)
+{
+	struct strbuf err = STRBUF_INIT;
+	struct ref_transaction *transaction;
+
+	transaction = lmdb_transaction_begin(&err);
+	if (!transaction) {
+		error("%s", err.buf);
+		strbuf_release(&err);
+		return -1;
+	}
+
+	if (lmdb_transaction_update(transaction, refname, sha1, NULL,
+				    REF_NO_REFLOG, NULL, &err)) {
+		error("%s", err.buf);
+		strbuf_release(&err);
+		return -1;
+	}
+
+	if (lmdb_transaction_commit(transaction, &err)) {
+		error("%s", err.buf);
+		strbuf_release(&err);
+		return -1;
+	}
+
+	return 0;
+}
+
+int lmdb_reflog_expire(const char *refname, const unsigned char *sha1,
+		       unsigned int flags,
+		       reflog_expiry_prepare_fn prepare_fn,
+		       reflog_expiry_should_prune_fn should_prune_fn,
+		       reflog_expiry_cleanup_fn cleanup_fn,
+		       void *policy_cb_data)
+{
+	struct expire_reflog_cb cb;
+	int dry_run = flags & EXPIRE_REFLOGS_DRY_RUN;
+	int status = 0;
+	struct strbuf err = STRBUF_INIT;
+	unsigned char resolved_sha1[20];
+	int type;
+
+	if (ref_type(refname) != REF_TYPE_NORMAL)
+		return files_reflog_expire(refname, sha1, flags, prepare_fn,
+					   should_prune_fn, cleanup_fn,
+					   policy_cb_data);
+
+	memset(&cb, 0, sizeof(cb));
+	cb.flags = flags;
+	cb.policy_cb = policy_cb_data;
+	cb.should_prune_fn = should_prune_fn;
+
+	lmdb_transaction_begin_flags_or_die(dry_run ? MDB_RDONLY : 0);
+
+	check_ref(transaction.info.txn, refname, sha1,
+		  resolved_sha1, 0, &type);
+
+	(*prepare_fn)(refname, sha1, cb.policy_cb);
+	lmdb_for_each_reflog_ent(refname, expire_reflog_ent, &cb);
+	(*cleanup_fn)(cb.policy_cb);
+
+	if (!dry_run) {
+		/*
+		 * It doesn't make sense to adjust a reference pointed
+		 * to by a symbolic ref based on expiring entries in
+		 * the symbolic reference's reflog. Nor can we update
+		 * a reference if there are no remaining reflog
+		 * entries.
+		 */
+		int update = (flags & EXPIRE_REFLOGS_UPDATE_REF) &&
+			!(type & REF_ISSYMREF) &&
+			!is_null_sha1(cb.last_kept_sha1);
+
+		if (lmdb_transaction_commit(
+			    (struct ref_transaction *)&transaction, &err)) {
+			status |= error("couldn't write logs/%s: %s", refname,
+					err.buf);
+			strbuf_release(&err);
+		} else if (update &&
+			   write_ref(refname, cb.last_kept_sha1)) {
+			status |= error("couldn't set %s",
+					refname);
+		}
+	}
+	return status;
+}
+
+static int lmdb_pack_refs(unsigned int flags)
+{
+	/* This concept does not exist in this backend. */
+	return 0;
+}
+
+static int lmdb_peel_ref(const char *refname, unsigned char *sha1)
+{
+	int flag;
+	unsigned char base[20];
+
+	if (read_ref_full(refname, RESOLVE_REF_READING, base, &flag))
+		return -1;
+
+	return peel_object(base, sha1);
+}
+
+static int lmdb_create_symref(struct ref_transaction *trans,
+			      const char *ref_target,
+			      const char *refs_heads_master,
+			      const char *logmsg)
+{
+
+	struct strbuf err = STRBUF_INIT;
+	unsigned char old_sha1[20], new_sha1[20];
+	MDB_val key, val;
+	char *valdata;
+	struct lmdb_transaction *transaction = (struct lmdb_transaction *)trans;
+
+	if (logmsg && read_ref(ref_target, old_sha1))
+		hashclr(old_sha1);
+
+	key.mv_size = strlen(ref_target) + 1;
+	key.mv_data = xstrdup(ref_target);
+
+	val.mv_size = strlen(refs_heads_master) + 1 + 5;
+	valdata = xmalloc(val.mv_size);
+	sprintf(valdata, "ref: %s", refs_heads_master);
+	val.mv_data = valdata;
+
+	mdb_put_or_die(&transaction->info, &key, &val, 0);
+
+	if (logmsg && !read_ref(refs_heads_master, new_sha1) &&
+	    log_ref_write(ref_target, old_sha1, new_sha1, logmsg, 0, &err)) {
+		error("log_ref_write failed: %s", err.buf);
+		strbuf_release(&err);
+	}
+
+	free(key.mv_data);
+	free(valdata);
+
+	return 0;
+}
+
+MDB_env *submodule_txn_begin(struct lmdb_transaction_info *info)
+{
+	int ret;
+	MDB_env *submodule_env = NULL;
+	struct strbuf path = STRBUF_INIT;
+
+	strbuf_git_path_submodule(&path, info->submodule, "refdb");
+
+	if (!is_directory(path.buf))
+		goto done;
+
+	mkdir(path.buf, 0775);
+
+	init_env(&submodule_env, path.buf);
+
+	if ((ret = mdb_txn_begin(submodule_env, NULL, MDB_RDONLY, &info->txn)) != MDB_SUCCESS) {
+		die("mdb_txn_begin failed: %s", mdb_strerror(ret));
+
+	}
+	if ((ret = mdb_dbi_open(info->txn, NULL, 0, &info->dbi)) != MDB_SUCCESS) {
+		die("mdb_txn_open failed: %s", mdb_strerror(ret));
+	}
+
+done:
+	strbuf_release(&path);
+	return submodule_env;
+}
+
+static int lmdb_resolve_gitlink_ref(const char *submodule, const char *refname,
+				     unsigned char *sha1)
+{
+	struct lmdb_transaction_info info;
+	MDB_env *submodule_env;
+	int result;
+
+	info.txn = NULL;
+	info.submodule = submodule;
+	submodule_env = submodule_txn_begin(&info);
+	if (!submodule_env)
+		return -1;
+	result = !resolve_ref_unsafe_txn(&info, refname,
+					 RESOLVE_REF_READING, sha1, NULL);
+
+	mdb_txn_abort(info.txn);
+	mdb_env_close(submodule_env);
+	return result ? -1 : 0;
+}
+
+static int do_head_ref(const char *submodule, each_ref_fn fn, void *cb_data)
+{
+	struct object_id oid;
+	int flag;
+
+	if (submodule) {
+		if (resolve_gitlink_ref(submodule, "HEAD", oid.hash) == 0)
+			return fn("HEAD", &oid, 0, cb_data);
+
+		return 0;
+	}
+
+	if (!read_ref_full("HEAD", RESOLVE_REF_READING, oid.hash, &flag))
+		return fn("HEAD", &oid, flag, cb_data);
+
+	return 0;
+}
+
+static int lmdb_head_ref(each_ref_fn fn, void *cb_data)
+{
+	return do_head_ref(NULL, fn, cb_data);
+}
+
+static int lmdb_head_ref_submodule(const char *submodule, each_ref_fn fn,
+				    void *cb_data)
+{
+	return do_head_ref(submodule, fn, cb_data);
+}
+
+/*
+ * Call fn for each reference for which the refname begins with base.
+ * If trim is non-zero, then trim that many characters off the
+ * beginning of each refname before passing the refname to fn.  flags
+ * can be DO_FOR_EACH_INCLUDE_BROKEN to include broken references in
+ * the iteration.  If fn ever returns a non-zero value, stop the
+ * iteration and return that value; otherwise, return 0.
+ */
+static int do_for_each_ref(struct lmdb_transaction_info *info,
+			   const char *base, each_ref_fn fn, int trim,
+			   int flags, void *cb_data)
+{
+
+	MDB_val key, val;
+	MDB_cursor *cursor;
+	int baselen;
+	char *search_key;
+	int retval;
+	int mdb_ret;
+
+	retval = do_for_each_per_worktree_ref(info->submodule, base, fn, trim,
+					      flags, cb_data);
+	if (retval)
+		return retval;
+
+	if (ref_paranoia < 0)
+		ref_paranoia = git_env_bool("GIT_REF_PARANOIA", 0);
+	if (ref_paranoia)
+		flags |= DO_FOR_EACH_INCLUDE_BROKEN;
+
+	if (!base || !*base) {
+		base = "refs/";
+		trim = 0;
+	}
+
+	baselen = strlen(base);
+	search_key = xmalloc(baselen + 1);
+	strcpy(search_key, base);
+	key.mv_size = baselen + 1;
+	key.mv_data = search_key;
+
+	mdb_cursor_open_or_die(info, &cursor);
+
+	mdb_ret = mdb_cursor_get_or_die(cursor, &key, &val, MDB_SET_RANGE);
+
+	while (!mdb_ret) {
+		struct object_id oid;
+		int parsed_flags = 0;
+
+		if (memcmp(key.mv_data, base, baselen))
+			break;
+
+		parse_ref_data(info, key.mv_data + (trim ? baselen : 0),
+			       val.mv_data, oid.hash, 0, &parsed_flags, 0);
+
+		if (flags & DO_FOR_EACH_INCLUDE_BROKEN ||
+		    (!(parsed_flags & REF_ISBROKEN) &&
+		     has_sha1_file(oid.hash))) {
+			retval = fn(key.mv_data + (trim ? baselen : 0), &oid, parsed_flags, cb_data);
+			if (retval)
+				break;
+		}
+
+		mdb_ret = mdb_cursor_get_or_die(cursor, &key, &val, MDB_NEXT);
+	}
+
+	mdb_cursor_close(cursor);
+	free(search_key);
+
+	return retval;
+}
+
+static int lmdb_for_each_ref(each_ref_fn fn, void *cb_data)
+{
+	lmdb_transaction_begin_flags_or_die(MDB_RDONLY);
+	return do_for_each_ref(&transaction.info, "", fn, 0, 0, cb_data);
+}
+
+static int lmdb_for_each_ref_submodule(const char *submodule, each_ref_fn fn,
+					void *cb_data)
+{
+	struct lmdb_transaction_info info;
+	MDB_env *submodule_env;
+	int result;
+
+	if (!submodule)
+		return for_each_ref(fn, cb_data);
+
+	info.txn = NULL;
+	info.submodule = submodule;
+
+	submodule_env = submodule_txn_begin(&info);
+	if (!submodule_env)
+		return 0;
+	result = do_for_each_ref(&info, "", fn, 0, 0, cb_data);
+	mdb_txn_abort(info.txn);
+	mdb_env_close(submodule_env);
+	return result;
+}
+
+static int lmdb_for_each_ref_in(const char *prefix, each_ref_fn fn,
+				 void *cb_data)
+{
+	lmdb_transaction_begin_flags_or_die(MDB_RDONLY);
+	return do_for_each_ref(&transaction.info, prefix, fn, strlen(prefix),
+			       0, cb_data);
+}
+
+static int lmdb_for_each_fullref_in(const char *prefix, each_ref_fn fn,
+				     void *cb_data, unsigned int broken)
+{
+	unsigned int flag = 0;
+
+	if (broken)
+		flag = DO_FOR_EACH_INCLUDE_BROKEN;
+	lmdb_transaction_begin_flags_or_die(MDB_RDONLY);
+	return do_for_each_ref(&transaction.info, prefix, fn, 0, flag, cb_data);
+}
+
+static int lmdb_for_each_ref_in_submodule(const char *submodule,
+					   const char *prefix,
+					   each_ref_fn fn, void *cb_data)
+{
+	struct lmdb_transaction_info info = {NULL};
+	MDB_env *submodule_env;
+	int result;
+
+	if (!submodule)
+		return for_each_ref_in(prefix, fn, cb_data);
+
+	info.submodule = submodule;
+	submodule_env = submodule_txn_begin(&info);
+	if (!submodule_env)
+		return 0;
+	result = do_for_each_ref(&info, prefix, fn,
+				 strlen(prefix), 0, cb_data);
+	mdb_txn_abort(info.txn);
+	mdb_env_close(submodule_env);
+	return result;
+}
+
+static int lmdb_for_each_replace_ref(each_ref_fn fn, void *cb_data)
+{
+	lmdb_transaction_begin_flags_or_die(MDB_RDONLY);
+	return do_for_each_ref(&transaction.info, git_replace_ref_base, fn,
+			       strlen(git_replace_ref_base), 0, cb_data);
+}
+
+static int lmdb_for_each_reftype_fullpath(each_ref_fn fn, char *type, unsigned int broken, void *cb_data)
+{
+	unsigned int flag = 0;
+
+	if (broken)
+		flag = DO_FOR_EACH_INCLUDE_BROKEN;
+	lmdb_transaction_begin_flags_or_die(MDB_RDONLY);
+	return do_for_each_ref(&transaction.info, type, fn, 0, flag, cb_data);
+}
+
+static int lmdb_for_each_namespaced_ref(each_ref_fn fn, void *cb_data)
+{
+	struct strbuf buf = STRBUF_INIT;
+	int ret;
+	strbuf_addf(&buf, "%srefs/", get_git_namespace());
+	lmdb_transaction_begin_flags_or_die(MDB_RDONLY);
+	ret = do_for_each_ref(&transaction.info, buf.buf, fn, 0, 0, cb_data);
+	strbuf_release(&buf);
+	return ret;
+}
+
+static int lmdb_for_each_rawref(each_ref_fn fn, void *cb_data)
+{
+	lmdb_transaction_begin_flags_or_die(MDB_RDONLY);
+	return do_for_each_ref(&transaction.info, "", fn, 0,
+			       DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
+}
+
+/* For testing only! */
+int test_refdb_raw_read(const char *key)
+{
+	MDB_val key_val, val;
+	char *keydup;
+	int ret;
+
+	lmdb_transaction_begin_flags_or_die(MDB_RDONLY);
+	keydup = xstrdup(key);
+	key_val.mv_data = keydup;
+	key_val.mv_size = strlen(key) + 1;
+
+	ret = mdb_get_or_die(&transaction.info, &key_val, &val);
+	free(keydup);
+	switch (ret) {
+	case 0:
+		printf("%s\n", (char *)val.mv_data);
+		return 0;
+	case MDB_NOTFOUND:
+		fprintf(stderr, "%s not found\n", key);
+		return 1;
+	default:
+		return 2;
+	}
+}
+
+/* For testing only! */
+void test_refdb_raw_write(const char *key, const char *value)
+{
+	MDB_val key_val, val;
+	char *keydup, *valdup;
+	struct strbuf err = STRBUF_INIT;
+
+	lmdb_transaction_begin_flags_or_die(0);
+
+	keydup = xstrdup(key);
+	key_val.mv_data = keydup;
+	key_val.mv_size = strlen(key) + 1;
+
+	valdup = xstrdup(value);
+	val.mv_data = valdup;
+	val.mv_size = strlen(value) + 1;
+
+	mdb_put_or_die(&transaction.info, &key_val, &val, 0);
+	assert(lmdb_transaction_commit((struct ref_transaction *)&transaction,
+				       &err) == 0);
+
+	free(keydup);
+	free(valdup);
+}
+
+/* For testing only! */
+int test_refdb_raw_delete(const char *key)
+{
+	MDB_val key_val;
+	char *keydup;
+	int ret;
+
+	struct strbuf err = STRBUF_INIT;
+
+	lmdb_transaction_begin_flags_or_die(0);
+	keydup = xstrdup(key);
+	key_val.mv_data = keydup;
+	key_val.mv_size = strlen(key) + 1;
+
+	ret = mdb_del_or_die(&transaction.info, &key_val, NULL);
+
+	assert(lmdb_transaction_commit((struct lmdb_transaction *)&transaction,
+				       &err) == 0);
+
+	free(keydup);
+	return ret;
+}
+
+static int print_raw_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
+				const char *email, unsigned long timestamp,
+				int tz, const char *message, void *cb_data)
+{
+	int *any = cb_data;
+	*any = 1;
+
+	if (*message != '\n')
+		printf("%s %s %s %lu %+05d\t%s", sha1_to_hex(osha1),
+		       sha1_to_hex(nsha1),
+		       email, timestamp, tz, message);
+	else
+		printf("%s %s %s %lu %+05d\n", sha1_to_hex(osha1),
+		       sha1_to_hex(nsha1),
+		       email, timestamp, tz);
+	return 0;
+}
+
+/* For testing only! */
+int test_refdb_raw_reflog(const char *refname)
+{
+	MDB_val key, val;
+	char *search_key;
+	char *log_path;
+	int len;
+	MDB_cursor *cursor;
+	int result = 1;
+	int any = 0;
+
+	for_each_reflog_ent(refname, print_raw_reflog_ent, &any);
+
+	return !any;
+}
+
+/* For testing only! */
+void test_refdb_raw_delete_reflog(char *refname)
+{
+	MDB_val key, val;
+	int mdb_ret;
+	char *search_key;
+	MDB_cursor *cursor;
+	int len;
+	struct strbuf err = STRBUF_INIT;
+
+	if (refname) {
+		len = strlen(refname) + 5 + 1; /* logs/ + 0*/
+		search_key = xmalloc(len);
+		sprintf(search_key, "logs/%s", refname);
+	} else {
+		len = 6; /* logs/ + 0*/
+		search_key = xstrdup("logs/");
+	}
+	key.mv_data = search_key;
+	key.mv_size = len;
+
+	lmdb_transaction_begin_flags_or_die(0);
+
+	mdb_cursor_open_or_die(&transaction.info, &cursor);
+
+	mdb_ret = mdb_cursor_get_or_die(cursor, &key, &val, MDB_SET_RANGE);
+	while (!mdb_ret) {
+		if (!starts_with(key.mv_data, search_key))
+			break;
+		if (refname && ((char *)val.mv_data)[len - 1] == 0)
+			break;
+
+		mdb_cursor_del_or_die(cursor, 0);
+		mdb_ret = mdb_cursor_get_or_die(cursor, &key, &val, MDB_NEXT);
+	}
+
+	free(search_key);
+	mdb_cursor_close(cursor);
+
+	assert(lmdb_transaction_commit(
+		       (struct ref_transaction *)&transaction, &err) == 0);
+	return;
+}
+
+static void format_lmdb_reflog_ent(struct strbuf *dst, struct strbuf *src)
+{
+	unsigned char osha1[20], nsha1[20];
+	const char *msg;
+
+	get_sha1_hex(src->buf, osha1);
+	get_sha1_hex(src->buf + 41, nsha1);
+
+	msg = strchr(src->buf + 82, '\n');
+	assert(msg);
+	msg += 1;
+
+	format_reflog_entry(dst, osha1, nsha1, src->buf + 82, msg);
+}
+
+/* For testing only! */
+void test_refdb_raw_append_reflog(const char *refname)
+{
+	struct strbuf input = STRBUF_INIT;
+	uint64_t now = getnanotime();
+	MDB_val key, val;
+	struct strbuf err = STRBUF_INIT;
+
+	key.mv_size = strlen(refname) + 14;
+	key.mv_data = xcalloc(1, key.mv_size);
+	sprintf(key.mv_data, "logs/%s", refname);
+
+	lmdb_transaction_begin_flags_or_die(0);
+
+	/* We do not remove the header here, because this is just for
+	 * tests, so it's OK to be a bit inefficient */
+
+	while (strbuf_getwholeline(&input, stdin, '\n') != EOF) {
+		struct strbuf sb = STRBUF_INIT;
+
+		/* "logs/" + \0 + 8-byte timestamp for sorting and expiry */
+		write_u64(key.mv_data + key.mv_size - 8, htonll(now++));
+
+		/* Convert the input from files-reflog format to
+		 * lmdb-reflog-format */
+
+		format_lmdb_reflog_ent(&sb, &input);
+		val.mv_data = sb.buf;
+		val.mv_size = sb.len + 1;
+		mdb_put_or_die(&transaction.info, &key, &val, 0);
+		strbuf_reset(&sb);
+		input.len = 0;
+	}
+
+	assert(lmdb_transaction_commit(&transaction, &err) == 0);
+	free(key.mv_data);
+}
+
+struct ref_be refs_be_lmdb = {
+	NULL,
+	"lmdb",
+	lmdb_init_backend,
+	lmdb_initdb,
+	lmdb_transaction_begin,
+	lmdb_transaction_update,
+	lmdb_transaction_create,
+	lmdb_transaction_delete,
+	lmdb_transaction_verify,
+	lmdb_transaction_commit,
+	lmdb_transaction_commit, /* initial commit */
+	lmdb_transaction_free,
+	lmdb_rename_ref,
+	lmdb_for_each_reflog_ent,
+	lmdb_for_each_reflog_ent_reverse,
+	lmdb_for_each_reflog,
+	lmdb_reflog_exists,
+	lmdb_create_reflog,
+	lmdb_delete_reflog,
+	lmdb_reflog_expire,
+	lmdb_resolve_ref_unsafe,
+	lmdb_verify_refname_available,
+	lmdb_pack_refs,
+	lmdb_peel_ref,
+	lmdb_create_symref,
+	lmdb_resolve_gitlink_ref,
+	lmdb_head_ref,
+	lmdb_head_ref_submodule,
+	lmdb_for_each_ref,
+	lmdb_for_each_ref_submodule,
+	lmdb_for_each_ref_in,
+	lmdb_for_each_fullref_in,
+	lmdb_for_each_ref_in_submodule,
+	lmdb_for_each_rawref,
+	lmdb_for_each_namespaced_ref,
+	lmdb_for_each_replace_ref,
+	lmdb_for_each_reftype_fullpath,
+};
diff --git a/refs.h b/refs.h
index ac88371..f499093 100644
--- a/refs.h
+++ b/refs.h
@@ -641,6 +641,15 @@ extern int reflog_expire(const char *refname, const unsigned char *sha1,
 			 reflog_expiry_cleanup_fn cleanup_fn,
 			 void *policy_cb_data);
 
+/*
+ * Read the refdb configuration data out of the config file
+ */
+struct refdb_config_data {
+	const char *refs_backend_type;
+	const char *refs_base;
+};
+int refdb_config(const char *var, const char *value, void *ptr);
+
 /* refs backends */
 typedef void (*ref_backend_init_fn)(void *data);
 typedef int (*ref_backend_initdb_fn)(struct strbuf *err, int shared);
@@ -763,6 +772,7 @@ struct ref_be {
 
 
 extern struct ref_be refs_be_files;
+extern struct ref_be refs_be_lmdb;
 int set_refs_backend(const char *name, void *init_data);
 void register_refs_backend(struct ref_be *be);
 
diff --git a/setup.c b/setup.c
index b264471..3e31112 100644
--- a/setup.c
+++ b/setup.c
@@ -1,5 +1,6 @@
 #include "cache.h"
 #include "dir.h"
+#include "refs.h"
 #include "string-list.h"
 
 static int inside_git_dir = -1;
@@ -262,16 +263,26 @@ int get_common_dir_noenv(struct strbuf *sb, const char *gitdir)
 	return ret;
 }
 
+int refdb_config(const char *var, const char *value, void *ptr)
+{
+       struct refdb_config_data *cdata = ptr;
+
+       if (!strcmp(var, "core.refs-backend-type"))
+	       cdata->refs_backend_type = strdup((char *)value);
+       return 0;
+}
+
 /*
  * Test if it looks like we're at a git directory.
  * We want to see:
  *
  *  - either an objects/ directory _or_ the proper
  *    GIT_OBJECT_DIRECTORY environment variable
- *  - a refs/ directory
- *  - either a HEAD symlink or a HEAD file that is formatted as
- *    a proper "ref:", or a regular file HEAD that has a properly
- *    formatted sha1 object name.
+ *  - a refdb/ directory or
+ *    - a refs/ directory
+ *    - either a HEAD symlink or a HEAD file that is formatted as
+ *      a proper "ref:", or a regular file HEAD that has a properly
+ *      formatted sha1 object name.
  */
 int is_git_directory(const char *suspect)
 {
@@ -302,8 +313,13 @@ int is_git_directory(const char *suspect)
 
 	strbuf_setlen(&path, len);
 	strbuf_addstr(&path, "/refs");
-	if (access(path.buf, X_OK))
-		goto done;
+
+	if (access(path.buf, X_OK)) {
+		strbuf_setlen(&path, len);
+		strbuf_addstr(&path, "/refdb");
+		if (access(path.buf, X_OK))
+			goto done;
+	}
 
 	ret = 1;
 done:
diff --git a/test-refs-be-lmdb.c b/test-refs-be-lmdb.c
new file mode 100644
index 0000000..bb875ff
--- /dev/null
+++ b/test-refs-be-lmdb.c
@@ -0,0 +1,68 @@
+#include "cache.h"
+#include "string-list.h"
+#include "parse-options.h"
+#include "refs.h"
+
+static const char * const test_refs_be_lmdb_usage[] = {
+	"git test-refs-be-lmdb <key>",
+	"git test-refs-be-lmdb <key> <value>",
+	NULL,
+};
+
+int test_refdb_raw_read(const char *key);
+void test_refdb_raw_write(const char *key, const char *value);
+int test_refdb_raw_reflog(const char *refname);
+int test_refdb_raw_delete(const char *key);
+void test_refdb_raw_delete_reflog(const char *refname);
+void test_refdb_raw_append_reflog(const char *refname);
+
+int main(int argc, const char **argv)
+{
+	const char *delete = NULL;
+	const char *reflog = NULL;
+	const char *append_reflog = NULL;
+	int delete_missing_error = 0;
+	int clear_reflog = 0;
+	struct refdb_config_data config_data = {NULL};
+
+	struct option options[] = {
+		OPT_STRING('d', NULL, &delete, "branch", "delete refdb entry"),
+		OPT_STRING('l', NULL, &reflog, "branch", "show reflog"),
+		OPT_STRING('a', NULL, &append_reflog, "branch", "append to reflog"),
+		OPT_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."),
+		OPT_BOOL('x', NULL, &delete_missing_error,
+			 "deleting a missing key is an error"),
+		OPT_END(),
+	};
+
+	argc = parse_options(argc, argv, "", options, test_refs_be_lmdb_usage,
+			     0);
+
+	if (!append_reflog && !clear_reflog && !delete && !reflog && argc != 1 && argc != 2)
+		usage_with_options(test_refs_be_lmdb_usage,
+				   options);
+
+	git_config(git_default_config, NULL);
+
+	config_data.refs_backend_type = "lmdb";
+	config_data.refs_base = get_git_dir();
+
+	register_refs_backend(&refs_be_lmdb);
+	set_refs_backend("lmdb", &config_data);
+
+	if (clear_reflog) {
+		test_refdb_raw_delete_reflog(argv[0]);
+	} else if (append_reflog) {
+		test_refdb_raw_append_reflog(append_reflog);
+	} else if (reflog) {
+		return test_refdb_raw_reflog(reflog);
+	} else if (delete) {
+		if (test_refdb_raw_delete(delete) && delete_missing_error)
+			return 1;
+	} else if (argc == 1) {
+		return test_refdb_raw_read(argv[0]);
+	} else {
+		test_refdb_raw_write(argv[0], argv[1]);
+	}
+	return 0;
+}
-- 
2.4.2.644.g97b850b-twtrsrc
Junio C Hamano· Oct 2, 2015, 21:35 UTC · re: David Turner · lore

Re: [PATCH v2 42/43] refs: add LMDB refs backend

David Turner <dturner@twopensource.com> writes:
Show 12 quoted lines
> diff --git a/refs-be-lmdb.c b/refs-be-lmdb.c
> new file mode 100644
> index 0000000..99cbd29
> --- /dev/null
> +++ b/refs-be-lmdb.c
> @@ -0,0 +1,2003 @@
> +/*
> + ...
> + */
> +#include <lmdb.h>
> +#include <sys/uio.h>
> +#include "cache.h"

"git-compat-util.h" (or "cache.h", because it is well known and includes "git-compat-util.h" as the first thing before doing anything else) must be the first file included in any of our C files, unless it is a platform specific compat/ object.

Michael Haggerty· Oct 5, 2015, 15:47 UTC · re: David Turner · lore

Re: [PATCH v2 42/43] refs: add LMDB refs backend

On 09/29/2015 12:02 AM, David Turner wrote:
Show 18 quoted lines
> Add a database backend for refs using LMDB.  This backend runs git
> for-each-ref about 30% faster than the files backend with fully-packed
> refs on a repo with ~120k refs.  It's also about 4x faster than using
> fully-unpacked refs.  In addition, and perhaps more importantly , it
> avoids case-conflict issues on OS X.
> 
> LMDB has a few features that make it suitable for usage in git:
> 
> 1. It is relatively lightweight; it requires only one header file, and
> the library code takes under 64k at runtime.
> 
> 2. It is well-tested: it's been used in OpenLDAP for years.
> 
> 3. It's very fast.  LMDB's benchmarks show that it is among
> the fastest key-value stores.
> 
> 4. It has a relatively simple concurrency story; readers don't
> block writers and writers don't block readers.

It would be great if you would go into more detail about this point in the permanent technical documentation (Documentation/technical/refs-be-lmdb.txt): how exactly do readers & writers interact with each others, and how do writers interact with other writers?

I think you have said before that if one writer holds the write lock on the DB, then other writers fail immediately. Is that correct? If so, is that something that can be adjusted? I think it would be preferable for the second writer to retry acquiring the write lock for a little while with a timeout (as we now do when trying to acquire the packed-refs lock). Otherwise you could have the unhappy situation that somebody spends a long time pushing a packfile to a server, only to have the reference update be rejected at the last moment due to a lock conflict with another process that was touching completely different references. We already do before/after consistency checks when updating references, so you wouldn't even have to add such code in the backend yourself.

Show 115 quoted lines
> Ronnie Sahlberg's original version of this patchset used tdb.  The
> major disadvantage of tdb is that tdb is hard to build on OS X.  It's
> also not in homebrew.  So lmdb seemed simpler.
> 
> To test this backend's correctness, I hacked test-lib.sh and
> test-lib-functions.sh to run all tests under the refs backend. Dozens
> of tests use manual ref/reflog reading/writing, or create submodules
> without passing --refs-backend-type to git init.  If those tests are
> changed to use the update-ref machinery or test-refs-be-db (or, in the
> case of packed-refs, corrupt refs, and dumb fetch tests, are skipped),
> the only remaining failing tests are the git-new-workdir tests and the
> gitweb tests.
> 
> Signed-off-by: David Turner <dturner@twopensource.com>
> ---
>  .gitignore                               |    1 +
>  Documentation/git-clone.txt              |    2 +-
>  Documentation/git-init-db.txt            |    2 +-
>  Documentation/git-init.txt               |    6 +
>  Documentation/technical/refs-be-lmdb.txt |   39 +
>  Makefile                                 |   12 +
>  builtin/init-db.c                        |   13 +
>  config.c                                 |   27 +
>  configure.ac                             |   33 +
>  contrib/workdir/git-new-workdir          |    2 +
>  environment.c                            |    1 +
>  refs-be-lmdb.c                           | 2003 ++++++++++++++++++++++++++++++
>  refs.h                                   |   10 +
>  setup.c                                  |   28 +-
>  test-refs-be-lmdb.c                      |   68 +
>  15 files changed, 2239 insertions(+), 8 deletions(-)
>  create mode 100644 Documentation/technical/refs-be-lmdb.txt
>  create mode 100644 refs-be-lmdb.c
>  create mode 100644 test-refs-be-lmdb.c
> 
> diff --git a/.gitignore b/.gitignore
> index 1c2f832..7decc2f 100644
> --- a/.gitignore
> +++ b/.gitignore
> @@ -199,6 +199,7 @@
>  /test-path-utils
>  /test-prio-queue
>  /test-read-cache
> +/test-refs-be-lmdb
>  /test-regex
>  /test-revision-walking
>  /test-run-command
> diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt
> index d7a4cb0..6a4cf28 100644
> --- a/Documentation/git-clone.txt
> +++ b/Documentation/git-clone.txt
> @@ -219,7 +219,7 @@ objects from the source repository into a pack in the cloned repository.
>  
>  --refs-backend-type=<name>::
>  	Type of refs backend. Default is to use the original files based
> -	backend.
> +	backend. Set to "lmdb" to activate the lmdb database backend.
>  
>  <repository>::
>  	The (possibly remote) repository to clone from.  See the
> diff --git a/Documentation/git-init-db.txt b/Documentation/git-init-db.txt
> index 648a6cd..72fbd71 100644
> --- a/Documentation/git-init-db.txt
> +++ b/Documentation/git-init-db.txt
> @@ -9,7 +9,7 @@ git-init-db - Creates an empty Git repository
>  SYNOPSIS
>  --------
>  [verse]
> -'git init-db' [-q | --quiet] [--bare] [--template=<template_directory>] [--separate-git-dir <git dir>] [--shared[=<permissions>]]
> +'git init-db' [-q | --quiet] [--bare] [--template=<template_directory>] [--separate-git-dir <git dir>] [--shared[=<permissions>]] [--refs-backend-type=<name>]
>  
>  
>  DESCRIPTION
> diff --git a/Documentation/git-init.txt b/Documentation/git-init.txt
> index 8174d27..d352788 100644
> --- a/Documentation/git-init.txt
> +++ b/Documentation/git-init.txt
> @@ -12,6 +12,7 @@ SYNOPSIS
>  'git init' [-q | --quiet] [--bare] [--template=<template_directory>]
>  	  [--separate-git-dir <git dir>]
>  	  [--shared[=<permissions>]] [directory]
> +	  [--refs-backend-type=<name>]
>  
>  
>  DESCRIPTION
> @@ -113,6 +114,11 @@ does not exist, it will be created.
>  
>  --
>  
> +--refs-backend-type=<name>::
> +Type of refs backend. Default is to use the original "files" backend,
> +which stores ref data in files in .git/refs and .git/packed-refs.  Set
> +to "lmdb" to activate the lmdb database backend.
> +
>  TEMPLATE DIRECTORY
>  ------------------
>  
> diff --git a/Documentation/technical/refs-be-lmdb.txt b/Documentation/technical/refs-be-lmdb.txt
> new file mode 100644
> index 0000000..98c13ec
> --- /dev/null
> +++ b/Documentation/technical/refs-be-lmdb.txt
> @@ -0,0 +1,39 @@
> +Notes on the LMDB refs backend
> +==============================
> +
> +Design:
> +------
> +
> +Refs and reflogs are stored in a lmdb database in .git/refdb.  All
> +keys and values are \0-terminated.
> +
> +Keys for refs are the name of the ref (e.g. refs/heads/master).
> +Values are the value of the ref
> +(e.g. 61f23eb0f81357c19fa91e2b8c6f3906c3a8f9b0).

Please document more unambiguously whether the value is stored as 40-char hexadecimal or 20-byte binary.

Do you store "peeled" reference values for tags, as is done in packed-refs? I think that is an important optimization.

Show 7 quoted lines
> +All per-worktree refs (refs/bisect/* and HEAD) are store using
> +the traditional files-based backend.
> +
> +Reflogs are stored as a series of database entries.
> +
> +For an empty reflog, there is a "header" entry to show that a reflog
> +exists.
What key is used for the "header" entry? Does it have a value?
Show 5 quoted lines
> +         For non-empty reflogs, there is one entry per logged ref
> +update.  The key format is logs/[refname]\0[timestamp].  The timestamp
> +is a 64-bit unsigned integer number of nanoseconds since 1/1/1970.
> +This means that reflog entries are chronologically ordered.  Because
> +LMDB is a btree database, we can efficiently iterate over these keys.

Currently we discard the reflog for a reference when the reference is deleted. This is mostly due to a limitation of the filesystem-based storage scheme--otherwise the reflog left over from a deleted reference could prevent the creation of a reflog for another reference whose name overlaps with it (in the sense of is_refname_available()).

It is a pretty nasty limitation because it is one of the few ways that your actions can cause Git to lose data in a way that it is hard to recover from.

Have you thought about removing this limitation in the lbdb backend?
Show 11 quoted lines
> +Reflog values are in the same format as the original files-based
> +reflog.
> +
> +Weaknesses:
> +-----------
> +
> +The reflog format is somewhat inefficient: a binary format could store
> +reflog date/time information in somewhat less space.
> +
> +The rsync and file:// transports don't work yet, because they
> +don't use the refs API.
Do they fail gracefully?
Show 102 quoted lines
> +git new-workdir is incompatible with the lmdb backend.  Fortunately,
> +git new-workdir is deprecated, and worktrees work fine.
> diff --git a/Makefile b/Makefile
> index 43ceab0..e96f84b 100644
> --- a/Makefile
> +++ b/Makefile
> @@ -1036,6 +1036,17 @@ ifdef USE_LIBPCRE
>  	EXTLIBS += -lpcre
>  endif
>  
> +ifdef USE_LIBLMDB
> +	BASIC_CFLAGS += -DUSE_LIBLMDB
> +	ifdef LIBLMDBDIR
> +		BASIC_CFLAGS += -I$(LIBLMDBDIR)/include
> +		EXTLIBS += -L$(LIBLMDBDIR)/$(lib) $(CC_LD_DYNPATH)$(LIBLMDBDIR)/$(lib)
> +	endif
> +	EXTLIBS += -llmdb
> +	LIB_OBJS += refs-be-lmdb.o
> +	TEST_PROGRAMS_NEED_X += test-refs-be-lmdb
> +endif
> +
>  ifdef HAVE_ALLOCA_H
>  	BASIC_CFLAGS += -DHAVE_ALLOCA_H
>  endif
> @@ -2122,6 +2133,7 @@ GIT-BUILD-OPTIONS: FORCE
>  	@echo NO_CURL=\''$(subst ','\'',$(subst ','\'',$(NO_CURL)))'\' >>$@+
>  	@echo NO_EXPAT=\''$(subst ','\'',$(subst ','\'',$(NO_EXPAT)))'\' >>$@+
>  	@echo USE_LIBPCRE=\''$(subst ','\'',$(subst ','\'',$(USE_LIBPCRE)))'\' >>$@+
> +	@echo USE_LIBLMDB=\''$(subst ','\'',$(subst ','\'',$(USE_LIBLMDB)))'\' >>$@+
>  	@echo NO_PERL=\''$(subst ','\'',$(subst ','\'',$(NO_PERL)))'\' >>$@+
>  	@echo NO_PYTHON=\''$(subst ','\'',$(subst ','\'',$(NO_PYTHON)))'\' >>$@+
>  	@echo NO_UNIX_SOCKETS=\''$(subst ','\'',$(subst ','\'',$(NO_UNIX_SOCKETS)))'\' >>$@+
> diff --git a/builtin/init-db.c b/builtin/init-db.c
> index 504a2dc..89f2c05 100644
> --- a/builtin/init-db.c
> +++ b/builtin/init-db.c
> @@ -203,6 +203,17 @@ static int create_default_files(const char *template_path)
>  		adjust_shared_perm(get_git_dir());
>  	}
>  
> +	if (refs_backend_type) {
> +		struct refdb_config_data config_data = {NULL};
> +		git_config_set("core.refs-backend-type", refs_backend_type);
> +		config_data.refs_backend_type = refs_backend_type;
> +		config_data.refs_base = get_git_dir();
> +#ifdef USE_LIBLMDB
> +		register_refs_backend(&refs_be_lmdb);
> +#endif
> +		set_refs_backend(refs_backend_type, &config_data);
> +	}
> +
>  	if (refs_initdb(&err, shared_repository))
>  		die("failed to set up refs db: %s", err.buf);
>  
> @@ -468,6 +479,8 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
>  		OPT_BIT('q', "quiet", &flags, N_("be quiet"), INIT_DB_QUIET),
>  		OPT_STRING(0, "separate-git-dir", &real_git_dir, N_("gitdir"),
>  			   N_("separate git dir from working tree")),
> +		OPT_STRING(0, "refs-backend-type", &refs_backend_type,
> +			   N_("name"), N_("name of backend type to use")),
>  		OPT_END()
>  	};
>  
> diff --git a/config.c b/config.c
> index 248a21a..9ce2ed2 100644
> --- a/config.c
> +++ b/config.c
> @@ -10,6 +10,7 @@
>  #include "exec_cmd.h"
>  #include "strbuf.h"
>  #include "quote.h"
> +#include "refs.h"
>  #include "hashmap.h"
>  #include "string-list.h"
>  #include "utf8.h"
> @@ -1207,6 +1208,32 @@ int git_config_early(config_fn_t fn, void *data, const char *repo_config)
>  	}
>  
>  	if (repo_config && !access_or_die(repo_config, R_OK, 0)) {
> +		struct refdb_config_data refdb_data = {NULL};
> +		char *repo_config_copy;
> +
> +		/*
> +		 * make sure we always read the backend config from the
> +		 * core section on startup
> +		 */
> +		ret += git_config_from_file(refdb_config, repo_config,
> +					    &refdb_data);
> +
> +		repo_config_copy = xstrdup(repo_config);
> +		refdb_data.refs_base = strdup(dirname(repo_config_copy));
> +		free(repo_config_copy);
> +
> +		if (refdb_data.refs_backend_type &&
> +		    !strcmp(refdb_data.refs_backend_type, "lmdb")) {
> +
> +#ifdef USE_LIBLMDB
> +			refs_backend_type = refdb_data.refs_backend_type;
> +			register_refs_backend(&refs_be_lmdb);
> +			set_refs_backend(refs_backend_type, &refdb_data);
> +#else
> +			die("Git was not built with USE_LIBLMDB, so the db refs backend is not available");
s/db/lmdb/

I'm somewhat surprised that you only register the lmdb backend if it is used in the main repo. I would expect the backend to be registered unconditionally on startup. The cost is trivial, isn't it?

It appears that you recognize "lmdb" but you treat a missing value *or any other value* like "files". I think you should be more conservative here and die() if you see a backend type that you don't recognize.

Show 67 quoted lines
> +#endif
> +		}
> +
>  		ret += git_config_from_file(fn, repo_config, data);
>  		found += 1;
>  	}
> diff --git a/configure.ac b/configure.ac
> index 63b7244..ea8e93d 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -271,6 +271,24 @@ AS_HELP_STRING([],           [ARG can be also prefix for libpcre library and hea
>          dnl it yet.
>  	GIT_CONF_SUBST([LIBPCREDIR])
>      fi)
> +
> +USE_LIBLMDB=YesPlease
> +AC_ARG_WITH(liblmdb,
> +AS_HELP_STRING([--with-liblmdb],[support lmdb (default is YES])
> +AS_HELP_STRING([],           [ARG can be also prefix for liblmdb library and headers]),
> +    if test "$withval" = "no"; then
> +	USE_LIBLMDB=
> +    elif test "$withval" = "yes"; then
> +	USE_LIBLMDB=YesPlease
> +    else
> +	USE_LIBLMDB=YesPlease
> +	LIBLMDBDIR=$withval
> +	AC_MSG_NOTICE([Setting LIBLMDBDIR to $LIBLMDBDIR])
> +        dnl USE_LIBLMDB can still be modified below, so don't substitute
> +        dnl it yet.
> +	GIT_CONF_SUBST([LIBLMDBDIR])
> +    fi)
> +
>  #
>  # Define HAVE_ALLOCA_H if you have working alloca(3) defined in that header.
>  AC_FUNC_ALLOCA
> @@ -510,6 +528,21 @@ GIT_CONF_SUBST([USE_LIBPCRE])
>  
>  fi
>  
> +if test -n "$USE_LIBLMDB"; then
> +
> +GIT_STASH_FLAGS($LIBLMDBDIR)
> +
> +AC_CHECK_LIB([lmdb], [mdb_env_open],
> +[USE_LIBLMDB=YesPlease],
> +[USE_LIBLMDB=])
> +
> +GIT_UNSTASH_FLAGS($LIBLMDBDIR)
> +
> +GIT_CONF_SUBST([USE_LIBLMDB])
> +
> +fi
> +
> +
>  #
>  # Define NO_CURL if you do not have libcurl installed.  git-http-pull and
>  # git-http-push are not built, and you cannot use http:// and https://
> diff --git a/contrib/workdir/git-new-workdir b/contrib/workdir/git-new-workdir
> index 888c34a..9761a1a 100755
> --- a/contrib/workdir/git-new-workdir
> +++ b/contrib/workdir/git-new-workdir
> @@ -28,6 +28,8 @@ git_dir=$(cd "$orig_git" 2>/dev/null &&
>    git rev-parse --git-dir 2>/dev/null) ||
>    die "Not a git repository: \"$orig_git\""
>  
> +test -d $git_dir/refdb && die "git-new-workdir is incompatible with the refs db backend"
> +

It would be safer to check $(git config core.refs-backend-type) to avoid depending on an implementation detail of the LMDB backend (and as a bit of safety against any backends that might be added in the future).

Show 91 quoted lines
>  case "$git_dir" in
>  .git)
>  	git_dir="$orig_git/.git"
> diff --git a/environment.c b/environment.c
> index c5b65f5..6a2f639 100644
> --- a/environment.c
> +++ b/environment.c
> @@ -65,6 +65,7 @@ int merge_log_config = -1;
>  int precomposed_unicode = -1; /* see probe_utf8_pathname_composition() */
>  struct startup_info *startup_info;
>  unsigned long pack_size_limit_cfg;
> +const char *refs_backend_type;
>  
>  #ifndef PROTECT_HFS_DEFAULT
>  #define PROTECT_HFS_DEFAULT 0
> diff --git a/refs-be-lmdb.c b/refs-be-lmdb.c
> new file mode 100644
> index 0000000..99cbd29
> --- /dev/null
> +++ b/refs-be-lmdb.c
> @@ -0,0 +1,2003 @@
> +/*
> + * This file implements a lmdb backend for refs.
> + *
> + * The design of this backend relies on lmdb's write lock -- that is, any
> + * write transaction blocks all other writers.  Thus, as soon as a ref
> + * transaction is opened, we know that any values we read won't
> + * change out from under us, and we have a fully-consistent view of the
> + * database.
> + *
> + * We store the content of refs including the trailing \0 so that
> + * standard C string functions can handle them.  Just like struct
> + * strbuf.
> + */
> +#include <lmdb.h>
> +#include <sys/uio.h>
> +#include "cache.h"
> +#include "object.h"
> +#include "refs.h"
> +#include "tag.h"
> +#include "lockfile.h"
> +#include "run-command.h"
> +
> +static struct trace_key db_trace = TRACE_KEY_INIT(LMDB);
> +
> +static MDB_env *env;
> +
> +static char *db_path;
> +
> +struct lmdb_transaction_info {
> +	MDB_txn *txn;
> +	MDB_dbi dbi;
> +	const char *submodule;
> +};
> +
> +struct lmdb_transaction {
> +	struct ref_transaction base;
> +	MDB_cursor *cursor;
> +	struct lmdb_transaction_info info;
> +	struct hashmap updated_refs;
> +	unsigned int flags;
> +};
> +
> +static struct lmdb_transaction transaction = {{}, NULL};
> +
> +struct ref_update {
> +	struct hashmap_entry ent;  /* must be first */
> +	size_t len;
> +	char refname[FLEX_ARRAY];
> +};
> +
> +static char *get_refdb_path(const char *base)
> +{
> +	struct strbuf path_buf = STRBUF_INIT;
> +	strbuf_addf(&path_buf, "%s/refdb", base);
> +	return strbuf_detach(&path_buf, NULL);
> +}
> +
> +static int in_write_transaction(void)
> +{
> +	return transaction.info.txn && !(transaction.flags & MDB_RDONLY);
> +}
> +
> +static void init_env(MDB_env **env, const char *path)
> +{
> +	int ret;
> +	if (*env)
> +		return;
> +
> +	if ((ret = mdb_env_create(env)) != MDB_SUCCESS)
> +		die("mdb_env_create failed: %s", mdb_strerror(ret));

The LMDB docs seem to say that their functions "return a non-zero error value on failure and 0 on success". So can we avoid writing MDB_SUCCESS everywhere and just compare to zero?

Also, the Git project CodingGuidelines say to avoid assignments in the condition of an "if" statement.

So all in all I think these should be written like
        ret = mdb_env_create(env);
        if (ret)
                die("mdb_env_create failed: %s", mdb_strerror(ret));
Show 33 quoted lines
> +	if ((ret = mdb_env_set_maxreaders(*env, 1000)) != MDB_SUCCESS)
> +		die("BUG: mdb_env_set_maxreaders failed: %s", mdb_strerror(ret));
> +	if ((ret = mdb_env_set_mapsize(*env, (1<<30))) != MDB_SUCCESS)
> +		die("BUG: mdb_set_mapsize failed: %s", mdb_strerror(ret));
> +	if ((ret = mdb_env_open(*env, path, 0 , 0664)) != MDB_SUCCESS)
> +		die("BUG: mdb_env_open (%s) failed: %s", path, mdb_strerror(ret));
> +}
> +
> +static int lmdb_initdb(struct strbuf *err, int shared)
> +{
> +	/* To create a db, all we need to do is make a directory for
> +	   it to live in; lmdb will do the rest. */
> +
> +	assert(db_path);
> +	if (mkdir(db_path, 0775)) {
> +		if (errno != EEXIST) {
> +			strbuf_addf(err, "%s", strerror(errno));
> +			return -1;
> +		}
> +	}
> +	return 0;
> +}
> +
> +static void lmdb_init_backend(struct refdb_config_data *data)
> +{
> +	if (db_path)
> +		return;
> +
> +	db_path = xstrdup(real_path(get_refdb_path(data->refs_base)));
> +	trace_printf_key(&db_trace, "Init backend\n");
> +}
> +
> +int ref_update_cmp(const void *entry, const void *entry_or_key, const void *keydata)
The usual convention for a "cmp" function is that it returns <0, =0, or
>0 depending on the relative ordering of its arguments. This one can

only be used for equality/inequality. Please add a comment warning the reader of this (and maybe rename it, e.g., to ref_update_not_equal(), to further reduce the chance of confusion).

Aside: the hashmap_init() documentation is very confusing. Based on the
documentation alone, I would have thought that its `equals_function`
should return true if the two arguments are *equal*.
Show 213 quoted lines
> +{
> +
> +	const struct ref_update *existing = entry;
> +	const struct ref_update *incoming = entry_or_key;
> +
> +	return existing->len != incoming->len ||
> +		memcmp(existing->refname, incoming->refname, existing->len);
> +}
> +
> +static void mdb_cursor_open_or_die(struct lmdb_transaction_info *info,
> +				   MDB_cursor **cursor)
> +{
> +	int ret = mdb_cursor_open(info->txn, info->dbi, cursor);
> +	if (ret)
> +		die("mdb_cursor_open failed: %s", mdb_strerror(ret));
> +}
> +
> +static void submodule_path(struct strbuf *sb, const char *submodule,
> +			   const char *refname)
> +{
> +	if (submodule)
> +		strbuf_git_path_submodule(sb, submodule, "%s", refname);
> +	else
> +		strbuf_git_path(sb, "%s", refname);
> +}
> +
> +static int read_per_worktree_ref(const char *submodule, const char *refname,
> +				 struct MDB_val *val)
> +{
> +	static struct strbuf sb = STRBUF_INIT;
> +	static struct strbuf path = STRBUF_INIT;
> +
> +	submodule_path(&path, submodule, refname);
> +
> +	if (strbuf_read_file(&sb, path.buf, 200) < 0) {
> +		strbuf_release(&sb);
> +		strbuf_release(&path);
> +		if (errno == ENOENT)
> +			return MDB_NOTFOUND;
> +		return -1;
> +	}
> +	strbuf_setlen(&sb, sb.len - 1);
> +
> +	val->mv_data = sb.buf;
> +	val->mv_size = sb.len + 1;
> +
> +	strbuf_detach(&sb, NULL);
> +	strbuf_release(&path);
> +	return 0;
> +}
> +
> +static void write_per_worktree_ref(const char *submodule, const char *refname,
> +				   MDB_val *val)
> +{
> +	static struct lock_file lock;
> +	int fd;
> +	int len = val->mv_size - 1;
> +	struct strbuf path = STRBUF_INIT;
> +
> +	submodule_path(&path, submodule, refname);
> +	safe_create_leading_directories(path.buf);
> +
> +	fd = hold_lock_file_for_update(&lock, path.buf, LOCK_DIE_ON_ERROR);
> +	strbuf_release(&path);
> +
> +	if (write_in_full(fd, val->mv_data, len) != len ||
> +	    write_in_full(fd, "\n", 1) != 1)
> +		die_errno("failed to write new HEAD");
> +
> +	if (commit_lock_file(&lock))
> +		die_errno("failed to write new HEAD");
> +}
> +
> +static int del_per_worktree_ref(const char *submodule, const char *refname,
> +				MDB_val *val)
> +{
> +	struct strbuf path = STRBUF_INIT;
> +	int result;
> +
> +	/*
> +	 * Returning deleted ref data is not yet implemented, but no
> +	 * callers need it.
> +	 */
> +	assert(val == NULL);
> +
> +	submodule_path(&path, submodule, refname);
> +
> +	result = unlink(path.buf);
> +	strbuf_release(&path);
> +	if (result) {
> +		if (errno == ENOENT)
> +			return 0;
> +		return 1;
> +	}
> +
> +	return 0;
> +}
> +
> +static int mdb_get_or_die(struct lmdb_transaction_info *info, MDB_val *key, MDB_val *val)
> +{
> +	int ret;
> +
> +	if (ref_type(key->mv_data) != REF_TYPE_NORMAL)
> +		return read_per_worktree_ref(info->submodule, key->mv_data, val);
> +
> +	ret = mdb_get(info->txn, info->dbi, key, val);
> +	if (ret) {
> +		if (ret != MDB_NOTFOUND)
> +			die("mdb_get failed: %s", mdb_strerror(ret));
> +		return ret;
> +	}
> +	return 0;
> +}
> +
> +static int mdb_del_or_die(struct lmdb_transaction_info *info, MDB_val *key, MDB_val *val)
> +{
> +	int ret;
> +
> +	if (ref_type(key->mv_data) != REF_TYPE_NORMAL)
> +		return del_per_worktree_ref(info->submodule, key->mv_data, val);
> +
> +	ret = mdb_del(info->txn, info->dbi, key, val);
> +	if (ret) {
> +		if (ret != MDB_NOTFOUND)
> +			die("mdb_del failed: %s", mdb_strerror(ret));
> +		return ret;
> +	}
> +	return 0;
> +}
> +
> +static void mdb_put_or_die(struct lmdb_transaction_info *info, MDB_val *key, MDB_val *val, int mode)
> +{
> +	int ret;
> +
> +	if (ref_type(key->mv_data) != REF_TYPE_NORMAL) {
> +		write_per_worktree_ref(info->submodule, key->mv_data, val);
> +		return;
> +	}
> +	assert(val->mv_size == 0 || ((char *)val->mv_data)[val->mv_size - 1] == 0);
> +
> +	ret = mdb_put(info->txn, info->dbi, key, val, mode);
> +	if (ret) {
> +		if (ret == MDB_BAD_VALSIZE)
> +			die("Ref name %s too long (max size is %d)",
> +			    (const char *)key->mv_data,
> +			    mdb_env_get_maxkeysize(env));
> +		else
> +			die("mdb_put failed: %s", mdb_strerror(ret));
> +	}
> +}
> +
> +static int mdb_cursor_get_or_die(MDB_cursor *cursor, MDB_val *key, MDB_val *val, int mode)
> +{
> +	int ret;
> +
> +	ret = mdb_cursor_get(cursor, key, val, mode);
> +	if (ret) {
> +		if (ret != MDB_NOTFOUND)
> +			die("mdb_cursor_get failed: %s", mdb_strerror(ret));
> +		return ret;
> +	}
> +	assert(((char *)val->mv_data)[val->mv_size - 1] == 0);
> +	return 0;
> +}
> +
> +static int mdb_cursor_del_or_die(MDB_cursor *cursor, int flags)
> +{
> +	int ret = mdb_cursor_del(cursor, flags);
> +	if (ret) {
> +		if (ret != MDB_NOTFOUND)
> +			die("mdb_cursor_del failed: %s", mdb_strerror(ret));
> +		return ret;
> +	}
> +	return 0;
> +}
> +
> +/*
> + * Begin a transaction. Because only one transaction per thread is
> + * permitted, we use a global transaction object.  If a read-write
> + * transaction is presently already in-progress, and a read-only
> + * transaction is requested, the read-write transaction will be
> + * returned instead.  If a read-write transaction is requested and a
> + * read-only transaction is open, the read-only transaction will be
> + * closed.
> + *
> + * It is a bug to request a read-write transaction during another
> + * read-write transaction.
> + *
> + * As a result, it is unsafe to retain read-only transactions past the
> + * point where a read-write transaction might be needed.  For
> + * instance, any call that has callbacks outside this module must
> + * conclude all of its reads from the database before calling those
> + * callbacks, or must reacquire the transaction after its callbacks
> + * are completed.
> + */
> +int lmdb_transaction_begin_flags(struct strbuf *err, unsigned int flags)
> +{
> +	int ret;
> +	MDB_txn *txn;
> +	static int last_commands_run = 0;
> +	int force_restart = 0;
> +
> +	init_env(&env, db_path);
> +
> +	if (total_commands_run != last_commands_run) {
> +		/*
> +		 * Since each transaction sees a consistent view of
> +		 * the db, downstream processes that write the db
> +		 * won't be seen in this transaction.  We don't know
> +		 * whether any given downstream process has made any
> +		 * writes, so if there have been any downstream processes,
> +		 * we had better reopen the transaction.
> +		 */

Is it possible to tell from the database whether there has been a write since the current read transaction was started? It seems like this would be a more reliable method, given that an unrelated process might have updated references while this command was running, or a long-running background process (like Duy's background `git gc --auto`) might still continue running even after the original command exits.

Of course in the end checks like this can never 100% prevent races. The only way, it would seem, would be to check-and-set during a single write transaction that holds a write lock on the DB.

That's exactly what we do for file-based references: ideally, the only allowed changes are compare-and-set operations that are made atomic by holding a lock while it is occurring (one or more loose reference lockfiles and/or the packed-refs lockfile). That is why our ref_updates have old_sha1 and new_sha1, and old_sha1 is always used for operations that start before a lock is held.

So I guess my question is this: to what extent are you relying on this total_commands_run mechanism for correctness, vs. just using it to avoid using reference values that are *really* old?

It seems to me that a very important feature of Git is the following:
    Two processes *must not* block each other (except possibly
    for a very brief time) if they are updating disjoint sets
    of references.

With the file-backed reference backend, this feature is accomplished via the following mechanisms:

* Locks are only held for very short periods of time, and never when
  calling other processes.
* Locks usually only apply to single references (loose references).
  Sometimes the packed-refs file has to be locked, but in such cases
  a second writer retries the lock acquisition for long enough that
  it should usually succeed.
* If a reference value was checked before a lock was held, then it
  is always re-checked after acquiring the lock to make sure that
  another process didn't change it in the meantime. (If the
  reference *was* changed from its expected value, it is ok to
  die().) This is necessary to preserve safety even if a lock is
  not held for the duration of a command.

In particular, locks are only held for the duration of the call to ref_transaction_commit(), *not* while building up a transaction using ref_transaction_update() etc.

I am afraid that the LMDB backend as currently implemented will be more subject to locking conflicts because the LMDB locks are broader in both time and space:

1. You are holding the LMDB transaction open for longer (during the
   whole time that the ref_transaction is being built up). This is
   worse simply because it is a longer time. But it is also worse
   because the process that is building up the transaction might be
   doing other things while holding the write lock. Have you checked
   whether any callers do things that could change references (for
   example, invoke user hooks) between the calls to
   ref_transaction_begin() and ref_transaction_commit()? With the
   files backend this isn't an issue because the lock isn't acquired
   until ref_transaction_commit() is called.
2. The LMDB write lock is database-wide. This means that a single
   writer, even if modifying only a single reference, blocks all
   writes to all references.

Therefore I am worried that the LMDB backend will be susceptable to more lock conflicts than the files backend.

Would would be the disadvantage of letting ref_transaction_update() and its friends build up `struct ref_update` objects in memory (much like they do in the files backend), and only start the LMDB write transaction when ref_transaction_commit() is called? Obviously it would cost some more bookkeeping, but that code is already written. This would be much closer to the files backend model.

This is where I stopped for the day.

I have a bunch of small stylistic and formatting suggestions regarding this patch in particular. Rather than describe them all in prose, I just made the suggested changes and pushed them to my GitHub fork [1], branch "refs-be-lmdb-housecleaning". Feel free to squash the ones you want into this commit.

A lot of these problems were found by gcc, which I apparently have configured more strictly than yours. You might want to pick some options from my config.mak [2] to make gcc enforce some of the Git project policies automatically. (Every dev on this list probably has his/her own variation on this file.)

I apologize again for not having reviewed your patches more promptly. I really think it is good and important work. I've just been so busy with other things and I knew it would take a couple of solid days of work to get through it. I'll try to review the rest tomorrow, but since I don't know anything about the LMDB API, the review will either be superficial or it will take a long time...

Let me finish up by stating (also to remind myself!) that it is not crucial to get everything perfect in the first iteration. As long as the database format is correct, other things can be improved over time. This reference backend will inevitably go though a longish period when it is considered experimental, during which we will have time to hammer bugs out of it.

Michael

[1] https://github.com/mhagger/git [2] https://gist.github.com/mhagger/b321e423481b3de405d2

-- 
Michael Haggerty
mhagger@alum.mit.edu
David Turner· Oct 7, 2015, 01:51 UTC · re: Michael Haggerty · lore

Re: [PATCH v2 42/43] refs: add LMDB refs backend

On Mon, 2015-10-05 at 17:47 +0200, Michael Haggerty wrote:
Show 25 quoted lines
> On 09/29/2015 12:02 AM, David Turner wrote:
> > Add a database backend for refs using LMDB.  This backend runs git
> > for-each-ref about 30% faster than the files backend with fully-packed
> > refs on a repo with ~120k refs.  It's also about 4x faster than using
> > fully-unpacked refs.  In addition, and perhaps more importantly , it
> > avoids case-conflict issues on OS X.
> > 
> > LMDB has a few features that make it suitable for usage in git:
> > 
> > 1. It is relatively lightweight; it requires only one header file, and
> > the library code takes under 64k at runtime.
> > 
> > 2. It is well-tested: it's been used in OpenLDAP for years.
> > 
> > 3. It's very fast.  LMDB's benchmarks show that it is among
> > the fastest key-value stores.
> > 
> > 4. It has a relatively simple concurrency story; readers don't
> > block writers and writers don't block readers.
> 
> It would be great if you would go into more detail about this point in
> the permanent technical documentation
> (Documentation/technical/refs-be-lmdb.txt): how exactly do readers &
> writers interact with each others, and how do writers interact with
> other writers?
I'll add a bit of text on that.
Show 11 quoted lines
> I think you have said before that if one writer holds the write lock on
> the DB, then other writers fail immediately. Is that correct? If so, is
> that something that can be adjusted? I think it would be preferable for
> the second writer to retry acquiring the write lock for a little while
> with a timeout (as we now do when trying to acquire the packed-refs
> lock). Otherwise you could have the unhappy situation that somebody
> spends a long time pushing a packfile to a server, only to have the
> reference update be rejected at the last moment due to a lock conflict
> with another process that was touching completely different references.
> We already do before/after consistency checks when updating references,
> so you wouldn't even have to add such code in the backend yourself.

No, the second writer waits for the first writer to unlock (or for it to crash).

Show 6 quoted lines
> > +Keys for refs are the name of the ref (e.g. refs/heads/master).
> > +Values are the value of the ref
> > +(e.g. 61f23eb0f81357c19fa91e2b8c6f3906c3a8f9b0).
> 
> Please document more unambiguously whether the value is stored as
> 40-char hexadecimal or 20-byte binary.
Will fix.
> Do you store "peeled" reference values for tags, as is done in
> packed-refs? I think that is an important optimization.

No. Do you happen to know in what situations this is a performance benefit, so that I can benchmark? I suspect it would matter much less for the LMDB backend, because lookups are pretty quick.

Show 9 quoted lines
> > +All per-worktree refs (refs/bisect/* and HEAD) are store using
> > +the traditional files-based backend.
> > +
> > +Reflogs are stored as a series of database entries.
> > +
> > +For an empty reflog, there is a "header" entry to show that a reflog
> > +exists.
> 
> What key is used for the "header" entry? Does it have a value?
Will fix.
Show 17 quoted lines
> > +         For non-empty reflogs, there is one entry per logged ref
> > +update.  The key format is logs/[refname]\0[timestamp].  The timestamp
> > +is a 64-bit unsigned integer number of nanoseconds since 1/1/1970.
> > +This means that reflog entries are chronologically ordered.  Because
> > +LMDB is a btree database, we can efficiently iterate over these keys.
> 
> Currently we discard the reflog for a reference when the reference is
> deleted. This is mostly due to a limitation of the filesystem-based
> storage scheme--otherwise the reflog left over from a deleted reference
> could prevent the creation of a reflog for another reference whose name
> overlaps with it (in the sense of is_refname_available()).
> 
> It is a pretty nasty limitation because it is one of the few ways that
> your actions can cause Git to lose data in a way that it is hard to
> recover from.
> 
> Have you thought about removing this limitation in the lbdb backend?

I'm going for feature parity first. We can always add new functionality later. This particular function would be pretty straightforward to add, I think.

Show 13 quoted lines
> > +Reflog values are in the same format as the original files-based
> > +reflog.
> > +
> > +Weaknesses:
> > +-----------
> > +
> > +The reflog format is somewhat inefficient: a binary format could store
> > +reflog date/time information in somewhat less space.
> > +
> > +The rsync and file:// transports don't work yet, because they
> > +don't use the refs API.
> 
> Do they fail gracefully?
Not particularly gracefully.

rsync: link_stat "/home/dturner/git/t/trash directory.t5510-fetch/.git/packed-refs" failed: No such file or directory (2) rsync error: some files/attrs were not transferred (see previous errors) (code 23) at main.c(1183) [sender=3.1.1] fatal: Could not run rsync to get refs -------------

The problem is that rsync on the client assumes that packed-refs exists. We could hack it to also check for refdb.

Show 12 quoted lines
> > +#ifdef USE_LIBLMDB
> > +			refs_backend_type = refdb_data.refs_backend_type;
> > +			register_refs_backend(&refs_be_lmdb);
> > +			set_refs_backend(refs_backend_type, &refdb_data);
> > +#else
> > +			die("Git was not built with USE_LIBLMDB, so the db refs backend is not available");
> 
> s/db/lmdb/
> 
> I'm somewhat surprised that you only register the lmdb backend if it is
> used in the main repo. I would expect the backend to be registered
> unconditionally on startup. The cost is trivial, isn't it?
Yeah, but this was the easiest place to do it.
> It appears that you recognize "lmdb" but you treat a missing value *or
> any other value* like "files". I think you should be more conservative
> here and die() if you see a backend type that you don't recognize.
Will fix, thanks.
Show 14 quoted lines
> > diff --git a/contrib/workdir/git-new-workdir b/contrib/workdir/git-new-workdir
> > index 888c34a..9761a1a 100755
> > --- a/contrib/workdir/git-new-workdir
> > +++ b/contrib/workdir/git-new-workdir
> > @@ -28,6 +28,8 @@ git_dir=$(cd "$orig_git" 2>/dev/null &&
> >    git rev-parse --git-dir 2>/dev/null) ||
> >    die "Not a git repository: \"$orig_git\""
> >  
> > +test -d $git_dir/refdb && die "git-new-workdir is incompatible with the refs db backend"
> > +
> 
> It would be safer to check $(git config core.refs-backend-type) to avoid
> depending on an implementation detail of the LMDB backend (and as a bit
> of safety against any backends that might be added in the future).
Will fix, thanks.
Show 12 quoted lines
> > +static void init_env(MDB_env **env, const char *path)
> > +{
> > +	int ret;
> > +	if (*env)
> > +		return;
> > +
> > +	if ((ret = mdb_env_create(env)) != MDB_SUCCESS)
> > +		die("mdb_env_create failed: %s", mdb_strerror(ret));
> 
> The LMDB docs seem to say that their functions "return a non-zero error
> value on failure and 0 on success". So can we avoid writing MDB_SUCCESS
> everywhere and just compare to zero?
Sure.
Show 8 quoted lines
> Also, the Git project CodingGuidelines say to avoid assignments in the
> condition of an "if" statement.
> 
> So all in all I think these should be written like
> 
>         ret = mdb_env_create(env);
>         if (ret)
>                 die("mdb_env_create failed: %s", mdb_strerror(ret));
OK.
Show 8 quoted lines
> > +
> > +int ref_update_cmp(const void *entry, const void *entry_or_key, const void *keydata)
> 
> The usual convention for a "cmp" function is that it returns <0, =0, or
> >0 depending on the relative ordering of its arguments. This one can
> only be used for equality/inequality. Please add a comment warning the
> reader of this (and maybe rename it, e.g., to ref_update_not_equal(), to
> further reduce the chance of confusion).
Will fix.
Show 12 quoted lines
> > +	if (total_commands_run != last_commands_run) {
> > +		/*
> > +		 * Since each transaction sees a consistent view of
> > +		 * the db, downstream processes that write the db
> > +		 * won't be seen in this transaction.  We don't know
> > +		 * whether any given downstream process has made any
> > +		 * writes, so if there have been any downstream processes,
> > +		 * we had better reopen the transaction.
> > +		 */
> 
> Is it possible to tell from the database whether there has been a write
> since the current read transaction was started? 
Actually, yes.  Fixed.
Show 20 quoted lines
> It seems like this would
> be a more reliable method, given that an unrelated process might have
> updated references while this command was running, or a long-running
> background process (like Duy's background `git gc --auto`) might still
> continue running even after the original command exits.
> 
> Of course in the end checks like this can never 100% prevent races. The
> only way, it would seem, would be to check-and-set during a single write
> transaction that holds a write lock on the DB.
> 
> That's exactly what we do for file-based references: ideally, the only
> allowed changes are compare-and-set operations that are made atomic by
> holding a lock while it is occurring (one or more loose reference
> lockfiles and/or the packed-refs lockfile). That is why our ref_updates
> have old_sha1 and new_sha1, and old_sha1 is always used for operations
> that start before a lock is held.
> 
> So I guess my question is this: to what extent are you relying on this
> total_commands_run mechanism for correctness, vs. just using it to avoid
> using reference values that are *really* old?

Some tests fail without it, unfortunately. (I can't remember which ones now).

Show 58 quoted lines
> It seems to me that a very important feature of Git is the following:
> 
>     Two processes *must not* block each other (except possibly
>     for a very brief time) if they are updating disjoint sets
>     of references.
>
> With the file-backed reference backend, this feature is accomplished via
> the following mechanisms:
> 
> * Locks are only held for very short periods of time, and never when
>   calling other processes.
> 
> * Locks usually only apply to single references (loose references).
>   Sometimes the packed-refs file has to be locked, but in such cases
>   a second writer retries the lock acquisition for long enough that
>   it should usually succeed.
> 
> * If a reference value was checked before a lock was held, then it
>   is always re-checked after acquiring the lock to make sure that
>   another process didn't change it in the meantime. (If the
>   reference *was* changed from its expected value, it is ok to
>   die().) This is necessary to preserve safety even if a lock is
>   not held for the duration of a command.
> 
> In particular, locks are only held for the duration of the call to
> ref_transaction_commit(), *not* while building up a transaction using
> ref_transaction_update() etc.
> 
> I am afraid that the LMDB backend as currently implemented will be more
> subject to locking conflicts because the LMDB locks are broader in both
> time and space:
> 
> 1. You are holding the LMDB transaction open for longer (during the
>    whole time that the ref_transaction is being built up). This is
>    worse simply because it is a longer time. But it is also worse
>    because the process that is building up the transaction might be
>    doing other things while holding the write lock. Have you checked
>    whether any callers do things that could change references (for
>    example, invoke user hooks) between the calls to
>    ref_transaction_begin() and ref_transaction_commit()? With the
>    files backend this isn't an issue because the lock isn't acquired
>    until ref_transaction_commit() is called.
> 
> 2. The LMDB write lock is database-wide. This means that a single
>    writer, even if modifying only a single reference, blocks all
>    writes to all references.
> 
> Therefore I am worried that the LMDB backend will be susceptable to more
> lock conflicts than the files backend.
> 
> [What] would be the disadvantage of letting ref_transaction_update() and
> its friends build up `struct ref_update` objects in memory (much like
> they do in the files backend), and only start the LMDB write transaction
> when ref_transaction_commit() is called? Obviously it would cost some
> more bookkeeping, but that code is already written. This would be much
> closer to the files backend model.
> 
> This is where I stopped for the day.

I think you have convinced me to switch over to this method. Unless something weird comes up while I'm doing the switch, in which case I'll send email.

Show 5 quoted lines
> I have a bunch of small stylistic and formatting suggestions regarding
> this patch in particular. Rather than describe them all in prose, I just
> made the suggested changes and pushed them to my GitHub fork [1], branch
> "refs-be-lmdb-housecleaning". Feel free to squash the ones you want into
> this commit.
I took them all.  THanks.
Show 5 quoted lines
> A lot of these problems were found by gcc, which I apparently have
> configured more strictly than yours. You might want to pick some options
> from my config.mak [2] to make gcc enforce some of the Git project
> policies automatically. (Every dev on this list probably has his/her own
> variation on this file.)
I've adopted yours, thanks.
Show 6 quoted lines
> I apologize again for not having reviewed your patches more promptly. I
> really think it is good and important work. I've just been so busy with
> other things and I knew it would take a couple of solid days of work to
> get through it. I'll try to review the rest tomorrow, but since I don't
> know anything about the LMDB API, the review will either be superficial
> or it will take a long time...

Thanks for the hard work here. I know this is a big chunk of code to review, and I'm grateful for the thoughts.

Michael Haggerty· Oct 7, 2015, 18:31 UTC · re: David Turner · lore

Re: [PATCH v2 42/43] refs: add LMDB refs backend

On 10/07/2015 03:51 AM, David Turner wrote:
Show 18 quoted lines
> On Mon, 2015-10-05 at 17:47 +0200, Michael Haggerty wrote:
>> On 09/29/2015 12:02 AM, David Turner wrote:
>>> Add a database backend for refs using LMDB.  [...]
>>
>> I think you have said before that if one writer holds the write lock on
>> the DB, then other writers fail immediately. Is that correct? If so, is
>> that something that can be adjusted? I think it would be preferable for
>> the second writer to retry acquiring the write lock for a little while
>> with a timeout (as we now do when trying to acquire the packed-refs
>> lock). Otherwise you could have the unhappy situation that somebody
>> spends a long time pushing a packfile to a server, only to have the
>> reference update be rejected at the last moment due to a lock conflict
>> with another process that was touching completely different references.
>> We already do before/after consistency checks when updating references,
>> so you wouldn't even have to add such code in the backend yourself.
> 
> No, the second writer waits for the first writer to unlock (or for it to
> crash).
Cool, that's better behavior.
Show 7 quoted lines
> [...]
>> Do you store "peeled" reference values for tags, as is done in
>> packed-refs? I think that is an important optimization.
> 
> No.  Do you happen to know in what situations this is a performance
> benefit, so that I can benchmark?  I suspect it would matter much less
> for the LMDB backend, because lookups are pretty quick.

The reference lookup speed is not relevant here. "Peeling" is applied to references that point at tag objects (a.k.a. annotated tags). It means that the tag object is looked up to see what *it* points at (recursively if necessary) and the result is stored to the packed-refs file in a specially-formatted extra line that looks like

    17f9f635c101aef03874e1de1d8d0322187494b3 refs/tags/v2.6.0
    ^be08dee9738eaaa0423885ed189c2b6ad8368cf0

I think a good command to benchmark would be `git show-refs -d` in a repository with a number of annotated tags. This command's output is similar to the output of `git ls-remote <remote>` and also comes up during reference negotiation when fetching (so its performance is definitely not moot).

Show 8 quoted lines
> [...]
>> Currently we discard the reflog for a reference when the reference is
>> deleted. [...]
>> Have you thought about removing this limitation in the lbdb backend?
> 
> I'm going for feature parity first.  We can always add new functionality
> later.  This particular function would be pretty straightforward to add,
> I think.
+1
Show 18 quoted lines
> [...]
>>> +The rsync and file:// transports don't work yet, because they
>>> +don't use the refs API.
>>
>> Do they fail gracefully?
> 
> Not particularly gracefully.
> 
> rsync: link_stat "/home/dturner/git/t/trash
> directory.t5510-fetch/.git/packed-refs" failed: No such file or
> directory (2)
> rsync error: some files/attrs were not transferred (see previous errors)
> (code 23) at main.c(1183) [sender=3.1.1]
> fatal: Could not run rsync to get refs
> -------------
> 
> The problem is that rsync on the client assumes that packed-refs exists.
> We could hack it to also check for refdb.

I guess this is something that will have to be improved sooner or later, though I guess not as a precondition for merging this patch series.

Show 6 quoted lines
> [...]
>> I'm somewhat surprised that you only register the lmdb backend if it is
>> used in the main repo. I would expect the backend to be registered
>> unconditionally on startup. The cost is trivial, isn't it?
> 
> Yeah, but this was the easiest place to do it.
OK.
> [...]
I'm really happy about your work.

Regarding strategy: I think a good approach would be to get as much of the preparatory work as possible (the abstraction and separation of refs-be-files) to the point where it can be merged before there is too much more code churn in the area. That work is not very controversial, I think, and letting it wait for a long time will increase the likelihood of conflicts with other people's changes. The refs-be-lmdb patches, on the other hand, (1) will take longer to get polished, (2) will take longer to review because other people are not familiar with LDMB, and (3) won't bitrot very fast anyway because they don't overlap as much with areas that other people are likely to work on. So I would advocate working on those at a more deliberate pace and planning for them to be merged as a separate batch.

Michael
-- 
Michael Haggerty
mhagger@alum.mit.edu
Junio C Hamano· Oct 7, 2015, 19:08 UTC · re: Michael Haggerty · lore

Re: [PATCH v2 42/43] refs: add LMDB refs backend

Michael Haggerty <mhagger@alum.mit.edu> writes:
Show 14 quoted lines
> I'm really happy about your work.
>
> Regarding strategy: I think a good approach would be to get as much of
> the preparatory work as possible (the abstraction and separation of
> refs-be-files) to the point where it can be merged before there is too
> much more code churn in the area. That work is not very controversial, I
> think, and letting it wait for a long time will increase the likelihood
> of conflicts with other people's changes. The refs-be-lmdb patches, on
> the other hand, (1) will take longer to get polished, (2) will take
> longer to review because other people are not familiar with LDMB, and
> (3) won't bitrot very fast anyway because they don't overlap as much
> with areas that other people are likely to work on. So I would advocate
> working on those at a more deliberate pace and planning for them to be
> merged as a separate batch.
I agree with you on all counts.

My wish is for you to play an interim maintainer starting around week #7 (Nov 9th) of this cycle and merge the early "preparatory" part to 'next' before I come back around the end of November ;-) to be shipped as part of the release at the end of this cycle.

Thanks.
David Turner· Oct 7, 2015, 19:20 UTC · re: Michael Haggerty · lore

Re: [PATCH v2 42/43] refs: add LMDB refs backend

On Wed, 2015-10-07 at 20:31 +0200, Michael Haggerty wrote:
Show 103 quoted lines
> On 10/07/2015 03:51 AM, David Turner wrote:
> > On Mon, 2015-10-05 at 17:47 +0200, Michael Haggerty wrote:
> >> On 09/29/2015 12:02 AM, David Turner wrote:
> >>> Add a database backend for refs using LMDB.  [...]
> >>
> >> I think you have said before that if one writer holds the write lock on
> >> the DB, then other writers fail immediately. Is that correct? If so, is
> >> that something that can be adjusted? I think it would be preferable for
> >> the second writer to retry acquiring the write lock for a little while
> >> with a timeout (as we now do when trying to acquire the packed-refs
> >> lock). Otherwise you could have the unhappy situation that somebody
> >> spends a long time pushing a packfile to a server, only to have the
> >> reference update be rejected at the last moment due to a lock conflict
> >> with another process that was touching completely different references.
> >> We already do before/after consistency checks when updating references,
> >> so you wouldn't even have to add such code in the backend yourself.
> > 
> > No, the second writer waits for the first writer to unlock (or for it to
> > crash).
> 
> Cool, that's better behavior.
> 
> > [...]
> >> Do you store "peeled" reference values for tags, as is done in
> >> packed-refs? I think that is an important optimization.
> > 
> > No.  Do you happen to know in what situations this is a performance
> > benefit, so that I can benchmark?  I suspect it would matter much less
> > for the LMDB backend, because lookups are pretty quick.
> 
> The reference lookup speed is not relevant here. "Peeling" is applied to
> references that point at tag objects (a.k.a. annotated tags). It means
> that the tag object is looked up to see what *it* points at (recursively
> if necessary) and the result is stored to the packed-refs file in a
> specially-formatted extra line that looks like
> 
>     17f9f635c101aef03874e1de1d8d0322187494b3 refs/tags/v2.6.0
>     ^be08dee9738eaaa0423885ed189c2b6ad8368cf0
> 
> I think a good command to benchmark would be `git show-refs -d` in a
> repository with a number of annotated tags. This command's output is
> similar to the output of `git ls-remote <remote>` and also comes up
> during reference negotiation when fetching (so its performance is
> definitely not moot).
> 
> > [...]
> >> Currently we discard the reflog for a reference when the reference is
> >> deleted. [...]
> >> Have you thought about removing this limitation in the lbdb backend?
> > 
> > I'm going for feature parity first.  We can always add new functionality
> > later.  This particular function would be pretty straightforward to add,
> > I think.
> 
> +1
> 
> > [...]
> >>> +The rsync and file:// transports don't work yet, because they
> >>> +don't use the refs API.
> >>
> >> Do they fail gracefully?
> > 
> > Not particularly gracefully.
> > 
> > rsync: link_stat "/home/dturner/git/t/trash
> > directory.t5510-fetch/.git/packed-refs" failed: No such file or
> > directory (2)
> > rsync error: some files/attrs were not transferred (see previous errors)
> > (code 23) at main.c(1183) [sender=3.1.1]
> > fatal: Could not run rsync to get refs
> > -------------
> > 
> > The problem is that rsync on the client assumes that packed-refs exists.
> > We could hack it to also check for refdb.
> 
> I guess this is something that will have to be improved sooner or later,
> though I guess not as a precondition for merging this patch series.
> 
> > [...]
> >> I'm somewhat surprised that you only register the lmdb backend if it is
> >> used in the main repo. I would expect the backend to be registered
> >> unconditionally on startup. The cost is trivial, isn't it?
> > 
> > Yeah, but this was the easiest place to do it.
> 
> OK.
> 
> > [...]
> 
> I'm really happy about your work.
> 
> Regarding strategy: I think a good approach would be to get as much of
> the preparatory work as possible (the abstraction and separation of
> refs-be-files) to the point where it can be merged before there is too
> much more code churn in the area. That work is not very controversial, I
> think, and letting it wait for a long time will increase the likelihood
> of conflicts with other people's changes. The refs-be-lmdb patches, on
> the other hand, (1) will take longer to get polished, (2) will take
> longer to review because other people are not familiar with LDMB, and
> (3) won't bitrot very fast anyway because they don't overlap as much
> with areas that other people are likely to work on. So I would advocate
> working on those at a more deliberate pace and planning for them to be
> merged as a separate batch.
Works for me.  

Would you like me to start sending those as a separate series, or shall I keep it as one series and let you split it as you choose?

Michael Haggerty· Oct 7, 2015, 22:13 UTC · re: David Turner · lore

Re: [PATCH v2 42/43] refs: add LMDB refs backend

On 10/07/2015 09:20 PM, David Turner wrote:
Show 21 quoted lines
> On Wed, 2015-10-07 at 20:31 +0200, Michael Haggerty wrote:
>> [...]
>> I'm really happy about your work.
>>
>> Regarding strategy: I think a good approach would be to get as much of
>> the preparatory work as possible (the abstraction and separation of
>> refs-be-files) to the point where it can be merged before there is too
>> much more code churn in the area. That work is not very controversial, I
>> think, and letting it wait for a long time will increase the likelihood
>> of conflicts with other people's changes. The refs-be-lmdb patches, on
>> the other hand, (1) will take longer to get polished, (2) will take
>> longer to review because other people are not familiar with LDMB, and
>> (3) won't bitrot very fast anyway because they don't overlap as much
>> with areas that other people are likely to work on. So I would advocate
>> working on those at a more deliberate pace and planning for them to be
>> merged as a separate batch.
> 
> Works for me.  
> 
> Would you like me to start sending those as a separate series, or shall
> I keep it as one series and let you split it as you choose?

That's really up to you, as the convenience tradeoff is mostly on your side. If you keep it as one series it is a tad easier for everybody to see the whole idea as a continuous story. But it means that whenever you rewrite any commit in the series, you have to propagate the change through all of the commits, every time. Whereas if you break them up, you have the option of letting the later patches idle for a while then to rebase them onto revision N of the earlier patch series in one big bang.

BTW I didn't have the impression that the series has to be broken into more than two or three subseries. Splitting off refs-be-files.{c,h} from refs.{c,h} and creating the virtual function table is pretty much one unit of work and I see no sense splitting it artificially into separate parts. Adding refs-be-lmdb.{c,h} is only a couple of (big!) patches which, again, don't really need to be split any further. If you decide to implement the delegation thing for handling the split between per-worktree vs. shared references (even when both use the files backend), that might be a third patch series between the other two.

The best dividing line to choose is between "uncontroversial" and "possibly controversial", because the former are more likely to succeed in a fast track.

Michael
-- 
Michael Haggerty
mhagger@alum.mit.edu
David Turner· Sep 28, 2015, 22:02 UTC · re: David Turner · lore

[PATCH v2 43/43] refs: tests for db backend

Add tests for the database backend.
Signed-off-by: David Turner <dturner@twopensource.com>
---
 t/t1460-refs-be-db.sh        | 1103 ++++++++++++++++++++++++++++++++++++++++++
 t/t1470-refs-be-db-reflog.sh |  353 ++++++++++++++
 2 files changed, 1456 insertions(+)
 create mode 100755 t/t1460-refs-be-db.sh
 create mode 100755 t/t1470-refs-be-db-reflog.sh
Show changes to 2 files +1456 −0

t/t1460-refs-be-db.sh, t/t1470-refs-be-db-reflog.sh

diff --git a/t/t1460-refs-be-db.sh b/t/t1460-refs-be-db.sh
new file mode 100755
index 0000000..f13b0f0
--- /dev/null
+++ b/t/t1460-refs-be-db.sh
@@ -0,0 +1,1103 @@
+#!/bin/sh
+#
+# Copyright (c) 2015 Twitter, Inc
+# Copyright (c) 2006 Shawn Pearce
+# This test is based on t1400-update-ref.sh
+#
+
+test_description='Test lmdb refs backend'
+TEST_NO_CREATE_REPO=1
+. ./test-lib.sh
+
+raw_ref() {
+	test-refs-be-lmdb "$1"
+}
+
+delete_ref() {
+	test-refs-be-lmdb -d "$1"
+}
+
+write_ref() {
+	test-refs-be-lmdb "$1" "$2"
+}
+
+raw_reflog() {
+	test-refs-be-lmdb -l "$1"
+}
+
+delete_all_reflogs() {
+	test-refs-be-lmdb -c
+}
+
+append_reflog() {
+	test-refs-be-lmdb -a "$1"
+}
+
+Z=$_z40
+
+test_expect_success setup '
+	git init --refs-backend-type=lmdb &&
+	for name in A B C D E F
+	do
+		test_tick &&
+		T=$(git write-tree) &&
+		sha1=$(echo $name | git commit-tree $T) &&
+		eval $name=$sha1
+	done
+'
+
+m=refs/heads/master
+n_dir=refs/heads/gu
+n=$n_dir/fixes
+
+test_expect_success \
+	"create $m" \
+	"git update-ref $m $A &&
+	 test $A"' = $(raw_ref '"$m"')'
+test_expect_success \
+	"create $m" \
+	"git update-ref $m $B $A &&
+	 test $B"' = $(raw_ref '"$m"')'
+test_expect_success "fail to delete $m with stale ref" '
+	test_must_fail git update-ref -d $m $A &&
+	test $B = "$(raw_ref $m)"
+'
+test_expect_success "delete $m" '
+	git update-ref -d $m $B &&
+	! raw_ref $m
+'
+delete_ref $m
+
+test_expect_success "delete $m without oldvalue verification" "
+	git update-ref $m $A &&
+	test $A = \$(raw_ref $m) &&
+	git update-ref -d $m &&
+	! raw_ref $m
+"
+delete_ref $m
+
+test_expect_success \
+	"fail to create $n" \
+	"git update-ref $n_dir $A &&
+	 test_must_fail git update-ref $n $A >out 2>err"
+
+delete_ref $n_dir
+rm -f out err
+
+test_expect_success \
+	"create $m (by HEAD)" \
+	"git update-ref HEAD $A &&
+	 test $A"' = $(raw_ref '"$m"')'
+test_expect_success \
+	"create $m (by HEAD)" \
+	"git update-ref HEAD $B $A &&
+	 test $B"' = $(raw_ref '"$m"')'
+test_expect_success "fail to delete $m (by HEAD) with stale ref" '
+	test_must_fail git update-ref -d HEAD $A &&
+	test $B = $(raw_ref '"$m"')
+'
+test_expect_success "delete $m (by HEAD)" '
+	git update-ref -d HEAD $B &&
+	! raw_ref $m
+'
+delete_ref $m
+
+test_expect_success \
+	"create $m (by HEAD)" \
+	"git update-ref HEAD $A &&
+	 test $A"' = $(raw_ref '"$m"')'
+test_expect_success \
+	"pack refs" \
+	"git pack-refs --all"
+test_expect_success \
+	"move $m (by HEAD)" \
+	"git update-ref HEAD $B $A &&
+	 test $B"' = $(raw_ref '"$m"')'
+test_expect_success "delete $m (by HEAD) should remove both packed and loose $m" '
+	git update-ref -d HEAD $B &&
+	! raw_ref $m
+'
+delete_ref $m
+
+OLD_HEAD=$(raw_ref HEAD)
+test_expect_success "delete symref without dereference" '
+	git update-ref --no-deref -d HEAD &&
+	! raw_ref HEAD
+'
+write_ref HEAD "$OLD_HEAD"
+
+test_expect_success "delete symref without dereference when the referred ref is packed" '
+	echo foo >foo.c &&
+	git add foo.c &&
+	git commit -m foo &&
+	git pack-refs --all &&
+	git update-ref --no-deref -d HEAD &&
+	! raw_ref HEAD
+'
+write_ref HEAD "$OLD_HEAD"
+delete_ref $m
+
+test_expect_success 'update-ref -d is not confused by self-reference' '
+	git symbolic-ref refs/heads/self refs/heads/self &&
+	test_when_finished "delete_ref refs/heads/self" &&
+	test_must_fail git update-ref -d refs/heads/self
+'
+
+test_expect_success 'update-ref --no-deref -d can delete self-reference' '
+	git symbolic-ref refs/heads/self refs/heads/self &&
+	test_when_finished "delete_ref refs/heads/self" &&
+	git update-ref --no-deref -d refs/heads/self
+'
+
+test_expect_success 'update-ref --no-deref -d can delete reference to bad ref' '
+	test-refs-be-lmdb refs/heads/bad "" &&
+	test_when_finished "delete_ref refs/heads/bad" &&
+	git symbolic-ref refs/heads/ref-to-bad refs/heads/bad &&
+	test_when_finished "delete_ref refs/heads/ref-to-bad" &&
+	raw_ref refs/heads/ref-to-bad &&
+	git update-ref --no-deref -d refs/heads/ref-to-bad &&
+	! raw_ref refs/heads/ref-to-bad
+'
+
+test_expect_success '(not) create HEAD with old sha1' "
+	test_must_fail git update-ref HEAD $A $B
+"
+test_expect_success "(not) prior created .git/$m" "
+	! raw_ref $m
+"
+delete_ref $m
+
+test_expect_success \
+	"create HEAD" \
+	"git update-ref HEAD $A"
+test_expect_success '(not) change HEAD with wrong SHA1' "
+	test_must_fail git update-ref HEAD $B $Z
+"
+test_expect_success "(not) changed .git/$m" "
+	! test $B"' = $(raw_ref '"$m"')
+'
+
+: a repository with working tree always has reflog these days...
+delete_all_reflogs
+: | append_reflog $m
+delete_ref $m
+
+test_expect_success \
+	"create $m (logged by touch)" \
+	'GIT_COMMITTER_DATE="2005-05-26 23:30" \
+	 git update-ref HEAD '"$A"' -m "Initial Creation" &&
+	 test '"$A"' = $(raw_ref '"$m"')'
+test_expect_success \
+	"update $m (logged by touch)" \
+	'GIT_COMMITTER_DATE="2005-05-26 23:31" \
+	 git update-ref HEAD'" $B $A "'-m "Switch" &&
+	 test '"$B"' = $(raw_ref '"$m"')'
+test_expect_success \
+	"set $m (logged by touch)" \
+	'GIT_COMMITTER_DATE="2005-05-26 23:41" \
+	 git update-ref HEAD'" $A &&
+	 test $A"' = $(raw_ref '"$m"')'
+
+cat >expect <<EOF
+$Z $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000	Initial Creation
+$A $B $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150260 +0000	Switch
+$B $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150860 +0000
+EOF
+test_expect_success \
+	"verifying $m's log" \
+	"raw_reflog $m >actual &&
+	 test_cmp expect actual"
+delete_ref $m
+delete_all_reflogs
+: | append_reflog $m
+rm -f actual expect
+
+test_expect_success \
+	'enable core.logAllRefUpdates' \
+	'git config core.logAllRefUpdates true &&
+	 test true = $(git config --bool --get core.logAllRefUpdates)'
+
+test_expect_success \
+	"create $m (logged by config)" \
+	'GIT_COMMITTER_DATE="2005-05-26 23:32" \
+	 git update-ref HEAD'" $A "'-m "Initial Creation" &&
+	 test '"$A"' = $(raw_ref '"$m"')'
+test_expect_success \
+	"update $m (logged by config)" \
+	'GIT_COMMITTER_DATE="2005-05-26 23:33" \
+	 git update-ref HEAD'" $B $A "'-m "Switch" &&
+	 test '"$B"' = $(raw_ref '"$m"')'
+test_expect_success \
+	"set $m (logged by config)" \
+	'GIT_COMMITTER_DATE="2005-05-26 23:43" \
+	 git update-ref HEAD '"$A &&
+	 test $A"' = $(raw_ref '"$m"')'
+
+cat >expect <<EOF
+$Z $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150320 +0000	Initial Creation
+$A $B $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150380 +0000	Switch
+$B $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150980 +0000
+EOF
+test_expect_success \
+	"verifying $m's log" \
+	'raw_reflog $m >actual &&
+	test_cmp expect actual'
+delete_ref $m
+rm -f expect
+
+git update-ref $m $D
+git reflog expire --expire=all $m
+
+append_reflog $m <<EOF
+0000000000000000000000000000000000000000 $C $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150320 -0500
+$C $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150350 -0500
+$A $B $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150380 -0500
+$F $Z $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150680 -0500
+$Z $E $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150980 -0500
+EOF
+
+ed="Thu, 26 May 2005 18:32:00 -0500"
+gd="Thu, 26 May 2005 18:33:00 -0500"
+ld="Thu, 26 May 2005 18:43:00 -0500"
+test_expect_success \
+	'Query "master@{May 25 2005}" (before history)' \
+	'rm -f o e &&
+	 git rev-parse --verify "master@{May 25 2005}" >o 2>e &&
+	 test '"$C"' = $(cat o) &&
+	 test "warning: Log for '\'master\'' only goes back to $ed." = "$(cat e)"'
+test_expect_success \
+	"Query master@{2005-05-25} (before history)" \
+	'rm -f o e &&
+	 git rev-parse --verify master@{2005-05-25} >o 2>e &&
+	 test '"$C"' = $(cat o) &&
+	 echo test "warning: Log for '\'master\'' only goes back to $ed." = "$(cat e)"'
+test_expect_success \
+	'Query "master@{May 26 2005 23:31:59}" (1 second before history)' \
+	'rm -f o e &&
+	 git rev-parse --verify "master@{May 26 2005 23:31:59}" >o 2>e &&
+	 test '"$C"' = $(cat o) &&
+	 test "warning: Log for '\''master'\'' only goes back to $ed." = "$(cat e)"'
+test_expect_success \
+	'Query "master@{May 26 2005 23:32:00}" (exactly history start)' \
+	'rm -f o e &&
+	 git rev-parse --verify "master@{May 26 2005 23:32:00}" >o 2>e &&
+	 test '"$C"' = $(cat o) &&
+	 test "" = "$(cat e)"'
+test_expect_success \
+	'Query "master@{May 26 2005 23:32:30}" (first non-creation change)' \
+	'rm -f o e &&
+	 git rev-parse --verify "master@{May 26 2005 23:32:30}" >o 2>e &&
+	 test '"$A"' = $(cat o) &&
+	 test "" = "$(cat e)"'
+test_expect_success \
+	'Query "master@{2005-05-26 23:33:01}" (middle of history with gap)' \
+	'rm -f o e &&
+	 git rev-parse --verify "master@{2005-05-26 23:33:01}" >o 2>e &&
+	 test '"$B"' = $(cat o) &&
+	 test "warning: Log for ref '"$m has gap after $gd"'." = "$(cat e)"'
+test_expect_success \
+	'Query "master@{2005-05-26 23:38:00}" (middle of history)' \
+	'rm -f o e &&
+	 git rev-parse --verify "master@{2005-05-26 23:38:00}" >o 2>e &&
+	 test '"$Z"' = $(cat o) &&
+	 test "" = "$(cat e)"'
+test_expect_success \
+	'Query "master@{2005-05-26 23:43:00}" (exact end of history)' \
+	'rm -f o e &&
+	 git rev-parse --verify "master@{2005-05-26 23:43:00}" >o 2>e &&
+	 test '"$E"' = $(cat o) &&
+	 test "" = "$(cat e)"'
+test_expect_success \
+	'Query "master@{2005-05-28}" (past end of history)' \
+	'rm -f o e &&
+	 git rev-parse --verify "master@{2005-05-28}" >o 2>e &&
+	 test '"$D"' = $(cat o) &&
+	 test "warning: Log for ref '"$m unexpectedly ended on $ld"'." = "$(cat e)"'
+
+
+git reflog expire --expire=all $m
+delete_ref $m
+
+test_expect_success \
+    'creating initial files' \
+    'echo TEST >F &&
+     git add F &&
+	 GIT_AUTHOR_DATE="2005-05-26 23:30" \
+	 GIT_COMMITTER_DATE="2005-05-26 23:30" git commit -m add -a &&
+	 h_TEST=$(git rev-parse --verify HEAD) &&
+	 echo The other day this did not work. >M &&
+	 echo And then Bob told me how to fix it. >>M &&
+	 echo OTHER >F &&
+	 GIT_AUTHOR_DATE="2005-05-26 23:41" \
+	 GIT_COMMITTER_DATE="2005-05-26 23:41" git commit -F M -a &&
+	 h_OTHER=$(git rev-parse --verify HEAD) &&
+	 GIT_AUTHOR_DATE="2005-05-26 23:44" \
+	 GIT_COMMITTER_DATE="2005-05-26 23:44" git commit --amend &&
+	 h_FIXED=$(git rev-parse --verify HEAD) &&
+	 echo Merged initial commit and a later commit. >M &&
+	 echo $h_TEST >.git/MERGE_HEAD &&
+	 GIT_AUTHOR_DATE="2005-05-26 23:45" \
+	 GIT_COMMITTER_DATE="2005-05-26 23:45" git commit -F M &&
+	 h_MERGED=$(git rev-parse --verify HEAD) &&
+	 rm -f M'
+
+cat >expect <<EOF
+$Z $h_TEST $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000	commit (initial): add
+$h_TEST $h_OTHER $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150860 +0000	commit: The other day this did not work.
+$h_OTHER $h_FIXED $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117151040 +0000	commit (amend): The other day this did not work.
+$h_FIXED $h_MERGED $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117151100 +0000	commit (merge): Merged initial commit and a later commit.
+EOF
+test_expect_success \
+	'git commit logged updates' \
+	"raw_reflog $m >actual &&
+	test_cmp expect actual"
+unset h_TEST h_OTHER h_FIXED h_MERGED
+
+test_expect_success \
+	'git cat-file blob master:F (expect OTHER)' \
+	'test OTHER = $(git cat-file blob master:F)'
+test_expect_success \
+	'git cat-file blob master@{2005-05-26 23:30}:F (expect TEST)' \
+	'test TEST = $(git cat-file blob "master@{2005-05-26 23:30}:F")'
+test_expect_success \
+	'git cat-file blob master@{2005-05-26 23:42}:F (expect OTHER)' \
+	'test OTHER = $(git cat-file blob "master@{2005-05-26 23:42}:F")'
+
+a=refs/heads/a
+b=refs/heads/b
+c=refs/heads/c
+E='""'
+F='%s\0'
+pws='path with space'
+
+test_expect_success 'stdin test setup' '
+	echo "$pws" >"$pws" &&
+	git add -- "$pws" &&
+	git commit -m "$pws"
+'
+
+test_expect_success '-z fails without --stdin' '
+	test_must_fail git update-ref -z $m $m $m 2>err &&
+	grep "usage: git update-ref" err
+'
+
+test_expect_success 'stdin works with no input' '
+	>stdin &&
+	git update-ref --stdin <stdin &&
+	git rev-parse --verify -q $m
+'
+
+test_expect_success 'stdin fails on empty line' '
+	echo "" >stdin &&
+	test_must_fail git update-ref --stdin <stdin 2>err &&
+	grep "fatal: empty command in input" err
+'
+
+test_expect_success 'stdin fails on only whitespace' '
+	echo " " >stdin &&
+	test_must_fail git update-ref --stdin <stdin 2>err &&
+	grep "fatal: whitespace before command:  " err
+'
+
+test_expect_success 'stdin fails on leading whitespace' '
+	echo " create $a $m" >stdin &&
+	test_must_fail git update-ref --stdin <stdin 2>err &&
+	grep "fatal: whitespace before command:  create $a $m" err
+'
+
+test_expect_success 'stdin fails on unknown command' '
+	echo "unknown $a" >stdin &&
+	test_must_fail git update-ref --stdin <stdin 2>err &&
+	grep "fatal: unknown command: unknown $a" err
+'
+
+test_expect_success 'stdin fails on unbalanced quotes' '
+	echo "create $a \"master" >stdin &&
+	test_must_fail git update-ref --stdin <stdin 2>err &&
+	grep "fatal: badly quoted argument: \\\"master" err
+'
+
+test_expect_success 'stdin fails on invalid escape' '
+	echo "create $a \"ma\zter\"" >stdin &&
+	test_must_fail git update-ref --stdin <stdin 2>err &&
+	grep "fatal: badly quoted argument: \\\"ma\\\\zter\\\"" err
+'
+
+test_expect_success 'stdin fails on junk after quoted argument' '
+	echo "create \"$a\"master" >stdin &&
+	test_must_fail git update-ref --stdin <stdin 2>err &&
+	grep "fatal: unexpected character after quoted argument: \\\"$a\\\"master" err
+'
+
+test_expect_success 'stdin fails create with no ref' '
+	echo "create " >stdin &&
+	test_must_fail git update-ref --stdin <stdin 2>err &&
+	grep "fatal: create: missing <ref>" err
+'
+
+test_expect_success 'stdin fails create with no new value' '
+	echo "create $a" >stdin &&
+	test_must_fail git update-ref --stdin <stdin 2>err &&
+	grep "fatal: create $a: missing <newvalue>" err
+'
+
+test_expect_success 'stdin fails create with too many arguments' '
+	echo "create $a $m $m" >stdin &&
+	test_must_fail git update-ref --stdin <stdin 2>err &&
+	grep "fatal: create $a: extra input:  $m" err
+'
+
+test_expect_success 'stdin fails update with no ref' '
+	echo "update " >stdin &&
+	test_must_fail git update-ref --stdin <stdin 2>err &&
+	grep "fatal: update: missing <ref>" err
+'
+
+test_expect_success 'stdin fails update with no new value' '
+	echo "update $a" >stdin &&
+	test_must_fail git update-ref --stdin <stdin 2>err &&
+	grep "fatal: update $a: missing <newvalue>" err
+'
+
+test_expect_success 'stdin fails update with too many arguments' '
+	echo "update $a $m $m $m" >stdin &&
+	test_must_fail git update-ref --stdin <stdin 2>err &&
+	grep "fatal: update $a: extra input:  $m" err
+'
+
+test_expect_success 'stdin fails delete with no ref' '
+	echo "delete " >stdin &&
+	test_must_fail git update-ref --stdin <stdin 2>err &&
+	grep "fatal: delete: missing <ref>" err
+'
+
+test_expect_success 'stdin fails delete with too many arguments' '
+	echo "delete $a $m $m" >stdin &&
+	test_must_fail git update-ref --stdin <stdin 2>err &&
+	grep "fatal: delete $a: extra input:  $m" err
+'
+
+test_expect_success 'stdin fails verify with too many arguments' '
+	echo "verify $a $m $m" >stdin &&
+	test_must_fail git update-ref --stdin <stdin 2>err &&
+	grep "fatal: verify $a: extra input:  $m" err
+'
+
+test_expect_success 'stdin fails option with unknown name' '
+	echo "option unknown" >stdin &&
+	test_must_fail git update-ref --stdin <stdin 2>err &&
+	grep "fatal: option unknown: unknown" err
+'
+
+test_expect_success 'stdin fails with duplicate refs' '
+	cat >stdin <<-EOF &&
+	create $a $m
+	create $b $m
+	create $a $m
+	EOF
+	test_must_fail git update-ref --stdin <stdin 2>err &&
+	grep "fatal: Multiple updates for ref '"'"'$a'"'"' not allowed." err
+'
+
+test_expect_success 'stdin create ref works' '
+	echo "create $a $m" >stdin &&
+	git update-ref --stdin <stdin &&
+	git rev-parse $m >expect &&
+	git rev-parse $a >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'stdin succeeds with quoted argument' '
+	git update-ref -d $a &&
+	echo "create $a \"$m\"" >stdin &&
+	git update-ref --stdin <stdin &&
+	git rev-parse $m >expect &&
+	git rev-parse $a >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'stdin succeeds with escaped character' '
+	git update-ref -d $a &&
+	echo "create $a \"ma\\163ter\"" >stdin &&
+	git update-ref --stdin <stdin &&
+	git rev-parse $m >expect &&
+	git rev-parse $a >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'stdin update ref creates with zero old value' '
+	echo "update $b $m $Z" >stdin &&
+	git update-ref --stdin <stdin &&
+	git rev-parse $m >expect &&
+	git rev-parse $b >actual &&
+	test_cmp expect actual &&
+	git update-ref -d $b
+'
+
+test_expect_success 'stdin update ref creates with empty old value' '
+	echo "update $b $m $E" >stdin &&
+	git update-ref --stdin <stdin &&
+	git rev-parse $m >expect &&
+	git rev-parse $b >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'stdin create ref works with path with space to blob' '
+	echo "create refs/blobs/pws \"$m:$pws\"" >stdin &&
+	git update-ref --stdin <stdin &&
+	git rev-parse "$m:$pws" >expect &&
+	git rev-parse refs/blobs/pws >actual &&
+	test_cmp expect actual &&
+	git update-ref -d refs/blobs/pws
+'
+
+test_expect_success 'stdin update ref fails with wrong old value' '
+	echo "update $c $m $m~1" >stdin &&
+	test_must_fail git update-ref --stdin <stdin 2>err &&
+	grep "fatal: cannot lock the ref '"'"'$c'"'"'" err &&
+	test_must_fail git rev-parse --verify -q $c
+'
+
+test_expect_success 'stdin update ref fails with bad old value' '
+	echo "update $c $m does-not-exist" >stdin &&
+	test_must_fail git update-ref --stdin <stdin 2>err &&
+	grep "fatal: update $c: invalid <oldvalue>: does-not-exist" err &&
+	test_must_fail git rev-parse --verify -q $c
+'
+
+test_expect_success 'stdin create ref fails with bad new value' '
+	echo "create $c does-not-exist" >stdin &&
+	test_must_fail git update-ref --stdin <stdin 2>err &&
+	grep "fatal: create $c: invalid <newvalue>: does-not-exist" err &&
+	test_must_fail git rev-parse --verify -q $c
+'
+
+test_expect_success 'stdin create ref fails with zero new value' '
+	echo "create $c " >stdin &&
+	test_must_fail git update-ref --stdin <stdin 2>err &&
+	grep "fatal: create $c: zero <newvalue>" err &&
+	test_must_fail git rev-parse --verify -q $c
+'
+
+test_expect_success 'stdin update ref works with right old value' '
+	echo "update $b $m~1 $m" >stdin &&
+	git update-ref --stdin <stdin &&
+	git rev-parse $m~1 >expect &&
+	git rev-parse $b >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'stdin delete ref fails with wrong old value' '
+	echo "delete $a $m~1" >stdin &&
+	test_must_fail git update-ref --stdin <stdin 2>err &&
+	grep "fatal: cannot lock the ref '"'"'$a'"'"'" err &&
+	git rev-parse $m >expect &&
+	git rev-parse $a >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'stdin delete ref fails with zero old value' '
+	echo "delete $a " >stdin &&
+	test_must_fail git update-ref --stdin <stdin 2>err &&
+	grep "fatal: delete $a: zero <oldvalue>" err &&
+	git rev-parse $m >expect &&
+	git rev-parse $a >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'stdin update symref works option no-deref' '
+	git symbolic-ref refs/TESTSYMREF $b &&
+	cat >stdin <<-EOF &&
+	option no-deref
+	update refs/TESTSYMREF $a $b
+	EOF
+	git update-ref --stdin <stdin &&
+	git rev-parse refs/TESTSYMREF >expect &&
+	git rev-parse $a >actual &&
+	test_cmp expect actual &&
+	git rev-parse $m~1 >expect &&
+	git rev-parse $b >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'stdin delete symref works option no-deref' '
+	git symbolic-ref refs/TESTSYMREF $b &&
+	cat >stdin <<-EOF &&
+	option no-deref
+	delete refs/TESTSYMREF $b
+	EOF
+	git update-ref --stdin <stdin &&
+	test_must_fail git rev-parse --verify -q refs/TESTSYMREF &&
+	git rev-parse $m~1 >expect &&
+	git rev-parse $b >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'stdin delete ref works with right old value' '
+	echo "delete $b $m~1" >stdin &&
+	git update-ref --stdin <stdin &&
+	test_must_fail git rev-parse --verify -q $b
+'
+
+test_expect_success 'stdin update/create/verify combination works' '
+	cat >stdin <<-EOF &&
+	update $a $m
+	create $b $m
+	verify $c
+	EOF
+	git update-ref --stdin <stdin &&
+	git rev-parse $m >expect &&
+	git rev-parse $a >actual &&
+	test_cmp expect actual &&
+	git rev-parse $b >actual &&
+	test_cmp expect actual &&
+	test_must_fail git rev-parse --verify -q $c
+'
+
+test_expect_success 'stdin verify succeeds for correct value' '
+	git rev-parse $m >expect &&
+	echo "verify $m $m" >stdin &&
+	git update-ref --stdin <stdin &&
+	git rev-parse $m >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'stdin verify succeeds for missing reference' '
+	echo "verify refs/heads/missing $Z" >stdin &&
+	git update-ref --stdin <stdin &&
+	test_must_fail git rev-parse --verify -q refs/heads/missing
+'
+
+test_expect_success 'stdin verify treats no value as missing' '
+	echo "verify refs/heads/missing" >stdin &&
+	git update-ref --stdin <stdin &&
+	test_must_fail git rev-parse --verify -q refs/heads/missing
+'
+
+test_expect_success 'stdin verify fails for wrong value' '
+	git rev-parse $m >expect &&
+	echo "verify $m $m~1" >stdin &&
+	test_must_fail git update-ref --stdin <stdin &&
+	git rev-parse $m >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'stdin verify fails for mistaken null value' '
+	git rev-parse $m >expect &&
+	echo "verify $m $Z" >stdin &&
+	test_must_fail git update-ref --stdin <stdin &&
+	git rev-parse $m >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'stdin verify fails for mistaken empty value' '
+	M=$(git rev-parse $m) &&
+	test_when_finished "git update-ref $m $M" &&
+	git rev-parse $m >expect &&
+	echo "verify $m" >stdin &&
+	test_must_fail git update-ref --stdin <stdin &&
+	git rev-parse $m >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'stdin update refs works with identity updates' '
+	cat >stdin <<-EOF &&
+	update $a $m $m
+	update $b $m $m
+	update $c $Z $E
+	EOF
+	git update-ref --stdin <stdin &&
+	git rev-parse $m >expect &&
+	git rev-parse $a >actual &&
+	test_cmp expect actual &&
+	git rev-parse $b >actual &&
+	test_cmp expect actual &&
+	test_must_fail git rev-parse --verify -q $c
+'
+
+test_expect_success 'stdin update refs fails with wrong old value' '
+	git update-ref $c $m &&
+	cat >stdin <<-EOF &&
+	update $a $m $m
+	update $b $m $m
+	update $c  ''
+	EOF
+	test_must_fail git update-ref --stdin <stdin 2>err &&
+	grep "fatal: cannot lock the ref '"'"'$c'"'"'" err &&
+	git rev-parse $m >expect &&
+	git rev-parse $a >actual &&
+	test_cmp expect actual &&
+	git rev-parse $b >actual &&
+	test_cmp expect actual &&
+	git rev-parse $c >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'stdin delete refs works with packed and loose refs' '
+	git pack-refs --all &&
+	git update-ref $c $m~1 &&
+	cat >stdin <<-EOF &&
+	delete $a $m
+	update $b $Z $m
+	update $c $E $m~1
+	EOF
+	git update-ref --stdin <stdin &&
+	test_must_fail git rev-parse --verify -q $a &&
+	test_must_fail git rev-parse --verify -q $b &&
+	test_must_fail git rev-parse --verify -q $c
+'
+
+test_expect_success 'stdin -z works on empty input' '
+	>stdin &&
+	git update-ref -z --stdin <stdin &&
+	git rev-parse --verify -q $m
+'
+
+test_expect_success 'stdin -z fails on empty line' '
+	echo "" >stdin &&
+	test_must_fail git update-ref -z --stdin <stdin 2>err &&
+	grep "fatal: whitespace before command: " err
+'
+
+test_expect_success 'stdin -z fails on empty command' '
+	printf $F "" >stdin &&
+	test_must_fail git update-ref -z --stdin <stdin 2>err &&
+	grep "fatal: empty command in input" err
+'
+
+test_expect_success 'stdin -z fails on only whitespace' '
+	printf $F " " >stdin &&
+	test_must_fail git update-ref -z --stdin <stdin 2>err &&
+	grep "fatal: whitespace before command:  " err
+'
+
+test_expect_success 'stdin -z fails on leading whitespace' '
+	printf $F " create $a" "$m" >stdin &&
+	test_must_fail git update-ref -z --stdin <stdin 2>err &&
+	grep "fatal: whitespace before command:  create $a" err
+'
+
+test_expect_success 'stdin -z fails on unknown command' '
+	printf $F "unknown $a" >stdin &&
+	test_must_fail git update-ref -z --stdin <stdin 2>err &&
+	grep "fatal: unknown command: unknown $a" err
+'
+
+test_expect_success 'stdin -z fails create with no ref' '
+	printf $F "create " >stdin &&
+	test_must_fail git update-ref -z --stdin <stdin 2>err &&
+	grep "fatal: create: missing <ref>" err
+'
+
+test_expect_success 'stdin -z fails create with no new value' '
+	printf $F "create $a" >stdin &&
+	test_must_fail git update-ref -z --stdin <stdin 2>err &&
+	grep "fatal: create $a: unexpected end of input when reading <newvalue>" err
+'
+
+test_expect_success 'stdin -z fails create with too many arguments' '
+	printf $F "create $a" "$m" "$m" >stdin &&
+	test_must_fail git update-ref -z --stdin <stdin 2>err &&
+	grep "fatal: unknown command: $m" err
+'
+
+test_expect_success 'stdin -z fails update with no ref' '
+	printf $F "update " >stdin &&
+	test_must_fail git update-ref -z --stdin <stdin 2>err &&
+	grep "fatal: update: missing <ref>" err
+'
+
+test_expect_success 'stdin -z fails update with too few args' '
+	printf $F "update $a" "$m" >stdin &&
+	test_must_fail git update-ref -z --stdin <stdin 2>err &&
+	grep "fatal: update $a: unexpected end of input when reading <oldvalue>" err
+'
+
+test_expect_success 'stdin -z emits warning with empty new value' '
+	git update-ref $a $m &&
+	printf $F "update $a" "" "" >stdin &&
+	git update-ref -z --stdin <stdin 2>err &&
+	grep "warning: update $a: missing <newvalue>, treating as zero" err &&
+	test_must_fail git rev-parse --verify -q $a
+'
+
+test_expect_success 'stdin -z fails update with no new value' '
+	printf $F "update $a" >stdin &&
+	test_must_fail git update-ref -z --stdin <stdin 2>err &&
+	grep "fatal: update $a: unexpected end of input when reading <newvalue>" err
+'
+
+test_expect_success 'stdin -z fails update with no old value' '
+	printf $F "update $a" "$m" >stdin &&
+	test_must_fail git update-ref -z --stdin <stdin 2>err &&
+	grep "fatal: update $a: unexpected end of input when reading <oldvalue>" err
+'
+
+test_expect_success 'stdin -z fails update with too many arguments' '
+	printf $F "update $m" "$m" "$m" "$m" >stdin &&
+	test_must_fail git update-ref -z --stdin <stdin 2>err &&
+	grep "fatal: unknown command: $m" err
+'
+
+test_expect_success 'stdin -z fails delete with no ref' '
+	printf $F "delete " >stdin &&
+	test_must_fail git update-ref -z --stdin <stdin 2>err &&
+	grep "fatal: delete: missing <ref>" err
+'
+
+test_expect_success 'stdin -z fails delete with no old value' '
+	printf $F "delete $a" >stdin &&
+	test_must_fail git update-ref -z --stdin <stdin 2>err &&
+	grep "fatal: delete $a: unexpected end of input when reading <oldvalue>" err
+'
+
+test_expect_success 'stdin -z fails delete with too many arguments' '
+	printf $F "delete $m" "$m" "$m" >stdin &&
+	test_must_fail git update-ref -z --stdin <stdin 2>err &&
+	grep "fatal: unknown command: $m" err
+'
+
+test_expect_success 'stdin -z fails verify with too many arguments' '
+	printf $F "verify $m" "$m" "$m" >stdin &&
+	test_must_fail git update-ref -z --stdin <stdin 2>err &&
+	grep "fatal: unknown command: $m" err
+'
+
+test_expect_success 'stdin -z fails verify with no old value' '
+	printf $F "verify $a" >stdin &&
+	test_must_fail git update-ref -z --stdin <stdin 2>err &&
+	grep "fatal: verify $a: unexpected end of input when reading <oldvalue>" err
+'
+
+test_expect_success 'stdin -z fails option with unknown name' '
+	printf $F "option unknown" >stdin &&
+	test_must_fail git update-ref -z --stdin <stdin 2>err &&
+	grep "fatal: option unknown: unknown" err
+'
+
+test_expect_success 'stdin -z fails with duplicate refs' '
+	printf $F "create $a" "$m" "create $b" "$m" "create $a" "$m" >stdin &&
+	test_must_fail git update-ref -z --stdin <stdin 2>err &&
+	grep "fatal: Multiple updates for ref '"'"'$a'"'"' not allowed." err
+'
+
+test_expect_success 'stdin -z create ref works' '
+	printf $F "create $a" "$m" >stdin &&
+	git update-ref -z --stdin <stdin &&
+	git rev-parse $m >expect &&
+	git rev-parse $a >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'stdin -z update ref creates with zero old value' '
+	printf $F "update $b" "$m" "$Z" >stdin &&
+	git update-ref -z --stdin <stdin &&
+	git rev-parse $m >expect &&
+	git rev-parse $b >actual &&
+	test_cmp expect actual &&
+	git update-ref -d $b
+'
+
+test_expect_success 'stdin -z update ref creates with empty old value' '
+	printf $F "update $b" "$m" "" >stdin &&
+	git update-ref -z --stdin <stdin &&
+	git rev-parse $m >expect &&
+	git rev-parse $b >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'stdin -z create ref works with path with space to blob' '
+	printf $F "create refs/blobs/pws" "$m:$pws" >stdin &&
+	git update-ref -z --stdin <stdin &&
+	git rev-parse "$m:$pws" >expect &&
+	git rev-parse refs/blobs/pws >actual &&
+	test_cmp expect actual &&
+	git update-ref -d refs/blobs/pws
+'
+
+test_expect_success 'stdin -z update ref fails with wrong old value' '
+	printf $F "update $c" "$m" "$m~1" >stdin &&
+	test_must_fail git update-ref -z --stdin <stdin 2>err &&
+	grep "fatal: cannot lock the ref '"'"'$c'"'"'" err &&
+	test_must_fail git rev-parse --verify -q $c
+'
+
+test_expect_success 'stdin -z update ref fails with bad old value' '
+	printf $F "update $c" "$m" "does-not-exist" >stdin &&
+	test_must_fail git update-ref -z --stdin <stdin 2>err &&
+	grep "fatal: update $c: invalid <oldvalue>: does-not-exist" err &&
+	test_must_fail git rev-parse --verify -q $c
+'
+
+test_expect_success 'stdin -z create ref fails when ref exists' '
+	git update-ref $c $m &&
+	git rev-parse "$c" >expect &&
+	printf $F "create $c" "$m~1" >stdin &&
+	test_must_fail git update-ref -z --stdin <stdin 2>err &&
+	grep "fatal: cannot lock the ref '"'"'$c'"'"'" err &&
+	git rev-parse "$c" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'stdin -z create ref fails with bad new value' '
+	git update-ref -d "$c" &&
+	printf $F "create $c" "does-not-exist" >stdin &&
+	test_must_fail git update-ref -z --stdin <stdin 2>err &&
+	grep "fatal: create $c: invalid <newvalue>: does-not-exist" err &&
+	test_must_fail git rev-parse --verify -q $c
+'
+
+test_expect_success 'stdin -z create ref fails with empty new value' '
+	printf $F "create $c" "" >stdin &&
+	test_must_fail git update-ref -z --stdin <stdin 2>err &&
+	grep "fatal: create $c: missing <newvalue>" err &&
+	test_must_fail git rev-parse --verify -q $c
+'
+
+test_expect_success 'stdin -z update ref works with right old value' '
+	printf $F "update $b" "$m~1" "$m" >stdin &&
+	git update-ref -z --stdin <stdin &&
+	git rev-parse $m~1 >expect &&
+	git rev-parse $b >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'stdin -z delete ref fails with wrong old value' '
+	printf $F "delete $a" "$m~1" >stdin &&
+	test_must_fail git update-ref -z --stdin <stdin 2>err &&
+	grep "fatal: cannot lock the ref '"'"'$a'"'"'" err &&
+	git rev-parse $m >expect &&
+	git rev-parse $a >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'stdin -z delete ref fails with zero old value' '
+	printf $F "delete $a" "$Z" >stdin &&
+	test_must_fail git update-ref -z --stdin <stdin 2>err &&
+	grep "fatal: delete $a: zero <oldvalue>" err &&
+	git rev-parse $m >expect &&
+	git rev-parse $a >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'stdin -z update symref works option no-deref' '
+	git symbolic-ref refs/TESTSYMREF $b &&
+	printf $F "option no-deref" "update refs/TESTSYMREF" "$a" "$b" >stdin &&
+	git update-ref -z --stdin <stdin &&
+	git rev-parse refs/TESTSYMREF >expect &&
+	git rev-parse $a >actual &&
+	test_cmp expect actual &&
+	git rev-parse $m~1 >expect &&
+	git rev-parse $b >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'stdin -z delete symref works option no-deref' '
+	git symbolic-ref refs/TESTSYMREF $b &&
+	printf $F "option no-deref" "delete refs/TESTSYMREF" "$b" >stdin &&
+	git update-ref -z --stdin <stdin &&
+	test_must_fail git rev-parse --verify -q refs/TESTSYMREF &&
+	git rev-parse $m~1 >expect &&
+	git rev-parse $b >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'stdin -z delete ref works with right old value' '
+	printf $F "delete $b" "$m~1" >stdin &&
+	git update-ref -z --stdin <stdin &&
+	test_must_fail git rev-parse --verify -q $b
+'
+
+test_expect_success 'stdin -z update/create/verify combination works' '
+	printf $F "update $a" "$m" "" "create $b" "$m" "verify $c" "" >stdin &&
+	git update-ref -z --stdin <stdin &&
+	git rev-parse $m >expect &&
+	git rev-parse $a >actual &&
+	test_cmp expect actual &&
+	git rev-parse $b >actual &&
+	test_cmp expect actual &&
+	test_must_fail git rev-parse --verify -q $c
+'
+
+test_expect_success 'stdin -z verify succeeds for correct value' '
+	git rev-parse $m >expect &&
+	printf $F "verify $m" "$m" >stdin &&
+	git update-ref -z --stdin <stdin &&
+	git rev-parse $m >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'stdin -z verify succeeds for missing reference' '
+	printf $F "verify refs/heads/missing" "$Z" >stdin &&
+	git update-ref -z --stdin <stdin &&
+	test_must_fail git rev-parse --verify -q refs/heads/missing
+'
+
+test_expect_success 'stdin -z verify treats no value as missing' '
+	printf $F "verify refs/heads/missing" "" >stdin &&
+	git update-ref -z --stdin <stdin &&
+	test_must_fail git rev-parse --verify -q refs/heads/missing
+'
+
+test_expect_success 'stdin -z verify fails for wrong value' '
+	git rev-parse $m >expect &&
+	printf $F "verify $m" "$m~1" >stdin &&
+	test_must_fail git update-ref -z --stdin <stdin &&
+	git rev-parse $m >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'stdin -z verify fails for mistaken null value' '
+	git rev-parse $m >expect &&
+	printf $F "verify $m" "$Z" >stdin &&
+	test_must_fail git update-ref -z --stdin <stdin &&
+	git rev-parse $m >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'stdin -z verify fails for mistaken empty value' '
+	M=$(git rev-parse $m) &&
+	test_when_finished "git update-ref $m $M" &&
+	git rev-parse $m >expect &&
+	printf $F "verify $m" "" >stdin &&
+	test_must_fail git update-ref -z --stdin <stdin &&
+	git rev-parse $m >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'stdin -z update refs works with identity updates' '
+	printf $F "update $a" "$m" "$m" "update $b" "$m" "$m" "update $c" "$Z" "" >stdin &&
+	git update-ref -z --stdin <stdin &&
+	git rev-parse $m >expect &&
+	git rev-parse $a >actual &&
+	test_cmp expect actual &&
+	git rev-parse $b >actual &&
+	test_cmp expect actual &&
+	test_must_fail git rev-parse --verify -q $c
+'
+
+test_expect_success 'stdin -z update refs fails with wrong old value' '
+	git update-ref $c $m &&
+	printf $F "update $a" "$m" "$m" "update $b" "$m" "$m" "update $c" "$m" "$Z" >stdin &&
+	test_must_fail git update-ref -z --stdin <stdin 2>err &&
+	grep "fatal: cannot lock the ref '"'"'$c'"'"'" err &&
+	git rev-parse $m >expect &&
+	git rev-parse $a >actual &&
+	test_cmp expect actual &&
+	git rev-parse $b >actual &&
+	test_cmp expect actual &&
+	git rev-parse $c >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'stdin -z delete refs works with packed and loose refs' '
+	git pack-refs --all &&
+	git update-ref $c $m~1 &&
+	printf $F "delete $a" "$m" "update $b" "$Z" "$m" "update $c" "" "$m~1" >stdin &&
+	git update-ref -z --stdin <stdin &&
+	test_must_fail git rev-parse --verify -q $a &&
+	test_must_fail git rev-parse --verify -q $b &&
+	test_must_fail git rev-parse --verify -q $c
+'
+
+test_done
diff --git a/t/t1470-refs-be-db-reflog.sh b/t/t1470-refs-be-db-reflog.sh
new file mode 100755
index 0000000..99a705d
--- /dev/null
+++ b/t/t1470-refs-be-db-reflog.sh
@@ -0,0 +1,353 @@
+#!/bin/sh
+#
+# Copyright (c) 2015 Twitter, Inc
+# Copyright (c) 2007 Junio C Hamano
+#
+
+test_description='Test prune and reflog expiration'
+TEST_NO_CREATE_REPO=1
+. ./test-lib.sh
+
+raw_reflog() {
+	cat .git/logs/$1 2>/dev/null || test-refs-be-lmdb -l "$1"
+}
+
+append_reflog() {
+	test-refs-be-lmdb -a "$1"
+}
+
+check_have () {
+	gaah= &&
+	for N in "$@"
+	do
+		eval "o=\$$N" && git cat-file -t $o || {
+			echo Gaah $N
+			gaah=$N
+			break
+		}
+	done &&
+	test -z "$gaah"
+}
+
+check_fsck () {
+	output=$(git fsck --full)
+	case "$1" in
+	'')
+		test -z "$output" ;;
+	*)
+		echo "$output" | grep "$1" ;;
+	esac
+}
+
+corrupt () {
+	aa=${1%??????????????????????????????????????} zz=${1#??}
+	mv .git/objects/$aa/$zz .git/$aa$zz
+}
+
+recover () {
+	aa=${1%??????????????????????????????????????} zz=${1#??}
+	mkdir -p .git/objects/$aa
+	mv .git/$aa$zz .git/objects/$aa/$zz
+}
+
+check_dont_have () {
+	gaah= &&
+	for N in "$@"
+	do
+		eval "o=\$$N"
+		git cat-file -t $o && {
+			echo Gaah $N
+			gaah=$N
+			break
+		}
+	done
+	test -z "$gaah"
+}
+
+test_expect_success setup '
+	git init --refs-backend-type=lmdb &&
+	mkdir -p A/B &&
+	echo rat >C &&
+	echo ox >A/D &&
+	echo tiger >A/B/E &&
+	git add . &&
+
+	test_tick && git commit -m rabbit &&
+	H=`git rev-parse --verify HEAD` &&
+	A=`git rev-parse --verify HEAD:A` &&
+	B=`git rev-parse --verify HEAD:A/B` &&
+	C=`git rev-parse --verify HEAD:C` &&
+	D=`git rev-parse --verify HEAD:A/D` &&
+	E=`git rev-parse --verify HEAD:A/B/E` &&
+	check_fsck &&
+
+	test_chmod +x C &&
+	git add C &&
+	test_tick && git commit -m dragon &&
+	L=`git rev-parse --verify HEAD` &&
+	check_fsck &&
+
+	rm -f C A/B/E &&
+	echo snake >F &&
+	echo horse >A/G &&
+	git add F A/G &&
+	test_tick && git commit -a -m sheep &&
+	F=`git rev-parse --verify HEAD:F` &&
+	G=`git rev-parse --verify HEAD:A/G` &&
+	I=`git rev-parse --verify HEAD:A` &&
+	J=`git rev-parse --verify HEAD` &&
+	check_fsck &&
+
+	rm -f A/G &&
+	test_tick && git commit -a -m monkey &&
+	K=`git rev-parse --verify HEAD` &&
+	check_fsck &&
+
+	check_have A B C D E F G H I J K L &&
+
+	git prune &&
+
+	check_have A B C D E F G H I J K L &&
+
+	check_fsck &&
+
+	raw_reflog refs/heads/master >reflog &&
+	test_when_finished rm -f reflog &&
+	test_line_count = 4 reflog
+'
+
+test_expect_success rewind '
+	test_tick && git reset --hard HEAD~2 &&
+	test -f C &&
+	test -f A/B/E &&
+	! test -f F &&
+	! test -f A/G &&
+
+	check_have A B C D E F G H I J K L &&
+
+	git prune &&
+
+	check_have A B C D E F G H I J K L &&
+
+	raw_reflog refs/heads/master >reflog &&
+	test_when_finished rm -f reflog &&
+	test_line_count = 5 reflog
+'
+
+test_expect_success 'corrupt and check' '
+
+	corrupt $F &&
+	check_fsck "missing blob $F"
+
+'
+
+test_expect_success 'reflog expire --dry-run should not touch reflog' '
+
+	git reflog expire --dry-run \
+		--expire=$(($test_tick - 10000)) \
+		--expire-unreachable=$(($test_tick - 10000)) \
+		--stale-fix \
+		--all &&
+
+	raw_reflog refs/heads/master >reflog &&
+	test_when_finished rm -f reflog &&
+	test_line_count = 5 reflog &&
+
+	check_fsck "missing blob $F"
+'
+
+test_expect_success 'reflog expire' '
+
+	git reflog expire --verbose \
+		--expire=$(($test_tick - 10000)) \
+		--expire-unreachable=$(($test_tick - 10000)) \
+		--stale-fix \
+		--all &&
+
+	echo git reflog expire --verbose \
+		--expire=$(($test_tick - 10000)) \
+		--expire-unreachable=$(($test_tick - 10000)) \
+		--stale-fix \
+		--all &&
+
+	raw_reflog refs/heads/master >reflog &&
+	test_when_finished rm -f reflog &&
+	test_line_count = 2 reflog &&
+
+	check_fsck "dangling commit $K"
+'
+
+test_expect_success 'prune and fsck' '
+
+	git prune &&
+	check_fsck &&
+
+	check_have A B C D E H L &&
+	check_dont_have F G I J K
+
+'
+
+test_expect_success 'recover and check' '
+
+	recover $F &&
+	check_fsck "dangling blob $F"
+
+'
+
+test_expect_success 'delete' '
+	echo 1 > C &&
+	test_tick &&
+	git commit -m rat C &&
+
+	echo 2 > C &&
+	test_tick &&
+	git commit -m ox C &&
+
+	echo 3 > C &&
+	test_tick &&
+	git commit -m tiger C &&
+
+	HEAD_entry_count=$(git reflog | wc -l) &&
+	master_entry_count=$(git reflog show master | wc -l) &&
+
+	test $HEAD_entry_count = 5 &&
+	test $master_entry_count = 5 &&
+
+
+	git reflog delete master@{1} &&
+	git reflog show master > output &&
+	test $(($master_entry_count - 1)) = $(wc -l < output) &&
+	test $HEAD_entry_count = $(git reflog | wc -l) &&
+	! grep ox < output &&
+
+	master_entry_count=$(wc -l < output) &&
+
+	git reflog delete HEAD@{1} &&
+	test $(($HEAD_entry_count -1)) = $(git reflog | wc -l) &&
+	test $master_entry_count = $(git reflog show master | wc -l) &&
+
+	HEAD_entry_count=$(git reflog | wc -l) &&
+
+	git reflog delete master@{07.04.2005.15:15:00.-0700} &&
+	git reflog show master > output &&
+	test $(($master_entry_count - 1)) = $(wc -l < output) &&
+	! grep dragon < output
+
+'
+
+test_expect_success 'rewind2' '
+
+	test_tick && git reset --hard HEAD~2 &&
+	raw_reflog refs/heads/master >reflog &&
+	test_when_finished rm -f reflog &&
+	test_line_count = 4 reflog
+'
+
+test_expect_success '--expire=never' '
+
+	git reflog expire --verbose \
+		--expire=never \
+		--expire-unreachable=never \
+		--all &&
+	raw_reflog refs/heads/master >reflog &&
+	test_when_finished rm -f reflog &&
+	test_line_count = 4 reflog
+'
+
+test_expect_success 'gc.reflogexpire=never' '
+
+	git config gc.reflogexpire never &&
+	git config gc.reflogexpireunreachable never &&
+	git reflog expire --verbose --all &&
+	raw_reflog refs/heads/master >reflog &&
+	test_when_finished rm -f reflog &&
+	test_line_count = 4 reflog
+'
+
+test_expect_success 'gc.reflogexpire=false' '
+
+	git config gc.reflogexpire false &&
+	git config gc.reflogexpireunreachable false &&
+	git reflog expire --verbose --all &&
+	raw_reflog refs/heads/master >reflog &&
+	test_when_finished rm -f reflog &&
+	test_line_count = 4 reflog &&
+
+	git config --unset gc.reflogexpire &&
+	git config --unset gc.reflogexpireunreachable
+
+'
+
+test_expect_success 'checkout should not delete log for packed ref' '
+	test $(git reflog master | wc -l) = 4 &&
+	git branch foo &&
+	git pack-refs --all &&
+	git checkout foo &&
+	test $(git reflog master | wc -l) = 4
+'
+
+test_expect_success 'stale dirs do not cause d/f conflicts (reflogs on)' '
+	test_when_finished "git branch -d one || git branch -d one/two" &&
+
+	git branch one/two master &&
+	echo "one/two@{0} branch: Created from master" >expect &&
+	git log -g --format="%gd %gs" one/two >actual &&
+	test_cmp expect actual &&
+	git branch -d one/two &&
+
+	# now logs/refs/heads/one is a stale directory, but
+	# we should move it out of the way to create "one" reflog
+	git branch one master &&
+	echo "one@{0} branch: Created from master" >expect &&
+	git log -g --format="%gd %gs" one >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'stale dirs do not cause d/f conflicts (reflogs off)' '
+	test_when_finished "git branch -d one || git branch -d one/two" &&
+
+	git branch one/two master &&
+	echo "one/two@{0} branch: Created from master" >expect &&
+	git log -g --format="%gd %gs" one/two >actual &&
+	test_cmp expect actual &&
+	git branch -d one/two &&
+
+	# same as before, but we only create a reflog for "one" if
+	# it already exists, which it does not
+	git -c core.logallrefupdates=false branch one master &&
+	: >expect &&
+	git log -g --format="%gd %gs" one >actual &&
+	test_cmp expect actual
+'
+
+# Triggering the bug detected by this test requires a newline to fall
+# exactly BUFSIZ-1 bytes from the end of the file. We don't know
+# what that value is, since it's platform dependent. However, if
+# we choose some value N, we also catch any D which divides N evenly
+# (since we will read backwards in chunks of D). So we choose 8K,
+# which catches glibc (with an 8K BUFSIZ) and *BSD (1K).
+#
+# Each line is 114 characters, so we need 75 to still have a few before the
+# last 8K. The 89-character padding on the final entry lines up our
+# newline exactly.
+test_expect_success 'parsing reverse reflogs at BUFSIZ boundaries' '
+	git checkout -b reflogskip &&
+	z38=00000000000000000000000000000000000000 &&
+	ident="abc <xyz> 0000000001 +0000" &&
+	for i in $(test_seq 1 75); do
+		printf "$z38%02d $z38%02d %s\t" $i $(($i+1)) "$ident" &&
+		if test $i = 75; then
+			for j in $(test_seq 1 89); do
+				printf X
+			done
+		else
+			printf X
+		fi &&
+		printf "\n"
+	done | append_reflog refs/heads/reflogskip &&
+	git rev-parse reflogskip@{73} >actual &&
+	echo ${z38}03 >expect &&
+	test_cmp expect actual
+'
+
+test_done
-- 
2.4.2.644.g97b850b-twtrsrc
Dennis Kaarsemaker· Oct 3, 2015, 17:39 UTC · re: David Turner · lore

Re: [PATCH v2 43/43] refs: tests for db backend

On Mon, Sep 28, 2015 at 06:02:18PM -0400, David Turner wrote:
Show 9 quoted lines
> Add tests for the database backend.
> 
> Signed-off-by: David Turner <dturner@twopensource.com>
> ---
>  t/t1460-refs-be-db.sh        | 1103 ++++++++++++++++++++++++++++++++++++++++++
>  t/t1470-refs-be-db-reflog.sh |  353 ++++++++++++++
>  2 files changed, 1456 insertions(+)
>  create mode 100755 t/t1460-refs-be-db.sh
>  create mode 100755 t/t1470-refs-be-db-reflog.sh

These break 'make test' on builds without the db backend. Maybe squash in something like the following:

Show changes to 2 files +10 −0

t/t1460-refs-be-db.sh, t/t1470-refs-be-db-reflog.sh

diff --git a/t/t1460-refs-be-db.sh b/t/t1460-refs-be-db.sh
index f13b0f0..c8222ed 100755
--- a/t/t1460-refs-be-db.sh
+++ b/t/t1460-refs-be-db.sh
@@ -9,6 +9,11 @@ test_description='Test lmdb refs backend'
 TEST_NO_CREATE_REPO=1
 . ./test-lib.sh
 
+if ! test -e ../../test-refs-be-lmdb; then
+	skip_all="Skipping lmdb refs backend tests, lmdb backend not built"
+	test_done
+fi
+
 raw_ref() {
 	test-refs-be-lmdb "$1"
 }
diff --git a/t/t1470-refs-be-db-reflog.sh b/t/t1470-refs-be-db-reflog.sh
index 99a705d..2538a58 100755
--- a/t/t1470-refs-be-db-reflog.sh
+++ b/t/t1470-refs-be-db-reflog.sh
@@ -8,6 +8,11 @@ test_description='Test prune and reflog expiration'
 TEST_NO_CREATE_REPO=1
 . ./test-lib.sh
 
+if ! test -e ../../test-refs-be-lmdb; then
+	skip_all="Skipping lmdb refs backend tests, lmdb backend not built"
+	test_done
+fi
+
 raw_reflog() {
 	cat .git/logs/$1 2>/dev/null || test-refs-be-lmdb -l "$1"
 }


Also, test 18 in t1460 is broken:
expecting success: 
    git symbolic-ref refs/heads/self refs/heads/self &&
        test_when_finished "delete_ref refs/heads/self" &&
            test_must_fail git update-ref -d refs/heads/self

test_must_fail: command succeeded: git update-ref -d refs/heads/self
not ok 18 - update-ref -d is not confused by self-reference
#   
#       git symbolic-ref refs/heads/self refs/heads/self &&
#       test_when_finished "delete_ref refs/heads/self" &&
#       test_must_fail git update-ref -d refs/heads/self
#   
-- 
Dennis Kaarsemaker <dennis@kaarsemaker.net>
http://twitter.com/seveas
Junio C Hamano· Oct 5, 2015, 16:56 UTC · re: Dennis Kaarsemaker · lore

Re: [PATCH v2 43/43] refs: tests for db backend

Dennis Kaarsemaker <dennis@kaarsemaker.net> writes:
Show 26 quoted lines
> On Mon, Sep 28, 2015 at 06:02:18PM -0400, David Turner wrote:
>> Add tests for the database backend.
>> 
>> Signed-off-by: David Turner <dturner@twopensource.com>
>> ---
>>  t/t1460-refs-be-db.sh        | 1103 ++++++++++++++++++++++++++++++++++++++++++
>>  t/t1470-refs-be-db-reflog.sh |  353 ++++++++++++++
>>  2 files changed, 1456 insertions(+)
>>  create mode 100755 t/t1460-refs-be-db.sh
>>  create mode 100755 t/t1470-refs-be-db-reflog.sh
>
> These break 'make test' on builds without the db backend. Maybe squash
> in something like the following:
>
> diff --git a/t/t1460-refs-be-db.sh b/t/t1460-refs-be-db.sh
> index f13b0f0..c8222ed 100755
> --- a/t/t1460-refs-be-db.sh
> +++ b/t/t1460-refs-be-db.sh
> @@ -9,6 +9,11 @@ test_description='Test lmdb refs backend'
>  TEST_NO_CREATE_REPO=1
>  . ./test-lib.sh
>  
> +if ! test -e ../../test-refs-be-lmdb; then
> +	skip_all="Skipping lmdb refs backend tests, lmdb backend not built"
> +	test_done
> +fi

The idea is sound, but $TRASH_DIRECTORY (i.e. $(cwd) there) is not necessarily two subdirectories down from the build repository root (cf. --root=<there> parameter to the test scripts), so you need to account for that.

Thanks.
David Turner· Oct 6, 2015, 00:20 UTC · re: Junio C Hamano · lore

Re: [PATCH v2 43/43] refs: tests for db backend

On Mon, 2015-10-05 at 09:56 -0700, Junio C Hamano wrote:
Show 33 quoted lines
> Dennis Kaarsemaker <dennis@kaarsemaker.net> writes:
> 
> > On Mon, Sep 28, 2015 at 06:02:18PM -0400, David Turner wrote:
> >> Add tests for the database backend.
> >> 
> >> Signed-off-by: David Turner <dturner@twopensource.com>
> >> ---
> >>  t/t1460-refs-be-db.sh        | 1103 ++++++++++++++++++++++++++++++++++++++++++
> >>  t/t1470-refs-be-db-reflog.sh |  353 ++++++++++++++
> >>  2 files changed, 1456 insertions(+)
> >>  create mode 100755 t/t1460-refs-be-db.sh
> >>  create mode 100755 t/t1470-refs-be-db-reflog.sh
> >
> > These break 'make test' on builds without the db backend. Maybe squash
> > in something like the following:
> >
> > diff --git a/t/t1460-refs-be-db.sh b/t/t1460-refs-be-db.sh
> > index f13b0f0..c8222ed 100755
> > --- a/t/t1460-refs-be-db.sh
> > +++ b/t/t1460-refs-be-db.sh
> > @@ -9,6 +9,11 @@ test_description='Test lmdb refs backend'
> >  TEST_NO_CREATE_REPO=1
> >  . ./test-lib.sh
> >  
> > +if ! test -e ../../test-refs-be-lmdb; then
> > +	skip_all="Skipping lmdb refs backend tests, lmdb backend not built"
> > +	test_done
> > +fi
> 
> The idea is sound, but $TRASH_DIRECTORY (i.e. $(cwd) there) is not
> necessarily two subdirectories down from the build repository root
> (cf. --root=<there> parameter to the test scripts), so you need to
> account for that.
will fix these, thanks Dennis and Junio.

← back to recent threads