git@vger.kernel.org mailing list mirror (one of many)
 help / color / mirror / code / Atom feed
Search results ordered by [date|relevance]  view[summary|nested|Atom feed]
thread overview below | download mbox.gz: |
* Re: [RFC PATCH v2 0/2] configuration-based hook management
  @ 2020-04-14 20:27  5%       ` Jeff King
  0 siblings, 0 replies; 200+ results
From: Jeff King @ 2020-04-14 20:27 UTC (permalink / raw)
  To: Emily Shaffer; +Cc: phillip.wood, git, Junio C Hamano, James Ramsay

On Tue, Apr 14, 2020 at 12:24:18PM -0700, Emily Shaffer wrote:

> > Without the redirection one could have
> >   hook.pre-commit.linter.command = my-command
> >   hook.pre-commit.check-whitespace.command = 'git diff --check --cached'
> [...]
> We'd need to fudge one of these fields to include the extra section, I
> think. Unfortunate, because I find your example very tidy, but in
> practice maybe not very neat. The closest thing I can find to a nice way
> of writing it might be:
> 
>   [hook.pre-commit "linter"]
>     command = my-command
>     before = check-whitespace
>   [hook.pre-commit "check-whitespace"]
>     command = 'git diff --check --cached'

Syntactically the whole section between the outer dots is the
subsection. So it's:

  [hook "pre-commit.check-whitespace"]
  command = ...

And I don't think we want to change the config syntax at this point.
Even in the neater dotted notation, we must keep that whole thing as a
subsection, because existing subsections may contain dots, too.

> But this is kind of a lie; the sections aren't "hook", "pre-commit", and
> "linter" as you'd expect. Whether it's OK to lie like this, though, I
> don't know - I suspect it might make it awkward for others trying to
> parse the config. (my Vim syntax highlighter had kind of a hard time.)

I think we should avoid it if possible. There are some subtleties there,
like the fact that subsections are case-sensitive, but sections and keys
are not.

-Peff

^ permalink raw reply	[relevance 5%]

* Re: [PATCH v12 3/5] bugreport: gather git version and build info
  2020-04-07 20:05  4%       ` Junio C Hamano
@ 2020-04-07 20:34  0%         ` Emily Shaffer
  0 siblings, 0 replies; 200+ results
From: Emily Shaffer @ 2020-04-07 20:34 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

On Tue, Apr 07, 2020 at 01:05:02PM -0700, Junio C Hamano wrote:
> Emily Shaffer <emilyshaffer@google.com> writes:
> 
> > To be clear, do you want me to include the output of get_compiler_info()
> > in "git version --build-options" when I do that change, too?
> 
> In the endgame, there are two kinds of information we'd want to
> gather and report, I would think.
> 
> Ones that can be different per-binary are things like:
> 
>  - what version of the source code the binary was compiled from
>  - with what compiler options
>  - using which compiler and
>  - linking with what libraries,
>  - where in the filesystem is the binary located
> 
> The others are various properties of the system the user is using,
> and having trouble using, Git on:
> 
>  - how many CPUs do we have,
>  - how much free memory,
>  - is the repository's filesystem case sensitive,
>  - what version of 'wish' is being used.
> 
> We'd want the former to be reported for each binary that matters, so
> "git version --build-options" would want to say it, "git remote-curl
> --build-options" would want to say it, and being different binaries,
> they may say different things.
> 
> There is not much point in duplicating the latter that are not
> binary specific, so it probably makes sense to gather them inside,
> and report them from, "git bugreport" itself.

Sure. Sounds great. Thanks for laying it out, this is very clear.

 - Emily

^ permalink raw reply	[relevance 0%]

* Re: [PATCH v12 3/5] bugreport: gather git version and build info
  @ 2020-04-07 20:05  4%       ` Junio C Hamano
  2020-04-07 20:34  0%         ` Emily Shaffer
  0 siblings, 1 reply; 200+ results
From: Junio C Hamano @ 2020-04-07 20:05 UTC (permalink / raw)
  To: Emily Shaffer; +Cc: git

Emily Shaffer <emilyshaffer@google.com> writes:

> To be clear, do you want me to include the output of get_compiler_info()
> in "git version --build-options" when I do that change, too?

In the endgame, there are two kinds of information we'd want to
gather and report, I would think.

Ones that can be different per-binary are things like:

 - what version of the source code the binary was compiled from
 - with what compiler options
 - using which compiler and
 - linking with what libraries,
 - where in the filesystem is the binary located

The others are various properties of the system the user is using,
and having trouble using, Git on:

 - how many CPUs do we have,
 - how much free memory,
 - is the repository's filesystem case sensitive,
 - what version of 'wish' is being used.

We'd want the former to be reported for each binary that matters, so
"git version --build-options" would want to say it, "git remote-curl
--build-options" would want to say it, and being different binaries,
they may say different things.

There is not much point in duplicating the latter that are not
binary specific, so it probably makes sense to gather them inside,
and report them from, "git bugreport" itself.

Thanks.

^ permalink raw reply	[relevance 4%]

* Re: Re*: [RFC PATCH 0/2] upload-pack.c: limit allowed filter choices
  @ 2020-03-19 17:03  5%       ` Jeff King
  0 siblings, 0 replies; 200+ results
From: Jeff King @ 2020-03-19 17:03 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Taylor Blau, git, christian.couder, james

On Wed, Mar 18, 2020 at 11:26:00AM -0700, Junio C Hamano wrote:

> > One thing that's a little ugly here is the embedded dot in the
> > subsection (i.e., "filter.<filter>"). It makes it look like a four-level
> > key, but really there is no such thing in Git.  But everything else we
> > tried was even uglier.
> 
> I think this gives us the best arrangement by upfront forcing all
> the configuration handers for "<subcommand>.*.<token>" namespace,
> current and future, to use "<group-prefix>" before the unbounded set
> of user-specifiable values that affects the <subcommand> (which is
> "uploadpack").
> 
> So far, the configuration variables that needs to be grouped by
> unbounded set of user-specifiable values we supported happened to
> have only one sensible such set for each <subcommand>, so we could
> get away without such <group-prefix> and it was perfectly OK to
> have, say "guitool.<name>.cmd".

Yeah. We have often just split those out into a separate hierarchy from
<subcommand> E.g., tar.<format>.command, which is really feeding the
git-archive command. We could do that here, too, but I wasn't sure of a
good name (this really is upload-pack specific, though I guess in theory
other commands could grow a need to look at or restrict "remote object
filters").

> Syntactically, the convention to always end such <group-prefix> with
> a dot "." may look unusual, or once readers' eyes get used to them,
> may look natural.  One tiny sad thing about it is that it cannot be
> mechanically enforced, but that is minor.

The biggest downside to implying a 4-level key is that the
case-sensitivity rules may be different. I.e., you can say:

  UploadPack.filter.blob:none.Allow

but not:

  UploadPack.Filter.blob:none.Allow

Since "filter" is part of the subsection, it's case sensitive. We could
match it case-insensitively in upload_pack_config(), but it would crop
up in other laces (e.g., "git config --unset" would still care).

> > We could do "uploadpackfilter.allow" and "uploadpackfilter.<filter>.allow",
> > but that's both ugly _and_ separates these options from the rest of
> > uploadpack.*.
> 
> There is an existing instance of a configuration that affects
> <subcommand> that uses a different word after <subcommand>, which is
> credentialCache.ignoreSIGHUP, and I tend to agree that it is ugly.

I don't think that's what's going on here. It affects only the
credential-cache subcommand, but we avoid hyphens in our key names.
So it really is the subcommand; it's just that the name is a superset of
another command name. :)

> By the way, I noticed the following while I was studying the current
> practice, so before I forget...
> 
> -- >8 --
> Subject: [PATCH] separate tar.* config to its own source file
> 
> Even though there is only one configuration variable in the
> namespace, it is not quite right to have tar.umask described
> among the variables for tag.* namespace.

Yeah, this is definitely an improvement. But I was surprised that
tar.<format>.* wasn't covered here. It is documented in git-archive.
Probably worth moving or duplicating it in git-config.

-Peff

^ permalink raw reply	[relevance 5%]

* [PATCH v7 0/6] Reftable support git-core
    2020-02-18  8:43  1%           ` [PATCH v6 4/5] Add reftable library Han-Wen Nienhuys via GitGitGadget
@ 2020-02-26  8:49  1%           ` Han-Wen Nienhuys via GitGitGadget
  1 sibling, 0 replies; 200+ results
From: Han-Wen Nienhuys via GitGitGadget @ 2020-02-26  8:49 UTC (permalink / raw)
  To: git; +Cc: Han-Wen Nienhuys

This adds the reftable library, and hooks it up as a ref backend.

Feedback wanted:

 * spots marked with XXX in the Git source code.
   
   
 * what is a good test strategy? Could we have a CI flavor where we flip the
   default to reftable, and see how it fares?
   
   

v7

 * support SHA256 as version 2 of the format.

v8

 * propagate errors to git.
 * discard empty tables in the stack. 
 * one very basic test (t0031.sh)

v9

 * Added spec as technical doc.
 * Use 4-byte hash ID as API. Storage format for SHA256 is still pending
   discussion.

Han-Wen Nienhuys (5):
  refs.h: clarify reflog iteration order
  create .git/refs in files-backend.c
  refs: document how ref_iterator_advance_fn should handle symrefs
  Add reftable library
  Reftable support for git-core

Jonathan Nieder (1):
  reftable: file format documentation

 Documentation/Makefile                        |    1 +
 Documentation/technical/reftable.txt          | 1067 ++++++++++++++++
 .../technical/repository-version.txt          |    7 +
 Makefile                                      |   24 +-
 builtin/clone.c                               |    4 +-
 builtin/init-db.c                             |   57 +-
 cache.h                                       |    4 +-
 refs.c                                        |   20 +-
 refs.h                                        |    8 +-
 refs/files-backend.c                          |    6 +
 refs/refs-internal.h                          |    6 +
 refs/reftable-backend.c                       | 1015 +++++++++++++++
 reftable/LICENSE                              |   31 +
 reftable/README.md                            |   11 +
 reftable/VERSION                              |    5 +
 reftable/basics.c                             |  160 +++
 reftable/basics.h                             |   30 +
 reftable/block.c                              |  413 ++++++
 reftable/block.h                              |   71 ++
 reftable/blocksource.h                        |   20 +
 reftable/bytes.c                              |    0
 reftable/config.h                             |    1 +
 reftable/constants.h                          |   25 +
 reftable/dump.c                               |   97 ++
 reftable/file.c                               |   97 ++
 reftable/iter.c                               |  229 ++++
 reftable/iter.h                               |   56 +
 reftable/merged.c                             |  290 +++++
 reftable/merged.h                             |   34 +
 reftable/pq.c                                 |  114 ++
 reftable/pq.h                                 |   34 +
 reftable/reader.c                             |  720 +++++++++++
 reftable/reader.h                             |   52 +
 reftable/record.c                             | 1119 +++++++++++++++++
 reftable/record.h                             |   79 ++
 reftable/reftable.h                           |  409 ++++++
 reftable/slice.c                              |  199 +++
 reftable/slice.h                              |   39 +
 reftable/stack.c                              | 1007 +++++++++++++++
 reftable/stack.h                              |   40 +
 reftable/system.h                             |   53 +
 reftable/tree.c                               |   66 +
 reftable/tree.h                               |   24 +
 reftable/update.sh                            |   13 +
 reftable/writer.c                             |  637 ++++++++++
 reftable/writer.h                             |   45 +
 reftable/zlib-compat.c                        |   92 ++
 repository.c                                  |    2 +
 repository.h                                  |    3 +
 setup.c                                       |   12 +-
 t/t0031-reftable.sh                           |   31 +
 51 files changed, 8547 insertions(+), 32 deletions(-)
 create mode 100644 Documentation/technical/reftable.txt
 create mode 100644 refs/reftable-backend.c
 create mode 100644 reftable/LICENSE
 create mode 100644 reftable/README.md
 create mode 100644 reftable/VERSION
 create mode 100644 reftable/basics.c
 create mode 100644 reftable/basics.h
 create mode 100644 reftable/block.c
 create mode 100644 reftable/block.h
 create mode 100644 reftable/blocksource.h
 create mode 100644 reftable/bytes.c
 create mode 100644 reftable/config.h
 create mode 100644 reftable/constants.h
 create mode 100644 reftable/dump.c
 create mode 100644 reftable/file.c
 create mode 100644 reftable/iter.c
 create mode 100644 reftable/iter.h
 create mode 100644 reftable/merged.c
 create mode 100644 reftable/merged.h
 create mode 100644 reftable/pq.c
 create mode 100644 reftable/pq.h
 create mode 100644 reftable/reader.c
 create mode 100644 reftable/reader.h
 create mode 100644 reftable/record.c
 create mode 100644 reftable/record.h
 create mode 100644 reftable/reftable.h
 create mode 100644 reftable/slice.c
 create mode 100644 reftable/slice.h
 create mode 100644 reftable/stack.c
 create mode 100644 reftable/stack.h
 create mode 100644 reftable/system.h
 create mode 100644 reftable/tree.c
 create mode 100644 reftable/tree.h
 create mode 100644 reftable/update.sh
 create mode 100644 reftable/writer.c
 create mode 100644 reftable/writer.h
 create mode 100644 reftable/zlib-compat.c
 create mode 100755 t/t0031-reftable.sh


base-commit: 51ebf55b9309824346a6589c9f3b130c6f371b8f
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-539%2Fhanwen%2Freftable-v7
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-539/hanwen/reftable-v7
Pull-Request: https://github.com/gitgitgadget/git/pull/539

Range-diff vs v6:

 1:  e4b0773becd = 1:  b1e44bc431e refs.h: clarify reflog iteration order
 2:  c25b6c601dc = 2:  b68488a095e create .git/refs in files-backend.c
 3:  e132e0f4e00 ! 3:  da538ef7421 refs: document how ref_iterator_advance_fn should handle symrefs
     @@ -13,8 +13,8 @@
       
      +/*
      + * backend-specific implementation of ref_iterator_advance.
     -+ * For symrefs, the function should set REF_ISSYMREF, and it should also dereference
     -+ * the symref to provide the OID referent.
     ++ * For symrefs, the function should set REF_ISSYMREF, and it should also
     ++ * dereference the symref to provide the OID referent.
      + */
       typedef int ref_iterator_advance_fn(struct ref_iterator *ref_iterator);
       
 -:  ----------- > 4:  aae26814983 reftable: file format documentation
 4:  fe29a9db399 ! 5:  30ed43a4fdb Add reftable library
     @@ -2,19 +2,11 @@
      
          Add reftable library
      
     -    Reftable is a new format for storing the ref database. It provides the
     -    following benefits:
     -
     -     * Simple and fast atomic ref transactions, including multiple refs and reflogs.
     -     * Compact storage of ref data.
     -     * Fast look ups of ref data.
     -     * Case-sensitive ref names on Windows/OSX, regardless of file system
     -     * Eliminates file/directory conflicts in ref names
     +    Reftable is a format for storing the ref database. Its rationale and
     +    specification is in the preceding commit.
      
          Further context and motivation can be found in background reading:
      
     -    * Spec: https://github.com/eclipse/jgit/blob/master/Documentation/technical/reftable.md
     -
          * Original discussion on JGit-dev:  https://www.eclipse.org/lists/jgit-dev/msg03389.html
      
          * First design discussion on git@vger: https://public-inbox.org/git/CAJo=hJtTp2eA3z9wW9cHo-nA7kK40vVThqh6inXpbCcqfdMP9g@mail.gmail.com/
     @@ -80,15 +72,7 @@
      +
      +To update the library, do:
      +
     -+   ((cd reftable-repo && git fetch origin && git checkout origin/master ) ||
     -+    git clone https://github.com/google/reftable reftable-repo) && \
     -+   cp reftable-repo/c/*.[ch] reftable/ && \
     -+   cp reftable-repo/LICENSE reftable/ &&
     -+   git --git-dir reftable-repo/.git show --no-patch origin/master \
     -+    > reftable/VERSION && \
     -+   sed -i~ 's|if REFTABLE_IN_GITCORE|if 1 /* REFTABLE_IN_GITCORE */|' reftable/system.h
     -+   rm reftable/*_test.c reftable/test_framework.*
     -+   git add reftable/*.[ch]
     ++   sh reftable/update.sh
      +
      +Bugfixes should be accompanied by a test and applied to upstream project at
      +https://github.com/google/reftable.
     @@ -98,17 +82,11 @@
       --- /dev/null
       +++ b/reftable/VERSION
      @@
     -+commit 42aa04f301a682197f0698867b9771b78ce4fdaf
     ++commit ccce3b3eb763e79b23a3b4677d65ecceb4155ba3
      +Author: Han-Wen Nienhuys <hanwen@google.com>
     -+Date:   Mon Feb 17 16:05:22 2020 +0100
     -+
     -+    C: simplify system and config header handling
     -+    
     -+    reftable.h only depends on <stdint.h>
     -+    
     -+    system.h is only included from .c files. The git-core specific code is
     -+    guarded by a #if REFTABLE_IN_GITCORE, which we'll edit with a sed
     -+    script on the git-core side.
     ++Date:   Tue Feb 25 20:42:29 2020 +0100
     ++
     ++    C: rephrase the hash ID API in terms of a uint32_t
      
       diff --git a/reftable/basics.c b/reftable/basics.c
       new file mode 100644
     @@ -127,61 +105,23 @@
      +
      +#include "system.h"
      +
     -+void put_u24(byte *out, uint32_t i)
     ++void put_be24(byte *out, uint32_t i)
      +{
      +	out[0] = (byte)((i >> 16) & 0xff);
      +	out[1] = (byte)((i >> 8) & 0xff);
      +	out[2] = (byte)((i)&0xff);
      +}
      +
     -+uint32_t get_u24(byte *in)
     ++uint32_t get_be24(byte *in)
      +{
      +	return (uint32_t)(in[0]) << 16 | (uint32_t)(in[1]) << 8 |
      +	       (uint32_t)(in[2]);
      +}
      +
     -+void put_u32(byte *out, uint32_t i)
     ++void put_be16(uint8_t *out, uint16_t i)
      +{
     -+	out[0] = (byte)((i >> 24) & 0xff);
     -+	out[1] = (byte)((i >> 16) & 0xff);
     -+	out[2] = (byte)((i >> 8) & 0xff);
     -+	out[3] = (byte)((i)&0xff);
     -+}
     -+
     -+uint32_t get_u32(byte *in)
     -+{
     -+	return (uint32_t)(in[0]) << 24 | (uint32_t)(in[1]) << 16 |
     -+	       (uint32_t)(in[2]) << 8 | (uint32_t)(in[3]);
     -+}
     -+
     -+void put_u64(byte *out, uint64_t v)
     -+{
     -+	int i = 0;
     -+	for (i = sizeof(uint64_t); i--;) {
     -+		out[i] = (byte)(v & 0xff);
     -+		v >>= 8;
     -+	}
     -+}
     -+
     -+uint64_t get_u64(byte *out)
     -+{
     -+	uint64_t v = 0;
     -+	int i = 0;
     -+	for (i = 0; i < sizeof(uint64_t); i++) {
     -+		v = (v << 8) | (byte)(out[i] & 0xff);
     -+	}
     -+	return v;
     -+}
     -+
     -+void put_u16(byte *out, uint16_t i)
     -+{
     -+	out[0] = (byte)((i >> 8) & 0xff);
     -+	out[1] = (byte)((i)&0xff);
     -+}
     -+
     -+uint16_t get_u16(byte *in)
     -+{
     -+	return (uint32_t)(in[0]) << 8 | (uint32_t)(in[1]);
     ++	out[0] = (uint8_t)((i >> 8) & 0xff);
     ++	out[1] = (uint8_t)((i)&0xff);
      +}
      +
      +/*
     @@ -234,7 +174,9 @@
      +int names_length(char **names)
      +{
      +	int len = 0;
     -+	for (char **p = names; *p; p++) {
     ++	char **p = names;
     ++	while (*p) {
     ++		p++;
      +		len++;
      +	}
      +	return len;
     @@ -262,7 +204,7 @@
      +				names = realloc(names,
      +						names_cap * sizeof(char *));
      +			}
     -+			names[names_len++] = strdup(p);
     ++			names[names_len++] = xstrdup(p);
      +		}
      +		p = next + 1;
      +	}
     @@ -335,17 +277,10 @@
      +#define true 1
      +#define false 0
      +
     -+void put_u24(byte *out, uint32_t i);
     -+uint32_t get_u24(byte *in);
     -+
     -+uint64_t get_u64(byte *in);
     -+void put_u64(byte *out, uint64_t i);
     ++void put_be24(byte *out, uint32_t i);
     ++uint32_t get_be24(byte *in);
     ++void put_be16(uint8_t *out, uint16_t i);
      +
     -+void put_u32(byte *out, uint32_t i);
     -+uint32_t get_u32(byte *in);
     -+
     -+void put_u16(byte *out, uint16_t i);
     -+uint16_t get_u16(byte *in);
      +int binsearch(int sz, int (*f)(int k, void *args), void *args);
      +
      +void free_names(char **a);
     @@ -403,7 +338,7 @@
      +   success */
      +int block_writer_add(struct block_writer *w, struct record rec)
      +{
     -+	struct slice empty = {};
     ++	struct slice empty = { 0 };
      +	struct slice last = w->entries % w->restart_interval == 0 ? empty :
      +								    w->last_key;
      +	struct slice out = {
     @@ -414,7 +349,7 @@
      +	struct slice start = out;
      +
      +	bool restart = false;
     -+	struct slice key = {};
     ++	struct slice key = { 0 };
      +	int n = 0;
      +
      +	record_key(rec, &key);
     @@ -480,17 +415,17 @@
      +{
      +	int i = 0;
      +	for (i = 0; i < w->restart_len; i++) {
     -+		put_u24(w->buf + w->next, w->restarts[i]);
     ++		put_be24(w->buf + w->next, w->restarts[i]);
      +		w->next += 3;
      +	}
      +
     -+	put_u16(w->buf + w->next, w->restart_len);
     ++	put_be16(w->buf + w->next, w->restart_len);
      +	w->next += 2;
     -+	put_u24(w->buf + 1 + w->header_off, w->next);
     ++	put_be24(w->buf + 1 + w->header_off, w->next);
      +
      +	if (block_writer_type(w) == BLOCK_TYPE_LOG) {
      +		int block_header_skip = 4 + w->header_off;
     -+		struct slice compressed = {};
     ++		struct slice compressed = { 0 };
      +		int zresult = 0;
      +		uLongf src_len = w->next - block_header_skip;
      +		slice_resize(&compressed, src_len);
     @@ -531,14 +466,14 @@
      +{
      +	uint32_t full_block_size = table_block_size;
      +	byte typ = block->data[header_off];
     -+	uint32_t sz = get_u24(block->data + header_off + 1);
     ++	uint32_t sz = get_be24(block->data + header_off + 1);
      +
      +	if (!is_block_type(typ)) {
      +		return FORMAT_ERROR;
      +	}
      +
      +	if (typ == BLOCK_TYPE_LOG) {
     -+		struct slice uncompressed = {};
     ++		struct slice uncompressed = { 0 };
      +		int block_header_skip = 4 + header_off;
      +		uLongf dst_len = sz - block_header_skip;
      +		uLongf src_len = block->len - block_header_skip;
     @@ -570,7 +505,7 @@
      +	}
      +
      +	{
     -+		uint16_t restart_count = get_u16(block->data + sz - 2);
     ++		uint16_t restart_count = get_be16(block->data + sz - 2);
      +		uint32_t restart_start = sz - 2 - 3 * restart_count;
      +
      +		byte *restart_bytes = block->data + restart_start;
     @@ -593,7 +528,7 @@
      +
      +static uint32_t block_reader_restart_offset(struct block_reader *br, int i)
      +{
     -+	return get_u24(br->restart_bytes + 3 * i);
     ++	return get_be24(br->restart_bytes + 3 * i);
      +}
      +
      +void block_reader_start(struct block_reader *br, struct block_iter *it)
     @@ -604,9 +539,9 @@
      +}
      +
      +struct restart_find_args {
     ++	int error;
      +	struct slice key;
      +	struct block_reader *r;
     -+	int error;
      +};
      +
      +static int restart_key_less(int idx, void *args)
     @@ -620,8 +555,8 @@
      +
      +	/* the restart key is verbatim in the block, so this could avoid the
      +	   alloc for decoding the key */
     -+	struct slice rkey = {};
     -+	struct slice last_key = {};
     ++	struct slice rkey = { 0 };
     ++	struct slice last_key = { 0 };
      +	byte unused_extra;
      +	int n = decode_key(&rkey, &unused_extra, last_key, in);
      +	if (n < 0) {
     @@ -656,7 +591,7 @@
      +			.len = it->br->block_len - it->next_off,
      +		};
      +		struct slice start = in;
     -+		struct slice key = {};
     ++		struct slice key = { 0 };
      +		byte extra;
      +		int n = decode_key(&key, &extra, it->last_key, in);
      +		if (n < 0) {
     @@ -681,7 +616,7 @@
      +
      +int block_reader_first_key(struct block_reader *br, struct slice *key)
      +{
     -+	struct slice empty = {};
     ++	struct slice empty = { 0 };
      +	int off = br->header_off + 4;
      +	struct slice in = {
      +		.buf = br->block.data + off,
     @@ -729,10 +664,10 @@
      +
      +	{
      +		struct record rec = new_record(block_reader_type(br));
     -+		struct slice key = {};
     ++		struct slice key = { 0 };
      +		int result = 0;
      +		int err = 0;
     -+		struct block_iter next = {};
     ++		struct block_iter next = { 0 };
      +		while (true) {
      +			block_iter_copy_from(&next, it);
      +
     @@ -830,9 +765,9 @@
      +};
      +
      +struct block_iter {
     ++	uint32_t next_off;
      +	struct block_reader *br;
      +	struct slice last_key;
     -+	uint32_t next_off;
      +};
      +
      +int block_reader_init(struct block_reader *br, struct block *bl,
     @@ -937,7 +872,7 @@
      +
      +static int dump_table(const char *tablename)
      +{
     -+	struct block_source src = {};
     ++	struct block_source src = { 0 };
      +	int err = block_source_from_file(&src, tablename);
      +	if (err < 0) {
      +		return err;
     @@ -950,13 +885,13 @@
      +	}
      +
      +	{
     -+		struct iterator it = {};
     ++		struct iterator it = { 0 };
      +		err = reader_seek_ref(r, &it, "");
      +		if (err < 0) {
      +			return err;
      +		}
      +
     -+		struct ref_record ref = {};
     ++		struct ref_record ref = { 0 };
      +		while (1) {
      +			err = iterator_next_ref(it, &ref);
      +			if (err > 0) {
     @@ -972,12 +907,12 @@
      +	}
      +
      +	{
     -+		struct iterator it = {};
     ++		struct iterator it = { 0 };
      +		err = reader_seek_log(r, &it, "");
      +		if (err < 0) {
      +			return err;
      +		}
     -+		struct log_record log = {};
     ++		struct log_record log = { 0 };
      +		while (1) {
      +			err = iterator_next_log(it, &log);
      +			if (err > 0) {
     @@ -1001,7 +936,7 @@
      +	while ((opt = getopt(argc, argv, "t:")) != -1) {
      +		switch (opt) {
      +		case 't':
     -+			table = strdup(optarg);
     ++			table = xstrdup(optarg);
      +			break;
      +		case '?':
      +			printf("usage: %s [-table tablefile]\n", argv[0]);
     @@ -1091,7 +1026,7 @@
      +
      +int block_source_from_file(struct block_source *bs, const char *name)
      +{
     -+	struct stat st = {};
     ++	struct stat st = { 0 };
      +	int err = 0;
      +	int fd = open(name, O_RDONLY);
      +	if (fd < 0) {
     @@ -1188,14 +1123,14 @@
      +
      +int iterator_next_ref(struct iterator it, struct ref_record *ref)
      +{
     -+	struct record rec = {};
     ++	struct record rec = { 0 };
      +	record_from_ref(&rec, ref);
      +	return iterator_next(it, rec);
      +}
      +
      +int iterator_next_log(struct iterator it, struct log_record *log)
      +{
     -+	struct record rec = {};
     ++	struct record rec = { 0 };
      +	record_from_log(&rec, log);
      +	return iterator_next(it, rec);
      +}
     @@ -1221,7 +1156,7 @@
      +		}
      +
      +		if (fri->double_check) {
     -+			struct iterator it = {};
     ++			struct iterator it = { 0 };
      +
      +			int err = reader_seek_ref(fri->r, &it, ref->ref_name);
      +			if (err == 0) {
     @@ -1389,9 +1324,9 @@
      +bool iterator_is_null(struct iterator it);
      +
      +struct filtering_ref_iterator {
     ++	bool double_check;
      +	struct reader *r;
      +	struct slice oid;
     -+	bool double_check;
      +	struct iterator it;
      +};
      +
     @@ -1511,7 +1446,7 @@
      +
      +static int merged_iter_next(struct merged_iter *mi, struct record rec)
      +{
     -+	struct slice entry_key = {};
     ++	struct slice entry_key = { 0 };
      +	struct pq_entry entry = merged_iter_pqueue_remove(&mi->pq);
      +	int err = merged_iter_advance_subiter(mi, entry.index);
      +	if (err < 0) {
     @@ -1521,7 +1456,7 @@
      +	record_key(entry.rec, &entry_key);
      +	while (!merged_iter_pqueue_is_empty(mi->pq)) {
      +		struct pq_entry top = merged_iter_pqueue_top(mi->pq);
     -+		struct slice k = {};
     ++		struct slice k = { 0 };
      +		int err = 0, cmp = 0;
      +
      +		record_key(top.rec, &k);
     @@ -1542,7 +1477,7 @@
      +		free(record_yield(&top.rec));
      +	}
      +
     -+	record_copy_from(rec, entry.rec, mi->hash_size);
     ++	record_copy_from(rec, entry.rec, hash_size(mi->hash_id));
      +	record_clear(entry.rec);
      +	free(record_yield(&entry.rec));
      +	free(slice_yield(&entry_key));
     @@ -1572,14 +1507,14 @@
      +}
      +
      +int new_merged_table(struct merged_table **dest, struct reader **stack, int n,
     -+		     int hash_size)
     ++		     uint32_t hash_id)
      +{
      +	uint64_t last_max = 0;
      +	uint64_t first_min = 0;
      +	int i = 0;
      +	for (i = 0; i < n; i++) {
      +		struct reader *r = stack[i];
     -+		if (reader_hash_size(r) != hash_size) {
     ++		if (r->hash_id != hash_id) {
      +			return FORMAT_ERROR;
      +		}
      +		if (i > 0 && last_max >= reader_min_update_index(r)) {
     @@ -1598,7 +1533,7 @@
      +			.stack_len = n,
      +			.min = first_min,
      +			.max = last_max,
     -+			.hash_size = hash_size,
     ++			.hash_id = hash_id,
      +		};
      +
      +		*dest = calloc(sizeof(struct merged_table), 1);
     @@ -1650,7 +1585,7 @@
      +	struct merged_iter merged = {
      +		.stack = iters,
      +		.typ = record_type(rec),
     -+		.hash_size = mt->hash_size,
     ++		.hash_id = mt->hash_id,
      +	};
      +	int n = 0;
      +	int err = 0;
     @@ -1693,7 +1628,7 @@
      +	struct ref_record ref = {
      +		.ref_name = (char *)name,
      +	};
     -+	struct record rec = {};
     ++	struct record rec = { 0 };
      +	record_from_ref(&rec, &ref);
      +	return merged_table_seek_record(mt, it, rec);
      +}
     @@ -1705,7 +1640,7 @@
      +		.ref_name = (char *)name,
      +		.update_index = update_index,
      +	};
     -+	struct record rec = {};
     ++	struct record rec = { 0 };
      +	record_from_log(&rec, &log);
      +	return merged_table_seek_record(mt, it, rec);
      +}
     @@ -1739,7 +1674,7 @@
      +struct merged_table {
      +	struct reader **stack;
      +	int stack_len;
     -+	int hash_size;
     ++	uint32_t hash_id;
      +
      +	uint64_t min;
      +	uint64_t max;
     @@ -1747,7 +1682,7 @@
      +
      +struct merged_iter {
      +	struct iterator *stack;
     -+	int hash_size;
     ++	uint32_t hash_id;
      +	int stack_len;
      +	byte typ;
      +	struct merged_iter_pqueue pq;
     @@ -1776,8 +1711,8 @@
      +
      +int pq_less(struct pq_entry a, struct pq_entry b)
      +{
     -+	struct slice ak = {};
     -+	struct slice bk = {};
     ++	struct slice ak = { 0 };
     ++	struct slice bk = { 0 };
      +	int cmp = 0;
      +	record_key(a.rec, &ak);
      +	record_key(b.rec, &bk);
     @@ -1896,8 +1831,8 @@
      +#include "record.h"
      +
      +struct pq_entry {
     -+	struct record rec;
      +	int index;
     ++	struct record rec;
      +};
      +
      +int pq_less(struct pq_entry a, struct pq_entry b);
     @@ -2005,9 +1940,9 @@
      +	block_source_return_block(r->source, p);
      +}
      +
     -+int reader_hash_size(struct reader *r)
     ++uint32_t reader_hash_id(struct reader *r)
      +{
     -+	return r->hash_size;
     ++	return r->hash_id;
      +}
      +
      +const char *reader_name(struct reader *r)
     @@ -2030,11 +1965,12 @@
      +		goto exit;
      +	}
      +
     -+	r->hash_size = SHA1_SIZE;
     ++	r->hash_id = SHA1_ID;
      +	{
      +		byte version = *f++;
      +		if (version == 2) {
     -+			r->hash_size = SHA256_SIZE;
     ++			/* DO NOT SUBMIT.  Not yet in the standard. */
     ++			r->hash_id = SHA256_ID;
      +			version = 1;
      +		}
      +		if (version != 1) {
     @@ -2043,33 +1979,33 @@
      +		}
      +	}
      +
     -+	r->block_size = get_u24(f);
     ++	r->block_size = get_be24(f);
      +
      +	f += 3;
     -+	r->min_update_index = get_u64(f);
     ++	r->min_update_index = get_be64(f);
      +	f += 8;
     -+	r->max_update_index = get_u64(f);
     ++	r->max_update_index = get_be64(f);
      +	f += 8;
      +
     -+	r->ref_offsets.index_offset = get_u64(f);
     ++	r->ref_offsets.index_offset = get_be64(f);
      +	f += 8;
      +
     -+	r->obj_offsets.offset = get_u64(f);
     ++	r->obj_offsets.offset = get_be64(f);
      +	f += 8;
      +
      +	r->object_id_len = r->obj_offsets.offset & ((1 << 5) - 1);
      +	r->obj_offsets.offset >>= 5;
      +
     -+	r->obj_offsets.index_offset = get_u64(f);
     ++	r->obj_offsets.index_offset = get_be64(f);
      +	f += 8;
     -+	r->log_offsets.offset = get_u64(f);
     ++	r->log_offsets.offset = get_be64(f);
      +	f += 8;
     -+	r->log_offsets.index_offset = get_u64(f);
     ++	r->log_offsets.index_offset = get_be64(f);
      +	f += 8;
      +
      +	{
      +		uint32_t computed_crc = crc32(0, footer, f - footer);
     -+		uint32_t file_crc = get_u32(f);
     ++		uint32_t file_crc = get_be32(f);
      +		f += 4;
      +		if (computed_crc != file_crc) {
      +			err = FORMAT_ERROR;
     @@ -2092,15 +2028,15 @@
      +
      +int init_reader(struct reader *r, struct block_source source, const char *name)
      +{
     -+	struct block footer = {};
     -+	struct block header = {};
     ++	struct block footer = { 0 };
     ++	struct block header = { 0 };
      +	int err = 0;
      +
      +	memset(r, 0, sizeof(struct reader));
      +	r->size = block_source_size(source) - FOOTER_SIZE;
      +	r->source = source;
     -+	r->name = strdup(name);
     -+	r->hash_size = 0;
     ++	r->name = xstrdup(name);
     ++	r->hash_id = 0;
      +
      +	err = block_source_read_block(source, &footer, r->size, FOOTER_SIZE);
      +	if (err != FOOTER_SIZE) {
     @@ -2173,7 +2109,7 @@
      +
      +	*typ = data[0];
      +	if (is_block_type(*typ)) {
     -+		result = get_u24(data + 1);
     ++		result = get_be24(data + 1);
      +	}
      +	return result;
      +}
     @@ -2183,7 +2119,7 @@
      +{
      +	int32_t guess_block_size = r->block_size ? r->block_size :
      +						   DEFAULT_BLOCK_SIZE;
     -+	struct block block = {};
     ++	struct block block = { 0 };
      +	byte block_typ = 0;
      +	int err = 0;
      +	uint32_t header_off = next_off ? 0 : HEADER_SIZE;
     @@ -2217,14 +2153,14 @@
      +	}
      +
      +	return block_reader_init(br, &block, header_off, r->block_size,
     -+				 r->hash_size);
     ++				 hash_size(r->hash_id));
      +}
      +
      +static int table_iter_next_block(struct table_iter *dest,
      +				 struct table_iter *src)
      +{
      +	uint64_t next_block_off = src->block_off + src->bi.br->full_block_size;
     -+	struct block_reader br = {};
     ++	struct block_reader br = { 0 };
      +	int err = 0;
      +
      +	dest->r = src->r;
     @@ -2257,7 +2193,7 @@
      +	}
      +
      +	while (true) {
     -+		struct table_iter next = {};
     ++		struct table_iter next = { 0 };
      +		int err = 0;
      +		if (ti->finished) {
      +			return 1;
     @@ -2307,7 +2243,7 @@
      +static int reader_table_iter_at(struct reader *r, struct table_iter *ti,
      +				uint64_t off, byte typ)
      +{
     -+	struct block_reader br = {};
     ++	struct block_reader br = { 0 };
      +	struct block_reader *brp = NULL;
      +
      +	int err = reader_init_block_reader(r, &br, off, typ);
     @@ -2344,9 +2280,9 @@
      +			      struct record want)
      +{
      +	struct record rec = new_record(record_type(want));
     -+	struct slice want_key = {};
     -+	struct slice got_key = {};
     -+	struct table_iter next = {};
     ++	struct slice want_key = { 0 };
     ++	struct slice got_key = { 0 };
     ++	struct table_iter next = { 0 };
      +	int err = -1;
      +	record_key(want, &want_key);
      +
     @@ -2394,12 +2330,12 @@
      +static int reader_seek_indexed(struct reader *r, struct iterator *it,
      +			       struct record rec)
      +{
     -+	struct index_record want_index = {};
     -+	struct record want_index_rec = {};
     -+	struct index_record index_result = {};
     -+	struct record index_result_rec = {};
     -+	struct table_iter index_iter = {};
     -+	struct table_iter next = {};
     ++	struct index_record want_index = { 0 };
     ++	struct record want_index_rec = { 0 };
     ++	struct index_record index_result = { 0 };
     ++	struct record index_result_rec = { 0 };
     ++	struct table_iter index_iter = { 0 };
     ++	struct table_iter next = { 0 };
      +	int err = 0;
      +
      +	record_key(rec, &want_index.last_key);
     @@ -2461,7 +2397,7 @@
      +{
      +	struct reader_offsets *offs = reader_offsets_for(r, record_type(rec));
      +	uint64_t idx = offs->index_offset;
     -+	struct table_iter ti = {};
     ++	struct table_iter ti = { 0 };
      +	int err = 0;
      +	if (idx > 0) {
      +		return reader_seek_indexed(r, it, rec);
     @@ -2503,7 +2439,7 @@
      +	struct ref_record ref = {
      +		.ref_name = (char *)name,
      +	};
     -+	struct record rec = {};
     ++	struct record rec = { 0 };
      +	record_from_ref(&rec, &ref);
      +	return reader_seek(r, it, rec);
      +}
     @@ -2515,7 +2451,7 @@
      +		.ref_name = (char *)name,
      +		.update_index = update_index,
      +	};
     -+	struct record rec = {};
     ++	struct record rec = { 0 };
      +	record_from_log(&rec, &log);
      +	return reader_seek(r, it, rec);
      +}
     @@ -2557,10 +2493,10 @@
      +		.hash_prefix = oid,
      +		.hash_prefix_len = r->object_id_len,
      +	};
     -+	struct record want_rec = {};
     -+	struct iterator oit = {};
     -+	struct obj_record got = {};
     -+	struct record got_rec = {};
     ++	struct record want_rec = { 0 };
     ++	struct iterator oit = { 0 };
     ++	struct obj_record got = { 0 };
     ++	struct record got_rec = { 0 };
      +	int err = 0;
      +
      +	record_from_obj(&want_rec, &want);
     @@ -2585,7 +2521,8 @@
      +
      +	{
      +		struct indexed_table_ref_iter *itr = NULL;
     -+		err = new_indexed_table_ref_iter(&itr, r, oid, r->hash_size,
     ++		err = new_indexed_table_ref_iter(&itr, r, oid,
     ++						 hash_size(r->hash_id),
      +						 got.offsets, got.offset_len);
      +		if (err < 0) {
      +			record_clear(got_rec);
     @@ -2675,9 +2612,9 @@
      +};
      +
      +struct reader {
     -+	struct block_source source;
      +	char *name;
     -+	int hash_size;
     ++	struct block_source source;
     ++	uint32_t hash_id;
      +	uint64_t size;
      +	uint32_t block_size;
      +	uint64_t min_update_index;
     @@ -2755,7 +2692,7 @@
      +
      +int put_var_int(struct slice dest, uint64_t val)
      +{
     -+	byte buf[10] = {};
     ++	byte buf[10] = { 0 };
      +	int i = 9;
      +	buf[i] = (byte)(val & 0x7f);
      +	i--;
     @@ -2868,11 +2805,11 @@
      +	   fields. */
      +	ref_record_clear(ref);
      +	if (src->ref_name != NULL) {
     -+		ref->ref_name = strdup(src->ref_name);
     ++		ref->ref_name = xstrdup(src->ref_name);
      +	}
      +
      +	if (src->target != NULL) {
     -+		ref->target = strdup(src->target);
     ++		ref->target = xstrdup(src->target);
      +	}
      +
      +	if (src->target_value != NULL) {
     @@ -2910,7 +2847,7 @@
      +
      +void ref_record_print(struct ref_record *ref, int hash_size)
      +{
     -+	char hex[SHA256_SIZE + 1] = {};
     ++	char hex[SHA256_SIZE + 1] = { 0 };
      +
      +	printf("ref{%s(%" PRIu64 ") ", ref->ref_name, ref->update_index);
      +	if (ref->value != NULL) {
     @@ -3066,7 +3003,7 @@
      +		in.len -= hash_size;
      +		break;
      +	case 3: {
     -+		struct slice dest = {};
     ++		struct slice dest = { 0 };
      +		int n = decode_string(&dest, in);
      +		if (n < 0) {
      +			return -1;
     @@ -3302,7 +3239,7 @@
      +
      +void log_record_print(struct log_record *log, int hash_size)
      +{
     -+	char hex[SHA256_SIZE + 1] = {};
     ++	char hex[SHA256_SIZE + 1] = { 0 };
      +
      +	printf("log{%s(%" PRIu64 ") %s <%s> %" PRIu64 " %04d\n", log->ref_name,
      +	       log->update_index, log->name, log->email, log->time,
     @@ -3326,7 +3263,7 @@
      +	slice_resize(dest, len + 9);
      +	memcpy(dest->buf, rec->ref_name, len + 1);
      +	ts = (~ts) - rec->update_index;
     -+	put_u64(dest->buf + 1 + len, ts);
     ++	put_be64(dest->buf + 1 + len, ts);
      +}
      +
      +static void log_record_copy_from(void *rec, const void *src_rec, int hash_size)
     @@ -3335,10 +3272,10 @@
      +	const struct log_record *src = (const struct log_record *)src_rec;
      +
      +	*dst = *src;
     -+	dst->ref_name = strdup(dst->ref_name);
     -+	dst->email = strdup(dst->email);
     -+	dst->name = strdup(dst->name);
     -+	dst->message = strdup(dst->message);
     ++	dst->ref_name = xstrdup(dst->ref_name);
     ++	dst->email = xstrdup(dst->email);
     ++	dst->name = xstrdup(dst->name);
     ++	dst->message = xstrdup(dst->message);
      +	if (dst->new_hash != NULL) {
      +		dst->new_hash = malloc(hash_size);
      +		memcpy(dst->new_hash, src->new_hash, hash_size);
     @@ -3371,7 +3308,7 @@
      +	return 1;
      +}
      +
     -+static byte zero[SHA256_SIZE] = {};
     ++static byte zero[SHA256_SIZE] = { 0 };
      +
      +static int log_record_encode(const void *rec, struct slice s, int hash_size)
      +{
     @@ -3421,7 +3358,7 @@
      +		return -1;
      +	}
      +
     -+	put_u16(s.buf, r->tz_offset);
     ++	put_be16(s.buf, r->tz_offset);
      +	s.buf += 2;
      +	s.len -= 2;
      +
     @@ -3442,7 +3379,7 @@
      +	struct log_record *r = (struct log_record *)rec;
      +	uint64_t max = 0;
      +	uint64_t ts = 0;
     -+	struct slice dest = {};
     ++	struct slice dest = { 0 };
      +	int n;
      +
      +	if (key.len <= 9 || key.buf[key.len - 9] != 0) {
     @@ -3451,7 +3388,7 @@
      +
      +	r->ref_name = realloc(r->ref_name, key.len - 8);
      +	memcpy(r->ref_name, key.buf, key.len - 8);
     -+	ts = get_u64(key.buf + key.len - 8);
     ++	ts = get_be64(key.buf + key.len - 8);
      +
      +	r->update_index = (~max) - ts;
      +
     @@ -3503,7 +3440,7 @@
      +		goto error;
      +	}
      +
     -+	r->tz_offset = get_u16(in.buf);
     ++	r->tz_offset = get_be16(in.buf);
      +	in.buf += 2;
      +	in.len -= 2;
      +
     @@ -3810,6 +3747,18 @@
      +{
      +	/* XXX */
      +	return false;
     ++}
     ++
     ++int hash_size(uint32_t id)
     ++{
     ++	switch (id) {
     ++	case 0:
     ++	case SHA1_ID:
     ++		return SHA1_SIZE;
     ++	case SHA256_ID:
     ++		return SHA256_SIZE;
     ++	}
     ++	abort();
      +}
      
       diff --git a/reftable/record.h b/reftable/record.h
     @@ -3842,7 +3791,7 @@
      +	void (*clear)(void *rec);
      +};
      +
     -+/* record is a generic wrapper for differnt types of records. */
     ++/* record is a generic wrapper for different types of records. */
      +struct record {
      +	void *data;
      +	struct record_vtable *ops;
     @@ -3863,8 +3812,8 @@
      +	       struct slice in);
      +
      +struct index_record {
     -+	struct slice last_key;
      +	uint64_t offset;
     ++	struct slice last_key;
      +};
      +
      +struct obj_record {
     @@ -3965,9 +3914,10 @@
      +	/* how often to write complete keys in each block. */
      +	int restart_interval;
      +
     -+	/* width of the hash. Should be 20 for SHA1 or 32 for SHA256. Defaults
     -+	 * to SHA1 if unset */
     -+	int hash_size;
     ++	/* 4-byte identifier ("sha1", "s256") of the hash.
     ++	 * Defaults to SHA1 if unset
     ++	 */
     ++	uint32_t hash_id;
      +};
      +
      +/* ref_record holds a ref database entry target_value */
     @@ -4102,6 +4052,9 @@
      +/* Decompression error */
      +#define ZLIB_ERROR -7
      +
     ++/* Wrote a table without blocks. */
     ++#define EMPTY_TABLE_ERROR -8
     ++
      +const char *error_str(int err);
      +
      +/* new_writer creates a new writer */
     @@ -4162,10 +4115,10 @@
      +   struct reader *r = NULL;
      +   int err = new_reader(&r, src, "filename");
      +   if (err < 0) { ... }
     -+   struct iterator it = {};
     ++   struct iterator it  = {0};
      +   err = reader_seek_ref(r, &it, "refs/heads/master");
      +   if (err < 0) { ... }
     -+   struct ref_record ref = {};
     ++   struct ref_record ref  = {0};
      +   while (1) {
      +     err = iterator_next_ref(it, &ref);
      +     if (err > 0) {
     @@ -4181,8 +4134,8 @@
      + */
      +int reader_seek_ref(struct reader *r, struct iterator *it, const char *name);
      +
     -+/* returns the hash size used in this table. */
     -+int reader_hash_size(struct reader *r);
     ++/* returns the hash ID used in this table. */
     ++uint32_t reader_hash_id(struct reader *r);
      +
      +/* seek to logs for the given name, older than update_index. */
      +int reader_seek_log_at(struct reader *r, struct iterator *it, const char *name,
     @@ -4211,10 +4164,10 @@
      +   array.
      +*/
      +int new_merged_table(struct merged_table **dest, struct reader **stack, int n,
     -+		     int hash_size);
     ++		     uint32_t hash_id);
      +
     -+/* returns the hash size used in this merged table. */
     -+int merged_hash_size(struct merged_table *mt);
     ++/* returns the hash id used in this merged table. */
     ++uint32_t merged_hash_id(struct merged_table *mt);
      +
      +/* returns an iterator positioned just before 'name' */
      +int merged_table_seek_ref(struct merged_table *mt, struct iterator *it,
     @@ -4403,7 +4356,7 @@
      +/* return a newly malloced string for this slice */
      +char *slice_to_string(struct slice in)
      +{
     -+	struct slice s = {};
     ++	struct slice s = { 0 };
      +	slice_resize(&s, in.len + 1);
      +	s.buf[in.len] = 0;
      +	memcpy(s.buf, in.buf, in.len);
     @@ -4585,8 +4538,8 @@
      +	struct stack *p = calloc(sizeof(struct stack), 1);
      +	int err = 0;
      +	*dest = NULL;
     -+	p->list_file = strdup(list_file);
     -+	p->reftable_dir = strdup(dir);
     ++	p->list_file = xstrdup(list_file);
     ++	p->reftable_dir = xstrdup(dir);
      +	p->config = config;
      +
      +	err = stack_reload(p);
     @@ -4690,7 +4643,7 @@
      +	int new_tables_len = 0;
      +	struct merged_table *new_merged = NULL;
      +
     -+	struct slice table_path = {};
     ++	struct slice table_path = { 0 };
      +
      +	while (*names) {
      +		struct reader *rd = NULL;
     @@ -4708,7 +4661,7 @@
      +		}
      +
      +		if (rd == NULL) {
     -+			struct block_source src = {};
     ++			struct block_source src = { 0 };
      +			slice_set_string(&table_path, st->reftable_dir);
      +			slice_append_string(&table_path, "/");
      +			slice_append_string(&table_path, name);
     @@ -4730,7 +4683,7 @@
      +
      +	/* success! */
      +	err = new_merged_table(&new_merged, new_tables, new_tables_len,
     -+			       st->config.hash_size);
     ++			       st->config.hash_id);
      +	if (err < 0) {
      +		goto exit;
      +	}
     @@ -4780,7 +4733,7 @@
      +
      +static int stack_reload_maybe_reuse(struct stack *st, bool reuse_open)
      +{
     -+	struct timeval deadline = {};
     ++	struct timeval deadline = { 0 };
      +	int err = gettimeofday(&deadline, NULL);
      +	int64_t delay = 0;
      +	int tries = 0;
     @@ -4792,7 +4745,7 @@
      +	while (true) {
      +		char **names = NULL;
      +		char **names_after = NULL;
     -+		struct timeval now = {};
     ++		struct timeval now = { 0 };
      +		int err = gettimeofday(&now, NULL);
      +		if (err < 0) {
      +			return err;
     @@ -4904,11 +4857,11 @@
      +int stack_try_add(struct stack *st,
      +		  int (*write_table)(struct writer *wr, void *arg), void *arg)
      +{
     -+	struct slice lock_name = {};
     -+	struct slice temp_tab_name = {};
     -+	struct slice tab_name = {};
     -+	struct slice next_name = {};
     -+	struct slice table_list = {};
     ++	struct slice lock_name = { 0 };
     ++	struct slice temp_tab_name = { 0 };
     ++	struct slice tab_name = { 0 };
     ++	struct slice next_name = { 0 };
     ++	struct slice table_list = { 0 };
      +	struct writer *wr = NULL;
      +	int err = 0;
      +	int tab_fd = 0;
     @@ -4962,6 +4915,10 @@
      +	}
      +
      +	err = writer_close(wr);
     ++	if (err == EMPTY_TABLE_ERROR) {
     ++		err = 0;
     ++		goto exit;
     ++	}
      +	if (err < 0) {
      +		goto exit;
      +	}
     @@ -5062,7 +5019,7 @@
      +				struct slice *temp_tab,
      +				struct log_expiry_config *config)
      +{
     -+	struct slice next_name = {};
     ++	struct slice next_name = { 0 };
      +	int tab_fd = -1;
      +	struct writer *wr = NULL;
      +	int err = 0;
     @@ -5113,9 +5070,9 @@
      +		calloc(sizeof(struct reader *), last - first + 1);
      +	struct merged_table *mt = NULL;
      +	int err = 0;
     -+	struct iterator it = {};
     -+	struct ref_record ref = {};
     -+	struct log_record log = {};
     ++	struct iterator it = { 0 };
     ++	struct ref_record ref = { 0 };
     ++	struct log_record log = { 0 };
      +
      +	int i = 0, j = 0;
      +	for (i = first, j = 0; i <= last; i++) {
     @@ -5126,7 +5083,7 @@
      +	writer_set_limits(wr, st->merged->stack[first]->min_update_index,
      +			  st->merged->stack[last]->max_update_index);
      +
     -+	err = new_merged_table(&mt, subtabs, subtabs_len, st->config.hash_size);
     ++	err = new_merged_table(&mt, subtabs, subtabs_len, st->config.hash_id);
      +	if (err < 0) {
      +		free(subtabs);
      +		goto exit;
     @@ -5207,11 +5164,11 @@
      +static int stack_compact_range(struct stack *st, int first, int last,
      +			       struct log_expiry_config *expiry)
      +{
     -+	struct slice temp_tab_name = {};
     -+	struct slice new_table_name = {};
     -+	struct slice lock_file_name = {};
     -+	struct slice ref_list_contents = {};
     -+	struct slice new_table_path = {};
     ++	struct slice temp_tab_name = { 0 };
     ++	struct slice new_table_name = { 0 };
     ++	struct slice lock_file_name = { 0 };
     ++	struct slice ref_list_contents = { 0 };
     ++	struct slice new_table_path = { 0 };
      +	int err = 0;
      +	bool have_lock = false;
      +	int lock_file_fd = 0;
     @@ -5220,6 +5177,7 @@
      +	char **subtable_locks = calloc(sizeof(char *), compact_count + 1);
      +	int i = 0;
      +	int j = 0;
     ++	bool is_empty_table = false;
      +
      +	if (first > last || (expiry == NULL && first == last)) {
      +		err = 0;
     @@ -5248,8 +5206,8 @@
      +	}
      +
      +	for (i = first, j = 0; i <= last; i++) {
     -+		struct slice subtab_name = {};
     -+		struct slice subtab_lock = {};
     ++		struct slice subtab_name = { 0 };
     ++		struct slice subtab_lock = { 0 };
      +		slice_set_string(&subtab_name, st->reftable_dir);
      +		slice_append_string(&subtab_name, "/");
      +		slice_append_string(&subtab_name,
     @@ -5288,6 +5246,12 @@
      +	have_lock = false;
      +
      +	err = stack_compact_locked(st, first, last, &temp_tab_name, expiry);
     ++	/* Compaction + tombstones can create an empty table out of non-empty
     ++	 * tables. */
     ++	is_empty_table = (err == EMPTY_TABLE_ERROR);
     ++	if (is_empty_table) {
     ++		err = 0;
     ++	}
      +	if (err < 0) {
      +		goto exit;
      +	}
     @@ -5313,10 +5277,12 @@
      +
      +	slice_append(&new_table_path, new_table_name);
      +
     -+	err = rename(slice_as_string(&temp_tab_name),
     -+		     slice_as_string(&new_table_path));
     -+	if (err < 0) {
     -+		goto exit;
     ++	if (!is_empty_table) {
     ++		err = rename(slice_as_string(&temp_tab_name),
     ++			     slice_as_string(&new_table_path));
     ++		if (err < 0) {
     ++			goto exit;
     ++		}
      +	}
      +
      +	for (i = 0; i < first; i++) {
     @@ -5324,8 +5290,10 @@
      +				    st->merged->stack[i]->name);
      +		slice_append_string(&ref_list_contents, "\n");
      +	}
     -+	slice_append(&ref_list_contents, new_table_name);
     -+	slice_append_string(&ref_list_contents, "\n");
     ++	if (!is_empty_table) {
     ++		slice_append(&ref_list_contents, new_table_name);
     ++		slice_append_string(&ref_list_contents, "\n");
     ++	}
      +	for (i = last + 1; i < st->merged->stack_len; i++) {
      +		slice_append_string(&ref_list_contents,
      +				    st->merged->stack[i]->name);
     @@ -5351,18 +5319,26 @@
      +	}
      +	have_lock = false;
      +
     -+	for (char **p = delete_on_success; *p; p++) {
     -+		if (strcmp(*p, slice_as_string(&new_table_path))) {
     -+			unlink(*p);
     ++	{
     ++		char **p = delete_on_success;
     ++		while (*p) {
     ++			if (strcmp(*p, slice_as_string(&new_table_path))) {
     ++				unlink(*p);
     ++			}
     ++			p++;
      +		}
      +	}
      +
      +	err = stack_reload_maybe_reuse(st, first < last);
      +exit:
     -+	for (char **p = subtable_locks; *p; p++) {
     -+		unlink(*p);
     -+	}
      +	free_names(delete_on_success);
     ++	{
     ++		char **p = subtable_locks;
     ++		while (*p) {
     ++			unlink(*p);
     ++			p++;
     ++		}
     ++	}
      +	free_names(subtable_locks);
      +	if (lock_file_fd > 0) {
      +		close(lock_file_fd);
     @@ -5413,7 +5389,7 @@
      +{
      +	struct segment *segs = calloc(sizeof(struct segment), n);
      +	int next = 0;
     -+	struct segment cur = {};
     ++	struct segment cur = { 0 };
      +	int i = 0;
      +	for (i = 0; i < n; i++) {
      +		int log = fastlog2(sizes[i]);
     @@ -5501,7 +5477,7 @@
      +int stack_read_ref(struct stack *st, const char *refname,
      +		   struct ref_record *ref)
      +{
     -+	struct iterator it = {};
     ++	struct iterator it = { 0 };
      +	struct merged_table *mt = stack_merged_table(st);
      +	int err = merged_table_seek_ref(mt, &it, refname);
      +	if (err) {
     @@ -5526,7 +5502,7 @@
      +int stack_read_log(struct stack *st, const char *refname,
      +		   struct log_record *log)
      +{
     -+	struct iterator it = {};
     ++	struct iterator it = { 0 };
      +	struct merged_table *mt = stack_merged_table(st);
      +	int err = merged_table_seek_log(mt, &it, refname);
      +	if (err) {
     @@ -5631,31 +5607,25 @@
      +#include <unistd.h>
      +#include <zlib.h>
      +
     -+#define ARRAY_SIZE(a) sizeof((a)) / sizeof((a)[0])
     -+#define FREE_AND_NULL(x)    \
     -+	do {                \
     -+		free(x);    \
     -+		(x) = NULL; \
     -+	} while (0)
     -+#define QSORT(arr, n, cmp) qsort(arr, n, sizeof(arr[0]), cmp)
     -+#define SWAP(a, b)                              \
     -+	{                                       \
     -+		char tmp[sizeof(a)];            \
     -+		assert(sizeof(a) == sizeof(b)); \
     -+		memcpy(&tmp[0], &a, sizeof(a)); \
     -+		memcpy(&a, &b, sizeof(a));      \
     -+		memcpy(&b, &tmp[0], sizeof(a)); \
     -+	}
     ++#include "compat.h"
     ++
      +#endif /* REFTABLE_IN_GITCORE */
      +
     ++#define SHA1_ID 0x73686131
     ++#define SHA256_ID 0x73323536
     ++#define SHA1_SIZE 20
     ++#define SHA256_SIZE 32
     ++
      +typedef uint8_t byte;
      +typedef int bool;
      +
     ++/* This is uncompress2, which is only available in zlib as of 2017.
     ++ *
     ++ * TODO: in git-core, this should fallback to uncompress2 if it is available.
     ++ */
      +int uncompress_return_consumed(Bytef *dest, uLongf *destLen,
      +			       const Bytef *source, uLong *sourceLen);
     -+
     -+#define SHA1_SIZE 20
     -+#define SHA256_SIZE 32
     ++int hash_size(uint32_t id);
      +
      +#endif
      
     @@ -5761,6 +5731,25 @@
      +
      +#endif
      
     + diff --git a/reftable/update.sh b/reftable/update.sh
     + new file mode 100644
     + --- /dev/null
     + +++ b/reftable/update.sh
     +@@
     ++#!/bin/sh
     ++
     ++set -eux
     ++
     ++((cd reftable-repo && git fetch origin && git checkout origin/master ) ||
     ++git clone https://github.com/google/reftable reftable-repo) && \
     ++cp reftable-repo/c/*.[ch] reftable/ && \
     ++cp reftable-repo/LICENSE reftable/ &&
     ++git --git-dir reftable-repo/.git show --no-patch origin/master \
     ++> reftable/VERSION && \
     ++sed -i~ 's|if REFTABLE_IN_GITCORE|if 1 /* REFTABLE_IN_GITCORE */|' reftable/system.h
     ++rm reftable/*_test.c reftable/test_framework.* reftable/compat.*
     ++git add reftable/*.[ch]
     +
       diff --git a/reftable/writer.c b/reftable/writer.c
       new file mode 100644
       --- /dev/null
     @@ -5831,8 +5820,8 @@
      +		opts->restart_interval = 16;
      +	}
      +
     -+	if (opts->hash_size == 0) {
     -+		opts->hash_size = SHA1_SIZE;
     ++	if (opts->hash_id == 0) {
     ++		opts->hash_id = SHA1_ID;
      +	}
      +	if (opts->block_size == 0) {
      +		opts->block_size = DEFAULT_BLOCK_SIZE;
     @@ -5842,11 +5831,14 @@
      +static int writer_write_header(struct writer *w, byte *dest)
      +{
      +	memcpy((char *)dest, "REFT", 4);
     -+	dest[4] = (w->hash_size == SHA1_SIZE) ? 1 : 2; /* version */
      +
     -+	put_u24(dest + 5, w->opts.block_size);
     -+	put_u64(dest + 8, w->min_update_index);
     -+	put_u64(dest + 16, w->max_update_index);
     ++	/* DO NOT SUBMIT.  This has not been encoded in the standard yet. */
     ++	dest[4] = (hash_size(w->opts.hash_id) == SHA1_SIZE) ? 1 : 2; /* version
     ++								      */
     ++
     ++	put_be24(dest + 5, w->opts.block_size);
     ++	put_be64(dest + 8, w->min_update_index);
     ++	put_be64(dest + 16, w->max_update_index);
      +	return 24;
      +}
      +
     @@ -5858,7 +5850,8 @@
      +	}
      +
      +	block_writer_init(&w->block_writer_data, typ, w->block,
     -+			  w->opts.block_size, block_start, w->hash_size);
     ++			  w->opts.block_size, block_start,
     ++			  hash_size(w->opts.hash_id));
      +	w->block_writer = &w->block_writer_data;
      +	w->block_writer->restart_interval = w->opts.restart_interval;
      +}
     @@ -5872,7 +5865,6 @@
      +		/* TODO - error return? */
      +		abort();
      +	}
     -+	wp->hash_size = opts->hash_size;
      +	wp->block = calloc(opts->block_size, 1);
      +	wp->write = writer_func;
      +	wp->write_arg = writer_arg;
     @@ -5941,7 +5933,7 @@
      +static int writer_add_record(struct writer *w, struct record rec)
      +{
      +	int result = -1;
     -+	struct slice key = {};
     ++	struct slice key = { 0 };
      +	int err = 0;
      +	record_key(rec, &key);
      +	if (slice_compare(w->last_key, key) >= 0) {
     @@ -5981,7 +5973,7 @@
      +
      +int writer_add_ref(struct writer *w, struct ref_record *ref)
      +{
     -+	struct record rec = {};
     ++	struct record rec = { 0 };
      +	struct ref_record copy = *ref;
      +	int err = 0;
      +
     @@ -6003,7 +5995,7 @@
      +	if (!w->opts.skip_index_objects && ref->value != NULL) {
      +		struct slice h = {
      +			.buf = ref->value,
     -+			.len = w->hash_size,
     ++			.len = hash_size(w->opts.hash_id),
      +		};
      +
      +		writer_index_hash(w, h);
     @@ -6011,7 +6003,7 @@
      +	if (!w->opts.skip_index_objects && ref->target_value != NULL) {
      +		struct slice h = {
      +			.buf = ref->target_value,
     -+			.len = w->hash_size,
     ++			.len = hash_size(w->opts.hash_id),
      +		};
      +		writer_index_hash(w, h);
      +	}
     @@ -6047,7 +6039,7 @@
      +	w->pending_padding = 0;
      +
      +	{
     -+		struct record rec = {};
     ++		struct record rec = { 0 };
      +		int err;
      +		record_from_log(&rec, log);
      +		err = writer_add_record(w, rec);
     @@ -6094,7 +6086,7 @@
      +		w->index_len = 0;
      +		w->index_cap = 0;
      +		for (i = 0; i < idx_len; i++) {
     -+			struct record rec = {};
     ++			struct record rec = { 0 };
      +			record_from_index(&rec, idx + i);
      +			if (block_writer_add(w->block_writer, rec) == 0) {
      +				continue;
     @@ -6172,7 +6164,7 @@
      +		.offsets = entry->offsets,
      +		.offset_len = entry->offset_len,
      +	};
     -+	struct record rec = {};
     ++	struct record rec = { 0 };
      +	if (arg->err < 0) {
      +		goto exit;
      +	}
     @@ -6214,7 +6206,7 @@
      +static int writer_dump_object_index(struct writer *w)
      +{
      +	struct write_record_arg closure = { .w = w };
     -+	struct common_prefix_arg common = {};
     ++	struct common_prefix_arg common = { 0 };
      +	if (w->obj_index_tree != NULL) {
      +		infix_walk(w->obj_index_tree, &update_common, &common);
      +	}
     @@ -6271,39 +6263,43 @@
      +
      +	int err = writer_finish_public_section(w);
      +	if (err != 0) {
     -+		return err;
     ++		goto exit;
      +	}
      +
      +	writer_write_header(w, footer);
      +	p += 24;
     -+	put_u64(p, w->stats.ref_stats.index_offset);
     ++	put_be64(p, w->stats.ref_stats.index_offset);
      +	p += 8;
     -+	put_u64(p, (w->stats.obj_stats.offset) << 5 | w->stats.object_id_len);
     ++	put_be64(p, (w->stats.obj_stats.offset) << 5 | w->stats.object_id_len);
      +	p += 8;
     -+	put_u64(p, w->stats.obj_stats.index_offset);
     ++	put_be64(p, w->stats.obj_stats.index_offset);
      +	p += 8;
      +
     -+	put_u64(p, w->stats.log_stats.offset);
     ++	put_be64(p, w->stats.log_stats.offset);
      +	p += 8;
     -+	put_u64(p, w->stats.log_stats.index_offset);
     ++	put_be64(p, w->stats.log_stats.index_offset);
      +	p += 8;
      +
     -+	put_u32(p, crc32(0, footer, p - footer));
     ++	put_be32(p, crc32(0, footer, p - footer));
      +	p += 4;
      +	w->pending_padding = 0;
      +
     -+	{
     -+		int n = padded_write(w, footer, sizeof(footer), 0);
     -+		if (n < 0) {
     -+			return n;
     -+		}
     ++	err = padded_write(w, footer, sizeof(footer), 0);
     ++	if (err < 0) {
     ++		goto exit;
     ++	}
     ++
     ++	if (w->stats.log_stats.entries + w->stats.ref_stats.entries == 0) {
     ++		err = EMPTY_TABLE_ERROR;
     ++		goto exit;
      +	}
      +
     ++exit:
      +	/* free up memory. */
      +	block_writer_clear(&w->block_writer_data);
      +	writer_clear_index(w);
      +	free(slice_yield(&w->last_key));
     -+	return 0;
     ++	return err;
      +}
      +
      +void writer_clear_index(struct writer *w)
     @@ -6348,7 +6344,7 @@
      +	if (debug) {
      +		fprintf(stderr, "block %c off %" PRIu64 " sz %d (%d)\n", typ,
      +			w->next, raw_bytes,
     -+			get_u24(w->block + w->block_writer->header_off + 1));
     ++			get_be24(w->block + w->block_writer->header_off + 1));
      +	}
      +
      +	if (w->next == 0) {
     @@ -6423,7 +6419,6 @@
      +	int (*write)(void *, byte *, int);
      +	void *write_arg;
      +	int pending_padding;
     -+	int hash_size;
      +	struct slice last_key;
      +
      +	uint64_t next;
 5:  8d95ab52f75 ! 6:  a622d8066c7 Reftable support for git-core
     @@ -9,32 +9,7 @@
           * Resolve spots marked with XXX
           * Test strategy?
      
     -    Example use:
     -
     -      $ ~/vc/git/git init --reftable
     -      warning: templates not found in /usr/local/google/home/hanwen/share/git-core/templates
     -      Initialized empty Git repository in /tmp/qz/.git/
     -      $ echo q > a
     -      $ ~/vc/git/git add a
     -      $ ~/vc/git/git commit -mx
     -      fatal: not a git repository (or any of the parent directories): .git
     -      [master (root-commit) 373d969] x
     -       1 file changed, 1 insertion(+)
     -       create mode 100644 a
     -      $ ~/vc/git/git show-ref
     -      373d96972fca9b63595740bba3898a762778ba20 HEAD
     -      373d96972fca9b63595740bba3898a762778ba20 refs/heads/master
     -      $ ls -l .git/reftable/
     -      total 12
     -      -rw------- 1 hanwen primarygroup  126 Jan 23 20:08 000000000001-000000000001.ref
     -      -rw------- 1 hanwen primarygroup 4277 Jan 23 20:08 000000000002-000000000002.ref
     -      $ go run ~/vc/reftable/cmd/dump.go  -table /tmp/qz/.git/reftable/000000000002-000000000002.ref
     -      ** DEBUG **
     -      name /tmp/qz/.git/reftable/000000000002-000000000002.ref, sz 4209: 'r' reftable.readerOffsets{Present:true, Offset:0x0, IndexOffset:0x0}, 'o' reftable.readerOffsets{Present:false, Offset:0x0, IndexOffset:0x0} 'g' reftable.readerOffsets{Present:true, Offset:0x1000, IndexOffset:0x0}
     -      ** REFS **
     -      reftable.RefRecord{RefName:"refs/heads/master", UpdateIndex:0x2, Value:[]uint8{0x37, 0x3d, 0x96, 0x97, 0x2f, 0xca, 0x9b, 0x63, 0x59, 0x57, 0x40, 0xbb, 0xa3, 0x89, 0x8a, 0x76, 0x27, 0x78, 0xba, 0x20}, TargetValue:[]uint8(nil), Target:""}
     -      ** LOGS **
     -      reftable.LogRecord{RefName:"HEAD", UpdateIndex:0x2, New:[]uint8{0x37, 0x3d, 0x96, 0x97, 0x2f, 0xca, 0x9b, 0x63, 0x59, 0x57, 0x40, 0xbb, 0xa3, 0x89, 0x8a, 0x76, 0x27, 0x78, 0xba, 0x20}, Old:[]uint8{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, Name:"Han-Wen Nienhuys", Email:"hanwen@google.com", Time:0x5e29ef27, TZOffset:100, Message:"commit (initial): x\n"}
     +    Example use: see t/t0031-reftable.sh
      
          Signed-off-by: Han-Wen Nienhuys <hanwen@google.com>
          Co-authored-by: Jeff King <peff@peff.net>
     @@ -463,7 +438,7 @@ $^
      +	struct ref_store *ref_store = (struct ref_store *)refs;
      +	struct write_options cfg = {
      +		.block_size = 4096,
     -+		.hash_size = the_hash_algo->rawsz,
     ++		.hash_id = the_hash_algo->format_id,
      +	};
      +	struct strbuf sb = STRBUF_INIT;
      +
     @@ -610,11 +585,13 @@ $^
      +	struct reftable_iterator *ri = xcalloc(1, sizeof(*ri));
      +	struct merged_table *mt = NULL;
      +
     -+	mt = stack_merged_table(refs->stack);
     -+	ri->err = refs->err;
     -+	if (ri->err == 0) {
     ++	if (refs->err < 0) {
     ++		ri->err = refs->err;
     ++	} else {
     ++		mt = stack_merged_table(refs->stack);
      +		ri->err = merged_table_seek_ref(mt, &ri->iter, prefix);
      +	}
     ++
      +	base_ref_iterator_init(&ri->base, &reftable_ref_iterator_vtable, 1);
      +	ri->base.oid = &ri->oid;
      +	ri->flags = flags;
     @@ -739,7 +716,12 @@ $^
      +{
      +	struct reftable_ref_store *refs =
      +		(struct reftable_ref_store *)ref_store;
     -+	int err = stack_add(refs->stack, &write_transaction_table, transaction);
     ++	int err = 0;
     ++	if (refs->err < 0) {
     ++		return refs->err;
     ++	}
     ++
     ++	err = stack_add(refs->stack, &write_transaction_table, transaction);
      +	if (err < 0) {
      +		strbuf_addf(errmsg, "reftable: transaction failure %s",
      +			    error_str(err));
     @@ -819,6 +801,10 @@ $^
      +		.logmsg = msg,
      +		.flags = flags,
      +	};
     ++	if (refs->err < 0) {
     ++		return refs->err;
     ++	}
     ++
      +	return stack_add(refs->stack, &write_delete_refs_table, &arg);
      +}
      +
     @@ -826,6 +812,9 @@ $^
      +{
      +	struct reftable_ref_store *refs =
      +		(struct reftable_ref_store *)ref_store;
     ++	if (refs->err < 0) {
     ++		return refs->err;
     ++	}
      +	return stack_compact_all(refs->stack, NULL);
      +}
      +
     @@ -896,6 +885,9 @@ $^
      +					       .refname = refname,
      +					       .target = target,
      +					       .logmsg = logmsg };
     ++	if (refs->err < 0) {
     ++		return refs->err;
     ++	}
      +	return stack_add(refs->stack, &write_create_symref_table, &arg);
      +}
      +
     @@ -912,6 +904,7 @@ $^
      +	uint64_t ts = stack_next_update_index(arg->stack);
      +	struct ref_record ref = {};
      +	int err = stack_read_ref(arg->stack, arg->oldname, &ref);
     ++
      +	if (err) {
      +		goto exit;
      +	}
     @@ -987,6 +980,10 @@ $^
      +		.newname = newrefname,
      +		.logmsg = logmsg,
      +	};
     ++	if (refs->err < 0) {
     ++		return refs->err;
     ++	}
     ++
      +	return stack_add(refs->stack, &write_rename_table, &arg);
      +}
      +
     @@ -1083,10 +1080,16 @@ $^
      +	struct iterator it = {};
      +	struct reftable_ref_store *refs =
      +		(struct reftable_ref_store *)ref_store;
     -+	struct merged_table *mt = stack_merged_table(refs->stack);
     -+	int err = merged_table_seek_log(mt, &it, refname);
     ++	struct merged_table *mt = NULL;
     ++	int err = 0;
      +	struct log_record log = {};
      +
     ++	if (refs->err < 0) {
     ++		return refs->err;
     ++	}
     ++
     ++	mt = stack_merged_table(refs->stack);
     ++	err = merged_table_seek_log(mt, &it, refname);
      +	while (err == 0) {
      +		err = iterator_next_log(it, &log);
      +		if (err != 0) {
     @@ -1133,12 +1136,17 @@ $^
      +	struct iterator it = {};
      +	struct reftable_ref_store *refs =
      +		(struct reftable_ref_store *)ref_store;
     -+	struct merged_table *mt = stack_merged_table(refs->stack);
     -+	int err = merged_table_seek_log(mt, &it, refname);
     -+
     ++	struct merged_table *mt = NULL;
      +	struct log_record *logs = NULL;
      +	int cap = 0;
      +	int len = 0;
     ++	int err = 0;
     ++
     ++	if (refs->err < 0) {
     ++		return refs->err;
     ++	}
     ++	mt = stack_merged_table(refs->stack);
     ++	err = merged_table_seek_log(mt, &it, refname);
      +
      +	while (err == 0) {
      +		struct log_record log = {};
     @@ -1280,13 +1288,19 @@ $^
      +	*/
      +	struct reftable_ref_store *refs =
      +		(struct reftable_ref_store *)ref_store;
     -+	struct merged_table *mt = stack_merged_table(refs->stack);
     ++	struct merged_table *mt = NULL;
      +	struct reflog_expiry_arg arg = {
      +		.refs = refs,
      +	};
      +	struct log_record log = {};
      +	struct iterator it = {};
     -+	int err = merged_table_seek_log(mt, &it, refname);
     ++	int err = 0;
     ++	if (refs->err < 0) {
     ++		return refs->err;
     ++	}
     ++
     ++	mt = stack_merged_table(refs->stack);
     ++	err = merged_table_seek_log(mt, &it, refname);
      +	if (err < 0) {
      +		return err;
      +	}
     @@ -1326,7 +1340,12 @@ $^
      +	struct reftable_ref_store *refs =
      +		(struct reftable_ref_store *)ref_store;
      +	struct ref_record ref = {};
     -+	int err = stack_read_ref(refs->stack, refname, &ref);
     ++	int err = 0;
     ++	if (refs->err < 0) {
     ++		return refs->err;
     ++	}
     ++
     ++	err = stack_read_ref(refs->stack, refname, &ref);
      +	if (err) {
      +		goto exit;
      +	}
     @@ -1436,3 +1455,40 @@ $^
       	}
       
       	strbuf_release(&dir);
     +
     + diff --git a/t/t0031-reftable.sh b/t/t0031-reftable.sh
     + new file mode 100755
     + --- /dev/null
     + +++ b/t/t0031-reftable.sh
     +@@
     ++#!/bin/sh
     ++#
     ++# Copyright (c) 2020 Google LLC
     ++#
     ++
     ++test_description='reftable basics'
     ++
     ++. ./test-lib.sh
     ++
     ++test_expect_success 'basic operation of reftable storage' '
     ++        git init --ref-storage=reftable repo &&
     ++        cd repo &&
     ++        echo "hello" > world.txt &&
     ++        git add world.txt &&
     ++        git commit -m "first post" &&
     ++        printf "HEAD\nrefs/heads/master\n" > want &&
     ++        git show-ref | cut -f2 -d" " > got &&
     ++        test_cmp got want &&
     ++        for count in $(test_seq 1 10); do
     ++                echo "hello" >> world.txt
     ++                git commit -m "number ${count}" world.txt
     ++        done &&
     ++        git gc &&
     ++        nfiles=$(ls -1 .git/reftable | wc -l | tr -d "[ \t]" ) &&
     ++        test "${nfiles}" = "2" &&
     ++        git reflog refs/heads/master > output &&
     ++        grep "commit (initial): first post" output &&
     ++        grep "commit: number 10" output
     ++'
     ++
     ++test_done

-- 
gitgitgadget

^ permalink raw reply	[relevance 1%]

* [PATCH v6 4/5] Add reftable library
  @ 2020-02-18  8:43  1%           ` Han-Wen Nienhuys via GitGitGadget
  2020-02-26  8:49  1%           ` [PATCH v7 0/6] Reftable support git-core Han-Wen Nienhuys via GitGitGadget
  1 sibling, 0 replies; 200+ results
From: Han-Wen Nienhuys via GitGitGadget @ 2020-02-18  8:43 UTC (permalink / raw)
  To: git; +Cc: Han-Wen Nienhuys, Han-Wen Nienhuys

From: Han-Wen Nienhuys <hanwen@google.com>

Reftable is a new format for storing the ref database. It provides the
following benefits:

 * Simple and fast atomic ref transactions, including multiple refs and reflogs.
 * Compact storage of ref data.
 * Fast look ups of ref data.
 * Case-sensitive ref names on Windows/OSX, regardless of file system
 * Eliminates file/directory conflicts in ref names

Further context and motivation can be found in background reading:

* Spec: https://github.com/eclipse/jgit/blob/master/Documentation/technical/reftable.md

* Original discussion on JGit-dev:  https://www.eclipse.org/lists/jgit-dev/msg03389.html

* First design discussion on git@vger: https://public-inbox.org/git/CAJo=hJtTp2eA3z9wW9cHo-nA7kK40vVThqh6inXpbCcqfdMP9g@mail.gmail.com/

* Last design discussion on git@vger: https://public-inbox.org/git/CAJo=hJsZcAM9sipdVr7TMD-FD2V2W6_pvMQ791EGCDsDkQ033w@mail.gmail.com/

* First attempt at implementation: https://public-inbox.org/git/CAP8UFD0PPZSjBnxCA7ez91vBuatcHKQ+JUWvTD1iHcXzPBjPBg@mail.gmail.com/

* libgit2 support issue: https://github.com/libgit2/libgit2/issues

* GitLab support issue: https://gitlab.com/gitlab-org/git/issues/6

* go-git support issue: https://github.com/src-d/go-git/issues/1059

Signed-off-by: Han-Wen Nienhuys <hanwen@google.com>
---
 reftable/LICENSE       |   31 ++
 reftable/README.md     |   19 +
 reftable/VERSION       |   11 +
 reftable/basics.c      |  196 +++++++
 reftable/basics.h      |   37 ++
 reftable/block.c       |  413 +++++++++++++++
 reftable/block.h       |   71 +++
 reftable/blocksource.h |   20 +
 reftable/bytes.c       |    0
 reftable/config.h      |    1 +
 reftable/constants.h   |   25 +
 reftable/dump.c        |   97 ++++
 reftable/file.c        |   97 ++++
 reftable/iter.c        |  229 +++++++++
 reftable/iter.h        |   56 ++
 reftable/merged.c      |  290 +++++++++++
 reftable/merged.h      |   34 ++
 reftable/pq.c          |  114 +++++
 reftable/pq.h          |   34 ++
 reftable/reader.c      |  718 ++++++++++++++++++++++++++
 reftable/reader.h      |   52 ++
 reftable/record.c      | 1107 ++++++++++++++++++++++++++++++++++++++++
 reftable/record.h      |   79 +++
 reftable/reftable.h    |  405 +++++++++++++++
 reftable/slice.c       |  199 ++++++++
 reftable/slice.h       |   39 ++
 reftable/stack.c       |  984 +++++++++++++++++++++++++++++++++++
 reftable/stack.h       |   40 ++
 reftable/system.h      |   59 +++
 reftable/tree.c        |   66 +++
 reftable/tree.h        |   24 +
 reftable/writer.c      |  630 +++++++++++++++++++++++
 reftable/writer.h      |   46 ++
 reftable/zlib-compat.c |   92 ++++
 34 files changed, 6315 insertions(+)
 create mode 100644 reftable/LICENSE
 create mode 100644 reftable/README.md
 create mode 100644 reftable/VERSION
 create mode 100644 reftable/basics.c
 create mode 100644 reftable/basics.h
 create mode 100644 reftable/block.c
 create mode 100644 reftable/block.h
 create mode 100644 reftable/blocksource.h
 create mode 100644 reftable/bytes.c
 create mode 100644 reftable/config.h
 create mode 100644 reftable/constants.h
 create mode 100644 reftable/dump.c
 create mode 100644 reftable/file.c
 create mode 100644 reftable/iter.c
 create mode 100644 reftable/iter.h
 create mode 100644 reftable/merged.c
 create mode 100644 reftable/merged.h
 create mode 100644 reftable/pq.c
 create mode 100644 reftable/pq.h
 create mode 100644 reftable/reader.c
 create mode 100644 reftable/reader.h
 create mode 100644 reftable/record.c
 create mode 100644 reftable/record.h
 create mode 100644 reftable/reftable.h
 create mode 100644 reftable/slice.c
 create mode 100644 reftable/slice.h
 create mode 100644 reftable/stack.c
 create mode 100644 reftable/stack.h
 create mode 100644 reftable/system.h
 create mode 100644 reftable/tree.c
 create mode 100644 reftable/tree.h
 create mode 100644 reftable/writer.c
 create mode 100644 reftable/writer.h
 create mode 100644 reftable/zlib-compat.c

diff --git a/reftable/LICENSE b/reftable/LICENSE
new file mode 100644
index 00000000000..402e0f9356b
--- /dev/null
+++ b/reftable/LICENSE
@@ -0,0 +1,31 @@
+BSD License
+
+Copyright (c) 2020, Google LLC
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+* Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+* Neither the name of Google LLC nor the names of its contributors may
+be used to endorse or promote products derived from this software
+without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/reftable/README.md b/reftable/README.md
new file mode 100644
index 00000000000..333b6ba02b4
--- /dev/null
+++ b/reftable/README.md
@@ -0,0 +1,19 @@
+
+The source code in this directory comes from https://github.com/google/reftable.
+
+The VERSION file keeps track of the current version of the reftable library.
+
+To update the library, do:
+
+   ((cd reftable-repo && git fetch origin && git checkout origin/master ) ||
+    git clone https://github.com/google/reftable reftable-repo) && \
+   cp reftable-repo/c/*.[ch] reftable/ && \
+   cp reftable-repo/LICENSE reftable/ &&
+   git --git-dir reftable-repo/.git show --no-patch origin/master \
+    > reftable/VERSION && \
+   sed -i~ 's|if REFTABLE_IN_GITCORE|if 1 /* REFTABLE_IN_GITCORE */|' reftable/system.h
+   rm reftable/*_test.c reftable/test_framework.*
+   git add reftable/*.[ch]
+
+Bugfixes should be accompanied by a test and applied to upstream project at
+https://github.com/google/reftable.
diff --git a/reftable/VERSION b/reftable/VERSION
new file mode 100644
index 00000000000..7a71a3b1d4e
--- /dev/null
+++ b/reftable/VERSION
@@ -0,0 +1,11 @@
+commit 42aa04f301a682197f0698867b9771b78ce4fdaf
+Author: Han-Wen Nienhuys <hanwen@google.com>
+Date:   Mon Feb 17 16:05:22 2020 +0100
+
+    C: simplify system and config header handling
+    
+    reftable.h only depends on <stdint.h>
+    
+    system.h is only included from .c files. The git-core specific code is
+    guarded by a #if REFTABLE_IN_GITCORE, which we'll edit with a sed
+    script on the git-core side.
diff --git a/reftable/basics.c b/reftable/basics.c
new file mode 100644
index 00000000000..791dcc867ad
--- /dev/null
+++ b/reftable/basics.c
@@ -0,0 +1,196 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "basics.h"
+
+#include "system.h"
+
+void put_u24(byte *out, uint32_t i)
+{
+	out[0] = (byte)((i >> 16) & 0xff);
+	out[1] = (byte)((i >> 8) & 0xff);
+	out[2] = (byte)((i)&0xff);
+}
+
+uint32_t get_u24(byte *in)
+{
+	return (uint32_t)(in[0]) << 16 | (uint32_t)(in[1]) << 8 |
+	       (uint32_t)(in[2]);
+}
+
+void put_u32(byte *out, uint32_t i)
+{
+	out[0] = (byte)((i >> 24) & 0xff);
+	out[1] = (byte)((i >> 16) & 0xff);
+	out[2] = (byte)((i >> 8) & 0xff);
+	out[3] = (byte)((i)&0xff);
+}
+
+uint32_t get_u32(byte *in)
+{
+	return (uint32_t)(in[0]) << 24 | (uint32_t)(in[1]) << 16 |
+	       (uint32_t)(in[2]) << 8 | (uint32_t)(in[3]);
+}
+
+void put_u64(byte *out, uint64_t v)
+{
+	int i = 0;
+	for (i = sizeof(uint64_t); i--;) {
+		out[i] = (byte)(v & 0xff);
+		v >>= 8;
+	}
+}
+
+uint64_t get_u64(byte *out)
+{
+	uint64_t v = 0;
+	int i = 0;
+	for (i = 0; i < sizeof(uint64_t); i++) {
+		v = (v << 8) | (byte)(out[i] & 0xff);
+	}
+	return v;
+}
+
+void put_u16(byte *out, uint16_t i)
+{
+	out[0] = (byte)((i >> 8) & 0xff);
+	out[1] = (byte)((i)&0xff);
+}
+
+uint16_t get_u16(byte *in)
+{
+	return (uint32_t)(in[0]) << 8 | (uint32_t)(in[1]);
+}
+
+/*
+  find smallest index i in [0, sz) at which f(i) is true, assuming
+  that f is ascending. Return sz if f(i) is false for all indices.
+*/
+int binsearch(int sz, int (*f)(int k, void *args), void *args)
+{
+	int lo = 0;
+	int hi = sz;
+
+	/* invariant: (hi == sz) || f(hi) == true
+	   (lo == 0 && f(0) == true) || fi(lo) == false
+	 */
+	while (hi - lo > 1) {
+		int mid = lo + (hi - lo) / 2;
+
+		int val = f(mid, args);
+		if (val) {
+			hi = mid;
+		} else {
+			lo = mid;
+		}
+	}
+
+	if (lo == 0) {
+		if (f(0, args)) {
+			return 0;
+		} else {
+			return 1;
+		}
+	}
+
+	return hi;
+}
+
+void free_names(char **a)
+{
+	char **p = a;
+	if (p == NULL) {
+		return;
+	}
+	while (*p) {
+		free(*p);
+		p++;
+	}
+	free(a);
+}
+
+int names_length(char **names)
+{
+	int len = 0;
+	for (char **p = names; *p; p++) {
+		len++;
+	}
+	return len;
+}
+
+/* parse a newline separated list of names. Empty names are discarded. */
+void parse_names(char *buf, int size, char ***namesp)
+{
+	char **names = NULL;
+	int names_cap = 0;
+	int names_len = 0;
+
+	char *p = buf;
+	char *end = buf + size;
+	while (p < end) {
+		char *next = strchr(p, '\n');
+		if (next != NULL) {
+			*next = 0;
+		} else {
+			next = end;
+		}
+		if (p < next) {
+			if (names_len == names_cap) {
+				names_cap = 2 * names_cap + 1;
+				names = realloc(names,
+						names_cap * sizeof(char *));
+			}
+			names[names_len++] = strdup(p);
+		}
+		p = next + 1;
+	}
+
+	if (names_len == names_cap) {
+		names_cap = 2 * names_cap + 1;
+		names = realloc(names, names_cap * sizeof(char *));
+	}
+
+	names[names_len] = NULL;
+	*namesp = names;
+}
+
+int names_equal(char **a, char **b)
+{
+	while (*a && *b) {
+		if (strcmp(*a, *b)) {
+			return 0;
+		}
+
+		a++;
+		b++;
+	}
+
+	return *a == *b;
+}
+
+const char *error_str(int err)
+{
+	switch (err) {
+	case IO_ERROR:
+		return "I/O error";
+	case FORMAT_ERROR:
+		return "FORMAT_ERROR";
+	case NOT_EXIST_ERROR:
+		return "NOT_EXIST_ERROR";
+	case LOCK_ERROR:
+		return "LOCK_ERROR";
+	case API_ERROR:
+		return "API_ERROR";
+	case ZLIB_ERROR:
+		return "ZLIB_ERROR";
+	case -1:
+		return "general error";
+	default:
+		return "unknown error code";
+	}
+}
diff --git a/reftable/basics.h b/reftable/basics.h
new file mode 100644
index 00000000000..0ad368cfd3d
--- /dev/null
+++ b/reftable/basics.h
@@ -0,0 +1,37 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef BASICS_H
+#define BASICS_H
+
+#include "system.h"
+
+#include "reftable.h"
+
+#define true 1
+#define false 0
+
+void put_u24(byte *out, uint32_t i);
+uint32_t get_u24(byte *in);
+
+uint64_t get_u64(byte *in);
+void put_u64(byte *out, uint64_t i);
+
+void put_u32(byte *out, uint32_t i);
+uint32_t get_u32(byte *in);
+
+void put_u16(byte *out, uint16_t i);
+uint16_t get_u16(byte *in);
+int binsearch(int sz, int (*f)(int k, void *args), void *args);
+
+void free_names(char **a);
+void parse_names(char *buf, int size, char ***namesp);
+int names_equal(char **a, char **b);
+int names_length(char **names);
+
+#endif
diff --git a/reftable/block.c b/reftable/block.c
new file mode 100644
index 00000000000..2ad1bea0ede
--- /dev/null
+++ b/reftable/block.c
@@ -0,0 +1,413 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "block.h"
+
+#include "system.h"
+
+#include "blocksource.h"
+#include "constants.h"
+#include "record.h"
+#include "reftable.h"
+#include "zlib.h"
+
+int block_writer_register_restart(struct block_writer *w, int n, bool restart,
+				  struct slice key);
+
+void block_writer_init(struct block_writer *bw, byte typ, byte *buf,
+		       uint32_t block_size, uint32_t header_off, int hash_size)
+{
+	bw->buf = buf;
+	bw->hash_size = hash_size;
+	bw->block_size = block_size;
+	bw->header_off = header_off;
+	bw->buf[header_off] = typ;
+	bw->next = header_off + 4;
+	bw->restart_interval = 16;
+	bw->entries = 0;
+}
+
+byte block_writer_type(struct block_writer *bw)
+{
+	return bw->buf[bw->header_off];
+}
+
+/* adds the record to the block. Returns -1 if it does not fit, 0 on
+   success */
+int block_writer_add(struct block_writer *w, struct record rec)
+{
+	struct slice empty = {};
+	struct slice last = w->entries % w->restart_interval == 0 ? empty :
+								    w->last_key;
+	struct slice out = {
+		.buf = w->buf + w->next,
+		.len = w->block_size - w->next,
+	};
+
+	struct slice start = out;
+
+	bool restart = false;
+	struct slice key = {};
+	int n = 0;
+
+	record_key(rec, &key);
+	n = encode_key(&restart, out, last, key, record_val_type(rec));
+	if (n < 0) {
+		goto err;
+	}
+	out.buf += n;
+	out.len -= n;
+
+	n = record_encode(rec, out, w->hash_size);
+	if (n < 0) {
+		goto err;
+	}
+
+	out.buf += n;
+	out.len -= n;
+
+	if (block_writer_register_restart(w, start.len - out.len, restart,
+					  key) < 0) {
+		goto err;
+	}
+
+	free(slice_yield(&key));
+	return 0;
+
+err:
+	free(slice_yield(&key));
+	return -1;
+}
+
+int block_writer_register_restart(struct block_writer *w, int n, bool restart,
+				  struct slice key)
+{
+	int rlen = w->restart_len;
+	if (rlen >= MAX_RESTARTS) {
+		restart = false;
+	}
+
+	if (restart) {
+		rlen++;
+	}
+	if (2 + 3 * rlen + n > w->block_size - w->next) {
+		return -1;
+	}
+	if (restart) {
+		if (w->restart_len == w->restart_cap) {
+			w->restart_cap = w->restart_cap * 2 + 1;
+			w->restarts = realloc(
+				w->restarts, sizeof(uint32_t) * w->restart_cap);
+		}
+
+		w->restarts[w->restart_len++] = w->next;
+	}
+
+	w->next += n;
+	slice_copy(&w->last_key, key);
+	w->entries++;
+	return 0;
+}
+
+int block_writer_finish(struct block_writer *w)
+{
+	int i = 0;
+	for (i = 0; i < w->restart_len; i++) {
+		put_u24(w->buf + w->next, w->restarts[i]);
+		w->next += 3;
+	}
+
+	put_u16(w->buf + w->next, w->restart_len);
+	w->next += 2;
+	put_u24(w->buf + 1 + w->header_off, w->next);
+
+	if (block_writer_type(w) == BLOCK_TYPE_LOG) {
+		int block_header_skip = 4 + w->header_off;
+		struct slice compressed = {};
+		int zresult = 0;
+		uLongf src_len = w->next - block_header_skip;
+		slice_resize(&compressed, src_len);
+
+		while (1) {
+			uLongf dest_len = compressed.len;
+
+			zresult = compress2(compressed.buf, &dest_len,
+					    w->buf + block_header_skip, src_len,
+					    9);
+			if (zresult == Z_BUF_ERROR) {
+				slice_resize(&compressed, 2 * compressed.len);
+				continue;
+			}
+
+			if (Z_OK != zresult) {
+				free(slice_yield(&compressed));
+				return ZLIB_ERROR;
+			}
+
+			memcpy(w->buf + block_header_skip, compressed.buf,
+			       dest_len);
+			w->next = dest_len + block_header_skip;
+			break;
+		}
+	}
+	return w->next;
+}
+
+byte block_reader_type(struct block_reader *r)
+{
+	return r->block.data[r->header_off];
+}
+
+int block_reader_init(struct block_reader *br, struct block *block,
+		      uint32_t header_off, uint32_t table_block_size,
+		      int hash_size)
+{
+	uint32_t full_block_size = table_block_size;
+	byte typ = block->data[header_off];
+	uint32_t sz = get_u24(block->data + header_off + 1);
+
+	if (!is_block_type(typ)) {
+		return FORMAT_ERROR;
+	}
+
+	if (typ == BLOCK_TYPE_LOG) {
+		struct slice uncompressed = {};
+		int block_header_skip = 4 + header_off;
+		uLongf dst_len = sz - block_header_skip;
+		uLongf src_len = block->len - block_header_skip;
+
+		slice_resize(&uncompressed, sz);
+		memcpy(uncompressed.buf, block->data, block_header_skip);
+
+		if (Z_OK != uncompress_return_consumed(
+				    uncompressed.buf + block_header_skip,
+				    &dst_len, block->data + block_header_skip,
+				    &src_len)) {
+			free(slice_yield(&uncompressed));
+			return ZLIB_ERROR;
+		}
+
+		block_source_return_block(block->source, block);
+		block->data = uncompressed.buf;
+		block->len = dst_len; /* XXX: 4 bytes missing? */
+		block->source = malloc_block_source();
+		full_block_size = src_len + block_header_skip;
+	} else if (full_block_size == 0) {
+		full_block_size = sz;
+	} else if (sz < full_block_size && sz < block->len &&
+		   block->data[sz] != 0) {
+		/* If the block is smaller than the full block size, it is
+		   padded (data followed by '\0') or the next block is
+		   unaligned. */
+		full_block_size = sz;
+	}
+
+	{
+		uint16_t restart_count = get_u16(block->data + sz - 2);
+		uint32_t restart_start = sz - 2 - 3 * restart_count;
+
+		byte *restart_bytes = block->data + restart_start;
+
+		/* transfer ownership. */
+		br->block = *block;
+		block->data = NULL;
+		block->len = 0;
+
+		br->hash_size = hash_size;
+		br->block_len = restart_start;
+		br->full_block_size = full_block_size;
+		br->header_off = header_off;
+		br->restart_count = restart_count;
+		br->restart_bytes = restart_bytes;
+	}
+
+	return 0;
+}
+
+static uint32_t block_reader_restart_offset(struct block_reader *br, int i)
+{
+	return get_u24(br->restart_bytes + 3 * i);
+}
+
+void block_reader_start(struct block_reader *br, struct block_iter *it)
+{
+	it->br = br;
+	slice_resize(&it->last_key, 0);
+	it->next_off = br->header_off + 4;
+}
+
+struct restart_find_args {
+	struct slice key;
+	struct block_reader *r;
+	int error;
+};
+
+static int restart_key_less(int idx, void *args)
+{
+	struct restart_find_args *a = (struct restart_find_args *)args;
+	uint32_t off = block_reader_restart_offset(a->r, idx);
+	struct slice in = {
+		.buf = a->r->block.data + off,
+		.len = a->r->block_len - off,
+	};
+
+	/* the restart key is verbatim in the block, so this could avoid the
+	   alloc for decoding the key */
+	struct slice rkey = {};
+	struct slice last_key = {};
+	byte unused_extra;
+	int n = decode_key(&rkey, &unused_extra, last_key, in);
+	if (n < 0) {
+		a->error = 1;
+		return -1;
+	}
+
+	{
+		int result = slice_compare(a->key, rkey);
+		free(slice_yield(&rkey));
+		return result;
+	}
+}
+
+void block_iter_copy_from(struct block_iter *dest, struct block_iter *src)
+{
+	dest->br = src->br;
+	dest->next_off = src->next_off;
+	slice_copy(&dest->last_key, src->last_key);
+}
+
+/* return < 0 for error, 0 for OK, > 0 for EOF. */
+int block_iter_next(struct block_iter *it, struct record rec)
+{
+	if (it->next_off >= it->br->block_len) {
+		return 1;
+	}
+
+	{
+		struct slice in = {
+			.buf = it->br->block.data + it->next_off,
+			.len = it->br->block_len - it->next_off,
+		};
+		struct slice start = in;
+		struct slice key = {};
+		byte extra;
+		int n = decode_key(&key, &extra, it->last_key, in);
+		if (n < 0) {
+			return -1;
+		}
+
+		in.buf += n;
+		in.len -= n;
+		n = record_decode(rec, key, extra, in, it->br->hash_size);
+		if (n < 0) {
+			return -1;
+		}
+		in.buf += n;
+		in.len -= n;
+
+		slice_copy(&it->last_key, key);
+		it->next_off += start.len - in.len;
+		free(slice_yield(&key));
+		return 0;
+	}
+}
+
+int block_reader_first_key(struct block_reader *br, struct slice *key)
+{
+	struct slice empty = {};
+	int off = br->header_off + 4;
+	struct slice in = {
+		.buf = br->block.data + off,
+		.len = br->block_len - off,
+	};
+
+	byte extra = 0;
+	int n = decode_key(key, &extra, empty, in);
+	if (n < 0) {
+		return n;
+	}
+	return 0;
+}
+
+int block_iter_seek(struct block_iter *it, struct slice want)
+{
+	return block_reader_seek(it->br, it, want);
+}
+
+void block_iter_close(struct block_iter *it)
+{
+	free(slice_yield(&it->last_key));
+}
+
+int block_reader_seek(struct block_reader *br, struct block_iter *it,
+		      struct slice want)
+{
+	struct restart_find_args args = {
+		.key = want,
+		.r = br,
+	};
+
+	int i = binsearch(br->restart_count, &restart_key_less, &args);
+	if (args.error) {
+		return -1;
+	}
+
+	it->br = br;
+	if (i > 0) {
+		i--;
+		it->next_off = block_reader_restart_offset(br, i);
+	} else {
+		it->next_off = br->header_off + 4;
+	}
+
+	{
+		struct record rec = new_record(block_reader_type(br));
+		struct slice key = {};
+		int result = 0;
+		int err = 0;
+		struct block_iter next = {};
+		while (true) {
+			block_iter_copy_from(&next, it);
+
+			err = block_iter_next(&next, rec);
+			if (err < 0) {
+				result = -1;
+				goto exit;
+			}
+
+			record_key(rec, &key);
+			if (err > 0 || slice_compare(key, want) >= 0) {
+				result = 0;
+				goto exit;
+			}
+
+			block_iter_copy_from(it, &next);
+		}
+
+	exit:
+		free(slice_yield(&key));
+		free(slice_yield(&next.last_key));
+		record_clear(rec);
+		free(record_yield(&rec));
+
+		return result;
+	}
+}
+
+void block_writer_reset(struct block_writer *bw)
+{
+	bw->restart_len = 0;
+	bw->last_key.len = 0;
+}
+
+void block_writer_clear(struct block_writer *bw)
+{
+	FREE_AND_NULL(bw->restarts);
+	free(slice_yield(&bw->last_key));
+	/* the block is not owned. */
+}
diff --git a/reftable/block.h b/reftable/block.h
new file mode 100644
index 00000000000..bb42588111f
--- /dev/null
+++ b/reftable/block.h
@@ -0,0 +1,71 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef BLOCK_H
+#define BLOCK_H
+
+#include "basics.h"
+#include "record.h"
+#include "reftable.h"
+
+struct block_writer {
+	byte *buf;
+	uint32_t block_size;
+	uint32_t header_off;
+	int restart_interval;
+	int hash_size;
+
+	uint32_t next;
+	uint32_t *restarts;
+	uint32_t restart_len;
+	uint32_t restart_cap;
+	struct slice last_key;
+	int entries;
+};
+
+void block_writer_init(struct block_writer *bw, byte typ, byte *buf,
+		       uint32_t block_size, uint32_t header_off, int hash_size);
+byte block_writer_type(struct block_writer *bw);
+int block_writer_add(struct block_writer *w, struct record rec);
+int block_writer_finish(struct block_writer *w);
+void block_writer_reset(struct block_writer *bw);
+void block_writer_clear(struct block_writer *bw);
+
+struct block_reader {
+	uint32_t header_off;
+	struct block block;
+	int hash_size;
+
+	/* size of the data, excluding restart data. */
+	uint32_t block_len;
+	byte *restart_bytes;
+	uint32_t full_block_size;
+	uint16_t restart_count;
+};
+
+struct block_iter {
+	struct block_reader *br;
+	struct slice last_key;
+	uint32_t next_off;
+};
+
+int block_reader_init(struct block_reader *br, struct block *bl,
+		      uint32_t header_off, uint32_t table_block_size,
+		      int hash_size);
+void block_reader_start(struct block_reader *br, struct block_iter *it);
+int block_reader_seek(struct block_reader *br, struct block_iter *it,
+		      struct slice want);
+byte block_reader_type(struct block_reader *r);
+int block_reader_first_key(struct block_reader *br, struct slice *key);
+
+void block_iter_copy_from(struct block_iter *dest, struct block_iter *src);
+int block_iter_next(struct block_iter *it, struct record rec);
+int block_iter_seek(struct block_iter *it, struct slice want);
+void block_iter_close(struct block_iter *it);
+
+#endif
diff --git a/reftable/blocksource.h b/reftable/blocksource.h
new file mode 100644
index 00000000000..f3ad3a4c229
--- /dev/null
+++ b/reftable/blocksource.h
@@ -0,0 +1,20 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef BLOCKSOURCE_H
+#define BLOCKSOURCE_H
+
+#include "reftable.h"
+
+uint64_t block_source_size(struct block_source source);
+int block_source_read_block(struct block_source source, struct block *dest,
+			    uint64_t off, uint32_t size);
+void block_source_return_block(struct block_source source, struct block *ret);
+void block_source_close(struct block_source source);
+
+#endif
diff --git a/reftable/bytes.c b/reftable/bytes.c
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/reftable/config.h b/reftable/config.h
new file mode 100644
index 00000000000..40a8c178f10
--- /dev/null
+++ b/reftable/config.h
@@ -0,0 +1 @@
+/* empty */
diff --git a/reftable/constants.h b/reftable/constants.h
new file mode 100644
index 00000000000..85399fa4758
--- /dev/null
+++ b/reftable/constants.h
@@ -0,0 +1,25 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef CONSTANTS_H
+#define CONSTANTS_H
+
+#define VERSION 1
+#define HEADER_SIZE 24
+#define FOOTER_SIZE 68
+
+#define BLOCK_TYPE_LOG 'g'
+#define BLOCK_TYPE_INDEX 'i'
+#define BLOCK_TYPE_REF 'r'
+#define BLOCK_TYPE_OBJ 'o'
+#define BLOCK_TYPE_ANY 0
+
+#define MAX_RESTARTS ((1 << 16) - 1)
+#define DEFAULT_BLOCK_SIZE 4096
+
+#endif
diff --git a/reftable/dump.c b/reftable/dump.c
new file mode 100644
index 00000000000..acabe18fbed
--- /dev/null
+++ b/reftable/dump.c
@@ -0,0 +1,97 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "system.h"
+
+#include "reftable.h"
+
+static int dump_table(const char *tablename)
+{
+	struct block_source src = {};
+	int err = block_source_from_file(&src, tablename);
+	if (err < 0) {
+		return err;
+	}
+
+	struct reader *r = NULL;
+	err = new_reader(&r, src, tablename);
+	if (err < 0) {
+		return err;
+	}
+
+	{
+		struct iterator it = {};
+		err = reader_seek_ref(r, &it, "");
+		if (err < 0) {
+			return err;
+		}
+
+		struct ref_record ref = {};
+		while (1) {
+			err = iterator_next_ref(it, &ref);
+			if (err > 0) {
+				break;
+			}
+			if (err < 0) {
+				return err;
+			}
+			ref_record_print(&ref, 20);
+		}
+		iterator_destroy(&it);
+		ref_record_clear(&ref);
+	}
+
+	{
+		struct iterator it = {};
+		err = reader_seek_log(r, &it, "");
+		if (err < 0) {
+			return err;
+		}
+		struct log_record log = {};
+		while (1) {
+			err = iterator_next_log(it, &log);
+			if (err > 0) {
+				break;
+			}
+			if (err < 0) {
+				return err;
+			}
+			log_record_print(&log, 20);
+		}
+		iterator_destroy(&it);
+		log_record_clear(&log);
+	}
+	return 0;
+}
+
+int main(int argc, char *argv[])
+{
+	int opt;
+	const char *table = NULL;
+	while ((opt = getopt(argc, argv, "t:")) != -1) {
+		switch (opt) {
+		case 't':
+			table = strdup(optarg);
+			break;
+		case '?':
+			printf("usage: %s [-table tablefile]\n", argv[0]);
+			return 2;
+			break;
+		}
+	}
+
+	if (table != NULL) {
+		int err = dump_table(table);
+		if (err < 0) {
+			fprintf(stderr, "%s: %s: %s\n", argv[0], table,
+				error_str(err));
+			return 1;
+		}
+	}
+	return 0;
+}
diff --git a/reftable/file.c b/reftable/file.c
new file mode 100644
index 00000000000..b2ea90bf941
--- /dev/null
+++ b/reftable/file.c
@@ -0,0 +1,97 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "system.h"
+
+#include "block.h"
+#include "iter.h"
+#include "record.h"
+#include "reftable.h"
+#include "tree.h"
+
+struct file_block_source {
+	int fd;
+	uint64_t size;
+};
+
+static uint64_t file_size(void *b)
+{
+	return ((struct file_block_source *)b)->size;
+}
+
+static void file_return_block(void *b, struct block *dest)
+{
+	memset(dest->data, 0xff, dest->len);
+	free(dest->data);
+}
+
+static void file_close(void *b)
+{
+	int fd = ((struct file_block_source *)b)->fd;
+	if (fd > 0) {
+		close(fd);
+		((struct file_block_source *)b)->fd = 0;
+	}
+
+	free(b);
+}
+
+static int file_read_block(void *v, struct block *dest, uint64_t off,
+			   uint32_t size)
+{
+	struct file_block_source *b = (struct file_block_source *)v;
+	assert(off + size <= b->size);
+	dest->data = malloc(size);
+	if (pread(b->fd, dest->data, size, off) != size) {
+		return -1;
+	}
+	dest->len = size;
+	return size;
+}
+
+struct block_source_vtable file_vtable = {
+	.size = &file_size,
+	.read_block = &file_read_block,
+	.return_block = &file_return_block,
+	.close = &file_close,
+};
+
+int block_source_from_file(struct block_source *bs, const char *name)
+{
+	struct stat st = {};
+	int err = 0;
+	int fd = open(name, O_RDONLY);
+	if (fd < 0) {
+		if (errno == ENOENT) {
+			return NOT_EXIST_ERROR;
+		}
+		return -1;
+	}
+
+	err = fstat(fd, &st);
+	if (err < 0) {
+		return -1;
+	}
+
+	{
+		struct file_block_source *p =
+			calloc(sizeof(struct file_block_source), 1);
+		p->size = st.st_size;
+		p->fd = fd;
+
+		bs->ops = &file_vtable;
+		bs->arg = p;
+	}
+	return 0;
+}
+
+int fd_writer(void *arg, byte *data, int sz)
+{
+	int *fdp = (int *)arg;
+	return write(*fdp, data, sz);
+}
diff --git a/reftable/iter.c b/reftable/iter.c
new file mode 100644
index 00000000000..c06c8913662
--- /dev/null
+++ b/reftable/iter.c
@@ -0,0 +1,229 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "iter.h"
+
+#include "system.h"
+
+#include "block.h"
+#include "constants.h"
+#include "reader.h"
+#include "reftable.h"
+
+bool iterator_is_null(struct iterator it)
+{
+	return it.ops == NULL;
+}
+
+static int empty_iterator_next(void *arg, struct record rec)
+{
+	return 1;
+}
+
+static void empty_iterator_close(void *arg)
+{
+}
+
+struct iterator_vtable empty_vtable = {
+	.next = &empty_iterator_next,
+	.close = &empty_iterator_close,
+};
+
+void iterator_set_empty(struct iterator *it)
+{
+	it->iter_arg = NULL;
+	it->ops = &empty_vtable;
+}
+
+int iterator_next(struct iterator it, struct record rec)
+{
+	return it.ops->next(it.iter_arg, rec);
+}
+
+void iterator_destroy(struct iterator *it)
+{
+	if (it->ops == NULL) {
+		return;
+	}
+	it->ops->close(it->iter_arg);
+	it->ops = NULL;
+	FREE_AND_NULL(it->iter_arg);
+}
+
+int iterator_next_ref(struct iterator it, struct ref_record *ref)
+{
+	struct record rec = {};
+	record_from_ref(&rec, ref);
+	return iterator_next(it, rec);
+}
+
+int iterator_next_log(struct iterator it, struct log_record *log)
+{
+	struct record rec = {};
+	record_from_log(&rec, log);
+	return iterator_next(it, rec);
+}
+
+static void filtering_ref_iterator_close(void *iter_arg)
+{
+	struct filtering_ref_iterator *fri =
+		(struct filtering_ref_iterator *)iter_arg;
+	free(slice_yield(&fri->oid));
+	iterator_destroy(&fri->it);
+}
+
+static int filtering_ref_iterator_next(void *iter_arg, struct record rec)
+{
+	struct filtering_ref_iterator *fri =
+		(struct filtering_ref_iterator *)iter_arg;
+	struct ref_record *ref = (struct ref_record *)rec.data;
+
+	while (true) {
+		int err = iterator_next_ref(fri->it, ref);
+		if (err != 0) {
+			return err;
+		}
+
+		if (fri->double_check) {
+			struct iterator it = {};
+
+			int err = reader_seek_ref(fri->r, &it, ref->ref_name);
+			if (err == 0) {
+				err = iterator_next_ref(it, ref);
+			}
+
+			iterator_destroy(&it);
+
+			if (err < 0) {
+				return err;
+			}
+
+			if (err > 0) {
+				continue;
+			}
+		}
+
+		if ((ref->target_value != NULL &&
+		     !memcmp(fri->oid.buf, ref->target_value, fri->oid.len)) ||
+		    (ref->value != NULL &&
+		     !memcmp(fri->oid.buf, ref->value, fri->oid.len))) {
+			return 0;
+		}
+	}
+}
+
+struct iterator_vtable filtering_ref_iterator_vtable = {
+	.next = &filtering_ref_iterator_next,
+	.close = &filtering_ref_iterator_close,
+};
+
+void iterator_from_filtering_ref_iterator(struct iterator *it,
+					  struct filtering_ref_iterator *fri)
+{
+	it->iter_arg = fri;
+	it->ops = &filtering_ref_iterator_vtable;
+}
+
+static void indexed_table_ref_iter_close(void *p)
+{
+	struct indexed_table_ref_iter *it = (struct indexed_table_ref_iter *)p;
+	block_iter_close(&it->cur);
+	reader_return_block(it->r, &it->block_reader.block);
+	free(slice_yield(&it->oid));
+}
+
+static int indexed_table_ref_iter_next_block(struct indexed_table_ref_iter *it)
+{
+	if (it->offset_idx == it->offset_len) {
+		it->finished = true;
+		return 1;
+	}
+
+	reader_return_block(it->r, &it->block_reader.block);
+
+	{
+		uint64_t off = it->offsets[it->offset_idx++];
+		int err = reader_init_block_reader(it->r, &it->block_reader,
+						   off, BLOCK_TYPE_REF);
+		if (err < 0) {
+			return err;
+		}
+		if (err > 0) {
+			/* indexed block does not exist. */
+			return FORMAT_ERROR;
+		}
+	}
+	block_reader_start(&it->block_reader, &it->cur);
+	return 0;
+}
+
+static int indexed_table_ref_iter_next(void *p, struct record rec)
+{
+	struct indexed_table_ref_iter *it = (struct indexed_table_ref_iter *)p;
+	struct ref_record *ref = (struct ref_record *)rec.data;
+
+	while (true) {
+		int err = block_iter_next(&it->cur, rec);
+		if (err < 0) {
+			return err;
+		}
+
+		if (err > 0) {
+			err = indexed_table_ref_iter_next_block(it);
+			if (err < 0) {
+				return err;
+			}
+
+			if (it->finished) {
+				return 1;
+			}
+			continue;
+		}
+
+		if (!memcmp(it->oid.buf, ref->target_value, it->oid.len) ||
+		    !memcmp(it->oid.buf, ref->value, it->oid.len)) {
+			return 0;
+		}
+	}
+}
+
+int new_indexed_table_ref_iter(struct indexed_table_ref_iter **dest,
+			       struct reader *r, byte *oid, int oid_len,
+			       uint64_t *offsets, int offset_len)
+{
+	struct indexed_table_ref_iter *itr =
+		calloc(sizeof(struct indexed_table_ref_iter), 1);
+	int err = 0;
+
+	itr->r = r;
+	slice_resize(&itr->oid, oid_len);
+	memcpy(itr->oid.buf, oid, oid_len);
+
+	itr->offsets = offsets;
+	itr->offset_len = offset_len;
+
+	err = indexed_table_ref_iter_next_block(itr);
+	if (err < 0) {
+		free(itr);
+	} else {
+		*dest = itr;
+	}
+	return err;
+}
+
+struct iterator_vtable indexed_table_ref_iter_vtable = {
+	.next = &indexed_table_ref_iter_next,
+	.close = &indexed_table_ref_iter_close,
+};
+
+void iterator_from_indexed_table_ref_iter(struct iterator *it,
+					  struct indexed_table_ref_iter *itr)
+{
+	it->iter_arg = itr;
+	it->ops = &indexed_table_ref_iter_vtable;
+}
diff --git a/reftable/iter.h b/reftable/iter.h
new file mode 100644
index 00000000000..f497f2a27eb
--- /dev/null
+++ b/reftable/iter.h
@@ -0,0 +1,56 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef ITER_H
+#define ITER_H
+
+#include "block.h"
+#include "record.h"
+#include "slice.h"
+
+struct iterator_vtable {
+	int (*next)(void *iter_arg, struct record rec);
+	void (*close)(void *iter_arg);
+};
+
+void iterator_set_empty(struct iterator *it);
+int iterator_next(struct iterator it, struct record rec);
+bool iterator_is_null(struct iterator it);
+
+struct filtering_ref_iterator {
+	struct reader *r;
+	struct slice oid;
+	bool double_check;
+	struct iterator it;
+};
+
+void iterator_from_filtering_ref_iterator(struct iterator *,
+					  struct filtering_ref_iterator *);
+
+struct indexed_table_ref_iter {
+	struct reader *r;
+	struct slice oid;
+
+	/* mutable */
+	uint64_t *offsets;
+
+	/* Points to the next offset to read. */
+	int offset_idx;
+	int offset_len;
+	struct block_reader block_reader;
+	struct block_iter cur;
+	bool finished;
+};
+
+void iterator_from_indexed_table_ref_iter(struct iterator *it,
+					  struct indexed_table_ref_iter *itr);
+int new_indexed_table_ref_iter(struct indexed_table_ref_iter **dest,
+			       struct reader *r, byte *oid, int oid_len,
+			       uint64_t *offsets, int offset_len);
+
+#endif
diff --git a/reftable/merged.c b/reftable/merged.c
new file mode 100644
index 00000000000..36ea9da9220
--- /dev/null
+++ b/reftable/merged.c
@@ -0,0 +1,290 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "merged.h"
+
+#include "system.h"
+
+#include "constants.h"
+#include "iter.h"
+#include "pq.h"
+#include "reader.h"
+
+static int merged_iter_init(struct merged_iter *mi)
+{
+	int i = 0;
+	for (i = 0; i < mi->stack_len; i++) {
+		struct record rec = new_record(mi->typ);
+		int err = iterator_next(mi->stack[i], rec);
+		if (err < 0) {
+			return err;
+		}
+
+		if (err > 0) {
+			iterator_destroy(&mi->stack[i]);
+			record_clear(rec);
+			free(record_yield(&rec));
+		} else {
+			struct pq_entry e = {
+				.rec = rec,
+				.index = i,
+			};
+			merged_iter_pqueue_add(&mi->pq, e);
+		}
+	}
+
+	return 0;
+}
+
+static void merged_iter_close(void *p)
+{
+	struct merged_iter *mi = (struct merged_iter *)p;
+	int i = 0;
+	merged_iter_pqueue_clear(&mi->pq);
+	for (i = 0; i < mi->stack_len; i++) {
+		iterator_destroy(&mi->stack[i]);
+	}
+	free(mi->stack);
+}
+
+static int merged_iter_advance_subiter(struct merged_iter *mi, int idx)
+{
+	if (iterator_is_null(mi->stack[idx])) {
+		return 0;
+	}
+
+	{
+		struct record rec = new_record(mi->typ);
+		struct pq_entry e = {
+			.rec = rec,
+			.index = idx,
+		};
+		int err = iterator_next(mi->stack[idx], rec);
+		if (err < 0) {
+			return err;
+		}
+
+		if (err > 0) {
+			iterator_destroy(&mi->stack[idx]);
+			record_clear(rec);
+			free(record_yield(&rec));
+			return 0;
+		}
+
+		merged_iter_pqueue_add(&mi->pq, e);
+	}
+	return 0;
+}
+
+static int merged_iter_next(struct merged_iter *mi, struct record rec)
+{
+	struct slice entry_key = {};
+	struct pq_entry entry = merged_iter_pqueue_remove(&mi->pq);
+	int err = merged_iter_advance_subiter(mi, entry.index);
+	if (err < 0) {
+		return err;
+	}
+
+	record_key(entry.rec, &entry_key);
+	while (!merged_iter_pqueue_is_empty(mi->pq)) {
+		struct pq_entry top = merged_iter_pqueue_top(mi->pq);
+		struct slice k = {};
+		int err = 0, cmp = 0;
+
+		record_key(top.rec, &k);
+
+		cmp = slice_compare(k, entry_key);
+		free(slice_yield(&k));
+
+		if (cmp > 0) {
+			break;
+		}
+
+		merged_iter_pqueue_remove(&mi->pq);
+		err = merged_iter_advance_subiter(mi, top.index);
+		if (err < 0) {
+			return err;
+		}
+		record_clear(top.rec);
+		free(record_yield(&top.rec));
+	}
+
+	record_copy_from(rec, entry.rec, mi->hash_size);
+	record_clear(entry.rec);
+	free(record_yield(&entry.rec));
+	free(slice_yield(&entry_key));
+	return 0;
+}
+
+static int merged_iter_next_void(void *p, struct record rec)
+{
+	struct merged_iter *mi = (struct merged_iter *)p;
+	if (merged_iter_pqueue_is_empty(mi->pq)) {
+		return 1;
+	}
+
+	return merged_iter_next(mi, rec);
+}
+
+struct iterator_vtable merged_iter_vtable = {
+	.next = &merged_iter_next_void,
+	.close = &merged_iter_close,
+};
+
+static void iterator_from_merged_iter(struct iterator *it,
+				      struct merged_iter *mi)
+{
+	it->iter_arg = mi;
+	it->ops = &merged_iter_vtable;
+}
+
+int new_merged_table(struct merged_table **dest, struct reader **stack, int n,
+		     int hash_size)
+{
+	uint64_t last_max = 0;
+	uint64_t first_min = 0;
+	int i = 0;
+	for (i = 0; i < n; i++) {
+		struct reader *r = stack[i];
+		if (reader_hash_size(r) != hash_size) {
+			return FORMAT_ERROR;
+		}
+		if (i > 0 && last_max >= reader_min_update_index(r)) {
+			return FORMAT_ERROR;
+		}
+		if (i == 0) {
+			first_min = reader_min_update_index(r);
+		}
+
+		last_max = reader_max_update_index(r);
+	}
+
+	{
+		struct merged_table m = {
+			.stack = stack,
+			.stack_len = n,
+			.min = first_min,
+			.max = last_max,
+			.hash_size = hash_size,
+		};
+
+		*dest = calloc(sizeof(struct merged_table), 1);
+		**dest = m;
+	}
+	return 0;
+}
+
+void merged_table_close(struct merged_table *mt)
+{
+	int i = 0;
+	for (i = 0; i < mt->stack_len; i++) {
+		reader_free(mt->stack[i]);
+	}
+	FREE_AND_NULL(mt->stack);
+	mt->stack_len = 0;
+}
+
+/* clears the list of subtable, without affecting the readers themselves. */
+void merged_table_clear(struct merged_table *mt)
+{
+	FREE_AND_NULL(mt->stack);
+	mt->stack_len = 0;
+}
+
+void merged_table_free(struct merged_table *mt)
+{
+	if (mt == NULL) {
+		return;
+	}
+	merged_table_clear(mt);
+	free(mt);
+}
+
+uint64_t merged_max_update_index(struct merged_table *mt)
+{
+	return mt->max;
+}
+
+uint64_t merged_min_update_index(struct merged_table *mt)
+{
+	return mt->min;
+}
+
+static int merged_table_seek_record(struct merged_table *mt,
+				    struct iterator *it, struct record rec)
+{
+	struct iterator *iters = calloc(sizeof(struct iterator), mt->stack_len);
+	struct merged_iter merged = {
+		.stack = iters,
+		.typ = record_type(rec),
+		.hash_size = mt->hash_size,
+	};
+	int n = 0;
+	int err = 0;
+	int i = 0;
+	for (i = 0; i < mt->stack_len && err == 0; i++) {
+		int e = reader_seek(mt->stack[i], &iters[n], rec);
+		if (e < 0) {
+			err = e;
+		}
+		if (e == 0) {
+			n++;
+		}
+	}
+	if (err < 0) {
+		int i = 0;
+		for (i = 0; i < n; i++) {
+			iterator_destroy(&iters[i]);
+		}
+		free(iters);
+		return err;
+	}
+
+	merged.stack_len = n, err = merged_iter_init(&merged);
+	if (err < 0) {
+		merged_iter_close(&merged);
+		return err;
+	}
+
+	{
+		struct merged_iter *p = malloc(sizeof(struct merged_iter));
+		*p = merged;
+		iterator_from_merged_iter(it, p);
+	}
+	return 0;
+}
+
+int merged_table_seek_ref(struct merged_table *mt, struct iterator *it,
+			  const char *name)
+{
+	struct ref_record ref = {
+		.ref_name = (char *)name,
+	};
+	struct record rec = {};
+	record_from_ref(&rec, &ref);
+	return merged_table_seek_record(mt, it, rec);
+}
+
+int merged_table_seek_log_at(struct merged_table *mt, struct iterator *it,
+			     const char *name, uint64_t update_index)
+{
+	struct log_record log = {
+		.ref_name = (char *)name,
+		.update_index = update_index,
+	};
+	struct record rec = {};
+	record_from_log(&rec, &log);
+	return merged_table_seek_record(mt, it, rec);
+}
+
+int merged_table_seek_log(struct merged_table *mt, struct iterator *it,
+			  const char *name)
+{
+	uint64_t max = ~((uint64_t)0);
+	return merged_table_seek_log_at(mt, it, name, max);
+}
diff --git a/reftable/merged.h b/reftable/merged.h
new file mode 100644
index 00000000000..b8d3572e26e
--- /dev/null
+++ b/reftable/merged.h
@@ -0,0 +1,34 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef MERGED_H
+#define MERGED_H
+
+#include "pq.h"
+#include "reftable.h"
+
+struct merged_table {
+	struct reader **stack;
+	int stack_len;
+	int hash_size;
+
+	uint64_t min;
+	uint64_t max;
+};
+
+struct merged_iter {
+	struct iterator *stack;
+	int hash_size;
+	int stack_len;
+	byte typ;
+	struct merged_iter_pqueue pq;
+} merged_iter;
+
+void merged_table_clear(struct merged_table *mt);
+
+#endif
diff --git a/reftable/pq.c b/reftable/pq.c
new file mode 100644
index 00000000000..a1aff7c98c3
--- /dev/null
+++ b/reftable/pq.c
@@ -0,0 +1,114 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "pq.h"
+
+#include "system.h"
+
+int pq_less(struct pq_entry a, struct pq_entry b)
+{
+	struct slice ak = {};
+	struct slice bk = {};
+	int cmp = 0;
+	record_key(a.rec, &ak);
+	record_key(b.rec, &bk);
+
+	cmp = slice_compare(ak, bk);
+
+	free(slice_yield(&ak));
+	free(slice_yield(&bk));
+
+	if (cmp == 0) {
+		return a.index > b.index;
+	}
+
+	return cmp < 0;
+}
+
+struct pq_entry merged_iter_pqueue_top(struct merged_iter_pqueue pq)
+{
+	return pq.heap[0];
+}
+
+bool merged_iter_pqueue_is_empty(struct merged_iter_pqueue pq)
+{
+	return pq.len == 0;
+}
+
+void merged_iter_pqueue_check(struct merged_iter_pqueue pq)
+{
+	int i = 0;
+	for (i = 1; i < pq.len; i++) {
+		int parent = (i - 1) / 2;
+
+		assert(pq_less(pq.heap[parent], pq.heap[i]));
+	}
+}
+
+struct pq_entry merged_iter_pqueue_remove(struct merged_iter_pqueue *pq)
+{
+	int i = 0;
+	struct pq_entry e = pq->heap[0];
+	pq->heap[0] = pq->heap[pq->len - 1];
+	pq->len--;
+
+	i = 0;
+	while (i < pq->len) {
+		int min = i;
+		int j = 2 * i + 1;
+		int k = 2 * i + 2;
+		if (j < pq->len && pq_less(pq->heap[j], pq->heap[i])) {
+			min = j;
+		}
+		if (k < pq->len && pq_less(pq->heap[k], pq->heap[min])) {
+			min = k;
+		}
+
+		if (min == i) {
+			break;
+		}
+
+		SWAP(pq->heap[i], pq->heap[min]);
+		i = min;
+	}
+
+	return e;
+}
+
+void merged_iter_pqueue_add(struct merged_iter_pqueue *pq, struct pq_entry e)
+{
+	int i = 0;
+	if (pq->len == pq->cap) {
+		pq->cap = 2 * pq->cap + 1;
+		pq->heap = realloc(pq->heap, pq->cap * sizeof(struct pq_entry));
+	}
+
+	pq->heap[pq->len++] = e;
+	i = pq->len - 1;
+	while (i > 0) {
+		int j = (i - 1) / 2;
+		if (pq_less(pq->heap[j], pq->heap[i])) {
+			break;
+		}
+
+		SWAP(pq->heap[j], pq->heap[i]);
+
+		i = j;
+	}
+}
+
+void merged_iter_pqueue_clear(struct merged_iter_pqueue *pq)
+{
+	int i = 0;
+	for (i = 0; i < pq->len; i++) {
+		record_clear(pq->heap[i].rec);
+		free(record_yield(&pq->heap[i].rec));
+	}
+	FREE_AND_NULL(pq->heap);
+	pq->len = pq->cap = 0;
+}
diff --git a/reftable/pq.h b/reftable/pq.h
new file mode 100644
index 00000000000..5f7018979dd
--- /dev/null
+++ b/reftable/pq.h
@@ -0,0 +1,34 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef PQ_H
+#define PQ_H
+
+#include "record.h"
+
+struct pq_entry {
+	struct record rec;
+	int index;
+};
+
+int pq_less(struct pq_entry a, struct pq_entry b);
+
+struct merged_iter_pqueue {
+	struct pq_entry *heap;
+	int len;
+	int cap;
+};
+
+struct pq_entry merged_iter_pqueue_top(struct merged_iter_pqueue pq);
+bool merged_iter_pqueue_is_empty(struct merged_iter_pqueue pq);
+void merged_iter_pqueue_check(struct merged_iter_pqueue pq);
+struct pq_entry merged_iter_pqueue_remove(struct merged_iter_pqueue *pq);
+void merged_iter_pqueue_add(struct merged_iter_pqueue *pq, struct pq_entry e);
+void merged_iter_pqueue_clear(struct merged_iter_pqueue *pq);
+
+#endif
diff --git a/reftable/reader.c b/reftable/reader.c
new file mode 100644
index 00000000000..f9a52992ab6
--- /dev/null
+++ b/reftable/reader.c
@@ -0,0 +1,718 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "reader.h"
+
+#include "system.h"
+
+#include "block.h"
+#include "constants.h"
+#include "iter.h"
+#include "record.h"
+#include "reftable.h"
+#include "tree.h"
+
+uint64_t block_source_size(struct block_source source)
+{
+	return source.ops->size(source.arg);
+}
+
+int block_source_read_block(struct block_source source, struct block *dest,
+			    uint64_t off, uint32_t size)
+{
+	int result = source.ops->read_block(source.arg, dest, off, size);
+	dest->source = source;
+	return result;
+}
+
+void block_source_return_block(struct block_source source, struct block *blockp)
+{
+	source.ops->return_block(source.arg, blockp);
+	blockp->data = NULL;
+	blockp->len = 0;
+	blockp->source.ops = NULL;
+	blockp->source.arg = NULL;
+}
+
+void block_source_close(struct block_source *source)
+{
+	if (source->ops == NULL) {
+		return;
+	}
+
+	source->ops->close(source->arg);
+	source->ops = NULL;
+}
+
+static struct reader_offsets *reader_offsets_for(struct reader *r, byte typ)
+{
+	switch (typ) {
+	case BLOCK_TYPE_REF:
+		return &r->ref_offsets;
+	case BLOCK_TYPE_LOG:
+		return &r->log_offsets;
+	case BLOCK_TYPE_OBJ:
+		return &r->obj_offsets;
+	}
+	abort();
+}
+
+static int reader_get_block(struct reader *r, struct block *dest, uint64_t off,
+			    uint32_t sz)
+{
+	if (off >= r->size) {
+		return 0;
+	}
+
+	if (off + sz > r->size) {
+		sz = r->size - off;
+	}
+
+	return block_source_read_block(r->source, dest, off, sz);
+}
+
+void reader_return_block(struct reader *r, struct block *p)
+{
+	block_source_return_block(r->source, p);
+}
+
+int reader_hash_size(struct reader *r)
+{
+	return r->hash_size;
+}
+
+const char *reader_name(struct reader *r)
+{
+	return r->name;
+}
+
+static int parse_footer(struct reader *r, byte *footer, byte *header)
+{
+	byte *f = footer;
+	int err = 0;
+	if (memcmp(f, "REFT", 4)) {
+		err = FORMAT_ERROR;
+		goto exit;
+	}
+	f += 4;
+
+	if (memcmp(footer, header, HEADER_SIZE)) {
+		err = FORMAT_ERROR;
+		goto exit;
+	}
+
+	r->hash_size = SHA1_SIZE;
+	{
+		byte version = *f++;
+		if (version == 2) {
+			r->hash_size = SHA256_SIZE;
+			version = 1;
+		}
+		if (version != 1) {
+			err = FORMAT_ERROR;
+			goto exit;
+		}
+	}
+
+	r->block_size = get_u24(f);
+
+	f += 3;
+	r->min_update_index = get_u64(f);
+	f += 8;
+	r->max_update_index = get_u64(f);
+	f += 8;
+
+	r->ref_offsets.index_offset = get_u64(f);
+	f += 8;
+
+	r->obj_offsets.offset = get_u64(f);
+	f += 8;
+
+	r->object_id_len = r->obj_offsets.offset & ((1 << 5) - 1);
+	r->obj_offsets.offset >>= 5;
+
+	r->obj_offsets.index_offset = get_u64(f);
+	f += 8;
+	r->log_offsets.offset = get_u64(f);
+	f += 8;
+	r->log_offsets.index_offset = get_u64(f);
+	f += 8;
+
+	{
+		uint32_t computed_crc = crc32(0, footer, f - footer);
+		uint32_t file_crc = get_u32(f);
+		f += 4;
+		if (computed_crc != file_crc) {
+			err = FORMAT_ERROR;
+			goto exit;
+		}
+	}
+
+	{
+		byte first_block_typ = header[HEADER_SIZE];
+		r->ref_offsets.present = (first_block_typ == BLOCK_TYPE_REF);
+		r->ref_offsets.offset = 0;
+		r->log_offsets.present = (first_block_typ == BLOCK_TYPE_LOG ||
+					  r->log_offsets.offset > 0);
+		r->obj_offsets.present = r->obj_offsets.offset > 0;
+	}
+	err = 0;
+exit:
+	return err;
+}
+
+int init_reader(struct reader *r, struct block_source source, const char *name)
+{
+	struct block footer = {};
+	struct block header = {};
+	int err = 0;
+
+	memset(r, 0, sizeof(struct reader));
+	r->size = block_source_size(source) - FOOTER_SIZE;
+	r->source = source;
+	r->name = strdup(name);
+	r->hash_size = 0;
+
+	err = block_source_read_block(source, &footer, r->size, FOOTER_SIZE);
+	if (err != FOOTER_SIZE) {
+		err = IO_ERROR;
+		goto exit;
+	}
+
+	/* Need +1 to read type of first block. */
+	err = reader_get_block(r, &header, 0, HEADER_SIZE + 1);
+	if (err != HEADER_SIZE + 1) {
+		err = IO_ERROR;
+		goto exit;
+	}
+
+	err = parse_footer(r, footer.data, header.data);
+exit:
+	block_source_return_block(r->source, &footer);
+	block_source_return_block(r->source, &header);
+	return err;
+}
+
+struct table_iter {
+	struct reader *r;
+	byte typ;
+	uint64_t block_off;
+	struct block_iter bi;
+	bool finished;
+};
+
+static void table_iter_copy_from(struct table_iter *dest,
+				 struct table_iter *src)
+{
+	dest->r = src->r;
+	dest->typ = src->typ;
+	dest->block_off = src->block_off;
+	dest->finished = src->finished;
+	block_iter_copy_from(&dest->bi, &src->bi);
+}
+
+static int table_iter_next_in_block(struct table_iter *ti, struct record rec)
+{
+	int res = block_iter_next(&ti->bi, rec);
+	if (res == 0 && record_type(rec) == BLOCK_TYPE_REF) {
+		((struct ref_record *)rec.data)->update_index +=
+			ti->r->min_update_index;
+	}
+
+	return res;
+}
+
+static void table_iter_block_done(struct table_iter *ti)
+{
+	if (ti->bi.br == NULL) {
+		return;
+	}
+	reader_return_block(ti->r, &ti->bi.br->block);
+	FREE_AND_NULL(ti->bi.br);
+
+	ti->bi.last_key.len = 0;
+	ti->bi.next_off = 0;
+}
+
+static int32_t extract_block_size(byte *data, byte *typ, uint64_t off)
+{
+	int32_t result = 0;
+
+	if (off == 0) {
+		data += 24;
+	}
+
+	*typ = data[0];
+	if (is_block_type(*typ)) {
+		result = get_u24(data + 1);
+	}
+	return result;
+}
+
+int reader_init_block_reader(struct reader *r, struct block_reader *br,
+			     uint64_t next_off, byte want_typ)
+{
+	int32_t guess_block_size = r->block_size ? r->block_size :
+						   DEFAULT_BLOCK_SIZE;
+	struct block block = {};
+	byte block_typ = 0;
+	int err = 0;
+	uint32_t header_off = next_off ? 0 : HEADER_SIZE;
+	int32_t block_size = 0;
+
+	if (next_off >= r->size) {
+		return 1;
+	}
+
+	err = reader_get_block(r, &block, next_off, guess_block_size);
+	if (err < 0) {
+		return err;
+	}
+
+	block_size = extract_block_size(block.data, &block_typ, next_off);
+	if (block_size < 0) {
+		return block_size;
+	}
+
+	if (want_typ != BLOCK_TYPE_ANY && block_typ != want_typ) {
+		reader_return_block(r, &block);
+		return 1;
+	}
+
+	if (block_size > guess_block_size) {
+		reader_return_block(r, &block);
+		err = reader_get_block(r, &block, next_off, block_size);
+		if (err < 0) {
+			return err;
+		}
+	}
+
+	return block_reader_init(br, &block, header_off, r->block_size,
+				 r->hash_size);
+}
+
+static int table_iter_next_block(struct table_iter *dest,
+				 struct table_iter *src)
+{
+	uint64_t next_block_off = src->block_off + src->bi.br->full_block_size;
+	struct block_reader br = {};
+	int err = 0;
+
+	dest->r = src->r;
+	dest->typ = src->typ;
+	dest->block_off = next_block_off;
+
+	err = reader_init_block_reader(src->r, &br, next_block_off, src->typ);
+	if (err > 0) {
+		dest->finished = true;
+		return 1;
+	}
+	if (err != 0) {
+		return err;
+	}
+
+	{
+		struct block_reader *brp = malloc(sizeof(struct block_reader));
+		*brp = br;
+
+		dest->finished = false;
+		block_reader_start(brp, &dest->bi);
+	}
+	return 0;
+}
+
+static int table_iter_next(struct table_iter *ti, struct record rec)
+{
+	if (record_type(rec) != ti->typ) {
+		return API_ERROR;
+	}
+
+	while (true) {
+		struct table_iter next = {};
+		int err = 0;
+		if (ti->finished) {
+			return 1;
+		}
+
+		err = table_iter_next_in_block(ti, rec);
+		if (err <= 0) {
+			return err;
+		}
+
+		err = table_iter_next_block(&next, ti);
+		if (err != 0) {
+			ti->finished = true;
+		}
+		table_iter_block_done(ti);
+		if (err != 0) {
+			return err;
+		}
+		table_iter_copy_from(ti, &next);
+		block_iter_close(&next.bi);
+	}
+}
+
+static int table_iter_next_void(void *ti, struct record rec)
+{
+	return table_iter_next((struct table_iter *)ti, rec);
+}
+
+static void table_iter_close(void *p)
+{
+	struct table_iter *ti = (struct table_iter *)p;
+	table_iter_block_done(ti);
+	block_iter_close(&ti->bi);
+}
+
+struct iterator_vtable table_iter_vtable = {
+	.next = &table_iter_next_void,
+	.close = &table_iter_close,
+};
+
+static void iterator_from_table_iter(struct iterator *it, struct table_iter *ti)
+{
+	it->iter_arg = ti;
+	it->ops = &table_iter_vtable;
+}
+
+static int reader_table_iter_at(struct reader *r, struct table_iter *ti,
+				uint64_t off, byte typ)
+{
+	struct block_reader br = {};
+	struct block_reader *brp = NULL;
+
+	int err = reader_init_block_reader(r, &br, off, typ);
+	if (err != 0) {
+		return err;
+	}
+
+	brp = malloc(sizeof(struct block_reader));
+	*brp = br;
+	ti->r = r;
+	ti->typ = block_reader_type(brp);
+	ti->block_off = off;
+	block_reader_start(brp, &ti->bi);
+	return 0;
+}
+
+static int reader_start(struct reader *r, struct table_iter *ti, byte typ,
+			bool index)
+{
+	struct reader_offsets *offs = reader_offsets_for(r, typ);
+	uint64_t off = offs->offset;
+	if (index) {
+		off = offs->index_offset;
+		if (off == 0) {
+			return 1;
+		}
+		typ = BLOCK_TYPE_INDEX;
+	}
+
+	return reader_table_iter_at(r, ti, off, typ);
+}
+
+static int reader_seek_linear(struct reader *r, struct table_iter *ti,
+			      struct record want)
+{
+	struct record rec = new_record(record_type(want));
+	struct slice want_key = {};
+	struct slice got_key = {};
+	struct table_iter next = {};
+	int err = -1;
+	record_key(want, &want_key);
+
+	while (true) {
+		err = table_iter_next_block(&next, ti);
+		if (err < 0) {
+			goto exit;
+		}
+
+		if (err > 0) {
+			break;
+		}
+
+		err = block_reader_first_key(next.bi.br, &got_key);
+		if (err < 0) {
+			goto exit;
+		}
+		{
+			int cmp = slice_compare(got_key, want_key);
+			if (cmp > 0) {
+				table_iter_block_done(&next);
+				break;
+			}
+		}
+
+		table_iter_block_done(ti);
+		table_iter_copy_from(ti, &next);
+	}
+
+	err = block_iter_seek(&ti->bi, want_key);
+	if (err < 0) {
+		goto exit;
+	}
+	err = 0;
+
+exit:
+	block_iter_close(&next.bi);
+	record_clear(rec);
+	free(record_yield(&rec));
+	free(slice_yield(&want_key));
+	free(slice_yield(&got_key));
+	return err;
+}
+
+static int reader_seek_indexed(struct reader *r, struct iterator *it,
+			       struct record rec)
+{
+	struct index_record want_index = {};
+	struct record want_index_rec = {};
+	struct index_record index_result = {};
+	struct record index_result_rec = {};
+	struct table_iter index_iter = {};
+	struct table_iter next = {};
+	int err = 0;
+
+	record_key(rec, &want_index.last_key);
+	record_from_index(&want_index_rec, &want_index);
+	record_from_index(&index_result_rec, &index_result);
+
+	err = reader_start(r, &index_iter, record_type(rec), true);
+	if (err < 0) {
+		goto exit;
+	}
+
+	err = reader_seek_linear(r, &index_iter, want_index_rec);
+	while (true) {
+		err = table_iter_next(&index_iter, index_result_rec);
+		table_iter_block_done(&index_iter);
+		if (err != 0) {
+			goto exit;
+		}
+
+		err = reader_table_iter_at(r, &next, index_result.offset, 0);
+		if (err != 0) {
+			goto exit;
+		}
+
+		err = block_iter_seek(&next.bi, want_index.last_key);
+		if (err < 0) {
+			goto exit;
+		}
+
+		if (next.typ == record_type(rec)) {
+			err = 0;
+			break;
+		}
+
+		if (next.typ != BLOCK_TYPE_INDEX) {
+			err = FORMAT_ERROR;
+			break;
+		}
+
+		table_iter_copy_from(&index_iter, &next);
+	}
+
+	if (err == 0) {
+		struct table_iter *malloced =
+			calloc(sizeof(struct table_iter), 1);
+		table_iter_copy_from(malloced, &next);
+		iterator_from_table_iter(it, malloced);
+	}
+exit:
+	block_iter_close(&next.bi);
+	table_iter_close(&index_iter);
+	record_clear(want_index_rec);
+	record_clear(index_result_rec);
+	return err;
+}
+
+static int reader_seek_internal(struct reader *r, struct iterator *it,
+				struct record rec)
+{
+	struct reader_offsets *offs = reader_offsets_for(r, record_type(rec));
+	uint64_t idx = offs->index_offset;
+	struct table_iter ti = {};
+	int err = 0;
+	if (idx > 0) {
+		return reader_seek_indexed(r, it, rec);
+	}
+
+	err = reader_start(r, &ti, record_type(rec), false);
+	if (err < 0) {
+		return err;
+	}
+	err = reader_seek_linear(r, &ti, rec);
+	if (err < 0) {
+		return err;
+	}
+
+	{
+		struct table_iter *p = malloc(sizeof(struct table_iter));
+		*p = ti;
+		iterator_from_table_iter(it, p);
+	}
+
+	return 0;
+}
+
+int reader_seek(struct reader *r, struct iterator *it, struct record rec)
+{
+	byte typ = record_type(rec);
+
+	struct reader_offsets *offs = reader_offsets_for(r, typ);
+	if (!offs->present) {
+		iterator_set_empty(it);
+		return 0;
+	}
+
+	return reader_seek_internal(r, it, rec);
+}
+
+int reader_seek_ref(struct reader *r, struct iterator *it, const char *name)
+{
+	struct ref_record ref = {
+		.ref_name = (char *)name,
+	};
+	struct record rec = {};
+	record_from_ref(&rec, &ref);
+	return reader_seek(r, it, rec);
+}
+
+int reader_seek_log_at(struct reader *r, struct iterator *it, const char *name,
+		       uint64_t update_index)
+{
+	struct log_record log = {
+		.ref_name = (char *)name,
+		.update_index = update_index,
+	};
+	struct record rec = {};
+	record_from_log(&rec, &log);
+	return reader_seek(r, it, rec);
+}
+
+int reader_seek_log(struct reader *r, struct iterator *it, const char *name)
+{
+	uint64_t max = ~((uint64_t)0);
+	return reader_seek_log_at(r, it, name, max);
+}
+
+void reader_close(struct reader *r)
+{
+	block_source_close(&r->source);
+	FREE_AND_NULL(r->name);
+}
+
+int new_reader(struct reader **p, struct block_source src, char const *name)
+{
+	struct reader *rd = calloc(sizeof(struct reader), 1);
+	int err = init_reader(rd, src, name);
+	if (err == 0) {
+		*p = rd;
+	} else {
+		free(rd);
+	}
+	return err;
+}
+
+void reader_free(struct reader *r)
+{
+	reader_close(r);
+	free(r);
+}
+
+static int reader_refs_for_indexed(struct reader *r, struct iterator *it,
+				   byte *oid)
+{
+	struct obj_record want = {
+		.hash_prefix = oid,
+		.hash_prefix_len = r->object_id_len,
+	};
+	struct record want_rec = {};
+	struct iterator oit = {};
+	struct obj_record got = {};
+	struct record got_rec = {};
+	int err = 0;
+
+	record_from_obj(&want_rec, &want);
+
+	err = reader_seek(r, &oit, want_rec);
+	if (err != 0) {
+		return err;
+	}
+
+	record_from_obj(&got_rec, &got);
+	err = iterator_next(oit, got_rec);
+	iterator_destroy(&oit);
+	if (err < 0) {
+		return err;
+	}
+
+	if (err > 0 ||
+	    memcmp(want.hash_prefix, got.hash_prefix, r->object_id_len)) {
+		iterator_set_empty(it);
+		return 0;
+	}
+
+	{
+		struct indexed_table_ref_iter *itr = NULL;
+		err = new_indexed_table_ref_iter(&itr, r, oid, r->hash_size,
+						 got.offsets, got.offset_len);
+		if (err < 0) {
+			record_clear(got_rec);
+			return err;
+		}
+		got.offsets = NULL;
+		record_clear(got_rec);
+
+		iterator_from_indexed_table_ref_iter(it, itr);
+	}
+
+	return 0;
+}
+
+static int reader_refs_for_unindexed(struct reader *r, struct iterator *it,
+				     byte *oid, int oid_len)
+{
+	struct table_iter *ti = calloc(sizeof(struct table_iter), 1);
+	struct filtering_ref_iterator *filter = NULL;
+	int err = reader_start(r, ti, BLOCK_TYPE_REF, false);
+	if (err < 0) {
+		free(ti);
+		return err;
+	}
+
+	filter = calloc(sizeof(struct filtering_ref_iterator), 1);
+	slice_resize(&filter->oid, oid_len);
+	memcpy(filter->oid.buf, oid, oid_len);
+	filter->r = r;
+	filter->double_check = false;
+	iterator_from_table_iter(&filter->it, ti);
+
+	iterator_from_filtering_ref_iterator(it, filter);
+	return 0;
+}
+
+int reader_refs_for(struct reader *r, struct iterator *it, byte *oid,
+		    int oid_len)
+{
+	if (r->obj_offsets.present) {
+		return reader_refs_for_indexed(r, it, oid);
+	}
+	return reader_refs_for_unindexed(r, it, oid, oid_len);
+}
+
+uint64_t reader_max_update_index(struct reader *r)
+{
+	return r->max_update_index;
+}
+
+uint64_t reader_min_update_index(struct reader *r)
+{
+	return r->min_update_index;
+}
diff --git a/reftable/reader.h b/reftable/reader.h
new file mode 100644
index 00000000000..599a90028e2
--- /dev/null
+++ b/reftable/reader.h
@@ -0,0 +1,52 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef READER_H
+#define READER_H
+
+#include "block.h"
+#include "record.h"
+#include "reftable.h"
+
+uint64_t block_source_size(struct block_source source);
+
+int block_source_read_block(struct block_source source, struct block *dest,
+			    uint64_t off, uint32_t size);
+void block_source_return_block(struct block_source source, struct block *ret);
+void block_source_close(struct block_source *source);
+
+struct reader_offsets {
+	bool present;
+	uint64_t offset;
+	uint64_t index_offset;
+};
+
+struct reader {
+	struct block_source source;
+	char *name;
+	int hash_size;
+	uint64_t size;
+	uint32_t block_size;
+	uint64_t min_update_index;
+	uint64_t max_update_index;
+	int object_id_len;
+
+	struct reader_offsets ref_offsets;
+	struct reader_offsets obj_offsets;
+	struct reader_offsets log_offsets;
+};
+
+int init_reader(struct reader *r, struct block_source source, const char *name);
+int reader_seek(struct reader *r, struct iterator *it, struct record rec);
+void reader_close(struct reader *r);
+const char *reader_name(struct reader *r);
+void reader_return_block(struct reader *r, struct block *p);
+int reader_init_block_reader(struct reader *r, struct block_reader *br,
+			     uint64_t next_off, byte want_typ);
+
+#endif
diff --git a/reftable/record.c b/reftable/record.c
new file mode 100644
index 00000000000..a006f9019d8
--- /dev/null
+++ b/reftable/record.c
@@ -0,0 +1,1107 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "record.h"
+
+#include "system.h"
+
+#include "constants.h"
+#include "reftable.h"
+
+int is_block_type(byte typ)
+{
+	switch (typ) {
+	case BLOCK_TYPE_REF:
+	case BLOCK_TYPE_LOG:
+	case BLOCK_TYPE_OBJ:
+	case BLOCK_TYPE_INDEX:
+		return true;
+	}
+	return false;
+}
+
+int get_var_int(uint64_t *dest, struct slice in)
+{
+	int ptr = 0;
+	uint64_t val;
+
+	if (in.len == 0) {
+		return -1;
+	}
+	val = in.buf[ptr] & 0x7f;
+
+	while (in.buf[ptr] & 0x80) {
+		ptr++;
+		if (ptr > in.len) {
+			return -1;
+		}
+		val = (val + 1) << 7 | (uint64_t)(in.buf[ptr] & 0x7f);
+	}
+
+	*dest = val;
+	return ptr + 1;
+}
+
+int put_var_int(struct slice dest, uint64_t val)
+{
+	byte buf[10] = {};
+	int i = 9;
+	buf[i] = (byte)(val & 0x7f);
+	i--;
+	while (true) {
+		val >>= 7;
+		if (!val) {
+			break;
+		}
+		val--;
+		buf[i] = 0x80 | (byte)(val & 0x7f);
+		i--;
+	}
+
+	{
+		int n = sizeof(buf) - i - 1;
+		if (dest.len < n) {
+			return -1;
+		}
+		memcpy(dest.buf, &buf[i + 1], n);
+		return n;
+	}
+}
+
+int common_prefix_size(struct slice a, struct slice b)
+{
+	int p = 0;
+	while (p < a.len && p < b.len) {
+		if (a.buf[p] != b.buf[p]) {
+			break;
+		}
+		p++;
+	}
+
+	return p;
+}
+
+static int decode_string(struct slice *dest, struct slice in)
+{
+	int start_len = in.len;
+	uint64_t tsize = 0;
+	int n = get_var_int(&tsize, in);
+	if (n <= 0) {
+		return -1;
+	}
+	in.buf += n;
+	in.len -= n;
+	if (in.len < tsize) {
+		return -1;
+	}
+
+	slice_resize(dest, tsize + 1);
+	dest->buf[tsize] = 0;
+	memcpy(dest->buf, in.buf, tsize);
+	in.buf += tsize;
+	in.len -= tsize;
+
+	return start_len - in.len;
+}
+
+int encode_key(bool *restart, struct slice dest, struct slice prev_key,
+	       struct slice key, byte extra)
+{
+	struct slice start = dest;
+	int prefix_len = common_prefix_size(prev_key, key);
+	uint64_t suffix_len = key.len - prefix_len;
+	int n = put_var_int(dest, (uint64_t)prefix_len);
+	if (n < 0) {
+		return -1;
+	}
+	dest.buf += n;
+	dest.len -= n;
+
+	*restart = (prefix_len == 0);
+
+	n = put_var_int(dest, suffix_len << 3 | (uint64_t)extra);
+	if (n < 0) {
+		return -1;
+	}
+	dest.buf += n;
+	dest.len -= n;
+
+	if (dest.len < suffix_len) {
+		return -1;
+	}
+	memcpy(dest.buf, key.buf + prefix_len, suffix_len);
+	dest.buf += suffix_len;
+	dest.len -= suffix_len;
+
+	return start.len - dest.len;
+}
+
+static byte ref_record_type(void)
+{
+	return BLOCK_TYPE_REF;
+}
+
+static void ref_record_key(const void *r, struct slice *dest)
+{
+	const struct ref_record *rec = (const struct ref_record *)r;
+	slice_set_string(dest, rec->ref_name);
+}
+
+static void ref_record_copy_from(void *rec, const void *src_rec, int hash_size)
+{
+	struct ref_record *ref = (struct ref_record *)rec;
+	struct ref_record *src = (struct ref_record *)src_rec;
+	assert(hash_size > 0);
+
+	/* This is simple and correct, but we could probably reuse the hash
+	   fields. */
+	ref_record_clear(ref);
+	if (src->ref_name != NULL) {
+		ref->ref_name = strdup(src->ref_name);
+	}
+
+	if (src->target != NULL) {
+		ref->target = strdup(src->target);
+	}
+
+	if (src->target_value != NULL) {
+		ref->target_value = malloc(hash_size);
+		memcpy(ref->target_value, src->target_value, hash_size);
+	}
+
+	if (src->value != NULL) {
+		ref->value = malloc(hash_size);
+		memcpy(ref->value, src->value, hash_size);
+	}
+	ref->update_index = src->update_index;
+}
+
+static char hexdigit(int c)
+{
+	if (c <= 9) {
+		return '0' + c;
+	}
+	return 'a' + (c - 10);
+}
+
+static void hex_format(char *dest, byte *src, int hash_size)
+{
+	assert(hash_size > 0);
+	if (src != NULL) {
+		int i = 0;
+		for (i = 0; i < hash_size; i++) {
+			dest[2 * i] = hexdigit(src[i] >> 4);
+			dest[2 * i + 1] = hexdigit(src[i] & 0xf);
+		}
+		dest[2 * hash_size] = 0;
+	}
+}
+
+void ref_record_print(struct ref_record *ref, int hash_size)
+{
+	char hex[SHA256_SIZE + 1] = {};
+
+	printf("ref{%s(%" PRIu64 ") ", ref->ref_name, ref->update_index);
+	if (ref->value != NULL) {
+		hex_format(hex, ref->value, hash_size);
+		printf("%s", hex);
+	}
+	if (ref->target_value != NULL) {
+		hex_format(hex, ref->target_value, hash_size);
+		printf(" (T %s)", hex);
+	}
+	if (ref->target != NULL) {
+		printf("=> %s", ref->target);
+	}
+	printf("}\n");
+}
+
+static void ref_record_clear_void(void *rec)
+{
+	ref_record_clear((struct ref_record *)rec);
+}
+
+void ref_record_clear(struct ref_record *ref)
+{
+	free(ref->ref_name);
+	free(ref->target);
+	free(ref->target_value);
+	free(ref->value);
+	memset(ref, 0, sizeof(struct ref_record));
+}
+
+static byte ref_record_val_type(const void *rec)
+{
+	const struct ref_record *r = (const struct ref_record *)rec;
+	if (r->value != NULL) {
+		if (r->target_value != NULL) {
+			return 2;
+		} else {
+			return 1;
+		}
+	} else if (r->target != NULL) {
+		return 3;
+	}
+	return 0;
+}
+
+static int encode_string(char *str, struct slice s)
+{
+	struct slice start = s;
+	int l = strlen(str);
+	int n = put_var_int(s, l);
+	if (n < 0) {
+		return -1;
+	}
+	s.buf += n;
+	s.len -= n;
+	if (s.len < l) {
+		return -1;
+	}
+	memcpy(s.buf, str, l);
+	s.buf += l;
+	s.len -= l;
+
+	return start.len - s.len;
+}
+
+static int ref_record_encode(const void *rec, struct slice s, int hash_size)
+{
+	const struct ref_record *r = (const struct ref_record *)rec;
+	struct slice start = s;
+	int n = put_var_int(s, r->update_index);
+	assert(hash_size > 0);
+	if (n < 0) {
+		return -1;
+	}
+	s.buf += n;
+	s.len -= n;
+
+	if (r->value != NULL) {
+		if (s.len < hash_size) {
+			return -1;
+		}
+		memcpy(s.buf, r->value, hash_size);
+		s.buf += hash_size;
+		s.len -= hash_size;
+	}
+
+	if (r->target_value != NULL) {
+		if (s.len < hash_size) {
+			return -1;
+		}
+		memcpy(s.buf, r->target_value, hash_size);
+		s.buf += hash_size;
+		s.len -= hash_size;
+	}
+
+	if (r->target != NULL) {
+		int n = encode_string(r->target, s);
+		if (n < 0) {
+			return -1;
+		}
+		s.buf += n;
+		s.len -= n;
+	}
+
+	return start.len - s.len;
+}
+
+static int ref_record_decode(void *rec, struct slice key, byte val_type,
+			     struct slice in, int hash_size)
+{
+	struct ref_record *r = (struct ref_record *)rec;
+	struct slice start = in;
+	bool seen_value = false;
+	bool seen_target_value = false;
+	bool seen_target = false;
+
+	int n = get_var_int(&r->update_index, in);
+	if (n < 0) {
+		return n;
+	}
+	assert(hash_size > 0);
+
+	in.buf += n;
+	in.len -= n;
+
+	r->ref_name = realloc(r->ref_name, key.len + 1);
+	memcpy(r->ref_name, key.buf, key.len);
+	r->ref_name[key.len] = 0;
+
+	switch (val_type) {
+	case 1:
+	case 2:
+		if (in.len < hash_size) {
+			return -1;
+		}
+
+		if (r->value == NULL) {
+			r->value = malloc(hash_size);
+		}
+		seen_value = true;
+		memcpy(r->value, in.buf, hash_size);
+		in.buf += hash_size;
+		in.len -= hash_size;
+		if (val_type == 1) {
+			break;
+		}
+		if (r->target_value == NULL) {
+			r->target_value = malloc(hash_size);
+		}
+		seen_target_value = true;
+		memcpy(r->target_value, in.buf, hash_size);
+		in.buf += hash_size;
+		in.len -= hash_size;
+		break;
+	case 3: {
+		struct slice dest = {};
+		int n = decode_string(&dest, in);
+		if (n < 0) {
+			return -1;
+		}
+		in.buf += n;
+		in.len -= n;
+		seen_target = true;
+		r->target = (char *)slice_as_string(&dest);
+	} break;
+
+	case 0:
+		break;
+	default:
+		abort();
+		break;
+	}
+
+	if (!seen_target && r->target != NULL) {
+		FREE_AND_NULL(r->target);
+	}
+	if (!seen_target_value && r->target_value != NULL) {
+		FREE_AND_NULL(r->target_value);
+	}
+	if (!seen_value && r->value != NULL) {
+		FREE_AND_NULL(r->value);
+	}
+
+	return start.len - in.len;
+}
+
+int decode_key(struct slice *key, byte *extra, struct slice last_key,
+	       struct slice in)
+{
+	int start_len = in.len;
+	uint64_t prefix_len = 0;
+	uint64_t suffix_len = 0;
+	int n = get_var_int(&prefix_len, in);
+	if (n < 0) {
+		return -1;
+	}
+	in.buf += n;
+	in.len -= n;
+
+	if (prefix_len > last_key.len) {
+		return -1;
+	}
+
+	n = get_var_int(&suffix_len, in);
+	if (n <= 0) {
+		return -1;
+	}
+	in.buf += n;
+	in.len -= n;
+
+	*extra = (byte)(suffix_len & 0x7);
+	suffix_len >>= 3;
+
+	if (in.len < suffix_len) {
+		return -1;
+	}
+
+	slice_resize(key, suffix_len + prefix_len);
+	memcpy(key->buf, last_key.buf, prefix_len);
+
+	memcpy(key->buf + prefix_len, in.buf, suffix_len);
+	in.buf += suffix_len;
+	in.len -= suffix_len;
+
+	return start_len - in.len;
+}
+
+struct record_vtable ref_record_vtable = {
+	.key = &ref_record_key,
+	.type = &ref_record_type,
+	.copy_from = &ref_record_copy_from,
+	.val_type = &ref_record_val_type,
+	.encode = &ref_record_encode,
+	.decode = &ref_record_decode,
+	.clear = &ref_record_clear_void,
+};
+
+static byte obj_record_type(void)
+{
+	return BLOCK_TYPE_OBJ;
+}
+
+static void obj_record_key(const void *r, struct slice *dest)
+{
+	const struct obj_record *rec = (const struct obj_record *)r;
+	slice_resize(dest, rec->hash_prefix_len);
+	memcpy(dest->buf, rec->hash_prefix, rec->hash_prefix_len);
+}
+
+static void obj_record_copy_from(void *rec, const void *src_rec, int hash_size)
+{
+	struct obj_record *ref = (struct obj_record *)rec;
+	const struct obj_record *src = (const struct obj_record *)src_rec;
+
+	*ref = *src;
+	ref->hash_prefix = malloc(ref->hash_prefix_len);
+	memcpy(ref->hash_prefix, src->hash_prefix, ref->hash_prefix_len);
+
+	{
+		int olen = ref->offset_len * sizeof(uint64_t);
+		ref->offsets = malloc(olen);
+		memcpy(ref->offsets, src->offsets, olen);
+	}
+}
+
+static void obj_record_clear(void *rec)
+{
+	struct obj_record *ref = (struct obj_record *)rec;
+	FREE_AND_NULL(ref->hash_prefix);
+	FREE_AND_NULL(ref->offsets);
+	memset(ref, 0, sizeof(struct obj_record));
+}
+
+static byte obj_record_val_type(const void *rec)
+{
+	struct obj_record *r = (struct obj_record *)rec;
+	if (r->offset_len > 0 && r->offset_len < 8) {
+		return r->offset_len;
+	}
+	return 0;
+}
+
+static int obj_record_encode(const void *rec, struct slice s, int hash_size)
+{
+	struct obj_record *r = (struct obj_record *)rec;
+	struct slice start = s;
+	int n = 0;
+	if (r->offset_len == 0 || r->offset_len >= 8) {
+		n = put_var_int(s, r->offset_len);
+		if (n < 0) {
+			return -1;
+		}
+		s.buf += n;
+		s.len -= n;
+	}
+	if (r->offset_len == 0) {
+		return start.len - s.len;
+	}
+	n = put_var_int(s, r->offsets[0]);
+	if (n < 0) {
+		return -1;
+	}
+	s.buf += n;
+	s.len -= n;
+
+	{
+		uint64_t last = r->offsets[0];
+		int i = 0;
+		for (i = 1; i < r->offset_len; i++) {
+			int n = put_var_int(s, r->offsets[i] - last);
+			if (n < 0) {
+				return -1;
+			}
+			s.buf += n;
+			s.len -= n;
+			last = r->offsets[i];
+		}
+	}
+	return start.len - s.len;
+}
+
+static int obj_record_decode(void *rec, struct slice key, byte val_type,
+			     struct slice in, int hash_size)
+{
+	struct slice start = in;
+	struct obj_record *r = (struct obj_record *)rec;
+	uint64_t count = val_type;
+	int n = 0;
+	r->hash_prefix = malloc(key.len);
+	memcpy(r->hash_prefix, key.buf, key.len);
+	r->hash_prefix_len = key.len;
+
+	if (val_type == 0) {
+		n = get_var_int(&count, in);
+		if (n < 0) {
+			return n;
+		}
+
+		in.buf += n;
+		in.len -= n;
+	}
+
+	r->offsets = NULL;
+	r->offset_len = 0;
+	if (count == 0) {
+		return start.len - in.len;
+	}
+
+	r->offsets = malloc(count * sizeof(uint64_t));
+	r->offset_len = count;
+
+	n = get_var_int(&r->offsets[0], in);
+	if (n < 0) {
+		return n;
+	}
+
+	in.buf += n;
+	in.len -= n;
+
+	{
+		uint64_t last = r->offsets[0];
+		int j = 1;
+		while (j < count) {
+			uint64_t delta = 0;
+			int n = get_var_int(&delta, in);
+			if (n < 0) {
+				return n;
+			}
+
+			in.buf += n;
+			in.len -= n;
+
+			last = r->offsets[j] = (delta + last);
+			j++;
+		}
+	}
+	return start.len - in.len;
+}
+
+struct record_vtable obj_record_vtable = {
+	.key = &obj_record_key,
+	.type = &obj_record_type,
+	.copy_from = &obj_record_copy_from,
+	.val_type = &obj_record_val_type,
+	.encode = &obj_record_encode,
+	.decode = &obj_record_decode,
+	.clear = &obj_record_clear,
+};
+
+void log_record_print(struct log_record *log, int hash_size)
+{
+	char hex[SHA256_SIZE + 1] = {};
+
+	printf("log{%s(%" PRIu64 ") %s <%s> %" PRIu64 " %04d\n", log->ref_name,
+	       log->update_index, log->name, log->email, log->time,
+	       log->tz_offset);
+	hex_format(hex, log->old_hash, hash_size);
+	printf("%s => ", hex);
+	hex_format(hex, log->new_hash, hash_size);
+	printf("%s\n\n%s\n}\n", hex, log->message);
+}
+
+static byte log_record_type(void)
+{
+	return BLOCK_TYPE_LOG;
+}
+
+static void log_record_key(const void *r, struct slice *dest)
+{
+	const struct log_record *rec = (const struct log_record *)r;
+	int len = strlen(rec->ref_name);
+	uint64_t ts = 0;
+	slice_resize(dest, len + 9);
+	memcpy(dest->buf, rec->ref_name, len + 1);
+	ts = (~ts) - rec->update_index;
+	put_u64(dest->buf + 1 + len, ts);
+}
+
+static void log_record_copy_from(void *rec, const void *src_rec, int hash_size)
+{
+	struct log_record *dst = (struct log_record *)rec;
+	const struct log_record *src = (const struct log_record *)src_rec;
+
+	*dst = *src;
+	dst->ref_name = strdup(dst->ref_name);
+	dst->email = strdup(dst->email);
+	dst->name = strdup(dst->name);
+	dst->message = strdup(dst->message);
+	if (dst->new_hash != NULL) {
+		dst->new_hash = malloc(hash_size);
+		memcpy(dst->new_hash, src->new_hash, hash_size);
+	}
+	if (dst->old_hash != NULL) {
+		dst->old_hash = malloc(hash_size);
+		memcpy(dst->old_hash, src->old_hash, hash_size);
+	}
+}
+
+static void log_record_clear_void(void *rec)
+{
+	struct log_record *r = (struct log_record *)rec;
+	log_record_clear(r);
+}
+
+void log_record_clear(struct log_record *r)
+{
+	free(r->ref_name);
+	free(r->new_hash);
+	free(r->old_hash);
+	free(r->name);
+	free(r->email);
+	free(r->message);
+	memset(r, 0, sizeof(struct log_record));
+}
+
+static byte log_record_val_type(const void *rec)
+{
+	return 1;
+}
+
+static byte zero[SHA256_SIZE] = {};
+
+static int log_record_encode(const void *rec, struct slice s, int hash_size)
+{
+	struct log_record *r = (struct log_record *)rec;
+	struct slice start = s;
+	int n = 0;
+	byte *oldh = r->old_hash;
+	byte *newh = r->new_hash;
+	if (oldh == NULL) {
+		oldh = zero;
+	}
+	if (newh == NULL) {
+		newh = zero;
+	}
+
+	if (s.len < 2 * hash_size) {
+		return -1;
+	}
+
+	memcpy(s.buf, oldh, hash_size);
+	memcpy(s.buf + hash_size, newh, hash_size);
+	s.buf += 2 * hash_size;
+	s.len -= 2 * hash_size;
+
+	n = encode_string(r->name ? r->name : "", s);
+	if (n < 0) {
+		return -1;
+	}
+	s.len -= n;
+	s.buf += n;
+
+	n = encode_string(r->email ? r->email : "", s);
+	if (n < 0) {
+		return -1;
+	}
+	s.len -= n;
+	s.buf += n;
+
+	n = put_var_int(s, r->time);
+	if (n < 0) {
+		return -1;
+	}
+	s.buf += n;
+	s.len -= n;
+
+	if (s.len < 2) {
+		return -1;
+	}
+
+	put_u16(s.buf, r->tz_offset);
+	s.buf += 2;
+	s.len -= 2;
+
+	n = encode_string(r->message ? r->message : "", s);
+	if (n < 0) {
+		return -1;
+	}
+	s.len -= n;
+	s.buf += n;
+
+	return start.len - s.len;
+}
+
+static int log_record_decode(void *rec, struct slice key, byte val_type,
+			     struct slice in, int hash_size)
+{
+	struct slice start = in;
+	struct log_record *r = (struct log_record *)rec;
+	uint64_t max = 0;
+	uint64_t ts = 0;
+	struct slice dest = {};
+	int n;
+
+	if (key.len <= 9 || key.buf[key.len - 9] != 0) {
+		return FORMAT_ERROR;
+	}
+
+	r->ref_name = realloc(r->ref_name, key.len - 8);
+	memcpy(r->ref_name, key.buf, key.len - 8);
+	ts = get_u64(key.buf + key.len - 8);
+
+	r->update_index = (~max) - ts;
+
+	if (in.len < 2 * hash_size) {
+		return FORMAT_ERROR;
+	}
+
+	r->old_hash = realloc(r->old_hash, hash_size);
+	r->new_hash = realloc(r->new_hash, hash_size);
+
+	memcpy(r->old_hash, in.buf, hash_size);
+	memcpy(r->new_hash, in.buf + hash_size, hash_size);
+
+	in.buf += 2 * hash_size;
+	in.len -= 2 * hash_size;
+
+	n = decode_string(&dest, in);
+	if (n < 0) {
+		goto error;
+	}
+	in.len -= n;
+	in.buf += n;
+
+	r->name = realloc(r->name, dest.len + 1);
+	memcpy(r->name, dest.buf, dest.len);
+	r->name[dest.len] = 0;
+
+	slice_resize(&dest, 0);
+	n = decode_string(&dest, in);
+	if (n < 0) {
+		goto error;
+	}
+	in.len -= n;
+	in.buf += n;
+
+	r->email = realloc(r->email, dest.len + 1);
+	memcpy(r->email, dest.buf, dest.len);
+	r->email[dest.len] = 0;
+
+	ts = 0;
+	n = get_var_int(&ts, in);
+	if (n < 0) {
+		goto error;
+	}
+	in.len -= n;
+	in.buf += n;
+	r->time = ts;
+	if (in.len < 2) {
+		goto error;
+	}
+
+	r->tz_offset = get_u16(in.buf);
+	in.buf += 2;
+	in.len -= 2;
+
+	slice_resize(&dest, 0);
+	n = decode_string(&dest, in);
+	if (n < 0) {
+		goto error;
+	}
+	in.len -= n;
+	in.buf += n;
+
+	r->message = realloc(r->message, dest.len + 1);
+	memcpy(r->message, dest.buf, dest.len);
+	r->message[dest.len] = 0;
+
+	return start.len - in.len;
+
+error:
+	free(slice_yield(&dest));
+	return FORMAT_ERROR;
+}
+
+static bool null_streq(char *a, char *b)
+{
+	char *empty = "";
+	if (a == NULL) {
+		a = empty;
+	}
+	if (b == NULL) {
+		b = empty;
+	}
+	return 0 == strcmp(a, b);
+}
+
+static bool zero_hash_eq(byte *a, byte *b, int sz)
+{
+	if (a == NULL) {
+		a = zero;
+	}
+	if (b == NULL) {
+		b = zero;
+	}
+	return !memcmp(a, b, sz);
+}
+
+bool log_record_equal(struct log_record *a, struct log_record *b, int hash_size)
+{
+	return null_streq(a->name, b->name) && null_streq(a->email, b->email) &&
+	       null_streq(a->message, b->message) &&
+	       zero_hash_eq(a->old_hash, b->old_hash, hash_size) &&
+	       zero_hash_eq(a->new_hash, b->new_hash, hash_size) &&
+	       a->time == b->time && a->tz_offset == b->tz_offset &&
+	       a->update_index == b->update_index;
+}
+
+struct record_vtable log_record_vtable = {
+	.key = &log_record_key,
+	.type = &log_record_type,
+	.copy_from = &log_record_copy_from,
+	.val_type = &log_record_val_type,
+	.encode = &log_record_encode,
+	.decode = &log_record_decode,
+	.clear = &log_record_clear_void,
+};
+
+struct record new_record(byte typ)
+{
+	struct record rec;
+	switch (typ) {
+	case BLOCK_TYPE_REF: {
+		struct ref_record *r = calloc(1, sizeof(struct ref_record));
+		record_from_ref(&rec, r);
+		return rec;
+	}
+
+	case BLOCK_TYPE_OBJ: {
+		struct obj_record *r = calloc(1, sizeof(struct obj_record));
+		record_from_obj(&rec, r);
+		return rec;
+	}
+	case BLOCK_TYPE_LOG: {
+		struct log_record *r = calloc(1, sizeof(struct log_record));
+		record_from_log(&rec, r);
+		return rec;
+	}
+	case BLOCK_TYPE_INDEX: {
+		struct index_record *r = calloc(1, sizeof(struct index_record));
+		record_from_index(&rec, r);
+		return rec;
+	}
+	}
+	abort();
+	return rec;
+}
+
+static byte index_record_type(void)
+{
+	return BLOCK_TYPE_INDEX;
+}
+
+static void index_record_key(const void *r, struct slice *dest)
+{
+	struct index_record *rec = (struct index_record *)r;
+	slice_copy(dest, rec->last_key);
+}
+
+static void index_record_copy_from(void *rec, const void *src_rec,
+				   int hash_size)
+{
+	struct index_record *dst = (struct index_record *)rec;
+	struct index_record *src = (struct index_record *)src_rec;
+
+	slice_copy(&dst->last_key, src->last_key);
+	dst->offset = src->offset;
+}
+
+static void index_record_clear(void *rec)
+{
+	struct index_record *idx = (struct index_record *)rec;
+	free(slice_yield(&idx->last_key));
+}
+
+static byte index_record_val_type(const void *rec)
+{
+	return 0;
+}
+
+static int index_record_encode(const void *rec, struct slice out, int hash_size)
+{
+	const struct index_record *r = (const struct index_record *)rec;
+	struct slice start = out;
+
+	int n = put_var_int(out, r->offset);
+	if (n < 0) {
+		return n;
+	}
+
+	out.buf += n;
+	out.len -= n;
+
+	return start.len - out.len;
+}
+
+static int index_record_decode(void *rec, struct slice key, byte val_type,
+			       struct slice in, int hash_size)
+{
+	struct slice start = in;
+	struct index_record *r = (struct index_record *)rec;
+	int n = 0;
+
+	slice_copy(&r->last_key, key);
+
+	n = get_var_int(&r->offset, in);
+	if (n < 0) {
+		return n;
+	}
+
+	in.buf += n;
+	in.len -= n;
+	return start.len - in.len;
+}
+
+struct record_vtable index_record_vtable = {
+	.key = &index_record_key,
+	.type = &index_record_type,
+	.copy_from = &index_record_copy_from,
+	.val_type = &index_record_val_type,
+	.encode = &index_record_encode,
+	.decode = &index_record_decode,
+	.clear = &index_record_clear,
+};
+
+void record_key(struct record rec, struct slice *dest)
+{
+	rec.ops->key(rec.data, dest);
+}
+
+byte record_type(struct record rec)
+{
+	return rec.ops->type();
+}
+
+int record_encode(struct record rec, struct slice dest, int hash_size)
+{
+	return rec.ops->encode(rec.data, dest, hash_size);
+}
+
+void record_copy_from(struct record rec, struct record src, int hash_size)
+{
+	assert(src.ops->type() == rec.ops->type());
+
+	rec.ops->copy_from(rec.data, src.data, hash_size);
+}
+
+byte record_val_type(struct record rec)
+{
+	return rec.ops->val_type(rec.data);
+}
+
+int record_decode(struct record rec, struct slice key, byte extra,
+		  struct slice src, int hash_size)
+{
+	return rec.ops->decode(rec.data, key, extra, src, hash_size);
+}
+
+void record_clear(struct record rec)
+{
+	return rec.ops->clear(rec.data);
+}
+
+void record_from_ref(struct record *rec, struct ref_record *ref_rec)
+{
+	rec->data = ref_rec;
+	rec->ops = &ref_record_vtable;
+}
+
+void record_from_obj(struct record *rec, struct obj_record *obj_rec)
+{
+	rec->data = obj_rec;
+	rec->ops = &obj_record_vtable;
+}
+
+void record_from_index(struct record *rec, struct index_record *index_rec)
+{
+	rec->data = index_rec;
+	rec->ops = &index_record_vtable;
+}
+
+void record_from_log(struct record *rec, struct log_record *log_rec)
+{
+	rec->data = log_rec;
+	rec->ops = &log_record_vtable;
+}
+
+void *record_yield(struct record *rec)
+{
+	void *p = rec->data;
+	rec->data = NULL;
+	return p;
+}
+
+struct ref_record *record_as_ref(struct record rec)
+{
+	assert(record_type(rec) == BLOCK_TYPE_REF);
+	return (struct ref_record *)rec.data;
+}
+
+static bool hash_equal(byte *a, byte *b, int hash_size)
+{
+	if (a != NULL && b != NULL) {
+		return !memcmp(a, b, hash_size);
+	}
+
+	return a == b;
+}
+
+static bool str_equal(char *a, char *b)
+{
+	if (a != NULL && b != NULL) {
+		return 0 == strcmp(a, b);
+	}
+
+	return a == b;
+}
+
+bool ref_record_equal(struct ref_record *a, struct ref_record *b, int hash_size)
+{
+	assert(hash_size > 0);
+	return 0 == strcmp(a->ref_name, b->ref_name) &&
+	       a->update_index == b->update_index &&
+	       hash_equal(a->value, b->value, hash_size) &&
+	       hash_equal(a->target_value, b->target_value, hash_size) &&
+	       str_equal(a->target, b->target);
+}
+
+int ref_record_compare_name(const void *a, const void *b)
+{
+	return strcmp(((struct ref_record *)a)->ref_name,
+		      ((struct ref_record *)b)->ref_name);
+}
+
+bool ref_record_is_deletion(const struct ref_record *ref)
+{
+	return ref->value == NULL && ref->target == NULL &&
+	       ref->target_value == NULL;
+}
+
+int log_record_compare_key(const void *a, const void *b)
+{
+	struct log_record *la = (struct log_record *)a;
+	struct log_record *lb = (struct log_record *)b;
+
+	int cmp = strcmp(la->ref_name, lb->ref_name);
+	if (cmp) {
+		return cmp;
+	}
+	if (la->update_index > lb->update_index) {
+		return -1;
+	}
+	return (la->update_index < lb->update_index) ? 1 : 0;
+}
+
+bool log_record_is_deletion(const struct log_record *log)
+{
+	/* XXX */
+	return false;
+}
diff --git a/reftable/record.h b/reftable/record.h
new file mode 100644
index 00000000000..dffdd71fc28
--- /dev/null
+++ b/reftable/record.h
@@ -0,0 +1,79 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef RECORD_H
+#define RECORD_H
+
+#include "reftable.h"
+#include "slice.h"
+
+struct record_vtable {
+	void (*key)(const void *rec, struct slice *dest);
+	byte (*type)(void);
+	void (*copy_from)(void *rec, const void *src, int hash_size);
+	byte (*val_type)(const void *rec);
+	int (*encode)(const void *rec, struct slice dest, int hash_size);
+	int (*decode)(void *rec, struct slice key, byte extra, struct slice src,
+		      int hash_size);
+	void (*clear)(void *rec);
+};
+
+/* record is a generic wrapper for differnt types of records. */
+struct record {
+	void *data;
+	struct record_vtable *ops;
+};
+
+int get_var_int(uint64_t *dest, struct slice in);
+int put_var_int(struct slice dest, uint64_t val);
+int common_prefix_size(struct slice a, struct slice b);
+
+int is_block_type(byte typ);
+struct record new_record(byte typ);
+
+extern struct record_vtable ref_record_vtable;
+
+int encode_key(bool *restart, struct slice dest, struct slice prev_key,
+	       struct slice key, byte extra);
+int decode_key(struct slice *key, byte *extra, struct slice last_key,
+	       struct slice in);
+
+struct index_record {
+	struct slice last_key;
+	uint64_t offset;
+};
+
+struct obj_record {
+	byte *hash_prefix;
+	int hash_prefix_len;
+	uint64_t *offsets;
+	int offset_len;
+};
+
+void record_key(struct record rec, struct slice *dest);
+byte record_type(struct record rec);
+void record_copy_from(struct record rec, struct record src, int hash_size);
+byte record_val_type(struct record rec);
+int record_encode(struct record rec, struct slice dest, int hash_size);
+int record_decode(struct record rec, struct slice key, byte extra,
+		  struct slice src, int hash_size);
+void record_clear(struct record rec);
+void *record_yield(struct record *rec);
+void record_from_obj(struct record *rec, struct obj_record *objrec);
+void record_from_index(struct record *rec, struct index_record *idxrec);
+void record_from_ref(struct record *rec, struct ref_record *refrec);
+void record_from_log(struct record *rec, struct log_record *logrec);
+struct ref_record *record_as_ref(struct record ref);
+
+/* for qsort. */
+int ref_record_compare_name(const void *a, const void *b);
+
+/* for qsort. */
+int log_record_compare_key(const void *a, const void *b);
+
+#endif
diff --git a/reftable/reftable.h b/reftable/reftable.h
new file mode 100644
index 00000000000..e66647bebcd
--- /dev/null
+++ b/reftable/reftable.h
@@ -0,0 +1,405 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef REFTABLE_H
+#define REFTABLE_H
+
+#include <stdint.h>
+
+/* block_source is a generic wrapper for a seekable readable file.
+   It is generally passed around by value.
+ */
+struct block_source {
+	struct block_source_vtable *ops;
+	void *arg;
+};
+
+/* a contiguous segment of bytes. It keeps track of its generating block_source
+   so it can return itself into the pool.
+*/
+struct block {
+	uint8_t *data;
+	int len;
+	struct block_source source;
+};
+
+/* block_source_vtable are the operations that make up block_source */
+struct block_source_vtable {
+	/* returns the size of a block source */
+	uint64_t (*size)(void *source);
+
+	/* reads a segment from the block source. It is an error to read
+	   beyond the end of the block */
+	int (*read_block)(void *source, struct block *dest, uint64_t off,
+			  uint32_t size);
+	/* mark the block as read; may return the data back to malloc */
+	void (*return_block)(void *source, struct block *blockp);
+
+	/* release all resources associated with the block source */
+	void (*close)(void *source);
+};
+
+/* opens a file on the file system as a block_source */
+int block_source_from_file(struct block_source *block_src, const char *name);
+
+/* write_options sets options for writing a single reftable. */
+struct write_options {
+	/* boolean: do not pad out blocks to block size. */
+	int unpadded;
+
+	/* the blocksize. Should be less than 2^24. */
+	uint32_t block_size;
+
+	/* boolean: do not generate a SHA1 => ref index. */
+	int skip_index_objects;
+
+	/* how often to write complete keys in each block. */
+	int restart_interval;
+
+	/* width of the hash. Should be 20 for SHA1 or 32 for SHA256. Defaults
+	 * to SHA1 if unset */
+	int hash_size;
+};
+
+/* ref_record holds a ref database entry target_value */
+struct ref_record {
+	char *ref_name; /* Name of the ref, malloced. */
+	uint64_t update_index; /* Logical timestamp at which this value is
+				  written */
+	uint8_t *value; /* SHA1, or NULL. malloced. */
+	uint8_t *target_value; /* peeled annotated tag, or NULL. malloced. */
+	char *target; /* symref, or NULL. malloced. */
+};
+
+/* returns whether 'ref' represents a deletion */
+int ref_record_is_deletion(const struct ref_record *ref);
+
+/* prints a ref_record onto stdout */
+void ref_record_print(struct ref_record *ref, int hash_size);
+
+/* frees and nulls all pointer values. */
+void ref_record_clear(struct ref_record *ref);
+
+/* returns whether two ref_records are the same */
+int ref_record_equal(struct ref_record *a, struct ref_record *b, int hash_size);
+
+/* log_record holds a reflog entry */
+struct log_record {
+	char *ref_name;
+	uint64_t update_index;
+	uint8_t *new_hash;
+	uint8_t *old_hash;
+	char *name;
+	char *email;
+	uint64_t time;
+	int16_t tz_offset;
+	char *message;
+};
+
+/* returns whether 'ref' represents the deletion of a log record. */
+int log_record_is_deletion(const struct log_record *log);
+
+/* frees and nulls all pointer values. */
+void log_record_clear(struct log_record *log);
+
+/* returns whether two records are equal. */
+int log_record_equal(struct log_record *a, struct log_record *b, int hash_size);
+
+void log_record_print(struct log_record *log, int hash_size);
+
+/* iterator is the generic interface for walking over data stored in a
+   reftable. It is generally passed around by value.
+*/
+struct iterator {
+	struct iterator_vtable *ops;
+	void *iter_arg;
+};
+
+/* reads the next ref_record. Returns < 0 for error, 0 for OK and > 0:
+   end of iteration.
+*/
+int iterator_next_ref(struct iterator it, struct ref_record *ref);
+
+/* reads the next log_record. Returns < 0 for error, 0 for OK and > 0:
+   end of iteration.
+*/
+int iterator_next_log(struct iterator it, struct log_record *log);
+
+/* releases resources associated with an iterator. */
+void iterator_destroy(struct iterator *it);
+
+/* block_stats holds statistics for a single block type */
+struct block_stats {
+	/* total number of entries written */
+	int entries;
+	/* total number of key restarts */
+	int restarts;
+	/* total number of blocks */
+	int blocks;
+	/* total number of index blocks */
+	int index_blocks;
+	/* depth of the index */
+	int max_index_level;
+
+	/* offset of the first block for this type */
+	uint64_t offset;
+	/* offset of the top level index block for this type, or 0 if not
+	 * present */
+	uint64_t index_offset;
+};
+
+/* stats holds overall statistics for a single reftable */
+struct stats {
+	/* total number of blocks written. */
+	int blocks;
+	/* stats for ref data */
+	struct block_stats ref_stats;
+	/* stats for the SHA1 to ref map. */
+	struct block_stats obj_stats;
+	/* stats for index blocks */
+	struct block_stats idx_stats;
+	/* stats for log blocks */
+	struct block_stats log_stats;
+
+	/* disambiguation length of shortened object IDs. */
+	int object_id_len;
+};
+
+/* different types of errors */
+
+/* Unexpected file system behavior */
+#define IO_ERROR -2
+
+/* Format inconsistency on reading data
+ */
+#define FORMAT_ERROR -3
+
+/* File does not exist. Returned from block_source_from_file(),  because it
+   needs special handling in stack.
+*/
+#define NOT_EXIST_ERROR -4
+
+/* Trying to write out-of-date data. */
+#define LOCK_ERROR -5
+
+/* Misuse of the API:
+   - on writing a record with NULL ref_name.
+   - on writing a ref_record outside the table limits
+   - on writing a ref or log record before the stack's next_update_index
+   - on reading a ref_record from log iterator, or vice versa.
+ */
+#define API_ERROR -6
+
+/* Decompression error */
+#define ZLIB_ERROR -7
+
+const char *error_str(int err);
+
+/* new_writer creates a new writer */
+struct writer *new_writer(int (*writer_func)(void *, uint8_t *, int),
+			  void *writer_arg, struct write_options *opts);
+
+/* write to a file descriptor. fdp should be an int* pointing to the fd. */
+int fd_writer(void *fdp, uint8_t *data, int size);
+
+/* Set the range of update indices for the records we will add.  When
+   writing a table into a stack, the min should be at least
+   stack_next_update_index(), or API_ERROR is returned.
+ */
+void writer_set_limits(struct writer *w, uint64_t min, uint64_t max);
+
+/* adds a ref_record. Must be called in ascending
+   order. The update_index must be within the limits set by
+   writer_set_limits(), or API_ERROR is returned.
+ */
+int writer_add_ref(struct writer *w, struct ref_record *ref);
+
+/* Convenience function to add multiple refs. Will sort the refs by
+   name before adding. */
+int writer_add_refs(struct writer *w, struct ref_record *refs, int n);
+
+/* adds a log_record. Must be called in ascending order (with more
+   recent log entries first.)
+ */
+int writer_add_log(struct writer *w, struct log_record *log);
+
+/* Convenience function to add multiple logs. Will sort the records by
+   key before adding. */
+int writer_add_logs(struct writer *w, struct log_record *logs, int n);
+
+/* writer_close finalizes the reftable. The writer is retained so statistics can
+ * be inspected. */
+int writer_close(struct writer *w);
+
+/* writer_stats returns the statistics on the reftable being written. */
+struct stats *writer_stats(struct writer *w);
+
+/* writer_free deallocates memory for the writer */
+void writer_free(struct writer *w);
+
+struct reader;
+
+/* new_reader opens a reftable for reading. If successful, returns 0
+ * code and sets pp.  The name is used for creating a
+ * stack. Typically, it is the basename of the file.
+ */
+int new_reader(struct reader **pp, struct block_source, const char *name);
+
+/* reader_seek_ref returns an iterator where 'name' would be inserted in the
+   table.
+
+   example:
+
+   struct reader *r = NULL;
+   int err = new_reader(&r, src, "filename");
+   if (err < 0) { ... }
+   struct iterator it = {};
+   err = reader_seek_ref(r, &it, "refs/heads/master");
+   if (err < 0) { ... }
+   struct ref_record ref = {};
+   while (1) {
+     err = iterator_next_ref(it, &ref);
+     if (err > 0) {
+       break;
+     }
+     if (err < 0) {
+       ..error handling..
+     }
+     ..found..
+   }
+   iterator_destroy(&it);
+   ref_record_clear(&ref);
+ */
+int reader_seek_ref(struct reader *r, struct iterator *it, const char *name);
+
+/* returns the hash size used in this table. */
+int reader_hash_size(struct reader *r);
+
+/* seek to logs for the given name, older than update_index. */
+int reader_seek_log_at(struct reader *r, struct iterator *it, const char *name,
+		       uint64_t update_index);
+
+/* seek to newest log entry for given name. */
+int reader_seek_log(struct reader *r, struct iterator *it, const char *name);
+
+/* closes and deallocates a reader. */
+void reader_free(struct reader *);
+
+/* return an iterator for the refs pointing to oid */
+int reader_refs_for(struct reader *r, struct iterator *it, uint8_t *oid,
+		    int oid_len);
+
+/* return the max_update_index for a table */
+uint64_t reader_max_update_index(struct reader *r);
+
+/* return the min_update_index for a table */
+uint64_t reader_min_update_index(struct reader *r);
+
+/* a merged table is implements seeking/iterating over a stack of tables. */
+struct merged_table;
+
+/* new_merged_table creates a new merged table. It takes ownership of the stack
+   array.
+*/
+int new_merged_table(struct merged_table **dest, struct reader **stack, int n,
+		     int hash_size);
+
+/* returns the hash size used in this merged table. */
+int merged_hash_size(struct merged_table *mt);
+
+/* returns an iterator positioned just before 'name' */
+int merged_table_seek_ref(struct merged_table *mt, struct iterator *it,
+			  const char *name);
+
+/* returns an iterator for log entry, at given update_index */
+int merged_table_seek_log_at(struct merged_table *mt, struct iterator *it,
+			     const char *name, uint64_t update_index);
+
+/* like merged_table_seek_log_at but look for the newest entry. */
+int merged_table_seek_log(struct merged_table *mt, struct iterator *it,
+			  const char *name);
+
+/* returns the max update_index covered by this merged table. */
+uint64_t merged_max_update_index(struct merged_table *mt);
+
+/* returns the min update_index covered by this merged table. */
+uint64_t merged_min_update_index(struct merged_table *mt);
+
+/* closes readers for the merged tables */
+void merged_table_close(struct merged_table *mt);
+
+/* releases memory for the merged_table */
+void merged_table_free(struct merged_table *m);
+
+/* a stack is a stack of reftables, which can be mutated by pushing a table to
+ * the top of the stack */
+struct stack;
+
+/* open a new reftable stack. The tables will be stored in 'dir', while the list
+   of tables is in 'list_file'. Typically, this should be .git/reftables and
+   .git/refs respectively.
+*/
+int new_stack(struct stack **dest, const char *dir, const char *list_file,
+	      struct write_options config);
+
+/* returns the update_index at which a next table should be written. */
+uint64_t stack_next_update_index(struct stack *st);
+
+/* add a new table to the stack. The write_table function must call
+   writer_set_limits, add refs and return an error value. */
+int stack_add(struct stack *st,
+	      int (*write_table)(struct writer *wr, void *write_arg),
+	      void *write_arg);
+
+/* returns the merged_table for seeking. This table is valid until the
+   next write or reload, and should not be closed or deleted.
+*/
+struct merged_table *stack_merged_table(struct stack *st);
+
+/* frees all resources associated with the stack. */
+void stack_destroy(struct stack *st);
+
+/* reloads the stack if necessary. */
+int stack_reload(struct stack *st);
+
+/* Policy for expiring reflog entries. */
+struct log_expiry_config {
+	/* Drop entries older than this timestamp */
+	uint64_t time;
+
+	/* Drop older entries */
+	uint64_t min_update_index;
+};
+
+/* compacts all reftables into a giant table. Expire reflog entries if config is
+ * non-NULL */
+int stack_compact_all(struct stack *st, struct log_expiry_config *config);
+
+/* heuristically compact unbalanced table stack. */
+int stack_auto_compact(struct stack *st);
+
+/* convenience function to read a single ref. Returns < 0 for error, 0
+   for success, and 1 if ref not found. */
+int stack_read_ref(struct stack *st, const char *refname,
+		   struct ref_record *ref);
+
+/* convenience function to read a single log. Returns < 0 for error, 0
+   for success, and 1 if ref not found. */
+int stack_read_log(struct stack *st, const char *refname,
+		   struct log_record *log);
+
+/* statistics on past compactions. */
+struct compaction_stats {
+	uint64_t bytes;
+	int attempts;
+	int failures;
+};
+
+struct compaction_stats *stack_compaction_stats(struct stack *st);
+
+#endif
diff --git a/reftable/slice.c b/reftable/slice.c
new file mode 100644
index 00000000000..efbe625253f
--- /dev/null
+++ b/reftable/slice.c
@@ -0,0 +1,199 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "slice.h"
+
+#include "system.h"
+
+#include "reftable.h"
+
+void slice_set_string(struct slice *s, const char *str)
+{
+	if (str == NULL) {
+		s->len = 0;
+		return;
+	}
+
+	{
+		int l = strlen(str);
+		l++; /* \0 */
+		slice_resize(s, l);
+		memcpy(s->buf, str, l);
+		s->len = l - 1;
+	}
+}
+
+void slice_resize(struct slice *s, int l)
+{
+	if (s->cap < l) {
+		int c = s->cap * 2;
+		if (c < l) {
+			c = l;
+		}
+		s->cap = c;
+		s->buf = realloc(s->buf, s->cap);
+	}
+	s->len = l;
+}
+
+void slice_append_string(struct slice *d, const char *s)
+{
+	int l1 = d->len;
+	int l2 = strlen(s);
+
+	slice_resize(d, l2 + l1);
+	memcpy(d->buf + l1, s, l2);
+}
+
+void slice_append(struct slice *s, struct slice a)
+{
+	int end = s->len;
+	slice_resize(s, s->len + a.len);
+	memcpy(s->buf + end, a.buf, a.len);
+}
+
+byte *slice_yield(struct slice *s)
+{
+	byte *p = s->buf;
+	s->buf = NULL;
+	s->cap = 0;
+	s->len = 0;
+	return p;
+}
+
+void slice_copy(struct slice *dest, struct slice src)
+{
+	slice_resize(dest, src.len);
+	memcpy(dest->buf, src.buf, src.len);
+}
+
+/* return the underlying data as char*. len is left unchanged, but
+   a \0 is added at the end. */
+const char *slice_as_string(struct slice *s)
+{
+	if (s->cap == s->len) {
+		int l = s->len;
+		slice_resize(s, l + 1);
+		s->len = l;
+	}
+	s->buf[s->len] = 0;
+	return (const char *)s->buf;
+}
+
+/* return a newly malloced string for this slice */
+char *slice_to_string(struct slice in)
+{
+	struct slice s = {};
+	slice_resize(&s, in.len + 1);
+	s.buf[in.len] = 0;
+	memcpy(s.buf, in.buf, in.len);
+	return (char *)slice_yield(&s);
+}
+
+bool slice_equal(struct slice a, struct slice b)
+{
+	if (a.len != b.len) {
+		return 0;
+	}
+	return memcmp(a.buf, b.buf, a.len) == 0;
+}
+
+int slice_compare(struct slice a, struct slice b)
+{
+	int min = a.len < b.len ? a.len : b.len;
+	int res = memcmp(a.buf, b.buf, min);
+	if (res != 0) {
+		return res;
+	}
+	if (a.len < b.len) {
+		return -1;
+	} else if (a.len > b.len) {
+		return 1;
+	} else {
+		return 0;
+	}
+}
+
+int slice_write(struct slice *b, byte *data, int sz)
+{
+	if (b->len + sz > b->cap) {
+		int newcap = 2 * b->cap + 1;
+		if (newcap < b->len + sz) {
+			newcap = (b->len + sz);
+		}
+		b->buf = realloc(b->buf, newcap);
+		b->cap = newcap;
+	}
+
+	memcpy(b->buf + b->len, data, sz);
+	b->len += sz;
+	return sz;
+}
+
+int slice_write_void(void *b, byte *data, int sz)
+{
+	return slice_write((struct slice *)b, data, sz);
+}
+
+static uint64_t slice_size(void *b)
+{
+	return ((struct slice *)b)->len;
+}
+
+static void slice_return_block(void *b, struct block *dest)
+{
+	memset(dest->data, 0xff, dest->len);
+	free(dest->data);
+}
+
+static void slice_close(void *b)
+{
+}
+
+static int slice_read_block(void *v, struct block *dest, uint64_t off,
+			    uint32_t size)
+{
+	struct slice *b = (struct slice *)v;
+	assert(off + size <= b->len);
+	dest->data = calloc(size, 1);
+	memcpy(dest->data, b->buf + off, size);
+	dest->len = size;
+	return size;
+}
+
+struct block_source_vtable slice_vtable = {
+	.size = &slice_size,
+	.read_block = &slice_read_block,
+	.return_block = &slice_return_block,
+	.close = &slice_close,
+};
+
+void block_source_from_slice(struct block_source *bs, struct slice *buf)
+{
+	bs->ops = &slice_vtable;
+	bs->arg = buf;
+}
+
+static void malloc_return_block(void *b, struct block *dest)
+{
+	memset(dest->data, 0xff, dest->len);
+	free(dest->data);
+}
+
+struct block_source_vtable malloc_vtable = {
+	.return_block = &malloc_return_block,
+};
+
+struct block_source malloc_block_source_instance = {
+	.ops = &malloc_vtable,
+};
+
+struct block_source malloc_block_source(void)
+{
+	return malloc_block_source_instance;
+}
diff --git a/reftable/slice.h b/reftable/slice.h
new file mode 100644
index 00000000000..f12a6db228c
--- /dev/null
+++ b/reftable/slice.h
@@ -0,0 +1,39 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef SLICE_H
+#define SLICE_H
+
+#include "basics.h"
+#include "reftable.h"
+
+struct slice {
+	byte *buf;
+	int len;
+	int cap;
+};
+
+void slice_set_string(struct slice *dest, const char *);
+void slice_append_string(struct slice *dest, const char *);
+char *slice_to_string(struct slice src);
+const char *slice_as_string(struct slice *src);
+bool slice_equal(struct slice a, struct slice b);
+byte *slice_yield(struct slice *s);
+void slice_copy(struct slice *dest, struct slice src);
+void slice_resize(struct slice *s, int l);
+int slice_compare(struct slice a, struct slice b);
+int slice_write(struct slice *b, byte *data, int sz);
+int slice_write_void(void *b, byte *data, int sz);
+void slice_append(struct slice *dest, struct slice add);
+
+struct block_source;
+void block_source_from_slice(struct block_source *bs, struct slice *buf);
+
+struct block_source malloc_block_source(void);
+
+#endif
diff --git a/reftable/stack.c b/reftable/stack.c
new file mode 100644
index 00000000000..6a5aebf2ecd
--- /dev/null
+++ b/reftable/stack.c
@@ -0,0 +1,984 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "stack.h"
+
+#include "system.h"
+#include "merged.h"
+#include "reader.h"
+#include "reftable.h"
+#include "writer.h"
+
+int new_stack(struct stack **dest, const char *dir, const char *list_file,
+	      struct write_options config)
+{
+	struct stack *p = calloc(sizeof(struct stack), 1);
+	int err = 0;
+	*dest = NULL;
+	p->list_file = strdup(list_file);
+	p->reftable_dir = strdup(dir);
+	p->config = config;
+
+	err = stack_reload(p);
+	if (err < 0) {
+		stack_destroy(p);
+	} else {
+		*dest = p;
+	}
+	return err;
+}
+
+static int fread_lines(FILE *f, char ***namesp)
+{
+	long size = 0;
+	int err = fseek(f, 0, SEEK_END);
+	char *buf = NULL;
+	if (err < 0) {
+		err = IO_ERROR;
+		goto exit;
+	}
+	size = ftell(f);
+	if (size < 0) {
+		err = IO_ERROR;
+		goto exit;
+	}
+	err = fseek(f, 0, SEEK_SET);
+	if (err < 0) {
+		err = IO_ERROR;
+		goto exit;
+	}
+
+	buf = malloc(size + 1);
+	if (fread(buf, 1, size, f) != size) {
+		err = IO_ERROR;
+		goto exit;
+	}
+	buf[size] = 0;
+
+	parse_names(buf, size, namesp);
+exit:
+	free(buf);
+	return err;
+}
+
+int read_lines(const char *filename, char ***namesp)
+{
+	FILE *f = fopen(filename, "r");
+	int err = 0;
+	if (f == NULL) {
+		if (errno == ENOENT) {
+			*namesp = calloc(sizeof(char *), 1);
+			return 0;
+		}
+
+		return IO_ERROR;
+	}
+	err = fread_lines(f, namesp);
+	fclose(f);
+	return err;
+}
+
+struct merged_table *stack_merged_table(struct stack *st)
+{
+	return st->merged;
+}
+
+/* Close and free the stack */
+void stack_destroy(struct stack *st)
+{
+	if (st->merged == NULL) {
+		return;
+	}
+
+	merged_table_close(st->merged);
+	merged_table_free(st->merged);
+	st->merged = NULL;
+
+	FREE_AND_NULL(st->list_file);
+	FREE_AND_NULL(st->reftable_dir);
+	free(st);
+}
+
+static struct reader **stack_copy_readers(struct stack *st, int cur_len)
+{
+	struct reader **cur = calloc(sizeof(struct reader *), cur_len);
+	int i = 0;
+	for (i = 0; i < cur_len; i++) {
+		cur[i] = st->merged->stack[i];
+	}
+	return cur;
+}
+
+static int stack_reload_once(struct stack *st, char **names, bool reuse_open)
+{
+	int cur_len = st->merged == NULL ? 0 : st->merged->stack_len;
+	struct reader **cur = stack_copy_readers(st, cur_len);
+	int err = 0;
+	int names_len = names_length(names);
+	struct reader **new_tables =
+		malloc(sizeof(struct reader *) * names_len);
+	int new_tables_len = 0;
+	struct merged_table *new_merged = NULL;
+
+	struct slice table_path = {};
+
+	while (*names) {
+		struct reader *rd = NULL;
+		char *name = *names++;
+
+		/* this is linear; we assume compaction keeps the number of
+		   tables under control so this is not quadratic. */
+		int j = 0;
+		for (j = 0; reuse_open && j < cur_len; j++) {
+			if (cur[j] != NULL && 0 == strcmp(cur[j]->name, name)) {
+				rd = cur[j];
+				cur[j] = NULL;
+				break;
+			}
+		}
+
+		if (rd == NULL) {
+			struct block_source src = {};
+			slice_set_string(&table_path, st->reftable_dir);
+			slice_append_string(&table_path, "/");
+			slice_append_string(&table_path, name);
+
+			err = block_source_from_file(
+				&src, slice_as_string(&table_path));
+			if (err < 0) {
+				goto exit;
+			}
+
+			err = new_reader(&rd, src, name);
+			if (err < 0) {
+				goto exit;
+			}
+		}
+
+		new_tables[new_tables_len++] = rd;
+	}
+
+	/* success! */
+	err = new_merged_table(&new_merged, new_tables, new_tables_len,
+			       st->config.hash_size);
+	if (err < 0) {
+		goto exit;
+	}
+
+	new_tables = NULL;
+	new_tables_len = 0;
+	if (st->merged != NULL) {
+		merged_table_clear(st->merged);
+		merged_table_free(st->merged);
+	}
+	st->merged = new_merged;
+
+	{
+		int i = 0;
+		for (i = 0; i < cur_len; i++) {
+			if (cur[i] != NULL) {
+				reader_close(cur[i]);
+				reader_free(cur[i]);
+			}
+		}
+	}
+exit:
+	free(slice_yield(&table_path));
+	{
+		int i = 0;
+		for (i = 0; i < new_tables_len; i++) {
+			reader_close(new_tables[i]);
+		}
+	}
+	free(new_tables);
+	free(cur);
+	return err;
+}
+
+/* return negative if a before b. */
+static int tv_cmp(struct timeval *a, struct timeval *b)
+{
+	time_t diff = a->tv_sec - b->tv_sec;
+	int udiff = a->tv_usec - b->tv_usec;
+
+	if (diff != 0) {
+		return diff;
+	}
+
+	return udiff;
+}
+
+static int stack_reload_maybe_reuse(struct stack *st, bool reuse_open)
+{
+	struct timeval deadline = {};
+	int err = gettimeofday(&deadline, NULL);
+	int64_t delay = 0;
+	int tries = 0;
+	if (err < 0) {
+		return err;
+	}
+
+	deadline.tv_sec += 3;
+	while (true) {
+		char **names = NULL;
+		char **names_after = NULL;
+		struct timeval now = {};
+		int err = gettimeofday(&now, NULL);
+		if (err < 0) {
+			return err;
+		}
+
+		/* Only look at deadlines after the first few times. This
+		   simplifies debugging in GDB */
+		tries++;
+		if (tries > 3 && tv_cmp(&now, &deadline) >= 0) {
+			break;
+		}
+
+		err = read_lines(st->list_file, &names);
+		if (err < 0) {
+			free_names(names);
+			return err;
+		}
+		err = stack_reload_once(st, names, reuse_open);
+		if (err == 0) {
+			free_names(names);
+			break;
+		}
+		if (err != NOT_EXIST_ERROR) {
+			free_names(names);
+			return err;
+		}
+
+		err = read_lines(st->list_file, &names_after);
+		if (err < 0) {
+			free_names(names);
+			return err;
+		}
+
+		if (names_equal(names_after, names)) {
+			free_names(names);
+			free_names(names_after);
+			return -1;
+		}
+		free_names(names);
+		free_names(names_after);
+
+		delay = delay + (delay * rand()) / RAND_MAX + 100;
+		usleep(delay);
+	}
+
+	return 0;
+}
+
+int stack_reload(struct stack *st)
+{
+	return stack_reload_maybe_reuse(st, true);
+}
+
+/* -1 = error
+ 0 = up to date
+ 1 = changed. */
+static int stack_uptodate(struct stack *st)
+{
+	char **names = NULL;
+	int err = read_lines(st->list_file, &names);
+	int i = 0;
+	if (err < 0) {
+		return err;
+	}
+
+	for (i = 0; i < st->merged->stack_len; i++) {
+		if (names[i] == NULL) {
+			err = 1;
+			goto exit;
+		}
+
+		if (strcmp(st->merged->stack[i]->name, names[i])) {
+			err = 1;
+			goto exit;
+		}
+	}
+
+	if (names[st->merged->stack_len] != NULL) {
+		err = 1;
+		goto exit;
+	}
+
+exit:
+	free_names(names);
+	return err;
+}
+
+int stack_add(struct stack *st, int (*write)(struct writer *wr, void *arg),
+	      void *arg)
+{
+	int err = stack_try_add(st, write, arg);
+	if (err < 0) {
+		if (err == LOCK_ERROR) {
+			err = stack_reload(st);
+		}
+		return err;
+	}
+
+	return stack_auto_compact(st);
+}
+
+static void format_name(struct slice *dest, uint64_t min, uint64_t max)
+{
+	char buf[100];
+	snprintf(buf, sizeof(buf), "%012" PRIx64 "-%012" PRIx64, min, max);
+	slice_set_string(dest, buf);
+}
+
+int stack_try_add(struct stack *st,
+		  int (*write_table)(struct writer *wr, void *arg), void *arg)
+{
+	struct slice lock_name = {};
+	struct slice temp_tab_name = {};
+	struct slice tab_name = {};
+	struct slice next_name = {};
+	struct slice table_list = {};
+	struct writer *wr = NULL;
+	int err = 0;
+	int tab_fd = 0;
+	int lock_fd = 0;
+	uint64_t next_update_index = 0;
+
+	slice_set_string(&lock_name, st->list_file);
+	slice_append_string(&lock_name, ".lock");
+
+	lock_fd = open(slice_as_string(&lock_name), O_EXCL | O_CREAT | O_WRONLY,
+		       0644);
+	if (lock_fd < 0) {
+		if (errno == EEXIST) {
+			err = LOCK_ERROR;
+			goto exit;
+		}
+		err = IO_ERROR;
+		goto exit;
+	}
+
+	err = stack_uptodate(st);
+	if (err < 0) {
+		goto exit;
+	}
+
+	if (err > 1) {
+		err = LOCK_ERROR;
+		goto exit;
+	}
+
+	next_update_index = stack_next_update_index(st);
+
+	slice_resize(&next_name, 0);
+	format_name(&next_name, next_update_index, next_update_index);
+
+	slice_set_string(&temp_tab_name, st->reftable_dir);
+	slice_append_string(&temp_tab_name, "/");
+	slice_append(&temp_tab_name, next_name);
+	slice_append_string(&temp_tab_name, ".temp.XXXXXX");
+
+	tab_fd = mkstemp((char *)slice_as_string(&temp_tab_name));
+	if (tab_fd < 0) {
+		err = IO_ERROR;
+		goto exit;
+	}
+
+	wr = new_writer(fd_writer, &tab_fd, &st->config);
+	err = write_table(wr, arg);
+	if (err < 0) {
+		goto exit;
+	}
+
+	err = writer_close(wr);
+	if (err < 0) {
+		goto exit;
+	}
+
+	err = close(tab_fd);
+	tab_fd = 0;
+	if (err < 0) {
+		err = IO_ERROR;
+		goto exit;
+	}
+
+	if (wr->min_update_index < next_update_index) {
+		err = API_ERROR;
+		goto exit;
+	}
+
+	{
+		int i = 0;
+		for (i = 0; i < st->merged->stack_len; i++) {
+			slice_append_string(&table_list,
+					    st->merged->stack[i]->name);
+			slice_append_string(&table_list, "\n");
+		}
+	}
+
+	format_name(&next_name, wr->min_update_index, wr->max_update_index);
+	slice_append_string(&next_name, ".ref");
+	slice_append(&table_list, next_name);
+	slice_append_string(&table_list, "\n");
+
+	slice_set_string(&tab_name, st->reftable_dir);
+	slice_append_string(&tab_name, "/");
+	slice_append(&tab_name, next_name);
+
+	err = rename(slice_as_string(&temp_tab_name),
+		     slice_as_string(&tab_name));
+	if (err < 0) {
+		err = IO_ERROR;
+		goto exit;
+	}
+	free(slice_yield(&temp_tab_name));
+
+	err = write(lock_fd, table_list.buf, table_list.len);
+	if (err < 0) {
+		err = IO_ERROR;
+		goto exit;
+	}
+	err = close(lock_fd);
+	lock_fd = 0;
+	if (err < 0) {
+		unlink(slice_as_string(&tab_name));
+		err = IO_ERROR;
+		goto exit;
+	}
+
+	err = rename(slice_as_string(&lock_name), st->list_file);
+	if (err < 0) {
+		unlink(slice_as_string(&tab_name));
+		err = IO_ERROR;
+		goto exit;
+	}
+
+	err = stack_reload(st);
+exit:
+	if (tab_fd > 0) {
+		close(tab_fd);
+		tab_fd = 0;
+	}
+	if (temp_tab_name.len > 0) {
+		unlink(slice_as_string(&temp_tab_name));
+	}
+	unlink(slice_as_string(&lock_name));
+
+	if (lock_fd > 0) {
+		close(lock_fd);
+		lock_fd = 0;
+	}
+
+	free(slice_yield(&lock_name));
+	free(slice_yield(&temp_tab_name));
+	free(slice_yield(&tab_name));
+	free(slice_yield(&next_name));
+	free(slice_yield(&table_list));
+	writer_free(wr);
+	return err;
+}
+
+uint64_t stack_next_update_index(struct stack *st)
+{
+	int sz = st->merged->stack_len;
+	if (sz > 0) {
+		return reader_max_update_index(st->merged->stack[sz - 1]) + 1;
+	}
+	return 1;
+}
+
+static int stack_compact_locked(struct stack *st, int first, int last,
+				struct slice *temp_tab,
+				struct log_expiry_config *config)
+{
+	struct slice next_name = {};
+	int tab_fd = -1;
+	struct writer *wr = NULL;
+	int err = 0;
+
+	format_name(&next_name,
+		    reader_min_update_index(st->merged->stack[first]),
+		    reader_max_update_index(st->merged->stack[first]));
+
+	slice_set_string(temp_tab, st->reftable_dir);
+	slice_append_string(temp_tab, "/");
+	slice_append(temp_tab, next_name);
+	slice_append_string(temp_tab, ".temp.XXXXXX");
+
+	tab_fd = mkstemp((char *)slice_as_string(temp_tab));
+	wr = new_writer(fd_writer, &tab_fd, &st->config);
+
+	err = stack_write_compact(st, wr, first, last, config);
+	if (err < 0) {
+		goto exit;
+	}
+	err = writer_close(wr);
+	if (err < 0) {
+		goto exit;
+	}
+	writer_free(wr);
+
+	err = close(tab_fd);
+	tab_fd = 0;
+
+exit:
+	if (tab_fd > 0) {
+		close(tab_fd);
+		tab_fd = 0;
+	}
+	if (err != 0 && temp_tab->len > 0) {
+		unlink(slice_as_string(temp_tab));
+		free(slice_yield(temp_tab));
+	}
+	free(slice_yield(&next_name));
+	return err;
+}
+
+int stack_write_compact(struct stack *st, struct writer *wr, int first,
+			int last, struct log_expiry_config *config)
+{
+	int subtabs_len = last - first + 1;
+	struct reader **subtabs =
+		calloc(sizeof(struct reader *), last - first + 1);
+	struct merged_table *mt = NULL;
+	int err = 0;
+	struct iterator it = {};
+	struct ref_record ref = {};
+	struct log_record log = {};
+
+	int i = 0, j = 0;
+	for (i = first, j = 0; i <= last; i++) {
+		struct reader *t = st->merged->stack[i];
+		subtabs[j++] = t;
+		st->stats.bytes += t->size;
+	}
+	writer_set_limits(wr, st->merged->stack[first]->min_update_index,
+			  st->merged->stack[last]->max_update_index);
+
+	err = new_merged_table(&mt, subtabs, subtabs_len, st->config.hash_size);
+	if (err < 0) {
+		free(subtabs);
+		goto exit;
+	}
+
+	err = merged_table_seek_ref(mt, &it, "");
+	if (err < 0) {
+		goto exit;
+	}
+
+	while (true) {
+		err = iterator_next_ref(it, &ref);
+		if (err > 0) {
+			err = 0;
+			break;
+		}
+		if (err < 0) {
+			break;
+		}
+		if (first == 0 && ref_record_is_deletion(&ref)) {
+			continue;
+		}
+
+		err = writer_add_ref(wr, &ref);
+		if (err < 0) {
+			break;
+		}
+	}
+
+	err = merged_table_seek_log(mt, &it, "");
+	if (err < 0) {
+		goto exit;
+	}
+
+	while (true) {
+		err = iterator_next_log(it, &log);
+		if (err > 0) {
+			err = 0;
+			break;
+		}
+		if (err < 0) {
+			break;
+		}
+		if (first == 0 && log_record_is_deletion(&log)) {
+			continue;
+		}
+
+		/* XXX collect stats? */
+
+		if (config != NULL && config->time > 0 &&
+		    log.time < config->time) {
+			continue;
+		}
+
+		if (config != NULL && config->min_update_index > 0 &&
+		    log.update_index < config->min_update_index) {
+			continue;
+		}
+
+		err = writer_add_log(wr, &log);
+		if (err < 0) {
+			break;
+		}
+	}
+
+exit:
+	iterator_destroy(&it);
+	if (mt != NULL) {
+		merged_table_clear(mt);
+		merged_table_free(mt);
+	}
+	ref_record_clear(&ref);
+
+	return err;
+}
+
+/* <  0: error. 0 == OK, > 0 attempt failed; could retry. */
+static int stack_compact_range(struct stack *st, int first, int last,
+			       struct log_expiry_config *expiry)
+{
+	struct slice temp_tab_name = {};
+	struct slice new_table_name = {};
+	struct slice lock_file_name = {};
+	struct slice ref_list_contents = {};
+	struct slice new_table_path = {};
+	int err = 0;
+	bool have_lock = false;
+	int lock_file_fd = 0;
+	int compact_count = last - first + 1;
+	char **delete_on_success = calloc(sizeof(char *), compact_count + 1);
+	char **subtable_locks = calloc(sizeof(char *), compact_count + 1);
+	int i = 0;
+	int j = 0;
+
+	if (first > last || (expiry == NULL && first == last)) {
+		err = 0;
+		goto exit;
+	}
+
+	st->stats.attempts++;
+
+	slice_set_string(&lock_file_name, st->list_file);
+	slice_append_string(&lock_file_name, ".lock");
+
+	lock_file_fd = open(slice_as_string(&lock_file_name),
+			    O_EXCL | O_CREAT | O_WRONLY, 0644);
+	if (lock_file_fd < 0) {
+		if (errno == EEXIST) {
+			err = 1;
+		} else {
+			err = IO_ERROR;
+		}
+		goto exit;
+	}
+	have_lock = true;
+	err = stack_uptodate(st);
+	if (err != 0) {
+		goto exit;
+	}
+
+	for (i = first, j = 0; i <= last; i++) {
+		struct slice subtab_name = {};
+		struct slice subtab_lock = {};
+		slice_set_string(&subtab_name, st->reftable_dir);
+		slice_append_string(&subtab_name, "/");
+		slice_append_string(&subtab_name,
+				    reader_name(st->merged->stack[i]));
+
+		slice_copy(&subtab_lock, subtab_name);
+		slice_append_string(&subtab_lock, ".lock");
+
+		{
+			int sublock_file_fd =
+				open(slice_as_string(&subtab_lock),
+				     O_EXCL | O_CREAT | O_WRONLY, 0644);
+			if (sublock_file_fd > 0) {
+				close(sublock_file_fd);
+			} else if (sublock_file_fd < 0) {
+				if (errno == EEXIST) {
+					err = 1;
+				}
+				err = IO_ERROR;
+			}
+		}
+
+		subtable_locks[j] = (char *)slice_as_string(&subtab_lock);
+		delete_on_success[j] = (char *)slice_as_string(&subtab_name);
+		j++;
+
+		if (err != 0) {
+			goto exit;
+		}
+	}
+
+	err = unlink(slice_as_string(&lock_file_name));
+	if (err < 0) {
+		goto exit;
+	}
+	have_lock = false;
+
+	err = stack_compact_locked(st, first, last, &temp_tab_name, expiry);
+	if (err < 0) {
+		goto exit;
+	}
+
+	lock_file_fd = open(slice_as_string(&lock_file_name),
+			    O_EXCL | O_CREAT | O_WRONLY, 0644);
+	if (lock_file_fd < 0) {
+		if (errno == EEXIST) {
+			err = 1;
+		} else {
+			err = IO_ERROR;
+		}
+		goto exit;
+	}
+	have_lock = true;
+
+	format_name(&new_table_name, st->merged->stack[first]->min_update_index,
+		    st->merged->stack[last]->max_update_index);
+	slice_append_string(&new_table_name, ".ref");
+
+	slice_set_string(&new_table_path, st->reftable_dir);
+	slice_append_string(&new_table_path, "/");
+
+	slice_append(&new_table_path, new_table_name);
+
+	err = rename(slice_as_string(&temp_tab_name),
+		     slice_as_string(&new_table_path));
+	if (err < 0) {
+		goto exit;
+	}
+
+	for (i = 0; i < first; i++) {
+		slice_append_string(&ref_list_contents,
+				    st->merged->stack[i]->name);
+		slice_append_string(&ref_list_contents, "\n");
+	}
+	slice_append(&ref_list_contents, new_table_name);
+	slice_append_string(&ref_list_contents, "\n");
+	for (i = last + 1; i < st->merged->stack_len; i++) {
+		slice_append_string(&ref_list_contents,
+				    st->merged->stack[i]->name);
+		slice_append_string(&ref_list_contents, "\n");
+	}
+
+	err = write(lock_file_fd, ref_list_contents.buf, ref_list_contents.len);
+	if (err < 0) {
+		unlink(slice_as_string(&new_table_path));
+		goto exit;
+	}
+	err = close(lock_file_fd);
+	lock_file_fd = 0;
+	if (err < 0) {
+		unlink(slice_as_string(&new_table_path));
+		goto exit;
+	}
+
+	err = rename(slice_as_string(&lock_file_name), st->list_file);
+	if (err < 0) {
+		unlink(slice_as_string(&new_table_path));
+		goto exit;
+	}
+	have_lock = false;
+
+	for (char **p = delete_on_success; *p; p++) {
+		if (strcmp(*p, slice_as_string(&new_table_path))) {
+			unlink(*p);
+		}
+	}
+
+	err = stack_reload_maybe_reuse(st, first < last);
+exit:
+	for (char **p = subtable_locks; *p; p++) {
+		unlink(*p);
+	}
+	free_names(delete_on_success);
+	free_names(subtable_locks);
+	if (lock_file_fd > 0) {
+		close(lock_file_fd);
+		lock_file_fd = 0;
+	}
+	if (have_lock) {
+		unlink(slice_as_string(&lock_file_name));
+	}
+	free(slice_yield(&new_table_name));
+	free(slice_yield(&new_table_path));
+	free(slice_yield(&ref_list_contents));
+	free(slice_yield(&temp_tab_name));
+	free(slice_yield(&lock_file_name));
+	return err;
+}
+
+int stack_compact_all(struct stack *st, struct log_expiry_config *config)
+{
+	return stack_compact_range(st, 0, st->merged->stack_len - 1, config);
+}
+
+static int stack_compact_range_stats(struct stack *st, int first, int last,
+				     struct log_expiry_config *config)
+{
+	int err = stack_compact_range(st, first, last, config);
+	if (err > 0) {
+		st->stats.failures++;
+	}
+	return err;
+}
+
+static int segment_size(struct segment *s)
+{
+	return s->end - s->start;
+}
+
+int fastlog2(uint64_t sz)
+{
+	int l = 0;
+	assert(sz > 0);
+	for (; sz; sz /= 2) {
+		l++;
+	}
+	return l - 1;
+}
+
+struct segment *sizes_to_segments(int *seglen, uint64_t *sizes, int n)
+{
+	struct segment *segs = calloc(sizeof(struct segment), n);
+	int next = 0;
+	struct segment cur = {};
+	int i = 0;
+	for (i = 0; i < n; i++) {
+		int log = fastlog2(sizes[i]);
+		if (cur.log != log && cur.bytes > 0) {
+			struct segment fresh = {
+				.start = i,
+			};
+
+			segs[next++] = cur;
+			cur = fresh;
+		}
+
+		cur.log = log;
+		cur.end = i + 1;
+		cur.bytes += sizes[i];
+	}
+
+	segs[next++] = cur;
+	*seglen = next;
+	return segs;
+}
+
+struct segment suggest_compaction_segment(uint64_t *sizes, int n)
+{
+	int seglen = 0;
+	struct segment *segs = sizes_to_segments(&seglen, sizes, n);
+	struct segment min_seg = {
+		.log = 64,
+	};
+	int i = 0;
+	for (i = 0; i < seglen; i++) {
+		if (segment_size(&segs[i]) == 1) {
+			continue;
+		}
+
+		if (segs[i].log < min_seg.log) {
+			min_seg = segs[i];
+		}
+	}
+
+	while (min_seg.start > 0) {
+		int prev = min_seg.start - 1;
+		if (fastlog2(min_seg.bytes) < fastlog2(sizes[prev])) {
+			break;
+		}
+
+		min_seg.start = prev;
+		min_seg.bytes += sizes[prev];
+	}
+
+	free(segs);
+	return min_seg;
+}
+
+static uint64_t *stack_table_sizes_for_compaction(struct stack *st)
+{
+	uint64_t *sizes = calloc(sizeof(uint64_t), st->merged->stack_len);
+	int i = 0;
+	for (i = 0; i < st->merged->stack_len; i++) {
+		/* overhead is 24 + 68 = 92. */
+		sizes[i] = st->merged->stack[i]->size - 91;
+	}
+	return sizes;
+}
+
+int stack_auto_compact(struct stack *st)
+{
+	uint64_t *sizes = stack_table_sizes_for_compaction(st);
+	struct segment seg =
+		suggest_compaction_segment(sizes, st->merged->stack_len);
+	free(sizes);
+	if (segment_size(&seg) > 0) {
+		return stack_compact_range_stats(st, seg.start, seg.end - 1,
+						 NULL);
+	}
+
+	return 0;
+}
+
+struct compaction_stats *stack_compaction_stats(struct stack *st)
+{
+	return &st->stats;
+}
+
+int stack_read_ref(struct stack *st, const char *refname,
+		   struct ref_record *ref)
+{
+	struct iterator it = {};
+	struct merged_table *mt = stack_merged_table(st);
+	int err = merged_table_seek_ref(mt, &it, refname);
+	if (err) {
+		goto exit;
+	}
+
+	err = iterator_next_ref(it, ref);
+	if (err) {
+		goto exit;
+	}
+
+	if (strcmp(ref->ref_name, refname) || ref_record_is_deletion(ref)) {
+		err = 1;
+		goto exit;
+	}
+
+exit:
+	iterator_destroy(&it);
+	return err;
+}
+
+int stack_read_log(struct stack *st, const char *refname,
+		   struct log_record *log)
+{
+	struct iterator it = {};
+	struct merged_table *mt = stack_merged_table(st);
+	int err = merged_table_seek_log(mt, &it, refname);
+	if (err) {
+		goto exit;
+	}
+
+	err = iterator_next_log(it, log);
+	if (err) {
+		goto exit;
+	}
+
+	if (strcmp(log->ref_name, refname) || log_record_is_deletion(log)) {
+		err = 1;
+		goto exit;
+	}
+
+exit:
+	iterator_destroy(&it);
+	return err;
+}
diff --git a/reftable/stack.h b/reftable/stack.h
new file mode 100644
index 00000000000..d5e2c93c293
--- /dev/null
+++ b/reftable/stack.h
@@ -0,0 +1,40 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef STACK_H
+#define STACK_H
+
+#include "reftable.h"
+
+struct stack {
+	char *list_file;
+	char *reftable_dir;
+
+	struct write_options config;
+
+	struct merged_table *merged;
+	struct compaction_stats stats;
+};
+
+int read_lines(const char *filename, char ***lines);
+int stack_try_add(struct stack *st,
+		  int (*write_table)(struct writer *wr, void *arg), void *arg);
+int stack_write_compact(struct stack *st, struct writer *wr, int first,
+			int last, struct log_expiry_config *config);
+int fastlog2(uint64_t sz);
+
+struct segment {
+	int start, end;
+	int log;
+	uint64_t bytes;
+};
+
+struct segment *sizes_to_segments(int *seglen, uint64_t *sizes, int n);
+struct segment suggest_compaction_segment(uint64_t *sizes, int n);
+
+#endif
diff --git a/reftable/system.h b/reftable/system.h
new file mode 100644
index 00000000000..1ab80f6a100
--- /dev/null
+++ b/reftable/system.h
@@ -0,0 +1,59 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef SYSTEM_H
+#define SYSTEM_H
+
+#if 1 /* REFTABLE_IN_GITCORE */
+
+#include "git-compat-util.h"
+#include <zlib.h>
+
+#else
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <zlib.h>
+
+#define ARRAY_SIZE(a) sizeof((a)) / sizeof((a)[0])
+#define FREE_AND_NULL(x)    \
+	do {                \
+		free(x);    \
+		(x) = NULL; \
+	} while (0)
+#define QSORT(arr, n, cmp) qsort(arr, n, sizeof(arr[0]), cmp)
+#define SWAP(a, b)                              \
+	{                                       \
+		char tmp[sizeof(a)];            \
+		assert(sizeof(a) == sizeof(b)); \
+		memcpy(&tmp[0], &a, sizeof(a)); \
+		memcpy(&a, &b, sizeof(a));      \
+		memcpy(&b, &tmp[0], sizeof(a)); \
+	}
+#endif /* REFTABLE_IN_GITCORE */
+
+typedef uint8_t byte;
+typedef int bool;
+
+int uncompress_return_consumed(Bytef *dest, uLongf *destLen,
+			       const Bytef *source, uLong *sourceLen);
+
+#define SHA1_SIZE 20
+#define SHA256_SIZE 32
+
+#endif
diff --git a/reftable/tree.c b/reftable/tree.c
new file mode 100644
index 00000000000..9bf7fe531ff
--- /dev/null
+++ b/reftable/tree.c
@@ -0,0 +1,66 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "tree.h"
+
+#include "system.h"
+
+struct tree_node *tree_search(void *key, struct tree_node **rootp,
+			      int (*compare)(const void *, const void *),
+			      int insert)
+{
+	if (*rootp == NULL) {
+		if (!insert) {
+			return NULL;
+		} else {
+			struct tree_node *n =
+				calloc(sizeof(struct tree_node), 1);
+			n->key = key;
+			*rootp = n;
+			return *rootp;
+		}
+	}
+
+	{
+		int res = compare(key, (*rootp)->key);
+		if (res < 0) {
+			return tree_search(key, &(*rootp)->left, compare,
+					   insert);
+		} else if (res > 0) {
+			return tree_search(key, &(*rootp)->right, compare,
+					   insert);
+		}
+	}
+	return *rootp;
+}
+
+void infix_walk(struct tree_node *t, void (*action)(void *arg, void *key),
+		void *arg)
+{
+	if (t->left != NULL) {
+		infix_walk(t->left, action, arg);
+	}
+	action(arg, t->key);
+	if (t->right != NULL) {
+		infix_walk(t->right, action, arg);
+	}
+}
+
+void tree_free(struct tree_node *t)
+{
+	if (t == NULL) {
+		return;
+	}
+	if (t->left != NULL) {
+		tree_free(t->left);
+	}
+	if (t->right != NULL) {
+		tree_free(t->right);
+	}
+	free(t);
+}
diff --git a/reftable/tree.h b/reftable/tree.h
new file mode 100644
index 00000000000..86a71715aee
--- /dev/null
+++ b/reftable/tree.h
@@ -0,0 +1,24 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef TREE_H
+#define TREE_H
+
+struct tree_node {
+	void *key;
+	struct tree_node *left, *right;
+};
+
+struct tree_node *tree_search(void *key, struct tree_node **rootp,
+			      int (*compare)(const void *, const void *),
+			      int insert);
+void infix_walk(struct tree_node *t, void (*action)(void *arg, void *key),
+		void *arg);
+void tree_free(struct tree_node *t);
+
+#endif
diff --git a/reftable/writer.c b/reftable/writer.c
new file mode 100644
index 00000000000..5a61288663d
--- /dev/null
+++ b/reftable/writer.c
@@ -0,0 +1,630 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "writer.h"
+
+#include "system.h"
+
+#include "block.h"
+#include "constants.h"
+#include "record.h"
+#include "reftable.h"
+#include "tree.h"
+
+static struct block_stats *writer_block_stats(struct writer *w, byte typ)
+{
+	switch (typ) {
+	case 'r':
+		return &w->stats.ref_stats;
+	case 'o':
+		return &w->stats.obj_stats;
+	case 'i':
+		return &w->stats.idx_stats;
+	case 'g':
+		return &w->stats.log_stats;
+	}
+	assert(false);
+	return NULL;
+}
+
+/* write data, queuing the padding for the next write. Returns negative for
+ * error. */
+static int padded_write(struct writer *w, byte *data, size_t len, int padding)
+{
+	int n = 0;
+	if (w->pending_padding > 0) {
+		byte *zeroed = calloc(w->pending_padding, 1);
+		int n = w->write(w->write_arg, zeroed, w->pending_padding);
+		if (n < 0) {
+			return n;
+		}
+
+		w->pending_padding = 0;
+		free(zeroed);
+	}
+
+	w->pending_padding = padding;
+	n = w->write(w->write_arg, data, len);
+	if (n < 0) {
+		return n;
+	}
+	n += padding;
+	return 0;
+}
+
+static void options_set_defaults(struct write_options *opts)
+{
+	if (opts->restart_interval == 0) {
+		opts->restart_interval = 16;
+	}
+
+	if (opts->hash_size == 0) {
+		opts->hash_size = SHA1_SIZE;
+	}
+	if (opts->block_size == 0) {
+		opts->block_size = DEFAULT_BLOCK_SIZE;
+	}
+}
+
+static int writer_write_header(struct writer *w, byte *dest)
+{
+	memcpy((char *)dest, "REFT", 4);
+	dest[4] = (w->hash_size == SHA1_SIZE) ? 1 : 2; /* version */
+
+	put_u24(dest + 5, w->opts.block_size);
+	put_u64(dest + 8, w->min_update_index);
+	put_u64(dest + 16, w->max_update_index);
+	return 24;
+}
+
+static void writer_reinit_block_writer(struct writer *w, byte typ)
+{
+	int block_start = 0;
+	if (w->next == 0) {
+		block_start = HEADER_SIZE;
+	}
+
+	block_writer_init(&w->block_writer_data, typ, w->block,
+			  w->opts.block_size, block_start, w->hash_size);
+	w->block_writer = &w->block_writer_data;
+	w->block_writer->restart_interval = w->opts.restart_interval;
+}
+
+struct writer *new_writer(int (*writer_func)(void *, byte *, int),
+			  void *writer_arg, struct write_options *opts)
+{
+	struct writer *wp = calloc(sizeof(struct writer), 1);
+	options_set_defaults(opts);
+	if (opts->block_size >= (1 << 24)) {
+		/* TODO - error return? */
+		abort();
+	}
+	wp->hash_size = opts->hash_size;
+	wp->block = calloc(opts->block_size, 1);
+	wp->write = writer_func;
+	wp->write_arg = writer_arg;
+	wp->opts = *opts;
+	writer_reinit_block_writer(wp, BLOCK_TYPE_REF);
+
+	return wp;
+}
+
+void writer_set_limits(struct writer *w, uint64_t min, uint64_t max)
+{
+	w->min_update_index = min;
+	w->max_update_index = max;
+}
+
+void writer_free(struct writer *w)
+{
+	free(w->block);
+	free(w);
+}
+
+struct obj_index_tree_node {
+	struct slice hash;
+	uint64_t *offsets;
+	int offset_len;
+	int offset_cap;
+};
+
+static int obj_index_tree_node_compare(const void *a, const void *b)
+{
+	return slice_compare(((const struct obj_index_tree_node *)a)->hash,
+			     ((const struct obj_index_tree_node *)b)->hash);
+}
+
+static void writer_index_hash(struct writer *w, struct slice hash)
+{
+	uint64_t off = w->next;
+
+	struct obj_index_tree_node want = { .hash = hash };
+
+	struct tree_node *node = tree_search(&want, &w->obj_index_tree,
+					     &obj_index_tree_node_compare, 0);
+	struct obj_index_tree_node *key = NULL;
+	if (node == NULL) {
+		key = calloc(sizeof(struct obj_index_tree_node), 1);
+		slice_copy(&key->hash, hash);
+		tree_search((void *)key, &w->obj_index_tree,
+			    &obj_index_tree_node_compare, 1);
+	} else {
+		key = node->key;
+	}
+
+	if (key->offset_len > 0 && key->offsets[key->offset_len - 1] == off) {
+		return;
+	}
+
+	if (key->offset_len == key->offset_cap) {
+		key->offset_cap = 2 * key->offset_cap + 1;
+		key->offsets = realloc(key->offsets,
+				       sizeof(uint64_t) * key->offset_cap);
+	}
+
+	key->offsets[key->offset_len++] = off;
+}
+
+static int writer_add_record(struct writer *w, struct record rec)
+{
+	int result = -1;
+	struct slice key = {};
+	int err = 0;
+	record_key(rec, &key);
+	if (slice_compare(w->last_key, key) >= 0) {
+		goto exit;
+	}
+
+	slice_copy(&w->last_key, key);
+	if (w->block_writer == NULL) {
+		writer_reinit_block_writer(w, record_type(rec));
+	}
+
+	assert(block_writer_type(w->block_writer) == record_type(rec));
+
+	if (block_writer_add(w->block_writer, rec) == 0) {
+		result = 0;
+		goto exit;
+	}
+
+	err = writer_flush_block(w);
+	if (err < 0) {
+		result = err;
+		goto exit;
+	}
+
+	writer_reinit_block_writer(w, record_type(rec));
+	err = block_writer_add(w->block_writer, rec);
+	if (err < 0) {
+		result = err;
+		goto exit;
+	}
+
+	result = 0;
+exit:
+	free(slice_yield(&key));
+	return result;
+}
+
+int writer_add_ref(struct writer *w, struct ref_record *ref)
+{
+	struct record rec = {};
+	struct ref_record copy = *ref;
+	int err = 0;
+
+	if (ref->ref_name == NULL) {
+		return API_ERROR;
+	}
+	if (ref->update_index < w->min_update_index ||
+	    ref->update_index > w->max_update_index) {
+		return API_ERROR;
+	}
+
+	record_from_ref(&rec, &copy);
+	copy.update_index -= w->min_update_index;
+	err = writer_add_record(w, rec);
+	if (err < 0) {
+		return err;
+	}
+
+	if (!w->opts.skip_index_objects && ref->value != NULL) {
+		struct slice h = {
+			.buf = ref->value,
+			.len = w->hash_size,
+		};
+
+		writer_index_hash(w, h);
+	}
+	if (!w->opts.skip_index_objects && ref->target_value != NULL) {
+		struct slice h = {
+			.buf = ref->target_value,
+			.len = w->hash_size,
+		};
+		writer_index_hash(w, h);
+	}
+	return 0;
+}
+
+int writer_add_refs(struct writer *w, struct ref_record *refs, int n)
+{
+	int err = 0;
+	int i = 0;
+	QSORT(refs, n, ref_record_compare_name);
+	for (i = 0; err == 0 && i < n; i++) {
+		err = writer_add_ref(w, &refs[i]);
+	}
+	return err;
+}
+
+int writer_add_log(struct writer *w, struct log_record *log)
+{
+	if (log->ref_name == NULL) {
+		return API_ERROR;
+	}
+
+	if (w->block_writer != NULL &&
+	    block_writer_type(w->block_writer) == BLOCK_TYPE_REF) {
+		int err = writer_finish_public_section(w);
+		if (err < 0) {
+			return err;
+		}
+	}
+
+	w->next -= w->pending_padding;
+	w->pending_padding = 0;
+
+	{
+		struct record rec = {};
+		int err;
+		record_from_log(&rec, log);
+		err = writer_add_record(w, rec);
+		return err;
+	}
+}
+
+int writer_add_logs(struct writer *w, struct log_record *logs, int n)
+{
+	int err = 0;
+	int i = 0;
+	QSORT(logs, n, log_record_compare_key);
+	for (i = 0; err == 0 && i < n; i++) {
+		err = writer_add_log(w, &logs[i]);
+	}
+	return err;
+}
+
+static int writer_finish_section(struct writer *w)
+{
+	byte typ = block_writer_type(w->block_writer);
+	uint64_t index_start = 0;
+	int max_level = 0;
+	int threshold = w->opts.unpadded ? 1 : 3;
+	int before_blocks = w->stats.idx_stats.blocks;
+	int err = writer_flush_block(w);
+	int i = 0;
+	if (err < 0) {
+		return err;
+	}
+
+	while (w->index_len > threshold) {
+		struct index_record *idx = NULL;
+		int idx_len = 0;
+
+		max_level++;
+		index_start = w->next;
+		writer_reinit_block_writer(w, BLOCK_TYPE_INDEX);
+
+		idx = w->index;
+		idx_len = w->index_len;
+
+		w->index = NULL;
+		w->index_len = 0;
+		w->index_cap = 0;
+		for (i = 0; i < idx_len; i++) {
+			struct record rec = {};
+			record_from_index(&rec, idx + i);
+			if (block_writer_add(w->block_writer, rec) == 0) {
+				continue;
+			}
+
+			{
+				int err = writer_flush_block(w);
+				if (err < 0) {
+					return err;
+				}
+			}
+
+			writer_reinit_block_writer(w, BLOCK_TYPE_INDEX);
+
+			err = block_writer_add(w->block_writer, rec);
+			assert(err == 0);
+		}
+		for (i = 0; i < idx_len; i++) {
+			free(slice_yield(&idx[i].last_key));
+		}
+		free(idx);
+	}
+
+	writer_clear_index(w);
+
+	err = writer_flush_block(w);
+	if (err < 0) {
+		return err;
+	}
+
+	{
+		struct block_stats *bstats = writer_block_stats(w, typ);
+		bstats->index_blocks =
+			w->stats.idx_stats.blocks - before_blocks;
+		bstats->index_offset = index_start;
+		bstats->max_index_level = max_level;
+	}
+
+	/* Reinit lastKey, as the next section can start with any key. */
+	w->last_key.len = 0;
+
+	return 0;
+}
+
+struct common_prefix_arg {
+	struct slice *last;
+	int max;
+};
+
+static void update_common(void *void_arg, void *key)
+{
+	struct common_prefix_arg *arg = (struct common_prefix_arg *)void_arg;
+	struct obj_index_tree_node *entry = (struct obj_index_tree_node *)key;
+	if (arg->last != NULL) {
+		int n = common_prefix_size(entry->hash, *arg->last);
+		if (n > arg->max) {
+			arg->max = n;
+		}
+	}
+	arg->last = &entry->hash;
+}
+
+struct write_record_arg {
+	struct writer *w;
+	int err;
+};
+
+static void write_object_record(void *void_arg, void *key)
+{
+	struct write_record_arg *arg = (struct write_record_arg *)void_arg;
+	struct obj_index_tree_node *entry = (struct obj_index_tree_node *)key;
+	struct obj_record obj_rec = {
+		.hash_prefix = entry->hash.buf,
+		.hash_prefix_len = arg->w->stats.object_id_len,
+		.offsets = entry->offsets,
+		.offset_len = entry->offset_len,
+	};
+	struct record rec = {};
+	if (arg->err < 0) {
+		goto exit;
+	}
+
+	record_from_obj(&rec, &obj_rec);
+	arg->err = block_writer_add(arg->w->block_writer, rec);
+	if (arg->err == 0) {
+		goto exit;
+	}
+
+	arg->err = writer_flush_block(arg->w);
+	if (arg->err < 0) {
+		goto exit;
+	}
+
+	writer_reinit_block_writer(arg->w, BLOCK_TYPE_OBJ);
+	arg->err = block_writer_add(arg->w->block_writer, rec);
+	if (arg->err == 0) {
+		goto exit;
+	}
+	obj_rec.offset_len = 0;
+	arg->err = block_writer_add(arg->w->block_writer, rec);
+
+	/* Should be able to write into a fresh block. */
+	assert(arg->err == 0);
+
+exit:;
+}
+
+static void object_record_free(void *void_arg, void *key)
+{
+	struct obj_index_tree_node *entry = (struct obj_index_tree_node *)key;
+
+	FREE_AND_NULL(entry->offsets);
+	free(slice_yield(&entry->hash));
+	free(entry);
+}
+
+static int writer_dump_object_index(struct writer *w)
+{
+	struct write_record_arg closure = { .w = w };
+	struct common_prefix_arg common = {};
+	if (w->obj_index_tree != NULL) {
+		infix_walk(w->obj_index_tree, &update_common, &common);
+	}
+	w->stats.object_id_len = common.max + 1;
+
+	writer_reinit_block_writer(w, BLOCK_TYPE_OBJ);
+
+	if (w->obj_index_tree != NULL) {
+		infix_walk(w->obj_index_tree, &write_object_record, &closure);
+	}
+
+	if (closure.err < 0) {
+		return closure.err;
+	}
+	return writer_finish_section(w);
+}
+
+int writer_finish_public_section(struct writer *w)
+{
+	byte typ = 0;
+	int err = 0;
+
+	if (w->block_writer == NULL) {
+		return 0;
+	}
+
+	typ = block_writer_type(w->block_writer);
+	err = writer_finish_section(w);
+	if (err < 0) {
+		return err;
+	}
+	if (typ == BLOCK_TYPE_REF && !w->opts.skip_index_objects &&
+	    w->stats.ref_stats.index_blocks > 0) {
+		err = writer_dump_object_index(w);
+		if (err < 0) {
+			return err;
+		}
+	}
+
+	if (w->obj_index_tree != NULL) {
+		infix_walk(w->obj_index_tree, &object_record_free, NULL);
+		tree_free(w->obj_index_tree);
+		w->obj_index_tree = NULL;
+	}
+
+	w->block_writer = NULL;
+	return 0;
+}
+
+int writer_close(struct writer *w)
+{
+	byte footer[68];
+	byte *p = footer;
+
+	int err = writer_finish_public_section(w);
+	if (err != 0) {
+		return err;
+	}
+
+	writer_write_header(w, footer);
+	p += 24;
+	put_u64(p, w->stats.ref_stats.index_offset);
+	p += 8;
+	put_u64(p, (w->stats.obj_stats.offset) << 5 | w->stats.object_id_len);
+	p += 8;
+	put_u64(p, w->stats.obj_stats.index_offset);
+	p += 8;
+
+	put_u64(p, w->stats.log_stats.offset);
+	p += 8;
+	put_u64(p, w->stats.log_stats.index_offset);
+	p += 8;
+
+	put_u32(p, crc32(0, footer, p - footer));
+	p += 4;
+	w->pending_padding = 0;
+
+	{
+		int n = padded_write(w, footer, sizeof(footer), 0);
+		if (n < 0) {
+			return n;
+		}
+	}
+
+	/* free up memory. */
+	block_writer_clear(&w->block_writer_data);
+	writer_clear_index(w);
+	free(slice_yield(&w->last_key));
+	return 0;
+}
+
+void writer_clear_index(struct writer *w)
+{
+	int i = 0;
+	for (i = 0; i < w->index_len; i++) {
+		free(slice_yield(&w->index[i].last_key));
+	}
+
+	FREE_AND_NULL(w->index);
+	w->index_len = 0;
+	w->index_cap = 0;
+}
+
+const int debug = 0;
+
+static int writer_flush_nonempty_block(struct writer *w)
+{
+	byte typ = block_writer_type(w->block_writer);
+	struct block_stats *bstats = writer_block_stats(w, typ);
+	uint64_t block_typ_off = (bstats->blocks == 0) ? w->next : 0;
+	int raw_bytes = block_writer_finish(w->block_writer);
+	int padding = 0;
+	int err = 0;
+	if (raw_bytes < 0) {
+		return raw_bytes;
+	}
+
+	if (!w->opts.unpadded && typ != BLOCK_TYPE_LOG) {
+		padding = w->opts.block_size - raw_bytes;
+	}
+
+	if (block_typ_off > 0) {
+		bstats->offset = block_typ_off;
+	}
+
+	bstats->entries += w->block_writer->entries;
+	bstats->restarts += w->block_writer->restart_len;
+	bstats->blocks++;
+	w->stats.blocks++;
+
+	if (debug) {
+		fprintf(stderr, "block %c off %" PRIu64 " sz %d (%d)\n", typ,
+			w->next, raw_bytes,
+			get_u24(w->block + w->block_writer->header_off + 1));
+	}
+
+	if (w->next == 0) {
+		writer_write_header(w, w->block);
+	}
+
+	err = padded_write(w, w->block, raw_bytes, padding);
+	if (err < 0) {
+		return err;
+	}
+
+	if (w->index_cap == w->index_len) {
+		w->index_cap = 2 * w->index_cap + 1;
+		w->index = realloc(w->index,
+				   sizeof(struct index_record) * w->index_cap);
+	}
+
+	{
+		struct index_record ir = {
+			.offset = w->next,
+		};
+		slice_copy(&ir.last_key, w->block_writer->last_key);
+		w->index[w->index_len] = ir;
+	}
+
+	w->index_len++;
+	w->next += padding + raw_bytes;
+	block_writer_reset(&w->block_writer_data);
+	w->block_writer = NULL;
+	return 0;
+}
+
+int writer_flush_block(struct writer *w)
+{
+	if (w->block_writer == NULL) {
+		return 0;
+	}
+	if (w->block_writer->entries == 0) {
+		return 0;
+	}
+	return writer_flush_nonempty_block(w);
+}
+
+struct stats *writer_stats(struct writer *w)
+{
+	return &w->stats;
+}
diff --git a/reftable/writer.h b/reftable/writer.h
new file mode 100644
index 00000000000..bd2386474b2
--- /dev/null
+++ b/reftable/writer.h
@@ -0,0 +1,46 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef WRITER_H
+#define WRITER_H
+
+#include "basics.h"
+#include "block.h"
+#include "reftable.h"
+#include "slice.h"
+#include "tree.h"
+
+struct writer {
+	int (*write)(void *, byte *, int);
+	void *write_arg;
+	int pending_padding;
+	int hash_size;
+	struct slice last_key;
+
+	uint64_t next;
+	uint64_t min_update_index, max_update_index;
+	struct write_options opts;
+
+	byte *block;
+	struct block_writer *block_writer;
+	struct block_writer block_writer_data;
+	struct index_record *index;
+	int index_len;
+	int index_cap;
+
+	/* tree for use with tsearch */
+	struct tree_node *obj_index_tree;
+
+	struct stats stats;
+};
+
+int writer_flush_block(struct writer *w);
+void writer_clear_index(struct writer *w);
+int writer_finish_public_section(struct writer *w);
+
+#endif
diff --git a/reftable/zlib-compat.c b/reftable/zlib-compat.c
new file mode 100644
index 00000000000..3e0b0f24f1c
--- /dev/null
+++ b/reftable/zlib-compat.c
@@ -0,0 +1,92 @@
+/* taken from zlib's uncompr.c
+
+   commit cacf7f1d4e3d44d871b605da3b647f07d718623f
+   Author: Mark Adler <madler@alumni.caltech.edu>
+   Date:   Sun Jan 15 09:18:46 2017 -0800
+
+       zlib 1.2.11
+
+*/
+
+/*
+ * Copyright (C) 1995-2003, 2010, 2014, 2016 Jean-loup Gailly, Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+#include "system.h"
+
+/* clang-format off */
+
+/* ===========================================================================
+     Decompresses the source buffer into the destination buffer.  *sourceLen is
+   the byte length of the source buffer. Upon entry, *destLen is the total size
+   of the destination buffer, which must be large enough to hold the entire
+   uncompressed data. (The size of the uncompressed data must have been saved
+   previously by the compressor and transmitted to the decompressor by some
+   mechanism outside the scope of this compression library.) Upon exit,
+   *destLen is the size of the decompressed data and *sourceLen is the number
+   of source bytes consumed. Upon return, source + *sourceLen points to the
+   first unused input byte.
+
+     uncompress returns Z_OK if success, Z_MEM_ERROR if there was not enough
+   memory, Z_BUF_ERROR if there was not enough room in the output buffer, or
+   Z_DATA_ERROR if the input data was corrupted, including if the input data is
+   an incomplete zlib stream.
+*/
+int ZEXPORT uncompress_return_consumed (
+    Bytef *dest,
+    uLongf *destLen,
+    const Bytef *source,
+    uLong *sourceLen) {
+    z_stream stream;
+    int err;
+    const uInt max = (uInt)-1;
+    uLong len, left;
+    Byte buf[1];    /* for detection of incomplete stream when *destLen == 0 */
+
+    len = *sourceLen;
+    if (*destLen) {
+        left = *destLen;
+        *destLen = 0;
+    }
+    else {
+        left = 1;
+        dest = buf;
+    }
+
+    stream.next_in = (z_const Bytef *)source;
+    stream.avail_in = 0;
+    stream.zalloc = (alloc_func)0;
+    stream.zfree = (free_func)0;
+    stream.opaque = (voidpf)0;
+
+    err = inflateInit(&stream);
+    if (err != Z_OK) return err;
+
+    stream.next_out = dest;
+    stream.avail_out = 0;
+
+    do {
+        if (stream.avail_out == 0) {
+            stream.avail_out = left > (uLong)max ? max : (uInt)left;
+            left -= stream.avail_out;
+        }
+        if (stream.avail_in == 0) {
+            stream.avail_in = len > (uLong)max ? max : (uInt)len;
+            len -= stream.avail_in;
+        }
+        err = inflate(&stream, Z_NO_FLUSH);
+    } while (err == Z_OK);
+
+    *sourceLen -= len + stream.avail_in;
+    if (dest != buf)
+        *destLen = stream.total_out;
+    else if (stream.total_out && err == Z_BUF_ERROR)
+        left = 1;
+
+    inflateEnd(&stream);
+    return err == Z_STREAM_END ? Z_OK :
+           err == Z_NEED_DICT ? Z_DATA_ERROR  :
+           err == Z_BUF_ERROR && left + stream.avail_out ? Z_DATA_ERROR :
+           err;
+}
-- 
gitgitgadget


^ permalink raw reply related	[relevance 1%]

* [PATCH v5 4/5] Add reftable library
  @ 2020-02-10 14:14  1%         ` Han-Wen Nienhuys via GitGitGadget
    1 sibling, 0 replies; 200+ results
From: Han-Wen Nienhuys via GitGitGadget @ 2020-02-10 14:14 UTC (permalink / raw)
  To: git; +Cc: Han-Wen Nienhuys, Han-Wen Nienhuys

From: Han-Wen Nienhuys <hanwen@google.com>

Reftable is a new format for storing the ref database. It provides the
following benefits:

 * Simple and fast atomic ref transactions, including multiple refs and reflogs.
 * Compact storage of ref data.
 * Fast look ups of ref data.
 * Case-sensitive ref names on Windows/OSX, regardless of file system
 * Eliminates file/directory conflicts in ref names

Further context and motivation can be found in background reading:

* Spec: https://github.com/eclipse/jgit/blob/master/Documentation/technical/reftable.md

* Original discussion on JGit-dev:  https://www.eclipse.org/lists/jgit-dev/msg03389.html

* First design discussion on git@vger: https://public-inbox.org/git/CAJo=hJtTp2eA3z9wW9cHo-nA7kK40vVThqh6inXpbCcqfdMP9g@mail.gmail.com/

* Last design discussion on git@vger: https://public-inbox.org/git/CAJo=hJsZcAM9sipdVr7TMD-FD2V2W6_pvMQ791EGCDsDkQ033w@mail.gmail.com/

* First attempt at implementation: https://public-inbox.org/git/CAP8UFD0PPZSjBnxCA7ez91vBuatcHKQ+JUWvTD1iHcXzPBjPBg@mail.gmail.com/

* libgit2 support issue: https://github.com/libgit2/libgit2/issues

* GitLab support issue: https://gitlab.com/gitlab-org/git/issues/6

* go-git support issue: https://github.com/src-d/go-git/issues/1059

Signed-off-by: Han-Wen Nienhuys <hanwen@google.com>
---
 reftable/LICENSE       |   31 ++
 reftable/README.md     |   19 +
 reftable/VERSION       |    5 +
 reftable/basics.c      |  196 +++++++
 reftable/basics.h      |   37 ++
 reftable/block.c       |  401 +++++++++++++++
 reftable/block.h       |   71 +++
 reftable/blocksource.h |   20 +
 reftable/bytes.c       |    0
 reftable/config.h      |    1 +
 reftable/constants.h   |   27 +
 reftable/dump.c        |   97 ++++
 reftable/file.c        |   97 ++++
 reftable/iter.c        |  229 +++++++++
 reftable/iter.h        |   56 ++
 reftable/merged.c      |  286 +++++++++++
 reftable/merged.h      |   34 ++
 reftable/pq.c          |  114 +++++
 reftable/pq.h          |   34 ++
 reftable/reader.c      |  708 +++++++++++++++++++++++++
 reftable/reader.h      |   52 ++
 reftable/record.c      | 1107 ++++++++++++++++++++++++++++++++++++++++
 reftable/record.h      |   79 +++
 reftable/reftable.h    |  394 ++++++++++++++
 reftable/slice.c       |  199 ++++++++
 reftable/slice.h       |   39 ++
 reftable/stack.c       |  983 +++++++++++++++++++++++++++++++++++
 reftable/stack.h       |   40 ++
 reftable/system.h      |   58 +++
 reftable/tree.c        |   66 +++
 reftable/tree.h        |   24 +
 reftable/writer.c      |  623 ++++++++++++++++++++++
 reftable/writer.h      |   46 ++
 reftable/zlib-compat.c |   92 ++++
 34 files changed, 6265 insertions(+)
 create mode 100644 reftable/LICENSE
 create mode 100644 reftable/README.md
 create mode 100644 reftable/VERSION
 create mode 100644 reftable/basics.c
 create mode 100644 reftable/basics.h
 create mode 100644 reftable/block.c
 create mode 100644 reftable/block.h
 create mode 100644 reftable/blocksource.h
 create mode 100644 reftable/bytes.c
 create mode 100644 reftable/config.h
 create mode 100644 reftable/constants.h
 create mode 100644 reftable/dump.c
 create mode 100644 reftable/file.c
 create mode 100644 reftable/iter.c
 create mode 100644 reftable/iter.h
 create mode 100644 reftable/merged.c
 create mode 100644 reftable/merged.h
 create mode 100644 reftable/pq.c
 create mode 100644 reftable/pq.h
 create mode 100644 reftable/reader.c
 create mode 100644 reftable/reader.h
 create mode 100644 reftable/record.c
 create mode 100644 reftable/record.h
 create mode 100644 reftable/reftable.h
 create mode 100644 reftable/slice.c
 create mode 100644 reftable/slice.h
 create mode 100644 reftable/stack.c
 create mode 100644 reftable/stack.h
 create mode 100644 reftable/system.h
 create mode 100644 reftable/tree.c
 create mode 100644 reftable/tree.h
 create mode 100644 reftable/writer.c
 create mode 100644 reftable/writer.h
 create mode 100644 reftable/zlib-compat.c

diff --git a/reftable/LICENSE b/reftable/LICENSE
new file mode 100644
index 0000000000..402e0f9356
--- /dev/null
+++ b/reftable/LICENSE
@@ -0,0 +1,31 @@
+BSD License
+
+Copyright (c) 2020, Google LLC
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+* Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+* Neither the name of Google LLC nor the names of its contributors may
+be used to endorse or promote products derived from this software
+without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/reftable/README.md b/reftable/README.md
new file mode 100644
index 0000000000..f527da0380
--- /dev/null
+++ b/reftable/README.md
@@ -0,0 +1,19 @@
+
+The source code in this directory comes from https://github.com/google/reftable.
+
+The VERSION file keeps track of the current version of the reftable library.
+
+To update the library, do:
+
+   ((cd reftable-repo && git fetch origin && git checkout origin/master ) ||
+    git clone https://github.com/google/reftable reftable-repo) && \
+   cp reftable-repo/c/*.[ch] reftable/ && \
+   cp reftable-repo/LICENSE reftable/ &&
+   git --git-dir reftable-repo/.git show --no-patch origin/master \
+    > reftable/VERSION && \
+   echo '/* empty */' > reftable/config.h
+   rm reftable/*_test.c reftable/test_framework.*
+   git add reftable/*.[ch]
+
+Bugfixes should be accompanied by a test and applied to upstream project at
+https://github.com/google/reftable.
diff --git a/reftable/VERSION b/reftable/VERSION
new file mode 100644
index 0000000000..67a4bc8042
--- /dev/null
+++ b/reftable/VERSION
@@ -0,0 +1,5 @@
+commit 6115b50fdb9bc662be39b05f5589bc109282ae7f
+Author: Han-Wen Nienhuys <hanwen@google.com>
+Date:   Mon Feb 10 13:59:52 2020 +0100
+
+    README: add a note about the Java implementation
diff --git a/reftable/basics.c b/reftable/basics.c
new file mode 100644
index 0000000000..791dcc867a
--- /dev/null
+++ b/reftable/basics.c
@@ -0,0 +1,196 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "basics.h"
+
+#include "system.h"
+
+void put_u24(byte *out, uint32_t i)
+{
+	out[0] = (byte)((i >> 16) & 0xff);
+	out[1] = (byte)((i >> 8) & 0xff);
+	out[2] = (byte)((i)&0xff);
+}
+
+uint32_t get_u24(byte *in)
+{
+	return (uint32_t)(in[0]) << 16 | (uint32_t)(in[1]) << 8 |
+	       (uint32_t)(in[2]);
+}
+
+void put_u32(byte *out, uint32_t i)
+{
+	out[0] = (byte)((i >> 24) & 0xff);
+	out[1] = (byte)((i >> 16) & 0xff);
+	out[2] = (byte)((i >> 8) & 0xff);
+	out[3] = (byte)((i)&0xff);
+}
+
+uint32_t get_u32(byte *in)
+{
+	return (uint32_t)(in[0]) << 24 | (uint32_t)(in[1]) << 16 |
+	       (uint32_t)(in[2]) << 8 | (uint32_t)(in[3]);
+}
+
+void put_u64(byte *out, uint64_t v)
+{
+	int i = 0;
+	for (i = sizeof(uint64_t); i--;) {
+		out[i] = (byte)(v & 0xff);
+		v >>= 8;
+	}
+}
+
+uint64_t get_u64(byte *out)
+{
+	uint64_t v = 0;
+	int i = 0;
+	for (i = 0; i < sizeof(uint64_t); i++) {
+		v = (v << 8) | (byte)(out[i] & 0xff);
+	}
+	return v;
+}
+
+void put_u16(byte *out, uint16_t i)
+{
+	out[0] = (byte)((i >> 8) & 0xff);
+	out[1] = (byte)((i)&0xff);
+}
+
+uint16_t get_u16(byte *in)
+{
+	return (uint32_t)(in[0]) << 8 | (uint32_t)(in[1]);
+}
+
+/*
+  find smallest index i in [0, sz) at which f(i) is true, assuming
+  that f is ascending. Return sz if f(i) is false for all indices.
+*/
+int binsearch(int sz, int (*f)(int k, void *args), void *args)
+{
+	int lo = 0;
+	int hi = sz;
+
+	/* invariant: (hi == sz) || f(hi) == true
+	   (lo == 0 && f(0) == true) || fi(lo) == false
+	 */
+	while (hi - lo > 1) {
+		int mid = lo + (hi - lo) / 2;
+
+		int val = f(mid, args);
+		if (val) {
+			hi = mid;
+		} else {
+			lo = mid;
+		}
+	}
+
+	if (lo == 0) {
+		if (f(0, args)) {
+			return 0;
+		} else {
+			return 1;
+		}
+	}
+
+	return hi;
+}
+
+void free_names(char **a)
+{
+	char **p = a;
+	if (p == NULL) {
+		return;
+	}
+	while (*p) {
+		free(*p);
+		p++;
+	}
+	free(a);
+}
+
+int names_length(char **names)
+{
+	int len = 0;
+	for (char **p = names; *p; p++) {
+		len++;
+	}
+	return len;
+}
+
+/* parse a newline separated list of names. Empty names are discarded. */
+void parse_names(char *buf, int size, char ***namesp)
+{
+	char **names = NULL;
+	int names_cap = 0;
+	int names_len = 0;
+
+	char *p = buf;
+	char *end = buf + size;
+	while (p < end) {
+		char *next = strchr(p, '\n');
+		if (next != NULL) {
+			*next = 0;
+		} else {
+			next = end;
+		}
+		if (p < next) {
+			if (names_len == names_cap) {
+				names_cap = 2 * names_cap + 1;
+				names = realloc(names,
+						names_cap * sizeof(char *));
+			}
+			names[names_len++] = strdup(p);
+		}
+		p = next + 1;
+	}
+
+	if (names_len == names_cap) {
+		names_cap = 2 * names_cap + 1;
+		names = realloc(names, names_cap * sizeof(char *));
+	}
+
+	names[names_len] = NULL;
+	*namesp = names;
+}
+
+int names_equal(char **a, char **b)
+{
+	while (*a && *b) {
+		if (strcmp(*a, *b)) {
+			return 0;
+		}
+
+		a++;
+		b++;
+	}
+
+	return *a == *b;
+}
+
+const char *error_str(int err)
+{
+	switch (err) {
+	case IO_ERROR:
+		return "I/O error";
+	case FORMAT_ERROR:
+		return "FORMAT_ERROR";
+	case NOT_EXIST_ERROR:
+		return "NOT_EXIST_ERROR";
+	case LOCK_ERROR:
+		return "LOCK_ERROR";
+	case API_ERROR:
+		return "API_ERROR";
+	case ZLIB_ERROR:
+		return "ZLIB_ERROR";
+	case -1:
+		return "general error";
+	default:
+		return "unknown error code";
+	}
+}
diff --git a/reftable/basics.h b/reftable/basics.h
new file mode 100644
index 0000000000..0ad368cfd3
--- /dev/null
+++ b/reftable/basics.h
@@ -0,0 +1,37 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef BASICS_H
+#define BASICS_H
+
+#include "system.h"
+
+#include "reftable.h"
+
+#define true 1
+#define false 0
+
+void put_u24(byte *out, uint32_t i);
+uint32_t get_u24(byte *in);
+
+uint64_t get_u64(byte *in);
+void put_u64(byte *out, uint64_t i);
+
+void put_u32(byte *out, uint32_t i);
+uint32_t get_u32(byte *in);
+
+void put_u16(byte *out, uint16_t i);
+uint16_t get_u16(byte *in);
+int binsearch(int sz, int (*f)(int k, void *args), void *args);
+
+void free_names(char **a);
+void parse_names(char *buf, int size, char ***namesp);
+int names_equal(char **a, char **b);
+int names_length(char **names);
+
+#endif
diff --git a/reftable/block.c b/reftable/block.c
new file mode 100644
index 0000000000..ffbfee13c3
--- /dev/null
+++ b/reftable/block.c
@@ -0,0 +1,401 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "block.h"
+
+#include "system.h"
+
+#include "blocksource.h"
+#include "constants.h"
+#include "record.h"
+#include "reftable.h"
+#include "zlib.h"
+
+int block_writer_register_restart(struct block_writer *w, int n, bool restart,
+				  struct slice key);
+
+void block_writer_init(struct block_writer *bw, byte typ, byte *buf,
+		       uint32_t block_size, uint32_t header_off, int hash_size)
+{
+	bw->buf = buf;
+	bw->hash_size = hash_size;
+	bw->block_size = block_size;
+	bw->header_off = header_off;
+	bw->buf[header_off] = typ;
+	bw->next = header_off + 4;
+	bw->restart_interval = 16;
+	bw->entries = 0;
+}
+
+byte block_writer_type(struct block_writer *bw)
+{
+	return bw->buf[bw->header_off];
+}
+
+/* adds the record to the block. Returns -1 if it does not fit, 0 on
+   success */
+int block_writer_add(struct block_writer *w, struct record rec)
+{
+	struct slice empty = {};
+	struct slice last = w->entries % w->restart_interval == 0 ? empty :
+								    w->last_key;
+	struct slice out = {
+		.buf = w->buf + w->next,
+		.len = w->block_size - w->next,
+	};
+
+	struct slice start = out;
+
+	bool restart = false;
+	struct slice key = {};
+	int n = 0;
+
+	record_key(rec, &key);
+	n = encode_key(&restart, out, last, key, record_val_type(rec));
+	if (n < 0) {
+		goto err;
+	}
+	out.buf += n;
+	out.len -= n;
+
+	n = record_encode(rec, out, w->hash_size);
+	if (n < 0) {
+		goto err;
+	}
+
+	out.buf += n;
+	out.len -= n;
+
+	if (block_writer_register_restart(w, start.len - out.len, restart,
+					  key) < 0) {
+		goto err;
+	}
+
+	free(slice_yield(&key));
+	return 0;
+
+err:
+	free(slice_yield(&key));
+	return -1;
+}
+
+int block_writer_register_restart(struct block_writer *w, int n, bool restart,
+				  struct slice key)
+{
+	int rlen = w->restart_len;
+	if (rlen >= MAX_RESTARTS) {
+		restart = false;
+	}
+
+	if (restart) {
+		rlen++;
+	}
+	if (2 + 3 * rlen + n > w->block_size - w->next) {
+		return -1;
+	}
+	if (restart) {
+		if (w->restart_len == w->restart_cap) {
+			w->restart_cap = w->restart_cap * 2 + 1;
+			w->restarts = realloc(
+				w->restarts, sizeof(uint32_t) * w->restart_cap);
+		}
+
+		w->restarts[w->restart_len++] = w->next;
+	}
+
+	w->next += n;
+	slice_copy(&w->last_key, key);
+	w->entries++;
+	return 0;
+}
+
+int block_writer_finish(struct block_writer *w)
+{
+	int i = 0;
+	for (i = 0; i < w->restart_len; i++) {
+		put_u24(w->buf + w->next, w->restarts[i]);
+		w->next += 3;
+	}
+
+	put_u16(w->buf + w->next, w->restart_len);
+	w->next += 2;
+	put_u24(w->buf + 1 + w->header_off, w->next);
+
+	if (block_writer_type(w) == BLOCK_TYPE_LOG) {
+		int block_header_skip = 4 + w->header_off;
+		struct slice compressed = {};
+		uLongf dest_len = 0, src_len = 0;
+		slice_resize(&compressed, w->next - block_header_skip);
+
+		dest_len = compressed.len;
+		src_len = w->next - block_header_skip;
+
+		if (Z_OK != compress2(compressed.buf, &dest_len,
+				      w->buf + block_header_skip, src_len, 9)) {
+			free(slice_yield(&compressed));
+			return ZLIB_ERROR;
+		}
+		memcpy(w->buf + block_header_skip, compressed.buf, dest_len);
+		w->next = dest_len + block_header_skip;
+	}
+	return w->next;
+}
+
+byte block_reader_type(struct block_reader *r)
+{
+	return r->block.data[r->header_off];
+}
+
+int block_reader_init(struct block_reader *br, struct block *block,
+		      uint32_t header_off, uint32_t table_block_size,
+		      int hash_size)
+{
+	uint32_t full_block_size = table_block_size;
+	byte typ = block->data[header_off];
+	uint32_t sz = get_u24(block->data + header_off + 1);
+
+	if (!is_block_type(typ)) {
+		return FORMAT_ERROR;
+	}
+
+	if (typ == BLOCK_TYPE_LOG) {
+		struct slice uncompressed = {};
+		int block_header_skip = 4 + header_off;
+		uLongf dst_len = sz - block_header_skip;
+		uLongf src_len = block->len - block_header_skip;
+
+		slice_resize(&uncompressed, sz);
+		memcpy(uncompressed.buf, block->data, block_header_skip);
+
+		if (Z_OK != uncompress_return_consumed(
+				    uncompressed.buf + block_header_skip,
+				    &dst_len, block->data + block_header_skip,
+				    &src_len)) {
+			free(slice_yield(&uncompressed));
+			return ZLIB_ERROR;
+		}
+
+		block_source_return_block(block->source, block);
+		block->data = uncompressed.buf;
+		block->len = dst_len; /* XXX: 4 bytes missing? */
+		block->source = malloc_block_source();
+		full_block_size = src_len + block_header_skip;
+	} else if (full_block_size == 0) {
+		full_block_size = sz;
+	} else if (sz < full_block_size && sz < block->len &&
+		   block->data[sz] != 0) {
+		/* If the block is smaller than the full block size, it is
+		   padded (data followed by '\0') or the next block is
+		   unaligned. */
+		full_block_size = sz;
+	}
+
+	{
+		uint16_t restart_count = get_u16(block->data + sz - 2);
+		uint32_t restart_start = sz - 2 - 3 * restart_count;
+
+		byte *restart_bytes = block->data + restart_start;
+
+		/* transfer ownership. */
+		br->block = *block;
+		block->data = NULL;
+		block->len = 0;
+
+		br->hash_size = hash_size;
+		br->block_len = restart_start;
+		br->full_block_size = full_block_size;
+		br->header_off = header_off;
+		br->restart_count = restart_count;
+		br->restart_bytes = restart_bytes;
+	}
+
+	return 0;
+}
+
+static uint32_t block_reader_restart_offset(struct block_reader *br, int i)
+{
+	return get_u24(br->restart_bytes + 3 * i);
+}
+
+void block_reader_start(struct block_reader *br, struct block_iter *it)
+{
+	it->br = br;
+	slice_resize(&it->last_key, 0);
+	it->next_off = br->header_off + 4;
+}
+
+struct restart_find_args {
+	struct slice key;
+	struct block_reader *r;
+	int error;
+};
+
+static int restart_key_less(int idx, void *args)
+{
+	struct restart_find_args *a = (struct restart_find_args *)args;
+	uint32_t off = block_reader_restart_offset(a->r, idx);
+	struct slice in = {
+		.buf = a->r->block.data + off,
+		.len = a->r->block_len - off,
+	};
+
+	/* the restart key is verbatim in the block, so this could avoid the
+	   alloc for decoding the key */
+	struct slice rkey = {};
+	struct slice last_key = {};
+	byte unused_extra;
+	int n = decode_key(&rkey, &unused_extra, last_key, in);
+	if (n < 0) {
+		a->error = 1;
+		return -1;
+	}
+
+	{
+		int result = slice_compare(a->key, rkey);
+		free(slice_yield(&rkey));
+		return result;
+	}
+}
+
+void block_iter_copy_from(struct block_iter *dest, struct block_iter *src)
+{
+	dest->br = src->br;
+	dest->next_off = src->next_off;
+	slice_copy(&dest->last_key, src->last_key);
+}
+
+/* return < 0 for error, 0 for OK, > 0 for EOF. */
+int block_iter_next(struct block_iter *it, struct record rec)
+{
+	if (it->next_off >= it->br->block_len) {
+		return 1;
+	}
+
+	{
+		struct slice in = {
+			.buf = it->br->block.data + it->next_off,
+			.len = it->br->block_len - it->next_off,
+		};
+		struct slice start = in;
+		struct slice key = {};
+		byte extra;
+		int n = decode_key(&key, &extra, it->last_key, in);
+		if (n < 0) {
+			return -1;
+		}
+
+		in.buf += n;
+		in.len -= n;
+		n = record_decode(rec, key, extra, in, it->br->hash_size);
+		if (n < 0) {
+			return -1;
+		}
+		in.buf += n;
+		in.len -= n;
+
+		slice_copy(&it->last_key, key);
+		it->next_off += start.len - in.len;
+		free(slice_yield(&key));
+		return 0;
+	}
+}
+
+int block_reader_first_key(struct block_reader *br, struct slice *key)
+{
+	struct slice empty = {};
+	int off = br->header_off + 4;
+	struct slice in = {
+		.buf = br->block.data + off,
+		.len = br->block_len - off,
+	};
+
+	byte extra = 0;
+	int n = decode_key(key, &extra, empty, in);
+	if (n < 0) {
+		return n;
+	}
+	return 0;
+}
+
+int block_iter_seek(struct block_iter *it, struct slice want)
+{
+	return block_reader_seek(it->br, it, want);
+}
+
+void block_iter_close(struct block_iter *it)
+{
+	free(slice_yield(&it->last_key));
+}
+
+int block_reader_seek(struct block_reader *br, struct block_iter *it,
+		      struct slice want)
+{
+	struct restart_find_args args = {
+		.key = want,
+		.r = br,
+	};
+
+	int i = binsearch(br->restart_count, &restart_key_less, &args);
+	if (args.error) {
+		return -1;
+	}
+
+	it->br = br;
+	if (i > 0) {
+		i--;
+		it->next_off = block_reader_restart_offset(br, i);
+	} else {
+		it->next_off = br->header_off + 4;
+	}
+
+	{
+		struct record rec = new_record(block_reader_type(br));
+		struct slice key = {};
+		int result = 0;
+		int err = 0;
+		struct block_iter next = {};
+		while (true) {
+			block_iter_copy_from(&next, it);
+
+			err = block_iter_next(&next, rec);
+			if (err < 0) {
+				result = -1;
+				goto exit;
+			}
+
+			record_key(rec, &key);
+			if (err > 0 || slice_compare(key, want) >= 0) {
+				result = 0;
+				goto exit;
+			}
+
+			block_iter_copy_from(it, &next);
+		}
+
+	exit:
+		free(slice_yield(&key));
+		free(slice_yield(&next.last_key));
+		record_clear(rec);
+		free(record_yield(&rec));
+
+		return result;
+	}
+}
+
+void block_writer_reset(struct block_writer *bw)
+{
+	bw->restart_len = 0;
+	bw->last_key.len = 0;
+}
+
+void block_writer_clear(struct block_writer *bw)
+{
+	FREE_AND_NULL(bw->restarts);
+	free(slice_yield(&bw->last_key));
+	/* the block is not owned. */
+}
diff --git a/reftable/block.h b/reftable/block.h
new file mode 100644
index 0000000000..bb42588111
--- /dev/null
+++ b/reftable/block.h
@@ -0,0 +1,71 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef BLOCK_H
+#define BLOCK_H
+
+#include "basics.h"
+#include "record.h"
+#include "reftable.h"
+
+struct block_writer {
+	byte *buf;
+	uint32_t block_size;
+	uint32_t header_off;
+	int restart_interval;
+	int hash_size;
+
+	uint32_t next;
+	uint32_t *restarts;
+	uint32_t restart_len;
+	uint32_t restart_cap;
+	struct slice last_key;
+	int entries;
+};
+
+void block_writer_init(struct block_writer *bw, byte typ, byte *buf,
+		       uint32_t block_size, uint32_t header_off, int hash_size);
+byte block_writer_type(struct block_writer *bw);
+int block_writer_add(struct block_writer *w, struct record rec);
+int block_writer_finish(struct block_writer *w);
+void block_writer_reset(struct block_writer *bw);
+void block_writer_clear(struct block_writer *bw);
+
+struct block_reader {
+	uint32_t header_off;
+	struct block block;
+	int hash_size;
+
+	/* size of the data, excluding restart data. */
+	uint32_t block_len;
+	byte *restart_bytes;
+	uint32_t full_block_size;
+	uint16_t restart_count;
+};
+
+struct block_iter {
+	struct block_reader *br;
+	struct slice last_key;
+	uint32_t next_off;
+};
+
+int block_reader_init(struct block_reader *br, struct block *bl,
+		      uint32_t header_off, uint32_t table_block_size,
+		      int hash_size);
+void block_reader_start(struct block_reader *br, struct block_iter *it);
+int block_reader_seek(struct block_reader *br, struct block_iter *it,
+		      struct slice want);
+byte block_reader_type(struct block_reader *r);
+int block_reader_first_key(struct block_reader *br, struct slice *key);
+
+void block_iter_copy_from(struct block_iter *dest, struct block_iter *src);
+int block_iter_next(struct block_iter *it, struct record rec);
+int block_iter_seek(struct block_iter *it, struct slice want);
+void block_iter_close(struct block_iter *it);
+
+#endif
diff --git a/reftable/blocksource.h b/reftable/blocksource.h
new file mode 100644
index 0000000000..f3ad3a4c22
--- /dev/null
+++ b/reftable/blocksource.h
@@ -0,0 +1,20 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef BLOCKSOURCE_H
+#define BLOCKSOURCE_H
+
+#include "reftable.h"
+
+uint64_t block_source_size(struct block_source source);
+int block_source_read_block(struct block_source source, struct block *dest,
+			    uint64_t off, uint32_t size);
+void block_source_return_block(struct block_source source, struct block *ret);
+void block_source_close(struct block_source source);
+
+#endif
diff --git a/reftable/bytes.c b/reftable/bytes.c
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/reftable/config.h b/reftable/config.h
new file mode 100644
index 0000000000..40a8c178f1
--- /dev/null
+++ b/reftable/config.h
@@ -0,0 +1 @@
+/* empty */
diff --git a/reftable/constants.h b/reftable/constants.h
new file mode 100644
index 0000000000..cd35704610
--- /dev/null
+++ b/reftable/constants.h
@@ -0,0 +1,27 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef CONSTANTS_H
+#define CONSTANTS_H
+
+#define SHA1_SIZE 20
+#define SHA256_SIZE 32
+#define VERSION 1
+#define HEADER_SIZE 24
+#define FOOTER_SIZE 68
+
+#define BLOCK_TYPE_LOG 'g'
+#define BLOCK_TYPE_INDEX 'i'
+#define BLOCK_TYPE_REF 'r'
+#define BLOCK_TYPE_OBJ 'o'
+#define BLOCK_TYPE_ANY 0
+
+#define MAX_RESTARTS ((1 << 16) - 1)
+#define DEFAULT_BLOCK_SIZE 4096
+
+#endif
diff --git a/reftable/dump.c b/reftable/dump.c
new file mode 100644
index 0000000000..acabe18fbe
--- /dev/null
+++ b/reftable/dump.c
@@ -0,0 +1,97 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "system.h"
+
+#include "reftable.h"
+
+static int dump_table(const char *tablename)
+{
+	struct block_source src = {};
+	int err = block_source_from_file(&src, tablename);
+	if (err < 0) {
+		return err;
+	}
+
+	struct reader *r = NULL;
+	err = new_reader(&r, src, tablename);
+	if (err < 0) {
+		return err;
+	}
+
+	{
+		struct iterator it = {};
+		err = reader_seek_ref(r, &it, "");
+		if (err < 0) {
+			return err;
+		}
+
+		struct ref_record ref = {};
+		while (1) {
+			err = iterator_next_ref(it, &ref);
+			if (err > 0) {
+				break;
+			}
+			if (err < 0) {
+				return err;
+			}
+			ref_record_print(&ref, 20);
+		}
+		iterator_destroy(&it);
+		ref_record_clear(&ref);
+	}
+
+	{
+		struct iterator it = {};
+		err = reader_seek_log(r, &it, "");
+		if (err < 0) {
+			return err;
+		}
+		struct log_record log = {};
+		while (1) {
+			err = iterator_next_log(it, &log);
+			if (err > 0) {
+				break;
+			}
+			if (err < 0) {
+				return err;
+			}
+			log_record_print(&log, 20);
+		}
+		iterator_destroy(&it);
+		log_record_clear(&log);
+	}
+	return 0;
+}
+
+int main(int argc, char *argv[])
+{
+	int opt;
+	const char *table = NULL;
+	while ((opt = getopt(argc, argv, "t:")) != -1) {
+		switch (opt) {
+		case 't':
+			table = strdup(optarg);
+			break;
+		case '?':
+			printf("usage: %s [-table tablefile]\n", argv[0]);
+			return 2;
+			break;
+		}
+	}
+
+	if (table != NULL) {
+		int err = dump_table(table);
+		if (err < 0) {
+			fprintf(stderr, "%s: %s: %s\n", argv[0], table,
+				error_str(err));
+			return 1;
+		}
+	}
+	return 0;
+}
diff --git a/reftable/file.c b/reftable/file.c
new file mode 100644
index 0000000000..b2ea90bf94
--- /dev/null
+++ b/reftable/file.c
@@ -0,0 +1,97 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "system.h"
+
+#include "block.h"
+#include "iter.h"
+#include "record.h"
+#include "reftable.h"
+#include "tree.h"
+
+struct file_block_source {
+	int fd;
+	uint64_t size;
+};
+
+static uint64_t file_size(void *b)
+{
+	return ((struct file_block_source *)b)->size;
+}
+
+static void file_return_block(void *b, struct block *dest)
+{
+	memset(dest->data, 0xff, dest->len);
+	free(dest->data);
+}
+
+static void file_close(void *b)
+{
+	int fd = ((struct file_block_source *)b)->fd;
+	if (fd > 0) {
+		close(fd);
+		((struct file_block_source *)b)->fd = 0;
+	}
+
+	free(b);
+}
+
+static int file_read_block(void *v, struct block *dest, uint64_t off,
+			   uint32_t size)
+{
+	struct file_block_source *b = (struct file_block_source *)v;
+	assert(off + size <= b->size);
+	dest->data = malloc(size);
+	if (pread(b->fd, dest->data, size, off) != size) {
+		return -1;
+	}
+	dest->len = size;
+	return size;
+}
+
+struct block_source_vtable file_vtable = {
+	.size = &file_size,
+	.read_block = &file_read_block,
+	.return_block = &file_return_block,
+	.close = &file_close,
+};
+
+int block_source_from_file(struct block_source *bs, const char *name)
+{
+	struct stat st = {};
+	int err = 0;
+	int fd = open(name, O_RDONLY);
+	if (fd < 0) {
+		if (errno == ENOENT) {
+			return NOT_EXIST_ERROR;
+		}
+		return -1;
+	}
+
+	err = fstat(fd, &st);
+	if (err < 0) {
+		return -1;
+	}
+
+	{
+		struct file_block_source *p =
+			calloc(sizeof(struct file_block_source), 1);
+		p->size = st.st_size;
+		p->fd = fd;
+
+		bs->ops = &file_vtable;
+		bs->arg = p;
+	}
+	return 0;
+}
+
+int fd_writer(void *arg, byte *data, int sz)
+{
+	int *fdp = (int *)arg;
+	return write(*fdp, data, sz);
+}
diff --git a/reftable/iter.c b/reftable/iter.c
new file mode 100644
index 0000000000..c06c891366
--- /dev/null
+++ b/reftable/iter.c
@@ -0,0 +1,229 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "iter.h"
+
+#include "system.h"
+
+#include "block.h"
+#include "constants.h"
+#include "reader.h"
+#include "reftable.h"
+
+bool iterator_is_null(struct iterator it)
+{
+	return it.ops == NULL;
+}
+
+static int empty_iterator_next(void *arg, struct record rec)
+{
+	return 1;
+}
+
+static void empty_iterator_close(void *arg)
+{
+}
+
+struct iterator_vtable empty_vtable = {
+	.next = &empty_iterator_next,
+	.close = &empty_iterator_close,
+};
+
+void iterator_set_empty(struct iterator *it)
+{
+	it->iter_arg = NULL;
+	it->ops = &empty_vtable;
+}
+
+int iterator_next(struct iterator it, struct record rec)
+{
+	return it.ops->next(it.iter_arg, rec);
+}
+
+void iterator_destroy(struct iterator *it)
+{
+	if (it->ops == NULL) {
+		return;
+	}
+	it->ops->close(it->iter_arg);
+	it->ops = NULL;
+	FREE_AND_NULL(it->iter_arg);
+}
+
+int iterator_next_ref(struct iterator it, struct ref_record *ref)
+{
+	struct record rec = {};
+	record_from_ref(&rec, ref);
+	return iterator_next(it, rec);
+}
+
+int iterator_next_log(struct iterator it, struct log_record *log)
+{
+	struct record rec = {};
+	record_from_log(&rec, log);
+	return iterator_next(it, rec);
+}
+
+static void filtering_ref_iterator_close(void *iter_arg)
+{
+	struct filtering_ref_iterator *fri =
+		(struct filtering_ref_iterator *)iter_arg;
+	free(slice_yield(&fri->oid));
+	iterator_destroy(&fri->it);
+}
+
+static int filtering_ref_iterator_next(void *iter_arg, struct record rec)
+{
+	struct filtering_ref_iterator *fri =
+		(struct filtering_ref_iterator *)iter_arg;
+	struct ref_record *ref = (struct ref_record *)rec.data;
+
+	while (true) {
+		int err = iterator_next_ref(fri->it, ref);
+		if (err != 0) {
+			return err;
+		}
+
+		if (fri->double_check) {
+			struct iterator it = {};
+
+			int err = reader_seek_ref(fri->r, &it, ref->ref_name);
+			if (err == 0) {
+				err = iterator_next_ref(it, ref);
+			}
+
+			iterator_destroy(&it);
+
+			if (err < 0) {
+				return err;
+			}
+
+			if (err > 0) {
+				continue;
+			}
+		}
+
+		if ((ref->target_value != NULL &&
+		     !memcmp(fri->oid.buf, ref->target_value, fri->oid.len)) ||
+		    (ref->value != NULL &&
+		     !memcmp(fri->oid.buf, ref->value, fri->oid.len))) {
+			return 0;
+		}
+	}
+}
+
+struct iterator_vtable filtering_ref_iterator_vtable = {
+	.next = &filtering_ref_iterator_next,
+	.close = &filtering_ref_iterator_close,
+};
+
+void iterator_from_filtering_ref_iterator(struct iterator *it,
+					  struct filtering_ref_iterator *fri)
+{
+	it->iter_arg = fri;
+	it->ops = &filtering_ref_iterator_vtable;
+}
+
+static void indexed_table_ref_iter_close(void *p)
+{
+	struct indexed_table_ref_iter *it = (struct indexed_table_ref_iter *)p;
+	block_iter_close(&it->cur);
+	reader_return_block(it->r, &it->block_reader.block);
+	free(slice_yield(&it->oid));
+}
+
+static int indexed_table_ref_iter_next_block(struct indexed_table_ref_iter *it)
+{
+	if (it->offset_idx == it->offset_len) {
+		it->finished = true;
+		return 1;
+	}
+
+	reader_return_block(it->r, &it->block_reader.block);
+
+	{
+		uint64_t off = it->offsets[it->offset_idx++];
+		int err = reader_init_block_reader(it->r, &it->block_reader,
+						   off, BLOCK_TYPE_REF);
+		if (err < 0) {
+			return err;
+		}
+		if (err > 0) {
+			/* indexed block does not exist. */
+			return FORMAT_ERROR;
+		}
+	}
+	block_reader_start(&it->block_reader, &it->cur);
+	return 0;
+}
+
+static int indexed_table_ref_iter_next(void *p, struct record rec)
+{
+	struct indexed_table_ref_iter *it = (struct indexed_table_ref_iter *)p;
+	struct ref_record *ref = (struct ref_record *)rec.data;
+
+	while (true) {
+		int err = block_iter_next(&it->cur, rec);
+		if (err < 0) {
+			return err;
+		}
+
+		if (err > 0) {
+			err = indexed_table_ref_iter_next_block(it);
+			if (err < 0) {
+				return err;
+			}
+
+			if (it->finished) {
+				return 1;
+			}
+			continue;
+		}
+
+		if (!memcmp(it->oid.buf, ref->target_value, it->oid.len) ||
+		    !memcmp(it->oid.buf, ref->value, it->oid.len)) {
+			return 0;
+		}
+	}
+}
+
+int new_indexed_table_ref_iter(struct indexed_table_ref_iter **dest,
+			       struct reader *r, byte *oid, int oid_len,
+			       uint64_t *offsets, int offset_len)
+{
+	struct indexed_table_ref_iter *itr =
+		calloc(sizeof(struct indexed_table_ref_iter), 1);
+	int err = 0;
+
+	itr->r = r;
+	slice_resize(&itr->oid, oid_len);
+	memcpy(itr->oid.buf, oid, oid_len);
+
+	itr->offsets = offsets;
+	itr->offset_len = offset_len;
+
+	err = indexed_table_ref_iter_next_block(itr);
+	if (err < 0) {
+		free(itr);
+	} else {
+		*dest = itr;
+	}
+	return err;
+}
+
+struct iterator_vtable indexed_table_ref_iter_vtable = {
+	.next = &indexed_table_ref_iter_next,
+	.close = &indexed_table_ref_iter_close,
+};
+
+void iterator_from_indexed_table_ref_iter(struct iterator *it,
+					  struct indexed_table_ref_iter *itr)
+{
+	it->iter_arg = itr;
+	it->ops = &indexed_table_ref_iter_vtable;
+}
diff --git a/reftable/iter.h b/reftable/iter.h
new file mode 100644
index 0000000000..f497f2a27e
--- /dev/null
+++ b/reftable/iter.h
@@ -0,0 +1,56 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef ITER_H
+#define ITER_H
+
+#include "block.h"
+#include "record.h"
+#include "slice.h"
+
+struct iterator_vtable {
+	int (*next)(void *iter_arg, struct record rec);
+	void (*close)(void *iter_arg);
+};
+
+void iterator_set_empty(struct iterator *it);
+int iterator_next(struct iterator it, struct record rec);
+bool iterator_is_null(struct iterator it);
+
+struct filtering_ref_iterator {
+	struct reader *r;
+	struct slice oid;
+	bool double_check;
+	struct iterator it;
+};
+
+void iterator_from_filtering_ref_iterator(struct iterator *,
+					  struct filtering_ref_iterator *);
+
+struct indexed_table_ref_iter {
+	struct reader *r;
+	struct slice oid;
+
+	/* mutable */
+	uint64_t *offsets;
+
+	/* Points to the next offset to read. */
+	int offset_idx;
+	int offset_len;
+	struct block_reader block_reader;
+	struct block_iter cur;
+	bool finished;
+};
+
+void iterator_from_indexed_table_ref_iter(struct iterator *it,
+					  struct indexed_table_ref_iter *itr);
+int new_indexed_table_ref_iter(struct indexed_table_ref_iter **dest,
+			       struct reader *r, byte *oid, int oid_len,
+			       uint64_t *offsets, int offset_len);
+
+#endif
diff --git a/reftable/merged.c b/reftable/merged.c
new file mode 100644
index 0000000000..7d52ec6232
--- /dev/null
+++ b/reftable/merged.c
@@ -0,0 +1,286 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "merged.h"
+
+#include "system.h"
+
+#include "constants.h"
+#include "iter.h"
+#include "pq.h"
+#include "reader.h"
+
+static int merged_iter_init(struct merged_iter *mi)
+{
+	int i = 0;
+	for (i = 0; i < mi->stack_len; i++) {
+		struct record rec = new_record(mi->typ);
+		int err = iterator_next(mi->stack[i], rec);
+		if (err < 0) {
+			return err;
+		}
+
+		if (err > 0) {
+			iterator_destroy(&mi->stack[i]);
+			record_clear(rec);
+			free(record_yield(&rec));
+		} else {
+			struct pq_entry e = {
+				.rec = rec,
+				.index = i,
+			};
+			merged_iter_pqueue_add(&mi->pq, e);
+		}
+	}
+
+	return 0;
+}
+
+static void merged_iter_close(void *p)
+{
+	struct merged_iter *mi = (struct merged_iter *)p;
+	int i = 0;
+	merged_iter_pqueue_clear(&mi->pq);
+	for (i = 0; i < mi->stack_len; i++) {
+		iterator_destroy(&mi->stack[i]);
+	}
+	free(mi->stack);
+}
+
+static int merged_iter_advance_subiter(struct merged_iter *mi, int idx)
+{
+	if (iterator_is_null(mi->stack[idx])) {
+		return 0;
+	}
+
+	{
+		struct record rec = new_record(mi->typ);
+		struct pq_entry e = {
+			.rec = rec,
+			.index = idx,
+		};
+		int err = iterator_next(mi->stack[idx], rec);
+		if (err < 0) {
+			return err;
+		}
+
+		if (err > 0) {
+			iterator_destroy(&mi->stack[idx]);
+			record_clear(rec);
+			free(record_yield(&rec));
+			return 0;
+		}
+
+		merged_iter_pqueue_add(&mi->pq, e);
+	}
+	return 0;
+}
+
+static int merged_iter_next(struct merged_iter *mi, struct record rec)
+{
+	struct slice entry_key = {};
+	struct pq_entry entry = merged_iter_pqueue_remove(&mi->pq);
+	int err = merged_iter_advance_subiter(mi, entry.index);
+	if (err < 0) {
+		return err;
+	}
+
+	record_key(entry.rec, &entry_key);
+	while (!merged_iter_pqueue_is_empty(mi->pq)) {
+		struct pq_entry top = merged_iter_pqueue_top(mi->pq);
+		struct slice k = {};
+		int err = 0, cmp = 0;
+
+		record_key(top.rec, &k);
+
+		cmp = slice_compare(k, entry_key);
+		free(slice_yield(&k));
+
+		if (cmp > 0) {
+			break;
+		}
+
+		merged_iter_pqueue_remove(&mi->pq);
+		err = merged_iter_advance_subiter(mi, top.index);
+		if (err < 0) {
+			return err;
+		}
+		record_clear(top.rec);
+		free(record_yield(&top.rec));
+	}
+
+	record_copy_from(rec, entry.rec, mi->hash_size);
+	record_clear(entry.rec);
+	free(record_yield(&entry.rec));
+	free(slice_yield(&entry_key));
+	return 0;
+}
+
+static int merged_iter_next_void(void *p, struct record rec)
+{
+	struct merged_iter *mi = (struct merged_iter *)p;
+	if (merged_iter_pqueue_is_empty(mi->pq)) {
+		return 1;
+	}
+
+	return merged_iter_next(mi, rec);
+}
+
+struct iterator_vtable merged_iter_vtable = {
+	.next = &merged_iter_next_void,
+	.close = &merged_iter_close,
+};
+
+static void iterator_from_merged_iter(struct iterator *it,
+				      struct merged_iter *mi)
+{
+	it->iter_arg = mi;
+	it->ops = &merged_iter_vtable;
+}
+
+int new_merged_table(struct merged_table **dest, struct reader **stack, int n)
+{
+	uint64_t last_max = 0;
+	uint64_t first_min = 0;
+	int i = 0;
+	for (i = 0; i < n; i++) {
+		struct reader *r = stack[i];
+		if (i > 0 && last_max >= reader_min_update_index(r)) {
+			return FORMAT_ERROR;
+		}
+		if (i == 0) {
+			first_min = reader_min_update_index(r);
+		}
+
+		last_max = reader_max_update_index(r);
+	}
+
+	{
+		struct merged_table m = {
+			.stack = stack,
+			.stack_len = n,
+			.min = first_min,
+			.max = last_max,
+			.hash_size = SHA1_SIZE,
+		};
+
+		*dest = calloc(sizeof(struct merged_table), 1);
+		**dest = m;
+	}
+	return 0;
+}
+
+void merged_table_close(struct merged_table *mt)
+{
+	int i = 0;
+	for (i = 0; i < mt->stack_len; i++) {
+		reader_free(mt->stack[i]);
+	}
+	FREE_AND_NULL(mt->stack);
+	mt->stack_len = 0;
+}
+
+/* clears the list of subtable, without affecting the readers themselves. */
+void merged_table_clear(struct merged_table *mt)
+{
+	FREE_AND_NULL(mt->stack);
+	mt->stack_len = 0;
+}
+
+void merged_table_free(struct merged_table *mt)
+{
+	if (mt == NULL) {
+		return;
+	}
+	merged_table_clear(mt);
+	free(mt);
+}
+
+uint64_t merged_max_update_index(struct merged_table *mt)
+{
+	return mt->max;
+}
+
+uint64_t merged_min_update_index(struct merged_table *mt)
+{
+	return mt->min;
+}
+
+static int merged_table_seek_record(struct merged_table *mt,
+				    struct iterator *it, struct record rec)
+{
+	struct iterator *iters = calloc(sizeof(struct iterator), mt->stack_len);
+	struct merged_iter merged = {
+		.stack = iters,
+		.typ = record_type(rec),
+		.hash_size = mt->hash_size,
+	};
+	int n = 0;
+	int err = 0;
+	int i = 0;
+	for (i = 0; i < mt->stack_len && err == 0; i++) {
+		int e = reader_seek(mt->stack[i], &iters[n], rec);
+		if (e < 0) {
+			err = e;
+		}
+		if (e == 0) {
+			n++;
+		}
+	}
+	if (err < 0) {
+		int i = 0;
+		for (i = 0; i < n; i++) {
+			iterator_destroy(&iters[i]);
+		}
+		free(iters);
+		return err;
+	}
+
+	merged.stack_len = n, err = merged_iter_init(&merged);
+	if (err < 0) {
+		merged_iter_close(&merged);
+		return err;
+	}
+
+	{
+		struct merged_iter *p = malloc(sizeof(struct merged_iter));
+		*p = merged;
+		iterator_from_merged_iter(it, p);
+	}
+	return 0;
+}
+
+int merged_table_seek_ref(struct merged_table *mt, struct iterator *it,
+			  const char *name)
+{
+	struct ref_record ref = {
+		.ref_name = (char *)name,
+	};
+	struct record rec = {};
+	record_from_ref(&rec, &ref);
+	return merged_table_seek_record(mt, it, rec);
+}
+
+int merged_table_seek_log_at(struct merged_table *mt, struct iterator *it,
+			     const char *name, uint64_t update_index)
+{
+	struct log_record log = {
+		.ref_name = (char *)name,
+		.update_index = update_index,
+	};
+	struct record rec = {};
+	record_from_log(&rec, &log);
+	return merged_table_seek_record(mt, it, rec);
+}
+
+int merged_table_seek_log(struct merged_table *mt, struct iterator *it,
+			  const char *name)
+{
+	uint64_t max = ~((uint64_t)0);
+	return merged_table_seek_log_at(mt, it, name, max);
+}
diff --git a/reftable/merged.h b/reftable/merged.h
new file mode 100644
index 0000000000..b8d3572e26
--- /dev/null
+++ b/reftable/merged.h
@@ -0,0 +1,34 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef MERGED_H
+#define MERGED_H
+
+#include "pq.h"
+#include "reftable.h"
+
+struct merged_table {
+	struct reader **stack;
+	int stack_len;
+	int hash_size;
+
+	uint64_t min;
+	uint64_t max;
+};
+
+struct merged_iter {
+	struct iterator *stack;
+	int hash_size;
+	int stack_len;
+	byte typ;
+	struct merged_iter_pqueue pq;
+} merged_iter;
+
+void merged_table_clear(struct merged_table *mt);
+
+#endif
diff --git a/reftable/pq.c b/reftable/pq.c
new file mode 100644
index 0000000000..a1aff7c98c
--- /dev/null
+++ b/reftable/pq.c
@@ -0,0 +1,114 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "pq.h"
+
+#include "system.h"
+
+int pq_less(struct pq_entry a, struct pq_entry b)
+{
+	struct slice ak = {};
+	struct slice bk = {};
+	int cmp = 0;
+	record_key(a.rec, &ak);
+	record_key(b.rec, &bk);
+
+	cmp = slice_compare(ak, bk);
+
+	free(slice_yield(&ak));
+	free(slice_yield(&bk));
+
+	if (cmp == 0) {
+		return a.index > b.index;
+	}
+
+	return cmp < 0;
+}
+
+struct pq_entry merged_iter_pqueue_top(struct merged_iter_pqueue pq)
+{
+	return pq.heap[0];
+}
+
+bool merged_iter_pqueue_is_empty(struct merged_iter_pqueue pq)
+{
+	return pq.len == 0;
+}
+
+void merged_iter_pqueue_check(struct merged_iter_pqueue pq)
+{
+	int i = 0;
+	for (i = 1; i < pq.len; i++) {
+		int parent = (i - 1) / 2;
+
+		assert(pq_less(pq.heap[parent], pq.heap[i]));
+	}
+}
+
+struct pq_entry merged_iter_pqueue_remove(struct merged_iter_pqueue *pq)
+{
+	int i = 0;
+	struct pq_entry e = pq->heap[0];
+	pq->heap[0] = pq->heap[pq->len - 1];
+	pq->len--;
+
+	i = 0;
+	while (i < pq->len) {
+		int min = i;
+		int j = 2 * i + 1;
+		int k = 2 * i + 2;
+		if (j < pq->len && pq_less(pq->heap[j], pq->heap[i])) {
+			min = j;
+		}
+		if (k < pq->len && pq_less(pq->heap[k], pq->heap[min])) {
+			min = k;
+		}
+
+		if (min == i) {
+			break;
+		}
+
+		SWAP(pq->heap[i], pq->heap[min]);
+		i = min;
+	}
+
+	return e;
+}
+
+void merged_iter_pqueue_add(struct merged_iter_pqueue *pq, struct pq_entry e)
+{
+	int i = 0;
+	if (pq->len == pq->cap) {
+		pq->cap = 2 * pq->cap + 1;
+		pq->heap = realloc(pq->heap, pq->cap * sizeof(struct pq_entry));
+	}
+
+	pq->heap[pq->len++] = e;
+	i = pq->len - 1;
+	while (i > 0) {
+		int j = (i - 1) / 2;
+		if (pq_less(pq->heap[j], pq->heap[i])) {
+			break;
+		}
+
+		SWAP(pq->heap[j], pq->heap[i]);
+
+		i = j;
+	}
+}
+
+void merged_iter_pqueue_clear(struct merged_iter_pqueue *pq)
+{
+	int i = 0;
+	for (i = 0; i < pq->len; i++) {
+		record_clear(pq->heap[i].rec);
+		free(record_yield(&pq->heap[i].rec));
+	}
+	FREE_AND_NULL(pq->heap);
+	pq->len = pq->cap = 0;
+}
diff --git a/reftable/pq.h b/reftable/pq.h
new file mode 100644
index 0000000000..5f7018979d
--- /dev/null
+++ b/reftable/pq.h
@@ -0,0 +1,34 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef PQ_H
+#define PQ_H
+
+#include "record.h"
+
+struct pq_entry {
+	struct record rec;
+	int index;
+};
+
+int pq_less(struct pq_entry a, struct pq_entry b);
+
+struct merged_iter_pqueue {
+	struct pq_entry *heap;
+	int len;
+	int cap;
+};
+
+struct pq_entry merged_iter_pqueue_top(struct merged_iter_pqueue pq);
+bool merged_iter_pqueue_is_empty(struct merged_iter_pqueue pq);
+void merged_iter_pqueue_check(struct merged_iter_pqueue pq);
+struct pq_entry merged_iter_pqueue_remove(struct merged_iter_pqueue *pq);
+void merged_iter_pqueue_add(struct merged_iter_pqueue *pq, struct pq_entry e);
+void merged_iter_pqueue_clear(struct merged_iter_pqueue *pq);
+
+#endif
diff --git a/reftable/reader.c b/reftable/reader.c
new file mode 100644
index 0000000000..981911f076
--- /dev/null
+++ b/reftable/reader.c
@@ -0,0 +1,708 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "reader.h"
+
+#include "system.h"
+
+#include "block.h"
+#include "constants.h"
+#include "iter.h"
+#include "record.h"
+#include "reftable.h"
+#include "tree.h"
+
+uint64_t block_source_size(struct block_source source)
+{
+	return source.ops->size(source.arg);
+}
+
+int block_source_read_block(struct block_source source, struct block *dest,
+			    uint64_t off, uint32_t size)
+{
+	int result = source.ops->read_block(source.arg, dest, off, size);
+	dest->source = source;
+	return result;
+}
+
+void block_source_return_block(struct block_source source, struct block *blockp)
+{
+	source.ops->return_block(source.arg, blockp);
+	blockp->data = NULL;
+	blockp->len = 0;
+	blockp->source.ops = NULL;
+	blockp->source.arg = NULL;
+}
+
+void block_source_close(struct block_source *source)
+{
+	if (source->ops == NULL) {
+		return;
+	}
+
+	source->ops->close(source->arg);
+	source->ops = NULL;
+}
+
+static struct reader_offsets *reader_offsets_for(struct reader *r, byte typ)
+{
+	switch (typ) {
+	case BLOCK_TYPE_REF:
+		return &r->ref_offsets;
+	case BLOCK_TYPE_LOG:
+		return &r->log_offsets;
+	case BLOCK_TYPE_OBJ:
+		return &r->obj_offsets;
+	}
+	abort();
+}
+
+static int reader_get_block(struct reader *r, struct block *dest, uint64_t off,
+			    uint32_t sz)
+{
+	if (off >= r->size) {
+		return 0;
+	}
+
+	if (off + sz > r->size) {
+		sz = r->size - off;
+	}
+
+	return block_source_read_block(r->source, dest, off, sz);
+}
+
+void reader_return_block(struct reader *r, struct block *p)
+{
+	block_source_return_block(r->source, p);
+}
+
+const char *reader_name(struct reader *r)
+{
+	return r->name;
+}
+
+static int parse_footer(struct reader *r, byte *footer, byte *header)
+{
+	byte *f = footer;
+	int err = 0;
+	if (memcmp(f, "REFT", 4)) {
+		err = FORMAT_ERROR;
+		goto exit;
+	}
+	f += 4;
+
+	if (memcmp(footer, header, HEADER_SIZE)) {
+		err = FORMAT_ERROR;
+		goto exit;
+	}
+
+	{
+		byte version = *f++;
+		if (version != 1) {
+			err = FORMAT_ERROR;
+			goto exit;
+		}
+	}
+
+	r->block_size = get_u24(f);
+
+	f += 3;
+	r->min_update_index = get_u64(f);
+	f += 8;
+	r->max_update_index = get_u64(f);
+	f += 8;
+
+	r->ref_offsets.index_offset = get_u64(f);
+	f += 8;
+
+	r->obj_offsets.offset = get_u64(f);
+	f += 8;
+
+	r->object_id_len = r->obj_offsets.offset & ((1 << 5) - 1);
+	r->obj_offsets.offset >>= 5;
+
+	r->obj_offsets.index_offset = get_u64(f);
+	f += 8;
+	r->log_offsets.offset = get_u64(f);
+	f += 8;
+	r->log_offsets.index_offset = get_u64(f);
+	f += 8;
+
+	{
+		uint32_t computed_crc = crc32(0, footer, f - footer);
+		uint32_t file_crc = get_u32(f);
+		f += 4;
+		if (computed_crc != file_crc) {
+			err = FORMAT_ERROR;
+			goto exit;
+		}
+	}
+
+	{
+		byte first_block_typ = header[HEADER_SIZE];
+		r->ref_offsets.present = (first_block_typ == BLOCK_TYPE_REF);
+		r->ref_offsets.offset = 0;
+		r->log_offsets.present = (first_block_typ == BLOCK_TYPE_LOG ||
+					  r->log_offsets.offset > 0);
+		r->obj_offsets.present = r->obj_offsets.offset > 0;
+	}
+	err = 0;
+exit:
+	return err;
+}
+
+int init_reader(struct reader *r, struct block_source source, const char *name)
+{
+	struct block footer = {};
+	struct block header = {};
+	int err = 0;
+
+	memset(r, 0, sizeof(struct reader));
+	r->size = block_source_size(source) - FOOTER_SIZE;
+	r->source = source;
+	r->name = strdup(name);
+	r->hash_size = SHA1_SIZE;
+
+	err = block_source_read_block(source, &footer, r->size, FOOTER_SIZE);
+	if (err != FOOTER_SIZE) {
+		err = IO_ERROR;
+		goto exit;
+	}
+
+	/* Need +1 to read type of first block. */
+	err = reader_get_block(r, &header, 0, HEADER_SIZE + 1);
+	if (err != HEADER_SIZE + 1) {
+		err = IO_ERROR;
+		goto exit;
+	}
+
+	err = parse_footer(r, footer.data, header.data);
+exit:
+	block_source_return_block(r->source, &footer);
+	block_source_return_block(r->source, &header);
+	return err;
+}
+
+struct table_iter {
+	struct reader *r;
+	byte typ;
+	uint64_t block_off;
+	struct block_iter bi;
+	bool finished;
+};
+
+static void table_iter_copy_from(struct table_iter *dest,
+				 struct table_iter *src)
+{
+	dest->r = src->r;
+	dest->typ = src->typ;
+	dest->block_off = src->block_off;
+	dest->finished = src->finished;
+	block_iter_copy_from(&dest->bi, &src->bi);
+}
+
+static int table_iter_next_in_block(struct table_iter *ti, struct record rec)
+{
+	int res = block_iter_next(&ti->bi, rec);
+	if (res == 0 && record_type(rec) == BLOCK_TYPE_REF) {
+		((struct ref_record *)rec.data)->update_index +=
+			ti->r->min_update_index;
+	}
+
+	return res;
+}
+
+static void table_iter_block_done(struct table_iter *ti)
+{
+	if (ti->bi.br == NULL) {
+		return;
+	}
+	reader_return_block(ti->r, &ti->bi.br->block);
+	FREE_AND_NULL(ti->bi.br);
+
+	ti->bi.last_key.len = 0;
+	ti->bi.next_off = 0;
+}
+
+static int32_t extract_block_size(byte *data, byte *typ, uint64_t off)
+{
+	int32_t result = 0;
+
+	if (off == 0) {
+		data += 24;
+	}
+
+	*typ = data[0];
+	if (is_block_type(*typ)) {
+		result = get_u24(data + 1);
+	}
+	return result;
+}
+
+int reader_init_block_reader(struct reader *r, struct block_reader *br,
+			     uint64_t next_off, byte want_typ)
+{
+	int32_t guess_block_size = r->block_size ? r->block_size :
+						   DEFAULT_BLOCK_SIZE;
+	struct block block = {};
+	byte block_typ = 0;
+	int err = 0;
+	uint32_t header_off = next_off ? 0 : HEADER_SIZE;
+	int32_t block_size = 0;
+
+	if (next_off >= r->size) {
+		return 1;
+	}
+
+	err = reader_get_block(r, &block, next_off, guess_block_size);
+	if (err < 0) {
+		return err;
+	}
+
+	block_size = extract_block_size(block.data, &block_typ, next_off);
+	if (block_size < 0) {
+		return block_size;
+	}
+
+	if (want_typ != BLOCK_TYPE_ANY && block_typ != want_typ) {
+		reader_return_block(r, &block);
+		return 1;
+	}
+
+	if (block_size > guess_block_size) {
+		reader_return_block(r, &block);
+		err = reader_get_block(r, &block, next_off, block_size);
+		if (err < 0) {
+			return err;
+		}
+	}
+
+	return block_reader_init(br, &block, header_off, r->block_size,
+				 r->hash_size);
+}
+
+static int table_iter_next_block(struct table_iter *dest,
+				 struct table_iter *src)
+{
+	uint64_t next_block_off = src->block_off + src->bi.br->full_block_size;
+	struct block_reader br = {};
+	int err = 0;
+
+	dest->r = src->r;
+	dest->typ = src->typ;
+	dest->block_off = next_block_off;
+
+	err = reader_init_block_reader(src->r, &br, next_block_off, src->typ);
+	if (err > 0) {
+		dest->finished = true;
+		return 1;
+	}
+	if (err != 0) {
+		return err;
+	}
+
+	{
+		struct block_reader *brp = malloc(sizeof(struct block_reader));
+		*brp = br;
+
+		dest->finished = false;
+		block_reader_start(brp, &dest->bi);
+	}
+	return 0;
+}
+
+static int table_iter_next(struct table_iter *ti, struct record rec)
+{
+	if (record_type(rec) != ti->typ) {
+		return API_ERROR;
+	}
+
+	while (true) {
+		struct table_iter next = {};
+		int err = 0;
+		if (ti->finished) {
+			return 1;
+		}
+
+		err = table_iter_next_in_block(ti, rec);
+		if (err <= 0) {
+			return err;
+		}
+
+		err = table_iter_next_block(&next, ti);
+		if (err != 0) {
+			ti->finished = true;
+		}
+		table_iter_block_done(ti);
+		if (err != 0) {
+			return err;
+		}
+		table_iter_copy_from(ti, &next);
+		block_iter_close(&next.bi);
+	}
+}
+
+static int table_iter_next_void(void *ti, struct record rec)
+{
+	return table_iter_next((struct table_iter *)ti, rec);
+}
+
+static void table_iter_close(void *p)
+{
+	struct table_iter *ti = (struct table_iter *)p;
+	table_iter_block_done(ti);
+	block_iter_close(&ti->bi);
+}
+
+struct iterator_vtable table_iter_vtable = {
+	.next = &table_iter_next_void,
+	.close = &table_iter_close,
+};
+
+static void iterator_from_table_iter(struct iterator *it, struct table_iter *ti)
+{
+	it->iter_arg = ti;
+	it->ops = &table_iter_vtable;
+}
+
+static int reader_table_iter_at(struct reader *r, struct table_iter *ti,
+				uint64_t off, byte typ)
+{
+	struct block_reader br = {};
+	struct block_reader *brp = NULL;
+
+	int err = reader_init_block_reader(r, &br, off, typ);
+	if (err != 0) {
+		return err;
+	}
+
+	brp = malloc(sizeof(struct block_reader));
+	*brp = br;
+	ti->r = r;
+	ti->typ = block_reader_type(brp);
+	ti->block_off = off;
+	block_reader_start(brp, &ti->bi);
+	return 0;
+}
+
+static int reader_start(struct reader *r, struct table_iter *ti, byte typ,
+			bool index)
+{
+	struct reader_offsets *offs = reader_offsets_for(r, typ);
+	uint64_t off = offs->offset;
+	if (index) {
+		off = offs->index_offset;
+		if (off == 0) {
+			return 1;
+		}
+		typ = BLOCK_TYPE_INDEX;
+	}
+
+	return reader_table_iter_at(r, ti, off, typ);
+}
+
+static int reader_seek_linear(struct reader *r, struct table_iter *ti,
+			      struct record want)
+{
+	struct record rec = new_record(record_type(want));
+	struct slice want_key = {};
+	struct slice got_key = {};
+	struct table_iter next = {};
+	int err = -1;
+	record_key(want, &want_key);
+
+	while (true) {
+		err = table_iter_next_block(&next, ti);
+		if (err < 0) {
+			goto exit;
+		}
+
+		if (err > 0) {
+			break;
+		}
+
+		err = block_reader_first_key(next.bi.br, &got_key);
+		if (err < 0) {
+			goto exit;
+		}
+		{
+			int cmp = slice_compare(got_key, want_key);
+			if (cmp > 0) {
+				table_iter_block_done(&next);
+				break;
+			}
+		}
+
+		table_iter_block_done(ti);
+		table_iter_copy_from(ti, &next);
+	}
+
+	err = block_iter_seek(&ti->bi, want_key);
+	if (err < 0) {
+		goto exit;
+	}
+	err = 0;
+
+exit:
+	block_iter_close(&next.bi);
+	record_clear(rec);
+	free(record_yield(&rec));
+	free(slice_yield(&want_key));
+	free(slice_yield(&got_key));
+	return err;
+}
+
+static int reader_seek_indexed(struct reader *r, struct iterator *it,
+			       struct record rec)
+{
+	struct index_record want_index = {};
+	struct record want_index_rec = {};
+	struct index_record index_result = {};
+	struct record index_result_rec = {};
+	struct table_iter index_iter = {};
+	struct table_iter next = {};
+	int err = 0;
+
+	record_key(rec, &want_index.last_key);
+	record_from_index(&want_index_rec, &want_index);
+	record_from_index(&index_result_rec, &index_result);
+
+	err = reader_start(r, &index_iter, record_type(rec), true);
+	if (err < 0) {
+		goto exit;
+	}
+
+	err = reader_seek_linear(r, &index_iter, want_index_rec);
+	while (true) {
+		err = table_iter_next(&index_iter, index_result_rec);
+		table_iter_block_done(&index_iter);
+		if (err != 0) {
+			goto exit;
+		}
+
+		err = reader_table_iter_at(r, &next, index_result.offset, 0);
+		if (err != 0) {
+			goto exit;
+		}
+
+		err = block_iter_seek(&next.bi, want_index.last_key);
+		if (err < 0) {
+			goto exit;
+		}
+
+		if (next.typ == record_type(rec)) {
+			err = 0;
+			break;
+		}
+
+		if (next.typ != BLOCK_TYPE_INDEX) {
+			err = FORMAT_ERROR;
+			break;
+		}
+
+		table_iter_copy_from(&index_iter, &next);
+	}
+
+	if (err == 0) {
+		struct table_iter *malloced =
+			calloc(sizeof(struct table_iter), 1);
+		table_iter_copy_from(malloced, &next);
+		iterator_from_table_iter(it, malloced);
+	}
+exit:
+	block_iter_close(&next.bi);
+	table_iter_close(&index_iter);
+	record_clear(want_index_rec);
+	record_clear(index_result_rec);
+	return err;
+}
+
+static int reader_seek_internal(struct reader *r, struct iterator *it,
+				struct record rec)
+{
+	struct reader_offsets *offs = reader_offsets_for(r, record_type(rec));
+	uint64_t idx = offs->index_offset;
+	struct table_iter ti = {};
+	int err = 0;
+	if (idx > 0) {
+		return reader_seek_indexed(r, it, rec);
+	}
+
+	err = reader_start(r, &ti, record_type(rec), false);
+	if (err < 0) {
+		return err;
+	}
+	err = reader_seek_linear(r, &ti, rec);
+	if (err < 0) {
+		return err;
+	}
+
+	{
+		struct table_iter *p = malloc(sizeof(struct table_iter));
+		*p = ti;
+		iterator_from_table_iter(it, p);
+	}
+
+	return 0;
+}
+
+int reader_seek(struct reader *r, struct iterator *it, struct record rec)
+{
+	byte typ = record_type(rec);
+
+	struct reader_offsets *offs = reader_offsets_for(r, typ);
+	if (!offs->present) {
+		iterator_set_empty(it);
+		return 0;
+	}
+
+	return reader_seek_internal(r, it, rec);
+}
+
+int reader_seek_ref(struct reader *r, struct iterator *it, const char *name)
+{
+	struct ref_record ref = {
+		.ref_name = (char *)name,
+	};
+	struct record rec = {};
+	record_from_ref(&rec, &ref);
+	return reader_seek(r, it, rec);
+}
+
+int reader_seek_log_at(struct reader *r, struct iterator *it, const char *name,
+		       uint64_t update_index)
+{
+	struct log_record log = {
+		.ref_name = (char *)name,
+		.update_index = update_index,
+	};
+	struct record rec = {};
+	record_from_log(&rec, &log);
+	return reader_seek(r, it, rec);
+}
+
+int reader_seek_log(struct reader *r, struct iterator *it, const char *name)
+{
+	uint64_t max = ~((uint64_t)0);
+	return reader_seek_log_at(r, it, name, max);
+}
+
+void reader_close(struct reader *r)
+{
+	block_source_close(&r->source);
+	FREE_AND_NULL(r->name);
+}
+
+int new_reader(struct reader **p, struct block_source src, char const *name)
+{
+	struct reader *rd = calloc(sizeof(struct reader), 1);
+	int err = init_reader(rd, src, name);
+	if (err == 0) {
+		*p = rd;
+	} else {
+		free(rd);
+	}
+	return err;
+}
+
+void reader_free(struct reader *r)
+{
+	reader_close(r);
+	free(r);
+}
+
+static int reader_refs_for_indexed(struct reader *r, struct iterator *it,
+				   byte *oid)
+{
+	struct obj_record want = {
+		.hash_prefix = oid,
+		.hash_prefix_len = r->object_id_len,
+	};
+	struct record want_rec = {};
+	struct iterator oit = {};
+	struct obj_record got = {};
+	struct record got_rec = {};
+	int err = 0;
+
+	record_from_obj(&want_rec, &want);
+
+	err = reader_seek(r, &oit, want_rec);
+	if (err != 0) {
+		return err;
+	}
+
+	record_from_obj(&got_rec, &got);
+	err = iterator_next(oit, got_rec);
+	iterator_destroy(&oit);
+	if (err < 0) {
+		return err;
+	}
+
+	if (err > 0 ||
+	    memcmp(want.hash_prefix, got.hash_prefix, r->object_id_len)) {
+		iterator_set_empty(it);
+		return 0;
+	}
+
+	{
+		struct indexed_table_ref_iter *itr = NULL;
+		err = new_indexed_table_ref_iter(&itr, r, oid, r->hash_size,
+						 got.offsets, got.offset_len);
+		if (err < 0) {
+			record_clear(got_rec);
+			return err;
+		}
+		got.offsets = NULL;
+		record_clear(got_rec);
+
+		iterator_from_indexed_table_ref_iter(it, itr);
+	}
+
+	return 0;
+}
+
+static int reader_refs_for_unindexed(struct reader *r, struct iterator *it,
+				     byte *oid, int oid_len)
+{
+	struct table_iter *ti = calloc(sizeof(struct table_iter), 1);
+	struct filtering_ref_iterator *filter = NULL;
+	int err = reader_start(r, ti, BLOCK_TYPE_REF, false);
+	if (err < 0) {
+		free(ti);
+		return err;
+	}
+
+	filter = calloc(sizeof(struct filtering_ref_iterator), 1);
+	slice_resize(&filter->oid, oid_len);
+	memcpy(filter->oid.buf, oid, oid_len);
+	filter->r = r;
+	filter->double_check = false;
+	iterator_from_table_iter(&filter->it, ti);
+
+	iterator_from_filtering_ref_iterator(it, filter);
+	return 0;
+}
+
+int reader_refs_for(struct reader *r, struct iterator *it, byte *oid,
+		    int oid_len)
+{
+	if (r->obj_offsets.present) {
+		return reader_refs_for_indexed(r, it, oid);
+	}
+	return reader_refs_for_unindexed(r, it, oid, oid_len);
+}
+
+uint64_t reader_max_update_index(struct reader *r)
+{
+	return r->max_update_index;
+}
+
+uint64_t reader_min_update_index(struct reader *r)
+{
+	return r->min_update_index;
+}
diff --git a/reftable/reader.h b/reftable/reader.h
new file mode 100644
index 0000000000..599a90028e
--- /dev/null
+++ b/reftable/reader.h
@@ -0,0 +1,52 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef READER_H
+#define READER_H
+
+#include "block.h"
+#include "record.h"
+#include "reftable.h"
+
+uint64_t block_source_size(struct block_source source);
+
+int block_source_read_block(struct block_source source, struct block *dest,
+			    uint64_t off, uint32_t size);
+void block_source_return_block(struct block_source source, struct block *ret);
+void block_source_close(struct block_source *source);
+
+struct reader_offsets {
+	bool present;
+	uint64_t offset;
+	uint64_t index_offset;
+};
+
+struct reader {
+	struct block_source source;
+	char *name;
+	int hash_size;
+	uint64_t size;
+	uint32_t block_size;
+	uint64_t min_update_index;
+	uint64_t max_update_index;
+	int object_id_len;
+
+	struct reader_offsets ref_offsets;
+	struct reader_offsets obj_offsets;
+	struct reader_offsets log_offsets;
+};
+
+int init_reader(struct reader *r, struct block_source source, const char *name);
+int reader_seek(struct reader *r, struct iterator *it, struct record rec);
+void reader_close(struct reader *r);
+const char *reader_name(struct reader *r);
+void reader_return_block(struct reader *r, struct block *p);
+int reader_init_block_reader(struct reader *r, struct block_reader *br,
+			     uint64_t next_off, byte want_typ);
+
+#endif
diff --git a/reftable/record.c b/reftable/record.c
new file mode 100644
index 0000000000..a006f9019d
--- /dev/null
+++ b/reftable/record.c
@@ -0,0 +1,1107 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "record.h"
+
+#include "system.h"
+
+#include "constants.h"
+#include "reftable.h"
+
+int is_block_type(byte typ)
+{
+	switch (typ) {
+	case BLOCK_TYPE_REF:
+	case BLOCK_TYPE_LOG:
+	case BLOCK_TYPE_OBJ:
+	case BLOCK_TYPE_INDEX:
+		return true;
+	}
+	return false;
+}
+
+int get_var_int(uint64_t *dest, struct slice in)
+{
+	int ptr = 0;
+	uint64_t val;
+
+	if (in.len == 0) {
+		return -1;
+	}
+	val = in.buf[ptr] & 0x7f;
+
+	while (in.buf[ptr] & 0x80) {
+		ptr++;
+		if (ptr > in.len) {
+			return -1;
+		}
+		val = (val + 1) << 7 | (uint64_t)(in.buf[ptr] & 0x7f);
+	}
+
+	*dest = val;
+	return ptr + 1;
+}
+
+int put_var_int(struct slice dest, uint64_t val)
+{
+	byte buf[10] = {};
+	int i = 9;
+	buf[i] = (byte)(val & 0x7f);
+	i--;
+	while (true) {
+		val >>= 7;
+		if (!val) {
+			break;
+		}
+		val--;
+		buf[i] = 0x80 | (byte)(val & 0x7f);
+		i--;
+	}
+
+	{
+		int n = sizeof(buf) - i - 1;
+		if (dest.len < n) {
+			return -1;
+		}
+		memcpy(dest.buf, &buf[i + 1], n);
+		return n;
+	}
+}
+
+int common_prefix_size(struct slice a, struct slice b)
+{
+	int p = 0;
+	while (p < a.len && p < b.len) {
+		if (a.buf[p] != b.buf[p]) {
+			break;
+		}
+		p++;
+	}
+
+	return p;
+}
+
+static int decode_string(struct slice *dest, struct slice in)
+{
+	int start_len = in.len;
+	uint64_t tsize = 0;
+	int n = get_var_int(&tsize, in);
+	if (n <= 0) {
+		return -1;
+	}
+	in.buf += n;
+	in.len -= n;
+	if (in.len < tsize) {
+		return -1;
+	}
+
+	slice_resize(dest, tsize + 1);
+	dest->buf[tsize] = 0;
+	memcpy(dest->buf, in.buf, tsize);
+	in.buf += tsize;
+	in.len -= tsize;
+
+	return start_len - in.len;
+}
+
+int encode_key(bool *restart, struct slice dest, struct slice prev_key,
+	       struct slice key, byte extra)
+{
+	struct slice start = dest;
+	int prefix_len = common_prefix_size(prev_key, key);
+	uint64_t suffix_len = key.len - prefix_len;
+	int n = put_var_int(dest, (uint64_t)prefix_len);
+	if (n < 0) {
+		return -1;
+	}
+	dest.buf += n;
+	dest.len -= n;
+
+	*restart = (prefix_len == 0);
+
+	n = put_var_int(dest, suffix_len << 3 | (uint64_t)extra);
+	if (n < 0) {
+		return -1;
+	}
+	dest.buf += n;
+	dest.len -= n;
+
+	if (dest.len < suffix_len) {
+		return -1;
+	}
+	memcpy(dest.buf, key.buf + prefix_len, suffix_len);
+	dest.buf += suffix_len;
+	dest.len -= suffix_len;
+
+	return start.len - dest.len;
+}
+
+static byte ref_record_type(void)
+{
+	return BLOCK_TYPE_REF;
+}
+
+static void ref_record_key(const void *r, struct slice *dest)
+{
+	const struct ref_record *rec = (const struct ref_record *)r;
+	slice_set_string(dest, rec->ref_name);
+}
+
+static void ref_record_copy_from(void *rec, const void *src_rec, int hash_size)
+{
+	struct ref_record *ref = (struct ref_record *)rec;
+	struct ref_record *src = (struct ref_record *)src_rec;
+	assert(hash_size > 0);
+
+	/* This is simple and correct, but we could probably reuse the hash
+	   fields. */
+	ref_record_clear(ref);
+	if (src->ref_name != NULL) {
+		ref->ref_name = strdup(src->ref_name);
+	}
+
+	if (src->target != NULL) {
+		ref->target = strdup(src->target);
+	}
+
+	if (src->target_value != NULL) {
+		ref->target_value = malloc(hash_size);
+		memcpy(ref->target_value, src->target_value, hash_size);
+	}
+
+	if (src->value != NULL) {
+		ref->value = malloc(hash_size);
+		memcpy(ref->value, src->value, hash_size);
+	}
+	ref->update_index = src->update_index;
+}
+
+static char hexdigit(int c)
+{
+	if (c <= 9) {
+		return '0' + c;
+	}
+	return 'a' + (c - 10);
+}
+
+static void hex_format(char *dest, byte *src, int hash_size)
+{
+	assert(hash_size > 0);
+	if (src != NULL) {
+		int i = 0;
+		for (i = 0; i < hash_size; i++) {
+			dest[2 * i] = hexdigit(src[i] >> 4);
+			dest[2 * i + 1] = hexdigit(src[i] & 0xf);
+		}
+		dest[2 * hash_size] = 0;
+	}
+}
+
+void ref_record_print(struct ref_record *ref, int hash_size)
+{
+	char hex[SHA256_SIZE + 1] = {};
+
+	printf("ref{%s(%" PRIu64 ") ", ref->ref_name, ref->update_index);
+	if (ref->value != NULL) {
+		hex_format(hex, ref->value, hash_size);
+		printf("%s", hex);
+	}
+	if (ref->target_value != NULL) {
+		hex_format(hex, ref->target_value, hash_size);
+		printf(" (T %s)", hex);
+	}
+	if (ref->target != NULL) {
+		printf("=> %s", ref->target);
+	}
+	printf("}\n");
+}
+
+static void ref_record_clear_void(void *rec)
+{
+	ref_record_clear((struct ref_record *)rec);
+}
+
+void ref_record_clear(struct ref_record *ref)
+{
+	free(ref->ref_name);
+	free(ref->target);
+	free(ref->target_value);
+	free(ref->value);
+	memset(ref, 0, sizeof(struct ref_record));
+}
+
+static byte ref_record_val_type(const void *rec)
+{
+	const struct ref_record *r = (const struct ref_record *)rec;
+	if (r->value != NULL) {
+		if (r->target_value != NULL) {
+			return 2;
+		} else {
+			return 1;
+		}
+	} else if (r->target != NULL) {
+		return 3;
+	}
+	return 0;
+}
+
+static int encode_string(char *str, struct slice s)
+{
+	struct slice start = s;
+	int l = strlen(str);
+	int n = put_var_int(s, l);
+	if (n < 0) {
+		return -1;
+	}
+	s.buf += n;
+	s.len -= n;
+	if (s.len < l) {
+		return -1;
+	}
+	memcpy(s.buf, str, l);
+	s.buf += l;
+	s.len -= l;
+
+	return start.len - s.len;
+}
+
+static int ref_record_encode(const void *rec, struct slice s, int hash_size)
+{
+	const struct ref_record *r = (const struct ref_record *)rec;
+	struct slice start = s;
+	int n = put_var_int(s, r->update_index);
+	assert(hash_size > 0);
+	if (n < 0) {
+		return -1;
+	}
+	s.buf += n;
+	s.len -= n;
+
+	if (r->value != NULL) {
+		if (s.len < hash_size) {
+			return -1;
+		}
+		memcpy(s.buf, r->value, hash_size);
+		s.buf += hash_size;
+		s.len -= hash_size;
+	}
+
+	if (r->target_value != NULL) {
+		if (s.len < hash_size) {
+			return -1;
+		}
+		memcpy(s.buf, r->target_value, hash_size);
+		s.buf += hash_size;
+		s.len -= hash_size;
+	}
+
+	if (r->target != NULL) {
+		int n = encode_string(r->target, s);
+		if (n < 0) {
+			return -1;
+		}
+		s.buf += n;
+		s.len -= n;
+	}
+
+	return start.len - s.len;
+}
+
+static int ref_record_decode(void *rec, struct slice key, byte val_type,
+			     struct slice in, int hash_size)
+{
+	struct ref_record *r = (struct ref_record *)rec;
+	struct slice start = in;
+	bool seen_value = false;
+	bool seen_target_value = false;
+	bool seen_target = false;
+
+	int n = get_var_int(&r->update_index, in);
+	if (n < 0) {
+		return n;
+	}
+	assert(hash_size > 0);
+
+	in.buf += n;
+	in.len -= n;
+
+	r->ref_name = realloc(r->ref_name, key.len + 1);
+	memcpy(r->ref_name, key.buf, key.len);
+	r->ref_name[key.len] = 0;
+
+	switch (val_type) {
+	case 1:
+	case 2:
+		if (in.len < hash_size) {
+			return -1;
+		}
+
+		if (r->value == NULL) {
+			r->value = malloc(hash_size);
+		}
+		seen_value = true;
+		memcpy(r->value, in.buf, hash_size);
+		in.buf += hash_size;
+		in.len -= hash_size;
+		if (val_type == 1) {
+			break;
+		}
+		if (r->target_value == NULL) {
+			r->target_value = malloc(hash_size);
+		}
+		seen_target_value = true;
+		memcpy(r->target_value, in.buf, hash_size);
+		in.buf += hash_size;
+		in.len -= hash_size;
+		break;
+	case 3: {
+		struct slice dest = {};
+		int n = decode_string(&dest, in);
+		if (n < 0) {
+			return -1;
+		}
+		in.buf += n;
+		in.len -= n;
+		seen_target = true;
+		r->target = (char *)slice_as_string(&dest);
+	} break;
+
+	case 0:
+		break;
+	default:
+		abort();
+		break;
+	}
+
+	if (!seen_target && r->target != NULL) {
+		FREE_AND_NULL(r->target);
+	}
+	if (!seen_target_value && r->target_value != NULL) {
+		FREE_AND_NULL(r->target_value);
+	}
+	if (!seen_value && r->value != NULL) {
+		FREE_AND_NULL(r->value);
+	}
+
+	return start.len - in.len;
+}
+
+int decode_key(struct slice *key, byte *extra, struct slice last_key,
+	       struct slice in)
+{
+	int start_len = in.len;
+	uint64_t prefix_len = 0;
+	uint64_t suffix_len = 0;
+	int n = get_var_int(&prefix_len, in);
+	if (n < 0) {
+		return -1;
+	}
+	in.buf += n;
+	in.len -= n;
+
+	if (prefix_len > last_key.len) {
+		return -1;
+	}
+
+	n = get_var_int(&suffix_len, in);
+	if (n <= 0) {
+		return -1;
+	}
+	in.buf += n;
+	in.len -= n;
+
+	*extra = (byte)(suffix_len & 0x7);
+	suffix_len >>= 3;
+
+	if (in.len < suffix_len) {
+		return -1;
+	}
+
+	slice_resize(key, suffix_len + prefix_len);
+	memcpy(key->buf, last_key.buf, prefix_len);
+
+	memcpy(key->buf + prefix_len, in.buf, suffix_len);
+	in.buf += suffix_len;
+	in.len -= suffix_len;
+
+	return start_len - in.len;
+}
+
+struct record_vtable ref_record_vtable = {
+	.key = &ref_record_key,
+	.type = &ref_record_type,
+	.copy_from = &ref_record_copy_from,
+	.val_type = &ref_record_val_type,
+	.encode = &ref_record_encode,
+	.decode = &ref_record_decode,
+	.clear = &ref_record_clear_void,
+};
+
+static byte obj_record_type(void)
+{
+	return BLOCK_TYPE_OBJ;
+}
+
+static void obj_record_key(const void *r, struct slice *dest)
+{
+	const struct obj_record *rec = (const struct obj_record *)r;
+	slice_resize(dest, rec->hash_prefix_len);
+	memcpy(dest->buf, rec->hash_prefix, rec->hash_prefix_len);
+}
+
+static void obj_record_copy_from(void *rec, const void *src_rec, int hash_size)
+{
+	struct obj_record *ref = (struct obj_record *)rec;
+	const struct obj_record *src = (const struct obj_record *)src_rec;
+
+	*ref = *src;
+	ref->hash_prefix = malloc(ref->hash_prefix_len);
+	memcpy(ref->hash_prefix, src->hash_prefix, ref->hash_prefix_len);
+
+	{
+		int olen = ref->offset_len * sizeof(uint64_t);
+		ref->offsets = malloc(olen);
+		memcpy(ref->offsets, src->offsets, olen);
+	}
+}
+
+static void obj_record_clear(void *rec)
+{
+	struct obj_record *ref = (struct obj_record *)rec;
+	FREE_AND_NULL(ref->hash_prefix);
+	FREE_AND_NULL(ref->offsets);
+	memset(ref, 0, sizeof(struct obj_record));
+}
+
+static byte obj_record_val_type(const void *rec)
+{
+	struct obj_record *r = (struct obj_record *)rec;
+	if (r->offset_len > 0 && r->offset_len < 8) {
+		return r->offset_len;
+	}
+	return 0;
+}
+
+static int obj_record_encode(const void *rec, struct slice s, int hash_size)
+{
+	struct obj_record *r = (struct obj_record *)rec;
+	struct slice start = s;
+	int n = 0;
+	if (r->offset_len == 0 || r->offset_len >= 8) {
+		n = put_var_int(s, r->offset_len);
+		if (n < 0) {
+			return -1;
+		}
+		s.buf += n;
+		s.len -= n;
+	}
+	if (r->offset_len == 0) {
+		return start.len - s.len;
+	}
+	n = put_var_int(s, r->offsets[0]);
+	if (n < 0) {
+		return -1;
+	}
+	s.buf += n;
+	s.len -= n;
+
+	{
+		uint64_t last = r->offsets[0];
+		int i = 0;
+		for (i = 1; i < r->offset_len; i++) {
+			int n = put_var_int(s, r->offsets[i] - last);
+			if (n < 0) {
+				return -1;
+			}
+			s.buf += n;
+			s.len -= n;
+			last = r->offsets[i];
+		}
+	}
+	return start.len - s.len;
+}
+
+static int obj_record_decode(void *rec, struct slice key, byte val_type,
+			     struct slice in, int hash_size)
+{
+	struct slice start = in;
+	struct obj_record *r = (struct obj_record *)rec;
+	uint64_t count = val_type;
+	int n = 0;
+	r->hash_prefix = malloc(key.len);
+	memcpy(r->hash_prefix, key.buf, key.len);
+	r->hash_prefix_len = key.len;
+
+	if (val_type == 0) {
+		n = get_var_int(&count, in);
+		if (n < 0) {
+			return n;
+		}
+
+		in.buf += n;
+		in.len -= n;
+	}
+
+	r->offsets = NULL;
+	r->offset_len = 0;
+	if (count == 0) {
+		return start.len - in.len;
+	}
+
+	r->offsets = malloc(count * sizeof(uint64_t));
+	r->offset_len = count;
+
+	n = get_var_int(&r->offsets[0], in);
+	if (n < 0) {
+		return n;
+	}
+
+	in.buf += n;
+	in.len -= n;
+
+	{
+		uint64_t last = r->offsets[0];
+		int j = 1;
+		while (j < count) {
+			uint64_t delta = 0;
+			int n = get_var_int(&delta, in);
+			if (n < 0) {
+				return n;
+			}
+
+			in.buf += n;
+			in.len -= n;
+
+			last = r->offsets[j] = (delta + last);
+			j++;
+		}
+	}
+	return start.len - in.len;
+}
+
+struct record_vtable obj_record_vtable = {
+	.key = &obj_record_key,
+	.type = &obj_record_type,
+	.copy_from = &obj_record_copy_from,
+	.val_type = &obj_record_val_type,
+	.encode = &obj_record_encode,
+	.decode = &obj_record_decode,
+	.clear = &obj_record_clear,
+};
+
+void log_record_print(struct log_record *log, int hash_size)
+{
+	char hex[SHA256_SIZE + 1] = {};
+
+	printf("log{%s(%" PRIu64 ") %s <%s> %" PRIu64 " %04d\n", log->ref_name,
+	       log->update_index, log->name, log->email, log->time,
+	       log->tz_offset);
+	hex_format(hex, log->old_hash, hash_size);
+	printf("%s => ", hex);
+	hex_format(hex, log->new_hash, hash_size);
+	printf("%s\n\n%s\n}\n", hex, log->message);
+}
+
+static byte log_record_type(void)
+{
+	return BLOCK_TYPE_LOG;
+}
+
+static void log_record_key(const void *r, struct slice *dest)
+{
+	const struct log_record *rec = (const struct log_record *)r;
+	int len = strlen(rec->ref_name);
+	uint64_t ts = 0;
+	slice_resize(dest, len + 9);
+	memcpy(dest->buf, rec->ref_name, len + 1);
+	ts = (~ts) - rec->update_index;
+	put_u64(dest->buf + 1 + len, ts);
+}
+
+static void log_record_copy_from(void *rec, const void *src_rec, int hash_size)
+{
+	struct log_record *dst = (struct log_record *)rec;
+	const struct log_record *src = (const struct log_record *)src_rec;
+
+	*dst = *src;
+	dst->ref_name = strdup(dst->ref_name);
+	dst->email = strdup(dst->email);
+	dst->name = strdup(dst->name);
+	dst->message = strdup(dst->message);
+	if (dst->new_hash != NULL) {
+		dst->new_hash = malloc(hash_size);
+		memcpy(dst->new_hash, src->new_hash, hash_size);
+	}
+	if (dst->old_hash != NULL) {
+		dst->old_hash = malloc(hash_size);
+		memcpy(dst->old_hash, src->old_hash, hash_size);
+	}
+}
+
+static void log_record_clear_void(void *rec)
+{
+	struct log_record *r = (struct log_record *)rec;
+	log_record_clear(r);
+}
+
+void log_record_clear(struct log_record *r)
+{
+	free(r->ref_name);
+	free(r->new_hash);
+	free(r->old_hash);
+	free(r->name);
+	free(r->email);
+	free(r->message);
+	memset(r, 0, sizeof(struct log_record));
+}
+
+static byte log_record_val_type(const void *rec)
+{
+	return 1;
+}
+
+static byte zero[SHA256_SIZE] = {};
+
+static int log_record_encode(const void *rec, struct slice s, int hash_size)
+{
+	struct log_record *r = (struct log_record *)rec;
+	struct slice start = s;
+	int n = 0;
+	byte *oldh = r->old_hash;
+	byte *newh = r->new_hash;
+	if (oldh == NULL) {
+		oldh = zero;
+	}
+	if (newh == NULL) {
+		newh = zero;
+	}
+
+	if (s.len < 2 * hash_size) {
+		return -1;
+	}
+
+	memcpy(s.buf, oldh, hash_size);
+	memcpy(s.buf + hash_size, newh, hash_size);
+	s.buf += 2 * hash_size;
+	s.len -= 2 * hash_size;
+
+	n = encode_string(r->name ? r->name : "", s);
+	if (n < 0) {
+		return -1;
+	}
+	s.len -= n;
+	s.buf += n;
+
+	n = encode_string(r->email ? r->email : "", s);
+	if (n < 0) {
+		return -1;
+	}
+	s.len -= n;
+	s.buf += n;
+
+	n = put_var_int(s, r->time);
+	if (n < 0) {
+		return -1;
+	}
+	s.buf += n;
+	s.len -= n;
+
+	if (s.len < 2) {
+		return -1;
+	}
+
+	put_u16(s.buf, r->tz_offset);
+	s.buf += 2;
+	s.len -= 2;
+
+	n = encode_string(r->message ? r->message : "", s);
+	if (n < 0) {
+		return -1;
+	}
+	s.len -= n;
+	s.buf += n;
+
+	return start.len - s.len;
+}
+
+static int log_record_decode(void *rec, struct slice key, byte val_type,
+			     struct slice in, int hash_size)
+{
+	struct slice start = in;
+	struct log_record *r = (struct log_record *)rec;
+	uint64_t max = 0;
+	uint64_t ts = 0;
+	struct slice dest = {};
+	int n;
+
+	if (key.len <= 9 || key.buf[key.len - 9] != 0) {
+		return FORMAT_ERROR;
+	}
+
+	r->ref_name = realloc(r->ref_name, key.len - 8);
+	memcpy(r->ref_name, key.buf, key.len - 8);
+	ts = get_u64(key.buf + key.len - 8);
+
+	r->update_index = (~max) - ts;
+
+	if (in.len < 2 * hash_size) {
+		return FORMAT_ERROR;
+	}
+
+	r->old_hash = realloc(r->old_hash, hash_size);
+	r->new_hash = realloc(r->new_hash, hash_size);
+
+	memcpy(r->old_hash, in.buf, hash_size);
+	memcpy(r->new_hash, in.buf + hash_size, hash_size);
+
+	in.buf += 2 * hash_size;
+	in.len -= 2 * hash_size;
+
+	n = decode_string(&dest, in);
+	if (n < 0) {
+		goto error;
+	}
+	in.len -= n;
+	in.buf += n;
+
+	r->name = realloc(r->name, dest.len + 1);
+	memcpy(r->name, dest.buf, dest.len);
+	r->name[dest.len] = 0;
+
+	slice_resize(&dest, 0);
+	n = decode_string(&dest, in);
+	if (n < 0) {
+		goto error;
+	}
+	in.len -= n;
+	in.buf += n;
+
+	r->email = realloc(r->email, dest.len + 1);
+	memcpy(r->email, dest.buf, dest.len);
+	r->email[dest.len] = 0;
+
+	ts = 0;
+	n = get_var_int(&ts, in);
+	if (n < 0) {
+		goto error;
+	}
+	in.len -= n;
+	in.buf += n;
+	r->time = ts;
+	if (in.len < 2) {
+		goto error;
+	}
+
+	r->tz_offset = get_u16(in.buf);
+	in.buf += 2;
+	in.len -= 2;
+
+	slice_resize(&dest, 0);
+	n = decode_string(&dest, in);
+	if (n < 0) {
+		goto error;
+	}
+	in.len -= n;
+	in.buf += n;
+
+	r->message = realloc(r->message, dest.len + 1);
+	memcpy(r->message, dest.buf, dest.len);
+	r->message[dest.len] = 0;
+
+	return start.len - in.len;
+
+error:
+	free(slice_yield(&dest));
+	return FORMAT_ERROR;
+}
+
+static bool null_streq(char *a, char *b)
+{
+	char *empty = "";
+	if (a == NULL) {
+		a = empty;
+	}
+	if (b == NULL) {
+		b = empty;
+	}
+	return 0 == strcmp(a, b);
+}
+
+static bool zero_hash_eq(byte *a, byte *b, int sz)
+{
+	if (a == NULL) {
+		a = zero;
+	}
+	if (b == NULL) {
+		b = zero;
+	}
+	return !memcmp(a, b, sz);
+}
+
+bool log_record_equal(struct log_record *a, struct log_record *b, int hash_size)
+{
+	return null_streq(a->name, b->name) && null_streq(a->email, b->email) &&
+	       null_streq(a->message, b->message) &&
+	       zero_hash_eq(a->old_hash, b->old_hash, hash_size) &&
+	       zero_hash_eq(a->new_hash, b->new_hash, hash_size) &&
+	       a->time == b->time && a->tz_offset == b->tz_offset &&
+	       a->update_index == b->update_index;
+}
+
+struct record_vtable log_record_vtable = {
+	.key = &log_record_key,
+	.type = &log_record_type,
+	.copy_from = &log_record_copy_from,
+	.val_type = &log_record_val_type,
+	.encode = &log_record_encode,
+	.decode = &log_record_decode,
+	.clear = &log_record_clear_void,
+};
+
+struct record new_record(byte typ)
+{
+	struct record rec;
+	switch (typ) {
+	case BLOCK_TYPE_REF: {
+		struct ref_record *r = calloc(1, sizeof(struct ref_record));
+		record_from_ref(&rec, r);
+		return rec;
+	}
+
+	case BLOCK_TYPE_OBJ: {
+		struct obj_record *r = calloc(1, sizeof(struct obj_record));
+		record_from_obj(&rec, r);
+		return rec;
+	}
+	case BLOCK_TYPE_LOG: {
+		struct log_record *r = calloc(1, sizeof(struct log_record));
+		record_from_log(&rec, r);
+		return rec;
+	}
+	case BLOCK_TYPE_INDEX: {
+		struct index_record *r = calloc(1, sizeof(struct index_record));
+		record_from_index(&rec, r);
+		return rec;
+	}
+	}
+	abort();
+	return rec;
+}
+
+static byte index_record_type(void)
+{
+	return BLOCK_TYPE_INDEX;
+}
+
+static void index_record_key(const void *r, struct slice *dest)
+{
+	struct index_record *rec = (struct index_record *)r;
+	slice_copy(dest, rec->last_key);
+}
+
+static void index_record_copy_from(void *rec, const void *src_rec,
+				   int hash_size)
+{
+	struct index_record *dst = (struct index_record *)rec;
+	struct index_record *src = (struct index_record *)src_rec;
+
+	slice_copy(&dst->last_key, src->last_key);
+	dst->offset = src->offset;
+}
+
+static void index_record_clear(void *rec)
+{
+	struct index_record *idx = (struct index_record *)rec;
+	free(slice_yield(&idx->last_key));
+}
+
+static byte index_record_val_type(const void *rec)
+{
+	return 0;
+}
+
+static int index_record_encode(const void *rec, struct slice out, int hash_size)
+{
+	const struct index_record *r = (const struct index_record *)rec;
+	struct slice start = out;
+
+	int n = put_var_int(out, r->offset);
+	if (n < 0) {
+		return n;
+	}
+
+	out.buf += n;
+	out.len -= n;
+
+	return start.len - out.len;
+}
+
+static int index_record_decode(void *rec, struct slice key, byte val_type,
+			       struct slice in, int hash_size)
+{
+	struct slice start = in;
+	struct index_record *r = (struct index_record *)rec;
+	int n = 0;
+
+	slice_copy(&r->last_key, key);
+
+	n = get_var_int(&r->offset, in);
+	if (n < 0) {
+		return n;
+	}
+
+	in.buf += n;
+	in.len -= n;
+	return start.len - in.len;
+}
+
+struct record_vtable index_record_vtable = {
+	.key = &index_record_key,
+	.type = &index_record_type,
+	.copy_from = &index_record_copy_from,
+	.val_type = &index_record_val_type,
+	.encode = &index_record_encode,
+	.decode = &index_record_decode,
+	.clear = &index_record_clear,
+};
+
+void record_key(struct record rec, struct slice *dest)
+{
+	rec.ops->key(rec.data, dest);
+}
+
+byte record_type(struct record rec)
+{
+	return rec.ops->type();
+}
+
+int record_encode(struct record rec, struct slice dest, int hash_size)
+{
+	return rec.ops->encode(rec.data, dest, hash_size);
+}
+
+void record_copy_from(struct record rec, struct record src, int hash_size)
+{
+	assert(src.ops->type() == rec.ops->type());
+
+	rec.ops->copy_from(rec.data, src.data, hash_size);
+}
+
+byte record_val_type(struct record rec)
+{
+	return rec.ops->val_type(rec.data);
+}
+
+int record_decode(struct record rec, struct slice key, byte extra,
+		  struct slice src, int hash_size)
+{
+	return rec.ops->decode(rec.data, key, extra, src, hash_size);
+}
+
+void record_clear(struct record rec)
+{
+	return rec.ops->clear(rec.data);
+}
+
+void record_from_ref(struct record *rec, struct ref_record *ref_rec)
+{
+	rec->data = ref_rec;
+	rec->ops = &ref_record_vtable;
+}
+
+void record_from_obj(struct record *rec, struct obj_record *obj_rec)
+{
+	rec->data = obj_rec;
+	rec->ops = &obj_record_vtable;
+}
+
+void record_from_index(struct record *rec, struct index_record *index_rec)
+{
+	rec->data = index_rec;
+	rec->ops = &index_record_vtable;
+}
+
+void record_from_log(struct record *rec, struct log_record *log_rec)
+{
+	rec->data = log_rec;
+	rec->ops = &log_record_vtable;
+}
+
+void *record_yield(struct record *rec)
+{
+	void *p = rec->data;
+	rec->data = NULL;
+	return p;
+}
+
+struct ref_record *record_as_ref(struct record rec)
+{
+	assert(record_type(rec) == BLOCK_TYPE_REF);
+	return (struct ref_record *)rec.data;
+}
+
+static bool hash_equal(byte *a, byte *b, int hash_size)
+{
+	if (a != NULL && b != NULL) {
+		return !memcmp(a, b, hash_size);
+	}
+
+	return a == b;
+}
+
+static bool str_equal(char *a, char *b)
+{
+	if (a != NULL && b != NULL) {
+		return 0 == strcmp(a, b);
+	}
+
+	return a == b;
+}
+
+bool ref_record_equal(struct ref_record *a, struct ref_record *b, int hash_size)
+{
+	assert(hash_size > 0);
+	return 0 == strcmp(a->ref_name, b->ref_name) &&
+	       a->update_index == b->update_index &&
+	       hash_equal(a->value, b->value, hash_size) &&
+	       hash_equal(a->target_value, b->target_value, hash_size) &&
+	       str_equal(a->target, b->target);
+}
+
+int ref_record_compare_name(const void *a, const void *b)
+{
+	return strcmp(((struct ref_record *)a)->ref_name,
+		      ((struct ref_record *)b)->ref_name);
+}
+
+bool ref_record_is_deletion(const struct ref_record *ref)
+{
+	return ref->value == NULL && ref->target == NULL &&
+	       ref->target_value == NULL;
+}
+
+int log_record_compare_key(const void *a, const void *b)
+{
+	struct log_record *la = (struct log_record *)a;
+	struct log_record *lb = (struct log_record *)b;
+
+	int cmp = strcmp(la->ref_name, lb->ref_name);
+	if (cmp) {
+		return cmp;
+	}
+	if (la->update_index > lb->update_index) {
+		return -1;
+	}
+	return (la->update_index < lb->update_index) ? 1 : 0;
+}
+
+bool log_record_is_deletion(const struct log_record *log)
+{
+	/* XXX */
+	return false;
+}
diff --git a/reftable/record.h b/reftable/record.h
new file mode 100644
index 0000000000..dffdd71fc2
--- /dev/null
+++ b/reftable/record.h
@@ -0,0 +1,79 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef RECORD_H
+#define RECORD_H
+
+#include "reftable.h"
+#include "slice.h"
+
+struct record_vtable {
+	void (*key)(const void *rec, struct slice *dest);
+	byte (*type)(void);
+	void (*copy_from)(void *rec, const void *src, int hash_size);
+	byte (*val_type)(const void *rec);
+	int (*encode)(const void *rec, struct slice dest, int hash_size);
+	int (*decode)(void *rec, struct slice key, byte extra, struct slice src,
+		      int hash_size);
+	void (*clear)(void *rec);
+};
+
+/* record is a generic wrapper for differnt types of records. */
+struct record {
+	void *data;
+	struct record_vtable *ops;
+};
+
+int get_var_int(uint64_t *dest, struct slice in);
+int put_var_int(struct slice dest, uint64_t val);
+int common_prefix_size(struct slice a, struct slice b);
+
+int is_block_type(byte typ);
+struct record new_record(byte typ);
+
+extern struct record_vtable ref_record_vtable;
+
+int encode_key(bool *restart, struct slice dest, struct slice prev_key,
+	       struct slice key, byte extra);
+int decode_key(struct slice *key, byte *extra, struct slice last_key,
+	       struct slice in);
+
+struct index_record {
+	struct slice last_key;
+	uint64_t offset;
+};
+
+struct obj_record {
+	byte *hash_prefix;
+	int hash_prefix_len;
+	uint64_t *offsets;
+	int offset_len;
+};
+
+void record_key(struct record rec, struct slice *dest);
+byte record_type(struct record rec);
+void record_copy_from(struct record rec, struct record src, int hash_size);
+byte record_val_type(struct record rec);
+int record_encode(struct record rec, struct slice dest, int hash_size);
+int record_decode(struct record rec, struct slice key, byte extra,
+		  struct slice src, int hash_size);
+void record_clear(struct record rec);
+void *record_yield(struct record *rec);
+void record_from_obj(struct record *rec, struct obj_record *objrec);
+void record_from_index(struct record *rec, struct index_record *idxrec);
+void record_from_ref(struct record *rec, struct ref_record *refrec);
+void record_from_log(struct record *rec, struct log_record *logrec);
+struct ref_record *record_as_ref(struct record ref);
+
+/* for qsort. */
+int ref_record_compare_name(const void *a, const void *b);
+
+/* for qsort. */
+int log_record_compare_key(const void *a, const void *b);
+
+#endif
diff --git a/reftable/reftable.h b/reftable/reftable.h
new file mode 100644
index 0000000000..7021bb333f
--- /dev/null
+++ b/reftable/reftable.h
@@ -0,0 +1,394 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef REFTABLE_H
+#define REFTABLE_H
+
+#include "system.h"
+
+/* block_source is a generic wrapper for a seekable readable file.
+   It is generally passed around by value.
+ */
+struct block_source {
+	struct block_source_vtable *ops;
+	void *arg;
+};
+
+/* a contiguous segment of bytes. It keeps track of its generating block_source
+   so it can return itself into the pool.
+*/
+struct block {
+	uint8_t *data;
+	int len;
+	struct block_source source;
+};
+
+/* block_source_vtable are the operations that make up block_source */
+struct block_source_vtable {
+	/* returns the size of a block source */
+	uint64_t (*size)(void *source);
+
+	/* reads a segment from the block source. It is an error to read
+	   beyond the end of the block */
+	int (*read_block)(void *source, struct block *dest, uint64_t off,
+			  uint32_t size);
+	/* mark the block as read; may return the data back to malloc */
+	void (*return_block)(void *source, struct block *blockp);
+
+	/* release all resources associated with the block source */
+	void (*close)(void *source);
+};
+
+/* opens a file on the file system as a block_source */
+int block_source_from_file(struct block_source *block_src, const char *name);
+
+/* write_options sets options for writing a single reftable. */
+struct write_options {
+	/* boolean: do not pad out blocks to block size. */
+	int unpadded;
+
+	/* the blocksize. Should be less than 2^24. */
+	uint32_t block_size;
+
+	/* boolean: do not generate a SHA1 => ref index. */
+	int skip_index_objects;
+
+	/* how often to write complete keys in each block. */
+	int restart_interval;
+};
+
+/* ref_record holds a ref database entry target_value */
+struct ref_record {
+	char *ref_name; /* Name of the ref, malloced. */
+	uint64_t update_index; /* Logical timestamp at which this value is
+				  written */
+	uint8_t *value; /* SHA1, or NULL. malloced. */
+	uint8_t *target_value; /* peeled annotated tag, or NULL. malloced. */
+	char *target; /* symref, or NULL. malloced. */
+};
+
+/* returns whether 'ref' represents a deletion */
+int ref_record_is_deletion(const struct ref_record *ref);
+
+/* prints a ref_record onto stdout */
+void ref_record_print(struct ref_record *ref, int hash_size);
+
+/* frees and nulls all pointer values. */
+void ref_record_clear(struct ref_record *ref);
+
+/* returns whether two ref_records are the same */
+int ref_record_equal(struct ref_record *a, struct ref_record *b, int hash_size);
+
+/* log_record holds a reflog entry */
+struct log_record {
+	char *ref_name;
+	uint64_t update_index;
+	uint8_t *new_hash;
+	uint8_t *old_hash;
+	char *name;
+	char *email;
+	uint64_t time;
+	int16_t tz_offset;
+	char *message;
+};
+
+/* returns whether 'ref' represents the deletion of a log record. */
+int log_record_is_deletion(const struct log_record *log);
+
+/* frees and nulls all pointer values. */
+void log_record_clear(struct log_record *log);
+
+/* returns whether two records are equal. */
+int log_record_equal(struct log_record *a, struct log_record *b, int hash_size);
+
+void log_record_print(struct log_record *log, int hash_size);
+
+/* iterator is the generic interface for walking over data stored in a
+   reftable. It is generally passed around by value.
+*/
+struct iterator {
+	struct iterator_vtable *ops;
+	void *iter_arg;
+};
+
+/* reads the next ref_record. Returns < 0 for error, 0 for OK and > 0:
+   end of iteration.
+*/
+int iterator_next_ref(struct iterator it, struct ref_record *ref);
+
+/* reads the next log_record. Returns < 0 for error, 0 for OK and > 0:
+   end of iteration.
+*/
+int iterator_next_log(struct iterator it, struct log_record *log);
+
+/* releases resources associated with an iterator. */
+void iterator_destroy(struct iterator *it);
+
+/* block_stats holds statistics for a single block type */
+struct block_stats {
+	/* total number of entries written */
+	int entries;
+	/* total number of key restarts */
+	int restarts;
+	/* total number of blocks */
+	int blocks;
+	/* total number of index blocks */
+	int index_blocks;
+	/* depth of the index */
+	int max_index_level;
+
+	/* offset of the first block for this type */
+	uint64_t offset;
+	/* offset of the top level index block for this type, or 0 if not
+	 * present */
+	uint64_t index_offset;
+};
+
+/* stats holds overall statistics for a single reftable */
+struct stats {
+	/* total number of blocks written. */
+	int blocks;
+	/* stats for ref data */
+	struct block_stats ref_stats;
+	/* stats for the SHA1 to ref map. */
+	struct block_stats obj_stats;
+	/* stats for index blocks */
+	struct block_stats idx_stats;
+	/* stats for log blocks */
+	struct block_stats log_stats;
+
+	/* disambiguation length of shortened object IDs. */
+	int object_id_len;
+};
+
+/* different types of errors */
+
+/* Unexpected file system behavior */
+#define IO_ERROR -2
+
+/* Format inconsistency on reading data
+ */
+#define FORMAT_ERROR -3
+
+/* File does not exist. Returned from block_source_from_file(),  because it
+   needs special handling in stack.
+*/
+#define NOT_EXIST_ERROR -4
+
+/* Trying to write out-of-date data. */
+#define LOCK_ERROR -5
+
+/* Misuse of the API:
+   - on writing a record with NULL ref_name.
+   - on writing a ref_record outside the table limits
+   - on writing a ref or log record before the stack's next_update_index
+   - on reading a ref_record from log iterator, or vice versa.
+ */
+#define API_ERROR -6
+
+/* Decompression error */
+#define ZLIB_ERROR -7
+
+const char *error_str(int err);
+
+/* new_writer creates a new writer */
+struct writer *new_writer(int (*writer_func)(void *, uint8_t *, int),
+			  void *writer_arg, struct write_options *opts);
+
+/* write to a file descriptor. fdp should be an int* pointing to the fd. */
+int fd_writer(void *fdp, uint8_t *data, int size);
+
+/* Set the range of update indices for the records we will add.  When
+   writing a table into a stack, the min should be at least
+   stack_next_update_index(), or API_ERROR is returned.
+ */
+void writer_set_limits(struct writer *w, uint64_t min, uint64_t max);
+
+/* adds a ref_record. Must be called in ascending
+   order. The update_index must be within the limits set by
+   writer_set_limits(), or API_ERROR is returned.
+ */
+int writer_add_ref(struct writer *w, struct ref_record *ref);
+
+/* Convenience function to add multiple refs. Will sort the refs by
+   name before adding. */
+int writer_add_refs(struct writer *w, struct ref_record *refs, int n);
+
+/* adds a log_record. Must be called in ascending order (with more
+   recent log entries first.)
+ */
+int writer_add_log(struct writer *w, struct log_record *log);
+
+/* Convenience function to add multiple logs. Will sort the records by
+   key before adding. */
+int writer_add_logs(struct writer *w, struct log_record *logs, int n);
+
+/* writer_close finalizes the reftable. The writer is retained so statistics can
+ * be inspected. */
+int writer_close(struct writer *w);
+
+/* writer_stats returns the statistics on the reftable being written. */
+struct stats *writer_stats(struct writer *w);
+
+/* writer_free deallocates memory for the writer */
+void writer_free(struct writer *w);
+
+struct reader;
+
+/* new_reader opens a reftable for reading. If successful, returns 0
+ * code and sets pp.  The name is used for creating a
+ * stack. Typically, it is the basename of the file.
+ */
+int new_reader(struct reader **pp, struct block_source, const char *name);
+
+/* reader_seek_ref returns an iterator where 'name' would be inserted in the
+   table.
+
+   example:
+
+   struct reader *r = NULL;
+   int err = new_reader(&r, src, "filename");
+   if (err < 0) { ... }
+   struct iterator it = {};
+   err = reader_seek_ref(r, &it, "refs/heads/master");
+   if (err < 0) { ... }
+   struct ref_record ref = {};
+   while (1) {
+     err = iterator_next_ref(it, &ref);
+     if (err > 0) {
+       break;
+     }
+     if (err < 0) {
+       ..error handling..
+     }
+     ..found..
+   }
+   iterator_destroy(&it);
+   ref_record_clear(&ref);
+ */
+int reader_seek_ref(struct reader *r, struct iterator *it, const char *name);
+
+/* seek to logs for the given name, older than update_index. */
+int reader_seek_log_at(struct reader *r, struct iterator *it, const char *name,
+		       uint64_t update_index);
+
+/* seek to newest log entry for given name. */
+int reader_seek_log(struct reader *r, struct iterator *it, const char *name);
+
+/* closes and deallocates a reader. */
+void reader_free(struct reader *);
+
+/* return an iterator for the refs pointing to oid */
+int reader_refs_for(struct reader *r, struct iterator *it, uint8_t *oid,
+		    int oid_len);
+
+/* return the max_update_index for a table */
+uint64_t reader_max_update_index(struct reader *r);
+
+/* return the min_update_index for a table */
+uint64_t reader_min_update_index(struct reader *r);
+
+/* a merged table is implements seeking/iterating over a stack of tables. */
+struct merged_table;
+
+/* new_merged_table creates a new merged table. It takes ownership of the stack
+   array.
+*/
+int new_merged_table(struct merged_table **dest, struct reader **stack, int n);
+
+/* returns an iterator positioned just before 'name' */
+int merged_table_seek_ref(struct merged_table *mt, struct iterator *it,
+			  const char *name);
+
+/* returns an iterator for log entry, at given update_index */
+int merged_table_seek_log_at(struct merged_table *mt, struct iterator *it,
+			     const char *name, uint64_t update_index);
+
+/* like merged_table_seek_log_at but look for the newest entry. */
+int merged_table_seek_log(struct merged_table *mt, struct iterator *it,
+			  const char *name);
+
+/* returns the max update_index covered by this merged table. */
+uint64_t merged_max_update_index(struct merged_table *mt);
+
+/* returns the min update_index covered by this merged table. */
+uint64_t merged_min_update_index(struct merged_table *mt);
+
+/* closes readers for the merged tables */
+void merged_table_close(struct merged_table *mt);
+
+/* releases memory for the merged_table */
+void merged_table_free(struct merged_table *m);
+
+/* a stack is a stack of reftables, which can be mutated by pushing a table to
+ * the top of the stack */
+struct stack;
+
+/* open a new reftable stack. The tables will be stored in 'dir', while the list
+   of tables is in 'list_file'. Typically, this should be .git/reftables and
+   .git/refs respectively.
+*/
+int new_stack(struct stack **dest, const char *dir, const char *list_file,
+	      struct write_options config);
+
+/* returns the update_index at which a next table should be written. */
+uint64_t stack_next_update_index(struct stack *st);
+
+/* add a new table to the stack. The write_table function must call
+   writer_set_limits, add refs and return an error value. */
+int stack_add(struct stack *st,
+	      int (*write_table)(struct writer *wr, void *write_arg),
+	      void *write_arg);
+
+/* returns the merged_table for seeking. This table is valid until the
+   next write or reload, and should not be closed or deleted.
+*/
+struct merged_table *stack_merged_table(struct stack *st);
+
+/* frees all resources associated with the stack. */
+void stack_destroy(struct stack *st);
+
+/* reloads the stack if necessary. */
+int stack_reload(struct stack *st);
+
+/* Policy for expiring reflog entries. */
+struct log_expiry_config {
+	/* Drop entries older than this timestamp */
+	uint64_t time;
+
+	/* Drop older entries */
+	uint64_t min_update_index;
+};
+
+/* compacts all reftables into a giant table. Expire reflog entries if config is
+ * non-NULL */
+int stack_compact_all(struct stack *st, struct log_expiry_config *config);
+
+/* heuristically compact unbalanced table stack. */
+int stack_auto_compact(struct stack *st);
+
+/* convenience function to read a single ref. Returns < 0 for error, 0
+   for success, and 1 if ref not found. */
+int stack_read_ref(struct stack *st, const char *refname,
+		   struct ref_record *ref);
+
+/* convenience function to read a single log. Returns < 0 for error, 0
+   for success, and 1 if ref not found. */
+int stack_read_log(struct stack *st, const char *refname,
+		   struct log_record *log);
+
+/* statistics on past compactions. */
+struct compaction_stats {
+	uint64_t bytes;
+	int attempts;
+	int failures;
+};
+
+struct compaction_stats *stack_compaction_stats(struct stack *st);
+
+#endif
diff --git a/reftable/slice.c b/reftable/slice.c
new file mode 100644
index 0000000000..efbe625253
--- /dev/null
+++ b/reftable/slice.c
@@ -0,0 +1,199 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "slice.h"
+
+#include "system.h"
+
+#include "reftable.h"
+
+void slice_set_string(struct slice *s, const char *str)
+{
+	if (str == NULL) {
+		s->len = 0;
+		return;
+	}
+
+	{
+		int l = strlen(str);
+		l++; /* \0 */
+		slice_resize(s, l);
+		memcpy(s->buf, str, l);
+		s->len = l - 1;
+	}
+}
+
+void slice_resize(struct slice *s, int l)
+{
+	if (s->cap < l) {
+		int c = s->cap * 2;
+		if (c < l) {
+			c = l;
+		}
+		s->cap = c;
+		s->buf = realloc(s->buf, s->cap);
+	}
+	s->len = l;
+}
+
+void slice_append_string(struct slice *d, const char *s)
+{
+	int l1 = d->len;
+	int l2 = strlen(s);
+
+	slice_resize(d, l2 + l1);
+	memcpy(d->buf + l1, s, l2);
+}
+
+void slice_append(struct slice *s, struct slice a)
+{
+	int end = s->len;
+	slice_resize(s, s->len + a.len);
+	memcpy(s->buf + end, a.buf, a.len);
+}
+
+byte *slice_yield(struct slice *s)
+{
+	byte *p = s->buf;
+	s->buf = NULL;
+	s->cap = 0;
+	s->len = 0;
+	return p;
+}
+
+void slice_copy(struct slice *dest, struct slice src)
+{
+	slice_resize(dest, src.len);
+	memcpy(dest->buf, src.buf, src.len);
+}
+
+/* return the underlying data as char*. len is left unchanged, but
+   a \0 is added at the end. */
+const char *slice_as_string(struct slice *s)
+{
+	if (s->cap == s->len) {
+		int l = s->len;
+		slice_resize(s, l + 1);
+		s->len = l;
+	}
+	s->buf[s->len] = 0;
+	return (const char *)s->buf;
+}
+
+/* return a newly malloced string for this slice */
+char *slice_to_string(struct slice in)
+{
+	struct slice s = {};
+	slice_resize(&s, in.len + 1);
+	s.buf[in.len] = 0;
+	memcpy(s.buf, in.buf, in.len);
+	return (char *)slice_yield(&s);
+}
+
+bool slice_equal(struct slice a, struct slice b)
+{
+	if (a.len != b.len) {
+		return 0;
+	}
+	return memcmp(a.buf, b.buf, a.len) == 0;
+}
+
+int slice_compare(struct slice a, struct slice b)
+{
+	int min = a.len < b.len ? a.len : b.len;
+	int res = memcmp(a.buf, b.buf, min);
+	if (res != 0) {
+		return res;
+	}
+	if (a.len < b.len) {
+		return -1;
+	} else if (a.len > b.len) {
+		return 1;
+	} else {
+		return 0;
+	}
+}
+
+int slice_write(struct slice *b, byte *data, int sz)
+{
+	if (b->len + sz > b->cap) {
+		int newcap = 2 * b->cap + 1;
+		if (newcap < b->len + sz) {
+			newcap = (b->len + sz);
+		}
+		b->buf = realloc(b->buf, newcap);
+		b->cap = newcap;
+	}
+
+	memcpy(b->buf + b->len, data, sz);
+	b->len += sz;
+	return sz;
+}
+
+int slice_write_void(void *b, byte *data, int sz)
+{
+	return slice_write((struct slice *)b, data, sz);
+}
+
+static uint64_t slice_size(void *b)
+{
+	return ((struct slice *)b)->len;
+}
+
+static void slice_return_block(void *b, struct block *dest)
+{
+	memset(dest->data, 0xff, dest->len);
+	free(dest->data);
+}
+
+static void slice_close(void *b)
+{
+}
+
+static int slice_read_block(void *v, struct block *dest, uint64_t off,
+			    uint32_t size)
+{
+	struct slice *b = (struct slice *)v;
+	assert(off + size <= b->len);
+	dest->data = calloc(size, 1);
+	memcpy(dest->data, b->buf + off, size);
+	dest->len = size;
+	return size;
+}
+
+struct block_source_vtable slice_vtable = {
+	.size = &slice_size,
+	.read_block = &slice_read_block,
+	.return_block = &slice_return_block,
+	.close = &slice_close,
+};
+
+void block_source_from_slice(struct block_source *bs, struct slice *buf)
+{
+	bs->ops = &slice_vtable;
+	bs->arg = buf;
+}
+
+static void malloc_return_block(void *b, struct block *dest)
+{
+	memset(dest->data, 0xff, dest->len);
+	free(dest->data);
+}
+
+struct block_source_vtable malloc_vtable = {
+	.return_block = &malloc_return_block,
+};
+
+struct block_source malloc_block_source_instance = {
+	.ops = &malloc_vtable,
+};
+
+struct block_source malloc_block_source(void)
+{
+	return malloc_block_source_instance;
+}
diff --git a/reftable/slice.h b/reftable/slice.h
new file mode 100644
index 0000000000..f12a6db228
--- /dev/null
+++ b/reftable/slice.h
@@ -0,0 +1,39 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef SLICE_H
+#define SLICE_H
+
+#include "basics.h"
+#include "reftable.h"
+
+struct slice {
+	byte *buf;
+	int len;
+	int cap;
+};
+
+void slice_set_string(struct slice *dest, const char *);
+void slice_append_string(struct slice *dest, const char *);
+char *slice_to_string(struct slice src);
+const char *slice_as_string(struct slice *src);
+bool slice_equal(struct slice a, struct slice b);
+byte *slice_yield(struct slice *s);
+void slice_copy(struct slice *dest, struct slice src);
+void slice_resize(struct slice *s, int l);
+int slice_compare(struct slice a, struct slice b);
+int slice_write(struct slice *b, byte *data, int sz);
+int slice_write_void(void *b, byte *data, int sz);
+void slice_append(struct slice *dest, struct slice add);
+
+struct block_source;
+void block_source_from_slice(struct block_source *bs, struct slice *buf);
+
+struct block_source malloc_block_source(void);
+
+#endif
diff --git a/reftable/stack.c b/reftable/stack.c
new file mode 100644
index 0000000000..303eed2f37
--- /dev/null
+++ b/reftable/stack.c
@@ -0,0 +1,983 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "stack.h"
+
+#include "system.h"
+#include "merged.h"
+#include "reader.h"
+#include "reftable.h"
+#include "writer.h"
+
+int new_stack(struct stack **dest, const char *dir, const char *list_file,
+	      struct write_options config)
+{
+	struct stack *p = calloc(sizeof(struct stack), 1);
+	int err = 0;
+	*dest = NULL;
+	p->list_file = strdup(list_file);
+	p->reftable_dir = strdup(dir);
+	p->config = config;
+
+	err = stack_reload(p);
+	if (err < 0) {
+		stack_destroy(p);
+	} else {
+		*dest = p;
+	}
+	return err;
+}
+
+static int fread_lines(FILE *f, char ***namesp)
+{
+	long size = 0;
+	int err = fseek(f, 0, SEEK_END);
+	char *buf = NULL;
+	if (err < 0) {
+		err = IO_ERROR;
+		goto exit;
+	}
+	size = ftell(f);
+	if (size < 0) {
+		err = IO_ERROR;
+		goto exit;
+	}
+	err = fseek(f, 0, SEEK_SET);
+	if (err < 0) {
+		err = IO_ERROR;
+		goto exit;
+	}
+
+	buf = malloc(size + 1);
+	if (fread(buf, 1, size, f) != size) {
+		err = IO_ERROR;
+		goto exit;
+	}
+	buf[size] = 0;
+
+	parse_names(buf, size, namesp);
+exit:
+	free(buf);
+	return err;
+}
+
+int read_lines(const char *filename, char ***namesp)
+{
+	FILE *f = fopen(filename, "r");
+	int err = 0;
+	if (f == NULL) {
+		if (errno == ENOENT) {
+			*namesp = calloc(sizeof(char *), 1);
+			return 0;
+		}
+
+		return IO_ERROR;
+	}
+	err = fread_lines(f, namesp);
+	fclose(f);
+	return err;
+}
+
+struct merged_table *stack_merged_table(struct stack *st)
+{
+	return st->merged;
+}
+
+/* Close and free the stack */
+void stack_destroy(struct stack *st)
+{
+	if (st->merged == NULL) {
+		return;
+	}
+
+	merged_table_close(st->merged);
+	merged_table_free(st->merged);
+	st->merged = NULL;
+
+	FREE_AND_NULL(st->list_file);
+	FREE_AND_NULL(st->reftable_dir);
+	free(st);
+}
+
+static struct reader **stack_copy_readers(struct stack *st, int cur_len)
+{
+	struct reader **cur = calloc(sizeof(struct reader *), cur_len);
+	int i = 0;
+	for (i = 0; i < cur_len; i++) {
+		cur[i] = st->merged->stack[i];
+	}
+	return cur;
+}
+
+static int stack_reload_once(struct stack *st, char **names, bool reuse_open)
+{
+	int cur_len = st->merged == NULL ? 0 : st->merged->stack_len;
+	struct reader **cur = stack_copy_readers(st, cur_len);
+	int err = 0;
+	int names_len = names_length(names);
+	struct reader **new_tables =
+		malloc(sizeof(struct reader *) * names_len);
+	int new_tables_len = 0;
+	struct merged_table *new_merged = NULL;
+
+	struct slice table_path = {};
+
+	while (*names) {
+		struct reader *rd = NULL;
+		char *name = *names++;
+
+		/* this is linear; we assume compaction keeps the number of
+		   tables under control so this is not quadratic. */
+		int j = 0;
+		for (j = 0; reuse_open && j < cur_len; j++) {
+			if (cur[j] != NULL && 0 == strcmp(cur[j]->name, name)) {
+				rd = cur[j];
+				cur[j] = NULL;
+				break;
+			}
+		}
+
+		if (rd == NULL) {
+			struct block_source src = {};
+			slice_set_string(&table_path, st->reftable_dir);
+			slice_append_string(&table_path, "/");
+			slice_append_string(&table_path, name);
+
+			err = block_source_from_file(
+				&src, slice_as_string(&table_path));
+			if (err < 0) {
+				goto exit;
+			}
+
+			err = new_reader(&rd, src, name);
+			if (err < 0) {
+				goto exit;
+			}
+		}
+
+		new_tables[new_tables_len++] = rd;
+	}
+
+	/* success! */
+	err = new_merged_table(&new_merged, new_tables, new_tables_len);
+	if (err < 0) {
+		goto exit;
+	}
+
+	new_tables = NULL;
+	new_tables_len = 0;
+	if (st->merged != NULL) {
+		merged_table_clear(st->merged);
+		merged_table_free(st->merged);
+	}
+	st->merged = new_merged;
+
+	{
+		int i = 0;
+		for (i = 0; i < cur_len; i++) {
+			if (cur[i] != NULL) {
+				reader_close(cur[i]);
+				reader_free(cur[i]);
+			}
+		}
+	}
+exit:
+	free(slice_yield(&table_path));
+	{
+		int i = 0;
+		for (i = 0; i < new_tables_len; i++) {
+			reader_close(new_tables[i]);
+		}
+	}
+	free(new_tables);
+	free(cur);
+	return err;
+}
+
+/* return negative if a before b. */
+static int tv_cmp(struct timeval *a, struct timeval *b)
+{
+	time_t diff = a->tv_sec - b->tv_sec;
+	int udiff = a->tv_usec - b->tv_usec;
+
+	if (diff != 0) {
+		return diff;
+	}
+
+	return udiff;
+}
+
+static int stack_reload_maybe_reuse(struct stack *st, bool reuse_open)
+{
+	struct timeval deadline = {};
+	int err = gettimeofday(&deadline, NULL);
+	int64_t delay = 0;
+	int tries = 0;
+	if (err < 0) {
+		return err;
+	}
+
+	deadline.tv_sec += 3;
+	while (true) {
+		char **names = NULL;
+		char **names_after = NULL;
+		struct timeval now = {};
+		int err = gettimeofday(&now, NULL);
+		if (err < 0) {
+			return err;
+		}
+
+		/* Only look at deadlines after the first few times. This
+		   simplifies debugging in GDB */
+		tries++;
+		if (tries > 3 && tv_cmp(&now, &deadline) >= 0) {
+			break;
+		}
+
+		err = read_lines(st->list_file, &names);
+		if (err < 0) {
+			free_names(names);
+			return err;
+		}
+		err = stack_reload_once(st, names, reuse_open);
+		if (err == 0) {
+			free_names(names);
+			break;
+		}
+		if (err != NOT_EXIST_ERROR) {
+			free_names(names);
+			return err;
+		}
+
+		err = read_lines(st->list_file, &names_after);
+		if (err < 0) {
+			free_names(names);
+			return err;
+		}
+
+		if (names_equal(names_after, names)) {
+			free_names(names);
+			free_names(names_after);
+			return -1;
+		}
+		free_names(names);
+		free_names(names_after);
+
+		delay = delay + (delay * rand()) / RAND_MAX + 100;
+		usleep(delay);
+	}
+
+	return 0;
+}
+
+int stack_reload(struct stack *st)
+{
+	return stack_reload_maybe_reuse(st, true);
+}
+
+/* -1 = error
+ 0 = up to date
+ 1 = changed. */
+static int stack_uptodate(struct stack *st)
+{
+	char **names = NULL;
+	int err = read_lines(st->list_file, &names);
+	int i = 0;
+	if (err < 0) {
+		return err;
+	}
+
+	for (i = 0; i < st->merged->stack_len; i++) {
+		if (names[i] == NULL) {
+			err = 1;
+			goto exit;
+		}
+
+		if (strcmp(st->merged->stack[i]->name, names[i])) {
+			err = 1;
+			goto exit;
+		}
+	}
+
+	if (names[st->merged->stack_len] != NULL) {
+		err = 1;
+		goto exit;
+	}
+
+exit:
+	free_names(names);
+	return err;
+}
+
+int stack_add(struct stack *st, int (*write)(struct writer *wr, void *arg),
+	      void *arg)
+{
+	int err = stack_try_add(st, write, arg);
+	if (err < 0) {
+		if (err == LOCK_ERROR) {
+			err = stack_reload(st);
+		}
+		return err;
+	}
+
+	return stack_auto_compact(st);
+}
+
+static void format_name(struct slice *dest, uint64_t min, uint64_t max)
+{
+	char buf[100];
+	snprintf(buf, sizeof(buf), "%012" PRIx64 "-%012" PRIx64, min, max);
+	slice_set_string(dest, buf);
+}
+
+int stack_try_add(struct stack *st,
+		  int (*write_table)(struct writer *wr, void *arg), void *arg)
+{
+	struct slice lock_name = {};
+	struct slice temp_tab_name = {};
+	struct slice tab_name = {};
+	struct slice next_name = {};
+	struct slice table_list = {};
+	struct writer *wr = NULL;
+	int err = 0;
+	int tab_fd = 0;
+	int lock_fd = 0;
+	uint64_t next_update_index = 0;
+
+	slice_set_string(&lock_name, st->list_file);
+	slice_append_string(&lock_name, ".lock");
+
+	lock_fd = open(slice_as_string(&lock_name), O_EXCL | O_CREAT | O_WRONLY,
+		       0644);
+	if (lock_fd < 0) {
+		if (errno == EEXIST) {
+			err = LOCK_ERROR;
+			goto exit;
+		}
+		err = IO_ERROR;
+		goto exit;
+	}
+
+	err = stack_uptodate(st);
+	if (err < 0) {
+		goto exit;
+	}
+
+	if (err > 1) {
+		err = LOCK_ERROR;
+		goto exit;
+	}
+
+	next_update_index = stack_next_update_index(st);
+
+	slice_resize(&next_name, 0);
+	format_name(&next_name, next_update_index, next_update_index);
+
+	slice_set_string(&temp_tab_name, st->reftable_dir);
+	slice_append_string(&temp_tab_name, "/");
+	slice_append(&temp_tab_name, next_name);
+	slice_append_string(&temp_tab_name, ".temp.XXXXXX");
+
+	tab_fd = mkstemp((char *)slice_as_string(&temp_tab_name));
+	if (tab_fd < 0) {
+		err = IO_ERROR;
+		goto exit;
+	}
+
+	wr = new_writer(fd_writer, &tab_fd, &st->config);
+	err = write_table(wr, arg);
+	if (err < 0) {
+		goto exit;
+	}
+
+	err = writer_close(wr);
+	if (err < 0) {
+		goto exit;
+	}
+
+	err = close(tab_fd);
+	tab_fd = 0;
+	if (err < 0) {
+		err = IO_ERROR;
+		goto exit;
+	}
+
+	if (wr->min_update_index < next_update_index) {
+		err = API_ERROR;
+		goto exit;
+	}
+
+	{
+		int i = 0;
+		for (i = 0; i < st->merged->stack_len; i++) {
+			slice_append_string(&table_list,
+					    st->merged->stack[i]->name);
+			slice_append_string(&table_list, "\n");
+		}
+	}
+
+	format_name(&next_name, wr->min_update_index, wr->max_update_index);
+	slice_append_string(&next_name, ".ref");
+	slice_append(&table_list, next_name);
+	slice_append_string(&table_list, "\n");
+
+	slice_set_string(&tab_name, st->reftable_dir);
+	slice_append_string(&tab_name, "/");
+	slice_append(&tab_name, next_name);
+
+	err = rename(slice_as_string(&temp_tab_name),
+		     slice_as_string(&tab_name));
+	if (err < 0) {
+		err = IO_ERROR;
+		goto exit;
+	}
+	free(slice_yield(&temp_tab_name));
+
+	err = write(lock_fd, table_list.buf, table_list.len);
+	if (err < 0) {
+		err = IO_ERROR;
+		goto exit;
+	}
+	err = close(lock_fd);
+	lock_fd = 0;
+	if (err < 0) {
+		unlink(slice_as_string(&tab_name));
+		err = IO_ERROR;
+		goto exit;
+	}
+
+	err = rename(slice_as_string(&lock_name), st->list_file);
+	if (err < 0) {
+		unlink(slice_as_string(&tab_name));
+		err = IO_ERROR;
+		goto exit;
+	}
+
+	err = stack_reload(st);
+exit:
+	if (tab_fd > 0) {
+		close(tab_fd);
+		tab_fd = 0;
+	}
+	if (temp_tab_name.len > 0) {
+		unlink(slice_as_string(&temp_tab_name));
+	}
+	unlink(slice_as_string(&lock_name));
+
+	if (lock_fd > 0) {
+		close(lock_fd);
+		lock_fd = 0;
+	}
+
+	free(slice_yield(&lock_name));
+	free(slice_yield(&temp_tab_name));
+	free(slice_yield(&tab_name));
+	free(slice_yield(&next_name));
+	free(slice_yield(&table_list));
+	writer_free(wr);
+	return err;
+}
+
+uint64_t stack_next_update_index(struct stack *st)
+{
+	int sz = st->merged->stack_len;
+	if (sz > 0) {
+		return reader_max_update_index(st->merged->stack[sz - 1]) + 1;
+	}
+	return 1;
+}
+
+static int stack_compact_locked(struct stack *st, int first, int last,
+				struct slice *temp_tab,
+				struct log_expiry_config *config)
+{
+	struct slice next_name = {};
+	int tab_fd = -1;
+	struct writer *wr = NULL;
+	int err = 0;
+
+	format_name(&next_name,
+		    reader_min_update_index(st->merged->stack[first]),
+		    reader_max_update_index(st->merged->stack[first]));
+
+	slice_set_string(temp_tab, st->reftable_dir);
+	slice_append_string(temp_tab, "/");
+	slice_append(temp_tab, next_name);
+	slice_append_string(temp_tab, ".temp.XXXXXX");
+
+	tab_fd = mkstemp((char *)slice_as_string(temp_tab));
+	wr = new_writer(fd_writer, &tab_fd, &st->config);
+
+	err = stack_write_compact(st, wr, first, last, config);
+	if (err < 0) {
+		goto exit;
+	}
+	err = writer_close(wr);
+	if (err < 0) {
+		goto exit;
+	}
+	writer_free(wr);
+
+	err = close(tab_fd);
+	tab_fd = 0;
+
+exit:
+	if (tab_fd > 0) {
+		close(tab_fd);
+		tab_fd = 0;
+	}
+	if (err != 0 && temp_tab->len > 0) {
+		unlink(slice_as_string(temp_tab));
+		free(slice_yield(temp_tab));
+	}
+	free(slice_yield(&next_name));
+	return err;
+}
+
+int stack_write_compact(struct stack *st, struct writer *wr, int first,
+			int last, struct log_expiry_config *config)
+{
+	int subtabs_len = last - first + 1;
+	struct reader **subtabs =
+		calloc(sizeof(struct reader *), last - first + 1);
+	struct merged_table *mt = NULL;
+	int err = 0;
+	struct iterator it = {};
+	struct ref_record ref = {};
+	struct log_record log = {};
+
+	int i = 0, j = 0;
+	for (i = first, j = 0; i <= last; i++) {
+		struct reader *t = st->merged->stack[i];
+		subtabs[j++] = t;
+		st->stats.bytes += t->size;
+	}
+	writer_set_limits(wr, st->merged->stack[first]->min_update_index,
+			  st->merged->stack[last]->max_update_index);
+
+	err = new_merged_table(&mt, subtabs, subtabs_len);
+	if (err < 0) {
+		free(subtabs);
+		goto exit;
+	}
+
+	err = merged_table_seek_ref(mt, &it, "");
+	if (err < 0) {
+		goto exit;
+	}
+
+	while (true) {
+		err = iterator_next_ref(it, &ref);
+		if (err > 0) {
+			err = 0;
+			break;
+		}
+		if (err < 0) {
+			break;
+		}
+		if (first == 0 && ref_record_is_deletion(&ref)) {
+			continue;
+		}
+
+		err = writer_add_ref(wr, &ref);
+		if (err < 0) {
+			break;
+		}
+	}
+
+	err = merged_table_seek_log(mt, &it, "");
+	if (err < 0) {
+		goto exit;
+	}
+
+	while (true) {
+		err = iterator_next_log(it, &log);
+		if (err > 0) {
+			err = 0;
+			break;
+		}
+		if (err < 0) {
+			break;
+		}
+		if (first == 0 && log_record_is_deletion(&log)) {
+			continue;
+		}
+
+		/* XXX collect stats? */
+
+		if (config != NULL && config->time > 0 &&
+		    log.time < config->time) {
+			continue;
+		}
+
+		if (config != NULL && config->min_update_index > 0 &&
+		    log.update_index < config->min_update_index) {
+			continue;
+		}
+
+		err = writer_add_log(wr, &log);
+		if (err < 0) {
+			break;
+		}
+	}
+
+exit:
+	iterator_destroy(&it);
+	if (mt != NULL) {
+		merged_table_clear(mt);
+		merged_table_free(mt);
+	}
+	ref_record_clear(&ref);
+
+	return err;
+}
+
+/* <  0: error. 0 == OK, > 0 attempt failed; could retry. */
+static int stack_compact_range(struct stack *st, int first, int last,
+			       struct log_expiry_config *expiry)
+{
+	struct slice temp_tab_name = {};
+	struct slice new_table_name = {};
+	struct slice lock_file_name = {};
+	struct slice ref_list_contents = {};
+	struct slice new_table_path = {};
+	int err = 0;
+	bool have_lock = false;
+	int lock_file_fd = 0;
+	int compact_count = last - first + 1;
+	char **delete_on_success = calloc(sizeof(char *), compact_count + 1);
+	char **subtable_locks = calloc(sizeof(char *), compact_count + 1);
+	int i = 0;
+	int j = 0;
+
+	if (first > last || (expiry == NULL && first == last)) {
+		err = 0;
+		goto exit;
+	}
+
+	st->stats.attempts++;
+
+	slice_set_string(&lock_file_name, st->list_file);
+	slice_append_string(&lock_file_name, ".lock");
+
+	lock_file_fd = open(slice_as_string(&lock_file_name),
+			    O_EXCL | O_CREAT | O_WRONLY, 0644);
+	if (lock_file_fd < 0) {
+		if (errno == EEXIST) {
+			err = 1;
+		} else {
+			err = IO_ERROR;
+		}
+		goto exit;
+	}
+	have_lock = true;
+	err = stack_uptodate(st);
+	if (err != 0) {
+		goto exit;
+	}
+
+	for (i = first, j = 0; i <= last; i++) {
+		struct slice subtab_name = {};
+		struct slice subtab_lock = {};
+		slice_set_string(&subtab_name, st->reftable_dir);
+		slice_append_string(&subtab_name, "/");
+		slice_append_string(&subtab_name,
+				    reader_name(st->merged->stack[i]));
+
+		slice_copy(&subtab_lock, subtab_name);
+		slice_append_string(&subtab_lock, ".lock");
+
+		{
+			int sublock_file_fd =
+				open(slice_as_string(&subtab_lock),
+				     O_EXCL | O_CREAT | O_WRONLY, 0644);
+			if (sublock_file_fd > 0) {
+				close(sublock_file_fd);
+			} else if (sublock_file_fd < 0) {
+				if (errno == EEXIST) {
+					err = 1;
+				}
+				err = IO_ERROR;
+			}
+		}
+
+		subtable_locks[j] = (char *)slice_as_string(&subtab_lock);
+		delete_on_success[j] = (char *)slice_as_string(&subtab_name);
+		j++;
+
+		if (err != 0) {
+			goto exit;
+		}
+	}
+
+	err = unlink(slice_as_string(&lock_file_name));
+	if (err < 0) {
+		goto exit;
+	}
+	have_lock = false;
+
+	err = stack_compact_locked(st, first, last, &temp_tab_name, expiry);
+	if (err < 0) {
+		goto exit;
+	}
+
+	lock_file_fd = open(slice_as_string(&lock_file_name),
+			    O_EXCL | O_CREAT | O_WRONLY, 0644);
+	if (lock_file_fd < 0) {
+		if (errno == EEXIST) {
+			err = 1;
+		} else {
+			err = IO_ERROR;
+		}
+		goto exit;
+	}
+	have_lock = true;
+
+	format_name(&new_table_name, st->merged->stack[first]->min_update_index,
+		    st->merged->stack[last]->max_update_index);
+	slice_append_string(&new_table_name, ".ref");
+
+	slice_set_string(&new_table_path, st->reftable_dir);
+	slice_append_string(&new_table_path, "/");
+
+	slice_append(&new_table_path, new_table_name);
+
+	err = rename(slice_as_string(&temp_tab_name),
+		     slice_as_string(&new_table_path));
+	if (err < 0) {
+		goto exit;
+	}
+
+	for (i = 0; i < first; i++) {
+		slice_append_string(&ref_list_contents,
+				    st->merged->stack[i]->name);
+		slice_append_string(&ref_list_contents, "\n");
+	}
+	slice_append(&ref_list_contents, new_table_name);
+	slice_append_string(&ref_list_contents, "\n");
+	for (i = last + 1; i < st->merged->stack_len; i++) {
+		slice_append_string(&ref_list_contents,
+				    st->merged->stack[i]->name);
+		slice_append_string(&ref_list_contents, "\n");
+	}
+
+	err = write(lock_file_fd, ref_list_contents.buf, ref_list_contents.len);
+	if (err < 0) {
+		unlink(slice_as_string(&new_table_path));
+		goto exit;
+	}
+	err = close(lock_file_fd);
+	lock_file_fd = 0;
+	if (err < 0) {
+		unlink(slice_as_string(&new_table_path));
+		goto exit;
+	}
+
+	err = rename(slice_as_string(&lock_file_name), st->list_file);
+	if (err < 0) {
+		unlink(slice_as_string(&new_table_path));
+		goto exit;
+	}
+	have_lock = false;
+
+	for (char **p = delete_on_success; *p; p++) {
+		if (strcmp(*p, slice_as_string(&new_table_path))) {
+			unlink(*p);
+		}
+	}
+
+	err = stack_reload_maybe_reuse(st, first < last);
+exit:
+	for (char **p = subtable_locks; *p; p++) {
+		unlink(*p);
+	}
+	free_names(delete_on_success);
+	free_names(subtable_locks);
+	if (lock_file_fd > 0) {
+		close(lock_file_fd);
+		lock_file_fd = 0;
+	}
+	if (have_lock) {
+		unlink(slice_as_string(&lock_file_name));
+	}
+	free(slice_yield(&new_table_name));
+	free(slice_yield(&new_table_path));
+	free(slice_yield(&ref_list_contents));
+	free(slice_yield(&temp_tab_name));
+	free(slice_yield(&lock_file_name));
+	return err;
+}
+
+int stack_compact_all(struct stack *st, struct log_expiry_config *config)
+{
+	return stack_compact_range(st, 0, st->merged->stack_len - 1, config);
+}
+
+static int stack_compact_range_stats(struct stack *st, int first, int last,
+				     struct log_expiry_config *config)
+{
+	int err = stack_compact_range(st, first, last, config);
+	if (err > 0) {
+		st->stats.failures++;
+	}
+	return err;
+}
+
+static int segment_size(struct segment *s)
+{
+	return s->end - s->start;
+}
+
+int fastlog2(uint64_t sz)
+{
+	int l = 0;
+	assert(sz > 0);
+	for (; sz; sz /= 2) {
+		l++;
+	}
+	return l - 1;
+}
+
+struct segment *sizes_to_segments(int *seglen, uint64_t *sizes, int n)
+{
+	struct segment *segs = calloc(sizeof(struct segment), n);
+	int next = 0;
+	struct segment cur = {};
+	int i = 0;
+	for (i = 0; i < n; i++) {
+		int log = fastlog2(sizes[i]);
+		if (cur.log != log && cur.bytes > 0) {
+			struct segment fresh = {
+				.start = i,
+			};
+
+			segs[next++] = cur;
+			cur = fresh;
+		}
+
+		cur.log = log;
+		cur.end = i + 1;
+		cur.bytes += sizes[i];
+	}
+
+	segs[next++] = cur;
+	*seglen = next;
+	return segs;
+}
+
+struct segment suggest_compaction_segment(uint64_t *sizes, int n)
+{
+	int seglen = 0;
+	struct segment *segs = sizes_to_segments(&seglen, sizes, n);
+	struct segment min_seg = {
+		.log = 64,
+	};
+	int i = 0;
+	for (i = 0; i < seglen; i++) {
+		if (segment_size(&segs[i]) == 1) {
+			continue;
+		}
+
+		if (segs[i].log < min_seg.log) {
+			min_seg = segs[i];
+		}
+	}
+
+	while (min_seg.start > 0) {
+		int prev = min_seg.start - 1;
+		if (fastlog2(min_seg.bytes) < fastlog2(sizes[prev])) {
+			break;
+		}
+
+		min_seg.start = prev;
+		min_seg.bytes += sizes[prev];
+	}
+
+	free(segs);
+	return min_seg;
+}
+
+static uint64_t *stack_table_sizes_for_compaction(struct stack *st)
+{
+	uint64_t *sizes = calloc(sizeof(uint64_t), st->merged->stack_len);
+	int i = 0;
+	for (i = 0; i < st->merged->stack_len; i++) {
+		/* overhead is 24 + 68 = 92. */
+		sizes[i] = st->merged->stack[i]->size - 91;
+	}
+	return sizes;
+}
+
+int stack_auto_compact(struct stack *st)
+{
+	uint64_t *sizes = stack_table_sizes_for_compaction(st);
+	struct segment seg =
+		suggest_compaction_segment(sizes, st->merged->stack_len);
+	free(sizes);
+	if (segment_size(&seg) > 0) {
+		return stack_compact_range_stats(st, seg.start, seg.end - 1,
+						 NULL);
+	}
+
+	return 0;
+}
+
+struct compaction_stats *stack_compaction_stats(struct stack *st)
+{
+	return &st->stats;
+}
+
+int stack_read_ref(struct stack *st, const char *refname,
+		   struct ref_record *ref)
+{
+	struct iterator it = {};
+	struct merged_table *mt = stack_merged_table(st);
+	int err = merged_table_seek_ref(mt, &it, refname);
+	if (err) {
+		goto exit;
+	}
+
+	err = iterator_next_ref(it, ref);
+	if (err) {
+		goto exit;
+	}
+
+	if (strcmp(ref->ref_name, refname) || ref_record_is_deletion(ref)) {
+		err = 1;
+		goto exit;
+	}
+
+exit:
+	iterator_destroy(&it);
+	return err;
+}
+
+int stack_read_log(struct stack *st, const char *refname,
+		   struct log_record *log)
+{
+	struct iterator it = {};
+	struct merged_table *mt = stack_merged_table(st);
+	int err = merged_table_seek_log(mt, &it, refname);
+	if (err) {
+		goto exit;
+	}
+
+	err = iterator_next_log(it, log);
+	if (err) {
+		goto exit;
+	}
+
+	if (strcmp(log->ref_name, refname) || log_record_is_deletion(log)) {
+		err = 1;
+		goto exit;
+	}
+
+exit:
+	iterator_destroy(&it);
+	return err;
+}
diff --git a/reftable/stack.h b/reftable/stack.h
new file mode 100644
index 0000000000..d5e2c93c29
--- /dev/null
+++ b/reftable/stack.h
@@ -0,0 +1,40 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef STACK_H
+#define STACK_H
+
+#include "reftable.h"
+
+struct stack {
+	char *list_file;
+	char *reftable_dir;
+
+	struct write_options config;
+
+	struct merged_table *merged;
+	struct compaction_stats stats;
+};
+
+int read_lines(const char *filename, char ***lines);
+int stack_try_add(struct stack *st,
+		  int (*write_table)(struct writer *wr, void *arg), void *arg);
+int stack_write_compact(struct stack *st, struct writer *wr, int first,
+			int last, struct log_expiry_config *config);
+int fastlog2(uint64_t sz);
+
+struct segment {
+	int start, end;
+	int log;
+	uint64_t bytes;
+};
+
+struct segment *sizes_to_segments(int *seglen, uint64_t *sizes, int n);
+struct segment suggest_compaction_segment(uint64_t *sizes, int n);
+
+#endif
diff --git a/reftable/system.h b/reftable/system.h
new file mode 100644
index 0000000000..e27c80e3ca
--- /dev/null
+++ b/reftable/system.h
@@ -0,0 +1,58 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef SYSTEM_H
+#define SYSTEM_H
+
+#include "config.h"
+
+#ifndef REFTABLE_STANDALONE
+
+#include "git-compat-util.h"
+#include <zlib.h>
+
+#else
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <zlib.h>
+
+#define ARRAY_SIZE(a) sizeof((a)) / sizeof((a)[0])
+#define FREE_AND_NULL(x)    \
+	do {                \
+		free(x);    \
+		(x) = NULL; \
+	} while (0)
+#define QSORT(arr, n, cmp) qsort(arr, n, sizeof(arr[0]), cmp)
+#define SWAP(a, b)                              \
+	{                                       \
+		char tmp[sizeof(a)];            \
+		assert(sizeof(a) == sizeof(b)); \
+		memcpy(&tmp[0], &a, sizeof(a)); \
+		memcpy(&a, &b, sizeof(a));      \
+		memcpy(&b, &tmp[0], sizeof(a)); \
+	}
+#endif
+
+typedef uint8_t byte;
+typedef int bool;
+
+int uncompress_return_consumed(Bytef *dest, uLongf *destLen,
+			       const Bytef *source, uLong *sourceLen);
+
+#endif
diff --git a/reftable/tree.c b/reftable/tree.c
new file mode 100644
index 0000000000..9bf7fe531f
--- /dev/null
+++ b/reftable/tree.c
@@ -0,0 +1,66 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "tree.h"
+
+#include "system.h"
+
+struct tree_node *tree_search(void *key, struct tree_node **rootp,
+			      int (*compare)(const void *, const void *),
+			      int insert)
+{
+	if (*rootp == NULL) {
+		if (!insert) {
+			return NULL;
+		} else {
+			struct tree_node *n =
+				calloc(sizeof(struct tree_node), 1);
+			n->key = key;
+			*rootp = n;
+			return *rootp;
+		}
+	}
+
+	{
+		int res = compare(key, (*rootp)->key);
+		if (res < 0) {
+			return tree_search(key, &(*rootp)->left, compare,
+					   insert);
+		} else if (res > 0) {
+			return tree_search(key, &(*rootp)->right, compare,
+					   insert);
+		}
+	}
+	return *rootp;
+}
+
+void infix_walk(struct tree_node *t, void (*action)(void *arg, void *key),
+		void *arg)
+{
+	if (t->left != NULL) {
+		infix_walk(t->left, action, arg);
+	}
+	action(arg, t->key);
+	if (t->right != NULL) {
+		infix_walk(t->right, action, arg);
+	}
+}
+
+void tree_free(struct tree_node *t)
+{
+	if (t == NULL) {
+		return;
+	}
+	if (t->left != NULL) {
+		tree_free(t->left);
+	}
+	if (t->right != NULL) {
+		tree_free(t->right);
+	}
+	free(t);
+}
diff --git a/reftable/tree.h b/reftable/tree.h
new file mode 100644
index 0000000000..86a71715ae
--- /dev/null
+++ b/reftable/tree.h
@@ -0,0 +1,24 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef TREE_H
+#define TREE_H
+
+struct tree_node {
+	void *key;
+	struct tree_node *left, *right;
+};
+
+struct tree_node *tree_search(void *key, struct tree_node **rootp,
+			      int (*compare)(const void *, const void *),
+			      int insert);
+void infix_walk(struct tree_node *t, void (*action)(void *arg, void *key),
+		void *arg);
+void tree_free(struct tree_node *t);
+
+#endif
diff --git a/reftable/writer.c b/reftable/writer.c
new file mode 100644
index 0000000000..3247481df3
--- /dev/null
+++ b/reftable/writer.c
@@ -0,0 +1,623 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "writer.h"
+
+#include "system.h"
+
+#include "block.h"
+#include "constants.h"
+#include "record.h"
+#include "reftable.h"
+#include "tree.h"
+
+static struct block_stats *writer_block_stats(struct writer *w, byte typ)
+{
+	switch (typ) {
+	case 'r':
+		return &w->stats.ref_stats;
+	case 'o':
+		return &w->stats.obj_stats;
+	case 'i':
+		return &w->stats.idx_stats;
+	case 'g':
+		return &w->stats.log_stats;
+	}
+	assert(false);
+	return NULL;
+}
+
+/* write data, queuing the padding for the next write. Returns negative for
+ * error. */
+static int padded_write(struct writer *w, byte *data, size_t len, int padding)
+{
+	int n = 0;
+	if (w->pending_padding > 0) {
+		byte *zeroed = calloc(w->pending_padding, 1);
+		int n = w->write(w->write_arg, zeroed, w->pending_padding);
+		if (n < 0) {
+			return n;
+		}
+
+		w->pending_padding = 0;
+		free(zeroed);
+	}
+
+	w->pending_padding = padding;
+	n = w->write(w->write_arg, data, len);
+	if (n < 0) {
+		return n;
+	}
+	n += padding;
+	return 0;
+}
+
+static void options_set_defaults(struct write_options *opts)
+{
+	if (opts->restart_interval == 0) {
+		opts->restart_interval = 16;
+	}
+
+	if (opts->block_size == 0) {
+		opts->block_size = DEFAULT_BLOCK_SIZE;
+	}
+}
+
+static int writer_write_header(struct writer *w, byte *dest)
+{
+	memcpy((char *)dest, "REFT", 4);
+	dest[4] = 1; /* version */
+	put_u24(dest + 5, w->opts.block_size);
+	put_u64(dest + 8, w->min_update_index);
+	put_u64(dest + 16, w->max_update_index);
+	return 24;
+}
+
+static void writer_reinit_block_writer(struct writer *w, byte typ)
+{
+	int block_start = 0;
+	if (w->next == 0) {
+		block_start = HEADER_SIZE;
+	}
+
+	block_writer_init(&w->block_writer_data, typ, w->block,
+			  w->opts.block_size, block_start, w->hash_size);
+	w->block_writer = &w->block_writer_data;
+	w->block_writer->restart_interval = w->opts.restart_interval;
+}
+
+struct writer *new_writer(int (*writer_func)(void *, byte *, int),
+			  void *writer_arg, struct write_options *opts)
+{
+	struct writer *wp = calloc(sizeof(struct writer), 1);
+	options_set_defaults(opts);
+	if (opts->block_size >= (1 << 24)) {
+		/* TODO - error return? */
+		abort();
+	}
+	wp->hash_size = SHA1_SIZE;
+	wp->block = calloc(opts->block_size, 1);
+	wp->write = writer_func;
+	wp->write_arg = writer_arg;
+	wp->opts = *opts;
+	writer_reinit_block_writer(wp, BLOCK_TYPE_REF);
+
+	return wp;
+}
+
+void writer_set_limits(struct writer *w, uint64_t min, uint64_t max)
+{
+	w->min_update_index = min;
+	w->max_update_index = max;
+}
+
+void writer_free(struct writer *w)
+{
+	free(w->block);
+	free(w);
+}
+
+struct obj_index_tree_node {
+	struct slice hash;
+	uint64_t *offsets;
+	int offset_len;
+	int offset_cap;
+};
+
+static int obj_index_tree_node_compare(const void *a, const void *b)
+{
+	return slice_compare(((const struct obj_index_tree_node *)a)->hash,
+			     ((const struct obj_index_tree_node *)b)->hash);
+}
+
+static void writer_index_hash(struct writer *w, struct slice hash)
+{
+	uint64_t off = w->next;
+
+	struct obj_index_tree_node want = { .hash = hash };
+
+	struct tree_node *node = tree_search(&want, &w->obj_index_tree,
+					     &obj_index_tree_node_compare, 0);
+	struct obj_index_tree_node *key = NULL;
+	if (node == NULL) {
+		key = calloc(sizeof(struct obj_index_tree_node), 1);
+		slice_copy(&key->hash, hash);
+		tree_search((void *)key, &w->obj_index_tree,
+			    &obj_index_tree_node_compare, 1);
+	} else {
+		key = node->key;
+	}
+
+	if (key->offset_len > 0 && key->offsets[key->offset_len - 1] == off) {
+		return;
+	}
+
+	if (key->offset_len == key->offset_cap) {
+		key->offset_cap = 2 * key->offset_cap + 1;
+		key->offsets = realloc(key->offsets,
+				       sizeof(uint64_t) * key->offset_cap);
+	}
+
+	key->offsets[key->offset_len++] = off;
+}
+
+static int writer_add_record(struct writer *w, struct record rec)
+{
+	int result = -1;
+	struct slice key = {};
+	int err = 0;
+	record_key(rec, &key);
+	if (slice_compare(w->last_key, key) >= 0) {
+		goto exit;
+	}
+
+	slice_copy(&w->last_key, key);
+	if (w->block_writer == NULL) {
+		writer_reinit_block_writer(w, record_type(rec));
+	}
+
+	assert(block_writer_type(w->block_writer) == record_type(rec));
+
+	if (block_writer_add(w->block_writer, rec) == 0) {
+		result = 0;
+		goto exit;
+	}
+
+	err = writer_flush_block(w);
+	if (err < 0) {
+		result = err;
+		goto exit;
+	}
+
+	writer_reinit_block_writer(w, record_type(rec));
+	err = block_writer_add(w->block_writer, rec);
+	if (err < 0) {
+		result = err;
+		goto exit;
+	}
+
+	result = 0;
+exit:
+	free(slice_yield(&key));
+	return result;
+}
+
+int writer_add_ref(struct writer *w, struct ref_record *ref)
+{
+	struct record rec = {};
+	struct ref_record copy = *ref;
+	int err = 0;
+
+	if (ref->ref_name == NULL) {
+		return API_ERROR;
+	}
+	if (ref->update_index < w->min_update_index ||
+	    ref->update_index > w->max_update_index) {
+		return API_ERROR;
+	}
+
+	record_from_ref(&rec, &copy);
+	copy.update_index -= w->min_update_index;
+	err = writer_add_record(w, rec);
+	if (err < 0) {
+		return err;
+	}
+
+	if (!w->opts.skip_index_objects && ref->value != NULL) {
+		struct slice h = {
+			.buf = ref->value,
+			.len = w->hash_size,
+		};
+
+		writer_index_hash(w, h);
+	}
+	if (!w->opts.skip_index_objects && ref->target_value != NULL) {
+		struct slice h = {
+			.buf = ref->target_value,
+			.len = w->hash_size,
+		};
+		writer_index_hash(w, h);
+	}
+	return 0;
+}
+
+int writer_add_refs(struct writer *w, struct ref_record *refs, int n)
+{
+	int err = 0;
+	int i = 0;
+	QSORT(refs, n, ref_record_compare_name);
+	for (i = 0; err == 0 && i < n; i++) {
+		err = writer_add_ref(w, &refs[i]);
+	}
+	return err;
+}
+
+int writer_add_log(struct writer *w, struct log_record *log)
+{
+	if (log->ref_name == NULL) {
+		return API_ERROR;
+	}
+
+	if (w->block_writer != NULL &&
+	    block_writer_type(w->block_writer) == BLOCK_TYPE_REF) {
+		int err = writer_finish_public_section(w);
+		if (err < 0) {
+			return err;
+		}
+	}
+
+	w->next -= w->pending_padding;
+	w->pending_padding = 0;
+
+	{
+		struct record rec = {};
+		int err;
+		record_from_log(&rec, log);
+		err = writer_add_record(w, rec);
+		return err;
+	}
+}
+
+int writer_add_logs(struct writer *w, struct log_record *logs, int n)
+{
+	int err = 0;
+	int i = 0;
+	QSORT(logs, n, log_record_compare_key);
+	for (i = 0; err == 0 && i < n; i++) {
+		err = writer_add_log(w, &logs[i]);
+	}
+	return err;
+}
+
+static int writer_finish_section(struct writer *w)
+{
+	byte typ = block_writer_type(w->block_writer);
+	uint64_t index_start = 0;
+	int max_level = 0;
+	int threshold = w->opts.unpadded ? 1 : 3;
+	int before_blocks = w->stats.idx_stats.blocks;
+	int err = writer_flush_block(w);
+	int i = 0;
+	if (err < 0) {
+		return err;
+	}
+
+	while (w->index_len > threshold) {
+		struct index_record *idx = NULL;
+		int idx_len = 0;
+
+		max_level++;
+		index_start = w->next;
+		writer_reinit_block_writer(w, BLOCK_TYPE_INDEX);
+
+		idx = w->index;
+		idx_len = w->index_len;
+
+		w->index = NULL;
+		w->index_len = 0;
+		w->index_cap = 0;
+		for (i = 0; i < idx_len; i++) {
+			struct record rec = {};
+			record_from_index(&rec, idx + i);
+			if (block_writer_add(w->block_writer, rec) == 0) {
+				continue;
+			}
+
+			{
+				int err = writer_flush_block(w);
+				if (err < 0) {
+					return err;
+				}
+			}
+
+			writer_reinit_block_writer(w, BLOCK_TYPE_INDEX);
+
+			err = block_writer_add(w->block_writer, rec);
+			assert(err == 0);
+		}
+		for (i = 0; i < idx_len; i++) {
+			free(slice_yield(&idx[i].last_key));
+		}
+		free(idx);
+	}
+
+	writer_clear_index(w);
+
+	err = writer_flush_block(w);
+	if (err < 0) {
+		return err;
+	}
+
+	{
+		struct block_stats *bstats = writer_block_stats(w, typ);
+		bstats->index_blocks =
+			w->stats.idx_stats.blocks - before_blocks;
+		bstats->index_offset = index_start;
+		bstats->max_index_level = max_level;
+	}
+
+	/* Reinit lastKey, as the next section can start with any key. */
+	w->last_key.len = 0;
+
+	return 0;
+}
+
+struct common_prefix_arg {
+	struct slice *last;
+	int max;
+};
+
+static void update_common(void *void_arg, void *key)
+{
+	struct common_prefix_arg *arg = (struct common_prefix_arg *)void_arg;
+	struct obj_index_tree_node *entry = (struct obj_index_tree_node *)key;
+	if (arg->last != NULL) {
+		int n = common_prefix_size(entry->hash, *arg->last);
+		if (n > arg->max) {
+			arg->max = n;
+		}
+	}
+	arg->last = &entry->hash;
+}
+
+struct write_record_arg {
+	struct writer *w;
+	int err;
+};
+
+static void write_object_record(void *void_arg, void *key)
+{
+	struct write_record_arg *arg = (struct write_record_arg *)void_arg;
+	struct obj_index_tree_node *entry = (struct obj_index_tree_node *)key;
+	struct obj_record obj_rec = {
+		.hash_prefix = entry->hash.buf,
+		.hash_prefix_len = arg->w->stats.object_id_len,
+		.offsets = entry->offsets,
+		.offset_len = entry->offset_len,
+	};
+	struct record rec = {};
+	if (arg->err < 0) {
+		goto exit;
+	}
+
+	record_from_obj(&rec, &obj_rec);
+	arg->err = block_writer_add(arg->w->block_writer, rec);
+	if (arg->err == 0) {
+		goto exit;
+	}
+
+	arg->err = writer_flush_block(arg->w);
+	if (arg->err < 0) {
+		goto exit;
+	}
+
+	writer_reinit_block_writer(arg->w, BLOCK_TYPE_OBJ);
+	arg->err = block_writer_add(arg->w->block_writer, rec);
+	if (arg->err == 0) {
+		goto exit;
+	}
+	obj_rec.offset_len = 0;
+	arg->err = block_writer_add(arg->w->block_writer, rec);
+
+	/* Should be able to write into a fresh block. */
+	assert(arg->err == 0);
+
+exit:;
+}
+
+static void object_record_free(void *void_arg, void *key)
+{
+	struct obj_index_tree_node *entry = (struct obj_index_tree_node *)key;
+
+	FREE_AND_NULL(entry->offsets);
+	free(slice_yield(&entry->hash));
+	free(entry);
+}
+
+static int writer_dump_object_index(struct writer *w)
+{
+	struct write_record_arg closure = { .w = w };
+	struct common_prefix_arg common = {};
+	if (w->obj_index_tree != NULL) {
+		infix_walk(w->obj_index_tree, &update_common, &common);
+	}
+	w->stats.object_id_len = common.max + 1;
+
+	writer_reinit_block_writer(w, BLOCK_TYPE_OBJ);
+
+	if (w->obj_index_tree != NULL) {
+		infix_walk(w->obj_index_tree, &write_object_record, &closure);
+	}
+
+	if (closure.err < 0) {
+		return closure.err;
+	}
+	return writer_finish_section(w);
+}
+
+int writer_finish_public_section(struct writer *w)
+{
+	byte typ = 0;
+	int err = 0;
+
+	if (w->block_writer == NULL) {
+		return 0;
+	}
+
+	typ = block_writer_type(w->block_writer);
+	err = writer_finish_section(w);
+	if (err < 0) {
+		return err;
+	}
+	if (typ == BLOCK_TYPE_REF && !w->opts.skip_index_objects &&
+	    w->stats.ref_stats.index_blocks > 0) {
+		err = writer_dump_object_index(w);
+		if (err < 0) {
+			return err;
+		}
+	}
+
+	if (w->obj_index_tree != NULL) {
+		infix_walk(w->obj_index_tree, &object_record_free, NULL);
+		tree_free(w->obj_index_tree);
+		w->obj_index_tree = NULL;
+	}
+
+	w->block_writer = NULL;
+	return 0;
+}
+
+int writer_close(struct writer *w)
+{
+	byte footer[68];
+	byte *p = footer;
+
+	writer_finish_public_section(w);
+
+	writer_write_header(w, footer);
+	p += 24;
+	put_u64(p, w->stats.ref_stats.index_offset);
+	p += 8;
+	put_u64(p, (w->stats.obj_stats.offset) << 5 | w->stats.object_id_len);
+	p += 8;
+	put_u64(p, w->stats.obj_stats.index_offset);
+	p += 8;
+
+	put_u64(p, w->stats.log_stats.offset);
+	p += 8;
+	put_u64(p, w->stats.log_stats.index_offset);
+	p += 8;
+
+	put_u32(p, crc32(0, footer, p - footer));
+	p += 4;
+	w->pending_padding = 0;
+
+	{
+		int n = padded_write(w, footer, sizeof(footer), 0);
+		if (n < 0) {
+			return n;
+		}
+	}
+
+	/* free up memory. */
+	block_writer_clear(&w->block_writer_data);
+	writer_clear_index(w);
+	free(slice_yield(&w->last_key));
+	return 0;
+}
+
+void writer_clear_index(struct writer *w)
+{
+	int i = 0;
+	for (i = 0; i < w->index_len; i++) {
+		free(slice_yield(&w->index[i].last_key));
+	}
+
+	FREE_AND_NULL(w->index);
+	w->index_len = 0;
+	w->index_cap = 0;
+}
+
+const int debug = 0;
+
+static int writer_flush_nonempty_block(struct writer *w)
+{
+	byte typ = block_writer_type(w->block_writer);
+	struct block_stats *bstats = writer_block_stats(w, typ);
+	uint64_t block_typ_off = (bstats->blocks == 0) ? w->next : 0;
+	int raw_bytes = block_writer_finish(w->block_writer);
+	int padding = 0;
+	int err = 0;
+	if (raw_bytes < 0) {
+		return raw_bytes;
+	}
+
+	if (!w->opts.unpadded && typ != BLOCK_TYPE_LOG) {
+		padding = w->opts.block_size - raw_bytes;
+	}
+
+	if (block_typ_off > 0) {
+		bstats->offset = block_typ_off;
+	}
+
+	bstats->entries += w->block_writer->entries;
+	bstats->restarts += w->block_writer->restart_len;
+	bstats->blocks++;
+	w->stats.blocks++;
+
+	if (debug) {
+		fprintf(stderr, "block %c off %" PRIu64 " sz %d (%d)\n", typ,
+			w->next, raw_bytes,
+			get_u24(w->block + w->block_writer->header_off + 1));
+	}
+
+	if (w->next == 0) {
+		writer_write_header(w, w->block);
+	}
+
+	err = padded_write(w, w->block, raw_bytes, padding);
+	if (err < 0) {
+		return err;
+	}
+
+	if (w->index_cap == w->index_len) {
+		w->index_cap = 2 * w->index_cap + 1;
+		w->index = realloc(w->index,
+				   sizeof(struct index_record) * w->index_cap);
+	}
+
+	{
+		struct index_record ir = {
+			.offset = w->next,
+		};
+		slice_copy(&ir.last_key, w->block_writer->last_key);
+		w->index[w->index_len] = ir;
+	}
+
+	w->index_len++;
+	w->next += padding + raw_bytes;
+	block_writer_reset(&w->block_writer_data);
+	w->block_writer = NULL;
+	return 0;
+}
+
+int writer_flush_block(struct writer *w)
+{
+	if (w->block_writer == NULL) {
+		return 0;
+	}
+	if (w->block_writer->entries == 0) {
+		return 0;
+	}
+	return writer_flush_nonempty_block(w);
+}
+
+struct stats *writer_stats(struct writer *w)
+{
+	return &w->stats;
+}
diff --git a/reftable/writer.h b/reftable/writer.h
new file mode 100644
index 0000000000..bd2386474b
--- /dev/null
+++ b/reftable/writer.h
@@ -0,0 +1,46 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef WRITER_H
+#define WRITER_H
+
+#include "basics.h"
+#include "block.h"
+#include "reftable.h"
+#include "slice.h"
+#include "tree.h"
+
+struct writer {
+	int (*write)(void *, byte *, int);
+	void *write_arg;
+	int pending_padding;
+	int hash_size;
+	struct slice last_key;
+
+	uint64_t next;
+	uint64_t min_update_index, max_update_index;
+	struct write_options opts;
+
+	byte *block;
+	struct block_writer *block_writer;
+	struct block_writer block_writer_data;
+	struct index_record *index;
+	int index_len;
+	int index_cap;
+
+	/* tree for use with tsearch */
+	struct tree_node *obj_index_tree;
+
+	struct stats stats;
+};
+
+int writer_flush_block(struct writer *w);
+void writer_clear_index(struct writer *w);
+int writer_finish_public_section(struct writer *w);
+
+#endif
diff --git a/reftable/zlib-compat.c b/reftable/zlib-compat.c
new file mode 100644
index 0000000000..3e0b0f24f1
--- /dev/null
+++ b/reftable/zlib-compat.c
@@ -0,0 +1,92 @@
+/* taken from zlib's uncompr.c
+
+   commit cacf7f1d4e3d44d871b605da3b647f07d718623f
+   Author: Mark Adler <madler@alumni.caltech.edu>
+   Date:   Sun Jan 15 09:18:46 2017 -0800
+
+       zlib 1.2.11
+
+*/
+
+/*
+ * Copyright (C) 1995-2003, 2010, 2014, 2016 Jean-loup Gailly, Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+#include "system.h"
+
+/* clang-format off */
+
+/* ===========================================================================
+     Decompresses the source buffer into the destination buffer.  *sourceLen is
+   the byte length of the source buffer. Upon entry, *destLen is the total size
+   of the destination buffer, which must be large enough to hold the entire
+   uncompressed data. (The size of the uncompressed data must have been saved
+   previously by the compressor and transmitted to the decompressor by some
+   mechanism outside the scope of this compression library.) Upon exit,
+   *destLen is the size of the decompressed data and *sourceLen is the number
+   of source bytes consumed. Upon return, source + *sourceLen points to the
+   first unused input byte.
+
+     uncompress returns Z_OK if success, Z_MEM_ERROR if there was not enough
+   memory, Z_BUF_ERROR if there was not enough room in the output buffer, or
+   Z_DATA_ERROR if the input data was corrupted, including if the input data is
+   an incomplete zlib stream.
+*/
+int ZEXPORT uncompress_return_consumed (
+    Bytef *dest,
+    uLongf *destLen,
+    const Bytef *source,
+    uLong *sourceLen) {
+    z_stream stream;
+    int err;
+    const uInt max = (uInt)-1;
+    uLong len, left;
+    Byte buf[1];    /* for detection of incomplete stream when *destLen == 0 */
+
+    len = *sourceLen;
+    if (*destLen) {
+        left = *destLen;
+        *destLen = 0;
+    }
+    else {
+        left = 1;
+        dest = buf;
+    }
+
+    stream.next_in = (z_const Bytef *)source;
+    stream.avail_in = 0;
+    stream.zalloc = (alloc_func)0;
+    stream.zfree = (free_func)0;
+    stream.opaque = (voidpf)0;
+
+    err = inflateInit(&stream);
+    if (err != Z_OK) return err;
+
+    stream.next_out = dest;
+    stream.avail_out = 0;
+
+    do {
+        if (stream.avail_out == 0) {
+            stream.avail_out = left > (uLong)max ? max : (uInt)left;
+            left -= stream.avail_out;
+        }
+        if (stream.avail_in == 0) {
+            stream.avail_in = len > (uLong)max ? max : (uInt)len;
+            len -= stream.avail_in;
+        }
+        err = inflate(&stream, Z_NO_FLUSH);
+    } while (err == Z_OK);
+
+    *sourceLen -= len + stream.avail_in;
+    if (dest != buf)
+        *destLen = stream.total_out;
+    else if (stream.total_out && err == Z_BUF_ERROR)
+        left = 1;
+
+    inflateEnd(&stream);
+    return err == Z_STREAM_END ? Z_OK :
+           err == Z_NEED_DICT ? Z_DATA_ERROR  :
+           err == Z_BUF_ERROR && left + stream.avail_out ? Z_DATA_ERROR :
+           err;
+}
-- 
gitgitgadget


^ permalink raw reply related	[relevance 1%]

* [PATCH v7 02/10] t1300: fix over-indented HERE-DOCs
  @ 2020-02-10  0:30  6%         ` Matthew Rogers via GitGitGadget
  0 siblings, 0 replies; 200+ results
From: Matthew Rogers via GitGitGadget @ 2020-02-10  0:30 UTC (permalink / raw)
  To: git; +Cc: Matthew Rogers, Matthew Rogers

From: Matthew Rogers <mattr94@gmail.com>

Prepare for the following patches by removing extraneous indents from
HERE-DOCs used in config tests.

Signed-off-by: Matthew Rogers <mattr94@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 t/t1300-config.sh | 168 +++++++++++++++++++++++-----------------------
 1 file changed, 84 insertions(+), 84 deletions(-)

diff --git a/t/t1300-config.sh b/t/t1300-config.sh
index 983a0a1583..e8b4575758 100755
--- a/t/t1300-config.sh
+++ b/t/t1300-config.sh
@@ -1191,47 +1191,47 @@ test_expect_success 'old-fashioned settings are case insensitive' '
 	test_when_finished "rm -f testConfig testConfig_expect testConfig_actual" &&
 
 	cat >testConfig_actual <<-EOF &&
-		[V.A]
-		r = value1
+	[V.A]
+	r = value1
 	EOF
 	q_to_tab >testConfig_expect <<-EOF &&
-		[V.A]
-		Qr = value2
+	[V.A]
+	Qr = value2
 	EOF
 	git config -f testConfig_actual "v.a.r" value2 &&
 	test_cmp testConfig_expect testConfig_actual &&
 
 	cat >testConfig_actual <<-EOF &&
-		[V.A]
-		r = value1
+	[V.A]
+	r = value1
 	EOF
 	q_to_tab >testConfig_expect <<-EOF &&
-		[V.A]
-		QR = value2
+	[V.A]
+	QR = value2
 	EOF
 	git config -f testConfig_actual "V.a.R" value2 &&
 	test_cmp testConfig_expect testConfig_actual &&
 
 	cat >testConfig_actual <<-EOF &&
-		[V.A]
-		r = value1
+	[V.A]
+	r = value1
 	EOF
 	q_to_tab >testConfig_expect <<-EOF &&
-		[V.A]
-		r = value1
-		Qr = value2
+	[V.A]
+	r = value1
+	Qr = value2
 	EOF
 	git config -f testConfig_actual "V.A.r" value2 &&
 	test_cmp testConfig_expect testConfig_actual &&
 
 	cat >testConfig_actual <<-EOF &&
-		[V.A]
-		r = value1
+	[V.A]
+	r = value1
 	EOF
 	q_to_tab >testConfig_expect <<-EOF &&
-		[V.A]
-		r = value1
-		Qr = value2
+	[V.A]
+	r = value1
+	Qr = value2
 	EOF
 	git config -f testConfig_actual "v.A.r" value2 &&
 	test_cmp testConfig_expect testConfig_actual
@@ -1241,26 +1241,26 @@ test_expect_success 'setting different case sensitive subsections ' '
 	test_when_finished "rm -f testConfig testConfig_expect testConfig_actual" &&
 
 	cat >testConfig_actual <<-EOF &&
-		[V "A"]
-		R = v1
-		[K "E"]
-		Y = v1
-		[a "b"]
-		c = v1
-		[d "e"]
-		f = v1
+	[V "A"]
+	R = v1
+	[K "E"]
+	Y = v1
+	[a "b"]
+	c = v1
+	[d "e"]
+	f = v1
 	EOF
 	q_to_tab >testConfig_expect <<-EOF &&
-		[V "A"]
-		Qr = v2
-		[K "E"]
-		Qy = v2
-		[a "b"]
-		Qc = v2
-		[d "e"]
-		f = v1
-		[d "E"]
-		Qf = v2
+	[V "A"]
+	Qr = v2
+	[K "E"]
+	Qy = v2
+	[a "b"]
+	Qc = v2
+	[d "e"]
+	f = v1
+	[d "E"]
+	Qf = v2
 	EOF
 	# exact match
 	git config -f testConfig_actual a.b.c v2 &&
@@ -1622,40 +1622,40 @@ test_expect_success 'set up --show-origin tests' '
 	INCLUDE_DIR="$HOME/include" &&
 	mkdir -p "$INCLUDE_DIR" &&
 	cat >"$INCLUDE_DIR"/absolute.include <<-\EOF &&
-		[user]
-			absolute = include
+	[user]
+		absolute = include
 	EOF
 	cat >"$INCLUDE_DIR"/relative.include <<-\EOF &&
-		[user]
-			relative = include
+	[user]
+		relative = include
 	EOF
 	cat >"$HOME"/.gitconfig <<-EOF &&
-		[user]
-			global = true
-			override = global
-		[include]
-			path = "$INCLUDE_DIR/absolute.include"
+	[user]
+		global = true
+		override = global
+	[include]
+		path = "$INCLUDE_DIR/absolute.include"
 	EOF
 	cat >.git/config <<-\EOF
-		[user]
-			local = true
-			override = local
-		[include]
-			path = ../include/relative.include
+	[user]
+		local = true
+		override = local
+	[include]
+		path = ../include/relative.include
 	EOF
 '
 
 test_expect_success '--show-origin with --list' '
 	cat >expect <<-EOF &&
-		file:$HOME/.gitconfig	user.global=true
-		file:$HOME/.gitconfig	user.override=global
-		file:$HOME/.gitconfig	include.path=$INCLUDE_DIR/absolute.include
-		file:$INCLUDE_DIR/absolute.include	user.absolute=include
-		file:.git/config	user.local=true
-		file:.git/config	user.override=local
-		file:.git/config	include.path=../include/relative.include
-		file:.git/../include/relative.include	user.relative=include
-		command line:	user.cmdline=true
+	file:$HOME/.gitconfig	user.global=true
+	file:$HOME/.gitconfig	user.override=global
+	file:$HOME/.gitconfig	include.path=$INCLUDE_DIR/absolute.include
+	file:$INCLUDE_DIR/absolute.include	user.absolute=include
+	file:.git/config	user.local=true
+	file:.git/config	user.override=local
+	file:.git/config	include.path=../include/relative.include
+	file:.git/../include/relative.include	user.relative=include
+	command line:	user.cmdline=true
 	EOF
 	git -c user.cmdline=true config --list --show-origin >output &&
 	test_cmp expect output
@@ -1663,16 +1663,16 @@ test_expect_success '--show-origin with --list' '
 
 test_expect_success '--show-origin with --list --null' '
 	cat >expect <<-EOF &&
-		file:$HOME/.gitconfigQuser.global
-		trueQfile:$HOME/.gitconfigQuser.override
-		globalQfile:$HOME/.gitconfigQinclude.path
-		$INCLUDE_DIR/absolute.includeQfile:$INCLUDE_DIR/absolute.includeQuser.absolute
-		includeQfile:.git/configQuser.local
-		trueQfile:.git/configQuser.override
-		localQfile:.git/configQinclude.path
-		../include/relative.includeQfile:.git/../include/relative.includeQuser.relative
-		includeQcommand line:Quser.cmdline
-		trueQ
+	file:$HOME/.gitconfigQuser.global
+	trueQfile:$HOME/.gitconfigQuser.override
+	globalQfile:$HOME/.gitconfigQinclude.path
+	$INCLUDE_DIR/absolute.includeQfile:$INCLUDE_DIR/absolute.includeQuser.absolute
+	includeQfile:.git/configQuser.local
+	trueQfile:.git/configQuser.override
+	localQfile:.git/configQinclude.path
+	../include/relative.includeQfile:.git/../include/relative.includeQuser.relative
+	includeQcommand line:Quser.cmdline
+	trueQ
 	EOF
 	git -c user.cmdline=true config --null --list --show-origin >output.raw &&
 	nul_to_q <output.raw >output &&
@@ -1684,9 +1684,9 @@ test_expect_success '--show-origin with --list --null' '
 
 test_expect_success '--show-origin with single file' '
 	cat >expect <<-\EOF &&
-		file:.git/config	user.local=true
-		file:.git/config	user.override=local
-		file:.git/config	include.path=../include/relative.include
+	file:.git/config	user.local=true
+	file:.git/config	user.override=local
+	file:.git/config	include.path=../include/relative.include
 	EOF
 	git config --local --list --show-origin >output &&
 	test_cmp expect output
@@ -1694,8 +1694,8 @@ test_expect_success '--show-origin with single file' '
 
 test_expect_success '--show-origin with --get-regexp' '
 	cat >expect <<-EOF &&
-		file:$HOME/.gitconfig	user.global true
-		file:.git/config	user.local true
+	file:$HOME/.gitconfig	user.global true
+	file:.git/config	user.local true
 	EOF
 	git config --show-origin --get-regexp "user\.[g|l].*" >output &&
 	test_cmp expect output
@@ -1703,7 +1703,7 @@ test_expect_success '--show-origin with --get-regexp' '
 
 test_expect_success '--show-origin getting a single key' '
 	cat >expect <<-\EOF &&
-		file:.git/config	local
+	file:.git/config	local
 	EOF
 	git config --show-origin user.override >output &&
 	test_cmp expect output
@@ -1712,14 +1712,14 @@ test_expect_success '--show-origin getting a single key' '
 test_expect_success 'set up custom config file' '
 	CUSTOM_CONFIG_FILE="file\" (dq) and spaces.conf" &&
 	cat >"$CUSTOM_CONFIG_FILE" <<-\EOF
-		[user]
-			custom = true
+	[user]
+		custom = true
 	EOF
 '
 
 test_expect_success !MINGW '--show-origin escape special file name characters' '
 	cat >expect <<-\EOF &&
-		file:"file\" (dq) and spaces.conf"	user.custom=true
+	file:"file\" (dq) and spaces.conf"	user.custom=true
 	EOF
 	git config --file "$CUSTOM_CONFIG_FILE" --show-origin --list >output &&
 	test_cmp expect output
@@ -1727,7 +1727,7 @@ test_expect_success !MINGW '--show-origin escape special file name characters' '
 
 test_expect_success '--show-origin stdin' '
 	cat >expect <<-\EOF &&
-		standard input:	user.custom=true
+	standard input:	user.custom=true
 	EOF
 	git config --file - --show-origin --list <"$CUSTOM_CONFIG_FILE" >output &&
 	test_cmp expect output
@@ -1735,11 +1735,11 @@ test_expect_success '--show-origin stdin' '
 
 test_expect_success '--show-origin stdin with file include' '
 	cat >"$INCLUDE_DIR"/stdin.include <<-EOF &&
-		[user]
-			stdin = include
+	[user]
+		stdin = include
 	EOF
 	cat >expect <<-EOF &&
-		file:$INCLUDE_DIR/stdin.include	include
+	file:$INCLUDE_DIR/stdin.include	include
 	EOF
 	echo "[include]path=\"$INCLUDE_DIR\"/stdin.include" |
 	git config --show-origin --includes --file - user.stdin >output &&
@@ -1750,7 +1750,7 @@ test_expect_success '--show-origin stdin with file include' '
 test_expect_success !MINGW '--show-origin blob' '
 	blob=$(git hash-object -w "$CUSTOM_CONFIG_FILE") &&
 	cat >expect <<-EOF &&
-		blob:$blob	user.custom=true
+	blob:$blob	user.custom=true
 	EOF
 	git config --blob=$blob --show-origin --list >output &&
 	test_cmp expect output
@@ -1758,7 +1758,7 @@ test_expect_success !MINGW '--show-origin blob' '
 
 test_expect_success !MINGW '--show-origin blob ref' '
 	cat >expect <<-\EOF &&
-		blob:"master:file\" (dq) and spaces.conf"	user.custom=true
+	blob:"master:file\" (dq) and spaces.conf"	user.custom=true
 	EOF
 	git add "$CUSTOM_CONFIG_FILE" &&
 	git commit -m "new config file" &&
-- 
gitgitgadget


^ permalink raw reply related	[relevance 6%]

* [PATCH v2 1/3] git-gui: update pot template and German translation to current source code
  2020-02-09 22:00  1% ` [PATCH v2 0/3] git gui: improve German translation Christian Stimming via GitGitGadget
@ 2020-02-09 22:00  1%   ` Christian Stimming via GitGitGadget
  2020-02-09 22:00  1%   ` [PATCH v2 3/3] git-gui: update German translation Christian Stimming via GitGitGadget
  1 sibling, 0 replies; 200+ results
From: Christian Stimming via GitGitGadget @ 2020-02-09 22:00 UTC (permalink / raw)
  To: git; +Cc: Christian Stimming, Pratyush Yadav, Christian Stimming

From: Christian Stimming <christian@cstimming.de>

No content changes so far, only the preparation for subsequent edits.

Signed-off-by: Christian Stimming <christian@cstimming.de>
---
 po/de.po       | 3520 ++++++++++++++++++++++++++----------------------
 po/git-gui.pot | 2526 ++++++++++++++++++----------------
 2 files changed, 3312 insertions(+), 2734 deletions(-)

diff --git a/po/de.po b/po/de.po
index baebff2fff..6bbcb1d469 100644
--- a/po/de.po
+++ b/po/de.po
@@ -7,41 +7,42 @@ msgid ""
 msgstr ""
 "Project-Id-Version: git-gui\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-01-26 22:22+0100\n"
+"POT-Creation-Date: 2020-02-08 22:54+0100\n"
 "PO-Revision-Date: 2010-01-26 22:25+0100\n"
 "Last-Translator: Christian Stimming <stimming@tuhh.de>\n"
 "Language-Team: German\n"
+"Language: \n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 
-#: git-gui.sh:41 git-gui.sh:793 git-gui.sh:807 git-gui.sh:820 git-gui.sh:903
-#: git-gui.sh:922
-msgid "git-gui: fatal error"
-msgstr "git-gui: Programmfehler"
-
-#: git-gui.sh:743
+#: git-gui.sh:847
 #, tcl-format
 msgid "Invalid font specified in %s:"
 msgstr "Ungültige Zeichensatz-Angabe in %s:"
 
-#: git-gui.sh:779
+#: git-gui.sh:901
 msgid "Main Font"
 msgstr "Programmschriftart"
 
-#: git-gui.sh:780
+#: git-gui.sh:902
 msgid "Diff/Console Font"
 msgstr "Vergleich-Schriftart"
 
-#: git-gui.sh:794
+#: git-gui.sh:917 git-gui.sh:931 git-gui.sh:944 git-gui.sh:1034 git-gui.sh:1053
+#: git-gui.sh:3212
+msgid "git-gui: fatal error"
+msgstr "git-gui: Programmfehler"
+
+#: git-gui.sh:918
 msgid "Cannot find git in PATH."
 msgstr "Git kann im PATH nicht gefunden werden."
 
-#: git-gui.sh:821
+#: git-gui.sh:945
 msgid "Cannot parse Git version string:"
 msgstr "Git Versionsangabe kann nicht erkannt werden:"
 
-#: git-gui.sh:839
+#: git-gui.sh:970
 #, tcl-format
 msgid ""
 "Git version cannot be determined.\n"
@@ -60,479 +61,532 @@ msgstr ""
 "\n"
 "Soll angenommen werden, »%s« sei Version 1.5.0?\n"
 
-#: git-gui.sh:1128
+#: git-gui.sh:1267
 msgid "Git directory not found:"
 msgstr "Git-Verzeichnis nicht gefunden:"
 
-#: git-gui.sh:1146
+#: git-gui.sh:1301
 msgid "Cannot move to top of working directory:"
 msgstr ""
 "Es konnte nicht in das oberste Verzeichnis der Arbeitskopie gewechselt "
 "werden:"
 
-#: git-gui.sh:1154
+#: git-gui.sh:1309
 msgid "Cannot use bare repository:"
 msgstr "Bloßes Projektarchiv kann nicht benutzt werden:"
 
-#: git-gui.sh:1162
+#: git-gui.sh:1317
 msgid "No working directory"
 msgstr "Kein Arbeitsverzeichnis"
 
-#: git-gui.sh:1334 lib/checkout_op.tcl:306
+#: git-gui.sh:1491 lib/checkout_op.tcl:306
 msgid "Refreshing file status..."
 msgstr "Dateistatus aktualisieren..."
 
-#: git-gui.sh:1390
+#: git-gui.sh:1551
 msgid "Scanning for modified files ..."
 msgstr "Nach geänderten Dateien suchen..."
 
-#: git-gui.sh:1454
+#: git-gui.sh:1629
 msgid "Calling prepare-commit-msg hook..."
-msgstr "Aufrufen der Eintragen-Vorbereiten-Kontrolle (»prepare-commit hook«)..."
+msgstr ""
+"Aufrufen der Eintragen-Vorbereiten-Kontrolle (»prepare-commit hook«)..."
 
-#: git-gui.sh:1471
+#: git-gui.sh:1646
 msgid "Commit declined by prepare-commit-msg hook."
 msgstr ""
 "Eintragen abgelehnt durch Eintragen-Vorbereiten-Kontrolle (»prepare-commit "
 "hook«)."
 
-#: git-gui.sh:1629 lib/browser.tcl:246
+#: git-gui.sh:1804 lib/browser.tcl:252
 msgid "Ready."
 msgstr "Bereit."
 
-#: git-gui.sh:1787
+#: git-gui.sh:1968
 #, tcl-format
-msgid "Displaying only %s of %s files."
-msgstr "Nur %s von %s Dateien werden angezeigt."
+msgid ""
+"Display limit (gui.maxfilesdisplayed = %s) reached, not showing all %s files."
+msgstr ""
 
-#: git-gui.sh:1913
+#: git-gui.sh:2091
 msgid "Unmodified"
 msgstr "Unverändert"
 
-#: git-gui.sh:1915
+#: git-gui.sh:2093
 msgid "Modified, not staged"
 msgstr "Verändert, nicht bereitgestellt"
 
-#: git-gui.sh:1916 git-gui.sh:1924
+#: git-gui.sh:2094 git-gui.sh:2106
 msgid "Staged for commit"
 msgstr "Bereitgestellt zum Eintragen"
 
-#: git-gui.sh:1917 git-gui.sh:1925
+#: git-gui.sh:2095 git-gui.sh:2107
 msgid "Portions staged for commit"
 msgstr "Teilweise bereitgestellt zum Eintragen"
 
-#: git-gui.sh:1918 git-gui.sh:1926
+#: git-gui.sh:2096 git-gui.sh:2108
 msgid "Staged for commit, missing"
 msgstr "Bereitgestellt zum Eintragen, fehlend"
 
-#: git-gui.sh:1920
+#: git-gui.sh:2098
 msgid "File type changed, not staged"
 msgstr "Dateityp geändert, nicht bereitgestellt"
 
-#: git-gui.sh:1921
+#: git-gui.sh:2099 git-gui.sh:2100
+#, fuzzy
+msgid "File type changed, old type staged for commit"
+msgstr "Dateityp geändert, nicht bereitgestellt"
+
+#: git-gui.sh:2101
 msgid "File type changed, staged"
 msgstr "Dateityp geändert, bereitgestellt"
 
-#: git-gui.sh:1923
+#: git-gui.sh:2102
+#, fuzzy
+msgid "File type change staged, modification not staged"
+msgstr "Dateityp geändert, nicht bereitgestellt"
+
+#: git-gui.sh:2103
+#, fuzzy
+msgid "File type change staged, file missing"
+msgstr "Dateityp geändert, bereitgestellt"
+
+#: git-gui.sh:2105
 msgid "Untracked, not staged"
 msgstr "Nicht unter Versionskontrolle, nicht bereitgestellt"
 
-#: git-gui.sh:1928
+#: git-gui.sh:2110
 msgid "Missing"
 msgstr "Fehlend"
 
-#: git-gui.sh:1929
+#: git-gui.sh:2111
 msgid "Staged for removal"
 msgstr "Bereitgestellt zum Löschen"
 
-#: git-gui.sh:1930
+#: git-gui.sh:2112
 msgid "Staged for removal, still present"
 msgstr "Bereitgestellt zum Löschen, trotzdem vorhanden"
 
-#: git-gui.sh:1932 git-gui.sh:1933 git-gui.sh:1934 git-gui.sh:1935
-#: git-gui.sh:1936 git-gui.sh:1937
+#: git-gui.sh:2114 git-gui.sh:2115 git-gui.sh:2116 git-gui.sh:2117
+#: git-gui.sh:2118 git-gui.sh:2119
 msgid "Requires merge resolution"
 msgstr "Konfliktauflösung nötig"
 
-#: git-gui.sh:1972
-msgid "Starting gitk... please wait..."
-msgstr "Gitk wird gestartet... bitte warten."
-
-#: git-gui.sh:1984
+#: git-gui.sh:2164
 msgid "Couldn't find gitk in PATH"
 msgstr "Gitk kann im PATH nicht gefunden werden."
 
-#: git-gui.sh:2043
+#: git-gui.sh:2210 git-gui.sh:2245
+#, fuzzy, tcl-format
+msgid "Starting %s... please wait..."
+msgstr "Gitk wird gestartet... bitte warten."
+
+#: git-gui.sh:2224
 msgid "Couldn't find git gui in PATH"
 msgstr "»Git gui« kann im PATH nicht gefunden werden."
 
-#: git-gui.sh:2455 lib/choose_repository.tcl:36
+#: git-gui.sh:2726 lib/choose_repository.tcl:53
 msgid "Repository"
 msgstr "Projektarchiv"
 
-#: git-gui.sh:2456
+#: git-gui.sh:2727
 msgid "Edit"
 msgstr "Bearbeiten"
 
-#: git-gui.sh:2458 lib/choose_rev.tcl:561
+#: git-gui.sh:2729 lib/choose_rev.tcl:567
 msgid "Branch"
 msgstr "Zweig"
 
-#: git-gui.sh:2461 lib/choose_rev.tcl:548
+#: git-gui.sh:2732 lib/choose_rev.tcl:554
 msgid "Commit@@noun"
 msgstr "Version"
 
-#: git-gui.sh:2464 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168
+#: git-gui.sh:2735 lib/merge.tcl:127 lib/merge.tcl:174
 msgid "Merge"
 msgstr "Zusammenführen"
 
-#: git-gui.sh:2465 lib/choose_rev.tcl:557
+#: git-gui.sh:2736 lib/choose_rev.tcl:563
 msgid "Remote"
 msgstr "Externe Archive"
 
-#: git-gui.sh:2468
+#: git-gui.sh:2739
 msgid "Tools"
 msgstr "Werkzeuge"
 
-#: git-gui.sh:2477
+#: git-gui.sh:2748
 msgid "Explore Working Copy"
 msgstr "Arbeitskopie im Dateimanager"
 
-#: git-gui.sh:2483
+#: git-gui.sh:2763
+msgid "Git Bash"
+msgstr ""
+
+#: git-gui.sh:2772
 msgid "Browse Current Branch's Files"
 msgstr "Aktuellen Zweig durchblättern"
 
-#: git-gui.sh:2487
+#: git-gui.sh:2776
 msgid "Browse Branch Files..."
 msgstr "Einen Zweig durchblättern..."
 
-#: git-gui.sh:2492
+#: git-gui.sh:2781
 msgid "Visualize Current Branch's History"
 msgstr "Aktuellen Zweig darstellen"
 
-#: git-gui.sh:2496
+#: git-gui.sh:2785
 msgid "Visualize All Branch History"
 msgstr "Alle Zweige darstellen"
 
-#: git-gui.sh:2503
+#: git-gui.sh:2792
 #, tcl-format
 msgid "Browse %s's Files"
 msgstr "Zweig »%s« durchblättern"
 
-#: git-gui.sh:2505
+#: git-gui.sh:2794
 #, tcl-format
 msgid "Visualize %s's History"
 msgstr "Historie von »%s« darstellen"
 
-#: git-gui.sh:2510 lib/database.tcl:27 lib/database.tcl:67
+#: git-gui.sh:2799 lib/database.tcl:40
 msgid "Database Statistics"
 msgstr "Datenbankstatistik"
 
-#: git-gui.sh:2513 lib/database.tcl:34
+#: git-gui.sh:2802 lib/database.tcl:33
 msgid "Compress Database"
 msgstr "Datenbank komprimieren"
 
-#: git-gui.sh:2516
+#: git-gui.sh:2805
 msgid "Verify Database"
 msgstr "Datenbank überprüfen"
 
-#: git-gui.sh:2523 git-gui.sh:2527 git-gui.sh:2531 lib/shortcut.tcl:8
-#: lib/shortcut.tcl:40 lib/shortcut.tcl:72
+#: git-gui.sh:2812 git-gui.sh:2816 git-gui.sh:2820
 msgid "Create Desktop Icon"
 msgstr "Desktop-Icon erstellen"
 
-#: git-gui.sh:2539 lib/choose_repository.tcl:183 lib/choose_repository.tcl:191
+#: git-gui.sh:2828 lib/choose_repository.tcl:209 lib/choose_repository.tcl:217
 msgid "Quit"
 msgstr "Beenden"
 
-#: git-gui.sh:2547
+#: git-gui.sh:2836
 msgid "Undo"
 msgstr "Rückgängig"
 
-#: git-gui.sh:2550
+#: git-gui.sh:2839
 msgid "Redo"
 msgstr "Wiederholen"
 
-#: git-gui.sh:2554 git-gui.sh:3109
+#: git-gui.sh:2843 git-gui.sh:3461
 msgid "Cut"
 msgstr "Ausschneiden"
 
-#: git-gui.sh:2557 git-gui.sh:3112 git-gui.sh:3186 git-gui.sh:3259
+#: git-gui.sh:2846 git-gui.sh:3464 git-gui.sh:3540 git-gui.sh:3633
 #: lib/console.tcl:69
 msgid "Copy"
 msgstr "Kopieren"
 
-#: git-gui.sh:2560 git-gui.sh:3115
+#: git-gui.sh:2849 git-gui.sh:3467
 msgid "Paste"
 msgstr "Einfügen"
 
-#: git-gui.sh:2563 git-gui.sh:3118 lib/branch_delete.tcl:26
-#: lib/remote_branch_delete.tcl:38
+#: git-gui.sh:2852 git-gui.sh:3470 lib/remote_branch_delete.tcl:39
+#: lib/branch_delete.tcl:28
 msgid "Delete"
 msgstr "Löschen"
 
-#: git-gui.sh:2567 git-gui.sh:3122 git-gui.sh:3263 lib/console.tcl:71
+#: git-gui.sh:2856 git-gui.sh:3474 git-gui.sh:3637 lib/console.tcl:71
 msgid "Select All"
 msgstr "Alle auswählen"
 
-#: git-gui.sh:2576
+#: git-gui.sh:2865
 msgid "Create..."
 msgstr "Erstellen..."
 
-#: git-gui.sh:2582
+#: git-gui.sh:2871
 msgid "Checkout..."
 msgstr "Umstellen..."
 
-#: git-gui.sh:2588
+#: git-gui.sh:2877
 msgid "Rename..."
 msgstr "Umbenennen..."
 
-#: git-gui.sh:2593
+#: git-gui.sh:2882
 msgid "Delete..."
 msgstr "Löschen..."
 
-#: git-gui.sh:2598
+#: git-gui.sh:2887
 msgid "Reset..."
 msgstr "Zurücksetzen..."
 
-#: git-gui.sh:2608
+#: git-gui.sh:2897
 msgid "Done"
 msgstr "Fertig"
 
-#: git-gui.sh:2610
+#: git-gui.sh:2899
 msgid "Commit@@verb"
 msgstr "Eintragen"
 
-#: git-gui.sh:2619 git-gui.sh:3050
-msgid "New Commit"
-msgstr "Neue Version"
-
-#: git-gui.sh:2627 git-gui.sh:3057
+#: git-gui.sh:2908 git-gui.sh:3400
 msgid "Amend Last Commit"
 msgstr "Letzte nachbessern"
 
-#: git-gui.sh:2637 git-gui.sh:3011 lib/remote_branch_delete.tcl:99
+#: git-gui.sh:2918 git-gui.sh:3361 lib/remote_branch_delete.tcl:101
 msgid "Rescan"
 msgstr "Neu laden"
 
-#: git-gui.sh:2643
+#: git-gui.sh:2924
 msgid "Stage To Commit"
 msgstr "Zum Eintragen bereitstellen"
 
-#: git-gui.sh:2649
+#: git-gui.sh:2930
 msgid "Stage Changed Files To Commit"
 msgstr "Geänderte Dateien bereitstellen"
 
-#: git-gui.sh:2655
+#: git-gui.sh:2936
 msgid "Unstage From Commit"
 msgstr "Aus der Bereitstellung herausnehmen"
 
-#: git-gui.sh:2661 lib/index.tcl:412
+#: git-gui.sh:2942 lib/index.tcl:521
 msgid "Revert Changes"
 msgstr "Änderungen verwerfen"
 
-#: git-gui.sh:2669 git-gui.sh:3310 git-gui.sh:3341
+#: git-gui.sh:2950 git-gui.sh:3700 git-gui.sh:3731
 msgid "Show Less Context"
 msgstr "Weniger Zeilen anzeigen"
 
-#: git-gui.sh:2673 git-gui.sh:3314 git-gui.sh:3345
+#: git-gui.sh:2954 git-gui.sh:3704 git-gui.sh:3735
 msgid "Show More Context"
 msgstr "Mehr Zeilen anzeigen"
 
-#: git-gui.sh:2680 git-gui.sh:3024 git-gui.sh:3133
+#: git-gui.sh:2961 git-gui.sh:3374 git-gui.sh:3485
 msgid "Sign Off"
 msgstr "Abzeichnen"
 
-#: git-gui.sh:2696
+#: git-gui.sh:2977
 msgid "Local Merge..."
 msgstr "Lokales Zusammenführen..."
 
-#: git-gui.sh:2701
+#: git-gui.sh:2982
 msgid "Abort Merge..."
 msgstr "Zusammenführen abbrechen..."
 
-#: git-gui.sh:2713 git-gui.sh:2741
+#: git-gui.sh:2994 git-gui.sh:3022
 msgid "Add..."
 msgstr "Hinzufügen..."
 
-#: git-gui.sh:2717
+#: git-gui.sh:2998
 msgid "Push..."
 msgstr "Versenden..."
 
-#: git-gui.sh:2721
+#: git-gui.sh:3002
 msgid "Delete Branch..."
 msgstr "Zweig löschen..."
 
-#: git-gui.sh:2731 git-gui.sh:3292
+#: git-gui.sh:3012 git-gui.sh:3666
 msgid "Options..."
 msgstr "Optionen..."
 
-#: git-gui.sh:2742
+#: git-gui.sh:3023
 msgid "Remove..."
 msgstr "Entfernen..."
 
-#: git-gui.sh:2751 lib/choose_repository.tcl:50
+#: git-gui.sh:3032 lib/choose_repository.tcl:67
 msgid "Help"
 msgstr "Hilfe"
 
-#: git-gui.sh:2755 git-gui.sh:2759 lib/about.tcl:14
-#: lib/choose_repository.tcl:44 lib/choose_repository.tcl:53
+#: git-gui.sh:3036 git-gui.sh:3040 lib/choose_repository.tcl:61
+#: lib/choose_repository.tcl:70 lib/about.tcl:14
 #, tcl-format
 msgid "About %s"
 msgstr "Über %s"
 
-#: git-gui.sh:2783
+#: git-gui.sh:3064
 msgid "Online Documentation"
 msgstr "Online-Dokumentation"
 
-#: git-gui.sh:2786 lib/choose_repository.tcl:47 lib/choose_repository.tcl:56
+#: git-gui.sh:3067 lib/choose_repository.tcl:64 lib/choose_repository.tcl:73
 msgid "Show SSH Key"
 msgstr "SSH-Schlüssel anzeigen"
 
-#: git-gui.sh:2893
+#: git-gui.sh:3097 git-gui.sh:3229
+msgid "usage:"
+msgstr ""
+
+#: git-gui.sh:3101 git-gui.sh:3233
+msgid "Usage"
+msgstr ""
+
+#: git-gui.sh:3182 lib/blame.tcl:575
+#, fuzzy
+msgid "Error"
+msgstr "Fehler"
+
+#: git-gui.sh:3213
 #, tcl-format
 msgid "fatal: cannot stat path %s: No such file or directory"
 msgstr ""
 "Fehler: Verzeichnis »%s« kann nicht gelesen werden: Datei oder Verzeichnis "
 "nicht gefunden"
 
-#: git-gui.sh:2926
+#: git-gui.sh:3246
 msgid "Current Branch:"
 msgstr "Aktueller Zweig:"
 
-#: git-gui.sh:2947
-msgid "Staged Changes (Will Commit)"
-msgstr "Bereitstellung (zum Eintragen)"
-
-#: git-gui.sh:2967
+#: git-gui.sh:3271
 msgid "Unstaged Changes"
 msgstr "Nicht bereitgestellte Änderungen"
 
-#: git-gui.sh:3017
+#: git-gui.sh:3293
+msgid "Staged Changes (Will Commit)"
+msgstr "Bereitstellung (zum Eintragen)"
+
+#: git-gui.sh:3367
 msgid "Stage Changed"
 msgstr "Alles bereitstellen"
 
-#: git-gui.sh:3036 lib/transport.tcl:104 lib/transport.tcl:193
+#: git-gui.sh:3386 lib/transport.tcl:137
 msgid "Push"
 msgstr "Versenden"
 
-#: git-gui.sh:3071
+#: git-gui.sh:3413
 msgid "Initial Commit Message:"
 msgstr "Erste Versionsbeschreibung:"
 
-#: git-gui.sh:3072
+#: git-gui.sh:3414
 msgid "Amended Commit Message:"
 msgstr "Nachgebesserte Beschreibung:"
 
-#: git-gui.sh:3073
+#: git-gui.sh:3415
 msgid "Amended Initial Commit Message:"
 msgstr "Nachgebesserte erste Beschreibung:"
 
-#: git-gui.sh:3074
+#: git-gui.sh:3416
 msgid "Amended Merge Commit Message:"
 msgstr "Nachgebesserte Zusammenführungs-Beschreibung:"
 
-#: git-gui.sh:3075
+#: git-gui.sh:3417
 msgid "Merge Commit Message:"
 msgstr "Zusammenführungs-Beschreibung:"
 
-#: git-gui.sh:3076
+#: git-gui.sh:3418
 msgid "Commit Message:"
 msgstr "Versionsbeschreibung:"
 
-#: git-gui.sh:3125 git-gui.sh:3267 lib/console.tcl:73
+#: git-gui.sh:3477 git-gui.sh:3641 lib/console.tcl:73
 msgid "Copy All"
 msgstr "Alle kopieren"
 
-#: git-gui.sh:3149 lib/blame.tcl:104
+#: git-gui.sh:3501 lib/blame.tcl:106
 msgid "File:"
 msgstr "Datei:"
 
-#: git-gui.sh:3255
+#: git-gui.sh:3549 lib/choose_repository.tcl:1100
+msgid "Open"
+msgstr "Öffnen"
+
+#: git-gui.sh:3629
 msgid "Refresh"
 msgstr "Aktualisieren"
 
-#: git-gui.sh:3276
+#: git-gui.sh:3650
 msgid "Decrease Font Size"
 msgstr "Schriftgröße verkleinern"
 
-#: git-gui.sh:3280
+#: git-gui.sh:3654
 msgid "Increase Font Size"
 msgstr "Schriftgröße vergrößern"
 
-#: git-gui.sh:3288 lib/blame.tcl:281
+#: git-gui.sh:3662 lib/blame.tcl:296
 msgid "Encoding"
 msgstr "Zeichenkodierung"
 
-#: git-gui.sh:3299
+#: git-gui.sh:3673
 msgid "Apply/Reverse Hunk"
 msgstr "Kontext anwenden/umkehren"
 
-#: git-gui.sh:3304
+#: git-gui.sh:3678
 msgid "Apply/Reverse Line"
 msgstr "Zeile anwenden/umkehren"
 
-#: git-gui.sh:3323
+#: git-gui.sh:3684 git-gui.sh:3794 git-gui.sh:3805
+#, fuzzy
+msgid "Revert Hunk"
+msgstr "Kontext anwenden/umkehren"
+
+#: git-gui.sh:3689 git-gui.sh:3801 git-gui.sh:3812
+#, fuzzy
+msgid "Revert Line"
+msgstr "Änderungen verwerfen"
+
+#: git-gui.sh:3694 git-gui.sh:3791
+msgid "Undo Last Revert"
+msgstr ""
+
+#: git-gui.sh:3713
 msgid "Run Merge Tool"
 msgstr "Zusammenführungswerkzeug"
 
-#: git-gui.sh:3328
+#: git-gui.sh:3718
 msgid "Use Remote Version"
 msgstr "Externe Version benutzen"
 
-#: git-gui.sh:3332
+#: git-gui.sh:3722
 msgid "Use Local Version"
 msgstr "Lokale Version benutzen"
 
-#: git-gui.sh:3336
+#: git-gui.sh:3726
 msgid "Revert To Base"
 msgstr "Ursprüngliche Version benutzen"
 
-#: git-gui.sh:3354
+#: git-gui.sh:3744
 msgid "Visualize These Changes In The Submodule"
 msgstr "Diese Änderungen im Untermodul darstellen"
 
-#: git-gui.sh:3358
+#: git-gui.sh:3748
 msgid "Visualize Current Branch History In The Submodule"
 msgstr "Aktuellen Zweig im Untermodul darstellen"
 
-#: git-gui.sh:3362
+#: git-gui.sh:3752
 msgid "Visualize All Branch History In The Submodule"
 msgstr "Alle Zweige im Untermodul darstellen"
 
-#: git-gui.sh:3367
+#: git-gui.sh:3757
 msgid "Start git gui In The Submodule"
 msgstr "Git gui im Untermodul starten"
 
-#: git-gui.sh:3389
+#: git-gui.sh:3793
 msgid "Unstage Hunk From Commit"
 msgstr "Kontext aus Bereitstellung herausnehmen"
 
-#: git-gui.sh:3391
+#: git-gui.sh:3797
 msgid "Unstage Lines From Commit"
 msgstr "Zeilen aus der Bereitstellung herausnehmen"
 
-#: git-gui.sh:3393
+#: git-gui.sh:3798 git-gui.sh:3809
+#, fuzzy
+msgid "Revert Lines"
+msgstr "Änderungen verwerfen"
+
+#: git-gui.sh:3800
 msgid "Unstage Line From Commit"
 msgstr "Zeile aus der Bereitstellung herausnehmen"
 
-#: git-gui.sh:3396
+#: git-gui.sh:3804
 msgid "Stage Hunk For Commit"
 msgstr "Kontext zur Bereitstellung hinzufügen"
 
-#: git-gui.sh:3398
+#: git-gui.sh:3808
 msgid "Stage Lines For Commit"
 msgstr "Zeilen zur Bereitstellung hinzufügen"
 
-#: git-gui.sh:3400
+#: git-gui.sh:3811
 msgid "Stage Line For Commit"
 msgstr "Zeile zur Bereitstellung hinzufügen"
 
-#: git-gui.sh:3424
+#: git-gui.sh:3861
 msgid "Initializing..."
 msgstr "Initialisieren..."
 
-#: git-gui.sh:3541
+#: git-gui.sh:4017
 #, tcl-format
 msgid ""
 "Possible environment issues exist.\n"
@@ -548,7 +602,7 @@ msgstr ""
 "von %s an Git weitergegeben werden:\n"
 "\n"
 
-#: git-gui.sh:3570
+#: git-gui.sh:4046
 msgid ""
 "\n"
 "This is due to a known issue with the\n"
@@ -558,7 +612,7 @@ msgstr ""
 "Dies ist ein bekanntes Problem der Tcl-Version, die\n"
 "in Cygwin mitgeliefert wird."
 
-#: git-gui.sh:3575
+#: git-gui.sh:4051
 #, tcl-format
 msgid ""
 "\n"
@@ -574,341 +628,183 @@ msgstr ""
 "gewünschten Werte für die Einstellung user.name und \n"
 "user.email in Ihre Datei ~/.gitconfig einfügen.\n"
 
-#: lib/about.tcl:26
-msgid "git-gui - a graphical user interface for Git."
-msgstr "git-gui - eine grafische Oberfläche für Git."
-
-#: lib/blame.tcl:72
-msgid "File Viewer"
-msgstr "Datei-Browser"
-
-#: lib/blame.tcl:78
-msgid "Commit:"
-msgstr "Version:"
-
-#: lib/blame.tcl:271
-msgid "Copy Commit"
-msgstr "Version kopieren"
-
-#: lib/blame.tcl:275
-msgid "Find Text..."
-msgstr "Text suchen..."
-
-#: lib/blame.tcl:284
-msgid "Do Full Copy Detection"
-msgstr "Volle Kopie-Erkennung"
+#: lib/spellcheck.tcl:57
+msgid "Unsupported spell checker"
+msgstr "Rechtschreibprüfungsprogramm nicht unterstützt"
 
-#: lib/blame.tcl:288
-msgid "Show History Context"
-msgstr "Historien-Kontext anzeigen"
+#: lib/spellcheck.tcl:65
+msgid "Spell checking is unavailable"
+msgstr "Rechtschreibprüfung nicht verfügbar"
 
-#: lib/blame.tcl:291
-msgid "Blame Parent Commit"
-msgstr "Elternversion annotieren"
+#: lib/spellcheck.tcl:68
+msgid "Invalid spell checking configuration"
+msgstr "Unbenutzbare Konfiguration der Rechtschreibprüfung"
 
-#: lib/blame.tcl:450
+#: lib/spellcheck.tcl:70
 #, tcl-format
-msgid "Reading %s..."
-msgstr "%s lesen..."
-
-#: lib/blame.tcl:557
-msgid "Loading copy/move tracking annotations..."
-msgstr "Annotierungen für Kopieren/Verschieben werden geladen..."
-
-#: lib/blame.tcl:577
-msgid "lines annotated"
-msgstr "Zeilen annotiert"
+msgid "Reverting dictionary to %s."
+msgstr "Wörterbuch auf %s zurückgesetzt."
 
-#: lib/blame.tcl:769
-msgid "Loading original location annotations..."
-msgstr "Annotierungen für ursprünglichen Ort werden geladen..."
+#: lib/spellcheck.tcl:73
+msgid "Spell checker silently failed on startup"
+msgstr "Rechtschreibprüfungsprogramm mit Fehler abgebrochen"
 
-#: lib/blame.tcl:772
-msgid "Annotation complete."
-msgstr "Annotierung vollständig."
+#: lib/spellcheck.tcl:80
+msgid "Unrecognized spell checker"
+msgstr "Unbekanntes Rechtschreibprüfungsprogramm"
 
-#: lib/blame.tcl:802
-msgid "Busy"
-msgstr "Verarbeitung läuft"
+#: lib/spellcheck.tcl:186
+msgid "No Suggestions"
+msgstr "Keine Vorschläge"
 
-#: lib/blame.tcl:803
-msgid "Annotation process is already running."
-msgstr "Annotierung läuft bereits."
+#: lib/spellcheck.tcl:388
+msgid "Unexpected EOF from spell checker"
+msgstr "Unerwartetes EOF vom Rechtschreibprüfungsprogramm"
 
-#: lib/blame.tcl:842
-msgid "Running thorough copy detection..."
-msgstr "Intensive Kopie-Erkennung läuft..."
+#: lib/spellcheck.tcl:392
+msgid "Spell Checker Failed"
+msgstr "Rechtschreibprüfung fehlgeschlagen"
 
-#: lib/blame.tcl:910
-msgid "Loading annotation..."
-msgstr "Annotierung laden..."
+#: lib/transport.tcl:6 lib/remote_add.tcl:132
+#, tcl-format
+msgid "fetch %s"
+msgstr "»%s« anfordern"
 
-#: lib/blame.tcl:963
-msgid "Author:"
-msgstr "Autor:"
+#: lib/transport.tcl:7
+#, tcl-format
+msgid "Fetching new changes from %s"
+msgstr "Neue Änderungen von »%s« holen"
 
-#: lib/blame.tcl:967
-msgid "Committer:"
-msgstr "Eintragender:"
+#: lib/transport.tcl:18
+#, tcl-format
+msgid "remote prune %s"
+msgstr "Aufräumen von »%s«"
 
-#: lib/blame.tcl:972
-msgid "Original File:"
-msgstr "Ursprüngliche Datei:"
+#: lib/transport.tcl:19
+#, tcl-format
+msgid "Pruning tracking branches deleted from %s"
+msgstr "Übernahmezweige aufräumen und entfernen, die in »%s« gelöscht wurden"
 
-#: lib/blame.tcl:1020
-msgid "Cannot find HEAD commit:"
-msgstr "Zweigspitze (»HEAD«) kann nicht gefunden werden:"
+#: lib/transport.tcl:25
+msgid "fetch all remotes"
+msgstr ""
 
-#: lib/blame.tcl:1075
-msgid "Cannot find parent commit:"
-msgstr "Elternversion kann nicht gefunden werden:"
+#: lib/transport.tcl:26
+#, fuzzy
+msgid "Fetching new changes from all remotes"
+msgstr "Neue Änderungen von »%s« holen"
 
-#: lib/blame.tcl:1090
-msgid "Unable to display parent"
-msgstr "Elternversion kann nicht angezeigt werden"
+#: lib/transport.tcl:40
+#, fuzzy
+msgid "remote prune all remotes"
+msgstr "Aufräumen von »%s«"
 
-#: lib/blame.tcl:1091 lib/diff.tcl:320
-msgid "Error loading diff:"
-msgstr "Fehler beim Laden des Vergleichs:"
+#: lib/transport.tcl:41
+#, fuzzy
+msgid "Pruning tracking branches deleted from all remotes"
+msgstr "Übernahmezweige aufräumen und entfernen, die in »%s« gelöscht wurden"
 
-#: lib/blame.tcl:1231
-msgid "Originally By:"
-msgstr "Ursprünglich von:"
+#: lib/transport.tcl:54 lib/transport.tcl:92 lib/transport.tcl:110
+#: lib/remote_add.tcl:162
+#, tcl-format
+msgid "push %s"
+msgstr "»%s« versenden..."
 
-#: lib/blame.tcl:1237
-msgid "In File:"
-msgstr "In Datei:"
+#: lib/transport.tcl:55
+#, tcl-format
+msgid "Pushing changes to %s"
+msgstr "Änderungen nach »%s« versenden"
 
-#: lib/blame.tcl:1242
-msgid "Copied Or Moved Here By:"
-msgstr "Kopiert oder verschoben durch:"
+#: lib/transport.tcl:93
+#, tcl-format
+msgid "Mirroring to %s"
+msgstr "Spiegeln nach %s"
 
-#: lib/branch_checkout.tcl:14 lib/branch_checkout.tcl:19
-msgid "Checkout Branch"
-msgstr "Auf Zweig umstellen"
+#: lib/transport.tcl:111
+#, tcl-format
+msgid "Pushing %s %s to %s"
+msgstr "%s %s nach %s versenden"
 
-#: lib/branch_checkout.tcl:23
-msgid "Checkout"
-msgstr "Umstellen"
+#: lib/transport.tcl:132
+msgid "Push Branches"
+msgstr "Zweige versenden"
 
-#: lib/branch_checkout.tcl:27 lib/branch_create.tcl:35
-#: lib/branch_delete.tcl:32 lib/branch_rename.tcl:30 lib/browser.tcl:282
-#: lib/checkout_op.tcl:579 lib/choose_font.tcl:43 lib/merge.tcl:172
-#: lib/option.tcl:125 lib/remote_add.tcl:32 lib/remote_branch_delete.tcl:42
-#: lib/tools_dlg.tcl:40 lib/tools_dlg.tcl:204 lib/tools_dlg.tcl:352
-#: lib/transport.tcl:108
+#: lib/transport.tcl:141 lib/checkout_op.tcl:580 lib/remote_add.tcl:34
+#: lib/browser.tcl:292 lib/branch_checkout.tcl:30 lib/branch_rename.tcl:32
+#: lib/choose_font.tcl:45 lib/option.tcl:127 lib/tools_dlg.tcl:41
+#: lib/tools_dlg.tcl:202 lib/tools_dlg.tcl:345 lib/remote_branch_delete.tcl:43
+#: lib/branch_create.tcl:37 lib/branch_delete.tcl:34 lib/merge.tcl:178
 msgid "Cancel"
 msgstr "Abbrechen"
 
-#: lib/branch_checkout.tcl:32 lib/browser.tcl:287 lib/tools_dlg.tcl:328
-msgid "Revision"
-msgstr "Version"
-
-#: lib/branch_checkout.tcl:36 lib/branch_create.tcl:69 lib/option.tcl:280
-msgid "Options"
-msgstr "Optionen"
-
-#: lib/branch_checkout.tcl:39 lib/branch_create.tcl:92
-msgid "Fetch Tracking Branch"
-msgstr "Übernahmezweig anfordern"
-
-#: lib/branch_checkout.tcl:44
-msgid "Detach From Local Branch"
-msgstr "Verbindung zu lokalem Zweig lösen"
-
-#: lib/branch_create.tcl:22
-msgid "Create Branch"
-msgstr "Zweig erstellen"
-
-#: lib/branch_create.tcl:27
-msgid "Create New Branch"
-msgstr "Neuen Zweig erstellen"
+#: lib/transport.tcl:147
+msgid "Source Branches"
+msgstr "Lokale Zweige"
 
-#: lib/branch_create.tcl:31 lib/choose_repository.tcl:381
-msgid "Create"
-msgstr "Erstellen"
+#: lib/transport.tcl:162
+msgid "Destination Repository"
+msgstr "Ziel-Projektarchiv"
 
-#: lib/branch_create.tcl:40
-msgid "Branch Name"
-msgstr "Zweigname"
+#: lib/transport.tcl:165 lib/remote_branch_delete.tcl:51
+msgid "Remote:"
+msgstr "Externes Archiv:"
 
-#: lib/branch_create.tcl:43 lib/remote_add.tcl:39 lib/tools_dlg.tcl:50
-msgid "Name:"
-msgstr "Name:"
+#: lib/transport.tcl:187 lib/remote_branch_delete.tcl:72
+msgid "Arbitrary Location:"
+msgstr "Adresse:"
 
-#: lib/branch_create.tcl:58
-msgid "Match Tracking Branch Name"
-msgstr "Passend zu Übernahmezweig-Name"
+#: lib/transport.tcl:205
+msgid "Transfer Options"
+msgstr "Netzwerk-Einstellungen"
 
-#: lib/branch_create.tcl:66
-msgid "Starting Revision"
-msgstr "Anfangsversion"
+#: lib/transport.tcl:207
+msgid "Force overwrite existing branch (may discard changes)"
+msgstr ""
+"Überschreiben von existierenden Zweigen erzwingen (könnte Änderungen löschen)"
 
-#: lib/branch_create.tcl:72
-msgid "Update Existing Branch:"
-msgstr "Existierenden Zweig aktualisieren:"
+#: lib/transport.tcl:211
+msgid "Use thin pack (for slow network connections)"
+msgstr "Kompaktes Datenformat benutzen (für langsame Netzverbindungen)"
 
-#: lib/branch_create.tcl:75
-msgid "No"
-msgstr "Nein"
+#: lib/transport.tcl:215
+msgid "Include tags"
+msgstr "Mit Markierungen übertragen"
 
-#: lib/branch_create.tcl:80
-msgid "Fast Forward Only"
-msgstr "Nur Schnellzusammenführung"
+#: lib/transport.tcl:229
+#, tcl-format
+msgid "%s (%s): Push"
+msgstr ""
 
-#: lib/branch_create.tcl:85 lib/checkout_op.tcl:571
-msgid "Reset"
-msgstr "Zurücksetzen"
+#: lib/checkout_op.tcl:85
+#, tcl-format
+msgid "Fetching %s from %s"
+msgstr "Änderungen »%s« von »%s« anfordern"
 
-#: lib/branch_create.tcl:97
-msgid "Checkout After Creation"
-msgstr "Arbeitskopie umstellen nach Erstellen"
+#: lib/checkout_op.tcl:133
+#, tcl-format
+msgid "fatal: Cannot resolve %s"
+msgstr "Fehler: »%s« kann nicht als Zweig oder Version erkannt werden"
 
-#: lib/branch_create.tcl:131
-msgid "Please select a tracking branch."
-msgstr "Bitte wählen Sie einen Übernahmezweig."
+#: lib/checkout_op.tcl:146 lib/sshkey.tcl:58 lib/console.tcl:81
+#: lib/database.tcl:30
+msgid "Close"
+msgstr "Schließen"
 
-#: lib/branch_create.tcl:140
+#: lib/checkout_op.tcl:175
 #, tcl-format
-msgid "Tracking branch %s is not a branch in the remote repository."
-msgstr "Übernahmezweig »%s« ist kein Zweig im externen Projektarchiv."
+msgid "Branch '%s' does not exist."
+msgstr "Zweig »%s« existiert nicht."
 
-#: lib/branch_create.tcl:153 lib/branch_rename.tcl:86
-msgid "Please supply a branch name."
-msgstr "Bitte geben Sie einen Zweignamen an."
+#: lib/checkout_op.tcl:194
+#, tcl-format
+msgid "Failed to configure simplified git-pull for '%s'."
+msgstr "Fehler beim Einrichten der vereinfachten git-pull für »%s«."
 
-#: lib/branch_create.tcl:164 lib/branch_rename.tcl:106
-#, tcl-format
-msgid "'%s' is not an acceptable branch name."
-msgstr "»%s« ist kein zulässiger Zweigname."
-
-#: lib/branch_delete.tcl:15
-msgid "Delete Branch"
-msgstr "Zweig löschen"
-
-#: lib/branch_delete.tcl:20
-msgid "Delete Local Branch"
-msgstr "Lokalen Zweig löschen"
-
-#: lib/branch_delete.tcl:37
-msgid "Local Branches"
-msgstr "Lokale Zweige"
-
-#: lib/branch_delete.tcl:52
-msgid "Delete Only If Merged Into"
-msgstr "Nur löschen, wenn zusammengeführt nach"
-
-#: lib/branch_delete.tcl:54 lib/remote_branch_delete.tcl:119
-msgid "Always (Do not perform merge checks)"
-msgstr "Immer (Keine Zusammenführungsprüfung)"
-
-#: lib/branch_delete.tcl:103
-#, tcl-format
-msgid "The following branches are not completely merged into %s:"
-msgstr "Folgende Zweige sind noch nicht mit »%s« zusammengeführt:"
-
-#: lib/branch_delete.tcl:115 lib/remote_branch_delete.tcl:217
-msgid ""
-"Recovering deleted branches is difficult.\n"
-"\n"
-"Delete the selected branches?"
-msgstr ""
-"Das Wiederherstellen von gelöschten Zweigen ist nur mit größerem Aufwand "
-"möglich.\n"
-"\n"
-"Sollen die ausgewählten Zweige gelöscht werden?"
-
-#: lib/branch_delete.tcl:141
-#, tcl-format
-msgid ""
-"Failed to delete branches:\n"
-"%s"
-msgstr ""
-"Fehler beim Löschen der Zweige:\n"
-"%s"
-
-#: lib/branch_rename.tcl:14 lib/branch_rename.tcl:22
-msgid "Rename Branch"
-msgstr "Zweig umbenennen"
-
-#: lib/branch_rename.tcl:26
-msgid "Rename"
-msgstr "Umbenennen"
-
-#: lib/branch_rename.tcl:36
-msgid "Branch:"
-msgstr "Zweig:"
-
-#: lib/branch_rename.tcl:39
-msgid "New Name:"
-msgstr "Neuer Name:"
-
-#: lib/branch_rename.tcl:75
-msgid "Please select a branch to rename."
-msgstr "Bitte wählen Sie einen Zweig zum umbenennen."
-
-#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:202
+#: lib/checkout_op.tcl:202 lib/branch_rename.tcl:102
 #, tcl-format
 msgid "Branch '%s' already exists."
 msgstr "Zweig »%s« existiert bereits."
 
-#: lib/branch_rename.tcl:117
-#, tcl-format
-msgid "Failed to rename '%s'."
-msgstr "Fehler beim Umbenennen von »%s«."
-
-#: lib/browser.tcl:17
-msgid "Starting..."
-msgstr "Starten..."
-
-#: lib/browser.tcl:26
-msgid "File Browser"
-msgstr "Datei-Browser"
-
-#: lib/browser.tcl:126 lib/browser.tcl:143
-#, tcl-format
-msgid "Loading %s..."
-msgstr "%s laden..."
-
-#: lib/browser.tcl:187
-msgid "[Up To Parent]"
-msgstr "[Nach oben]"
-
-#: lib/browser.tcl:267 lib/browser.tcl:273
-msgid "Browse Branch Files"
-msgstr "Dateien des Zweigs durchblättern"
-
-#: lib/browser.tcl:278 lib/choose_repository.tcl:398
-#: lib/choose_repository.tcl:486 lib/choose_repository.tcl:497
-#: lib/choose_repository.tcl:1028
-msgid "Browse"
-msgstr "Blättern"
-
-#: lib/checkout_op.tcl:85
-#, tcl-format
-msgid "Fetching %s from %s"
-msgstr "Änderungen »%s« von »%s« anfordern"
-
-#: lib/checkout_op.tcl:133
-#, tcl-format
-msgid "fatal: Cannot resolve %s"
-msgstr "Fehler: »%s« kann nicht als Zweig oder Version erkannt werden"
-
-#: lib/checkout_op.tcl:146 lib/console.tcl:81 lib/database.tcl:31
-#: lib/sshkey.tcl:53
-msgid "Close"
-msgstr "Schließen"
-
-#: lib/checkout_op.tcl:175
-#, tcl-format
-msgid "Branch '%s' does not exist."
-msgstr "Zweig »%s« existiert nicht."
-
-#: lib/checkout_op.tcl:194
-#, tcl-format
-msgid "Failed to configure simplified git-pull for '%s'."
-msgstr "Fehler beim Einrichten der vereinfachten git-pull für »%s«."
-
 #: lib/checkout_op.tcl:229
 #, tcl-format
 msgid ""
@@ -961,23 +857,23 @@ msgstr "Arbeitskopie umstellen auf »%s«..."
 msgid "files checked out"
 msgstr "Dateien aktualisiert"
 
-#: lib/checkout_op.tcl:376
+#: lib/checkout_op.tcl:377
 #, tcl-format
 msgid "Aborted checkout of '%s' (file level merging is required)."
 msgstr ""
 "Auf Zweig »%s« umstellen abgebrochen (Zusammenführen der Dateien ist "
 "notwendig)."
 
-#: lib/checkout_op.tcl:377
+#: lib/checkout_op.tcl:378
 msgid "File level merge required."
 msgstr "Zusammenführen der Dateien ist notwendig."
 
-#: lib/checkout_op.tcl:381
+#: lib/checkout_op.tcl:382
 #, tcl-format
 msgid "Staying on branch '%s'."
 msgstr "Es wird auf Zweig »%s« verblieben."
 
-#: lib/checkout_op.tcl:452
+#: lib/checkout_op.tcl:453
 msgid ""
 "You are no longer on a local branch.\n"
 "\n"
@@ -989,32 +885,36 @@ msgstr ""
 "Wenn Sie auf einem Zweig arbeiten möchten, erstellen Sie bitte jetzt einen "
 "Zweig mit der Auswahl »Abgetrennte Arbeitskopie-Version«."
 
-#: lib/checkout_op.tcl:503 lib/checkout_op.tcl:507
+#: lib/checkout_op.tcl:504 lib/checkout_op.tcl:508
 #, tcl-format
 msgid "Checked out '%s'."
 msgstr "Umgestellt auf »%s«."
 
-#: lib/checkout_op.tcl:535
+#: lib/checkout_op.tcl:536
 #, tcl-format
 msgid "Resetting '%s' to '%s' will lose the following commits:"
 msgstr "Zurücksetzen von »%s« nach »%s« wird folgende Versionen verwerfen:"
 
-#: lib/checkout_op.tcl:557
+#: lib/checkout_op.tcl:558
 msgid "Recovering lost commits may not be easy."
 msgstr ""
 "Verworfene Versionen können nur mit größerem Aufwand wiederhergestellt "
 "werden."
 
-#: lib/checkout_op.tcl:562
+#: lib/checkout_op.tcl:563
 #, tcl-format
 msgid "Reset '%s'?"
 msgstr "»%s« zurücksetzen?"
 
-#: lib/checkout_op.tcl:567 lib/merge.tcl:164 lib/tools_dlg.tcl:343
+#: lib/checkout_op.tcl:568 lib/tools_dlg.tcl:336 lib/merge.tcl:170
 msgid "Visualize"
 msgstr "Darstellen"
 
-#: lib/checkout_op.tcl:635
+#: lib/checkout_op.tcl:572 lib/branch_create.tcl:85
+msgid "Reset"
+msgstr "Zurücksetzen"
+
+#: lib/checkout_op.tcl:636
 #, tcl-format
 msgid ""
 "Failed to set current branch.\n"
@@ -1032,658 +932,511 @@ msgstr ""
 "\n"
 "Dies ist ein interner Programmfehler von %s. Programm wird jetzt abgebrochen."
 
-#: lib/choose_font.tcl:39
-msgid "Select"
-msgstr "Auswählen"
-
-#: lib/choose_font.tcl:53
-msgid "Font Family"
-msgstr "Schriftfamilie"
-
-#: lib/choose_font.tcl:74
-msgid "Font Size"
-msgstr "Schriftgröße"
-
-#: lib/choose_font.tcl:91
-msgid "Font Example"
-msgstr "Schriftbeispiel"
+#: lib/remote_add.tcl:20
+#, fuzzy, tcl-format
+msgid "%s (%s): Add Remote"
+msgstr "Externes Archiv hinzufügen"
 
-#: lib/choose_font.tcl:103
-msgid ""
-"This is example text.\n"
-"If you like this text, it can be your font."
-msgstr ""
-"Dies ist ein Beispieltext.\n"
-"Wenn Ihnen dieser Text gefällt, sollten Sie diese Schriftart wählen."
+#: lib/remote_add.tcl:25
+msgid "Add New Remote"
+msgstr "Neues externes Archiv hinzufügen"
 
-#: lib/choose_repository.tcl:28
-msgid "Git Gui"
-msgstr "Git Gui"
+#: lib/remote_add.tcl:30 lib/tools_dlg.tcl:37
+msgid "Add"
+msgstr "Hinzufügen"
 
-#: lib/choose_repository.tcl:87 lib/choose_repository.tcl:386
-msgid "Create New Repository"
-msgstr "Neues Projektarchiv"
+#: lib/remote_add.tcl:39
+msgid "Remote Details"
+msgstr "Einzelheiten des externen Archivs"
 
-#: lib/choose_repository.tcl:93
-msgid "New..."
-msgstr "Neu..."
+#: lib/remote_add.tcl:41 lib/tools_dlg.tcl:51 lib/branch_create.tcl:44
+msgid "Name:"
+msgstr "Name:"
 
-#: lib/choose_repository.tcl:100 lib/choose_repository.tcl:471
-msgid "Clone Existing Repository"
-msgstr "Projektarchiv klonen"
+#: lib/remote_add.tcl:50
+msgid "Location:"
+msgstr "Adresse:"
 
-#: lib/choose_repository.tcl:106
-msgid "Clone..."
-msgstr "Klonen..."
+#: lib/remote_add.tcl:60
+msgid "Further Action"
+msgstr "Weitere Aktion jetzt"
 
-#: lib/choose_repository.tcl:113 lib/choose_repository.tcl:1016
-msgid "Open Existing Repository"
-msgstr "Projektarchiv öffnen"
+#: lib/remote_add.tcl:63
+msgid "Fetch Immediately"
+msgstr "Gleich anfordern"
 
-#: lib/choose_repository.tcl:119
-msgid "Open..."
-msgstr "Öffnen..."
+#: lib/remote_add.tcl:69
+msgid "Initialize Remote Repository and Push"
+msgstr "Externes Archiv initialisieren und dahin versenden"
 
-#: lib/choose_repository.tcl:132
-msgid "Recent Repositories"
-msgstr "Zuletzt benutzte Projektarchive"
+#: lib/remote_add.tcl:75
+msgid "Do Nothing Else Now"
+msgstr "Nichts tun"
 
-#: lib/choose_repository.tcl:138
-msgid "Open Recent Repository:"
-msgstr "Zuletzt benutztes Projektarchiv öffnen:"
+#: lib/remote_add.tcl:100
+msgid "Please supply a remote name."
+msgstr "Bitte geben Sie einen Namen des externen Archivs an."
 
-#: lib/choose_repository.tcl:306 lib/choose_repository.tcl:313
-#: lib/choose_repository.tcl:320
+#: lib/remote_add.tcl:113
 #, tcl-format
-msgid "Failed to create repository %s:"
-msgstr "Projektarchiv »%s« konnte nicht erstellt werden:"
+msgid "'%s' is not an acceptable remote name."
+msgstr "»%s« ist kein zulässiger Name eines externen Archivs."
 
-#: lib/choose_repository.tcl:391
-msgid "Directory:"
-msgstr "Verzeichnis:"
+#: lib/remote_add.tcl:124
+#, tcl-format
+msgid "Failed to add remote '%s' of location '%s'."
+msgstr ""
+"Fehler beim Hinzufügen des externen Archivs »%s« aus Herkunftsort »%s«."
 
-#: lib/choose_repository.tcl:423 lib/choose_repository.tcl:550
-#: lib/choose_repository.tcl:1052
-msgid "Git Repository"
-msgstr "Git Projektarchiv"
+#: lib/remote_add.tcl:133
+#, tcl-format
+msgid "Fetching the %s"
+msgstr "»%s« anfordern"
 
-#: lib/choose_repository.tcl:448
+#: lib/remote_add.tcl:156
 #, tcl-format
-msgid "Directory %s already exists."
-msgstr "Verzeichnis »%s« existiert bereits."
+msgid "Do not know how to initialize repository at location '%s'."
+msgstr ""
+"Initialisieren eines externen Archivs an Adresse »%s« ist nicht möglich."
 
-#: lib/choose_repository.tcl:452
+#: lib/remote_add.tcl:163
 #, tcl-format
-msgid "File %s already exists."
-msgstr "Datei »%s« existiert bereits."
+msgid "Setting up the %s (at %s)"
+msgstr "Einrichten von »%s« an »%s«"
 
-#: lib/choose_repository.tcl:466
-msgid "Clone"
-msgstr "Klonen"
+#: lib/browser.tcl:17
+msgid "Starting..."
+msgstr "Starten..."
 
-#: lib/choose_repository.tcl:479
-msgid "Source Location:"
-msgstr "Herkunft:"
+#: lib/browser.tcl:27
+#, fuzzy, tcl-format
+msgid "%s (%s): File Browser"
+msgstr "Datei-Browser"
 
-#: lib/choose_repository.tcl:490
-msgid "Target Directory:"
-msgstr "Zielverzeichnis:"
+#: lib/browser.tcl:132 lib/browser.tcl:149
+#, tcl-format
+msgid "Loading %s..."
+msgstr "%s laden..."
 
-#: lib/choose_repository.tcl:502
-msgid "Clone Type:"
-msgstr "Art des Klonens:"
+#: lib/browser.tcl:193
+msgid "[Up To Parent]"
+msgstr "[Nach oben]"
 
-#: lib/choose_repository.tcl:508
-msgid "Standard (Fast, Semi-Redundant, Hardlinks)"
-msgstr "Standard (schnell, teilweise redundant, Hardlinks)"
+#: lib/browser.tcl:275
+#, fuzzy, tcl-format
+msgid "%s (%s): Browse Branch Files"
+msgstr "Dateien des Zweigs durchblättern"
 
-#: lib/choose_repository.tcl:514
-msgid "Full Copy (Slower, Redundant Backup)"
-msgstr "Alles kopieren (langsamer, volle Redundanz)"
+#: lib/browser.tcl:282
+msgid "Browse Branch Files"
+msgstr "Dateien des Zweigs durchblättern"
 
-#: lib/choose_repository.tcl:520
-msgid "Shared (Fastest, Not Recommended, No Backup)"
-msgstr "Verknüpft (schnell, nicht empfohlen, kein Backup)"
+#: lib/browser.tcl:288 lib/choose_repository.tcl:437
+#: lib/choose_repository.tcl:524 lib/choose_repository.tcl:533
+#: lib/choose_repository.tcl:1115
+msgid "Browse"
+msgstr "Blättern"
 
-#: lib/choose_repository.tcl:556 lib/choose_repository.tcl:603
-#: lib/choose_repository.tcl:749 lib/choose_repository.tcl:819
-#: lib/choose_repository.tcl:1058 lib/choose_repository.tcl:1066
-#, tcl-format
-msgid "Not a Git repository: %s"
-msgstr "Kein Git-Projektarchiv in »%s« gefunden."
+#: lib/browser.tcl:297 lib/branch_checkout.tcl:35 lib/tools_dlg.tcl:321
+msgid "Revision"
+msgstr "Version"
 
-#: lib/choose_repository.tcl:592
-msgid "Standard only available for local repository."
-msgstr "Standard ist nur für lokale Projektarchive verfügbar."
+#: lib/index.tcl:6
+msgid "Unable to unlock the index."
+msgstr "Bereitstellung kann nicht wieder freigegeben werden."
 
-#: lib/choose_repository.tcl:596
-msgid "Shared only available for local repository."
-msgstr "Verknüpft ist nur für lokale Projektarchive verfügbar."
+#: lib/index.tcl:30
+msgid "Index Error"
+msgstr "Fehler in Bereitstellung"
 
-#: lib/choose_repository.tcl:617
-#, tcl-format
-msgid "Location %s already exists."
-msgstr "Projektarchiv »%s« existiert bereits."
+#: lib/index.tcl:32
+msgid ""
+"Updating the Git index failed.  A rescan will be automatically started to "
+"resynchronize git-gui."
+msgstr ""
+"Das Aktualisieren der Git-Bereitstellung ist fehlgeschlagen. Eine allgemeine "
+"Git-Aktualisierung wird jetzt gestartet, um git-gui wieder mit git zu "
+"synchronisieren."
 
-#: lib/choose_repository.tcl:628
-msgid "Failed to configure origin"
-msgstr "Der Ursprungsort konnte nicht eingerichtet werden"
+#: lib/index.tcl:43
+msgid "Continue"
+msgstr "Fortsetzen"
 
-#: lib/choose_repository.tcl:640
-msgid "Counting objects"
-msgstr "Objekte werden gezählt"
+#: lib/index.tcl:46
+msgid "Unlock Index"
+msgstr "Bereitstellung freigeben"
 
-#: lib/choose_repository.tcl:641
-msgid "buckets"
-msgstr "Buckets"
+#: lib/index.tcl:77 lib/index.tcl:146 lib/index.tcl:220 lib/index.tcl:587
+#: lib/choose_repository.tcl:999
+msgid "files"
+msgstr "Dateien"
 
-#: lib/choose_repository.tcl:665
-#, tcl-format
-msgid "Unable to copy objects/info/alternates: %s"
-msgstr "Kopien von Objekten/Info/Alternates konnten nicht erstellt werden: %s"
+#: lib/index.tcl:326
+#, fuzzy
+msgid "Unstaging selected files from commit"
+msgstr "Datei »%s« aus der Bereitstellung herausnehmen"
 
-#: lib/choose_repository.tcl:701
+#: lib/index.tcl:330
 #, tcl-format
-msgid "Nothing to clone from %s."
-msgstr "Von »%s« konnte nichts geklont werden."
+msgid "Unstaging %s from commit"
+msgstr "Datei »%s« aus der Bereitstellung herausnehmen"
 
-#: lib/choose_repository.tcl:703 lib/choose_repository.tcl:917
-#: lib/choose_repository.tcl:929
-msgid "The 'master' branch has not been initialized."
-msgstr "Der »master«-Zweig wurde noch nicht initialisiert."
+#: lib/index.tcl:369
+msgid "Ready to commit."
+msgstr "Bereit zum Eintragen."
 
-#: lib/choose_repository.tcl:716
-msgid "Hardlinks are unavailable.  Falling back to copying."
-msgstr "Hardlinks nicht verfügbar. Stattdessen wird kopiert."
+#: lib/index.tcl:378
+#, fuzzy
+msgid "Adding selected files"
+msgstr "Änderungen in gewählten Dateien verwerfen"
 
-#: lib/choose_repository.tcl:728
+#: lib/index.tcl:382
 #, tcl-format
-msgid "Cloning from %s"
-msgstr "Kopieren von »%s«"
-
-#: lib/choose_repository.tcl:759
-msgid "Copying objects"
-msgstr "Objektdatenbank kopieren"
-
-#: lib/choose_repository.tcl:760
-msgid "KiB"
-msgstr "KB"
+msgid "Adding %s"
+msgstr "»%s« hinzufügen..."
 
-#: lib/choose_repository.tcl:784
+#: lib/index.tcl:412
 #, tcl-format
-msgid "Unable to copy object: %s"
-msgstr "Objekt kann nicht kopiert werden: %s"
+msgid "Stage %d untracked files?"
+msgstr ""
 
-#: lib/choose_repository.tcl:794
-msgid "Linking objects"
-msgstr "Objekte verlinken"
+#: lib/index.tcl:420
+msgid "Adding all changed files"
+msgstr ""
 
-#: lib/choose_repository.tcl:795
-msgid "objects"
-msgstr "Objekte"
+#: lib/index.tcl:503
+#, tcl-format
+msgid "Revert changes in file %s?"
+msgstr "Änderungen in Datei »%s« verwerfen?"
 
-#: lib/choose_repository.tcl:803
+#: lib/index.tcl:508
 #, tcl-format
-msgid "Unable to hardlink object: %s"
-msgstr "Für Objekt konnte kein Hardlink erstellt werden: %s"
+msgid "Revert changes in these %i files?"
+msgstr "Änderungen in den gewählten %i Dateien verwerfen?"
 
-#: lib/choose_repository.tcl:858
-msgid "Cannot fetch branches and objects.  See console output for details."
+#: lib/index.tcl:517
+msgid "Any unstaged changes will be permanently lost by the revert."
 msgstr ""
-"Zweige und Objekte konnten nicht angefordert werden.  Kontrollieren Sie die "
-"Ausgaben auf der Konsole für weitere Angaben."
+"Alle nicht bereitgestellten Änderungen werden beim Verwerfen verloren gehen."
 
-#: lib/choose_repository.tcl:869
-msgid "Cannot fetch tags.  See console output for details."
+#: lib/index.tcl:520 lib/index.tcl:563
+msgid "Do Nothing"
+msgstr "Nichts tun"
+
+#: lib/index.tcl:545
+#, fuzzy, tcl-format
+msgid "Delete untracked file %s?"
+msgstr "Zweige auf »%s« werden gelöscht"
+
+#: lib/index.tcl:550
+#, fuzzy, tcl-format
+msgid "Delete these %i untracked files?"
+msgstr "Änderungen in den gewählten %i Dateien verwerfen?"
+
+#: lib/index.tcl:560
+msgid "Files will be permanently deleted."
 msgstr ""
-"Markierungen konnten nicht angefordert werden.  Kontrollieren Sie die "
-"Ausgaben auf der Konsole für weitere Angaben."
 
-#: lib/choose_repository.tcl:893
-msgid "Cannot determine HEAD.  See console output for details."
+#: lib/index.tcl:564
+#, fuzzy
+msgid "Delete Files"
+msgstr "Löschen"
+
+#: lib/index.tcl:586
+#, fuzzy
+msgid "Deleting"
+msgstr "Löschen"
+
+#: lib/index.tcl:665
+msgid "Encountered errors deleting files:\n"
 msgstr ""
-"Die Zweigspitze (HEAD) konnte nicht gefunden werden.  Kontrollieren Sie die "
-"Ausgaben auf der Konsole für weitere Angaben."
 
-#: lib/choose_repository.tcl:902
+#: lib/index.tcl:674
 #, tcl-format
-msgid "Unable to cleanup %s"
-msgstr "Verzeichnis »%s« kann nicht aufgeräumt werden."
+msgid "None of the %d selected files could be deleted."
+msgstr ""
 
-#: lib/choose_repository.tcl:908
-msgid "Clone failed."
-msgstr "Klonen fehlgeschlagen."
+#: lib/index.tcl:679
+#, tcl-format
+msgid "%d of the %d selected files could not be deleted."
+msgstr ""
 
-#: lib/choose_repository.tcl:915
-msgid "No default branch obtained."
-msgstr "Kein voreingestellter Zweig gefunden."
+#: lib/index.tcl:726
+msgid "Reverting selected files"
+msgstr "Änderungen in gewählten Dateien verwerfen"
 
-#: lib/choose_repository.tcl:926
+#: lib/index.tcl:730
 #, tcl-format
-msgid "Cannot resolve %s as a commit."
-msgstr "»%s« wurde nicht als Version gefunden."
+msgid "Reverting %s"
+msgstr "Änderungen in %s verwerfen"
 
-#: lib/choose_repository.tcl:938
-msgid "Creating working directory"
-msgstr "Arbeitskopie erstellen"
+#: lib/branch_checkout.tcl:16
+#, fuzzy, tcl-format
+msgid "%s (%s): Checkout Branch"
+msgstr "Auf Zweig umstellen"
 
-#: lib/choose_repository.tcl:939 lib/index.tcl:67 lib/index.tcl:130
-#: lib/index.tcl:198
-msgid "files"
-msgstr "Dateien"
+#: lib/branch_checkout.tcl:21
+msgid "Checkout Branch"
+msgstr "Auf Zweig umstellen"
 
-#: lib/choose_repository.tcl:968
-msgid "Initial file checkout failed."
-msgstr "Erstellen der Arbeitskopie fehlgeschlagen."
+#: lib/branch_checkout.tcl:26
+msgid "Checkout"
+msgstr "Umstellen"
 
-#: lib/choose_repository.tcl:1011
-msgid "Open"
-msgstr "Öffnen"
+#: lib/branch_checkout.tcl:39 lib/option.tcl:310 lib/branch_create.tcl:69
+msgid "Options"
+msgstr "Optionen"
 
-#: lib/choose_repository.tcl:1021
-msgid "Repository:"
-msgstr "Projektarchiv:"
+#: lib/branch_checkout.tcl:42 lib/branch_create.tcl:92
+msgid "Fetch Tracking Branch"
+msgstr "Übernahmezweig anfordern"
 
-#: lib/choose_repository.tcl:1072
+#: lib/branch_checkout.tcl:47
+msgid "Detach From Local Branch"
+msgstr "Verbindung zu lokalem Zweig lösen"
+
+#: lib/status_bar.tcl:263
 #, tcl-format
-msgid "Failed to open repository %s:"
-msgstr "Projektarchiv »%s« konnte nicht geöffnet werden."
+msgid "%s ... %*i of %*i %s (%3i%%)"
+msgstr "%s ... %*i von %*i %s (%3i%%)"
 
-#: lib/choose_rev.tcl:53
-msgid "This Detached Checkout"
-msgstr "Abgetrennte Arbeitskopie-Version"
+#: lib/remote.tcl:200
+msgid "Push to"
+msgstr "Versenden nach"
 
-#: lib/choose_rev.tcl:60
-msgid "Revision Expression:"
-msgstr "Version Regexp-Ausdruck:"
+#: lib/remote.tcl:218
+msgid "Remove Remote"
+msgstr "Externes Archiv entfernen"
 
-#: lib/choose_rev.tcl:74
-msgid "Local Branch"
-msgstr "Lokaler Zweig"
+#: lib/remote.tcl:223
+msgid "Prune from"
+msgstr "Aufräumen von"
 
-#: lib/choose_rev.tcl:79
-msgid "Tracking Branch"
-msgstr "Übernahmezweig"
+#: lib/remote.tcl:228
+msgid "Fetch from"
+msgstr "Anfordern von"
 
-#: lib/choose_rev.tcl:84 lib/choose_rev.tcl:538
-msgid "Tag"
-msgstr "Markierung"
+#: lib/remote.tcl:249 lib/remote.tcl:253 lib/remote.tcl:258 lib/remote.tcl:264
+msgid "All"
+msgstr ""
 
-#: lib/choose_rev.tcl:317
-#, tcl-format
-msgid "Invalid revision: %s"
-msgstr "Ungültige Version: %s"
+#: lib/branch_rename.tcl:15
+#, fuzzy, tcl-format
+msgid "%s (%s): Rename Branch"
+msgstr "Zweig umbenennen"
 
-#: lib/choose_rev.tcl:338
-msgid "No revision selected."
-msgstr "Keine Version ausgewählt."
+#: lib/branch_rename.tcl:23
+msgid "Rename Branch"
+msgstr "Zweig umbenennen"
 
-#: lib/choose_rev.tcl:346
-msgid "Revision expression is empty."
-msgstr "Versions-Ausdruck ist leer."
+#: lib/branch_rename.tcl:28
+msgid "Rename"
+msgstr "Umbenennen"
 
-#: lib/choose_rev.tcl:531
-msgid "Updated"
-msgstr "Aktualisiert"
+#: lib/branch_rename.tcl:38
+msgid "Branch:"
+msgstr "Zweig:"
 
-#: lib/choose_rev.tcl:559
-msgid "URL"
-msgstr "URL"
+#: lib/branch_rename.tcl:46
+msgid "New Name:"
+msgstr "Neuer Name:"
 
-#: lib/commit.tcl:9
-msgid ""
-"There is nothing to amend.\n"
-"\n"
-"You are about to create the initial commit.  There is no commit before this "
-"to amend.\n"
-msgstr ""
-"Keine Version zur Nachbesserung vorhanden.\n"
-"\n"
-"Sie sind dabei, die erste Version zu übertragen. Es gibt keine existierende "
-"Version, die Sie nachbessern könnten.\n"
+#: lib/branch_rename.tcl:81
+msgid "Please select a branch to rename."
+msgstr "Bitte wählen Sie einen Zweig zum umbenennen."
 
-#: lib/commit.tcl:18
-msgid ""
-"Cannot amend while merging.\n"
-"\n"
-"You are currently in the middle of a merge that has not been fully "
-"completed.  You cannot amend the prior commit unless you first abort the "
-"current merge activity.\n"
-msgstr ""
-"Nachbesserung währen Zusammenführung nicht möglich.\n"
-"\n"
-"Sie haben das Zusammenführen von Versionen angefangen, aber noch nicht "
-"beendet. Sie können keine vorige Übertragung nachbessern, solange eine "
-"unfertige Zusammenführung existiert. Dazu müssen Sie die Zusammenführung "
-"beenden oder abbrechen.\n"
+#: lib/branch_rename.tcl:92 lib/branch_create.tcl:154
+msgid "Please supply a branch name."
+msgstr "Bitte geben Sie einen Zweignamen an."
 
-#: lib/commit.tcl:48
-msgid "Error loading commit data for amend:"
-msgstr "Fehler beim Laden der Versionsdaten für Nachbessern:"
+#: lib/branch_rename.tcl:112 lib/branch_create.tcl:165
+#, tcl-format
+msgid "'%s' is not an acceptable branch name."
+msgstr "»%s« ist kein zulässiger Zweigname."
 
-#: lib/commit.tcl:75
-msgid "Unable to obtain your identity:"
-msgstr "Benutzername konnte nicht bestimmt werden:"
+#: lib/branch_rename.tcl:123
+#, tcl-format
+msgid "Failed to rename '%s'."
+msgstr "Fehler beim Umbenennen von »%s«."
 
-#: lib/commit.tcl:80
-msgid "Invalid GIT_COMMITTER_IDENT:"
-msgstr "Ungültiger Wert von GIT_COMMITTER_INDENT:"
+#: lib/choose_font.tcl:41
+msgid "Select"
+msgstr "Auswählen"
 
-#: lib/commit.tcl:129
-#, tcl-format
-msgid "warning: Tcl does not support encoding '%s'."
-msgstr "Warning: Tcl/Tk unterstützt die Zeichencodierung »%s« nicht."
+#: lib/choose_font.tcl:55
+msgid "Font Family"
+msgstr "Schriftfamilie"
+
+#: lib/choose_font.tcl:76
+msgid "Font Size"
+msgstr "Schriftgröße"
 
-#: lib/commit.tcl:149
+#: lib/choose_font.tcl:93
+msgid "Font Example"
+msgstr "Schriftbeispiel"
+
+#: lib/choose_font.tcl:105
 msgid ""
-"Last scanned state does not match repository state.\n"
-"\n"
-"Another Git program has modified this repository since the last scan.  A "
-"rescan must be performed before another commit can be created.\n"
-"\n"
-"The rescan will be automatically started now.\n"
+"This is example text.\n"
+"If you like this text, it can be your font."
 msgstr ""
-"Der letzte geladene Status stimmt nicht mehr mit dem Projektarchiv überein.\n"
-"\n"
-"Ein anderes Git-Programm hat das Projektarchiv seit dem letzten Laden "
-"geändert.  Vor dem Eintragen einer neuen Version muss neu geladen werden.\n"
-"\n"
-"Es wird gleich neu geladen.\n"
+"Dies ist ein Beispieltext.\n"
+"Wenn Ihnen dieser Text gefällt, sollten Sie diese Schriftart wählen."
 
-#: lib/commit.tcl:172
+#: lib/option.tcl:11
 #, tcl-format
-msgid ""
-"Unmerged files cannot be committed.\n"
-"\n"
-"File %s has merge conflicts.  You must resolve them and stage the file "
-"before committing.\n"
-msgstr ""
-"Nicht zusammengeführte Dateien können nicht eingetragen werden.\n"
-"\n"
-"Die Datei »%s« hat noch nicht aufgelöste Zusammenführungs-Konflikte. Sie "
-"müssen diese Konflikte auflösen, bevor Sie eintragen können.\n"
+msgid "Invalid global encoding '%s'"
+msgstr "Ungültige globale Zeichenkodierung »%s«"
 
-#: lib/commit.tcl:180
+#: lib/option.tcl:19
 #, tcl-format
-msgid ""
-"Unknown file state %s detected.\n"
-"\n"
-"File %s cannot be committed by this program.\n"
-msgstr ""
-"Unbekannter Dateizustand »%s«.\n"
-"\n"
-"Datei »%s« kann nicht eingetragen werden.\n"
+msgid "Invalid repo encoding '%s'"
+msgstr "Ungültige Archiv-Zeichenkodierung »%s«"
 
-#: lib/commit.tcl:188
-msgid ""
-"No changes to commit.\n"
-"\n"
-"You must stage at least 1 file before you can commit.\n"
-msgstr ""
-"Keine Änderungen vorhanden, die eingetragen werden könnten.\n"
-"\n"
-"Sie müssen mindestens eine Datei bereitstellen, bevor Sie eintragen können.\n"
+#: lib/option.tcl:119
+msgid "Restore Defaults"
+msgstr "Voreinstellungen wiederherstellen"
 
-#: lib/commit.tcl:203
-msgid ""
-"Please supply a commit message.\n"
-"\n"
-"A good commit message has the following format:\n"
-"\n"
-"- First line: Describe in one sentence what you did.\n"
-"- Second line: Blank\n"
-"- Remaining lines: Describe why this change is good.\n"
-msgstr ""
-"Bitte geben Sie eine Versionsbeschreibung ein.\n"
-"\n"
-"Eine gute Versionsbeschreibung enthält folgende Abschnitte:\n"
-"\n"
-"- Erste Zeile: Eine Zusammenfassung, was man gemacht hat.\n"
-"\n"
-"- Zweite Zeile: Leerzeile\n"
-"\n"
-"- Rest: Eine ausführliche Beschreibung, warum diese Änderung hilfreich ist.\n"
-
-#: lib/commit.tcl:234
-msgid "Calling pre-commit hook..."
-msgstr "Aufrufen der Vor-Eintragen-Kontrolle (»pre-commit hook«)..."
-
-#: lib/commit.tcl:249
-msgid "Commit declined by pre-commit hook."
-msgstr "Eintragen abgelehnt durch Vor-Eintragen-Kontrolle (»pre-commit hook«)."
-
-#: lib/commit.tcl:272
-msgid "Calling commit-msg hook..."
-msgstr "Aufrufen der Versionsbeschreibungs-Kontrolle (»commit-message hook«)..."
-
-#: lib/commit.tcl:287
-msgid "Commit declined by commit-msg hook."
-msgstr ""
-"Eintragen abgelehnt durch Versionsbeschreibungs-Kontrolle (»commit-message "
-"hook«)."
-
-#: lib/commit.tcl:300
-msgid "Committing changes..."
-msgstr "Änderungen eintragen..."
-
-#: lib/commit.tcl:316
-msgid "write-tree failed:"
-msgstr "write-tree fehlgeschlagen:"
-
-#: lib/commit.tcl:317 lib/commit.tcl:361 lib/commit.tcl:382
-msgid "Commit failed."
-msgstr "Eintragen fehlgeschlagen."
+#: lib/option.tcl:123
+msgid "Save"
+msgstr "Speichern"
 
-#: lib/commit.tcl:334
+#: lib/option.tcl:133
 #, tcl-format
-msgid "Commit %s appears to be corrupt"
-msgstr "Version »%s« scheint beschädigt zu sein"
-
-#: lib/commit.tcl:339
-msgid ""
-"No changes to commit.\n"
-"\n"
-"No files were modified by this commit and it was not a merge commit.\n"
-"\n"
-"A rescan will be automatically started now.\n"
-msgstr ""
-"Keine Änderungen einzutragen.\n"
-"\n"
-"Es gibt keine geänderte Datei bei dieser Version und es wurde auch nichts "
-"zusammengeführt.\n"
-"\n"
-"Das Arbeitsverzeichnis wird daher jetzt neu geladen.\n"
+msgid "%s Repository"
+msgstr "Projektarchiv %s"
 
-#: lib/commit.tcl:346
-msgid "No changes to commit."
-msgstr "Keine Änderungen, die eingetragen werden können."
+#: lib/option.tcl:134
+msgid "Global (All Repositories)"
+msgstr "Global (Alle Projektarchive)"
 
-#: lib/commit.tcl:360
-msgid "commit-tree failed:"
-msgstr "commit-tree fehlgeschlagen:"
+#: lib/option.tcl:140
+msgid "User Name"
+msgstr "Benutzername"
 
-#: lib/commit.tcl:381
-msgid "update-ref failed:"
-msgstr "update-ref fehlgeschlagen:"
+#: lib/option.tcl:141
+msgid "Email Address"
+msgstr "E-Mail-Adresse"
 
-#: lib/commit.tcl:469
-#, tcl-format
-msgid "Created commit %s: %s"
-msgstr "Version %s übertragen: %s"
+#: lib/option.tcl:143
+msgid "Summarize Merge Commits"
+msgstr "Zusammenführungs-Versionen zusammenfassen"
 
-#: lib/console.tcl:59
-msgid "Working... please wait..."
-msgstr "Verarbeitung. Bitte warten..."
+#: lib/option.tcl:144
+msgid "Merge Verbosity"
+msgstr "Ausführlichkeit der Zusammenführen-Meldungen"
 
-#: lib/console.tcl:186
-msgid "Success"
-msgstr "Erfolgreich"
+#: lib/option.tcl:145
+msgid "Show Diffstat After Merge"
+msgstr "Vergleichsstatistik nach Zusammenführen anzeigen"
 
-#: lib/console.tcl:200
-msgid "Error: Command Failed"
-msgstr "Fehler: Kommando fehlgeschlagen"
+#: lib/option.tcl:146
+msgid "Use Merge Tool"
+msgstr "Zusammenführungswerkzeug"
 
-#: lib/database.tcl:43
-msgid "Number of loose objects"
-msgstr "Anzahl unverknüpfter Objekte"
+#: lib/option.tcl:148
+msgid "Trust File Modification Timestamps"
+msgstr "Auf Dateiänderungsdatum verlassen"
 
-#: lib/database.tcl:44
-msgid "Disk space used by loose objects"
-msgstr "Festplattenplatz von unverknüpften Objekten"
+#: lib/option.tcl:149
+msgid "Prune Tracking Branches During Fetch"
+msgstr "Übernahmezweige aufräumen während Anforderung"
 
-#: lib/database.tcl:45
-msgid "Number of packed objects"
-msgstr "Anzahl komprimierter Objekte"
+#: lib/option.tcl:150
+msgid "Match Tracking Branches"
+msgstr "Passend zu Übernahmezweig"
 
-#: lib/database.tcl:46
-msgid "Number of packs"
-msgstr "Anzahl Komprimierungseinheiten"
+#: lib/option.tcl:151
+msgid "Use Textconv For Diffs and Blames"
+msgstr ""
 
-#: lib/database.tcl:47
-msgid "Disk space used by packed objects"
-msgstr "Festplattenplatz von komprimierten Objekten"
+#: lib/option.tcl:152
+msgid "Blame Copy Only On Changed Files"
+msgstr "Kopie-Annotieren nur bei geänderten Dateien"
 
-#: lib/database.tcl:48
-msgid "Packed objects waiting for pruning"
-msgstr "Komprimierte Objekte, die zum Aufräumen vorgesehen sind"
+#: lib/option.tcl:153
+#, fuzzy
+msgid "Maximum Length of Recent Repositories List"
+msgstr "Zuletzt benutzte Projektarchive"
 
-#: lib/database.tcl:49
-msgid "Garbage files"
-msgstr "Dateien im Mülleimer"
+#: lib/option.tcl:154
+msgid "Minimum Letters To Blame Copy On"
+msgstr "Mindestzahl Zeichen für Kopie-Annotieren"
 
-#: lib/database.tcl:72
-msgid "Compressing the object database"
-msgstr "Objektdatenbank komprimieren"
+#: lib/option.tcl:155
+msgid "Blame History Context Radius (days)"
+msgstr "Anzahl Tage für Historien-Kontext"
 
-#: lib/database.tcl:83
-msgid "Verifying the object database with fsck-objects"
-msgstr "Die Objektdatenbank durch »fsck-objects« überprüfen lassen"
+#: lib/option.tcl:156
+msgid "Number of Diff Context Lines"
+msgstr "Anzahl der Kontextzeilen beim Vergleich"
 
-#: lib/database.tcl:107
-#, tcl-format
-msgid ""
-"This repository currently has approximately %i loose objects.\n"
-"\n"
-"To maintain optimal performance it is strongly recommended that you compress "
-"the database.\n"
-"\n"
-"Compress the database now?"
+#: lib/option.tcl:157
+msgid "Additional Diff Parameters"
 msgstr ""
-"Dieses Projektarchiv enthält ungefähr %i nicht verknüpfte Objekte.\n"
-"\n"
-"Für eine optimale Performance wird empfohlen, die Datenbank des Projektarchivs zu komprimieren.\n"
-"\n"
-"Soll die Datenbank jetzt komprimiert werden?"
 
-#: lib/date.tcl:25
-#, tcl-format
-msgid "Invalid date from Git: %s"
-msgstr "Ungültiges Datum von Git: %s"
+#: lib/option.tcl:158
+msgid "Commit Message Text Width"
+msgstr "Textbreite der Versionsbeschreibung"
 
-#: lib/diff.tcl:64
-#, tcl-format
-msgid ""
-"No differences detected.\n"
-"\n"
-"%s has no changes.\n"
-"\n"
-"The modification date of this file was updated by another application, but "
-"the content within the file was not changed.\n"
-"\n"
-"A rescan will be automatically started to find other files which may have "
-"the same state."
-msgstr ""
-"Keine Änderungen feststellbar.\n"
-"\n"
-"»%s« enthält keine Änderungen. Zwar wurde das Änderungsdatum dieser Datei von "
-"einem anderen Programm modifiziert, aber der Inhalt der Datei ist "
-"unverändert.\n"
-"\n"
-"Das Arbeitsverzeichnis wird jetzt neu geladen, um diese Änderung bei allen "
-"Dateien zu prüfen."
+#: lib/option.tcl:159
+msgid "New Branch Name Template"
+msgstr "Namensvorschlag für neue Zweige"
 
-#: lib/diff.tcl:104
-#, tcl-format
-msgid "Loading diff of %s..."
-msgstr "Vergleich von »%s« laden..."
+#: lib/option.tcl:160
+msgid "Default File Contents Encoding"
+msgstr "Voreingestellte Zeichenkodierung"
 
-#: lib/diff.tcl:125
-msgid ""
-"LOCAL: deleted\n"
-"REMOTE:\n"
+#: lib/option.tcl:161
+msgid "Warn before committing to a detached head"
 msgstr ""
-"LOKAL: gelöscht\n"
-"ANDERES:\n"
 
-#: lib/diff.tcl:130
-msgid ""
-"REMOTE: deleted\n"
-"LOCAL:\n"
+#: lib/option.tcl:162
+msgid "Staging of untracked files"
 msgstr ""
-"ANDERES: gelöscht\n"
-"LOKAL:\n"
 
-#: lib/diff.tcl:137
-msgid "LOCAL:\n"
-msgstr "LOKAL:\n"
+#: lib/option.tcl:163
+msgid "Show untracked files"
+msgstr ""
 
-#: lib/diff.tcl:140
-msgid "REMOTE:\n"
-msgstr "ANDERES:\n"
+#: lib/option.tcl:164
+msgid "Tab spacing"
+msgstr ""
 
-#: lib/diff.tcl:202 lib/diff.tcl:319
+#: lib/option.tcl:182 lib/option.tcl:197 lib/option.tcl:220 lib/option.tcl:282
+#: lib/database.tcl:57
 #, tcl-format
-msgid "Unable to display %s"
-msgstr "Datei »%s« kann nicht angezeigt werden"
-
-#: lib/diff.tcl:203
-msgid "Error loading file:"
-msgstr "Fehler beim Laden der Datei:"
+msgid "%s:"
+msgstr ""
 
-#: lib/diff.tcl:210
-msgid "Git Repository (subproject)"
-msgstr "Git-Projektarchiv (Unterprojekt)"
+#: lib/option.tcl:210
+msgid "Change"
+msgstr "Ändern"
 
-#: lib/diff.tcl:222
-msgid "* Binary file (not showing content)."
-msgstr "* Binärdatei (Inhalt wird nicht angezeigt)"
+#: lib/option.tcl:254
+msgid "Spelling Dictionary:"
+msgstr "Wörterbuch Rechtschreibprüfung:"
 
-#: lib/diff.tcl:227
-#, tcl-format
-msgid ""
-"* Untracked file is %d bytes.\n"
-"* Showing only first %d bytes.\n"
-msgstr ""
-"* Datei nicht unter Versionskontrolle, Dateigröße %d Bytes.\n"
-"* Nur erste %d Bytes werden angezeigt.\n"
+#: lib/option.tcl:284
+msgid "Change Font"
+msgstr "Schriftart ändern"
 
-#: lib/diff.tcl:233
+#: lib/option.tcl:288
 #, tcl-format
-msgid ""
-"\n"
-"* Untracked file clipped here by %s.\n"
-"* To see the entire file, use an external editor.\n"
-msgstr ""
-"\n"
-"* Datei nicht unter Versionskontrolle, hier abgeschnitten durch %s.\n"
-"* Zum Ansehen der vollständigen Datei externen Editor benutzen.\n"
-
-#: lib/diff.tcl:482
-msgid "Failed to unstage selected hunk."
-msgstr ""
-"Fehler beim Herausnehmen des gewählten Kontexts aus der Bereitstellung."
+msgid "Choose %s"
+msgstr "%s wählen"
 
-#: lib/diff.tcl:489
-msgid "Failed to stage selected hunk."
-msgstr "Fehler beim Bereitstellen des gewählten Kontexts."
+#: lib/option.tcl:294
+msgid "pt."
+msgstr "pt."
 
-#: lib/diff.tcl:568
-msgid "Failed to unstage selected line."
-msgstr "Fehler beim Herausnehmen der gewählten Zeile aus der Bereitstellung."
+#: lib/option.tcl:308
+msgid "Preferences"
+msgstr "Einstellungen"
 
-#: lib/diff.tcl:576
-msgid "Failed to stage selected line."
-msgstr "Fehler beim Bereitstellen der gewählten Zeile."
+#: lib/option.tcl:345
+msgid "Failed to completely save options:"
+msgstr "Optionen konnten nicht gespeichert werden:"
 
 #: lib/encoding.tcl:443
 msgid "Default"
@@ -1698,229 +1451,40 @@ msgstr "Systemweit (%s)"
 msgid "Other"
 msgstr "Andere"
 
-#: lib/error.tcl:20 lib/error.tcl:114
-msgid "error"
-msgstr "Fehler"
+#: lib/tools.tcl:76
+#, tcl-format
+msgid "Running %s requires a selected file."
+msgstr "Um »%s« zu starten, muss eine Datei ausgewählt sein."
 
-#: lib/error.tcl:36
-msgid "warning"
-msgstr "Warnung"
+#: lib/tools.tcl:92
+#, fuzzy, tcl-format
+msgid "Are you sure you want to run %1$s on file \"%2$s\"?"
+msgstr "Wollen Sie %s wirklich starten?"
 
-#: lib/error.tcl:94
-msgid "You must correct the above errors before committing."
-msgstr ""
-"Sie müssen die obigen Fehler zuerst beheben, bevor Sie eintragen können."
-
-#: lib/index.tcl:6
-msgid "Unable to unlock the index."
-msgstr "Bereitstellung kann nicht wieder freigegeben werden."
-
-#: lib/index.tcl:15
-msgid "Index Error"
-msgstr "Fehler in Bereitstellung"
-
-#: lib/index.tcl:17
-msgid ""
-"Updating the Git index failed.  A rescan will be automatically started to "
-"resynchronize git-gui."
-msgstr ""
-"Das Aktualisieren der Git-Bereitstellung ist fehlgeschlagen. Eine allgemeine "
-"Git-Aktualisierung wird jetzt gestartet, um git-gui wieder mit git zu "
-"synchronisieren."
-
-#: lib/index.tcl:28
-msgid "Continue"
-msgstr "Fortsetzen"
-
-#: lib/index.tcl:31
-msgid "Unlock Index"
-msgstr "Bereitstellung freigeben"
-
-#: lib/index.tcl:289
-#, tcl-format
-msgid "Unstaging %s from commit"
-msgstr "Datei »%s« aus der Bereitstellung herausnehmen"
-
-#: lib/index.tcl:328
-msgid "Ready to commit."
-msgstr "Bereit zum Eintragen."
-
-#: lib/index.tcl:341
-#, tcl-format
-msgid "Adding %s"
-msgstr "»%s« hinzufügen..."
-
-#: lib/index.tcl:398
-#, tcl-format
-msgid "Revert changes in file %s?"
-msgstr "Änderungen in Datei »%s« verwerfen?"
-
-#: lib/index.tcl:400
-#, tcl-format
-msgid "Revert changes in these %i files?"
-msgstr "Änderungen in den gewählten %i Dateien verwerfen?"
-
-#: lib/index.tcl:408
-msgid "Any unstaged changes will be permanently lost by the revert."
-msgstr ""
-"Alle nicht bereitgestellten Änderungen werden beim Verwerfen verloren gehen."
-
-#: lib/index.tcl:411
-msgid "Do Nothing"
-msgstr "Nichts tun"
-
-#: lib/index.tcl:429
-msgid "Reverting selected files"
-msgstr "Änderungen in gewählten Dateien verwerfen"
-
-#: lib/index.tcl:433
-#, tcl-format
-msgid "Reverting %s"
-msgstr "Änderungen in %s verwerfen"
-
-#: lib/merge.tcl:13
-msgid ""
-"Cannot merge while amending.\n"
-"\n"
-"You must finish amending this commit before starting any type of merge.\n"
-msgstr ""
-"Zusammenführen kann nicht gleichzeitig mit Nachbessern durchgeführt werden.\n"
-"\n"
-"Sie müssen zuerst die Nachbesserungs-Version abschließen, bevor Sie "
-"zusammenführen können.\n"
-
-#: lib/merge.tcl:27
-msgid ""
-"Last scanned state does not match repository state.\n"
-"\n"
-"Another Git program has modified this repository since the last scan.  A "
-"rescan must be performed before a merge can be performed.\n"
-"\n"
-"The rescan will be automatically started now.\n"
-msgstr ""
-"Der letzte geladene Status stimmt nicht mehr mit dem Projektarchiv überein.\n"
-"\n"
-"Ein anderes Git-Programm hat das Projektarchiv seit dem letzten Laden "
-"geändert.  Vor einem Zusammenführen muss neu geladen werden.\n"
-"\n"
-"Es wird gleich neu geladen.\n"
-
-#: lib/merge.tcl:45
+#: lib/tools.tcl:96
 #, tcl-format
-msgid ""
-"You are in the middle of a conflicted merge.\n"
-"\n"
-"File %s has merge conflicts.\n"
-"\n"
-"You must resolve them, stage the file, and commit to complete the current "
-"merge.  Only then can you begin another merge.\n"
-msgstr ""
-"Zusammenführung mit Konflikten.\n"
-"\n"
-"Die Datei »%s« enthält Konflikte beim Zusammenführen. Sie müssen diese "
-"Konflikte per Hand auflösen. Anschließend müssen Sie die Datei wieder "
-"bereitstellen und eintragen, um die Zusammenführung abzuschließen. Erst "
-"danach kann eine neue Zusammenführung begonnen werden.\n"
+msgid "Are you sure you want to run %s?"
+msgstr "Wollen Sie %s wirklich starten?"
 
-#: lib/merge.tcl:55
+#: lib/tools.tcl:118
 #, tcl-format
-msgid ""
-"You are in the middle of a change.\n"
-"\n"
-"File %s is modified.\n"
-"\n"
-"You should complete the current commit before starting a merge.  Doing so "
-"will help you abort a failed merge, should the need arise.\n"
-msgstr ""
-"Es liegen Änderungen vor.\n"
-"\n"
-"Die Datei »%s« wurde geändert.  Sie sollten zuerst die bereitgestellte "
-"Version abschließen, bevor Sie eine Zusammenführung beginnen.  Mit dieser "
-"Reihenfolge können Sie mögliche Konflikte beim Zusammenführen wesentlich "
-"einfacher beheben oder abbrechen.\n"
+msgid "Tool: %s"
+msgstr "Werkzeug: %s"
 
-#: lib/merge.tcl:107
+#: lib/tools.tcl:119
 #, tcl-format
-msgid "%s of %s"
-msgstr "%s von %s"
+msgid "Running: %s"
+msgstr "Starten: %s"
 
-#: lib/merge.tcl:120
+#: lib/tools.tcl:158
 #, tcl-format
-msgid "Merging %s and %s..."
-msgstr "Zusammenführen von %s und %s..."
-
-#: lib/merge.tcl:131
-msgid "Merge completed successfully."
-msgstr "Zusammenführen erfolgreich abgeschlossen."
-
-#: lib/merge.tcl:133
-msgid "Merge failed.  Conflict resolution is required."
-msgstr "Zusammenführen fehlgeschlagen. Konfliktauflösung ist notwendig."
+msgid "Tool completed successfully: %s"
+msgstr "Werkzeug erfolgreich abgeschlossen: %s"
 
-#: lib/merge.tcl:158
+#: lib/tools.tcl:160
 #, tcl-format
-msgid "Merge Into %s"
-msgstr "Zusammenführen in »%s«"
-
-#: lib/merge.tcl:177
-msgid "Revision To Merge"
-msgstr "Zusammenzuführende Version"
-
-#: lib/merge.tcl:212
-msgid ""
-"Cannot abort while amending.\n"
-"\n"
-"You must finish amending this commit.\n"
-msgstr ""
-"Abbruch der Nachbesserung ist nicht möglich.\n"
-"\n"
-"Sie müssen die Nachbesserung der Version abschließen.\n"
-
-#: lib/merge.tcl:222
-msgid ""
-"Abort merge?\n"
-"\n"
-"Aborting the current merge will cause *ALL* uncommitted changes to be lost.\n"
-"\n"
-"Continue with aborting the current merge?"
-msgstr ""
-"Zusammenführen abbrechen?\n"
-"\n"
-"Wenn Sie abbrechen, gehen alle noch nicht eingetragenen Änderungen "
-"verloren.\n"
-"\n"
-"Zusammenführen jetzt abbrechen?"
-
-#: lib/merge.tcl:228
-msgid ""
-"Reset changes?\n"
-"\n"
-"Resetting the changes will cause *ALL* uncommitted changes to be lost.\n"
-"\n"
-"Continue with resetting the current changes?"
-msgstr ""
-"Änderungen zurücksetzen?\n"
-"\n"
-"Wenn Sie zurücksetzen, gehen alle noch nicht eingetragenen Änderungen "
-"verloren.\n"
-"\n"
-"Änderungen jetzt zurücksetzen?"
-
-#: lib/merge.tcl:239
-msgid "Aborting"
-msgstr "Abbruch"
-
-#: lib/merge.tcl:239
-msgid "files reset"
-msgstr "Dateien zurückgesetzt"
-
-#: lib/merge.tcl:267
-msgid "Abort failed."
-msgstr "Abbruch fehlgeschlagen."
-
-#: lib/merge.tcl:269
-msgid "Abort completed.  Ready."
-msgstr "Abbruch durchgeführt. Bereit."
+msgid "Tool failed: %s"
+msgstr "Werkzeug fehlgeschlagen: %s"
 
 #: lib/mergetool.tcl:8
 msgid "Force resolution to the base version?"
@@ -1970,21 +1534,21 @@ msgstr ""
 msgid "Conflict file does not exist"
 msgstr "Konflikt-Datei existiert nicht"
 
-#: lib/mergetool.tcl:264
+#: lib/mergetool.tcl:246
 #, tcl-format
 msgid "Not a GUI merge tool: '%s'"
 msgstr "Kein GUI Zusammenführungswerkzeug: »%s«"
 
-#: lib/mergetool.tcl:268
+#: lib/mergetool.tcl:275
 #, tcl-format
 msgid "Unsupported merge tool '%s'"
 msgstr "Unbekanntes Zusammenführungswerkzeug: »%s«"
 
-#: lib/mergetool.tcl:303
+#: lib/mergetool.tcl:310
 msgid "Merge tool is already running, terminate it?"
 msgstr "Zusammenführungswerkzeug läuft bereits. Soll es abgebrochen werden?"
 
-#: lib/mergetool.tcl:323
+#: lib/mergetool.tcl:330
 #, tcl-format
 msgid ""
 "Error retrieving versions:\n"
@@ -1993,7 +1557,7 @@ msgstr ""
 "Fehler beim Abrufen der Dateiversionen:\n"
 "%s"
 
-#: lib/mergetool.tcl:343
+#: lib/mergetool.tcl:350
 #, tcl-format
 msgid ""
 "Could not start the merge tool:\n"
@@ -2004,243 +1568,180 @@ msgstr ""
 "\n"
 "%s"
 
-#: lib/mergetool.tcl:347
+#: lib/mergetool.tcl:354
 msgid "Running merge tool..."
 msgstr "Zusammenführungswerkzeug starten..."
 
-#: lib/mergetool.tcl:375 lib/mergetool.tcl:383
+#: lib/mergetool.tcl:382 lib/mergetool.tcl:390
 msgid "Merge tool failed."
 msgstr "Zusammenführungswerkzeug fehlgeschlagen."
 
-#: lib/option.tcl:11
-#, tcl-format
-msgid "Invalid global encoding '%s'"
-msgstr "Ungültige globale Zeichenkodierung »%s«"
-
-#: lib/option.tcl:19
-#, tcl-format
-msgid "Invalid repo encoding '%s'"
-msgstr "Ungültige Archiv-Zeichenkodierung »%s«"
+#: lib/tools_dlg.tcl:22
+#, fuzzy, tcl-format
+msgid "%s (%s): Add Tool"
+msgstr "Werkzeug hinzufügen"
 
-#: lib/option.tcl:117
-msgid "Restore Defaults"
-msgstr "Voreinstellungen wiederherstellen"
+#: lib/tools_dlg.tcl:28
+msgid "Add New Tool Command"
+msgstr "Neues Kommando für Werkzeug hinzufügen"
 
-#: lib/option.tcl:121
-msgid "Save"
-msgstr "Speichern"
+#: lib/tools_dlg.tcl:34
+msgid "Add globally"
+msgstr "Global hinzufügen"
 
-#: lib/option.tcl:131
-#, tcl-format
-msgid "%s Repository"
-msgstr "Projektarchiv %s"
+#: lib/tools_dlg.tcl:46
+msgid "Tool Details"
+msgstr "Einzelheiten des Werkzeugs"
 
-#: lib/option.tcl:132
-msgid "Global (All Repositories)"
-msgstr "Global (Alle Projektarchive)"
+#: lib/tools_dlg.tcl:49
+msgid "Use '/' separators to create a submenu tree:"
+msgstr "Benutzen Sie einen Schrägstrich »/«, um Untermenüs zu erstellen:"
 
-#: lib/option.tcl:138
-msgid "User Name"
-msgstr "Benutzername"
+#: lib/tools_dlg.tcl:60
+msgid "Command:"
+msgstr "Kommando:"
 
-#: lib/option.tcl:139
-msgid "Email Address"
-msgstr "E-Mail-Adresse"
+#: lib/tools_dlg.tcl:71
+msgid "Show a dialog before running"
+msgstr "Bestätigungsfrage vor Starten anzeigen"
 
-#: lib/option.tcl:141
-msgid "Summarize Merge Commits"
-msgstr "Zusammenführungs-Versionen zusammenfassen"
+#: lib/tools_dlg.tcl:77
+msgid "Ask the user to select a revision (sets $REVISION)"
+msgstr "Benutzer nach Version fragen (setzt $REVISION)"
 
-#: lib/option.tcl:142
-msgid "Merge Verbosity"
-msgstr "Ausführlichkeit der Zusammenführen-Meldungen"
+#: lib/tools_dlg.tcl:82
+msgid "Ask the user for additional arguments (sets $ARGS)"
+msgstr "Benutzer nach zusätzlichen Argumenten fragen (setzt $ARGS)"
 
-#: lib/option.tcl:143
-msgid "Show Diffstat After Merge"
-msgstr "Vergleichsstatistik nach Zusammenführen anzeigen"
+#: lib/tools_dlg.tcl:89
+msgid "Don't show the command output window"
+msgstr "Kein Ausgabefenster zeigen"
 
-#: lib/option.tcl:144
-msgid "Use Merge Tool"
-msgstr "Zusammenführungswerkzeug"
+#: lib/tools_dlg.tcl:94
+msgid "Run only if a diff is selected ($FILENAME not empty)"
+msgstr "Nur starten, wenn ein Vergleich gewählt ist ($FILENAME ist nicht leer)"
 
-#: lib/option.tcl:146
-msgid "Trust File Modification Timestamps"
-msgstr "Auf Dateiänderungsdatum verlassen"
+#: lib/tools_dlg.tcl:118
+msgid "Please supply a name for the tool."
+msgstr "Bitte geben Sie einen Werkzeugnamen an."
 
-#: lib/option.tcl:147
-msgid "Prune Tracking Branches During Fetch"
-msgstr "Übernahmezweige aufräumen während Anforderung"
+#: lib/tools_dlg.tcl:126
+#, tcl-format
+msgid "Tool '%s' already exists."
+msgstr "Werkzeug »%s« existiert bereits."
 
-#: lib/option.tcl:148
-msgid "Match Tracking Branches"
-msgstr "Passend zu Übernahmezweig"
-
-#: lib/option.tcl:149
-msgid "Blame Copy Only On Changed Files"
-msgstr "Kopie-Annotieren nur bei geänderten Dateien"
-
-#: lib/option.tcl:150
-msgid "Minimum Letters To Blame Copy On"
-msgstr "Mindestzahl Zeichen für Kopie-Annotieren"
-
-#: lib/option.tcl:151
-msgid "Blame History Context Radius (days)"
-msgstr "Anzahl Tage für Historien-Kontext"
-
-#: lib/option.tcl:152
-msgid "Number of Diff Context Lines"
-msgstr "Anzahl der Kontextzeilen beim Vergleich"
-
-#: lib/option.tcl:153
-msgid "Commit Message Text Width"
-msgstr "Textbreite der Versionsbeschreibung"
-
-#: lib/option.tcl:154
-msgid "New Branch Name Template"
-msgstr "Namensvorschlag für neue Zweige"
-
-#: lib/option.tcl:155
-msgid "Default File Contents Encoding"
-msgstr "Voreingestellte Zeichenkodierung"
-
-#: lib/option.tcl:203
-msgid "Change"
-msgstr "Ändern"
-
-#: lib/option.tcl:230
-msgid "Spelling Dictionary:"
-msgstr "Wörterbuch Rechtschreibprüfung:"
-
-#: lib/option.tcl:254
-msgid "Change Font"
-msgstr "Schriftart ändern"
-
-#: lib/option.tcl:258
+#: lib/tools_dlg.tcl:148
 #, tcl-format
-msgid "Choose %s"
-msgstr "%s wählen"
-
-#: lib/option.tcl:264
-msgid "pt."
-msgstr "pt."
-
-#: lib/option.tcl:278
-msgid "Preferences"
-msgstr "Einstellungen"
-
-#: lib/option.tcl:314
-msgid "Failed to completely save options:"
-msgstr "Optionen konnten nicht gespeichert werden:"
+msgid ""
+"Could not add tool:\n"
+"%s"
+msgstr ""
+"Werkzeug konnte nicht hinzugefügt werden:\n"
+"\n"
+"%s"
 
-#: lib/remote_add.tcl:19
-msgid "Add Remote"
-msgstr "Externes Archiv hinzufügen"
+#: lib/tools_dlg.tcl:187
+#, fuzzy, tcl-format
+msgid "%s (%s): Remove Tool"
+msgstr "Werkzeug entfernen"
 
-#: lib/remote_add.tcl:24
-msgid "Add New Remote"
-msgstr "Neues externes Archiv hinzufügen"
+#: lib/tools_dlg.tcl:193
+msgid "Remove Tool Commands"
+msgstr "Werkzeugkommandos entfernen"
 
-#: lib/remote_add.tcl:28 lib/tools_dlg.tcl:36
-msgid "Add"
-msgstr "Hinzufügen"
+#: lib/tools_dlg.tcl:198
+msgid "Remove"
+msgstr "Entfernen"
 
-#: lib/remote_add.tcl:37
-msgid "Remote Details"
-msgstr "Einzelheiten des externen Archivs"
+#: lib/tools_dlg.tcl:231
+msgid "(Blue denotes repository-local tools)"
+msgstr "(Werkzeuge für lokales Archiv werden in Blau angezeigt)"
 
-#: lib/remote_add.tcl:50
-msgid "Location:"
-msgstr "Adresse:"
+#: lib/tools_dlg.tcl:283
+#, fuzzy, tcl-format
+msgid "%s (%s):"
+msgstr "Systemweit (%s)"
 
-#: lib/remote_add.tcl:62
-msgid "Further Action"
-msgstr "Weitere Aktion jetzt"
+#: lib/tools_dlg.tcl:292
+#, tcl-format
+msgid "Run Command: %s"
+msgstr "Kommando aufrufen: %s"
 
-#: lib/remote_add.tcl:65
-msgid "Fetch Immediately"
-msgstr "Gleich anfordern"
+#: lib/tools_dlg.tcl:306
+msgid "Arguments"
+msgstr "Argumente"
 
-#: lib/remote_add.tcl:71
-msgid "Initialize Remote Repository and Push"
-msgstr "Externes Archiv initialisieren und dahin versenden"
+#: lib/tools_dlg.tcl:341
+msgid "OK"
+msgstr "Ok"
 
-#: lib/remote_add.tcl:77
-msgid "Do Nothing Else Now"
-msgstr "Nichts tun"
+#: lib/search.tcl:48
+msgid "Find:"
+msgstr "Suchen:"
 
-#: lib/remote_add.tcl:101
-msgid "Please supply a remote name."
-msgstr "Bitte geben Sie einen Namen des externen Archivs an."
+#: lib/search.tcl:50
+msgid "Next"
+msgstr "Nächster"
 
-#: lib/remote_add.tcl:114
-#, tcl-format
-msgid "'%s' is not an acceptable remote name."
-msgstr "»%s« ist kein zulässiger Name eines externen Archivs."
+#: lib/search.tcl:51
+msgid "Prev"
+msgstr "Voriger"
 
-#: lib/remote_add.tcl:125
-#, tcl-format
-msgid "Failed to add remote '%s' of location '%s'."
-msgstr "Fehler beim Hinzufügen des externen Archivs »%s« aus Herkunftsort »%s«."
+#: lib/search.tcl:52
+msgid "RegExp"
+msgstr ""
 
-#: lib/remote_add.tcl:133 lib/transport.tcl:6
-#, tcl-format
-msgid "fetch %s"
-msgstr "»%s« anfordern"
+#: lib/search.tcl:54
+msgid "Case"
+msgstr ""
 
-#: lib/remote_add.tcl:134
-#, tcl-format
-msgid "Fetching the %s"
-msgstr "»%s« anfordern"
+#: lib/shortcut.tcl:8 lib/shortcut.tcl:43 lib/shortcut.tcl:75
+#, fuzzy, tcl-format
+msgid "%s (%s): Create Desktop Icon"
+msgstr "Desktop-Icon erstellen"
 
-#: lib/remote_add.tcl:157
-#, tcl-format
-msgid "Do not know how to initialize repository at location '%s'."
-msgstr "Initialisieren eines externen Archivs an Adresse »%s« ist nicht möglich."
+#: lib/shortcut.tcl:24 lib/shortcut.tcl:65
+msgid "Cannot write shortcut:"
+msgstr "Fehler beim Schreiben der Verknüpfung:"
 
-#: lib/remote_add.tcl:163 lib/transport.tcl:25 lib/transport.tcl:63
-#: lib/transport.tcl:81
-#, tcl-format
-msgid "push %s"
-msgstr "»%s« versenden..."
+#: lib/shortcut.tcl:140
+msgid "Cannot write icon:"
+msgstr "Fehler beim Erstellen des Icons:"
 
-#: lib/remote_add.tcl:164
-#, tcl-format
-msgid "Setting up the %s (at %s)"
-msgstr "Einrichten von »%s« an »%s«"
+#: lib/remote_branch_delete.tcl:29
+#, fuzzy, tcl-format
+msgid "%s (%s): Delete Branch Remotely"
+msgstr "Zweig in externem Archiv löschen"
 
-#: lib/remote_branch_delete.tcl:29 lib/remote_branch_delete.tcl:34
+#: lib/remote_branch_delete.tcl:34
 msgid "Delete Branch Remotely"
 msgstr "Zweig in externem Archiv löschen"
 
-#: lib/remote_branch_delete.tcl:47
+#: lib/remote_branch_delete.tcl:48
 msgid "From Repository"
 msgstr "In Projektarchiv"
 
-#: lib/remote_branch_delete.tcl:50 lib/transport.tcl:134
-msgid "Remote:"
-msgstr "Externes Archiv:"
-
-#: lib/remote_branch_delete.tcl:66 lib/transport.tcl:149
-msgid "Arbitrary Location:"
-msgstr "Adresse:"
-
-#: lib/remote_branch_delete.tcl:84
+#: lib/remote_branch_delete.tcl:88
 msgid "Branches"
 msgstr "Zweige"
 
-#: lib/remote_branch_delete.tcl:109
+#: lib/remote_branch_delete.tcl:110
 msgid "Delete Only If"
 msgstr "Nur löschen, wenn"
 
-#: lib/remote_branch_delete.tcl:111
+#: lib/remote_branch_delete.tcl:112
 msgid "Merged Into:"
 msgstr "Zusammengeführt mit:"
 
-#: lib/remote_branch_delete.tcl:152
+#: lib/remote_branch_delete.tcl:120 lib/branch_delete.tcl:53
+msgid "Always (Do not perform merge checks)"
+msgstr "Immer (Keine Zusammenführungsprüfung)"
+
+#: lib/remote_branch_delete.tcl:153
 msgid "A branch is required for 'Merged Into'."
 msgstr "Für »Zusammenführen mit« muss ein Zweig angegeben werden."
 
-#: lib/remote_branch_delete.tcl:184
+#: lib/remote_branch_delete.tcl:185
 #, tcl-format
 msgid ""
 "The following branches are not completely merged into %s:\n"
@@ -2251,7 +1752,7 @@ msgstr ""
 "\n"
 " - %s"
 
-#: lib/remote_branch_delete.tcl:189
+#: lib/remote_branch_delete.tcl:190
 #, tcl-format
 msgid ""
 "One or more of the merge tests failed because you have not fetched the "
@@ -2261,332 +1762,1137 @@ msgstr ""
 "notwendigen Versionen vorher angefordert haben.  Sie sollten versuchen, "
 "zuerst von »%s« anzufordern."
 
-#: lib/remote_branch_delete.tcl:207
+#: lib/remote_branch_delete.tcl:208
 msgid "Please select one or more branches to delete."
 msgstr "Bitte wählen Sie mindestens einen Zweig, der gelöscht werden soll."
 
-#: lib/remote_branch_delete.tcl:226
+#: lib/remote_branch_delete.tcl:218 lib/branch_delete.tcl:115
+msgid ""
+"Recovering deleted branches is difficult.\n"
+"\n"
+"Delete the selected branches?"
+msgstr ""
+"Das Wiederherstellen von gelöschten Zweigen ist nur mit größerem Aufwand "
+"möglich.\n"
+"\n"
+"Sollen die ausgewählten Zweige gelöscht werden?"
+
+#: lib/remote_branch_delete.tcl:227
 #, tcl-format
 msgid "Deleting branches from %s"
 msgstr "Zweige auf »%s« werden gelöscht"
 
-#: lib/remote_branch_delete.tcl:292
+#: lib/remote_branch_delete.tcl:300
 msgid "No repository selected."
 msgstr "Kein Projektarchiv ausgewählt."
 
-#: lib/remote_branch_delete.tcl:297
+#: lib/remote_branch_delete.tcl:305
 #, tcl-format
 msgid "Scanning %s..."
 msgstr "»%s« laden..."
 
-#: lib/remote.tcl:163
-msgid "Remove Remote"
-msgstr "Externes Archiv entfernen"
+#: lib/choose_repository.tcl:45
+msgid "Git Gui"
+msgstr "Git Gui"
 
-#: lib/remote.tcl:168
-msgid "Prune from"
-msgstr "Aufräumen von"
+#: lib/choose_repository.tcl:104 lib/choose_repository.tcl:427
+msgid "Create New Repository"
+msgstr "Neues Projektarchiv"
 
-#: lib/remote.tcl:173
-msgid "Fetch from"
-msgstr "Anfordern von"
+#: lib/choose_repository.tcl:110
+msgid "New..."
+msgstr "Neu..."
 
-#: lib/remote.tcl:215
-msgid "Push to"
-msgstr "Versenden nach"
+#: lib/choose_repository.tcl:117 lib/choose_repository.tcl:511
+msgid "Clone Existing Repository"
+msgstr "Projektarchiv klonen"
 
-#: lib/search.tcl:21
-msgid "Find:"
-msgstr "Suchen:"
+#: lib/choose_repository.tcl:128
+msgid "Clone..."
+msgstr "Klonen..."
 
-#: lib/search.tcl:23
-msgid "Next"
-msgstr "Nächster"
+#: lib/choose_repository.tcl:135 lib/choose_repository.tcl:1105
+msgid "Open Existing Repository"
+msgstr "Projektarchiv öffnen"
 
-#: lib/search.tcl:24
-msgid "Prev"
-msgstr "Voriger"
+#: lib/choose_repository.tcl:141
+msgid "Open..."
+msgstr "Öffnen..."
 
-#: lib/search.tcl:25
-msgid "Case-Sensitive"
-msgstr "Groß-/Kleinschreibung unterscheiden"
+#: lib/choose_repository.tcl:154
+msgid "Recent Repositories"
+msgstr "Zuletzt benutzte Projektarchive"
 
-#: lib/shortcut.tcl:21 lib/shortcut.tcl:62
-msgid "Cannot write shortcut:"
-msgstr "Fehler beim Schreiben der Verknüpfung:"
+#: lib/choose_repository.tcl:164
+msgid "Open Recent Repository:"
+msgstr "Zuletzt benutztes Projektarchiv öffnen:"
 
-#: lib/shortcut.tcl:137
-msgid "Cannot write icon:"
-msgstr "Fehler beim Erstellen des Icons:"
+#: lib/choose_repository.tcl:331 lib/choose_repository.tcl:338
+#: lib/choose_repository.tcl:345
+#, tcl-format
+msgid "Failed to create repository %s:"
+msgstr "Projektarchiv »%s« konnte nicht erstellt werden:"
 
-#: lib/spellcheck.tcl:57
-msgid "Unsupported spell checker"
-msgstr "Rechtschreibprüfungsprogramm nicht unterstützt"
+#: lib/choose_repository.tcl:422 lib/branch_create.tcl:33
+msgid "Create"
+msgstr "Erstellen"
 
-#: lib/spellcheck.tcl:65
-msgid "Spell checking is unavailable"
-msgstr "Rechtschreibprüfung nicht verfügbar"
+#: lib/choose_repository.tcl:432
+msgid "Directory:"
+msgstr "Verzeichnis:"
 
-#: lib/spellcheck.tcl:68
-msgid "Invalid spell checking configuration"
-msgstr "Unbenutzbare Konfiguration der Rechtschreibprüfung"
+#: lib/choose_repository.tcl:462 lib/choose_repository.tcl:588
+#: lib/choose_repository.tcl:1139
+msgid "Git Repository"
+msgstr "Git Projektarchiv"
 
-#: lib/spellcheck.tcl:70
+#: lib/choose_repository.tcl:487
 #, tcl-format
-msgid "Reverting dictionary to %s."
-msgstr "Wörterbuch auf %s zurückgesetzt."
+msgid "Directory %s already exists."
+msgstr "Verzeichnis »%s« existiert bereits."
 
-#: lib/spellcheck.tcl:73
-msgid "Spell checker silently failed on startup"
-msgstr "Rechtschreibprüfungsprogramm mit Fehler abgebrochen"
+#: lib/choose_repository.tcl:491
+#, tcl-format
+msgid "File %s already exists."
+msgstr "Datei »%s« existiert bereits."
 
-#: lib/spellcheck.tcl:80
-msgid "Unrecognized spell checker"
-msgstr "Unbekanntes Rechtschreibprüfungsprogramm"
+#: lib/choose_repository.tcl:506
+msgid "Clone"
+msgstr "Klonen"
 
-#: lib/spellcheck.tcl:186
-msgid "No Suggestions"
-msgstr "Keine Vorschläge"
+#: lib/choose_repository.tcl:519
+msgid "Source Location:"
+msgstr "Herkunft:"
 
-#: lib/spellcheck.tcl:388
-msgid "Unexpected EOF from spell checker"
-msgstr "Unerwartetes EOF vom Rechtschreibprüfungsprogramm"
+#: lib/choose_repository.tcl:528
+msgid "Target Directory:"
+msgstr "Zielverzeichnis:"
 
-#: lib/spellcheck.tcl:392
-msgid "Spell Checker Failed"
-msgstr "Rechtschreibprüfung fehlgeschlagen"
+#: lib/choose_repository.tcl:538
+msgid "Clone Type:"
+msgstr "Art des Klonens:"
 
-#: lib/sshkey.tcl:31
-msgid "No keys found."
-msgstr "Keine Schlüssel gefunden."
+#: lib/choose_repository.tcl:543
+msgid "Standard (Fast, Semi-Redundant, Hardlinks)"
+msgstr "Standard (schnell, teilweise redundant, Hardlinks)"
 
-#: lib/sshkey.tcl:34
-#, tcl-format
-msgid "Found a public key in: %s"
-msgstr "Öffentlicher Schlüssel gefunden in: %s"
+#: lib/choose_repository.tcl:548
+msgid "Full Copy (Slower, Redundant Backup)"
+msgstr "Alles kopieren (langsamer, volle Redundanz)"
 
-#: lib/sshkey.tcl:40
-msgid "Generate Key"
-msgstr "Schlüssel erzeugen"
+#: lib/choose_repository.tcl:553
+msgid "Shared (Fastest, Not Recommended, No Backup)"
+msgstr "Verknüpft (schnell, nicht empfohlen, kein Backup)"
 
-#: lib/sshkey.tcl:56
-msgid "Copy To Clipboard"
-msgstr "In Zwischenablage kopieren"
+#: lib/choose_repository.tcl:560
+msgid "Recursively clone submodules too"
+msgstr ""
 
-#: lib/sshkey.tcl:70
-msgid "Your OpenSSH Public Key"
-msgstr "Ihr OpenSSH öffenlicher Schlüssel"
+#: lib/choose_repository.tcl:594 lib/choose_repository.tcl:641
+#: lib/choose_repository.tcl:790 lib/choose_repository.tcl:864
+#: lib/choose_repository.tcl:1145 lib/choose_repository.tcl:1153
+#, tcl-format
+msgid "Not a Git repository: %s"
+msgstr "Kein Git-Projektarchiv in »%s« gefunden."
 
-#: lib/sshkey.tcl:78
-msgid "Generating..."
-msgstr "Erzeugen..."
+#: lib/choose_repository.tcl:630
+msgid "Standard only available for local repository."
+msgstr "Standard ist nur für lokale Projektarchive verfügbar."
 
-#: lib/sshkey.tcl:84
-#, tcl-format
-msgid ""
-"Could not start ssh-keygen:\n"
-"\n"
-"%s"
-msgstr ""
-"Konnte »ssh-keygen« nicht starten:\n"
-"\n"
-"%s"
+#: lib/choose_repository.tcl:634
+msgid "Shared only available for local repository."
+msgstr "Verknüpft ist nur für lokale Projektarchive verfügbar."
 
-#: lib/sshkey.tcl:111
-msgid "Generation failed."
-msgstr "Schlüsselerzeugung fehlgeschlagen."
+#: lib/choose_repository.tcl:655
+#, tcl-format
+msgid "Location %s already exists."
+msgstr "Projektarchiv »%s« existiert bereits."
 
-#: lib/sshkey.tcl:118
-msgid "Generation succeeded, but no keys found."
-msgstr "Schlüsselerzeugung erfolgreich, aber keine Schlüssel gefunden."
+#: lib/choose_repository.tcl:666
+msgid "Failed to configure origin"
+msgstr "Der Ursprungsort konnte nicht eingerichtet werden"
+
+#: lib/choose_repository.tcl:678
+msgid "Counting objects"
+msgstr "Objekte werden gezählt"
+
+#: lib/choose_repository.tcl:679
+msgid "buckets"
+msgstr "Buckets"
 
-#: lib/sshkey.tcl:121
+#: lib/choose_repository.tcl:703
 #, tcl-format
-msgid "Your key is in: %s"
-msgstr "Ihr Schlüssel ist abgelegt in: %s"
+msgid "Unable to copy objects/info/alternates: %s"
+msgstr "Kopien von Objekten/Info/Alternates konnten nicht erstellt werden: %s"
 
-#: lib/status_bar.tcl:83
+#: lib/choose_repository.tcl:740
 #, tcl-format
-msgid "%s ... %*i of %*i %s (%3i%%)"
-msgstr "%s ... %*i von %*i %s (%3i%%)"
+msgid "Nothing to clone from %s."
+msgstr "Von »%s« konnte nichts geklont werden."
 
-#: lib/tools_dlg.tcl:22
-msgid "Add Tool"
-msgstr "Werkzeug hinzufügen"
+#: lib/choose_repository.tcl:742 lib/choose_repository.tcl:962
+#: lib/choose_repository.tcl:974
+msgid "The 'master' branch has not been initialized."
+msgstr "Der »master«-Zweig wurde noch nicht initialisiert."
 
-#: lib/tools_dlg.tcl:28
-msgid "Add New Tool Command"
-msgstr "Neues Kommando für Werkzeug hinzufügen"
+#: lib/choose_repository.tcl:755
+msgid "Hardlinks are unavailable.  Falling back to copying."
+msgstr "Hardlinks nicht verfügbar. Stattdessen wird kopiert."
 
-#: lib/tools_dlg.tcl:33
-msgid "Add globally"
-msgstr "Global hinzufügen"
+#: lib/choose_repository.tcl:769
+#, tcl-format
+msgid "Cloning from %s"
+msgstr "Kopieren von »%s«"
 
-#: lib/tools_dlg.tcl:45
-msgid "Tool Details"
-msgstr "Einzelheiten des Werkzeugs"
+#: lib/choose_repository.tcl:800
+msgid "Copying objects"
+msgstr "Objektdatenbank kopieren"
 
-#: lib/tools_dlg.tcl:48
-msgid "Use '/' separators to create a submenu tree:"
-msgstr "Benutzen Sie einen Schrägstrich »/«, um Untermenüs zu erstellen:"
+#: lib/choose_repository.tcl:801
+msgid "KiB"
+msgstr "KB"
 
-#: lib/tools_dlg.tcl:61
-msgid "Command:"
-msgstr "Kommando:"
+#: lib/choose_repository.tcl:825
+#, tcl-format
+msgid "Unable to copy object: %s"
+msgstr "Objekt kann nicht kopiert werden: %s"
 
-#: lib/tools_dlg.tcl:74
-msgid "Show a dialog before running"
-msgstr "Bestätigungsfrage vor Starten anzeigen"
+#: lib/choose_repository.tcl:837
+msgid "Linking objects"
+msgstr "Objekte verlinken"
 
-#: lib/tools_dlg.tcl:80
-msgid "Ask the user to select a revision (sets $REVISION)"
-msgstr "Benutzer nach Version fragen (setzt $REVISION)"
+#: lib/choose_repository.tcl:838
+msgid "objects"
+msgstr "Objekte"
 
-#: lib/tools_dlg.tcl:85
-msgid "Ask the user for additional arguments (sets $ARGS)"
-msgstr "Benutzer nach zusätzlichen Argumenten fragen (setzt $ARGS)"
+#: lib/choose_repository.tcl:846
+#, tcl-format
+msgid "Unable to hardlink object: %s"
+msgstr "Für Objekt konnte kein Hardlink erstellt werden: %s"
 
-#: lib/tools_dlg.tcl:92
-msgid "Don't show the command output window"
-msgstr "Kein Ausgabefenster zeigen"
+#: lib/choose_repository.tcl:903
+msgid "Cannot fetch branches and objects.  See console output for details."
+msgstr ""
+"Zweige und Objekte konnten nicht angefordert werden.  Kontrollieren Sie die "
+"Ausgaben auf der Konsole für weitere Angaben."
 
-#: lib/tools_dlg.tcl:97
-msgid "Run only if a diff is selected ($FILENAME not empty)"
-msgstr "Nur starten, wenn ein Vergleich gewählt ist ($FILENAME ist nicht leer)"
+#: lib/choose_repository.tcl:914
+msgid "Cannot fetch tags.  See console output for details."
+msgstr ""
+"Markierungen konnten nicht angefordert werden.  Kontrollieren Sie die "
+"Ausgaben auf der Konsole für weitere Angaben."
 
-#: lib/tools_dlg.tcl:121
-msgid "Please supply a name for the tool."
-msgstr "Bitte geben Sie einen Werkzeugnamen an."
+#: lib/choose_repository.tcl:938
+msgid "Cannot determine HEAD.  See console output for details."
+msgstr ""
+"Die Zweigspitze (HEAD) konnte nicht gefunden werden.  Kontrollieren Sie die "
+"Ausgaben auf der Konsole für weitere Angaben."
 
-#: lib/tools_dlg.tcl:129
+#: lib/choose_repository.tcl:947
 #, tcl-format
-msgid "Tool '%s' already exists."
-msgstr "Werkzeug »%s« existiert bereits."
+msgid "Unable to cleanup %s"
+msgstr "Verzeichnis »%s« kann nicht aufgeräumt werden."
 
-#: lib/tools_dlg.tcl:151
+#: lib/choose_repository.tcl:953
+msgid "Clone failed."
+msgstr "Klonen fehlgeschlagen."
+
+#: lib/choose_repository.tcl:960
+msgid "No default branch obtained."
+msgstr "Kein voreingestellter Zweig gefunden."
+
+#: lib/choose_repository.tcl:971
 #, tcl-format
-msgid ""
-"Could not add tool:\n"
-"%s"
-msgstr ""
-"Werkzeug konnte nicht hinzugefügt werden:\n"
-"\n"
-"%s"
+msgid "Cannot resolve %s as a commit."
+msgstr "»%s« wurde nicht als Version gefunden."
 
-#: lib/tools_dlg.tcl:190
-msgid "Remove Tool"
-msgstr "Werkzeug entfernen"
+#: lib/choose_repository.tcl:998
+msgid "Creating working directory"
+msgstr "Arbeitskopie erstellen"
 
-#: lib/tools_dlg.tcl:196
-msgid "Remove Tool Commands"
-msgstr "Werkzeugkommandos entfernen"
+#: lib/choose_repository.tcl:1028
+msgid "Initial file checkout failed."
+msgstr "Erstellen der Arbeitskopie fehlgeschlagen."
 
-#: lib/tools_dlg.tcl:200
-msgid "Remove"
-msgstr "Entfernen"
+#: lib/choose_repository.tcl:1072
+#, fuzzy
+msgid "Cloning submodules"
+msgstr "Kopieren von »%s«"
 
-#: lib/tools_dlg.tcl:236
-msgid "(Blue denotes repository-local tools)"
-msgstr "(Werkzeuge für lokales Archiv werden in Blau angezeigt)"
+#: lib/choose_repository.tcl:1087
+msgid "Cannot clone submodules."
+msgstr ""
+
+#: lib/choose_repository.tcl:1110
+msgid "Repository:"
+msgstr "Projektarchiv:"
 
-#: lib/tools_dlg.tcl:297
+#: lib/choose_repository.tcl:1159
 #, tcl-format
-msgid "Run Command: %s"
-msgstr "Kommando aufrufen: %s"
+msgid "Failed to open repository %s:"
+msgstr "Projektarchiv »%s« konnte nicht geöffnet werden."
 
-#: lib/tools_dlg.tcl:311
-msgid "Arguments"
-msgstr "Argumente"
+#: lib/about.tcl:26
+msgid "git-gui - a graphical user interface for Git."
+msgstr "git-gui - eine grafische Oberfläche für Git."
 
-#: lib/tools_dlg.tcl:348
-msgid "OK"
-msgstr "Ok"
+#: lib/blame.tcl:74
+#, fuzzy, tcl-format
+msgid "%s (%s): File Viewer"
+msgstr "Datei-Browser"
 
-#: lib/tools.tcl:75
-#, tcl-format
-msgid "Running %s requires a selected file."
-msgstr "Um »%s« zu starten, muss eine Datei ausgewählt sein."
+#: lib/blame.tcl:80
+msgid "Commit:"
+msgstr "Version:"
 
-#: lib/tools.tcl:90
-#, tcl-format
-msgid "Are you sure you want to run %s?"
-msgstr "Wollen Sie %s wirklich starten?"
+#: lib/blame.tcl:282
+msgid "Copy Commit"
+msgstr "Version kopieren"
 
-#: lib/tools.tcl:110
-#, tcl-format
-msgid "Tool: %s"
-msgstr "Werkzeug: %s"
+#: lib/blame.tcl:286
+msgid "Find Text..."
+msgstr "Text suchen..."
 
-#: lib/tools.tcl:111
-#, tcl-format
-msgid "Running: %s"
-msgstr "Starten: %s"
+#: lib/blame.tcl:290
+#, fuzzy
+msgid "Goto Line..."
+msgstr "Klonen..."
 
-#: lib/tools.tcl:149
-#, tcl-format
-msgid "Tool completed successfully: %s"
-msgstr "Werkzeug erfolgreich abgeschlossen: %s"
+#: lib/blame.tcl:299
+msgid "Do Full Copy Detection"
+msgstr "Volle Kopie-Erkennung"
 
-#: lib/tools.tcl:151
-#, tcl-format
-msgid "Tool failed: %s"
-msgstr "Werkzeug fehlgeschlagen: %s"
+#: lib/blame.tcl:303
+msgid "Show History Context"
+msgstr "Historien-Kontext anzeigen"
 
-#: lib/transport.tcl:7
-#, tcl-format
-msgid "Fetching new changes from %s"
-msgstr "Neue Änderungen von »%s« holen"
+#: lib/blame.tcl:306
+msgid "Blame Parent Commit"
+msgstr "Elternversion annotieren"
 
-#: lib/transport.tcl:18
+#: lib/blame.tcl:468
 #, tcl-format
-msgid "remote prune %s"
-msgstr "Aufräumen von »%s«"
+msgid "Reading %s..."
+msgstr "%s lesen..."
 
-#: lib/transport.tcl:19
-#, tcl-format
-msgid "Pruning tracking branches deleted from %s"
-msgstr "Übernahmezweige aufräumen und entfernen, die in »%s« gelöscht wurden"
+#: lib/blame.tcl:596
+msgid "Loading copy/move tracking annotations..."
+msgstr "Annotierungen für Kopieren/Verschieben werden geladen..."
 
-#: lib/transport.tcl:26
-#, tcl-format
-msgid "Pushing changes to %s"
-msgstr "Änderungen nach »%s« versenden"
+#: lib/blame.tcl:613
+msgid "lines annotated"
+msgstr "Zeilen annotiert"
 
-#: lib/transport.tcl:64
-#, tcl-format
-msgid "Mirroring to %s"
-msgstr "Spiegeln nach %s"
+#: lib/blame.tcl:815
+msgid "Loading original location annotations..."
+msgstr "Annotierungen für ursprünglichen Ort werden geladen..."
 
-#: lib/transport.tcl:82
-#, tcl-format
-msgid "Pushing %s %s to %s"
-msgstr "%s %s nach %s versenden"
+#: lib/blame.tcl:818
+msgid "Annotation complete."
+msgstr "Annotierung vollständig."
 
-#: lib/transport.tcl:100
-msgid "Push Branches"
-msgstr "Zweige versenden"
+#: lib/blame.tcl:849
+msgid "Busy"
+msgstr "Verarbeitung läuft"
 
-#: lib/transport.tcl:114
-msgid "Source Branches"
-msgstr "Lokale Zweige"
+#: lib/blame.tcl:850
+msgid "Annotation process is already running."
+msgstr "Annotierung läuft bereits."
 
-#: lib/transport.tcl:131
-msgid "Destination Repository"
-msgstr "Ziel-Projektarchiv"
+#: lib/blame.tcl:889
+msgid "Running thorough copy detection..."
+msgstr "Intensive Kopie-Erkennung läuft..."
 
-#: lib/transport.tcl:169
-msgid "Transfer Options"
-msgstr "Netzwerk-Einstellungen"
+#: lib/blame.tcl:957
+msgid "Loading annotation..."
+msgstr "Annotierung laden..."
 
-#: lib/transport.tcl:171
-msgid "Force overwrite existing branch (may discard changes)"
-msgstr ""
-"Überschreiben von existierenden Zweigen erzwingen (könnte Änderungen löschen)"
+#: lib/blame.tcl:1010
+msgid "Author:"
+msgstr "Autor:"
 
-#: lib/transport.tcl:175
-msgid "Use thin pack (for slow network connections)"
-msgstr "Kompaktes Datenformat benutzen (für langsame Netzverbindungen)"
+#: lib/blame.tcl:1014
+msgid "Committer:"
+msgstr "Eintragender:"
 
-#: lib/transport.tcl:179
-msgid "Include tags"
-msgstr "Mit Markierungen übertragen"
+#: lib/blame.tcl:1019
+msgid "Original File:"
+msgstr "Ursprüngliche Datei:"
+
+#: lib/blame.tcl:1067
+msgid "Cannot find HEAD commit:"
+msgstr "Zweigspitze (»HEAD«) kann nicht gefunden werden:"
+
+#: lib/blame.tcl:1122
+msgid "Cannot find parent commit:"
+msgstr "Elternversion kann nicht gefunden werden:"
+
+#: lib/blame.tcl:1137
+msgid "Unable to display parent"
+msgstr "Elternversion kann nicht angezeigt werden"
+
+#: lib/blame.tcl:1138 lib/diff.tcl:345
+msgid "Error loading diff:"
+msgstr "Fehler beim Laden des Vergleichs:"
+
+#: lib/blame.tcl:1279
+msgid "Originally By:"
+msgstr "Ursprünglich von:"
+
+#: lib/blame.tcl:1285
+msgid "In File:"
+msgstr "In Datei:"
+
+#: lib/blame.tcl:1290
+msgid "Copied Or Moved Here By:"
+msgstr "Kopiert oder verschoben durch:"
+
+#: lib/diff.tcl:77
+#, tcl-format
+msgid ""
+"No differences detected.\n"
+"\n"
+"%s has no changes.\n"
+"\n"
+"The modification date of this file was updated by another application, but "
+"the content within the file was not changed.\n"
+"\n"
+"A rescan will be automatically started to find other files which may have "
+"the same state."
+msgstr ""
+"Keine Änderungen feststellbar.\n"
+"\n"
+"»%s« enthält keine Änderungen. Zwar wurde das Änderungsdatum dieser Datei "
+"von einem anderen Programm modifiziert, aber der Inhalt der Datei ist "
+"unverändert.\n"
+"\n"
+"Das Arbeitsverzeichnis wird jetzt neu geladen, um diese Änderung bei allen "
+"Dateien zu prüfen."
+
+#: lib/diff.tcl:117
+#, tcl-format
+msgid "Loading diff of %s..."
+msgstr "Vergleich von »%s« laden..."
+
+#: lib/diff.tcl:143
+msgid ""
+"LOCAL: deleted\n"
+"REMOTE:\n"
+msgstr ""
+"LOKAL: gelöscht\n"
+"ANDERES:\n"
+
+#: lib/diff.tcl:148
+msgid ""
+"REMOTE: deleted\n"
+"LOCAL:\n"
+msgstr ""
+"ANDERES: gelöscht\n"
+"LOKAL:\n"
+
+#: lib/diff.tcl:155
+msgid "LOCAL:\n"
+msgstr "LOKAL:\n"
+
+#: lib/diff.tcl:158
+msgid "REMOTE:\n"
+msgstr "ANDERES:\n"
+
+#: lib/diff.tcl:220 lib/diff.tcl:344
+#, tcl-format
+msgid "Unable to display %s"
+msgstr "Datei »%s« kann nicht angezeigt werden"
+
+#: lib/diff.tcl:221
+msgid "Error loading file:"
+msgstr "Fehler beim Laden der Datei:"
+
+#: lib/diff.tcl:227
+msgid "Git Repository (subproject)"
+msgstr "Git-Projektarchiv (Unterprojekt)"
+
+#: lib/diff.tcl:239
+msgid "* Binary file (not showing content)."
+msgstr "* Binärdatei (Inhalt wird nicht angezeigt)"
+
+#: lib/diff.tcl:244
+#, tcl-format
+msgid ""
+"* Untracked file is %d bytes.\n"
+"* Showing only first %d bytes.\n"
+msgstr ""
+"* Datei nicht unter Versionskontrolle, Dateigröße %d Bytes.\n"
+"* Nur erste %d Bytes werden angezeigt.\n"
+
+#: lib/diff.tcl:250
+#, tcl-format
+msgid ""
+"\n"
+"* Untracked file clipped here by %s.\n"
+"* To see the entire file, use an external editor.\n"
+msgstr ""
+"\n"
+"* Datei nicht unter Versionskontrolle, hier abgeschnitten durch %s.\n"
+"* Zum Ansehen der vollständigen Datei externen Editor benutzen.\n"
+
+#: lib/diff.tcl:583
+msgid "Failed to unstage selected hunk."
+msgstr ""
+"Fehler beim Herausnehmen des gewählten Kontexts aus der Bereitstellung."
+
+#: lib/diff.tcl:591
+#, fuzzy
+msgid "Failed to revert selected hunk."
+msgstr "Fehler beim Bereitstellen des gewählten Kontexts."
+
+#: lib/diff.tcl:594
+msgid "Failed to stage selected hunk."
+msgstr "Fehler beim Bereitstellen des gewählten Kontexts."
+
+#: lib/diff.tcl:687
+msgid "Failed to unstage selected line."
+msgstr "Fehler beim Herausnehmen der gewählten Zeile aus der Bereitstellung."
+
+#: lib/diff.tcl:696
+#, fuzzy
+msgid "Failed to revert selected line."
+msgstr "Fehler beim Bereitstellen der gewählten Zeile."
+
+#: lib/diff.tcl:700
+msgid "Failed to stage selected line."
+msgstr "Fehler beim Bereitstellen der gewählten Zeile."
+
+#: lib/diff.tcl:889
+#, fuzzy
+msgid "Failed to undo last revert."
+msgstr "Aktualisieren von »%s« fehlgeschlagen."
+
+#: lib/sshkey.tcl:34
+msgid "No keys found."
+msgstr "Keine Schlüssel gefunden."
+
+#: lib/sshkey.tcl:37
+#, tcl-format
+msgid "Found a public key in: %s"
+msgstr "Öffentlicher Schlüssel gefunden in: %s"
+
+#: lib/sshkey.tcl:43
+msgid "Generate Key"
+msgstr "Schlüssel erzeugen"
+
+#: lib/sshkey.tcl:61
+msgid "Copy To Clipboard"
+msgstr "In Zwischenablage kopieren"
+
+#: lib/sshkey.tcl:75
+msgid "Your OpenSSH Public Key"
+msgstr "Ihr OpenSSH öffenlicher Schlüssel"
+
+#: lib/sshkey.tcl:83
+msgid "Generating..."
+msgstr "Erzeugen..."
+
+#: lib/sshkey.tcl:89
+#, tcl-format
+msgid ""
+"Could not start ssh-keygen:\n"
+"\n"
+"%s"
+msgstr ""
+"Konnte »ssh-keygen« nicht starten:\n"
+"\n"
+"%s"
+
+#: lib/sshkey.tcl:116
+msgid "Generation failed."
+msgstr "Schlüsselerzeugung fehlgeschlagen."
+
+#: lib/sshkey.tcl:123
+msgid "Generation succeeded, but no keys found."
+msgstr "Schlüsselerzeugung erfolgreich, aber keine Schlüssel gefunden."
+
+#: lib/sshkey.tcl:126
+#, tcl-format
+msgid "Your key is in: %s"
+msgstr "Ihr Schlüssel ist abgelegt in: %s"
+
+#: lib/branch_create.tcl:23
+#, fuzzy, tcl-format
+msgid "%s (%s): Create Branch"
+msgstr "Zweig erstellen"
+
+#: lib/branch_create.tcl:28
+msgid "Create New Branch"
+msgstr "Neuen Zweig erstellen"
+
+#: lib/branch_create.tcl:42
+msgid "Branch Name"
+msgstr "Zweigname"
+
+#: lib/branch_create.tcl:57
+msgid "Match Tracking Branch Name"
+msgstr "Passend zu Übernahmezweig-Name"
+
+#: lib/branch_create.tcl:66
+msgid "Starting Revision"
+msgstr "Anfangsversion"
+
+#: lib/branch_create.tcl:72
+msgid "Update Existing Branch:"
+msgstr "Existierenden Zweig aktualisieren:"
+
+#: lib/branch_create.tcl:75
+msgid "No"
+msgstr "Nein"
+
+#: lib/branch_create.tcl:80
+msgid "Fast Forward Only"
+msgstr "Nur Schnellzusammenführung"
+
+#: lib/branch_create.tcl:97
+msgid "Checkout After Creation"
+msgstr "Arbeitskopie umstellen nach Erstellen"
+
+#: lib/branch_create.tcl:132
+msgid "Please select a tracking branch."
+msgstr "Bitte wählen Sie einen Übernahmezweig."
+
+#: lib/branch_create.tcl:141
+#, tcl-format
+msgid "Tracking branch %s is not a branch in the remote repository."
+msgstr "Übernahmezweig »%s« ist kein Zweig im externen Projektarchiv."
+
+#: lib/console.tcl:59
+msgid "Working... please wait..."
+msgstr "Verarbeitung. Bitte warten..."
+
+#: lib/console.tcl:186
+msgid "Success"
+msgstr "Erfolgreich"
+
+#: lib/console.tcl:200
+msgid "Error: Command Failed"
+msgstr "Fehler: Kommando fehlgeschlagen"
+
+#: lib/line.tcl:17
+msgid "Goto Line:"
+msgstr ""
+
+#: lib/line.tcl:23
+msgid "Go"
+msgstr ""
+
+#: lib/choose_rev.tcl:52
+msgid "This Detached Checkout"
+msgstr "Abgetrennte Arbeitskopie-Version"
+
+#: lib/choose_rev.tcl:60
+msgid "Revision Expression:"
+msgstr "Version Regexp-Ausdruck:"
+
+#: lib/choose_rev.tcl:72
+msgid "Local Branch"
+msgstr "Lokaler Zweig"
+
+#: lib/choose_rev.tcl:77
+msgid "Tracking Branch"
+msgstr "Übernahmezweig"
+
+#: lib/choose_rev.tcl:82 lib/choose_rev.tcl:544
+msgid "Tag"
+msgstr "Markierung"
+
+#: lib/choose_rev.tcl:321
+#, tcl-format
+msgid "Invalid revision: %s"
+msgstr "Ungültige Version: %s"
+
+#: lib/choose_rev.tcl:342
+msgid "No revision selected."
+msgstr "Keine Version ausgewählt."
+
+#: lib/choose_rev.tcl:350
+msgid "Revision expression is empty."
+msgstr "Versions-Ausdruck ist leer."
+
+#: lib/choose_rev.tcl:537
+msgid "Updated"
+msgstr "Aktualisiert"
+
+#: lib/choose_rev.tcl:565
+msgid "URL"
+msgstr "URL"
+
+#: lib/commit.tcl:9
+msgid ""
+"There is nothing to amend.\n"
+"\n"
+"You are about to create the initial commit.  There is no commit before this "
+"to amend.\n"
+msgstr ""
+"Keine Version zur Nachbesserung vorhanden.\n"
+"\n"
+"Sie sind dabei, die erste Version zu übertragen. Es gibt keine existierende "
+"Version, die Sie nachbessern könnten.\n"
+
+#: lib/commit.tcl:18
+msgid ""
+"Cannot amend while merging.\n"
+"\n"
+"You are currently in the middle of a merge that has not been fully "
+"completed.  You cannot amend the prior commit unless you first abort the "
+"current merge activity.\n"
+msgstr ""
+"Nachbesserung währen Zusammenführung nicht möglich.\n"
+"\n"
+"Sie haben das Zusammenführen von Versionen angefangen, aber noch nicht "
+"beendet. Sie können keine vorige Übertragung nachbessern, solange eine "
+"unfertige Zusammenführung existiert. Dazu müssen Sie die Zusammenführung "
+"beenden oder abbrechen.\n"
+
+#: lib/commit.tcl:56
+msgid "Error loading commit data for amend:"
+msgstr "Fehler beim Laden der Versionsdaten für Nachbessern:"
+
+#: lib/commit.tcl:83
+msgid "Unable to obtain your identity:"
+msgstr "Benutzername konnte nicht bestimmt werden:"
+
+#: lib/commit.tcl:88
+msgid "Invalid GIT_COMMITTER_IDENT:"
+msgstr "Ungültiger Wert von GIT_COMMITTER_INDENT:"
+
+#: lib/commit.tcl:138
+#, tcl-format
+msgid "warning: Tcl does not support encoding '%s'."
+msgstr "Warning: Tcl/Tk unterstützt die Zeichencodierung »%s« nicht."
+
+#: lib/commit.tcl:158
+msgid ""
+"Last scanned state does not match repository state.\n"
+"\n"
+"Another Git program has modified this repository since the last scan.  A "
+"rescan must be performed before another commit can be created.\n"
+"\n"
+"The rescan will be automatically started now.\n"
+msgstr ""
+"Der letzte geladene Status stimmt nicht mehr mit dem Projektarchiv überein.\n"
+"\n"
+"Ein anderes Git-Programm hat das Projektarchiv seit dem letzten Laden "
+"geändert.  Vor dem Eintragen einer neuen Version muss neu geladen werden.\n"
+"\n"
+"Es wird gleich neu geladen.\n"
+
+#: lib/commit.tcl:182
+#, tcl-format
+msgid ""
+"Unmerged files cannot be committed.\n"
+"\n"
+"File %s has merge conflicts.  You must resolve them and stage the file "
+"before committing.\n"
+msgstr ""
+"Nicht zusammengeführte Dateien können nicht eingetragen werden.\n"
+"\n"
+"Die Datei »%s« hat noch nicht aufgelöste Zusammenführungs-Konflikte. Sie "
+"müssen diese Konflikte auflösen, bevor Sie eintragen können.\n"
+
+#: lib/commit.tcl:190
+#, tcl-format
+msgid ""
+"Unknown file state %s detected.\n"
+"\n"
+"File %s cannot be committed by this program.\n"
+msgstr ""
+"Unbekannter Dateizustand »%s«.\n"
+"\n"
+"Datei »%s« kann nicht eingetragen werden.\n"
+
+#: lib/commit.tcl:198
+msgid ""
+"No changes to commit.\n"
+"\n"
+"You must stage at least 1 file before you can commit.\n"
+msgstr ""
+"Keine Änderungen vorhanden, die eingetragen werden könnten.\n"
+"\n"
+"Sie müssen mindestens eine Datei bereitstellen, bevor Sie eintragen können.\n"
+
+#: lib/commit.tcl:213
+msgid ""
+"Please supply a commit message.\n"
+"\n"
+"A good commit message has the following format:\n"
+"\n"
+"- First line: Describe in one sentence what you did.\n"
+"- Second line: Blank\n"
+"- Remaining lines: Describe why this change is good.\n"
+msgstr ""
+"Bitte geben Sie eine Versionsbeschreibung ein.\n"
+"\n"
+"Eine gute Versionsbeschreibung enthält folgende Abschnitte:\n"
+"\n"
+"- Erste Zeile: Eine Zusammenfassung, was man gemacht hat.\n"
+"\n"
+"- Zweite Zeile: Leerzeile\n"
+"\n"
+"- Rest: Eine ausführliche Beschreibung, warum diese Änderung hilfreich ist.\n"
+
+#: lib/commit.tcl:244
+msgid "Calling pre-commit hook..."
+msgstr "Aufrufen der Vor-Eintragen-Kontrolle (»pre-commit hook«)..."
+
+#: lib/commit.tcl:259
+msgid "Commit declined by pre-commit hook."
+msgstr "Eintragen abgelehnt durch Vor-Eintragen-Kontrolle (»pre-commit hook«)."
+
+#: lib/commit.tcl:278
+msgid ""
+"You are about to commit on a detached head. This is a potentially dangerous "
+"thing to do because if you switch to another branch you will lose your "
+"changes and it can be difficult to retrieve them later from the reflog. You "
+"should probably cancel this commit and create a new branch to continue.\n"
+" \n"
+" Do you really want to proceed with your Commit?"
+msgstr ""
+
+#: lib/commit.tcl:299
+msgid "Calling commit-msg hook..."
+msgstr ""
+"Aufrufen der Versionsbeschreibungs-Kontrolle (»commit-message hook«)..."
+
+#: lib/commit.tcl:314
+msgid "Commit declined by commit-msg hook."
+msgstr ""
+"Eintragen abgelehnt durch Versionsbeschreibungs-Kontrolle (»commit-message "
+"hook«)."
+
+#: lib/commit.tcl:327
+msgid "Committing changes..."
+msgstr "Änderungen eintragen..."
+
+#: lib/commit.tcl:344
+msgid "write-tree failed:"
+msgstr "write-tree fehlgeschlagen:"
+
+#: lib/commit.tcl:345 lib/commit.tcl:395 lib/commit.tcl:422
+msgid "Commit failed."
+msgstr "Eintragen fehlgeschlagen."
+
+#: lib/commit.tcl:362
+#, tcl-format
+msgid "Commit %s appears to be corrupt"
+msgstr "Version »%s« scheint beschädigt zu sein"
+
+#: lib/commit.tcl:367
+msgid ""
+"No changes to commit.\n"
+"\n"
+"No files were modified by this commit and it was not a merge commit.\n"
+"\n"
+"A rescan will be automatically started now.\n"
+msgstr ""
+"Keine Änderungen einzutragen.\n"
+"\n"
+"Es gibt keine geänderte Datei bei dieser Version und es wurde auch nichts "
+"zusammengeführt.\n"
+"\n"
+"Das Arbeitsverzeichnis wird daher jetzt neu geladen.\n"
+
+#: lib/commit.tcl:374
+msgid "No changes to commit."
+msgstr "Keine Änderungen, die eingetragen werden können."
+
+#: lib/commit.tcl:394
+msgid "commit-tree failed:"
+msgstr "commit-tree fehlgeschlagen:"
+
+#: lib/commit.tcl:421
+msgid "update-ref failed:"
+msgstr "update-ref fehlgeschlagen:"
+
+#: lib/commit.tcl:514
+#, tcl-format
+msgid "Created commit %s: %s"
+msgstr "Version %s übertragen: %s"
+
+#: lib/branch_delete.tcl:16
+#, fuzzy, tcl-format
+msgid "%s (%s): Delete Branch"
+msgstr "Zweig löschen"
+
+#: lib/branch_delete.tcl:21
+msgid "Delete Local Branch"
+msgstr "Lokalen Zweig löschen"
+
+#: lib/branch_delete.tcl:39
+msgid "Local Branches"
+msgstr "Lokale Zweige"
+
+#: lib/branch_delete.tcl:51
+msgid "Delete Only If Merged Into"
+msgstr "Nur löschen, wenn zusammengeführt nach"
+
+#: lib/branch_delete.tcl:103
+#, tcl-format
+msgid "The following branches are not completely merged into %s:"
+msgstr "Folgende Zweige sind noch nicht mit »%s« zusammengeführt:"
+
+#: lib/branch_delete.tcl:131
+#, tcl-format
+msgid " - %s:"
+msgstr ""
+
+#: lib/branch_delete.tcl:141
+#, tcl-format
+msgid ""
+"Failed to delete branches:\n"
+"%s"
+msgstr ""
+"Fehler beim Löschen der Zweige:\n"
+"%s"
+
+#: lib/date.tcl:25
+#, tcl-format
+msgid "Invalid date from Git: %s"
+msgstr "Ungültiges Datum von Git: %s"
+
+#: lib/database.tcl:42
+msgid "Number of loose objects"
+msgstr "Anzahl unverknüpfter Objekte"
+
+#: lib/database.tcl:43
+msgid "Disk space used by loose objects"
+msgstr "Festplattenplatz von unverknüpften Objekten"
+
+#: lib/database.tcl:44
+msgid "Number of packed objects"
+msgstr "Anzahl komprimierter Objekte"
+
+#: lib/database.tcl:45
+msgid "Number of packs"
+msgstr "Anzahl Komprimierungseinheiten"
+
+#: lib/database.tcl:46
+msgid "Disk space used by packed objects"
+msgstr "Festplattenplatz von komprimierten Objekten"
+
+#: lib/database.tcl:47
+msgid "Packed objects waiting for pruning"
+msgstr "Komprimierte Objekte, die zum Aufräumen vorgesehen sind"
+
+#: lib/database.tcl:48
+msgid "Garbage files"
+msgstr "Dateien im Mülleimer"
+
+#: lib/database.tcl:66
+#, fuzzy, tcl-format
+msgid "%s (%s): Database Statistics"
+msgstr "Datenbankstatistik"
+
+#: lib/database.tcl:72
+msgid "Compressing the object database"
+msgstr "Objektdatenbank komprimieren"
+
+#: lib/database.tcl:83
+msgid "Verifying the object database with fsck-objects"
+msgstr "Die Objektdatenbank durch »fsck-objects« überprüfen lassen"
+
+#: lib/database.tcl:107
+#, tcl-format
+msgid ""
+"This repository currently has approximately %i loose objects.\n"
+"\n"
+"To maintain optimal performance it is strongly recommended that you compress "
+"the database.\n"
+"\n"
+"Compress the database now?"
+msgstr ""
+"Dieses Projektarchiv enthält ungefähr %i nicht verknüpfte Objekte.\n"
+"\n"
+"Für eine optimale Performance wird empfohlen, die Datenbank des "
+"Projektarchivs zu komprimieren.\n"
+"\n"
+"Soll die Datenbank jetzt komprimiert werden?"
+
+#: lib/error.tcl:20
+#, fuzzy, tcl-format
+msgid "%s: error"
+msgstr "Fehler"
+
+#: lib/error.tcl:36
+#, fuzzy, tcl-format
+msgid "%s: warning"
+msgstr "Warnung"
+
+#: lib/error.tcl:80
+#, fuzzy, tcl-format
+msgid "%s hook failed:"
+msgstr "Werkzeug fehlgeschlagen: %s"
+
+#: lib/error.tcl:96
+msgid "You must correct the above errors before committing."
+msgstr ""
+"Sie müssen die obigen Fehler zuerst beheben, bevor Sie eintragen können."
+
+#: lib/error.tcl:116
+#, tcl-format
+msgid "%s (%s): error"
+msgstr ""
+
+#: lib/merge.tcl:13
+msgid ""
+"Cannot merge while amending.\n"
+"\n"
+"You must finish amending this commit before starting any type of merge.\n"
+msgstr ""
+"Zusammenführen kann nicht gleichzeitig mit Nachbessern durchgeführt werden.\n"
+"\n"
+"Sie müssen zuerst die Nachbesserungs-Version abschließen, bevor Sie "
+"zusammenführen können.\n"
+
+#: lib/merge.tcl:27
+msgid ""
+"Last scanned state does not match repository state.\n"
+"\n"
+"Another Git program has modified this repository since the last scan.  A "
+"rescan must be performed before a merge can be performed.\n"
+"\n"
+"The rescan will be automatically started now.\n"
+msgstr ""
+"Der letzte geladene Status stimmt nicht mehr mit dem Projektarchiv überein.\n"
+"\n"
+"Ein anderes Git-Programm hat das Projektarchiv seit dem letzten Laden "
+"geändert.  Vor einem Zusammenführen muss neu geladen werden.\n"
+"\n"
+"Es wird gleich neu geladen.\n"
+
+#: lib/merge.tcl:45
+#, tcl-format
+msgid ""
+"You are in the middle of a conflicted merge.\n"
+"\n"
+"File %s has merge conflicts.\n"
+"\n"
+"You must resolve them, stage the file, and commit to complete the current "
+"merge.  Only then can you begin another merge.\n"
+msgstr ""
+"Zusammenführung mit Konflikten.\n"
+"\n"
+"Die Datei »%s« enthält Konflikte beim Zusammenführen. Sie müssen diese "
+"Konflikte per Hand auflösen. Anschließend müssen Sie die Datei wieder "
+"bereitstellen und eintragen, um die Zusammenführung abzuschließen. Erst "
+"danach kann eine neue Zusammenführung begonnen werden.\n"
+
+#: lib/merge.tcl:55
+#, tcl-format
+msgid ""
+"You are in the middle of a change.\n"
+"\n"
+"File %s is modified.\n"
+"\n"
+"You should complete the current commit before starting a merge.  Doing so "
+"will help you abort a failed merge, should the need arise.\n"
+msgstr ""
+"Es liegen Änderungen vor.\n"
+"\n"
+"Die Datei »%s« wurde geändert.  Sie sollten zuerst die bereitgestellte "
+"Version abschließen, bevor Sie eine Zusammenführung beginnen.  Mit dieser "
+"Reihenfolge können Sie mögliche Konflikte beim Zusammenführen wesentlich "
+"einfacher beheben oder abbrechen.\n"
+
+#: lib/merge.tcl:108
+#, tcl-format
+msgid "%s of %s"
+msgstr "%s von %s"
+
+#: lib/merge.tcl:126
+#, tcl-format
+msgid "Merging %s and %s..."
+msgstr "Zusammenführen von %s und %s..."
+
+#: lib/merge.tcl:137
+msgid "Merge completed successfully."
+msgstr "Zusammenführen erfolgreich abgeschlossen."
+
+#: lib/merge.tcl:139
+msgid "Merge failed.  Conflict resolution is required."
+msgstr "Zusammenführen fehlgeschlagen. Konfliktauflösung ist notwendig."
+
+#: lib/merge.tcl:156
+#, tcl-format
+msgid "%s (%s): Merge"
+msgstr ""
+
+#: lib/merge.tcl:164
+#, tcl-format
+msgid "Merge Into %s"
+msgstr "Zusammenführen in »%s«"
+
+#: lib/merge.tcl:183
+msgid "Revision To Merge"
+msgstr "Zusammenzuführende Version"
+
+#: lib/merge.tcl:218
+msgid ""
+"Cannot abort while amending.\n"
+"\n"
+"You must finish amending this commit.\n"
+msgstr ""
+"Abbruch der Nachbesserung ist nicht möglich.\n"
+"\n"
+"Sie müssen die Nachbesserung der Version abschließen.\n"
+
+#: lib/merge.tcl:228
+msgid ""
+"Abort merge?\n"
+"\n"
+"Aborting the current merge will cause *ALL* uncommitted changes to be lost.\n"
+"\n"
+"Continue with aborting the current merge?"
+msgstr ""
+"Zusammenführen abbrechen?\n"
+"\n"
+"Wenn Sie abbrechen, gehen alle noch nicht eingetragenen Änderungen "
+"verloren.\n"
+"\n"
+"Zusammenführen jetzt abbrechen?"
+
+#: lib/merge.tcl:234
+msgid ""
+"Reset changes?\n"
+"\n"
+"Resetting the changes will cause *ALL* uncommitted changes to be lost.\n"
+"\n"
+"Continue with resetting the current changes?"
+msgstr ""
+"Änderungen zurücksetzen?\n"
+"\n"
+"Wenn Sie zurücksetzen, gehen alle noch nicht eingetragenen Änderungen "
+"verloren.\n"
+"\n"
+"Änderungen jetzt zurücksetzen?"
+
+#: lib/merge.tcl:246
+msgid "Aborting"
+msgstr "Abbruch"
+
+#: lib/merge.tcl:247
+msgid "files reset"
+msgstr "Dateien zurückgesetzt"
+
+#: lib/merge.tcl:277
+msgid "Abort failed."
+msgstr "Abbruch fehlgeschlagen."
+
+#: lib/merge.tcl:279
+msgid "Abort completed.  Ready."
+msgstr "Abbruch durchgeführt. Bereit."
+
+#~ msgid "Displaying only %s of %s files."
+#~ msgstr "Nur %s von %s Dateien werden angezeigt."
+
+#~ msgid "New Commit"
+#~ msgstr "Neue Version"
+
+#~ msgid "Case-Sensitive"
+#~ msgstr "Groß-/Kleinschreibung unterscheiden"
diff --git a/po/git-gui.pot b/po/git-gui.pot
index 0c94f9c2c6..b79ed4e133 100644
--- a/po/git-gui.pot
+++ b/po/git-gui.pot
@@ -8,41 +8,42 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-01-26 15:47-0800\n"
+"POT-Creation-Date: 2020-02-08 22:54+0100\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: \n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=CHARSET\n"
 "Content-Transfer-Encoding: 8bit\n"
 
-#: git-gui.sh:41 git-gui.sh:793 git-gui.sh:807 git-gui.sh:820 git-gui.sh:903
-#: git-gui.sh:922
-msgid "git-gui: fatal error"
-msgstr ""
-
-#: git-gui.sh:743
+#: git-gui.sh:847
 #, tcl-format
 msgid "Invalid font specified in %s:"
 msgstr ""
 
-#: git-gui.sh:779
+#: git-gui.sh:901
 msgid "Main Font"
 msgstr ""
 
-#: git-gui.sh:780
+#: git-gui.sh:902
 msgid "Diff/Console Font"
 msgstr ""
 
-#: git-gui.sh:794
+#: git-gui.sh:917 git-gui.sh:931 git-gui.sh:944 git-gui.sh:1034 git-gui.sh:1053
+#: git-gui.sh:3212
+msgid "git-gui: fatal error"
+msgstr ""
+
+#: git-gui.sh:918
 msgid "Cannot find git in PATH."
 msgstr ""
 
-#: git-gui.sh:821
+#: git-gui.sh:945
 msgid "Cannot parse Git version string:"
 msgstr ""
 
-#: git-gui.sh:839
+#: git-gui.sh:970
 #, tcl-format
 msgid ""
 "Git version cannot be determined.\n"
@@ -54,473 +55,518 @@ msgid ""
 "Assume '%s' is version 1.5.0?\n"
 msgstr ""
 
-#: git-gui.sh:1128
+#: git-gui.sh:1267
 msgid "Git directory not found:"
 msgstr ""
 
-#: git-gui.sh:1146
+#: git-gui.sh:1301
 msgid "Cannot move to top of working directory:"
 msgstr ""
 
-#: git-gui.sh:1154
+#: git-gui.sh:1309
 msgid "Cannot use bare repository:"
 msgstr ""
 
-#: git-gui.sh:1162
+#: git-gui.sh:1317
 msgid "No working directory"
 msgstr ""
 
-#: git-gui.sh:1334 lib/checkout_op.tcl:306
+#: git-gui.sh:1491 lib/checkout_op.tcl:306
 msgid "Refreshing file status..."
 msgstr ""
 
-#: git-gui.sh:1390
+#: git-gui.sh:1551
 msgid "Scanning for modified files ..."
 msgstr ""
 
-#: git-gui.sh:1454
+#: git-gui.sh:1629
 msgid "Calling prepare-commit-msg hook..."
 msgstr ""
 
-#: git-gui.sh:1471
+#: git-gui.sh:1646
 msgid "Commit declined by prepare-commit-msg hook."
 msgstr ""
 
-#: git-gui.sh:1629 lib/browser.tcl:246
+#: git-gui.sh:1804 lib/browser.tcl:252
 msgid "Ready."
 msgstr ""
 
-#: git-gui.sh:1787
+#: git-gui.sh:1968
 #, tcl-format
-msgid "Displaying only %s of %s files."
+msgid ""
+"Display limit (gui.maxfilesdisplayed = %s) reached, not showing all %s files."
 msgstr ""
 
-#: git-gui.sh:1913
+#: git-gui.sh:2091
 msgid "Unmodified"
 msgstr ""
 
-#: git-gui.sh:1915
+#: git-gui.sh:2093
 msgid "Modified, not staged"
 msgstr ""
 
-#: git-gui.sh:1916 git-gui.sh:1924
+#: git-gui.sh:2094 git-gui.sh:2106
 msgid "Staged for commit"
 msgstr ""
 
-#: git-gui.sh:1917 git-gui.sh:1925
+#: git-gui.sh:2095 git-gui.sh:2107
 msgid "Portions staged for commit"
 msgstr ""
 
-#: git-gui.sh:1918 git-gui.sh:1926
+#: git-gui.sh:2096 git-gui.sh:2108
 msgid "Staged for commit, missing"
 msgstr ""
 
-#: git-gui.sh:1920
+#: git-gui.sh:2098
 msgid "File type changed, not staged"
 msgstr ""
 
-#: git-gui.sh:1921
+#: git-gui.sh:2099 git-gui.sh:2100
+msgid "File type changed, old type staged for commit"
+msgstr ""
+
+#: git-gui.sh:2101
 msgid "File type changed, staged"
 msgstr ""
 
-#: git-gui.sh:1923
+#: git-gui.sh:2102
+msgid "File type change staged, modification not staged"
+msgstr ""
+
+#: git-gui.sh:2103
+msgid "File type change staged, file missing"
+msgstr ""
+
+#: git-gui.sh:2105
 msgid "Untracked, not staged"
 msgstr ""
 
-#: git-gui.sh:1928
+#: git-gui.sh:2110
 msgid "Missing"
 msgstr ""
 
-#: git-gui.sh:1929
+#: git-gui.sh:2111
 msgid "Staged for removal"
 msgstr ""
 
-#: git-gui.sh:1930
+#: git-gui.sh:2112
 msgid "Staged for removal, still present"
 msgstr ""
 
-#: git-gui.sh:1932 git-gui.sh:1933 git-gui.sh:1934 git-gui.sh:1935
-#: git-gui.sh:1936 git-gui.sh:1937
+#: git-gui.sh:2114 git-gui.sh:2115 git-gui.sh:2116 git-gui.sh:2117
+#: git-gui.sh:2118 git-gui.sh:2119
 msgid "Requires merge resolution"
 msgstr ""
 
-#: git-gui.sh:1972
-msgid "Starting gitk... please wait..."
+#: git-gui.sh:2164
+msgid "Couldn't find gitk in PATH"
 msgstr ""
 
-#: git-gui.sh:1984
-msgid "Couldn't find gitk in PATH"
+#: git-gui.sh:2210 git-gui.sh:2245
+#, tcl-format
+msgid "Starting %s... please wait..."
 msgstr ""
 
-#: git-gui.sh:2043
+#: git-gui.sh:2224
 msgid "Couldn't find git gui in PATH"
 msgstr ""
 
-#: git-gui.sh:2455 lib/choose_repository.tcl:36
+#: git-gui.sh:2726 lib/choose_repository.tcl:53
 msgid "Repository"
 msgstr ""
 
-#: git-gui.sh:2456
+#: git-gui.sh:2727
 msgid "Edit"
 msgstr ""
 
-#: git-gui.sh:2458 lib/choose_rev.tcl:561
+#: git-gui.sh:2729 lib/choose_rev.tcl:567
 msgid "Branch"
 msgstr ""
 
-#: git-gui.sh:2461 lib/choose_rev.tcl:548
+#: git-gui.sh:2732 lib/choose_rev.tcl:554
 msgid "Commit@@noun"
 msgstr ""
 
-#: git-gui.sh:2464 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168
+#: git-gui.sh:2735 lib/merge.tcl:127 lib/merge.tcl:174
 msgid "Merge"
 msgstr ""
 
-#: git-gui.sh:2465 lib/choose_rev.tcl:557
+#: git-gui.sh:2736 lib/choose_rev.tcl:563
 msgid "Remote"
 msgstr ""
 
-#: git-gui.sh:2468
+#: git-gui.sh:2739
 msgid "Tools"
 msgstr ""
 
-#: git-gui.sh:2477
+#: git-gui.sh:2748
 msgid "Explore Working Copy"
 msgstr ""
 
-#: git-gui.sh:2483
+#: git-gui.sh:2763
+msgid "Git Bash"
+msgstr ""
+
+#: git-gui.sh:2772
 msgid "Browse Current Branch's Files"
 msgstr ""
 
-#: git-gui.sh:2487
+#: git-gui.sh:2776
 msgid "Browse Branch Files..."
 msgstr ""
 
-#: git-gui.sh:2492
+#: git-gui.sh:2781
 msgid "Visualize Current Branch's History"
 msgstr ""
 
-#: git-gui.sh:2496
+#: git-gui.sh:2785
 msgid "Visualize All Branch History"
 msgstr ""
 
-#: git-gui.sh:2503
+#: git-gui.sh:2792
 #, tcl-format
 msgid "Browse %s's Files"
 msgstr ""
 
-#: git-gui.sh:2505
+#: git-gui.sh:2794
 #, tcl-format
 msgid "Visualize %s's History"
 msgstr ""
 
-#: git-gui.sh:2510 lib/database.tcl:27 lib/database.tcl:67
+#: git-gui.sh:2799 lib/database.tcl:40
 msgid "Database Statistics"
 msgstr ""
 
-#: git-gui.sh:2513 lib/database.tcl:34
+#: git-gui.sh:2802 lib/database.tcl:33
 msgid "Compress Database"
 msgstr ""
 
-#: git-gui.sh:2516
+#: git-gui.sh:2805
 msgid "Verify Database"
 msgstr ""
 
-#: git-gui.sh:2523 git-gui.sh:2527 git-gui.sh:2531 lib/shortcut.tcl:8
-#: lib/shortcut.tcl:40 lib/shortcut.tcl:72
+#: git-gui.sh:2812 git-gui.sh:2816 git-gui.sh:2820
 msgid "Create Desktop Icon"
 msgstr ""
 
-#: git-gui.sh:2539 lib/choose_repository.tcl:183 lib/choose_repository.tcl:191
+#: git-gui.sh:2828 lib/choose_repository.tcl:209 lib/choose_repository.tcl:217
 msgid "Quit"
 msgstr ""
 
-#: git-gui.sh:2547
+#: git-gui.sh:2836
 msgid "Undo"
 msgstr ""
 
-#: git-gui.sh:2550
+#: git-gui.sh:2839
 msgid "Redo"
 msgstr ""
 
-#: git-gui.sh:2554 git-gui.sh:3109
+#: git-gui.sh:2843 git-gui.sh:3461
 msgid "Cut"
 msgstr ""
 
-#: git-gui.sh:2557 git-gui.sh:3112 git-gui.sh:3186 git-gui.sh:3259
+#: git-gui.sh:2846 git-gui.sh:3464 git-gui.sh:3540 git-gui.sh:3633
 #: lib/console.tcl:69
 msgid "Copy"
 msgstr ""
 
-#: git-gui.sh:2560 git-gui.sh:3115
+#: git-gui.sh:2849 git-gui.sh:3467
 msgid "Paste"
 msgstr ""
 
-#: git-gui.sh:2563 git-gui.sh:3118 lib/branch_delete.tcl:26
-#: lib/remote_branch_delete.tcl:38
+#: git-gui.sh:2852 git-gui.sh:3470 lib/remote_branch_delete.tcl:39
+#: lib/branch_delete.tcl:28
 msgid "Delete"
 msgstr ""
 
-#: git-gui.sh:2567 git-gui.sh:3122 git-gui.sh:3263 lib/console.tcl:71
+#: git-gui.sh:2856 git-gui.sh:3474 git-gui.sh:3637 lib/console.tcl:71
 msgid "Select All"
 msgstr ""
 
-#: git-gui.sh:2576
+#: git-gui.sh:2865
 msgid "Create..."
 msgstr ""
 
-#: git-gui.sh:2582
+#: git-gui.sh:2871
 msgid "Checkout..."
 msgstr ""
 
-#: git-gui.sh:2588
+#: git-gui.sh:2877
 msgid "Rename..."
 msgstr ""
 
-#: git-gui.sh:2593
+#: git-gui.sh:2882
 msgid "Delete..."
 msgstr ""
 
-#: git-gui.sh:2598
+#: git-gui.sh:2887
 msgid "Reset..."
 msgstr ""
 
-#: git-gui.sh:2608
+#: git-gui.sh:2897
 msgid "Done"
 msgstr ""
 
-#: git-gui.sh:2610
+#: git-gui.sh:2899
 msgid "Commit@@verb"
 msgstr ""
 
-#: git-gui.sh:2619 git-gui.sh:3050
-msgid "New Commit"
-msgstr ""
-
-#: git-gui.sh:2627 git-gui.sh:3057
+#: git-gui.sh:2908 git-gui.sh:3400
 msgid "Amend Last Commit"
 msgstr ""
 
-#: git-gui.sh:2637 git-gui.sh:3011 lib/remote_branch_delete.tcl:99
+#: git-gui.sh:2918 git-gui.sh:3361 lib/remote_branch_delete.tcl:101
 msgid "Rescan"
 msgstr ""
 
-#: git-gui.sh:2643
+#: git-gui.sh:2924
 msgid "Stage To Commit"
 msgstr ""
 
-#: git-gui.sh:2649
+#: git-gui.sh:2930
 msgid "Stage Changed Files To Commit"
 msgstr ""
 
-#: git-gui.sh:2655
+#: git-gui.sh:2936
 msgid "Unstage From Commit"
 msgstr ""
 
-#: git-gui.sh:2661 lib/index.tcl:412
+#: git-gui.sh:2942 lib/index.tcl:521
 msgid "Revert Changes"
 msgstr ""
 
-#: git-gui.sh:2669 git-gui.sh:3310 git-gui.sh:3341
+#: git-gui.sh:2950 git-gui.sh:3700 git-gui.sh:3731
 msgid "Show Less Context"
 msgstr ""
 
-#: git-gui.sh:2673 git-gui.sh:3314 git-gui.sh:3345
+#: git-gui.sh:2954 git-gui.sh:3704 git-gui.sh:3735
 msgid "Show More Context"
 msgstr ""
 
-#: git-gui.sh:2680 git-gui.sh:3024 git-gui.sh:3133
+#: git-gui.sh:2961 git-gui.sh:3374 git-gui.sh:3485
 msgid "Sign Off"
 msgstr ""
 
-#: git-gui.sh:2696
+#: git-gui.sh:2977
 msgid "Local Merge..."
 msgstr ""
 
-#: git-gui.sh:2701
+#: git-gui.sh:2982
 msgid "Abort Merge..."
 msgstr ""
 
-#: git-gui.sh:2713 git-gui.sh:2741
+#: git-gui.sh:2994 git-gui.sh:3022
 msgid "Add..."
 msgstr ""
 
-#: git-gui.sh:2717
+#: git-gui.sh:2998
 msgid "Push..."
 msgstr ""
 
-#: git-gui.sh:2721
+#: git-gui.sh:3002
 msgid "Delete Branch..."
 msgstr ""
 
-#: git-gui.sh:2731 git-gui.sh:3292
+#: git-gui.sh:3012 git-gui.sh:3666
 msgid "Options..."
 msgstr ""
 
-#: git-gui.sh:2742
+#: git-gui.sh:3023
 msgid "Remove..."
 msgstr ""
 
-#: git-gui.sh:2751 lib/choose_repository.tcl:50
+#: git-gui.sh:3032 lib/choose_repository.tcl:67
 msgid "Help"
 msgstr ""
 
-#: git-gui.sh:2755 git-gui.sh:2759 lib/about.tcl:14
-#: lib/choose_repository.tcl:44 lib/choose_repository.tcl:53
+#: git-gui.sh:3036 git-gui.sh:3040 lib/choose_repository.tcl:61
+#: lib/choose_repository.tcl:70 lib/about.tcl:14
 #, tcl-format
 msgid "About %s"
 msgstr ""
 
-#: git-gui.sh:2783
+#: git-gui.sh:3064
 msgid "Online Documentation"
 msgstr ""
 
-#: git-gui.sh:2786 lib/choose_repository.tcl:47 lib/choose_repository.tcl:56
+#: git-gui.sh:3067 lib/choose_repository.tcl:64 lib/choose_repository.tcl:73
 msgid "Show SSH Key"
 msgstr ""
 
-#: git-gui.sh:2893
+#: git-gui.sh:3097 git-gui.sh:3229
+msgid "usage:"
+msgstr ""
+
+#: git-gui.sh:3101 git-gui.sh:3233
+msgid "Usage"
+msgstr ""
+
+#: git-gui.sh:3182 lib/blame.tcl:575
+msgid "Error"
+msgstr ""
+
+#: git-gui.sh:3213
 #, tcl-format
 msgid "fatal: cannot stat path %s: No such file or directory"
 msgstr ""
 
-#: git-gui.sh:2926
+#: git-gui.sh:3246
 msgid "Current Branch:"
 msgstr ""
 
-#: git-gui.sh:2947
-msgid "Staged Changes (Will Commit)"
+#: git-gui.sh:3271
+msgid "Unstaged Changes"
 msgstr ""
 
-#: git-gui.sh:2967
-msgid "Unstaged Changes"
+#: git-gui.sh:3293
+msgid "Staged Changes (Will Commit)"
 msgstr ""
 
-#: git-gui.sh:3017
+#: git-gui.sh:3367
 msgid "Stage Changed"
 msgstr ""
 
-#: git-gui.sh:3036 lib/transport.tcl:104 lib/transport.tcl:193
+#: git-gui.sh:3386 lib/transport.tcl:137
 msgid "Push"
 msgstr ""
 
-#: git-gui.sh:3071
+#: git-gui.sh:3413
 msgid "Initial Commit Message:"
 msgstr ""
 
-#: git-gui.sh:3072
+#: git-gui.sh:3414
 msgid "Amended Commit Message:"
 msgstr ""
 
-#: git-gui.sh:3073
+#: git-gui.sh:3415
 msgid "Amended Initial Commit Message:"
 msgstr ""
 
-#: git-gui.sh:3074
+#: git-gui.sh:3416
 msgid "Amended Merge Commit Message:"
 msgstr ""
 
-#: git-gui.sh:3075
+#: git-gui.sh:3417
 msgid "Merge Commit Message:"
 msgstr ""
 
-#: git-gui.sh:3076
+#: git-gui.sh:3418
 msgid "Commit Message:"
 msgstr ""
 
-#: git-gui.sh:3125 git-gui.sh:3267 lib/console.tcl:73
+#: git-gui.sh:3477 git-gui.sh:3641 lib/console.tcl:73
 msgid "Copy All"
 msgstr ""
 
-#: git-gui.sh:3149 lib/blame.tcl:104
+#: git-gui.sh:3501 lib/blame.tcl:106
 msgid "File:"
 msgstr ""
 
-#: git-gui.sh:3255
+#: git-gui.sh:3549 lib/choose_repository.tcl:1100
+msgid "Open"
+msgstr ""
+
+#: git-gui.sh:3629
 msgid "Refresh"
 msgstr ""
 
-#: git-gui.sh:3276
+#: git-gui.sh:3650
 msgid "Decrease Font Size"
 msgstr ""
 
-#: git-gui.sh:3280
+#: git-gui.sh:3654
 msgid "Increase Font Size"
 msgstr ""
 
-#: git-gui.sh:3288 lib/blame.tcl:281
+#: git-gui.sh:3662 lib/blame.tcl:296
 msgid "Encoding"
 msgstr ""
 
-#: git-gui.sh:3299
+#: git-gui.sh:3673
 msgid "Apply/Reverse Hunk"
 msgstr ""
 
-#: git-gui.sh:3304
+#: git-gui.sh:3678
 msgid "Apply/Reverse Line"
 msgstr ""
 
-#: git-gui.sh:3323
+#: git-gui.sh:3684 git-gui.sh:3794 git-gui.sh:3805
+msgid "Revert Hunk"
+msgstr ""
+
+#: git-gui.sh:3689 git-gui.sh:3801 git-gui.sh:3812
+msgid "Revert Line"
+msgstr ""
+
+#: git-gui.sh:3694 git-gui.sh:3791
+msgid "Undo Last Revert"
+msgstr ""
+
+#: git-gui.sh:3713
 msgid "Run Merge Tool"
 msgstr ""
 
-#: git-gui.sh:3328
+#: git-gui.sh:3718
 msgid "Use Remote Version"
 msgstr ""
 
-#: git-gui.sh:3332
+#: git-gui.sh:3722
 msgid "Use Local Version"
 msgstr ""
 
-#: git-gui.sh:3336
+#: git-gui.sh:3726
 msgid "Revert To Base"
 msgstr ""
 
-#: git-gui.sh:3354
+#: git-gui.sh:3744
 msgid "Visualize These Changes In The Submodule"
 msgstr ""
 
-#: git-gui.sh:3358
+#: git-gui.sh:3748
 msgid "Visualize Current Branch History In The Submodule"
 msgstr ""
 
-#: git-gui.sh:3362
+#: git-gui.sh:3752
 msgid "Visualize All Branch History In The Submodule"
 msgstr ""
 
-#: git-gui.sh:3367
+#: git-gui.sh:3757
 msgid "Start git gui In The Submodule"
 msgstr ""
 
-#: git-gui.sh:3389
+#: git-gui.sh:3793
 msgid "Unstage Hunk From Commit"
 msgstr ""
 
-#: git-gui.sh:3391
+#: git-gui.sh:3797
 msgid "Unstage Lines From Commit"
 msgstr ""
 
-#: git-gui.sh:3393
+#: git-gui.sh:3798 git-gui.sh:3809
+msgid "Revert Lines"
+msgstr ""
+
+#: git-gui.sh:3800
 msgid "Unstage Line From Commit"
 msgstr ""
 
-#: git-gui.sh:3396
+#: git-gui.sh:3804
 msgid "Stage Hunk For Commit"
 msgstr ""
 
-#: git-gui.sh:3398
+#: git-gui.sh:3808
 msgid "Stage Lines For Commit"
 msgstr ""
 
-#: git-gui.sh:3400
+#: git-gui.sh:3811
 msgid "Stage Line For Commit"
 msgstr ""
 
-#: git-gui.sh:3424
+#: git-gui.sh:3861
 msgid "Initializing..."
 msgstr ""
 
-#: git-gui.sh:3541
+#: git-gui.sh:4017
 #, tcl-format
 msgid ""
 "Possible environment issues exist.\n"
@@ -531,14 +577,14 @@ msgid ""
 "\n"
 msgstr ""
 
-#: git-gui.sh:3570
+#: git-gui.sh:4046
 msgid ""
 "\n"
 "This is due to a known issue with the\n"
 "Tcl binary distributed by Cygwin."
 msgstr ""
 
-#: git-gui.sh:3575
+#: git-gui.sh:4051
 #, tcl-format
 msgid ""
 "\n"
@@ -549,1846 +595,2072 @@ msgid ""
 "~/.gitconfig file.\n"
 msgstr ""
 
-#: lib/about.tcl:26
-msgid "git-gui - a graphical user interface for Git."
+#: lib/spellcheck.tcl:57
+msgid "Unsupported spell checker"
 msgstr ""
 
-#: lib/blame.tcl:72
-msgid "File Viewer"
+#: lib/spellcheck.tcl:65
+msgid "Spell checking is unavailable"
 msgstr ""
 
-#: lib/blame.tcl:78
-msgid "Commit:"
+#: lib/spellcheck.tcl:68
+msgid "Invalid spell checking configuration"
 msgstr ""
 
-#: lib/blame.tcl:271
-msgid "Copy Commit"
+#: lib/spellcheck.tcl:70
+#, tcl-format
+msgid "Reverting dictionary to %s."
 msgstr ""
 
-#: lib/blame.tcl:275
-msgid "Find Text..."
+#: lib/spellcheck.tcl:73
+msgid "Spell checker silently failed on startup"
 msgstr ""
 
-#: lib/blame.tcl:284
-msgid "Do Full Copy Detection"
+#: lib/spellcheck.tcl:80
+msgid "Unrecognized spell checker"
 msgstr ""
 
-#: lib/blame.tcl:288
-msgid "Show History Context"
+#: lib/spellcheck.tcl:186
+msgid "No Suggestions"
 msgstr ""
 
-#: lib/blame.tcl:291
-msgid "Blame Parent Commit"
+#: lib/spellcheck.tcl:388
+msgid "Unexpected EOF from spell checker"
 msgstr ""
 
-#: lib/blame.tcl:450
-#, tcl-format
-msgid "Reading %s..."
+#: lib/spellcheck.tcl:392
+msgid "Spell Checker Failed"
 msgstr ""
 
-#: lib/blame.tcl:557
-msgid "Loading copy/move tracking annotations..."
+#: lib/transport.tcl:6 lib/remote_add.tcl:132
+#, tcl-format
+msgid "fetch %s"
 msgstr ""
 
-#: lib/blame.tcl:577
-msgid "lines annotated"
+#: lib/transport.tcl:7
+#, tcl-format
+msgid "Fetching new changes from %s"
 msgstr ""
 
-#: lib/blame.tcl:769
-msgid "Loading original location annotations..."
+#: lib/transport.tcl:18
+#, tcl-format
+msgid "remote prune %s"
 msgstr ""
 
-#: lib/blame.tcl:772
-msgid "Annotation complete."
+#: lib/transport.tcl:19
+#, tcl-format
+msgid "Pruning tracking branches deleted from %s"
 msgstr ""
 
-#: lib/blame.tcl:802
-msgid "Busy"
+#: lib/transport.tcl:25
+msgid "fetch all remotes"
 msgstr ""
 
-#: lib/blame.tcl:803
-msgid "Annotation process is already running."
+#: lib/transport.tcl:26
+msgid "Fetching new changes from all remotes"
 msgstr ""
 
-#: lib/blame.tcl:842
-msgid "Running thorough copy detection..."
+#: lib/transport.tcl:40
+msgid "remote prune all remotes"
 msgstr ""
 
-#: lib/blame.tcl:910
-msgid "Loading annotation..."
+#: lib/transport.tcl:41
+msgid "Pruning tracking branches deleted from all remotes"
 msgstr ""
 
-#: lib/blame.tcl:963
-msgid "Author:"
+#: lib/transport.tcl:54 lib/transport.tcl:92 lib/transport.tcl:110
+#: lib/remote_add.tcl:162
+#, tcl-format
+msgid "push %s"
 msgstr ""
 
-#: lib/blame.tcl:967
-msgid "Committer:"
+#: lib/transport.tcl:55
+#, tcl-format
+msgid "Pushing changes to %s"
 msgstr ""
 
-#: lib/blame.tcl:972
-msgid "Original File:"
+#: lib/transport.tcl:93
+#, tcl-format
+msgid "Mirroring to %s"
 msgstr ""
 
-#: lib/blame.tcl:1020
-msgid "Cannot find HEAD commit:"
+#: lib/transport.tcl:111
+#, tcl-format
+msgid "Pushing %s %s to %s"
 msgstr ""
 
-#: lib/blame.tcl:1075
-msgid "Cannot find parent commit:"
+#: lib/transport.tcl:132
+msgid "Push Branches"
 msgstr ""
 
-#: lib/blame.tcl:1090
-msgid "Unable to display parent"
+#: lib/transport.tcl:141 lib/checkout_op.tcl:580 lib/remote_add.tcl:34
+#: lib/browser.tcl:292 lib/branch_checkout.tcl:30 lib/branch_rename.tcl:32
+#: lib/choose_font.tcl:45 lib/option.tcl:127 lib/tools_dlg.tcl:41
+#: lib/tools_dlg.tcl:202 lib/tools_dlg.tcl:345 lib/remote_branch_delete.tcl:43
+#: lib/branch_create.tcl:37 lib/branch_delete.tcl:34 lib/merge.tcl:178
+msgid "Cancel"
 msgstr ""
 
-#: lib/blame.tcl:1091 lib/diff.tcl:320
-msgid "Error loading diff:"
+#: lib/transport.tcl:147
+msgid "Source Branches"
 msgstr ""
 
-#: lib/blame.tcl:1231
-msgid "Originally By:"
+#: lib/transport.tcl:162
+msgid "Destination Repository"
 msgstr ""
 
-#: lib/blame.tcl:1237
-msgid "In File:"
+#: lib/transport.tcl:165 lib/remote_branch_delete.tcl:51
+msgid "Remote:"
 msgstr ""
 
-#: lib/blame.tcl:1242
-msgid "Copied Or Moved Here By:"
+#: lib/transport.tcl:187 lib/remote_branch_delete.tcl:72
+msgid "Arbitrary Location:"
 msgstr ""
 
-#: lib/branch_checkout.tcl:14 lib/branch_checkout.tcl:19
-msgid "Checkout Branch"
+#: lib/transport.tcl:205
+msgid "Transfer Options"
 msgstr ""
 
-#: lib/branch_checkout.tcl:23
-msgid "Checkout"
+#: lib/transport.tcl:207
+msgid "Force overwrite existing branch (may discard changes)"
 msgstr ""
 
-#: lib/branch_checkout.tcl:27 lib/branch_create.tcl:35
-#: lib/branch_delete.tcl:32 lib/branch_rename.tcl:30 lib/browser.tcl:282
-#: lib/checkout_op.tcl:579 lib/choose_font.tcl:43 lib/merge.tcl:172
-#: lib/option.tcl:125 lib/remote_add.tcl:32 lib/remote_branch_delete.tcl:42
-#: lib/tools_dlg.tcl:40 lib/tools_dlg.tcl:204 lib/tools_dlg.tcl:352
-#: lib/transport.tcl:108
-msgid "Cancel"
+#: lib/transport.tcl:211
+msgid "Use thin pack (for slow network connections)"
 msgstr ""
 
-#: lib/branch_checkout.tcl:32 lib/browser.tcl:287 lib/tools_dlg.tcl:328
-msgid "Revision"
+#: lib/transport.tcl:215
+msgid "Include tags"
 msgstr ""
 
-#: lib/branch_checkout.tcl:36 lib/branch_create.tcl:69 lib/option.tcl:280
-msgid "Options"
+#: lib/transport.tcl:229
+#, tcl-format
+msgid "%s (%s): Push"
 msgstr ""
 
-#: lib/branch_checkout.tcl:39 lib/branch_create.tcl:92
-msgid "Fetch Tracking Branch"
+#: lib/checkout_op.tcl:85
+#, tcl-format
+msgid "Fetching %s from %s"
 msgstr ""
 
-#: lib/branch_checkout.tcl:44
-msgid "Detach From Local Branch"
+#: lib/checkout_op.tcl:133
+#, tcl-format
+msgid "fatal: Cannot resolve %s"
 msgstr ""
 
-#: lib/branch_create.tcl:22
-msgid "Create Branch"
+#: lib/checkout_op.tcl:146 lib/sshkey.tcl:58 lib/console.tcl:81
+#: lib/database.tcl:30
+msgid "Close"
 msgstr ""
 
-#: lib/branch_create.tcl:27
-msgid "Create New Branch"
+#: lib/checkout_op.tcl:175
+#, tcl-format
+msgid "Branch '%s' does not exist."
 msgstr ""
 
-#: lib/branch_create.tcl:31 lib/choose_repository.tcl:381
-msgid "Create"
+#: lib/checkout_op.tcl:194
+#, tcl-format
+msgid "Failed to configure simplified git-pull for '%s'."
 msgstr ""
 
-#: lib/branch_create.tcl:40
-msgid "Branch Name"
+#: lib/checkout_op.tcl:202 lib/branch_rename.tcl:102
+#, tcl-format
+msgid "Branch '%s' already exists."
 msgstr ""
 
-#: lib/branch_create.tcl:43 lib/remote_add.tcl:39 lib/tools_dlg.tcl:50
-msgid "Name:"
+#: lib/checkout_op.tcl:229
+#, tcl-format
+msgid ""
+"Branch '%s' already exists.\n"
+"\n"
+"It cannot fast-forward to %s.\n"
+"A merge is required."
 msgstr ""
 
-#: lib/branch_create.tcl:58
-msgid "Match Tracking Branch Name"
+#: lib/checkout_op.tcl:243
+#, tcl-format
+msgid "Merge strategy '%s' not supported."
 msgstr ""
 
-#: lib/branch_create.tcl:66
-msgid "Starting Revision"
+#: lib/checkout_op.tcl:262
+#, tcl-format
+msgid "Failed to update '%s'."
 msgstr ""
 
-#: lib/branch_create.tcl:72
-msgid "Update Existing Branch:"
+#: lib/checkout_op.tcl:274
+msgid "Staging area (index) is already locked."
 msgstr ""
 
-#: lib/branch_create.tcl:75
-msgid "No"
+#: lib/checkout_op.tcl:289
+msgid ""
+"Last scanned state does not match repository state.\n"
+"\n"
+"Another Git program has modified this repository since the last scan.  A "
+"rescan must be performed before the current branch can be changed.\n"
+"\n"
+"The rescan will be automatically started now.\n"
 msgstr ""
 
-#: lib/branch_create.tcl:80
-msgid "Fast Forward Only"
+#: lib/checkout_op.tcl:345
+#, tcl-format
+msgid "Updating working directory to '%s'..."
 msgstr ""
 
-#: lib/branch_create.tcl:85 lib/checkout_op.tcl:571
-msgid "Reset"
+#: lib/checkout_op.tcl:346
+msgid "files checked out"
 msgstr ""
 
-#: lib/branch_create.tcl:97
-msgid "Checkout After Creation"
+#: lib/checkout_op.tcl:377
+#, tcl-format
+msgid "Aborted checkout of '%s' (file level merging is required)."
 msgstr ""
 
-#: lib/branch_create.tcl:131
-msgid "Please select a tracking branch."
+#: lib/checkout_op.tcl:378
+msgid "File level merge required."
 msgstr ""
 
-#: lib/branch_create.tcl:140
+#: lib/checkout_op.tcl:382
 #, tcl-format
-msgid "Tracking branch %s is not a branch in the remote repository."
+msgid "Staying on branch '%s'."
 msgstr ""
 
-#: lib/branch_create.tcl:153 lib/branch_rename.tcl:86
-msgid "Please supply a branch name."
+#: lib/checkout_op.tcl:453
+msgid ""
+"You are no longer on a local branch.\n"
+"\n"
+"If you wanted to be on a branch, create one now starting from 'This Detached "
+"Checkout'."
 msgstr ""
 
-#: lib/branch_create.tcl:164 lib/branch_rename.tcl:106
+#: lib/checkout_op.tcl:504 lib/checkout_op.tcl:508
 #, tcl-format
-msgid "'%s' is not an acceptable branch name."
+msgid "Checked out '%s'."
 msgstr ""
 
-#: lib/branch_delete.tcl:15
-msgid "Delete Branch"
+#: lib/checkout_op.tcl:536
+#, tcl-format
+msgid "Resetting '%s' to '%s' will lose the following commits:"
 msgstr ""
 
-#: lib/branch_delete.tcl:20
-msgid "Delete Local Branch"
+#: lib/checkout_op.tcl:558
+msgid "Recovering lost commits may not be easy."
 msgstr ""
 
-#: lib/branch_delete.tcl:37
-msgid "Local Branches"
+#: lib/checkout_op.tcl:563
+#, tcl-format
+msgid "Reset '%s'?"
 msgstr ""
 
-#: lib/branch_delete.tcl:52
-msgid "Delete Only If Merged Into"
+#: lib/checkout_op.tcl:568 lib/tools_dlg.tcl:336 lib/merge.tcl:170
+msgid "Visualize"
 msgstr ""
 
-#: lib/branch_delete.tcl:54 lib/remote_branch_delete.tcl:119
-msgid "Always (Do not perform merge checks)"
+#: lib/checkout_op.tcl:572 lib/branch_create.tcl:85
+msgid "Reset"
 msgstr ""
 
-#: lib/branch_delete.tcl:103
+#: lib/checkout_op.tcl:636
 #, tcl-format
-msgid "The following branches are not completely merged into %s:"
-msgstr ""
-
-#: lib/branch_delete.tcl:115 lib/remote_branch_delete.tcl:217
 msgid ""
-"Recovering deleted branches is difficult.\n"
+"Failed to set current branch.\n"
 "\n"
-"Delete the selected branches?"
+"This working directory is only partially switched.  We successfully updated "
+"your files, but failed to update an internal Git file.\n"
+"\n"
+"This should not have occurred.  %s will now close and give up."
 msgstr ""
 
-#: lib/branch_delete.tcl:141
+#: lib/remote_add.tcl:20
 #, tcl-format
-msgid ""
-"Failed to delete branches:\n"
-"%s"
+msgid "%s (%s): Add Remote"
 msgstr ""
 
-#: lib/branch_rename.tcl:14 lib/branch_rename.tcl:22
-msgid "Rename Branch"
+#: lib/remote_add.tcl:25
+msgid "Add New Remote"
 msgstr ""
 
-#: lib/branch_rename.tcl:26
-msgid "Rename"
+#: lib/remote_add.tcl:30 lib/tools_dlg.tcl:37
+msgid "Add"
 msgstr ""
 
-#: lib/branch_rename.tcl:36
-msgid "Branch:"
+#: lib/remote_add.tcl:39
+msgid "Remote Details"
 msgstr ""
 
-#: lib/branch_rename.tcl:39
-msgid "New Name:"
+#: lib/remote_add.tcl:41 lib/tools_dlg.tcl:51 lib/branch_create.tcl:44
+msgid "Name:"
 msgstr ""
 
-#: lib/branch_rename.tcl:75
-msgid "Please select a branch to rename."
+#: lib/remote_add.tcl:50
+msgid "Location:"
 msgstr ""
 
-#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:202
-#, tcl-format
-msgid "Branch '%s' already exists."
+#: lib/remote_add.tcl:60
+msgid "Further Action"
 msgstr ""
 
-#: lib/branch_rename.tcl:117
-#, tcl-format
-msgid "Failed to rename '%s'."
+#: lib/remote_add.tcl:63
+msgid "Fetch Immediately"
 msgstr ""
 
-#: lib/browser.tcl:17
-msgid "Starting..."
+#: lib/remote_add.tcl:69
+msgid "Initialize Remote Repository and Push"
 msgstr ""
 
-#: lib/browser.tcl:26
-msgid "File Browser"
+#: lib/remote_add.tcl:75
+msgid "Do Nothing Else Now"
 msgstr ""
 
-#: lib/browser.tcl:126 lib/browser.tcl:143
-#, tcl-format
-msgid "Loading %s..."
+#: lib/remote_add.tcl:100
+msgid "Please supply a remote name."
 msgstr ""
 
-#: lib/browser.tcl:187
-msgid "[Up To Parent]"
+#: lib/remote_add.tcl:113
+#, tcl-format
+msgid "'%s' is not an acceptable remote name."
 msgstr ""
 
-#: lib/browser.tcl:267 lib/browser.tcl:273
-msgid "Browse Branch Files"
+#: lib/remote_add.tcl:124
+#, tcl-format
+msgid "Failed to add remote '%s' of location '%s'."
 msgstr ""
 
-#: lib/browser.tcl:278 lib/choose_repository.tcl:398
-#: lib/choose_repository.tcl:486 lib/choose_repository.tcl:497
-#: lib/choose_repository.tcl:1028
-msgid "Browse"
+#: lib/remote_add.tcl:133
+#, tcl-format
+msgid "Fetching the %s"
 msgstr ""
 
-#: lib/checkout_op.tcl:85
+#: lib/remote_add.tcl:156
 #, tcl-format
-msgid "Fetching %s from %s"
+msgid "Do not know how to initialize repository at location '%s'."
 msgstr ""
 
-#: lib/checkout_op.tcl:133
+#: lib/remote_add.tcl:163
 #, tcl-format
-msgid "fatal: Cannot resolve %s"
+msgid "Setting up the %s (at %s)"
 msgstr ""
 
-#: lib/checkout_op.tcl:146 lib/console.tcl:81 lib/database.tcl:31
-#: lib/sshkey.tcl:53
-msgid "Close"
+#: lib/browser.tcl:17
+msgid "Starting..."
 msgstr ""
 
-#: lib/checkout_op.tcl:175
+#: lib/browser.tcl:27
 #, tcl-format
-msgid "Branch '%s' does not exist."
+msgid "%s (%s): File Browser"
 msgstr ""
 
-#: lib/checkout_op.tcl:194
+#: lib/browser.tcl:132 lib/browser.tcl:149
 #, tcl-format
-msgid "Failed to configure simplified git-pull for '%s'."
+msgid "Loading %s..."
 msgstr ""
 
-#: lib/checkout_op.tcl:229
-#, tcl-format
-msgid ""
-"Branch '%s' already exists.\n"
-"\n"
-"It cannot fast-forward to %s.\n"
-"A merge is required."
+#: lib/browser.tcl:193
+msgid "[Up To Parent]"
 msgstr ""
 
-#: lib/checkout_op.tcl:243
+#: lib/browser.tcl:275
 #, tcl-format
-msgid "Merge strategy '%s' not supported."
+msgid "%s (%s): Browse Branch Files"
 msgstr ""
 
-#: lib/checkout_op.tcl:262
-#, tcl-format
-msgid "Failed to update '%s'."
+#: lib/browser.tcl:282
+msgid "Browse Branch Files"
 msgstr ""
 
-#: lib/checkout_op.tcl:274
-msgid "Staging area (index) is already locked."
+#: lib/browser.tcl:288 lib/choose_repository.tcl:437
+#: lib/choose_repository.tcl:524 lib/choose_repository.tcl:533
+#: lib/choose_repository.tcl:1115
+msgid "Browse"
 msgstr ""
 
-#: lib/checkout_op.tcl:289
-msgid ""
-"Last scanned state does not match repository state.\n"
-"\n"
-"Another Git program has modified this repository since the last scan.  A "
-"rescan must be performed before the current branch can be changed.\n"
-"\n"
-"The rescan will be automatically started now.\n"
+#: lib/browser.tcl:297 lib/branch_checkout.tcl:35 lib/tools_dlg.tcl:321
+msgid "Revision"
 msgstr ""
 
-#: lib/checkout_op.tcl:345
-#, tcl-format
-msgid "Updating working directory to '%s'..."
+#: lib/index.tcl:6
+msgid "Unable to unlock the index."
 msgstr ""
 
-#: lib/checkout_op.tcl:346
-msgid "files checked out"
+#: lib/index.tcl:30
+msgid "Index Error"
 msgstr ""
 
-#: lib/checkout_op.tcl:376
-#, tcl-format
-msgid "Aborted checkout of '%s' (file level merging is required)."
+#: lib/index.tcl:32
+msgid ""
+"Updating the Git index failed.  A rescan will be automatically started to "
+"resynchronize git-gui."
 msgstr ""
 
-#: lib/checkout_op.tcl:377
-msgid "File level merge required."
+#: lib/index.tcl:43
+msgid "Continue"
 msgstr ""
 
-#: lib/checkout_op.tcl:381
-#, tcl-format
-msgid "Staying on branch '%s'."
+#: lib/index.tcl:46
+msgid "Unlock Index"
 msgstr ""
 
-#: lib/checkout_op.tcl:452
-msgid ""
-"You are no longer on a local branch.\n"
-"\n"
-"If you wanted to be on a branch, create one now starting from 'This Detached "
-"Checkout'."
+#: lib/index.tcl:77 lib/index.tcl:146 lib/index.tcl:220 lib/index.tcl:587
+#: lib/choose_repository.tcl:999
+msgid "files"
 msgstr ""
 
-#: lib/checkout_op.tcl:503 lib/checkout_op.tcl:507
-#, tcl-format
-msgid "Checked out '%s'."
+#: lib/index.tcl:326
+msgid "Unstaging selected files from commit"
 msgstr ""
 
-#: lib/checkout_op.tcl:535
+#: lib/index.tcl:330
 #, tcl-format
-msgid "Resetting '%s' to '%s' will lose the following commits:"
+msgid "Unstaging %s from commit"
 msgstr ""
 
-#: lib/checkout_op.tcl:557
-msgid "Recovering lost commits may not be easy."
+#: lib/index.tcl:369
+msgid "Ready to commit."
 msgstr ""
 
-#: lib/checkout_op.tcl:562
-#, tcl-format
-msgid "Reset '%s'?"
+#: lib/index.tcl:378
+msgid "Adding selected files"
 msgstr ""
 
-#: lib/checkout_op.tcl:567 lib/merge.tcl:164 lib/tools_dlg.tcl:343
-msgid "Visualize"
+#: lib/index.tcl:382
+#, tcl-format
+msgid "Adding %s"
 msgstr ""
 
-#: lib/checkout_op.tcl:635
+#: lib/index.tcl:412
 #, tcl-format
-msgid ""
-"Failed to set current branch.\n"
-"\n"
-"This working directory is only partially switched.  We successfully updated "
-"your files, but failed to update an internal Git file.\n"
-"\n"
-"This should not have occurred.  %s will now close and give up."
+msgid "Stage %d untracked files?"
 msgstr ""
 
-#: lib/choose_font.tcl:39
-msgid "Select"
+#: lib/index.tcl:420
+msgid "Adding all changed files"
 msgstr ""
 
-#: lib/choose_font.tcl:53
-msgid "Font Family"
+#: lib/index.tcl:503
+#, tcl-format
+msgid "Revert changes in file %s?"
 msgstr ""
 
-#: lib/choose_font.tcl:74
-msgid "Font Size"
+#: lib/index.tcl:508
+#, tcl-format
+msgid "Revert changes in these %i files?"
 msgstr ""
 
-#: lib/choose_font.tcl:91
-msgid "Font Example"
+#: lib/index.tcl:517
+msgid "Any unstaged changes will be permanently lost by the revert."
 msgstr ""
 
-#: lib/choose_font.tcl:103
-msgid ""
-"This is example text.\n"
-"If you like this text, it can be your font."
+#: lib/index.tcl:520 lib/index.tcl:563
+msgid "Do Nothing"
 msgstr ""
 
-#: lib/choose_repository.tcl:28
-msgid "Git Gui"
+#: lib/index.tcl:545
+#, tcl-format
+msgid "Delete untracked file %s?"
 msgstr ""
 
-#: lib/choose_repository.tcl:87 lib/choose_repository.tcl:386
-msgid "Create New Repository"
+#: lib/index.tcl:550
+#, tcl-format
+msgid "Delete these %i untracked files?"
 msgstr ""
 
-#: lib/choose_repository.tcl:93
-msgid "New..."
+#: lib/index.tcl:560
+msgid "Files will be permanently deleted."
 msgstr ""
 
-#: lib/choose_repository.tcl:100 lib/choose_repository.tcl:471
-msgid "Clone Existing Repository"
+#: lib/index.tcl:564
+msgid "Delete Files"
 msgstr ""
 
-#: lib/choose_repository.tcl:106
-msgid "Clone..."
+#: lib/index.tcl:586
+msgid "Deleting"
 msgstr ""
 
-#: lib/choose_repository.tcl:113 lib/choose_repository.tcl:1016
-msgid "Open Existing Repository"
+#: lib/index.tcl:665
+msgid "Encountered errors deleting files:\n"
 msgstr ""
 
-#: lib/choose_repository.tcl:119
-msgid "Open..."
+#: lib/index.tcl:674
+#, tcl-format
+msgid "None of the %d selected files could be deleted."
 msgstr ""
 
-#: lib/choose_repository.tcl:132
-msgid "Recent Repositories"
+#: lib/index.tcl:679
+#, tcl-format
+msgid "%d of the %d selected files could not be deleted."
 msgstr ""
 
-#: lib/choose_repository.tcl:138
-msgid "Open Recent Repository:"
+#: lib/index.tcl:726
+msgid "Reverting selected files"
 msgstr ""
 
-#: lib/choose_repository.tcl:306 lib/choose_repository.tcl:313
-#: lib/choose_repository.tcl:320
+#: lib/index.tcl:730
 #, tcl-format
-msgid "Failed to create repository %s:"
+msgid "Reverting %s"
 msgstr ""
 
-#: lib/choose_repository.tcl:391
-msgid "Directory:"
+#: lib/branch_checkout.tcl:16
+#, tcl-format
+msgid "%s (%s): Checkout Branch"
 msgstr ""
 
-#: lib/choose_repository.tcl:423 lib/choose_repository.tcl:550
-#: lib/choose_repository.tcl:1052
-msgid "Git Repository"
+#: lib/branch_checkout.tcl:21
+msgid "Checkout Branch"
 msgstr ""
 
-#: lib/choose_repository.tcl:448
-#, tcl-format
-msgid "Directory %s already exists."
+#: lib/branch_checkout.tcl:26
+msgid "Checkout"
 msgstr ""
 
-#: lib/choose_repository.tcl:452
-#, tcl-format
-msgid "File %s already exists."
+#: lib/branch_checkout.tcl:39 lib/option.tcl:310 lib/branch_create.tcl:69
+msgid "Options"
 msgstr ""
 
-#: lib/choose_repository.tcl:466
-msgid "Clone"
+#: lib/branch_checkout.tcl:42 lib/branch_create.tcl:92
+msgid "Fetch Tracking Branch"
 msgstr ""
 
-#: lib/choose_repository.tcl:479
-msgid "Source Location:"
+#: lib/branch_checkout.tcl:47
+msgid "Detach From Local Branch"
 msgstr ""
 
-#: lib/choose_repository.tcl:490
-msgid "Target Directory:"
+#: lib/status_bar.tcl:263
+#, tcl-format
+msgid "%s ... %*i of %*i %s (%3i%%)"
 msgstr ""
 
-#: lib/choose_repository.tcl:502
-msgid "Clone Type:"
+#: lib/remote.tcl:200
+msgid "Push to"
 msgstr ""
 
-#: lib/choose_repository.tcl:508
-msgid "Standard (Fast, Semi-Redundant, Hardlinks)"
+#: lib/remote.tcl:218
+msgid "Remove Remote"
 msgstr ""
 
-#: lib/choose_repository.tcl:514
-msgid "Full Copy (Slower, Redundant Backup)"
+#: lib/remote.tcl:223
+msgid "Prune from"
 msgstr ""
 
-#: lib/choose_repository.tcl:520
-msgid "Shared (Fastest, Not Recommended, No Backup)"
+#: lib/remote.tcl:228
+msgid "Fetch from"
+msgstr ""
+
+#: lib/remote.tcl:249 lib/remote.tcl:253 lib/remote.tcl:258 lib/remote.tcl:264
+msgid "All"
 msgstr ""
 
-#: lib/choose_repository.tcl:556 lib/choose_repository.tcl:603
-#: lib/choose_repository.tcl:749 lib/choose_repository.tcl:819
-#: lib/choose_repository.tcl:1058 lib/choose_repository.tcl:1066
+#: lib/branch_rename.tcl:15
 #, tcl-format
-msgid "Not a Git repository: %s"
+msgid "%s (%s): Rename Branch"
 msgstr ""
 
-#: lib/choose_repository.tcl:592
-msgid "Standard only available for local repository."
+#: lib/branch_rename.tcl:23
+msgid "Rename Branch"
 msgstr ""
 
-#: lib/choose_repository.tcl:596
-msgid "Shared only available for local repository."
+#: lib/branch_rename.tcl:28
+msgid "Rename"
 msgstr ""
 
-#: lib/choose_repository.tcl:617
-#, tcl-format
-msgid "Location %s already exists."
+#: lib/branch_rename.tcl:38
+msgid "Branch:"
 msgstr ""
 
-#: lib/choose_repository.tcl:628
-msgid "Failed to configure origin"
+#: lib/branch_rename.tcl:46
+msgid "New Name:"
 msgstr ""
 
-#: lib/choose_repository.tcl:640
-msgid "Counting objects"
+#: lib/branch_rename.tcl:81
+msgid "Please select a branch to rename."
 msgstr ""
 
-#: lib/choose_repository.tcl:641
-msgid "buckets"
+#: lib/branch_rename.tcl:92 lib/branch_create.tcl:154
+msgid "Please supply a branch name."
 msgstr ""
 
-#: lib/choose_repository.tcl:665
+#: lib/branch_rename.tcl:112 lib/branch_create.tcl:165
 #, tcl-format
-msgid "Unable to copy objects/info/alternates: %s"
+msgid "'%s' is not an acceptable branch name."
 msgstr ""
 
-#: lib/choose_repository.tcl:701
+#: lib/branch_rename.tcl:123
 #, tcl-format
-msgid "Nothing to clone from %s."
+msgid "Failed to rename '%s'."
 msgstr ""
 
-#: lib/choose_repository.tcl:703 lib/choose_repository.tcl:917
-#: lib/choose_repository.tcl:929
-msgid "The 'master' branch has not been initialized."
+#: lib/choose_font.tcl:41
+msgid "Select"
 msgstr ""
 
-#: lib/choose_repository.tcl:716
-msgid "Hardlinks are unavailable.  Falling back to copying."
+#: lib/choose_font.tcl:55
+msgid "Font Family"
 msgstr ""
 
-#: lib/choose_repository.tcl:728
-#, tcl-format
-msgid "Cloning from %s"
+#: lib/choose_font.tcl:76
+msgid "Font Size"
 msgstr ""
 
-#: lib/choose_repository.tcl:759
-msgid "Copying objects"
+#: lib/choose_font.tcl:93
+msgid "Font Example"
 msgstr ""
 
-#: lib/choose_repository.tcl:760
-msgid "KiB"
+#: lib/choose_font.tcl:105
+msgid ""
+"This is example text.\n"
+"If you like this text, it can be your font."
 msgstr ""
 
-#: lib/choose_repository.tcl:784
+#: lib/option.tcl:11
 #, tcl-format
-msgid "Unable to copy object: %s"
+msgid "Invalid global encoding '%s'"
 msgstr ""
 
-#: lib/choose_repository.tcl:794
-msgid "Linking objects"
+#: lib/option.tcl:19
+#, tcl-format
+msgid "Invalid repo encoding '%s'"
 msgstr ""
 
-#: lib/choose_repository.tcl:795
-msgid "objects"
+#: lib/option.tcl:119
+msgid "Restore Defaults"
+msgstr ""
+
+#: lib/option.tcl:123
+msgid "Save"
 msgstr ""
 
-#: lib/choose_repository.tcl:803
+#: lib/option.tcl:133
 #, tcl-format
-msgid "Unable to hardlink object: %s"
+msgid "%s Repository"
 msgstr ""
 
-#: lib/choose_repository.tcl:858
-msgid "Cannot fetch branches and objects.  See console output for details."
+#: lib/option.tcl:134
+msgid "Global (All Repositories)"
 msgstr ""
 
-#: lib/choose_repository.tcl:869
-msgid "Cannot fetch tags.  See console output for details."
+#: lib/option.tcl:140
+msgid "User Name"
 msgstr ""
 
-#: lib/choose_repository.tcl:893
-msgid "Cannot determine HEAD.  See console output for details."
+#: lib/option.tcl:141
+msgid "Email Address"
 msgstr ""
 
-#: lib/choose_repository.tcl:902
-#, tcl-format
-msgid "Unable to cleanup %s"
+#: lib/option.tcl:143
+msgid "Summarize Merge Commits"
 msgstr ""
 
-#: lib/choose_repository.tcl:908
-msgid "Clone failed."
+#: lib/option.tcl:144
+msgid "Merge Verbosity"
 msgstr ""
 
-#: lib/choose_repository.tcl:915
-msgid "No default branch obtained."
+#: lib/option.tcl:145
+msgid "Show Diffstat After Merge"
 msgstr ""
 
-#: lib/choose_repository.tcl:926
-#, tcl-format
-msgid "Cannot resolve %s as a commit."
+#: lib/option.tcl:146
+msgid "Use Merge Tool"
 msgstr ""
 
-#: lib/choose_repository.tcl:938
-msgid "Creating working directory"
+#: lib/option.tcl:148
+msgid "Trust File Modification Timestamps"
 msgstr ""
 
-#: lib/choose_repository.tcl:939 lib/index.tcl:67 lib/index.tcl:130
-#: lib/index.tcl:198
-msgid "files"
+#: lib/option.tcl:149
+msgid "Prune Tracking Branches During Fetch"
 msgstr ""
 
-#: lib/choose_repository.tcl:968
-msgid "Initial file checkout failed."
+#: lib/option.tcl:150
+msgid "Match Tracking Branches"
 msgstr ""
 
-#: lib/choose_repository.tcl:1011
-msgid "Open"
+#: lib/option.tcl:151
+msgid "Use Textconv For Diffs and Blames"
 msgstr ""
 
-#: lib/choose_repository.tcl:1021
-msgid "Repository:"
+#: lib/option.tcl:152
+msgid "Blame Copy Only On Changed Files"
 msgstr ""
 
-#: lib/choose_repository.tcl:1072
-#, tcl-format
-msgid "Failed to open repository %s:"
+#: lib/option.tcl:153
+msgid "Maximum Length of Recent Repositories List"
 msgstr ""
 
-#: lib/choose_rev.tcl:53
-msgid "This Detached Checkout"
+#: lib/option.tcl:154
+msgid "Minimum Letters To Blame Copy On"
 msgstr ""
 
-#: lib/choose_rev.tcl:60
-msgid "Revision Expression:"
+#: lib/option.tcl:155
+msgid "Blame History Context Radius (days)"
 msgstr ""
 
-#: lib/choose_rev.tcl:74
-msgid "Local Branch"
+#: lib/option.tcl:156
+msgid "Number of Diff Context Lines"
 msgstr ""
 
-#: lib/choose_rev.tcl:79
-msgid "Tracking Branch"
+#: lib/option.tcl:157
+msgid "Additional Diff Parameters"
 msgstr ""
 
-#: lib/choose_rev.tcl:84 lib/choose_rev.tcl:538
-msgid "Tag"
+#: lib/option.tcl:158
+msgid "Commit Message Text Width"
 msgstr ""
 
-#: lib/choose_rev.tcl:317
-#, tcl-format
-msgid "Invalid revision: %s"
+#: lib/option.tcl:159
+msgid "New Branch Name Template"
 msgstr ""
 
-#: lib/choose_rev.tcl:338
-msgid "No revision selected."
+#: lib/option.tcl:160
+msgid "Default File Contents Encoding"
 msgstr ""
 
-#: lib/choose_rev.tcl:346
-msgid "Revision expression is empty."
+#: lib/option.tcl:161
+msgid "Warn before committing to a detached head"
 msgstr ""
 
-#: lib/choose_rev.tcl:531
-msgid "Updated"
+#: lib/option.tcl:162
+msgid "Staging of untracked files"
 msgstr ""
 
-#: lib/choose_rev.tcl:559
-msgid "URL"
+#: lib/option.tcl:163
+msgid "Show untracked files"
 msgstr ""
 
-#: lib/commit.tcl:9
-msgid ""
-"There is nothing to amend.\n"
-"\n"
-"You are about to create the initial commit.  There is no commit before this "
-"to amend.\n"
+#: lib/option.tcl:164
+msgid "Tab spacing"
 msgstr ""
 
-#: lib/commit.tcl:18
-msgid ""
-"Cannot amend while merging.\n"
-"\n"
-"You are currently in the middle of a merge that has not been fully "
-"completed.  You cannot amend the prior commit unless you first abort the "
-"current merge activity.\n"
+#: lib/option.tcl:182 lib/option.tcl:197 lib/option.tcl:220 lib/option.tcl:282
+#: lib/database.tcl:57
+#, tcl-format
+msgid "%s:"
 msgstr ""
 
-#: lib/commit.tcl:48
-msgid "Error loading commit data for amend:"
+#: lib/option.tcl:210
+msgid "Change"
 msgstr ""
 
-#: lib/commit.tcl:75
-msgid "Unable to obtain your identity:"
+#: lib/option.tcl:254
+msgid "Spelling Dictionary:"
 msgstr ""
 
-#: lib/commit.tcl:80
-msgid "Invalid GIT_COMMITTER_IDENT:"
+#: lib/option.tcl:284
+msgid "Change Font"
 msgstr ""
 
-#: lib/commit.tcl:129
+#: lib/option.tcl:288
 #, tcl-format
-msgid "warning: Tcl does not support encoding '%s'."
+msgid "Choose %s"
 msgstr ""
 
-#: lib/commit.tcl:149
-msgid ""
-"Last scanned state does not match repository state.\n"
-"\n"
-"Another Git program has modified this repository since the last scan.  A "
-"rescan must be performed before another commit can be created.\n"
-"\n"
-"The rescan will be automatically started now.\n"
+#: lib/option.tcl:294
+msgid "pt."
 msgstr ""
 
-#: lib/commit.tcl:172
+#: lib/option.tcl:308
+msgid "Preferences"
+msgstr ""
+
+#: lib/option.tcl:345
+msgid "Failed to completely save options:"
+msgstr ""
+
+#: lib/encoding.tcl:443
+msgid "Default"
+msgstr ""
+
+#: lib/encoding.tcl:448
 #, tcl-format
-msgid ""
-"Unmerged files cannot be committed.\n"
-"\n"
-"File %s has merge conflicts.  You must resolve them and stage the file "
-"before committing.\n"
+msgid "System (%s)"
+msgstr ""
+
+#: lib/encoding.tcl:459 lib/encoding.tcl:465
+msgid "Other"
 msgstr ""
 
-#: lib/commit.tcl:180
+#: lib/tools.tcl:76
 #, tcl-format
-msgid ""
-"Unknown file state %s detected.\n"
-"\n"
-"File %s cannot be committed by this program.\n"
+msgid "Running %s requires a selected file."
 msgstr ""
 
-#: lib/commit.tcl:188
-msgid ""
-"No changes to commit.\n"
-"\n"
-"You must stage at least 1 file before you can commit.\n"
+#: lib/tools.tcl:92
+#, tcl-format
+msgid "Are you sure you want to run %1$s on file \"%2$s\"?"
+msgstr ""
+
+#: lib/tools.tcl:96
+#, tcl-format
+msgid "Are you sure you want to run %s?"
+msgstr ""
+
+#: lib/tools.tcl:118
+#, tcl-format
+msgid "Tool: %s"
 msgstr ""
 
-#: lib/commit.tcl:203
+#: lib/tools.tcl:119
+#, tcl-format
+msgid "Running: %s"
+msgstr ""
+
+#: lib/tools.tcl:158
+#, tcl-format
+msgid "Tool completed successfully: %s"
+msgstr ""
+
+#: lib/tools.tcl:160
+#, tcl-format
+msgid "Tool failed: %s"
+msgstr ""
+
+#: lib/mergetool.tcl:8
+msgid "Force resolution to the base version?"
+msgstr ""
+
+#: lib/mergetool.tcl:9
+msgid "Force resolution to this branch?"
+msgstr ""
+
+#: lib/mergetool.tcl:10
+msgid "Force resolution to the other branch?"
+msgstr ""
+
+#: lib/mergetool.tcl:14
+#, tcl-format
 msgid ""
-"Please supply a commit message.\n"
+"Note that the diff shows only conflicting changes.\n"
 "\n"
-"A good commit message has the following format:\n"
+"%s will be overwritten.\n"
 "\n"
-"- First line: Describe in one sentence what you did.\n"
-"- Second line: Blank\n"
-"- Remaining lines: Describe why this change is good.\n"
+"This operation can be undone only by restarting the merge."
 msgstr ""
 
-#: lib/commit.tcl:234
-msgid "Calling pre-commit hook..."
+#: lib/mergetool.tcl:45
+#, tcl-format
+msgid "File %s seems to have unresolved conflicts, still stage?"
 msgstr ""
 
-#: lib/commit.tcl:249
-msgid "Commit declined by pre-commit hook."
+#: lib/mergetool.tcl:60
+#, tcl-format
+msgid "Adding resolution for %s"
 msgstr ""
 
-#: lib/commit.tcl:272
-msgid "Calling commit-msg hook..."
+#: lib/mergetool.tcl:141
+msgid "Cannot resolve deletion or link conflicts using a tool"
 msgstr ""
 
-#: lib/commit.tcl:287
-msgid "Commit declined by commit-msg hook."
+#: lib/mergetool.tcl:146
+msgid "Conflict file does not exist"
 msgstr ""
 
-#: lib/commit.tcl:300
-msgid "Committing changes..."
+#: lib/mergetool.tcl:246
+#, tcl-format
+msgid "Not a GUI merge tool: '%s'"
 msgstr ""
 
-#: lib/commit.tcl:316
-msgid "write-tree failed:"
+#: lib/mergetool.tcl:275
+#, tcl-format
+msgid "Unsupported merge tool '%s'"
 msgstr ""
 
-#: lib/commit.tcl:317 lib/commit.tcl:361 lib/commit.tcl:382
-msgid "Commit failed."
+#: lib/mergetool.tcl:310
+msgid "Merge tool is already running, terminate it?"
 msgstr ""
 
-#: lib/commit.tcl:334
+#: lib/mergetool.tcl:330
 #, tcl-format
-msgid "Commit %s appears to be corrupt"
+msgid ""
+"Error retrieving versions:\n"
+"%s"
 msgstr ""
 
-#: lib/commit.tcl:339
+#: lib/mergetool.tcl:350
+#, tcl-format
 msgid ""
-"No changes to commit.\n"
-"\n"
-"No files were modified by this commit and it was not a merge commit.\n"
+"Could not start the merge tool:\n"
 "\n"
-"A rescan will be automatically started now.\n"
-msgstr ""
-
-#: lib/commit.tcl:346
-msgid "No changes to commit."
+"%s"
 msgstr ""
 
-#: lib/commit.tcl:360
-msgid "commit-tree failed:"
+#: lib/mergetool.tcl:354
+msgid "Running merge tool..."
 msgstr ""
 
-#: lib/commit.tcl:381
-msgid "update-ref failed:"
+#: lib/mergetool.tcl:382 lib/mergetool.tcl:390
+msgid "Merge tool failed."
 msgstr ""
 
-#: lib/commit.tcl:469
+#: lib/tools_dlg.tcl:22
 #, tcl-format
-msgid "Created commit %s: %s"
+msgid "%s (%s): Add Tool"
 msgstr ""
 
-#: lib/console.tcl:59
-msgid "Working... please wait..."
+#: lib/tools_dlg.tcl:28
+msgid "Add New Tool Command"
 msgstr ""
 
-#: lib/console.tcl:186
-msgid "Success"
+#: lib/tools_dlg.tcl:34
+msgid "Add globally"
 msgstr ""
 
-#: lib/console.tcl:200
-msgid "Error: Command Failed"
+#: lib/tools_dlg.tcl:46
+msgid "Tool Details"
 msgstr ""
 
-#: lib/database.tcl:43
-msgid "Number of loose objects"
+#: lib/tools_dlg.tcl:49
+msgid "Use '/' separators to create a submenu tree:"
 msgstr ""
 
-#: lib/database.tcl:44
-msgid "Disk space used by loose objects"
+#: lib/tools_dlg.tcl:60
+msgid "Command:"
 msgstr ""
 
-#: lib/database.tcl:45
-msgid "Number of packed objects"
+#: lib/tools_dlg.tcl:71
+msgid "Show a dialog before running"
 msgstr ""
 
-#: lib/database.tcl:46
-msgid "Number of packs"
+#: lib/tools_dlg.tcl:77
+msgid "Ask the user to select a revision (sets $REVISION)"
 msgstr ""
 
-#: lib/database.tcl:47
-msgid "Disk space used by packed objects"
+#: lib/tools_dlg.tcl:82
+msgid "Ask the user for additional arguments (sets $ARGS)"
 msgstr ""
 
-#: lib/database.tcl:48
-msgid "Packed objects waiting for pruning"
+#: lib/tools_dlg.tcl:89
+msgid "Don't show the command output window"
 msgstr ""
 
-#: lib/database.tcl:49
-msgid "Garbage files"
+#: lib/tools_dlg.tcl:94
+msgid "Run only if a diff is selected ($FILENAME not empty)"
 msgstr ""
 
-#: lib/database.tcl:72
-msgid "Compressing the object database"
+#: lib/tools_dlg.tcl:118
+msgid "Please supply a name for the tool."
 msgstr ""
 
-#: lib/database.tcl:83
-msgid "Verifying the object database with fsck-objects"
+#: lib/tools_dlg.tcl:126
+#, tcl-format
+msgid "Tool '%s' already exists."
 msgstr ""
 
-#: lib/database.tcl:107
+#: lib/tools_dlg.tcl:148
 #, tcl-format
 msgid ""
-"This repository currently has approximately %i loose objects.\n"
-"\n"
-"To maintain optimal performance it is strongly recommended that you compress "
-"the database.\n"
-"\n"
-"Compress the database now?"
+"Could not add tool:\n"
+"%s"
 msgstr ""
 
-#: lib/date.tcl:25
+#: lib/tools_dlg.tcl:187
 #, tcl-format
-msgid "Invalid date from Git: %s"
+msgid "%s (%s): Remove Tool"
+msgstr ""
+
+#: lib/tools_dlg.tcl:193
+msgid "Remove Tool Commands"
+msgstr ""
+
+#: lib/tools_dlg.tcl:198
+msgid "Remove"
 msgstr ""
 
-#: lib/diff.tcl:64
+#: lib/tools_dlg.tcl:231
+msgid "(Blue denotes repository-local tools)"
+msgstr ""
+
+#: lib/tools_dlg.tcl:283
 #, tcl-format
-msgid ""
-"No differences detected.\n"
-"\n"
-"%s has no changes.\n"
-"\n"
-"The modification date of this file was updated by another application, but "
-"the content within the file was not changed.\n"
-"\n"
-"A rescan will be automatically started to find other files which may have "
-"the same state."
+msgid "%s (%s):"
 msgstr ""
 
-#: lib/diff.tcl:104
+#: lib/tools_dlg.tcl:292
 #, tcl-format
-msgid "Loading diff of %s..."
+msgid "Run Command: %s"
 msgstr ""
 
-#: lib/diff.tcl:125
-msgid ""
-"LOCAL: deleted\n"
-"REMOTE:\n"
+#: lib/tools_dlg.tcl:306
+msgid "Arguments"
 msgstr ""
 
-#: lib/diff.tcl:130
-msgid ""
-"REMOTE: deleted\n"
-"LOCAL:\n"
+#: lib/tools_dlg.tcl:341
+msgid "OK"
 msgstr ""
 
-#: lib/diff.tcl:137
-msgid "LOCAL:\n"
+#: lib/search.tcl:48
+msgid "Find:"
 msgstr ""
 
-#: lib/diff.tcl:140
-msgid "REMOTE:\n"
+#: lib/search.tcl:50
+msgid "Next"
 msgstr ""
 
-#: lib/diff.tcl:202 lib/diff.tcl:319
-#, tcl-format
-msgid "Unable to display %s"
+#: lib/search.tcl:51
+msgid "Prev"
 msgstr ""
 
-#: lib/diff.tcl:203
-msgid "Error loading file:"
+#: lib/search.tcl:52
+msgid "RegExp"
 msgstr ""
 
-#: lib/diff.tcl:210
-msgid "Git Repository (subproject)"
+#: lib/search.tcl:54
+msgid "Case"
 msgstr ""
 
-#: lib/diff.tcl:222
-msgid "* Binary file (not showing content)."
+#: lib/shortcut.tcl:8 lib/shortcut.tcl:43 lib/shortcut.tcl:75
+#, tcl-format
+msgid "%s (%s): Create Desktop Icon"
 msgstr ""
 
-#: lib/diff.tcl:227
+#: lib/shortcut.tcl:24 lib/shortcut.tcl:65
+msgid "Cannot write shortcut:"
+msgstr ""
+
+#: lib/shortcut.tcl:140
+msgid "Cannot write icon:"
+msgstr ""
+
+#: lib/remote_branch_delete.tcl:29
 #, tcl-format
-msgid ""
-"* Untracked file is %d bytes.\n"
-"* Showing only first %d bytes.\n"
+msgid "%s (%s): Delete Branch Remotely"
+msgstr ""
+
+#: lib/remote_branch_delete.tcl:34
+msgid "Delete Branch Remotely"
+msgstr ""
+
+#: lib/remote_branch_delete.tcl:48
+msgid "From Repository"
 msgstr ""
 
-#: lib/diff.tcl:233
+#: lib/remote_branch_delete.tcl:88
+msgid "Branches"
+msgstr ""
+
+#: lib/remote_branch_delete.tcl:110
+msgid "Delete Only If"
+msgstr ""
+
+#: lib/remote_branch_delete.tcl:112
+msgid "Merged Into:"
+msgstr ""
+
+#: lib/remote_branch_delete.tcl:120 lib/branch_delete.tcl:53
+msgid "Always (Do not perform merge checks)"
+msgstr ""
+
+#: lib/remote_branch_delete.tcl:153
+msgid "A branch is required for 'Merged Into'."
+msgstr ""
+
+#: lib/remote_branch_delete.tcl:185
 #, tcl-format
 msgid ""
+"The following branches are not completely merged into %s:\n"
 "\n"
-"* Untracked file clipped here by %s.\n"
-"* To see the entire file, use an external editor.\n"
+" - %s"
 msgstr ""
 
-#: lib/diff.tcl:482
-msgid "Failed to unstage selected hunk."
+#: lib/remote_branch_delete.tcl:190
+#, tcl-format
+msgid ""
+"One or more of the merge tests failed because you have not fetched the "
+"necessary commits.  Try fetching from %s first."
 msgstr ""
 
-#: lib/diff.tcl:489
-msgid "Failed to stage selected hunk."
+#: lib/remote_branch_delete.tcl:208
+msgid "Please select one or more branches to delete."
 msgstr ""
 
-#: lib/diff.tcl:568
-msgid "Failed to unstage selected line."
+#: lib/remote_branch_delete.tcl:218 lib/branch_delete.tcl:115
+msgid ""
+"Recovering deleted branches is difficult.\n"
+"\n"
+"Delete the selected branches?"
 msgstr ""
 
-#: lib/diff.tcl:576
-msgid "Failed to stage selected line."
+#: lib/remote_branch_delete.tcl:227
+#, tcl-format
+msgid "Deleting branches from %s"
 msgstr ""
 
-#: lib/encoding.tcl:443
-msgid "Default"
+#: lib/remote_branch_delete.tcl:300
+msgid "No repository selected."
 msgstr ""
 
-#: lib/encoding.tcl:448
+#: lib/remote_branch_delete.tcl:305
 #, tcl-format
-msgid "System (%s)"
+msgid "Scanning %s..."
 msgstr ""
 
-#: lib/encoding.tcl:459 lib/encoding.tcl:465
-msgid "Other"
+#: lib/choose_repository.tcl:45
+msgid "Git Gui"
 msgstr ""
 
-#: lib/error.tcl:20 lib/error.tcl:114
-msgid "error"
+#: lib/choose_repository.tcl:104 lib/choose_repository.tcl:427
+msgid "Create New Repository"
 msgstr ""
 
-#: lib/error.tcl:36
-msgid "warning"
+#: lib/choose_repository.tcl:110
+msgid "New..."
 msgstr ""
 
-#: lib/error.tcl:94
-msgid "You must correct the above errors before committing."
+#: lib/choose_repository.tcl:117 lib/choose_repository.tcl:511
+msgid "Clone Existing Repository"
 msgstr ""
 
-#: lib/index.tcl:6
-msgid "Unable to unlock the index."
+#: lib/choose_repository.tcl:128
+msgid "Clone..."
 msgstr ""
 
-#: lib/index.tcl:15
-msgid "Index Error"
+#: lib/choose_repository.tcl:135 lib/choose_repository.tcl:1105
+msgid "Open Existing Repository"
 msgstr ""
 
-#: lib/index.tcl:17
-msgid ""
-"Updating the Git index failed.  A rescan will be automatically started to "
-"resynchronize git-gui."
+#: lib/choose_repository.tcl:141
+msgid "Open..."
 msgstr ""
 
-#: lib/index.tcl:28
-msgid "Continue"
+#: lib/choose_repository.tcl:154
+msgid "Recent Repositories"
 msgstr ""
 
-#: lib/index.tcl:31
-msgid "Unlock Index"
+#: lib/choose_repository.tcl:164
+msgid "Open Recent Repository:"
 msgstr ""
 
-#: lib/index.tcl:289
+#: lib/choose_repository.tcl:331 lib/choose_repository.tcl:338
+#: lib/choose_repository.tcl:345
 #, tcl-format
-msgid "Unstaging %s from commit"
+msgid "Failed to create repository %s:"
 msgstr ""
 
-#: lib/index.tcl:328
-msgid "Ready to commit."
+#: lib/choose_repository.tcl:422 lib/branch_create.tcl:33
+msgid "Create"
 msgstr ""
 
-#: lib/index.tcl:341
-#, tcl-format
-msgid "Adding %s"
+#: lib/choose_repository.tcl:432
+msgid "Directory:"
+msgstr ""
+
+#: lib/choose_repository.tcl:462 lib/choose_repository.tcl:588
+#: lib/choose_repository.tcl:1139
+msgid "Git Repository"
 msgstr ""
 
-#: lib/index.tcl:398
+#: lib/choose_repository.tcl:487
 #, tcl-format
-msgid "Revert changes in file %s?"
+msgid "Directory %s already exists."
 msgstr ""
 
-#: lib/index.tcl:400
+#: lib/choose_repository.tcl:491
 #, tcl-format
-msgid "Revert changes in these %i files?"
+msgid "File %s already exists."
 msgstr ""
 
-#: lib/index.tcl:408
-msgid "Any unstaged changes will be permanently lost by the revert."
+#: lib/choose_repository.tcl:506
+msgid "Clone"
 msgstr ""
 
-#: lib/index.tcl:411
-msgid "Do Nothing"
+#: lib/choose_repository.tcl:519
+msgid "Source Location:"
 msgstr ""
 
-#: lib/index.tcl:429
-msgid "Reverting selected files"
+#: lib/choose_repository.tcl:528
+msgid "Target Directory:"
+msgstr ""
+
+#: lib/choose_repository.tcl:538
+msgid "Clone Type:"
+msgstr ""
+
+#: lib/choose_repository.tcl:543
+msgid "Standard (Fast, Semi-Redundant, Hardlinks)"
+msgstr ""
+
+#: lib/choose_repository.tcl:548
+msgid "Full Copy (Slower, Redundant Backup)"
+msgstr ""
+
+#: lib/choose_repository.tcl:553
+msgid "Shared (Fastest, Not Recommended, No Backup)"
+msgstr ""
+
+#: lib/choose_repository.tcl:560
+msgid "Recursively clone submodules too"
 msgstr ""
 
-#: lib/index.tcl:433
+#: lib/choose_repository.tcl:594 lib/choose_repository.tcl:641
+#: lib/choose_repository.tcl:790 lib/choose_repository.tcl:864
+#: lib/choose_repository.tcl:1145 lib/choose_repository.tcl:1153
 #, tcl-format
-msgid "Reverting %s"
+msgid "Not a Git repository: %s"
 msgstr ""
 
-#: lib/merge.tcl:13
-msgid ""
-"Cannot merge while amending.\n"
-"\n"
-"You must finish amending this commit before starting any type of merge.\n"
+#: lib/choose_repository.tcl:630
+msgid "Standard only available for local repository."
 msgstr ""
 
-#: lib/merge.tcl:27
-msgid ""
-"Last scanned state does not match repository state.\n"
-"\n"
-"Another Git program has modified this repository since the last scan.  A "
-"rescan must be performed before a merge can be performed.\n"
-"\n"
-"The rescan will be automatically started now.\n"
+#: lib/choose_repository.tcl:634
+msgid "Shared only available for local repository."
 msgstr ""
 
-#: lib/merge.tcl:45
+#: lib/choose_repository.tcl:655
 #, tcl-format
-msgid ""
-"You are in the middle of a conflicted merge.\n"
-"\n"
-"File %s has merge conflicts.\n"
-"\n"
-"You must resolve them, stage the file, and commit to complete the current "
-"merge.  Only then can you begin another merge.\n"
+msgid "Location %s already exists."
 msgstr ""
 
-#: lib/merge.tcl:55
+#: lib/choose_repository.tcl:666
+msgid "Failed to configure origin"
+msgstr ""
+
+#: lib/choose_repository.tcl:678
+msgid "Counting objects"
+msgstr ""
+
+#: lib/choose_repository.tcl:679
+msgid "buckets"
+msgstr ""
+
+#: lib/choose_repository.tcl:703
 #, tcl-format
-msgid ""
-"You are in the middle of a change.\n"
-"\n"
-"File %s is modified.\n"
-"\n"
-"You should complete the current commit before starting a merge.  Doing so "
-"will help you abort a failed merge, should the need arise.\n"
+msgid "Unable to copy objects/info/alternates: %s"
 msgstr ""
 
-#: lib/merge.tcl:107
+#: lib/choose_repository.tcl:740
 #, tcl-format
-msgid "%s of %s"
+msgid "Nothing to clone from %s."
+msgstr ""
+
+#: lib/choose_repository.tcl:742 lib/choose_repository.tcl:962
+#: lib/choose_repository.tcl:974
+msgid "The 'master' branch has not been initialized."
 msgstr ""
 
-#: lib/merge.tcl:120
+#: lib/choose_repository.tcl:755
+msgid "Hardlinks are unavailable.  Falling back to copying."
+msgstr ""
+
+#: lib/choose_repository.tcl:769
 #, tcl-format
-msgid "Merging %s and %s..."
+msgid "Cloning from %s"
 msgstr ""
 
-#: lib/merge.tcl:131
-msgid "Merge completed successfully."
+#: lib/choose_repository.tcl:800
+msgid "Copying objects"
 msgstr ""
 
-#: lib/merge.tcl:133
-msgid "Merge failed.  Conflict resolution is required."
+#: lib/choose_repository.tcl:801
+msgid "KiB"
 msgstr ""
 
-#: lib/merge.tcl:158
+#: lib/choose_repository.tcl:825
 #, tcl-format
-msgid "Merge Into %s"
+msgid "Unable to copy object: %s"
 msgstr ""
 
-#: lib/merge.tcl:177
-msgid "Revision To Merge"
+#: lib/choose_repository.tcl:837
+msgid "Linking objects"
 msgstr ""
 
-#: lib/merge.tcl:212
-msgid ""
-"Cannot abort while amending.\n"
-"\n"
-"You must finish amending this commit.\n"
+#: lib/choose_repository.tcl:838
+msgid "objects"
 msgstr ""
 
-#: lib/merge.tcl:222
-msgid ""
-"Abort merge?\n"
-"\n"
-"Aborting the current merge will cause *ALL* uncommitted changes to be lost.\n"
-"\n"
-"Continue with aborting the current merge?"
+#: lib/choose_repository.tcl:846
+#, tcl-format
+msgid "Unable to hardlink object: %s"
 msgstr ""
 
-#: lib/merge.tcl:228
-msgid ""
-"Reset changes?\n"
-"\n"
-"Resetting the changes will cause *ALL* uncommitted changes to be lost.\n"
-"\n"
-"Continue with resetting the current changes?"
+#: lib/choose_repository.tcl:903
+msgid "Cannot fetch branches and objects.  See console output for details."
 msgstr ""
 
-#: lib/merge.tcl:239
-msgid "Aborting"
+#: lib/choose_repository.tcl:914
+msgid "Cannot fetch tags.  See console output for details."
 msgstr ""
 
-#: lib/merge.tcl:239
-msgid "files reset"
+#: lib/choose_repository.tcl:938
+msgid "Cannot determine HEAD.  See console output for details."
 msgstr ""
 
-#: lib/merge.tcl:267
-msgid "Abort failed."
+#: lib/choose_repository.tcl:947
+#, tcl-format
+msgid "Unable to cleanup %s"
 msgstr ""
 
-#: lib/merge.tcl:269
-msgid "Abort completed.  Ready."
+#: lib/choose_repository.tcl:953
+msgid "Clone failed."
 msgstr ""
 
-#: lib/mergetool.tcl:8
-msgid "Force resolution to the base version?"
+#: lib/choose_repository.tcl:960
+msgid "No default branch obtained."
 msgstr ""
 
-#: lib/mergetool.tcl:9
-msgid "Force resolution to this branch?"
+#: lib/choose_repository.tcl:971
+#, tcl-format
+msgid "Cannot resolve %s as a commit."
+msgstr ""
+
+#: lib/choose_repository.tcl:998
+msgid "Creating working directory"
+msgstr ""
+
+#: lib/choose_repository.tcl:1028
+msgid "Initial file checkout failed."
+msgstr ""
+
+#: lib/choose_repository.tcl:1072
+msgid "Cloning submodules"
+msgstr ""
+
+#: lib/choose_repository.tcl:1087
+msgid "Cannot clone submodules."
+msgstr ""
+
+#: lib/choose_repository.tcl:1110
+msgid "Repository:"
+msgstr ""
+
+#: lib/choose_repository.tcl:1159
+#, tcl-format
+msgid "Failed to open repository %s:"
 msgstr ""
 
-#: lib/mergetool.tcl:10
-msgid "Force resolution to the other branch?"
+#: lib/about.tcl:26
+msgid "git-gui - a graphical user interface for Git."
 msgstr ""
 
-#: lib/mergetool.tcl:14
+#: lib/blame.tcl:74
 #, tcl-format
-msgid ""
-"Note that the diff shows only conflicting changes.\n"
-"\n"
-"%s will be overwritten.\n"
-"\n"
-"This operation can be undone only by restarting the merge."
+msgid "%s (%s): File Viewer"
 msgstr ""
 
-#: lib/mergetool.tcl:45
-#, tcl-format
-msgid "File %s seems to have unresolved conflicts, still stage?"
+#: lib/blame.tcl:80
+msgid "Commit:"
 msgstr ""
 
-#: lib/mergetool.tcl:60
-#, tcl-format
-msgid "Adding resolution for %s"
+#: lib/blame.tcl:282
+msgid "Copy Commit"
 msgstr ""
 
-#: lib/mergetool.tcl:141
-msgid "Cannot resolve deletion or link conflicts using a tool"
+#: lib/blame.tcl:286
+msgid "Find Text..."
 msgstr ""
 
-#: lib/mergetool.tcl:146
-msgid "Conflict file does not exist"
+#: lib/blame.tcl:290
+msgid "Goto Line..."
 msgstr ""
 
-#: lib/mergetool.tcl:264
-#, tcl-format
-msgid "Not a GUI merge tool: '%s'"
+#: lib/blame.tcl:299
+msgid "Do Full Copy Detection"
 msgstr ""
 
-#: lib/mergetool.tcl:268
-#, tcl-format
-msgid "Unsupported merge tool '%s'"
+#: lib/blame.tcl:303
+msgid "Show History Context"
 msgstr ""
 
-#: lib/mergetool.tcl:303
-msgid "Merge tool is already running, terminate it?"
+#: lib/blame.tcl:306
+msgid "Blame Parent Commit"
 msgstr ""
 
-#: lib/mergetool.tcl:323
+#: lib/blame.tcl:468
 #, tcl-format
-msgid ""
-"Error retrieving versions:\n"
-"%s"
+msgid "Reading %s..."
 msgstr ""
 
-#: lib/mergetool.tcl:343
-#, tcl-format
-msgid ""
-"Could not start the merge tool:\n"
-"\n"
-"%s"
+#: lib/blame.tcl:596
+msgid "Loading copy/move tracking annotations..."
 msgstr ""
 
-#: lib/mergetool.tcl:347
-msgid "Running merge tool..."
+#: lib/blame.tcl:613
+msgid "lines annotated"
 msgstr ""
 
-#: lib/mergetool.tcl:375 lib/mergetool.tcl:383
-msgid "Merge tool failed."
+#: lib/blame.tcl:815
+msgid "Loading original location annotations..."
 msgstr ""
 
-#: lib/option.tcl:11
-#, tcl-format
-msgid "Invalid global encoding '%s'"
+#: lib/blame.tcl:818
+msgid "Annotation complete."
 msgstr ""
 
-#: lib/option.tcl:19
-#, tcl-format
-msgid "Invalid repo encoding '%s'"
+#: lib/blame.tcl:849
+msgid "Busy"
 msgstr ""
 
-#: lib/option.tcl:117
-msgid "Restore Defaults"
+#: lib/blame.tcl:850
+msgid "Annotation process is already running."
 msgstr ""
 
-#: lib/option.tcl:121
-msgid "Save"
+#: lib/blame.tcl:889
+msgid "Running thorough copy detection..."
 msgstr ""
 
-#: lib/option.tcl:131
-#, tcl-format
-msgid "%s Repository"
+#: lib/blame.tcl:957
+msgid "Loading annotation..."
 msgstr ""
 
-#: lib/option.tcl:132
-msgid "Global (All Repositories)"
+#: lib/blame.tcl:1010
+msgid "Author:"
 msgstr ""
 
-#: lib/option.tcl:138
-msgid "User Name"
+#: lib/blame.tcl:1014
+msgid "Committer:"
 msgstr ""
 
-#: lib/option.tcl:139
-msgid "Email Address"
+#: lib/blame.tcl:1019
+msgid "Original File:"
 msgstr ""
 
-#: lib/option.tcl:141
-msgid "Summarize Merge Commits"
+#: lib/blame.tcl:1067
+msgid "Cannot find HEAD commit:"
 msgstr ""
 
-#: lib/option.tcl:142
-msgid "Merge Verbosity"
+#: lib/blame.tcl:1122
+msgid "Cannot find parent commit:"
 msgstr ""
 
-#: lib/option.tcl:143
-msgid "Show Diffstat After Merge"
+#: lib/blame.tcl:1137
+msgid "Unable to display parent"
 msgstr ""
 
-#: lib/option.tcl:144
-msgid "Use Merge Tool"
+#: lib/blame.tcl:1138 lib/diff.tcl:345
+msgid "Error loading diff:"
 msgstr ""
 
-#: lib/option.tcl:146
-msgid "Trust File Modification Timestamps"
+#: lib/blame.tcl:1279
+msgid "Originally By:"
 msgstr ""
 
-#: lib/option.tcl:147
-msgid "Prune Tracking Branches During Fetch"
+#: lib/blame.tcl:1285
+msgid "In File:"
 msgstr ""
 
-#: lib/option.tcl:148
-msgid "Match Tracking Branches"
+#: lib/blame.tcl:1290
+msgid "Copied Or Moved Here By:"
 msgstr ""
 
-#: lib/option.tcl:149
-msgid "Blame Copy Only On Changed Files"
+#: lib/diff.tcl:77
+#, tcl-format
+msgid ""
+"No differences detected.\n"
+"\n"
+"%s has no changes.\n"
+"\n"
+"The modification date of this file was updated by another application, but "
+"the content within the file was not changed.\n"
+"\n"
+"A rescan will be automatically started to find other files which may have "
+"the same state."
 msgstr ""
 
-#: lib/option.tcl:150
-msgid "Minimum Letters To Blame Copy On"
+#: lib/diff.tcl:117
+#, tcl-format
+msgid "Loading diff of %s..."
 msgstr ""
 
-#: lib/option.tcl:151
-msgid "Blame History Context Radius (days)"
+#: lib/diff.tcl:143
+msgid ""
+"LOCAL: deleted\n"
+"REMOTE:\n"
 msgstr ""
 
-#: lib/option.tcl:152
-msgid "Number of Diff Context Lines"
+#: lib/diff.tcl:148
+msgid ""
+"REMOTE: deleted\n"
+"LOCAL:\n"
 msgstr ""
 
-#: lib/option.tcl:153
-msgid "Commit Message Text Width"
+#: lib/diff.tcl:155
+msgid "LOCAL:\n"
 msgstr ""
 
-#: lib/option.tcl:154
-msgid "New Branch Name Template"
+#: lib/diff.tcl:158
+msgid "REMOTE:\n"
 msgstr ""
 
-#: lib/option.tcl:155
-msgid "Default File Contents Encoding"
+#: lib/diff.tcl:220 lib/diff.tcl:344
+#, tcl-format
+msgid "Unable to display %s"
 msgstr ""
 
-#: lib/option.tcl:203
-msgid "Change"
+#: lib/diff.tcl:221
+msgid "Error loading file:"
 msgstr ""
 
-#: lib/option.tcl:230
-msgid "Spelling Dictionary:"
+#: lib/diff.tcl:227
+msgid "Git Repository (subproject)"
 msgstr ""
 
-#: lib/option.tcl:254
-msgid "Change Font"
+#: lib/diff.tcl:239
+msgid "* Binary file (not showing content)."
 msgstr ""
 
-#: lib/option.tcl:258
+#: lib/diff.tcl:244
 #, tcl-format
-msgid "Choose %s"
+msgid ""
+"* Untracked file is %d bytes.\n"
+"* Showing only first %d bytes.\n"
 msgstr ""
 
-#: lib/option.tcl:264
-msgid "pt."
+#: lib/diff.tcl:250
+#, tcl-format
+msgid ""
+"\n"
+"* Untracked file clipped here by %s.\n"
+"* To see the entire file, use an external editor.\n"
 msgstr ""
 
-#: lib/option.tcl:278
-msgid "Preferences"
+#: lib/diff.tcl:583
+msgid "Failed to unstage selected hunk."
 msgstr ""
 
-#: lib/option.tcl:314
-msgid "Failed to completely save options:"
+#: lib/diff.tcl:591
+msgid "Failed to revert selected hunk."
 msgstr ""
 
-#: lib/remote.tcl:163
-msgid "Remove Remote"
+#: lib/diff.tcl:594
+msgid "Failed to stage selected hunk."
 msgstr ""
 
-#: lib/remote.tcl:168
-msgid "Prune from"
+#: lib/diff.tcl:687
+msgid "Failed to unstage selected line."
 msgstr ""
 
-#: lib/remote.tcl:173
-msgid "Fetch from"
+#: lib/diff.tcl:696
+msgid "Failed to revert selected line."
 msgstr ""
 
-#: lib/remote.tcl:215
-msgid "Push to"
+#: lib/diff.tcl:700
+msgid "Failed to stage selected line."
 msgstr ""
 
-#: lib/remote_add.tcl:19
-msgid "Add Remote"
+#: lib/diff.tcl:889
+msgid "Failed to undo last revert."
 msgstr ""
 
-#: lib/remote_add.tcl:24
-msgid "Add New Remote"
+#: lib/sshkey.tcl:34
+msgid "No keys found."
 msgstr ""
 
-#: lib/remote_add.tcl:28 lib/tools_dlg.tcl:36
-msgid "Add"
+#: lib/sshkey.tcl:37
+#, tcl-format
+msgid "Found a public key in: %s"
 msgstr ""
 
-#: lib/remote_add.tcl:37
-msgid "Remote Details"
+#: lib/sshkey.tcl:43
+msgid "Generate Key"
 msgstr ""
 
-#: lib/remote_add.tcl:50
-msgid "Location:"
+#: lib/sshkey.tcl:61
+msgid "Copy To Clipboard"
 msgstr ""
 
-#: lib/remote_add.tcl:62
-msgid "Further Action"
+#: lib/sshkey.tcl:75
+msgid "Your OpenSSH Public Key"
 msgstr ""
 
-#: lib/remote_add.tcl:65
-msgid "Fetch Immediately"
+#: lib/sshkey.tcl:83
+msgid "Generating..."
 msgstr ""
 
-#: lib/remote_add.tcl:71
-msgid "Initialize Remote Repository and Push"
+#: lib/sshkey.tcl:89
+#, tcl-format
+msgid ""
+"Could not start ssh-keygen:\n"
+"\n"
+"%s"
 msgstr ""
 
-#: lib/remote_add.tcl:77
-msgid "Do Nothing Else Now"
+#: lib/sshkey.tcl:116
+msgid "Generation failed."
 msgstr ""
 
-#: lib/remote_add.tcl:101
-msgid "Please supply a remote name."
+#: lib/sshkey.tcl:123
+msgid "Generation succeeded, but no keys found."
 msgstr ""
 
-#: lib/remote_add.tcl:114
+#: lib/sshkey.tcl:126
 #, tcl-format
-msgid "'%s' is not an acceptable remote name."
+msgid "Your key is in: %s"
 msgstr ""
 
-#: lib/remote_add.tcl:125
+#: lib/branch_create.tcl:23
 #, tcl-format
-msgid "Failed to add remote '%s' of location '%s'."
+msgid "%s (%s): Create Branch"
 msgstr ""
 
-#: lib/remote_add.tcl:133 lib/transport.tcl:6
-#, tcl-format
-msgid "fetch %s"
+#: lib/branch_create.tcl:28
+msgid "Create New Branch"
 msgstr ""
 
-#: lib/remote_add.tcl:134
-#, tcl-format
-msgid "Fetching the %s"
+#: lib/branch_create.tcl:42
+msgid "Branch Name"
 msgstr ""
 
-#: lib/remote_add.tcl:157
-#, tcl-format
-msgid "Do not know how to initialize repository at location '%s'."
+#: lib/branch_create.tcl:57
+msgid "Match Tracking Branch Name"
 msgstr ""
 
-#: lib/remote_add.tcl:163 lib/transport.tcl:25 lib/transport.tcl:63
-#: lib/transport.tcl:81
-#, tcl-format
-msgid "push %s"
+#: lib/branch_create.tcl:66
+msgid "Starting Revision"
 msgstr ""
 
-#: lib/remote_add.tcl:164
-#, tcl-format
-msgid "Setting up the %s (at %s)"
+#: lib/branch_create.tcl:72
+msgid "Update Existing Branch:"
 msgstr ""
 
-#: lib/remote_branch_delete.tcl:29 lib/remote_branch_delete.tcl:34
-msgid "Delete Branch Remotely"
+#: lib/branch_create.tcl:75
+msgid "No"
 msgstr ""
 
-#: lib/remote_branch_delete.tcl:47
-msgid "From Repository"
+#: lib/branch_create.tcl:80
+msgid "Fast Forward Only"
 msgstr ""
 
-#: lib/remote_branch_delete.tcl:50 lib/transport.tcl:134
-msgid "Remote:"
+#: lib/branch_create.tcl:97
+msgid "Checkout After Creation"
 msgstr ""
 
-#: lib/remote_branch_delete.tcl:66 lib/transport.tcl:149
-msgid "Arbitrary Location:"
+#: lib/branch_create.tcl:132
+msgid "Please select a tracking branch."
 msgstr ""
 
-#: lib/remote_branch_delete.tcl:84
-msgid "Branches"
+#: lib/branch_create.tcl:141
+#, tcl-format
+msgid "Tracking branch %s is not a branch in the remote repository."
 msgstr ""
 
-#: lib/remote_branch_delete.tcl:109
-msgid "Delete Only If"
+#: lib/console.tcl:59
+msgid "Working... please wait..."
 msgstr ""
 
-#: lib/remote_branch_delete.tcl:111
-msgid "Merged Into:"
+#: lib/console.tcl:186
+msgid "Success"
 msgstr ""
 
-#: lib/remote_branch_delete.tcl:152
-msgid "A branch is required for 'Merged Into'."
+#: lib/console.tcl:200
+msgid "Error: Command Failed"
 msgstr ""
 
-#: lib/remote_branch_delete.tcl:184
-#, tcl-format
-msgid ""
-"The following branches are not completely merged into %s:\n"
-"\n"
-" - %s"
+#: lib/line.tcl:17
+msgid "Goto Line:"
 msgstr ""
 
-#: lib/remote_branch_delete.tcl:189
-#, tcl-format
-msgid ""
-"One or more of the merge tests failed because you have not fetched the "
-"necessary commits.  Try fetching from %s first."
+#: lib/line.tcl:23
+msgid "Go"
 msgstr ""
 
-#: lib/remote_branch_delete.tcl:207
-msgid "Please select one or more branches to delete."
+#: lib/choose_rev.tcl:52
+msgid "This Detached Checkout"
 msgstr ""
 
-#: lib/remote_branch_delete.tcl:226
-#, tcl-format
-msgid "Deleting branches from %s"
+#: lib/choose_rev.tcl:60
+msgid "Revision Expression:"
 msgstr ""
 
-#: lib/remote_branch_delete.tcl:292
-msgid "No repository selected."
+#: lib/choose_rev.tcl:72
+msgid "Local Branch"
+msgstr ""
+
+#: lib/choose_rev.tcl:77
+msgid "Tracking Branch"
+msgstr ""
+
+#: lib/choose_rev.tcl:82 lib/choose_rev.tcl:544
+msgid "Tag"
 msgstr ""
 
-#: lib/remote_branch_delete.tcl:297
+#: lib/choose_rev.tcl:321
 #, tcl-format
-msgid "Scanning %s..."
+msgid "Invalid revision: %s"
 msgstr ""
 
-#: lib/search.tcl:21
-msgid "Find:"
+#: lib/choose_rev.tcl:342
+msgid "No revision selected."
 msgstr ""
 
-#: lib/search.tcl:23
-msgid "Next"
+#: lib/choose_rev.tcl:350
+msgid "Revision expression is empty."
+msgstr ""
+
+#: lib/choose_rev.tcl:537
+msgid "Updated"
+msgstr ""
+
+#: lib/choose_rev.tcl:565
+msgid "URL"
 msgstr ""
 
-#: lib/search.tcl:24
-msgid "Prev"
+#: lib/commit.tcl:9
+msgid ""
+"There is nothing to amend.\n"
+"\n"
+"You are about to create the initial commit.  There is no commit before this "
+"to amend.\n"
 msgstr ""
 
-#: lib/search.tcl:25
-msgid "Case-Sensitive"
+#: lib/commit.tcl:18
+msgid ""
+"Cannot amend while merging.\n"
+"\n"
+"You are currently in the middle of a merge that has not been fully "
+"completed.  You cannot amend the prior commit unless you first abort the "
+"current merge activity.\n"
 msgstr ""
 
-#: lib/shortcut.tcl:21 lib/shortcut.tcl:62
-msgid "Cannot write shortcut:"
+#: lib/commit.tcl:56
+msgid "Error loading commit data for amend:"
 msgstr ""
 
-#: lib/shortcut.tcl:137
-msgid "Cannot write icon:"
+#: lib/commit.tcl:83
+msgid "Unable to obtain your identity:"
 msgstr ""
 
-#: lib/spellcheck.tcl:57
-msgid "Unsupported spell checker"
+#: lib/commit.tcl:88
+msgid "Invalid GIT_COMMITTER_IDENT:"
 msgstr ""
 
-#: lib/spellcheck.tcl:65
-msgid "Spell checking is unavailable"
+#: lib/commit.tcl:138
+#, tcl-format
+msgid "warning: Tcl does not support encoding '%s'."
 msgstr ""
 
-#: lib/spellcheck.tcl:68
-msgid "Invalid spell checking configuration"
+#: lib/commit.tcl:158
+msgid ""
+"Last scanned state does not match repository state.\n"
+"\n"
+"Another Git program has modified this repository since the last scan.  A "
+"rescan must be performed before another commit can be created.\n"
+"\n"
+"The rescan will be automatically started now.\n"
 msgstr ""
 
-#: lib/spellcheck.tcl:70
+#: lib/commit.tcl:182
 #, tcl-format
-msgid "Reverting dictionary to %s."
+msgid ""
+"Unmerged files cannot be committed.\n"
+"\n"
+"File %s has merge conflicts.  You must resolve them and stage the file "
+"before committing.\n"
 msgstr ""
 
-#: lib/spellcheck.tcl:73
-msgid "Spell checker silently failed on startup"
+#: lib/commit.tcl:190
+#, tcl-format
+msgid ""
+"Unknown file state %s detected.\n"
+"\n"
+"File %s cannot be committed by this program.\n"
 msgstr ""
 
-#: lib/spellcheck.tcl:80
-msgid "Unrecognized spell checker"
+#: lib/commit.tcl:198
+msgid ""
+"No changes to commit.\n"
+"\n"
+"You must stage at least 1 file before you can commit.\n"
 msgstr ""
 
-#: lib/spellcheck.tcl:186
-msgid "No Suggestions"
+#: lib/commit.tcl:213
+msgid ""
+"Please supply a commit message.\n"
+"\n"
+"A good commit message has the following format:\n"
+"\n"
+"- First line: Describe in one sentence what you did.\n"
+"- Second line: Blank\n"
+"- Remaining lines: Describe why this change is good.\n"
 msgstr ""
 
-#: lib/spellcheck.tcl:388
-msgid "Unexpected EOF from spell checker"
+#: lib/commit.tcl:244
+msgid "Calling pre-commit hook..."
 msgstr ""
 
-#: lib/spellcheck.tcl:392
-msgid "Spell Checker Failed"
+#: lib/commit.tcl:259
+msgid "Commit declined by pre-commit hook."
 msgstr ""
 
-#: lib/sshkey.tcl:31
-msgid "No keys found."
+#: lib/commit.tcl:278
+msgid ""
+"You are about to commit on a detached head. This is a potentially dangerous "
+"thing to do because if you switch to another branch you will lose your "
+"changes and it can be difficult to retrieve them later from the reflog. You "
+"should probably cancel this commit and create a new branch to continue.\n"
+" \n"
+" Do you really want to proceed with your Commit?"
 msgstr ""
 
-#: lib/sshkey.tcl:34
-#, tcl-format
-msgid "Found a public key in: %s"
+#: lib/commit.tcl:299
+msgid "Calling commit-msg hook..."
 msgstr ""
 
-#: lib/sshkey.tcl:40
-msgid "Generate Key"
+#: lib/commit.tcl:314
+msgid "Commit declined by commit-msg hook."
 msgstr ""
 
-#: lib/sshkey.tcl:56
-msgid "Copy To Clipboard"
+#: lib/commit.tcl:327
+msgid "Committing changes..."
 msgstr ""
 
-#: lib/sshkey.tcl:70
-msgid "Your OpenSSH Public Key"
+#: lib/commit.tcl:344
+msgid "write-tree failed:"
 msgstr ""
 
-#: lib/sshkey.tcl:78
-msgid "Generating..."
+#: lib/commit.tcl:345 lib/commit.tcl:395 lib/commit.tcl:422
+msgid "Commit failed."
 msgstr ""
 
-#: lib/sshkey.tcl:84
+#: lib/commit.tcl:362
 #, tcl-format
+msgid "Commit %s appears to be corrupt"
+msgstr ""
+
+#: lib/commit.tcl:367
 msgid ""
-"Could not start ssh-keygen:\n"
+"No changes to commit.\n"
 "\n"
-"%s"
+"No files were modified by this commit and it was not a merge commit.\n"
+"\n"
+"A rescan will be automatically started now.\n"
 msgstr ""
 
-#: lib/sshkey.tcl:111
-msgid "Generation failed."
+#: lib/commit.tcl:374
+msgid "No changes to commit."
 msgstr ""
 
-#: lib/sshkey.tcl:118
-msgid "Generation succeeded, but no keys found."
+#: lib/commit.tcl:394
+msgid "commit-tree failed:"
 msgstr ""
 
-#: lib/sshkey.tcl:121
-#, tcl-format
-msgid "Your key is in: %s"
+#: lib/commit.tcl:421
+msgid "update-ref failed:"
 msgstr ""
 
-#: lib/status_bar.tcl:83
+#: lib/commit.tcl:514
 #, tcl-format
-msgid "%s ... %*i of %*i %s (%3i%%)"
+msgid "Created commit %s: %s"
 msgstr ""
 
-#: lib/tools.tcl:75
+#: lib/branch_delete.tcl:16
 #, tcl-format
-msgid "Running %s requires a selected file."
+msgid "%s (%s): Delete Branch"
 msgstr ""
 
-#: lib/tools.tcl:90
-#, tcl-format
-msgid "Are you sure you want to run %s?"
+#: lib/branch_delete.tcl:21
+msgid "Delete Local Branch"
+msgstr ""
+
+#: lib/branch_delete.tcl:39
+msgid "Local Branches"
+msgstr ""
+
+#: lib/branch_delete.tcl:51
+msgid "Delete Only If Merged Into"
 msgstr ""
 
-#: lib/tools.tcl:110
+#: lib/branch_delete.tcl:103
 #, tcl-format
-msgid "Tool: %s"
+msgid "The following branches are not completely merged into %s:"
 msgstr ""
 
-#: lib/tools.tcl:111
+#: lib/branch_delete.tcl:131
 #, tcl-format
-msgid "Running: %s"
+msgid " - %s:"
 msgstr ""
 
-#: lib/tools.tcl:149
+#: lib/branch_delete.tcl:141
 #, tcl-format
-msgid "Tool completed successfully: %s"
+msgid ""
+"Failed to delete branches:\n"
+"%s"
 msgstr ""
 
-#: lib/tools.tcl:151
+#: lib/date.tcl:25
 #, tcl-format
-msgid "Tool failed: %s"
+msgid "Invalid date from Git: %s"
 msgstr ""
 
-#: lib/tools_dlg.tcl:22
-msgid "Add Tool"
+#: lib/database.tcl:42
+msgid "Number of loose objects"
 msgstr ""
 
-#: lib/tools_dlg.tcl:28
-msgid "Add New Tool Command"
+#: lib/database.tcl:43
+msgid "Disk space used by loose objects"
 msgstr ""
 
-#: lib/tools_dlg.tcl:33
-msgid "Add globally"
+#: lib/database.tcl:44
+msgid "Number of packed objects"
 msgstr ""
 
-#: lib/tools_dlg.tcl:45
-msgid "Tool Details"
+#: lib/database.tcl:45
+msgid "Number of packs"
 msgstr ""
 
-#: lib/tools_dlg.tcl:48
-msgid "Use '/' separators to create a submenu tree:"
+#: lib/database.tcl:46
+msgid "Disk space used by packed objects"
 msgstr ""
 
-#: lib/tools_dlg.tcl:61
-msgid "Command:"
+#: lib/database.tcl:47
+msgid "Packed objects waiting for pruning"
 msgstr ""
 
-#: lib/tools_dlg.tcl:74
-msgid "Show a dialog before running"
+#: lib/database.tcl:48
+msgid "Garbage files"
 msgstr ""
 
-#: lib/tools_dlg.tcl:80
-msgid "Ask the user to select a revision (sets $REVISION)"
+#: lib/database.tcl:66
+#, tcl-format
+msgid "%s (%s): Database Statistics"
 msgstr ""
 
-#: lib/tools_dlg.tcl:85
-msgid "Ask the user for additional arguments (sets $ARGS)"
+#: lib/database.tcl:72
+msgid "Compressing the object database"
 msgstr ""
 
-#: lib/tools_dlg.tcl:92
-msgid "Don't show the command output window"
+#: lib/database.tcl:83
+msgid "Verifying the object database with fsck-objects"
 msgstr ""
 
-#: lib/tools_dlg.tcl:97
-msgid "Run only if a diff is selected ($FILENAME not empty)"
+#: lib/database.tcl:107
+#, tcl-format
+msgid ""
+"This repository currently has approximately %i loose objects.\n"
+"\n"
+"To maintain optimal performance it is strongly recommended that you compress "
+"the database.\n"
+"\n"
+"Compress the database now?"
 msgstr ""
 
-#: lib/tools_dlg.tcl:121
-msgid "Please supply a name for the tool."
+#: lib/error.tcl:20
+#, tcl-format
+msgid "%s: error"
 msgstr ""
 
-#: lib/tools_dlg.tcl:129
+#: lib/error.tcl:36
 #, tcl-format
-msgid "Tool '%s' already exists."
+msgid "%s: warning"
 msgstr ""
 
-#: lib/tools_dlg.tcl:151
+#: lib/error.tcl:80
 #, tcl-format
-msgid ""
-"Could not add tool:\n"
-"%s"
+msgid "%s hook failed:"
 msgstr ""
 
-#: lib/tools_dlg.tcl:190
-msgid "Remove Tool"
+#: lib/error.tcl:96
+msgid "You must correct the above errors before committing."
 msgstr ""
 
-#: lib/tools_dlg.tcl:196
-msgid "Remove Tool Commands"
+#: lib/error.tcl:116
+#, tcl-format
+msgid "%s (%s): error"
 msgstr ""
 
-#: lib/tools_dlg.tcl:200
-msgid "Remove"
+#: lib/merge.tcl:13
+msgid ""
+"Cannot merge while amending.\n"
+"\n"
+"You must finish amending this commit before starting any type of merge.\n"
 msgstr ""
 
-#: lib/tools_dlg.tcl:236
-msgid "(Blue denotes repository-local tools)"
+#: lib/merge.tcl:27
+msgid ""
+"Last scanned state does not match repository state.\n"
+"\n"
+"Another Git program has modified this repository since the last scan.  A "
+"rescan must be performed before a merge can be performed.\n"
+"\n"
+"The rescan will be automatically started now.\n"
 msgstr ""
 
-#: lib/tools_dlg.tcl:297
+#: lib/merge.tcl:45
 #, tcl-format
-msgid "Run Command: %s"
+msgid ""
+"You are in the middle of a conflicted merge.\n"
+"\n"
+"File %s has merge conflicts.\n"
+"\n"
+"You must resolve them, stage the file, and commit to complete the current "
+"merge.  Only then can you begin another merge.\n"
 msgstr ""
 
-#: lib/tools_dlg.tcl:311
-msgid "Arguments"
+#: lib/merge.tcl:55
+#, tcl-format
+msgid ""
+"You are in the middle of a change.\n"
+"\n"
+"File %s is modified.\n"
+"\n"
+"You should complete the current commit before starting a merge.  Doing so "
+"will help you abort a failed merge, should the need arise.\n"
 msgstr ""
 
-#: lib/tools_dlg.tcl:348
-msgid "OK"
+#: lib/merge.tcl:108
+#, tcl-format
+msgid "%s of %s"
 msgstr ""
 
-#: lib/transport.tcl:7
+#: lib/merge.tcl:126
 #, tcl-format
-msgid "Fetching new changes from %s"
+msgid "Merging %s and %s..."
 msgstr ""
 
-#: lib/transport.tcl:18
-#, tcl-format
-msgid "remote prune %s"
+#: lib/merge.tcl:137
+msgid "Merge completed successfully."
 msgstr ""
 
-#: lib/transport.tcl:19
-#, tcl-format
-msgid "Pruning tracking branches deleted from %s"
+#: lib/merge.tcl:139
+msgid "Merge failed.  Conflict resolution is required."
 msgstr ""
 
-#: lib/transport.tcl:26
+#: lib/merge.tcl:156
 #, tcl-format
-msgid "Pushing changes to %s"
+msgid "%s (%s): Merge"
 msgstr ""
 
-#: lib/transport.tcl:64
+#: lib/merge.tcl:164
 #, tcl-format
-msgid "Mirroring to %s"
+msgid "Merge Into %s"
 msgstr ""
 
-#: lib/transport.tcl:82
-#, tcl-format
-msgid "Pushing %s %s to %s"
+#: lib/merge.tcl:183
+msgid "Revision To Merge"
 msgstr ""
 
-#: lib/transport.tcl:100
-msgid "Push Branches"
+#: lib/merge.tcl:218
+msgid ""
+"Cannot abort while amending.\n"
+"\n"
+"You must finish amending this commit.\n"
 msgstr ""
 
-#: lib/transport.tcl:114
-msgid "Source Branches"
+#: lib/merge.tcl:228
+msgid ""
+"Abort merge?\n"
+"\n"
+"Aborting the current merge will cause *ALL* uncommitted changes to be lost.\n"
+"\n"
+"Continue with aborting the current merge?"
 msgstr ""
 
-#: lib/transport.tcl:131
-msgid "Destination Repository"
+#: lib/merge.tcl:234
+msgid ""
+"Reset changes?\n"
+"\n"
+"Resetting the changes will cause *ALL* uncommitted changes to be lost.\n"
+"\n"
+"Continue with resetting the current changes?"
 msgstr ""
 
-#: lib/transport.tcl:169
-msgid "Transfer Options"
+#: lib/merge.tcl:246
+msgid "Aborting"
 msgstr ""
 
-#: lib/transport.tcl:171
-msgid "Force overwrite existing branch (may discard changes)"
+#: lib/merge.tcl:247
+msgid "files reset"
 msgstr ""
 
-#: lib/transport.tcl:175
-msgid "Use thin pack (for slow network connections)"
+#: lib/merge.tcl:277
+msgid "Abort failed."
 msgstr ""
 
-#: lib/transport.tcl:179
-msgid "Include tags"
+#: lib/merge.tcl:279
+msgid "Abort completed.  Ready."
 msgstr ""
-- 
gitgitgadget


^ permalink raw reply related	[relevance 1%]

* [PATCH v2 3/3] git-gui: update German translation
  2020-02-09 22:00  1% ` [PATCH v2 0/3] git gui: improve German translation Christian Stimming via GitGitGadget
  2020-02-09 22:00  1%   ` [PATCH v2 1/3] git-gui: update pot template and German translation to current source code Christian Stimming via GitGitGadget
@ 2020-02-09 22:00  1%   ` Christian Stimming via GitGitGadget
  1 sibling, 0 replies; 200+ results
From: Christian Stimming via GitGitGadget @ 2020-02-09 22:00 UTC (permalink / raw)
  To: git; +Cc: Christian Stimming, Pratyush Yadav, Christian Stimming

From: Christian Stimming <christian@cstimming.de>

Update German translation (glossary and final translation) with
recent additions, but also switch several terms from uncommon
translations back to English vocabulary.

This most prominently concerns "commit" (noun, verb), "repository",
"branch", and some more. These uncommon translations have been introduced
long ago and never been changed since. In fact, the whole German
translation here hasn't been touched for a long time. However, in German
literature and magazines, git-gui is regularly noted for its uncommon
choice of translated vocabulary. This somewhat distracts from the actual
benefits of this tool. So it is probably better to abandon the uncommon
translations and rather stick to the common English vocabulary in git
version control.

Signed-off-by: Christian Stimming <christian@cstimming.de>
---
 po/de.po          | 672 ++++++++++++++++++++++------------------------
 po/glossary/de.po | 315 +++++++++++++++++++---
 2 files changed, 602 insertions(+), 385 deletions(-)

diff --git a/po/de.po b/po/de.po
index 6bbcb1d469..a8d5f61ca3 100644
--- a/po/de.po
+++ b/po/de.po
@@ -8,10 +8,10 @@ msgstr ""
 "Project-Id-Version: git-gui\n"
 "Report-Msgid-Bugs-To: \n"
 "POT-Creation-Date: 2020-02-08 22:54+0100\n"
-"PO-Revision-Date: 2010-01-26 22:25+0100\n"
-"Last-Translator: Christian Stimming <stimming@tuhh.de>\n"
+"PO-Revision-Date: 2020-02-09 22:40+0100\n"
+"Last-Translator: Christian Stimming <christian@cstimming.de>\n"
 "Language-Team: German\n"
-"Language: \n"
+"Language: de_DE\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
@@ -73,7 +73,7 @@ msgstr ""
 
 #: git-gui.sh:1309
 msgid "Cannot use bare repository:"
-msgstr "Bloßes Projektarchiv kann nicht benutzt werden:"
+msgstr "Bloßes Repository kann nicht benutzt werden:"
 
 #: git-gui.sh:1317
 msgid "No working directory"
@@ -89,14 +89,11 @@ msgstr "Nach geänderten Dateien suchen..."
 
 #: git-gui.sh:1629
 msgid "Calling prepare-commit-msg hook..."
-msgstr ""
-"Aufrufen der Eintragen-Vorbereiten-Kontrolle (»prepare-commit hook«)..."
+msgstr "Aufrufen des »prepare-commit-msg hook«..."
 
 #: git-gui.sh:1646
 msgid "Commit declined by prepare-commit-msg hook."
-msgstr ""
-"Eintragen abgelehnt durch Eintragen-Vorbereiten-Kontrolle (»prepare-commit "
-"hook«)."
+msgstr "Commit abgelehnt durch »prepare-commit-msg hook«."
 
 #: git-gui.sh:1804 lib/browser.tcl:252
 msgid "Ready."
@@ -107,6 +104,8 @@ msgstr "Bereit."
 msgid ""
 "Display limit (gui.maxfilesdisplayed = %s) reached, not showing all %s files."
 msgstr ""
+"Anzeigelimit erreicht (gui.maxfilesdisplayed = %s) für Anzahl Einträge. Es "
+"werden nicht alle %s Dateien gezeigt."
 
 #: git-gui.sh:2091
 msgid "Unmodified"
@@ -118,42 +117,39 @@ msgstr "Verändert, nicht bereitgestellt"
 
 #: git-gui.sh:2094 git-gui.sh:2106
 msgid "Staged for commit"
-msgstr "Bereitgestellt zum Eintragen"
+msgstr "Bereitgestellt zum Committen"
 
 #: git-gui.sh:2095 git-gui.sh:2107
 msgid "Portions staged for commit"
-msgstr "Teilweise bereitgestellt zum Eintragen"
+msgstr "Teilweise bereitgestellt zum Committen"
 
 #: git-gui.sh:2096 git-gui.sh:2108
 msgid "Staged for commit, missing"
-msgstr "Bereitgestellt zum Eintragen, fehlend"
+msgstr "Bereitgestellt zum Committen, fehlend"
 
 #: git-gui.sh:2098
 msgid "File type changed, not staged"
 msgstr "Dateityp geändert, nicht bereitgestellt"
 
 #: git-gui.sh:2099 git-gui.sh:2100
-#, fuzzy
 msgid "File type changed, old type staged for commit"
-msgstr "Dateityp geändert, nicht bereitgestellt"
+msgstr "Dateityp geändert, alter Dateityp bereitgestellt"
 
 #: git-gui.sh:2101
 msgid "File type changed, staged"
 msgstr "Dateityp geändert, bereitgestellt"
 
 #: git-gui.sh:2102
-#, fuzzy
 msgid "File type change staged, modification not staged"
-msgstr "Dateityp geändert, nicht bereitgestellt"
+msgstr "Dateityp-Änderung bereitgestellt, Inhaltsänderung nicht bereitgestellt"
 
 #: git-gui.sh:2103
-#, fuzzy
 msgid "File type change staged, file missing"
-msgstr "Dateityp geändert, bereitgestellt"
+msgstr "Dateityp-Änderung bereitgestellt, Datei gelöscht"
 
 #: git-gui.sh:2105
 msgid "Untracked, not staged"
-msgstr "Nicht unter Versionskontrolle, nicht bereitgestellt"
+msgstr "Unversioniert, nicht bereitgestellt"
 
 #: git-gui.sh:2110
 msgid "Missing"
@@ -177,9 +173,9 @@ msgid "Couldn't find gitk in PATH"
 msgstr "Gitk kann im PATH nicht gefunden werden."
 
 #: git-gui.sh:2210 git-gui.sh:2245
-#, fuzzy, tcl-format
+#, tcl-format
 msgid "Starting %s... please wait..."
-msgstr "Gitk wird gestartet... bitte warten."
+msgstr "%s wird gestartet... bitte warten."
 
 #: git-gui.sh:2224
 msgid "Couldn't find git gui in PATH"
@@ -187,7 +183,7 @@ msgstr "»Git gui« kann im PATH nicht gefunden werden."
 
 #: git-gui.sh:2726 lib/choose_repository.tcl:53
 msgid "Repository"
-msgstr "Projektarchiv"
+msgstr "Repository"
 
 #: git-gui.sh:2727
 msgid "Edit"
@@ -195,11 +191,11 @@ msgstr "Bearbeiten"
 
 #: git-gui.sh:2729 lib/choose_rev.tcl:567
 msgid "Branch"
-msgstr "Zweig"
+msgstr "Branch"
 
 #: git-gui.sh:2732 lib/choose_rev.tcl:554
 msgid "Commit@@noun"
-msgstr "Version"
+msgstr "Commit"
 
 #: git-gui.sh:2735 lib/merge.tcl:127 lib/merge.tcl:174
 msgid "Merge"
@@ -207,7 +203,7 @@ msgstr "Zusammenführen"
 
 #: git-gui.sh:2736 lib/choose_rev.tcl:563
 msgid "Remote"
-msgstr "Externe Archive"
+msgstr "Extern"
 
 #: git-gui.sh:2739
 msgid "Tools"
@@ -215,32 +211,32 @@ msgstr "Werkzeuge"
 
 #: git-gui.sh:2748
 msgid "Explore Working Copy"
-msgstr "Arbeitskopie im Dateimanager"
+msgstr "Arbeitskopie im Dateimanager öffnen"
 
 #: git-gui.sh:2763
 msgid "Git Bash"
-msgstr ""
+msgstr "Git Bash"
 
 #: git-gui.sh:2772
 msgid "Browse Current Branch's Files"
-msgstr "Aktuellen Zweig durchblättern"
+msgstr "Aktuellen Branch durchblättern"
 
 #: git-gui.sh:2776
 msgid "Browse Branch Files..."
-msgstr "Einen Zweig durchblättern..."
+msgstr "Branch durchblättern..."
 
 #: git-gui.sh:2781
 msgid "Visualize Current Branch's History"
-msgstr "Aktuellen Zweig darstellen"
+msgstr "Aktuellen Branch darstellen"
 
 #: git-gui.sh:2785
 msgid "Visualize All Branch History"
-msgstr "Alle Zweige darstellen"
+msgstr "Historie aller Branches darstellen"
 
 #: git-gui.sh:2792
 #, tcl-format
 msgid "Browse %s's Files"
-msgstr "Zweig »%s« durchblättern"
+msgstr "Branch »%s« durchblättern"
 
 #: git-gui.sh:2794
 #, tcl-format
@@ -303,7 +299,7 @@ msgstr "Erstellen..."
 
 #: git-gui.sh:2871
 msgid "Checkout..."
-msgstr "Umstellen..."
+msgstr "Auschecken..."
 
 #: git-gui.sh:2877
 msgid "Rename..."
@@ -315,7 +311,7 @@ msgstr "Löschen..."
 
 #: git-gui.sh:2887
 msgid "Reset..."
-msgstr "Zurücksetzen..."
+msgstr "Änderungen verwerfen..."
 
 #: git-gui.sh:2897
 msgid "Done"
@@ -323,11 +319,11 @@ msgstr "Fertig"
 
 #: git-gui.sh:2899
 msgid "Commit@@verb"
-msgstr "Eintragen"
+msgstr "Committen"
 
 #: git-gui.sh:2908 git-gui.sh:3400
 msgid "Amend Last Commit"
-msgstr "Letzte nachbessern"
+msgstr "Letzten Commit nachbessern"
 
 #: git-gui.sh:2918 git-gui.sh:3361 lib/remote_branch_delete.tcl:101
 msgid "Rescan"
@@ -335,15 +331,15 @@ msgstr "Neu laden"
 
 #: git-gui.sh:2924
 msgid "Stage To Commit"
-msgstr "Zum Eintragen bereitstellen"
+msgstr "Für Commit bereitstellen"
 
 #: git-gui.sh:2930
 msgid "Stage Changed Files To Commit"
-msgstr "Geänderte Dateien bereitstellen"
+msgstr "Geänderte Dateien für Commit bereitstellen"
 
 #: git-gui.sh:2936
 msgid "Unstage From Commit"
-msgstr "Aus der Bereitstellung herausnehmen"
+msgstr "Aus Commit-Bereitstellung herausnehmen"
 
 #: git-gui.sh:2942 lib/index.tcl:521
 msgid "Revert Changes"
@@ -371,7 +367,7 @@ msgstr "Zusammenführen abbrechen..."
 
 #: git-gui.sh:2994 git-gui.sh:3022
 msgid "Add..."
-msgstr "Hinzufügen..."
+msgstr "Neues hinzufügen..."
 
 #: git-gui.sh:2998
 msgid "Push..."
@@ -379,7 +375,7 @@ msgstr "Versenden..."
 
 #: git-gui.sh:3002
 msgid "Delete Branch..."
-msgstr "Zweig löschen..."
+msgstr "Branch löschen..."
 
 #: git-gui.sh:3012 git-gui.sh:3666
 msgid "Options..."
@@ -409,14 +405,13 @@ msgstr "SSH-Schlüssel anzeigen"
 
 #: git-gui.sh:3097 git-gui.sh:3229
 msgid "usage:"
-msgstr ""
+msgstr "Verwendung:"
 
 #: git-gui.sh:3101 git-gui.sh:3233
 msgid "Usage"
-msgstr ""
+msgstr "Verwendung"
 
 #: git-gui.sh:3182 lib/blame.tcl:575
-#, fuzzy
 msgid "Error"
 msgstr "Fehler"
 
@@ -429,7 +424,7 @@ msgstr ""
 
 #: git-gui.sh:3246
 msgid "Current Branch:"
-msgstr "Aktueller Zweig:"
+msgstr "Aktueller Branch:"
 
 #: git-gui.sh:3271
 msgid "Unstaged Changes"
@@ -437,7 +432,7 @@ msgstr "Nicht bereitgestellte Änderungen"
 
 #: git-gui.sh:3293
 msgid "Staged Changes (Will Commit)"
-msgstr "Bereitstellung (zum Eintragen)"
+msgstr "Bereitstellung (zum Committen)"
 
 #: git-gui.sh:3367
 msgid "Stage Changed"
@@ -449,7 +444,7 @@ msgstr "Versenden"
 
 #: git-gui.sh:3413
 msgid "Initial Commit Message:"
-msgstr "Erste Versionsbeschreibung:"
+msgstr "Erste Commit-Beschreibung:"
 
 #: git-gui.sh:3414
 msgid "Amended Commit Message:"
@@ -469,7 +464,7 @@ msgstr "Zusammenführungs-Beschreibung:"
 
 #: git-gui.sh:3418
 msgid "Commit Message:"
-msgstr "Versionsbeschreibung:"
+msgstr "Commit-Beschreibung:"
 
 #: git-gui.sh:3477 git-gui.sh:3641 lib/console.tcl:73
 msgid "Copy All"
@@ -501,25 +496,23 @@ msgstr "Zeichenkodierung"
 
 #: git-gui.sh:3673
 msgid "Apply/Reverse Hunk"
-msgstr "Kontext anwenden/umkehren"
+msgstr "Patch-Block anwenden/zurücknehmen"
 
 #: git-gui.sh:3678
 msgid "Apply/Reverse Line"
-msgstr "Zeile anwenden/umkehren"
+msgstr "Zeile anwenden/zurücknehmen"
 
 #: git-gui.sh:3684 git-gui.sh:3794 git-gui.sh:3805
-#, fuzzy
 msgid "Revert Hunk"
-msgstr "Kontext anwenden/umkehren"
+msgstr "Patch-Block zurücknehmen"
 
 #: git-gui.sh:3689 git-gui.sh:3801 git-gui.sh:3812
-#, fuzzy
 msgid "Revert Line"
-msgstr "Änderungen verwerfen"
+msgstr "Zeilenänderungen zurücknehmen"
 
 #: git-gui.sh:3694 git-gui.sh:3791
 msgid "Undo Last Revert"
-msgstr ""
+msgstr "Letztes Zurücknehmen rückgängig"
 
 #: git-gui.sh:3713
 msgid "Run Merge Tool"
@@ -535,36 +528,35 @@ msgstr "Lokale Version benutzen"
 
 #: git-gui.sh:3726
 msgid "Revert To Base"
-msgstr "Ursprüngliche Version benutzen"
+msgstr "Zurücksetzen auf ursprünglichen Commit"
 
 #: git-gui.sh:3744
 msgid "Visualize These Changes In The Submodule"
-msgstr "Diese Änderungen im Untermodul darstellen"
+msgstr "Diese Änderungen im Submodul darstellen"
 
 #: git-gui.sh:3748
 msgid "Visualize Current Branch History In The Submodule"
-msgstr "Aktuellen Zweig im Untermodul darstellen"
+msgstr "Aktuellen Branch im Submodul darstellen"
 
 #: git-gui.sh:3752
 msgid "Visualize All Branch History In The Submodule"
-msgstr "Alle Zweige im Untermodul darstellen"
+msgstr "Alle Branches im Submodul darstellen"
 
 #: git-gui.sh:3757
 msgid "Start git gui In The Submodule"
-msgstr "Git gui im Untermodul starten"
+msgstr "Git gui im Submodul starten"
 
 #: git-gui.sh:3793
 msgid "Unstage Hunk From Commit"
-msgstr "Kontext aus Bereitstellung herausnehmen"
+msgstr "Patch-Block aus Bereitstellung herausnehmen"
 
 #: git-gui.sh:3797
 msgid "Unstage Lines From Commit"
 msgstr "Zeilen aus der Bereitstellung herausnehmen"
 
 #: git-gui.sh:3798 git-gui.sh:3809
-#, fuzzy
 msgid "Revert Lines"
-msgstr "Änderungen verwerfen"
+msgstr "Zeilenänderung zurücknehmen"
 
 #: git-gui.sh:3800
 msgid "Unstage Line From Commit"
@@ -572,7 +564,7 @@ msgstr "Zeile aus der Bereitstellung herausnehmen"
 
 #: git-gui.sh:3804
 msgid "Stage Hunk For Commit"
-msgstr "Kontext zur Bereitstellung hinzufügen"
+msgstr "Patch-Block zur Bereitstellung hinzufügen"
 
 #: git-gui.sh:3808
 msgid "Stage Lines For Commit"
@@ -678,31 +670,29 @@ msgstr "Neue Änderungen von »%s« holen"
 #: lib/transport.tcl:18
 #, tcl-format
 msgid "remote prune %s"
-msgstr "Aufräumen von »%s«"
+msgstr "Gelöschte externe Branches aus »%s« entfernen"
 
 #: lib/transport.tcl:19
 #, tcl-format
 msgid "Pruning tracking branches deleted from %s"
-msgstr "Übernahmezweige aufräumen und entfernen, die in »%s« gelöscht wurden"
+msgstr "Gelöschte externe Trackingbranches aus »%s« werden entfernt"
 
 #: lib/transport.tcl:25
 msgid "fetch all remotes"
-msgstr ""
+msgstr "Abrufen aller externen"
 
 #: lib/transport.tcl:26
-#, fuzzy
 msgid "Fetching new changes from all remotes"
-msgstr "Neue Änderungen von »%s« holen"
+msgstr "Neue Änderungen von allen externen anfordern"
 
 #: lib/transport.tcl:40
-#, fuzzy
 msgid "remote prune all remotes"
-msgstr "Aufräumen von »%s«"
+msgstr "Extern veraltete Branches entfernen aller Repositories"
 
 #: lib/transport.tcl:41
-#, fuzzy
 msgid "Pruning tracking branches deleted from all remotes"
-msgstr "Übernahmezweige aufräumen und entfernen, die in »%s« gelöscht wurden"
+msgstr ""
+"Gelöschte externe Trackingbranches aus allen Repositories werden entfernt"
 
 #: lib/transport.tcl:54 lib/transport.tcl:92 lib/transport.tcl:110
 #: lib/remote_add.tcl:162
@@ -727,7 +717,7 @@ msgstr "%s %s nach %s versenden"
 
 #: lib/transport.tcl:132
 msgid "Push Branches"
-msgstr "Zweige versenden"
+msgstr "Branches versenden"
 
 #: lib/transport.tcl:141 lib/checkout_op.tcl:580 lib/remote_add.tcl:34
 #: lib/browser.tcl:292 lib/branch_checkout.tcl:30 lib/branch_rename.tcl:32
@@ -739,19 +729,19 @@ msgstr "Abbrechen"
 
 #: lib/transport.tcl:147
 msgid "Source Branches"
-msgstr "Lokale Zweige"
+msgstr "Lokale Branches"
 
 #: lib/transport.tcl:162
 msgid "Destination Repository"
-msgstr "Ziel-Projektarchiv"
+msgstr "Ziel-Repository"
 
 #: lib/transport.tcl:165 lib/remote_branch_delete.tcl:51
 msgid "Remote:"
-msgstr "Externes Archiv:"
+msgstr "Externes Repository:"
 
 #: lib/transport.tcl:187 lib/remote_branch_delete.tcl:72
 msgid "Arbitrary Location:"
-msgstr "Adresse:"
+msgstr "Beliebige Adresse:"
 
 #: lib/transport.tcl:205
 msgid "Transfer Options"
@@ -760,7 +750,8 @@ msgstr "Netzwerk-Einstellungen"
 #: lib/transport.tcl:207
 msgid "Force overwrite existing branch (may discard changes)"
 msgstr ""
-"Überschreiben von existierenden Zweigen erzwingen (könnte Änderungen löschen)"
+"Überschreiben von existierenden Branches erzwingen (könnte Änderungen "
+"löschen)"
 
 #: lib/transport.tcl:211
 msgid "Use thin pack (for slow network connections)"
@@ -768,12 +759,12 @@ msgstr "Kompaktes Datenformat benutzen (für langsame Netzverbindungen)"
 
 #: lib/transport.tcl:215
 msgid "Include tags"
-msgstr "Mit Markierungen übertragen"
+msgstr "Mit Tags versenden"
 
 #: lib/transport.tcl:229
 #, tcl-format
 msgid "%s (%s): Push"
-msgstr ""
+msgstr "%s (%s): Versenden"
 
 #: lib/checkout_op.tcl:85
 #, tcl-format
@@ -783,7 +774,7 @@ msgstr "Änderungen »%s« von »%s« anfordern"
 #: lib/checkout_op.tcl:133
 #, tcl-format
 msgid "fatal: Cannot resolve %s"
-msgstr "Fehler: »%s« kann nicht als Zweig oder Version erkannt werden"
+msgstr "Fehler: »%s« kann nicht als Branch oder Version erkannt werden"
 
 #: lib/checkout_op.tcl:146 lib/sshkey.tcl:58 lib/console.tcl:81
 #: lib/database.tcl:30
@@ -793,7 +784,7 @@ msgstr "Schließen"
 #: lib/checkout_op.tcl:175
 #, tcl-format
 msgid "Branch '%s' does not exist."
-msgstr "Zweig »%s« existiert nicht."
+msgstr "Branch »%s« existiert nicht."
 
 #: lib/checkout_op.tcl:194
 #, tcl-format
@@ -803,7 +794,7 @@ msgstr "Fehler beim Einrichten der vereinfachten git-pull für »%s«."
 #: lib/checkout_op.tcl:202 lib/branch_rename.tcl:102
 #, tcl-format
 msgid "Branch '%s' already exists."
-msgstr "Zweig »%s« existiert bereits."
+msgstr "Branch »%s« existiert bereits."
 
 #: lib/checkout_op.tcl:229
 #, tcl-format
@@ -813,10 +804,10 @@ msgid ""
 "It cannot fast-forward to %s.\n"
 "A merge is required."
 msgstr ""
-"Zweig »%s« existiert bereits.\n"
+"Branch »%s« existiert bereits.\n"
 "\n"
-"Zweig kann nicht mit »%s« schnellzusammengeführt werden. Reguläres "
-"Zusammenführen ist notwendig."
+"Branch kann nicht auf »%s« vorgespult werden. Reguläres Zusammenführen ist "
+"notwendig."
 
 #: lib/checkout_op.tcl:243
 #, tcl-format
@@ -841,17 +832,17 @@ msgid ""
 "\n"
 "The rescan will be automatically started now.\n"
 msgstr ""
-"Der letzte geladene Status stimmt nicht mehr mit dem Projektarchiv überein.\n"
+"Der letzte geladene Status stimmt nicht mehr mit dem Repository überein.\n"
 "\n"
-"Ein anderes Git-Programm hat das Projektarchiv seit dem letzten Laden "
-"geändert.  Vor dem Wechseln des lokalen Zweigs muss neu geladen werden.\n"
+"Ein anderes Git-Programm hat das Repository seit dem letzten Laden "
+"geändert.  Vor dem Wechseln des lokalen Branches muss neu geladen werden.\n"
 "\n"
 "Es wird gleich neu geladen.\n"
 
 #: lib/checkout_op.tcl:345
 #, tcl-format
 msgid "Updating working directory to '%s'..."
-msgstr "Arbeitskopie umstellen auf »%s«..."
+msgstr "Arbeitskopie aktualisieren auf »%s«..."
 
 #: lib/checkout_op.tcl:346
 msgid "files checked out"
@@ -861,7 +852,7 @@ msgstr "Dateien aktualisiert"
 #, tcl-format
 msgid "Aborted checkout of '%s' (file level merging is required)."
 msgstr ""
-"Auf Zweig »%s« umstellen abgebrochen (Zusammenführen der Dateien ist "
+"Branch »%s« Auschecken abgebrochen (Zusammenführen der Dateien ist "
 "notwendig)."
 
 #: lib/checkout_op.tcl:378
@@ -871,7 +862,7 @@ msgstr "Zusammenführen der Dateien ist notwendig."
 #: lib/checkout_op.tcl:382
 #, tcl-format
 msgid "Staying on branch '%s'."
-msgstr "Es wird auf Zweig »%s« verblieben."
+msgstr "Es wird auf Branch »%s« verblieben."
 
 #: lib/checkout_op.tcl:453
 msgid ""
@@ -880,10 +871,10 @@ msgid ""
 "If you wanted to be on a branch, create one now starting from 'This Detached "
 "Checkout'."
 msgstr ""
-"Die Arbeitskopie ist nicht auf einem lokalen Zweig.\n"
+"Die Arbeitskopie ist nicht auf einem lokalen Branch.\n"
 "\n"
-"Wenn Sie auf einem Zweig arbeiten möchten, erstellen Sie bitte jetzt einen "
-"Zweig mit der Auswahl »Abgetrennte Arbeitskopie-Version«."
+"Wenn Sie auf einem Branch arbeiten möchten, erstellen Sie bitte jetzt einen "
+"Branch mit der Auswahl »Losgelöste Arbeitskopie-Version«."
 
 #: lib/checkout_op.tcl:504 lib/checkout_op.tcl:508
 #, tcl-format
@@ -893,18 +884,17 @@ msgstr "Umgestellt auf »%s«."
 #: lib/checkout_op.tcl:536
 #, tcl-format
 msgid "Resetting '%s' to '%s' will lose the following commits:"
-msgstr "Zurücksetzen von »%s« nach »%s« wird folgende Versionen verwerfen:"
+msgstr "Umsetzen von »%s« nach »%s« wird folgende Commits verlieren:"
 
 #: lib/checkout_op.tcl:558
 msgid "Recovering lost commits may not be easy."
 msgstr ""
-"Verworfene Versionen können nur mit größerem Aufwand wiederhergestellt "
-"werden."
+"Verlorene Commits können nur mit größerem Aufwand wiederhergestellt werden."
 
 #: lib/checkout_op.tcl:563
 #, tcl-format
 msgid "Reset '%s'?"
-msgstr "»%s« zurücksetzen?"
+msgstr "»%s« umsetzen?"
 
 #: lib/checkout_op.tcl:568 lib/tools_dlg.tcl:336 lib/merge.tcl:170
 msgid "Visualize"
@@ -912,7 +902,7 @@ msgstr "Darstellen"
 
 #: lib/checkout_op.tcl:572 lib/branch_create.tcl:85
 msgid "Reset"
-msgstr "Zurücksetzen"
+msgstr "Umsetzen (Reset)"
 
 #: lib/checkout_op.tcl:636
 #, tcl-format
@@ -924,7 +914,7 @@ msgid ""
 "\n"
 "This should not have occurred.  %s will now close and give up."
 msgstr ""
-"Lokaler Zweig kann nicht gesetzt werden.\n"
+"Lokaler Branch kann nicht gesetzt werden.\n"
 "\n"
 "Diese Arbeitskopie ist nur teilweise umgestellt. Die Dateien sind korrekt "
 "aktualisiert, aber einige interne Git-Dateien konnten nicht geändert "
@@ -933,13 +923,13 @@ msgstr ""
 "Dies ist ein interner Programmfehler von %s. Programm wird jetzt abgebrochen."
 
 #: lib/remote_add.tcl:20
-#, fuzzy, tcl-format
+#, tcl-format
 msgid "%s (%s): Add Remote"
-msgstr "Externes Archiv hinzufügen"
+msgstr "%s (%s): Externes Repository hinzufügen"
 
 #: lib/remote_add.tcl:25
 msgid "Add New Remote"
-msgstr "Neues externes Archiv hinzufügen"
+msgstr "Neues externes Repository hinzufügen"
 
 #: lib/remote_add.tcl:30 lib/tools_dlg.tcl:37
 msgid "Add"
@@ -947,7 +937,7 @@ msgstr "Hinzufügen"
 
 #: lib/remote_add.tcl:39
 msgid "Remote Details"
-msgstr "Einzelheiten des externen Archivs"
+msgstr "Einzelheiten des externen Repository"
 
 #: lib/remote_add.tcl:41 lib/tools_dlg.tcl:51 lib/branch_create.tcl:44
 msgid "Name:"
@@ -959,34 +949,33 @@ msgstr "Adresse:"
 
 #: lib/remote_add.tcl:60
 msgid "Further Action"
-msgstr "Weitere Aktion jetzt"
+msgstr "Weitere Aktion"
 
 #: lib/remote_add.tcl:63
 msgid "Fetch Immediately"
-msgstr "Gleich anfordern"
+msgstr "Jetzt anfordern"
 
 #: lib/remote_add.tcl:69
 msgid "Initialize Remote Repository and Push"
-msgstr "Externes Archiv initialisieren und dahin versenden"
+msgstr "Externes Repository initialisieren und dahin versenden"
 
 #: lib/remote_add.tcl:75
 msgid "Do Nothing Else Now"
-msgstr "Nichts tun"
+msgstr "Keine weitere Aktion"
 
 #: lib/remote_add.tcl:100
 msgid "Please supply a remote name."
-msgstr "Bitte geben Sie einen Namen des externen Archivs an."
+msgstr "Bitte geben Sie einen Namen des externen Repository an."
 
 #: lib/remote_add.tcl:113
 #, tcl-format
 msgid "'%s' is not an acceptable remote name."
-msgstr "»%s« ist kein zulässiger Name eines externen Archivs."
+msgstr "»%s« ist kein zulässiger Name eines externen Repository."
 
 #: lib/remote_add.tcl:124
 #, tcl-format
 msgid "Failed to add remote '%s' of location '%s'."
-msgstr ""
-"Fehler beim Hinzufügen des externen Archivs »%s« aus Herkunftsort »%s«."
+msgstr "Fehler beim Hinzufügen des externen Repository »%s« aus Adresse »%s«."
 
 #: lib/remote_add.tcl:133
 #, tcl-format
@@ -997,7 +986,7 @@ msgstr "»%s« anfordern"
 #, tcl-format
 msgid "Do not know how to initialize repository at location '%s'."
 msgstr ""
-"Initialisieren eines externen Archivs an Adresse »%s« ist nicht möglich."
+"Initialisieren eines externen Repositories an Adresse »%s« ist nicht möglich."
 
 #: lib/remote_add.tcl:163
 #, tcl-format
@@ -1009,9 +998,9 @@ msgid "Starting..."
 msgstr "Starten..."
 
 #: lib/browser.tcl:27
-#, fuzzy, tcl-format
+#, tcl-format
 msgid "%s (%s): File Browser"
-msgstr "Datei-Browser"
+msgstr "%s (%s): Datei-Browser"
 
 #: lib/browser.tcl:132 lib/browser.tcl:149
 #, tcl-format
@@ -1023,13 +1012,13 @@ msgid "[Up To Parent]"
 msgstr "[Nach oben]"
 
 #: lib/browser.tcl:275
-#, fuzzy, tcl-format
+#, tcl-format
 msgid "%s (%s): Browse Branch Files"
-msgstr "Dateien des Zweigs durchblättern"
+msgstr "%s (%s): Dateien des Branches durchblättern"
 
 #: lib/browser.tcl:282
 msgid "Browse Branch Files"
-msgstr "Dateien des Zweigs durchblättern"
+msgstr "Dateien des Branches durchblättern"
 
 #: lib/browser.tcl:288 lib/choose_repository.tcl:437
 #: lib/choose_repository.tcl:524 lib/choose_repository.tcl:533
@@ -1072,9 +1061,8 @@ msgid "files"
 msgstr "Dateien"
 
 #: lib/index.tcl:326
-#, fuzzy
 msgid "Unstaging selected files from commit"
-msgstr "Datei »%s« aus der Bereitstellung herausnehmen"
+msgstr "Gewählte Dateien aus der Bereitstellung herausnehmen"
 
 #: lib/index.tcl:330
 #, tcl-format
@@ -1083,26 +1071,25 @@ msgstr "Datei »%s« aus der Bereitstellung herausnehmen"
 
 #: lib/index.tcl:369
 msgid "Ready to commit."
-msgstr "Bereit zum Eintragen."
+msgstr "Bereit zum Committen."
 
 #: lib/index.tcl:378
-#, fuzzy
 msgid "Adding selected files"
-msgstr "Änderungen in gewählten Dateien verwerfen"
+msgstr "Gewählte Dateien hinzufügen"
 
 #: lib/index.tcl:382
 #, tcl-format
 msgid "Adding %s"
-msgstr "»%s« hinzufügen..."
+msgstr "»%s« hinzufügen"
 
 #: lib/index.tcl:412
 #, tcl-format
 msgid "Stage %d untracked files?"
-msgstr ""
+msgstr "%d unversionierte Dateien bereitstellen?"
 
 #: lib/index.tcl:420
 msgid "Adding all changed files"
-msgstr ""
+msgstr "Alle geänderten Dateien hinzufügen"
 
 #: lib/index.tcl:503
 #, tcl-format
@@ -1112,7 +1099,7 @@ msgstr "Änderungen in Datei »%s« verwerfen?"
 #: lib/index.tcl:508
 #, tcl-format
 msgid "Revert changes in these %i files?"
-msgstr "Änderungen in den gewählten %i Dateien verwerfen?"
+msgstr "Änderungen in diesen %i Dateien verwerfen?"
 
 #: lib/index.tcl:517
 msgid "Any unstaged changes will be permanently lost by the revert."
@@ -1124,42 +1111,40 @@ msgid "Do Nothing"
 msgstr "Nichts tun"
 
 #: lib/index.tcl:545
-#, fuzzy, tcl-format
+#, tcl-format
 msgid "Delete untracked file %s?"
-msgstr "Zweige auf »%s« werden gelöscht"
+msgstr "Unversionierte Datei »%s« löschen?"
 
 #: lib/index.tcl:550
-#, fuzzy, tcl-format
+#, tcl-format
 msgid "Delete these %i untracked files?"
-msgstr "Änderungen in den gewählten %i Dateien verwerfen?"
+msgstr "Diese %i unversionierten Dateien löschen?"
 
 #: lib/index.tcl:560
 msgid "Files will be permanently deleted."
-msgstr ""
+msgstr "Dateien werden endgültig gelöscht."
 
 #: lib/index.tcl:564
-#, fuzzy
 msgid "Delete Files"
-msgstr "Löschen"
+msgstr "Dateien löschen"
 
 #: lib/index.tcl:586
-#, fuzzy
 msgid "Deleting"
 msgstr "Löschen"
 
 #: lib/index.tcl:665
 msgid "Encountered errors deleting files:\n"
-msgstr ""
+msgstr "Fehler beim Löschen der Dateien:\n"
 
 #: lib/index.tcl:674
 #, tcl-format
 msgid "None of the %d selected files could be deleted."
-msgstr ""
+msgstr "Keine der %d gewählten Dateien konnten gelöscht werden."
 
 #: lib/index.tcl:679
 #, tcl-format
 msgid "%d of the %d selected files could not be deleted."
-msgstr ""
+msgstr "%d der %d gewählten Dateien konnten nicht gelöscht werden."
 
 #: lib/index.tcl:726
 msgid "Reverting selected files"
@@ -1171,17 +1156,17 @@ msgid "Reverting %s"
 msgstr "Änderungen in %s verwerfen"
 
 #: lib/branch_checkout.tcl:16
-#, fuzzy, tcl-format
+#, tcl-format
 msgid "%s (%s): Checkout Branch"
-msgstr "Auf Zweig umstellen"
+msgstr "%s (%s): Branch auschecken"
 
 #: lib/branch_checkout.tcl:21
 msgid "Checkout Branch"
-msgstr "Auf Zweig umstellen"
+msgstr "Branch auschecken"
 
 #: lib/branch_checkout.tcl:26
 msgid "Checkout"
-msgstr "Umstellen"
+msgstr "Auschecken"
 
 #: lib/branch_checkout.tcl:39 lib/option.tcl:310 lib/branch_create.tcl:69
 msgid "Options"
@@ -1189,11 +1174,11 @@ msgstr "Optionen"
 
 #: lib/branch_checkout.tcl:42 lib/branch_create.tcl:92
 msgid "Fetch Tracking Branch"
-msgstr "Übernahmezweig anfordern"
+msgstr "Trackingbranch anfordern"
 
 #: lib/branch_checkout.tcl:47
 msgid "Detach From Local Branch"
-msgstr "Verbindung zu lokalem Zweig lösen"
+msgstr "Verbindung zu lokalem Branch lösen"
 
 #: lib/status_bar.tcl:263
 #, tcl-format
@@ -1206,28 +1191,28 @@ msgstr "Versenden nach"
 
 #: lib/remote.tcl:218
 msgid "Remove Remote"
-msgstr "Externes Archiv entfernen"
+msgstr "Externes Repository entfernen"
 
 #: lib/remote.tcl:223
 msgid "Prune from"
-msgstr "Aufräumen von"
+msgstr "Veraltete Branches entfernen"
 
 #: lib/remote.tcl:228
 msgid "Fetch from"
-msgstr "Anfordern von"
+msgstr "Anfordern"
 
 #: lib/remote.tcl:249 lib/remote.tcl:253 lib/remote.tcl:258 lib/remote.tcl:264
 msgid "All"
-msgstr ""
+msgstr "Alle"
 
 #: lib/branch_rename.tcl:15
-#, fuzzy, tcl-format
+#, tcl-format
 msgid "%s (%s): Rename Branch"
-msgstr "Zweig umbenennen"
+msgstr "%s (%s): Branch umbenennen"
 
 #: lib/branch_rename.tcl:23
 msgid "Rename Branch"
-msgstr "Zweig umbenennen"
+msgstr "Branch umbenennen"
 
 #: lib/branch_rename.tcl:28
 msgid "Rename"
@@ -1235,7 +1220,7 @@ msgstr "Umbenennen"
 
 #: lib/branch_rename.tcl:38
 msgid "Branch:"
-msgstr "Zweig:"
+msgstr "Branch:"
 
 #: lib/branch_rename.tcl:46
 msgid "New Name:"
@@ -1243,16 +1228,16 @@ msgstr "Neuer Name:"
 
 #: lib/branch_rename.tcl:81
 msgid "Please select a branch to rename."
-msgstr "Bitte wählen Sie einen Zweig zum umbenennen."
+msgstr "Bitte wählen Sie einen Branch zum umbenennen."
 
 #: lib/branch_rename.tcl:92 lib/branch_create.tcl:154
 msgid "Please supply a branch name."
-msgstr "Bitte geben Sie einen Zweignamen an."
+msgstr "Bitte geben Sie einen Branchnamen an."
 
 #: lib/branch_rename.tcl:112 lib/branch_create.tcl:165
 #, tcl-format
 msgid "'%s' is not an acceptable branch name."
-msgstr "»%s« ist kein zulässiger Zweigname."
+msgstr "»%s« ist kein zulässiger Branchname."
 
 #: lib/branch_rename.tcl:123
 #, tcl-format
@@ -1291,7 +1276,7 @@ msgstr "Ungültige globale Zeichenkodierung »%s«"
 #: lib/option.tcl:19
 #, tcl-format
 msgid "Invalid repo encoding '%s'"
-msgstr "Ungültige Archiv-Zeichenkodierung »%s«"
+msgstr "Ungültige Repository-Zeichenkodierung »%s«"
 
 #: lib/option.tcl:119
 msgid "Restore Defaults"
@@ -1304,11 +1289,11 @@ msgstr "Speichern"
 #: lib/option.tcl:133
 #, tcl-format
 msgid "%s Repository"
-msgstr "Projektarchiv %s"
+msgstr "%s Repository"
 
 #: lib/option.tcl:134
 msgid "Global (All Repositories)"
-msgstr "Global (Alle Projektarchive)"
+msgstr "Global (Alle Repositories)"
 
 #: lib/option.tcl:140
 msgid "User Name"
@@ -1320,7 +1305,7 @@ msgstr "E-Mail-Adresse"
 
 #: lib/option.tcl:143
 msgid "Summarize Merge Commits"
-msgstr "Zusammenführungs-Versionen zusammenfassen"
+msgstr "Zusammenführungs-Commits zusammenfassen"
 
 #: lib/option.tcl:144
 msgid "Merge Verbosity"
@@ -1340,24 +1325,23 @@ msgstr "Auf Dateiänderungsdatum verlassen"
 
 #: lib/option.tcl:149
 msgid "Prune Tracking Branches During Fetch"
-msgstr "Übernahmezweige aufräumen während Anforderung"
+msgstr "Veraltete Trackingbranches entfernen während Anforderung"
 
 #: lib/option.tcl:150
 msgid "Match Tracking Branches"
-msgstr "Passend zu Übernahmezweig"
+msgstr "Neue Branches automatisch als Trackingbranch"
 
 #: lib/option.tcl:151
 msgid "Use Textconv For Diffs and Blames"
-msgstr ""
+msgstr "Benutze »textconv« für Vergleich und Annotieren"
 
 #: lib/option.tcl:152
 msgid "Blame Copy Only On Changed Files"
 msgstr "Kopie-Annotieren nur bei geänderten Dateien"
 
 #: lib/option.tcl:153
-#, fuzzy
 msgid "Maximum Length of Recent Repositories List"
-msgstr "Zuletzt benutzte Projektarchive"
+msgstr "Anzahl Einträge in »Letzte Repositories«"
 
 #: lib/option.tcl:154
 msgid "Minimum Letters To Blame Copy On"
@@ -1365,7 +1349,7 @@ msgstr "Mindestzahl Zeichen für Kopie-Annotieren"
 
 #: lib/option.tcl:155
 msgid "Blame History Context Radius (days)"
-msgstr "Anzahl Tage für Historien-Kontext"
+msgstr "Anzahl Tage für Annotieren-Historien-Kontext"
 
 #: lib/option.tcl:156
 msgid "Number of Diff Context Lines"
@@ -1373,15 +1357,15 @@ msgstr "Anzahl der Kontextzeilen beim Vergleich"
 
 #: lib/option.tcl:157
 msgid "Additional Diff Parameters"
-msgstr ""
+msgstr "Zusätzliche Vergleich-/diff-Parameter"
 
 #: lib/option.tcl:158
 msgid "Commit Message Text Width"
-msgstr "Textbreite der Versionsbeschreibung"
+msgstr "Textbreite der Commit-Beschreibung"
 
 #: lib/option.tcl:159
 msgid "New Branch Name Template"
-msgstr "Namensvorschlag für neue Zweige"
+msgstr "Namensvorlage für neue Branches"
 
 #: lib/option.tcl:160
 msgid "Default File Contents Encoding"
@@ -1389,25 +1373,25 @@ msgstr "Voreingestellte Zeichenkodierung"
 
 #: lib/option.tcl:161
 msgid "Warn before committing to a detached head"
-msgstr ""
+msgstr "Warnen vor Committen auf losgelöste Branchspitze"
 
 #: lib/option.tcl:162
 msgid "Staging of untracked files"
-msgstr ""
+msgstr "Unversionierte Dateien bereitstellen"
 
 #: lib/option.tcl:163
 msgid "Show untracked files"
-msgstr ""
+msgstr "Unversionierte Dateien anzeigen"
 
 #: lib/option.tcl:164
 msgid "Tab spacing"
-msgstr ""
+msgstr "Tabulator-Breite"
 
 #: lib/option.tcl:182 lib/option.tcl:197 lib/option.tcl:220 lib/option.tcl:282
 #: lib/database.tcl:57
 #, tcl-format
 msgid "%s:"
-msgstr ""
+msgstr "%s:"
 
 #: lib/option.tcl:210
 msgid "Change"
@@ -1457,9 +1441,9 @@ msgid "Running %s requires a selected file."
 msgstr "Um »%s« zu starten, muss eine Datei ausgewählt sein."
 
 #: lib/tools.tcl:92
-#, fuzzy, tcl-format
+#, tcl-format
 msgid "Are you sure you want to run %1$s on file \"%2$s\"?"
-msgstr "Wollen Sie %s wirklich starten?"
+msgstr "Wollen Sie %1$s wirklich auf Datei »%2$s« starten?"
 
 #: lib/tools.tcl:96
 #, tcl-format
@@ -1492,11 +1476,11 @@ msgstr "Konflikt durch Basisversion ersetzen?"
 
 #: lib/mergetool.tcl:9
 msgid "Force resolution to this branch?"
-msgstr "Konflikt durch diesen Zweig ersetzen?"
+msgstr "Konflikt durch diesen Branch ersetzen?"
 
 #: lib/mergetool.tcl:10
 msgid "Force resolution to the other branch?"
-msgstr "Konflikt durch anderen Zweig ersetzen?"
+msgstr "Konflikt durch anderen Branch ersetzen?"
 
 #: lib/mergetool.tcl:14
 #, tcl-format
@@ -1577,9 +1561,9 @@ msgid "Merge tool failed."
 msgstr "Zusammenführungswerkzeug fehlgeschlagen."
 
 #: lib/tools_dlg.tcl:22
-#, fuzzy, tcl-format
+#, tcl-format
 msgid "%s (%s): Add Tool"
-msgstr "Werkzeug hinzufügen"
+msgstr "%s (%s): Werkzeug hinzufügen"
 
 #: lib/tools_dlg.tcl:28
 msgid "Add New Tool Command"
@@ -1641,9 +1625,9 @@ msgstr ""
 "%s"
 
 #: lib/tools_dlg.tcl:187
-#, fuzzy, tcl-format
+#, tcl-format
 msgid "%s (%s): Remove Tool"
-msgstr "Werkzeug entfernen"
+msgstr "%s (%s): Werkzeug entfernen"
 
 #: lib/tools_dlg.tcl:193
 msgid "Remove Tool Commands"
@@ -1655,12 +1639,12 @@ msgstr "Entfernen"
 
 #: lib/tools_dlg.tcl:231
 msgid "(Blue denotes repository-local tools)"
-msgstr "(Werkzeuge für lokales Archiv werden in Blau angezeigt)"
+msgstr "(Werkzeuge für lokales Repository werden in Blau angezeigt)"
 
 #: lib/tools_dlg.tcl:283
-#, fuzzy, tcl-format
+#, tcl-format
 msgid "%s (%s):"
-msgstr "Systemweit (%s)"
+msgstr "%s (%s):"
 
 #: lib/tools_dlg.tcl:292
 #, tcl-format
@@ -1689,16 +1673,16 @@ msgstr "Voriger"
 
 #: lib/search.tcl:52
 msgid "RegExp"
-msgstr ""
+msgstr "RegAusdruck"
 
 #: lib/search.tcl:54
 msgid "Case"
-msgstr ""
+msgstr "Groß/klein"
 
 #: lib/shortcut.tcl:8 lib/shortcut.tcl:43 lib/shortcut.tcl:75
-#, fuzzy, tcl-format
+#, tcl-format
 msgid "%s (%s): Create Desktop Icon"
-msgstr "Desktop-Icon erstellen"
+msgstr "%s (%s): Desktop-Icon erstellen"
 
 #: lib/shortcut.tcl:24 lib/shortcut.tcl:65
 msgid "Cannot write shortcut:"
@@ -1709,21 +1693,21 @@ msgid "Cannot write icon:"
 msgstr "Fehler beim Erstellen des Icons:"
 
 #: lib/remote_branch_delete.tcl:29
-#, fuzzy, tcl-format
+#, tcl-format
 msgid "%s (%s): Delete Branch Remotely"
-msgstr "Zweig in externem Archiv löschen"
+msgstr "%s (%s): Branch in externem Repository löschen"
 
 #: lib/remote_branch_delete.tcl:34
 msgid "Delete Branch Remotely"
-msgstr "Zweig in externem Archiv löschen"
+msgstr "Branch in externem Repository löschen"
 
 #: lib/remote_branch_delete.tcl:48
 msgid "From Repository"
-msgstr "In Projektarchiv"
+msgstr "In Repository"
 
 #: lib/remote_branch_delete.tcl:88
 msgid "Branches"
-msgstr "Zweige"
+msgstr "Branches"
 
 #: lib/remote_branch_delete.tcl:110
 msgid "Delete Only If"
@@ -1739,7 +1723,7 @@ msgstr "Immer (Keine Zusammenführungsprüfung)"
 
 #: lib/remote_branch_delete.tcl:153
 msgid "A branch is required for 'Merged Into'."
-msgstr "Für »Zusammenführen mit« muss ein Zweig angegeben werden."
+msgstr "Für »Zusammenführen mit« muss ein Branch angegeben werden."
 
 #: lib/remote_branch_delete.tcl:185
 #, tcl-format
@@ -1748,7 +1732,7 @@ msgid ""
 "\n"
 " - %s"
 msgstr ""
-"Folgende Zweige sind noch nicht mit »%s« zusammengeführt:\n"
+"Folgende Branches sind noch nicht mit »%s« zusammengeführt:\n"
 "\n"
 " - %s"
 
@@ -1759,12 +1743,12 @@ msgid ""
 "necessary commits.  Try fetching from %s first."
 msgstr ""
 "Ein oder mehrere Zusammenführungen sind fehlgeschlagen, da Sie nicht die "
-"notwendigen Versionen vorher angefordert haben.  Sie sollten versuchen, "
-"zuerst von »%s« anzufordern."
+"notwendigen Commits vorher angefordert haben.  Sie sollten versuchen, zuerst "
+"von »%s« anzufordern."
 
 #: lib/remote_branch_delete.tcl:208
 msgid "Please select one or more branches to delete."
-msgstr "Bitte wählen Sie mindestens einen Zweig, der gelöscht werden soll."
+msgstr "Bitte wählen Sie mindestens einen Branch, der gelöscht werden soll."
 
 #: lib/remote_branch_delete.tcl:218 lib/branch_delete.tcl:115
 msgid ""
@@ -1772,19 +1756,19 @@ msgid ""
 "\n"
 "Delete the selected branches?"
 msgstr ""
-"Das Wiederherstellen von gelöschten Zweigen ist nur mit größerem Aufwand "
+"Das Wiederherstellen von gelöschten Branches ist nur mit größerem Aufwand "
 "möglich.\n"
 "\n"
-"Sollen die ausgewählten Zweige gelöscht werden?"
+"Sollen die ausgewählten Branches gelöscht werden?"
 
 #: lib/remote_branch_delete.tcl:227
 #, tcl-format
 msgid "Deleting branches from %s"
-msgstr "Zweige auf »%s« werden gelöscht"
+msgstr "Branches auf »%s« werden gelöscht"
 
 #: lib/remote_branch_delete.tcl:300
 msgid "No repository selected."
-msgstr "Kein Projektarchiv ausgewählt."
+msgstr "Kein Repository ausgewählt."
 
 #: lib/remote_branch_delete.tcl:305
 #, tcl-format
@@ -1797,7 +1781,7 @@ msgstr "Git Gui"
 
 #: lib/choose_repository.tcl:104 lib/choose_repository.tcl:427
 msgid "Create New Repository"
-msgstr "Neues Projektarchiv"
+msgstr "Repository neu erstellen"
 
 #: lib/choose_repository.tcl:110
 msgid "New..."
@@ -1805,7 +1789,7 @@ msgstr "Neu..."
 
 #: lib/choose_repository.tcl:117 lib/choose_repository.tcl:511
 msgid "Clone Existing Repository"
-msgstr "Projektarchiv klonen"
+msgstr "Repository klonen"
 
 #: lib/choose_repository.tcl:128
 msgid "Clone..."
@@ -1813,7 +1797,7 @@ msgstr "Klonen..."
 
 #: lib/choose_repository.tcl:135 lib/choose_repository.tcl:1105
 msgid "Open Existing Repository"
-msgstr "Projektarchiv öffnen"
+msgstr "Repository öffnen"
 
 #: lib/choose_repository.tcl:141
 msgid "Open..."
@@ -1821,17 +1805,17 @@ msgstr "Öffnen..."
 
 #: lib/choose_repository.tcl:154
 msgid "Recent Repositories"
-msgstr "Zuletzt benutzte Projektarchive"
+msgstr "Letzte Repositories"
 
 #: lib/choose_repository.tcl:164
 msgid "Open Recent Repository:"
-msgstr "Zuletzt benutztes Projektarchiv öffnen:"
+msgstr "Zuletzt benutztes Repository öffnen:"
 
 #: lib/choose_repository.tcl:331 lib/choose_repository.tcl:338
 #: lib/choose_repository.tcl:345
 #, tcl-format
 msgid "Failed to create repository %s:"
-msgstr "Projektarchiv »%s« konnte nicht erstellt werden:"
+msgstr "Repository »%s« konnte nicht erstellt werden:"
 
 #: lib/choose_repository.tcl:422 lib/branch_create.tcl:33
 msgid "Create"
@@ -1844,7 +1828,7 @@ msgstr "Verzeichnis:"
 #: lib/choose_repository.tcl:462 lib/choose_repository.tcl:588
 #: lib/choose_repository.tcl:1139
 msgid "Git Repository"
-msgstr "Git Projektarchiv"
+msgstr "Git Repository"
 
 #: lib/choose_repository.tcl:487
 #, tcl-format
@@ -1862,7 +1846,7 @@ msgstr "Klonen"
 
 #: lib/choose_repository.tcl:519
 msgid "Source Location:"
-msgstr "Herkunft:"
+msgstr "Herkunfts-Adresse:"
 
 #: lib/choose_repository.tcl:528
 msgid "Target Directory:"
@@ -1886,27 +1870,27 @@ msgstr "Verknüpft (schnell, nicht empfohlen, kein Backup)"
 
 #: lib/choose_repository.tcl:560
 msgid "Recursively clone submodules too"
-msgstr ""
+msgstr "Rekursiv weitere Submodule klonen"
 
 #: lib/choose_repository.tcl:594 lib/choose_repository.tcl:641
 #: lib/choose_repository.tcl:790 lib/choose_repository.tcl:864
 #: lib/choose_repository.tcl:1145 lib/choose_repository.tcl:1153
 #, tcl-format
 msgid "Not a Git repository: %s"
-msgstr "Kein Git-Projektarchiv in »%s« gefunden."
+msgstr "Kein Git-Repository: %s"
 
 #: lib/choose_repository.tcl:630
 msgid "Standard only available for local repository."
-msgstr "Standard ist nur für lokale Projektarchive verfügbar."
+msgstr "Standard ist nur für lokale Repositories verfügbar."
 
 #: lib/choose_repository.tcl:634
 msgid "Shared only available for local repository."
-msgstr "Verknüpft ist nur für lokale Projektarchive verfügbar."
+msgstr "Verknüpft ist nur für lokale Repositories verfügbar."
 
 #: lib/choose_repository.tcl:655
 #, tcl-format
 msgid "Location %s already exists."
-msgstr "Projektarchiv »%s« existiert bereits."
+msgstr "Adresse »%s« existiert bereits."
 
 #: lib/choose_repository.tcl:666
 msgid "Failed to configure origin"
@@ -1933,7 +1917,7 @@ msgstr "Von »%s« konnte nichts geklont werden."
 #: lib/choose_repository.tcl:742 lib/choose_repository.tcl:962
 #: lib/choose_repository.tcl:974
 msgid "The 'master' branch has not been initialized."
-msgstr "Der »master«-Zweig wurde noch nicht initialisiert."
+msgstr "Der »master«-Branch wurde noch nicht initialisiert."
 
 #: lib/choose_repository.tcl:755
 msgid "Hardlinks are unavailable.  Falling back to copying."
@@ -1973,19 +1957,19 @@ msgstr "Für Objekt konnte kein Hardlink erstellt werden: %s"
 #: lib/choose_repository.tcl:903
 msgid "Cannot fetch branches and objects.  See console output for details."
 msgstr ""
-"Zweige und Objekte konnten nicht angefordert werden.  Kontrollieren Sie die "
-"Ausgaben auf der Konsole für weitere Angaben."
+"Branches und Objekte konnten nicht angefordert werden.  Kontrollieren Sie "
+"die Ausgaben auf der Konsole für weitere Angaben."
 
 #: lib/choose_repository.tcl:914
 msgid "Cannot fetch tags.  See console output for details."
 msgstr ""
-"Markierungen konnten nicht angefordert werden.  Kontrollieren Sie die "
-"Ausgaben auf der Konsole für weitere Angaben."
+"Tags konnten nicht angefordert werden.  Kontrollieren Sie die Ausgaben auf "
+"der Konsole für weitere Angaben."
 
 #: lib/choose_repository.tcl:938
 msgid "Cannot determine HEAD.  See console output for details."
 msgstr ""
-"Die Zweigspitze (HEAD) konnte nicht gefunden werden.  Kontrollieren Sie die "
+"Die Branchspitze (HEAD) konnte nicht gefunden werden.  Kontrollieren Sie die "
 "Ausgaben auf der Konsole für weitere Angaben."
 
 #: lib/choose_repository.tcl:947
@@ -1999,12 +1983,12 @@ msgstr "Klonen fehlgeschlagen."
 
 #: lib/choose_repository.tcl:960
 msgid "No default branch obtained."
-msgstr "Kein voreingestellter Zweig gefunden."
+msgstr "Kein voreingestellter Branch gefunden."
 
 #: lib/choose_repository.tcl:971
 #, tcl-format
 msgid "Cannot resolve %s as a commit."
-msgstr "»%s« wurde nicht als Version gefunden."
+msgstr "»%s« wurde nicht als Commit gefunden."
 
 #: lib/choose_repository.tcl:998
 msgid "Creating working directory"
@@ -2015,48 +1999,46 @@ msgid "Initial file checkout failed."
 msgstr "Erstellen der Arbeitskopie fehlgeschlagen."
 
 #: lib/choose_repository.tcl:1072
-#, fuzzy
 msgid "Cloning submodules"
-msgstr "Kopieren von »%s«"
+msgstr "Klone Submodul"
 
 #: lib/choose_repository.tcl:1087
 msgid "Cannot clone submodules."
-msgstr ""
+msgstr "Submodul konnte nicht geklont werden."
 
 #: lib/choose_repository.tcl:1110
 msgid "Repository:"
-msgstr "Projektarchiv:"
+msgstr "Repository:"
 
 #: lib/choose_repository.tcl:1159
 #, tcl-format
 msgid "Failed to open repository %s:"
-msgstr "Projektarchiv »%s« konnte nicht geöffnet werden."
+msgstr "Repository »%s« konnte nicht geöffnet werden."
 
 #: lib/about.tcl:26
 msgid "git-gui - a graphical user interface for Git."
 msgstr "git-gui - eine grafische Oberfläche für Git."
 
 #: lib/blame.tcl:74
-#, fuzzy, tcl-format
+#, tcl-format
 msgid "%s (%s): File Viewer"
-msgstr "Datei-Browser"
+msgstr "%s (%s): Datei-Browser"
 
 #: lib/blame.tcl:80
 msgid "Commit:"
-msgstr "Version:"
+msgstr "Commit:"
 
 #: lib/blame.tcl:282
 msgid "Copy Commit"
-msgstr "Version kopieren"
+msgstr "Commit kopieren"
 
 #: lib/blame.tcl:286
 msgid "Find Text..."
 msgstr "Text suchen..."
 
 #: lib/blame.tcl:290
-#, fuzzy
 msgid "Goto Line..."
-msgstr "Klonen..."
+msgstr "Gehe zu Zeile..."
 
 #: lib/blame.tcl:299
 msgid "Do Full Copy Detection"
@@ -2068,7 +2050,7 @@ msgstr "Historien-Kontext anzeigen"
 
 #: lib/blame.tcl:306
 msgid "Blame Parent Commit"
-msgstr "Elternversion annotieren"
+msgstr "Elterncommit annotieren"
 
 #: lib/blame.tcl:468
 #, tcl-format
@@ -2113,7 +2095,7 @@ msgstr "Autor:"
 
 #: lib/blame.tcl:1014
 msgid "Committer:"
-msgstr "Eintragender:"
+msgstr "Committer:"
 
 #: lib/blame.tcl:1019
 msgid "Original File:"
@@ -2121,15 +2103,15 @@ msgstr "Ursprüngliche Datei:"
 
 #: lib/blame.tcl:1067
 msgid "Cannot find HEAD commit:"
-msgstr "Zweigspitze (»HEAD«) kann nicht gefunden werden:"
+msgstr "Branchspitze (»HEAD commit«) kann nicht gefunden werden:"
 
 #: lib/blame.tcl:1122
 msgid "Cannot find parent commit:"
-msgstr "Elternversion kann nicht gefunden werden:"
+msgstr "Elterncommit kann nicht gefunden werden:"
 
 #: lib/blame.tcl:1137
 msgid "Unable to display parent"
-msgstr "Elternversion kann nicht angezeigt werden"
+msgstr "Elterncommit kann nicht angezeigt werden"
 
 #: lib/blame.tcl:1138 lib/diff.tcl:345
 msgid "Error loading diff:"
@@ -2180,14 +2162,14 @@ msgid ""
 "REMOTE:\n"
 msgstr ""
 "LOKAL: gelöscht\n"
-"ANDERES:\n"
+"EXTERN:\n"
 
 #: lib/diff.tcl:148
 msgid ""
 "REMOTE: deleted\n"
 "LOCAL:\n"
 msgstr ""
-"ANDERES: gelöscht\n"
+"EXTERN: gelöscht\n"
 "LOKAL:\n"
 
 #: lib/diff.tcl:155
@@ -2196,7 +2178,7 @@ msgstr "LOKAL:\n"
 
 #: lib/diff.tcl:158
 msgid "REMOTE:\n"
-msgstr "ANDERES:\n"
+msgstr "EXTERN:\n"
 
 #: lib/diff.tcl:220 lib/diff.tcl:344
 #, tcl-format
@@ -2209,7 +2191,7 @@ msgstr "Fehler beim Laden der Datei:"
 
 #: lib/diff.tcl:227
 msgid "Git Repository (subproject)"
-msgstr "Git-Projektarchiv (Unterprojekt)"
+msgstr "Git-Repository (Subprojekt)"
 
 #: lib/diff.tcl:239
 msgid "* Binary file (not showing content)."
@@ -2221,7 +2203,7 @@ msgid ""
 "* Untracked file is %d bytes.\n"
 "* Showing only first %d bytes.\n"
 msgstr ""
-"* Datei nicht unter Versionskontrolle, Dateigröße %d Bytes.\n"
+"* Unversionierte Datei hat %d Bytes.\n"
 "* Nur erste %d Bytes werden angezeigt.\n"
 
 #: lib/diff.tcl:250
@@ -2232,40 +2214,37 @@ msgid ""
 "* To see the entire file, use an external editor.\n"
 msgstr ""
 "\n"
-"* Datei nicht unter Versionskontrolle, hier abgeschnitten durch %s.\n"
+"* Unversionierte Datei, hier abgeschnitten durch %s.\n"
 "* Zum Ansehen der vollständigen Datei externen Editor benutzen.\n"
 
 #: lib/diff.tcl:583
 msgid "Failed to unstage selected hunk."
 msgstr ""
-"Fehler beim Herausnehmen des gewählten Kontexts aus der Bereitstellung."
+"Fehler beim Herausnehmen des gewählten Patch-Blocks aus der Bereitstellung."
 
 #: lib/diff.tcl:591
-#, fuzzy
 msgid "Failed to revert selected hunk."
-msgstr "Fehler beim Bereitstellen des gewählten Kontexts."
+msgstr "Fehler beim Zurücknehmen des gewählten Patch-Blocks."
 
 #: lib/diff.tcl:594
 msgid "Failed to stage selected hunk."
-msgstr "Fehler beim Bereitstellen des gewählten Kontexts."
+msgstr "Fehler beim Bereitstellen des gewählten Patch-Blocks."
 
 #: lib/diff.tcl:687
 msgid "Failed to unstage selected line."
 msgstr "Fehler beim Herausnehmen der gewählten Zeile aus der Bereitstellung."
 
 #: lib/diff.tcl:696
-#, fuzzy
 msgid "Failed to revert selected line."
-msgstr "Fehler beim Bereitstellen der gewählten Zeile."
+msgstr "Fehler beim Zurücknehmen der gewählten Zeile."
 
 #: lib/diff.tcl:700
 msgid "Failed to stage selected line."
 msgstr "Fehler beim Bereitstellen der gewählten Zeile."
 
 #: lib/diff.tcl:889
-#, fuzzy
 msgid "Failed to undo last revert."
-msgstr "Aktualisieren von »%s« fehlgeschlagen."
+msgstr "Fehler beim Rückgängigmachen des letzten Zurücknehmen-Commits"
 
 #: lib/sshkey.tcl:34
 msgid "No keys found."
@@ -2317,21 +2296,21 @@ msgid "Your key is in: %s"
 msgstr "Ihr Schlüssel ist abgelegt in: %s"
 
 #: lib/branch_create.tcl:23
-#, fuzzy, tcl-format
+#, tcl-format
 msgid "%s (%s): Create Branch"
-msgstr "Zweig erstellen"
+msgstr "%s (%s): Branch erstellen"
 
 #: lib/branch_create.tcl:28
 msgid "Create New Branch"
-msgstr "Neuen Zweig erstellen"
+msgstr "Neuen Branch erstellen"
 
 #: lib/branch_create.tcl:42
 msgid "Branch Name"
-msgstr "Zweigname"
+msgstr "Branchname"
 
 #: lib/branch_create.tcl:57
 msgid "Match Tracking Branch Name"
-msgstr "Passend zu Übernahmezweig-Name"
+msgstr "Passend zu Trackingbranch-Name"
 
 #: lib/branch_create.tcl:66
 msgid "Starting Revision"
@@ -2339,7 +2318,7 @@ msgstr "Anfangsversion"
 
 #: lib/branch_create.tcl:72
 msgid "Update Existing Branch:"
-msgstr "Existierenden Zweig aktualisieren:"
+msgstr "Existierenden Branch aktualisieren:"
 
 #: lib/branch_create.tcl:75
 msgid "No"
@@ -2347,20 +2326,20 @@ msgstr "Nein"
 
 #: lib/branch_create.tcl:80
 msgid "Fast Forward Only"
-msgstr "Nur Schnellzusammenführung"
+msgstr "Nur Vorspulen"
 
 #: lib/branch_create.tcl:97
 msgid "Checkout After Creation"
-msgstr "Arbeitskopie umstellen nach Erstellen"
+msgstr "Branch auschecken nach Erstellen"
 
 #: lib/branch_create.tcl:132
 msgid "Please select a tracking branch."
-msgstr "Bitte wählen Sie einen Übernahmezweig."
+msgstr "Bitte wählen Sie einen Trackingbranch."
 
 #: lib/branch_create.tcl:141
 #, tcl-format
 msgid "Tracking branch %s is not a branch in the remote repository."
-msgstr "Übernahmezweig »%s« ist kein Zweig im externen Projektarchiv."
+msgstr "Trackingbranch »%s« ist kein Branch im externen Repository."
 
 #: lib/console.tcl:59
 msgid "Working... please wait..."
@@ -2376,31 +2355,31 @@ msgstr "Fehler: Kommando fehlgeschlagen"
 
 #: lib/line.tcl:17
 msgid "Goto Line:"
-msgstr ""
+msgstr "Gehe zu Zeile:"
 
 #: lib/line.tcl:23
 msgid "Go"
-msgstr ""
+msgstr "Gehe"
 
 #: lib/choose_rev.tcl:52
 msgid "This Detached Checkout"
-msgstr "Abgetrennte Arbeitskopie-Version"
+msgstr "Losgelöste Arbeitskopie-Version"
 
 #: lib/choose_rev.tcl:60
 msgid "Revision Expression:"
-msgstr "Version Regexp-Ausdruck:"
+msgstr "Version Regex-Ausdruck:"
 
 #: lib/choose_rev.tcl:72
 msgid "Local Branch"
-msgstr "Lokaler Zweig"
+msgstr "Lokaler Branch"
 
 #: lib/choose_rev.tcl:77
 msgid "Tracking Branch"
-msgstr "Übernahmezweig"
+msgstr "Trackingbranch"
 
 #: lib/choose_rev.tcl:82 lib/choose_rev.tcl:544
 msgid "Tag"
-msgstr "Markierung"
+msgstr "Tag"
 
 #: lib/choose_rev.tcl:321
 #, tcl-format
@@ -2430,10 +2409,10 @@ msgid ""
 "You are about to create the initial commit.  There is no commit before this "
 "to amend.\n"
 msgstr ""
-"Keine Version zur Nachbesserung vorhanden.\n"
+"Kein Commit zur Nachbesserung vorhanden.\n"
 "\n"
-"Sie sind dabei, die erste Version zu übertragen. Es gibt keine existierende "
-"Version, die Sie nachbessern könnten.\n"
+"Sie sind dabei, den ersten Commit zu erstellen. Es gibt keinen existierenden "
+"Commit, den Sie nachbessern könnten.\n"
 
 #: lib/commit.tcl:18
 msgid ""
@@ -2443,16 +2422,16 @@ msgid ""
 "completed.  You cannot amend the prior commit unless you first abort the "
 "current merge activity.\n"
 msgstr ""
-"Nachbesserung währen Zusammenführung nicht möglich.\n"
+"Nachbesserung bei Zusammenführung nicht möglich.\n"
 "\n"
-"Sie haben das Zusammenführen von Versionen angefangen, aber noch nicht "
-"beendet. Sie können keine vorige Übertragung nachbessern, solange eine "
+"Sie haben das Zusammenführen von Commits angefangen, aber noch nicht "
+"beendet. Sie können keinen vorigen Commit nachbessern, solange eine "
 "unfertige Zusammenführung existiert. Dazu müssen Sie die Zusammenführung "
 "beenden oder abbrechen.\n"
 
 #: lib/commit.tcl:56
 msgid "Error loading commit data for amend:"
-msgstr "Fehler beim Laden der Versionsdaten für Nachbessern:"
+msgstr "Fehler beim Laden der Commitdaten für Nachbessern:"
 
 #: lib/commit.tcl:83
 msgid "Unable to obtain your identity:"
@@ -2476,10 +2455,10 @@ msgid ""
 "\n"
 "The rescan will be automatically started now.\n"
 msgstr ""
-"Der letzte geladene Status stimmt nicht mehr mit dem Projektarchiv überein.\n"
+"Der letzte geladene Status stimmt nicht mehr mit dem Repository überein.\n"
 "\n"
-"Ein anderes Git-Programm hat das Projektarchiv seit dem letzten Laden "
-"geändert.  Vor dem Eintragen einer neuen Version muss neu geladen werden.\n"
+"Ein anderes Git-Programm hat das Repository seit dem letzten Laden "
+"geändert.  Vor dem nächsten Commit muss neu geladen werden.\n"
 "\n"
 "Es wird gleich neu geladen.\n"
 
@@ -2491,10 +2470,11 @@ msgid ""
 "File %s has merge conflicts.  You must resolve them and stage the file "
 "before committing.\n"
 msgstr ""
-"Nicht zusammengeführte Dateien können nicht eingetragen werden.\n"
+"Nicht zusammengeführte Dateien können nicht committet werden.\n"
 "\n"
 "Die Datei »%s« hat noch nicht aufgelöste Zusammenführungs-Konflikte. Sie "
-"müssen diese Konflikte auflösen, bevor Sie eintragen können.\n"
+"müssen diese Konflikte auflösen und die Dateien in die Bereitstellung "
+"hinzufügen, bevor Sie committen können.\n"
 
 #: lib/commit.tcl:190
 #, tcl-format
@@ -2505,7 +2485,7 @@ msgid ""
 msgstr ""
 "Unbekannter Dateizustand »%s«.\n"
 "\n"
-"Datei »%s« kann nicht eingetragen werden.\n"
+"Datei »%s« kann nicht committet werden.\n"
 
 #: lib/commit.tcl:198
 msgid ""
@@ -2513,9 +2493,9 @@ msgid ""
 "\n"
 "You must stage at least 1 file before you can commit.\n"
 msgstr ""
-"Keine Änderungen vorhanden, die eingetragen werden könnten.\n"
+"Keine Änderungen vorhanden, die committet werden könnten.\n"
 "\n"
-"Sie müssen mindestens eine Datei bereitstellen, bevor Sie eintragen können.\n"
+"Sie müssen mindestens eine Datei bereitstellen, bevor Sie committen können.\n"
 
 #: lib/commit.tcl:213
 msgid ""
@@ -2539,11 +2519,11 @@ msgstr ""
 
 #: lib/commit.tcl:244
 msgid "Calling pre-commit hook..."
-msgstr "Aufrufen der Vor-Eintragen-Kontrolle (»pre-commit hook«)..."
+msgstr "Aufrufen des »pre-commit hook«..."
 
 #: lib/commit.tcl:259
 msgid "Commit declined by pre-commit hook."
-msgstr "Eintragen abgelehnt durch Vor-Eintragen-Kontrolle (»pre-commit hook«)."
+msgstr "Committen abgelehnt durch »pre-commit hook«."
 
 #: lib/commit.tcl:278
 msgid ""
@@ -2554,21 +2534,26 @@ msgid ""
 " \n"
 " Do you really want to proceed with your Commit?"
 msgstr ""
+"Sie sind dabei, einen Commit auf losgelöste Branchspitze (»commit to "
+"detached head«) zu erstellen. Das ist riskant, denn wenn Sie zu einem "
+"anderen Branch wechseln, würden Sie diese Änderungen verlieren und es ist "
+"nachträglich schwierig, diese aus dem Commit-Log (»reflog«) wiederzufinden. "
+"Es wird empfohlen, diesen Commit abzubrechen und zunächst einen neuen Branch "
+"zu erstellen.\n"
+"\n"
+" Wollen Sie den Commit trotzdem in dieser Form erstellen?"
 
 #: lib/commit.tcl:299
 msgid "Calling commit-msg hook..."
-msgstr ""
-"Aufrufen der Versionsbeschreibungs-Kontrolle (»commit-message hook«)..."
+msgstr "Aufrufen des »commit-msg hook«..."
 
 #: lib/commit.tcl:314
 msgid "Commit declined by commit-msg hook."
-msgstr ""
-"Eintragen abgelehnt durch Versionsbeschreibungs-Kontrolle (»commit-message "
-"hook«)."
+msgstr "Committen abgelehnt durch »commit-msg hook«."
 
 #: lib/commit.tcl:327
 msgid "Committing changes..."
-msgstr "Änderungen eintragen..."
+msgstr "Änderungen committen..."
 
 #: lib/commit.tcl:344
 msgid "write-tree failed:"
@@ -2576,7 +2561,7 @@ msgstr "write-tree fehlgeschlagen:"
 
 #: lib/commit.tcl:345 lib/commit.tcl:395 lib/commit.tcl:422
 msgid "Commit failed."
-msgstr "Eintragen fehlgeschlagen."
+msgstr "Committen fehlgeschlagen."
 
 #: lib/commit.tcl:362
 #, tcl-format
@@ -2591,16 +2576,16 @@ msgid ""
 "\n"
 "A rescan will be automatically started now.\n"
 msgstr ""
-"Keine Änderungen einzutragen.\n"
+"Keine Änderungen zum committen.\n"
 "\n"
-"Es gibt keine geänderte Datei bei dieser Version und es wurde auch nichts "
+"Es gibt keine geänderte Datei in diesem Commit und es wurde auch nichts "
 "zusammengeführt.\n"
 "\n"
 "Das Arbeitsverzeichnis wird daher jetzt neu geladen.\n"
 
 #: lib/commit.tcl:374
 msgid "No changes to commit."
-msgstr "Keine Änderungen, die eingetragen werden können."
+msgstr "Keine Änderungen, die committet werden können."
 
 #: lib/commit.tcl:394
 msgid "commit-tree failed:"
@@ -2613,20 +2598,20 @@ msgstr "update-ref fehlgeschlagen:"
 #: lib/commit.tcl:514
 #, tcl-format
 msgid "Created commit %s: %s"
-msgstr "Version %s übertragen: %s"
+msgstr "Commit %s erstellt: %s"
 
 #: lib/branch_delete.tcl:16
-#, fuzzy, tcl-format
+#, tcl-format
 msgid "%s (%s): Delete Branch"
-msgstr "Zweig löschen"
+msgstr "%s (%s): Branch löschen"
 
 #: lib/branch_delete.tcl:21
 msgid "Delete Local Branch"
-msgstr "Lokalen Zweig löschen"
+msgstr "Lokalen Branch löschen"
 
 #: lib/branch_delete.tcl:39
 msgid "Local Branches"
-msgstr "Lokale Zweige"
+msgstr "Lokale Branches"
 
 #: lib/branch_delete.tcl:51
 msgid "Delete Only If Merged Into"
@@ -2635,12 +2620,12 @@ msgstr "Nur löschen, wenn zusammengeführt nach"
 #: lib/branch_delete.tcl:103
 #, tcl-format
 msgid "The following branches are not completely merged into %s:"
-msgstr "Folgende Zweige sind noch nicht mit »%s« zusammengeführt:"
+msgstr "Folgende Branches sind noch nicht mit »%s« zusammengeführt:"
 
 #: lib/branch_delete.tcl:131
 #, tcl-format
 msgid " - %s:"
-msgstr ""
+msgstr " - %s:"
 
 #: lib/branch_delete.tcl:141
 #, tcl-format
@@ -2648,7 +2633,7 @@ msgid ""
 "Failed to delete branches:\n"
 "%s"
 msgstr ""
-"Fehler beim Löschen der Zweige:\n"
+"Fehler beim Löschen der Branches:\n"
 "%s"
 
 #: lib/date.tcl:25
@@ -2685,9 +2670,9 @@ msgid "Garbage files"
 msgstr "Dateien im Mülleimer"
 
 #: lib/database.tcl:66
-#, fuzzy, tcl-format
+#, tcl-format
 msgid "%s (%s): Database Statistics"
-msgstr "Datenbankstatistik"
+msgstr "%s (%s): Datenbankstatistik"
 
 #: lib/database.tcl:72
 msgid "Compressing the object database"
@@ -2707,37 +2692,37 @@ msgid ""
 "\n"
 "Compress the database now?"
 msgstr ""
-"Dieses Projektarchiv enthält ungefähr %i nicht verknüpfte Objekte.\n"
+"Dieses Repository enthält ungefähr %i nicht verknüpfte Objekte.\n"
 "\n"
-"Für eine optimale Performance wird empfohlen, die Datenbank des "
-"Projektarchivs zu komprimieren.\n"
+"Für eine optimale Performance wird empfohlen, die Datenbank des Repository "
+"zu komprimieren.\n"
 "\n"
 "Soll die Datenbank jetzt komprimiert werden?"
 
 #: lib/error.tcl:20
-#, fuzzy, tcl-format
+#, tcl-format
 msgid "%s: error"
-msgstr "Fehler"
+msgstr "%s: Fehler"
 
 #: lib/error.tcl:36
-#, fuzzy, tcl-format
+#, tcl-format
 msgid "%s: warning"
-msgstr "Warnung"
+msgstr "%s: Warnung"
 
 #: lib/error.tcl:80
-#, fuzzy, tcl-format
+#, tcl-format
 msgid "%s hook failed:"
-msgstr "Werkzeug fehlgeschlagen: %s"
+msgstr "%s hook fehlgeschlagen:"
 
 #: lib/error.tcl:96
 msgid "You must correct the above errors before committing."
 msgstr ""
-"Sie müssen die obigen Fehler zuerst beheben, bevor Sie eintragen können."
+"Sie müssen die obigen Fehler zuerst beheben, bevor Sie committen können."
 
 #: lib/error.tcl:116
 #, tcl-format
 msgid "%s (%s): error"
-msgstr ""
+msgstr "%s (%s): Fehler"
 
 #: lib/merge.tcl:13
 msgid ""
@@ -2747,7 +2732,7 @@ msgid ""
 msgstr ""
 "Zusammenführen kann nicht gleichzeitig mit Nachbessern durchgeführt werden.\n"
 "\n"
-"Sie müssen zuerst die Nachbesserungs-Version abschließen, bevor Sie "
+"Sie müssen zuerst den Nachbesserungs-Commit abschließen, bevor Sie "
 "zusammenführen können.\n"
 
 #: lib/merge.tcl:27
@@ -2759,9 +2744,9 @@ msgid ""
 "\n"
 "The rescan will be automatically started now.\n"
 msgstr ""
-"Der letzte geladene Status stimmt nicht mehr mit dem Projektarchiv überein.\n"
+"Der letzte geladene Status stimmt nicht mehr mit dem Repository überein.\n"
 "\n"
-"Ein anderes Git-Programm hat das Projektarchiv seit dem letzten Laden "
+"Ein anderes Git-Programm hat das Repository seit dem letzten Laden "
 "geändert.  Vor einem Zusammenführen muss neu geladen werden.\n"
 "\n"
 "Es wird gleich neu geladen.\n"
@@ -2778,10 +2763,11 @@ msgid ""
 msgstr ""
 "Zusammenführung mit Konflikten.\n"
 "\n"
-"Die Datei »%s« enthält Konflikte beim Zusammenführen. Sie müssen diese "
-"Konflikte per Hand auflösen. Anschließend müssen Sie die Datei wieder "
-"bereitstellen und eintragen, um die Zusammenführung abzuschließen. Erst "
-"danach kann eine neue Zusammenführung begonnen werden.\n"
+"Die Datei »%s« enthält Konflikte beim Zusammenführen.\n"
+"\n"
+"Sie müssen diese Konflikte per Hand auflösen. Anschließend müssen Sie die "
+"Datei wieder bereitstellen und committen, um die Zusammenführung "
+"abzuschließen. Erst danach kann eine neue Zusammenführung begonnen werden.\n"
 
 #: lib/merge.tcl:55
 #, tcl-format
@@ -2795,10 +2781,11 @@ msgid ""
 msgstr ""
 "Es liegen Änderungen vor.\n"
 "\n"
-"Die Datei »%s« wurde geändert.  Sie sollten zuerst die bereitgestellte "
-"Version abschließen, bevor Sie eine Zusammenführung beginnen.  Mit dieser "
-"Reihenfolge können Sie mögliche Konflikte beim Zusammenführen wesentlich "
-"einfacher beheben oder abbrechen.\n"
+"Die Datei »%s« wurde geändert.\n"
+"\n"
+"Sie sollten zuerst den bereitgestellten Commit abschließen, bevor Sie eine "
+"Zusammenführung beginnen.  Mit dieser Reihenfolge können Sie mögliche "
+"Konflikte beim Zusammenführen wesentlich einfacher beheben oder abbrechen.\n"
 
 #: lib/merge.tcl:108
 #, tcl-format
@@ -2821,7 +2808,7 @@ msgstr "Zusammenführen fehlgeschlagen. Konfliktauflösung ist notwendig."
 #: lib/merge.tcl:156
 #, tcl-format
 msgid "%s (%s): Merge"
-msgstr ""
+msgstr "%s (%s): Zusammenführen"
 
 #: lib/merge.tcl:164
 #, tcl-format
@@ -2840,7 +2827,7 @@ msgid ""
 msgstr ""
 "Abbruch der Nachbesserung ist nicht möglich.\n"
 "\n"
-"Sie müssen die Nachbesserung der Version abschließen.\n"
+"Sie müssen die Nachbesserung diese Commits abschließen.\n"
 
 #: lib/merge.tcl:228
 msgid ""
@@ -2852,8 +2839,7 @@ msgid ""
 msgstr ""
 "Zusammenführen abbrechen?\n"
 "\n"
-"Wenn Sie abbrechen, gehen alle noch nicht eingetragenen Änderungen "
-"verloren.\n"
+"Wenn Sie abbrechen, gehen alle noch nicht committeten Änderungen verloren.\n"
 "\n"
 "Zusammenführen jetzt abbrechen?"
 
@@ -2865,12 +2851,11 @@ msgid ""
 "\n"
 "Continue with resetting the current changes?"
 msgstr ""
-"Änderungen zurücksetzen?\n"
+"Änderungen verwerfen?\n"
 "\n"
-"Wenn Sie zurücksetzen, gehen alle noch nicht eingetragenen Änderungen "
-"verloren.\n"
+"Alle noch nicht committeten Änderungen würden verloren gehen.\n"
 "\n"
-"Änderungen jetzt zurücksetzen?"
+"Änderungen jetzt verwerfen?"
 
 #: lib/merge.tcl:246
 msgid "Aborting"
@@ -2887,12 +2872,3 @@ msgstr "Abbruch fehlgeschlagen."
 #: lib/merge.tcl:279
 msgid "Abort completed.  Ready."
 msgstr "Abbruch durchgeführt. Bereit."
-
-#~ msgid "Displaying only %s of %s files."
-#~ msgstr "Nur %s von %s Dateien werden angezeigt."
-
-#~ msgid "New Commit"
-#~ msgstr "Neue Version"
-
-#~ msgid "Case-Sensitive"
-#~ msgstr "Groß-/Kleinschreibung unterscheiden"
diff --git a/po/glossary/de.po b/po/glossary/de.po
index 35764d1d22..4c5f233ee5 100644
--- a/po/glossary/de.po
+++ b/po/glossary/de.po
@@ -6,10 +6,11 @@
 msgid ""
 msgstr ""
 "Project-Id-Version: git-gui glossary\n"
-"POT-Creation-Date: 2008-01-07 21:20+0100\n"
-"PO-Revision-Date: 2008-02-16 21:48+0100\n"
+"POT-Creation-Date: 2020-01-26 22:26+0100\n"
+"PO-Revision-Date: 2020-02-09 21:22+0100\n"
 "Last-Translator: Christian Stimming <stimming@tuhh.de>\n"
 "Language-Team: German \n"
+"Language: de_DE\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
@@ -19,6 +20,9 @@ msgid ""
 "English Term (Dear translator: This file will never be visible to the user!)"
 msgstr ""
 "Deutsche Übersetzung.\n"
+"Git-core glossary:\n"
+"  https://github.com/ruester/git-po-de/wiki/Translation-Guidelines\n"
+"\n"
 "Andere deutsche SCM:\n"
 "  http://tortoisesvn.net/docs/release/TortoiseSVN_de/index.html und http://"
 "tortoisesvn.tigris.org/svn/tortoisesvn/trunk/Languages/Tortoise_de.po "
@@ -32,33 +36,77 @@ msgstr ""
 "  http://rapidsvn.tigris.org/svn/rapidsvn/trunk/src/locale/de/rapidsvn.po "
 "(username=guest, password empty, schlecht)"
 
+#. "prematurely stop and abandon an operation"
+msgid "abort"
+msgstr "abbrechen"
+
 #. ""
 msgid "amend"
 msgstr "nachbessern (ergänzen)"
 
+#. "a commit that succeeds the current one in git's graph of commits (not necessarily directly)"
+msgid "ancestor"
+msgstr "Vorgänger-Commit"
+
 #. ""
 msgid "annotate"
 msgstr "annotieren"
 
+#. "The person who initially created (authored) a commit"
+msgid "author"
+msgstr "Autor"
+
+#. "a repository with only .git directory, without working directory"
+msgid "bare repository"
+msgstr "bloßes Projektarchiv"
+
+#. "a parent version of the current file"
+msgid "base"
+msgstr "Ursprung"
+
+#. ""
+msgid "bisect"
+msgstr "binäre Suche [noun], binäre Suche benutzen [verb]"
+
+#. "get the authors responsible for each line in a file"
+msgid "blame"
+msgstr "annotieren"
+
+#.      ""
+msgid "blob"
+msgstr "Blob"
+
 #. "A 'branch' is an active line of development."
 msgid "branch [noun]"
-msgstr "Zweig"
+msgstr "Branch"
 
 #. ""
 msgid "branch [verb]"
-msgstr "verzweigen"
+msgstr "branchen"
 
 #. ""
 msgid "checkout [noun]"
 msgstr ""
-"Arbeitskopie (Erstellung einer Arbeitskopie; Auscheck? Ausspielung? Abruf? "
-"Source Safe: Auscheckvorgang)"
+"Arbeitskopie (Checkout; Erstellung einer Arbeitskopie; Auscheck? Source "
+"Safe: Auscheckvorgang)"
 
 #. "The action of updating the working tree to a revision which was stored in the object database."
 msgid "checkout [verb]"
 msgstr ""
-"Arbeitskopie erstellen; Zweig umstellen [checkout a branch] (auschecken? "
-"ausspielen? abrufen? Source Safe: auschecken)"
+"Arbeitskopie erstellen; Branch auschecken [checkout a branch] (umstellen? "
+"Source Safe: auschecken)"
+
+#. "to select and apply a single commit to the current HEAD without merging"
+msgid "cherry-pick"
+msgstr "cherry-pick (pflücken?)"
+
+#. "a commit that directly succeeds the current one in git's graph of commits"
+msgid "child commit"
+msgstr "Kind-Commit"
+
+#. "clean the state of the git repository, often after manually stopped operation"
+msgid "cleanup"
+msgstr "aufräumen"
 
 #. ""
 msgid "clone [verb]"
@@ -66,39 +114,98 @@ msgstr "klonen"
 
 #. "A single point in the git history."
 msgid "commit [noun]"
-msgstr ""
-"Version; Eintragung; Änderung (Buchung?, Eintragung?, Übertragung?, "
-"Sendung?, Übergabe?, Einspielung?, Ablagevorgang?)"
+msgstr "Commit (Version?)"
 
 #. "The action of storing a new snapshot of the project's state in the git history."
 msgid "commit [verb]"
 msgstr ""
-"eintragen (TortoiseSVN: übertragen; Source Safe: einchecken; senden?, "
-"übergeben?, einspielen?, einpflegen?, ablegen?)"
+"committen (eintragen?, TortoiseSVN: übertragen; Source Safe: einchecken)"
+
+#. "a message that gets attached with any commit"
+msgid "commit message"
+msgstr "Commit-Beschreibung (Meldung?, Nachricht?; Source Safe: Kommentar)"
+
+#.   "The person who committed a commit (to the current branch), which might be different than the author."
+msgid "committer"
+msgstr "Committer"
+
+#. "a commit that precedes the current one in git's graph of commits (not necessarily directly)"
+msgid "descendant"
+msgstr "Nachfolger-Commit"
+
+#.       "checkout of a revision rather than some head"
+msgid "detached HEAD"
+msgstr "losgelöster HEAD / Branchspitze"
+
+#. "checkout of a revision rather than some head"
+msgid "detached checkout"
+msgstr "losgelöster Commit (von Branch losgelöster Commit?)"
 
 #. ""
 msgid "diff [noun]"
-msgstr "Vergleich (Source Safe: Unterschiede)"
+msgstr "Vergleich (Diff? Source Safe: Unterschiede)"
 
 #. ""
 msgid "diff [verb]"
 msgstr "vergleichen"
 
-#. "A fast-forward is a special type of merge where you have a revision and you are merging another branch's changes that happen to be a descendant of what you have."
-msgid "fast forward merge"
-msgstr "Schnellzusammenführung"
+#.   ""
+msgid "directory"
+msgstr "Verzeichnis"
+
+#. "A fast-forward merge is a special type of merge where you have a revision and you are merging another branch's changes that happen to be a descendant of what you have."
+msgid "fast-forward"
+msgstr "vorspulen"
 
 #. "Fetching a branch means to get the branch's head from a remote repository, to find out which objects are missing from the local object database, and to get them, too."
 msgid "fetch"
 msgstr "anfordern (holen?)"
 
+#. "any merge strategy that works on a file by file basis"
+msgid "file level merging"
+msgstr "Datei-basiertes zusammenführen"
+
+#.      ""
+msgid "file"
+msgstr "Datei"
+
+#. "the last revision in a branch"
+msgid "head"
+msgstr "HEAD / Branchspitze"
+
+#. "script that gets executed automatically on some event"
+msgid "hook"
+msgstr "Hook (in der dt. Informatik wohl als Einschubmethode bezeichnet)"
+
 #. "One context of consecutive lines in a whole patch, which consists of many such hunks"
 msgid "hunk"
-msgstr "Kontext"
+msgstr "Patch-Block (Kontext?)"
 
 #. "A collection of files. The index is a stored version of your working tree."
 msgid "index (in git-gui: staging area)"
-msgstr "Bereitstellung"
+msgstr ""
+"Bereitstellung (sofern der git index gemeint ist. In git-gui sowieso: "
+"staging area)"
+
+#. "the first checkout during a clone operation"
+msgid "initial checkout"
+msgstr "Erstellen der Arbeitskopie, auschecken"
+
+#. "The very first commit in a repository"
+msgid "initial commit"
+msgstr "Allererster Commit"
+
+#. "a branch that resides in the local git repository"
+msgid "local branch"
+msgstr "Lokaler Branch"
+
+#. "a Git object that is not part of any pack"
+msgid "loose object"
+msgstr "loses Objekt"
+
+#. "a branch called by convention 'master' that exists in a newly created git repository"
+msgid "master branch"
+msgstr "Master-Branch"
 
 #. "A successful merge results in the creation of a new commit representing the result of the merge."
 msgid "merge [noun]"
@@ -112,78 +219,212 @@ msgstr "zusammenführen"
 msgid "message"
 msgstr "Beschreibung (Meldung?, Nachricht?; Source Safe: Kommentar)"
 
-#. "Deletes all stale tracking branches under <name>. These stale branches have already been removed from the remote repository referenced by <name>, but are still locally available in 'remotes/<name>'."
+#. "a remote called by convention 'origin' that the current git repository has been cloned from"
+msgid "origin"
+msgstr "origin"
+
+#.       ""
+msgid "orphan commit"
+msgstr "verwaister Commit"
+
+#.  ""
+msgid "orphan reference"
+msgstr "verwaiste Referenz"
+
+#. "a file containing many git objects packed together"
+msgid "pack [noun]"
+msgstr "Pack-Datei"
+
+#.     "the process of creating a pack file"
+msgid "pack [verb]"
+msgstr "Pack-Datei erstellen"
+
+#. "a Git object part of some pack"
+msgid "packed object"
+msgstr "gepacktes Objekt"
+
+#. "a commit that directly precedes the current one in git's graph of commits"
+msgid "parent commit"
+msgstr "Eltern-Commit"
+
+msgid "patch"
+msgstr "Patch"
+
+#. "The path to a file"
+msgid "path"
+msgstr "Pfad"
+
+#. "Delete all stale tracking branches under <name>. These stale branches have already been removed from the remote repository referenced by <name>, but are still locally available in 'remotes/<name>'."
 msgid "prune"
-msgstr "aufräumen (entfernen?)"
+msgstr "veraltete Branches entfernen (aufräumen?, entfernen?)"
 
 #. "Pulling a branch means to fetch it and merge it."
 msgid "pull"
-msgstr "übernehmen (ziehen?)"
+msgstr ""
+"übernehmen (pull? ziehen? Vorsicht: zusammenführen = merge, aber pull kann "
+"auch rebase bewirken)"
 
 #. "Pushing a branch means to get the branch's head ref from a remote repository, and ... (well, can someone please explain it for mere mortals?)"
 msgid "push"
 msgstr "versenden (ausliefern? hochladen? verschicken? schieben?)"
 
+#. "The process of rebasing one set of commits on top of another branch's head"
+msgid "rebase [noun]"
+msgstr "der Rebase (das Umpflanzen)"
+
+#. "Re-apply one set of commits on top of another branch's head. Contrary to merge."
+msgid "rebase [verb]"
+msgstr "rebase (umpflanzen)"
+
 #. ""
 msgid "redo"
 msgstr "wiederholen"
 
-#. "An other repository ('remote'). One might have a set of remotes whose branches one tracks."
-msgid "remote"
-msgstr "Andere Archive (Gegenseite?, Entfernte?, Server?)"
+#.   ""
+msgid "reference"
+msgstr "Referenz"
+
+#. "the log file containing all states of the HEAD reference (in other words past pristine states of the working copy)"
+msgid "reflog"
+msgstr "Commit-Log, »reflog«"
+
+msgid "refmap"
+msgstr "Refmap"
+
+#. ""
+msgid "refspec"
+msgstr "Refspec"
+
+#. "The adjective for anything which is outside of the current (local) repository"
+msgid "remote [adj]"
+msgstr "Extern (Andere?, Gegenseite?, Entfernte?, Server?)"
+
+#.       "A branch in any other ('remote') repository"
+msgid "remote branch"
+msgstr "Externer branch"
+
+#.   "An other repository ('remote'). One might have a set of remotes whose branches one tracks."
+msgid "remote repository"
+msgstr "Externes Repository"
 
 #. "A collection of refs (?) together with an object database containing all objects which are reachable from the refs... (oops, you've lost me here. Again, please an explanation for mere mortals?)"
 msgid "repository"
-msgstr "Projektarchiv"
+msgstr "Repository"
 
 #. ""
 msgid "reset"
-msgstr "zurücksetzen (zurückkehren?)"
+msgstr "umsetzen (reset to commit), Änderungen verwerfen (reset to HEAD)"
+
+#. "decide which changes from alternative versions of a file should persist in Git"
+msgid "resolve (a conflict)"
+msgstr "auflösen (einen Konflikt)"
+
+#. "abandon changes and go to pristine version"
+msgid "revert changes"
+msgstr ""
+"verwerfen (bei git-reset bzw. checkout), zurücknehmen (bei git-revert, also "
+"mit neuem commit; umkehren?)"
 
 #. ""
 msgid "revert"
-msgstr "verwerfen (bei git-reset), revidieren (bei git-revert, also mit neuem commit)"
+msgstr ""
+"verwerfen (bei git-reset bzw. checkout), zurücknehmen (bei git-revert, also "
+"mit neuem commit; umkehren?)"
+
+#. "expression that signifies a revision in git"
+msgid "revision expression"
+msgstr "Version Regexp-Ausdruck"
 
 #. "A particular state of files and directories which was stored in the object database."
 msgid "revision"
-msgstr "Version (TortoiseSVN: Revision; Source Safe: Version)"
+msgstr ""
+"Version (aber was macht das Wort revision hier im Git?? TortoiseSVN: "
+"Revision; Source Safe: Version)"
 
 #. ""
 msgid "sign off"
-msgstr "abzeichnen (gegenzeichnen?, freizeichnen?, absegnen?)"
+msgstr "abzeichnen (signieren? gegenzeichnen?, freizeichnen?)"
 
-#. ""
+#.     "see: staging area. In some areas of git this is called 'index'."
+msgid "stage [noun], index"
+msgstr "Bereitstellung"
+
+#. "add some content of files and directories to the staging area in preparation for a commit"
+msgid "stage [verb]"
+msgstr "bereitstellen"
+
+#. "The place where changes from files are marked to be included for the next commit. In some areas of git this is called 'index'."
 msgid "staging area"
 msgstr "Bereitstellung"
 
+#. "The place (stack) where changes can be temporarily saved without committing"
+msgid "stash [noun]"
+msgstr "der Stash"
+
+#. "temporarily save changes in a stack without committing"
+msgid "stash [verb]"
+msgstr "in Stash speichern; \"stash\" benutzen"
+
 #. ""
 msgid "status"
 msgstr "Status"
 
-#. "A ref pointing to a tag or commit object"
+#. ""
+msgid "submodule"
+msgstr "Submodul (Untermodul?)"
+
+#. "A ref pointing to some commit object. In other words: A label on a specific commit."
 msgid "tag [noun]"
-msgstr "Markierung"
+msgstr "Tag (Markierung?)"
 
-#. ""
+#. "The process of creating a tag at a specific commit object"
 msgid "tag [verb]"
-msgstr "markieren"
+msgstr "taggen (markieren?)"
+
+#. "The person who created a tag"
+msgid "tagger"
+msgstr "Tag-Ersteller (Markierungs-Ersteller?)"
+
+#. "file whose content is tracked/not tracked by git"
+msgid "tracked/untracked"
+msgstr "versioniert/unversioniert"
 
 #. "A regular git branch that is used to follow changes from another repository."
 msgid "tracking branch"
-msgstr "Übernahmezweig"
+msgstr "Tracking-Branch (Verfolgungsbranch? Übernahmebranch?)"
+
+#. ""
+msgid "trailer"
+msgstr "Anhang"
+
+#. "1. tree object, 2. directory tree"
+msgid "tree"
+msgstr "1. Baum-Objekt, 2. Verzeichnisbaum"
 
 #. ""
 msgid "undo"
 msgstr "rückgängig"
 
+#. "Remove content of files from the staging area again so that it will not be part of the next commit"
+msgid "unstage"
+msgstr "aus Bereitstellung herausnehmen"
+
+#. "Retrieving the temporarily saved changes back again from the stash"
+msgid "unstash [verb]"
+msgstr "aus Stash zurückladen"
+
 #. ""
 msgid "update"
 msgstr "aktualisieren"
 
+#. ""
+msgid "upstream branch"
+msgstr "Upstream-Branch"
+
 #. ""
 msgid "verify"
 msgstr "überprüfen"
 
 #. "The tree of actual checked out files."
-msgid "working copy, working tree"
+msgid "working directory, working copy, working tree"
 msgstr "Arbeitskopie"
-- 
gitgitgadget

^ permalink raw reply related	[relevance 1%]

* [PATCH v2 0/3] git gui: improve German translation
    2020-01-24 22:33  1% ` [PATCH 1/3] git-gui: update german translation to most recently created pot templates Christian Stimming via GitGitGadget
  2020-01-24 22:33  1% ` [PATCH 2/3] git-gui: update german translation Christian Stimming via GitGitGadget
@ 2020-02-09 22:00  1% ` Christian Stimming via GitGitGadget
  2020-02-09 22:00  1%   ` [PATCH v2 1/3] git-gui: update pot template and German translation to current source code Christian Stimming via GitGitGadget
  2020-02-09 22:00  1%   ` [PATCH v2 3/3] git-gui: update German translation Christian Stimming via GitGitGadget
  2 siblings, 2 replies; 200+ results
From: Christian Stimming via GitGitGadget @ 2020-02-09 22:00 UTC (permalink / raw)
  To: git; +Cc: Christian Stimming, Pratyush Yadav

git-gui: update/improve German translation

Update translation template and translation glossary as prerequisite. Then,
update German translation (glossary and final translation) to recent source
code changes, but also switch several terms from uncommon translations back
to English vocabulary, similar to the rest of git-core.

This most prominently concerns "commit" (noun, verb), "repository",
"branch", and some more. These uncommon translations have been introduced
long ago and never been changed since. In fact, the whole German translation
here hasn't been touched for a long time. However, in German literature and
magazines, git-gui is regularly noted for its uncommon choice of translated
vocabulary. This somewhat distracts from the actual benefits of this tool.
So it is probably better to abandon the uncommon translations and rather
stick to the common English vocabulary in git version control.

The glossary is adapted to the git-core glossary at
https://github.com/ruester/git-po-de/wiki/Translation-Guidelinesand the
changed and updated terms are used in the actual translation accordingly.

Changes since v1:

 * commit message titles with proper capitalization
 * commit message includes reason for why German wording has been changed
 * changes in German translation are squashed into one commit
 * some more wording changes have been integrated after discussion with
   other German git translators

Christian Stimming (3):
  git-gui: update pot template and German translation to current source
    code
  git-gui: extend translation glossary template with more terms
  git-gui: update German translation

 po/de.po                         | 3622 ++++++++++++++++--------------
 po/git-gui.pot                   | 2526 +++++++++++----------
 po/glossary/de.po                |  315 ++-
 po/glossary/git-gui-glossary.pot |  250 ++-
 po/glossary/git-gui-glossary.txt |  101 +-
 5 files changed, 3934 insertions(+), 2880 deletions(-)


base-commit: 0d2116c6441079a5a1091e4cf152fd9d5fa9811b
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-525%2Fcstim%2Fcstim-gitgui-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-525/cstim/cstim-gitgui-v2
Pull-Request: https://github.com/gitgitgadget/git/pull/525

Range-diff vs v1:

 1:  1f6265c858 ! 1:  f1ab13b08a git-gui: update german translation to most recently created pot templates
     @@ -1,6 +1,6 @@
      Author: Christian Stimming <christian@cstimming.de>
      
     -    git-gui: update german translation to most recently created pot templates
     +    git-gui: update pot template and German translation to current source code
      
          No content changes so far, only the preparation for subsequent edits.
      
     @@ -14,7 +14,7 @@
       "Project-Id-Version: git-gui\n"
       "Report-Msgid-Bugs-To: \n"
      -"POT-Creation-Date: 2010-01-26 22:22+0100\n"
     -+"POT-Creation-Date: 2020-01-13 21:51+0100\n"
     ++"POT-Creation-Date: 2020-02-08 22:54+0100\n"
       "PO-Revision-Date: 2010-01-26 22:25+0100\n"
       "Last-Translator: Christian Stimming <stimming@tuhh.de>\n"
       "Language-Team: German\n"
     @@ -4364,164 +4364,3783 @@
      +#~ msgid "Case-Sensitive"
      +#~ msgstr "Groß-/Kleinschreibung unterscheiden"
      
     - diff --git a/po/glossary/de.po b/po/glossary/de.po
     - --- a/po/glossary/de.po
     - +++ b/po/glossary/de.po
     + diff --git a/po/git-gui.pot b/po/git-gui.pot
     + --- a/po/git-gui.pot
     + +++ b/po/git-gui.pot
      @@
     - msgid ""
       msgstr ""
     - "Project-Id-Version: git-gui glossary\n"
     --"POT-Creation-Date: 2008-01-07 21:20+0100\n"
     --"PO-Revision-Date: 2008-02-16 21:48+0100\n"
     -+"POT-Creation-Date: 2020-01-13 21:40+0100\n"
     -+"PO-Revision-Date: 2020-01-13 21:53+0100\n"
     - "Last-Translator: Christian Stimming <stimming@tuhh.de>\n"
     - "Language-Team: German \n"
     -+"Language: de_DE\n"
     + "Project-Id-Version: PACKAGE VERSION\n"
     + "Report-Msgid-Bugs-To: \n"
     +-"POT-Creation-Date: 2010-01-26 15:47-0800\n"
     ++"POT-Creation-Date: 2020-02-08 22:54+0100\n"
     + "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
     + "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
     + "Language-Team: LANGUAGE <LL@li.org>\n"
     ++"Language: \n"
       "MIME-Version: 1.0\n"
     - "Content-Type: text/plain; charset=UTF-8\n"
     + "Content-Type: text/plain; charset=CHARSET\n"
       "Content-Transfer-Encoding: 8bit\n"
     -@@
       
     - #. "An other repository ('remote'). One might have a set of remotes whose branches one tracks."
     - msgid "remote"
     --msgstr "Andere Archive (Gegenseite?, Entfernte?, Server?)"
     -+msgstr "Extern (Andere?, Gegenseite?, Entfernte?, Server?)"
     +-#: git-gui.sh:41 git-gui.sh:793 git-gui.sh:807 git-gui.sh:820 git-gui.sh:903
     +-#: git-gui.sh:922
     +-msgid "git-gui: fatal error"
     +-msgstr ""
     +-
     +-#: git-gui.sh:743
     ++#: git-gui.sh:847
     + #, tcl-format
     + msgid "Invalid font specified in %s:"
     + msgstr ""
       
     - #. "A collection of refs (?) together with an object database containing all objects which are reachable from the refs... (oops, you've lost me here. Again, please an explanation for mere mortals?)"
     - msgid "repository"
     -@@
     +-#: git-gui.sh:779
     ++#: git-gui.sh:901
     + msgid "Main Font"
     + msgstr ""
       
     - #. ""
     - msgid "revert"
     --msgstr "verwerfen (bei git-reset), revidieren (bei git-revert, also mit neuem commit)"
     -+msgstr ""
     -+"verwerfen (bei git-reset), revidieren (bei git-revert, also mit neuem commit)"
     +-#: git-gui.sh:780
     ++#: git-gui.sh:902
     + msgid "Diff/Console Font"
     + msgstr ""
       
     - #. "A particular state of files and directories which was stored in the object database."
     - msgid "revision"
     -@@
     - #. "The tree of actual checked out files."
     - msgid "working copy, working tree"
     - msgstr "Arbeitskopie"
     -+
     -+#. "a commit that succeeds the current one in git's graph of commits (not necessarily directly)"
     -+msgid "ancestor"
     -+msgstr ""
     -+
     -+#. "prematurely stop and abandon an operation"
     -+msgid "abort"
     -+msgstr ""
     -+
     -+#. "a repository with only .git directory, without working directory"
     -+msgid "bare repository"
     -+msgstr "bloßes Projektarchiv"
     -+
     -+#. "a parent version of the current file"
     -+msgid "base"
     -+msgstr ""
     -+
     -+#. "get the authors responsible for each line in a file"
     -+msgid "blame"
     -+msgstr ""
     -+
     -+#. "to select and apply a single commit without merging"
     -+msgid "cherry-pick"
     -+msgstr ""
     -+
     -+#. "a commit that directly succeeds the current one in git's graph of commits"
     -+msgid "child"
     -+msgstr ""
     -+
     -+#. "clean the state of the git repository, often after manually stopped operation"
     -+msgid "cleanup"
     -+msgstr ""
     -+
     -+#. "a message that gets attached with any commit"
     -+#, fuzzy
     -+msgid "commit message"
     -+msgstr "Beschreibung (Meldung?, Nachricht?; Source Safe: Kommentar)"
     -+
     -+#. "a commit that precedes the current one in git's graph of commits (not necessarily directly)"
     -+msgid "descendant"
     -+msgstr ""
     -+
     -+#. "checkout of a revision rather than a some head"
     -+msgid "detached checkout"
     -+msgstr ""
     -+
     -+#. "any merge strategy that works on a file by file basis"
     -+msgid "file level merging"
     -+msgstr ""
     -+
     -+#. "the last revision in a branch"
     -+msgid "head"
     +-#: git-gui.sh:794
     ++#: git-gui.sh:917 git-gui.sh:931 git-gui.sh:944 git-gui.sh:1034 git-gui.sh:1053
     ++#: git-gui.sh:3212
     ++msgid "git-gui: fatal error"
      +msgstr ""
      +
     -+#. "script that gets executed automatically on some event"
     -+msgid "hook"
     ++#: git-gui.sh:918
     + msgid "Cannot find git in PATH."
     + msgstr ""
     + 
     +-#: git-gui.sh:821
     ++#: git-gui.sh:945
     + msgid "Cannot parse Git version string:"
     + msgstr ""
     + 
     +-#: git-gui.sh:839
     ++#: git-gui.sh:970
     + #, tcl-format
     + msgid ""
     + "Git version cannot be determined.\n"
     +@@
     + "Assume '%s' is version 1.5.0?\n"
     + msgstr ""
     + 
     +-#: git-gui.sh:1128
     ++#: git-gui.sh:1267
     + msgid "Git directory not found:"
     + msgstr ""
     + 
     +-#: git-gui.sh:1146
     ++#: git-gui.sh:1301
     + msgid "Cannot move to top of working directory:"
     + msgstr ""
     + 
     +-#: git-gui.sh:1154
     ++#: git-gui.sh:1309
     + msgid "Cannot use bare repository:"
     + msgstr ""
     + 
     +-#: git-gui.sh:1162
     ++#: git-gui.sh:1317
     + msgid "No working directory"
     + msgstr ""
     + 
     +-#: git-gui.sh:1334 lib/checkout_op.tcl:306
     ++#: git-gui.sh:1491 lib/checkout_op.tcl:306
     + msgid "Refreshing file status..."
     + msgstr ""
     + 
     +-#: git-gui.sh:1390
     ++#: git-gui.sh:1551
     + msgid "Scanning for modified files ..."
     + msgstr ""
     + 
     +-#: git-gui.sh:1454
     ++#: git-gui.sh:1629
     + msgid "Calling prepare-commit-msg hook..."
     + msgstr ""
     + 
     +-#: git-gui.sh:1471
     ++#: git-gui.sh:1646
     + msgid "Commit declined by prepare-commit-msg hook."
     + msgstr ""
     + 
     +-#: git-gui.sh:1629 lib/browser.tcl:246
     ++#: git-gui.sh:1804 lib/browser.tcl:252
     + msgid "Ready."
     + msgstr ""
     + 
     +-#: git-gui.sh:1787
     ++#: git-gui.sh:1968
     + #, tcl-format
     +-msgid "Displaying only %s of %s files."
     ++msgid ""
     ++"Display limit (gui.maxfilesdisplayed = %s) reached, not showing all %s files."
     + msgstr ""
     + 
     +-#: git-gui.sh:1913
     ++#: git-gui.sh:2091
     + msgid "Unmodified"
     + msgstr ""
     + 
     +-#: git-gui.sh:1915
     ++#: git-gui.sh:2093
     + msgid "Modified, not staged"
     + msgstr ""
     + 
     +-#: git-gui.sh:1916 git-gui.sh:1924
     ++#: git-gui.sh:2094 git-gui.sh:2106
     + msgid "Staged for commit"
     + msgstr ""
     + 
     +-#: git-gui.sh:1917 git-gui.sh:1925
     ++#: git-gui.sh:2095 git-gui.sh:2107
     + msgid "Portions staged for commit"
     + msgstr ""
     + 
     +-#: git-gui.sh:1918 git-gui.sh:1926
     ++#: git-gui.sh:2096 git-gui.sh:2108
     + msgid "Staged for commit, missing"
     + msgstr ""
     + 
     +-#: git-gui.sh:1920
     ++#: git-gui.sh:2098
     + msgid "File type changed, not staged"
     + msgstr ""
     + 
     +-#: git-gui.sh:1921
     ++#: git-gui.sh:2099 git-gui.sh:2100
     ++msgid "File type changed, old type staged for commit"
      +msgstr ""
      +
     -+#. "the first checkout during a clone operation"
     -+msgid "initial checkout"
     ++#: git-gui.sh:2101
     + msgid "File type changed, staged"
     + msgstr ""
     + 
     +-#: git-gui.sh:1923
     ++#: git-gui.sh:2102
     ++msgid "File type change staged, modification not staged"
      +msgstr ""
      +
     -+#. "a branch that resides in the local git repository"
     -+msgid "local branch"
     ++#: git-gui.sh:2103
     ++msgid "File type change staged, file missing"
      +msgstr ""
      +
     -+#. "a Git object that is not part of any pack"
     -+msgid "loose object"
     ++#: git-gui.sh:2105
     + msgid "Untracked, not staged"
     + msgstr ""
     + 
     +-#: git-gui.sh:1928
     ++#: git-gui.sh:2110
     + msgid "Missing"
     + msgstr ""
     + 
     +-#: git-gui.sh:1929
     ++#: git-gui.sh:2111
     + msgid "Staged for removal"
     + msgstr ""
     + 
     +-#: git-gui.sh:1930
     ++#: git-gui.sh:2112
     + msgid "Staged for removal, still present"
     + msgstr ""
     + 
     +-#: git-gui.sh:1932 git-gui.sh:1933 git-gui.sh:1934 git-gui.sh:1935
     +-#: git-gui.sh:1936 git-gui.sh:1937
     ++#: git-gui.sh:2114 git-gui.sh:2115 git-gui.sh:2116 git-gui.sh:2117
     ++#: git-gui.sh:2118 git-gui.sh:2119
     + msgid "Requires merge resolution"
     + msgstr ""
     + 
     +-#: git-gui.sh:1972
     +-msgid "Starting gitk... please wait..."
     ++#: git-gui.sh:2164
     ++msgid "Couldn't find gitk in PATH"
     + msgstr ""
     + 
     +-#: git-gui.sh:1984
     +-msgid "Couldn't find gitk in PATH"
     ++#: git-gui.sh:2210 git-gui.sh:2245
     ++#, tcl-format
     ++msgid "Starting %s... please wait..."
     + msgstr ""
     + 
     +-#: git-gui.sh:2043
     ++#: git-gui.sh:2224
     + msgid "Couldn't find git gui in PATH"
     + msgstr ""
     + 
     +-#: git-gui.sh:2455 lib/choose_repository.tcl:36
     ++#: git-gui.sh:2726 lib/choose_repository.tcl:53
     + msgid "Repository"
     + msgstr ""
     + 
     +-#: git-gui.sh:2456
     ++#: git-gui.sh:2727
     + msgid "Edit"
     + msgstr ""
     + 
     +-#: git-gui.sh:2458 lib/choose_rev.tcl:561
     ++#: git-gui.sh:2729 lib/choose_rev.tcl:567
     + msgid "Branch"
     + msgstr ""
     + 
     +-#: git-gui.sh:2461 lib/choose_rev.tcl:548
     ++#: git-gui.sh:2732 lib/choose_rev.tcl:554
     + msgid "Commit@@noun"
     + msgstr ""
     + 
     +-#: git-gui.sh:2464 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168
     ++#: git-gui.sh:2735 lib/merge.tcl:127 lib/merge.tcl:174
     + msgid "Merge"
     + msgstr ""
     + 
     +-#: git-gui.sh:2465 lib/choose_rev.tcl:557
     ++#: git-gui.sh:2736 lib/choose_rev.tcl:563
     + msgid "Remote"
     + msgstr ""
     + 
     +-#: git-gui.sh:2468
     ++#: git-gui.sh:2739
     + msgid "Tools"
     + msgstr ""
     + 
     +-#: git-gui.sh:2477
     ++#: git-gui.sh:2748
     + msgid "Explore Working Copy"
     + msgstr ""
     + 
     +-#: git-gui.sh:2483
     ++#: git-gui.sh:2763
     ++msgid "Git Bash"
      +msgstr ""
      +
     -+#. "a branch called by convention 'master' that exists in a newly created git repository"
     -+#, fuzzy
     -+msgid "master branch"
     -+msgstr "Übernahmezweig"
     -+
     -+#. "a remote called by convention 'origin' that the current git repository has been cloned from"
     -+msgid "origin"
     -+msgstr "origin"
     -+
     -+#. "a file containing many git objects packed together"
     -+#, fuzzy
     -+msgid "pack [noun]"
     -+msgstr "Markierung"
     -+
     -+#. "a Git object part of some pack"
     -+msgid "packed object"
     -+msgstr ""
     -+
     -+#. "a commit that directly precedes the current one in git's graph of commits"
     -+msgid "parent"
     -+msgstr ""
     -+
     -+#. "the log file containing all states of the HEAD reference (in other words past pristine states of the working copy)"
     -+msgid "reflog"
     -+msgstr ""
     -+
     -+#. "decide which changes from alternative versions of a file should persist in Git"
     -+msgid "resolve (a conflict)"
     -+msgstr ""
     -+
     -+#. "abandon changes and go to pristine version"
     -+#, fuzzy
     -+msgid "revert changes"
     ++#: git-gui.sh:2772
     + msgid "Browse Current Branch's Files"
     + msgstr ""
     + 
     +-#: git-gui.sh:2487
     ++#: git-gui.sh:2776
     + msgid "Browse Branch Files..."
     + msgstr ""
     + 
     +-#: git-gui.sh:2492
     ++#: git-gui.sh:2781
     + msgid "Visualize Current Branch's History"
     + msgstr ""
     + 
     +-#: git-gui.sh:2496
     ++#: git-gui.sh:2785
     + msgid "Visualize All Branch History"
     + msgstr ""
     + 
     +-#: git-gui.sh:2503
     ++#: git-gui.sh:2792
     + #, tcl-format
     + msgid "Browse %s's Files"
     + msgstr ""
     + 
     +-#: git-gui.sh:2505
     ++#: git-gui.sh:2794
     + #, tcl-format
     + msgid "Visualize %s's History"
     + msgstr ""
     + 
     +-#: git-gui.sh:2510 lib/database.tcl:27 lib/database.tcl:67
     ++#: git-gui.sh:2799 lib/database.tcl:40
     + msgid "Database Statistics"
     + msgstr ""
     + 
     +-#: git-gui.sh:2513 lib/database.tcl:34
     ++#: git-gui.sh:2802 lib/database.tcl:33
     + msgid "Compress Database"
     + msgstr ""
     + 
     +-#: git-gui.sh:2516
     ++#: git-gui.sh:2805
     + msgid "Verify Database"
     + msgstr ""
     + 
     +-#: git-gui.sh:2523 git-gui.sh:2527 git-gui.sh:2531 lib/shortcut.tcl:8
     +-#: lib/shortcut.tcl:40 lib/shortcut.tcl:72
     ++#: git-gui.sh:2812 git-gui.sh:2816 git-gui.sh:2820
     + msgid "Create Desktop Icon"
     + msgstr ""
     + 
     +-#: git-gui.sh:2539 lib/choose_repository.tcl:183 lib/choose_repository.tcl:191
     ++#: git-gui.sh:2828 lib/choose_repository.tcl:209 lib/choose_repository.tcl:217
     + msgid "Quit"
     + msgstr ""
     + 
     +-#: git-gui.sh:2547
     ++#: git-gui.sh:2836
     + msgid "Undo"
     + msgstr ""
     + 
     +-#: git-gui.sh:2550
     ++#: git-gui.sh:2839
     + msgid "Redo"
     + msgstr ""
     + 
     +-#: git-gui.sh:2554 git-gui.sh:3109
     ++#: git-gui.sh:2843 git-gui.sh:3461
     + msgid "Cut"
     + msgstr ""
     + 
     +-#: git-gui.sh:2557 git-gui.sh:3112 git-gui.sh:3186 git-gui.sh:3259
     ++#: git-gui.sh:2846 git-gui.sh:3464 git-gui.sh:3540 git-gui.sh:3633
     + #: lib/console.tcl:69
     + msgid "Copy"
     + msgstr ""
     + 
     +-#: git-gui.sh:2560 git-gui.sh:3115
     ++#: git-gui.sh:2849 git-gui.sh:3467
     + msgid "Paste"
     + msgstr ""
     + 
     +-#: git-gui.sh:2563 git-gui.sh:3118 lib/branch_delete.tcl:26
     +-#: lib/remote_branch_delete.tcl:38
     ++#: git-gui.sh:2852 git-gui.sh:3470 lib/remote_branch_delete.tcl:39
     ++#: lib/branch_delete.tcl:28
     + msgid "Delete"
     + msgstr ""
     + 
     +-#: git-gui.sh:2567 git-gui.sh:3122 git-gui.sh:3263 lib/console.tcl:71
     ++#: git-gui.sh:2856 git-gui.sh:3474 git-gui.sh:3637 lib/console.tcl:71
     + msgid "Select All"
     + msgstr ""
     + 
     +-#: git-gui.sh:2576
     ++#: git-gui.sh:2865
     + msgid "Create..."
     + msgstr ""
     + 
     +-#: git-gui.sh:2582
     ++#: git-gui.sh:2871
     + msgid "Checkout..."
     + msgstr ""
     + 
     +-#: git-gui.sh:2588
     ++#: git-gui.sh:2877
     + msgid "Rename..."
     + msgstr ""
     + 
     +-#: git-gui.sh:2593
     ++#: git-gui.sh:2882
     + msgid "Delete..."
     + msgstr ""
     + 
     +-#: git-gui.sh:2598
     ++#: git-gui.sh:2887
     + msgid "Reset..."
     + msgstr ""
     + 
     +-#: git-gui.sh:2608
     ++#: git-gui.sh:2897
     + msgid "Done"
     + msgstr ""
     + 
     +-#: git-gui.sh:2610
     ++#: git-gui.sh:2899
     + msgid "Commit@@verb"
     + msgstr ""
     + 
     +-#: git-gui.sh:2619 git-gui.sh:3050
     +-msgid "New Commit"
     +-msgstr ""
     +-
     +-#: git-gui.sh:2627 git-gui.sh:3057
     ++#: git-gui.sh:2908 git-gui.sh:3400
     + msgid "Amend Last Commit"
     + msgstr ""
     + 
     +-#: git-gui.sh:2637 git-gui.sh:3011 lib/remote_branch_delete.tcl:99
     ++#: git-gui.sh:2918 git-gui.sh:3361 lib/remote_branch_delete.tcl:101
     + msgid "Rescan"
     + msgstr ""
     + 
     +-#: git-gui.sh:2643
     ++#: git-gui.sh:2924
     + msgid "Stage To Commit"
     + msgstr ""
     + 
     +-#: git-gui.sh:2649
     ++#: git-gui.sh:2930
     + msgid "Stage Changed Files To Commit"
     + msgstr ""
     + 
     +-#: git-gui.sh:2655
     ++#: git-gui.sh:2936
     + msgid "Unstage From Commit"
     + msgstr ""
     + 
     +-#: git-gui.sh:2661 lib/index.tcl:412
     ++#: git-gui.sh:2942 lib/index.tcl:521
     + msgid "Revert Changes"
     + msgstr ""
     + 
     +-#: git-gui.sh:2669 git-gui.sh:3310 git-gui.sh:3341
     ++#: git-gui.sh:2950 git-gui.sh:3700 git-gui.sh:3731
     + msgid "Show Less Context"
     + msgstr ""
     + 
     +-#: git-gui.sh:2673 git-gui.sh:3314 git-gui.sh:3345
     ++#: git-gui.sh:2954 git-gui.sh:3704 git-gui.sh:3735
     + msgid "Show More Context"
     + msgstr ""
     + 
     +-#: git-gui.sh:2680 git-gui.sh:3024 git-gui.sh:3133
     ++#: git-gui.sh:2961 git-gui.sh:3374 git-gui.sh:3485
     + msgid "Sign Off"
     + msgstr ""
     + 
     +-#: git-gui.sh:2696
     ++#: git-gui.sh:2977
     + msgid "Local Merge..."
     + msgstr ""
     + 
     +-#: git-gui.sh:2701
     ++#: git-gui.sh:2982
     + msgid "Abort Merge..."
     + msgstr ""
     + 
     +-#: git-gui.sh:2713 git-gui.sh:2741
     ++#: git-gui.sh:2994 git-gui.sh:3022
     + msgid "Add..."
     + msgstr ""
     + 
     +-#: git-gui.sh:2717
     ++#: git-gui.sh:2998
     + msgid "Push..."
     + msgstr ""
     + 
     +-#: git-gui.sh:2721
     ++#: git-gui.sh:3002
     + msgid "Delete Branch..."
     + msgstr ""
     + 
     +-#: git-gui.sh:2731 git-gui.sh:3292
     ++#: git-gui.sh:3012 git-gui.sh:3666
     + msgid "Options..."
     + msgstr ""
     + 
     +-#: git-gui.sh:2742
     ++#: git-gui.sh:3023
     + msgid "Remove..."
     + msgstr ""
     + 
     +-#: git-gui.sh:2751 lib/choose_repository.tcl:50
     ++#: git-gui.sh:3032 lib/choose_repository.tcl:67
     + msgid "Help"
     + msgstr ""
     + 
     +-#: git-gui.sh:2755 git-gui.sh:2759 lib/about.tcl:14
     +-#: lib/choose_repository.tcl:44 lib/choose_repository.tcl:53
     ++#: git-gui.sh:3036 git-gui.sh:3040 lib/choose_repository.tcl:61
     ++#: lib/choose_repository.tcl:70 lib/about.tcl:14
     + #, tcl-format
     + msgid "About %s"
     + msgstr ""
     + 
     +-#: git-gui.sh:2783
     ++#: git-gui.sh:3064
     + msgid "Online Documentation"
     + msgstr ""
     + 
     +-#: git-gui.sh:2786 lib/choose_repository.tcl:47 lib/choose_repository.tcl:56
     ++#: git-gui.sh:3067 lib/choose_repository.tcl:64 lib/choose_repository.tcl:73
     + msgid "Show SSH Key"
     + msgstr ""
     + 
     +-#: git-gui.sh:2893
     ++#: git-gui.sh:3097 git-gui.sh:3229
     ++msgid "usage:"
      +msgstr ""
     -+"verwerfen (bei git-reset), revidieren (bei git-revert, also mit neuem commit)"
      +
     -+#. "expression that signifies a revision in git"
     -+msgid "revision expression"
     ++#: git-gui.sh:3101 git-gui.sh:3233
     ++msgid "Usage"
      +msgstr ""
      +
     -+#. "add some content of files and directories to the staging area in preparation for a commit"
     -+msgid "stage/unstage"
     ++#: git-gui.sh:3182 lib/blame.tcl:575
     ++msgid "Error"
      +msgstr ""
      +
     -+#. "temporarily save changes in a stack without committing"
     -+msgid "stash"
     ++#: git-gui.sh:3213
     + #, tcl-format
     + msgid "fatal: cannot stat path %s: No such file or directory"
     + msgstr ""
     + 
     +-#: git-gui.sh:2926
     ++#: git-gui.sh:3246
     + msgid "Current Branch:"
     + msgstr ""
     + 
     +-#: git-gui.sh:2947
     +-msgid "Staged Changes (Will Commit)"
     ++#: git-gui.sh:3271
     ++msgid "Unstaged Changes"
     + msgstr ""
     + 
     +-#: git-gui.sh:2967
     +-msgid "Unstaged Changes"
     ++#: git-gui.sh:3293
     ++msgid "Staged Changes (Will Commit)"
     + msgstr ""
     + 
     +-#: git-gui.sh:3017
     ++#: git-gui.sh:3367
     + msgid "Stage Changed"
     + msgstr ""
     + 
     +-#: git-gui.sh:3036 lib/transport.tcl:104 lib/transport.tcl:193
     ++#: git-gui.sh:3386 lib/transport.tcl:137
     + msgid "Push"
     + msgstr ""
     + 
     +-#: git-gui.sh:3071
     ++#: git-gui.sh:3413
     + msgid "Initial Commit Message:"
     + msgstr ""
     + 
     +-#: git-gui.sh:3072
     ++#: git-gui.sh:3414
     + msgid "Amended Commit Message:"
     + msgstr ""
     + 
     +-#: git-gui.sh:3073
     ++#: git-gui.sh:3415
     + msgid "Amended Initial Commit Message:"
     + msgstr ""
     + 
     +-#: git-gui.sh:3074
     ++#: git-gui.sh:3416
     + msgid "Amended Merge Commit Message:"
     + msgstr ""
     + 
     +-#: git-gui.sh:3075
     ++#: git-gui.sh:3417
     + msgid "Merge Commit Message:"
     + msgstr ""
     + 
     +-#: git-gui.sh:3076
     ++#: git-gui.sh:3418
     + msgid "Commit Message:"
     + msgstr ""
     + 
     +-#: git-gui.sh:3125 git-gui.sh:3267 lib/console.tcl:73
     ++#: git-gui.sh:3477 git-gui.sh:3641 lib/console.tcl:73
     + msgid "Copy All"
     + msgstr ""
     + 
     +-#: git-gui.sh:3149 lib/blame.tcl:104
     ++#: git-gui.sh:3501 lib/blame.tcl:106
     + msgid "File:"
     + msgstr ""
     + 
     +-#: git-gui.sh:3255
     ++#: git-gui.sh:3549 lib/choose_repository.tcl:1100
     ++msgid "Open"
     ++msgstr ""
     ++
     ++#: git-gui.sh:3629
     + msgid "Refresh"
     + msgstr ""
     + 
     +-#: git-gui.sh:3276
     ++#: git-gui.sh:3650
     + msgid "Decrease Font Size"
     + msgstr ""
     + 
     +-#: git-gui.sh:3280
     ++#: git-gui.sh:3654
     + msgid "Increase Font Size"
     + msgstr ""
     + 
     +-#: git-gui.sh:3288 lib/blame.tcl:281
     ++#: git-gui.sh:3662 lib/blame.tcl:296
     + msgid "Encoding"
     + msgstr ""
     + 
     +-#: git-gui.sh:3299
     ++#: git-gui.sh:3673
     + msgid "Apply/Reverse Hunk"
     + msgstr ""
     + 
     +-#: git-gui.sh:3304
     ++#: git-gui.sh:3678
     + msgid "Apply/Reverse Line"
     + msgstr ""
     + 
     +-#: git-gui.sh:3323
     ++#: git-gui.sh:3684 git-gui.sh:3794 git-gui.sh:3805
     ++msgid "Revert Hunk"
     ++msgstr ""
     ++
     ++#: git-gui.sh:3689 git-gui.sh:3801 git-gui.sh:3812
     ++msgid "Revert Line"
     ++msgstr ""
     ++
     ++#: git-gui.sh:3694 git-gui.sh:3791
     ++msgid "Undo Last Revert"
      +msgstr ""
      +
     -+#. "file whose content is tracked/not tracked by git"
     -+msgid "tracked/untracked"
     ++#: git-gui.sh:3713
     + msgid "Run Merge Tool"
     + msgstr ""
     + 
     +-#: git-gui.sh:3328
     ++#: git-gui.sh:3718
     + msgid "Use Remote Version"
     + msgstr ""
     + 
     +-#: git-gui.sh:3332
     ++#: git-gui.sh:3722
     + msgid "Use Local Version"
     + msgstr ""
     + 
     +-#: git-gui.sh:3336
     ++#: git-gui.sh:3726
     + msgid "Revert To Base"
     + msgstr ""
     + 
     +-#: git-gui.sh:3354
     ++#: git-gui.sh:3744
     + msgid "Visualize These Changes In The Submodule"
     + msgstr ""
     + 
     +-#: git-gui.sh:3358
     ++#: git-gui.sh:3748
     + msgid "Visualize Current Branch History In The Submodule"
     + msgstr ""
     + 
     +-#: git-gui.sh:3362
     ++#: git-gui.sh:3752
     + msgid "Visualize All Branch History In The Submodule"
     + msgstr ""
     + 
     +-#: git-gui.sh:3367
     ++#: git-gui.sh:3757
     + msgid "Start git gui In The Submodule"
     + msgstr ""
     + 
     +-#: git-gui.sh:3389
     ++#: git-gui.sh:3793
     + msgid "Unstage Hunk From Commit"
     + msgstr ""
     + 
     +-#: git-gui.sh:3391
     ++#: git-gui.sh:3797
     + msgid "Unstage Lines From Commit"
     + msgstr ""
     + 
     +-#: git-gui.sh:3393
     ++#: git-gui.sh:3798 git-gui.sh:3809
     ++msgid "Revert Lines"
      +msgstr ""
     ++
     ++#: git-gui.sh:3800
     + msgid "Unstage Line From Commit"
     + msgstr ""
     + 
     +-#: git-gui.sh:3396
     ++#: git-gui.sh:3804
     + msgid "Stage Hunk For Commit"
     + msgstr ""
     + 
     +-#: git-gui.sh:3398
     ++#: git-gui.sh:3808
     + msgid "Stage Lines For Commit"
     + msgstr ""
     + 
     +-#: git-gui.sh:3400
     ++#: git-gui.sh:3811
     + msgid "Stage Line For Commit"
     + msgstr ""
     + 
     +-#: git-gui.sh:3424
     ++#: git-gui.sh:3861
     + msgid "Initializing..."
     + msgstr ""
     + 
     +-#: git-gui.sh:3541
     ++#: git-gui.sh:4017
     + #, tcl-format
     + msgid ""
     + "Possible environment issues exist.\n"
     +@@
     + "\n"
     + msgstr ""
     + 
     +-#: git-gui.sh:3570
     ++#: git-gui.sh:4046
     + msgid ""
     + "\n"
     + "This is due to a known issue with the\n"
     + "Tcl binary distributed by Cygwin."
     + msgstr ""
     + 
     +-#: git-gui.sh:3575
     ++#: git-gui.sh:4051
     + #, tcl-format
     + msgid ""
     + "\n"
     +@@
     + "~/.gitconfig file.\n"
     + msgstr ""
     + 
     +-#: lib/about.tcl:26
     +-msgid "git-gui - a graphical user interface for Git."
     ++#: lib/spellcheck.tcl:57
     ++msgid "Unsupported spell checker"
     + msgstr ""
     + 
     +-#: lib/blame.tcl:72
     +-msgid "File Viewer"
     ++#: lib/spellcheck.tcl:65
     ++msgid "Spell checking is unavailable"
     + msgstr ""
     + 
     +-#: lib/blame.tcl:78
     +-msgid "Commit:"
     ++#: lib/spellcheck.tcl:68
     ++msgid "Invalid spell checking configuration"
     + msgstr ""
     + 
     +-#: lib/blame.tcl:271
     +-msgid "Copy Commit"
     ++#: lib/spellcheck.tcl:70
     ++#, tcl-format
     ++msgid "Reverting dictionary to %s."
     + msgstr ""
     + 
     +-#: lib/blame.tcl:275
     +-msgid "Find Text..."
     ++#: lib/spellcheck.tcl:73
     ++msgid "Spell checker silently failed on startup"
     + msgstr ""
     + 
     +-#: lib/blame.tcl:284
     +-msgid "Do Full Copy Detection"
     ++#: lib/spellcheck.tcl:80
     ++msgid "Unrecognized spell checker"
     + msgstr ""
     + 
     +-#: lib/blame.tcl:288
     +-msgid "Show History Context"
     ++#: lib/spellcheck.tcl:186
     ++msgid "No Suggestions"
     + msgstr ""
     + 
     +-#: lib/blame.tcl:291
     +-msgid "Blame Parent Commit"
     ++#: lib/spellcheck.tcl:388
     ++msgid "Unexpected EOF from spell checker"
     + msgstr ""
     + 
     +-#: lib/blame.tcl:450
     +-#, tcl-format
     +-msgid "Reading %s..."
     ++#: lib/spellcheck.tcl:392
     ++msgid "Spell Checker Failed"
     + msgstr ""
     + 
     +-#: lib/blame.tcl:557
     +-msgid "Loading copy/move tracking annotations..."
     ++#: lib/transport.tcl:6 lib/remote_add.tcl:132
     ++#, tcl-format
     ++msgid "fetch %s"
     + msgstr ""
     + 
     +-#: lib/blame.tcl:577
     +-msgid "lines annotated"
     ++#: lib/transport.tcl:7
     ++#, tcl-format
     ++msgid "Fetching new changes from %s"
     + msgstr ""
     + 
     +-#: lib/blame.tcl:769
     +-msgid "Loading original location annotations..."
     ++#: lib/transport.tcl:18
     ++#, tcl-format
     ++msgid "remote prune %s"
     + msgstr ""
     + 
     +-#: lib/blame.tcl:772
     +-msgid "Annotation complete."
     ++#: lib/transport.tcl:19
     ++#, tcl-format
     ++msgid "Pruning tracking branches deleted from %s"
     + msgstr ""
     + 
     +-#: lib/blame.tcl:802
     +-msgid "Busy"
     ++#: lib/transport.tcl:25
     ++msgid "fetch all remotes"
     + msgstr ""
     + 
     +-#: lib/blame.tcl:803
     +-msgid "Annotation process is already running."
     ++#: lib/transport.tcl:26
     ++msgid "Fetching new changes from all remotes"
     + msgstr ""
     + 
     +-#: lib/blame.tcl:842
     +-msgid "Running thorough copy detection..."
     ++#: lib/transport.tcl:40
     ++msgid "remote prune all remotes"
     + msgstr ""
     + 
     +-#: lib/blame.tcl:910
     +-msgid "Loading annotation..."
     ++#: lib/transport.tcl:41
     ++msgid "Pruning tracking branches deleted from all remotes"
     + msgstr ""
     + 
     +-#: lib/blame.tcl:963
     +-msgid "Author:"
     ++#: lib/transport.tcl:54 lib/transport.tcl:92 lib/transport.tcl:110
     ++#: lib/remote_add.tcl:162
     ++#, tcl-format
     ++msgid "push %s"
     + msgstr ""
     + 
     +-#: lib/blame.tcl:967
     +-msgid "Committer:"
     ++#: lib/transport.tcl:55
     ++#, tcl-format
     ++msgid "Pushing changes to %s"
     + msgstr ""
     + 
     +-#: lib/blame.tcl:972
     +-msgid "Original File:"
     ++#: lib/transport.tcl:93
     ++#, tcl-format
     ++msgid "Mirroring to %s"
     + msgstr ""
     + 
     +-#: lib/blame.tcl:1020
     +-msgid "Cannot find HEAD commit:"
     ++#: lib/transport.tcl:111
     ++#, tcl-format
     ++msgid "Pushing %s %s to %s"
     + msgstr ""
     + 
     +-#: lib/blame.tcl:1075
     +-msgid "Cannot find parent commit:"
     ++#: lib/transport.tcl:132
     ++msgid "Push Branches"
     + msgstr ""
     + 
     +-#: lib/blame.tcl:1090
     +-msgid "Unable to display parent"
     ++#: lib/transport.tcl:141 lib/checkout_op.tcl:580 lib/remote_add.tcl:34
     ++#: lib/browser.tcl:292 lib/branch_checkout.tcl:30 lib/branch_rename.tcl:32
     ++#: lib/choose_font.tcl:45 lib/option.tcl:127 lib/tools_dlg.tcl:41
     ++#: lib/tools_dlg.tcl:202 lib/tools_dlg.tcl:345 lib/remote_branch_delete.tcl:43
     ++#: lib/branch_create.tcl:37 lib/branch_delete.tcl:34 lib/merge.tcl:178
     ++msgid "Cancel"
     + msgstr ""
     + 
     +-#: lib/blame.tcl:1091 lib/diff.tcl:320
     +-msgid "Error loading diff:"
     ++#: lib/transport.tcl:147
     ++msgid "Source Branches"
     + msgstr ""
     + 
     +-#: lib/blame.tcl:1231
     +-msgid "Originally By:"
     ++#: lib/transport.tcl:162
     ++msgid "Destination Repository"
     + msgstr ""
     + 
     +-#: lib/blame.tcl:1237
     +-msgid "In File:"
     ++#: lib/transport.tcl:165 lib/remote_branch_delete.tcl:51
     ++msgid "Remote:"
     + msgstr ""
     + 
     +-#: lib/blame.tcl:1242
     +-msgid "Copied Or Moved Here By:"
     ++#: lib/transport.tcl:187 lib/remote_branch_delete.tcl:72
     ++msgid "Arbitrary Location:"
     + msgstr ""
     + 
     +-#: lib/branch_checkout.tcl:14 lib/branch_checkout.tcl:19
     +-msgid "Checkout Branch"
     ++#: lib/transport.tcl:205
     ++msgid "Transfer Options"
     + msgstr ""
     + 
     +-#: lib/branch_checkout.tcl:23
     +-msgid "Checkout"
     ++#: lib/transport.tcl:207
     ++msgid "Force overwrite existing branch (may discard changes)"
     + msgstr ""
     + 
     +-#: lib/branch_checkout.tcl:27 lib/branch_create.tcl:35
     +-#: lib/branch_delete.tcl:32 lib/branch_rename.tcl:30 lib/browser.tcl:282
     +-#: lib/checkout_op.tcl:579 lib/choose_font.tcl:43 lib/merge.tcl:172
     +-#: lib/option.tcl:125 lib/remote_add.tcl:32 lib/remote_branch_delete.tcl:42
     +-#: lib/tools_dlg.tcl:40 lib/tools_dlg.tcl:204 lib/tools_dlg.tcl:352
     +-#: lib/transport.tcl:108
     +-msgid "Cancel"
     ++#: lib/transport.tcl:211
     ++msgid "Use thin pack (for slow network connections)"
     + msgstr ""
     + 
     +-#: lib/branch_checkout.tcl:32 lib/browser.tcl:287 lib/tools_dlg.tcl:328
     +-msgid "Revision"
     ++#: lib/transport.tcl:215
     ++msgid "Include tags"
     + msgstr ""
     + 
     +-#: lib/branch_checkout.tcl:36 lib/branch_create.tcl:69 lib/option.tcl:280
     +-msgid "Options"
     ++#: lib/transport.tcl:229
     ++#, tcl-format
     ++msgid "%s (%s): Push"
     + msgstr ""
     + 
     +-#: lib/branch_checkout.tcl:39 lib/branch_create.tcl:92
     +-msgid "Fetch Tracking Branch"
     ++#: lib/checkout_op.tcl:85
     ++#, tcl-format
     ++msgid "Fetching %s from %s"
     + msgstr ""
     + 
     +-#: lib/branch_checkout.tcl:44
     +-msgid "Detach From Local Branch"
     ++#: lib/checkout_op.tcl:133
     ++#, tcl-format
     ++msgid "fatal: Cannot resolve %s"
     + msgstr ""
     + 
     +-#: lib/branch_create.tcl:22
     +-msgid "Create Branch"
     ++#: lib/checkout_op.tcl:146 lib/sshkey.tcl:58 lib/console.tcl:81
     ++#: lib/database.tcl:30
     ++msgid "Close"
     + msgstr ""
     + 
     +-#: lib/branch_create.tcl:27
     +-msgid "Create New Branch"
     ++#: lib/checkout_op.tcl:175
     ++#, tcl-format
     ++msgid "Branch '%s' does not exist."
     + msgstr ""
     + 
     +-#: lib/branch_create.tcl:31 lib/choose_repository.tcl:381
     +-msgid "Create"
     ++#: lib/checkout_op.tcl:194
     ++#, tcl-format
     ++msgid "Failed to configure simplified git-pull for '%s'."
     + msgstr ""
     + 
     +-#: lib/branch_create.tcl:40
     +-msgid "Branch Name"
     ++#: lib/checkout_op.tcl:202 lib/branch_rename.tcl:102
     ++#, tcl-format
     ++msgid "Branch '%s' already exists."
     + msgstr ""
     + 
     +-#: lib/branch_create.tcl:43 lib/remote_add.tcl:39 lib/tools_dlg.tcl:50
     +-msgid "Name:"
     ++#: lib/checkout_op.tcl:229
     ++#, tcl-format
     ++msgid ""
     ++"Branch '%s' already exists.\n"
     ++"\n"
     ++"It cannot fast-forward to %s.\n"
     ++"A merge is required."
     + msgstr ""
     + 
     +-#: lib/branch_create.tcl:58
     +-msgid "Match Tracking Branch Name"
     ++#: lib/checkout_op.tcl:243
     ++#, tcl-format
     ++msgid "Merge strategy '%s' not supported."
     + msgstr ""
     + 
     +-#: lib/branch_create.tcl:66
     +-msgid "Starting Revision"
     ++#: lib/checkout_op.tcl:262
     ++#, tcl-format
     ++msgid "Failed to update '%s'."
     + msgstr ""
     + 
     +-#: lib/branch_create.tcl:72
     +-msgid "Update Existing Branch:"
     ++#: lib/checkout_op.tcl:274
     ++msgid "Staging area (index) is already locked."
     + msgstr ""
     + 
     +-#: lib/branch_create.tcl:75
     +-msgid "No"
     ++#: lib/checkout_op.tcl:289
     ++msgid ""
     ++"Last scanned state does not match repository state.\n"
     ++"\n"
     ++"Another Git program has modified this repository since the last scan.  A "
     ++"rescan must be performed before the current branch can be changed.\n"
     ++"\n"
     ++"The rescan will be automatically started now.\n"
     + msgstr ""
     + 
     +-#: lib/branch_create.tcl:80
     +-msgid "Fast Forward Only"
     ++#: lib/checkout_op.tcl:345
     ++#, tcl-format
     ++msgid "Updating working directory to '%s'..."
     + msgstr ""
     + 
     +-#: lib/branch_create.tcl:85 lib/checkout_op.tcl:571
     +-msgid "Reset"
     ++#: lib/checkout_op.tcl:346
     ++msgid "files checked out"
     + msgstr ""
     + 
     +-#: lib/branch_create.tcl:97
     +-msgid "Checkout After Creation"
     ++#: lib/checkout_op.tcl:377
     ++#, tcl-format
     ++msgid "Aborted checkout of '%s' (file level merging is required)."
     + msgstr ""
     + 
     +-#: lib/branch_create.tcl:131
     +-msgid "Please select a tracking branch."
     ++#: lib/checkout_op.tcl:378
     ++msgid "File level merge required."
     + msgstr ""
     + 
     +-#: lib/branch_create.tcl:140
     ++#: lib/checkout_op.tcl:382
     + #, tcl-format
     +-msgid "Tracking branch %s is not a branch in the remote repository."
     ++msgid "Staying on branch '%s'."
     + msgstr ""
     + 
     +-#: lib/branch_create.tcl:153 lib/branch_rename.tcl:86
     +-msgid "Please supply a branch name."
     ++#: lib/checkout_op.tcl:453
     ++msgid ""
     ++"You are no longer on a local branch.\n"
     ++"\n"
     ++"If you wanted to be on a branch, create one now starting from 'This Detached "
     ++"Checkout'."
     + msgstr ""
     + 
     +-#: lib/branch_create.tcl:164 lib/branch_rename.tcl:106
     ++#: lib/checkout_op.tcl:504 lib/checkout_op.tcl:508
     + #, tcl-format
     +-msgid "'%s' is not an acceptable branch name."
     ++msgid "Checked out '%s'."
     + msgstr ""
     + 
     +-#: lib/branch_delete.tcl:15
     +-msgid "Delete Branch"
     ++#: lib/checkout_op.tcl:536
     ++#, tcl-format
     ++msgid "Resetting '%s' to '%s' will lose the following commits:"
     + msgstr ""
     + 
     +-#: lib/branch_delete.tcl:20
     +-msgid "Delete Local Branch"
     ++#: lib/checkout_op.tcl:558
     ++msgid "Recovering lost commits may not be easy."
     + msgstr ""
     + 
     +-#: lib/branch_delete.tcl:37
     +-msgid "Local Branches"
     ++#: lib/checkout_op.tcl:563
     ++#, tcl-format
     ++msgid "Reset '%s'?"
     + msgstr ""
     + 
     +-#: lib/branch_delete.tcl:52
     +-msgid "Delete Only If Merged Into"
     ++#: lib/checkout_op.tcl:568 lib/tools_dlg.tcl:336 lib/merge.tcl:170
     ++msgid "Visualize"
     + msgstr ""
     + 
     +-#: lib/branch_delete.tcl:54 lib/remote_branch_delete.tcl:119
     +-msgid "Always (Do not perform merge checks)"
     ++#: lib/checkout_op.tcl:572 lib/branch_create.tcl:85
     ++msgid "Reset"
     + msgstr ""
     + 
     +-#: lib/branch_delete.tcl:103
     ++#: lib/checkout_op.tcl:636
     + #, tcl-format
     +-msgid "The following branches are not completely merged into %s:"
     +-msgstr ""
     +-
     +-#: lib/branch_delete.tcl:115 lib/remote_branch_delete.tcl:217
     + msgid ""
     +-"Recovering deleted branches is difficult.\n"
     ++"Failed to set current branch.\n"
     + "\n"
     +-"Delete the selected branches?"
     ++"This working directory is only partially switched.  We successfully updated "
     ++"your files, but failed to update an internal Git file.\n"
     ++"\n"
     ++"This should not have occurred.  %s will now close and give up."
     + msgstr ""
     + 
     +-#: lib/branch_delete.tcl:141
     ++#: lib/remote_add.tcl:20
     + #, tcl-format
     +-msgid ""
     +-"Failed to delete branches:\n"
     +-"%s"
     ++msgid "%s (%s): Add Remote"
     + msgstr ""
     + 
     +-#: lib/branch_rename.tcl:14 lib/branch_rename.tcl:22
     +-msgid "Rename Branch"
     ++#: lib/remote_add.tcl:25
     ++msgid "Add New Remote"
     + msgstr ""
     + 
     +-#: lib/branch_rename.tcl:26
     +-msgid "Rename"
     ++#: lib/remote_add.tcl:30 lib/tools_dlg.tcl:37
     ++msgid "Add"
     + msgstr ""
     + 
     +-#: lib/branch_rename.tcl:36
     +-msgid "Branch:"
     ++#: lib/remote_add.tcl:39
     ++msgid "Remote Details"
     + msgstr ""
     + 
     +-#: lib/branch_rename.tcl:39
     +-msgid "New Name:"
     ++#: lib/remote_add.tcl:41 lib/tools_dlg.tcl:51 lib/branch_create.tcl:44
     ++msgid "Name:"
     + msgstr ""
     + 
     +-#: lib/branch_rename.tcl:75
     +-msgid "Please select a branch to rename."
     ++#: lib/remote_add.tcl:50
     ++msgid "Location:"
     + msgstr ""
     + 
     +-#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:202
     +-#, tcl-format
     +-msgid "Branch '%s' already exists."
     ++#: lib/remote_add.tcl:60
     ++msgid "Further Action"
     + msgstr ""
     + 
     +-#: lib/branch_rename.tcl:117
     +-#, tcl-format
     +-msgid "Failed to rename '%s'."
     ++#: lib/remote_add.tcl:63
     ++msgid "Fetch Immediately"
     + msgstr ""
     + 
     +-#: lib/browser.tcl:17
     +-msgid "Starting..."
     ++#: lib/remote_add.tcl:69
     ++msgid "Initialize Remote Repository and Push"
     + msgstr ""
     + 
     +-#: lib/browser.tcl:26
     +-msgid "File Browser"
     ++#: lib/remote_add.tcl:75
     ++msgid "Do Nothing Else Now"
     + msgstr ""
     + 
     +-#: lib/browser.tcl:126 lib/browser.tcl:143
     +-#, tcl-format
     +-msgid "Loading %s..."
     ++#: lib/remote_add.tcl:100
     ++msgid "Please supply a remote name."
     + msgstr ""
     + 
     +-#: lib/browser.tcl:187
     +-msgid "[Up To Parent]"
     ++#: lib/remote_add.tcl:113
     ++#, tcl-format
     ++msgid "'%s' is not an acceptable remote name."
     + msgstr ""
     + 
     +-#: lib/browser.tcl:267 lib/browser.tcl:273
     +-msgid "Browse Branch Files"
     ++#: lib/remote_add.tcl:124
     ++#, tcl-format
     ++msgid "Failed to add remote '%s' of location '%s'."
     + msgstr ""
     + 
     +-#: lib/browser.tcl:278 lib/choose_repository.tcl:398
     +-#: lib/choose_repository.tcl:486 lib/choose_repository.tcl:497
     +-#: lib/choose_repository.tcl:1028
     +-msgid "Browse"
     ++#: lib/remote_add.tcl:133
     ++#, tcl-format
     ++msgid "Fetching the %s"
     + msgstr ""
     + 
     +-#: lib/checkout_op.tcl:85
     ++#: lib/remote_add.tcl:156
     + #, tcl-format
     +-msgid "Fetching %s from %s"
     ++msgid "Do not know how to initialize repository at location '%s'."
     + msgstr ""
     + 
     +-#: lib/checkout_op.tcl:133
     ++#: lib/remote_add.tcl:163
     + #, tcl-format
     +-msgid "fatal: Cannot resolve %s"
     ++msgid "Setting up the %s (at %s)"
     + msgstr ""
     + 
     +-#: lib/checkout_op.tcl:146 lib/console.tcl:81 lib/database.tcl:31
     +-#: lib/sshkey.tcl:53
     +-msgid "Close"
     ++#: lib/browser.tcl:17
     ++msgid "Starting..."
     + msgstr ""
     + 
     +-#: lib/checkout_op.tcl:175
     ++#: lib/browser.tcl:27
     + #, tcl-format
     +-msgid "Branch '%s' does not exist."
     ++msgid "%s (%s): File Browser"
     + msgstr ""
     + 
     +-#: lib/checkout_op.tcl:194
     ++#: lib/browser.tcl:132 lib/browser.tcl:149
     + #, tcl-format
     +-msgid "Failed to configure simplified git-pull for '%s'."
     ++msgid "Loading %s..."
     + msgstr ""
     + 
     +-#: lib/checkout_op.tcl:229
     +-#, tcl-format
     +-msgid ""
     +-"Branch '%s' already exists.\n"
     +-"\n"
     +-"It cannot fast-forward to %s.\n"
     +-"A merge is required."
     ++#: lib/browser.tcl:193
     ++msgid "[Up To Parent]"
     + msgstr ""
     + 
     +-#: lib/checkout_op.tcl:243
     ++#: lib/browser.tcl:275
     + #, tcl-format
     +-msgid "Merge strategy '%s' not supported."
     ++msgid "%s (%s): Browse Branch Files"
     + msgstr ""
     + 
     +-#: lib/checkout_op.tcl:262
     +-#, tcl-format
     +-msgid "Failed to update '%s'."
     ++#: lib/browser.tcl:282
     ++msgid "Browse Branch Files"
     + msgstr ""
     + 
     +-#: lib/checkout_op.tcl:274
     +-msgid "Staging area (index) is already locked."
     ++#: lib/browser.tcl:288 lib/choose_repository.tcl:437
     ++#: lib/choose_repository.tcl:524 lib/choose_repository.tcl:533
     ++#: lib/choose_repository.tcl:1115
     ++msgid "Browse"
     + msgstr ""
     + 
     +-#: lib/checkout_op.tcl:289
     +-msgid ""
     +-"Last scanned state does not match repository state.\n"
     +-"\n"
     +-"Another Git program has modified this repository since the last scan.  A "
     +-"rescan must be performed before the current branch can be changed.\n"
     +-"\n"
     +-"The rescan will be automatically started now.\n"
     ++#: lib/browser.tcl:297 lib/branch_checkout.tcl:35 lib/tools_dlg.tcl:321
     ++msgid "Revision"
     + msgstr ""
     + 
     +-#: lib/checkout_op.tcl:345
     +-#, tcl-format
     +-msgid "Updating working directory to '%s'..."
     ++#: lib/index.tcl:6
     ++msgid "Unable to unlock the index."
     + msgstr ""
     + 
     +-#: lib/checkout_op.tcl:346
     +-msgid "files checked out"
     ++#: lib/index.tcl:30
     ++msgid "Index Error"
     + msgstr ""
     + 
     +-#: lib/checkout_op.tcl:376
     +-#, tcl-format
     +-msgid "Aborted checkout of '%s' (file level merging is required)."
     ++#: lib/index.tcl:32
     ++msgid ""
     ++"Updating the Git index failed.  A rescan will be automatically started to "
     ++"resynchronize git-gui."
     + msgstr ""
     + 
     +-#: lib/checkout_op.tcl:377
     +-msgid "File level merge required."
     ++#: lib/index.tcl:43
     ++msgid "Continue"
     + msgstr ""
     + 
     +-#: lib/checkout_op.tcl:381
     +-#, tcl-format
     +-msgid "Staying on branch '%s'."
     ++#: lib/index.tcl:46
     ++msgid "Unlock Index"
     + msgstr ""
     + 
     +-#: lib/checkout_op.tcl:452
     +-msgid ""
     +-"You are no longer on a local branch.\n"
     +-"\n"
     +-"If you wanted to be on a branch, create one now starting from 'This Detached "
     +-"Checkout'."
     ++#: lib/index.tcl:77 lib/index.tcl:146 lib/index.tcl:220 lib/index.tcl:587
     ++#: lib/choose_repository.tcl:999
     ++msgid "files"
     + msgstr ""
     + 
     +-#: lib/checkout_op.tcl:503 lib/checkout_op.tcl:507
     +-#, tcl-format
     +-msgid "Checked out '%s'."
     ++#: lib/index.tcl:326
     ++msgid "Unstaging selected files from commit"
     + msgstr ""
     + 
     +-#: lib/checkout_op.tcl:535
     ++#: lib/index.tcl:330
     + #, tcl-format
     +-msgid "Resetting '%s' to '%s' will lose the following commits:"
     ++msgid "Unstaging %s from commit"
     + msgstr ""
     + 
     +-#: lib/checkout_op.tcl:557
     +-msgid "Recovering lost commits may not be easy."
     ++#: lib/index.tcl:369
     ++msgid "Ready to commit."
     + msgstr ""
     + 
     +-#: lib/checkout_op.tcl:562
     +-#, tcl-format
     +-msgid "Reset '%s'?"
     ++#: lib/index.tcl:378
     ++msgid "Adding selected files"
     + msgstr ""
     + 
     +-#: lib/checkout_op.tcl:567 lib/merge.tcl:164 lib/tools_dlg.tcl:343
     +-msgid "Visualize"
     ++#: lib/index.tcl:382
     ++#, tcl-format
     ++msgid "Adding %s"
     + msgstr ""
     + 
     +-#: lib/checkout_op.tcl:635
     ++#: lib/index.tcl:412
     + #, tcl-format
     +-msgid ""
     +-"Failed to set current branch.\n"
     +-"\n"
     +-"This working directory is only partially switched.  We successfully updated "
     +-"your files, but failed to update an internal Git file.\n"
     +-"\n"
     +-"This should not have occurred.  %s will now close and give up."
     ++msgid "Stage %d untracked files?"
     + msgstr ""
     + 
     +-#: lib/choose_font.tcl:39
     +-msgid "Select"
     ++#: lib/index.tcl:420
     ++msgid "Adding all changed files"
     + msgstr ""
     + 
     +-#: lib/choose_font.tcl:53
     +-msgid "Font Family"
     ++#: lib/index.tcl:503
     ++#, tcl-format
     ++msgid "Revert changes in file %s?"
     + msgstr ""
     + 
     +-#: lib/choose_font.tcl:74
     +-msgid "Font Size"
     ++#: lib/index.tcl:508
     ++#, tcl-format
     ++msgid "Revert changes in these %i files?"
     + msgstr ""
     + 
     +-#: lib/choose_font.tcl:91
     +-msgid "Font Example"
     ++#: lib/index.tcl:517
     ++msgid "Any unstaged changes will be permanently lost by the revert."
     + msgstr ""
     + 
     +-#: lib/choose_font.tcl:103
     +-msgid ""
     +-"This is example text.\n"
     +-"If you like this text, it can be your font."
     ++#: lib/index.tcl:520 lib/index.tcl:563
     ++msgid "Do Nothing"
     + msgstr ""
     + 
     +-#: lib/choose_repository.tcl:28
     +-msgid "Git Gui"
     ++#: lib/index.tcl:545
     ++#, tcl-format
     ++msgid "Delete untracked file %s?"
     + msgstr ""
     + 
     +-#: lib/choose_repository.tcl:87 lib/choose_repository.tcl:386
     +-msgid "Create New Repository"
     ++#: lib/index.tcl:550
     ++#, tcl-format
     ++msgid "Delete these %i untracked files?"
     + msgstr ""
     + 
     +-#: lib/choose_repository.tcl:93
     +-msgid "New..."
     ++#: lib/index.tcl:560
     ++msgid "Files will be permanently deleted."
     + msgstr ""
     + 
     +-#: lib/choose_repository.tcl:100 lib/choose_repository.tcl:471
     +-msgid "Clone Existing Repository"
     ++#: lib/index.tcl:564
     ++msgid "Delete Files"
     + msgstr ""
     + 
     +-#: lib/choose_repository.tcl:106
     +-msgid "Clone..."
     ++#: lib/index.tcl:586
     ++msgid "Deleting"
     + msgstr ""
     + 
     +-#: lib/choose_repository.tcl:113 lib/choose_repository.tcl:1016
     +-msgid "Open Existing Repository"
     ++#: lib/index.tcl:665
     ++msgid "Encountered errors deleting files:\n"
     + msgstr ""
     + 
     +-#: lib/choose_repository.tcl:119
     +-msgid "Open..."
     ++#: lib/index.tcl:674
     ++#, tcl-format
     ++msgid "None of the %d selected files could be deleted."
     + msgstr ""
     + 
     +-#: lib/choose_repository.tcl:132
     +-msgid "Recent Repositories"
     ++#: lib/index.tcl:679
     ++#, tcl-format
     ++msgid "%d of the %d selected files could not be deleted."
     + msgstr ""
     + 
     +-#: lib/choose_repository.tcl:138
     +-msgid "Open Recent Repository:"
     ++#: lib/index.tcl:726
     ++msgid "Reverting selected files"
     + msgstr ""
     + 
     +-#: lib/choose_repository.tcl:306 lib/choose_repository.tcl:313
     +-#: lib/choose_repository.tcl:320
     ++#: lib/index.tcl:730
     + #, tcl-format
     +-msgid "Failed to create repository %s:"
     ++msgid "Reverting %s"
     + msgstr ""
     + 
     +-#: lib/choose_repository.tcl:391
     +-msgid "Directory:"
     ++#: lib/branch_checkout.tcl:16
     ++#, tcl-format
     ++msgid "%s (%s): Checkout Branch"
     + msgstr ""
     + 
     +-#: lib/choose_repository.tcl:423 lib/choose_repository.tcl:550
     +-#: lib/choose_repository.tcl:1052
     +-msgid "Git Repository"
     ++#: lib/branch_checkout.tcl:21
     ++msgid "Checkout Branch"
     + msgstr ""
     + 
     +-#: lib/choose_repository.tcl:448
     +-#, tcl-format
     +-msgid "Directory %s already exists."
     ++#: lib/branch_checkout.tcl:26
     ++msgid "Checkout"
     + msgstr ""
     + 
     +-#: lib/choose_repository.tcl:452
     +-#, tcl-format
     +-msgid "File %s already exists."
     ++#: lib/branch_checkout.tcl:39 lib/option.tcl:310 lib/branch_create.tcl:69
     ++msgid "Options"
     + msgstr ""
     + 
     +-#: lib/choose_repository.tcl:466
     +-msgid "Clone"
     ++#: lib/branch_checkout.tcl:42 lib/branch_create.tcl:92
     ++msgid "Fetch Tracking Branch"
     + msgstr ""
     + 
     +-#: lib/choose_repository.tcl:479
     +-msgid "Source Location:"
     ++#: lib/branch_checkout.tcl:47
     ++msgid "Detach From Local Branch"
     + msgstr ""
     + 
     +-#: lib/choose_repository.tcl:490
     +-msgid "Target Directory:"
     ++#: lib/status_bar.tcl:263
     ++#, tcl-format
     ++msgid "%s ... %*i of %*i %s (%3i%%)"
     + msgstr ""
     + 
     +-#: lib/choose_repository.tcl:502
     +-msgid "Clone Type:"
     ++#: lib/remote.tcl:200
     ++msgid "Push to"
     + msgstr ""
     + 
     +-#: lib/choose_repository.tcl:508
     +-msgid "Standard (Fast, Semi-Redundant, Hardlinks)"
     ++#: lib/remote.tcl:218
     ++msgid "Remove Remote"
     + msgstr ""
     + 
     +-#: lib/choose_repository.tcl:514
     +-msgid "Full Copy (Slower, Redundant Backup)"
     ++#: lib/remote.tcl:223
     ++msgid "Prune from"
     + msgstr ""
     + 
     +-#: lib/choose_repository.tcl:520
     +-msgid "Shared (Fastest, Not Recommended, No Backup)"
     ++#: lib/remote.tcl:228
     ++msgid "Fetch from"
     ++msgstr ""
     ++
     ++#: lib/remote.tcl:249 lib/remote.tcl:253 lib/remote.tcl:258 lib/remote.tcl:264
     ++msgid "All"
     + msgstr ""
     + 
     +-#: lib/choose_repository.tcl:556 lib/choose_repository.tcl:603
     +-#: lib/choose_repository.tcl:749 lib/choose_repository.tcl:819
     +-#: lib/choose_repository.tcl:1058 lib/choose_repository.tcl:1066
     ++#: lib/branch_rename.tcl:15
     + #, tcl-format
     +-msgid "Not a Git repository: %s"
     ++msgid "%s (%s): Rename Branch"
     + msgstr ""
     + 
     +-#: lib/choose_repository.tcl:592
     +-msgid "Standard only available for local repository."
     ++#: lib/branch_rename.tcl:23
     ++msgid "Rename Branch"
     + msgstr ""
     + 
     +-#: lib/choose_repository.tcl:596
     +-msgid "Shared only available for local repository."
     ++#: lib/branch_rename.tcl:28
     ++msgid "Rename"
     + msgstr ""
     + 
     +-#: lib/choose_repository.tcl:617
     +-#, tcl-format
     +-msgid "Location %s already exists."
     ++#: lib/branch_rename.tcl:38
     ++msgid "Branch:"
     + msgstr ""
     + 
     +-#: lib/choose_repository.tcl:628
     +-msgid "Failed to configure origin"
     ++#: lib/branch_rename.tcl:46
     ++msgid "New Name:"
     + msgstr ""
     + 
     +-#: lib/choose_repository.tcl:640
     +-msgid "Counting objects"
     ++#: lib/branch_rename.tcl:81
     ++msgid "Please select a branch to rename."
     + msgstr ""
     + 
     +-#: lib/choose_repository.tcl:641
     +-msgid "buckets"
     ++#: lib/branch_rename.tcl:92 lib/branch_create.tcl:154
     ++msgid "Please supply a branch name."
     + msgstr ""
     + 
     +-#: lib/choose_repository.tcl:665
     ++#: lib/branch_rename.tcl:112 lib/branch_create.tcl:165
     + #, tcl-format
     +-msgid "Unable to copy objects/info/alternates: %s"
     ++msgid "'%s' is not an acceptable branch name."
     + msgstr ""
     + 
     +-#: lib/choose_repository.tcl:701
     ++#: lib/branch_rename.tcl:123
     + #, tcl-format
     +-msgid "Nothing to clone from %s."
     ++msgid "Failed to rename '%s'."
     + msgstr ""
     + 
     +-#: lib/choose_repository.tcl:703 lib/choose_repository.tcl:917
     +-#: lib/choose_repository.tcl:929
     +-msgid "The 'master' branch has not been initialized."
     ++#: lib/choose_font.tcl:41
     ++msgid "Select"
     + msgstr ""
     + 
     +-#: lib/choose_repository.tcl:716
     +-msgid "Hardlinks are unavailable.  Falling back to copying."
     ++#: lib/choose_font.tcl:55
     ++msgid "Font Family"
     + msgstr ""
     + 
     +-#: lib/choose_repository.tcl:728
     +-#, tcl-format
     +-msgid "Cloning from %s"
     ++#: lib/choose_font.tcl:76
     ++msgid "Font Size"
     + msgstr ""
     + 
     +-#: lib/choose_repository.tcl:759
     +-msgid "Copying objects"
     ++#: lib/choose_font.tcl:93
     ++msgid "Font Example"
     + msgstr ""
     + 
     +-#: lib/choose_repository.tcl:760
     +-msgid "KiB"
     ++#: lib/choose_font.tcl:105
     ++msgid ""
     ++"This is example text.\n"
     ++"If you like this text, it can be your font."
     + msgstr ""
     + 
     +-#: lib/choose_repository.tcl:784
     ++#: lib/option.tcl:11
     + #, tcl-format
     +-msgid "Unable to copy object: %s"
     ++msgid "Invalid global encoding '%s'"
     + msgstr ""
     + 
     +-#: lib/choose_repository.tcl:794
     +-msgid "Linking objects"
     ++#: lib/option.tcl:19
     ++#, tcl-format
     ++msgid "Invalid repo encoding '%s'"
     + msgstr ""
     + 
     +-#: lib/choose_repository.tcl:795
     +-msgid "objects"
     ++#: lib/option.tcl:119
     ++msgid "Restore Defaults"
     ++msgstr ""
     ++
     ++#: lib/option.tcl:123
     ++msgid "Save"
     + msgstr ""
     + 
     +-#: lib/choose_repository.tcl:803
     ++#: lib/option.tcl:133
     + #, tcl-format
     +-msgid "Unable to hardlink object: %s"
     ++msgid "%s Repository"
     + msgstr ""
     + 
     +-#: lib/choose_repository.tcl:858
     +-msgid "Cannot fetch branches and objects.  See console output for details."
     ++#: lib/option.tcl:134
     ++msgid "Global (All Repositories)"
     + msgstr ""
     + 
     +-#: lib/choose_repository.tcl:869
     +-msgid "Cannot fetch tags.  See console output for details."
     ++#: lib/option.tcl:140
     ++msgid "User Name"
     + msgstr ""
     + 
     +-#: lib/choose_repository.tcl:893
     +-msgid "Cannot determine HEAD.  See console output for details."
     ++#: lib/option.tcl:141
     ++msgid "Email Address"
     + msgstr ""
     + 
     +-#: lib/choose_repository.tcl:902
     +-#, tcl-format
     +-msgid "Unable to cleanup %s"
     ++#: lib/option.tcl:143
     ++msgid "Summarize Merge Commits"
     + msgstr ""
     + 
     +-#: lib/choose_repository.tcl:908
     +-msgid "Clone failed."
     ++#: lib/option.tcl:144
     ++msgid "Merge Verbosity"
     + msgstr ""
     + 
     +-#: lib/choose_repository.tcl:915
     +-msgid "No default branch obtained."
     ++#: lib/option.tcl:145
     ++msgid "Show Diffstat After Merge"
     + msgstr ""
     + 
     +-#: lib/choose_repository.tcl:926
     +-#, tcl-format
     +-msgid "Cannot resolve %s as a commit."
     ++#: lib/option.tcl:146
     ++msgid "Use Merge Tool"
     + msgstr ""
     + 
     +-#: lib/choose_repository.tcl:938
     +-msgid "Creating working directory"
     ++#: lib/option.tcl:148
     ++msgid "Trust File Modification Timestamps"
     + msgstr ""
     + 
     +-#: lib/choose_repository.tcl:939 lib/index.tcl:67 lib/index.tcl:130
     +-#: lib/index.tcl:198
     +-msgid "files"
     ++#: lib/option.tcl:149
     ++msgid "Prune Tracking Branches During Fetch"
     + msgstr ""
     + 
     +-#: lib/choose_repository.tcl:968
     +-msgid "Initial file checkout failed."
     ++#: lib/option.tcl:150
     ++msgid "Match Tracking Branches"
     + msgstr ""
     + 
     +-#: lib/choose_repository.tcl:1011
     +-msgid "Open"
     ++#: lib/option.tcl:151
     ++msgid "Use Textconv For Diffs and Blames"
     + msgstr ""
     + 
     +-#: lib/choose_repository.tcl:1021
     +-msgid "Repository:"
     ++#: lib/option.tcl:152
     ++msgid "Blame Copy Only On Changed Files"
     + msgstr ""
     + 
     +-#: lib/choose_repository.tcl:1072
     +-#, tcl-format
     +-msgid "Failed to open repository %s:"
     ++#: lib/option.tcl:153
     ++msgid "Maximum Length of Recent Repositories List"
     + msgstr ""
     + 
     +-#: lib/choose_rev.tcl:53
     +-msgid "This Detached Checkout"
     ++#: lib/option.tcl:154
     ++msgid "Minimum Letters To Blame Copy On"
     + msgstr ""
     + 
     +-#: lib/choose_rev.tcl:60
     +-msgid "Revision Expression:"
     ++#: lib/option.tcl:155
     ++msgid "Blame History Context Radius (days)"
     + msgstr ""
     + 
     +-#: lib/choose_rev.tcl:74
     +-msgid "Local Branch"
     ++#: lib/option.tcl:156
     ++msgid "Number of Diff Context Lines"
     + msgstr ""
     + 
     +-#: lib/choose_rev.tcl:79
     +-msgid "Tracking Branch"
     ++#: lib/option.tcl:157
     ++msgid "Additional Diff Parameters"
     + msgstr ""
     + 
     +-#: lib/choose_rev.tcl:84 lib/choose_rev.tcl:538
     +-msgid "Tag"
     ++#: lib/option.tcl:158
     ++msgid "Commit Message Text Width"
     + msgstr ""
     + 
     +-#: lib/choose_rev.tcl:317
     +-#, tcl-format
     +-msgid "Invalid revision: %s"
     ++#: lib/option.tcl:159
     ++msgid "New Branch Name Template"
     + msgstr ""
     + 
     +-#: lib/choose_rev.tcl:338
     +-msgid "No revision selected."
     ++#: lib/option.tcl:160
     ++msgid "Default File Contents Encoding"
     + msgstr ""
     + 
     +-#: lib/choose_rev.tcl:346
     +-msgid "Revision expression is empty."
     ++#: lib/option.tcl:161
     ++msgid "Warn before committing to a detached head"
     + msgstr ""
     + 
     +-#: lib/choose_rev.tcl:531
     +-msgid "Updated"
     ++#: lib/option.tcl:162
     ++msgid "Staging of untracked files"
     + msgstr ""
     + 
     +-#: lib/choose_rev.tcl:559
     +-msgid "URL"
     ++#: lib/option.tcl:163
     ++msgid "Show untracked files"
     + msgstr ""
     + 
     +-#: lib/commit.tcl:9
     +-msgid ""
     +-"There is nothing to amend.\n"
     +-"\n"
     +-"You are about to create the initial commit.  There is no commit before this "
     +-"to amend.\n"
     ++#: lib/option.tcl:164
     ++msgid "Tab spacing"
     + msgstr ""
     + 
     +-#: lib/commit.tcl:18
     +-msgid ""
     +-"Cannot amend while merging.\n"
     +-"\n"
     +-"You are currently in the middle of a merge that has not been fully "
     +-"completed.  You cannot amend the prior commit unless you first abort the "
     +-"current merge activity.\n"
     ++#: lib/option.tcl:182 lib/option.tcl:197 lib/option.tcl:220 lib/option.tcl:282
     ++#: lib/database.tcl:57
     ++#, tcl-format
     ++msgid "%s:"
     + msgstr ""
     + 
     +-#: lib/commit.tcl:48
     +-msgid "Error loading commit data for amend:"
     ++#: lib/option.tcl:210
     ++msgid "Change"
     + msgstr ""
     + 
     +-#: lib/commit.tcl:75
     +-msgid "Unable to obtain your identity:"
     ++#: lib/option.tcl:254
     ++msgid "Spelling Dictionary:"
     + msgstr ""
     + 
     +-#: lib/commit.tcl:80
     +-msgid "Invalid GIT_COMMITTER_IDENT:"
     ++#: lib/option.tcl:284
     ++msgid "Change Font"
     + msgstr ""
     + 
     +-#: lib/commit.tcl:129
     ++#: lib/option.tcl:288
     + #, tcl-format
     +-msgid "warning: Tcl does not support encoding '%s'."
     ++msgid "Choose %s"
     + msgstr ""
     + 
     +-#: lib/commit.tcl:149
     +-msgid ""
     +-"Last scanned state does not match repository state.\n"
     +-"\n"
     +-"Another Git program has modified this repository since the last scan.  A "
     +-"rescan must be performed before another commit can be created.\n"
     +-"\n"
     +-"The rescan will be automatically started now.\n"
     ++#: lib/option.tcl:294
     ++msgid "pt."
     + msgstr ""
     + 
     +-#: lib/commit.tcl:172
     ++#: lib/option.tcl:308
     ++msgid "Preferences"
     ++msgstr ""
     ++
     ++#: lib/option.tcl:345
     ++msgid "Failed to completely save options:"
     ++msgstr ""
     ++
     ++#: lib/encoding.tcl:443
     ++msgid "Default"
     ++msgstr ""
     ++
     ++#: lib/encoding.tcl:448
     + #, tcl-format
     +-msgid ""
     +-"Unmerged files cannot be committed.\n"
     +-"\n"
     +-"File %s has merge conflicts.  You must resolve them and stage the file "
     +-"before committing.\n"
     ++msgid "System (%s)"
     ++msgstr ""
     ++
     ++#: lib/encoding.tcl:459 lib/encoding.tcl:465
     ++msgid "Other"
     + msgstr ""
     + 
     +-#: lib/commit.tcl:180
     ++#: lib/tools.tcl:76
     + #, tcl-format
     +-msgid ""
     +-"Unknown file state %s detected.\n"
     +-"\n"
     +-"File %s cannot be committed by this program.\n"
     ++msgid "Running %s requires a selected file."
     + msgstr ""
     + 
     +-#: lib/commit.tcl:188
     +-msgid ""
     +-"No changes to commit.\n"
     +-"\n"
     +-"You must stage at least 1 file before you can commit.\n"
     ++#: lib/tools.tcl:92
     ++#, tcl-format
     ++msgid "Are you sure you want to run %1$s on file \"%2$s\"?"
     ++msgstr ""
     ++
     ++#: lib/tools.tcl:96
     ++#, tcl-format
     ++msgid "Are you sure you want to run %s?"
     ++msgstr ""
     ++
     ++#: lib/tools.tcl:118
     ++#, tcl-format
     ++msgid "Tool: %s"
     + msgstr ""
     + 
     +-#: lib/commit.tcl:203
     ++#: lib/tools.tcl:119
     ++#, tcl-format
     ++msgid "Running: %s"
     ++msgstr ""
     ++
     ++#: lib/tools.tcl:158
     ++#, tcl-format
     ++msgid "Tool completed successfully: %s"
     ++msgstr ""
     ++
     ++#: lib/tools.tcl:160
     ++#, tcl-format
     ++msgid "Tool failed: %s"
     ++msgstr ""
     ++
     ++#: lib/mergetool.tcl:8
     ++msgid "Force resolution to the base version?"
     ++msgstr ""
     ++
     ++#: lib/mergetool.tcl:9
     ++msgid "Force resolution to this branch?"
     ++msgstr ""
     ++
     ++#: lib/mergetool.tcl:10
     ++msgid "Force resolution to the other branch?"
     ++msgstr ""
     ++
     ++#: lib/mergetool.tcl:14
     ++#, tcl-format
     + msgid ""
     +-"Please supply a commit message.\n"
     ++"Note that the diff shows only conflicting changes.\n"
     + "\n"
     +-"A good commit message has the following format:\n"
     ++"%s will be overwritten.\n"
     + "\n"
     +-"- First line: Describe in one sentence what you did.\n"
     +-"- Second line: Blank\n"
     +-"- Remaining lines: Describe why this change is good.\n"
     ++"This operation can be undone only by restarting the merge."
     + msgstr ""
     + 
     +-#: lib/commit.tcl:234
     +-msgid "Calling pre-commit hook..."
     ++#: lib/mergetool.tcl:45
     ++#, tcl-format
     ++msgid "File %s seems to have unresolved conflicts, still stage?"
     + msgstr ""
     + 
     +-#: lib/commit.tcl:249
     +-msgid "Commit declined by pre-commit hook."
     ++#: lib/mergetool.tcl:60
     ++#, tcl-format
     ++msgid "Adding resolution for %s"
     + msgstr ""
     + 
     +-#: lib/commit.tcl:272
     +-msgid "Calling commit-msg hook..."
     ++#: lib/mergetool.tcl:141
     ++msgid "Cannot resolve deletion or link conflicts using a tool"
     + msgstr ""
     + 
     +-#: lib/commit.tcl:287
     +-msgid "Commit declined by commit-msg hook."
     ++#: lib/mergetool.tcl:146
     ++msgid "Conflict file does not exist"
     + msgstr ""
     + 
     +-#: lib/commit.tcl:300
     +-msgid "Committing changes..."
     ++#: lib/mergetool.tcl:246
     ++#, tcl-format
     ++msgid "Not a GUI merge tool: '%s'"
     + msgstr ""
     + 
     +-#: lib/commit.tcl:316
     +-msgid "write-tree failed:"
     ++#: lib/mergetool.tcl:275
     ++#, tcl-format
     ++msgid "Unsupported merge tool '%s'"
     + msgstr ""
     + 
     +-#: lib/commit.tcl:317 lib/commit.tcl:361 lib/commit.tcl:382
     +-msgid "Commit failed."
     ++#: lib/mergetool.tcl:310
     ++msgid "Merge tool is already running, terminate it?"
     + msgstr ""
     + 
     +-#: lib/commit.tcl:334
     ++#: lib/mergetool.tcl:330
     + #, tcl-format
     +-msgid "Commit %s appears to be corrupt"
     ++msgid ""
     ++"Error retrieving versions:\n"
     ++"%s"
     + msgstr ""
     + 
     +-#: lib/commit.tcl:339
     ++#: lib/mergetool.tcl:350
     ++#, tcl-format
     + msgid ""
     +-"No changes to commit.\n"
     +-"\n"
     +-"No files were modified by this commit and it was not a merge commit.\n"
     ++"Could not start the merge tool:\n"
     + "\n"
     +-"A rescan will be automatically started now.\n"
     +-msgstr ""
     +-
     +-#: lib/commit.tcl:346
     +-msgid "No changes to commit."
     ++"%s"
     + msgstr ""
     + 
     +-#: lib/commit.tcl:360
     +-msgid "commit-tree failed:"
     ++#: lib/mergetool.tcl:354
     ++msgid "Running merge tool..."
     + msgstr ""
     + 
     +-#: lib/commit.tcl:381
     +-msgid "update-ref failed:"
     ++#: lib/mergetool.tcl:382 lib/mergetool.tcl:390
     ++msgid "Merge tool failed."
     + msgstr ""
     + 
     +-#: lib/commit.tcl:469
     ++#: lib/tools_dlg.tcl:22
     + #, tcl-format
     +-msgid "Created commit %s: %s"
     ++msgid "%s (%s): Add Tool"
     + msgstr ""
     + 
     +-#: lib/console.tcl:59
     +-msgid "Working... please wait..."
     ++#: lib/tools_dlg.tcl:28
     ++msgid "Add New Tool Command"
     + msgstr ""
     + 
     +-#: lib/console.tcl:186
     +-msgid "Success"
     ++#: lib/tools_dlg.tcl:34
     ++msgid "Add globally"
     + msgstr ""
     + 
     +-#: lib/console.tcl:200
     +-msgid "Error: Command Failed"
     ++#: lib/tools_dlg.tcl:46
     ++msgid "Tool Details"
     + msgstr ""
     + 
     +-#: lib/database.tcl:43
     +-msgid "Number of loose objects"
     ++#: lib/tools_dlg.tcl:49
     ++msgid "Use '/' separators to create a submenu tree:"
     + msgstr ""
     + 
     +-#: lib/database.tcl:44
     +-msgid "Disk space used by loose objects"
     ++#: lib/tools_dlg.tcl:60
     ++msgid "Command:"
     + msgstr ""
     + 
     +-#: lib/database.tcl:45
     +-msgid "Number of packed objects"
     ++#: lib/tools_dlg.tcl:71
     ++msgid "Show a dialog before running"
     + msgstr ""
     + 
     +-#: lib/database.tcl:46
     +-msgid "Number of packs"
     ++#: lib/tools_dlg.tcl:77
     ++msgid "Ask the user to select a revision (sets $REVISION)"
     + msgstr ""
     + 
     +-#: lib/database.tcl:47
     +-msgid "Disk space used by packed objects"
     ++#: lib/tools_dlg.tcl:82
     ++msgid "Ask the user for additional arguments (sets $ARGS)"
     + msgstr ""
     + 
     +-#: lib/database.tcl:48
     +-msgid "Packed objects waiting for pruning"
     ++#: lib/tools_dlg.tcl:89
     ++msgid "Don't show the command output window"
     + msgstr ""
     + 
     +-#: lib/database.tcl:49
     +-msgid "Garbage files"
     ++#: lib/tools_dlg.tcl:94
     ++msgid "Run only if a diff is selected ($FILENAME not empty)"
     + msgstr ""
     + 
     +-#: lib/database.tcl:72
     +-msgid "Compressing the object database"
     ++#: lib/tools_dlg.tcl:118
     ++msgid "Please supply a name for the tool."
     + msgstr ""
     + 
     +-#: lib/database.tcl:83
     +-msgid "Verifying the object database with fsck-objects"
     ++#: lib/tools_dlg.tcl:126
     ++#, tcl-format
     ++msgid "Tool '%s' already exists."
     + msgstr ""
     + 
     +-#: lib/database.tcl:107
     ++#: lib/tools_dlg.tcl:148
     + #, tcl-format
     + msgid ""
     +-"This repository currently has approximately %i loose objects.\n"
     +-"\n"
     +-"To maintain optimal performance it is strongly recommended that you compress "
     +-"the database.\n"
     +-"\n"
     +-"Compress the database now?"
     ++"Could not add tool:\n"
     ++"%s"
     + msgstr ""
     + 
     +-#: lib/date.tcl:25
     ++#: lib/tools_dlg.tcl:187
     + #, tcl-format
     +-msgid "Invalid date from Git: %s"
     ++msgid "%s (%s): Remove Tool"
     ++msgstr ""
     ++
     ++#: lib/tools_dlg.tcl:193
     ++msgid "Remove Tool Commands"
     ++msgstr ""
     ++
     ++#: lib/tools_dlg.tcl:198
     ++msgid "Remove"
     + msgstr ""
     + 
     +-#: lib/diff.tcl:64
     ++#: lib/tools_dlg.tcl:231
     ++msgid "(Blue denotes repository-local tools)"
     ++msgstr ""
     ++
     ++#: lib/tools_dlg.tcl:283
     + #, tcl-format
     +-msgid ""
     +-"No differences detected.\n"
     +-"\n"
     +-"%s has no changes.\n"
     +-"\n"
     +-"The modification date of this file was updated by another application, but "
     +-"the content within the file was not changed.\n"
     +-"\n"
     +-"A rescan will be automatically started to find other files which may have "
     +-"the same state."
     ++msgid "%s (%s):"
     + msgstr ""
     + 
     +-#: lib/diff.tcl:104
     ++#: lib/tools_dlg.tcl:292
     + #, tcl-format
     +-msgid "Loading diff of %s..."
     ++msgid "Run Command: %s"
     + msgstr ""
     + 
     +-#: lib/diff.tcl:125
     +-msgid ""
     +-"LOCAL: deleted\n"
     +-"REMOTE:\n"
     ++#: lib/tools_dlg.tcl:306
     ++msgid "Arguments"
     + msgstr ""
     + 
     +-#: lib/diff.tcl:130
     +-msgid ""
     +-"REMOTE: deleted\n"
     +-"LOCAL:\n"
     ++#: lib/tools_dlg.tcl:341
     ++msgid "OK"
     + msgstr ""
     + 
     +-#: lib/diff.tcl:137
     +-msgid "LOCAL:\n"
     ++#: lib/search.tcl:48
     ++msgid "Find:"
     + msgstr ""
     + 
     +-#: lib/diff.tcl:140
     +-msgid "REMOTE:\n"
     ++#: lib/search.tcl:50
     ++msgid "Next"
     + msgstr ""
     + 
     +-#: lib/diff.tcl:202 lib/diff.tcl:319
     +-#, tcl-format
     +-msgid "Unable to display %s"
     ++#: lib/search.tcl:51
     ++msgid "Prev"
     + msgstr ""
     + 
     +-#: lib/diff.tcl:203
     +-msgid "Error loading file:"
     ++#: lib/search.tcl:52
     ++msgid "RegExp"
     + msgstr ""
     + 
     +-#: lib/diff.tcl:210
     +-msgid "Git Repository (subproject)"
     ++#: lib/search.tcl:54
     ++msgid "Case"
     + msgstr ""
     + 
     +-#: lib/diff.tcl:222
     +-msgid "* Binary file (not showing content)."
     ++#: lib/shortcut.tcl:8 lib/shortcut.tcl:43 lib/shortcut.tcl:75
     ++#, tcl-format
     ++msgid "%s (%s): Create Desktop Icon"
     + msgstr ""
     + 
     +-#: lib/diff.tcl:227
     ++#: lib/shortcut.tcl:24 lib/shortcut.tcl:65
     ++msgid "Cannot write shortcut:"
     ++msgstr ""
     ++
     ++#: lib/shortcut.tcl:140
     ++msgid "Cannot write icon:"
     ++msgstr ""
     ++
     ++#: lib/remote_branch_delete.tcl:29
     + #, tcl-format
     +-msgid ""
     +-"* Untracked file is %d bytes.\n"
     +-"* Showing only first %d bytes.\n"
     ++msgid "%s (%s): Delete Branch Remotely"
     ++msgstr ""
     ++
     ++#: lib/remote_branch_delete.tcl:34
     ++msgid "Delete Branch Remotely"
     ++msgstr ""
     ++
     ++#: lib/remote_branch_delete.tcl:48
     ++msgid "From Repository"
     + msgstr ""
     + 
     +-#: lib/diff.tcl:233
     ++#: lib/remote_branch_delete.tcl:88
     ++msgid "Branches"
     ++msgstr ""
     ++
     ++#: lib/remote_branch_delete.tcl:110
     ++msgid "Delete Only If"
     ++msgstr ""
     ++
     ++#: lib/remote_branch_delete.tcl:112
     ++msgid "Merged Into:"
     ++msgstr ""
     ++
     ++#: lib/remote_branch_delete.tcl:120 lib/branch_delete.tcl:53
     ++msgid "Always (Do not perform merge checks)"
     ++msgstr ""
     ++
     ++#: lib/remote_branch_delete.tcl:153
     ++msgid "A branch is required for 'Merged Into'."
     ++msgstr ""
     ++
     ++#: lib/remote_branch_delete.tcl:185
     + #, tcl-format
     + msgid ""
     ++"The following branches are not completely merged into %s:\n"
     + "\n"
     +-"* Untracked file clipped here by %s.\n"
     +-"* To see the entire file, use an external editor.\n"
     ++" - %s"
     + msgstr ""
     + 
     +-#: lib/diff.tcl:482
     +-msgid "Failed to unstage selected hunk."
     ++#: lib/remote_branch_delete.tcl:190
     ++#, tcl-format
     ++msgid ""
     ++"One or more of the merge tests failed because you have not fetched the "
     ++"necessary commits.  Try fetching from %s first."
     + msgstr ""
     + 
     +-#: lib/diff.tcl:489
     +-msgid "Failed to stage selected hunk."
     ++#: lib/remote_branch_delete.tcl:208
     ++msgid "Please select one or more branches to delete."
     + msgstr ""
     + 
     +-#: lib/diff.tcl:568
     +-msgid "Failed to unstage selected line."
     ++#: lib/remote_branch_delete.tcl:218 lib/branch_delete.tcl:115
     ++msgid ""
     ++"Recovering deleted branches is difficult.\n"
     ++"\n"
     ++"Delete the selected branches?"
     + msgstr ""
     + 
     +-#: lib/diff.tcl:576
     +-msgid "Failed to stage selected line."
     ++#: lib/remote_branch_delete.tcl:227
     ++#, tcl-format
     ++msgid "Deleting branches from %s"
     + msgstr ""
     + 
     +-#: lib/encoding.tcl:443
     +-msgid "Default"
     ++#: lib/remote_branch_delete.tcl:300
     ++msgid "No repository selected."
     + msgstr ""
     + 
     +-#: lib/encoding.tcl:448
     ++#: lib/remote_branch_delete.tcl:305
     + #, tcl-format
     +-msgid "System (%s)"
     ++msgid "Scanning %s..."
     + msgstr ""
     + 
     +-#: lib/encoding.tcl:459 lib/encoding.tcl:465
     +-msgid "Other"
     ++#: lib/choose_repository.tcl:45
     ++msgid "Git Gui"
     + msgstr ""
     + 
     +-#: lib/error.tcl:20 lib/error.tcl:114
     +-msgid "error"
     ++#: lib/choose_repository.tcl:104 lib/choose_repository.tcl:427
     ++msgid "Create New Repository"
     + msgstr ""
     + 
     +-#: lib/error.tcl:36
     +-msgid "warning"
     ++#: lib/choose_repository.tcl:110
     ++msgid "New..."
     + msgstr ""
     + 
     +-#: lib/error.tcl:94
     +-msgid "You must correct the above errors before committing."
     ++#: lib/choose_repository.tcl:117 lib/choose_repository.tcl:511
     ++msgid "Clone Existing Repository"
     + msgstr ""
     + 
     +-#: lib/index.tcl:6
     +-msgid "Unable to unlock the index."
     ++#: lib/choose_repository.tcl:128
     ++msgid "Clone..."
     + msgstr ""
     + 
     +-#: lib/index.tcl:15
     +-msgid "Index Error"
     ++#: lib/choose_repository.tcl:135 lib/choose_repository.tcl:1105
     ++msgid "Open Existing Repository"
     + msgstr ""
     + 
     +-#: lib/index.tcl:17
     +-msgid ""
     +-"Updating the Git index failed.  A rescan will be automatically started to "
     +-"resynchronize git-gui."
     ++#: lib/choose_repository.tcl:141
     ++msgid "Open..."
     + msgstr ""
     + 
     +-#: lib/index.tcl:28
     +-msgid "Continue"
     ++#: lib/choose_repository.tcl:154
     ++msgid "Recent Repositories"
     + msgstr ""
     + 
     +-#: lib/index.tcl:31
     +-msgid "Unlock Index"
     ++#: lib/choose_repository.tcl:164
     ++msgid "Open Recent Repository:"
     + msgstr ""
     + 
     +-#: lib/index.tcl:289
     ++#: lib/choose_repository.tcl:331 lib/choose_repository.tcl:338
     ++#: lib/choose_repository.tcl:345
     + #, tcl-format
     +-msgid "Unstaging %s from commit"
     ++msgid "Failed to create repository %s:"
     + msgstr ""
     + 
     +-#: lib/index.tcl:328
     +-msgid "Ready to commit."
     ++#: lib/choose_repository.tcl:422 lib/branch_create.tcl:33
     ++msgid "Create"
     + msgstr ""
     + 
     +-#: lib/index.tcl:341
     +-#, tcl-format
     +-msgid "Adding %s"
     ++#: lib/choose_repository.tcl:432
     ++msgid "Directory:"
     ++msgstr ""
     ++
     ++#: lib/choose_repository.tcl:462 lib/choose_repository.tcl:588
     ++#: lib/choose_repository.tcl:1139
     ++msgid "Git Repository"
     + msgstr ""
     + 
     +-#: lib/index.tcl:398
     ++#: lib/choose_repository.tcl:487
     + #, tcl-format
     +-msgid "Revert changes in file %s?"
     ++msgid "Directory %s already exists."
     + msgstr ""
     + 
     +-#: lib/index.tcl:400
     ++#: lib/choose_repository.tcl:491
     + #, tcl-format
     +-msgid "Revert changes in these %i files?"
     ++msgid "File %s already exists."
     + msgstr ""
     + 
     +-#: lib/index.tcl:408
     +-msgid "Any unstaged changes will be permanently lost by the revert."
     ++#: lib/choose_repository.tcl:506
     ++msgid "Clone"
     + msgstr ""
     + 
     +-#: lib/index.tcl:411
     +-msgid "Do Nothing"
     ++#: lib/choose_repository.tcl:519
     ++msgid "Source Location:"
     + msgstr ""
     + 
     +-#: lib/index.tcl:429
     +-msgid "Reverting selected files"
     ++#: lib/choose_repository.tcl:528
     ++msgid "Target Directory:"
     ++msgstr ""
     ++
     ++#: lib/choose_repository.tcl:538
     ++msgid "Clone Type:"
     ++msgstr ""
     ++
     ++#: lib/choose_repository.tcl:543
     ++msgid "Standard (Fast, Semi-Redundant, Hardlinks)"
     ++msgstr ""
     ++
     ++#: lib/choose_repository.tcl:548
     ++msgid "Full Copy (Slower, Redundant Backup)"
     ++msgstr ""
     ++
     ++#: lib/choose_repository.tcl:553
     ++msgid "Shared (Fastest, Not Recommended, No Backup)"
     ++msgstr ""
     ++
     ++#: lib/choose_repository.tcl:560
     ++msgid "Recursively clone submodules too"
     + msgstr ""
     + 
     +-#: lib/index.tcl:433
     ++#: lib/choose_repository.tcl:594 lib/choose_repository.tcl:641
     ++#: lib/choose_repository.tcl:790 lib/choose_repository.tcl:864
     ++#: lib/choose_repository.tcl:1145 lib/choose_repository.tcl:1153
     + #, tcl-format
     +-msgid "Reverting %s"
     ++msgid "Not a Git repository: %s"
     + msgstr ""
     + 
     +-#: lib/merge.tcl:13
     +-msgid ""
     +-"Cannot merge while amending.\n"
     +-"\n"
     +-"You must finish amending this commit before starting any type of merge.\n"
     ++#: lib/choose_repository.tcl:630
     ++msgid "Standard only available for local repository."
     + msgstr ""
     + 
     +-#: lib/merge.tcl:27
     +-msgid ""
     +-"Last scanned state does not match repository state.\n"
     +-"\n"
     +-"Another Git program has modified this repository since the last scan.  A "
     +-"rescan must be performed before a merge can be performed.\n"
     +-"\n"
     +-"The rescan will be automatically started now.\n"
     ++#: lib/choose_repository.tcl:634
     ++msgid "Shared only available for local repository."
     + msgstr ""
     + 
     +-#: lib/merge.tcl:45
     ++#: lib/choose_repository.tcl:655
     + #, tcl-format
     +-msgid ""
     +-"You are in the middle of a conflicted merge.\n"
     +-"\n"
     +-"File %s has merge conflicts.\n"
     +-"\n"
     +-"You must resolve them, stage the file, and commit to complete the current "
     +-"merge.  Only then can you begin another merge.\n"
     ++msgid "Location %s already exists."
     + msgstr ""
     + 
     +-#: lib/merge.tcl:55
     ++#: lib/choose_repository.tcl:666
     ++msgid "Failed to configure origin"
     ++msgstr ""
     ++
     ++#: lib/choose_repository.tcl:678
     ++msgid "Counting objects"
     ++msgstr ""
     ++
     ++#: lib/choose_repository.tcl:679
     ++msgid "buckets"
     ++msgstr ""
     ++
     ++#: lib/choose_repository.tcl:703
     + #, tcl-format
     +-msgid ""
     +-"You are in the middle of a change.\n"
     +-"\n"
     +-"File %s is modified.\n"
     +-"\n"
     +-"You should complete the current commit before starting a merge.  Doing so "
     +-"will help you abort a failed merge, should the need arise.\n"
     ++msgid "Unable to copy objects/info/alternates: %s"
     + msgstr ""
     + 
     +-#: lib/merge.tcl:107
     ++#: lib/choose_repository.tcl:740
     + #, tcl-format
     +-msgid "%s of %s"
     ++msgid "Nothing to clone from %s."
     ++msgstr ""
     ++
     ++#: lib/choose_repository.tcl:742 lib/choose_repository.tcl:962
     ++#: lib/choose_repository.tcl:974
     ++msgid "The 'master' branch has not been initialized."
     + msgstr ""
     + 
     +-#: lib/merge.tcl:120
     ++#: lib/choose_repository.tcl:755
     ++msgid "Hardlinks are unavailable.  Falling back to copying."
     ++msgstr ""
     ++
     ++#: lib/choose_repository.tcl:769
     + #, tcl-format
     +-msgid "Merging %s and %s..."
     ++msgid "Cloning from %s"
     + msgstr ""
     + 
     +-#: lib/merge.tcl:131
     +-msgid "Merge completed successfully."
     ++#: lib/choose_repository.tcl:800
     ++msgid "Copying objects"
     + msgstr ""
     + 
     +-#: lib/merge.tcl:133
     +-msgid "Merge failed.  Conflict resolution is required."
     ++#: lib/choose_repository.tcl:801
     ++msgid "KiB"
     + msgstr ""
     + 
     +-#: lib/merge.tcl:158
     ++#: lib/choose_repository.tcl:825
     + #, tcl-format
     +-msgid "Merge Into %s"
     ++msgid "Unable to copy object: %s"
     + msgstr ""
     + 
     +-#: lib/merge.tcl:177
     +-msgid "Revision To Merge"
     ++#: lib/choose_repository.tcl:837
     ++msgid "Linking objects"
     + msgstr ""
     + 
     +-#: lib/merge.tcl:212
     +-msgid ""
     +-"Cannot abort while amending.\n"
     +-"\n"
     +-"You must finish amending this commit.\n"
     ++#: lib/choose_repository.tcl:838
     ++msgid "objects"
     + msgstr ""
     + 
     +-#: lib/merge.tcl:222
     +-msgid ""
     +-"Abort merge?\n"
     +-"\n"
     +-"Aborting the current merge will cause *ALL* uncommitted changes to be lost.\n"
     +-"\n"
     +-"Continue with aborting the current merge?"
     ++#: lib/choose_repository.tcl:846
     ++#, tcl-format
     ++msgid "Unable to hardlink object: %s"
     + msgstr ""
     + 
     +-#: lib/merge.tcl:228
     +-msgid ""
     +-"Reset changes?\n"
     +-"\n"
     +-"Resetting the changes will cause *ALL* uncommitted changes to be lost.\n"
     +-"\n"
     +-"Continue with resetting the current changes?"
     ++#: lib/choose_repository.tcl:903
     ++msgid "Cannot fetch branches and objects.  See console output for details."
     + msgstr ""
     + 
     +-#: lib/merge.tcl:239
     +-msgid "Aborting"
     ++#: lib/choose_repository.tcl:914
     ++msgid "Cannot fetch tags.  See console output for details."
     + msgstr ""
     + 
     +-#: lib/merge.tcl:239
     +-msgid "files reset"
     ++#: lib/choose_repository.tcl:938
     ++msgid "Cannot determine HEAD.  See console output for details."
     + msgstr ""
     + 
     +-#: lib/merge.tcl:267
     +-msgid "Abort failed."
     ++#: lib/choose_repository.tcl:947
     ++#, tcl-format
     ++msgid "Unable to cleanup %s"
     + msgstr ""
     + 
     +-#: lib/merge.tcl:269
     +-msgid "Abort completed.  Ready."
     ++#: lib/choose_repository.tcl:953
     ++msgid "Clone failed."
     + msgstr ""
     + 
     +-#: lib/mergetool.tcl:8
     +-msgid "Force resolution to the base version?"
     ++#: lib/choose_repository.tcl:960
     ++msgid "No default branch obtained."
     + msgstr ""
     + 
     +-#: lib/mergetool.tcl:9
     +-msgid "Force resolution to this branch?"
     ++#: lib/choose_repository.tcl:971
     ++#, tcl-format
     ++msgid "Cannot resolve %s as a commit."
     ++msgstr ""
     ++
     ++#: lib/choose_repository.tcl:998
     ++msgid "Creating working directory"
     ++msgstr ""
     ++
     ++#: lib/choose_repository.tcl:1028
     ++msgid "Initial file checkout failed."
     ++msgstr ""
     ++
     ++#: lib/choose_repository.tcl:1072
     ++msgid "Cloning submodules"
     ++msgstr ""
     ++
     ++#: lib/choose_repository.tcl:1087
     ++msgid "Cannot clone submodules."
     ++msgstr ""
     ++
     ++#: lib/choose_repository.tcl:1110
     ++msgid "Repository:"
     ++msgstr ""
     ++
     ++#: lib/choose_repository.tcl:1159
     ++#, tcl-format
     ++msgid "Failed to open repository %s:"
     + msgstr ""
     + 
     +-#: lib/mergetool.tcl:10
     +-msgid "Force resolution to the other branch?"
     ++#: lib/about.tcl:26
     ++msgid "git-gui - a graphical user interface for Git."
     + msgstr ""
     + 
     +-#: lib/mergetool.tcl:14
     ++#: lib/blame.tcl:74
     + #, tcl-format
     +-msgid ""
     +-"Note that the diff shows only conflicting changes.\n"
     +-"\n"
     +-"%s will be overwritten.\n"
     +-"\n"
     +-"This operation can be undone only by restarting the merge."
     ++msgid "%s (%s): File Viewer"
     + msgstr ""
     + 
     +-#: lib/mergetool.tcl:45
     +-#, tcl-format
     +-msgid "File %s seems to have unresolved conflicts, still stage?"
     ++#: lib/blame.tcl:80
     ++msgid "Commit:"
     + msgstr ""
     + 
     +-#: lib/mergetool.tcl:60
     +-#, tcl-format
     +-msgid "Adding resolution for %s"
     ++#: lib/blame.tcl:282
     ++msgid "Copy Commit"
     + msgstr ""
     + 
     +-#: lib/mergetool.tcl:141
     +-msgid "Cannot resolve deletion or link conflicts using a tool"
     ++#: lib/blame.tcl:286
     ++msgid "Find Text..."
     + msgstr ""
     + 
     +-#: lib/mergetool.tcl:146
     +-msgid "Conflict file does not exist"
     ++#: lib/blame.tcl:290
     ++msgid "Goto Line..."
     + msgstr ""
     + 
     +-#: lib/mergetool.tcl:264
     +-#, tcl-format
     +-msgid "Not a GUI merge tool: '%s'"
     ++#: lib/blame.tcl:299
     ++msgid "Do Full Copy Detection"
     + msgstr ""
     + 
     +-#: lib/mergetool.tcl:268
     +-#, tcl-format
     +-msgid "Unsupported merge tool '%s'"
     ++#: lib/blame.tcl:303
     ++msgid "Show History Context"
     + msgstr ""
     + 
     +-#: lib/mergetool.tcl:303
     +-msgid "Merge tool is already running, terminate it?"
     ++#: lib/blame.tcl:306
     ++msgid "Blame Parent Commit"
     + msgstr ""
     + 
     +-#: lib/mergetool.tcl:323
     ++#: lib/blame.tcl:468
     + #, tcl-format
     +-msgid ""
     +-"Error retrieving versions:\n"
     +-"%s"
     ++msgid "Reading %s..."
     + msgstr ""
     + 
     +-#: lib/mergetool.tcl:343
     +-#, tcl-format
     +-msgid ""
     +-"Could not start the merge tool:\n"
     +-"\n"
     +-"%s"
     ++#: lib/blame.tcl:596
     ++msgid "Loading copy/move tracking annotations..."
     + msgstr ""
     + 
     +-#: lib/mergetool.tcl:347
     +-msgid "Running merge tool..."
     ++#: lib/blame.tcl:613
     ++msgid "lines annotated"
     + msgstr ""
     + 
     +-#: lib/mergetool.tcl:375 lib/mergetool.tcl:383
     +-msgid "Merge tool failed."
     ++#: lib/blame.tcl:815
     ++msgid "Loading original location annotations..."
     + msgstr ""
     + 
     +-#: lib/option.tcl:11
     +-#, tcl-format
     +-msgid "Invalid global encoding '%s'"
     ++#: lib/blame.tcl:818
     ++msgid "Annotation complete."
     + msgstr ""
     + 
     +-#: lib/option.tcl:19
     +-#, tcl-format
     +-msgid "Invalid repo encoding '%s'"
     ++#: lib/blame.tcl:849
     ++msgid "Busy"
     + msgstr ""
     + 
     +-#: lib/option.tcl:117
     +-msgid "Restore Defaults"
     ++#: lib/blame.tcl:850
     ++msgid "Annotation process is already running."
     + msgstr ""
     + 
     +-#: lib/option.tcl:121
     +-msgid "Save"
     ++#: lib/blame.tcl:889
     ++msgid "Running thorough copy detection..."
     + msgstr ""
     + 
     +-#: lib/option.tcl:131
     +-#, tcl-format
     +-msgid "%s Repository"
     ++#: lib/blame.tcl:957
     ++msgid "Loading annotation..."
     + msgstr ""
     + 
     +-#: lib/option.tcl:132
     +-msgid "Global (All Repositories)"
     ++#: lib/blame.tcl:1010
     ++msgid "Author:"
     + msgstr ""
     + 
     +-#: lib/option.tcl:138
     +-msgid "User Name"
     ++#: lib/blame.tcl:1014
     ++msgid "Committer:"
     + msgstr ""
     + 
     +-#: lib/option.tcl:139
     +-msgid "Email Address"
     ++#: lib/blame.tcl:1019
     ++msgid "Original File:"
     + msgstr ""
     + 
     +-#: lib/option.tcl:141
     +-msgid "Summarize Merge Commits"
     ++#: lib/blame.tcl:1067
     ++msgid "Cannot find HEAD commit:"
     + msgstr ""
     + 
     +-#: lib/option.tcl:142
     +-msgid "Merge Verbosity"
     ++#: lib/blame.tcl:1122
     ++msgid "Cannot find parent commit:"
     + msgstr ""
     + 
     +-#: lib/option.tcl:143
     +-msgid "Show Diffstat After Merge"
     ++#: lib/blame.tcl:1137
     ++msgid "Unable to display parent"
     + msgstr ""
     + 
     +-#: lib/option.tcl:144
     +-msgid "Use Merge Tool"
     ++#: lib/blame.tcl:1138 lib/diff.tcl:345
     ++msgid "Error loading diff:"
     + msgstr ""
     + 
     +-#: lib/option.tcl:146
     +-msgid "Trust File Modification Timestamps"
     ++#: lib/blame.tcl:1279
     ++msgid "Originally By:"
     + msgstr ""
     + 
     +-#: lib/option.tcl:147
     +-msgid "Prune Tracking Branches During Fetch"
     ++#: lib/blame.tcl:1285
     ++msgid "In File:"
     + msgstr ""
     + 
     +-#: lib/option.tcl:148
     +-msgid "Match Tracking Branches"
     ++#: lib/blame.tcl:1290
     ++msgid "Copied Or Moved Here By:"
     + msgstr ""
     + 
     +-#: lib/option.tcl:149
     +-msgid "Blame Copy Only On Changed Files"
     ++#: lib/diff.tcl:77
     ++#, tcl-format
     ++msgid ""
     ++"No differences detected.\n"
     ++"\n"
     ++"%s has no changes.\n"
     ++"\n"
     ++"The modification date of this file was updated by another application, but "
     ++"the content within the file was not changed.\n"
     ++"\n"
     ++"A rescan will be automatically started to find other files which may have "
     ++"the same state."
     + msgstr ""
     + 
     +-#: lib/option.tcl:150
     +-msgid "Minimum Letters To Blame Copy On"
     ++#: lib/diff.tcl:117
     ++#, tcl-format
     ++msgid "Loading diff of %s..."
     + msgstr ""
     + 
     +-#: lib/option.tcl:151
     +-msgid "Blame History Context Radius (days)"
     ++#: lib/diff.tcl:143
     ++msgid ""
     ++"LOCAL: deleted\n"
     ++"REMOTE:\n"
     + msgstr ""
     + 
     +-#: lib/option.tcl:152
     +-msgid "Number of Diff Context Lines"
     ++#: lib/diff.tcl:148
     ++msgid ""
     ++"REMOTE: deleted\n"
     ++"LOCAL:\n"
     + msgstr ""
     + 
     +-#: lib/option.tcl:153
     +-msgid "Commit Message Text Width"
     ++#: lib/diff.tcl:155
     ++msgid "LOCAL:\n"
     + msgstr ""
     + 
     +-#: lib/option.tcl:154
     +-msgid "New Branch Name Template"
     ++#: lib/diff.tcl:158
     ++msgid "REMOTE:\n"
     + msgstr ""
     + 
     +-#: lib/option.tcl:155
     +-msgid "Default File Contents Encoding"
     ++#: lib/diff.tcl:220 lib/diff.tcl:344
     ++#, tcl-format
     ++msgid "Unable to display %s"
     + msgstr ""
     + 
     +-#: lib/option.tcl:203
     +-msgid "Change"
     ++#: lib/diff.tcl:221
     ++msgid "Error loading file:"
     + msgstr ""
     + 
     +-#: lib/option.tcl:230
     +-msgid "Spelling Dictionary:"
     ++#: lib/diff.tcl:227
     ++msgid "Git Repository (subproject)"
     + msgstr ""
     + 
     +-#: lib/option.tcl:254
     +-msgid "Change Font"
     ++#: lib/diff.tcl:239
     ++msgid "* Binary file (not showing content)."
     + msgstr ""
     + 
     +-#: lib/option.tcl:258
     ++#: lib/diff.tcl:244
     + #, tcl-format
     +-msgid "Choose %s"
     ++msgid ""
     ++"* Untracked file is %d bytes.\n"
     ++"* Showing only first %d bytes.\n"
     + msgstr ""
     + 
     +-#: lib/option.tcl:264
     +-msgid "pt."
     ++#: lib/diff.tcl:250
     ++#, tcl-format
     ++msgid ""
     ++"\n"
     ++"* Untracked file clipped here by %s.\n"
     ++"* To see the entire file, use an external editor.\n"
     + msgstr ""
     + 
     +-#: lib/option.tcl:278
     +-msgid "Preferences"
     ++#: lib/diff.tcl:583
     ++msgid "Failed to unstage selected hunk."
     + msgstr ""
     + 
     +-#: lib/option.tcl:314
     +-msgid "Failed to completely save options:"
     ++#: lib/diff.tcl:591
     ++msgid "Failed to revert selected hunk."
     + msgstr ""
     + 
     +-#: lib/remote.tcl:163
     +-msgid "Remove Remote"
     ++#: lib/diff.tcl:594
     ++msgid "Failed to stage selected hunk."
     + msgstr ""
     + 
     +-#: lib/remote.tcl:168
     +-msgid "Prune from"
     ++#: lib/diff.tcl:687
     ++msgid "Failed to unstage selected line."
     + msgstr ""
     + 
     +-#: lib/remote.tcl:173
     +-msgid "Fetch from"
     ++#: lib/diff.tcl:696
     ++msgid "Failed to revert selected line."
     + msgstr ""
     + 
     +-#: lib/remote.tcl:215
     +-msgid "Push to"
     ++#: lib/diff.tcl:700
     ++msgid "Failed to stage selected line."
     + msgstr ""
     + 
     +-#: lib/remote_add.tcl:19
     +-msgid "Add Remote"
     ++#: lib/diff.tcl:889
     ++msgid "Failed to undo last revert."
     + msgstr ""
     + 
     +-#: lib/remote_add.tcl:24
     +-msgid "Add New Remote"
     ++#: lib/sshkey.tcl:34
     ++msgid "No keys found."
     + msgstr ""
     + 
     +-#: lib/remote_add.tcl:28 lib/tools_dlg.tcl:36
     +-msgid "Add"
     ++#: lib/sshkey.tcl:37
     ++#, tcl-format
     ++msgid "Found a public key in: %s"
     + msgstr ""
     + 
     +-#: lib/remote_add.tcl:37
     +-msgid "Remote Details"
     ++#: lib/sshkey.tcl:43
     ++msgid "Generate Key"
     + msgstr ""
     + 
     +-#: lib/remote_add.tcl:50
     +-msgid "Location:"
     ++#: lib/sshkey.tcl:61
     ++msgid "Copy To Clipboard"
     + msgstr ""
     + 
     +-#: lib/remote_add.tcl:62
     +-msgid "Further Action"
     ++#: lib/sshkey.tcl:75
     ++msgid "Your OpenSSH Public Key"
     + msgstr ""
     + 
     +-#: lib/remote_add.tcl:65
     +-msgid "Fetch Immediately"
     ++#: lib/sshkey.tcl:83
     ++msgid "Generating..."
     + msgstr ""
     + 
     +-#: lib/remote_add.tcl:71
     +-msgid "Initialize Remote Repository and Push"
     ++#: lib/sshkey.tcl:89
     ++#, tcl-format
     ++msgid ""
     ++"Could not start ssh-keygen:\n"
     ++"\n"
     ++"%s"
     + msgstr ""
     + 
     +-#: lib/remote_add.tcl:77
     +-msgid "Do Nothing Else Now"
     ++#: lib/sshkey.tcl:116
     ++msgid "Generation failed."
     + msgstr ""
     + 
     +-#: lib/remote_add.tcl:101
     +-msgid "Please supply a remote name."
     ++#: lib/sshkey.tcl:123
     ++msgid "Generation succeeded, but no keys found."
     + msgstr ""
     + 
     +-#: lib/remote_add.tcl:114
     ++#: lib/sshkey.tcl:126
     + #, tcl-format
     +-msgid "'%s' is not an acceptable remote name."
     ++msgid "Your key is in: %s"
     + msgstr ""
     + 
     +-#: lib/remote_add.tcl:125
     ++#: lib/branch_create.tcl:23
     + #, tcl-format
     +-msgid "Failed to add remote '%s' of location '%s'."
     ++msgid "%s (%s): Create Branch"
     + msgstr ""
     + 
     +-#: lib/remote_add.tcl:133 lib/transport.tcl:6
     +-#, tcl-format
     +-msgid "fetch %s"
     ++#: lib/branch_create.tcl:28
     ++msgid "Create New Branch"
     + msgstr ""
     + 
     +-#: lib/remote_add.tcl:134
     +-#, tcl-format
     +-msgid "Fetching the %s"
     ++#: lib/branch_create.tcl:42
     ++msgid "Branch Name"
     + msgstr ""
     + 
     +-#: lib/remote_add.tcl:157
     +-#, tcl-format
     +-msgid "Do not know how to initialize repository at location '%s'."
     ++#: lib/branch_create.tcl:57
     ++msgid "Match Tracking Branch Name"
     + msgstr ""
     + 
     +-#: lib/remote_add.tcl:163 lib/transport.tcl:25 lib/transport.tcl:63
     +-#: lib/transport.tcl:81
     +-#, tcl-format
     +-msgid "push %s"
     ++#: lib/branch_create.tcl:66
     ++msgid "Starting Revision"
     + msgstr ""
     + 
     +-#: lib/remote_add.tcl:164
     +-#, tcl-format
     +-msgid "Setting up the %s (at %s)"
     ++#: lib/branch_create.tcl:72
     ++msgid "Update Existing Branch:"
     + msgstr ""
     + 
     +-#: lib/remote_branch_delete.tcl:29 lib/remote_branch_delete.tcl:34
     +-msgid "Delete Branch Remotely"
     ++#: lib/branch_create.tcl:75
     ++msgid "No"
     + msgstr ""
     + 
     +-#: lib/remote_branch_delete.tcl:47
     +-msgid "From Repository"
     ++#: lib/branch_create.tcl:80
     ++msgid "Fast Forward Only"
     + msgstr ""
     + 
     +-#: lib/remote_branch_delete.tcl:50 lib/transport.tcl:134
     +-msgid "Remote:"
     ++#: lib/branch_create.tcl:97
     ++msgid "Checkout After Creation"
     + msgstr ""
     + 
     +-#: lib/remote_branch_delete.tcl:66 lib/transport.tcl:149
     +-msgid "Arbitrary Location:"
     ++#: lib/branch_create.tcl:132
     ++msgid "Please select a tracking branch."
     + msgstr ""
     + 
     +-#: lib/remote_branch_delete.tcl:84
     +-msgid "Branches"
     ++#: lib/branch_create.tcl:141
     ++#, tcl-format
     ++msgid "Tracking branch %s is not a branch in the remote repository."
     + msgstr ""
     + 
     +-#: lib/remote_branch_delete.tcl:109
     +-msgid "Delete Only If"
     ++#: lib/console.tcl:59
     ++msgid "Working... please wait..."
     + msgstr ""
     + 
     +-#: lib/remote_branch_delete.tcl:111
     +-msgid "Merged Into:"
     ++#: lib/console.tcl:186
     ++msgid "Success"
     + msgstr ""
     + 
     +-#: lib/remote_branch_delete.tcl:152
     +-msgid "A branch is required for 'Merged Into'."
     ++#: lib/console.tcl:200
     ++msgid "Error: Command Failed"
     + msgstr ""
     + 
     +-#: lib/remote_branch_delete.tcl:184
     +-#, tcl-format
     +-msgid ""
     +-"The following branches are not completely merged into %s:\n"
     +-"\n"
     +-" - %s"
     ++#: lib/line.tcl:17
     ++msgid "Goto Line:"
     + msgstr ""
     + 
     +-#: lib/remote_branch_delete.tcl:189
     +-#, tcl-format
     +-msgid ""
     +-"One or more of the merge tests failed because you have not fetched the "
     +-"necessary commits.  Try fetching from %s first."
     ++#: lib/line.tcl:23
     ++msgid "Go"
     + msgstr ""
     + 
     +-#: lib/remote_branch_delete.tcl:207
     +-msgid "Please select one or more branches to delete."
     ++#: lib/choose_rev.tcl:52
     ++msgid "This Detached Checkout"
     + msgstr ""
     + 
     +-#: lib/remote_branch_delete.tcl:226
     +-#, tcl-format
     +-msgid "Deleting branches from %s"
     ++#: lib/choose_rev.tcl:60
     ++msgid "Revision Expression:"
     + msgstr ""
     + 
     +-#: lib/remote_branch_delete.tcl:292
     +-msgid "No repository selected."
     ++#: lib/choose_rev.tcl:72
     ++msgid "Local Branch"
     ++msgstr ""
     ++
     ++#: lib/choose_rev.tcl:77
     ++msgid "Tracking Branch"
     ++msgstr ""
     ++
     ++#: lib/choose_rev.tcl:82 lib/choose_rev.tcl:544
     ++msgid "Tag"
     + msgstr ""
     + 
     +-#: lib/remote_branch_delete.tcl:297
     ++#: lib/choose_rev.tcl:321
     + #, tcl-format
     +-msgid "Scanning %s..."
     ++msgid "Invalid revision: %s"
     + msgstr ""
     + 
     +-#: lib/search.tcl:21
     +-msgid "Find:"
     ++#: lib/choose_rev.tcl:342
     ++msgid "No revision selected."
     + msgstr ""
     + 
     +-#: lib/search.tcl:23
     +-msgid "Next"
     ++#: lib/choose_rev.tcl:350
     ++msgid "Revision expression is empty."
     ++msgstr ""
     ++
     ++#: lib/choose_rev.tcl:537
     ++msgid "Updated"
     ++msgstr ""
     ++
     ++#: lib/choose_rev.tcl:565
     ++msgid "URL"
     + msgstr ""
     + 
     +-#: lib/search.tcl:24
     +-msgid "Prev"
     ++#: lib/commit.tcl:9
     ++msgid ""
     ++"There is nothing to amend.\n"
     ++"\n"
     ++"You are about to create the initial commit.  There is no commit before this "
     ++"to amend.\n"
     + msgstr ""
     + 
     +-#: lib/search.tcl:25
     +-msgid "Case-Sensitive"
     ++#: lib/commit.tcl:18
     ++msgid ""
     ++"Cannot amend while merging.\n"
     ++"\n"
     ++"You are currently in the middle of a merge that has not been fully "
     ++"completed.  You cannot amend the prior commit unless you first abort the "
     ++"current merge activity.\n"
     + msgstr ""
     + 
     +-#: lib/shortcut.tcl:21 lib/shortcut.tcl:62
     +-msgid "Cannot write shortcut:"
     ++#: lib/commit.tcl:56
     ++msgid "Error loading commit data for amend:"
     + msgstr ""
     + 
     +-#: lib/shortcut.tcl:137
     +-msgid "Cannot write icon:"
     ++#: lib/commit.tcl:83
     ++msgid "Unable to obtain your identity:"
     + msgstr ""
     + 
     +-#: lib/spellcheck.tcl:57
     +-msgid "Unsupported spell checker"
     ++#: lib/commit.tcl:88
     ++msgid "Invalid GIT_COMMITTER_IDENT:"
     + msgstr ""
     + 
     +-#: lib/spellcheck.tcl:65
     +-msgid "Spell checking is unavailable"
     ++#: lib/commit.tcl:138
     ++#, tcl-format
     ++msgid "warning: Tcl does not support encoding '%s'."
     + msgstr ""
     + 
     +-#: lib/spellcheck.tcl:68
     +-msgid "Invalid spell checking configuration"
     ++#: lib/commit.tcl:158
     ++msgid ""
     ++"Last scanned state does not match repository state.\n"
     ++"\n"
     ++"Another Git program has modified this repository since the last scan.  A "
     ++"rescan must be performed before another commit can be created.\n"
     ++"\n"
     ++"The rescan will be automatically started now.\n"
     + msgstr ""
     + 
     +-#: lib/spellcheck.tcl:70
     ++#: lib/commit.tcl:182
     + #, tcl-format
     +-msgid "Reverting dictionary to %s."
     ++msgid ""
     ++"Unmerged files cannot be committed.\n"
     ++"\n"
     ++"File %s has merge conflicts.  You must resolve them and stage the file "
     ++"before committing.\n"
     + msgstr ""
     + 
     +-#: lib/spellcheck.tcl:73
     +-msgid "Spell checker silently failed on startup"
     ++#: lib/commit.tcl:190
     ++#, tcl-format
     ++msgid ""
     ++"Unknown file state %s detected.\n"
     ++"\n"
     ++"File %s cannot be committed by this program.\n"
     + msgstr ""
     + 
     +-#: lib/spellcheck.tcl:80
     +-msgid "Unrecognized spell checker"
     ++#: lib/commit.tcl:198
     ++msgid ""
     ++"No changes to commit.\n"
     ++"\n"
     ++"You must stage at least 1 file before you can commit.\n"
     + msgstr ""
     + 
     +-#: lib/spellcheck.tcl:186
     +-msgid "No Suggestions"
     ++#: lib/commit.tcl:213
     ++msgid ""
     ++"Please supply a commit message.\n"
     ++"\n"
     ++"A good commit message has the following format:\n"
     ++"\n"
     ++"- First line: Describe in one sentence what you did.\n"
     ++"- Second line: Blank\n"
     ++"- Remaining lines: Describe why this change is good.\n"
     + msgstr ""
     + 
     +-#: lib/spellcheck.tcl:388
     +-msgid "Unexpected EOF from spell checker"
     ++#: lib/commit.tcl:244
     ++msgid "Calling pre-commit hook..."
     + msgstr ""
     + 
     +-#: lib/spellcheck.tcl:392
     +-msgid "Spell Checker Failed"
     ++#: lib/commit.tcl:259
     ++msgid "Commit declined by pre-commit hook."
     + msgstr ""
     + 
     +-#: lib/sshkey.tcl:31
     +-msgid "No keys found."
     ++#: lib/commit.tcl:278
     ++msgid ""
     ++"You are about to commit on a detached head. This is a potentially dangerous "
     ++"thing to do because if you switch to another branch you will lose your "
     ++"changes and it can be difficult to retrieve them later from the reflog. You "
     ++"should probably cancel this commit and create a new branch to continue.\n"
     ++" \n"
     ++" Do you really want to proceed with your Commit?"
     + msgstr ""
     + 
     +-#: lib/sshkey.tcl:34
     +-#, tcl-format
     +-msgid "Found a public key in: %s"
     ++#: lib/commit.tcl:299
     ++msgid "Calling commit-msg hook..."
     + msgstr ""
     + 
     +-#: lib/sshkey.tcl:40
     +-msgid "Generate Key"
     ++#: lib/commit.tcl:314
     ++msgid "Commit declined by commit-msg hook."
     + msgstr ""
     + 
     +-#: lib/sshkey.tcl:56
     +-msgid "Copy To Clipboard"
     ++#: lib/commit.tcl:327
     ++msgid "Committing changes..."
     + msgstr ""
     + 
     +-#: lib/sshkey.tcl:70
     +-msgid "Your OpenSSH Public Key"
     ++#: lib/commit.tcl:344
     ++msgid "write-tree failed:"
     + msgstr ""
     + 
     +-#: lib/sshkey.tcl:78
     +-msgid "Generating..."
     ++#: lib/commit.tcl:345 lib/commit.tcl:395 lib/commit.tcl:422
     ++msgid "Commit failed."
     + msgstr ""
     + 
     +-#: lib/sshkey.tcl:84
     ++#: lib/commit.tcl:362
     + #, tcl-format
     ++msgid "Commit %s appears to be corrupt"
     ++msgstr ""
     ++
     ++#: lib/commit.tcl:367
     + msgid ""
     +-"Could not start ssh-keygen:\n"
     ++"No changes to commit.\n"
     + "\n"
     +-"%s"
     ++"No files were modified by this commit and it was not a merge commit.\n"
     ++"\n"
     ++"A rescan will be automatically started now.\n"
     + msgstr ""
     + 
     +-#: lib/sshkey.tcl:111
     +-msgid "Generation failed."
     ++#: lib/commit.tcl:374
     ++msgid "No changes to commit."
     + msgstr ""
     + 
     +-#: lib/sshkey.tcl:118
     +-msgid "Generation succeeded, but no keys found."
     ++#: lib/commit.tcl:394
     ++msgid "commit-tree failed:"
     + msgstr ""
     + 
     +-#: lib/sshkey.tcl:121
     +-#, tcl-format
     +-msgid "Your key is in: %s"
     ++#: lib/commit.tcl:421
     ++msgid "update-ref failed:"
     + msgstr ""
     + 
     +-#: lib/status_bar.tcl:83
     ++#: lib/commit.tcl:514
     + #, tcl-format
     +-msgid "%s ... %*i of %*i %s (%3i%%)"
     ++msgid "Created commit %s: %s"
     + msgstr ""
     + 
     +-#: lib/tools.tcl:75
     ++#: lib/branch_delete.tcl:16
     + #, tcl-format
     +-msgid "Running %s requires a selected file."
     ++msgid "%s (%s): Delete Branch"
     + msgstr ""
     + 
     +-#: lib/tools.tcl:90
     +-#, tcl-format
     +-msgid "Are you sure you want to run %s?"
     ++#: lib/branch_delete.tcl:21
     ++msgid "Delete Local Branch"
     ++msgstr ""
     ++
     ++#: lib/branch_delete.tcl:39
     ++msgid "Local Branches"
     ++msgstr ""
     ++
     ++#: lib/branch_delete.tcl:51
     ++msgid "Delete Only If Merged Into"
     + msgstr ""
     + 
     +-#: lib/tools.tcl:110
     ++#: lib/branch_delete.tcl:103
     + #, tcl-format
     +-msgid "Tool: %s"
     ++msgid "The following branches are not completely merged into %s:"
     + msgstr ""
     + 
     +-#: lib/tools.tcl:111
     ++#: lib/branch_delete.tcl:131
     + #, tcl-format
     +-msgid "Running: %s"
     ++msgid " - %s:"
     + msgstr ""
     + 
     +-#: lib/tools.tcl:149
     ++#: lib/branch_delete.tcl:141
     + #, tcl-format
     +-msgid "Tool completed successfully: %s"
     ++msgid ""
     ++"Failed to delete branches:\n"
     ++"%s"
     + msgstr ""
     + 
     +-#: lib/tools.tcl:151
     ++#: lib/date.tcl:25
     + #, tcl-format
     +-msgid "Tool failed: %s"
     ++msgid "Invalid date from Git: %s"
     + msgstr ""
     + 
     +-#: lib/tools_dlg.tcl:22
     +-msgid "Add Tool"
     ++#: lib/database.tcl:42
     ++msgid "Number of loose objects"
     + msgstr ""
     + 
     +-#: lib/tools_dlg.tcl:28
     +-msgid "Add New Tool Command"
     ++#: lib/database.tcl:43
     ++msgid "Disk space used by loose objects"
     + msgstr ""
     + 
     +-#: lib/tools_dlg.tcl:33
     +-msgid "Add globally"
     ++#: lib/database.tcl:44
     ++msgid "Number of packed objects"
     + msgstr ""
     + 
     +-#: lib/tools_dlg.tcl:45
     +-msgid "Tool Details"
     ++#: lib/database.tcl:45
     ++msgid "Number of packs"
     + msgstr ""
     + 
     +-#: lib/tools_dlg.tcl:48
     +-msgid "Use '/' separators to create a submenu tree:"
     ++#: lib/database.tcl:46
     ++msgid "Disk space used by packed objects"
     + msgstr ""
     + 
     +-#: lib/tools_dlg.tcl:61
     +-msgid "Command:"
     ++#: lib/database.tcl:47
     ++msgid "Packed objects waiting for pruning"
     + msgstr ""
     + 
     +-#: lib/tools_dlg.tcl:74
     +-msgid "Show a dialog before running"
     ++#: lib/database.tcl:48
     ++msgid "Garbage files"
     + msgstr ""
     + 
     +-#: lib/tools_dlg.tcl:80
     +-msgid "Ask the user to select a revision (sets $REVISION)"
     ++#: lib/database.tcl:66
     ++#, tcl-format
     ++msgid "%s (%s): Database Statistics"
     + msgstr ""
     + 
     +-#: lib/tools_dlg.tcl:85
     +-msgid "Ask the user for additional arguments (sets $ARGS)"
     ++#: lib/database.tcl:72
     ++msgid "Compressing the object database"
     + msgstr ""
     + 
     +-#: lib/tools_dlg.tcl:92
     +-msgid "Don't show the command output window"
     ++#: lib/database.tcl:83
     ++msgid "Verifying the object database with fsck-objects"
     + msgstr ""
     + 
     +-#: lib/tools_dlg.tcl:97
     +-msgid "Run only if a diff is selected ($FILENAME not empty)"
     ++#: lib/database.tcl:107
     ++#, tcl-format
     ++msgid ""
     ++"This repository currently has approximately %i loose objects.\n"
     ++"\n"
     ++"To maintain optimal performance it is strongly recommended that you compress "
     ++"the database.\n"
     ++"\n"
     ++"Compress the database now?"
     + msgstr ""
     + 
     +-#: lib/tools_dlg.tcl:121
     +-msgid "Please supply a name for the tool."
     ++#: lib/error.tcl:20
     ++#, tcl-format
     ++msgid "%s: error"
     + msgstr ""
     + 
     +-#: lib/tools_dlg.tcl:129
     ++#: lib/error.tcl:36
     + #, tcl-format
     +-msgid "Tool '%s' already exists."
     ++msgid "%s: warning"
     + msgstr ""
     + 
     +-#: lib/tools_dlg.tcl:151
     ++#: lib/error.tcl:80
     + #, tcl-format
     +-msgid ""
     +-"Could not add tool:\n"
     +-"%s"
     ++msgid "%s hook failed:"
     + msgstr ""
     + 
     +-#: lib/tools_dlg.tcl:190
     +-msgid "Remove Tool"
     ++#: lib/error.tcl:96
     ++msgid "You must correct the above errors before committing."
     + msgstr ""
     + 
     +-#: lib/tools_dlg.tcl:196
     +-msgid "Remove Tool Commands"
     ++#: lib/error.tcl:116
     ++#, tcl-format
     ++msgid "%s (%s): error"
     + msgstr ""
     + 
     +-#: lib/tools_dlg.tcl:200
     +-msgid "Remove"
     ++#: lib/merge.tcl:13
     ++msgid ""
     ++"Cannot merge while amending.\n"
     ++"\n"
     ++"You must finish amending this commit before starting any type of merge.\n"
     + msgstr ""
     + 
     +-#: lib/tools_dlg.tcl:236
     +-msgid "(Blue denotes repository-local tools)"
     ++#: lib/merge.tcl:27
     ++msgid ""
     ++"Last scanned state does not match repository state.\n"
     ++"\n"
     ++"Another Git program has modified this repository since the last scan.  A "
     ++"rescan must be performed before a merge can be performed.\n"
     ++"\n"
     ++"The rescan will be automatically started now.\n"
     + msgstr ""
     + 
     +-#: lib/tools_dlg.tcl:297
     ++#: lib/merge.tcl:45
     + #, tcl-format
     +-msgid "Run Command: %s"
     ++msgid ""
     ++"You are in the middle of a conflicted merge.\n"
     ++"\n"
     ++"File %s has merge conflicts.\n"
     ++"\n"
     ++"You must resolve them, stage the file, and commit to complete the current "
     ++"merge.  Only then can you begin another merge.\n"
     + msgstr ""
     + 
     +-#: lib/tools_dlg.tcl:311
     +-msgid "Arguments"
     ++#: lib/merge.tcl:55
     ++#, tcl-format
     ++msgid ""
     ++"You are in the middle of a change.\n"
     ++"\n"
     ++"File %s is modified.\n"
     ++"\n"
     ++"You should complete the current commit before starting a merge.  Doing so "
     ++"will help you abort a failed merge, should the need arise.\n"
     + msgstr ""
     + 
     +-#: lib/tools_dlg.tcl:348
     +-msgid "OK"
     ++#: lib/merge.tcl:108
     ++#, tcl-format
     ++msgid "%s of %s"
     + msgstr ""
     + 
     +-#: lib/transport.tcl:7
     ++#: lib/merge.tcl:126
     + #, tcl-format
     +-msgid "Fetching new changes from %s"
     ++msgid "Merging %s and %s..."
     + msgstr ""
     + 
     +-#: lib/transport.tcl:18
     +-#, tcl-format
     +-msgid "remote prune %s"
     ++#: lib/merge.tcl:137
     ++msgid "Merge completed successfully."
     + msgstr ""
     + 
     +-#: lib/transport.tcl:19
     +-#, tcl-format
     +-msgid "Pruning tracking branches deleted from %s"
     ++#: lib/merge.tcl:139
     ++msgid "Merge failed.  Conflict resolution is required."
     + msgstr ""
     + 
     +-#: lib/transport.tcl:26
     ++#: lib/merge.tcl:156
     + #, tcl-format
     +-msgid "Pushing changes to %s"
     ++msgid "%s (%s): Merge"
     + msgstr ""
     + 
     +-#: lib/transport.tcl:64
     ++#: lib/merge.tcl:164
     + #, tcl-format
     +-msgid "Mirroring to %s"
     ++msgid "Merge Into %s"
     + msgstr ""
     + 
     +-#: lib/transport.tcl:82
     +-#, tcl-format
     +-msgid "Pushing %s %s to %s"
     ++#: lib/merge.tcl:183
     ++msgid "Revision To Merge"
     + msgstr ""
     + 
     +-#: lib/transport.tcl:100
     +-msgid "Push Branches"
     ++#: lib/merge.tcl:218
     ++msgid ""
     ++"Cannot abort while amending.\n"
     ++"\n"
     ++"You must finish amending this commit.\n"
     + msgstr ""
     + 
     +-#: lib/transport.tcl:114
     +-msgid "Source Branches"
     ++#: lib/merge.tcl:228
     ++msgid ""
     ++"Abort merge?\n"
     ++"\n"
     ++"Aborting the current merge will cause *ALL* uncommitted changes to be lost.\n"
     ++"\n"
     ++"Continue with aborting the current merge?"
     + msgstr ""
     + 
     +-#: lib/transport.tcl:131
     +-msgid "Destination Repository"
     ++#: lib/merge.tcl:234
     ++msgid ""
     ++"Reset changes?\n"
     ++"\n"
     ++"Resetting the changes will cause *ALL* uncommitted changes to be lost.\n"
     ++"\n"
     ++"Continue with resetting the current changes?"
     + msgstr ""
     + 
     +-#: lib/transport.tcl:169
     +-msgid "Transfer Options"
     ++#: lib/merge.tcl:246
     ++msgid "Aborting"
     + msgstr ""
     + 
     +-#: lib/transport.tcl:171
     +-msgid "Force overwrite existing branch (may discard changes)"
     ++#: lib/merge.tcl:247
     ++msgid "files reset"
     + msgstr ""
     + 
     +-#: lib/transport.tcl:175
     +-msgid "Use thin pack (for slow network connections)"
     ++#: lib/merge.tcl:277
     ++msgid "Abort failed."
     + msgstr ""
     + 
     +-#: lib/transport.tcl:179
     +-msgid "Include tags"
     ++#: lib/merge.tcl:279
     ++msgid "Abort completed.  Ready."
     + msgstr ""
 -:  ---------- > 2:  721bbc1cc8 git-gui: extend translation glossary template with more terms
 2:  8584228222 ! 3:  76fbb3d7cc git-gui: update german translation
     @@ -1,10 +1,20 @@
      Author: Christian Stimming <christian@cstimming.de>
      
     -    git-gui: update german translation
     +    git-gui: update German translation
      
     -    Switch several terms from uncommon translations back to english
     -    vocabulary, most prominently commit (noun, verb) and repository. Adapt
     -    glossary and translation accordingly.
     +    Update German translation (glossary and final translation) with
     +    recent additions, but also switch several terms from uncommon
     +    translations back to English vocabulary.
     +
     +    This most prominently concerns "commit" (noun, verb), "repository",
     +    "branch", and some more. These uncommon translations have been introduced
     +    long ago and never been changed since. In fact, the whole German
     +    translation here hasn't been touched for a long time. However, in German
     +    literature and magazines, git-gui is regularly noted for its uncommon
     +    choice of translated vocabulary. This somewhat distracts from the actual
     +    benefits of this tool. So it is probably better to abandon the uncommon
     +    translations and rather stick to the common English vocabulary in git
     +    version control.
      
          Signed-off-by: Christian Stimming <christian@cstimming.de>
      
     @@ -14,10 +24,11 @@
      @@
       "Project-Id-Version: git-gui\n"
       "Report-Msgid-Bugs-To: \n"
     - "POT-Creation-Date: 2020-01-13 21:51+0100\n"
     + "POT-Creation-Date: 2020-02-08 22:54+0100\n"
      -"PO-Revision-Date: 2010-01-26 22:25+0100\n"
     -+"PO-Revision-Date: 2020-01-13 22:37+0100\n"
     - "Last-Translator: Christian Stimming <stimming@tuhh.de>\n"
     +-"Last-Translator: Christian Stimming <stimming@tuhh.de>\n"
     ++"PO-Revision-Date: 2020-02-09 22:40+0100\n"
     ++"Last-Translator: Christian Stimming <christian@cstimming.de>\n"
       "Language-Team: German\n"
      -"Language: \n"
      +"Language: de_DE\n"
     @@ -39,18 +50,27 @@
       msgid "Calling prepare-commit-msg hook..."
      -msgstr ""
      -"Aufrufen der Eintragen-Vorbereiten-Kontrolle (»prepare-commit hook«)..."
     -+msgstr "Aufrufen des »prepare-commit hook«..."
     ++msgstr "Aufrufen des »prepare-commit-msg hook«..."
       
       #: git-gui.sh:1646
       msgid "Commit declined by prepare-commit-msg hook."
      -msgstr ""
      -"Eintragen abgelehnt durch Eintragen-Vorbereiten-Kontrolle (»prepare-commit "
      -"hook«)."
     -+msgstr "Commit abgelehnt durch »prepare-commit hook«."
     ++msgstr "Commit abgelehnt durch »prepare-commit-msg hook«."
       
       #: git-gui.sh:1804 lib/browser.tcl:252
       msgid "Ready."
      @@
     + msgid ""
     + "Display limit (gui.maxfilesdisplayed = %s) reached, not showing all %s files."
     + msgstr ""
     ++"Anzeigelimit erreicht (gui.maxfilesdisplayed = %s) für Anzahl Einträge. Es "
     ++"werden nicht alle %s Dateien gezeigt."
     + 
     + #: git-gui.sh:2091
     + msgid "Unmodified"
     +@@
       
       #: git-gui.sh:2094 git-gui.sh:2106
       msgid "Staged for commit"
     @@ -95,6 +115,11 @@
       
       #: git-gui.sh:2105
       msgid "Untracked, not staged"
     +-msgstr "Nicht unter Versionskontrolle, nicht bereitgestellt"
     ++msgstr "Unversioniert, nicht bereitgestellt"
     + 
     + #: git-gui.sh:2110
     + msgid "Missing"
      @@
       msgstr "Gitk kann im PATH nicht gefunden werden."
       
     @@ -118,6 +143,11 @@
       msgid "Edit"
      @@
       
     + #: git-gui.sh:2729 lib/choose_rev.tcl:567
     + msgid "Branch"
     +-msgstr "Zweig"
     ++msgstr "Branch"
     + 
       #: git-gui.sh:2732 lib/choose_rev.tcl:554
       msgid "Commit@@noun"
      -msgstr "Version"
     @@ -136,6 +166,11 @@
       msgid "Tools"
      @@
       
     + #: git-gui.sh:2748
     + msgid "Explore Working Copy"
     +-msgstr "Arbeitskopie im Dateimanager"
     ++msgstr "Arbeitskopie im Dateimanager öffnen"
     + 
       #: git-gui.sh:2763
       msgid "Git Bash"
      -msgstr ""
     @@ -143,6 +178,50 @@
       
       #: git-gui.sh:2772
       msgid "Browse Current Branch's Files"
     +-msgstr "Aktuellen Zweig durchblättern"
     ++msgstr "Aktuellen Branch durchblättern"
     + 
     + #: git-gui.sh:2776
     + msgid "Browse Branch Files..."
     +-msgstr "Einen Zweig durchblättern..."
     ++msgstr "Branch durchblättern..."
     + 
     + #: git-gui.sh:2781
     + msgid "Visualize Current Branch's History"
     +-msgstr "Aktuellen Zweig darstellen"
     ++msgstr "Aktuellen Branch darstellen"
     + 
     + #: git-gui.sh:2785
     + msgid "Visualize All Branch History"
     +-msgstr "Alle Zweige darstellen"
     ++msgstr "Historie aller Branches darstellen"
     + 
     + #: git-gui.sh:2792
     + #, tcl-format
     + msgid "Browse %s's Files"
     +-msgstr "Zweig »%s« durchblättern"
     ++msgstr "Branch »%s« durchblättern"
     + 
     + #: git-gui.sh:2794
     + #, tcl-format
     +@@
     + 
     + #: git-gui.sh:2871
     + msgid "Checkout..."
     +-msgstr "Umstellen..."
     ++msgstr "Auschecken..."
     + 
     + #: git-gui.sh:2877
     + msgid "Rename..."
     +@@
     + 
     + #: git-gui.sh:2887
     + msgid "Reset..."
     +-msgstr "Zurücksetzen..."
     ++msgstr "Änderungen verwerfen..."
     + 
     + #: git-gui.sh:2897
     + msgid "Done"
      @@
       
       #: git-gui.sh:2899
     @@ -162,7 +241,7 @@
       #: git-gui.sh:2924
       msgid "Stage To Commit"
      -msgstr "Zum Eintragen bereitstellen"
     -+msgstr "Zum Committen bereitstellen"
     ++msgstr "Für Commit bereitstellen"
       
       #: git-gui.sh:2930
       msgid "Stage Changed Files To Commit"
     @@ -172,12 +251,30 @@
       #: git-gui.sh:2936
       msgid "Unstage From Commit"
      -msgstr "Aus der Bereitstellung herausnehmen"
     -+msgstr "Aus der Commit-Bereitstellung herausnehmen"
     ++msgstr "Aus Commit-Bereitstellung herausnehmen"
       
       #: git-gui.sh:2942 lib/index.tcl:521
       msgid "Revert Changes"
      @@
       
     + #: git-gui.sh:2994 git-gui.sh:3022
     + msgid "Add..."
     +-msgstr "Hinzufügen..."
     ++msgstr "Neues hinzufügen..."
     + 
     + #: git-gui.sh:2998
     + msgid "Push..."
     +@@
     + 
     + #: git-gui.sh:3002
     + msgid "Delete Branch..."
     +-msgstr "Zweig löschen..."
     ++msgstr "Branch löschen..."
     + 
     + #: git-gui.sh:3012 git-gui.sh:3666
     + msgid "Options..."
     +@@
     + 
       #: git-gui.sh:3097 git-gui.sh:3229
       msgid "usage:"
      -msgstr ""
     @@ -193,6 +290,15 @@
       msgid "Error"
       msgstr "Fehler"
       
     +@@
     + 
     + #: git-gui.sh:3246
     + msgid "Current Branch:"
     +-msgstr "Aktueller Zweig:"
     ++msgstr "Aktueller Branch:"
     + 
     + #: git-gui.sh:3271
     + msgid "Unstaged Changes"
      @@
       
       #: git-gui.sh:3293
     @@ -221,24 +327,33 @@
       #: git-gui.sh:3477 git-gui.sh:3641 lib/console.tcl:73
       msgid "Copy All"
      @@
     - msgstr "Zeile anwenden/umkehren"
     + 
     + #: git-gui.sh:3673
     + msgid "Apply/Reverse Hunk"
     +-msgstr "Kontext anwenden/umkehren"
     ++msgstr "Patch-Block anwenden/zurücknehmen"
     + 
     + #: git-gui.sh:3678
     + msgid "Apply/Reverse Line"
     +-msgstr "Zeile anwenden/umkehren"
     ++msgstr "Zeile anwenden/zurücknehmen"
       
       #: git-gui.sh:3684 git-gui.sh:3794 git-gui.sh:3805
      -#, fuzzy
       msgid "Revert Hunk"
      -msgstr "Kontext anwenden/umkehren"
     -+msgstr "Kontextänderung umkehren"
     ++msgstr "Patch-Block zurücknehmen"
       
       #: git-gui.sh:3689 git-gui.sh:3801 git-gui.sh:3812
      -#, fuzzy
       msgid "Revert Line"
      -msgstr "Änderungen verwerfen"
     -+msgstr "Zeilenänderungen umkehren"
     ++msgstr "Zeilenänderungen zurücknehmen"
       
       #: git-gui.sh:3694 git-gui.sh:3791
       msgid "Undo Last Revert"
      -msgstr ""
     -+msgstr "Letzte Umkehrung rückgängig"
     ++msgstr "Letztes Zurücknehmen rückgängig"
       
       #: git-gui.sh:3713
       msgid "Run Merge Tool"
     @@ -251,27 +366,62 @@
       
       #: git-gui.sh:3744
       msgid "Visualize These Changes In The Submodule"
     -@@
     +-msgstr "Diese Änderungen im Untermodul darstellen"
     ++msgstr "Diese Änderungen im Submodul darstellen"
     + 
     + #: git-gui.sh:3748
     + msgid "Visualize Current Branch History In The Submodule"
     +-msgstr "Aktuellen Zweig im Untermodul darstellen"
     ++msgstr "Aktuellen Branch im Submodul darstellen"
     + 
     + #: git-gui.sh:3752
     + msgid "Visualize All Branch History In The Submodule"
     +-msgstr "Alle Zweige im Untermodul darstellen"
     ++msgstr "Alle Branches im Submodul darstellen"
     + 
     + #: git-gui.sh:3757
     + msgid "Start git gui In The Submodule"
     +-msgstr "Git gui im Untermodul starten"
     ++msgstr "Git gui im Submodul starten"
     + 
     + #: git-gui.sh:3793
     + msgid "Unstage Hunk From Commit"
     +-msgstr "Kontext aus Bereitstellung herausnehmen"
     ++msgstr "Patch-Block aus Bereitstellung herausnehmen"
     + 
     + #: git-gui.sh:3797
     + msgid "Unstage Lines From Commit"
       msgstr "Zeilen aus der Bereitstellung herausnehmen"
       
       #: git-gui.sh:3798 git-gui.sh:3809
      -#, fuzzy
       msgid "Revert Lines"
      -msgstr "Änderungen verwerfen"
     -+msgstr "Zeilenänderung umkehren"
     ++msgstr "Zeilenänderung zurücknehmen"
       
       #: git-gui.sh:3800
       msgid "Unstage Line From Commit"
      @@
     + 
     + #: git-gui.sh:3804
     + msgid "Stage Hunk For Commit"
     +-msgstr "Kontext zur Bereitstellung hinzufügen"
     ++msgstr "Patch-Block zur Bereitstellung hinzufügen"
     + 
     + #: git-gui.sh:3808
     + msgid "Stage Lines For Commit"
     +@@
       #: lib/transport.tcl:18
       #, tcl-format
       msgid "remote prune %s"
      -msgstr "Aufräumen von »%s«"
     -+msgstr "Extern aufräumen von »%s«"
     ++msgstr "Gelöschte externe Branches aus »%s« entfernen"
       
       #: lib/transport.tcl:19
       #, tcl-format
     -@@
     + msgid "Pruning tracking branches deleted from %s"
     +-msgstr "Übernahmezweige aufräumen und entfernen, die in »%s« gelöscht wurden"
     ++msgstr "Gelöschte externe Trackingbranches aus »%s« werden entfernt"
       
       #: lib/transport.tcl:25
       msgid "fetch all remotes"
     @@ -288,18 +438,33 @@
      -#, fuzzy
       msgid "remote prune all remotes"
      -msgstr "Aufräumen von »%s«"
     -+msgstr "Extern aufräumen aller externen Repositories"
     ++msgstr "Extern veraltete Branches entfernen aller Repositories"
       
       #: lib/transport.tcl:41
      -#, fuzzy
       msgid "Pruning tracking branches deleted from all remotes"
      -msgstr "Übernahmezweige aufräumen und entfernen, die in »%s« gelöscht wurden"
     -+msgstr "Übernahmezweige aufräumen und entfernen, die in allen externen Repositories gelöscht wurden"
     ++msgstr ""
     ++"Gelöschte externe Trackingbranches aus allen Repositories werden entfernt"
       
       #: lib/transport.tcl:54 lib/transport.tcl:92 lib/transport.tcl:110
       #: lib/remote_add.tcl:162
      @@
       
     + #: lib/transport.tcl:132
     + msgid "Push Branches"
     +-msgstr "Zweige versenden"
     ++msgstr "Branches versenden"
     + 
     + #: lib/transport.tcl:141 lib/checkout_op.tcl:580 lib/remote_add.tcl:34
     + #: lib/browser.tcl:292 lib/branch_checkout.tcl:30 lib/branch_rename.tcl:32
     +@@
     + 
     + #: lib/transport.tcl:147
     + msgid "Source Branches"
     +-msgstr "Lokale Zweige"
     ++msgstr "Lokale Branches"
     + 
       #: lib/transport.tcl:162
       msgid "Destination Repository"
      -msgstr "Ziel-Projektarchiv"
     @@ -318,14 +483,71 @@
       #: lib/transport.tcl:205
       msgid "Transfer Options"
      @@
     + #: lib/transport.tcl:207
     + msgid "Force overwrite existing branch (may discard changes)"
     + msgstr ""
     +-"Überschreiben von existierenden Zweigen erzwingen (könnte Änderungen löschen)"
     ++"Überschreiben von existierenden Branches erzwingen (könnte Änderungen "
     ++"löschen)"
     + 
     + #: lib/transport.tcl:211
     + msgid "Use thin pack (for slow network connections)"
     +@@
     + 
     + #: lib/transport.tcl:215
     + msgid "Include tags"
     +-msgstr "Mit Markierungen übertragen"
     ++msgstr "Mit Tags versenden"
     + 
       #: lib/transport.tcl:229
       #, tcl-format
       msgid "%s (%s): Push"
      -msgstr ""
     -+msgstr "%s (%s): Übertragen"
     ++msgstr "%s (%s): Versenden"
       
       #: lib/checkout_op.tcl:85
       #, tcl-format
     +@@
     + #: lib/checkout_op.tcl:133
     + #, tcl-format
     + msgid "fatal: Cannot resolve %s"
     +-msgstr "Fehler: »%s« kann nicht als Zweig oder Version erkannt werden"
     ++msgstr "Fehler: »%s« kann nicht als Branch oder Version erkannt werden"
     + 
     + #: lib/checkout_op.tcl:146 lib/sshkey.tcl:58 lib/console.tcl:81
     + #: lib/database.tcl:30
     +@@
     + #: lib/checkout_op.tcl:175
     + #, tcl-format
     + msgid "Branch '%s' does not exist."
     +-msgstr "Zweig »%s« existiert nicht."
     ++msgstr "Branch »%s« existiert nicht."
     + 
     + #: lib/checkout_op.tcl:194
     + #, tcl-format
     +@@
     + #: lib/checkout_op.tcl:202 lib/branch_rename.tcl:102
     + #, tcl-format
     + msgid "Branch '%s' already exists."
     +-msgstr "Zweig »%s« existiert bereits."
     ++msgstr "Branch »%s« existiert bereits."
     + 
     + #: lib/checkout_op.tcl:229
     + #, tcl-format
     +@@
     + "It cannot fast-forward to %s.\n"
     + "A merge is required."
     + msgstr ""
     +-"Zweig »%s« existiert bereits.\n"
     ++"Branch »%s« existiert bereits.\n"
     + "\n"
     +-"Zweig kann nicht mit »%s« schnellzusammengeführt werden. Reguläres "
     +-"Zusammenführen ist notwendig."
     ++"Branch kann nicht auf »%s« vorgespult werden. Reguläres Zusammenführen ist "
     ++"notwendig."
     + 
     + #: lib/checkout_op.tcl:243
     + #, tcl-format
      @@
       "\n"
       "The rescan will be automatically started now.\n"
     @@ -335,26 +557,91 @@
       "\n"
      -"Ein anderes Git-Programm hat das Projektarchiv seit dem letzten Laden "
      -"geändert.  Vor dem Wechseln des lokalen Zweigs muss neu geladen werden.\n"
     -+"Ein anderes Git-Programm hat das Repository seit dem letzten Laden geändert.  Vor dem Wechseln des lokalen Zweigs muss neu geladen werden.\n"
     ++"Ein anderes Git-Programm hat das Repository seit dem letzten Laden "
     ++"geändert.  Vor dem Wechseln des lokalen Branches muss neu geladen werden.\n"
       "\n"
       "Es wird gleich neu geladen.\n"
       
     + #: lib/checkout_op.tcl:345
     + #, tcl-format
     + msgid "Updating working directory to '%s'..."
     +-msgstr "Arbeitskopie umstellen auf »%s«..."
     ++msgstr "Arbeitskopie aktualisieren auf »%s«..."
     + 
     + #: lib/checkout_op.tcl:346
     + msgid "files checked out"
     +@@
     + #, tcl-format
     + msgid "Aborted checkout of '%s' (file level merging is required)."
     + msgstr ""
     +-"Auf Zweig »%s« umstellen abgebrochen (Zusammenführen der Dateien ist "
     ++"Branch »%s« Auschecken abgebrochen (Zusammenführen der Dateien ist "
     + "notwendig)."
     + 
     + #: lib/checkout_op.tcl:378
     +@@
     + #: lib/checkout_op.tcl:382
     + #, tcl-format
     + msgid "Staying on branch '%s'."
     +-msgstr "Es wird auf Zweig »%s« verblieben."
     ++msgstr "Es wird auf Branch »%s« verblieben."
     + 
     + #: lib/checkout_op.tcl:453
     + msgid ""
     +@@
     + "If you wanted to be on a branch, create one now starting from 'This Detached "
     + "Checkout'."
     + msgstr ""
     +-"Die Arbeitskopie ist nicht auf einem lokalen Zweig.\n"
     ++"Die Arbeitskopie ist nicht auf einem lokalen Branch.\n"
     + "\n"
     +-"Wenn Sie auf einem Zweig arbeiten möchten, erstellen Sie bitte jetzt einen "
     +-"Zweig mit der Auswahl »Abgetrennte Arbeitskopie-Version«."
     ++"Wenn Sie auf einem Branch arbeiten möchten, erstellen Sie bitte jetzt einen "
     ++"Branch mit der Auswahl »Losgelöste Arbeitskopie-Version«."
     + 
     + #: lib/checkout_op.tcl:504 lib/checkout_op.tcl:508
     + #, tcl-format
      @@
       #: lib/checkout_op.tcl:536
       #, tcl-format
       msgid "Resetting '%s' to '%s' will lose the following commits:"
      -msgstr "Zurücksetzen von »%s« nach »%s« wird folgende Versionen verwerfen:"
     -+msgstr "Zurücksetzen von »%s« nach »%s« wird folgenden Commit verwerfen:"
     ++msgstr "Umsetzen von »%s« nach »%s« wird folgende Commits verlieren:"
       
       #: lib/checkout_op.tcl:558
       msgid "Recovering lost commits may not be easy."
     --msgstr ""
     + msgstr ""
      -"Verworfene Versionen können nur mit größerem Aufwand wiederhergestellt "
      -"werden."
     -+msgstr "Verworfene Commits können nur mit größerem Aufwand wiederhergestellt werden."
     ++"Verlorene Commits können nur mit größerem Aufwand wiederhergestellt werden."
       
       #: lib/checkout_op.tcl:563
       #, tcl-format
     + msgid "Reset '%s'?"
     +-msgstr "»%s« zurücksetzen?"
     ++msgstr "»%s« umsetzen?"
     + 
     + #: lib/checkout_op.tcl:568 lib/tools_dlg.tcl:336 lib/merge.tcl:170
     + msgid "Visualize"
     +@@
     + 
     + #: lib/checkout_op.tcl:572 lib/branch_create.tcl:85
     + msgid "Reset"
     +-msgstr "Zurücksetzen"
     ++msgstr "Umsetzen (Reset)"
     + 
     + #: lib/checkout_op.tcl:636
     + #, tcl-format
     +@@
     + "\n"
     + "This should not have occurred.  %s will now close and give up."
     + msgstr ""
     +-"Lokaler Zweig kann nicht gesetzt werden.\n"
     ++"Lokaler Branch kann nicht gesetzt werden.\n"
     + "\n"
     + "Diese Arbeitskopie ist nur teilweise umgestellt. Die Dateien sind korrekt "
     + "aktualisiert, aber einige interne Git-Dateien konnten nicht geändert "
      @@
       "Dies ist ein interner Programmfehler von %s. Programm wird jetzt abgebrochen."
       
     @@ -383,6 +670,16 @@
       msgid "Name:"
      @@
       
     + #: lib/remote_add.tcl:60
     + msgid "Further Action"
     +-msgstr "Weitere Aktion jetzt"
     ++msgstr "Weitere Aktion"
     + 
     + #: lib/remote_add.tcl:63
     + msgid "Fetch Immediately"
     +-msgstr "Gleich anfordern"
     ++msgstr "Jetzt anfordern"
     + 
       #: lib/remote_add.tcl:69
       msgid "Initialize Remote Repository and Push"
      -msgstr "Externes Archiv initialisieren und dahin versenden"
     @@ -390,7 +687,8 @@
       
       #: lib/remote_add.tcl:75
       msgid "Do Nothing Else Now"
     -@@
     +-msgstr "Nichts tun"
     ++msgstr "Keine weitere Aktion"
       
       #: lib/remote_add.tcl:100
       msgid "Please supply a remote name."
     @@ -413,12 +711,11 @@
       #: lib/remote_add.tcl:133
       #, tcl-format
      @@
     - #: lib/remote_add.tcl:156
       #, tcl-format
       msgid "Do not know how to initialize repository at location '%s'."
     --msgstr ""
     + msgstr ""
      -"Initialisieren eines externen Archivs an Adresse »%s« ist nicht möglich."
     -+msgstr "Initialisieren eines externen Repositories an Adresse »%s« ist nicht möglich."
     ++"Initialisieren eines externen Repositories an Adresse »%s« ist nicht möglich."
       
       #: lib/remote_add.tcl:163
       #, tcl-format
     @@ -442,10 +739,15 @@
      +#, tcl-format
       msgid "%s (%s): Browse Branch Files"
      -msgstr "Dateien des Zweigs durchblättern"
     -+msgstr "%s (%s): Dateien des Zweigs durchblättern"
     ++msgstr "%s (%s): Dateien des Branches durchblättern"
       
       #: lib/browser.tcl:282
       msgid "Browse Branch Files"
     +-msgstr "Dateien des Zweigs durchblättern"
     ++msgstr "Dateien des Branches durchblättern"
     + 
     + #: lib/browser.tcl:288 lib/choose_repository.tcl:437
     + #: lib/choose_repository.tcl:524 lib/choose_repository.tcl:533
      @@
       msgstr "Dateien"
       
     @@ -453,7 +755,7 @@
      -#, fuzzy
       msgid "Unstaging selected files from commit"
      -msgstr "Datei »%s« aus der Bereitstellung herausnehmen"
     -+msgstr ""
     ++msgstr "Gewählte Dateien aus der Bereitstellung herausnehmen"
       
       #: lib/index.tcl:330
       #, tcl-format
     @@ -478,6 +780,17 @@
       
       #: lib/index.tcl:412
       #, tcl-format
     + msgid "Stage %d untracked files?"
     +-msgstr ""
     ++msgstr "%d unversionierte Dateien bereitstellen?"
     + 
     + #: lib/index.tcl:420
     + msgid "Adding all changed files"
     +-msgstr ""
     ++msgstr "Alle geänderten Dateien hinzufügen"
     + 
     + #: lib/index.tcl:503
     + #, tcl-format
      @@
       #: lib/index.tcl:508
       #, tcl-format
     @@ -488,6 +801,21 @@
       #: lib/index.tcl:517
       msgid "Any unstaged changes will be permanently lost by the revert."
      @@
     + msgstr "Nichts tun"
     + 
     + #: lib/index.tcl:545
     +-#, fuzzy, tcl-format
     ++#, tcl-format
     + msgid "Delete untracked file %s?"
     +-msgstr "Zweige auf »%s« werden gelöscht"
     ++msgstr "Unversionierte Datei »%s« löschen?"
     + 
     + #: lib/index.tcl:550
     +-#, fuzzy, tcl-format
     ++#, tcl-format
     + msgid "Delete these %i untracked files?"
     +-msgstr "Änderungen in den gewählten %i Dateien verwerfen?"
     ++msgstr "Diese %i unversionierten Dateien löschen?"
       
       #: lib/index.tcl:560
       msgid "Files will be permanently deleted."
     @@ -532,10 +860,34 @@
      +#, tcl-format
       msgid "%s (%s): Checkout Branch"
      -msgstr "Auf Zweig umstellen"
     -+msgstr "%s (%s): Auf Zweig umstellen"
     ++msgstr "%s (%s): Branch auschecken"
       
       #: lib/branch_checkout.tcl:21
       msgid "Checkout Branch"
     +-msgstr "Auf Zweig umstellen"
     ++msgstr "Branch auschecken"
     + 
     + #: lib/branch_checkout.tcl:26
     + msgid "Checkout"
     +-msgstr "Umstellen"
     ++msgstr "Auschecken"
     + 
     + #: lib/branch_checkout.tcl:39 lib/option.tcl:310 lib/branch_create.tcl:69
     + msgid "Options"
     +@@
     + 
     + #: lib/branch_checkout.tcl:42 lib/branch_create.tcl:92
     + msgid "Fetch Tracking Branch"
     +-msgstr "Übernahmezweig anfordern"
     ++msgstr "Trackingbranch anfordern"
     + 
     + #: lib/branch_checkout.tcl:47
     + msgid "Detach From Local Branch"
     +-msgstr "Verbindung zu lokalem Zweig lösen"
     ++msgstr "Verbindung zu lokalem Branch lösen"
     + 
     + #: lib/status_bar.tcl:263
     + #, tcl-format
      @@
       
       #: lib/remote.tcl:218
     @@ -545,7 +897,13 @@
       
       #: lib/remote.tcl:223
       msgid "Prune from"
     -@@
     +-msgstr "Aufräumen von"
     ++msgstr "Veraltete Branches entfernen"
     + 
     + #: lib/remote.tcl:228
     + msgid "Fetch from"
     +-msgstr "Anfordern von"
     ++msgstr "Anfordern"
       
       #: lib/remote.tcl:249 lib/remote.tcl:253 lib/remote.tcl:258 lib/remote.tcl:264
       msgid "All"
     @@ -557,10 +915,44 @@
      +#, tcl-format
       msgid "%s (%s): Rename Branch"
      -msgstr "Zweig umbenennen"
     -+msgstr "%s (%s): Zweig umbenennen"
     ++msgstr "%s (%s): Branch umbenennen"
       
       #: lib/branch_rename.tcl:23
       msgid "Rename Branch"
     +-msgstr "Zweig umbenennen"
     ++msgstr "Branch umbenennen"
     + 
     + #: lib/branch_rename.tcl:28
     + msgid "Rename"
     +@@
     + 
     + #: lib/branch_rename.tcl:38
     + msgid "Branch:"
     +-msgstr "Zweig:"
     ++msgstr "Branch:"
     + 
     + #: lib/branch_rename.tcl:46
     + msgid "New Name:"
     +@@
     + 
     + #: lib/branch_rename.tcl:81
     + msgid "Please select a branch to rename."
     +-msgstr "Bitte wählen Sie einen Zweig zum umbenennen."
     ++msgstr "Bitte wählen Sie einen Branch zum umbenennen."
     + 
     + #: lib/branch_rename.tcl:92 lib/branch_create.tcl:154
     + msgid "Please supply a branch name."
     +-msgstr "Bitte geben Sie einen Zweignamen an."
     ++msgstr "Bitte geben Sie einen Branchnamen an."
     + 
     + #: lib/branch_rename.tcl:112 lib/branch_create.tcl:165
     + #, tcl-format
     + msgid "'%s' is not an acceptable branch name."
     +-msgstr "»%s« ist kein zulässiger Zweigname."
     ++msgstr "»%s« ist kein zulässiger Branchname."
     + 
     + #: lib/branch_rename.tcl:123
     + #, tcl-format
      @@
       #: lib/option.tcl:19
       #, tcl-format
     @@ -594,6 +986,24 @@
       #: lib/option.tcl:144
       msgid "Merge Verbosity"
      @@
     + 
     + #: lib/option.tcl:149
     + msgid "Prune Tracking Branches During Fetch"
     +-msgstr "Übernahmezweige aufräumen während Anforderung"
     ++msgstr "Veraltete Trackingbranches entfernen während Anforderung"
     + 
     + #: lib/option.tcl:150
     + msgid "Match Tracking Branches"
     +-msgstr "Passend zu Übernahmezweig"
     ++msgstr "Neue Branches automatisch als Trackingbranch"
     + 
     + #: lib/option.tcl:151
     + msgid "Use Textconv For Diffs and Blames"
     +-msgstr ""
     ++msgstr "Benutze »textconv« für Vergleich und Annotieren"
     + 
     + #: lib/option.tcl:152
     + msgid "Blame Copy Only On Changed Files"
       msgstr "Kopie-Annotieren nur bei geänderten Dateien"
       
       #: lib/option.tcl:153
     @@ -606,6 +1016,20 @@
       msgid "Minimum Letters To Blame Copy On"
      @@
       
     + #: lib/option.tcl:155
     + msgid "Blame History Context Radius (days)"
     +-msgstr "Anzahl Tage für Historien-Kontext"
     ++msgstr "Anzahl Tage für Annotieren-Historien-Kontext"
     + 
     + #: lib/option.tcl:156
     + msgid "Number of Diff Context Lines"
     +@@
     + 
     + #: lib/option.tcl:157
     + msgid "Additional Diff Parameters"
     +-msgstr ""
     ++msgstr "Zusätzliche Vergleich-/diff-Parameter"
     + 
       #: lib/option.tcl:158
       msgid "Commit Message Text Width"
      -msgstr "Textbreite der Versionsbeschreibung"
     @@ -613,7 +1037,34 @@
       
       #: lib/option.tcl:159
       msgid "New Branch Name Template"
     +-msgstr "Namensvorschlag für neue Zweige"
     ++msgstr "Namensvorlage für neue Branches"
     + 
     + #: lib/option.tcl:160
     + msgid "Default File Contents Encoding"
      @@
     + 
     + #: lib/option.tcl:161
     + msgid "Warn before committing to a detached head"
     +-msgstr ""
     ++msgstr "Warnen vor Committen auf losgelöste Branchspitze"
     + 
     + #: lib/option.tcl:162
     + msgid "Staging of untracked files"
     +-msgstr ""
     ++msgstr "Unversionierte Dateien bereitstellen"
     + 
     + #: lib/option.tcl:163
     + msgid "Show untracked files"
     +-msgstr ""
     ++msgstr "Unversionierte Dateien anzeigen"
     + 
     + #: lib/option.tcl:164
     + msgid "Tab spacing"
     +-msgstr ""
     ++msgstr "Tabulator-Breite"
     + 
     + #: lib/option.tcl:182 lib/option.tcl:197 lib/option.tcl:220 lib/option.tcl:282
       #: lib/database.tcl:57
       #, tcl-format
       msgid "%s:"
     @@ -622,6 +1073,32 @@
       
       #: lib/option.tcl:210
       msgid "Change"
     +@@
     + msgstr "Um »%s« zu starten, muss eine Datei ausgewählt sein."
     + 
     + #: lib/tools.tcl:92
     +-#, fuzzy, tcl-format
     ++#, tcl-format
     + msgid "Are you sure you want to run %1$s on file \"%2$s\"?"
     +-msgstr "Wollen Sie %s wirklich starten?"
     ++msgstr "Wollen Sie %1$s wirklich auf Datei »%2$s« starten?"
     + 
     + #: lib/tools.tcl:96
     + #, tcl-format
     +@@
     + 
     + #: lib/mergetool.tcl:9
     + msgid "Force resolution to this branch?"
     +-msgstr "Konflikt durch diesen Zweig ersetzen?"
     ++msgstr "Konflikt durch diesen Branch ersetzen?"
     + 
     + #: lib/mergetool.tcl:10
     + msgid "Force resolution to the other branch?"
     +-msgstr "Konflikt durch anderen Zweig ersetzen?"
     ++msgstr "Konflikt durch anderen Branch ersetzen?"
     + 
     + #: lib/mergetool.tcl:14
     + #, tcl-format
      @@
       msgstr "Zusammenführungswerkzeug fehlgeschlagen."
       
     @@ -691,12 +1168,12 @@
      +#, tcl-format
       msgid "%s (%s): Delete Branch Remotely"
      -msgstr "Zweig in externem Archiv löschen"
     -+msgstr "%s (%s): Zweig in externem Repository löschen"
     ++msgstr "%s (%s): Branch in externem Repository löschen"
       
       #: lib/remote_branch_delete.tcl:34
       msgid "Delete Branch Remotely"
      -msgstr "Zweig in externem Archiv löschen"
     -+msgstr "Zweig in externem Repository löschen"
     ++msgstr "Branch in externem Repository löschen"
       
       #: lib/remote_branch_delete.tcl:48
       msgid "From Repository"
     @@ -705,19 +1182,61 @@
       
       #: lib/remote_branch_delete.tcl:88
       msgid "Branches"
     +-msgstr "Zweige"
     ++msgstr "Branches"
     + 
     + #: lib/remote_branch_delete.tcl:110
     + msgid "Delete Only If"
     +@@
     + 
     + #: lib/remote_branch_delete.tcl:153
     + msgid "A branch is required for 'Merged Into'."
     +-msgstr "Für »Zusammenführen mit« muss ein Zweig angegeben werden."
     ++msgstr "Für »Zusammenführen mit« muss ein Branch angegeben werden."
     + 
     + #: lib/remote_branch_delete.tcl:185
     + #, tcl-format
     +@@
     + "\n"
     + " - %s"
     + msgstr ""
     +-"Folgende Zweige sind noch nicht mit »%s« zusammengeführt:\n"
     ++"Folgende Branches sind noch nicht mit »%s« zusammengeführt:\n"
     + "\n"
     + " - %s"
     + 
      @@
     - msgid ""
     - "One or more of the merge tests failed because you have not fetched the "
       "necessary commits.  Try fetching from %s first."
     --msgstr ""
     --"Ein oder mehrere Zusammenführungen sind fehlgeschlagen, da Sie nicht die "
     + msgstr ""
     + "Ein oder mehrere Zusammenführungen sind fehlgeschlagen, da Sie nicht die "
      -"notwendigen Versionen vorher angefordert haben.  Sie sollten versuchen, "
      -"zuerst von »%s« anzufordern."
     -+msgstr "Ein oder mehrere Zusammenführungen sind fehlgeschlagen, da Sie nicht die notwendigen Commits vorher angefordert haben.  Sie sollten versuchen, zuerst von »%s« anzufordern."
     ++"notwendigen Commits vorher angefordert haben.  Sie sollten versuchen, zuerst "
     ++"von »%s« anzufordern."
       
       #: lib/remote_branch_delete.tcl:208
       msgid "Please select one or more branches to delete."
     +-msgstr "Bitte wählen Sie mindestens einen Zweig, der gelöscht werden soll."
     ++msgstr "Bitte wählen Sie mindestens einen Branch, der gelöscht werden soll."
     + 
     + #: lib/remote_branch_delete.tcl:218 lib/branch_delete.tcl:115
     + msgid ""
      @@
     + "\n"
     + "Delete the selected branches?"
     + msgstr ""
     +-"Das Wiederherstellen von gelöschten Zweigen ist nur mit größerem Aufwand "
     ++"Das Wiederherstellen von gelöschten Branches ist nur mit größerem Aufwand "
     + "möglich.\n"
     + "\n"
     +-"Sollen die ausgewählten Zweige gelöscht werden?"
     ++"Sollen die ausgewählten Branches gelöscht werden?"
     + 
     + #: lib/remote_branch_delete.tcl:227
     + #, tcl-format
     + msgid "Deleting branches from %s"
     +-msgstr "Zweige auf »%s« werden gelöscht"
     ++msgstr "Branches auf »%s« werden gelöscht"
       
       #: lib/remote_branch_delete.tcl:300
       msgid "No repository selected."
     @@ -793,6 +1312,14 @@
       #: lib/choose_repository.tcl:528
       msgid "Target Directory:"
      @@
     + 
     + #: lib/choose_repository.tcl:560
     + msgid "Recursively clone submodules too"
     +-msgstr ""
     ++msgstr "Rekursiv weitere Submodule klonen"
     + 
     + #: lib/choose_repository.tcl:594 lib/choose_repository.tcl:641
     + #: lib/choose_repository.tcl:790 lib/choose_repository.tcl:864
       #: lib/choose_repository.tcl:1145 lib/choose_repository.tcl:1153
       #, tcl-format
       msgid "Not a Git repository: %s"
     @@ -818,6 +1345,46 @@
       #: lib/choose_repository.tcl:666
       msgid "Failed to configure origin"
      @@
     + #: lib/choose_repository.tcl:742 lib/choose_repository.tcl:962
     + #: lib/choose_repository.tcl:974
     + msgid "The 'master' branch has not been initialized."
     +-msgstr "Der »master«-Zweig wurde noch nicht initialisiert."
     ++msgstr "Der »master«-Branch wurde noch nicht initialisiert."
     + 
     + #: lib/choose_repository.tcl:755
     + msgid "Hardlinks are unavailable.  Falling back to copying."
     +@@
     + #: lib/choose_repository.tcl:903
     + msgid "Cannot fetch branches and objects.  See console output for details."
     + msgstr ""
     +-"Zweige und Objekte konnten nicht angefordert werden.  Kontrollieren Sie die "
     +-"Ausgaben auf der Konsole für weitere Angaben."
     ++"Branches und Objekte konnten nicht angefordert werden.  Kontrollieren Sie "
     ++"die Ausgaben auf der Konsole für weitere Angaben."
     + 
     + #: lib/choose_repository.tcl:914
     + msgid "Cannot fetch tags.  See console output for details."
     + msgstr ""
     +-"Markierungen konnten nicht angefordert werden.  Kontrollieren Sie die "
     +-"Ausgaben auf der Konsole für weitere Angaben."
     ++"Tags konnten nicht angefordert werden.  Kontrollieren Sie die Ausgaben auf "
     ++"der Konsole für weitere Angaben."
     + 
     + #: lib/choose_repository.tcl:938
     + msgid "Cannot determine HEAD.  See console output for details."
     + msgstr ""
     +-"Die Zweigspitze (HEAD) konnte nicht gefunden werden.  Kontrollieren Sie die "
     ++"Die Branchspitze (HEAD) konnte nicht gefunden werden.  Kontrollieren Sie die "
     + "Ausgaben auf der Konsole für weitere Angaben."
     + 
     + #: lib/choose_repository.tcl:947
     +@@
     + 
     + #: lib/choose_repository.tcl:960
     + msgid "No default branch obtained."
     +-msgstr "Kein voreingestellter Zweig gefunden."
     ++msgstr "Kein voreingestellter Branch gefunden."
     + 
       #: lib/choose_repository.tcl:971
       #, tcl-format
       msgid "Cannot resolve %s as a commit."
     @@ -827,6 +1394,18 @@
       #: lib/choose_repository.tcl:998
       msgid "Creating working directory"
      @@
     + msgstr "Erstellen der Arbeitskopie fehlgeschlagen."
     + 
     + #: lib/choose_repository.tcl:1072
     +-#, fuzzy
     + msgid "Cloning submodules"
     +-msgstr "Kopieren von »%s«"
     ++msgstr "Klone Submodul"
     + 
     + #: lib/choose_repository.tcl:1087
     + msgid "Cannot clone submodules."
     +-msgstr ""
     ++msgstr "Submodul konnte nicht geklont werden."
       
       #: lib/choose_repository.tcl:1110
       msgid "Repository:"
     @@ -895,7 +1474,7 @@
       #: lib/blame.tcl:1067
       msgid "Cannot find HEAD commit:"
      -msgstr "Zweigspitze (»HEAD«) kann nicht gefunden werden:"
     -+msgstr "Zweigspitze (»HEAD commit«) kann nicht gefunden werden:"
     ++msgstr "Branchspitze (»HEAD commit«) kann nicht gefunden werden:"
       
       #: lib/blame.tcl:1122
       msgid "Cannot find parent commit:"
     @@ -945,24 +1524,48 @@
       #: lib/diff.tcl:239
       msgid "* Binary file (not showing content)."
      @@
     - "Fehler beim Herausnehmen des gewählten Kontexts aus der Bereitstellung."
     + "* Untracked file is %d bytes.\n"
     + "* Showing only first %d bytes.\n"
     + msgstr ""
     +-"* Datei nicht unter Versionskontrolle, Dateigröße %d Bytes.\n"
     ++"* Unversionierte Datei hat %d Bytes.\n"
     + "* Nur erste %d Bytes werden angezeigt.\n"
     + 
     + #: lib/diff.tcl:250
     +@@
     + "* To see the entire file, use an external editor.\n"
     + msgstr ""
     + "\n"
     +-"* Datei nicht unter Versionskontrolle, hier abgeschnitten durch %s.\n"
     ++"* Unversionierte Datei, hier abgeschnitten durch %s.\n"
     + "* Zum Ansehen der vollständigen Datei externen Editor benutzen.\n"
     + 
     + #: lib/diff.tcl:583
     + msgid "Failed to unstage selected hunk."
     + msgstr ""
     +-"Fehler beim Herausnehmen des gewählten Kontexts aus der Bereitstellung."
     ++"Fehler beim Herausnehmen des gewählten Patch-Blocks aus der Bereitstellung."
       
       #: lib/diff.tcl:591
      -#, fuzzy
       msgid "Failed to revert selected hunk."
      -msgstr "Fehler beim Bereitstellen des gewählten Kontexts."
     -+msgstr "Fehler beim Umkehren des gewählten Kontexts."
     ++msgstr "Fehler beim Zurücknehmen des gewählten Patch-Blocks."
       
       #: lib/diff.tcl:594
       msgid "Failed to stage selected hunk."
     -@@
     +-msgstr "Fehler beim Bereitstellen des gewählten Kontexts."
     ++msgstr "Fehler beim Bereitstellen des gewählten Patch-Blocks."
     + 
     + #: lib/diff.tcl:687
     + msgid "Failed to unstage selected line."
       msgstr "Fehler beim Herausnehmen der gewählten Zeile aus der Bereitstellung."
       
       #: lib/diff.tcl:696
      -#, fuzzy
       msgid "Failed to revert selected line."
      -msgstr "Fehler beim Bereitstellen der gewählten Zeile."
     -+msgstr "Fehler beim Umkehren der gewählten Zeile."
     ++msgstr "Fehler beim Zurücknehmen der gewählten Zeile."
       
       #: lib/diff.tcl:700
       msgid "Failed to stage selected line."
     @@ -972,7 +1575,7 @@
      -#, fuzzy
       msgid "Failed to undo last revert."
      -msgstr "Aktualisieren von »%s« fehlgeschlagen."
     -+msgstr "Fehler beim Rückgängigmachen des letzten Umkehren-Commits"
     ++msgstr "Fehler beim Rückgängigmachen des letzten Zurücknehmen-Commits"
       
       #: lib/sshkey.tcl:34
       msgid "No keys found."
     @@ -984,16 +1587,56 @@
      +#, tcl-format
       msgid "%s (%s): Create Branch"
      -msgstr "Zweig erstellen"
     -+msgstr "%s (%s): Zweig erstellen"
     ++msgstr "%s (%s): Branch erstellen"
       
       #: lib/branch_create.tcl:28
       msgid "Create New Branch"
     +-msgstr "Neuen Zweig erstellen"
     ++msgstr "Neuen Branch erstellen"
     + 
     + #: lib/branch_create.tcl:42
     + msgid "Branch Name"
     +-msgstr "Zweigname"
     ++msgstr "Branchname"
     + 
     + #: lib/branch_create.tcl:57
     + msgid "Match Tracking Branch Name"
     +-msgstr "Passend zu Übernahmezweig-Name"
     ++msgstr "Passend zu Trackingbranch-Name"
     + 
     + #: lib/branch_create.tcl:66
     + msgid "Starting Revision"
      @@
     + 
     + #: lib/branch_create.tcl:72
     + msgid "Update Existing Branch:"
     +-msgstr "Existierenden Zweig aktualisieren:"
     ++msgstr "Existierenden Branch aktualisieren:"
     + 
     + #: lib/branch_create.tcl:75
     + msgid "No"
     +@@
     + 
     + #: lib/branch_create.tcl:80
     + msgid "Fast Forward Only"
     +-msgstr "Nur Schnellzusammenführung"
     ++msgstr "Nur Vorspulen"
     + 
     + #: lib/branch_create.tcl:97
     + msgid "Checkout After Creation"
     +-msgstr "Arbeitskopie umstellen nach Erstellen"
     ++msgstr "Branch auschecken nach Erstellen"
     + 
     + #: lib/branch_create.tcl:132
     + msgid "Please select a tracking branch."
     +-msgstr "Bitte wählen Sie einen Übernahmezweig."
     ++msgstr "Bitte wählen Sie einen Trackingbranch."
     + 
       #: lib/branch_create.tcl:141
       #, tcl-format
       msgid "Tracking branch %s is not a branch in the remote repository."
      -msgstr "Übernahmezweig »%s« ist kein Zweig im externen Projektarchiv."
     -+msgstr "Übernahmezweig »%s« ist kein Zweig im externen Repository"
     ++msgstr "Trackingbranch »%s« ist kein Branch im externen Repository."
       
       #: lib/console.tcl:59
       msgid "Working... please wait..."
     @@ -1011,6 +1654,31 @@
       
       #: lib/choose_rev.tcl:52
       msgid "This Detached Checkout"
     +-msgstr "Abgetrennte Arbeitskopie-Version"
     ++msgstr "Losgelöste Arbeitskopie-Version"
     + 
     + #: lib/choose_rev.tcl:60
     + msgid "Revision Expression:"
     +-msgstr "Version Regexp-Ausdruck:"
     ++msgstr "Version Regex-Ausdruck:"
     + 
     + #: lib/choose_rev.tcl:72
     + msgid "Local Branch"
     +-msgstr "Lokaler Zweig"
     ++msgstr "Lokaler Branch"
     + 
     + #: lib/choose_rev.tcl:77
     + msgid "Tracking Branch"
     +-msgstr "Übernahmezweig"
     ++msgstr "Trackingbranch"
     + 
     + #: lib/choose_rev.tcl:82 lib/choose_rev.tcl:544
     + msgid "Tag"
     +-msgstr "Markierung"
     ++msgstr "Tag"
     + 
     + #: lib/choose_rev.tcl:321
     + #, tcl-format
      @@
       "You are about to create the initial commit.  There is no commit before this "
       "to amend.\n"
     @@ -1020,7 +1688,8 @@
       "\n"
      -"Sie sind dabei, die erste Version zu übertragen. Es gibt keine existierende "
      -"Version, die Sie nachbessern könnten.\n"
     -+"Sie sind dabei, den ersten Commit zu erstellen. Es gibt keinen existierenden Commit, den Sie nachbessern könnten.\n"
     ++"Sie sind dabei, den ersten Commit zu erstellen. Es gibt keinen existierenden "
     ++"Commit, den Sie nachbessern könnten.\n"
       
       #: lib/commit.tcl:18
       msgid ""
     @@ -1033,9 +1702,10 @@
       "\n"
      -"Sie haben das Zusammenführen von Versionen angefangen, aber noch nicht "
      -"beendet. Sie können keine vorige Übertragung nachbessern, solange eine "
     --"unfertige Zusammenführung existiert. Dazu müssen Sie die Zusammenführung "
     --"beenden oder abbrechen.\n"
     -+"Sie haben das Zusammenführen von Commits angefangen, aber noch nicht beendet. Sie können keinen vorigen Commit nachbessern, solange eine unfertige Zusammenführung existiert. Dazu müssen Sie die Zusammenführung beenden oder abbrechen.\n"
     ++"Sie haben das Zusammenführen von Commits angefangen, aber noch nicht "
     ++"beendet. Sie können keinen vorigen Commit nachbessern, solange eine "
     + "unfertige Zusammenführung existiert. Dazu müssen Sie die Zusammenführung "
     + "beenden oder abbrechen.\n"
       
       #: lib/commit.tcl:56
       msgid "Error loading commit data for amend:"
     @@ -1053,7 +1723,8 @@
       "\n"
      -"Ein anderes Git-Programm hat das Projektarchiv seit dem letzten Laden "
      -"geändert.  Vor dem Eintragen einer neuen Version muss neu geladen werden.\n"
     -+"Ein anderes Git-Programm hat das Repository seit dem letzten Laden geändert.  Vor dem nächsten Commit muss neu geladen werden.\n"
     ++"Ein anderes Git-Programm hat das Repository seit dem letzten Laden "
     ++"geändert.  Vor dem nächsten Commit muss neu geladen werden.\n"
       "\n"
       "Es wird gleich neu geladen.\n"
       
     @@ -1064,9 +1735,10 @@
      -"Nicht zusammengeführte Dateien können nicht eingetragen werden.\n"
      +"Nicht zusammengeführte Dateien können nicht committet werden.\n"
       "\n"
     --"Die Datei »%s« hat noch nicht aufgelöste Zusammenführungs-Konflikte. Sie "
     + "Die Datei »%s« hat noch nicht aufgelöste Zusammenführungs-Konflikte. Sie "
      -"müssen diese Konflikte auflösen, bevor Sie eintragen können.\n"
     -+"Die Datei »%s« hat noch nicht aufgelöste Zusammenführungs-Konflikte. Sie müssen diese Konflikte auflösen und die Dateien in die Bereitstellung hinzufügen, bevor Sie committen können.\n"
     ++"müssen diese Konflikte auflösen und die Dateien in die Bereitstellung "
     ++"hinzufügen, bevor Sie committen können.\n"
       
       #: lib/commit.tcl:190
       #, tcl-format
     @@ -1106,19 +1778,30 @@
       #: lib/commit.tcl:278
       msgid ""
      @@
     + " \n"
     + " Do you really want to proceed with your Commit?"
     + msgstr ""
     ++"Sie sind dabei, einen Commit auf losgelöste Branchspitze (»commit to "
     ++"detached head«) zu erstellen. Das ist riskant, denn wenn Sie zu einem "
     ++"anderen Branch wechseln, würden Sie diese Änderungen verlieren und es ist "
     ++"nachträglich schwierig, diese aus dem Commit-Log (»reflog«) wiederzufinden. "
     ++"Es wird empfohlen, diesen Commit abzubrechen und zunächst einen neuen Branch "
     ++"zu erstellen.\n"
     ++"\n"
     ++" Wollen Sie den Commit trotzdem in dieser Form erstellen?"
       
       #: lib/commit.tcl:299
       msgid "Calling commit-msg hook..."
      -msgstr ""
      -"Aufrufen der Versionsbeschreibungs-Kontrolle (»commit-message hook«)..."
     -+msgstr "Aufrufen des »commit-message hook«..."
     ++msgstr "Aufrufen des »commit-msg hook«..."
       
       #: lib/commit.tcl:314
       msgid "Commit declined by commit-msg hook."
      -msgstr ""
      -"Eintragen abgelehnt durch Versionsbeschreibungs-Kontrolle (»commit-message "
      -"hook«)."
     -+msgstr "Committen abgelehnt durch »commit-message hook«."
     ++msgstr "Committen abgelehnt durch »commit-msg hook«."
       
       #: lib/commit.tcl:327
       msgid "Committing changes..."
     @@ -1144,8 +1827,8 @@
      +"Keine Änderungen zum committen.\n"
       "\n"
      -"Es gibt keine geänderte Datei bei dieser Version und es wurde auch nichts "
     --"zusammengeführt.\n"
     -+"Es gibt keine geänderte Datei in diesem Commit und es wurde auch nichts zusammengeführt.\n"
     ++"Es gibt keine geänderte Datei in diesem Commit und es wurde auch nichts "
     + "zusammengeführt.\n"
       "\n"
       "Das Arbeitsverzeichnis wird daher jetzt neu geladen.\n"
       
     @@ -1157,18 +1840,38 @@
       #: lib/commit.tcl:394
       msgid "commit-tree failed:"
      @@
     - msgstr "Version %s übertragen: %s"
     + #: lib/commit.tcl:514
     + #, tcl-format
     + msgid "Created commit %s: %s"
     +-msgstr "Version %s übertragen: %s"
     ++msgstr "Commit %s erstellt: %s"
       
       #: lib/branch_delete.tcl:16
      -#, fuzzy, tcl-format
      +#, tcl-format
       msgid "%s (%s): Delete Branch"
      -msgstr "Zweig löschen"
     -+msgstr "%s (%s): Zweig löschen"
     ++msgstr "%s (%s): Branch löschen"
       
       #: lib/branch_delete.tcl:21
       msgid "Delete Local Branch"
     +-msgstr "Lokalen Zweig löschen"
     ++msgstr "Lokalen Branch löschen"
     + 
     + #: lib/branch_delete.tcl:39
     + msgid "Local Branches"
     +-msgstr "Lokale Zweige"
     ++msgstr "Lokale Branches"
     + 
     + #: lib/branch_delete.tcl:51
     + msgid "Delete Only If Merged Into"
      @@
     + #: lib/branch_delete.tcl:103
     + #, tcl-format
     + msgid "The following branches are not completely merged into %s:"
     +-msgstr "Folgende Zweige sind noch nicht mit »%s« zusammengeführt:"
     ++msgstr "Folgende Branches sind noch nicht mit »%s« zusammengeführt:"
     + 
       #: lib/branch_delete.tcl:131
       #, tcl-format
       msgid " - %s:"
     @@ -1177,6 +1880,15 @@
       
       #: lib/branch_delete.tcl:141
       #, tcl-format
     +@@
     + "Failed to delete branches:\n"
     + "%s"
     + msgstr ""
     +-"Fehler beim Löschen der Zweige:\n"
     ++"Fehler beim Löschen der Branches:\n"
     + "%s"
     + 
     + #: lib/date.tcl:25
      @@
       msgstr "Dateien im Mülleimer"
       
     @@ -1198,7 +1910,8 @@
       "\n"
      -"Für eine optimale Performance wird empfohlen, die Datenbank des "
      -"Projektarchivs zu komprimieren.\n"
     -+"Für eine optimale Performance wird empfohlen, die Datenbank des Repository zu komprimieren.\n"
     ++"Für eine optimale Performance wird empfohlen, die Datenbank des Repository "
     ++"zu komprimieren.\n"
       "\n"
       "Soll die Datenbank jetzt komprimiert werden?"
       
     @@ -1225,9 +1938,9 @@
       
       #: lib/error.tcl:96
       msgid "You must correct the above errors before committing."
     --msgstr ""
     + msgstr ""
      -"Sie müssen die obigen Fehler zuerst beheben, bevor Sie eintragen können."
     -+msgstr "Sie müssen die obigen Fehler zuerst beheben, bevor Sie committen können."
     ++"Sie müssen die obigen Fehler zuerst beheben, bevor Sie committen können."
       
       #: lib/error.tcl:116
       #, tcl-format
     @@ -1242,11 +1955,10 @@
       "Zusammenführen kann nicht gleichzeitig mit Nachbessern durchgeführt werden.\n"
       "\n"
      -"Sie müssen zuerst die Nachbesserungs-Version abschließen, bevor Sie "
     --"zusammenführen können.\n"
     -+"Sie müssen zuerst den Nachbesserungs-Commit abschließen, bevor Sie zusammenführen können.\n"
     ++"Sie müssen zuerst den Nachbesserungs-Commit abschließen, bevor Sie "
     + "zusammenführen können.\n"
       
       #: lib/merge.tcl:27
     - msgid ""
      @@
       "\n"
       "The rescan will be automatically started now.\n"
     @@ -1255,11 +1967,10 @@
      +"Der letzte geladene Status stimmt nicht mehr mit dem Repository überein.\n"
       "\n"
      -"Ein anderes Git-Programm hat das Projektarchiv seit dem letzten Laden "
     --"geändert.  Vor einem Zusammenführen muss neu geladen werden.\n"
     -+"Ein anderes Git-Programm hat das Repository seit dem letzten Laden geändert.  Vor einem Zusammenführen muss neu geladen werden.\n"
     ++"Ein anderes Git-Programm hat das Repository seit dem letzten Laden "
     + "geändert.  Vor einem Zusammenführen muss neu geladen werden.\n"
       "\n"
       "Es wird gleich neu geladen.\n"
     - 
      @@
       msgstr ""
       "Zusammenführung mit Konflikten.\n"
     @@ -1270,7 +1981,9 @@
      -"danach kann eine neue Zusammenführung begonnen werden.\n"
      +"Die Datei »%s« enthält Konflikte beim Zusammenführen.\n"
      +"\n"
     -+"Sie müssen diese Konflikte per Hand auflösen. Anschließend müssen Sie die Datei wieder bereitstellen und committen, um die Zusammenführung abzuschließen. Erst danach kann eine neue Zusammenführung begonnen werden.\n"
     ++"Sie müssen diese Konflikte per Hand auflösen. Anschließend müssen Sie die "
     ++"Datei wieder bereitstellen und committen, um die Zusammenführung "
     ++"abzuschließen. Erst danach kann eine neue Zusammenführung begonnen werden.\n"
       
       #: lib/merge.tcl:55
       #, tcl-format
     @@ -1284,7 +1997,9 @@
      -"einfacher beheben oder abbrechen.\n"
      +"Die Datei »%s« wurde geändert.\n"
      +"\n"
     -+"Sie sollten zuerst den bereitgestellten Commit abschließen, bevor Sie eine Zusammenführung beginnen.  Mit dieser Reihenfolge können Sie mögliche Konflikte beim Zusammenführen wesentlich einfacher beheben oder abbrechen.\n"
     ++"Sie sollten zuerst den bereitgestellten Commit abschließen, bevor Sie eine "
     ++"Zusammenführung beginnen.  Mit dieser Reihenfolge können Sie mögliche "
     ++"Konflikte beim Zusammenführen wesentlich einfacher beheben oder abbrechen.\n"
       
       #: lib/merge.tcl:108
       #, tcl-format
     @@ -1317,15 +2032,21 @@
       "Zusammenführen jetzt abbrechen?"
       
      @@
     + "\n"
     + "Continue with resetting the current changes?"
       msgstr ""
     - "Änderungen zurücksetzen?\n"
     +-"Änderungen zurücksetzen?\n"
     ++"Änderungen verwerfen?\n"
       "\n"
      -"Wenn Sie zurücksetzen, gehen alle noch nicht eingetragenen Änderungen "
      -"verloren.\n"
     -+"Wenn Sie zurücksetzen, gehen alle noch nicht committeten Änderungen verloren.\n"
     ++"Alle noch nicht committeten Änderungen würden verloren gehen.\n"
       "\n"
     - "Änderungen jetzt zurücksetzen?"
     +-"Änderungen jetzt zurücksetzen?"
     ++"Änderungen jetzt verwerfen?"
       
     + #: lib/merge.tcl:246
     + msgid "Aborting"
      @@
       #: lib/merge.tcl:279
       msgid "Abort completed.  Ready."
     @@ -1344,14 +2065,113 @@
       --- a/po/glossary/de.po
       +++ b/po/glossary/de.po
      @@
     + msgid ""
       msgstr ""
       "Project-Id-Version: git-gui glossary\n"
     - "POT-Creation-Date: 2020-01-13 21:40+0100\n"
     --"PO-Revision-Date: 2020-01-13 21:53+0100\n"
     -+"PO-Revision-Date: 2020-01-13 22:30+0100\n"
     +-"POT-Creation-Date: 2008-01-07 21:20+0100\n"
     +-"PO-Revision-Date: 2008-02-16 21:48+0100\n"
     ++"POT-Creation-Date: 2020-01-26 22:26+0100\n"
     ++"PO-Revision-Date: 2020-02-09 21:22+0100\n"
       "Last-Translator: Christian Stimming <stimming@tuhh.de>\n"
       "Language-Team: German \n"
     - "Language: de_DE\n"
     ++"Language: de_DE\n"
     + "MIME-Version: 1.0\n"
     + "Content-Type: text/plain; charset=UTF-8\n"
     + "Content-Transfer-Encoding: 8bit\n"
     +@@
     + "English Term (Dear translator: This file will never be visible to the user!)"
     + msgstr ""
     + "Deutsche Übersetzung.\n"
     ++"Git-core glossary:\n"
     ++"  https://github.com/ruester/git-po-de/wiki/Translation-Guidelines\n"
     ++"\n"
     + "Andere deutsche SCM:\n"
     + "  http://tortoisesvn.net/docs/release/TortoiseSVN_de/index.html und http://"
     + "tortoisesvn.tigris.org/svn/tortoisesvn/trunk/Languages/Tortoise_de.po "
     +@@
     + "  http://rapidsvn.tigris.org/svn/rapidsvn/trunk/src/locale/de/rapidsvn.po "
     + "(username=guest, password empty, schlecht)"
     + 
     ++#. "prematurely stop and abandon an operation"
     ++msgid "abort"
     ++msgstr "abbrechen"
     ++
     + #. ""
     + msgid "amend"
     + msgstr "nachbessern (ergänzen)"
     + 
     ++#. "a commit that succeeds the current one in git's graph of commits (not necessarily directly)"
     ++msgid "ancestor"
     ++msgstr "Vorgänger-Commit"
     ++
     + #. ""
     + msgid "annotate"
     + msgstr "annotieren"
     + 
     ++#. "The person who initially created (authored) a commit"
     ++msgid "author"
     ++msgstr "Autor"
     ++
     ++#. "a repository with only .git directory, without working directory"
     ++msgid "bare repository"
     ++msgstr "bloßes Projektarchiv"
     ++
     ++#. "a parent version of the current file"
     ++msgid "base"
     ++msgstr "Ursprung"
     ++
     ++#. ""
     ++msgid "bisect"
     ++msgstr "binäre Suche [noun], binäre Suche benutzen [verb]"
     ++
     ++#. "get the authors responsible for each line in a file"
     ++msgid "blame"
     ++msgstr "annotieren"
     ++
     ++#.      ""
     ++msgid "blob"
     ++msgstr "Blob"
     ++
     + #. "A 'branch' is an active line of development."
     + msgid "branch [noun]"
     +-msgstr "Zweig"
     ++msgstr "Branch"
     + 
     + #. ""
     + msgid "branch [verb]"
     +-msgstr "verzweigen"
     ++msgstr "branchen"
     + 
     + #. ""
     + msgid "checkout [noun]"
     + msgstr ""
     +-"Arbeitskopie (Erstellung einer Arbeitskopie; Auscheck? Ausspielung? Abruf? "
     +-"Source Safe: Auscheckvorgang)"
     ++"Arbeitskopie (Checkout; Erstellung einer Arbeitskopie; Auscheck? Source "
     ++"Safe: Auscheckvorgang)"
     + 
     + #. "The action of updating the working tree to a revision which was stored in the object database."
     + msgid "checkout [verb]"
     + msgstr ""
     +-"Arbeitskopie erstellen; Zweig umstellen [checkout a branch] (auschecken? "
     +-"ausspielen? abrufen? Source Safe: auschecken)"
     ++"Arbeitskopie erstellen; Branch auschecken [checkout a branch] (umstellen? "
     ++"Source Safe: auschecken)"
     ++
     ++#. "to select and apply a single commit to the current HEAD without merging"
     ++msgid "cherry-pick"
     ++msgstr "cherry-pick (pflücken?)"
     ++
     ++#. "a commit that directly succeeds the current one in git's graph of commits"
     ++msgid "child commit"
     ++msgstr "Kind-Commit"
     ++
     ++#. "clean the state of the git repository, often after manually stopped operation"
     ++msgid "cleanup"
     ++msgstr "aufräumen"
     + 
     + #. ""
     + msgid "clone [verb]"
      @@
       
       #. "A single point in the git history."
     @@ -1363,14 +2183,202 @@
       
       #. "The action of storing a new snapshot of the project's state in the git history."
       msgid "commit [verb]"
     --msgstr ""
     + msgstr ""
      -"eintragen (TortoiseSVN: übertragen; Source Safe: einchecken; senden?, "
      -"übergeben?, einspielen?, einpflegen?, ablegen?)"
     -+msgstr "committen (eintragen?, TortoiseSVN: übertragen; Source Safe: einchecken)"
     ++"committen (eintragen?, TortoiseSVN: übertragen; Source Safe: einchecken)"
     ++
     ++#. "a message that gets attached with any commit"
     ++msgid "commit message"
     ++msgstr "Commit-Beschreibung (Meldung?, Nachricht?; Source Safe: Kommentar)"
     ++
     ++#.   "The person who committed a commit (to the current branch), which might be different than the author."
     ++msgid "committer"
     ++msgstr "Committer"
     ++
     ++#. "a commit that precedes the current one in git's graph of commits (not necessarily directly)"
     ++msgid "descendant"
     ++msgstr "Nachfolger-Commit"
     ++
     ++#.       "checkout of a revision rather than some head"
     ++msgid "detached HEAD"
     ++msgstr "losgelöster HEAD / Branchspitze"
     ++
     ++#. "checkout of a revision rather than some head"
     ++msgid "detached checkout"
     ++msgstr "losgelöster Commit (von Branch losgelöster Commit?)"
       
       #. ""
       msgid "diff [noun]"
     -@@
     +-msgstr "Vergleich (Source Safe: Unterschiede)"
     ++msgstr "Vergleich (Diff? Source Safe: Unterschiede)"
     + 
     + #. ""
     + msgid "diff [verb]"
     + msgstr "vergleichen"
     + 
     +-#. "A fast-forward is a special type of merge where you have a revision and you are merging another branch's changes that happen to be a descendant of what you have."
     +-msgid "fast forward merge"
     +-msgstr "Schnellzusammenführung"
     ++#.   ""
     ++msgid "directory"
     ++msgstr "Verzeichnis"
     ++
     ++#. "A fast-forward merge is a special type of merge where you have a revision and you are merging another branch's changes that happen to be a descendant of what you have."
     ++msgid "fast-forward"
     ++msgstr "vorspulen"
     + 
     + #. "Fetching a branch means to get the branch's head from a remote repository, to find out which objects are missing from the local object database, and to get them, too."
     + msgid "fetch"
     + msgstr "anfordern (holen?)"
     + 
     ++#. "any merge strategy that works on a file by file basis"
     ++msgid "file level merging"
     ++msgstr "Datei-basiertes zusammenführen"
     ++
     ++#.      ""
     ++msgid "file"
     ++msgstr "Datei"
     ++
     ++#. "the last revision in a branch"
     ++msgid "head"
     ++msgstr "HEAD / Branchspitze"
     ++
     ++#. "script that gets executed automatically on some event"
     ++msgid "hook"
     ++msgstr "Hook (in der dt. Informatik wohl als Einschubmethode bezeichnet)"
     ++
     + #. "One context of consecutive lines in a whole patch, which consists of many such hunks"
     + msgid "hunk"
     +-msgstr "Kontext"
     ++msgstr "Patch-Block (Kontext?)"
     + 
     + #. "A collection of files. The index is a stored version of your working tree."
     + msgid "index (in git-gui: staging area)"
     +-msgstr "Bereitstellung"
     ++msgstr ""
     ++"Bereitstellung (sofern der git index gemeint ist. In git-gui sowieso: "
     ++"staging area)"
     ++
     ++#. "the first checkout during a clone operation"
     ++msgid "initial checkout"
     ++msgstr "Erstellen der Arbeitskopie, auschecken"
     ++
     ++#. "The very first commit in a repository"
     ++msgid "initial commit"
     ++msgstr "Allererster Commit"
     ++
     ++#. "a branch that resides in the local git repository"
     ++msgid "local branch"
     ++msgstr "Lokaler Branch"
     ++
     ++#. "a Git object that is not part of any pack"
     ++msgid "loose object"
     ++msgstr "loses Objekt"
     ++
     ++#. "a branch called by convention 'master' that exists in a newly created git repository"
     ++msgid "master branch"
     ++msgstr "Master-Branch"
     + 
     + #. "A successful merge results in the creation of a new commit representing the result of the merge."
     + msgid "merge [noun]"
     +@@
     + msgid "message"
     + msgstr "Beschreibung (Meldung?, Nachricht?; Source Safe: Kommentar)"
     + 
     +-#. "Deletes all stale tracking branches under <name>. These stale branches have already been removed from the remote repository referenced by <name>, but are still locally available in 'remotes/<name>'."
     ++#. "a remote called by convention 'origin' that the current git repository has been cloned from"
     ++msgid "origin"
     ++msgstr "origin"
     ++
     ++#.       ""
     ++msgid "orphan commit"
     ++msgstr "verwaister Commit"
     ++
     ++#.  ""
     ++msgid "orphan reference"
     ++msgstr "verwaiste Referenz"
     ++
     ++#. "a file containing many git objects packed together"
     ++msgid "pack [noun]"
     ++msgstr "Pack-Datei"
     ++
     ++#.     "the process of creating a pack file"
     ++msgid "pack [verb]"
     ++msgstr "Pack-Datei erstellen"
     ++
     ++#. "a Git object part of some pack"
     ++msgid "packed object"
     ++msgstr "gepacktes Objekt"
     ++
     ++#. "a commit that directly precedes the current one in git's graph of commits"
     ++msgid "parent commit"
     ++msgstr "Eltern-Commit"
     ++
     ++msgid "patch"
     ++msgstr "Patch"
     ++
     ++#. "The path to a file"
     ++msgid "path"
     ++msgstr "Pfad"
     ++
     ++#. "Delete all stale tracking branches under <name>. These stale branches have already been removed from the remote repository referenced by <name>, but are still locally available in 'remotes/<name>'."
     + msgid "prune"
     +-msgstr "aufräumen (entfernen?)"
     ++msgstr "veraltete Branches entfernen (aufräumen?, entfernen?)"
     + 
     + #. "Pulling a branch means to fetch it and merge it."
     + msgid "pull"
     +-msgstr "übernehmen (ziehen?)"
     ++msgstr ""
     ++"übernehmen (pull? ziehen? Vorsicht: zusammenführen = merge, aber pull kann "
     ++"auch rebase bewirken)"
     + 
     + #. "Pushing a branch means to get the branch's head ref from a remote repository, and ... (well, can someone please explain it for mere mortals?)"
     + msgid "push"
     + msgstr "versenden (ausliefern? hochladen? verschicken? schieben?)"
     + 
     ++#. "The process of rebasing one set of commits on top of another branch's head"
     ++msgid "rebase [noun]"
     ++msgstr "der Rebase (das Umpflanzen)"
     ++
     ++#. "Re-apply one set of commits on top of another branch's head. Contrary to merge."
     ++msgid "rebase [verb]"
     ++msgstr "rebase (umpflanzen)"
     ++
     + #. ""
     + msgid "redo"
     + msgstr "wiederholen"
     + 
     +-#. "An other repository ('remote'). One might have a set of remotes whose branches one tracks."
     +-msgid "remote"
     +-msgstr "Andere Archive (Gegenseite?, Entfernte?, Server?)"
     ++#.   ""
     ++msgid "reference"
     ++msgstr "Referenz"
     ++
     ++#. "the log file containing all states of the HEAD reference (in other words past pristine states of the working copy)"
     ++msgid "reflog"
     ++msgstr "Commit-Log, »reflog«"
     ++
     ++msgid "refmap"
     ++msgstr "Refmap"
     ++
     ++#. ""
     ++msgid "refspec"
     ++msgstr "Refspec"
     ++
     ++#. "The adjective for anything which is outside of the current (local) repository"
     ++msgid "remote [adj]"
     ++msgstr "Extern (Andere?, Gegenseite?, Entfernte?, Server?)"
     ++
     ++#.       "A branch in any other ('remote') repository"
     ++msgid "remote branch"
     ++msgstr "Externer branch"
     ++
     ++#.   "An other repository ('remote'). One might have a set of remotes whose branches one tracks."
     ++msgid "remote repository"
     ++msgstr "Externes Repository"
       
       #. "A collection of refs (?) together with an object database containing all objects which are reachable from the refs... (oops, you've lost me here. Again, please an explanation for mere mortals?)"
       msgid "repository"
     @@ -1379,29 +2387,129 @@
       
       #. ""
       msgid "reset"
     -@@
     +-msgstr "zurücksetzen (zurückkehren?)"
     ++msgstr "umsetzen (reset to commit), Änderungen verwerfen (reset to HEAD)"
     ++
     ++#. "decide which changes from alternative versions of a file should persist in Git"
     ++msgid "resolve (a conflict)"
     ++msgstr "auflösen (einen Konflikt)"
     ++
     ++#. "abandon changes and go to pristine version"
     ++msgid "revert changes"
     ++msgstr ""
     ++"verwerfen (bei git-reset bzw. checkout), zurücknehmen (bei git-revert, also "
     ++"mit neuem commit; umkehren?)"
       
       #. ""
       msgid "revert"
     --msgstr ""
     --"verwerfen (bei git-reset), revidieren (bei git-revert, also mit neuem commit)"
     -+msgstr "verwerfen (bei git-reset bzw. checkout), umkehren (bei git-revert, also mit neuem commit)"
     +-msgstr "verwerfen (bei git-reset), revidieren (bei git-revert, also mit neuem commit)"
     ++msgstr ""
     ++"verwerfen (bei git-reset bzw. checkout), zurücknehmen (bei git-revert, also "
     ++"mit neuem commit; umkehren?)"
     ++
     ++#. "expression that signifies a revision in git"
     ++msgid "revision expression"
     ++msgstr "Version Regexp-Ausdruck"
       
       #. "A particular state of files and directories which was stored in the object database."
       msgid "revision"
      -msgstr "Version (TortoiseSVN: Revision; Source Safe: Version)"
     -+msgstr "Version (aber was macht das Wort revision hier im Git?? TortoiseSVN: Revision; Source Safe: Version)"
     ++msgstr ""
     ++"Version (aber was macht das Wort revision hier im Git?? TortoiseSVN: "
     ++"Revision; Source Safe: Version)"
       
       #. ""
       msgid "sign off"
     -@@
     - msgstr ""
     +-msgstr "abzeichnen (gegenzeichnen?, freizeichnen?, absegnen?)"
     ++msgstr "abzeichnen (signieren? gegenzeichnen?, freizeichnen?)"
     + 
     +-#. ""
     ++#.     "see: staging area. In some areas of git this is called 'index'."
     ++msgid "stage [noun], index"
     ++msgstr "Bereitstellung"
     ++
     ++#. "add some content of files and directories to the staging area in preparation for a commit"
     ++msgid "stage [verb]"
     ++msgstr "bereitstellen"
     ++
     ++#. "The place where changes from files are marked to be included for the next commit. In some areas of git this is called 'index'."
     + msgid "staging area"
     + msgstr "Bereitstellung"
     + 
     ++#. "The place (stack) where changes can be temporarily saved without committing"
     ++msgid "stash [noun]"
     ++msgstr "der Stash"
     ++
     ++#. "temporarily save changes in a stack without committing"
     ++msgid "stash [verb]"
     ++msgstr "in Stash speichern; \"stash\" benutzen"
     ++
     + #. ""
     + msgid "status"
     + msgstr "Status"
     + 
     +-#. "A ref pointing to a tag or commit object"
     ++#. ""
     ++msgid "submodule"
     ++msgstr "Submodul (Untermodul?)"
     ++
     ++#. "A ref pointing to some commit object. In other words: A label on a specific commit."
     + msgid "tag [noun]"
     +-msgstr "Markierung"
     ++msgstr "Tag (Markierung?)"
     + 
     +-#. ""
     ++#. "The process of creating a tag at a specific commit object"
     + msgid "tag [verb]"
     +-msgstr "markieren"
     ++msgstr "taggen (markieren?)"
     ++
     ++#. "The person who created a tag"
     ++msgid "tagger"
     ++msgstr "Tag-Ersteller (Markierungs-Ersteller?)"
     ++
     ++#. "file whose content is tracked/not tracked by git"
     ++msgid "tracked/untracked"
     ++msgstr "versioniert/unversioniert"
     + 
     + #. "A regular git branch that is used to follow changes from another repository."
     + msgid "tracking branch"
     +-msgstr "Übernahmezweig"
     ++msgstr "Tracking-Branch (Verfolgungsbranch? Übernahmebranch?)"
     ++
     ++#. ""
     ++msgid "trailer"
     ++msgstr "Anhang"
     ++
     ++#. "1. tree object, 2. directory tree"
     ++msgid "tree"
     ++msgstr "1. Baum-Objekt, 2. Verzeichnisbaum"
       
     - #. "a message that gets attached with any commit"
     --#, fuzzy
     - msgid "commit message"
     --msgstr "Beschreibung (Meldung?, Nachricht?; Source Safe: Kommentar)"
     -+msgstr "Commit-Beschreibung (Meldung?, Nachricht?; Source Safe: Kommentar)"
     + #. ""
     + msgid "undo"
     + msgstr "rückgängig"
     + 
     ++#. "Remove content of files from the staging area again so that it will not be part of the next commit"
     ++msgid "unstage"
     ++msgstr "aus Bereitstellung herausnehmen"
     ++
     ++#. "Retrieving the temporarily saved changes back again from the stash"
     ++msgid "unstash [verb]"
     ++msgstr "aus Stash zurückladen"
     ++
     + #. ""
     + msgid "update"
     + msgstr "aktualisieren"
     + 
     ++#. ""
     ++msgid "upstream branch"
     ++msgstr "Upstream-Branch"
     ++
     + #. ""
     + msgid "verify"
     + msgstr "überprüfen"
       
     - #. "a commit that precedes the current one in git's graph of commits (not necessarily directly)"
     - msgid "descendant"
     + #. "The tree of actual checked out files."
     +-msgid "working copy, working tree"
     ++msgid "working directory, working copy, working tree"
     + msgstr "Arbeitskopie"
 3:  c91a84b0ed < -:  ---------- git-gui: completed german translation

-- 
gitgitgadget

^ permalink raw reply	[relevance 1%]

* [PATCH v4 4/5] Add reftable library
  @ 2020-02-06 22:55  1%       ` Han-Wen Nienhuys via GitGitGadget
    1 sibling, 0 replies; 200+ results
From: Han-Wen Nienhuys via GitGitGadget @ 2020-02-06 22:55 UTC (permalink / raw)
  To: git; +Cc: Han-Wen Nienhuys, Han-Wen Nienhuys

From: Han-Wen Nienhuys <hanwen@google.com>

Reftable is a new format for storing the ref database. It provides the
following benefits:

 * Simple and fast atomic ref transactions, including multiple refs and reflogs.
 * Compact storage of ref data.
 * Fast look ups of ref data.
 * Case-sensitive ref names on Windows/OSX, regardless of file system
 * Eliminates file/directory conflicts in ref names

Further context and motivation can be found in background reading:

* Spec: https://github.com/eclipse/jgit/blob/master/Documentation/technical/reftable.md

* Original discussion on JGit-dev:  https://www.eclipse.org/lists/jgit-dev/msg03389.html

* First design discussion on git@vger: https://public-inbox.org/git/CAJo=hJtTp2eA3z9wW9cHo-nA7kK40vVThqh6inXpbCcqfdMP9g@mail.gmail.com/

* Last design discussion on git@vger: https://public-inbox.org/git/CAJo=hJsZcAM9sipdVr7TMD-FD2V2W6_pvMQ791EGCDsDkQ033w@mail.gmail.com/

* First attempt at implementation: https://public-inbox.org/git/CAP8UFD0PPZSjBnxCA7ez91vBuatcHKQ+JUWvTD1iHcXzPBjPBg@mail.gmail.com/

* libgit2 support issue: https://github.com/libgit2/libgit2/issues

* GitLab support issue: https://gitlab.com/gitlab-org/git/issues/6

* go-git support issue: https://github.com/src-d/go-git/issues/1059

Signed-off-by: Han-Wen Nienhuys <hanwen@google.com>
---
 reftable/LICENSE       |   31 ++
 reftable/README.md     |   19 +
 reftable/VERSION       |    5 +
 reftable/basics.c      |  196 +++++++
 reftable/basics.h      |   37 ++
 reftable/block.c       |  401 +++++++++++++++
 reftable/block.h       |   71 +++
 reftable/blocksource.h |   20 +
 reftable/bytes.c       |    0
 reftable/config.h      |    1 +
 reftable/constants.h   |   27 +
 reftable/dump.c        |   97 ++++
 reftable/file.c        |   97 ++++
 reftable/iter.c        |  229 +++++++++
 reftable/iter.h        |   56 ++
 reftable/merged.c      |  286 +++++++++++
 reftable/merged.h      |   34 ++
 reftable/pq.c          |  114 +++++
 reftable/pq.h          |   34 ++
 reftable/reader.c      |  708 +++++++++++++++++++++++++
 reftable/reader.h      |   52 ++
 reftable/record.c      | 1107 ++++++++++++++++++++++++++++++++++++++++
 reftable/record.h      |   79 +++
 reftable/reftable.h    |  399 +++++++++++++++
 reftable/slice.c       |  199 ++++++++
 reftable/slice.h       |   39 ++
 reftable/stack.c       |  983 +++++++++++++++++++++++++++++++++++
 reftable/stack.h       |   40 ++
 reftable/system.h      |   55 ++
 reftable/tree.c        |   66 +++
 reftable/tree.h        |   24 +
 reftable/writer.c      |  623 ++++++++++++++++++++++
 reftable/writer.h      |   46 ++
 reftable/zlib-compat.c |   92 ++++
 34 files changed, 6267 insertions(+)
 create mode 100644 reftable/LICENSE
 create mode 100644 reftable/README.md
 create mode 100644 reftable/VERSION
 create mode 100644 reftable/basics.c
 create mode 100644 reftable/basics.h
 create mode 100644 reftable/block.c
 create mode 100644 reftable/block.h
 create mode 100644 reftable/blocksource.h
 create mode 100644 reftable/bytes.c
 create mode 100644 reftable/config.h
 create mode 100644 reftable/constants.h
 create mode 100644 reftable/dump.c
 create mode 100644 reftable/file.c
 create mode 100644 reftable/iter.c
 create mode 100644 reftable/iter.h
 create mode 100644 reftable/merged.c
 create mode 100644 reftable/merged.h
 create mode 100644 reftable/pq.c
 create mode 100644 reftable/pq.h
 create mode 100644 reftable/reader.c
 create mode 100644 reftable/reader.h
 create mode 100644 reftable/record.c
 create mode 100644 reftable/record.h
 create mode 100644 reftable/reftable.h
 create mode 100644 reftable/slice.c
 create mode 100644 reftable/slice.h
 create mode 100644 reftable/stack.c
 create mode 100644 reftable/stack.h
 create mode 100644 reftable/system.h
 create mode 100644 reftable/tree.c
 create mode 100644 reftable/tree.h
 create mode 100644 reftable/writer.c
 create mode 100644 reftable/writer.h
 create mode 100644 reftable/zlib-compat.c

diff --git a/reftable/LICENSE b/reftable/LICENSE
new file mode 100644
index 0000000000..402e0f9356
--- /dev/null
+++ b/reftable/LICENSE
@@ -0,0 +1,31 @@
+BSD License
+
+Copyright (c) 2020, Google LLC
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+* Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+* Neither the name of Google LLC nor the names of its contributors may
+be used to endorse or promote products derived from this software
+without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/reftable/README.md b/reftable/README.md
new file mode 100644
index 0000000000..f527da0380
--- /dev/null
+++ b/reftable/README.md
@@ -0,0 +1,19 @@
+
+The source code in this directory comes from https://github.com/google/reftable.
+
+The VERSION file keeps track of the current version of the reftable library.
+
+To update the library, do:
+
+   ((cd reftable-repo && git fetch origin && git checkout origin/master ) ||
+    git clone https://github.com/google/reftable reftable-repo) && \
+   cp reftable-repo/c/*.[ch] reftable/ && \
+   cp reftable-repo/LICENSE reftable/ &&
+   git --git-dir reftable-repo/.git show --no-patch origin/master \
+    > reftable/VERSION && \
+   echo '/* empty */' > reftable/config.h
+   rm reftable/*_test.c reftable/test_framework.*
+   git add reftable/*.[ch]
+
+Bugfixes should be accompanied by a test and applied to upstream project at
+https://github.com/google/reftable.
diff --git a/reftable/VERSION b/reftable/VERSION
new file mode 100644
index 0000000000..5d505c6c0a
--- /dev/null
+++ b/reftable/VERSION
@@ -0,0 +1,5 @@
+commit e7c3fc3099d9999bc8d895f84027b0e36348d5e6
+Author: Han-Wen Nienhuys <hanwen@google.com>
+Date:   Thu Feb 6 20:17:40 2020 +0100
+
+    C: use inttypes.h header definitions
diff --git a/reftable/basics.c b/reftable/basics.c
new file mode 100644
index 0000000000..791dcc867a
--- /dev/null
+++ b/reftable/basics.c
@@ -0,0 +1,196 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "basics.h"
+
+#include "system.h"
+
+void put_u24(byte *out, uint32_t i)
+{
+	out[0] = (byte)((i >> 16) & 0xff);
+	out[1] = (byte)((i >> 8) & 0xff);
+	out[2] = (byte)((i)&0xff);
+}
+
+uint32_t get_u24(byte *in)
+{
+	return (uint32_t)(in[0]) << 16 | (uint32_t)(in[1]) << 8 |
+	       (uint32_t)(in[2]);
+}
+
+void put_u32(byte *out, uint32_t i)
+{
+	out[0] = (byte)((i >> 24) & 0xff);
+	out[1] = (byte)((i >> 16) & 0xff);
+	out[2] = (byte)((i >> 8) & 0xff);
+	out[3] = (byte)((i)&0xff);
+}
+
+uint32_t get_u32(byte *in)
+{
+	return (uint32_t)(in[0]) << 24 | (uint32_t)(in[1]) << 16 |
+	       (uint32_t)(in[2]) << 8 | (uint32_t)(in[3]);
+}
+
+void put_u64(byte *out, uint64_t v)
+{
+	int i = 0;
+	for (i = sizeof(uint64_t); i--;) {
+		out[i] = (byte)(v & 0xff);
+		v >>= 8;
+	}
+}
+
+uint64_t get_u64(byte *out)
+{
+	uint64_t v = 0;
+	int i = 0;
+	for (i = 0; i < sizeof(uint64_t); i++) {
+		v = (v << 8) | (byte)(out[i] & 0xff);
+	}
+	return v;
+}
+
+void put_u16(byte *out, uint16_t i)
+{
+	out[0] = (byte)((i >> 8) & 0xff);
+	out[1] = (byte)((i)&0xff);
+}
+
+uint16_t get_u16(byte *in)
+{
+	return (uint32_t)(in[0]) << 8 | (uint32_t)(in[1]);
+}
+
+/*
+  find smallest index i in [0, sz) at which f(i) is true, assuming
+  that f is ascending. Return sz if f(i) is false for all indices.
+*/
+int binsearch(int sz, int (*f)(int k, void *args), void *args)
+{
+	int lo = 0;
+	int hi = sz;
+
+	/* invariant: (hi == sz) || f(hi) == true
+	   (lo == 0 && f(0) == true) || fi(lo) == false
+	 */
+	while (hi - lo > 1) {
+		int mid = lo + (hi - lo) / 2;
+
+		int val = f(mid, args);
+		if (val) {
+			hi = mid;
+		} else {
+			lo = mid;
+		}
+	}
+
+	if (lo == 0) {
+		if (f(0, args)) {
+			return 0;
+		} else {
+			return 1;
+		}
+	}
+
+	return hi;
+}
+
+void free_names(char **a)
+{
+	char **p = a;
+	if (p == NULL) {
+		return;
+	}
+	while (*p) {
+		free(*p);
+		p++;
+	}
+	free(a);
+}
+
+int names_length(char **names)
+{
+	int len = 0;
+	for (char **p = names; *p; p++) {
+		len++;
+	}
+	return len;
+}
+
+/* parse a newline separated list of names. Empty names are discarded. */
+void parse_names(char *buf, int size, char ***namesp)
+{
+	char **names = NULL;
+	int names_cap = 0;
+	int names_len = 0;
+
+	char *p = buf;
+	char *end = buf + size;
+	while (p < end) {
+		char *next = strchr(p, '\n');
+		if (next != NULL) {
+			*next = 0;
+		} else {
+			next = end;
+		}
+		if (p < next) {
+			if (names_len == names_cap) {
+				names_cap = 2 * names_cap + 1;
+				names = realloc(names,
+						names_cap * sizeof(char *));
+			}
+			names[names_len++] = strdup(p);
+		}
+		p = next + 1;
+	}
+
+	if (names_len == names_cap) {
+		names_cap = 2 * names_cap + 1;
+		names = realloc(names, names_cap * sizeof(char *));
+	}
+
+	names[names_len] = NULL;
+	*namesp = names;
+}
+
+int names_equal(char **a, char **b)
+{
+	while (*a && *b) {
+		if (strcmp(*a, *b)) {
+			return 0;
+		}
+
+		a++;
+		b++;
+	}
+
+	return *a == *b;
+}
+
+const char *error_str(int err)
+{
+	switch (err) {
+	case IO_ERROR:
+		return "I/O error";
+	case FORMAT_ERROR:
+		return "FORMAT_ERROR";
+	case NOT_EXIST_ERROR:
+		return "NOT_EXIST_ERROR";
+	case LOCK_ERROR:
+		return "LOCK_ERROR";
+	case API_ERROR:
+		return "API_ERROR";
+	case ZLIB_ERROR:
+		return "ZLIB_ERROR";
+	case -1:
+		return "general error";
+	default:
+		return "unknown error code";
+	}
+}
diff --git a/reftable/basics.h b/reftable/basics.h
new file mode 100644
index 0000000000..0ad368cfd3
--- /dev/null
+++ b/reftable/basics.h
@@ -0,0 +1,37 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef BASICS_H
+#define BASICS_H
+
+#include "system.h"
+
+#include "reftable.h"
+
+#define true 1
+#define false 0
+
+void put_u24(byte *out, uint32_t i);
+uint32_t get_u24(byte *in);
+
+uint64_t get_u64(byte *in);
+void put_u64(byte *out, uint64_t i);
+
+void put_u32(byte *out, uint32_t i);
+uint32_t get_u32(byte *in);
+
+void put_u16(byte *out, uint16_t i);
+uint16_t get_u16(byte *in);
+int binsearch(int sz, int (*f)(int k, void *args), void *args);
+
+void free_names(char **a);
+void parse_names(char *buf, int size, char ***namesp);
+int names_equal(char **a, char **b);
+int names_length(char **names);
+
+#endif
diff --git a/reftable/block.c b/reftable/block.c
new file mode 100644
index 0000000000..ffbfee13c3
--- /dev/null
+++ b/reftable/block.c
@@ -0,0 +1,401 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "block.h"
+
+#include "system.h"
+
+#include "blocksource.h"
+#include "constants.h"
+#include "record.h"
+#include "reftable.h"
+#include "zlib.h"
+
+int block_writer_register_restart(struct block_writer *w, int n, bool restart,
+				  struct slice key);
+
+void block_writer_init(struct block_writer *bw, byte typ, byte *buf,
+		       uint32_t block_size, uint32_t header_off, int hash_size)
+{
+	bw->buf = buf;
+	bw->hash_size = hash_size;
+	bw->block_size = block_size;
+	bw->header_off = header_off;
+	bw->buf[header_off] = typ;
+	bw->next = header_off + 4;
+	bw->restart_interval = 16;
+	bw->entries = 0;
+}
+
+byte block_writer_type(struct block_writer *bw)
+{
+	return bw->buf[bw->header_off];
+}
+
+/* adds the record to the block. Returns -1 if it does not fit, 0 on
+   success */
+int block_writer_add(struct block_writer *w, struct record rec)
+{
+	struct slice empty = {};
+	struct slice last = w->entries % w->restart_interval == 0 ? empty :
+								    w->last_key;
+	struct slice out = {
+		.buf = w->buf + w->next,
+		.len = w->block_size - w->next,
+	};
+
+	struct slice start = out;
+
+	bool restart = false;
+	struct slice key = {};
+	int n = 0;
+
+	record_key(rec, &key);
+	n = encode_key(&restart, out, last, key, record_val_type(rec));
+	if (n < 0) {
+		goto err;
+	}
+	out.buf += n;
+	out.len -= n;
+
+	n = record_encode(rec, out, w->hash_size);
+	if (n < 0) {
+		goto err;
+	}
+
+	out.buf += n;
+	out.len -= n;
+
+	if (block_writer_register_restart(w, start.len - out.len, restart,
+					  key) < 0) {
+		goto err;
+	}
+
+	free(slice_yield(&key));
+	return 0;
+
+err:
+	free(slice_yield(&key));
+	return -1;
+}
+
+int block_writer_register_restart(struct block_writer *w, int n, bool restart,
+				  struct slice key)
+{
+	int rlen = w->restart_len;
+	if (rlen >= MAX_RESTARTS) {
+		restart = false;
+	}
+
+	if (restart) {
+		rlen++;
+	}
+	if (2 + 3 * rlen + n > w->block_size - w->next) {
+		return -1;
+	}
+	if (restart) {
+		if (w->restart_len == w->restart_cap) {
+			w->restart_cap = w->restart_cap * 2 + 1;
+			w->restarts = realloc(
+				w->restarts, sizeof(uint32_t) * w->restart_cap);
+		}
+
+		w->restarts[w->restart_len++] = w->next;
+	}
+
+	w->next += n;
+	slice_copy(&w->last_key, key);
+	w->entries++;
+	return 0;
+}
+
+int block_writer_finish(struct block_writer *w)
+{
+	int i = 0;
+	for (i = 0; i < w->restart_len; i++) {
+		put_u24(w->buf + w->next, w->restarts[i]);
+		w->next += 3;
+	}
+
+	put_u16(w->buf + w->next, w->restart_len);
+	w->next += 2;
+	put_u24(w->buf + 1 + w->header_off, w->next);
+
+	if (block_writer_type(w) == BLOCK_TYPE_LOG) {
+		int block_header_skip = 4 + w->header_off;
+		struct slice compressed = {};
+		uLongf dest_len = 0, src_len = 0;
+		slice_resize(&compressed, w->next - block_header_skip);
+
+		dest_len = compressed.len;
+		src_len = w->next - block_header_skip;
+
+		if (Z_OK != compress2(compressed.buf, &dest_len,
+				      w->buf + block_header_skip, src_len, 9)) {
+			free(slice_yield(&compressed));
+			return ZLIB_ERROR;
+		}
+		memcpy(w->buf + block_header_skip, compressed.buf, dest_len);
+		w->next = dest_len + block_header_skip;
+	}
+	return w->next;
+}
+
+byte block_reader_type(struct block_reader *r)
+{
+	return r->block.data[r->header_off];
+}
+
+int block_reader_init(struct block_reader *br, struct block *block,
+		      uint32_t header_off, uint32_t table_block_size,
+		      int hash_size)
+{
+	uint32_t full_block_size = table_block_size;
+	byte typ = block->data[header_off];
+	uint32_t sz = get_u24(block->data + header_off + 1);
+
+	if (!is_block_type(typ)) {
+		return FORMAT_ERROR;
+	}
+
+	if (typ == BLOCK_TYPE_LOG) {
+		struct slice uncompressed = {};
+		int block_header_skip = 4 + header_off;
+		uLongf dst_len = sz - block_header_skip;
+		uLongf src_len = block->len - block_header_skip;
+
+		slice_resize(&uncompressed, sz);
+		memcpy(uncompressed.buf, block->data, block_header_skip);
+
+		if (Z_OK != uncompress_return_consumed(
+				    uncompressed.buf + block_header_skip,
+				    &dst_len, block->data + block_header_skip,
+				    &src_len)) {
+			free(slice_yield(&uncompressed));
+			return ZLIB_ERROR;
+		}
+
+		block_source_return_block(block->source, block);
+		block->data = uncompressed.buf;
+		block->len = dst_len; /* XXX: 4 bytes missing? */
+		block->source = malloc_block_source();
+		full_block_size = src_len + block_header_skip;
+	} else if (full_block_size == 0) {
+		full_block_size = sz;
+	} else if (sz < full_block_size && sz < block->len &&
+		   block->data[sz] != 0) {
+		/* If the block is smaller than the full block size, it is
+		   padded (data followed by '\0') or the next block is
+		   unaligned. */
+		full_block_size = sz;
+	}
+
+	{
+		uint16_t restart_count = get_u16(block->data + sz - 2);
+		uint32_t restart_start = sz - 2 - 3 * restart_count;
+
+		byte *restart_bytes = block->data + restart_start;
+
+		/* transfer ownership. */
+		br->block = *block;
+		block->data = NULL;
+		block->len = 0;
+
+		br->hash_size = hash_size;
+		br->block_len = restart_start;
+		br->full_block_size = full_block_size;
+		br->header_off = header_off;
+		br->restart_count = restart_count;
+		br->restart_bytes = restart_bytes;
+	}
+
+	return 0;
+}
+
+static uint32_t block_reader_restart_offset(struct block_reader *br, int i)
+{
+	return get_u24(br->restart_bytes + 3 * i);
+}
+
+void block_reader_start(struct block_reader *br, struct block_iter *it)
+{
+	it->br = br;
+	slice_resize(&it->last_key, 0);
+	it->next_off = br->header_off + 4;
+}
+
+struct restart_find_args {
+	struct slice key;
+	struct block_reader *r;
+	int error;
+};
+
+static int restart_key_less(int idx, void *args)
+{
+	struct restart_find_args *a = (struct restart_find_args *)args;
+	uint32_t off = block_reader_restart_offset(a->r, idx);
+	struct slice in = {
+		.buf = a->r->block.data + off,
+		.len = a->r->block_len - off,
+	};
+
+	/* the restart key is verbatim in the block, so this could avoid the
+	   alloc for decoding the key */
+	struct slice rkey = {};
+	struct slice last_key = {};
+	byte unused_extra;
+	int n = decode_key(&rkey, &unused_extra, last_key, in);
+	if (n < 0) {
+		a->error = 1;
+		return -1;
+	}
+
+	{
+		int result = slice_compare(a->key, rkey);
+		free(slice_yield(&rkey));
+		return result;
+	}
+}
+
+void block_iter_copy_from(struct block_iter *dest, struct block_iter *src)
+{
+	dest->br = src->br;
+	dest->next_off = src->next_off;
+	slice_copy(&dest->last_key, src->last_key);
+}
+
+/* return < 0 for error, 0 for OK, > 0 for EOF. */
+int block_iter_next(struct block_iter *it, struct record rec)
+{
+	if (it->next_off >= it->br->block_len) {
+		return 1;
+	}
+
+	{
+		struct slice in = {
+			.buf = it->br->block.data + it->next_off,
+			.len = it->br->block_len - it->next_off,
+		};
+		struct slice start = in;
+		struct slice key = {};
+		byte extra;
+		int n = decode_key(&key, &extra, it->last_key, in);
+		if (n < 0) {
+			return -1;
+		}
+
+		in.buf += n;
+		in.len -= n;
+		n = record_decode(rec, key, extra, in, it->br->hash_size);
+		if (n < 0) {
+			return -1;
+		}
+		in.buf += n;
+		in.len -= n;
+
+		slice_copy(&it->last_key, key);
+		it->next_off += start.len - in.len;
+		free(slice_yield(&key));
+		return 0;
+	}
+}
+
+int block_reader_first_key(struct block_reader *br, struct slice *key)
+{
+	struct slice empty = {};
+	int off = br->header_off + 4;
+	struct slice in = {
+		.buf = br->block.data + off,
+		.len = br->block_len - off,
+	};
+
+	byte extra = 0;
+	int n = decode_key(key, &extra, empty, in);
+	if (n < 0) {
+		return n;
+	}
+	return 0;
+}
+
+int block_iter_seek(struct block_iter *it, struct slice want)
+{
+	return block_reader_seek(it->br, it, want);
+}
+
+void block_iter_close(struct block_iter *it)
+{
+	free(slice_yield(&it->last_key));
+}
+
+int block_reader_seek(struct block_reader *br, struct block_iter *it,
+		      struct slice want)
+{
+	struct restart_find_args args = {
+		.key = want,
+		.r = br,
+	};
+
+	int i = binsearch(br->restart_count, &restart_key_less, &args);
+	if (args.error) {
+		return -1;
+	}
+
+	it->br = br;
+	if (i > 0) {
+		i--;
+		it->next_off = block_reader_restart_offset(br, i);
+	} else {
+		it->next_off = br->header_off + 4;
+	}
+
+	{
+		struct record rec = new_record(block_reader_type(br));
+		struct slice key = {};
+		int result = 0;
+		int err = 0;
+		struct block_iter next = {};
+		while (true) {
+			block_iter_copy_from(&next, it);
+
+			err = block_iter_next(&next, rec);
+			if (err < 0) {
+				result = -1;
+				goto exit;
+			}
+
+			record_key(rec, &key);
+			if (err > 0 || slice_compare(key, want) >= 0) {
+				result = 0;
+				goto exit;
+			}
+
+			block_iter_copy_from(it, &next);
+		}
+
+	exit:
+		free(slice_yield(&key));
+		free(slice_yield(&next.last_key));
+		record_clear(rec);
+		free(record_yield(&rec));
+
+		return result;
+	}
+}
+
+void block_writer_reset(struct block_writer *bw)
+{
+	bw->restart_len = 0;
+	bw->last_key.len = 0;
+}
+
+void block_writer_clear(struct block_writer *bw)
+{
+	FREE_AND_NULL(bw->restarts);
+	free(slice_yield(&bw->last_key));
+	/* the block is not owned. */
+}
diff --git a/reftable/block.h b/reftable/block.h
new file mode 100644
index 0000000000..bb42588111
--- /dev/null
+++ b/reftable/block.h
@@ -0,0 +1,71 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef BLOCK_H
+#define BLOCK_H
+
+#include "basics.h"
+#include "record.h"
+#include "reftable.h"
+
+struct block_writer {
+	byte *buf;
+	uint32_t block_size;
+	uint32_t header_off;
+	int restart_interval;
+	int hash_size;
+
+	uint32_t next;
+	uint32_t *restarts;
+	uint32_t restart_len;
+	uint32_t restart_cap;
+	struct slice last_key;
+	int entries;
+};
+
+void block_writer_init(struct block_writer *bw, byte typ, byte *buf,
+		       uint32_t block_size, uint32_t header_off, int hash_size);
+byte block_writer_type(struct block_writer *bw);
+int block_writer_add(struct block_writer *w, struct record rec);
+int block_writer_finish(struct block_writer *w);
+void block_writer_reset(struct block_writer *bw);
+void block_writer_clear(struct block_writer *bw);
+
+struct block_reader {
+	uint32_t header_off;
+	struct block block;
+	int hash_size;
+
+	/* size of the data, excluding restart data. */
+	uint32_t block_len;
+	byte *restart_bytes;
+	uint32_t full_block_size;
+	uint16_t restart_count;
+};
+
+struct block_iter {
+	struct block_reader *br;
+	struct slice last_key;
+	uint32_t next_off;
+};
+
+int block_reader_init(struct block_reader *br, struct block *bl,
+		      uint32_t header_off, uint32_t table_block_size,
+		      int hash_size);
+void block_reader_start(struct block_reader *br, struct block_iter *it);
+int block_reader_seek(struct block_reader *br, struct block_iter *it,
+		      struct slice want);
+byte block_reader_type(struct block_reader *r);
+int block_reader_first_key(struct block_reader *br, struct slice *key);
+
+void block_iter_copy_from(struct block_iter *dest, struct block_iter *src);
+int block_iter_next(struct block_iter *it, struct record rec);
+int block_iter_seek(struct block_iter *it, struct slice want);
+void block_iter_close(struct block_iter *it);
+
+#endif
diff --git a/reftable/blocksource.h b/reftable/blocksource.h
new file mode 100644
index 0000000000..f3ad3a4c22
--- /dev/null
+++ b/reftable/blocksource.h
@@ -0,0 +1,20 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef BLOCKSOURCE_H
+#define BLOCKSOURCE_H
+
+#include "reftable.h"
+
+uint64_t block_source_size(struct block_source source);
+int block_source_read_block(struct block_source source, struct block *dest,
+			    uint64_t off, uint32_t size);
+void block_source_return_block(struct block_source source, struct block *ret);
+void block_source_close(struct block_source source);
+
+#endif
diff --git a/reftable/bytes.c b/reftable/bytes.c
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/reftable/config.h b/reftable/config.h
new file mode 100644
index 0000000000..40a8c178f1
--- /dev/null
+++ b/reftable/config.h
@@ -0,0 +1 @@
+/* empty */
diff --git a/reftable/constants.h b/reftable/constants.h
new file mode 100644
index 0000000000..cd35704610
--- /dev/null
+++ b/reftable/constants.h
@@ -0,0 +1,27 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef CONSTANTS_H
+#define CONSTANTS_H
+
+#define SHA1_SIZE 20
+#define SHA256_SIZE 32
+#define VERSION 1
+#define HEADER_SIZE 24
+#define FOOTER_SIZE 68
+
+#define BLOCK_TYPE_LOG 'g'
+#define BLOCK_TYPE_INDEX 'i'
+#define BLOCK_TYPE_REF 'r'
+#define BLOCK_TYPE_OBJ 'o'
+#define BLOCK_TYPE_ANY 0
+
+#define MAX_RESTARTS ((1 << 16) - 1)
+#define DEFAULT_BLOCK_SIZE 4096
+
+#endif
diff --git a/reftable/dump.c b/reftable/dump.c
new file mode 100644
index 0000000000..acabe18fbe
--- /dev/null
+++ b/reftable/dump.c
@@ -0,0 +1,97 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "system.h"
+
+#include "reftable.h"
+
+static int dump_table(const char *tablename)
+{
+	struct block_source src = {};
+	int err = block_source_from_file(&src, tablename);
+	if (err < 0) {
+		return err;
+	}
+
+	struct reader *r = NULL;
+	err = new_reader(&r, src, tablename);
+	if (err < 0) {
+		return err;
+	}
+
+	{
+		struct iterator it = {};
+		err = reader_seek_ref(r, &it, "");
+		if (err < 0) {
+			return err;
+		}
+
+		struct ref_record ref = {};
+		while (1) {
+			err = iterator_next_ref(it, &ref);
+			if (err > 0) {
+				break;
+			}
+			if (err < 0) {
+				return err;
+			}
+			ref_record_print(&ref, 20);
+		}
+		iterator_destroy(&it);
+		ref_record_clear(&ref);
+	}
+
+	{
+		struct iterator it = {};
+		err = reader_seek_log(r, &it, "");
+		if (err < 0) {
+			return err;
+		}
+		struct log_record log = {};
+		while (1) {
+			err = iterator_next_log(it, &log);
+			if (err > 0) {
+				break;
+			}
+			if (err < 0) {
+				return err;
+			}
+			log_record_print(&log, 20);
+		}
+		iterator_destroy(&it);
+		log_record_clear(&log);
+	}
+	return 0;
+}
+
+int main(int argc, char *argv[])
+{
+	int opt;
+	const char *table = NULL;
+	while ((opt = getopt(argc, argv, "t:")) != -1) {
+		switch (opt) {
+		case 't':
+			table = strdup(optarg);
+			break;
+		case '?':
+			printf("usage: %s [-table tablefile]\n", argv[0]);
+			return 2;
+			break;
+		}
+	}
+
+	if (table != NULL) {
+		int err = dump_table(table);
+		if (err < 0) {
+			fprintf(stderr, "%s: %s: %s\n", argv[0], table,
+				error_str(err));
+			return 1;
+		}
+	}
+	return 0;
+}
diff --git a/reftable/file.c b/reftable/file.c
new file mode 100644
index 0000000000..b2ea90bf94
--- /dev/null
+++ b/reftable/file.c
@@ -0,0 +1,97 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "system.h"
+
+#include "block.h"
+#include "iter.h"
+#include "record.h"
+#include "reftable.h"
+#include "tree.h"
+
+struct file_block_source {
+	int fd;
+	uint64_t size;
+};
+
+static uint64_t file_size(void *b)
+{
+	return ((struct file_block_source *)b)->size;
+}
+
+static void file_return_block(void *b, struct block *dest)
+{
+	memset(dest->data, 0xff, dest->len);
+	free(dest->data);
+}
+
+static void file_close(void *b)
+{
+	int fd = ((struct file_block_source *)b)->fd;
+	if (fd > 0) {
+		close(fd);
+		((struct file_block_source *)b)->fd = 0;
+	}
+
+	free(b);
+}
+
+static int file_read_block(void *v, struct block *dest, uint64_t off,
+			   uint32_t size)
+{
+	struct file_block_source *b = (struct file_block_source *)v;
+	assert(off + size <= b->size);
+	dest->data = malloc(size);
+	if (pread(b->fd, dest->data, size, off) != size) {
+		return -1;
+	}
+	dest->len = size;
+	return size;
+}
+
+struct block_source_vtable file_vtable = {
+	.size = &file_size,
+	.read_block = &file_read_block,
+	.return_block = &file_return_block,
+	.close = &file_close,
+};
+
+int block_source_from_file(struct block_source *bs, const char *name)
+{
+	struct stat st = {};
+	int err = 0;
+	int fd = open(name, O_RDONLY);
+	if (fd < 0) {
+		if (errno == ENOENT) {
+			return NOT_EXIST_ERROR;
+		}
+		return -1;
+	}
+
+	err = fstat(fd, &st);
+	if (err < 0) {
+		return -1;
+	}
+
+	{
+		struct file_block_source *p =
+			calloc(sizeof(struct file_block_source), 1);
+		p->size = st.st_size;
+		p->fd = fd;
+
+		bs->ops = &file_vtable;
+		bs->arg = p;
+	}
+	return 0;
+}
+
+int fd_writer(void *arg, byte *data, int sz)
+{
+	int *fdp = (int *)arg;
+	return write(*fdp, data, sz);
+}
diff --git a/reftable/iter.c b/reftable/iter.c
new file mode 100644
index 0000000000..c06c891366
--- /dev/null
+++ b/reftable/iter.c
@@ -0,0 +1,229 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "iter.h"
+
+#include "system.h"
+
+#include "block.h"
+#include "constants.h"
+#include "reader.h"
+#include "reftable.h"
+
+bool iterator_is_null(struct iterator it)
+{
+	return it.ops == NULL;
+}
+
+static int empty_iterator_next(void *arg, struct record rec)
+{
+	return 1;
+}
+
+static void empty_iterator_close(void *arg)
+{
+}
+
+struct iterator_vtable empty_vtable = {
+	.next = &empty_iterator_next,
+	.close = &empty_iterator_close,
+};
+
+void iterator_set_empty(struct iterator *it)
+{
+	it->iter_arg = NULL;
+	it->ops = &empty_vtable;
+}
+
+int iterator_next(struct iterator it, struct record rec)
+{
+	return it.ops->next(it.iter_arg, rec);
+}
+
+void iterator_destroy(struct iterator *it)
+{
+	if (it->ops == NULL) {
+		return;
+	}
+	it->ops->close(it->iter_arg);
+	it->ops = NULL;
+	FREE_AND_NULL(it->iter_arg);
+}
+
+int iterator_next_ref(struct iterator it, struct ref_record *ref)
+{
+	struct record rec = {};
+	record_from_ref(&rec, ref);
+	return iterator_next(it, rec);
+}
+
+int iterator_next_log(struct iterator it, struct log_record *log)
+{
+	struct record rec = {};
+	record_from_log(&rec, log);
+	return iterator_next(it, rec);
+}
+
+static void filtering_ref_iterator_close(void *iter_arg)
+{
+	struct filtering_ref_iterator *fri =
+		(struct filtering_ref_iterator *)iter_arg;
+	free(slice_yield(&fri->oid));
+	iterator_destroy(&fri->it);
+}
+
+static int filtering_ref_iterator_next(void *iter_arg, struct record rec)
+{
+	struct filtering_ref_iterator *fri =
+		(struct filtering_ref_iterator *)iter_arg;
+	struct ref_record *ref = (struct ref_record *)rec.data;
+
+	while (true) {
+		int err = iterator_next_ref(fri->it, ref);
+		if (err != 0) {
+			return err;
+		}
+
+		if (fri->double_check) {
+			struct iterator it = {};
+
+			int err = reader_seek_ref(fri->r, &it, ref->ref_name);
+			if (err == 0) {
+				err = iterator_next_ref(it, ref);
+			}
+
+			iterator_destroy(&it);
+
+			if (err < 0) {
+				return err;
+			}
+
+			if (err > 0) {
+				continue;
+			}
+		}
+
+		if ((ref->target_value != NULL &&
+		     !memcmp(fri->oid.buf, ref->target_value, fri->oid.len)) ||
+		    (ref->value != NULL &&
+		     !memcmp(fri->oid.buf, ref->value, fri->oid.len))) {
+			return 0;
+		}
+	}
+}
+
+struct iterator_vtable filtering_ref_iterator_vtable = {
+	.next = &filtering_ref_iterator_next,
+	.close = &filtering_ref_iterator_close,
+};
+
+void iterator_from_filtering_ref_iterator(struct iterator *it,
+					  struct filtering_ref_iterator *fri)
+{
+	it->iter_arg = fri;
+	it->ops = &filtering_ref_iterator_vtable;
+}
+
+static void indexed_table_ref_iter_close(void *p)
+{
+	struct indexed_table_ref_iter *it = (struct indexed_table_ref_iter *)p;
+	block_iter_close(&it->cur);
+	reader_return_block(it->r, &it->block_reader.block);
+	free(slice_yield(&it->oid));
+}
+
+static int indexed_table_ref_iter_next_block(struct indexed_table_ref_iter *it)
+{
+	if (it->offset_idx == it->offset_len) {
+		it->finished = true;
+		return 1;
+	}
+
+	reader_return_block(it->r, &it->block_reader.block);
+
+	{
+		uint64_t off = it->offsets[it->offset_idx++];
+		int err = reader_init_block_reader(it->r, &it->block_reader,
+						   off, BLOCK_TYPE_REF);
+		if (err < 0) {
+			return err;
+		}
+		if (err > 0) {
+			/* indexed block does not exist. */
+			return FORMAT_ERROR;
+		}
+	}
+	block_reader_start(&it->block_reader, &it->cur);
+	return 0;
+}
+
+static int indexed_table_ref_iter_next(void *p, struct record rec)
+{
+	struct indexed_table_ref_iter *it = (struct indexed_table_ref_iter *)p;
+	struct ref_record *ref = (struct ref_record *)rec.data;
+
+	while (true) {
+		int err = block_iter_next(&it->cur, rec);
+		if (err < 0) {
+			return err;
+		}
+
+		if (err > 0) {
+			err = indexed_table_ref_iter_next_block(it);
+			if (err < 0) {
+				return err;
+			}
+
+			if (it->finished) {
+				return 1;
+			}
+			continue;
+		}
+
+		if (!memcmp(it->oid.buf, ref->target_value, it->oid.len) ||
+		    !memcmp(it->oid.buf, ref->value, it->oid.len)) {
+			return 0;
+		}
+	}
+}
+
+int new_indexed_table_ref_iter(struct indexed_table_ref_iter **dest,
+			       struct reader *r, byte *oid, int oid_len,
+			       uint64_t *offsets, int offset_len)
+{
+	struct indexed_table_ref_iter *itr =
+		calloc(sizeof(struct indexed_table_ref_iter), 1);
+	int err = 0;
+
+	itr->r = r;
+	slice_resize(&itr->oid, oid_len);
+	memcpy(itr->oid.buf, oid, oid_len);
+
+	itr->offsets = offsets;
+	itr->offset_len = offset_len;
+
+	err = indexed_table_ref_iter_next_block(itr);
+	if (err < 0) {
+		free(itr);
+	} else {
+		*dest = itr;
+	}
+	return err;
+}
+
+struct iterator_vtable indexed_table_ref_iter_vtable = {
+	.next = &indexed_table_ref_iter_next,
+	.close = &indexed_table_ref_iter_close,
+};
+
+void iterator_from_indexed_table_ref_iter(struct iterator *it,
+					  struct indexed_table_ref_iter *itr)
+{
+	it->iter_arg = itr;
+	it->ops = &indexed_table_ref_iter_vtable;
+}
diff --git a/reftable/iter.h b/reftable/iter.h
new file mode 100644
index 0000000000..f497f2a27e
--- /dev/null
+++ b/reftable/iter.h
@@ -0,0 +1,56 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef ITER_H
+#define ITER_H
+
+#include "block.h"
+#include "record.h"
+#include "slice.h"
+
+struct iterator_vtable {
+	int (*next)(void *iter_arg, struct record rec);
+	void (*close)(void *iter_arg);
+};
+
+void iterator_set_empty(struct iterator *it);
+int iterator_next(struct iterator it, struct record rec);
+bool iterator_is_null(struct iterator it);
+
+struct filtering_ref_iterator {
+	struct reader *r;
+	struct slice oid;
+	bool double_check;
+	struct iterator it;
+};
+
+void iterator_from_filtering_ref_iterator(struct iterator *,
+					  struct filtering_ref_iterator *);
+
+struct indexed_table_ref_iter {
+	struct reader *r;
+	struct slice oid;
+
+	/* mutable */
+	uint64_t *offsets;
+
+	/* Points to the next offset to read. */
+	int offset_idx;
+	int offset_len;
+	struct block_reader block_reader;
+	struct block_iter cur;
+	bool finished;
+};
+
+void iterator_from_indexed_table_ref_iter(struct iterator *it,
+					  struct indexed_table_ref_iter *itr);
+int new_indexed_table_ref_iter(struct indexed_table_ref_iter **dest,
+			       struct reader *r, byte *oid, int oid_len,
+			       uint64_t *offsets, int offset_len);
+
+#endif
diff --git a/reftable/merged.c b/reftable/merged.c
new file mode 100644
index 0000000000..7d52ec6232
--- /dev/null
+++ b/reftable/merged.c
@@ -0,0 +1,286 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "merged.h"
+
+#include "system.h"
+
+#include "constants.h"
+#include "iter.h"
+#include "pq.h"
+#include "reader.h"
+
+static int merged_iter_init(struct merged_iter *mi)
+{
+	int i = 0;
+	for (i = 0; i < mi->stack_len; i++) {
+		struct record rec = new_record(mi->typ);
+		int err = iterator_next(mi->stack[i], rec);
+		if (err < 0) {
+			return err;
+		}
+
+		if (err > 0) {
+			iterator_destroy(&mi->stack[i]);
+			record_clear(rec);
+			free(record_yield(&rec));
+		} else {
+			struct pq_entry e = {
+				.rec = rec,
+				.index = i,
+			};
+			merged_iter_pqueue_add(&mi->pq, e);
+		}
+	}
+
+	return 0;
+}
+
+static void merged_iter_close(void *p)
+{
+	struct merged_iter *mi = (struct merged_iter *)p;
+	int i = 0;
+	merged_iter_pqueue_clear(&mi->pq);
+	for (i = 0; i < mi->stack_len; i++) {
+		iterator_destroy(&mi->stack[i]);
+	}
+	free(mi->stack);
+}
+
+static int merged_iter_advance_subiter(struct merged_iter *mi, int idx)
+{
+	if (iterator_is_null(mi->stack[idx])) {
+		return 0;
+	}
+
+	{
+		struct record rec = new_record(mi->typ);
+		struct pq_entry e = {
+			.rec = rec,
+			.index = idx,
+		};
+		int err = iterator_next(mi->stack[idx], rec);
+		if (err < 0) {
+			return err;
+		}
+
+		if (err > 0) {
+			iterator_destroy(&mi->stack[idx]);
+			record_clear(rec);
+			free(record_yield(&rec));
+			return 0;
+		}
+
+		merged_iter_pqueue_add(&mi->pq, e);
+	}
+	return 0;
+}
+
+static int merged_iter_next(struct merged_iter *mi, struct record rec)
+{
+	struct slice entry_key = {};
+	struct pq_entry entry = merged_iter_pqueue_remove(&mi->pq);
+	int err = merged_iter_advance_subiter(mi, entry.index);
+	if (err < 0) {
+		return err;
+	}
+
+	record_key(entry.rec, &entry_key);
+	while (!merged_iter_pqueue_is_empty(mi->pq)) {
+		struct pq_entry top = merged_iter_pqueue_top(mi->pq);
+		struct slice k = {};
+		int err = 0, cmp = 0;
+
+		record_key(top.rec, &k);
+
+		cmp = slice_compare(k, entry_key);
+		free(slice_yield(&k));
+
+		if (cmp > 0) {
+			break;
+		}
+
+		merged_iter_pqueue_remove(&mi->pq);
+		err = merged_iter_advance_subiter(mi, top.index);
+		if (err < 0) {
+			return err;
+		}
+		record_clear(top.rec);
+		free(record_yield(&top.rec));
+	}
+
+	record_copy_from(rec, entry.rec, mi->hash_size);
+	record_clear(entry.rec);
+	free(record_yield(&entry.rec));
+	free(slice_yield(&entry_key));
+	return 0;
+}
+
+static int merged_iter_next_void(void *p, struct record rec)
+{
+	struct merged_iter *mi = (struct merged_iter *)p;
+	if (merged_iter_pqueue_is_empty(mi->pq)) {
+		return 1;
+	}
+
+	return merged_iter_next(mi, rec);
+}
+
+struct iterator_vtable merged_iter_vtable = {
+	.next = &merged_iter_next_void,
+	.close = &merged_iter_close,
+};
+
+static void iterator_from_merged_iter(struct iterator *it,
+				      struct merged_iter *mi)
+{
+	it->iter_arg = mi;
+	it->ops = &merged_iter_vtable;
+}
+
+int new_merged_table(struct merged_table **dest, struct reader **stack, int n)
+{
+	uint64_t last_max = 0;
+	uint64_t first_min = 0;
+	int i = 0;
+	for (i = 0; i < n; i++) {
+		struct reader *r = stack[i];
+		if (i > 0 && last_max >= reader_min_update_index(r)) {
+			return FORMAT_ERROR;
+		}
+		if (i == 0) {
+			first_min = reader_min_update_index(r);
+		}
+
+		last_max = reader_max_update_index(r);
+	}
+
+	{
+		struct merged_table m = {
+			.stack = stack,
+			.stack_len = n,
+			.min = first_min,
+			.max = last_max,
+			.hash_size = SHA1_SIZE,
+		};
+
+		*dest = calloc(sizeof(struct merged_table), 1);
+		**dest = m;
+	}
+	return 0;
+}
+
+void merged_table_close(struct merged_table *mt)
+{
+	int i = 0;
+	for (i = 0; i < mt->stack_len; i++) {
+		reader_free(mt->stack[i]);
+	}
+	FREE_AND_NULL(mt->stack);
+	mt->stack_len = 0;
+}
+
+/* clears the list of subtable, without affecting the readers themselves. */
+void merged_table_clear(struct merged_table *mt)
+{
+	FREE_AND_NULL(mt->stack);
+	mt->stack_len = 0;
+}
+
+void merged_table_free(struct merged_table *mt)
+{
+	if (mt == NULL) {
+		return;
+	}
+	merged_table_clear(mt);
+	free(mt);
+}
+
+uint64_t merged_max_update_index(struct merged_table *mt)
+{
+	return mt->max;
+}
+
+uint64_t merged_min_update_index(struct merged_table *mt)
+{
+	return mt->min;
+}
+
+static int merged_table_seek_record(struct merged_table *mt,
+				    struct iterator *it, struct record rec)
+{
+	struct iterator *iters = calloc(sizeof(struct iterator), mt->stack_len);
+	struct merged_iter merged = {
+		.stack = iters,
+		.typ = record_type(rec),
+		.hash_size = mt->hash_size,
+	};
+	int n = 0;
+	int err = 0;
+	int i = 0;
+	for (i = 0; i < mt->stack_len && err == 0; i++) {
+		int e = reader_seek(mt->stack[i], &iters[n], rec);
+		if (e < 0) {
+			err = e;
+		}
+		if (e == 0) {
+			n++;
+		}
+	}
+	if (err < 0) {
+		int i = 0;
+		for (i = 0; i < n; i++) {
+			iterator_destroy(&iters[i]);
+		}
+		free(iters);
+		return err;
+	}
+
+	merged.stack_len = n, err = merged_iter_init(&merged);
+	if (err < 0) {
+		merged_iter_close(&merged);
+		return err;
+	}
+
+	{
+		struct merged_iter *p = malloc(sizeof(struct merged_iter));
+		*p = merged;
+		iterator_from_merged_iter(it, p);
+	}
+	return 0;
+}
+
+int merged_table_seek_ref(struct merged_table *mt, struct iterator *it,
+			  const char *name)
+{
+	struct ref_record ref = {
+		.ref_name = (char *)name,
+	};
+	struct record rec = {};
+	record_from_ref(&rec, &ref);
+	return merged_table_seek_record(mt, it, rec);
+}
+
+int merged_table_seek_log_at(struct merged_table *mt, struct iterator *it,
+			     const char *name, uint64_t update_index)
+{
+	struct log_record log = {
+		.ref_name = (char *)name,
+		.update_index = update_index,
+	};
+	struct record rec = {};
+	record_from_log(&rec, &log);
+	return merged_table_seek_record(mt, it, rec);
+}
+
+int merged_table_seek_log(struct merged_table *mt, struct iterator *it,
+			  const char *name)
+{
+	uint64_t max = ~((uint64_t)0);
+	return merged_table_seek_log_at(mt, it, name, max);
+}
diff --git a/reftable/merged.h b/reftable/merged.h
new file mode 100644
index 0000000000..b8d3572e26
--- /dev/null
+++ b/reftable/merged.h
@@ -0,0 +1,34 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef MERGED_H
+#define MERGED_H
+
+#include "pq.h"
+#include "reftable.h"
+
+struct merged_table {
+	struct reader **stack;
+	int stack_len;
+	int hash_size;
+
+	uint64_t min;
+	uint64_t max;
+};
+
+struct merged_iter {
+	struct iterator *stack;
+	int hash_size;
+	int stack_len;
+	byte typ;
+	struct merged_iter_pqueue pq;
+} merged_iter;
+
+void merged_table_clear(struct merged_table *mt);
+
+#endif
diff --git a/reftable/pq.c b/reftable/pq.c
new file mode 100644
index 0000000000..a1aff7c98c
--- /dev/null
+++ b/reftable/pq.c
@@ -0,0 +1,114 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "pq.h"
+
+#include "system.h"
+
+int pq_less(struct pq_entry a, struct pq_entry b)
+{
+	struct slice ak = {};
+	struct slice bk = {};
+	int cmp = 0;
+	record_key(a.rec, &ak);
+	record_key(b.rec, &bk);
+
+	cmp = slice_compare(ak, bk);
+
+	free(slice_yield(&ak));
+	free(slice_yield(&bk));
+
+	if (cmp == 0) {
+		return a.index > b.index;
+	}
+
+	return cmp < 0;
+}
+
+struct pq_entry merged_iter_pqueue_top(struct merged_iter_pqueue pq)
+{
+	return pq.heap[0];
+}
+
+bool merged_iter_pqueue_is_empty(struct merged_iter_pqueue pq)
+{
+	return pq.len == 0;
+}
+
+void merged_iter_pqueue_check(struct merged_iter_pqueue pq)
+{
+	int i = 0;
+	for (i = 1; i < pq.len; i++) {
+		int parent = (i - 1) / 2;
+
+		assert(pq_less(pq.heap[parent], pq.heap[i]));
+	}
+}
+
+struct pq_entry merged_iter_pqueue_remove(struct merged_iter_pqueue *pq)
+{
+	int i = 0;
+	struct pq_entry e = pq->heap[0];
+	pq->heap[0] = pq->heap[pq->len - 1];
+	pq->len--;
+
+	i = 0;
+	while (i < pq->len) {
+		int min = i;
+		int j = 2 * i + 1;
+		int k = 2 * i + 2;
+		if (j < pq->len && pq_less(pq->heap[j], pq->heap[i])) {
+			min = j;
+		}
+		if (k < pq->len && pq_less(pq->heap[k], pq->heap[min])) {
+			min = k;
+		}
+
+		if (min == i) {
+			break;
+		}
+
+		SWAP(pq->heap[i], pq->heap[min]);
+		i = min;
+	}
+
+	return e;
+}
+
+void merged_iter_pqueue_add(struct merged_iter_pqueue *pq, struct pq_entry e)
+{
+	int i = 0;
+	if (pq->len == pq->cap) {
+		pq->cap = 2 * pq->cap + 1;
+		pq->heap = realloc(pq->heap, pq->cap * sizeof(struct pq_entry));
+	}
+
+	pq->heap[pq->len++] = e;
+	i = pq->len - 1;
+	while (i > 0) {
+		int j = (i - 1) / 2;
+		if (pq_less(pq->heap[j], pq->heap[i])) {
+			break;
+		}
+
+		SWAP(pq->heap[j], pq->heap[i]);
+
+		i = j;
+	}
+}
+
+void merged_iter_pqueue_clear(struct merged_iter_pqueue *pq)
+{
+	int i = 0;
+	for (i = 0; i < pq->len; i++) {
+		record_clear(pq->heap[i].rec);
+		free(record_yield(&pq->heap[i].rec));
+	}
+	FREE_AND_NULL(pq->heap);
+	pq->len = pq->cap = 0;
+}
diff --git a/reftable/pq.h b/reftable/pq.h
new file mode 100644
index 0000000000..5f7018979d
--- /dev/null
+++ b/reftable/pq.h
@@ -0,0 +1,34 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef PQ_H
+#define PQ_H
+
+#include "record.h"
+
+struct pq_entry {
+	struct record rec;
+	int index;
+};
+
+int pq_less(struct pq_entry a, struct pq_entry b);
+
+struct merged_iter_pqueue {
+	struct pq_entry *heap;
+	int len;
+	int cap;
+};
+
+struct pq_entry merged_iter_pqueue_top(struct merged_iter_pqueue pq);
+bool merged_iter_pqueue_is_empty(struct merged_iter_pqueue pq);
+void merged_iter_pqueue_check(struct merged_iter_pqueue pq);
+struct pq_entry merged_iter_pqueue_remove(struct merged_iter_pqueue *pq);
+void merged_iter_pqueue_add(struct merged_iter_pqueue *pq, struct pq_entry e);
+void merged_iter_pqueue_clear(struct merged_iter_pqueue *pq);
+
+#endif
diff --git a/reftable/reader.c b/reftable/reader.c
new file mode 100644
index 0000000000..981911f076
--- /dev/null
+++ b/reftable/reader.c
@@ -0,0 +1,708 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "reader.h"
+
+#include "system.h"
+
+#include "block.h"
+#include "constants.h"
+#include "iter.h"
+#include "record.h"
+#include "reftable.h"
+#include "tree.h"
+
+uint64_t block_source_size(struct block_source source)
+{
+	return source.ops->size(source.arg);
+}
+
+int block_source_read_block(struct block_source source, struct block *dest,
+			    uint64_t off, uint32_t size)
+{
+	int result = source.ops->read_block(source.arg, dest, off, size);
+	dest->source = source;
+	return result;
+}
+
+void block_source_return_block(struct block_source source, struct block *blockp)
+{
+	source.ops->return_block(source.arg, blockp);
+	blockp->data = NULL;
+	blockp->len = 0;
+	blockp->source.ops = NULL;
+	blockp->source.arg = NULL;
+}
+
+void block_source_close(struct block_source *source)
+{
+	if (source->ops == NULL) {
+		return;
+	}
+
+	source->ops->close(source->arg);
+	source->ops = NULL;
+}
+
+static struct reader_offsets *reader_offsets_for(struct reader *r, byte typ)
+{
+	switch (typ) {
+	case BLOCK_TYPE_REF:
+		return &r->ref_offsets;
+	case BLOCK_TYPE_LOG:
+		return &r->log_offsets;
+	case BLOCK_TYPE_OBJ:
+		return &r->obj_offsets;
+	}
+	abort();
+}
+
+static int reader_get_block(struct reader *r, struct block *dest, uint64_t off,
+			    uint32_t sz)
+{
+	if (off >= r->size) {
+		return 0;
+	}
+
+	if (off + sz > r->size) {
+		sz = r->size - off;
+	}
+
+	return block_source_read_block(r->source, dest, off, sz);
+}
+
+void reader_return_block(struct reader *r, struct block *p)
+{
+	block_source_return_block(r->source, p);
+}
+
+const char *reader_name(struct reader *r)
+{
+	return r->name;
+}
+
+static int parse_footer(struct reader *r, byte *footer, byte *header)
+{
+	byte *f = footer;
+	int err = 0;
+	if (memcmp(f, "REFT", 4)) {
+		err = FORMAT_ERROR;
+		goto exit;
+	}
+	f += 4;
+
+	if (memcmp(footer, header, HEADER_SIZE)) {
+		err = FORMAT_ERROR;
+		goto exit;
+	}
+
+	{
+		byte version = *f++;
+		if (version != 1) {
+			err = FORMAT_ERROR;
+			goto exit;
+		}
+	}
+
+	r->block_size = get_u24(f);
+
+	f += 3;
+	r->min_update_index = get_u64(f);
+	f += 8;
+	r->max_update_index = get_u64(f);
+	f += 8;
+
+	r->ref_offsets.index_offset = get_u64(f);
+	f += 8;
+
+	r->obj_offsets.offset = get_u64(f);
+	f += 8;
+
+	r->object_id_len = r->obj_offsets.offset & ((1 << 5) - 1);
+	r->obj_offsets.offset >>= 5;
+
+	r->obj_offsets.index_offset = get_u64(f);
+	f += 8;
+	r->log_offsets.offset = get_u64(f);
+	f += 8;
+	r->log_offsets.index_offset = get_u64(f);
+	f += 8;
+
+	{
+		uint32_t computed_crc = crc32(0, footer, f - footer);
+		uint32_t file_crc = get_u32(f);
+		f += 4;
+		if (computed_crc != file_crc) {
+			err = FORMAT_ERROR;
+			goto exit;
+		}
+	}
+
+	{
+		byte first_block_typ = header[HEADER_SIZE];
+		r->ref_offsets.present = (first_block_typ == BLOCK_TYPE_REF);
+		r->ref_offsets.offset = 0;
+		r->log_offsets.present = (first_block_typ == BLOCK_TYPE_LOG ||
+					  r->log_offsets.offset > 0);
+		r->obj_offsets.present = r->obj_offsets.offset > 0;
+	}
+	err = 0;
+exit:
+	return err;
+}
+
+int init_reader(struct reader *r, struct block_source source, const char *name)
+{
+	struct block footer = {};
+	struct block header = {};
+	int err = 0;
+
+	memset(r, 0, sizeof(struct reader));
+	r->size = block_source_size(source) - FOOTER_SIZE;
+	r->source = source;
+	r->name = strdup(name);
+	r->hash_size = SHA1_SIZE;
+
+	err = block_source_read_block(source, &footer, r->size, FOOTER_SIZE);
+	if (err != FOOTER_SIZE) {
+		err = IO_ERROR;
+		goto exit;
+	}
+
+	/* Need +1 to read type of first block. */
+	err = reader_get_block(r, &header, 0, HEADER_SIZE + 1);
+	if (err != HEADER_SIZE + 1) {
+		err = IO_ERROR;
+		goto exit;
+	}
+
+	err = parse_footer(r, footer.data, header.data);
+exit:
+	block_source_return_block(r->source, &footer);
+	block_source_return_block(r->source, &header);
+	return err;
+}
+
+struct table_iter {
+	struct reader *r;
+	byte typ;
+	uint64_t block_off;
+	struct block_iter bi;
+	bool finished;
+};
+
+static void table_iter_copy_from(struct table_iter *dest,
+				 struct table_iter *src)
+{
+	dest->r = src->r;
+	dest->typ = src->typ;
+	dest->block_off = src->block_off;
+	dest->finished = src->finished;
+	block_iter_copy_from(&dest->bi, &src->bi);
+}
+
+static int table_iter_next_in_block(struct table_iter *ti, struct record rec)
+{
+	int res = block_iter_next(&ti->bi, rec);
+	if (res == 0 && record_type(rec) == BLOCK_TYPE_REF) {
+		((struct ref_record *)rec.data)->update_index +=
+			ti->r->min_update_index;
+	}
+
+	return res;
+}
+
+static void table_iter_block_done(struct table_iter *ti)
+{
+	if (ti->bi.br == NULL) {
+		return;
+	}
+	reader_return_block(ti->r, &ti->bi.br->block);
+	FREE_AND_NULL(ti->bi.br);
+
+	ti->bi.last_key.len = 0;
+	ti->bi.next_off = 0;
+}
+
+static int32_t extract_block_size(byte *data, byte *typ, uint64_t off)
+{
+	int32_t result = 0;
+
+	if (off == 0) {
+		data += 24;
+	}
+
+	*typ = data[0];
+	if (is_block_type(*typ)) {
+		result = get_u24(data + 1);
+	}
+	return result;
+}
+
+int reader_init_block_reader(struct reader *r, struct block_reader *br,
+			     uint64_t next_off, byte want_typ)
+{
+	int32_t guess_block_size = r->block_size ? r->block_size :
+						   DEFAULT_BLOCK_SIZE;
+	struct block block = {};
+	byte block_typ = 0;
+	int err = 0;
+	uint32_t header_off = next_off ? 0 : HEADER_SIZE;
+	int32_t block_size = 0;
+
+	if (next_off >= r->size) {
+		return 1;
+	}
+
+	err = reader_get_block(r, &block, next_off, guess_block_size);
+	if (err < 0) {
+		return err;
+	}
+
+	block_size = extract_block_size(block.data, &block_typ, next_off);
+	if (block_size < 0) {
+		return block_size;
+	}
+
+	if (want_typ != BLOCK_TYPE_ANY && block_typ != want_typ) {
+		reader_return_block(r, &block);
+		return 1;
+	}
+
+	if (block_size > guess_block_size) {
+		reader_return_block(r, &block);
+		err = reader_get_block(r, &block, next_off, block_size);
+		if (err < 0) {
+			return err;
+		}
+	}
+
+	return block_reader_init(br, &block, header_off, r->block_size,
+				 r->hash_size);
+}
+
+static int table_iter_next_block(struct table_iter *dest,
+				 struct table_iter *src)
+{
+	uint64_t next_block_off = src->block_off + src->bi.br->full_block_size;
+	struct block_reader br = {};
+	int err = 0;
+
+	dest->r = src->r;
+	dest->typ = src->typ;
+	dest->block_off = next_block_off;
+
+	err = reader_init_block_reader(src->r, &br, next_block_off, src->typ);
+	if (err > 0) {
+		dest->finished = true;
+		return 1;
+	}
+	if (err != 0) {
+		return err;
+	}
+
+	{
+		struct block_reader *brp = malloc(sizeof(struct block_reader));
+		*brp = br;
+
+		dest->finished = false;
+		block_reader_start(brp, &dest->bi);
+	}
+	return 0;
+}
+
+static int table_iter_next(struct table_iter *ti, struct record rec)
+{
+	if (record_type(rec) != ti->typ) {
+		return API_ERROR;
+	}
+
+	while (true) {
+		struct table_iter next = {};
+		int err = 0;
+		if (ti->finished) {
+			return 1;
+		}
+
+		err = table_iter_next_in_block(ti, rec);
+		if (err <= 0) {
+			return err;
+		}
+
+		err = table_iter_next_block(&next, ti);
+		if (err != 0) {
+			ti->finished = true;
+		}
+		table_iter_block_done(ti);
+		if (err != 0) {
+			return err;
+		}
+		table_iter_copy_from(ti, &next);
+		block_iter_close(&next.bi);
+	}
+}
+
+static int table_iter_next_void(void *ti, struct record rec)
+{
+	return table_iter_next((struct table_iter *)ti, rec);
+}
+
+static void table_iter_close(void *p)
+{
+	struct table_iter *ti = (struct table_iter *)p;
+	table_iter_block_done(ti);
+	block_iter_close(&ti->bi);
+}
+
+struct iterator_vtable table_iter_vtable = {
+	.next = &table_iter_next_void,
+	.close = &table_iter_close,
+};
+
+static void iterator_from_table_iter(struct iterator *it, struct table_iter *ti)
+{
+	it->iter_arg = ti;
+	it->ops = &table_iter_vtable;
+}
+
+static int reader_table_iter_at(struct reader *r, struct table_iter *ti,
+				uint64_t off, byte typ)
+{
+	struct block_reader br = {};
+	struct block_reader *brp = NULL;
+
+	int err = reader_init_block_reader(r, &br, off, typ);
+	if (err != 0) {
+		return err;
+	}
+
+	brp = malloc(sizeof(struct block_reader));
+	*brp = br;
+	ti->r = r;
+	ti->typ = block_reader_type(brp);
+	ti->block_off = off;
+	block_reader_start(brp, &ti->bi);
+	return 0;
+}
+
+static int reader_start(struct reader *r, struct table_iter *ti, byte typ,
+			bool index)
+{
+	struct reader_offsets *offs = reader_offsets_for(r, typ);
+	uint64_t off = offs->offset;
+	if (index) {
+		off = offs->index_offset;
+		if (off == 0) {
+			return 1;
+		}
+		typ = BLOCK_TYPE_INDEX;
+	}
+
+	return reader_table_iter_at(r, ti, off, typ);
+}
+
+static int reader_seek_linear(struct reader *r, struct table_iter *ti,
+			      struct record want)
+{
+	struct record rec = new_record(record_type(want));
+	struct slice want_key = {};
+	struct slice got_key = {};
+	struct table_iter next = {};
+	int err = -1;
+	record_key(want, &want_key);
+
+	while (true) {
+		err = table_iter_next_block(&next, ti);
+		if (err < 0) {
+			goto exit;
+		}
+
+		if (err > 0) {
+			break;
+		}
+
+		err = block_reader_first_key(next.bi.br, &got_key);
+		if (err < 0) {
+			goto exit;
+		}
+		{
+			int cmp = slice_compare(got_key, want_key);
+			if (cmp > 0) {
+				table_iter_block_done(&next);
+				break;
+			}
+		}
+
+		table_iter_block_done(ti);
+		table_iter_copy_from(ti, &next);
+	}
+
+	err = block_iter_seek(&ti->bi, want_key);
+	if (err < 0) {
+		goto exit;
+	}
+	err = 0;
+
+exit:
+	block_iter_close(&next.bi);
+	record_clear(rec);
+	free(record_yield(&rec));
+	free(slice_yield(&want_key));
+	free(slice_yield(&got_key));
+	return err;
+}
+
+static int reader_seek_indexed(struct reader *r, struct iterator *it,
+			       struct record rec)
+{
+	struct index_record want_index = {};
+	struct record want_index_rec = {};
+	struct index_record index_result = {};
+	struct record index_result_rec = {};
+	struct table_iter index_iter = {};
+	struct table_iter next = {};
+	int err = 0;
+
+	record_key(rec, &want_index.last_key);
+	record_from_index(&want_index_rec, &want_index);
+	record_from_index(&index_result_rec, &index_result);
+
+	err = reader_start(r, &index_iter, record_type(rec), true);
+	if (err < 0) {
+		goto exit;
+	}
+
+	err = reader_seek_linear(r, &index_iter, want_index_rec);
+	while (true) {
+		err = table_iter_next(&index_iter, index_result_rec);
+		table_iter_block_done(&index_iter);
+		if (err != 0) {
+			goto exit;
+		}
+
+		err = reader_table_iter_at(r, &next, index_result.offset, 0);
+		if (err != 0) {
+			goto exit;
+		}
+
+		err = block_iter_seek(&next.bi, want_index.last_key);
+		if (err < 0) {
+			goto exit;
+		}
+
+		if (next.typ == record_type(rec)) {
+			err = 0;
+			break;
+		}
+
+		if (next.typ != BLOCK_TYPE_INDEX) {
+			err = FORMAT_ERROR;
+			break;
+		}
+
+		table_iter_copy_from(&index_iter, &next);
+	}
+
+	if (err == 0) {
+		struct table_iter *malloced =
+			calloc(sizeof(struct table_iter), 1);
+		table_iter_copy_from(malloced, &next);
+		iterator_from_table_iter(it, malloced);
+	}
+exit:
+	block_iter_close(&next.bi);
+	table_iter_close(&index_iter);
+	record_clear(want_index_rec);
+	record_clear(index_result_rec);
+	return err;
+}
+
+static int reader_seek_internal(struct reader *r, struct iterator *it,
+				struct record rec)
+{
+	struct reader_offsets *offs = reader_offsets_for(r, record_type(rec));
+	uint64_t idx = offs->index_offset;
+	struct table_iter ti = {};
+	int err = 0;
+	if (idx > 0) {
+		return reader_seek_indexed(r, it, rec);
+	}
+
+	err = reader_start(r, &ti, record_type(rec), false);
+	if (err < 0) {
+		return err;
+	}
+	err = reader_seek_linear(r, &ti, rec);
+	if (err < 0) {
+		return err;
+	}
+
+	{
+		struct table_iter *p = malloc(sizeof(struct table_iter));
+		*p = ti;
+		iterator_from_table_iter(it, p);
+	}
+
+	return 0;
+}
+
+int reader_seek(struct reader *r, struct iterator *it, struct record rec)
+{
+	byte typ = record_type(rec);
+
+	struct reader_offsets *offs = reader_offsets_for(r, typ);
+	if (!offs->present) {
+		iterator_set_empty(it);
+		return 0;
+	}
+
+	return reader_seek_internal(r, it, rec);
+}
+
+int reader_seek_ref(struct reader *r, struct iterator *it, const char *name)
+{
+	struct ref_record ref = {
+		.ref_name = (char *)name,
+	};
+	struct record rec = {};
+	record_from_ref(&rec, &ref);
+	return reader_seek(r, it, rec);
+}
+
+int reader_seek_log_at(struct reader *r, struct iterator *it, const char *name,
+		       uint64_t update_index)
+{
+	struct log_record log = {
+		.ref_name = (char *)name,
+		.update_index = update_index,
+	};
+	struct record rec = {};
+	record_from_log(&rec, &log);
+	return reader_seek(r, it, rec);
+}
+
+int reader_seek_log(struct reader *r, struct iterator *it, const char *name)
+{
+	uint64_t max = ~((uint64_t)0);
+	return reader_seek_log_at(r, it, name, max);
+}
+
+void reader_close(struct reader *r)
+{
+	block_source_close(&r->source);
+	FREE_AND_NULL(r->name);
+}
+
+int new_reader(struct reader **p, struct block_source src, char const *name)
+{
+	struct reader *rd = calloc(sizeof(struct reader), 1);
+	int err = init_reader(rd, src, name);
+	if (err == 0) {
+		*p = rd;
+	} else {
+		free(rd);
+	}
+	return err;
+}
+
+void reader_free(struct reader *r)
+{
+	reader_close(r);
+	free(r);
+}
+
+static int reader_refs_for_indexed(struct reader *r, struct iterator *it,
+				   byte *oid)
+{
+	struct obj_record want = {
+		.hash_prefix = oid,
+		.hash_prefix_len = r->object_id_len,
+	};
+	struct record want_rec = {};
+	struct iterator oit = {};
+	struct obj_record got = {};
+	struct record got_rec = {};
+	int err = 0;
+
+	record_from_obj(&want_rec, &want);
+
+	err = reader_seek(r, &oit, want_rec);
+	if (err != 0) {
+		return err;
+	}
+
+	record_from_obj(&got_rec, &got);
+	err = iterator_next(oit, got_rec);
+	iterator_destroy(&oit);
+	if (err < 0) {
+		return err;
+	}
+
+	if (err > 0 ||
+	    memcmp(want.hash_prefix, got.hash_prefix, r->object_id_len)) {
+		iterator_set_empty(it);
+		return 0;
+	}
+
+	{
+		struct indexed_table_ref_iter *itr = NULL;
+		err = new_indexed_table_ref_iter(&itr, r, oid, r->hash_size,
+						 got.offsets, got.offset_len);
+		if (err < 0) {
+			record_clear(got_rec);
+			return err;
+		}
+		got.offsets = NULL;
+		record_clear(got_rec);
+
+		iterator_from_indexed_table_ref_iter(it, itr);
+	}
+
+	return 0;
+}
+
+static int reader_refs_for_unindexed(struct reader *r, struct iterator *it,
+				     byte *oid, int oid_len)
+{
+	struct table_iter *ti = calloc(sizeof(struct table_iter), 1);
+	struct filtering_ref_iterator *filter = NULL;
+	int err = reader_start(r, ti, BLOCK_TYPE_REF, false);
+	if (err < 0) {
+		free(ti);
+		return err;
+	}
+
+	filter = calloc(sizeof(struct filtering_ref_iterator), 1);
+	slice_resize(&filter->oid, oid_len);
+	memcpy(filter->oid.buf, oid, oid_len);
+	filter->r = r;
+	filter->double_check = false;
+	iterator_from_table_iter(&filter->it, ti);
+
+	iterator_from_filtering_ref_iterator(it, filter);
+	return 0;
+}
+
+int reader_refs_for(struct reader *r, struct iterator *it, byte *oid,
+		    int oid_len)
+{
+	if (r->obj_offsets.present) {
+		return reader_refs_for_indexed(r, it, oid);
+	}
+	return reader_refs_for_unindexed(r, it, oid, oid_len);
+}
+
+uint64_t reader_max_update_index(struct reader *r)
+{
+	return r->max_update_index;
+}
+
+uint64_t reader_min_update_index(struct reader *r)
+{
+	return r->min_update_index;
+}
diff --git a/reftable/reader.h b/reftable/reader.h
new file mode 100644
index 0000000000..599a90028e
--- /dev/null
+++ b/reftable/reader.h
@@ -0,0 +1,52 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef READER_H
+#define READER_H
+
+#include "block.h"
+#include "record.h"
+#include "reftable.h"
+
+uint64_t block_source_size(struct block_source source);
+
+int block_source_read_block(struct block_source source, struct block *dest,
+			    uint64_t off, uint32_t size);
+void block_source_return_block(struct block_source source, struct block *ret);
+void block_source_close(struct block_source *source);
+
+struct reader_offsets {
+	bool present;
+	uint64_t offset;
+	uint64_t index_offset;
+};
+
+struct reader {
+	struct block_source source;
+	char *name;
+	int hash_size;
+	uint64_t size;
+	uint32_t block_size;
+	uint64_t min_update_index;
+	uint64_t max_update_index;
+	int object_id_len;
+
+	struct reader_offsets ref_offsets;
+	struct reader_offsets obj_offsets;
+	struct reader_offsets log_offsets;
+};
+
+int init_reader(struct reader *r, struct block_source source, const char *name);
+int reader_seek(struct reader *r, struct iterator *it, struct record rec);
+void reader_close(struct reader *r);
+const char *reader_name(struct reader *r);
+void reader_return_block(struct reader *r, struct block *p);
+int reader_init_block_reader(struct reader *r, struct block_reader *br,
+			     uint64_t next_off, byte want_typ);
+
+#endif
diff --git a/reftable/record.c b/reftable/record.c
new file mode 100644
index 0000000000..a006f9019d
--- /dev/null
+++ b/reftable/record.c
@@ -0,0 +1,1107 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "record.h"
+
+#include "system.h"
+
+#include "constants.h"
+#include "reftable.h"
+
+int is_block_type(byte typ)
+{
+	switch (typ) {
+	case BLOCK_TYPE_REF:
+	case BLOCK_TYPE_LOG:
+	case BLOCK_TYPE_OBJ:
+	case BLOCK_TYPE_INDEX:
+		return true;
+	}
+	return false;
+}
+
+int get_var_int(uint64_t *dest, struct slice in)
+{
+	int ptr = 0;
+	uint64_t val;
+
+	if (in.len == 0) {
+		return -1;
+	}
+	val = in.buf[ptr] & 0x7f;
+
+	while (in.buf[ptr] & 0x80) {
+		ptr++;
+		if (ptr > in.len) {
+			return -1;
+		}
+		val = (val + 1) << 7 | (uint64_t)(in.buf[ptr] & 0x7f);
+	}
+
+	*dest = val;
+	return ptr + 1;
+}
+
+int put_var_int(struct slice dest, uint64_t val)
+{
+	byte buf[10] = {};
+	int i = 9;
+	buf[i] = (byte)(val & 0x7f);
+	i--;
+	while (true) {
+		val >>= 7;
+		if (!val) {
+			break;
+		}
+		val--;
+		buf[i] = 0x80 | (byte)(val & 0x7f);
+		i--;
+	}
+
+	{
+		int n = sizeof(buf) - i - 1;
+		if (dest.len < n) {
+			return -1;
+		}
+		memcpy(dest.buf, &buf[i + 1], n);
+		return n;
+	}
+}
+
+int common_prefix_size(struct slice a, struct slice b)
+{
+	int p = 0;
+	while (p < a.len && p < b.len) {
+		if (a.buf[p] != b.buf[p]) {
+			break;
+		}
+		p++;
+	}
+
+	return p;
+}
+
+static int decode_string(struct slice *dest, struct slice in)
+{
+	int start_len = in.len;
+	uint64_t tsize = 0;
+	int n = get_var_int(&tsize, in);
+	if (n <= 0) {
+		return -1;
+	}
+	in.buf += n;
+	in.len -= n;
+	if (in.len < tsize) {
+		return -1;
+	}
+
+	slice_resize(dest, tsize + 1);
+	dest->buf[tsize] = 0;
+	memcpy(dest->buf, in.buf, tsize);
+	in.buf += tsize;
+	in.len -= tsize;
+
+	return start_len - in.len;
+}
+
+int encode_key(bool *restart, struct slice dest, struct slice prev_key,
+	       struct slice key, byte extra)
+{
+	struct slice start = dest;
+	int prefix_len = common_prefix_size(prev_key, key);
+	uint64_t suffix_len = key.len - prefix_len;
+	int n = put_var_int(dest, (uint64_t)prefix_len);
+	if (n < 0) {
+		return -1;
+	}
+	dest.buf += n;
+	dest.len -= n;
+
+	*restart = (prefix_len == 0);
+
+	n = put_var_int(dest, suffix_len << 3 | (uint64_t)extra);
+	if (n < 0) {
+		return -1;
+	}
+	dest.buf += n;
+	dest.len -= n;
+
+	if (dest.len < suffix_len) {
+		return -1;
+	}
+	memcpy(dest.buf, key.buf + prefix_len, suffix_len);
+	dest.buf += suffix_len;
+	dest.len -= suffix_len;
+
+	return start.len - dest.len;
+}
+
+static byte ref_record_type(void)
+{
+	return BLOCK_TYPE_REF;
+}
+
+static void ref_record_key(const void *r, struct slice *dest)
+{
+	const struct ref_record *rec = (const struct ref_record *)r;
+	slice_set_string(dest, rec->ref_name);
+}
+
+static void ref_record_copy_from(void *rec, const void *src_rec, int hash_size)
+{
+	struct ref_record *ref = (struct ref_record *)rec;
+	struct ref_record *src = (struct ref_record *)src_rec;
+	assert(hash_size > 0);
+
+	/* This is simple and correct, but we could probably reuse the hash
+	   fields. */
+	ref_record_clear(ref);
+	if (src->ref_name != NULL) {
+		ref->ref_name = strdup(src->ref_name);
+	}
+
+	if (src->target != NULL) {
+		ref->target = strdup(src->target);
+	}
+
+	if (src->target_value != NULL) {
+		ref->target_value = malloc(hash_size);
+		memcpy(ref->target_value, src->target_value, hash_size);
+	}
+
+	if (src->value != NULL) {
+		ref->value = malloc(hash_size);
+		memcpy(ref->value, src->value, hash_size);
+	}
+	ref->update_index = src->update_index;
+}
+
+static char hexdigit(int c)
+{
+	if (c <= 9) {
+		return '0' + c;
+	}
+	return 'a' + (c - 10);
+}
+
+static void hex_format(char *dest, byte *src, int hash_size)
+{
+	assert(hash_size > 0);
+	if (src != NULL) {
+		int i = 0;
+		for (i = 0; i < hash_size; i++) {
+			dest[2 * i] = hexdigit(src[i] >> 4);
+			dest[2 * i + 1] = hexdigit(src[i] & 0xf);
+		}
+		dest[2 * hash_size] = 0;
+	}
+}
+
+void ref_record_print(struct ref_record *ref, int hash_size)
+{
+	char hex[SHA256_SIZE + 1] = {};
+
+	printf("ref{%s(%" PRIu64 ") ", ref->ref_name, ref->update_index);
+	if (ref->value != NULL) {
+		hex_format(hex, ref->value, hash_size);
+		printf("%s", hex);
+	}
+	if (ref->target_value != NULL) {
+		hex_format(hex, ref->target_value, hash_size);
+		printf(" (T %s)", hex);
+	}
+	if (ref->target != NULL) {
+		printf("=> %s", ref->target);
+	}
+	printf("}\n");
+}
+
+static void ref_record_clear_void(void *rec)
+{
+	ref_record_clear((struct ref_record *)rec);
+}
+
+void ref_record_clear(struct ref_record *ref)
+{
+	free(ref->ref_name);
+	free(ref->target);
+	free(ref->target_value);
+	free(ref->value);
+	memset(ref, 0, sizeof(struct ref_record));
+}
+
+static byte ref_record_val_type(const void *rec)
+{
+	const struct ref_record *r = (const struct ref_record *)rec;
+	if (r->value != NULL) {
+		if (r->target_value != NULL) {
+			return 2;
+		} else {
+			return 1;
+		}
+	} else if (r->target != NULL) {
+		return 3;
+	}
+	return 0;
+}
+
+static int encode_string(char *str, struct slice s)
+{
+	struct slice start = s;
+	int l = strlen(str);
+	int n = put_var_int(s, l);
+	if (n < 0) {
+		return -1;
+	}
+	s.buf += n;
+	s.len -= n;
+	if (s.len < l) {
+		return -1;
+	}
+	memcpy(s.buf, str, l);
+	s.buf += l;
+	s.len -= l;
+
+	return start.len - s.len;
+}
+
+static int ref_record_encode(const void *rec, struct slice s, int hash_size)
+{
+	const struct ref_record *r = (const struct ref_record *)rec;
+	struct slice start = s;
+	int n = put_var_int(s, r->update_index);
+	assert(hash_size > 0);
+	if (n < 0) {
+		return -1;
+	}
+	s.buf += n;
+	s.len -= n;
+
+	if (r->value != NULL) {
+		if (s.len < hash_size) {
+			return -1;
+		}
+		memcpy(s.buf, r->value, hash_size);
+		s.buf += hash_size;
+		s.len -= hash_size;
+	}
+
+	if (r->target_value != NULL) {
+		if (s.len < hash_size) {
+			return -1;
+		}
+		memcpy(s.buf, r->target_value, hash_size);
+		s.buf += hash_size;
+		s.len -= hash_size;
+	}
+
+	if (r->target != NULL) {
+		int n = encode_string(r->target, s);
+		if (n < 0) {
+			return -1;
+		}
+		s.buf += n;
+		s.len -= n;
+	}
+
+	return start.len - s.len;
+}
+
+static int ref_record_decode(void *rec, struct slice key, byte val_type,
+			     struct slice in, int hash_size)
+{
+	struct ref_record *r = (struct ref_record *)rec;
+	struct slice start = in;
+	bool seen_value = false;
+	bool seen_target_value = false;
+	bool seen_target = false;
+
+	int n = get_var_int(&r->update_index, in);
+	if (n < 0) {
+		return n;
+	}
+	assert(hash_size > 0);
+
+	in.buf += n;
+	in.len -= n;
+
+	r->ref_name = realloc(r->ref_name, key.len + 1);
+	memcpy(r->ref_name, key.buf, key.len);
+	r->ref_name[key.len] = 0;
+
+	switch (val_type) {
+	case 1:
+	case 2:
+		if (in.len < hash_size) {
+			return -1;
+		}
+
+		if (r->value == NULL) {
+			r->value = malloc(hash_size);
+		}
+		seen_value = true;
+		memcpy(r->value, in.buf, hash_size);
+		in.buf += hash_size;
+		in.len -= hash_size;
+		if (val_type == 1) {
+			break;
+		}
+		if (r->target_value == NULL) {
+			r->target_value = malloc(hash_size);
+		}
+		seen_target_value = true;
+		memcpy(r->target_value, in.buf, hash_size);
+		in.buf += hash_size;
+		in.len -= hash_size;
+		break;
+	case 3: {
+		struct slice dest = {};
+		int n = decode_string(&dest, in);
+		if (n < 0) {
+			return -1;
+		}
+		in.buf += n;
+		in.len -= n;
+		seen_target = true;
+		r->target = (char *)slice_as_string(&dest);
+	} break;
+
+	case 0:
+		break;
+	default:
+		abort();
+		break;
+	}
+
+	if (!seen_target && r->target != NULL) {
+		FREE_AND_NULL(r->target);
+	}
+	if (!seen_target_value && r->target_value != NULL) {
+		FREE_AND_NULL(r->target_value);
+	}
+	if (!seen_value && r->value != NULL) {
+		FREE_AND_NULL(r->value);
+	}
+
+	return start.len - in.len;
+}
+
+int decode_key(struct slice *key, byte *extra, struct slice last_key,
+	       struct slice in)
+{
+	int start_len = in.len;
+	uint64_t prefix_len = 0;
+	uint64_t suffix_len = 0;
+	int n = get_var_int(&prefix_len, in);
+	if (n < 0) {
+		return -1;
+	}
+	in.buf += n;
+	in.len -= n;
+
+	if (prefix_len > last_key.len) {
+		return -1;
+	}
+
+	n = get_var_int(&suffix_len, in);
+	if (n <= 0) {
+		return -1;
+	}
+	in.buf += n;
+	in.len -= n;
+
+	*extra = (byte)(suffix_len & 0x7);
+	suffix_len >>= 3;
+
+	if (in.len < suffix_len) {
+		return -1;
+	}
+
+	slice_resize(key, suffix_len + prefix_len);
+	memcpy(key->buf, last_key.buf, prefix_len);
+
+	memcpy(key->buf + prefix_len, in.buf, suffix_len);
+	in.buf += suffix_len;
+	in.len -= suffix_len;
+
+	return start_len - in.len;
+}
+
+struct record_vtable ref_record_vtable = {
+	.key = &ref_record_key,
+	.type = &ref_record_type,
+	.copy_from = &ref_record_copy_from,
+	.val_type = &ref_record_val_type,
+	.encode = &ref_record_encode,
+	.decode = &ref_record_decode,
+	.clear = &ref_record_clear_void,
+};
+
+static byte obj_record_type(void)
+{
+	return BLOCK_TYPE_OBJ;
+}
+
+static void obj_record_key(const void *r, struct slice *dest)
+{
+	const struct obj_record *rec = (const struct obj_record *)r;
+	slice_resize(dest, rec->hash_prefix_len);
+	memcpy(dest->buf, rec->hash_prefix, rec->hash_prefix_len);
+}
+
+static void obj_record_copy_from(void *rec, const void *src_rec, int hash_size)
+{
+	struct obj_record *ref = (struct obj_record *)rec;
+	const struct obj_record *src = (const struct obj_record *)src_rec;
+
+	*ref = *src;
+	ref->hash_prefix = malloc(ref->hash_prefix_len);
+	memcpy(ref->hash_prefix, src->hash_prefix, ref->hash_prefix_len);
+
+	{
+		int olen = ref->offset_len * sizeof(uint64_t);
+		ref->offsets = malloc(olen);
+		memcpy(ref->offsets, src->offsets, olen);
+	}
+}
+
+static void obj_record_clear(void *rec)
+{
+	struct obj_record *ref = (struct obj_record *)rec;
+	FREE_AND_NULL(ref->hash_prefix);
+	FREE_AND_NULL(ref->offsets);
+	memset(ref, 0, sizeof(struct obj_record));
+}
+
+static byte obj_record_val_type(const void *rec)
+{
+	struct obj_record *r = (struct obj_record *)rec;
+	if (r->offset_len > 0 && r->offset_len < 8) {
+		return r->offset_len;
+	}
+	return 0;
+}
+
+static int obj_record_encode(const void *rec, struct slice s, int hash_size)
+{
+	struct obj_record *r = (struct obj_record *)rec;
+	struct slice start = s;
+	int n = 0;
+	if (r->offset_len == 0 || r->offset_len >= 8) {
+		n = put_var_int(s, r->offset_len);
+		if (n < 0) {
+			return -1;
+		}
+		s.buf += n;
+		s.len -= n;
+	}
+	if (r->offset_len == 0) {
+		return start.len - s.len;
+	}
+	n = put_var_int(s, r->offsets[0]);
+	if (n < 0) {
+		return -1;
+	}
+	s.buf += n;
+	s.len -= n;
+
+	{
+		uint64_t last = r->offsets[0];
+		int i = 0;
+		for (i = 1; i < r->offset_len; i++) {
+			int n = put_var_int(s, r->offsets[i] - last);
+			if (n < 0) {
+				return -1;
+			}
+			s.buf += n;
+			s.len -= n;
+			last = r->offsets[i];
+		}
+	}
+	return start.len - s.len;
+}
+
+static int obj_record_decode(void *rec, struct slice key, byte val_type,
+			     struct slice in, int hash_size)
+{
+	struct slice start = in;
+	struct obj_record *r = (struct obj_record *)rec;
+	uint64_t count = val_type;
+	int n = 0;
+	r->hash_prefix = malloc(key.len);
+	memcpy(r->hash_prefix, key.buf, key.len);
+	r->hash_prefix_len = key.len;
+
+	if (val_type == 0) {
+		n = get_var_int(&count, in);
+		if (n < 0) {
+			return n;
+		}
+
+		in.buf += n;
+		in.len -= n;
+	}
+
+	r->offsets = NULL;
+	r->offset_len = 0;
+	if (count == 0) {
+		return start.len - in.len;
+	}
+
+	r->offsets = malloc(count * sizeof(uint64_t));
+	r->offset_len = count;
+
+	n = get_var_int(&r->offsets[0], in);
+	if (n < 0) {
+		return n;
+	}
+
+	in.buf += n;
+	in.len -= n;
+
+	{
+		uint64_t last = r->offsets[0];
+		int j = 1;
+		while (j < count) {
+			uint64_t delta = 0;
+			int n = get_var_int(&delta, in);
+			if (n < 0) {
+				return n;
+			}
+
+			in.buf += n;
+			in.len -= n;
+
+			last = r->offsets[j] = (delta + last);
+			j++;
+		}
+	}
+	return start.len - in.len;
+}
+
+struct record_vtable obj_record_vtable = {
+	.key = &obj_record_key,
+	.type = &obj_record_type,
+	.copy_from = &obj_record_copy_from,
+	.val_type = &obj_record_val_type,
+	.encode = &obj_record_encode,
+	.decode = &obj_record_decode,
+	.clear = &obj_record_clear,
+};
+
+void log_record_print(struct log_record *log, int hash_size)
+{
+	char hex[SHA256_SIZE + 1] = {};
+
+	printf("log{%s(%" PRIu64 ") %s <%s> %" PRIu64 " %04d\n", log->ref_name,
+	       log->update_index, log->name, log->email, log->time,
+	       log->tz_offset);
+	hex_format(hex, log->old_hash, hash_size);
+	printf("%s => ", hex);
+	hex_format(hex, log->new_hash, hash_size);
+	printf("%s\n\n%s\n}\n", hex, log->message);
+}
+
+static byte log_record_type(void)
+{
+	return BLOCK_TYPE_LOG;
+}
+
+static void log_record_key(const void *r, struct slice *dest)
+{
+	const struct log_record *rec = (const struct log_record *)r;
+	int len = strlen(rec->ref_name);
+	uint64_t ts = 0;
+	slice_resize(dest, len + 9);
+	memcpy(dest->buf, rec->ref_name, len + 1);
+	ts = (~ts) - rec->update_index;
+	put_u64(dest->buf + 1 + len, ts);
+}
+
+static void log_record_copy_from(void *rec, const void *src_rec, int hash_size)
+{
+	struct log_record *dst = (struct log_record *)rec;
+	const struct log_record *src = (const struct log_record *)src_rec;
+
+	*dst = *src;
+	dst->ref_name = strdup(dst->ref_name);
+	dst->email = strdup(dst->email);
+	dst->name = strdup(dst->name);
+	dst->message = strdup(dst->message);
+	if (dst->new_hash != NULL) {
+		dst->new_hash = malloc(hash_size);
+		memcpy(dst->new_hash, src->new_hash, hash_size);
+	}
+	if (dst->old_hash != NULL) {
+		dst->old_hash = malloc(hash_size);
+		memcpy(dst->old_hash, src->old_hash, hash_size);
+	}
+}
+
+static void log_record_clear_void(void *rec)
+{
+	struct log_record *r = (struct log_record *)rec;
+	log_record_clear(r);
+}
+
+void log_record_clear(struct log_record *r)
+{
+	free(r->ref_name);
+	free(r->new_hash);
+	free(r->old_hash);
+	free(r->name);
+	free(r->email);
+	free(r->message);
+	memset(r, 0, sizeof(struct log_record));
+}
+
+static byte log_record_val_type(const void *rec)
+{
+	return 1;
+}
+
+static byte zero[SHA256_SIZE] = {};
+
+static int log_record_encode(const void *rec, struct slice s, int hash_size)
+{
+	struct log_record *r = (struct log_record *)rec;
+	struct slice start = s;
+	int n = 0;
+	byte *oldh = r->old_hash;
+	byte *newh = r->new_hash;
+	if (oldh == NULL) {
+		oldh = zero;
+	}
+	if (newh == NULL) {
+		newh = zero;
+	}
+
+	if (s.len < 2 * hash_size) {
+		return -1;
+	}
+
+	memcpy(s.buf, oldh, hash_size);
+	memcpy(s.buf + hash_size, newh, hash_size);
+	s.buf += 2 * hash_size;
+	s.len -= 2 * hash_size;
+
+	n = encode_string(r->name ? r->name : "", s);
+	if (n < 0) {
+		return -1;
+	}
+	s.len -= n;
+	s.buf += n;
+
+	n = encode_string(r->email ? r->email : "", s);
+	if (n < 0) {
+		return -1;
+	}
+	s.len -= n;
+	s.buf += n;
+
+	n = put_var_int(s, r->time);
+	if (n < 0) {
+		return -1;
+	}
+	s.buf += n;
+	s.len -= n;
+
+	if (s.len < 2) {
+		return -1;
+	}
+
+	put_u16(s.buf, r->tz_offset);
+	s.buf += 2;
+	s.len -= 2;
+
+	n = encode_string(r->message ? r->message : "", s);
+	if (n < 0) {
+		return -1;
+	}
+	s.len -= n;
+	s.buf += n;
+
+	return start.len - s.len;
+}
+
+static int log_record_decode(void *rec, struct slice key, byte val_type,
+			     struct slice in, int hash_size)
+{
+	struct slice start = in;
+	struct log_record *r = (struct log_record *)rec;
+	uint64_t max = 0;
+	uint64_t ts = 0;
+	struct slice dest = {};
+	int n;
+
+	if (key.len <= 9 || key.buf[key.len - 9] != 0) {
+		return FORMAT_ERROR;
+	}
+
+	r->ref_name = realloc(r->ref_name, key.len - 8);
+	memcpy(r->ref_name, key.buf, key.len - 8);
+	ts = get_u64(key.buf + key.len - 8);
+
+	r->update_index = (~max) - ts;
+
+	if (in.len < 2 * hash_size) {
+		return FORMAT_ERROR;
+	}
+
+	r->old_hash = realloc(r->old_hash, hash_size);
+	r->new_hash = realloc(r->new_hash, hash_size);
+
+	memcpy(r->old_hash, in.buf, hash_size);
+	memcpy(r->new_hash, in.buf + hash_size, hash_size);
+
+	in.buf += 2 * hash_size;
+	in.len -= 2 * hash_size;
+
+	n = decode_string(&dest, in);
+	if (n < 0) {
+		goto error;
+	}
+	in.len -= n;
+	in.buf += n;
+
+	r->name = realloc(r->name, dest.len + 1);
+	memcpy(r->name, dest.buf, dest.len);
+	r->name[dest.len] = 0;
+
+	slice_resize(&dest, 0);
+	n = decode_string(&dest, in);
+	if (n < 0) {
+		goto error;
+	}
+	in.len -= n;
+	in.buf += n;
+
+	r->email = realloc(r->email, dest.len + 1);
+	memcpy(r->email, dest.buf, dest.len);
+	r->email[dest.len] = 0;
+
+	ts = 0;
+	n = get_var_int(&ts, in);
+	if (n < 0) {
+		goto error;
+	}
+	in.len -= n;
+	in.buf += n;
+	r->time = ts;
+	if (in.len < 2) {
+		goto error;
+	}
+
+	r->tz_offset = get_u16(in.buf);
+	in.buf += 2;
+	in.len -= 2;
+
+	slice_resize(&dest, 0);
+	n = decode_string(&dest, in);
+	if (n < 0) {
+		goto error;
+	}
+	in.len -= n;
+	in.buf += n;
+
+	r->message = realloc(r->message, dest.len + 1);
+	memcpy(r->message, dest.buf, dest.len);
+	r->message[dest.len] = 0;
+
+	return start.len - in.len;
+
+error:
+	free(slice_yield(&dest));
+	return FORMAT_ERROR;
+}
+
+static bool null_streq(char *a, char *b)
+{
+	char *empty = "";
+	if (a == NULL) {
+		a = empty;
+	}
+	if (b == NULL) {
+		b = empty;
+	}
+	return 0 == strcmp(a, b);
+}
+
+static bool zero_hash_eq(byte *a, byte *b, int sz)
+{
+	if (a == NULL) {
+		a = zero;
+	}
+	if (b == NULL) {
+		b = zero;
+	}
+	return !memcmp(a, b, sz);
+}
+
+bool log_record_equal(struct log_record *a, struct log_record *b, int hash_size)
+{
+	return null_streq(a->name, b->name) && null_streq(a->email, b->email) &&
+	       null_streq(a->message, b->message) &&
+	       zero_hash_eq(a->old_hash, b->old_hash, hash_size) &&
+	       zero_hash_eq(a->new_hash, b->new_hash, hash_size) &&
+	       a->time == b->time && a->tz_offset == b->tz_offset &&
+	       a->update_index == b->update_index;
+}
+
+struct record_vtable log_record_vtable = {
+	.key = &log_record_key,
+	.type = &log_record_type,
+	.copy_from = &log_record_copy_from,
+	.val_type = &log_record_val_type,
+	.encode = &log_record_encode,
+	.decode = &log_record_decode,
+	.clear = &log_record_clear_void,
+};
+
+struct record new_record(byte typ)
+{
+	struct record rec;
+	switch (typ) {
+	case BLOCK_TYPE_REF: {
+		struct ref_record *r = calloc(1, sizeof(struct ref_record));
+		record_from_ref(&rec, r);
+		return rec;
+	}
+
+	case BLOCK_TYPE_OBJ: {
+		struct obj_record *r = calloc(1, sizeof(struct obj_record));
+		record_from_obj(&rec, r);
+		return rec;
+	}
+	case BLOCK_TYPE_LOG: {
+		struct log_record *r = calloc(1, sizeof(struct log_record));
+		record_from_log(&rec, r);
+		return rec;
+	}
+	case BLOCK_TYPE_INDEX: {
+		struct index_record *r = calloc(1, sizeof(struct index_record));
+		record_from_index(&rec, r);
+		return rec;
+	}
+	}
+	abort();
+	return rec;
+}
+
+static byte index_record_type(void)
+{
+	return BLOCK_TYPE_INDEX;
+}
+
+static void index_record_key(const void *r, struct slice *dest)
+{
+	struct index_record *rec = (struct index_record *)r;
+	slice_copy(dest, rec->last_key);
+}
+
+static void index_record_copy_from(void *rec, const void *src_rec,
+				   int hash_size)
+{
+	struct index_record *dst = (struct index_record *)rec;
+	struct index_record *src = (struct index_record *)src_rec;
+
+	slice_copy(&dst->last_key, src->last_key);
+	dst->offset = src->offset;
+}
+
+static void index_record_clear(void *rec)
+{
+	struct index_record *idx = (struct index_record *)rec;
+	free(slice_yield(&idx->last_key));
+}
+
+static byte index_record_val_type(const void *rec)
+{
+	return 0;
+}
+
+static int index_record_encode(const void *rec, struct slice out, int hash_size)
+{
+	const struct index_record *r = (const struct index_record *)rec;
+	struct slice start = out;
+
+	int n = put_var_int(out, r->offset);
+	if (n < 0) {
+		return n;
+	}
+
+	out.buf += n;
+	out.len -= n;
+
+	return start.len - out.len;
+}
+
+static int index_record_decode(void *rec, struct slice key, byte val_type,
+			       struct slice in, int hash_size)
+{
+	struct slice start = in;
+	struct index_record *r = (struct index_record *)rec;
+	int n = 0;
+
+	slice_copy(&r->last_key, key);
+
+	n = get_var_int(&r->offset, in);
+	if (n < 0) {
+		return n;
+	}
+
+	in.buf += n;
+	in.len -= n;
+	return start.len - in.len;
+}
+
+struct record_vtable index_record_vtable = {
+	.key = &index_record_key,
+	.type = &index_record_type,
+	.copy_from = &index_record_copy_from,
+	.val_type = &index_record_val_type,
+	.encode = &index_record_encode,
+	.decode = &index_record_decode,
+	.clear = &index_record_clear,
+};
+
+void record_key(struct record rec, struct slice *dest)
+{
+	rec.ops->key(rec.data, dest);
+}
+
+byte record_type(struct record rec)
+{
+	return rec.ops->type();
+}
+
+int record_encode(struct record rec, struct slice dest, int hash_size)
+{
+	return rec.ops->encode(rec.data, dest, hash_size);
+}
+
+void record_copy_from(struct record rec, struct record src, int hash_size)
+{
+	assert(src.ops->type() == rec.ops->type());
+
+	rec.ops->copy_from(rec.data, src.data, hash_size);
+}
+
+byte record_val_type(struct record rec)
+{
+	return rec.ops->val_type(rec.data);
+}
+
+int record_decode(struct record rec, struct slice key, byte extra,
+		  struct slice src, int hash_size)
+{
+	return rec.ops->decode(rec.data, key, extra, src, hash_size);
+}
+
+void record_clear(struct record rec)
+{
+	return rec.ops->clear(rec.data);
+}
+
+void record_from_ref(struct record *rec, struct ref_record *ref_rec)
+{
+	rec->data = ref_rec;
+	rec->ops = &ref_record_vtable;
+}
+
+void record_from_obj(struct record *rec, struct obj_record *obj_rec)
+{
+	rec->data = obj_rec;
+	rec->ops = &obj_record_vtable;
+}
+
+void record_from_index(struct record *rec, struct index_record *index_rec)
+{
+	rec->data = index_rec;
+	rec->ops = &index_record_vtable;
+}
+
+void record_from_log(struct record *rec, struct log_record *log_rec)
+{
+	rec->data = log_rec;
+	rec->ops = &log_record_vtable;
+}
+
+void *record_yield(struct record *rec)
+{
+	void *p = rec->data;
+	rec->data = NULL;
+	return p;
+}
+
+struct ref_record *record_as_ref(struct record rec)
+{
+	assert(record_type(rec) == BLOCK_TYPE_REF);
+	return (struct ref_record *)rec.data;
+}
+
+static bool hash_equal(byte *a, byte *b, int hash_size)
+{
+	if (a != NULL && b != NULL) {
+		return !memcmp(a, b, hash_size);
+	}
+
+	return a == b;
+}
+
+static bool str_equal(char *a, char *b)
+{
+	if (a != NULL && b != NULL) {
+		return 0 == strcmp(a, b);
+	}
+
+	return a == b;
+}
+
+bool ref_record_equal(struct ref_record *a, struct ref_record *b, int hash_size)
+{
+	assert(hash_size > 0);
+	return 0 == strcmp(a->ref_name, b->ref_name) &&
+	       a->update_index == b->update_index &&
+	       hash_equal(a->value, b->value, hash_size) &&
+	       hash_equal(a->target_value, b->target_value, hash_size) &&
+	       str_equal(a->target, b->target);
+}
+
+int ref_record_compare_name(const void *a, const void *b)
+{
+	return strcmp(((struct ref_record *)a)->ref_name,
+		      ((struct ref_record *)b)->ref_name);
+}
+
+bool ref_record_is_deletion(const struct ref_record *ref)
+{
+	return ref->value == NULL && ref->target == NULL &&
+	       ref->target_value == NULL;
+}
+
+int log_record_compare_key(const void *a, const void *b)
+{
+	struct log_record *la = (struct log_record *)a;
+	struct log_record *lb = (struct log_record *)b;
+
+	int cmp = strcmp(la->ref_name, lb->ref_name);
+	if (cmp) {
+		return cmp;
+	}
+	if (la->update_index > lb->update_index) {
+		return -1;
+	}
+	return (la->update_index < lb->update_index) ? 1 : 0;
+}
+
+bool log_record_is_deletion(const struct log_record *log)
+{
+	/* XXX */
+	return false;
+}
diff --git a/reftable/record.h b/reftable/record.h
new file mode 100644
index 0000000000..dffdd71fc2
--- /dev/null
+++ b/reftable/record.h
@@ -0,0 +1,79 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef RECORD_H
+#define RECORD_H
+
+#include "reftable.h"
+#include "slice.h"
+
+struct record_vtable {
+	void (*key)(const void *rec, struct slice *dest);
+	byte (*type)(void);
+	void (*copy_from)(void *rec, const void *src, int hash_size);
+	byte (*val_type)(const void *rec);
+	int (*encode)(const void *rec, struct slice dest, int hash_size);
+	int (*decode)(void *rec, struct slice key, byte extra, struct slice src,
+		      int hash_size);
+	void (*clear)(void *rec);
+};
+
+/* record is a generic wrapper for differnt types of records. */
+struct record {
+	void *data;
+	struct record_vtable *ops;
+};
+
+int get_var_int(uint64_t *dest, struct slice in);
+int put_var_int(struct slice dest, uint64_t val);
+int common_prefix_size(struct slice a, struct slice b);
+
+int is_block_type(byte typ);
+struct record new_record(byte typ);
+
+extern struct record_vtable ref_record_vtable;
+
+int encode_key(bool *restart, struct slice dest, struct slice prev_key,
+	       struct slice key, byte extra);
+int decode_key(struct slice *key, byte *extra, struct slice last_key,
+	       struct slice in);
+
+struct index_record {
+	struct slice last_key;
+	uint64_t offset;
+};
+
+struct obj_record {
+	byte *hash_prefix;
+	int hash_prefix_len;
+	uint64_t *offsets;
+	int offset_len;
+};
+
+void record_key(struct record rec, struct slice *dest);
+byte record_type(struct record rec);
+void record_copy_from(struct record rec, struct record src, int hash_size);
+byte record_val_type(struct record rec);
+int record_encode(struct record rec, struct slice dest, int hash_size);
+int record_decode(struct record rec, struct slice key, byte extra,
+		  struct slice src, int hash_size);
+void record_clear(struct record rec);
+void *record_yield(struct record *rec);
+void record_from_obj(struct record *rec, struct obj_record *objrec);
+void record_from_index(struct record *rec, struct index_record *idxrec);
+void record_from_ref(struct record *rec, struct ref_record *refrec);
+void record_from_log(struct record *rec, struct log_record *logrec);
+struct ref_record *record_as_ref(struct record ref);
+
+/* for qsort. */
+int ref_record_compare_name(const void *a, const void *b);
+
+/* for qsort. */
+int log_record_compare_key(const void *a, const void *b);
+
+#endif
diff --git a/reftable/reftable.h b/reftable/reftable.h
new file mode 100644
index 0000000000..d84cc2004a
--- /dev/null
+++ b/reftable/reftable.h
@@ -0,0 +1,399 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef REFTABLE_H
+#define REFTABLE_H
+
+#include "system.h"
+
+typedef uint8_t byte;
+typedef byte bool;
+
+/* block_source is a generic wrapper for a seekable readable file.
+   It is generally passed around by value.
+ */
+struct block_source {
+	struct block_source_vtable *ops;
+	void *arg;
+};
+
+/* a contiguous segment of bytes. It keeps track of its generating block_source
+   so it can return itself into the pool.
+*/
+struct block {
+	byte *data;
+	int len;
+	struct block_source source;
+};
+
+/* block_source_vtable are the operations that make up block_source */
+struct block_source_vtable {
+	/* returns the size of a block source */
+	uint64_t (*size)(void *source);
+
+	/* reads a segment from the block source. It is an error to read
+	   beyond the end of the block */
+	int (*read_block)(void *source, struct block *dest, uint64_t off,
+			  uint32_t size);
+	/* mark the block as read; may return the data back to malloc */
+	void (*return_block)(void *source, struct block *blockp);
+
+	/* release all resources associated with the block source */
+	void (*close)(void *source);
+};
+
+/* opens a file on the file system as a block_source */
+int block_source_from_file(struct block_source *block_src, const char *name);
+
+/* write_options sets options for writing a single reftable. */
+struct write_options {
+	/* do not pad out blocks to block size. */
+	bool unpadded;
+
+	/* the blocksize. Should be less than 2^24. */
+	uint32_t block_size;
+
+	/* do not generate a SHA1 => ref index. */
+	bool skip_index_objects;
+
+	/* how often to write complete keys in each block. */
+	int restart_interval;
+};
+
+/* ref_record holds a ref database entry target_value */
+struct ref_record {
+	char *ref_name; /* Name of the ref, malloced. */
+	uint64_t update_index; /* Logical timestamp at which this value is
+				  written */
+	byte *value; /* SHA1, or NULL. malloced. */
+	byte *target_value; /* peeled annotated tag, or NULL. malloced. */
+	char *target; /* symref, or NULL. malloced. */
+};
+
+/* returns whether 'ref' represents a deletion */
+bool ref_record_is_deletion(const struct ref_record *ref);
+
+/* prints a ref_record onto stdout */
+void ref_record_print(struct ref_record *ref, int hash_size);
+
+/* frees and nulls all pointer values. */
+void ref_record_clear(struct ref_record *ref);
+
+/* returns whether two ref_records are the same */
+bool ref_record_equal(struct ref_record *a, struct ref_record *b,
+		      int hash_size);
+
+/* log_record holds a reflog entry */
+struct log_record {
+	char *ref_name;
+	uint64_t update_index;
+	byte *new_hash;
+	byte *old_hash;
+	char *name;
+	char *email;
+	uint64_t time;
+	int16_t tz_offset;
+	char *message;
+};
+
+/* returns whether 'ref' represents the deletion of a log record. */
+bool log_record_is_deletion(const struct log_record *log);
+
+/* frees and nulls all pointer values. */
+void log_record_clear(struct log_record *log);
+
+/* returns whether two records are equal. */
+bool log_record_equal(struct log_record *a, struct log_record *b,
+		      int hash_size);
+
+void log_record_print(struct log_record *log, int hash_size);
+
+/* iterator is the generic interface for walking over data stored in a
+   reftable. It is generally passed around by value.
+*/
+struct iterator {
+	struct iterator_vtable *ops;
+	void *iter_arg;
+};
+
+/* reads the next ref_record. Returns < 0 for error, 0 for OK and > 0:
+   end of iteration.
+*/
+int iterator_next_ref(struct iterator it, struct ref_record *ref);
+
+/* reads the next log_record. Returns < 0 for error, 0 for OK and > 0:
+   end of iteration.
+*/
+int iterator_next_log(struct iterator it, struct log_record *log);
+
+/* releases resources associated with an iterator. */
+void iterator_destroy(struct iterator *it);
+
+/* block_stats holds statistics for a single block type */
+struct block_stats {
+	/* total number of entries written */
+	int entries;
+	/* total number of key restarts */
+	int restarts;
+	/* total number of blocks */
+	int blocks;
+	/* total number of index blocks */
+	int index_blocks;
+	/* depth of the index */
+	int max_index_level;
+
+	/* offset of the first block for this type */
+	uint64_t offset;
+	/* offset of the top level index block for this type, or 0 if not
+	 * present */
+	uint64_t index_offset;
+};
+
+/* stats holds overall statistics for a single reftable */
+struct stats {
+	/* total number of blocks written. */
+	int blocks;
+	/* stats for ref data */
+	struct block_stats ref_stats;
+	/* stats for the SHA1 to ref map. */
+	struct block_stats obj_stats;
+	/* stats for index blocks */
+	struct block_stats idx_stats;
+	/* stats for log blocks */
+	struct block_stats log_stats;
+
+	/* disambiguation length of shortened object IDs. */
+	int object_id_len;
+};
+
+/* different types of errors */
+
+/* Unexpected file system behavior */
+#define IO_ERROR -2
+
+/* Format inconsistency on reading data
+ */
+#define FORMAT_ERROR -3
+
+/* File does not exist. Returned from block_source_from_file(),  because it
+   needs special handling in stack.
+*/
+#define NOT_EXIST_ERROR -4
+
+/* Trying to write out-of-date data. */
+#define LOCK_ERROR -5
+
+/* Misuse of the API:
+   - on writing a record with NULL ref_name.
+   - on writing a ref_record outside the table limits
+   - on writing a ref or log record before the stack's next_update_index
+   - on reading a ref_record from log iterator, or vice versa.
+ */
+#define API_ERROR -6
+
+/* Decompression error */
+#define ZLIB_ERROR -7
+
+const char *error_str(int err);
+
+/* new_writer creates a new writer */
+struct writer *new_writer(int (*writer_func)(void *, byte *, int),
+			  void *writer_arg, struct write_options *opts);
+
+/* write to a file descriptor. fdp should be an int* pointing to the fd. */
+int fd_writer(void *fdp, byte *data, int size);
+
+/* Set the range of update indices for the records we will add.  When
+   writing a table into a stack, the min should be at least
+   stack_next_update_index(), or API_ERROR is returned.
+ */
+void writer_set_limits(struct writer *w, uint64_t min, uint64_t max);
+
+/* adds a ref_record. Must be called in ascending
+   order. The update_index must be within the limits set by
+   writer_set_limits(), or API_ERROR is returned.
+ */
+int writer_add_ref(struct writer *w, struct ref_record *ref);
+
+/* Convenience function to add multiple refs. Will sort the refs by
+   name before adding. */
+int writer_add_refs(struct writer *w, struct ref_record *refs, int n);
+
+/* adds a log_record. Must be called in ascending order (with more
+   recent log entries first.)
+ */
+int writer_add_log(struct writer *w, struct log_record *log);
+
+/* Convenience function to add multiple logs. Will sort the records by
+   key before adding. */
+int writer_add_logs(struct writer *w, struct log_record *logs, int n);
+
+/* writer_close finalizes the reftable. The writer is retained so statistics can
+ * be inspected. */
+int writer_close(struct writer *w);
+
+/* writer_stats returns the statistics on the reftable being written. */
+struct stats *writer_stats(struct writer *w);
+
+/* writer_free deallocates memory for the writer */
+void writer_free(struct writer *w);
+
+struct reader;
+
+/* new_reader opens a reftable for reading. If successful, returns 0
+ * code and sets pp.  The name is used for creating a
+ * stack. Typically, it is the basename of the file.
+ */
+int new_reader(struct reader **pp, struct block_source, const char *name);
+
+/* reader_seek_ref returns an iterator where 'name' would be inserted in the
+   table.
+
+   example:
+
+   struct reader *r = NULL;
+   int err = new_reader(&r, src, "filename");
+   if (err < 0) { ... }
+   struct iterator it = {};
+   err = reader_seek_ref(r, &it, "refs/heads/master");
+   if (err < 0) { ... }
+   struct ref_record ref = {};
+   while (1) {
+     err = iterator_next_ref(it, &ref);
+     if (err > 0) {
+       break;
+     }
+     if (err < 0) {
+       ..error handling..
+     }
+     ..found..
+   }
+   iterator_destroy(&it);
+   ref_record_clear(&ref);
+ */
+int reader_seek_ref(struct reader *r, struct iterator *it, const char *name);
+
+/* seek to logs for the given name, older than update_index. */
+int reader_seek_log_at(struct reader *r, struct iterator *it, const char *name,
+		       uint64_t update_index);
+
+/* seek to newest log entry for given name. */
+int reader_seek_log(struct reader *r, struct iterator *it, const char *name);
+
+/* closes and deallocates a reader. */
+void reader_free(struct reader *);
+
+/* return an iterator for the refs pointing to oid */
+int reader_refs_for(struct reader *r, struct iterator *it, byte *oid,
+		    int oid_len);
+
+/* return the max_update_index for a table */
+uint64_t reader_max_update_index(struct reader *r);
+
+/* return the min_update_index for a table */
+uint64_t reader_min_update_index(struct reader *r);
+
+/* a merged table is implements seeking/iterating over a stack of tables. */
+struct merged_table;
+
+/* new_merged_table creates a new merged table. It takes ownership of the stack
+   array.
+*/
+int new_merged_table(struct merged_table **dest, struct reader **stack, int n);
+
+/* returns an iterator positioned just before 'name' */
+int merged_table_seek_ref(struct merged_table *mt, struct iterator *it,
+			  const char *name);
+
+/* returns an iterator for log entry, at given update_index */
+int merged_table_seek_log_at(struct merged_table *mt, struct iterator *it,
+			     const char *name, uint64_t update_index);
+
+/* like merged_table_seek_log_at but look for the newest entry. */
+int merged_table_seek_log(struct merged_table *mt, struct iterator *it,
+			  const char *name);
+
+/* returns the max update_index covered by this merged table. */
+uint64_t merged_max_update_index(struct merged_table *mt);
+
+/* returns the min update_index covered by this merged table. */
+uint64_t merged_min_update_index(struct merged_table *mt);
+
+/* closes readers for the merged tables */
+void merged_table_close(struct merged_table *mt);
+
+/* releases memory for the merged_table */
+void merged_table_free(struct merged_table *m);
+
+/* a stack is a stack of reftables, which can be mutated by pushing a table to
+ * the top of the stack */
+struct stack;
+
+/* open a new reftable stack. The tables will be stored in 'dir', while the list
+   of tables is in 'list_file'. Typically, this should be .git/reftables and
+   .git/refs respectively.
+*/
+int new_stack(struct stack **dest, const char *dir, const char *list_file,
+	      struct write_options config);
+
+/* returns the update_index at which a next table should be written. */
+uint64_t stack_next_update_index(struct stack *st);
+
+/* add a new table to the stack. The write_table function must call
+   writer_set_limits, add refs and return an error value. */
+int stack_add(struct stack *st,
+	      int (*write_table)(struct writer *wr, void *write_arg),
+	      void *write_arg);
+
+/* returns the merged_table for seeking. This table is valid until the
+   next write or reload, and should not be closed or deleted.
+*/
+struct merged_table *stack_merged_table(struct stack *st);
+
+/* frees all resources associated with the stack. */
+void stack_destroy(struct stack *st);
+
+/* reloads the stack if necessary. */
+int stack_reload(struct stack *st);
+
+/* Policy for expiring reflog entries. */
+struct log_expiry_config {
+	/* Drop entries older than this timestamp */
+	uint64_t time;
+
+	/* Drop older entries */
+	uint64_t min_update_index;
+};
+
+/* compacts all reftables into a giant table. Expire reflog entries if config is
+ * non-NULL */
+int stack_compact_all(struct stack *st, struct log_expiry_config *config);
+
+/* heuristically compact unbalanced table stack. */
+int stack_auto_compact(struct stack *st);
+
+/* convenience function to read a single ref. Returns < 0 for error, 0
+   for success, and 1 if ref not found. */
+int stack_read_ref(struct stack *st, const char *refname,
+		   struct ref_record *ref);
+
+/* convenience function to read a single log. Returns < 0 for error, 0
+   for success, and 1 if ref not found. */
+int stack_read_log(struct stack *st, const char *refname,
+		   struct log_record *log);
+
+/* statistics on past compactions. */
+struct compaction_stats {
+	uint64_t bytes;
+	int attempts;
+	int failures;
+};
+
+struct compaction_stats *stack_compaction_stats(struct stack *st);
+
+#endif
diff --git a/reftable/slice.c b/reftable/slice.c
new file mode 100644
index 0000000000..efbe625253
--- /dev/null
+++ b/reftable/slice.c
@@ -0,0 +1,199 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "slice.h"
+
+#include "system.h"
+
+#include "reftable.h"
+
+void slice_set_string(struct slice *s, const char *str)
+{
+	if (str == NULL) {
+		s->len = 0;
+		return;
+	}
+
+	{
+		int l = strlen(str);
+		l++; /* \0 */
+		slice_resize(s, l);
+		memcpy(s->buf, str, l);
+		s->len = l - 1;
+	}
+}
+
+void slice_resize(struct slice *s, int l)
+{
+	if (s->cap < l) {
+		int c = s->cap * 2;
+		if (c < l) {
+			c = l;
+		}
+		s->cap = c;
+		s->buf = realloc(s->buf, s->cap);
+	}
+	s->len = l;
+}
+
+void slice_append_string(struct slice *d, const char *s)
+{
+	int l1 = d->len;
+	int l2 = strlen(s);
+
+	slice_resize(d, l2 + l1);
+	memcpy(d->buf + l1, s, l2);
+}
+
+void slice_append(struct slice *s, struct slice a)
+{
+	int end = s->len;
+	slice_resize(s, s->len + a.len);
+	memcpy(s->buf + end, a.buf, a.len);
+}
+
+byte *slice_yield(struct slice *s)
+{
+	byte *p = s->buf;
+	s->buf = NULL;
+	s->cap = 0;
+	s->len = 0;
+	return p;
+}
+
+void slice_copy(struct slice *dest, struct slice src)
+{
+	slice_resize(dest, src.len);
+	memcpy(dest->buf, src.buf, src.len);
+}
+
+/* return the underlying data as char*. len is left unchanged, but
+   a \0 is added at the end. */
+const char *slice_as_string(struct slice *s)
+{
+	if (s->cap == s->len) {
+		int l = s->len;
+		slice_resize(s, l + 1);
+		s->len = l;
+	}
+	s->buf[s->len] = 0;
+	return (const char *)s->buf;
+}
+
+/* return a newly malloced string for this slice */
+char *slice_to_string(struct slice in)
+{
+	struct slice s = {};
+	slice_resize(&s, in.len + 1);
+	s.buf[in.len] = 0;
+	memcpy(s.buf, in.buf, in.len);
+	return (char *)slice_yield(&s);
+}
+
+bool slice_equal(struct slice a, struct slice b)
+{
+	if (a.len != b.len) {
+		return 0;
+	}
+	return memcmp(a.buf, b.buf, a.len) == 0;
+}
+
+int slice_compare(struct slice a, struct slice b)
+{
+	int min = a.len < b.len ? a.len : b.len;
+	int res = memcmp(a.buf, b.buf, min);
+	if (res != 0) {
+		return res;
+	}
+	if (a.len < b.len) {
+		return -1;
+	} else if (a.len > b.len) {
+		return 1;
+	} else {
+		return 0;
+	}
+}
+
+int slice_write(struct slice *b, byte *data, int sz)
+{
+	if (b->len + sz > b->cap) {
+		int newcap = 2 * b->cap + 1;
+		if (newcap < b->len + sz) {
+			newcap = (b->len + sz);
+		}
+		b->buf = realloc(b->buf, newcap);
+		b->cap = newcap;
+	}
+
+	memcpy(b->buf + b->len, data, sz);
+	b->len += sz;
+	return sz;
+}
+
+int slice_write_void(void *b, byte *data, int sz)
+{
+	return slice_write((struct slice *)b, data, sz);
+}
+
+static uint64_t slice_size(void *b)
+{
+	return ((struct slice *)b)->len;
+}
+
+static void slice_return_block(void *b, struct block *dest)
+{
+	memset(dest->data, 0xff, dest->len);
+	free(dest->data);
+}
+
+static void slice_close(void *b)
+{
+}
+
+static int slice_read_block(void *v, struct block *dest, uint64_t off,
+			    uint32_t size)
+{
+	struct slice *b = (struct slice *)v;
+	assert(off + size <= b->len);
+	dest->data = calloc(size, 1);
+	memcpy(dest->data, b->buf + off, size);
+	dest->len = size;
+	return size;
+}
+
+struct block_source_vtable slice_vtable = {
+	.size = &slice_size,
+	.read_block = &slice_read_block,
+	.return_block = &slice_return_block,
+	.close = &slice_close,
+};
+
+void block_source_from_slice(struct block_source *bs, struct slice *buf)
+{
+	bs->ops = &slice_vtable;
+	bs->arg = buf;
+}
+
+static void malloc_return_block(void *b, struct block *dest)
+{
+	memset(dest->data, 0xff, dest->len);
+	free(dest->data);
+}
+
+struct block_source_vtable malloc_vtable = {
+	.return_block = &malloc_return_block,
+};
+
+struct block_source malloc_block_source_instance = {
+	.ops = &malloc_vtable,
+};
+
+struct block_source malloc_block_source(void)
+{
+	return malloc_block_source_instance;
+}
diff --git a/reftable/slice.h b/reftable/slice.h
new file mode 100644
index 0000000000..f12a6db228
--- /dev/null
+++ b/reftable/slice.h
@@ -0,0 +1,39 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef SLICE_H
+#define SLICE_H
+
+#include "basics.h"
+#include "reftable.h"
+
+struct slice {
+	byte *buf;
+	int len;
+	int cap;
+};
+
+void slice_set_string(struct slice *dest, const char *);
+void slice_append_string(struct slice *dest, const char *);
+char *slice_to_string(struct slice src);
+const char *slice_as_string(struct slice *src);
+bool slice_equal(struct slice a, struct slice b);
+byte *slice_yield(struct slice *s);
+void slice_copy(struct slice *dest, struct slice src);
+void slice_resize(struct slice *s, int l);
+int slice_compare(struct slice a, struct slice b);
+int slice_write(struct slice *b, byte *data, int sz);
+int slice_write_void(void *b, byte *data, int sz);
+void slice_append(struct slice *dest, struct slice add);
+
+struct block_source;
+void block_source_from_slice(struct block_source *bs, struct slice *buf);
+
+struct block_source malloc_block_source(void);
+
+#endif
diff --git a/reftable/stack.c b/reftable/stack.c
new file mode 100644
index 0000000000..303eed2f37
--- /dev/null
+++ b/reftable/stack.c
@@ -0,0 +1,983 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "stack.h"
+
+#include "system.h"
+#include "merged.h"
+#include "reader.h"
+#include "reftable.h"
+#include "writer.h"
+
+int new_stack(struct stack **dest, const char *dir, const char *list_file,
+	      struct write_options config)
+{
+	struct stack *p = calloc(sizeof(struct stack), 1);
+	int err = 0;
+	*dest = NULL;
+	p->list_file = strdup(list_file);
+	p->reftable_dir = strdup(dir);
+	p->config = config;
+
+	err = stack_reload(p);
+	if (err < 0) {
+		stack_destroy(p);
+	} else {
+		*dest = p;
+	}
+	return err;
+}
+
+static int fread_lines(FILE *f, char ***namesp)
+{
+	long size = 0;
+	int err = fseek(f, 0, SEEK_END);
+	char *buf = NULL;
+	if (err < 0) {
+		err = IO_ERROR;
+		goto exit;
+	}
+	size = ftell(f);
+	if (size < 0) {
+		err = IO_ERROR;
+		goto exit;
+	}
+	err = fseek(f, 0, SEEK_SET);
+	if (err < 0) {
+		err = IO_ERROR;
+		goto exit;
+	}
+
+	buf = malloc(size + 1);
+	if (fread(buf, 1, size, f) != size) {
+		err = IO_ERROR;
+		goto exit;
+	}
+	buf[size] = 0;
+
+	parse_names(buf, size, namesp);
+exit:
+	free(buf);
+	return err;
+}
+
+int read_lines(const char *filename, char ***namesp)
+{
+	FILE *f = fopen(filename, "r");
+	int err = 0;
+	if (f == NULL) {
+		if (errno == ENOENT) {
+			*namesp = calloc(sizeof(char *), 1);
+			return 0;
+		}
+
+		return IO_ERROR;
+	}
+	err = fread_lines(f, namesp);
+	fclose(f);
+	return err;
+}
+
+struct merged_table *stack_merged_table(struct stack *st)
+{
+	return st->merged;
+}
+
+/* Close and free the stack */
+void stack_destroy(struct stack *st)
+{
+	if (st->merged == NULL) {
+		return;
+	}
+
+	merged_table_close(st->merged);
+	merged_table_free(st->merged);
+	st->merged = NULL;
+
+	FREE_AND_NULL(st->list_file);
+	FREE_AND_NULL(st->reftable_dir);
+	free(st);
+}
+
+static struct reader **stack_copy_readers(struct stack *st, int cur_len)
+{
+	struct reader **cur = calloc(sizeof(struct reader *), cur_len);
+	int i = 0;
+	for (i = 0; i < cur_len; i++) {
+		cur[i] = st->merged->stack[i];
+	}
+	return cur;
+}
+
+static int stack_reload_once(struct stack *st, char **names, bool reuse_open)
+{
+	int cur_len = st->merged == NULL ? 0 : st->merged->stack_len;
+	struct reader **cur = stack_copy_readers(st, cur_len);
+	int err = 0;
+	int names_len = names_length(names);
+	struct reader **new_tables =
+		malloc(sizeof(struct reader *) * names_len);
+	int new_tables_len = 0;
+	struct merged_table *new_merged = NULL;
+
+	struct slice table_path = {};
+
+	while (*names) {
+		struct reader *rd = NULL;
+		char *name = *names++;
+
+		/* this is linear; we assume compaction keeps the number of
+		   tables under control so this is not quadratic. */
+		int j = 0;
+		for (j = 0; reuse_open && j < cur_len; j++) {
+			if (cur[j] != NULL && 0 == strcmp(cur[j]->name, name)) {
+				rd = cur[j];
+				cur[j] = NULL;
+				break;
+			}
+		}
+
+		if (rd == NULL) {
+			struct block_source src = {};
+			slice_set_string(&table_path, st->reftable_dir);
+			slice_append_string(&table_path, "/");
+			slice_append_string(&table_path, name);
+
+			err = block_source_from_file(
+				&src, slice_as_string(&table_path));
+			if (err < 0) {
+				goto exit;
+			}
+
+			err = new_reader(&rd, src, name);
+			if (err < 0) {
+				goto exit;
+			}
+		}
+
+		new_tables[new_tables_len++] = rd;
+	}
+
+	/* success! */
+	err = new_merged_table(&new_merged, new_tables, new_tables_len);
+	if (err < 0) {
+		goto exit;
+	}
+
+	new_tables = NULL;
+	new_tables_len = 0;
+	if (st->merged != NULL) {
+		merged_table_clear(st->merged);
+		merged_table_free(st->merged);
+	}
+	st->merged = new_merged;
+
+	{
+		int i = 0;
+		for (i = 0; i < cur_len; i++) {
+			if (cur[i] != NULL) {
+				reader_close(cur[i]);
+				reader_free(cur[i]);
+			}
+		}
+	}
+exit:
+	free(slice_yield(&table_path));
+	{
+		int i = 0;
+		for (i = 0; i < new_tables_len; i++) {
+			reader_close(new_tables[i]);
+		}
+	}
+	free(new_tables);
+	free(cur);
+	return err;
+}
+
+/* return negative if a before b. */
+static int tv_cmp(struct timeval *a, struct timeval *b)
+{
+	time_t diff = a->tv_sec - b->tv_sec;
+	int udiff = a->tv_usec - b->tv_usec;
+
+	if (diff != 0) {
+		return diff;
+	}
+
+	return udiff;
+}
+
+static int stack_reload_maybe_reuse(struct stack *st, bool reuse_open)
+{
+	struct timeval deadline = {};
+	int err = gettimeofday(&deadline, NULL);
+	int64_t delay = 0;
+	int tries = 0;
+	if (err < 0) {
+		return err;
+	}
+
+	deadline.tv_sec += 3;
+	while (true) {
+		char **names = NULL;
+		char **names_after = NULL;
+		struct timeval now = {};
+		int err = gettimeofday(&now, NULL);
+		if (err < 0) {
+			return err;
+		}
+
+		/* Only look at deadlines after the first few times. This
+		   simplifies debugging in GDB */
+		tries++;
+		if (tries > 3 && tv_cmp(&now, &deadline) >= 0) {
+			break;
+		}
+
+		err = read_lines(st->list_file, &names);
+		if (err < 0) {
+			free_names(names);
+			return err;
+		}
+		err = stack_reload_once(st, names, reuse_open);
+		if (err == 0) {
+			free_names(names);
+			break;
+		}
+		if (err != NOT_EXIST_ERROR) {
+			free_names(names);
+			return err;
+		}
+
+		err = read_lines(st->list_file, &names_after);
+		if (err < 0) {
+			free_names(names);
+			return err;
+		}
+
+		if (names_equal(names_after, names)) {
+			free_names(names);
+			free_names(names_after);
+			return -1;
+		}
+		free_names(names);
+		free_names(names_after);
+
+		delay = delay + (delay * rand()) / RAND_MAX + 100;
+		usleep(delay);
+	}
+
+	return 0;
+}
+
+int stack_reload(struct stack *st)
+{
+	return stack_reload_maybe_reuse(st, true);
+}
+
+/* -1 = error
+ 0 = up to date
+ 1 = changed. */
+static int stack_uptodate(struct stack *st)
+{
+	char **names = NULL;
+	int err = read_lines(st->list_file, &names);
+	int i = 0;
+	if (err < 0) {
+		return err;
+	}
+
+	for (i = 0; i < st->merged->stack_len; i++) {
+		if (names[i] == NULL) {
+			err = 1;
+			goto exit;
+		}
+
+		if (strcmp(st->merged->stack[i]->name, names[i])) {
+			err = 1;
+			goto exit;
+		}
+	}
+
+	if (names[st->merged->stack_len] != NULL) {
+		err = 1;
+		goto exit;
+	}
+
+exit:
+	free_names(names);
+	return err;
+}
+
+int stack_add(struct stack *st, int (*write)(struct writer *wr, void *arg),
+	      void *arg)
+{
+	int err = stack_try_add(st, write, arg);
+	if (err < 0) {
+		if (err == LOCK_ERROR) {
+			err = stack_reload(st);
+		}
+		return err;
+	}
+
+	return stack_auto_compact(st);
+}
+
+static void format_name(struct slice *dest, uint64_t min, uint64_t max)
+{
+	char buf[100];
+	snprintf(buf, sizeof(buf), "%012" PRIx64 "-%012" PRIx64, min, max);
+	slice_set_string(dest, buf);
+}
+
+int stack_try_add(struct stack *st,
+		  int (*write_table)(struct writer *wr, void *arg), void *arg)
+{
+	struct slice lock_name = {};
+	struct slice temp_tab_name = {};
+	struct slice tab_name = {};
+	struct slice next_name = {};
+	struct slice table_list = {};
+	struct writer *wr = NULL;
+	int err = 0;
+	int tab_fd = 0;
+	int lock_fd = 0;
+	uint64_t next_update_index = 0;
+
+	slice_set_string(&lock_name, st->list_file);
+	slice_append_string(&lock_name, ".lock");
+
+	lock_fd = open(slice_as_string(&lock_name), O_EXCL | O_CREAT | O_WRONLY,
+		       0644);
+	if (lock_fd < 0) {
+		if (errno == EEXIST) {
+			err = LOCK_ERROR;
+			goto exit;
+		}
+		err = IO_ERROR;
+		goto exit;
+	}
+
+	err = stack_uptodate(st);
+	if (err < 0) {
+		goto exit;
+	}
+
+	if (err > 1) {
+		err = LOCK_ERROR;
+		goto exit;
+	}
+
+	next_update_index = stack_next_update_index(st);
+
+	slice_resize(&next_name, 0);
+	format_name(&next_name, next_update_index, next_update_index);
+
+	slice_set_string(&temp_tab_name, st->reftable_dir);
+	slice_append_string(&temp_tab_name, "/");
+	slice_append(&temp_tab_name, next_name);
+	slice_append_string(&temp_tab_name, ".temp.XXXXXX");
+
+	tab_fd = mkstemp((char *)slice_as_string(&temp_tab_name));
+	if (tab_fd < 0) {
+		err = IO_ERROR;
+		goto exit;
+	}
+
+	wr = new_writer(fd_writer, &tab_fd, &st->config);
+	err = write_table(wr, arg);
+	if (err < 0) {
+		goto exit;
+	}
+
+	err = writer_close(wr);
+	if (err < 0) {
+		goto exit;
+	}
+
+	err = close(tab_fd);
+	tab_fd = 0;
+	if (err < 0) {
+		err = IO_ERROR;
+		goto exit;
+	}
+
+	if (wr->min_update_index < next_update_index) {
+		err = API_ERROR;
+		goto exit;
+	}
+
+	{
+		int i = 0;
+		for (i = 0; i < st->merged->stack_len; i++) {
+			slice_append_string(&table_list,
+					    st->merged->stack[i]->name);
+			slice_append_string(&table_list, "\n");
+		}
+	}
+
+	format_name(&next_name, wr->min_update_index, wr->max_update_index);
+	slice_append_string(&next_name, ".ref");
+	slice_append(&table_list, next_name);
+	slice_append_string(&table_list, "\n");
+
+	slice_set_string(&tab_name, st->reftable_dir);
+	slice_append_string(&tab_name, "/");
+	slice_append(&tab_name, next_name);
+
+	err = rename(slice_as_string(&temp_tab_name),
+		     slice_as_string(&tab_name));
+	if (err < 0) {
+		err = IO_ERROR;
+		goto exit;
+	}
+	free(slice_yield(&temp_tab_name));
+
+	err = write(lock_fd, table_list.buf, table_list.len);
+	if (err < 0) {
+		err = IO_ERROR;
+		goto exit;
+	}
+	err = close(lock_fd);
+	lock_fd = 0;
+	if (err < 0) {
+		unlink(slice_as_string(&tab_name));
+		err = IO_ERROR;
+		goto exit;
+	}
+
+	err = rename(slice_as_string(&lock_name), st->list_file);
+	if (err < 0) {
+		unlink(slice_as_string(&tab_name));
+		err = IO_ERROR;
+		goto exit;
+	}
+
+	err = stack_reload(st);
+exit:
+	if (tab_fd > 0) {
+		close(tab_fd);
+		tab_fd = 0;
+	}
+	if (temp_tab_name.len > 0) {
+		unlink(slice_as_string(&temp_tab_name));
+	}
+	unlink(slice_as_string(&lock_name));
+
+	if (lock_fd > 0) {
+		close(lock_fd);
+		lock_fd = 0;
+	}
+
+	free(slice_yield(&lock_name));
+	free(slice_yield(&temp_tab_name));
+	free(slice_yield(&tab_name));
+	free(slice_yield(&next_name));
+	free(slice_yield(&table_list));
+	writer_free(wr);
+	return err;
+}
+
+uint64_t stack_next_update_index(struct stack *st)
+{
+	int sz = st->merged->stack_len;
+	if (sz > 0) {
+		return reader_max_update_index(st->merged->stack[sz - 1]) + 1;
+	}
+	return 1;
+}
+
+static int stack_compact_locked(struct stack *st, int first, int last,
+				struct slice *temp_tab,
+				struct log_expiry_config *config)
+{
+	struct slice next_name = {};
+	int tab_fd = -1;
+	struct writer *wr = NULL;
+	int err = 0;
+
+	format_name(&next_name,
+		    reader_min_update_index(st->merged->stack[first]),
+		    reader_max_update_index(st->merged->stack[first]));
+
+	slice_set_string(temp_tab, st->reftable_dir);
+	slice_append_string(temp_tab, "/");
+	slice_append(temp_tab, next_name);
+	slice_append_string(temp_tab, ".temp.XXXXXX");
+
+	tab_fd = mkstemp((char *)slice_as_string(temp_tab));
+	wr = new_writer(fd_writer, &tab_fd, &st->config);
+
+	err = stack_write_compact(st, wr, first, last, config);
+	if (err < 0) {
+		goto exit;
+	}
+	err = writer_close(wr);
+	if (err < 0) {
+		goto exit;
+	}
+	writer_free(wr);
+
+	err = close(tab_fd);
+	tab_fd = 0;
+
+exit:
+	if (tab_fd > 0) {
+		close(tab_fd);
+		tab_fd = 0;
+	}
+	if (err != 0 && temp_tab->len > 0) {
+		unlink(slice_as_string(temp_tab));
+		free(slice_yield(temp_tab));
+	}
+	free(slice_yield(&next_name));
+	return err;
+}
+
+int stack_write_compact(struct stack *st, struct writer *wr, int first,
+			int last, struct log_expiry_config *config)
+{
+	int subtabs_len = last - first + 1;
+	struct reader **subtabs =
+		calloc(sizeof(struct reader *), last - first + 1);
+	struct merged_table *mt = NULL;
+	int err = 0;
+	struct iterator it = {};
+	struct ref_record ref = {};
+	struct log_record log = {};
+
+	int i = 0, j = 0;
+	for (i = first, j = 0; i <= last; i++) {
+		struct reader *t = st->merged->stack[i];
+		subtabs[j++] = t;
+		st->stats.bytes += t->size;
+	}
+	writer_set_limits(wr, st->merged->stack[first]->min_update_index,
+			  st->merged->stack[last]->max_update_index);
+
+	err = new_merged_table(&mt, subtabs, subtabs_len);
+	if (err < 0) {
+		free(subtabs);
+		goto exit;
+	}
+
+	err = merged_table_seek_ref(mt, &it, "");
+	if (err < 0) {
+		goto exit;
+	}
+
+	while (true) {
+		err = iterator_next_ref(it, &ref);
+		if (err > 0) {
+			err = 0;
+			break;
+		}
+		if (err < 0) {
+			break;
+		}
+		if (first == 0 && ref_record_is_deletion(&ref)) {
+			continue;
+		}
+
+		err = writer_add_ref(wr, &ref);
+		if (err < 0) {
+			break;
+		}
+	}
+
+	err = merged_table_seek_log(mt, &it, "");
+	if (err < 0) {
+		goto exit;
+	}
+
+	while (true) {
+		err = iterator_next_log(it, &log);
+		if (err > 0) {
+			err = 0;
+			break;
+		}
+		if (err < 0) {
+			break;
+		}
+		if (first == 0 && log_record_is_deletion(&log)) {
+			continue;
+		}
+
+		/* XXX collect stats? */
+
+		if (config != NULL && config->time > 0 &&
+		    log.time < config->time) {
+			continue;
+		}
+
+		if (config != NULL && config->min_update_index > 0 &&
+		    log.update_index < config->min_update_index) {
+			continue;
+		}
+
+		err = writer_add_log(wr, &log);
+		if (err < 0) {
+			break;
+		}
+	}
+
+exit:
+	iterator_destroy(&it);
+	if (mt != NULL) {
+		merged_table_clear(mt);
+		merged_table_free(mt);
+	}
+	ref_record_clear(&ref);
+
+	return err;
+}
+
+/* <  0: error. 0 == OK, > 0 attempt failed; could retry. */
+static int stack_compact_range(struct stack *st, int first, int last,
+			       struct log_expiry_config *expiry)
+{
+	struct slice temp_tab_name = {};
+	struct slice new_table_name = {};
+	struct slice lock_file_name = {};
+	struct slice ref_list_contents = {};
+	struct slice new_table_path = {};
+	int err = 0;
+	bool have_lock = false;
+	int lock_file_fd = 0;
+	int compact_count = last - first + 1;
+	char **delete_on_success = calloc(sizeof(char *), compact_count + 1);
+	char **subtable_locks = calloc(sizeof(char *), compact_count + 1);
+	int i = 0;
+	int j = 0;
+
+	if (first > last || (expiry == NULL && first == last)) {
+		err = 0;
+		goto exit;
+	}
+
+	st->stats.attempts++;
+
+	slice_set_string(&lock_file_name, st->list_file);
+	slice_append_string(&lock_file_name, ".lock");
+
+	lock_file_fd = open(slice_as_string(&lock_file_name),
+			    O_EXCL | O_CREAT | O_WRONLY, 0644);
+	if (lock_file_fd < 0) {
+		if (errno == EEXIST) {
+			err = 1;
+		} else {
+			err = IO_ERROR;
+		}
+		goto exit;
+	}
+	have_lock = true;
+	err = stack_uptodate(st);
+	if (err != 0) {
+		goto exit;
+	}
+
+	for (i = first, j = 0; i <= last; i++) {
+		struct slice subtab_name = {};
+		struct slice subtab_lock = {};
+		slice_set_string(&subtab_name, st->reftable_dir);
+		slice_append_string(&subtab_name, "/");
+		slice_append_string(&subtab_name,
+				    reader_name(st->merged->stack[i]));
+
+		slice_copy(&subtab_lock, subtab_name);
+		slice_append_string(&subtab_lock, ".lock");
+
+		{
+			int sublock_file_fd =
+				open(slice_as_string(&subtab_lock),
+				     O_EXCL | O_CREAT | O_WRONLY, 0644);
+			if (sublock_file_fd > 0) {
+				close(sublock_file_fd);
+			} else if (sublock_file_fd < 0) {
+				if (errno == EEXIST) {
+					err = 1;
+				}
+				err = IO_ERROR;
+			}
+		}
+
+		subtable_locks[j] = (char *)slice_as_string(&subtab_lock);
+		delete_on_success[j] = (char *)slice_as_string(&subtab_name);
+		j++;
+
+		if (err != 0) {
+			goto exit;
+		}
+	}
+
+	err = unlink(slice_as_string(&lock_file_name));
+	if (err < 0) {
+		goto exit;
+	}
+	have_lock = false;
+
+	err = stack_compact_locked(st, first, last, &temp_tab_name, expiry);
+	if (err < 0) {
+		goto exit;
+	}
+
+	lock_file_fd = open(slice_as_string(&lock_file_name),
+			    O_EXCL | O_CREAT | O_WRONLY, 0644);
+	if (lock_file_fd < 0) {
+		if (errno == EEXIST) {
+			err = 1;
+		} else {
+			err = IO_ERROR;
+		}
+		goto exit;
+	}
+	have_lock = true;
+
+	format_name(&new_table_name, st->merged->stack[first]->min_update_index,
+		    st->merged->stack[last]->max_update_index);
+	slice_append_string(&new_table_name, ".ref");
+
+	slice_set_string(&new_table_path, st->reftable_dir);
+	slice_append_string(&new_table_path, "/");
+
+	slice_append(&new_table_path, new_table_name);
+
+	err = rename(slice_as_string(&temp_tab_name),
+		     slice_as_string(&new_table_path));
+	if (err < 0) {
+		goto exit;
+	}
+
+	for (i = 0; i < first; i++) {
+		slice_append_string(&ref_list_contents,
+				    st->merged->stack[i]->name);
+		slice_append_string(&ref_list_contents, "\n");
+	}
+	slice_append(&ref_list_contents, new_table_name);
+	slice_append_string(&ref_list_contents, "\n");
+	for (i = last + 1; i < st->merged->stack_len; i++) {
+		slice_append_string(&ref_list_contents,
+				    st->merged->stack[i]->name);
+		slice_append_string(&ref_list_contents, "\n");
+	}
+
+	err = write(lock_file_fd, ref_list_contents.buf, ref_list_contents.len);
+	if (err < 0) {
+		unlink(slice_as_string(&new_table_path));
+		goto exit;
+	}
+	err = close(lock_file_fd);
+	lock_file_fd = 0;
+	if (err < 0) {
+		unlink(slice_as_string(&new_table_path));
+		goto exit;
+	}
+
+	err = rename(slice_as_string(&lock_file_name), st->list_file);
+	if (err < 0) {
+		unlink(slice_as_string(&new_table_path));
+		goto exit;
+	}
+	have_lock = false;
+
+	for (char **p = delete_on_success; *p; p++) {
+		if (strcmp(*p, slice_as_string(&new_table_path))) {
+			unlink(*p);
+		}
+	}
+
+	err = stack_reload_maybe_reuse(st, first < last);
+exit:
+	for (char **p = subtable_locks; *p; p++) {
+		unlink(*p);
+	}
+	free_names(delete_on_success);
+	free_names(subtable_locks);
+	if (lock_file_fd > 0) {
+		close(lock_file_fd);
+		lock_file_fd = 0;
+	}
+	if (have_lock) {
+		unlink(slice_as_string(&lock_file_name));
+	}
+	free(slice_yield(&new_table_name));
+	free(slice_yield(&new_table_path));
+	free(slice_yield(&ref_list_contents));
+	free(slice_yield(&temp_tab_name));
+	free(slice_yield(&lock_file_name));
+	return err;
+}
+
+int stack_compact_all(struct stack *st, struct log_expiry_config *config)
+{
+	return stack_compact_range(st, 0, st->merged->stack_len - 1, config);
+}
+
+static int stack_compact_range_stats(struct stack *st, int first, int last,
+				     struct log_expiry_config *config)
+{
+	int err = stack_compact_range(st, first, last, config);
+	if (err > 0) {
+		st->stats.failures++;
+	}
+	return err;
+}
+
+static int segment_size(struct segment *s)
+{
+	return s->end - s->start;
+}
+
+int fastlog2(uint64_t sz)
+{
+	int l = 0;
+	assert(sz > 0);
+	for (; sz; sz /= 2) {
+		l++;
+	}
+	return l - 1;
+}
+
+struct segment *sizes_to_segments(int *seglen, uint64_t *sizes, int n)
+{
+	struct segment *segs = calloc(sizeof(struct segment), n);
+	int next = 0;
+	struct segment cur = {};
+	int i = 0;
+	for (i = 0; i < n; i++) {
+		int log = fastlog2(sizes[i]);
+		if (cur.log != log && cur.bytes > 0) {
+			struct segment fresh = {
+				.start = i,
+			};
+
+			segs[next++] = cur;
+			cur = fresh;
+		}
+
+		cur.log = log;
+		cur.end = i + 1;
+		cur.bytes += sizes[i];
+	}
+
+	segs[next++] = cur;
+	*seglen = next;
+	return segs;
+}
+
+struct segment suggest_compaction_segment(uint64_t *sizes, int n)
+{
+	int seglen = 0;
+	struct segment *segs = sizes_to_segments(&seglen, sizes, n);
+	struct segment min_seg = {
+		.log = 64,
+	};
+	int i = 0;
+	for (i = 0; i < seglen; i++) {
+		if (segment_size(&segs[i]) == 1) {
+			continue;
+		}
+
+		if (segs[i].log < min_seg.log) {
+			min_seg = segs[i];
+		}
+	}
+
+	while (min_seg.start > 0) {
+		int prev = min_seg.start - 1;
+		if (fastlog2(min_seg.bytes) < fastlog2(sizes[prev])) {
+			break;
+		}
+
+		min_seg.start = prev;
+		min_seg.bytes += sizes[prev];
+	}
+
+	free(segs);
+	return min_seg;
+}
+
+static uint64_t *stack_table_sizes_for_compaction(struct stack *st)
+{
+	uint64_t *sizes = calloc(sizeof(uint64_t), st->merged->stack_len);
+	int i = 0;
+	for (i = 0; i < st->merged->stack_len; i++) {
+		/* overhead is 24 + 68 = 92. */
+		sizes[i] = st->merged->stack[i]->size - 91;
+	}
+	return sizes;
+}
+
+int stack_auto_compact(struct stack *st)
+{
+	uint64_t *sizes = stack_table_sizes_for_compaction(st);
+	struct segment seg =
+		suggest_compaction_segment(sizes, st->merged->stack_len);
+	free(sizes);
+	if (segment_size(&seg) > 0) {
+		return stack_compact_range_stats(st, seg.start, seg.end - 1,
+						 NULL);
+	}
+
+	return 0;
+}
+
+struct compaction_stats *stack_compaction_stats(struct stack *st)
+{
+	return &st->stats;
+}
+
+int stack_read_ref(struct stack *st, const char *refname,
+		   struct ref_record *ref)
+{
+	struct iterator it = {};
+	struct merged_table *mt = stack_merged_table(st);
+	int err = merged_table_seek_ref(mt, &it, refname);
+	if (err) {
+		goto exit;
+	}
+
+	err = iterator_next_ref(it, ref);
+	if (err) {
+		goto exit;
+	}
+
+	if (strcmp(ref->ref_name, refname) || ref_record_is_deletion(ref)) {
+		err = 1;
+		goto exit;
+	}
+
+exit:
+	iterator_destroy(&it);
+	return err;
+}
+
+int stack_read_log(struct stack *st, const char *refname,
+		   struct log_record *log)
+{
+	struct iterator it = {};
+	struct merged_table *mt = stack_merged_table(st);
+	int err = merged_table_seek_log(mt, &it, refname);
+	if (err) {
+		goto exit;
+	}
+
+	err = iterator_next_log(it, log);
+	if (err) {
+		goto exit;
+	}
+
+	if (strcmp(log->ref_name, refname) || log_record_is_deletion(log)) {
+		err = 1;
+		goto exit;
+	}
+
+exit:
+	iterator_destroy(&it);
+	return err;
+}
diff --git a/reftable/stack.h b/reftable/stack.h
new file mode 100644
index 0000000000..d5e2c93c29
--- /dev/null
+++ b/reftable/stack.h
@@ -0,0 +1,40 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef STACK_H
+#define STACK_H
+
+#include "reftable.h"
+
+struct stack {
+	char *list_file;
+	char *reftable_dir;
+
+	struct write_options config;
+
+	struct merged_table *merged;
+	struct compaction_stats stats;
+};
+
+int read_lines(const char *filename, char ***lines);
+int stack_try_add(struct stack *st,
+		  int (*write_table)(struct writer *wr, void *arg), void *arg);
+int stack_write_compact(struct stack *st, struct writer *wr, int first,
+			int last, struct log_expiry_config *config);
+int fastlog2(uint64_t sz);
+
+struct segment {
+	int start, end;
+	int log;
+	uint64_t bytes;
+};
+
+struct segment *sizes_to_segments(int *seglen, uint64_t *sizes, int n);
+struct segment suggest_compaction_segment(uint64_t *sizes, int n);
+
+#endif
diff --git a/reftable/system.h b/reftable/system.h
new file mode 100644
index 0000000000..ee7a6e1d70
--- /dev/null
+++ b/reftable/system.h
@@ -0,0 +1,55 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef SYSTEM_H
+#define SYSTEM_H
+
+#include "config.h"
+
+#ifndef REFTABLE_STANDALONE
+
+#include "git-compat-util.h"
+#include <zlib.h>
+
+#else
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <zlib.h>
+
+#define ARRAY_SIZE(a) sizeof((a)) / sizeof((a)[0])
+#define FREE_AND_NULL(x)    \
+	do {                \
+		free(x);    \
+		(x) = NULL; \
+	} while (0)
+#define QSORT(arr, n, cmp) qsort(arr, n, sizeof(arr[0]), cmp)
+#define SWAP(a, b)                              \
+	{                                       \
+		char tmp[sizeof(a)];            \
+		assert(sizeof(a) == sizeof(b)); \
+		memcpy(&tmp[0], &a, sizeof(a)); \
+		memcpy(&a, &b, sizeof(a));      \
+		memcpy(&b, &tmp[0], sizeof(a)); \
+	}
+#endif
+
+int uncompress_return_consumed(Bytef *dest, uLongf *destLen,
+			       const Bytef *source, uLong *sourceLen);
+
+#endif
diff --git a/reftable/tree.c b/reftable/tree.c
new file mode 100644
index 0000000000..9bf7fe531f
--- /dev/null
+++ b/reftable/tree.c
@@ -0,0 +1,66 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "tree.h"
+
+#include "system.h"
+
+struct tree_node *tree_search(void *key, struct tree_node **rootp,
+			      int (*compare)(const void *, const void *),
+			      int insert)
+{
+	if (*rootp == NULL) {
+		if (!insert) {
+			return NULL;
+		} else {
+			struct tree_node *n =
+				calloc(sizeof(struct tree_node), 1);
+			n->key = key;
+			*rootp = n;
+			return *rootp;
+		}
+	}
+
+	{
+		int res = compare(key, (*rootp)->key);
+		if (res < 0) {
+			return tree_search(key, &(*rootp)->left, compare,
+					   insert);
+		} else if (res > 0) {
+			return tree_search(key, &(*rootp)->right, compare,
+					   insert);
+		}
+	}
+	return *rootp;
+}
+
+void infix_walk(struct tree_node *t, void (*action)(void *arg, void *key),
+		void *arg)
+{
+	if (t->left != NULL) {
+		infix_walk(t->left, action, arg);
+	}
+	action(arg, t->key);
+	if (t->right != NULL) {
+		infix_walk(t->right, action, arg);
+	}
+}
+
+void tree_free(struct tree_node *t)
+{
+	if (t == NULL) {
+		return;
+	}
+	if (t->left != NULL) {
+		tree_free(t->left);
+	}
+	if (t->right != NULL) {
+		tree_free(t->right);
+	}
+	free(t);
+}
diff --git a/reftable/tree.h b/reftable/tree.h
new file mode 100644
index 0000000000..86a71715ae
--- /dev/null
+++ b/reftable/tree.h
@@ -0,0 +1,24 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef TREE_H
+#define TREE_H
+
+struct tree_node {
+	void *key;
+	struct tree_node *left, *right;
+};
+
+struct tree_node *tree_search(void *key, struct tree_node **rootp,
+			      int (*compare)(const void *, const void *),
+			      int insert);
+void infix_walk(struct tree_node *t, void (*action)(void *arg, void *key),
+		void *arg);
+void tree_free(struct tree_node *t);
+
+#endif
diff --git a/reftable/writer.c b/reftable/writer.c
new file mode 100644
index 0000000000..3247481df3
--- /dev/null
+++ b/reftable/writer.c
@@ -0,0 +1,623 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "writer.h"
+
+#include "system.h"
+
+#include "block.h"
+#include "constants.h"
+#include "record.h"
+#include "reftable.h"
+#include "tree.h"
+
+static struct block_stats *writer_block_stats(struct writer *w, byte typ)
+{
+	switch (typ) {
+	case 'r':
+		return &w->stats.ref_stats;
+	case 'o':
+		return &w->stats.obj_stats;
+	case 'i':
+		return &w->stats.idx_stats;
+	case 'g':
+		return &w->stats.log_stats;
+	}
+	assert(false);
+	return NULL;
+}
+
+/* write data, queuing the padding for the next write. Returns negative for
+ * error. */
+static int padded_write(struct writer *w, byte *data, size_t len, int padding)
+{
+	int n = 0;
+	if (w->pending_padding > 0) {
+		byte *zeroed = calloc(w->pending_padding, 1);
+		int n = w->write(w->write_arg, zeroed, w->pending_padding);
+		if (n < 0) {
+			return n;
+		}
+
+		w->pending_padding = 0;
+		free(zeroed);
+	}
+
+	w->pending_padding = padding;
+	n = w->write(w->write_arg, data, len);
+	if (n < 0) {
+		return n;
+	}
+	n += padding;
+	return 0;
+}
+
+static void options_set_defaults(struct write_options *opts)
+{
+	if (opts->restart_interval == 0) {
+		opts->restart_interval = 16;
+	}
+
+	if (opts->block_size == 0) {
+		opts->block_size = DEFAULT_BLOCK_SIZE;
+	}
+}
+
+static int writer_write_header(struct writer *w, byte *dest)
+{
+	memcpy((char *)dest, "REFT", 4);
+	dest[4] = 1; /* version */
+	put_u24(dest + 5, w->opts.block_size);
+	put_u64(dest + 8, w->min_update_index);
+	put_u64(dest + 16, w->max_update_index);
+	return 24;
+}
+
+static void writer_reinit_block_writer(struct writer *w, byte typ)
+{
+	int block_start = 0;
+	if (w->next == 0) {
+		block_start = HEADER_SIZE;
+	}
+
+	block_writer_init(&w->block_writer_data, typ, w->block,
+			  w->opts.block_size, block_start, w->hash_size);
+	w->block_writer = &w->block_writer_data;
+	w->block_writer->restart_interval = w->opts.restart_interval;
+}
+
+struct writer *new_writer(int (*writer_func)(void *, byte *, int),
+			  void *writer_arg, struct write_options *opts)
+{
+	struct writer *wp = calloc(sizeof(struct writer), 1);
+	options_set_defaults(opts);
+	if (opts->block_size >= (1 << 24)) {
+		/* TODO - error return? */
+		abort();
+	}
+	wp->hash_size = SHA1_SIZE;
+	wp->block = calloc(opts->block_size, 1);
+	wp->write = writer_func;
+	wp->write_arg = writer_arg;
+	wp->opts = *opts;
+	writer_reinit_block_writer(wp, BLOCK_TYPE_REF);
+
+	return wp;
+}
+
+void writer_set_limits(struct writer *w, uint64_t min, uint64_t max)
+{
+	w->min_update_index = min;
+	w->max_update_index = max;
+}
+
+void writer_free(struct writer *w)
+{
+	free(w->block);
+	free(w);
+}
+
+struct obj_index_tree_node {
+	struct slice hash;
+	uint64_t *offsets;
+	int offset_len;
+	int offset_cap;
+};
+
+static int obj_index_tree_node_compare(const void *a, const void *b)
+{
+	return slice_compare(((const struct obj_index_tree_node *)a)->hash,
+			     ((const struct obj_index_tree_node *)b)->hash);
+}
+
+static void writer_index_hash(struct writer *w, struct slice hash)
+{
+	uint64_t off = w->next;
+
+	struct obj_index_tree_node want = { .hash = hash };
+
+	struct tree_node *node = tree_search(&want, &w->obj_index_tree,
+					     &obj_index_tree_node_compare, 0);
+	struct obj_index_tree_node *key = NULL;
+	if (node == NULL) {
+		key = calloc(sizeof(struct obj_index_tree_node), 1);
+		slice_copy(&key->hash, hash);
+		tree_search((void *)key, &w->obj_index_tree,
+			    &obj_index_tree_node_compare, 1);
+	} else {
+		key = node->key;
+	}
+
+	if (key->offset_len > 0 && key->offsets[key->offset_len - 1] == off) {
+		return;
+	}
+
+	if (key->offset_len == key->offset_cap) {
+		key->offset_cap = 2 * key->offset_cap + 1;
+		key->offsets = realloc(key->offsets,
+				       sizeof(uint64_t) * key->offset_cap);
+	}
+
+	key->offsets[key->offset_len++] = off;
+}
+
+static int writer_add_record(struct writer *w, struct record rec)
+{
+	int result = -1;
+	struct slice key = {};
+	int err = 0;
+	record_key(rec, &key);
+	if (slice_compare(w->last_key, key) >= 0) {
+		goto exit;
+	}
+
+	slice_copy(&w->last_key, key);
+	if (w->block_writer == NULL) {
+		writer_reinit_block_writer(w, record_type(rec));
+	}
+
+	assert(block_writer_type(w->block_writer) == record_type(rec));
+
+	if (block_writer_add(w->block_writer, rec) == 0) {
+		result = 0;
+		goto exit;
+	}
+
+	err = writer_flush_block(w);
+	if (err < 0) {
+		result = err;
+		goto exit;
+	}
+
+	writer_reinit_block_writer(w, record_type(rec));
+	err = block_writer_add(w->block_writer, rec);
+	if (err < 0) {
+		result = err;
+		goto exit;
+	}
+
+	result = 0;
+exit:
+	free(slice_yield(&key));
+	return result;
+}
+
+int writer_add_ref(struct writer *w, struct ref_record *ref)
+{
+	struct record rec = {};
+	struct ref_record copy = *ref;
+	int err = 0;
+
+	if (ref->ref_name == NULL) {
+		return API_ERROR;
+	}
+	if (ref->update_index < w->min_update_index ||
+	    ref->update_index > w->max_update_index) {
+		return API_ERROR;
+	}
+
+	record_from_ref(&rec, &copy);
+	copy.update_index -= w->min_update_index;
+	err = writer_add_record(w, rec);
+	if (err < 0) {
+		return err;
+	}
+
+	if (!w->opts.skip_index_objects && ref->value != NULL) {
+		struct slice h = {
+			.buf = ref->value,
+			.len = w->hash_size,
+		};
+
+		writer_index_hash(w, h);
+	}
+	if (!w->opts.skip_index_objects && ref->target_value != NULL) {
+		struct slice h = {
+			.buf = ref->target_value,
+			.len = w->hash_size,
+		};
+		writer_index_hash(w, h);
+	}
+	return 0;
+}
+
+int writer_add_refs(struct writer *w, struct ref_record *refs, int n)
+{
+	int err = 0;
+	int i = 0;
+	QSORT(refs, n, ref_record_compare_name);
+	for (i = 0; err == 0 && i < n; i++) {
+		err = writer_add_ref(w, &refs[i]);
+	}
+	return err;
+}
+
+int writer_add_log(struct writer *w, struct log_record *log)
+{
+	if (log->ref_name == NULL) {
+		return API_ERROR;
+	}
+
+	if (w->block_writer != NULL &&
+	    block_writer_type(w->block_writer) == BLOCK_TYPE_REF) {
+		int err = writer_finish_public_section(w);
+		if (err < 0) {
+			return err;
+		}
+	}
+
+	w->next -= w->pending_padding;
+	w->pending_padding = 0;
+
+	{
+		struct record rec = {};
+		int err;
+		record_from_log(&rec, log);
+		err = writer_add_record(w, rec);
+		return err;
+	}
+}
+
+int writer_add_logs(struct writer *w, struct log_record *logs, int n)
+{
+	int err = 0;
+	int i = 0;
+	QSORT(logs, n, log_record_compare_key);
+	for (i = 0; err == 0 && i < n; i++) {
+		err = writer_add_log(w, &logs[i]);
+	}
+	return err;
+}
+
+static int writer_finish_section(struct writer *w)
+{
+	byte typ = block_writer_type(w->block_writer);
+	uint64_t index_start = 0;
+	int max_level = 0;
+	int threshold = w->opts.unpadded ? 1 : 3;
+	int before_blocks = w->stats.idx_stats.blocks;
+	int err = writer_flush_block(w);
+	int i = 0;
+	if (err < 0) {
+		return err;
+	}
+
+	while (w->index_len > threshold) {
+		struct index_record *idx = NULL;
+		int idx_len = 0;
+
+		max_level++;
+		index_start = w->next;
+		writer_reinit_block_writer(w, BLOCK_TYPE_INDEX);
+
+		idx = w->index;
+		idx_len = w->index_len;
+
+		w->index = NULL;
+		w->index_len = 0;
+		w->index_cap = 0;
+		for (i = 0; i < idx_len; i++) {
+			struct record rec = {};
+			record_from_index(&rec, idx + i);
+			if (block_writer_add(w->block_writer, rec) == 0) {
+				continue;
+			}
+
+			{
+				int err = writer_flush_block(w);
+				if (err < 0) {
+					return err;
+				}
+			}
+
+			writer_reinit_block_writer(w, BLOCK_TYPE_INDEX);
+
+			err = block_writer_add(w->block_writer, rec);
+			assert(err == 0);
+		}
+		for (i = 0; i < idx_len; i++) {
+			free(slice_yield(&idx[i].last_key));
+		}
+		free(idx);
+	}
+
+	writer_clear_index(w);
+
+	err = writer_flush_block(w);
+	if (err < 0) {
+		return err;
+	}
+
+	{
+		struct block_stats *bstats = writer_block_stats(w, typ);
+		bstats->index_blocks =
+			w->stats.idx_stats.blocks - before_blocks;
+		bstats->index_offset = index_start;
+		bstats->max_index_level = max_level;
+	}
+
+	/* Reinit lastKey, as the next section can start with any key. */
+	w->last_key.len = 0;
+
+	return 0;
+}
+
+struct common_prefix_arg {
+	struct slice *last;
+	int max;
+};
+
+static void update_common(void *void_arg, void *key)
+{
+	struct common_prefix_arg *arg = (struct common_prefix_arg *)void_arg;
+	struct obj_index_tree_node *entry = (struct obj_index_tree_node *)key;
+	if (arg->last != NULL) {
+		int n = common_prefix_size(entry->hash, *arg->last);
+		if (n > arg->max) {
+			arg->max = n;
+		}
+	}
+	arg->last = &entry->hash;
+}
+
+struct write_record_arg {
+	struct writer *w;
+	int err;
+};
+
+static void write_object_record(void *void_arg, void *key)
+{
+	struct write_record_arg *arg = (struct write_record_arg *)void_arg;
+	struct obj_index_tree_node *entry = (struct obj_index_tree_node *)key;
+	struct obj_record obj_rec = {
+		.hash_prefix = entry->hash.buf,
+		.hash_prefix_len = arg->w->stats.object_id_len,
+		.offsets = entry->offsets,
+		.offset_len = entry->offset_len,
+	};
+	struct record rec = {};
+	if (arg->err < 0) {
+		goto exit;
+	}
+
+	record_from_obj(&rec, &obj_rec);
+	arg->err = block_writer_add(arg->w->block_writer, rec);
+	if (arg->err == 0) {
+		goto exit;
+	}
+
+	arg->err = writer_flush_block(arg->w);
+	if (arg->err < 0) {
+		goto exit;
+	}
+
+	writer_reinit_block_writer(arg->w, BLOCK_TYPE_OBJ);
+	arg->err = block_writer_add(arg->w->block_writer, rec);
+	if (arg->err == 0) {
+		goto exit;
+	}
+	obj_rec.offset_len = 0;
+	arg->err = block_writer_add(arg->w->block_writer, rec);
+
+	/* Should be able to write into a fresh block. */
+	assert(arg->err == 0);
+
+exit:;
+}
+
+static void object_record_free(void *void_arg, void *key)
+{
+	struct obj_index_tree_node *entry = (struct obj_index_tree_node *)key;
+
+	FREE_AND_NULL(entry->offsets);
+	free(slice_yield(&entry->hash));
+	free(entry);
+}
+
+static int writer_dump_object_index(struct writer *w)
+{
+	struct write_record_arg closure = { .w = w };
+	struct common_prefix_arg common = {};
+	if (w->obj_index_tree != NULL) {
+		infix_walk(w->obj_index_tree, &update_common, &common);
+	}
+	w->stats.object_id_len = common.max + 1;
+
+	writer_reinit_block_writer(w, BLOCK_TYPE_OBJ);
+
+	if (w->obj_index_tree != NULL) {
+		infix_walk(w->obj_index_tree, &write_object_record, &closure);
+	}
+
+	if (closure.err < 0) {
+		return closure.err;
+	}
+	return writer_finish_section(w);
+}
+
+int writer_finish_public_section(struct writer *w)
+{
+	byte typ = 0;
+	int err = 0;
+
+	if (w->block_writer == NULL) {
+		return 0;
+	}
+
+	typ = block_writer_type(w->block_writer);
+	err = writer_finish_section(w);
+	if (err < 0) {
+		return err;
+	}
+	if (typ == BLOCK_TYPE_REF && !w->opts.skip_index_objects &&
+	    w->stats.ref_stats.index_blocks > 0) {
+		err = writer_dump_object_index(w);
+		if (err < 0) {
+			return err;
+		}
+	}
+
+	if (w->obj_index_tree != NULL) {
+		infix_walk(w->obj_index_tree, &object_record_free, NULL);
+		tree_free(w->obj_index_tree);
+		w->obj_index_tree = NULL;
+	}
+
+	w->block_writer = NULL;
+	return 0;
+}
+
+int writer_close(struct writer *w)
+{
+	byte footer[68];
+	byte *p = footer;
+
+	writer_finish_public_section(w);
+
+	writer_write_header(w, footer);
+	p += 24;
+	put_u64(p, w->stats.ref_stats.index_offset);
+	p += 8;
+	put_u64(p, (w->stats.obj_stats.offset) << 5 | w->stats.object_id_len);
+	p += 8;
+	put_u64(p, w->stats.obj_stats.index_offset);
+	p += 8;
+
+	put_u64(p, w->stats.log_stats.offset);
+	p += 8;
+	put_u64(p, w->stats.log_stats.index_offset);
+	p += 8;
+
+	put_u32(p, crc32(0, footer, p - footer));
+	p += 4;
+	w->pending_padding = 0;
+
+	{
+		int n = padded_write(w, footer, sizeof(footer), 0);
+		if (n < 0) {
+			return n;
+		}
+	}
+
+	/* free up memory. */
+	block_writer_clear(&w->block_writer_data);
+	writer_clear_index(w);
+	free(slice_yield(&w->last_key));
+	return 0;
+}
+
+void writer_clear_index(struct writer *w)
+{
+	int i = 0;
+	for (i = 0; i < w->index_len; i++) {
+		free(slice_yield(&w->index[i].last_key));
+	}
+
+	FREE_AND_NULL(w->index);
+	w->index_len = 0;
+	w->index_cap = 0;
+}
+
+const int debug = 0;
+
+static int writer_flush_nonempty_block(struct writer *w)
+{
+	byte typ = block_writer_type(w->block_writer);
+	struct block_stats *bstats = writer_block_stats(w, typ);
+	uint64_t block_typ_off = (bstats->blocks == 0) ? w->next : 0;
+	int raw_bytes = block_writer_finish(w->block_writer);
+	int padding = 0;
+	int err = 0;
+	if (raw_bytes < 0) {
+		return raw_bytes;
+	}
+
+	if (!w->opts.unpadded && typ != BLOCK_TYPE_LOG) {
+		padding = w->opts.block_size - raw_bytes;
+	}
+
+	if (block_typ_off > 0) {
+		bstats->offset = block_typ_off;
+	}
+
+	bstats->entries += w->block_writer->entries;
+	bstats->restarts += w->block_writer->restart_len;
+	bstats->blocks++;
+	w->stats.blocks++;
+
+	if (debug) {
+		fprintf(stderr, "block %c off %" PRIu64 " sz %d (%d)\n", typ,
+			w->next, raw_bytes,
+			get_u24(w->block + w->block_writer->header_off + 1));
+	}
+
+	if (w->next == 0) {
+		writer_write_header(w, w->block);
+	}
+
+	err = padded_write(w, w->block, raw_bytes, padding);
+	if (err < 0) {
+		return err;
+	}
+
+	if (w->index_cap == w->index_len) {
+		w->index_cap = 2 * w->index_cap + 1;
+		w->index = realloc(w->index,
+				   sizeof(struct index_record) * w->index_cap);
+	}
+
+	{
+		struct index_record ir = {
+			.offset = w->next,
+		};
+		slice_copy(&ir.last_key, w->block_writer->last_key);
+		w->index[w->index_len] = ir;
+	}
+
+	w->index_len++;
+	w->next += padding + raw_bytes;
+	block_writer_reset(&w->block_writer_data);
+	w->block_writer = NULL;
+	return 0;
+}
+
+int writer_flush_block(struct writer *w)
+{
+	if (w->block_writer == NULL) {
+		return 0;
+	}
+	if (w->block_writer->entries == 0) {
+		return 0;
+	}
+	return writer_flush_nonempty_block(w);
+}
+
+struct stats *writer_stats(struct writer *w)
+{
+	return &w->stats;
+}
diff --git a/reftable/writer.h b/reftable/writer.h
new file mode 100644
index 0000000000..bd2386474b
--- /dev/null
+++ b/reftable/writer.h
@@ -0,0 +1,46 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef WRITER_H
+#define WRITER_H
+
+#include "basics.h"
+#include "block.h"
+#include "reftable.h"
+#include "slice.h"
+#include "tree.h"
+
+struct writer {
+	int (*write)(void *, byte *, int);
+	void *write_arg;
+	int pending_padding;
+	int hash_size;
+	struct slice last_key;
+
+	uint64_t next;
+	uint64_t min_update_index, max_update_index;
+	struct write_options opts;
+
+	byte *block;
+	struct block_writer *block_writer;
+	struct block_writer block_writer_data;
+	struct index_record *index;
+	int index_len;
+	int index_cap;
+
+	/* tree for use with tsearch */
+	struct tree_node *obj_index_tree;
+
+	struct stats stats;
+};
+
+int writer_flush_block(struct writer *w);
+void writer_clear_index(struct writer *w);
+int writer_finish_public_section(struct writer *w);
+
+#endif
diff --git a/reftable/zlib-compat.c b/reftable/zlib-compat.c
new file mode 100644
index 0000000000..3e0b0f24f1
--- /dev/null
+++ b/reftable/zlib-compat.c
@@ -0,0 +1,92 @@
+/* taken from zlib's uncompr.c
+
+   commit cacf7f1d4e3d44d871b605da3b647f07d718623f
+   Author: Mark Adler <madler@alumni.caltech.edu>
+   Date:   Sun Jan 15 09:18:46 2017 -0800
+
+       zlib 1.2.11
+
+*/
+
+/*
+ * Copyright (C) 1995-2003, 2010, 2014, 2016 Jean-loup Gailly, Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+#include "system.h"
+
+/* clang-format off */
+
+/* ===========================================================================
+     Decompresses the source buffer into the destination buffer.  *sourceLen is
+   the byte length of the source buffer. Upon entry, *destLen is the total size
+   of the destination buffer, which must be large enough to hold the entire
+   uncompressed data. (The size of the uncompressed data must have been saved
+   previously by the compressor and transmitted to the decompressor by some
+   mechanism outside the scope of this compression library.) Upon exit,
+   *destLen is the size of the decompressed data and *sourceLen is the number
+   of source bytes consumed. Upon return, source + *sourceLen points to the
+   first unused input byte.
+
+     uncompress returns Z_OK if success, Z_MEM_ERROR if there was not enough
+   memory, Z_BUF_ERROR if there was not enough room in the output buffer, or
+   Z_DATA_ERROR if the input data was corrupted, including if the input data is
+   an incomplete zlib stream.
+*/
+int ZEXPORT uncompress_return_consumed (
+    Bytef *dest,
+    uLongf *destLen,
+    const Bytef *source,
+    uLong *sourceLen) {
+    z_stream stream;
+    int err;
+    const uInt max = (uInt)-1;
+    uLong len, left;
+    Byte buf[1];    /* for detection of incomplete stream when *destLen == 0 */
+
+    len = *sourceLen;
+    if (*destLen) {
+        left = *destLen;
+        *destLen = 0;
+    }
+    else {
+        left = 1;
+        dest = buf;
+    }
+
+    stream.next_in = (z_const Bytef *)source;
+    stream.avail_in = 0;
+    stream.zalloc = (alloc_func)0;
+    stream.zfree = (free_func)0;
+    stream.opaque = (voidpf)0;
+
+    err = inflateInit(&stream);
+    if (err != Z_OK) return err;
+
+    stream.next_out = dest;
+    stream.avail_out = 0;
+
+    do {
+        if (stream.avail_out == 0) {
+            stream.avail_out = left > (uLong)max ? max : (uInt)left;
+            left -= stream.avail_out;
+        }
+        if (stream.avail_in == 0) {
+            stream.avail_in = len > (uLong)max ? max : (uInt)len;
+            len -= stream.avail_in;
+        }
+        err = inflate(&stream, Z_NO_FLUSH);
+    } while (err == Z_OK);
+
+    *sourceLen -= len + stream.avail_in;
+    if (dest != buf)
+        *destLen = stream.total_out;
+    else if (stream.total_out && err == Z_BUF_ERROR)
+        left = 1;
+
+    inflateEnd(&stream);
+    return err == Z_STREAM_END ? Z_OK :
+           err == Z_NEED_DICT ? Z_DATA_ERROR  :
+           err == Z_BUF_ERROR && left + stream.avail_out ? Z_DATA_ERROR :
+           err;
+}
-- 
gitgitgadget


^ permalink raw reply related	[relevance 1%]

* [PATCH v3 5/6] Add reftable library
  @ 2020-02-04 20:27  1%     ` Han-Wen Nienhuys via GitGitGadget
    1 sibling, 0 replies; 200+ results
From: Han-Wen Nienhuys via GitGitGadget @ 2020-02-04 20:27 UTC (permalink / raw)
  To: git; +Cc: Han-Wen Nienhuys, Han-Wen Nienhuys

From: Han-Wen Nienhuys <hanwen@google.com>

Reftable is a new format for storing the ref database. It provides the
following benefits:

 * Simple and fast atomic ref transactions, including multiple refs and reflogs.
 * Compact storage of ref data.
 * Fast look ups of ref data.
 * Case-sensitive ref names on Windows/OSX, regardless of file system
 * Eliminates file/directory conflicts in ref names

Further context and motivation can be found in background reading:

* Spec: https://github.com/eclipse/jgit/blob/master/Documentation/technical/reftable.md

* Original discussion on JGit-dev:  https://www.eclipse.org/lists/jgit-dev/msg03389.html

* First design discussion on git@vger: https://public-inbox.org/git/CAJo=hJtTp2eA3z9wW9cHo-nA7kK40vVThqh6inXpbCcqfdMP9g@mail.gmail.com/

* Last design discussion on git@vger: https://public-inbox.org/git/CAJo=hJsZcAM9sipdVr7TMD-FD2V2W6_pvMQ791EGCDsDkQ033w@mail.gmail.com/

* First attempt at implementation: https://public-inbox.org/git/CAP8UFD0PPZSjBnxCA7ez91vBuatcHKQ+JUWvTD1iHcXzPBjPBg@mail.gmail.com/

* libgit2 support issue: https://github.com/libgit2/libgit2/issues

* GitLab support issue: https://gitlab.com/gitlab-org/git/issues/6

* go-git support issue: https://github.com/src-d/go-git/issues/1059

Signed-off-by: Han-Wen Nienhuys <hanwen@google.com>
---
 reftable/LICENSE       |   31 ++
 reftable/README.md     |   19 +
 reftable/VERSION       |    5 +
 reftable/basics.c      |  196 +++++++
 reftable/basics.h      |   37 ++
 reftable/block.c       |  401 +++++++++++++++
 reftable/block.h       |   71 +++
 reftable/blocksource.h |   20 +
 reftable/bytes.c       |    0
 reftable/config.h      |    1 +
 reftable/constants.h   |   27 +
 reftable/dump.c        |   97 ++++
 reftable/file.c        |   97 ++++
 reftable/iter.c        |  229 +++++++++
 reftable/iter.h        |   56 ++
 reftable/merged.c      |  286 +++++++++++
 reftable/merged.h      |   34 ++
 reftable/pq.c          |  114 +++++
 reftable/pq.h          |   34 ++
 reftable/reader.c      |  708 +++++++++++++++++++++++++
 reftable/reader.h      |   52 ++
 reftable/record.c      | 1107 ++++++++++++++++++++++++++++++++++++++++
 reftable/record.h      |   79 +++
 reftable/reftable.h    |  399 +++++++++++++++
 reftable/slice.c       |  199 ++++++++
 reftable/slice.h       |   39 ++
 reftable/stack.c       |  983 +++++++++++++++++++++++++++++++++++
 reftable/stack.h       |   40 ++
 reftable/system.h      |   57 +++
 reftable/tree.c        |   66 +++
 reftable/tree.h        |   24 +
 reftable/writer.c      |  622 ++++++++++++++++++++++
 reftable/writer.h      |   46 ++
 reftable/zlib-compat.c |   92 ++++
 34 files changed, 6268 insertions(+)
 create mode 100644 reftable/LICENSE
 create mode 100644 reftable/README.md
 create mode 100644 reftable/VERSION
 create mode 100644 reftable/basics.c
 create mode 100644 reftable/basics.h
 create mode 100644 reftable/block.c
 create mode 100644 reftable/block.h
 create mode 100644 reftable/blocksource.h
 create mode 100644 reftable/bytes.c
 create mode 100644 reftable/config.h
 create mode 100644 reftable/constants.h
 create mode 100644 reftable/dump.c
 create mode 100644 reftable/file.c
 create mode 100644 reftable/iter.c
 create mode 100644 reftable/iter.h
 create mode 100644 reftable/merged.c
 create mode 100644 reftable/merged.h
 create mode 100644 reftable/pq.c
 create mode 100644 reftable/pq.h
 create mode 100644 reftable/reader.c
 create mode 100644 reftable/reader.h
 create mode 100644 reftable/record.c
 create mode 100644 reftable/record.h
 create mode 100644 reftable/reftable.h
 create mode 100644 reftable/slice.c
 create mode 100644 reftable/slice.h
 create mode 100644 reftable/stack.c
 create mode 100644 reftable/stack.h
 create mode 100644 reftable/system.h
 create mode 100644 reftable/tree.c
 create mode 100644 reftable/tree.h
 create mode 100644 reftable/writer.c
 create mode 100644 reftable/writer.h
 create mode 100644 reftable/zlib-compat.c

diff --git a/reftable/LICENSE b/reftable/LICENSE
new file mode 100644
index 0000000000..402e0f9356
--- /dev/null
+++ b/reftable/LICENSE
@@ -0,0 +1,31 @@
+BSD License
+
+Copyright (c) 2020, Google LLC
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+* Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+* Neither the name of Google LLC nor the names of its contributors may
+be used to endorse or promote products derived from this software
+without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/reftable/README.md b/reftable/README.md
new file mode 100644
index 0000000000..f527da0380
--- /dev/null
+++ b/reftable/README.md
@@ -0,0 +1,19 @@
+
+The source code in this directory comes from https://github.com/google/reftable.
+
+The VERSION file keeps track of the current version of the reftable library.
+
+To update the library, do:
+
+   ((cd reftable-repo && git fetch origin && git checkout origin/master ) ||
+    git clone https://github.com/google/reftable reftable-repo) && \
+   cp reftable-repo/c/*.[ch] reftable/ && \
+   cp reftable-repo/LICENSE reftable/ &&
+   git --git-dir reftable-repo/.git show --no-patch origin/master \
+    > reftable/VERSION && \
+   echo '/* empty */' > reftable/config.h
+   rm reftable/*_test.c reftable/test_framework.*
+   git add reftable/*.[ch]
+
+Bugfixes should be accompanied by a test and applied to upstream project at
+https://github.com/google/reftable.
diff --git a/reftable/VERSION b/reftable/VERSION
new file mode 100644
index 0000000000..94624f583b
--- /dev/null
+++ b/reftable/VERSION
@@ -0,0 +1,5 @@
+commit e54326f73d95bfe8b17f264c400f4c365dbd5e5e
+Author: Han-Wen Nienhuys <hanwen@google.com>
+Date:   Tue Feb 4 19:48:17 2020 +0100
+
+    C: PRI?MAX use; clang-format.
diff --git a/reftable/basics.c b/reftable/basics.c
new file mode 100644
index 0000000000..791dcc867a
--- /dev/null
+++ b/reftable/basics.c
@@ -0,0 +1,196 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "basics.h"
+
+#include "system.h"
+
+void put_u24(byte *out, uint32_t i)
+{
+	out[0] = (byte)((i >> 16) & 0xff);
+	out[1] = (byte)((i >> 8) & 0xff);
+	out[2] = (byte)((i)&0xff);
+}
+
+uint32_t get_u24(byte *in)
+{
+	return (uint32_t)(in[0]) << 16 | (uint32_t)(in[1]) << 8 |
+	       (uint32_t)(in[2]);
+}
+
+void put_u32(byte *out, uint32_t i)
+{
+	out[0] = (byte)((i >> 24) & 0xff);
+	out[1] = (byte)((i >> 16) & 0xff);
+	out[2] = (byte)((i >> 8) & 0xff);
+	out[3] = (byte)((i)&0xff);
+}
+
+uint32_t get_u32(byte *in)
+{
+	return (uint32_t)(in[0]) << 24 | (uint32_t)(in[1]) << 16 |
+	       (uint32_t)(in[2]) << 8 | (uint32_t)(in[3]);
+}
+
+void put_u64(byte *out, uint64_t v)
+{
+	int i = 0;
+	for (i = sizeof(uint64_t); i--;) {
+		out[i] = (byte)(v & 0xff);
+		v >>= 8;
+	}
+}
+
+uint64_t get_u64(byte *out)
+{
+	uint64_t v = 0;
+	int i = 0;
+	for (i = 0; i < sizeof(uint64_t); i++) {
+		v = (v << 8) | (byte)(out[i] & 0xff);
+	}
+	return v;
+}
+
+void put_u16(byte *out, uint16_t i)
+{
+	out[0] = (byte)((i >> 8) & 0xff);
+	out[1] = (byte)((i)&0xff);
+}
+
+uint16_t get_u16(byte *in)
+{
+	return (uint32_t)(in[0]) << 8 | (uint32_t)(in[1]);
+}
+
+/*
+  find smallest index i in [0, sz) at which f(i) is true, assuming
+  that f is ascending. Return sz if f(i) is false for all indices.
+*/
+int binsearch(int sz, int (*f)(int k, void *args), void *args)
+{
+	int lo = 0;
+	int hi = sz;
+
+	/* invariant: (hi == sz) || f(hi) == true
+	   (lo == 0 && f(0) == true) || fi(lo) == false
+	 */
+	while (hi - lo > 1) {
+		int mid = lo + (hi - lo) / 2;
+
+		int val = f(mid, args);
+		if (val) {
+			hi = mid;
+		} else {
+			lo = mid;
+		}
+	}
+
+	if (lo == 0) {
+		if (f(0, args)) {
+			return 0;
+		} else {
+			return 1;
+		}
+	}
+
+	return hi;
+}
+
+void free_names(char **a)
+{
+	char **p = a;
+	if (p == NULL) {
+		return;
+	}
+	while (*p) {
+		free(*p);
+		p++;
+	}
+	free(a);
+}
+
+int names_length(char **names)
+{
+	int len = 0;
+	for (char **p = names; *p; p++) {
+		len++;
+	}
+	return len;
+}
+
+/* parse a newline separated list of names. Empty names are discarded. */
+void parse_names(char *buf, int size, char ***namesp)
+{
+	char **names = NULL;
+	int names_cap = 0;
+	int names_len = 0;
+
+	char *p = buf;
+	char *end = buf + size;
+	while (p < end) {
+		char *next = strchr(p, '\n');
+		if (next != NULL) {
+			*next = 0;
+		} else {
+			next = end;
+		}
+		if (p < next) {
+			if (names_len == names_cap) {
+				names_cap = 2 * names_cap + 1;
+				names = realloc(names,
+						names_cap * sizeof(char *));
+			}
+			names[names_len++] = strdup(p);
+		}
+		p = next + 1;
+	}
+
+	if (names_len == names_cap) {
+		names_cap = 2 * names_cap + 1;
+		names = realloc(names, names_cap * sizeof(char *));
+	}
+
+	names[names_len] = NULL;
+	*namesp = names;
+}
+
+int names_equal(char **a, char **b)
+{
+	while (*a && *b) {
+		if (strcmp(*a, *b)) {
+			return 0;
+		}
+
+		a++;
+		b++;
+	}
+
+	return *a == *b;
+}
+
+const char *error_str(int err)
+{
+	switch (err) {
+	case IO_ERROR:
+		return "I/O error";
+	case FORMAT_ERROR:
+		return "FORMAT_ERROR";
+	case NOT_EXIST_ERROR:
+		return "NOT_EXIST_ERROR";
+	case LOCK_ERROR:
+		return "LOCK_ERROR";
+	case API_ERROR:
+		return "API_ERROR";
+	case ZLIB_ERROR:
+		return "ZLIB_ERROR";
+	case -1:
+		return "general error";
+	default:
+		return "unknown error code";
+	}
+}
diff --git a/reftable/basics.h b/reftable/basics.h
new file mode 100644
index 0000000000..0ad368cfd3
--- /dev/null
+++ b/reftable/basics.h
@@ -0,0 +1,37 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef BASICS_H
+#define BASICS_H
+
+#include "system.h"
+
+#include "reftable.h"
+
+#define true 1
+#define false 0
+
+void put_u24(byte *out, uint32_t i);
+uint32_t get_u24(byte *in);
+
+uint64_t get_u64(byte *in);
+void put_u64(byte *out, uint64_t i);
+
+void put_u32(byte *out, uint32_t i);
+uint32_t get_u32(byte *in);
+
+void put_u16(byte *out, uint16_t i);
+uint16_t get_u16(byte *in);
+int binsearch(int sz, int (*f)(int k, void *args), void *args);
+
+void free_names(char **a);
+void parse_names(char *buf, int size, char ***namesp);
+int names_equal(char **a, char **b);
+int names_length(char **names);
+
+#endif
diff --git a/reftable/block.c b/reftable/block.c
new file mode 100644
index 0000000000..ffbfee13c3
--- /dev/null
+++ b/reftable/block.c
@@ -0,0 +1,401 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "block.h"
+
+#include "system.h"
+
+#include "blocksource.h"
+#include "constants.h"
+#include "record.h"
+#include "reftable.h"
+#include "zlib.h"
+
+int block_writer_register_restart(struct block_writer *w, int n, bool restart,
+				  struct slice key);
+
+void block_writer_init(struct block_writer *bw, byte typ, byte *buf,
+		       uint32_t block_size, uint32_t header_off, int hash_size)
+{
+	bw->buf = buf;
+	bw->hash_size = hash_size;
+	bw->block_size = block_size;
+	bw->header_off = header_off;
+	bw->buf[header_off] = typ;
+	bw->next = header_off + 4;
+	bw->restart_interval = 16;
+	bw->entries = 0;
+}
+
+byte block_writer_type(struct block_writer *bw)
+{
+	return bw->buf[bw->header_off];
+}
+
+/* adds the record to the block. Returns -1 if it does not fit, 0 on
+   success */
+int block_writer_add(struct block_writer *w, struct record rec)
+{
+	struct slice empty = {};
+	struct slice last = w->entries % w->restart_interval == 0 ? empty :
+								    w->last_key;
+	struct slice out = {
+		.buf = w->buf + w->next,
+		.len = w->block_size - w->next,
+	};
+
+	struct slice start = out;
+
+	bool restart = false;
+	struct slice key = {};
+	int n = 0;
+
+	record_key(rec, &key);
+	n = encode_key(&restart, out, last, key, record_val_type(rec));
+	if (n < 0) {
+		goto err;
+	}
+	out.buf += n;
+	out.len -= n;
+
+	n = record_encode(rec, out, w->hash_size);
+	if (n < 0) {
+		goto err;
+	}
+
+	out.buf += n;
+	out.len -= n;
+
+	if (block_writer_register_restart(w, start.len - out.len, restart,
+					  key) < 0) {
+		goto err;
+	}
+
+	free(slice_yield(&key));
+	return 0;
+
+err:
+	free(slice_yield(&key));
+	return -1;
+}
+
+int block_writer_register_restart(struct block_writer *w, int n, bool restart,
+				  struct slice key)
+{
+	int rlen = w->restart_len;
+	if (rlen >= MAX_RESTARTS) {
+		restart = false;
+	}
+
+	if (restart) {
+		rlen++;
+	}
+	if (2 + 3 * rlen + n > w->block_size - w->next) {
+		return -1;
+	}
+	if (restart) {
+		if (w->restart_len == w->restart_cap) {
+			w->restart_cap = w->restart_cap * 2 + 1;
+			w->restarts = realloc(
+				w->restarts, sizeof(uint32_t) * w->restart_cap);
+		}
+
+		w->restarts[w->restart_len++] = w->next;
+	}
+
+	w->next += n;
+	slice_copy(&w->last_key, key);
+	w->entries++;
+	return 0;
+}
+
+int block_writer_finish(struct block_writer *w)
+{
+	int i = 0;
+	for (i = 0; i < w->restart_len; i++) {
+		put_u24(w->buf + w->next, w->restarts[i]);
+		w->next += 3;
+	}
+
+	put_u16(w->buf + w->next, w->restart_len);
+	w->next += 2;
+	put_u24(w->buf + 1 + w->header_off, w->next);
+
+	if (block_writer_type(w) == BLOCK_TYPE_LOG) {
+		int block_header_skip = 4 + w->header_off;
+		struct slice compressed = {};
+		uLongf dest_len = 0, src_len = 0;
+		slice_resize(&compressed, w->next - block_header_skip);
+
+		dest_len = compressed.len;
+		src_len = w->next - block_header_skip;
+
+		if (Z_OK != compress2(compressed.buf, &dest_len,
+				      w->buf + block_header_skip, src_len, 9)) {
+			free(slice_yield(&compressed));
+			return ZLIB_ERROR;
+		}
+		memcpy(w->buf + block_header_skip, compressed.buf, dest_len);
+		w->next = dest_len + block_header_skip;
+	}
+	return w->next;
+}
+
+byte block_reader_type(struct block_reader *r)
+{
+	return r->block.data[r->header_off];
+}
+
+int block_reader_init(struct block_reader *br, struct block *block,
+		      uint32_t header_off, uint32_t table_block_size,
+		      int hash_size)
+{
+	uint32_t full_block_size = table_block_size;
+	byte typ = block->data[header_off];
+	uint32_t sz = get_u24(block->data + header_off + 1);
+
+	if (!is_block_type(typ)) {
+		return FORMAT_ERROR;
+	}
+
+	if (typ == BLOCK_TYPE_LOG) {
+		struct slice uncompressed = {};
+		int block_header_skip = 4 + header_off;
+		uLongf dst_len = sz - block_header_skip;
+		uLongf src_len = block->len - block_header_skip;
+
+		slice_resize(&uncompressed, sz);
+		memcpy(uncompressed.buf, block->data, block_header_skip);
+
+		if (Z_OK != uncompress_return_consumed(
+				    uncompressed.buf + block_header_skip,
+				    &dst_len, block->data + block_header_skip,
+				    &src_len)) {
+			free(slice_yield(&uncompressed));
+			return ZLIB_ERROR;
+		}
+
+		block_source_return_block(block->source, block);
+		block->data = uncompressed.buf;
+		block->len = dst_len; /* XXX: 4 bytes missing? */
+		block->source = malloc_block_source();
+		full_block_size = src_len + block_header_skip;
+	} else if (full_block_size == 0) {
+		full_block_size = sz;
+	} else if (sz < full_block_size && sz < block->len &&
+		   block->data[sz] != 0) {
+		/* If the block is smaller than the full block size, it is
+		   padded (data followed by '\0') or the next block is
+		   unaligned. */
+		full_block_size = sz;
+	}
+
+	{
+		uint16_t restart_count = get_u16(block->data + sz - 2);
+		uint32_t restart_start = sz - 2 - 3 * restart_count;
+
+		byte *restart_bytes = block->data + restart_start;
+
+		/* transfer ownership. */
+		br->block = *block;
+		block->data = NULL;
+		block->len = 0;
+
+		br->hash_size = hash_size;
+		br->block_len = restart_start;
+		br->full_block_size = full_block_size;
+		br->header_off = header_off;
+		br->restart_count = restart_count;
+		br->restart_bytes = restart_bytes;
+	}
+
+	return 0;
+}
+
+static uint32_t block_reader_restart_offset(struct block_reader *br, int i)
+{
+	return get_u24(br->restart_bytes + 3 * i);
+}
+
+void block_reader_start(struct block_reader *br, struct block_iter *it)
+{
+	it->br = br;
+	slice_resize(&it->last_key, 0);
+	it->next_off = br->header_off + 4;
+}
+
+struct restart_find_args {
+	struct slice key;
+	struct block_reader *r;
+	int error;
+};
+
+static int restart_key_less(int idx, void *args)
+{
+	struct restart_find_args *a = (struct restart_find_args *)args;
+	uint32_t off = block_reader_restart_offset(a->r, idx);
+	struct slice in = {
+		.buf = a->r->block.data + off,
+		.len = a->r->block_len - off,
+	};
+
+	/* the restart key is verbatim in the block, so this could avoid the
+	   alloc for decoding the key */
+	struct slice rkey = {};
+	struct slice last_key = {};
+	byte unused_extra;
+	int n = decode_key(&rkey, &unused_extra, last_key, in);
+	if (n < 0) {
+		a->error = 1;
+		return -1;
+	}
+
+	{
+		int result = slice_compare(a->key, rkey);
+		free(slice_yield(&rkey));
+		return result;
+	}
+}
+
+void block_iter_copy_from(struct block_iter *dest, struct block_iter *src)
+{
+	dest->br = src->br;
+	dest->next_off = src->next_off;
+	slice_copy(&dest->last_key, src->last_key);
+}
+
+/* return < 0 for error, 0 for OK, > 0 for EOF. */
+int block_iter_next(struct block_iter *it, struct record rec)
+{
+	if (it->next_off >= it->br->block_len) {
+		return 1;
+	}
+
+	{
+		struct slice in = {
+			.buf = it->br->block.data + it->next_off,
+			.len = it->br->block_len - it->next_off,
+		};
+		struct slice start = in;
+		struct slice key = {};
+		byte extra;
+		int n = decode_key(&key, &extra, it->last_key, in);
+		if (n < 0) {
+			return -1;
+		}
+
+		in.buf += n;
+		in.len -= n;
+		n = record_decode(rec, key, extra, in, it->br->hash_size);
+		if (n < 0) {
+			return -1;
+		}
+		in.buf += n;
+		in.len -= n;
+
+		slice_copy(&it->last_key, key);
+		it->next_off += start.len - in.len;
+		free(slice_yield(&key));
+		return 0;
+	}
+}
+
+int block_reader_first_key(struct block_reader *br, struct slice *key)
+{
+	struct slice empty = {};
+	int off = br->header_off + 4;
+	struct slice in = {
+		.buf = br->block.data + off,
+		.len = br->block_len - off,
+	};
+
+	byte extra = 0;
+	int n = decode_key(key, &extra, empty, in);
+	if (n < 0) {
+		return n;
+	}
+	return 0;
+}
+
+int block_iter_seek(struct block_iter *it, struct slice want)
+{
+	return block_reader_seek(it->br, it, want);
+}
+
+void block_iter_close(struct block_iter *it)
+{
+	free(slice_yield(&it->last_key));
+}
+
+int block_reader_seek(struct block_reader *br, struct block_iter *it,
+		      struct slice want)
+{
+	struct restart_find_args args = {
+		.key = want,
+		.r = br,
+	};
+
+	int i = binsearch(br->restart_count, &restart_key_less, &args);
+	if (args.error) {
+		return -1;
+	}
+
+	it->br = br;
+	if (i > 0) {
+		i--;
+		it->next_off = block_reader_restart_offset(br, i);
+	} else {
+		it->next_off = br->header_off + 4;
+	}
+
+	{
+		struct record rec = new_record(block_reader_type(br));
+		struct slice key = {};
+		int result = 0;
+		int err = 0;
+		struct block_iter next = {};
+		while (true) {
+			block_iter_copy_from(&next, it);
+
+			err = block_iter_next(&next, rec);
+			if (err < 0) {
+				result = -1;
+				goto exit;
+			}
+
+			record_key(rec, &key);
+			if (err > 0 || slice_compare(key, want) >= 0) {
+				result = 0;
+				goto exit;
+			}
+
+			block_iter_copy_from(it, &next);
+		}
+
+	exit:
+		free(slice_yield(&key));
+		free(slice_yield(&next.last_key));
+		record_clear(rec);
+		free(record_yield(&rec));
+
+		return result;
+	}
+}
+
+void block_writer_reset(struct block_writer *bw)
+{
+	bw->restart_len = 0;
+	bw->last_key.len = 0;
+}
+
+void block_writer_clear(struct block_writer *bw)
+{
+	FREE_AND_NULL(bw->restarts);
+	free(slice_yield(&bw->last_key));
+	/* the block is not owned. */
+}
diff --git a/reftable/block.h b/reftable/block.h
new file mode 100644
index 0000000000..bb42588111
--- /dev/null
+++ b/reftable/block.h
@@ -0,0 +1,71 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef BLOCK_H
+#define BLOCK_H
+
+#include "basics.h"
+#include "record.h"
+#include "reftable.h"
+
+struct block_writer {
+	byte *buf;
+	uint32_t block_size;
+	uint32_t header_off;
+	int restart_interval;
+	int hash_size;
+
+	uint32_t next;
+	uint32_t *restarts;
+	uint32_t restart_len;
+	uint32_t restart_cap;
+	struct slice last_key;
+	int entries;
+};
+
+void block_writer_init(struct block_writer *bw, byte typ, byte *buf,
+		       uint32_t block_size, uint32_t header_off, int hash_size);
+byte block_writer_type(struct block_writer *bw);
+int block_writer_add(struct block_writer *w, struct record rec);
+int block_writer_finish(struct block_writer *w);
+void block_writer_reset(struct block_writer *bw);
+void block_writer_clear(struct block_writer *bw);
+
+struct block_reader {
+	uint32_t header_off;
+	struct block block;
+	int hash_size;
+
+	/* size of the data, excluding restart data. */
+	uint32_t block_len;
+	byte *restart_bytes;
+	uint32_t full_block_size;
+	uint16_t restart_count;
+};
+
+struct block_iter {
+	struct block_reader *br;
+	struct slice last_key;
+	uint32_t next_off;
+};
+
+int block_reader_init(struct block_reader *br, struct block *bl,
+		      uint32_t header_off, uint32_t table_block_size,
+		      int hash_size);
+void block_reader_start(struct block_reader *br, struct block_iter *it);
+int block_reader_seek(struct block_reader *br, struct block_iter *it,
+		      struct slice want);
+byte block_reader_type(struct block_reader *r);
+int block_reader_first_key(struct block_reader *br, struct slice *key);
+
+void block_iter_copy_from(struct block_iter *dest, struct block_iter *src);
+int block_iter_next(struct block_iter *it, struct record rec);
+int block_iter_seek(struct block_iter *it, struct slice want);
+void block_iter_close(struct block_iter *it);
+
+#endif
diff --git a/reftable/blocksource.h b/reftable/blocksource.h
new file mode 100644
index 0000000000..f3ad3a4c22
--- /dev/null
+++ b/reftable/blocksource.h
@@ -0,0 +1,20 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef BLOCKSOURCE_H
+#define BLOCKSOURCE_H
+
+#include "reftable.h"
+
+uint64_t block_source_size(struct block_source source);
+int block_source_read_block(struct block_source source, struct block *dest,
+			    uint64_t off, uint32_t size);
+void block_source_return_block(struct block_source source, struct block *ret);
+void block_source_close(struct block_source source);
+
+#endif
diff --git a/reftable/bytes.c b/reftable/bytes.c
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/reftable/config.h b/reftable/config.h
new file mode 100644
index 0000000000..40a8c178f1
--- /dev/null
+++ b/reftable/config.h
@@ -0,0 +1 @@
+/* empty */
diff --git a/reftable/constants.h b/reftable/constants.h
new file mode 100644
index 0000000000..cd35704610
--- /dev/null
+++ b/reftable/constants.h
@@ -0,0 +1,27 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef CONSTANTS_H
+#define CONSTANTS_H
+
+#define SHA1_SIZE 20
+#define SHA256_SIZE 32
+#define VERSION 1
+#define HEADER_SIZE 24
+#define FOOTER_SIZE 68
+
+#define BLOCK_TYPE_LOG 'g'
+#define BLOCK_TYPE_INDEX 'i'
+#define BLOCK_TYPE_REF 'r'
+#define BLOCK_TYPE_OBJ 'o'
+#define BLOCK_TYPE_ANY 0
+
+#define MAX_RESTARTS ((1 << 16) - 1)
+#define DEFAULT_BLOCK_SIZE 4096
+
+#endif
diff --git a/reftable/dump.c b/reftable/dump.c
new file mode 100644
index 0000000000..acabe18fbe
--- /dev/null
+++ b/reftable/dump.c
@@ -0,0 +1,97 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "system.h"
+
+#include "reftable.h"
+
+static int dump_table(const char *tablename)
+{
+	struct block_source src = {};
+	int err = block_source_from_file(&src, tablename);
+	if (err < 0) {
+		return err;
+	}
+
+	struct reader *r = NULL;
+	err = new_reader(&r, src, tablename);
+	if (err < 0) {
+		return err;
+	}
+
+	{
+		struct iterator it = {};
+		err = reader_seek_ref(r, &it, "");
+		if (err < 0) {
+			return err;
+		}
+
+		struct ref_record ref = {};
+		while (1) {
+			err = iterator_next_ref(it, &ref);
+			if (err > 0) {
+				break;
+			}
+			if (err < 0) {
+				return err;
+			}
+			ref_record_print(&ref, 20);
+		}
+		iterator_destroy(&it);
+		ref_record_clear(&ref);
+	}
+
+	{
+		struct iterator it = {};
+		err = reader_seek_log(r, &it, "");
+		if (err < 0) {
+			return err;
+		}
+		struct log_record log = {};
+		while (1) {
+			err = iterator_next_log(it, &log);
+			if (err > 0) {
+				break;
+			}
+			if (err < 0) {
+				return err;
+			}
+			log_record_print(&log, 20);
+		}
+		iterator_destroy(&it);
+		log_record_clear(&log);
+	}
+	return 0;
+}
+
+int main(int argc, char *argv[])
+{
+	int opt;
+	const char *table = NULL;
+	while ((opt = getopt(argc, argv, "t:")) != -1) {
+		switch (opt) {
+		case 't':
+			table = strdup(optarg);
+			break;
+		case '?':
+			printf("usage: %s [-table tablefile]\n", argv[0]);
+			return 2;
+			break;
+		}
+	}
+
+	if (table != NULL) {
+		int err = dump_table(table);
+		if (err < 0) {
+			fprintf(stderr, "%s: %s: %s\n", argv[0], table,
+				error_str(err));
+			return 1;
+		}
+	}
+	return 0;
+}
diff --git a/reftable/file.c b/reftable/file.c
new file mode 100644
index 0000000000..b2ea90bf94
--- /dev/null
+++ b/reftable/file.c
@@ -0,0 +1,97 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "system.h"
+
+#include "block.h"
+#include "iter.h"
+#include "record.h"
+#include "reftable.h"
+#include "tree.h"
+
+struct file_block_source {
+	int fd;
+	uint64_t size;
+};
+
+static uint64_t file_size(void *b)
+{
+	return ((struct file_block_source *)b)->size;
+}
+
+static void file_return_block(void *b, struct block *dest)
+{
+	memset(dest->data, 0xff, dest->len);
+	free(dest->data);
+}
+
+static void file_close(void *b)
+{
+	int fd = ((struct file_block_source *)b)->fd;
+	if (fd > 0) {
+		close(fd);
+		((struct file_block_source *)b)->fd = 0;
+	}
+
+	free(b);
+}
+
+static int file_read_block(void *v, struct block *dest, uint64_t off,
+			   uint32_t size)
+{
+	struct file_block_source *b = (struct file_block_source *)v;
+	assert(off + size <= b->size);
+	dest->data = malloc(size);
+	if (pread(b->fd, dest->data, size, off) != size) {
+		return -1;
+	}
+	dest->len = size;
+	return size;
+}
+
+struct block_source_vtable file_vtable = {
+	.size = &file_size,
+	.read_block = &file_read_block,
+	.return_block = &file_return_block,
+	.close = &file_close,
+};
+
+int block_source_from_file(struct block_source *bs, const char *name)
+{
+	struct stat st = {};
+	int err = 0;
+	int fd = open(name, O_RDONLY);
+	if (fd < 0) {
+		if (errno == ENOENT) {
+			return NOT_EXIST_ERROR;
+		}
+		return -1;
+	}
+
+	err = fstat(fd, &st);
+	if (err < 0) {
+		return -1;
+	}
+
+	{
+		struct file_block_source *p =
+			calloc(sizeof(struct file_block_source), 1);
+		p->size = st.st_size;
+		p->fd = fd;
+
+		bs->ops = &file_vtable;
+		bs->arg = p;
+	}
+	return 0;
+}
+
+int fd_writer(void *arg, byte *data, int sz)
+{
+	int *fdp = (int *)arg;
+	return write(*fdp, data, sz);
+}
diff --git a/reftable/iter.c b/reftable/iter.c
new file mode 100644
index 0000000000..c06c891366
--- /dev/null
+++ b/reftable/iter.c
@@ -0,0 +1,229 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "iter.h"
+
+#include "system.h"
+
+#include "block.h"
+#include "constants.h"
+#include "reader.h"
+#include "reftable.h"
+
+bool iterator_is_null(struct iterator it)
+{
+	return it.ops == NULL;
+}
+
+static int empty_iterator_next(void *arg, struct record rec)
+{
+	return 1;
+}
+
+static void empty_iterator_close(void *arg)
+{
+}
+
+struct iterator_vtable empty_vtable = {
+	.next = &empty_iterator_next,
+	.close = &empty_iterator_close,
+};
+
+void iterator_set_empty(struct iterator *it)
+{
+	it->iter_arg = NULL;
+	it->ops = &empty_vtable;
+}
+
+int iterator_next(struct iterator it, struct record rec)
+{
+	return it.ops->next(it.iter_arg, rec);
+}
+
+void iterator_destroy(struct iterator *it)
+{
+	if (it->ops == NULL) {
+		return;
+	}
+	it->ops->close(it->iter_arg);
+	it->ops = NULL;
+	FREE_AND_NULL(it->iter_arg);
+}
+
+int iterator_next_ref(struct iterator it, struct ref_record *ref)
+{
+	struct record rec = {};
+	record_from_ref(&rec, ref);
+	return iterator_next(it, rec);
+}
+
+int iterator_next_log(struct iterator it, struct log_record *log)
+{
+	struct record rec = {};
+	record_from_log(&rec, log);
+	return iterator_next(it, rec);
+}
+
+static void filtering_ref_iterator_close(void *iter_arg)
+{
+	struct filtering_ref_iterator *fri =
+		(struct filtering_ref_iterator *)iter_arg;
+	free(slice_yield(&fri->oid));
+	iterator_destroy(&fri->it);
+}
+
+static int filtering_ref_iterator_next(void *iter_arg, struct record rec)
+{
+	struct filtering_ref_iterator *fri =
+		(struct filtering_ref_iterator *)iter_arg;
+	struct ref_record *ref = (struct ref_record *)rec.data;
+
+	while (true) {
+		int err = iterator_next_ref(fri->it, ref);
+		if (err != 0) {
+			return err;
+		}
+
+		if (fri->double_check) {
+			struct iterator it = {};
+
+			int err = reader_seek_ref(fri->r, &it, ref->ref_name);
+			if (err == 0) {
+				err = iterator_next_ref(it, ref);
+			}
+
+			iterator_destroy(&it);
+
+			if (err < 0) {
+				return err;
+			}
+
+			if (err > 0) {
+				continue;
+			}
+		}
+
+		if ((ref->target_value != NULL &&
+		     !memcmp(fri->oid.buf, ref->target_value, fri->oid.len)) ||
+		    (ref->value != NULL &&
+		     !memcmp(fri->oid.buf, ref->value, fri->oid.len))) {
+			return 0;
+		}
+	}
+}
+
+struct iterator_vtable filtering_ref_iterator_vtable = {
+	.next = &filtering_ref_iterator_next,
+	.close = &filtering_ref_iterator_close,
+};
+
+void iterator_from_filtering_ref_iterator(struct iterator *it,
+					  struct filtering_ref_iterator *fri)
+{
+	it->iter_arg = fri;
+	it->ops = &filtering_ref_iterator_vtable;
+}
+
+static void indexed_table_ref_iter_close(void *p)
+{
+	struct indexed_table_ref_iter *it = (struct indexed_table_ref_iter *)p;
+	block_iter_close(&it->cur);
+	reader_return_block(it->r, &it->block_reader.block);
+	free(slice_yield(&it->oid));
+}
+
+static int indexed_table_ref_iter_next_block(struct indexed_table_ref_iter *it)
+{
+	if (it->offset_idx == it->offset_len) {
+		it->finished = true;
+		return 1;
+	}
+
+	reader_return_block(it->r, &it->block_reader.block);
+
+	{
+		uint64_t off = it->offsets[it->offset_idx++];
+		int err = reader_init_block_reader(it->r, &it->block_reader,
+						   off, BLOCK_TYPE_REF);
+		if (err < 0) {
+			return err;
+		}
+		if (err > 0) {
+			/* indexed block does not exist. */
+			return FORMAT_ERROR;
+		}
+	}
+	block_reader_start(&it->block_reader, &it->cur);
+	return 0;
+}
+
+static int indexed_table_ref_iter_next(void *p, struct record rec)
+{
+	struct indexed_table_ref_iter *it = (struct indexed_table_ref_iter *)p;
+	struct ref_record *ref = (struct ref_record *)rec.data;
+
+	while (true) {
+		int err = block_iter_next(&it->cur, rec);
+		if (err < 0) {
+			return err;
+		}
+
+		if (err > 0) {
+			err = indexed_table_ref_iter_next_block(it);
+			if (err < 0) {
+				return err;
+			}
+
+			if (it->finished) {
+				return 1;
+			}
+			continue;
+		}
+
+		if (!memcmp(it->oid.buf, ref->target_value, it->oid.len) ||
+		    !memcmp(it->oid.buf, ref->value, it->oid.len)) {
+			return 0;
+		}
+	}
+}
+
+int new_indexed_table_ref_iter(struct indexed_table_ref_iter **dest,
+			       struct reader *r, byte *oid, int oid_len,
+			       uint64_t *offsets, int offset_len)
+{
+	struct indexed_table_ref_iter *itr =
+		calloc(sizeof(struct indexed_table_ref_iter), 1);
+	int err = 0;
+
+	itr->r = r;
+	slice_resize(&itr->oid, oid_len);
+	memcpy(itr->oid.buf, oid, oid_len);
+
+	itr->offsets = offsets;
+	itr->offset_len = offset_len;
+
+	err = indexed_table_ref_iter_next_block(itr);
+	if (err < 0) {
+		free(itr);
+	} else {
+		*dest = itr;
+	}
+	return err;
+}
+
+struct iterator_vtable indexed_table_ref_iter_vtable = {
+	.next = &indexed_table_ref_iter_next,
+	.close = &indexed_table_ref_iter_close,
+};
+
+void iterator_from_indexed_table_ref_iter(struct iterator *it,
+					  struct indexed_table_ref_iter *itr)
+{
+	it->iter_arg = itr;
+	it->ops = &indexed_table_ref_iter_vtable;
+}
diff --git a/reftable/iter.h b/reftable/iter.h
new file mode 100644
index 0000000000..f497f2a27e
--- /dev/null
+++ b/reftable/iter.h
@@ -0,0 +1,56 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef ITER_H
+#define ITER_H
+
+#include "block.h"
+#include "record.h"
+#include "slice.h"
+
+struct iterator_vtable {
+	int (*next)(void *iter_arg, struct record rec);
+	void (*close)(void *iter_arg);
+};
+
+void iterator_set_empty(struct iterator *it);
+int iterator_next(struct iterator it, struct record rec);
+bool iterator_is_null(struct iterator it);
+
+struct filtering_ref_iterator {
+	struct reader *r;
+	struct slice oid;
+	bool double_check;
+	struct iterator it;
+};
+
+void iterator_from_filtering_ref_iterator(struct iterator *,
+					  struct filtering_ref_iterator *);
+
+struct indexed_table_ref_iter {
+	struct reader *r;
+	struct slice oid;
+
+	/* mutable */
+	uint64_t *offsets;
+
+	/* Points to the next offset to read. */
+	int offset_idx;
+	int offset_len;
+	struct block_reader block_reader;
+	struct block_iter cur;
+	bool finished;
+};
+
+void iterator_from_indexed_table_ref_iter(struct iterator *it,
+					  struct indexed_table_ref_iter *itr);
+int new_indexed_table_ref_iter(struct indexed_table_ref_iter **dest,
+			       struct reader *r, byte *oid, int oid_len,
+			       uint64_t *offsets, int offset_len);
+
+#endif
diff --git a/reftable/merged.c b/reftable/merged.c
new file mode 100644
index 0000000000..7d52ec6232
--- /dev/null
+++ b/reftable/merged.c
@@ -0,0 +1,286 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "merged.h"
+
+#include "system.h"
+
+#include "constants.h"
+#include "iter.h"
+#include "pq.h"
+#include "reader.h"
+
+static int merged_iter_init(struct merged_iter *mi)
+{
+	int i = 0;
+	for (i = 0; i < mi->stack_len; i++) {
+		struct record rec = new_record(mi->typ);
+		int err = iterator_next(mi->stack[i], rec);
+		if (err < 0) {
+			return err;
+		}
+
+		if (err > 0) {
+			iterator_destroy(&mi->stack[i]);
+			record_clear(rec);
+			free(record_yield(&rec));
+		} else {
+			struct pq_entry e = {
+				.rec = rec,
+				.index = i,
+			};
+			merged_iter_pqueue_add(&mi->pq, e);
+		}
+	}
+
+	return 0;
+}
+
+static void merged_iter_close(void *p)
+{
+	struct merged_iter *mi = (struct merged_iter *)p;
+	int i = 0;
+	merged_iter_pqueue_clear(&mi->pq);
+	for (i = 0; i < mi->stack_len; i++) {
+		iterator_destroy(&mi->stack[i]);
+	}
+	free(mi->stack);
+}
+
+static int merged_iter_advance_subiter(struct merged_iter *mi, int idx)
+{
+	if (iterator_is_null(mi->stack[idx])) {
+		return 0;
+	}
+
+	{
+		struct record rec = new_record(mi->typ);
+		struct pq_entry e = {
+			.rec = rec,
+			.index = idx,
+		};
+		int err = iterator_next(mi->stack[idx], rec);
+		if (err < 0) {
+			return err;
+		}
+
+		if (err > 0) {
+			iterator_destroy(&mi->stack[idx]);
+			record_clear(rec);
+			free(record_yield(&rec));
+			return 0;
+		}
+
+		merged_iter_pqueue_add(&mi->pq, e);
+	}
+	return 0;
+}
+
+static int merged_iter_next(struct merged_iter *mi, struct record rec)
+{
+	struct slice entry_key = {};
+	struct pq_entry entry = merged_iter_pqueue_remove(&mi->pq);
+	int err = merged_iter_advance_subiter(mi, entry.index);
+	if (err < 0) {
+		return err;
+	}
+
+	record_key(entry.rec, &entry_key);
+	while (!merged_iter_pqueue_is_empty(mi->pq)) {
+		struct pq_entry top = merged_iter_pqueue_top(mi->pq);
+		struct slice k = {};
+		int err = 0, cmp = 0;
+
+		record_key(top.rec, &k);
+
+		cmp = slice_compare(k, entry_key);
+		free(slice_yield(&k));
+
+		if (cmp > 0) {
+			break;
+		}
+
+		merged_iter_pqueue_remove(&mi->pq);
+		err = merged_iter_advance_subiter(mi, top.index);
+		if (err < 0) {
+			return err;
+		}
+		record_clear(top.rec);
+		free(record_yield(&top.rec));
+	}
+
+	record_copy_from(rec, entry.rec, mi->hash_size);
+	record_clear(entry.rec);
+	free(record_yield(&entry.rec));
+	free(slice_yield(&entry_key));
+	return 0;
+}
+
+static int merged_iter_next_void(void *p, struct record rec)
+{
+	struct merged_iter *mi = (struct merged_iter *)p;
+	if (merged_iter_pqueue_is_empty(mi->pq)) {
+		return 1;
+	}
+
+	return merged_iter_next(mi, rec);
+}
+
+struct iterator_vtable merged_iter_vtable = {
+	.next = &merged_iter_next_void,
+	.close = &merged_iter_close,
+};
+
+static void iterator_from_merged_iter(struct iterator *it,
+				      struct merged_iter *mi)
+{
+	it->iter_arg = mi;
+	it->ops = &merged_iter_vtable;
+}
+
+int new_merged_table(struct merged_table **dest, struct reader **stack, int n)
+{
+	uint64_t last_max = 0;
+	uint64_t first_min = 0;
+	int i = 0;
+	for (i = 0; i < n; i++) {
+		struct reader *r = stack[i];
+		if (i > 0 && last_max >= reader_min_update_index(r)) {
+			return FORMAT_ERROR;
+		}
+		if (i == 0) {
+			first_min = reader_min_update_index(r);
+		}
+
+		last_max = reader_max_update_index(r);
+	}
+
+	{
+		struct merged_table m = {
+			.stack = stack,
+			.stack_len = n,
+			.min = first_min,
+			.max = last_max,
+			.hash_size = SHA1_SIZE,
+		};
+
+		*dest = calloc(sizeof(struct merged_table), 1);
+		**dest = m;
+	}
+	return 0;
+}
+
+void merged_table_close(struct merged_table *mt)
+{
+	int i = 0;
+	for (i = 0; i < mt->stack_len; i++) {
+		reader_free(mt->stack[i]);
+	}
+	FREE_AND_NULL(mt->stack);
+	mt->stack_len = 0;
+}
+
+/* clears the list of subtable, without affecting the readers themselves. */
+void merged_table_clear(struct merged_table *mt)
+{
+	FREE_AND_NULL(mt->stack);
+	mt->stack_len = 0;
+}
+
+void merged_table_free(struct merged_table *mt)
+{
+	if (mt == NULL) {
+		return;
+	}
+	merged_table_clear(mt);
+	free(mt);
+}
+
+uint64_t merged_max_update_index(struct merged_table *mt)
+{
+	return mt->max;
+}
+
+uint64_t merged_min_update_index(struct merged_table *mt)
+{
+	return mt->min;
+}
+
+static int merged_table_seek_record(struct merged_table *mt,
+				    struct iterator *it, struct record rec)
+{
+	struct iterator *iters = calloc(sizeof(struct iterator), mt->stack_len);
+	struct merged_iter merged = {
+		.stack = iters,
+		.typ = record_type(rec),
+		.hash_size = mt->hash_size,
+	};
+	int n = 0;
+	int err = 0;
+	int i = 0;
+	for (i = 0; i < mt->stack_len && err == 0; i++) {
+		int e = reader_seek(mt->stack[i], &iters[n], rec);
+		if (e < 0) {
+			err = e;
+		}
+		if (e == 0) {
+			n++;
+		}
+	}
+	if (err < 0) {
+		int i = 0;
+		for (i = 0; i < n; i++) {
+			iterator_destroy(&iters[i]);
+		}
+		free(iters);
+		return err;
+	}
+
+	merged.stack_len = n, err = merged_iter_init(&merged);
+	if (err < 0) {
+		merged_iter_close(&merged);
+		return err;
+	}
+
+	{
+		struct merged_iter *p = malloc(sizeof(struct merged_iter));
+		*p = merged;
+		iterator_from_merged_iter(it, p);
+	}
+	return 0;
+}
+
+int merged_table_seek_ref(struct merged_table *mt, struct iterator *it,
+			  const char *name)
+{
+	struct ref_record ref = {
+		.ref_name = (char *)name,
+	};
+	struct record rec = {};
+	record_from_ref(&rec, &ref);
+	return merged_table_seek_record(mt, it, rec);
+}
+
+int merged_table_seek_log_at(struct merged_table *mt, struct iterator *it,
+			     const char *name, uint64_t update_index)
+{
+	struct log_record log = {
+		.ref_name = (char *)name,
+		.update_index = update_index,
+	};
+	struct record rec = {};
+	record_from_log(&rec, &log);
+	return merged_table_seek_record(mt, it, rec);
+}
+
+int merged_table_seek_log(struct merged_table *mt, struct iterator *it,
+			  const char *name)
+{
+	uint64_t max = ~((uint64_t)0);
+	return merged_table_seek_log_at(mt, it, name, max);
+}
diff --git a/reftable/merged.h b/reftable/merged.h
new file mode 100644
index 0000000000..b8d3572e26
--- /dev/null
+++ b/reftable/merged.h
@@ -0,0 +1,34 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef MERGED_H
+#define MERGED_H
+
+#include "pq.h"
+#include "reftable.h"
+
+struct merged_table {
+	struct reader **stack;
+	int stack_len;
+	int hash_size;
+
+	uint64_t min;
+	uint64_t max;
+};
+
+struct merged_iter {
+	struct iterator *stack;
+	int hash_size;
+	int stack_len;
+	byte typ;
+	struct merged_iter_pqueue pq;
+} merged_iter;
+
+void merged_table_clear(struct merged_table *mt);
+
+#endif
diff --git a/reftable/pq.c b/reftable/pq.c
new file mode 100644
index 0000000000..a1aff7c98c
--- /dev/null
+++ b/reftable/pq.c
@@ -0,0 +1,114 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "pq.h"
+
+#include "system.h"
+
+int pq_less(struct pq_entry a, struct pq_entry b)
+{
+	struct slice ak = {};
+	struct slice bk = {};
+	int cmp = 0;
+	record_key(a.rec, &ak);
+	record_key(b.rec, &bk);
+
+	cmp = slice_compare(ak, bk);
+
+	free(slice_yield(&ak));
+	free(slice_yield(&bk));
+
+	if (cmp == 0) {
+		return a.index > b.index;
+	}
+
+	return cmp < 0;
+}
+
+struct pq_entry merged_iter_pqueue_top(struct merged_iter_pqueue pq)
+{
+	return pq.heap[0];
+}
+
+bool merged_iter_pqueue_is_empty(struct merged_iter_pqueue pq)
+{
+	return pq.len == 0;
+}
+
+void merged_iter_pqueue_check(struct merged_iter_pqueue pq)
+{
+	int i = 0;
+	for (i = 1; i < pq.len; i++) {
+		int parent = (i - 1) / 2;
+
+		assert(pq_less(pq.heap[parent], pq.heap[i]));
+	}
+}
+
+struct pq_entry merged_iter_pqueue_remove(struct merged_iter_pqueue *pq)
+{
+	int i = 0;
+	struct pq_entry e = pq->heap[0];
+	pq->heap[0] = pq->heap[pq->len - 1];
+	pq->len--;
+
+	i = 0;
+	while (i < pq->len) {
+		int min = i;
+		int j = 2 * i + 1;
+		int k = 2 * i + 2;
+		if (j < pq->len && pq_less(pq->heap[j], pq->heap[i])) {
+			min = j;
+		}
+		if (k < pq->len && pq_less(pq->heap[k], pq->heap[min])) {
+			min = k;
+		}
+
+		if (min == i) {
+			break;
+		}
+
+		SWAP(pq->heap[i], pq->heap[min]);
+		i = min;
+	}
+
+	return e;
+}
+
+void merged_iter_pqueue_add(struct merged_iter_pqueue *pq, struct pq_entry e)
+{
+	int i = 0;
+	if (pq->len == pq->cap) {
+		pq->cap = 2 * pq->cap + 1;
+		pq->heap = realloc(pq->heap, pq->cap * sizeof(struct pq_entry));
+	}
+
+	pq->heap[pq->len++] = e;
+	i = pq->len - 1;
+	while (i > 0) {
+		int j = (i - 1) / 2;
+		if (pq_less(pq->heap[j], pq->heap[i])) {
+			break;
+		}
+
+		SWAP(pq->heap[j], pq->heap[i]);
+
+		i = j;
+	}
+}
+
+void merged_iter_pqueue_clear(struct merged_iter_pqueue *pq)
+{
+	int i = 0;
+	for (i = 0; i < pq->len; i++) {
+		record_clear(pq->heap[i].rec);
+		free(record_yield(&pq->heap[i].rec));
+	}
+	FREE_AND_NULL(pq->heap);
+	pq->len = pq->cap = 0;
+}
diff --git a/reftable/pq.h b/reftable/pq.h
new file mode 100644
index 0000000000..5f7018979d
--- /dev/null
+++ b/reftable/pq.h
@@ -0,0 +1,34 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef PQ_H
+#define PQ_H
+
+#include "record.h"
+
+struct pq_entry {
+	struct record rec;
+	int index;
+};
+
+int pq_less(struct pq_entry a, struct pq_entry b);
+
+struct merged_iter_pqueue {
+	struct pq_entry *heap;
+	int len;
+	int cap;
+};
+
+struct pq_entry merged_iter_pqueue_top(struct merged_iter_pqueue pq);
+bool merged_iter_pqueue_is_empty(struct merged_iter_pqueue pq);
+void merged_iter_pqueue_check(struct merged_iter_pqueue pq);
+struct pq_entry merged_iter_pqueue_remove(struct merged_iter_pqueue *pq);
+void merged_iter_pqueue_add(struct merged_iter_pqueue *pq, struct pq_entry e);
+void merged_iter_pqueue_clear(struct merged_iter_pqueue *pq);
+
+#endif
diff --git a/reftable/reader.c b/reftable/reader.c
new file mode 100644
index 0000000000..981911f076
--- /dev/null
+++ b/reftable/reader.c
@@ -0,0 +1,708 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "reader.h"
+
+#include "system.h"
+
+#include "block.h"
+#include "constants.h"
+#include "iter.h"
+#include "record.h"
+#include "reftable.h"
+#include "tree.h"
+
+uint64_t block_source_size(struct block_source source)
+{
+	return source.ops->size(source.arg);
+}
+
+int block_source_read_block(struct block_source source, struct block *dest,
+			    uint64_t off, uint32_t size)
+{
+	int result = source.ops->read_block(source.arg, dest, off, size);
+	dest->source = source;
+	return result;
+}
+
+void block_source_return_block(struct block_source source, struct block *blockp)
+{
+	source.ops->return_block(source.arg, blockp);
+	blockp->data = NULL;
+	blockp->len = 0;
+	blockp->source.ops = NULL;
+	blockp->source.arg = NULL;
+}
+
+void block_source_close(struct block_source *source)
+{
+	if (source->ops == NULL) {
+		return;
+	}
+
+	source->ops->close(source->arg);
+	source->ops = NULL;
+}
+
+static struct reader_offsets *reader_offsets_for(struct reader *r, byte typ)
+{
+	switch (typ) {
+	case BLOCK_TYPE_REF:
+		return &r->ref_offsets;
+	case BLOCK_TYPE_LOG:
+		return &r->log_offsets;
+	case BLOCK_TYPE_OBJ:
+		return &r->obj_offsets;
+	}
+	abort();
+}
+
+static int reader_get_block(struct reader *r, struct block *dest, uint64_t off,
+			    uint32_t sz)
+{
+	if (off >= r->size) {
+		return 0;
+	}
+
+	if (off + sz > r->size) {
+		sz = r->size - off;
+	}
+
+	return block_source_read_block(r->source, dest, off, sz);
+}
+
+void reader_return_block(struct reader *r, struct block *p)
+{
+	block_source_return_block(r->source, p);
+}
+
+const char *reader_name(struct reader *r)
+{
+	return r->name;
+}
+
+static int parse_footer(struct reader *r, byte *footer, byte *header)
+{
+	byte *f = footer;
+	int err = 0;
+	if (memcmp(f, "REFT", 4)) {
+		err = FORMAT_ERROR;
+		goto exit;
+	}
+	f += 4;
+
+	if (memcmp(footer, header, HEADER_SIZE)) {
+		err = FORMAT_ERROR;
+		goto exit;
+	}
+
+	{
+		byte version = *f++;
+		if (version != 1) {
+			err = FORMAT_ERROR;
+			goto exit;
+		}
+	}
+
+	r->block_size = get_u24(f);
+
+	f += 3;
+	r->min_update_index = get_u64(f);
+	f += 8;
+	r->max_update_index = get_u64(f);
+	f += 8;
+
+	r->ref_offsets.index_offset = get_u64(f);
+	f += 8;
+
+	r->obj_offsets.offset = get_u64(f);
+	f += 8;
+
+	r->object_id_len = r->obj_offsets.offset & ((1 << 5) - 1);
+	r->obj_offsets.offset >>= 5;
+
+	r->obj_offsets.index_offset = get_u64(f);
+	f += 8;
+	r->log_offsets.offset = get_u64(f);
+	f += 8;
+	r->log_offsets.index_offset = get_u64(f);
+	f += 8;
+
+	{
+		uint32_t computed_crc = crc32(0, footer, f - footer);
+		uint32_t file_crc = get_u32(f);
+		f += 4;
+		if (computed_crc != file_crc) {
+			err = FORMAT_ERROR;
+			goto exit;
+		}
+	}
+
+	{
+		byte first_block_typ = header[HEADER_SIZE];
+		r->ref_offsets.present = (first_block_typ == BLOCK_TYPE_REF);
+		r->ref_offsets.offset = 0;
+		r->log_offsets.present = (first_block_typ == BLOCK_TYPE_LOG ||
+					  r->log_offsets.offset > 0);
+		r->obj_offsets.present = r->obj_offsets.offset > 0;
+	}
+	err = 0;
+exit:
+	return err;
+}
+
+int init_reader(struct reader *r, struct block_source source, const char *name)
+{
+	struct block footer = {};
+	struct block header = {};
+	int err = 0;
+
+	memset(r, 0, sizeof(struct reader));
+	r->size = block_source_size(source) - FOOTER_SIZE;
+	r->source = source;
+	r->name = strdup(name);
+	r->hash_size = SHA1_SIZE;
+
+	err = block_source_read_block(source, &footer, r->size, FOOTER_SIZE);
+	if (err != FOOTER_SIZE) {
+		err = IO_ERROR;
+		goto exit;
+	}
+
+	/* Need +1 to read type of first block. */
+	err = reader_get_block(r, &header, 0, HEADER_SIZE + 1);
+	if (err != HEADER_SIZE + 1) {
+		err = IO_ERROR;
+		goto exit;
+	}
+
+	err = parse_footer(r, footer.data, header.data);
+exit:
+	block_source_return_block(r->source, &footer);
+	block_source_return_block(r->source, &header);
+	return err;
+}
+
+struct table_iter {
+	struct reader *r;
+	byte typ;
+	uint64_t block_off;
+	struct block_iter bi;
+	bool finished;
+};
+
+static void table_iter_copy_from(struct table_iter *dest,
+				 struct table_iter *src)
+{
+	dest->r = src->r;
+	dest->typ = src->typ;
+	dest->block_off = src->block_off;
+	dest->finished = src->finished;
+	block_iter_copy_from(&dest->bi, &src->bi);
+}
+
+static int table_iter_next_in_block(struct table_iter *ti, struct record rec)
+{
+	int res = block_iter_next(&ti->bi, rec);
+	if (res == 0 && record_type(rec) == BLOCK_TYPE_REF) {
+		((struct ref_record *)rec.data)->update_index +=
+			ti->r->min_update_index;
+	}
+
+	return res;
+}
+
+static void table_iter_block_done(struct table_iter *ti)
+{
+	if (ti->bi.br == NULL) {
+		return;
+	}
+	reader_return_block(ti->r, &ti->bi.br->block);
+	FREE_AND_NULL(ti->bi.br);
+
+	ti->bi.last_key.len = 0;
+	ti->bi.next_off = 0;
+}
+
+static int32_t extract_block_size(byte *data, byte *typ, uint64_t off)
+{
+	int32_t result = 0;
+
+	if (off == 0) {
+		data += 24;
+	}
+
+	*typ = data[0];
+	if (is_block_type(*typ)) {
+		result = get_u24(data + 1);
+	}
+	return result;
+}
+
+int reader_init_block_reader(struct reader *r, struct block_reader *br,
+			     uint64_t next_off, byte want_typ)
+{
+	int32_t guess_block_size = r->block_size ? r->block_size :
+						   DEFAULT_BLOCK_SIZE;
+	struct block block = {};
+	byte block_typ = 0;
+	int err = 0;
+	uint32_t header_off = next_off ? 0 : HEADER_SIZE;
+	int32_t block_size = 0;
+
+	if (next_off >= r->size) {
+		return 1;
+	}
+
+	err = reader_get_block(r, &block, next_off, guess_block_size);
+	if (err < 0) {
+		return err;
+	}
+
+	block_size = extract_block_size(block.data, &block_typ, next_off);
+	if (block_size < 0) {
+		return block_size;
+	}
+
+	if (want_typ != BLOCK_TYPE_ANY && block_typ != want_typ) {
+		reader_return_block(r, &block);
+		return 1;
+	}
+
+	if (block_size > guess_block_size) {
+		reader_return_block(r, &block);
+		err = reader_get_block(r, &block, next_off, block_size);
+		if (err < 0) {
+			return err;
+		}
+	}
+
+	return block_reader_init(br, &block, header_off, r->block_size,
+				 r->hash_size);
+}
+
+static int table_iter_next_block(struct table_iter *dest,
+				 struct table_iter *src)
+{
+	uint64_t next_block_off = src->block_off + src->bi.br->full_block_size;
+	struct block_reader br = {};
+	int err = 0;
+
+	dest->r = src->r;
+	dest->typ = src->typ;
+	dest->block_off = next_block_off;
+
+	err = reader_init_block_reader(src->r, &br, next_block_off, src->typ);
+	if (err > 0) {
+		dest->finished = true;
+		return 1;
+	}
+	if (err != 0) {
+		return err;
+	}
+
+	{
+		struct block_reader *brp = malloc(sizeof(struct block_reader));
+		*brp = br;
+
+		dest->finished = false;
+		block_reader_start(brp, &dest->bi);
+	}
+	return 0;
+}
+
+static int table_iter_next(struct table_iter *ti, struct record rec)
+{
+	if (record_type(rec) != ti->typ) {
+		return API_ERROR;
+	}
+
+	while (true) {
+		struct table_iter next = {};
+		int err = 0;
+		if (ti->finished) {
+			return 1;
+		}
+
+		err = table_iter_next_in_block(ti, rec);
+		if (err <= 0) {
+			return err;
+		}
+
+		err = table_iter_next_block(&next, ti);
+		if (err != 0) {
+			ti->finished = true;
+		}
+		table_iter_block_done(ti);
+		if (err != 0) {
+			return err;
+		}
+		table_iter_copy_from(ti, &next);
+		block_iter_close(&next.bi);
+	}
+}
+
+static int table_iter_next_void(void *ti, struct record rec)
+{
+	return table_iter_next((struct table_iter *)ti, rec);
+}
+
+static void table_iter_close(void *p)
+{
+	struct table_iter *ti = (struct table_iter *)p;
+	table_iter_block_done(ti);
+	block_iter_close(&ti->bi);
+}
+
+struct iterator_vtable table_iter_vtable = {
+	.next = &table_iter_next_void,
+	.close = &table_iter_close,
+};
+
+static void iterator_from_table_iter(struct iterator *it, struct table_iter *ti)
+{
+	it->iter_arg = ti;
+	it->ops = &table_iter_vtable;
+}
+
+static int reader_table_iter_at(struct reader *r, struct table_iter *ti,
+				uint64_t off, byte typ)
+{
+	struct block_reader br = {};
+	struct block_reader *brp = NULL;
+
+	int err = reader_init_block_reader(r, &br, off, typ);
+	if (err != 0) {
+		return err;
+	}
+
+	brp = malloc(sizeof(struct block_reader));
+	*brp = br;
+	ti->r = r;
+	ti->typ = block_reader_type(brp);
+	ti->block_off = off;
+	block_reader_start(brp, &ti->bi);
+	return 0;
+}
+
+static int reader_start(struct reader *r, struct table_iter *ti, byte typ,
+			bool index)
+{
+	struct reader_offsets *offs = reader_offsets_for(r, typ);
+	uint64_t off = offs->offset;
+	if (index) {
+		off = offs->index_offset;
+		if (off == 0) {
+			return 1;
+		}
+		typ = BLOCK_TYPE_INDEX;
+	}
+
+	return reader_table_iter_at(r, ti, off, typ);
+}
+
+static int reader_seek_linear(struct reader *r, struct table_iter *ti,
+			      struct record want)
+{
+	struct record rec = new_record(record_type(want));
+	struct slice want_key = {};
+	struct slice got_key = {};
+	struct table_iter next = {};
+	int err = -1;
+	record_key(want, &want_key);
+
+	while (true) {
+		err = table_iter_next_block(&next, ti);
+		if (err < 0) {
+			goto exit;
+		}
+
+		if (err > 0) {
+			break;
+		}
+
+		err = block_reader_first_key(next.bi.br, &got_key);
+		if (err < 0) {
+			goto exit;
+		}
+		{
+			int cmp = slice_compare(got_key, want_key);
+			if (cmp > 0) {
+				table_iter_block_done(&next);
+				break;
+			}
+		}
+
+		table_iter_block_done(ti);
+		table_iter_copy_from(ti, &next);
+	}
+
+	err = block_iter_seek(&ti->bi, want_key);
+	if (err < 0) {
+		goto exit;
+	}
+	err = 0;
+
+exit:
+	block_iter_close(&next.bi);
+	record_clear(rec);
+	free(record_yield(&rec));
+	free(slice_yield(&want_key));
+	free(slice_yield(&got_key));
+	return err;
+}
+
+static int reader_seek_indexed(struct reader *r, struct iterator *it,
+			       struct record rec)
+{
+	struct index_record want_index = {};
+	struct record want_index_rec = {};
+	struct index_record index_result = {};
+	struct record index_result_rec = {};
+	struct table_iter index_iter = {};
+	struct table_iter next = {};
+	int err = 0;
+
+	record_key(rec, &want_index.last_key);
+	record_from_index(&want_index_rec, &want_index);
+	record_from_index(&index_result_rec, &index_result);
+
+	err = reader_start(r, &index_iter, record_type(rec), true);
+	if (err < 0) {
+		goto exit;
+	}
+
+	err = reader_seek_linear(r, &index_iter, want_index_rec);
+	while (true) {
+		err = table_iter_next(&index_iter, index_result_rec);
+		table_iter_block_done(&index_iter);
+		if (err != 0) {
+			goto exit;
+		}
+
+		err = reader_table_iter_at(r, &next, index_result.offset, 0);
+		if (err != 0) {
+			goto exit;
+		}
+
+		err = block_iter_seek(&next.bi, want_index.last_key);
+		if (err < 0) {
+			goto exit;
+		}
+
+		if (next.typ == record_type(rec)) {
+			err = 0;
+			break;
+		}
+
+		if (next.typ != BLOCK_TYPE_INDEX) {
+			err = FORMAT_ERROR;
+			break;
+		}
+
+		table_iter_copy_from(&index_iter, &next);
+	}
+
+	if (err == 0) {
+		struct table_iter *malloced =
+			calloc(sizeof(struct table_iter), 1);
+		table_iter_copy_from(malloced, &next);
+		iterator_from_table_iter(it, malloced);
+	}
+exit:
+	block_iter_close(&next.bi);
+	table_iter_close(&index_iter);
+	record_clear(want_index_rec);
+	record_clear(index_result_rec);
+	return err;
+}
+
+static int reader_seek_internal(struct reader *r, struct iterator *it,
+				struct record rec)
+{
+	struct reader_offsets *offs = reader_offsets_for(r, record_type(rec));
+	uint64_t idx = offs->index_offset;
+	struct table_iter ti = {};
+	int err = 0;
+	if (idx > 0) {
+		return reader_seek_indexed(r, it, rec);
+	}
+
+	err = reader_start(r, &ti, record_type(rec), false);
+	if (err < 0) {
+		return err;
+	}
+	err = reader_seek_linear(r, &ti, rec);
+	if (err < 0) {
+		return err;
+	}
+
+	{
+		struct table_iter *p = malloc(sizeof(struct table_iter));
+		*p = ti;
+		iterator_from_table_iter(it, p);
+	}
+
+	return 0;
+}
+
+int reader_seek(struct reader *r, struct iterator *it, struct record rec)
+{
+	byte typ = record_type(rec);
+
+	struct reader_offsets *offs = reader_offsets_for(r, typ);
+	if (!offs->present) {
+		iterator_set_empty(it);
+		return 0;
+	}
+
+	return reader_seek_internal(r, it, rec);
+}
+
+int reader_seek_ref(struct reader *r, struct iterator *it, const char *name)
+{
+	struct ref_record ref = {
+		.ref_name = (char *)name,
+	};
+	struct record rec = {};
+	record_from_ref(&rec, &ref);
+	return reader_seek(r, it, rec);
+}
+
+int reader_seek_log_at(struct reader *r, struct iterator *it, const char *name,
+		       uint64_t update_index)
+{
+	struct log_record log = {
+		.ref_name = (char *)name,
+		.update_index = update_index,
+	};
+	struct record rec = {};
+	record_from_log(&rec, &log);
+	return reader_seek(r, it, rec);
+}
+
+int reader_seek_log(struct reader *r, struct iterator *it, const char *name)
+{
+	uint64_t max = ~((uint64_t)0);
+	return reader_seek_log_at(r, it, name, max);
+}
+
+void reader_close(struct reader *r)
+{
+	block_source_close(&r->source);
+	FREE_AND_NULL(r->name);
+}
+
+int new_reader(struct reader **p, struct block_source src, char const *name)
+{
+	struct reader *rd = calloc(sizeof(struct reader), 1);
+	int err = init_reader(rd, src, name);
+	if (err == 0) {
+		*p = rd;
+	} else {
+		free(rd);
+	}
+	return err;
+}
+
+void reader_free(struct reader *r)
+{
+	reader_close(r);
+	free(r);
+}
+
+static int reader_refs_for_indexed(struct reader *r, struct iterator *it,
+				   byte *oid)
+{
+	struct obj_record want = {
+		.hash_prefix = oid,
+		.hash_prefix_len = r->object_id_len,
+	};
+	struct record want_rec = {};
+	struct iterator oit = {};
+	struct obj_record got = {};
+	struct record got_rec = {};
+	int err = 0;
+
+	record_from_obj(&want_rec, &want);
+
+	err = reader_seek(r, &oit, want_rec);
+	if (err != 0) {
+		return err;
+	}
+
+	record_from_obj(&got_rec, &got);
+	err = iterator_next(oit, got_rec);
+	iterator_destroy(&oit);
+	if (err < 0) {
+		return err;
+	}
+
+	if (err > 0 ||
+	    memcmp(want.hash_prefix, got.hash_prefix, r->object_id_len)) {
+		iterator_set_empty(it);
+		return 0;
+	}
+
+	{
+		struct indexed_table_ref_iter *itr = NULL;
+		err = new_indexed_table_ref_iter(&itr, r, oid, r->hash_size,
+						 got.offsets, got.offset_len);
+		if (err < 0) {
+			record_clear(got_rec);
+			return err;
+		}
+		got.offsets = NULL;
+		record_clear(got_rec);
+
+		iterator_from_indexed_table_ref_iter(it, itr);
+	}
+
+	return 0;
+}
+
+static int reader_refs_for_unindexed(struct reader *r, struct iterator *it,
+				     byte *oid, int oid_len)
+{
+	struct table_iter *ti = calloc(sizeof(struct table_iter), 1);
+	struct filtering_ref_iterator *filter = NULL;
+	int err = reader_start(r, ti, BLOCK_TYPE_REF, false);
+	if (err < 0) {
+		free(ti);
+		return err;
+	}
+
+	filter = calloc(sizeof(struct filtering_ref_iterator), 1);
+	slice_resize(&filter->oid, oid_len);
+	memcpy(filter->oid.buf, oid, oid_len);
+	filter->r = r;
+	filter->double_check = false;
+	iterator_from_table_iter(&filter->it, ti);
+
+	iterator_from_filtering_ref_iterator(it, filter);
+	return 0;
+}
+
+int reader_refs_for(struct reader *r, struct iterator *it, byte *oid,
+		    int oid_len)
+{
+	if (r->obj_offsets.present) {
+		return reader_refs_for_indexed(r, it, oid);
+	}
+	return reader_refs_for_unindexed(r, it, oid, oid_len);
+}
+
+uint64_t reader_max_update_index(struct reader *r)
+{
+	return r->max_update_index;
+}
+
+uint64_t reader_min_update_index(struct reader *r)
+{
+	return r->min_update_index;
+}
diff --git a/reftable/reader.h b/reftable/reader.h
new file mode 100644
index 0000000000..599a90028e
--- /dev/null
+++ b/reftable/reader.h
@@ -0,0 +1,52 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef READER_H
+#define READER_H
+
+#include "block.h"
+#include "record.h"
+#include "reftable.h"
+
+uint64_t block_source_size(struct block_source source);
+
+int block_source_read_block(struct block_source source, struct block *dest,
+			    uint64_t off, uint32_t size);
+void block_source_return_block(struct block_source source, struct block *ret);
+void block_source_close(struct block_source *source);
+
+struct reader_offsets {
+	bool present;
+	uint64_t offset;
+	uint64_t index_offset;
+};
+
+struct reader {
+	struct block_source source;
+	char *name;
+	int hash_size;
+	uint64_t size;
+	uint32_t block_size;
+	uint64_t min_update_index;
+	uint64_t max_update_index;
+	int object_id_len;
+
+	struct reader_offsets ref_offsets;
+	struct reader_offsets obj_offsets;
+	struct reader_offsets log_offsets;
+};
+
+int init_reader(struct reader *r, struct block_source source, const char *name);
+int reader_seek(struct reader *r, struct iterator *it, struct record rec);
+void reader_close(struct reader *r);
+const char *reader_name(struct reader *r);
+void reader_return_block(struct reader *r, struct block *p);
+int reader_init_block_reader(struct reader *r, struct block_reader *br,
+			     uint64_t next_off, byte want_typ);
+
+#endif
diff --git a/reftable/record.c b/reftable/record.c
new file mode 100644
index 0000000000..95b6ffcc9f
--- /dev/null
+++ b/reftable/record.c
@@ -0,0 +1,1107 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "record.h"
+
+#include "system.h"
+
+#include "constants.h"
+#include "reftable.h"
+
+int is_block_type(byte typ)
+{
+	switch (typ) {
+	case BLOCK_TYPE_REF:
+	case BLOCK_TYPE_LOG:
+	case BLOCK_TYPE_OBJ:
+	case BLOCK_TYPE_INDEX:
+		return true;
+	}
+	return false;
+}
+
+int get_var_int(uint64_t *dest, struct slice in)
+{
+	int ptr = 0;
+	uint64_t val;
+
+	if (in.len == 0) {
+		return -1;
+	}
+	val = in.buf[ptr] & 0x7f;
+
+	while (in.buf[ptr] & 0x80) {
+		ptr++;
+		if (ptr > in.len) {
+			return -1;
+		}
+		val = (val + 1) << 7 | (uint64_t)(in.buf[ptr] & 0x7f);
+	}
+
+	*dest = val;
+	return ptr + 1;
+}
+
+int put_var_int(struct slice dest, uint64_t val)
+{
+	byte buf[10] = {};
+	int i = 9;
+	buf[i] = (byte)(val & 0x7f);
+	i--;
+	while (true) {
+		val >>= 7;
+		if (!val) {
+			break;
+		}
+		val--;
+		buf[i] = 0x80 | (byte)(val & 0x7f);
+		i--;
+	}
+
+	{
+		int n = sizeof(buf) - i - 1;
+		if (dest.len < n) {
+			return -1;
+		}
+		memcpy(dest.buf, &buf[i + 1], n);
+		return n;
+	}
+}
+
+int common_prefix_size(struct slice a, struct slice b)
+{
+	int p = 0;
+	while (p < a.len && p < b.len) {
+		if (a.buf[p] != b.buf[p]) {
+			break;
+		}
+		p++;
+	}
+
+	return p;
+}
+
+static int decode_string(struct slice *dest, struct slice in)
+{
+	int start_len = in.len;
+	uint64_t tsize = 0;
+	int n = get_var_int(&tsize, in);
+	if (n <= 0) {
+		return -1;
+	}
+	in.buf += n;
+	in.len -= n;
+	if (in.len < tsize) {
+		return -1;
+	}
+
+	slice_resize(dest, tsize + 1);
+	dest->buf[tsize] = 0;
+	memcpy(dest->buf, in.buf, tsize);
+	in.buf += tsize;
+	in.len -= tsize;
+
+	return start_len - in.len;
+}
+
+int encode_key(bool *restart, struct slice dest, struct slice prev_key,
+	       struct slice key, byte extra)
+{
+	struct slice start = dest;
+	int prefix_len = common_prefix_size(prev_key, key);
+	uint64_t suffix_len = key.len - prefix_len;
+	int n = put_var_int(dest, (uint64_t)prefix_len);
+	if (n < 0) {
+		return -1;
+	}
+	dest.buf += n;
+	dest.len -= n;
+
+	*restart = (prefix_len == 0);
+
+	n = put_var_int(dest, suffix_len << 3 | (uint64_t)extra);
+	if (n < 0) {
+		return -1;
+	}
+	dest.buf += n;
+	dest.len -= n;
+
+	if (dest.len < suffix_len) {
+		return -1;
+	}
+	memcpy(dest.buf, key.buf + prefix_len, suffix_len);
+	dest.buf += suffix_len;
+	dest.len -= suffix_len;
+
+	return start.len - dest.len;
+}
+
+static byte ref_record_type(void)
+{
+	return BLOCK_TYPE_REF;
+}
+
+static void ref_record_key(const void *r, struct slice *dest)
+{
+	const struct ref_record *rec = (const struct ref_record *)r;
+	slice_set_string(dest, rec->ref_name);
+}
+
+static void ref_record_copy_from(void *rec, const void *src_rec, int hash_size)
+{
+	struct ref_record *ref = (struct ref_record *)rec;
+	struct ref_record *src = (struct ref_record *)src_rec;
+	assert(hash_size > 0);
+
+	/* This is simple and correct, but we could probably reuse the hash
+	   fields. */
+	ref_record_clear(ref);
+	if (src->ref_name != NULL) {
+		ref->ref_name = strdup(src->ref_name);
+	}
+
+	if (src->target != NULL) {
+		ref->target = strdup(src->target);
+	}
+
+	if (src->target_value != NULL) {
+		ref->target_value = malloc(hash_size);
+		memcpy(ref->target_value, src->target_value, hash_size);
+	}
+
+	if (src->value != NULL) {
+		ref->value = malloc(hash_size);
+		memcpy(ref->value, src->value, hash_size);
+	}
+	ref->update_index = src->update_index;
+}
+
+static char hexdigit(int c)
+{
+	if (c <= 9) {
+		return '0' + c;
+	}
+	return 'a' + (c - 10);
+}
+
+static void hex_format(char *dest, byte *src, int hash_size)
+{
+	assert(hash_size > 0);
+	if (src != NULL) {
+		int i = 0;
+		for (i = 0; i < hash_size; i++) {
+			dest[2 * i] = hexdigit(src[i] >> 4);
+			dest[2 * i + 1] = hexdigit(src[i] & 0xf);
+		}
+		dest[2 * hash_size] = 0;
+	}
+}
+
+void ref_record_print(struct ref_record *ref, int hash_size)
+{
+	char hex[SHA256_SIZE + 1] = {};
+
+	printf("ref{%s(%" PRIdMAX ") ", ref->ref_name, ref->update_index);
+	if (ref->value != NULL) {
+		hex_format(hex, ref->value, hash_size);
+		printf("%s", hex);
+	}
+	if (ref->target_value != NULL) {
+		hex_format(hex, ref->target_value, hash_size);
+		printf(" (T %s)", hex);
+	}
+	if (ref->target != NULL) {
+		printf("=> %s", ref->target);
+	}
+	printf("}\n");
+}
+
+static void ref_record_clear_void(void *rec)
+{
+	ref_record_clear((struct ref_record *)rec);
+}
+
+void ref_record_clear(struct ref_record *ref)
+{
+	free(ref->ref_name);
+	free(ref->target);
+	free(ref->target_value);
+	free(ref->value);
+	memset(ref, 0, sizeof(struct ref_record));
+}
+
+static byte ref_record_val_type(const void *rec)
+{
+	const struct ref_record *r = (const struct ref_record *)rec;
+	if (r->value != NULL) {
+		if (r->target_value != NULL) {
+			return 2;
+		} else {
+			return 1;
+		}
+	} else if (r->target != NULL) {
+		return 3;
+	}
+	return 0;
+}
+
+static int encode_string(char *str, struct slice s)
+{
+	struct slice start = s;
+	int l = strlen(str);
+	int n = put_var_int(s, l);
+	if (n < 0) {
+		return -1;
+	}
+	s.buf += n;
+	s.len -= n;
+	if (s.len < l) {
+		return -1;
+	}
+	memcpy(s.buf, str, l);
+	s.buf += l;
+	s.len -= l;
+
+	return start.len - s.len;
+}
+
+static int ref_record_encode(const void *rec, struct slice s, int hash_size)
+{
+	const struct ref_record *r = (const struct ref_record *)rec;
+	struct slice start = s;
+	int n = put_var_int(s, r->update_index);
+	assert(hash_size > 0);
+	if (n < 0) {
+		return -1;
+	}
+	s.buf += n;
+	s.len -= n;
+
+	if (r->value != NULL) {
+		if (s.len < hash_size) {
+			return -1;
+		}
+		memcpy(s.buf, r->value, hash_size);
+		s.buf += hash_size;
+		s.len -= hash_size;
+	}
+
+	if (r->target_value != NULL) {
+		if (s.len < hash_size) {
+			return -1;
+		}
+		memcpy(s.buf, r->target_value, hash_size);
+		s.buf += hash_size;
+		s.len -= hash_size;
+	}
+
+	if (r->target != NULL) {
+		int n = encode_string(r->target, s);
+		if (n < 0) {
+			return -1;
+		}
+		s.buf += n;
+		s.len -= n;
+	}
+
+	return start.len - s.len;
+}
+
+static int ref_record_decode(void *rec, struct slice key, byte val_type,
+			     struct slice in, int hash_size)
+{
+	struct ref_record *r = (struct ref_record *)rec;
+	struct slice start = in;
+	bool seen_value = false;
+	bool seen_target_value = false;
+	bool seen_target = false;
+
+	int n = get_var_int(&r->update_index, in);
+	if (n < 0) {
+		return n;
+	}
+	assert(hash_size > 0);
+
+	in.buf += n;
+	in.len -= n;
+
+	r->ref_name = realloc(r->ref_name, key.len + 1);
+	memcpy(r->ref_name, key.buf, key.len);
+	r->ref_name[key.len] = 0;
+
+	switch (val_type) {
+	case 1:
+	case 2:
+		if (in.len < hash_size) {
+			return -1;
+		}
+
+		if (r->value == NULL) {
+			r->value = malloc(hash_size);
+		}
+		seen_value = true;
+		memcpy(r->value, in.buf, hash_size);
+		in.buf += hash_size;
+		in.len -= hash_size;
+		if (val_type == 1) {
+			break;
+		}
+		if (r->target_value == NULL) {
+			r->target_value = malloc(hash_size);
+		}
+		seen_target_value = true;
+		memcpy(r->target_value, in.buf, hash_size);
+		in.buf += hash_size;
+		in.len -= hash_size;
+		break;
+	case 3: {
+		struct slice dest = {};
+		int n = decode_string(&dest, in);
+		if (n < 0) {
+			return -1;
+		}
+		in.buf += n;
+		in.len -= n;
+		seen_target = true;
+		r->target = (char *)slice_as_string(&dest);
+	} break;
+
+	case 0:
+		break;
+	default:
+		abort();
+		break;
+	}
+
+	if (!seen_target && r->target != NULL) {
+		FREE_AND_NULL(r->target);
+	}
+	if (!seen_target_value && r->target_value != NULL) {
+		FREE_AND_NULL(r->target_value);
+	}
+	if (!seen_value && r->value != NULL) {
+		FREE_AND_NULL(r->value);
+	}
+
+	return start.len - in.len;
+}
+
+int decode_key(struct slice *key, byte *extra, struct slice last_key,
+	       struct slice in)
+{
+	int start_len = in.len;
+	uint64_t prefix_len = 0;
+	uint64_t suffix_len = 0;
+	int n = get_var_int(&prefix_len, in);
+	if (n < 0) {
+		return -1;
+	}
+	in.buf += n;
+	in.len -= n;
+
+	if (prefix_len > last_key.len) {
+		return -1;
+	}
+
+	n = get_var_int(&suffix_len, in);
+	if (n <= 0) {
+		return -1;
+	}
+	in.buf += n;
+	in.len -= n;
+
+	*extra = (byte)(suffix_len & 0x7);
+	suffix_len >>= 3;
+
+	if (in.len < suffix_len) {
+		return -1;
+	}
+
+	slice_resize(key, suffix_len + prefix_len);
+	memcpy(key->buf, last_key.buf, prefix_len);
+
+	memcpy(key->buf + prefix_len, in.buf, suffix_len);
+	in.buf += suffix_len;
+	in.len -= suffix_len;
+
+	return start_len - in.len;
+}
+
+struct record_vtable ref_record_vtable = {
+	.key = &ref_record_key,
+	.type = &ref_record_type,
+	.copy_from = &ref_record_copy_from,
+	.val_type = &ref_record_val_type,
+	.encode = &ref_record_encode,
+	.decode = &ref_record_decode,
+	.clear = &ref_record_clear_void,
+};
+
+static byte obj_record_type(void)
+{
+	return BLOCK_TYPE_OBJ;
+}
+
+static void obj_record_key(const void *r, struct slice *dest)
+{
+	const struct obj_record *rec = (const struct obj_record *)r;
+	slice_resize(dest, rec->hash_prefix_len);
+	memcpy(dest->buf, rec->hash_prefix, rec->hash_prefix_len);
+}
+
+static void obj_record_copy_from(void *rec, const void *src_rec, int hash_size)
+{
+	struct obj_record *ref = (struct obj_record *)rec;
+	const struct obj_record *src = (const struct obj_record *)src_rec;
+
+	*ref = *src;
+	ref->hash_prefix = malloc(ref->hash_prefix_len);
+	memcpy(ref->hash_prefix, src->hash_prefix, ref->hash_prefix_len);
+
+	{
+		int olen = ref->offset_len * sizeof(uint64_t);
+		ref->offsets = malloc(olen);
+		memcpy(ref->offsets, src->offsets, olen);
+	}
+}
+
+static void obj_record_clear(void *rec)
+{
+	struct obj_record *ref = (struct obj_record *)rec;
+	FREE_AND_NULL(ref->hash_prefix);
+	FREE_AND_NULL(ref->offsets);
+	memset(ref, 0, sizeof(struct obj_record));
+}
+
+static byte obj_record_val_type(const void *rec)
+{
+	struct obj_record *r = (struct obj_record *)rec;
+	if (r->offset_len > 0 && r->offset_len < 8) {
+		return r->offset_len;
+	}
+	return 0;
+}
+
+static int obj_record_encode(const void *rec, struct slice s, int hash_size)
+{
+	struct obj_record *r = (struct obj_record *)rec;
+	struct slice start = s;
+	int n = 0;
+	if (r->offset_len == 0 || r->offset_len >= 8) {
+		n = put_var_int(s, r->offset_len);
+		if (n < 0) {
+			return -1;
+		}
+		s.buf += n;
+		s.len -= n;
+	}
+	if (r->offset_len == 0) {
+		return start.len - s.len;
+	}
+	n = put_var_int(s, r->offsets[0]);
+	if (n < 0) {
+		return -1;
+	}
+	s.buf += n;
+	s.len -= n;
+
+	{
+		uint64_t last = r->offsets[0];
+		int i = 0;
+		for (i = 1; i < r->offset_len; i++) {
+			int n = put_var_int(s, r->offsets[i] - last);
+			if (n < 0) {
+				return -1;
+			}
+			s.buf += n;
+			s.len -= n;
+			last = r->offsets[i];
+		}
+	}
+	return start.len - s.len;
+}
+
+static int obj_record_decode(void *rec, struct slice key, byte val_type,
+			     struct slice in, int hash_size)
+{
+	struct slice start = in;
+	struct obj_record *r = (struct obj_record *)rec;
+	uint64_t count = val_type;
+	int n = 0;
+	r->hash_prefix = malloc(key.len);
+	memcpy(r->hash_prefix, key.buf, key.len);
+	r->hash_prefix_len = key.len;
+
+	if (val_type == 0) {
+		n = get_var_int(&count, in);
+		if (n < 0) {
+			return n;
+		}
+
+		in.buf += n;
+		in.len -= n;
+	}
+
+	r->offsets = NULL;
+	r->offset_len = 0;
+	if (count == 0) {
+		return start.len - in.len;
+	}
+
+	r->offsets = malloc(count * sizeof(uint64_t));
+	r->offset_len = count;
+
+	n = get_var_int(&r->offsets[0], in);
+	if (n < 0) {
+		return n;
+	}
+
+	in.buf += n;
+	in.len -= n;
+
+	{
+		uint64_t last = r->offsets[0];
+		int j = 1;
+		while (j < count) {
+			uint64_t delta = 0;
+			int n = get_var_int(&delta, in);
+			if (n < 0) {
+				return n;
+			}
+
+			in.buf += n;
+			in.len -= n;
+
+			last = r->offsets[j] = (delta + last);
+			j++;
+		}
+	}
+	return start.len - in.len;
+}
+
+struct record_vtable obj_record_vtable = {
+	.key = &obj_record_key,
+	.type = &obj_record_type,
+	.copy_from = &obj_record_copy_from,
+	.val_type = &obj_record_val_type,
+	.encode = &obj_record_encode,
+	.decode = &obj_record_decode,
+	.clear = &obj_record_clear,
+};
+
+void log_record_print(struct log_record *log, int hash_size)
+{
+	char hex[SHA256_SIZE + 1] = {};
+
+	printf("log{%s(%" PRIdMAX ") %s <%s> %" PRIuMAX " %04d\n",
+	       log->ref_name, log->update_index, log->name, log->email,
+	       log->time, log->tz_offset);
+	hex_format(hex, log->old_hash, hash_size);
+	printf("%s => ", hex);
+	hex_format(hex, log->new_hash, hash_size);
+	printf("%s\n\n%s\n}\n", hex, log->message);
+}
+
+static byte log_record_type(void)
+{
+	return BLOCK_TYPE_LOG;
+}
+
+static void log_record_key(const void *r, struct slice *dest)
+{
+	const struct log_record *rec = (const struct log_record *)r;
+	int len = strlen(rec->ref_name);
+	uint64_t ts = 0;
+	slice_resize(dest, len + 9);
+	memcpy(dest->buf, rec->ref_name, len + 1);
+	ts = (~ts) - rec->update_index;
+	put_u64(dest->buf + 1 + len, ts);
+}
+
+static void log_record_copy_from(void *rec, const void *src_rec, int hash_size)
+{
+	struct log_record *dst = (struct log_record *)rec;
+	const struct log_record *src = (const struct log_record *)src_rec;
+
+	*dst = *src;
+	dst->ref_name = strdup(dst->ref_name);
+	dst->email = strdup(dst->email);
+	dst->name = strdup(dst->name);
+	dst->message = strdup(dst->message);
+	if (dst->new_hash != NULL) {
+		dst->new_hash = malloc(hash_size);
+		memcpy(dst->new_hash, src->new_hash, hash_size);
+	}
+	if (dst->old_hash != NULL) {
+		dst->old_hash = malloc(hash_size);
+		memcpy(dst->old_hash, src->old_hash, hash_size);
+	}
+}
+
+static void log_record_clear_void(void *rec)
+{
+	struct log_record *r = (struct log_record *)rec;
+	log_record_clear(r);
+}
+
+void log_record_clear(struct log_record *r)
+{
+	free(r->ref_name);
+	free(r->new_hash);
+	free(r->old_hash);
+	free(r->name);
+	free(r->email);
+	free(r->message);
+	memset(r, 0, sizeof(struct log_record));
+}
+
+static byte log_record_val_type(const void *rec)
+{
+	return 1;
+}
+
+static byte zero[SHA256_SIZE] = {};
+
+static int log_record_encode(const void *rec, struct slice s, int hash_size)
+{
+	struct log_record *r = (struct log_record *)rec;
+	struct slice start = s;
+	int n = 0;
+	byte *oldh = r->old_hash;
+	byte *newh = r->new_hash;
+	if (oldh == NULL) {
+		oldh = zero;
+	}
+	if (newh == NULL) {
+		newh = zero;
+	}
+
+	if (s.len < 2 * hash_size) {
+		return -1;
+	}
+
+	memcpy(s.buf, oldh, hash_size);
+	memcpy(s.buf + hash_size, newh, hash_size);
+	s.buf += 2 * hash_size;
+	s.len -= 2 * hash_size;
+
+	n = encode_string(r->name ? r->name : "", s);
+	if (n < 0) {
+		return -1;
+	}
+	s.len -= n;
+	s.buf += n;
+
+	n = encode_string(r->email ? r->email : "", s);
+	if (n < 0) {
+		return -1;
+	}
+	s.len -= n;
+	s.buf += n;
+
+	n = put_var_int(s, r->time);
+	if (n < 0) {
+		return -1;
+	}
+	s.buf += n;
+	s.len -= n;
+
+	if (s.len < 2) {
+		return -1;
+	}
+
+	put_u16(s.buf, r->tz_offset);
+	s.buf += 2;
+	s.len -= 2;
+
+	n = encode_string(r->message ? r->message : "", s);
+	if (n < 0) {
+		return -1;
+	}
+	s.len -= n;
+	s.buf += n;
+
+	return start.len - s.len;
+}
+
+static int log_record_decode(void *rec, struct slice key, byte val_type,
+			     struct slice in, int hash_size)
+{
+	struct slice start = in;
+	struct log_record *r = (struct log_record *)rec;
+	uint64_t max = 0;
+	uint64_t ts = 0;
+	struct slice dest = {};
+	int n;
+
+	if (key.len <= 9 || key.buf[key.len - 9] != 0) {
+		return FORMAT_ERROR;
+	}
+
+	r->ref_name = realloc(r->ref_name, key.len - 8);
+	memcpy(r->ref_name, key.buf, key.len - 8);
+	ts = get_u64(key.buf + key.len - 8);
+
+	r->update_index = (~max) - ts;
+
+	if (in.len < 2 * hash_size) {
+		return FORMAT_ERROR;
+	}
+
+	r->old_hash = realloc(r->old_hash, hash_size);
+	r->new_hash = realloc(r->new_hash, hash_size);
+
+	memcpy(r->old_hash, in.buf, hash_size);
+	memcpy(r->new_hash, in.buf + hash_size, hash_size);
+
+	in.buf += 2 * hash_size;
+	in.len -= 2 * hash_size;
+
+	n = decode_string(&dest, in);
+	if (n < 0) {
+		goto error;
+	}
+	in.len -= n;
+	in.buf += n;
+
+	r->name = realloc(r->name, dest.len + 1);
+	memcpy(r->name, dest.buf, dest.len);
+	r->name[dest.len] = 0;
+
+	slice_resize(&dest, 0);
+	n = decode_string(&dest, in);
+	if (n < 0) {
+		goto error;
+	}
+	in.len -= n;
+	in.buf += n;
+
+	r->email = realloc(r->email, dest.len + 1);
+	memcpy(r->email, dest.buf, dest.len);
+	r->email[dest.len] = 0;
+
+	ts = 0;
+	n = get_var_int(&ts, in);
+	if (n < 0) {
+		goto error;
+	}
+	in.len -= n;
+	in.buf += n;
+	r->time = ts;
+	if (in.len < 2) {
+		goto error;
+	}
+
+	r->tz_offset = get_u16(in.buf);
+	in.buf += 2;
+	in.len -= 2;
+
+	slice_resize(&dest, 0);
+	n = decode_string(&dest, in);
+	if (n < 0) {
+		goto error;
+	}
+	in.len -= n;
+	in.buf += n;
+
+	r->message = realloc(r->message, dest.len + 1);
+	memcpy(r->message, dest.buf, dest.len);
+	r->message[dest.len] = 0;
+
+	return start.len - in.len;
+
+error:
+	free(slice_yield(&dest));
+	return FORMAT_ERROR;
+}
+
+static bool null_streq(char *a, char *b)
+{
+	char *empty = "";
+	if (a == NULL) {
+		a = empty;
+	}
+	if (b == NULL) {
+		b = empty;
+	}
+	return 0 == strcmp(a, b);
+}
+
+static bool zero_hash_eq(byte *a, byte *b, int sz)
+{
+	if (a == NULL) {
+		a = zero;
+	}
+	if (b == NULL) {
+		b = zero;
+	}
+	return !memcmp(a, b, sz);
+}
+
+bool log_record_equal(struct log_record *a, struct log_record *b, int hash_size)
+{
+	return null_streq(a->name, b->name) && null_streq(a->email, b->email) &&
+	       null_streq(a->message, b->message) &&
+	       zero_hash_eq(a->old_hash, b->old_hash, hash_size) &&
+	       zero_hash_eq(a->new_hash, b->new_hash, hash_size) &&
+	       a->time == b->time && a->tz_offset == b->tz_offset &&
+	       a->update_index == b->update_index;
+}
+
+struct record_vtable log_record_vtable = {
+	.key = &log_record_key,
+	.type = &log_record_type,
+	.copy_from = &log_record_copy_from,
+	.val_type = &log_record_val_type,
+	.encode = &log_record_encode,
+	.decode = &log_record_decode,
+	.clear = &log_record_clear_void,
+};
+
+struct record new_record(byte typ)
+{
+	struct record rec;
+	switch (typ) {
+	case BLOCK_TYPE_REF: {
+		struct ref_record *r = calloc(1, sizeof(struct ref_record));
+		record_from_ref(&rec, r);
+		return rec;
+	}
+
+	case BLOCK_TYPE_OBJ: {
+		struct obj_record *r = calloc(1, sizeof(struct obj_record));
+		record_from_obj(&rec, r);
+		return rec;
+	}
+	case BLOCK_TYPE_LOG: {
+		struct log_record *r = calloc(1, sizeof(struct log_record));
+		record_from_log(&rec, r);
+		return rec;
+	}
+	case BLOCK_TYPE_INDEX: {
+		struct index_record *r = calloc(1, sizeof(struct index_record));
+		record_from_index(&rec, r);
+		return rec;
+	}
+	}
+	abort();
+	return rec;
+}
+
+static byte index_record_type(void)
+{
+	return BLOCK_TYPE_INDEX;
+}
+
+static void index_record_key(const void *r, struct slice *dest)
+{
+	struct index_record *rec = (struct index_record *)r;
+	slice_copy(dest, rec->last_key);
+}
+
+static void index_record_copy_from(void *rec, const void *src_rec,
+				   int hash_size)
+{
+	struct index_record *dst = (struct index_record *)rec;
+	struct index_record *src = (struct index_record *)src_rec;
+
+	slice_copy(&dst->last_key, src->last_key);
+	dst->offset = src->offset;
+}
+
+static void index_record_clear(void *rec)
+{
+	struct index_record *idx = (struct index_record *)rec;
+	free(slice_yield(&idx->last_key));
+}
+
+static byte index_record_val_type(const void *rec)
+{
+	return 0;
+}
+
+static int index_record_encode(const void *rec, struct slice out, int hash_size)
+{
+	const struct index_record *r = (const struct index_record *)rec;
+	struct slice start = out;
+
+	int n = put_var_int(out, r->offset);
+	if (n < 0) {
+		return n;
+	}
+
+	out.buf += n;
+	out.len -= n;
+
+	return start.len - out.len;
+}
+
+static int index_record_decode(void *rec, struct slice key, byte val_type,
+			       struct slice in, int hash_size)
+{
+	struct slice start = in;
+	struct index_record *r = (struct index_record *)rec;
+	int n = 0;
+
+	slice_copy(&r->last_key, key);
+
+	n = get_var_int(&r->offset, in);
+	if (n < 0) {
+		return n;
+	}
+
+	in.buf += n;
+	in.len -= n;
+	return start.len - in.len;
+}
+
+struct record_vtable index_record_vtable = {
+	.key = &index_record_key,
+	.type = &index_record_type,
+	.copy_from = &index_record_copy_from,
+	.val_type = &index_record_val_type,
+	.encode = &index_record_encode,
+	.decode = &index_record_decode,
+	.clear = &index_record_clear,
+};
+
+void record_key(struct record rec, struct slice *dest)
+{
+	rec.ops->key(rec.data, dest);
+}
+
+byte record_type(struct record rec)
+{
+	return rec.ops->type();
+}
+
+int record_encode(struct record rec, struct slice dest, int hash_size)
+{
+	return rec.ops->encode(rec.data, dest, hash_size);
+}
+
+void record_copy_from(struct record rec, struct record src, int hash_size)
+{
+	assert(src.ops->type() == rec.ops->type());
+
+	rec.ops->copy_from(rec.data, src.data, hash_size);
+}
+
+byte record_val_type(struct record rec)
+{
+	return rec.ops->val_type(rec.data);
+}
+
+int record_decode(struct record rec, struct slice key, byte extra,
+		  struct slice src, int hash_size)
+{
+	return rec.ops->decode(rec.data, key, extra, src, hash_size);
+}
+
+void record_clear(struct record rec)
+{
+	return rec.ops->clear(rec.data);
+}
+
+void record_from_ref(struct record *rec, struct ref_record *ref_rec)
+{
+	rec->data = ref_rec;
+	rec->ops = &ref_record_vtable;
+}
+
+void record_from_obj(struct record *rec, struct obj_record *obj_rec)
+{
+	rec->data = obj_rec;
+	rec->ops = &obj_record_vtable;
+}
+
+void record_from_index(struct record *rec, struct index_record *index_rec)
+{
+	rec->data = index_rec;
+	rec->ops = &index_record_vtable;
+}
+
+void record_from_log(struct record *rec, struct log_record *log_rec)
+{
+	rec->data = log_rec;
+	rec->ops = &log_record_vtable;
+}
+
+void *record_yield(struct record *rec)
+{
+	void *p = rec->data;
+	rec->data = NULL;
+	return p;
+}
+
+struct ref_record *record_as_ref(struct record rec)
+{
+	assert(record_type(rec) == BLOCK_TYPE_REF);
+	return (struct ref_record *)rec.data;
+}
+
+static bool hash_equal(byte *a, byte *b, int hash_size)
+{
+	if (a != NULL && b != NULL) {
+		return !memcmp(a, b, hash_size);
+	}
+
+	return a == b;
+}
+
+static bool str_equal(char *a, char *b)
+{
+	if (a != NULL && b != NULL) {
+		return 0 == strcmp(a, b);
+	}
+
+	return a == b;
+}
+
+bool ref_record_equal(struct ref_record *a, struct ref_record *b, int hash_size)
+{
+	assert(hash_size > 0);
+	return 0 == strcmp(a->ref_name, b->ref_name) &&
+	       a->update_index == b->update_index &&
+	       hash_equal(a->value, b->value, hash_size) &&
+	       hash_equal(a->target_value, b->target_value, hash_size) &&
+	       str_equal(a->target, b->target);
+}
+
+int ref_record_compare_name(const void *a, const void *b)
+{
+	return strcmp(((struct ref_record *)a)->ref_name,
+		      ((struct ref_record *)b)->ref_name);
+}
+
+bool ref_record_is_deletion(const struct ref_record *ref)
+{
+	return ref->value == NULL && ref->target == NULL &&
+	       ref->target_value == NULL;
+}
+
+int log_record_compare_key(const void *a, const void *b)
+{
+	struct log_record *la = (struct log_record *)a;
+	struct log_record *lb = (struct log_record *)b;
+
+	int cmp = strcmp(la->ref_name, lb->ref_name);
+	if (cmp) {
+		return cmp;
+	}
+	if (la->update_index > lb->update_index) {
+		return -1;
+	}
+	return (la->update_index < lb->update_index) ? 1 : 0;
+}
+
+bool log_record_is_deletion(const struct log_record *log)
+{
+	/* XXX */
+	return false;
+}
diff --git a/reftable/record.h b/reftable/record.h
new file mode 100644
index 0000000000..dffdd71fc2
--- /dev/null
+++ b/reftable/record.h
@@ -0,0 +1,79 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef RECORD_H
+#define RECORD_H
+
+#include "reftable.h"
+#include "slice.h"
+
+struct record_vtable {
+	void (*key)(const void *rec, struct slice *dest);
+	byte (*type)(void);
+	void (*copy_from)(void *rec, const void *src, int hash_size);
+	byte (*val_type)(const void *rec);
+	int (*encode)(const void *rec, struct slice dest, int hash_size);
+	int (*decode)(void *rec, struct slice key, byte extra, struct slice src,
+		      int hash_size);
+	void (*clear)(void *rec);
+};
+
+/* record is a generic wrapper for differnt types of records. */
+struct record {
+	void *data;
+	struct record_vtable *ops;
+};
+
+int get_var_int(uint64_t *dest, struct slice in);
+int put_var_int(struct slice dest, uint64_t val);
+int common_prefix_size(struct slice a, struct slice b);
+
+int is_block_type(byte typ);
+struct record new_record(byte typ);
+
+extern struct record_vtable ref_record_vtable;
+
+int encode_key(bool *restart, struct slice dest, struct slice prev_key,
+	       struct slice key, byte extra);
+int decode_key(struct slice *key, byte *extra, struct slice last_key,
+	       struct slice in);
+
+struct index_record {
+	struct slice last_key;
+	uint64_t offset;
+};
+
+struct obj_record {
+	byte *hash_prefix;
+	int hash_prefix_len;
+	uint64_t *offsets;
+	int offset_len;
+};
+
+void record_key(struct record rec, struct slice *dest);
+byte record_type(struct record rec);
+void record_copy_from(struct record rec, struct record src, int hash_size);
+byte record_val_type(struct record rec);
+int record_encode(struct record rec, struct slice dest, int hash_size);
+int record_decode(struct record rec, struct slice key, byte extra,
+		  struct slice src, int hash_size);
+void record_clear(struct record rec);
+void *record_yield(struct record *rec);
+void record_from_obj(struct record *rec, struct obj_record *objrec);
+void record_from_index(struct record *rec, struct index_record *idxrec);
+void record_from_ref(struct record *rec, struct ref_record *refrec);
+void record_from_log(struct record *rec, struct log_record *logrec);
+struct ref_record *record_as_ref(struct record ref);
+
+/* for qsort. */
+int ref_record_compare_name(const void *a, const void *b);
+
+/* for qsort. */
+int log_record_compare_key(const void *a, const void *b);
+
+#endif
diff --git a/reftable/reftable.h b/reftable/reftable.h
new file mode 100644
index 0000000000..d84cc2004a
--- /dev/null
+++ b/reftable/reftable.h
@@ -0,0 +1,399 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef REFTABLE_H
+#define REFTABLE_H
+
+#include "system.h"
+
+typedef uint8_t byte;
+typedef byte bool;
+
+/* block_source is a generic wrapper for a seekable readable file.
+   It is generally passed around by value.
+ */
+struct block_source {
+	struct block_source_vtable *ops;
+	void *arg;
+};
+
+/* a contiguous segment of bytes. It keeps track of its generating block_source
+   so it can return itself into the pool.
+*/
+struct block {
+	byte *data;
+	int len;
+	struct block_source source;
+};
+
+/* block_source_vtable are the operations that make up block_source */
+struct block_source_vtable {
+	/* returns the size of a block source */
+	uint64_t (*size)(void *source);
+
+	/* reads a segment from the block source. It is an error to read
+	   beyond the end of the block */
+	int (*read_block)(void *source, struct block *dest, uint64_t off,
+			  uint32_t size);
+	/* mark the block as read; may return the data back to malloc */
+	void (*return_block)(void *source, struct block *blockp);
+
+	/* release all resources associated with the block source */
+	void (*close)(void *source);
+};
+
+/* opens a file on the file system as a block_source */
+int block_source_from_file(struct block_source *block_src, const char *name);
+
+/* write_options sets options for writing a single reftable. */
+struct write_options {
+	/* do not pad out blocks to block size. */
+	bool unpadded;
+
+	/* the blocksize. Should be less than 2^24. */
+	uint32_t block_size;
+
+	/* do not generate a SHA1 => ref index. */
+	bool skip_index_objects;
+
+	/* how often to write complete keys in each block. */
+	int restart_interval;
+};
+
+/* ref_record holds a ref database entry target_value */
+struct ref_record {
+	char *ref_name; /* Name of the ref, malloced. */
+	uint64_t update_index; /* Logical timestamp at which this value is
+				  written */
+	byte *value; /* SHA1, or NULL. malloced. */
+	byte *target_value; /* peeled annotated tag, or NULL. malloced. */
+	char *target; /* symref, or NULL. malloced. */
+};
+
+/* returns whether 'ref' represents a deletion */
+bool ref_record_is_deletion(const struct ref_record *ref);
+
+/* prints a ref_record onto stdout */
+void ref_record_print(struct ref_record *ref, int hash_size);
+
+/* frees and nulls all pointer values. */
+void ref_record_clear(struct ref_record *ref);
+
+/* returns whether two ref_records are the same */
+bool ref_record_equal(struct ref_record *a, struct ref_record *b,
+		      int hash_size);
+
+/* log_record holds a reflog entry */
+struct log_record {
+	char *ref_name;
+	uint64_t update_index;
+	byte *new_hash;
+	byte *old_hash;
+	char *name;
+	char *email;
+	uint64_t time;
+	int16_t tz_offset;
+	char *message;
+};
+
+/* returns whether 'ref' represents the deletion of a log record. */
+bool log_record_is_deletion(const struct log_record *log);
+
+/* frees and nulls all pointer values. */
+void log_record_clear(struct log_record *log);
+
+/* returns whether two records are equal. */
+bool log_record_equal(struct log_record *a, struct log_record *b,
+		      int hash_size);
+
+void log_record_print(struct log_record *log, int hash_size);
+
+/* iterator is the generic interface for walking over data stored in a
+   reftable. It is generally passed around by value.
+*/
+struct iterator {
+	struct iterator_vtable *ops;
+	void *iter_arg;
+};
+
+/* reads the next ref_record. Returns < 0 for error, 0 for OK and > 0:
+   end of iteration.
+*/
+int iterator_next_ref(struct iterator it, struct ref_record *ref);
+
+/* reads the next log_record. Returns < 0 for error, 0 for OK and > 0:
+   end of iteration.
+*/
+int iterator_next_log(struct iterator it, struct log_record *log);
+
+/* releases resources associated with an iterator. */
+void iterator_destroy(struct iterator *it);
+
+/* block_stats holds statistics for a single block type */
+struct block_stats {
+	/* total number of entries written */
+	int entries;
+	/* total number of key restarts */
+	int restarts;
+	/* total number of blocks */
+	int blocks;
+	/* total number of index blocks */
+	int index_blocks;
+	/* depth of the index */
+	int max_index_level;
+
+	/* offset of the first block for this type */
+	uint64_t offset;
+	/* offset of the top level index block for this type, or 0 if not
+	 * present */
+	uint64_t index_offset;
+};
+
+/* stats holds overall statistics for a single reftable */
+struct stats {
+	/* total number of blocks written. */
+	int blocks;
+	/* stats for ref data */
+	struct block_stats ref_stats;
+	/* stats for the SHA1 to ref map. */
+	struct block_stats obj_stats;
+	/* stats for index blocks */
+	struct block_stats idx_stats;
+	/* stats for log blocks */
+	struct block_stats log_stats;
+
+	/* disambiguation length of shortened object IDs. */
+	int object_id_len;
+};
+
+/* different types of errors */
+
+/* Unexpected file system behavior */
+#define IO_ERROR -2
+
+/* Format inconsistency on reading data
+ */
+#define FORMAT_ERROR -3
+
+/* File does not exist. Returned from block_source_from_file(),  because it
+   needs special handling in stack.
+*/
+#define NOT_EXIST_ERROR -4
+
+/* Trying to write out-of-date data. */
+#define LOCK_ERROR -5
+
+/* Misuse of the API:
+   - on writing a record with NULL ref_name.
+   - on writing a ref_record outside the table limits
+   - on writing a ref or log record before the stack's next_update_index
+   - on reading a ref_record from log iterator, or vice versa.
+ */
+#define API_ERROR -6
+
+/* Decompression error */
+#define ZLIB_ERROR -7
+
+const char *error_str(int err);
+
+/* new_writer creates a new writer */
+struct writer *new_writer(int (*writer_func)(void *, byte *, int),
+			  void *writer_arg, struct write_options *opts);
+
+/* write to a file descriptor. fdp should be an int* pointing to the fd. */
+int fd_writer(void *fdp, byte *data, int size);
+
+/* Set the range of update indices for the records we will add.  When
+   writing a table into a stack, the min should be at least
+   stack_next_update_index(), or API_ERROR is returned.
+ */
+void writer_set_limits(struct writer *w, uint64_t min, uint64_t max);
+
+/* adds a ref_record. Must be called in ascending
+   order. The update_index must be within the limits set by
+   writer_set_limits(), or API_ERROR is returned.
+ */
+int writer_add_ref(struct writer *w, struct ref_record *ref);
+
+/* Convenience function to add multiple refs. Will sort the refs by
+   name before adding. */
+int writer_add_refs(struct writer *w, struct ref_record *refs, int n);
+
+/* adds a log_record. Must be called in ascending order (with more
+   recent log entries first.)
+ */
+int writer_add_log(struct writer *w, struct log_record *log);
+
+/* Convenience function to add multiple logs. Will sort the records by
+   key before adding. */
+int writer_add_logs(struct writer *w, struct log_record *logs, int n);
+
+/* writer_close finalizes the reftable. The writer is retained so statistics can
+ * be inspected. */
+int writer_close(struct writer *w);
+
+/* writer_stats returns the statistics on the reftable being written. */
+struct stats *writer_stats(struct writer *w);
+
+/* writer_free deallocates memory for the writer */
+void writer_free(struct writer *w);
+
+struct reader;
+
+/* new_reader opens a reftable for reading. If successful, returns 0
+ * code and sets pp.  The name is used for creating a
+ * stack. Typically, it is the basename of the file.
+ */
+int new_reader(struct reader **pp, struct block_source, const char *name);
+
+/* reader_seek_ref returns an iterator where 'name' would be inserted in the
+   table.
+
+   example:
+
+   struct reader *r = NULL;
+   int err = new_reader(&r, src, "filename");
+   if (err < 0) { ... }
+   struct iterator it = {};
+   err = reader_seek_ref(r, &it, "refs/heads/master");
+   if (err < 0) { ... }
+   struct ref_record ref = {};
+   while (1) {
+     err = iterator_next_ref(it, &ref);
+     if (err > 0) {
+       break;
+     }
+     if (err < 0) {
+       ..error handling..
+     }
+     ..found..
+   }
+   iterator_destroy(&it);
+   ref_record_clear(&ref);
+ */
+int reader_seek_ref(struct reader *r, struct iterator *it, const char *name);
+
+/* seek to logs for the given name, older than update_index. */
+int reader_seek_log_at(struct reader *r, struct iterator *it, const char *name,
+		       uint64_t update_index);
+
+/* seek to newest log entry for given name. */
+int reader_seek_log(struct reader *r, struct iterator *it, const char *name);
+
+/* closes and deallocates a reader. */
+void reader_free(struct reader *);
+
+/* return an iterator for the refs pointing to oid */
+int reader_refs_for(struct reader *r, struct iterator *it, byte *oid,
+		    int oid_len);
+
+/* return the max_update_index for a table */
+uint64_t reader_max_update_index(struct reader *r);
+
+/* return the min_update_index for a table */
+uint64_t reader_min_update_index(struct reader *r);
+
+/* a merged table is implements seeking/iterating over a stack of tables. */
+struct merged_table;
+
+/* new_merged_table creates a new merged table. It takes ownership of the stack
+   array.
+*/
+int new_merged_table(struct merged_table **dest, struct reader **stack, int n);
+
+/* returns an iterator positioned just before 'name' */
+int merged_table_seek_ref(struct merged_table *mt, struct iterator *it,
+			  const char *name);
+
+/* returns an iterator for log entry, at given update_index */
+int merged_table_seek_log_at(struct merged_table *mt, struct iterator *it,
+			     const char *name, uint64_t update_index);
+
+/* like merged_table_seek_log_at but look for the newest entry. */
+int merged_table_seek_log(struct merged_table *mt, struct iterator *it,
+			  const char *name);
+
+/* returns the max update_index covered by this merged table. */
+uint64_t merged_max_update_index(struct merged_table *mt);
+
+/* returns the min update_index covered by this merged table. */
+uint64_t merged_min_update_index(struct merged_table *mt);
+
+/* closes readers for the merged tables */
+void merged_table_close(struct merged_table *mt);
+
+/* releases memory for the merged_table */
+void merged_table_free(struct merged_table *m);
+
+/* a stack is a stack of reftables, which can be mutated by pushing a table to
+ * the top of the stack */
+struct stack;
+
+/* open a new reftable stack. The tables will be stored in 'dir', while the list
+   of tables is in 'list_file'. Typically, this should be .git/reftables and
+   .git/refs respectively.
+*/
+int new_stack(struct stack **dest, const char *dir, const char *list_file,
+	      struct write_options config);
+
+/* returns the update_index at which a next table should be written. */
+uint64_t stack_next_update_index(struct stack *st);
+
+/* add a new table to the stack. The write_table function must call
+   writer_set_limits, add refs and return an error value. */
+int stack_add(struct stack *st,
+	      int (*write_table)(struct writer *wr, void *write_arg),
+	      void *write_arg);
+
+/* returns the merged_table for seeking. This table is valid until the
+   next write or reload, and should not be closed or deleted.
+*/
+struct merged_table *stack_merged_table(struct stack *st);
+
+/* frees all resources associated with the stack. */
+void stack_destroy(struct stack *st);
+
+/* reloads the stack if necessary. */
+int stack_reload(struct stack *st);
+
+/* Policy for expiring reflog entries. */
+struct log_expiry_config {
+	/* Drop entries older than this timestamp */
+	uint64_t time;
+
+	/* Drop older entries */
+	uint64_t min_update_index;
+};
+
+/* compacts all reftables into a giant table. Expire reflog entries if config is
+ * non-NULL */
+int stack_compact_all(struct stack *st, struct log_expiry_config *config);
+
+/* heuristically compact unbalanced table stack. */
+int stack_auto_compact(struct stack *st);
+
+/* convenience function to read a single ref. Returns < 0 for error, 0
+   for success, and 1 if ref not found. */
+int stack_read_ref(struct stack *st, const char *refname,
+		   struct ref_record *ref);
+
+/* convenience function to read a single log. Returns < 0 for error, 0
+   for success, and 1 if ref not found. */
+int stack_read_log(struct stack *st, const char *refname,
+		   struct log_record *log);
+
+/* statistics on past compactions. */
+struct compaction_stats {
+	uint64_t bytes;
+	int attempts;
+	int failures;
+};
+
+struct compaction_stats *stack_compaction_stats(struct stack *st);
+
+#endif
diff --git a/reftable/slice.c b/reftable/slice.c
new file mode 100644
index 0000000000..efbe625253
--- /dev/null
+++ b/reftable/slice.c
@@ -0,0 +1,199 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "slice.h"
+
+#include "system.h"
+
+#include "reftable.h"
+
+void slice_set_string(struct slice *s, const char *str)
+{
+	if (str == NULL) {
+		s->len = 0;
+		return;
+	}
+
+	{
+		int l = strlen(str);
+		l++; /* \0 */
+		slice_resize(s, l);
+		memcpy(s->buf, str, l);
+		s->len = l - 1;
+	}
+}
+
+void slice_resize(struct slice *s, int l)
+{
+	if (s->cap < l) {
+		int c = s->cap * 2;
+		if (c < l) {
+			c = l;
+		}
+		s->cap = c;
+		s->buf = realloc(s->buf, s->cap);
+	}
+	s->len = l;
+}
+
+void slice_append_string(struct slice *d, const char *s)
+{
+	int l1 = d->len;
+	int l2 = strlen(s);
+
+	slice_resize(d, l2 + l1);
+	memcpy(d->buf + l1, s, l2);
+}
+
+void slice_append(struct slice *s, struct slice a)
+{
+	int end = s->len;
+	slice_resize(s, s->len + a.len);
+	memcpy(s->buf + end, a.buf, a.len);
+}
+
+byte *slice_yield(struct slice *s)
+{
+	byte *p = s->buf;
+	s->buf = NULL;
+	s->cap = 0;
+	s->len = 0;
+	return p;
+}
+
+void slice_copy(struct slice *dest, struct slice src)
+{
+	slice_resize(dest, src.len);
+	memcpy(dest->buf, src.buf, src.len);
+}
+
+/* return the underlying data as char*. len is left unchanged, but
+   a \0 is added at the end. */
+const char *slice_as_string(struct slice *s)
+{
+	if (s->cap == s->len) {
+		int l = s->len;
+		slice_resize(s, l + 1);
+		s->len = l;
+	}
+	s->buf[s->len] = 0;
+	return (const char *)s->buf;
+}
+
+/* return a newly malloced string for this slice */
+char *slice_to_string(struct slice in)
+{
+	struct slice s = {};
+	slice_resize(&s, in.len + 1);
+	s.buf[in.len] = 0;
+	memcpy(s.buf, in.buf, in.len);
+	return (char *)slice_yield(&s);
+}
+
+bool slice_equal(struct slice a, struct slice b)
+{
+	if (a.len != b.len) {
+		return 0;
+	}
+	return memcmp(a.buf, b.buf, a.len) == 0;
+}
+
+int slice_compare(struct slice a, struct slice b)
+{
+	int min = a.len < b.len ? a.len : b.len;
+	int res = memcmp(a.buf, b.buf, min);
+	if (res != 0) {
+		return res;
+	}
+	if (a.len < b.len) {
+		return -1;
+	} else if (a.len > b.len) {
+		return 1;
+	} else {
+		return 0;
+	}
+}
+
+int slice_write(struct slice *b, byte *data, int sz)
+{
+	if (b->len + sz > b->cap) {
+		int newcap = 2 * b->cap + 1;
+		if (newcap < b->len + sz) {
+			newcap = (b->len + sz);
+		}
+		b->buf = realloc(b->buf, newcap);
+		b->cap = newcap;
+	}
+
+	memcpy(b->buf + b->len, data, sz);
+	b->len += sz;
+	return sz;
+}
+
+int slice_write_void(void *b, byte *data, int sz)
+{
+	return slice_write((struct slice *)b, data, sz);
+}
+
+static uint64_t slice_size(void *b)
+{
+	return ((struct slice *)b)->len;
+}
+
+static void slice_return_block(void *b, struct block *dest)
+{
+	memset(dest->data, 0xff, dest->len);
+	free(dest->data);
+}
+
+static void slice_close(void *b)
+{
+}
+
+static int slice_read_block(void *v, struct block *dest, uint64_t off,
+			    uint32_t size)
+{
+	struct slice *b = (struct slice *)v;
+	assert(off + size <= b->len);
+	dest->data = calloc(size, 1);
+	memcpy(dest->data, b->buf + off, size);
+	dest->len = size;
+	return size;
+}
+
+struct block_source_vtable slice_vtable = {
+	.size = &slice_size,
+	.read_block = &slice_read_block,
+	.return_block = &slice_return_block,
+	.close = &slice_close,
+};
+
+void block_source_from_slice(struct block_source *bs, struct slice *buf)
+{
+	bs->ops = &slice_vtable;
+	bs->arg = buf;
+}
+
+static void malloc_return_block(void *b, struct block *dest)
+{
+	memset(dest->data, 0xff, dest->len);
+	free(dest->data);
+}
+
+struct block_source_vtable malloc_vtable = {
+	.return_block = &malloc_return_block,
+};
+
+struct block_source malloc_block_source_instance = {
+	.ops = &malloc_vtable,
+};
+
+struct block_source malloc_block_source(void)
+{
+	return malloc_block_source_instance;
+}
diff --git a/reftable/slice.h b/reftable/slice.h
new file mode 100644
index 0000000000..f12a6db228
--- /dev/null
+++ b/reftable/slice.h
@@ -0,0 +1,39 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef SLICE_H
+#define SLICE_H
+
+#include "basics.h"
+#include "reftable.h"
+
+struct slice {
+	byte *buf;
+	int len;
+	int cap;
+};
+
+void slice_set_string(struct slice *dest, const char *);
+void slice_append_string(struct slice *dest, const char *);
+char *slice_to_string(struct slice src);
+const char *slice_as_string(struct slice *src);
+bool slice_equal(struct slice a, struct slice b);
+byte *slice_yield(struct slice *s);
+void slice_copy(struct slice *dest, struct slice src);
+void slice_resize(struct slice *s, int l);
+int slice_compare(struct slice a, struct slice b);
+int slice_write(struct slice *b, byte *data, int sz);
+int slice_write_void(void *b, byte *data, int sz);
+void slice_append(struct slice *dest, struct slice add);
+
+struct block_source;
+void block_source_from_slice(struct block_source *bs, struct slice *buf);
+
+struct block_source malloc_block_source(void);
+
+#endif
diff --git a/reftable/stack.c b/reftable/stack.c
new file mode 100644
index 0000000000..dd16b1fe06
--- /dev/null
+++ b/reftable/stack.c
@@ -0,0 +1,983 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "stack.h"
+
+#include "system.h"
+#include "merged.h"
+#include "reader.h"
+#include "reftable.h"
+#include "writer.h"
+
+int new_stack(struct stack **dest, const char *dir, const char *list_file,
+	      struct write_options config)
+{
+	struct stack *p = calloc(sizeof(struct stack), 1);
+	int err = 0;
+	*dest = NULL;
+	p->list_file = strdup(list_file);
+	p->reftable_dir = strdup(dir);
+	p->config = config;
+
+	err = stack_reload(p);
+	if (err < 0) {
+		stack_destroy(p);
+	} else {
+		*dest = p;
+	}
+	return err;
+}
+
+static int fread_lines(FILE *f, char ***namesp)
+{
+	long size = 0;
+	int err = fseek(f, 0, SEEK_END);
+	char *buf = NULL;
+	if (err < 0) {
+		err = IO_ERROR;
+		goto exit;
+	}
+	size = ftell(f);
+	if (size < 0) {
+		err = IO_ERROR;
+		goto exit;
+	}
+	err = fseek(f, 0, SEEK_SET);
+	if (err < 0) {
+		err = IO_ERROR;
+		goto exit;
+	}
+
+	buf = malloc(size + 1);
+	if (fread(buf, 1, size, f) != size) {
+		err = IO_ERROR;
+		goto exit;
+	}
+	buf[size] = 0;
+
+	parse_names(buf, size, namesp);
+exit:
+	free(buf);
+	return err;
+}
+
+int read_lines(const char *filename, char ***namesp)
+{
+	FILE *f = fopen(filename, "r");
+	int err = 0;
+	if (f == NULL) {
+		if (errno == ENOENT) {
+			*namesp = calloc(sizeof(char *), 1);
+			return 0;
+		}
+
+		return IO_ERROR;
+	}
+	err = fread_lines(f, namesp);
+	fclose(f);
+	return err;
+}
+
+struct merged_table *stack_merged_table(struct stack *st)
+{
+	return st->merged;
+}
+
+/* Close and free the stack */
+void stack_destroy(struct stack *st)
+{
+	if (st->merged == NULL) {
+		return;
+	}
+
+	merged_table_close(st->merged);
+	merged_table_free(st->merged);
+	st->merged = NULL;
+
+	FREE_AND_NULL(st->list_file);
+	FREE_AND_NULL(st->reftable_dir);
+	free(st);
+}
+
+static struct reader **stack_copy_readers(struct stack *st, int cur_len)
+{
+	struct reader **cur = calloc(sizeof(struct reader *), cur_len);
+	int i = 0;
+	for (i = 0; i < cur_len; i++) {
+		cur[i] = st->merged->stack[i];
+	}
+	return cur;
+}
+
+static int stack_reload_once(struct stack *st, char **names, bool reuse_open)
+{
+	int cur_len = st->merged == NULL ? 0 : st->merged->stack_len;
+	struct reader **cur = stack_copy_readers(st, cur_len);
+	int err = 0;
+	int names_len = names_length(names);
+	struct reader **new_tables =
+		malloc(sizeof(struct reader *) * names_len);
+	int new_tables_len = 0;
+	struct merged_table *new_merged = NULL;
+
+	struct slice table_path = {};
+
+	while (*names) {
+		struct reader *rd = NULL;
+		char *name = *names++;
+
+		/* this is linear; we assume compaction keeps the number of
+		   tables under control so this is not quadratic. */
+		int j = 0;
+		for (j = 0; reuse_open && j < cur_len; j++) {
+			if (cur[j] != NULL && 0 == strcmp(cur[j]->name, name)) {
+				rd = cur[j];
+				cur[j] = NULL;
+				break;
+			}
+		}
+
+		if (rd == NULL) {
+			struct block_source src = {};
+			slice_set_string(&table_path, st->reftable_dir);
+			slice_append_string(&table_path, "/");
+			slice_append_string(&table_path, name);
+
+			err = block_source_from_file(
+				&src, slice_as_string(&table_path));
+			if (err < 0) {
+				goto exit;
+			}
+
+			err = new_reader(&rd, src, name);
+			if (err < 0) {
+				goto exit;
+			}
+		}
+
+		new_tables[new_tables_len++] = rd;
+	}
+
+	/* success! */
+	err = new_merged_table(&new_merged, new_tables, new_tables_len);
+	if (err < 0) {
+		goto exit;
+	}
+
+	new_tables = NULL;
+	new_tables_len = 0;
+	if (st->merged != NULL) {
+		merged_table_clear(st->merged);
+		merged_table_free(st->merged);
+	}
+	st->merged = new_merged;
+
+	{
+		int i = 0;
+		for (i = 0; i < cur_len; i++) {
+			if (cur[i] != NULL) {
+				reader_close(cur[i]);
+				reader_free(cur[i]);
+			}
+		}
+	}
+exit:
+	free(slice_yield(&table_path));
+	{
+		int i = 0;
+		for (i = 0; i < new_tables_len; i++) {
+			reader_close(new_tables[i]);
+		}
+	}
+	free(new_tables);
+	free(cur);
+	return err;
+}
+
+/* return negative if a before b. */
+static int tv_cmp(struct timeval *a, struct timeval *b)
+{
+	time_t diff = a->tv_sec - b->tv_sec;
+	int udiff = a->tv_usec - b->tv_usec;
+
+	if (diff != 0) {
+		return diff;
+	}
+
+	return udiff;
+}
+
+static int stack_reload_maybe_reuse(struct stack *st, bool reuse_open)
+{
+	struct timeval deadline = {};
+	int err = gettimeofday(&deadline, NULL);
+	int64_t delay = 0;
+	int tries = 0;
+	if (err < 0) {
+		return err;
+	}
+
+	deadline.tv_sec += 3;
+	while (true) {
+		char **names = NULL;
+		char **names_after = NULL;
+		struct timeval now = {};
+		int err = gettimeofday(&now, NULL);
+		if (err < 0) {
+			return err;
+		}
+
+		/* Only look at deadlines after the first few times. This
+		   simplifies debugging in GDB */
+		tries++;
+		if (tries > 3 && tv_cmp(&now, &deadline) >= 0) {
+			break;
+		}
+
+		err = read_lines(st->list_file, &names);
+		if (err < 0) {
+			free_names(names);
+			return err;
+		}
+		err = stack_reload_once(st, names, reuse_open);
+		if (err == 0) {
+			free_names(names);
+			break;
+		}
+		if (err != NOT_EXIST_ERROR) {
+			free_names(names);
+			return err;
+		}
+
+		err = read_lines(st->list_file, &names_after);
+		if (err < 0) {
+			free_names(names);
+			return err;
+		}
+
+		if (names_equal(names_after, names)) {
+			free_names(names);
+			free_names(names_after);
+			return -1;
+		}
+		free_names(names);
+		free_names(names_after);
+
+		delay = delay + (delay * rand()) / RAND_MAX + 100;
+		usleep(delay);
+	}
+
+	return 0;
+}
+
+int stack_reload(struct stack *st)
+{
+	return stack_reload_maybe_reuse(st, true);
+}
+
+/* -1 = error
+ 0 = up to date
+ 1 = changed. */
+static int stack_uptodate(struct stack *st)
+{
+	char **names = NULL;
+	int err = read_lines(st->list_file, &names);
+	int i = 0;
+	if (err < 0) {
+		return err;
+	}
+
+	for (i = 0; i < st->merged->stack_len; i++) {
+		if (names[i] == NULL) {
+			err = 1;
+			goto exit;
+		}
+
+		if (strcmp(st->merged->stack[i]->name, names[i])) {
+			err = 1;
+			goto exit;
+		}
+	}
+
+	if (names[st->merged->stack_len] != NULL) {
+		err = 1;
+		goto exit;
+	}
+
+exit:
+	free_names(names);
+	return err;
+}
+
+int stack_add(struct stack *st, int (*write)(struct writer *wr, void *arg),
+	      void *arg)
+{
+	int err = stack_try_add(st, write, arg);
+	if (err < 0) {
+		if (err == LOCK_ERROR) {
+			err = stack_reload(st);
+		}
+		return err;
+	}
+
+	return stack_auto_compact(st);
+}
+
+static void format_name(struct slice *dest, uint64_t min, uint64_t max)
+{
+	char buf[100];
+	snprintf(buf, sizeof(buf), "%012" PRIxMAX "-%012" PRIxMAX, min, max);
+	slice_set_string(dest, buf);
+}
+
+int stack_try_add(struct stack *st,
+		  int (*write_table)(struct writer *wr, void *arg), void *arg)
+{
+	struct slice lock_name = {};
+	struct slice temp_tab_name = {};
+	struct slice tab_name = {};
+	struct slice next_name = {};
+	struct slice table_list = {};
+	struct writer *wr = NULL;
+	int err = 0;
+	int tab_fd = 0;
+	int lock_fd = 0;
+	uint64_t next_update_index = 0;
+
+	slice_set_string(&lock_name, st->list_file);
+	slice_append_string(&lock_name, ".lock");
+
+	lock_fd = open(slice_as_string(&lock_name), O_EXCL | O_CREAT | O_WRONLY,
+		       0644);
+	if (lock_fd < 0) {
+		if (errno == EEXIST) {
+			err = LOCK_ERROR;
+			goto exit;
+		}
+		err = IO_ERROR;
+		goto exit;
+	}
+
+	err = stack_uptodate(st);
+	if (err < 0) {
+		goto exit;
+	}
+
+	if (err > 1) {
+		err = LOCK_ERROR;
+		goto exit;
+	}
+
+	next_update_index = stack_next_update_index(st);
+
+	slice_resize(&next_name, 0);
+	format_name(&next_name, next_update_index, next_update_index);
+
+	slice_set_string(&temp_tab_name, st->reftable_dir);
+	slice_append_string(&temp_tab_name, "/");
+	slice_append(&temp_tab_name, next_name);
+	slice_append_string(&temp_tab_name, ".temp.XXXXXX");
+
+	tab_fd = mkstemp((char *)slice_as_string(&temp_tab_name));
+	if (tab_fd < 0) {
+		err = IO_ERROR;
+		goto exit;
+	}
+
+	wr = new_writer(fd_writer, &tab_fd, &st->config);
+	err = write_table(wr, arg);
+	if (err < 0) {
+		goto exit;
+	}
+
+	err = writer_close(wr);
+	if (err < 0) {
+		goto exit;
+	}
+
+	err = close(tab_fd);
+	tab_fd = 0;
+	if (err < 0) {
+		err = IO_ERROR;
+		goto exit;
+	}
+
+	if (wr->min_update_index < next_update_index) {
+		err = API_ERROR;
+		goto exit;
+	}
+
+	{
+		int i = 0;
+		for (i = 0; i < st->merged->stack_len; i++) {
+			slice_append_string(&table_list,
+					    st->merged->stack[i]->name);
+			slice_append_string(&table_list, "\n");
+		}
+	}
+
+	format_name(&next_name, wr->min_update_index, wr->max_update_index);
+	slice_append_string(&next_name, ".ref");
+	slice_append(&table_list, next_name);
+	slice_append_string(&table_list, "\n");
+
+	slice_set_string(&tab_name, st->reftable_dir);
+	slice_append_string(&tab_name, "/");
+	slice_append(&tab_name, next_name);
+
+	err = rename(slice_as_string(&temp_tab_name),
+		     slice_as_string(&tab_name));
+	if (err < 0) {
+		err = IO_ERROR;
+		goto exit;
+	}
+	free(slice_yield(&temp_tab_name));
+
+	err = write(lock_fd, table_list.buf, table_list.len);
+	if (err < 0) {
+		err = IO_ERROR;
+		goto exit;
+	}
+	err = close(lock_fd);
+	lock_fd = 0;
+	if (err < 0) {
+		unlink(slice_as_string(&tab_name));
+		err = IO_ERROR;
+		goto exit;
+	}
+
+	err = rename(slice_as_string(&lock_name), st->list_file);
+	if (err < 0) {
+		unlink(slice_as_string(&tab_name));
+		err = IO_ERROR;
+		goto exit;
+	}
+
+	err = stack_reload(st);
+exit:
+	if (tab_fd > 0) {
+		close(tab_fd);
+		tab_fd = 0;
+	}
+	if (temp_tab_name.len > 0) {
+		unlink(slice_as_string(&temp_tab_name));
+	}
+	unlink(slice_as_string(&lock_name));
+
+	if (lock_fd > 0) {
+		close(lock_fd);
+		lock_fd = 0;
+	}
+
+	free(slice_yield(&lock_name));
+	free(slice_yield(&temp_tab_name));
+	free(slice_yield(&tab_name));
+	free(slice_yield(&next_name));
+	free(slice_yield(&table_list));
+	writer_free(wr);
+	return err;
+}
+
+uint64_t stack_next_update_index(struct stack *st)
+{
+	int sz = st->merged->stack_len;
+	if (sz > 0) {
+		return reader_max_update_index(st->merged->stack[sz - 1]) + 1;
+	}
+	return 1;
+}
+
+static int stack_compact_locked(struct stack *st, int first, int last,
+				struct slice *temp_tab,
+				struct log_expiry_config *config)
+{
+	struct slice next_name = {};
+	int tab_fd = -1;
+	struct writer *wr = NULL;
+	int err = 0;
+
+	format_name(&next_name,
+		    reader_min_update_index(st->merged->stack[first]),
+		    reader_max_update_index(st->merged->stack[first]));
+
+	slice_set_string(temp_tab, st->reftable_dir);
+	slice_append_string(temp_tab, "/");
+	slice_append(temp_tab, next_name);
+	slice_append_string(temp_tab, ".temp.XXXXXX");
+
+	tab_fd = mkstemp((char *)slice_as_string(temp_tab));
+	wr = new_writer(fd_writer, &tab_fd, &st->config);
+
+	err = stack_write_compact(st, wr, first, last, config);
+	if (err < 0) {
+		goto exit;
+	}
+	err = writer_close(wr);
+	if (err < 0) {
+		goto exit;
+	}
+	writer_free(wr);
+
+	err = close(tab_fd);
+	tab_fd = 0;
+
+exit:
+	if (tab_fd > 0) {
+		close(tab_fd);
+		tab_fd = 0;
+	}
+	if (err != 0 && temp_tab->len > 0) {
+		unlink(slice_as_string(temp_tab));
+		free(slice_yield(temp_tab));
+	}
+	free(slice_yield(&next_name));
+	return err;
+}
+
+int stack_write_compact(struct stack *st, struct writer *wr, int first,
+			int last, struct log_expiry_config *config)
+{
+	int subtabs_len = last - first + 1;
+	struct reader **subtabs =
+		calloc(sizeof(struct reader *), last - first + 1);
+	struct merged_table *mt = NULL;
+	int err = 0;
+	struct iterator it = {};
+	struct ref_record ref = {};
+	struct log_record log = {};
+
+	int i = 0, j = 0;
+	for (i = first, j = 0; i <= last; i++) {
+		struct reader *t = st->merged->stack[i];
+		subtabs[j++] = t;
+		st->stats.bytes += t->size;
+	}
+	writer_set_limits(wr, st->merged->stack[first]->min_update_index,
+			  st->merged->stack[last]->max_update_index);
+
+	err = new_merged_table(&mt, subtabs, subtabs_len);
+	if (err < 0) {
+		free(subtabs);
+		goto exit;
+	}
+
+	err = merged_table_seek_ref(mt, &it, "");
+	if (err < 0) {
+		goto exit;
+	}
+
+	while (true) {
+		err = iterator_next_ref(it, &ref);
+		if (err > 0) {
+			err = 0;
+			break;
+		}
+		if (err < 0) {
+			break;
+		}
+		if (first == 0 && ref_record_is_deletion(&ref)) {
+			continue;
+		}
+
+		err = writer_add_ref(wr, &ref);
+		if (err < 0) {
+			break;
+		}
+	}
+
+	err = merged_table_seek_log(mt, &it, "");
+	if (err < 0) {
+		goto exit;
+	}
+
+	while (true) {
+		err = iterator_next_log(it, &log);
+		if (err > 0) {
+			err = 0;
+			break;
+		}
+		if (err < 0) {
+			break;
+		}
+		if (first == 0 && log_record_is_deletion(&log)) {
+			continue;
+		}
+
+		/* XXX collect stats? */
+
+		if (config != NULL && config->time > 0 &&
+		    log.time < config->time) {
+			continue;
+		}
+
+		if (config != NULL && config->min_update_index > 0 &&
+		    log.update_index < config->min_update_index) {
+			continue;
+		}
+
+		err = writer_add_log(wr, &log);
+		if (err < 0) {
+			break;
+		}
+	}
+
+exit:
+	iterator_destroy(&it);
+	if (mt != NULL) {
+		merged_table_clear(mt);
+		merged_table_free(mt);
+	}
+	ref_record_clear(&ref);
+
+	return err;
+}
+
+/* <  0: error. 0 == OK, > 0 attempt failed; could retry. */
+static int stack_compact_range(struct stack *st, int first, int last,
+			       struct log_expiry_config *expiry)
+{
+	struct slice temp_tab_name = {};
+	struct slice new_table_name = {};
+	struct slice lock_file_name = {};
+	struct slice ref_list_contents = {};
+	struct slice new_table_path = {};
+	int err = 0;
+	bool have_lock = false;
+	int lock_file_fd = 0;
+	int compact_count = last - first + 1;
+	char **delete_on_success = calloc(sizeof(char *), compact_count + 1);
+	char **subtable_locks = calloc(sizeof(char *), compact_count + 1);
+	int i = 0;
+	int j = 0;
+
+	if (first > last || (expiry == NULL && first == last)) {
+		err = 0;
+		goto exit;
+	}
+
+	st->stats.attempts++;
+
+	slice_set_string(&lock_file_name, st->list_file);
+	slice_append_string(&lock_file_name, ".lock");
+
+	lock_file_fd = open(slice_as_string(&lock_file_name),
+			    O_EXCL | O_CREAT | O_WRONLY, 0644);
+	if (lock_file_fd < 0) {
+		if (errno == EEXIST) {
+			err = 1;
+		} else {
+			err = IO_ERROR;
+		}
+		goto exit;
+	}
+	have_lock = true;
+	err = stack_uptodate(st);
+	if (err != 0) {
+		goto exit;
+	}
+
+	for (i = first, j = 0; i <= last; i++) {
+		struct slice subtab_name = {};
+		struct slice subtab_lock = {};
+		slice_set_string(&subtab_name, st->reftable_dir);
+		slice_append_string(&subtab_name, "/");
+		slice_append_string(&subtab_name,
+				    reader_name(st->merged->stack[i]));
+
+		slice_copy(&subtab_lock, subtab_name);
+		slice_append_string(&subtab_lock, ".lock");
+
+		{
+			int sublock_file_fd =
+				open(slice_as_string(&subtab_lock),
+				     O_EXCL | O_CREAT | O_WRONLY, 0644);
+			if (sublock_file_fd > 0) {
+				close(sublock_file_fd);
+			} else if (sublock_file_fd < 0) {
+				if (errno == EEXIST) {
+					err = 1;
+				}
+				err = IO_ERROR;
+			}
+		}
+
+		subtable_locks[j] = (char *)slice_as_string(&subtab_lock);
+		delete_on_success[j] = (char *)slice_as_string(&subtab_name);
+		j++;
+
+		if (err != 0) {
+			goto exit;
+		}
+	}
+
+	err = unlink(slice_as_string(&lock_file_name));
+	if (err < 0) {
+		goto exit;
+	}
+	have_lock = false;
+
+	err = stack_compact_locked(st, first, last, &temp_tab_name, expiry);
+	if (err < 0) {
+		goto exit;
+	}
+
+	lock_file_fd = open(slice_as_string(&lock_file_name),
+			    O_EXCL | O_CREAT | O_WRONLY, 0644);
+	if (lock_file_fd < 0) {
+		if (errno == EEXIST) {
+			err = 1;
+		} else {
+			err = IO_ERROR;
+		}
+		goto exit;
+	}
+	have_lock = true;
+
+	format_name(&new_table_name, st->merged->stack[first]->min_update_index,
+		    st->merged->stack[last]->max_update_index);
+	slice_append_string(&new_table_name, ".ref");
+
+	slice_set_string(&new_table_path, st->reftable_dir);
+	slice_append_string(&new_table_path, "/");
+
+	slice_append(&new_table_path, new_table_name);
+
+	err = rename(slice_as_string(&temp_tab_name),
+		     slice_as_string(&new_table_path));
+	if (err < 0) {
+		goto exit;
+	}
+
+	for (i = 0; i < first; i++) {
+		slice_append_string(&ref_list_contents,
+				    st->merged->stack[i]->name);
+		slice_append_string(&ref_list_contents, "\n");
+	}
+	slice_append(&ref_list_contents, new_table_name);
+	slice_append_string(&ref_list_contents, "\n");
+	for (i = last + 1; i < st->merged->stack_len; i++) {
+		slice_append_string(&ref_list_contents,
+				    st->merged->stack[i]->name);
+		slice_append_string(&ref_list_contents, "\n");
+	}
+
+	err = write(lock_file_fd, ref_list_contents.buf, ref_list_contents.len);
+	if (err < 0) {
+		unlink(slice_as_string(&new_table_path));
+		goto exit;
+	}
+	err = close(lock_file_fd);
+	lock_file_fd = 0;
+	if (err < 0) {
+		unlink(slice_as_string(&new_table_path));
+		goto exit;
+	}
+
+	err = rename(slice_as_string(&lock_file_name), st->list_file);
+	if (err < 0) {
+		unlink(slice_as_string(&new_table_path));
+		goto exit;
+	}
+	have_lock = false;
+
+	for (char **p = delete_on_success; *p; p++) {
+		if (strcmp(*p, slice_as_string(&new_table_path))) {
+			unlink(*p);
+		}
+	}
+
+	err = stack_reload_maybe_reuse(st, first < last);
+exit:
+	for (char **p = subtable_locks; *p; p++) {
+		unlink(*p);
+	}
+	free_names(delete_on_success);
+	free_names(subtable_locks);
+	if (lock_file_fd > 0) {
+		close(lock_file_fd);
+		lock_file_fd = 0;
+	}
+	if (have_lock) {
+		unlink(slice_as_string(&lock_file_name));
+	}
+	free(slice_yield(&new_table_name));
+	free(slice_yield(&new_table_path));
+	free(slice_yield(&ref_list_contents));
+	free(slice_yield(&temp_tab_name));
+	free(slice_yield(&lock_file_name));
+	return err;
+}
+
+int stack_compact_all(struct stack *st, struct log_expiry_config *config)
+{
+	return stack_compact_range(st, 0, st->merged->stack_len - 1, config);
+}
+
+static int stack_compact_range_stats(struct stack *st, int first, int last,
+				     struct log_expiry_config *config)
+{
+	int err = stack_compact_range(st, first, last, config);
+	if (err > 0) {
+		st->stats.failures++;
+	}
+	return err;
+}
+
+static int segment_size(struct segment *s)
+{
+	return s->end - s->start;
+}
+
+int fastlog2(uint64_t sz)
+{
+	int l = 0;
+	assert(sz > 0);
+	for (; sz; sz /= 2) {
+		l++;
+	}
+	return l - 1;
+}
+
+struct segment *sizes_to_segments(int *seglen, uint64_t *sizes, int n)
+{
+	struct segment *segs = calloc(sizeof(struct segment), n);
+	int next = 0;
+	struct segment cur = {};
+	int i = 0;
+	for (i = 0; i < n; i++) {
+		int log = fastlog2(sizes[i]);
+		if (cur.log != log && cur.bytes > 0) {
+			struct segment fresh = {
+				.start = i,
+			};
+
+			segs[next++] = cur;
+			cur = fresh;
+		}
+
+		cur.log = log;
+		cur.end = i + 1;
+		cur.bytes += sizes[i];
+	}
+
+	segs[next++] = cur;
+	*seglen = next;
+	return segs;
+}
+
+struct segment suggest_compaction_segment(uint64_t *sizes, int n)
+{
+	int seglen = 0;
+	struct segment *segs = sizes_to_segments(&seglen, sizes, n);
+	struct segment min_seg = {
+		.log = 64,
+	};
+	int i = 0;
+	for (i = 0; i < seglen; i++) {
+		if (segment_size(&segs[i]) == 1) {
+			continue;
+		}
+
+		if (segs[i].log < min_seg.log) {
+			min_seg = segs[i];
+		}
+	}
+
+	while (min_seg.start > 0) {
+		int prev = min_seg.start - 1;
+		if (fastlog2(min_seg.bytes) < fastlog2(sizes[prev])) {
+			break;
+		}
+
+		min_seg.start = prev;
+		min_seg.bytes += sizes[prev];
+	}
+
+	free(segs);
+	return min_seg;
+}
+
+static uint64_t *stack_table_sizes_for_compaction(struct stack *st)
+{
+	uint64_t *sizes = calloc(sizeof(uint64_t), st->merged->stack_len);
+	int i = 0;
+	for (i = 0; i < st->merged->stack_len; i++) {
+		/* overhead is 24 + 68 = 92. */
+		sizes[i] = st->merged->stack[i]->size - 91;
+	}
+	return sizes;
+}
+
+int stack_auto_compact(struct stack *st)
+{
+	uint64_t *sizes = stack_table_sizes_for_compaction(st);
+	struct segment seg =
+		suggest_compaction_segment(sizes, st->merged->stack_len);
+	free(sizes);
+	if (segment_size(&seg) > 0) {
+		return stack_compact_range_stats(st, seg.start, seg.end - 1,
+						 NULL);
+	}
+
+	return 0;
+}
+
+struct compaction_stats *stack_compaction_stats(struct stack *st)
+{
+	return &st->stats;
+}
+
+int stack_read_ref(struct stack *st, const char *refname,
+		   struct ref_record *ref)
+{
+	struct iterator it = {};
+	struct merged_table *mt = stack_merged_table(st);
+	int err = merged_table_seek_ref(mt, &it, refname);
+	if (err) {
+		goto exit;
+	}
+
+	err = iterator_next_ref(it, ref);
+	if (err) {
+		goto exit;
+	}
+
+	if (strcmp(ref->ref_name, refname) || ref_record_is_deletion(ref)) {
+		err = 1;
+		goto exit;
+	}
+
+exit:
+	iterator_destroy(&it);
+	return err;
+}
+
+int stack_read_log(struct stack *st, const char *refname,
+		   struct log_record *log)
+{
+	struct iterator it = {};
+	struct merged_table *mt = stack_merged_table(st);
+	int err = merged_table_seek_log(mt, &it, refname);
+	if (err) {
+		goto exit;
+	}
+
+	err = iterator_next_log(it, log);
+	if (err) {
+		goto exit;
+	}
+
+	if (strcmp(log->ref_name, refname) || log_record_is_deletion(log)) {
+		err = 1;
+		goto exit;
+	}
+
+exit:
+	iterator_destroy(&it);
+	return err;
+}
diff --git a/reftable/stack.h b/reftable/stack.h
new file mode 100644
index 0000000000..d5e2c93c29
--- /dev/null
+++ b/reftable/stack.h
@@ -0,0 +1,40 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef STACK_H
+#define STACK_H
+
+#include "reftable.h"
+
+struct stack {
+	char *list_file;
+	char *reftable_dir;
+
+	struct write_options config;
+
+	struct merged_table *merged;
+	struct compaction_stats stats;
+};
+
+int read_lines(const char *filename, char ***lines);
+int stack_try_add(struct stack *st,
+		  int (*write_table)(struct writer *wr, void *arg), void *arg);
+int stack_write_compact(struct stack *st, struct writer *wr, int first,
+			int last, struct log_expiry_config *config);
+int fastlog2(uint64_t sz);
+
+struct segment {
+	int start, end;
+	int log;
+	uint64_t bytes;
+};
+
+struct segment *sizes_to_segments(int *seglen, uint64_t *sizes, int n);
+struct segment suggest_compaction_segment(uint64_t *sizes, int n);
+
+#endif
diff --git a/reftable/system.h b/reftable/system.h
new file mode 100644
index 0000000000..5e9f442374
--- /dev/null
+++ b/reftable/system.h
@@ -0,0 +1,57 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef SYSTEM_H
+#define SYSTEM_H
+
+#include "config.h"
+
+#ifndef REFTABLE_STANDALONE
+
+#include "git-compat-util.h"
+#include <zlib.h>
+
+#else
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <zlib.h>
+
+#define PRIuMAX "lu"
+#define PRIdMAX "ld"
+#define PRIxMAX "lx"
+#define ARRAY_SIZE(a) sizeof((a)) / sizeof((a)[0])
+#define FREE_AND_NULL(x)    \
+	do {                \
+		free(x);    \
+		(x) = NULL; \
+	} while (0)
+#define QSORT(arr, n, cmp) qsort(arr, n, sizeof(arr[0]), cmp)
+#define SWAP(a, b) \
+  { \
+     char tmp[sizeof(a)]; \
+     assert(sizeof(a)==sizeof(b)); \
+     memcpy(&tmp[0], &a, sizeof(a)); \
+     memcpy(&a, &b, sizeof(a)); \
+     memcpy(&b, &tmp[0], sizeof(a)); \
+  }
+#endif
+
+int uncompress_return_consumed(Bytef *dest, uLongf *destLen,
+			       const Bytef *source, uLong *sourceLen);
+
+#endif
diff --git a/reftable/tree.c b/reftable/tree.c
new file mode 100644
index 0000000000..9bf7fe531f
--- /dev/null
+++ b/reftable/tree.c
@@ -0,0 +1,66 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "tree.h"
+
+#include "system.h"
+
+struct tree_node *tree_search(void *key, struct tree_node **rootp,
+			      int (*compare)(const void *, const void *),
+			      int insert)
+{
+	if (*rootp == NULL) {
+		if (!insert) {
+			return NULL;
+		} else {
+			struct tree_node *n =
+				calloc(sizeof(struct tree_node), 1);
+			n->key = key;
+			*rootp = n;
+			return *rootp;
+		}
+	}
+
+	{
+		int res = compare(key, (*rootp)->key);
+		if (res < 0) {
+			return tree_search(key, &(*rootp)->left, compare,
+					   insert);
+		} else if (res > 0) {
+			return tree_search(key, &(*rootp)->right, compare,
+					   insert);
+		}
+	}
+	return *rootp;
+}
+
+void infix_walk(struct tree_node *t, void (*action)(void *arg, void *key),
+		void *arg)
+{
+	if (t->left != NULL) {
+		infix_walk(t->left, action, arg);
+	}
+	action(arg, t->key);
+	if (t->right != NULL) {
+		infix_walk(t->right, action, arg);
+	}
+}
+
+void tree_free(struct tree_node *t)
+{
+	if (t == NULL) {
+		return;
+	}
+	if (t->left != NULL) {
+		tree_free(t->left);
+	}
+	if (t->right != NULL) {
+		tree_free(t->right);
+	}
+	free(t);
+}
diff --git a/reftable/tree.h b/reftable/tree.h
new file mode 100644
index 0000000000..86a71715ae
--- /dev/null
+++ b/reftable/tree.h
@@ -0,0 +1,24 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef TREE_H
+#define TREE_H
+
+struct tree_node {
+	void *key;
+	struct tree_node *left, *right;
+};
+
+struct tree_node *tree_search(void *key, struct tree_node **rootp,
+			      int (*compare)(const void *, const void *),
+			      int insert);
+void infix_walk(struct tree_node *t, void (*action)(void *arg, void *key),
+		void *arg);
+void tree_free(struct tree_node *t);
+
+#endif
diff --git a/reftable/writer.c b/reftable/writer.c
new file mode 100644
index 0000000000..3785bc8a4a
--- /dev/null
+++ b/reftable/writer.c
@@ -0,0 +1,622 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "writer.h"
+
+#include "system.h"
+
+#include "block.h"
+#include "constants.h"
+#include "record.h"
+#include "reftable.h"
+#include "tree.h"
+
+static struct block_stats *writer_block_stats(struct writer *w, byte typ)
+{
+	switch (typ) {
+	case 'r':
+		return &w->stats.ref_stats;
+	case 'o':
+		return &w->stats.obj_stats;
+	case 'i':
+		return &w->stats.idx_stats;
+	case 'g':
+		return &w->stats.log_stats;
+	}
+	assert(false);
+}
+
+/* write data, queuing the padding for the next write. Returns negative for
+ * error. */
+static int padded_write(struct writer *w, byte *data, size_t len, int padding)
+{
+	int n = 0;
+	if (w->pending_padding > 0) {
+		byte *zeroed = calloc(w->pending_padding, 1);
+		int n = w->write(w->write_arg, zeroed, w->pending_padding);
+		if (n < 0) {
+			return n;
+		}
+
+		w->pending_padding = 0;
+		free(zeroed);
+	}
+
+	w->pending_padding = padding;
+	n = w->write(w->write_arg, data, len);
+	if (n < 0) {
+		return n;
+	}
+	n += padding;
+	return 0;
+}
+
+static void options_set_defaults(struct write_options *opts)
+{
+	if (opts->restart_interval == 0) {
+		opts->restart_interval = 16;
+	}
+
+	if (opts->block_size == 0) {
+		opts->block_size = DEFAULT_BLOCK_SIZE;
+	}
+}
+
+static int writer_write_header(struct writer *w, byte *dest)
+{
+	memcpy((char *)dest, "REFT", 4);
+	dest[4] = 1; /* version */
+	put_u24(dest + 5, w->opts.block_size);
+	put_u64(dest + 8, w->min_update_index);
+	put_u64(dest + 16, w->max_update_index);
+	return 24;
+}
+
+static void writer_reinit_block_writer(struct writer *w, byte typ)
+{
+	int block_start = 0;
+	if (w->next == 0) {
+		block_start = HEADER_SIZE;
+	}
+
+	block_writer_init(&w->block_writer_data, typ, w->block,
+			  w->opts.block_size, block_start, w->hash_size);
+	w->block_writer = &w->block_writer_data;
+	w->block_writer->restart_interval = w->opts.restart_interval;
+}
+
+struct writer *new_writer(int (*writer_func)(void *, byte *, int),
+			  void *writer_arg, struct write_options *opts)
+{
+	struct writer *wp = calloc(sizeof(struct writer), 1);
+	options_set_defaults(opts);
+	if (opts->block_size >= (1 << 24)) {
+		/* TODO - error return? */
+		abort();
+	}
+	wp->hash_size = SHA1_SIZE;
+	wp->block = calloc(opts->block_size, 1);
+	wp->write = writer_func;
+	wp->write_arg = writer_arg;
+	wp->opts = *opts;
+	writer_reinit_block_writer(wp, BLOCK_TYPE_REF);
+
+	return wp;
+}
+
+void writer_set_limits(struct writer *w, uint64_t min, uint64_t max)
+{
+	w->min_update_index = min;
+	w->max_update_index = max;
+}
+
+void writer_free(struct writer *w)
+{
+	free(w->block);
+	free(w);
+}
+
+struct obj_index_tree_node {
+	struct slice hash;
+	uint64_t *offsets;
+	int offset_len;
+	int offset_cap;
+};
+
+static int obj_index_tree_node_compare(const void *a, const void *b)
+{
+	return slice_compare(((const struct obj_index_tree_node *)a)->hash,
+			     ((const struct obj_index_tree_node *)b)->hash);
+}
+
+static void writer_index_hash(struct writer *w, struct slice hash)
+{
+	uint64_t off = w->next;
+
+	struct obj_index_tree_node want = { .hash = hash };
+
+	struct tree_node *node = tree_search(&want, &w->obj_index_tree,
+					     &obj_index_tree_node_compare, 0);
+	struct obj_index_tree_node *key = NULL;
+	if (node == NULL) {
+		key = calloc(sizeof(struct obj_index_tree_node), 1);
+		slice_copy(&key->hash, hash);
+		tree_search((void *)key, &w->obj_index_tree,
+			    &obj_index_tree_node_compare, 1);
+	} else {
+		key = node->key;
+	}
+
+	if (key->offset_len > 0 && key->offsets[key->offset_len - 1] == off) {
+		return;
+	}
+
+	if (key->offset_len == key->offset_cap) {
+		key->offset_cap = 2 * key->offset_cap + 1;
+		key->offsets = realloc(key->offsets,
+				       sizeof(uint64_t) * key->offset_cap);
+	}
+
+	key->offsets[key->offset_len++] = off;
+}
+
+static int writer_add_record(struct writer *w, struct record rec)
+{
+	int result = -1;
+	struct slice key = {};
+	int err = 0;
+	record_key(rec, &key);
+	if (slice_compare(w->last_key, key) >= 0) {
+		goto exit;
+	}
+
+	slice_copy(&w->last_key, key);
+	if (w->block_writer == NULL) {
+		writer_reinit_block_writer(w, record_type(rec));
+	}
+
+	assert(block_writer_type(w->block_writer) == record_type(rec));
+
+	if (block_writer_add(w->block_writer, rec) == 0) {
+		result = 0;
+		goto exit;
+	}
+
+	err = writer_flush_block(w);
+	if (err < 0) {
+		result = err;
+		goto exit;
+	}
+
+	writer_reinit_block_writer(w, record_type(rec));
+	err = block_writer_add(w->block_writer, rec);
+	if (err < 0) {
+		result = err;
+		goto exit;
+	}
+
+	result = 0;
+exit:
+	free(slice_yield(&key));
+	return result;
+}
+
+int writer_add_ref(struct writer *w, struct ref_record *ref)
+{
+	struct record rec = {};
+	struct ref_record copy = *ref;
+	int err = 0;
+
+	if (ref->ref_name == NULL) {
+		return API_ERROR;
+	}
+	if (ref->update_index < w->min_update_index ||
+	    ref->update_index > w->max_update_index) {
+		return API_ERROR;
+	}
+
+	record_from_ref(&rec, &copy);
+	copy.update_index -= w->min_update_index;
+	err = writer_add_record(w, rec);
+	if (err < 0) {
+		return err;
+	}
+
+	if (!w->opts.skip_index_objects && ref->value != NULL) {
+		struct slice h = {
+			.buf = ref->value,
+			.len = w->hash_size,
+		};
+
+		writer_index_hash(w, h);
+	}
+	if (!w->opts.skip_index_objects && ref->target_value != NULL) {
+		struct slice h = {
+			.buf = ref->target_value,
+			.len = w->hash_size,
+		};
+		writer_index_hash(w, h);
+	}
+	return 0;
+}
+
+int writer_add_refs(struct writer *w, struct ref_record *refs, int n)
+{
+	int err = 0;
+	int i = 0;
+	QSORT(refs, n, ref_record_compare_name);
+	for (i = 0; err == 0 && i < n; i++) {
+		err = writer_add_ref(w, &refs[i]);
+	}
+	return err;
+}
+
+int writer_add_log(struct writer *w, struct log_record *log)
+{
+	if (log->ref_name == NULL) {
+		return API_ERROR;
+	}
+
+	if (w->block_writer != NULL &&
+	    block_writer_type(w->block_writer) == BLOCK_TYPE_REF) {
+		int err = writer_finish_public_section(w);
+		if (err < 0) {
+			return err;
+		}
+	}
+
+	w->next -= w->pending_padding;
+	w->pending_padding = 0;
+
+	{
+		struct record rec = {};
+		int err;
+		record_from_log(&rec, log);
+		err = writer_add_record(w, rec);
+		return err;
+	}
+}
+
+int writer_add_logs(struct writer *w, struct log_record *logs, int n)
+{
+	int err = 0;
+	int i = 0;
+	QSORT(logs, n, log_record_compare_key);
+	for (i = 0; err == 0 && i < n; i++) {
+		err = writer_add_log(w, &logs[i]);
+	}
+	return err;
+}
+
+static int writer_finish_section(struct writer *w)
+{
+	byte typ = block_writer_type(w->block_writer);
+	uint64_t index_start = 0;
+	int max_level = 0;
+	int threshold = w->opts.unpadded ? 1 : 3;
+	int before_blocks = w->stats.idx_stats.blocks;
+	int err = writer_flush_block(w);
+	int i = 0;
+	if (err < 0) {
+		return err;
+	}
+
+	while (w->index_len > threshold) {
+		struct index_record *idx = NULL;
+		int idx_len = 0;
+
+		max_level++;
+		index_start = w->next;
+		writer_reinit_block_writer(w, BLOCK_TYPE_INDEX);
+
+		idx = w->index;
+		idx_len = w->index_len;
+
+		w->index = NULL;
+		w->index_len = 0;
+		w->index_cap = 0;
+		for (i = 0; i < idx_len; i++) {
+			struct record rec = {};
+			record_from_index(&rec, idx + i);
+			if (block_writer_add(w->block_writer, rec) == 0) {
+				continue;
+			}
+
+			{
+				int err = writer_flush_block(w);
+				if (err < 0) {
+					return err;
+				}
+			}
+
+			writer_reinit_block_writer(w, BLOCK_TYPE_INDEX);
+
+			err = block_writer_add(w->block_writer, rec);
+			assert(err == 0);
+		}
+		for (i = 0; i < idx_len; i++) {
+			free(slice_yield(&idx[i].last_key));
+		}
+		free(idx);
+	}
+
+	writer_clear_index(w);
+
+	err = writer_flush_block(w);
+	if (err < 0) {
+		return err;
+	}
+
+	{
+		struct block_stats *bstats = writer_block_stats(w, typ);
+		bstats->index_blocks =
+			w->stats.idx_stats.blocks - before_blocks;
+		bstats->index_offset = index_start;
+		bstats->max_index_level = max_level;
+	}
+
+	/* Reinit lastKey, as the next section can start with any key. */
+	w->last_key.len = 0;
+
+	return 0;
+}
+
+struct common_prefix_arg {
+	struct slice *last;
+	int max;
+};
+
+static void update_common(void *void_arg, void *key)
+{
+	struct common_prefix_arg *arg = (struct common_prefix_arg *)void_arg;
+	struct obj_index_tree_node *entry = (struct obj_index_tree_node *)key;
+	if (arg->last != NULL) {
+		int n = common_prefix_size(entry->hash, *arg->last);
+		if (n > arg->max) {
+			arg->max = n;
+		}
+	}
+	arg->last = &entry->hash;
+}
+
+struct write_record_arg {
+	struct writer *w;
+	int err;
+};
+
+static void write_object_record(void *void_arg, void *key)
+{
+	struct write_record_arg *arg = (struct write_record_arg *)void_arg;
+	struct obj_index_tree_node *entry = (struct obj_index_tree_node *)key;
+	struct obj_record obj_rec = {
+		.hash_prefix = entry->hash.buf,
+		.hash_prefix_len = arg->w->stats.object_id_len,
+		.offsets = entry->offsets,
+		.offset_len = entry->offset_len,
+	};
+	struct record rec = {};
+	if (arg->err < 0) {
+		goto exit;
+	}
+
+	record_from_obj(&rec, &obj_rec);
+	arg->err = block_writer_add(arg->w->block_writer, rec);
+	if (arg->err == 0) {
+		goto exit;
+	}
+
+	arg->err = writer_flush_block(arg->w);
+	if (arg->err < 0) {
+		goto exit;
+	}
+
+	writer_reinit_block_writer(arg->w, BLOCK_TYPE_OBJ);
+	arg->err = block_writer_add(arg->w->block_writer, rec);
+	if (arg->err == 0) {
+		goto exit;
+	}
+	obj_rec.offset_len = 0;
+	arg->err = block_writer_add(arg->w->block_writer, rec);
+
+	/* Should be able to write into a fresh block. */
+	assert(arg->err == 0);
+
+exit:;
+}
+
+static void object_record_free(void *void_arg, void *key)
+{
+	struct obj_index_tree_node *entry = (struct obj_index_tree_node *)key;
+
+	FREE_AND_NULL(entry->offsets);
+	free(slice_yield(&entry->hash));
+	free(entry);
+}
+
+static int writer_dump_object_index(struct writer *w)
+{
+	struct write_record_arg closure = { .w = w };
+	struct common_prefix_arg common = {};
+	if (w->obj_index_tree != NULL) {
+		infix_walk(w->obj_index_tree, &update_common, &common);
+	}
+	w->stats.object_id_len = common.max + 1;
+
+	writer_reinit_block_writer(w, BLOCK_TYPE_OBJ);
+
+	if (w->obj_index_tree != NULL) {
+		infix_walk(w->obj_index_tree, &write_object_record, &closure);
+	}
+
+	if (closure.err < 0) {
+		return closure.err;
+	}
+	return writer_finish_section(w);
+}
+
+int writer_finish_public_section(struct writer *w)
+{
+	byte typ = 0;
+	int err = 0;
+
+	if (w->block_writer == NULL) {
+		return 0;
+	}
+
+	typ = block_writer_type(w->block_writer);
+	err = writer_finish_section(w);
+	if (err < 0) {
+		return err;
+	}
+	if (typ == BLOCK_TYPE_REF && !w->opts.skip_index_objects &&
+	    w->stats.ref_stats.index_blocks > 0) {
+		err = writer_dump_object_index(w);
+		if (err < 0) {
+			return err;
+		}
+	}
+
+	if (w->obj_index_tree != NULL) {
+		infix_walk(w->obj_index_tree, &object_record_free, NULL);
+		tree_free(w->obj_index_tree);
+		w->obj_index_tree = NULL;
+	}
+
+	w->block_writer = NULL;
+	return 0;
+}
+
+int writer_close(struct writer *w)
+{
+	byte footer[68];
+	byte *p = footer;
+
+	writer_finish_public_section(w);
+
+	writer_write_header(w, footer);
+	p += 24;
+	put_u64(p, w->stats.ref_stats.index_offset);
+	p += 8;
+	put_u64(p, (w->stats.obj_stats.offset) << 5 | w->stats.object_id_len);
+	p += 8;
+	put_u64(p, w->stats.obj_stats.index_offset);
+	p += 8;
+
+	put_u64(p, w->stats.log_stats.offset);
+	p += 8;
+	put_u64(p, w->stats.log_stats.index_offset);
+	p += 8;
+
+	put_u32(p, crc32(0, footer, p - footer));
+	p += 4;
+	w->pending_padding = 0;
+
+	{
+		int n = padded_write(w, footer, sizeof(footer), 0);
+		if (n < 0) {
+			return n;
+		}
+	}
+
+	/* free up memory. */
+	block_writer_clear(&w->block_writer_data);
+	writer_clear_index(w);
+	free(slice_yield(&w->last_key));
+	return 0;
+}
+
+void writer_clear_index(struct writer *w)
+{
+	int i = 0;
+	for (i = 0; i < w->index_len; i++) {
+		free(slice_yield(&w->index[i].last_key));
+	}
+
+	FREE_AND_NULL(w->index);
+	w->index_len = 0;
+	w->index_cap = 0;
+}
+
+const int debug = 0;
+
+static int writer_flush_nonempty_block(struct writer *w)
+{
+	byte typ = block_writer_type(w->block_writer);
+	struct block_stats *bstats = writer_block_stats(w, typ);
+	uint64_t block_typ_off = (bstats->blocks == 0) ? w->next : 0;
+	int raw_bytes = block_writer_finish(w->block_writer);
+	int padding = 0;
+	int err = 0;
+	if (raw_bytes < 0) {
+		return raw_bytes;
+	}
+
+	if (!w->opts.unpadded && typ != BLOCK_TYPE_LOG) {
+		padding = w->opts.block_size - raw_bytes;
+	}
+
+	if (block_typ_off > 0) {
+		bstats->offset = block_typ_off;
+	}
+
+	bstats->entries += w->block_writer->entries;
+	bstats->restarts += w->block_writer->restart_len;
+	bstats->blocks++;
+	w->stats.blocks++;
+
+	if (debug) {
+		fprintf(stderr, "block %c off %" PRIuMAX " sz %d (%d)\n", typ,
+			w->next, raw_bytes,
+			get_u24(w->block + w->block_writer->header_off + 1));
+	}
+
+	if (w->next == 0) {
+		writer_write_header(w, w->block);
+	}
+
+	err = padded_write(w, w->block, raw_bytes, padding);
+	if (err < 0) {
+		return err;
+	}
+
+	if (w->index_cap == w->index_len) {
+		w->index_cap = 2 * w->index_cap + 1;
+		w->index = realloc(w->index,
+				   sizeof(struct index_record) * w->index_cap);
+	}
+
+	{
+		struct index_record ir = {
+			.offset = w->next,
+		};
+		slice_copy(&ir.last_key, w->block_writer->last_key);
+		w->index[w->index_len] = ir;
+	}
+
+	w->index_len++;
+	w->next += padding + raw_bytes;
+	block_writer_reset(&w->block_writer_data);
+	w->block_writer = NULL;
+	return 0;
+}
+
+int writer_flush_block(struct writer *w)
+{
+	if (w->block_writer == NULL) {
+		return 0;
+	}
+	if (w->block_writer->entries == 0) {
+		return 0;
+	}
+	return writer_flush_nonempty_block(w);
+}
+
+struct stats *writer_stats(struct writer *w)
+{
+	return &w->stats;
+}
diff --git a/reftable/writer.h b/reftable/writer.h
new file mode 100644
index 0000000000..bd2386474b
--- /dev/null
+++ b/reftable/writer.h
@@ -0,0 +1,46 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef WRITER_H
+#define WRITER_H
+
+#include "basics.h"
+#include "block.h"
+#include "reftable.h"
+#include "slice.h"
+#include "tree.h"
+
+struct writer {
+	int (*write)(void *, byte *, int);
+	void *write_arg;
+	int pending_padding;
+	int hash_size;
+	struct slice last_key;
+
+	uint64_t next;
+	uint64_t min_update_index, max_update_index;
+	struct write_options opts;
+
+	byte *block;
+	struct block_writer *block_writer;
+	struct block_writer block_writer_data;
+	struct index_record *index;
+	int index_len;
+	int index_cap;
+
+	/* tree for use with tsearch */
+	struct tree_node *obj_index_tree;
+
+	struct stats stats;
+};
+
+int writer_flush_block(struct writer *w);
+void writer_clear_index(struct writer *w);
+int writer_finish_public_section(struct writer *w);
+
+#endif
diff --git a/reftable/zlib-compat.c b/reftable/zlib-compat.c
new file mode 100644
index 0000000000..3e0b0f24f1
--- /dev/null
+++ b/reftable/zlib-compat.c
@@ -0,0 +1,92 @@
+/* taken from zlib's uncompr.c
+
+   commit cacf7f1d4e3d44d871b605da3b647f07d718623f
+   Author: Mark Adler <madler@alumni.caltech.edu>
+   Date:   Sun Jan 15 09:18:46 2017 -0800
+
+       zlib 1.2.11
+
+*/
+
+/*
+ * Copyright (C) 1995-2003, 2010, 2014, 2016 Jean-loup Gailly, Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+#include "system.h"
+
+/* clang-format off */
+
+/* ===========================================================================
+     Decompresses the source buffer into the destination buffer.  *sourceLen is
+   the byte length of the source buffer. Upon entry, *destLen is the total size
+   of the destination buffer, which must be large enough to hold the entire
+   uncompressed data. (The size of the uncompressed data must have been saved
+   previously by the compressor and transmitted to the decompressor by some
+   mechanism outside the scope of this compression library.) Upon exit,
+   *destLen is the size of the decompressed data and *sourceLen is the number
+   of source bytes consumed. Upon return, source + *sourceLen points to the
+   first unused input byte.
+
+     uncompress returns Z_OK if success, Z_MEM_ERROR if there was not enough
+   memory, Z_BUF_ERROR if there was not enough room in the output buffer, or
+   Z_DATA_ERROR if the input data was corrupted, including if the input data is
+   an incomplete zlib stream.
+*/
+int ZEXPORT uncompress_return_consumed (
+    Bytef *dest,
+    uLongf *destLen,
+    const Bytef *source,
+    uLong *sourceLen) {
+    z_stream stream;
+    int err;
+    const uInt max = (uInt)-1;
+    uLong len, left;
+    Byte buf[1];    /* for detection of incomplete stream when *destLen == 0 */
+
+    len = *sourceLen;
+    if (*destLen) {
+        left = *destLen;
+        *destLen = 0;
+    }
+    else {
+        left = 1;
+        dest = buf;
+    }
+
+    stream.next_in = (z_const Bytef *)source;
+    stream.avail_in = 0;
+    stream.zalloc = (alloc_func)0;
+    stream.zfree = (free_func)0;
+    stream.opaque = (voidpf)0;
+
+    err = inflateInit(&stream);
+    if (err != Z_OK) return err;
+
+    stream.next_out = dest;
+    stream.avail_out = 0;
+
+    do {
+        if (stream.avail_out == 0) {
+            stream.avail_out = left > (uLong)max ? max : (uInt)left;
+            left -= stream.avail_out;
+        }
+        if (stream.avail_in == 0) {
+            stream.avail_in = len > (uLong)max ? max : (uInt)len;
+            len -= stream.avail_in;
+        }
+        err = inflate(&stream, Z_NO_FLUSH);
+    } while (err == Z_OK);
+
+    *sourceLen -= len + stream.avail_in;
+    if (dest != buf)
+        *destLen = stream.total_out;
+    else if (stream.total_out && err == Z_BUF_ERROR)
+        left = 1;
+
+    inflateEnd(&stream);
+    return err == Z_STREAM_END ? Z_OK :
+           err == Z_NEED_DICT ? Z_DATA_ERROR  :
+           err == Z_BUF_ERROR && left + stream.avail_out ? Z_DATA_ERROR :
+           err;
+}
-- 
gitgitgadget


^ permalink raw reply related	[relevance 1%]

* [PATCH v6 2/6] t1300: fix over-indented HERE-DOCs
  @ 2020-01-29  3:34  6%       ` Matthew Rogers via GitGitGadget
    1 sibling, 0 replies; 200+ results
From: Matthew Rogers via GitGitGadget @ 2020-01-29  3:34 UTC (permalink / raw)
  To: git; +Cc: Matthew Rogers, Matthew Rogers

From: Matthew Rogers <mattr94@gmail.com>

Prepare for the following patches by removing extraneous indents from
HERE-DOCs used in config tests.

Signed-off-by: Matthew Rogers <mattr94@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 t/t1300-config.sh | 168 +++++++++++++++++++++++-----------------------
 1 file changed, 84 insertions(+), 84 deletions(-)

diff --git a/t/t1300-config.sh b/t/t1300-config.sh
index 983a0a1583..e8b4575758 100755
--- a/t/t1300-config.sh
+++ b/t/t1300-config.sh
@@ -1191,47 +1191,47 @@ test_expect_success 'old-fashioned settings are case insensitive' '
 	test_when_finished "rm -f testConfig testConfig_expect testConfig_actual" &&
 
 	cat >testConfig_actual <<-EOF &&
-		[V.A]
-		r = value1
+	[V.A]
+	r = value1
 	EOF
 	q_to_tab >testConfig_expect <<-EOF &&
-		[V.A]
-		Qr = value2
+	[V.A]
+	Qr = value2
 	EOF
 	git config -f testConfig_actual "v.a.r" value2 &&
 	test_cmp testConfig_expect testConfig_actual &&
 
 	cat >testConfig_actual <<-EOF &&
-		[V.A]
-		r = value1
+	[V.A]
+	r = value1
 	EOF
 	q_to_tab >testConfig_expect <<-EOF &&
-		[V.A]
-		QR = value2
+	[V.A]
+	QR = value2
 	EOF
 	git config -f testConfig_actual "V.a.R" value2 &&
 	test_cmp testConfig_expect testConfig_actual &&
 
 	cat >testConfig_actual <<-EOF &&
-		[V.A]
-		r = value1
+	[V.A]
+	r = value1
 	EOF
 	q_to_tab >testConfig_expect <<-EOF &&
-		[V.A]
-		r = value1
-		Qr = value2
+	[V.A]
+	r = value1
+	Qr = value2
 	EOF
 	git config -f testConfig_actual "V.A.r" value2 &&
 	test_cmp testConfig_expect testConfig_actual &&
 
 	cat >testConfig_actual <<-EOF &&
-		[V.A]
-		r = value1
+	[V.A]
+	r = value1
 	EOF
 	q_to_tab >testConfig_expect <<-EOF &&
-		[V.A]
-		r = value1
-		Qr = value2
+	[V.A]
+	r = value1
+	Qr = value2
 	EOF
 	git config -f testConfig_actual "v.A.r" value2 &&
 	test_cmp testConfig_expect testConfig_actual
@@ -1241,26 +1241,26 @@ test_expect_success 'setting different case sensitive subsections ' '
 	test_when_finished "rm -f testConfig testConfig_expect testConfig_actual" &&
 
 	cat >testConfig_actual <<-EOF &&
-		[V "A"]
-		R = v1
-		[K "E"]
-		Y = v1
-		[a "b"]
-		c = v1
-		[d "e"]
-		f = v1
+	[V "A"]
+	R = v1
+	[K "E"]
+	Y = v1
+	[a "b"]
+	c = v1
+	[d "e"]
+	f = v1
 	EOF
 	q_to_tab >testConfig_expect <<-EOF &&
-		[V "A"]
-		Qr = v2
-		[K "E"]
-		Qy = v2
-		[a "b"]
-		Qc = v2
-		[d "e"]
-		f = v1
-		[d "E"]
-		Qf = v2
+	[V "A"]
+	Qr = v2
+	[K "E"]
+	Qy = v2
+	[a "b"]
+	Qc = v2
+	[d "e"]
+	f = v1
+	[d "E"]
+	Qf = v2
 	EOF
 	# exact match
 	git config -f testConfig_actual a.b.c v2 &&
@@ -1622,40 +1622,40 @@ test_expect_success 'set up --show-origin tests' '
 	INCLUDE_DIR="$HOME/include" &&
 	mkdir -p "$INCLUDE_DIR" &&
 	cat >"$INCLUDE_DIR"/absolute.include <<-\EOF &&
-		[user]
-			absolute = include
+	[user]
+		absolute = include
 	EOF
 	cat >"$INCLUDE_DIR"/relative.include <<-\EOF &&
-		[user]
-			relative = include
+	[user]
+		relative = include
 	EOF
 	cat >"$HOME"/.gitconfig <<-EOF &&
-		[user]
-			global = true
-			override = global
-		[include]
-			path = "$INCLUDE_DIR/absolute.include"
+	[user]
+		global = true
+		override = global
+	[include]
+		path = "$INCLUDE_DIR/absolute.include"
 	EOF
 	cat >.git/config <<-\EOF
-		[user]
-			local = true
-			override = local
-		[include]
-			path = ../include/relative.include
+	[user]
+		local = true
+		override = local
+	[include]
+		path = ../include/relative.include
 	EOF
 '
 
 test_expect_success '--show-origin with --list' '
 	cat >expect <<-EOF &&
-		file:$HOME/.gitconfig	user.global=true
-		file:$HOME/.gitconfig	user.override=global
-		file:$HOME/.gitconfig	include.path=$INCLUDE_DIR/absolute.include
-		file:$INCLUDE_DIR/absolute.include	user.absolute=include
-		file:.git/config	user.local=true
-		file:.git/config	user.override=local
-		file:.git/config	include.path=../include/relative.include
-		file:.git/../include/relative.include	user.relative=include
-		command line:	user.cmdline=true
+	file:$HOME/.gitconfig	user.global=true
+	file:$HOME/.gitconfig	user.override=global
+	file:$HOME/.gitconfig	include.path=$INCLUDE_DIR/absolute.include
+	file:$INCLUDE_DIR/absolute.include	user.absolute=include
+	file:.git/config	user.local=true
+	file:.git/config	user.override=local
+	file:.git/config	include.path=../include/relative.include
+	file:.git/../include/relative.include	user.relative=include
+	command line:	user.cmdline=true
 	EOF
 	git -c user.cmdline=true config --list --show-origin >output &&
 	test_cmp expect output
@@ -1663,16 +1663,16 @@ test_expect_success '--show-origin with --list' '
 
 test_expect_success '--show-origin with --list --null' '
 	cat >expect <<-EOF &&
-		file:$HOME/.gitconfigQuser.global
-		trueQfile:$HOME/.gitconfigQuser.override
-		globalQfile:$HOME/.gitconfigQinclude.path
-		$INCLUDE_DIR/absolute.includeQfile:$INCLUDE_DIR/absolute.includeQuser.absolute
-		includeQfile:.git/configQuser.local
-		trueQfile:.git/configQuser.override
-		localQfile:.git/configQinclude.path
-		../include/relative.includeQfile:.git/../include/relative.includeQuser.relative
-		includeQcommand line:Quser.cmdline
-		trueQ
+	file:$HOME/.gitconfigQuser.global
+	trueQfile:$HOME/.gitconfigQuser.override
+	globalQfile:$HOME/.gitconfigQinclude.path
+	$INCLUDE_DIR/absolute.includeQfile:$INCLUDE_DIR/absolute.includeQuser.absolute
+	includeQfile:.git/configQuser.local
+	trueQfile:.git/configQuser.override
+	localQfile:.git/configQinclude.path
+	../include/relative.includeQfile:.git/../include/relative.includeQuser.relative
+	includeQcommand line:Quser.cmdline
+	trueQ
 	EOF
 	git -c user.cmdline=true config --null --list --show-origin >output.raw &&
 	nul_to_q <output.raw >output &&
@@ -1684,9 +1684,9 @@ test_expect_success '--show-origin with --list --null' '
 
 test_expect_success '--show-origin with single file' '
 	cat >expect <<-\EOF &&
-		file:.git/config	user.local=true
-		file:.git/config	user.override=local
-		file:.git/config	include.path=../include/relative.include
+	file:.git/config	user.local=true
+	file:.git/config	user.override=local
+	file:.git/config	include.path=../include/relative.include
 	EOF
 	git config --local --list --show-origin >output &&
 	test_cmp expect output
@@ -1694,8 +1694,8 @@ test_expect_success '--show-origin with single file' '
 
 test_expect_success '--show-origin with --get-regexp' '
 	cat >expect <<-EOF &&
-		file:$HOME/.gitconfig	user.global true
-		file:.git/config	user.local true
+	file:$HOME/.gitconfig	user.global true
+	file:.git/config	user.local true
 	EOF
 	git config --show-origin --get-regexp "user\.[g|l].*" >output &&
 	test_cmp expect output
@@ -1703,7 +1703,7 @@ test_expect_success '--show-origin with --get-regexp' '
 
 test_expect_success '--show-origin getting a single key' '
 	cat >expect <<-\EOF &&
-		file:.git/config	local
+	file:.git/config	local
 	EOF
 	git config --show-origin user.override >output &&
 	test_cmp expect output
@@ -1712,14 +1712,14 @@ test_expect_success '--show-origin getting a single key' '
 test_expect_success 'set up custom config file' '
 	CUSTOM_CONFIG_FILE="file\" (dq) and spaces.conf" &&
 	cat >"$CUSTOM_CONFIG_FILE" <<-\EOF
-		[user]
-			custom = true
+	[user]
+		custom = true
 	EOF
 '
 
 test_expect_success !MINGW '--show-origin escape special file name characters' '
 	cat >expect <<-\EOF &&
-		file:"file\" (dq) and spaces.conf"	user.custom=true
+	file:"file\" (dq) and spaces.conf"	user.custom=true
 	EOF
 	git config --file "$CUSTOM_CONFIG_FILE" --show-origin --list >output &&
 	test_cmp expect output
@@ -1727,7 +1727,7 @@ test_expect_success !MINGW '--show-origin escape special file name characters' '
 
 test_expect_success '--show-origin stdin' '
 	cat >expect <<-\EOF &&
-		standard input:	user.custom=true
+	standard input:	user.custom=true
 	EOF
 	git config --file - --show-origin --list <"$CUSTOM_CONFIG_FILE" >output &&
 	test_cmp expect output
@@ -1735,11 +1735,11 @@ test_expect_success '--show-origin stdin' '
 
 test_expect_success '--show-origin stdin with file include' '
 	cat >"$INCLUDE_DIR"/stdin.include <<-EOF &&
-		[user]
-			stdin = include
+	[user]
+		stdin = include
 	EOF
 	cat >expect <<-EOF &&
-		file:$INCLUDE_DIR/stdin.include	include
+	file:$INCLUDE_DIR/stdin.include	include
 	EOF
 	echo "[include]path=\"$INCLUDE_DIR\"/stdin.include" |
 	git config --show-origin --includes --file - user.stdin >output &&
@@ -1750,7 +1750,7 @@ test_expect_success '--show-origin stdin with file include' '
 test_expect_success !MINGW '--show-origin blob' '
 	blob=$(git hash-object -w "$CUSTOM_CONFIG_FILE") &&
 	cat >expect <<-EOF &&
-		blob:$blob	user.custom=true
+	blob:$blob	user.custom=true
 	EOF
 	git config --blob=$blob --show-origin --list >output &&
 	test_cmp expect output
@@ -1758,7 +1758,7 @@ test_expect_success !MINGW '--show-origin blob' '
 
 test_expect_success !MINGW '--show-origin blob ref' '
 	cat >expect <<-\EOF &&
-		blob:"master:file\" (dq) and spaces.conf"	user.custom=true
+	blob:"master:file\" (dq) and spaces.conf"	user.custom=true
 	EOF
 	git add "$CUSTOM_CONFIG_FILE" &&
 	git commit -m "new config file" &&
-- 
gitgitgadget


^ permalink raw reply related	[relevance 6%]

* [PATCH v2 4/5] Add reftable library
  2020-01-27 14:22  1% ` [PATCH v2 " Han-Wen Nienhuys via GitGitGadget
@ 2020-01-27 14:22  1%   ` Han-Wen Nienhuys via GitGitGadget
    1 sibling, 0 replies; 200+ results
From: Han-Wen Nienhuys via GitGitGadget @ 2020-01-27 14:22 UTC (permalink / raw)
  To: git; +Cc: Han-Wen Nienhuys, Han-Wen Nienhuys

From: Han-Wen Nienhuys <hanwen@google.com>

Reftable is a new format for storing the ref database. It provides the
following benefits:

 * Simple and fast atomic ref transactions, including multiple refs and reflogs.
 * Compact storage of ref data.
 * Fast look ups of ref data.
 * Case-sensitive ref names on Windows/OSX, regardless of file system
 * Eliminates file/directory conflicts in ref names

Further context and motivation can be found in background reading:

* Spec: https://github.com/eclipse/jgit/blob/master/Documentation/technical/reftable.md

* Original discussion on JGit-dev:  https://www.eclipse.org/lists/jgit-dev/msg03389.html

* First design discussion on git@vger: https://public-inbox.org/git/CAJo=hJtTp2eA3z9wW9cHo-nA7kK40vVThqh6inXpbCcqfdMP9g@mail.gmail.com/

* Last design discussion on git@vger: https://public-inbox.org/git/CAJo=hJsZcAM9sipdVr7TMD-FD2V2W6_pvMQ791EGCDsDkQ033w@mail.gmail.com/

* First attempt at implementation: https://public-inbox.org/git/CAP8UFD0PPZSjBnxCA7ez91vBuatcHKQ+JUWvTD1iHcXzPBjPBg@mail.gmail.com/

* libgit2 support issue: https://github.com/libgit2/libgit2/issues

* GitLab support issue: https://gitlab.com/gitlab-org/git/issues/6

* go-git support issue: https://github.com/src-d/go-git/issues/1059

Signed-off-by: Han-Wen Nienhuys <hanwen@google.com>
Change-Id: Id396ff42be8b42b9e11f194a32e2f95b8250c109
---
 reftable/LICENSE          |   31 ++
 reftable/README.md        |   17 +
 reftable/VERSION          |    5 +
 reftable/basics.c         |  196 +++++++
 reftable/basics.h         |   38 ++
 reftable/block.c          |  401 ++++++++++++++
 reftable/block.h          |   71 +++
 reftable/block_test.c     |  151 +++++
 reftable/blocksource.h    |   20 +
 reftable/bytes.c          |    0
 reftable/config.h         |    1 +
 reftable/constants.h      |   27 +
 reftable/dump.c           |   97 ++++
 reftable/file.c           |   97 ++++
 reftable/iter.c           |  230 ++++++++
 reftable/iter.h           |   56 ++
 reftable/merged.c         |  288 ++++++++++
 reftable/merged.h         |   34 ++
 reftable/merged_test.c    |  258 +++++++++
 reftable/pq.c             |  124 +++++
 reftable/pq.h             |   34 ++
 reftable/reader.c         |  710 ++++++++++++++++++++++++
 reftable/reader.h         |   52 ++
 reftable/record.c         | 1110 +++++++++++++++++++++++++++++++++++++
 reftable/record.h         |   79 +++
 reftable/record_test.c    |  332 +++++++++++
 reftable/reftable.h       |  399 +++++++++++++
 reftable/reftable_test.c  |  481 ++++++++++++++++
 reftable/slice.c          |  199 +++++++
 reftable/slice.h          |   39 ++
 reftable/slice_test.c     |   38 ++
 reftable/stack.c          |  985 ++++++++++++++++++++++++++++++++
 reftable/stack.h          |   40 ++
 reftable/stack_test.c     |  281 ++++++++++
 reftable/system.h         |   36 ++
 reftable/test_framework.c |   67 +++
 reftable/test_framework.h |   64 +++
 reftable/tree.c           |   66 +++
 reftable/tree.h           |   24 +
 reftable/tree_test.c      |   61 ++
 reftable/writer.c         |  624 +++++++++++++++++++++
 reftable/writer.h         |   46 ++
 42 files changed, 7909 insertions(+)
 create mode 100644 reftable/LICENSE
 create mode 100644 reftable/README.md
 create mode 100644 reftable/VERSION
 create mode 100644 reftable/basics.c
 create mode 100644 reftable/basics.h
 create mode 100644 reftable/block.c
 create mode 100644 reftable/block.h
 create mode 100644 reftable/block_test.c
 create mode 100644 reftable/blocksource.h
 create mode 100644 reftable/bytes.c
 create mode 100644 reftable/config.h
 create mode 100644 reftable/constants.h
 create mode 100644 reftable/dump.c
 create mode 100644 reftable/file.c
 create mode 100644 reftable/iter.c
 create mode 100644 reftable/iter.h
 create mode 100644 reftable/merged.c
 create mode 100644 reftable/merged.h
 create mode 100644 reftable/merged_test.c
 create mode 100644 reftable/pq.c
 create mode 100644 reftable/pq.h
 create mode 100644 reftable/reader.c
 create mode 100644 reftable/reader.h
 create mode 100644 reftable/record.c
 create mode 100644 reftable/record.h
 create mode 100644 reftable/record_test.c
 create mode 100644 reftable/reftable.h
 create mode 100644 reftable/reftable_test.c
 create mode 100644 reftable/slice.c
 create mode 100644 reftable/slice.h
 create mode 100644 reftable/slice_test.c
 create mode 100644 reftable/stack.c
 create mode 100644 reftable/stack.h
 create mode 100644 reftable/stack_test.c
 create mode 100644 reftable/system.h
 create mode 100644 reftable/test_framework.c
 create mode 100644 reftable/test_framework.h
 create mode 100644 reftable/tree.c
 create mode 100644 reftable/tree.h
 create mode 100644 reftable/tree_test.c
 create mode 100644 reftable/writer.c
 create mode 100644 reftable/writer.h

diff --git a/reftable/LICENSE b/reftable/LICENSE
new file mode 100644
index 0000000000..402e0f9356
--- /dev/null
+++ b/reftable/LICENSE
@@ -0,0 +1,31 @@
+BSD License
+
+Copyright (c) 2020, Google LLC
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+* Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+* Neither the name of Google LLC nor the names of its contributors may
+be used to endorse or promote products derived from this software
+without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/reftable/README.md b/reftable/README.md
new file mode 100644
index 0000000000..1c52d55833
--- /dev/null
+++ b/reftable/README.md
@@ -0,0 +1,17 @@
+
+The source code in this directory comes from https://github.com/google/reftable.
+
+The VERSION file keeps track of the current version of the reftable library.
+
+To update the library, do:
+
+   ((cd reftable-repo && git fetch origin && git checkout origin/master ) ||
+    git clone https://github.com/google/reftable reftable-repo) && \
+   cp reftable-repo/c/*.[ch] reftable/ && \
+   cp reftable-repo/LICENSE reftable/ &&
+   git --git-dir reftable-repo/.git show --no-patch origin/master \
+    > reftable/VERSION && \
+   echo '/* empty */' > reftable/config.h
+
+Bugfixes should be accompanied by a test and applied to upstream project at
+https://github.com/google/reftable.
diff --git a/reftable/VERSION b/reftable/VERSION
new file mode 100644
index 0000000000..627f9c49f6
--- /dev/null
+++ b/reftable/VERSION
@@ -0,0 +1,5 @@
+commit c616d53b88657c3a5fe4d2e7243a48effc34c626
+Author: Han-Wen Nienhuys <hanwen@google.com>
+Date:   Mon Jan 27 15:05:43 2020 +0100
+
+    C: ban // comments
diff --git a/reftable/basics.c b/reftable/basics.c
new file mode 100644
index 0000000000..791dcc867a
--- /dev/null
+++ b/reftable/basics.c
@@ -0,0 +1,196 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "basics.h"
+
+#include "system.h"
+
+void put_u24(byte *out, uint32_t i)
+{
+	out[0] = (byte)((i >> 16) & 0xff);
+	out[1] = (byte)((i >> 8) & 0xff);
+	out[2] = (byte)((i)&0xff);
+}
+
+uint32_t get_u24(byte *in)
+{
+	return (uint32_t)(in[0]) << 16 | (uint32_t)(in[1]) << 8 |
+	       (uint32_t)(in[2]);
+}
+
+void put_u32(byte *out, uint32_t i)
+{
+	out[0] = (byte)((i >> 24) & 0xff);
+	out[1] = (byte)((i >> 16) & 0xff);
+	out[2] = (byte)((i >> 8) & 0xff);
+	out[3] = (byte)((i)&0xff);
+}
+
+uint32_t get_u32(byte *in)
+{
+	return (uint32_t)(in[0]) << 24 | (uint32_t)(in[1]) << 16 |
+	       (uint32_t)(in[2]) << 8 | (uint32_t)(in[3]);
+}
+
+void put_u64(byte *out, uint64_t v)
+{
+	int i = 0;
+	for (i = sizeof(uint64_t); i--;) {
+		out[i] = (byte)(v & 0xff);
+		v >>= 8;
+	}
+}
+
+uint64_t get_u64(byte *out)
+{
+	uint64_t v = 0;
+	int i = 0;
+	for (i = 0; i < sizeof(uint64_t); i++) {
+		v = (v << 8) | (byte)(out[i] & 0xff);
+	}
+	return v;
+}
+
+void put_u16(byte *out, uint16_t i)
+{
+	out[0] = (byte)((i >> 8) & 0xff);
+	out[1] = (byte)((i)&0xff);
+}
+
+uint16_t get_u16(byte *in)
+{
+	return (uint32_t)(in[0]) << 8 | (uint32_t)(in[1]);
+}
+
+/*
+  find smallest index i in [0, sz) at which f(i) is true, assuming
+  that f is ascending. Return sz if f(i) is false for all indices.
+*/
+int binsearch(int sz, int (*f)(int k, void *args), void *args)
+{
+	int lo = 0;
+	int hi = sz;
+
+	/* invariant: (hi == sz) || f(hi) == true
+	   (lo == 0 && f(0) == true) || fi(lo) == false
+	 */
+	while (hi - lo > 1) {
+		int mid = lo + (hi - lo) / 2;
+
+		int val = f(mid, args);
+		if (val) {
+			hi = mid;
+		} else {
+			lo = mid;
+		}
+	}
+
+	if (lo == 0) {
+		if (f(0, args)) {
+			return 0;
+		} else {
+			return 1;
+		}
+	}
+
+	return hi;
+}
+
+void free_names(char **a)
+{
+	char **p = a;
+	if (p == NULL) {
+		return;
+	}
+	while (*p) {
+		free(*p);
+		p++;
+	}
+	free(a);
+}
+
+int names_length(char **names)
+{
+	int len = 0;
+	for (char **p = names; *p; p++) {
+		len++;
+	}
+	return len;
+}
+
+/* parse a newline separated list of names. Empty names are discarded. */
+void parse_names(char *buf, int size, char ***namesp)
+{
+	char **names = NULL;
+	int names_cap = 0;
+	int names_len = 0;
+
+	char *p = buf;
+	char *end = buf + size;
+	while (p < end) {
+		char *next = strchr(p, '\n');
+		if (next != NULL) {
+			*next = 0;
+		} else {
+			next = end;
+		}
+		if (p < next) {
+			if (names_len == names_cap) {
+				names_cap = 2 * names_cap + 1;
+				names = realloc(names,
+						names_cap * sizeof(char *));
+			}
+			names[names_len++] = strdup(p);
+		}
+		p = next + 1;
+	}
+
+	if (names_len == names_cap) {
+		names_cap = 2 * names_cap + 1;
+		names = realloc(names, names_cap * sizeof(char *));
+	}
+
+	names[names_len] = NULL;
+	*namesp = names;
+}
+
+int names_equal(char **a, char **b)
+{
+	while (*a && *b) {
+		if (strcmp(*a, *b)) {
+			return 0;
+		}
+
+		a++;
+		b++;
+	}
+
+	return *a == *b;
+}
+
+const char *error_str(int err)
+{
+	switch (err) {
+	case IO_ERROR:
+		return "I/O error";
+	case FORMAT_ERROR:
+		return "FORMAT_ERROR";
+	case NOT_EXIST_ERROR:
+		return "NOT_EXIST_ERROR";
+	case LOCK_ERROR:
+		return "LOCK_ERROR";
+	case API_ERROR:
+		return "API_ERROR";
+	case ZLIB_ERROR:
+		return "ZLIB_ERROR";
+	case -1:
+		return "general error";
+	default:
+		return "unknown error code";
+	}
+}
diff --git a/reftable/basics.h b/reftable/basics.h
new file mode 100644
index 0000000000..e2115bb879
--- /dev/null
+++ b/reftable/basics.h
@@ -0,0 +1,38 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef BASICS_H
+#define BASICS_H
+
+#include "system.h"
+
+#include "reftable.h"
+
+#define true 1
+#define false 0
+#define ARRAYSIZE(a) sizeof(a) / sizeof(a[0])
+
+void put_u24(byte *out, uint32_t i);
+uint32_t get_u24(byte *in);
+
+uint64_t get_u64(byte *in);
+void put_u64(byte *out, uint64_t i);
+
+void put_u32(byte *out, uint32_t i);
+uint32_t get_u32(byte *in);
+
+void put_u16(byte *out, uint16_t i);
+uint16_t get_u16(byte *in);
+int binsearch(int sz, int (*f)(int k, void *args), void *args);
+
+void free_names(char **a);
+void parse_names(char *buf, int size, char ***namesp);
+int names_equal(char **a, char **b);
+int names_length(char **names);
+
+#endif
diff --git a/reftable/block.c b/reftable/block.c
new file mode 100644
index 0000000000..9fe4f8a080
--- /dev/null
+++ b/reftable/block.c
@@ -0,0 +1,401 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "block.h"
+
+#include "system.h"
+
+#include "blocksource.h"
+#include "constants.h"
+#include "record.h"
+#include "reftable.h"
+#include "zlib.h"
+
+int block_writer_register_restart(struct block_writer *w, int n, bool restart,
+				  struct slice key);
+
+void block_writer_init(struct block_writer *bw, byte typ, byte *buf,
+		       uint32_t block_size, uint32_t header_off, int hash_size)
+{
+	bw->buf = buf;
+	bw->hash_size = hash_size;
+	bw->block_size = block_size;
+	bw->header_off = header_off;
+	bw->buf[header_off] = typ;
+	bw->next = header_off + 4;
+	bw->restart_interval = 16;
+	bw->entries = 0;
+}
+
+byte block_writer_type(struct block_writer *bw)
+{
+	return bw->buf[bw->header_off];
+}
+
+/* adds the record to the block. Returns -1 if it does not fit, 0 on
+   success */
+int block_writer_add(struct block_writer *w, struct record rec)
+{
+	struct slice empty = {};
+	struct slice last = w->entries % w->restart_interval == 0 ? empty :
+								    w->last_key;
+	struct slice out = {
+		.buf = w->buf + w->next,
+		.len = w->block_size - w->next,
+	};
+
+	struct slice start = out;
+
+	bool restart = false;
+	struct slice key = {};
+	int n = 0;
+
+	record_key(rec, &key);
+	n = encode_key(&restart, out, last, key, record_val_type(rec));
+	if (n < 0) {
+		goto err;
+	}
+	out.buf += n;
+	out.len -= n;
+
+	n = record_encode(rec, out, w->hash_size);
+	if (n < 0) {
+		goto err;
+	}
+
+	out.buf += n;
+	out.len -= n;
+
+	if (block_writer_register_restart(w, start.len - out.len, restart,
+					  key) < 0) {
+		goto err;
+	}
+
+	free(slice_yield(&key));
+	return 0;
+
+err:
+	free(slice_yield(&key));
+	return -1;
+}
+
+int block_writer_register_restart(struct block_writer *w, int n, bool restart,
+				  struct slice key)
+{
+	int rlen = w->restart_len;
+	if (rlen >= MAX_RESTARTS) {
+		restart = false;
+	}
+
+	if (restart) {
+		rlen++;
+	}
+	if (2 + 3 * rlen + n > w->block_size - w->next) {
+		return -1;
+	}
+	if (restart) {
+		if (w->restart_len == w->restart_cap) {
+			w->restart_cap = w->restart_cap * 2 + 1;
+			w->restarts = realloc(
+				w->restarts, sizeof(uint32_t) * w->restart_cap);
+		}
+
+		w->restarts[w->restart_len++] = w->next;
+	}
+
+	w->next += n;
+	slice_copy(&w->last_key, key);
+	w->entries++;
+	return 0;
+}
+
+int block_writer_finish(struct block_writer *w)
+{
+	int i = 0;
+	for (i = 0; i < w->restart_len; i++) {
+		put_u24(w->buf + w->next, w->restarts[i]);
+		w->next += 3;
+	}
+
+	put_u16(w->buf + w->next, w->restart_len);
+	w->next += 2;
+	put_u24(w->buf + 1 + w->header_off, w->next);
+
+	if (block_writer_type(w) == BLOCK_TYPE_LOG) {
+		int block_header_skip = 4 + w->header_off;
+		struct slice compressed = {};
+		uLongf dest_len = 0, src_len = 0;
+		slice_resize(&compressed, w->next - block_header_skip);
+
+		dest_len = compressed.len;
+		src_len = w->next - block_header_skip;
+
+		if (Z_OK != compress2(compressed.buf, &dest_len,
+				      w->buf + block_header_skip, src_len, 9)) {
+			free(slice_yield(&compressed));
+			return ZLIB_ERROR;
+		}
+		memcpy(w->buf + block_header_skip, compressed.buf, dest_len);
+		w->next = dest_len + block_header_skip;
+	}
+	return w->next;
+}
+
+byte block_reader_type(struct block_reader *r)
+{
+	return r->block.data[r->header_off];
+}
+
+int block_reader_init(struct block_reader *br, struct block *block,
+		      uint32_t header_off, uint32_t table_block_size,
+		      int hash_size)
+{
+	uint32_t full_block_size = table_block_size;
+	byte typ = block->data[header_off];
+	uint32_t sz = get_u24(block->data + header_off + 1);
+
+	if (!is_block_type(typ)) {
+		return FORMAT_ERROR;
+	}
+
+	if (typ == BLOCK_TYPE_LOG) {
+		struct slice uncompressed = {};
+		int block_header_skip = 4 + header_off;
+		uLongf dst_len = sz - block_header_skip;
+		uLongf src_len = block->len - block_header_skip;
+
+		slice_resize(&uncompressed, sz);
+		memcpy(uncompressed.buf, block->data, block_header_skip);
+
+		if (Z_OK !=
+		    uncompress2(uncompressed.buf + block_header_skip, &dst_len,
+				block->data + block_header_skip, &src_len)) {
+			free(slice_yield(&uncompressed));
+			return ZLIB_ERROR;
+		}
+
+		block_source_return_block(block->source, block);
+		block->data = uncompressed.buf;
+		block->len = dst_len; /* XXX: 4 bytes missing? */
+		block->source = malloc_block_source();
+		full_block_size = src_len + block_header_skip;
+	} else if (full_block_size == 0) {
+		full_block_size = sz;
+	} else if (sz < full_block_size && sz < block->len &&
+		   block->data[sz] != 0) {
+		/* If the block is smaller than the full block size, it is
+                   padded (data followed by '\0') or the next block is
+                   unaligned. */
+		full_block_size = sz;
+	}
+
+	{
+		uint16_t restart_count = get_u16(block->data + sz - 2);
+		uint32_t restart_start = sz - 2 - 3 * restart_count;
+
+		byte *restart_bytes = block->data + restart_start;
+
+		/* transfer ownership. */
+		br->block = *block;
+		block->data = NULL;
+		block->len = 0;
+
+		br->hash_size = hash_size;
+		br->block_len = restart_start;
+		br->full_block_size = full_block_size;
+		br->header_off = header_off;
+		br->restart_count = restart_count;
+		br->restart_bytes = restart_bytes;
+	}
+
+	return 0;
+}
+
+static uint32_t block_reader_restart_offset(struct block_reader *br, int i)
+{
+	return get_u24(br->restart_bytes + 3 * i);
+}
+
+void block_reader_start(struct block_reader *br, struct block_iter *it)
+{
+	it->br = br;
+	slice_resize(&it->last_key, 0);
+	it->next_off = br->header_off + 4;
+}
+
+struct restart_find_args {
+	struct slice key;
+	struct block_reader *r;
+	int error;
+};
+
+static int restart_key_less(int idx, void *args)
+{
+	struct restart_find_args *a = (struct restart_find_args *)args;
+	uint32_t off = block_reader_restart_offset(a->r, idx);
+	struct slice in = {
+		.buf = a->r->block.data + off,
+		.len = a->r->block_len - off,
+	};
+
+	/* the restart key is verbatim in the block, so this could avoid the
+	   alloc for decoding the key */
+	struct slice rkey = {};
+	struct slice last_key = {};
+	byte unused_extra;
+	int n = decode_key(&rkey, &unused_extra, last_key, in);
+	if (n < 0) {
+		a->error = 1;
+		return -1;
+	}
+
+	{
+		int result = slice_compare(a->key, rkey);
+		free(slice_yield(&rkey));
+		return result;
+	}
+}
+
+void block_iter_copy_from(struct block_iter *dest, struct block_iter *src)
+{
+	dest->br = src->br;
+	dest->next_off = src->next_off;
+	slice_copy(&dest->last_key, src->last_key);
+}
+
+/* return < 0 for error, 0 for OK, > 0 for EOF. */
+int block_iter_next(struct block_iter *it, struct record rec)
+{
+	if (it->next_off >= it->br->block_len) {
+		return 1;
+	}
+
+	{
+		struct slice in = {
+			.buf = it->br->block.data + it->next_off,
+			.len = it->br->block_len - it->next_off,
+		};
+		struct slice start = in;
+		struct slice key = {};
+		byte extra;
+		int n = decode_key(&key, &extra, it->last_key, in);
+		if (n < 0) {
+			return -1;
+		}
+
+		in.buf += n;
+		in.len -= n;
+		n = record_decode(rec, key, extra, in, it->br->hash_size);
+		if (n < 0) {
+			return -1;
+		}
+		in.buf += n;
+		in.len -= n;
+
+		slice_copy(&it->last_key, key);
+		it->next_off += start.len - in.len;
+		free(slice_yield(&key));
+		return 0;
+	}
+}
+
+int block_reader_first_key(struct block_reader *br, struct slice *key)
+{
+	struct slice empty = {};
+	int off = br->header_off + 4;
+	struct slice in = {
+		.buf = br->block.data + off,
+		.len = br->block_len - off,
+	};
+
+	byte extra = 0;
+	int n = decode_key(key, &extra, empty, in);
+	if (n < 0) {
+		return n;
+	}
+	return 0;
+}
+
+int block_iter_seek(struct block_iter *it, struct slice want)
+{
+	return block_reader_seek(it->br, it, want);
+}
+
+void block_iter_close(struct block_iter *it)
+{
+	free(slice_yield(&it->last_key));
+}
+
+int block_reader_seek(struct block_reader *br, struct block_iter *it,
+		      struct slice want)
+{
+	struct restart_find_args args = {
+		.key = want,
+		.r = br,
+	};
+
+	int i = binsearch(br->restart_count, &restart_key_less, &args);
+	if (args.error) {
+		return -1;
+	}
+
+	it->br = br;
+	if (i > 0) {
+		i--;
+		it->next_off = block_reader_restart_offset(br, i);
+	} else {
+		it->next_off = br->header_off + 4;
+	}
+
+	{
+		struct record rec = new_record(block_reader_type(br));
+		struct slice key = {};
+		int result = 0;
+		int err = 0;
+		struct block_iter next = {};
+		while (true) {
+			block_iter_copy_from(&next, it);
+
+			err = block_iter_next(&next, rec);
+			if (err < 0) {
+				result = -1;
+				goto exit;
+			}
+
+			record_key(rec, &key);
+			if (err > 0 || slice_compare(key, want) >= 0) {
+				result = 0;
+				goto exit;
+			}
+
+			block_iter_copy_from(it, &next);
+		}
+
+	exit:
+		free(slice_yield(&key));
+		free(slice_yield(&next.last_key));
+		record_clear(rec);
+		free(record_yield(&rec));
+
+		return result;
+	}
+}
+
+void block_writer_reset(struct block_writer *bw)
+{
+	bw->restart_len = 0;
+	bw->last_key.len = 0;
+}
+
+void block_writer_clear(struct block_writer *bw)
+{
+	free(bw->restarts);
+	bw->restarts = NULL;
+	free(slice_yield(&bw->last_key));
+	/* the block is not owned. */
+}
diff --git a/reftable/block.h b/reftable/block.h
new file mode 100644
index 0000000000..bb42588111
--- /dev/null
+++ b/reftable/block.h
@@ -0,0 +1,71 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef BLOCK_H
+#define BLOCK_H
+
+#include "basics.h"
+#include "record.h"
+#include "reftable.h"
+
+struct block_writer {
+	byte *buf;
+	uint32_t block_size;
+	uint32_t header_off;
+	int restart_interval;
+	int hash_size;
+
+	uint32_t next;
+	uint32_t *restarts;
+	uint32_t restart_len;
+	uint32_t restart_cap;
+	struct slice last_key;
+	int entries;
+};
+
+void block_writer_init(struct block_writer *bw, byte typ, byte *buf,
+		       uint32_t block_size, uint32_t header_off, int hash_size);
+byte block_writer_type(struct block_writer *bw);
+int block_writer_add(struct block_writer *w, struct record rec);
+int block_writer_finish(struct block_writer *w);
+void block_writer_reset(struct block_writer *bw);
+void block_writer_clear(struct block_writer *bw);
+
+struct block_reader {
+	uint32_t header_off;
+	struct block block;
+	int hash_size;
+
+	/* size of the data, excluding restart data. */
+	uint32_t block_len;
+	byte *restart_bytes;
+	uint32_t full_block_size;
+	uint16_t restart_count;
+};
+
+struct block_iter {
+	struct block_reader *br;
+	struct slice last_key;
+	uint32_t next_off;
+};
+
+int block_reader_init(struct block_reader *br, struct block *bl,
+		      uint32_t header_off, uint32_t table_block_size,
+		      int hash_size);
+void block_reader_start(struct block_reader *br, struct block_iter *it);
+int block_reader_seek(struct block_reader *br, struct block_iter *it,
+		      struct slice want);
+byte block_reader_type(struct block_reader *r);
+int block_reader_first_key(struct block_reader *br, struct slice *key);
+
+void block_iter_copy_from(struct block_iter *dest, struct block_iter *src);
+int block_iter_next(struct block_iter *it, struct record rec);
+int block_iter_seek(struct block_iter *it, struct slice want);
+void block_iter_close(struct block_iter *it);
+
+#endif
diff --git a/reftable/block_test.c b/reftable/block_test.c
new file mode 100644
index 0000000000..694333f80f
--- /dev/null
+++ b/reftable/block_test.c
@@ -0,0 +1,151 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "block.h"
+
+#include "system.h"
+
+#include "basics.h"
+#include "constants.h"
+#include "record.h"
+#include "reftable.h"
+#include "test_framework.h"
+
+struct binsearch_args {
+	int key;
+	int *arr;
+};
+
+static int binsearch_func(int i, void *void_args)
+{
+	struct binsearch_args *args = (struct binsearch_args *)void_args;
+
+	return args->key < args->arr[i];
+}
+
+void test_binsearch()
+{
+	int arr[] = { 2, 4, 6, 8, 10 };
+	int sz = ARRAYSIZE(arr);
+	struct binsearch_args args = {
+		.arr = arr,
+	};
+
+	int i = 0;
+	for (i = 1; i < 11; i++) {
+		args.key = i;
+		int res = binsearch(sz, &binsearch_func, &args);
+
+		if (res < sz) {
+			assert(args.key < arr[res]);
+			if (res > 0) {
+				assert(args.key >= arr[res - 1]);
+			}
+		} else {
+			assert(args.key == 10 || args.key == 11);
+		}
+	}
+}
+
+void test_block_read_write()
+{
+	const int header_off = 21; /* random */
+	const int N = 30;
+	char *names[N];
+	const int block_size = 1024;
+	struct block block = {};
+	block.data = calloc(block_size, 1);
+	block.len = block_size;
+
+	struct block_writer bw = {};
+	block_writer_init(&bw, BLOCK_TYPE_REF, block.data, block_size,
+			  header_off, SHA1_SIZE);
+	struct ref_record ref = {};
+	struct record rec = {};
+	record_from_ref(&rec, &ref);
+
+	int i = 0;
+	for (i = 0; i < N; i++) {
+		char name[100];
+		snprintf(name, sizeof(name), "branch%02d", i);
+
+		byte hash[SHA1_SIZE];
+		memset(hash, i, sizeof(hash));
+
+		ref.ref_name = name;
+		ref.value = hash;
+		names[i] = strdup(name);
+		int n = block_writer_add(&bw, rec);
+		ref.ref_name = NULL;
+		ref.value = NULL;
+		assert(n == 0);
+	}
+
+	int n = block_writer_finish(&bw);
+	assert(n > 0);
+
+	block_writer_clear(&bw);
+
+	struct block_reader br = {};
+	block_reader_init(&br, &block, header_off, block_size, SHA1_SIZE);
+
+	struct block_iter it = {};
+	block_reader_start(&br, &it);
+
+	int j = 0;
+	while (true) {
+		int r = block_iter_next(&it, rec);
+		assert(r >= 0);
+		if (r > 0) {
+			break;
+		}
+		assert_streq(names[j], ref.ref_name);
+		j++;
+	}
+
+	record_clear(rec);
+	block_iter_close(&it);
+
+	struct slice want = {};
+	for (i = 0; i < N; i++) {
+		slice_set_string(&want, names[i]);
+
+		struct block_iter it = {};
+		int n = block_reader_seek(&br, &it, want);
+		assert(n == 0);
+
+		n = block_iter_next(&it, rec);
+		assert(n == 0);
+
+		assert_streq(names[i], ref.ref_name);
+
+		want.len--;
+		n = block_reader_seek(&br, &it, want);
+		assert(n == 0);
+
+		n = block_iter_next(&it, rec);
+		assert(n == 0);
+		assert_streq(names[10 * (i / 10)], ref.ref_name);
+
+		block_iter_close(&it);
+	}
+
+	record_clear(rec);
+	free(block.data);
+	free(slice_yield(&want));
+	for (i = 0; i < N; i++) {
+		free(names[i]);
+	}
+}
+
+int main()
+{
+	add_test_case("binsearch", &test_binsearch);
+	add_test_case("block_read_write", &test_block_read_write);
+	test_main();
+}
diff --git a/reftable/blocksource.h b/reftable/blocksource.h
new file mode 100644
index 0000000000..f3ad3a4c22
--- /dev/null
+++ b/reftable/blocksource.h
@@ -0,0 +1,20 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef BLOCKSOURCE_H
+#define BLOCKSOURCE_H
+
+#include "reftable.h"
+
+uint64_t block_source_size(struct block_source source);
+int block_source_read_block(struct block_source source, struct block *dest,
+			    uint64_t off, uint32_t size);
+void block_source_return_block(struct block_source source, struct block *ret);
+void block_source_close(struct block_source source);
+
+#endif
diff --git a/reftable/bytes.c b/reftable/bytes.c
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/reftable/config.h b/reftable/config.h
new file mode 100644
index 0000000000..40a8c178f1
--- /dev/null
+++ b/reftable/config.h
@@ -0,0 +1 @@
+/* empty */
diff --git a/reftable/constants.h b/reftable/constants.h
new file mode 100644
index 0000000000..cd35704610
--- /dev/null
+++ b/reftable/constants.h
@@ -0,0 +1,27 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef CONSTANTS_H
+#define CONSTANTS_H
+
+#define SHA1_SIZE 20
+#define SHA256_SIZE 32
+#define VERSION 1
+#define HEADER_SIZE 24
+#define FOOTER_SIZE 68
+
+#define BLOCK_TYPE_LOG 'g'
+#define BLOCK_TYPE_INDEX 'i'
+#define BLOCK_TYPE_REF 'r'
+#define BLOCK_TYPE_OBJ 'o'
+#define BLOCK_TYPE_ANY 0
+
+#define MAX_RESTARTS ((1 << 16) - 1)
+#define DEFAULT_BLOCK_SIZE 4096
+
+#endif
diff --git a/reftable/dump.c b/reftable/dump.c
new file mode 100644
index 0000000000..acabe18fbe
--- /dev/null
+++ b/reftable/dump.c
@@ -0,0 +1,97 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "system.h"
+
+#include "reftable.h"
+
+static int dump_table(const char *tablename)
+{
+	struct block_source src = {};
+	int err = block_source_from_file(&src, tablename);
+	if (err < 0) {
+		return err;
+	}
+
+	struct reader *r = NULL;
+	err = new_reader(&r, src, tablename);
+	if (err < 0) {
+		return err;
+	}
+
+	{
+		struct iterator it = {};
+		err = reader_seek_ref(r, &it, "");
+		if (err < 0) {
+			return err;
+		}
+
+		struct ref_record ref = {};
+		while (1) {
+			err = iterator_next_ref(it, &ref);
+			if (err > 0) {
+				break;
+			}
+			if (err < 0) {
+				return err;
+			}
+			ref_record_print(&ref, 20);
+		}
+		iterator_destroy(&it);
+		ref_record_clear(&ref);
+	}
+
+	{
+		struct iterator it = {};
+		err = reader_seek_log(r, &it, "");
+		if (err < 0) {
+			return err;
+		}
+		struct log_record log = {};
+		while (1) {
+			err = iterator_next_log(it, &log);
+			if (err > 0) {
+				break;
+			}
+			if (err < 0) {
+				return err;
+			}
+			log_record_print(&log, 20);
+		}
+		iterator_destroy(&it);
+		log_record_clear(&log);
+	}
+	return 0;
+}
+
+int main(int argc, char *argv[])
+{
+	int opt;
+	const char *table = NULL;
+	while ((opt = getopt(argc, argv, "t:")) != -1) {
+		switch (opt) {
+		case 't':
+			table = strdup(optarg);
+			break;
+		case '?':
+			printf("usage: %s [-table tablefile]\n", argv[0]);
+			return 2;
+			break;
+		}
+	}
+
+	if (table != NULL) {
+		int err = dump_table(table);
+		if (err < 0) {
+			fprintf(stderr, "%s: %s: %s\n", argv[0], table,
+				error_str(err));
+			return 1;
+		}
+	}
+	return 0;
+}
diff --git a/reftable/file.c b/reftable/file.c
new file mode 100644
index 0000000000..b2ea90bf94
--- /dev/null
+++ b/reftable/file.c
@@ -0,0 +1,97 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "system.h"
+
+#include "block.h"
+#include "iter.h"
+#include "record.h"
+#include "reftable.h"
+#include "tree.h"
+
+struct file_block_source {
+	int fd;
+	uint64_t size;
+};
+
+static uint64_t file_size(void *b)
+{
+	return ((struct file_block_source *)b)->size;
+}
+
+static void file_return_block(void *b, struct block *dest)
+{
+	memset(dest->data, 0xff, dest->len);
+	free(dest->data);
+}
+
+static void file_close(void *b)
+{
+	int fd = ((struct file_block_source *)b)->fd;
+	if (fd > 0) {
+		close(fd);
+		((struct file_block_source *)b)->fd = 0;
+	}
+
+	free(b);
+}
+
+static int file_read_block(void *v, struct block *dest, uint64_t off,
+			   uint32_t size)
+{
+	struct file_block_source *b = (struct file_block_source *)v;
+	assert(off + size <= b->size);
+	dest->data = malloc(size);
+	if (pread(b->fd, dest->data, size, off) != size) {
+		return -1;
+	}
+	dest->len = size;
+	return size;
+}
+
+struct block_source_vtable file_vtable = {
+	.size = &file_size,
+	.read_block = &file_read_block,
+	.return_block = &file_return_block,
+	.close = &file_close,
+};
+
+int block_source_from_file(struct block_source *bs, const char *name)
+{
+	struct stat st = {};
+	int err = 0;
+	int fd = open(name, O_RDONLY);
+	if (fd < 0) {
+		if (errno == ENOENT) {
+			return NOT_EXIST_ERROR;
+		}
+		return -1;
+	}
+
+	err = fstat(fd, &st);
+	if (err < 0) {
+		return -1;
+	}
+
+	{
+		struct file_block_source *p =
+			calloc(sizeof(struct file_block_source), 1);
+		p->size = st.st_size;
+		p->fd = fd;
+
+		bs->ops = &file_vtable;
+		bs->arg = p;
+	}
+	return 0;
+}
+
+int fd_writer(void *arg, byte *data, int sz)
+{
+	int *fdp = (int *)arg;
+	return write(*fdp, data, sz);
+}
diff --git a/reftable/iter.c b/reftable/iter.c
new file mode 100644
index 0000000000..e306a5d465
--- /dev/null
+++ b/reftable/iter.c
@@ -0,0 +1,230 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "iter.h"
+
+#include "system.h"
+
+#include "block.h"
+#include "constants.h"
+#include "reader.h"
+#include "reftable.h"
+
+bool iterator_is_null(struct iterator it)
+{
+	return it.ops == NULL;
+}
+
+static int empty_iterator_next(void *arg, struct record rec)
+{
+	return 1;
+}
+
+static void empty_iterator_close(void *arg)
+{
+}
+
+struct iterator_vtable empty_vtable = {
+	.next = &empty_iterator_next,
+	.close = &empty_iterator_close,
+};
+
+void iterator_set_empty(struct iterator *it)
+{
+	it->iter_arg = NULL;
+	it->ops = &empty_vtable;
+}
+
+int iterator_next(struct iterator it, struct record rec)
+{
+	return it.ops->next(it.iter_arg, rec);
+}
+
+void iterator_destroy(struct iterator *it)
+{
+	if (it->ops == NULL) {
+		return;
+	}
+	it->ops->close(it->iter_arg);
+	it->ops = NULL;
+	free(it->iter_arg);
+	it->iter_arg = NULL;
+}
+
+int iterator_next_ref(struct iterator it, struct ref_record *ref)
+{
+	struct record rec = {};
+	record_from_ref(&rec, ref);
+	return iterator_next(it, rec);
+}
+
+int iterator_next_log(struct iterator it, struct log_record *log)
+{
+	struct record rec = {};
+	record_from_log(&rec, log);
+	return iterator_next(it, rec);
+}
+
+static void filtering_ref_iterator_close(void *iter_arg)
+{
+	struct filtering_ref_iterator *fri =
+		(struct filtering_ref_iterator *)iter_arg;
+	free(slice_yield(&fri->oid));
+	iterator_destroy(&fri->it);
+}
+
+static int filtering_ref_iterator_next(void *iter_arg, struct record rec)
+{
+	struct filtering_ref_iterator *fri =
+		(struct filtering_ref_iterator *)iter_arg;
+	struct ref_record *ref = (struct ref_record *)rec.data;
+
+	while (true) {
+		int err = iterator_next_ref(fri->it, ref);
+		if (err != 0) {
+			return err;
+		}
+
+		if (fri->double_check) {
+			struct iterator it = {};
+
+			int err = reader_seek_ref(fri->r, &it, ref->ref_name);
+			if (err == 0) {
+				err = iterator_next_ref(it, ref);
+			}
+
+			iterator_destroy(&it);
+
+			if (err < 0) {
+				return err;
+			}
+
+			if (err > 0) {
+				continue;
+			}
+		}
+
+		if ((ref->target_value != NULL &&
+		     !memcmp(fri->oid.buf, ref->target_value, fri->oid.len)) ||
+		    (ref->value != NULL &&
+		     !memcmp(fri->oid.buf, ref->value, fri->oid.len))) {
+			return 0;
+		}
+	}
+}
+
+struct iterator_vtable filtering_ref_iterator_vtable = {
+	.next = &filtering_ref_iterator_next,
+	.close = &filtering_ref_iterator_close,
+};
+
+void iterator_from_filtering_ref_iterator(struct iterator *it,
+					  struct filtering_ref_iterator *fri)
+{
+	it->iter_arg = fri;
+	it->ops = &filtering_ref_iterator_vtable;
+}
+
+static void indexed_table_ref_iter_close(void *p)
+{
+	struct indexed_table_ref_iter *it = (struct indexed_table_ref_iter *)p;
+	block_iter_close(&it->cur);
+	reader_return_block(it->r, &it->block_reader.block);
+	free(slice_yield(&it->oid));
+}
+
+static int indexed_table_ref_iter_next_block(struct indexed_table_ref_iter *it)
+{
+	if (it->offset_idx == it->offset_len) {
+		it->finished = true;
+		return 1;
+	}
+
+	reader_return_block(it->r, &it->block_reader.block);
+
+	{
+		uint64_t off = it->offsets[it->offset_idx++];
+		int err = reader_init_block_reader(it->r, &it->block_reader,
+						   off, BLOCK_TYPE_REF);
+		if (err < 0) {
+			return err;
+		}
+		if (err > 0) {
+			/* indexed block does not exist. */
+			return FORMAT_ERROR;
+		}
+	}
+	block_reader_start(&it->block_reader, &it->cur);
+	return 0;
+}
+
+static int indexed_table_ref_iter_next(void *p, struct record rec)
+{
+	struct indexed_table_ref_iter *it = (struct indexed_table_ref_iter *)p;
+	struct ref_record *ref = (struct ref_record *)rec.data;
+
+	while (true) {
+		int err = block_iter_next(&it->cur, rec);
+		if (err < 0) {
+			return err;
+		}
+
+		if (err > 0) {
+			err = indexed_table_ref_iter_next_block(it);
+			if (err < 0) {
+				return err;
+			}
+
+			if (it->finished) {
+				return 1;
+			}
+			continue;
+		}
+
+		if (!memcmp(it->oid.buf, ref->target_value, it->oid.len) ||
+		    !memcmp(it->oid.buf, ref->value, it->oid.len)) {
+			return 0;
+		}
+	}
+}
+
+int new_indexed_table_ref_iter(struct indexed_table_ref_iter **dest,
+			       struct reader *r, byte *oid, int oid_len,
+			       uint64_t *offsets, int offset_len)
+{
+	struct indexed_table_ref_iter *itr =
+		calloc(sizeof(struct indexed_table_ref_iter), 1);
+	int err = 0;
+
+	itr->r = r;
+	slice_resize(&itr->oid, oid_len);
+	memcpy(itr->oid.buf, oid, oid_len);
+
+	itr->offsets = offsets;
+	itr->offset_len = offset_len;
+
+	err = indexed_table_ref_iter_next_block(itr);
+	if (err < 0) {
+		free(itr);
+	} else {
+		*dest = itr;
+	}
+	return err;
+}
+
+struct iterator_vtable indexed_table_ref_iter_vtable = {
+	.next = &indexed_table_ref_iter_next,
+	.close = &indexed_table_ref_iter_close,
+};
+
+void iterator_from_indexed_table_ref_iter(struct iterator *it,
+					  struct indexed_table_ref_iter *itr)
+{
+	it->iter_arg = itr;
+	it->ops = &indexed_table_ref_iter_vtable;
+}
diff --git a/reftable/iter.h b/reftable/iter.h
new file mode 100644
index 0000000000..f497f2a27e
--- /dev/null
+++ b/reftable/iter.h
@@ -0,0 +1,56 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef ITER_H
+#define ITER_H
+
+#include "block.h"
+#include "record.h"
+#include "slice.h"
+
+struct iterator_vtable {
+	int (*next)(void *iter_arg, struct record rec);
+	void (*close)(void *iter_arg);
+};
+
+void iterator_set_empty(struct iterator *it);
+int iterator_next(struct iterator it, struct record rec);
+bool iterator_is_null(struct iterator it);
+
+struct filtering_ref_iterator {
+	struct reader *r;
+	struct slice oid;
+	bool double_check;
+	struct iterator it;
+};
+
+void iterator_from_filtering_ref_iterator(struct iterator *,
+					  struct filtering_ref_iterator *);
+
+struct indexed_table_ref_iter {
+	struct reader *r;
+	struct slice oid;
+
+	/* mutable */
+	uint64_t *offsets;
+
+	/* Points to the next offset to read. */
+	int offset_idx;
+	int offset_len;
+	struct block_reader block_reader;
+	struct block_iter cur;
+	bool finished;
+};
+
+void iterator_from_indexed_table_ref_iter(struct iterator *it,
+					  struct indexed_table_ref_iter *itr);
+int new_indexed_table_ref_iter(struct indexed_table_ref_iter **dest,
+			       struct reader *r, byte *oid, int oid_len,
+			       uint64_t *offsets, int offset_len);
+
+#endif
diff --git a/reftable/merged.c b/reftable/merged.c
new file mode 100644
index 0000000000..7e32bb5d8a
--- /dev/null
+++ b/reftable/merged.c
@@ -0,0 +1,288 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "merged.h"
+
+#include "system.h"
+
+#include "constants.h"
+#include "iter.h"
+#include "pq.h"
+#include "reader.h"
+
+static int merged_iter_init(struct merged_iter *mi)
+{
+	int i = 0;
+	for (i = 0; i < mi->stack_len; i++) {
+		struct record rec = new_record(mi->typ);
+		int err = iterator_next(mi->stack[i], rec);
+		if (err < 0) {
+			return err;
+		}
+
+		if (err > 0) {
+			iterator_destroy(&mi->stack[i]);
+			record_clear(rec);
+			free(record_yield(&rec));
+		} else {
+			struct pq_entry e = {
+				.rec = rec,
+				.index = i,
+			};
+			merged_iter_pqueue_add(&mi->pq, e);
+		}
+	}
+
+	return 0;
+}
+
+static void merged_iter_close(void *p)
+{
+	struct merged_iter *mi = (struct merged_iter *)p;
+	int i = 0;
+	merged_iter_pqueue_clear(&mi->pq);
+	for (i = 0; i < mi->stack_len; i++) {
+		iterator_destroy(&mi->stack[i]);
+	}
+	free(mi->stack);
+}
+
+static int merged_iter_advance_subiter(struct merged_iter *mi, int idx)
+{
+	if (iterator_is_null(mi->stack[idx])) {
+		return 0;
+	}
+
+	{
+		struct record rec = new_record(mi->typ);
+		struct pq_entry e = {
+			.rec = rec,
+			.index = idx,
+		};
+		int err = iterator_next(mi->stack[idx], rec);
+		if (err < 0) {
+			return err;
+		}
+
+		if (err > 0) {
+			iterator_destroy(&mi->stack[idx]);
+			record_clear(rec);
+			free(record_yield(&rec));
+			return 0;
+		}
+
+		merged_iter_pqueue_add(&mi->pq, e);
+	}
+	return 0;
+}
+
+static int merged_iter_next(struct merged_iter *mi, struct record rec)
+{
+	struct slice entry_key = {};
+	struct pq_entry entry = merged_iter_pqueue_remove(&mi->pq);
+	int err = merged_iter_advance_subiter(mi, entry.index);
+	if (err < 0) {
+		return err;
+	}
+
+	record_key(entry.rec, &entry_key);
+	while (!merged_iter_pqueue_is_empty(mi->pq)) {
+		struct pq_entry top = merged_iter_pqueue_top(mi->pq);
+		struct slice k = {};
+		int err = 0, cmp = 0;
+
+		record_key(top.rec, &k);
+
+		cmp = slice_compare(k, entry_key);
+		free(slice_yield(&k));
+
+		if (cmp > 0) {
+			break;
+		}
+
+		merged_iter_pqueue_remove(&mi->pq);
+		err = merged_iter_advance_subiter(mi, top.index);
+		if (err < 0) {
+			return err;
+		}
+		record_clear(top.rec);
+		free(record_yield(&top.rec));
+	}
+
+	record_copy_from(rec, entry.rec, mi->hash_size);
+	record_clear(entry.rec);
+	free(record_yield(&entry.rec));
+	free(slice_yield(&entry_key));
+	return 0;
+}
+
+static int merged_iter_next_void(void *p, struct record rec)
+{
+	struct merged_iter *mi = (struct merged_iter *)p;
+	if (merged_iter_pqueue_is_empty(mi->pq)) {
+		return 1;
+	}
+
+	return merged_iter_next(mi, rec);
+}
+
+struct iterator_vtable merged_iter_vtable = {
+	.next = &merged_iter_next_void,
+	.close = &merged_iter_close,
+};
+
+static void iterator_from_merged_iter(struct iterator *it,
+				      struct merged_iter *mi)
+{
+	it->iter_arg = mi;
+	it->ops = &merged_iter_vtable;
+}
+
+int new_merged_table(struct merged_table **dest, struct reader **stack, int n)
+{
+	uint64_t last_max = 0;
+	uint64_t first_min = 0;
+	int i = 0;
+	for (i = 0; i < n; i++) {
+		struct reader *r = stack[i];
+		if (i > 0 && last_max >= reader_min_update_index(r)) {
+			return FORMAT_ERROR;
+		}
+		if (i == 0) {
+			first_min = reader_min_update_index(r);
+		}
+
+		last_max = reader_max_update_index(r);
+	}
+
+	{
+		struct merged_table m = {
+			.stack = stack,
+			.stack_len = n,
+			.min = first_min,
+			.max = last_max,
+			.hash_size = SHA1_SIZE,
+		};
+
+		*dest = calloc(sizeof(struct merged_table), 1);
+		**dest = m;
+	}
+	return 0;
+}
+
+void merged_table_close(struct merged_table *mt)
+{
+	int i = 0;
+	for (i = 0; i < mt->stack_len; i++) {
+		reader_free(mt->stack[i]);
+	}
+	free(mt->stack);
+	mt->stack = NULL;
+	mt->stack_len = 0;
+}
+
+/* clears the list of subtable, without affecting the readers themselves. */
+void merged_table_clear(struct merged_table *mt)
+{
+	free(mt->stack);
+	mt->stack = NULL;
+	mt->stack_len = 0;
+}
+
+void merged_table_free(struct merged_table *mt)
+{
+	if (mt == NULL) {
+		return;
+	}
+	merged_table_clear(mt);
+	free(mt);
+}
+
+uint64_t merged_max_update_index(struct merged_table *mt)
+{
+	return mt->max;
+}
+
+uint64_t merged_min_update_index(struct merged_table *mt)
+{
+	return mt->min;
+}
+
+static int merged_table_seek_record(struct merged_table *mt,
+				    struct iterator *it, struct record rec)
+{
+	struct iterator *iters = calloc(sizeof(struct iterator), mt->stack_len);
+	struct merged_iter merged = {
+		.stack = iters,
+		.typ = record_type(rec),
+		.hash_size = mt->hash_size,
+	};
+	int n = 0;
+	int err = 0;
+	int i = 0;
+	for (i = 0; i < mt->stack_len && err == 0; i++) {
+		int e = reader_seek(mt->stack[i], &iters[n], rec);
+		if (e < 0) {
+			err = e;
+		}
+		if (e == 0) {
+			n++;
+		}
+	}
+	if (err < 0) {
+		int i = 0;
+		for (i = 0; i < n; i++) {
+			iterator_destroy(&iters[i]);
+		}
+		free(iters);
+		return err;
+	}
+
+	merged.stack_len = n, err = merged_iter_init(&merged);
+	if (err < 0) {
+		merged_iter_close(&merged);
+		return err;
+	}
+
+	{
+		struct merged_iter *p = malloc(sizeof(struct merged_iter));
+		*p = merged;
+		iterator_from_merged_iter(it, p);
+	}
+	return 0;
+}
+
+int merged_table_seek_ref(struct merged_table *mt, struct iterator *it,
+			  const char *name)
+{
+	struct ref_record ref = {
+		.ref_name = (char *)name,
+	};
+	struct record rec = {};
+	record_from_ref(&rec, &ref);
+	return merged_table_seek_record(mt, it, rec);
+}
+
+int merged_table_seek_log_at(struct merged_table *mt, struct iterator *it,
+			     const char *name, uint64_t update_index)
+{
+	struct log_record log = {
+		.ref_name = (char *)name,
+		.update_index = update_index,
+	};
+	struct record rec = {};
+	record_from_log(&rec, &log);
+	return merged_table_seek_record(mt, it, rec);
+}
+
+int merged_table_seek_log(struct merged_table *mt, struct iterator *it,
+			  const char *name)
+{
+	uint64_t max = ~((uint64_t)0);
+	return merged_table_seek_log_at(mt, it, name, max);
+}
diff --git a/reftable/merged.h b/reftable/merged.h
new file mode 100644
index 0000000000..b8d3572e26
--- /dev/null
+++ b/reftable/merged.h
@@ -0,0 +1,34 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef MERGED_H
+#define MERGED_H
+
+#include "pq.h"
+#include "reftable.h"
+
+struct merged_table {
+	struct reader **stack;
+	int stack_len;
+	int hash_size;
+
+	uint64_t min;
+	uint64_t max;
+};
+
+struct merged_iter {
+	struct iterator *stack;
+	int hash_size;
+	int stack_len;
+	byte typ;
+	struct merged_iter_pqueue pq;
+} merged_iter;
+
+void merged_table_clear(struct merged_table *mt);
+
+#endif
diff --git a/reftable/merged_test.c b/reftable/merged_test.c
new file mode 100644
index 0000000000..2a46c5ffbd
--- /dev/null
+++ b/reftable/merged_test.c
@@ -0,0 +1,258 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "merged.h"
+
+#include "system.h"
+
+#include "basics.h"
+#include "block.h"
+#include "constants.h"
+#include "pq.h"
+#include "reader.h"
+#include "record.h"
+#include "reftable.h"
+#include "test_framework.h"
+
+void test_pq(void)
+{
+	char *names[54] = {};
+	int N = ARRAYSIZE(names) - 1;
+
+	int i = 0;
+	for (i = 0; i < N; i++) {
+		char name[100];
+		snprintf(name, sizeof(name), "%02d", i);
+		names[i] = strdup(name);
+	}
+
+	struct merged_iter_pqueue pq = {};
+
+	i = 1;
+	do {
+		struct record rec = new_record(BLOCK_TYPE_REF);
+		record_as_ref(rec)->ref_name = names[i];
+
+		struct pq_entry e = {
+			.rec = rec,
+		};
+		merged_iter_pqueue_add(&pq, e);
+		merged_iter_pqueue_check(pq);
+		i = (i * 7) % N;
+	} while (i != 1);
+
+	const char *last = NULL;
+	while (!merged_iter_pqueue_is_empty(pq)) {
+		struct pq_entry e = merged_iter_pqueue_remove(&pq);
+		merged_iter_pqueue_check(pq);
+		struct ref_record *ref = record_as_ref(e.rec);
+
+		if (last != NULL) {
+			assert(strcmp(last, ref->ref_name) < 0);
+		}
+		last = ref->ref_name;
+		ref->ref_name = NULL;
+		free(ref);
+	}
+
+	for (i = 0; i < N; i++) {
+		free(names[i]);
+	}
+
+	merged_iter_pqueue_clear(&pq);
+}
+
+void write_test_table(struct slice *buf, struct ref_record refs[], int n)
+{
+	int min = 0xffffffff;
+	int max = 0;
+	int i = 0;
+	for (i = 0; i < n; i++) {
+		uint64_t ui = refs[i].update_index;
+		if (ui > max) {
+			max = ui;
+		}
+		if (ui < min) {
+			min = ui;
+		}
+	}
+
+	struct write_options opts = {
+		.block_size = 256,
+	};
+
+	struct writer *w = new_writer(&slice_write_void, buf, &opts);
+	writer_set_limits(w, min, max);
+
+	for (i = 0; i < n; i++) {
+		uint64_t before = refs[i].update_index;
+		int n = writer_add_ref(w, &refs[i]);
+		assert(n == 0);
+		assert(before == refs[i].update_index);
+	}
+
+	int err = writer_close(w);
+	assert(err == 0);
+
+	writer_free(w);
+	w = NULL;
+}
+
+static struct merged_table *merged_table_from_records(struct ref_record **refs,
+						      int *sizes,
+						      struct slice *buf, int n)
+{
+	struct block_source *source = calloc(n, sizeof(*source));
+	struct reader **rd = calloc(n, sizeof(*rd));
+	int i = 0;
+	for (i = 0; i < n; i++) {
+		write_test_table(&buf[i], refs[i], sizes[i]);
+		block_source_from_slice(&source[i], &buf[i]);
+
+		int err = new_reader(&rd[i], source[i], "name");
+		assert(err == 0);
+	}
+
+	struct merged_table *mt = NULL;
+	int err = new_merged_table(&mt, rd, n);
+	assert(err == 0);
+	return mt;
+}
+
+void test_merged_between(void)
+{
+	byte hash1[SHA1_SIZE];
+	byte hash2[SHA1_SIZE];
+
+	set_test_hash(hash1, 1);
+	set_test_hash(hash2, 2);
+	struct ref_record r1[] = { {
+		.ref_name = "b",
+		.update_index = 1,
+		.value = hash1,
+	} };
+	struct ref_record r2[] = { {
+		.ref_name = "a",
+		.update_index = 2,
+	} };
+
+	struct ref_record *refs[] = { r1, r2 };
+	int sizes[] = { 1, 1 };
+	struct slice bufs[2] = {};
+	struct merged_table *mt =
+		merged_table_from_records(refs, sizes, bufs, 2);
+
+	struct iterator it = {};
+	int err = merged_table_seek_ref(mt, &it, "a");
+	assert(err == 0);
+
+	struct ref_record ref = {};
+	err = iterator_next_ref(it, &ref);
+	assert_err(err);
+	assert(ref.update_index == 2);
+}
+
+void test_merged(void)
+{
+	byte hash1[SHA1_SIZE];
+	byte hash2[SHA1_SIZE];
+
+	set_test_hash(hash1, 1);
+	set_test_hash(hash2, 2);
+	struct ref_record r1[] = { {
+					   .ref_name = "a",
+					   .update_index = 1,
+					   .value = hash1,
+				   },
+				   {
+					   .ref_name = "b",
+					   .update_index = 1,
+					   .value = hash1,
+				   },
+				   {
+					   .ref_name = "c",
+					   .update_index = 1,
+					   .value = hash1,
+				   } };
+	struct ref_record r2[] = { {
+		.ref_name = "a",
+		.update_index = 2,
+	} };
+	struct ref_record r3[] = {
+		{
+			.ref_name = "c",
+			.update_index = 3,
+			.value = hash2,
+		},
+		{
+			.ref_name = "d",
+			.update_index = 3,
+			.value = hash1,
+		},
+	};
+
+	struct ref_record *refs[] = { r1, r2, r3 };
+	int sizes[3] = { 3, 1, 2 };
+	struct slice bufs[3] = {};
+
+	struct merged_table *mt =
+		merged_table_from_records(refs, sizes, bufs, 3);
+
+	struct iterator it = {};
+	int err = merged_table_seek_ref(mt, &it, "a");
+	assert(err == 0);
+
+	struct ref_record *out = NULL;
+	int len = 0;
+	int cap = 0;
+	while (len < 100) { /* cap loops/recursion. */
+		struct ref_record ref = {};
+		int err = iterator_next_ref(it, &ref);
+		if (err > 0) {
+			break;
+		}
+		if (len == cap) {
+			cap = 2 * cap + 1;
+			out = realloc(out, sizeof(struct ref_record) * cap);
+		}
+		out[len++] = ref;
+	}
+	iterator_destroy(&it);
+
+	struct ref_record want[] = {
+		r2[0],
+		r1[1],
+		r3[0],
+		r3[1],
+	};
+	assert(ARRAYSIZE(want) == len);
+	int i = 0;
+	for (i = 0; i < len; i++) {
+		assert(ref_record_equal(&want[i], &out[i], SHA1_SIZE));
+	}
+	for (i = 0; i < len; i++) {
+		ref_record_clear(&out[i]);
+	}
+	free(out);
+
+	for (i = 0; i < 3; i++) {
+		free(slice_yield(&bufs[i]));
+	}
+	merged_table_close(mt);
+	merged_table_free(mt);
+}
+
+/* XXX test refs_for(oid) */
+
+int main()
+{
+	add_test_case("test_merged_between", &test_merged_between);
+	add_test_case("test_pq", &test_pq);
+	add_test_case("test_merged", &test_merged);
+	test_main();
+}
diff --git a/reftable/pq.c b/reftable/pq.c
new file mode 100644
index 0000000000..62cb75c5c9
--- /dev/null
+++ b/reftable/pq.c
@@ -0,0 +1,124 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "pq.h"
+
+#include "system.h"
+
+int pq_less(struct pq_entry a, struct pq_entry b)
+{
+	struct slice ak = {};
+	struct slice bk = {};
+	int cmp = 0;
+	record_key(a.rec, &ak);
+	record_key(b.rec, &bk);
+
+	cmp = slice_compare(ak, bk);
+
+	free(slice_yield(&ak));
+	free(slice_yield(&bk));
+
+	if (cmp == 0) {
+		return a.index > b.index;
+	}
+
+	return cmp < 0;
+}
+
+struct pq_entry merged_iter_pqueue_top(struct merged_iter_pqueue pq)
+{
+	return pq.heap[0];
+}
+
+bool merged_iter_pqueue_is_empty(struct merged_iter_pqueue pq)
+{
+	return pq.len == 0;
+}
+
+void merged_iter_pqueue_check(struct merged_iter_pqueue pq)
+{
+	int i = 0;
+	for (i = 1; i < pq.len; i++) {
+		int parent = (i - 1) / 2;
+
+		assert(pq_less(pq.heap[parent], pq.heap[i]));
+	}
+}
+
+struct pq_entry merged_iter_pqueue_remove(struct merged_iter_pqueue *pq)
+{
+	int i = 0;
+	struct pq_entry e = pq->heap[0];
+	pq->heap[0] = pq->heap[pq->len - 1];
+	pq->len--;
+
+	i = 0;
+	while (i < pq->len) {
+		int min = i;
+		int j = 2 * i + 1;
+		int k = 2 * i + 2;
+		if (j < pq->len && pq_less(pq->heap[j], pq->heap[i])) {
+			min = j;
+		}
+		if (k < pq->len && pq_less(pq->heap[k], pq->heap[min])) {
+			min = k;
+		}
+
+		if (min == i) {
+			break;
+		}
+
+		{
+			struct pq_entry tmp = pq->heap[min];
+			pq->heap[min] = pq->heap[i];
+			pq->heap[i] = tmp;
+		}
+
+		i = min;
+	}
+
+	return e;
+}
+
+void merged_iter_pqueue_add(struct merged_iter_pqueue *pq, struct pq_entry e)
+{
+	int i = 0;
+	if (pq->len == pq->cap) {
+		pq->cap = 2 * pq->cap + 1;
+		pq->heap = realloc(pq->heap, pq->cap * sizeof(struct pq_entry));
+	}
+
+	pq->heap[pq->len++] = e;
+	i = pq->len - 1;
+	while (i > 0) {
+		int j = (i - 1) / 2;
+		if (pq_less(pq->heap[j], pq->heap[i])) {
+			break;
+		}
+
+		{
+			struct pq_entry tmp = pq->heap[j];
+			pq->heap[j] = pq->heap[i];
+			pq->heap[i] = tmp;
+		}
+
+		i = j;
+	}
+}
+
+void merged_iter_pqueue_clear(struct merged_iter_pqueue *pq)
+{
+	int i = 0;
+	for (i = 0; i < pq->len; i++) {
+		record_clear(pq->heap[i].rec);
+		free(record_yield(&pq->heap[i].rec));
+	}
+	free(pq->heap);
+	pq->heap = NULL;
+	pq->len = pq->cap = 0;
+}
diff --git a/reftable/pq.h b/reftable/pq.h
new file mode 100644
index 0000000000..5f7018979d
--- /dev/null
+++ b/reftable/pq.h
@@ -0,0 +1,34 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef PQ_H
+#define PQ_H
+
+#include "record.h"
+
+struct pq_entry {
+	struct record rec;
+	int index;
+};
+
+int pq_less(struct pq_entry a, struct pq_entry b);
+
+struct merged_iter_pqueue {
+	struct pq_entry *heap;
+	int len;
+	int cap;
+};
+
+struct pq_entry merged_iter_pqueue_top(struct merged_iter_pqueue pq);
+bool merged_iter_pqueue_is_empty(struct merged_iter_pqueue pq);
+void merged_iter_pqueue_check(struct merged_iter_pqueue pq);
+struct pq_entry merged_iter_pqueue_remove(struct merged_iter_pqueue *pq);
+void merged_iter_pqueue_add(struct merged_iter_pqueue *pq, struct pq_entry e);
+void merged_iter_pqueue_clear(struct merged_iter_pqueue *pq);
+
+#endif
diff --git a/reftable/reader.c b/reftable/reader.c
new file mode 100644
index 0000000000..754114b58b
--- /dev/null
+++ b/reftable/reader.c
@@ -0,0 +1,710 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "reader.h"
+
+#include "system.h"
+
+#include "block.h"
+#include "constants.h"
+#include "iter.h"
+#include "record.h"
+#include "reftable.h"
+#include "tree.h"
+
+uint64_t block_source_size(struct block_source source)
+{
+	return source.ops->size(source.arg);
+}
+
+int block_source_read_block(struct block_source source, struct block *dest,
+			    uint64_t off, uint32_t size)
+{
+	int result = source.ops->read_block(source.arg, dest, off, size);
+	dest->source = source;
+	return result;
+}
+
+void block_source_return_block(struct block_source source, struct block *blockp)
+{
+	source.ops->return_block(source.arg, blockp);
+	blockp->data = NULL;
+	blockp->len = 0;
+	blockp->source.ops = NULL;
+	blockp->source.arg = NULL;
+}
+
+void block_source_close(struct block_source *source)
+{
+	if (source->ops == NULL) {
+		return;
+	}
+
+	source->ops->close(source->arg);
+	source->ops = NULL;
+}
+
+static struct reader_offsets *reader_offsets_for(struct reader *r, byte typ)
+{
+	switch (typ) {
+	case BLOCK_TYPE_REF:
+		return &r->ref_offsets;
+	case BLOCK_TYPE_LOG:
+		return &r->log_offsets;
+	case BLOCK_TYPE_OBJ:
+		return &r->obj_offsets;
+	}
+	abort();
+}
+
+static int reader_get_block(struct reader *r, struct block *dest, uint64_t off,
+			    uint32_t sz)
+{
+	if (off >= r->size) {
+		return 0;
+	}
+
+	if (off + sz > r->size) {
+		sz = r->size - off;
+	}
+
+	return block_source_read_block(r->source, dest, off, sz);
+}
+
+void reader_return_block(struct reader *r, struct block *p)
+{
+	block_source_return_block(r->source, p);
+}
+
+const char *reader_name(struct reader *r)
+{
+	return r->name;
+}
+
+static int parse_footer(struct reader *r, byte *footer, byte *header)
+{
+	byte *f = footer;
+	int err = 0;
+	if (memcmp(f, "REFT", 4)) {
+		err = FORMAT_ERROR;
+		goto exit;
+	}
+	f += 4;
+
+	if (memcmp(footer, header, HEADER_SIZE)) {
+		err = FORMAT_ERROR;
+		goto exit;
+	}
+
+	{
+		byte version = *f++;
+		if (version != 1) {
+			err = FORMAT_ERROR;
+			goto exit;
+		}
+	}
+
+	r->block_size = get_u24(f);
+
+	f += 3;
+	r->min_update_index = get_u64(f);
+	f += 8;
+	r->max_update_index = get_u64(f);
+	f += 8;
+
+	r->ref_offsets.index_offset = get_u64(f);
+	f += 8;
+
+	r->obj_offsets.offset = get_u64(f);
+	f += 8;
+
+	r->object_id_len = r->obj_offsets.offset & ((1 << 5) - 1);
+	r->obj_offsets.offset >>= 5;
+
+	r->obj_offsets.index_offset = get_u64(f);
+	f += 8;
+	r->log_offsets.offset = get_u64(f);
+	f += 8;
+	r->log_offsets.index_offset = get_u64(f);
+	f += 8;
+
+	{
+		uint32_t computed_crc = crc32(0, footer, f - footer);
+		uint32_t file_crc = get_u32(f);
+		f += 4;
+		if (computed_crc != file_crc) {
+			err = FORMAT_ERROR;
+			goto exit;
+		}
+	}
+
+	{
+		byte first_block_typ = header[HEADER_SIZE];
+		r->ref_offsets.present = (first_block_typ == BLOCK_TYPE_REF);
+		r->ref_offsets.offset = 0;
+		r->log_offsets.present = (first_block_typ == BLOCK_TYPE_LOG ||
+					  r->log_offsets.offset > 0);
+		r->obj_offsets.present = r->obj_offsets.offset > 0;
+	}
+	err = 0;
+exit:
+	return err;
+}
+
+int init_reader(struct reader *r, struct block_source source, const char *name)
+{
+	struct block footer = {};
+	struct block header = {};
+	int err = 0;
+
+	memset(r, 0, sizeof(struct reader));
+	r->size = block_source_size(source) - FOOTER_SIZE;
+	r->source = source;
+	r->name = strdup(name);
+	r->hash_size = SHA1_SIZE;
+
+	err = block_source_read_block(source, &footer, r->size, FOOTER_SIZE);
+	if (err != FOOTER_SIZE) {
+		err = IO_ERROR;
+		goto exit;
+	}
+
+	/* Need +1 to read type of first block. */
+	err = reader_get_block(r, &header, 0, HEADER_SIZE + 1);
+	if (err != HEADER_SIZE + 1) {
+		err = IO_ERROR;
+		goto exit;
+	}
+
+	err = parse_footer(r, footer.data, header.data);
+exit:
+	block_source_return_block(r->source, &footer);
+	block_source_return_block(r->source, &header);
+	return err;
+}
+
+struct table_iter {
+	struct reader *r;
+	byte typ;
+	uint64_t block_off;
+	struct block_iter bi;
+	bool finished;
+};
+
+static void table_iter_copy_from(struct table_iter *dest,
+				 struct table_iter *src)
+{
+	dest->r = src->r;
+	dest->typ = src->typ;
+	dest->block_off = src->block_off;
+	dest->finished = src->finished;
+	block_iter_copy_from(&dest->bi, &src->bi);
+}
+
+static int table_iter_next_in_block(struct table_iter *ti, struct record rec)
+{
+	int res = block_iter_next(&ti->bi, rec);
+	if (res == 0 && record_type(rec) == BLOCK_TYPE_REF) {
+		((struct ref_record *)rec.data)->update_index +=
+			ti->r->min_update_index;
+	}
+
+	return res;
+}
+
+static void table_iter_block_done(struct table_iter *ti)
+{
+	if (ti->bi.br == NULL) {
+		return;
+	}
+	reader_return_block(ti->r, &ti->bi.br->block);
+	free(ti->bi.br);
+	ti->bi.br = NULL;
+
+	ti->bi.last_key.len = 0;
+	ti->bi.next_off = 0;
+}
+
+static int32_t extract_block_size(byte *data, byte *typ, uint64_t off)
+{
+	int32_t result = 0;
+
+	if (off == 0) {
+		data += 24;
+	}
+
+	*typ = data[0];
+	if (is_block_type(*typ)) {
+		result = get_u24(data + 1);
+	}
+	return result;
+}
+
+int reader_init_block_reader(struct reader *r, struct block_reader *br,
+			     uint64_t next_off, byte want_typ)
+{
+	int32_t guess_block_size = r->block_size ? r->block_size :
+						   DEFAULT_BLOCK_SIZE;
+	struct block block = {};
+	byte block_typ = 0;
+	int err = 0;
+	uint32_t header_off = next_off ? 0 : HEADER_SIZE;
+	int32_t block_size = 0;
+
+	if (next_off >= r->size) {
+		return 1;
+	}
+
+	err = reader_get_block(r, &block, next_off, guess_block_size);
+	if (err < 0) {
+		return err;
+	}
+
+	block_size = extract_block_size(block.data, &block_typ, next_off);
+	if (block_size < 0) {
+		return block_size;
+	}
+
+	if (want_typ != BLOCK_TYPE_ANY && block_typ != want_typ) {
+		reader_return_block(r, &block);
+		return 1;
+	}
+
+	if (block_size > guess_block_size) {
+		reader_return_block(r, &block);
+		err = reader_get_block(r, &block, next_off, block_size);
+		if (err < 0) {
+			return err;
+		}
+	}
+
+	return block_reader_init(br, &block, header_off, r->block_size,
+				 r->hash_size);
+}
+
+static int table_iter_next_block(struct table_iter *dest,
+				 struct table_iter *src)
+{
+	uint64_t next_block_off = src->block_off + src->bi.br->full_block_size;
+	struct block_reader br = {};
+	int err = 0;
+
+	dest->r = src->r;
+	dest->typ = src->typ;
+	dest->block_off = next_block_off;
+
+	err = reader_init_block_reader(src->r, &br, next_block_off, src->typ);
+	if (err > 0) {
+		dest->finished = true;
+		return 1;
+	}
+	if (err != 0) {
+		return err;
+	}
+
+	{
+		struct block_reader *brp = malloc(sizeof(struct block_reader));
+		*brp = br;
+
+		dest->finished = false;
+		block_reader_start(brp, &dest->bi);
+	}
+	return 0;
+}
+
+static int table_iter_next(struct table_iter *ti, struct record rec)
+{
+	if (record_type(rec) != ti->typ) {
+		return API_ERROR;
+	}
+
+	while (true) {
+		struct table_iter next = {};
+		int err = 0;
+		if (ti->finished) {
+			return 1;
+		}
+
+		err = table_iter_next_in_block(ti, rec);
+		if (err <= 0) {
+			return err;
+		}
+
+		err = table_iter_next_block(&next, ti);
+		if (err != 0) {
+			ti->finished = true;
+		}
+		table_iter_block_done(ti);
+		if (err != 0) {
+			return err;
+		}
+		table_iter_copy_from(ti, &next);
+		block_iter_close(&next.bi);
+	}
+}
+
+static int table_iter_next_void(void *ti, struct record rec)
+{
+	return table_iter_next((struct table_iter *)ti, rec);
+}
+
+static void table_iter_close(void *p)
+{
+	struct table_iter *ti = (struct table_iter *)p;
+	table_iter_block_done(ti);
+	block_iter_close(&ti->bi);
+}
+
+struct iterator_vtable table_iter_vtable = {
+	.next = &table_iter_next_void,
+	.close = &table_iter_close,
+};
+
+static void iterator_from_table_iter(struct iterator *it, struct table_iter *ti)
+{
+	it->iter_arg = ti;
+	it->ops = &table_iter_vtable;
+}
+
+static int reader_table_iter_at(struct reader *r, struct table_iter *ti,
+				uint64_t off, byte typ)
+{
+	struct block_reader br = {};
+	struct block_reader *brp = NULL;
+
+	int err = reader_init_block_reader(r, &br, off, typ);
+	if (err != 0) {
+		return err;
+	}
+
+	brp = malloc(sizeof(struct block_reader));
+	*brp = br;
+	ti->r = r;
+	ti->typ = block_reader_type(brp);
+	ti->block_off = off;
+	block_reader_start(brp, &ti->bi);
+	return 0;
+}
+
+static int reader_start(struct reader *r, struct table_iter *ti, byte typ,
+			bool index)
+{
+	struct reader_offsets *offs = reader_offsets_for(r, typ);
+	uint64_t off = offs->offset;
+	if (index) {
+		off = offs->index_offset;
+		if (off == 0) {
+			return 1;
+		}
+		typ = BLOCK_TYPE_INDEX;
+	}
+
+	return reader_table_iter_at(r, ti, off, typ);
+}
+
+static int reader_seek_linear(struct reader *r, struct table_iter *ti,
+			      struct record want)
+{
+	struct record rec = new_record(record_type(want));
+	struct slice want_key = {};
+	struct slice got_key = {};
+	struct table_iter next = {};
+	int err = -1;
+	record_key(want, &want_key);
+
+	while (true) {
+		err = table_iter_next_block(&next, ti);
+		if (err < 0) {
+			goto exit;
+		}
+
+		if (err > 0) {
+			break;
+		}
+
+		err = block_reader_first_key(next.bi.br, &got_key);
+		if (err < 0) {
+			goto exit;
+		}
+		{
+			int cmp = slice_compare(got_key, want_key);
+			if (cmp > 0) {
+				table_iter_block_done(&next);
+				break;
+			}
+		}
+
+		table_iter_block_done(ti);
+		table_iter_copy_from(ti, &next);
+	}
+
+	err = block_iter_seek(&ti->bi, want_key);
+	if (err < 0) {
+		goto exit;
+	}
+	err = 0;
+
+exit:
+	block_iter_close(&next.bi);
+	record_clear(rec);
+	free(record_yield(&rec));
+	free(slice_yield(&want_key));
+	free(slice_yield(&got_key));
+	return err;
+}
+
+static int reader_seek_indexed(struct reader *r, struct iterator *it,
+			       struct record rec)
+{
+	struct index_record want_index = {};
+	struct record want_index_rec = {};
+	struct index_record index_result = {};
+	struct record index_result_rec = {};
+	struct table_iter index_iter = {};
+	struct table_iter next = {};
+	int err = 0;
+
+	record_key(rec, &want_index.last_key);
+	record_from_index(&want_index_rec, &want_index);
+	record_from_index(&index_result_rec, &index_result);
+
+	err = reader_start(r, &index_iter, record_type(rec), true);
+	if (err < 0) {
+		goto exit;
+	}
+
+	err = reader_seek_linear(r, &index_iter, want_index_rec);
+	while (true) {
+		err = table_iter_next(&index_iter, index_result_rec);
+		table_iter_block_done(&index_iter);
+		if (err != 0) {
+			goto exit;
+		}
+
+		err = reader_table_iter_at(r, &next, index_result.offset, 0);
+		if (err != 0) {
+			goto exit;
+		}
+
+		err = block_iter_seek(&next.bi, want_index.last_key);
+		if (err < 0) {
+			goto exit;
+		}
+
+		if (next.typ == record_type(rec)) {
+			err = 0;
+			break;
+		}
+
+		if (next.typ != BLOCK_TYPE_INDEX) {
+			err = FORMAT_ERROR;
+			break;
+		}
+
+		table_iter_copy_from(&index_iter, &next);
+	}
+
+	if (err == 0) {
+		struct table_iter *malloced =
+			calloc(sizeof(struct table_iter), 1);
+		table_iter_copy_from(malloced, &next);
+		iterator_from_table_iter(it, malloced);
+	}
+exit:
+	block_iter_close(&next.bi);
+	table_iter_close(&index_iter);
+	record_clear(want_index_rec);
+	record_clear(index_result_rec);
+	return err;
+}
+
+static int reader_seek_internal(struct reader *r, struct iterator *it,
+				struct record rec)
+{
+	struct reader_offsets *offs = reader_offsets_for(r, record_type(rec));
+	uint64_t idx = offs->index_offset;
+	struct table_iter ti = {};
+	int err = 0;
+	if (idx > 0) {
+		return reader_seek_indexed(r, it, rec);
+	}
+
+	err = reader_start(r, &ti, record_type(rec), false);
+	if (err < 0) {
+		return err;
+	}
+	err = reader_seek_linear(r, &ti, rec);
+	if (err < 0) {
+		return err;
+	}
+
+	{
+		struct table_iter *p = malloc(sizeof(struct table_iter));
+		*p = ti;
+		iterator_from_table_iter(it, p);
+	}
+
+	return 0;
+}
+
+int reader_seek(struct reader *r, struct iterator *it, struct record rec)
+{
+	byte typ = record_type(rec);
+
+	struct reader_offsets *offs = reader_offsets_for(r, typ);
+	if (!offs->present) {
+		iterator_set_empty(it);
+		return 0;
+	}
+
+	return reader_seek_internal(r, it, rec);
+}
+
+int reader_seek_ref(struct reader *r, struct iterator *it, const char *name)
+{
+	struct ref_record ref = {
+		.ref_name = (char *)name,
+	};
+	struct record rec = {};
+	record_from_ref(&rec, &ref);
+	return reader_seek(r, it, rec);
+}
+
+int reader_seek_log_at(struct reader *r, struct iterator *it, const char *name,
+		       uint64_t update_index)
+{
+	struct log_record log = {
+		.ref_name = (char *)name,
+		.update_index = update_index,
+	};
+	struct record rec = {};
+	record_from_log(&rec, &log);
+	return reader_seek(r, it, rec);
+}
+
+int reader_seek_log(struct reader *r, struct iterator *it, const char *name)
+{
+	uint64_t max = ~((uint64_t)0);
+	return reader_seek_log_at(r, it, name, max);
+}
+
+void reader_close(struct reader *r)
+{
+	block_source_close(&r->source);
+	free(r->name);
+	r->name = NULL;
+}
+
+int new_reader(struct reader **p, struct block_source src, char const *name)
+{
+	struct reader *rd = calloc(sizeof(struct reader), 1);
+	int err = init_reader(rd, src, name);
+	if (err == 0) {
+		*p = rd;
+	} else {
+		free(rd);
+	}
+	return err;
+}
+
+void reader_free(struct reader *r)
+{
+	reader_close(r);
+	free(r);
+}
+
+static int reader_refs_for_indexed(struct reader *r, struct iterator *it,
+				   byte *oid)
+{
+	struct obj_record want = {
+		.hash_prefix = oid,
+		.hash_prefix_len = r->object_id_len,
+	};
+	struct record want_rec = {};
+	struct iterator oit = {};
+	struct obj_record got = {};
+	struct record got_rec = {};
+	int err = 0;
+
+	record_from_obj(&want_rec, &want);
+
+	err = reader_seek(r, &oit, want_rec);
+	if (err != 0) {
+		return err;
+	}
+
+	record_from_obj(&got_rec, &got);
+	err = iterator_next(oit, got_rec);
+	iterator_destroy(&oit);
+	if (err < 0) {
+		return err;
+	}
+
+	if (err > 0 ||
+	    memcmp(want.hash_prefix, got.hash_prefix, r->object_id_len)) {
+		iterator_set_empty(it);
+		return 0;
+	}
+
+	{
+		struct indexed_table_ref_iter *itr = NULL;
+		err = new_indexed_table_ref_iter(&itr, r, oid, r->hash_size,
+						 got.offsets, got.offset_len);
+		if (err < 0) {
+			record_clear(got_rec);
+			return err;
+		}
+		got.offsets = NULL;
+		record_clear(got_rec);
+
+		iterator_from_indexed_table_ref_iter(it, itr);
+	}
+
+	return 0;
+}
+
+static int reader_refs_for_unindexed(struct reader *r, struct iterator *it,
+				     byte *oid, int oid_len)
+{
+	struct table_iter *ti = calloc(sizeof(struct table_iter), 1);
+	struct filtering_ref_iterator *filter = NULL;
+	int err = reader_start(r, ti, BLOCK_TYPE_REF, false);
+	if (err < 0) {
+		free(ti);
+		return err;
+	}
+
+	filter = calloc(sizeof(struct filtering_ref_iterator), 1);
+	slice_resize(&filter->oid, oid_len);
+	memcpy(filter->oid.buf, oid, oid_len);
+	filter->r = r;
+	filter->double_check = false;
+	iterator_from_table_iter(&filter->it, ti);
+
+	iterator_from_filtering_ref_iterator(it, filter);
+	return 0;
+}
+
+int reader_refs_for(struct reader *r, struct iterator *it, byte *oid,
+		    int oid_len)
+{
+	if (r->obj_offsets.present) {
+		return reader_refs_for_indexed(r, it, oid);
+	}
+	return reader_refs_for_unindexed(r, it, oid, oid_len);
+}
+
+uint64_t reader_max_update_index(struct reader *r)
+{
+	return r->max_update_index;
+}
+
+uint64_t reader_min_update_index(struct reader *r)
+{
+	return r->min_update_index;
+}
diff --git a/reftable/reader.h b/reftable/reader.h
new file mode 100644
index 0000000000..599a90028e
--- /dev/null
+++ b/reftable/reader.h
@@ -0,0 +1,52 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef READER_H
+#define READER_H
+
+#include "block.h"
+#include "record.h"
+#include "reftable.h"
+
+uint64_t block_source_size(struct block_source source);
+
+int block_source_read_block(struct block_source source, struct block *dest,
+			    uint64_t off, uint32_t size);
+void block_source_return_block(struct block_source source, struct block *ret);
+void block_source_close(struct block_source *source);
+
+struct reader_offsets {
+	bool present;
+	uint64_t offset;
+	uint64_t index_offset;
+};
+
+struct reader {
+	struct block_source source;
+	char *name;
+	int hash_size;
+	uint64_t size;
+	uint32_t block_size;
+	uint64_t min_update_index;
+	uint64_t max_update_index;
+	int object_id_len;
+
+	struct reader_offsets ref_offsets;
+	struct reader_offsets obj_offsets;
+	struct reader_offsets log_offsets;
+};
+
+int init_reader(struct reader *r, struct block_source source, const char *name);
+int reader_seek(struct reader *r, struct iterator *it, struct record rec);
+void reader_close(struct reader *r);
+const char *reader_name(struct reader *r);
+void reader_return_block(struct reader *r, struct block *p);
+int reader_init_block_reader(struct reader *r, struct block_reader *br,
+			     uint64_t next_off, byte want_typ);
+
+#endif
diff --git a/reftable/record.c b/reftable/record.c
new file mode 100644
index 0000000000..657e0e0d91
--- /dev/null
+++ b/reftable/record.c
@@ -0,0 +1,1110 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "record.h"
+
+#include "system.h"
+
+#include "constants.h"
+#include "reftable.h"
+
+int is_block_type(byte typ)
+{
+	switch (typ) {
+	case BLOCK_TYPE_REF:
+	case BLOCK_TYPE_LOG:
+	case BLOCK_TYPE_OBJ:
+	case BLOCK_TYPE_INDEX:
+		return true;
+	}
+	return false;
+}
+
+int get_var_int(uint64_t *dest, struct slice in)
+{
+	int ptr = 0;
+	uint64_t val;
+
+	if (in.len == 0) {
+		return -1;
+	}
+	val = in.buf[ptr] & 0x7f;
+
+	while (in.buf[ptr] & 0x80) {
+		ptr++;
+		if (ptr > in.len) {
+			return -1;
+		}
+		val = (val + 1) << 7 | (uint64_t)(in.buf[ptr] & 0x7f);
+	}
+
+	*dest = val;
+	return ptr + 1;
+}
+
+int put_var_int(struct slice dest, uint64_t val)
+{
+	byte buf[10] = {};
+	int i = 9;
+	buf[i] = (byte)(val & 0x7f);
+	i--;
+	while (true) {
+		val >>= 7;
+		if (!val) {
+			break;
+		}
+		val--;
+		buf[i] = 0x80 | (byte)(val & 0x7f);
+		i--;
+	}
+
+	{
+		int n = sizeof(buf) - i - 1;
+		if (dest.len < n) {
+			return -1;
+		}
+		memcpy(dest.buf, &buf[i + 1], n);
+		return n;
+	}
+}
+
+int common_prefix_size(struct slice a, struct slice b)
+{
+	int p = 0;
+	while (p < a.len && p < b.len) {
+		if (a.buf[p] != b.buf[p]) {
+			break;
+		}
+		p++;
+	}
+
+	return p;
+}
+
+static int decode_string(struct slice *dest, struct slice in)
+{
+	int start_len = in.len;
+	uint64_t tsize = 0;
+	int n = get_var_int(&tsize, in);
+	if (n <= 0) {
+		return -1;
+	}
+	in.buf += n;
+	in.len -= n;
+	if (in.len < tsize) {
+		return -1;
+	}
+
+	slice_resize(dest, tsize + 1);
+	dest->buf[tsize] = 0;
+	memcpy(dest->buf, in.buf, tsize);
+	in.buf += tsize;
+	in.len -= tsize;
+
+	return start_len - in.len;
+}
+
+int encode_key(bool *restart, struct slice dest, struct slice prev_key,
+	       struct slice key, byte extra)
+{
+	struct slice start = dest;
+	int prefix_len = common_prefix_size(prev_key, key);
+	uint64_t suffix_len = key.len - prefix_len;
+	int n = put_var_int(dest, (uint64_t)prefix_len);
+	if (n < 0) {
+		return -1;
+	}
+	dest.buf += n;
+	dest.len -= n;
+
+	*restart = (prefix_len == 0);
+
+	n = put_var_int(dest, suffix_len << 3 | (uint64_t)extra);
+	if (n < 0) {
+		return -1;
+	}
+	dest.buf += n;
+	dest.len -= n;
+
+	if (dest.len < suffix_len) {
+		return -1;
+	}
+	memcpy(dest.buf, key.buf + prefix_len, suffix_len);
+	dest.buf += suffix_len;
+	dest.len -= suffix_len;
+
+	return start.len - dest.len;
+}
+
+static byte ref_record_type(void)
+{
+	return BLOCK_TYPE_REF;
+}
+
+static void ref_record_key(const void *r, struct slice *dest)
+{
+	const struct ref_record *rec = (const struct ref_record *)r;
+	slice_set_string(dest, rec->ref_name);
+}
+
+static void ref_record_copy_from(void *rec, const void *src_rec, int hash_size)
+{
+	struct ref_record *ref = (struct ref_record *)rec;
+	struct ref_record *src = (struct ref_record *)src_rec;
+	assert(hash_size > 0);
+
+	/* This is simple and correct, but we could probably reuse the hash
+           fields. */
+	ref_record_clear(ref);
+	if (src->ref_name != NULL) {
+		ref->ref_name = strdup(src->ref_name);
+	}
+
+	if (src->target != NULL) {
+		ref->target = strdup(src->target);
+	}
+
+	if (src->target_value != NULL) {
+		ref->target_value = malloc(hash_size);
+		memcpy(ref->target_value, src->target_value, hash_size);
+	}
+
+	if (src->value != NULL) {
+		ref->value = malloc(hash_size);
+		memcpy(ref->value, src->value, hash_size);
+	}
+	ref->update_index = src->update_index;
+}
+
+static char hexdigit(int c)
+{
+	if (c <= 9) {
+		return '0' + c;
+	}
+	return 'a' + (c - 10);
+}
+
+static void hex_format(char *dest, byte *src, int hash_size)
+{
+	assert(hash_size > 0);
+	if (src != NULL) {
+		int i = 0;
+		for (i = 0; i < hash_size; i++) {
+			dest[2 * i] = hexdigit(src[i] >> 4);
+			dest[2 * i + 1] = hexdigit(src[i] & 0xf);
+		}
+		dest[2 * hash_size] = 0;
+	}
+}
+
+void ref_record_print(struct ref_record *ref, int hash_size)
+{
+	char hex[SHA256_SIZE + 1] = {};
+
+	printf("ref{%s(%" PRIdMAX ") ", ref->ref_name, ref->update_index);
+	if (ref->value != NULL) {
+		hex_format(hex, ref->value, hash_size);
+		printf("%s", hex);
+	}
+	if (ref->target_value != NULL) {
+		hex_format(hex, ref->target_value, hash_size);
+		printf(" (T %s)", hex);
+	}
+	if (ref->target != NULL) {
+		printf("=> %s", ref->target);
+	}
+	printf("}\n");
+}
+
+static void ref_record_clear_void(void *rec)
+{
+	ref_record_clear((struct ref_record *)rec);
+}
+
+void ref_record_clear(struct ref_record *ref)
+{
+	free(ref->ref_name);
+	free(ref->target);
+	free(ref->target_value);
+	free(ref->value);
+	memset(ref, 0, sizeof(struct ref_record));
+}
+
+static byte ref_record_val_type(const void *rec)
+{
+	const struct ref_record *r = (const struct ref_record *)rec;
+	if (r->value != NULL) {
+		if (r->target_value != NULL) {
+			return 2;
+		} else {
+			return 1;
+		}
+	} else if (r->target != NULL) {
+		return 3;
+	}
+	return 0;
+}
+
+static int encode_string(char *str, struct slice s)
+{
+	struct slice start = s;
+	int l = strlen(str);
+	int n = put_var_int(s, l);
+	if (n < 0) {
+		return -1;
+	}
+	s.buf += n;
+	s.len -= n;
+	if (s.len < l) {
+		return -1;
+	}
+	memcpy(s.buf, str, l);
+	s.buf += l;
+	s.len -= l;
+
+	return start.len - s.len;
+}
+
+static int ref_record_encode(const void *rec, struct slice s, int hash_size)
+{
+	const struct ref_record *r = (const struct ref_record *)rec;
+	struct slice start = s;
+	int n = put_var_int(s, r->update_index);
+	assert(hash_size > 0);
+	if (n < 0) {
+		return -1;
+	}
+	s.buf += n;
+	s.len -= n;
+
+	if (r->value != NULL) {
+		if (s.len < hash_size) {
+			return -1;
+		}
+		memcpy(s.buf, r->value, hash_size);
+		s.buf += hash_size;
+		s.len -= hash_size;
+	}
+
+	if (r->target_value != NULL) {
+		if (s.len < hash_size) {
+			return -1;
+		}
+		memcpy(s.buf, r->target_value, hash_size);
+		s.buf += hash_size;
+		s.len -= hash_size;
+	}
+
+	if (r->target != NULL) {
+		int n = encode_string(r->target, s);
+		if (n < 0) {
+			return -1;
+		}
+		s.buf += n;
+		s.len -= n;
+	}
+
+	return start.len - s.len;
+}
+
+static int ref_record_decode(void *rec, struct slice key, byte val_type,
+			     struct slice in, int hash_size)
+{
+	struct ref_record *r = (struct ref_record *)rec;
+	struct slice start = in;
+	bool seen_value = false;
+	bool seen_target_value = false;
+	bool seen_target = false;
+
+	int n = get_var_int(&r->update_index, in);
+	if (n < 0) {
+		return n;
+	}
+	assert(hash_size > 0);
+
+	in.buf += n;
+	in.len -= n;
+
+	r->ref_name = realloc(r->ref_name, key.len + 1);
+	memcpy(r->ref_name, key.buf, key.len);
+	r->ref_name[key.len] = 0;
+
+	switch (val_type) {
+	case 1:
+	case 2:
+		if (in.len < hash_size) {
+			return -1;
+		}
+
+		if (r->value == NULL) {
+			r->value = malloc(hash_size);
+		}
+		seen_value = true;
+		memcpy(r->value, in.buf, hash_size);
+		in.buf += hash_size;
+		in.len -= hash_size;
+		if (val_type == 1) {
+			break;
+		}
+		if (r->target_value == NULL) {
+			r->target_value = malloc(hash_size);
+		}
+		seen_target_value = true;
+		memcpy(r->target_value, in.buf, hash_size);
+		in.buf += hash_size;
+		in.len -= hash_size;
+		break;
+	case 3: {
+		struct slice dest = {};
+		int n = decode_string(&dest, in);
+		if (n < 0) {
+			return -1;
+		}
+		in.buf += n;
+		in.len -= n;
+		seen_target = true;
+		r->target = (char *)slice_as_string(&dest);
+	} break;
+
+	case 0:
+		break;
+	default:
+		abort();
+		break;
+	}
+
+	if (!seen_target && r->target != NULL) {
+		free(r->target);
+		r->target = NULL;
+	}
+	if (!seen_target_value && r->target_value != NULL) {
+		free(r->target_value);
+		r->target_value = NULL;
+	}
+	if (!seen_value && r->value != NULL) {
+		free(r->value);
+		r->value = NULL;
+	}
+
+	return start.len - in.len;
+}
+
+int decode_key(struct slice *key, byte *extra, struct slice last_key,
+	       struct slice in)
+{
+	int start_len = in.len;
+	uint64_t prefix_len = 0;
+	uint64_t suffix_len = 0;
+	int n = get_var_int(&prefix_len, in);
+	if (n < 0) {
+		return -1;
+	}
+	in.buf += n;
+	in.len -= n;
+
+	if (prefix_len > last_key.len) {
+		return -1;
+	}
+
+	n = get_var_int(&suffix_len, in);
+	if (n <= 0) {
+		return -1;
+	}
+	in.buf += n;
+	in.len -= n;
+
+	*extra = (byte)(suffix_len & 0x7);
+	suffix_len >>= 3;
+
+	if (in.len < suffix_len) {
+		return -1;
+	}
+
+	slice_resize(key, suffix_len + prefix_len);
+	memcpy(key->buf, last_key.buf, prefix_len);
+
+	memcpy(key->buf + prefix_len, in.buf, suffix_len);
+	in.buf += suffix_len;
+	in.len -= suffix_len;
+
+	return start_len - in.len;
+}
+
+struct record_vtable ref_record_vtable = {
+	.key = &ref_record_key,
+	.type = &ref_record_type,
+	.copy_from = &ref_record_copy_from,
+	.val_type = &ref_record_val_type,
+	.encode = &ref_record_encode,
+	.decode = &ref_record_decode,
+	.clear = &ref_record_clear_void,
+};
+
+static byte obj_record_type(void)
+{
+	return BLOCK_TYPE_OBJ;
+}
+
+static void obj_record_key(const void *r, struct slice *dest)
+{
+	const struct obj_record *rec = (const struct obj_record *)r;
+	slice_resize(dest, rec->hash_prefix_len);
+	memcpy(dest->buf, rec->hash_prefix, rec->hash_prefix_len);
+}
+
+static void obj_record_copy_from(void *rec, const void *src_rec, int hash_size)
+{
+	struct obj_record *ref = (struct obj_record *)rec;
+	const struct obj_record *src = (const struct obj_record *)src_rec;
+
+	*ref = *src;
+	ref->hash_prefix = malloc(ref->hash_prefix_len);
+	memcpy(ref->hash_prefix, src->hash_prefix, ref->hash_prefix_len);
+
+	{
+		int olen = ref->offset_len * sizeof(uint64_t);
+		ref->offsets = malloc(olen);
+		memcpy(ref->offsets, src->offsets, olen);
+	}
+}
+
+static void obj_record_clear(void *rec)
+{
+	struct obj_record *ref = (struct obj_record *)rec;
+	free(ref->hash_prefix);
+	free(ref->offsets);
+	memset(ref, 0, sizeof(struct obj_record));
+}
+
+static byte obj_record_val_type(const void *rec)
+{
+	struct obj_record *r = (struct obj_record *)rec;
+	if (r->offset_len > 0 && r->offset_len < 8) {
+		return r->offset_len;
+	}
+	return 0;
+}
+
+static int obj_record_encode(const void *rec, struct slice s, int hash_size)
+{
+	struct obj_record *r = (struct obj_record *)rec;
+	struct slice start = s;
+	int n = 0;
+	if (r->offset_len == 0 || r->offset_len >= 8) {
+		n = put_var_int(s, r->offset_len);
+		if (n < 0) {
+			return -1;
+		}
+		s.buf += n;
+		s.len -= n;
+	}
+	if (r->offset_len == 0) {
+		return start.len - s.len;
+	}
+	n = put_var_int(s, r->offsets[0]);
+	if (n < 0) {
+		return -1;
+	}
+	s.buf += n;
+	s.len -= n;
+
+	{
+		uint64_t last = r->offsets[0];
+		int i = 0;
+		for (i = 1; i < r->offset_len; i++) {
+			int n = put_var_int(s, r->offsets[i] - last);
+			if (n < 0) {
+				return -1;
+			}
+			s.buf += n;
+			s.len -= n;
+			last = r->offsets[i];
+		}
+	}
+	return start.len - s.len;
+}
+
+static int obj_record_decode(void *rec, struct slice key, byte val_type,
+			     struct slice in, int hash_size)
+{
+	struct slice start = in;
+	struct obj_record *r = (struct obj_record *)rec;
+	uint64_t count = val_type;
+	int n = 0;
+	r->hash_prefix = malloc(key.len);
+	memcpy(r->hash_prefix, key.buf, key.len);
+	r->hash_prefix_len = key.len;
+
+	if (val_type == 0) {
+		n = get_var_int(&count, in);
+		if (n < 0) {
+			return n;
+		}
+
+		in.buf += n;
+		in.len -= n;
+	}
+
+	r->offsets = NULL;
+	r->offset_len = 0;
+	if (count == 0) {
+		return start.len - in.len;
+	}
+
+	r->offsets = malloc(count * sizeof(uint64_t));
+	r->offset_len = count;
+
+	n = get_var_int(&r->offsets[0], in);
+	if (n < 0) {
+		return n;
+	}
+
+	in.buf += n;
+	in.len -= n;
+
+	{
+		uint64_t last = r->offsets[0];
+		int j = 1;
+		while (j < count) {
+			uint64_t delta = 0;
+			int n = get_var_int(&delta, in);
+			if (n < 0) {
+				return n;
+			}
+
+			in.buf += n;
+			in.len -= n;
+
+			last = r->offsets[j] = (delta + last);
+			j++;
+		}
+	}
+	return start.len - in.len;
+}
+
+struct record_vtable obj_record_vtable = {
+	.key = &obj_record_key,
+	.type = &obj_record_type,
+	.copy_from = &obj_record_copy_from,
+	.val_type = &obj_record_val_type,
+	.encode = &obj_record_encode,
+	.decode = &obj_record_decode,
+	.clear = &obj_record_clear,
+};
+
+void log_record_print(struct log_record *log, int hash_size)
+{
+	char hex[SHA256_SIZE + 1] = {};
+
+	printf("log{%s(%" PRIdMAX ") %s <%s> %lu %04d\n", log->ref_name,
+	       log->update_index, log->name, log->email, log->time,
+	       log->tz_offset);
+	hex_format(hex, log->old_hash, hash_size);
+	printf("%s => ", hex);
+	hex_format(hex, log->new_hash, hash_size);
+	printf("%s\n\n%s\n}\n", hex, log->message);
+}
+
+static byte log_record_type(void)
+{
+	return BLOCK_TYPE_LOG;
+}
+
+static void log_record_key(const void *r, struct slice *dest)
+{
+	const struct log_record *rec = (const struct log_record *)r;
+	int len = strlen(rec->ref_name);
+	uint64_t ts = 0;
+	slice_resize(dest, len + 9);
+	memcpy(dest->buf, rec->ref_name, len + 1);
+	ts = (~ts) - rec->update_index;
+	put_u64(dest->buf + 1 + len, ts);
+}
+
+static void log_record_copy_from(void *rec, const void *src_rec, int hash_size)
+{
+	struct log_record *dst = (struct log_record *)rec;
+	const struct log_record *src = (const struct log_record *)src_rec;
+
+	*dst = *src;
+	dst->ref_name = strdup(dst->ref_name);
+	dst->email = strdup(dst->email);
+	dst->name = strdup(dst->name);
+	dst->message = strdup(dst->message);
+	if (dst->new_hash != NULL) {
+		dst->new_hash = malloc(hash_size);
+		memcpy(dst->new_hash, src->new_hash, hash_size);
+	}
+	if (dst->old_hash != NULL) {
+		dst->old_hash = malloc(hash_size);
+		memcpy(dst->old_hash, src->old_hash, hash_size);
+	}
+}
+
+static void log_record_clear_void(void *rec)
+{
+	struct log_record *r = (struct log_record *)rec;
+	log_record_clear(r);
+}
+
+void log_record_clear(struct log_record *r)
+{
+	free(r->ref_name);
+	free(r->new_hash);
+	free(r->old_hash);
+	free(r->name);
+	free(r->email);
+	free(r->message);
+	memset(r, 0, sizeof(struct log_record));
+}
+
+static byte log_record_val_type(const void *rec)
+{
+	return 1;
+}
+
+static byte zero[SHA256_SIZE] = {};
+
+static int log_record_encode(const void *rec, struct slice s, int hash_size)
+{
+	struct log_record *r = (struct log_record *)rec;
+	struct slice start = s;
+	int n = 0;
+	byte *oldh = r->old_hash;
+	byte *newh = r->new_hash;
+	if (oldh == NULL) {
+		oldh = zero;
+	}
+	if (newh == NULL) {
+		newh = zero;
+	}
+
+	if (s.len < 2 * hash_size) {
+		return -1;
+	}
+
+	memcpy(s.buf, oldh, hash_size);
+	memcpy(s.buf + hash_size, newh, hash_size);
+	s.buf += 2 * hash_size;
+	s.len -= 2 * hash_size;
+
+	n = encode_string(r->name ? r->name : "", s);
+	if (n < 0) {
+		return -1;
+	}
+	s.len -= n;
+	s.buf += n;
+
+	n = encode_string(r->email ? r->email : "", s);
+	if (n < 0) {
+		return -1;
+	}
+	s.len -= n;
+	s.buf += n;
+
+	n = put_var_int(s, r->time);
+	if (n < 0) {
+		return -1;
+	}
+	s.buf += n;
+	s.len -= n;
+
+	if (s.len < 2) {
+		return -1;
+	}
+
+	put_u16(s.buf, r->tz_offset);
+	s.buf += 2;
+	s.len -= 2;
+
+	n = encode_string(r->message ? r->message : "", s);
+	if (n < 0) {
+		return -1;
+	}
+	s.len -= n;
+	s.buf += n;
+
+	return start.len - s.len;
+}
+
+static int log_record_decode(void *rec, struct slice key, byte val_type,
+			     struct slice in, int hash_size)
+{
+	struct slice start = in;
+	struct log_record *r = (struct log_record *)rec;
+	uint64_t max = 0;
+	uint64_t ts = 0;
+	struct slice dest = {};
+	int n;
+
+	if (key.len <= 9 || key.buf[key.len - 9] != 0) {
+		return FORMAT_ERROR;
+	}
+
+	r->ref_name = realloc(r->ref_name, key.len - 8);
+	memcpy(r->ref_name, key.buf, key.len - 8);
+	ts = get_u64(key.buf + key.len - 8);
+
+	r->update_index = (~max) - ts;
+
+	if (in.len < 2 * hash_size) {
+		return FORMAT_ERROR;
+	}
+
+	r->old_hash = realloc(r->old_hash, hash_size);
+	r->new_hash = realloc(r->new_hash, hash_size);
+
+	memcpy(r->old_hash, in.buf, hash_size);
+	memcpy(r->new_hash, in.buf + hash_size, hash_size);
+
+	in.buf += 2 * hash_size;
+	in.len -= 2 * hash_size;
+
+	n = decode_string(&dest, in);
+	if (n < 0) {
+		goto error;
+	}
+	in.len -= n;
+	in.buf += n;
+
+	r->name = realloc(r->name, dest.len + 1);
+	memcpy(r->name, dest.buf, dest.len);
+	r->name[dest.len] = 0;
+
+	slice_resize(&dest, 0);
+	n = decode_string(&dest, in);
+	if (n < 0) {
+		goto error;
+	}
+	in.len -= n;
+	in.buf += n;
+
+	r->email = realloc(r->email, dest.len + 1);
+	memcpy(r->email, dest.buf, dest.len);
+	r->email[dest.len] = 0;
+
+	ts = 0;
+	n = get_var_int(&ts, in);
+	if (n < 0) {
+		goto error;
+	}
+	in.len -= n;
+	in.buf += n;
+	r->time = ts;
+	if (in.len < 2) {
+		goto error;
+	}
+
+	r->tz_offset = get_u16(in.buf);
+	in.buf += 2;
+	in.len -= 2;
+
+	slice_resize(&dest, 0);
+	n = decode_string(&dest, in);
+	if (n < 0) {
+		goto error;
+	}
+	in.len -= n;
+	in.buf += n;
+
+	r->message = realloc(r->message, dest.len + 1);
+	memcpy(r->message, dest.buf, dest.len);
+	r->message[dest.len] = 0;
+
+	return start.len - in.len;
+
+error:
+	free(slice_yield(&dest));
+	return FORMAT_ERROR;
+}
+
+static bool null_streq(char *a, char *b)
+{
+	char *empty = "";
+	if (a == NULL) {
+		a = empty;
+	}
+	if (b == NULL) {
+		b = empty;
+	}
+	return 0 == strcmp(a, b);
+}
+
+static bool zero_hash_eq(byte *a, byte *b, int sz)
+{
+	if (a == NULL) {
+		a = zero;
+	}
+	if (b == NULL) {
+		b = zero;
+	}
+	return !memcmp(a, b, sz);
+}
+
+bool log_record_equal(struct log_record *a, struct log_record *b, int hash_size)
+{
+	return null_streq(a->name, b->name) && null_streq(a->email, b->email) &&
+	       null_streq(a->message, b->message) &&
+	       zero_hash_eq(a->old_hash, b->old_hash, hash_size) &&
+	       zero_hash_eq(a->new_hash, b->new_hash, hash_size) &&
+	       a->time == b->time && a->tz_offset == b->tz_offset &&
+	       a->update_index == b->update_index;
+}
+
+struct record_vtable log_record_vtable = {
+	.key = &log_record_key,
+	.type = &log_record_type,
+	.copy_from = &log_record_copy_from,
+	.val_type = &log_record_val_type,
+	.encode = &log_record_encode,
+	.decode = &log_record_decode,
+	.clear = &log_record_clear_void,
+};
+
+struct record new_record(byte typ)
+{
+	struct record rec;
+	switch (typ) {
+	case BLOCK_TYPE_REF: {
+		struct ref_record *r = calloc(1, sizeof(struct ref_record));
+		record_from_ref(&rec, r);
+		return rec;
+	}
+
+	case BLOCK_TYPE_OBJ: {
+		struct obj_record *r = calloc(1, sizeof(struct obj_record));
+		record_from_obj(&rec, r);
+		return rec;
+	}
+	case BLOCK_TYPE_LOG: {
+		struct log_record *r = calloc(1, sizeof(struct log_record));
+		record_from_log(&rec, r);
+		return rec;
+	}
+	case BLOCK_TYPE_INDEX: {
+		struct index_record *r = calloc(1, sizeof(struct index_record));
+		record_from_index(&rec, r);
+		return rec;
+	}
+	}
+	abort();
+	return rec;
+}
+
+static byte index_record_type(void)
+{
+	return BLOCK_TYPE_INDEX;
+}
+
+static void index_record_key(const void *r, struct slice *dest)
+{
+	struct index_record *rec = (struct index_record *)r;
+	slice_copy(dest, rec->last_key);
+}
+
+static void index_record_copy_from(void *rec, const void *src_rec,
+				   int hash_size)
+{
+	struct index_record *dst = (struct index_record *)rec;
+	struct index_record *src = (struct index_record *)src_rec;
+
+	slice_copy(&dst->last_key, src->last_key);
+	dst->offset = src->offset;
+}
+
+static void index_record_clear(void *rec)
+{
+	struct index_record *idx = (struct index_record *)rec;
+	free(slice_yield(&idx->last_key));
+}
+
+static byte index_record_val_type(const void *rec)
+{
+	return 0;
+}
+
+static int index_record_encode(const void *rec, struct slice out, int hash_size)
+{
+	const struct index_record *r = (const struct index_record *)rec;
+	struct slice start = out;
+
+	int n = put_var_int(out, r->offset);
+	if (n < 0) {
+		return n;
+	}
+
+	out.buf += n;
+	out.len -= n;
+
+	return start.len - out.len;
+}
+
+static int index_record_decode(void *rec, struct slice key, byte val_type,
+			       struct slice in, int hash_size)
+{
+	struct slice start = in;
+	struct index_record *r = (struct index_record *)rec;
+	int n = 0;
+
+	slice_copy(&r->last_key, key);
+
+	n = get_var_int(&r->offset, in);
+	if (n < 0) {
+		return n;
+	}
+
+	in.buf += n;
+	in.len -= n;
+	return start.len - in.len;
+}
+
+struct record_vtable index_record_vtable = {
+	.key = &index_record_key,
+	.type = &index_record_type,
+	.copy_from = &index_record_copy_from,
+	.val_type = &index_record_val_type,
+	.encode = &index_record_encode,
+	.decode = &index_record_decode,
+	.clear = &index_record_clear,
+};
+
+void record_key(struct record rec, struct slice *dest)
+{
+	rec.ops->key(rec.data, dest);
+}
+
+byte record_type(struct record rec)
+{
+	return rec.ops->type();
+}
+
+int record_encode(struct record rec, struct slice dest, int hash_size)
+{
+	return rec.ops->encode(rec.data, dest, hash_size);
+}
+
+void record_copy_from(struct record rec, struct record src, int hash_size)
+{
+	assert(src.ops->type() == rec.ops->type());
+
+	rec.ops->copy_from(rec.data, src.data, hash_size);
+}
+
+byte record_val_type(struct record rec)
+{
+	return rec.ops->val_type(rec.data);
+}
+
+int record_decode(struct record rec, struct slice key, byte extra,
+		  struct slice src, int hash_size)
+{
+	return rec.ops->decode(rec.data, key, extra, src, hash_size);
+}
+
+void record_clear(struct record rec)
+{
+	return rec.ops->clear(rec.data);
+}
+
+void record_from_ref(struct record *rec, struct ref_record *ref_rec)
+{
+	rec->data = ref_rec;
+	rec->ops = &ref_record_vtable;
+}
+
+void record_from_obj(struct record *rec, struct obj_record *obj_rec)
+{
+	rec->data = obj_rec;
+	rec->ops = &obj_record_vtable;
+}
+
+void record_from_index(struct record *rec, struct index_record *index_rec)
+{
+	rec->data = index_rec;
+	rec->ops = &index_record_vtable;
+}
+
+void record_from_log(struct record *rec, struct log_record *log_rec)
+{
+	rec->data = log_rec;
+	rec->ops = &log_record_vtable;
+}
+
+void *record_yield(struct record *rec)
+{
+	void *p = rec->data;
+	rec->data = NULL;
+	return p;
+}
+
+struct ref_record *record_as_ref(struct record rec)
+{
+	assert(record_type(rec) == BLOCK_TYPE_REF);
+	return (struct ref_record *)rec.data;
+}
+
+static bool hash_equal(byte *a, byte *b, int hash_size)
+{
+	if (a != NULL && b != NULL) {
+		return !memcmp(a, b, hash_size);
+	}
+
+	return a == b;
+}
+
+static bool str_equal(char *a, char *b)
+{
+	if (a != NULL && b != NULL) {
+		return 0 == strcmp(a, b);
+	}
+
+	return a == b;
+}
+
+bool ref_record_equal(struct ref_record *a, struct ref_record *b, int hash_size)
+{
+	assert(hash_size > 0);
+	return 0 == strcmp(a->ref_name, b->ref_name) &&
+	       a->update_index == b->update_index &&
+	       hash_equal(a->value, b->value, hash_size) &&
+	       hash_equal(a->target_value, b->target_value, hash_size) &&
+	       str_equal(a->target, b->target);
+}
+
+int ref_record_compare_name(const void *a, const void *b)
+{
+	return strcmp(((struct ref_record *)a)->ref_name,
+		      ((struct ref_record *)b)->ref_name);
+}
+
+bool ref_record_is_deletion(const struct ref_record *ref)
+{
+	return ref->value == NULL && ref->target == NULL &&
+	       ref->target_value == NULL;
+}
+
+int log_record_compare_key(const void *a, const void *b)
+{
+	struct log_record *la = (struct log_record *)a;
+	struct log_record *lb = (struct log_record *)b;
+
+	int cmp = strcmp(la->ref_name, lb->ref_name);
+	if (cmp) {
+		return cmp;
+	}
+	if (la->update_index > lb->update_index) {
+		return -1;
+	}
+	return (la->update_index < lb->update_index) ? 1 : 0;
+}
+
+bool log_record_is_deletion(const struct log_record *log)
+{
+	/* XXX */
+	return false;
+}
diff --git a/reftable/record.h b/reftable/record.h
new file mode 100644
index 0000000000..dffdd71fc2
--- /dev/null
+++ b/reftable/record.h
@@ -0,0 +1,79 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef RECORD_H
+#define RECORD_H
+
+#include "reftable.h"
+#include "slice.h"
+
+struct record_vtable {
+	void (*key)(const void *rec, struct slice *dest);
+	byte (*type)(void);
+	void (*copy_from)(void *rec, const void *src, int hash_size);
+	byte (*val_type)(const void *rec);
+	int (*encode)(const void *rec, struct slice dest, int hash_size);
+	int (*decode)(void *rec, struct slice key, byte extra, struct slice src,
+		      int hash_size);
+	void (*clear)(void *rec);
+};
+
+/* record is a generic wrapper for differnt types of records. */
+struct record {
+	void *data;
+	struct record_vtable *ops;
+};
+
+int get_var_int(uint64_t *dest, struct slice in);
+int put_var_int(struct slice dest, uint64_t val);
+int common_prefix_size(struct slice a, struct slice b);
+
+int is_block_type(byte typ);
+struct record new_record(byte typ);
+
+extern struct record_vtable ref_record_vtable;
+
+int encode_key(bool *restart, struct slice dest, struct slice prev_key,
+	       struct slice key, byte extra);
+int decode_key(struct slice *key, byte *extra, struct slice last_key,
+	       struct slice in);
+
+struct index_record {
+	struct slice last_key;
+	uint64_t offset;
+};
+
+struct obj_record {
+	byte *hash_prefix;
+	int hash_prefix_len;
+	uint64_t *offsets;
+	int offset_len;
+};
+
+void record_key(struct record rec, struct slice *dest);
+byte record_type(struct record rec);
+void record_copy_from(struct record rec, struct record src, int hash_size);
+byte record_val_type(struct record rec);
+int record_encode(struct record rec, struct slice dest, int hash_size);
+int record_decode(struct record rec, struct slice key, byte extra,
+		  struct slice src, int hash_size);
+void record_clear(struct record rec);
+void *record_yield(struct record *rec);
+void record_from_obj(struct record *rec, struct obj_record *objrec);
+void record_from_index(struct record *rec, struct index_record *idxrec);
+void record_from_ref(struct record *rec, struct ref_record *refrec);
+void record_from_log(struct record *rec, struct log_record *logrec);
+struct ref_record *record_as_ref(struct record ref);
+
+/* for qsort. */
+int ref_record_compare_name(const void *a, const void *b);
+
+/* for qsort. */
+int log_record_compare_key(const void *a, const void *b);
+
+#endif
diff --git a/reftable/record_test.c b/reftable/record_test.c
new file mode 100644
index 0000000000..b95ac6aa1a
--- /dev/null
+++ b/reftable/record_test.c
@@ -0,0 +1,332 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "record.h"
+
+#include "system.h"
+
+#include "basics.h"
+#include "constants.h"
+#include "reftable.h"
+#include "test_framework.h"
+
+void varint_roundtrip()
+{
+	uint64_t inputs[] = { 0,
+			      1,
+			      27,
+			      127,
+			      128,
+			      257,
+			      4096,
+			      ((uint64_t)1 << 63),
+			      ((uint64_t)1 << 63) + ((uint64_t)1 << 63) - 1 };
+	int i = 0;
+	for (i = 0; i < ARRAYSIZE(inputs); i++) {
+		byte dest[10];
+
+		struct slice out = { .buf = dest, .len = 10, .cap = 10 };
+
+		uint64_t in = inputs[i];
+		int n = put_var_int(out, in);
+		assert(n > 0);
+		out.len = n;
+
+		uint64_t got = 0;
+		n = get_var_int(&got, out);
+		assert(n > 0);
+
+		assert(got == in);
+	}
+}
+
+void test_common_prefix()
+{
+	struct {
+		const char *a, *b;
+		int want;
+	} cases[] = {
+		{ "abc", "ab", 2 },
+		{ "", "abc", 0 },
+		{ "abc", "abd", 2 },
+		{ "abc", "pqr", 0 },
+	};
+
+	int i = 0;
+	for (i = 0; i < ARRAYSIZE(cases); i++) {
+		struct slice a = {};
+		struct slice b = {};
+		slice_set_string(&a, cases[i].a);
+		slice_set_string(&b, cases[i].b);
+
+		int got = common_prefix_size(a, b);
+		assert(got == cases[i].want);
+
+		free(slice_yield(&a));
+		free(slice_yield(&b));
+	}
+}
+
+void set_hash(byte *h, int j)
+{
+	int i = 0;
+	for (i = 0; i < SHA1_SIZE; i++) {
+		h[i] = (j >> i) & 0xff;
+	}
+}
+
+void test_ref_record_roundtrip()
+{
+	int i = 0;
+	for (i = 0; i <= 3; i++) {
+		printf("subtest %d\n", i);
+		struct ref_record in = {};
+		switch (i) {
+		case 0:
+			break;
+		case 1:
+			in.value = malloc(SHA1_SIZE);
+			set_hash(in.value, 1);
+			break;
+		case 2:
+			in.value = malloc(SHA1_SIZE);
+			set_hash(in.value, 1);
+			in.target_value = malloc(SHA1_SIZE);
+			set_hash(in.target_value, 2);
+			break;
+		case 3:
+			in.target = strdup("target");
+			break;
+		}
+		in.ref_name = strdup("refs/heads/master");
+
+		struct record rec = {};
+		record_from_ref(&rec, &in);
+		assert(record_val_type(rec) == i);
+		byte buf[1024];
+		struct slice key = {};
+		record_key(rec, &key);
+		struct slice dest = {
+			.buf = buf,
+			.len = sizeof(buf),
+		};
+		int n = record_encode(rec, dest, SHA1_SIZE);
+		assert(n > 0);
+
+		struct ref_record out = {};
+		struct record rec_out = {};
+		record_from_ref(&rec_out, &out);
+		int m = record_decode(rec_out, key, i, dest, SHA1_SIZE);
+		assert(n == m);
+
+		assert((out.value != NULL) == (in.value != NULL));
+		assert((out.target_value != NULL) == (in.target_value != NULL));
+		assert((out.target != NULL) == (in.target != NULL));
+		free(slice_yield(&key));
+		record_clear(rec_out);
+		ref_record_clear(&in);
+	}
+}
+
+void test_log_record_roundtrip()
+{
+	struct log_record in = {
+		.ref_name = strdup("refs/heads/master"),
+		.old_hash = malloc(SHA1_SIZE),
+		.new_hash = malloc(SHA1_SIZE),
+		.name = strdup("han-wen"),
+		.email = strdup("hanwen@google.com"),
+		.message = strdup("test"),
+		.update_index = 42,
+		.time = 1577123507,
+		.tz_offset = 100,
+	};
+
+	struct record rec = {};
+	record_from_log(&rec, &in);
+
+	struct slice key = {};
+	record_key(rec, &key);
+
+	byte buf[1024];
+	struct slice dest = {
+		.buf = buf,
+		.len = sizeof(buf),
+	};
+
+	int n = record_encode(rec, dest, SHA1_SIZE);
+	assert(n > 0);
+
+	struct log_record out = {};
+	struct record rec_out = {};
+	record_from_log(&rec_out, &out);
+	int valtype = record_val_type(rec);
+	int m = record_decode(rec_out, key, valtype, dest, SHA1_SIZE);
+	assert(n == m);
+
+	assert(log_record_equal(&in, &out, SHA1_SIZE));
+	log_record_clear(&in);
+	free(slice_yield(&key));
+	record_clear(rec_out);
+}
+
+void test_u24_roundtrip()
+{
+	uint32_t in = 0x112233;
+	byte dest[3];
+
+	put_u24(dest, in);
+	uint32_t out = get_u24(dest);
+	assert(in == out);
+}
+
+void test_key_roundtrip()
+{
+	struct slice dest = {}, last_key = {}, key = {}, roundtrip = {};
+
+	slice_resize(&dest, 1024);
+	slice_set_string(&last_key, "refs/heads/master");
+	slice_set_string(&key, "refs/tags/bla");
+
+	bool restart;
+	byte extra = 6;
+	int n = encode_key(&restart, dest, last_key, key, extra);
+	assert(!restart);
+	assert(n > 0);
+
+	byte rt_extra;
+	int m = decode_key(&roundtrip, &rt_extra, last_key, dest);
+	assert(n == m);
+	assert(slice_equal(key, roundtrip));
+	assert(rt_extra == extra);
+
+	free(slice_yield(&last_key));
+	free(slice_yield(&key));
+	free(slice_yield(&dest));
+	free(slice_yield(&roundtrip));
+}
+
+void print_bytes(byte *p, int l)
+{
+	int i = 0;
+	for (i = 0; i < l; i++) {
+		byte c = *p;
+		if (c < 32) {
+			c = '.';
+		}
+		printf("%02x[%c] ", p[i], c);
+	}
+	printf("(%d)\n", l);
+}
+
+void test_obj_record_roundtrip()
+{
+	byte testHash1[SHA1_SIZE] = {};
+	set_hash(testHash1, 1);
+	uint64_t till9[] = { 1, 2, 3, 4, 500, 600, 700, 800, 9000 };
+
+	struct obj_record recs[3] = { {
+					      .hash_prefix = testHash1,
+					      .hash_prefix_len = 5,
+					      .offsets = till9,
+					      .offset_len = 3,
+				      },
+				      {
+					      .hash_prefix = testHash1,
+					      .hash_prefix_len = 5,
+					      .offsets = till9,
+					      .offset_len = 9,
+				      },
+				      {
+					      .hash_prefix = testHash1,
+					      .hash_prefix_len = 5,
+				      }
+
+	};
+	int i = 0;
+	for (i = 0; i < ARRAYSIZE(recs); i++) {
+		printf("subtest %d\n", i);
+		struct obj_record in = recs[i];
+		byte buf[1024];
+		struct record rec = {};
+		record_from_obj(&rec, &in);
+		struct slice key = {};
+		record_key(rec, &key);
+		struct slice dest = {
+			.buf = buf,
+			.len = sizeof(buf),
+		};
+		int n = record_encode(rec, dest, SHA1_SIZE);
+		assert(n > 0);
+		byte extra = record_val_type(rec);
+		struct obj_record out = {};
+		struct record rec_out = {};
+		record_from_obj(&rec_out, &out);
+		int m = record_decode(rec_out, key, extra, dest, SHA1_SIZE);
+		assert(n == m);
+
+		assert(in.hash_prefix_len == out.hash_prefix_len);
+		assert(in.offset_len == out.offset_len);
+
+		assert(!memcmp(in.hash_prefix, out.hash_prefix,
+			       in.hash_prefix_len));
+		assert(0 == memcmp(in.offsets, out.offsets,
+				   sizeof(uint64_t) * in.offset_len));
+		free(slice_yield(&key));
+		record_clear(rec_out);
+	}
+}
+
+void test_index_record_roundtrip()
+{
+	struct index_record in = { .offset = 42 };
+
+	slice_set_string(&in.last_key, "refs/heads/master");
+
+	struct slice key = {};
+	struct record rec = {};
+	record_from_index(&rec, &in);
+	record_key(rec, &key);
+
+	assert(0 == slice_compare(key, in.last_key));
+
+	byte buf[1024];
+	struct slice dest = {
+		.buf = buf,
+		.len = sizeof(buf),
+	};
+	int n = record_encode(rec, dest, SHA1_SIZE);
+	assert(n > 0);
+
+	byte extra = record_val_type(rec);
+	struct index_record out = {};
+	struct record out_rec;
+	record_from_index(&out_rec, &out);
+	int m = record_decode(out_rec, key, extra, dest, SHA1_SIZE);
+	assert(m == n);
+
+	assert(in.offset == out.offset);
+
+	record_clear(out_rec);
+	free(slice_yield(&key));
+	free(slice_yield(&in.last_key));
+}
+
+int main()
+{
+	add_test_case("test_log_record_roundtrip", &test_log_record_roundtrip);
+	add_test_case("test_ref_record_roundtrip", &test_ref_record_roundtrip);
+	add_test_case("varint_roundtrip", &varint_roundtrip);
+	add_test_case("test_key_roundtrip", &test_key_roundtrip);
+	add_test_case("test_common_prefix", &test_common_prefix);
+	add_test_case("test_obj_record_roundtrip", &test_obj_record_roundtrip);
+	add_test_case("test_index_record_roundtrip",
+		      &test_index_record_roundtrip);
+	add_test_case("test_u24_roundtrip", &test_u24_roundtrip);
+	test_main();
+}
diff --git a/reftable/reftable.h b/reftable/reftable.h
new file mode 100644
index 0000000000..d84cc2004a
--- /dev/null
+++ b/reftable/reftable.h
@@ -0,0 +1,399 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef REFTABLE_H
+#define REFTABLE_H
+
+#include "system.h"
+
+typedef uint8_t byte;
+typedef byte bool;
+
+/* block_source is a generic wrapper for a seekable readable file.
+   It is generally passed around by value.
+ */
+struct block_source {
+	struct block_source_vtable *ops;
+	void *arg;
+};
+
+/* a contiguous segment of bytes. It keeps track of its generating block_source
+   so it can return itself into the pool.
+*/
+struct block {
+	byte *data;
+	int len;
+	struct block_source source;
+};
+
+/* block_source_vtable are the operations that make up block_source */
+struct block_source_vtable {
+	/* returns the size of a block source */
+	uint64_t (*size)(void *source);
+
+	/* reads a segment from the block source. It is an error to read
+	   beyond the end of the block */
+	int (*read_block)(void *source, struct block *dest, uint64_t off,
+			  uint32_t size);
+	/* mark the block as read; may return the data back to malloc */
+	void (*return_block)(void *source, struct block *blockp);
+
+	/* release all resources associated with the block source */
+	void (*close)(void *source);
+};
+
+/* opens a file on the file system as a block_source */
+int block_source_from_file(struct block_source *block_src, const char *name);
+
+/* write_options sets options for writing a single reftable. */
+struct write_options {
+	/* do not pad out blocks to block size. */
+	bool unpadded;
+
+	/* the blocksize. Should be less than 2^24. */
+	uint32_t block_size;
+
+	/* do not generate a SHA1 => ref index. */
+	bool skip_index_objects;
+
+	/* how often to write complete keys in each block. */
+	int restart_interval;
+};
+
+/* ref_record holds a ref database entry target_value */
+struct ref_record {
+	char *ref_name; /* Name of the ref, malloced. */
+	uint64_t update_index; /* Logical timestamp at which this value is
+				  written */
+	byte *value; /* SHA1, or NULL. malloced. */
+	byte *target_value; /* peeled annotated tag, or NULL. malloced. */
+	char *target; /* symref, or NULL. malloced. */
+};
+
+/* returns whether 'ref' represents a deletion */
+bool ref_record_is_deletion(const struct ref_record *ref);
+
+/* prints a ref_record onto stdout */
+void ref_record_print(struct ref_record *ref, int hash_size);
+
+/* frees and nulls all pointer values. */
+void ref_record_clear(struct ref_record *ref);
+
+/* returns whether two ref_records are the same */
+bool ref_record_equal(struct ref_record *a, struct ref_record *b,
+		      int hash_size);
+
+/* log_record holds a reflog entry */
+struct log_record {
+	char *ref_name;
+	uint64_t update_index;
+	byte *new_hash;
+	byte *old_hash;
+	char *name;
+	char *email;
+	uint64_t time;
+	int16_t tz_offset;
+	char *message;
+};
+
+/* returns whether 'ref' represents the deletion of a log record. */
+bool log_record_is_deletion(const struct log_record *log);
+
+/* frees and nulls all pointer values. */
+void log_record_clear(struct log_record *log);
+
+/* returns whether two records are equal. */
+bool log_record_equal(struct log_record *a, struct log_record *b,
+		      int hash_size);
+
+void log_record_print(struct log_record *log, int hash_size);
+
+/* iterator is the generic interface for walking over data stored in a
+   reftable. It is generally passed around by value.
+*/
+struct iterator {
+	struct iterator_vtable *ops;
+	void *iter_arg;
+};
+
+/* reads the next ref_record. Returns < 0 for error, 0 for OK and > 0:
+   end of iteration.
+*/
+int iterator_next_ref(struct iterator it, struct ref_record *ref);
+
+/* reads the next log_record. Returns < 0 for error, 0 for OK and > 0:
+   end of iteration.
+*/
+int iterator_next_log(struct iterator it, struct log_record *log);
+
+/* releases resources associated with an iterator. */
+void iterator_destroy(struct iterator *it);
+
+/* block_stats holds statistics for a single block type */
+struct block_stats {
+	/* total number of entries written */
+	int entries;
+	/* total number of key restarts */
+	int restarts;
+	/* total number of blocks */
+	int blocks;
+	/* total number of index blocks */
+	int index_blocks;
+	/* depth of the index */
+	int max_index_level;
+
+	/* offset of the first block for this type */
+	uint64_t offset;
+	/* offset of the top level index block for this type, or 0 if not
+	 * present */
+	uint64_t index_offset;
+};
+
+/* stats holds overall statistics for a single reftable */
+struct stats {
+	/* total number of blocks written. */
+	int blocks;
+	/* stats for ref data */
+	struct block_stats ref_stats;
+	/* stats for the SHA1 to ref map. */
+	struct block_stats obj_stats;
+	/* stats for index blocks */
+	struct block_stats idx_stats;
+	/* stats for log blocks */
+	struct block_stats log_stats;
+
+	/* disambiguation length of shortened object IDs. */
+	int object_id_len;
+};
+
+/* different types of errors */
+
+/* Unexpected file system behavior */
+#define IO_ERROR -2
+
+/* Format inconsistency on reading data
+ */
+#define FORMAT_ERROR -3
+
+/* File does not exist. Returned from block_source_from_file(),  because it
+   needs special handling in stack.
+*/
+#define NOT_EXIST_ERROR -4
+
+/* Trying to write out-of-date data. */
+#define LOCK_ERROR -5
+
+/* Misuse of the API:
+   - on writing a record with NULL ref_name.
+   - on writing a ref_record outside the table limits
+   - on writing a ref or log record before the stack's next_update_index
+   - on reading a ref_record from log iterator, or vice versa.
+ */
+#define API_ERROR -6
+
+/* Decompression error */
+#define ZLIB_ERROR -7
+
+const char *error_str(int err);
+
+/* new_writer creates a new writer */
+struct writer *new_writer(int (*writer_func)(void *, byte *, int),
+			  void *writer_arg, struct write_options *opts);
+
+/* write to a file descriptor. fdp should be an int* pointing to the fd. */
+int fd_writer(void *fdp, byte *data, int size);
+
+/* Set the range of update indices for the records we will add.  When
+   writing a table into a stack, the min should be at least
+   stack_next_update_index(), or API_ERROR is returned.
+ */
+void writer_set_limits(struct writer *w, uint64_t min, uint64_t max);
+
+/* adds a ref_record. Must be called in ascending
+   order. The update_index must be within the limits set by
+   writer_set_limits(), or API_ERROR is returned.
+ */
+int writer_add_ref(struct writer *w, struct ref_record *ref);
+
+/* Convenience function to add multiple refs. Will sort the refs by
+   name before adding. */
+int writer_add_refs(struct writer *w, struct ref_record *refs, int n);
+
+/* adds a log_record. Must be called in ascending order (with more
+   recent log entries first.)
+ */
+int writer_add_log(struct writer *w, struct log_record *log);
+
+/* Convenience function to add multiple logs. Will sort the records by
+   key before adding. */
+int writer_add_logs(struct writer *w, struct log_record *logs, int n);
+
+/* writer_close finalizes the reftable. The writer is retained so statistics can
+ * be inspected. */
+int writer_close(struct writer *w);
+
+/* writer_stats returns the statistics on the reftable being written. */
+struct stats *writer_stats(struct writer *w);
+
+/* writer_free deallocates memory for the writer */
+void writer_free(struct writer *w);
+
+struct reader;
+
+/* new_reader opens a reftable for reading. If successful, returns 0
+ * code and sets pp.  The name is used for creating a
+ * stack. Typically, it is the basename of the file.
+ */
+int new_reader(struct reader **pp, struct block_source, const char *name);
+
+/* reader_seek_ref returns an iterator where 'name' would be inserted in the
+   table.
+
+   example:
+
+   struct reader *r = NULL;
+   int err = new_reader(&r, src, "filename");
+   if (err < 0) { ... }
+   struct iterator it = {};
+   err = reader_seek_ref(r, &it, "refs/heads/master");
+   if (err < 0) { ... }
+   struct ref_record ref = {};
+   while (1) {
+     err = iterator_next_ref(it, &ref);
+     if (err > 0) {
+       break;
+     }
+     if (err < 0) {
+       ..error handling..
+     }
+     ..found..
+   }
+   iterator_destroy(&it);
+   ref_record_clear(&ref);
+ */
+int reader_seek_ref(struct reader *r, struct iterator *it, const char *name);
+
+/* seek to logs for the given name, older than update_index. */
+int reader_seek_log_at(struct reader *r, struct iterator *it, const char *name,
+		       uint64_t update_index);
+
+/* seek to newest log entry for given name. */
+int reader_seek_log(struct reader *r, struct iterator *it, const char *name);
+
+/* closes and deallocates a reader. */
+void reader_free(struct reader *);
+
+/* return an iterator for the refs pointing to oid */
+int reader_refs_for(struct reader *r, struct iterator *it, byte *oid,
+		    int oid_len);
+
+/* return the max_update_index for a table */
+uint64_t reader_max_update_index(struct reader *r);
+
+/* return the min_update_index for a table */
+uint64_t reader_min_update_index(struct reader *r);
+
+/* a merged table is implements seeking/iterating over a stack of tables. */
+struct merged_table;
+
+/* new_merged_table creates a new merged table. It takes ownership of the stack
+   array.
+*/
+int new_merged_table(struct merged_table **dest, struct reader **stack, int n);
+
+/* returns an iterator positioned just before 'name' */
+int merged_table_seek_ref(struct merged_table *mt, struct iterator *it,
+			  const char *name);
+
+/* returns an iterator for log entry, at given update_index */
+int merged_table_seek_log_at(struct merged_table *mt, struct iterator *it,
+			     const char *name, uint64_t update_index);
+
+/* like merged_table_seek_log_at but look for the newest entry. */
+int merged_table_seek_log(struct merged_table *mt, struct iterator *it,
+			  const char *name);
+
+/* returns the max update_index covered by this merged table. */
+uint64_t merged_max_update_index(struct merged_table *mt);
+
+/* returns the min update_index covered by this merged table. */
+uint64_t merged_min_update_index(struct merged_table *mt);
+
+/* closes readers for the merged tables */
+void merged_table_close(struct merged_table *mt);
+
+/* releases memory for the merged_table */
+void merged_table_free(struct merged_table *m);
+
+/* a stack is a stack of reftables, which can be mutated by pushing a table to
+ * the top of the stack */
+struct stack;
+
+/* open a new reftable stack. The tables will be stored in 'dir', while the list
+   of tables is in 'list_file'. Typically, this should be .git/reftables and
+   .git/refs respectively.
+*/
+int new_stack(struct stack **dest, const char *dir, const char *list_file,
+	      struct write_options config);
+
+/* returns the update_index at which a next table should be written. */
+uint64_t stack_next_update_index(struct stack *st);
+
+/* add a new table to the stack. The write_table function must call
+   writer_set_limits, add refs and return an error value. */
+int stack_add(struct stack *st,
+	      int (*write_table)(struct writer *wr, void *write_arg),
+	      void *write_arg);
+
+/* returns the merged_table for seeking. This table is valid until the
+   next write or reload, and should not be closed or deleted.
+*/
+struct merged_table *stack_merged_table(struct stack *st);
+
+/* frees all resources associated with the stack. */
+void stack_destroy(struct stack *st);
+
+/* reloads the stack if necessary. */
+int stack_reload(struct stack *st);
+
+/* Policy for expiring reflog entries. */
+struct log_expiry_config {
+	/* Drop entries older than this timestamp */
+	uint64_t time;
+
+	/* Drop older entries */
+	uint64_t min_update_index;
+};
+
+/* compacts all reftables into a giant table. Expire reflog entries if config is
+ * non-NULL */
+int stack_compact_all(struct stack *st, struct log_expiry_config *config);
+
+/* heuristically compact unbalanced table stack. */
+int stack_auto_compact(struct stack *st);
+
+/* convenience function to read a single ref. Returns < 0 for error, 0
+   for success, and 1 if ref not found. */
+int stack_read_ref(struct stack *st, const char *refname,
+		   struct ref_record *ref);
+
+/* convenience function to read a single log. Returns < 0 for error, 0
+   for success, and 1 if ref not found. */
+int stack_read_log(struct stack *st, const char *refname,
+		   struct log_record *log);
+
+/* statistics on past compactions. */
+struct compaction_stats {
+	uint64_t bytes;
+	int attempts;
+	int failures;
+};
+
+struct compaction_stats *stack_compaction_stats(struct stack *st);
+
+#endif
diff --git a/reftable/reftable_test.c b/reftable/reftable_test.c
new file mode 100644
index 0000000000..1e89d37d9d
--- /dev/null
+++ b/reftable/reftable_test.c
@@ -0,0 +1,481 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "reftable.h"
+
+#include "system.h"
+
+#include "basics.h"
+#include "block.h"
+#include "constants.h"
+#include "reader.h"
+#include "record.h"
+#include "test_framework.h"
+
+static const int update_index = 5;
+
+void test_buffer(void)
+{
+	struct slice buf = {};
+
+	byte in[] = "hello";
+	slice_write(&buf, in, sizeof(in));
+	struct block_source source;
+	block_source_from_slice(&source, &buf);
+	assert(block_source_size(source) == 6);
+	struct block out = {};
+	int n = block_source_read_block(source, &out, 0, sizeof(in));
+	assert(n == sizeof(in));
+	assert(!memcmp(in, out.data, n));
+	block_source_return_block(source, &out);
+
+	n = block_source_read_block(source, &out, 1, 2);
+	assert(n == 2);
+	assert(!memcmp(out.data, "el", 2));
+
+	block_source_return_block(source, &out);
+	block_source_close(&source);
+	free(slice_yield(&buf));
+}
+
+void write_table(char ***names, struct slice *buf, int N, int block_size)
+{
+	*names = calloc(sizeof(char *), N + 1);
+
+	struct write_options opts = {
+		.block_size = block_size,
+	};
+
+	struct writer *w = new_writer(&slice_write_void, buf, &opts);
+
+	writer_set_limits(w, update_index, update_index);
+	{
+		struct ref_record ref = {};
+		int i = 0;
+		for (i = 0; i < N; i++) {
+			byte hash[SHA1_SIZE];
+			set_test_hash(hash, i);
+
+			char name[100];
+			snprintf(name, sizeof(name), "refs/heads/branch%02d",
+				 i);
+
+			ref.ref_name = name;
+			ref.value = hash;
+			ref.update_index = update_index;
+			(*names)[i] = strdup(name);
+
+			int n = writer_add_ref(w, &ref);
+			assert(n == 0);
+		}
+	}
+	{
+		struct log_record log = {};
+		int i = 0;
+		for (i = 0; i < N; i++) {
+			byte hash[SHA1_SIZE];
+			set_test_hash(hash, i);
+
+			char name[100];
+			snprintf(name, sizeof(name), "refs/heads/branch%02d",
+				 i);
+
+			log.ref_name = name;
+			log.new_hash = hash;
+			log.update_index = update_index;
+			log.message = "message";
+
+			int n = writer_add_log(w, &log);
+			assert(n == 0);
+		}
+	}
+
+	int n = writer_close(w);
+	assert(n == 0);
+
+	struct stats *stats = writer_stats(w);
+	int i = 0;
+	for (i = 0; i < stats->ref_stats.blocks; i++) {
+		int off = i * opts.block_size;
+		if (off == 0) {
+			off = HEADER_SIZE;
+		}
+		assert(buf->buf[off] == 'r');
+	}
+
+	writer_free(w);
+	w = NULL;
+}
+
+void test_log_write_read(void)
+{
+	int N = 2;
+	char **names = calloc(sizeof(char *), N + 1);
+
+	struct write_options opts = {
+		.block_size = 256,
+	};
+
+	struct slice buf = {};
+	struct writer *w = new_writer(&slice_write_void, &buf, &opts);
+
+	writer_set_limits(w, 0, N);
+	{
+		struct ref_record ref = {};
+		int i = 0;
+		for (i = 0; i < N; i++) {
+			char name[256];
+			snprintf(name, sizeof(name), "b%02d%0*d", i, 130, 7);
+			names[i] = strdup(name);
+			puts(name);
+			ref.ref_name = name;
+			ref.update_index = i;
+
+			int err = writer_add_ref(w, &ref);
+			assert_err(err);
+		}
+	}
+
+	{
+		struct log_record log = {};
+		int i = 0;
+		for (i = 0; i < N; i++) {
+			byte hash1[SHA1_SIZE], hash2[SHA1_SIZE];
+			set_test_hash(hash1, i);
+			set_test_hash(hash2, i + 1);
+
+			log.ref_name = names[i];
+			log.update_index = i;
+			log.old_hash = hash1;
+			log.new_hash = hash2;
+
+			int err = writer_add_log(w, &log);
+			assert_err(err);
+		}
+	}
+
+	int n = writer_close(w);
+	assert(n == 0);
+
+	struct stats *stats = writer_stats(w);
+	assert(stats->log_stats.blocks > 0);
+	writer_free(w);
+	w = NULL;
+
+	struct block_source source = {};
+	block_source_from_slice(&source, &buf);
+
+	struct reader rd = {};
+	int err = init_reader(&rd, source, "file.log");
+	assert(err == 0);
+
+	{
+		struct iterator it = {};
+		err = reader_seek_ref(&rd, &it, names[N - 1]);
+		assert(err == 0);
+
+		struct ref_record ref = {};
+		err = iterator_next_ref(it, &ref);
+		assert_err(err);
+
+		/* end of iteration. */
+		err = iterator_next_ref(it, &ref);
+		assert(0 < err);
+
+		iterator_destroy(&it);
+		ref_record_clear(&ref);
+	}
+
+	{
+		struct iterator it = {};
+		err = reader_seek_log(&rd, &it, "");
+		assert(err == 0);
+
+		struct log_record log = {};
+		int i = 0;
+		while (true) {
+			int err = iterator_next_log(it, &log);
+			if (err > 0) {
+				break;
+			}
+
+			assert_err(err);
+			assert_streq(names[i], log.ref_name);
+			assert(i == log.update_index);
+			i++;
+		}
+
+		assert(i == N);
+		iterator_destroy(&it);
+	}
+
+	/* cleanup. */
+	free(slice_yield(&buf));
+	free_names(names);
+	reader_close(&rd);
+}
+
+void test_table_read_write_sequential(void)
+{
+	char **names;
+	struct slice buf = {};
+	int N = 50;
+	write_table(&names, &buf, N, 256);
+
+	struct block_source source = {};
+	block_source_from_slice(&source, &buf);
+
+	struct reader rd = {};
+	int err = init_reader(&rd, source, "file.ref");
+	assert(err == 0);
+
+	struct iterator it = {};
+	err = reader_seek_ref(&rd, &it, "");
+	assert(err == 0);
+
+	int j = 0;
+	while (true) {
+		struct ref_record ref = {};
+		int r = iterator_next_ref(it, &ref);
+		assert(r >= 0);
+		if (r > 0) {
+			break;
+		}
+		assert(0 == strcmp(names[j], ref.ref_name));
+		assert(update_index == ref.update_index);
+
+		j++;
+		ref_record_clear(&ref);
+	}
+	assert(j == N);
+	iterator_destroy(&it);
+	free(slice_yield(&buf));
+	free_names(names);
+
+	reader_close(&rd);
+}
+
+void test_table_write_small_table(void)
+{
+	char **names;
+	struct slice buf = {};
+	int N = 1;
+	write_table(&names, &buf, N, 4096);
+	assert(buf.len < 200);
+	free(slice_yield(&buf));
+	free_names(names);
+}
+
+void test_table_read_api(void)
+{
+	char **names;
+	struct slice buf = {};
+	int N = 50;
+	write_table(&names, &buf, N, 256);
+
+	struct reader rd = {};
+	struct block_source source = {};
+	block_source_from_slice(&source, &buf);
+
+	int err = init_reader(&rd, source, "file.ref");
+	assert(err == 0);
+
+	struct iterator it = {};
+	err = reader_seek_ref(&rd, &it, names[0]);
+	assert(err == 0);
+
+	struct log_record log = {};
+	err = iterator_next_log(it, &log);
+	assert(err == API_ERROR);
+
+	free(slice_yield(&buf));
+	int i = 0;
+	for (i = 0; i < N; i++) {
+		free(names[i]);
+	}
+	free(names);
+	reader_close(&rd);
+}
+
+void test_table_read_write_seek(bool index)
+{
+	char **names;
+	struct slice buf = {};
+	int N = 50;
+	write_table(&names, &buf, N, 256);
+
+	struct reader rd = {};
+	struct block_source source = {};
+	block_source_from_slice(&source, &buf);
+
+	int err = init_reader(&rd, source, "file.ref");
+	assert(err == 0);
+
+	if (!index) {
+		rd.ref_offsets.index_offset = 0;
+	}
+
+	int i = 0;
+	for (i = 1; i < N; i++) {
+		struct iterator it = {};
+		int err = reader_seek_ref(&rd, &it, names[i]);
+		assert(err == 0);
+		struct ref_record ref = {};
+		err = iterator_next_ref(it, &ref);
+		assert(err == 0);
+		assert(0 == strcmp(names[i], ref.ref_name));
+		assert(i == ref.value[0]);
+
+		ref_record_clear(&ref);
+		iterator_destroy(&it);
+	}
+
+	free(slice_yield(&buf));
+	for (i = 0; i < N; i++) {
+		free(names[i]);
+	}
+	free(names);
+	reader_close(&rd);
+}
+
+void test_table_read_write_seek_linear(void)
+{
+	test_table_read_write_seek(false);
+}
+
+void test_table_read_write_seek_index(void)
+{
+	test_table_read_write_seek(true);
+}
+
+void test_table_refs_for(bool indexed)
+{
+	int N = 50;
+
+	char **want_names = calloc(sizeof(char *), N + 1);
+
+	int want_names_len = 0;
+	byte want_hash[SHA1_SIZE];
+	set_test_hash(want_hash, 4);
+
+	struct write_options opts = {
+		.block_size = 256,
+	};
+
+	struct slice buf = {};
+	struct writer *w = new_writer(&slice_write_void, &buf, &opts);
+	{
+		struct ref_record ref = {};
+		int i = 0;
+		for (i = 0; i < N; i++) {
+			byte hash[SHA1_SIZE];
+			memset(hash, i, sizeof(hash));
+			char fill[51] = {};
+			memset(fill, 'x', 50);
+			char name[100];
+			/* Put the variable part in the start */
+			snprintf(name, sizeof(name), "br%02d%s", i, fill);
+			name[40] = 0;
+			ref.ref_name = name;
+
+			byte hash1[SHA1_SIZE];
+			byte hash2[SHA1_SIZE];
+
+			set_test_hash(hash1, i / 4);
+			set_test_hash(hash2, 3 + i / 4);
+			ref.value = hash1;
+			ref.target_value = hash2;
+
+			/* 80 bytes / entry, so 3 entries per block. Yields 17 */
+			/* blocks. */
+			int n = writer_add_ref(w, &ref);
+			assert(n == 0);
+
+			if (!memcmp(hash1, want_hash, SHA1_SIZE) ||
+			    !memcmp(hash2, want_hash, SHA1_SIZE)) {
+				want_names[want_names_len++] = strdup(name);
+			}
+		}
+	}
+
+	int n = writer_close(w);
+	assert(n == 0);
+
+	writer_free(w);
+	w = NULL;
+
+	struct reader rd;
+	struct block_source source = {};
+	block_source_from_slice(&source, &buf);
+
+	int err = init_reader(&rd, source, "file.ref");
+	assert(err == 0);
+	if (!indexed) {
+		rd.obj_offsets.present = 0;
+	}
+
+	struct iterator it = {};
+	err = reader_seek_ref(&rd, &it, "");
+	assert(err == 0);
+	iterator_destroy(&it);
+
+	err = reader_refs_for(&rd, &it, want_hash, SHA1_SIZE);
+	assert(err == 0);
+
+	struct ref_record ref = {};
+
+	int j = 0;
+	while (true) {
+		int err = iterator_next_ref(it, &ref);
+		assert(err >= 0);
+		if (err > 0) {
+			break;
+		}
+
+		assert(j < want_names_len);
+		assert(0 == strcmp(ref.ref_name, want_names[j]));
+		j++;
+		ref_record_clear(&ref);
+	}
+	assert(j == want_names_len);
+
+	free(slice_yield(&buf));
+	free_names(want_names);
+	iterator_destroy(&it);
+	reader_close(&rd);
+}
+
+void test_table_refs_for_no_index(void)
+{
+	test_table_refs_for(false);
+}
+
+void test_table_refs_for_obj_index(void)
+{
+	test_table_refs_for(true);
+}
+
+int main()
+{
+	add_test_case("test_log_write_read", test_log_write_read);
+	add_test_case("test_table_write_small_table",
+		      &test_table_write_small_table);
+	add_test_case("test_buffer", &test_buffer);
+	add_test_case("test_table_read_api", &test_table_read_api);
+	add_test_case("test_table_read_write_sequential",
+		      &test_table_read_write_sequential);
+	add_test_case("test_table_read_write_seek_linear",
+		      &test_table_read_write_seek_linear);
+	add_test_case("test_table_read_write_seek_index",
+		      &test_table_read_write_seek_index);
+	add_test_case("test_table_read_write_refs_for_no_index",
+		      &test_table_refs_for_no_index);
+	add_test_case("test_table_read_write_refs_for_obj_index",
+		      &test_table_refs_for_obj_index);
+	test_main();
+}
diff --git a/reftable/slice.c b/reftable/slice.c
new file mode 100644
index 0000000000..efbe625253
--- /dev/null
+++ b/reftable/slice.c
@@ -0,0 +1,199 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "slice.h"
+
+#include "system.h"
+
+#include "reftable.h"
+
+void slice_set_string(struct slice *s, const char *str)
+{
+	if (str == NULL) {
+		s->len = 0;
+		return;
+	}
+
+	{
+		int l = strlen(str);
+		l++; /* \0 */
+		slice_resize(s, l);
+		memcpy(s->buf, str, l);
+		s->len = l - 1;
+	}
+}
+
+void slice_resize(struct slice *s, int l)
+{
+	if (s->cap < l) {
+		int c = s->cap * 2;
+		if (c < l) {
+			c = l;
+		}
+		s->cap = c;
+		s->buf = realloc(s->buf, s->cap);
+	}
+	s->len = l;
+}
+
+void slice_append_string(struct slice *d, const char *s)
+{
+	int l1 = d->len;
+	int l2 = strlen(s);
+
+	slice_resize(d, l2 + l1);
+	memcpy(d->buf + l1, s, l2);
+}
+
+void slice_append(struct slice *s, struct slice a)
+{
+	int end = s->len;
+	slice_resize(s, s->len + a.len);
+	memcpy(s->buf + end, a.buf, a.len);
+}
+
+byte *slice_yield(struct slice *s)
+{
+	byte *p = s->buf;
+	s->buf = NULL;
+	s->cap = 0;
+	s->len = 0;
+	return p;
+}
+
+void slice_copy(struct slice *dest, struct slice src)
+{
+	slice_resize(dest, src.len);
+	memcpy(dest->buf, src.buf, src.len);
+}
+
+/* return the underlying data as char*. len is left unchanged, but
+   a \0 is added at the end. */
+const char *slice_as_string(struct slice *s)
+{
+	if (s->cap == s->len) {
+		int l = s->len;
+		slice_resize(s, l + 1);
+		s->len = l;
+	}
+	s->buf[s->len] = 0;
+	return (const char *)s->buf;
+}
+
+/* return a newly malloced string for this slice */
+char *slice_to_string(struct slice in)
+{
+	struct slice s = {};
+	slice_resize(&s, in.len + 1);
+	s.buf[in.len] = 0;
+	memcpy(s.buf, in.buf, in.len);
+	return (char *)slice_yield(&s);
+}
+
+bool slice_equal(struct slice a, struct slice b)
+{
+	if (a.len != b.len) {
+		return 0;
+	}
+	return memcmp(a.buf, b.buf, a.len) == 0;
+}
+
+int slice_compare(struct slice a, struct slice b)
+{
+	int min = a.len < b.len ? a.len : b.len;
+	int res = memcmp(a.buf, b.buf, min);
+	if (res != 0) {
+		return res;
+	}
+	if (a.len < b.len) {
+		return -1;
+	} else if (a.len > b.len) {
+		return 1;
+	} else {
+		return 0;
+	}
+}
+
+int slice_write(struct slice *b, byte *data, int sz)
+{
+	if (b->len + sz > b->cap) {
+		int newcap = 2 * b->cap + 1;
+		if (newcap < b->len + sz) {
+			newcap = (b->len + sz);
+		}
+		b->buf = realloc(b->buf, newcap);
+		b->cap = newcap;
+	}
+
+	memcpy(b->buf + b->len, data, sz);
+	b->len += sz;
+	return sz;
+}
+
+int slice_write_void(void *b, byte *data, int sz)
+{
+	return slice_write((struct slice *)b, data, sz);
+}
+
+static uint64_t slice_size(void *b)
+{
+	return ((struct slice *)b)->len;
+}
+
+static void slice_return_block(void *b, struct block *dest)
+{
+	memset(dest->data, 0xff, dest->len);
+	free(dest->data);
+}
+
+static void slice_close(void *b)
+{
+}
+
+static int slice_read_block(void *v, struct block *dest, uint64_t off,
+			    uint32_t size)
+{
+	struct slice *b = (struct slice *)v;
+	assert(off + size <= b->len);
+	dest->data = calloc(size, 1);
+	memcpy(dest->data, b->buf + off, size);
+	dest->len = size;
+	return size;
+}
+
+struct block_source_vtable slice_vtable = {
+	.size = &slice_size,
+	.read_block = &slice_read_block,
+	.return_block = &slice_return_block,
+	.close = &slice_close,
+};
+
+void block_source_from_slice(struct block_source *bs, struct slice *buf)
+{
+	bs->ops = &slice_vtable;
+	bs->arg = buf;
+}
+
+static void malloc_return_block(void *b, struct block *dest)
+{
+	memset(dest->data, 0xff, dest->len);
+	free(dest->data);
+}
+
+struct block_source_vtable malloc_vtable = {
+	.return_block = &malloc_return_block,
+};
+
+struct block_source malloc_block_source_instance = {
+	.ops = &malloc_vtable,
+};
+
+struct block_source malloc_block_source(void)
+{
+	return malloc_block_source_instance;
+}
diff --git a/reftable/slice.h b/reftable/slice.h
new file mode 100644
index 0000000000..f12a6db228
--- /dev/null
+++ b/reftable/slice.h
@@ -0,0 +1,39 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef SLICE_H
+#define SLICE_H
+
+#include "basics.h"
+#include "reftable.h"
+
+struct slice {
+	byte *buf;
+	int len;
+	int cap;
+};
+
+void slice_set_string(struct slice *dest, const char *);
+void slice_append_string(struct slice *dest, const char *);
+char *slice_to_string(struct slice src);
+const char *slice_as_string(struct slice *src);
+bool slice_equal(struct slice a, struct slice b);
+byte *slice_yield(struct slice *s);
+void slice_copy(struct slice *dest, struct slice src);
+void slice_resize(struct slice *s, int l);
+int slice_compare(struct slice a, struct slice b);
+int slice_write(struct slice *b, byte *data, int sz);
+int slice_write_void(void *b, byte *data, int sz);
+void slice_append(struct slice *dest, struct slice add);
+
+struct block_source;
+void block_source_from_slice(struct block_source *bs, struct slice *buf);
+
+struct block_source malloc_block_source(void);
+
+#endif
diff --git a/reftable/slice_test.c b/reftable/slice_test.c
new file mode 100644
index 0000000000..40a391383c
--- /dev/null
+++ b/reftable/slice_test.c
@@ -0,0 +1,38 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "slice.h"
+
+#include "system.h"
+
+#include "basics.h"
+#include "record.h"
+#include "reftable.h"
+#include "test_framework.h"
+
+void test_slice(void)
+{
+	struct slice s = {};
+	slice_set_string(&s, "abc");
+	assert(0 == strcmp("abc", slice_as_string(&s)));
+
+	struct slice t = {};
+	slice_set_string(&t, "pqr");
+
+	slice_append(&s, t);
+	assert(0 == strcmp("abcpqr", slice_as_string(&s)));
+
+	free(slice_yield(&s));
+	free(slice_yield(&t));
+}
+
+int main()
+{
+	add_test_case("test_slice", &test_slice);
+	test_main();
+}
diff --git a/reftable/stack.c b/reftable/stack.c
new file mode 100644
index 0000000000..58b1656364
--- /dev/null
+++ b/reftable/stack.c
@@ -0,0 +1,985 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "stack.h"
+
+#include "system.h"
+#include "merged.h"
+#include "reader.h"
+#include "reftable.h"
+#include "writer.h"
+
+int new_stack(struct stack **dest, const char *dir, const char *list_file,
+	      struct write_options config)
+{
+	struct stack *p = calloc(sizeof(struct stack), 1);
+	int err = 0;
+	*dest = NULL;
+	p->list_file = strdup(list_file);
+	p->reftable_dir = strdup(dir);
+	p->config = config;
+
+	err = stack_reload(p);
+	if (err < 0) {
+		stack_destroy(p);
+	} else {
+		*dest = p;
+	}
+	return err;
+}
+
+static int fread_lines(FILE *f, char ***namesp)
+{
+	long size = 0;
+	int err = fseek(f, 0, SEEK_END);
+	char *buf = NULL;
+	if (err < 0) {
+		err = IO_ERROR;
+		goto exit;
+	}
+	size = ftell(f);
+	if (size < 0) {
+		err = IO_ERROR;
+		goto exit;
+	}
+	err = fseek(f, 0, SEEK_SET);
+	if (err < 0) {
+		err = IO_ERROR;
+		goto exit;
+	}
+
+	buf = malloc(size + 1);
+	if (fread(buf, 1, size, f) != size) {
+		err = IO_ERROR;
+		goto exit;
+	}
+	buf[size] = 0;
+
+	parse_names(buf, size, namesp);
+exit:
+	free(buf);
+	return err;
+}
+
+int read_lines(const char *filename, char ***namesp)
+{
+	FILE *f = fopen(filename, "r");
+	int err = 0;
+	if (f == NULL) {
+		if (errno == ENOENT) {
+			*namesp = calloc(sizeof(char *), 1);
+			return 0;
+		}
+
+		return IO_ERROR;
+	}
+	err = fread_lines(f, namesp);
+	fclose(f);
+	return err;
+}
+
+struct merged_table *stack_merged_table(struct stack *st)
+{
+	return st->merged;
+}
+
+/* Close and free the stack */
+void stack_destroy(struct stack *st)
+{
+	if (st->merged == NULL) {
+		return;
+	}
+
+	merged_table_close(st->merged);
+	merged_table_free(st->merged);
+	st->merged = NULL;
+
+	free(st->list_file);
+	st->list_file = NULL;
+	free(st->reftable_dir);
+	st->reftable_dir = NULL;
+	free(st);
+}
+
+static struct reader **stack_copy_readers(struct stack *st, int cur_len)
+{
+	struct reader **cur = calloc(sizeof(struct reader *), cur_len);
+	int i = 0;
+	for (i = 0; i < cur_len; i++) {
+		cur[i] = st->merged->stack[i];
+	}
+	return cur;
+}
+
+static int stack_reload_once(struct stack *st, char **names, bool reuse_open)
+{
+	int cur_len = st->merged == NULL ? 0 : st->merged->stack_len;
+	struct reader **cur = stack_copy_readers(st, cur_len);
+	int err = 0;
+	int names_len = names_length(names);
+	struct reader **new_tables =
+		malloc(sizeof(struct reader *) * names_len);
+	int new_tables_len = 0;
+	struct merged_table *new_merged = NULL;
+
+	struct slice table_path = {};
+
+	while (*names) {
+		struct reader *rd = NULL;
+		char *name = *names++;
+
+		/* this is linear; we assume compaction keeps the number of
+		   tables under control so this is not quadratic. */
+		int j = 0;
+		for (j = 0; reuse_open && j < cur_len; j++) {
+			if (cur[j] != NULL && 0 == strcmp(cur[j]->name, name)) {
+				rd = cur[j];
+				cur[j] = NULL;
+				break;
+			}
+		}
+
+		if (rd == NULL) {
+			struct block_source src = {};
+			slice_set_string(&table_path, st->reftable_dir);
+			slice_append_string(&table_path, "/");
+			slice_append_string(&table_path, name);
+
+			err = block_source_from_file(
+				&src, slice_as_string(&table_path));
+			if (err < 0) {
+				goto exit;
+			}
+
+			err = new_reader(&rd, src, name);
+			if (err < 0) {
+				goto exit;
+			}
+		}
+
+		new_tables[new_tables_len++] = rd;
+	}
+
+	/* success! */
+	err = new_merged_table(&new_merged, new_tables, new_tables_len);
+	if (err < 0) {
+		goto exit;
+	}
+
+	new_tables = NULL;
+	new_tables_len = 0;
+	if (st->merged != NULL) {
+		merged_table_clear(st->merged);
+		merged_table_free(st->merged);
+	}
+	st->merged = new_merged;
+
+	{
+		int i = 0;
+		for (i = 0; i < cur_len; i++) {
+			if (cur[i] != NULL) {
+				reader_close(cur[i]);
+				reader_free(cur[i]);
+			}
+		}
+	}
+exit:
+	free(slice_yield(&table_path));
+	{
+		int i = 0;
+		for (i = 0; i < new_tables_len; i++) {
+			reader_close(new_tables[i]);
+		}
+	}
+	free(new_tables);
+	free(cur);
+	return err;
+}
+
+/* return negative if a before b. */
+static int tv_cmp(struct timeval *a, struct timeval *b)
+{
+	time_t diff = a->tv_sec - b->tv_sec;
+	int udiff = a->tv_usec - b->tv_usec;
+
+	if (diff != 0) {
+		return diff;
+	}
+
+	return udiff;
+}
+
+static int stack_reload_maybe_reuse(struct stack *st, bool reuse_open)
+{
+	struct timeval deadline = {};
+	int err = gettimeofday(&deadline, NULL);
+	int64_t delay = 0;
+	int tries = 0;
+	if (err < 0) {
+		return err;
+	}
+
+	deadline.tv_sec += 3;
+	while (true) {
+		char **names = NULL;
+		char **names_after = NULL;
+		struct timeval now = {};
+		int err = gettimeofday(&now, NULL);
+		if (err < 0) {
+			return err;
+		}
+
+		/* Only look at deadlines after the first few times. This
+		   simplifies debugging in GDB */
+		tries++;
+		if (tries > 3 && tv_cmp(&now, &deadline) >= 0) {
+			break;
+		}
+
+		err = read_lines(st->list_file, &names);
+		if (err < 0) {
+			free_names(names);
+			return err;
+		}
+		err = stack_reload_once(st, names, reuse_open);
+		if (err == 0) {
+			free_names(names);
+			break;
+		}
+		if (err != NOT_EXIST_ERROR) {
+			free_names(names);
+			return err;
+		}
+
+		err = read_lines(st->list_file, &names_after);
+		if (err < 0) {
+			free_names(names);
+			return err;
+		}
+
+		if (names_equal(names_after, names)) {
+			free_names(names);
+			free_names(names_after);
+			return -1;
+		}
+		free_names(names);
+		free_names(names_after);
+
+		delay = delay + (delay * rand()) / RAND_MAX + 100;
+		usleep(delay);
+	}
+
+	return 0;
+}
+
+int stack_reload(struct stack *st)
+{
+	return stack_reload_maybe_reuse(st, true);
+}
+
+/* -1 = error
+ 0 = up to date
+ 1 = changed. */
+static int stack_uptodate(struct stack *st)
+{
+	char **names = NULL;
+	int err = read_lines(st->list_file, &names);
+	int i = 0;
+	if (err < 0) {
+		return err;
+	}
+
+	for (i = 0; i < st->merged->stack_len; i++) {
+		if (names[i] == NULL) {
+			err = 1;
+			goto exit;
+		}
+
+		if (strcmp(st->merged->stack[i]->name, names[i])) {
+			err = 1;
+			goto exit;
+		}
+	}
+
+	if (names[st->merged->stack_len] != NULL) {
+		err = 1;
+		goto exit;
+	}
+
+exit:
+	free_names(names);
+	return err;
+}
+
+int stack_add(struct stack *st, int (*write)(struct writer *wr, void *arg),
+	      void *arg)
+{
+	int err = stack_try_add(st, write, arg);
+	if (err < 0) {
+		if (err == LOCK_ERROR) {
+			err = stack_reload(st);
+		}
+		return err;
+	}
+
+	return stack_auto_compact(st);
+}
+
+static void format_name(struct slice *dest, uint64_t min, uint64_t max)
+{
+	char buf[100];
+	snprintf(buf, sizeof(buf), "%012lx-%012lx", min, max);
+	slice_set_string(dest, buf);
+}
+
+int stack_try_add(struct stack *st,
+		  int (*write_table)(struct writer *wr, void *arg), void *arg)
+{
+	struct slice lock_name = {};
+	struct slice temp_tab_name = {};
+	struct slice tab_name = {};
+	struct slice next_name = {};
+	struct slice table_list = {};
+	struct writer *wr = NULL;
+	int err = 0;
+	int tab_fd = 0;
+	int lock_fd = 0;
+	uint64_t next_update_index = 0;
+
+	slice_set_string(&lock_name, st->list_file);
+	slice_append_string(&lock_name, ".lock");
+
+	lock_fd = open(slice_as_string(&lock_name), O_EXCL | O_CREAT | O_WRONLY,
+		       0644);
+	if (lock_fd < 0) {
+		if (errno == EEXIST) {
+			err = LOCK_ERROR;
+			goto exit;
+		}
+		err = IO_ERROR;
+		goto exit;
+	}
+
+	err = stack_uptodate(st);
+	if (err < 0) {
+		goto exit;
+	}
+
+	if (err > 1) {
+		err = LOCK_ERROR;
+		goto exit;
+	}
+
+	next_update_index = stack_next_update_index(st);
+
+	slice_resize(&next_name, 0);
+	format_name(&next_name, next_update_index, next_update_index);
+
+	slice_set_string(&temp_tab_name, st->reftable_dir);
+	slice_append_string(&temp_tab_name, "/");
+	slice_append(&temp_tab_name, next_name);
+	slice_append_string(&temp_tab_name, ".temp.XXXXXX");
+
+	tab_fd = mkstemp((char *)slice_as_string(&temp_tab_name));
+	if (tab_fd < 0) {
+		err = IO_ERROR;
+		goto exit;
+	}
+
+	wr = new_writer(fd_writer, &tab_fd, &st->config);
+	err = write_table(wr, arg);
+	if (err < 0) {
+		goto exit;
+	}
+
+	err = writer_close(wr);
+	if (err < 0) {
+		goto exit;
+	}
+
+	err = close(tab_fd);
+	tab_fd = 0;
+	if (err < 0) {
+		err = IO_ERROR;
+		goto exit;
+	}
+
+	if (wr->min_update_index < next_update_index) {
+		err = API_ERROR;
+		goto exit;
+	}
+
+	{
+		int i = 0;
+		for (i = 0; i < st->merged->stack_len; i++) {
+			slice_append_string(&table_list,
+					    st->merged->stack[i]->name);
+			slice_append_string(&table_list, "\n");
+		}
+	}
+
+	format_name(&next_name, wr->min_update_index, wr->max_update_index);
+	slice_append_string(&next_name, ".ref");
+	slice_append(&table_list, next_name);
+	slice_append_string(&table_list, "\n");
+
+	slice_set_string(&tab_name, st->reftable_dir);
+	slice_append_string(&tab_name, "/");
+	slice_append(&tab_name, next_name);
+
+	err = rename(slice_as_string(&temp_tab_name),
+		     slice_as_string(&tab_name));
+	if (err < 0) {
+		err = IO_ERROR;
+		goto exit;
+	}
+	free(slice_yield(&temp_tab_name));
+
+	err = write(lock_fd, table_list.buf, table_list.len);
+	if (err < 0) {
+		err = IO_ERROR;
+		goto exit;
+	}
+	err = close(lock_fd);
+	lock_fd = 0;
+	if (err < 0) {
+		unlink(slice_as_string(&tab_name));
+		err = IO_ERROR;
+		goto exit;
+	}
+
+	err = rename(slice_as_string(&lock_name), st->list_file);
+	if (err < 0) {
+		unlink(slice_as_string(&tab_name));
+		err = IO_ERROR;
+		goto exit;
+	}
+
+	err = stack_reload(st);
+exit:
+	if (tab_fd > 0) {
+		close(tab_fd);
+		tab_fd = 0;
+	}
+	if (temp_tab_name.len > 0) {
+		unlink(slice_as_string(&temp_tab_name));
+	}
+	unlink(slice_as_string(&lock_name));
+
+	if (lock_fd > 0) {
+		close(lock_fd);
+		lock_fd = 0;
+	}
+
+	free(slice_yield(&lock_name));
+	free(slice_yield(&temp_tab_name));
+	free(slice_yield(&tab_name));
+	free(slice_yield(&next_name));
+	free(slice_yield(&table_list));
+	writer_free(wr);
+	return err;
+}
+
+uint64_t stack_next_update_index(struct stack *st)
+{
+	int sz = st->merged->stack_len;
+	if (sz > 0) {
+		return reader_max_update_index(st->merged->stack[sz - 1]) + 1;
+	}
+	return 1;
+}
+
+static int stack_compact_locked(struct stack *st, int first, int last,
+				struct slice *temp_tab,
+				struct log_expiry_config *config)
+{
+	struct slice next_name = {};
+	int tab_fd = -1;
+	struct writer *wr = NULL;
+	int err = 0;
+
+	format_name(&next_name,
+		    reader_min_update_index(st->merged->stack[first]),
+		    reader_max_update_index(st->merged->stack[first]));
+
+	slice_set_string(temp_tab, st->reftable_dir);
+	slice_append_string(temp_tab, "/");
+	slice_append(temp_tab, next_name);
+	slice_append_string(temp_tab, ".temp.XXXXXX");
+
+	tab_fd = mkstemp((char *)slice_as_string(temp_tab));
+	wr = new_writer(fd_writer, &tab_fd, &st->config);
+
+	err = stack_write_compact(st, wr, first, last, config);
+	if (err < 0) {
+		goto exit;
+	}
+	err = writer_close(wr);
+	if (err < 0) {
+		goto exit;
+	}
+	writer_free(wr);
+
+	err = close(tab_fd);
+	tab_fd = 0;
+
+exit:
+	if (tab_fd > 0) {
+		close(tab_fd);
+		tab_fd = 0;
+	}
+	if (err != 0 && temp_tab->len > 0) {
+		unlink(slice_as_string(temp_tab));
+		free(slice_yield(temp_tab));
+	}
+	free(slice_yield(&next_name));
+	return err;
+}
+
+int stack_write_compact(struct stack *st, struct writer *wr, int first,
+			int last, struct log_expiry_config *config)
+{
+	int subtabs_len = last - first + 1;
+	struct reader **subtabs =
+		calloc(sizeof(struct reader *), last - first + 1);
+	struct merged_table *mt = NULL;
+	int err = 0;
+	struct iterator it = {};
+	struct ref_record ref = {};
+	struct log_record log = {};
+
+	int i = 0, j = 0;
+	for (i = first, j = 0; i <= last; i++) {
+		struct reader *t = st->merged->stack[i];
+		subtabs[j++] = t;
+		st->stats.bytes += t->size;
+	}
+	writer_set_limits(wr, st->merged->stack[first]->min_update_index,
+			  st->merged->stack[last]->max_update_index);
+
+	err = new_merged_table(&mt, subtabs, subtabs_len);
+	if (err < 0) {
+		free(subtabs);
+		goto exit;
+	}
+
+	err = merged_table_seek_ref(mt, &it, "");
+	if (err < 0) {
+		goto exit;
+	}
+
+	while (true) {
+		err = iterator_next_ref(it, &ref);
+		if (err > 0) {
+			err = 0;
+			break;
+		}
+		if (err < 0) {
+			break;
+		}
+		if (first == 0 && ref_record_is_deletion(&ref)) {
+			continue;
+		}
+
+		err = writer_add_ref(wr, &ref);
+		if (err < 0) {
+			break;
+		}
+	}
+
+	err = merged_table_seek_log(mt, &it, "");
+	if (err < 0) {
+		goto exit;
+	}
+
+	while (true) {
+		err = iterator_next_log(it, &log);
+		if (err > 0) {
+			err = 0;
+			break;
+		}
+		if (err < 0) {
+			break;
+		}
+		if (first == 0 && log_record_is_deletion(&log)) {
+			continue;
+		}
+
+		/* XXX collect stats? */
+
+		if (config != NULL && config->time > 0 &&
+		    log.time < config->time) {
+			continue;
+		}
+
+		if (config != NULL && config->min_update_index > 0 &&
+		    log.update_index < config->min_update_index) {
+			continue;
+		}
+
+		err = writer_add_log(wr, &log);
+		if (err < 0) {
+			break;
+		}
+	}
+
+exit:
+	iterator_destroy(&it);
+	if (mt != NULL) {
+		merged_table_clear(mt);
+		merged_table_free(mt);
+	}
+	ref_record_clear(&ref);
+
+	return err;
+}
+
+/* <  0: error. 0 == OK, > 0 attempt failed; could retry. */
+static int stack_compact_range(struct stack *st, int first, int last,
+			       struct log_expiry_config *expiry)
+{
+	struct slice temp_tab_name = {};
+	struct slice new_table_name = {};
+	struct slice lock_file_name = {};
+	struct slice ref_list_contents = {};
+	struct slice new_table_path = {};
+	int err = 0;
+	bool have_lock = false;
+	int lock_file_fd = 0;
+	int compact_count = last - first + 1;
+	char **delete_on_success = calloc(sizeof(char *), compact_count + 1);
+	char **subtable_locks = calloc(sizeof(char *), compact_count + 1);
+	int i = 0;
+	int j = 0;
+
+	if (first > last || (expiry == NULL && first == last)) {
+		err = 0;
+		goto exit;
+	}
+
+	st->stats.attempts++;
+
+	slice_set_string(&lock_file_name, st->list_file);
+	slice_append_string(&lock_file_name, ".lock");
+
+	lock_file_fd = open(slice_as_string(&lock_file_name),
+			    O_EXCL | O_CREAT | O_WRONLY, 0644);
+	if (lock_file_fd < 0) {
+		if (errno == EEXIST) {
+			err = 1;
+		} else {
+			err = IO_ERROR;
+		}
+		goto exit;
+	}
+	have_lock = true;
+	err = stack_uptodate(st);
+	if (err != 0) {
+		goto exit;
+	}
+
+	for (i = first, j = 0; i <= last; i++) {
+		struct slice subtab_name = {};
+		struct slice subtab_lock = {};
+		slice_set_string(&subtab_name, st->reftable_dir);
+		slice_append_string(&subtab_name, "/");
+		slice_append_string(&subtab_name,
+				    reader_name(st->merged->stack[i]));
+
+		slice_copy(&subtab_lock, subtab_name);
+		slice_append_string(&subtab_lock, ".lock");
+
+		{
+			int sublock_file_fd =
+				open(slice_as_string(&subtab_lock),
+				     O_EXCL | O_CREAT | O_WRONLY, 0644);
+			if (sublock_file_fd > 0) {
+				close(sublock_file_fd);
+			} else if (sublock_file_fd < 0) {
+				if (errno == EEXIST) {
+					err = 1;
+				}
+				err = IO_ERROR;
+			}
+		}
+
+		subtable_locks[j] = (char *)slice_as_string(&subtab_lock);
+		delete_on_success[j] = (char *)slice_as_string(&subtab_name);
+		j++;
+
+		if (err != 0) {
+			goto exit;
+		}
+	}
+
+	err = unlink(slice_as_string(&lock_file_name));
+	if (err < 0) {
+		goto exit;
+	}
+	have_lock = false;
+
+	err = stack_compact_locked(st, first, last, &temp_tab_name, expiry);
+	if (err < 0) {
+		goto exit;
+	}
+
+	lock_file_fd = open(slice_as_string(&lock_file_name),
+			    O_EXCL | O_CREAT | O_WRONLY, 0644);
+	if (lock_file_fd < 0) {
+		if (errno == EEXIST) {
+			err = 1;
+		} else {
+			err = IO_ERROR;
+		}
+		goto exit;
+	}
+	have_lock = true;
+
+	format_name(&new_table_name, st->merged->stack[first]->min_update_index,
+		    st->merged->stack[last]->max_update_index);
+	slice_append_string(&new_table_name, ".ref");
+
+	slice_set_string(&new_table_path, st->reftable_dir);
+	slice_append_string(&new_table_path, "/");
+
+	slice_append(&new_table_path, new_table_name);
+
+	err = rename(slice_as_string(&temp_tab_name),
+		     slice_as_string(&new_table_path));
+	if (err < 0) {
+		goto exit;
+	}
+
+	for (i = 0; i < first; i++) {
+		slice_append_string(&ref_list_contents,
+				    st->merged->stack[i]->name);
+		slice_append_string(&ref_list_contents, "\n");
+	}
+	slice_append(&ref_list_contents, new_table_name);
+	slice_append_string(&ref_list_contents, "\n");
+	for (i = last + 1; i < st->merged->stack_len; i++) {
+		slice_append_string(&ref_list_contents,
+				    st->merged->stack[i]->name);
+		slice_append_string(&ref_list_contents, "\n");
+	}
+
+	err = write(lock_file_fd, ref_list_contents.buf, ref_list_contents.len);
+	if (err < 0) {
+		unlink(slice_as_string(&new_table_path));
+		goto exit;
+	}
+	err = close(lock_file_fd);
+	lock_file_fd = 0;
+	if (err < 0) {
+		unlink(slice_as_string(&new_table_path));
+		goto exit;
+	}
+
+	err = rename(slice_as_string(&lock_file_name), st->list_file);
+	if (err < 0) {
+		unlink(slice_as_string(&new_table_path));
+		goto exit;
+	}
+	have_lock = false;
+
+	for (char **p = delete_on_success; *p; p++) {
+		if (strcmp(*p, slice_as_string(&new_table_path))) {
+			unlink(*p);
+		}
+	}
+
+	err = stack_reload_maybe_reuse(st, first < last);
+exit:
+	for (char **p = subtable_locks; *p; p++) {
+		unlink(*p);
+	}
+	free_names(delete_on_success);
+	free_names(subtable_locks);
+	if (lock_file_fd > 0) {
+		close(lock_file_fd);
+		lock_file_fd = 0;
+	}
+	if (have_lock) {
+		unlink(slice_as_string(&lock_file_name));
+	}
+	free(slice_yield(&new_table_name));
+	free(slice_yield(&new_table_path));
+	free(slice_yield(&ref_list_contents));
+	free(slice_yield(&temp_tab_name));
+	free(slice_yield(&lock_file_name));
+	return err;
+}
+
+int stack_compact_all(struct stack *st, struct log_expiry_config *config)
+{
+	return stack_compact_range(st, 0, st->merged->stack_len - 1, config);
+}
+
+static int stack_compact_range_stats(struct stack *st, int first, int last,
+				     struct log_expiry_config *config)
+{
+	int err = stack_compact_range(st, first, last, config);
+	if (err > 0) {
+		st->stats.failures++;
+	}
+	return err;
+}
+
+static int segment_size(struct segment *s)
+{
+	return s->end - s->start;
+}
+
+int fastlog2(uint64_t sz)
+{
+	int l = 0;
+	assert(sz > 0);
+	for (; sz; sz /= 2) {
+		l++;
+	}
+	return l - 1;
+}
+
+struct segment *sizes_to_segments(int *seglen, uint64_t *sizes, int n)
+{
+	struct segment *segs = calloc(sizeof(struct segment), n);
+	int next = 0;
+	struct segment cur = {};
+	int i = 0;
+	for (i = 0; i < n; i++) {
+		int log = fastlog2(sizes[i]);
+		if (cur.log != log && cur.bytes > 0) {
+			struct segment fresh = {
+				.start = i,
+			};
+
+			segs[next++] = cur;
+			cur = fresh;
+		}
+
+		cur.log = log;
+		cur.end = i + 1;
+		cur.bytes += sizes[i];
+	}
+
+	segs[next++] = cur;
+	*seglen = next;
+	return segs;
+}
+
+struct segment suggest_compaction_segment(uint64_t *sizes, int n)
+{
+	int seglen = 0;
+	struct segment *segs = sizes_to_segments(&seglen, sizes, n);
+	struct segment min_seg = {
+		.log = 64,
+	};
+	int i = 0;
+	for (i = 0; i < seglen; i++) {
+		if (segment_size(&segs[i]) == 1) {
+			continue;
+		}
+
+		if (segs[i].log < min_seg.log) {
+			min_seg = segs[i];
+		}
+	}
+
+	while (min_seg.start > 0) {
+		int prev = min_seg.start - 1;
+		if (fastlog2(min_seg.bytes) < fastlog2(sizes[prev])) {
+			break;
+		}
+
+		min_seg.start = prev;
+		min_seg.bytes += sizes[prev];
+	}
+
+	free(segs);
+	return min_seg;
+}
+
+static uint64_t *stack_table_sizes_for_compaction(struct stack *st)
+{
+	uint64_t *sizes = calloc(sizeof(uint64_t), st->merged->stack_len);
+	int i = 0;
+	for (i = 0; i < st->merged->stack_len; i++) {
+		/* overhead is 24 + 68 = 92. */
+		sizes[i] = st->merged->stack[i]->size - 91;
+	}
+	return sizes;
+}
+
+int stack_auto_compact(struct stack *st)
+{
+	uint64_t *sizes = stack_table_sizes_for_compaction(st);
+	struct segment seg =
+		suggest_compaction_segment(sizes, st->merged->stack_len);
+	free(sizes);
+	if (segment_size(&seg) > 0) {
+		return stack_compact_range_stats(st, seg.start, seg.end - 1,
+						 NULL);
+	}
+
+	return 0;
+}
+
+struct compaction_stats *stack_compaction_stats(struct stack *st)
+{
+	return &st->stats;
+}
+
+int stack_read_ref(struct stack *st, const char *refname,
+		   struct ref_record *ref)
+{
+	struct iterator it = {};
+	struct merged_table *mt = stack_merged_table(st);
+	int err = merged_table_seek_ref(mt, &it, refname);
+	if (err) {
+		goto exit;
+	}
+
+	err = iterator_next_ref(it, ref);
+	if (err) {
+		goto exit;
+	}
+
+	if (strcmp(ref->ref_name, refname) || ref_record_is_deletion(ref)) {
+		err = 1;
+		goto exit;
+	}
+
+exit:
+	iterator_destroy(&it);
+	return err;
+}
+
+int stack_read_log(struct stack *st, const char *refname,
+		   struct log_record *log)
+{
+	struct iterator it = {};
+	struct merged_table *mt = stack_merged_table(st);
+	int err = merged_table_seek_log(mt, &it, refname);
+	if (err) {
+		goto exit;
+	}
+
+	err = iterator_next_log(it, log);
+	if (err) {
+		goto exit;
+	}
+
+	if (strcmp(log->ref_name, refname) || log_record_is_deletion(log)) {
+		err = 1;
+		goto exit;
+	}
+
+exit:
+	iterator_destroy(&it);
+	return err;
+}
diff --git a/reftable/stack.h b/reftable/stack.h
new file mode 100644
index 0000000000..d5e2c93c29
--- /dev/null
+++ b/reftable/stack.h
@@ -0,0 +1,40 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef STACK_H
+#define STACK_H
+
+#include "reftable.h"
+
+struct stack {
+	char *list_file;
+	char *reftable_dir;
+
+	struct write_options config;
+
+	struct merged_table *merged;
+	struct compaction_stats stats;
+};
+
+int read_lines(const char *filename, char ***lines);
+int stack_try_add(struct stack *st,
+		  int (*write_table)(struct writer *wr, void *arg), void *arg);
+int stack_write_compact(struct stack *st, struct writer *wr, int first,
+			int last, struct log_expiry_config *config);
+int fastlog2(uint64_t sz);
+
+struct segment {
+	int start, end;
+	int log;
+	uint64_t bytes;
+};
+
+struct segment *sizes_to_segments(int *seglen, uint64_t *sizes, int n);
+struct segment suggest_compaction_segment(uint64_t *sizes, int n);
+
+#endif
diff --git a/reftable/stack_test.c b/reftable/stack_test.c
new file mode 100644
index 0000000000..d80cb1789a
--- /dev/null
+++ b/reftable/stack_test.c
@@ -0,0 +1,281 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "stack.h"
+
+#include "system.h"
+
+#include "basics.h"
+#include "constants.h"
+#include "record.h"
+#include "reftable.h"
+#include "test_framework.h"
+
+void test_read_file(void)
+{
+	char fn[256] = "/tmp/stack.test_read_file.XXXXXX";
+	int fd = mkstemp(fn);
+	assert(fd > 0);
+
+	char out[1024] = "line1\n\nline2\nline3";
+
+	int n = write(fd, out, strlen(out));
+	assert(n == strlen(out));
+	int err = close(fd);
+	assert(err >= 0);
+
+	char **names = NULL;
+	err = read_lines(fn, &names);
+	assert_err(err);
+
+	char *want[] = { "line1", "line2", "line3" };
+	int i = 0;
+	for (i = 0; names[i] != NULL; i++) {
+		assert(0 == strcmp(want[i], names[i]));
+	}
+	free_names(names);
+	remove(fn);
+}
+
+void test_parse_names(void)
+{
+	char buf[] = "line\n";
+	char **names = NULL;
+	parse_names(buf, strlen(buf), &names);
+
+	assert(NULL != names[0]);
+	assert(0 == strcmp(names[0], "line"));
+	assert(NULL == names[1]);
+	free_names(names);
+}
+
+void test_names_equal(void)
+{
+	char *a[] = { "a", "b", "c", NULL };
+	char *b[] = { "a", "b", "d", NULL };
+	char *c[] = { "a", "b", NULL };
+
+	assert(names_equal(a, a));
+	assert(!names_equal(a, b));
+	assert(!names_equal(a, c));
+}
+
+int write_test_ref(struct writer *wr, void *arg)
+{
+	struct ref_record *ref = arg;
+
+	writer_set_limits(wr, ref->update_index, ref->update_index);
+	int err = writer_add_ref(wr, ref);
+
+	return err;
+}
+
+int write_test_log(struct writer *wr, void *arg)
+{
+	struct log_record *log = arg;
+
+	writer_set_limits(wr, log->update_index, log->update_index);
+	int err = writer_add_log(wr, log);
+
+	return err;
+}
+
+void test_stack_add(void)
+{
+	int i = 0;
+	char dir[256] = "/tmp/stack.test_stack_add.XXXXXX";
+	assert(mkdtemp(dir));
+	printf("%s\n", dir);
+	char fn[256] = "";
+	strcat(fn, dir);
+	strcat(fn, "/refs");
+
+	struct write_options cfg = {};
+	struct stack *st = NULL;
+	int err = new_stack(&st, dir, fn, cfg);
+	assert_err(err);
+
+	struct ref_record refs[2] = {};
+	struct log_record logs[2] = {};
+	int N = ARRAYSIZE(refs);
+	for (i = 0; i < N; i++) {
+		char buf[256];
+		snprintf(buf, sizeof(buf), "branch%02d", i);
+		refs[i].ref_name = strdup(buf);
+		refs[i].value = malloc(SHA1_SIZE);
+		refs[i].update_index = i + 1;
+		set_test_hash(refs[i].value, i);
+
+		logs[i].ref_name = strdup(buf);
+		logs[i].update_index = N + i + 1;
+		logs[i].new_hash = malloc(SHA1_SIZE);
+		logs[i].email = strdup("identity@invalid");
+		set_test_hash(logs[i].new_hash, i);
+	}
+
+	for (i = 0; i < N; i++) {
+		int err = stack_add(st, &write_test_ref, &refs[i]);
+		assert_err(err);
+	}
+
+	for (i = 0; i < N; i++) {
+		int err = stack_add(st, &write_test_log, &logs[i]);
+		assert_err(err);
+	}
+
+	err = stack_compact_all(st, NULL);
+	assert_err(err);
+
+	for (i = 0; i < N; i++) {
+		struct ref_record dest = {};
+		int err = stack_read_ref(st, refs[i].ref_name, &dest);
+		assert_err(err);
+		assert(ref_record_equal(&dest, refs + i, SHA1_SIZE));
+		ref_record_clear(&dest);
+	}
+
+	for (i = 0; i < N; i++) {
+		struct log_record dest = {};
+		int err = stack_read_log(st, refs[i].ref_name, &dest);
+		assert_err(err);
+		assert(log_record_equal(&dest, logs + i, SHA1_SIZE));
+		log_record_clear(&dest);
+	}
+
+	/* cleanup */
+	stack_destroy(st);
+	for (i = 0; i < N; i++) {
+		ref_record_clear(&refs[i]);
+		log_record_clear(&logs[i]);
+	}
+}
+
+void test_log2(void)
+{
+	assert(1 == fastlog2(3));
+	assert(2 == fastlog2(4));
+	assert(2 == fastlog2(5));
+}
+
+void test_sizes_to_segments(void)
+{
+	uint64_t sizes[] = { 2, 3, 4, 5, 7, 9 };
+	/* .................0  1  2  3  4  5 */
+
+	int seglen = 0;
+	struct segment *segs =
+		sizes_to_segments(&seglen, sizes, ARRAYSIZE(sizes));
+	assert(segs[2].log == 3);
+	assert(segs[2].start == 5);
+	assert(segs[2].end == 6);
+
+	assert(segs[1].log == 2);
+	assert(segs[1].start == 2);
+	assert(segs[1].end == 5);
+	free(segs);
+}
+
+void test_suggest_compaction_segment(void)
+{
+	{
+		uint64_t sizes[] = { 128, 64, 17, 16, 9, 9, 9, 16, 16 };
+		/* .................0    1    2  3   4  5  6 */
+		struct segment min =
+			suggest_compaction_segment(sizes, ARRAYSIZE(sizes));
+		assert(min.start == 2);
+		assert(min.end == 7);
+	}
+
+	{
+		uint64_t sizes[] = { 64, 32, 16, 8, 4, 2 };
+		struct segment result =
+			suggest_compaction_segment(sizes, ARRAYSIZE(sizes));
+		assert(result.start == result.end);
+	}
+}
+
+void test_reflog_expire(void)
+{
+	char dir[256] = "/tmp/stack.test_reflog_expire.XXXXXX";
+	assert(mkdtemp(dir));
+	printf("%s\n", dir);
+	char fn[256] = "";
+	strcat(fn, dir);
+	strcat(fn, "/refs");
+
+	struct write_options cfg = {};
+	struct stack *st = NULL;
+	int err = new_stack(&st, dir, fn, cfg);
+	assert_err(err);
+
+	struct log_record logs[20] = {};
+	int N = ARRAYSIZE(logs) - 1;
+	int i = 0;
+	for (i = 1; i <= N; i++) {
+		char buf[256];
+		snprintf(buf, sizeof(buf), "branch%02d", i);
+
+		logs[i].ref_name = strdup(buf);
+		logs[i].update_index = i;
+		logs[i].time = i;
+		logs[i].new_hash = malloc(SHA1_SIZE);
+		logs[i].email = strdup("identity@invalid");
+		set_test_hash(logs[i].new_hash, i);
+	}
+
+	for (i = 1; i <= N; i++) {
+		int err = stack_add(st, &write_test_log, &logs[i]);
+		assert_err(err);
+	}
+
+	err = stack_compact_all(st, NULL);
+	assert_err(err);
+
+	struct log_expiry_config expiry = {
+		.time = 10,
+	};
+	err = stack_compact_all(st, &expiry);
+	assert_err(err);
+
+	struct log_record log = {};
+	err = stack_read_log(st, logs[9].ref_name, &log);
+	assert(err == 1);
+
+	err = stack_read_log(st, logs[11].ref_name, &log);
+	assert_err(err);
+
+	expiry.min_update_index = 15;
+	err = stack_compact_all(st, &expiry);
+	assert_err(err);
+
+	err = stack_read_log(st, logs[14].ref_name, &log);
+	assert(err == 1);
+
+	err = stack_read_log(st, logs[16].ref_name, &log);
+	assert_err(err);
+
+	/* cleanup */
+	stack_destroy(st);
+	for (i = 0; i < N; i++) {
+		log_record_clear(&logs[i]);
+	}
+}
+
+int main()
+{
+	add_test_case("test_reflog_expire", test_reflog_expire);
+	add_test_case("test_suggest_compaction_segment",
+		      &test_suggest_compaction_segment);
+	add_test_case("test_sizes_to_segments", &test_sizes_to_segments);
+	add_test_case("test_log2", &test_log2);
+	add_test_case("test_parse_names", &test_parse_names);
+	add_test_case("test_read_file", &test_read_file);
+	add_test_case("test_names_equal", &test_names_equal);
+	add_test_case("test_stack_add", &test_stack_add);
+	test_main();
+}
diff --git a/reftable/system.h b/reftable/system.h
new file mode 100644
index 0000000000..97214ae210
--- /dev/null
+++ b/reftable/system.h
@@ -0,0 +1,36 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef SYSTEM_H
+#define SYSTEM_H
+
+#include "config.h"
+
+#ifndef REFTABLE_STANDALONE
+#include "git-compat-util.h"
+#else
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <zlib.h>
+
+#define PRIuMAX "lu"
+#define PRIdMAX "ld"
+#define PRIxMAX "lx"
+
+#endif
+
+#endif
diff --git a/reftable/test_framework.c b/reftable/test_framework.c
new file mode 100644
index 0000000000..b9f633934a
--- /dev/null
+++ b/reftable/test_framework.c
@@ -0,0 +1,67 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "test_framework.h"
+
+#include "system.h"
+
+#include "constants.h"
+
+struct test_case **test_cases;
+int test_case_len;
+int test_case_cap;
+
+struct test_case *new_test_case(const char *name, void (*testfunc)())
+{
+	struct test_case *tc = malloc(sizeof(struct test_case));
+	tc->name = name;
+	tc->testfunc = testfunc;
+	return tc;
+}
+
+struct test_case *add_test_case(const char *name, void (*testfunc)())
+{
+	struct test_case *tc = new_test_case(name, testfunc);
+	if (test_case_len == test_case_cap) {
+		test_case_cap = 2 * test_case_cap + 1;
+		test_cases = realloc(test_cases,
+				     sizeof(struct test_case) * test_case_cap);
+	}
+
+	test_cases[test_case_len++] = tc;
+	return tc;
+}
+
+void test_main()
+{
+	int i = 0;
+	for (i = 0; i < test_case_len; i++) {
+		printf("case %s\n", test_cases[i]->name);
+		test_cases[i]->testfunc();
+	}
+}
+
+void set_test_hash(byte *p, int i)
+{
+	memset(p, (byte)i, SHA1_SIZE);
+}
+
+void print_names(char **a)
+{
+	if (a == NULL || *a == NULL) {
+		puts("[]");
+		return;
+	}
+	puts("[");
+	char **p = a;
+	while (*p) {
+		puts(*p);
+		p++;
+	}
+	puts("]");
+}
diff --git a/reftable/test_framework.h b/reftable/test_framework.h
new file mode 100644
index 0000000000..d3e793c3a6
--- /dev/null
+++ b/reftable/test_framework.h
@@ -0,0 +1,64 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef TEST_FRAMEWORK_H
+#define TEST_FRAMEWORK_H
+
+#include "system.h"
+
+#include "reftable.h"
+
+#ifdef NDEBUG
+#undef NDEBUG
+#endif
+
+#include "system.h"
+
+#ifdef assert
+#undef assert
+#endif
+
+#define assert_err(c)                                                 \
+	if (c != 0) {                                                 \
+		fflush(stderr);                                       \
+		fflush(stdout);                                       \
+		fprintf(stderr, "%s: %d: error == %d (%s), want 0\n", \
+			__FILE__, __LINE__, c, error_str(c));         \
+		abort();                                              \
+	}
+
+#define assert_streq(a, b)                                               \
+	if (strcmp(a, b)) {                                              \
+		fflush(stderr);                                          \
+		fflush(stdout);                                          \
+		fprintf(stderr, "%s:%d: %s (%s) != %s (%s)\n", __FILE__, \
+			__LINE__, #a, a, #b, b);                         \
+		abort();                                                 \
+	}
+
+#define assert(c)                                                          \
+	if (!(c)) {                                                        \
+		fflush(stderr);                                            \
+		fflush(stdout);                                            \
+		fprintf(stderr, "%s: %d: failed assertion %s\n", __FILE__, \
+			__LINE__, #c);                                     \
+		abort();                                                   \
+	}
+
+struct test_case {
+	const char *name;
+	void (*testfunc)();
+};
+
+struct test_case *new_test_case(const char *name, void (*testfunc)());
+struct test_case *add_test_case(const char *name, void (*testfunc)());
+void test_main();
+
+void set_test_hash(byte *p, int i);
+
+#endif
diff --git a/reftable/tree.c b/reftable/tree.c
new file mode 100644
index 0000000000..9bf7fe531f
--- /dev/null
+++ b/reftable/tree.c
@@ -0,0 +1,66 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "tree.h"
+
+#include "system.h"
+
+struct tree_node *tree_search(void *key, struct tree_node **rootp,
+			      int (*compare)(const void *, const void *),
+			      int insert)
+{
+	if (*rootp == NULL) {
+		if (!insert) {
+			return NULL;
+		} else {
+			struct tree_node *n =
+				calloc(sizeof(struct tree_node), 1);
+			n->key = key;
+			*rootp = n;
+			return *rootp;
+		}
+	}
+
+	{
+		int res = compare(key, (*rootp)->key);
+		if (res < 0) {
+			return tree_search(key, &(*rootp)->left, compare,
+					   insert);
+		} else if (res > 0) {
+			return tree_search(key, &(*rootp)->right, compare,
+					   insert);
+		}
+	}
+	return *rootp;
+}
+
+void infix_walk(struct tree_node *t, void (*action)(void *arg, void *key),
+		void *arg)
+{
+	if (t->left != NULL) {
+		infix_walk(t->left, action, arg);
+	}
+	action(arg, t->key);
+	if (t->right != NULL) {
+		infix_walk(t->right, action, arg);
+	}
+}
+
+void tree_free(struct tree_node *t)
+{
+	if (t == NULL) {
+		return;
+	}
+	if (t->left != NULL) {
+		tree_free(t->left);
+	}
+	if (t->right != NULL) {
+		tree_free(t->right);
+	}
+	free(t);
+}
diff --git a/reftable/tree.h b/reftable/tree.h
new file mode 100644
index 0000000000..86a71715ae
--- /dev/null
+++ b/reftable/tree.h
@@ -0,0 +1,24 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef TREE_H
+#define TREE_H
+
+struct tree_node {
+	void *key;
+	struct tree_node *left, *right;
+};
+
+struct tree_node *tree_search(void *key, struct tree_node **rootp,
+			      int (*compare)(const void *, const void *),
+			      int insert);
+void infix_walk(struct tree_node *t, void (*action)(void *arg, void *key),
+		void *arg);
+void tree_free(struct tree_node *t);
+
+#endif
diff --git a/reftable/tree_test.c b/reftable/tree_test.c
new file mode 100644
index 0000000000..842985376c
--- /dev/null
+++ b/reftable/tree_test.c
@@ -0,0 +1,61 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "tree.h"
+
+#include "basics.h"
+#include "record.h"
+#include "reftable.h"
+#include "test_framework.h"
+
+static int test_compare(const void *a, const void *b)
+{
+	return a - b;
+}
+
+struct curry {
+	void *last;
+};
+
+void check_increasing(void *arg, void *key)
+{
+	struct curry *c = (struct curry *)arg;
+	if (c->last != NULL) {
+		assert(test_compare(c->last, key) < 0);
+	}
+	c->last = key;
+}
+
+void test_tree()
+{
+	struct tree_node *root = NULL;
+
+	void *values[11] = {};
+	struct tree_node *nodes[11] = {};
+	int i = 1;
+	do {
+		nodes[i] = tree_search(values + i, &root, &test_compare, 1);
+		i = (i * 7) % 11;
+	} while (i != 1);
+
+	for (i = 1; i < ARRAYSIZE(nodes); i++) {
+		assert(values + i == nodes[i]->key);
+		assert(nodes[i] ==
+		       tree_search(values + i, &root, &test_compare, 0));
+	}
+
+	struct curry c = {};
+	infix_walk(root, check_increasing, &c);
+	tree_free(root);
+}
+
+int main()
+{
+	add_test_case("test_tree", &test_tree);
+	test_main();
+}
diff --git a/reftable/writer.c b/reftable/writer.c
new file mode 100644
index 0000000000..cb8e4bbf89
--- /dev/null
+++ b/reftable/writer.c
@@ -0,0 +1,624 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "writer.h"
+
+#include "system.h"
+
+#include "block.h"
+#include "constants.h"
+#include "record.h"
+#include "reftable.h"
+#include "tree.h"
+
+static struct block_stats *writer_block_stats(struct writer *w, byte typ)
+{
+	switch (typ) {
+	case 'r':
+		return &w->stats.ref_stats;
+	case 'o':
+		return &w->stats.obj_stats;
+	case 'i':
+		return &w->stats.idx_stats;
+	case 'g':
+		return &w->stats.log_stats;
+	}
+	assert(false);
+}
+
+/* write data, queuing the padding for the next write. Returns negative for
+ * error. */
+static int padded_write(struct writer *w, byte *data, size_t len, int padding)
+{
+	int n = 0;
+	if (w->pending_padding > 0) {
+		byte *zeroed = calloc(w->pending_padding, 1);
+		int n = w->write(w->write_arg, zeroed, w->pending_padding);
+		if (n < 0) {
+			return n;
+		}
+
+		w->pending_padding = 0;
+		free(zeroed);
+	}
+
+	w->pending_padding = padding;
+	n = w->write(w->write_arg, data, len);
+	if (n < 0) {
+		return n;
+	}
+	n += padding;
+	return 0;
+}
+
+static void options_set_defaults(struct write_options *opts)
+{
+	if (opts->restart_interval == 0) {
+		opts->restart_interval = 16;
+	}
+
+	if (opts->block_size == 0) {
+		opts->block_size = DEFAULT_BLOCK_SIZE;
+	}
+}
+
+static int writer_write_header(struct writer *w, byte *dest)
+{
+	memcpy((char *)dest, "REFT", 4);
+	dest[4] = 1; /* version */
+	put_u24(dest + 5, w->opts.block_size);
+	put_u64(dest + 8, w->min_update_index);
+	put_u64(dest + 16, w->max_update_index);
+	return 24;
+}
+
+static void writer_reinit_block_writer(struct writer *w, byte typ)
+{
+	int block_start = 0;
+	if (w->next == 0) {
+		block_start = HEADER_SIZE;
+	}
+
+	block_writer_init(&w->block_writer_data, typ, w->block,
+			  w->opts.block_size, block_start, w->hash_size);
+	w->block_writer = &w->block_writer_data;
+	w->block_writer->restart_interval = w->opts.restart_interval;
+}
+
+struct writer *new_writer(int (*writer_func)(void *, byte *, int),
+			  void *writer_arg, struct write_options *opts)
+{
+	struct writer *wp = calloc(sizeof(struct writer), 1);
+	options_set_defaults(opts);
+	if (opts->block_size >= (1 << 24)) {
+		/* TODO - error return? */
+		abort();
+	}
+	wp->hash_size = SHA1_SIZE;
+	wp->block = calloc(opts->block_size, 1);
+	wp->write = writer_func;
+	wp->write_arg = writer_arg;
+	wp->opts = *opts;
+	writer_reinit_block_writer(wp, BLOCK_TYPE_REF);
+
+	return wp;
+}
+
+void writer_set_limits(struct writer *w, uint64_t min, uint64_t max)
+{
+	w->min_update_index = min;
+	w->max_update_index = max;
+}
+
+void writer_free(struct writer *w)
+{
+	free(w->block);
+	free(w);
+}
+
+struct obj_index_tree_node {
+	struct slice hash;
+	uint64_t *offsets;
+	int offset_len;
+	int offset_cap;
+};
+
+static int obj_index_tree_node_compare(const void *a, const void *b)
+{
+	return slice_compare(((const struct obj_index_tree_node *)a)->hash,
+			     ((const struct obj_index_tree_node *)b)->hash);
+}
+
+static void writer_index_hash(struct writer *w, struct slice hash)
+{
+	uint64_t off = w->next;
+
+	struct obj_index_tree_node want = { .hash = hash };
+
+	struct tree_node *node = tree_search(&want, &w->obj_index_tree,
+					     &obj_index_tree_node_compare, 0);
+	struct obj_index_tree_node *key = NULL;
+	if (node == NULL) {
+		key = calloc(sizeof(struct obj_index_tree_node), 1);
+		slice_copy(&key->hash, hash);
+		tree_search((void *)key, &w->obj_index_tree,
+			    &obj_index_tree_node_compare, 1);
+	} else {
+		key = node->key;
+	}
+
+	if (key->offset_len > 0 && key->offsets[key->offset_len - 1] == off) {
+		return;
+	}
+
+	if (key->offset_len == key->offset_cap) {
+		key->offset_cap = 2 * key->offset_cap + 1;
+		key->offsets = realloc(key->offsets,
+				       sizeof(uint64_t) * key->offset_cap);
+	}
+
+	key->offsets[key->offset_len++] = off;
+}
+
+static int writer_add_record(struct writer *w, struct record rec)
+{
+	int result = -1;
+	struct slice key = {};
+	int err = 0;
+	record_key(rec, &key);
+	if (slice_compare(w->last_key, key) >= 0) {
+		goto exit;
+	}
+
+	slice_copy(&w->last_key, key);
+	if (w->block_writer == NULL) {
+		writer_reinit_block_writer(w, record_type(rec));
+	}
+
+	assert(block_writer_type(w->block_writer) == record_type(rec));
+
+	if (block_writer_add(w->block_writer, rec) == 0) {
+		result = 0;
+		goto exit;
+	}
+
+	err = writer_flush_block(w);
+	if (err < 0) {
+		result = err;
+		goto exit;
+	}
+
+	writer_reinit_block_writer(w, record_type(rec));
+	err = block_writer_add(w->block_writer, rec);
+	if (err < 0) {
+		result = err;
+		goto exit;
+	}
+
+	result = 0;
+exit:
+	free(slice_yield(&key));
+	return result;
+}
+
+int writer_add_ref(struct writer *w, struct ref_record *ref)
+{
+	struct record rec = {};
+	struct ref_record copy = *ref;
+	int err = 0;
+
+	if (ref->ref_name == NULL) {
+		return API_ERROR;
+	}
+	if (ref->update_index < w->min_update_index ||
+	    ref->update_index > w->max_update_index) {
+		return API_ERROR;
+	}
+
+	record_from_ref(&rec, &copy);
+	copy.update_index -= w->min_update_index;
+	err = writer_add_record(w, rec);
+	if (err < 0) {
+		return err;
+	}
+
+	if (!w->opts.skip_index_objects && ref->value != NULL) {
+		struct slice h = {
+			.buf = ref->value,
+			.len = w->hash_size,
+		};
+
+		writer_index_hash(w, h);
+	}
+	if (!w->opts.skip_index_objects && ref->target_value != NULL) {
+		struct slice h = {
+			.buf = ref->target_value,
+			.len = w->hash_size,
+		};
+		writer_index_hash(w, h);
+	}
+	return 0;
+}
+
+int writer_add_refs(struct writer *w, struct ref_record *refs, int n)
+{
+	int err = 0;
+	int i = 0;
+	qsort(refs, n, sizeof(struct ref_record), ref_record_compare_name);
+	for (i = 0; err == 0 && i < n; i++) {
+		err = writer_add_ref(w, &refs[i]);
+	}
+	return err;
+}
+
+int writer_add_log(struct writer *w, struct log_record *log)
+{
+	if (log->ref_name == NULL) {
+		return API_ERROR;
+	}
+
+	if (w->block_writer != NULL &&
+	    block_writer_type(w->block_writer) == BLOCK_TYPE_REF) {
+		int err = writer_finish_public_section(w);
+		if (err < 0) {
+			return err;
+		}
+	}
+
+	w->next -= w->pending_padding;
+	w->pending_padding = 0;
+
+	{
+		struct record rec = {};
+		int err;
+		record_from_log(&rec, log);
+		err = writer_add_record(w, rec);
+		return err;
+	}
+}
+
+int writer_add_logs(struct writer *w, struct log_record *logs, int n)
+{
+	int err = 0;
+	int i = 0;
+	qsort(logs, n, sizeof(struct log_record), log_record_compare_key);
+	for (i = 0; err == 0 && i < n; i++) {
+		err = writer_add_log(w, &logs[i]);
+	}
+	return err;
+}
+
+static int writer_finish_section(struct writer *w)
+{
+	byte typ = block_writer_type(w->block_writer);
+	uint64_t index_start = 0;
+	int max_level = 0;
+	int threshold = w->opts.unpadded ? 1 : 3;
+	int before_blocks = w->stats.idx_stats.blocks;
+	int err = writer_flush_block(w);
+	int i = 0;
+	if (err < 0) {
+		return err;
+	}
+
+	while (w->index_len > threshold) {
+		struct index_record *idx = NULL;
+		int idx_len = 0;
+
+		max_level++;
+		index_start = w->next;
+		writer_reinit_block_writer(w, BLOCK_TYPE_INDEX);
+
+		idx = w->index;
+		idx_len = w->index_len;
+
+		w->index = NULL;
+		w->index_len = 0;
+		w->index_cap = 0;
+		for (i = 0; i < idx_len; i++) {
+			struct record rec = {};
+			record_from_index(&rec, idx + i);
+			if (block_writer_add(w->block_writer, rec) == 0) {
+				continue;
+			}
+
+			{
+				int err = writer_flush_block(w);
+				if (err < 0) {
+					return err;
+				}
+			}
+
+			writer_reinit_block_writer(w, BLOCK_TYPE_INDEX);
+
+			err = block_writer_add(w->block_writer, rec);
+			assert(err == 0);
+		}
+		for (i = 0; i < idx_len; i++) {
+			free(slice_yield(&idx[i].last_key));
+		}
+		free(idx);
+	}
+
+	writer_clear_index(w);
+
+	err = writer_flush_block(w);
+	if (err < 0) {
+		return err;
+	}
+
+	{
+		struct block_stats *bstats = writer_block_stats(w, typ);
+		bstats->index_blocks =
+			w->stats.idx_stats.blocks - before_blocks;
+		bstats->index_offset = index_start;
+		bstats->max_index_level = max_level;
+	}
+
+	/* Reinit lastKey, as the next section can start with any key. */
+	w->last_key.len = 0;
+
+	return 0;
+}
+
+struct common_prefix_arg {
+	struct slice *last;
+	int max;
+};
+
+static void update_common(void *void_arg, void *key)
+{
+	struct common_prefix_arg *arg = (struct common_prefix_arg *)void_arg;
+	struct obj_index_tree_node *entry = (struct obj_index_tree_node *)key;
+	if (arg->last != NULL) {
+		int n = common_prefix_size(entry->hash, *arg->last);
+		if (n > arg->max) {
+			arg->max = n;
+		}
+	}
+	arg->last = &entry->hash;
+}
+
+struct write_record_arg {
+	struct writer *w;
+	int err;
+};
+
+static void write_object_record(void *void_arg, void *key)
+{
+	struct write_record_arg *arg = (struct write_record_arg *)void_arg;
+	struct obj_index_tree_node *entry = (struct obj_index_tree_node *)key;
+	struct obj_record obj_rec = {
+		.hash_prefix = entry->hash.buf,
+		.hash_prefix_len = arg->w->stats.object_id_len,
+		.offsets = entry->offsets,
+		.offset_len = entry->offset_len,
+	};
+	struct record rec = {};
+	if (arg->err < 0) {
+		goto exit;
+	}
+
+	record_from_obj(&rec, &obj_rec);
+	arg->err = block_writer_add(arg->w->block_writer, rec);
+	if (arg->err == 0) {
+		goto exit;
+	}
+
+	arg->err = writer_flush_block(arg->w);
+	if (arg->err < 0) {
+		goto exit;
+	}
+
+	writer_reinit_block_writer(arg->w, BLOCK_TYPE_OBJ);
+	arg->err = block_writer_add(arg->w->block_writer, rec);
+	if (arg->err == 0) {
+		goto exit;
+	}
+	obj_rec.offset_len = 0;
+	arg->err = block_writer_add(arg->w->block_writer, rec);
+
+	/* Should be able to write into a fresh block. */
+	assert(arg->err == 0);
+
+exit:;
+}
+
+static void object_record_free(void *void_arg, void *key)
+{
+	struct obj_index_tree_node *entry = (struct obj_index_tree_node *)key;
+
+	free(entry->offsets);
+	entry->offsets = NULL;
+	free(slice_yield(&entry->hash));
+	free(entry);
+}
+
+static int writer_dump_object_index(struct writer *w)
+{
+	struct write_record_arg closure = { .w = w };
+	struct common_prefix_arg common = {};
+	if (w->obj_index_tree != NULL) {
+		infix_walk(w->obj_index_tree, &update_common, &common);
+	}
+	w->stats.object_id_len = common.max + 1;
+
+	writer_reinit_block_writer(w, BLOCK_TYPE_OBJ);
+
+	if (w->obj_index_tree != NULL) {
+		infix_walk(w->obj_index_tree, &write_object_record, &closure);
+	}
+
+	if (closure.err < 0) {
+		return closure.err;
+	}
+	return writer_finish_section(w);
+}
+
+int writer_finish_public_section(struct writer *w)
+{
+	byte typ = 0;
+	int err = 0;
+
+	if (w->block_writer == NULL) {
+		return 0;
+	}
+
+	typ = block_writer_type(w->block_writer);
+	err = writer_finish_section(w);
+	if (err < 0) {
+		return err;
+	}
+	if (typ == BLOCK_TYPE_REF && !w->opts.skip_index_objects &&
+	    w->stats.ref_stats.index_blocks > 0) {
+		err = writer_dump_object_index(w);
+		if (err < 0) {
+			return err;
+		}
+	}
+
+	if (w->obj_index_tree != NULL) {
+		infix_walk(w->obj_index_tree, &object_record_free, NULL);
+		tree_free(w->obj_index_tree);
+		w->obj_index_tree = NULL;
+	}
+
+	w->block_writer = NULL;
+	return 0;
+}
+
+int writer_close(struct writer *w)
+{
+	byte footer[68];
+	byte *p = footer;
+
+	writer_finish_public_section(w);
+
+	writer_write_header(w, footer);
+	p += 24;
+	put_u64(p, w->stats.ref_stats.index_offset);
+	p += 8;
+	put_u64(p, (w->stats.obj_stats.offset) << 5 | w->stats.object_id_len);
+	p += 8;
+	put_u64(p, w->stats.obj_stats.index_offset);
+	p += 8;
+
+	put_u64(p, w->stats.log_stats.offset);
+	p += 8;
+	put_u64(p, w->stats.log_stats.index_offset);
+	p += 8;
+
+	put_u32(p, crc32(0, footer, p - footer));
+	p += 4;
+	w->pending_padding = 0;
+
+	{
+		int n = padded_write(w, footer, sizeof(footer), 0);
+		if (n < 0) {
+			return n;
+		}
+	}
+
+	/* free up memory. */
+	block_writer_clear(&w->block_writer_data);
+	writer_clear_index(w);
+	free(slice_yield(&w->last_key));
+	return 0;
+}
+
+void writer_clear_index(struct writer *w)
+{
+	int i = 0;
+	for (i = 0; i < w->index_len; i++) {
+		free(slice_yield(&w->index[i].last_key));
+	}
+
+	free(w->index);
+	w->index = NULL;
+	w->index_len = 0;
+	w->index_cap = 0;
+}
+
+const int debug = 0;
+
+static int writer_flush_nonempty_block(struct writer *w)
+{
+	byte typ = block_writer_type(w->block_writer);
+	struct block_stats *bstats = writer_block_stats(w, typ);
+	uint64_t block_typ_off = (bstats->blocks == 0) ? w->next : 0;
+	int raw_bytes = block_writer_finish(w->block_writer);
+	int padding = 0;
+	int err = 0;
+	if (raw_bytes < 0) {
+		return raw_bytes;
+	}
+
+	if (!w->opts.unpadded && typ != BLOCK_TYPE_LOG) {
+		padding = w->opts.block_size - raw_bytes;
+	}
+
+	if (block_typ_off > 0) {
+		bstats->offset = block_typ_off;
+	}
+
+	bstats->entries += w->block_writer->entries;
+	bstats->restarts += w->block_writer->restart_len;
+	bstats->blocks++;
+	w->stats.blocks++;
+
+	if (debug) {
+		fprintf(stderr, "block %c off %" PRIuMAX " sz %d (%d)\n", typ,
+			w->next, raw_bytes,
+			get_u24(w->block + w->block_writer->header_off + 1));
+	}
+
+	if (w->next == 0) {
+		writer_write_header(w, w->block);
+	}
+
+	err = padded_write(w, w->block, raw_bytes, padding);
+	if (err < 0) {
+		return err;
+	}
+
+	if (w->index_cap == w->index_len) {
+		w->index_cap = 2 * w->index_cap + 1;
+		w->index = realloc(w->index,
+				   sizeof(struct index_record) * w->index_cap);
+	}
+
+	{
+		struct index_record ir = {
+			.offset = w->next,
+		};
+		slice_copy(&ir.last_key, w->block_writer->last_key);
+		w->index[w->index_len] = ir;
+	}
+
+	w->index_len++;
+	w->next += padding + raw_bytes;
+	block_writer_reset(&w->block_writer_data);
+	w->block_writer = NULL;
+	return 0;
+}
+
+int writer_flush_block(struct writer *w)
+{
+	if (w->block_writer == NULL) {
+		return 0;
+	}
+	if (w->block_writer->entries == 0) {
+		return 0;
+	}
+	return writer_flush_nonempty_block(w);
+}
+
+struct stats *writer_stats(struct writer *w)
+{
+	return &w->stats;
+}
diff --git a/reftable/writer.h b/reftable/writer.h
new file mode 100644
index 0000000000..bd2386474b
--- /dev/null
+++ b/reftable/writer.h
@@ -0,0 +1,46 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#ifndef WRITER_H
+#define WRITER_H
+
+#include "basics.h"
+#include "block.h"
+#include "reftable.h"
+#include "slice.h"
+#include "tree.h"
+
+struct writer {
+	int (*write)(void *, byte *, int);
+	void *write_arg;
+	int pending_padding;
+	int hash_size;
+	struct slice last_key;
+
+	uint64_t next;
+	uint64_t min_update_index, max_update_index;
+	struct write_options opts;
+
+	byte *block;
+	struct block_writer *block_writer;
+	struct block_writer block_writer_data;
+	struct index_record *index;
+	int index_len;
+	int index_cap;
+
+	/* tree for use with tsearch */
+	struct tree_node *obj_index_tree;
+
+	struct stats stats;
+};
+
+int writer_flush_block(struct writer *w);
+void writer_clear_index(struct writer *w);
+int writer_finish_public_section(struct writer *w);
+
+#endif
-- 
gitgitgadget


^ permalink raw reply related	[relevance 1%]

* [PATCH v2 0/5] Reftable support git-core
  @ 2020-01-27 14:22  1% ` Han-Wen Nienhuys via GitGitGadget
  2020-01-27 14:22  1%   ` [PATCH v2 4/5] Add reftable library Han-Wen Nienhuys via GitGitGadget
    0 siblings, 2 replies; 200+ results
From: Han-Wen Nienhuys via GitGitGadget @ 2020-01-27 14:22 UTC (permalink / raw)
  To: git; +Cc: Han-Wen Nienhuys

This adds the reftable library, and hooks it up as a ref backend.

At this point, I am mainly interested in feedback on the spots marked with
XXX in the Git source code, in particular, how to handle reflog expiry in
this backend.

v2

 * address Jun's nits.
 * address Dscho's portability comments
 * more background in commit messages.

Han-Wen Nienhuys (5):
  setup.c: enable repo detection for reftable
  create .git/refs in files-backend.c
  Document how ref iterators and symrefs interact
  Add reftable library
  Reftable support for git-core

 Makefile                  |   23 +-
 builtin/init-db.c         |    2 -
 refs.c                    |   18 +-
 refs.h                    |    2 +
 refs/files-backend.c      |    4 +
 refs/refs-internal.h      |    4 +
 refs/reftable-backend.c   |  812 +++++++++++++++++++++++++++
 reftable/LICENSE          |   31 ++
 reftable/README.md        |   17 +
 reftable/VERSION          |    5 +
 reftable/basics.c         |  196 +++++++
 reftable/basics.h         |   38 ++
 reftable/block.c          |  401 ++++++++++++++
 reftable/block.h          |   71 +++
 reftable/block_test.c     |  151 +++++
 reftable/blocksource.h    |   20 +
 reftable/bytes.c          |    0
 reftable/config.h         |    1 +
 reftable/constants.h      |   27 +
 reftable/dump.c           |   97 ++++
 reftable/file.c           |   97 ++++
 reftable/iter.c           |  230 ++++++++
 reftable/iter.h           |   56 ++
 reftable/merged.c         |  288 ++++++++++
 reftable/merged.h         |   34 ++
 reftable/merged_test.c    |  258 +++++++++
 reftable/pq.c             |  124 +++++
 reftable/pq.h             |   34 ++
 reftable/reader.c         |  710 ++++++++++++++++++++++++
 reftable/reader.h         |   52 ++
 reftable/record.c         | 1110 +++++++++++++++++++++++++++++++++++++
 reftable/record.h         |   79 +++
 reftable/record_test.c    |  332 +++++++++++
 reftable/reftable.h       |  399 +++++++++++++
 reftable/reftable_test.c  |  481 ++++++++++++++++
 reftable/slice.c          |  199 +++++++
 reftable/slice.h          |   39 ++
 reftable/slice_test.c     |   38 ++
 reftable/stack.c          |  985 ++++++++++++++++++++++++++++++++
 reftable/stack.h          |   40 ++
 reftable/stack_test.c     |  281 ++++++++++
 reftable/system.h         |   36 ++
 reftable/test_framework.c |   67 +++
 reftable/test_framework.h |   64 +++
 reftable/tree.c           |   66 +++
 reftable/tree.h           |   24 +
 reftable/tree_test.c      |   61 ++
 reftable/writer.c         |  624 +++++++++++++++++++++
 reftable/writer.h         |   46 ++
 setup.c                   |   20 +-
 50 files changed, 8782 insertions(+), 12 deletions(-)
 create mode 100644 refs/reftable-backend.c
 create mode 100644 reftable/LICENSE
 create mode 100644 reftable/README.md
 create mode 100644 reftable/VERSION
 create mode 100644 reftable/basics.c
 create mode 100644 reftable/basics.h
 create mode 100644 reftable/block.c
 create mode 100644 reftable/block.h
 create mode 100644 reftable/block_test.c
 create mode 100644 reftable/blocksource.h
 create mode 100644 reftable/bytes.c
 create mode 100644 reftable/config.h
 create mode 100644 reftable/constants.h
 create mode 100644 reftable/dump.c
 create mode 100644 reftable/file.c
 create mode 100644 reftable/iter.c
 create mode 100644 reftable/iter.h
 create mode 100644 reftable/merged.c
 create mode 100644 reftable/merged.h
 create mode 100644 reftable/merged_test.c
 create mode 100644 reftable/pq.c
 create mode 100644 reftable/pq.h
 create mode 100644 reftable/reader.c
 create mode 100644 reftable/reader.h
 create mode 100644 reftable/record.c
 create mode 100644 reftable/record.h
 create mode 100644 reftable/record_test.c
 create mode 100644 reftable/reftable.h
 create mode 100644 reftable/reftable_test.c
 create mode 100644 reftable/slice.c
 create mode 100644 reftable/slice.h
 create mode 100644 reftable/slice_test.c
 create mode 100644 reftable/stack.c
 create mode 100644 reftable/stack.h
 create mode 100644 reftable/stack_test.c
 create mode 100644 reftable/system.h
 create mode 100644 reftable/test_framework.c
 create mode 100644 reftable/test_framework.h
 create mode 100644 reftable/tree.c
 create mode 100644 reftable/tree.h
 create mode 100644 reftable/tree_test.c
 create mode 100644 reftable/writer.c
 create mode 100644 reftable/writer.h


base-commit: bc7a3d4dc04dd719e7c8c35ebd7a6e6651c5c5b6
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-539%2Fhanwen%2Freftable-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-539/hanwen/reftable-v2
Pull-Request: https://github.com/gitgitgadget/git/pull/539

Range-diff vs v1:

 1:  fd2baf1628 = 1:  174b98f6db setup.c: enable repo detection for reftable
 2:  bc643d0b0c = 2:  d7d642dcf6 create .git/refs in files-backend.c
 3:  1a01e0b1b5 = 3:  9cf185b51f Document how ref iterators and symrefs interact
 4:  3c86bd1d7e ! 4:  2106ff286b Add reftable library
     @@ -2,23 +2,92 @@
      
          Add reftable library
      
     +    Reftable is a new format for storing the ref database. It provides the
     +    following benefits:
     +
     +     * Simple and fast atomic ref transactions, including multiple refs and reflogs.
     +     * Compact storage of ref data.
     +     * Fast look ups of ref data.
     +     * Case-sensitive ref names on Windows/OSX, regardless of file system
     +     * Eliminates file/directory conflicts in ref names
     +
     +    Further context and motivation can be found in background reading:
     +
     +    * Spec: https://github.com/eclipse/jgit/blob/master/Documentation/technical/reftable.md
     +
     +    * Original discussion on JGit-dev:  https://www.eclipse.org/lists/jgit-dev/msg03389.html
     +
     +    * First design discussion on git@vger: https://public-inbox.org/git/CAJo=hJtTp2eA3z9wW9cHo-nA7kK40vVThqh6inXpbCcqfdMP9g@mail.gmail.com/
     +
     +    * Last design discussion on git@vger: https://public-inbox.org/git/CAJo=hJsZcAM9sipdVr7TMD-FD2V2W6_pvMQ791EGCDsDkQ033w@mail.gmail.com/
     +
     +    * First attempt at implementation: https://public-inbox.org/git/CAP8UFD0PPZSjBnxCA7ez91vBuatcHKQ+JUWvTD1iHcXzPBjPBg@mail.gmail.com/
     +
     +    * libgit2 support issue: https://github.com/libgit2/libgit2/issues
     +
     +    * GitLab support issue: https://gitlab.com/gitlab-org/git/issues/6
     +
     +    * go-git support issue: https://github.com/src-d/go-git/issues/1059
     +
          Signed-off-by: Han-Wen Nienhuys <hanwen@google.com>
          Change-Id: Id396ff42be8b42b9e11f194a32e2f95b8250c109
      
     + diff --git a/reftable/LICENSE b/reftable/LICENSE
     + new file mode 100644
     + --- /dev/null
     + +++ b/reftable/LICENSE
     +@@
     ++BSD License
     ++
     ++Copyright (c) 2020, Google LLC
     ++All rights reserved.
     ++
     ++Redistribution and use in source and binary forms, with or without
     ++modification, are permitted provided that the following conditions are
     ++met:
     ++
     ++* Redistributions of source code must retain the above copyright notice,
     ++this list of conditions and the following disclaimer.
     ++
     ++* Redistributions in binary form must reproduce the above copyright
     ++notice, this list of conditions and the following disclaimer in the
     ++documentation and/or other materials provided with the distribution.
     ++
     ++* Neither the name of Google LLC nor the names of its contributors may
     ++be used to endorse or promote products derived from this software
     ++without specific prior written permission.
     ++
     ++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     ++"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     ++LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     ++A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     ++OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     ++SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     ++LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     ++DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     ++THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     ++(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     ++OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     +
       diff --git a/reftable/README.md b/reftable/README.md
       new file mode 100644
       --- /dev/null
       +++ b/reftable/README.md
      @@
     -+The source code in this directory comes from https://github.com/google/reftable
     -+and can be updated by doing:
      +
     -+   rm -rf reftable-repo && \
     -+   git clone https://github.com/google/reftable reftable-repo && \
     ++The source code in this directory comes from https://github.com/google/reftable.
     ++
     ++The VERSION file keeps track of the current version of the reftable library.
     ++
     ++To update the library, do:
     ++
     ++   ((cd reftable-repo && git fetch origin && git checkout origin/master ) ||
     ++    git clone https://github.com/google/reftable reftable-repo) && \
      +   cp reftable-repo/c/*.[ch] reftable/ && \
     -+   cp reftable-repo/LICENSE reftable/
     ++   cp reftable-repo/LICENSE reftable/ &&
      +   git --git-dir reftable-repo/.git show --no-patch origin/master \
     -+      > reftable/VERSION
     ++    > reftable/VERSION && \
     ++   echo '/* empty */' > reftable/config.h
      +
      +Bugfixes should be accompanied by a test and applied to upstream project at
      +https://github.com/google/reftable.
     @@ -28,299 +97,11 @@
       --- /dev/null
       +++ b/reftable/VERSION
      @@
     -+commit 887250932a9c39a15aee26aef59df2300c77c03f
     ++commit c616d53b88657c3a5fe4d2e7243a48effc34c626
      +Author: Han-Wen Nienhuys <hanwen@google.com>
     -+Date:   Wed Jan 22 19:50:16 2020 +0100
     -+
     -+    C: implement reflog expiry
     -+
     -+diff --git a/c/reftable.h b/c/reftable.h
     -+index 2f44973..d760af9 100644
     -+--- a/c/reftable.h
     -++++ b/c/reftable.h
     -+@@ -359,8 +359,17 @@ void stack_destroy(struct stack *st);
     -+ /* reloads the stack if necessary. */
     -+ int stack_reload(struct stack *st);
     -+ 
     -+-/* compacts all reftables into a giant table. */
     -+-int stack_compact_all(struct stack *st);
     -++/* Policy for expiring reflog entries. */
     -++struct log_expiry_config {
     -++  /* Drop entries older than this timestamp */
     -++  uint64_t time;
     -++
     -++  /* Drop older entries */
     -++  uint64_t min_update_index;
     -++};
     -++
     -++/* compacts all reftables into a giant table. Expire reflog entries if config is non-NULL */
     -++int stack_compact_all(struct stack *st, struct log_expiry_config *config);
     -+ 
     -+ /* heuristically compact unbalanced table stack. */
     -+ int stack_auto_compact(struct stack *st);
     -+diff --git a/c/stack.c b/c/stack.c
     -+index d67828f..641ac52 100644
     -+--- a/c/stack.c
     -++++ b/c/stack.c
     -+@@ -119,7 +119,7 @@ static struct reader **stack_copy_readers(struct stack *st, int cur_len) {
     -+   return cur;
     -+ }
     -+ 
     -+-static int stack_reload_once(struct stack *st, char **names) {
     -++static int stack_reload_once(struct stack *st, char **names, bool reuse_open) {
     -+   int cur_len = st->merged == NULL ? 0 : st->merged->stack_len;
     -+   struct reader **cur = stack_copy_readers(st, cur_len);
     -+   int err = 0;
     -+@@ -136,7 +136,7 @@ static int stack_reload_once(struct stack *st, char **names) {
     -+ 
     -+     // this is linear; we assume compaction keeps the number of tables
     -+     // under control so this is not quadratic.
     -+-    for (int j = 0; j < cur_len; j++) {
     -++    for (int j = 0; reuse_open && j < cur_len; j++) {
     -+       if (cur[j] != NULL && 0 == strcmp(cur[j]->name, name)) {
     -+         rd = cur[j];
     -+         cur[j] = NULL;
     -+@@ -207,7 +207,7 @@ static int tv_cmp(struct timeval *a, struct timeval *b) {
     -+   return udiff;
     -+ }
     -+ 
     -+-int stack_reload(struct stack *st) {
     -++static int stack_reload_maybe_reuse(struct stack *st, bool reuse_open) {
     -+   struct timeval deadline = {};
     -+   int err = gettimeofday(&deadline, NULL);
     -+   int64_t delay = 0;
     -+@@ -238,7 +238,7 @@ int stack_reload(struct stack *st) {
     -+       free_names(names);
     -+       return err;
     -+     }
     -+-    err = stack_reload_once(st, names);
     -++    err = stack_reload_once(st, names, reuse_open);
     -+     if (err == 0) {
     -+       free_names(names);
     -+       break;
     -+@@ -269,6 +269,10 @@ int stack_reload(struct stack *st) {
     -+   return 0;
     -+ }
     -+ 
     -++int stack_reload(struct stack *st) {
     -++  return stack_reload_maybe_reuse(st, true);
     -++}
     -++
     -+ // -1 = error
     -+ // 0 = up to date
     -+ // 1 = changed.
     -+@@ -471,7 +475,7 @@ uint64_t stack_next_update_index(struct stack *st) {
     -+ }
     -+ 
     -+ static int stack_compact_locked(struct stack *st, int first, int last,
     -+-                                struct slice *temp_tab) {
     -++                                struct slice *temp_tab, struct log_expiry_config *config) {
     -+   struct slice next_name = {};
     -+   int tab_fd = -1;
     -+   struct writer *wr = NULL;
     -+@@ -488,7 +492,7 @@ static int stack_compact_locked(struct stack *st, int first, int last,
     -+   tab_fd = mkstemp((char *)slice_as_string(temp_tab));
     -+   wr = new_writer(fd_writer, &tab_fd, &st->config);
     -+ 
     -+-  err = stack_write_compact(st, wr, first, last);
     -++  err = stack_write_compact(st, wr, first, last, config);
     -+   if (err < 0) {
     -+     goto exit;
     -+   }
     -+@@ -515,7 +519,7 @@ static int stack_compact_locked(struct stack *st, int first, int last,
     -+ }
     -+ 
     -+ int stack_write_compact(struct stack *st, struct writer *wr, int first,
     -+-                        int last) {
     -++                        int last, struct log_expiry_config *config) {
     -+   int subtabs_len = last - first + 1;
     -+   struct reader **subtabs = calloc(sizeof(struct reader *), last - first + 1);
     -+   struct merged_table *mt = NULL;
     -+@@ -580,6 +584,16 @@ int stack_write_compact(struct stack *st, struct writer *wr, int first,
     -+       continue;
     -+     }
     -+ 
     -++    // XXX collect stats?
     -++
     -++    if (config != NULL && config->time > 0 && log.time < config->time) {
     -++      continue;
     -++    }
     -++
     -++    if (config != NULL && config->min_update_index > 0 && log.update_index < config->min_update_index) {
     -++      continue;
     -++    }
     -++
     -+     err = writer_add_log(wr, &log);
     -+     if (err < 0) {
     -+       break;
     -+@@ -599,7 +613,7 @@ int stack_write_compact(struct stack *st, struct writer *wr, int first,
     -+ }
     -+ 
     -+ // <  0: error. 0 == OK, > 0 attempt failed; could retry.
     -+-static int stack_compact_range(struct stack *st, int first, int last) {
     -++static int stack_compact_range(struct stack *st, int first, int last, struct log_expiry_config *expiry) {
     -+   struct slice temp_tab_name = {};
     -+   struct slice new_table_name = {};
     -+   struct slice lock_file_name = {};
     -+@@ -612,7 +626,7 @@ static int stack_compact_range(struct stack *st, int first, int last) {
     -+   char **delete_on_success = calloc(sizeof(char *), compact_count + 1);
     -+   char **subtable_locks = calloc(sizeof(char *), compact_count + 1);
     -+ 
     -+-  if (first >= last) {
     -++  if (first > last || (expiry == NULL && first == last)) {
     -+     err = 0;
     -+     goto exit;
     -+   }
     -+@@ -676,7 +690,7 @@ static int stack_compact_range(struct stack *st, int first, int last) {
     -+   }
     -+   have_lock = false;
     -+ 
     -+-  err = stack_compact_locked(st, first, last, &temp_tab_name);
     -++  err = stack_compact_locked(st, first, last, &temp_tab_name, expiry);
     -+   if (err < 0) {
     -+     goto exit;
     -+   }
     -+@@ -739,10 +753,12 @@ static int stack_compact_range(struct stack *st, int first, int last) {
     -+   have_lock = false;
     -+ 
     -+   for (char **p = delete_on_success; *p; p++) {
     -+-    unlink(*p);
     -++    if (0 != strcmp(*p, slice_as_string(&new_table_path))) {
     -++      unlink(*p);
     -++    }
     -+   }
     -+ 
     -+-  err = stack_reload(st);
     -++  err = stack_reload_maybe_reuse(st, first < last);
     -+ exit:
     -+   for (char **p = subtable_locks; *p; p++) {
     -+     unlink(*p);
     -+@@ -764,12 +780,12 @@ static int stack_compact_range(struct stack *st, int first, int last) {
     -+   return err;
     -+ }
     -+ 
     -+-int stack_compact_all(struct stack *st) {
     -+-  return stack_compact_range(st, 0, st->merged->stack_len - 1);
     -++int stack_compact_all(struct stack *st, struct log_expiry_config *config) {
     -++  return stack_compact_range(st, 0, st->merged->stack_len - 1, config);
     -+ }
     -+ 
     -+-static int stack_compact_range_stats(struct stack *st, int first, int last) {
     -+-  int err = stack_compact_range(st, first, last);
     -++static int stack_compact_range_stats(struct stack *st, int first, int last, struct log_expiry_config *config) {
     -++  int err = stack_compact_range(st, first, last, config);
     -+   if (err > 0) {
     -+     st->stats.failures++;
     -+   }
     -+@@ -856,7 +872,7 @@ int stack_auto_compact(struct stack *st) {
     -+   struct segment seg = suggest_compaction_segment(sizes, st->merged->stack_len);
     -+   free(sizes);
     -+   if (segment_size(&seg) > 0) {
     -+-    return stack_compact_range_stats(st, seg.start, seg.end - 1);
     -++    return stack_compact_range_stats(st, seg.start, seg.end - 1, NULL);
     -+   }
     -+ 
     -+   return 0;
     -+diff --git a/c/stack.h b/c/stack.h
     -+index 8cb2cb0..092a03b 100644
     -+--- a/c/stack.h
     -++++ b/c/stack.h
     -+@@ -25,7 +25,7 @@ int read_lines(const char *filename, char ***lines);
     -+ int stack_try_add(struct stack *st,
     -+                   int (*write_table)(struct writer *wr, void *arg), void *arg);
     -+ int stack_write_compact(struct stack *st, struct writer *wr, int first,
     -+-                        int last);
     -++                        int last, struct log_expiry_config *config);
     -+ int fastlog2(uint64_t sz);
     -+ 
     -+ struct segment {
     -+diff --git a/c/stack_test.c b/c/stack_test.c
     -+index c49d8b8..8c0cde0 100644
     -+--- a/c/stack_test.c
     -++++ b/c/stack_test.c
     -+@@ -121,7 +121,7 @@ void test_stack_add(void) {
     -+     assert_err(err);
     -+   }
     -+ 
     -+-  err = stack_compact_all(st);
     -++  err = stack_compact_all(st, NULL);
     -+   assert_err(err);
     -+ 
     -+   for (int i = 0; i < N; i++) {
     -+@@ -186,7 +186,73 @@ void test_suggest_compaction_segment(void) {
     -+   }
     -+ }
     -+ 
     -++void test_reflog_expire(void) {
     -++  char dir[256] = "/tmp/stack.XXXXXX";
     -++  assert(mkdtemp(dir));
     -++  printf("%s\n", dir);
     -++  char fn[256] = "";
     -++  strcat(fn, dir);
     -++  strcat(fn, "/refs");
     -++
     -++  struct write_options cfg = {};
     -++  struct stack *st = NULL;
     -++  int err = new_stack(&st, dir, fn, cfg);
     -++  assert_err(err);
     -++
     -++  struct log_record logs[20] = {};
     -++  int N = ARRAYSIZE(logs) -1;
     -++  for (int i = 1; i <= N; i++) {
     -++    char buf[256];
     -++    sprintf(buf, "branch%02d", i);
     -++
     -++    logs[i].ref_name = strdup(buf);
     -++    logs[i].update_index = i;
     -++    logs[i].time = i;
     -++    logs[i].new_hash = malloc(SHA1_SIZE);
     -++    logs[i].email = strdup("identity@invalid");
     -++    set_test_hash(logs[i].new_hash, i);
     -++  }
     -++
     -++  for (int i = 1; i <= N; i++) {
     -++    int err = stack_add(st, &write_test_log, &logs[i]);
     -++    assert_err(err);
     -++  }
     -++
     -++  err = stack_compact_all(st, NULL);
     -++  assert_err(err);
     -++
     -++  struct log_expiry_config expiry = {
     -++    .time = 10,
     -++  };
     -++  err = stack_compact_all(st, &expiry);
     -++  assert_err(err);
     -++
     -++  struct log_record log = {};
     -++  err = stack_read_log(st, logs[9].ref_name, &log);
     -++  assert(err == 1);
     -++
     -++  err = stack_read_log(st, logs[11].ref_name, &log);
     -++  assert_err(err);
     -++
     -++  expiry.min_update_index = 15;
     -++  err = stack_compact_all(st, &expiry);
     -++  assert_err(err);
     -++
     -++  err = stack_read_log(st, logs[14].ref_name, &log);
     -++  assert(err == 1);
     -++
     -++  err = stack_read_log(st, logs[16].ref_name, &log);
     -++  assert_err(err);
     -++
     -++  // cleanup
     -++  stack_destroy(st);
     -++  for (int i = 0; i < N; i++) {
     -++    log_record_clear(&logs[i]);
     -++  }
     -++}
     -++
     -+ int main() {
     -++  add_test_case("test_reflog_expire", test_reflog_expire);
     -+   add_test_case("test_suggest_compaction_segment",
     -+                 &test_suggest_compaction_segment);
     -+   add_test_case("test_sizes_to_segments", &test_sizes_to_segments);
     ++Date:   Mon Jan 27 15:05:43 2020 +0100
     ++
     ++    C: ban // comments
      
       diff --git a/reftable/basics.c b/reftable/basics.c
       new file mode 100644
     @@ -337,175 +118,191 @@
      +
      +#include "basics.h"
      +
     -+#include <stdio.h>
     -+#include <stdlib.h>
     -+#include <string.h>
     ++#include "system.h"
      +
     -+void put_u24(byte *out, uint32_t i) {
     -+  out[0] = (byte)((i >> 16) & 0xff);
     -+  out[1] = (byte)((i >> 8) & 0xff);
     -+  out[2] = (byte)((i)&0xff);
     ++void put_u24(byte *out, uint32_t i)
     ++{
     ++	out[0] = (byte)((i >> 16) & 0xff);
     ++	out[1] = (byte)((i >> 8) & 0xff);
     ++	out[2] = (byte)((i)&0xff);
      +}
      +
     -+uint32_t get_u24(byte *in) {
     -+  return (uint32_t)(in[0]) << 16 | (uint32_t)(in[1]) << 8 | (uint32_t)(in[2]);
     ++uint32_t get_u24(byte *in)
     ++{
     ++	return (uint32_t)(in[0]) << 16 | (uint32_t)(in[1]) << 8 |
     ++	       (uint32_t)(in[2]);
      +}
      +
     -+void put_u32(byte *out, uint32_t i) {
     -+  out[0] = (byte)((i >> 24) & 0xff);
     -+  out[1] = (byte)((i >> 16) & 0xff);
     -+  out[2] = (byte)((i >> 8) & 0xff);
     -+  out[3] = (byte)((i)&0xff);
     ++void put_u32(byte *out, uint32_t i)
     ++{
     ++	out[0] = (byte)((i >> 24) & 0xff);
     ++	out[1] = (byte)((i >> 16) & 0xff);
     ++	out[2] = (byte)((i >> 8) & 0xff);
     ++	out[3] = (byte)((i)&0xff);
      +}
      +
     -+uint32_t get_u32(byte *in) {
     -+  return (uint32_t)(in[0]) << 24 | (uint32_t)(in[1]) << 16 |
     -+         (uint32_t)(in[2]) << 8 | (uint32_t)(in[3]);
     ++uint32_t get_u32(byte *in)
     ++{
     ++	return (uint32_t)(in[0]) << 24 | (uint32_t)(in[1]) << 16 |
     ++	       (uint32_t)(in[2]) << 8 | (uint32_t)(in[3]);
      +}
      +
     -+void put_u64(byte *out, uint64_t v) {
     -+  for (int i = sizeof(uint64_t); i--;) {
     -+    out[i] = (byte)(v & 0xff);
     -+    v >>= 8;
     -+  }
     ++void put_u64(byte *out, uint64_t v)
     ++{
     ++	int i = 0;
     ++	for (i = sizeof(uint64_t); i--;) {
     ++		out[i] = (byte)(v & 0xff);
     ++		v >>= 8;
     ++	}
      +}
      +
     -+uint64_t get_u64(byte *out) {
     -+  uint64_t v = 0;
     -+  for (int i = 0; i < sizeof(uint64_t); i++) {
     -+    v = (v << 8) | (byte)(out[i] & 0xff);
     -+  }
     -+  return v;
     ++uint64_t get_u64(byte *out)
     ++{
     ++	uint64_t v = 0;
     ++	int i = 0;
     ++	for (i = 0; i < sizeof(uint64_t); i++) {
     ++		v = (v << 8) | (byte)(out[i] & 0xff);
     ++	}
     ++	return v;
      +}
      +
     -+void put_u16(byte *out, uint16_t i) {
     -+  out[0] = (byte)((i >> 8) & 0xff);
     -+  out[1] = (byte)((i)&0xff);
     ++void put_u16(byte *out, uint16_t i)
     ++{
     ++	out[0] = (byte)((i >> 8) & 0xff);
     ++	out[1] = (byte)((i)&0xff);
      +}
      +
     -+uint16_t get_u16(byte *in) {
     -+  return (uint32_t)(in[0]) << 8 | (uint32_t)(in[1]);
     ++uint16_t get_u16(byte *in)
     ++{
     ++	return (uint32_t)(in[0]) << 8 | (uint32_t)(in[1]);
      +}
      +
      +/*
      +  find smallest index i in [0, sz) at which f(i) is true, assuming
      +  that f is ascending. Return sz if f(i) is false for all indices.
      +*/
     -+int binsearch(int sz, int (*f)(int k, void *args), void *args) {
     -+  int lo = 0;
     -+  int hi = sz;
     -+
     -+  /* invariant: (hi == sz) || f(hi) == true
     -+     (lo == 0 && f(0) == true) || fi(lo) == false
     -+   */
     -+  while (hi - lo > 1) {
     -+    int mid = lo + (hi - lo) / 2;
     -+
     -+    int val = f(mid, args);
     -+    if (val) {
     -+      hi = mid;
     -+    } else {
     -+      lo = mid;
     -+    }
     -+  }
     -+
     -+  if (lo == 0) {
     -+    if (f(0, args)) {
     -+      return 0;
     -+    } else {
     -+      return 1;
     -+    }
     -+  }
     -+
     -+  return hi;
     -+}
     -+
     -+void free_names(char **a) {
     -+  char **p = a;
     -+  if (p == NULL) {
     -+    return;
     -+  }
     -+  while (*p) {
     -+    free(*p);
     -+    p++;
     -+  }
     -+  free(a);
     -+}
     -+
     -+int names_length(char **names) {
     -+  int len = 0;
     -+  for (char **p = names; *p; p++) {
     -+    len++;
     -+  }
     -+  return len;
     ++int binsearch(int sz, int (*f)(int k, void *args), void *args)
     ++{
     ++	int lo = 0;
     ++	int hi = sz;
     ++
     ++	/* invariant: (hi == sz) || f(hi) == true
     ++	   (lo == 0 && f(0) == true) || fi(lo) == false
     ++	 */
     ++	while (hi - lo > 1) {
     ++		int mid = lo + (hi - lo) / 2;
     ++
     ++		int val = f(mid, args);
     ++		if (val) {
     ++			hi = mid;
     ++		} else {
     ++			lo = mid;
     ++		}
     ++	}
     ++
     ++	if (lo == 0) {
     ++		if (f(0, args)) {
     ++			return 0;
     ++		} else {
     ++			return 1;
     ++		}
     ++	}
     ++
     ++	return hi;
     ++}
     ++
     ++void free_names(char **a)
     ++{
     ++	char **p = a;
     ++	if (p == NULL) {
     ++		return;
     ++	}
     ++	while (*p) {
     ++		free(*p);
     ++		p++;
     ++	}
     ++	free(a);
     ++}
     ++
     ++int names_length(char **names)
     ++{
     ++	int len = 0;
     ++	for (char **p = names; *p; p++) {
     ++		len++;
     ++	}
     ++	return len;
      +}
      +
      +/* parse a newline separated list of names. Empty names are discarded. */
     -+void parse_names(char *buf, int size, char ***namesp) {
     -+  char **names = NULL;
     -+  int names_cap = 0;
     -+  int names_len = 0;
     -+
     -+  char *p = buf;
     -+  char *end = buf + size;
     -+  while (p < end) {
     -+    char *next = strchr(p, '\n');
     -+    if (next != NULL) {
     -+      *next = 0;
     -+    } else {
     -+      next = end;
     -+    }
     -+    if (p < next) {
     -+      if (names_len == names_cap) {
     -+        names_cap = 2 * names_cap + 1;
     -+        names = realloc(names, names_cap * sizeof(char *));
     -+      }
     -+      names[names_len++] = strdup(p);
     -+    }
     -+    p = next + 1;
     -+  }
     -+
     -+  if (names_len == names_cap) {
     -+    names_cap = 2 * names_cap + 1;
     -+    names = realloc(names, names_cap * sizeof(char *));
     -+  }
     -+
     -+  names[names_len] = NULL;
     -+  *namesp = names;
     -+}
     -+
     -+int names_equal(char **a, char **b) {
     -+  while (*a && *b) {
     -+    if (0 != strcmp(*a, *b)) {
     -+      return 0;
     -+    }
     -+
     -+    a++;
     -+    b++;
     -+  }
     -+
     -+  return *a == *b;
     -+}
     -+
     -+const char *error_str(int err) {
     -+  switch (err) {
     -+    case IO_ERROR:
     -+      return "I/O error";
     -+    case FORMAT_ERROR:
     -+      return "FORMAT_ERROR";
     -+    case NOT_EXIST_ERROR:
     -+      return "NOT_EXIST_ERROR";
     -+    case LOCK_ERROR:
     -+      return "LOCK_ERROR";
     -+    case API_ERROR:
     -+      return "API_ERROR";
     -+    case ZLIB_ERROR:
     -+      return "ZLIB_ERROR";
     -+    case -1:
     -+      return "general error";
     -+    default:
     -+      return "unknown error code";
     -+  }
     ++void parse_names(char *buf, int size, char ***namesp)
     ++{
     ++	char **names = NULL;
     ++	int names_cap = 0;
     ++	int names_len = 0;
     ++
     ++	char *p = buf;
     ++	char *end = buf + size;
     ++	while (p < end) {
     ++		char *next = strchr(p, '\n');
     ++		if (next != NULL) {
     ++			*next = 0;
     ++		} else {
     ++			next = end;
     ++		}
     ++		if (p < next) {
     ++			if (names_len == names_cap) {
     ++				names_cap = 2 * names_cap + 1;
     ++				names = realloc(names,
     ++						names_cap * sizeof(char *));
     ++			}
     ++			names[names_len++] = strdup(p);
     ++		}
     ++		p = next + 1;
     ++	}
     ++
     ++	if (names_len == names_cap) {
     ++		names_cap = 2 * names_cap + 1;
     ++		names = realloc(names, names_cap * sizeof(char *));
     ++	}
     ++
     ++	names[names_len] = NULL;
     ++	*namesp = names;
     ++}
     ++
     ++int names_equal(char **a, char **b)
     ++{
     ++	while (*a && *b) {
     ++		if (strcmp(*a, *b)) {
     ++			return 0;
     ++		}
     ++
     ++		a++;
     ++		b++;
     ++	}
     ++
     ++	return *a == *b;
     ++}
     ++
     ++const char *error_str(int err)
     ++{
     ++	switch (err) {
     ++	case IO_ERROR:
     ++		return "I/O error";
     ++	case FORMAT_ERROR:
     ++		return "FORMAT_ERROR";
     ++	case NOT_EXIST_ERROR:
     ++		return "NOT_EXIST_ERROR";
     ++	case LOCK_ERROR:
     ++		return "LOCK_ERROR";
     ++	case API_ERROR:
     ++		return "API_ERROR";
     ++	case ZLIB_ERROR:
     ++		return "ZLIB_ERROR";
     ++	case -1:
     ++		return "general error";
     ++	default:
     ++		return "unknown error code";
     ++	}
      +}
      
       diff --git a/reftable/basics.h b/reftable/basics.h
     @@ -524,7 +321,7 @@
      +#ifndef BASICS_H
      +#define BASICS_H
      +
     -+#include <stdint.h>
     ++#include "system.h"
      +
      +#include "reftable.h"
      +
     @@ -567,10 +364,7 @@
      +
      +#include "block.h"
      +
     -+#include <assert.h>
     -+#include <stdio.h>
     -+#include <stdlib.h>
     -+#include <string.h>
     ++#include "system.h"
      +
      +#include "blocksource.h"
      +#include "constants.h"
     @@ -579,365 +373,387 @@
      +#include "zlib.h"
      +
      +int block_writer_register_restart(struct block_writer *w, int n, bool restart,
     -+                                  struct slice key);
     ++				  struct slice key);
      +
      +void block_writer_init(struct block_writer *bw, byte typ, byte *buf,
     -+                       uint32_t block_size, uint32_t header_off,
     -+                       int hash_size) {
     -+  bw->buf = buf;
     -+  bw->hash_size = hash_size;
     -+  bw->block_size = block_size;
     -+  bw->header_off = header_off;
     -+  bw->buf[header_off] = typ;
     -+  bw->next = header_off + 4;
     -+  bw->restart_interval = 16;
     -+  bw->entries = 0;
     ++		       uint32_t block_size, uint32_t header_off, int hash_size)
     ++{
     ++	bw->buf = buf;
     ++	bw->hash_size = hash_size;
     ++	bw->block_size = block_size;
     ++	bw->header_off = header_off;
     ++	bw->buf[header_off] = typ;
     ++	bw->next = header_off + 4;
     ++	bw->restart_interval = 16;
     ++	bw->entries = 0;
      +}
      +
     -+byte block_writer_type(struct block_writer *bw) {
     -+  return bw->buf[bw->header_off];
     ++byte block_writer_type(struct block_writer *bw)
     ++{
     ++	return bw->buf[bw->header_off];
      +}
      +
      +/* adds the record to the block. Returns -1 if it does not fit, 0 on
      +   success */
     -+int block_writer_add(struct block_writer *w, struct record rec) {
     -+  struct slice empty = {};
     -+  struct slice last =
     -+      w->entries % w->restart_interval == 0 ? empty : w->last_key;
     -+  struct slice out = {
     -+      .buf = w->buf + w->next,
     -+      .len = w->block_size - w->next,
     -+  };
     -+
     -+  struct slice start = out;
     -+
     -+  bool restart = false;
     -+  struct slice key = {};
     -+  int n = 0;
     -+
     -+  record_key(rec, &key);
     -+  n = encode_key(&restart, out, last, key, record_val_type(rec));
     -+  if (n < 0) {
     -+    goto err;
     -+  }
     -+  out.buf += n;
     -+  out.len -= n;
     -+
     -+  n = record_encode(rec, out, w->hash_size);
     -+  if (n < 0) {
     -+    goto err;
     -+  }
     -+
     -+  out.buf += n;
     -+  out.len -= n;
     -+
     -+  if (block_writer_register_restart(w, start.len - out.len, restart, key) < 0) {
     -+    goto err;
     -+  }
     -+
     -+  free(slice_yield(&key));
     -+  return 0;
     ++int block_writer_add(struct block_writer *w, struct record rec)
     ++{
     ++	struct slice empty = {};
     ++	struct slice last = w->entries % w->restart_interval == 0 ? empty :
     ++								    w->last_key;
     ++	struct slice out = {
     ++		.buf = w->buf + w->next,
     ++		.len = w->block_size - w->next,
     ++	};
     ++
     ++	struct slice start = out;
     ++
     ++	bool restart = false;
     ++	struct slice key = {};
     ++	int n = 0;
     ++
     ++	record_key(rec, &key);
     ++	n = encode_key(&restart, out, last, key, record_val_type(rec));
     ++	if (n < 0) {
     ++		goto err;
     ++	}
     ++	out.buf += n;
     ++	out.len -= n;
     ++
     ++	n = record_encode(rec, out, w->hash_size);
     ++	if (n < 0) {
     ++		goto err;
     ++	}
     ++
     ++	out.buf += n;
     ++	out.len -= n;
     ++
     ++	if (block_writer_register_restart(w, start.len - out.len, restart,
     ++					  key) < 0) {
     ++		goto err;
     ++	}
     ++
     ++	free(slice_yield(&key));
     ++	return 0;
      +
      +err:
     -+  free(slice_yield(&key));
     -+  return -1;
     ++	free(slice_yield(&key));
     ++	return -1;
      +}
      +
      +int block_writer_register_restart(struct block_writer *w, int n, bool restart,
     -+                                  struct slice key) {
     -+  int rlen = w->restart_len;
     -+  if (rlen >= MAX_RESTARTS) {
     -+    restart = false;
     -+  }
     -+
     -+  if (restart) {
     -+    rlen++;
     -+  }
     -+  if (2 + 3 * rlen + n > w->block_size - w->next) {
     -+    return -1;
     -+  }
     -+  if (restart) {
     -+    if (w->restart_len == w->restart_cap) {
     -+      w->restart_cap = w->restart_cap * 2 + 1;
     -+      w->restarts = realloc(w->restarts, sizeof(uint32_t) * w->restart_cap);
     -+    }
     -+
     -+    w->restarts[w->restart_len++] = w->next;
     -+  }
     -+
     -+  w->next += n;
     -+  slice_copy(&w->last_key, key);
     -+  w->entries++;
     -+  return 0;
     -+}
     -+
     -+int block_writer_finish(struct block_writer *w) {
     -+  for (int i = 0; i < w->restart_len; i++) {
     -+    put_u24(w->buf + w->next, w->restarts[i]);
     -+    w->next += 3;
     -+  }
     -+
     -+  put_u16(w->buf + w->next, w->restart_len);
     -+  w->next += 2;
     -+  put_u24(w->buf + 1 + w->header_off, w->next);
     -+
     -+  if (block_writer_type(w) == BLOCK_TYPE_LOG) {
     -+    int block_header_skip = 4 + w->header_off;
     -+    struct slice compressed = {};
     -+    uLongf dest_len = 0, src_len = 0;
     -+    slice_resize(&compressed, w->next - block_header_skip);
     -+
     -+    dest_len = compressed.len;
     -+    src_len = w->next - block_header_skip;
     -+
     -+    if (Z_OK != compress2(compressed.buf, &dest_len, w->buf + block_header_skip,
     -+                          src_len, 9)) {
     -+      free(slice_yield(&compressed));
     -+      return ZLIB_ERROR;
     -+    }
     -+    memcpy(w->buf + block_header_skip, compressed.buf, dest_len);
     -+    w->next = dest_len + block_header_skip;
     -+  }
     -+  return w->next;
     -+}
     -+
     -+byte block_reader_type(struct block_reader *r) {
     -+  return r->block.data[r->header_off];
     ++				  struct slice key)
     ++{
     ++	int rlen = w->restart_len;
     ++	if (rlen >= MAX_RESTARTS) {
     ++		restart = false;
     ++	}
     ++
     ++	if (restart) {
     ++		rlen++;
     ++	}
     ++	if (2 + 3 * rlen + n > w->block_size - w->next) {
     ++		return -1;
     ++	}
     ++	if (restart) {
     ++		if (w->restart_len == w->restart_cap) {
     ++			w->restart_cap = w->restart_cap * 2 + 1;
     ++			w->restarts = realloc(
     ++				w->restarts, sizeof(uint32_t) * w->restart_cap);
     ++		}
     ++
     ++		w->restarts[w->restart_len++] = w->next;
     ++	}
     ++
     ++	w->next += n;
     ++	slice_copy(&w->last_key, key);
     ++	w->entries++;
     ++	return 0;
     ++}
     ++
     ++int block_writer_finish(struct block_writer *w)
     ++{
     ++	int i = 0;
     ++	for (i = 0; i < w->restart_len; i++) {
     ++		put_u24(w->buf + w->next, w->restarts[i]);
     ++		w->next += 3;
     ++	}
     ++
     ++	put_u16(w->buf + w->next, w->restart_len);
     ++	w->next += 2;
     ++	put_u24(w->buf + 1 + w->header_off, w->next);
     ++
     ++	if (block_writer_type(w) == BLOCK_TYPE_LOG) {
     ++		int block_header_skip = 4 + w->header_off;
     ++		struct slice compressed = {};
     ++		uLongf dest_len = 0, src_len = 0;
     ++		slice_resize(&compressed, w->next - block_header_skip);
     ++
     ++		dest_len = compressed.len;
     ++		src_len = w->next - block_header_skip;
     ++
     ++		if (Z_OK != compress2(compressed.buf, &dest_len,
     ++				      w->buf + block_header_skip, src_len, 9)) {
     ++			free(slice_yield(&compressed));
     ++			return ZLIB_ERROR;
     ++		}
     ++		memcpy(w->buf + block_header_skip, compressed.buf, dest_len);
     ++		w->next = dest_len + block_header_skip;
     ++	}
     ++	return w->next;
     ++}
     ++
     ++byte block_reader_type(struct block_reader *r)
     ++{
     ++	return r->block.data[r->header_off];
      +}
      +
      +int block_reader_init(struct block_reader *br, struct block *block,
     -+                      uint32_t header_off, uint32_t table_block_size,
     -+                      int hash_size) {
     -+  uint32_t full_block_size = table_block_size;
     -+  byte typ = block->data[header_off];
     -+  uint32_t sz = get_u24(block->data + header_off + 1);
     -+
     -+  if (!is_block_type(typ)) {
     -+    return FORMAT_ERROR;
     -+  }
     -+
     -+  if (typ == BLOCK_TYPE_LOG) {
     -+    struct slice uncompressed = {};
     -+    int block_header_skip = 4 + header_off;
     -+    uLongf dst_len = sz - block_header_skip;
     -+    uLongf src_len = block->len - block_header_skip;
     -+
     -+    slice_resize(&uncompressed, sz);
     -+    memcpy(uncompressed.buf, block->data, block_header_skip);
     -+
     -+    if (Z_OK != uncompress2(uncompressed.buf + block_header_skip, &dst_len,
     -+                            block->data + block_header_skip, &src_len)) {
     -+      free(slice_yield(&uncompressed));
     -+      return ZLIB_ERROR;
     -+    }
     -+
     -+    block_source_return_block(block->source, block);
     -+    block->data = uncompressed.buf;
     -+    block->len = dst_len; /* XXX: 4 bytes missing? */
     -+    block->source = malloc_block_source();
     -+    full_block_size = src_len + block_header_skip;
     -+  } else if (full_block_size == 0) {
     -+    full_block_size = sz;
     -+  } else if (sz < full_block_size && sz < block->len && block->data[sz] != 0) {
     -+    // If the block is smaller than the full block size,
     -+    // it is padded (data followed by '\0') or the next
     -+    // block is unaligned.
     -+    full_block_size = sz;
     -+  }
     -+
     -+  {
     -+    uint16_t restart_count = get_u16(block->data + sz - 2);
     -+    uint32_t restart_start = sz - 2 - 3 * restart_count;
     -+
     -+    byte *restart_bytes = block->data + restart_start;
     -+
     -+    // transfer ownership.
     -+    br->block = *block;
     -+    block->data = NULL;
     -+    block->len = 0;
     -+
     -+    br->hash_size = hash_size;
     -+    br->block_len = restart_start;
     -+    br->full_block_size = full_block_size;
     -+    br->header_off = header_off;
     -+    br->restart_count = restart_count;
     -+    br->restart_bytes = restart_bytes;
     -+  }
     -+
     -+  return 0;
     -+}
     -+
     -+static uint32_t block_reader_restart_offset(struct block_reader *br, int i) {
     -+  return get_u24(br->restart_bytes + 3 * i);
     -+}
     -+
     -+void block_reader_start(struct block_reader *br, struct block_iter *it) {
     -+  it->br = br;
     -+  slice_resize(&it->last_key, 0);
     -+  it->next_off = br->header_off + 4;
     ++		      uint32_t header_off, uint32_t table_block_size,
     ++		      int hash_size)
     ++{
     ++	uint32_t full_block_size = table_block_size;
     ++	byte typ = block->data[header_off];
     ++	uint32_t sz = get_u24(block->data + header_off + 1);
     ++
     ++	if (!is_block_type(typ)) {
     ++		return FORMAT_ERROR;
     ++	}
     ++
     ++	if (typ == BLOCK_TYPE_LOG) {
     ++		struct slice uncompressed = {};
     ++		int block_header_skip = 4 + header_off;
     ++		uLongf dst_len = sz - block_header_skip;
     ++		uLongf src_len = block->len - block_header_skip;
     ++
     ++		slice_resize(&uncompressed, sz);
     ++		memcpy(uncompressed.buf, block->data, block_header_skip);
     ++
     ++		if (Z_OK !=
     ++		    uncompress2(uncompressed.buf + block_header_skip, &dst_len,
     ++				block->data + block_header_skip, &src_len)) {
     ++			free(slice_yield(&uncompressed));
     ++			return ZLIB_ERROR;
     ++		}
     ++
     ++		block_source_return_block(block->source, block);
     ++		block->data = uncompressed.buf;
     ++		block->len = dst_len; /* XXX: 4 bytes missing? */
     ++		block->source = malloc_block_source();
     ++		full_block_size = src_len + block_header_skip;
     ++	} else if (full_block_size == 0) {
     ++		full_block_size = sz;
     ++	} else if (sz < full_block_size && sz < block->len &&
     ++		   block->data[sz] != 0) {
     ++		/* If the block is smaller than the full block size, it is
     ++                   padded (data followed by '\0') or the next block is
     ++                   unaligned. */
     ++		full_block_size = sz;
     ++	}
     ++
     ++	{
     ++		uint16_t restart_count = get_u16(block->data + sz - 2);
     ++		uint32_t restart_start = sz - 2 - 3 * restart_count;
     ++
     ++		byte *restart_bytes = block->data + restart_start;
     ++
     ++		/* transfer ownership. */
     ++		br->block = *block;
     ++		block->data = NULL;
     ++		block->len = 0;
     ++
     ++		br->hash_size = hash_size;
     ++		br->block_len = restart_start;
     ++		br->full_block_size = full_block_size;
     ++		br->header_off = header_off;
     ++		br->restart_count = restart_count;
     ++		br->restart_bytes = restart_bytes;
     ++	}
     ++
     ++	return 0;
     ++}
     ++
     ++static uint32_t block_reader_restart_offset(struct block_reader *br, int i)
     ++{
     ++	return get_u24(br->restart_bytes + 3 * i);
     ++}
     ++
     ++void block_reader_start(struct block_reader *br, struct block_iter *it)
     ++{
     ++	it->br = br;
     ++	slice_resize(&it->last_key, 0);
     ++	it->next_off = br->header_off + 4;
      +}
      +
      +struct restart_find_args {
     -+  struct slice key;
     -+  struct block_reader *r;
     -+  int error;
     ++	struct slice key;
     ++	struct block_reader *r;
     ++	int error;
      +};
      +
     -+static int restart_key_less(int idx, void *args) {
     -+  struct restart_find_args *a = (struct restart_find_args *)args;
     -+  uint32_t off = block_reader_restart_offset(a->r, idx);
     -+  struct slice in = {
     -+      .buf = a->r->block.data + off,
     -+      .len = a->r->block_len - off,
     -+  };
     -+
     -+  /* the restart key is verbatim in the block, so this could avoid the
     -+     alloc for decoding the key */
     -+  struct slice rkey = {};
     -+  struct slice last_key = {};
     -+  byte unused_extra;
     -+  int n = decode_key(&rkey, &unused_extra, last_key, in);
     -+  if (n < 0) {
     -+    a->error = 1;
     -+    return -1;
     -+  }
     -+
     -+  {
     -+    int result = slice_compare(a->key, rkey);
     -+    free(slice_yield(&rkey));
     -+    return result;
     -+  }
     -+}
     -+
     -+void block_iter_copy_from(struct block_iter *dest, struct block_iter *src) {
     -+  dest->br = src->br;
     -+  dest->next_off = src->next_off;
     -+  slice_copy(&dest->last_key, src->last_key);
     -+}
     -+
     -+// return < 0 for error, 0 for OK, > 0 for EOF.
     -+int block_iter_next(struct block_iter *it, struct record rec) {
     -+  if (it->next_off >= it->br->block_len) {
     -+    return 1;
     -+  }
     -+
     -+  {
     -+    struct slice in = {
     -+        .buf = it->br->block.data + it->next_off,
     -+        .len = it->br->block_len - it->next_off,
     -+    };
     -+    struct slice start = in;
     -+    struct slice key = {};
     -+    byte extra;
     -+    int n = decode_key(&key, &extra, it->last_key, in);
     -+    if (n < 0) {
     -+      return -1;
     -+    }
     -+
     -+    in.buf += n;
     -+    in.len -= n;
     -+    n = record_decode(rec, key, extra, in, it->br->hash_size);
     -+    if (n < 0) {
     -+      return -1;
     -+    }
     -+    in.buf += n;
     -+    in.len -= n;
     -+
     -+    slice_copy(&it->last_key, key);
     -+    it->next_off += start.len - in.len;
     -+    free(slice_yield(&key));
     -+    return 0;
     -+  }
     -+}
     -+
     -+int block_reader_first_key(struct block_reader *br, struct slice *key) {
     -+  struct slice empty = {};
     -+  int off = br->header_off + 4;
     -+  struct slice in = {
     -+      .buf = br->block.data + off,
     -+      .len = br->block_len - off,
     -+  };
     -+
     -+  byte extra = 0;
     -+  int n = decode_key(key, &extra, empty, in);
     -+  if (n < 0) {
     -+    return n;
     -+  }
     -+  return 0;
     -+}
     -+
     -+int block_iter_seek(struct block_iter *it, struct slice want) {
     -+  return block_reader_seek(it->br, it, want);
     -+}
     -+
     -+void block_iter_close(struct block_iter *it) {
     -+  free(slice_yield(&it->last_key));
     ++static int restart_key_less(int idx, void *args)
     ++{
     ++	struct restart_find_args *a = (struct restart_find_args *)args;
     ++	uint32_t off = block_reader_restart_offset(a->r, idx);
     ++	struct slice in = {
     ++		.buf = a->r->block.data + off,
     ++		.len = a->r->block_len - off,
     ++	};
     ++
     ++	/* the restart key is verbatim in the block, so this could avoid the
     ++	   alloc for decoding the key */
     ++	struct slice rkey = {};
     ++	struct slice last_key = {};
     ++	byte unused_extra;
     ++	int n = decode_key(&rkey, &unused_extra, last_key, in);
     ++	if (n < 0) {
     ++		a->error = 1;
     ++		return -1;
     ++	}
     ++
     ++	{
     ++		int result = slice_compare(a->key, rkey);
     ++		free(slice_yield(&rkey));
     ++		return result;
     ++	}
     ++}
     ++
     ++void block_iter_copy_from(struct block_iter *dest, struct block_iter *src)
     ++{
     ++	dest->br = src->br;
     ++	dest->next_off = src->next_off;
     ++	slice_copy(&dest->last_key, src->last_key);
     ++}
     ++
     ++/* return < 0 for error, 0 for OK, > 0 for EOF. */
     ++int block_iter_next(struct block_iter *it, struct record rec)
     ++{
     ++	if (it->next_off >= it->br->block_len) {
     ++		return 1;
     ++	}
     ++
     ++	{
     ++		struct slice in = {
     ++			.buf = it->br->block.data + it->next_off,
     ++			.len = it->br->block_len - it->next_off,
     ++		};
     ++		struct slice start = in;
     ++		struct slice key = {};
     ++		byte extra;
     ++		int n = decode_key(&key, &extra, it->last_key, in);
     ++		if (n < 0) {
     ++			return -1;
     ++		}
     ++
     ++		in.buf += n;
     ++		in.len -= n;
     ++		n = record_decode(rec, key, extra, in, it->br->hash_size);
     ++		if (n < 0) {
     ++			return -1;
     ++		}
     ++		in.buf += n;
     ++		in.len -= n;
     ++
     ++		slice_copy(&it->last_key, key);
     ++		it->next_off += start.len - in.len;
     ++		free(slice_yield(&key));
     ++		return 0;
     ++	}
     ++}
     ++
     ++int block_reader_first_key(struct block_reader *br, struct slice *key)
     ++{
     ++	struct slice empty = {};
     ++	int off = br->header_off + 4;
     ++	struct slice in = {
     ++		.buf = br->block.data + off,
     ++		.len = br->block_len - off,
     ++	};
     ++
     ++	byte extra = 0;
     ++	int n = decode_key(key, &extra, empty, in);
     ++	if (n < 0) {
     ++		return n;
     ++	}
     ++	return 0;
     ++}
     ++
     ++int block_iter_seek(struct block_iter *it, struct slice want)
     ++{
     ++	return block_reader_seek(it->br, it, want);
     ++}
     ++
     ++void block_iter_close(struct block_iter *it)
     ++{
     ++	free(slice_yield(&it->last_key));
      +}
      +
      +int block_reader_seek(struct block_reader *br, struct block_iter *it,
     -+                      struct slice want) {
     -+  struct restart_find_args args = {
     -+      .key = want,
     -+      .r = br,
     -+  };
     -+
     -+  int i = binsearch(br->restart_count, &restart_key_less, &args);
     -+  if (args.error) {
     -+    return -1;
     -+  }
     -+
     -+  it->br = br;
     -+  if (i > 0) {
     -+    i--;
     -+    it->next_off = block_reader_restart_offset(br, i);
     -+  } else {
     -+    it->next_off = br->header_off + 4;
     -+  }
     -+
     -+  {
     -+    struct record rec = new_record(block_reader_type(br));
     -+    struct slice key = {};
     -+    int result = 0;
     -+    int err = 0;
     -+    struct block_iter next = {};
     -+    while (true) {
     -+      block_iter_copy_from(&next, it);
     -+
     -+      err = block_iter_next(&next, rec);
     -+      if (err < 0) {
     -+        result = -1;
     -+        goto exit;
     -+      }
     -+
     -+      record_key(rec, &key);
     -+      if (err > 0 || slice_compare(key, want) >= 0) {
     -+        result = 0;
     -+        goto exit;
     -+      }
     -+
     -+      block_iter_copy_from(it, &next);
     -+    }
     -+
     -+  exit:
     -+    free(slice_yield(&key));
     -+    free(slice_yield(&next.last_key));
     -+    record_clear(rec);
     -+    free(record_yield(&rec));
     -+
     -+    return result;
     -+  }
     -+}
     -+
     -+void block_writer_reset(struct block_writer *bw) {
     -+  bw->restart_len = 0;
     -+  bw->last_key.len = 0;
     -+}
     -+
     -+void block_writer_clear(struct block_writer *bw) {
     -+  free(bw->restarts);
     -+  bw->restarts = NULL;
     -+  free(slice_yield(&bw->last_key));
     -+  // the block is not owned.
     ++		      struct slice want)
     ++{
     ++	struct restart_find_args args = {
     ++		.key = want,
     ++		.r = br,
     ++	};
     ++
     ++	int i = binsearch(br->restart_count, &restart_key_less, &args);
     ++	if (args.error) {
     ++		return -1;
     ++	}
     ++
     ++	it->br = br;
     ++	if (i > 0) {
     ++		i--;
     ++		it->next_off = block_reader_restart_offset(br, i);
     ++	} else {
     ++		it->next_off = br->header_off + 4;
     ++	}
     ++
     ++	{
     ++		struct record rec = new_record(block_reader_type(br));
     ++		struct slice key = {};
     ++		int result = 0;
     ++		int err = 0;
     ++		struct block_iter next = {};
     ++		while (true) {
     ++			block_iter_copy_from(&next, it);
     ++
     ++			err = block_iter_next(&next, rec);
     ++			if (err < 0) {
     ++				result = -1;
     ++				goto exit;
     ++			}
     ++
     ++			record_key(rec, &key);
     ++			if (err > 0 || slice_compare(key, want) >= 0) {
     ++				result = 0;
     ++				goto exit;
     ++			}
     ++
     ++			block_iter_copy_from(it, &next);
     ++		}
     ++
     ++	exit:
     ++		free(slice_yield(&key));
     ++		free(slice_yield(&next.last_key));
     ++		record_clear(rec);
     ++		free(record_yield(&rec));
     ++
     ++		return result;
     ++	}
     ++}
     ++
     ++void block_writer_reset(struct block_writer *bw)
     ++{
     ++	bw->restart_len = 0;
     ++	bw->last_key.len = 0;
     ++}
     ++
     ++void block_writer_clear(struct block_writer *bw)
     ++{
     ++	free(bw->restarts);
     ++	bw->restarts = NULL;
     ++	free(slice_yield(&bw->last_key));
     ++	/* the block is not owned. */
      +}
      
       diff --git a/reftable/block.h b/reftable/block.h
     @@ -961,22 +777,22 @@
      +#include "reftable.h"
      +
      +struct block_writer {
     -+  byte *buf;
     -+  uint32_t block_size;
     -+  uint32_t header_off;
     -+  int restart_interval;
     -+  int hash_size;
     -+
     -+  uint32_t next;
     -+  uint32_t *restarts;
     -+  uint32_t restart_len;
     -+  uint32_t restart_cap;
     -+  struct slice last_key;
     -+  int entries;
     ++	byte *buf;
     ++	uint32_t block_size;
     ++	uint32_t header_off;
     ++	int restart_interval;
     ++	int hash_size;
     ++
     ++	uint32_t next;
     ++	uint32_t *restarts;
     ++	uint32_t restart_len;
     ++	uint32_t restart_cap;
     ++	struct slice last_key;
     ++	int entries;
      +};
      +
      +void block_writer_init(struct block_writer *bw, byte typ, byte *buf,
     -+                       uint32_t block_size, uint32_t header_off, int hash_size);
     ++		       uint32_t block_size, uint32_t header_off, int hash_size);
      +byte block_writer_type(struct block_writer *bw);
      +int block_writer_add(struct block_writer *w, struct record rec);
      +int block_writer_finish(struct block_writer *w);
     @@ -984,29 +800,29 @@
      +void block_writer_clear(struct block_writer *bw);
      +
      +struct block_reader {
     -+  uint32_t header_off;
     -+  struct block block;
     -+  int hash_size;
     -+
     -+  // size of the data, excluding restart data.
     -+  uint32_t block_len;
     -+  byte *restart_bytes;
     -+  uint32_t full_block_size;
     -+  uint16_t restart_count;
     ++	uint32_t header_off;
     ++	struct block block;
     ++	int hash_size;
     ++
     ++	/* size of the data, excluding restart data. */
     ++	uint32_t block_len;
     ++	byte *restart_bytes;
     ++	uint32_t full_block_size;
     ++	uint16_t restart_count;
      +};
      +
      +struct block_iter {
     -+  struct block_reader *br;
     -+  struct slice last_key;
     -+  uint32_t next_off;
     ++	struct block_reader *br;
     ++	struct slice last_key;
     ++	uint32_t next_off;
      +};
      +
      +int block_reader_init(struct block_reader *br, struct block *bl,
     -+                      uint32_t header_off, uint32_t table_block_size,
     -+                      int hash_size);
     ++		      uint32_t header_off, uint32_t table_block_size,
     ++		      int hash_size);
      +void block_reader_start(struct block_reader *br, struct block_iter *it);
      +int block_reader_seek(struct block_reader *br, struct block_iter *it,
     -+                      struct slice want);
     ++		      struct slice want);
      +byte block_reader_type(struct block_reader *r);
      +int block_reader_first_key(struct block_reader *br, struct slice *key);
      +
     @@ -1032,7 +848,7 @@
      +
      +#include "block.h"
      +
     -+#include <string.h>
     ++#include "system.h"
      +
      +#include "basics.h"
      +#include "constants.h"
     @@ -1041,131 +857,137 @@
      +#include "test_framework.h"
      +
      +struct binsearch_args {
     -+  int key;
     -+  int *arr;
     ++	int key;
     ++	int *arr;
      +};
      +
     -+static int binsearch_func(int i, void *void_args) {
     -+  struct binsearch_args *args = (struct binsearch_args *)void_args;
     -+
     -+  return args->key < args->arr[i];
     -+}
     -+
     -+void test_binsearch() {
     -+  int arr[] = {2, 4, 6, 8, 10};
     -+  int sz = ARRAYSIZE(arr);
     -+  struct binsearch_args args = {
     -+      .arr = arr,
     -+  };
     -+
     -+  for (int i = 1; i < 11; i++) {
     -+    args.key = i;
     -+    int res = binsearch(sz, &binsearch_func, &args);
     -+
     -+    if (res < sz) {
     -+      assert(args.key < arr[res]);
     -+      if (res > 0) {
     -+        assert(args.key >= arr[res - 1]);
     -+      }
     -+    } else {
     -+      assert(args.key == 10 || args.key == 11);
     -+    }
     -+  }
     -+}
     -+
     -+void test_block_read_write() {
     -+  const int header_off = 21;  // random
     -+  const int N = 30;
     -+  char *names[N];
     -+  const int block_size = 1024;
     -+  struct block block = {};
     -+  block.data = calloc(block_size, 1);
     -+  block.len = block_size;
     -+
     -+  struct block_writer bw = {};
     -+  block_writer_init(&bw, BLOCK_TYPE_REF, block.data, block_size, header_off,
     -+                    SHA1_SIZE);
     -+  struct ref_record ref = {};
     -+  struct record rec = {};
     -+  record_from_ref(&rec, &ref);
     -+
     -+  for (int i = 0; i < N; i++) {
     -+    char name[100];
     -+    sprintf(name, "branch%02d", i);
     -+
     -+    byte hash[SHA1_SIZE];
     -+    memset(hash, i, sizeof(hash));
     -+
     -+    ref.ref_name = name;
     -+    ref.value = hash;
     -+    names[i] = strdup(name);
     -+    int n = block_writer_add(&bw, rec);
     -+    ref.ref_name = NULL;
     -+    ref.value = NULL;
     -+    assert(n == 0);
     -+  }
     -+
     -+  int n = block_writer_finish(&bw);
     -+  assert(n > 0);
     -+
     -+  block_writer_clear(&bw);
     -+
     -+  struct block_reader br = {};
     -+  block_reader_init(&br, &block, header_off, block_size, SHA1_SIZE);
     -+
     -+  struct block_iter it = {};
     -+  block_reader_start(&br, &it);
     -+
     -+  int j = 0;
     -+  while (true) {
     -+    int r = block_iter_next(&it, rec);
     -+    assert(r >= 0);
     -+    if (r > 0) {
     -+      break;
     -+    }
     -+    assert_streq(names[j], ref.ref_name);
     -+    j++;
     -+  }
     -+
     -+  record_clear(rec);
     -+  block_iter_close(&it);
     -+
     -+  struct slice want = {};
     -+  for (int i = 0; i < N; i++) {
     -+    slice_set_string(&want, names[i]);
     -+
     -+    struct block_iter it = {};
     -+    int n = block_reader_seek(&br, &it, want);
     -+    assert(n == 0);
     -+
     -+    n = block_iter_next(&it, rec);
     -+    assert(n == 0);
     -+
     -+    assert_streq(names[i], ref.ref_name);
     -+
     -+    want.len--;
     -+    n = block_reader_seek(&br, &it, want);
     -+    assert(n == 0);
     -+
     -+    n = block_iter_next(&it, rec);
     -+    assert(n == 0);
     -+    assert_streq(names[10 * (i / 10)], ref.ref_name);
     -+
     -+    block_iter_close(&it);
     -+  }
     -+
     -+  record_clear(rec);
     -+  free(block.data);
     -+  free(slice_yield(&want));
     -+  for (int i = 0; i < N; i++) {
     -+    free(names[i]);
     -+  }
     -+}
     -+
     -+int main() {
     -+  add_test_case("binsearch", &test_binsearch);
     -+  add_test_case("block_read_write", &test_block_read_write);
     -+  test_main();
     ++static int binsearch_func(int i, void *void_args)
     ++{
     ++	struct binsearch_args *args = (struct binsearch_args *)void_args;
     ++
     ++	return args->key < args->arr[i];
     ++}
     ++
     ++void test_binsearch()
     ++{
     ++	int arr[] = { 2, 4, 6, 8, 10 };
     ++	int sz = ARRAYSIZE(arr);
     ++	struct binsearch_args args = {
     ++		.arr = arr,
     ++	};
     ++
     ++	int i = 0;
     ++	for (i = 1; i < 11; i++) {
     ++		args.key = i;
     ++		int res = binsearch(sz, &binsearch_func, &args);
     ++
     ++		if (res < sz) {
     ++			assert(args.key < arr[res]);
     ++			if (res > 0) {
     ++				assert(args.key >= arr[res - 1]);
     ++			}
     ++		} else {
     ++			assert(args.key == 10 || args.key == 11);
     ++		}
     ++	}
     ++}
     ++
     ++void test_block_read_write()
     ++{
     ++	const int header_off = 21; /* random */
     ++	const int N = 30;
     ++	char *names[N];
     ++	const int block_size = 1024;
     ++	struct block block = {};
     ++	block.data = calloc(block_size, 1);
     ++	block.len = block_size;
     ++
     ++	struct block_writer bw = {};
     ++	block_writer_init(&bw, BLOCK_TYPE_REF, block.data, block_size,
     ++			  header_off, SHA1_SIZE);
     ++	struct ref_record ref = {};
     ++	struct record rec = {};
     ++	record_from_ref(&rec, &ref);
     ++
     ++	int i = 0;
     ++	for (i = 0; i < N; i++) {
     ++		char name[100];
     ++		snprintf(name, sizeof(name), "branch%02d", i);
     ++
     ++		byte hash[SHA1_SIZE];
     ++		memset(hash, i, sizeof(hash));
     ++
     ++		ref.ref_name = name;
     ++		ref.value = hash;
     ++		names[i] = strdup(name);
     ++		int n = block_writer_add(&bw, rec);
     ++		ref.ref_name = NULL;
     ++		ref.value = NULL;
     ++		assert(n == 0);
     ++	}
     ++
     ++	int n = block_writer_finish(&bw);
     ++	assert(n > 0);
     ++
     ++	block_writer_clear(&bw);
     ++
     ++	struct block_reader br = {};
     ++	block_reader_init(&br, &block, header_off, block_size, SHA1_SIZE);
     ++
     ++	struct block_iter it = {};
     ++	block_reader_start(&br, &it);
     ++
     ++	int j = 0;
     ++	while (true) {
     ++		int r = block_iter_next(&it, rec);
     ++		assert(r >= 0);
     ++		if (r > 0) {
     ++			break;
     ++		}
     ++		assert_streq(names[j], ref.ref_name);
     ++		j++;
     ++	}
     ++
     ++	record_clear(rec);
     ++	block_iter_close(&it);
     ++
     ++	struct slice want = {};
     ++	for (i = 0; i < N; i++) {
     ++		slice_set_string(&want, names[i]);
     ++
     ++		struct block_iter it = {};
     ++		int n = block_reader_seek(&br, &it, want);
     ++		assert(n == 0);
     ++
     ++		n = block_iter_next(&it, rec);
     ++		assert(n == 0);
     ++
     ++		assert_streq(names[i], ref.ref_name);
     ++
     ++		want.len--;
     ++		n = block_reader_seek(&br, &it, want);
     ++		assert(n == 0);
     ++
     ++		n = block_iter_next(&it, rec);
     ++		assert(n == 0);
     ++		assert_streq(names[10 * (i / 10)], ref.ref_name);
     ++
     ++		block_iter_close(&it);
     ++	}
     ++
     ++	record_clear(rec);
     ++	free(block.data);
     ++	free(slice_yield(&want));
     ++	for (i = 0; i < N; i++) {
     ++		free(names[i]);
     ++	}
     ++}
     ++
     ++int main()
     ++{
     ++	add_test_case("binsearch", &test_binsearch);
     ++	add_test_case("block_read_write", &test_block_read_write);
     ++	test_main();
      +}
      
       diff --git a/reftable/blocksource.h b/reftable/blocksource.h
     @@ -1188,7 +1010,7 @@
      +
      +uint64_t block_source_size(struct block_source source);
      +int block_source_read_block(struct block_source source, struct block *dest,
     -+                            uint64_t off, uint32_t size);
     ++			    uint64_t off, uint32_t size);
      +void block_source_return_block(struct block_source source, struct block *ret);
      +void block_source_close(struct block_source source);
      +
     @@ -1197,6 +1019,13 @@
       diff --git a/reftable/bytes.c b/reftable/bytes.c
       new file mode 100644
      
     + diff --git a/reftable/config.h b/reftable/config.h
     + new file mode 100644
     + --- /dev/null
     + +++ b/reftable/config.h
     +@@
     ++/* empty */
     +
       diff --git a/reftable/constants.h b/reftable/constants.h
       new file mode 100644
       --- /dev/null
     @@ -1235,107 +1064,102 @@
       --- /dev/null
       +++ b/reftable/dump.c
      @@
     -+// Copyright 2020 Google Inc. All rights reserved.
     -+//
     -+// Licensed under the Apache License, Version 2.0 (the "License");
     -+// you may not use this file except in compliance with the License.
     -+// You may obtain a copy of the License at
     -+//
     -+//    http://www.apache.org/licenses/LICENSE-2.0
     -+//
     -+// Unless required by applicable law or agreed to in writing, software
     -+// distributed under the License is distributed on an "AS IS" BASIS,
     -+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     -+// See the License for the specific language governing permissions and
     -+// limitations under the License.
     ++/*
     ++Copyright 2020 Google LLC
      +
     -+#include <stdio.h>
     -+#include <string.h>
     -+#include <unistd.h>
     ++Use of this source code is governed by a BSD-style
     ++license that can be found in the LICENSE file or at
     ++https://developers.google.com/open-source/licenses/bsd
     ++*/
     ++
     ++#include "system.h"
      +
      +#include "reftable.h"
      +
     -+static int dump_table(const char *tablename) {
     -+  struct block_source src = {};
     -+  int err = block_source_from_file(&src, tablename);
     -+  if (err < 0) {
     -+    return err;
     -+  }
     -+
     -+  struct reader *r = NULL;
     -+  err = new_reader(&r, src, tablename);
     -+  if (err < 0) {
     -+    return err;
     -+  }
     -+
     -+  {
     -+    struct iterator it = {};
     -+    err = reader_seek_ref(r, &it, "");
     -+    if (err < 0) {
     -+      return err;
     -+    }
     -+
     -+    struct ref_record ref = {};
     -+    while (1) {
     -+      err = iterator_next_ref(it, &ref);
     -+      if (err > 0) {
     -+        break;
     -+      }
     -+      if (err < 0) {
     -+        return err;
     -+      }
     -+      ref_record_print(&ref, 20);
     -+    }
     -+    iterator_destroy(&it);
     -+    ref_record_clear(&ref);
     -+  }
     -+
     -+  {
     -+    struct iterator it = {};
     -+    err = reader_seek_log(r, &it, "");
     -+    if (err < 0) {
     -+      return err;
     -+    }
     -+    struct log_record log = {};
     -+    while (1) {
     -+      err = iterator_next_log(it, &log);
     -+      if (err > 0) {
     -+        break;
     -+      }
     -+      if (err < 0) {
     -+        return err;
     -+      }
     -+      log_record_print(&log, 20);
     -+    }
     -+    iterator_destroy(&it);
     -+    log_record_clear(&log);
     -+  }
     -+  return 0;
     -+}
     -+
     -+int main(int argc, char *argv[]) {
     -+  int opt;
     -+  const char *table = NULL;
     -+  while ((opt = getopt(argc, argv, "t:")) != -1) {
     -+    switch (opt) {
     -+      case 't':
     -+        table = strdup(optarg);
     -+        break;
     -+      case '?':
     -+        printf("usage: %s [-table tablefile]\n", argv[0]);
     -+        return 2;
     -+        break;
     -+    }
     -+  }
     -+
     -+  if (table != NULL) {
     -+    int err = dump_table(table);
     -+    if (err < 0) {
     -+      fprintf(stderr, "%s: %s: %s\n", argv[0], table, error_str(err));
     -+      return 1;
     -+    }
     -+  }
     -+  return 0;
     ++static int dump_table(const char *tablename)
     ++{
     ++	struct block_source src = {};
     ++	int err = block_source_from_file(&src, tablename);
     ++	if (err < 0) {
     ++		return err;
     ++	}
     ++
     ++	struct reader *r = NULL;
     ++	err = new_reader(&r, src, tablename);
     ++	if (err < 0) {
     ++		return err;
     ++	}
     ++
     ++	{
     ++		struct iterator it = {};
     ++		err = reader_seek_ref(r, &it, "");
     ++		if (err < 0) {
     ++			return err;
     ++		}
     ++
     ++		struct ref_record ref = {};
     ++		while (1) {
     ++			err = iterator_next_ref(it, &ref);
     ++			if (err > 0) {
     ++				break;
     ++			}
     ++			if (err < 0) {
     ++				return err;
     ++			}
     ++			ref_record_print(&ref, 20);
     ++		}
     ++		iterator_destroy(&it);
     ++		ref_record_clear(&ref);
     ++	}
     ++
     ++	{
     ++		struct iterator it = {};
     ++		err = reader_seek_log(r, &it, "");
     ++		if (err < 0) {
     ++			return err;
     ++		}
     ++		struct log_record log = {};
     ++		while (1) {
     ++			err = iterator_next_log(it, &log);
     ++			if (err > 0) {
     ++				break;
     ++			}
     ++			if (err < 0) {
     ++				return err;
     ++			}
     ++			log_record_print(&log, 20);
     ++		}
     ++		iterator_destroy(&it);
     ++		log_record_clear(&log);
     ++	}
     ++	return 0;
     ++}
     ++
     ++int main(int argc, char *argv[])
     ++{
     ++	int opt;
     ++	const char *table = NULL;
     ++	while ((opt = getopt(argc, argv, "t:")) != -1) {
     ++		switch (opt) {
     ++		case 't':
     ++			table = strdup(optarg);
     ++			break;
     ++		case '?':
     ++			printf("usage: %s [-table tablefile]\n", argv[0]);
     ++			return 2;
     ++			break;
     ++		}
     ++	}
     ++
     ++	if (table != NULL) {
     ++		int err = dump_table(table);
     ++		if (err < 0) {
     ++			fprintf(stderr, "%s: %s: %s\n", argv[0], table,
     ++				error_str(err));
     ++			return 1;
     ++		}
     ++	}
     ++	return 0;
      +}
      
       diff --git a/reftable/file.c b/reftable/file.c
     @@ -1351,15 +1175,7 @@
      +https://developers.google.com/open-source/licenses/bsd
      +*/
      +
     -+#include <assert.h>
     -+#include <errno.h>
     -+#include <fcntl.h>
     -+#include <stdio.h>
     -+#include <stdlib.h>
     -+#include <string.h>
     -+#include <sys/stat.h>
     -+#include <sys/types.h>
     -+#include <unistd.h>
     ++#include "system.h"
      +
      +#include "block.h"
      +#include "iter.h"
     @@ -1368,78 +1184,85 @@
      +#include "tree.h"
      +
      +struct file_block_source {
     -+  int fd;
     -+  uint64_t size;
     ++	int fd;
     ++	uint64_t size;
      +};
      +
     -+static uint64_t file_size(void *b) {
     -+  return ((struct file_block_source *)b)->size;
     ++static uint64_t file_size(void *b)
     ++{
     ++	return ((struct file_block_source *)b)->size;
      +}
      +
     -+static void file_return_block(void *b, struct block *dest) {
     -+  memset(dest->data, 0xff, dest->len);
     -+  free(dest->data);
     ++static void file_return_block(void *b, struct block *dest)
     ++{
     ++	memset(dest->data, 0xff, dest->len);
     ++	free(dest->data);
      +}
      +
     -+static void file_close(void *b) {
     -+  int fd = ((struct file_block_source *)b)->fd;
     -+  if (fd > 0) {
     -+    close(fd);
     -+    ((struct file_block_source *)b)->fd = 0;
     -+  }
     ++static void file_close(void *b)
     ++{
     ++	int fd = ((struct file_block_source *)b)->fd;
     ++	if (fd > 0) {
     ++		close(fd);
     ++		((struct file_block_source *)b)->fd = 0;
     ++	}
      +
     -+  free(b);
     ++	free(b);
      +}
      +
      +static int file_read_block(void *v, struct block *dest, uint64_t off,
     -+                           uint32_t size) {
     -+  struct file_block_source *b = (struct file_block_source *)v;
     -+  assert(off + size <= b->size);
     -+  dest->data = malloc(size);
     -+  if (pread(b->fd, dest->data, size, off) != size) {
     -+    return -1;
     -+  }
     -+  dest->len = size;
     -+  return size;
     ++			   uint32_t size)
     ++{
     ++	struct file_block_source *b = (struct file_block_source *)v;
     ++	assert(off + size <= b->size);
     ++	dest->data = malloc(size);
     ++	if (pread(b->fd, dest->data, size, off) != size) {
     ++		return -1;
     ++	}
     ++	dest->len = size;
     ++	return size;
      +}
      +
      +struct block_source_vtable file_vtable = {
     -+    .size = &file_size,
     -+    .read_block = &file_read_block,
     -+    .return_block = &file_return_block,
     -+    .close = &file_close,
     ++	.size = &file_size,
     ++	.read_block = &file_read_block,
     ++	.return_block = &file_return_block,
     ++	.close = &file_close,
      +};
      +
     -+int block_source_from_file(struct block_source *bs, const char *name) {
     -+  struct stat st = {};
     -+  int err = 0;
     -+  int fd = open(name, O_RDONLY);
     -+  if (fd < 0) {
     -+    if (errno == ENOENT) {
     -+      return NOT_EXIST_ERROR;
     -+    }
     -+    return -1;
     -+  }
     -+
     -+  err = fstat(fd, &st);
     -+  if (err < 0) {
     -+    return -1;
     -+  }
     -+
     -+  {
     -+    struct file_block_source *p = calloc(sizeof(struct file_block_source), 1);
     -+    p->size = st.st_size;
     -+    p->fd = fd;
     -+
     -+    bs->ops = &file_vtable;
     -+    bs->arg = p;
     -+  }
     -+  return 0;
     -+}
     -+
     -+int fd_writer(void *arg, byte *data, int sz) {
     -+  int *fdp = (int *)arg;
     -+  return write(*fdp, data, sz);
     ++int block_source_from_file(struct block_source *bs, const char *name)
     ++{
     ++	struct stat st = {};
     ++	int err = 0;
     ++	int fd = open(name, O_RDONLY);
     ++	if (fd < 0) {
     ++		if (errno == ENOENT) {
     ++			return NOT_EXIST_ERROR;
     ++		}
     ++		return -1;
     ++	}
     ++
     ++	err = fstat(fd, &st);
     ++	if (err < 0) {
     ++		return -1;
     ++	}
     ++
     ++	{
     ++		struct file_block_source *p =
     ++			calloc(sizeof(struct file_block_source), 1);
     ++		p->size = st.st_size;
     ++		p->fd = fd;
     ++
     ++		bs->ops = &file_vtable;
     ++		bs->arg = p;
     ++	}
     ++	return 0;
     ++}
     ++
     ++int fd_writer(void *arg, byte *data, int sz)
     ++{
     ++	int *fdp = (int *)arg;
     ++	return write(*fdp, data, sz);
      +}
      
       diff --git a/reftable/iter.c b/reftable/iter.c
     @@ -1457,206 +1280,225 @@
      +
      +#include "iter.h"
      +
     -+#include <stdlib.h>
     -+#include <string.h>
     ++#include "system.h"
      +
      +#include "block.h"
      +#include "constants.h"
      +#include "reader.h"
      +#include "reftable.h"
      +
     -+bool iterator_is_null(struct iterator it) { return it.ops == NULL; }
     ++bool iterator_is_null(struct iterator it)
     ++{
     ++	return it.ops == NULL;
     ++}
      +
     -+static int empty_iterator_next(void *arg, struct record rec) { return 1; }
     ++static int empty_iterator_next(void *arg, struct record rec)
     ++{
     ++	return 1;
     ++}
      +
     -+static void empty_iterator_close(void *arg) {}
     ++static void empty_iterator_close(void *arg)
     ++{
     ++}
      +
      +struct iterator_vtable empty_vtable = {
     -+    .next = &empty_iterator_next,
     -+    .close = &empty_iterator_close,
     ++	.next = &empty_iterator_next,
     ++	.close = &empty_iterator_close,
      +};
      +
     -+void iterator_set_empty(struct iterator *it) {
     -+  it->iter_arg = NULL;
     -+  it->ops = &empty_vtable;
     ++void iterator_set_empty(struct iterator *it)
     ++{
     ++	it->iter_arg = NULL;
     ++	it->ops = &empty_vtable;
      +}
      +
     -+int iterator_next(struct iterator it, struct record rec) {
     -+  return it.ops->next(it.iter_arg, rec);
     ++int iterator_next(struct iterator it, struct record rec)
     ++{
     ++	return it.ops->next(it.iter_arg, rec);
      +}
      +
     -+void iterator_destroy(struct iterator *it) {
     -+  if (it->ops == NULL) {
     -+    return;
     -+  }
     -+  it->ops->close(it->iter_arg);
     -+  it->ops = NULL;
     -+  free(it->iter_arg);
     -+  it->iter_arg = NULL;
     ++void iterator_destroy(struct iterator *it)
     ++{
     ++	if (it->ops == NULL) {
     ++		return;
     ++	}
     ++	it->ops->close(it->iter_arg);
     ++	it->ops = NULL;
     ++	free(it->iter_arg);
     ++	it->iter_arg = NULL;
      +}
      +
     -+int iterator_next_ref(struct iterator it, struct ref_record *ref) {
     -+  struct record rec = {};
     -+  record_from_ref(&rec, ref);
     -+  return iterator_next(it, rec);
     ++int iterator_next_ref(struct iterator it, struct ref_record *ref)
     ++{
     ++	struct record rec = {};
     ++	record_from_ref(&rec, ref);
     ++	return iterator_next(it, rec);
      +}
      +
     -+int iterator_next_log(struct iterator it, struct log_record *log) {
     -+  struct record rec = {};
     -+  record_from_log(&rec, log);
     -+  return iterator_next(it, rec);
     ++int iterator_next_log(struct iterator it, struct log_record *log)
     ++{
     ++	struct record rec = {};
     ++	record_from_log(&rec, log);
     ++	return iterator_next(it, rec);
      +}
      +
     -+static void filtering_ref_iterator_close(void *iter_arg) {
     -+  struct filtering_ref_iterator *fri =
     -+      (struct filtering_ref_iterator *)iter_arg;
     -+  free(slice_yield(&fri->oid));
     -+  iterator_destroy(&fri->it);
     ++static void filtering_ref_iterator_close(void *iter_arg)
     ++{
     ++	struct filtering_ref_iterator *fri =
     ++		(struct filtering_ref_iterator *)iter_arg;
     ++	free(slice_yield(&fri->oid));
     ++	iterator_destroy(&fri->it);
      +}
      +
     -+static int filtering_ref_iterator_next(void *iter_arg, struct record rec) {
     -+  struct filtering_ref_iterator *fri =
     -+      (struct filtering_ref_iterator *)iter_arg;
     -+  struct ref_record *ref = (struct ref_record *)rec.data;
     ++static int filtering_ref_iterator_next(void *iter_arg, struct record rec)
     ++{
     ++	struct filtering_ref_iterator *fri =
     ++		(struct filtering_ref_iterator *)iter_arg;
     ++	struct ref_record *ref = (struct ref_record *)rec.data;
      +
     -+  while (true) {
     -+    int err = iterator_next_ref(fri->it, ref);
     -+    if (err != 0) {
     -+      return err;
     -+    }
     ++	while (true) {
     ++		int err = iterator_next_ref(fri->it, ref);
     ++		if (err != 0) {
     ++			return err;
     ++		}
      +
     -+    if (fri->double_check) {
     -+      struct iterator it = {};
     ++		if (fri->double_check) {
     ++			struct iterator it = {};
      +
     -+      int err = reader_seek_ref(fri->r, &it, ref->ref_name);
     -+      if (err == 0) {
     -+        err = iterator_next_ref(it, ref);
     -+      }
     ++			int err = reader_seek_ref(fri->r, &it, ref->ref_name);
     ++			if (err == 0) {
     ++				err = iterator_next_ref(it, ref);
     ++			}
      +
     -+      iterator_destroy(&it);
     ++			iterator_destroy(&it);
      +
     -+      if (err < 0) {
     -+        return err;
     -+      }
     ++			if (err < 0) {
     ++				return err;
     ++			}
      +
     -+      if (err > 0) {
     -+        continue;
     -+      }
     -+    }
     ++			if (err > 0) {
     ++				continue;
     ++			}
     ++		}
      +
     -+    if ((ref->target_value != NULL &&
     -+         0 == memcmp(fri->oid.buf, ref->target_value, fri->oid.len)) ||
     -+        (ref->value != NULL &&
     -+         0 == memcmp(fri->oid.buf, ref->value, fri->oid.len))) {
     -+      return 0;
     -+    }
     -+  }
     ++		if ((ref->target_value != NULL &&
     ++		     !memcmp(fri->oid.buf, ref->target_value, fri->oid.len)) ||
     ++		    (ref->value != NULL &&
     ++		     !memcmp(fri->oid.buf, ref->value, fri->oid.len))) {
     ++			return 0;
     ++		}
     ++	}
      +}
      +
      +struct iterator_vtable filtering_ref_iterator_vtable = {
     -+    .next = &filtering_ref_iterator_next,
     -+    .close = &filtering_ref_iterator_close,
     ++	.next = &filtering_ref_iterator_next,
     ++	.close = &filtering_ref_iterator_close,
      +};
      +
      +void iterator_from_filtering_ref_iterator(struct iterator *it,
     -+                                          struct filtering_ref_iterator *fri) {
     -+  it->iter_arg = fri;
     -+  it->ops = &filtering_ref_iterator_vtable;
     -+}
     -+
     -+static void indexed_table_ref_iter_close(void *p) {
     -+  struct indexed_table_ref_iter *it = (struct indexed_table_ref_iter *)p;
     -+  block_iter_close(&it->cur);
     -+  reader_return_block(it->r, &it->block_reader.block);
     -+  free(slice_yield(&it->oid));
     -+}
     -+
     -+static int indexed_table_ref_iter_next_block(
     -+    struct indexed_table_ref_iter *it) {
     -+  if (it->offset_idx == it->offset_len) {
     -+    it->finished = true;
     -+    return 1;
     -+  }
     -+
     -+  reader_return_block(it->r, &it->block_reader.block);
     -+
     -+  {
     -+    uint64_t off = it->offsets[it->offset_idx++];
     -+    int err =
     -+        reader_init_block_reader(it->r, &it->block_reader, off, BLOCK_TYPE_REF);
     -+    if (err < 0) {
     -+      return err;
     -+    }
     -+    if (err > 0) {
     -+      // indexed block does not exist.
     -+      return FORMAT_ERROR;
     -+    }
     -+  }
     -+  block_reader_start(&it->block_reader, &it->cur);
     -+  return 0;
     -+}
     -+
     -+static int indexed_table_ref_iter_next(void *p, struct record rec) {
     -+  struct indexed_table_ref_iter *it = (struct indexed_table_ref_iter *)p;
     -+  struct ref_record *ref = (struct ref_record *)rec.data;
     -+
     -+  while (true) {
     -+    int err = block_iter_next(&it->cur, rec);
     -+    if (err < 0) {
     -+      return err;
     -+    }
     -+
     -+    if (err > 0) {
     -+      err = indexed_table_ref_iter_next_block(it);
     -+      if (err < 0) {
     -+        return err;
     -+      }
     -+
     -+      if (it->finished) {
     -+        return 1;
     -+      }
     -+      continue;
     -+    }
     -+
     -+    if (0 == memcmp(it->oid.buf, ref->target_value, it->oid.len) ||
     -+        0 == memcmp(it->oid.buf, ref->value, it->oid.len)) {
     -+      return 0;
     -+    }
     -+  }
     ++					  struct filtering_ref_iterator *fri)
     ++{
     ++	it->iter_arg = fri;
     ++	it->ops = &filtering_ref_iterator_vtable;
     ++}
     ++
     ++static void indexed_table_ref_iter_close(void *p)
     ++{
     ++	struct indexed_table_ref_iter *it = (struct indexed_table_ref_iter *)p;
     ++	block_iter_close(&it->cur);
     ++	reader_return_block(it->r, &it->block_reader.block);
     ++	free(slice_yield(&it->oid));
     ++}
     ++
     ++static int indexed_table_ref_iter_next_block(struct indexed_table_ref_iter *it)
     ++{
     ++	if (it->offset_idx == it->offset_len) {
     ++		it->finished = true;
     ++		return 1;
     ++	}
     ++
     ++	reader_return_block(it->r, &it->block_reader.block);
     ++
     ++	{
     ++		uint64_t off = it->offsets[it->offset_idx++];
     ++		int err = reader_init_block_reader(it->r, &it->block_reader,
     ++						   off, BLOCK_TYPE_REF);
     ++		if (err < 0) {
     ++			return err;
     ++		}
     ++		if (err > 0) {
     ++			/* indexed block does not exist. */
     ++			return FORMAT_ERROR;
     ++		}
     ++	}
     ++	block_reader_start(&it->block_reader, &it->cur);
     ++	return 0;
     ++}
     ++
     ++static int indexed_table_ref_iter_next(void *p, struct record rec)
     ++{
     ++	struct indexed_table_ref_iter *it = (struct indexed_table_ref_iter *)p;
     ++	struct ref_record *ref = (struct ref_record *)rec.data;
     ++
     ++	while (true) {
     ++		int err = block_iter_next(&it->cur, rec);
     ++		if (err < 0) {
     ++			return err;
     ++		}
     ++
     ++		if (err > 0) {
     ++			err = indexed_table_ref_iter_next_block(it);
     ++			if (err < 0) {
     ++				return err;
     ++			}
     ++
     ++			if (it->finished) {
     ++				return 1;
     ++			}
     ++			continue;
     ++		}
     ++
     ++		if (!memcmp(it->oid.buf, ref->target_value, it->oid.len) ||
     ++		    !memcmp(it->oid.buf, ref->value, it->oid.len)) {
     ++			return 0;
     ++		}
     ++	}
      +}
      +
      +int new_indexed_table_ref_iter(struct indexed_table_ref_iter **dest,
     -+                               struct reader *r, byte *oid, int oid_len,
     -+                               uint64_t *offsets, int offset_len) {
     -+  struct indexed_table_ref_iter *itr =
     -+      calloc(sizeof(struct indexed_table_ref_iter), 1);
     -+  int err = 0;
     ++			       struct reader *r, byte *oid, int oid_len,
     ++			       uint64_t *offsets, int offset_len)
     ++{
     ++	struct indexed_table_ref_iter *itr =
     ++		calloc(sizeof(struct indexed_table_ref_iter), 1);
     ++	int err = 0;
      +
     -+  itr->r = r;
     -+  slice_resize(&itr->oid, oid_len);
     -+  memcpy(itr->oid.buf, oid, oid_len);
     ++	itr->r = r;
     ++	slice_resize(&itr->oid, oid_len);
     ++	memcpy(itr->oid.buf, oid, oid_len);
      +
     -+  itr->offsets = offsets;
     -+  itr->offset_len = offset_len;
     ++	itr->offsets = offsets;
     ++	itr->offset_len = offset_len;
      +
     -+  err = indexed_table_ref_iter_next_block(itr);
     -+  if (err < 0) {
     -+    free(itr);
     -+  } else {
     -+    *dest = itr;
     -+  }
     -+  return err;
     ++	err = indexed_table_ref_iter_next_block(itr);
     ++	if (err < 0) {
     ++		free(itr);
     ++	} else {
     ++		*dest = itr;
     ++	}
     ++	return err;
      +}
      +
      +struct iterator_vtable indexed_table_ref_iter_vtable = {
     -+    .next = &indexed_table_ref_iter_next,
     -+    .close = &indexed_table_ref_iter_close,
     ++	.next = &indexed_table_ref_iter_next,
     ++	.close = &indexed_table_ref_iter_close,
      +};
      +
      +void iterator_from_indexed_table_ref_iter(struct iterator *it,
     -+                                          struct indexed_table_ref_iter *itr) {
     -+  it->iter_arg = itr;
     -+  it->ops = &indexed_table_ref_iter_vtable;
     ++					  struct indexed_table_ref_iter *itr)
     ++{
     ++	it->iter_arg = itr;
     ++	it->ops = &indexed_table_ref_iter_vtable;
      +}
      
       diff --git a/reftable/iter.h b/reftable/iter.h
     @@ -1680,8 +1522,8 @@
      +#include "slice.h"
      +
      +struct iterator_vtable {
     -+  int (*next)(void *iter_arg, struct record rec);
     -+  void (*close)(void *iter_arg);
     ++	int (*next)(void *iter_arg, struct record rec);
     ++	void (*close)(void *iter_arg);
      +};
      +
      +void iterator_set_empty(struct iterator *it);
     @@ -1689,35 +1531,35 @@
      +bool iterator_is_null(struct iterator it);
      +
      +struct filtering_ref_iterator {
     -+  struct reader *r;
     -+  struct slice oid;
     -+  bool double_check;
     -+  struct iterator it;
     ++	struct reader *r;
     ++	struct slice oid;
     ++	bool double_check;
     ++	struct iterator it;
      +};
      +
      +void iterator_from_filtering_ref_iterator(struct iterator *,
     -+                                          struct filtering_ref_iterator *);
     ++					  struct filtering_ref_iterator *);
      +
      +struct indexed_table_ref_iter {
     -+  struct reader *r;
     -+  struct slice oid;
     -+
     -+  // mutable
     -+  uint64_t *offsets;
     -+
     -+  // Points to the next offset to read.
     -+  int offset_idx;
     -+  int offset_len;
     -+  struct block_reader block_reader;
     -+  struct block_iter cur;
     -+  bool finished;
     ++	struct reader *r;
     ++	struct slice oid;
     ++
     ++	/* mutable */
     ++	uint64_t *offsets;
     ++
     ++	/* Points to the next offset to read. */
     ++	int offset_idx;
     ++	int offset_len;
     ++	struct block_reader block_reader;
     ++	struct block_iter cur;
     ++	bool finished;
      +};
      +
      +void iterator_from_indexed_table_ref_iter(struct iterator *it,
     -+                                          struct indexed_table_ref_iter *itr);
     ++					  struct indexed_table_ref_iter *itr);
      +int new_indexed_table_ref_iter(struct indexed_table_ref_iter **dest,
     -+                               struct reader *r, byte *oid, int oid_len,
     -+                               uint64_t *offsets, int offset_len);
     ++			       struct reader *r, byte *oid, int oid_len,
     ++			       uint64_t *offsets, int offset_len);
      +
      +#endif
      
     @@ -1736,257 +1578,283 @@
      +
      +#include "merged.h"
      +
     -+#include <stdlib.h>
     ++#include "system.h"
      +
      +#include "constants.h"
      +#include "iter.h"
      +#include "pq.h"
      +#include "reader.h"
      +
     -+static int merged_iter_init(struct merged_iter *mi) {
     -+  for (int i = 0; i < mi->stack_len; i++) {
     -+    struct record rec = new_record(mi->typ);
     -+    int err = iterator_next(mi->stack[i], rec);
     -+    if (err < 0) {
     -+      return err;
     -+    }
     -+
     -+    if (err > 0) {
     -+      iterator_destroy(&mi->stack[i]);
     -+      record_clear(rec);
     -+      free(record_yield(&rec));
     -+    } else {
     -+      struct pq_entry e = {
     -+          .rec = rec,
     -+          .index = i,
     -+      };
     -+      merged_iter_pqueue_add(&mi->pq, e);
     -+    }
     -+  }
     -+
     -+  return 0;
     -+}
     -+
     -+static void merged_iter_close(void *p) {
     -+  struct merged_iter *mi = (struct merged_iter *)p;
     -+  merged_iter_pqueue_clear(&mi->pq);
     -+  for (int i = 0; i < mi->stack_len; i++) {
     -+    iterator_destroy(&mi->stack[i]);
     -+  }
     -+  free(mi->stack);
     -+}
     -+
     -+static int merged_iter_advance_subiter(struct merged_iter *mi, int idx) {
     -+  if (iterator_is_null(mi->stack[idx])) {
     -+    return 0;
     -+  }
     -+
     -+  {
     -+    struct record rec = new_record(mi->typ);
     -+    struct pq_entry e = {
     -+        .rec = rec,
     -+        .index = idx,
     -+    };
     -+    int err = iterator_next(mi->stack[idx], rec);
     -+    if (err < 0) {
     -+      return err;
     -+    }
     -+
     -+    if (err > 0) {
     -+      iterator_destroy(&mi->stack[idx]);
     -+      record_clear(rec);
     -+      free(record_yield(&rec));
     -+      return 0;
     -+    }
     -+
     -+    merged_iter_pqueue_add(&mi->pq, e);
     -+  }
     -+  return 0;
     -+}
     -+
     -+static int merged_iter_next(struct merged_iter *mi, struct record rec) {
     -+  struct slice entry_key = {};
     -+  struct pq_entry entry = merged_iter_pqueue_remove(&mi->pq);
     -+  int err = merged_iter_advance_subiter(mi, entry.index);
     -+  if (err < 0) {
     -+    return err;
     -+  }
     -+
     -+  record_key(entry.rec, &entry_key);
     -+  while (!merged_iter_pqueue_is_empty(mi->pq)) {
     -+    struct pq_entry top = merged_iter_pqueue_top(mi->pq);
     -+    struct slice k = {};
     -+    int err = 0, cmp = 0;
     -+
     -+    record_key(top.rec, &k);
     -+
     -+    cmp = slice_compare(k, entry_key);
     -+    free(slice_yield(&k));
     -+
     -+    if (cmp > 0) {
     -+      break;
     -+    }
     -+
     -+    merged_iter_pqueue_remove(&mi->pq);
     -+    err = merged_iter_advance_subiter(mi, top.index);
     -+    if (err < 0) {
     -+      return err;
     -+    }
     -+    record_clear(top.rec);
     -+    free(record_yield(&top.rec));
     -+  }
     -+
     -+  record_copy_from(rec, entry.rec, mi->hash_size);
     -+  record_clear(entry.rec);
     -+  free(record_yield(&entry.rec));
     -+  free(slice_yield(&entry_key));
     -+  return 0;
     -+}
     -+
     -+static int merged_iter_next_void(void *p, struct record rec) {
     -+  struct merged_iter *mi = (struct merged_iter *)p;
     -+  if (merged_iter_pqueue_is_empty(mi->pq)) {
     -+    return 1;
     -+  }
     -+
     -+  return merged_iter_next(mi, rec);
     ++static int merged_iter_init(struct merged_iter *mi)
     ++{
     ++	int i = 0;
     ++	for (i = 0; i < mi->stack_len; i++) {
     ++		struct record rec = new_record(mi->typ);
     ++		int err = iterator_next(mi->stack[i], rec);
     ++		if (err < 0) {
     ++			return err;
     ++		}
     ++
     ++		if (err > 0) {
     ++			iterator_destroy(&mi->stack[i]);
     ++			record_clear(rec);
     ++			free(record_yield(&rec));
     ++		} else {
     ++			struct pq_entry e = {
     ++				.rec = rec,
     ++				.index = i,
     ++			};
     ++			merged_iter_pqueue_add(&mi->pq, e);
     ++		}
     ++	}
     ++
     ++	return 0;
     ++}
     ++
     ++static void merged_iter_close(void *p)
     ++{
     ++	struct merged_iter *mi = (struct merged_iter *)p;
     ++	int i = 0;
     ++	merged_iter_pqueue_clear(&mi->pq);
     ++	for (i = 0; i < mi->stack_len; i++) {
     ++		iterator_destroy(&mi->stack[i]);
     ++	}
     ++	free(mi->stack);
     ++}
     ++
     ++static int merged_iter_advance_subiter(struct merged_iter *mi, int idx)
     ++{
     ++	if (iterator_is_null(mi->stack[idx])) {
     ++		return 0;
     ++	}
     ++
     ++	{
     ++		struct record rec = new_record(mi->typ);
     ++		struct pq_entry e = {
     ++			.rec = rec,
     ++			.index = idx,
     ++		};
     ++		int err = iterator_next(mi->stack[idx], rec);
     ++		if (err < 0) {
     ++			return err;
     ++		}
     ++
     ++		if (err > 0) {
     ++			iterator_destroy(&mi->stack[idx]);
     ++			record_clear(rec);
     ++			free(record_yield(&rec));
     ++			return 0;
     ++		}
     ++
     ++		merged_iter_pqueue_add(&mi->pq, e);
     ++	}
     ++	return 0;
     ++}
     ++
     ++static int merged_iter_next(struct merged_iter *mi, struct record rec)
     ++{
     ++	struct slice entry_key = {};
     ++	struct pq_entry entry = merged_iter_pqueue_remove(&mi->pq);
     ++	int err = merged_iter_advance_subiter(mi, entry.index);
     ++	if (err < 0) {
     ++		return err;
     ++	}
     ++
     ++	record_key(entry.rec, &entry_key);
     ++	while (!merged_iter_pqueue_is_empty(mi->pq)) {
     ++		struct pq_entry top = merged_iter_pqueue_top(mi->pq);
     ++		struct slice k = {};
     ++		int err = 0, cmp = 0;
     ++
     ++		record_key(top.rec, &k);
     ++
     ++		cmp = slice_compare(k, entry_key);
     ++		free(slice_yield(&k));
     ++
     ++		if (cmp > 0) {
     ++			break;
     ++		}
     ++
     ++		merged_iter_pqueue_remove(&mi->pq);
     ++		err = merged_iter_advance_subiter(mi, top.index);
     ++		if (err < 0) {
     ++			return err;
     ++		}
     ++		record_clear(top.rec);
     ++		free(record_yield(&top.rec));
     ++	}
     ++
     ++	record_copy_from(rec, entry.rec, mi->hash_size);
     ++	record_clear(entry.rec);
     ++	free(record_yield(&entry.rec));
     ++	free(slice_yield(&entry_key));
     ++	return 0;
     ++}
     ++
     ++static int merged_iter_next_void(void *p, struct record rec)
     ++{
     ++	struct merged_iter *mi = (struct merged_iter *)p;
     ++	if (merged_iter_pqueue_is_empty(mi->pq)) {
     ++		return 1;
     ++	}
     ++
     ++	return merged_iter_next(mi, rec);
      +}
      +
      +struct iterator_vtable merged_iter_vtable = {
     -+    .next = &merged_iter_next_void,
     -+    .close = &merged_iter_close,
     ++	.next = &merged_iter_next_void,
     ++	.close = &merged_iter_close,
      +};
      +
      +static void iterator_from_merged_iter(struct iterator *it,
     -+                                      struct merged_iter *mi) {
     -+  it->iter_arg = mi;
     -+  it->ops = &merged_iter_vtable;
     -+}
     -+
     -+int new_merged_table(struct merged_table **dest, struct reader **stack, int n) {
     -+  uint64_t last_max = 0;
     -+  uint64_t first_min = 0;
     -+  for (int i = 0; i < n; i++) {
     -+    struct reader *r = stack[i];
     -+    if (i > 0 && last_max >= reader_min_update_index(r)) {
     -+      return FORMAT_ERROR;
     -+    }
     -+    if (i == 0) {
     -+      first_min = reader_min_update_index(r);
     -+    }
     -+
     -+    last_max = reader_max_update_index(r);
     -+  }
     -+
     -+  {
     -+    struct merged_table m = {
     -+        .stack = stack,
     -+        .stack_len = n,
     -+        .min = first_min,
     -+        .max = last_max,
     -+        .hash_size = SHA1_SIZE,
     -+    };
     -+
     -+    *dest = calloc(sizeof(struct merged_table), 1);
     -+    **dest = m;
     -+  }
     -+  return 0;
     -+}
     -+
     -+void merged_table_close(struct merged_table *mt) {
     -+  for (int i = 0; i < mt->stack_len; i++) {
     -+    reader_free(mt->stack[i]);
     -+  }
     -+  free(mt->stack);
     -+  mt->stack = NULL;
     -+  mt->stack_len = 0;
     ++				      struct merged_iter *mi)
     ++{
     ++	it->iter_arg = mi;
     ++	it->ops = &merged_iter_vtable;
     ++}
     ++
     ++int new_merged_table(struct merged_table **dest, struct reader **stack, int n)
     ++{
     ++	uint64_t last_max = 0;
     ++	uint64_t first_min = 0;
     ++	int i = 0;
     ++	for (i = 0; i < n; i++) {
     ++		struct reader *r = stack[i];
     ++		if (i > 0 && last_max >= reader_min_update_index(r)) {
     ++			return FORMAT_ERROR;
     ++		}
     ++		if (i == 0) {
     ++			first_min = reader_min_update_index(r);
     ++		}
     ++
     ++		last_max = reader_max_update_index(r);
     ++	}
     ++
     ++	{
     ++		struct merged_table m = {
     ++			.stack = stack,
     ++			.stack_len = n,
     ++			.min = first_min,
     ++			.max = last_max,
     ++			.hash_size = SHA1_SIZE,
     ++		};
     ++
     ++		*dest = calloc(sizeof(struct merged_table), 1);
     ++		**dest = m;
     ++	}
     ++	return 0;
     ++}
     ++
     ++void merged_table_close(struct merged_table *mt)
     ++{
     ++	int i = 0;
     ++	for (i = 0; i < mt->stack_len; i++) {
     ++		reader_free(mt->stack[i]);
     ++	}
     ++	free(mt->stack);
     ++	mt->stack = NULL;
     ++	mt->stack_len = 0;
      +}
      +
      +/* clears the list of subtable, without affecting the readers themselves. */
     -+void merged_table_clear(struct merged_table *mt) {
     -+  free(mt->stack);
     -+  mt->stack = NULL;
     -+  mt->stack_len = 0;
     ++void merged_table_clear(struct merged_table *mt)
     ++{
     ++	free(mt->stack);
     ++	mt->stack = NULL;
     ++	mt->stack_len = 0;
      +}
      +
     -+void merged_table_free(struct merged_table *mt) {
     -+  if (mt == NULL) {
     -+    return;
     -+  }
     -+  merged_table_clear(mt);
     -+  free(mt);
     ++void merged_table_free(struct merged_table *mt)
     ++{
     ++	if (mt == NULL) {
     ++		return;
     ++	}
     ++	merged_table_clear(mt);
     ++	free(mt);
      +}
      +
     -+uint64_t merged_max_update_index(struct merged_table *mt) { return mt->max; }
     ++uint64_t merged_max_update_index(struct merged_table *mt)
     ++{
     ++	return mt->max;
     ++}
      +
     -+uint64_t merged_min_update_index(struct merged_table *mt) { return mt->min; }
     ++uint64_t merged_min_update_index(struct merged_table *mt)
     ++{
     ++	return mt->min;
     ++}
      +
      +static int merged_table_seek_record(struct merged_table *mt,
     -+                                    struct iterator *it, struct record rec) {
     -+  struct iterator *iters = calloc(sizeof(struct iterator), mt->stack_len);
     -+  struct merged_iter merged = {
     -+      .stack = iters,
     -+      .typ = record_type(rec),
     -+      .hash_size = mt->hash_size,
     -+  };
     -+  int n = 0;
     -+  int err = 0;
     -+  for (int i = 0; i < mt->stack_len && err == 0; i++) {
     -+    int e = reader_seek(mt->stack[i], &iters[n], rec);
     -+    if (e < 0) {
     -+      err = e;
     -+    }
     -+    if (e == 0) {
     -+      n++;
     -+    }
     -+  }
     -+  if (err < 0) {
     -+    for (int i = 0; i < n; i++) {
     -+      iterator_destroy(&iters[i]);
     -+    }
     -+    free(iters);
     -+    return err;
     -+  }
     -+
     -+  merged.stack_len = n, err = merged_iter_init(&merged);
     -+  if (err < 0) {
     -+    merged_iter_close(&merged);
     -+    return err;
     -+  }
     -+
     -+  {
     -+    struct merged_iter *p = malloc(sizeof(struct merged_iter));
     -+    *p = merged;
     -+    iterator_from_merged_iter(it, p);
     -+  }
     -+  return 0;
     ++				    struct iterator *it, struct record rec)
     ++{
     ++	struct iterator *iters = calloc(sizeof(struct iterator), mt->stack_len);
     ++	struct merged_iter merged = {
     ++		.stack = iters,
     ++		.typ = record_type(rec),
     ++		.hash_size = mt->hash_size,
     ++	};
     ++	int n = 0;
     ++	int err = 0;
     ++	int i = 0;
     ++	for (i = 0; i < mt->stack_len && err == 0; i++) {
     ++		int e = reader_seek(mt->stack[i], &iters[n], rec);
     ++		if (e < 0) {
     ++			err = e;
     ++		}
     ++		if (e == 0) {
     ++			n++;
     ++		}
     ++	}
     ++	if (err < 0) {
     ++		int i = 0;
     ++		for (i = 0; i < n; i++) {
     ++			iterator_destroy(&iters[i]);
     ++		}
     ++		free(iters);
     ++		return err;
     ++	}
     ++
     ++	merged.stack_len = n, err = merged_iter_init(&merged);
     ++	if (err < 0) {
     ++		merged_iter_close(&merged);
     ++		return err;
     ++	}
     ++
     ++	{
     ++		struct merged_iter *p = malloc(sizeof(struct merged_iter));
     ++		*p = merged;
     ++		iterator_from_merged_iter(it, p);
     ++	}
     ++	return 0;
      +}
      +
      +int merged_table_seek_ref(struct merged_table *mt, struct iterator *it,
     -+                          const char *name) {
     -+  struct ref_record ref = {
     -+      .ref_name = (char *)name,
     -+  };
     -+  struct record rec = {};
     -+  record_from_ref(&rec, &ref);
     -+  return merged_table_seek_record(mt, it, rec);
     ++			  const char *name)
     ++{
     ++	struct ref_record ref = {
     ++		.ref_name = (char *)name,
     ++	};
     ++	struct record rec = {};
     ++	record_from_ref(&rec, &ref);
     ++	return merged_table_seek_record(mt, it, rec);
      +}
      +
      +int merged_table_seek_log_at(struct merged_table *mt, struct iterator *it,
     -+                             const char *name, uint64_t update_index) {
     -+  struct log_record log = {
     -+      .ref_name = (char *)name,
     -+      .update_index = update_index,
     -+  };
     -+  struct record rec = {};
     -+  record_from_log(&rec, &log);
     -+  return merged_table_seek_record(mt, it, rec);
     ++			     const char *name, uint64_t update_index)
     ++{
     ++	struct log_record log = {
     ++		.ref_name = (char *)name,
     ++		.update_index = update_index,
     ++	};
     ++	struct record rec = {};
     ++	record_from_log(&rec, &log);
     ++	return merged_table_seek_record(mt, it, rec);
      +}
      +
      +int merged_table_seek_log(struct merged_table *mt, struct iterator *it,
     -+                          const char *name) {
     -+  uint64_t max = ~((uint64_t)0);
     -+  return merged_table_seek_log_at(mt, it, name, max);
     ++			  const char *name)
     ++{
     ++	uint64_t max = ~((uint64_t)0);
     ++	return merged_table_seek_log_at(mt, it, name, max);
      +}
      
       diff --git a/reftable/merged.h b/reftable/merged.h
     @@ -2009,20 +1877,20 @@
      +#include "reftable.h"
      +
      +struct merged_table {
     -+  struct reader **stack;
     -+  int stack_len;
     -+  int hash_size;
     ++	struct reader **stack;
     ++	int stack_len;
     ++	int hash_size;
      +
     -+  uint64_t min;
     -+  uint64_t max;
     ++	uint64_t min;
     ++	uint64_t max;
      +};
      +
      +struct merged_iter {
     -+  struct iterator *stack;
     -+  int hash_size;
     -+  int stack_len;
     -+  byte typ;
     -+  struct merged_iter_pqueue pq;
     ++	struct iterator *stack;
     ++	int hash_size;
     ++	int stack_len;
     ++	byte typ;
     ++	struct merged_iter_pqueue pq;
      +} merged_iter;
      +
      +void merged_table_clear(struct merged_table *mt);
     @@ -2044,7 +1912,7 @@
      +
      +#include "merged.h"
      +
     -+#include <string.h>
     ++#include "system.h"
      +
      +#include "basics.h"
      +#include "block.h"
     @@ -2055,231 +1923,242 @@
      +#include "reftable.h"
      +#include "test_framework.h"
      +
     -+void test_pq(void) {
     -+  char *names[54] = {};
     -+  int N = ARRAYSIZE(names) - 1;
     -+
     -+  for (int i = 0; i < N; i++) {
     -+    char name[100];
     -+    sprintf(name, "%02d", i);
     -+    names[i] = strdup(name);
     -+  }
     -+
     -+  struct merged_iter_pqueue pq = {};
     -+
     -+  int i = 1;
     -+  do {
     -+    struct record rec = new_record(BLOCK_TYPE_REF);
     -+    record_as_ref(rec)->ref_name = names[i];
     -+
     -+    struct pq_entry e = {
     -+        .rec = rec,
     -+    };
     -+    merged_iter_pqueue_add(&pq, e);
     -+    merged_iter_pqueue_check(pq);
     -+    i = (i * 7) % N;
     -+  } while (i != 1);
     -+
     -+  const char *last = NULL;
     -+  while (!merged_iter_pqueue_is_empty(pq)) {
     -+    struct pq_entry e = merged_iter_pqueue_remove(&pq);
     -+    merged_iter_pqueue_check(pq);
     -+    struct ref_record *ref = record_as_ref(e.rec);
     -+
     -+    if (last != NULL) {
     -+      assert(strcmp(last, ref->ref_name) < 0);
     -+    }
     -+    last = ref->ref_name;
     -+    ref->ref_name = NULL;
     -+    free(ref);
     -+  }
     -+
     -+  for (int i = 0; i < N; i++) {
     -+    free(names[i]);
     -+  }
     -+
     -+  merged_iter_pqueue_clear(&pq);
     -+}
     -+
     -+void write_test_table(struct slice *buf, struct ref_record refs[], int n) {
     -+  int min = 0xffffffff;
     -+  int max = 0;
     -+  for (int i = 0; i < n; i++) {
     -+    uint64_t ui = refs[i].update_index;
     -+    if (ui > max) {
     -+      max = ui;
     -+    }
     -+    if (ui < min) {
     -+      min = ui;
     -+    }
     -+  }
     -+
     -+  struct write_options opts = {
     -+      .block_size = 256,
     -+  };
     -+
     -+  struct writer *w = new_writer(&slice_write_void, buf, &opts);
     -+  writer_set_limits(w, min, max);
     -+
     -+  for (int i = 0; i < n; i++) {
     -+    uint64_t before = refs[i].update_index;
     -+    int n = writer_add_ref(w, &refs[i]);
     -+    assert(n == 0);
     -+    assert(before == refs[i].update_index);
     -+  }
     -+
     -+  int err = writer_close(w);
     -+  assert(err == 0);
     -+
     -+  writer_free(w);
     -+  w = NULL;
     ++void test_pq(void)
     ++{
     ++	char *names[54] = {};
     ++	int N = ARRAYSIZE(names) - 1;
     ++
     ++	int i = 0;
     ++	for (i = 0; i < N; i++) {
     ++		char name[100];
     ++		snprintf(name, sizeof(name), "%02d", i);
     ++		names[i] = strdup(name);
     ++	}
     ++
     ++	struct merged_iter_pqueue pq = {};
     ++
     ++	i = 1;
     ++	do {
     ++		struct record rec = new_record(BLOCK_TYPE_REF);
     ++		record_as_ref(rec)->ref_name = names[i];
     ++
     ++		struct pq_entry e = {
     ++			.rec = rec,
     ++		};
     ++		merged_iter_pqueue_add(&pq, e);
     ++		merged_iter_pqueue_check(pq);
     ++		i = (i * 7) % N;
     ++	} while (i != 1);
     ++
     ++	const char *last = NULL;
     ++	while (!merged_iter_pqueue_is_empty(pq)) {
     ++		struct pq_entry e = merged_iter_pqueue_remove(&pq);
     ++		merged_iter_pqueue_check(pq);
     ++		struct ref_record *ref = record_as_ref(e.rec);
     ++
     ++		if (last != NULL) {
     ++			assert(strcmp(last, ref->ref_name) < 0);
     ++		}
     ++		last = ref->ref_name;
     ++		ref->ref_name = NULL;
     ++		free(ref);
     ++	}
     ++
     ++	for (i = 0; i < N; i++) {
     ++		free(names[i]);
     ++	}
     ++
     ++	merged_iter_pqueue_clear(&pq);
     ++}
     ++
     ++void write_test_table(struct slice *buf, struct ref_record refs[], int n)
     ++{
     ++	int min = 0xffffffff;
     ++	int max = 0;
     ++	int i = 0;
     ++	for (i = 0; i < n; i++) {
     ++		uint64_t ui = refs[i].update_index;
     ++		if (ui > max) {
     ++			max = ui;
     ++		}
     ++		if (ui < min) {
     ++			min = ui;
     ++		}
     ++	}
     ++
     ++	struct write_options opts = {
     ++		.block_size = 256,
     ++	};
     ++
     ++	struct writer *w = new_writer(&slice_write_void, buf, &opts);
     ++	writer_set_limits(w, min, max);
     ++
     ++	for (i = 0; i < n; i++) {
     ++		uint64_t before = refs[i].update_index;
     ++		int n = writer_add_ref(w, &refs[i]);
     ++		assert(n == 0);
     ++		assert(before == refs[i].update_index);
     ++	}
     ++
     ++	int err = writer_close(w);
     ++	assert(err == 0);
     ++
     ++	writer_free(w);
     ++	w = NULL;
      +}
      +
      +static struct merged_table *merged_table_from_records(struct ref_record **refs,
     -+                                                      int *sizes,
     -+                                                      struct slice *buf,
     -+                                                      int n) {
     -+  struct block_source *source = calloc(n, sizeof(*source));
     -+  struct reader **rd = calloc(n, sizeof(*rd));
     -+  for (int i = 0; i < n; i++) {
     -+    write_test_table(&buf[i], refs[i], sizes[i]);
     -+    block_source_from_slice(&source[i], &buf[i]);
     -+
     -+    int err = new_reader(&rd[i], source[i], "name");
     -+    assert(err == 0);
     -+  }
     -+
     -+  struct merged_table *mt = NULL;
     -+  int err = new_merged_table(&mt, rd, n);
     -+  assert(err == 0);
     -+  return mt;
     -+}
     -+
     -+void test_merged_between(void) {
     -+  byte hash1[SHA1_SIZE];
     -+  byte hash2[SHA1_SIZE];
     -+
     -+  set_test_hash(hash1, 1);
     -+  set_test_hash(hash2, 2);
     -+  struct ref_record r1[] = {{
     -+      .ref_name = "b",
     -+      .update_index = 1,
     -+      .value = hash1,
     -+  }};
     -+  struct ref_record r2[] = {{
     -+      .ref_name = "a",
     -+      .update_index = 2,
     -+  }};
     -+
     -+  struct ref_record *refs[] = {r1, r2};
     -+  int sizes[] = {1, 1};
     -+  struct slice bufs[2] = {};
     -+  struct merged_table *mt = merged_table_from_records(refs, sizes, bufs, 2);
     -+
     -+  struct iterator it = {};
     -+  int err = merged_table_seek_ref(mt, &it, "a");
     -+  assert(err == 0);
     -+
     -+  struct ref_record ref = {};
     -+  err = iterator_next_ref(it, &ref);
     -+  assert_err(err);
     -+  assert(ref.update_index == 2);
     -+}
     -+
     -+void test_merged(void) {
     -+  byte hash1[SHA1_SIZE];
     -+  byte hash2[SHA1_SIZE];
     -+
     -+  set_test_hash(hash1, 1);
     -+  set_test_hash(hash2, 2);
     -+  struct ref_record r1[] = {{
     -+                                .ref_name = "a",
     -+                                .update_index = 1,
     -+                                .value = hash1,
     -+                            },
     -+                            {
     -+                                .ref_name = "b",
     -+                                .update_index = 1,
     -+                                .value = hash1,
     -+                            },
     -+                            {
     -+                                .ref_name = "c",
     -+                                .update_index = 1,
     -+                                .value = hash1,
     -+                            }};
     -+  struct ref_record r2[] = {{
     -+      .ref_name = "a",
     -+      .update_index = 2,
     -+  }};
     -+  struct ref_record r3[] = {
     -+      {
     -+          .ref_name = "c",
     -+          .update_index = 3,
     -+          .value = hash2,
     -+      },
     -+      {
     -+          .ref_name = "d",
     -+          .update_index = 3,
     -+          .value = hash1,
     -+      },
     -+  };
     -+
     -+  struct ref_record *refs[] = {r1, r2, r3};
     -+  int sizes[3] = {3, 1, 2};
     -+  struct slice bufs[3] = {};
     -+
     -+  struct merged_table *mt = merged_table_from_records(refs, sizes, bufs, 3);
     -+
     -+  struct iterator it = {};
     -+  int err = merged_table_seek_ref(mt, &it, "a");
     -+  assert(err == 0);
     -+
     -+  struct ref_record *out = NULL;
     -+  int len = 0;
     -+  int cap = 0;
     -+  while (len < 100) {  // cap loops/recursion.
     -+    struct ref_record ref = {};
     -+    int err = iterator_next_ref(it, &ref);
     -+    if (err > 0) {
     -+      break;
     -+    }
     -+    if (len == cap) {
     -+      cap = 2 * cap + 1;
     -+      out = realloc(out, sizeof(struct ref_record) * cap);
     -+    }
     -+    out[len++] = ref;
     -+  }
     -+  iterator_destroy(&it);
     -+
     -+  struct ref_record want[] = {
     -+      r2[0],
     -+      r1[1],
     -+      r3[0],
     -+      r3[1],
     -+  };
     -+  assert(ARRAYSIZE(want) == len);
     -+  for (int i = 0; i < len; i++) {
     -+    assert(ref_record_equal(&want[i], &out[i], SHA1_SIZE));
     -+  }
     -+  for (int i = 0; i < len; i++) {
     -+    ref_record_clear(&out[i]);
     -+  }
     -+  free(out);
     -+
     -+  for (int i = 0; i < 3; i++) {
     -+    free(slice_yield(&bufs[i]));
     -+  }
     -+  merged_table_close(mt);
     -+  merged_table_free(mt);
     -+}
     -+
     -+// XXX test refs_for(oid)
     -+
     -+int main() {
     -+  add_test_case("test_merged_between", &test_merged_between);
     -+  add_test_case("test_pq", &test_pq);
     -+  add_test_case("test_merged", &test_merged);
     -+  test_main();
     ++						      int *sizes,
     ++						      struct slice *buf, int n)
     ++{
     ++	struct block_source *source = calloc(n, sizeof(*source));
     ++	struct reader **rd = calloc(n, sizeof(*rd));
     ++	int i = 0;
     ++	for (i = 0; i < n; i++) {
     ++		write_test_table(&buf[i], refs[i], sizes[i]);
     ++		block_source_from_slice(&source[i], &buf[i]);
     ++
     ++		int err = new_reader(&rd[i], source[i], "name");
     ++		assert(err == 0);
     ++	}
     ++
     ++	struct merged_table *mt = NULL;
     ++	int err = new_merged_table(&mt, rd, n);
     ++	assert(err == 0);
     ++	return mt;
     ++}
     ++
     ++void test_merged_between(void)
     ++{
     ++	byte hash1[SHA1_SIZE];
     ++	byte hash2[SHA1_SIZE];
     ++
     ++	set_test_hash(hash1, 1);
     ++	set_test_hash(hash2, 2);
     ++	struct ref_record r1[] = { {
     ++		.ref_name = "b",
     ++		.update_index = 1,
     ++		.value = hash1,
     ++	} };
     ++	struct ref_record r2[] = { {
     ++		.ref_name = "a",
     ++		.update_index = 2,
     ++	} };
     ++
     ++	struct ref_record *refs[] = { r1, r2 };
     ++	int sizes[] = { 1, 1 };
     ++	struct slice bufs[2] = {};
     ++	struct merged_table *mt =
     ++		merged_table_from_records(refs, sizes, bufs, 2);
     ++
     ++	struct iterator it = {};
     ++	int err = merged_table_seek_ref(mt, &it, "a");
     ++	assert(err == 0);
     ++
     ++	struct ref_record ref = {};
     ++	err = iterator_next_ref(it, &ref);
     ++	assert_err(err);
     ++	assert(ref.update_index == 2);
     ++}
     ++
     ++void test_merged(void)
     ++{
     ++	byte hash1[SHA1_SIZE];
     ++	byte hash2[SHA1_SIZE];
     ++
     ++	set_test_hash(hash1, 1);
     ++	set_test_hash(hash2, 2);
     ++	struct ref_record r1[] = { {
     ++					   .ref_name = "a",
     ++					   .update_index = 1,
     ++					   .value = hash1,
     ++				   },
     ++				   {
     ++					   .ref_name = "b",
     ++					   .update_index = 1,
     ++					   .value = hash1,
     ++				   },
     ++				   {
     ++					   .ref_name = "c",
     ++					   .update_index = 1,
     ++					   .value = hash1,
     ++				   } };
     ++	struct ref_record r2[] = { {
     ++		.ref_name = "a",
     ++		.update_index = 2,
     ++	} };
     ++	struct ref_record r3[] = {
     ++		{
     ++			.ref_name = "c",
     ++			.update_index = 3,
     ++			.value = hash2,
     ++		},
     ++		{
     ++			.ref_name = "d",
     ++			.update_index = 3,
     ++			.value = hash1,
     ++		},
     ++	};
     ++
     ++	struct ref_record *refs[] = { r1, r2, r3 };
     ++	int sizes[3] = { 3, 1, 2 };
     ++	struct slice bufs[3] = {};
     ++
     ++	struct merged_table *mt =
     ++		merged_table_from_records(refs, sizes, bufs, 3);
     ++
     ++	struct iterator it = {};
     ++	int err = merged_table_seek_ref(mt, &it, "a");
     ++	assert(err == 0);
     ++
     ++	struct ref_record *out = NULL;
     ++	int len = 0;
     ++	int cap = 0;
     ++	while (len < 100) { /* cap loops/recursion. */
     ++		struct ref_record ref = {};
     ++		int err = iterator_next_ref(it, &ref);
     ++		if (err > 0) {
     ++			break;
     ++		}
     ++		if (len == cap) {
     ++			cap = 2 * cap + 1;
     ++			out = realloc(out, sizeof(struct ref_record) * cap);
     ++		}
     ++		out[len++] = ref;
     ++	}
     ++	iterator_destroy(&it);
     ++
     ++	struct ref_record want[] = {
     ++		r2[0],
     ++		r1[1],
     ++		r3[0],
     ++		r3[1],
     ++	};
     ++	assert(ARRAYSIZE(want) == len);
     ++	int i = 0;
     ++	for (i = 0; i < len; i++) {
     ++		assert(ref_record_equal(&want[i], &out[i], SHA1_SIZE));
     ++	}
     ++	for (i = 0; i < len; i++) {
     ++		ref_record_clear(&out[i]);
     ++	}
     ++	free(out);
     ++
     ++	for (i = 0; i < 3; i++) {
     ++		free(slice_yield(&bufs[i]));
     ++	}
     ++	merged_table_close(mt);
     ++	merged_table_free(mt);
     ++}
     ++
     ++/* XXX test refs_for(oid) */
     ++
     ++int main()
     ++{
     ++	add_test_case("test_merged_between", &test_merged_between);
     ++	add_test_case("test_pq", &test_pq);
     ++	add_test_case("test_merged", &test_merged);
     ++	test_main();
      +}
      
       diff --git a/reftable/pq.c b/reftable/pq.c
     @@ -2297,111 +2176,119 @@
      +
      +#include "pq.h"
      +
     -+#include <assert.h>
     -+#include <stdlib.h>
     ++#include "system.h"
      +
     -+int pq_less(struct pq_entry a, struct pq_entry b) {
     -+  struct slice ak = {};
     -+  struct slice bk = {};
     -+  int cmp = 0;
     -+  record_key(a.rec, &ak);
     -+  record_key(b.rec, &bk);
     ++int pq_less(struct pq_entry a, struct pq_entry b)
     ++{
     ++	struct slice ak = {};
     ++	struct slice bk = {};
     ++	int cmp = 0;
     ++	record_key(a.rec, &ak);
     ++	record_key(b.rec, &bk);
      +
     -+  cmp = slice_compare(ak, bk);
     ++	cmp = slice_compare(ak, bk);
     ++
     ++	free(slice_yield(&ak));
     ++	free(slice_yield(&bk));
      +
     -+  free(slice_yield(&ak));
     -+  free(slice_yield(&bk));
     ++	if (cmp == 0) {
     ++		return a.index > b.index;
     ++	}
      +
     -+  if (cmp == 0) {
     -+    return a.index > b.index;
     -+  }
     -+
     -+  return cmp < 0;
     ++	return cmp < 0;
      +}
      +
     -+struct pq_entry merged_iter_pqueue_top(struct merged_iter_pqueue pq) {
     -+  return pq.heap[0];
     ++struct pq_entry merged_iter_pqueue_top(struct merged_iter_pqueue pq)
     ++{
     ++	return pq.heap[0];
      +}
      +
     -+bool merged_iter_pqueue_is_empty(struct merged_iter_pqueue pq) {
     -+  return pq.len == 0;
     ++bool merged_iter_pqueue_is_empty(struct merged_iter_pqueue pq)
     ++{
     ++	return pq.len == 0;
      +}
      +
     -+void merged_iter_pqueue_check(struct merged_iter_pqueue pq) {
     -+  for (int i = 1; i < pq.len; i++) {
     -+    int parent = (i - 1) / 2;
     ++void merged_iter_pqueue_check(struct merged_iter_pqueue pq)
     ++{
     ++	int i = 0;
     ++	for (i = 1; i < pq.len; i++) {
     ++		int parent = (i - 1) / 2;
      +
     -+    assert(pq_less(pq.heap[parent], pq.heap[i]));
     -+  }
     ++		assert(pq_less(pq.heap[parent], pq.heap[i]));
     ++	}
      +}
      +
     -+struct pq_entry merged_iter_pqueue_remove(struct merged_iter_pqueue *pq) {
     -+  int i = 0;
     -+  struct pq_entry e = pq->heap[0];
     -+  pq->heap[0] = pq->heap[pq->len - 1];
     -+  pq->len--;
     ++struct pq_entry merged_iter_pqueue_remove(struct merged_iter_pqueue *pq)
     ++{
     ++	int i = 0;
     ++	struct pq_entry e = pq->heap[0];
     ++	pq->heap[0] = pq->heap[pq->len - 1];
     ++	pq->len--;
      +
     -+  i = 0;
     -+  while (i < pq->len) {
     -+    int min = i;
     -+    int j = 2 * i + 1;
     -+    int k = 2 * i + 2;
     -+    if (j < pq->len && pq_less(pq->heap[j], pq->heap[i])) {
     -+      min = j;
     -+    }
     -+    if (k < pq->len && pq_less(pq->heap[k], pq->heap[min])) {
     -+      min = k;
     -+    }
     ++	i = 0;
     ++	while (i < pq->len) {
     ++		int min = i;
     ++		int j = 2 * i + 1;
     ++		int k = 2 * i + 2;
     ++		if (j < pq->len && pq_less(pq->heap[j], pq->heap[i])) {
     ++			min = j;
     ++		}
     ++		if (k < pq->len && pq_less(pq->heap[k], pq->heap[min])) {
     ++			min = k;
     ++		}
      +
     -+    if (min == i) {
     -+      break;
     -+    }
     ++		if (min == i) {
     ++			break;
     ++		}
      +
     -+    {
     -+      struct pq_entry tmp = pq->heap[min];
     -+      pq->heap[min] = pq->heap[i];
     -+      pq->heap[i] = tmp;
     -+    }
     ++		{
     ++			struct pq_entry tmp = pq->heap[min];
     ++			pq->heap[min] = pq->heap[i];
     ++			pq->heap[i] = tmp;
     ++		}
      +
     -+    i = min;
     -+  }
     ++		i = min;
     ++	}
      +
     -+  return e;
     ++	return e;
      +}
      +
     -+void merged_iter_pqueue_add(struct merged_iter_pqueue *pq, struct pq_entry e) {
     -+  int i = 0;
     -+  if (pq->len == pq->cap) {
     -+    pq->cap = 2 * pq->cap + 1;
     -+    pq->heap = realloc(pq->heap, pq->cap * sizeof(struct pq_entry));
     -+  }
     -+
     -+  pq->heap[pq->len++] = e;
     -+  i = pq->len - 1;
     -+  while (i > 0) {
     -+    int j = (i - 1) / 2;
     -+    if (pq_less(pq->heap[j], pq->heap[i])) {
     -+      break;
     -+    }
     ++void merged_iter_pqueue_add(struct merged_iter_pqueue *pq, struct pq_entry e)
     ++{
     ++	int i = 0;
     ++	if (pq->len == pq->cap) {
     ++		pq->cap = 2 * pq->cap + 1;
     ++		pq->heap = realloc(pq->heap, pq->cap * sizeof(struct pq_entry));
     ++	}
      +
     -+    {
     -+      struct pq_entry tmp = pq->heap[j];
     -+      pq->heap[j] = pq->heap[i];
     -+      pq->heap[i] = tmp;
     -+    }
     ++	pq->heap[pq->len++] = e;
     ++	i = pq->len - 1;
     ++	while (i > 0) {
     ++		int j = (i - 1) / 2;
     ++		if (pq_less(pq->heap[j], pq->heap[i])) {
     ++			break;
     ++		}
      +
     -+    i = j;
     -+  }
     -+}
     ++		{
     ++			struct pq_entry tmp = pq->heap[j];
     ++			pq->heap[j] = pq->heap[i];
     ++			pq->heap[i] = tmp;
     ++		}
      +
     -+void merged_iter_pqueue_clear(struct merged_iter_pqueue *pq) {
     -+  for (int i = 0; i < pq->len; i++) {
     -+    record_clear(pq->heap[i].rec);
     -+    free(record_yield(&pq->heap[i].rec));
     -+  }
     -+  free(pq->heap);
     -+  pq->heap = NULL;
     -+  pq->len = pq->cap = 0;
     ++		i = j;
     ++	}
     ++}
     ++
     ++void merged_iter_pqueue_clear(struct merged_iter_pqueue *pq)
     ++{
     ++	int i = 0;
     ++	for (i = 0; i < pq->len; i++) {
     ++		record_clear(pq->heap[i].rec);
     ++		free(record_yield(&pq->heap[i].rec));
     ++	}
     ++	free(pq->heap);
     ++	pq->heap = NULL;
     ++	pq->len = pq->cap = 0;
      +}
      
       diff --git a/reftable/pq.h b/reftable/pq.h
     @@ -2423,16 +2310,16 @@
      +#include "record.h"
      +
      +struct pq_entry {
     -+  struct record rec;
     -+  int index;
     ++	struct record rec;
     ++	int index;
      +};
      +
      +int pq_less(struct pq_entry a, struct pq_entry b);
      +
      +struct merged_iter_pqueue {
     -+  struct pq_entry *heap;
     -+  int len;
     -+  int cap;
     ++	struct pq_entry *heap;
     ++	int len;
     ++	int cap;
      +};
      +
      +struct pq_entry merged_iter_pqueue_top(struct merged_iter_pqueue pq);
     @@ -2459,10 +2346,7 @@
      +
      +#include "reader.h"
      +
     -+#include <stdio.h>
     -+#include <stdlib.h>
     -+#include <string.h>
     -+#include <zlib.h>
     ++#include "system.h"
      +
      +#include "block.h"
      +#include "constants.h"
     @@ -2471,655 +2355,696 @@
      +#include "reftable.h"
      +#include "tree.h"
      +
     -+uint64_t block_source_size(struct block_source source) {
     -+  return source.ops->size(source.arg);
     ++uint64_t block_source_size(struct block_source source)
     ++{
     ++	return source.ops->size(source.arg);
      +}
      +
      +int block_source_read_block(struct block_source source, struct block *dest,
     -+                            uint64_t off, uint32_t size) {
     -+  int result = source.ops->read_block(source.arg, dest, off, size);
     -+  dest->source = source;
     -+  return result;
     ++			    uint64_t off, uint32_t size)
     ++{
     ++	int result = source.ops->read_block(source.arg, dest, off, size);
     ++	dest->source = source;
     ++	return result;
      +}
      +
     -+void block_source_return_block(struct block_source source,
     -+                               struct block *blockp) {
     -+  source.ops->return_block(source.arg, blockp);
     -+  blockp->data = NULL;
     -+  blockp->len = 0;
     -+  blockp->source.ops = NULL;
     -+  blockp->source.arg = NULL;
     ++void block_source_return_block(struct block_source source, struct block *blockp)
     ++{
     ++	source.ops->return_block(source.arg, blockp);
     ++	blockp->data = NULL;
     ++	blockp->len = 0;
     ++	blockp->source.ops = NULL;
     ++	blockp->source.arg = NULL;
      +}
      +
     -+void block_source_close(struct block_source *source) {
     -+  if (source->ops == NULL) {
     -+    return;
     -+  }
     ++void block_source_close(struct block_source *source)
     ++{
     ++	if (source->ops == NULL) {
     ++		return;
     ++	}
      +
     -+  source->ops->close(source->arg);
     -+  source->ops = NULL;
     ++	source->ops->close(source->arg);
     ++	source->ops = NULL;
      +}
      +
     -+static struct reader_offsets *reader_offsets_for(struct reader *r, byte typ) {
     -+  switch (typ) {
     -+    case BLOCK_TYPE_REF:
     -+      return &r->ref_offsets;
     -+    case BLOCK_TYPE_LOG:
     -+      return &r->log_offsets;
     -+    case BLOCK_TYPE_OBJ:
     -+      return &r->obj_offsets;
     -+  }
     -+  abort();
     ++static struct reader_offsets *reader_offsets_for(struct reader *r, byte typ)
     ++{
     ++	switch (typ) {
     ++	case BLOCK_TYPE_REF:
     ++		return &r->ref_offsets;
     ++	case BLOCK_TYPE_LOG:
     ++		return &r->log_offsets;
     ++	case BLOCK_TYPE_OBJ:
     ++		return &r->obj_offsets;
     ++	}
     ++	abort();
      +}
      +
      +static int reader_get_block(struct reader *r, struct block *dest, uint64_t off,
     -+                            uint32_t sz) {
     -+  if (off >= r->size) {
     -+    return 0;
     -+  }
     -+
     -+  if (off + sz > r->size) {
     -+    sz = r->size - off;
     -+  }
     -+
     -+  return block_source_read_block(r->source, dest, off, sz);
     -+}
     -+
     -+void reader_return_block(struct reader *r, struct block *p) {
     -+  block_source_return_block(r->source, p);
     -+}
     -+
     -+const char *reader_name(struct reader *r) { return r->name; }
     -+
     -+static int parse_footer(struct reader *r, byte *footer, byte *header) {
     -+  byte *f = footer;
     -+  int err = 0;
     -+  if (memcmp(f, "REFT", 4)) {
     -+    err = FORMAT_ERROR;
     -+    goto exit;
     -+  }
     -+  f += 4;
     -+
     -+  if (0 != memcmp(footer, header, HEADER_SIZE)) {
     -+    err = FORMAT_ERROR;
     -+    goto exit;
     -+  }
     -+
     -+  {
     -+    byte version = *f++;
     -+    if (version != 1) {
     -+      err = FORMAT_ERROR;
     -+      goto exit;
     -+    }
     -+  }
     -+
     -+  r->block_size = get_u24(f);
     -+
     -+  f += 3;
     -+  r->min_update_index = get_u64(f);
     -+  f += 8;
     -+  r->max_update_index = get_u64(f);
     -+  f += 8;
     -+
     -+  r->ref_offsets.index_offset = get_u64(f);
     -+  f += 8;
     -+
     -+  r->obj_offsets.offset = get_u64(f);
     -+  f += 8;
     -+
     -+  r->object_id_len = r->obj_offsets.offset & ((1 << 5) - 1);
     -+  r->obj_offsets.offset >>= 5;
     -+
     -+  r->obj_offsets.index_offset = get_u64(f);
     -+  f += 8;
     -+  r->log_offsets.offset = get_u64(f);
     -+  f += 8;
     -+  r->log_offsets.index_offset = get_u64(f);
     -+  f += 8;
     -+
     -+  {
     -+    uint32_t computed_crc = crc32(0, footer, f - footer);
     -+    uint32_t file_crc = get_u32(f);
     -+    f += 4;
     -+    if (computed_crc != file_crc) {
     -+      err = FORMAT_ERROR;
     -+      goto exit;
     -+    }
     -+  }
     -+
     -+  {
     -+    byte first_block_typ = header[HEADER_SIZE];
     -+    r->ref_offsets.present = (first_block_typ == BLOCK_TYPE_REF);
     -+    r->ref_offsets.offset = 0;
     -+    r->log_offsets.present =
     -+        (first_block_typ == BLOCK_TYPE_LOG || r->log_offsets.offset > 0);
     -+    r->obj_offsets.present = r->obj_offsets.offset > 0;
     -+  }
     -+  err = 0;
     ++			    uint32_t sz)
     ++{
     ++	if (off >= r->size) {
     ++		return 0;
     ++	}
     ++
     ++	if (off + sz > r->size) {
     ++		sz = r->size - off;
     ++	}
     ++
     ++	return block_source_read_block(r->source, dest, off, sz);
     ++}
     ++
     ++void reader_return_block(struct reader *r, struct block *p)
     ++{
     ++	block_source_return_block(r->source, p);
     ++}
     ++
     ++const char *reader_name(struct reader *r)
     ++{
     ++	return r->name;
     ++}
     ++
     ++static int parse_footer(struct reader *r, byte *footer, byte *header)
     ++{
     ++	byte *f = footer;
     ++	int err = 0;
     ++	if (memcmp(f, "REFT", 4)) {
     ++		err = FORMAT_ERROR;
     ++		goto exit;
     ++	}
     ++	f += 4;
     ++
     ++	if (memcmp(footer, header, HEADER_SIZE)) {
     ++		err = FORMAT_ERROR;
     ++		goto exit;
     ++	}
     ++
     ++	{
     ++		byte version = *f++;
     ++		if (version != 1) {
     ++			err = FORMAT_ERROR;
     ++			goto exit;
     ++		}
     ++	}
     ++
     ++	r->block_size = get_u24(f);
     ++
     ++	f += 3;
     ++	r->min_update_index = get_u64(f);
     ++	f += 8;
     ++	r->max_update_index = get_u64(f);
     ++	f += 8;
     ++
     ++	r->ref_offsets.index_offset = get_u64(f);
     ++	f += 8;
     ++
     ++	r->obj_offsets.offset = get_u64(f);
     ++	f += 8;
     ++
     ++	r->object_id_len = r->obj_offsets.offset & ((1 << 5) - 1);
     ++	r->obj_offsets.offset >>= 5;
     ++
     ++	r->obj_offsets.index_offset = get_u64(f);
     ++	f += 8;
     ++	r->log_offsets.offset = get_u64(f);
     ++	f += 8;
     ++	r->log_offsets.index_offset = get_u64(f);
     ++	f += 8;
     ++
     ++	{
     ++		uint32_t computed_crc = crc32(0, footer, f - footer);
     ++		uint32_t file_crc = get_u32(f);
     ++		f += 4;
     ++		if (computed_crc != file_crc) {
     ++			err = FORMAT_ERROR;
     ++			goto exit;
     ++		}
     ++	}
     ++
     ++	{
     ++		byte first_block_typ = header[HEADER_SIZE];
     ++		r->ref_offsets.present = (first_block_typ == BLOCK_TYPE_REF);
     ++		r->ref_offsets.offset = 0;
     ++		r->log_offsets.present = (first_block_typ == BLOCK_TYPE_LOG ||
     ++					  r->log_offsets.offset > 0);
     ++		r->obj_offsets.present = r->obj_offsets.offset > 0;
     ++	}
     ++	err = 0;
      +exit:
     -+  return err;
     -+}
     -+
     -+int init_reader(struct reader *r, struct block_source source,
     -+                const char *name) {
     -+  struct block footer = {};
     -+  struct block header = {};
     -+  int err = 0;
     -+
     -+  memset(r, 0, sizeof(struct reader));
     -+  r->size = block_source_size(source) - FOOTER_SIZE;
     -+  r->source = source;
     -+  r->name = strdup(name);
     -+  r->hash_size = SHA1_SIZE;
     -+
     -+  err = block_source_read_block(source, &footer, r->size, FOOTER_SIZE);
     -+  if (err != FOOTER_SIZE) {
     -+    err = IO_ERROR;
     -+    goto exit;
     -+  }
     -+
     -+  // Need +1 to read type of first block.
     -+  err = reader_get_block(r, &header, 0, HEADER_SIZE + 1);
     -+  if (err != HEADER_SIZE + 1) {
     -+    err = IO_ERROR;
     -+    goto exit;
     -+  }
     -+
     -+  err = parse_footer(r, footer.data, header.data);
     ++	return err;
     ++}
     ++
     ++int init_reader(struct reader *r, struct block_source source, const char *name)
     ++{
     ++	struct block footer = {};
     ++	struct block header = {};
     ++	int err = 0;
     ++
     ++	memset(r, 0, sizeof(struct reader));
     ++	r->size = block_source_size(source) - FOOTER_SIZE;
     ++	r->source = source;
     ++	r->name = strdup(name);
     ++	r->hash_size = SHA1_SIZE;
     ++
     ++	err = block_source_read_block(source, &footer, r->size, FOOTER_SIZE);
     ++	if (err != FOOTER_SIZE) {
     ++		err = IO_ERROR;
     ++		goto exit;
     ++	}
     ++
     ++	/* Need +1 to read type of first block. */
     ++	err = reader_get_block(r, &header, 0, HEADER_SIZE + 1);
     ++	if (err != HEADER_SIZE + 1) {
     ++		err = IO_ERROR;
     ++		goto exit;
     ++	}
     ++
     ++	err = parse_footer(r, footer.data, header.data);
      +exit:
     -+  block_source_return_block(r->source, &footer);
     -+  block_source_return_block(r->source, &header);
     -+  return err;
     ++	block_source_return_block(r->source, &footer);
     ++	block_source_return_block(r->source, &header);
     ++	return err;
      +}
      +
      +struct table_iter {
     -+  struct reader *r;
     -+  byte typ;
     -+  uint64_t block_off;
     -+  struct block_iter bi;
     -+  bool finished;
     ++	struct reader *r;
     ++	byte typ;
     ++	uint64_t block_off;
     ++	struct block_iter bi;
     ++	bool finished;
      +};
      +
      +static void table_iter_copy_from(struct table_iter *dest,
     -+                                 struct table_iter *src) {
     -+  dest->r = src->r;
     -+  dest->typ = src->typ;
     -+  dest->block_off = src->block_off;
     -+  dest->finished = src->finished;
     -+  block_iter_copy_from(&dest->bi, &src->bi);
     ++				 struct table_iter *src)
     ++{
     ++	dest->r = src->r;
     ++	dest->typ = src->typ;
     ++	dest->block_off = src->block_off;
     ++	dest->finished = src->finished;
     ++	block_iter_copy_from(&dest->bi, &src->bi);
      +}
      +
     -+static int table_iter_next_in_block(struct table_iter *ti, struct record rec) {
     -+  int res = block_iter_next(&ti->bi, rec);
     -+  if (res == 0 && record_type(rec) == BLOCK_TYPE_REF) {
     -+    ((struct ref_record *)rec.data)->update_index += ti->r->min_update_index;
     -+  }
     ++static int table_iter_next_in_block(struct table_iter *ti, struct record rec)
     ++{
     ++	int res = block_iter_next(&ti->bi, rec);
     ++	if (res == 0 && record_type(rec) == BLOCK_TYPE_REF) {
     ++		((struct ref_record *)rec.data)->update_index +=
     ++			ti->r->min_update_index;
     ++	}
      +
     -+  return res;
     ++	return res;
      +}
      +
     -+static void table_iter_block_done(struct table_iter *ti) {
     -+  if (ti->bi.br == NULL) {
     -+    return;
     -+  }
     -+  reader_return_block(ti->r, &ti->bi.br->block);
     -+  free(ti->bi.br);
     -+  ti->bi.br = NULL;
     ++static void table_iter_block_done(struct table_iter *ti)
     ++{
     ++	if (ti->bi.br == NULL) {
     ++		return;
     ++	}
     ++	reader_return_block(ti->r, &ti->bi.br->block);
     ++	free(ti->bi.br);
     ++	ti->bi.br = NULL;
      +
     -+  ti->bi.last_key.len = 0;
     -+  ti->bi.next_off = 0;
     ++	ti->bi.last_key.len = 0;
     ++	ti->bi.next_off = 0;
      +}
      +
     -+static int32_t extract_block_size(byte *data, byte *typ, uint64_t off) {
     -+  int32_t result = 0;
     ++static int32_t extract_block_size(byte *data, byte *typ, uint64_t off)
     ++{
     ++	int32_t result = 0;
      +
     -+  if (off == 0) {
     -+    data += 24;
     -+  }
     ++	if (off == 0) {
     ++		data += 24;
     ++	}
      +
     -+  *typ = data[0];
     -+  if (is_block_type(*typ)) {
     -+    result = get_u24(data + 1);
     -+  }
     -+  return result;
     ++	*typ = data[0];
     ++	if (is_block_type(*typ)) {
     ++		result = get_u24(data + 1);
     ++	}
     ++	return result;
      +}
      +
      +int reader_init_block_reader(struct reader *r, struct block_reader *br,
     -+                             uint64_t next_off, byte want_typ) {
     -+  int32_t guess_block_size = r->block_size ? r->block_size : DEFAULT_BLOCK_SIZE;
     -+  struct block block = {};
     -+  byte block_typ = 0;
     -+  int err = 0;
     -+  uint32_t header_off = next_off ? 0 : HEADER_SIZE;
     -+  int32_t block_size = 0;
     -+
     -+  if (next_off >= r->size) {
     -+    return 1;
     -+  }
     -+
     -+  err = reader_get_block(r, &block, next_off, guess_block_size);
     -+  if (err < 0) {
     -+    return err;
     -+  }
     -+
     -+  block_size = extract_block_size(block.data, &block_typ, next_off);
     -+  if (block_size < 0) {
     -+    return block_size;
     -+  }
     -+
     -+  if (want_typ != BLOCK_TYPE_ANY && block_typ != want_typ) {
     -+    reader_return_block(r, &block);
     -+    return 1;
     -+  }
     -+
     -+  if (block_size > guess_block_size) {
     -+    reader_return_block(r, &block);
     -+    err = reader_get_block(r, &block, next_off, block_size);
     -+    if (err < 0) {
     -+      return err;
     -+    }
     -+  }
     -+
     -+  return block_reader_init(br, &block, header_off, r->block_size, r->hash_size);
     ++			     uint64_t next_off, byte want_typ)
     ++{
     ++	int32_t guess_block_size = r->block_size ? r->block_size :
     ++						   DEFAULT_BLOCK_SIZE;
     ++	struct block block = {};
     ++	byte block_typ = 0;
     ++	int err = 0;
     ++	uint32_t header_off = next_off ? 0 : HEADER_SIZE;
     ++	int32_t block_size = 0;
     ++
     ++	if (next_off >= r->size) {
     ++		return 1;
     ++	}
     ++
     ++	err = reader_get_block(r, &block, next_off, guess_block_size);
     ++	if (err < 0) {
     ++		return err;
     ++	}
     ++
     ++	block_size = extract_block_size(block.data, &block_typ, next_off);
     ++	if (block_size < 0) {
     ++		return block_size;
     ++	}
     ++
     ++	if (want_typ != BLOCK_TYPE_ANY && block_typ != want_typ) {
     ++		reader_return_block(r, &block);
     ++		return 1;
     ++	}
     ++
     ++	if (block_size > guess_block_size) {
     ++		reader_return_block(r, &block);
     ++		err = reader_get_block(r, &block, next_off, block_size);
     ++		if (err < 0) {
     ++			return err;
     ++		}
     ++	}
     ++
     ++	return block_reader_init(br, &block, header_off, r->block_size,
     ++				 r->hash_size);
      +}
      +
      +static int table_iter_next_block(struct table_iter *dest,
     -+                                 struct table_iter *src) {
     -+  uint64_t next_block_off = src->block_off + src->bi.br->full_block_size;
     -+  struct block_reader br = {};
     -+  int err = 0;
     -+
     -+  dest->r = src->r;
     -+  dest->typ = src->typ;
     -+  dest->block_off = next_block_off;
     -+
     -+  err = reader_init_block_reader(src->r, &br, next_block_off, src->typ);
     -+  if (err > 0) {
     -+    dest->finished = true;
     -+    return 1;
     -+  }
     -+  if (err != 0) {
     -+    return err;
     -+  }
     -+
     -+  {
     -+    struct block_reader *brp = malloc(sizeof(struct block_reader));
     -+    *brp = br;
     -+
     -+    dest->finished = false;
     -+    block_reader_start(brp, &dest->bi);
     -+  }
     -+  return 0;
     -+}
     -+
     -+static int table_iter_next(struct table_iter *ti, struct record rec) {
     -+  if (record_type(rec) != ti->typ) {
     -+    return API_ERROR;
     -+  }
     -+
     -+  while (true) {
     -+    struct table_iter next = {};
     -+    int err = 0;
     -+    if (ti->finished) {
     -+      return 1;
     -+    }
     -+
     -+    err = table_iter_next_in_block(ti, rec);
     -+    if (err <= 0) {
     -+      return err;
     -+    }
     -+
     -+    err = table_iter_next_block(&next, ti);
     -+    if (err != 0) {
     -+      ti->finished = true;
     -+    }
     -+    table_iter_block_done(ti);
     -+    if (err != 0) {
     -+      return err;
     -+    }
     -+    table_iter_copy_from(ti, &next);
     -+    block_iter_close(&next.bi);
     -+  }
     -+}
     -+
     -+static int table_iter_next_void(void *ti, struct record rec) {
     -+  return table_iter_next((struct table_iter *)ti, rec);
     -+}
     -+
     -+static void table_iter_close(void *p) {
     -+  struct table_iter *ti = (struct table_iter *)p;
     -+  table_iter_block_done(ti);
     -+  block_iter_close(&ti->bi);
     ++				 struct table_iter *src)
     ++{
     ++	uint64_t next_block_off = src->block_off + src->bi.br->full_block_size;
     ++	struct block_reader br = {};
     ++	int err = 0;
     ++
     ++	dest->r = src->r;
     ++	dest->typ = src->typ;
     ++	dest->block_off = next_block_off;
     ++
     ++	err = reader_init_block_reader(src->r, &br, next_block_off, src->typ);
     ++	if (err > 0) {
     ++		dest->finished = true;
     ++		return 1;
     ++	}
     ++	if (err != 0) {
     ++		return err;
     ++	}
     ++
     ++	{
     ++		struct block_reader *brp = malloc(sizeof(struct block_reader));
     ++		*brp = br;
     ++
     ++		dest->finished = false;
     ++		block_reader_start(brp, &dest->bi);
     ++	}
     ++	return 0;
     ++}
     ++
     ++static int table_iter_next(struct table_iter *ti, struct record rec)
     ++{
     ++	if (record_type(rec) != ti->typ) {
     ++		return API_ERROR;
     ++	}
     ++
     ++	while (true) {
     ++		struct table_iter next = {};
     ++		int err = 0;
     ++		if (ti->finished) {
     ++			return 1;
     ++		}
     ++
     ++		err = table_iter_next_in_block(ti, rec);
     ++		if (err <= 0) {
     ++			return err;
     ++		}
     ++
     ++		err = table_iter_next_block(&next, ti);
     ++		if (err != 0) {
     ++			ti->finished = true;
     ++		}
     ++		table_iter_block_done(ti);
     ++		if (err != 0) {
     ++			return err;
     ++		}
     ++		table_iter_copy_from(ti, &next);
     ++		block_iter_close(&next.bi);
     ++	}
     ++}
     ++
     ++static int table_iter_next_void(void *ti, struct record rec)
     ++{
     ++	return table_iter_next((struct table_iter *)ti, rec);
     ++}
     ++
     ++static void table_iter_close(void *p)
     ++{
     ++	struct table_iter *ti = (struct table_iter *)p;
     ++	table_iter_block_done(ti);
     ++	block_iter_close(&ti->bi);
      +}
      +
      +struct iterator_vtable table_iter_vtable = {
     -+    .next = &table_iter_next_void,
     -+    .close = &table_iter_close,
     ++	.next = &table_iter_next_void,
     ++	.close = &table_iter_close,
      +};
      +
     -+static void iterator_from_table_iter(struct iterator *it,
     -+                                     struct table_iter *ti) {
     -+  it->iter_arg = ti;
     -+  it->ops = &table_iter_vtable;
     ++static void iterator_from_table_iter(struct iterator *it, struct table_iter *ti)
     ++{
     ++	it->iter_arg = ti;
     ++	it->ops = &table_iter_vtable;
      +}
      +
      +static int reader_table_iter_at(struct reader *r, struct table_iter *ti,
     -+                                uint64_t off, byte typ) {
     -+  struct block_reader br = {};
     -+  struct block_reader *brp = NULL;
     ++				uint64_t off, byte typ)
     ++{
     ++	struct block_reader br = {};
     ++	struct block_reader *brp = NULL;
      +
     -+  int err = reader_init_block_reader(r, &br, off, typ);
     -+  if (err != 0) {
     -+    return err;
     -+  }
     ++	int err = reader_init_block_reader(r, &br, off, typ);
     ++	if (err != 0) {
     ++		return err;
     ++	}
      +
     -+  brp = malloc(sizeof(struct block_reader));
     -+  *brp = br;
     -+  ti->r = r;
     -+  ti->typ = block_reader_type(brp);
     -+  ti->block_off = off;
     -+  block_reader_start(brp, &ti->bi);
     -+  return 0;
     ++	brp = malloc(sizeof(struct block_reader));
     ++	*brp = br;
     ++	ti->r = r;
     ++	ti->typ = block_reader_type(brp);
     ++	ti->block_off = off;
     ++	block_reader_start(brp, &ti->bi);
     ++	return 0;
      +}
      +
      +static int reader_start(struct reader *r, struct table_iter *ti, byte typ,
     -+                        bool index) {
     -+  struct reader_offsets *offs = reader_offsets_for(r, typ);
     -+  uint64_t off = offs->offset;
     -+  if (index) {
     -+    off = offs->index_offset;
     -+    if (off == 0) {
     -+      return 1;
     -+    }
     -+    typ = BLOCK_TYPE_INDEX;
     -+  }
     ++			bool index)
     ++{
     ++	struct reader_offsets *offs = reader_offsets_for(r, typ);
     ++	uint64_t off = offs->offset;
     ++	if (index) {
     ++		off = offs->index_offset;
     ++		if (off == 0) {
     ++			return 1;
     ++		}
     ++		typ = BLOCK_TYPE_INDEX;
     ++	}
      +
     -+  return reader_table_iter_at(r, ti, off, typ);
     ++	return reader_table_iter_at(r, ti, off, typ);
      +}
      +
      +static int reader_seek_linear(struct reader *r, struct table_iter *ti,
     -+                              struct record want) {
     -+  struct record rec = new_record(record_type(want));
     -+  struct slice want_key = {};
     -+  struct slice got_key = {};
     -+  struct table_iter next = {};
     -+  int err = -1;
     -+  record_key(want, &want_key);
     -+
     -+  while (true) {
     -+    err = table_iter_next_block(&next, ti);
     -+    if (err < 0) {
     -+      goto exit;
     -+    }
     -+
     -+    if (err > 0) {
     -+      break;
     -+    }
     -+
     -+    err = block_reader_first_key(next.bi.br, &got_key);
     -+    if (err < 0) {
     -+      goto exit;
     -+    }
     -+    {
     -+      int cmp = slice_compare(got_key, want_key);
     -+      if (cmp > 0) {
     -+        table_iter_block_done(&next);
     -+        break;
     -+      }
     -+    }
     -+
     -+    table_iter_block_done(ti);
     -+    table_iter_copy_from(ti, &next);
     -+  }
     -+
     -+  err = block_iter_seek(&ti->bi, want_key);
     -+  if (err < 0) {
     -+    goto exit;
     -+  }
     -+  err = 0;
     ++			      struct record want)
     ++{
     ++	struct record rec = new_record(record_type(want));
     ++	struct slice want_key = {};
     ++	struct slice got_key = {};
     ++	struct table_iter next = {};
     ++	int err = -1;
     ++	record_key(want, &want_key);
     ++
     ++	while (true) {
     ++		err = table_iter_next_block(&next, ti);
     ++		if (err < 0) {
     ++			goto exit;
     ++		}
     ++
     ++		if (err > 0) {
     ++			break;
     ++		}
     ++
     ++		err = block_reader_first_key(next.bi.br, &got_key);
     ++		if (err < 0) {
     ++			goto exit;
     ++		}
     ++		{
     ++			int cmp = slice_compare(got_key, want_key);
     ++			if (cmp > 0) {
     ++				table_iter_block_done(&next);
     ++				break;
     ++			}
     ++		}
     ++
     ++		table_iter_block_done(ti);
     ++		table_iter_copy_from(ti, &next);
     ++	}
     ++
     ++	err = block_iter_seek(&ti->bi, want_key);
     ++	if (err < 0) {
     ++		goto exit;
     ++	}
     ++	err = 0;
      +
      +exit:
     -+  block_iter_close(&next.bi);
     -+  record_clear(rec);
     -+  free(record_yield(&rec));
     -+  free(slice_yield(&want_key));
     -+  free(slice_yield(&got_key));
     -+  return err;
     ++	block_iter_close(&next.bi);
     ++	record_clear(rec);
     ++	free(record_yield(&rec));
     ++	free(slice_yield(&want_key));
     ++	free(slice_yield(&got_key));
     ++	return err;
      +}
      +
      +static int reader_seek_indexed(struct reader *r, struct iterator *it,
     -+                               struct record rec) {
     -+  struct index_record want_index = {};
     -+  struct record want_index_rec = {};
     -+  struct index_record index_result = {};
     -+  struct record index_result_rec = {};
     -+  struct table_iter index_iter = {};
     -+  struct table_iter next = {};
     -+  int err = 0;
     -+
     -+  record_key(rec, &want_index.last_key);
     -+  record_from_index(&want_index_rec, &want_index);
     -+  record_from_index(&index_result_rec, &index_result);
     -+
     -+  err = reader_start(r, &index_iter, record_type(rec), true);
     -+  if (err < 0) {
     -+    goto exit;
     -+  }
     -+
     -+  err = reader_seek_linear(r, &index_iter, want_index_rec);
     -+  while (true) {
     -+    err = table_iter_next(&index_iter, index_result_rec);
     -+    table_iter_block_done(&index_iter);
     -+    if (err != 0) {
     -+      goto exit;
     -+    }
     -+
     -+    err = reader_table_iter_at(r, &next, index_result.offset, 0);
     -+    if (err != 0) {
     -+      goto exit;
     -+    }
     -+
     -+    err = block_iter_seek(&next.bi, want_index.last_key);
     -+    if (err < 0) {
     -+      goto exit;
     -+    }
     -+
     -+    if (next.typ == record_type(rec)) {
     -+      err = 0;
     -+      break;
     -+    }
     -+
     -+    if (next.typ != BLOCK_TYPE_INDEX) {
     -+      err = FORMAT_ERROR;
     -+      break;
     -+    }
     -+
     -+    table_iter_copy_from(&index_iter, &next);
     -+  }
     -+
     -+  if (err == 0) {
     -+    struct table_iter *malloced = calloc(sizeof(struct table_iter), 1);
     -+    table_iter_copy_from(malloced, &next);
     -+    iterator_from_table_iter(it, malloced);
     -+  }
     ++			       struct record rec)
     ++{
     ++	struct index_record want_index = {};
     ++	struct record want_index_rec = {};
     ++	struct index_record index_result = {};
     ++	struct record index_result_rec = {};
     ++	struct table_iter index_iter = {};
     ++	struct table_iter next = {};
     ++	int err = 0;
     ++
     ++	record_key(rec, &want_index.last_key);
     ++	record_from_index(&want_index_rec, &want_index);
     ++	record_from_index(&index_result_rec, &index_result);
     ++
     ++	err = reader_start(r, &index_iter, record_type(rec), true);
     ++	if (err < 0) {
     ++		goto exit;
     ++	}
     ++
     ++	err = reader_seek_linear(r, &index_iter, want_index_rec);
     ++	while (true) {
     ++		err = table_iter_next(&index_iter, index_result_rec);
     ++		table_iter_block_done(&index_iter);
     ++		if (err != 0) {
     ++			goto exit;
     ++		}
     ++
     ++		err = reader_table_iter_at(r, &next, index_result.offset, 0);
     ++		if (err != 0) {
     ++			goto exit;
     ++		}
     ++
     ++		err = block_iter_seek(&next.bi, want_index.last_key);
     ++		if (err < 0) {
     ++			goto exit;
     ++		}
     ++
     ++		if (next.typ == record_type(rec)) {
     ++			err = 0;
     ++			break;
     ++		}
     ++
     ++		if (next.typ != BLOCK_TYPE_INDEX) {
     ++			err = FORMAT_ERROR;
     ++			break;
     ++		}
     ++
     ++		table_iter_copy_from(&index_iter, &next);
     ++	}
     ++
     ++	if (err == 0) {
     ++		struct table_iter *malloced =
     ++			calloc(sizeof(struct table_iter), 1);
     ++		table_iter_copy_from(malloced, &next);
     ++		iterator_from_table_iter(it, malloced);
     ++	}
      +exit:
     -+  block_iter_close(&next.bi);
     -+  table_iter_close(&index_iter);
     -+  record_clear(want_index_rec);
     -+  record_clear(index_result_rec);
     -+  return err;
     ++	block_iter_close(&next.bi);
     ++	table_iter_close(&index_iter);
     ++	record_clear(want_index_rec);
     ++	record_clear(index_result_rec);
     ++	return err;
      +}
      +
      +static int reader_seek_internal(struct reader *r, struct iterator *it,
     -+                                struct record rec) {
     -+  struct reader_offsets *offs = reader_offsets_for(r, record_type(rec));
     -+  uint64_t idx = offs->index_offset;
     -+  struct table_iter ti = {};
     -+  int err = 0;
     -+  if (idx > 0) {
     -+    return reader_seek_indexed(r, it, rec);
     -+  }
     -+
     -+  err = reader_start(r, &ti, record_type(rec), false);
     -+  if (err < 0) {
     -+    return err;
     -+  }
     -+  err = reader_seek_linear(r, &ti, rec);
     -+  if (err < 0) {
     -+    return err;
     -+  }
     -+
     -+  {
     -+    struct table_iter *p = malloc(sizeof(struct table_iter));
     -+    *p = ti;
     -+    iterator_from_table_iter(it, p);
     -+  }
     -+
     -+  return 0;
     -+}
     -+
     -+int reader_seek(struct reader *r, struct iterator *it, struct record rec) {
     -+  byte typ = record_type(rec);
     -+
     -+  struct reader_offsets *offs = reader_offsets_for(r, typ);
     -+  if (!offs->present) {
     -+    iterator_set_empty(it);
     -+    return 0;
     -+  }
     -+
     -+  return reader_seek_internal(r, it, rec);
     -+}
     -+
     -+int reader_seek_ref(struct reader *r, struct iterator *it, const char *name) {
     -+  struct ref_record ref = {
     -+      .ref_name = (char *)name,
     -+  };
     -+  struct record rec = {};
     -+  record_from_ref(&rec, &ref);
     -+  return reader_seek(r, it, rec);
     ++				struct record rec)
     ++{
     ++	struct reader_offsets *offs = reader_offsets_for(r, record_type(rec));
     ++	uint64_t idx = offs->index_offset;
     ++	struct table_iter ti = {};
     ++	int err = 0;
     ++	if (idx > 0) {
     ++		return reader_seek_indexed(r, it, rec);
     ++	}
     ++
     ++	err = reader_start(r, &ti, record_type(rec), false);
     ++	if (err < 0) {
     ++		return err;
     ++	}
     ++	err = reader_seek_linear(r, &ti, rec);
     ++	if (err < 0) {
     ++		return err;
     ++	}
     ++
     ++	{
     ++		struct table_iter *p = malloc(sizeof(struct table_iter));
     ++		*p = ti;
     ++		iterator_from_table_iter(it, p);
     ++	}
     ++
     ++	return 0;
     ++}
     ++
     ++int reader_seek(struct reader *r, struct iterator *it, struct record rec)
     ++{
     ++	byte typ = record_type(rec);
     ++
     ++	struct reader_offsets *offs = reader_offsets_for(r, typ);
     ++	if (!offs->present) {
     ++		iterator_set_empty(it);
     ++		return 0;
     ++	}
     ++
     ++	return reader_seek_internal(r, it, rec);
     ++}
     ++
     ++int reader_seek_ref(struct reader *r, struct iterator *it, const char *name)
     ++{
     ++	struct ref_record ref = {
     ++		.ref_name = (char *)name,
     ++	};
     ++	struct record rec = {};
     ++	record_from_ref(&rec, &ref);
     ++	return reader_seek(r, it, rec);
      +}
      +
      +int reader_seek_log_at(struct reader *r, struct iterator *it, const char *name,
     -+                    uint64_t update_index) {
     -+  struct log_record log = {
     -+      .ref_name = (char *)name,
     -+      .update_index = update_index,
     -+  };
     -+  struct record rec = {};
     -+  record_from_log(&rec, &log);
     -+  return reader_seek(r, it, rec);
     ++		       uint64_t update_index)
     ++{
     ++	struct log_record log = {
     ++		.ref_name = (char *)name,
     ++		.update_index = update_index,
     ++	};
     ++	struct record rec = {};
     ++	record_from_log(&rec, &log);
     ++	return reader_seek(r, it, rec);
      +}
      +
     -+int reader_seek_log(struct reader *r, struct iterator *it, const char *name) {
     -+  uint64_t max = ~((uint64_t)0);
     -+  return reader_seek_log_at(r, it, name, max);
     ++int reader_seek_log(struct reader *r, struct iterator *it, const char *name)
     ++{
     ++	uint64_t max = ~((uint64_t)0);
     ++	return reader_seek_log_at(r, it, name, max);
      +}
      +
     -+void reader_close(struct reader *r) {
     -+  block_source_close(&r->source);
     -+  free(r->name);
     -+  r->name = NULL;
     ++void reader_close(struct reader *r)
     ++{
     ++	block_source_close(&r->source);
     ++	free(r->name);
     ++	r->name = NULL;
      +}
      +
     -+int new_reader(struct reader **p, struct block_source src, char const *name) {
     -+  struct reader *rd = calloc(sizeof(struct reader), 1);
     -+  int err = init_reader(rd, src, name);
     -+  if (err == 0) {
     -+    *p = rd;
     -+  } else {
     -+    free(rd);
     -+  }
     -+  return err;
     ++int new_reader(struct reader **p, struct block_source src, char const *name)
     ++{
     ++	struct reader *rd = calloc(sizeof(struct reader), 1);
     ++	int err = init_reader(rd, src, name);
     ++	if (err == 0) {
     ++		*p = rd;
     ++	} else {
     ++		free(rd);
     ++	}
     ++	return err;
      +}
      +
     -+void reader_free(struct reader *r) {
     -+  reader_close(r);
     -+  free(r);
     ++void reader_free(struct reader *r)
     ++{
     ++	reader_close(r);
     ++	free(r);
      +}
      +
      +static int reader_refs_for_indexed(struct reader *r, struct iterator *it,
     -+                                   byte *oid) {
     -+  struct obj_record want = {
     -+      .hash_prefix = oid,
     -+      .hash_prefix_len = r->object_id_len,
     -+  };
     -+  struct record want_rec = {};
     -+  struct iterator oit = {};
     -+  struct obj_record got = {};
     -+  struct record got_rec = {};
     -+  int err = 0;
     -+
     -+  record_from_obj(&want_rec, &want);
     -+
     -+  err = reader_seek(r, &oit, want_rec);
     -+  if (err != 0) {
     -+    return err;
     -+  }
     -+
     -+  record_from_obj(&got_rec, &got);
     -+  err = iterator_next(oit, got_rec);
     -+  iterator_destroy(&oit);
     -+  if (err < 0) {
     -+    return err;
     -+  }
     -+
     -+  if (err > 0 || memcmp(want.hash_prefix, got.hash_prefix, r->object_id_len)) {
     -+    iterator_set_empty(it);
     -+    return 0;
     -+  }
     -+
     -+  {
     -+    struct indexed_table_ref_iter *itr = NULL;
     -+    err = new_indexed_table_ref_iter(&itr, r, oid, r->hash_size, got.offsets,
     -+                                     got.offset_len);
     -+    if (err < 0) {
     -+      record_clear(got_rec);
     -+      return err;
     -+    }
     -+    got.offsets = NULL;
     -+    record_clear(got_rec);
     -+
     -+    iterator_from_indexed_table_ref_iter(it, itr);
     -+  }
     -+
     -+  return 0;
     ++				   byte *oid)
     ++{
     ++	struct obj_record want = {
     ++		.hash_prefix = oid,
     ++		.hash_prefix_len = r->object_id_len,
     ++	};
     ++	struct record want_rec = {};
     ++	struct iterator oit = {};
     ++	struct obj_record got = {};
     ++	struct record got_rec = {};
     ++	int err = 0;
     ++
     ++	record_from_obj(&want_rec, &want);
     ++
     ++	err = reader_seek(r, &oit, want_rec);
     ++	if (err != 0) {
     ++		return err;
     ++	}
     ++
     ++	record_from_obj(&got_rec, &got);
     ++	err = iterator_next(oit, got_rec);
     ++	iterator_destroy(&oit);
     ++	if (err < 0) {
     ++		return err;
     ++	}
     ++
     ++	if (err > 0 ||
     ++	    memcmp(want.hash_prefix, got.hash_prefix, r->object_id_len)) {
     ++		iterator_set_empty(it);
     ++		return 0;
     ++	}
     ++
     ++	{
     ++		struct indexed_table_ref_iter *itr = NULL;
     ++		err = new_indexed_table_ref_iter(&itr, r, oid, r->hash_size,
     ++						 got.offsets, got.offset_len);
     ++		if (err < 0) {
     ++			record_clear(got_rec);
     ++			return err;
     ++		}
     ++		got.offsets = NULL;
     ++		record_clear(got_rec);
     ++
     ++		iterator_from_indexed_table_ref_iter(it, itr);
     ++	}
     ++
     ++	return 0;
      +}
      +
      +static int reader_refs_for_unindexed(struct reader *r, struct iterator *it,
     -+                                     byte *oid, int oid_len) {
     -+  struct table_iter *ti = calloc(sizeof(struct table_iter), 1);
     -+  struct filtering_ref_iterator *filter = NULL;
     -+  int err = reader_start(r, ti, BLOCK_TYPE_REF, false);
     -+  if (err < 0) {
     -+    free(ti);
     -+    return err;
     -+  }
     -+
     -+  filter = calloc(sizeof(struct filtering_ref_iterator), 1);
     -+  slice_resize(&filter->oid, oid_len);
     -+  memcpy(filter->oid.buf, oid, oid_len);
     -+  filter->r = r;
     -+  filter->double_check = false;
     -+  iterator_from_table_iter(&filter->it, ti);
     -+
     -+  iterator_from_filtering_ref_iterator(it, filter);
     -+  return 0;
     ++				     byte *oid, int oid_len)
     ++{
     ++	struct table_iter *ti = calloc(sizeof(struct table_iter), 1);
     ++	struct filtering_ref_iterator *filter = NULL;
     ++	int err = reader_start(r, ti, BLOCK_TYPE_REF, false);
     ++	if (err < 0) {
     ++		free(ti);
     ++		return err;
     ++	}
     ++
     ++	filter = calloc(sizeof(struct filtering_ref_iterator), 1);
     ++	slice_resize(&filter->oid, oid_len);
     ++	memcpy(filter->oid.buf, oid, oid_len);
     ++	filter->r = r;
     ++	filter->double_check = false;
     ++	iterator_from_table_iter(&filter->it, ti);
     ++
     ++	iterator_from_filtering_ref_iterator(it, filter);
     ++	return 0;
      +}
      +
      +int reader_refs_for(struct reader *r, struct iterator *it, byte *oid,
     -+                    int oid_len) {
     -+  if (r->obj_offsets.present) {
     -+    return reader_refs_for_indexed(r, it, oid);
     -+  }
     -+  return reader_refs_for_unindexed(r, it, oid, oid_len);
     ++		    int oid_len)
     ++{
     ++	if (r->obj_offsets.present) {
     ++		return reader_refs_for_indexed(r, it, oid);
     ++	}
     ++	return reader_refs_for_unindexed(r, it, oid, oid_len);
      +}
      +
     -+uint64_t reader_max_update_index(struct reader *r) {
     -+  return r->max_update_index;
     ++uint64_t reader_max_update_index(struct reader *r)
     ++{
     ++	return r->max_update_index;
      +}
      +
     -+uint64_t reader_min_update_index(struct reader *r) {
     -+  return r->min_update_index;
     ++uint64_t reader_min_update_index(struct reader *r)
     ++{
     ++	return r->min_update_index;
      +}
      
       diff --git a/reftable/reader.h b/reftable/reader.h
     @@ -3145,29 +3070,29 @@
      +uint64_t block_source_size(struct block_source source);
      +
      +int block_source_read_block(struct block_source source, struct block *dest,
     -+                            uint64_t off, uint32_t size);
     ++			    uint64_t off, uint32_t size);
      +void block_source_return_block(struct block_source source, struct block *ret);
      +void block_source_close(struct block_source *source);
      +
      +struct reader_offsets {
     -+  bool present;
     -+  uint64_t offset;
     -+  uint64_t index_offset;
     ++	bool present;
     ++	uint64_t offset;
     ++	uint64_t index_offset;
      +};
      +
      +struct reader {
     -+  struct block_source source;
     -+  char *name;
     -+  int hash_size;
     -+  uint64_t size;
     -+  uint32_t block_size;
     -+  uint64_t min_update_index;
     -+  uint64_t max_update_index;
     -+  int object_id_len;
     -+
     -+  struct reader_offsets ref_offsets;
     -+  struct reader_offsets obj_offsets;
     -+  struct reader_offsets log_offsets;
     ++	struct block_source source;
     ++	char *name;
     ++	int hash_size;
     ++	uint64_t size;
     ++	uint32_t block_size;
     ++	uint64_t min_update_index;
     ++	uint64_t max_update_index;
     ++	int object_id_len;
     ++
     ++	struct reader_offsets ref_offsets;
     ++	struct reader_offsets obj_offsets;
     ++	struct reader_offsets log_offsets;
      +};
      +
      +int init_reader(struct reader *r, struct block_source source, const char *name);
     @@ -3176,7 +3101,7 @@
      +const char *reader_name(struct reader *r);
      +void reader_return_block(struct reader *r, struct block *p);
      +int reader_init_block_reader(struct reader *r, struct block_reader *br,
     -+                             uint64_t next_off, byte want_typ);
     ++			     uint64_t next_off, byte want_typ);
      +
      +#endif
      
     @@ -3195,1025 +3120,1105 @@
      +
      +#include "record.h"
      +
     -+#include <assert.h>
     -+#include <stdio.h>
     -+#include <stdlib.h>
     -+#include <string.h>
     ++#include "system.h"
      +
      +#include "constants.h"
      +#include "reftable.h"
      +
     -+int is_block_type(byte typ) {
     -+  switch (typ) {
     -+    case BLOCK_TYPE_REF:
     -+    case BLOCK_TYPE_LOG:
     -+    case BLOCK_TYPE_OBJ:
     -+    case BLOCK_TYPE_INDEX:
     -+      return true;
     -+  }
     -+  return false;
     -+}
     -+
     -+int get_var_int(uint64_t *dest, struct slice in) {
     -+  int ptr = 0;
     -+  uint64_t val;
     -+
     -+  if (in.len == 0) {
     -+    return -1;
     -+  }
     -+  val = in.buf[ptr] & 0x7f;
     -+
     -+  while (in.buf[ptr] & 0x80) {
     -+    ptr++;
     -+    if (ptr > in.len) {
     -+      return -1;
     -+    }
     -+    val = (val + 1) << 7 | (uint64_t)(in.buf[ptr] & 0x7f);
     -+  }
     -+
     -+  *dest = val;
     -+  return ptr + 1;
     -+}
     -+
     -+int put_var_int(struct slice dest, uint64_t val) {
     -+  byte buf[10] = {};
     -+  int i = 9;
     -+  buf[i] = (byte)(val & 0x7f);
     -+  i--;
     -+  while (true) {
     -+    val >>= 7;
     -+    if (!val) {
     -+      break;
     -+    }
     -+    val--;
     -+    buf[i] = 0x80 | (byte)(val & 0x7f);
     -+    i--;
     -+  }
     -+
     -+  {
     -+    int n = sizeof(buf) - i - 1;
     -+    if (dest.len < n) {
     -+      return -1;
     -+    }
     -+    memcpy(dest.buf, &buf[i + 1], n);
     -+    return n;
     -+  }
     -+}
     -+
     -+int common_prefix_size(struct slice a, struct slice b) {
     -+  int p = 0;
     -+  while (p < a.len && p < b.len) {
     -+    if (a.buf[p] != b.buf[p]) {
     -+      break;
     -+    }
     -+    p++;
     -+  }
     -+
     -+  return p;
     -+}
     -+
     -+static int decode_string(struct slice *dest, struct slice in) {
     -+  int start_len = in.len;
     -+  uint64_t tsize = 0;
     -+  int n = get_var_int(&tsize, in);
     -+  if (n <= 0) {
     -+    return -1;
     -+  }
     -+  in.buf += n;
     -+  in.len -= n;
     -+  if (in.len < tsize) {
     -+    return -1;
     -+  }
     -+
     -+  slice_resize(dest, tsize + 1);
     -+  dest->buf[tsize] = 0;
     -+  memcpy(dest->buf, in.buf, tsize);
     -+  in.buf += tsize;
     -+  in.len -= tsize;
     -+
     -+  return start_len - in.len;
     ++int is_block_type(byte typ)
     ++{
     ++	switch (typ) {
     ++	case BLOCK_TYPE_REF:
     ++	case BLOCK_TYPE_LOG:
     ++	case BLOCK_TYPE_OBJ:
     ++	case BLOCK_TYPE_INDEX:
     ++		return true;
     ++	}
     ++	return false;
     ++}
     ++
     ++int get_var_int(uint64_t *dest, struct slice in)
     ++{
     ++	int ptr = 0;
     ++	uint64_t val;
     ++
     ++	if (in.len == 0) {
     ++		return -1;
     ++	}
     ++	val = in.buf[ptr] & 0x7f;
     ++
     ++	while (in.buf[ptr] & 0x80) {
     ++		ptr++;
     ++		if (ptr > in.len) {
     ++			return -1;
     ++		}
     ++		val = (val + 1) << 7 | (uint64_t)(in.buf[ptr] & 0x7f);
     ++	}
     ++
     ++	*dest = val;
     ++	return ptr + 1;
     ++}
     ++
     ++int put_var_int(struct slice dest, uint64_t val)
     ++{
     ++	byte buf[10] = {};
     ++	int i = 9;
     ++	buf[i] = (byte)(val & 0x7f);
     ++	i--;
     ++	while (true) {
     ++		val >>= 7;
     ++		if (!val) {
     ++			break;
     ++		}
     ++		val--;
     ++		buf[i] = 0x80 | (byte)(val & 0x7f);
     ++		i--;
     ++	}
     ++
     ++	{
     ++		int n = sizeof(buf) - i - 1;
     ++		if (dest.len < n) {
     ++			return -1;
     ++		}
     ++		memcpy(dest.buf, &buf[i + 1], n);
     ++		return n;
     ++	}
     ++}
     ++
     ++int common_prefix_size(struct slice a, struct slice b)
     ++{
     ++	int p = 0;
     ++	while (p < a.len && p < b.len) {
     ++		if (a.buf[p] != b.buf[p]) {
     ++			break;
     ++		}
     ++		p++;
     ++	}
     ++
     ++	return p;
     ++}
     ++
     ++static int decode_string(struct slice *dest, struct slice in)
     ++{
     ++	int start_len = in.len;
     ++	uint64_t tsize = 0;
     ++	int n = get_var_int(&tsize, in);
     ++	if (n <= 0) {
     ++		return -1;
     ++	}
     ++	in.buf += n;
     ++	in.len -= n;
     ++	if (in.len < tsize) {
     ++		return -1;
     ++	}
     ++
     ++	slice_resize(dest, tsize + 1);
     ++	dest->buf[tsize] = 0;
     ++	memcpy(dest->buf, in.buf, tsize);
     ++	in.buf += tsize;
     ++	in.len -= tsize;
     ++
     ++	return start_len - in.len;
      +}
      +
      +int encode_key(bool *restart, struct slice dest, struct slice prev_key,
     -+               struct slice key, byte extra) {
     -+  struct slice start = dest;
     -+  int prefix_len = common_prefix_size(prev_key, key);
     -+  uint64_t suffix_len = key.len - prefix_len;
     -+  int n = put_var_int(dest, (uint64_t)prefix_len);
     -+  if (n < 0) {
     -+    return -1;
     -+  }
     -+  dest.buf += n;
     -+  dest.len -= n;
     -+
     -+  *restart = (prefix_len == 0);
     -+
     -+  n = put_var_int(dest, suffix_len << 3 | (uint64_t)extra);
     -+  if (n < 0) {
     -+    return -1;
     -+  }
     -+  dest.buf += n;
     -+  dest.len -= n;
     -+
     -+  if (dest.len < suffix_len) {
     -+    return -1;
     -+  }
     -+  memcpy(dest.buf, key.buf + prefix_len, suffix_len);
     -+  dest.buf += suffix_len;
     -+  dest.len -= suffix_len;
     -+
     -+  return start.len - dest.len;
     -+}
     -+
     -+static byte ref_record_type(void) { return BLOCK_TYPE_REF; }
     -+
     -+static void ref_record_key(const void *r, struct slice *dest) {
     -+  const struct ref_record *rec = (const struct ref_record *)r;
     -+  slice_set_string(dest, rec->ref_name);
     -+}
     -+
     -+static void ref_record_copy_from(void *rec, const void *src_rec,
     -+                                 int hash_size) {
     -+  struct ref_record *ref = (struct ref_record *)rec;
     -+  struct ref_record *src = (struct ref_record *)src_rec;
     -+  assert(hash_size > 0);
     -+
     -+  // This is simple and correct, but we could probably reuse the hash fields.
     -+  ref_record_clear(ref);
     -+  if (src->ref_name != NULL) {
     -+    ref->ref_name = strdup(src->ref_name);
     -+  }
     -+
     -+  if (src->target != NULL) {
     -+    ref->target = strdup(src->target);
     -+  }
     -+
     -+  if (src->target_value != NULL) {
     -+    ref->target_value = malloc(hash_size);
     -+    memcpy(ref->target_value, src->target_value, hash_size);
     -+  }
     -+
     -+  if (src->value != NULL) {
     -+    ref->value = malloc(hash_size);
     -+    memcpy(ref->value, src->value, hash_size);
     -+  }
     -+  ref->update_index = src->update_index;
     -+}
     -+
     -+static char hexdigit(int c) {
     -+  if (c <= 9) {
     -+    return '0' + c;
     -+  }
     -+  return 'a' + (c - 10);
     -+}
     -+
     -+static void hex_format(char *dest, byte *src, int hash_size) {
     -+  assert(hash_size > 0);
     -+  if (src != NULL) {
     -+    for (int i = 0; i < hash_size; i++) {
     -+      dest[2 * i] = hexdigit(src[i] >> 4);
     -+      dest[2 * i + 1] = hexdigit(src[i] & 0xf);
     -+    }
     -+    dest[2 * hash_size] = 0;
     -+  }
     -+}
     -+
     -+void ref_record_print(struct ref_record *ref, int hash_size) {
     -+  char hex[SHA256_SIZE + 1] = {};
     -+
     -+  printf("ref{%s(%ld) ", ref->ref_name, ref->update_index);
     -+  if (ref->value != NULL) {
     -+    hex_format(hex, ref->value, hash_size);
     -+    printf("%s", hex);
     -+  }
     -+  if (ref->target_value != NULL) {
     -+    hex_format(hex, ref->target_value, hash_size);
     -+    printf(" (T %s)", hex);
     -+  }
     -+  if (ref->target != NULL) {
     -+    printf("=> %s", ref->target);
     -+  }
     -+  printf("}\n");
     -+}
     -+
     -+static void ref_record_clear_void(void *rec) {
     -+  ref_record_clear((struct ref_record *)rec);
     -+}
     -+
     -+void ref_record_clear(struct ref_record *ref) {
     -+  free(ref->ref_name);
     -+  free(ref->target);
     -+  free(ref->target_value);
     -+  free(ref->value);
     -+  memset(ref, 0, sizeof(struct ref_record));
     -+}
     -+
     -+static byte ref_record_val_type(const void *rec) {
     -+  const struct ref_record *r = (const struct ref_record *)rec;
     -+  if (r->value != NULL) {
     -+    if (r->target_value != NULL) {
     -+      return 2;
     -+    } else {
     -+      return 1;
     -+    }
     -+  } else if (r->target != NULL) {
     -+    return 3;
     -+  }
     -+  return 0;
     -+}
     -+
     -+static int encode_string(char *str, struct slice s) {
     -+  struct slice start = s;
     -+  int l = strlen(str);
     -+  int n = put_var_int(s, l);
     -+  if (n < 0) {
     -+    return -1;
     -+  }
     -+  s.buf += n;
     -+  s.len -= n;
     -+  if (s.len < l) {
     -+    return -1;
     -+  }
     -+  memcpy(s.buf, str, l);
     -+  s.buf += l;
     -+  s.len -= l;
     -+
     -+  return start.len - s.len;
     -+}
     -+
     -+static int ref_record_encode(const void *rec, struct slice s, int hash_size) {
     -+  const struct ref_record *r = (const struct ref_record *)rec;
     -+  struct slice start = s;
     -+  int n = put_var_int(s, r->update_index);
     -+  assert(hash_size > 0);
     -+  if (n < 0) {
     -+    return -1;
     -+  }
     -+  s.buf += n;
     -+  s.len -= n;
     -+
     -+  if (r->value != NULL) {
     -+    if (s.len < hash_size) {
     -+      return -1;
     -+    }
     -+    memcpy(s.buf, r->value, hash_size);
     -+    s.buf += hash_size;
     -+    s.len -= hash_size;
     -+  }
     -+
     -+  if (r->target_value != NULL) {
     -+    if (s.len < hash_size) {
     -+      return -1;
     -+    }
     -+    memcpy(s.buf, r->target_value, hash_size);
     -+    s.buf += hash_size;
     -+    s.len -= hash_size;
     -+  }
     -+
     -+  if (r->target != NULL) {
     -+    int n = encode_string(r->target, s);
     -+    if (n < 0) {
     -+      return -1;
     -+    }
     -+    s.buf += n;
     -+    s.len -= n;
     -+  }
     -+
     -+  return start.len - s.len;
     ++	       struct slice key, byte extra)
     ++{
     ++	struct slice start = dest;
     ++	int prefix_len = common_prefix_size(prev_key, key);
     ++	uint64_t suffix_len = key.len - prefix_len;
     ++	int n = put_var_int(dest, (uint64_t)prefix_len);
     ++	if (n < 0) {
     ++		return -1;
     ++	}
     ++	dest.buf += n;
     ++	dest.len -= n;
     ++
     ++	*restart = (prefix_len == 0);
     ++
     ++	n = put_var_int(dest, suffix_len << 3 | (uint64_t)extra);
     ++	if (n < 0) {
     ++		return -1;
     ++	}
     ++	dest.buf += n;
     ++	dest.len -= n;
     ++
     ++	if (dest.len < suffix_len) {
     ++		return -1;
     ++	}
     ++	memcpy(dest.buf, key.buf + prefix_len, suffix_len);
     ++	dest.buf += suffix_len;
     ++	dest.len -= suffix_len;
     ++
     ++	return start.len - dest.len;
     ++}
     ++
     ++static byte ref_record_type(void)
     ++{
     ++	return BLOCK_TYPE_REF;
     ++}
     ++
     ++static void ref_record_key(const void *r, struct slice *dest)
     ++{
     ++	const struct ref_record *rec = (const struct ref_record *)r;
     ++	slice_set_string(dest, rec->ref_name);
     ++}
     ++
     ++static void ref_record_copy_from(void *rec, const void *src_rec, int hash_size)
     ++{
     ++	struct ref_record *ref = (struct ref_record *)rec;
     ++	struct ref_record *src = (struct ref_record *)src_rec;
     ++	assert(hash_size > 0);
     ++
     ++	/* This is simple and correct, but we could probably reuse the hash
     ++           fields. */
     ++	ref_record_clear(ref);
     ++	if (src->ref_name != NULL) {
     ++		ref->ref_name = strdup(src->ref_name);
     ++	}
     ++
     ++	if (src->target != NULL) {
     ++		ref->target = strdup(src->target);
     ++	}
     ++
     ++	if (src->target_value != NULL) {
     ++		ref->target_value = malloc(hash_size);
     ++		memcpy(ref->target_value, src->target_value, hash_size);
     ++	}
     ++
     ++	if (src->value != NULL) {
     ++		ref->value = malloc(hash_size);
     ++		memcpy(ref->value, src->value, hash_size);
     ++	}
     ++	ref->update_index = src->update_index;
     ++}
     ++
     ++static char hexdigit(int c)
     ++{
     ++	if (c <= 9) {
     ++		return '0' + c;
     ++	}
     ++	return 'a' + (c - 10);
     ++}
     ++
     ++static void hex_format(char *dest, byte *src, int hash_size)
     ++{
     ++	assert(hash_size > 0);
     ++	if (src != NULL) {
     ++		int i = 0;
     ++		for (i = 0; i < hash_size; i++) {
     ++			dest[2 * i] = hexdigit(src[i] >> 4);
     ++			dest[2 * i + 1] = hexdigit(src[i] & 0xf);
     ++		}
     ++		dest[2 * hash_size] = 0;
     ++	}
     ++}
     ++
     ++void ref_record_print(struct ref_record *ref, int hash_size)
     ++{
     ++	char hex[SHA256_SIZE + 1] = {};
     ++
     ++	printf("ref{%s(%" PRIdMAX ") ", ref->ref_name, ref->update_index);
     ++	if (ref->value != NULL) {
     ++		hex_format(hex, ref->value, hash_size);
     ++		printf("%s", hex);
     ++	}
     ++	if (ref->target_value != NULL) {
     ++		hex_format(hex, ref->target_value, hash_size);
     ++		printf(" (T %s)", hex);
     ++	}
     ++	if (ref->target != NULL) {
     ++		printf("=> %s", ref->target);
     ++	}
     ++	printf("}\n");
     ++}
     ++
     ++static void ref_record_clear_void(void *rec)
     ++{
     ++	ref_record_clear((struct ref_record *)rec);
     ++}
     ++
     ++void ref_record_clear(struct ref_record *ref)
     ++{
     ++	free(ref->ref_name);
     ++	free(ref->target);
     ++	free(ref->target_value);
     ++	free(ref->value);
     ++	memset(ref, 0, sizeof(struct ref_record));
     ++}
     ++
     ++static byte ref_record_val_type(const void *rec)
     ++{
     ++	const struct ref_record *r = (const struct ref_record *)rec;
     ++	if (r->value != NULL) {
     ++		if (r->target_value != NULL) {
     ++			return 2;
     ++		} else {
     ++			return 1;
     ++		}
     ++	} else if (r->target != NULL) {
     ++		return 3;
     ++	}
     ++	return 0;
     ++}
     ++
     ++static int encode_string(char *str, struct slice s)
     ++{
     ++	struct slice start = s;
     ++	int l = strlen(str);
     ++	int n = put_var_int(s, l);
     ++	if (n < 0) {
     ++		return -1;
     ++	}
     ++	s.buf += n;
     ++	s.len -= n;
     ++	if (s.len < l) {
     ++		return -1;
     ++	}
     ++	memcpy(s.buf, str, l);
     ++	s.buf += l;
     ++	s.len -= l;
     ++
     ++	return start.len - s.len;
     ++}
     ++
     ++static int ref_record_encode(const void *rec, struct slice s, int hash_size)
     ++{
     ++	const struct ref_record *r = (const struct ref_record *)rec;
     ++	struct slice start = s;
     ++	int n = put_var_int(s, r->update_index);
     ++	assert(hash_size > 0);
     ++	if (n < 0) {
     ++		return -1;
     ++	}
     ++	s.buf += n;
     ++	s.len -= n;
     ++
     ++	if (r->value != NULL) {
     ++		if (s.len < hash_size) {
     ++			return -1;
     ++		}
     ++		memcpy(s.buf, r->value, hash_size);
     ++		s.buf += hash_size;
     ++		s.len -= hash_size;
     ++	}
     ++
     ++	if (r->target_value != NULL) {
     ++		if (s.len < hash_size) {
     ++			return -1;
     ++		}
     ++		memcpy(s.buf, r->target_value, hash_size);
     ++		s.buf += hash_size;
     ++		s.len -= hash_size;
     ++	}
     ++
     ++	if (r->target != NULL) {
     ++		int n = encode_string(r->target, s);
     ++		if (n < 0) {
     ++			return -1;
     ++		}
     ++		s.buf += n;
     ++		s.len -= n;
     ++	}
     ++
     ++	return start.len - s.len;
      +}
      +
      +static int ref_record_decode(void *rec, struct slice key, byte val_type,
     -+                             struct slice in, int hash_size) {
     -+  struct ref_record *r = (struct ref_record *)rec;
     -+  struct slice start = in;
     -+  bool seen_value = false;
     -+  bool seen_target_value = false;
     -+  bool seen_target = false;
     -+
     -+  int n = get_var_int(&r->update_index, in);
     -+  if (n < 0) {
     -+    return n;
     -+  }
     -+  assert(hash_size > 0);
     -+
     -+  in.buf += n;
     -+  in.len -= n;
     -+
     -+  r->ref_name = realloc(r->ref_name, key.len + 1);
     -+  memcpy(r->ref_name, key.buf, key.len);
     -+  r->ref_name[key.len] = 0;
     -+
     -+  switch (val_type) {
     -+    case 1:
     -+    case 2:
     -+      if (in.len < hash_size) {
     -+        return -1;
     -+      }
     -+
     -+      if (r->value == NULL) {
     -+        r->value = malloc(hash_size);
     -+      }
     -+      seen_value = true;
     -+      memcpy(r->value, in.buf, hash_size);
     -+      in.buf += hash_size;
     -+      in.len -= hash_size;
     -+      if (val_type == 1) {
     -+        break;
     -+      }
     -+      if (r->target_value == NULL) {
     -+        r->target_value = malloc(hash_size);
     -+      }
     -+      seen_target_value = true;
     -+      memcpy(r->target_value, in.buf, hash_size);
     -+      in.buf += hash_size;
     -+      in.len -= hash_size;
     -+      break;
     -+    case 3: {
     -+      struct slice dest = {};
     -+      int n = decode_string(&dest, in);
     -+      if (n < 0) {
     -+        return -1;
     -+      }
     -+      in.buf += n;
     -+      in.len -= n;
     -+      seen_target = true;
     -+      r->target = (char *)slice_as_string(&dest);
     -+    } break;
     -+
     -+    case 0:
     -+      break;
     -+    default:
     -+      abort();
     -+      break;
     -+  }
     -+
     -+  if (!seen_target && r->target != NULL) {
     -+    free(r->target);
     -+    r->target = NULL;
     -+  }
     -+  if (!seen_target_value && r->target_value != NULL) {
     -+    free(r->target_value);
     -+    r->target_value = NULL;
     -+  }
     -+  if (!seen_value && r->value != NULL) {
     -+    free(r->value);
     -+    r->value = NULL;
     -+  }
     -+
     -+  return start.len - in.len;
     ++			     struct slice in, int hash_size)
     ++{
     ++	struct ref_record *r = (struct ref_record *)rec;
     ++	struct slice start = in;
     ++	bool seen_value = false;
     ++	bool seen_target_value = false;
     ++	bool seen_target = false;
     ++
     ++	int n = get_var_int(&r->update_index, in);
     ++	if (n < 0) {
     ++		return n;
     ++	}
     ++	assert(hash_size > 0);
     ++
     ++	in.buf += n;
     ++	in.len -= n;
     ++
     ++	r->ref_name = realloc(r->ref_name, key.len + 1);
     ++	memcpy(r->ref_name, key.buf, key.len);
     ++	r->ref_name[key.len] = 0;
     ++
     ++	switch (val_type) {
     ++	case 1:
     ++	case 2:
     ++		if (in.len < hash_size) {
     ++			return -1;
     ++		}
     ++
     ++		if (r->value == NULL) {
     ++			r->value = malloc(hash_size);
     ++		}
     ++		seen_value = true;
     ++		memcpy(r->value, in.buf, hash_size);
     ++		in.buf += hash_size;
     ++		in.len -= hash_size;
     ++		if (val_type == 1) {
     ++			break;
     ++		}
     ++		if (r->target_value == NULL) {
     ++			r->target_value = malloc(hash_size);
     ++		}
     ++		seen_target_value = true;
     ++		memcpy(r->target_value, in.buf, hash_size);
     ++		in.buf += hash_size;
     ++		in.len -= hash_size;
     ++		break;
     ++	case 3: {
     ++		struct slice dest = {};
     ++		int n = decode_string(&dest, in);
     ++		if (n < 0) {
     ++			return -1;
     ++		}
     ++		in.buf += n;
     ++		in.len -= n;
     ++		seen_target = true;
     ++		r->target = (char *)slice_as_string(&dest);
     ++	} break;
     ++
     ++	case 0:
     ++		break;
     ++	default:
     ++		abort();
     ++		break;
     ++	}
     ++
     ++	if (!seen_target && r->target != NULL) {
     ++		free(r->target);
     ++		r->target = NULL;
     ++	}
     ++	if (!seen_target_value && r->target_value != NULL) {
     ++		free(r->target_value);
     ++		r->target_value = NULL;
     ++	}
     ++	if (!seen_value && r->value != NULL) {
     ++		free(r->value);
     ++		r->value = NULL;
     ++	}
     ++
     ++	return start.len - in.len;
      +}
      +
      +int decode_key(struct slice *key, byte *extra, struct slice last_key,
     -+               struct slice in) {
     -+  int start_len = in.len;
     -+  uint64_t prefix_len = 0;
     -+  uint64_t suffix_len = 0;
     -+  int n = get_var_int(&prefix_len, in);
     -+  if (n < 0) {
     -+    return -1;
     -+  }
     -+  in.buf += n;
     -+  in.len -= n;
     -+
     -+  if (prefix_len > last_key.len) {
     -+    return -1;
     -+  }
     -+
     -+  n = get_var_int(&suffix_len, in);
     -+  if (n <= 0) {
     -+    return -1;
     -+  }
     -+  in.buf += n;
     -+  in.len -= n;
     -+
     -+  *extra = (byte)(suffix_len & 0x7);
     -+  suffix_len >>= 3;
     -+
     -+  if (in.len < suffix_len) {
     -+    return -1;
     -+  }
     -+
     -+  slice_resize(key, suffix_len + prefix_len);
     -+  memcpy(key->buf, last_key.buf, prefix_len);
     -+
     -+  memcpy(key->buf + prefix_len, in.buf, suffix_len);
     -+  in.buf += suffix_len;
     -+  in.len -= suffix_len;
     -+
     -+  return start_len - in.len;
     ++	       struct slice in)
     ++{
     ++	int start_len = in.len;
     ++	uint64_t prefix_len = 0;
     ++	uint64_t suffix_len = 0;
     ++	int n = get_var_int(&prefix_len, in);
     ++	if (n < 0) {
     ++		return -1;
     ++	}
     ++	in.buf += n;
     ++	in.len -= n;
     ++
     ++	if (prefix_len > last_key.len) {
     ++		return -1;
     ++	}
     ++
     ++	n = get_var_int(&suffix_len, in);
     ++	if (n <= 0) {
     ++		return -1;
     ++	}
     ++	in.buf += n;
     ++	in.len -= n;
     ++
     ++	*extra = (byte)(suffix_len & 0x7);
     ++	suffix_len >>= 3;
     ++
     ++	if (in.len < suffix_len) {
     ++		return -1;
     ++	}
     ++
     ++	slice_resize(key, suffix_len + prefix_len);
     ++	memcpy(key->buf, last_key.buf, prefix_len);
     ++
     ++	memcpy(key->buf + prefix_len, in.buf, suffix_len);
     ++	in.buf += suffix_len;
     ++	in.len -= suffix_len;
     ++
     ++	return start_len - in.len;
      +}
      +
      +struct record_vtable ref_record_vtable = {
     -+    .key = &ref_record_key,
     -+    .type = &ref_record_type,
     -+    .copy_from = &ref_record_copy_from,
     -+    .val_type = &ref_record_val_type,
     -+    .encode = &ref_record_encode,
     -+    .decode = &ref_record_decode,
     -+    .clear = &ref_record_clear_void,
     ++	.key = &ref_record_key,
     ++	.type = &ref_record_type,
     ++	.copy_from = &ref_record_copy_from,
     ++	.val_type = &ref_record_val_type,
     ++	.encode = &ref_record_encode,
     ++	.decode = &ref_record_decode,
     ++	.clear = &ref_record_clear_void,
      +};
      +
     -+static byte obj_record_type(void) { return BLOCK_TYPE_OBJ; }
     -+
     -+static void obj_record_key(const void *r, struct slice *dest) {
     -+  const struct obj_record *rec = (const struct obj_record *)r;
     -+  slice_resize(dest, rec->hash_prefix_len);
     -+  memcpy(dest->buf, rec->hash_prefix, rec->hash_prefix_len);
     -+}
     -+
     -+static void obj_record_copy_from(void *rec, const void *src_rec,
     -+                                 int hash_size) {
     -+  struct obj_record *ref = (struct obj_record *)rec;
     -+  const struct obj_record *src = (const struct obj_record *)src_rec;
     -+
     -+  *ref = *src;
     -+  ref->hash_prefix = malloc(ref->hash_prefix_len);
     -+  memcpy(ref->hash_prefix, src->hash_prefix, ref->hash_prefix_len);
     -+
     -+  {
     -+    int olen = ref->offset_len * sizeof(uint64_t);
     -+    ref->offsets = malloc(olen);
     -+    memcpy(ref->offsets, src->offsets, olen);
     -+  }
     -+}
     -+
     -+static void obj_record_clear(void *rec) {
     -+  struct obj_record *ref = (struct obj_record *)rec;
     -+  free(ref->hash_prefix);
     -+  free(ref->offsets);
     -+  memset(ref, 0, sizeof(struct obj_record));
     -+}
     -+
     -+static byte obj_record_val_type(const void *rec) {
     -+  struct obj_record *r = (struct obj_record *)rec;
     -+  if (r->offset_len > 0 && r->offset_len < 8) {
     -+    return r->offset_len;
     -+  }
     -+  return 0;
     -+}
     -+
     -+static int obj_record_encode(const void *rec, struct slice s, int hash_size) {
     -+  struct obj_record *r = (struct obj_record *)rec;
     -+  struct slice start = s;
     -+  int n = 0;
     -+  if (r->offset_len == 0 || r->offset_len >= 8) {
     -+    n = put_var_int(s, r->offset_len);
     -+    if (n < 0) {
     -+      return -1;
     -+    }
     -+    s.buf += n;
     -+    s.len -= n;
     -+  }
     -+  if (r->offset_len == 0) {
     -+    return start.len - s.len;
     -+  }
     -+  n = put_var_int(s, r->offsets[0]);
     -+  if (n < 0) {
     -+    return -1;
     -+  }
     -+  s.buf += n;
     -+  s.len -= n;
     -+
     -+  {
     -+    uint64_t last = r->offsets[0];
     -+    for (int i = 1; i < r->offset_len; i++) {
     -+      int n = put_var_int(s, r->offsets[i] - last);
     -+      if (n < 0) {
     -+        return -1;
     -+      }
     -+      s.buf += n;
     -+      s.len -= n;
     -+      last = r->offsets[i];
     -+    }
     -+  }
     -+  return start.len - s.len;
     ++static byte obj_record_type(void)
     ++{
     ++	return BLOCK_TYPE_OBJ;
     ++}
     ++
     ++static void obj_record_key(const void *r, struct slice *dest)
     ++{
     ++	const struct obj_record *rec = (const struct obj_record *)r;
     ++	slice_resize(dest, rec->hash_prefix_len);
     ++	memcpy(dest->buf, rec->hash_prefix, rec->hash_prefix_len);
     ++}
     ++
     ++static void obj_record_copy_from(void *rec, const void *src_rec, int hash_size)
     ++{
     ++	struct obj_record *ref = (struct obj_record *)rec;
     ++	const struct obj_record *src = (const struct obj_record *)src_rec;
     ++
     ++	*ref = *src;
     ++	ref->hash_prefix = malloc(ref->hash_prefix_len);
     ++	memcpy(ref->hash_prefix, src->hash_prefix, ref->hash_prefix_len);
     ++
     ++	{
     ++		int olen = ref->offset_len * sizeof(uint64_t);
     ++		ref->offsets = malloc(olen);
     ++		memcpy(ref->offsets, src->offsets, olen);
     ++	}
     ++}
     ++
     ++static void obj_record_clear(void *rec)
     ++{
     ++	struct obj_record *ref = (struct obj_record *)rec;
     ++	free(ref->hash_prefix);
     ++	free(ref->offsets);
     ++	memset(ref, 0, sizeof(struct obj_record));
     ++}
     ++
     ++static byte obj_record_val_type(const void *rec)
     ++{
     ++	struct obj_record *r = (struct obj_record *)rec;
     ++	if (r->offset_len > 0 && r->offset_len < 8) {
     ++		return r->offset_len;
     ++	}
     ++	return 0;
     ++}
     ++
     ++static int obj_record_encode(const void *rec, struct slice s, int hash_size)
     ++{
     ++	struct obj_record *r = (struct obj_record *)rec;
     ++	struct slice start = s;
     ++	int n = 0;
     ++	if (r->offset_len == 0 || r->offset_len >= 8) {
     ++		n = put_var_int(s, r->offset_len);
     ++		if (n < 0) {
     ++			return -1;
     ++		}
     ++		s.buf += n;
     ++		s.len -= n;
     ++	}
     ++	if (r->offset_len == 0) {
     ++		return start.len - s.len;
     ++	}
     ++	n = put_var_int(s, r->offsets[0]);
     ++	if (n < 0) {
     ++		return -1;
     ++	}
     ++	s.buf += n;
     ++	s.len -= n;
     ++
     ++	{
     ++		uint64_t last = r->offsets[0];
     ++		int i = 0;
     ++		for (i = 1; i < r->offset_len; i++) {
     ++			int n = put_var_int(s, r->offsets[i] - last);
     ++			if (n < 0) {
     ++				return -1;
     ++			}
     ++			s.buf += n;
     ++			s.len -= n;
     ++			last = r->offsets[i];
     ++		}
     ++	}
     ++	return start.len - s.len;
      +}
      +
      +static int obj_record_decode(void *rec, struct slice key, byte val_type,
     -+                             struct slice in, int hash_size) {
     -+  struct slice start = in;
     -+  struct obj_record *r = (struct obj_record *)rec;
     -+  uint64_t count = val_type;
     -+  int n = 0;
     -+  r->hash_prefix = malloc(key.len);
     -+  memcpy(r->hash_prefix, key.buf, key.len);
     -+  r->hash_prefix_len = key.len;
     -+
     -+  if (val_type == 0) {
     -+    n = get_var_int(&count, in);
     -+    if (n < 0) {
     -+      return n;
     -+    }
     -+
     -+    in.buf += n;
     -+    in.len -= n;
     -+  }
     -+
     -+  r->offsets = NULL;
     -+  r->offset_len = 0;
     -+  if (count == 0) {
     -+    return start.len - in.len;
     -+  }
     -+
     -+  r->offsets = malloc(count * sizeof(uint64_t));
     -+  r->offset_len = count;
     -+
     -+  n = get_var_int(&r->offsets[0], in);
     -+  if (n < 0) {
     -+    return n;
     -+  }
     -+
     -+  in.buf += n;
     -+  in.len -= n;
     -+
     -+  {
     -+    uint64_t last = r->offsets[0];
     -+    int j = 1;
     -+    while (j < count) {
     -+      uint64_t delta = 0;
     -+      int n = get_var_int(&delta, in);
     -+      if (n < 0) {
     -+        return n;
     -+      }
     -+
     -+      in.buf += n;
     -+      in.len -= n;
     -+
     -+      last = r->offsets[j] = (delta + last);
     -+      j++;
     -+    }
     -+  }
     -+  return start.len - in.len;
     ++			     struct slice in, int hash_size)
     ++{
     ++	struct slice start = in;
     ++	struct obj_record *r = (struct obj_record *)rec;
     ++	uint64_t count = val_type;
     ++	int n = 0;
     ++	r->hash_prefix = malloc(key.len);
     ++	memcpy(r->hash_prefix, key.buf, key.len);
     ++	r->hash_prefix_len = key.len;
     ++
     ++	if (val_type == 0) {
     ++		n = get_var_int(&count, in);
     ++		if (n < 0) {
     ++			return n;
     ++		}
     ++
     ++		in.buf += n;
     ++		in.len -= n;
     ++	}
     ++
     ++	r->offsets = NULL;
     ++	r->offset_len = 0;
     ++	if (count == 0) {
     ++		return start.len - in.len;
     ++	}
     ++
     ++	r->offsets = malloc(count * sizeof(uint64_t));
     ++	r->offset_len = count;
     ++
     ++	n = get_var_int(&r->offsets[0], in);
     ++	if (n < 0) {
     ++		return n;
     ++	}
     ++
     ++	in.buf += n;
     ++	in.len -= n;
     ++
     ++	{
     ++		uint64_t last = r->offsets[0];
     ++		int j = 1;
     ++		while (j < count) {
     ++			uint64_t delta = 0;
     ++			int n = get_var_int(&delta, in);
     ++			if (n < 0) {
     ++				return n;
     ++			}
     ++
     ++			in.buf += n;
     ++			in.len -= n;
     ++
     ++			last = r->offsets[j] = (delta + last);
     ++			j++;
     ++		}
     ++	}
     ++	return start.len - in.len;
      +}
      +
      +struct record_vtable obj_record_vtable = {
     -+    .key = &obj_record_key,
     -+    .type = &obj_record_type,
     -+    .copy_from = &obj_record_copy_from,
     -+    .val_type = &obj_record_val_type,
     -+    .encode = &obj_record_encode,
     -+    .decode = &obj_record_decode,
     -+    .clear = &obj_record_clear,
     ++	.key = &obj_record_key,
     ++	.type = &obj_record_type,
     ++	.copy_from = &obj_record_copy_from,
     ++	.val_type = &obj_record_val_type,
     ++	.encode = &obj_record_encode,
     ++	.decode = &obj_record_decode,
     ++	.clear = &obj_record_clear,
      +};
      +
     -+void log_record_print(struct log_record *log, int hash_size) {
     -+  char hex[SHA256_SIZE + 1] = {};
     -+
     -+  printf("log{%s(%ld) %s <%s> %lu %04d\n", log->ref_name, log->update_index,
     -+         log->name, log->email, log->time, log->tz_offset);
     -+  hex_format(hex, log->old_hash, hash_size);
     -+  printf("%s => ", hex);
     -+  hex_format(hex, log->new_hash, hash_size);
     -+  printf("%s\n\n%s\n}\n", hex, log->message);
     ++void log_record_print(struct log_record *log, int hash_size)
     ++{
     ++	char hex[SHA256_SIZE + 1] = {};
     ++
     ++	printf("log{%s(%" PRIdMAX ") %s <%s> %lu %04d\n", log->ref_name,
     ++	       log->update_index, log->name, log->email, log->time,
     ++	       log->tz_offset);
     ++	hex_format(hex, log->old_hash, hash_size);
     ++	printf("%s => ", hex);
     ++	hex_format(hex, log->new_hash, hash_size);
     ++	printf("%s\n\n%s\n}\n", hex, log->message);
     ++}
     ++
     ++static byte log_record_type(void)
     ++{
     ++	return BLOCK_TYPE_LOG;
     ++}
     ++
     ++static void log_record_key(const void *r, struct slice *dest)
     ++{
     ++	const struct log_record *rec = (const struct log_record *)r;
     ++	int len = strlen(rec->ref_name);
     ++	uint64_t ts = 0;
     ++	slice_resize(dest, len + 9);
     ++	memcpy(dest->buf, rec->ref_name, len + 1);
     ++	ts = (~ts) - rec->update_index;
     ++	put_u64(dest->buf + 1 + len, ts);
     ++}
     ++
     ++static void log_record_copy_from(void *rec, const void *src_rec, int hash_size)
     ++{
     ++	struct log_record *dst = (struct log_record *)rec;
     ++	const struct log_record *src = (const struct log_record *)src_rec;
     ++
     ++	*dst = *src;
     ++	dst->ref_name = strdup(dst->ref_name);
     ++	dst->email = strdup(dst->email);
     ++	dst->name = strdup(dst->name);
     ++	dst->message = strdup(dst->message);
     ++	if (dst->new_hash != NULL) {
     ++		dst->new_hash = malloc(hash_size);
     ++		memcpy(dst->new_hash, src->new_hash, hash_size);
     ++	}
     ++	if (dst->old_hash != NULL) {
     ++		dst->old_hash = malloc(hash_size);
     ++		memcpy(dst->old_hash, src->old_hash, hash_size);
     ++	}
     ++}
     ++
     ++static void log_record_clear_void(void *rec)
     ++{
     ++	struct log_record *r = (struct log_record *)rec;
     ++	log_record_clear(r);
     ++}
     ++
     ++void log_record_clear(struct log_record *r)
     ++{
     ++	free(r->ref_name);
     ++	free(r->new_hash);
     ++	free(r->old_hash);
     ++	free(r->name);
     ++	free(r->email);
     ++	free(r->message);
     ++	memset(r, 0, sizeof(struct log_record));
     ++}
     ++
     ++static byte log_record_val_type(const void *rec)
     ++{
     ++	return 1;
      +}
      +
     -+static byte log_record_type(void) { return BLOCK_TYPE_LOG; }
     -+
     -+static void log_record_key(const void *r, struct slice *dest) {
     -+  const struct log_record *rec = (const struct log_record *)r;
     -+  int len = strlen(rec->ref_name);
     -+  uint64_t ts = 0;
     -+  slice_resize(dest, len + 9);
     -+  memcpy(dest->buf, rec->ref_name, len + 1);
     -+  ts = (~ts) - rec->update_index;
     -+  put_u64(dest->buf + 1 + len, ts);
     -+}
     -+
     -+static void log_record_copy_from(void *rec, const void *src_rec,
     -+                                 int hash_size) {
     -+  struct log_record *dst = (struct log_record *)rec;
     -+  const struct log_record *src = (const struct log_record *)src_rec;
     -+
     -+  *dst = *src;
     -+  dst->ref_name = strdup(dst->ref_name);
     -+  dst->email = strdup(dst->email);
     -+  dst->name = strdup(dst->name);
     -+  dst->message = strdup(dst->message);
     -+  if (dst->new_hash != NULL) {
     -+    dst->new_hash = malloc(hash_size);
     -+    memcpy(dst->new_hash, src->new_hash, hash_size);
     -+  }
     -+  if (dst->old_hash != NULL) {
     -+    dst->old_hash = malloc(hash_size);
     -+    memcpy(dst->old_hash, src->old_hash, hash_size);
     -+  }
     -+}
     -+
     -+static void log_record_clear_void(void *rec) {
     -+  struct log_record *r = (struct log_record *)rec;
     -+  log_record_clear(r);
     -+}
     -+
     -+void log_record_clear(struct log_record *r) {
     -+  free(r->ref_name);
     -+  free(r->new_hash);
     -+  free(r->old_hash);
     -+  free(r->name);
     -+  free(r->email);
     -+  free(r->message);
     -+  memset(r, 0, sizeof(struct log_record));
     -+}
     -+
     -+static byte log_record_val_type(const void *rec) { return 1; }
     -+
      +static byte zero[SHA256_SIZE] = {};
      +
     -+static int log_record_encode(const void *rec, struct slice s, int hash_size) {
     -+  struct log_record *r = (struct log_record *)rec;
     -+  struct slice start = s;
     -+  int n = 0;
     -+  byte *oldh = r->old_hash;
     -+  byte *newh = r->new_hash;
     -+  if (oldh == NULL) {
     -+    oldh = zero;
     -+  }
     -+  if (newh == NULL) {
     -+    newh = zero;
     -+  }
     -+
     -+  if (s.len < 2 * hash_size) {
     -+    return -1;
     -+  }
     -+
     -+  memcpy(s.buf, oldh, hash_size);
     -+  memcpy(s.buf + hash_size, newh, hash_size);
     -+  s.buf += 2 * hash_size;
     -+  s.len -= 2 * hash_size;
     -+
     -+  n = encode_string(r->name ? r->name : "", s);
     -+  if (n < 0) {
     -+    return -1;
     -+  }
     -+  s.len -= n;
     -+  s.buf += n;
     -+
     -+  n = encode_string(r->email ? r->email : "", s);
     -+  if (n < 0) {
     -+    return -1;
     -+  }
     -+  s.len -= n;
     -+  s.buf += n;
     -+
     -+  n = put_var_int(s, r->time);
     -+  if (n < 0) {
     -+    return -1;
     -+  }
     -+  s.buf += n;
     -+  s.len -= n;
     -+
     -+  if (s.len < 2) {
     -+    return -1;
     -+  }
     -+
     -+  put_u16(s.buf, r->tz_offset);
     -+  s.buf += 2;
     -+  s.len -= 2;
     -+
     -+  n = encode_string(r->message ? r->message : "", s);
     -+  if (n < 0) {
     -+    return -1;
     -+  }
     -+  s.len -= n;
     -+  s.buf += n;
     -+
     -+  return start.len - s.len;
     ++static int log_record_encode(const void *rec, struct slice s, int hash_size)
     ++{
     ++	struct log_record *r = (struct log_record *)rec;
     ++	struct slice start = s;
     ++	int n = 0;
     ++	byte *oldh = r->old_hash;
     ++	byte *newh = r->new_hash;
     ++	if (oldh == NULL) {
     ++		oldh = zero;
     ++	}
     ++	if (newh == NULL) {
     ++		newh = zero;
     ++	}
     ++
     ++	if (s.len < 2 * hash_size) {
     ++		return -1;
     ++	}
     ++
     ++	memcpy(s.buf, oldh, hash_size);
     ++	memcpy(s.buf + hash_size, newh, hash_size);
     ++	s.buf += 2 * hash_size;
     ++	s.len -= 2 * hash_size;
     ++
     ++	n = encode_string(r->name ? r->name : "", s);
     ++	if (n < 0) {
     ++		return -1;
     ++	}
     ++	s.len -= n;
     ++	s.buf += n;
     ++
     ++	n = encode_string(r->email ? r->email : "", s);
     ++	if (n < 0) {
     ++		return -1;
     ++	}
     ++	s.len -= n;
     ++	s.buf += n;
     ++
     ++	n = put_var_int(s, r->time);
     ++	if (n < 0) {
     ++		return -1;
     ++	}
     ++	s.buf += n;
     ++	s.len -= n;
     ++
     ++	if (s.len < 2) {
     ++		return -1;
     ++	}
     ++
     ++	put_u16(s.buf, r->tz_offset);
     ++	s.buf += 2;
     ++	s.len -= 2;
     ++
     ++	n = encode_string(r->message ? r->message : "", s);
     ++	if (n < 0) {
     ++		return -1;
     ++	}
     ++	s.len -= n;
     ++	s.buf += n;
     ++
     ++	return start.len - s.len;
      +}
      +
      +static int log_record_decode(void *rec, struct slice key, byte val_type,
     -+                             struct slice in, int hash_size) {
     -+  struct slice start = in;
     -+  struct log_record *r = (struct log_record *)rec;
     -+  uint64_t max = 0;
     -+  uint64_t ts = 0;
     -+  struct slice dest = {};
     -+  int n;
     -+
     -+  if (key.len <= 9 || key.buf[key.len - 9] != 0) {
     -+    return FORMAT_ERROR;
     -+  }
     -+
     -+  r->ref_name = realloc(r->ref_name, key.len - 8);
     -+  memcpy(r->ref_name, key.buf, key.len - 8);
     -+  ts = get_u64(key.buf + key.len - 8);
     -+
     -+  r->update_index = (~max) - ts;
     -+
     -+  if (in.len < 2 * hash_size) {
     -+    return FORMAT_ERROR;
     -+  }
     -+
     -+  r->old_hash = realloc(r->old_hash, hash_size);
     -+  r->new_hash = realloc(r->new_hash, hash_size);
     -+
     -+  memcpy(r->old_hash, in.buf, hash_size);
     -+  memcpy(r->new_hash, in.buf + hash_size, hash_size);
     -+
     -+  in.buf += 2 * hash_size;
     -+  in.len -= 2 * hash_size;
     -+
     -+  n = decode_string(&dest, in);
     -+  if (n < 0) {
     -+    goto error;
     -+  }
     -+  in.len -= n;
     -+  in.buf += n;
     -+
     -+  r->name = realloc(r->name, dest.len + 1);
     -+  memcpy(r->name, dest.buf, dest.len);
     -+  r->name[dest.len] = 0;
     -+
     -+  slice_resize(&dest, 0);
     -+  n = decode_string(&dest, in);
     -+  if (n < 0) {
     -+    goto error;
     -+  }
     -+  in.len -= n;
     -+  in.buf += n;
     -+
     -+  r->email = realloc(r->email, dest.len + 1);
     -+  memcpy(r->email, dest.buf, dest.len);
     -+  r->email[dest.len] = 0;
     -+
     -+  ts = 0;
     -+  n = get_var_int(&ts, in);
     -+  if (n < 0) {
     -+    goto error;
     -+  }
     -+  in.len -= n;
     -+  in.buf += n;
     -+  r->time = ts;
     -+  if (in.len < 2) {
     -+    goto error;
     -+  }
     -+
     -+  r->tz_offset = get_u16(in.buf);
     -+  in.buf += 2;
     -+  in.len -= 2;
     -+
     -+  slice_resize(&dest, 0);
     -+  n = decode_string(&dest, in);
     -+  if (n < 0) {
     -+    goto error;
     -+  }
     -+  in.len -= n;
     -+  in.buf += n;
     -+
     -+  r->message = realloc(r->message, dest.len + 1);
     -+  memcpy(r->message, dest.buf, dest.len);
     -+  r->message[dest.len] = 0;
     -+
     -+  return start.len - in.len;
     ++			     struct slice in, int hash_size)
     ++{
     ++	struct slice start = in;
     ++	struct log_record *r = (struct log_record *)rec;
     ++	uint64_t max = 0;
     ++	uint64_t ts = 0;
     ++	struct slice dest = {};
     ++	int n;
     ++
     ++	if (key.len <= 9 || key.buf[key.len - 9] != 0) {
     ++		return FORMAT_ERROR;
     ++	}
     ++
     ++	r->ref_name = realloc(r->ref_name, key.len - 8);
     ++	memcpy(r->ref_name, key.buf, key.len - 8);
     ++	ts = get_u64(key.buf + key.len - 8);
     ++
     ++	r->update_index = (~max) - ts;
     ++
     ++	if (in.len < 2 * hash_size) {
     ++		return FORMAT_ERROR;
     ++	}
     ++
     ++	r->old_hash = realloc(r->old_hash, hash_size);
     ++	r->new_hash = realloc(r->new_hash, hash_size);
     ++
     ++	memcpy(r->old_hash, in.buf, hash_size);
     ++	memcpy(r->new_hash, in.buf + hash_size, hash_size);
     ++
     ++	in.buf += 2 * hash_size;
     ++	in.len -= 2 * hash_size;
     ++
     ++	n = decode_string(&dest, in);
     ++	if (n < 0) {
     ++		goto error;
     ++	}
     ++	in.len -= n;
     ++	in.buf += n;
     ++
     ++	r->name = realloc(r->name, dest.len + 1);
     ++	memcpy(r->name, dest.buf, dest.len);
     ++	r->name[dest.len] = 0;
     ++
     ++	slice_resize(&dest, 0);
     ++	n = decode_string(&dest, in);
     ++	if (n < 0) {
     ++		goto error;
     ++	}
     ++	in.len -= n;
     ++	in.buf += n;
     ++
     ++	r->email = realloc(r->email, dest.len + 1);
     ++	memcpy(r->email, dest.buf, dest.len);
     ++	r->email[dest.len] = 0;
     ++
     ++	ts = 0;
     ++	n = get_var_int(&ts, in);
     ++	if (n < 0) {
     ++		goto error;
     ++	}
     ++	in.len -= n;
     ++	in.buf += n;
     ++	r->time = ts;
     ++	if (in.len < 2) {
     ++		goto error;
     ++	}
     ++
     ++	r->tz_offset = get_u16(in.buf);
     ++	in.buf += 2;
     ++	in.len -= 2;
     ++
     ++	slice_resize(&dest, 0);
     ++	n = decode_string(&dest, in);
     ++	if (n < 0) {
     ++		goto error;
     ++	}
     ++	in.len -= n;
     ++	in.buf += n;
     ++
     ++	r->message = realloc(r->message, dest.len + 1);
     ++	memcpy(r->message, dest.buf, dest.len);
     ++	r->message[dest.len] = 0;
     ++
     ++	return start.len - in.len;
      +
      +error:
     -+  free(slice_yield(&dest));
     -+  return FORMAT_ERROR;
     -+}
     -+
     -+static bool null_streq(char *a, char *b) {
     -+  char *empty = "";
     -+  if (a == NULL) {
     -+    a = empty;
     -+  }
     -+  if (b == NULL) {
     -+    b = empty;
     -+  }
     -+  return 0 == strcmp(a, b);
     -+}
     -+
     -+static bool zero_hash_eq(byte *a, byte *b, int sz) {
     -+  if (a == NULL) {
     -+    a = zero;
     -+  }
     -+  if (b == NULL) {
     -+    b = zero;
     -+  }
     -+  return 0 == memcmp(a, b, sz);
     -+}
     -+
     -+bool log_record_equal(struct log_record *a, struct log_record *b,
     -+                      int hash_size) {
     -+  return null_streq(a->name, b->name) && null_streq(a->email, b->email) &&
     -+         null_streq(a->message, b->message) &&
     -+         zero_hash_eq(a->old_hash, b->old_hash, hash_size) &&
     -+         zero_hash_eq(a->new_hash, b->new_hash, hash_size) &&
     -+         a->time == b->time && a->tz_offset == b->tz_offset &&
     -+         a->update_index == b->update_index;
     ++	free(slice_yield(&dest));
     ++	return FORMAT_ERROR;
     ++}
     ++
     ++static bool null_streq(char *a, char *b)
     ++{
     ++	char *empty = "";
     ++	if (a == NULL) {
     ++		a = empty;
     ++	}
     ++	if (b == NULL) {
     ++		b = empty;
     ++	}
     ++	return 0 == strcmp(a, b);
     ++}
     ++
     ++static bool zero_hash_eq(byte *a, byte *b, int sz)
     ++{
     ++	if (a == NULL) {
     ++		a = zero;
     ++	}
     ++	if (b == NULL) {
     ++		b = zero;
     ++	}
     ++	return !memcmp(a, b, sz);
     ++}
     ++
     ++bool log_record_equal(struct log_record *a, struct log_record *b, int hash_size)
     ++{
     ++	return null_streq(a->name, b->name) && null_streq(a->email, b->email) &&
     ++	       null_streq(a->message, b->message) &&
     ++	       zero_hash_eq(a->old_hash, b->old_hash, hash_size) &&
     ++	       zero_hash_eq(a->new_hash, b->new_hash, hash_size) &&
     ++	       a->time == b->time && a->tz_offset == b->tz_offset &&
     ++	       a->update_index == b->update_index;
      +}
      +
      +struct record_vtable log_record_vtable = {
     -+    .key = &log_record_key,
     -+    .type = &log_record_type,
     -+    .copy_from = &log_record_copy_from,
     -+    .val_type = &log_record_val_type,
     -+    .encode = &log_record_encode,
     -+    .decode = &log_record_decode,
     -+    .clear = &log_record_clear_void,
     ++	.key = &log_record_key,
     ++	.type = &log_record_type,
     ++	.copy_from = &log_record_copy_from,
     ++	.val_type = &log_record_val_type,
     ++	.encode = &log_record_encode,
     ++	.decode = &log_record_decode,
     ++	.clear = &log_record_clear_void,
      +};
      +
     -+struct record new_record(byte typ) {
     -+  struct record rec;
     -+  switch (typ) {
     -+    case BLOCK_TYPE_REF: {
     -+      struct ref_record *r = calloc(1, sizeof(struct ref_record));
     -+      record_from_ref(&rec, r);
     -+      return rec;
     -+    }
     -+
     -+    case BLOCK_TYPE_OBJ: {
     -+      struct obj_record *r = calloc(1, sizeof(struct obj_record));
     -+      record_from_obj(&rec, r);
     -+      return rec;
     -+    }
     -+    case BLOCK_TYPE_LOG: {
     -+      struct log_record *r = calloc(1, sizeof(struct log_record));
     -+      record_from_log(&rec, r);
     -+      return rec;
     -+    }
     -+    case BLOCK_TYPE_INDEX: {
     -+      struct index_record *r = calloc(1, sizeof(struct index_record));
     -+      record_from_index(&rec, r);
     -+      return rec;
     -+    }
     -+  }
     -+  abort();
     -+  return rec;
     -+}
     -+
     -+static byte index_record_type(void) { return BLOCK_TYPE_INDEX; }
     -+
     -+static void index_record_key(const void *r, struct slice *dest) {
     -+  struct index_record *rec = (struct index_record *)r;
     -+  slice_copy(dest, rec->last_key);
     ++struct record new_record(byte typ)
     ++{
     ++	struct record rec;
     ++	switch (typ) {
     ++	case BLOCK_TYPE_REF: {
     ++		struct ref_record *r = calloc(1, sizeof(struct ref_record));
     ++		record_from_ref(&rec, r);
     ++		return rec;
     ++	}
     ++
     ++	case BLOCK_TYPE_OBJ: {
     ++		struct obj_record *r = calloc(1, sizeof(struct obj_record));
     ++		record_from_obj(&rec, r);
     ++		return rec;
     ++	}
     ++	case BLOCK_TYPE_LOG: {
     ++		struct log_record *r = calloc(1, sizeof(struct log_record));
     ++		record_from_log(&rec, r);
     ++		return rec;
     ++	}
     ++	case BLOCK_TYPE_INDEX: {
     ++		struct index_record *r = calloc(1, sizeof(struct index_record));
     ++		record_from_index(&rec, r);
     ++		return rec;
     ++	}
     ++	}
     ++	abort();
     ++	return rec;
     ++}
     ++
     ++static byte index_record_type(void)
     ++{
     ++	return BLOCK_TYPE_INDEX;
     ++}
     ++
     ++static void index_record_key(const void *r, struct slice *dest)
     ++{
     ++	struct index_record *rec = (struct index_record *)r;
     ++	slice_copy(dest, rec->last_key);
      +}
      +
      +static void index_record_copy_from(void *rec, const void *src_rec,
     -+                                   int hash_size) {
     -+  struct index_record *dst = (struct index_record *)rec;
     -+  struct index_record *src = (struct index_record *)src_rec;
     ++				   int hash_size)
     ++{
     ++	struct index_record *dst = (struct index_record *)rec;
     ++	struct index_record *src = (struct index_record *)src_rec;
      +
     -+  slice_copy(&dst->last_key, src->last_key);
     -+  dst->offset = src->offset;
     ++	slice_copy(&dst->last_key, src->last_key);
     ++	dst->offset = src->offset;
      +}
      +
     -+static void index_record_clear(void *rec) {
     -+  struct index_record *idx = (struct index_record *)rec;
     -+  free(slice_yield(&idx->last_key));
     ++static void index_record_clear(void *rec)
     ++{
     ++	struct index_record *idx = (struct index_record *)rec;
     ++	free(slice_yield(&idx->last_key));
      +}
      +
     -+static byte index_record_val_type(const void *rec) { return 0; }
     ++static byte index_record_val_type(const void *rec)
     ++{
     ++	return 0;
     ++}
      +
     -+static int index_record_encode(const void *rec, struct slice out,
     -+                               int hash_size) {
     -+  const struct index_record *r = (const struct index_record *)rec;
     -+  struct slice start = out;
     ++static int index_record_encode(const void *rec, struct slice out, int hash_size)
     ++{
     ++	const struct index_record *r = (const struct index_record *)rec;
     ++	struct slice start = out;
      +
     -+  int n = put_var_int(out, r->offset);
     -+  if (n < 0) {
     -+    return n;
     -+  }
     ++	int n = put_var_int(out, r->offset);
     ++	if (n < 0) {
     ++		return n;
     ++	}
      +
     -+  out.buf += n;
     -+  out.len -= n;
     ++	out.buf += n;
     ++	out.len -= n;
      +
     -+  return start.len - out.len;
     ++	return start.len - out.len;
      +}
      +
      +static int index_record_decode(void *rec, struct slice key, byte val_type,
     -+                               struct slice in, int hash_size) {
     -+  struct slice start = in;
     -+  struct index_record *r = (struct index_record *)rec;
     -+  int n = 0;
     ++			       struct slice in, int hash_size)
     ++{
     ++	struct slice start = in;
     ++	struct index_record *r = (struct index_record *)rec;
     ++	int n = 0;
      +
     -+  slice_copy(&r->last_key, key);
     ++	slice_copy(&r->last_key, key);
      +
     -+  n = get_var_int(&r->offset, in);
     -+  if (n < 0) {
     -+    return n;
     -+  }
     ++	n = get_var_int(&r->offset, in);
     ++	if (n < 0) {
     ++		return n;
     ++	}
      +
     -+  in.buf += n;
     -+  in.len -= n;
     -+  return start.len - in.len;
     ++	in.buf += n;
     ++	in.len -= n;
     ++	return start.len - in.len;
      +}
      +
      +struct record_vtable index_record_vtable = {
     -+    .key = &index_record_key,
     -+    .type = &index_record_type,
     -+    .copy_from = &index_record_copy_from,
     -+    .val_type = &index_record_val_type,
     -+    .encode = &index_record_encode,
     -+    .decode = &index_record_decode,
     -+    .clear = &index_record_clear,
     ++	.key = &index_record_key,
     ++	.type = &index_record_type,
     ++	.copy_from = &index_record_copy_from,
     ++	.val_type = &index_record_val_type,
     ++	.encode = &index_record_encode,
     ++	.decode = &index_record_decode,
     ++	.clear = &index_record_clear,
      +};
      +
     -+void record_key(struct record rec, struct slice *dest) {
     -+  rec.ops->key(rec.data, dest);
     ++void record_key(struct record rec, struct slice *dest)
     ++{
     ++	rec.ops->key(rec.data, dest);
      +}
      +
     -+byte record_type(struct record rec) { return rec.ops->type(); }
     ++byte record_type(struct record rec)
     ++{
     ++	return rec.ops->type();
     ++}
      +
     -+int record_encode(struct record rec, struct slice dest, int hash_size) {
     -+  return rec.ops->encode(rec.data, dest, hash_size);
     ++int record_encode(struct record rec, struct slice dest, int hash_size)
     ++{
     ++	return rec.ops->encode(rec.data, dest, hash_size);
      +}
      +
     -+void record_copy_from(struct record rec, struct record src, int hash_size) {
     -+  assert(src.ops->type() == rec.ops->type());
     ++void record_copy_from(struct record rec, struct record src, int hash_size)
     ++{
     ++	assert(src.ops->type() == rec.ops->type());
      +
     -+  rec.ops->copy_from(rec.data, src.data, hash_size);
     ++	rec.ops->copy_from(rec.data, src.data, hash_size);
      +}
      +
     -+byte record_val_type(struct record rec) { return rec.ops->val_type(rec.data); }
     ++byte record_val_type(struct record rec)
     ++{
     ++	return rec.ops->val_type(rec.data);
     ++}
      +
      +int record_decode(struct record rec, struct slice key, byte extra,
     -+                  struct slice src, int hash_size) {
     -+  return rec.ops->decode(rec.data, key, extra, src, hash_size);
     ++		  struct slice src, int hash_size)
     ++{
     ++	return rec.ops->decode(rec.data, key, extra, src, hash_size);
      +}
      +
     -+void record_clear(struct record rec) { return rec.ops->clear(rec.data); }
     ++void record_clear(struct record rec)
     ++{
     ++	return rec.ops->clear(rec.data);
     ++}
      +
     -+void record_from_ref(struct record *rec, struct ref_record *ref_rec) {
     -+  rec->data = ref_rec;
     -+  rec->ops = &ref_record_vtable;
     ++void record_from_ref(struct record *rec, struct ref_record *ref_rec)
     ++{
     ++	rec->data = ref_rec;
     ++	rec->ops = &ref_record_vtable;
      +}
      +
     -+void record_from_obj(struct record *rec, struct obj_record *obj_rec) {
     -+  rec->data = obj_rec;
     -+  rec->ops = &obj_record_vtable;
     ++void record_from_obj(struct record *rec, struct obj_record *obj_rec)
     ++{
     ++	rec->data = obj_rec;
     ++	rec->ops = &obj_record_vtable;
      +}
      +
     -+void record_from_index(struct record *rec, struct index_record *index_rec) {
     -+  rec->data = index_rec;
     -+  rec->ops = &index_record_vtable;
     ++void record_from_index(struct record *rec, struct index_record *index_rec)
     ++{
     ++	rec->data = index_rec;
     ++	rec->ops = &index_record_vtable;
      +}
      +
     -+void record_from_log(struct record *rec, struct log_record *log_rec) {
     -+  rec->data = log_rec;
     -+  rec->ops = &log_record_vtable;
     ++void record_from_log(struct record *rec, struct log_record *log_rec)
     ++{
     ++	rec->data = log_rec;
     ++	rec->ops = &log_record_vtable;
      +}
      +
     -+void *record_yield(struct record *rec) {
     -+  void *p = rec->data;
     -+  rec->data = NULL;
     -+  return p;
     ++void *record_yield(struct record *rec)
     ++{
     ++	void *p = rec->data;
     ++	rec->data = NULL;
     ++	return p;
      +}
      +
     -+struct ref_record *record_as_ref(struct record rec) {
     -+  assert(record_type(rec) == BLOCK_TYPE_REF);
     -+  return (struct ref_record *)rec.data;
     ++struct ref_record *record_as_ref(struct record rec)
     ++{
     ++	assert(record_type(rec) == BLOCK_TYPE_REF);
     ++	return (struct ref_record *)rec.data;
      +}
      +
     -+static bool hash_equal(byte *a, byte *b, int hash_size) {
     -+  if (a != NULL && b != NULL) {
     -+    return 0 == memcmp(a, b, hash_size);
     -+  }
     ++static bool hash_equal(byte *a, byte *b, int hash_size)
     ++{
     ++	if (a != NULL && b != NULL) {
     ++		return !memcmp(a, b, hash_size);
     ++	}
      +
     -+  return a == b;
     ++	return a == b;
      +}
      +
     -+static bool str_equal(char *a, char *b) {
     -+  if (a != NULL && b != NULL) {
     -+    return 0 == strcmp(a, b);
     -+  }
     ++static bool str_equal(char *a, char *b)
     ++{
     ++	if (a != NULL && b != NULL) {
     ++		return 0 == strcmp(a, b);
     ++	}
      +
     -+  return a == b;
     ++	return a == b;
      +}
      +
     -+bool ref_record_equal(struct ref_record *a, struct ref_record *b,
     -+                      int hash_size) {
     -+  assert(hash_size > 0);
     -+  return 0 == strcmp(a->ref_name, b->ref_name) &&
     -+         a->update_index == b->update_index &&
     -+         hash_equal(a->value, b->value, hash_size) &&
     -+         hash_equal(a->target_value, b->target_value, hash_size) &&
     -+         str_equal(a->target, b->target);
     ++bool ref_record_equal(struct ref_record *a, struct ref_record *b, int hash_size)
     ++{
     ++	assert(hash_size > 0);
     ++	return 0 == strcmp(a->ref_name, b->ref_name) &&
     ++	       a->update_index == b->update_index &&
     ++	       hash_equal(a->value, b->value, hash_size) &&
     ++	       hash_equal(a->target_value, b->target_value, hash_size) &&
     ++	       str_equal(a->target, b->target);
      +}
      +
     -+int ref_record_compare_name(const void *a, const void *b) {
     -+  return strcmp(((struct ref_record *)a)->ref_name,
     -+                ((struct ref_record *)b)->ref_name);
     ++int ref_record_compare_name(const void *a, const void *b)
     ++{
     ++	return strcmp(((struct ref_record *)a)->ref_name,
     ++		      ((struct ref_record *)b)->ref_name);
      +}
      +
     -+bool ref_record_is_deletion(const struct ref_record *ref) {
     -+  return ref->value == NULL && ref->target == NULL && ref->target_value == NULL;
     ++bool ref_record_is_deletion(const struct ref_record *ref)
     ++{
     ++	return ref->value == NULL && ref->target == NULL &&
     ++	       ref->target_value == NULL;
      +}
      +
     -+int log_record_compare_key(const void *a, const void *b) {
     -+  struct log_record *la = (struct log_record *)a;
     -+  struct log_record *lb = (struct log_record *)b;
     ++int log_record_compare_key(const void *a, const void *b)
     ++{
     ++	struct log_record *la = (struct log_record *)a;
     ++	struct log_record *lb = (struct log_record *)b;
      +
     -+  int cmp  = strcmp(la->ref_name, lb->ref_name);
     -+  if (cmp) {
     -+    return cmp;
     -+  }
     -+  if (la->update_index > lb->update_index) {
     -+    return -1;
     -+  }
     -+  return (la->update_index < lb->update_index) ? 1 : 0;
     ++	int cmp = strcmp(la->ref_name, lb->ref_name);
     ++	if (cmp) {
     ++		return cmp;
     ++	}
     ++	if (la->update_index > lb->update_index) {
     ++		return -1;
     ++	}
     ++	return (la->update_index < lb->update_index) ? 1 : 0;
      +}
      +
     -+bool log_record_is_deletion(const struct log_record *log) {
     -+  // XXX
     -+  return false;
     ++bool log_record_is_deletion(const struct log_record *log)
     ++{
     ++	/* XXX */
     ++	return false;
      +}
      
       diff --git a/reftable/record.h b/reftable/record.h
     @@ -4236,20 +4241,20 @@
      +#include "slice.h"
      +
      +struct record_vtable {
     -+  void (*key)(const void *rec, struct slice *dest);
     -+  byte (*type)(void);
     -+  void (*copy_from)(void *rec, const void *src, int hash_size);
     -+  byte (*val_type)(const void *rec);
     -+  int (*encode)(const void *rec, struct slice dest, int hash_size);
     -+  int (*decode)(void *rec, struct slice key, byte extra, struct slice src,
     -+                int hash_size);
     -+  void (*clear)(void *rec);
     ++	void (*key)(const void *rec, struct slice *dest);
     ++	byte (*type)(void);
     ++	void (*copy_from)(void *rec, const void *src, int hash_size);
     ++	byte (*val_type)(const void *rec);
     ++	int (*encode)(const void *rec, struct slice dest, int hash_size);
     ++	int (*decode)(void *rec, struct slice key, byte extra, struct slice src,
     ++		      int hash_size);
     ++	void (*clear)(void *rec);
      +};
      +
      +/* record is a generic wrapper for differnt types of records. */
      +struct record {
     -+  void *data;
     -+  struct record_vtable *ops;
     ++	void *data;
     ++	struct record_vtable *ops;
      +};
      +
      +int get_var_int(uint64_t *dest, struct slice in);
     @@ -4262,20 +4267,20 @@
      +extern struct record_vtable ref_record_vtable;
      +
      +int encode_key(bool *restart, struct slice dest, struct slice prev_key,
     -+               struct slice key, byte extra);
     ++	       struct slice key, byte extra);
      +int decode_key(struct slice *key, byte *extra, struct slice last_key,
     -+               struct slice in);
     ++	       struct slice in);
      +
      +struct index_record {
     -+  struct slice last_key;
     -+  uint64_t offset;
     ++	struct slice last_key;
     ++	uint64_t offset;
      +};
      +
      +struct obj_record {
     -+  byte *hash_prefix;
     -+  int hash_prefix_len;
     -+  uint64_t *offsets;
     -+  int offset_len;
     ++	byte *hash_prefix;
     ++	int hash_prefix_len;
     ++	uint64_t *offsets;
     ++	int offset_len;
      +};
      +
      +void record_key(struct record rec, struct slice *dest);
     @@ -4284,7 +4289,7 @@
      +byte record_val_type(struct record rec);
      +int record_encode(struct record rec, struct slice dest, int hash_size);
      +int record_decode(struct record rec, struct slice key, byte extra,
     -+                  struct slice src, int hash_size);
     ++		  struct slice src, int hash_size);
      +void record_clear(struct record rec);
      +void *record_yield(struct record *rec);
      +void record_from_obj(struct record *rec, struct obj_record *objrec);
     @@ -4293,10 +4298,10 @@
      +void record_from_log(struct record *rec, struct log_record *logrec);
      +struct ref_record *record_as_ref(struct record ref);
      +
     -+// for qsort.
     ++/* for qsort. */
      +int ref_record_compare_name(const void *a, const void *b);
      +
     -+// for qsort.
     ++/* for qsort. */
      +int log_record_compare_key(const void *a, const void *b);
      +
      +#endif
     @@ -4316,308 +4321,327 @@
      +
      +#include "record.h"
      +
     -+#include <string.h>
     ++#include "system.h"
      +
      +#include "basics.h"
      +#include "constants.h"
      +#include "reftable.h"
      +#include "test_framework.h"
      +
     -+void varint_roundtrip() {
     -+  uint64_t inputs[] = {0,
     -+                       1,
     -+                       27,
     -+                       127,
     -+                       128,
     -+                       257,
     -+                       4096,
     -+                       ((uint64_t)1 << 63),
     -+                       ((uint64_t)1 << 63) + ((uint64_t)1 << 63) - 1};
     -+  for (int i = 0; i < ARRAYSIZE(inputs); i++) {
     -+    byte dest[10];
     -+
     -+    struct slice out = {.buf = dest, .len = 10, .cap = 10};
     -+
     -+    uint64_t in = inputs[i];
     -+    int n = put_var_int(out, in);
     -+    assert(n > 0);
     -+    out.len = n;
     -+
     -+    uint64_t got = 0;
     -+    n = get_var_int(&got, out);
     -+    assert(n > 0);
     -+
     -+    assert(got == in);
     -+  }
     -+}
     -+
     -+void test_common_prefix() {
     -+  struct {
     -+    const char *a, *b;
     -+    int want;
     -+  } cases[] = {
     -+      {"abc", "ab", 2},
     -+      {"", "abc", 0},
     -+      {"abc", "abd", 2},
     -+      {"abc", "pqr", 0},
     -+  };
     -+
     -+  for (int i = 0; i < ARRAYSIZE(cases); i++) {
     -+    struct slice a = {};
     -+    struct slice b = {};
     -+    slice_set_string(&a, cases[i].a);
     -+    slice_set_string(&b, cases[i].b);
     -+
     -+    int got = common_prefix_size(a, b);
     -+    assert(got == cases[i].want);
     -+
     -+    free(slice_yield(&a));
     -+    free(slice_yield(&b));
     -+  }
     -+}
     -+
     -+void set_hash(byte *h, int j) {
     -+  for (int i = 0; i < SHA1_SIZE; i++) {
     -+    h[i] = (j >> i) & 0xff;
     -+  }
     -+}
     -+
     -+void test_ref_record_roundtrip() {
     -+  for (int i = 0; i <= 3; i++) {
     -+    printf("subtest %d\n", i);
     -+    struct ref_record in = {};
     -+    switch (i) {
     -+      case 0:
     -+        break;
     -+      case 1:
     -+        in.value = malloc(SHA1_SIZE);
     -+        set_hash(in.value, 1);
     -+        break;
     -+      case 2:
     -+        in.value = malloc(SHA1_SIZE);
     -+        set_hash(in.value, 1);
     -+        in.target_value = malloc(SHA1_SIZE);
     -+        set_hash(in.target_value, 2);
     -+        break;
     -+      case 3:
     -+        in.target = strdup("target");
     -+        break;
     -+    }
     -+    in.ref_name = strdup("refs/heads/master");
     -+
     -+    struct record rec = {};
     -+    record_from_ref(&rec, &in);
     -+    assert(record_val_type(rec) == i);
     -+    byte buf[1024];
     -+    struct slice key = {};
     -+    record_key(rec, &key);
     -+    struct slice dest = {
     -+        .buf = buf,
     -+        .len = sizeof(buf),
     -+    };
     -+    int n = record_encode(rec, dest, SHA1_SIZE);
     -+    assert(n > 0);
     -+
     -+    struct ref_record out = {};
     -+    struct record rec_out = {};
     -+    record_from_ref(&rec_out, &out);
     -+    int m = record_decode(rec_out, key, i, dest, SHA1_SIZE);
     -+    assert(n == m);
     -+
     -+    assert((out.value != NULL) == (in.value != NULL));
     -+    assert((out.target_value != NULL) == (in.target_value != NULL));
     -+    assert((out.target != NULL) == (in.target != NULL));
     -+    free(slice_yield(&key));
     -+    record_clear(rec_out);
     -+    ref_record_clear(&in);
     -+  }
     -+}
     -+
     -+void test_log_record_roundtrip() {
     -+  struct log_record in = {
     -+      .ref_name = strdup("refs/heads/master"),
     -+      .old_hash = malloc(SHA1_SIZE),
     -+      .new_hash = malloc(SHA1_SIZE),
     -+      .name = strdup("han-wen"),
     -+      .email = strdup("hanwen@google.com"),
     -+      .message = strdup("test"),
     -+      .update_index = 42,
     -+      .time = 1577123507,
     -+      .tz_offset = 100,
     -+  };
     -+
     -+  struct record rec = {};
     -+  record_from_log(&rec, &in);
     -+
     -+  struct slice key = {};
     -+  record_key(rec, &key);
     -+
     -+  byte buf[1024];
     -+  struct slice dest = {
     -+      .buf = buf,
     -+      .len = sizeof(buf),
     -+  };
     -+
     -+  int n = record_encode(rec, dest, SHA1_SIZE);
     -+  assert(n > 0);
     -+
     -+  struct log_record out = {};
     -+  struct record rec_out = {};
     -+  record_from_log(&rec_out, &out);
     -+  int valtype = record_val_type(rec);
     -+  int m = record_decode(rec_out, key, valtype, dest, SHA1_SIZE);
     -+  assert(n == m);
     -+
     -+  assert(log_record_equal(&in, &out, SHA1_SIZE));
     -+  log_record_clear(&in);
     -+  free(slice_yield(&key));
     -+  record_clear(rec_out);
     -+}
     -+
     -+void test_u24_roundtrip() {
     -+  uint32_t in = 0x112233;
     -+  byte dest[3];
     -+
     -+  put_u24(dest, in);
     -+  uint32_t out = get_u24(dest);
     -+  assert(in == out);
     -+}
     -+
     -+void test_key_roundtrip() {
     -+  struct slice dest = {}, last_key = {}, key = {}, roundtrip = {};
     -+
     -+  slice_resize(&dest, 1024);
     -+  slice_set_string(&last_key, "refs/heads/master");
     -+  slice_set_string(&key, "refs/tags/bla");
     -+
     -+  bool restart;
     -+  byte extra = 6;
     -+  int n = encode_key(&restart, dest, last_key, key, extra);
     -+  assert(!restart);
     -+  assert(n > 0);
     -+
     -+  byte rt_extra;
     -+  int m = decode_key(&roundtrip, &rt_extra, last_key, dest);
     -+  assert(n == m);
     -+  assert(slice_equal(key, roundtrip));
     -+  assert(rt_extra == extra);
     -+
     -+  free(slice_yield(&last_key));
     -+  free(slice_yield(&key));
     -+  free(slice_yield(&dest));
     -+  free(slice_yield(&roundtrip));
     -+}
     -+
     -+void print_bytes(byte *p, int l) {
     -+  for (int i = 0; i < l; i++) {
     -+    byte c = *p;
     -+    if (c < 32) {
     -+      c = '.';
     -+    }
     -+    printf("%02x[%c] ", p[i], c);
     -+  }
     -+  printf("(%d)\n", l);
     -+}
     -+
     -+void test_obj_record_roundtrip() {
     -+  byte testHash1[SHA1_SIZE] = {};
     -+  set_hash(testHash1, 1);
     -+  uint64_t till9[] = {1, 2, 3, 4, 500, 600, 700, 800, 9000};
     -+
     -+  struct obj_record recs[3] = {{
     -+                                   .hash_prefix = testHash1,
     -+                                   .hash_prefix_len = 5,
     -+                                   .offsets = till9,
     -+                                   .offset_len = 3,
     -+                               },
     -+                               {
     -+                                   .hash_prefix = testHash1,
     -+                                   .hash_prefix_len = 5,
     -+                                   .offsets = till9,
     -+                                   .offset_len = 9,
     -+                               },
     -+                               {
     -+                                   .hash_prefix = testHash1,
     -+                                   .hash_prefix_len = 5,
     -+                               }
     -+
     -+  };
     -+  for (int i = 0; i < ARRAYSIZE(recs); i++) {
     -+    printf("subtest %d\n", i);
     -+    struct obj_record in = recs[i];
     -+    byte buf[1024];
     -+    struct record rec = {};
     -+    record_from_obj(&rec, &in);
     -+    struct slice key = {};
     -+    record_key(rec, &key);
     -+    struct slice dest = {
     -+        .buf = buf,
     -+        .len = sizeof(buf),
     -+    };
     -+    int n = record_encode(rec, dest, SHA1_SIZE);
     -+    assert(n > 0);
     -+    byte extra = record_val_type(rec);
     -+    struct obj_record out = {};
     -+    struct record rec_out = {};
     -+    record_from_obj(&rec_out, &out);
     -+    int m = record_decode(rec_out, key, extra, dest, SHA1_SIZE);
     -+    assert(n == m);
     -+
     -+    assert(in.hash_prefix_len == out.hash_prefix_len);
     -+    assert(in.offset_len == out.offset_len);
     -+
     -+    assert(0 == memcmp(in.hash_prefix, out.hash_prefix, in.hash_prefix_len));
     -+    assert(0 ==
     -+           memcmp(in.offsets, out.offsets, sizeof(uint64_t) * in.offset_len));
     -+    free(slice_yield(&key));
     -+    record_clear(rec_out);
     -+  }
     -+}
     -+
     -+void test_index_record_roundtrip() {
     -+  struct index_record in = {.offset = 42};
     -+
     -+  slice_set_string(&in.last_key, "refs/heads/master");
     -+
     -+  struct slice key = {};
     -+  struct record rec = {};
     -+  record_from_index(&rec, &in);
     -+  record_key(rec, &key);
     -+
     -+  assert(0 == slice_compare(key, in.last_key));
     -+
     -+  byte buf[1024];
     -+  struct slice dest = {
     -+      .buf = buf,
     -+      .len = sizeof(buf),
     -+  };
     -+  int n = record_encode(rec, dest, SHA1_SIZE);
     -+  assert(n > 0);
     -+
     -+  byte extra = record_val_type(rec);
     -+  struct index_record out = {};
     -+  struct record out_rec;
     -+  record_from_index(&out_rec, &out);
     -+  int m = record_decode(out_rec, key, extra, dest, SHA1_SIZE);
     -+  assert(m == n);
     -+
     -+  assert(in.offset == out.offset);
     -+
     -+  record_clear(out_rec);
     -+  free(slice_yield(&key));
     -+  free(slice_yield(&in.last_key));
     -+}
     -+
     -+int main() {
     -+  add_test_case("test_log_record_roundtrip", &test_log_record_roundtrip);
     -+  add_test_case("test_ref_record_roundtrip", &test_ref_record_roundtrip);
     -+  add_test_case("varint_roundtrip", &varint_roundtrip);
     -+  add_test_case("test_key_roundtrip", &test_key_roundtrip);
     -+  add_test_case("test_common_prefix", &test_common_prefix);
     -+  add_test_case("test_obj_record_roundtrip", &test_obj_record_roundtrip);
     -+  add_test_case("test_index_record_roundtrip", &test_index_record_roundtrip);
     -+  add_test_case("test_u24_roundtrip", &test_u24_roundtrip);
     -+  test_main();
     ++void varint_roundtrip()
     ++{
     ++	uint64_t inputs[] = { 0,
     ++			      1,
     ++			      27,
     ++			      127,
     ++			      128,
     ++			      257,
     ++			      4096,
     ++			      ((uint64_t)1 << 63),
     ++			      ((uint64_t)1 << 63) + ((uint64_t)1 << 63) - 1 };
     ++	int i = 0;
     ++	for (i = 0; i < ARRAYSIZE(inputs); i++) {
     ++		byte dest[10];
     ++
     ++		struct slice out = { .buf = dest, .len = 10, .cap = 10 };
     ++
     ++		uint64_t in = inputs[i];
     ++		int n = put_var_int(out, in);
     ++		assert(n > 0);
     ++		out.len = n;
     ++
     ++		uint64_t got = 0;
     ++		n = get_var_int(&got, out);
     ++		assert(n > 0);
     ++
     ++		assert(got == in);
     ++	}
     ++}
     ++
     ++void test_common_prefix()
     ++{
     ++	struct {
     ++		const char *a, *b;
     ++		int want;
     ++	} cases[] = {
     ++		{ "abc", "ab", 2 },
     ++		{ "", "abc", 0 },
     ++		{ "abc", "abd", 2 },
     ++		{ "abc", "pqr", 0 },
     ++	};
     ++
     ++	int i = 0;
     ++	for (i = 0; i < ARRAYSIZE(cases); i++) {
     ++		struct slice a = {};
     ++		struct slice b = {};
     ++		slice_set_string(&a, cases[i].a);
     ++		slice_set_string(&b, cases[i].b);
     ++
     ++		int got = common_prefix_size(a, b);
     ++		assert(got == cases[i].want);
     ++
     ++		free(slice_yield(&a));
     ++		free(slice_yield(&b));
     ++	}
     ++}
     ++
     ++void set_hash(byte *h, int j)
     ++{
     ++	int i = 0;
     ++	for (i = 0; i < SHA1_SIZE; i++) {
     ++		h[i] = (j >> i) & 0xff;
     ++	}
     ++}
     ++
     ++void test_ref_record_roundtrip()
     ++{
     ++	int i = 0;
     ++	for (i = 0; i <= 3; i++) {
     ++		printf("subtest %d\n", i);
     ++		struct ref_record in = {};
     ++		switch (i) {
     ++		case 0:
     ++			break;
     ++		case 1:
     ++			in.value = malloc(SHA1_SIZE);
     ++			set_hash(in.value, 1);
     ++			break;
     ++		case 2:
     ++			in.value = malloc(SHA1_SIZE);
     ++			set_hash(in.value, 1);
     ++			in.target_value = malloc(SHA1_SIZE);
     ++			set_hash(in.target_value, 2);
     ++			break;
     ++		case 3:
     ++			in.target = strdup("target");
     ++			break;
     ++		}
     ++		in.ref_name = strdup("refs/heads/master");
     ++
     ++		struct record rec = {};
     ++		record_from_ref(&rec, &in);
     ++		assert(record_val_type(rec) == i);
     ++		byte buf[1024];
     ++		struct slice key = {};
     ++		record_key(rec, &key);
     ++		struct slice dest = {
     ++			.buf = buf,
     ++			.len = sizeof(buf),
     ++		};
     ++		int n = record_encode(rec, dest, SHA1_SIZE);
     ++		assert(n > 0);
     ++
     ++		struct ref_record out = {};
     ++		struct record rec_out = {};
     ++		record_from_ref(&rec_out, &out);
     ++		int m = record_decode(rec_out, key, i, dest, SHA1_SIZE);
     ++		assert(n == m);
     ++
     ++		assert((out.value != NULL) == (in.value != NULL));
     ++		assert((out.target_value != NULL) == (in.target_value != NULL));
     ++		assert((out.target != NULL) == (in.target != NULL));
     ++		free(slice_yield(&key));
     ++		record_clear(rec_out);
     ++		ref_record_clear(&in);
     ++	}
     ++}
     ++
     ++void test_log_record_roundtrip()
     ++{
     ++	struct log_record in = {
     ++		.ref_name = strdup("refs/heads/master"),
     ++		.old_hash = malloc(SHA1_SIZE),
     ++		.new_hash = malloc(SHA1_SIZE),
     ++		.name = strdup("han-wen"),
     ++		.email = strdup("hanwen@google.com"),
     ++		.message = strdup("test"),
     ++		.update_index = 42,
     ++		.time = 1577123507,
     ++		.tz_offset = 100,
     ++	};
     ++
     ++	struct record rec = {};
     ++	record_from_log(&rec, &in);
     ++
     ++	struct slice key = {};
     ++	record_key(rec, &key);
     ++
     ++	byte buf[1024];
     ++	struct slice dest = {
     ++		.buf = buf,
     ++		.len = sizeof(buf),
     ++	};
     ++
     ++	int n = record_encode(rec, dest, SHA1_SIZE);
     ++	assert(n > 0);
     ++
     ++	struct log_record out = {};
     ++	struct record rec_out = {};
     ++	record_from_log(&rec_out, &out);
     ++	int valtype = record_val_type(rec);
     ++	int m = record_decode(rec_out, key, valtype, dest, SHA1_SIZE);
     ++	assert(n == m);
     ++
     ++	assert(log_record_equal(&in, &out, SHA1_SIZE));
     ++	log_record_clear(&in);
     ++	free(slice_yield(&key));
     ++	record_clear(rec_out);
     ++}
     ++
     ++void test_u24_roundtrip()
     ++{
     ++	uint32_t in = 0x112233;
     ++	byte dest[3];
     ++
     ++	put_u24(dest, in);
     ++	uint32_t out = get_u24(dest);
     ++	assert(in == out);
     ++}
     ++
     ++void test_key_roundtrip()
     ++{
     ++	struct slice dest = {}, last_key = {}, key = {}, roundtrip = {};
     ++
     ++	slice_resize(&dest, 1024);
     ++	slice_set_string(&last_key, "refs/heads/master");
     ++	slice_set_string(&key, "refs/tags/bla");
     ++
     ++	bool restart;
     ++	byte extra = 6;
     ++	int n = encode_key(&restart, dest, last_key, key, extra);
     ++	assert(!restart);
     ++	assert(n > 0);
     ++
     ++	byte rt_extra;
     ++	int m = decode_key(&roundtrip, &rt_extra, last_key, dest);
     ++	assert(n == m);
     ++	assert(slice_equal(key, roundtrip));
     ++	assert(rt_extra == extra);
     ++
     ++	free(slice_yield(&last_key));
     ++	free(slice_yield(&key));
     ++	free(slice_yield(&dest));
     ++	free(slice_yield(&roundtrip));
     ++}
     ++
     ++void print_bytes(byte *p, int l)
     ++{
     ++	int i = 0;
     ++	for (i = 0; i < l; i++) {
     ++		byte c = *p;
     ++		if (c < 32) {
     ++			c = '.';
     ++		}
     ++		printf("%02x[%c] ", p[i], c);
     ++	}
     ++	printf("(%d)\n", l);
     ++}
     ++
     ++void test_obj_record_roundtrip()
     ++{
     ++	byte testHash1[SHA1_SIZE] = {};
     ++	set_hash(testHash1, 1);
     ++	uint64_t till9[] = { 1, 2, 3, 4, 500, 600, 700, 800, 9000 };
     ++
     ++	struct obj_record recs[3] = { {
     ++					      .hash_prefix = testHash1,
     ++					      .hash_prefix_len = 5,
     ++					      .offsets = till9,
     ++					      .offset_len = 3,
     ++				      },
     ++				      {
     ++					      .hash_prefix = testHash1,
     ++					      .hash_prefix_len = 5,
     ++					      .offsets = till9,
     ++					      .offset_len = 9,
     ++				      },
     ++				      {
     ++					      .hash_prefix = testHash1,
     ++					      .hash_prefix_len = 5,
     ++				      }
     ++
     ++	};
     ++	int i = 0;
     ++	for (i = 0; i < ARRAYSIZE(recs); i++) {
     ++		printf("subtest %d\n", i);
     ++		struct obj_record in = recs[i];
     ++		byte buf[1024];
     ++		struct record rec = {};
     ++		record_from_obj(&rec, &in);
     ++		struct slice key = {};
     ++		record_key(rec, &key);
     ++		struct slice dest = {
     ++			.buf = buf,
     ++			.len = sizeof(buf),
     ++		};
     ++		int n = record_encode(rec, dest, SHA1_SIZE);
     ++		assert(n > 0);
     ++		byte extra = record_val_type(rec);
     ++		struct obj_record out = {};
     ++		struct record rec_out = {};
     ++		record_from_obj(&rec_out, &out);
     ++		int m = record_decode(rec_out, key, extra, dest, SHA1_SIZE);
     ++		assert(n == m);
     ++
     ++		assert(in.hash_prefix_len == out.hash_prefix_len);
     ++		assert(in.offset_len == out.offset_len);
     ++
     ++		assert(!memcmp(in.hash_prefix, out.hash_prefix,
     ++			       in.hash_prefix_len));
     ++		assert(0 == memcmp(in.offsets, out.offsets,
     ++				   sizeof(uint64_t) * in.offset_len));
     ++		free(slice_yield(&key));
     ++		record_clear(rec_out);
     ++	}
     ++}
     ++
     ++void test_index_record_roundtrip()
     ++{
     ++	struct index_record in = { .offset = 42 };
     ++
     ++	slice_set_string(&in.last_key, "refs/heads/master");
     ++
     ++	struct slice key = {};
     ++	struct record rec = {};
     ++	record_from_index(&rec, &in);
     ++	record_key(rec, &key);
     ++
     ++	assert(0 == slice_compare(key, in.last_key));
     ++
     ++	byte buf[1024];
     ++	struct slice dest = {
     ++		.buf = buf,
     ++		.len = sizeof(buf),
     ++	};
     ++	int n = record_encode(rec, dest, SHA1_SIZE);
     ++	assert(n > 0);
     ++
     ++	byte extra = record_val_type(rec);
     ++	struct index_record out = {};
     ++	struct record out_rec;
     ++	record_from_index(&out_rec, &out);
     ++	int m = record_decode(out_rec, key, extra, dest, SHA1_SIZE);
     ++	assert(m == n);
     ++
     ++	assert(in.offset == out.offset);
     ++
     ++	record_clear(out_rec);
     ++	free(slice_yield(&key));
     ++	free(slice_yield(&in.last_key));
     ++}
     ++
     ++int main()
     ++{
     ++	add_test_case("test_log_record_roundtrip", &test_log_record_roundtrip);
     ++	add_test_case("test_ref_record_roundtrip", &test_ref_record_roundtrip);
     ++	add_test_case("varint_roundtrip", &varint_roundtrip);
     ++	add_test_case("test_key_roundtrip", &test_key_roundtrip);
     ++	add_test_case("test_common_prefix", &test_common_prefix);
     ++	add_test_case("test_obj_record_roundtrip", &test_obj_record_roundtrip);
     ++	add_test_case("test_index_record_roundtrip",
     ++		      &test_index_record_roundtrip);
     ++	add_test_case("test_u24_roundtrip", &test_u24_roundtrip);
     ++	test_main();
      +}
      
       diff --git a/reftable/reftable.h b/reftable/reftable.h
     @@ -4636,7 +4660,7 @@
      +#ifndef REFTABLE_H
      +#define REFTABLE_H
      +
     -+#include <stdint.h>
     ++#include "system.h"
      +
      +typedef uint8_t byte;
      +typedef byte bool;
     @@ -4645,33 +4669,33 @@
      +   It is generally passed around by value.
      + */
      +struct block_source {
     -+  struct block_source_vtable *ops;
     -+  void *arg;
     ++	struct block_source_vtable *ops;
     ++	void *arg;
      +};
      +
      +/* a contiguous segment of bytes. It keeps track of its generating block_source
      +   so it can return itself into the pool.
      +*/
      +struct block {
     -+  byte *data;
     -+  int len;
     -+  struct block_source source;
     ++	byte *data;
     ++	int len;
     ++	struct block_source source;
      +};
      +
      +/* block_source_vtable are the operations that make up block_source */
      +struct block_source_vtable {
     -+  /* returns the size of a block source */
     -+  uint64_t (*size)(void *source);
     -+
     -+  /* reads a segment from the block source. It is an error to read
     -+     beyond the end of the block */
     -+  int (*read_block)(void *source, struct block *dest, uint64_t off,
     -+                    uint32_t size);
     -+  /* mark the block as read; may return the data back to malloc */
     -+  void (*return_block)(void *source, struct block *blockp);
     -+
     -+  /* release all resources associated with the block source */
     -+  void (*close)(void *source);
     ++	/* returns the size of a block source */
     ++	uint64_t (*size)(void *source);
     ++
     ++	/* reads a segment from the block source. It is an error to read
     ++	   beyond the end of the block */
     ++	int (*read_block)(void *source, struct block *dest, uint64_t off,
     ++			  uint32_t size);
     ++	/* mark the block as read; may return the data back to malloc */
     ++	void (*return_block)(void *source, struct block *blockp);
     ++
     ++	/* release all resources associated with the block source */
     ++	void (*close)(void *source);
      +};
      +
      +/* opens a file on the file system as a block_source */
     @@ -4679,26 +4703,27 @@
      +
      +/* write_options sets options for writing a single reftable. */
      +struct write_options {
     -+  /* do not pad out blocks to block size. */
     -+  bool unpadded;
     ++	/* do not pad out blocks to block size. */
     ++	bool unpadded;
      +
     -+  /* the blocksize. Should be less than 2^24. */
     -+  uint32_t block_size;
     ++	/* the blocksize. Should be less than 2^24. */
     ++	uint32_t block_size;
      +
     -+  /* do not generate a SHA1 => ref index. */
     -+  bool skip_index_objects;
     ++	/* do not generate a SHA1 => ref index. */
     ++	bool skip_index_objects;
      +
     -+  /* how often to write complete keys in each block. */
     -+  int restart_interval;
     ++	/* how often to write complete keys in each block. */
     ++	int restart_interval;
      +};
      +
      +/* ref_record holds a ref database entry target_value */
      +struct ref_record {
     -+  char *ref_name;        /* Name of the ref, malloced. */
     -+  uint64_t update_index; /* Logical timestamp at which this value is written */
     -+  byte *value;           /* SHA1, or NULL. malloced. */
     -+  byte *target_value;    /* peeled annotated tag, or NULL. malloced. */
     -+  char *target;          /* symref, or NULL. malloced. */
     ++	char *ref_name; /* Name of the ref, malloced. */
     ++	uint64_t update_index; /* Logical timestamp at which this value is
     ++				  written */
     ++	byte *value; /* SHA1, or NULL. malloced. */
     ++	byte *target_value; /* peeled annotated tag, or NULL. malloced. */
     ++	char *target; /* symref, or NULL. malloced. */
      +};
      +
      +/* returns whether 'ref' represents a deletion */
     @@ -4712,19 +4737,19 @@
      +
      +/* returns whether two ref_records are the same */
      +bool ref_record_equal(struct ref_record *a, struct ref_record *b,
     -+                      int hash_size);
     ++		      int hash_size);
      +
      +/* log_record holds a reflog entry */
      +struct log_record {
     -+  char *ref_name;
     -+  uint64_t update_index;
     -+  byte *new_hash;
     -+  byte *old_hash;
     -+  char *name;
     -+  char *email;
     -+  uint64_t time;
     -+  int16_t tz_offset;
     -+  char *message;
     ++	char *ref_name;
     ++	uint64_t update_index;
     ++	byte *new_hash;
     ++	byte *old_hash;
     ++	char *name;
     ++	char *email;
     ++	uint64_t time;
     ++	int16_t tz_offset;
     ++	char *message;
      +};
      +
      +/* returns whether 'ref' represents the deletion of a log record. */
     @@ -4735,7 +4760,7 @@
      +
      +/* returns whether two records are equal. */
      +bool log_record_equal(struct log_record *a, struct log_record *b,
     -+                      int hash_size);
     ++		      int hash_size);
      +
      +void log_record_print(struct log_record *log, int hash_size);
      +
     @@ -4743,8 +4768,8 @@
      +   reftable. It is generally passed around by value.
      +*/
      +struct iterator {
     -+  struct iterator_vtable *ops;
     -+  void *iter_arg;
     ++	struct iterator_vtable *ops;
     ++	void *iter_arg;
      +};
      +
      +/* reads the next ref_record. Returns < 0 for error, 0 for OK and > 0:
     @@ -4762,38 +4787,39 @@
      +
      +/* block_stats holds statistics for a single block type */
      +struct block_stats {
     -+  /* total number of entries written */
     -+  int entries;
     -+  /* total number of key restarts */
     -+  int restarts;
     -+  /* total number of blocks */
     -+  int blocks;
     -+  /* total number of index blocks */
     -+  int index_blocks;
     -+  /* depth of the index */
     -+  int max_index_level;
     -+
     -+  /* offset of the first block for this type */
     -+  uint64_t offset;
     -+  /* offset of the top level index block for this type, or 0 if not present */
     -+  uint64_t index_offset;
     ++	/* total number of entries written */
     ++	int entries;
     ++	/* total number of key restarts */
     ++	int restarts;
     ++	/* total number of blocks */
     ++	int blocks;
     ++	/* total number of index blocks */
     ++	int index_blocks;
     ++	/* depth of the index */
     ++	int max_index_level;
     ++
     ++	/* offset of the first block for this type */
     ++	uint64_t offset;
     ++	/* offset of the top level index block for this type, or 0 if not
     ++	 * present */
     ++	uint64_t index_offset;
      +};
      +
      +/* stats holds overall statistics for a single reftable */
      +struct stats {
     -+  /* total number of blocks written. */
     -+  int blocks;
     -+  /* stats for ref data */
     -+  struct block_stats ref_stats;
     -+  /* stats for the SHA1 to ref map. */
     -+  struct block_stats obj_stats;
     -+  /* stats for index blocks */
     -+  struct block_stats idx_stats;
     -+  /* stats for log blocks */
     -+  struct block_stats log_stats;
     -+
     -+  /* disambiguation length of shortened object IDs. */
     -+  int object_id_len;
     ++	/* total number of blocks written. */
     ++	int blocks;
     ++	/* stats for ref data */
     ++	struct block_stats ref_stats;
     ++	/* stats for the SHA1 to ref map. */
     ++	struct block_stats obj_stats;
     ++	/* stats for index blocks */
     ++	struct block_stats idx_stats;
     ++	/* stats for log blocks */
     ++	struct block_stats log_stats;
     ++
     ++	/* disambiguation length of shortened object IDs. */
     ++	int object_id_len;
      +};
      +
      +/* different types of errors */
     @@ -4814,6 +4840,7 @@
      +#define LOCK_ERROR -5
      +
      +/* Misuse of the API:
     ++   - on writing a record with NULL ref_name.
      +   - on writing a ref_record outside the table limits
      +   - on writing a ref or log record before the stack's next_update_index
      +   - on reading a ref_record from log iterator, or vice versa.
     @@ -4827,7 +4854,7 @@
      +
      +/* new_writer creates a new writer */
      +struct writer *new_writer(int (*writer_func)(void *, byte *, int),
     -+                          void *writer_arg, struct write_options *opts);
     ++			  void *writer_arg, struct write_options *opts);
      +
      +/* write to a file descriptor. fdp should be an int* pointing to the fd. */
      +int fd_writer(void *fdp, byte *data, int size);
     @@ -4857,7 +4884,6 @@
      +   key before adding. */
      +int writer_add_logs(struct writer *w, struct log_record *logs, int n);
      +
     -+
      +/* writer_close finalizes the reftable. The writer is retained so statistics can
      + * be inspected. */
      +int writer_close(struct writer *w);
     @@ -4905,7 +4931,7 @@
      +
      +/* seek to logs for the given name, older than update_index. */
      +int reader_seek_log_at(struct reader *r, struct iterator *it, const char *name,
     -+                       uint64_t update_index);
     ++		       uint64_t update_index);
      +
      +/* seek to newest log entry for given name. */
      +int reader_seek_log(struct reader *r, struct iterator *it, const char *name);
     @@ -4915,7 +4941,7 @@
      +
      +/* return an iterator for the refs pointing to oid */
      +int reader_refs_for(struct reader *r, struct iterator *it, byte *oid,
     -+                    int oid_len);
     ++		    int oid_len);
      +
      +/* return the max_update_index for a table */
      +uint64_t reader_max_update_index(struct reader *r);
     @@ -4933,15 +4959,15 @@
      +
      +/* returns an iterator positioned just before 'name' */
      +int merged_table_seek_ref(struct merged_table *mt, struct iterator *it,
     -+                          const char *name);
     ++			  const char *name);
      +
      +/* returns an iterator for log entry, at given update_index */
      +int merged_table_seek_log_at(struct merged_table *mt, struct iterator *it,
     -+                             const char *name, uint64_t update_index);
     ++			     const char *name, uint64_t update_index);
      +
      +/* like merged_table_seek_log_at but look for the newest entry. */
      +int merged_table_seek_log(struct merged_table *mt, struct iterator *it,
     -+                          const char *name);
     ++			  const char *name);
      +
      +/* returns the max update_index covered by this merged table. */
      +uint64_t merged_max_update_index(struct merged_table *mt);
     @@ -4964,7 +4990,7 @@
      +   .git/refs respectively.
      +*/
      +int new_stack(struct stack **dest, const char *dir, const char *list_file,
     -+              struct write_options config);
     ++	      struct write_options config);
      +
      +/* returns the update_index at which a next table should be written. */
      +uint64_t stack_next_update_index(struct stack *st);
     @@ -4972,8 +4998,8 @@
      +/* add a new table to the stack. The write_table function must call
      +   writer_set_limits, add refs and return an error value. */
      +int stack_add(struct stack *st,
     -+              int (*write_table)(struct writer *wr, void *write_arg),
     -+              void *write_arg);
     ++	      int (*write_table)(struct writer *wr, void *write_arg),
     ++	      void *write_arg);
      +
      +/* returns the merged_table for seeking. This table is valid until the
      +   next write or reload, and should not be closed or deleted.
     @@ -4988,14 +5014,15 @@
      +
      +/* Policy for expiring reflog entries. */
      +struct log_expiry_config {
     -+  /* Drop entries older than this timestamp */
     -+  uint64_t time;
     ++	/* Drop entries older than this timestamp */
     ++	uint64_t time;
      +
     -+  /* Drop older entries */
     -+  uint64_t min_update_index;
     ++	/* Drop older entries */
     ++	uint64_t min_update_index;
      +};
      +
     -+/* compacts all reftables into a giant table. Expire reflog entries if config is non-NULL */
     ++/* compacts all reftables into a giant table. Expire reflog entries if config is
     ++ * non-NULL */
      +int stack_compact_all(struct stack *st, struct log_expiry_config *config);
      +
      +/* heuristically compact unbalanced table stack. */
     @@ -5004,18 +5031,18 @@
      +/* convenience function to read a single ref. Returns < 0 for error, 0
      +   for success, and 1 if ref not found. */
      +int stack_read_ref(struct stack *st, const char *refname,
     -+                   struct ref_record *ref);
     ++		   struct ref_record *ref);
      +
      +/* convenience function to read a single log. Returns < 0 for error, 0
      +   for success, and 1 if ref not found. */
      +int stack_read_log(struct stack *st, const char *refname,
     -+                   struct log_record *log);
     ++		   struct log_record *log);
      +
      +/* statistics on past compactions. */
      +struct compaction_stats {
     -+  uint64_t bytes;
     -+  int attempts;
     -+  int failures;
     ++	uint64_t bytes;
     ++	int attempts;
     ++	int failures;
      +};
      +
      +struct compaction_stats *stack_compaction_stats(struct stack *st);
     @@ -5037,7 +5064,7 @@
      +
      +#include "reftable.h"
      +
     -+#include <string.h>
     ++#include "system.h"
      +
      +#include "basics.h"
      +#include "block.h"
     @@ -5048,418 +5075,465 @@
      +
      +static const int update_index = 5;
      +
     -+void test_buffer(void) {
     -+  struct slice buf = {};
     -+
     -+  byte in[] = "hello";
     -+  slice_write(&buf, in, sizeof(in));
     -+  struct block_source source;
     -+  block_source_from_slice(&source, &buf);
     -+  assert(block_source_size(source) == 6);
     -+  struct block out = {};
     -+  int n = block_source_read_block(source, &out, 0, sizeof(in));
     -+  assert(n == sizeof(in));
     -+  assert(0 == memcmp(in, out.data, n));
     -+  block_source_return_block(source, &out);
     -+
     -+  n = block_source_read_block(source, &out, 1, 2);
     -+  assert(n == 2);
     -+  assert(0 == memcmp(out.data, "el", 2));
     -+
     -+  block_source_return_block(source, &out);
     -+  block_source_close(&source);
     -+  free(slice_yield(&buf));
     -+}
     -+
     -+void write_table(char ***names, struct slice *buf, int N, int block_size) {
     -+  *names = calloc(sizeof(char *), N + 1);
     -+
     -+  struct write_options opts = {
     -+      .block_size = block_size,
     -+  };
     -+
     -+  struct writer *w = new_writer(&slice_write_void, buf, &opts);
     -+
     -+  writer_set_limits(w, update_index, update_index);
     -+  {
     -+    struct ref_record ref = {};
     -+    for (int i = 0; i < N; i++) {
     -+      byte hash[SHA1_SIZE];
     -+      set_test_hash(hash, i);
     -+
     -+      char name[100];
     -+      sprintf(name, "refs/heads/branch%02d", i);
     -+
     -+      ref.ref_name = name;
     -+      ref.value = hash;
     -+      ref.update_index = update_index;
     -+      (*names)[i] = strdup(name);
     -+
     -+      fflush(stdout);
     -+      int n = writer_add_ref(w, &ref);
     -+      assert(n == 0);
     -+    }
     -+  }
     -+  int n = writer_close(w);
     -+  assert(n == 0);
     -+
     -+  struct stats *stats = writer_stats(w);
     -+  for (int i = 0; i < stats->ref_stats.blocks; i++) {
     -+    int off = i * opts.block_size;
     -+    if (off == 0) {
     -+      off = HEADER_SIZE;
     -+    }
     -+    assert(buf->buf[off] == 'r');
     -+  }
     -+
     -+  writer_free(w);
     -+  w = NULL;
     -+}
     -+
     -+void test_log_write_read(void) {
     -+  int N = 2;
     -+  char **names = calloc(sizeof(char *), N + 1);
     -+
     -+  struct write_options opts = {
     -+      .block_size = 256,
     -+  };
     -+
     -+  struct slice buf = {};
     -+  struct writer *w = new_writer(&slice_write_void, &buf, &opts);
     -+
     -+  writer_set_limits(w, 0, N);
     -+  {
     -+    struct ref_record ref = {};
     -+    for (int i = 0; i < N; i++) {
     -+      char name[256];
     -+      sprintf(name, "b%02d%0*d", i, 130, 7);
     -+      names[i] = strdup(name);
     -+      puts(name);
     -+      ref.ref_name = name;
     -+      ref.update_index = i;
     -+
     -+      int err = writer_add_ref(w, &ref);
     -+      assert_err(err);
     -+    }
     -+  }
     -+
     -+  {
     -+    struct log_record log = {};
     -+    for (int i = 0; i < N; i++) {
     -+      byte hash1[SHA1_SIZE], hash2[SHA1_SIZE];
     -+      set_test_hash(hash1, i);
     -+      set_test_hash(hash2, i + 1);
     -+
     -+      log.ref_name = names[i];
     -+      log.update_index = i;
     -+      log.old_hash = hash1;
     -+      log.new_hash = hash2;
     -+
     -+      int err = writer_add_log(w, &log);
     -+      assert_err(err);
     -+    }
     -+  }
     -+
     -+  int n = writer_close(w);
     -+  assert(n == 0);
     -+
     -+  struct stats *stats = writer_stats(w);
     -+  assert(stats->log_stats.blocks > 0);
     -+  writer_free(w);
     -+  w = NULL;
     -+
     -+  struct block_source source = {};
     -+  block_source_from_slice(&source, &buf);
     -+
     -+  struct reader rd = {};
     -+  int err = init_reader(&rd, source, "file.log");
     -+  assert(err == 0);
     -+
     -+  {
     -+    struct iterator it = {};
     -+    err = reader_seek_ref(&rd, &it, names[N-1]);
     -+    assert(err == 0);
     -+
     -+    struct ref_record ref = {};
     -+    err = iterator_next_ref(it, &ref);
     -+    assert_err(err);
     -+
     -+    // end of iteration.
     -+    err = iterator_next_ref(it, &ref);
     -+    assert(0 < err);
     -+
     -+    iterator_destroy(&it);
     -+    ref_record_clear(&ref);
     -+  }
     -+
     -+  {
     -+    struct iterator it = {};
     -+    err = reader_seek_log(&rd, &it, "");
     -+    assert(err == 0);
     -+
     -+    struct log_record log = {};
     -+    int i = 0;
     -+    while (true) {
     -+      int err = iterator_next_log(it, &log);
     -+      if (err > 0) {
     -+        break;
     -+      }
     -+
     -+      assert_err(err);
     -+      assert_streq(names[i], log.ref_name);
     -+      assert(i == log.update_index);
     -+      i++;
     -+    }
     -+
     -+    assert(i == N);
     -+    iterator_destroy(&it);
     -+  }
     -+
     -+  // cleanup.
     -+  free(slice_yield(&buf));
     -+  free_names(names);
     -+  reader_close(&rd);
     -+}
     -+
     -+void test_table_read_write_sequential(void) {
     -+  char **names;
     -+  struct slice buf = {};
     -+  int N = 50;
     -+  write_table(&names, &buf, N, 256);
     -+
     -+  struct block_source source = {};
     -+  block_source_from_slice(&source, &buf);
     -+
     -+  struct reader rd = {};
     -+  int err = init_reader(&rd, source, "file.ref");
     -+  assert(err == 0);
     -+
     -+  struct iterator it = {};
     -+  err = reader_seek_ref(&rd, &it, "");
     -+  assert(err == 0);
     -+
     -+  int j = 0;
     -+  while (true) {
     -+    struct ref_record ref = {};
     -+    int r = iterator_next_ref(it, &ref);
     -+    assert(r >= 0);
     -+    if (r > 0) {
     -+      break;
     -+    }
     -+    assert(0 == strcmp(names[j], ref.ref_name));
     -+    assert(update_index == ref.update_index);
     -+
     -+    j++;
     -+    ref_record_clear(&ref);
     -+  }
     -+  assert(j == N);
     -+  iterator_destroy(&it);
     -+  free(slice_yield(&buf));
     -+  free_names(names);
     -+
     -+  reader_close(&rd);
     -+}
     -+
     -+void test_table_write_small_table(void) {
     -+  char **names;
     -+  struct slice buf = {};
     -+  int N = 1;
     -+  write_table(&names, &buf, N, 4096);
     -+  assert(buf.len < 200);
     -+  free(slice_yield(&buf));
     -+  free_names(names);
     -+}
     -+
     -+void test_table_read_api(void) {
     -+  char **names;
     -+  struct slice buf = {};
     -+  int N = 50;
     -+  write_table(&names, &buf, N, 256);
     -+
     -+  struct reader rd = {};
     -+  struct block_source source = {};
     -+  block_source_from_slice(&source, &buf);
     -+
     -+  int err = init_reader(&rd, source, "file.ref");
     -+  assert(err == 0);
     -+
     -+  struct iterator it = {};
     -+  err = reader_seek_ref(&rd, &it, names[0]);
     -+  assert(err == 0);
     -+
     -+  struct log_record log = {};
     -+  err = iterator_next_log(it, &log);
     -+  assert(err == API_ERROR);
     -+
     -+  free(slice_yield(&buf));
     -+  for (int i = 0; i < N; i++) {
     -+    free(names[i]);
     -+  }
     -+  free(names);
     -+  reader_close(&rd);
     -+}
     -+
     -+void test_table_read_write_seek(bool index) {
     -+  char **names;
     -+  struct slice buf = {};
     -+  int N = 50;
     -+  write_table(&names, &buf, N, 256);
     -+
     -+  struct reader rd = {};
     -+  struct block_source source = {};
     -+  block_source_from_slice(&source, &buf);
     -+
     -+  int err = init_reader(&rd, source, "file.ref");
     -+  assert(err == 0);
     -+
     -+  if (!index) {
     -+    rd.ref_offsets.index_offset = 0;
     -+  }
     -+
     -+  for (int i = 1; i < N; i++) {
     -+    struct iterator it = {};
     -+    int err = reader_seek_ref(&rd, &it, names[i]);
     -+    assert(err == 0);
     -+    struct ref_record ref = {};
     -+    err = iterator_next_ref(it, &ref);
     -+    assert(err == 0);
     -+    assert(0 == strcmp(names[i], ref.ref_name));
     -+    assert(i == ref.value[0]);
     -+
     -+    ref_record_clear(&ref);
     -+    iterator_destroy(&it);
     -+  }
     -+
     -+  free(slice_yield(&buf));
     -+  for (int i = 0; i < N; i++) {
     -+    free(names[i]);
     -+  }
     -+  free(names);
     -+  reader_close(&rd);
     -+}
     -+
     -+void test_table_read_write_seek_linear(void) {
     -+  test_table_read_write_seek(false);
     -+}
     -+
     -+void test_table_read_write_seek_index(void) {
     -+  test_table_read_write_seek(true);
     -+}
     -+
     -+void test_table_refs_for(bool indexed) {
     -+  int N = 50;
     -+
     -+  char **want_names = calloc(sizeof(char *), N + 1);
     -+
     -+  int want_names_len = 0;
     -+  byte want_hash[SHA1_SIZE];
     -+  set_test_hash(want_hash, 4);
     -+
     -+  struct write_options opts = {
     -+      .block_size = 256,
     -+  };
     -+
     -+  struct slice buf = {};
     -+  struct writer *w = new_writer(&slice_write_void, &buf, &opts);
     -+  {
     -+    struct ref_record ref = {};
     -+    for (int i = 0; i < N; i++) {
     -+      byte hash[SHA1_SIZE];
     -+      memset(hash, i, sizeof(hash));
     -+      char fill[51] = {};
     -+      memset(fill, 'x', 50);
     -+      char name[100];
     -+      // Put the variable part in the start
     -+      sprintf(name, "br%02d%s", i, fill);
     -+      name[40] = 0;
     -+      ref.ref_name = name;
     -+
     -+      byte hash1[SHA1_SIZE];
     -+      byte hash2[SHA1_SIZE];
     -+
     -+      set_test_hash(hash1, i / 4);
     -+      set_test_hash(hash2, 3 + i / 4);
     -+      ref.value = hash1;
     -+      ref.target_value = hash2;
     -+
     -+      // 80 bytes / entry, so 3 entries per block. Yields 17 blocks.
     -+      int n = writer_add_ref(w, &ref);
     -+      assert(n == 0);
     -+
     -+      if (0 == memcmp(hash1, want_hash, SHA1_SIZE) ||
     -+          0 == memcmp(hash2, want_hash, SHA1_SIZE)) {
     -+        want_names[want_names_len++] = strdup(name);
     -+      }
     -+    }
     -+  }
     -+
     -+  int n = writer_close(w);
     -+  assert(n == 0);
     -+
     -+  writer_free(w);
     -+  w = NULL;
     -+
     -+  struct reader rd;
     -+  struct block_source source = {};
     -+  block_source_from_slice(&source, &buf);
     -+
     -+  int err = init_reader(&rd, source, "file.ref");
     -+  assert(err == 0);
     -+  if (!indexed) {
     -+    rd.obj_offsets.present = 0;
     -+  }
     -+
     -+  struct iterator it = {};
     -+  err = reader_seek_ref(&rd, &it, "");
     -+  assert(err == 0);
     -+  iterator_destroy(&it);
     -+
     -+  err = reader_refs_for(&rd, &it, want_hash, SHA1_SIZE);
     -+  assert(err == 0);
     -+
     -+  struct ref_record ref = {};
     -+
     -+  int j = 0;
     -+  while (true) {
     -+    int err = iterator_next_ref(it, &ref);
     -+    assert(err >= 0);
     -+    if (err > 0) {
     -+      break;
     -+    }
     -+
     -+    assert(j < want_names_len);
     -+    assert(0 == strcmp(ref.ref_name, want_names[j]));
     -+    j++;
     -+    ref_record_clear(&ref);
     -+  }
     -+  assert(j == want_names_len);
     -+
     -+  free(slice_yield(&buf));
     -+  free_names(want_names);
     -+  iterator_destroy(&it);
     -+  reader_close(&rd);
     -+}
     -+
     -+void test_table_refs_for_no_index(void) { test_table_refs_for(false); }
     -+
     -+void test_table_refs_for_obj_index(void) { test_table_refs_for(true); }
     -+
     -+int main() {
     -+  add_test_case("test_log_write_read", test_log_write_read);
     -+  add_test_case("test_table_write_small_table", &test_table_write_small_table);
     -+  add_test_case("test_buffer", &test_buffer);
     -+  add_test_case("test_table_read_api", &test_table_read_api);
     -+  add_test_case("test_table_read_write_sequential",
     -+                &test_table_read_write_sequential);
     -+  add_test_case("test_table_read_write_seek_linear",
     -+                &test_table_read_write_seek_linear);
     -+  add_test_case("test_table_read_write_seek_index",
     -+                &test_table_read_write_seek_index);
     -+  add_test_case("test_table_read_write_refs_for_no_index",
     -+                &test_table_refs_for_no_index);
     -+  add_test_case("test_table_read_write_refs_for_obj_index",
     -+                &test_table_refs_for_obj_index);
     -+  test_main();
     ++void test_buffer(void)
     ++{
     ++	struct slice buf = {};
     ++
     ++	byte in[] = "hello";
     ++	slice_write(&buf, in, sizeof(in));
     ++	struct block_source source;
     ++	block_source_from_slice(&source, &buf);
     ++	assert(block_source_size(source) == 6);
     ++	struct block out = {};
     ++	int n = block_source_read_block(source, &out, 0, sizeof(in));
     ++	assert(n == sizeof(in));
     ++	assert(!memcmp(in, out.data, n));
     ++	block_source_return_block(source, &out);
     ++
     ++	n = block_source_read_block(source, &out, 1, 2);
     ++	assert(n == 2);
     ++	assert(!memcmp(out.data, "el", 2));
     ++
     ++	block_source_return_block(source, &out);
     ++	block_source_close(&source);
     ++	free(slice_yield(&buf));
     ++}
     ++
     ++void write_table(char ***names, struct slice *buf, int N, int block_size)
     ++{
     ++	*names = calloc(sizeof(char *), N + 1);
     ++
     ++	struct write_options opts = {
     ++		.block_size = block_size,
     ++	};
     ++
     ++	struct writer *w = new_writer(&slice_write_void, buf, &opts);
     ++
     ++	writer_set_limits(w, update_index, update_index);
     ++	{
     ++		struct ref_record ref = {};
     ++		int i = 0;
     ++		for (i = 0; i < N; i++) {
     ++			byte hash[SHA1_SIZE];
     ++			set_test_hash(hash, i);
     ++
     ++			char name[100];
     ++			snprintf(name, sizeof(name), "refs/heads/branch%02d",
     ++				 i);
     ++
     ++			ref.ref_name = name;
     ++			ref.value = hash;
     ++			ref.update_index = update_index;
     ++			(*names)[i] = strdup(name);
     ++
     ++			int n = writer_add_ref(w, &ref);
     ++			assert(n == 0);
     ++		}
     ++	}
     ++	{
     ++		struct log_record log = {};
     ++		int i = 0;
     ++		for (i = 0; i < N; i++) {
     ++			byte hash[SHA1_SIZE];
     ++			set_test_hash(hash, i);
     ++
     ++			char name[100];
     ++			snprintf(name, sizeof(name), "refs/heads/branch%02d",
     ++				 i);
     ++
     ++			log.ref_name = name;
     ++			log.new_hash = hash;
     ++			log.update_index = update_index;
     ++			log.message = "message";
     ++
     ++			int n = writer_add_log(w, &log);
     ++			assert(n == 0);
     ++		}
     ++	}
     ++
     ++	int n = writer_close(w);
     ++	assert(n == 0);
     ++
     ++	struct stats *stats = writer_stats(w);
     ++	int i = 0;
     ++	for (i = 0; i < stats->ref_stats.blocks; i++) {
     ++		int off = i * opts.block_size;
     ++		if (off == 0) {
     ++			off = HEADER_SIZE;
     ++		}
     ++		assert(buf->buf[off] == 'r');
     ++	}
     ++
     ++	writer_free(w);
     ++	w = NULL;
     ++}
     ++
     ++void test_log_write_read(void)
     ++{
     ++	int N = 2;
     ++	char **names = calloc(sizeof(char *), N + 1);
     ++
     ++	struct write_options opts = {
     ++		.block_size = 256,
     ++	};
     ++
     ++	struct slice buf = {};
     ++	struct writer *w = new_writer(&slice_write_void, &buf, &opts);
     ++
     ++	writer_set_limits(w, 0, N);
     ++	{
     ++		struct ref_record ref = {};
     ++		int i = 0;
     ++		for (i = 0; i < N; i++) {
     ++			char name[256];
     ++			snprintf(name, sizeof(name), "b%02d%0*d", i, 130, 7);
     ++			names[i] = strdup(name);
     ++			puts(name);
     ++			ref.ref_name = name;
     ++			ref.update_index = i;
     ++
     ++			int err = writer_add_ref(w, &ref);
     ++			assert_err(err);
     ++		}
     ++	}
     ++
     ++	{
     ++		struct log_record log = {};
     ++		int i = 0;
     ++		for (i = 0; i < N; i++) {
     ++			byte hash1[SHA1_SIZE], hash2[SHA1_SIZE];
     ++			set_test_hash(hash1, i);
     ++			set_test_hash(hash2, i + 1);
     ++
     ++			log.ref_name = names[i];
     ++			log.update_index = i;
     ++			log.old_hash = hash1;
     ++			log.new_hash = hash2;
     ++
     ++			int err = writer_add_log(w, &log);
     ++			assert_err(err);
     ++		}
     ++	}
     ++
     ++	int n = writer_close(w);
     ++	assert(n == 0);
     ++
     ++	struct stats *stats = writer_stats(w);
     ++	assert(stats->log_stats.blocks > 0);
     ++	writer_free(w);
     ++	w = NULL;
     ++
     ++	struct block_source source = {};
     ++	block_source_from_slice(&source, &buf);
     ++
     ++	struct reader rd = {};
     ++	int err = init_reader(&rd, source, "file.log");
     ++	assert(err == 0);
     ++
     ++	{
     ++		struct iterator it = {};
     ++		err = reader_seek_ref(&rd, &it, names[N - 1]);
     ++		assert(err == 0);
     ++
     ++		struct ref_record ref = {};
     ++		err = iterator_next_ref(it, &ref);
     ++		assert_err(err);
     ++
     ++		/* end of iteration. */
     ++		err = iterator_next_ref(it, &ref);
     ++		assert(0 < err);
     ++
     ++		iterator_destroy(&it);
     ++		ref_record_clear(&ref);
     ++	}
     ++
     ++	{
     ++		struct iterator it = {};
     ++		err = reader_seek_log(&rd, &it, "");
     ++		assert(err == 0);
     ++
     ++		struct log_record log = {};
     ++		int i = 0;
     ++		while (true) {
     ++			int err = iterator_next_log(it, &log);
     ++			if (err > 0) {
     ++				break;
     ++			}
     ++
     ++			assert_err(err);
     ++			assert_streq(names[i], log.ref_name);
     ++			assert(i == log.update_index);
     ++			i++;
     ++		}
     ++
     ++		assert(i == N);
     ++		iterator_destroy(&it);
     ++	}
     ++
     ++	/* cleanup. */
     ++	free(slice_yield(&buf));
     ++	free_names(names);
     ++	reader_close(&rd);
     ++}
     ++
     ++void test_table_read_write_sequential(void)
     ++{
     ++	char **names;
     ++	struct slice buf = {};
     ++	int N = 50;
     ++	write_table(&names, &buf, N, 256);
     ++
     ++	struct block_source source = {};
     ++	block_source_from_slice(&source, &buf);
     ++
     ++	struct reader rd = {};
     ++	int err = init_reader(&rd, source, "file.ref");
     ++	assert(err == 0);
     ++
     ++	struct iterator it = {};
     ++	err = reader_seek_ref(&rd, &it, "");
     ++	assert(err == 0);
     ++
     ++	int j = 0;
     ++	while (true) {
     ++		struct ref_record ref = {};
     ++		int r = iterator_next_ref(it, &ref);
     ++		assert(r >= 0);
     ++		if (r > 0) {
     ++			break;
     ++		}
     ++		assert(0 == strcmp(names[j], ref.ref_name));
     ++		assert(update_index == ref.update_index);
     ++
     ++		j++;
     ++		ref_record_clear(&ref);
     ++	}
     ++	assert(j == N);
     ++	iterator_destroy(&it);
     ++	free(slice_yield(&buf));
     ++	free_names(names);
     ++
     ++	reader_close(&rd);
     ++}
     ++
     ++void test_table_write_small_table(void)
     ++{
     ++	char **names;
     ++	struct slice buf = {};
     ++	int N = 1;
     ++	write_table(&names, &buf, N, 4096);
     ++	assert(buf.len < 200);
     ++	free(slice_yield(&buf));
     ++	free_names(names);
     ++}
     ++
     ++void test_table_read_api(void)
     ++{
     ++	char **names;
     ++	struct slice buf = {};
     ++	int N = 50;
     ++	write_table(&names, &buf, N, 256);
     ++
     ++	struct reader rd = {};
     ++	struct block_source source = {};
     ++	block_source_from_slice(&source, &buf);
     ++
     ++	int err = init_reader(&rd, source, "file.ref");
     ++	assert(err == 0);
     ++
     ++	struct iterator it = {};
     ++	err = reader_seek_ref(&rd, &it, names[0]);
     ++	assert(err == 0);
     ++
     ++	struct log_record log = {};
     ++	err = iterator_next_log(it, &log);
     ++	assert(err == API_ERROR);
     ++
     ++	free(slice_yield(&buf));
     ++	int i = 0;
     ++	for (i = 0; i < N; i++) {
     ++		free(names[i]);
     ++	}
     ++	free(names);
     ++	reader_close(&rd);
     ++}
     ++
     ++void test_table_read_write_seek(bool index)
     ++{
     ++	char **names;
     ++	struct slice buf = {};
     ++	int N = 50;
     ++	write_table(&names, &buf, N, 256);
     ++
     ++	struct reader rd = {};
     ++	struct block_source source = {};
     ++	block_source_from_slice(&source, &buf);
     ++
     ++	int err = init_reader(&rd, source, "file.ref");
     ++	assert(err == 0);
     ++
     ++	if (!index) {
     ++		rd.ref_offsets.index_offset = 0;
     ++	}
     ++
     ++	int i = 0;
     ++	for (i = 1; i < N; i++) {
     ++		struct iterator it = {};
     ++		int err = reader_seek_ref(&rd, &it, names[i]);
     ++		assert(err == 0);
     ++		struct ref_record ref = {};
     ++		err = iterator_next_ref(it, &ref);
     ++		assert(err == 0);
     ++		assert(0 == strcmp(names[i], ref.ref_name));
     ++		assert(i == ref.value[0]);
     ++
     ++		ref_record_clear(&ref);
     ++		iterator_destroy(&it);
     ++	}
     ++
     ++	free(slice_yield(&buf));
     ++	for (i = 0; i < N; i++) {
     ++		free(names[i]);
     ++	}
     ++	free(names);
     ++	reader_close(&rd);
     ++}
     ++
     ++void test_table_read_write_seek_linear(void)
     ++{
     ++	test_table_read_write_seek(false);
     ++}
     ++
     ++void test_table_read_write_seek_index(void)
     ++{
     ++	test_table_read_write_seek(true);
     ++}
     ++
     ++void test_table_refs_for(bool indexed)
     ++{
     ++	int N = 50;
     ++
     ++	char **want_names = calloc(sizeof(char *), N + 1);
     ++
     ++	int want_names_len = 0;
     ++	byte want_hash[SHA1_SIZE];
     ++	set_test_hash(want_hash, 4);
     ++
     ++	struct write_options opts = {
     ++		.block_size = 256,
     ++	};
     ++
     ++	struct slice buf = {};
     ++	struct writer *w = new_writer(&slice_write_void, &buf, &opts);
     ++	{
     ++		struct ref_record ref = {};
     ++		int i = 0;
     ++		for (i = 0; i < N; i++) {
     ++			byte hash[SHA1_SIZE];
     ++			memset(hash, i, sizeof(hash));
     ++			char fill[51] = {};
     ++			memset(fill, 'x', 50);
     ++			char name[100];
     ++			/* Put the variable part in the start */
     ++			snprintf(name, sizeof(name), "br%02d%s", i, fill);
     ++			name[40] = 0;
     ++			ref.ref_name = name;
     ++
     ++			byte hash1[SHA1_SIZE];
     ++			byte hash2[SHA1_SIZE];
     ++
     ++			set_test_hash(hash1, i / 4);
     ++			set_test_hash(hash2, 3 + i / 4);
     ++			ref.value = hash1;
     ++			ref.target_value = hash2;
     ++
     ++			/* 80 bytes / entry, so 3 entries per block. Yields 17 */
     ++			/* blocks. */
     ++			int n = writer_add_ref(w, &ref);
     ++			assert(n == 0);
     ++
     ++			if (!memcmp(hash1, want_hash, SHA1_SIZE) ||
     ++			    !memcmp(hash2, want_hash, SHA1_SIZE)) {
     ++				want_names[want_names_len++] = strdup(name);
     ++			}
     ++		}
     ++	}
     ++
     ++	int n = writer_close(w);
     ++	assert(n == 0);
     ++
     ++	writer_free(w);
     ++	w = NULL;
     ++
     ++	struct reader rd;
     ++	struct block_source source = {};
     ++	block_source_from_slice(&source, &buf);
     ++
     ++	int err = init_reader(&rd, source, "file.ref");
     ++	assert(err == 0);
     ++	if (!indexed) {
     ++		rd.obj_offsets.present = 0;
     ++	}
     ++
     ++	struct iterator it = {};
     ++	err = reader_seek_ref(&rd, &it, "");
     ++	assert(err == 0);
     ++	iterator_destroy(&it);
     ++
     ++	err = reader_refs_for(&rd, &it, want_hash, SHA1_SIZE);
     ++	assert(err == 0);
     ++
     ++	struct ref_record ref = {};
     ++
     ++	int j = 0;
     ++	while (true) {
     ++		int err = iterator_next_ref(it, &ref);
     ++		assert(err >= 0);
     ++		if (err > 0) {
     ++			break;
     ++		}
     ++
     ++		assert(j < want_names_len);
     ++		assert(0 == strcmp(ref.ref_name, want_names[j]));
     ++		j++;
     ++		ref_record_clear(&ref);
     ++	}
     ++	assert(j == want_names_len);
     ++
     ++	free(slice_yield(&buf));
     ++	free_names(want_names);
     ++	iterator_destroy(&it);
     ++	reader_close(&rd);
     ++}
     ++
     ++void test_table_refs_for_no_index(void)
     ++{
     ++	test_table_refs_for(false);
     ++}
     ++
     ++void test_table_refs_for_obj_index(void)
     ++{
     ++	test_table_refs_for(true);
     ++}
     ++
     ++int main()
     ++{
     ++	add_test_case("test_log_write_read", test_log_write_read);
     ++	add_test_case("test_table_write_small_table",
     ++		      &test_table_write_small_table);
     ++	add_test_case("test_buffer", &test_buffer);
     ++	add_test_case("test_table_read_api", &test_table_read_api);
     ++	add_test_case("test_table_read_write_sequential",
     ++		      &test_table_read_write_sequential);
     ++	add_test_case("test_table_read_write_seek_linear",
     ++		      &test_table_read_write_seek_linear);
     ++	add_test_case("test_table_read_write_seek_index",
     ++		      &test_table_read_write_seek_index);
     ++	add_test_case("test_table_read_write_refs_for_no_index",
     ++		      &test_table_refs_for_no_index);
     ++	add_test_case("test_table_read_write_refs_for_obj_index",
     ++		      &test_table_refs_for_obj_index);
     ++	test_main();
      +}
      
       diff --git a/reftable/slice.c b/reftable/slice.c
     @@ -5477,174 +5551,194 @@
      +
      +#include "slice.h"
      +
     -+#include <assert.h>
     -+#include <stdlib.h>
     -+#include <string.h>
     ++#include "system.h"
      +
      +#include "reftable.h"
      +
     -+void slice_set_string(struct slice *s, const char *str) {
     -+  if (str == NULL) {
     -+    s->len = 0;
     -+    return;
     -+  }
     ++void slice_set_string(struct slice *s, const char *str)
     ++{
     ++	if (str == NULL) {
     ++		s->len = 0;
     ++		return;
     ++	}
      +
     -+  {
     -+    int l = strlen(str);
     -+    l++;  // \0
     -+    slice_resize(s, l);
     -+    memcpy(s->buf, str, l);
     -+    s->len = l - 1;
     -+  }
     ++	{
     ++		int l = strlen(str);
     ++		l++; /* \0 */
     ++		slice_resize(s, l);
     ++		memcpy(s->buf, str, l);
     ++		s->len = l - 1;
     ++	}
      +}
      +
     -+void slice_resize(struct slice *s, int l) {
     -+  if (s->cap < l) {
     -+    int c = s->cap * 2;
     -+    if (c < l) {
     -+      c = l;
     -+    }
     -+    s->cap = c;
     -+    s->buf = realloc(s->buf, s->cap);
     -+  }
     -+  s->len = l;
     ++void slice_resize(struct slice *s, int l)
     ++{
     ++	if (s->cap < l) {
     ++		int c = s->cap * 2;
     ++		if (c < l) {
     ++			c = l;
     ++		}
     ++		s->cap = c;
     ++		s->buf = realloc(s->buf, s->cap);
     ++	}
     ++	s->len = l;
      +}
      +
     -+void slice_append_string(struct slice *d, const char *s) {
     -+  int l1 = d->len;
     -+  int l2 = strlen(s);
     ++void slice_append_string(struct slice *d, const char *s)
     ++{
     ++	int l1 = d->len;
     ++	int l2 = strlen(s);
      +
     -+  slice_resize(d, l2 + l1);
     -+  memcpy(d->buf + l1, s, l2);
     ++	slice_resize(d, l2 + l1);
     ++	memcpy(d->buf + l1, s, l2);
      +}
      +
     -+void slice_append(struct slice *s, struct slice a) {
     -+  int end = s->len;
     -+  slice_resize(s, s->len + a.len);
     -+  memcpy(s->buf + end, a.buf, a.len);
     ++void slice_append(struct slice *s, struct slice a)
     ++{
     ++	int end = s->len;
     ++	slice_resize(s, s->len + a.len);
     ++	memcpy(s->buf + end, a.buf, a.len);
      +}
      +
     -+byte *slice_yield(struct slice *s) {
     -+  byte *p = s->buf;
     -+  s->buf = NULL;
     -+  s->cap = 0;
     -+  s->len = 0;
     -+  return p;
     ++byte *slice_yield(struct slice *s)
     ++{
     ++	byte *p = s->buf;
     ++	s->buf = NULL;
     ++	s->cap = 0;
     ++	s->len = 0;
     ++	return p;
      +}
      +
     -+void slice_copy(struct slice *dest, struct slice src) {
     -+  slice_resize(dest, src.len);
     -+  memcpy(dest->buf, src.buf, src.len);
     ++void slice_copy(struct slice *dest, struct slice src)
     ++{
     ++	slice_resize(dest, src.len);
     ++	memcpy(dest->buf, src.buf, src.len);
      +}
      +
      +/* return the underlying data as char*. len is left unchanged, but
      +   a \0 is added at the end. */
     -+const char *slice_as_string(struct slice *s) {
     -+  if (s->cap == s->len) {
     -+    int l = s->len;
     -+    slice_resize(s, l + 1);
     -+    s->len = l;
     -+  }
     -+  s->buf[s->len] = 0;
     -+  return (const char *)s->buf;
     ++const char *slice_as_string(struct slice *s)
     ++{
     ++	if (s->cap == s->len) {
     ++		int l = s->len;
     ++		slice_resize(s, l + 1);
     ++		s->len = l;
     ++	}
     ++	s->buf[s->len] = 0;
     ++	return (const char *)s->buf;
      +}
      +
      +/* return a newly malloced string for this slice */
     -+char *slice_to_string(struct slice in) {
     -+  struct slice s = {};
     -+  slice_resize(&s, in.len + 1);
     -+  s.buf[in.len] = 0;
     -+  memcpy(s.buf, in.buf, in.len);
     -+  return (char *)slice_yield(&s);
     ++char *slice_to_string(struct slice in)
     ++{
     ++	struct slice s = {};
     ++	slice_resize(&s, in.len + 1);
     ++	s.buf[in.len] = 0;
     ++	memcpy(s.buf, in.buf, in.len);
     ++	return (char *)slice_yield(&s);
      +}
      +
     -+bool slice_equal(struct slice a, struct slice b) {
     -+  if (a.len != b.len) {
     -+    return 0;
     -+  }
     -+  return memcmp(a.buf, b.buf, a.len) == 0;
     ++bool slice_equal(struct slice a, struct slice b)
     ++{
     ++	if (a.len != b.len) {
     ++		return 0;
     ++	}
     ++	return memcmp(a.buf, b.buf, a.len) == 0;
      +}
      +
     -+int slice_compare(struct slice a, struct slice b) {
     -+  int min = a.len < b.len ? a.len : b.len;
     -+  int res = memcmp(a.buf, b.buf, min);
     -+  if (res != 0) {
     -+    return res;
     -+  }
     -+  if (a.len < b.len) {
     -+    return -1;
     -+  } else if (a.len > b.len) {
     -+    return 1;
     -+  } else {
     -+    return 0;
     -+  }
     ++int slice_compare(struct slice a, struct slice b)
     ++{
     ++	int min = a.len < b.len ? a.len : b.len;
     ++	int res = memcmp(a.buf, b.buf, min);
     ++	if (res != 0) {
     ++		return res;
     ++	}
     ++	if (a.len < b.len) {
     ++		return -1;
     ++	} else if (a.len > b.len) {
     ++		return 1;
     ++	} else {
     ++		return 0;
     ++	}
      +}
      +
     -+int slice_write(struct slice *b, byte *data, int sz) {
     -+  if (b->len + sz > b->cap) {
     -+    int newcap = 2 * b->cap + 1;
     -+    if (newcap < b->len + sz) {
     -+      newcap = (b->len + sz);
     -+    }
     -+    b->buf = realloc(b->buf, newcap);
     -+    b->cap = newcap;
     -+  }
     ++int slice_write(struct slice *b, byte *data, int sz)
     ++{
     ++	if (b->len + sz > b->cap) {
     ++		int newcap = 2 * b->cap + 1;
     ++		if (newcap < b->len + sz) {
     ++			newcap = (b->len + sz);
     ++		}
     ++		b->buf = realloc(b->buf, newcap);
     ++		b->cap = newcap;
     ++	}
      +
     -+  memcpy(b->buf + b->len, data, sz);
     -+  b->len += sz;
     -+  return sz;
     ++	memcpy(b->buf + b->len, data, sz);
     ++	b->len += sz;
     ++	return sz;
      +}
      +
     -+int slice_write_void(void *b, byte *data, int sz) {
     -+  return slice_write((struct slice *)b, data, sz);
     ++int slice_write_void(void *b, byte *data, int sz)
     ++{
     ++	return slice_write((struct slice *)b, data, sz);
      +}
      +
     -+static uint64_t slice_size(void *b) { return ((struct slice *)b)->len; }
     ++static uint64_t slice_size(void *b)
     ++{
     ++	return ((struct slice *)b)->len;
     ++}
      +
     -+static void slice_return_block(void *b, struct block *dest) {
     -+  memset(dest->data, 0xff, dest->len);
     -+  free(dest->data);
     ++static void slice_return_block(void *b, struct block *dest)
     ++{
     ++	memset(dest->data, 0xff, dest->len);
     ++	free(dest->data);
      +}
      +
     -+static void slice_close(void *b) {}
     ++static void slice_close(void *b)
     ++{
     ++}
      +
      +static int slice_read_block(void *v, struct block *dest, uint64_t off,
     -+                            uint32_t size) {
     -+  struct slice *b = (struct slice *)v;
     -+  assert(off + size <= b->len);
     -+  dest->data = calloc(size, 1);
     -+  memcpy(dest->data, b->buf + off, size);
     -+  dest->len = size;
     -+  return size;
     ++			    uint32_t size)
     ++{
     ++	struct slice *b = (struct slice *)v;
     ++	assert(off + size <= b->len);
     ++	dest->data = calloc(size, 1);
     ++	memcpy(dest->data, b->buf + off, size);
     ++	dest->len = size;
     ++	return size;
      +}
      +
      +struct block_source_vtable slice_vtable = {
     -+    .size = &slice_size,
     -+    .read_block = &slice_read_block,
     -+    .return_block = &slice_return_block,
     -+    .close = &slice_close,
     ++	.size = &slice_size,
     ++	.read_block = &slice_read_block,
     ++	.return_block = &slice_return_block,
     ++	.close = &slice_close,
      +};
      +
     -+void block_source_from_slice(struct block_source *bs, struct slice *buf) {
     -+  bs->ops = &slice_vtable;
     -+  bs->arg = buf;
     ++void block_source_from_slice(struct block_source *bs, struct slice *buf)
     ++{
     ++	bs->ops = &slice_vtable;
     ++	bs->arg = buf;
      +}
      +
     -+static void malloc_return_block(void *b, struct block *dest) {
     -+  memset(dest->data, 0xff, dest->len);
     -+  free(dest->data);
     ++static void malloc_return_block(void *b, struct block *dest)
     ++{
     ++	memset(dest->data, 0xff, dest->len);
     ++	free(dest->data);
      +}
      +
      +struct block_source_vtable malloc_vtable = {
     -+    .return_block = &malloc_return_block,
     ++	.return_block = &malloc_return_block,
      +};
      +
      +struct block_source malloc_block_source_instance = {
     -+    .ops = &malloc_vtable,
     ++	.ops = &malloc_vtable,
      +};
      +
     -+struct block_source malloc_block_source(void) {
     -+  return malloc_block_source_instance;
     ++struct block_source malloc_block_source(void)
     ++{
     ++	return malloc_block_source_instance;
      +}
      
       diff --git a/reftable/slice.h b/reftable/slice.h
     @@ -5667,9 +5761,9 @@
      +#include "reftable.h"
      +
      +struct slice {
     -+  byte *buf;
     -+  int len;
     -+  int cap;
     ++	byte *buf;
     ++	int len;
     ++	int cap;
      +};
      +
      +void slice_set_string(struct slice *dest, const char *);
     @@ -5707,33 +5801,33 @@
      +
      +#include "slice.h"
      +
     -+#include <assert.h>
     -+#include <stdlib.h>
     -+#include <string.h>
     ++#include "system.h"
      +
      +#include "basics.h"
      +#include "record.h"
      +#include "reftable.h"
      +#include "test_framework.h"
      +
     -+void test_slice(void) {
     -+  struct slice s = {};
     -+  slice_set_string(&s, "abc");
     -+  assert(0 == strcmp("abc", slice_as_string(&s)));
     ++void test_slice(void)
     ++{
     ++	struct slice s = {};
     ++	slice_set_string(&s, "abc");
     ++	assert(0 == strcmp("abc", slice_as_string(&s)));
      +
     -+  struct slice t = {};
     -+  slice_set_string(&t, "pqr");
     ++	struct slice t = {};
     ++	slice_set_string(&t, "pqr");
      +
     -+  slice_append(&s, t);
     -+  assert(0 == strcmp("abcpqr", slice_as_string(&s)));
     ++	slice_append(&s, t);
     ++	assert(0 == strcmp("abcpqr", slice_as_string(&s)));
      +
     -+  free(slice_yield(&s));
     -+  free(slice_yield(&t));
     ++	free(slice_yield(&s));
     ++	free(slice_yield(&t));
      +}
      +
     -+int main() {
     -+  add_test_case("test_slice", &test_slice);
     -+  test_main();
     ++int main()
     ++{
     ++	add_test_case("test_slice", &test_slice);
     ++	test_main();
      +}
      
       diff --git a/reftable/stack.c b/reftable/stack.c
     @@ -5751,926 +5845,980 @@
      +
      +#include "stack.h"
      +
     -+#include <assert.h>
     -+#include <errno.h>
     -+#include <fcntl.h>
     -+#include <stdio.h>
     -+#include <stdlib.h>
     -+#include <string.h>
     -+#include <sys/stat.h>
     -+#include <sys/time.h>
     -+#include <sys/types.h>
     -+#include <unistd.h>
     -+
     ++#include "system.h"
      +#include "merged.h"
      +#include "reader.h"
      +#include "reftable.h"
      +#include "writer.h"
      +
      +int new_stack(struct stack **dest, const char *dir, const char *list_file,
     -+              struct write_options config) {
     -+  struct stack *p = calloc(sizeof(struct stack), 1);
     -+  int err = 0;
     -+  *dest = NULL;
     -+  p->list_file = strdup(list_file);
     -+  p->reftable_dir = strdup(dir);
     -+  p->config = config;
     -+
     -+  err = stack_reload(p);
     -+  if (err < 0) {
     -+    stack_destroy(p);
     -+  } else {
     -+    *dest = p;
     -+  }
     -+  return err;
     -+}
     -+
     -+static int fread_lines(FILE *f, char ***namesp) {
     -+  long size = 0;
     -+  int err = fseek(f, 0, SEEK_END);
     -+  char *buf = NULL;
     -+  if (err < 0) {
     -+    err = IO_ERROR;
     -+    goto exit;
     -+  }
     -+  size = ftell(f);
     -+  if (size < 0) {
     -+    err = IO_ERROR;
     -+    goto exit;
     -+  }
     -+  err = fseek(f, 0, SEEK_SET);
     -+  if (err < 0) {
     -+    err = IO_ERROR;
     -+    goto exit;
     -+  }
     -+
     -+  buf = malloc(size + 1);
     -+  if (fread(buf, 1, size, f) != size) {
     -+    err = IO_ERROR;
     -+    goto exit;
     -+  }
     -+  buf[size] = 0;
     -+
     -+  parse_names(buf, size, namesp);
     ++	      struct write_options config)
     ++{
     ++	struct stack *p = calloc(sizeof(struct stack), 1);
     ++	int err = 0;
     ++	*dest = NULL;
     ++	p->list_file = strdup(list_file);
     ++	p->reftable_dir = strdup(dir);
     ++	p->config = config;
     ++
     ++	err = stack_reload(p);
     ++	if (err < 0) {
     ++		stack_destroy(p);
     ++	} else {
     ++		*dest = p;
     ++	}
     ++	return err;
     ++}
     ++
     ++static int fread_lines(FILE *f, char ***namesp)
     ++{
     ++	long size = 0;
     ++	int err = fseek(f, 0, SEEK_END);
     ++	char *buf = NULL;
     ++	if (err < 0) {
     ++		err = IO_ERROR;
     ++		goto exit;
     ++	}
     ++	size = ftell(f);
     ++	if (size < 0) {
     ++		err = IO_ERROR;
     ++		goto exit;
     ++	}
     ++	err = fseek(f, 0, SEEK_SET);
     ++	if (err < 0) {
     ++		err = IO_ERROR;
     ++		goto exit;
     ++	}
     ++
     ++	buf = malloc(size + 1);
     ++	if (fread(buf, 1, size, f) != size) {
     ++		err = IO_ERROR;
     ++		goto exit;
     ++	}
     ++	buf[size] = 0;
     ++
     ++	parse_names(buf, size, namesp);
      +exit:
     -+  free(buf);
     -+  return err;
     ++	free(buf);
     ++	return err;
      +}
      +
     -+int read_lines(const char *filename, char ***namesp) {
     -+  FILE *f = fopen(filename, "r");
     -+  int err = 0;
     -+  if (f == NULL) {
     -+    if (errno == ENOENT) {
     -+      *namesp = calloc(sizeof(char *), 1);
     -+      return 0;
     -+    }
     ++int read_lines(const char *filename, char ***namesp)
     ++{
     ++	FILE *f = fopen(filename, "r");
     ++	int err = 0;
     ++	if (f == NULL) {
     ++		if (errno == ENOENT) {
     ++			*namesp = calloc(sizeof(char *), 1);
     ++			return 0;
     ++		}
      +
     -+    return IO_ERROR;
     -+  }
     -+  err = fread_lines(f, namesp);
     -+  fclose(f);
     -+  return err;
     ++		return IO_ERROR;
     ++	}
     ++	err = fread_lines(f, namesp);
     ++	fclose(f);
     ++	return err;
      +}
      +
     -+struct merged_table *stack_merged_table(struct stack *st) {
     -+  return st->merged;
     ++struct merged_table *stack_merged_table(struct stack *st)
     ++{
     ++	return st->merged;
      +}
      +
      +/* Close and free the stack */
     -+void stack_destroy(struct stack *st) {
     -+  if (st->merged == NULL) {
     -+    return;
     -+  }
     -+
     -+  merged_table_close(st->merged);
     -+  merged_table_free(st->merged);
     -+  st->merged = NULL;
     -+
     -+  free(st->list_file);
     -+  st->list_file = NULL;
     -+  free(st->reftable_dir);
     -+  st->reftable_dir = NULL;
     -+  free(st);
     -+}
     -+
     -+static struct reader **stack_copy_readers(struct stack *st, int cur_len) {
     -+  struct reader **cur = calloc(sizeof(struct reader *), cur_len);
     -+  for (int i = 0; i < cur_len; i++) {
     -+    cur[i] = st->merged->stack[i];
     -+  }
     -+  return cur;
     -+}
     -+
     -+static int stack_reload_once(struct stack *st, char **names, bool reuse_open) {
     -+  int cur_len = st->merged == NULL ? 0 : st->merged->stack_len;
     -+  struct reader **cur = stack_copy_readers(st, cur_len);
     -+  int err = 0;
     -+  int names_len = names_length(names);
     -+  struct reader **new_tables = malloc(sizeof(struct reader *) * names_len);
     -+  int new_tables_len = 0;
     -+  struct merged_table *new_merged = NULL;
     -+
     -+  struct slice table_path = {};
     -+
     -+  while (*names) {
     -+    struct reader *rd = NULL;
     -+    char *name = *names++;
     -+
     -+    // this is linear; we assume compaction keeps the number of tables
     -+    // under control so this is not quadratic.
     -+    for (int j = 0; reuse_open && j < cur_len; j++) {
     -+      if (cur[j] != NULL && 0 == strcmp(cur[j]->name, name)) {
     -+        rd = cur[j];
     -+        cur[j] = NULL;
     -+        break;
     -+      }
     -+    }
     -+
     -+    if (rd == NULL) {
     -+      struct block_source src = {};
     -+      slice_set_string(&table_path, st->reftable_dir);
     -+      slice_append_string(&table_path, "/");
     -+      slice_append_string(&table_path, name);
     -+
     -+      err = block_source_from_file(&src, slice_as_string(&table_path));
     -+      if (err < 0) {
     -+        goto exit;
     -+      }
     -+
     -+      err = new_reader(&rd, src, name);
     -+      if (err < 0) {
     -+        goto exit;
     -+      }
     -+    }
     -+
     -+    new_tables[new_tables_len++] = rd;
     -+  }
     -+
     -+  // success!
     -+  err = new_merged_table(&new_merged, new_tables, new_tables_len);
     -+  if (err < 0) {
     -+    goto exit;
     -+  }
     -+
     -+  new_tables = NULL;
     -+  new_tables_len = 0;
     -+  if (st->merged != NULL) {
     -+    merged_table_clear(st->merged);
     -+    merged_table_free(st->merged);
     -+  }
     -+  st->merged = new_merged;
     -+
     -+  for (int i = 0; i < cur_len; i++) {
     -+    if (cur[i] != NULL) {
     -+      reader_close(cur[i]);
     -+      reader_free(cur[i]);
     -+    }
     -+  }
     -+
     ++void stack_destroy(struct stack *st)
     ++{
     ++	if (st->merged == NULL) {
     ++		return;
     ++	}
     ++
     ++	merged_table_close(st->merged);
     ++	merged_table_free(st->merged);
     ++	st->merged = NULL;
     ++
     ++	free(st->list_file);
     ++	st->list_file = NULL;
     ++	free(st->reftable_dir);
     ++	st->reftable_dir = NULL;
     ++	free(st);
     ++}
     ++
     ++static struct reader **stack_copy_readers(struct stack *st, int cur_len)
     ++{
     ++	struct reader **cur = calloc(sizeof(struct reader *), cur_len);
     ++	int i = 0;
     ++	for (i = 0; i < cur_len; i++) {
     ++		cur[i] = st->merged->stack[i];
     ++	}
     ++	return cur;
     ++}
     ++
     ++static int stack_reload_once(struct stack *st, char **names, bool reuse_open)
     ++{
     ++	int cur_len = st->merged == NULL ? 0 : st->merged->stack_len;
     ++	struct reader **cur = stack_copy_readers(st, cur_len);
     ++	int err = 0;
     ++	int names_len = names_length(names);
     ++	struct reader **new_tables =
     ++		malloc(sizeof(struct reader *) * names_len);
     ++	int new_tables_len = 0;
     ++	struct merged_table *new_merged = NULL;
     ++
     ++	struct slice table_path = {};
     ++
     ++	while (*names) {
     ++		struct reader *rd = NULL;
     ++		char *name = *names++;
     ++
     ++		/* this is linear; we assume compaction keeps the number of
     ++		   tables under control so this is not quadratic. */
     ++		int j = 0;
     ++		for (j = 0; reuse_open && j < cur_len; j++) {
     ++			if (cur[j] != NULL && 0 == strcmp(cur[j]->name, name)) {
     ++				rd = cur[j];
     ++				cur[j] = NULL;
     ++				break;
     ++			}
     ++		}
     ++
     ++		if (rd == NULL) {
     ++			struct block_source src = {};
     ++			slice_set_string(&table_path, st->reftable_dir);
     ++			slice_append_string(&table_path, "/");
     ++			slice_append_string(&table_path, name);
     ++
     ++			err = block_source_from_file(
     ++				&src, slice_as_string(&table_path));
     ++			if (err < 0) {
     ++				goto exit;
     ++			}
     ++
     ++			err = new_reader(&rd, src, name);
     ++			if (err < 0) {
     ++				goto exit;
     ++			}
     ++		}
     ++
     ++		new_tables[new_tables_len++] = rd;
     ++	}
     ++
     ++	/* success! */
     ++	err = new_merged_table(&new_merged, new_tables, new_tables_len);
     ++	if (err < 0) {
     ++		goto exit;
     ++	}
     ++
     ++	new_tables = NULL;
     ++	new_tables_len = 0;
     ++	if (st->merged != NULL) {
     ++		merged_table_clear(st->merged);
     ++		merged_table_free(st->merged);
     ++	}
     ++	st->merged = new_merged;
     ++
     ++	{
     ++		int i = 0;
     ++		for (i = 0; i < cur_len; i++) {
     ++			if (cur[i] != NULL) {
     ++				reader_close(cur[i]);
     ++				reader_free(cur[i]);
     ++			}
     ++		}
     ++	}
      +exit:
     -+  free(slice_yield(&table_path));
     -+  for (int i = 0; i < new_tables_len; i++) {
     -+    reader_close(new_tables[i]);
     -+  }
     -+  free(new_tables);
     -+  free(cur);
     -+  return err;
     -+}
     -+
     -+// return negative if a before b.
     -+static int tv_cmp(struct timeval *a, struct timeval *b) {
     -+  time_t diff = a->tv_sec - b->tv_sec;
     -+  suseconds_t udiff = a->tv_usec - b->tv_usec;
     -+
     -+  if (diff != 0) {
     -+    return diff;
     -+  }
     -+
     -+  return udiff;
     -+}
     -+
     -+static int stack_reload_maybe_reuse(struct stack *st, bool reuse_open) {
     -+  struct timeval deadline = {};
     -+  int err = gettimeofday(&deadline, NULL);
     -+  int64_t delay = 0;
     -+  int tries = 0;
     -+  if (err < 0) {
     -+    return err;
     -+  }
     -+
     -+  deadline.tv_sec += 3;
     -+  while (true) {
     -+    char **names = NULL;
     -+    char **names_after = NULL;
     -+    struct timeval now = {};
     -+    int err = gettimeofday(&now, NULL);
     -+    if (err < 0) {
     -+      return err;
     -+    }
     -+
     -+    // Only look at deadlines after the first few times. This
     -+    // simplifies debugging in GDB
     -+    tries++;
     -+    if (tries > 3 && tv_cmp(&now, &deadline) >= 0) {
     -+      break;
     -+    }
     -+
     -+    err = read_lines(st->list_file, &names);
     -+    if (err < 0) {
     -+      free_names(names);
     -+      return err;
     -+    }
     -+    err = stack_reload_once(st, names, reuse_open);
     -+    if (err == 0) {
     -+      free_names(names);
     -+      break;
     -+    }
     -+    if (err != NOT_EXIST_ERROR) {
     -+      free_names(names);
     -+      return err;
     -+    }
     -+
     -+    err = read_lines(st->list_file, &names_after);
     -+    if (err < 0) {
     -+      free_names(names);
     -+      return err;
     -+    }
     -+
     -+    if (names_equal(names_after, names)) {
     -+      free_names(names);
     -+      free_names(names_after);
     -+      return -1;
     -+    }
     -+    free_names(names);
     -+    free_names(names_after);
     -+
     -+    delay = delay + (delay * rand()) / RAND_MAX + 100;
     -+    usleep(delay);
     -+  }
     -+
     -+  return 0;
     -+}
     -+
     -+int stack_reload(struct stack *st) {
     -+  return stack_reload_maybe_reuse(st, true);
     -+}
     -+
     -+// -1 = error
     -+// 0 = up to date
     -+// 1 = changed.
     -+static int stack_uptodate(struct stack *st) {
     -+  char **names = NULL;
     -+  int err = read_lines(st->list_file, &names);
     -+  if (err < 0) {
     -+    return err;
     -+  }
     -+
     -+  for (int i = 0; i < st->merged->stack_len; i++) {
     -+    if (names[i] == NULL) {
     -+      err = 1;
     -+      goto exit;
     -+    }
     -+
     -+    if (0 != strcmp(st->merged->stack[i]->name, names[i])) {
     -+      err = 1;
     -+      goto exit;
     -+    }
     -+  }
     -+
     -+  if (names[st->merged->stack_len] != NULL) {
     -+    err = 1;
     -+    goto exit;
     -+  }
     ++	free(slice_yield(&table_path));
     ++	{
     ++		int i = 0;
     ++		for (i = 0; i < new_tables_len; i++) {
     ++			reader_close(new_tables[i]);
     ++		}
     ++	}
     ++	free(new_tables);
     ++	free(cur);
     ++	return err;
     ++}
     ++
     ++/* return negative if a before b. */
     ++static int tv_cmp(struct timeval *a, struct timeval *b)
     ++{
     ++	time_t diff = a->tv_sec - b->tv_sec;
     ++	int udiff = a->tv_usec - b->tv_usec;
     ++
     ++	if (diff != 0) {
     ++		return diff;
     ++	}
     ++
     ++	return udiff;
     ++}
     ++
     ++static int stack_reload_maybe_reuse(struct stack *st, bool reuse_open)
     ++{
     ++	struct timeval deadline = {};
     ++	int err = gettimeofday(&deadline, NULL);
     ++	int64_t delay = 0;
     ++	int tries = 0;
     ++	if (err < 0) {
     ++		return err;
     ++	}
     ++
     ++	deadline.tv_sec += 3;
     ++	while (true) {
     ++		char **names = NULL;
     ++		char **names_after = NULL;
     ++		struct timeval now = {};
     ++		int err = gettimeofday(&now, NULL);
     ++		if (err < 0) {
     ++			return err;
     ++		}
     ++
     ++		/* Only look at deadlines after the first few times. This
     ++		   simplifies debugging in GDB */
     ++		tries++;
     ++		if (tries > 3 && tv_cmp(&now, &deadline) >= 0) {
     ++			break;
     ++		}
     ++
     ++		err = read_lines(st->list_file, &names);
     ++		if (err < 0) {
     ++			free_names(names);
     ++			return err;
     ++		}
     ++		err = stack_reload_once(st, names, reuse_open);
     ++		if (err == 0) {
     ++			free_names(names);
     ++			break;
     ++		}
     ++		if (err != NOT_EXIST_ERROR) {
     ++			free_names(names);
     ++			return err;
     ++		}
     ++
     ++		err = read_lines(st->list_file, &names_after);
     ++		if (err < 0) {
     ++			free_names(names);
     ++			return err;
     ++		}
     ++
     ++		if (names_equal(names_after, names)) {
     ++			free_names(names);
     ++			free_names(names_after);
     ++			return -1;
     ++		}
     ++		free_names(names);
     ++		free_names(names_after);
     ++
     ++		delay = delay + (delay * rand()) / RAND_MAX + 100;
     ++		usleep(delay);
     ++	}
     ++
     ++	return 0;
     ++}
     ++
     ++int stack_reload(struct stack *st)
     ++{
     ++	return stack_reload_maybe_reuse(st, true);
     ++}
     ++
     ++/* -1 = error
     ++ 0 = up to date
     ++ 1 = changed. */
     ++static int stack_uptodate(struct stack *st)
     ++{
     ++	char **names = NULL;
     ++	int err = read_lines(st->list_file, &names);
     ++	int i = 0;
     ++	if (err < 0) {
     ++		return err;
     ++	}
     ++
     ++	for (i = 0; i < st->merged->stack_len; i++) {
     ++		if (names[i] == NULL) {
     ++			err = 1;
     ++			goto exit;
     ++		}
     ++
     ++		if (strcmp(st->merged->stack[i]->name, names[i])) {
     ++			err = 1;
     ++			goto exit;
     ++		}
     ++	}
     ++
     ++	if (names[st->merged->stack_len] != NULL) {
     ++		err = 1;
     ++		goto exit;
     ++	}
      +
      +exit:
     -+  free_names(names);
     -+  return err;
     ++	free_names(names);
     ++	return err;
      +}
      +
      +int stack_add(struct stack *st, int (*write)(struct writer *wr, void *arg),
     -+              void *arg) {
     -+  int err = stack_try_add(st, write, arg);
     -+  if (err < 0) {
     -+    if (err == LOCK_ERROR) {
     -+      err = stack_reload(st);
     -+    }
     -+    return err;
     -+  }
     ++	      void *arg)
     ++{
     ++	int err = stack_try_add(st, write, arg);
     ++	if (err < 0) {
     ++		if (err == LOCK_ERROR) {
     ++			err = stack_reload(st);
     ++		}
     ++		return err;
     ++	}
      +
     -+  return stack_auto_compact(st);
     ++	return stack_auto_compact(st);
      +}
      +
     -+static void format_name(struct slice *dest, uint64_t min, uint64_t max) {
     -+  char buf[1024];
     -+  sprintf(buf, "%012lx-%012lx", min, max);
     -+  slice_set_string(dest, buf);
     ++static void format_name(struct slice *dest, uint64_t min, uint64_t max)
     ++{
     ++	char buf[100];
     ++	snprintf(buf, sizeof(buf), "%012lx-%012lx", min, max);
     ++	slice_set_string(dest, buf);
      +}
      +
      +int stack_try_add(struct stack *st,
     -+                  int (*write_table)(struct writer *wr, void *arg), void *arg) {
     -+  struct slice lock_name = {};
     -+  struct slice temp_tab_name = {};
     -+  struct slice tab_name = {};
     -+  struct slice next_name = {};
     -+  struct slice table_list = {};
     -+  struct writer *wr = NULL;
     -+  int err = 0;
     -+  int tab_fd = 0;
     -+  int lock_fd = 0;
     -+  uint64_t next_update_index = 0;
     -+
     -+  slice_set_string(&lock_name, st->list_file);
     -+  slice_append_string(&lock_name, ".lock");
     -+
     -+  lock_fd =
     -+      open(slice_as_string(&lock_name), O_EXCL | O_CREAT | O_WRONLY, 0644);
     -+  if (lock_fd < 0) {
     -+    if (errno == EEXIST) {
     -+      err = LOCK_ERROR;
     -+      goto exit;
     -+    }
     -+    err = IO_ERROR;
     -+    goto exit;
     -+  }
     -+
     -+  err = stack_uptodate(st);
     -+  if (err < 0) {
     -+    goto exit;
     -+  }
     -+
     -+  if (err > 1) {
     -+    err = LOCK_ERROR;
     -+    goto exit;
     -+  }
     -+
     -+  next_update_index = stack_next_update_index(st);
     -+
     -+  slice_resize(&next_name, 0);
     -+  format_name(&next_name, next_update_index, next_update_index);
     -+
     -+  slice_set_string(&temp_tab_name, st->reftable_dir);
     -+  slice_append_string(&temp_tab_name, "/");
     -+  slice_append(&temp_tab_name, next_name);
     -+  slice_append_string(&temp_tab_name, "XXXXXX");
     -+
     -+  tab_fd = mkstemp((char *)slice_as_string(&temp_tab_name));
     -+  if (tab_fd < 0) {
     -+    err = IO_ERROR;
     -+    goto exit;
     -+  }
     -+
     -+  wr = new_writer(fd_writer, &tab_fd, &st->config);
     -+  err = write_table(wr, arg);
     -+  if (err < 0) {
     -+    goto exit;
     -+  }
     -+
     -+  err = writer_close(wr);
     -+  if (err < 0) {
     -+    goto exit;
     -+  }
     -+
     -+  err = close(tab_fd);
     -+  tab_fd = 0;
     -+  if (err < 0) {
     -+    err = IO_ERROR;
     -+    goto exit;
     -+  }
     -+
     -+  if (wr->min_update_index < next_update_index) {
     -+    err = API_ERROR;
     -+    goto exit;
     -+  }
     -+
     -+  for (int i = 0; i < st->merged->stack_len; i++) {
     -+    slice_append_string(&table_list, st->merged->stack[i]->name);
     -+    slice_append_string(&table_list, "\n");
     -+  }
     -+
     -+  format_name(&next_name, wr->min_update_index, wr->max_update_index);
     -+  slice_append_string(&next_name, ".ref");
     -+  slice_append(&table_list, next_name);
     -+  slice_append_string(&table_list, "\n");
     -+
     -+  slice_set_string(&tab_name, st->reftable_dir);
     -+  slice_append_string(&tab_name, "/");
     -+  slice_append(&tab_name, next_name);
     -+
     -+  err = rename(slice_as_string(&temp_tab_name), slice_as_string(&tab_name));
     -+  if (err < 0) {
     -+    err = IO_ERROR;
     -+    goto exit;
     -+  }
     -+  free(slice_yield(&temp_tab_name));
     -+
     -+  err = write(lock_fd, table_list.buf, table_list.len);
     -+  if (err < 0) {
     -+    err = IO_ERROR;
     -+    goto exit;
     -+  }
     -+  err = close(lock_fd);
     -+  lock_fd = 0;
     -+  if (err < 0) {
     -+    unlink(slice_as_string(&tab_name));
     -+    err = IO_ERROR;
     -+    goto exit;
     -+  }
     -+
     -+  err = rename(slice_as_string(&lock_name), st->list_file);
     -+  if (err < 0) {
     -+    unlink(slice_as_string(&tab_name));
     -+    err = IO_ERROR;
     -+    goto exit;
     -+  }
     -+
     -+  err = stack_reload(st);
     ++		  int (*write_table)(struct writer *wr, void *arg), void *arg)
     ++{
     ++	struct slice lock_name = {};
     ++	struct slice temp_tab_name = {};
     ++	struct slice tab_name = {};
     ++	struct slice next_name = {};
     ++	struct slice table_list = {};
     ++	struct writer *wr = NULL;
     ++	int err = 0;
     ++	int tab_fd = 0;
     ++	int lock_fd = 0;
     ++	uint64_t next_update_index = 0;
     ++
     ++	slice_set_string(&lock_name, st->list_file);
     ++	slice_append_string(&lock_name, ".lock");
     ++
     ++	lock_fd = open(slice_as_string(&lock_name), O_EXCL | O_CREAT | O_WRONLY,
     ++		       0644);
     ++	if (lock_fd < 0) {
     ++		if (errno == EEXIST) {
     ++			err = LOCK_ERROR;
     ++			goto exit;
     ++		}
     ++		err = IO_ERROR;
     ++		goto exit;
     ++	}
     ++
     ++	err = stack_uptodate(st);
     ++	if (err < 0) {
     ++		goto exit;
     ++	}
     ++
     ++	if (err > 1) {
     ++		err = LOCK_ERROR;
     ++		goto exit;
     ++	}
     ++
     ++	next_update_index = stack_next_update_index(st);
     ++
     ++	slice_resize(&next_name, 0);
     ++	format_name(&next_name, next_update_index, next_update_index);
     ++
     ++	slice_set_string(&temp_tab_name, st->reftable_dir);
     ++	slice_append_string(&temp_tab_name, "/");
     ++	slice_append(&temp_tab_name, next_name);
     ++	slice_append_string(&temp_tab_name, ".temp.XXXXXX");
     ++
     ++	tab_fd = mkstemp((char *)slice_as_string(&temp_tab_name));
     ++	if (tab_fd < 0) {
     ++		err = IO_ERROR;
     ++		goto exit;
     ++	}
     ++
     ++	wr = new_writer(fd_writer, &tab_fd, &st->config);
     ++	err = write_table(wr, arg);
     ++	if (err < 0) {
     ++		goto exit;
     ++	}
     ++
     ++	err = writer_close(wr);
     ++	if (err < 0) {
     ++		goto exit;
     ++	}
     ++
     ++	err = close(tab_fd);
     ++	tab_fd = 0;
     ++	if (err < 0) {
     ++		err = IO_ERROR;
     ++		goto exit;
     ++	}
     ++
     ++	if (wr->min_update_index < next_update_index) {
     ++		err = API_ERROR;
     ++		goto exit;
     ++	}
     ++
     ++	{
     ++		int i = 0;
     ++		for (i = 0; i < st->merged->stack_len; i++) {
     ++			slice_append_string(&table_list,
     ++					    st->merged->stack[i]->name);
     ++			slice_append_string(&table_list, "\n");
     ++		}
     ++	}
     ++
     ++	format_name(&next_name, wr->min_update_index, wr->max_update_index);
     ++	slice_append_string(&next_name, ".ref");
     ++	slice_append(&table_list, next_name);
     ++	slice_append_string(&table_list, "\n");
     ++
     ++	slice_set_string(&tab_name, st->reftable_dir);
     ++	slice_append_string(&tab_name, "/");
     ++	slice_append(&tab_name, next_name);
     ++
     ++	err = rename(slice_as_string(&temp_tab_name),
     ++		     slice_as_string(&tab_name));
     ++	if (err < 0) {
     ++		err = IO_ERROR;
     ++		goto exit;
     ++	}
     ++	free(slice_yield(&temp_tab_name));
     ++
     ++	err = write(lock_fd, table_list.buf, table_list.len);
     ++	if (err < 0) {
     ++		err = IO_ERROR;
     ++		goto exit;
     ++	}
     ++	err = close(lock_fd);
     ++	lock_fd = 0;
     ++	if (err < 0) {
     ++		unlink(slice_as_string(&tab_name));
     ++		err = IO_ERROR;
     ++		goto exit;
     ++	}
     ++
     ++	err = rename(slice_as_string(&lock_name), st->list_file);
     ++	if (err < 0) {
     ++		unlink(slice_as_string(&tab_name));
     ++		err = IO_ERROR;
     ++		goto exit;
     ++	}
     ++
     ++	err = stack_reload(st);
      +exit:
     -+  if (tab_fd > 0) {
     -+    close(tab_fd);
     -+    tab_fd = 0;
     -+  }
     -+  if (temp_tab_name.len > 0) {
     -+    unlink(slice_as_string(&temp_tab_name));
     -+  }
     -+  unlink(slice_as_string(&lock_name));
     -+
     -+  if (lock_fd > 0) {
     -+    close(lock_fd);
     -+    lock_fd = 0;
     -+  }
     -+
     -+  free(slice_yield(&lock_name));
     -+  free(slice_yield(&temp_tab_name));
     -+  free(slice_yield(&tab_name));
     -+  free(slice_yield(&next_name));
     -+  free(slice_yield(&table_list));
     -+  writer_free(wr);
     -+  return err;
     -+}
     -+
     -+uint64_t stack_next_update_index(struct stack *st) {
     -+  int sz = st->merged->stack_len;
     -+  if (sz > 0) {
     -+    return reader_max_update_index(st->merged->stack[sz - 1]) + 1;
     -+  }
     -+  return 1;
     ++	if (tab_fd > 0) {
     ++		close(tab_fd);
     ++		tab_fd = 0;
     ++	}
     ++	if (temp_tab_name.len > 0) {
     ++		unlink(slice_as_string(&temp_tab_name));
     ++	}
     ++	unlink(slice_as_string(&lock_name));
     ++
     ++	if (lock_fd > 0) {
     ++		close(lock_fd);
     ++		lock_fd = 0;
     ++	}
     ++
     ++	free(slice_yield(&lock_name));
     ++	free(slice_yield(&temp_tab_name));
     ++	free(slice_yield(&tab_name));
     ++	free(slice_yield(&next_name));
     ++	free(slice_yield(&table_list));
     ++	writer_free(wr);
     ++	return err;
     ++}
     ++
     ++uint64_t stack_next_update_index(struct stack *st)
     ++{
     ++	int sz = st->merged->stack_len;
     ++	if (sz > 0) {
     ++		return reader_max_update_index(st->merged->stack[sz - 1]) + 1;
     ++	}
     ++	return 1;
      +}
      +
      +static int stack_compact_locked(struct stack *st, int first, int last,
     -+                                struct slice *temp_tab, struct log_expiry_config *config) {
     -+  struct slice next_name = {};
     -+  int tab_fd = -1;
     -+  struct writer *wr = NULL;
     -+  int err = 0;
     -+
     -+  format_name(&next_name, reader_min_update_index(st->merged->stack[first]),
     -+              reader_max_update_index(st->merged->stack[first]));
     -+
     -+  slice_set_string(temp_tab, st->reftable_dir);
     -+  slice_append_string(temp_tab, "/");
     -+  slice_append(temp_tab, next_name);
     -+  slice_append_string(temp_tab, "XXXXXX");
     -+
     -+  tab_fd = mkstemp((char *)slice_as_string(temp_tab));
     -+  wr = new_writer(fd_writer, &tab_fd, &st->config);
     -+
     -+  err = stack_write_compact(st, wr, first, last, config);
     -+  if (err < 0) {
     -+    goto exit;
     -+  }
     -+  err = writer_close(wr);
     -+  if (err < 0) {
     -+    goto exit;
     -+  }
     -+  writer_free(wr);
     -+
     -+  err = close(tab_fd);
     -+  tab_fd = 0;
     ++				struct slice *temp_tab,
     ++				struct log_expiry_config *config)
     ++{
     ++	struct slice next_name = {};
     ++	int tab_fd = -1;
     ++	struct writer *wr = NULL;
     ++	int err = 0;
     ++
     ++	format_name(&next_name,
     ++		    reader_min_update_index(st->merged->stack[first]),
     ++		    reader_max_update_index(st->merged->stack[first]));
     ++
     ++	slice_set_string(temp_tab, st->reftable_dir);
     ++	slice_append_string(temp_tab, "/");
     ++	slice_append(temp_tab, next_name);
     ++	slice_append_string(temp_tab, ".temp.XXXXXX");
     ++
     ++	tab_fd = mkstemp((char *)slice_as_string(temp_tab));
     ++	wr = new_writer(fd_writer, &tab_fd, &st->config);
     ++
     ++	err = stack_write_compact(st, wr, first, last, config);
     ++	if (err < 0) {
     ++		goto exit;
     ++	}
     ++	err = writer_close(wr);
     ++	if (err < 0) {
     ++		goto exit;
     ++	}
     ++	writer_free(wr);
     ++
     ++	err = close(tab_fd);
     ++	tab_fd = 0;
      +
      +exit:
     -+  if (tab_fd > 0) {
     -+    close(tab_fd);
     -+    tab_fd = 0;
     -+  }
     -+  if (err != 0 && temp_tab->len > 0) {
     -+    unlink(slice_as_string(temp_tab));
     -+    free(slice_yield(temp_tab));
     -+  }
     -+  free(slice_yield(&next_name));
     -+  return err;
     ++	if (tab_fd > 0) {
     ++		close(tab_fd);
     ++		tab_fd = 0;
     ++	}
     ++	if (err != 0 && temp_tab->len > 0) {
     ++		unlink(slice_as_string(temp_tab));
     ++		free(slice_yield(temp_tab));
     ++	}
     ++	free(slice_yield(&next_name));
     ++	return err;
      +}
      +
      +int stack_write_compact(struct stack *st, struct writer *wr, int first,
     -+                        int last, struct log_expiry_config *config) {
     -+  int subtabs_len = last - first + 1;
     -+  struct reader **subtabs = calloc(sizeof(struct reader *), last - first + 1);
     -+  struct merged_table *mt = NULL;
     -+  int err = 0;
     -+  struct iterator it = {};
     -+  struct ref_record ref = {};
     -+  struct log_record log = {};
     -+
     -+  for (int i = first, j = 0; i <= last; i++) {
     -+    struct reader *t = st->merged->stack[i];
     -+    subtabs[j++] = t;
     -+    st->stats.bytes += t->size;
     -+  }
     -+  writer_set_limits(wr, st->merged->stack[first]->min_update_index,
     -+                    st->merged->stack[last]->max_update_index);
     -+
     -+  err = new_merged_table(&mt, subtabs, subtabs_len);
     -+  if (err < 0) {
     -+    free(subtabs);
     -+    goto exit;
     -+  }
     -+
     -+  err = merged_table_seek_ref(mt, &it, "");
     -+  if (err < 0) {
     -+    goto exit;
     -+  }
     -+
     -+  while (true) {
     -+    err = iterator_next_ref(it, &ref);
     -+    if (err > 0) {
     -+      err = 0;
     -+      break;
     -+    }
     -+    if (err < 0) {
     -+      break;
     -+    }
     -+    if (first == 0 && ref_record_is_deletion(&ref)) {
     -+      continue;
     -+    }
     -+
     -+    err = writer_add_ref(wr, &ref);
     -+    if (err < 0) {
     -+      break;
     -+    }
     -+  }
     -+
     -+  err = merged_table_seek_log(mt, &it, "");
     -+  if (err < 0) {
     -+    goto exit;
     -+  }
     -+
     -+  while (true) {
     -+    err = iterator_next_log(it, &log);
     -+    if (err > 0) {
     -+      err = 0;
     -+      break;
     -+    }
     -+    if (err < 0) {
     -+      break;
     -+    }
     -+    if (first == 0 && log_record_is_deletion(&log)) {
     -+      continue;
     -+    }
     -+
     -+    // XXX collect stats?
     -+
     -+    if (config != NULL && config->time > 0 && log.time < config->time) {
     -+      continue;
     -+    }
     -+
     -+    if (config != NULL && config->min_update_index > 0 && log.update_index < config->min_update_index) {
     -+      continue;
     -+    }
     -+
     -+    err = writer_add_log(wr, &log);
     -+    if (err < 0) {
     -+      break;
     -+    }
     -+  }
     -+
     ++			int last, struct log_expiry_config *config)
     ++{
     ++	int subtabs_len = last - first + 1;
     ++	struct reader **subtabs =
     ++		calloc(sizeof(struct reader *), last - first + 1);
     ++	struct merged_table *mt = NULL;
     ++	int err = 0;
     ++	struct iterator it = {};
     ++	struct ref_record ref = {};
     ++	struct log_record log = {};
     ++
     ++	int i = 0, j = 0;
     ++	for (i = first, j = 0; i <= last; i++) {
     ++		struct reader *t = st->merged->stack[i];
     ++		subtabs[j++] = t;
     ++		st->stats.bytes += t->size;
     ++	}
     ++	writer_set_limits(wr, st->merged->stack[first]->min_update_index,
     ++			  st->merged->stack[last]->max_update_index);
     ++
     ++	err = new_merged_table(&mt, subtabs, subtabs_len);
     ++	if (err < 0) {
     ++		free(subtabs);
     ++		goto exit;
     ++	}
     ++
     ++	err = merged_table_seek_ref(mt, &it, "");
     ++	if (err < 0) {
     ++		goto exit;
     ++	}
     ++
     ++	while (true) {
     ++		err = iterator_next_ref(it, &ref);
     ++		if (err > 0) {
     ++			err = 0;
     ++			break;
     ++		}
     ++		if (err < 0) {
     ++			break;
     ++		}
     ++		if (first == 0 && ref_record_is_deletion(&ref)) {
     ++			continue;
     ++		}
     ++
     ++		err = writer_add_ref(wr, &ref);
     ++		if (err < 0) {
     ++			break;
     ++		}
     ++	}
     ++
     ++	err = merged_table_seek_log(mt, &it, "");
     ++	if (err < 0) {
     ++		goto exit;
     ++	}
     ++
     ++	while (true) {
     ++		err = iterator_next_log(it, &log);
     ++		if (err > 0) {
     ++			err = 0;
     ++			break;
     ++		}
     ++		if (err < 0) {
     ++			break;
     ++		}
     ++		if (first == 0 && log_record_is_deletion(&log)) {
     ++			continue;
     ++		}
     ++
     ++		/* XXX collect stats? */
     ++
     ++		if (config != NULL && config->time > 0 &&
     ++		    log.time < config->time) {
     ++			continue;
     ++		}
     ++
     ++		if (config != NULL && config->min_update_index > 0 &&
     ++		    log.update_index < config->min_update_index) {
     ++			continue;
     ++		}
     ++
     ++		err = writer_add_log(wr, &log);
     ++		if (err < 0) {
     ++			break;
     ++		}
     ++	}
      +
      +exit:
     -+  iterator_destroy(&it);
     -+  if (mt != NULL) {
     -+    merged_table_clear(mt);
     -+    merged_table_free(mt);
     -+  }
     -+  ref_record_clear(&ref);
     -+
     -+  return err;
     -+}
     -+
     -+// <  0: error. 0 == OK, > 0 attempt failed; could retry.
     -+static int stack_compact_range(struct stack *st, int first, int last, struct log_expiry_config *expiry) {
     -+  struct slice temp_tab_name = {};
     -+  struct slice new_table_name = {};
     -+  struct slice lock_file_name = {};
     -+  struct slice ref_list_contents = {};
     -+  struct slice new_table_path = {};
     -+  int err = 0;
     -+  bool have_lock = false;
     -+  int lock_file_fd = 0;
     -+  int compact_count = last - first + 1;
     -+  char **delete_on_success = calloc(sizeof(char *), compact_count + 1);
     -+  char **subtable_locks = calloc(sizeof(char *), compact_count + 1);
     -+
     -+  if (first > last || (expiry == NULL && first == last)) {
     -+    err = 0;
     -+    goto exit;
     -+  }
     -+
     -+  st->stats.attempts++;
     -+
     -+  slice_set_string(&lock_file_name, st->list_file);
     -+  slice_append_string(&lock_file_name, ".lock");
     -+
     -+  lock_file_fd =
     -+      open(slice_as_string(&lock_file_name), O_EXCL | O_CREAT | O_WRONLY, 0644);
     -+  if (lock_file_fd < 0) {
     -+    if (errno == EEXIST) {
     -+      err = 1;
     -+    } else {
     -+      err = IO_ERROR;
     -+    }
     -+    goto exit;
     -+  }
     -+  have_lock = true;
     -+  err = stack_uptodate(st);
     -+  if (err != 0) {
     -+    goto exit;
     -+  }
     -+
     -+  for (int i = first, j = 0; i <= last; i++) {
     -+    struct slice subtab_name = {};
     -+    struct slice subtab_lock = {};
     -+    slice_set_string(&subtab_name, st->reftable_dir);
     -+    slice_append_string(&subtab_name, "/");
     -+    slice_append_string(&subtab_name, reader_name(st->merged->stack[i]));
     -+
     -+    slice_copy(&subtab_lock, subtab_name);
     -+    slice_append_string(&subtab_lock, ".lock");
     -+
     -+    {
     -+      int sublock_file_fd = open(slice_as_string(&subtab_lock),
     -+                                 O_EXCL | O_CREAT | O_WRONLY, 0644);
     -+      if (sublock_file_fd > 0) {
     -+        close(sublock_file_fd);
     -+      } else if (sublock_file_fd < 0) {
     -+        if (errno == EEXIST) {
     -+          err = 1;
     -+        }
     -+        err = IO_ERROR;
     -+      }
     -+    }
     -+
     -+    subtable_locks[j] = (char *)slice_as_string(&subtab_lock);
     -+    delete_on_success[j] = (char *)slice_as_string(&subtab_name);
     -+    j++;
     -+
     -+    if (err != 0) {
     -+      goto exit;
     -+    }
     -+  }
     -+
     -+  err = unlink(slice_as_string(&lock_file_name));
     -+  if (err < 0) {
     -+    goto exit;
     -+  }
     -+  have_lock = false;
     -+
     -+  err = stack_compact_locked(st, first, last, &temp_tab_name, expiry);
     -+  if (err < 0) {
     -+    goto exit;
     -+  }
     -+
     -+  lock_file_fd =
     -+      open(slice_as_string(&lock_file_name), O_EXCL | O_CREAT | O_WRONLY, 0644);
     -+  if (lock_file_fd < 0) {
     -+    if (errno == EEXIST) {
     -+      err = 1;
     -+    } else {
     -+      err = IO_ERROR;
     -+    }
     -+    goto exit;
     -+  }
     -+  have_lock = true;
     -+
     -+  format_name(&new_table_name, st->merged->stack[first]->min_update_index,
     -+              st->merged->stack[last]->max_update_index);
     -+  slice_append_string(&new_table_name, ".ref");
     -+
     -+  slice_set_string(&new_table_path, st->reftable_dir);
     -+  slice_append_string(&new_table_path, "/");
     -+
     -+  slice_append(&new_table_path, new_table_name);
     -+
     -+  err =
     -+      rename(slice_as_string(&temp_tab_name), slice_as_string(&new_table_path));
     -+  if (err < 0) {
     -+    goto exit;
     -+  }
     -+
     -+  for (int i = 0; i < first; i++) {
     -+    slice_append_string(&ref_list_contents, st->merged->stack[i]->name);
     -+    slice_append_string(&ref_list_contents, "\n");
     -+  }
     -+  slice_append(&ref_list_contents, new_table_name);
     -+  slice_append_string(&ref_list_contents, "\n");
     -+  for (int i = last + 1; i < st->merged->stack_len; i++) {
     -+    slice_append_string(&ref_list_contents, st->merged->stack[i]->name);
     -+    slice_append_string(&ref_list_contents, "\n");
     -+  }
     -+
     -+  err = write(lock_file_fd, ref_list_contents.buf, ref_list_contents.len);
     -+  if (err < 0) {
     -+    unlink(slice_as_string(&new_table_path));
     -+    goto exit;
     -+  }
     -+  err = close(lock_file_fd);
     -+  lock_file_fd = 0;
     -+  if (err < 0) {
     -+    unlink(slice_as_string(&new_table_path));
     -+    goto exit;
     -+  }
     -+
     -+  err = rename(slice_as_string(&lock_file_name), st->list_file);
     -+  if (err < 0) {
     -+    unlink(slice_as_string(&new_table_path));
     -+    goto exit;
     -+  }
     -+  have_lock = false;
     -+
     -+  for (char **p = delete_on_success; *p; p++) {
     -+    if (0 != strcmp(*p, slice_as_string(&new_table_path))) {
     -+      unlink(*p);
     -+    }
     -+  }
     -+
     -+  err = stack_reload_maybe_reuse(st, first < last);
     ++	iterator_destroy(&it);
     ++	if (mt != NULL) {
     ++		merged_table_clear(mt);
     ++		merged_table_free(mt);
     ++	}
     ++	ref_record_clear(&ref);
     ++
     ++	return err;
     ++}
     ++
     ++/* <  0: error. 0 == OK, > 0 attempt failed; could retry. */
     ++static int stack_compact_range(struct stack *st, int first, int last,
     ++			       struct log_expiry_config *expiry)
     ++{
     ++	struct slice temp_tab_name = {};
     ++	struct slice new_table_name = {};
     ++	struct slice lock_file_name = {};
     ++	struct slice ref_list_contents = {};
     ++	struct slice new_table_path = {};
     ++	int err = 0;
     ++	bool have_lock = false;
     ++	int lock_file_fd = 0;
     ++	int compact_count = last - first + 1;
     ++	char **delete_on_success = calloc(sizeof(char *), compact_count + 1);
     ++	char **subtable_locks = calloc(sizeof(char *), compact_count + 1);
     ++	int i = 0;
     ++	int j = 0;
     ++
     ++	if (first > last || (expiry == NULL && first == last)) {
     ++		err = 0;
     ++		goto exit;
     ++	}
     ++
     ++	st->stats.attempts++;
     ++
     ++	slice_set_string(&lock_file_name, st->list_file);
     ++	slice_append_string(&lock_file_name, ".lock");
     ++
     ++	lock_file_fd = open(slice_as_string(&lock_file_name),
     ++			    O_EXCL | O_CREAT | O_WRONLY, 0644);
     ++	if (lock_file_fd < 0) {
     ++		if (errno == EEXIST) {
     ++			err = 1;
     ++		} else {
     ++			err = IO_ERROR;
     ++		}
     ++		goto exit;
     ++	}
     ++	have_lock = true;
     ++	err = stack_uptodate(st);
     ++	if (err != 0) {
     ++		goto exit;
     ++	}
     ++
     ++	for (i = first, j = 0; i <= last; i++) {
     ++		struct slice subtab_name = {};
     ++		struct slice subtab_lock = {};
     ++		slice_set_string(&subtab_name, st->reftable_dir);
     ++		slice_append_string(&subtab_name, "/");
     ++		slice_append_string(&subtab_name,
     ++				    reader_name(st->merged->stack[i]));
     ++
     ++		slice_copy(&subtab_lock, subtab_name);
     ++		slice_append_string(&subtab_lock, ".lock");
     ++
     ++		{
     ++			int sublock_file_fd =
     ++				open(slice_as_string(&subtab_lock),
     ++				     O_EXCL | O_CREAT | O_WRONLY, 0644);
     ++			if (sublock_file_fd > 0) {
     ++				close(sublock_file_fd);
     ++			} else if (sublock_file_fd < 0) {
     ++				if (errno == EEXIST) {
     ++					err = 1;
     ++				}
     ++				err = IO_ERROR;
     ++			}
     ++		}
     ++
     ++		subtable_locks[j] = (char *)slice_as_string(&subtab_lock);
     ++		delete_on_success[j] = (char *)slice_as_string(&subtab_name);
     ++		j++;
     ++
     ++		if (err != 0) {
     ++			goto exit;
     ++		}
     ++	}
     ++
     ++	err = unlink(slice_as_string(&lock_file_name));
     ++	if (err < 0) {
     ++		goto exit;
     ++	}
     ++	have_lock = false;
     ++
     ++	err = stack_compact_locked(st, first, last, &temp_tab_name, expiry);
     ++	if (err < 0) {
     ++		goto exit;
     ++	}
     ++
     ++	lock_file_fd = open(slice_as_string(&lock_file_name),
     ++			    O_EXCL | O_CREAT | O_WRONLY, 0644);
     ++	if (lock_file_fd < 0) {
     ++		if (errno == EEXIST) {
     ++			err = 1;
     ++		} else {
     ++			err = IO_ERROR;
     ++		}
     ++		goto exit;
     ++	}
     ++	have_lock = true;
     ++
     ++	format_name(&new_table_name, st->merged->stack[first]->min_update_index,
     ++		    st->merged->stack[last]->max_update_index);
     ++	slice_append_string(&new_table_name, ".ref");
     ++
     ++	slice_set_string(&new_table_path, st->reftable_dir);
     ++	slice_append_string(&new_table_path, "/");
     ++
     ++	slice_append(&new_table_path, new_table_name);
     ++
     ++	err = rename(slice_as_string(&temp_tab_name),
     ++		     slice_as_string(&new_table_path));
     ++	if (err < 0) {
     ++		goto exit;
     ++	}
     ++
     ++	for (i = 0; i < first; i++) {
     ++		slice_append_string(&ref_list_contents,
     ++				    st->merged->stack[i]->name);
     ++		slice_append_string(&ref_list_contents, "\n");
     ++	}
     ++	slice_append(&ref_list_contents, new_table_name);
     ++	slice_append_string(&ref_list_contents, "\n");
     ++	for (i = last + 1; i < st->merged->stack_len; i++) {
     ++		slice_append_string(&ref_list_contents,
     ++				    st->merged->stack[i]->name);
     ++		slice_append_string(&ref_list_contents, "\n");
     ++	}
     ++
     ++	err = write(lock_file_fd, ref_list_contents.buf, ref_list_contents.len);
     ++	if (err < 0) {
     ++		unlink(slice_as_string(&new_table_path));
     ++		goto exit;
     ++	}
     ++	err = close(lock_file_fd);
     ++	lock_file_fd = 0;
     ++	if (err < 0) {
     ++		unlink(slice_as_string(&new_table_path));
     ++		goto exit;
     ++	}
     ++
     ++	err = rename(slice_as_string(&lock_file_name), st->list_file);
     ++	if (err < 0) {
     ++		unlink(slice_as_string(&new_table_path));
     ++		goto exit;
     ++	}
     ++	have_lock = false;
     ++
     ++	for (char **p = delete_on_success; *p; p++) {
     ++		if (strcmp(*p, slice_as_string(&new_table_path))) {
     ++			unlink(*p);
     ++		}
     ++	}
     ++
     ++	err = stack_reload_maybe_reuse(st, first < last);
      +exit:
     -+  for (char **p = subtable_locks; *p; p++) {
     -+    unlink(*p);
     -+  }
     -+  free_names(delete_on_success);
     -+  free_names(subtable_locks);
     -+  if (lock_file_fd > 0) {
     -+    close(lock_file_fd);
     -+    lock_file_fd = 0;
     -+  }
     -+  if (have_lock) {
     -+    unlink(slice_as_string(&lock_file_name));
     -+  }
     -+  free(slice_yield(&new_table_name));
     -+  free(slice_yield(&new_table_path));
     -+  free(slice_yield(&ref_list_contents));
     -+  free(slice_yield(&temp_tab_name));
     -+  free(slice_yield(&lock_file_name));
     -+  return err;
     -+}
     -+
     -+int stack_compact_all(struct stack *st, struct log_expiry_config *config) {
     -+  return stack_compact_range(st, 0, st->merged->stack_len - 1, config);
     -+}
     -+
     -+static int stack_compact_range_stats(struct stack *st, int first, int last, struct log_expiry_config *config) {
     -+  int err = stack_compact_range(st, first, last, config);
     -+  if (err > 0) {
     -+    st->stats.failures++;
     -+  }
     -+  return err;
     -+}
     -+
     -+static int segment_size(struct segment *s) { return s->end - s->start; }
     -+
     -+int fastlog2(uint64_t sz) {
     -+  int l = 0;
     -+  assert(sz > 0);
     -+  for (; sz; sz /= 2) {
     -+    l++;
     -+  }
     -+  return l - 1;
     -+}
     -+
     -+struct segment *sizes_to_segments(int *seglen, uint64_t *sizes, int n) {
     -+  struct segment *segs = calloc(sizeof(struct segment), n);
     -+  int next = 0;
     -+  struct segment cur = {};
     -+  for (int i = 0; i < n; i++) {
     -+    int log = fastlog2(sizes[i]);
     -+    if (cur.log != log && cur.bytes > 0) {
     -+      struct segment fresh = {
     -+          .start = i,
     -+      };
     -+
     -+      segs[next++] = cur;
     -+      cur = fresh;
     -+    }
     -+
     -+    cur.log = log;
     -+    cur.end = i + 1;
     -+    cur.bytes += sizes[i];
     -+  }
     -+
     -+  segs[next++] = cur;
     -+  *seglen = next;
     -+  return segs;
     -+}
     -+
     -+struct segment suggest_compaction_segment(uint64_t *sizes, int n) {
     -+  int seglen = 0;
     -+  struct segment *segs = sizes_to_segments(&seglen, sizes, n);
     -+  struct segment min_seg = {
     -+      .log = 64,
     -+  };
     -+  for (int i = 0; i < seglen; i++) {
     -+    if (segment_size(&segs[i]) == 1) {
     -+      continue;
     -+    }
     -+
     -+    if (segs[i].log < min_seg.log) {
     -+      min_seg = segs[i];
     -+    }
     -+  }
     -+
     -+  while (min_seg.start > 0) {
     -+    int prev = min_seg.start - 1;
     -+    if (fastlog2(min_seg.bytes) < fastlog2(sizes[prev])) {
     -+      break;
     -+    }
     -+
     -+    min_seg.start = prev;
     -+    min_seg.bytes += sizes[prev];
     -+  }
     -+
     -+  free(segs);
     -+  return min_seg;
     -+}
     -+
     -+static uint64_t *stack_table_sizes_for_compaction(struct stack *st) {
     -+  uint64_t *sizes = calloc(sizeof(uint64_t), st->merged->stack_len);
     -+  for (int i = 0; i < st->merged->stack_len; i++) {
     -+    // overhead is 24 + 68 = 92.
     -+    sizes[i] = st->merged->stack[i]->size - 91;
     -+  }
     -+  return sizes;
     -+}
     -+
     -+int stack_auto_compact(struct stack *st) {
     -+  uint64_t *sizes = stack_table_sizes_for_compaction(st);
     -+  struct segment seg = suggest_compaction_segment(sizes, st->merged->stack_len);
     -+  free(sizes);
     -+  if (segment_size(&seg) > 0) {
     -+    return stack_compact_range_stats(st, seg.start, seg.end - 1, NULL);
     -+  }
     -+
     -+  return 0;
     -+}
     -+
     -+struct compaction_stats *stack_compaction_stats(struct stack *st) {
     -+  return &st->stats;
     ++	for (char **p = subtable_locks; *p; p++) {
     ++		unlink(*p);
     ++	}
     ++	free_names(delete_on_success);
     ++	free_names(subtable_locks);
     ++	if (lock_file_fd > 0) {
     ++		close(lock_file_fd);
     ++		lock_file_fd = 0;
     ++	}
     ++	if (have_lock) {
     ++		unlink(slice_as_string(&lock_file_name));
     ++	}
     ++	free(slice_yield(&new_table_name));
     ++	free(slice_yield(&new_table_path));
     ++	free(slice_yield(&ref_list_contents));
     ++	free(slice_yield(&temp_tab_name));
     ++	free(slice_yield(&lock_file_name));
     ++	return err;
     ++}
     ++
     ++int stack_compact_all(struct stack *st, struct log_expiry_config *config)
     ++{
     ++	return stack_compact_range(st, 0, st->merged->stack_len - 1, config);
     ++}
     ++
     ++static int stack_compact_range_stats(struct stack *st, int first, int last,
     ++				     struct log_expiry_config *config)
     ++{
     ++	int err = stack_compact_range(st, first, last, config);
     ++	if (err > 0) {
     ++		st->stats.failures++;
     ++	}
     ++	return err;
     ++}
     ++
     ++static int segment_size(struct segment *s)
     ++{
     ++	return s->end - s->start;
     ++}
     ++
     ++int fastlog2(uint64_t sz)
     ++{
     ++	int l = 0;
     ++	assert(sz > 0);
     ++	for (; sz; sz /= 2) {
     ++		l++;
     ++	}
     ++	return l - 1;
     ++}
     ++
     ++struct segment *sizes_to_segments(int *seglen, uint64_t *sizes, int n)
     ++{
     ++	struct segment *segs = calloc(sizeof(struct segment), n);
     ++	int next = 0;
     ++	struct segment cur = {};
     ++	int i = 0;
     ++	for (i = 0; i < n; i++) {
     ++		int log = fastlog2(sizes[i]);
     ++		if (cur.log != log && cur.bytes > 0) {
     ++			struct segment fresh = {
     ++				.start = i,
     ++			};
     ++
     ++			segs[next++] = cur;
     ++			cur = fresh;
     ++		}
     ++
     ++		cur.log = log;
     ++		cur.end = i + 1;
     ++		cur.bytes += sizes[i];
     ++	}
     ++
     ++	segs[next++] = cur;
     ++	*seglen = next;
     ++	return segs;
     ++}
     ++
     ++struct segment suggest_compaction_segment(uint64_t *sizes, int n)
     ++{
     ++	int seglen = 0;
     ++	struct segment *segs = sizes_to_segments(&seglen, sizes, n);
     ++	struct segment min_seg = {
     ++		.log = 64,
     ++	};
     ++	int i = 0;
     ++	for (i = 0; i < seglen; i++) {
     ++		if (segment_size(&segs[i]) == 1) {
     ++			continue;
     ++		}
     ++
     ++		if (segs[i].log < min_seg.log) {
     ++			min_seg = segs[i];
     ++		}
     ++	}
     ++
     ++	while (min_seg.start > 0) {
     ++		int prev = min_seg.start - 1;
     ++		if (fastlog2(min_seg.bytes) < fastlog2(sizes[prev])) {
     ++			break;
     ++		}
     ++
     ++		min_seg.start = prev;
     ++		min_seg.bytes += sizes[prev];
     ++	}
     ++
     ++	free(segs);
     ++	return min_seg;
     ++}
     ++
     ++static uint64_t *stack_table_sizes_for_compaction(struct stack *st)
     ++{
     ++	uint64_t *sizes = calloc(sizeof(uint64_t), st->merged->stack_len);
     ++	int i = 0;
     ++	for (i = 0; i < st->merged->stack_len; i++) {
     ++		/* overhead is 24 + 68 = 92. */
     ++		sizes[i] = st->merged->stack[i]->size - 91;
     ++	}
     ++	return sizes;
     ++}
     ++
     ++int stack_auto_compact(struct stack *st)
     ++{
     ++	uint64_t *sizes = stack_table_sizes_for_compaction(st);
     ++	struct segment seg =
     ++		suggest_compaction_segment(sizes, st->merged->stack_len);
     ++	free(sizes);
     ++	if (segment_size(&seg) > 0) {
     ++		return stack_compact_range_stats(st, seg.start, seg.end - 1,
     ++						 NULL);
     ++	}
     ++
     ++	return 0;
     ++}
     ++
     ++struct compaction_stats *stack_compaction_stats(struct stack *st)
     ++{
     ++	return &st->stats;
      +}
      +
      +int stack_read_ref(struct stack *st, const char *refname,
     -+                   struct ref_record *ref) {
     -+  struct iterator it = {};
     -+  struct merged_table *mt = stack_merged_table(st);
     -+  int err = merged_table_seek_ref(mt, &it, refname);
     -+  if (err) {
     -+    goto exit;
     -+  }
     -+
     -+  err = iterator_next_ref(it, ref);
     -+  if (err) {
     -+    goto exit;
     -+  }
     -+
     -+  if (0 != strcmp(ref->ref_name, refname) || ref_record_is_deletion(ref)) {
     -+    err = 1;
     -+    goto exit;
     -+  }
     ++		   struct ref_record *ref)
     ++{
     ++	struct iterator it = {};
     ++	struct merged_table *mt = stack_merged_table(st);
     ++	int err = merged_table_seek_ref(mt, &it, refname);
     ++	if (err) {
     ++		goto exit;
     ++	}
     ++
     ++	err = iterator_next_ref(it, ref);
     ++	if (err) {
     ++		goto exit;
     ++	}
     ++
     ++	if (strcmp(ref->ref_name, refname) || ref_record_is_deletion(ref)) {
     ++		err = 1;
     ++		goto exit;
     ++	}
      +
      +exit:
     -+  iterator_destroy(&it);
     -+  return err;
     ++	iterator_destroy(&it);
     ++	return err;
      +}
      +
      +int stack_read_log(struct stack *st, const char *refname,
     -+                   struct log_record *log) {
     -+  struct iterator it = {};
     -+  struct merged_table *mt = stack_merged_table(st);
     -+  int err = merged_table_seek_log(mt, &it, refname);
     -+  if (err) {
     -+    goto exit;
     -+  }
     -+
     -+  err = iterator_next_log(it, log);
     -+  if (err) {
     -+    goto exit;
     -+  }
     -+
     -+  if (0 != strcmp(log->ref_name, refname) || log_record_is_deletion(log)) {
     -+    err = 1;
     -+    goto exit;
     -+  }
     ++		   struct log_record *log)
     ++{
     ++	struct iterator it = {};
     ++	struct merged_table *mt = stack_merged_table(st);
     ++	int err = merged_table_seek_log(mt, &it, refname);
     ++	if (err) {
     ++		goto exit;
     ++	}
     ++
     ++	err = iterator_next_log(it, log);
     ++	if (err) {
     ++		goto exit;
     ++	}
     ++
     ++	if (strcmp(log->ref_name, refname) || log_record_is_deletion(log)) {
     ++		err = 1;
     ++		goto exit;
     ++	}
      +
      +exit:
     -+  iterator_destroy(&it);
     -+  return err;
     ++	iterator_destroy(&it);
     ++	return err;
      +}
      
       diff --git a/reftable/stack.h b/reftable/stack.h
     @@ -6692,26 +6840,26 @@
      +#include "reftable.h"
      +
      +struct stack {
     -+  char *list_file;
     -+  char *reftable_dir;
     ++	char *list_file;
     ++	char *reftable_dir;
      +
     -+  struct write_options config;
     ++	struct write_options config;
      +
     -+  struct merged_table *merged;
     -+  struct compaction_stats stats;
     ++	struct merged_table *merged;
     ++	struct compaction_stats stats;
      +};
      +
      +int read_lines(const char *filename, char ***lines);
      +int stack_try_add(struct stack *st,
     -+                  int (*write_table)(struct writer *wr, void *arg), void *arg);
     ++		  int (*write_table)(struct writer *wr, void *arg), void *arg);
      +int stack_write_compact(struct stack *st, struct writer *wr, int first,
     -+                        int last, struct log_expiry_config *config);
     ++			int last, struct log_expiry_config *config);
      +int fastlog2(uint64_t sz);
      +
      +struct segment {
     -+  int start, end;
     -+  int log;
     -+  uint64_t bytes;
     ++	int start, end;
     ++	int log;
     ++	uint64_t bytes;
      +};
      +
      +struct segment *sizes_to_segments(int *seglen, uint64_t *sizes, int n);
     @@ -6734,8 +6882,7 @@
      +
      +#include "stack.h"
      +
     -+#include <string.h>
     -+#include <unistd.h>
     ++#include "system.h"
      +
      +#include "basics.h"
      +#include "constants.h"
     @@ -6743,252 +6890,311 @@
      +#include "reftable.h"
      +#include "test_framework.h"
      +
     -+void test_read_file(void) {
     -+  char fn[256] = "/tmp/stack.XXXXXX";
     -+  int fd = mkstemp(fn);
     -+  assert(fd > 0);
     ++void test_read_file(void)
     ++{
     ++	char fn[256] = "/tmp/stack.test_read_file.XXXXXX";
     ++	int fd = mkstemp(fn);
     ++	assert(fd > 0);
     ++
     ++	char out[1024] = "line1\n\nline2\nline3";
     ++
     ++	int n = write(fd, out, strlen(out));
     ++	assert(n == strlen(out));
     ++	int err = close(fd);
     ++	assert(err >= 0);
     ++
     ++	char **names = NULL;
     ++	err = read_lines(fn, &names);
     ++	assert_err(err);
     ++
     ++	char *want[] = { "line1", "line2", "line3" };
     ++	int i = 0;
     ++	for (i = 0; names[i] != NULL; i++) {
     ++		assert(0 == strcmp(want[i], names[i]));
     ++	}
     ++	free_names(names);
     ++	remove(fn);
     ++}
     ++
     ++void test_parse_names(void)
     ++{
     ++	char buf[] = "line\n";
     ++	char **names = NULL;
     ++	parse_names(buf, strlen(buf), &names);
     ++
     ++	assert(NULL != names[0]);
     ++	assert(0 == strcmp(names[0], "line"));
     ++	assert(NULL == names[1]);
     ++	free_names(names);
     ++}
     ++
     ++void test_names_equal(void)
     ++{
     ++	char *a[] = { "a", "b", "c", NULL };
     ++	char *b[] = { "a", "b", "d", NULL };
     ++	char *c[] = { "a", "b", NULL };
     ++
     ++	assert(names_equal(a, a));
     ++	assert(!names_equal(a, b));
     ++	assert(!names_equal(a, c));
     ++}
     ++
     ++int write_test_ref(struct writer *wr, void *arg)
     ++{
     ++	struct ref_record *ref = arg;
     ++
     ++	writer_set_limits(wr, ref->update_index, ref->update_index);
     ++	int err = writer_add_ref(wr, ref);
     ++
     ++	return err;
     ++}
     ++
     ++int write_test_log(struct writer *wr, void *arg)
     ++{
     ++	struct log_record *log = arg;
     ++
     ++	writer_set_limits(wr, log->update_index, log->update_index);
     ++	int err = writer_add_log(wr, log);
     ++
     ++	return err;
     ++}
     ++
     ++void test_stack_add(void)
     ++{
     ++	int i = 0;
     ++	char dir[256] = "/tmp/stack.test_stack_add.XXXXXX";
     ++	assert(mkdtemp(dir));
     ++	printf("%s\n", dir);
     ++	char fn[256] = "";
     ++	strcat(fn, dir);
     ++	strcat(fn, "/refs");
     ++
     ++	struct write_options cfg = {};
     ++	struct stack *st = NULL;
     ++	int err = new_stack(&st, dir, fn, cfg);
     ++	assert_err(err);
     ++
     ++	struct ref_record refs[2] = {};
     ++	struct log_record logs[2] = {};
     ++	int N = ARRAYSIZE(refs);
     ++	for (i = 0; i < N; i++) {
     ++		char buf[256];
     ++		snprintf(buf, sizeof(buf), "branch%02d", i);
     ++		refs[i].ref_name = strdup(buf);
     ++		refs[i].value = malloc(SHA1_SIZE);
     ++		refs[i].update_index = i + 1;
     ++		set_test_hash(refs[i].value, i);
     ++
     ++		logs[i].ref_name = strdup(buf);
     ++		logs[i].update_index = N + i + 1;
     ++		logs[i].new_hash = malloc(SHA1_SIZE);
     ++		logs[i].email = strdup("identity@invalid");
     ++		set_test_hash(logs[i].new_hash, i);
     ++	}
     ++
     ++	for (i = 0; i < N; i++) {
     ++		int err = stack_add(st, &write_test_ref, &refs[i]);
     ++		assert_err(err);
     ++	}
     ++
     ++	for (i = 0; i < N; i++) {
     ++		int err = stack_add(st, &write_test_log, &logs[i]);
     ++		assert_err(err);
     ++	}
     ++
     ++	err = stack_compact_all(st, NULL);
     ++	assert_err(err);
     ++
     ++	for (i = 0; i < N; i++) {
     ++		struct ref_record dest = {};
     ++		int err = stack_read_ref(st, refs[i].ref_name, &dest);
     ++		assert_err(err);
     ++		assert(ref_record_equal(&dest, refs + i, SHA1_SIZE));
     ++		ref_record_clear(&dest);
     ++	}
     ++
     ++	for (i = 0; i < N; i++) {
     ++		struct log_record dest = {};
     ++		int err = stack_read_log(st, refs[i].ref_name, &dest);
     ++		assert_err(err);
     ++		assert(log_record_equal(&dest, logs + i, SHA1_SIZE));
     ++		log_record_clear(&dest);
     ++	}
     ++
     ++	/* cleanup */
     ++	stack_destroy(st);
     ++	for (i = 0; i < N; i++) {
     ++		ref_record_clear(&refs[i]);
     ++		log_record_clear(&logs[i]);
     ++	}
     ++}
     ++
     ++void test_log2(void)
     ++{
     ++	assert(1 == fastlog2(3));
     ++	assert(2 == fastlog2(4));
     ++	assert(2 == fastlog2(5));
     ++}
     ++
     ++void test_sizes_to_segments(void)
     ++{
     ++	uint64_t sizes[] = { 2, 3, 4, 5, 7, 9 };
     ++	/* .................0  1  2  3  4  5 */
     ++
     ++	int seglen = 0;
     ++	struct segment *segs =
     ++		sizes_to_segments(&seglen, sizes, ARRAYSIZE(sizes));
     ++	assert(segs[2].log == 3);
     ++	assert(segs[2].start == 5);
     ++	assert(segs[2].end == 6);
     ++
     ++	assert(segs[1].log == 2);
     ++	assert(segs[1].start == 2);
     ++	assert(segs[1].end == 5);
     ++	free(segs);
     ++}
     ++
     ++void test_suggest_compaction_segment(void)
     ++{
     ++	{
     ++		uint64_t sizes[] = { 128, 64, 17, 16, 9, 9, 9, 16, 16 };
     ++		/* .................0    1    2  3   4  5  6 */
     ++		struct segment min =
     ++			suggest_compaction_segment(sizes, ARRAYSIZE(sizes));
     ++		assert(min.start == 2);
     ++		assert(min.end == 7);
     ++	}
     ++
     ++	{
     ++		uint64_t sizes[] = { 64, 32, 16, 8, 4, 2 };
     ++		struct segment result =
     ++			suggest_compaction_segment(sizes, ARRAYSIZE(sizes));
     ++		assert(result.start == result.end);
     ++	}
     ++}
     ++
     ++void test_reflog_expire(void)
     ++{
     ++	char dir[256] = "/tmp/stack.test_reflog_expire.XXXXXX";
     ++	assert(mkdtemp(dir));
     ++	printf("%s\n", dir);
     ++	char fn[256] = "";
     ++	strcat(fn, dir);
     ++	strcat(fn, "/refs");
     ++
     ++	struct write_options cfg = {};
     ++	struct stack *st = NULL;
     ++	int err = new_stack(&st, dir, fn, cfg);
     ++	assert_err(err);
     ++
     ++	struct log_record logs[20] = {};
     ++	int N = ARRAYSIZE(logs) - 1;
     ++	int i = 0;
     ++	for (i = 1; i <= N; i++) {
     ++		char buf[256];
     ++		snprintf(buf, sizeof(buf), "branch%02d", i);
     ++
     ++		logs[i].ref_name = strdup(buf);
     ++		logs[i].update_index = i;
     ++		logs[i].time = i;
     ++		logs[i].new_hash = malloc(SHA1_SIZE);
     ++		logs[i].email = strdup("identity@invalid");
     ++		set_test_hash(logs[i].new_hash, i);
     ++	}
     ++
     ++	for (i = 1; i <= N; i++) {
     ++		int err = stack_add(st, &write_test_log, &logs[i]);
     ++		assert_err(err);
     ++	}
     ++
     ++	err = stack_compact_all(st, NULL);
     ++	assert_err(err);
     ++
     ++	struct log_expiry_config expiry = {
     ++		.time = 10,
     ++	};
     ++	err = stack_compact_all(st, &expiry);
     ++	assert_err(err);
     ++
     ++	struct log_record log = {};
     ++	err = stack_read_log(st, logs[9].ref_name, &log);
     ++	assert(err == 1);
     ++
     ++	err = stack_read_log(st, logs[11].ref_name, &log);
     ++	assert_err(err);
     ++
     ++	expiry.min_update_index = 15;
     ++	err = stack_compact_all(st, &expiry);
     ++	assert_err(err);
     ++
     ++	err = stack_read_log(st, logs[14].ref_name, &log);
     ++	assert(err == 1);
     ++
     ++	err = stack_read_log(st, logs[16].ref_name, &log);
     ++	assert_err(err);
     ++
     ++	/* cleanup */
     ++	stack_destroy(st);
     ++	for (i = 0; i < N; i++) {
     ++		log_record_clear(&logs[i]);
     ++	}
     ++}
     ++
     ++int main()
     ++{
     ++	add_test_case("test_reflog_expire", test_reflog_expire);
     ++	add_test_case("test_suggest_compaction_segment",
     ++		      &test_suggest_compaction_segment);
     ++	add_test_case("test_sizes_to_segments", &test_sizes_to_segments);
     ++	add_test_case("test_log2", &test_log2);
     ++	add_test_case("test_parse_names", &test_parse_names);
     ++	add_test_case("test_read_file", &test_read_file);
     ++	add_test_case("test_names_equal", &test_names_equal);
     ++	add_test_case("test_stack_add", &test_stack_add);
     ++	test_main();
     ++}
     +
     + diff --git a/reftable/system.h b/reftable/system.h
     + new file mode 100644
     + --- /dev/null
     + +++ b/reftable/system.h
     +@@
     ++/*
     ++Copyright 2020 Google LLC
      +
     -+  char out[1024] = "line1\n\nline2\nline3";
     ++Use of this source code is governed by a BSD-style
     ++license that can be found in the LICENSE file or at
     ++https://developers.google.com/open-source/licenses/bsd
     ++*/
      +
     -+  int n = write(fd, out, strlen(out));
     -+  assert(n == strlen(out));
     -+  int err = close(fd);
     -+  assert(err >= 0);
     ++#ifndef SYSTEM_H
     ++#define SYSTEM_H
      +
     -+  char **names = NULL;
     -+  err = read_lines(fn, &names);
     -+  assert_err(err);
     ++#include "config.h"
      +
     -+  char *want[] = {"line1", "line2", "line3"};
     -+  for (int i = 0; names[i] != NULL; i++) {
     -+    assert(0 == strcmp(want[i], names[i]));
     -+  }
     -+  free_names(names);
     -+  remove(fn);
     -+}
     -+
     -+void test_parse_names(void) {
     -+  char buf[] = "line\n";
     -+  char **names = NULL;
     -+  parse_names(buf, strlen(buf), &names);
     -+
     -+  assert(NULL != names[0]);
     -+  assert(0 == strcmp(names[0], "line"));
     -+  assert(NULL == names[1]);
     -+  free_names(names);
     -+}
     -+
     -+void test_names_equal(void) {
     -+  char *a[] = {"a", "b", "c", NULL};
     -+  char *b[] = {"a", "b", "d", NULL};
     -+  char *c[] = {"a", "b", NULL};
     -+
     -+  assert(names_equal(a, a));
     -+  assert(!names_equal(a, b));
     -+  assert(!names_equal(a, c));
     -+}
     -+
     -+int write_test_ref(struct writer *wr, void *arg) {
     -+  struct ref_record *ref = arg;
     -+
     -+  writer_set_limits(wr, ref->update_index, ref->update_index);
     -+  int err = writer_add_ref(wr, ref);
     -+
     -+  return err;
     -+}
     ++#ifndef REFTABLE_STANDALONE
     ++#include "git-compat-util.h"
     ++#else
     ++#include <assert.h>
     ++#include <errno.h>
     ++#include <fcntl.h>
     ++#include <stdint.h>
     ++#include <stdio.h>
     ++#include <stdlib.h>
     ++#include <string.h>
     ++#include <sys/stat.h>
     ++#include <sys/time.h>
     ++#include <sys/types.h>
     ++#include <unistd.h>
     ++#include <zlib.h>
      +
     -+int write_test_log(struct writer *wr, void *arg) {
     -+  struct log_record *log = arg;
     -+
     -+  writer_set_limits(wr, log->update_index, log->update_index);
     -+  int err = writer_add_log(wr, log);
     -+
     -+  return err;
     -+}
     -+
     -+void test_stack_add(void) {
     -+  char dir[256] = "/tmp/stack.XXXXXX";
     -+  assert(mkdtemp(dir));
     -+  printf("%s\n", dir);
     -+  char fn[256] = "";
     -+  strcat(fn, dir);
     -+  strcat(fn, "/refs");
     -+
     -+  struct write_options cfg = {};
     -+  struct stack *st = NULL;
     -+  int err = new_stack(&st, dir, fn, cfg);
     -+  assert_err(err);
     -+
     -+  struct ref_record refs[2] = {};
     -+  struct log_record logs[2] = {};
     -+  int N = ARRAYSIZE(refs);
     -+  for (int i = 0; i < N; i++) {
     -+    char buf[256];
     -+    sprintf(buf, "branch%02d", i);
     -+    refs[i].ref_name = strdup(buf);
     -+    refs[i].value = malloc(SHA1_SIZE);
     -+    refs[i].update_index = i + 1;
     -+    set_test_hash(refs[i].value, i);
     -+
     -+    logs[i].ref_name = strdup(buf);
     -+    logs[i].update_index = N + i+1;
     -+    logs[i].new_hash = malloc(SHA1_SIZE);
     -+    logs[i].email = strdup("identity@invalid");
     -+    set_test_hash(logs[i].new_hash, i);
     -+  }
     -+
     -+  for (int i = 0; i < N; i++) {
     -+    int err = stack_add(st, &write_test_ref, &refs[i]);
     -+    assert_err(err);
     -+  }
     -+
     -+  for (int i = 0; i < N; i++) {
     -+    int err = stack_add(st, &write_test_log, &logs[i]);
     -+    assert_err(err);
     -+  }
     -+
     -+  err = stack_compact_all(st, NULL);
     -+  assert_err(err);
     -+
     -+  for (int i = 0; i < N; i++) {
     -+    struct ref_record dest = {};
     -+    int err = stack_read_ref(st, refs[i].ref_name, &dest);
     -+    assert_err(err);
     -+    assert(ref_record_equal(&dest, refs + i, SHA1_SIZE));
     -+    ref_record_clear(&dest);
     -+  }
     -+
     -+  for (int i = 0; i < N; i++) {
     -+    struct log_record dest = {};
     -+    int err = stack_read_log(st, refs[i].ref_name, &dest);
     -+    assert_err(err);
     -+    assert(log_record_equal(&dest, logs + i, SHA1_SIZE));
     -+    log_record_clear(&dest);
     -+  }
     -+
     -+  // cleanup
     -+  stack_destroy(st);
     -+  for (int i = 0; i < N; i++) {
     -+    ref_record_clear(&refs[i]);
     -+    log_record_clear(&logs[i]);
     -+  }
     -+}
     -+
     -+void test_log2(void) {
     -+  assert(1 == fastlog2(3));
     -+  assert(2 == fastlog2(4));
     -+  assert(2 == fastlog2(5));
     -+}
     -+
     -+void test_sizes_to_segments(void) {
     -+  uint64_t sizes[] = {2, 3, 4, 5, 7, 9};
     -+  // .................0  1  2  3  4  5
     -+
     -+  int seglen = 0;
     -+  struct segment *segs = sizes_to_segments(&seglen, sizes, ARRAYSIZE(sizes));
     -+  assert(segs[2].log == 3);
     -+  assert(segs[2].start == 5);
     -+  assert(segs[2].end == 6);
     -+
     -+  assert(segs[1].log == 2);
     -+  assert(segs[1].start == 2);
     -+  assert(segs[1].end == 5);
     -+  free(segs);
     -+}
     -+
     -+void test_suggest_compaction_segment(void) {
     -+  {
     -+    uint64_t sizes[] = {128, 64, 17, 16, 9, 9, 9, 16, 16};
     -+    // .................0    1    2  3   4  5  6
     -+    struct segment min = suggest_compaction_segment(sizes, ARRAYSIZE(sizes));
     -+    assert(min.start == 2);
     -+    assert(min.end == 7);
     -+  }
     -+
     -+  {
     -+    uint64_t sizes[] = {64, 32, 16, 8, 4, 2};
     -+    struct segment result = suggest_compaction_segment(sizes, ARRAYSIZE(sizes));
     -+    assert(result.start == result.end);
     -+  }
     -+}
     -+
     -+void test_reflog_expire(void) {
     -+  char dir[256] = "/tmp/stack.XXXXXX";
     -+  assert(mkdtemp(dir));
     -+  printf("%s\n", dir);
     -+  char fn[256] = "";
     -+  strcat(fn, dir);
     -+  strcat(fn, "/refs");
     -+
     -+  struct write_options cfg = {};
     -+  struct stack *st = NULL;
     -+  int err = new_stack(&st, dir, fn, cfg);
     -+  assert_err(err);
     -+
     -+  struct log_record logs[20] = {};
     -+  int N = ARRAYSIZE(logs) -1;
     -+  for (int i = 1; i <= N; i++) {
     -+    char buf[256];
     -+    sprintf(buf, "branch%02d", i);
     -+
     -+    logs[i].ref_name = strdup(buf);
     -+    logs[i].update_index = i;
     -+    logs[i].time = i;
     -+    logs[i].new_hash = malloc(SHA1_SIZE);
     -+    logs[i].email = strdup("identity@invalid");
     -+    set_test_hash(logs[i].new_hash, i);
     -+  }
     -+
     -+  for (int i = 1; i <= N; i++) {
     -+    int err = stack_add(st, &write_test_log, &logs[i]);
     -+    assert_err(err);
     -+  }
     -+
     -+  err = stack_compact_all(st, NULL);
     -+  assert_err(err);
     -+
     -+  struct log_expiry_config expiry = {
     -+    .time = 10,
     -+  };
     -+  err = stack_compact_all(st, &expiry);
     -+  assert_err(err);
     -+
     -+  struct log_record log = {};
     -+  err = stack_read_log(st, logs[9].ref_name, &log);
     -+  assert(err == 1);
     -+
     -+  err = stack_read_log(st, logs[11].ref_name, &log);
     -+  assert_err(err);
     -+
     -+  expiry.min_update_index = 15;
     -+  err = stack_compact_all(st, &expiry);
     -+  assert_err(err);
     -+
     -+  err = stack_read_log(st, logs[14].ref_name, &log);
     -+  assert(err == 1);
     -+
     -+  err = stack_read_log(st, logs[16].ref_name, &log);
     -+  assert_err(err);
     -+
     -+  // cleanup
     -+  stack_destroy(st);
     -+  for (int i = 0; i < N; i++) {
     -+    log_record_clear(&logs[i]);
     -+  }
     -+}
     -+
     -+int main() {
     -+  add_test_case("test_reflog_expire", test_reflog_expire);
     -+  add_test_case("test_suggest_compaction_segment",
     -+                &test_suggest_compaction_segment);
     -+  add_test_case("test_sizes_to_segments", &test_sizes_to_segments);
     -+  add_test_case("test_log2", &test_log2);
     -+  add_test_case("test_parse_names", &test_parse_names);
     -+  add_test_case("test_read_file", &test_read_file);
     -+  add_test_case("test_names_equal", &test_names_equal);
     -+  add_test_case("test_stack_add", &test_stack_add);
     -+  test_main();
     -+}
     ++#define PRIuMAX "lu"
     ++#define PRIdMAX "ld"
     ++#define PRIxMAX "lx"
     ++
     ++#endif
     ++
     ++#endif
      
       diff --git a/reftable/test_framework.c b/reftable/test_framework.c
       new file mode 100644
     @@ -7005,9 +7211,7 @@
      +
      +#include "test_framework.h"
      +
     -+#include <stdio.h>
     -+#include <stdlib.h>
     -+#include <string.h>
     ++#include "system.h"
      +
      +#include "constants.h"
      +
     @@ -7015,45 +7219,54 @@
      +int test_case_len;
      +int test_case_cap;
      +
     -+struct test_case *new_test_case(const char *name, void (*testfunc)()) {
     -+  struct test_case *tc = malloc(sizeof(struct test_case));
     -+  tc->name = name;
     -+  tc->testfunc = testfunc;
     -+  return tc;
     -+}
     -+
     -+struct test_case *add_test_case(const char *name, void (*testfunc)()) {
     -+  struct test_case *tc = new_test_case(name, testfunc);
     -+  if (test_case_len == test_case_cap) {
     -+    test_case_cap = 2 * test_case_cap + 1;
     -+    test_cases = realloc(test_cases, sizeof(struct test_case) * test_case_cap);
     -+  }
     -+
     -+  test_cases[test_case_len++] = tc;
     -+  return tc;
     -+}
     -+
     -+void test_main() {
     -+  for (int i = 0; i < test_case_len; i++) {
     -+    printf("case %s\n", test_cases[i]->name);
     -+    test_cases[i]->testfunc();
     -+  }
     -+}
     -+
     -+void set_test_hash(byte *p, int i) { memset(p, (byte)i, SHA1_SIZE); }
     -+
     -+void print_names(char **a) {
     -+  if (a == NULL || *a == NULL) {
     -+    puts("[]");
     -+    return;
     -+  }
     -+  puts("[");
     -+  char **p = a;
     -+  while (*p) {
     -+    puts(*p);
     -+    p++;
     -+  }
     -+  puts("]");
     ++struct test_case *new_test_case(const char *name, void (*testfunc)())
     ++{
     ++	struct test_case *tc = malloc(sizeof(struct test_case));
     ++	tc->name = name;
     ++	tc->testfunc = testfunc;
     ++	return tc;
     ++}
     ++
     ++struct test_case *add_test_case(const char *name, void (*testfunc)())
     ++{
     ++	struct test_case *tc = new_test_case(name, testfunc);
     ++	if (test_case_len == test_case_cap) {
     ++		test_case_cap = 2 * test_case_cap + 1;
     ++		test_cases = realloc(test_cases,
     ++				     sizeof(struct test_case) * test_case_cap);
     ++	}
     ++
     ++	test_cases[test_case_len++] = tc;
     ++	return tc;
     ++}
     ++
     ++void test_main()
     ++{
     ++	int i = 0;
     ++	for (i = 0; i < test_case_len; i++) {
     ++		printf("case %s\n", test_cases[i]->name);
     ++		test_cases[i]->testfunc();
     ++	}
     ++}
     ++
     ++void set_test_hash(byte *p, int i)
     ++{
     ++	memset(p, (byte)i, SHA1_SIZE);
     ++}
     ++
     ++void print_names(char **a)
     ++{
     ++	if (a == NULL || *a == NULL) {
     ++		puts("[]");
     ++		return;
     ++	}
     ++	puts("[");
     ++	char **p = a;
     ++	while (*p) {
     ++		puts(*p);
     ++		p++;
     ++	}
     ++	puts("]");
      +}
      
       diff --git a/reftable/test_framework.h b/reftable/test_framework.h
     @@ -7072,8 +7285,7 @@
      +#ifndef TEST_FRAMEWORK_H
      +#define TEST_FRAMEWORK_H
      +
     -+#include <stdio.h>
     -+#include <stdlib.h>
     ++#include "system.h"
      +
      +#include "reftable.h"
      +
     @@ -7081,40 +7293,42 @@
      +#undef NDEBUG
      +#endif
      +
     -+#include <assert.h>
     ++#include "system.h"
      +
      +#ifdef assert
      +#undef assert
      +#endif
      +
     -+#define assert_err(c)                                                        \
     -+  if (c != 0) {                                                              \
     -+    fflush(stderr);                                                          \
     -+    fflush(stdout);                                                          \
     -+    fprintf(stderr, "%s: %d: error == %d (%s), want 0\n", __FILE__, __LINE__, c, error_str(c)); \
     -+    abort();                                                                 \
     -+  }
     -+
     -+#define assert_streq(a, b)                                                    \
     -+  if (0 != strcmp(a, b)) {                                                    \
     -+    fflush(stderr);                                                           \
     -+    fflush(stdout);                                                           \
     -+    fprintf(stderr, "%s:%d: %s (%s) != %s (%s)\n", __FILE__, __LINE__, #a, a, \
     -+            #b, b);                                                           \
     -+    abort();                                                                  \
     -+  }
     -+
     -+#define assert(c)                                                             \
     -+  if (!(c)) {                                                                 \
     -+    fflush(stderr);                                                           \
     -+    fflush(stdout);                                                           \
     -+    fprintf(stderr, "%s: %d: failed assertion %s\n", __FILE__, __LINE__, #c); \
     -+    abort();                                                                  \
     -+  }
     ++#define assert_err(c)                                                 \
     ++	if (c != 0) {                                                 \
     ++		fflush(stderr);                                       \
     ++		fflush(stdout);                                       \
     ++		fprintf(stderr, "%s: %d: error == %d (%s), want 0\n", \
     ++			__FILE__, __LINE__, c, error_str(c));         \
     ++		abort();                                              \
     ++	}
     ++
     ++#define assert_streq(a, b)                                               \
     ++	if (strcmp(a, b)) {                                              \
     ++		fflush(stderr);                                          \
     ++		fflush(stdout);                                          \
     ++		fprintf(stderr, "%s:%d: %s (%s) != %s (%s)\n", __FILE__, \
     ++			__LINE__, #a, a, #b, b);                         \
     ++		abort();                                                 \
     ++	}
     ++
     ++#define assert(c)                                                          \
     ++	if (!(c)) {                                                        \
     ++		fflush(stderr);                                            \
     ++		fflush(stdout);                                            \
     ++		fprintf(stderr, "%s: %d: failed assertion %s\n", __FILE__, \
     ++			__LINE__, #c);                                     \
     ++		abort();                                                   \
     ++	}
      +
      +struct test_case {
     -+  const char *name;
     -+  void (*testfunc)();
     ++	const char *name;
     ++	void (*testfunc)();
      +};
      +
      +struct test_case *new_test_case(const char *name, void (*testfunc)());
     @@ -7140,55 +7354,61 @@
      +
      +#include "tree.h"
      +
     -+#include <stdlib.h>
     ++#include "system.h"
      +
      +struct tree_node *tree_search(void *key, struct tree_node **rootp,
     -+                              int (*compare)(const void *, const void *),
     -+                              int insert) {
     -+  if (*rootp == NULL) {
     -+    if (!insert) {
     -+      return NULL;
     -+    } else {
     -+      struct tree_node *n = calloc(sizeof(struct tree_node), 1);
     -+      n->key = key;
     -+      *rootp = n;
     -+      return *rootp;
     -+    }
     -+  }
     -+
     -+  {
     -+    int res = compare(key, (*rootp)->key);
     -+    if (res < 0) {
     -+      return tree_search(key, &(*rootp)->left, compare, insert);
     -+    } else if (res > 0) {
     -+      return tree_search(key, &(*rootp)->right, compare, insert);
     -+    }
     -+  }
     -+  return *rootp;
     ++			      int (*compare)(const void *, const void *),
     ++			      int insert)
     ++{
     ++	if (*rootp == NULL) {
     ++		if (!insert) {
     ++			return NULL;
     ++		} else {
     ++			struct tree_node *n =
     ++				calloc(sizeof(struct tree_node), 1);
     ++			n->key = key;
     ++			*rootp = n;
     ++			return *rootp;
     ++		}
     ++	}
     ++
     ++	{
     ++		int res = compare(key, (*rootp)->key);
     ++		if (res < 0) {
     ++			return tree_search(key, &(*rootp)->left, compare,
     ++					   insert);
     ++		} else if (res > 0) {
     ++			return tree_search(key, &(*rootp)->right, compare,
     ++					   insert);
     ++		}
     ++	}
     ++	return *rootp;
      +}
      +
      +void infix_walk(struct tree_node *t, void (*action)(void *arg, void *key),
     -+                void *arg) {
     -+  if (t->left != NULL) {
     -+    infix_walk(t->left, action, arg);
     -+  }
     -+  action(arg, t->key);
     -+  if (t->right != NULL) {
     -+    infix_walk(t->right, action, arg);
     -+  }
     -+}
     -+
     -+void tree_free(struct tree_node *t) {
     -+  if (t == NULL) {
     -+    return;
     -+  }
     -+  if (t->left != NULL) {
     -+    tree_free(t->left);
     -+  }
     -+  if (t->right != NULL) {
     -+    tree_free(t->right);
     -+  }
     -+  free(t);
     ++		void *arg)
     ++{
     ++	if (t->left != NULL) {
     ++		infix_walk(t->left, action, arg);
     ++	}
     ++	action(arg, t->key);
     ++	if (t->right != NULL) {
     ++		infix_walk(t->right, action, arg);
     ++	}
     ++}
     ++
     ++void tree_free(struct tree_node *t)
     ++{
     ++	if (t == NULL) {
     ++		return;
     ++	}
     ++	if (t->left != NULL) {
     ++		tree_free(t->left);
     ++	}
     ++	if (t->right != NULL) {
     ++		tree_free(t->right);
     ++	}
     ++	free(t);
      +}
      
       diff --git a/reftable/tree.h b/reftable/tree.h
     @@ -7208,15 +7428,15 @@
      +#define TREE_H
      +
      +struct tree_node {
     -+  void *key;
     -+  struct tree_node *left, *right;
     ++	void *key;
     ++	struct tree_node *left, *right;
      +};
      +
      +struct tree_node *tree_search(void *key, struct tree_node **rootp,
     -+                              int (*compare)(const void *, const void *),
     -+                              int insert);
     ++			      int (*compare)(const void *, const void *),
     ++			      int insert);
      +void infix_walk(struct tree_node *t, void (*action)(void *arg, void *key),
     -+                void *arg);
     ++		void *arg);
      +void tree_free(struct tree_node *t);
      +
      +#endif
     @@ -7241,44 +7461,51 @@
      +#include "reftable.h"
      +#include "test_framework.h"
      +
     -+static int test_compare(const void *a, const void *b) { return a - b; }
     ++static int test_compare(const void *a, const void *b)
     ++{
     ++	return a - b;
     ++}
      +
      +struct curry {
     -+  void *last;
     ++	void *last;
      +};
      +
     -+void check_increasing(void *arg, void *key) {
     -+  struct curry *c = (struct curry *)arg;
     -+  if (c->last != NULL) {
     -+    assert(test_compare(c->last, key) < 0);
     -+  }
     -+  c->last = key;
     ++void check_increasing(void *arg, void *key)
     ++{
     ++	struct curry *c = (struct curry *)arg;
     ++	if (c->last != NULL) {
     ++		assert(test_compare(c->last, key) < 0);
     ++	}
     ++	c->last = key;
      +}
      +
     -+void test_tree() {
     -+  struct tree_node *root = NULL;
     ++void test_tree()
     ++{
     ++	struct tree_node *root = NULL;
      +
     -+  void *values[11] = {};
     -+  struct tree_node *nodes[11] = {};
     -+  int i = 1;
     -+  do {
     -+    nodes[i] = tree_search(values + i, &root, &test_compare, 1);
     -+    i = (i * 7) % 11;
     -+  } while (i != 1);
     ++	void *values[11] = {};
     ++	struct tree_node *nodes[11] = {};
     ++	int i = 1;
     ++	do {
     ++		nodes[i] = tree_search(values + i, &root, &test_compare, 1);
     ++		i = (i * 7) % 11;
     ++	} while (i != 1);
      +
     -+  for (int i = 1; i < ARRAYSIZE(nodes); i++) {
     -+    assert(values + i == nodes[i]->key);
     -+    assert(nodes[i] == tree_search(values + i, &root, &test_compare, 0));
     -+  }
     ++	for (i = 1; i < ARRAYSIZE(nodes); i++) {
     ++		assert(values + i == nodes[i]->key);
     ++		assert(nodes[i] ==
     ++		       tree_search(values + i, &root, &test_compare, 0));
     ++	}
      +
     -+  struct curry c = {};
     -+  infix_walk(root, check_increasing, &c);
     -+  tree_free(root);
     ++	struct curry c = {};
     ++	infix_walk(root, check_increasing, &c);
     ++	tree_free(root);
      +}
      +
     -+int main() {
     -+  add_test_case("test_tree", &test_tree);
     -+  test_main();
     ++int main()
     ++{
     ++	add_test_case("test_tree", &test_tree);
     ++	test_main();
      +}
      
       diff --git a/reftable/writer.c b/reftable/writer.c
     @@ -7296,12 +7523,7 @@
      +
      +#include "writer.h"
      +
     -+#include <assert.h>
     -+#include <stdint.h>
     -+#include <stdio.h>  // debug
     -+#include <stdlib.h>
     -+#include <string.h>
     -+#include <zlib.h>
     ++#include "system.h"
      +
      +#include "block.h"
      +#include "constants.h"
     @@ -7309,568 +7531,611 @@
      +#include "reftable.h"
      +#include "tree.h"
      +
     -+static struct block_stats *writer_block_stats(struct writer *w, byte typ) {
     -+  switch (typ) {
     -+    case 'r':
     -+      return &w->stats.ref_stats;
     -+    case 'o':
     -+      return &w->stats.obj_stats;
     -+    case 'i':
     -+      return &w->stats.idx_stats;
     -+    case 'g':
     -+      return &w->stats.log_stats;
     -+  }
     -+  assert(false);
     ++static struct block_stats *writer_block_stats(struct writer *w, byte typ)
     ++{
     ++	switch (typ) {
     ++	case 'r':
     ++		return &w->stats.ref_stats;
     ++	case 'o':
     ++		return &w->stats.obj_stats;
     ++	case 'i':
     ++		return &w->stats.idx_stats;
     ++	case 'g':
     ++		return &w->stats.log_stats;
     ++	}
     ++	assert(false);
      +}
      +
      +/* write data, queuing the padding for the next write. Returns negative for
      + * error. */
     -+static int padded_write(struct writer *w, byte *data, size_t len, int padding) {
     -+  int n = 0;
     -+  if (w->pending_padding > 0) {
     -+    byte *zeroed = calloc(w->pending_padding, 1);
     -+    int n = w->write(w->write_arg, zeroed, w->pending_padding);
     -+    if (n < 0) {
     -+      return n;
     -+    }
     -+
     -+    w->pending_padding = 0;
     -+    free(zeroed);
     -+  }
     -+
     -+  w->pending_padding = padding;
     -+  n = w->write(w->write_arg, data, len);
     -+  if (n < 0) {
     -+    return n;
     -+  }
     -+  n += padding;
     -+  return 0;
     -+}
     -+
     -+static void options_set_defaults(struct write_options *opts) {
     -+  if (opts->restart_interval == 0) {
     -+    opts->restart_interval = 16;
     -+  }
     -+
     -+  if (opts->block_size == 0) {
     -+    opts->block_size = DEFAULT_BLOCK_SIZE;
     -+  }
     -+}
     -+
     -+static int writer_write_header(struct writer *w, byte *dest) {
     -+  strcpy((char *)dest, "REFT");
     -+  dest[4] = 1;  // version
     -+  put_u24(dest + 5, w->opts.block_size);
     -+  put_u64(dest + 8, w->min_update_index);
     -+  put_u64(dest + 16, w->max_update_index);
     -+  return 24;
     -+}
     -+
     -+static void writer_reinit_block_writer(struct writer *w, byte typ) {
     -+  int block_start = 0;
     -+  if (w->next == 0) {
     -+    block_start = HEADER_SIZE;
     -+  }
     -+
     -+  block_writer_init(&w->block_writer_data, typ, w->block, w->opts.block_size,
     -+                    block_start, w->hash_size);
     -+  w->block_writer = &w->block_writer_data;
     -+  w->block_writer->restart_interval = w->opts.restart_interval;
     ++static int padded_write(struct writer *w, byte *data, size_t len, int padding)
     ++{
     ++	int n = 0;
     ++	if (w->pending_padding > 0) {
     ++		byte *zeroed = calloc(w->pending_padding, 1);
     ++		int n = w->write(w->write_arg, zeroed, w->pending_padding);
     ++		if (n < 0) {
     ++			return n;
     ++		}
     ++
     ++		w->pending_padding = 0;
     ++		free(zeroed);
     ++	}
     ++
     ++	w->pending_padding = padding;
     ++	n = w->write(w->write_arg, data, len);
     ++	if (n < 0) {
     ++		return n;
     ++	}
     ++	n += padding;
     ++	return 0;
     ++}
     ++
     ++static void options_set_defaults(struct write_options *opts)
     ++{
     ++	if (opts->restart_interval == 0) {
     ++		opts->restart_interval = 16;
     ++	}
     ++
     ++	if (opts->block_size == 0) {
     ++		opts->block_size = DEFAULT_BLOCK_SIZE;
     ++	}
     ++}
     ++
     ++static int writer_write_header(struct writer *w, byte *dest)
     ++{
     ++	memcpy((char *)dest, "REFT", 4);
     ++	dest[4] = 1; /* version */
     ++	put_u24(dest + 5, w->opts.block_size);
     ++	put_u64(dest + 8, w->min_update_index);
     ++	put_u64(dest + 16, w->max_update_index);
     ++	return 24;
     ++}
     ++
     ++static void writer_reinit_block_writer(struct writer *w, byte typ)
     ++{
     ++	int block_start = 0;
     ++	if (w->next == 0) {
     ++		block_start = HEADER_SIZE;
     ++	}
     ++
     ++	block_writer_init(&w->block_writer_data, typ, w->block,
     ++			  w->opts.block_size, block_start, w->hash_size);
     ++	w->block_writer = &w->block_writer_data;
     ++	w->block_writer->restart_interval = w->opts.restart_interval;
      +}
      +
      +struct writer *new_writer(int (*writer_func)(void *, byte *, int),
     -+                          void *writer_arg, struct write_options *opts) {
     -+  struct writer *wp = calloc(sizeof(struct writer), 1);
     -+  options_set_defaults(opts);
     -+  if (opts->block_size >= (1 << 24)) {
     -+    // TODO - error return?
     -+    abort();
     -+  }
     -+  wp->hash_size = SHA1_SIZE;
     -+  wp->block = calloc(opts->block_size, 1);
     -+  wp->write = writer_func;
     -+  wp->write_arg = writer_arg;
     -+  wp->opts = *opts;
     -+  writer_reinit_block_writer(wp, BLOCK_TYPE_REF);
     ++			  void *writer_arg, struct write_options *opts)
     ++{
     ++	struct writer *wp = calloc(sizeof(struct writer), 1);
     ++	options_set_defaults(opts);
     ++	if (opts->block_size >= (1 << 24)) {
     ++		/* TODO - error return? */
     ++		abort();
     ++	}
     ++	wp->hash_size = SHA1_SIZE;
     ++	wp->block = calloc(opts->block_size, 1);
     ++	wp->write = writer_func;
     ++	wp->write_arg = writer_arg;
     ++	wp->opts = *opts;
     ++	writer_reinit_block_writer(wp, BLOCK_TYPE_REF);
      +
     -+  return wp;
     ++	return wp;
      +}
      +
     -+void writer_set_limits(struct writer *w, uint64_t min, uint64_t max) {
     -+  w->min_update_index = min;
     -+  w->max_update_index = max;
     ++void writer_set_limits(struct writer *w, uint64_t min, uint64_t max)
     ++{
     ++	w->min_update_index = min;
     ++	w->max_update_index = max;
      +}
      +
     -+void writer_free(struct writer *w) {
     -+  free(w->block);
     -+  free(w);
     ++void writer_free(struct writer *w)
     ++{
     ++	free(w->block);
     ++	free(w);
      +}
      +
      +struct obj_index_tree_node {
     -+  struct slice hash;
     -+  uint64_t *offsets;
     -+  int offset_len;
     -+  int offset_cap;
     ++	struct slice hash;
     ++	uint64_t *offsets;
     ++	int offset_len;
     ++	int offset_cap;
      +};
      +
     -+static int obj_index_tree_node_compare(const void *a, const void *b) {
     -+  return slice_compare(((const struct obj_index_tree_node *)a)->hash,
     -+                       ((const struct obj_index_tree_node *)b)->hash);
     -+}
     -+
     -+static void writer_index_hash(struct writer *w, struct slice hash) {
     -+  uint64_t off = w->next;
     -+
     -+  struct obj_index_tree_node want = {.hash = hash};
     -+
     -+  struct tree_node *node =
     -+      tree_search(&want, &w->obj_index_tree, &obj_index_tree_node_compare, 0);
     -+  struct obj_index_tree_node *key = NULL;
     -+  if (node == NULL) {
     -+    key = calloc(sizeof(struct obj_index_tree_node), 1);
     -+    slice_copy(&key->hash, hash);
     -+    tree_search((void *)key, &w->obj_index_tree, &obj_index_tree_node_compare,
     -+                1);
     -+  } else {
     -+    key = node->key;
     -+  }
     -+
     -+  if (key->offset_len > 0 && key->offsets[key->offset_len - 1] == off) {
     -+    return;
     -+  }
     -+
     -+  if (key->offset_len == key->offset_cap) {
     -+    key->offset_cap = 2 * key->offset_cap + 1;
     -+    key->offsets = realloc(key->offsets, sizeof(uint64_t) * key->offset_cap);
     -+  }
     -+
     -+  key->offsets[key->offset_len++] = off;
     -+}
     -+
     -+static int writer_add_record(struct writer *w, struct record rec) {
     -+  int result = -1;
     -+  struct slice key = {};
     -+  int err = 0;
     -+  record_key(rec, &key);
     -+  if (slice_compare(w->last_key, key) >= 0) {
     -+    goto exit;
     -+  }
     -+
     -+  slice_copy(&w->last_key, key);
     -+  if (w->block_writer == NULL) {
     -+    writer_reinit_block_writer(w, record_type(rec));
     -+  }
     -+
     -+  assert(block_writer_type(w->block_writer) == record_type(rec));
     -+
     -+  if (block_writer_add(w->block_writer, rec) == 0) {
     -+    result = 0;
     -+    goto exit;
     -+  }
     -+
     -+  err = writer_flush_block(w);
     -+  if (err < 0) {
     -+    result = err;
     -+    goto exit;
     -+  }
     -+
     -+  writer_reinit_block_writer(w, record_type(rec));
     -+  err = block_writer_add(w->block_writer, rec);
     -+  if (err < 0) {
     -+    result = err;
     -+    goto exit;
     -+  }
     -+
     -+  result = 0;
     ++static int obj_index_tree_node_compare(const void *a, const void *b)
     ++{
     ++	return slice_compare(((const struct obj_index_tree_node *)a)->hash,
     ++			     ((const struct obj_index_tree_node *)b)->hash);
     ++}
     ++
     ++static void writer_index_hash(struct writer *w, struct slice hash)
     ++{
     ++	uint64_t off = w->next;
     ++
     ++	struct obj_index_tree_node want = { .hash = hash };
     ++
     ++	struct tree_node *node = tree_search(&want, &w->obj_index_tree,
     ++					     &obj_index_tree_node_compare, 0);
     ++	struct obj_index_tree_node *key = NULL;
     ++	if (node == NULL) {
     ++		key = calloc(sizeof(struct obj_index_tree_node), 1);
     ++		slice_copy(&key->hash, hash);
     ++		tree_search((void *)key, &w->obj_index_tree,
     ++			    &obj_index_tree_node_compare, 1);
     ++	} else {
     ++		key = node->key;
     ++	}
     ++
     ++	if (key->offset_len > 0 && key->offsets[key->offset_len - 1] == off) {
     ++		return;
     ++	}
     ++
     ++	if (key->offset_len == key->offset_cap) {
     ++		key->offset_cap = 2 * key->offset_cap + 1;
     ++		key->offsets = realloc(key->offsets,
     ++				       sizeof(uint64_t) * key->offset_cap);
     ++	}
     ++
     ++	key->offsets[key->offset_len++] = off;
     ++}
     ++
     ++static int writer_add_record(struct writer *w, struct record rec)
     ++{
     ++	int result = -1;
     ++	struct slice key = {};
     ++	int err = 0;
     ++	record_key(rec, &key);
     ++	if (slice_compare(w->last_key, key) >= 0) {
     ++		goto exit;
     ++	}
     ++
     ++	slice_copy(&w->last_key, key);
     ++	if (w->block_writer == NULL) {
     ++		writer_reinit_block_writer(w, record_type(rec));
     ++	}
     ++
     ++	assert(block_writer_type(w->block_writer) == record_type(rec));
     ++
     ++	if (block_writer_add(w->block_writer, rec) == 0) {
     ++		result = 0;
     ++		goto exit;
     ++	}
     ++
     ++	err = writer_flush_block(w);
     ++	if (err < 0) {
     ++		result = err;
     ++		goto exit;
     ++	}
     ++
     ++	writer_reinit_block_writer(w, record_type(rec));
     ++	err = block_writer_add(w->block_writer, rec);
     ++	if (err < 0) {
     ++		result = err;
     ++		goto exit;
     ++	}
     ++
     ++	result = 0;
      +exit:
     -+  free(slice_yield(&key));
     -+  return result;
     -+}
     -+
     -+int writer_add_ref(struct writer *w, struct ref_record *ref) {
     -+  struct record rec = {};
     -+  struct ref_record copy = *ref;
     -+  int err = 0;
     -+
     -+  if (ref->update_index < w->min_update_index ||
     -+      ref->update_index > w->max_update_index) {
     -+    return API_ERROR;
     -+  }
     -+
     -+  record_from_ref(&rec, &copy);
     -+  copy.update_index -= w->min_update_index;
     -+  err = writer_add_record(w, rec);
     -+  if (err < 0) {
     -+    return err;
     -+  }
     -+
     -+  if (!w->opts.skip_index_objects && ref->value != NULL) {
     -+    struct slice h = {
     -+        .buf = ref->value,
     -+        .len = w->hash_size,
     -+    };
     -+
     -+    writer_index_hash(w, h);
     -+  }
     -+  if (!w->opts.skip_index_objects && ref->target_value != NULL) {
     -+    struct slice h = {
     -+        .buf = ref->target_value,
     -+        .len = w->hash_size,
     -+    };
     -+    writer_index_hash(w, h);
     -+  }
     -+  return 0;
     -+}
     -+
     -+int writer_add_refs(struct writer *w, struct ref_record *refs, int n) {
     -+  int err = 0;
     -+  qsort(refs, n, sizeof(struct ref_record), ref_record_compare_name);
     -+  for (int i = 0; err == 0 && i < n; i++) {
     -+    err = writer_add_ref(w, &refs[i]);
     -+  }
     -+  return err;
     -+}
     -+
     -+int writer_add_log(struct writer *w, struct log_record *log) {
     -+  if (w->block_writer != NULL &&
     -+      block_writer_type(w->block_writer) == BLOCK_TYPE_REF) {
     -+    int err = writer_finish_public_section(w);
     -+    if (err < 0) {
     -+      return err;
     -+    }
     -+  }
     -+
     -+  {
     -+    struct record rec = {};
     -+    int err;
     -+    record_from_log(&rec, log);
     -+    err = writer_add_record(w, rec);
     -+    return err;
     -+  }
     -+}
     -+
     -+int writer_add_logs(struct writer *w, struct log_record *logs, int n) {
     -+  int err = 0;
     -+  qsort(logs, n, sizeof(struct log_record), log_record_compare_key);
     -+  for (int i = 0; err == 0 && i < n; i++) {
     -+    err = writer_add_log(w, &logs[i]);
     -+  }
     -+  return err;
     -+}
     -+
     -+
     -+static int writer_finish_section(struct writer *w) {
     -+  byte typ = block_writer_type(w->block_writer);
     -+  uint64_t index_start = 0;
     -+  int max_level = 0;
     -+  int threshold = w->opts.unpadded ? 1 : 3;
     -+  int before_blocks = w->stats.idx_stats.blocks;
     -+  int err = writer_flush_block(w);
     -+  if (err < 0) {
     -+    return err;
     -+  }
     -+
     -+  while (w->index_len > threshold) {
     -+    struct index_record *idx = NULL;
     -+    int idx_len = 0;
     -+
     -+    max_level++;
     -+    index_start = w->next;
     -+    writer_reinit_block_writer(w, BLOCK_TYPE_INDEX);
     -+
     -+    idx = w->index;
     -+    idx_len = w->index_len;
     -+
     -+    w->index = NULL;
     -+    w->index_len = 0;
     -+    w->index_cap = 0;
     -+    for (int i = 0; i < idx_len; i++) {
     -+      struct record rec = {};
     -+      record_from_index(&rec, idx + i);
     -+      if (block_writer_add(w->block_writer, rec) == 0) {
     -+        continue;
     -+      }
     -+
     -+      {
     -+        int err = writer_flush_block(w);
     -+        if (err < 0) {
     -+          return err;
     -+        }
     -+      }
     -+
     -+      writer_reinit_block_writer(w, BLOCK_TYPE_INDEX);
     -+
     -+      err = block_writer_add(w->block_writer, rec);
     -+      assert(err == 0);
     -+    }
     -+    for (int i = 0; i < idx_len; i++) {
     -+      free(slice_yield(&idx[i].last_key));
     -+    }
     -+    free(idx);
     -+  }
     -+
     -+  writer_clear_index(w);
     -+
     -+  err = writer_flush_block(w);
     -+  if (err < 0) {
     -+    return err;
     -+  }
     -+
     -+  {
     -+    struct block_stats *bstats = writer_block_stats(w, typ);
     -+    bstats->index_blocks = w->stats.idx_stats.blocks - before_blocks;
     -+    bstats->index_offset = index_start;
     -+    bstats->max_index_level = max_level;
     -+  }
     -+
     -+  // Reinit lastKey, as the next section can start with any key.
     -+  w->last_key.len = 0;
     -+
     -+  return 0;
     ++	free(slice_yield(&key));
     ++	return result;
     ++}
     ++
     ++int writer_add_ref(struct writer *w, struct ref_record *ref)
     ++{
     ++	struct record rec = {};
     ++	struct ref_record copy = *ref;
     ++	int err = 0;
     ++
     ++	if (ref->ref_name == NULL) {
     ++		return API_ERROR;
     ++	}
     ++	if (ref->update_index < w->min_update_index ||
     ++	    ref->update_index > w->max_update_index) {
     ++		return API_ERROR;
     ++	}
     ++
     ++	record_from_ref(&rec, &copy);
     ++	copy.update_index -= w->min_update_index;
     ++	err = writer_add_record(w, rec);
     ++	if (err < 0) {
     ++		return err;
     ++	}
     ++
     ++	if (!w->opts.skip_index_objects && ref->value != NULL) {
     ++		struct slice h = {
     ++			.buf = ref->value,
     ++			.len = w->hash_size,
     ++		};
     ++
     ++		writer_index_hash(w, h);
     ++	}
     ++	if (!w->opts.skip_index_objects && ref->target_value != NULL) {
     ++		struct slice h = {
     ++			.buf = ref->target_value,
     ++			.len = w->hash_size,
     ++		};
     ++		writer_index_hash(w, h);
     ++	}
     ++	return 0;
     ++}
     ++
     ++int writer_add_refs(struct writer *w, struct ref_record *refs, int n)
     ++{
     ++	int err = 0;
     ++	int i = 0;
     ++	qsort(refs, n, sizeof(struct ref_record), ref_record_compare_name);
     ++	for (i = 0; err == 0 && i < n; i++) {
     ++		err = writer_add_ref(w, &refs[i]);
     ++	}
     ++	return err;
     ++}
     ++
     ++int writer_add_log(struct writer *w, struct log_record *log)
     ++{
     ++	if (log->ref_name == NULL) {
     ++		return API_ERROR;
     ++	}
     ++
     ++	if (w->block_writer != NULL &&
     ++	    block_writer_type(w->block_writer) == BLOCK_TYPE_REF) {
     ++		int err = writer_finish_public_section(w);
     ++		if (err < 0) {
     ++			return err;
     ++		}
     ++	}
     ++
     ++	w->next -= w->pending_padding;
     ++	w->pending_padding = 0;
     ++
     ++	{
     ++		struct record rec = {};
     ++		int err;
     ++		record_from_log(&rec, log);
     ++		err = writer_add_record(w, rec);
     ++		return err;
     ++	}
     ++}
     ++
     ++int writer_add_logs(struct writer *w, struct log_record *logs, int n)
     ++{
     ++	int err = 0;
     ++	int i = 0;
     ++	qsort(logs, n, sizeof(struct log_record), log_record_compare_key);
     ++	for (i = 0; err == 0 && i < n; i++) {
     ++		err = writer_add_log(w, &logs[i]);
     ++	}
     ++	return err;
     ++}
     ++
     ++static int writer_finish_section(struct writer *w)
     ++{
     ++	byte typ = block_writer_type(w->block_writer);
     ++	uint64_t index_start = 0;
     ++	int max_level = 0;
     ++	int threshold = w->opts.unpadded ? 1 : 3;
     ++	int before_blocks = w->stats.idx_stats.blocks;
     ++	int err = writer_flush_block(w);
     ++	int i = 0;
     ++	if (err < 0) {
     ++		return err;
     ++	}
     ++
     ++	while (w->index_len > threshold) {
     ++		struct index_record *idx = NULL;
     ++		int idx_len = 0;
     ++
     ++		max_level++;
     ++		index_start = w->next;
     ++		writer_reinit_block_writer(w, BLOCK_TYPE_INDEX);
     ++
     ++		idx = w->index;
     ++		idx_len = w->index_len;
     ++
     ++		w->index = NULL;
     ++		w->index_len = 0;
     ++		w->index_cap = 0;
     ++		for (i = 0; i < idx_len; i++) {
     ++			struct record rec = {};
     ++			record_from_index(&rec, idx + i);
     ++			if (block_writer_add(w->block_writer, rec) == 0) {
     ++				continue;
     ++			}
     ++
     ++			{
     ++				int err = writer_flush_block(w);
     ++				if (err < 0) {
     ++					return err;
     ++				}
     ++			}
     ++
     ++			writer_reinit_block_writer(w, BLOCK_TYPE_INDEX);
     ++
     ++			err = block_writer_add(w->block_writer, rec);
     ++			assert(err == 0);
     ++		}
     ++		for (i = 0; i < idx_len; i++) {
     ++			free(slice_yield(&idx[i].last_key));
     ++		}
     ++		free(idx);
     ++	}
     ++
     ++	writer_clear_index(w);
     ++
     ++	err = writer_flush_block(w);
     ++	if (err < 0) {
     ++		return err;
     ++	}
     ++
     ++	{
     ++		struct block_stats *bstats = writer_block_stats(w, typ);
     ++		bstats->index_blocks =
     ++			w->stats.idx_stats.blocks - before_blocks;
     ++		bstats->index_offset = index_start;
     ++		bstats->max_index_level = max_level;
     ++	}
     ++
     ++	/* Reinit lastKey, as the next section can start with any key. */
     ++	w->last_key.len = 0;
     ++
     ++	return 0;
      +}
      +
      +struct common_prefix_arg {
     -+  struct slice *last;
     -+  int max;
     ++	struct slice *last;
     ++	int max;
      +};
      +
     -+static void update_common(void *void_arg, void *key) {
     -+  struct common_prefix_arg *arg = (struct common_prefix_arg *)void_arg;
     -+  struct obj_index_tree_node *entry = (struct obj_index_tree_node *)key;
     -+  if (arg->last != NULL) {
     -+    int n = common_prefix_size(entry->hash, *arg->last);
     -+    if (n > arg->max) {
     -+      arg->max = n;
     -+    }
     -+  }
     -+  arg->last = &entry->hash;
     ++static void update_common(void *void_arg, void *key)
     ++{
     ++	struct common_prefix_arg *arg = (struct common_prefix_arg *)void_arg;
     ++	struct obj_index_tree_node *entry = (struct obj_index_tree_node *)key;
     ++	if (arg->last != NULL) {
     ++		int n = common_prefix_size(entry->hash, *arg->last);
     ++		if (n > arg->max) {
     ++			arg->max = n;
     ++		}
     ++	}
     ++	arg->last = &entry->hash;
      +}
      +
      +struct write_record_arg {
     -+  struct writer *w;
     -+  int err;
     ++	struct writer *w;
     ++	int err;
      +};
      +
     -+static void write_object_record(void *void_arg, void *key) {
     -+  struct write_record_arg *arg = (struct write_record_arg *)void_arg;
     -+  struct obj_index_tree_node *entry = (struct obj_index_tree_node *)key;
     -+  struct obj_record obj_rec = {
     -+      .hash_prefix = entry->hash.buf,
     -+      .hash_prefix_len = arg->w->stats.object_id_len,
     -+      .offsets = entry->offsets,
     -+      .offset_len = entry->offset_len,
     -+  };
     -+  struct record rec = {};
     -+  if (arg->err < 0) {
     -+    goto exit;
     -+  }
     -+
     -+  record_from_obj(&rec, &obj_rec);
     -+  arg->err = block_writer_add(arg->w->block_writer, rec);
     -+  if (arg->err == 0) {
     -+    goto exit;
     -+  }
     -+
     -+  arg->err = writer_flush_block(arg->w);
     -+  if (arg->err < 0) {
     -+    goto exit;
     -+  }
     -+
     -+  writer_reinit_block_writer(arg->w, BLOCK_TYPE_OBJ);
     -+  arg->err = block_writer_add(arg->w->block_writer, rec);
     -+  if (arg->err == 0) {
     -+    goto exit;
     -+  }
     -+  obj_rec.offset_len = 0;
     -+  arg->err = block_writer_add(arg->w->block_writer, rec);
     -+
     -+  // Should be able to write into a fresh block.
     -+  assert(arg->err == 0);
     ++static void write_object_record(void *void_arg, void *key)
     ++{
     ++	struct write_record_arg *arg = (struct write_record_arg *)void_arg;
     ++	struct obj_index_tree_node *entry = (struct obj_index_tree_node *)key;
     ++	struct obj_record obj_rec = {
     ++		.hash_prefix = entry->hash.buf,
     ++		.hash_prefix_len = arg->w->stats.object_id_len,
     ++		.offsets = entry->offsets,
     ++		.offset_len = entry->offset_len,
     ++	};
     ++	struct record rec = {};
     ++	if (arg->err < 0) {
     ++		goto exit;
     ++	}
     ++
     ++	record_from_obj(&rec, &obj_rec);
     ++	arg->err = block_writer_add(arg->w->block_writer, rec);
     ++	if (arg->err == 0) {
     ++		goto exit;
     ++	}
     ++
     ++	arg->err = writer_flush_block(arg->w);
     ++	if (arg->err < 0) {
     ++		goto exit;
     ++	}
     ++
     ++	writer_reinit_block_writer(arg->w, BLOCK_TYPE_OBJ);
     ++	arg->err = block_writer_add(arg->w->block_writer, rec);
     ++	if (arg->err == 0) {
     ++		goto exit;
     ++	}
     ++	obj_rec.offset_len = 0;
     ++	arg->err = block_writer_add(arg->w->block_writer, rec);
     ++
     ++	/* Should be able to write into a fresh block. */
     ++	assert(arg->err == 0);
      +
      +exit:;
      +}
      +
     -+static void object_record_free(void *void_arg, void *key) {
     -+  struct obj_index_tree_node *entry = (struct obj_index_tree_node *)key;
     -+
     -+  free(entry->offsets);
     -+  entry->offsets = NULL;
     -+  free(slice_yield(&entry->hash));
     -+  free(entry);
     -+}
     -+
     -+static int writer_dump_object_index(struct writer *w) {
     -+  struct write_record_arg closure = {.w = w};
     -+  struct common_prefix_arg common = {};
     -+  if (w->obj_index_tree != NULL) {
     -+    infix_walk(w->obj_index_tree, &update_common, &common);
     -+  }
     -+  w->stats.object_id_len = common.max + 1;
     -+
     -+  writer_reinit_block_writer(w, BLOCK_TYPE_OBJ);
     -+
     -+  if (w->obj_index_tree != NULL) {
     -+    infix_walk(w->obj_index_tree, &write_object_record, &closure);
     -+  }
     -+
     -+  if (closure.err < 0) {
     -+    return closure.err;
     -+  }
     -+  return writer_finish_section(w);
     -+}
     -+
     -+int writer_finish_public_section(struct writer *w) {
     -+  byte typ = 0;
     -+  int err = 0;
     -+
     -+  if (w->block_writer == NULL) {
     -+    return 0;
     -+  }
     -+
     -+  typ = block_writer_type(w->block_writer);
     -+  err = writer_finish_section(w);
     -+  if (err < 0) {
     -+    return err;
     -+  }
     -+  if (typ == BLOCK_TYPE_REF && !w->opts.skip_index_objects &&
     -+      w->stats.ref_stats.index_blocks > 0) {
     -+    err = writer_dump_object_index(w);
     -+    if (err < 0) {
     -+      return err;
     -+    }
     -+  }
     -+
     -+  if (w->obj_index_tree != NULL) {
     -+    infix_walk(w->obj_index_tree, &object_record_free, NULL);
     -+    tree_free(w->obj_index_tree);
     -+    w->obj_index_tree = NULL;
     -+  }
     -+
     -+  w->block_writer = NULL;
     -+  return 0;
     -+}
     -+
     -+int writer_close(struct writer *w) {
     -+  byte footer[68];
     -+  byte *p = footer;
     -+
     -+  writer_finish_public_section(w);
     -+
     -+  writer_write_header(w, footer);
     -+  p += 24;
     -+  put_u64(p, w->stats.ref_stats.index_offset);
     -+  p += 8;
     -+  put_u64(p, (w->stats.obj_stats.offset) << 5 | w->stats.object_id_len);
     -+  p += 8;
     -+  put_u64(p, w->stats.obj_stats.index_offset);
     -+  p += 8;
     -+
     -+  put_u64(p, w->stats.log_stats.offset);
     -+  p += 8;
     -+  put_u64(p, w->stats.log_stats.index_offset);
     -+  p += 8;
     -+
     -+  put_u32(p, crc32(0, footer, p - footer));
     -+  p += 4;
     -+  w->pending_padding = 0;
     -+
     -+  {
     -+    int n = padded_write(w, footer, sizeof(footer), 0);
     -+    if (n < 0) {
     -+      return n;
     -+    }
     -+  }
     -+
     -+  // free up memory.
     -+  block_writer_clear(&w->block_writer_data);
     -+  writer_clear_index(w);
     -+  free(slice_yield(&w->last_key));
     -+  return 0;
     -+}
     -+
     -+void writer_clear_index(struct writer *w) {
     -+  for (int i = 0; i < w->index_len; i++) {
     -+    free(slice_yield(&w->index[i].last_key));
     -+  }
     -+
     -+  free(w->index);
     -+  w->index = NULL;
     -+  w->index_len = 0;
     -+  w->index_cap = 0;
     ++static void object_record_free(void *void_arg, void *key)
     ++{
     ++	struct obj_index_tree_node *entry = (struct obj_index_tree_node *)key;
     ++
     ++	free(entry->offsets);
     ++	entry->offsets = NULL;
     ++	free(slice_yield(&entry->hash));
     ++	free(entry);
     ++}
     ++
     ++static int writer_dump_object_index(struct writer *w)
     ++{
     ++	struct write_record_arg closure = { .w = w };
     ++	struct common_prefix_arg common = {};
     ++	if (w->obj_index_tree != NULL) {
     ++		infix_walk(w->obj_index_tree, &update_common, &common);
     ++	}
     ++	w->stats.object_id_len = common.max + 1;
     ++
     ++	writer_reinit_block_writer(w, BLOCK_TYPE_OBJ);
     ++
     ++	if (w->obj_index_tree != NULL) {
     ++		infix_walk(w->obj_index_tree, &write_object_record, &closure);
     ++	}
     ++
     ++	if (closure.err < 0) {
     ++		return closure.err;
     ++	}
     ++	return writer_finish_section(w);
     ++}
     ++
     ++int writer_finish_public_section(struct writer *w)
     ++{
     ++	byte typ = 0;
     ++	int err = 0;
     ++
     ++	if (w->block_writer == NULL) {
     ++		return 0;
     ++	}
     ++
     ++	typ = block_writer_type(w->block_writer);
     ++	err = writer_finish_section(w);
     ++	if (err < 0) {
     ++		return err;
     ++	}
     ++	if (typ == BLOCK_TYPE_REF && !w->opts.skip_index_objects &&
     ++	    w->stats.ref_stats.index_blocks > 0) {
     ++		err = writer_dump_object_index(w);
     ++		if (err < 0) {
     ++			return err;
     ++		}
     ++	}
     ++
     ++	if (w->obj_index_tree != NULL) {
     ++		infix_walk(w->obj_index_tree, &object_record_free, NULL);
     ++		tree_free(w->obj_index_tree);
     ++		w->obj_index_tree = NULL;
     ++	}
     ++
     ++	w->block_writer = NULL;
     ++	return 0;
     ++}
     ++
     ++int writer_close(struct writer *w)
     ++{
     ++	byte footer[68];
     ++	byte *p = footer;
     ++
     ++	writer_finish_public_section(w);
     ++
     ++	writer_write_header(w, footer);
     ++	p += 24;
     ++	put_u64(p, w->stats.ref_stats.index_offset);
     ++	p += 8;
     ++	put_u64(p, (w->stats.obj_stats.offset) << 5 | w->stats.object_id_len);
     ++	p += 8;
     ++	put_u64(p, w->stats.obj_stats.index_offset);
     ++	p += 8;
     ++
     ++	put_u64(p, w->stats.log_stats.offset);
     ++	p += 8;
     ++	put_u64(p, w->stats.log_stats.index_offset);
     ++	p += 8;
     ++
     ++	put_u32(p, crc32(0, footer, p - footer));
     ++	p += 4;
     ++	w->pending_padding = 0;
     ++
     ++	{
     ++		int n = padded_write(w, footer, sizeof(footer), 0);
     ++		if (n < 0) {
     ++			return n;
     ++		}
     ++	}
     ++
     ++	/* free up memory. */
     ++	block_writer_clear(&w->block_writer_data);
     ++	writer_clear_index(w);
     ++	free(slice_yield(&w->last_key));
     ++	return 0;
     ++}
     ++
     ++void writer_clear_index(struct writer *w)
     ++{
     ++	int i = 0;
     ++	for (i = 0; i < w->index_len; i++) {
     ++		free(slice_yield(&w->index[i].last_key));
     ++	}
     ++
     ++	free(w->index);
     ++	w->index = NULL;
     ++	w->index_len = 0;
     ++	w->index_cap = 0;
      +}
      +
      +const int debug = 0;
      +
     -+static int writer_flush_nonempty_block(struct writer *w) {
     -+  byte typ = block_writer_type(w->block_writer);
     -+  struct block_stats *bstats = writer_block_stats(w, typ);
     -+  uint64_t block_typ_off = (bstats->blocks == 0) ? w->next : 0;
     -+  int raw_bytes = block_writer_finish(w->block_writer);
     -+  int padding = 0;
     -+  int err = 0;
     -+  if (raw_bytes < 0) {
     -+    return raw_bytes;
     -+  }
     -+
     -+  if (!w->opts.unpadded && typ != BLOCK_TYPE_LOG) {
     -+    padding = w->opts.block_size - raw_bytes;
     -+  }
     -+
     -+  if (block_typ_off > 0) {
     -+    bstats->offset = block_typ_off;
     -+  }
     -+
     -+  bstats->entries += w->block_writer->entries;
     -+  bstats->restarts += w->block_writer->restart_len;
     -+  bstats->blocks++;
     -+  w->stats.blocks++;
     -+
     -+  if (debug) {
     -+    fprintf(stderr, "block %c off %ld sz %d (%d)\n", typ, w->next, raw_bytes,
     -+            get_u24(w->block + w->block_writer->header_off + 1));
     -+  }
     -+
     -+  if (w->next == 0) {
     -+    writer_write_header(w, w->block);
     -+  }
     -+
     -+  err = padded_write(w, w->block, raw_bytes, padding);
     -+  if (err < 0) {
     -+    return err;
     -+  }
     -+
     -+  if (w->index_cap == w->index_len) {
     -+    w->index_cap = 2 * w->index_cap + 1;
     -+    w->index = realloc(w->index, sizeof(struct index_record) * w->index_cap);
     -+  }
     -+
     -+  {
     -+    struct index_record ir = {
     -+        .offset = w->next,
     -+    };
     -+    slice_copy(&ir.last_key, w->block_writer->last_key);
     -+    w->index[w->index_len] = ir;
     -+  }
     -+
     -+  w->index_len++;
     -+  w->next += padding + raw_bytes;
     -+  block_writer_reset(&w->block_writer_data);
     -+  w->block_writer = NULL;
     -+  return 0;
     -+}
     -+
     -+int writer_flush_block(struct writer *w) {
     -+  if (w->block_writer == NULL) {
     -+    return 0;
     -+  }
     -+  if (w->block_writer->entries == 0) {
     -+    return 0;
     -+  }
     -+  return writer_flush_nonempty_block(w);
     -+}
     -+
     -+struct stats *writer_stats(struct writer *w) {
     -+  return &w->stats;
     ++static int writer_flush_nonempty_block(struct writer *w)
     ++{
     ++	byte typ = block_writer_type(w->block_writer);
     ++	struct block_stats *bstats = writer_block_stats(w, typ);
     ++	uint64_t block_typ_off = (bstats->blocks == 0) ? w->next : 0;
     ++	int raw_bytes = block_writer_finish(w->block_writer);
     ++	int padding = 0;
     ++	int err = 0;
     ++	if (raw_bytes < 0) {
     ++		return raw_bytes;
     ++	}
     ++
     ++	if (!w->opts.unpadded && typ != BLOCK_TYPE_LOG) {
     ++		padding = w->opts.block_size - raw_bytes;
     ++	}
     ++
     ++	if (block_typ_off > 0) {
     ++		bstats->offset = block_typ_off;
     ++	}
     ++
     ++	bstats->entries += w->block_writer->entries;
     ++	bstats->restarts += w->block_writer->restart_len;
     ++	bstats->blocks++;
     ++	w->stats.blocks++;
     ++
     ++	if (debug) {
     ++		fprintf(stderr, "block %c off %" PRIuMAX " sz %d (%d)\n", typ,
     ++			w->next, raw_bytes,
     ++			get_u24(w->block + w->block_writer->header_off + 1));
     ++	}
     ++
     ++	if (w->next == 0) {
     ++		writer_write_header(w, w->block);
     ++	}
     ++
     ++	err = padded_write(w, w->block, raw_bytes, padding);
     ++	if (err < 0) {
     ++		return err;
     ++	}
     ++
     ++	if (w->index_cap == w->index_len) {
     ++		w->index_cap = 2 * w->index_cap + 1;
     ++		w->index = realloc(w->index,
     ++				   sizeof(struct index_record) * w->index_cap);
     ++	}
     ++
     ++	{
     ++		struct index_record ir = {
     ++			.offset = w->next,
     ++		};
     ++		slice_copy(&ir.last_key, w->block_writer->last_key);
     ++		w->index[w->index_len] = ir;
     ++	}
     ++
     ++	w->index_len++;
     ++	w->next += padding + raw_bytes;
     ++	block_writer_reset(&w->block_writer_data);
     ++	w->block_writer = NULL;
     ++	return 0;
     ++}
     ++
     ++int writer_flush_block(struct writer *w)
     ++{
     ++	if (w->block_writer == NULL) {
     ++		return 0;
     ++	}
     ++	if (w->block_writer->entries == 0) {
     ++		return 0;
     ++	}
     ++	return writer_flush_nonempty_block(w);
     ++}
     ++
     ++struct stats *writer_stats(struct writer *w)
     ++{
     ++	return &w->stats;
      +}
      
       diff --git a/reftable/writer.h b/reftable/writer.h
     @@ -7896,27 +8161,27 @@
      +#include "tree.h"
      +
      +struct writer {
     -+  int (*write)(void *, byte *, int);
     -+  void *write_arg;
     -+  int pending_padding;
     -+  int hash_size;
     -+  struct slice last_key;
     -+
     -+  uint64_t next;
     -+  uint64_t min_update_index, max_update_index;
     -+  struct write_options opts;
     -+
     -+  byte *block;
     -+  struct block_writer *block_writer;
     -+  struct block_writer block_writer_data;
     -+  struct index_record *index;
     -+  int index_len;
     -+  int index_cap;
     -+
     -+  // tree for use with tsearch
     -+  struct tree_node *obj_index_tree;
     -+
     -+  struct stats stats;
     ++	int (*write)(void *, byte *, int);
     ++	void *write_arg;
     ++	int pending_padding;
     ++	int hash_size;
     ++	struct slice last_key;
     ++
     ++	uint64_t next;
     ++	uint64_t min_update_index, max_update_index;
     ++	struct write_options opts;
     ++
     ++	byte *block;
     ++	struct block_writer *block_writer;
     ++	struct block_writer block_writer_data;
     ++	struct index_record *index;
     ++	int index_len;
     ++	int index_cap;
     ++
     ++	/* tree for use with tsearch */
     ++	struct tree_node *obj_index_tree;
     ++
     ++	struct stats stats;
      +};
      +
      +int writer_flush_block(struct writer *w);
 5:  a615afa0a8 ! 5:  721201269d Reftable support for git-core
     @@ -2,33 +2,39 @@
      
          Reftable support for git-core
      
     -    $ ~/vc/git/git init
     -    warning: templates not found in /usr/local/google/home/hanwen/share/git-core/templates
     -    Initialized empty Git repository in /tmp/qz/.git/
     -    $ echo q > a
     -    $ ~/vc/git/git add a
     -    $ ~/vc/git/git commit -mx
     -    fatal: not a git repository (or any of the parent directories): .git
     -    [master (root-commit) 373d969] x
     -     1 file changed, 1 insertion(+)
     -     create mode 100644 a
     -    $ ~/vc/git/git show-ref
     -    373d96972fca9b63595740bba3898a762778ba20 HEAD
     -    373d96972fca9b63595740bba3898a762778ba20 refs/heads/master
     -    $ ls -l .git/reftable/
     -    total 12
     -    -rw------- 1 hanwen primarygroup  126 Jan 23 20:08 000000000001-000000000001.ref
     -    -rw------- 1 hanwen primarygroup 4277 Jan 23 20:08 000000000002-000000000002.ref
     -    $ go run ~/vc/reftable/cmd/dump.go  -table /tmp/qz/.git/reftable/000000000002-000000000002.ref
     -    ** DEBUG **
     -    name /tmp/qz/.git/reftable/000000000002-000000000002.ref, sz 4209: 'r' reftable.readerOffsets{Present:true, Offset:0x0, IndexOffset:0x0}, 'o' reftable.readerOffsets{Present:false, Offset:0x0, IndexOffset:0x0} 'g' reftable.readerOffsets{Present:true, Offset:0x1000, IndexOffset:0x0}
     -    ** REFS **
     -    reftable.RefRecord{RefName:"refs/heads/master", UpdateIndex:0x2, Value:[]uint8{0x37, 0x3d, 0x96, 0x97, 0x2f, 0xca, 0x9b, 0x63, 0x59, 0x57, 0x40, 0xbb, 0xa3, 0x89, 0x8a, 0x76, 0x27, 0x78, 0xba, 0x20}, TargetValue:[]uint8(nil), Target:""}
     -    ** LOGS **
     -    reftable.LogRecord{RefName:"HEAD", UpdateIndex:0x2, New:[]uint8{0x37, 0x3d, 0x96, 0x97, 0x2f, 0xca, 0x9b, 0x63, 0x59, 0x57, 0x40, 0xbb, 0xa3, 0x89, 0x8a, 0x76, 0x27, 0x78, 0xba, 0x20}, Old:[]uint8{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, Name:"Han-Wen Nienhuys", Email:"hanwen@google.com", Time:0x5e29ef27, TZOffset:100, Message:"commit (initial): x\n"}
     +    For background, see the previous commit introducing the library.
      
          TODO:
     -     * resolve the design problem with reflog expiry.
     +
     +     * Resolve the design problem with reflog expiry.
     +     * Resolve spots marked with XXX
     +
     +    Example use:
     +
     +      $ ~/vc/git/git init
     +      warning: templates not found in /usr/local/google/home/hanwen/share/git-core/templates
     +      Initialized empty Git repository in /tmp/qz/.git/
     +      $ echo q > a
     +      $ ~/vc/git/git add a
     +      $ ~/vc/git/git commit -mx
     +      fatal: not a git repository (or any of the parent directories): .git
     +      [master (root-commit) 373d969] x
     +       1 file changed, 1 insertion(+)
     +       create mode 100644 a
     +      $ ~/vc/git/git show-ref
     +      373d96972fca9b63595740bba3898a762778ba20 HEAD
     +      373d96972fca9b63595740bba3898a762778ba20 refs/heads/master
     +      $ ls -l .git/reftable/
     +      total 12
     +      -rw------- 1 hanwen primarygroup  126 Jan 23 20:08 000000000001-000000000001.ref
     +      -rw------- 1 hanwen primarygroup 4277 Jan 23 20:08 000000000002-000000000002.ref
     +      $ go run ~/vc/reftable/cmd/dump.go  -table /tmp/qz/.git/reftable/000000000002-000000000002.ref
     +      ** DEBUG **
     +      name /tmp/qz/.git/reftable/000000000002-000000000002.ref, sz 4209: 'r' reftable.readerOffsets{Present:true, Offset:0x0, IndexOffset:0x0}, 'o' reftable.readerOffsets{Present:false, Offset:0x0, IndexOffset:0x0} 'g' reftable.readerOffsets{Present:true, Offset:0x1000, IndexOffset:0x0}
     +      ** REFS **
     +      reftable.RefRecord{RefName:"refs/heads/master", UpdateIndex:0x2, Value:[]uint8{0x37, 0x3d, 0x96, 0x97, 0x2f, 0xca, 0x9b, 0x63, 0x59, 0x57, 0x40, 0xbb, 0xa3, 0x89, 0x8a, 0x76, 0x27, 0x78, 0xba, 0x20}, TargetValue:[]uint8(nil), Target:""}
     +      ** LOGS **
     +      reftable.LogRecord{RefName:"HEAD", UpdateIndex:0x2, New:[]uint8{0x37, 0x3d, 0x96, 0x97, 0x2f, 0xca, 0x9b, 0x63, 0x59, 0x57, 0x40, 0xbb, 0xa3, 0x89, 0x8a, 0x76, 0x27, 0x78, 0xba, 0x20}, Old:[]uint8{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, Name:"Han-Wen Nienhuys", Email:"hanwen@google.com", Time:0x5e29ef27, TZOffset:100, Message:"commit (initial): x\n"}
      
          Change-Id: I225ee6317b7911edf9aa95f43299f6c7c4511914
          Signed-off-by: Han-Wen Nienhuys <hanwen@google.com>
     @@ -120,8 +126,8 @@ $^
      -	struct ref_storage_be *be = find_ref_storage_backend(be_name);
      +	struct strbuf refs_path = STRBUF_INIT;
      +
     -+        // XXX this should probably come from a git config setting and not
     -+        // default to reftable.
     ++        /* XXX this should probably come from a git config setting and not
     ++           default to reftable. */
      +	const char *be_name = "reftable";
      +	struct ref_storage_be *be;
       	struct ref_store *refs;
     @@ -146,7 +152,7 @@ $^
       				     each_reflog_ent_fn fn,
       				     void *cb_data);
      +
     -+// XXX which ordering are these? Newest or oldest first?
     ++/* XXX which ordering are these? Newest or oldest first? */
       int for_each_reflog_ent(const char *refname, each_reflog_ent_fn fn, void *cb_data);
       int for_each_reflog_ent_reverse(const char *refname, each_reflog_ent_fn fn, void *cb_data);
       
     @@ -190,40 +196,45 @@ $^
      +	struct stack *stack;
      +};
      +
     -+static void clear_log_record(struct log_record* log) {
     -+        log->old_hash = NULL;
     -+        log->new_hash = NULL;
     -+        log->message = NULL;
     -+        log->ref_name = NULL;
     -+        log_record_clear(log);
     -+}
     -+
     -+static void fill_log_record(struct log_record* log) {
     -+        const char* info = git_committer_info(0);
     -+        struct ident_split split = {};
     -+        int result = split_ident_line(&split, info, strlen(info));
     -+        int sign = 1;
     -+        assert(0==result);
     -+
     -+        log_record_clear(log);
     -+        log->name = xstrndup(split.name_begin, split.name_end - split.name_begin);
     -+        log->email = xstrndup(split.mail_begin, split.mail_end - split.mail_begin);
     -+        log->time = atol(split.date_begin);
     -+        if (*split.tz_begin == '-') {
     -+                sign = -1;
     -+                split.tz_begin ++;
     -+        }
     -+        if (*split.tz_begin == '+') {
     -+                sign = 1;
     -+                split.tz_begin ++;
     -+        }
     -+
     -+        log->tz_offset = sign * atoi(split.tz_begin);
     ++static void clear_log_record(struct log_record *log)
     ++{
     ++	log->old_hash = NULL;
     ++	log->new_hash = NULL;
     ++	log->message = NULL;
     ++	log->ref_name = NULL;
     ++	log_record_clear(log);
     ++}
     ++
     ++static void fill_log_record(struct log_record *log)
     ++{
     ++	const char *info = git_committer_info(0);
     ++	struct ident_split split = {};
     ++	int result = split_ident_line(&split, info, strlen(info));
     ++	int sign = 1;
     ++	assert(0 == result);
     ++
     ++	log_record_clear(log);
     ++	log->name =
     ++		xstrndup(split.name_begin, split.name_end - split.name_begin);
     ++	log->email =
     ++		xstrndup(split.mail_begin, split.mail_end - split.mail_begin);
     ++	log->time = atol(split.date_begin);
     ++	if (*split.tz_begin == '-') {
     ++		sign = -1;
     ++		split.tz_begin++;
     ++	}
     ++	if (*split.tz_begin == '+') {
     ++		sign = 1;
     ++		split.tz_begin++;
     ++	}
     ++
     ++	log->tz_offset = sign * atoi(split.tz_begin);
      +}
      +
      +static struct ref_store *reftable_ref_store_create(const char *path,
     -+                                                   unsigned int store_flags) {
     -+  	struct reftable_ref_store *refs = xcalloc(1, sizeof(*refs));
     ++						   unsigned int store_flags)
     ++{
     ++	struct reftable_ref_store *refs = xcalloc(1, sizeof(*refs));
      +	struct ref_store *ref_store = (struct ref_store *)refs;
      +	struct write_options cfg = {
      +		.block_size = 4096,
     @@ -240,17 +251,19 @@ $^
      +	refs->reftable_dir = xstrdup(sb.buf);
      +	strbuf_release(&sb);
      +
     -+	refs->err = new_stack(&refs->stack, refs->reftable_dir, refs->table_list_file, cfg);
     ++	refs->err = new_stack(&refs->stack, refs->reftable_dir,
     ++			      refs->table_list_file, cfg);
      +
      +	return ref_store;
      +}
      +
      +static int reftable_init_db(struct ref_store *ref_store, struct strbuf *err)
      +{
     -+	struct reftable_ref_store *refs = (struct reftable_ref_store*) ref_store;
     ++	struct reftable_ref_store *refs =
     ++		(struct reftable_ref_store *)ref_store;
      +	FILE *f = fopen(refs->table_list_file, "a");
      +	if (f == NULL) {
     -+	  return -1;
     ++		return -1;
      +	}
      +	fclose(f);
      +
     @@ -258,56 +271,58 @@ $^
      +	return 0;
      +}
      +
     -+
      +struct reftable_iterator {
      +	struct ref_iterator base;
      +	struct iterator iter;
      +	struct ref_record ref;
      +	struct object_id oid;
     -+        struct ref_store *ref_store;
     ++	struct ref_store *ref_store;
      +	char *prefix;
      +};
      +
      +static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator)
      +{
     -+        while (1) {
     -+                struct reftable_iterator *ri = (struct reftable_iterator*) ref_iterator;
     -+                int err = iterator_next_ref(ri->iter, &ri->ref);
     -+                if (err > 0) {
     -+                        return ITER_DONE;
     -+                }
     -+                if (err < 0) {
     -+                        return ITER_ERROR;
     -+                }
     -+
     -+                ri->base.refname = ri->ref.ref_name;
     -+                if (ri->prefix != NULL && 0 != strncmp(ri->prefix, ri->ref.ref_name, strlen(ri->prefix))) {
     -+                        return ITER_DONE;
     -+                }
     -+
     -+                ri->base.flags = 0;
     -+                if (ri->ref.value != NULL) {
     -+                        memcpy(ri->oid.hash, ri->ref.value, GIT_SHA1_RAWSZ);
     -+                } else if (ri->ref.target != NULL) {
     -+                        int out_flags = 0;
     -+                        const char *resolved =
     -+                                refs_resolve_ref_unsafe(ri->ref_store, ri->ref.ref_name, RESOLVE_REF_READING,
     -+                                                        &ri->oid, &out_flags);
     -+                        ri->base.flags = out_flags;
     -+                        if (resolved == NULL && !(ri->base.flags & DO_FOR_EACH_INCLUDE_BROKEN)
     -+                            && (ri->base.flags & REF_ISBROKEN)) {
     -+                                continue;
     -+                        }
     -+                }
     -+                ri->base.oid  = &ri->oid;
     -+                return ITER_OK;
     -+        }
     ++	while (1) {
     ++		struct reftable_iterator *ri =
     ++			(struct reftable_iterator *)ref_iterator;
     ++		int err = iterator_next_ref(ri->iter, &ri->ref);
     ++		if (err > 0) {
     ++			return ITER_DONE;
     ++		}
     ++		if (err < 0) {
     ++			return ITER_ERROR;
     ++		}
     ++
     ++		ri->base.refname = ri->ref.ref_name;
     ++		if (ri->prefix != NULL &&
     ++		    strncmp(ri->prefix, ri->ref.ref_name, strlen(ri->prefix))) {
     ++			return ITER_DONE;
     ++		}
     ++
     ++		ri->base.flags = 0;
     ++		if (ri->ref.value != NULL) {
     ++			memcpy(ri->oid.hash, ri->ref.value, GIT_SHA1_RAWSZ);
     ++		} else if (ri->ref.target != NULL) {
     ++			int out_flags = 0;
     ++			const char *resolved = refs_resolve_ref_unsafe(
     ++				ri->ref_store, ri->ref.ref_name,
     ++				RESOLVE_REF_READING, &ri->oid, &out_flags);
     ++			ri->base.flags = out_flags;
     ++			if (resolved == NULL &&
     ++			    !(ri->base.flags & DO_FOR_EACH_INCLUDE_BROKEN) &&
     ++			    (ri->base.flags & REF_ISBROKEN)) {
     ++				continue;
     ++			}
     ++		}
     ++		ri->base.oid = &ri->oid;
     ++		return ITER_OK;
     ++	}
      +}
      +
      +static int reftable_ref_iterator_peel(struct ref_iterator *ref_iterator,
      +				      struct object_id *peeled)
      +{
     -+	struct reftable_iterator *ri = (struct reftable_iterator*) ref_iterator;
     ++	struct reftable_iterator *ri = (struct reftable_iterator *)ref_iterator;
      +	if (ri->ref.target_value != NULL) {
      +		memcpy(peeled->hash, ri->ref.target_value, GIT_SHA1_RAWSZ);
      +		return 0;
     @@ -318,40 +333,40 @@ $^
      +
      +static int reftable_ref_iterator_abort(struct ref_iterator *ref_iterator)
      +{
     -+	struct reftable_iterator *ri = (struct reftable_iterator*) ref_iterator;
     ++	struct reftable_iterator *ri = (struct reftable_iterator *)ref_iterator;
      +	ref_record_clear(&ri->ref);
      +	iterator_destroy(&ri->iter);
      +	return 0;
      +}
      +
      +static struct ref_iterator_vtable reftable_ref_iterator_vtable = {
     -+	reftable_ref_iterator_advance,
     -+	reftable_ref_iterator_peel,
     ++	reftable_ref_iterator_advance, reftable_ref_iterator_peel,
      +	reftable_ref_iterator_abort
      +};
      +
     -+static struct ref_iterator *reftable_ref_iterator_begin(
     -+		struct ref_store *ref_store,
     -+		const char *prefix, unsigned int flags)
     ++static struct ref_iterator *
     ++reftable_ref_iterator_begin(struct ref_store *ref_store, const char *prefix,
     ++			    unsigned int flags)
      +{
     -+	struct reftable_ref_store *refs = (struct reftable_ref_store*) ref_store;
     ++	struct reftable_ref_store *refs =
     ++		(struct reftable_ref_store *)ref_store;
      +	struct reftable_iterator *ri = xcalloc(1, sizeof(*ri));
      +	struct merged_table *mt = NULL;
      +	int err = 0;
      +	if (refs->err) {
     -+		// XXX ?
     ++		/* how to propagate errors? */
      +		return NULL;
      +	}
      +
      +	mt = stack_merged_table(refs->stack);
      +
     -+	// XXX something with flags?
     ++	/* XXX something with flags? */
      +	err = merged_table_seek_ref(mt, &ri->iter, prefix);
     -+	// XXX what to do with err?
     ++	/* XXX what to do with err? */
      +	assert(err == 0);
      +	base_ref_iterator_init(&ri->base, &reftable_ref_iterator_vtable, 1);
      +	ri->base.oid = &ri->oid;
     -+        ri->ref_store = ref_store;
     ++	ri->ref_store = ref_store;
      +	return &ri->base;
      +}
      +
     @@ -363,92 +378,100 @@ $^
      +}
      +
      +static int reftable_transaction_abort(struct ref_store *ref_store,
     -+				    struct ref_transaction *transaction,
     -+				    struct strbuf *err)
     ++				      struct ref_transaction *transaction,
     ++				      struct strbuf *err)
      +{
     -+	struct reftable_ref_store *refs = (struct reftable_ref_store*) ref_store;
     ++	struct reftable_ref_store *refs =
     ++		(struct reftable_ref_store *)ref_store;
      +	(void)refs;
      +	return 0;
      +}
      +
     -+
     -+static int ref_update_cmp(const void *a, const void *b) {
     -+	return strcmp(((struct ref_update*)a)->refname, ((struct ref_update*)b)->refname);
     ++static int ref_update_cmp(const void *a, const void *b)
     ++{
     ++	return strcmp(((struct ref_update *)a)->refname,
     ++		      ((struct ref_update *)b)->refname);
      +}
      +
     -+static int reftable_check_old_oid(struct ref_store *refs, const char *refname, struct object_id *want_oid) {
     -+        struct object_id out_oid = {};
     -+        int out_flags = 0;
     -+        const char *resolved =
     -+                refs_resolve_ref_unsafe(refs, refname, RESOLVE_REF_READING,
     -+                                        &out_oid, &out_flags);
     -+        if (is_null_oid(want_oid) != (resolved == NULL)) {
     -+                return LOCK_ERROR;
     -+        }
     ++static int reftable_check_old_oid(struct ref_store *refs, const char *refname,
     ++				  struct object_id *want_oid)
     ++{
     ++	struct object_id out_oid = {};
     ++	int out_flags = 0;
     ++	const char *resolved = refs_resolve_ref_unsafe(
     ++		refs, refname, RESOLVE_REF_READING, &out_oid, &out_flags);
     ++	if (is_null_oid(want_oid) != (resolved == NULL)) {
     ++		return LOCK_ERROR;
     ++	}
      +
     -+        if (resolved != NULL && !oideq(&out_oid, want_oid)) {
     -+                return LOCK_ERROR;
     -+        }
     ++	if (resolved != NULL && !oideq(&out_oid, want_oid)) {
     ++		return LOCK_ERROR;
     ++	}
      +
     -+        return 0;
     ++	return 0;
      +}
      +
     -+static int write_transaction_table(struct writer *writer, void *arg) {
     ++static int write_transaction_table(struct writer *writer, void *arg)
     ++{
      +	struct ref_transaction *transaction = (struct ref_transaction *)arg;
     -+	struct reftable_ref_store *refs
     -+		= (struct reftable_ref_store*)transaction->ref_store;
     ++	struct reftable_ref_store *refs =
     ++		(struct reftable_ref_store *)transaction->ref_store;
      +	uint64_t ts = stack_next_update_index(refs->stack);
      +	int err = 0;
     -+	// XXX - are we allowed to mutate the input data?
     -+	qsort(transaction->updates, transaction->nr, sizeof(struct ref_update*),
     -+	      ref_update_cmp);
     ++	/* XXX - are we allowed to mutate the input data? */
     ++	qsort(transaction->updates, transaction->nr,
     ++	      sizeof(struct ref_update *), ref_update_cmp);
      +	writer_set_limits(writer, ts, ts);
      +
     -+	for (int i = 0; i <  transaction->nr; i++) {
     -+		struct ref_update * u = transaction->updates[i];
     ++	for (int i = 0; i < transaction->nr; i++) {
     ++		struct ref_update *u = transaction->updates[i];
      +		if (u->flags & REF_HAVE_OLD) {
     -+			err = reftable_check_old_oid(transaction->ref_store, u->refname, &u->old_oid);
     ++			err = reftable_check_old_oid(transaction->ref_store,
     ++						     u->refname, &u->old_oid);
      +			if (err < 0) {
      +				goto exit;
      +			}
      +		}
      +	}
      +
     -+	for (int i = 0; i <  transaction->nr; i++) {
     -+		struct ref_update * u = transaction->updates[i];
     ++	for (int i = 0; i < transaction->nr; i++) {
     ++		struct ref_update *u = transaction->updates[i];
      +		if (u->flags & REF_HAVE_NEW) {
     -+                        struct object_id out_oid = {};
     -+                        int out_flags = 0;
     -+                        // XXX who owns the memory here?
     -+                        const char *resolved
     -+                                = refs_resolve_ref_unsafe(transaction->ref_store, u->refname, 0, &out_oid, &out_flags);
     -+                        struct ref_record ref = {};
     -+                        ref.ref_name = (char*)(resolved ? resolved : u->refname);
     -+                        ref.value = u->new_oid.hash;
     -+                        ref.update_index = ts;
     -+                        err = writer_add_ref(writer, &ref);
     -+                        if (err < 0) {
     -+                                goto exit;
     -+                        }
     ++			struct object_id out_oid = {};
     ++			int out_flags = 0;
     ++			/* XXX who owns the memory here? */
     ++			const char *resolved = refs_resolve_ref_unsafe(
     ++				transaction->ref_store, u->refname, 0, &out_oid,
     ++				&out_flags);
     ++			struct ref_record ref = {};
     ++			ref.ref_name =
     ++				(char *)(resolved ? resolved : u->refname);
     ++			ref.value = u->new_oid.hash;
     ++			ref.update_index = ts;
     ++			err = writer_add_ref(writer, &ref);
     ++			if (err < 0) {
     ++				goto exit;
     ++			}
      +		}
      +	}
      +
     ++	for (int i = 0; i < transaction->nr; i++) {
     ++		struct ref_update *u = transaction->updates[i];
     ++		struct log_record log = {};
     ++		fill_log_record(&log);
      +
     -+	for (int i = 0; i <  transaction->nr; i++) {
     -+		struct ref_update * u = transaction->updates[i];
     -+                struct log_record log = {};
     -+                fill_log_record(&log);
     ++		log.ref_name = (char *)u->refname;
     ++		log.old_hash = u->old_oid.hash;
     ++		log.new_hash = u->new_oid.hash;
     ++		log.update_index = ts;
     ++		log.message = u->msg;
      +
     -+                log.ref_name = (char*)u->refname;
     -+                log.old_hash = u->old_oid.hash;
     -+                log.new_hash = u->new_oid.hash;
     -+                log.update_index = ts;
     -+                log.message = u->msg;
     -+
     -+                err = writer_add_log(writer, &log);
     -+                clear_log_record(&log);
     -+                if (err < 0) { return err; }
     -+        }
     ++		err = writer_add_log(writer, &log);
     ++		clear_log_record(&log);
     ++		if (err < 0) {
     ++			return err;
     ++		}
     ++	}
      +exit:
      +	return err;
      +}
     @@ -457,89 +480,91 @@ $^
      +				       struct ref_transaction *transaction,
      +				       struct strbuf *errmsg)
      +{
     -+	struct reftable_ref_store *refs = (struct reftable_ref_store*) ref_store;
     -+	int err = stack_add(refs->stack,
     -+		  &write_transaction_table,
     -+		  transaction);
     ++	struct reftable_ref_store *refs =
     ++		(struct reftable_ref_store *)ref_store;
     ++	int err = stack_add(refs->stack, &write_transaction_table, transaction);
      +	if (err < 0) {
     -+		strbuf_addf(errmsg, "reftable: transaction failure %s", error_str(err));
     ++		strbuf_addf(errmsg, "reftable: transaction failure %s",
     ++			    error_str(err));
      +		return -1;
     -+ 	}
     ++	}
      +
      +	return 0;
      +}
      +
     -+
      +static int reftable_transaction_finish(struct ref_store *ref_store,
     -+				     struct ref_transaction *transaction,
     -+				     struct strbuf *err)
     ++				       struct ref_transaction *transaction,
     ++				       struct strbuf *err)
      +{
     -+        return reftable_transaction_commit(ref_store, transaction, err);
     ++	return reftable_transaction_commit(ref_store, transaction, err);
      +}
      +
     -+
     -+
      +struct write_delete_refs_arg {
      +	struct stack *stack;
      +	struct string_list *refnames;
      +	const char *logmsg;
     -+        unsigned int flags;
     ++	unsigned int flags;
      +};
      +
     -+static int write_delete_refs_table(struct writer *writer, void *argv) {
     -+	struct write_delete_refs_arg *arg = (struct write_delete_refs_arg*)argv;
     ++static int write_delete_refs_table(struct writer *writer, void *argv)
     ++{
     ++	struct write_delete_refs_arg *arg =
     ++		(struct write_delete_refs_arg *)argv;
      +	uint64_t ts = stack_next_update_index(arg->stack);
      +	int err = 0;
      +
     -+        writer_set_limits(writer, ts, ts);
     -+        for (int i = 0; i < arg->refnames->nr; i++) {
     -+                struct ref_record ref = {
     -+                        .ref_name = (char*) arg->refnames->items[i].string,
     -+                        .update_index = ts,
     -+                };
     -+                err = writer_add_ref(writer, &ref);
     -+                if (err < 0) {
     -+                        return err;
     -+                }
     -+        }
     -+
     -+        for (int i = 0; i < arg->refnames->nr; i++) {
     -+                struct log_record log = {};
     -+                fill_log_record(&log);
     -+                log.message = xstrdup(arg->logmsg);
     -+                log.new_hash = NULL;
     -+
     -+                // XXX should lookup old oid.
     -+                log.old_hash = NULL;
     -+                log.update_index = ts;
     -+                log.ref_name = (char*) arg->refnames->items[i].string;
     -+
     -+                err = writer_add_log(writer, &log);
     -+                clear_log_record(&log);
     -+                if (err < 0) {
     -+                        return err;
     -+                }
     -+        }
     ++	writer_set_limits(writer, ts, ts);
     ++	for (int i = 0; i < arg->refnames->nr; i++) {
     ++		struct ref_record ref = {
     ++			.ref_name = (char *)arg->refnames->items[i].string,
     ++			.update_index = ts,
     ++		};
     ++		err = writer_add_ref(writer, &ref);
     ++		if (err < 0) {
     ++			return err;
     ++		}
     ++	}
     ++
     ++	for (int i = 0; i < arg->refnames->nr; i++) {
     ++		struct log_record log = {};
     ++		fill_log_record(&log);
     ++		log.message = xstrdup(arg->logmsg);
     ++		log.new_hash = NULL;
     ++
     ++		/* XXX should lookup old oid. */
     ++		log.old_hash = NULL;
     ++		log.update_index = ts;
     ++		log.ref_name = (char *)arg->refnames->items[i].string;
     ++
     ++		err = writer_add_log(writer, &log);
     ++		clear_log_record(&log);
     ++		if (err < 0) {
     ++			return err;
     ++		}
     ++	}
      +	return 0;
      +}
      +
      +static int reftable_delete_refs(struct ref_store *ref_store, const char *msg,
     -+                                struct string_list *refnames, unsigned int flags)
     ++				struct string_list *refnames,
     ++				unsigned int flags)
      +{
     -+	struct reftable_ref_store *refs = (struct reftable_ref_store*)ref_store;
     ++	struct reftable_ref_store *refs =
     ++		(struct reftable_ref_store *)ref_store;
      +	struct write_delete_refs_arg arg = {
      +		.stack = refs->stack,
      +		.refnames = refnames,
      +		.logmsg = msg,
     -+                .flags = flags,
     ++		.flags = flags,
      +	};
      +	return stack_add(refs->stack, &write_delete_refs_table, &arg);
      +}
      +
      +static int reftable_pack_refs(struct ref_store *ref_store, unsigned int flags)
      +{
     -+	struct reftable_ref_store *refs = (struct reftable_ref_store*)ref_store;
     -+        // XXX reflog expiry.
     ++	struct reftable_ref_store *refs =
     ++		(struct reftable_ref_store *)ref_store;
     ++	/* XXX reflog expiry. */
      +	return stack_compact_all(refs->stack, NULL);
      +}
      +
     @@ -550,14 +575,16 @@ $^
      +	const char *logmsg;
      +};
      +
     -+static int write_create_symref_table(struct writer *writer, void *arg) {
     -+	struct write_create_symref_arg *create = (struct write_create_symref_arg*)arg;
     ++static int write_create_symref_table(struct writer *writer, void *arg)
     ++{
     ++	struct write_create_symref_arg *create =
     ++		(struct write_create_symref_arg *)arg;
      +	uint64_t ts = stack_next_update_index(create->stack);
      +	int err = 0;
      +
      +	struct ref_record ref = {
     -+		.ref_name = (char*) create->refname,
     -+		.target = (char*) create->target,
     ++		.ref_name = (char *)create->refname,
     ++		.target = (char *)create->target,
      +		.update_index = ts,
      +	};
      +	writer_set_limits(writer, ts, ts);
     @@ -566,7 +593,7 @@ $^
      +		return err;
      +	}
      +
     -+	// XXX reflog?
     ++	/* XXX reflog? */
      +
      +	return 0;
      +}
     @@ -575,17 +602,15 @@ $^
      +				  const char *refname, const char *target,
      +				  const char *logmsg)
      +{
     -+	struct reftable_ref_store *refs = (struct reftable_ref_store*)ref_store;
     -+	struct write_create_symref_arg arg = {
     -+		.stack = refs->stack,
     -+		.refname = refname,
     -+		.target = target,
     -+		.logmsg = logmsg
     -+	};
     ++	struct reftable_ref_store *refs =
     ++		(struct reftable_ref_store *)ref_store;
     ++	struct write_create_symref_arg arg = { .stack = refs->stack,
     ++					       .refname = refname,
     ++					       .target = target,
     ++					       .logmsg = logmsg };
      +	return stack_add(refs->stack, &write_create_symref_table, &arg);
      +}
      +
     -+
      +struct write_rename_arg {
      +	struct stack *stack;
      +	const char *oldname;
     @@ -593,8 +618,9 @@ $^
      +	const char *logmsg;
      +};
      +
     -+static int write_rename_table(struct writer *writer, void *argv) {
     -+	struct write_rename_arg *arg = (struct write_rename_arg*)argv;
     ++static int write_rename_table(struct writer *writer, void *argv)
     ++{
     ++	struct write_rename_arg *arg = (struct write_rename_arg *)argv;
      +	uint64_t ts = stack_next_update_index(arg->stack);
      +	struct ref_record ref = {};
      +	int err = stack_read_ref(arg->stack, arg->oldname, &ref);
     @@ -602,15 +628,15 @@ $^
      +		goto exit;
      +	}
      +
     -+	// XXX should check that dest doesn't exist?
     ++	/* XXX should check that dest doesn't exist? */
      +	free(ref.ref_name);
      +	ref.ref_name = strdup(arg->newname);
      +	writer_set_limits(writer, ts, ts);
      +	ref.update_index = ts;
      +
      +	{
     -+		struct ref_record todo[2]  = {};
     -+		todo[0].ref_name =  (char *) arg->oldname;
     ++		struct ref_record todo[2] = {};
     ++		todo[0].ref_name = (char *)arg->oldname;
      +		todo[0].update_index = ts;
      +		todo[1] = ref;
      +		todo[1].update_index = ts;
     @@ -621,47 +647,47 @@ $^
      +		}
      +	}
      +
     -+        if (ref.value != NULL) {
     -+                struct log_record todo[2] = {};
     -+                fill_log_record(&todo[0]);
     -+                fill_log_record(&todo[1]);
     ++	if (ref.value != NULL) {
     ++		struct log_record todo[2] = {};
     ++		fill_log_record(&todo[0]);
     ++		fill_log_record(&todo[1]);
      +
     -+                todo[0].ref_name = (char *) arg->oldname;
     -+                todo[0].update_index = ts;
     -+                todo[0].message = (char*)arg->logmsg;
     -+                todo[0].old_hash = ref.value;
     -+                todo[0].new_hash = NULL;
     ++		todo[0].ref_name = (char *)arg->oldname;
     ++		todo[0].update_index = ts;
     ++		todo[0].message = (char *)arg->logmsg;
     ++		todo[0].old_hash = ref.value;
     ++		todo[0].new_hash = NULL;
      +
     -+                todo[1].ref_name = (char *) arg->newname;
     -+                todo[1].update_index = ts;
     -+                todo[1].old_hash = NULL;
     -+                todo[1].new_hash = ref.value;
     -+                todo[1].message = (char*) arg->logmsg;
     ++		todo[1].ref_name = (char *)arg->newname;
     ++		todo[1].update_index = ts;
     ++		todo[1].old_hash = NULL;
     ++		todo[1].new_hash = ref.value;
     ++		todo[1].message = (char *)arg->logmsg;
      +
      +		err = writer_add_logs(writer, todo, 2);
      +
     -+                clear_log_record(&todo[0]);
     -+                clear_log_record(&todo[1]);
     ++		clear_log_record(&todo[0]);
     ++		clear_log_record(&todo[1]);
      +
      +		if (err < 0) {
      +			goto exit;
      +		}
      +
     -+        } else {
     -+                // symrefs?
     -+        }
     ++	} else {
     ++		/* XXX symrefs? */
     ++	}
      +
      +exit:
      +	ref_record_clear(&ref);
      +	return err;
      +}
      +
     -+
      +static int reftable_rename_ref(struct ref_store *ref_store,
     -+			    const char *oldrefname, const char *newrefname,
     -+			    const char *logmsg)
     ++			       const char *oldrefname, const char *newrefname,
     ++			       const char *logmsg)
      +{
     -+	struct reftable_ref_store *refs = (struct reftable_ref_store*) ref_store;
     ++	struct reftable_ref_store *refs =
     ++		(struct reftable_ref_store *)ref_store;
      +	struct write_rename_arg arg = {
      +		.stack = refs->stack,
      +		.oldname = oldrefname,
     @@ -672,237 +698,249 @@ $^
      +}
      +
      +static int reftable_copy_ref(struct ref_store *ref_store,
     -+			   const char *oldrefname, const char *newrefname,
     -+			   const char *logmsg)
     ++			     const char *oldrefname, const char *newrefname,
     ++			     const char *logmsg)
      +{
      +	BUG("reftable reference store does not support copying references");
      +}
      +
      +struct reftable_reflog_ref_iterator {
     -+        struct ref_iterator base;
     -+        struct iterator iter;
     -+        struct log_record log;
     -+        struct object_id oid;
     -+        char *last_name;
     ++	struct ref_iterator base;
     ++	struct iterator iter;
     ++	struct log_record log;
     ++	struct object_id oid;
     ++	char *last_name;
      +};
      +
     -+static int reftable_reflog_ref_iterator_advance(struct ref_iterator *ref_iterator)
     ++static int
     ++reftable_reflog_ref_iterator_advance(struct ref_iterator *ref_iterator)
      +{
     -+        struct reftable_reflog_ref_iterator *ri = (struct reftable_reflog_ref_iterator*) ref_iterator;
     ++	struct reftable_reflog_ref_iterator *ri =
     ++		(struct reftable_reflog_ref_iterator *)ref_iterator;
      +
     -+        while (1) {
     -+                int err = iterator_next_log(ri->iter, &ri->log);
     -+                if (err > 0) {
     -+                        return ITER_DONE;
     -+                }
     -+                if (err < 0) {
     -+                        return ITER_ERROR;
     -+                }
     ++	while (1) {
     ++		int err = iterator_next_log(ri->iter, &ri->log);
     ++		if (err > 0) {
     ++			return ITER_DONE;
     ++		}
     ++		if (err < 0) {
     ++			return ITER_ERROR;
     ++		}
      +
     -+                ri->base.refname = ri->log.ref_name;
     -+                if (ri->last_name != NULL && 0 == strcmp(ri->log.ref_name, ri->last_name)) {
     -+                        continue;
     -+                }
     ++		ri->base.refname = ri->log.ref_name;
     ++		if (ri->last_name != NULL &&
     ++		    !strcmp(ri->log.ref_name, ri->last_name)) {
     ++			continue;
     ++		}
      +
     -+                free(ri->last_name);
     -+                ri->last_name = xstrdup(ri->log.ref_name);
     -+                // XXX const?
     -+                memcpy(&ri->oid.hash, ri->log.new_hash, GIT_SHA1_RAWSZ);
     -+                return ITER_OK;
     -+        }
     ++		free(ri->last_name);
     ++		ri->last_name = xstrdup(ri->log.ref_name);
     ++		/* XXX const? */
     ++		memcpy(&ri->oid.hash, ri->log.new_hash, GIT_SHA1_RAWSZ);
     ++		return ITER_OK;
     ++	}
      +}
      +
      +static int reftable_reflog_ref_iterator_peel(struct ref_iterator *ref_iterator,
     -+                                             struct object_id *peeled)
     ++					     struct object_id *peeled)
      +{
     -+        BUG("not supported.");
     ++	BUG("not supported.");
      +	return -1;
      +}
      +
      +static int reftable_reflog_ref_iterator_abort(struct ref_iterator *ref_iterator)
      +{
     -+	struct reftable_reflog_ref_iterator *ri = (struct reftable_reflog_ref_iterator*) ref_iterator;
     ++	struct reftable_reflog_ref_iterator *ri =
     ++		(struct reftable_reflog_ref_iterator *)ref_iterator;
      +	log_record_clear(&ri->log);
      +	iterator_destroy(&ri->iter);
      +	return 0;
      +}
      +
      +static struct ref_iterator_vtable reftable_reflog_ref_iterator_vtable = {
     -+	reftable_reflog_ref_iterator_advance,
     -+	reftable_reflog_ref_iterator_peel,
     ++	reftable_reflog_ref_iterator_advance, reftable_reflog_ref_iterator_peel,
      +	reftable_reflog_ref_iterator_abort
      +};
      +
     -+static struct ref_iterator *reftable_reflog_iterator_begin(struct ref_store *ref_store)
     ++static struct ref_iterator *
     ++reftable_reflog_iterator_begin(struct ref_store *ref_store)
      +{
     -+        struct reftable_reflog_ref_iterator *ri = xcalloc(sizeof(*ri), 1);
     -+        struct reftable_ref_store *refs = (struct reftable_ref_store*) ref_store;
     ++	struct reftable_reflog_ref_iterator *ri = xcalloc(sizeof(*ri), 1);
     ++	struct reftable_ref_store *refs =
     ++		(struct reftable_ref_store *)ref_store;
      +
      +	struct merged_table *mt = stack_merged_table(refs->stack);
     -+        int err = merged_table_seek_log(mt, &ri->iter, "");
     -+        if (err < 0) {
     -+                free(ri);
     -+                return NULL;
     -+        }
     ++	int err = merged_table_seek_log(mt, &ri->iter, "");
     ++	if (err < 0) {
     ++		free(ri);
     ++		return NULL;
     ++	}
      +
     -+	base_ref_iterator_init(&ri->base, &reftable_reflog_ref_iterator_vtable, 1);
     ++	base_ref_iterator_init(&ri->base, &reftable_reflog_ref_iterator_vtable,
     ++			       1);
      +	ri->base.oid = &ri->oid;
      +
      +	return empty_ref_iterator_begin();
      +}
      +
     -+static int reftable_for_each_reflog_ent_newest_first(struct ref_store *ref_store,
     -+                                        const char *refname,
     -+                                        each_reflog_ent_fn fn, void *cb_data)
     -+{
     -+        struct iterator it = { };
     -+        struct reftable_ref_store *refs = (struct reftable_ref_store*) ref_store;
     -+        struct merged_table *mt = stack_merged_table(refs->stack);
     -+        int err = merged_table_seek_log(mt, &it, refname);
     -+        struct log_record log = {};
     -+
     -+        while (err == 0)  {
     -+                err = iterator_next_log(it, &log);
     -+                if (err != 0) {
     -+                        break;
     -+                }
     -+
     -+                if (0 != strcmp(log.ref_name, refname)) {
     -+                        break;
     -+                }
     -+
     -+                {
     -+                        struct object_id old_oid = {};
     -+                        struct object_id new_oid = {};
     -+
     -+                        memcpy(&old_oid.hash, log.old_hash, GIT_SHA1_RAWSZ);
     -+                        memcpy(&new_oid.hash, log.new_hash, GIT_SHA1_RAWSZ);
     -+
     -+                        // XXX committer = email? name?
     -+                        if (fn(&old_oid, &new_oid, log.name, log.time, log.tz_offset, log.message, cb_data)) {
     -+                                err = -1;
     -+                                break;
     -+                        }
     -+                }
     -+        }
     -+
     -+        log_record_clear(&log);
     -+        iterator_destroy(&it);
     -+        if (err > 0) {
     -+                err = 0;
     -+        }
     ++static int
     ++reftable_for_each_reflog_ent_newest_first(struct ref_store *ref_store,
     ++					  const char *refname,
     ++					  each_reflog_ent_fn fn, void *cb_data)
     ++{
     ++	struct iterator it = {};
     ++	struct reftable_ref_store *refs =
     ++		(struct reftable_ref_store *)ref_store;
     ++	struct merged_table *mt = stack_merged_table(refs->stack);
     ++	int err = merged_table_seek_log(mt, &it, refname);
     ++	struct log_record log = {};
     ++
     ++	while (err == 0) {
     ++		err = iterator_next_log(it, &log);
     ++		if (err != 0) {
     ++			break;
     ++		}
     ++
     ++		if (strcmp(log.ref_name, refname)) {
     ++			break;
     ++		}
     ++
     ++		{
     ++			struct object_id old_oid = {};
     ++			struct object_id new_oid = {};
     ++
     ++			memcpy(&old_oid.hash, log.old_hash, GIT_SHA1_RAWSZ);
     ++			memcpy(&new_oid.hash, log.new_hash, GIT_SHA1_RAWSZ);
     ++
     ++			/* XXX committer = email? name? */
     ++			if (fn(&old_oid, &new_oid, log.name, log.time,
     ++			       log.tz_offset, log.message, cb_data)) {
     ++				err = -1;
     ++				break;
     ++			}
     ++		}
     ++	}
     ++
     ++	log_record_clear(&log);
     ++	iterator_destroy(&it);
     ++	if (err > 0) {
     ++		err = 0;
     ++	}
      +	return err;
      +}
      +
     -+static int reftable_for_each_reflog_ent_oldest_first(struct ref_store *ref_store,
     -+                                                     const char *refname,
     -+                                                     each_reflog_ent_fn fn,
     -+                                                     void *cb_data)
     -+{
     -+        struct iterator it = { };
     -+        struct reftable_ref_store *refs = (struct reftable_ref_store*) ref_store;
     -+        struct merged_table *mt = stack_merged_table(refs->stack);
     -+        int err = merged_table_seek_log(mt, &it, refname);
     -+
     -+        struct log_record *logs = NULL;
     -+        int cap = 0;
     -+        int len = 0;
     -+
     -+        printf("oldest first\n");
     -+        while (err == 0)  {
     -+                struct log_record log = {};
     -+                err = iterator_next_log(it, &log);
     -+                if (err != 0) {
     -+                        break;
     -+                }
     -+
     -+                if (0 != strcmp(log.ref_name, refname)) {
     -+                        break;
     -+                }
     -+
     -+                if (len == cap) {
     -+                        cap  =2*cap + 1;
     -+                        logs = realloc(logs, cap * sizeof(*logs));
     -+                }
     -+
     -+                logs[len++] = log;
     -+        }
     -+
     -+        for (int i = len; i--; ) {
     -+                struct log_record *log = &logs[i];
     -+                struct object_id old_oid = {};
     -+                struct object_id new_oid = {};
     -+
     -+                memcpy(&old_oid.hash, log->old_hash, GIT_SHA1_RAWSZ);
     -+                memcpy(&new_oid.hash, log->new_hash, GIT_SHA1_RAWSZ);
     -+
     -+                // XXX committer = email? name?
     -+                if (!fn(&old_oid, &new_oid, log->name, log->time, log->tz_offset, log->message, cb_data)) {
     -+                        err = -1;
     -+                        break;
     -+                }
     -+        }
     -+
     -+        for (int i = 0; i < len; i++) {
     -+                log_record_clear(&logs[i]);
     -+        }
     -+        free(logs);
     -+
     -+        iterator_destroy(&it);
     -+        if (err > 0) {
     -+                err = 0;
     -+        }
     ++static int
     ++reftable_for_each_reflog_ent_oldest_first(struct ref_store *ref_store,
     ++					  const char *refname,
     ++					  each_reflog_ent_fn fn, void *cb_data)
     ++{
     ++	struct iterator it = {};
     ++	struct reftable_ref_store *refs =
     ++		(struct reftable_ref_store *)ref_store;
     ++	struct merged_table *mt = stack_merged_table(refs->stack);
     ++	int err = merged_table_seek_log(mt, &it, refname);
     ++
     ++	struct log_record *logs = NULL;
     ++	int cap = 0;
     ++	int len = 0;
     ++
     ++	printf("oldest first\n");
     ++	while (err == 0) {
     ++		struct log_record log = {};
     ++		err = iterator_next_log(it, &log);
     ++		if (err != 0) {
     ++			break;
     ++		}
     ++
     ++		if (strcmp(log.ref_name, refname)) {
     ++			break;
     ++		}
     ++
     ++		if (len == cap) {
     ++			cap = 2 * cap + 1;
     ++			logs = realloc(logs, cap * sizeof(*logs));
     ++		}
     ++
     ++		logs[len++] = log;
     ++	}
     ++
     ++	for (int i = len; i--;) {
     ++		struct log_record *log = &logs[i];
     ++		struct object_id old_oid = {};
     ++		struct object_id new_oid = {};
     ++
     ++		memcpy(&old_oid.hash, log->old_hash, GIT_SHA1_RAWSZ);
     ++		memcpy(&new_oid.hash, log->new_hash, GIT_SHA1_RAWSZ);
     ++
     ++		/* XXX committer = email? name? */
     ++		if (!fn(&old_oid, &new_oid, log->name, log->time,
     ++			log->tz_offset, log->message, cb_data)) {
     ++			err = -1;
     ++			break;
     ++		}
     ++	}
     ++
     ++	for (int i = 0; i < len; i++) {
     ++		log_record_clear(&logs[i]);
     ++	}
     ++	free(logs);
     ++
     ++	iterator_destroy(&it);
     ++	if (err > 0) {
     ++		err = 0;
     ++	}
      +	return err;
      +}
      +
      +static int reftable_reflog_exists(struct ref_store *ref_store,
     -+                                  const char *refname)
     ++				  const char *refname)
      +{
     -+        // always exists.
     ++	/* always exists. */
      +	return 1;
      +}
      +
      +static int reftable_create_reflog(struct ref_store *ref_store,
     -+                                  const char *refname, int force_create,
     -+                                  struct strbuf *err)
     ++				  const char *refname, int force_create,
     ++				  struct strbuf *err)
      +{
     -+        return 0;
     ++	return 0;
      +}
      +
      +static int reftable_delete_reflog(struct ref_store *ref_store,
     -+                                  const char *refname)
     ++				  const char *refname)
      +{
      +	return 0;
      +}
      +
      +static int reftable_reflog_expire(struct ref_store *ref_store,
     -+				const char *refname, const struct object_id *oid,
     -+				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)
     -+{
     -+        /*
     -+          XXX
     -+
     -+          This doesn't fit with the reftable API. If we are expiring for space
     -+          reasons, the expiry should be combined with a compaction, and there
     -+          should be a policy that can be called for all refnames, not for a
     -+          single ref name.
     -+
     -+          If this is for cleaning up individual entries, we'll have to write
     -+          extra data to create tombstones.
     -+         */
     ++				  const char *refname,
     ++				  const struct object_id *oid,
     ++				  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)
     ++{
     ++	/*
     ++	  XXX
     ++
     ++	  This doesn't fit with the reftable API. If we are expiring for space
     ++	  reasons, the expiry should be combined with a compaction, and there
     ++	  should be a policy that can be called for all refnames, not for a
     ++	  single ref name.
     ++
     ++	  If this is for cleaning up individual entries, we'll have to write
     ++	  extra data to create tombstones.
     ++	 */
      +	return 0;
      +}
      +
     -+
      +static int reftable_read_raw_ref(struct ref_store *ref_store,
      +				 const char *refname, struct object_id *oid,
      +				 struct strbuf *referent, unsigned int *type)
      +{
     -+	struct reftable_ref_store *refs = (struct reftable_ref_store*) ref_store;
     ++	struct reftable_ref_store *refs =
     ++		(struct reftable_ref_store *)ref_store;
      +	struct ref_record ref = {};
      +	int err = stack_read_ref(refs->stack, refname, &ref);
      +	if (err) {
     @@ -912,7 +950,7 @@ $^
      +		strbuf_reset(referent);
      +		strbuf_addstr(referent, ref.target);
      +		*type |= REF_ISSYMREF;
     -+ 	} else  {
     ++	} else {
      +		memcpy(oid->hash, ref.value, GIT_SHA1_RAWSZ);
      +	}
      +

-- 
gitgitgadget

^ permalink raw reply	[relevance 1%]

* [PATCH v5 2/6] t1300: fix over-indented HERE-DOCs
  @ 2020-01-25  0:39  6%     ` Matthew Rogers via GitGitGadget
    1 sibling, 0 replies; 200+ results
From: Matthew Rogers via GitGitGadget @ 2020-01-25  0:39 UTC (permalink / raw)
  To: git; +Cc: Matthew Rogers, Matthew Rogers

From: Matthew Rogers <mattr94@gmail.com>

Prepare for the following patches by removing extraneous indents from
HERE-DOCs used in config tests.

Signed-off-by: Matthew Rogers <mattr94@gmail.com>
---
 t/t1300-config.sh | 168 +++++++++++++++++++++++-----------------------
 1 file changed, 84 insertions(+), 84 deletions(-)

diff --git a/t/t1300-config.sh b/t/t1300-config.sh
index 983a0a1583..e8b4575758 100755
--- a/t/t1300-config.sh
+++ b/t/t1300-config.sh
@@ -1191,47 +1191,47 @@ test_expect_success 'old-fashioned settings are case insensitive' '
 	test_when_finished "rm -f testConfig testConfig_expect testConfig_actual" &&
 
 	cat >testConfig_actual <<-EOF &&
-		[V.A]
-		r = value1
+	[V.A]
+	r = value1
 	EOF
 	q_to_tab >testConfig_expect <<-EOF &&
-		[V.A]
-		Qr = value2
+	[V.A]
+	Qr = value2
 	EOF
 	git config -f testConfig_actual "v.a.r" value2 &&
 	test_cmp testConfig_expect testConfig_actual &&
 
 	cat >testConfig_actual <<-EOF &&
-		[V.A]
-		r = value1
+	[V.A]
+	r = value1
 	EOF
 	q_to_tab >testConfig_expect <<-EOF &&
-		[V.A]
-		QR = value2
+	[V.A]
+	QR = value2
 	EOF
 	git config -f testConfig_actual "V.a.R" value2 &&
 	test_cmp testConfig_expect testConfig_actual &&
 
 	cat >testConfig_actual <<-EOF &&
-		[V.A]
-		r = value1
+	[V.A]
+	r = value1
 	EOF
 	q_to_tab >testConfig_expect <<-EOF &&
-		[V.A]
-		r = value1
-		Qr = value2
+	[V.A]
+	r = value1
+	Qr = value2
 	EOF
 	git config -f testConfig_actual "V.A.r" value2 &&
 	test_cmp testConfig_expect testConfig_actual &&
 
 	cat >testConfig_actual <<-EOF &&
-		[V.A]
-		r = value1
+	[V.A]
+	r = value1
 	EOF
 	q_to_tab >testConfig_expect <<-EOF &&
-		[V.A]
-		r = value1
-		Qr = value2
+	[V.A]
+	r = value1
+	Qr = value2
 	EOF
 	git config -f testConfig_actual "v.A.r" value2 &&
 	test_cmp testConfig_expect testConfig_actual
@@ -1241,26 +1241,26 @@ test_expect_success 'setting different case sensitive subsections ' '
 	test_when_finished "rm -f testConfig testConfig_expect testConfig_actual" &&
 
 	cat >testConfig_actual <<-EOF &&
-		[V "A"]
-		R = v1
-		[K "E"]
-		Y = v1
-		[a "b"]
-		c = v1
-		[d "e"]
-		f = v1
+	[V "A"]
+	R = v1
+	[K "E"]
+	Y = v1
+	[a "b"]
+	c = v1
+	[d "e"]
+	f = v1
 	EOF
 	q_to_tab >testConfig_expect <<-EOF &&
-		[V "A"]
-		Qr = v2
-		[K "E"]
-		Qy = v2
-		[a "b"]
-		Qc = v2
-		[d "e"]
-		f = v1
-		[d "E"]
-		Qf = v2
+	[V "A"]
+	Qr = v2
+	[K "E"]
+	Qy = v2
+	[a "b"]
+	Qc = v2
+	[d "e"]
+	f = v1
+	[d "E"]
+	Qf = v2
 	EOF
 	# exact match
 	git config -f testConfig_actual a.b.c v2 &&
@@ -1622,40 +1622,40 @@ test_expect_success 'set up --show-origin tests' '
 	INCLUDE_DIR="$HOME/include" &&
 	mkdir -p "$INCLUDE_DIR" &&
 	cat >"$INCLUDE_DIR"/absolute.include <<-\EOF &&
-		[user]
-			absolute = include
+	[user]
+		absolute = include
 	EOF
 	cat >"$INCLUDE_DIR"/relative.include <<-\EOF &&
-		[user]
-			relative = include
+	[user]
+		relative = include
 	EOF
 	cat >"$HOME"/.gitconfig <<-EOF &&
-		[user]
-			global = true
-			override = global
-		[include]
-			path = "$INCLUDE_DIR/absolute.include"
+	[user]
+		global = true
+		override = global
+	[include]
+		path = "$INCLUDE_DIR/absolute.include"
 	EOF
 	cat >.git/config <<-\EOF
-		[user]
-			local = true
-			override = local
-		[include]
-			path = ../include/relative.include
+	[user]
+		local = true
+		override = local
+	[include]
+		path = ../include/relative.include
 	EOF
 '
 
 test_expect_success '--show-origin with --list' '
 	cat >expect <<-EOF &&
-		file:$HOME/.gitconfig	user.global=true
-		file:$HOME/.gitconfig	user.override=global
-		file:$HOME/.gitconfig	include.path=$INCLUDE_DIR/absolute.include
-		file:$INCLUDE_DIR/absolute.include	user.absolute=include
-		file:.git/config	user.local=true
-		file:.git/config	user.override=local
-		file:.git/config	include.path=../include/relative.include
-		file:.git/../include/relative.include	user.relative=include
-		command line:	user.cmdline=true
+	file:$HOME/.gitconfig	user.global=true
+	file:$HOME/.gitconfig	user.override=global
+	file:$HOME/.gitconfig	include.path=$INCLUDE_DIR/absolute.include
+	file:$INCLUDE_DIR/absolute.include	user.absolute=include
+	file:.git/config	user.local=true
+	file:.git/config	user.override=local
+	file:.git/config	include.path=../include/relative.include
+	file:.git/../include/relative.include	user.relative=include
+	command line:	user.cmdline=true
 	EOF
 	git -c user.cmdline=true config --list --show-origin >output &&
 	test_cmp expect output
@@ -1663,16 +1663,16 @@ test_expect_success '--show-origin with --list' '
 
 test_expect_success '--show-origin with --list --null' '
 	cat >expect <<-EOF &&
-		file:$HOME/.gitconfigQuser.global
-		trueQfile:$HOME/.gitconfigQuser.override
-		globalQfile:$HOME/.gitconfigQinclude.path
-		$INCLUDE_DIR/absolute.includeQfile:$INCLUDE_DIR/absolute.includeQuser.absolute
-		includeQfile:.git/configQuser.local
-		trueQfile:.git/configQuser.override
-		localQfile:.git/configQinclude.path
-		../include/relative.includeQfile:.git/../include/relative.includeQuser.relative
-		includeQcommand line:Quser.cmdline
-		trueQ
+	file:$HOME/.gitconfigQuser.global
+	trueQfile:$HOME/.gitconfigQuser.override
+	globalQfile:$HOME/.gitconfigQinclude.path
+	$INCLUDE_DIR/absolute.includeQfile:$INCLUDE_DIR/absolute.includeQuser.absolute
+	includeQfile:.git/configQuser.local
+	trueQfile:.git/configQuser.override
+	localQfile:.git/configQinclude.path
+	../include/relative.includeQfile:.git/../include/relative.includeQuser.relative
+	includeQcommand line:Quser.cmdline
+	trueQ
 	EOF
 	git -c user.cmdline=true config --null --list --show-origin >output.raw &&
 	nul_to_q <output.raw >output &&
@@ -1684,9 +1684,9 @@ test_expect_success '--show-origin with --list --null' '
 
 test_expect_success '--show-origin with single file' '
 	cat >expect <<-\EOF &&
-		file:.git/config	user.local=true
-		file:.git/config	user.override=local
-		file:.git/config	include.path=../include/relative.include
+	file:.git/config	user.local=true
+	file:.git/config	user.override=local
+	file:.git/config	include.path=../include/relative.include
 	EOF
 	git config --local --list --show-origin >output &&
 	test_cmp expect output
@@ -1694,8 +1694,8 @@ test_expect_success '--show-origin with single file' '
 
 test_expect_success '--show-origin with --get-regexp' '
 	cat >expect <<-EOF &&
-		file:$HOME/.gitconfig	user.global true
-		file:.git/config	user.local true
+	file:$HOME/.gitconfig	user.global true
+	file:.git/config	user.local true
 	EOF
 	git config --show-origin --get-regexp "user\.[g|l].*" >output &&
 	test_cmp expect output
@@ -1703,7 +1703,7 @@ test_expect_success '--show-origin with --get-regexp' '
 
 test_expect_success '--show-origin getting a single key' '
 	cat >expect <<-\EOF &&
-		file:.git/config	local
+	file:.git/config	local
 	EOF
 	git config --show-origin user.override >output &&
 	test_cmp expect output
@@ -1712,14 +1712,14 @@ test_expect_success '--show-origin getting a single key' '
 test_expect_success 'set up custom config file' '
 	CUSTOM_CONFIG_FILE="file\" (dq) and spaces.conf" &&
 	cat >"$CUSTOM_CONFIG_FILE" <<-\EOF
-		[user]
-			custom = true
+	[user]
+		custom = true
 	EOF
 '
 
 test_expect_success !MINGW '--show-origin escape special file name characters' '
 	cat >expect <<-\EOF &&
-		file:"file\" (dq) and spaces.conf"	user.custom=true
+	file:"file\" (dq) and spaces.conf"	user.custom=true
 	EOF
 	git config --file "$CUSTOM_CONFIG_FILE" --show-origin --list >output &&
 	test_cmp expect output
@@ -1727,7 +1727,7 @@ test_expect_success !MINGW '--show-origin escape special file name characters' '
 
 test_expect_success '--show-origin stdin' '
 	cat >expect <<-\EOF &&
-		standard input:	user.custom=true
+	standard input:	user.custom=true
 	EOF
 	git config --file - --show-origin --list <"$CUSTOM_CONFIG_FILE" >output &&
 	test_cmp expect output
@@ -1735,11 +1735,11 @@ test_expect_success '--show-origin stdin' '
 
 test_expect_success '--show-origin stdin with file include' '
 	cat >"$INCLUDE_DIR"/stdin.include <<-EOF &&
-		[user]
-			stdin = include
+	[user]
+		stdin = include
 	EOF
 	cat >expect <<-EOF &&
-		file:$INCLUDE_DIR/stdin.include	include
+	file:$INCLUDE_DIR/stdin.include	include
 	EOF
 	echo "[include]path=\"$INCLUDE_DIR\"/stdin.include" |
 	git config --show-origin --includes --file - user.stdin >output &&
@@ -1750,7 +1750,7 @@ test_expect_success '--show-origin stdin with file include' '
 test_expect_success !MINGW '--show-origin blob' '
 	blob=$(git hash-object -w "$CUSTOM_CONFIG_FILE") &&
 	cat >expect <<-EOF &&
-		blob:$blob	user.custom=true
+	blob:$blob	user.custom=true
 	EOF
 	git config --blob=$blob --show-origin --list >output &&
 	test_cmp expect output
@@ -1758,7 +1758,7 @@ test_expect_success !MINGW '--show-origin blob' '
 
 test_expect_success !MINGW '--show-origin blob ref' '
 	cat >expect <<-\EOF &&
-		blob:"master:file\" (dq) and spaces.conf"	user.custom=true
+	blob:"master:file\" (dq) and spaces.conf"	user.custom=true
 	EOF
 	git add "$CUSTOM_CONFIG_FILE" &&
 	git commit -m "new config file" &&
-- 
gitgitgadget


^ permalink raw reply related	[relevance 6%]

* [PATCH 1/3] git-gui: update german translation to most recently created pot templates
  @ 2020-01-24 22:33  1% ` Christian Stimming via GitGitGadget
  2020-01-24 22:33  1% ` [PATCH 2/3] git-gui: update german translation Christian Stimming via GitGitGadget
  2020-02-09 22:00  1% ` [PATCH v2 0/3] git gui: improve German translation Christian Stimming via GitGitGadget
  2 siblings, 0 replies; 200+ results
From: Christian Stimming via GitGitGadget @ 2020-01-24 22:33 UTC (permalink / raw)
  To: git; +Cc: Christian Stimming, Pratyush Yadav, Christian Stimming

From: Christian Stimming <christian@cstimming.de>

No content changes so far, only the preparation for subsequent edits.

Signed-off-by: Christian Stimming <christian@cstimming.de>
---
 po/de.po          | 3520 ++++++++++++++++++++++++---------------------
 po/glossary/de.po |  131 +-
 2 files changed, 2040 insertions(+), 1611 deletions(-)

diff --git a/po/de.po b/po/de.po
index baebff2fff..162dc8aebe 100644
--- a/po/de.po
+++ b/po/de.po
@@ -7,41 +7,42 @@ msgid ""
 msgstr ""
 "Project-Id-Version: git-gui\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-01-26 22:22+0100\n"
+"POT-Creation-Date: 2020-01-13 21:51+0100\n"
 "PO-Revision-Date: 2010-01-26 22:25+0100\n"
 "Last-Translator: Christian Stimming <stimming@tuhh.de>\n"
 "Language-Team: German\n"
+"Language: \n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 
-#: git-gui.sh:41 git-gui.sh:793 git-gui.sh:807 git-gui.sh:820 git-gui.sh:903
-#: git-gui.sh:922
-msgid "git-gui: fatal error"
-msgstr "git-gui: Programmfehler"
-
-#: git-gui.sh:743
+#: git-gui.sh:847
 #, tcl-format
 msgid "Invalid font specified in %s:"
 msgstr "Ungültige Zeichensatz-Angabe in %s:"
 
-#: git-gui.sh:779
+#: git-gui.sh:901
 msgid "Main Font"
 msgstr "Programmschriftart"
 
-#: git-gui.sh:780
+#: git-gui.sh:902
 msgid "Diff/Console Font"
 msgstr "Vergleich-Schriftart"
 
-#: git-gui.sh:794
+#: git-gui.sh:917 git-gui.sh:931 git-gui.sh:944 git-gui.sh:1034 git-gui.sh:1053
+#: git-gui.sh:3212
+msgid "git-gui: fatal error"
+msgstr "git-gui: Programmfehler"
+
+#: git-gui.sh:918
 msgid "Cannot find git in PATH."
 msgstr "Git kann im PATH nicht gefunden werden."
 
-#: git-gui.sh:821
+#: git-gui.sh:945
 msgid "Cannot parse Git version string:"
 msgstr "Git Versionsangabe kann nicht erkannt werden:"
 
-#: git-gui.sh:839
+#: git-gui.sh:970
 #, tcl-format
 msgid ""
 "Git version cannot be determined.\n"
@@ -60,479 +61,532 @@ msgstr ""
 "\n"
 "Soll angenommen werden, »%s« sei Version 1.5.0?\n"
 
-#: git-gui.sh:1128
+#: git-gui.sh:1267
 msgid "Git directory not found:"
 msgstr "Git-Verzeichnis nicht gefunden:"
 
-#: git-gui.sh:1146
+#: git-gui.sh:1301
 msgid "Cannot move to top of working directory:"
 msgstr ""
 "Es konnte nicht in das oberste Verzeichnis der Arbeitskopie gewechselt "
 "werden:"
 
-#: git-gui.sh:1154
+#: git-gui.sh:1309
 msgid "Cannot use bare repository:"
 msgstr "Bloßes Projektarchiv kann nicht benutzt werden:"
 
-#: git-gui.sh:1162
+#: git-gui.sh:1317
 msgid "No working directory"
 msgstr "Kein Arbeitsverzeichnis"
 
-#: git-gui.sh:1334 lib/checkout_op.tcl:306
+#: git-gui.sh:1491 lib/checkout_op.tcl:306
 msgid "Refreshing file status..."
 msgstr "Dateistatus aktualisieren..."
 
-#: git-gui.sh:1390
+#: git-gui.sh:1551
 msgid "Scanning for modified files ..."
 msgstr "Nach geänderten Dateien suchen..."
 
-#: git-gui.sh:1454
+#: git-gui.sh:1629
 msgid "Calling prepare-commit-msg hook..."
-msgstr "Aufrufen der Eintragen-Vorbereiten-Kontrolle (»prepare-commit hook«)..."
+msgstr ""
+"Aufrufen der Eintragen-Vorbereiten-Kontrolle (»prepare-commit hook«)..."
 
-#: git-gui.sh:1471
+#: git-gui.sh:1646
 msgid "Commit declined by prepare-commit-msg hook."
 msgstr ""
 "Eintragen abgelehnt durch Eintragen-Vorbereiten-Kontrolle (»prepare-commit "
 "hook«)."
 
-#: git-gui.sh:1629 lib/browser.tcl:246
+#: git-gui.sh:1804 lib/browser.tcl:252
 msgid "Ready."
 msgstr "Bereit."
 
-#: git-gui.sh:1787
+#: git-gui.sh:1968
 #, tcl-format
-msgid "Displaying only %s of %s files."
-msgstr "Nur %s von %s Dateien werden angezeigt."
+msgid ""
+"Display limit (gui.maxfilesdisplayed = %s) reached, not showing all %s files."
+msgstr ""
 
-#: git-gui.sh:1913
+#: git-gui.sh:2091
 msgid "Unmodified"
 msgstr "Unverändert"
 
-#: git-gui.sh:1915
+#: git-gui.sh:2093
 msgid "Modified, not staged"
 msgstr "Verändert, nicht bereitgestellt"
 
-#: git-gui.sh:1916 git-gui.sh:1924
+#: git-gui.sh:2094 git-gui.sh:2106
 msgid "Staged for commit"
 msgstr "Bereitgestellt zum Eintragen"
 
-#: git-gui.sh:1917 git-gui.sh:1925
+#: git-gui.sh:2095 git-gui.sh:2107
 msgid "Portions staged for commit"
 msgstr "Teilweise bereitgestellt zum Eintragen"
 
-#: git-gui.sh:1918 git-gui.sh:1926
+#: git-gui.sh:2096 git-gui.sh:2108
 msgid "Staged for commit, missing"
 msgstr "Bereitgestellt zum Eintragen, fehlend"
 
-#: git-gui.sh:1920
+#: git-gui.sh:2098
 msgid "File type changed, not staged"
 msgstr "Dateityp geändert, nicht bereitgestellt"
 
-#: git-gui.sh:1921
+#: git-gui.sh:2099 git-gui.sh:2100
+#, fuzzy
+msgid "File type changed, old type staged for commit"
+msgstr "Dateityp geändert, nicht bereitgestellt"
+
+#: git-gui.sh:2101
 msgid "File type changed, staged"
 msgstr "Dateityp geändert, bereitgestellt"
 
-#: git-gui.sh:1923
+#: git-gui.sh:2102
+#, fuzzy
+msgid "File type change staged, modification not staged"
+msgstr "Dateityp geändert, nicht bereitgestellt"
+
+#: git-gui.sh:2103
+#, fuzzy
+msgid "File type change staged, file missing"
+msgstr "Dateityp geändert, bereitgestellt"
+
+#: git-gui.sh:2105
 msgid "Untracked, not staged"
 msgstr "Nicht unter Versionskontrolle, nicht bereitgestellt"
 
-#: git-gui.sh:1928
+#: git-gui.sh:2110
 msgid "Missing"
 msgstr "Fehlend"
 
-#: git-gui.sh:1929
+#: git-gui.sh:2111
 msgid "Staged for removal"
 msgstr "Bereitgestellt zum Löschen"
 
-#: git-gui.sh:1930
+#: git-gui.sh:2112
 msgid "Staged for removal, still present"
 msgstr "Bereitgestellt zum Löschen, trotzdem vorhanden"
 
-#: git-gui.sh:1932 git-gui.sh:1933 git-gui.sh:1934 git-gui.sh:1935
-#: git-gui.sh:1936 git-gui.sh:1937
+#: git-gui.sh:2114 git-gui.sh:2115 git-gui.sh:2116 git-gui.sh:2117
+#: git-gui.sh:2118 git-gui.sh:2119
 msgid "Requires merge resolution"
 msgstr "Konfliktauflösung nötig"
 
-#: git-gui.sh:1972
-msgid "Starting gitk... please wait..."
-msgstr "Gitk wird gestartet... bitte warten."
-
-#: git-gui.sh:1984
+#: git-gui.sh:2164
 msgid "Couldn't find gitk in PATH"
 msgstr "Gitk kann im PATH nicht gefunden werden."
 
-#: git-gui.sh:2043
+#: git-gui.sh:2210 git-gui.sh:2245
+#, fuzzy, tcl-format
+msgid "Starting %s... please wait..."
+msgstr "Gitk wird gestartet... bitte warten."
+
+#: git-gui.sh:2224
 msgid "Couldn't find git gui in PATH"
 msgstr "»Git gui« kann im PATH nicht gefunden werden."
 
-#: git-gui.sh:2455 lib/choose_repository.tcl:36
+#: git-gui.sh:2726 lib/choose_repository.tcl:53
 msgid "Repository"
 msgstr "Projektarchiv"
 
-#: git-gui.sh:2456
+#: git-gui.sh:2727
 msgid "Edit"
 msgstr "Bearbeiten"
 
-#: git-gui.sh:2458 lib/choose_rev.tcl:561
+#: git-gui.sh:2729 lib/choose_rev.tcl:567
 msgid "Branch"
 msgstr "Zweig"
 
-#: git-gui.sh:2461 lib/choose_rev.tcl:548
+#: git-gui.sh:2732 lib/choose_rev.tcl:554
 msgid "Commit@@noun"
 msgstr "Version"
 
-#: git-gui.sh:2464 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168
+#: git-gui.sh:2735 lib/merge.tcl:127 lib/merge.tcl:174
 msgid "Merge"
 msgstr "Zusammenführen"
 
-#: git-gui.sh:2465 lib/choose_rev.tcl:557
+#: git-gui.sh:2736 lib/choose_rev.tcl:563
 msgid "Remote"
 msgstr "Externe Archive"
 
-#: git-gui.sh:2468
+#: git-gui.sh:2739
 msgid "Tools"
 msgstr "Werkzeuge"
 
-#: git-gui.sh:2477
+#: git-gui.sh:2748
 msgid "Explore Working Copy"
 msgstr "Arbeitskopie im Dateimanager"
 
-#: git-gui.sh:2483
+#: git-gui.sh:2763
+msgid "Git Bash"
+msgstr ""
+
+#: git-gui.sh:2772
 msgid "Browse Current Branch's Files"
 msgstr "Aktuellen Zweig durchblättern"
 
-#: git-gui.sh:2487
+#: git-gui.sh:2776
 msgid "Browse Branch Files..."
 msgstr "Einen Zweig durchblättern..."
 
-#: git-gui.sh:2492
+#: git-gui.sh:2781
 msgid "Visualize Current Branch's History"
 msgstr "Aktuellen Zweig darstellen"
 
-#: git-gui.sh:2496
+#: git-gui.sh:2785
 msgid "Visualize All Branch History"
 msgstr "Alle Zweige darstellen"
 
-#: git-gui.sh:2503
+#: git-gui.sh:2792
 #, tcl-format
 msgid "Browse %s's Files"
 msgstr "Zweig »%s« durchblättern"
 
-#: git-gui.sh:2505
+#: git-gui.sh:2794
 #, tcl-format
 msgid "Visualize %s's History"
 msgstr "Historie von »%s« darstellen"
 
-#: git-gui.sh:2510 lib/database.tcl:27 lib/database.tcl:67
+#: git-gui.sh:2799 lib/database.tcl:40
 msgid "Database Statistics"
 msgstr "Datenbankstatistik"
 
-#: git-gui.sh:2513 lib/database.tcl:34
+#: git-gui.sh:2802 lib/database.tcl:33
 msgid "Compress Database"
 msgstr "Datenbank komprimieren"
 
-#: git-gui.sh:2516
+#: git-gui.sh:2805
 msgid "Verify Database"
 msgstr "Datenbank überprüfen"
 
-#: git-gui.sh:2523 git-gui.sh:2527 git-gui.sh:2531 lib/shortcut.tcl:8
-#: lib/shortcut.tcl:40 lib/shortcut.tcl:72
+#: git-gui.sh:2812 git-gui.sh:2816 git-gui.sh:2820
 msgid "Create Desktop Icon"
 msgstr "Desktop-Icon erstellen"
 
-#: git-gui.sh:2539 lib/choose_repository.tcl:183 lib/choose_repository.tcl:191
+#: git-gui.sh:2828 lib/choose_repository.tcl:209 lib/choose_repository.tcl:217
 msgid "Quit"
 msgstr "Beenden"
 
-#: git-gui.sh:2547
+#: git-gui.sh:2836
 msgid "Undo"
 msgstr "Rückgängig"
 
-#: git-gui.sh:2550
+#: git-gui.sh:2839
 msgid "Redo"
 msgstr "Wiederholen"
 
-#: git-gui.sh:2554 git-gui.sh:3109
+#: git-gui.sh:2843 git-gui.sh:3461
 msgid "Cut"
 msgstr "Ausschneiden"
 
-#: git-gui.sh:2557 git-gui.sh:3112 git-gui.sh:3186 git-gui.sh:3259
+#: git-gui.sh:2846 git-gui.sh:3464 git-gui.sh:3540 git-gui.sh:3633
 #: lib/console.tcl:69
 msgid "Copy"
 msgstr "Kopieren"
 
-#: git-gui.sh:2560 git-gui.sh:3115
+#: git-gui.sh:2849 git-gui.sh:3467
 msgid "Paste"
 msgstr "Einfügen"
 
-#: git-gui.sh:2563 git-gui.sh:3118 lib/branch_delete.tcl:26
-#: lib/remote_branch_delete.tcl:38
+#: git-gui.sh:2852 git-gui.sh:3470 lib/remote_branch_delete.tcl:39
+#: lib/branch_delete.tcl:28
 msgid "Delete"
 msgstr "Löschen"
 
-#: git-gui.sh:2567 git-gui.sh:3122 git-gui.sh:3263 lib/console.tcl:71
+#: git-gui.sh:2856 git-gui.sh:3474 git-gui.sh:3637 lib/console.tcl:71
 msgid "Select All"
 msgstr "Alle auswählen"
 
-#: git-gui.sh:2576
+#: git-gui.sh:2865
 msgid "Create..."
 msgstr "Erstellen..."
 
-#: git-gui.sh:2582
+#: git-gui.sh:2871
 msgid "Checkout..."
 msgstr "Umstellen..."
 
-#: git-gui.sh:2588
+#: git-gui.sh:2877
 msgid "Rename..."
 msgstr "Umbenennen..."
 
-#: git-gui.sh:2593
+#: git-gui.sh:2882
 msgid "Delete..."
 msgstr "Löschen..."
 
-#: git-gui.sh:2598
+#: git-gui.sh:2887
 msgid "Reset..."
 msgstr "Zurücksetzen..."
 
-#: git-gui.sh:2608
+#: git-gui.sh:2897
 msgid "Done"
 msgstr "Fertig"
 
-#: git-gui.sh:2610
+#: git-gui.sh:2899
 msgid "Commit@@verb"
 msgstr "Eintragen"
 
-#: git-gui.sh:2619 git-gui.sh:3050
-msgid "New Commit"
-msgstr "Neue Version"
-
-#: git-gui.sh:2627 git-gui.sh:3057
+#: git-gui.sh:2908 git-gui.sh:3400
 msgid "Amend Last Commit"
 msgstr "Letzte nachbessern"
 
-#: git-gui.sh:2637 git-gui.sh:3011 lib/remote_branch_delete.tcl:99
+#: git-gui.sh:2918 git-gui.sh:3361 lib/remote_branch_delete.tcl:101
 msgid "Rescan"
 msgstr "Neu laden"
 
-#: git-gui.sh:2643
+#: git-gui.sh:2924
 msgid "Stage To Commit"
 msgstr "Zum Eintragen bereitstellen"
 
-#: git-gui.sh:2649
+#: git-gui.sh:2930
 msgid "Stage Changed Files To Commit"
 msgstr "Geänderte Dateien bereitstellen"
 
-#: git-gui.sh:2655
+#: git-gui.sh:2936
 msgid "Unstage From Commit"
 msgstr "Aus der Bereitstellung herausnehmen"
 
-#: git-gui.sh:2661 lib/index.tcl:412
+#: git-gui.sh:2942 lib/index.tcl:521
 msgid "Revert Changes"
 msgstr "Änderungen verwerfen"
 
-#: git-gui.sh:2669 git-gui.sh:3310 git-gui.sh:3341
+#: git-gui.sh:2950 git-gui.sh:3700 git-gui.sh:3731
 msgid "Show Less Context"
 msgstr "Weniger Zeilen anzeigen"
 
-#: git-gui.sh:2673 git-gui.sh:3314 git-gui.sh:3345
+#: git-gui.sh:2954 git-gui.sh:3704 git-gui.sh:3735
 msgid "Show More Context"
 msgstr "Mehr Zeilen anzeigen"
 
-#: git-gui.sh:2680 git-gui.sh:3024 git-gui.sh:3133
+#: git-gui.sh:2961 git-gui.sh:3374 git-gui.sh:3485
 msgid "Sign Off"
 msgstr "Abzeichnen"
 
-#: git-gui.sh:2696
+#: git-gui.sh:2977
 msgid "Local Merge..."
 msgstr "Lokales Zusammenführen..."
 
-#: git-gui.sh:2701
+#: git-gui.sh:2982
 msgid "Abort Merge..."
 msgstr "Zusammenführen abbrechen..."
 
-#: git-gui.sh:2713 git-gui.sh:2741
+#: git-gui.sh:2994 git-gui.sh:3022
 msgid "Add..."
 msgstr "Hinzufügen..."
 
-#: git-gui.sh:2717
+#: git-gui.sh:2998
 msgid "Push..."
 msgstr "Versenden..."
 
-#: git-gui.sh:2721
+#: git-gui.sh:3002
 msgid "Delete Branch..."
 msgstr "Zweig löschen..."
 
-#: git-gui.sh:2731 git-gui.sh:3292
+#: git-gui.sh:3012 git-gui.sh:3666
 msgid "Options..."
 msgstr "Optionen..."
 
-#: git-gui.sh:2742
+#: git-gui.sh:3023
 msgid "Remove..."
 msgstr "Entfernen..."
 
-#: git-gui.sh:2751 lib/choose_repository.tcl:50
+#: git-gui.sh:3032 lib/choose_repository.tcl:67
 msgid "Help"
 msgstr "Hilfe"
 
-#: git-gui.sh:2755 git-gui.sh:2759 lib/about.tcl:14
-#: lib/choose_repository.tcl:44 lib/choose_repository.tcl:53
+#: git-gui.sh:3036 git-gui.sh:3040 lib/choose_repository.tcl:61
+#: lib/choose_repository.tcl:70 lib/about.tcl:14
 #, tcl-format
 msgid "About %s"
 msgstr "Über %s"
 
-#: git-gui.sh:2783
+#: git-gui.sh:3064
 msgid "Online Documentation"
 msgstr "Online-Dokumentation"
 
-#: git-gui.sh:2786 lib/choose_repository.tcl:47 lib/choose_repository.tcl:56
+#: git-gui.sh:3067 lib/choose_repository.tcl:64 lib/choose_repository.tcl:73
 msgid "Show SSH Key"
 msgstr "SSH-Schlüssel anzeigen"
 
-#: git-gui.sh:2893
+#: git-gui.sh:3097 git-gui.sh:3229
+msgid "usage:"
+msgstr ""
+
+#: git-gui.sh:3101 git-gui.sh:3233
+msgid "Usage"
+msgstr ""
+
+#: git-gui.sh:3182 lib/blame.tcl:575
+#, fuzzy
+msgid "Error"
+msgstr "Fehler"
+
+#: git-gui.sh:3213
 #, tcl-format
 msgid "fatal: cannot stat path %s: No such file or directory"
 msgstr ""
 "Fehler: Verzeichnis »%s« kann nicht gelesen werden: Datei oder Verzeichnis "
 "nicht gefunden"
 
-#: git-gui.sh:2926
+#: git-gui.sh:3246
 msgid "Current Branch:"
 msgstr "Aktueller Zweig:"
 
-#: git-gui.sh:2947
-msgid "Staged Changes (Will Commit)"
-msgstr "Bereitstellung (zum Eintragen)"
-
-#: git-gui.sh:2967
+#: git-gui.sh:3271
 msgid "Unstaged Changes"
 msgstr "Nicht bereitgestellte Änderungen"
 
-#: git-gui.sh:3017
+#: git-gui.sh:3293
+msgid "Staged Changes (Will Commit)"
+msgstr "Bereitstellung (zum Eintragen)"
+
+#: git-gui.sh:3367
 msgid "Stage Changed"
 msgstr "Alles bereitstellen"
 
-#: git-gui.sh:3036 lib/transport.tcl:104 lib/transport.tcl:193
+#: git-gui.sh:3386 lib/transport.tcl:137
 msgid "Push"
 msgstr "Versenden"
 
-#: git-gui.sh:3071
+#: git-gui.sh:3413
 msgid "Initial Commit Message:"
 msgstr "Erste Versionsbeschreibung:"
 
-#: git-gui.sh:3072
+#: git-gui.sh:3414
 msgid "Amended Commit Message:"
 msgstr "Nachgebesserte Beschreibung:"
 
-#: git-gui.sh:3073
+#: git-gui.sh:3415
 msgid "Amended Initial Commit Message:"
 msgstr "Nachgebesserte erste Beschreibung:"
 
-#: git-gui.sh:3074
+#: git-gui.sh:3416
 msgid "Amended Merge Commit Message:"
 msgstr "Nachgebesserte Zusammenführungs-Beschreibung:"
 
-#: git-gui.sh:3075
+#: git-gui.sh:3417
 msgid "Merge Commit Message:"
 msgstr "Zusammenführungs-Beschreibung:"
 
-#: git-gui.sh:3076
+#: git-gui.sh:3418
 msgid "Commit Message:"
 msgstr "Versionsbeschreibung:"
 
-#: git-gui.sh:3125 git-gui.sh:3267 lib/console.tcl:73
+#: git-gui.sh:3477 git-gui.sh:3641 lib/console.tcl:73
 msgid "Copy All"
 msgstr "Alle kopieren"
 
-#: git-gui.sh:3149 lib/blame.tcl:104
+#: git-gui.sh:3501 lib/blame.tcl:106
 msgid "File:"
 msgstr "Datei:"
 
-#: git-gui.sh:3255
+#: git-gui.sh:3549 lib/choose_repository.tcl:1100
+msgid "Open"
+msgstr "Öffnen"
+
+#: git-gui.sh:3629
 msgid "Refresh"
 msgstr "Aktualisieren"
 
-#: git-gui.sh:3276
+#: git-gui.sh:3650
 msgid "Decrease Font Size"
 msgstr "Schriftgröße verkleinern"
 
-#: git-gui.sh:3280
+#: git-gui.sh:3654
 msgid "Increase Font Size"
 msgstr "Schriftgröße vergrößern"
 
-#: git-gui.sh:3288 lib/blame.tcl:281
+#: git-gui.sh:3662 lib/blame.tcl:296
 msgid "Encoding"
 msgstr "Zeichenkodierung"
 
-#: git-gui.sh:3299
+#: git-gui.sh:3673
 msgid "Apply/Reverse Hunk"
 msgstr "Kontext anwenden/umkehren"
 
-#: git-gui.sh:3304
+#: git-gui.sh:3678
 msgid "Apply/Reverse Line"
 msgstr "Zeile anwenden/umkehren"
 
-#: git-gui.sh:3323
+#: git-gui.sh:3684 git-gui.sh:3794 git-gui.sh:3805
+#, fuzzy
+msgid "Revert Hunk"
+msgstr "Kontext anwenden/umkehren"
+
+#: git-gui.sh:3689 git-gui.sh:3801 git-gui.sh:3812
+#, fuzzy
+msgid "Revert Line"
+msgstr "Änderungen verwerfen"
+
+#: git-gui.sh:3694 git-gui.sh:3791
+msgid "Undo Last Revert"
+msgstr ""
+
+#: git-gui.sh:3713
 msgid "Run Merge Tool"
 msgstr "Zusammenführungswerkzeug"
 
-#: git-gui.sh:3328
+#: git-gui.sh:3718
 msgid "Use Remote Version"
 msgstr "Externe Version benutzen"
 
-#: git-gui.sh:3332
+#: git-gui.sh:3722
 msgid "Use Local Version"
 msgstr "Lokale Version benutzen"
 
-#: git-gui.sh:3336
+#: git-gui.sh:3726
 msgid "Revert To Base"
 msgstr "Ursprüngliche Version benutzen"
 
-#: git-gui.sh:3354
+#: git-gui.sh:3744
 msgid "Visualize These Changes In The Submodule"
 msgstr "Diese Änderungen im Untermodul darstellen"
 
-#: git-gui.sh:3358
+#: git-gui.sh:3748
 msgid "Visualize Current Branch History In The Submodule"
 msgstr "Aktuellen Zweig im Untermodul darstellen"
 
-#: git-gui.sh:3362
+#: git-gui.sh:3752
 msgid "Visualize All Branch History In The Submodule"
 msgstr "Alle Zweige im Untermodul darstellen"
 
-#: git-gui.sh:3367
+#: git-gui.sh:3757
 msgid "Start git gui In The Submodule"
 msgstr "Git gui im Untermodul starten"
 
-#: git-gui.sh:3389
+#: git-gui.sh:3793
 msgid "Unstage Hunk From Commit"
 msgstr "Kontext aus Bereitstellung herausnehmen"
 
-#: git-gui.sh:3391
+#: git-gui.sh:3797
 msgid "Unstage Lines From Commit"
 msgstr "Zeilen aus der Bereitstellung herausnehmen"
 
-#: git-gui.sh:3393
+#: git-gui.sh:3798 git-gui.sh:3809
+#, fuzzy
+msgid "Revert Lines"
+msgstr "Änderungen verwerfen"
+
+#: git-gui.sh:3800
 msgid "Unstage Line From Commit"
 msgstr "Zeile aus der Bereitstellung herausnehmen"
 
-#: git-gui.sh:3396
+#: git-gui.sh:3804
 msgid "Stage Hunk For Commit"
 msgstr "Kontext zur Bereitstellung hinzufügen"
 
-#: git-gui.sh:3398
+#: git-gui.sh:3808
 msgid "Stage Lines For Commit"
 msgstr "Zeilen zur Bereitstellung hinzufügen"
 
-#: git-gui.sh:3400
+#: git-gui.sh:3811
 msgid "Stage Line For Commit"
 msgstr "Zeile zur Bereitstellung hinzufügen"
 
-#: git-gui.sh:3424
+#: git-gui.sh:3861
 msgid "Initializing..."
 msgstr "Initialisieren..."
 
-#: git-gui.sh:3541
+#: git-gui.sh:4017
 #, tcl-format
 msgid ""
 "Possible environment issues exist.\n"
@@ -548,7 +602,7 @@ msgstr ""
 "von %s an Git weitergegeben werden:\n"
 "\n"
 
-#: git-gui.sh:3570
+#: git-gui.sh:4046
 msgid ""
 "\n"
 "This is due to a known issue with the\n"
@@ -558,7 +612,7 @@ msgstr ""
 "Dies ist ein bekanntes Problem der Tcl-Version, die\n"
 "in Cygwin mitgeliefert wird."
 
-#: git-gui.sh:3575
+#: git-gui.sh:4051
 #, tcl-format
 msgid ""
 "\n"
@@ -574,341 +628,183 @@ msgstr ""
 "gewünschten Werte für die Einstellung user.name und \n"
 "user.email in Ihre Datei ~/.gitconfig einfügen.\n"
 
-#: lib/about.tcl:26
-msgid "git-gui - a graphical user interface for Git."
-msgstr "git-gui - eine grafische Oberfläche für Git."
-
-#: lib/blame.tcl:72
-msgid "File Viewer"
-msgstr "Datei-Browser"
-
-#: lib/blame.tcl:78
-msgid "Commit:"
-msgstr "Version:"
-
-#: lib/blame.tcl:271
-msgid "Copy Commit"
-msgstr "Version kopieren"
-
-#: lib/blame.tcl:275
-msgid "Find Text..."
-msgstr "Text suchen..."
-
-#: lib/blame.tcl:284
-msgid "Do Full Copy Detection"
-msgstr "Volle Kopie-Erkennung"
+#: lib/spellcheck.tcl:57
+msgid "Unsupported spell checker"
+msgstr "Rechtschreibprüfungsprogramm nicht unterstützt"
 
-#: lib/blame.tcl:288
-msgid "Show History Context"
-msgstr "Historien-Kontext anzeigen"
+#: lib/spellcheck.tcl:65
+msgid "Spell checking is unavailable"
+msgstr "Rechtschreibprüfung nicht verfügbar"
 
-#: lib/blame.tcl:291
-msgid "Blame Parent Commit"
-msgstr "Elternversion annotieren"
+#: lib/spellcheck.tcl:68
+msgid "Invalid spell checking configuration"
+msgstr "Unbenutzbare Konfiguration der Rechtschreibprüfung"
 
-#: lib/blame.tcl:450
+#: lib/spellcheck.tcl:70
 #, tcl-format
-msgid "Reading %s..."
-msgstr "%s lesen..."
-
-#: lib/blame.tcl:557
-msgid "Loading copy/move tracking annotations..."
-msgstr "Annotierungen für Kopieren/Verschieben werden geladen..."
-
-#: lib/blame.tcl:577
-msgid "lines annotated"
-msgstr "Zeilen annotiert"
+msgid "Reverting dictionary to %s."
+msgstr "Wörterbuch auf %s zurückgesetzt."
 
-#: lib/blame.tcl:769
-msgid "Loading original location annotations..."
-msgstr "Annotierungen für ursprünglichen Ort werden geladen..."
+#: lib/spellcheck.tcl:73
+msgid "Spell checker silently failed on startup"
+msgstr "Rechtschreibprüfungsprogramm mit Fehler abgebrochen"
 
-#: lib/blame.tcl:772
-msgid "Annotation complete."
-msgstr "Annotierung vollständig."
+#: lib/spellcheck.tcl:80
+msgid "Unrecognized spell checker"
+msgstr "Unbekanntes Rechtschreibprüfungsprogramm"
 
-#: lib/blame.tcl:802
-msgid "Busy"
-msgstr "Verarbeitung läuft"
+#: lib/spellcheck.tcl:186
+msgid "No Suggestions"
+msgstr "Keine Vorschläge"
 
-#: lib/blame.tcl:803
-msgid "Annotation process is already running."
-msgstr "Annotierung läuft bereits."
+#: lib/spellcheck.tcl:388
+msgid "Unexpected EOF from spell checker"
+msgstr "Unerwartetes EOF vom Rechtschreibprüfungsprogramm"
 
-#: lib/blame.tcl:842
-msgid "Running thorough copy detection..."
-msgstr "Intensive Kopie-Erkennung läuft..."
+#: lib/spellcheck.tcl:392
+msgid "Spell Checker Failed"
+msgstr "Rechtschreibprüfung fehlgeschlagen"
 
-#: lib/blame.tcl:910
-msgid "Loading annotation..."
-msgstr "Annotierung laden..."
+#: lib/transport.tcl:6 lib/remote_add.tcl:132
+#, tcl-format
+msgid "fetch %s"
+msgstr "»%s« anfordern"
 
-#: lib/blame.tcl:963
-msgid "Author:"
-msgstr "Autor:"
+#: lib/transport.tcl:7
+#, tcl-format
+msgid "Fetching new changes from %s"
+msgstr "Neue Änderungen von »%s« holen"
 
-#: lib/blame.tcl:967
-msgid "Committer:"
-msgstr "Eintragender:"
+#: lib/transport.tcl:18
+#, tcl-format
+msgid "remote prune %s"
+msgstr "Aufräumen von »%s«"
 
-#: lib/blame.tcl:972
-msgid "Original File:"
-msgstr "Ursprüngliche Datei:"
+#: lib/transport.tcl:19
+#, tcl-format
+msgid "Pruning tracking branches deleted from %s"
+msgstr "Übernahmezweige aufräumen und entfernen, die in »%s« gelöscht wurden"
 
-#: lib/blame.tcl:1020
-msgid "Cannot find HEAD commit:"
-msgstr "Zweigspitze (»HEAD«) kann nicht gefunden werden:"
+#: lib/transport.tcl:25
+msgid "fetch all remotes"
+msgstr ""
 
-#: lib/blame.tcl:1075
-msgid "Cannot find parent commit:"
-msgstr "Elternversion kann nicht gefunden werden:"
+#: lib/transport.tcl:26
+#, fuzzy
+msgid "Fetching new changes from all remotes"
+msgstr "Neue Änderungen von »%s« holen"
 
-#: lib/blame.tcl:1090
-msgid "Unable to display parent"
-msgstr "Elternversion kann nicht angezeigt werden"
+#: lib/transport.tcl:40
+#, fuzzy
+msgid "remote prune all remotes"
+msgstr "Aufräumen von »%s«"
 
-#: lib/blame.tcl:1091 lib/diff.tcl:320
-msgid "Error loading diff:"
-msgstr "Fehler beim Laden des Vergleichs:"
+#: lib/transport.tcl:41
+#, fuzzy
+msgid "Pruning tracking branches deleted from all remotes"
+msgstr "Übernahmezweige aufräumen und entfernen, die in »%s« gelöscht wurden"
 
-#: lib/blame.tcl:1231
-msgid "Originally By:"
-msgstr "Ursprünglich von:"
+#: lib/transport.tcl:54 lib/transport.tcl:92 lib/transport.tcl:110
+#: lib/remote_add.tcl:162
+#, tcl-format
+msgid "push %s"
+msgstr "»%s« versenden..."
 
-#: lib/blame.tcl:1237
-msgid "In File:"
-msgstr "In Datei:"
+#: lib/transport.tcl:55
+#, tcl-format
+msgid "Pushing changes to %s"
+msgstr "Änderungen nach »%s« versenden"
 
-#: lib/blame.tcl:1242
-msgid "Copied Or Moved Here By:"
-msgstr "Kopiert oder verschoben durch:"
+#: lib/transport.tcl:93
+#, tcl-format
+msgid "Mirroring to %s"
+msgstr "Spiegeln nach %s"
 
-#: lib/branch_checkout.tcl:14 lib/branch_checkout.tcl:19
-msgid "Checkout Branch"
-msgstr "Auf Zweig umstellen"
+#: lib/transport.tcl:111
+#, tcl-format
+msgid "Pushing %s %s to %s"
+msgstr "%s %s nach %s versenden"
 
-#: lib/branch_checkout.tcl:23
-msgid "Checkout"
-msgstr "Umstellen"
+#: lib/transport.tcl:132
+msgid "Push Branches"
+msgstr "Zweige versenden"
 
-#: lib/branch_checkout.tcl:27 lib/branch_create.tcl:35
-#: lib/branch_delete.tcl:32 lib/branch_rename.tcl:30 lib/browser.tcl:282
-#: lib/checkout_op.tcl:579 lib/choose_font.tcl:43 lib/merge.tcl:172
-#: lib/option.tcl:125 lib/remote_add.tcl:32 lib/remote_branch_delete.tcl:42
-#: lib/tools_dlg.tcl:40 lib/tools_dlg.tcl:204 lib/tools_dlg.tcl:352
-#: lib/transport.tcl:108
+#: lib/transport.tcl:141 lib/checkout_op.tcl:580 lib/remote_add.tcl:34
+#: lib/browser.tcl:292 lib/branch_checkout.tcl:30 lib/branch_rename.tcl:32
+#: lib/choose_font.tcl:45 lib/option.tcl:127 lib/tools_dlg.tcl:41
+#: lib/tools_dlg.tcl:202 lib/tools_dlg.tcl:345 lib/remote_branch_delete.tcl:43
+#: lib/branch_create.tcl:37 lib/branch_delete.tcl:34 lib/merge.tcl:178
 msgid "Cancel"
 msgstr "Abbrechen"
 
-#: lib/branch_checkout.tcl:32 lib/browser.tcl:287 lib/tools_dlg.tcl:328
-msgid "Revision"
-msgstr "Version"
-
-#: lib/branch_checkout.tcl:36 lib/branch_create.tcl:69 lib/option.tcl:280
-msgid "Options"
-msgstr "Optionen"
-
-#: lib/branch_checkout.tcl:39 lib/branch_create.tcl:92
-msgid "Fetch Tracking Branch"
-msgstr "Übernahmezweig anfordern"
-
-#: lib/branch_checkout.tcl:44
-msgid "Detach From Local Branch"
-msgstr "Verbindung zu lokalem Zweig lösen"
-
-#: lib/branch_create.tcl:22
-msgid "Create Branch"
-msgstr "Zweig erstellen"
-
-#: lib/branch_create.tcl:27
-msgid "Create New Branch"
-msgstr "Neuen Zweig erstellen"
+#: lib/transport.tcl:147
+msgid "Source Branches"
+msgstr "Lokale Zweige"
 
-#: lib/branch_create.tcl:31 lib/choose_repository.tcl:381
-msgid "Create"
-msgstr "Erstellen"
+#: lib/transport.tcl:162
+msgid "Destination Repository"
+msgstr "Ziel-Projektarchiv"
 
-#: lib/branch_create.tcl:40
-msgid "Branch Name"
-msgstr "Zweigname"
+#: lib/transport.tcl:165 lib/remote_branch_delete.tcl:51
+msgid "Remote:"
+msgstr "Externes Archiv:"
 
-#: lib/branch_create.tcl:43 lib/remote_add.tcl:39 lib/tools_dlg.tcl:50
-msgid "Name:"
-msgstr "Name:"
+#: lib/transport.tcl:187 lib/remote_branch_delete.tcl:72
+msgid "Arbitrary Location:"
+msgstr "Adresse:"
 
-#: lib/branch_create.tcl:58
-msgid "Match Tracking Branch Name"
-msgstr "Passend zu Übernahmezweig-Name"
+#: lib/transport.tcl:205
+msgid "Transfer Options"
+msgstr "Netzwerk-Einstellungen"
 
-#: lib/branch_create.tcl:66
-msgid "Starting Revision"
-msgstr "Anfangsversion"
+#: lib/transport.tcl:207
+msgid "Force overwrite existing branch (may discard changes)"
+msgstr ""
+"Überschreiben von existierenden Zweigen erzwingen (könnte Änderungen löschen)"
 
-#: lib/branch_create.tcl:72
-msgid "Update Existing Branch:"
-msgstr "Existierenden Zweig aktualisieren:"
+#: lib/transport.tcl:211
+msgid "Use thin pack (for slow network connections)"
+msgstr "Kompaktes Datenformat benutzen (für langsame Netzverbindungen)"
 
-#: lib/branch_create.tcl:75
-msgid "No"
-msgstr "Nein"
+#: lib/transport.tcl:215
+msgid "Include tags"
+msgstr "Mit Markierungen übertragen"
 
-#: lib/branch_create.tcl:80
-msgid "Fast Forward Only"
-msgstr "Nur Schnellzusammenführung"
+#: lib/transport.tcl:229
+#, tcl-format
+msgid "%s (%s): Push"
+msgstr ""
 
-#: lib/branch_create.tcl:85 lib/checkout_op.tcl:571
-msgid "Reset"
-msgstr "Zurücksetzen"
+#: lib/checkout_op.tcl:85
+#, tcl-format
+msgid "Fetching %s from %s"
+msgstr "Änderungen »%s« von »%s« anfordern"
 
-#: lib/branch_create.tcl:97
-msgid "Checkout After Creation"
-msgstr "Arbeitskopie umstellen nach Erstellen"
+#: lib/checkout_op.tcl:133
+#, tcl-format
+msgid "fatal: Cannot resolve %s"
+msgstr "Fehler: »%s« kann nicht als Zweig oder Version erkannt werden"
 
-#: lib/branch_create.tcl:131
-msgid "Please select a tracking branch."
-msgstr "Bitte wählen Sie einen Übernahmezweig."
+#: lib/checkout_op.tcl:146 lib/sshkey.tcl:58 lib/console.tcl:81
+#: lib/database.tcl:30
+msgid "Close"
+msgstr "Schließen"
 
-#: lib/branch_create.tcl:140
+#: lib/checkout_op.tcl:175
 #, tcl-format
-msgid "Tracking branch %s is not a branch in the remote repository."
-msgstr "Übernahmezweig »%s« ist kein Zweig im externen Projektarchiv."
+msgid "Branch '%s' does not exist."
+msgstr "Zweig »%s« existiert nicht."
 
-#: lib/branch_create.tcl:153 lib/branch_rename.tcl:86
-msgid "Please supply a branch name."
-msgstr "Bitte geben Sie einen Zweignamen an."
+#: lib/checkout_op.tcl:194
+#, tcl-format
+msgid "Failed to configure simplified git-pull for '%s'."
+msgstr "Fehler beim Einrichten der vereinfachten git-pull für »%s«."
 
-#: lib/branch_create.tcl:164 lib/branch_rename.tcl:106
-#, tcl-format
-msgid "'%s' is not an acceptable branch name."
-msgstr "»%s« ist kein zulässiger Zweigname."
-
-#: lib/branch_delete.tcl:15
-msgid "Delete Branch"
-msgstr "Zweig löschen"
-
-#: lib/branch_delete.tcl:20
-msgid "Delete Local Branch"
-msgstr "Lokalen Zweig löschen"
-
-#: lib/branch_delete.tcl:37
-msgid "Local Branches"
-msgstr "Lokale Zweige"
-
-#: lib/branch_delete.tcl:52
-msgid "Delete Only If Merged Into"
-msgstr "Nur löschen, wenn zusammengeführt nach"
-
-#: lib/branch_delete.tcl:54 lib/remote_branch_delete.tcl:119
-msgid "Always (Do not perform merge checks)"
-msgstr "Immer (Keine Zusammenführungsprüfung)"
-
-#: lib/branch_delete.tcl:103
-#, tcl-format
-msgid "The following branches are not completely merged into %s:"
-msgstr "Folgende Zweige sind noch nicht mit »%s« zusammengeführt:"
-
-#: lib/branch_delete.tcl:115 lib/remote_branch_delete.tcl:217
-msgid ""
-"Recovering deleted branches is difficult.\n"
-"\n"
-"Delete the selected branches?"
-msgstr ""
-"Das Wiederherstellen von gelöschten Zweigen ist nur mit größerem Aufwand "
-"möglich.\n"
-"\n"
-"Sollen die ausgewählten Zweige gelöscht werden?"
-
-#: lib/branch_delete.tcl:141
-#, tcl-format
-msgid ""
-"Failed to delete branches:\n"
-"%s"
-msgstr ""
-"Fehler beim Löschen der Zweige:\n"
-"%s"
-
-#: lib/branch_rename.tcl:14 lib/branch_rename.tcl:22
-msgid "Rename Branch"
-msgstr "Zweig umbenennen"
-
-#: lib/branch_rename.tcl:26
-msgid "Rename"
-msgstr "Umbenennen"
-
-#: lib/branch_rename.tcl:36
-msgid "Branch:"
-msgstr "Zweig:"
-
-#: lib/branch_rename.tcl:39
-msgid "New Name:"
-msgstr "Neuer Name:"
-
-#: lib/branch_rename.tcl:75
-msgid "Please select a branch to rename."
-msgstr "Bitte wählen Sie einen Zweig zum umbenennen."
-
-#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:202
+#: lib/checkout_op.tcl:202 lib/branch_rename.tcl:102
 #, tcl-format
 msgid "Branch '%s' already exists."
 msgstr "Zweig »%s« existiert bereits."
 
-#: lib/branch_rename.tcl:117
-#, tcl-format
-msgid "Failed to rename '%s'."
-msgstr "Fehler beim Umbenennen von »%s«."
-
-#: lib/browser.tcl:17
-msgid "Starting..."
-msgstr "Starten..."
-
-#: lib/browser.tcl:26
-msgid "File Browser"
-msgstr "Datei-Browser"
-
-#: lib/browser.tcl:126 lib/browser.tcl:143
-#, tcl-format
-msgid "Loading %s..."
-msgstr "%s laden..."
-
-#: lib/browser.tcl:187
-msgid "[Up To Parent]"
-msgstr "[Nach oben]"
-
-#: lib/browser.tcl:267 lib/browser.tcl:273
-msgid "Browse Branch Files"
-msgstr "Dateien des Zweigs durchblättern"
-
-#: lib/browser.tcl:278 lib/choose_repository.tcl:398
-#: lib/choose_repository.tcl:486 lib/choose_repository.tcl:497
-#: lib/choose_repository.tcl:1028
-msgid "Browse"
-msgstr "Blättern"
-
-#: lib/checkout_op.tcl:85
-#, tcl-format
-msgid "Fetching %s from %s"
-msgstr "Änderungen »%s« von »%s« anfordern"
-
-#: lib/checkout_op.tcl:133
-#, tcl-format
-msgid "fatal: Cannot resolve %s"
-msgstr "Fehler: »%s« kann nicht als Zweig oder Version erkannt werden"
-
-#: lib/checkout_op.tcl:146 lib/console.tcl:81 lib/database.tcl:31
-#: lib/sshkey.tcl:53
-msgid "Close"
-msgstr "Schließen"
-
-#: lib/checkout_op.tcl:175
-#, tcl-format
-msgid "Branch '%s' does not exist."
-msgstr "Zweig »%s« existiert nicht."
-
-#: lib/checkout_op.tcl:194
-#, tcl-format
-msgid "Failed to configure simplified git-pull for '%s'."
-msgstr "Fehler beim Einrichten der vereinfachten git-pull für »%s«."
-
 #: lib/checkout_op.tcl:229
 #, tcl-format
 msgid ""
@@ -961,23 +857,23 @@ msgstr "Arbeitskopie umstellen auf »%s«..."
 msgid "files checked out"
 msgstr "Dateien aktualisiert"
 
-#: lib/checkout_op.tcl:376
+#: lib/checkout_op.tcl:377
 #, tcl-format
 msgid "Aborted checkout of '%s' (file level merging is required)."
 msgstr ""
 "Auf Zweig »%s« umstellen abgebrochen (Zusammenführen der Dateien ist "
 "notwendig)."
 
-#: lib/checkout_op.tcl:377
+#: lib/checkout_op.tcl:378
 msgid "File level merge required."
 msgstr "Zusammenführen der Dateien ist notwendig."
 
-#: lib/checkout_op.tcl:381
+#: lib/checkout_op.tcl:382
 #, tcl-format
 msgid "Staying on branch '%s'."
 msgstr "Es wird auf Zweig »%s« verblieben."
 
-#: lib/checkout_op.tcl:452
+#: lib/checkout_op.tcl:453
 msgid ""
 "You are no longer on a local branch.\n"
 "\n"
@@ -989,32 +885,36 @@ msgstr ""
 "Wenn Sie auf einem Zweig arbeiten möchten, erstellen Sie bitte jetzt einen "
 "Zweig mit der Auswahl »Abgetrennte Arbeitskopie-Version«."
 
-#: lib/checkout_op.tcl:503 lib/checkout_op.tcl:507
+#: lib/checkout_op.tcl:504 lib/checkout_op.tcl:508
 #, tcl-format
 msgid "Checked out '%s'."
 msgstr "Umgestellt auf »%s«."
 
-#: lib/checkout_op.tcl:535
+#: lib/checkout_op.tcl:536
 #, tcl-format
 msgid "Resetting '%s' to '%s' will lose the following commits:"
 msgstr "Zurücksetzen von »%s« nach »%s« wird folgende Versionen verwerfen:"
 
-#: lib/checkout_op.tcl:557
+#: lib/checkout_op.tcl:558
 msgid "Recovering lost commits may not be easy."
 msgstr ""
 "Verworfene Versionen können nur mit größerem Aufwand wiederhergestellt "
 "werden."
 
-#: lib/checkout_op.tcl:562
+#: lib/checkout_op.tcl:563
 #, tcl-format
 msgid "Reset '%s'?"
 msgstr "»%s« zurücksetzen?"
 
-#: lib/checkout_op.tcl:567 lib/merge.tcl:164 lib/tools_dlg.tcl:343
+#: lib/checkout_op.tcl:568 lib/tools_dlg.tcl:336 lib/merge.tcl:170
 msgid "Visualize"
 msgstr "Darstellen"
 
-#: lib/checkout_op.tcl:635
+#: lib/checkout_op.tcl:572 lib/branch_create.tcl:85
+msgid "Reset"
+msgstr "Zurücksetzen"
+
+#: lib/checkout_op.tcl:636
 #, tcl-format
 msgid ""
 "Failed to set current branch.\n"
@@ -1032,658 +932,511 @@ msgstr ""
 "\n"
 "Dies ist ein interner Programmfehler von %s. Programm wird jetzt abgebrochen."
 
-#: lib/choose_font.tcl:39
-msgid "Select"
-msgstr "Auswählen"
-
-#: lib/choose_font.tcl:53
-msgid "Font Family"
-msgstr "Schriftfamilie"
-
-#: lib/choose_font.tcl:74
-msgid "Font Size"
-msgstr "Schriftgröße"
-
-#: lib/choose_font.tcl:91
-msgid "Font Example"
-msgstr "Schriftbeispiel"
+#: lib/remote_add.tcl:20
+#, fuzzy, tcl-format
+msgid "%s (%s): Add Remote"
+msgstr "Externes Archiv hinzufügen"
 
-#: lib/choose_font.tcl:103
-msgid ""
-"This is example text.\n"
-"If you like this text, it can be your font."
-msgstr ""
-"Dies ist ein Beispieltext.\n"
-"Wenn Ihnen dieser Text gefällt, sollten Sie diese Schriftart wählen."
+#: lib/remote_add.tcl:25
+msgid "Add New Remote"
+msgstr "Neues externes Archiv hinzufügen"
 
-#: lib/choose_repository.tcl:28
-msgid "Git Gui"
-msgstr "Git Gui"
+#: lib/remote_add.tcl:30 lib/tools_dlg.tcl:37
+msgid "Add"
+msgstr "Hinzufügen"
 
-#: lib/choose_repository.tcl:87 lib/choose_repository.tcl:386
-msgid "Create New Repository"
-msgstr "Neues Projektarchiv"
+#: lib/remote_add.tcl:39
+msgid "Remote Details"
+msgstr "Einzelheiten des externen Archivs"
 
-#: lib/choose_repository.tcl:93
-msgid "New..."
-msgstr "Neu..."
+#: lib/remote_add.tcl:41 lib/tools_dlg.tcl:51 lib/branch_create.tcl:44
+msgid "Name:"
+msgstr "Name:"
 
-#: lib/choose_repository.tcl:100 lib/choose_repository.tcl:471
-msgid "Clone Existing Repository"
-msgstr "Projektarchiv klonen"
+#: lib/remote_add.tcl:50
+msgid "Location:"
+msgstr "Adresse:"
 
-#: lib/choose_repository.tcl:106
-msgid "Clone..."
-msgstr "Klonen..."
+#: lib/remote_add.tcl:60
+msgid "Further Action"
+msgstr "Weitere Aktion jetzt"
 
-#: lib/choose_repository.tcl:113 lib/choose_repository.tcl:1016
-msgid "Open Existing Repository"
-msgstr "Projektarchiv öffnen"
+#: lib/remote_add.tcl:63
+msgid "Fetch Immediately"
+msgstr "Gleich anfordern"
 
-#: lib/choose_repository.tcl:119
-msgid "Open..."
-msgstr "Öffnen..."
+#: lib/remote_add.tcl:69
+msgid "Initialize Remote Repository and Push"
+msgstr "Externes Archiv initialisieren und dahin versenden"
 
-#: lib/choose_repository.tcl:132
-msgid "Recent Repositories"
-msgstr "Zuletzt benutzte Projektarchive"
+#: lib/remote_add.tcl:75
+msgid "Do Nothing Else Now"
+msgstr "Nichts tun"
 
-#: lib/choose_repository.tcl:138
-msgid "Open Recent Repository:"
-msgstr "Zuletzt benutztes Projektarchiv öffnen:"
+#: lib/remote_add.tcl:100
+msgid "Please supply a remote name."
+msgstr "Bitte geben Sie einen Namen des externen Archivs an."
 
-#: lib/choose_repository.tcl:306 lib/choose_repository.tcl:313
-#: lib/choose_repository.tcl:320
+#: lib/remote_add.tcl:113
 #, tcl-format
-msgid "Failed to create repository %s:"
-msgstr "Projektarchiv »%s« konnte nicht erstellt werden:"
+msgid "'%s' is not an acceptable remote name."
+msgstr "»%s« ist kein zulässiger Name eines externen Archivs."
 
-#: lib/choose_repository.tcl:391
-msgid "Directory:"
-msgstr "Verzeichnis:"
+#: lib/remote_add.tcl:124
+#, tcl-format
+msgid "Failed to add remote '%s' of location '%s'."
+msgstr ""
+"Fehler beim Hinzufügen des externen Archivs »%s« aus Herkunftsort »%s«."
 
-#: lib/choose_repository.tcl:423 lib/choose_repository.tcl:550
-#: lib/choose_repository.tcl:1052
-msgid "Git Repository"
-msgstr "Git Projektarchiv"
+#: lib/remote_add.tcl:133
+#, tcl-format
+msgid "Fetching the %s"
+msgstr "»%s« anfordern"
 
-#: lib/choose_repository.tcl:448
+#: lib/remote_add.tcl:156
 #, tcl-format
-msgid "Directory %s already exists."
-msgstr "Verzeichnis »%s« existiert bereits."
+msgid "Do not know how to initialize repository at location '%s'."
+msgstr ""
+"Initialisieren eines externen Archivs an Adresse »%s« ist nicht möglich."
 
-#: lib/choose_repository.tcl:452
+#: lib/remote_add.tcl:163
 #, tcl-format
-msgid "File %s already exists."
-msgstr "Datei »%s« existiert bereits."
+msgid "Setting up the %s (at %s)"
+msgstr "Einrichten von »%s« an »%s«"
 
-#: lib/choose_repository.tcl:466
-msgid "Clone"
-msgstr "Klonen"
+#: lib/browser.tcl:17
+msgid "Starting..."
+msgstr "Starten..."
 
-#: lib/choose_repository.tcl:479
-msgid "Source Location:"
-msgstr "Herkunft:"
+#: lib/browser.tcl:27
+#, fuzzy, tcl-format
+msgid "%s (%s): File Browser"
+msgstr "Datei-Browser"
 
-#: lib/choose_repository.tcl:490
-msgid "Target Directory:"
-msgstr "Zielverzeichnis:"
+#: lib/browser.tcl:132 lib/browser.tcl:149
+#, tcl-format
+msgid "Loading %s..."
+msgstr "%s laden..."
 
-#: lib/choose_repository.tcl:502
-msgid "Clone Type:"
-msgstr "Art des Klonens:"
+#: lib/browser.tcl:193
+msgid "[Up To Parent]"
+msgstr "[Nach oben]"
 
-#: lib/choose_repository.tcl:508
-msgid "Standard (Fast, Semi-Redundant, Hardlinks)"
-msgstr "Standard (schnell, teilweise redundant, Hardlinks)"
+#: lib/browser.tcl:275
+#, fuzzy, tcl-format
+msgid "%s (%s): Browse Branch Files"
+msgstr "Dateien des Zweigs durchblättern"
 
-#: lib/choose_repository.tcl:514
-msgid "Full Copy (Slower, Redundant Backup)"
-msgstr "Alles kopieren (langsamer, volle Redundanz)"
+#: lib/browser.tcl:282
+msgid "Browse Branch Files"
+msgstr "Dateien des Zweigs durchblättern"
 
-#: lib/choose_repository.tcl:520
-msgid "Shared (Fastest, Not Recommended, No Backup)"
-msgstr "Verknüpft (schnell, nicht empfohlen, kein Backup)"
+#: lib/browser.tcl:288 lib/choose_repository.tcl:437
+#: lib/choose_repository.tcl:524 lib/choose_repository.tcl:533
+#: lib/choose_repository.tcl:1115
+msgid "Browse"
+msgstr "Blättern"
 
-#: lib/choose_repository.tcl:556 lib/choose_repository.tcl:603
-#: lib/choose_repository.tcl:749 lib/choose_repository.tcl:819
-#: lib/choose_repository.tcl:1058 lib/choose_repository.tcl:1066
-#, tcl-format
-msgid "Not a Git repository: %s"
-msgstr "Kein Git-Projektarchiv in »%s« gefunden."
+#: lib/browser.tcl:297 lib/branch_checkout.tcl:35 lib/tools_dlg.tcl:321
+msgid "Revision"
+msgstr "Version"
 
-#: lib/choose_repository.tcl:592
-msgid "Standard only available for local repository."
-msgstr "Standard ist nur für lokale Projektarchive verfügbar."
+#: lib/index.tcl:6
+msgid "Unable to unlock the index."
+msgstr "Bereitstellung kann nicht wieder freigegeben werden."
 
-#: lib/choose_repository.tcl:596
-msgid "Shared only available for local repository."
-msgstr "Verknüpft ist nur für lokale Projektarchive verfügbar."
+#: lib/index.tcl:30
+msgid "Index Error"
+msgstr "Fehler in Bereitstellung"
 
-#: lib/choose_repository.tcl:617
-#, tcl-format
-msgid "Location %s already exists."
-msgstr "Projektarchiv »%s« existiert bereits."
+#: lib/index.tcl:32
+msgid ""
+"Updating the Git index failed.  A rescan will be automatically started to "
+"resynchronize git-gui."
+msgstr ""
+"Das Aktualisieren der Git-Bereitstellung ist fehlgeschlagen. Eine allgemeine "
+"Git-Aktualisierung wird jetzt gestartet, um git-gui wieder mit git zu "
+"synchronisieren."
 
-#: lib/choose_repository.tcl:628
-msgid "Failed to configure origin"
-msgstr "Der Ursprungsort konnte nicht eingerichtet werden"
+#: lib/index.tcl:43
+msgid "Continue"
+msgstr "Fortsetzen"
 
-#: lib/choose_repository.tcl:640
-msgid "Counting objects"
-msgstr "Objekte werden gezählt"
+#: lib/index.tcl:46
+msgid "Unlock Index"
+msgstr "Bereitstellung freigeben"
 
-#: lib/choose_repository.tcl:641
-msgid "buckets"
-msgstr "Buckets"
+#: lib/index.tcl:77 lib/index.tcl:146 lib/index.tcl:220 lib/index.tcl:587
+#: lib/choose_repository.tcl:999
+msgid "files"
+msgstr "Dateien"
 
-#: lib/choose_repository.tcl:665
-#, tcl-format
-msgid "Unable to copy objects/info/alternates: %s"
-msgstr "Kopien von Objekten/Info/Alternates konnten nicht erstellt werden: %s"
+#: lib/index.tcl:326
+#, fuzzy
+msgid "Unstaging selected files from commit"
+msgstr "Datei »%s« aus der Bereitstellung herausnehmen"
 
-#: lib/choose_repository.tcl:701
+#: lib/index.tcl:330
 #, tcl-format
-msgid "Nothing to clone from %s."
-msgstr "Von »%s« konnte nichts geklont werden."
+msgid "Unstaging %s from commit"
+msgstr "Datei »%s« aus der Bereitstellung herausnehmen"
 
-#: lib/choose_repository.tcl:703 lib/choose_repository.tcl:917
-#: lib/choose_repository.tcl:929
-msgid "The 'master' branch has not been initialized."
-msgstr "Der »master«-Zweig wurde noch nicht initialisiert."
+#: lib/index.tcl:369
+msgid "Ready to commit."
+msgstr "Bereit zum Eintragen."
 
-#: lib/choose_repository.tcl:716
-msgid "Hardlinks are unavailable.  Falling back to copying."
-msgstr "Hardlinks nicht verfügbar. Stattdessen wird kopiert."
+#: lib/index.tcl:378
+#, fuzzy
+msgid "Adding selected files"
+msgstr "Änderungen in gewählten Dateien verwerfen"
 
-#: lib/choose_repository.tcl:728
+#: lib/index.tcl:382
 #, tcl-format
-msgid "Cloning from %s"
-msgstr "Kopieren von »%s«"
-
-#: lib/choose_repository.tcl:759
-msgid "Copying objects"
-msgstr "Objektdatenbank kopieren"
-
-#: lib/choose_repository.tcl:760
-msgid "KiB"
-msgstr "KB"
+msgid "Adding %s"
+msgstr "»%s« hinzufügen..."
 
-#: lib/choose_repository.tcl:784
+#: lib/index.tcl:412
 #, tcl-format
-msgid "Unable to copy object: %s"
-msgstr "Objekt kann nicht kopiert werden: %s"
+msgid "Stage %d untracked files?"
+msgstr ""
 
-#: lib/choose_repository.tcl:794
-msgid "Linking objects"
-msgstr "Objekte verlinken"
+#: lib/index.tcl:420
+msgid "Adding all changed files"
+msgstr ""
 
-#: lib/choose_repository.tcl:795
-msgid "objects"
-msgstr "Objekte"
+#: lib/index.tcl:503
+#, tcl-format
+msgid "Revert changes in file %s?"
+msgstr "Änderungen in Datei »%s« verwerfen?"
 
-#: lib/choose_repository.tcl:803
+#: lib/index.tcl:508
 #, tcl-format
-msgid "Unable to hardlink object: %s"
-msgstr "Für Objekt konnte kein Hardlink erstellt werden: %s"
+msgid "Revert changes in these %i files?"
+msgstr "Änderungen in den gewählten %i Dateien verwerfen?"
 
-#: lib/choose_repository.tcl:858
-msgid "Cannot fetch branches and objects.  See console output for details."
+#: lib/index.tcl:517
+msgid "Any unstaged changes will be permanently lost by the revert."
 msgstr ""
-"Zweige und Objekte konnten nicht angefordert werden.  Kontrollieren Sie die "
-"Ausgaben auf der Konsole für weitere Angaben."
+"Alle nicht bereitgestellten Änderungen werden beim Verwerfen verloren gehen."
 
-#: lib/choose_repository.tcl:869
-msgid "Cannot fetch tags.  See console output for details."
+#: lib/index.tcl:520 lib/index.tcl:563
+msgid "Do Nothing"
+msgstr "Nichts tun"
+
+#: lib/index.tcl:545
+#, fuzzy, tcl-format
+msgid "Delete untracked file %s?"
+msgstr "Zweige auf »%s« werden gelöscht"
+
+#: lib/index.tcl:550
+#, fuzzy, tcl-format
+msgid "Delete these %i untracked files?"
+msgstr "Änderungen in den gewählten %i Dateien verwerfen?"
+
+#: lib/index.tcl:560
+msgid "Files will be permanently deleted."
 msgstr ""
-"Markierungen konnten nicht angefordert werden.  Kontrollieren Sie die "
-"Ausgaben auf der Konsole für weitere Angaben."
 
-#: lib/choose_repository.tcl:893
-msgid "Cannot determine HEAD.  See console output for details."
+#: lib/index.tcl:564
+#, fuzzy
+msgid "Delete Files"
+msgstr "Löschen"
+
+#: lib/index.tcl:586
+#, fuzzy
+msgid "Deleting"
+msgstr "Löschen"
+
+#: lib/index.tcl:665
+msgid "Encountered errors deleting files:\n"
 msgstr ""
-"Die Zweigspitze (HEAD) konnte nicht gefunden werden.  Kontrollieren Sie die "
-"Ausgaben auf der Konsole für weitere Angaben."
 
-#: lib/choose_repository.tcl:902
+#: lib/index.tcl:674
 #, tcl-format
-msgid "Unable to cleanup %s"
-msgstr "Verzeichnis »%s« kann nicht aufgeräumt werden."
+msgid "None of the %d selected files could be deleted."
+msgstr ""
 
-#: lib/choose_repository.tcl:908
-msgid "Clone failed."
-msgstr "Klonen fehlgeschlagen."
+#: lib/index.tcl:679
+#, tcl-format
+msgid "%d of the %d selected files could not be deleted."
+msgstr ""
 
-#: lib/choose_repository.tcl:915
-msgid "No default branch obtained."
-msgstr "Kein voreingestellter Zweig gefunden."
+#: lib/index.tcl:726
+msgid "Reverting selected files"
+msgstr "Änderungen in gewählten Dateien verwerfen"
 
-#: lib/choose_repository.tcl:926
+#: lib/index.tcl:730
 #, tcl-format
-msgid "Cannot resolve %s as a commit."
-msgstr "»%s« wurde nicht als Version gefunden."
+msgid "Reverting %s"
+msgstr "Änderungen in %s verwerfen"
 
-#: lib/choose_repository.tcl:938
-msgid "Creating working directory"
-msgstr "Arbeitskopie erstellen"
+#: lib/branch_checkout.tcl:16
+#, fuzzy, tcl-format
+msgid "%s (%s): Checkout Branch"
+msgstr "Auf Zweig umstellen"
 
-#: lib/choose_repository.tcl:939 lib/index.tcl:67 lib/index.tcl:130
-#: lib/index.tcl:198
-msgid "files"
-msgstr "Dateien"
+#: lib/branch_checkout.tcl:21
+msgid "Checkout Branch"
+msgstr "Auf Zweig umstellen"
 
-#: lib/choose_repository.tcl:968
-msgid "Initial file checkout failed."
-msgstr "Erstellen der Arbeitskopie fehlgeschlagen."
+#: lib/branch_checkout.tcl:26
+msgid "Checkout"
+msgstr "Umstellen"
 
-#: lib/choose_repository.tcl:1011
-msgid "Open"
-msgstr "Öffnen"
+#: lib/branch_checkout.tcl:39 lib/option.tcl:310 lib/branch_create.tcl:69
+msgid "Options"
+msgstr "Optionen"
 
-#: lib/choose_repository.tcl:1021
-msgid "Repository:"
-msgstr "Projektarchiv:"
+#: lib/branch_checkout.tcl:42 lib/branch_create.tcl:92
+msgid "Fetch Tracking Branch"
+msgstr "Übernahmezweig anfordern"
 
-#: lib/choose_repository.tcl:1072
+#: lib/branch_checkout.tcl:47
+msgid "Detach From Local Branch"
+msgstr "Verbindung zu lokalem Zweig lösen"
+
+#: lib/status_bar.tcl:263
 #, tcl-format
-msgid "Failed to open repository %s:"
-msgstr "Projektarchiv »%s« konnte nicht geöffnet werden."
+msgid "%s ... %*i of %*i %s (%3i%%)"
+msgstr "%s ... %*i von %*i %s (%3i%%)"
 
-#: lib/choose_rev.tcl:53
-msgid "This Detached Checkout"
-msgstr "Abgetrennte Arbeitskopie-Version"
+#: lib/remote.tcl:200
+msgid "Push to"
+msgstr "Versenden nach"
 
-#: lib/choose_rev.tcl:60
-msgid "Revision Expression:"
-msgstr "Version Regexp-Ausdruck:"
+#: lib/remote.tcl:218
+msgid "Remove Remote"
+msgstr "Externes Archiv entfernen"
 
-#: lib/choose_rev.tcl:74
-msgid "Local Branch"
-msgstr "Lokaler Zweig"
+#: lib/remote.tcl:223
+msgid "Prune from"
+msgstr "Aufräumen von"
 
-#: lib/choose_rev.tcl:79
-msgid "Tracking Branch"
-msgstr "Übernahmezweig"
+#: lib/remote.tcl:228
+msgid "Fetch from"
+msgstr "Anfordern von"
 
-#: lib/choose_rev.tcl:84 lib/choose_rev.tcl:538
-msgid "Tag"
-msgstr "Markierung"
+#: lib/remote.tcl:249 lib/remote.tcl:253 lib/remote.tcl:258 lib/remote.tcl:264
+msgid "All"
+msgstr ""
 
-#: lib/choose_rev.tcl:317
-#, tcl-format
-msgid "Invalid revision: %s"
-msgstr "Ungültige Version: %s"
+#: lib/branch_rename.tcl:15
+#, fuzzy, tcl-format
+msgid "%s (%s): Rename Branch"
+msgstr "Zweig umbenennen"
 
-#: lib/choose_rev.tcl:338
-msgid "No revision selected."
-msgstr "Keine Version ausgewählt."
+#: lib/branch_rename.tcl:23
+msgid "Rename Branch"
+msgstr "Zweig umbenennen"
 
-#: lib/choose_rev.tcl:346
-msgid "Revision expression is empty."
-msgstr "Versions-Ausdruck ist leer."
+#: lib/branch_rename.tcl:28
+msgid "Rename"
+msgstr "Umbenennen"
 
-#: lib/choose_rev.tcl:531
-msgid "Updated"
-msgstr "Aktualisiert"
+#: lib/branch_rename.tcl:38
+msgid "Branch:"
+msgstr "Zweig:"
 
-#: lib/choose_rev.tcl:559
-msgid "URL"
-msgstr "URL"
+#: lib/branch_rename.tcl:46
+msgid "New Name:"
+msgstr "Neuer Name:"
 
-#: lib/commit.tcl:9
-msgid ""
-"There is nothing to amend.\n"
-"\n"
-"You are about to create the initial commit.  There is no commit before this "
-"to amend.\n"
-msgstr ""
-"Keine Version zur Nachbesserung vorhanden.\n"
-"\n"
-"Sie sind dabei, die erste Version zu übertragen. Es gibt keine existierende "
-"Version, die Sie nachbessern könnten.\n"
+#: lib/branch_rename.tcl:81
+msgid "Please select a branch to rename."
+msgstr "Bitte wählen Sie einen Zweig zum umbenennen."
 
-#: lib/commit.tcl:18
-msgid ""
-"Cannot amend while merging.\n"
-"\n"
-"You are currently in the middle of a merge that has not been fully "
-"completed.  You cannot amend the prior commit unless you first abort the "
-"current merge activity.\n"
-msgstr ""
-"Nachbesserung währen Zusammenführung nicht möglich.\n"
-"\n"
-"Sie haben das Zusammenführen von Versionen angefangen, aber noch nicht "
-"beendet. Sie können keine vorige Übertragung nachbessern, solange eine "
-"unfertige Zusammenführung existiert. Dazu müssen Sie die Zusammenführung "
-"beenden oder abbrechen.\n"
+#: lib/branch_rename.tcl:92 lib/branch_create.tcl:154
+msgid "Please supply a branch name."
+msgstr "Bitte geben Sie einen Zweignamen an."
 
-#: lib/commit.tcl:48
-msgid "Error loading commit data for amend:"
-msgstr "Fehler beim Laden der Versionsdaten für Nachbessern:"
+#: lib/branch_rename.tcl:112 lib/branch_create.tcl:165
+#, tcl-format
+msgid "'%s' is not an acceptable branch name."
+msgstr "»%s« ist kein zulässiger Zweigname."
 
-#: lib/commit.tcl:75
-msgid "Unable to obtain your identity:"
-msgstr "Benutzername konnte nicht bestimmt werden:"
+#: lib/branch_rename.tcl:123
+#, tcl-format
+msgid "Failed to rename '%s'."
+msgstr "Fehler beim Umbenennen von »%s«."
 
-#: lib/commit.tcl:80
-msgid "Invalid GIT_COMMITTER_IDENT:"
-msgstr "Ungültiger Wert von GIT_COMMITTER_INDENT:"
+#: lib/choose_font.tcl:41
+msgid "Select"
+msgstr "Auswählen"
 
-#: lib/commit.tcl:129
-#, tcl-format
-msgid "warning: Tcl does not support encoding '%s'."
-msgstr "Warning: Tcl/Tk unterstützt die Zeichencodierung »%s« nicht."
+#: lib/choose_font.tcl:55
+msgid "Font Family"
+msgstr "Schriftfamilie"
+
+#: lib/choose_font.tcl:76
+msgid "Font Size"
+msgstr "Schriftgröße"
 
-#: lib/commit.tcl:149
+#: lib/choose_font.tcl:93
+msgid "Font Example"
+msgstr "Schriftbeispiel"
+
+#: lib/choose_font.tcl:105
 msgid ""
-"Last scanned state does not match repository state.\n"
-"\n"
-"Another Git program has modified this repository since the last scan.  A "
-"rescan must be performed before another commit can be created.\n"
-"\n"
-"The rescan will be automatically started now.\n"
+"This is example text.\n"
+"If you like this text, it can be your font."
 msgstr ""
-"Der letzte geladene Status stimmt nicht mehr mit dem Projektarchiv überein.\n"
-"\n"
-"Ein anderes Git-Programm hat das Projektarchiv seit dem letzten Laden "
-"geändert.  Vor dem Eintragen einer neuen Version muss neu geladen werden.\n"
-"\n"
-"Es wird gleich neu geladen.\n"
+"Dies ist ein Beispieltext.\n"
+"Wenn Ihnen dieser Text gefällt, sollten Sie diese Schriftart wählen."
 
-#: lib/commit.tcl:172
+#: lib/option.tcl:11
 #, tcl-format
-msgid ""
-"Unmerged files cannot be committed.\n"
-"\n"
-"File %s has merge conflicts.  You must resolve them and stage the file "
-"before committing.\n"
-msgstr ""
-"Nicht zusammengeführte Dateien können nicht eingetragen werden.\n"
-"\n"
-"Die Datei »%s« hat noch nicht aufgelöste Zusammenführungs-Konflikte. Sie "
-"müssen diese Konflikte auflösen, bevor Sie eintragen können.\n"
+msgid "Invalid global encoding '%s'"
+msgstr "Ungültige globale Zeichenkodierung »%s«"
 
-#: lib/commit.tcl:180
+#: lib/option.tcl:19
 #, tcl-format
-msgid ""
-"Unknown file state %s detected.\n"
-"\n"
-"File %s cannot be committed by this program.\n"
-msgstr ""
-"Unbekannter Dateizustand »%s«.\n"
-"\n"
-"Datei »%s« kann nicht eingetragen werden.\n"
+msgid "Invalid repo encoding '%s'"
+msgstr "Ungültige Archiv-Zeichenkodierung »%s«"
 
-#: lib/commit.tcl:188
-msgid ""
-"No changes to commit.\n"
-"\n"
-"You must stage at least 1 file before you can commit.\n"
-msgstr ""
-"Keine Änderungen vorhanden, die eingetragen werden könnten.\n"
-"\n"
-"Sie müssen mindestens eine Datei bereitstellen, bevor Sie eintragen können.\n"
+#: lib/option.tcl:119
+msgid "Restore Defaults"
+msgstr "Voreinstellungen wiederherstellen"
 
-#: lib/commit.tcl:203
-msgid ""
-"Please supply a commit message.\n"
-"\n"
-"A good commit message has the following format:\n"
-"\n"
-"- First line: Describe in one sentence what you did.\n"
-"- Second line: Blank\n"
-"- Remaining lines: Describe why this change is good.\n"
-msgstr ""
-"Bitte geben Sie eine Versionsbeschreibung ein.\n"
-"\n"
-"Eine gute Versionsbeschreibung enthält folgende Abschnitte:\n"
-"\n"
-"- Erste Zeile: Eine Zusammenfassung, was man gemacht hat.\n"
-"\n"
-"- Zweite Zeile: Leerzeile\n"
-"\n"
-"- Rest: Eine ausführliche Beschreibung, warum diese Änderung hilfreich ist.\n"
-
-#: lib/commit.tcl:234
-msgid "Calling pre-commit hook..."
-msgstr "Aufrufen der Vor-Eintragen-Kontrolle (»pre-commit hook«)..."
-
-#: lib/commit.tcl:249
-msgid "Commit declined by pre-commit hook."
-msgstr "Eintragen abgelehnt durch Vor-Eintragen-Kontrolle (»pre-commit hook«)."
-
-#: lib/commit.tcl:272
-msgid "Calling commit-msg hook..."
-msgstr "Aufrufen der Versionsbeschreibungs-Kontrolle (»commit-message hook«)..."
-
-#: lib/commit.tcl:287
-msgid "Commit declined by commit-msg hook."
-msgstr ""
-"Eintragen abgelehnt durch Versionsbeschreibungs-Kontrolle (»commit-message "
-"hook«)."
-
-#: lib/commit.tcl:300
-msgid "Committing changes..."
-msgstr "Änderungen eintragen..."
-
-#: lib/commit.tcl:316
-msgid "write-tree failed:"
-msgstr "write-tree fehlgeschlagen:"
-
-#: lib/commit.tcl:317 lib/commit.tcl:361 lib/commit.tcl:382
-msgid "Commit failed."
-msgstr "Eintragen fehlgeschlagen."
+#: lib/option.tcl:123
+msgid "Save"
+msgstr "Speichern"
 
-#: lib/commit.tcl:334
+#: lib/option.tcl:133
 #, tcl-format
-msgid "Commit %s appears to be corrupt"
-msgstr "Version »%s« scheint beschädigt zu sein"
-
-#: lib/commit.tcl:339
-msgid ""
-"No changes to commit.\n"
-"\n"
-"No files were modified by this commit and it was not a merge commit.\n"
-"\n"
-"A rescan will be automatically started now.\n"
-msgstr ""
-"Keine Änderungen einzutragen.\n"
-"\n"
-"Es gibt keine geänderte Datei bei dieser Version und es wurde auch nichts "
-"zusammengeführt.\n"
-"\n"
-"Das Arbeitsverzeichnis wird daher jetzt neu geladen.\n"
+msgid "%s Repository"
+msgstr "Projektarchiv %s"
 
-#: lib/commit.tcl:346
-msgid "No changes to commit."
-msgstr "Keine Änderungen, die eingetragen werden können."
+#: lib/option.tcl:134
+msgid "Global (All Repositories)"
+msgstr "Global (Alle Projektarchive)"
 
-#: lib/commit.tcl:360
-msgid "commit-tree failed:"
-msgstr "commit-tree fehlgeschlagen:"
+#: lib/option.tcl:140
+msgid "User Name"
+msgstr "Benutzername"
 
-#: lib/commit.tcl:381
-msgid "update-ref failed:"
-msgstr "update-ref fehlgeschlagen:"
+#: lib/option.tcl:141
+msgid "Email Address"
+msgstr "E-Mail-Adresse"
 
-#: lib/commit.tcl:469
-#, tcl-format
-msgid "Created commit %s: %s"
-msgstr "Version %s übertragen: %s"
+#: lib/option.tcl:143
+msgid "Summarize Merge Commits"
+msgstr "Zusammenführungs-Versionen zusammenfassen"
 
-#: lib/console.tcl:59
-msgid "Working... please wait..."
-msgstr "Verarbeitung. Bitte warten..."
+#: lib/option.tcl:144
+msgid "Merge Verbosity"
+msgstr "Ausführlichkeit der Zusammenführen-Meldungen"
 
-#: lib/console.tcl:186
-msgid "Success"
-msgstr "Erfolgreich"
+#: lib/option.tcl:145
+msgid "Show Diffstat After Merge"
+msgstr "Vergleichsstatistik nach Zusammenführen anzeigen"
 
-#: lib/console.tcl:200
-msgid "Error: Command Failed"
-msgstr "Fehler: Kommando fehlgeschlagen"
+#: lib/option.tcl:146
+msgid "Use Merge Tool"
+msgstr "Zusammenführungswerkzeug"
 
-#: lib/database.tcl:43
-msgid "Number of loose objects"
-msgstr "Anzahl unverknüpfter Objekte"
+#: lib/option.tcl:148
+msgid "Trust File Modification Timestamps"
+msgstr "Auf Dateiänderungsdatum verlassen"
 
-#: lib/database.tcl:44
-msgid "Disk space used by loose objects"
-msgstr "Festplattenplatz von unverknüpften Objekten"
+#: lib/option.tcl:149
+msgid "Prune Tracking Branches During Fetch"
+msgstr "Übernahmezweige aufräumen während Anforderung"
 
-#: lib/database.tcl:45
-msgid "Number of packed objects"
-msgstr "Anzahl komprimierter Objekte"
+#: lib/option.tcl:150
+msgid "Match Tracking Branches"
+msgstr "Passend zu Übernahmezweig"
 
-#: lib/database.tcl:46
-msgid "Number of packs"
-msgstr "Anzahl Komprimierungseinheiten"
+#: lib/option.tcl:151
+msgid "Use Textconv For Diffs and Blames"
+msgstr ""
 
-#: lib/database.tcl:47
-msgid "Disk space used by packed objects"
-msgstr "Festplattenplatz von komprimierten Objekten"
+#: lib/option.tcl:152
+msgid "Blame Copy Only On Changed Files"
+msgstr "Kopie-Annotieren nur bei geänderten Dateien"
 
-#: lib/database.tcl:48
-msgid "Packed objects waiting for pruning"
-msgstr "Komprimierte Objekte, die zum Aufräumen vorgesehen sind"
+#: lib/option.tcl:153
+#, fuzzy
+msgid "Maximum Length of Recent Repositories List"
+msgstr "Zuletzt benutzte Projektarchive"
 
-#: lib/database.tcl:49
-msgid "Garbage files"
-msgstr "Dateien im Mülleimer"
+#: lib/option.tcl:154
+msgid "Minimum Letters To Blame Copy On"
+msgstr "Mindestzahl Zeichen für Kopie-Annotieren"
 
-#: lib/database.tcl:72
-msgid "Compressing the object database"
-msgstr "Objektdatenbank komprimieren"
+#: lib/option.tcl:155
+msgid "Blame History Context Radius (days)"
+msgstr "Anzahl Tage für Historien-Kontext"
 
-#: lib/database.tcl:83
-msgid "Verifying the object database with fsck-objects"
-msgstr "Die Objektdatenbank durch »fsck-objects« überprüfen lassen"
+#: lib/option.tcl:156
+msgid "Number of Diff Context Lines"
+msgstr "Anzahl der Kontextzeilen beim Vergleich"
 
-#: lib/database.tcl:107
-#, tcl-format
-msgid ""
-"This repository currently has approximately %i loose objects.\n"
-"\n"
-"To maintain optimal performance it is strongly recommended that you compress "
-"the database.\n"
-"\n"
-"Compress the database now?"
+#: lib/option.tcl:157
+msgid "Additional Diff Parameters"
 msgstr ""
-"Dieses Projektarchiv enthält ungefähr %i nicht verknüpfte Objekte.\n"
-"\n"
-"Für eine optimale Performance wird empfohlen, die Datenbank des Projektarchivs zu komprimieren.\n"
-"\n"
-"Soll die Datenbank jetzt komprimiert werden?"
 
-#: lib/date.tcl:25
-#, tcl-format
-msgid "Invalid date from Git: %s"
-msgstr "Ungültiges Datum von Git: %s"
+#: lib/option.tcl:158
+msgid "Commit Message Text Width"
+msgstr "Textbreite der Versionsbeschreibung"
 
-#: lib/diff.tcl:64
-#, tcl-format
-msgid ""
-"No differences detected.\n"
-"\n"
-"%s has no changes.\n"
-"\n"
-"The modification date of this file was updated by another application, but "
-"the content within the file was not changed.\n"
-"\n"
-"A rescan will be automatically started to find other files which may have "
-"the same state."
-msgstr ""
-"Keine Änderungen feststellbar.\n"
-"\n"
-"»%s« enthält keine Änderungen. Zwar wurde das Änderungsdatum dieser Datei von "
-"einem anderen Programm modifiziert, aber der Inhalt der Datei ist "
-"unverändert.\n"
-"\n"
-"Das Arbeitsverzeichnis wird jetzt neu geladen, um diese Änderung bei allen "
-"Dateien zu prüfen."
+#: lib/option.tcl:159
+msgid "New Branch Name Template"
+msgstr "Namensvorschlag für neue Zweige"
 
-#: lib/diff.tcl:104
-#, tcl-format
-msgid "Loading diff of %s..."
-msgstr "Vergleich von »%s« laden..."
+#: lib/option.tcl:160
+msgid "Default File Contents Encoding"
+msgstr "Voreingestellte Zeichenkodierung"
 
-#: lib/diff.tcl:125
-msgid ""
-"LOCAL: deleted\n"
-"REMOTE:\n"
+#: lib/option.tcl:161
+msgid "Warn before committing to a detached head"
 msgstr ""
-"LOKAL: gelöscht\n"
-"ANDERES:\n"
 
-#: lib/diff.tcl:130
-msgid ""
-"REMOTE: deleted\n"
-"LOCAL:\n"
+#: lib/option.tcl:162
+msgid "Staging of untracked files"
 msgstr ""
-"ANDERES: gelöscht\n"
-"LOKAL:\n"
 
-#: lib/diff.tcl:137
-msgid "LOCAL:\n"
-msgstr "LOKAL:\n"
+#: lib/option.tcl:163
+msgid "Show untracked files"
+msgstr ""
 
-#: lib/diff.tcl:140
-msgid "REMOTE:\n"
-msgstr "ANDERES:\n"
+#: lib/option.tcl:164
+msgid "Tab spacing"
+msgstr ""
 
-#: lib/diff.tcl:202 lib/diff.tcl:319
+#: lib/option.tcl:182 lib/option.tcl:197 lib/option.tcl:220 lib/option.tcl:282
+#: lib/database.tcl:57
 #, tcl-format
-msgid "Unable to display %s"
-msgstr "Datei »%s« kann nicht angezeigt werden"
-
-#: lib/diff.tcl:203
-msgid "Error loading file:"
-msgstr "Fehler beim Laden der Datei:"
+msgid "%s:"
+msgstr ""
 
-#: lib/diff.tcl:210
-msgid "Git Repository (subproject)"
-msgstr "Git-Projektarchiv (Unterprojekt)"
+#: lib/option.tcl:210
+msgid "Change"
+msgstr "Ändern"
 
-#: lib/diff.tcl:222
-msgid "* Binary file (not showing content)."
-msgstr "* Binärdatei (Inhalt wird nicht angezeigt)"
+#: lib/option.tcl:254
+msgid "Spelling Dictionary:"
+msgstr "Wörterbuch Rechtschreibprüfung:"
 
-#: lib/diff.tcl:227
-#, tcl-format
-msgid ""
-"* Untracked file is %d bytes.\n"
-"* Showing only first %d bytes.\n"
-msgstr ""
-"* Datei nicht unter Versionskontrolle, Dateigröße %d Bytes.\n"
-"* Nur erste %d Bytes werden angezeigt.\n"
+#: lib/option.tcl:284
+msgid "Change Font"
+msgstr "Schriftart ändern"
 
-#: lib/diff.tcl:233
+#: lib/option.tcl:288
 #, tcl-format
-msgid ""
-"\n"
-"* Untracked file clipped here by %s.\n"
-"* To see the entire file, use an external editor.\n"
-msgstr ""
-"\n"
-"* Datei nicht unter Versionskontrolle, hier abgeschnitten durch %s.\n"
-"* Zum Ansehen der vollständigen Datei externen Editor benutzen.\n"
-
-#: lib/diff.tcl:482
-msgid "Failed to unstage selected hunk."
-msgstr ""
-"Fehler beim Herausnehmen des gewählten Kontexts aus der Bereitstellung."
+msgid "Choose %s"
+msgstr "%s wählen"
 
-#: lib/diff.tcl:489
-msgid "Failed to stage selected hunk."
-msgstr "Fehler beim Bereitstellen des gewählten Kontexts."
+#: lib/option.tcl:294
+msgid "pt."
+msgstr "pt."
 
-#: lib/diff.tcl:568
-msgid "Failed to unstage selected line."
-msgstr "Fehler beim Herausnehmen der gewählten Zeile aus der Bereitstellung."
+#: lib/option.tcl:308
+msgid "Preferences"
+msgstr "Einstellungen"
 
-#: lib/diff.tcl:576
-msgid "Failed to stage selected line."
-msgstr "Fehler beim Bereitstellen der gewählten Zeile."
+#: lib/option.tcl:345
+msgid "Failed to completely save options:"
+msgstr "Optionen konnten nicht gespeichert werden:"
 
 #: lib/encoding.tcl:443
 msgid "Default"
@@ -1698,229 +1451,40 @@ msgstr "Systemweit (%s)"
 msgid "Other"
 msgstr "Andere"
 
-#: lib/error.tcl:20 lib/error.tcl:114
-msgid "error"
-msgstr "Fehler"
+#: lib/tools.tcl:76
+#, tcl-format
+msgid "Running %s requires a selected file."
+msgstr "Um »%s« zu starten, muss eine Datei ausgewählt sein."
 
-#: lib/error.tcl:36
-msgid "warning"
-msgstr "Warnung"
+#: lib/tools.tcl:92
+#, fuzzy, tcl-format
+msgid "Are you sure you want to run %1$s on file \"%2$s\"?"
+msgstr "Wollen Sie %s wirklich starten?"
 
-#: lib/error.tcl:94
-msgid "You must correct the above errors before committing."
-msgstr ""
-"Sie müssen die obigen Fehler zuerst beheben, bevor Sie eintragen können."
-
-#: lib/index.tcl:6
-msgid "Unable to unlock the index."
-msgstr "Bereitstellung kann nicht wieder freigegeben werden."
-
-#: lib/index.tcl:15
-msgid "Index Error"
-msgstr "Fehler in Bereitstellung"
-
-#: lib/index.tcl:17
-msgid ""
-"Updating the Git index failed.  A rescan will be automatically started to "
-"resynchronize git-gui."
-msgstr ""
-"Das Aktualisieren der Git-Bereitstellung ist fehlgeschlagen. Eine allgemeine "
-"Git-Aktualisierung wird jetzt gestartet, um git-gui wieder mit git zu "
-"synchronisieren."
-
-#: lib/index.tcl:28
-msgid "Continue"
-msgstr "Fortsetzen"
-
-#: lib/index.tcl:31
-msgid "Unlock Index"
-msgstr "Bereitstellung freigeben"
-
-#: lib/index.tcl:289
-#, tcl-format
-msgid "Unstaging %s from commit"
-msgstr "Datei »%s« aus der Bereitstellung herausnehmen"
-
-#: lib/index.tcl:328
-msgid "Ready to commit."
-msgstr "Bereit zum Eintragen."
-
-#: lib/index.tcl:341
-#, tcl-format
-msgid "Adding %s"
-msgstr "»%s« hinzufügen..."
-
-#: lib/index.tcl:398
-#, tcl-format
-msgid "Revert changes in file %s?"
-msgstr "Änderungen in Datei »%s« verwerfen?"
-
-#: lib/index.tcl:400
-#, tcl-format
-msgid "Revert changes in these %i files?"
-msgstr "Änderungen in den gewählten %i Dateien verwerfen?"
-
-#: lib/index.tcl:408
-msgid "Any unstaged changes will be permanently lost by the revert."
-msgstr ""
-"Alle nicht bereitgestellten Änderungen werden beim Verwerfen verloren gehen."
-
-#: lib/index.tcl:411
-msgid "Do Nothing"
-msgstr "Nichts tun"
-
-#: lib/index.tcl:429
-msgid "Reverting selected files"
-msgstr "Änderungen in gewählten Dateien verwerfen"
-
-#: lib/index.tcl:433
-#, tcl-format
-msgid "Reverting %s"
-msgstr "Änderungen in %s verwerfen"
-
-#: lib/merge.tcl:13
-msgid ""
-"Cannot merge while amending.\n"
-"\n"
-"You must finish amending this commit before starting any type of merge.\n"
-msgstr ""
-"Zusammenführen kann nicht gleichzeitig mit Nachbessern durchgeführt werden.\n"
-"\n"
-"Sie müssen zuerst die Nachbesserungs-Version abschließen, bevor Sie "
-"zusammenführen können.\n"
-
-#: lib/merge.tcl:27
-msgid ""
-"Last scanned state does not match repository state.\n"
-"\n"
-"Another Git program has modified this repository since the last scan.  A "
-"rescan must be performed before a merge can be performed.\n"
-"\n"
-"The rescan will be automatically started now.\n"
-msgstr ""
-"Der letzte geladene Status stimmt nicht mehr mit dem Projektarchiv überein.\n"
-"\n"
-"Ein anderes Git-Programm hat das Projektarchiv seit dem letzten Laden "
-"geändert.  Vor einem Zusammenführen muss neu geladen werden.\n"
-"\n"
-"Es wird gleich neu geladen.\n"
-
-#: lib/merge.tcl:45
+#: lib/tools.tcl:96
 #, tcl-format
-msgid ""
-"You are in the middle of a conflicted merge.\n"
-"\n"
-"File %s has merge conflicts.\n"
-"\n"
-"You must resolve them, stage the file, and commit to complete the current "
-"merge.  Only then can you begin another merge.\n"
-msgstr ""
-"Zusammenführung mit Konflikten.\n"
-"\n"
-"Die Datei »%s« enthält Konflikte beim Zusammenführen. Sie müssen diese "
-"Konflikte per Hand auflösen. Anschließend müssen Sie die Datei wieder "
-"bereitstellen und eintragen, um die Zusammenführung abzuschließen. Erst "
-"danach kann eine neue Zusammenführung begonnen werden.\n"
+msgid "Are you sure you want to run %s?"
+msgstr "Wollen Sie %s wirklich starten?"
 
-#: lib/merge.tcl:55
+#: lib/tools.tcl:118
 #, tcl-format
-msgid ""
-"You are in the middle of a change.\n"
-"\n"
-"File %s is modified.\n"
-"\n"
-"You should complete the current commit before starting a merge.  Doing so "
-"will help you abort a failed merge, should the need arise.\n"
-msgstr ""
-"Es liegen Änderungen vor.\n"
-"\n"
-"Die Datei »%s« wurde geändert.  Sie sollten zuerst die bereitgestellte "
-"Version abschließen, bevor Sie eine Zusammenführung beginnen.  Mit dieser "
-"Reihenfolge können Sie mögliche Konflikte beim Zusammenführen wesentlich "
-"einfacher beheben oder abbrechen.\n"
+msgid "Tool: %s"
+msgstr "Werkzeug: %s"
 
-#: lib/merge.tcl:107
+#: lib/tools.tcl:119
 #, tcl-format
-msgid "%s of %s"
-msgstr "%s von %s"
+msgid "Running: %s"
+msgstr "Starten: %s"
 
-#: lib/merge.tcl:120
+#: lib/tools.tcl:158
 #, tcl-format
-msgid "Merging %s and %s..."
-msgstr "Zusammenführen von %s und %s..."
-
-#: lib/merge.tcl:131
-msgid "Merge completed successfully."
-msgstr "Zusammenführen erfolgreich abgeschlossen."
-
-#: lib/merge.tcl:133
-msgid "Merge failed.  Conflict resolution is required."
-msgstr "Zusammenführen fehlgeschlagen. Konfliktauflösung ist notwendig."
+msgid "Tool completed successfully: %s"
+msgstr "Werkzeug erfolgreich abgeschlossen: %s"
 
-#: lib/merge.tcl:158
+#: lib/tools.tcl:160
 #, tcl-format
-msgid "Merge Into %s"
-msgstr "Zusammenführen in »%s«"
-
-#: lib/merge.tcl:177
-msgid "Revision To Merge"
-msgstr "Zusammenzuführende Version"
-
-#: lib/merge.tcl:212
-msgid ""
-"Cannot abort while amending.\n"
-"\n"
-"You must finish amending this commit.\n"
-msgstr ""
-"Abbruch der Nachbesserung ist nicht möglich.\n"
-"\n"
-"Sie müssen die Nachbesserung der Version abschließen.\n"
-
-#: lib/merge.tcl:222
-msgid ""
-"Abort merge?\n"
-"\n"
-"Aborting the current merge will cause *ALL* uncommitted changes to be lost.\n"
-"\n"
-"Continue with aborting the current merge?"
-msgstr ""
-"Zusammenführen abbrechen?\n"
-"\n"
-"Wenn Sie abbrechen, gehen alle noch nicht eingetragenen Änderungen "
-"verloren.\n"
-"\n"
-"Zusammenführen jetzt abbrechen?"
-
-#: lib/merge.tcl:228
-msgid ""
-"Reset changes?\n"
-"\n"
-"Resetting the changes will cause *ALL* uncommitted changes to be lost.\n"
-"\n"
-"Continue with resetting the current changes?"
-msgstr ""
-"Änderungen zurücksetzen?\n"
-"\n"
-"Wenn Sie zurücksetzen, gehen alle noch nicht eingetragenen Änderungen "
-"verloren.\n"
-"\n"
-"Änderungen jetzt zurücksetzen?"
-
-#: lib/merge.tcl:239
-msgid "Aborting"
-msgstr "Abbruch"
-
-#: lib/merge.tcl:239
-msgid "files reset"
-msgstr "Dateien zurückgesetzt"
-
-#: lib/merge.tcl:267
-msgid "Abort failed."
-msgstr "Abbruch fehlgeschlagen."
-
-#: lib/merge.tcl:269
-msgid "Abort completed.  Ready."
-msgstr "Abbruch durchgeführt. Bereit."
+msgid "Tool failed: %s"
+msgstr "Werkzeug fehlgeschlagen: %s"
 
 #: lib/mergetool.tcl:8
 msgid "Force resolution to the base version?"
@@ -1970,21 +1534,21 @@ msgstr ""
 msgid "Conflict file does not exist"
 msgstr "Konflikt-Datei existiert nicht"
 
-#: lib/mergetool.tcl:264
+#: lib/mergetool.tcl:246
 #, tcl-format
 msgid "Not a GUI merge tool: '%s'"
 msgstr "Kein GUI Zusammenführungswerkzeug: »%s«"
 
-#: lib/mergetool.tcl:268
+#: lib/mergetool.tcl:275
 #, tcl-format
 msgid "Unsupported merge tool '%s'"
 msgstr "Unbekanntes Zusammenführungswerkzeug: »%s«"
 
-#: lib/mergetool.tcl:303
+#: lib/mergetool.tcl:310
 msgid "Merge tool is already running, terminate it?"
 msgstr "Zusammenführungswerkzeug läuft bereits. Soll es abgebrochen werden?"
 
-#: lib/mergetool.tcl:323
+#: lib/mergetool.tcl:330
 #, tcl-format
 msgid ""
 "Error retrieving versions:\n"
@@ -1993,7 +1557,7 @@ msgstr ""
 "Fehler beim Abrufen der Dateiversionen:\n"
 "%s"
 
-#: lib/mergetool.tcl:343
+#: lib/mergetool.tcl:350
 #, tcl-format
 msgid ""
 "Could not start the merge tool:\n"
@@ -2004,243 +1568,180 @@ msgstr ""
 "\n"
 "%s"
 
-#: lib/mergetool.tcl:347
+#: lib/mergetool.tcl:354
 msgid "Running merge tool..."
 msgstr "Zusammenführungswerkzeug starten..."
 
-#: lib/mergetool.tcl:375 lib/mergetool.tcl:383
+#: lib/mergetool.tcl:382 lib/mergetool.tcl:390
 msgid "Merge tool failed."
 msgstr "Zusammenführungswerkzeug fehlgeschlagen."
 
-#: lib/option.tcl:11
-#, tcl-format
-msgid "Invalid global encoding '%s'"
-msgstr "Ungültige globale Zeichenkodierung »%s«"
-
-#: lib/option.tcl:19
-#, tcl-format
-msgid "Invalid repo encoding '%s'"
-msgstr "Ungültige Archiv-Zeichenkodierung »%s«"
+#: lib/tools_dlg.tcl:22
+#, fuzzy, tcl-format
+msgid "%s (%s): Add Tool"
+msgstr "Werkzeug hinzufügen"
 
-#: lib/option.tcl:117
-msgid "Restore Defaults"
-msgstr "Voreinstellungen wiederherstellen"
+#: lib/tools_dlg.tcl:28
+msgid "Add New Tool Command"
+msgstr "Neues Kommando für Werkzeug hinzufügen"
 
-#: lib/option.tcl:121
-msgid "Save"
-msgstr "Speichern"
+#: lib/tools_dlg.tcl:34
+msgid "Add globally"
+msgstr "Global hinzufügen"
 
-#: lib/option.tcl:131
-#, tcl-format
-msgid "%s Repository"
-msgstr "Projektarchiv %s"
+#: lib/tools_dlg.tcl:46
+msgid "Tool Details"
+msgstr "Einzelheiten des Werkzeugs"
 
-#: lib/option.tcl:132
-msgid "Global (All Repositories)"
-msgstr "Global (Alle Projektarchive)"
+#: lib/tools_dlg.tcl:49
+msgid "Use '/' separators to create a submenu tree:"
+msgstr "Benutzen Sie einen Schrägstrich »/«, um Untermenüs zu erstellen:"
 
-#: lib/option.tcl:138
-msgid "User Name"
-msgstr "Benutzername"
+#: lib/tools_dlg.tcl:60
+msgid "Command:"
+msgstr "Kommando:"
 
-#: lib/option.tcl:139
-msgid "Email Address"
-msgstr "E-Mail-Adresse"
+#: lib/tools_dlg.tcl:71
+msgid "Show a dialog before running"
+msgstr "Bestätigungsfrage vor Starten anzeigen"
 
-#: lib/option.tcl:141
-msgid "Summarize Merge Commits"
-msgstr "Zusammenführungs-Versionen zusammenfassen"
+#: lib/tools_dlg.tcl:77
+msgid "Ask the user to select a revision (sets $REVISION)"
+msgstr "Benutzer nach Version fragen (setzt $REVISION)"
 
-#: lib/option.tcl:142
-msgid "Merge Verbosity"
-msgstr "Ausführlichkeit der Zusammenführen-Meldungen"
+#: lib/tools_dlg.tcl:82
+msgid "Ask the user for additional arguments (sets $ARGS)"
+msgstr "Benutzer nach zusätzlichen Argumenten fragen (setzt $ARGS)"
 
-#: lib/option.tcl:143
-msgid "Show Diffstat After Merge"
-msgstr "Vergleichsstatistik nach Zusammenführen anzeigen"
+#: lib/tools_dlg.tcl:89
+msgid "Don't show the command output window"
+msgstr "Kein Ausgabefenster zeigen"
 
-#: lib/option.tcl:144
-msgid "Use Merge Tool"
-msgstr "Zusammenführungswerkzeug"
+#: lib/tools_dlg.tcl:94
+msgid "Run only if a diff is selected ($FILENAME not empty)"
+msgstr "Nur starten, wenn ein Vergleich gewählt ist ($FILENAME ist nicht leer)"
 
-#: lib/option.tcl:146
-msgid "Trust File Modification Timestamps"
-msgstr "Auf Dateiänderungsdatum verlassen"
+#: lib/tools_dlg.tcl:118
+msgid "Please supply a name for the tool."
+msgstr "Bitte geben Sie einen Werkzeugnamen an."
 
-#: lib/option.tcl:147
-msgid "Prune Tracking Branches During Fetch"
-msgstr "Übernahmezweige aufräumen während Anforderung"
+#: lib/tools_dlg.tcl:126
+#, tcl-format
+msgid "Tool '%s' already exists."
+msgstr "Werkzeug »%s« existiert bereits."
 
-#: lib/option.tcl:148
-msgid "Match Tracking Branches"
-msgstr "Passend zu Übernahmezweig"
-
-#: lib/option.tcl:149
-msgid "Blame Copy Only On Changed Files"
-msgstr "Kopie-Annotieren nur bei geänderten Dateien"
-
-#: lib/option.tcl:150
-msgid "Minimum Letters To Blame Copy On"
-msgstr "Mindestzahl Zeichen für Kopie-Annotieren"
-
-#: lib/option.tcl:151
-msgid "Blame History Context Radius (days)"
-msgstr "Anzahl Tage für Historien-Kontext"
-
-#: lib/option.tcl:152
-msgid "Number of Diff Context Lines"
-msgstr "Anzahl der Kontextzeilen beim Vergleich"
-
-#: lib/option.tcl:153
-msgid "Commit Message Text Width"
-msgstr "Textbreite der Versionsbeschreibung"
-
-#: lib/option.tcl:154
-msgid "New Branch Name Template"
-msgstr "Namensvorschlag für neue Zweige"
-
-#: lib/option.tcl:155
-msgid "Default File Contents Encoding"
-msgstr "Voreingestellte Zeichenkodierung"
-
-#: lib/option.tcl:203
-msgid "Change"
-msgstr "Ändern"
-
-#: lib/option.tcl:230
-msgid "Spelling Dictionary:"
-msgstr "Wörterbuch Rechtschreibprüfung:"
-
-#: lib/option.tcl:254
-msgid "Change Font"
-msgstr "Schriftart ändern"
-
-#: lib/option.tcl:258
+#: lib/tools_dlg.tcl:148
 #, tcl-format
-msgid "Choose %s"
-msgstr "%s wählen"
-
-#: lib/option.tcl:264
-msgid "pt."
-msgstr "pt."
-
-#: lib/option.tcl:278
-msgid "Preferences"
-msgstr "Einstellungen"
-
-#: lib/option.tcl:314
-msgid "Failed to completely save options:"
-msgstr "Optionen konnten nicht gespeichert werden:"
+msgid ""
+"Could not add tool:\n"
+"%s"
+msgstr ""
+"Werkzeug konnte nicht hinzugefügt werden:\n"
+"\n"
+"%s"
 
-#: lib/remote_add.tcl:19
-msgid "Add Remote"
-msgstr "Externes Archiv hinzufügen"
+#: lib/tools_dlg.tcl:187
+#, fuzzy, tcl-format
+msgid "%s (%s): Remove Tool"
+msgstr "Werkzeug entfernen"
 
-#: lib/remote_add.tcl:24
-msgid "Add New Remote"
-msgstr "Neues externes Archiv hinzufügen"
+#: lib/tools_dlg.tcl:193
+msgid "Remove Tool Commands"
+msgstr "Werkzeugkommandos entfernen"
 
-#: lib/remote_add.tcl:28 lib/tools_dlg.tcl:36
-msgid "Add"
-msgstr "Hinzufügen"
+#: lib/tools_dlg.tcl:198
+msgid "Remove"
+msgstr "Entfernen"
 
-#: lib/remote_add.tcl:37
-msgid "Remote Details"
-msgstr "Einzelheiten des externen Archivs"
+#: lib/tools_dlg.tcl:231
+msgid "(Blue denotes repository-local tools)"
+msgstr "(Werkzeuge für lokales Archiv werden in Blau angezeigt)"
 
-#: lib/remote_add.tcl:50
-msgid "Location:"
-msgstr "Adresse:"
+#: lib/tools_dlg.tcl:283
+#, fuzzy, tcl-format
+msgid "%s (%s):"
+msgstr "Systemweit (%s)"
 
-#: lib/remote_add.tcl:62
-msgid "Further Action"
-msgstr "Weitere Aktion jetzt"
+#: lib/tools_dlg.tcl:292
+#, tcl-format
+msgid "Run Command: %s"
+msgstr "Kommando aufrufen: %s"
 
-#: lib/remote_add.tcl:65
-msgid "Fetch Immediately"
-msgstr "Gleich anfordern"
+#: lib/tools_dlg.tcl:306
+msgid "Arguments"
+msgstr "Argumente"
 
-#: lib/remote_add.tcl:71
-msgid "Initialize Remote Repository and Push"
-msgstr "Externes Archiv initialisieren und dahin versenden"
+#: lib/tools_dlg.tcl:341
+msgid "OK"
+msgstr "Ok"
 
-#: lib/remote_add.tcl:77
-msgid "Do Nothing Else Now"
-msgstr "Nichts tun"
+#: lib/search.tcl:48
+msgid "Find:"
+msgstr "Suchen:"
 
-#: lib/remote_add.tcl:101
-msgid "Please supply a remote name."
-msgstr "Bitte geben Sie einen Namen des externen Archivs an."
+#: lib/search.tcl:50
+msgid "Next"
+msgstr "Nächster"
 
-#: lib/remote_add.tcl:114
-#, tcl-format
-msgid "'%s' is not an acceptable remote name."
-msgstr "»%s« ist kein zulässiger Name eines externen Archivs."
+#: lib/search.tcl:51
+msgid "Prev"
+msgstr "Voriger"
 
-#: lib/remote_add.tcl:125
-#, tcl-format
-msgid "Failed to add remote '%s' of location '%s'."
-msgstr "Fehler beim Hinzufügen des externen Archivs »%s« aus Herkunftsort »%s«."
+#: lib/search.tcl:52
+msgid "RegExp"
+msgstr ""
 
-#: lib/remote_add.tcl:133 lib/transport.tcl:6
-#, tcl-format
-msgid "fetch %s"
-msgstr "»%s« anfordern"
+#: lib/search.tcl:54
+msgid "Case"
+msgstr ""
 
-#: lib/remote_add.tcl:134
-#, tcl-format
-msgid "Fetching the %s"
-msgstr "»%s« anfordern"
+#: lib/shortcut.tcl:8 lib/shortcut.tcl:43 lib/shortcut.tcl:75
+#, fuzzy, tcl-format
+msgid "%s (%s): Create Desktop Icon"
+msgstr "Desktop-Icon erstellen"
 
-#: lib/remote_add.tcl:157
-#, tcl-format
-msgid "Do not know how to initialize repository at location '%s'."
-msgstr "Initialisieren eines externen Archivs an Adresse »%s« ist nicht möglich."
+#: lib/shortcut.tcl:24 lib/shortcut.tcl:65
+msgid "Cannot write shortcut:"
+msgstr "Fehler beim Schreiben der Verknüpfung:"
 
-#: lib/remote_add.tcl:163 lib/transport.tcl:25 lib/transport.tcl:63
-#: lib/transport.tcl:81
-#, tcl-format
-msgid "push %s"
-msgstr "»%s« versenden..."
+#: lib/shortcut.tcl:140
+msgid "Cannot write icon:"
+msgstr "Fehler beim Erstellen des Icons:"
 
-#: lib/remote_add.tcl:164
-#, tcl-format
-msgid "Setting up the %s (at %s)"
-msgstr "Einrichten von »%s« an »%s«"
+#: lib/remote_branch_delete.tcl:29
+#, fuzzy, tcl-format
+msgid "%s (%s): Delete Branch Remotely"
+msgstr "Zweig in externem Archiv löschen"
 
-#: lib/remote_branch_delete.tcl:29 lib/remote_branch_delete.tcl:34
+#: lib/remote_branch_delete.tcl:34
 msgid "Delete Branch Remotely"
 msgstr "Zweig in externem Archiv löschen"
 
-#: lib/remote_branch_delete.tcl:47
+#: lib/remote_branch_delete.tcl:48
 msgid "From Repository"
 msgstr "In Projektarchiv"
 
-#: lib/remote_branch_delete.tcl:50 lib/transport.tcl:134
-msgid "Remote:"
-msgstr "Externes Archiv:"
-
-#: lib/remote_branch_delete.tcl:66 lib/transport.tcl:149
-msgid "Arbitrary Location:"
-msgstr "Adresse:"
-
-#: lib/remote_branch_delete.tcl:84
+#: lib/remote_branch_delete.tcl:88
 msgid "Branches"
 msgstr "Zweige"
 
-#: lib/remote_branch_delete.tcl:109
+#: lib/remote_branch_delete.tcl:110
 msgid "Delete Only If"
 msgstr "Nur löschen, wenn"
 
-#: lib/remote_branch_delete.tcl:111
+#: lib/remote_branch_delete.tcl:112
 msgid "Merged Into:"
 msgstr "Zusammengeführt mit:"
 
-#: lib/remote_branch_delete.tcl:152
+#: lib/remote_branch_delete.tcl:120 lib/branch_delete.tcl:53
+msgid "Always (Do not perform merge checks)"
+msgstr "Immer (Keine Zusammenführungsprüfung)"
+
+#: lib/remote_branch_delete.tcl:153
 msgid "A branch is required for 'Merged Into'."
 msgstr "Für »Zusammenführen mit« muss ein Zweig angegeben werden."
 
-#: lib/remote_branch_delete.tcl:184
+#: lib/remote_branch_delete.tcl:185
 #, tcl-format
 msgid ""
 "The following branches are not completely merged into %s:\n"
@@ -2251,7 +1752,7 @@ msgstr ""
 "\n"
 " - %s"
 
-#: lib/remote_branch_delete.tcl:189
+#: lib/remote_branch_delete.tcl:190
 #, tcl-format
 msgid ""
 "One or more of the merge tests failed because you have not fetched the "
@@ -2261,332 +1762,1137 @@ msgstr ""
 "notwendigen Versionen vorher angefordert haben.  Sie sollten versuchen, "
 "zuerst von »%s« anzufordern."
 
-#: lib/remote_branch_delete.tcl:207
+#: lib/remote_branch_delete.tcl:208
 msgid "Please select one or more branches to delete."
 msgstr "Bitte wählen Sie mindestens einen Zweig, der gelöscht werden soll."
 
-#: lib/remote_branch_delete.tcl:226
+#: lib/remote_branch_delete.tcl:218 lib/branch_delete.tcl:115
+msgid ""
+"Recovering deleted branches is difficult.\n"
+"\n"
+"Delete the selected branches?"
+msgstr ""
+"Das Wiederherstellen von gelöschten Zweigen ist nur mit größerem Aufwand "
+"möglich.\n"
+"\n"
+"Sollen die ausgewählten Zweige gelöscht werden?"
+
+#: lib/remote_branch_delete.tcl:227
 #, tcl-format
 msgid "Deleting branches from %s"
 msgstr "Zweige auf »%s« werden gelöscht"
 
-#: lib/remote_branch_delete.tcl:292
+#: lib/remote_branch_delete.tcl:300
 msgid "No repository selected."
 msgstr "Kein Projektarchiv ausgewählt."
 
-#: lib/remote_branch_delete.tcl:297
+#: lib/remote_branch_delete.tcl:305
 #, tcl-format
 msgid "Scanning %s..."
 msgstr "»%s« laden..."
 
-#: lib/remote.tcl:163
-msgid "Remove Remote"
-msgstr "Externes Archiv entfernen"
+#: lib/choose_repository.tcl:45
+msgid "Git Gui"
+msgstr "Git Gui"
 
-#: lib/remote.tcl:168
-msgid "Prune from"
-msgstr "Aufräumen von"
+#: lib/choose_repository.tcl:104 lib/choose_repository.tcl:427
+msgid "Create New Repository"
+msgstr "Neues Projektarchiv"
 
-#: lib/remote.tcl:173
-msgid "Fetch from"
-msgstr "Anfordern von"
+#: lib/choose_repository.tcl:110
+msgid "New..."
+msgstr "Neu..."
 
-#: lib/remote.tcl:215
-msgid "Push to"
-msgstr "Versenden nach"
+#: lib/choose_repository.tcl:117 lib/choose_repository.tcl:511
+msgid "Clone Existing Repository"
+msgstr "Projektarchiv klonen"
 
-#: lib/search.tcl:21
-msgid "Find:"
-msgstr "Suchen:"
+#: lib/choose_repository.tcl:128
+msgid "Clone..."
+msgstr "Klonen..."
 
-#: lib/search.tcl:23
-msgid "Next"
-msgstr "Nächster"
+#: lib/choose_repository.tcl:135 lib/choose_repository.tcl:1105
+msgid "Open Existing Repository"
+msgstr "Projektarchiv öffnen"
 
-#: lib/search.tcl:24
-msgid "Prev"
-msgstr "Voriger"
+#: lib/choose_repository.tcl:141
+msgid "Open..."
+msgstr "Öffnen..."
 
-#: lib/search.tcl:25
-msgid "Case-Sensitive"
-msgstr "Groß-/Kleinschreibung unterscheiden"
+#: lib/choose_repository.tcl:154
+msgid "Recent Repositories"
+msgstr "Zuletzt benutzte Projektarchive"
 
-#: lib/shortcut.tcl:21 lib/shortcut.tcl:62
-msgid "Cannot write shortcut:"
-msgstr "Fehler beim Schreiben der Verknüpfung:"
+#: lib/choose_repository.tcl:164
+msgid "Open Recent Repository:"
+msgstr "Zuletzt benutztes Projektarchiv öffnen:"
 
-#: lib/shortcut.tcl:137
-msgid "Cannot write icon:"
-msgstr "Fehler beim Erstellen des Icons:"
+#: lib/choose_repository.tcl:331 lib/choose_repository.tcl:338
+#: lib/choose_repository.tcl:345
+#, tcl-format
+msgid "Failed to create repository %s:"
+msgstr "Projektarchiv »%s« konnte nicht erstellt werden:"
 
-#: lib/spellcheck.tcl:57
-msgid "Unsupported spell checker"
-msgstr "Rechtschreibprüfungsprogramm nicht unterstützt"
+#: lib/choose_repository.tcl:422 lib/branch_create.tcl:33
+msgid "Create"
+msgstr "Erstellen"
 
-#: lib/spellcheck.tcl:65
-msgid "Spell checking is unavailable"
-msgstr "Rechtschreibprüfung nicht verfügbar"
+#: lib/choose_repository.tcl:432
+msgid "Directory:"
+msgstr "Verzeichnis:"
 
-#: lib/spellcheck.tcl:68
-msgid "Invalid spell checking configuration"
-msgstr "Unbenutzbare Konfiguration der Rechtschreibprüfung"
+#: lib/choose_repository.tcl:462 lib/choose_repository.tcl:588
+#: lib/choose_repository.tcl:1139
+msgid "Git Repository"
+msgstr "Git Projektarchiv"
 
-#: lib/spellcheck.tcl:70
+#: lib/choose_repository.tcl:487
 #, tcl-format
-msgid "Reverting dictionary to %s."
-msgstr "Wörterbuch auf %s zurückgesetzt."
+msgid "Directory %s already exists."
+msgstr "Verzeichnis »%s« existiert bereits."
 
-#: lib/spellcheck.tcl:73
-msgid "Spell checker silently failed on startup"
-msgstr "Rechtschreibprüfungsprogramm mit Fehler abgebrochen"
+#: lib/choose_repository.tcl:491
+#, tcl-format
+msgid "File %s already exists."
+msgstr "Datei »%s« existiert bereits."
 
-#: lib/spellcheck.tcl:80
-msgid "Unrecognized spell checker"
-msgstr "Unbekanntes Rechtschreibprüfungsprogramm"
+#: lib/choose_repository.tcl:506
+msgid "Clone"
+msgstr "Klonen"
 
-#: lib/spellcheck.tcl:186
-msgid "No Suggestions"
-msgstr "Keine Vorschläge"
+#: lib/choose_repository.tcl:519
+msgid "Source Location:"
+msgstr "Herkunft:"
 
-#: lib/spellcheck.tcl:388
-msgid "Unexpected EOF from spell checker"
-msgstr "Unerwartetes EOF vom Rechtschreibprüfungsprogramm"
+#: lib/choose_repository.tcl:528
+msgid "Target Directory:"
+msgstr "Zielverzeichnis:"
 
-#: lib/spellcheck.tcl:392
-msgid "Spell Checker Failed"
-msgstr "Rechtschreibprüfung fehlgeschlagen"
+#: lib/choose_repository.tcl:538
+msgid "Clone Type:"
+msgstr "Art des Klonens:"
 
-#: lib/sshkey.tcl:31
-msgid "No keys found."
-msgstr "Keine Schlüssel gefunden."
+#: lib/choose_repository.tcl:543
+msgid "Standard (Fast, Semi-Redundant, Hardlinks)"
+msgstr "Standard (schnell, teilweise redundant, Hardlinks)"
 
-#: lib/sshkey.tcl:34
-#, tcl-format
-msgid "Found a public key in: %s"
-msgstr "Öffentlicher Schlüssel gefunden in: %s"
+#: lib/choose_repository.tcl:548
+msgid "Full Copy (Slower, Redundant Backup)"
+msgstr "Alles kopieren (langsamer, volle Redundanz)"
 
-#: lib/sshkey.tcl:40
-msgid "Generate Key"
-msgstr "Schlüssel erzeugen"
+#: lib/choose_repository.tcl:553
+msgid "Shared (Fastest, Not Recommended, No Backup)"
+msgstr "Verknüpft (schnell, nicht empfohlen, kein Backup)"
 
-#: lib/sshkey.tcl:56
-msgid "Copy To Clipboard"
-msgstr "In Zwischenablage kopieren"
+#: lib/choose_repository.tcl:560
+msgid "Recursively clone submodules too"
+msgstr ""
 
-#: lib/sshkey.tcl:70
-msgid "Your OpenSSH Public Key"
-msgstr "Ihr OpenSSH öffenlicher Schlüssel"
+#: lib/choose_repository.tcl:594 lib/choose_repository.tcl:641
+#: lib/choose_repository.tcl:790 lib/choose_repository.tcl:864
+#: lib/choose_repository.tcl:1145 lib/choose_repository.tcl:1153
+#, tcl-format
+msgid "Not a Git repository: %s"
+msgstr "Kein Git-Projektarchiv in »%s« gefunden."
 
-#: lib/sshkey.tcl:78
-msgid "Generating..."
-msgstr "Erzeugen..."
+#: lib/choose_repository.tcl:630
+msgid "Standard only available for local repository."
+msgstr "Standard ist nur für lokale Projektarchive verfügbar."
 
-#: lib/sshkey.tcl:84
-#, tcl-format
-msgid ""
-"Could not start ssh-keygen:\n"
-"\n"
-"%s"
-msgstr ""
-"Konnte »ssh-keygen« nicht starten:\n"
-"\n"
-"%s"
+#: lib/choose_repository.tcl:634
+msgid "Shared only available for local repository."
+msgstr "Verknüpft ist nur für lokale Projektarchive verfügbar."
 
-#: lib/sshkey.tcl:111
-msgid "Generation failed."
-msgstr "Schlüsselerzeugung fehlgeschlagen."
+#: lib/choose_repository.tcl:655
+#, tcl-format
+msgid "Location %s already exists."
+msgstr "Projektarchiv »%s« existiert bereits."
 
-#: lib/sshkey.tcl:118
-msgid "Generation succeeded, but no keys found."
-msgstr "Schlüsselerzeugung erfolgreich, aber keine Schlüssel gefunden."
+#: lib/choose_repository.tcl:666
+msgid "Failed to configure origin"
+msgstr "Der Ursprungsort konnte nicht eingerichtet werden"
+
+#: lib/choose_repository.tcl:678
+msgid "Counting objects"
+msgstr "Objekte werden gezählt"
+
+#: lib/choose_repository.tcl:679
+msgid "buckets"
+msgstr "Buckets"
 
-#: lib/sshkey.tcl:121
+#: lib/choose_repository.tcl:703
 #, tcl-format
-msgid "Your key is in: %s"
-msgstr "Ihr Schlüssel ist abgelegt in: %s"
+msgid "Unable to copy objects/info/alternates: %s"
+msgstr "Kopien von Objekten/Info/Alternates konnten nicht erstellt werden: %s"
 
-#: lib/status_bar.tcl:83
+#: lib/choose_repository.tcl:740
 #, tcl-format
-msgid "%s ... %*i of %*i %s (%3i%%)"
-msgstr "%s ... %*i von %*i %s (%3i%%)"
+msgid "Nothing to clone from %s."
+msgstr "Von »%s« konnte nichts geklont werden."
 
-#: lib/tools_dlg.tcl:22
-msgid "Add Tool"
-msgstr "Werkzeug hinzufügen"
+#: lib/choose_repository.tcl:742 lib/choose_repository.tcl:962
+#: lib/choose_repository.tcl:974
+msgid "The 'master' branch has not been initialized."
+msgstr "Der »master«-Zweig wurde noch nicht initialisiert."
 
-#: lib/tools_dlg.tcl:28
-msgid "Add New Tool Command"
-msgstr "Neues Kommando für Werkzeug hinzufügen"
+#: lib/choose_repository.tcl:755
+msgid "Hardlinks are unavailable.  Falling back to copying."
+msgstr "Hardlinks nicht verfügbar. Stattdessen wird kopiert."
 
-#: lib/tools_dlg.tcl:33
-msgid "Add globally"
-msgstr "Global hinzufügen"
+#: lib/choose_repository.tcl:769
+#, tcl-format
+msgid "Cloning from %s"
+msgstr "Kopieren von »%s«"
 
-#: lib/tools_dlg.tcl:45
-msgid "Tool Details"
-msgstr "Einzelheiten des Werkzeugs"
+#: lib/choose_repository.tcl:800
+msgid "Copying objects"
+msgstr "Objektdatenbank kopieren"
 
-#: lib/tools_dlg.tcl:48
-msgid "Use '/' separators to create a submenu tree:"
-msgstr "Benutzen Sie einen Schrägstrich »/«, um Untermenüs zu erstellen:"
+#: lib/choose_repository.tcl:801
+msgid "KiB"
+msgstr "KB"
 
-#: lib/tools_dlg.tcl:61
-msgid "Command:"
-msgstr "Kommando:"
+#: lib/choose_repository.tcl:825
+#, tcl-format
+msgid "Unable to copy object: %s"
+msgstr "Objekt kann nicht kopiert werden: %s"
 
-#: lib/tools_dlg.tcl:74
-msgid "Show a dialog before running"
-msgstr "Bestätigungsfrage vor Starten anzeigen"
+#: lib/choose_repository.tcl:837
+msgid "Linking objects"
+msgstr "Objekte verlinken"
 
-#: lib/tools_dlg.tcl:80
-msgid "Ask the user to select a revision (sets $REVISION)"
-msgstr "Benutzer nach Version fragen (setzt $REVISION)"
+#: lib/choose_repository.tcl:838
+msgid "objects"
+msgstr "Objekte"
 
-#: lib/tools_dlg.tcl:85
-msgid "Ask the user for additional arguments (sets $ARGS)"
-msgstr "Benutzer nach zusätzlichen Argumenten fragen (setzt $ARGS)"
+#: lib/choose_repository.tcl:846
+#, tcl-format
+msgid "Unable to hardlink object: %s"
+msgstr "Für Objekt konnte kein Hardlink erstellt werden: %s"
 
-#: lib/tools_dlg.tcl:92
-msgid "Don't show the command output window"
-msgstr "Kein Ausgabefenster zeigen"
+#: lib/choose_repository.tcl:903
+msgid "Cannot fetch branches and objects.  See console output for details."
+msgstr ""
+"Zweige und Objekte konnten nicht angefordert werden.  Kontrollieren Sie die "
+"Ausgaben auf der Konsole für weitere Angaben."
 
-#: lib/tools_dlg.tcl:97
-msgid "Run only if a diff is selected ($FILENAME not empty)"
-msgstr "Nur starten, wenn ein Vergleich gewählt ist ($FILENAME ist nicht leer)"
+#: lib/choose_repository.tcl:914
+msgid "Cannot fetch tags.  See console output for details."
+msgstr ""
+"Markierungen konnten nicht angefordert werden.  Kontrollieren Sie die "
+"Ausgaben auf der Konsole für weitere Angaben."
 
-#: lib/tools_dlg.tcl:121
-msgid "Please supply a name for the tool."
-msgstr "Bitte geben Sie einen Werkzeugnamen an."
+#: lib/choose_repository.tcl:938
+msgid "Cannot determine HEAD.  See console output for details."
+msgstr ""
+"Die Zweigspitze (HEAD) konnte nicht gefunden werden.  Kontrollieren Sie die "
+"Ausgaben auf der Konsole für weitere Angaben."
 
-#: lib/tools_dlg.tcl:129
+#: lib/choose_repository.tcl:947
 #, tcl-format
-msgid "Tool '%s' already exists."
-msgstr "Werkzeug »%s« existiert bereits."
+msgid "Unable to cleanup %s"
+msgstr "Verzeichnis »%s« kann nicht aufgeräumt werden."
 
-#: lib/tools_dlg.tcl:151
+#: lib/choose_repository.tcl:953
+msgid "Clone failed."
+msgstr "Klonen fehlgeschlagen."
+
+#: lib/choose_repository.tcl:960
+msgid "No default branch obtained."
+msgstr "Kein voreingestellter Zweig gefunden."
+
+#: lib/choose_repository.tcl:971
 #, tcl-format
-msgid ""
-"Could not add tool:\n"
-"%s"
-msgstr ""
-"Werkzeug konnte nicht hinzugefügt werden:\n"
-"\n"
-"%s"
+msgid "Cannot resolve %s as a commit."
+msgstr "»%s« wurde nicht als Version gefunden."
 
-#: lib/tools_dlg.tcl:190
-msgid "Remove Tool"
-msgstr "Werkzeug entfernen"
+#: lib/choose_repository.tcl:998
+msgid "Creating working directory"
+msgstr "Arbeitskopie erstellen"
 
-#: lib/tools_dlg.tcl:196
-msgid "Remove Tool Commands"
-msgstr "Werkzeugkommandos entfernen"
+#: lib/choose_repository.tcl:1028
+msgid "Initial file checkout failed."
+msgstr "Erstellen der Arbeitskopie fehlgeschlagen."
 
-#: lib/tools_dlg.tcl:200
-msgid "Remove"
-msgstr "Entfernen"
+#: lib/choose_repository.tcl:1072
+#, fuzzy
+msgid "Cloning submodules"
+msgstr "Kopieren von »%s«"
 
-#: lib/tools_dlg.tcl:236
-msgid "(Blue denotes repository-local tools)"
-msgstr "(Werkzeuge für lokales Archiv werden in Blau angezeigt)"
+#: lib/choose_repository.tcl:1087
+msgid "Cannot clone submodules."
+msgstr ""
+
+#: lib/choose_repository.tcl:1110
+msgid "Repository:"
+msgstr "Projektarchiv:"
 
-#: lib/tools_dlg.tcl:297
+#: lib/choose_repository.tcl:1159
 #, tcl-format
-msgid "Run Command: %s"
-msgstr "Kommando aufrufen: %s"
+msgid "Failed to open repository %s:"
+msgstr "Projektarchiv »%s« konnte nicht geöffnet werden."
 
-#: lib/tools_dlg.tcl:311
-msgid "Arguments"
-msgstr "Argumente"
+#: lib/about.tcl:26
+msgid "git-gui - a graphical user interface for Git."
+msgstr "git-gui - eine grafische Oberfläche für Git."
 
-#: lib/tools_dlg.tcl:348
-msgid "OK"
-msgstr "Ok"
+#: lib/blame.tcl:74
+#, fuzzy, tcl-format
+msgid "%s (%s): File Viewer"
+msgstr "Datei-Browser"
 
-#: lib/tools.tcl:75
-#, tcl-format
-msgid "Running %s requires a selected file."
-msgstr "Um »%s« zu starten, muss eine Datei ausgewählt sein."
+#: lib/blame.tcl:80
+msgid "Commit:"
+msgstr "Version:"
 
-#: lib/tools.tcl:90
-#, tcl-format
-msgid "Are you sure you want to run %s?"
-msgstr "Wollen Sie %s wirklich starten?"
+#: lib/blame.tcl:282
+msgid "Copy Commit"
+msgstr "Version kopieren"
 
-#: lib/tools.tcl:110
-#, tcl-format
-msgid "Tool: %s"
-msgstr "Werkzeug: %s"
+#: lib/blame.tcl:286
+msgid "Find Text..."
+msgstr "Text suchen..."
 
-#: lib/tools.tcl:111
-#, tcl-format
-msgid "Running: %s"
-msgstr "Starten: %s"
+#: lib/blame.tcl:290
+#, fuzzy
+msgid "Goto Line..."
+msgstr "Klonen..."
 
-#: lib/tools.tcl:149
-#, tcl-format
-msgid "Tool completed successfully: %s"
-msgstr "Werkzeug erfolgreich abgeschlossen: %s"
+#: lib/blame.tcl:299
+msgid "Do Full Copy Detection"
+msgstr "Volle Kopie-Erkennung"
 
-#: lib/tools.tcl:151
-#, tcl-format
-msgid "Tool failed: %s"
-msgstr "Werkzeug fehlgeschlagen: %s"
+#: lib/blame.tcl:303
+msgid "Show History Context"
+msgstr "Historien-Kontext anzeigen"
 
-#: lib/transport.tcl:7
-#, tcl-format
-msgid "Fetching new changes from %s"
-msgstr "Neue Änderungen von »%s« holen"
+#: lib/blame.tcl:306
+msgid "Blame Parent Commit"
+msgstr "Elternversion annotieren"
 
-#: lib/transport.tcl:18
+#: lib/blame.tcl:468
 #, tcl-format
-msgid "remote prune %s"
-msgstr "Aufräumen von »%s«"
+msgid "Reading %s..."
+msgstr "%s lesen..."
 
-#: lib/transport.tcl:19
-#, tcl-format
-msgid "Pruning tracking branches deleted from %s"
-msgstr "Übernahmezweige aufräumen und entfernen, die in »%s« gelöscht wurden"
+#: lib/blame.tcl:596
+msgid "Loading copy/move tracking annotations..."
+msgstr "Annotierungen für Kopieren/Verschieben werden geladen..."
 
-#: lib/transport.tcl:26
-#, tcl-format
-msgid "Pushing changes to %s"
-msgstr "Änderungen nach »%s« versenden"
+#: lib/blame.tcl:613
+msgid "lines annotated"
+msgstr "Zeilen annotiert"
 
-#: lib/transport.tcl:64
-#, tcl-format
-msgid "Mirroring to %s"
-msgstr "Spiegeln nach %s"
+#: lib/blame.tcl:815
+msgid "Loading original location annotations..."
+msgstr "Annotierungen für ursprünglichen Ort werden geladen..."
 
-#: lib/transport.tcl:82
-#, tcl-format
-msgid "Pushing %s %s to %s"
-msgstr "%s %s nach %s versenden"
+#: lib/blame.tcl:818
+msgid "Annotation complete."
+msgstr "Annotierung vollständig."
 
-#: lib/transport.tcl:100
-msgid "Push Branches"
-msgstr "Zweige versenden"
+#: lib/blame.tcl:849
+msgid "Busy"
+msgstr "Verarbeitung läuft"
 
-#: lib/transport.tcl:114
-msgid "Source Branches"
-msgstr "Lokale Zweige"
+#: lib/blame.tcl:850
+msgid "Annotation process is already running."
+msgstr "Annotierung läuft bereits."
 
-#: lib/transport.tcl:131
-msgid "Destination Repository"
-msgstr "Ziel-Projektarchiv"
+#: lib/blame.tcl:889
+msgid "Running thorough copy detection..."
+msgstr "Intensive Kopie-Erkennung läuft..."
 
-#: lib/transport.tcl:169
-msgid "Transfer Options"
-msgstr "Netzwerk-Einstellungen"
+#: lib/blame.tcl:957
+msgid "Loading annotation..."
+msgstr "Annotierung laden..."
 
-#: lib/transport.tcl:171
-msgid "Force overwrite existing branch (may discard changes)"
-msgstr ""
-"Überschreiben von existierenden Zweigen erzwingen (könnte Änderungen löschen)"
+#: lib/blame.tcl:1010
+msgid "Author:"
+msgstr "Autor:"
 
-#: lib/transport.tcl:175
-msgid "Use thin pack (for slow network connections)"
-msgstr "Kompaktes Datenformat benutzen (für langsame Netzverbindungen)"
+#: lib/blame.tcl:1014
+msgid "Committer:"
+msgstr "Eintragender:"
 
-#: lib/transport.tcl:179
-msgid "Include tags"
-msgstr "Mit Markierungen übertragen"
+#: lib/blame.tcl:1019
+msgid "Original File:"
+msgstr "Ursprüngliche Datei:"
+
+#: lib/blame.tcl:1067
+msgid "Cannot find HEAD commit:"
+msgstr "Zweigspitze (»HEAD«) kann nicht gefunden werden:"
+
+#: lib/blame.tcl:1122
+msgid "Cannot find parent commit:"
+msgstr "Elternversion kann nicht gefunden werden:"
+
+#: lib/blame.tcl:1137
+msgid "Unable to display parent"
+msgstr "Elternversion kann nicht angezeigt werden"
+
+#: lib/blame.tcl:1138 lib/diff.tcl:345
+msgid "Error loading diff:"
+msgstr "Fehler beim Laden des Vergleichs:"
+
+#: lib/blame.tcl:1279
+msgid "Originally By:"
+msgstr "Ursprünglich von:"
+
+#: lib/blame.tcl:1285
+msgid "In File:"
+msgstr "In Datei:"
+
+#: lib/blame.tcl:1290
+msgid "Copied Or Moved Here By:"
+msgstr "Kopiert oder verschoben durch:"
+
+#: lib/diff.tcl:77
+#, tcl-format
+msgid ""
+"No differences detected.\n"
+"\n"
+"%s has no changes.\n"
+"\n"
+"The modification date of this file was updated by another application, but "
+"the content within the file was not changed.\n"
+"\n"
+"A rescan will be automatically started to find other files which may have "
+"the same state."
+msgstr ""
+"Keine Änderungen feststellbar.\n"
+"\n"
+"»%s« enthält keine Änderungen. Zwar wurde das Änderungsdatum dieser Datei "
+"von einem anderen Programm modifiziert, aber der Inhalt der Datei ist "
+"unverändert.\n"
+"\n"
+"Das Arbeitsverzeichnis wird jetzt neu geladen, um diese Änderung bei allen "
+"Dateien zu prüfen."
+
+#: lib/diff.tcl:117
+#, tcl-format
+msgid "Loading diff of %s..."
+msgstr "Vergleich von »%s« laden..."
+
+#: lib/diff.tcl:143
+msgid ""
+"LOCAL: deleted\n"
+"REMOTE:\n"
+msgstr ""
+"LOKAL: gelöscht\n"
+"ANDERES:\n"
+
+#: lib/diff.tcl:148
+msgid ""
+"REMOTE: deleted\n"
+"LOCAL:\n"
+msgstr ""
+"ANDERES: gelöscht\n"
+"LOKAL:\n"
+
+#: lib/diff.tcl:155
+msgid "LOCAL:\n"
+msgstr "LOKAL:\n"
+
+#: lib/diff.tcl:158
+msgid "REMOTE:\n"
+msgstr "ANDERES:\n"
+
+#: lib/diff.tcl:220 lib/diff.tcl:344
+#, tcl-format
+msgid "Unable to display %s"
+msgstr "Datei »%s« kann nicht angezeigt werden"
+
+#: lib/diff.tcl:221
+msgid "Error loading file:"
+msgstr "Fehler beim Laden der Datei:"
+
+#: lib/diff.tcl:227
+msgid "Git Repository (subproject)"
+msgstr "Git-Projektarchiv (Unterprojekt)"
+
+#: lib/diff.tcl:239
+msgid "* Binary file (not showing content)."
+msgstr "* Binärdatei (Inhalt wird nicht angezeigt)"
+
+#: lib/diff.tcl:244
+#, tcl-format
+msgid ""
+"* Untracked file is %d bytes.\n"
+"* Showing only first %d bytes.\n"
+msgstr ""
+"* Datei nicht unter Versionskontrolle, Dateigröße %d Bytes.\n"
+"* Nur erste %d Bytes werden angezeigt.\n"
+
+#: lib/diff.tcl:250
+#, tcl-format
+msgid ""
+"\n"
+"* Untracked file clipped here by %s.\n"
+"* To see the entire file, use an external editor.\n"
+msgstr ""
+"\n"
+"* Datei nicht unter Versionskontrolle, hier abgeschnitten durch %s.\n"
+"* Zum Ansehen der vollständigen Datei externen Editor benutzen.\n"
+
+#: lib/diff.tcl:583
+msgid "Failed to unstage selected hunk."
+msgstr ""
+"Fehler beim Herausnehmen des gewählten Kontexts aus der Bereitstellung."
+
+#: lib/diff.tcl:591
+#, fuzzy
+msgid "Failed to revert selected hunk."
+msgstr "Fehler beim Bereitstellen des gewählten Kontexts."
+
+#: lib/diff.tcl:594
+msgid "Failed to stage selected hunk."
+msgstr "Fehler beim Bereitstellen des gewählten Kontexts."
+
+#: lib/diff.tcl:687
+msgid "Failed to unstage selected line."
+msgstr "Fehler beim Herausnehmen der gewählten Zeile aus der Bereitstellung."
+
+#: lib/diff.tcl:696
+#, fuzzy
+msgid "Failed to revert selected line."
+msgstr "Fehler beim Bereitstellen der gewählten Zeile."
+
+#: lib/diff.tcl:700
+msgid "Failed to stage selected line."
+msgstr "Fehler beim Bereitstellen der gewählten Zeile."
+
+#: lib/diff.tcl:889
+#, fuzzy
+msgid "Failed to undo last revert."
+msgstr "Aktualisieren von »%s« fehlgeschlagen."
+
+#: lib/sshkey.tcl:34
+msgid "No keys found."
+msgstr "Keine Schlüssel gefunden."
+
+#: lib/sshkey.tcl:37
+#, tcl-format
+msgid "Found a public key in: %s"
+msgstr "Öffentlicher Schlüssel gefunden in: %s"
+
+#: lib/sshkey.tcl:43
+msgid "Generate Key"
+msgstr "Schlüssel erzeugen"
+
+#: lib/sshkey.tcl:61
+msgid "Copy To Clipboard"
+msgstr "In Zwischenablage kopieren"
+
+#: lib/sshkey.tcl:75
+msgid "Your OpenSSH Public Key"
+msgstr "Ihr OpenSSH öffenlicher Schlüssel"
+
+#: lib/sshkey.tcl:83
+msgid "Generating..."
+msgstr "Erzeugen..."
+
+#: lib/sshkey.tcl:89
+#, tcl-format
+msgid ""
+"Could not start ssh-keygen:\n"
+"\n"
+"%s"
+msgstr ""
+"Konnte »ssh-keygen« nicht starten:\n"
+"\n"
+"%s"
+
+#: lib/sshkey.tcl:116
+msgid "Generation failed."
+msgstr "Schlüsselerzeugung fehlgeschlagen."
+
+#: lib/sshkey.tcl:123
+msgid "Generation succeeded, but no keys found."
+msgstr "Schlüsselerzeugung erfolgreich, aber keine Schlüssel gefunden."
+
+#: lib/sshkey.tcl:126
+#, tcl-format
+msgid "Your key is in: %s"
+msgstr "Ihr Schlüssel ist abgelegt in: %s"
+
+#: lib/branch_create.tcl:23
+#, fuzzy, tcl-format
+msgid "%s (%s): Create Branch"
+msgstr "Zweig erstellen"
+
+#: lib/branch_create.tcl:28
+msgid "Create New Branch"
+msgstr "Neuen Zweig erstellen"
+
+#: lib/branch_create.tcl:42
+msgid "Branch Name"
+msgstr "Zweigname"
+
+#: lib/branch_create.tcl:57
+msgid "Match Tracking Branch Name"
+msgstr "Passend zu Übernahmezweig-Name"
+
+#: lib/branch_create.tcl:66
+msgid "Starting Revision"
+msgstr "Anfangsversion"
+
+#: lib/branch_create.tcl:72
+msgid "Update Existing Branch:"
+msgstr "Existierenden Zweig aktualisieren:"
+
+#: lib/branch_create.tcl:75
+msgid "No"
+msgstr "Nein"
+
+#: lib/branch_create.tcl:80
+msgid "Fast Forward Only"
+msgstr "Nur Schnellzusammenführung"
+
+#: lib/branch_create.tcl:97
+msgid "Checkout After Creation"
+msgstr "Arbeitskopie umstellen nach Erstellen"
+
+#: lib/branch_create.tcl:132
+msgid "Please select a tracking branch."
+msgstr "Bitte wählen Sie einen Übernahmezweig."
+
+#: lib/branch_create.tcl:141
+#, tcl-format
+msgid "Tracking branch %s is not a branch in the remote repository."
+msgstr "Übernahmezweig »%s« ist kein Zweig im externen Projektarchiv."
+
+#: lib/console.tcl:59
+msgid "Working... please wait..."
+msgstr "Verarbeitung. Bitte warten..."
+
+#: lib/console.tcl:186
+msgid "Success"
+msgstr "Erfolgreich"
+
+#: lib/console.tcl:200
+msgid "Error: Command Failed"
+msgstr "Fehler: Kommando fehlgeschlagen"
+
+#: lib/line.tcl:17
+msgid "Goto Line:"
+msgstr ""
+
+#: lib/line.tcl:23
+msgid "Go"
+msgstr ""
+
+#: lib/choose_rev.tcl:52
+msgid "This Detached Checkout"
+msgstr "Abgetrennte Arbeitskopie-Version"
+
+#: lib/choose_rev.tcl:60
+msgid "Revision Expression:"
+msgstr "Version Regexp-Ausdruck:"
+
+#: lib/choose_rev.tcl:72
+msgid "Local Branch"
+msgstr "Lokaler Zweig"
+
+#: lib/choose_rev.tcl:77
+msgid "Tracking Branch"
+msgstr "Übernahmezweig"
+
+#: lib/choose_rev.tcl:82 lib/choose_rev.tcl:544
+msgid "Tag"
+msgstr "Markierung"
+
+#: lib/choose_rev.tcl:321
+#, tcl-format
+msgid "Invalid revision: %s"
+msgstr "Ungültige Version: %s"
+
+#: lib/choose_rev.tcl:342
+msgid "No revision selected."
+msgstr "Keine Version ausgewählt."
+
+#: lib/choose_rev.tcl:350
+msgid "Revision expression is empty."
+msgstr "Versions-Ausdruck ist leer."
+
+#: lib/choose_rev.tcl:537
+msgid "Updated"
+msgstr "Aktualisiert"
+
+#: lib/choose_rev.tcl:565
+msgid "URL"
+msgstr "URL"
+
+#: lib/commit.tcl:9
+msgid ""
+"There is nothing to amend.\n"
+"\n"
+"You are about to create the initial commit.  There is no commit before this "
+"to amend.\n"
+msgstr ""
+"Keine Version zur Nachbesserung vorhanden.\n"
+"\n"
+"Sie sind dabei, die erste Version zu übertragen. Es gibt keine existierende "
+"Version, die Sie nachbessern könnten.\n"
+
+#: lib/commit.tcl:18
+msgid ""
+"Cannot amend while merging.\n"
+"\n"
+"You are currently in the middle of a merge that has not been fully "
+"completed.  You cannot amend the prior commit unless you first abort the "
+"current merge activity.\n"
+msgstr ""
+"Nachbesserung währen Zusammenführung nicht möglich.\n"
+"\n"
+"Sie haben das Zusammenführen von Versionen angefangen, aber noch nicht "
+"beendet. Sie können keine vorige Übertragung nachbessern, solange eine "
+"unfertige Zusammenführung existiert. Dazu müssen Sie die Zusammenführung "
+"beenden oder abbrechen.\n"
+
+#: lib/commit.tcl:56
+msgid "Error loading commit data for amend:"
+msgstr "Fehler beim Laden der Versionsdaten für Nachbessern:"
+
+#: lib/commit.tcl:83
+msgid "Unable to obtain your identity:"
+msgstr "Benutzername konnte nicht bestimmt werden:"
+
+#: lib/commit.tcl:88
+msgid "Invalid GIT_COMMITTER_IDENT:"
+msgstr "Ungültiger Wert von GIT_COMMITTER_INDENT:"
+
+#: lib/commit.tcl:138
+#, tcl-format
+msgid "warning: Tcl does not support encoding '%s'."
+msgstr "Warning: Tcl/Tk unterstützt die Zeichencodierung »%s« nicht."
+
+#: lib/commit.tcl:158
+msgid ""
+"Last scanned state does not match repository state.\n"
+"\n"
+"Another Git program has modified this repository since the last scan.  A "
+"rescan must be performed before another commit can be created.\n"
+"\n"
+"The rescan will be automatically started now.\n"
+msgstr ""
+"Der letzte geladene Status stimmt nicht mehr mit dem Projektarchiv überein.\n"
+"\n"
+"Ein anderes Git-Programm hat das Projektarchiv seit dem letzten Laden "
+"geändert.  Vor dem Eintragen einer neuen Version muss neu geladen werden.\n"
+"\n"
+"Es wird gleich neu geladen.\n"
+
+#: lib/commit.tcl:182
+#, tcl-format
+msgid ""
+"Unmerged files cannot be committed.\n"
+"\n"
+"File %s has merge conflicts.  You must resolve them and stage the file "
+"before committing.\n"
+msgstr ""
+"Nicht zusammengeführte Dateien können nicht eingetragen werden.\n"
+"\n"
+"Die Datei »%s« hat noch nicht aufgelöste Zusammenführungs-Konflikte. Sie "
+"müssen diese Konflikte auflösen, bevor Sie eintragen können.\n"
+
+#: lib/commit.tcl:190
+#, tcl-format
+msgid ""
+"Unknown file state %s detected.\n"
+"\n"
+"File %s cannot be committed by this program.\n"
+msgstr ""
+"Unbekannter Dateizustand »%s«.\n"
+"\n"
+"Datei »%s« kann nicht eingetragen werden.\n"
+
+#: lib/commit.tcl:198
+msgid ""
+"No changes to commit.\n"
+"\n"
+"You must stage at least 1 file before you can commit.\n"
+msgstr ""
+"Keine Änderungen vorhanden, die eingetragen werden könnten.\n"
+"\n"
+"Sie müssen mindestens eine Datei bereitstellen, bevor Sie eintragen können.\n"
+
+#: lib/commit.tcl:213
+msgid ""
+"Please supply a commit message.\n"
+"\n"
+"A good commit message has the following format:\n"
+"\n"
+"- First line: Describe in one sentence what you did.\n"
+"- Second line: Blank\n"
+"- Remaining lines: Describe why this change is good.\n"
+msgstr ""
+"Bitte geben Sie eine Versionsbeschreibung ein.\n"
+"\n"
+"Eine gute Versionsbeschreibung enthält folgende Abschnitte:\n"
+"\n"
+"- Erste Zeile: Eine Zusammenfassung, was man gemacht hat.\n"
+"\n"
+"- Zweite Zeile: Leerzeile\n"
+"\n"
+"- Rest: Eine ausführliche Beschreibung, warum diese Änderung hilfreich ist.\n"
+
+#: lib/commit.tcl:244
+msgid "Calling pre-commit hook..."
+msgstr "Aufrufen der Vor-Eintragen-Kontrolle (»pre-commit hook«)..."
+
+#: lib/commit.tcl:259
+msgid "Commit declined by pre-commit hook."
+msgstr "Eintragen abgelehnt durch Vor-Eintragen-Kontrolle (»pre-commit hook«)."
+
+#: lib/commit.tcl:278
+msgid ""
+"You are about to commit on a detached head. This is a potentially dangerous "
+"thing to do because if you switch to another branch you will lose your "
+"changes and it can be difficult to retrieve them later from the reflog. You "
+"should probably cancel this commit and create a new branch to continue.\n"
+" \n"
+" Do you really want to proceed with your Commit?"
+msgstr ""
+
+#: lib/commit.tcl:299
+msgid "Calling commit-msg hook..."
+msgstr ""
+"Aufrufen der Versionsbeschreibungs-Kontrolle (»commit-message hook«)..."
+
+#: lib/commit.tcl:314
+msgid "Commit declined by commit-msg hook."
+msgstr ""
+"Eintragen abgelehnt durch Versionsbeschreibungs-Kontrolle (»commit-message "
+"hook«)."
+
+#: lib/commit.tcl:327
+msgid "Committing changes..."
+msgstr "Änderungen eintragen..."
+
+#: lib/commit.tcl:344
+msgid "write-tree failed:"
+msgstr "write-tree fehlgeschlagen:"
+
+#: lib/commit.tcl:345 lib/commit.tcl:395 lib/commit.tcl:422
+msgid "Commit failed."
+msgstr "Eintragen fehlgeschlagen."
+
+#: lib/commit.tcl:362
+#, tcl-format
+msgid "Commit %s appears to be corrupt"
+msgstr "Version »%s« scheint beschädigt zu sein"
+
+#: lib/commit.tcl:367
+msgid ""
+"No changes to commit.\n"
+"\n"
+"No files were modified by this commit and it was not a merge commit.\n"
+"\n"
+"A rescan will be automatically started now.\n"
+msgstr ""
+"Keine Änderungen einzutragen.\n"
+"\n"
+"Es gibt keine geänderte Datei bei dieser Version und es wurde auch nichts "
+"zusammengeführt.\n"
+"\n"
+"Das Arbeitsverzeichnis wird daher jetzt neu geladen.\n"
+
+#: lib/commit.tcl:374
+msgid "No changes to commit."
+msgstr "Keine Änderungen, die eingetragen werden können."
+
+#: lib/commit.tcl:394
+msgid "commit-tree failed:"
+msgstr "commit-tree fehlgeschlagen:"
+
+#: lib/commit.tcl:421
+msgid "update-ref failed:"
+msgstr "update-ref fehlgeschlagen:"
+
+#: lib/commit.tcl:514
+#, tcl-format
+msgid "Created commit %s: %s"
+msgstr "Version %s übertragen: %s"
+
+#: lib/branch_delete.tcl:16
+#, fuzzy, tcl-format
+msgid "%s (%s): Delete Branch"
+msgstr "Zweig löschen"
+
+#: lib/branch_delete.tcl:21
+msgid "Delete Local Branch"
+msgstr "Lokalen Zweig löschen"
+
+#: lib/branch_delete.tcl:39
+msgid "Local Branches"
+msgstr "Lokale Zweige"
+
+#: lib/branch_delete.tcl:51
+msgid "Delete Only If Merged Into"
+msgstr "Nur löschen, wenn zusammengeführt nach"
+
+#: lib/branch_delete.tcl:103
+#, tcl-format
+msgid "The following branches are not completely merged into %s:"
+msgstr "Folgende Zweige sind noch nicht mit »%s« zusammengeführt:"
+
+#: lib/branch_delete.tcl:131
+#, tcl-format
+msgid " - %s:"
+msgstr ""
+
+#: lib/branch_delete.tcl:141
+#, tcl-format
+msgid ""
+"Failed to delete branches:\n"
+"%s"
+msgstr ""
+"Fehler beim Löschen der Zweige:\n"
+"%s"
+
+#: lib/date.tcl:25
+#, tcl-format
+msgid "Invalid date from Git: %s"
+msgstr "Ungültiges Datum von Git: %s"
+
+#: lib/database.tcl:42
+msgid "Number of loose objects"
+msgstr "Anzahl unverknüpfter Objekte"
+
+#: lib/database.tcl:43
+msgid "Disk space used by loose objects"
+msgstr "Festplattenplatz von unverknüpften Objekten"
+
+#: lib/database.tcl:44
+msgid "Number of packed objects"
+msgstr "Anzahl komprimierter Objekte"
+
+#: lib/database.tcl:45
+msgid "Number of packs"
+msgstr "Anzahl Komprimierungseinheiten"
+
+#: lib/database.tcl:46
+msgid "Disk space used by packed objects"
+msgstr "Festplattenplatz von komprimierten Objekten"
+
+#: lib/database.tcl:47
+msgid "Packed objects waiting for pruning"
+msgstr "Komprimierte Objekte, die zum Aufräumen vorgesehen sind"
+
+#: lib/database.tcl:48
+msgid "Garbage files"
+msgstr "Dateien im Mülleimer"
+
+#: lib/database.tcl:66
+#, fuzzy, tcl-format
+msgid "%s (%s): Database Statistics"
+msgstr "Datenbankstatistik"
+
+#: lib/database.tcl:72
+msgid "Compressing the object database"
+msgstr "Objektdatenbank komprimieren"
+
+#: lib/database.tcl:83
+msgid "Verifying the object database with fsck-objects"
+msgstr "Die Objektdatenbank durch »fsck-objects« überprüfen lassen"
+
+#: lib/database.tcl:107
+#, tcl-format
+msgid ""
+"This repository currently has approximately %i loose objects.\n"
+"\n"
+"To maintain optimal performance it is strongly recommended that you compress "
+"the database.\n"
+"\n"
+"Compress the database now?"
+msgstr ""
+"Dieses Projektarchiv enthält ungefähr %i nicht verknüpfte Objekte.\n"
+"\n"
+"Für eine optimale Performance wird empfohlen, die Datenbank des "
+"Projektarchivs zu komprimieren.\n"
+"\n"
+"Soll die Datenbank jetzt komprimiert werden?"
+
+#: lib/error.tcl:20
+#, fuzzy, tcl-format
+msgid "%s: error"
+msgstr "Fehler"
+
+#: lib/error.tcl:36
+#, fuzzy, tcl-format
+msgid "%s: warning"
+msgstr "Warnung"
+
+#: lib/error.tcl:80
+#, fuzzy, tcl-format
+msgid "%s hook failed:"
+msgstr "Werkzeug fehlgeschlagen: %s"
+
+#: lib/error.tcl:96
+msgid "You must correct the above errors before committing."
+msgstr ""
+"Sie müssen die obigen Fehler zuerst beheben, bevor Sie eintragen können."
+
+#: lib/error.tcl:116
+#, tcl-format
+msgid "%s (%s): error"
+msgstr ""
+
+#: lib/merge.tcl:13
+msgid ""
+"Cannot merge while amending.\n"
+"\n"
+"You must finish amending this commit before starting any type of merge.\n"
+msgstr ""
+"Zusammenführen kann nicht gleichzeitig mit Nachbessern durchgeführt werden.\n"
+"\n"
+"Sie müssen zuerst die Nachbesserungs-Version abschließen, bevor Sie "
+"zusammenführen können.\n"
+
+#: lib/merge.tcl:27
+msgid ""
+"Last scanned state does not match repository state.\n"
+"\n"
+"Another Git program has modified this repository since the last scan.  A "
+"rescan must be performed before a merge can be performed.\n"
+"\n"
+"The rescan will be automatically started now.\n"
+msgstr ""
+"Der letzte geladene Status stimmt nicht mehr mit dem Projektarchiv überein.\n"
+"\n"
+"Ein anderes Git-Programm hat das Projektarchiv seit dem letzten Laden "
+"geändert.  Vor einem Zusammenführen muss neu geladen werden.\n"
+"\n"
+"Es wird gleich neu geladen.\n"
+
+#: lib/merge.tcl:45
+#, tcl-format
+msgid ""
+"You are in the middle of a conflicted merge.\n"
+"\n"
+"File %s has merge conflicts.\n"
+"\n"
+"You must resolve them, stage the file, and commit to complete the current "
+"merge.  Only then can you begin another merge.\n"
+msgstr ""
+"Zusammenführung mit Konflikten.\n"
+"\n"
+"Die Datei »%s« enthält Konflikte beim Zusammenführen. Sie müssen diese "
+"Konflikte per Hand auflösen. Anschließend müssen Sie die Datei wieder "
+"bereitstellen und eintragen, um die Zusammenführung abzuschließen. Erst "
+"danach kann eine neue Zusammenführung begonnen werden.\n"
+
+#: lib/merge.tcl:55
+#, tcl-format
+msgid ""
+"You are in the middle of a change.\n"
+"\n"
+"File %s is modified.\n"
+"\n"
+"You should complete the current commit before starting a merge.  Doing so "
+"will help you abort a failed merge, should the need arise.\n"
+msgstr ""
+"Es liegen Änderungen vor.\n"
+"\n"
+"Die Datei »%s« wurde geändert.  Sie sollten zuerst die bereitgestellte "
+"Version abschließen, bevor Sie eine Zusammenführung beginnen.  Mit dieser "
+"Reihenfolge können Sie mögliche Konflikte beim Zusammenführen wesentlich "
+"einfacher beheben oder abbrechen.\n"
+
+#: lib/merge.tcl:108
+#, tcl-format
+msgid "%s of %s"
+msgstr "%s von %s"
+
+#: lib/merge.tcl:126
+#, tcl-format
+msgid "Merging %s and %s..."
+msgstr "Zusammenführen von %s und %s..."
+
+#: lib/merge.tcl:137
+msgid "Merge completed successfully."
+msgstr "Zusammenführen erfolgreich abgeschlossen."
+
+#: lib/merge.tcl:139
+msgid "Merge failed.  Conflict resolution is required."
+msgstr "Zusammenführen fehlgeschlagen. Konfliktauflösung ist notwendig."
+
+#: lib/merge.tcl:156
+#, tcl-format
+msgid "%s (%s): Merge"
+msgstr ""
+
+#: lib/merge.tcl:164
+#, tcl-format
+msgid "Merge Into %s"
+msgstr "Zusammenführen in »%s«"
+
+#: lib/merge.tcl:183
+msgid "Revision To Merge"
+msgstr "Zusammenzuführende Version"
+
+#: lib/merge.tcl:218
+msgid ""
+"Cannot abort while amending.\n"
+"\n"
+"You must finish amending this commit.\n"
+msgstr ""
+"Abbruch der Nachbesserung ist nicht möglich.\n"
+"\n"
+"Sie müssen die Nachbesserung der Version abschließen.\n"
+
+#: lib/merge.tcl:228
+msgid ""
+"Abort merge?\n"
+"\n"
+"Aborting the current merge will cause *ALL* uncommitted changes to be lost.\n"
+"\n"
+"Continue with aborting the current merge?"
+msgstr ""
+"Zusammenführen abbrechen?\n"
+"\n"
+"Wenn Sie abbrechen, gehen alle noch nicht eingetragenen Änderungen "
+"verloren.\n"
+"\n"
+"Zusammenführen jetzt abbrechen?"
+
+#: lib/merge.tcl:234
+msgid ""
+"Reset changes?\n"
+"\n"
+"Resetting the changes will cause *ALL* uncommitted changes to be lost.\n"
+"\n"
+"Continue with resetting the current changes?"
+msgstr ""
+"Änderungen zurücksetzen?\n"
+"\n"
+"Wenn Sie zurücksetzen, gehen alle noch nicht eingetragenen Änderungen "
+"verloren.\n"
+"\n"
+"Änderungen jetzt zurücksetzen?"
+
+#: lib/merge.tcl:246
+msgid "Aborting"
+msgstr "Abbruch"
+
+#: lib/merge.tcl:247
+msgid "files reset"
+msgstr "Dateien zurückgesetzt"
+
+#: lib/merge.tcl:277
+msgid "Abort failed."
+msgstr "Abbruch fehlgeschlagen."
+
+#: lib/merge.tcl:279
+msgid "Abort completed.  Ready."
+msgstr "Abbruch durchgeführt. Bereit."
+
+#~ msgid "Displaying only %s of %s files."
+#~ msgstr "Nur %s von %s Dateien werden angezeigt."
+
+#~ msgid "New Commit"
+#~ msgstr "Neue Version"
+
+#~ msgid "Case-Sensitive"
+#~ msgstr "Groß-/Kleinschreibung unterscheiden"
diff --git a/po/glossary/de.po b/po/glossary/de.po
index 35764d1d22..5af06bf4c1 100644
--- a/po/glossary/de.po
+++ b/po/glossary/de.po
@@ -6,10 +6,11 @@
 msgid ""
 msgstr ""
 "Project-Id-Version: git-gui glossary\n"
-"POT-Creation-Date: 2008-01-07 21:20+0100\n"
-"PO-Revision-Date: 2008-02-16 21:48+0100\n"
+"POT-Creation-Date: 2020-01-13 21:40+0100\n"
+"PO-Revision-Date: 2020-01-13 21:53+0100\n"
 "Last-Translator: Christian Stimming <stimming@tuhh.de>\n"
 "Language-Team: German \n"
+"Language: de_DE\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
@@ -130,7 +131,7 @@ msgstr "wiederholen"
 
 #. "An other repository ('remote'). One might have a set of remotes whose branches one tracks."
 msgid "remote"
-msgstr "Andere Archive (Gegenseite?, Entfernte?, Server?)"
+msgstr "Extern (Andere?, Gegenseite?, Entfernte?, Server?)"
 
 #. "A collection of refs (?) together with an object database containing all objects which are reachable from the refs... (oops, you've lost me here. Again, please an explanation for mere mortals?)"
 msgid "repository"
@@ -142,7 +143,8 @@ msgstr "zurücksetzen (zurückkehren?)"
 
 #. ""
 msgid "revert"
-msgstr "verwerfen (bei git-reset), revidieren (bei git-revert, also mit neuem commit)"
+msgstr ""
+"verwerfen (bei git-reset), revidieren (bei git-revert, also mit neuem commit)"
 
 #. "A particular state of files and directories which was stored in the object database."
 msgid "revision"
@@ -187,3 +189,124 @@ msgstr "überprüfen"
 #. "The tree of actual checked out files."
 msgid "working copy, working tree"
 msgstr "Arbeitskopie"
+
+#. "a commit that succeeds the current one in git's graph of commits (not necessarily directly)"
+msgid "ancestor"
+msgstr ""
+
+#. "prematurely stop and abandon an operation"
+msgid "abort"
+msgstr ""
+
+#. "a repository with only .git directory, without working directory"
+msgid "bare repository"
+msgstr "bloßes Projektarchiv"
+
+#. "a parent version of the current file"
+msgid "base"
+msgstr ""
+
+#. "get the authors responsible for each line in a file"
+msgid "blame"
+msgstr ""
+
+#. "to select and apply a single commit without merging"
+msgid "cherry-pick"
+msgstr ""
+
+#. "a commit that directly succeeds the current one in git's graph of commits"
+msgid "child"
+msgstr ""
+
+#. "clean the state of the git repository, often after manually stopped operation"
+msgid "cleanup"
+msgstr ""
+
+#. "a message that gets attached with any commit"
+#, fuzzy
+msgid "commit message"
+msgstr "Beschreibung (Meldung?, Nachricht?; Source Safe: Kommentar)"
+
+#. "a commit that precedes the current one in git's graph of commits (not necessarily directly)"
+msgid "descendant"
+msgstr ""
+
+#. "checkout of a revision rather than a some head"
+msgid "detached checkout"
+msgstr ""
+
+#. "any merge strategy that works on a file by file basis"
+msgid "file level merging"
+msgstr ""
+
+#. "the last revision in a branch"
+msgid "head"
+msgstr ""
+
+#. "script that gets executed automatically on some event"
+msgid "hook"
+msgstr ""
+
+#. "the first checkout during a clone operation"
+msgid "initial checkout"
+msgstr ""
+
+#. "a branch that resides in the local git repository"
+msgid "local branch"
+msgstr ""
+
+#. "a Git object that is not part of any pack"
+msgid "loose object"
+msgstr ""
+
+#. "a branch called by convention 'master' that exists in a newly created git repository"
+#, fuzzy
+msgid "master branch"
+msgstr "Übernahmezweig"
+
+#. "a remote called by convention 'origin' that the current git repository has been cloned from"
+msgid "origin"
+msgstr "origin"
+
+#. "a file containing many git objects packed together"
+#, fuzzy
+msgid "pack [noun]"
+msgstr "Markierung"
+
+#. "a Git object part of some pack"
+msgid "packed object"
+msgstr ""
+
+#. "a commit that directly precedes the current one in git's graph of commits"
+msgid "parent"
+msgstr ""
+
+#. "the log file containing all states of the HEAD reference (in other words past pristine states of the working copy)"
+msgid "reflog"
+msgstr ""
+
+#. "decide which changes from alternative versions of a file should persist in Git"
+msgid "resolve (a conflict)"
+msgstr ""
+
+#. "abandon changes and go to pristine version"
+#, fuzzy
+msgid "revert changes"
+msgstr ""
+"verwerfen (bei git-reset), revidieren (bei git-revert, also mit neuem commit)"
+
+#. "expression that signifies a revision in git"
+msgid "revision expression"
+msgstr ""
+
+#. "add some content of files and directories to the staging area in preparation for a commit"
+msgid "stage/unstage"
+msgstr ""
+
+#. "temporarily save changes in a stack without committing"
+msgid "stash"
+msgstr ""
+
+#. "file whose content is tracked/not tracked by git"
+msgid "tracked/untracked"
+msgstr ""
-- 
gitgitgadget


^ permalink raw reply related	[relevance 1%]

* [PATCH 2/3] git-gui: update german translation
    2020-01-24 22:33  1% ` [PATCH 1/3] git-gui: update german translation to most recently created pot templates Christian Stimming via GitGitGadget
@ 2020-01-24 22:33  1% ` Christian Stimming via GitGitGadget
  2020-02-09 22:00  1% ` [PATCH v2 0/3] git gui: improve German translation Christian Stimming via GitGitGadget
  2 siblings, 0 replies; 200+ results
From: Christian Stimming via GitGitGadget @ 2020-01-24 22:33 UTC (permalink / raw)
  To: git; +Cc: Christian Stimming, Pratyush Yadav, Christian Stimming

From: Christian Stimming <christian@cstimming.de>

Switch several terms from uncommon translations back to english
vocabulary, most prominently commit (noun, verb) and repository. Adapt
glossary and translation accordingly.

Signed-off-by: Christian Stimming <christian@cstimming.de>
---
 po/de.po          | 425 ++++++++++++++++++++--------------------------
 po/glossary/de.po |  20 +--
 2 files changed, 191 insertions(+), 254 deletions(-)

diff --git a/po/de.po b/po/de.po
index 162dc8aebe..ae7b0d8fd9 100644
--- a/po/de.po
+++ b/po/de.po
@@ -8,10 +8,10 @@ msgstr ""
 "Project-Id-Version: git-gui\n"
 "Report-Msgid-Bugs-To: \n"
 "POT-Creation-Date: 2020-01-13 21:51+0100\n"
-"PO-Revision-Date: 2010-01-26 22:25+0100\n"
+"PO-Revision-Date: 2020-01-13 22:37+0100\n"
 "Last-Translator: Christian Stimming <stimming@tuhh.de>\n"
 "Language-Team: German\n"
-"Language: \n"
+"Language: de_DE\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
@@ -73,7 +73,7 @@ msgstr ""
 
 #: git-gui.sh:1309
 msgid "Cannot use bare repository:"
-msgstr "Bloßes Projektarchiv kann nicht benutzt werden:"
+msgstr "Bloßes Repository kann nicht benutzt werden:"
 
 #: git-gui.sh:1317
 msgid "No working directory"
@@ -89,14 +89,11 @@ msgstr "Nach geänderten Dateien suchen..."
 
 #: git-gui.sh:1629
 msgid "Calling prepare-commit-msg hook..."
-msgstr ""
-"Aufrufen der Eintragen-Vorbereiten-Kontrolle (»prepare-commit hook«)..."
+msgstr "Aufrufen des »prepare-commit hook«..."
 
 #: git-gui.sh:1646
 msgid "Commit declined by prepare-commit-msg hook."
-msgstr ""
-"Eintragen abgelehnt durch Eintragen-Vorbereiten-Kontrolle (»prepare-commit "
-"hook«)."
+msgstr "Commit abgelehnt durch »prepare-commit hook«."
 
 #: git-gui.sh:1804 lib/browser.tcl:252
 msgid "Ready."
@@ -118,38 +115,35 @@ msgstr "Verändert, nicht bereitgestellt"
 
 #: git-gui.sh:2094 git-gui.sh:2106
 msgid "Staged for commit"
-msgstr "Bereitgestellt zum Eintragen"
+msgstr "Bereitgestellt zum Committen"
 
 #: git-gui.sh:2095 git-gui.sh:2107
 msgid "Portions staged for commit"
-msgstr "Teilweise bereitgestellt zum Eintragen"
+msgstr "Teilweise bereitgestellt zum Committen"
 
 #: git-gui.sh:2096 git-gui.sh:2108
 msgid "Staged for commit, missing"
-msgstr "Bereitgestellt zum Eintragen, fehlend"
+msgstr "Bereitgestellt zum Committen, fehlend"
 
 #: git-gui.sh:2098
 msgid "File type changed, not staged"
 msgstr "Dateityp geändert, nicht bereitgestellt"
 
 #: git-gui.sh:2099 git-gui.sh:2100
-#, fuzzy
 msgid "File type changed, old type staged for commit"
-msgstr "Dateityp geändert, nicht bereitgestellt"
+msgstr "Dateityp geändert, alter Dateityp bereitgestellt"
 
 #: git-gui.sh:2101
 msgid "File type changed, staged"
 msgstr "Dateityp geändert, bereitgestellt"
 
 #: git-gui.sh:2102
-#, fuzzy
 msgid "File type change staged, modification not staged"
-msgstr "Dateityp geändert, nicht bereitgestellt"
+msgstr "Dateityp-Änderung bereitgestellt, Inhaltsänderung nicht bereitgestellt"
 
 #: git-gui.sh:2103
-#, fuzzy
 msgid "File type change staged, file missing"
-msgstr "Dateityp geändert, bereitgestellt"
+msgstr "Dateityp-Änderung bereitgestellt, Datei gelöscht"
 
 #: git-gui.sh:2105
 msgid "Untracked, not staged"
@@ -177,9 +171,9 @@ msgid "Couldn't find gitk in PATH"
 msgstr "Gitk kann im PATH nicht gefunden werden."
 
 #: git-gui.sh:2210 git-gui.sh:2245
-#, fuzzy, tcl-format
+#, tcl-format
 msgid "Starting %s... please wait..."
-msgstr "Gitk wird gestartet... bitte warten."
+msgstr "%s wird gestartet... bitte warten."
 
 #: git-gui.sh:2224
 msgid "Couldn't find git gui in PATH"
@@ -187,7 +181,7 @@ msgstr "»Git gui« kann im PATH nicht gefunden werden."
 
 #: git-gui.sh:2726 lib/choose_repository.tcl:53
 msgid "Repository"
-msgstr "Projektarchiv"
+msgstr "Repository"
 
 #: git-gui.sh:2727
 msgid "Edit"
@@ -199,7 +193,7 @@ msgstr "Zweig"
 
 #: git-gui.sh:2732 lib/choose_rev.tcl:554
 msgid "Commit@@noun"
-msgstr "Version"
+msgstr "Commit"
 
 #: git-gui.sh:2735 lib/merge.tcl:127 lib/merge.tcl:174
 msgid "Merge"
@@ -207,7 +201,7 @@ msgstr "Zusammenführen"
 
 #: git-gui.sh:2736 lib/choose_rev.tcl:563
 msgid "Remote"
-msgstr "Externe Archive"
+msgstr "Extern"
 
 #: git-gui.sh:2739
 msgid "Tools"
@@ -219,7 +213,7 @@ msgstr "Arbeitskopie im Dateimanager"
 
 #: git-gui.sh:2763
 msgid "Git Bash"
-msgstr ""
+msgstr "Git Bash"
 
 #: git-gui.sh:2772
 msgid "Browse Current Branch's Files"
@@ -323,11 +317,11 @@ msgstr "Fertig"
 
 #: git-gui.sh:2899
 msgid "Commit@@verb"
-msgstr "Eintragen"
+msgstr "Committen"
 
 #: git-gui.sh:2908 git-gui.sh:3400
 msgid "Amend Last Commit"
-msgstr "Letzte nachbessern"
+msgstr "Letzten Commit nachbessern"
 
 #: git-gui.sh:2918 git-gui.sh:3361 lib/remote_branch_delete.tcl:101
 msgid "Rescan"
@@ -335,15 +329,15 @@ msgstr "Neu laden"
 
 #: git-gui.sh:2924
 msgid "Stage To Commit"
-msgstr "Zum Eintragen bereitstellen"
+msgstr "Zum Committen bereitstellen"
 
 #: git-gui.sh:2930
 msgid "Stage Changed Files To Commit"
-msgstr "Geänderte Dateien bereitstellen"
+msgstr "Geänderte Dateien für Commit bereitstellen"
 
 #: git-gui.sh:2936
 msgid "Unstage From Commit"
-msgstr "Aus der Bereitstellung herausnehmen"
+msgstr "Aus der Commit-Bereitstellung herausnehmen"
 
 #: git-gui.sh:2942 lib/index.tcl:521
 msgid "Revert Changes"
@@ -409,14 +403,13 @@ msgstr "SSH-Schlüssel anzeigen"
 
 #: git-gui.sh:3097 git-gui.sh:3229
 msgid "usage:"
-msgstr ""
+msgstr "Verwendung:"
 
 #: git-gui.sh:3101 git-gui.sh:3233
 msgid "Usage"
-msgstr ""
+msgstr "Verwendung"
 
 #: git-gui.sh:3182 lib/blame.tcl:575
-#, fuzzy
 msgid "Error"
 msgstr "Fehler"
 
@@ -437,7 +430,7 @@ msgstr "Nicht bereitgestellte Änderungen"
 
 #: git-gui.sh:3293
 msgid "Staged Changes (Will Commit)"
-msgstr "Bereitstellung (zum Eintragen)"
+msgstr "Bereitstellung (zum Committen)"
 
 #: git-gui.sh:3367
 msgid "Stage Changed"
@@ -449,7 +442,7 @@ msgstr "Versenden"
 
 #: git-gui.sh:3413
 msgid "Initial Commit Message:"
-msgstr "Erste Versionsbeschreibung:"
+msgstr "Erste Commit-Beschreibung:"
 
 #: git-gui.sh:3414
 msgid "Amended Commit Message:"
@@ -469,7 +462,7 @@ msgstr "Zusammenführungs-Beschreibung:"
 
 #: git-gui.sh:3418
 msgid "Commit Message:"
-msgstr "Versionsbeschreibung:"
+msgstr "Commit-Beschreibung:"
 
 #: git-gui.sh:3477 git-gui.sh:3641 lib/console.tcl:73
 msgid "Copy All"
@@ -508,18 +501,16 @@ msgid "Apply/Reverse Line"
 msgstr "Zeile anwenden/umkehren"
 
 #: git-gui.sh:3684 git-gui.sh:3794 git-gui.sh:3805
-#, fuzzy
 msgid "Revert Hunk"
-msgstr "Kontext anwenden/umkehren"
+msgstr "Kontextänderung umkehren"
 
 #: git-gui.sh:3689 git-gui.sh:3801 git-gui.sh:3812
-#, fuzzy
 msgid "Revert Line"
-msgstr "Änderungen verwerfen"
+msgstr "Zeilenänderungen umkehren"
 
 #: git-gui.sh:3694 git-gui.sh:3791
 msgid "Undo Last Revert"
-msgstr ""
+msgstr "Letzte Umkehrung rückgängig"
 
 #: git-gui.sh:3713
 msgid "Run Merge Tool"
@@ -535,7 +526,7 @@ msgstr "Lokale Version benutzen"
 
 #: git-gui.sh:3726
 msgid "Revert To Base"
-msgstr "Ursprüngliche Version benutzen"
+msgstr "Zurücksetzen auf ursprünglichen Commit"
 
 #: git-gui.sh:3744
 msgid "Visualize These Changes In The Submodule"
@@ -562,9 +553,8 @@ msgid "Unstage Lines From Commit"
 msgstr "Zeilen aus der Bereitstellung herausnehmen"
 
 #: git-gui.sh:3798 git-gui.sh:3809
-#, fuzzy
 msgid "Revert Lines"
-msgstr "Änderungen verwerfen"
+msgstr "Zeilenänderung umkehren"
 
 #: git-gui.sh:3800
 msgid "Unstage Line From Commit"
@@ -678,7 +668,7 @@ msgstr "Neue Änderungen von »%s« holen"
 #: lib/transport.tcl:18
 #, tcl-format
 msgid "remote prune %s"
-msgstr "Aufräumen von »%s«"
+msgstr "Extern aufräumen von »%s«"
 
 #: lib/transport.tcl:19
 #, tcl-format
@@ -687,22 +677,19 @@ msgstr "Übernahmezweige aufräumen und entfernen, die in »%s« gelöscht wurde
 
 #: lib/transport.tcl:25
 msgid "fetch all remotes"
-msgstr ""
+msgstr "Abrufen aller externen"
 
 #: lib/transport.tcl:26
-#, fuzzy
 msgid "Fetching new changes from all remotes"
-msgstr "Neue Änderungen von »%s« holen"
+msgstr "Neue Änderungen von allen externen anfordern"
 
 #: lib/transport.tcl:40
-#, fuzzy
 msgid "remote prune all remotes"
-msgstr "Aufräumen von »%s«"
+msgstr "Extern aufräumen aller externen Repositories"
 
 #: lib/transport.tcl:41
-#, fuzzy
 msgid "Pruning tracking branches deleted from all remotes"
-msgstr "Übernahmezweige aufräumen und entfernen, die in »%s« gelöscht wurden"
+msgstr "Übernahmezweige aufräumen und entfernen, die in allen externen Repositories gelöscht wurden"
 
 #: lib/transport.tcl:54 lib/transport.tcl:92 lib/transport.tcl:110
 #: lib/remote_add.tcl:162
@@ -743,15 +730,15 @@ msgstr "Lokale Zweige"
 
 #: lib/transport.tcl:162
 msgid "Destination Repository"
-msgstr "Ziel-Projektarchiv"
+msgstr "Ziel-Repository"
 
 #: lib/transport.tcl:165 lib/remote_branch_delete.tcl:51
 msgid "Remote:"
-msgstr "Externes Archiv:"
+msgstr "Externes Repository:"
 
 #: lib/transport.tcl:187 lib/remote_branch_delete.tcl:72
 msgid "Arbitrary Location:"
-msgstr "Adresse:"
+msgstr "Beliebige Adresse:"
 
 #: lib/transport.tcl:205
 msgid "Transfer Options"
@@ -773,7 +760,7 @@ msgstr "Mit Markierungen übertragen"
 #: lib/transport.tcl:229
 #, tcl-format
 msgid "%s (%s): Push"
-msgstr ""
+msgstr "%s (%s): Übertragen"
 
 #: lib/checkout_op.tcl:85
 #, tcl-format
@@ -841,10 +828,9 @@ msgid ""
 "\n"
 "The rescan will be automatically started now.\n"
 msgstr ""
-"Der letzte geladene Status stimmt nicht mehr mit dem Projektarchiv überein.\n"
+"Der letzte geladene Status stimmt nicht mehr mit dem Repository überein.\n"
 "\n"
-"Ein anderes Git-Programm hat das Projektarchiv seit dem letzten Laden "
-"geändert.  Vor dem Wechseln des lokalen Zweigs muss neu geladen werden.\n"
+"Ein anderes Git-Programm hat das Repository seit dem letzten Laden geändert.  Vor dem Wechseln des lokalen Zweigs muss neu geladen werden.\n"
 "\n"
 "Es wird gleich neu geladen.\n"
 
@@ -893,13 +879,11 @@ msgstr "Umgestellt auf »%s«."
 #: lib/checkout_op.tcl:536
 #, tcl-format
 msgid "Resetting '%s' to '%s' will lose the following commits:"
-msgstr "Zurücksetzen von »%s« nach »%s« wird folgende Versionen verwerfen:"
+msgstr "Zurücksetzen von »%s« nach »%s« wird folgenden Commit verwerfen:"
 
 #: lib/checkout_op.tcl:558
 msgid "Recovering lost commits may not be easy."
-msgstr ""
-"Verworfene Versionen können nur mit größerem Aufwand wiederhergestellt "
-"werden."
+msgstr "Verworfene Commits können nur mit größerem Aufwand wiederhergestellt werden."
 
 #: lib/checkout_op.tcl:563
 #, tcl-format
@@ -933,13 +917,13 @@ msgstr ""
 "Dies ist ein interner Programmfehler von %s. Programm wird jetzt abgebrochen."
 
 #: lib/remote_add.tcl:20
-#, fuzzy, tcl-format
+#, tcl-format
 msgid "%s (%s): Add Remote"
-msgstr "Externes Archiv hinzufügen"
+msgstr "%s (%s): Externes Repository hinzufügen"
 
 #: lib/remote_add.tcl:25
 msgid "Add New Remote"
-msgstr "Neues externes Archiv hinzufügen"
+msgstr "Neues externes Repository hinzufügen"
 
 #: lib/remote_add.tcl:30 lib/tools_dlg.tcl:37
 msgid "Add"
@@ -947,7 +931,7 @@ msgstr "Hinzufügen"
 
 #: lib/remote_add.tcl:39
 msgid "Remote Details"
-msgstr "Einzelheiten des externen Archivs"
+msgstr "Einzelheiten des externen Repository"
 
 #: lib/remote_add.tcl:41 lib/tools_dlg.tcl:51 lib/branch_create.tcl:44
 msgid "Name:"
@@ -967,7 +951,7 @@ msgstr "Gleich anfordern"
 
 #: lib/remote_add.tcl:69
 msgid "Initialize Remote Repository and Push"
-msgstr "Externes Archiv initialisieren und dahin versenden"
+msgstr "Externes Repository initialisieren und dahin versenden"
 
 #: lib/remote_add.tcl:75
 msgid "Do Nothing Else Now"
@@ -975,18 +959,17 @@ msgstr "Nichts tun"
 
 #: lib/remote_add.tcl:100
 msgid "Please supply a remote name."
-msgstr "Bitte geben Sie einen Namen des externen Archivs an."
+msgstr "Bitte geben Sie einen Namen des externen Repository an."
 
 #: lib/remote_add.tcl:113
 #, tcl-format
 msgid "'%s' is not an acceptable remote name."
-msgstr "»%s« ist kein zulässiger Name eines externen Archivs."
+msgstr "»%s« ist kein zulässiger Name eines externen Repository."
 
 #: lib/remote_add.tcl:124
 #, tcl-format
 msgid "Failed to add remote '%s' of location '%s'."
-msgstr ""
-"Fehler beim Hinzufügen des externen Archivs »%s« aus Herkunftsort »%s«."
+msgstr "Fehler beim Hinzufügen des externen Repository »%s« aus Adresse »%s«."
 
 #: lib/remote_add.tcl:133
 #, tcl-format
@@ -996,8 +979,7 @@ msgstr "»%s« anfordern"
 #: lib/remote_add.tcl:156
 #, tcl-format
 msgid "Do not know how to initialize repository at location '%s'."
-msgstr ""
-"Initialisieren eines externen Archivs an Adresse »%s« ist nicht möglich."
+msgstr "Initialisieren eines externen Repositories an Adresse »%s« ist nicht möglich."
 
 #: lib/remote_add.tcl:163
 #, tcl-format
@@ -1009,9 +991,9 @@ msgid "Starting..."
 msgstr "Starten..."
 
 #: lib/browser.tcl:27
-#, fuzzy, tcl-format
+#, tcl-format
 msgid "%s (%s): File Browser"
-msgstr "Datei-Browser"
+msgstr "%s (%s): Datei-Browser"
 
 #: lib/browser.tcl:132 lib/browser.tcl:149
 #, tcl-format
@@ -1023,9 +1005,9 @@ msgid "[Up To Parent]"
 msgstr "[Nach oben]"
 
 #: lib/browser.tcl:275
-#, fuzzy, tcl-format
+#, tcl-format
 msgid "%s (%s): Browse Branch Files"
-msgstr "Dateien des Zweigs durchblättern"
+msgstr "%s (%s): Dateien des Zweigs durchblättern"
 
 #: lib/browser.tcl:282
 msgid "Browse Branch Files"
@@ -1072,9 +1054,8 @@ msgid "files"
 msgstr "Dateien"
 
 #: lib/index.tcl:326
-#, fuzzy
 msgid "Unstaging selected files from commit"
-msgstr "Datei »%s« aus der Bereitstellung herausnehmen"
+msgstr ""
 
 #: lib/index.tcl:330
 #, tcl-format
@@ -1083,17 +1064,16 @@ msgstr "Datei »%s« aus der Bereitstellung herausnehmen"
 
 #: lib/index.tcl:369
 msgid "Ready to commit."
-msgstr "Bereit zum Eintragen."
+msgstr "Bereit zum Committen."
 
 #: lib/index.tcl:378
-#, fuzzy
 msgid "Adding selected files"
-msgstr "Änderungen in gewählten Dateien verwerfen"
+msgstr "Gewählte Dateien hinzufügen"
 
 #: lib/index.tcl:382
 #, tcl-format
 msgid "Adding %s"
-msgstr "»%s« hinzufügen..."
+msgstr "»%s« hinzufügen"
 
 #: lib/index.tcl:412
 #, tcl-format
@@ -1112,7 +1092,7 @@ msgstr "Änderungen in Datei »%s« verwerfen?"
 #: lib/index.tcl:508
 #, tcl-format
 msgid "Revert changes in these %i files?"
-msgstr "Änderungen in den gewählten %i Dateien verwerfen?"
+msgstr "Änderungen in diesen %i Dateien verwerfen?"
 
 #: lib/index.tcl:517
 msgid "Any unstaged changes will be permanently lost by the revert."
@@ -1135,31 +1115,29 @@ msgstr "Änderungen in den gewählten %i Dateien verwerfen?"
 
 #: lib/index.tcl:560
 msgid "Files will be permanently deleted."
-msgstr ""
+msgstr "Dateien werden endgültig gelöscht."
 
 #: lib/index.tcl:564
-#, fuzzy
 msgid "Delete Files"
-msgstr "Löschen"
+msgstr "Dateien löschen"
 
 #: lib/index.tcl:586
-#, fuzzy
 msgid "Deleting"
 msgstr "Löschen"
 
 #: lib/index.tcl:665
 msgid "Encountered errors deleting files:\n"
-msgstr ""
+msgstr "Fehler beim Löschen der Dateien:\n"
 
 #: lib/index.tcl:674
 #, tcl-format
 msgid "None of the %d selected files could be deleted."
-msgstr ""
+msgstr "Keine der %d gewählten Dateien konnten gelöscht werden."
 
 #: lib/index.tcl:679
 #, tcl-format
 msgid "%d of the %d selected files could not be deleted."
-msgstr ""
+msgstr "%d der %d gewählten Dateien konnten nicht gelöscht werden."
 
 #: lib/index.tcl:726
 msgid "Reverting selected files"
@@ -1171,9 +1149,9 @@ msgid "Reverting %s"
 msgstr "Änderungen in %s verwerfen"
 
 #: lib/branch_checkout.tcl:16
-#, fuzzy, tcl-format
+#, tcl-format
 msgid "%s (%s): Checkout Branch"
-msgstr "Auf Zweig umstellen"
+msgstr "%s (%s): Auf Zweig umstellen"
 
 #: lib/branch_checkout.tcl:21
 msgid "Checkout Branch"
@@ -1206,7 +1184,7 @@ msgstr "Versenden nach"
 
 #: lib/remote.tcl:218
 msgid "Remove Remote"
-msgstr "Externes Archiv entfernen"
+msgstr "Externes Repository entfernen"
 
 #: lib/remote.tcl:223
 msgid "Prune from"
@@ -1218,12 +1196,12 @@ msgstr "Anfordern von"
 
 #: lib/remote.tcl:249 lib/remote.tcl:253 lib/remote.tcl:258 lib/remote.tcl:264
 msgid "All"
-msgstr ""
+msgstr "Alle"
 
 #: lib/branch_rename.tcl:15
-#, fuzzy, tcl-format
+#, tcl-format
 msgid "%s (%s): Rename Branch"
-msgstr "Zweig umbenennen"
+msgstr "%s (%s): Zweig umbenennen"
 
 #: lib/branch_rename.tcl:23
 msgid "Rename Branch"
@@ -1291,7 +1269,7 @@ msgstr "Ungültige globale Zeichenkodierung »%s«"
 #: lib/option.tcl:19
 #, tcl-format
 msgid "Invalid repo encoding '%s'"
-msgstr "Ungültige Archiv-Zeichenkodierung »%s«"
+msgstr "Ungültige Repository-Zeichenkodierung »%s«"
 
 #: lib/option.tcl:119
 msgid "Restore Defaults"
@@ -1304,11 +1282,11 @@ msgstr "Speichern"
 #: lib/option.tcl:133
 #, tcl-format
 msgid "%s Repository"
-msgstr "Projektarchiv %s"
+msgstr "%s Repository"
 
 #: lib/option.tcl:134
 msgid "Global (All Repositories)"
-msgstr "Global (Alle Projektarchive)"
+msgstr "Global (Alle Repositories)"
 
 #: lib/option.tcl:140
 msgid "User Name"
@@ -1320,7 +1298,7 @@ msgstr "E-Mail-Adresse"
 
 #: lib/option.tcl:143
 msgid "Summarize Merge Commits"
-msgstr "Zusammenführungs-Versionen zusammenfassen"
+msgstr "Zusammenführungs-Commits zusammenfassen"
 
 #: lib/option.tcl:144
 msgid "Merge Verbosity"
@@ -1355,9 +1333,8 @@ msgid "Blame Copy Only On Changed Files"
 msgstr "Kopie-Annotieren nur bei geänderten Dateien"
 
 #: lib/option.tcl:153
-#, fuzzy
 msgid "Maximum Length of Recent Repositories List"
-msgstr "Zuletzt benutzte Projektarchive"
+msgstr "Anzahl Einträge in »Letzte Repositories«"
 
 #: lib/option.tcl:154
 msgid "Minimum Letters To Blame Copy On"
@@ -1377,7 +1354,7 @@ msgstr ""
 
 #: lib/option.tcl:158
 msgid "Commit Message Text Width"
-msgstr "Textbreite der Versionsbeschreibung"
+msgstr "Textbreite der Commit-Beschreibung"
 
 #: lib/option.tcl:159
 msgid "New Branch Name Template"
@@ -1407,7 +1384,7 @@ msgstr ""
 #: lib/database.tcl:57
 #, tcl-format
 msgid "%s:"
-msgstr ""
+msgstr "%s:"
 
 #: lib/option.tcl:210
 msgid "Change"
@@ -1577,9 +1554,9 @@ msgid "Merge tool failed."
 msgstr "Zusammenführungswerkzeug fehlgeschlagen."
 
 #: lib/tools_dlg.tcl:22
-#, fuzzy, tcl-format
+#, tcl-format
 msgid "%s (%s): Add Tool"
-msgstr "Werkzeug hinzufügen"
+msgstr "%s (%s): Werkzeug hinzufügen"
 
 #: lib/tools_dlg.tcl:28
 msgid "Add New Tool Command"
@@ -1641,9 +1618,9 @@ msgstr ""
 "%s"
 
 #: lib/tools_dlg.tcl:187
-#, fuzzy, tcl-format
+#, tcl-format
 msgid "%s (%s): Remove Tool"
-msgstr "Werkzeug entfernen"
+msgstr "%s (%s): Werkzeug entfernen"
 
 #: lib/tools_dlg.tcl:193
 msgid "Remove Tool Commands"
@@ -1655,12 +1632,12 @@ msgstr "Entfernen"
 
 #: lib/tools_dlg.tcl:231
 msgid "(Blue denotes repository-local tools)"
-msgstr "(Werkzeuge für lokales Archiv werden in Blau angezeigt)"
+msgstr "(Werkzeuge für lokales Repository werden in Blau angezeigt)"
 
 #: lib/tools_dlg.tcl:283
-#, fuzzy, tcl-format
+#, tcl-format
 msgid "%s (%s):"
-msgstr "Systemweit (%s)"
+msgstr "%s (%s):"
 
 #: lib/tools_dlg.tcl:292
 #, tcl-format
@@ -1689,16 +1666,16 @@ msgstr "Voriger"
 
 #: lib/search.tcl:52
 msgid "RegExp"
-msgstr ""
+msgstr "RegAusdruck"
 
 #: lib/search.tcl:54
 msgid "Case"
-msgstr ""
+msgstr "Groß/klein"
 
 #: lib/shortcut.tcl:8 lib/shortcut.tcl:43 lib/shortcut.tcl:75
-#, fuzzy, tcl-format
+#, tcl-format
 msgid "%s (%s): Create Desktop Icon"
-msgstr "Desktop-Icon erstellen"
+msgstr "%s (%s): Desktop-Icon erstellen"
 
 #: lib/shortcut.tcl:24 lib/shortcut.tcl:65
 msgid "Cannot write shortcut:"
@@ -1709,17 +1686,17 @@ msgid "Cannot write icon:"
 msgstr "Fehler beim Erstellen des Icons:"
 
 #: lib/remote_branch_delete.tcl:29
-#, fuzzy, tcl-format
+#, tcl-format
 msgid "%s (%s): Delete Branch Remotely"
-msgstr "Zweig in externem Archiv löschen"
+msgstr "%s (%s): Zweig in externem Repository löschen"
 
 #: lib/remote_branch_delete.tcl:34
 msgid "Delete Branch Remotely"
-msgstr "Zweig in externem Archiv löschen"
+msgstr "Zweig in externem Repository löschen"
 
 #: lib/remote_branch_delete.tcl:48
 msgid "From Repository"
-msgstr "In Projektarchiv"
+msgstr "In Repository"
 
 #: lib/remote_branch_delete.tcl:88
 msgid "Branches"
@@ -1757,10 +1734,7 @@ msgstr ""
 msgid ""
 "One or more of the merge tests failed because you have not fetched the "
 "necessary commits.  Try fetching from %s first."
-msgstr ""
-"Ein oder mehrere Zusammenführungen sind fehlgeschlagen, da Sie nicht die "
-"notwendigen Versionen vorher angefordert haben.  Sie sollten versuchen, "
-"zuerst von »%s« anzufordern."
+msgstr "Ein oder mehrere Zusammenführungen sind fehlgeschlagen, da Sie nicht die notwendigen Commits vorher angefordert haben.  Sie sollten versuchen, zuerst von »%s« anzufordern."
 
 #: lib/remote_branch_delete.tcl:208
 msgid "Please select one or more branches to delete."
@@ -1784,7 +1758,7 @@ msgstr "Zweige auf »%s« werden gelöscht"
 
 #: lib/remote_branch_delete.tcl:300
 msgid "No repository selected."
-msgstr "Kein Projektarchiv ausgewählt."
+msgstr "Kein Repository ausgewählt."
 
 #: lib/remote_branch_delete.tcl:305
 #, tcl-format
@@ -1797,7 +1771,7 @@ msgstr "Git Gui"
 
 #: lib/choose_repository.tcl:104 lib/choose_repository.tcl:427
 msgid "Create New Repository"
-msgstr "Neues Projektarchiv"
+msgstr "Repository neu erstellen"
 
 #: lib/choose_repository.tcl:110
 msgid "New..."
@@ -1805,7 +1779,7 @@ msgstr "Neu..."
 
 #: lib/choose_repository.tcl:117 lib/choose_repository.tcl:511
 msgid "Clone Existing Repository"
-msgstr "Projektarchiv klonen"
+msgstr "Repository klonen"
 
 #: lib/choose_repository.tcl:128
 msgid "Clone..."
@@ -1813,7 +1787,7 @@ msgstr "Klonen..."
 
 #: lib/choose_repository.tcl:135 lib/choose_repository.tcl:1105
 msgid "Open Existing Repository"
-msgstr "Projektarchiv öffnen"
+msgstr "Repository öffnen"
 
 #: lib/choose_repository.tcl:141
 msgid "Open..."
@@ -1821,17 +1795,17 @@ msgstr "Öffnen..."
 
 #: lib/choose_repository.tcl:154
 msgid "Recent Repositories"
-msgstr "Zuletzt benutzte Projektarchive"
+msgstr "Letzte Repositories"
 
 #: lib/choose_repository.tcl:164
 msgid "Open Recent Repository:"
-msgstr "Zuletzt benutztes Projektarchiv öffnen:"
+msgstr "Zuletzt benutztes Repository öffnen:"
 
 #: lib/choose_repository.tcl:331 lib/choose_repository.tcl:338
 #: lib/choose_repository.tcl:345
 #, tcl-format
 msgid "Failed to create repository %s:"
-msgstr "Projektarchiv »%s« konnte nicht erstellt werden:"
+msgstr "Repository »%s« konnte nicht erstellt werden:"
 
 #: lib/choose_repository.tcl:422 lib/branch_create.tcl:33
 msgid "Create"
@@ -1844,7 +1818,7 @@ msgstr "Verzeichnis:"
 #: lib/choose_repository.tcl:462 lib/choose_repository.tcl:588
 #: lib/choose_repository.tcl:1139
 msgid "Git Repository"
-msgstr "Git Projektarchiv"
+msgstr "Git Repository"
 
 #: lib/choose_repository.tcl:487
 #, tcl-format
@@ -1862,7 +1836,7 @@ msgstr "Klonen"
 
 #: lib/choose_repository.tcl:519
 msgid "Source Location:"
-msgstr "Herkunft:"
+msgstr "Herkunfts-Adresse:"
 
 #: lib/choose_repository.tcl:528
 msgid "Target Directory:"
@@ -1893,20 +1867,20 @@ msgstr ""
 #: lib/choose_repository.tcl:1145 lib/choose_repository.tcl:1153
 #, tcl-format
 msgid "Not a Git repository: %s"
-msgstr "Kein Git-Projektarchiv in »%s« gefunden."
+msgstr "Kein Git-Repository: %s"
 
 #: lib/choose_repository.tcl:630
 msgid "Standard only available for local repository."
-msgstr "Standard ist nur für lokale Projektarchive verfügbar."
+msgstr "Standard ist nur für lokale Repositories verfügbar."
 
 #: lib/choose_repository.tcl:634
 msgid "Shared only available for local repository."
-msgstr "Verknüpft ist nur für lokale Projektarchive verfügbar."
+msgstr "Verknüpft ist nur für lokale Repositories verfügbar."
 
 #: lib/choose_repository.tcl:655
 #, tcl-format
 msgid "Location %s already exists."
-msgstr "Projektarchiv »%s« existiert bereits."
+msgstr "Adresse »%s« existiert bereits."
 
 #: lib/choose_repository.tcl:666
 msgid "Failed to configure origin"
@@ -2004,7 +1978,7 @@ msgstr "Kein voreingestellter Zweig gefunden."
 #: lib/choose_repository.tcl:971
 #, tcl-format
 msgid "Cannot resolve %s as a commit."
-msgstr "»%s« wurde nicht als Version gefunden."
+msgstr "»%s« wurde nicht als Commit gefunden."
 
 #: lib/choose_repository.tcl:998
 msgid "Creating working directory"
@@ -2025,38 +1999,37 @@ msgstr ""
 
 #: lib/choose_repository.tcl:1110
 msgid "Repository:"
-msgstr "Projektarchiv:"
+msgstr "Repository:"
 
 #: lib/choose_repository.tcl:1159
 #, tcl-format
 msgid "Failed to open repository %s:"
-msgstr "Projektarchiv »%s« konnte nicht geöffnet werden."
+msgstr "Repository »%s« konnte nicht geöffnet werden."
 
 #: lib/about.tcl:26
 msgid "git-gui - a graphical user interface for Git."
 msgstr "git-gui - eine grafische Oberfläche für Git."
 
 #: lib/blame.tcl:74
-#, fuzzy, tcl-format
+#, tcl-format
 msgid "%s (%s): File Viewer"
-msgstr "Datei-Browser"
+msgstr "%s (%s): Datei-Browser"
 
 #: lib/blame.tcl:80
 msgid "Commit:"
-msgstr "Version:"
+msgstr "Commit:"
 
 #: lib/blame.tcl:282
 msgid "Copy Commit"
-msgstr "Version kopieren"
+msgstr "Commit kopieren"
 
 #: lib/blame.tcl:286
 msgid "Find Text..."
 msgstr "Text suchen..."
 
 #: lib/blame.tcl:290
-#, fuzzy
 msgid "Goto Line..."
-msgstr "Klonen..."
+msgstr "Gehe zu Zeile..."
 
 #: lib/blame.tcl:299
 msgid "Do Full Copy Detection"
@@ -2068,7 +2041,7 @@ msgstr "Historien-Kontext anzeigen"
 
 #: lib/blame.tcl:306
 msgid "Blame Parent Commit"
-msgstr "Elternversion annotieren"
+msgstr "Elterncommit annotieren"
 
 #: lib/blame.tcl:468
 #, tcl-format
@@ -2113,7 +2086,7 @@ msgstr "Autor:"
 
 #: lib/blame.tcl:1014
 msgid "Committer:"
-msgstr "Eintragender:"
+msgstr "Committer:"
 
 #: lib/blame.tcl:1019
 msgid "Original File:"
@@ -2121,15 +2094,15 @@ msgstr "Ursprüngliche Datei:"
 
 #: lib/blame.tcl:1067
 msgid "Cannot find HEAD commit:"
-msgstr "Zweigspitze (»HEAD«) kann nicht gefunden werden:"
+msgstr "Zweigspitze (»HEAD commit«) kann nicht gefunden werden:"
 
 #: lib/blame.tcl:1122
 msgid "Cannot find parent commit:"
-msgstr "Elternversion kann nicht gefunden werden:"
+msgstr "Elterncommit kann nicht gefunden werden:"
 
 #: lib/blame.tcl:1137
 msgid "Unable to display parent"
-msgstr "Elternversion kann nicht angezeigt werden"
+msgstr "Elterncommit kann nicht angezeigt werden"
 
 #: lib/blame.tcl:1138 lib/diff.tcl:345
 msgid "Error loading diff:"
@@ -2180,14 +2153,14 @@ msgid ""
 "REMOTE:\n"
 msgstr ""
 "LOKAL: gelöscht\n"
-"ANDERES:\n"
+"EXTERN:\n"
 
 #: lib/diff.tcl:148
 msgid ""
 "REMOTE: deleted\n"
 "LOCAL:\n"
 msgstr ""
-"ANDERES: gelöscht\n"
+"EXTERN: gelöscht\n"
 "LOKAL:\n"
 
 #: lib/diff.tcl:155
@@ -2196,7 +2169,7 @@ msgstr "LOKAL:\n"
 
 #: lib/diff.tcl:158
 msgid "REMOTE:\n"
-msgstr "ANDERES:\n"
+msgstr "EXTERN:\n"
 
 #: lib/diff.tcl:220 lib/diff.tcl:344
 #, tcl-format
@@ -2209,7 +2182,7 @@ msgstr "Fehler beim Laden der Datei:"
 
 #: lib/diff.tcl:227
 msgid "Git Repository (subproject)"
-msgstr "Git-Projektarchiv (Unterprojekt)"
+msgstr "Git-Repository (Subprojekt)"
 
 #: lib/diff.tcl:239
 msgid "* Binary file (not showing content)."
@@ -2241,9 +2214,8 @@ msgstr ""
 "Fehler beim Herausnehmen des gewählten Kontexts aus der Bereitstellung."
 
 #: lib/diff.tcl:591
-#, fuzzy
 msgid "Failed to revert selected hunk."
-msgstr "Fehler beim Bereitstellen des gewählten Kontexts."
+msgstr "Fehler beim Umkehren des gewählten Kontexts."
 
 #: lib/diff.tcl:594
 msgid "Failed to stage selected hunk."
@@ -2254,18 +2226,16 @@ msgid "Failed to unstage selected line."
 msgstr "Fehler beim Herausnehmen der gewählten Zeile aus der Bereitstellung."
 
 #: lib/diff.tcl:696
-#, fuzzy
 msgid "Failed to revert selected line."
-msgstr "Fehler beim Bereitstellen der gewählten Zeile."
+msgstr "Fehler beim Umkehren der gewählten Zeile."
 
 #: lib/diff.tcl:700
 msgid "Failed to stage selected line."
 msgstr "Fehler beim Bereitstellen der gewählten Zeile."
 
 #: lib/diff.tcl:889
-#, fuzzy
 msgid "Failed to undo last revert."
-msgstr "Aktualisieren von »%s« fehlgeschlagen."
+msgstr "Fehler beim Rückgängigmachen des letzten Umkehren-Commits"
 
 #: lib/sshkey.tcl:34
 msgid "No keys found."
@@ -2317,9 +2287,9 @@ msgid "Your key is in: %s"
 msgstr "Ihr Schlüssel ist abgelegt in: %s"
 
 #: lib/branch_create.tcl:23
-#, fuzzy, tcl-format
+#, tcl-format
 msgid "%s (%s): Create Branch"
-msgstr "Zweig erstellen"
+msgstr "%s (%s): Zweig erstellen"
 
 #: lib/branch_create.tcl:28
 msgid "Create New Branch"
@@ -2360,7 +2330,7 @@ msgstr "Bitte wählen Sie einen Übernahmezweig."
 #: lib/branch_create.tcl:141
 #, tcl-format
 msgid "Tracking branch %s is not a branch in the remote repository."
-msgstr "Übernahmezweig »%s« ist kein Zweig im externen Projektarchiv."
+msgstr "Übernahmezweig »%s« ist kein Zweig im externen Repository"
 
 #: lib/console.tcl:59
 msgid "Working... please wait..."
@@ -2376,11 +2346,11 @@ msgstr "Fehler: Kommando fehlgeschlagen"
 
 #: lib/line.tcl:17
 msgid "Goto Line:"
-msgstr ""
+msgstr "Gehe zu Zeile:"
 
 #: lib/line.tcl:23
 msgid "Go"
-msgstr ""
+msgstr "Gehe"
 
 #: lib/choose_rev.tcl:52
 msgid "This Detached Checkout"
@@ -2430,10 +2400,9 @@ msgid ""
 "You are about to create the initial commit.  There is no commit before this "
 "to amend.\n"
 msgstr ""
-"Keine Version zur Nachbesserung vorhanden.\n"
+"Kein Commit zur Nachbesserung vorhanden.\n"
 "\n"
-"Sie sind dabei, die erste Version zu übertragen. Es gibt keine existierende "
-"Version, die Sie nachbessern könnten.\n"
+"Sie sind dabei, den ersten Commit zu erstellen. Es gibt keinen existierenden Commit, den Sie nachbessern könnten.\n"
 
 #: lib/commit.tcl:18
 msgid ""
@@ -2443,16 +2412,13 @@ msgid ""
 "completed.  You cannot amend the prior commit unless you first abort the "
 "current merge activity.\n"
 msgstr ""
-"Nachbesserung währen Zusammenführung nicht möglich.\n"
+"Nachbesserung bei Zusammenführung nicht möglich.\n"
 "\n"
-"Sie haben das Zusammenführen von Versionen angefangen, aber noch nicht "
-"beendet. Sie können keine vorige Übertragung nachbessern, solange eine "
-"unfertige Zusammenführung existiert. Dazu müssen Sie die Zusammenführung "
-"beenden oder abbrechen.\n"
+"Sie haben das Zusammenführen von Commits angefangen, aber noch nicht beendet. Sie können keinen vorigen Commit nachbessern, solange eine unfertige Zusammenführung existiert. Dazu müssen Sie die Zusammenführung beenden oder abbrechen.\n"
 
 #: lib/commit.tcl:56
 msgid "Error loading commit data for amend:"
-msgstr "Fehler beim Laden der Versionsdaten für Nachbessern:"
+msgstr "Fehler beim Laden der Commitdaten für Nachbessern:"
 
 #: lib/commit.tcl:83
 msgid "Unable to obtain your identity:"
@@ -2476,10 +2442,9 @@ msgid ""
 "\n"
 "The rescan will be automatically started now.\n"
 msgstr ""
-"Der letzte geladene Status stimmt nicht mehr mit dem Projektarchiv überein.\n"
+"Der letzte geladene Status stimmt nicht mehr mit dem Repository überein.\n"
 "\n"
-"Ein anderes Git-Programm hat das Projektarchiv seit dem letzten Laden "
-"geändert.  Vor dem Eintragen einer neuen Version muss neu geladen werden.\n"
+"Ein anderes Git-Programm hat das Repository seit dem letzten Laden geändert.  Vor dem nächsten Commit muss neu geladen werden.\n"
 "\n"
 "Es wird gleich neu geladen.\n"
 
@@ -2491,10 +2456,9 @@ msgid ""
 "File %s has merge conflicts.  You must resolve them and stage the file "
 "before committing.\n"
 msgstr ""
-"Nicht zusammengeführte Dateien können nicht eingetragen werden.\n"
+"Nicht zusammengeführte Dateien können nicht committet werden.\n"
 "\n"
-"Die Datei »%s« hat noch nicht aufgelöste Zusammenführungs-Konflikte. Sie "
-"müssen diese Konflikte auflösen, bevor Sie eintragen können.\n"
+"Die Datei »%s« hat noch nicht aufgelöste Zusammenführungs-Konflikte. Sie müssen diese Konflikte auflösen und die Dateien in die Bereitstellung hinzufügen, bevor Sie committen können.\n"
 
 #: lib/commit.tcl:190
 #, tcl-format
@@ -2505,7 +2469,7 @@ msgid ""
 msgstr ""
 "Unbekannter Dateizustand »%s«.\n"
 "\n"
-"Datei »%s« kann nicht eingetragen werden.\n"
+"Datei »%s« kann nicht committet werden.\n"
 
 #: lib/commit.tcl:198
 msgid ""
@@ -2513,9 +2477,9 @@ msgid ""
 "\n"
 "You must stage at least 1 file before you can commit.\n"
 msgstr ""
-"Keine Änderungen vorhanden, die eingetragen werden könnten.\n"
+"Keine Änderungen vorhanden, die committet werden könnten.\n"
 "\n"
-"Sie müssen mindestens eine Datei bereitstellen, bevor Sie eintragen können.\n"
+"Sie müssen mindestens eine Datei bereitstellen, bevor Sie committen können.\n"
 
 #: lib/commit.tcl:213
 msgid ""
@@ -2539,11 +2503,11 @@ msgstr ""
 
 #: lib/commit.tcl:244
 msgid "Calling pre-commit hook..."
-msgstr "Aufrufen der Vor-Eintragen-Kontrolle (»pre-commit hook«)..."
+msgstr "Aufrufen des »pre-commit hook«..."
 
 #: lib/commit.tcl:259
 msgid "Commit declined by pre-commit hook."
-msgstr "Eintragen abgelehnt durch Vor-Eintragen-Kontrolle (»pre-commit hook«)."
+msgstr "Committen abgelehnt durch »pre-commit hook«."
 
 #: lib/commit.tcl:278
 msgid ""
@@ -2557,18 +2521,15 @@ msgstr ""
 
 #: lib/commit.tcl:299
 msgid "Calling commit-msg hook..."
-msgstr ""
-"Aufrufen der Versionsbeschreibungs-Kontrolle (»commit-message hook«)..."
+msgstr "Aufrufen des »commit-message hook«..."
 
 #: lib/commit.tcl:314
 msgid "Commit declined by commit-msg hook."
-msgstr ""
-"Eintragen abgelehnt durch Versionsbeschreibungs-Kontrolle (»commit-message "
-"hook«)."
+msgstr "Committen abgelehnt durch »commit-message hook«."
 
 #: lib/commit.tcl:327
 msgid "Committing changes..."
-msgstr "Änderungen eintragen..."
+msgstr "Änderungen committen..."
 
 #: lib/commit.tcl:344
 msgid "write-tree failed:"
@@ -2576,7 +2537,7 @@ msgstr "write-tree fehlgeschlagen:"
 
 #: lib/commit.tcl:345 lib/commit.tcl:395 lib/commit.tcl:422
 msgid "Commit failed."
-msgstr "Eintragen fehlgeschlagen."
+msgstr "Committen fehlgeschlagen."
 
 #: lib/commit.tcl:362
 #, tcl-format
@@ -2591,16 +2552,15 @@ msgid ""
 "\n"
 "A rescan will be automatically started now.\n"
 msgstr ""
-"Keine Änderungen einzutragen.\n"
+"Keine Änderungen zum committen.\n"
 "\n"
-"Es gibt keine geänderte Datei bei dieser Version und es wurde auch nichts "
-"zusammengeführt.\n"
+"Es gibt keine geänderte Datei in diesem Commit und es wurde auch nichts zusammengeführt.\n"
 "\n"
 "Das Arbeitsverzeichnis wird daher jetzt neu geladen.\n"
 
 #: lib/commit.tcl:374
 msgid "No changes to commit."
-msgstr "Keine Änderungen, die eingetragen werden können."
+msgstr "Keine Änderungen, die committet werden können."
 
 #: lib/commit.tcl:394
 msgid "commit-tree failed:"
@@ -2616,9 +2576,9 @@ msgid "Created commit %s: %s"
 msgstr "Version %s übertragen: %s"
 
 #: lib/branch_delete.tcl:16
-#, fuzzy, tcl-format
+#, tcl-format
 msgid "%s (%s): Delete Branch"
-msgstr "Zweig löschen"
+msgstr "%s (%s): Zweig löschen"
 
 #: lib/branch_delete.tcl:21
 msgid "Delete Local Branch"
@@ -2640,7 +2600,7 @@ msgstr "Folgende Zweige sind noch nicht mit »%s« zusammengeführt:"
 #: lib/branch_delete.tcl:131
 #, tcl-format
 msgid " - %s:"
-msgstr ""
+msgstr " - %s:"
 
 #: lib/branch_delete.tcl:141
 #, tcl-format
@@ -2685,9 +2645,9 @@ msgid "Garbage files"
 msgstr "Dateien im Mülleimer"
 
 #: lib/database.tcl:66
-#, fuzzy, tcl-format
+#, tcl-format
 msgid "%s (%s): Database Statistics"
-msgstr "Datenbankstatistik"
+msgstr "%s (%s): Datenbankstatistik"
 
 #: lib/database.tcl:72
 msgid "Compressing the object database"
@@ -2707,37 +2667,35 @@ msgid ""
 "\n"
 "Compress the database now?"
 msgstr ""
-"Dieses Projektarchiv enthält ungefähr %i nicht verknüpfte Objekte.\n"
+"Dieses Repository enthält ungefähr %i nicht verknüpfte Objekte.\n"
 "\n"
-"Für eine optimale Performance wird empfohlen, die Datenbank des "
-"Projektarchivs zu komprimieren.\n"
+"Für eine optimale Performance wird empfohlen, die Datenbank des Repository zu komprimieren.\n"
 "\n"
 "Soll die Datenbank jetzt komprimiert werden?"
 
 #: lib/error.tcl:20
-#, fuzzy, tcl-format
+#, tcl-format
 msgid "%s: error"
-msgstr "Fehler"
+msgstr "%s: Fehler"
 
 #: lib/error.tcl:36
-#, fuzzy, tcl-format
+#, tcl-format
 msgid "%s: warning"
-msgstr "Warnung"
+msgstr "%s: Warnung"
 
 #: lib/error.tcl:80
-#, fuzzy, tcl-format
+#, tcl-format
 msgid "%s hook failed:"
-msgstr "Werkzeug fehlgeschlagen: %s"
+msgstr "%s hook fehlgeschlagen:"
 
 #: lib/error.tcl:96
 msgid "You must correct the above errors before committing."
-msgstr ""
-"Sie müssen die obigen Fehler zuerst beheben, bevor Sie eintragen können."
+msgstr "Sie müssen die obigen Fehler zuerst beheben, bevor Sie committen können."
 
 #: lib/error.tcl:116
 #, tcl-format
 msgid "%s (%s): error"
-msgstr ""
+msgstr "%s (%s): Fehler"
 
 #: lib/merge.tcl:13
 msgid ""
@@ -2747,8 +2705,7 @@ msgid ""
 msgstr ""
 "Zusammenführen kann nicht gleichzeitig mit Nachbessern durchgeführt werden.\n"
 "\n"
-"Sie müssen zuerst die Nachbesserungs-Version abschließen, bevor Sie "
-"zusammenführen können.\n"
+"Sie müssen zuerst den Nachbesserungs-Commit abschließen, bevor Sie zusammenführen können.\n"
 
 #: lib/merge.tcl:27
 msgid ""
@@ -2759,10 +2716,9 @@ msgid ""
 "\n"
 "The rescan will be automatically started now.\n"
 msgstr ""
-"Der letzte geladene Status stimmt nicht mehr mit dem Projektarchiv überein.\n"
+"Der letzte geladene Status stimmt nicht mehr mit dem Repository überein.\n"
 "\n"
-"Ein anderes Git-Programm hat das Projektarchiv seit dem letzten Laden "
-"geändert.  Vor einem Zusammenführen muss neu geladen werden.\n"
+"Ein anderes Git-Programm hat das Repository seit dem letzten Laden geändert.  Vor einem Zusammenführen muss neu geladen werden.\n"
 "\n"
 "Es wird gleich neu geladen.\n"
 
@@ -2778,10 +2734,9 @@ msgid ""
 msgstr ""
 "Zusammenführung mit Konflikten.\n"
 "\n"
-"Die Datei »%s« enthält Konflikte beim Zusammenführen. Sie müssen diese "
-"Konflikte per Hand auflösen. Anschließend müssen Sie die Datei wieder "
-"bereitstellen und eintragen, um die Zusammenführung abzuschließen. Erst "
-"danach kann eine neue Zusammenführung begonnen werden.\n"
+"Die Datei »%s« enthält Konflikte beim Zusammenführen.\n"
+"\n"
+"Sie müssen diese Konflikte per Hand auflösen. Anschließend müssen Sie die Datei wieder bereitstellen und committen, um die Zusammenführung abzuschließen. Erst danach kann eine neue Zusammenführung begonnen werden.\n"
 
 #: lib/merge.tcl:55
 #, tcl-format
@@ -2795,10 +2750,9 @@ msgid ""
 msgstr ""
 "Es liegen Änderungen vor.\n"
 "\n"
-"Die Datei »%s« wurde geändert.  Sie sollten zuerst die bereitgestellte "
-"Version abschließen, bevor Sie eine Zusammenführung beginnen.  Mit dieser "
-"Reihenfolge können Sie mögliche Konflikte beim Zusammenführen wesentlich "
-"einfacher beheben oder abbrechen.\n"
+"Die Datei »%s« wurde geändert.\n"
+"\n"
+"Sie sollten zuerst den bereitgestellten Commit abschließen, bevor Sie eine Zusammenführung beginnen.  Mit dieser Reihenfolge können Sie mögliche Konflikte beim Zusammenführen wesentlich einfacher beheben oder abbrechen.\n"
 
 #: lib/merge.tcl:108
 #, tcl-format
@@ -2821,7 +2775,7 @@ msgstr "Zusammenführen fehlgeschlagen. Konfliktauflösung ist notwendig."
 #: lib/merge.tcl:156
 #, tcl-format
 msgid "%s (%s): Merge"
-msgstr ""
+msgstr "%s (%s): Zusammenführen"
 
 #: lib/merge.tcl:164
 #, tcl-format
@@ -2840,7 +2794,7 @@ msgid ""
 msgstr ""
 "Abbruch der Nachbesserung ist nicht möglich.\n"
 "\n"
-"Sie müssen die Nachbesserung der Version abschließen.\n"
+"Sie müssen die Nachbesserung diese Commits abschließen.\n"
 
 #: lib/merge.tcl:228
 msgid ""
@@ -2852,8 +2806,7 @@ msgid ""
 msgstr ""
 "Zusammenführen abbrechen?\n"
 "\n"
-"Wenn Sie abbrechen, gehen alle noch nicht eingetragenen Änderungen "
-"verloren.\n"
+"Wenn Sie abbrechen, gehen alle noch nicht committeten Änderungen verloren.\n"
 "\n"
 "Zusammenführen jetzt abbrechen?"
 
@@ -2867,8 +2820,7 @@ msgid ""
 msgstr ""
 "Änderungen zurücksetzen?\n"
 "\n"
-"Wenn Sie zurücksetzen, gehen alle noch nicht eingetragenen Änderungen "
-"verloren.\n"
+"Wenn Sie zurücksetzen, gehen alle noch nicht committeten Änderungen verloren.\n"
 "\n"
 "Änderungen jetzt zurücksetzen?"
 
@@ -2887,12 +2839,3 @@ msgstr "Abbruch fehlgeschlagen."
 #: lib/merge.tcl:279
 msgid "Abort completed.  Ready."
 msgstr "Abbruch durchgeführt. Bereit."
-
-#~ msgid "Displaying only %s of %s files."
-#~ msgstr "Nur %s von %s Dateien werden angezeigt."
-
-#~ msgid "New Commit"
-#~ msgstr "Neue Version"
-
-#~ msgid "Case-Sensitive"
-#~ msgstr "Groß-/Kleinschreibung unterscheiden"
diff --git a/po/glossary/de.po b/po/glossary/de.po
index 5af06bf4c1..ed392748c2 100644
--- a/po/glossary/de.po
+++ b/po/glossary/de.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: git-gui glossary\n"
 "POT-Creation-Date: 2020-01-13 21:40+0100\n"
-"PO-Revision-Date: 2020-01-13 21:53+0100\n"
+"PO-Revision-Date: 2020-01-13 22:30+0100\n"
 "Last-Translator: Christian Stimming <stimming@tuhh.de>\n"
 "Language-Team: German \n"
 "Language: de_DE\n"
@@ -67,15 +67,11 @@ msgstr "klonen"
 
 #. "A single point in the git history."
 msgid "commit [noun]"
-msgstr ""
-"Version; Eintragung; Änderung (Buchung?, Eintragung?, Übertragung?, "
-"Sendung?, Übergabe?, Einspielung?, Ablagevorgang?)"
+msgstr "Commit (Version?)"
 
 #. "The action of storing a new snapshot of the project's state in the git history."
 msgid "commit [verb]"
-msgstr ""
-"eintragen (TortoiseSVN: übertragen; Source Safe: einchecken; senden?, "
-"übergeben?, einspielen?, einpflegen?, ablegen?)"
+msgstr "committen (eintragen?, TortoiseSVN: übertragen; Source Safe: einchecken)"
 
 #. ""
 msgid "diff [noun]"
@@ -135,7 +131,7 @@ msgstr "Extern (Andere?, Gegenseite?, Entfernte?, Server?)"
 
 #. "A collection of refs (?) together with an object database containing all objects which are reachable from the refs... (oops, you've lost me here. Again, please an explanation for mere mortals?)"
 msgid "repository"
-msgstr "Projektarchiv"
+msgstr "Repository"
 
 #. ""
 msgid "reset"
@@ -143,12 +139,11 @@ msgstr "zurücksetzen (zurückkehren?)"
 
 #. ""
 msgid "revert"
-msgstr ""
-"verwerfen (bei git-reset), revidieren (bei git-revert, also mit neuem commit)"
+msgstr "verwerfen (bei git-reset bzw. checkout), umkehren (bei git-revert, also mit neuem commit)"
 
 #. "A particular state of files and directories which was stored in the object database."
 msgid "revision"
-msgstr "Version (TortoiseSVN: Revision; Source Safe: Version)"
+msgstr "Version (aber was macht das Wort revision hier im Git?? TortoiseSVN: Revision; Source Safe: Version)"
 
 #. ""
 msgid "sign off"
@@ -223,9 +218,8 @@ msgid "cleanup"
 msgstr ""
 
 #. "a message that gets attached with any commit"
-#, fuzzy
 msgid "commit message"
-msgstr "Beschreibung (Meldung?, Nachricht?; Source Safe: Kommentar)"
+msgstr "Commit-Beschreibung (Meldung?, Nachricht?; Source Safe: Kommentar)"
 
 #. "a commit that precedes the current one in git's graph of commits (not necessarily directly)"
 msgid "descendant"
-- 
gitgitgadget


^ permalink raw reply related	[relevance 1%]

* [PATCH v4 2/6] t1300: fix over-indented HERE-DOCs
  @ 2020-01-24  0:21  6%   ` Matthew Rogers via GitGitGadget
    1 sibling, 0 replies; 200+ results
From: Matthew Rogers via GitGitGadget @ 2020-01-24  0:21 UTC (permalink / raw)
  To: git; +Cc: Matthew Rogers, Matthew Rogers

From: Matthew Rogers <mattr94@gmail.com>

Prepare for the following patches by removing extraneous indents from
HERE-DOCs used in config tests.

Signed-off-by: Matthew Rogers <mattr94@gmail.com>
---
 t/t1300-config.sh | 168 +++++++++++++++++++++++-----------------------
 1 file changed, 84 insertions(+), 84 deletions(-)

diff --git a/t/t1300-config.sh b/t/t1300-config.sh
index 983a0a1583..e8b4575758 100755
--- a/t/t1300-config.sh
+++ b/t/t1300-config.sh
@@ -1191,47 +1191,47 @@ test_expect_success 'old-fashioned settings are case insensitive' '
 	test_when_finished "rm -f testConfig testConfig_expect testConfig_actual" &&
 
 	cat >testConfig_actual <<-EOF &&
-		[V.A]
-		r = value1
+	[V.A]
+	r = value1
 	EOF
 	q_to_tab >testConfig_expect <<-EOF &&
-		[V.A]
-		Qr = value2
+	[V.A]
+	Qr = value2
 	EOF
 	git config -f testConfig_actual "v.a.r" value2 &&
 	test_cmp testConfig_expect testConfig_actual &&
 
 	cat >testConfig_actual <<-EOF &&
-		[V.A]
-		r = value1
+	[V.A]
+	r = value1
 	EOF
 	q_to_tab >testConfig_expect <<-EOF &&
-		[V.A]
-		QR = value2
+	[V.A]
+	QR = value2
 	EOF
 	git config -f testConfig_actual "V.a.R" value2 &&
 	test_cmp testConfig_expect testConfig_actual &&
 
 	cat >testConfig_actual <<-EOF &&
-		[V.A]
-		r = value1
+	[V.A]
+	r = value1
 	EOF
 	q_to_tab >testConfig_expect <<-EOF &&
-		[V.A]
-		r = value1
-		Qr = value2
+	[V.A]
+	r = value1
+	Qr = value2
 	EOF
 	git config -f testConfig_actual "V.A.r" value2 &&
 	test_cmp testConfig_expect testConfig_actual &&
 
 	cat >testConfig_actual <<-EOF &&
-		[V.A]
-		r = value1
+	[V.A]
+	r = value1
 	EOF
 	q_to_tab >testConfig_expect <<-EOF &&
-		[V.A]
-		r = value1
-		Qr = value2
+	[V.A]
+	r = value1
+	Qr = value2
 	EOF
 	git config -f testConfig_actual "v.A.r" value2 &&
 	test_cmp testConfig_expect testConfig_actual
@@ -1241,26 +1241,26 @@ test_expect_success 'setting different case sensitive subsections ' '
 	test_when_finished "rm -f testConfig testConfig_expect testConfig_actual" &&
 
 	cat >testConfig_actual <<-EOF &&
-		[V "A"]
-		R = v1
-		[K "E"]
-		Y = v1
-		[a "b"]
-		c = v1
-		[d "e"]
-		f = v1
+	[V "A"]
+	R = v1
+	[K "E"]
+	Y = v1
+	[a "b"]
+	c = v1
+	[d "e"]
+	f = v1
 	EOF
 	q_to_tab >testConfig_expect <<-EOF &&
-		[V "A"]
-		Qr = v2
-		[K "E"]
-		Qy = v2
-		[a "b"]
-		Qc = v2
-		[d "e"]
-		f = v1
-		[d "E"]
-		Qf = v2
+	[V "A"]
+	Qr = v2
+	[K "E"]
+	Qy = v2
+	[a "b"]
+	Qc = v2
+	[d "e"]
+	f = v1
+	[d "E"]
+	Qf = v2
 	EOF
 	# exact match
 	git config -f testConfig_actual a.b.c v2 &&
@@ -1622,40 +1622,40 @@ test_expect_success 'set up --show-origin tests' '
 	INCLUDE_DIR="$HOME/include" &&
 	mkdir -p "$INCLUDE_DIR" &&
 	cat >"$INCLUDE_DIR"/absolute.include <<-\EOF &&
-		[user]
-			absolute = include
+	[user]
+		absolute = include
 	EOF
 	cat >"$INCLUDE_DIR"/relative.include <<-\EOF &&
-		[user]
-			relative = include
+	[user]
+		relative = include
 	EOF
 	cat >"$HOME"/.gitconfig <<-EOF &&
-		[user]
-			global = true
-			override = global
-		[include]
-			path = "$INCLUDE_DIR/absolute.include"
+	[user]
+		global = true
+		override = global
+	[include]
+		path = "$INCLUDE_DIR/absolute.include"
 	EOF
 	cat >.git/config <<-\EOF
-		[user]
-			local = true
-			override = local
-		[include]
-			path = ../include/relative.include
+	[user]
+		local = true
+		override = local
+	[include]
+		path = ../include/relative.include
 	EOF
 '
 
 test_expect_success '--show-origin with --list' '
 	cat >expect <<-EOF &&
-		file:$HOME/.gitconfig	user.global=true
-		file:$HOME/.gitconfig	user.override=global
-		file:$HOME/.gitconfig	include.path=$INCLUDE_DIR/absolute.include
-		file:$INCLUDE_DIR/absolute.include	user.absolute=include
-		file:.git/config	user.local=true
-		file:.git/config	user.override=local
-		file:.git/config	include.path=../include/relative.include
-		file:.git/../include/relative.include	user.relative=include
-		command line:	user.cmdline=true
+	file:$HOME/.gitconfig	user.global=true
+	file:$HOME/.gitconfig	user.override=global
+	file:$HOME/.gitconfig	include.path=$INCLUDE_DIR/absolute.include
+	file:$INCLUDE_DIR/absolute.include	user.absolute=include
+	file:.git/config	user.local=true
+	file:.git/config	user.override=local
+	file:.git/config	include.path=../include/relative.include
+	file:.git/../include/relative.include	user.relative=include
+	command line:	user.cmdline=true
 	EOF
 	git -c user.cmdline=true config --list --show-origin >output &&
 	test_cmp expect output
@@ -1663,16 +1663,16 @@ test_expect_success '--show-origin with --list' '
 
 test_expect_success '--show-origin with --list --null' '
 	cat >expect <<-EOF &&
-		file:$HOME/.gitconfigQuser.global
-		trueQfile:$HOME/.gitconfigQuser.override
-		globalQfile:$HOME/.gitconfigQinclude.path
-		$INCLUDE_DIR/absolute.includeQfile:$INCLUDE_DIR/absolute.includeQuser.absolute
-		includeQfile:.git/configQuser.local
-		trueQfile:.git/configQuser.override
-		localQfile:.git/configQinclude.path
-		../include/relative.includeQfile:.git/../include/relative.includeQuser.relative
-		includeQcommand line:Quser.cmdline
-		trueQ
+	file:$HOME/.gitconfigQuser.global
+	trueQfile:$HOME/.gitconfigQuser.override
+	globalQfile:$HOME/.gitconfigQinclude.path
+	$INCLUDE_DIR/absolute.includeQfile:$INCLUDE_DIR/absolute.includeQuser.absolute
+	includeQfile:.git/configQuser.local
+	trueQfile:.git/configQuser.override
+	localQfile:.git/configQinclude.path
+	../include/relative.includeQfile:.git/../include/relative.includeQuser.relative
+	includeQcommand line:Quser.cmdline
+	trueQ
 	EOF
 	git -c user.cmdline=true config --null --list --show-origin >output.raw &&
 	nul_to_q <output.raw >output &&
@@ -1684,9 +1684,9 @@ test_expect_success '--show-origin with --list --null' '
 
 test_expect_success '--show-origin with single file' '
 	cat >expect <<-\EOF &&
-		file:.git/config	user.local=true
-		file:.git/config	user.override=local
-		file:.git/config	include.path=../include/relative.include
+	file:.git/config	user.local=true
+	file:.git/config	user.override=local
+	file:.git/config	include.path=../include/relative.include
 	EOF
 	git config --local --list --show-origin >output &&
 	test_cmp expect output
@@ -1694,8 +1694,8 @@ test_expect_success '--show-origin with single file' '
 
 test_expect_success '--show-origin with --get-regexp' '
 	cat >expect <<-EOF &&
-		file:$HOME/.gitconfig	user.global true
-		file:.git/config	user.local true
+	file:$HOME/.gitconfig	user.global true
+	file:.git/config	user.local true
 	EOF
 	git config --show-origin --get-regexp "user\.[g|l].*" >output &&
 	test_cmp expect output
@@ -1703,7 +1703,7 @@ test_expect_success '--show-origin with --get-regexp' '
 
 test_expect_success '--show-origin getting a single key' '
 	cat >expect <<-\EOF &&
-		file:.git/config	local
+	file:.git/config	local
 	EOF
 	git config --show-origin user.override >output &&
 	test_cmp expect output
@@ -1712,14 +1712,14 @@ test_expect_success '--show-origin getting a single key' '
 test_expect_success 'set up custom config file' '
 	CUSTOM_CONFIG_FILE="file\" (dq) and spaces.conf" &&
 	cat >"$CUSTOM_CONFIG_FILE" <<-\EOF
-		[user]
-			custom = true
+	[user]
+		custom = true
 	EOF
 '
 
 test_expect_success !MINGW '--show-origin escape special file name characters' '
 	cat >expect <<-\EOF &&
-		file:"file\" (dq) and spaces.conf"	user.custom=true
+	file:"file\" (dq) and spaces.conf"	user.custom=true
 	EOF
 	git config --file "$CUSTOM_CONFIG_FILE" --show-origin --list >output &&
 	test_cmp expect output
@@ -1727,7 +1727,7 @@ test_expect_success !MINGW '--show-origin escape special file name characters' '
 
 test_expect_success '--show-origin stdin' '
 	cat >expect <<-\EOF &&
-		standard input:	user.custom=true
+	standard input:	user.custom=true
 	EOF
 	git config --file - --show-origin --list <"$CUSTOM_CONFIG_FILE" >output &&
 	test_cmp expect output
@@ -1735,11 +1735,11 @@ test_expect_success '--show-origin stdin' '
 
 test_expect_success '--show-origin stdin with file include' '
 	cat >"$INCLUDE_DIR"/stdin.include <<-EOF &&
-		[user]
-			stdin = include
+	[user]
+		stdin = include
 	EOF
 	cat >expect <<-EOF &&
-		file:$INCLUDE_DIR/stdin.include	include
+	file:$INCLUDE_DIR/stdin.include	include
 	EOF
 	echo "[include]path=\"$INCLUDE_DIR\"/stdin.include" |
 	git config --show-origin --includes --file - user.stdin >output &&
@@ -1750,7 +1750,7 @@ test_expect_success '--show-origin stdin with file include' '
 test_expect_success !MINGW '--show-origin blob' '
 	blob=$(git hash-object -w "$CUSTOM_CONFIG_FILE") &&
 	cat >expect <<-EOF &&
-		blob:$blob	user.custom=true
+	blob:$blob	user.custom=true
 	EOF
 	git config --blob=$blob --show-origin --list >output &&
 	test_cmp expect output
@@ -1758,7 +1758,7 @@ test_expect_success !MINGW '--show-origin blob' '
 
 test_expect_success !MINGW '--show-origin blob ref' '
 	cat >expect <<-\EOF &&
-		blob:"master:file\" (dq) and spaces.conf"	user.custom=true
+	blob:"master:file\" (dq) and spaces.conf"	user.custom=true
 	EOF
 	git add "$CUSTOM_CONFIG_FILE" &&
 	git commit -m "new config file" &&
-- 
gitgitgadget


^ permalink raw reply related	[relevance 6%]

* [PATCH v3 05/15] t0003: don't use `test_must_fail attr_check`
  @ 2019-12-20 18:15  6%     ` Denton Liu
  0 siblings, 0 replies; 200+ results
From: Denton Liu @ 2019-12-20 18:15 UTC (permalink / raw)
  To: Git Mailing List; +Cc: Eric Sunshine, Johannes Sixt, Junio C Hamano

In an effort to remove test_must_fail for all invocations not related to
git or test-tool, replace invocations of `test_must_fail attr_check`
with a plain attr_check call with the $expect argument set to the
actual value output by git.

Signed-off-by: Denton Liu <liu.denton@gmail.com>
---
 t/t0003-attributes.sh | 30 +++++++++++++++---------------
 1 file changed, 15 insertions(+), 15 deletions(-)

diff --git a/t/t0003-attributes.sh b/t/t0003-attributes.sh
index c30c736d3f..b660593c20 100755
--- a/t/t0003-attributes.sh
+++ b/t/t0003-attributes.sh
@@ -24,7 +24,7 @@ attr_check_quote () {
 
 test_expect_success 'open-quoted pathname' '
 	echo "\"a test=a" >.gitattributes &&
-	test_must_fail attr_check a a
+	attr_check a unspecified
 '
 
 
@@ -109,20 +109,20 @@ test_expect_success 'attribute test' '
 
 test_expect_success 'attribute matching is case sensitive when core.ignorecase=0' '
 
-	test_must_fail attr_check F f "-c core.ignorecase=0" &&
-	test_must_fail attr_check a/F f "-c core.ignorecase=0" &&
-	test_must_fail attr_check a/c/F f "-c core.ignorecase=0" &&
-	test_must_fail attr_check a/G a/g "-c core.ignorecase=0" &&
-	test_must_fail attr_check a/B/g a/b/g "-c core.ignorecase=0" &&
-	test_must_fail attr_check a/b/G a/b/g "-c core.ignorecase=0" &&
-	test_must_fail attr_check a/b/H a/b/h "-c core.ignorecase=0" &&
-	test_must_fail attr_check a/b/D/g "a/b/d/*" "-c core.ignorecase=0" &&
-	test_must_fail attr_check oNoFf unset "-c core.ignorecase=0" &&
-	test_must_fail attr_check oFfOn set "-c core.ignorecase=0" &&
+	attr_check F unspecified "-c core.ignorecase=0" &&
+	attr_check a/F unspecified "-c core.ignorecase=0" &&
+	attr_check a/c/F unspecified "-c core.ignorecase=0" &&
+	attr_check a/G unspecified "-c core.ignorecase=0" &&
+	attr_check a/B/g a/g "-c core.ignorecase=0" &&
+	attr_check a/b/G unspecified "-c core.ignorecase=0" &&
+	attr_check a/b/H unspecified "-c core.ignorecase=0" &&
+	attr_check a/b/D/g a/g "-c core.ignorecase=0" &&
+	attr_check oNoFf unspecified "-c core.ignorecase=0" &&
+	attr_check oFfOn unspecified "-c core.ignorecase=0" &&
 	attr_check NO unspecified "-c core.ignorecase=0" &&
-	test_must_fail attr_check a/b/D/NO "a/b/d/*" "-c core.ignorecase=0" &&
+	attr_check a/b/D/NO unspecified "-c core.ignorecase=0" &&
 	attr_check a/b/d/YES a/b/d/* "-c core.ignorecase=0" &&
-	test_must_fail attr_check a/E/f "A/e/F" "-c core.ignorecase=0"
+	attr_check a/E/f f "-c core.ignorecase=0"
 
 '
 
@@ -146,8 +146,8 @@ test_expect_success 'attribute matching is case insensitive when core.ignorecase
 '
 
 test_expect_success CASE_INSENSITIVE_FS 'additional case insensitivity tests' '
-	test_must_fail attr_check a/B/D/g "a/b/d/*" "-c core.ignorecase=0" &&
-	test_must_fail attr_check A/B/D/NO "a/b/d/*" "-c core.ignorecase=0" &&
+	attr_check a/B/D/g a/g "-c core.ignorecase=0" &&
+	attr_check A/B/D/NO unspecified "-c core.ignorecase=0" &&
 	attr_check A/b/h a/b/h "-c core.ignorecase=1" &&
 	attr_check a/B/D/g "a/b/d/*" "-c core.ignorecase=1" &&
 	attr_check A/B/D/NO "a/b/d/*" "-c core.ignorecase=1"
-- 
2.24.1.703.g2f499f1283


^ permalink raw reply related	[relevance 6%]

* [PATCH v2 06/16] t0003: don't use `test_must_fail attr_check`
  @ 2019-12-19 22:22  6%   ` Denton Liu
    1 sibling, 0 replies; 200+ results
From: Denton Liu @ 2019-12-19 22:22 UTC (permalink / raw)
  To: Git Mailing List; +Cc: Eric Sunshine, Johannes Sixt, Junio C Hamano

In an effort to remove test_must_fail for all invocations not related to
git or test-tool, replace invocations of `test_must_fail attr_check`
with a plain attr_check call with the $expect argument set to the
actual value output by git.

Signed-off-by: Denton Liu <liu.denton@gmail.com>
---
 t/t0003-attributes.sh | 30 +++++++++++++++---------------
 1 file changed, 15 insertions(+), 15 deletions(-)

diff --git a/t/t0003-attributes.sh b/t/t0003-attributes.sh
index c30c736d3f..b660593c20 100755
--- a/t/t0003-attributes.sh
+++ b/t/t0003-attributes.sh
@@ -24,7 +24,7 @@ attr_check_quote () {
 
 test_expect_success 'open-quoted pathname' '
 	echo "\"a test=a" >.gitattributes &&
-	test_must_fail attr_check a a
+	attr_check a unspecified
 '
 
 
@@ -109,20 +109,20 @@ test_expect_success 'attribute test' '
 
 test_expect_success 'attribute matching is case sensitive when core.ignorecase=0' '
 
-	test_must_fail attr_check F f "-c core.ignorecase=0" &&
-	test_must_fail attr_check a/F f "-c core.ignorecase=0" &&
-	test_must_fail attr_check a/c/F f "-c core.ignorecase=0" &&
-	test_must_fail attr_check a/G a/g "-c core.ignorecase=0" &&
-	test_must_fail attr_check a/B/g a/b/g "-c core.ignorecase=0" &&
-	test_must_fail attr_check a/b/G a/b/g "-c core.ignorecase=0" &&
-	test_must_fail attr_check a/b/H a/b/h "-c core.ignorecase=0" &&
-	test_must_fail attr_check a/b/D/g "a/b/d/*" "-c core.ignorecase=0" &&
-	test_must_fail attr_check oNoFf unset "-c core.ignorecase=0" &&
-	test_must_fail attr_check oFfOn set "-c core.ignorecase=0" &&
+	attr_check F unspecified "-c core.ignorecase=0" &&
+	attr_check a/F unspecified "-c core.ignorecase=0" &&
+	attr_check a/c/F unspecified "-c core.ignorecase=0" &&
+	attr_check a/G unspecified "-c core.ignorecase=0" &&
+	attr_check a/B/g a/g "-c core.ignorecase=0" &&
+	attr_check a/b/G unspecified "-c core.ignorecase=0" &&
+	attr_check a/b/H unspecified "-c core.ignorecase=0" &&
+	attr_check a/b/D/g a/g "-c core.ignorecase=0" &&
+	attr_check oNoFf unspecified "-c core.ignorecase=0" &&
+	attr_check oFfOn unspecified "-c core.ignorecase=0" &&
 	attr_check NO unspecified "-c core.ignorecase=0" &&
-	test_must_fail attr_check a/b/D/NO "a/b/d/*" "-c core.ignorecase=0" &&
+	attr_check a/b/D/NO unspecified "-c core.ignorecase=0" &&
 	attr_check a/b/d/YES a/b/d/* "-c core.ignorecase=0" &&
-	test_must_fail attr_check a/E/f "A/e/F" "-c core.ignorecase=0"
+	attr_check a/E/f f "-c core.ignorecase=0"
 
 '
 
@@ -146,8 +146,8 @@ test_expect_success 'attribute matching is case insensitive when core.ignorecase
 '
 
 test_expect_success CASE_INSENSITIVE_FS 'additional case insensitivity tests' '
-	test_must_fail attr_check a/B/D/g "a/b/d/*" "-c core.ignorecase=0" &&
-	test_must_fail attr_check A/B/D/NO "a/b/d/*" "-c core.ignorecase=0" &&
+	attr_check a/B/D/g a/g "-c core.ignorecase=0" &&
+	attr_check A/B/D/NO unspecified "-c core.ignorecase=0" &&
 	attr_check A/b/h a/b/h "-c core.ignorecase=1" &&
 	attr_check a/B/D/g "a/b/d/*" "-c core.ignorecase=1" &&
 	attr_check A/B/D/NO "a/b/d/*" "-c core.ignorecase=1"
-- 
2.24.1.703.g2f499f1283


^ permalink raw reply related	[relevance 6%]

* [PATCH 06/15] t0003: don't use `test_must_fail attr_check`
  @ 2019-12-17 12:01  6% ` Denton Liu
    1 sibling, 0 replies; 200+ results
From: Denton Liu @ 2019-12-17 12:01 UTC (permalink / raw)
  To: Git Mailing List

In an effort to remove test_must_fail for all invocations not related to
git or test-tool, replace invocations of `test_must_fail attr_check`
with a plain attr_check call with the $expect argument set to the
actual value output by git.

Signed-off-by: Denton Liu <liu.denton@gmail.com>
---
 t/t0003-attributes.sh | 30 +++++++++++++++---------------
 1 file changed, 15 insertions(+), 15 deletions(-)

diff --git a/t/t0003-attributes.sh b/t/t0003-attributes.sh
index 53a730e2ee..8d4343afdb 100755
--- a/t/t0003-attributes.sh
+++ b/t/t0003-attributes.sh
@@ -28,7 +28,7 @@ attr_check_quote () {
 
 test_expect_success 'open-quoted pathname' '
 	echo "\"a test=a" >.gitattributes &&
-	test_must_fail attr_check a a
+	attr_check a unspecified
 '
 
 
@@ -113,20 +113,20 @@ test_expect_success 'attribute test' '
 
 test_expect_success 'attribute matching is case sensitive when core.ignorecase=0' '
 
-	test_must_fail attr_check F f "-c core.ignorecase=0" &&
-	test_must_fail attr_check a/F f "-c core.ignorecase=0" &&
-	test_must_fail attr_check a/c/F f "-c core.ignorecase=0" &&
-	test_must_fail attr_check a/G a/g "-c core.ignorecase=0" &&
-	test_must_fail attr_check a/B/g a/b/g "-c core.ignorecase=0" &&
-	test_must_fail attr_check a/b/G a/b/g "-c core.ignorecase=0" &&
-	test_must_fail attr_check a/b/H a/b/h "-c core.ignorecase=0" &&
-	test_must_fail attr_check a/b/D/g "a/b/d/*" "-c core.ignorecase=0" &&
-	test_must_fail attr_check oNoFf unset "-c core.ignorecase=0" &&
-	test_must_fail attr_check oFfOn set "-c core.ignorecase=0" &&
+	attr_check F unspecified "-c core.ignorecase=0" &&
+	attr_check a/F unspecified "-c core.ignorecase=0" &&
+	attr_check a/c/F unspecified "-c core.ignorecase=0" &&
+	attr_check a/G unspecified "-c core.ignorecase=0" &&
+	attr_check a/B/g a/g "-c core.ignorecase=0" &&
+	attr_check a/b/G unspecified "-c core.ignorecase=0" &&
+	attr_check a/b/H unspecified "-c core.ignorecase=0" &&
+	attr_check a/b/D/g a/g "-c core.ignorecase=0" &&
+	attr_check oNoFf unspecified "-c core.ignorecase=0" &&
+	attr_check oFfOn unspecified "-c core.ignorecase=0" &&
 	attr_check NO unspecified "-c core.ignorecase=0" &&
-	test_must_fail attr_check a/b/D/NO "a/b/d/*" "-c core.ignorecase=0" &&
+	attr_check a/b/D/NO unspecified "-c core.ignorecase=0" &&
 	attr_check a/b/d/YES a/b/d/* "-c core.ignorecase=0" &&
-	test_must_fail attr_check a/E/f "A/e/F" "-c core.ignorecase=0"
+	attr_check a/E/f f "-c core.ignorecase=0"
 
 '
 
@@ -150,8 +150,8 @@ test_expect_success 'attribute matching is case insensitive when core.ignorecase
 '
 
 test_expect_success CASE_INSENSITIVE_FS 'additional case insensitivity tests' '
-	test_must_fail attr_check a/B/D/g "a/b/d/*" "-c core.ignorecase=0" &&
-	test_must_fail attr_check A/B/D/NO "a/b/d/*" "-c core.ignorecase=0" &&
+	attr_check a/B/D/g a/g "-c core.ignorecase=0" &&
+	attr_check A/B/D/NO unspecified "-c core.ignorecase=0" &&
 	attr_check A/b/h a/b/h "-c core.ignorecase=1" &&
 	attr_check a/B/D/g "a/b/d/*" "-c core.ignorecase=1" &&
 	attr_check A/B/D/NO "a/b/d/*" "-c core.ignorecase=1"
-- 
2.24.0.627.geba02921db


^ permalink raw reply related	[relevance 6%]

* Re: [PATCH 1/1] sparse-checkout: respect core.ignoreCase in cone mode
  2019-12-11 21:37  4%           ` Junio C Hamano
@ 2019-12-12  2:51  0%             ` Derrick Stolee
  0 siblings, 0 replies; 200+ results
From: Derrick Stolee @ 2019-12-12  2:51 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Derrick Stolee via GitGitGadget, git, szeder.dev, newren,
	Derrick Stolee

On 12/11/2019 4:37 PM, Junio C Hamano wrote:
> Derrick Stolee <stolee@gmail.com> writes:
> 
>> The specific example I have in my mind is that the filesystem can have
>> "README.TXT" as what it would report by readdir(), but a user can type
>>
>> 	git add readme.txt
>>
>> Without core.ignoreCase, the index will store the path as "readme.txt",
>> possibly adding a new entry in the index next to "README.TXT". If
>> core.ignoreCase is enabled, then the case reported by readdir() is used.
>> (This works both for a tracked and untracked path.)
> 
> Yes, but which one is "correct"?  readme.txt read from the index or
> README.TXT read from the filesystem?  Presumably, when the paths got
> first checked out of the index, it would have been in "readme.txt"
> on the filesystem, so the user must have done on purpose something
> to cause the file to be named in all uppercase since then.

Ah. The issue here is that with core.ignoreCase=true, the index and
filesystem agree. It's just the user input that differs.

>> This is a case that is difficult to control for. We have no source
>> of truth (filesystem or index) at the time of the 'git sparse-checkout
>> set' command. I cannot think of a solution to this specific issue
>> without doing the more-costly approach on every unpack_trees() call.
>>
>> I believe this case may be narrow enough to "document and don't-fix",
>> but please speak up if anyone thinks this _must_ be fixed.
> 
> I do not think it "must be" fixed in the sense that "if it hurts,
> don't do so" is a sufficient remedy.  But then for exactly the same
> reason, I do not think it is worth doing what you do in this patch.
> 
> On the other hand, I think runtime case folding, just like what we
> do when "git add" is told to handle a path in different case, would
> be the only "right" way to match end-user expectations on a case
> insensitive system, even though that is a "nice to do so" and
> certainly not a "must do so" thing.
> 
>> The "git add" behavior made me think there was sufficient precedent
>> in Git to try this change.
> 
> Since I view that the "git add" behaviour is a "nice to do so"
> runtime conversion, I would actually think the approach you
> discarded would be more in line with the precedent.
> 
>> I'm happy to follow the community's opinion in this matter, including
>> a hard "we will not correct for case in this feature" or "if you want
>> this then you'll pay for it in performance." In the latter case I'd
>> prefer a new config option to toggle the sparse-checkout case
>> insensitivity. That way users could have core.ignoreCase behavior without
>> the perf hit if they use clean input to the sparse-checkout feature.
> 
> I value correctness more---a system that does incorrect thing
> quickly is not all that interesting.
> 
> Assuming that your users are sensible and feed good data, how much
> "performance hit" are we talking about?

I'll need to build a prototype to test the performance hit. Maybe
I'm overestimating the effect of using a case-insensitive hash.

> Wouldn't this be something
> we can make into a "if you have the paths in the correct case, we'll
> see the match just as quickly as in the case sensitive system, so
> most of the time there is no penalty, but for correctness we would
> also fall back to try different cases"?

I think we would want the config option present to say "my data may
not be in the proper case, please do extra work for me". I can't
think of a way to do the fallback in real-time.

I'll try again with the case-insensitive hash algorithm and see
how that shakes out.

Thanks,
-Stolee



^ permalink raw reply	[relevance 0%]

* Re: [PATCH 1/1] sparse-checkout: respect core.ignoreCase in cone mode
  2019-12-11 20:29  0%         ` Derrick Stolee
@ 2019-12-11 21:37  4%           ` Junio C Hamano
  2019-12-12  2:51  0%             ` Derrick Stolee
  0 siblings, 1 reply; 200+ results
From: Junio C Hamano @ 2019-12-11 21:37 UTC (permalink / raw)
  To: Derrick Stolee
  Cc: Derrick Stolee via GitGitGadget, git, szeder.dev, newren,
	Derrick Stolee

Derrick Stolee <stolee@gmail.com> writes:

> The specific example I have in my mind is that the filesystem can have
> "README.TXT" as what it would report by readdir(), but a user can type
>
> 	git add readme.txt
>
> Without core.ignoreCase, the index will store the path as "readme.txt",
> possibly adding a new entry in the index next to "README.TXT". If
> core.ignoreCase is enabled, then the case reported by readdir() is used.
> (This works both for a tracked and untracked path.)

Yes, but which one is "correct"?  readme.txt read from the index or
README.TXT read from the filesystem?  Presumably, when the paths got
first checked out of the index, it would have been in "readme.txt"
on the filesystem, so the user must have done on purpose something
to cause the file to be named in all uppercase since then.

> This is a case that is difficult to control for. We have no source
> of truth (filesystem or index) at the time of the 'git sparse-checkout
> set' command. I cannot think of a solution to this specific issue
> without doing the more-costly approach on every unpack_trees() call.
>
> I believe this case may be narrow enough to "document and don't-fix",
> but please speak up if anyone thinks this _must_ be fixed.

I do not think it "must be" fixed in the sense that "if it hurts,
don't do so" is a sufficient remedy.  But then for exactly the same
reason, I do not think it is worth doing what you do in this patch.

On the other hand, I think runtime case folding, just like what we
do when "git add" is told to handle a path in different case, would
be the only "right" way to match end-user expectations on a case
insensitive system, even though that is a "nice to do so" and
certainly not a "must do so" thing.

> The "git add" behavior made me think there was sufficient precedent
> in Git to try this change.

Since I view that the "git add" behaviour is a "nice to do so"
runtime conversion, I would actually think the approach you
discarded would be more in line with the precedent.

> I'm happy to follow the community's opinion in this matter, including
> a hard "we will not correct for case in this feature" or "if you want
> this then you'll pay for it in performance." In the latter case I'd
> prefer a new config option to toggle the sparse-checkout case
> insensitivity. That way users could have core.ignoreCase behavior without
> the perf hit if they use clean input to the sparse-checkout feature.

I value correctness more---a system that does incorrect thing
quickly is not all that interesting.

Assuming that your users are sensible and feed good data, how much
"performance hit" are we talking about?  Wouldn't this be something
we can make into a "if you have the paths in the correct case, we'll
see the match just as quickly as in the case sensitive system, so
most of the time there is no penalty, but for correctness we would
also fall back to try different cases"?

^ permalink raw reply	[relevance 4%]

* Re: [PATCH 1/1] sparse-checkout: respect core.ignoreCase in cone mode
  2019-12-11 20:00  5%       ` Junio C Hamano
@ 2019-12-11 20:29  0%         ` Derrick Stolee
  2019-12-11 21:37  4%           ` Junio C Hamano
  0 siblings, 1 reply; 200+ results
From: Derrick Stolee @ 2019-12-11 20:29 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Derrick Stolee via GitGitGadget, git, szeder.dev, newren,
	Derrick Stolee

On 12/11/2019 3:00 PM, Junio C Hamano wrote:
> Derrick Stolee <stolee@gmail.com> writes:
> 
>> I'm trying to find a way around these two ideas:
>>
>> 1. The index is case-sensitive, and the sparse-checkout patterns are
>>    case-sensitive.
> 
> OK.  The latter is local to the repository and not shared to the
> world where people with case sensitive systems would live, right?

Yes, this information is local to a specific local copy. Even worktrees
have distinct sparse-checkout files.

>> 2. If a filesystem is case-insensitive, most index-change operations
>>    already succeed with incorrect case, especially with core.ignoreCase
>>    enabled.
> 
> I am not sure about "incorrect", though.  
> 
> My understanding is that modern case-insensitive systems are not
> information-destroying [*1*].  A new file you create as "ReadMe.txt"
> on your filesystem would be seen in that mixed case spelling via
> readdir() or equivalent, so adding it to the index as-is would
> likely be in the "correct" case, no?  If, after adding that path to
> the index, you did "rm ReadMe.txt" and created "README.TXT", surely
> we won't have both ReadMe.txt and README.TXT in the index with
> ignoreCase set, and keep using ReadMe.txt that matches what you
> added to the index.  I am not sure which one is the "incorrect" case
> in such a scenario.

The specific example I have in my mind is that the filesystem can have
"README.TXT" as what it would report by readdir(), but a user can type

	git add readme.txt

Without core.ignoreCase, the index will store the path as "readme.txt",
possibly adding a new entry in the index next to "README.TXT". If
core.ignoreCase is enabled, then the case reported by readdir() is used.
(This works both for a tracked and untracked path.)

>> The approach I have is to allow a user to provide a case that does not
>> match the index, and then we store the pattern in the sparse-checkout
>> that matches the case in the index.
> 
> Yes, I understood that from your proposed log message and cover
> letter.  They were very clearly written.
> 
> But imagine that your user writes ABC in the sparse pattern file,
> and there is no abc anything in the index in any case permutations.
> 
> When you check out a branch that has Abc, shouldn't the pattern ABC
> affect the operation just like a pattern Abc would on a case
> insensitive system?
> 
> Or are end users perfectly aware that the thing in that branch is
> spelled "Abc" and not "ABC" (the data in Git does---it comes from a
> tree object that is case sensitive)?  If so, then the pattern ABC
> should not affect the subtree whose name is "Abc" even on a case
> insensitive system.

This is a case that is difficult to control for. We have no source
of truth (filesystem or index) at the time of the 'git sparse-checkout
set' command. I cannot think of a solution to this specific issue
without doing the more-costly approach on every unpack_trees() call.

I believe this case may be narrow enough to "document and don't-fix",
but please speak up if anyone thinks this _must_ be fixed.

The other thing I can say is that my current approach _could_ be
replaced in the future by the more invasive check in unpack_trees().
The behavior change would be invisible to users who don't inspect
their sparse-checkout files other than this narrow case.

Specifically, our internal customer is planning to update the
sparse-checkout cone based on files in the workdir, which means that
the paths are expected to be in the index at the time of the 'set'
call.

> I am not sure what the design of this part of the system expects out
> of the end user.  Perhaps keeping the patterns case insensitive and
> tell the end users to spell them correctly is the right way to go, I
> guess, if it is just the filesystem that cannot represente the cases
> correctly at times and the users are perfectly capable of telling
> the right and wrong cases apart.

My first reaction to this internal request was "just clean up your
data." The issue is keeping it clean as users are changing the data
often and the only current checks are whether the build passes (and
the build "sees" the files because the filesystem accepts the wrong
case).

The "git add" behavior made me think there was sufficient precedent
in Git to try this change.

I'm happy to follow the community's opinion in this matter, including
a hard "we will not correct for case in this feature" or "if you want
this then you'll pay for it in performance." In the latter case I'd
prefer a new config option to toggle the sparse-checkout case
insensitivity. That way users could have core.ignoreCase behavior without
the perf hit if they use clean input to the sparse-checkout feature.

> But then I am not sure why correcting misspelled names by comparing
> them with the index entries is a good idea, either.

Right, that seems like a line too far.

>> It sounds like you are preferring this second option, despite the
>> performance costs. It is possible to use a case-insensitive hashing
>> algorithm when in the cone mode, but I'm not sure about how to do
>> a similar concept for the normal mode.
> 
> I have no strong preference, other than that I prefer to see things
> being done consistently.  Don't we already use case folding hash
> function to ensure that we won't add duplicate to the index on
> case-insensitive system?  I suspect that we would need to do the
> same, whether in cone mode or just a normal sparse-checkout mode
> consistently.

Since the cone mode uses a hashset to match the paths to the patterns,
that conversion is possible. I'm not sure how to start making arbitrary
regexes be case-insensitive in the non-cone-mode case. Suggestions?

Thank you for the discussion. I was hoping to get feedback on this
approach, which is why this patch is isolated to only this issue.

Thanks,
-Stolee



^ permalink raw reply	[relevance 0%]

* Re: [PATCH 1/1] sparse-checkout: respect core.ignoreCase in cone mode
  2019-12-11 19:11  6%     ` Derrick Stolee
@ 2019-12-11 20:00  5%       ` Junio C Hamano
  2019-12-11 20:29  0%         ` Derrick Stolee
  0 siblings, 1 reply; 200+ results
From: Junio C Hamano @ 2019-12-11 20:00 UTC (permalink / raw)
  To: Derrick Stolee
  Cc: Derrick Stolee via GitGitGadget, git, szeder.dev, newren,
	Derrick Stolee

Derrick Stolee <stolee@gmail.com> writes:

> I'm trying to find a way around these two ideas:
>
> 1. The index is case-sensitive, and the sparse-checkout patterns are
>    case-sensitive.

OK.  The latter is local to the repository and not shared to the
world where people with case sensitive systems would live, right?

> 2. If a filesystem is case-insensitive, most index-change operations
>    already succeed with incorrect case, especially with core.ignoreCase
>    enabled.

I am not sure about "incorrect", though.  

My understanding is that modern case-insensitive systems are not
information-destroying [*1*].  A new file you create as "ReadMe.txt"
on your filesystem would be seen in that mixed case spelling via
readdir() or equivalent, so adding it to the index as-is would
likely be in the "correct" case, no?  If, after adding that path to
the index, you did "rm ReadMe.txt" and created "README.TXT", surely
we won't have both ReadMe.txt and README.TXT in the index with
ignoreCase set, and keep using ReadMe.txt that matches what you
added to the index.  I am not sure which one is the "incorrect" case
in such a scenario.

> The approach I have is to allow a user to provide a case that does not
> match the index, and then we store the pattern in the sparse-checkout
> that matches the case in the index.

Yes, I understood that from your proposed log message and cover
letter.  They were very clearly written.

But imagine that your user writes ABC in the sparse pattern file,
and there is no abc anything in the index in any case permutations.

When you check out a branch that has Abc, shouldn't the pattern ABC
affect the operation just like a pattern Abc would on a case
insensitive system?

Or are end users perfectly aware that the thing in that branch is
spelled "Abc" and not "ABC" (the data in Git does---it comes from a
tree object that is case sensitive)?  If so, then the pattern ABC
should not affect the subtree whose name is "Abc" even on a case
insensitive system.

I am not sure what the design of this part of the system expects out
of the end user.  Perhaps keeping the patterns case insensitive and
tell the end users to spell them correctly is the right way to go, I
guess, if it is just the filesystem that cannot represente the cases
correctly at times and the users are perfectly capable of telling
the right and wrong cases apart.

But then I am not sure why correcting misspelled names by comparing
them with the index entries is a good idea, either.

> It sounds like you are preferring this second option, despite the
> performance costs. It is possible to use a case-insensitive hashing
> algorithm when in the cone mode, but I'm not sure about how to do
> a similar concept for the normal mode.

I have no strong preference, other than that I prefer to see things
being done consistently.  Don't we already use case folding hash
function to ensure that we won't add duplicate to the index on
case-insensitive system?  I suspect that we would need to do the
same, whether in cone mode or just a normal sparse-checkout mode
consistently.

Thanks.


[Footnote]

*1* ... unlike HFS+ where everything is forced to NKD and a bit of
information---whether the original was in NKC or NKD---is discarded
forever.

^ permalink raw reply	[relevance 5%]

* Re: [PATCH 1/1] sparse-checkout: respect core.ignoreCase in cone mode
  2019-12-11 18:44  5%   ` Junio C Hamano
@ 2019-12-11 19:11  6%     ` Derrick Stolee
  2019-12-11 20:00  5%       ` Junio C Hamano
  0 siblings, 1 reply; 200+ results
From: Derrick Stolee @ 2019-12-11 19:11 UTC (permalink / raw)
  To: Junio C Hamano, Derrick Stolee via GitGitGadget
  Cc: git, szeder.dev, newren, Derrick Stolee

On 12/11/2019 1:44 PM, Junio C Hamano wrote:
> "Derrick Stolee via GitGitGadget" <gitgitgadget@gmail.com> writes:
> 
>> Another option would be to do case-insensitive checks while
>> updating the skip-worktree bits during unpack_trees(). Outside of
>> the potential performance loss on a more expensive code path, that
>> also breaks compatibility with older versions of Git as using the
>> same sparse-checkout file would change the paths that are included.
> 
> I haven't thought things through, but that sounds as if saying that
> we cannot fix old bugs.  We use the entries in the index as a hint
> for "correcting" a path to the right case on a case-insensitive
> filesystem to avoid corrupting the case in the index, which is case
> sensitive and is a way to convey the intended case (by writing them
> out to a tree object) to those who use systems that can reliably
> reproduce cases in pathnames.  But that still does not mean on a
> case-insensitive filesystem, "hello.c" in the "right" case recorded
> in the index will always be spelled like so---somebody can make
> readdir() or equivalent to yield "Hello.c" for it, and if the user
> tells us to say they want to do X (e.g. ignore, skip checkout, etc.)
> to "hello.c", we should do the same to "Hello.c" on such a system, I
> would think.
> 
> If the runtime needs to deal with the case insensitive filesystems
> anyway (and I suspect it does), there isn't much point matching and
> forcing the case to the paths in the index like this patch does, I
> think.  So...

I'm trying to find a way around these two ideas:

1. The index is case-sensitive, and the sparse-checkout patterns are
   case-sensitive.

2. If a filesystem is case-insensitive, most index-change operations
   already succeed with incorrect case, especially with core.ignoreCase
   enabled.

The approach I have is to allow a user to provide a case that does not
match the index, and then we store the pattern in the sparse-checkout
that matches the case in the index.

Another approach would be to make the sparse-checkout patterns be
case-insensitive when core.ignoreCase is enabled. I chose against
this due to the compatibility and the performance cost. We would
likely pay that performance penalty even if all the patterns have
the correct case. It violates the existing "contract" with the
sparse-checkout file, and that is probably what you are talking about
with "we cannot fix old bugs".

It sounds like you are preferring this second option, despite the
performance costs. It is possible to use a case-insensitive hashing
algorithm when in the cone mode, but I'm not sure about how to do
a similar concept for the normal mode.

Thanks,
-Stolee

^ permalink raw reply	[relevance 6%]

* Re: [PATCH 1/1] sparse-checkout: respect core.ignoreCase in cone mode
  @ 2019-12-11 18:44  5%   ` Junio C Hamano
  2019-12-11 19:11  6%     ` Derrick Stolee
  0 siblings, 1 reply; 200+ results
From: Junio C Hamano @ 2019-12-11 18:44 UTC (permalink / raw)
  To: Derrick Stolee via GitGitGadget; +Cc: git, szeder.dev, newren, Derrick Stolee

"Derrick Stolee via GitGitGadget" <gitgitgadget@gmail.com> writes:

> Another option would be to do case-insensitive checks while
> updating the skip-worktree bits during unpack_trees(). Outside of
> the potential performance loss on a more expensive code path, that
> also breaks compatibility with older versions of Git as using the
> same sparse-checkout file would change the paths that are included.

I haven't thought things through, but that sounds as if saying that
we cannot fix old bugs.  We use the entries in the index as a hint
for "correcting" a path to the right case on a case-insensitive
filesystem to avoid corrupting the case in the index, which is case
sensitive and is a way to convey the intended case (by writing them
out to a tree object) to those who use systems that can reliably
reproduce cases in pathnames.  But that still does not mean on a
case-insensitive filesystem, "hello.c" in the "right" case recorded
in the index will always be spelled like so---somebody can make
readdir() or equivalent to yield "Hello.c" for it, and if the user
tells us to say they want to do X (e.g. ignore, skip checkout, etc.)
to "hello.c", we should do the same to "Hello.c" on such a system, I
would think.

If the runtime needs to deal with the case insensitive filesystems
anyway (and I suspect it does), there isn't much point matching and
forcing the case to the paths in the index like this patch does, I
think.  So...


> Signed-off-by: Derrick Stolee <dstolee@microsoft.com>
> ---
>  Documentation/git-sparse-checkout.txt |  4 ++++
>  builtin/sparse-checkout.c             | 19 +++++++++++++++++--
>  cache.h                               |  1 +
>  name-hash.c                           | 10 ++++++++++
>  t/t1091-sparse-checkout-builtin.sh    | 13 +++++++++++++
>  5 files changed, 45 insertions(+), 2 deletions(-)
>
> diff --git a/Documentation/git-sparse-checkout.txt b/Documentation/git-sparse-checkout.txt
> index b975285673..849efa0f0b 100644
> --- a/Documentation/git-sparse-checkout.txt
> +++ b/Documentation/git-sparse-checkout.txt
> @@ -150,6 +150,10 @@ expecting patterns of these types. Git will warn if the patterns do not match.
>  If the patterns do match the expected format, then Git will use faster hash-
>  based algorithms to compute inclusion in the sparse-checkout.
>  
> +If `core.ignoreCase=true`, then the 'git sparse-checkout set' command will
> +correct for incorrect case when assigning patterns to the sparse-checkout
> +file.
> +
>  SEE ALSO
>  --------
>  
> diff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c
> index a542d617a5..0de426384e 100644
> --- a/builtin/sparse-checkout.c
> +++ b/builtin/sparse-checkout.c
> @@ -336,6 +336,22 @@ static void insert_recursive_pattern(struct pattern_list *pl, struct strbuf *pat
>  	}
>  }
>  
> +static void sanitize_cone_input(struct strbuf *line)
> +{
> +	if (ignore_case) {
> +		struct index_state *istate = the_repository->index;
> +		const char *name = index_dir_matching_name(istate, line->buf, line->len);
> +
> +		if (name) {
> +			strbuf_setlen(line, 0);
> +			strbuf_addstr(line, name);
> +		}
> +	}
> +
> +	if (line->buf[0] != '/')
> +		strbuf_insert(line, 0, "/", 1);
> +}
> +
>  static void strbuf_to_cone_pattern(struct strbuf *line, struct pattern_list *pl)
>  {
>  	strbuf_trim(line);
> @@ -345,8 +361,7 @@ static void strbuf_to_cone_pattern(struct strbuf *line, struct pattern_list *pl)
>  	if (!line->len)
>  		return;
>  
> -	if (line->buf[0] != '/')
> -		strbuf_insert(line, 0, "/", 1);
> +	sanitize_cone_input(line);
>  
>  	insert_recursive_pattern(pl, line);
>  }
> diff --git a/cache.h b/cache.h
> index d3c89e7a53..a2d9d437f0 100644
> --- a/cache.h
> +++ b/cache.h
> @@ -728,6 +728,7 @@ int repo_index_has_changes(struct repository *repo,
>  int verify_path(const char *path, unsigned mode);
>  int strcmp_offset(const char *s1, const char *s2, size_t *first_change);
>  int index_dir_exists(struct index_state *istate, const char *name, int namelen);
> +const char *index_dir_matching_name(struct index_state *istate, const char *name, int namelen);
>  void adjust_dirname_case(struct index_state *istate, char *name);
>  struct cache_entry *index_file_exists(struct index_state *istate, const char *name, int namelen, int igncase);
>  
> diff --git a/name-hash.c b/name-hash.c
> index ceb1d7bd6f..46898b6571 100644
> --- a/name-hash.c
> +++ b/name-hash.c
> @@ -681,6 +681,16 @@ int index_dir_exists(struct index_state *istate, const char *name, int namelen)
>  	return dir && dir->nr;
>  }
>  
> +const char *index_dir_matching_name(struct index_state *istate, const char *name, int namelen)
> +{
> +	struct dir_entry *dir;
> +
> +	lazy_init_name_hash(istate);
> +	dir = find_dir_entry(istate, name, namelen);
> +
> +	return dir ? dir->name : NULL;
> +}
> +
>  void adjust_dirname_case(struct index_state *istate, char *name)
>  {
>  	const char *startPtr = name;
> diff --git a/t/t1091-sparse-checkout-builtin.sh b/t/t1091-sparse-checkout-builtin.sh
> index d5e2892526..d0ce48869f 100755
> --- a/t/t1091-sparse-checkout-builtin.sh
> +++ b/t/t1091-sparse-checkout-builtin.sh
> @@ -304,4 +304,17 @@ test_expect_success 'sparse-checkout (init|set|disable) fails with dirty status'
>  	git -C dirty sparse-checkout disable
>  '
>  
> +test_expect_success 'cone mode: set with core.ignoreCase=true' '
> +	test_when_finished git -C repo config --unset core.ignoreCase &&
> +	git -C repo sparse-checkout init --cone &&
> +	git -C repo config core.ignoreCase true &&
> +	git -C repo sparse-checkout set Folder1 &&
> +	cat >expect <<-EOF &&
> +		/*
> +		!/*/
> +		/folder1/
> +	EOF
> +	test_cmp expect repo/.git/info/sparse-checkout
> +'
> +
>  test_done

^ permalink raw reply	[relevance 5%]

* [PATCH 5/8] rebase: drop support for `--preserve-merges`
  @ 2019-11-23 20:50  2% ` Johannes Schindelin via GitGitGadget
  0 siblings, 0 replies; 200+ results
From: Johannes Schindelin via GitGitGadget @ 2019-11-23 20:50 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Junio C Hamano, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

This option was deprecated in favor of `--rebase-merges` some time ago,
and now we retire it.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 .gitignore                     |    1 -
 Documentation/git-rebase.txt   |   47 --
 Makefile                       |    2 -
 builtin/rebase.c               |  135 +---
 git-rebase--preserve-merges.sh | 1067 --------------------------------
 5 files changed, 6 insertions(+), 1246 deletions(-)
 delete mode 100644 git-rebase--preserve-merges.sh

diff --git a/.gitignore b/.gitignore
index 89b3b79c1a..9989dce1d2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -123,7 +123,6 @@
 /git-range-diff
 /git-read-tree
 /git-rebase
-/git-rebase--preserve-merges
 /git-receive-pack
 /git-reflog
 /git-remote
diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 639a4179d1..7cced2f272 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -442,29 +442,12 @@ i.e. commits that would be excluded by linkgit:git-log[1]'s
 the `rebase-cousins` mode is turned on, such commits are instead rebased
 onto `<upstream>` (or `<onto>`, if specified).
 +
-The `--rebase-merges` mode is similar in spirit to the deprecated
-`--preserve-merges`, but in contrast to that option works well in interactive
-rebases: commits can be reordered, inserted and dropped at will.
-+
 It is currently only possible to recreate the merge commits using the
 `recursive` merge strategy; Different merge strategies can be used only via
 explicit `exec git merge -s <strategy> [...]` commands.
 +
 See also REBASING MERGES and INCOMPATIBLE OPTIONS below.
 
--p::
---preserve-merges::
-	[DEPRECATED: use `--rebase-merges` instead] Recreate merge commits
-	instead of flattening the history by replaying commits a merge commit
-	introduces. Merge conflict resolutions or manual amendments to merge
-	commits are not preserved.
-+
-This uses the `--interactive` machinery internally, but combining it
-with the `--interactive` option explicitly is generally not a good
-idea unless you know what you are doing (see BUGS below).
-+
-See also INCOMPATIBLE OPTIONS below.
-
 -x <cmd>::
 --exec <cmd>::
 	Append "exec <cmd>" after each line creating a commit in the
@@ -496,9 +479,6 @@ See also INCOMPATIBLE OPTIONS below.
 	the root commit(s) on a branch.  When used with --onto, it
 	will skip changes already contained in <newbase> (instead of
 	<upstream>) whereas without --onto it will operate on every change.
-	When used together with both --onto and --preserve-merges,
-	'all' root commits will be rewritten to have <newbase> as parent
-	instead.
 +
 See also INCOMPATIBLE OPTIONS below.
 
@@ -553,7 +533,6 @@ are incompatible with the following options:
  * --allow-empty-message
  * --[no-]autosquash
  * --rebase-merges
- * --preserve-merges
  * --interactive
  * --exec
  * --keep-empty
@@ -562,9 +541,6 @@ are incompatible with the following options:
 
 In addition, the following pairs of options are incompatible:
 
- * --preserve-merges and --interactive
- * --preserve-merges and --signoff
- * --preserve-merges and --rebase-merges
  * --keep-base and --onto
  * --keep-base and --root
 
@@ -1048,29 +1024,6 @@ merge tlsv1.3
 merge cmake
 ------------
 
-BUGS
-----
-The todo list presented by the deprecated `--preserve-merges --interactive`
-does not represent the topology of the revision graph (use `--rebase-merges`
-instead).  Editing commits and rewording their commit messages should work
-fine, but attempts to reorder commits tend to produce counterintuitive results.
-Use `--rebase-merges` in such scenarios instead.
-
-For example, an attempt to rearrange
-------------
-1 --- 2 --- 3 --- 4 --- 5
-------------
-to
-------------
-1 --- 2 --- 4 --- 3 --- 5
-------------
-by moving the "pick 4" line will result in the following history:
-------------
-	3
-       /
-1 --- 2 --- 4 --- 5
-------------
-
 GIT
 ---
 Part of the linkgit:git[1] suite
diff --git a/Makefile b/Makefile
index 58b92af54b..e2c26843e2 100644
--- a/Makefile
+++ b/Makefile
@@ -616,7 +616,6 @@ SCRIPT_SH += git-web--browse.sh
 
 SCRIPT_LIB += git-mergetool--lib
 SCRIPT_LIB += git-parse-remote
-SCRIPT_LIB += git-rebase--preserve-merges
 SCRIPT_LIB += git-sh-setup
 SCRIPT_LIB += git-sh-i18n
 
@@ -2522,7 +2521,6 @@ XGETTEXT_FLAGS_PERL = $(XGETTEXT_FLAGS) --language=Perl \
 LOCALIZED_C = $(C_OBJ:o=c) $(LIB_H) $(GENERATED_H)
 LOCALIZED_SH = $(SCRIPT_SH)
 LOCALIZED_SH += git-parse-remote.sh
-LOCALIZED_SH += git-rebase--preserve-merges.sh
 LOCALIZED_SH += git-sh-setup.sh
 LOCALIZED_PERL = $(SCRIPT_PERL)
 
diff --git a/builtin/rebase.c b/builtin/rebase.c
index e755087b0f..2970a9ae47 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -46,8 +46,7 @@ enum rebase_type {
 	REBASE_UNSPECIFIED = -1,
 	REBASE_AM,
 	REBASE_MERGE,
-	REBASE_INTERACTIVE,
-	REBASE_PRESERVE_MERGES
+	REBASE_INTERACTIVE
 };
 
 struct rebase_options {
@@ -524,8 +523,7 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix)
 
 static int is_interactive(struct rebase_options *opts)
 {
-	return opts->type == REBASE_INTERACTIVE ||
-		opts->type == REBASE_PRESERVE_MERGES;
+	return opts->type == REBASE_INTERACTIVE;
 }
 
 static void imply_interactive(struct rebase_options *opts, const char *option)
@@ -535,7 +533,6 @@ static void imply_interactive(struct rebase_options *opts, const char *option)
 		die(_("%s requires an interactive rebase"), option);
 		break;
 	case REBASE_INTERACTIVE:
-	case REBASE_PRESERVE_MERGES:
 		break;
 	case REBASE_MERGE:
 		/* we now implement --merge via --interactive */
@@ -776,17 +773,6 @@ static struct commit *peel_committish(const char *name)
 	return (struct commit *)peel_to_type(name, 0, obj, OBJ_COMMIT);
 }
 
-static void add_var(struct strbuf *buf, const char *name, const char *value)
-{
-	if (!value)
-		strbuf_addf(buf, "unset %s; ", name);
-	else {
-		strbuf_addf(buf, "%s=", name);
-		sq_quote_buf(buf, value);
-		strbuf_addstr(buf, "; ");
-	}
-}
-
 #define GIT_REFLOG_ACTION_ENVIRONMENT "GIT_REFLOG_ACTION"
 
 #define RESET_HEAD_DETACH (1<<0)
@@ -1076,10 +1062,8 @@ static int run_am(struct rebase_options *opts)
 
 static int run_specific_rebase(struct rebase_options *opts, enum action action)
 {
-	const char *argv[] = { NULL, NULL };
-	struct strbuf script_snippet = STRBUF_INIT, buf = STRBUF_INIT;
+	struct strbuf script_snippet = STRBUF_INIT;
 	int status;
-	const char *backend, *backend_func;
 
 	if (opts->type == REBASE_INTERACTIVE) {
 		/* Run builtin interactive rebase */
@@ -1096,89 +1080,11 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)
 		}
 
 		status = run_rebase_interactive(opts, action);
-		goto finished_rebase;
-	}
-
-	if (opts->type == REBASE_AM) {
+	} else if (opts->type == REBASE_AM)
 		status = run_am(opts);
-		goto finished_rebase;
-	}
-
-	add_var(&script_snippet, "GIT_DIR", absolute_path(get_git_dir()));
-	add_var(&script_snippet, "state_dir", opts->state_dir);
-
-	add_var(&script_snippet, "upstream_name", opts->upstream_name);
-	add_var(&script_snippet, "upstream", opts->upstream ?
-		oid_to_hex(&opts->upstream->object.oid) : NULL);
-	add_var(&script_snippet, "head_name",
-		opts->head_name ? opts->head_name : "detached HEAD");
-	add_var(&script_snippet, "orig_head", oid_to_hex(&opts->orig_head));
-	add_var(&script_snippet, "onto", opts->onto ?
-		oid_to_hex(&opts->onto->object.oid) : NULL);
-	add_var(&script_snippet, "onto_name", opts->onto_name);
-	add_var(&script_snippet, "revisions", opts->revisions);
-	add_var(&script_snippet, "restrict_revision", opts->restrict_revision ?
-		oid_to_hex(&opts->restrict_revision->object.oid) : NULL);
-	add_var(&script_snippet, "GIT_QUIET",
-		opts->flags & REBASE_NO_QUIET ? "" : "t");
-	sq_quote_argv_pretty(&buf, opts->git_am_opts.argv);
-	add_var(&script_snippet, "git_am_opt", buf.buf);
-	strbuf_release(&buf);
-	add_var(&script_snippet, "verbose",
-		opts->flags & REBASE_VERBOSE ? "t" : "");
-	add_var(&script_snippet, "diffstat",
-		opts->flags & REBASE_DIFFSTAT ? "t" : "");
-	add_var(&script_snippet, "force_rebase",
-		opts->flags & REBASE_FORCE ? "t" : "");
-	if (opts->switch_to)
-		add_var(&script_snippet, "switch_to", opts->switch_to);
-	add_var(&script_snippet, "action", opts->action ? opts->action : "");
-	add_var(&script_snippet, "signoff", opts->signoff ? "--signoff" : "");
-	add_var(&script_snippet, "allow_rerere_autoupdate",
-		opts->allow_rerere_autoupdate ?
-			opts->allow_rerere_autoupdate == RERERE_AUTOUPDATE ?
-			"--rerere-autoupdate" : "--no-rerere-autoupdate" : "");
-	add_var(&script_snippet, "keep_empty", opts->keep_empty ? "yes" : "");
-	add_var(&script_snippet, "autosquash", opts->autosquash ? "t" : "");
-	add_var(&script_snippet, "gpg_sign_opt", opts->gpg_sign_opt);
-	add_var(&script_snippet, "cmd", opts->cmd);
-	add_var(&script_snippet, "allow_empty_message",
-		opts->allow_empty_message ?  "--allow-empty-message" : "");
-	add_var(&script_snippet, "rebase_merges",
-		opts->rebase_merges ? "t" : "");
-	add_var(&script_snippet, "rebase_cousins",
-		opts->rebase_cousins ? "t" : "");
-	add_var(&script_snippet, "strategy", opts->strategy);
-	add_var(&script_snippet, "strategy_opts", opts->strategy_opts);
-	add_var(&script_snippet, "rebase_root", opts->root ? "t" : "");
-	add_var(&script_snippet, "squash_onto",
-		opts->squash_onto ? oid_to_hex(opts->squash_onto) : "");
-	add_var(&script_snippet, "git_format_patch_opt",
-		opts->git_format_patch_opt.buf);
-
-	if (is_interactive(opts) &&
-	    !(opts->flags & REBASE_INTERACTIVE_EXPLICIT)) {
-		strbuf_addstr(&script_snippet,
-			      "GIT_SEQUENCE_EDITOR=:; export GIT_SEQUENCE_EDITOR; ");
-		opts->autosquash = 0;
-	}
-
-	switch (opts->type) {
-	case REBASE_PRESERVE_MERGES:
-		backend = "git-rebase--preserve-merges";
-		backend_func = "git_rebase__preserve_merges";
-		break;
-	default:
+	else
 		BUG("Unhandled rebase type %d", opts->type);
-		break;
-	}
 
-	strbuf_addf(&script_snippet,
-		    ". git-sh-setup && . %s && %s", backend, backend_func);
-	argv[0] = script_snippet.buf;
-
-	status = run_command_v_opt(argv, RUN_USING_SHELL);
-finished_rebase:
 	if (opts->dont_finish_rebase)
 		; /* do nothing */
 	else if (opts->type == REBASE_INTERACTIVE)
@@ -1471,10 +1377,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 			N_("let the user edit the list of commits to rebase"),
 			PARSE_OPT_NOARG | PARSE_OPT_NONEG,
 			parse_opt_interactive },
-		OPT_SET_INT_F('p', "preserve-merges", &options.type,
-			      N_("(DEPRECATED) try to recreate merges instead of "
-				 "ignoring them"),
-			      REBASE_PRESERVE_MERGES, PARSE_OPT_HIDDEN),
 		OPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate),
 		OPT_BOOL('k', "keep-empty", &options.keep_empty,
 			 N_("preserve empty commits during rebase")),
@@ -1537,8 +1439,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 		strbuf_reset(&buf);
 		strbuf_addf(&buf, "%s/rewritten", merge_dir());
 		if (is_directory(buf.buf)) {
-			options.type = REBASE_PRESERVE_MERGES;
-			options.flags |= REBASE_INTERACTIVE_EXPLICIT;
+			die("`rebase -p` is no longer supported");
 		} else {
 			strbuf_reset(&buf);
 			strbuf_addf(&buf, "%s/interactive", merge_dir());
@@ -1568,10 +1469,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 		usage_with_options(builtin_rebase_usage,
 				   builtin_rebase_options);
 
-	if (options.type == REBASE_PRESERVE_MERGES)
-		warning(_("git rebase --preserve-merges is deprecated. "
-			  "Use --rebase-merges instead."));
-
 	if (keep_base) {
 		if (options.onto_name)
 			die(_("cannot combine '--keep-base' with '--onto'"));
@@ -1789,7 +1686,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 			die(_("--strategy requires --merge or --interactive"));
 		case REBASE_MERGE:
 		case REBASE_INTERACTIVE:
-		case REBASE_PRESERVE_MERGES:
 			/* compatible */
 			break;
 		case REBASE_UNSPECIFIED:
@@ -1812,7 +1708,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	switch (options.type) {
 	case REBASE_MERGE:
 	case REBASE_INTERACTIVE:
-	case REBASE_PRESERVE_MERGES:
 		options.state_dir = merge_dir();
 		break;
 	case REBASE_AM:
@@ -1843,28 +1738,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	}
 
 	if (options.signoff) {
-		if (options.type == REBASE_PRESERVE_MERGES)
-			die("cannot combine '--signoff' with "
-			    "'--preserve-merges'");
 		argv_array_push(&options.git_am_opts, "--signoff");
 		options.flags |= REBASE_FORCE;
 	}
 
-	if (options.type == REBASE_PRESERVE_MERGES) {
-		/*
-		 * Note: incompatibility with --signoff handled in signoff block above
-		 * Note: incompatibility with --interactive is just a strong warning;
-		 *       git-rebase.txt caveats with "unless you know what you are doing"
-		 */
-		if (options.rebase_merges)
-			die(_("cannot combine '--preserve-merges' with "
-			      "'--rebase-merges'"));
-
-		if (options.reschedule_failed_exec)
-			die(_("error: cannot combine '--preserve-merges' with "
-			      "'--reschedule-failed-exec'"));
-	}
-
 	if (!options.root) {
 		if (argc < 1) {
 			struct branch *branch;
diff --git a/git-rebase--preserve-merges.sh b/git-rebase--preserve-merges.sh
deleted file mode 100644
index dec90e9af6..0000000000
--- a/git-rebase--preserve-merges.sh
+++ /dev/null
@@ -1,1067 +0,0 @@
-# This shell script fragment is sourced by git-rebase to implement its
-# preserve-merges mode.
-#
-# Copyright (c) 2006 Johannes E. Schindelin
-#
-# The file containing rebase commands, comments, and empty lines.
-# This file is created by "git rebase -i" then edited by the user.  As
-# the lines are processed, they are removed from the front of this
-# file and written to the tail of $done.
-todo="$state_dir"/git-rebase-todo
-
-# The rebase command lines that have already been processed.  A line
-# is moved here when it is first handled, before any associated user
-# actions.
-done="$state_dir"/done
-
-# The commit message that is planned to be used for any changes that
-# need to be committed following a user interaction.
-msg="$state_dir"/message
-
-# The file into which is accumulated the suggested commit message for
-# squash/fixup commands.  When the first of a series of squash/fixups
-# is seen, the file is created and the commit message from the
-# previous commit and from the first squash/fixup commit are written
-# to it.  The commit message for each subsequent squash/fixup commit
-# is appended to the file as it is processed.
-#
-# The first line of the file is of the form
-#     # This is a combination of $count commits.
-# where $count is the number of commits whose messages have been
-# written to the file so far (including the initial "pick" commit).
-# Each time that a commit message is processed, this line is read and
-# updated.  It is deleted just before the combined commit is made.
-squash_msg="$state_dir"/message-squash
-
-# If the current series of squash/fixups has not yet included a squash
-# command, then this file exists and holds the commit message of the
-# original "pick" commit.  (If the series ends without a "squash"
-# command, then this can be used as the commit message of the combined
-# commit without opening the editor.)
-fixup_msg="$state_dir"/message-fixup
-
-# $rewritten is the name of a directory containing files for each
-# commit that is reachable by at least one merge base of $head and
-# $upstream. They are not necessarily rewritten, but their children
-# might be.  This ensures that commits on merged, but otherwise
-# unrelated side branches are left alone. (Think "X" in the man page's
-# example.)
-rewritten="$state_dir"/rewritten
-
-dropped="$state_dir"/dropped
-
-end="$state_dir"/end
-msgnum="$state_dir"/msgnum
-
-# A script to set the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and
-# GIT_AUTHOR_DATE that will be used for the commit that is currently
-# being rebased.
-author_script="$state_dir"/author-script
-
-# When an "edit" rebase command is being processed, the SHA1 of the
-# commit to be edited is recorded in this file.  When "git rebase
-# --continue" is executed, if there are any staged changes then they
-# will be amended to the HEAD commit, but only provided the HEAD
-# commit is still the commit to be edited.  When any other rebase
-# command is processed, this file is deleted.
-amend="$state_dir"/amend
-
-# For the post-rewrite hook, we make a list of rewritten commits and
-# their new sha1s.  The rewritten-pending list keeps the sha1s of
-# commits that have been processed, but not committed yet,
-# e.g. because they are waiting for a 'squash' command.
-rewritten_list="$state_dir"/rewritten-list
-rewritten_pending="$state_dir"/rewritten-pending
-
-# Work around Git for Windows' Bash whose "read" does not strip CRLF
-# and leaves CR at the end instead.
-cr=$(printf "\015")
-
-resolvemsg="
-$(gettext 'Resolve all conflicts manually, mark them as resolved with
-"git add/rm <conflicted_files>", then run "git rebase --continue".
-You can instead skip this commit: run "git rebase --skip".
-To abort and get back to the state before "git rebase", run "git rebase --abort".')
-"
-
-write_basic_state () {
-	echo "$head_name" > "$state_dir"/head-name &&
-	echo "$onto" > "$state_dir"/onto &&
-	echo "$orig_head" > "$state_dir"/orig-head &&
-	test t = "$GIT_QUIET" && : > "$state_dir"/quiet
-	test t = "$verbose" && : > "$state_dir"/verbose
-	test -n "$strategy" && echo "$strategy" > "$state_dir"/strategy
-	test -n "$strategy_opts" && echo "$strategy_opts" > \
-		"$state_dir"/strategy_opts
-	test -n "$allow_rerere_autoupdate" && echo "$allow_rerere_autoupdate" > \
-		"$state_dir"/allow_rerere_autoupdate
-	test -n "$gpg_sign_opt" && echo "$gpg_sign_opt" > "$state_dir"/gpg_sign_opt
-	test -n "$signoff" && echo "$signoff" >"$state_dir"/signoff
-	test -n "$reschedule_failed_exec" && : > "$state_dir"/reschedule-failed-exec
-}
-
-apply_autostash () {
-	if test -f "$state_dir/autostash"
-	then
-		stash_sha1=$(cat "$state_dir/autostash")
-		if git stash apply $stash_sha1 >/dev/null 2>&1
-		then
-			echo "$(gettext 'Applied autostash.')" >&2
-		else
-			git stash store -m "autostash" -q $stash_sha1 ||
-			die "$(eval_gettext "Cannot store \$stash_sha1")"
-			gettext 'Applying autostash resulted in conflicts.
-Your changes are safe in the stash.
-You can run "git stash pop" or "git stash drop" at any time.
-' >&2
-		fi
-	fi
-}
-
-output () {
-	case "$verbose" in
-	'')
-		output=$("$@" 2>&1 )
-		status=$?
-		test $status != 0 && printf "%s\n" "$output"
-		return $status
-		;;
-	*)
-		"$@"
-		;;
-	esac
-}
-
-strategy_args=${strategy:+--strategy=$strategy}
-test -n "$strategy_opts" &&
-eval '
-	for strategy_opt in '"$strategy_opts"'
-	do
-		strategy_args="$strategy_args -X$(git rev-parse --sq-quote "${strategy_opt#--}")"
-	done
-'
-
-GIT_CHERRY_PICK_HELP="$resolvemsg"
-export GIT_CHERRY_PICK_HELP
-
-comment_char=$(git config --get core.commentchar 2>/dev/null)
-case "$comment_char" in
-'' | auto)
-	comment_char="#"
-	;;
-?)
-	;;
-*)
-	comment_char=$(echo "$comment_char" | cut -c1)
-	;;
-esac
-
-warn () {
-	printf '%s\n' "$*" >&2
-}
-
-# Output the commit message for the specified commit.
-commit_message () {
-	git cat-file commit "$1" | sed "1,/^$/d"
-}
-
-orig_reflog_action="$GIT_REFLOG_ACTION"
-
-comment_for_reflog () {
-	case "$orig_reflog_action" in
-	''|rebase*)
-		GIT_REFLOG_ACTION="rebase -i ($1)"
-		export GIT_REFLOG_ACTION
-		;;
-	esac
-}
-
-last_count=
-mark_action_done () {
-	sed -e 1q < "$todo" >> "$done"
-	sed -e 1d < "$todo" >> "$todo".new
-	mv -f "$todo".new "$todo"
-	new_count=$(( $(git stripspace --strip-comments <"$done" | wc -l) ))
-	echo $new_count >"$msgnum"
-	total=$(($new_count + $(git stripspace --strip-comments <"$todo" | wc -l)))
-	echo $total >"$end"
-	if test "$last_count" != "$new_count"
-	then
-		last_count=$new_count
-		eval_gettext "Rebasing (\$new_count/\$total)"; printf "\r"
-		test -z "$verbose" || echo
-	fi
-}
-
-# Put the last action marked done at the beginning of the todo list
-# again. If there has not been an action marked done yet, leave the list of
-# items on the todo list unchanged.
-reschedule_last_action () {
-	tail -n 1 "$done" | cat - "$todo" >"$todo".new
-	sed -e \$d <"$done" >"$done".new
-	mv -f "$todo".new "$todo"
-	mv -f "$done".new "$done"
-}
-
-append_todo_help () {
-	gettext "
-Commands:
-p, pick <commit> = use commit
-r, reword <commit> = use commit, but edit the commit message
-e, edit <commit> = use commit, but stop for amending
-s, squash <commit> = use commit, but meld into previous commit
-f, fixup <commit> = like \"squash\", but discard this commit's log message
-x, exec <commit> = run command (the rest of the line) using shell
-d, drop <commit> = remove commit
-l, label <label> = label current HEAD with a name
-t, reset <label> = reset HEAD to a label
-m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
-.       create a merge commit using the original merge commit's
-.       message (or the oneline, if no original merge commit was
-.       specified). Use -c <commit> to reword the commit message.
-
-These lines can be re-ordered; they are executed from top to bottom.
-" | git stripspace --comment-lines >>"$todo"
-
-	if test $(get_missing_commit_check_level) = error
-	then
-		gettext "
-Do not remove any line. Use 'drop' explicitly to remove a commit.
-" | git stripspace --comment-lines >>"$todo"
-	else
-		gettext "
-If you remove a line here THAT COMMIT WILL BE LOST.
-" | git stripspace --comment-lines >>"$todo"
-	fi
-}
-
-make_patch () {
-	sha1_and_parents="$(git rev-list --parents -1 "$1")"
-	case "$sha1_and_parents" in
-	?*' '?*' '?*)
-		git diff --cc $sha1_and_parents
-		;;
-	?*' '?*)
-		git diff-tree -p "$1^!"
-		;;
-	*)
-		echo "Root commit"
-		;;
-	esac > "$state_dir"/patch
-	test -f "$msg" ||
-		commit_message "$1" > "$msg"
-	test -f "$author_script" ||
-		get_author_ident_from_commit "$1" > "$author_script"
-}
-
-die_with_patch () {
-	echo "$1" > "$state_dir"/stopped-sha
-	git update-ref REBASE_HEAD "$1"
-	make_patch "$1"
-	die "$2"
-}
-
-exit_with_patch () {
-	echo "$1" > "$state_dir"/stopped-sha
-	git update-ref REBASE_HEAD "$1"
-	make_patch $1
-	git rev-parse --verify HEAD > "$amend"
-	gpg_sign_opt_quoted=${gpg_sign_opt:+$(git rev-parse --sq-quote "$gpg_sign_opt")}
-	warn "$(eval_gettext "\
-You can amend the commit now, with
-
-	git commit --amend \$gpg_sign_opt_quoted
-
-Once you are satisfied with your changes, run
-
-	git rebase --continue")"
-	warn
-	exit $2
-}
-
-die_abort () {
-	apply_autostash
-	rm -rf "$state_dir"
-	die "$1"
-}
-
-has_action () {
-	test -n "$(git stripspace --strip-comments <"$1")"
-}
-
-is_empty_commit() {
-	tree=$(git rev-parse -q --verify "$1"^{tree} 2>/dev/null) || {
-		sha1=$1
-		die "$(eval_gettext "\$sha1: not a commit that can be picked")"
-	}
-	ptree=$(git rev-parse -q --verify "$1"^^{tree} 2>/dev/null) ||
-		ptree=4b825dc642cb6eb9a060e54bf8d69288fbee4904
-	test "$tree" = "$ptree"
-}
-
-is_merge_commit()
-{
-	git rev-parse --verify --quiet "$1"^2 >/dev/null 2>&1
-}
-
-# Run command with GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and
-# GIT_AUTHOR_DATE exported from the current environment.
-do_with_author () {
-	(
-		export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE
-		"$@"
-	)
-}
-
-git_sequence_editor () {
-	if test -z "$GIT_SEQUENCE_EDITOR"
-	then
-		GIT_SEQUENCE_EDITOR="$(git config sequence.editor)"
-		if [ -z "$GIT_SEQUENCE_EDITOR" ]
-		then
-			GIT_SEQUENCE_EDITOR="$(git var GIT_EDITOR)" || return $?
-		fi
-	fi
-
-	eval "$GIT_SEQUENCE_EDITOR" '"$@"'
-}
-
-pick_one () {
-	ff=--ff
-
-	case "$1" in -n) sha1=$2; ff= ;; *) sha1=$1 ;; esac
-	case "$force_rebase" in '') ;; ?*) ff= ;; esac
-	output git rev-parse --verify $sha1 || die "$(eval_gettext "Invalid commit name: \$sha1")"
-
-	if is_empty_commit "$sha1"
-	then
-		empty_args="--allow-empty"
-	fi
-
-	pick_one_preserving_merges "$@"
-}
-
-pick_one_preserving_merges () {
-	fast_forward=t
-	case "$1" in
-	-n)
-		fast_forward=f
-		sha1=$2
-		;;
-	*)
-		sha1=$1
-		;;
-	esac
-	sha1=$(git rev-parse $sha1)
-
-	if test -f "$state_dir"/current-commit && test "$fast_forward" = t
-	then
-		while read current_commit
-		do
-			git rev-parse HEAD > "$rewritten"/$current_commit
-		done <"$state_dir"/current-commit
-		rm "$state_dir"/current-commit ||
-			die "$(gettext "Cannot write current commit's replacement sha1")"
-	fi
-
-	echo $sha1 >> "$state_dir"/current-commit
-
-	# rewrite parents; if none were rewritten, we can fast-forward.
-	new_parents=
-	pend=" $(git rev-list --parents -1 $sha1 | cut -d' ' -s -f2-)"
-	if test "$pend" = " "
-	then
-		pend=" root"
-	fi
-	while [ "$pend" != "" ]
-	do
-		p=$(expr "$pend" : ' \([^ ]*\)')
-		pend="${pend# $p}"
-
-		if test -f "$rewritten"/$p
-		then
-			new_p=$(cat "$rewritten"/$p)
-
-			# If the todo reordered commits, and our parent is marked for
-			# rewriting, but hasn't been gotten to yet, assume the user meant to
-			# drop it on top of the current HEAD
-			if test -z "$new_p"
-			then
-				new_p=$(git rev-parse HEAD)
-			fi
-
-			test $p != $new_p && fast_forward=f
-			case "$new_parents" in
-			*$new_p*)
-				;; # do nothing; that parent is already there
-			*)
-				new_parents="$new_parents $new_p"
-				;;
-			esac
-		else
-			if test -f "$dropped"/$p
-			then
-				fast_forward=f
-				replacement="$(cat "$dropped"/$p)"
-				test -z "$replacement" && replacement=root
-				pend=" $replacement$pend"
-			else
-				new_parents="$new_parents $p"
-			fi
-		fi
-	done
-	case $fast_forward in
-	t)
-		output warn "$(eval_gettext "Fast-forward to \$sha1")"
-		output git reset --hard $sha1 ||
-			die "$(eval_gettext "Cannot fast-forward to \$sha1")"
-		;;
-	f)
-		first_parent=$(expr "$new_parents" : ' \([^ ]*\)')
-
-		if [ "$1" != "-n" ]
-		then
-			# detach HEAD to current parent
-			output git checkout $first_parent 2> /dev/null ||
-				die "$(eval_gettext "Cannot move HEAD to \$first_parent")"
-		fi
-
-		case "$new_parents" in
-		' '*' '*)
-			test "a$1" = a-n && die "$(eval_gettext "Refusing to squash a merge: \$sha1")"
-
-			# redo merge
-			author_script_content=$(get_author_ident_from_commit $sha1)
-			eval "$author_script_content"
-			msg_content="$(commit_message $sha1)"
-			# No point in merging the first parent, that's HEAD
-			new_parents=${new_parents# $first_parent}
-			merge_args="--no-log --no-ff"
-			if ! do_with_author output eval \
-				git merge ${gpg_sign_opt:+$(git rev-parse \
-					--sq-quote "$gpg_sign_opt")} \
-				$allow_rerere_autoupdate "$merge_args" \
-				"$strategy_args" \
-				-m "$(git rev-parse --sq-quote "$msg_content")" \
-				"$new_parents"
-			then
-				printf "%s\n" "$msg_content" > "$GIT_DIR"/MERGE_MSG
-				die_with_patch $sha1 "$(eval_gettext "Error redoing merge \$sha1")"
-			fi
-			echo "$sha1 $(git rev-parse HEAD^0)" >> "$rewritten_list"
-			;;
-		*)
-			output eval git cherry-pick $allow_rerere_autoupdate \
-				$allow_empty_message \
-				${gpg_sign_opt:+$(git rev-parse --sq-quote "$gpg_sign_opt")} \
-				"$strategy_args" "$@" ||
-				die_with_patch $sha1 "$(eval_gettext "Could not pick \$sha1")"
-			;;
-		esac
-		;;
-	esac
-}
-
-this_nth_commit_message () {
-	n=$1
-	eval_gettext "This is the commit message #\${n}:"
-}
-
-skip_nth_commit_message () {
-	n=$1
-	eval_gettext "The commit message #\${n} will be skipped:"
-}
-
-update_squash_messages () {
-	if test -f "$squash_msg"; then
-		mv "$squash_msg" "$squash_msg".bak || exit
-		count=$(($(sed -n \
-			-e "1s/^$comment_char[^0-9]*\([0-9][0-9]*\).*/\1/p" \
-			-e "q" < "$squash_msg".bak)+1))
-		{
-			printf '%s\n' "$comment_char $(eval_ngettext \
-				"This is a combination of \$count commit." \
-				"This is a combination of \$count commits." \
-				$count)"
-			sed -e 1d -e '2,/^./{
-				/^$/d
-			}' <"$squash_msg".bak
-		} >"$squash_msg"
-	else
-		commit_message HEAD >"$fixup_msg" ||
-		die "$(eval_gettext "Cannot write \$fixup_msg")"
-		count=2
-		{
-			printf '%s\n' "$comment_char $(gettext "This is a combination of 2 commits.")"
-			printf '%s\n' "$comment_char $(gettext "This is the 1st commit message:")"
-			echo
-			cat "$fixup_msg"
-		} >"$squash_msg"
-	fi
-	case $1 in
-	squash)
-		rm -f "$fixup_msg"
-		echo
-		printf '%s\n' "$comment_char $(this_nth_commit_message $count)"
-		echo
-		commit_message $2
-		;;
-	fixup)
-		echo
-		printf '%s\n' "$comment_char $(skip_nth_commit_message $count)"
-		echo
-		# Change the space after the comment character to TAB:
-		commit_message $2 | git stripspace --comment-lines | sed -e 's/ /	/'
-		;;
-	esac >>"$squash_msg"
-}
-
-peek_next_command () {
-	git stripspace --strip-comments <"$todo" | sed -n -e 's/ .*//p' -e q
-}
-
-# A squash/fixup has failed.  Prepare the long version of the squash
-# commit message, then die_with_patch.  This code path requires the
-# user to edit the combined commit message for all commits that have
-# been squashed/fixedup so far.  So also erase the old squash
-# messages, effectively causing the combined commit to be used as the
-# new basis for any further squash/fixups.  Args: sha1 rest
-die_failed_squash() {
-	sha1=$1
-	rest=$2
-	mv "$squash_msg" "$msg" || exit
-	rm -f "$fixup_msg"
-	cp "$msg" "$GIT_DIR"/MERGE_MSG || exit
-	warn
-	warn "$(eval_gettext "Could not apply \$sha1... \$rest")"
-	die_with_patch $sha1 ""
-}
-
-flush_rewritten_pending() {
-	test -s "$rewritten_pending" || return
-	newsha1="$(git rev-parse HEAD^0)"
-	sed "s/$/ $newsha1/" < "$rewritten_pending" >> "$rewritten_list"
-	rm -f "$rewritten_pending"
-}
-
-record_in_rewritten() {
-	oldsha1="$(git rev-parse $1)"
-	echo "$oldsha1" >> "$rewritten_pending"
-
-	case "$(peek_next_command)" in
-	squash|s|fixup|f)
-		;;
-	*)
-		flush_rewritten_pending
-		;;
-	esac
-}
-
-do_pick () {
-	sha1=$1
-	rest=$2
-	if test "$(git rev-parse HEAD)" = "$squash_onto"
-	then
-		# Set the correct commit message and author info on the
-		# sentinel root before cherry-picking the original changes
-		# without committing (-n).  Finally, update the sentinel again
-		# to include these changes.  If the cherry-pick results in a
-		# conflict, this means our behaviour is similar to a standard
-		# failed cherry-pick during rebase, with a dirty index to
-		# resolve before manually running git commit --amend then git
-		# rebase --continue.
-		git commit --allow-empty --allow-empty-message --amend \
-			   --no-post-rewrite -n -q -C $sha1 $signoff &&
-			pick_one -n $sha1 &&
-			git commit --allow-empty --allow-empty-message \
-				   --amend --no-post-rewrite -n -q -C $sha1 $signoff \
-				   ${gpg_sign_opt:+"$gpg_sign_opt"} ||
-				   die_with_patch $sha1 "$(eval_gettext "Could not apply \$sha1... \$rest")"
-	else
-		pick_one $sha1 ||
-			die_with_patch $sha1 "$(eval_gettext "Could not apply \$sha1... \$rest")"
-	fi
-}
-
-do_next () {
-	rm -f "$msg" "$author_script" "$amend" "$state_dir"/stopped-sha || exit
-	read -r command sha1 rest < "$todo"
-	case "$command" in
-	"$comment_char"*|''|noop|drop|d)
-		mark_action_done
-		;;
-	"$cr")
-		# Work around CR left by "read" (e.g. with Git for Windows' Bash).
-		mark_action_done
-		;;
-	pick|p)
-		comment_for_reflog pick
-
-		mark_action_done
-		do_pick $sha1 "$rest"
-		record_in_rewritten $sha1
-		;;
-	reword|r)
-		comment_for_reflog reword
-
-		mark_action_done
-		do_pick $sha1 "$rest"
-		git commit --amend --no-post-rewrite ${gpg_sign_opt:+"$gpg_sign_opt"} \
-			$allow_empty_message || {
-			warn "$(eval_gettext "\
-Could not amend commit after successfully picking \$sha1... \$rest
-This is most likely due to an empty commit message, or the pre-commit hook
-failed. If the pre-commit hook failed, you may need to resolve the issue before
-you are able to reword the commit.")"
-			exit_with_patch $sha1 1
-		}
-		record_in_rewritten $sha1
-		;;
-	edit|e)
-		comment_for_reflog edit
-
-		mark_action_done
-		do_pick $sha1 "$rest"
-		sha1_abbrev=$(git rev-parse --short $sha1)
-		warn "$(eval_gettext "Stopped at \$sha1_abbrev... \$rest")"
-		exit_with_patch $sha1 0
-		;;
-	squash|s|fixup|f)
-		case "$command" in
-		squash|s)
-			squash_style=squash
-			;;
-		fixup|f)
-			squash_style=fixup
-			;;
-		esac
-		comment_for_reflog $squash_style
-
-		test -f "$done" && has_action "$done" ||
-			die "$(eval_gettext "Cannot '\$squash_style' without a previous commit")"
-
-		mark_action_done
-		update_squash_messages $squash_style $sha1
-		author_script_content=$(get_author_ident_from_commit HEAD)
-		echo "$author_script_content" > "$author_script"
-		eval "$author_script_content"
-		if ! pick_one -n $sha1
-		then
-			git rev-parse --verify HEAD >"$amend"
-			die_failed_squash $sha1 "$rest"
-		fi
-		case "$(peek_next_command)" in
-		squash|s|fixup|f)
-			# This is an intermediate commit; its message will only be
-			# used in case of trouble.  So use the long version:
-			do_with_author output git commit --amend --no-verify -F "$squash_msg" \
-				${gpg_sign_opt:+"$gpg_sign_opt"} $allow_empty_message ||
-				die_failed_squash $sha1 "$rest"
-			;;
-		*)
-			# This is the final command of this squash/fixup group
-			if test -f "$fixup_msg"
-			then
-				do_with_author git commit --amend --no-verify -F "$fixup_msg" \
-					${gpg_sign_opt:+"$gpg_sign_opt"} $allow_empty_message ||
-					die_failed_squash $sha1 "$rest"
-			else
-				cp "$squash_msg" "$GIT_DIR"/SQUASH_MSG || exit
-				rm -f "$GIT_DIR"/MERGE_MSG
-				do_with_author git commit --amend --no-verify -F "$GIT_DIR"/SQUASH_MSG -e \
-					${gpg_sign_opt:+"$gpg_sign_opt"} $allow_empty_message ||
-					die_failed_squash $sha1 "$rest"
-			fi
-			rm -f "$squash_msg" "$fixup_msg"
-			;;
-		esac
-		record_in_rewritten $sha1
-		;;
-	x|"exec")
-		read -r command rest < "$todo"
-		mark_action_done
-		eval_gettextln "Executing: \$rest"
-		"${SHELL:-@SHELL_PATH@}" -c "$rest" # Actual execution
-		status=$?
-		# Run in subshell because require_clean_work_tree can die.
-		dirty=f
-		(require_clean_work_tree "rebase" 2>/dev/null) || dirty=t
-		if test "$status" -ne 0
-		then
-			warn "$(eval_gettext "Execution failed: \$rest")"
-			test "$dirty" = f ||
-				warn "$(gettext "and made changes to the index and/or the working tree")"
-
-			warn "$(gettext "\
-You can fix the problem, and then run
-
-	git rebase --continue")"
-			warn
-			if test $status -eq 127		# command not found
-			then
-				status=1
-			fi
-			exit "$status"
-		elif test "$dirty" = t
-		then
-			# TRANSLATORS: after these lines is a command to be issued by the user
-			warn "$(eval_gettext "\
-Execution succeeded: \$rest
-but left changes to the index and/or the working tree
-Commit or stash your changes, and then run
-
-	git rebase --continue")"
-			warn
-			exit 1
-		fi
-		;;
-	*)
-		warn "$(eval_gettext "Unknown command: \$command \$sha1 \$rest")"
-		fixtodo="$(gettext "Please fix this using 'git rebase --edit-todo'.")"
-		if git rev-parse --verify -q "$sha1" >/dev/null
-		then
-			die_with_patch $sha1 "$fixtodo"
-		else
-			die "$fixtodo"
-		fi
-		;;
-	esac
-	test -s "$todo" && return
-
-	comment_for_reflog finish &&
-	newhead=$(git rev-parse HEAD) &&
-	case $head_name in
-	refs/*)
-		message="$GIT_REFLOG_ACTION: $head_name onto $onto" &&
-		git update-ref -m "$message" $head_name $newhead $orig_head &&
-		git symbolic-ref \
-		  -m "$GIT_REFLOG_ACTION: returning to $head_name" \
-		  HEAD $head_name
-		;;
-	esac && {
-		test ! -f "$state_dir"/verbose ||
-			git diff-tree --stat $orig_head..HEAD
-	} &&
-	{
-		test -s "$rewritten_list" &&
-		git notes copy --for-rewrite=rebase < "$rewritten_list" ||
-		true # we don't care if this copying failed
-	} &&
-	hook="$(git rev-parse --git-path hooks/post-rewrite)"
-	if test -x "$hook" && test -s "$rewritten_list"; then
-		"$hook" rebase < "$rewritten_list"
-		true # we don't care if this hook failed
-	fi &&
-		warn "$(eval_gettext "Successfully rebased and updated \$head_name.")"
-
-	return 1 # not failure; just to break the do_rest loop
-}
-
-# can only return 0, when the infinite loop breaks
-do_rest () {
-	while :
-	do
-		do_next || break
-	done
-}
-
-expand_todo_ids() {
-	git rebase--interactive --expand-ids
-}
-
-collapse_todo_ids() {
-	git rebase--interactive --shorten-ids
-}
-
-# Switch to the branch in $into and notify it in the reflog
-checkout_onto () {
-	GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $onto_name"
-	output git checkout $onto || die_abort "$(gettext "could not detach HEAD")"
-	git update-ref ORIG_HEAD $orig_head
-}
-
-get_missing_commit_check_level () {
-	check_level=$(git config --get rebase.missingCommitsCheck)
-	check_level=${check_level:-ignore}
-	# Don't be case sensitive
-	printf '%s' "$check_level" | tr 'A-Z' 'a-z'
-}
-
-# Initiate an action. If the cannot be any
-# further action it  may exec a command
-# or exit and not return.
-#
-# TODO: Consider a cleaner return model so it
-# never exits and always return 0 if process
-# is complete.
-#
-# Parameter 1 is the action to initiate.
-#
-# Returns 0 if the action was able to complete
-# and if 1 if further processing is required.
-initiate_action () {
-	case "$1" in
-	continue)
-		# do we have anything to commit?
-		if git diff-index --cached --quiet HEAD --
-		then
-			# Nothing to commit -- skip this commit
-
-			test ! -f "$GIT_DIR"/CHERRY_PICK_HEAD ||
-			rm "$GIT_DIR"/CHERRY_PICK_HEAD ||
-			die "$(gettext "Could not remove CHERRY_PICK_HEAD")"
-		else
-			if ! test -f "$author_script"
-			then
-				gpg_sign_opt_quoted=${gpg_sign_opt:+$(git rev-parse --sq-quote "$gpg_sign_opt")}
-				die "$(eval_gettext "\
-You have staged changes in your working tree.
-If these changes are meant to be
-squashed into the previous commit, run:
-
-  git commit --amend \$gpg_sign_opt_quoted
-
-If they are meant to go into a new commit, run:
-
-  git commit \$gpg_sign_opt_quoted
-
-In both cases, once you're done, continue with:
-
-  git rebase --continue
-")"
-			fi
-			. "$author_script" ||
-				die "$(gettext "Error trying to find the author identity to amend commit")"
-			if test -f "$amend"
-			then
-				current_head=$(git rev-parse --verify HEAD)
-				test "$current_head" = $(cat "$amend") ||
-				die "$(gettext "\
-You have uncommitted changes in your working tree. Please commit them
-first and then run 'git rebase --continue' again.")"
-				do_with_author git commit --amend --no-verify -F "$msg" -e \
-					${gpg_sign_opt:+"$gpg_sign_opt"} $allow_empty_message ||
-					die "$(gettext "Could not commit staged changes.")"
-			else
-				do_with_author git commit --no-verify -F "$msg" -e \
-					${gpg_sign_opt:+"$gpg_sign_opt"} $allow_empty_message ||
-					die "$(gettext "Could not commit staged changes.")"
-			fi
-		fi
-
-		if test -r "$state_dir"/stopped-sha
-		then
-			record_in_rewritten "$(cat "$state_dir"/stopped-sha)"
-		fi
-
-		require_clean_work_tree "rebase"
-		do_rest
-		return 0
-		;;
-	skip)
-		git rerere clear
-		do_rest
-		return 0
-		;;
-	edit-todo)
-		git stripspace --strip-comments <"$todo" >"$todo".new
-		mv -f "$todo".new "$todo"
-		collapse_todo_ids
-		append_todo_help
-		gettext "
-You are editing the todo file of an ongoing interactive rebase.
-To continue rebase after editing, run:
-    git rebase --continue
-
-" | git stripspace --comment-lines >>"$todo"
-
-		git_sequence_editor "$todo" ||
-			die "$(gettext "Could not execute editor")"
-		expand_todo_ids
-
-		exit
-		;;
-	show-current-patch)
-		exec git show REBASE_HEAD --
-		;;
-	*)
-		return 1 # continue
-		;;
-	esac
-}
-
-setup_reflog_action () {
-	comment_for_reflog start
-
-	if test ! -z "$switch_to"
-	then
-		GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $switch_to"
-		output git checkout "$switch_to" -- ||
-			die "$(eval_gettext "Could not checkout \$switch_to")"
-
-		comment_for_reflog start
-	fi
-}
-
-init_basic_state () {
-	orig_head=$(git rev-parse --verify HEAD) || die "$(gettext "No HEAD?")"
-	mkdir -p "$state_dir" || die "$(eval_gettext "Could not create temporary \$state_dir")"
-	rm -f "$(git rev-parse --git-path REBASE_HEAD)"
-
-	: > "$state_dir"/interactive || die "$(gettext "Could not mark as interactive")"
-	write_basic_state
-}
-
-init_revisions_and_shortrevisions () {
-	shorthead=$(git rev-parse --short $orig_head)
-	shortonto=$(git rev-parse --short $onto)
-	if test -z "$rebase_root"
-		# this is now equivalent to ! -z "$upstream"
-	then
-		shortupstream=$(git rev-parse --short $upstream)
-		revisions=$upstream...$orig_head
-		shortrevisions=$shortupstream..$shorthead
-	else
-		revisions=$onto...$orig_head
-		shortrevisions=$shorthead
-		test -z "$squash_onto" ||
-		echo "$squash_onto" >"$state_dir"/squash-onto
-	fi
-}
-
-complete_action() {
-	test -s "$todo" || echo noop >> "$todo"
-	test -z "$autosquash" || git rebase--interactive --rearrange-squash || exit
-	test -n "$cmd" && git rebase--interactive --add-exec-commands --cmd "$cmd"
-
-	todocount=$(git stripspace --strip-comments <"$todo" | wc -l)
-	todocount=${todocount##* }
-
-cat >>"$todo" <<EOF
-
-$comment_char $(eval_ngettext \
-	"Rebase \$shortrevisions onto \$shortonto (\$todocount command)" \
-	"Rebase \$shortrevisions onto \$shortonto (\$todocount commands)" \
-	"$todocount")
-EOF
-	append_todo_help
-	gettext "
-However, if you remove everything, the rebase will be aborted.
-
-" | git stripspace --comment-lines >>"$todo"
-
-	if test -z "$keep_empty"
-	then
-		printf '%s\n' "$comment_char $(gettext "Note that empty commits are commented out")" >>"$todo"
-	fi
-
-
-	has_action "$todo" ||
-		return 2
-
-	cp "$todo" "$todo".backup
-	collapse_todo_ids
-	git_sequence_editor "$todo" ||
-		die_abort "$(gettext "Could not execute editor")"
-
-	has_action "$todo" ||
-		return 2
-
-	git rebase--interactive --check-todo-list || {
-		ret=$?
-		checkout_onto
-		exit $ret
-	}
-
-	expand_todo_ids
-	checkout_onto
-	do_rest
-}
-
-git_rebase__preserve_merges () {
-	initiate_action "$action"
-	ret=$?
-	if test $ret = 0; then
-		return 0
-	fi
-
-	setup_reflog_action
-	init_basic_state
-
-	if test -z "$rebase_root"
-	then
-		mkdir "$rewritten" &&
-		for c in $(git merge-base --all $orig_head $upstream)
-		do
-			echo $onto > "$rewritten"/$c ||
-				die "$(gettext "Could not init rewritten commits")"
-		done
-	else
-		mkdir "$rewritten" &&
-		echo $onto > "$rewritten"/root ||
-			die "$(gettext "Could not init rewritten commits")"
-	fi
-
-	init_revisions_and_shortrevisions
-
-	format=$(git config --get rebase.instructionFormat)
-	# the 'rev-list .. | sed' requires %m to parse; the instruction requires %H to parse
-	git rev-list --format="%m%H ${format:-%s}" \
-		--reverse --left-right --topo-order \
-		$revisions ${restrict_revision+^$restrict_revision} | \
-		sed -n "s/^>//p" |
-	while read -r sha1 rest
-	do
-		if test -z "$keep_empty" && is_empty_commit $sha1 && ! is_merge_commit $sha1
-		then
-			comment_out="$comment_char "
-		else
-			comment_out=
-		fi
-
-		if test -z "$rebase_root"
-		then
-			preserve=t
-			for p in $(git rev-list --parents -1 $sha1 | cut -d' ' -s -f2-)
-			do
-				if test -f "$rewritten"/$p
-				then
-					preserve=f
-				fi
-			done
-		else
-			preserve=f
-		fi
-		if test f = "$preserve"
-		then
-			touch "$rewritten"/$sha1
-			printf '%s\n' "${comment_out}pick $sha1 $rest" >>"$todo"
-		fi
-	done
-
-	# Watch for commits that been dropped by --cherry-pick
-	mkdir "$dropped"
-	# Save all non-cherry-picked changes
-	git rev-list $revisions --left-right --cherry-pick | \
-		sed -n "s/^>//p" > "$state_dir"/not-cherry-picks
-	# Now all commits and note which ones are missing in
-	# not-cherry-picks and hence being dropped
-	git rev-list $revisions |
-	while read rev
-	do
-		if test -f "$rewritten"/$rev &&
-		   ! sane_grep "$rev" "$state_dir"/not-cherry-picks >/dev/null
-		then
-			# Use -f2 because if rev-list is telling us this commit is
-			# not worthwhile, we don't want to track its multiple heads,
-			# just the history of its first-parent for others that will
-			# be rebasing on top of it
-			git rev-list --parents -1 $rev | cut -d' ' -s -f2 > "$dropped"/$rev
-			sha1=$(git rev-list -1 $rev)
-			sane_grep -v "^[a-z][a-z]* $sha1" <"$todo" > "${todo}2" ; mv "${todo}2" "$todo"
-			rm "$rewritten"/$rev
-		fi
-	done
-
-	complete_action
-}
-- 
gitgitgadget


^ permalink raw reply related	[relevance 2%]

* [PATCH v4 15/21] parse-options: move doc to parse-options.h
  @ 2019-11-15  9:53  4%       ` Heba Waly via GitGitGadget
  0 siblings, 0 replies; 200+ results
From: Heba Waly via GitGitGadget @ 2019-11-15  9:53 UTC (permalink / raw)
  To: git; +Cc: Heba Waly, Junio C Hamano, Heba Waly

From: Heba Waly <heba.waly@gmail.com>

Move the documentation from Documentation/technical/api-parse-options.txt
to parse-options.h as it's easier for the developers to find the usage
information beside the code instead of looking for it in another doc file.

Documentation/technical/api-parse-options.txt is removed because the
information it has is now redundant and it'll be hard to keep it up to
date and synchronized with the documentation in the header file.

Documentation/MyFirstContribution.txt now link
to parse-options.h instead of Documentation/technical/api-parse-options.txt for
details about parsing options.

Signed-off-by: Heba Waly <heba.waly@gmail.com>
---
 Documentation/MyFirstContribution.txt         |   2 +-
 Documentation/technical/api-parse-options.txt | 313 -----------------
 parse-options.h                               | 328 ++++++++++++++++++
 3 files changed, 329 insertions(+), 314 deletions(-)
 delete mode 100644 Documentation/technical/api-parse-options.txt

diff --git a/Documentation/MyFirstContribution.txt b/Documentation/MyFirstContribution.txt
index 5e9b808f5f..a2a91d92e2 100644
--- a/Documentation/MyFirstContribution.txt
+++ b/Documentation/MyFirstContribution.txt
@@ -487,7 +487,7 @@ Try and run `./bin-wrappers/git psuh -h`. Your command should crash at the end.
 That's because `-h` is a special case which your command should handle by
 printing usage.
 
-Take a look at `Documentation/technical/api-parse-options.txt`. This is a handy
+Take a look at `parse-options.h`. This is a handy
 tool for pulling out options you need to be able to handle, and it takes a
 usage string.
 
diff --git a/Documentation/technical/api-parse-options.txt b/Documentation/technical/api-parse-options.txt
deleted file mode 100644
index 2e2e7c10c6..0000000000
--- a/Documentation/technical/api-parse-options.txt
+++ /dev/null
@@ -1,313 +0,0 @@
-parse-options API
-=================
-
-The parse-options API is used to parse and massage options in Git
-and to provide a usage help with consistent look.
-
-Basics
-------
-
-The argument vector `argv[]` may usually contain mandatory or optional
-'non-option arguments', e.g. a filename or a branch, and 'options'.
-Options are optional arguments that start with a dash and
-that allow to change the behavior of a command.
-
-* There are basically three types of options:
-  'boolean' options,
-  options with (mandatory) 'arguments' and
-  options with 'optional arguments'
-  (i.e. a boolean option that can be adjusted).
-
-* There are basically two forms of options:
-  'Short options' consist of one dash (`-`) and one alphanumeric
-  character.
-  'Long options' begin with two dashes (`--`) and some
-  alphanumeric characters.
-
-* Options are case-sensitive.
-  Please define 'lower-case long options' only.
-
-The parse-options API allows:
-
-* 'stuck' and 'separate form' of options with arguments.
-  `-oArg` is stuck, `-o Arg` is separate form.
-  `--option=Arg` is stuck, `--option Arg` is separate form.
-
-* Long options may be 'abbreviated', as long as the abbreviation
-  is unambiguous.
-
-* Short options may be bundled, e.g. `-a -b` can be specified as `-ab`.
-
-* Boolean long options can be 'negated' (or 'unset') by prepending
-  `no-`, e.g. `--no-abbrev` instead of `--abbrev`. Conversely,
-  options that begin with `no-` can be 'negated' by removing it.
-  Other long options can be unset (e.g., set string to NULL, set
-  integer to 0) by prepending `no-`.
-
-* Options and non-option arguments can clearly be separated using the `--`
-  option, e.g. `-a -b --option -- --this-is-a-file` indicates that
-  `--this-is-a-file` must not be processed as an option.
-
-Steps to parse options
-----------------------
-
-. `#include "parse-options.h"`
-
-. define a NULL-terminated
-  `static const char * const builtin_foo_usage[]` array
-  containing alternative usage strings
-
-. define `builtin_foo_options` array as described below
-  in section 'Data Structure'.
-
-. in `cmd_foo(int argc, const char **argv, const char *prefix)`
-  call
-
-	argc = parse_options(argc, argv, prefix, builtin_foo_options, builtin_foo_usage, flags);
-+
-`parse_options()` will filter out the processed options of `argv[]` and leave the
-non-option arguments in `argv[]`.
-`argc` is updated appropriately because of the assignment.
-+
-You can also pass NULL instead of a usage array as the fifth parameter of
-parse_options(), to avoid displaying a help screen with usage info and
-option list.  This should only be done if necessary, e.g. to implement
-a limited parser for only a subset of the options that needs to be run
-before the full parser, which in turn shows the full help message.
-+
-Flags are the bitwise-or of:
-
-`PARSE_OPT_KEEP_DASHDASH`::
-	Keep the `--` that usually separates options from
-	non-option arguments.
-
-`PARSE_OPT_STOP_AT_NON_OPTION`::
-	Usually the whole argument vector is massaged and reordered.
-	Using this flag, processing is stopped at the first non-option
-	argument.
-
-`PARSE_OPT_KEEP_ARGV0`::
-	Keep the first argument, which contains the program name.  It's
-	removed from argv[] by default.
-
-`PARSE_OPT_KEEP_UNKNOWN`::
-	Keep unknown arguments instead of erroring out.  This doesn't
-	work for all combinations of arguments as users might expect
-	it to do.  E.g. if the first argument in `--unknown --known`
-	takes a value (which we can't know), the second one is
-	mistakenly interpreted as a known option.  Similarly, if
-	`PARSE_OPT_STOP_AT_NON_OPTION` is set, the second argument in
-	`--unknown value` will be mistakenly interpreted as a
-	non-option, not as a value belonging to the unknown option,
-	the parser early.  That's why parse_options() errors out if
-	both options are set.
-
-`PARSE_OPT_NO_INTERNAL_HELP`::
-	By default, parse_options() handles `-h`, `--help` and
-	`--help-all` internally, by showing a help screen.  This option
-	turns it off and allows one to add custom handlers for these
-	options, or to just leave them unknown.
-
-Data Structure
---------------
-
-The main data structure is an array of the `option` struct,
-say `static struct option builtin_add_options[]`.
-There are some macros to easily define options:
-
-`OPT__ABBREV(&int_var)`::
-	Add `--abbrev[=<n>]`.
-
-`OPT__COLOR(&int_var, description)`::
-	Add `--color[=<when>]` and `--no-color`.
-
-`OPT__DRY_RUN(&int_var, description)`::
-	Add `-n, --dry-run`.
-
-`OPT__FORCE(&int_var, description)`::
-	Add `-f, --force`.
-
-`OPT__QUIET(&int_var, description)`::
-	Add `-q, --quiet`.
-
-`OPT__VERBOSE(&int_var, description)`::
-	Add `-v, --verbose`.
-
-`OPT_GROUP(description)`::
-	Start an option group. `description` is a short string that
-	describes the group or an empty string.
-	Start the description with an upper-case letter.
-
-`OPT_BOOL(short, long, &int_var, description)`::
-	Introduce a boolean option. `int_var` is set to one with
-	`--option` and set to zero with `--no-option`.
-
-`OPT_COUNTUP(short, long, &int_var, description)`::
-	Introduce a count-up option.
-	Each use of `--option` increments `int_var`, starting from zero
-	(even if initially negative), and `--no-option` resets it to
-	zero. To determine if `--option` or `--no-option` was encountered at
-	all, initialize `int_var` to a negative value, and if it is still
-	negative after parse_options(), then neither `--option` nor
-	`--no-option` was seen.
-
-`OPT_BIT(short, long, &int_var, description, mask)`::
-	Introduce a boolean option.
-	If used, `int_var` is bitwise-ored with `mask`.
-
-`OPT_NEGBIT(short, long, &int_var, description, mask)`::
-	Introduce a boolean option.
-	If used, `int_var` is bitwise-anded with the inverted `mask`.
-
-`OPT_SET_INT(short, long, &int_var, description, integer)`::
-	Introduce an integer option.
-	`int_var` is set to `integer` with `--option`, and
-	reset to zero with `--no-option`.
-
-`OPT_STRING(short, long, &str_var, arg_str, description)`::
-	Introduce an option with string argument.
-	The string argument is put into `str_var`.
-
-`OPT_STRING_LIST(short, long, &struct string_list, arg_str, description)`::
-	Introduce an option with string argument.
-	The string argument is stored as an element in `string_list`.
-	Use of `--no-option` will clear the list of preceding values.
-
-`OPT_INTEGER(short, long, &int_var, description)`::
-	Introduce an option with integer argument.
-	The integer is put into `int_var`.
-
-`OPT_MAGNITUDE(short, long, &unsigned_long_var, description)`::
-	Introduce an option with a size argument. The argument must be a
-	non-negative integer and may include a suffix of 'k', 'm' or 'g' to
-	scale the provided value by 1024, 1024^2 or 1024^3 respectively.
-	The scaled value is put into `unsigned_long_var`.
-
-`OPT_EXPIRY_DATE(short, long, &timestamp_t_var, description)`::
-	Introduce an option with expiry date argument, see `parse_expiry_date()`.
-	The timestamp is put into `timestamp_t_var`.
-
-`OPT_CALLBACK(short, long, &var, arg_str, description, func_ptr)`::
-	Introduce an option with argument.
-	The argument will be fed into the function given by `func_ptr`
-	and the result will be put into `var`.
-	See 'Option Callbacks' below for a more elaborate description.
-
-`OPT_FILENAME(short, long, &var, description)`::
-	Introduce an option with a filename argument.
-	The filename will be prefixed by passing the filename along with
-	the prefix argument of `parse_options()` to `prefix_filename()`.
-
-`OPT_ARGUMENT(long, &int_var, description)`::
-	Introduce a long-option argument that will be kept in `argv[]`.
-	If this option was seen, `int_var` will be set to one (except
-	if a `NULL` pointer was passed).
-
-`OPT_NUMBER_CALLBACK(&var, description, func_ptr)`::
-	Recognize numerical options like -123 and feed the integer as
-	if it was an argument to the function given by `func_ptr`.
-	The result will be put into `var`.  There can be only one such
-	option definition.  It cannot be negated and it takes no
-	arguments.  Short options that happen to be digits take
-	precedence over it.
-
-`OPT_COLOR_FLAG(short, long, &int_var, description)`::
-	Introduce an option that takes an optional argument that can
-	have one of three values: "always", "never", or "auto".  If the
-	argument is not given, it defaults to "always".  The `--no-` form
-	works like `--long=never`; it cannot take an argument.  If
-	"always", set `int_var` to 1; if "never", set `int_var` to 0; if
-	"auto", set `int_var` to 1 if stdout is a tty or a pager,
-	0 otherwise.
-
-`OPT_NOOP_NOARG(short, long)`::
-	Introduce an option that has no effect and takes no arguments.
-	Use it to hide deprecated options that are still to be recognized
-	and ignored silently.
-
-`OPT_PASSTHRU(short, long, &char_var, arg_str, description, flags)`::
-	Introduce an option that will be reconstructed into a char* string,
-	which must be initialized to NULL. This is useful when you need to
-	pass the command-line option to another command. Any previous value
-	will be overwritten, so this should only be used for options where
-	the last one specified on the command line wins.
-
-`OPT_PASSTHRU_ARGV(short, long, &argv_array_var, arg_str, description, flags)`::
-	Introduce an option where all instances of it on the command-line will
-	be reconstructed into an argv_array. This is useful when you need to
-	pass the command-line option, which can be specified multiple times,
-	to another command.
-
-`OPT_CMDMODE(short, long, &int_var, description, enum_val)`::
-	Define an "operation mode" option, only one of which in the same
-	group of "operating mode" options that share the same `int_var`
-	can be given by the user. `enum_val` is set to `int_var` when the
-	option is used, but an error is reported if other "operating mode"
-	option has already set its value to the same `int_var`.
-
-
-The last element of the array must be `OPT_END()`.
-
-If not stated otherwise, interpret the arguments as follows:
-
-* `short` is a character for the short option
-  (e.g. `'e'` for `-e`, use `0` to omit),
-
-* `long` is a string for the long option
-  (e.g. `"example"` for `--example`, use `NULL` to omit),
-
-* `int_var` is an integer variable,
-
-* `str_var` is a string variable (`char *`),
-
-* `arg_str` is the string that is shown as argument
-  (e.g. `"branch"` will result in `<branch>`).
-  If set to `NULL`, three dots (`...`) will be displayed.
-
-* `description` is a short string to describe the effect of the option.
-  It shall begin with a lower-case letter and a full stop (`.`) shall be
-  omitted at the end.
-
-Option Callbacks
-----------------
-
-The function must be defined in this form:
-
-	int func(const struct option *opt, const char *arg, int unset)
-
-The callback mechanism is as follows:
-
-* Inside `func`, the only interesting member of the structure
-  given by `opt` is the void pointer `opt->value`.
-  `*opt->value` will be the value that is saved into `var`, if you
-  use `OPT_CALLBACK()`.
-  For example, do `*(unsigned long *)opt->value = 42;` to get 42
-  into an `unsigned long` variable.
-
-* Return value `0` indicates success and non-zero return
-  value will invoke `usage_with_options()` and, thus, die.
-
-* If the user negates the option, `arg` is `NULL` and `unset` is 1.
-
-Sophisticated option parsing
-----------------------------
-
-If you need, for example, option callbacks with optional arguments
-or without arguments at all, or if you need other special cases,
-that are not handled by the macros above, you need to specify the
-members of the `option` structure manually.
-
-This is not covered in this document, but well documented
-in `parse-options.h` itself.
-
-Examples
---------
-
-See `test-parse-options.c` and
-`builtin/add.c`,
-`builtin/clone.c`,
-`builtin/commit.c`,
-`builtin/fetch.c`,
-`builtin/fsck.c`,
-`builtin/rm.c`
-for real-world examples.
diff --git a/parse-options.h b/parse-options.h
index 38a33a087e..52e6dac4d3 100644
--- a/parse-options.h
+++ b/parse-options.h
@@ -1,6 +1,102 @@
 #ifndef PARSE_OPTIONS_H
 #define PARSE_OPTIONS_H
 
+/**
+ * The parse-options API is used to parse and massage options in Git
+ * and to provide a usage help with consistent look.
+ *
+ * Basics
+ * ------
+ *
+ * The argument vector `argv[]` may usually contain mandatory or optional
+ * 'non-option arguments', e.g. a filename or a branch, and 'options'.
+ * Options are optional arguments that start with a dash and
+ * that allow to change the behavior of a command.
+ *
+ * - There are basically three types of options:
+ *   'boolean' options,
+ *   options with (mandatory) 'arguments' and
+ *   options with 'optional arguments'
+ *   (i.e. a boolean option that can be adjusted).
+ *
+ * - There are basically two forms of options:
+ *   'Short options' consist of one dash (`-`) and one alphanumeric
+ *   character.
+ *   'Long options' begin with two dashes (`--`) and some
+ *   alphanumeric characters.
+ *
+ * - Options are case-sensitive.
+ *   Please define 'lower-case long options' only.
+ *
+ * The parse-options API allows:
+ *
+ * - 'stuck' and 'separate form' of options with arguments.
+ *   `-oArg` is stuck, `-o Arg` is separate form.
+ *   `--option=Arg` is stuck, `--option Arg` is separate form.
+ *
+ * - Long options may be 'abbreviated', as long as the abbreviation
+ *   is unambiguous.
+ *
+ * - Short options may be bundled, e.g. `-a -b` can be specified as `-ab`.
+ *
+ * - Boolean long options can be 'negated' (or 'unset') by prepending
+ *   `no-`, e.g. `--no-abbrev` instead of `--abbrev`. Conversely,
+ *   options that begin with `no-` can be 'negated' by removing it.
+ *   Other long options can be unset (e.g., set string to NULL, set
+ *   integer to 0) by prepending `no-`.
+ *
+ * - Options and non-option arguments can clearly be separated using the `--`
+ *   option, e.g. `-a -b --option -- --this-is-a-file` indicates that
+ *   `--this-is-a-file` must not be processed as an option.
+ *
+ * Steps to parse options
+ * ----------------------
+ *
+ * - `#include "parse-options.h"`
+ *
+ * - define a NULL-terminated
+ *   `static const char * const builtin_foo_usage[]` array
+ *   containing alternative usage strings
+ *
+ * - define `builtin_foo_options` array.
+ *
+ * - in `cmd_foo(int argc, const char **argv, const char *prefix)` call
+ *   argc = parse_options(argc, argv, prefix, builtin_foo_options, builtin_foo_usage, flags);
+ *
+ * `parse_options()` will filter out the processed options of `argv[]` and leave the
+ * non-option arguments in `argv[]`.
+ * `argc` is updated appropriately because of the assignment.
+ *
+ * You can also pass NULL instead of a usage array as the fifth parameter of
+ * parse_options(), to avoid displaying a help screen with usage info and
+ * option list. This should only be done if necessary, e.g. to implement
+ * a limited parser for only a subset of the options that needs to be run
+ * before the full parser, which in turn shows the full help message.
+ *
+ * Flags are the bitwise-or of the members of parse_opt_flags.
+ *
+ * Sophisticated option parsing
+ * ----------------------------
+ *
+ * If you need, for example, option callbacks with optional arguments
+ * or without arguments at all, or if you need other special cases,
+ * that are not handled by the macros here, you need to specify the
+ * members of the `option` structure manually.
+ *
+ * Examples
+ * --------
+ *
+ * See `test-parse-options.c` and
+ * `builtin/add.c`,
+ * `builtin/clone.c`,
+ * `builtin/commit.c`,
+ * `builtin/fetch.c`,
+ * `builtin/fsck.c`,
+ * `builtin/rm.c`
+ * for real-world examples.
+ *
+ */
+
 enum parse_opt_type {
 	/* special types */
 	OPTION_END,
@@ -25,11 +121,47 @@ enum parse_opt_type {
 };
 
 enum parse_opt_flags {
+
+	/**
+	 * Keep the `--` that usually separates options from non-option arguments.
+	 */
 	PARSE_OPT_KEEP_DASHDASH = 1,
+
+	/**
+	 * Usually the whole argument vector is massaged and reordered.
+	 * Using this flag, processing is stopped at the first non-option
+	 * argument.
+	 */
 	PARSE_OPT_STOP_AT_NON_OPTION = 2,
+
+	/**
+	 * Keep the first argument, which contains the program name.  It's
+	 * removed from argv[] by default.
+	 */
 	PARSE_OPT_KEEP_ARGV0 = 4,
+
+	/**
+	 * Keep unknown arguments instead of erroring out.  This doesn't
+	 * work for all combinations of arguments as users might expect
+	 * it to do. E.g. if the first argument in `--unknown --known`
+	 * takes a value (which we can't know), the second one is
+	 * mistakenly interpreted as a known option.  Similarly, if
+	 * `PARSE_OPT_STOP_AT_NON_OPTION` is set, the second argument in
+	 * `--unknown value` will be mistakenly interpreted as a
+	 * non-option, not as a value belonging to the unknown option,
+	 * the parser early. That's why parse_options() errors out if
+	 * both options are set.
+	 */
 	PARSE_OPT_KEEP_UNKNOWN = 8,
+
+	/**
+	 * By default, parse_options() handles `-h`, `--help` and
+	 * `--help-all` internally, by showing a help screen. This option
+	 * turns it off and allows one to add custom handlers for these
+	 * options, or to just leave them unknown.
+	 */
 	PARSE_OPT_NO_INTERNAL_HELP = 16,
+
 	PARSE_OPT_ONE_SHOT = 32
 };
 
@@ -56,6 +188,30 @@ enum parse_opt_result {
 };
 
 struct option;
+
+/**
+ * Option Callbacks
+ * ----------------
+ *
+ * The function must be defined in this form:
+ *
+ * 	int func(const struct option *opt, const char *arg, int unset)
+ *
+ * The callback mechanism is as follows:
+ *
+ * - Inside `func`, the only interesting member of the structure
+ *   given by `opt` is the void pointer `opt->value`.
+ *   `*opt->value` will be the value that is saved into `var`, if you
+ *   use `OPT_CALLBACK()`.
+ *   For example, do `*(unsigned long *)opt->value = 42;` to get 42
+ *   into an `unsigned long` variable.
+ *
+ * - Return value `0` indicates success and non-zero return
+ *   value will invoke `usage_with_options()` and, thus, die.
+ *
+ * - If the user negates the option, `arg` is `NULL` and `unset` is 1.
+ *
+ */
 typedef int parse_opt_cb(const struct option *, const char *arg, int unset);
 
 struct parse_opt_ctx_t;
@@ -137,6 +293,36 @@ struct option {
 	intptr_t extra;
 };
 
+/**
+ * Data Structure
+ * --------------
+ *
+ * The main data structure is an array of the `option` struct, say
+ * `static struct option builtin_add_options[]`.
+ * The following macros can be used to easily define options.
+ * The last element of the array must be `OPT_END()`.
+ *
+ * If not stated otherwise, interpret the arguments as follows:
+ *
+ * - `short` is a character for the short option
+ *   (e.g. `'e'` for `-e`, use `0` to omit),
+ *
+ * - `long` is a string for the long option
+ *   (e.g. `"example"` for `--example`, use `NULL` to omit),
+ *
+ * - `int_var` is an integer variable,
+ *
+ * - `str_var` is a string variable (`char *`),
+ *
+ * - `arg_str` is the string that is shown as argument
+ *   (e.g. `"branch"` will result in `<branch>`).
+ *   If set to `NULL`, three dots (`...`) will be displayed.
+ *
+ * - `description` is a short string to describe the effect of the option.
+ *   It shall begin with a lower-case letter and a full stop (`.`) shall be
+ *   omitted at the end.
+ */
+
 #define OPT_BIT_F(s, l, v, h, b, f) { OPTION_BIT, (s), (l), (v), NULL, (h), \
 				      PARSE_OPT_NOARG|(f), NULL, (b) }
 #define OPT_COUNTUP_F(s, l, v, h, f) { OPTION_COUNTUP, (s), (l), (v), NULL, \
@@ -150,44 +336,158 @@ struct option {
 #define OPT_INTEGER_F(s, l, v, h, f)     { OPTION_INTEGER, (s), (l), (v), N_("n"), (h), (f) }
 
 #define OPT_END()                   { OPTION_END }
+
+/**
+ * Introduce a long-option argument that will be kept in `argv[]`.
+ * If this option was seen, `int_var` will be set to one (except
+ * if a `NULL` pointer was passed).
+ */
 #define OPT_ARGUMENT(l, v, h)       { OPTION_ARGUMENT, 0, (l), (v), NULL, \
 				      (h), PARSE_OPT_NOARG, NULL, 1 }
+
+/**
+ * Start an option group. `description` is a short string that describes the
+ * group or an empty string. Start the description with an upper-case letter.
+ */
 #define OPT_GROUP(h)                { OPTION_GROUP, 0, NULL, NULL, NULL, (h) }
+
+/**
+ * Introduce a boolean option. If used, `int_var` is bitwise-ored with `mask`.
+ */
 #define OPT_BIT(s, l, v, h, b)      OPT_BIT_F(s, l, v, h, b, 0)
+
 #define OPT_BITOP(s, l, v, h, set, clear) { OPTION_BITOP, (s), (l), (v), NULL, (h), \
 					    PARSE_OPT_NOARG|PARSE_OPT_NONEG, NULL, \
 					    (set), NULL, (clear) }
+
+/**
+ * Introduce a boolean option. If used, `int_var` is bitwise-anded with the
+ * inverted `mask`.
+ */
 #define OPT_NEGBIT(s, l, v, h, b)   { OPTION_NEGBIT, (s), (l), (v), NULL, \
 				      (h), PARSE_OPT_NOARG, NULL, (b) }
+
+/**
+ * Introduce a count-up option.
+ * Each use of `--option` increments `int_var`, starting from zero
+ * (even if initially negative), and `--no-option` resets it to
+ * zero. To determine if `--option` or `--no-option` was encountered at
+ * all, initialize `int_var` to a negative value, and if it is still
+ * negative after parse_options(), then neither `--option` nor
+ * `--no-option` was seen.
+ */
 #define OPT_COUNTUP(s, l, v, h)     OPT_COUNTUP_F(s, l, v, h, 0)
+
+/**
+ * Introduce an integer option. `int_var` is set to `integer` with `--option`,
+ * and reset to zero with `--no-option`.
+ */
 #define OPT_SET_INT(s, l, v, h, i)  OPT_SET_INT_F(s, l, v, h, i, 0)
+
+/**
+ * Introduce a boolean option. `int_var` is set to one with `--option` and set
+ * to zero with `--no-option`.
+ */
 #define OPT_BOOL(s, l, v, h)        OPT_BOOL_F(s, l, v, h, 0)
+
 #define OPT_HIDDEN_BOOL(s, l, v, h) { OPTION_SET_INT, (s), (l), (v), NULL, \
 				      (h), PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, NULL, 1}
+
+/**
+ * Define an "operation mode" option, only one of which in the same
+ * group of "operating mode" options that share the same `int_var`
+ * can be given by the user. `enum_val` is set to `int_var` when the
+ * option is used, but an error is reported if other "operating mode"
+ * option has already set its value to the same `int_var`.
+ */
 #define OPT_CMDMODE(s, l, v, h, i)  { OPTION_CMDMODE, (s), (l), (v), NULL, \
 				      (h), PARSE_OPT_NOARG|PARSE_OPT_NONEG, NULL, (i) }
+
+/**
+ * Introduce an option with integer argument. The integer is put into `int_var`.
+ */
 #define OPT_INTEGER(s, l, v, h)     OPT_INTEGER_F(s, l, v, h, 0)
+
+/**
+ * Introduce an option with a size argument. The argument must be a
+ * non-negative integer and may include a suffix of 'k', 'm' or 'g' to
+ * scale the provided value by 1024, 1024^2 or 1024^3 respectively.
+ * The scaled value is put into `unsigned_long_var`.
+ */
 #define OPT_MAGNITUDE(s, l, v, h)   { OPTION_MAGNITUDE, (s), (l), (v), \
 				      N_("n"), (h), PARSE_OPT_NONEG }
+
+/**
+ * Introduce an option with string argument. The string argument is put
+ * into `str_var`.
+ */
 #define OPT_STRING(s, l, v, a, h)   OPT_STRING_F(s, l, v, a, h, 0)
+
+/**
+ * Introduce an option with string argument.
+ * The string argument is stored as an element in `string_list`.
+ * Use of `--no-option` will clear the list of preceding values.
+ */
 #define OPT_STRING_LIST(s, l, v, a, h) \
 				    { OPTION_CALLBACK, (s), (l), (v), (a), \
 				      (h), 0, &parse_opt_string_list }
+
 #define OPT_UYN(s, l, v, h)         { OPTION_CALLBACK, (s), (l), (v), NULL, \
 				      (h), PARSE_OPT_NOARG, &parse_opt_tertiary }
+
+/**
+ * Introduce an option with expiry date argument, see `parse_expiry_date()`.
+ * The timestamp is put into `timestamp_t_var`.
+ */
 #define OPT_EXPIRY_DATE(s, l, v, h) \
 	{ OPTION_CALLBACK, (s), (l), (v), N_("expiry-date"),(h), 0,	\
 	  parse_opt_expiry_date_cb }
+
+/**
+ * Introduce an option with argument.
+ * The argument will be fed into the function given by `func_ptr`
+ * and the result will be put into `var`.
+ */
 #define OPT_CALLBACK(s, l, v, a, h, f) OPT_CALLBACK_F(s, l, v, a, h, 0, f)
+
+/**
+ * Recognize numerical options like -123 and feed the integer as
+ * if it was an argument to the function given by `func_ptr`.
+ * The result will be put into `var`.  There can be only one such
+ * option definition.  It cannot be negated and it takes no
+ * arguments.  Short options that happen to be digits take
+ * precedence over it.
+ */
 #define OPT_NUMBER_CALLBACK(v, h, f) \
 	{ OPTION_NUMBER, 0, NULL, (v), NULL, (h), \
 	  PARSE_OPT_NOARG | PARSE_OPT_NONEG, (f) }
+
+/**
+ * Introduce an option with a filename argument.
+ * The filename will be prefixed by passing the filename along with
+ * the prefix argument of `parse_options()` to `prefix_filename()`.
+ */
 #define OPT_FILENAME(s, l, v, h)    { OPTION_FILENAME, (s), (l), (v), \
 				       N_("file"), (h) }
+
+/**
+ * Introduce an option that takes an optional argument that can
+ * have one of three values: "always", "never", or "auto".  If the
+ * argument is not given, it defaults to "always".  The `--no-` form
+ * works like `--long=never`; it cannot take an argument.  If
+ * "always", set `int_var` to 1; if "never", set `int_var` to 0; if
+ * "auto", set `int_var` to 1 if stdout is a tty or a pager,
+ * 0 otherwise.
+ */
 #define OPT_COLOR_FLAG(s, l, v, h) \
 	{ OPTION_CALLBACK, (s), (l), (v), N_("when"), (h), PARSE_OPT_OPTARG, \
 		parse_opt_color_flag_cb, (intptr_t)"always" }
 
+/**
+ * Introduce an option that has no effect and takes no arguments.
+ * Use it to hide deprecated options that are still to be recognized
+ * and ignored silently.
+ */
 #define OPT_NOOP_NOARG(s, l) \
 	{ OPTION_CALLBACK, (s), (l), NULL, NULL, \
 	  N_("no-op (backward compatibility)"),		\
@@ -296,24 +596,52 @@ int parse_opt_noop_cb(const struct option *, const char *, int);
 enum parse_opt_result parse_opt_unknown_cb(struct parse_opt_ctx_t *ctx,
 					   const struct option *,
 					   const char *, int);
+
+/**
+ * Introduce an option that will be reconstructed into a char* string,
+ * which must be initialized to NULL. This is useful when you need to
+ * pass the command-line option to another command. Any previous value
+ * will be overwritten, so this should only be used for options where
+ * the last one specified on the command line wins.
+ */
 int parse_opt_passthru(const struct option *, const char *, int);
+
+/**
+ * Introduce an option where all instances of it on the command-line will
+ * be reconstructed into an argv_array. This is useful when you need to
+ * pass the command-line option, which can be specified multiple times,
+ * to another command.
+ */
 int parse_opt_passthru_argv(const struct option *, const char *, int);
 
+/* Add `-v, --verbose`. */
 #define OPT__VERBOSE(var, h)  OPT_COUNTUP('v', "verbose", (var), (h))
+
+/* Add `-q, --quiet`. */
 #define OPT__QUIET(var, h)    OPT_COUNTUP('q', "quiet",   (var), (h))
+
 #define OPT__VERBOSITY(var) \
 	{ OPTION_CALLBACK, 'v', "verbose", (var), NULL, N_("be more verbose"), \
 	  PARSE_OPT_NOARG, &parse_opt_verbosity_cb, 0 }, \
 	{ OPTION_CALLBACK, 'q', "quiet", (var), NULL, N_("be more quiet"), \
 	  PARSE_OPT_NOARG, &parse_opt_verbosity_cb, 0 }
+
+/* Add `-n, --dry-run`. */
 #define OPT__DRY_RUN(var, h)  OPT_BOOL('n', "dry-run", (var), (h))
+
+/* Add `-f, --force`. */
 #define OPT__FORCE(var, h, f) OPT_COUNTUP_F('f', "force",   (var), (h), (f))
+
+/* Add `--abbrev[=<n>]`. */
 #define OPT__ABBREV(var)  \
 	{ OPTION_CALLBACK, 0, "abbrev", (var), N_("n"),	\
 	  N_("use <n> digits to display SHA-1s"),	\
 	  PARSE_OPT_OPTARG, &parse_opt_abbrev_cb, 0 }
+
+/* Add `--color[=<when>]` and `--no-color`. */
 #define OPT__COLOR(var, h) \
 	OPT_COLOR_FLAG(0, "color", (var), (h))
+
 #define OPT_COLUMN(s, l, v, h) \
 	{ OPTION_CALLBACK, (s), (l), (v), N_("style"), (h), PARSE_OPT_OPTARG, parseopt_column_callback }
 #define OPT_PASSTHRU(s, l, v, a, h, f) \
-- 
gitgitgadget


^ permalink raw reply related	[relevance 4%]

* [PATCH v3 15/21] parse-options: move doc to parse-options.h
  @ 2019-11-11 21:27  4%     ` Heba Waly via GitGitGadget
    1 sibling, 0 replies; 200+ results
From: Heba Waly via GitGitGadget @ 2019-11-11 21:27 UTC (permalink / raw)
  To: git; +Cc: Heba Waly, Junio C Hamano, Heba Waly

From: Heba Waly <heba.waly@gmail.com>

Move the documentation from Documentation/technical/api-parse-options.txt
to parse-options.h as it's easier for the developers to find the usage
information beside the code instead of looking for it in another doc file.

Documentation/technical/api-parse-options.txt is removed because the
information it has is now redundant and it'll be hard to keep it up to
date and synchronized with the documentation in the header file.

Documentation/MyFirstContribution.txt now link
to parse-options.h instead of Documentation/technical/api-parse-options.txt for
details about parsing options.

Signed-off-by: Heba Waly <heba.waly@gmail.com>
---
 Documentation/MyFirstContribution.txt         |   2 +-
 Documentation/technical/api-parse-options.txt | 313 -----------------
 parse-options.h                               | 329 ++++++++++++++++++
 3 files changed, 330 insertions(+), 314 deletions(-)
 delete mode 100644 Documentation/technical/api-parse-options.txt

diff --git a/Documentation/MyFirstContribution.txt b/Documentation/MyFirstContribution.txt
index 5e9b808f5f..a2a91d92e2 100644
--- a/Documentation/MyFirstContribution.txt
+++ b/Documentation/MyFirstContribution.txt
@@ -487,7 +487,7 @@ Try and run `./bin-wrappers/git psuh -h`. Your command should crash at the end.
 That's because `-h` is a special case which your command should handle by
 printing usage.
 
-Take a look at `Documentation/technical/api-parse-options.txt`. This is a handy
+Take a look at `parse-options.h`. This is a handy
 tool for pulling out options you need to be able to handle, and it takes a
 usage string.
 
diff --git a/Documentation/technical/api-parse-options.txt b/Documentation/technical/api-parse-options.txt
deleted file mode 100644
index 2e2e7c10c6..0000000000
--- a/Documentation/technical/api-parse-options.txt
+++ /dev/null
@@ -1,313 +0,0 @@
-parse-options API
-=================
-
-The parse-options API is used to parse and massage options in Git
-and to provide a usage help with consistent look.
-
-Basics
-------
-
-The argument vector `argv[]` may usually contain mandatory or optional
-'non-option arguments', e.g. a filename or a branch, and 'options'.
-Options are optional arguments that start with a dash and
-that allow to change the behavior of a command.
-
-* There are basically three types of options:
-  'boolean' options,
-  options with (mandatory) 'arguments' and
-  options with 'optional arguments'
-  (i.e. a boolean option that can be adjusted).
-
-* There are basically two forms of options:
-  'Short options' consist of one dash (`-`) and one alphanumeric
-  character.
-  'Long options' begin with two dashes (`--`) and some
-  alphanumeric characters.
-
-* Options are case-sensitive.
-  Please define 'lower-case long options' only.
-
-The parse-options API allows:
-
-* 'stuck' and 'separate form' of options with arguments.
-  `-oArg` is stuck, `-o Arg` is separate form.
-  `--option=Arg` is stuck, `--option Arg` is separate form.
-
-* Long options may be 'abbreviated', as long as the abbreviation
-  is unambiguous.
-
-* Short options may be bundled, e.g. `-a -b` can be specified as `-ab`.
-
-* Boolean long options can be 'negated' (or 'unset') by prepending
-  `no-`, e.g. `--no-abbrev` instead of `--abbrev`. Conversely,
-  options that begin with `no-` can be 'negated' by removing it.
-  Other long options can be unset (e.g., set string to NULL, set
-  integer to 0) by prepending `no-`.
-
-* Options and non-option arguments can clearly be separated using the `--`
-  option, e.g. `-a -b --option -- --this-is-a-file` indicates that
-  `--this-is-a-file` must not be processed as an option.
-
-Steps to parse options
-----------------------
-
-. `#include "parse-options.h"`
-
-. define a NULL-terminated
-  `static const char * const builtin_foo_usage[]` array
-  containing alternative usage strings
-
-. define `builtin_foo_options` array as described below
-  in section 'Data Structure'.
-
-. in `cmd_foo(int argc, const char **argv, const char *prefix)`
-  call
-
-	argc = parse_options(argc, argv, prefix, builtin_foo_options, builtin_foo_usage, flags);
-+
-`parse_options()` will filter out the processed options of `argv[]` and leave the
-non-option arguments in `argv[]`.
-`argc` is updated appropriately because of the assignment.
-+
-You can also pass NULL instead of a usage array as the fifth parameter of
-parse_options(), to avoid displaying a help screen with usage info and
-option list.  This should only be done if necessary, e.g. to implement
-a limited parser for only a subset of the options that needs to be run
-before the full parser, which in turn shows the full help message.
-+
-Flags are the bitwise-or of:
-
-`PARSE_OPT_KEEP_DASHDASH`::
-	Keep the `--` that usually separates options from
-	non-option arguments.
-
-`PARSE_OPT_STOP_AT_NON_OPTION`::
-	Usually the whole argument vector is massaged and reordered.
-	Using this flag, processing is stopped at the first non-option
-	argument.
-
-`PARSE_OPT_KEEP_ARGV0`::
-	Keep the first argument, which contains the program name.  It's
-	removed from argv[] by default.
-
-`PARSE_OPT_KEEP_UNKNOWN`::
-	Keep unknown arguments instead of erroring out.  This doesn't
-	work for all combinations of arguments as users might expect
-	it to do.  E.g. if the first argument in `--unknown --known`
-	takes a value (which we can't know), the second one is
-	mistakenly interpreted as a known option.  Similarly, if
-	`PARSE_OPT_STOP_AT_NON_OPTION` is set, the second argument in
-	`--unknown value` will be mistakenly interpreted as a
-	non-option, not as a value belonging to the unknown option,
-	the parser early.  That's why parse_options() errors out if
-	both options are set.
-
-`PARSE_OPT_NO_INTERNAL_HELP`::
-	By default, parse_options() handles `-h`, `--help` and
-	`--help-all` internally, by showing a help screen.  This option
-	turns it off and allows one to add custom handlers for these
-	options, or to just leave them unknown.
-
-Data Structure
---------------
-
-The main data structure is an array of the `option` struct,
-say `static struct option builtin_add_options[]`.
-There are some macros to easily define options:
-
-`OPT__ABBREV(&int_var)`::
-	Add `--abbrev[=<n>]`.
-
-`OPT__COLOR(&int_var, description)`::
-	Add `--color[=<when>]` and `--no-color`.
-
-`OPT__DRY_RUN(&int_var, description)`::
-	Add `-n, --dry-run`.
-
-`OPT__FORCE(&int_var, description)`::
-	Add `-f, --force`.
-
-`OPT__QUIET(&int_var, description)`::
-	Add `-q, --quiet`.
-
-`OPT__VERBOSE(&int_var, description)`::
-	Add `-v, --verbose`.
-
-`OPT_GROUP(description)`::
-	Start an option group. `description` is a short string that
-	describes the group or an empty string.
-	Start the description with an upper-case letter.
-
-`OPT_BOOL(short, long, &int_var, description)`::
-	Introduce a boolean option. `int_var` is set to one with
-	`--option` and set to zero with `--no-option`.
-
-`OPT_COUNTUP(short, long, &int_var, description)`::
-	Introduce a count-up option.
-	Each use of `--option` increments `int_var`, starting from zero
-	(even if initially negative), and `--no-option` resets it to
-	zero. To determine if `--option` or `--no-option` was encountered at
-	all, initialize `int_var` to a negative value, and if it is still
-	negative after parse_options(), then neither `--option` nor
-	`--no-option` was seen.
-
-`OPT_BIT(short, long, &int_var, description, mask)`::
-	Introduce a boolean option.
-	If used, `int_var` is bitwise-ored with `mask`.
-
-`OPT_NEGBIT(short, long, &int_var, description, mask)`::
-	Introduce a boolean option.
-	If used, `int_var` is bitwise-anded with the inverted `mask`.
-
-`OPT_SET_INT(short, long, &int_var, description, integer)`::
-	Introduce an integer option.
-	`int_var` is set to `integer` with `--option`, and
-	reset to zero with `--no-option`.
-
-`OPT_STRING(short, long, &str_var, arg_str, description)`::
-	Introduce an option with string argument.
-	The string argument is put into `str_var`.
-
-`OPT_STRING_LIST(short, long, &struct string_list, arg_str, description)`::
-	Introduce an option with string argument.
-	The string argument is stored as an element in `string_list`.
-	Use of `--no-option` will clear the list of preceding values.
-
-`OPT_INTEGER(short, long, &int_var, description)`::
-	Introduce an option with integer argument.
-	The integer is put into `int_var`.
-
-`OPT_MAGNITUDE(short, long, &unsigned_long_var, description)`::
-	Introduce an option with a size argument. The argument must be a
-	non-negative integer and may include a suffix of 'k', 'm' or 'g' to
-	scale the provided value by 1024, 1024^2 or 1024^3 respectively.
-	The scaled value is put into `unsigned_long_var`.
-
-`OPT_EXPIRY_DATE(short, long, &timestamp_t_var, description)`::
-	Introduce an option with expiry date argument, see `parse_expiry_date()`.
-	The timestamp is put into `timestamp_t_var`.
-
-`OPT_CALLBACK(short, long, &var, arg_str, description, func_ptr)`::
-	Introduce an option with argument.
-	The argument will be fed into the function given by `func_ptr`
-	and the result will be put into `var`.
-	See 'Option Callbacks' below for a more elaborate description.
-
-`OPT_FILENAME(short, long, &var, description)`::
-	Introduce an option with a filename argument.
-	The filename will be prefixed by passing the filename along with
-	the prefix argument of `parse_options()` to `prefix_filename()`.
-
-`OPT_ARGUMENT(long, &int_var, description)`::
-	Introduce a long-option argument that will be kept in `argv[]`.
-	If this option was seen, `int_var` will be set to one (except
-	if a `NULL` pointer was passed).
-
-`OPT_NUMBER_CALLBACK(&var, description, func_ptr)`::
-	Recognize numerical options like -123 and feed the integer as
-	if it was an argument to the function given by `func_ptr`.
-	The result will be put into `var`.  There can be only one such
-	option definition.  It cannot be negated and it takes no
-	arguments.  Short options that happen to be digits take
-	precedence over it.
-
-`OPT_COLOR_FLAG(short, long, &int_var, description)`::
-	Introduce an option that takes an optional argument that can
-	have one of three values: "always", "never", or "auto".  If the
-	argument is not given, it defaults to "always".  The `--no-` form
-	works like `--long=never`; it cannot take an argument.  If
-	"always", set `int_var` to 1; if "never", set `int_var` to 0; if
-	"auto", set `int_var` to 1 if stdout is a tty or a pager,
-	0 otherwise.
-
-`OPT_NOOP_NOARG(short, long)`::
-	Introduce an option that has no effect and takes no arguments.
-	Use it to hide deprecated options that are still to be recognized
-	and ignored silently.
-
-`OPT_PASSTHRU(short, long, &char_var, arg_str, description, flags)`::
-	Introduce an option that will be reconstructed into a char* string,
-	which must be initialized to NULL. This is useful when you need to
-	pass the command-line option to another command. Any previous value
-	will be overwritten, so this should only be used for options where
-	the last one specified on the command line wins.
-
-`OPT_PASSTHRU_ARGV(short, long, &argv_array_var, arg_str, description, flags)`::
-	Introduce an option where all instances of it on the command-line will
-	be reconstructed into an argv_array. This is useful when you need to
-	pass the command-line option, which can be specified multiple times,
-	to another command.
-
-`OPT_CMDMODE(short, long, &int_var, description, enum_val)`::
-	Define an "operation mode" option, only one of which in the same
-	group of "operating mode" options that share the same `int_var`
-	can be given by the user. `enum_val` is set to `int_var` when the
-	option is used, but an error is reported if other "operating mode"
-	option has already set its value to the same `int_var`.
-
-
-The last element of the array must be `OPT_END()`.
-
-If not stated otherwise, interpret the arguments as follows:
-
-* `short` is a character for the short option
-  (e.g. `'e'` for `-e`, use `0` to omit),
-
-* `long` is a string for the long option
-  (e.g. `"example"` for `--example`, use `NULL` to omit),
-
-* `int_var` is an integer variable,
-
-* `str_var` is a string variable (`char *`),
-
-* `arg_str` is the string that is shown as argument
-  (e.g. `"branch"` will result in `<branch>`).
-  If set to `NULL`, three dots (`...`) will be displayed.
-
-* `description` is a short string to describe the effect of the option.
-  It shall begin with a lower-case letter and a full stop (`.`) shall be
-  omitted at the end.
-
-Option Callbacks
-----------------
-
-The function must be defined in this form:
-
-	int func(const struct option *opt, const char *arg, int unset)
-
-The callback mechanism is as follows:
-
-* Inside `func`, the only interesting member of the structure
-  given by `opt` is the void pointer `opt->value`.
-  `*opt->value` will be the value that is saved into `var`, if you
-  use `OPT_CALLBACK()`.
-  For example, do `*(unsigned long *)opt->value = 42;` to get 42
-  into an `unsigned long` variable.
-
-* Return value `0` indicates success and non-zero return
-  value will invoke `usage_with_options()` and, thus, die.
-
-* If the user negates the option, `arg` is `NULL` and `unset` is 1.
-
-Sophisticated option parsing
-----------------------------
-
-If you need, for example, option callbacks with optional arguments
-or without arguments at all, or if you need other special cases,
-that are not handled by the macros above, you need to specify the
-members of the `option` structure manually.
-
-This is not covered in this document, but well documented
-in `parse-options.h` itself.
-
-Examples
---------
-
-See `test-parse-options.c` and
-`builtin/add.c`,
-`builtin/clone.c`,
-`builtin/commit.c`,
-`builtin/fetch.c`,
-`builtin/fsck.c`,
-`builtin/rm.c`
-for real-world examples.
diff --git a/parse-options.h b/parse-options.h
index 38a33a087e..eda2a52289 100644
--- a/parse-options.h
+++ b/parse-options.h
@@ -1,6 +1,103 @@
 #ifndef PARSE_OPTIONS_H
 #define PARSE_OPTIONS_H
 
+/**
+ * The parse-options API is used to parse and massage options in Git
+ * and to provide a usage help with consistent look.
+ *
+ * Basics
+ * ------
+ *
+ * The argument vector `argv[]` may usually contain mandatory or optional
+ * 'non-option arguments', e.g. a filename or a branch, and 'options'.
+ * Options are optional arguments that start with a dash and
+ * that allow to change the behavior of a command.
+ *
+ * - There are basically three types of options:
+ *   'boolean' options,
+ *   options with (mandatory) 'arguments' and
+ *   options with 'optional arguments'
+ *   (i.e. a boolean option that can be adjusted).
+ *
+ * - There are basically two forms of options:
+ *   'Short options' consist of one dash (`-`) and one alphanumeric
+ *   character.
+ *   'Long options' begin with two dashes (`--`) and some
+ *   alphanumeric characters.
+ *
+ * - Options are case-sensitive.
+ *   Please define 'lower-case long options' only.
+ *
+ * The parse-options API allows:
+ *
+ * - 'stuck' and 'separate form' of options with arguments.
+ *   `-oArg` is stuck, `-o Arg` is separate form.
+ *   `--option=Arg` is stuck, `--option Arg` is separate form.
+ *
+ * - Long options may be 'abbreviated', as long as the abbreviation
+ *   is unambiguous.
+ *
+ * - Short options may be bundled, e.g. `-a -b` can be specified as `-ab`.
+ *
+ * - Boolean long options can be 'negated' (or 'unset') by prepending
+ *   `no-`, e.g. `--no-abbrev` instead of `--abbrev`. Conversely,
+ *   options that begin with `no-` can be 'negated' by removing it.
+ *   Other long options can be unset (e.g., set string to NULL, set
+ *   integer to 0) by prepending `no-`.
+ *
+ * - Options and non-option arguments can clearly be separated using the `--`
+ *   option, e.g. `-a -b --option -- --this-is-a-file` indicates that
+ *   `--this-is-a-file` must not be processed as an option.
+ *
+ * Steps to parse options
+ * ----------------------
+ *
+ * - `#include "parse-options.h"`
+ *
+ * - define a NULL-terminated
+ *   `static const char * const builtin_foo_usage[]` array
+ *   containing alternative usage strings
+ *
+ * - define `builtin_foo_options` array as described below
+ *   in section 'Data Structure'.
+ *
+ * - in `cmd_foo(int argc, const char **argv, const char *prefix)`
+ *   call
+ *
+ * 	argc = parse_options(argc, argv, prefix, builtin_foo_options, builtin_foo_usage, flags);
+ *
+ * `parse_options()` will filter out the processed options of `argv[]` and leave the
+ * non-option arguments in `argv[]`.
+ * `argc` is updated appropriately because of the assignment.
+ *
+ * You can also pass NULL instead of a usage array as the fifth parameter of
+ * parse_options(), to avoid displaying a help screen with usage info and
+ * option list. This should only be done if necessary, e.g. to implement
+ * a limited parser for only a subset of the options that needs to be run
+ * before the full parser, which in turn shows the full help message.
+ *
+ * Sophisticated option parsing
+ * ----------------------------
+ *
+ * If you need, for example, option callbacks with optional arguments
+ * or without arguments at all, or if you need other special cases,
+ * that are not handled by the macros here, you need to specify the
+ * members of the `option` structure manually.
+ *
+ * Examples
+ * --------
+ *
+ * See `test-parse-options.c` and
+ * `builtin/add.c`,
+ * `builtin/clone.c`,
+ * `builtin/commit.c`,
+ * `builtin/fetch.c`,
+ * `builtin/fsck.c`,
+ * `builtin/rm.c`
+ * for real-world examples.
+ *
+ */
+
 enum parse_opt_type {
 	/* special types */
 	OPTION_END,
@@ -25,11 +122,47 @@ enum parse_opt_type {
 };
 
 enum parse_opt_flags {
+
+	/**
+	 * Keep the `--` that usually separates options from non-option arguments.
+	 */
 	PARSE_OPT_KEEP_DASHDASH = 1,
+
+	/**
+	 * Usually the whole argument vector is massaged and reordered.
+	 * Using this flag, processing is stopped at the first non-option
+	 * argument.
+	 */
 	PARSE_OPT_STOP_AT_NON_OPTION = 2,
+
+	/**
+	 * Keep the first argument, which contains the program name.  It's
+	 * removed from argv[] by default.
+	 */
 	PARSE_OPT_KEEP_ARGV0 = 4,
+
+	/**
+	 * Keep unknown arguments instead of erroring out.  This doesn't
+	 * work for all combinations of arguments as users might expect
+	 * it to do. E.g. if the first argument in `--unknown --known`
+	 * takes a value (which we can't know), the second one is
+	 * mistakenly interpreted as a known option.  Similarly, if
+	 * `PARSE_OPT_STOP_AT_NON_OPTION` is set, the second argument in
+	 * `--unknown value` will be mistakenly interpreted as a
+	 * non-option, not as a value belonging to the unknown option,
+	 * the parser early. That's why parse_options() errors out if
+	 * both options are set.
+	 */
 	PARSE_OPT_KEEP_UNKNOWN = 8,
+
+	/**
+	 * By default, parse_options() handles `-h`, `--help` and
+	 * `--help-all` internally, by showing a help screen. This option
+	 * turns it off and allows one to add custom handlers for these
+	 * options, or to just leave them unknown.
+	 */
 	PARSE_OPT_NO_INTERNAL_HELP = 16,
+
 	PARSE_OPT_ONE_SHOT = 32
 };
 
@@ -56,6 +189,30 @@ enum parse_opt_result {
 };
 
 struct option;
+
+/**
+ * Option Callbacks
+ * ----------------
+ *
+ * The function must be defined in this form:
+ *
+ * 	int func(const struct option *opt, const char *arg, int unset)
+ *
+ * The callback mechanism is as follows:
+ *
+ * - Inside `func`, the only interesting member of the structure
+ *   given by `opt` is the void pointer `opt->value`.
+ *   `*opt->value` will be the value that is saved into `var`, if you
+ *   use `OPT_CALLBACK()`.
+ *   For example, do `*(unsigned long *)opt->value = 42;` to get 42
+ *   into an `unsigned long` variable.
+ *
+ * - Return value `0` indicates success and non-zero return
+ *   value will invoke `usage_with_options()` and, thus, die.
+ *
+ * - If the user negates the option, `arg` is `NULL` and `unset` is 1.
+ *
+ */
 typedef int parse_opt_cb(const struct option *, const char *arg, int unset);
 
 struct parse_opt_ctx_t;
@@ -137,6 +294,36 @@ struct option {
 	intptr_t extra;
 };
 
+/**
+ * Data Structure
+ * --------------
+ *
+ * The main data structure is an array of the `option` struct, say
+ * `static struct option builtin_add_options[]`.
+ * The following macros can be used to easily define options.
+ * The last element of the array must be `OPT_END()`.
+ *
+ * If not stated otherwise, interpret the arguments as follows:
+ *
+ * - `short` is a character for the short option
+ *   (e.g. `'e'` for `-e`, use `0` to omit),
+ *
+ * - `long` is a string for the long option
+ *   (e.g. `"example"` for `--example`, use `NULL` to omit),
+ *
+ * - `int_var` is an integer variable,
+ *
+ * - `str_var` is a string variable (`char *`),
+ *
+ * - `arg_str` is the string that is shown as argument
+ *   (e.g. `"branch"` will result in `<branch>`).
+ *   If set to `NULL`, three dots (`...`) will be displayed.
+ *
+ * - `description` is a short string to describe the effect of the option.
+ *   It shall begin with a lower-case letter and a full stop (`.`) shall be
+ *   omitted at the end.
+ */
+
 #define OPT_BIT_F(s, l, v, h, b, f) { OPTION_BIT, (s), (l), (v), NULL, (h), \
 				      PARSE_OPT_NOARG|(f), NULL, (b) }
 #define OPT_COUNTUP_F(s, l, v, h, f) { OPTION_COUNTUP, (s), (l), (v), NULL, \
@@ -150,44 +337,158 @@ struct option {
 #define OPT_INTEGER_F(s, l, v, h, f)     { OPTION_INTEGER, (s), (l), (v), N_("n"), (h), (f) }
 
 #define OPT_END()                   { OPTION_END }
+
+/**
+ * Introduce a long-option argument that will be kept in `argv[]`.
+ * If this option was seen, `int_var` will be set to one (except
+ * if a `NULL` pointer was passed).
+ */
 #define OPT_ARGUMENT(l, v, h)       { OPTION_ARGUMENT, 0, (l), (v), NULL, \
 				      (h), PARSE_OPT_NOARG, NULL, 1 }
+
+/**
+ * Start an option group. `description` is a short string that describes the
+ * group or an empty string. Start the description with an upper-case letter.
+ */
 #define OPT_GROUP(h)                { OPTION_GROUP, 0, NULL, NULL, NULL, (h) }
+
+/**
+ * Introduce a boolean option. If used, `int_var` is bitwise-ored with `mask`.
+ */
 #define OPT_BIT(s, l, v, h, b)      OPT_BIT_F(s, l, v, h, b, 0)
+
 #define OPT_BITOP(s, l, v, h, set, clear) { OPTION_BITOP, (s), (l), (v), NULL, (h), \
 					    PARSE_OPT_NOARG|PARSE_OPT_NONEG, NULL, \
 					    (set), NULL, (clear) }
+
+/**
+ * Introduce a boolean option. If used, `int_var` is bitwise-anded with the
+ * inverted `mask`.
+ */
 #define OPT_NEGBIT(s, l, v, h, b)   { OPTION_NEGBIT, (s), (l), (v), NULL, \
 				      (h), PARSE_OPT_NOARG, NULL, (b) }
+
+/**
+ * Introduce a count-up option.
+ * Each use of `--option` increments `int_var`, starting from zero
+ * (even if initially negative), and `--no-option` resets it to
+ * zero. To determine if `--option` or `--no-option` was encountered at
+ * all, initialize `int_var` to a negative value, and if it is still
+ * negative after parse_options(), then neither `--option` nor
+ * `--no-option` was seen.
+ */
 #define OPT_COUNTUP(s, l, v, h)     OPT_COUNTUP_F(s, l, v, h, 0)
+
+/**
+ * Introduce an integer option. `int_var` is set to `integer` with `--option`,
+ * and reset to zero with `--no-option`.
+ */
 #define OPT_SET_INT(s, l, v, h, i)  OPT_SET_INT_F(s, l, v, h, i, 0)
+
+/**
+ * Introduce a boolean option. `int_var` is set to one with `--option` and set
+ * to zero with `--no-option`.
+ */
 #define OPT_BOOL(s, l, v, h)        OPT_BOOL_F(s, l, v, h, 0)
+
 #define OPT_HIDDEN_BOOL(s, l, v, h) { OPTION_SET_INT, (s), (l), (v), NULL, \
 				      (h), PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, NULL, 1}
+
+/**
+ * Define an "operation mode" option, only one of which in the same
+ * group of "operating mode" options that share the same `int_var`
+ * can be given by the user. `enum_val` is set to `int_var` when the
+ * option is used, but an error is reported if other "operating mode"
+ * option has already set its value to the same `int_var`.
+ */
 #define OPT_CMDMODE(s, l, v, h, i)  { OPTION_CMDMODE, (s), (l), (v), NULL, \
 				      (h), PARSE_OPT_NOARG|PARSE_OPT_NONEG, NULL, (i) }
+
+/**
+ * Introduce an option with integer argument. The integer is put into `int_var`.
+ */
 #define OPT_INTEGER(s, l, v, h)     OPT_INTEGER_F(s, l, v, h, 0)
+
+/**
+ * Introduce an option with a size argument. The argument must be a
+ * non-negative integer and may include a suffix of 'k', 'm' or 'g' to
+ * scale the provided value by 1024, 1024^2 or 1024^3 respectively.
+ * The scaled value is put into `unsigned_long_var`.
+ */
 #define OPT_MAGNITUDE(s, l, v, h)   { OPTION_MAGNITUDE, (s), (l), (v), \
 				      N_("n"), (h), PARSE_OPT_NONEG }
+
+/**
+ * Introduce an option with string argument. The string argument is put
+ * into `str_var`.
+ */
 #define OPT_STRING(s, l, v, a, h)   OPT_STRING_F(s, l, v, a, h, 0)
+
+/**
+ * Introduce an option with string argument.
+ * The string argument is stored as an element in `string_list`.
+ * Use of `--no-option` will clear the list of preceding values.
+ */
 #define OPT_STRING_LIST(s, l, v, a, h) \
 				    { OPTION_CALLBACK, (s), (l), (v), (a), \
 				      (h), 0, &parse_opt_string_list }
+
 #define OPT_UYN(s, l, v, h)         { OPTION_CALLBACK, (s), (l), (v), NULL, \
 				      (h), PARSE_OPT_NOARG, &parse_opt_tertiary }
+
+/**
+ * Introduce an option with expiry date argument, see `parse_expiry_date()`.
+ * The timestamp is put into `timestamp_t_var`.
+ */
 #define OPT_EXPIRY_DATE(s, l, v, h) \
 	{ OPTION_CALLBACK, (s), (l), (v), N_("expiry-date"),(h), 0,	\
 	  parse_opt_expiry_date_cb }
+
+/**
+ * Introduce an option with argument.
+ * The argument will be fed into the function given by `func_ptr`
+ * and the result will be put into `var`.
+ */
 #define OPT_CALLBACK(s, l, v, a, h, f) OPT_CALLBACK_F(s, l, v, a, h, 0, f)
+
+/**
+ * Recognize numerical options like -123 and feed the integer as
+ * if it was an argument to the function given by `func_ptr`.
+ * The result will be put into `var`.  There can be only one such
+ * option definition.  It cannot be negated and it takes no
+ * arguments.  Short options that happen to be digits take
+ * precedence over it.
+ */
 #define OPT_NUMBER_CALLBACK(v, h, f) \
 	{ OPTION_NUMBER, 0, NULL, (v), NULL, (h), \
 	  PARSE_OPT_NOARG | PARSE_OPT_NONEG, (f) }
+
+/**
+ * Introduce an option with a filename argument.
+ * The filename will be prefixed by passing the filename along with
+ * the prefix argument of `parse_options()` to `prefix_filename()`.
+ */
 #define OPT_FILENAME(s, l, v, h)    { OPTION_FILENAME, (s), (l), (v), \
 				       N_("file"), (h) }
+
+/**
+ * Introduce an option that takes an optional argument that can
+ * have one of three values: "always", "never", or "auto".  If the
+ * argument is not given, it defaults to "always".  The `--no-` form
+ * works like `--long=never`; it cannot take an argument.  If
+ * "always", set `int_var` to 1; if "never", set `int_var` to 0; if
+ * "auto", set `int_var` to 1 if stdout is a tty or a pager,
+ * 0 otherwise.
+ */
 #define OPT_COLOR_FLAG(s, l, v, h) \
 	{ OPTION_CALLBACK, (s), (l), (v), N_("when"), (h), PARSE_OPT_OPTARG, \
 		parse_opt_color_flag_cb, (intptr_t)"always" }
 
+/**
+ * Introduce an option that has no effect and takes no arguments.
+ * Use it to hide deprecated options that are still to be recognized
+ * and ignored silently.
+ */
 #define OPT_NOOP_NOARG(s, l) \
 	{ OPTION_CALLBACK, (s), (l), NULL, NULL, \
 	  N_("no-op (backward compatibility)"),		\
@@ -296,24 +597,52 @@ int parse_opt_noop_cb(const struct option *, const char *, int);
 enum parse_opt_result parse_opt_unknown_cb(struct parse_opt_ctx_t *ctx,
 					   const struct option *,
 					   const char *, int);
+
+/**
+ * Introduce an option that will be reconstructed into a char* string,
+ * which must be initialized to NULL. This is useful when you need to
+ * pass the command-line option to another command. Any previous value
+ * will be overwritten, so this should only be used for options where
+ * the last one specified on the command line wins.
+ */
 int parse_opt_passthru(const struct option *, const char *, int);
+
+/**
+ * Introduce an option where all instances of it on the command-line will
+ * be reconstructed into an argv_array. This is useful when you need to
+ * pass the command-line option, which can be specified multiple times,
+ * to another command.
+ */
 int parse_opt_passthru_argv(const struct option *, const char *, int);
 
+/* Add `-v, --verbose`. */
 #define OPT__VERBOSE(var, h)  OPT_COUNTUP('v', "verbose", (var), (h))
+
+/* Add `-q, --quiet`. */
 #define OPT__QUIET(var, h)    OPT_COUNTUP('q', "quiet",   (var), (h))
+
 #define OPT__VERBOSITY(var) \
 	{ OPTION_CALLBACK, 'v', "verbose", (var), NULL, N_("be more verbose"), \
 	  PARSE_OPT_NOARG, &parse_opt_verbosity_cb, 0 }, \
 	{ OPTION_CALLBACK, 'q', "quiet", (var), NULL, N_("be more quiet"), \
 	  PARSE_OPT_NOARG, &parse_opt_verbosity_cb, 0 }
+
+/* Add `-n, --dry-run`. */
 #define OPT__DRY_RUN(var, h)  OPT_BOOL('n', "dry-run", (var), (h))
+
+/* Add `-f, --force`. */
 #define OPT__FORCE(var, h, f) OPT_COUNTUP_F('f', "force",   (var), (h), (f))
+
+/* Add `--abbrev[=<n>]`. */
 #define OPT__ABBREV(var)  \
 	{ OPTION_CALLBACK, 0, "abbrev", (var), N_("n"),	\
 	  N_("use <n> digits to display SHA-1s"),	\
 	  PARSE_OPT_OPTARG, &parse_opt_abbrev_cb, 0 }
+
+/* Add `--color[=<when>]` and `--no-color`. */
 #define OPT__COLOR(var, h) \
 	OPT_COLOR_FLAG(0, "color", (var), (h))
+
 #define OPT_COLUMN(s, l, v, h) \
 	{ OPTION_CALLBACK, (s), (l), (v), N_("style"), (h), PARSE_OPT_OPTARG, parseopt_column_callback }
 #define OPT_PASSTHRU(s, l, v, a, h, f) \
-- 
gitgitgadget


^ permalink raw reply related	[relevance 4%]

* [PATCH v2 15/20] parse-options: move doc to parse-options.h
  @ 2019-11-06  9:59  4%   ` Heba Waly via GitGitGadget
    1 sibling, 0 replies; 200+ results
From: Heba Waly via GitGitGadget @ 2019-11-06  9:59 UTC (permalink / raw)
  To: git; +Cc: Heba Waly, Junio C Hamano, Heba Waly

From: Heba Waly <heba.waly@gmail.com>

Move the documentation from Documentation/technical/api-parse-options.txt
to parse-options.h as it's easier for the developers to find the usage
information beside the code instead of looking for it in another doc file.

Documentation/technical/api-parse-options.txt is removed because the
information it has is now redundant and it'll be hard to keep it up to
date and synchronized with the documentation in the header file.

Documentation/MyFirstContribution.txt now link
to parse-options.h instead of Documentation/technical/api-parse-options.txt for
details about parsing options.

Signed-off-by: Heba Waly <heba.waly@gmail.com>
---
 Documentation/MyFirstContribution.txt         |   2 +-
 Documentation/technical/api-parse-options.txt | 313 -----------------
 parse-options.h                               | 329 ++++++++++++++++++
 3 files changed, 330 insertions(+), 314 deletions(-)
 delete mode 100644 Documentation/technical/api-parse-options.txt

diff --git a/Documentation/MyFirstContribution.txt b/Documentation/MyFirstContribution.txt
index 5e9b808f5f..a2a91d92e2 100644
--- a/Documentation/MyFirstContribution.txt
+++ b/Documentation/MyFirstContribution.txt
@@ -487,7 +487,7 @@ Try and run `./bin-wrappers/git psuh -h`. Your command should crash at the end.
 That's because `-h` is a special case which your command should handle by
 printing usage.
 
-Take a look at `Documentation/technical/api-parse-options.txt`. This is a handy
+Take a look at `parse-options.h`. This is a handy
 tool for pulling out options you need to be able to handle, and it takes a
 usage string.
 
diff --git a/Documentation/technical/api-parse-options.txt b/Documentation/technical/api-parse-options.txt
deleted file mode 100644
index 2e2e7c10c6..0000000000
--- a/Documentation/technical/api-parse-options.txt
+++ /dev/null
@@ -1,313 +0,0 @@
-parse-options API
-=================
-
-The parse-options API is used to parse and massage options in Git
-and to provide a usage help with consistent look.
-
-Basics
-------
-
-The argument vector `argv[]` may usually contain mandatory or optional
-'non-option arguments', e.g. a filename or a branch, and 'options'.
-Options are optional arguments that start with a dash and
-that allow to change the behavior of a command.
-
-* There are basically three types of options:
-  'boolean' options,
-  options with (mandatory) 'arguments' and
-  options with 'optional arguments'
-  (i.e. a boolean option that can be adjusted).
-
-* There are basically two forms of options:
-  'Short options' consist of one dash (`-`) and one alphanumeric
-  character.
-  'Long options' begin with two dashes (`--`) and some
-  alphanumeric characters.
-
-* Options are case-sensitive.
-  Please define 'lower-case long options' only.
-
-The parse-options API allows:
-
-* 'stuck' and 'separate form' of options with arguments.
-  `-oArg` is stuck, `-o Arg` is separate form.
-  `--option=Arg` is stuck, `--option Arg` is separate form.
-
-* Long options may be 'abbreviated', as long as the abbreviation
-  is unambiguous.
-
-* Short options may be bundled, e.g. `-a -b` can be specified as `-ab`.
-
-* Boolean long options can be 'negated' (or 'unset') by prepending
-  `no-`, e.g. `--no-abbrev` instead of `--abbrev`. Conversely,
-  options that begin with `no-` can be 'negated' by removing it.
-  Other long options can be unset (e.g., set string to NULL, set
-  integer to 0) by prepending `no-`.
-
-* Options and non-option arguments can clearly be separated using the `--`
-  option, e.g. `-a -b --option -- --this-is-a-file` indicates that
-  `--this-is-a-file` must not be processed as an option.
-
-Steps to parse options
-----------------------
-
-. `#include "parse-options.h"`
-
-. define a NULL-terminated
-  `static const char * const builtin_foo_usage[]` array
-  containing alternative usage strings
-
-. define `builtin_foo_options` array as described below
-  in section 'Data Structure'.
-
-. in `cmd_foo(int argc, const char **argv, const char *prefix)`
-  call
-
-	argc = parse_options(argc, argv, prefix, builtin_foo_options, builtin_foo_usage, flags);
-+
-`parse_options()` will filter out the processed options of `argv[]` and leave the
-non-option arguments in `argv[]`.
-`argc` is updated appropriately because of the assignment.
-+
-You can also pass NULL instead of a usage array as the fifth parameter of
-parse_options(), to avoid displaying a help screen with usage info and
-option list.  This should only be done if necessary, e.g. to implement
-a limited parser for only a subset of the options that needs to be run
-before the full parser, which in turn shows the full help message.
-+
-Flags are the bitwise-or of:
-
-`PARSE_OPT_KEEP_DASHDASH`::
-	Keep the `--` that usually separates options from
-	non-option arguments.
-
-`PARSE_OPT_STOP_AT_NON_OPTION`::
-	Usually the whole argument vector is massaged and reordered.
-	Using this flag, processing is stopped at the first non-option
-	argument.
-
-`PARSE_OPT_KEEP_ARGV0`::
-	Keep the first argument, which contains the program name.  It's
-	removed from argv[] by default.
-
-`PARSE_OPT_KEEP_UNKNOWN`::
-	Keep unknown arguments instead of erroring out.  This doesn't
-	work for all combinations of arguments as users might expect
-	it to do.  E.g. if the first argument in `--unknown --known`
-	takes a value (which we can't know), the second one is
-	mistakenly interpreted as a known option.  Similarly, if
-	`PARSE_OPT_STOP_AT_NON_OPTION` is set, the second argument in
-	`--unknown value` will be mistakenly interpreted as a
-	non-option, not as a value belonging to the unknown option,
-	the parser early.  That's why parse_options() errors out if
-	both options are set.
-
-`PARSE_OPT_NO_INTERNAL_HELP`::
-	By default, parse_options() handles `-h`, `--help` and
-	`--help-all` internally, by showing a help screen.  This option
-	turns it off and allows one to add custom handlers for these
-	options, or to just leave them unknown.
-
-Data Structure
---------------
-
-The main data structure is an array of the `option` struct,
-say `static struct option builtin_add_options[]`.
-There are some macros to easily define options:
-
-`OPT__ABBREV(&int_var)`::
-	Add `--abbrev[=<n>]`.
-
-`OPT__COLOR(&int_var, description)`::
-	Add `--color[=<when>]` and `--no-color`.
-
-`OPT__DRY_RUN(&int_var, description)`::
-	Add `-n, --dry-run`.
-
-`OPT__FORCE(&int_var, description)`::
-	Add `-f, --force`.
-
-`OPT__QUIET(&int_var, description)`::
-	Add `-q, --quiet`.
-
-`OPT__VERBOSE(&int_var, description)`::
-	Add `-v, --verbose`.
-
-`OPT_GROUP(description)`::
-	Start an option group. `description` is a short string that
-	describes the group or an empty string.
-	Start the description with an upper-case letter.
-
-`OPT_BOOL(short, long, &int_var, description)`::
-	Introduce a boolean option. `int_var` is set to one with
-	`--option` and set to zero with `--no-option`.
-
-`OPT_COUNTUP(short, long, &int_var, description)`::
-	Introduce a count-up option.
-	Each use of `--option` increments `int_var`, starting from zero
-	(even if initially negative), and `--no-option` resets it to
-	zero. To determine if `--option` or `--no-option` was encountered at
-	all, initialize `int_var` to a negative value, and if it is still
-	negative after parse_options(), then neither `--option` nor
-	`--no-option` was seen.
-
-`OPT_BIT(short, long, &int_var, description, mask)`::
-	Introduce a boolean option.
-	If used, `int_var` is bitwise-ored with `mask`.
-
-`OPT_NEGBIT(short, long, &int_var, description, mask)`::
-	Introduce a boolean option.
-	If used, `int_var` is bitwise-anded with the inverted `mask`.
-
-`OPT_SET_INT(short, long, &int_var, description, integer)`::
-	Introduce an integer option.
-	`int_var` is set to `integer` with `--option`, and
-	reset to zero with `--no-option`.
-
-`OPT_STRING(short, long, &str_var, arg_str, description)`::
-	Introduce an option with string argument.
-	The string argument is put into `str_var`.
-
-`OPT_STRING_LIST(short, long, &struct string_list, arg_str, description)`::
-	Introduce an option with string argument.
-	The string argument is stored as an element in `string_list`.
-	Use of `--no-option` will clear the list of preceding values.
-
-`OPT_INTEGER(short, long, &int_var, description)`::
-	Introduce an option with integer argument.
-	The integer is put into `int_var`.
-
-`OPT_MAGNITUDE(short, long, &unsigned_long_var, description)`::
-	Introduce an option with a size argument. The argument must be a
-	non-negative integer and may include a suffix of 'k', 'm' or 'g' to
-	scale the provided value by 1024, 1024^2 or 1024^3 respectively.
-	The scaled value is put into `unsigned_long_var`.
-
-`OPT_EXPIRY_DATE(short, long, &timestamp_t_var, description)`::
-	Introduce an option with expiry date argument, see `parse_expiry_date()`.
-	The timestamp is put into `timestamp_t_var`.
-
-`OPT_CALLBACK(short, long, &var, arg_str, description, func_ptr)`::
-	Introduce an option with argument.
-	The argument will be fed into the function given by `func_ptr`
-	and the result will be put into `var`.
-	See 'Option Callbacks' below for a more elaborate description.
-
-`OPT_FILENAME(short, long, &var, description)`::
-	Introduce an option with a filename argument.
-	The filename will be prefixed by passing the filename along with
-	the prefix argument of `parse_options()` to `prefix_filename()`.
-
-`OPT_ARGUMENT(long, &int_var, description)`::
-	Introduce a long-option argument that will be kept in `argv[]`.
-	If this option was seen, `int_var` will be set to one (except
-	if a `NULL` pointer was passed).
-
-`OPT_NUMBER_CALLBACK(&var, description, func_ptr)`::
-	Recognize numerical options like -123 and feed the integer as
-	if it was an argument to the function given by `func_ptr`.
-	The result will be put into `var`.  There can be only one such
-	option definition.  It cannot be negated and it takes no
-	arguments.  Short options that happen to be digits take
-	precedence over it.
-
-`OPT_COLOR_FLAG(short, long, &int_var, description)`::
-	Introduce an option that takes an optional argument that can
-	have one of three values: "always", "never", or "auto".  If the
-	argument is not given, it defaults to "always".  The `--no-` form
-	works like `--long=never`; it cannot take an argument.  If
-	"always", set `int_var` to 1; if "never", set `int_var` to 0; if
-	"auto", set `int_var` to 1 if stdout is a tty or a pager,
-	0 otherwise.
-
-`OPT_NOOP_NOARG(short, long)`::
-	Introduce an option that has no effect and takes no arguments.
-	Use it to hide deprecated options that are still to be recognized
-	and ignored silently.
-
-`OPT_PASSTHRU(short, long, &char_var, arg_str, description, flags)`::
-	Introduce an option that will be reconstructed into a char* string,
-	which must be initialized to NULL. This is useful when you need to
-	pass the command-line option to another command. Any previous value
-	will be overwritten, so this should only be used for options where
-	the last one specified on the command line wins.
-
-`OPT_PASSTHRU_ARGV(short, long, &argv_array_var, arg_str, description, flags)`::
-	Introduce an option where all instances of it on the command-line will
-	be reconstructed into an argv_array. This is useful when you need to
-	pass the command-line option, which can be specified multiple times,
-	to another command.
-
-`OPT_CMDMODE(short, long, &int_var, description, enum_val)`::
-	Define an "operation mode" option, only one of which in the same
-	group of "operating mode" options that share the same `int_var`
-	can be given by the user. `enum_val` is set to `int_var` when the
-	option is used, but an error is reported if other "operating mode"
-	option has already set its value to the same `int_var`.
-
-
-The last element of the array must be `OPT_END()`.
-
-If not stated otherwise, interpret the arguments as follows:
-
-* `short` is a character for the short option
-  (e.g. `'e'` for `-e`, use `0` to omit),
-
-* `long` is a string for the long option
-  (e.g. `"example"` for `--example`, use `NULL` to omit),
-
-* `int_var` is an integer variable,
-
-* `str_var` is a string variable (`char *`),
-
-* `arg_str` is the string that is shown as argument
-  (e.g. `"branch"` will result in `<branch>`).
-  If set to `NULL`, three dots (`...`) will be displayed.
-
-* `description` is a short string to describe the effect of the option.
-  It shall begin with a lower-case letter and a full stop (`.`) shall be
-  omitted at the end.
-
-Option Callbacks
-----------------
-
-The function must be defined in this form:
-
-	int func(const struct option *opt, const char *arg, int unset)
-
-The callback mechanism is as follows:
-
-* Inside `func`, the only interesting member of the structure
-  given by `opt` is the void pointer `opt->value`.
-  `*opt->value` will be the value that is saved into `var`, if you
-  use `OPT_CALLBACK()`.
-  For example, do `*(unsigned long *)opt->value = 42;` to get 42
-  into an `unsigned long` variable.
-
-* Return value `0` indicates success and non-zero return
-  value will invoke `usage_with_options()` and, thus, die.
-
-* If the user negates the option, `arg` is `NULL` and `unset` is 1.
-
-Sophisticated option parsing
-----------------------------
-
-If you need, for example, option callbacks with optional arguments
-or without arguments at all, or if you need other special cases,
-that are not handled by the macros above, you need to specify the
-members of the `option` structure manually.
-
-This is not covered in this document, but well documented
-in `parse-options.h` itself.
-
-Examples
---------
-
-See `test-parse-options.c` and
-`builtin/add.c`,
-`builtin/clone.c`,
-`builtin/commit.c`,
-`builtin/fetch.c`,
-`builtin/fsck.c`,
-`builtin/rm.c`
-for real-world examples.
diff --git a/parse-options.h b/parse-options.h
index 38a33a087e..d71a3baa48 100644
--- a/parse-options.h
+++ b/parse-options.h
@@ -1,6 +1,103 @@
 #ifndef PARSE_OPTIONS_H
 #define PARSE_OPTIONS_H
 
+/**
+ * The parse-options API is used to parse and massage options in Git
+ * and to provide a usage help with consistent look.
+ *
+ * Basics
+ * ------
+ *
+ * The argument vector `argv[]` may usually contain mandatory or optional
+ * 'non-option arguments', e.g. a filename or a branch, and 'options'.
+ * Options are optional arguments that start with a dash and
+ * that allow to change the behavior of a command.
+ *
+ * - There are basically three types of options:
+ *   'boolean' options,
+ *   options with (mandatory) 'arguments' and
+ *   options with 'optional arguments'
+ *   (i.e. a boolean option that can be adjusted).
+ *
+ * - There are basically two forms of options:
+ *   'Short options' consist of one dash (`-`) and one alphanumeric
+ *   character.
+ *   'Long options' begin with two dashes (`--`) and some
+ *   alphanumeric characters.
+ *
+ * - Options are case-sensitive.
+ *   Please define 'lower-case long options' only.
+ *
+ * The parse-options API allows:
+ *
+ * - 'stuck' and 'separate form' of options with arguments.
+ *   `-oArg` is stuck, `-o Arg` is separate form.
+ *   `--option=Arg` is stuck, `--option Arg` is separate form.
+ *
+ * - Long options may be 'abbreviated', as long as the abbreviation
+ *   is unambiguous.
+ *
+ * - Short options may be bundled, e.g. `-a -b` can be specified as `-ab`.
+ *
+ * - Boolean long options can be 'negated' (or 'unset') by prepending
+ *   `no-`, e.g. `--no-abbrev` instead of `--abbrev`. Conversely,
+ *   options that begin with `no-` can be 'negated' by removing it.
+ *   Other long options can be unset (e.g., set string to NULL, set
+ *   integer to 0) by prepending `no-`.
+ *
+ * - Options and non-option arguments can clearly be separated using the `--`
+ *   option, e.g. `-a -b --option -- --this-is-a-file` indicates that
+ *   `--this-is-a-file` must not be processed as an option.
+ *
+ * Steps to parse options
+ * ----------------------
+ *
+ * - `#include "parse-options.h"`
+ *
+ * - define a NULL-terminated
+ *   `static const char * const builtin_foo_usage[]` array
+ *   containing alternative usage strings
+ *
+ * - define `builtin_foo_options` array as described below
+ *   in section 'Data Structure'.
+ *
+ * - in `cmd_foo(int argc, const char **argv, const char *prefix)`
+ *   call
+ *
+ * 	argc = parse_options(argc, argv, prefix, builtin_foo_options, builtin_foo_usage, flags);
+ *
+ * `parse_options()` will filter out the processed options of `argv[]` and leave the
+ * non-option arguments in `argv[]`.
+ * `argc` is updated appropriately because of the assignment.
+ *
+ * You can also pass NULL instead of a usage array as the fifth parameter of
+ * parse_options(), to avoid displaying a help screen with usage info and
+ * option list. This should only be done if necessary, e.g. to implement
+ * a limited parser for only a subset of the options that needs to be run
+ * before the full parser, which in turn shows the full help message.
+ *
+ * Sophisticated option parsing
+ * ----------------------------
+ *
+ * If you need, for example, option callbacks with optional arguments
+ * or without arguments at all, or if you need other special cases,
+ * that are not handled by the macros here, you need to specify the
+ * members of the `option` structure manually.
+ *
+ * Examples
+ * --------
+ *
+ * See `test-parse-options.c` and
+ * `builtin/add.c`,
+ * `builtin/clone.c`,
+ * `builtin/commit.c`,
+ * `builtin/fetch.c`,
+ * `builtin/fsck.c`,
+ * `builtin/rm.c`
+ * for real-world examples.
+ *
+ */
+
 enum parse_opt_type {
 	/* special types */
 	OPTION_END,
@@ -25,11 +122,47 @@ enum parse_opt_type {
 };
 
 enum parse_opt_flags {
+
+    /**
+     * Keep the `--` that usually separates options from non-option arguments.
+     */
 	PARSE_OPT_KEEP_DASHDASH = 1,
+
+	/**
+	 * Usually the whole argument vector is massaged and reordered.
+	 * Using this flag, processing is stopped at the first non-option
+	 * argument.
+	 */
 	PARSE_OPT_STOP_AT_NON_OPTION = 2,
+
+	/**
+	 * Keep the first argument, which contains the program name.  It's
+	 * removed from argv[] by default.
+	 */
 	PARSE_OPT_KEEP_ARGV0 = 4,
+
+	/**
+	 * Keep unknown arguments instead of erroring out.  This doesn't
+	 * work for all combinations of arguments as users might expect
+	 * it to do. E.g. if the first argument in `--unknown --known`
+	 * takes a value (which we can't know), the second one is
+	 * mistakenly interpreted as a known option.  Similarly, if
+	 * `PARSE_OPT_STOP_AT_NON_OPTION` is set, the second argument in
+	 * `--unknown value` will be mistakenly interpreted as a
+	 * non-option, not as a value belonging to the unknown option,
+	 * the parser early. That's why parse_options() errors out if
+	 * both options are set.
+	 */
 	PARSE_OPT_KEEP_UNKNOWN = 8,
+
+	/**
+	 * By default, parse_options() handles `-h`, `--help` and
+	 * `--help-all` internally, by showing a help screen. This option
+	 * turns it off and allows one to add custom handlers for these
+	 * options, or to just leave them unknown.
+	 */
 	PARSE_OPT_NO_INTERNAL_HELP = 16,
+
 	PARSE_OPT_ONE_SHOT = 32
 };
 
@@ -56,6 +189,30 @@ enum parse_opt_result {
 };
 
 struct option;
+
+/**
+ * Option Callbacks
+ * ----------------
+ *
+ * The function must be defined in this form:
+ *
+ * 	int func(const struct option *opt, const char *arg, int unset)
+ *
+ * The callback mechanism is as follows:
+ *
+ * - Inside `func`, the only interesting member of the structure
+ *   given by `opt` is the void pointer `opt->value`.
+ *   `*opt->value` will be the value that is saved into `var`, if you
+ *   use `OPT_CALLBACK()`.
+ *   For example, do `*(unsigned long *)opt->value = 42;` to get 42
+ *   into an `unsigned long` variable.
+ *
+ * - Return value `0` indicates success and non-zero return
+ *   value will invoke `usage_with_options()` and, thus, die.
+ *
+ * - If the user negates the option, `arg` is `NULL` and `unset` is 1.
+ *
+ */
 typedef int parse_opt_cb(const struct option *, const char *arg, int unset);
 
 struct parse_opt_ctx_t;
@@ -137,6 +294,36 @@ struct option {
 	intptr_t extra;
 };
 
+/**
+ * Data Structure
+ * --------------
+ *
+ * The main data structure is an array of the `option` struct, say
+ * `static struct option builtin_add_options[]`.
+ * The following macros can be used to easily define options.
+ * The last element of the array must be `OPT_END()`.
+ *
+ * If not stated otherwise, interpret the arguments as follows:
+ *
+ * - `short` is a character for the short option
+ *   (e.g. `'e'` for `-e`, use `0` to omit),
+ *
+ * - `long` is a string for the long option
+ *   (e.g. `"example"` for `--example`, use `NULL` to omit),
+ *
+ * - `int_var` is an integer variable,
+ *
+ * - `str_var` is a string variable (`char *`),
+ *
+ * - `arg_str` is the string that is shown as argument
+ *   (e.g. `"branch"` will result in `<branch>`).
+ *   If set to `NULL`, three dots (`...`) will be displayed.
+ *
+ * - `description` is a short string to describe the effect of the option.
+ *   It shall begin with a lower-case letter and a full stop (`.`) shall be
+ *   omitted at the end.
+ */
+
 #define OPT_BIT_F(s, l, v, h, b, f) { OPTION_BIT, (s), (l), (v), NULL, (h), \
 				      PARSE_OPT_NOARG|(f), NULL, (b) }
 #define OPT_COUNTUP_F(s, l, v, h, f) { OPTION_COUNTUP, (s), (l), (v), NULL, \
@@ -150,44 +337,158 @@ struct option {
 #define OPT_INTEGER_F(s, l, v, h, f)     { OPTION_INTEGER, (s), (l), (v), N_("n"), (h), (f) }
 
 #define OPT_END()                   { OPTION_END }
+
+/**
+ * Introduce a long-option argument that will be kept in `argv[]`.
+ * If this option was seen, `int_var` will be set to one (except
+ * if a `NULL` pointer was passed).
+ */
 #define OPT_ARGUMENT(l, v, h)       { OPTION_ARGUMENT, 0, (l), (v), NULL, \
 				      (h), PARSE_OPT_NOARG, NULL, 1 }
+
+/**
+ * Start an option group. `description` is a short string that describes the
+ * group or an empty string. Start the description with an upper-case letter.
+ */
 #define OPT_GROUP(h)                { OPTION_GROUP, 0, NULL, NULL, NULL, (h) }
+
+/**
+ * Introduce a boolean option. If used, `int_var` is bitwise-ored with `mask`.
+ */
 #define OPT_BIT(s, l, v, h, b)      OPT_BIT_F(s, l, v, h, b, 0)
+
 #define OPT_BITOP(s, l, v, h, set, clear) { OPTION_BITOP, (s), (l), (v), NULL, (h), \
 					    PARSE_OPT_NOARG|PARSE_OPT_NONEG, NULL, \
 					    (set), NULL, (clear) }
+
+/**
+ * Introduce a boolean option. If used, `int_var` is bitwise-anded with the
+ * inverted `mask`.
+ */
 #define OPT_NEGBIT(s, l, v, h, b)   { OPTION_NEGBIT, (s), (l), (v), NULL, \
 				      (h), PARSE_OPT_NOARG, NULL, (b) }
+
+/**
+ * Introduce a count-up option.
+ * Each use of `--option` increments `int_var`, starting from zero
+ * (even if initially negative), and `--no-option` resets it to
+ * zero. To determine if `--option` or `--no-option` was encountered at
+ * all, initialize `int_var` to a negative value, and if it is still
+ * negative after parse_options(), then neither `--option` nor
+ * `--no-option` was seen.
+ */
 #define OPT_COUNTUP(s, l, v, h)     OPT_COUNTUP_F(s, l, v, h, 0)
+
+/**
+ * Introduce an integer option. `int_var` is set to `integer` with `--option`,
+ * and reset to zero with `--no-option`.
+ */
 #define OPT_SET_INT(s, l, v, h, i)  OPT_SET_INT_F(s, l, v, h, i, 0)
+
+/**
+ * Introduce a boolean option. `int_var` is set to one with `--option` and set
+ * to zero with `--no-option`.
+ */
 #define OPT_BOOL(s, l, v, h)        OPT_BOOL_F(s, l, v, h, 0)
+
 #define OPT_HIDDEN_BOOL(s, l, v, h) { OPTION_SET_INT, (s), (l), (v), NULL, \
 				      (h), PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, NULL, 1}
+
+/**
+ * Define an "operation mode" option, only one of which in the same
+ * group of "operating mode" options that share the same `int_var`
+ * can be given by the user. `enum_val` is set to `int_var` when the
+ * option is used, but an error is reported if other "operating mode"
+ * option has already set its value to the same `int_var`.
+ */
 #define OPT_CMDMODE(s, l, v, h, i)  { OPTION_CMDMODE, (s), (l), (v), NULL, \
 				      (h), PARSE_OPT_NOARG|PARSE_OPT_NONEG, NULL, (i) }
+
+/**
+ * Introduce an option with integer argument. The integer is put into `int_var`.
+ */
 #define OPT_INTEGER(s, l, v, h)     OPT_INTEGER_F(s, l, v, h, 0)
+
+/**
+ * Introduce an option with a size argument. The argument must be a
+ * non-negative integer and may include a suffix of 'k', 'm' or 'g' to
+ * scale the provided value by 1024, 1024^2 or 1024^3 respectively.
+ * The scaled value is put into `unsigned_long_var`.
+ */
 #define OPT_MAGNITUDE(s, l, v, h)   { OPTION_MAGNITUDE, (s), (l), (v), \
 				      N_("n"), (h), PARSE_OPT_NONEG }
+
+/**
+ * Introduce an option with string argument. The string argument is put
+ * into `str_var`.
+ */
 #define OPT_STRING(s, l, v, a, h)   OPT_STRING_F(s, l, v, a, h, 0)
+
+/**
+ * Introduce an option with string argument.
+ * The string argument is stored as an element in `string_list`.
+ * Use of `--no-option` will clear the list of preceding values.
+ */
 #define OPT_STRING_LIST(s, l, v, a, h) \
 				    { OPTION_CALLBACK, (s), (l), (v), (a), \
 				      (h), 0, &parse_opt_string_list }
+
 #define OPT_UYN(s, l, v, h)         { OPTION_CALLBACK, (s), (l), (v), NULL, \
 				      (h), PARSE_OPT_NOARG, &parse_opt_tertiary }
+
+/**
+ * Introduce an option with expiry date argument, see `parse_expiry_date()`.
+ * The timestamp is put into `timestamp_t_var`.
+ */
 #define OPT_EXPIRY_DATE(s, l, v, h) \
 	{ OPTION_CALLBACK, (s), (l), (v), N_("expiry-date"),(h), 0,	\
 	  parse_opt_expiry_date_cb }
+
+/**
+ * Introduce an option with argument.
+ * The argument will be fed into the function given by `func_ptr`
+ * and the result will be put into `var`.
+ */
 #define OPT_CALLBACK(s, l, v, a, h, f) OPT_CALLBACK_F(s, l, v, a, h, 0, f)
+
+/**
+ * Recognize numerical options like -123 and feed the integer as
+ * if it was an argument to the function given by `func_ptr`.
+ * The result will be put into `var`.  There can be only one such
+ * option definition.  It cannot be negated and it takes no
+ * arguments.  Short options that happen to be digits take
+ * precedence over it.
+ */
 #define OPT_NUMBER_CALLBACK(v, h, f) \
 	{ OPTION_NUMBER, 0, NULL, (v), NULL, (h), \
 	  PARSE_OPT_NOARG | PARSE_OPT_NONEG, (f) }
+
+/**
+ * Introduce an option with a filename argument.
+ * The filename will be prefixed by passing the filename along with
+ * the prefix argument of `parse_options()` to `prefix_filename()`.
+ */
 #define OPT_FILENAME(s, l, v, h)    { OPTION_FILENAME, (s), (l), (v), \
 				       N_("file"), (h) }
+
+/**
+ * Introduce an option that takes an optional argument that can
+ * have one of three values: "always", "never", or "auto".  If the
+ * argument is not given, it defaults to "always".  The `--no-` form
+ * works like `--long=never`; it cannot take an argument.  If
+ * "always", set `int_var` to 1; if "never", set `int_var` to 0; if
+ * "auto", set `int_var` to 1 if stdout is a tty or a pager,
+ * 0 otherwise.
+ */
 #define OPT_COLOR_FLAG(s, l, v, h) \
 	{ OPTION_CALLBACK, (s), (l), (v), N_("when"), (h), PARSE_OPT_OPTARG, \
 		parse_opt_color_flag_cb, (intptr_t)"always" }
 
+/**
+ * Introduce an option that has no effect and takes no arguments.
+ * Use it to hide deprecated options that are still to be recognized
+ * and ignored silently.
+ */
 #define OPT_NOOP_NOARG(s, l) \
 	{ OPTION_CALLBACK, (s), (l), NULL, NULL, \
 	  N_("no-op (backward compatibility)"),		\
@@ -296,24 +597,52 @@ int parse_opt_noop_cb(const struct option *, const char *, int);
 enum parse_opt_result parse_opt_unknown_cb(struct parse_opt_ctx_t *ctx,
 					   const struct option *,
 					   const char *, int);
+
+/**
+ * Introduce an option that will be reconstructed into a char* string,
+ * which must be initialized to NULL. This is useful when you need to
+ * pass the command-line option to another command. Any previous value
+ * will be overwritten, so this should only be used for options where
+ * the last one specified on the command line wins.
+ */
 int parse_opt_passthru(const struct option *, const char *, int);
+
+/**
+ * Introduce an option where all instances of it on the command-line will
+ * be reconstructed into an argv_array. This is useful when you need to
+ * pass the command-line option, which can be specified multiple times,
+ * to another command.
+ */
 int parse_opt_passthru_argv(const struct option *, const char *, int);
 
+/* Add `-v, --verbose`. */
 #define OPT__VERBOSE(var, h)  OPT_COUNTUP('v', "verbose", (var), (h))
+
+/* Add `-q, --quiet`. */
 #define OPT__QUIET(var, h)    OPT_COUNTUP('q', "quiet",   (var), (h))
+
 #define OPT__VERBOSITY(var) \
 	{ OPTION_CALLBACK, 'v', "verbose", (var), NULL, N_("be more verbose"), \
 	  PARSE_OPT_NOARG, &parse_opt_verbosity_cb, 0 }, \
 	{ OPTION_CALLBACK, 'q', "quiet", (var), NULL, N_("be more quiet"), \
 	  PARSE_OPT_NOARG, &parse_opt_verbosity_cb, 0 }
+
+/* Add `-n, --dry-run`. */
 #define OPT__DRY_RUN(var, h)  OPT_BOOL('n', "dry-run", (var), (h))
+
+/* Add `-f, --force`. */
 #define OPT__FORCE(var, h, f) OPT_COUNTUP_F('f', "force",   (var), (h), (f))
+
+/* Add `--abbrev[=<n>]`. */
 #define OPT__ABBREV(var)  \
 	{ OPTION_CALLBACK, 0, "abbrev", (var), N_("n"),	\
 	  N_("use <n> digits to display SHA-1s"),	\
 	  PARSE_OPT_OPTARG, &parse_opt_abbrev_cb, 0 }
+
+/* Add `--color[=<when>]` and `--no-color`. */
 #define OPT__COLOR(var, h) \
 	OPT_COLOR_FLAG(0, "color", (var), (h))
+
 #define OPT_COLUMN(s, l, v, h) \
 	{ OPTION_CALLBACK, (s), (l), (v), N_("style"), (h), PARSE_OPT_OPTARG, parseopt_column_callback }
 #define OPT_PASSTHRU(s, l, v, a, h, f) \
-- 
gitgitgadget


^ permalink raw reply related	[relevance 4%]

* Re: [PATCH v3] dir: special case check for the possibility that pathspec is NULL
  @ 2019-10-07 18:04  5%   ` SZEDER Gábor
  0 siblings, 0 replies; 200+ results
From: SZEDER Gábor @ 2019-10-07 18:04 UTC (permalink / raw)
  To: Elijah Newren; +Cc: git, Junio C Hamano, Denton Liu

On Tue, Oct 01, 2019 at 11:55:24AM -0700, Elijah Newren wrote:
> Commits 404ebceda01c ("dir: also check directories for matching
> pathspecs", 2019-09-17) and 89a1f4aaf765 ("dir: if our pathspec might
> match files under a dir, recurse into it", 2019-09-17) added calls to
> match_pathspec() and do_match_pathspec() passing along their pathspec
> parameter.  Both match_pathspec() and do_match_pathspec() assume the
> pathspec argument they are given is non-NULL.  It turns out that
> unpack-tree.c's verify_clean_subdirectory() calls read_directory() with
> pathspec == NULL, and it is possible on case insensitive filesystems for
> that NULL to make it to these new calls to match_pathspec() and
> do_match_pathspec().  Add appropriate checks on the NULLness of pathspec
> to avoid a segfault.
> 
> In case the negation throws anyone off (one of the calls was to
> do_match_pathspec() while the other was to !match_pathspec(), yet no
> negation of the NULLness of pathspec is used), there are two ways to
> understand the differences:
>   * The code already handled the pathspec == NULL cases before this
>     series, and this series only tried to change behavior when there was
>     a pathspec, thus we only want to go into the if-block if pathspec is
>     non-NULL.
>   * One of the calls is for whether to recurse into a subdirectory, the
>     other is for after we've recursed into it for whether we want to
>     remove the subdirectory itself (i.e. the subdirectory didn't match
>     but something under it could have).  That difference in situation
>     leads to the slight differences in logic used (well, that and the
>     slightly unusual fact that we don't want empty pathspecs to remove
>     untracked directories by default).
> 
> Denton found and analyzed one issue and provided the patch for the
> match_pathspec() call, SZEDER figured out why the issue only reproduced
> for some folks and not others and provided the testcase, and I looked
> through the remainder of the series and noted the do_match_pathspec()
> call that should have the same check.
> 
> Co-authored-by: Denton Liu <liu.denton@gmail.com>
> Co-authored-by: SZEDER Gábor <szeder.dev@gmail.com>
> Signed-off-by: Elijah Newren <newren@gmail.com>
> ---
> Note: Applies on top of en/clean-nested-with-ignored, in next.
> 
> As with v1, the authorship is really mixed, so I don't know if I
> should use Co-authored-by (highlighted as a possibility by Denton), or
> the far more common Helped-by (as suggested by Junio but based on a
> more limited summary of the different contributions), or if perhaps
> Denton or SZEDER should be marked as the author and I be marked as
> Helped-by or Co-authored-by.  Since Denton commented on round 1, I
> used his suggestion for attribution in this round, but I'm open to
> changing it to whatever works best.
> 
> Changes since v2:
>   - This time actually removed the entire unnecessary comment
> 
> Range-diff:
> 1:  c495b9303c ! 1:  40392c6bba dir: special case check for the possibility that pathspec is NULL
>     @@ t/t0050-filesystem.sh: $test_unicode 'merge (silent unicode normalization)' '
>      +		git reset --hard &&
>      +		mkdir -p gitweb/subdir &&
>      +		>gitweb/subdir/file &&
>     -+		# it is not strictly necessary to add and commit the
>      +		git add gitweb &&
>      +		git commit -m "add gitweb/subdir/file" &&
>      +
> 
>  dir.c                 |  8 +++++---
>  t/t0050-filesystem.sh | 21 +++++++++++++++++++++
>  2 files changed, 26 insertions(+), 3 deletions(-)
> 
> diff --git a/dir.c b/dir.c
> index 7ff79170fc..bd39b86be4 100644
> --- a/dir.c
> +++ b/dir.c
> @@ -1962,8 +1962,9 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,
>  			((state == path_untracked) &&
>  			 (get_dtype(cdir.de, istate, path.buf, path.len) == DT_DIR) &&
>  			 ((dir->flags & DIR_SHOW_IGNORED_TOO) ||
> -			  do_match_pathspec(istate, pathspec, path.buf, path.len,
> -					    baselen, NULL, DO_MATCH_LEADING_PATHSPEC) == MATCHED_RECURSIVELY_LEADING_PATHSPEC))) {
> +			  (pathspec &&
> +			   do_match_pathspec(istate, pathspec, path.buf, path.len,
> +					     baselen, NULL, DO_MATCH_LEADING_PATHSPEC) == MATCHED_RECURSIVELY_LEADING_PATHSPEC)))) {
>  			struct untracked_cache_dir *ud;
>  			ud = lookup_untracked(dir->untracked, untracked,
>  					      path.buf + baselen,
> @@ -1975,7 +1976,8 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,
>  			if (subdir_state > dir_state)
>  				dir_state = subdir_state;
>  
> -			if (!match_pathspec(istate, pathspec, path.buf, path.len,
> +			if (pathspec &&
> +			    !match_pathspec(istate, pathspec, path.buf, path.len,
>  					    0 /* prefix */, NULL,
>  					    0 /* do NOT special case dirs */))
>  				state = path_none;
> diff --git a/t/t0050-filesystem.sh b/t/t0050-filesystem.sh
> index 192c94eccd..a840919967 100755
> --- a/t/t0050-filesystem.sh
> +++ b/t/t0050-filesystem.sh
> @@ -131,4 +131,25 @@ $test_unicode 'merge (silent unicode normalization)' '
>  	git merge topic
>  '
>  
> +test_expect_success CASE_INSENSITIVE_FS 'checkout with no pathspec and a case insensitive fs' '
> +	git init repo &&
> +	(
> +		cd repo &&
> +
> +		>Gitweb &&
> +		git add Gitweb &&
> +		git commit -m "add Gitweb" &&
> +
> +		git checkout --orphan todo &&
> +		git reset --hard &&
> +		mkdir -p gitweb/subdir &&
> +		>gitweb/subdir/file &&
> +		git add gitweb &&
> +		git commit -m "add gitweb/subdir/file" &&
> +
> +		git checkout master
> +	)
> +'

I don't like this test ;)

I only intended it as a "here is how to reliably reproduce the
segfault without all the clutter of the full git.git repository" that
I wrote way past my bedtime.  But I think that:

  - it shouldn't have the CASE_INSENSITIVE_FS prereq.  Yes, that
    segfault could only be triggered on a case insensitive filesystem,
    but the given sequence of commands should succeed in a case
    sensitive file system just as well.

    (Have no idea why I added that prereq in the first place; as I
    said above, it was way past my bedtime...)

  - it's in the wrong test script; it would be better among other
    tests checking what 'git checkout' should or must not overwrite
    when switching branches, but not sure which test script that is.

    (I think I added it to this test script, because it stood out a
    bit when grepping for case insensitive fs in the test suite; I
    play the "past my bedtime" card again :)

  - it's already satisfied by 'git checkout master' not failing, but
    it doesn't check whether the resulting contents of the worktree
    are as expected.

  - it still bothers me why that additional subdir was necessary to
    trigger the segfault.  Did you look into it?


^ permalink raw reply	[relevance 5%]

* Re: [BUG] git is segfaulting, was [PATCH v4 04/12] dir: also check directories for matching pathspecs
  2019-09-27  2:17  6%         ` SZEDER Gábor
@ 2019-09-27 17:10  0%           ` Denton Liu
  0 siblings, 0 replies; 200+ results
From: Denton Liu @ 2019-09-27 17:10 UTC (permalink / raw)
  To: SZEDER Gábor
  Cc: Elijah Newren, git, Junio C Hamano, Jeff King,
	Rafael Ascensão, Samuel Lijin

On Fri, Sep 27, 2019 at 04:17:46AM +0200, SZEDER Gábor wrote:
> On Fri, Sep 27, 2019 at 03:09:30AM +0200, SZEDER Gábor wrote:
> > On Wed, Sep 25, 2019 at 01:39:19PM -0700, Denton Liu wrote:
> > > Hi Elijah,
> > > 
> > > I ran into a segfault on MacOS. I managed to bisect it down to
> > > 404ebceda0 (dir: also check directories for matching pathspecs,
> > > 2019-09-17), which should be the patch in the parent thread. The test
> > > case below works fine without this patch applied but segfaults once it
> > > is applied.
> > > 
> > > 	#!/bin/sh
> > > 
> > > 	git worktree add testdir
> > > 	git -C testdir checkout master
> > > 	git -C testdir fetch https://github.com/git/git.git todo
> > > 	bin-wrappers/git -C testdir checkout FETCH_HEAD # segfault here
> > > 
> > > Note that the worktree part isn't necessary to reproduce the problem but
> > > I didn't want my files to be constantly refreshed, triggering a rebuild
> > > each time.
> > > 
> > > I also managed to get this backtrace from running lldb at the segfault
> > > but it is based on the latest "jch" commit, 1cc52d20df (Merge branch
> > > 'jt/merge-recursive-symlink-is-not-a-dir-in-way' into jch, 2019-09-20).
> > > 
> > > 	* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x8)
> > > 	  * frame #0: 0x00000001000f63a0 git`do_match_pathspec(istate=0x0000000100299940, ps=0x000000010200aa80, name="Gitweb/static/js/lib/", namelen=21, prefix=0, seen=0x0000000000000000, flags=0) at dir.c:420:2 [opt]
> > > 		frame #1: 0x00000001000f632c git`match_pathspec(istate=0x0000000100299940, ps=0x0000000000000000, name="Gitweb/static/js/lib/", namelen=21, prefix=0, seen=0x0000000000000000, is_dir=0) at dir.c:490:13 [opt]
> > > 		frame #2: 0x00000001000f8315 git`read_directory_recursive(dir=0x00007ffeefbfe278, istate=0x0000000100299940, base=<unavailable>, baselen=17, untracked=<unavailable>, check_only=0, stop_at_first_file=0, pathspec=0x0000000000000000) at dir.c:1990:9 [opt]
> > > 		frame #3: 0x00000001000f82e9 git`read_directory_recursive(dir=0x00007ffeefbfe278, istate=0x0000000100299940, base=<unavailable>, baselen=14, untracked=<unavailable>, check_only=0, stop_at_first_file=0, pathspec=0x0000000000000000) at dir.c:1984:5 [opt]
> > > 		frame #4: 0x00000001000f82e9 git`read_directory_recursive(dir=0x00007ffeefbfe278, istate=0x0000000100299940, base=<unavailable>, baselen=7, untracked=<unavailable>, check_only=0, stop_at_first_file=0, pathspec=0x0000000000000000) at dir.c:1984:5 [opt]
> > > 		frame #5: 0x00000001000f60d1 git`read_directory(dir=0x00007ffeefbfe278, istate=0x0000000100299940, path="Gitweb/", len=7, pathspec=0x0000000000000000) at dir.c:2298:3 [opt]
> > > 		frame #6: 0x00000001001bded1 git`verify_clean_subdirectory(ce=<unavailable>, o=0x00007ffeefbfe8c0) at unpack-trees.c:1846:6 [opt]
> > > 		frame #7: 0x00000001001bdc1d git`check_ok_to_remove(name="Gitweb", len=6, dtype=4, ce=0x0000000103e70de0, st=0x00007ffeefbfe438, error_type=ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN, o=0x00007ffeefbfe8c0) at unpack-trees.c:1901:7 [opt]
> > 
> > That 'name="Gitweb" parameter caught my eye.  origin/todo contains a
> > 'Gitweb' file, with upper case 'G', while master contains a 'gitweb'
> > directory, with lower case 'g'.  
> > 
> > Could it be that case (in)sensitivity plays a crucial rule in
> > triggering the segfault?  FWIW I could reproduce it following Denton's
> > description on Travis CI's macOS VM with the debug shell access, and
> > it uses case insensitive file system.
> 
> Indeed, with 404ebceda0 the test below segfaults on case insensitive
> fs, but not on a case sensitive one.

Wow, good catch. I didn't even notice that in the backtrace.

> 
> 
> diff --git a/t/t0050-filesystem.sh b/t/t0050-filesystem.sh
> index 192c94eccd..5b405c97d7 100755
> --- a/t/t0050-filesystem.sh
> +++ b/t/t0050-filesystem.sh
> @@ -131,4 +131,27 @@ $test_unicode 'merge (silent unicode normalization)' '
>  	git merge topic
>  '
>  
> +test_expect_success CASE_INSENSITIVE_FS "Denton's segfault" '
> +	git init repo &&
> +	(
> +		cd repo &&
> +
> +		echo foo >Gitweb &&
> +		git add Gitweb &&
> +		git commit -m "add Gitweb" &&
> +
> +		git checkout --orphan todo &&
> +		git reset --hard &&
> +		# the subdir is crucial, without it there is no segfault
> +		mkdir -p gitweb/subdir &&
> +		echo bar >gitweb/subdir/file &&
> +		# it is not strictly necessary to add and commit the
> +		# gitweb directory, its presence is sufficient
> +		git add gitweb &&
> +		git commit -m "add gitweb/subdir/file" &&
> +
> +		git checkout master
> +	)
> +'
> +
>  test_done

I can confirm that this test case reproduces for me. Thanks for writing
this.

> 
> 
> 
> The end of its trace:
> 
> ++git checkout master
> ./test-lib.sh: line 910: 11220 Segmentation fault: 11  git checkout master
> error: last command exited with $?=139
> 
> Case insensitivity is important because check_ok_to_remove() is
> invoked from verify_absent_1(), which looks like this:
> 
>   if (...)
>      ....
>   else if (...)
>      ....
>   else if (lstat(ce->name, &st))
>       // That lstat() checked whether 'Gitweb' is absent.  On a case
>       // sensitive fs it's absent, so it returns.  On a case
>       // insensitive fs it finds 'master's 'gitweb' directory, so it
>       // goes on to the else below, and eventually segfaults.
>       return;
>   else
>       check_ok_to_remove()
> 
> 
> Good night :)

Thanks for your help!

^ permalink raw reply	[relevance 0%]

* Re: [BUG] git is segfaulting, was [PATCH v4 04/12] dir: also check directories for matching pathspecs
  @ 2019-09-27  2:17  6%         ` SZEDER Gábor
  2019-09-27 17:10  0%           ` Denton Liu
  0 siblings, 1 reply; 200+ results
From: SZEDER Gábor @ 2019-09-27  2:17 UTC (permalink / raw)
  To: Denton Liu
  Cc: Elijah Newren, git, Junio C Hamano, Jeff King,
	Rafael Ascensão, Samuel Lijin

On Fri, Sep 27, 2019 at 03:09:30AM +0200, SZEDER Gábor wrote:
> On Wed, Sep 25, 2019 at 01:39:19PM -0700, Denton Liu wrote:
> > Hi Elijah,
> > 
> > I ran into a segfault on MacOS. I managed to bisect it down to
> > 404ebceda0 (dir: also check directories for matching pathspecs,
> > 2019-09-17), which should be the patch in the parent thread. The test
> > case below works fine without this patch applied but segfaults once it
> > is applied.
> > 
> > 	#!/bin/sh
> > 
> > 	git worktree add testdir
> > 	git -C testdir checkout master
> > 	git -C testdir fetch https://github.com/git/git.git todo
> > 	bin-wrappers/git -C testdir checkout FETCH_HEAD # segfault here
> > 
> > Note that the worktree part isn't necessary to reproduce the problem but
> > I didn't want my files to be constantly refreshed, triggering a rebuild
> > each time.
> > 
> > I also managed to get this backtrace from running lldb at the segfault
> > but it is based on the latest "jch" commit, 1cc52d20df (Merge branch
> > 'jt/merge-recursive-symlink-is-not-a-dir-in-way' into jch, 2019-09-20).
> > 
> > 	* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x8)
> > 	  * frame #0: 0x00000001000f63a0 git`do_match_pathspec(istate=0x0000000100299940, ps=0x000000010200aa80, name="Gitweb/static/js/lib/", namelen=21, prefix=0, seen=0x0000000000000000, flags=0) at dir.c:420:2 [opt]
> > 		frame #1: 0x00000001000f632c git`match_pathspec(istate=0x0000000100299940, ps=0x0000000000000000, name="Gitweb/static/js/lib/", namelen=21, prefix=0, seen=0x0000000000000000, is_dir=0) at dir.c:490:13 [opt]
> > 		frame #2: 0x00000001000f8315 git`read_directory_recursive(dir=0x00007ffeefbfe278, istate=0x0000000100299940, base=<unavailable>, baselen=17, untracked=<unavailable>, check_only=0, stop_at_first_file=0, pathspec=0x0000000000000000) at dir.c:1990:9 [opt]
> > 		frame #3: 0x00000001000f82e9 git`read_directory_recursive(dir=0x00007ffeefbfe278, istate=0x0000000100299940, base=<unavailable>, baselen=14, untracked=<unavailable>, check_only=0, stop_at_first_file=0, pathspec=0x0000000000000000) at dir.c:1984:5 [opt]
> > 		frame #4: 0x00000001000f82e9 git`read_directory_recursive(dir=0x00007ffeefbfe278, istate=0x0000000100299940, base=<unavailable>, baselen=7, untracked=<unavailable>, check_only=0, stop_at_first_file=0, pathspec=0x0000000000000000) at dir.c:1984:5 [opt]
> > 		frame #5: 0x00000001000f60d1 git`read_directory(dir=0x00007ffeefbfe278, istate=0x0000000100299940, path="Gitweb/", len=7, pathspec=0x0000000000000000) at dir.c:2298:3 [opt]
> > 		frame #6: 0x00000001001bded1 git`verify_clean_subdirectory(ce=<unavailable>, o=0x00007ffeefbfe8c0) at unpack-trees.c:1846:6 [opt]
> > 		frame #7: 0x00000001001bdc1d git`check_ok_to_remove(name="Gitweb", len=6, dtype=4, ce=0x0000000103e70de0, st=0x00007ffeefbfe438, error_type=ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN, o=0x00007ffeefbfe8c0) at unpack-trees.c:1901:7 [opt]
> 
> That 'name="Gitweb" parameter caught my eye.  origin/todo contains a
> 'Gitweb' file, with upper case 'G', while master contains a 'gitweb'
> directory, with lower case 'g'.  
> 
> Could it be that case (in)sensitivity plays a crucial rule in
> triggering the segfault?  FWIW I could reproduce it following Denton's
> description on Travis CI's macOS VM with the debug shell access, and
> it uses case insensitive file system.

Indeed, with 404ebceda0 the test below segfaults on case insensitive
fs, but not on a case sensitive one.


diff --git a/t/t0050-filesystem.sh b/t/t0050-filesystem.sh
index 192c94eccd..5b405c97d7 100755
--- a/t/t0050-filesystem.sh
+++ b/t/t0050-filesystem.sh
@@ -131,4 +131,27 @@ $test_unicode 'merge (silent unicode normalization)' '
 	git merge topic
 '
 
+test_expect_success CASE_INSENSITIVE_FS "Denton's segfault" '
+	git init repo &&
+	(
+		cd repo &&
+
+		echo foo >Gitweb &&
+		git add Gitweb &&
+		git commit -m "add Gitweb" &&
+
+		git checkout --orphan todo &&
+		git reset --hard &&
+		# the subdir is crucial, without it there is no segfault
+		mkdir -p gitweb/subdir &&
+		echo bar >gitweb/subdir/file &&
+		# it is not strictly necessary to add and commit the
+		# gitweb directory, its presence is sufficient
+		git add gitweb &&
+		git commit -m "add gitweb/subdir/file" &&
+
+		git checkout master
+	)
+'
+
 test_done



The end of its trace:

++git checkout master
./test-lib.sh: line 910: 11220 Segmentation fault: 11  git checkout master
error: last command exited with $?=139

Case insensitivity is important because check_ok_to_remove() is
invoked from verify_absent_1(), which looks like this:

  if (...)
     ....
  else if (...)
     ....
  else if (lstat(ce->name, &st))
      // That lstat() checked whether 'Gitweb' is absent.  On a case
      // sensitive fs it's absent, so it returns.  On a case
      // insensitive fs it finds 'master's 'gitweb' directory, so it
      // goes on to the else below, and eventually segfaults.
      return;
  else
      check_ok_to_remove()


Good night :)

^ permalink raw reply related	[relevance 6%]

* Re: [PATCH] gitk: rename zh_CN.po to zh_cn.po
  2019-09-17  8:52  4% [PATCH] gitk: rename zh_CN.po to zh_cn.po Denton Liu
@ 2019-09-17 16:48  0% ` Junio C Hamano
  0 siblings, 0 replies; 200+ results
From: Junio C Hamano @ 2019-09-17 16:48 UTC (permalink / raw)
  To: Denton Liu; +Cc: Git Mailing List, Paul Mackerras

Denton Liu <liu.denton@gmail.com> writes:

> When running make from a clean environment, all of the *.po files should
> be converted into *.msg files. After that, when make is run without any
> changes, make should not do anything.
>
> After beffae768a (gitk: Add Chinese (zh_CN) translation, 2017-03-11),
> zh_CN.po was introduced. When make was run, a zh_cn.msg file was
> generated (notice the lowercase). However, since make is case-sensitive,
> it expects zh_CN.po to generate a zh_CN.msg file so make will keep
> reattempting to generate a zh_CN.msg so successive make invocations
> result in
>
>     Generating catalog po/zh_cn.msg
>     msgfmt --statistics --tcl po/zh_cn.po -l zh_cn -d po/
>     317 translated messages.
>
> happening continuously.
>
> Rename zh_CN.po to zh_cn.po so that when make generates the zh_cn.msg
> file, it will realize that it was successfully generated and only run
> once.

True, and it is not just that.  The install target would fail
because it cannot find zh_CN.msg, I think.

> Signed-off-by: Denton Liu <liu.denton@gmail.com>
> ---
>  po/{zh_CN.po => zh_cn.po} | 0
>  1 file changed, 0 insertions(+), 0 deletions(-)
>  rename po/{zh_CN.po => zh_cn.po} (100%)
>
> diff --git a/po/zh_CN.po b/po/zh_cn.po
> similarity index 100%
> rename from po/zh_CN.po
> rename to po/zh_cn.po

This would make this in line with pt_{pt,br}.po, existing locales
with country code in them.

Reviewed-by: Junio C Hamano <gitster@pobox.com>

^ permalink raw reply	[relevance 0%]

* [PATCH] gitk: rename zh_CN.po to zh_cn.po
@ 2019-09-17  8:52  4% Denton Liu
  2019-09-17 16:48  0% ` Junio C Hamano
  0 siblings, 1 reply; 200+ results
From: Denton Liu @ 2019-09-17  8:52 UTC (permalink / raw)
  To: Git Mailing List; +Cc: Paul Mackerras

When running make from a clean environment, all of the *.po files should
be converted into *.msg files. After that, when make is run without any
changes, make should not do anything.

After beffae768a (gitk: Add Chinese (zh_CN) translation, 2017-03-11),
zh_CN.po was introduced. When make was run, a zh_cn.msg file was
generated (notice the lowercase). However, since make is case-sensitive,
it expects zh_CN.po to generate a zh_CN.msg file so make will keep
reattempting to generate a zh_CN.msg so successive make invocations
result in

    Generating catalog po/zh_cn.msg
    msgfmt --statistics --tcl po/zh_cn.po -l zh_cn -d po/
    317 translated messages.

happening continuously.

Rename zh_CN.po to zh_cn.po so that when make generates the zh_cn.msg
file, it will realize that it was successfully generated and only run
once.

Signed-off-by: Denton Liu <liu.denton@gmail.com>
---
 po/{zh_CN.po => zh_cn.po} | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename po/{zh_CN.po => zh_cn.po} (100%)

diff --git a/po/zh_CN.po b/po/zh_cn.po
similarity index 100%
rename from po/zh_CN.po
rename to po/zh_cn.po
-- 
2.23.0.248.g3a9dd8fb08


^ permalink raw reply	[relevance 4%]

* Re: [PATCH v2 1/1] t0001: fix on case-insensitive filesystems
  2019-06-24 17:38  5%             ` Johannes Schindelin
@ 2019-06-24 19:22  0%               ` Junio C Hamano
  0 siblings, 0 replies; 200+ results
From: Junio C Hamano @ 2019-06-24 19:22 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Johannes Schindelin via GitGitGadget, git, Martin Ågren,
	brian m. carlson

Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:

> The other path category is the paths in the index, which _are_
> case-sensitive, no matter what core.ignoreCase says.
>
> So I'd rather keep the `fs`.

Sensible.  Thanks.

^ permalink raw reply	[relevance 0%]

* Re: [PATCH v2 1/1] t0001: fix on case-insensitive filesystems
  @ 2019-06-24 17:38  5%             ` Johannes Schindelin
  2019-06-24 19:22  0%               ` Junio C Hamano
  0 siblings, 1 reply; 200+ results
From: Johannes Schindelin @ 2019-06-24 17:38 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Johannes Schindelin via GitGitGadget, git, Martin Ågren,
	brian m. carlson

Hi Junio,

On Mon, 24 Jun 2019, Junio C Hamano wrote:

> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
>
> >> I wonder if we want (possibly local) test_cmp_fspath that can be
> >> used more like
> >>
> >> 	test_cmp_fspath "$(pwd)/realgitdir" "$(sed -e "s/^gitdir: //" newdir/.git)"
> >>
> >> to compare two paths, honoring the case sensitivity of the
> >> filesystem.
> >
> > I agree that that's a much better approach to fix the issue.
>
> I find that response somewhat surprising :-|.  In any case, I am not
> sure what kind of 'path' other than the filesystem one we would deal
> with in the context of Git and its test suite, so perhaps we should
> drop 'fs' from the name of the helper function if we were to go that
> route.

The other path category is the paths in the index, which _are_
case-sensitive, no matter what core.ignoreCase says.

So I'd rather keep the `fs`.

Ciao,
Dscho

^ permalink raw reply	[relevance 5%]

* Re: [RFC/PATCH 0/5] Fix fetch regression with transport helpers
  2019-06-05 12:22  0%     ` Duy Nguyen
@ 2019-06-06 13:07  0%       ` Johannes Schindelin
  0 siblings, 0 replies; 200+ results
From: Johannes Schindelin @ 2019-06-06 13:07 UTC (permalink / raw)
  To: Duy Nguyen; +Cc: Jeff King, Felipe Contreras, Git Mailing List, Junio C Hamano

Hi Duy,

On Wed, 5 Jun 2019, Duy Nguyen wrote:

> On Wed, Jun 5, 2019 at 6:27 PM Jeff King <peff@peff.net> wrote:
> >
> > On Wed, Jun 05, 2019 at 10:12:12AM +0200, Johannes Schindelin wrote:
> >
> > > This fails on macOS, in t5601, both in our osx-clang and osx-gcc jobs, as
> > > well as in the StaticAnalysis job. For details, see
> > > https://dev.azure.com/gitgitgadget/git/_build/results?buildId=10206
> >
> > Hmm. I'm having a hard time seeing why (and I can't seem to reproduce it
> > locally on a case-insensitive HFS+ filesystem under Linux).
> >
> > In particular, if the problem is here:
> >
> > > expecting success:
> > >       grep X icasefs/warning &&
> > >       grep x icasefs/warning &&
> > >       test_i18ngrep "the following paths have collided" icasefs/warning
> > >
> > > ++ grep X icasefs/warning
> > > error: last command exited with $?=1
> > > not ok 99 - colliding file detection
> >
> > then that implies it has to do with the checkout phase, which Felipe's
> > patch doesn't touch. Later in the log we see the actual file contents
> > (I'm confused as to how this gets here; it looks like debugging bits
> > that were added after the main script?):
> >
> >   2019-06-05T07:58:37.7961890Z Cloning into 'bogus'...
> >   2019-06-05T07:58:37.7962430Z done.
> >   2019-06-05T07:58:37.7963360Z warning: the following paths have collided (e.g. case-sensitive paths
> >   2019-06-05T07:58:37.7964300Z on a case-insensitive filesystem) and only one from the same
> >   2019-06-05T07:58:37.7964880Z colliding group is in the working tree:
> >   2019-06-05T07:58:37.7965290Z
> >   2019-06-05T07:58:37.7966250Z   'x'
> >
> > whereas a succeeding test expects us to mention both 'x' and 'X'.
> >
> > So we _did_ find the collision, but somehow 'X' was not reported.
> > Looking at the code, I'm not even sure how that could happen. Given that
> > this process does involve looking at stat data, it makes me wonder if
>
> It does use stat data in mark_colliding_entries() if core.checkStat is
> false. I think on MacOS it's actually true.
>
> I vaguely recall seeing just one 'x' once. I think last time I had a
> problem with truncating st_ino, but that should be fixed in e66ceca94b
> (clone: fix colliding file detection on APFS, 2018-11-20). So no idea
> how this happens again.

Good catch. I think the reason it happens again is simply that Junio
picked a base commit that is older than the commit you referenced.

Point in favor: Junio merged these here patches into `pu` and those
test failures (as well as the StaticAnalysis issues) are gone.

Thanks,
Johannes

>
> > there could be some raciness involved. But again, I'm scratching my head
> > as to how exactly, and I couldn't reproduce it under load or with some
> > carefully inserted sleep() calls.
> --
> Duy
>

^ permalink raw reply	[relevance 0%]

* Re: [RFC/PATCH 0/5] Fix fetch regression with transport helpers
  2019-06-05 11:27  4%   ` Jeff King
@ 2019-06-05 12:22  0%     ` Duy Nguyen
  2019-06-06 13:07  0%       ` Johannes Schindelin
  0 siblings, 1 reply; 200+ results
From: Duy Nguyen @ 2019-06-05 12:22 UTC (permalink / raw)
  To: Jeff King
  Cc: Johannes Schindelin, Felipe Contreras, Git Mailing List,
	Junio C Hamano

On Wed, Jun 5, 2019 at 6:27 PM Jeff King <peff@peff.net> wrote:
>
> On Wed, Jun 05, 2019 at 10:12:12AM +0200, Johannes Schindelin wrote:
>
> > This fails on macOS, in t5601, both in our osx-clang and osx-gcc jobs, as
> > well as in the StaticAnalysis job. For details, see
> > https://dev.azure.com/gitgitgadget/git/_build/results?buildId=10206
>
> Hmm. I'm having a hard time seeing why (and I can't seem to reproduce it
> locally on a case-insensitive HFS+ filesystem under Linux).
>
> In particular, if the problem is here:
>
> > expecting success:
> >       grep X icasefs/warning &&
> >       grep x icasefs/warning &&
> >       test_i18ngrep "the following paths have collided" icasefs/warning
> >
> > ++ grep X icasefs/warning
> > error: last command exited with $?=1
> > not ok 99 - colliding file detection
>
> then that implies it has to do with the checkout phase, which Felipe's
> patch doesn't touch. Later in the log we see the actual file contents
> (I'm confused as to how this gets here; it looks like debugging bits
> that were added after the main script?):
>
>   2019-06-05T07:58:37.7961890Z Cloning into 'bogus'...
>   2019-06-05T07:58:37.7962430Z done.
>   2019-06-05T07:58:37.7963360Z warning: the following paths have collided (e.g. case-sensitive paths
>   2019-06-05T07:58:37.7964300Z on a case-insensitive filesystem) and only one from the same
>   2019-06-05T07:58:37.7964880Z colliding group is in the working tree:
>   2019-06-05T07:58:37.7965290Z
>   2019-06-05T07:58:37.7966250Z   'x'
>
> whereas a succeeding test expects us to mention both 'x' and 'X'.
>
> So we _did_ find the collision, but somehow 'X' was not reported.
> Looking at the code, I'm not even sure how that could happen. Given that
> this process does involve looking at stat data, it makes me wonder if

It does use stat data in mark_colliding_entries() if core.checkStat is
false. I think on MacOS it's actually true.

I vaguely recall seeing just one 'x' once. I think last time I had a
problem with truncating st_ino, but that should be fixed in e66ceca94b
(clone: fix colliding file detection on APFS, 2018-11-20). So no idea
how this happens again.

> there could be some raciness involved. But again, I'm scratching my head
> as to how exactly, and I couldn't reproduce it under load or with some
> carefully inserted sleep() calls.
-- 
Duy

^ permalink raw reply	[relevance 0%]

* Re: [RFC/PATCH 0/5] Fix fetch regression with transport helpers
  2019-06-05  8:12  4% ` Johannes Schindelin
@ 2019-06-05 11:27  4%   ` Jeff King
  2019-06-05 12:22  0%     ` Duy Nguyen
  0 siblings, 1 reply; 200+ results
From: Jeff King @ 2019-06-05 11:27 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Nguyễn Thái Ngọc Duy, Felipe Contreras, git,
	Junio C Hamano

On Wed, Jun 05, 2019 at 10:12:12AM +0200, Johannes Schindelin wrote:

> This fails on macOS, in t5601, both in our osx-clang and osx-gcc jobs, as
> well as in the StaticAnalysis job. For details, see
> https://dev.azure.com/gitgitgadget/git/_build/results?buildId=10206

Hmm. I'm having a hard time seeing why (and I can't seem to reproduce it
locally on a case-insensitive HFS+ filesystem under Linux).

In particular, if the problem is here:

> expecting success:
> 	grep X icasefs/warning &&
> 	grep x icasefs/warning &&
> 	test_i18ngrep "the following paths have collided" icasefs/warning
> 
> ++ grep X icasefs/warning
> error: last command exited with $?=1
> not ok 99 - colliding file detection

then that implies it has to do with the checkout phase, which Felipe's
patch doesn't touch. Later in the log we see the actual file contents
(I'm confused as to how this gets here; it looks like debugging bits
that were added after the main script?):

  2019-06-05T07:58:37.7961890Z Cloning into 'bogus'...
  2019-06-05T07:58:37.7962430Z done.
  2019-06-05T07:58:37.7963360Z warning: the following paths have collided (e.g. case-sensitive paths
  2019-06-05T07:58:37.7964300Z on a case-insensitive filesystem) and only one from the same
  2019-06-05T07:58:37.7964880Z colliding group is in the working tree:
  2019-06-05T07:58:37.7965290Z 
  2019-06-05T07:58:37.7966250Z   'x'

whereas a succeeding test expects us to mention both 'x' and 'X'.

So we _did_ find the collision, but somehow 'X' was not reported.
Looking at the code, I'm not even sure how that could happen. Given that
this process does involve looking at stat data, it makes me wonder if
there could be some raciness involved. But again, I'm scratching my head
as to how exactly, and I couldn't reproduce it under load or with some
carefully inserted sleep() calls.

And it looks like it did reproduce twice on Azure.

Can somebody who has osx locally reproduce this? Or is there a way to
interactively access the Azure environment to dig further?

> My guess is that your changes remove something that was expected before,
> and is still expected, and that this was only tested on Linux, and only on
> a file system with case-sensitive file names.

It sounds like you're suggesting that changes to the test script subtly
affected the later state. Which is indeed a common culprit. But the
changes in Felipe's series were all to t5801, not the failing t5601. Am
I misunderstanding what you mean?

-Peff

^ permalink raw reply	[relevance 4%]

* Re: [RFC/PATCH 0/5] Fix fetch regression with transport helpers
  @ 2019-06-05  8:12  4% ` Johannes Schindelin
  2019-06-05 11:27  4%   ` Jeff King
  0 siblings, 1 reply; 200+ results
From: Johannes Schindelin @ 2019-06-05  8:12 UTC (permalink / raw)
  To: Felipe Contreras; +Cc: git, Junio C Hamano, Jeff King

On Mon, 3 Jun 2019, Felipe Contreras wrote:

> Felipe Contreras (5):
>   t5801 (remote-helpers): cleanup refspec stuff
>   t5801 (remote-helpers): add test to fetch tags
>   fetch: trivial cleanup
>   fetch: make the code more understandable
>   fetch: fix regression with transport helpers
>
>  builtin/fetch.c            | 28 ++++++++++++++++++----------
>  t/t5801-remote-helpers.sh  | 18 ++++++++++++++----
>  t/t5801/git-remote-testgit | 22 +++++++++++++---------
>  3 files changed, 45 insertions(+), 23 deletions(-)

This fails on macOS, in t5601, both in our osx-clang and osx-gcc jobs, as
well as in the StaticAnalysis job. For details, see
https://dev.azure.com/gitgitgadget/git/_build/results?buildId=10206

The t5601 failure looks like this:

-- snip --
checking prerequisite: CASE_INSENSITIVE_FS

mkdir -p "$TRASH_DIRECTORY/prereq-test-dir" &&
(
	cd "$TRASH_DIRECTORY/prereq-test-dir" &&
	echo good >CamelCase &&
	echo bad >camelcase &&
	test "$(cat CamelCase)" != good

)
++ mkdir -p '/Users/vsts/agent/2.150.3/work/1/s/t/trash
directory.t5601-clone/prereq-test-dir'
++ cd '/Users/vsts/agent/2.150.3/work/1/s/t/trash
directory.t5601-clone/prereq-test-dir'
++ echo good
++ echo bad
+++ cat CamelCase
++ test bad '!=' good
prerequisite CASE_INSENSITIVE_FS ok
expecting success:
	grep X icasefs/warning &&
	grep x icasefs/warning &&
	test_i18ngrep "the following paths have collided" icasefs/warning

++ grep X icasefs/warning
error: last command exited with $?=1
not ok 99 - colliding file detection
-- snap --

My guess is that your changes remove something that was expected before,
and is still expected, and that this was only tested on Linux, and only on
a file system with case-sensitive file names.

The StaticAnalysis job (which is admittedly a misnomer) points out another
few valid issues, but that is probably because Junio applied this patch
series on top of a very old commit. I, at least, could not spot any file
in the Coccinelle report that was touched by this here patch series.

Ciao,
Johannes

^ permalink raw reply	[relevance 4%]

* Re: [PATCH 1/3] transport_anonymize_url(): support retaining username
  @ 2019-05-20 16:36  5%   ` Johannes Schindelin
  0 siblings, 0 replies; 200+ results
From: Johannes Schindelin @ 2019-05-20 16:36 UTC (permalink / raw)
  To: Jeff King
  Cc: Ævar Arnfjörð Bjarmason, Martin Langhoff,
	Git Mailing List

Hi Peff,

On Sun, 19 May 2019, Jeff King wrote:

> When we anonymize URLs to show in messages, we strip out both the
> username and password (if any). But there are also contexts where we
> should strip out the password (to avoid leaking it) but retain the
> username.
>
> Let's generalize transport_anonymize_url() to support both cases. We'll
> give it a new name since the password-only mode isn't really
> "anonymizing", but keep the old name as a synonym to avoid disrupting
> existing callers.
>
> Note that there are actually three places we parse URLs, and this
> functionality _could_ go into any of them:
>
>   - transport_anonymize_url(), which we modify here
>
>   - the urlmatch.c code parses a URL into its constituent parts, from
>     which we could easily remove the elements we want to drop and
>     re-format it as a single URL. But its parsing also normalizes
>     elements (e.g., downcasing hostnames).  This isn't wrong, but it's
>     more friendly if we can leave the rest of the URL untouched.

I have not looked into it at all, but I seem to vaguely remember that the
result of this code might be used to look up `url.<url>.insteadOf`
settings, where the middle part *is* case-sensitive.

>   - credential_form_url() parses a URL and decodes the specific
>     elements, but it's hard to convert it back into a regular URL. It
>     treats "host:port" as a single unit, meaning it needs to be
>     re-encoded specially (since a colon would otherwise end
>     percent-encoded).
>
> Since transport_anonymize_url() seemed closest to what we want here, I
> used that as the base.
>
> Signed-off-by: Jeff King <peff@peff.net>
> ---
> I think it would be beneficial to unify these three cases under a single
> parser, but it seemed like too big a rabbit hole for this topic. Of the
> three, the urlmatch one seems the most mature. I think if we could
> simply separate the normalization from the parsing/decoding, the others
> could build on top of it. It might also require some careful thinking
> about how pseudo-urls like ssh "host:path" interact.

In light of what I mentioned above, I am not sure that we should go there
in the first place...

Thanks,
Dscho

> I won't call that a #leftoverbits, because it's more of a feast. :)
>
>  transport.c | 21 ++++++++++++++-------
>  transport.h | 11 ++++++++++-
>  2 files changed, 24 insertions(+), 8 deletions(-)
>
> diff --git a/transport.c b/transport.c
> index f1fcd2c4b0..ba61e57295 100644
> --- a/transport.c
> +++ b/transport.c
> @@ -1335,11 +1335,7 @@ int transport_disconnect(struct transport *transport)
>  	return ret;
>  }
>
> -/*
> - * Strip username (and password) from a URL and return
> - * it in a newly allocated string.
> - */
> -char *transport_anonymize_url(const char *url)
> +char *transport_strip_url(const char *url, int strip_user)
>  {
>  	char *scheme_prefix, *anon_part;
>  	size_t anon_len, prefix_len = 0;
> @@ -1348,7 +1344,10 @@ char *transport_anonymize_url(const char *url)
>  	if (url_is_local_not_ssh(url) || !anon_part)
>  		goto literal_copy;
>
> -	anon_len = strlen(++anon_part);
> +	anon_len = strlen(anon_part);
> +	if (strip_user)
> +		anon_part++;
> +
>  	scheme_prefix = strstr(url, "://");
>  	if (!scheme_prefix) {
>  		if (!strchr(anon_part, ':'))
> @@ -1373,7 +1372,15 @@ char *transport_anonymize_url(const char *url)
>  		cp = strchr(scheme_prefix + 3, '/');
>  		if (cp && cp < anon_part)
>  			goto literal_copy;
> -		prefix_len = scheme_prefix - url + 3;
> +
> +		if (strip_user)
> +			prefix_len = scheme_prefix - url + 3;
> +		else {
> +			cp = strchr(scheme_prefix + 3, ':');
> +			if (cp && cp > anon_part)
> +				goto literal_copy; /* username only */
> +			prefix_len = cp - url;
> +		}
>  	}
>  	return xstrfmt("%.*s%.*s", (int)prefix_len, url,
>  		       (int)anon_len, anon_part);
> diff --git a/transport.h b/transport.h
> index 06e06d3d89..6d8c99ac91 100644
> --- a/transport.h
> +++ b/transport.h
> @@ -243,10 +243,19 @@ const struct ref *transport_get_remote_refs(struct transport *transport,
>  int transport_fetch_refs(struct transport *transport, struct ref *refs);
>  void transport_unlock_pack(struct transport *transport);
>  int transport_disconnect(struct transport *transport);
> -char *transport_anonymize_url(const char *url);
>  void transport_take_over(struct transport *transport,
>  			 struct child_process *child);
>
> +/*
> + * Strip password and optionally username from a URL and return
> + * it in a newly allocated string (even if nothing was stripped).
> + */
> +char *transport_strip_url(const char *url, int strip_username);
> +static inline char *transport_anonymize_url(const char *url)
> +{
> +	return transport_strip_url(url, 1);
> +}
> +
>  int transport_connect(struct transport *transport, const char *name,
>  		      const char *exec, int fd[2]);
>
> --
> 2.22.0.rc0.583.g23d90da2b3
>
>

^ permalink raw reply	[relevance 5%]

* [PATCH v3 3/8] git-p4: match branches case insensitively if configured
  @ 2019-04-01 18:02  5%   ` Mazo, Andrey
  0 siblings, 0 replies; 200+ results
From: Mazo, Andrey @ 2019-04-01 18:02 UTC (permalink / raw)
  To: git@vger.kernel.org
  Cc: Mazo, Andrey, Luke Diamand, Eric Sunshine, George Vanburgh,
	Lars Schneider, Miguel Torroja, Romain Merland, Vitor Antunes,
	Andrew Oakley, SZEDER Gábor, Andrey Mazo, Junio C Hamano

git-p4 knows how to handle case insensitivity in file paths
if core.ignorecase is set.
However, when determining a branch for a file,
it still does a case-sensitive prefix match.
This may result in some file changes to be lost on import.

For example, given the following commits
 1. add //depot/main/file1
 2. add //depot/DirA/file2
 3. add //depot/dira/file3
 4. add //depot/DirA/file4
and "branchList = main:DirA" branch mapping,
commit 3 will be lost.

So, do branch search case insensitively if running with core.ignorecase set.
Teach splitFilesIntoBranches() to use the p4PathStartsWith() function
for path prefix matches instead of always case-sensitive match.

Signed-off-by: Andrey Mazo <amazo@checkvideo.com>
---
 git-p4.py                | 4 ++--
 t/t9801-git-p4-branch.sh | 4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/git-p4.py b/git-p4.py
index c0a3068b6f..f3e5ccb7af 100755
--- a/git-p4.py
+++ b/git-p4.py
@@ -2666,11 +2666,11 @@ def stripRepoPath(self, path, prefixes):
             # branch detection moves files up a level (the branch name)
             # from what client spec interpretation gives
             path = self.clientSpecDirs.map_in_client(path)
             if self.detectBranches:
                 for b in self.knownBranches:
-                    if path.startswith(b + "/"):
+                    if p4PathStartsWith(path, b + "/"):
                         path = path[len(b)+1:]
 
         elif self.keepRepoPath:
             # Preserve everything in relative path name except leading
             # //depot/; just look at first prefix as they all should
@@ -2721,11 +2721,11 @@ def splitFilesIntoBranches(self, commit):
                 relPath = self.stripRepoPath(path, self.depotPaths)
 
             for branch in self.knownBranches.keys():
                 # add a trailing slash so that a commit into qt/4.2foo
                 # doesn't end up in qt/4.2, e.g.
-                if relPath.startswith(branch + "/"):
+                if p4PathStartsWith(relPath, branch + "/"):
                     if branch not in branches:
                         branches[branch] = []
                     branches[branch].append(file)
                     break
 
diff --git a/t/t9801-git-p4-branch.sh b/t/t9801-git-p4-branch.sh
index c48532e12b..4779448b4c 100755
--- a/t/t9801-git-p4-branch.sh
+++ b/t/t9801-git-p4-branch.sh
@@ -648,11 +648,11 @@ test_expect_success !CASE_INSENSITIVE_FS 'basic p4 branches for case folding' '
 		p4 submit -d "branch1/b1f4"
 	)
 '
 
 # Check that files are properly split across branches when ignorecase is set
-test_expect_failure !CASE_INSENSITIVE_FS 'git p4 clone, branchList branch definition, ignorecase' '
+test_expect_success !CASE_INSENSITIVE_FS 'git p4 clone, branchList branch definition, ignorecase' '
 	test_when_finished cleanup_git &&
 	test_create_repo "$git" &&
 	(
 		cd "$git" &&
 		git config git-p4.branchList main:branch1 &&
@@ -674,11 +674,11 @@ test_expect_failure !CASE_INSENSITIVE_FS 'git p4 clone, branchList branch defini
 		test_path_is_file b1f4
 	)
 '
 
 # Check that files are properly split across branches when ignorecase is set, use-client-spec case
-test_expect_failure !CASE_INSENSITIVE_FS 'git p4 clone with client-spec, branchList branch definition, ignorecase' '
+test_expect_success !CASE_INSENSITIVE_FS 'git p4 clone with client-spec, branchList branch definition, ignorecase' '
 	client_view "//depot/... //client/..." &&
 	test_when_finished cleanup_git &&
 	test_create_repo "$git" &&
 	(
 		cd "$git" &&
-- 
2.19.2


^ permalink raw reply related	[relevance 5%]

* Re: [PATCH v2 2/7] git-p4: match branches case insensitively if configured
  2019-03-23  9:15  0%   ` Luke Diamand
@ 2019-03-25 17:20  0%     ` Mazo, Andrey
  0 siblings, 0 replies; 200+ results
From: Mazo, Andrey @ 2019-03-25 17:20 UTC (permalink / raw)
  To: luke@diamand.org
  Cc: ahippo@yandex.com, Mazo, Andrey, aoakley@roku.com,
	git@vger.kernel.org, gitster@pobox.com, gvanburgh@bloomberg.net,
	larsxschneider@gmail.com, merlorom@yahoo.fr,
	miguel.torroja@gmail.com, sunshine@sunshineco.com,
	szeder.dev@gmail.com, vitor.hda@gmail.com

From: "Mazo, Andrey" <amazo@checkvideo.com>


23.03.2019, 05:16, "Luke Diamand" <luke@diamand.org>:
> On Thu, 21 Mar 2019 at 22:32, Mazo, Andrey <amazo@checkvideo.com> wrote:
>>  git-p4 knows how to handle case insensitivity in file paths
>>  if core.ignorecase is set.
>>  However, when determining a branch for a file,
>>  it still does a case-sensitive prefix match.
>>  This may result in some file changes to be lost on import.
>>
>>  For example, given the following commits
>>   1. add //depot/main/file1
>>   2. add //depot/DirA/file2
>>   3. add //depot/dira/file3
>>   4. add //depot/DirA/file4
>>  and "branchList = main:DirA" branch mapping,
>>  commit 3 will be lost.
>>
>>  So, do branch search case insensitively if running with core.ignorecase set.
>>  Teach splitFilesIntoBranches() to use the p4PathStartsWith() function
>>  for path prefix matches instead of always case-sensitive match.
>
> I wonder what other code paths break due to this problem!
>
> Looks reasonable but I fear there may be some other holes in there -
> quickly looking through the code suggests there are several other
> places this problem occurs.

From a quick search for .startswith(), I only see that stripRepoPath() might have a similar problem in useclientspec case.
If you see other apparent problematic places, could you, please, point them out?

Or let me try to come up with a test case, and see what other places break.

Thank you,
Andrey.

^ permalink raw reply	[relevance 0%]

* Re: [PATCH v2 2/7] git-p4: match branches case insensitively if configured
  2019-03-21 22:32  6% ` [PATCH v2 2/7] git-p4: match branches case insensitively if configured Mazo, Andrey
@ 2019-03-23  9:15  0%   ` Luke Diamand
  2019-03-25 17:20  0%     ` Mazo, Andrey
  0 siblings, 1 reply; 200+ results
From: Luke Diamand @ 2019-03-23  9:15 UTC (permalink / raw)
  To: Mazo, Andrey
  Cc: git@vger.kernel.org, sunshine@sunshineco.com,
	gvanburgh@bloomberg.net, larsxschneider@gmail.com,
	miguel.torroja@gmail.com, merlorom@yahoo.fr, vitor.hda@gmail.com,
	aoakley@roku.com, szeder.dev@gmail.com, ahippo@yandex.com,
	gitster@pobox.com

On Thu, 21 Mar 2019 at 22:32, Mazo, Andrey <amazo@checkvideo.com> wrote:
>
> git-p4 knows how to handle case insensitivity in file paths
> if core.ignorecase is set.
> However, when determining a branch for a file,
> it still does a case-sensitive prefix match.
> This may result in some file changes to be lost on import.
>
> For example, given the following commits
>  1. add //depot/main/file1
>  2. add //depot/DirA/file2
>  3. add //depot/dira/file3
>  4. add //depot/DirA/file4
> and "branchList = main:DirA" branch mapping,
> commit 3 will be lost.
>
> So, do branch search case insensitively if running with core.ignorecase set.
> Teach splitFilesIntoBranches() to use the p4PathStartsWith() function
> for path prefix matches instead of always case-sensitive match.

I wonder what other code paths break due to this problem!

Looks reasonable but I fear there may be some other holes in there -
quickly looking through the code suggests there are several other
places this problem occurs.

Luke

>
> Signed-off-by: Andrey Mazo <amazo@checkvideo.com>
> ---
>  git-p4.py | 2 +-
>  1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/git-p4.py b/git-p4.py
> index c0a3068b6f..91c610f960 100755
> --- a/git-p4.py
> +++ b/git-p4.py
> @@ -2721,11 +2721,11 @@ def splitFilesIntoBranches(self, commit):
>                  relPath = self.stripRepoPath(path, self.depotPaths)
>
>              for branch in self.knownBranches.keys():
>                  # add a trailing slash so that a commit into qt/4.2foo
>                  # doesn't end up in qt/4.2, e.g.
> -                if relPath.startswith(branch + "/"):
> +                if p4PathStartsWith(relPath, branch + "/"):
>                      if branch not in branches:
>                          branches[branch] = []
>                      branches[branch].append(file)
>                      break
>
> --
> 2.19.2
>

^ permalink raw reply	[relevance 0%]

* [PATCH v2 2/7] git-p4: match branches case insensitively if configured
  @ 2019-03-21 22:32  6% ` Mazo, Andrey
  2019-03-23  9:15  0%   ` Luke Diamand
    1 sibling, 1 reply; 200+ results
From: Mazo, Andrey @ 2019-03-21 22:32 UTC (permalink / raw)
  To: git@vger.kernel.org
  Cc: Mazo, Andrey, luke@diamand.org, sunshine@sunshineco.com,
	gvanburgh@bloomberg.net, larsxschneider@gmail.com,
	miguel.torroja@gmail.com, merlorom@yahoo.fr, vitor.hda@gmail.com,
	aoakley@roku.com, szeder.dev@gmail.com, ahippo@yandex.com,
	gitster@pobox.com

git-p4 knows how to handle case insensitivity in file paths
if core.ignorecase is set.
However, when determining a branch for a file,
it still does a case-sensitive prefix match.
This may result in some file changes to be lost on import.

For example, given the following commits
 1. add //depot/main/file1
 2. add //depot/DirA/file2
 3. add //depot/dira/file3
 4. add //depot/DirA/file4
and "branchList = main:DirA" branch mapping,
commit 3 will be lost.

So, do branch search case insensitively if running with core.ignorecase set.
Teach splitFilesIntoBranches() to use the p4PathStartsWith() function
for path prefix matches instead of always case-sensitive match.

Signed-off-by: Andrey Mazo <amazo@checkvideo.com>
---
 git-p4.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/git-p4.py b/git-p4.py
index c0a3068b6f..91c610f960 100755
--- a/git-p4.py
+++ b/git-p4.py
@@ -2721,11 +2721,11 @@ def splitFilesIntoBranches(self, commit):
                 relPath = self.stripRepoPath(path, self.depotPaths)
 
             for branch in self.knownBranches.keys():
                 # add a trailing slash so that a commit into qt/4.2foo
                 # doesn't end up in qt/4.2, e.g.
-                if relPath.startswith(branch + "/"):
+                if p4PathStartsWith(relPath, branch + "/"):
                     if branch not in branches:
                         branches[branch] = []
                     branches[branch].append(file)
                     break
 
-- 
2.19.2


^ permalink raw reply related	[relevance 6%]

* Re: [PATCH 0/1] de-alphabetize command list
  2019-03-11  9:04  0%     ` frederik
@ 2019-03-11 14:38  0%       ` Jacob Keller
  0 siblings, 0 replies; 200+ results
From: Jacob Keller @ 2019-03-11 14:38 UTC (permalink / raw)
  To: frederik
  Cc: Git mailing list, Junio C Hamano, Eric Sunshine, Jonathan Nieder,
	Theodore Y. Ts'o

On Mon, Mar 11, 2019 at 2:09 AM <frederik@ofb.net> wrote:
>
> Hey Git people,
>
> I didn't get a reply and I'm not sure what the appropriate ping
> interval is, or when I should conclude that no one is interested.
>
> There seemed to be some vaguely positive feedback before I embarked on
> this project. At the same time I don't want to pester anyone into
> applying patches in a disorganized fashion.
>
> I'm not subscribed to the mailing list so I apologize if I'm out of
> tune with a release cycle or current development thrust.
>
> Thanks,
>
> Frederick
>

FWIW, I felt like the changes in your proposed patch were good. I
haven't had more time to dig into reviewing it much though :(

-Jake

> On Thu, Feb 21, 2019 at 10:05:22AM -0800, frederik@ofb.net wrote:
> >I realized that it would probably be easier to discuss this proposal
> >if I attached the final command listing and the rendered manual page.
> >Please find them attached to this message.
> >
> >Thank you,
> >
> >Frederick
> >
> >On Tue, Feb 19, 2019 at 09:54:12AM -0800, Frederick Eaton wrote:
> >>This is a follow-up to my proposal to de-alphabetize the command
> >>listings in the git(1) manual page, from 6 July 2018.
> >>
> >>Some projects have manual page items listed in alphabetical order,
> >>some don't. As I argued in my proposal, I find it easier to learn from
> >>material which is not alphabetized. If this patch is accepted, I hope
> >>that it will make the Git documentation more accessible to myself and
> >>others.
> >>
> >>I produced the reordered command list in this patch using several
> >>sources, as indicated by comments in the new command-list.txt file.
> >>First, all the commands in the main part of "gittutorial(7)" appear in
> >>order, then the commands in giteveryday(7). Then appear additional
> >>commands from a friend's shell history, in reverse order of frequency.
> >>Then gittutorial-2(7), then gitcore-tutorial(7). After that there is a
> >>list of "guides", followed by about 100 commands not appearing in the
> >>earlier lists. I kept the guides and the remaining commands in their
> >>category groupings (guide, mainporcelain, ancillarymanipulators,
> >>etc.), but ordered the commands within each category according to my
> >>own judgment after skimming each manual page.
> >>
> >>To verify that the new list is a permutation of the most recent list,
> >>I use the following command (it should produce no output and exit 0):
> >>
> >>   diff <(git show master:command-list.txt | grep -v '^#' | sort ) <(cat command-list.txt | grep -v '^#' | sort)
> >>
> >>Note this patch changes the order of commands appearing in the
> >>generated file "command-list.h", which mostly seems to be used by
> >>"help.c". Probably due to the various occurrences of QSORT in
> >>"help.c", I think this reordering has no visible effect. I am willing
> >>to do any additional testing which may be recommended to ensure that
> >>this patch has no undesired consequences.
> >>
> >>Frederick Eaton (1):
> >> Prioritize list of commands appearing in git(1), via command-list.txt.
> >>   Don't invoke 'sort' in Documentation/cmd-list.perl.
> >>
> >>Documentation/cmd-list.perl |   2 +-
> >>command-list.txt            | 295 +++++++++++++++++++-----------------
> >>2 files changed, 158 insertions(+), 139 deletions(-)
> >>
> >>--
> >>2.20.1
> >>
>
> ># Command classification list
> ># ---------------------------
> ># All supported commands, builtin or external, must be described in
> ># here. This info is used to list commands in various places. Each
> ># command is on one line followed by one or more attributes.
> >#
> ># The first attribute group is mandatory and indicates the command
> ># type. This group includes:
> >#
> >#   mainporcelain
> >#   ancillarymanipulators
> >#   ancillaryinterrogators
> >#   foreignscminterface
> >#   plumbingmanipulators
> >#   plumbinginterrogators
> >#   synchingrepositories
> >#   synchelpers
> >#   purehelpers
> >#
> ># The type names are self explanatory. But if you want to see what
> ># command belongs to what group to get a better picture, have a look
> ># at "git" man page, "GIT COMMANDS" section.
> >#
> ># Commands of type mainporcelain can also optionally have one of these
> ># attributes:
> >#
> >#   init
> >#   worktree
> >#   info
> >#   history
> >#   remote
> >#
> ># These commands are considered "common" and will show up in "git
> ># help" output in groups. Uncommon porcelain commands must not
> ># specify any of these attributes.
> >#
> ># "complete" attribute is used to mark that the command should be
> ># completable by git-completion.bash. Note that by default,
> ># mainporcelain commands are completable so you don't need this
> ># attribute.
> >#
> ># As part of the Git man page list, the man(5/7) guides are also
> ># specified here, which can only have "guide" attribute and nothing
> ># else.
> >#
> ># February 2019: This list had been sorted alphabetically but has been
> ># reordered to make it easier for people to learn from the main git(1)
> ># manual page. The new ordering is according to approximate usefulness
> ># / frequency of use / order of use, with some grouping by topic. The
> ># idea is to make it possible to read the manual page from beginning
> ># to end and see the most important commands first, rather than
> ># getting them in alphabetical order - in other words, to make the
> ># manual page more like a table of contents and less like an index.
> ># Please consider this when adding new commands.
> >#
> >### command list (do not change this line, also do not change alignment)
> ># command name                          category [category] [category]
> ># From gittutorial
> >git-help                                ancillaryinterrogators          complete
> >git-config                              ancillarymanipulators           complete
> >git-clone                               mainporcelain           init
> >git-init                                mainporcelain           init
> >git-add                                 mainporcelain           worktree
> >git-commit                              mainporcelain           history
> >git-diff                                mainporcelain           history
> >git-status                              mainporcelain           info
> >git-log                                 mainporcelain           info
> >git-branch                              mainporcelain           history
> >git-checkout                            mainporcelain           history
> >git-merge                               mainporcelain           history
> >gitk                                    mainporcelain
> >git-pull                                mainporcelain           remote
> >git-fetch                               mainporcelain           remote
> ># From tutorial NEXT STEPS
> >git-format-patch                        mainporcelain
> >git-bisect                              mainporcelain           info
> >giteveryday                             guide
> >gitworkflows                            guide
> >gitcvs-migration                        guide
> ># From giteveryday
> >git-reset                               mainporcelain           worktree
> >git-rebase                              mainporcelain           history
> >git-tag                                 mainporcelain           history
> >git-push                                mainporcelain           remote
> >git-send-email                          foreignscminterface             complete
> >git-request-pull                        foreignscminterface             complete
> >git-am                                  mainporcelain
> >git-revert                              mainporcelain
> >git-daemon                              synchingrepositories
> >git-shell                               synchelpers
> >git-http-backend                        synchingrepositories
> >gitweb                                  ancillaryinterrogators
> ># From user feedback
> >git-grep                                mainporcelain           info
> >git-show                                mainporcelain           info
> >git-submodule                           mainporcelain
> >git-cherry-pick                         mainporcelain
> >git-clean                               mainporcelain
> ># From gittutorial-2
> >git-cat-file                            plumbinginterrogators
> >git-ls-tree                             plumbinginterrogators
> >git-ls-files                            plumbinginterrogators
> >gitcore-tutorial                        guide
> >gitglossary                             guide
> ># From gitcore-tutorial
> >git-update-index                        plumbingmanipulators
> >git-diff-files                          plumbinginterrogators
> >git-write-tree                          plumbingmanipulators
> >git-read-tree                           plumbingmanipulators
> >git-checkout-index                      plumbingmanipulators
> >git-show-branch                         ancillaryinterrogators          complete
> >git-name-rev                            plumbinginterrogators
> >git-merge-index                         plumbingmanipulators
> >git-repack                              ancillarymanipulators           complete
> >git-prune-packed                        plumbingmanipulators
> >git-update-server-info                  synchingrepositories
> >git-prune                               ancillarymanipulators
> >git-cherry                              plumbinginterrogators          complete
> ># Guides, reordered
> >gittutorial                             guide
> >gittutorial-2                           guide
> >gitrevisions                            guide
> >gitignore                               guide
> >gitcli                                  guide
> >gitrepository-layout                    guide
> >gitdiffcore                             guide
> >gitmodules                              guide
> >githooks                                guide
> >gitnamespaces                           guide
> >gitattributes                           guide
> ># All other commands, sorted by man page category and then by
> ># approximate priority
> >git-stash                               mainporcelain
> >git-rm                                  mainporcelain           worktree
> >git-mv                                  mainporcelain           worktree
> >git-gui                                 mainporcelain
> >git-citool                              mainporcelain
> >git-archive                             mainporcelain
> >git-shortlog                            mainporcelain
> >git-describe                            mainporcelain
> >git-gc                                  mainporcelain
> >git-notes                               mainporcelain
> >git-worktree                            mainporcelain
> >git-bundle                              mainporcelain
> >git-range-diff                          mainporcelain
> >git-stage                                                               complete
> >git-reflog                              ancillarymanipulators           complete
> >git-remote                              ancillarymanipulators           complete
> >git-mergetool                           ancillarymanipulators           complete
> >git-filter-branch                       ancillarymanipulators
> >git-replace                             ancillarymanipulators           complete
> >git-fast-export                         ancillarymanipulators
> >git-fast-import                         ancillarymanipulators
> >git-pack-refs                           ancillarymanipulators
> >git-cvsimport                           foreignscminterface
> >git-cvsserver                           foreignscminterface
> >git-cvsexportcommit                     foreignscminterface
> >git-svn                                 foreignscminterface
> >git-p4                                  foreignscminterface
> >git-quiltimport                         foreignscminterface
> >git-archimport                          foreignscminterface
> >git-imap-send                           foreignscminterface
> >git-apply                               plumbingmanipulators            complete
> >git-merge-file                          plumbingmanipulators
> >git-mktag                               plumbingmanipulators
> >git-hash-object                         plumbingmanipulators
> >git-update-ref                          plumbingmanipulators
> >git-symbolic-ref                        plumbingmanipulators
> >git-commit-tree                         plumbingmanipulators
> >git-commit-graph                        plumbingmanipulators
> >git-mktree                              plumbingmanipulators
> >git-pack-objects                        plumbingmanipulators
> >git-unpack-objects                      plumbingmanipulators
> >git-index-pack                          plumbingmanipulators
> >git-multi-pack-index                    plumbingmanipulators
> >git-blame                               ancillaryinterrogators          complete
> >git-annotate                            ancillaryinterrogators
> >git-instaweb                            ancillaryinterrogators          complete
> >git-rerere                              ancillaryinterrogators
> >git-fsck                                ancillaryinterrogators          complete
> >git-whatchanged                         ancillaryinterrogators          complete
> >git-difftool                            ancillaryinterrogators          complete
> >git-merge-tree                          ancillaryinterrogators
> >git-count-objects                       ancillaryinterrogators
> >git-verify-commit                       ancillaryinterrogators
> >git-verify-tag                          ancillaryinterrogators
> >git-send-pack                           synchingrepositories
> >git-fetch-pack                          synchingrepositories
> >git-parse-remote                        synchelpers
> >git-receive-pack                        synchelpers
> >git-upload-pack                         synchelpers
> >git-upload-archive                      synchelpers
> >git-http-fetch                          synchelpers
> >git-http-push                           synchelpers
> >git-var                                 plumbinginterrogators
> >git-rev-list                            plumbinginterrogators
> >git-rev-parse                           plumbinginterrogators
> >git-for-each-ref                        plumbinginterrogators
> >git-show-ref                            plumbinginterrogators
> >git-ls-remote                           plumbinginterrogators
> >git-diff-tree                           plumbinginterrogators
> >git-diff-index                          plumbinginterrogators
> >git-merge-base                          plumbinginterrogators
> >git-verify-pack                         plumbinginterrogators
> >git-pack-redundant                      plumbinginterrogators
> >git-unpack-file                         plumbinginterrogators
> >git-show-index                          plumbinginterrogators
> >git-get-tar-commit-id                   plumbinginterrogators
> >git-merge-one-file                      purehelpers
> >git-sh-setup                            purehelpers
> >git-check-ref-format                    purehelpers
> >git-check-ignore                        purehelpers
> >git-check-attr                          purehelpers
> >git-credential                          purehelpers
> >git-credential-cache                    purehelpers
> >git-credential-store                    purehelpers
> >git-fmt-merge-msg                       purehelpers
> >git-check-mailmap                       purehelpers
> >git-mailsplit                           purehelpers
> >git-mailinfo                            purehelpers
> >git-interpret-trailers                  purehelpers
> >git-column                              purehelpers
> >git-stripspace                          purehelpers
> >git-patch-id                            purehelpers
> >git-sh-i18n                             purehelpers
>
> >GIT(1)                            Git Manual                            GIT(1)
> >
> >NAME
> >       git - the stupid content tracker
> >
> >SYNOPSIS
> >       git [--version] [--help] [-C <path>] [-c <name>=<value>]
> >           [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
> >           [-p|--paginate|-P|--no-pager] [--no-replace-objects] [--bare]
> >           [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]
> >           [--super-prefix=<path>]
> >           <command> [<args>]
> >
> >DESCRIPTION
> >       Git is a fast, scalable, distributed revision control system with an
> >       unusually rich command set that provides both high-level operations and
> >       full access to internals.
> >
> >       See gittutorial(7) to get started, then see giteveryday(7) for a useful
> >       minimum set of commands. The Git User's Manual[1] has a more in-depth
> >       introduction.
> >
> >       After you mastered the basic concepts, you can come back to this page
> >       to learn what commands Git offers. You can learn more about individual
> >       Git commands with "git help command". gitcli(7) manual page gives you
> >       an overview of the command-line command syntax.
> >
> >       A formatted and hyperlinked copy of the latest Git documentation can be
> >       viewed at https://git.github.io/htmldocs/git.html.
> >
> >OPTIONS
> >       --version
> >           Prints the Git suite version that the git program came from.
> >
> >       --help
> >           Prints the synopsis and a list of the most commonly used commands.
> >           If the option --all or -a is given then all available commands are
> >           printed. If a Git command is named this option will bring up the
> >           manual page for that command.
> >
> >           Other options are available to control how the manual page is
> >           displayed. See git-help(1) for more information, because git --help
> >           ...  is converted internally into git help ....
> >
> >       -C <path>
> >           Run as if git was started in <path> instead of the current working
> >           directory. When multiple -C options are given, each subsequent
> >           non-absolute -C <path> is interpreted relative to the preceding -C
> >           <path>.
> >
> >           This option affects options that expect path name like --git-dir
> >           and --work-tree in that their interpretations of the path names
> >           would be made relative to the working directory caused by the -C
> >           option. For example the following invocations are equivalent:
> >
> >               git --git-dir=a.git --work-tree=b -C c status
> >               git --git-dir=c/a.git --work-tree=c/b status
> >
> >       -c <name>=<value>
> >           Pass a configuration parameter to the command. The value given will
> >           override values from configuration files. The <name> is expected in
> >           the same format as listed by git config (subkeys separated by
> >           dots).
> >
> >           Note that omitting the = in git -c foo.bar ...  is allowed and sets
> >           foo.bar to the boolean true value (just like [foo]bar would in a
> >           config file). Including the equals but with an empty value (like
> >           git -c foo.bar= ...) sets foo.bar to the empty string which git
> >           config --type=bool will convert to false.
> >
> >       --exec-path[=<path>]
> >           Path to wherever your core Git programs are installed. This can
> >           also be controlled by setting the GIT_EXEC_PATH environment
> >           variable. If no path is given, git will print the current setting
> >           and then exit.
> >
> >       --html-path
> >           Print the path, without trailing slash, where Git's HTML
> >           documentation is installed and exit.
> >
> >       --man-path
> >           Print the manpath (see man(1)) for the man pages for this version
> >           of Git and exit.
> >
> >       --info-path
> >           Print the path where the Info files documenting this version of Git
> >           are installed and exit.
> >
> >       -p, --paginate
> >           Pipe all output into less (or if set, $PAGER) if standard output is
> >           a terminal. This overrides the pager.<cmd> configuration options
> >           (see the "Configuration Mechanism" section below).
> >
> >       -P, --no-pager
> >           Do not pipe Git output into a pager.
> >
> >       --git-dir=<path>
> >           Set the path to the repository. This can also be controlled by
> >           setting the GIT_DIR environment variable. It can be an absolute
> >           path or relative path to current working directory.
> >
> >       --work-tree=<path>
> >           Set the path to the working tree. It can be an absolute path or a
> >           path relative to the current working directory. This can also be
> >           controlled by setting the GIT_WORK_TREE environment variable and
> >           the core.worktree configuration variable (see core.worktree in git-
> >           config(1) for a more detailed discussion).
> >
> >       --namespace=<path>
> >           Set the Git namespace. See gitnamespaces(7) for more details.
> >           Equivalent to setting the GIT_NAMESPACE environment variable.
> >
> >       --super-prefix=<path>
> >           Currently for internal use only. Set a prefix which gives a path
> >           from above a repository down to its root. One use is to give
> >           submodules context about the superproject that invoked it.
> >
> >       --bare
> >           Treat the repository as a bare repository. If GIT_DIR environment
> >           is not set, it is set to the current working directory.
> >
> >       --no-replace-objects
> >           Do not use replacement refs to replace Git objects. See git-
> >           replace(1) for more information.
> >
> >       --literal-pathspecs
> >           Treat pathspecs literally (i.e. no globbing, no pathspec magic).
> >           This is equivalent to setting the GIT_LITERAL_PATHSPECS environment
> >           variable to 1.
> >
> >       --glob-pathspecs
> >           Add "glob" magic to all pathspec. This is equivalent to setting the
> >           GIT_GLOB_PATHSPECS environment variable to 1. Disabling globbing on
> >           individual pathspecs can be done using pathspec magic ":(literal)"
> >
> >       --noglob-pathspecs
> >           Add "literal" magic to all pathspec. This is equivalent to setting
> >           the GIT_NOGLOB_PATHSPECS environment variable to 1. Enabling
> >           globbing on individual pathspecs can be done using pathspec magic
> >           ":(glob)"
> >
> >       --icase-pathspecs
> >           Add "icase" magic to all pathspec. This is equivalent to setting
> >           the GIT_ICASE_PATHSPECS environment variable to 1.
> >
> >       --no-optional-locks
> >           Do not perform optional operations that require locks. This is
> >           equivalent to setting the GIT_OPTIONAL_LOCKS to 0.
> >
> >       --list-cmds=group[,group...]
> >           List commands by group. This is an internal/experimental option and
> >           may change or be removed in the future. Supported groups are:
> >           builtins, parseopt (builtin commands that use parse-options), main
> >           (all commands in libexec directory), others (all other commands in
> >           $PATH that have git- prefix), list-<category> (see categories in
> >           command-list.txt), nohelpers (exclude helper commands), alias and
> >           config (retrieve command list from config variable
> >           completion.commands)
> >
> >GIT COMMANDS
> >       We divide Git into high level ("porcelain") commands and low level
> >       ("plumbing") commands.
> >
> >HIGH-LEVEL COMMANDS (PORCELAIN)
> >       We separate the porcelain commands into the main commands and some
> >       ancillary user utilities.
> >
> >   Main porcelain commands
> >       git-clone(1)
> >           Clone a repository into a new directory.
> >
> >       git-init(1)
> >           Create an empty Git repository or reinitialize an existing one.
> >
> >       git-add(1)
> >           Add file contents to the index.
> >
> >       git-commit(1)
> >           Record changes to the repository.
> >
> >       git-diff(1)
> >           Show changes between commits, commit and working tree, etc.
> >
> >       git-status(1)
> >           Show the working tree status.
> >
> >       git-log(1)
> >           Show commit logs.
> >
> >       git-branch(1)
> >           List, create, or delete branches.
> >
> >       git-checkout(1)
> >           Switch branches or restore working tree files.
> >
> >       git-merge(1)
> >           Join two or more development histories together.
> >
> >       gitk(1)
> >           The Git repository browser.
> >
> >       git-pull(1)
> >           Fetch from and integrate with another repository or a local branch.
> >
> >       git-fetch(1)
> >           Download objects and refs from another repository.
> >
> >       git-format-patch(1)
> >           Prepare patches for e-mail submission.
> >
> >       git-bisect(1)
> >           Use binary search to find the commit that introduced a bug.
> >
> >       git-reset(1)
> >           Reset current HEAD to the specified state.
> >
> >       git-rebase(1)
> >           Reapply commits on top of another base tip.
> >
> >       git-tag(1)
> >           Create, list, delete or verify a tag object signed with GPG.
> >
> >       git-push(1)
> >           Update remote refs along with associated objects.
> >
> >       git-am(1)
> >           Apply a series of patches from a mailbox.
> >
> >       git-revert(1)
> >           Revert some existing commits.
> >
> >       git-grep(1)
> >           Print lines matching a pattern.
> >
> >       git-show(1)
> >           Show various types of objects.
> >
> >       git-submodule(1)
> >           Initialize, update or inspect submodules.
> >
> >       git-cherry-pick(1)
> >           Apply the changes introduced by some existing commits.
> >
> >       git-clean(1)
> >           Remove untracked files from the working tree.
> >
> >       git-stash(1)
> >           Stash the changes in a dirty working directory away.
> >
> >       git-rm(1)
> >           Remove files from the working tree and from the index.
> >
> >       git-mv(1)
> >           Move or rename a file, a directory, or a symlink.
> >
> >       git-gui(1)
> >           A portable graphical interface to Git.
> >
> >       git-citool(1)
> >           Graphical alternative to git-commit.
> >
> >       git-archive(1)
> >           Create an archive of files from a named tree.
> >
> >       git-shortlog(1)
> >           Summarize git log output.
> >
> >       git-describe(1)
> >           Give an object a human readable name based on an available ref.
> >
> >       git-gc(1)
> >           Cleanup unnecessary files and optimize the local repository.
> >
> >       git-notes(1)
> >           Add or inspect object notes.
> >
> >       git-worktree(1)
> >           Manage multiple working trees.
> >
> >       git-bundle(1)
> >           Move objects and refs by archive.
> >
> >       git-range-diff(1)
> >           Compare two commit ranges (e.g. two versions of a branch).
> >
> >   Ancillary Commands
> >       Manipulators:
> >
> >       git-config(1)
> >           Get and set repository or global options.
> >
> >       git-repack(1)
> >           Pack unpacked objects in a repository.
> >
> >       git-prune(1)
> >           Prune all unreachable objects from the object database.
> >
> >       git-reflog(1)
> >           Manage reflog information.
> >
> >       git-remote(1)
> >           Manage set of tracked repositories.
> >
> >       git-mergetool(1)
> >           Run merge conflict resolution tools to resolve merge conflicts.
> >
> >       git-filter-branch(1)
> >           Rewrite branches.
> >
> >       git-replace(1)
> >           Create, list, delete refs to replace objects.
> >
> >       git-fast-export(1)
> >           Git data exporter.
> >
> >       git-fast-import(1)
> >           Backend for fast Git data importers.
> >
> >       git-pack-refs(1)
> >           Pack heads and tags for efficient repository access.
> >
> >       Interrogators:
> >
> >       git-help(1)
> >           Display help information about Git.
> >
> >       gitweb(1)
> >           Git web interface (web frontend to Git repositories).
> >
> >       git-show-branch(1)
> >           Show branches and their commits.
> >
> >       git-blame(1)
> >           Show what revision and author last modified each line of a file.
> >
> >       git-annotate(1)
> >           Annotate file lines with commit information.
> >
> >       git-instaweb(1)
> >           Instantly browse your working repository in gitweb.
> >
> >       git-rerere(1)
> >           Reuse recorded resolution of conflicted merges.
> >
> >       git-fsck(1)
> >           Verifies the connectivity and validity of the objects in the
> >           database.
> >
> >       git-whatchanged(1)
> >           Show logs with difference each commit introduces.
> >
> >       git-difftool(1)
> >           Show changes using common diff tools.
> >
> >       git-merge-tree(1)
> >           Show three-way merge without touching index.
> >
> >       git-count-objects(1)
> >           Count unpacked number of objects and their disk consumption.
> >
> >       git-verify-commit(1)
> >           Check the GPG signature of commits.
> >
> >       git-verify-tag(1)
> >           Check the GPG signature of tags.
> >
> >   Interacting with Others
> >       These commands are to interact with foreign SCM and with other people
> >       via patch over e-mail.
> >
> >       git-send-email(1)
> >           Send a collection of patches as emails.
> >
> >       git-request-pull(1)
> >           Generates a summary of pending changes.
> >
> >       git-cvsimport(1)
> >           Salvage your data out of another SCM people love to hate.
> >
> >       git-cvsserver(1)
> >           A CVS server emulator for Git.
> >
> >       git-cvsexportcommit(1)
> >           Export a single commit to a CVS checkout.
> >
> >       git-svn(1)
> >           Bidirectional operation between a Subversion repository and Git.
> >
> >       git-p4(1)
> >           Import from and submit to Perforce repositories.
> >
> >       git-quiltimport(1)
> >           Applies a quilt patchset onto the current branch.
> >
> >       git-archimport(1)
> >           Import a GNU Arch repository into Git.
> >
> >       git-imap-send(1)
> >           Send a collection of patches from stdin to an IMAP folder.
> >
> >LOW-LEVEL COMMANDS (PLUMBING)
> >       Although Git includes its own porcelain layer, its low-level commands
> >       are sufficient to support development of alternative porcelains.
> >       Developers of such porcelains might start by reading about git-update-
> >       index(1) and git-read-tree(1).
> >
> >       The interface (input, output, set of options and the semantics) to
> >       these low-level commands are meant to be a lot more stable than
> >       Porcelain level commands, because these commands are primarily for
> >       scripted use. The interface to Porcelain commands on the other hand are
> >       subject to change in order to improve the end user experience.
> >
> >       The following description divides the low-level commands into commands
> >       that manipulate objects (in the repository, index, and working tree),
> >       commands that interrogate and compare objects, and commands that move
> >       objects and references between repositories.
> >
> >   Manipulation commands
> >       git-update-index(1)
> >           Register file contents in the working tree to the index.
> >
> >       git-write-tree(1)
> >           Create a tree object from the current index.
> >
> >       git-read-tree(1)
> >           Reads tree information into the index.
> >
> >       git-checkout-index(1)
> >           Copy files from the index to the working tree.
> >
> >       git-merge-index(1)
> >           Run a merge for files needing merging.
> >
> >       git-prune-packed(1)
> >           Remove extra objects that are already in pack files.
> >
> >       git-apply(1)
> >           Apply a patch to files and/or to the index.
> >
> >       git-merge-file(1)
> >           Run a three-way file merge.
> >
> >       git-mktag(1)
> >           Creates a tag object.
> >
> >       git-hash-object(1)
> >           Compute object ID and optionally creates a blob from a file.
> >
> >       git-update-ref(1)
> >           Update the object name stored in a ref safely.
> >
> >       git-symbolic-ref(1)
> >           Read, modify and delete symbolic refs.
> >
> >       git-commit-tree(1)
> >           Create a new commit object.
> >
> >       git-commit-graph(1)
> >           Write and verify Git commit-graph files.
> >
> >       git-mktree(1)
> >           Build a tree-object from ls-tree formatted text.
> >
> >       git-pack-objects(1)
> >           Create a packed archive of objects.
> >
> >       git-unpack-objects(1)
> >           Unpack objects from a packed archive.
> >
> >       git-index-pack(1)
> >           Build pack index file for an existing packed archive.
> >
> >       git-multi-pack-index(1)
> >           Write and verify multi-pack-indexes.
> >
> >   Interrogation commands
> >       git-cat-file(1)
> >           Provide content or type and size information for repository
> >           objects.
> >
> >       git-ls-tree(1)
> >           List the contents of a tree object.
> >
> >       git-ls-files(1)
> >           Show information about files in the index and the working tree.
> >
> >       git-diff-files(1)
> >           Compares files in the working tree and the index.
> >
> >       git-name-rev(1)
> >           Find symbolic names for given revs.
> >
> >       git-cherry(1)
> >           Find commits yet to be applied to upstream.
> >
> >       git-var(1)
> >           Show a Git logical variable.
> >
> >       git-rev-list(1)
> >           Lists commit objects in reverse chronological order.
> >
> >       git-rev-parse(1)
> >           Pick out and massage parameters.
> >
> >       git-for-each-ref(1)
> >           Output information on each ref.
> >
> >       git-show-ref(1)
> >           List references in a local repository.
> >
> >       git-ls-remote(1)
> >           List references in a remote repository.
> >
> >       git-diff-tree(1)
> >           Compares the content and mode of blobs found via two tree objects.
> >
> >       git-diff-index(1)
> >           Compare a tree to the working tree or index.
> >
> >       git-merge-base(1)
> >           Find as good common ancestors as possible for a merge.
> >
> >       git-verify-pack(1)
> >           Validate packed Git archive files.
> >
> >       git-pack-redundant(1)
> >           Find redundant pack files.
> >
> >       git-unpack-file(1)
> >           Creates a temporary file with a blob's contents.
> >
> >       git-show-index(1)
> >           Show packed archive index.
> >
> >       git-get-tar-commit-id(1)
> >           Extract commit ID from an archive created using git-archive.
> >
> >       In general, the interrogate commands do not touch the files in the
> >       working tree.
> >
> >   Synching repositories
> >       git-daemon(1)
> >           A really simple server for Git repositories.
> >
> >       git-http-backend(1)
> >           Server side implementation of Git over HTTP.
> >
> >       git-update-server-info(1)
> >           Update auxiliary info file to help dumb servers.
> >
> >       git-send-pack(1)
> >           Push objects over Git protocol to another repository.
> >
> >       git-fetch-pack(1)
> >           Receive missing objects from another repository.
> >
> >       The following are helper commands used by the above; end users
> >       typically do not use them directly.
> >
> >       git-shell(1)
> >           Restricted login shell for Git-only SSH access.
> >
> >       git-parse-remote(1)
> >           Routines to help parsing remote repository access parameters.
> >
> >       git-receive-pack(1)
> >           Receive what is pushed into the repository.
> >
> >       git-upload-pack(1)
> >           Send objects packed back to git-fetch-pack.
> >
> >       git-upload-archive(1)
> >           Send archive back to git-archive.
> >
> >       git-http-fetch(1)
> >           Download from a remote Git repository via HTTP.
> >
> >       git-http-push(1)
> >           Push objects over HTTP/DAV to another repository.
> >
> >   Internal helper commands
> >       These are internal helper commands used by other commands; end users
> >       typically do not use them directly.
> >
> >       git-merge-one-file(1)
> >           The standard helper program to use with git-merge-index.
> >
> >       git-sh-setup(1)
> >           Common Git shell script setup code.
> >
> >       git-check-ref-format(1)
> >           Ensures that a reference name is well formed.
> >
> >       git-check-ignore(1)
> >           Debug gitignore / exclude files.
> >
> >       git-check-attr(1)
> >           Display gitattributes information.
> >
> >       git-credential(1)
> >           Retrieve and store user credentials.
> >
> >       git-credential-cache(1)
> >           Helper to temporarily store passwords in memory.
> >
> >       git-credential-store(1)
> >           Helper to store credentials on disk.
> >
> >       git-fmt-merge-msg(1)
> >           Produce a merge commit message.
> >
> >       git-check-mailmap(1)
> >           Show canonical names and email addresses of contacts.
> >
> >       git-mailsplit(1)
> >           Simple UNIX mbox splitter program.
> >
> >       git-mailinfo(1)
> >           Extracts patch and authorship from a single e-mail message.
> >
> >       git-interpret-trailers(1)
> >           add or parse structured information in commit messages.
> >
> >       git-column(1)
> >           Display data in columns.
> >
> >       git-stripspace(1)
> >           Remove unnecessary whitespace.
> >
> >       git-patch-id(1)
> >           Compute unique ID for a patch.
> >
> >       git-sh-i18n(1)
> >           Git's i18n setup code for shell scripts.
> >
> >CONFIGURATION MECHANISM
> >       Git uses a simple text format to store customizations that are per
> >       repository and are per user. Such a configuration file may look like
> >       this:
> >
> >           #
> >           # A '#' or ';' character indicates a comment.
> >           #
> >
> >           ; core variables
> >           [core]
> >                   ; Don't trust file modes
> >                   filemode = false
> >
> >           ; user identity
> >           [user]
> >                   name = "Junio C Hamano"
> >                   email = "gitster@pobox.com"
> >
> >       Various commands read from the configuration file and adjust their
> >       operation accordingly. See git-config(1) for a list and more details
> >       about the configuration mechanism.
> >
> >IDENTIFIER TERMINOLOGY
> >       <object>
> >           Indicates the object name for any type of object.
> >
> >       <blob>
> >           Indicates a blob object name.
> >
> >       <tree>
> >           Indicates a tree object name.
> >
> >       <commit>
> >           Indicates a commit object name.
> >
> >       <tree-ish>
> >           Indicates a tree, commit or tag object name. A command that takes a
> >           <tree-ish> argument ultimately wants to operate on a <tree> object
> >           but automatically dereferences <commit> and <tag> objects that
> >           point at a <tree>.
> >
> >       <commit-ish>
> >           Indicates a commit or tag object name. A command that takes a
> >           <commit-ish> argument ultimately wants to operate on a <commit>
> >           object but automatically dereferences <tag> objects that point at a
> >           <commit>.
> >
> >       <type>
> >           Indicates that an object type is required. Currently one of: blob,
> >           tree, commit, or tag.
> >
> >       <file>
> >           Indicates a filename - almost always relative to the root of the
> >           tree structure GIT_INDEX_FILE describes.
> >
> >SYMBOLIC IDENTIFIERS
> >       Any Git command accepting any <object> can also use the following
> >       symbolic notation:
> >
> >       HEAD
> >           indicates the head of the current branch.
> >
> >       <tag>
> >           a valid tag name (i.e. a refs/tags/<tag> reference).
> >
> >       <head>
> >           a valid head name (i.e. a refs/heads/<head> reference).
> >
> >       For a more complete list of ways to spell object names, see "SPECIFYING
> >       REVISIONS" section in gitrevisions(7).
> >
> >FILE/DIRECTORY STRUCTURE
> >       Please see the gitrepository-layout(5) document.
> >
> >       Read githooks(5) for more details about each hook.
> >
> >       Higher level SCMs may provide and manage additional information in the
> >       $GIT_DIR.
> >
> >TERMINOLOGY
> >       Please see gitglossary(7).
> >
> >ENVIRONMENT VARIABLES
> >       Various Git commands use the following environment variables:
> >
> >   The Git Repository
> >       These environment variables apply to all core Git commands. Nb: it is
> >       worth noting that they may be used/overridden by SCMS sitting above Git
> >       so take care if using a foreign front-end.
> >
> >       GIT_INDEX_FILE
> >           This environment allows the specification of an alternate index
> >           file. If not specified, the default of $GIT_DIR/index is used.
> >
> >       GIT_INDEX_VERSION
> >           This environment variable allows the specification of an index
> >           version for new repositories. It won't affect existing index files.
> >           By default index file version 2 or 3 is used. See git-update-
> >           index(1) for more information.
> >
> >       GIT_OBJECT_DIRECTORY
> >           If the object storage directory is specified via this environment
> >           variable then the sha1 directories are created underneath -
> >           otherwise the default $GIT_DIR/objects directory is used.
> >
> >       GIT_ALTERNATE_OBJECT_DIRECTORIES
> >           Due to the immutable nature of Git objects, old objects can be
> >           archived into shared, read-only directories. This variable
> >           specifies a ":" separated (on Windows ";" separated) list of Git
> >           object directories which can be used to search for Git objects. New
> >           objects will not be written to these directories.
> >
> >           Entries that begin with " (double-quote) will be interpreted as
> >           C-style quoted paths, removing leading and trailing double-quotes
> >           and respecting backslash escapes. E.g., the value
> >           "path-with-\"-and-:-in-it":vanilla-path has two paths:
> >           path-with-"-and-:-in-it and vanilla-path.
> >
> >       GIT_DIR
> >           If the GIT_DIR environment variable is set then it specifies a path
> >           to use instead of the default .git for the base of the repository.
> >           The --git-dir command-line option also sets this value.
> >
> >       GIT_WORK_TREE
> >           Set the path to the root of the working tree. This can also be
> >           controlled by the --work-tree command-line option and the
> >           core.worktree configuration variable.
> >
> >       GIT_NAMESPACE
> >           Set the Git namespace; see gitnamespaces(7) for details. The
> >           --namespace command-line option also sets this value.
> >
> >       GIT_CEILING_DIRECTORIES
> >           This should be a colon-separated list of absolute paths. If set, it
> >           is a list of directories that Git should not chdir up into while
> >           looking for a repository directory (useful for excluding
> >           slow-loading network directories). It will not exclude the current
> >           working directory or a GIT_DIR set on the command line or in the
> >           environment. Normally, Git has to read the entries in this list and
> >           resolve any symlink that might be present in order to compare them
> >           with the current directory. However, if even this access is slow,
> >           you can add an empty entry to the list to tell Git that the
> >           subsequent entries are not symlinks and needn't be resolved; e.g.,
> >           GIT_CEILING_DIRECTORIES=/maybe/symlink::/very/slow/non/symlink.
> >
> >       GIT_DISCOVERY_ACROSS_FILESYSTEM
> >           When run in a directory that does not have ".git" repository
> >           directory, Git tries to find such a directory in the parent
> >           directories to find the top of the working tree, but by default it
> >           does not cross filesystem boundaries. This environment variable can
> >           be set to true to tell Git not to stop at filesystem boundaries.
> >           Like GIT_CEILING_DIRECTORIES, this will not affect an explicit
> >           repository directory set via GIT_DIR or on the command line.
> >
> >       GIT_COMMON_DIR
> >           If this variable is set to a path, non-worktree files that are
> >           normally in $GIT_DIR will be taken from this path instead.
> >           Worktree-specific files such as HEAD or index are taken from
> >           $GIT_DIR. See gitrepository-layout(5) and git-worktree(1) for
> >           details. This variable has lower precedence than other path
> >           variables such as GIT_INDEX_FILE, GIT_OBJECT_DIRECTORY...
> >
> >   Git Commits
> >       GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, GIT_AUTHOR_DATE, GIT_COMMITTER_NAME,
> >       GIT_COMMITTER_EMAIL, GIT_COMMITTER_DATE, EMAIL
> >           see git-commit-tree(1)
> >
> >   Git Diffs
> >       GIT_DIFF_OPTS
> >           Only valid setting is "--unified=??" or "-u??" to set the number of
> >           context lines shown when a unified diff is created. This takes
> >           precedence over any "-U" or "--unified" option value passed on the
> >           Git diff command line.
> >
> >       GIT_EXTERNAL_DIFF
> >           When the environment variable GIT_EXTERNAL_DIFF is set, the program
> >           named by it is called, instead of the diff invocation described
> >           above. For a path that is added, removed, or modified,
> >           GIT_EXTERNAL_DIFF is called with 7 parameters:
> >
> >               path old-file old-hex old-mode new-file new-hex new-mode
> >
> >           where:
> >
> >       <old|new>-file
> >           are files GIT_EXTERNAL_DIFF can use to read the contents of
> >           <old|new>,
> >
> >       <old|new>-hex
> >           are the 40-hexdigit SHA-1 hashes,
> >
> >       <old|new>-mode
> >           are the octal representation of the file modes.
> >
> >           The file parameters can point at the user's working file (e.g.
> >           new-file in "git-diff-files"), /dev/null (e.g.  old-file when a new
> >           file is added), or a temporary file (e.g.  old-file in the index).
> >           GIT_EXTERNAL_DIFF should not worry about unlinking the temporary
> >           file --- it is removed when GIT_EXTERNAL_DIFF exits.
> >
> >           For a path that is unmerged, GIT_EXTERNAL_DIFF is called with 1
> >           parameter, <path>.
> >
> >           For each path GIT_EXTERNAL_DIFF is called, two environment
> >           variables, GIT_DIFF_PATH_COUNTER and GIT_DIFF_PATH_TOTAL are set.
> >
> >       GIT_DIFF_PATH_COUNTER
> >           A 1-based counter incremented by one for every path.
> >
> >       GIT_DIFF_PATH_TOTAL
> >           The total number of paths.
> >
> >   other
> >       GIT_MERGE_VERBOSITY
> >           A number controlling the amount of output shown by the recursive
> >           merge strategy. Overrides merge.verbosity. See git-merge(1)
> >
> >       GIT_PAGER
> >           This environment variable overrides $PAGER. If it is set to an
> >           empty string or to the value "cat", Git will not launch a pager.
> >           See also the core.pager option in git-config(1).
> >
> >       GIT_EDITOR
> >           This environment variable overrides $EDITOR and $VISUAL. It is used
> >           by several Git commands when, on interactive mode, an editor is to
> >           be launched. See also git-var(1) and the core.editor option in git-
> >           config(1).
> >
> >       GIT_SSH, GIT_SSH_COMMAND
> >           If either of these environment variables is set then git fetch and
> >           git push will use the specified command instead of ssh when they
> >           need to connect to a remote system. The command-line parameters
> >           passed to the configured command are determined by the ssh variant.
> >           See ssh.variant option in git-config(1) for details.
> >
> >       + $GIT_SSH_COMMAND takes precedence over $GIT_SSH, and is interpreted
> >       by the shell, which allows additional arguments to be included.
> >       $GIT_SSH on the other hand must be just the path to a program (which
> >       can be a wrapper shell script, if additional arguments are needed).
> >
> >       + Usually it is easier to configure any desired options through your
> >       personal .ssh/config file. Please consult your ssh documentation for
> >       further details.
> >
> >       GIT_SSH_VARIANT
> >           If this environment variable is set, it overrides Git's
> >           autodetection whether GIT_SSH/GIT_SSH_COMMAND/core.sshCommand refer
> >           to OpenSSH, plink or tortoiseplink. This variable overrides the
> >           config setting ssh.variant that serves the same purpose.
> >
> >       GIT_ASKPASS
> >           If this environment variable is set, then Git commands which need
> >           to acquire passwords or passphrases (e.g. for HTTP or IMAP
> >           authentication) will call this program with a suitable prompt as
> >           command-line argument and read the password from its STDOUT. See
> >           also the core.askPass option in git-config(1).
> >
> >       GIT_TERMINAL_PROMPT
> >           If this environment variable is set to 0, git will not prompt on
> >           the terminal (e.g., when asking for HTTP authentication).
> >
> >       GIT_CONFIG_NOSYSTEM
> >           Whether to skip reading settings from the system-wide
> >           $(prefix)/etc/gitconfig file. This environment variable can be used
> >           along with $HOME and $XDG_CONFIG_HOME to create a predictable
> >           environment for a picky script, or you can set it temporarily to
> >           avoid using a buggy /etc/gitconfig file while waiting for someone
> >           with sufficient permissions to fix it.
> >
> >       GIT_FLUSH
> >           If this environment variable is set to "1", then commands such as
> >           git blame (in incremental mode), git rev-list, git log, git
> >           check-attr and git check-ignore will force a flush of the output
> >           stream after each record have been flushed. If this variable is set
> >           to "0", the output of these commands will be done using completely
> >           buffered I/O. If this environment variable is not set, Git will
> >           choose buffered or record-oriented flushing based on whether stdout
> >           appears to be redirected to a file or not.
> >
> >       GIT_TRACE
> >           Enables general trace messages, e.g. alias expansion, built-in
> >           command execution and external command execution.
> >
> >           If this variable is set to "1", "2" or "true" (comparison is case
> >           insensitive), trace messages will be printed to stderr.
> >
> >           If the variable is set to an integer value greater than 2 and lower
> >           than 10 (strictly) then Git will interpret this value as an open
> >           file descriptor and will try to write the trace messages into this
> >           file descriptor.
> >
> >           Alternatively, if the variable is set to an absolute path (starting
> >           with a / character), Git will interpret this as a file path and
> >           will try to append the trace messages to it.
> >
> >           Unsetting the variable, or setting it to empty, "0" or "false"
> >           (case insensitive) disables trace messages.
> >
> >       GIT_TRACE_FSMONITOR
> >           Enables trace messages for the filesystem monitor extension. See
> >           GIT_TRACE for available trace output options.
> >
> >       GIT_TRACE_PACK_ACCESS
> >           Enables trace messages for all accesses to any packs. For each
> >           access, the pack file name and an offset in the pack is recorded.
> >           This may be helpful for troubleshooting some pack-related
> >           performance problems. See GIT_TRACE for available trace output
> >           options.
> >
> >       GIT_TRACE_PACKET
> >           Enables trace messages for all packets coming in or out of a given
> >           program. This can help with debugging object negotiation or other
> >           protocol issues. Tracing is turned off at a packet starting with
> >           "PACK" (but see GIT_TRACE_PACKFILE below). See GIT_TRACE for
> >           available trace output options.
> >
> >       GIT_TRACE_PACKFILE
> >           Enables tracing of packfiles sent or received by a given program.
> >           Unlike other trace output, this trace is verbatim: no headers, and
> >           no quoting of binary data. You almost certainly want to direct into
> >           a file (e.g., GIT_TRACE_PACKFILE=/tmp/my.pack) rather than
> >           displaying it on the terminal or mixing it with other trace output.
> >
> >           Note that this is currently only implemented for the client side of
> >           clones and fetches.
> >
> >       GIT_TRACE_PERFORMANCE
> >           Enables performance related trace messages, e.g. total execution
> >           time of each Git command. See GIT_TRACE for available trace output
> >           options.
> >
> >       GIT_TRACE_SETUP
> >           Enables trace messages printing the .git, working tree and current
> >           working directory after Git has completed its setup phase. See
> >           GIT_TRACE for available trace output options.
> >
> >       GIT_TRACE_SHALLOW
> >           Enables trace messages that can help debugging fetching / cloning
> >           of shallow repositories. See GIT_TRACE for available trace output
> >           options.
> >
> >       GIT_TRACE_CURL
> >           Enables a curl full trace dump of all incoming and outgoing data,
> >           including descriptive information, of the git transport protocol.
> >           This is similar to doing curl --trace-ascii on the command line.
> >           This option overrides setting the GIT_CURL_VERBOSE environment
> >           variable. See GIT_TRACE for available trace output options.
> >
> >       GIT_TRACE_CURL_NO_DATA
> >           When a curl trace is enabled (see GIT_TRACE_CURL above), do not
> >           dump data (that is, only dump info lines and headers).
> >
> >       GIT_REDACT_COOKIES
> >           This can be set to a comma-separated list of strings. When a curl
> >           trace is enabled (see GIT_TRACE_CURL above), whenever a "Cookies:"
> >           header sent by the client is dumped, values of cookies whose key is
> >           in that list (case-sensitive) are redacted.
> >
> >       GIT_LITERAL_PATHSPECS
> >           Setting this variable to 1 will cause Git to treat all pathspecs
> >           literally, rather than as glob patterns. For example, running
> >           GIT_LITERAL_PATHSPECS=1 git log -- '*.c' will search for commits
> >           that touch the path *.c, not any paths that the glob *.c matches.
> >           You might want this if you are feeding literal paths to Git (e.g.,
> >           paths previously given to you by git ls-tree, --raw diff output,
> >           etc).
> >
> >       GIT_GLOB_PATHSPECS
> >           Setting this variable to 1 will cause Git to treat all pathspecs as
> >           glob patterns (aka "glob" magic).
> >
> >       GIT_NOGLOB_PATHSPECS
> >           Setting this variable to 1 will cause Git to treat all pathspecs as
> >           literal (aka "literal" magic).
> >
> >       GIT_ICASE_PATHSPECS
> >           Setting this variable to 1 will cause Git to treat all pathspecs as
> >           case-insensitive.
> >
> >       GIT_REFLOG_ACTION
> >           When a ref is updated, reflog entries are created to keep track of
> >           the reason why the ref was updated (which is typically the name of
> >           the high-level command that updated the ref), in addition to the
> >           old and new values of the ref. A scripted Porcelain command can use
> >           set_reflog_action helper function in git-sh-setup to set its name
> >           to this variable when it is invoked as the top level command by the
> >           end user, to be recorded in the body of the reflog.
> >
> >       GIT_REF_PARANOIA
> >           If set to 1, include broken or badly named refs when iterating over
> >           lists of refs. In a normal, non-corrupted repository, this does
> >           nothing. However, enabling it may help git to detect and abort some
> >           operations in the presence of broken refs. Git sets this variable
> >           automatically when performing destructive operations like git-
> >           prune(1). You should not need to set it yourself unless you want to
> >           be paranoid about making sure an operation has touched every ref
> >           (e.g., because you are cloning a repository to make a backup).
> >
> >       GIT_ALLOW_PROTOCOL
> >           If set to a colon-separated list of protocols, behave as if
> >           protocol.allow is set to never, and each of the listed protocols
> >           has protocol.<name>.allow set to always (overriding any existing
> >           configuration). In other words, any protocol not mentioned will be
> >           disallowed (i.e., this is a whitelist, not a blacklist). See the
> >           description of protocol.allow in git-config(1) for more details.
> >
> >       GIT_PROTOCOL_FROM_USER
> >           Set to 0 to prevent protocols used by fetch/push/clone which are
> >           configured to the user state. This is useful to restrict recursive
> >           submodule initialization from an untrusted repository or for
> >           programs which feed potentially-untrusted URLS to git commands. See
> >           git-config(1) for more details.
> >
> >       GIT_PROTOCOL
> >           For internal use only. Used in handshaking the wire protocol.
> >           Contains a colon : separated list of keys with optional values
> >           key[=value]. Presence of unknown keys and values must be ignored.
> >
> >       GIT_OPTIONAL_LOCKS
> >           If set to 0, Git will complete any requested operation without
> >           performing any optional sub-operations that require taking a lock.
> >           For example, this will prevent git status from refreshing the index
> >           as a side effect. This is useful for processes running in the
> >           background which do not want to cause lock contention with other
> >           operations on the repository. Defaults to 1.
> >
> >       GIT_REDIRECT_STDIN, GIT_REDIRECT_STDOUT, GIT_REDIRECT_STDERR
> >           Windows-only: allow redirecting the standard input/output/error
> >           handles to paths specified by the environment variables. This is
> >           particularly useful in multi-threaded applications where the
> >           canonical way to pass standard handles via CreateProcess() is not
> >           an option because it would require the handles to be marked
> >           inheritable (and consequently every spawned process would inherit
> >           them, possibly blocking regular Git operations). The primary
> >           intended use case is to use named pipes for communication (e.g.
> >           \\.\pipe\my-git-stdin-123).
> >
> >           Two special values are supported: off will simply close the
> >           corresponding standard handle, and if GIT_REDIRECT_STDERR is 2>&1,
> >           standard error will be redirected to the same handle as standard
> >           output.
> >
> >       GIT_PRINT_SHA1_ELLIPSIS (deprecated)
> >           If set to yes, print an ellipsis following an (abbreviated) SHA-1
> >           value. This affects indications of detached HEADs (git-checkout(1))
> >           and the raw diff output (git-diff(1)). Printing an ellipsis in the
> >           cases mentioned is no longer considered adequate and support for it
> >           is likely to be removed in the foreseeable future (along with the
> >           variable).
> >
> >DISCUSSION
> >       More detail on the following is available from the Git concepts chapter
> >       of the user-manual[2] and gitcore-tutorial(7).
> >
> >       A Git project normally consists of a working directory with a ".git"
> >       subdirectory at the top level. The .git directory contains, among other
> >       things, a compressed object database representing the complete history
> >       of the project, an "index" file which links that history to the current
> >       contents of the working tree, and named pointers into that history such
> >       as tags and branch heads.
> >
> >       The object database contains objects of three main types: blobs, which
> >       hold file data; trees, which point to blobs and other trees to build up
> >       directory hierarchies; and commits, which each reference a single tree
> >       and some number of parent commits.
> >
> >       The commit, equivalent to what other systems call a "changeset" or
> >       "version", represents a step in the project's history, and each parent
> >       represents an immediately preceding step. Commits with more than one
> >       parent represent merges of independent lines of development.
> >
> >       All objects are named by the SHA-1 hash of their contents, normally
> >       written as a string of 40 hex digits. Such names are globally unique.
> >       The entire history leading up to a commit can be vouched for by signing
> >       just that commit. A fourth object type, the tag, is provided for this
> >       purpose.
> >
> >       When first created, objects are stored in individual files, but for
> >       efficiency may later be compressed together into "pack files".
> >
> >       Named pointers called refs mark interesting points in history. A ref
> >       may contain the SHA-1 name of an object or the name of another ref.
> >       Refs with names beginning ref/head/ contain the SHA-1 name of the most
> >       recent commit (or "head") of a branch under development. SHA-1 names of
> >       tags of interest are stored under ref/tags/. A special ref named HEAD
> >       contains the name of the currently checked-out branch.
> >
> >       The index file is initialized with a list of all paths and, for each
> >       path, a blob object and a set of attributes. The blob object represents
> >       the contents of the file as of the head of the current branch. The
> >       attributes (last modified time, size, etc.) are taken from the
> >       corresponding file in the working tree. Subsequent changes to the
> >       working tree can be found by comparing these attributes. The index may
> >       be updated with new content, and new commits may be created from the
> >       content stored in the index.
> >
> >       The index is also capable of storing multiple entries (called "stages")
> >       for a given pathname. These stages are used to hold the various
> >       unmerged version of a file when a merge is in progress.
> >
> >FURTHER DOCUMENTATION
> >       See the references in the "description" section to get started using
> >       Git. The following is probably more detail than necessary for a
> >       first-time user.
> >
> >       The Git concepts chapter of the user-manual[2] and gitcore-tutorial(7)
> >       both provide introductions to the underlying Git architecture.
> >
> >       See gitworkflows(7) for an overview of recommended workflows.
> >
> >       See also the howto[3] documents for some useful examples.
> >
> >       The internals are documented in the Git API documentation[4].
> >
> >       Users migrating from CVS may also want to read gitcvs-migration(7).
> >
> >AUTHORS
> >       Git was started by Linus Torvalds, and is currently maintained by Junio
> >       C Hamano. Numerous contributions have come from the Git mailing list
> >       <git@vger.kernel.org[5]>.
> >       http://www.openhub.net/p/git/contributors/summary gives you a more
> >       complete list of contributors.
> >
> >       If you have a clone of git.git itself, the output of git-shortlog(1)
> >       and git-blame(1) can show you the authors for specific parts of the
> >       project.
> >
> >REPORTING BUGS
> >       Report bugs to the Git mailing list <git@vger.kernel.org[5]> where the
> >       development and maintenance is primarily done. You do not have to be
> >       subscribed to the list to send a message there. See the list archive at
> >       https://public-inbox.org/git for previous bug reports and other
> >       discussions.
> >
> >       Issues which are security relevant should be disclosed privately to the
> >       Git Security mailing list <git-security@googlegroups.com[6]>.
> >
> >SEE ALSO
> >       gittutorial(7), gittutorial-2(7), giteveryday(7), gitcvs-migration(7),
> >       gitglossary(7), gitcore-tutorial(7), gitcli(7), The Git User's
> >       Manual[1], gitworkflows(7)
> >
> >GIT
> >       Part of the git(1) suite
> >
> >NOTES
> >        1. Git User's Manual
> >           file:///home/frederik/share/doc/git-doc/user-manual.html
> >
> >        2. Git concepts chapter of the user-manual
> >           file:///home/frederik/share/doc/git-doc/user-manual.html#git-concepts
> >
> >        3. howto
> >           file:///home/frederik/share/doc/git-doc/howto-index.html
> >
> >        4. Git API documentation
> >           file:///home/frederik/share/doc/git-doc/technical/api-index.html
> >
> >        5. git@vger.kernel.org
> >           mailto:git@vger.kernel.org
> >
> >        6. git-security@googlegroups.com
> >           mailto:git-security@googlegroups.com
> >
> >Git 2.21.0.rc1.9.g3f              02/18/2019                            GIT(1)
>

^ permalink raw reply	[relevance 0%]

* Re: [PATCH 0/1] de-alphabetize command list
  2019-02-21 18:05  1%   ` frederik
@ 2019-03-11  9:04  0%     ` frederik
  2019-03-11 14:38  0%       ` Jacob Keller
  0 siblings, 1 reply; 200+ results
From: frederik @ 2019-03-11  9:04 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Eric Sunshine, Jonathan Nieder,
	Theodore Y. Ts'o

Hey Git people,

I didn't get a reply and I'm not sure what the appropriate ping
interval is, or when I should conclude that no one is interested.

There seemed to be some vaguely positive feedback before I embarked on
this project. At the same time I don't want to pester anyone into
applying patches in a disorganized fashion.

I'm not subscribed to the mailing list so I apologize if I'm out of
tune with a release cycle or current development thrust.

Thanks,

Frederick

On Thu, Feb 21, 2019 at 10:05:22AM -0800, frederik@ofb.net wrote:
>I realized that it would probably be easier to discuss this proposal
>if I attached the final command listing and the rendered manual page.
>Please find them attached to this message.
>
>Thank you,
>
>Frederick
>
>On Tue, Feb 19, 2019 at 09:54:12AM -0800, Frederick Eaton wrote:
>>This is a follow-up to my proposal to de-alphabetize the command
>>listings in the git(1) manual page, from 6 July 2018.
>>
>>Some projects have manual page items listed in alphabetical order,
>>some don't. As I argued in my proposal, I find it easier to learn from
>>material which is not alphabetized. If this patch is accepted, I hope
>>that it will make the Git documentation more accessible to myself and
>>others.
>>
>>I produced the reordered command list in this patch using several
>>sources, as indicated by comments in the new command-list.txt file.
>>First, all the commands in the main part of "gittutorial(7)" appear in
>>order, then the commands in giteveryday(7). Then appear additional
>>commands from a friend's shell history, in reverse order of frequency.
>>Then gittutorial-2(7), then gitcore-tutorial(7). After that there is a
>>list of "guides", followed by about 100 commands not appearing in the
>>earlier lists. I kept the guides and the remaining commands in their
>>category groupings (guide, mainporcelain, ancillarymanipulators,
>>etc.), but ordered the commands within each category according to my
>>own judgment after skimming each manual page.
>>
>>To verify that the new list is a permutation of the most recent list,
>>I use the following command (it should produce no output and exit 0):
>>
>>   diff <(git show master:command-list.txt | grep -v '^#' | sort ) <(cat command-list.txt | grep -v '^#' | sort)
>>
>>Note this patch changes the order of commands appearing in the
>>generated file "command-list.h", which mostly seems to be used by
>>"help.c". Probably due to the various occurrences of QSORT in
>>"help.c", I think this reordering has no visible effect. I am willing
>>to do any additional testing which may be recommended to ensure that
>>this patch has no undesired consequences.
>>
>>Frederick Eaton (1):
>> Prioritize list of commands appearing in git(1), via command-list.txt.
>>   Don't invoke 'sort' in Documentation/cmd-list.perl.
>>
>>Documentation/cmd-list.perl |   2 +-
>>command-list.txt            | 295 +++++++++++++++++++-----------------
>>2 files changed, 158 insertions(+), 139 deletions(-)
>>
>>-- 
>>2.20.1
>>

># Command classification list
># ---------------------------
># All supported commands, builtin or external, must be described in
># here. This info is used to list commands in various places. Each
># command is on one line followed by one or more attributes.
>#
># The first attribute group is mandatory and indicates the command
># type. This group includes:
>#
>#   mainporcelain
>#   ancillarymanipulators
>#   ancillaryinterrogators
>#   foreignscminterface
>#   plumbingmanipulators
>#   plumbinginterrogators
>#   synchingrepositories
>#   synchelpers
>#   purehelpers
>#
># The type names are self explanatory. But if you want to see what
># command belongs to what group to get a better picture, have a look
># at "git" man page, "GIT COMMANDS" section.
>#
># Commands of type mainporcelain can also optionally have one of these
># attributes:
>#
>#   init
>#   worktree
>#   info
>#   history
>#   remote
>#
># These commands are considered "common" and will show up in "git
># help" output in groups. Uncommon porcelain commands must not
># specify any of these attributes.
>#
># "complete" attribute is used to mark that the command should be
># completable by git-completion.bash. Note that by default,
># mainporcelain commands are completable so you don't need this
># attribute.
>#
># As part of the Git man page list, the man(5/7) guides are also
># specified here, which can only have "guide" attribute and nothing
># else.
>#
># February 2019: This list had been sorted alphabetically but has been
># reordered to make it easier for people to learn from the main git(1)
># manual page. The new ordering is according to approximate usefulness
># / frequency of use / order of use, with some grouping by topic. The
># idea is to make it possible to read the manual page from beginning
># to end and see the most important commands first, rather than
># getting them in alphabetical order - in other words, to make the
># manual page more like a table of contents and less like an index.
># Please consider this when adding new commands.
>#
>### command list (do not change this line, also do not change alignment)
># command name                          category [category] [category]
># From gittutorial
>git-help                                ancillaryinterrogators          complete
>git-config                              ancillarymanipulators           complete
>git-clone                               mainporcelain           init
>git-init                                mainporcelain           init
>git-add                                 mainporcelain           worktree
>git-commit                              mainporcelain           history
>git-diff                                mainporcelain           history
>git-status                              mainporcelain           info
>git-log                                 mainporcelain           info
>git-branch                              mainporcelain           history
>git-checkout                            mainporcelain           history
>git-merge                               mainporcelain           history
>gitk                                    mainporcelain
>git-pull                                mainporcelain           remote
>git-fetch                               mainporcelain           remote
># From tutorial NEXT STEPS
>git-format-patch                        mainporcelain
>git-bisect                              mainporcelain           info
>giteveryday                             guide
>gitworkflows                            guide
>gitcvs-migration                        guide
># From giteveryday
>git-reset                               mainporcelain           worktree
>git-rebase                              mainporcelain           history
>git-tag                                 mainporcelain           history
>git-push                                mainporcelain           remote
>git-send-email                          foreignscminterface             complete
>git-request-pull                        foreignscminterface             complete
>git-am                                  mainporcelain
>git-revert                              mainporcelain
>git-daemon                              synchingrepositories
>git-shell                               synchelpers
>git-http-backend                        synchingrepositories
>gitweb                                  ancillaryinterrogators
># From user feedback
>git-grep                                mainporcelain           info
>git-show                                mainporcelain           info
>git-submodule                           mainporcelain
>git-cherry-pick                         mainporcelain
>git-clean                               mainporcelain
># From gittutorial-2
>git-cat-file                            plumbinginterrogators
>git-ls-tree                             plumbinginterrogators
>git-ls-files                            plumbinginterrogators
>gitcore-tutorial                        guide
>gitglossary                             guide
># From gitcore-tutorial
>git-update-index                        plumbingmanipulators
>git-diff-files                          plumbinginterrogators
>git-write-tree                          plumbingmanipulators
>git-read-tree                           plumbingmanipulators
>git-checkout-index                      plumbingmanipulators
>git-show-branch                         ancillaryinterrogators          complete
>git-name-rev                            plumbinginterrogators
>git-merge-index                         plumbingmanipulators
>git-repack                              ancillarymanipulators           complete
>git-prune-packed                        plumbingmanipulators
>git-update-server-info                  synchingrepositories
>git-prune                               ancillarymanipulators
>git-cherry                              plumbinginterrogators          complete
># Guides, reordered
>gittutorial                             guide
>gittutorial-2                           guide
>gitrevisions                            guide
>gitignore                               guide
>gitcli                                  guide
>gitrepository-layout                    guide
>gitdiffcore                             guide
>gitmodules                              guide
>githooks                                guide
>gitnamespaces                           guide
>gitattributes                           guide
># All other commands, sorted by man page category and then by
># approximate priority
>git-stash                               mainporcelain
>git-rm                                  mainporcelain           worktree
>git-mv                                  mainporcelain           worktree
>git-gui                                 mainporcelain
>git-citool                              mainporcelain
>git-archive                             mainporcelain
>git-shortlog                            mainporcelain
>git-describe                            mainporcelain
>git-gc                                  mainporcelain
>git-notes                               mainporcelain
>git-worktree                            mainporcelain
>git-bundle                              mainporcelain
>git-range-diff                          mainporcelain
>git-stage                                                               complete
>git-reflog                              ancillarymanipulators           complete
>git-remote                              ancillarymanipulators           complete
>git-mergetool                           ancillarymanipulators           complete
>git-filter-branch                       ancillarymanipulators
>git-replace                             ancillarymanipulators           complete
>git-fast-export                         ancillarymanipulators
>git-fast-import                         ancillarymanipulators
>git-pack-refs                           ancillarymanipulators
>git-cvsimport                           foreignscminterface
>git-cvsserver                           foreignscminterface
>git-cvsexportcommit                     foreignscminterface
>git-svn                                 foreignscminterface
>git-p4                                  foreignscminterface
>git-quiltimport                         foreignscminterface
>git-archimport                          foreignscminterface
>git-imap-send                           foreignscminterface
>git-apply                               plumbingmanipulators            complete
>git-merge-file                          plumbingmanipulators
>git-mktag                               plumbingmanipulators
>git-hash-object                         plumbingmanipulators
>git-update-ref                          plumbingmanipulators
>git-symbolic-ref                        plumbingmanipulators
>git-commit-tree                         plumbingmanipulators
>git-commit-graph                        plumbingmanipulators
>git-mktree                              plumbingmanipulators
>git-pack-objects                        plumbingmanipulators
>git-unpack-objects                      plumbingmanipulators
>git-index-pack                          plumbingmanipulators
>git-multi-pack-index                    plumbingmanipulators
>git-blame                               ancillaryinterrogators          complete
>git-annotate                            ancillaryinterrogators
>git-instaweb                            ancillaryinterrogators          complete
>git-rerere                              ancillaryinterrogators
>git-fsck                                ancillaryinterrogators          complete
>git-whatchanged                         ancillaryinterrogators          complete
>git-difftool                            ancillaryinterrogators          complete
>git-merge-tree                          ancillaryinterrogators
>git-count-objects                       ancillaryinterrogators
>git-verify-commit                       ancillaryinterrogators
>git-verify-tag                          ancillaryinterrogators
>git-send-pack                           synchingrepositories
>git-fetch-pack                          synchingrepositories
>git-parse-remote                        synchelpers
>git-receive-pack                        synchelpers
>git-upload-pack                         synchelpers
>git-upload-archive                      synchelpers
>git-http-fetch                          synchelpers
>git-http-push                           synchelpers
>git-var                                 plumbinginterrogators
>git-rev-list                            plumbinginterrogators
>git-rev-parse                           plumbinginterrogators
>git-for-each-ref                        plumbinginterrogators
>git-show-ref                            plumbinginterrogators
>git-ls-remote                           plumbinginterrogators
>git-diff-tree                           plumbinginterrogators
>git-diff-index                          plumbinginterrogators
>git-merge-base                          plumbinginterrogators
>git-verify-pack                         plumbinginterrogators
>git-pack-redundant                      plumbinginterrogators
>git-unpack-file                         plumbinginterrogators
>git-show-index                          plumbinginterrogators
>git-get-tar-commit-id                   plumbinginterrogators
>git-merge-one-file                      purehelpers
>git-sh-setup                            purehelpers
>git-check-ref-format                    purehelpers
>git-check-ignore                        purehelpers
>git-check-attr                          purehelpers
>git-credential                          purehelpers
>git-credential-cache                    purehelpers
>git-credential-store                    purehelpers
>git-fmt-merge-msg                       purehelpers
>git-check-mailmap                       purehelpers
>git-mailsplit                           purehelpers
>git-mailinfo                            purehelpers
>git-interpret-trailers                  purehelpers
>git-column                              purehelpers
>git-stripspace                          purehelpers
>git-patch-id                            purehelpers
>git-sh-i18n                             purehelpers

>GIT(1)                            Git Manual                            GIT(1)
>
>NAME
>       git - the stupid content tracker
>
>SYNOPSIS
>       git [--version] [--help] [-C <path>] [-c <name>=<value>]
>           [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
>           [-p|--paginate|-P|--no-pager] [--no-replace-objects] [--bare]
>           [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]
>           [--super-prefix=<path>]
>           <command> [<args>]
>
>DESCRIPTION
>       Git is a fast, scalable, distributed revision control system with an
>       unusually rich command set that provides both high-level operations and
>       full access to internals.
>
>       See gittutorial(7) to get started, then see giteveryday(7) for a useful
>       minimum set of commands. The Git User's Manual[1] has a more in-depth
>       introduction.
>
>       After you mastered the basic concepts, you can come back to this page
>       to learn what commands Git offers. You can learn more about individual
>       Git commands with "git help command". gitcli(7) manual page gives you
>       an overview of the command-line command syntax.
>
>       A formatted and hyperlinked copy of the latest Git documentation can be
>       viewed at https://git.github.io/htmldocs/git.html.
>
>OPTIONS
>       --version
>           Prints the Git suite version that the git program came from.
>
>       --help
>           Prints the synopsis and a list of the most commonly used commands.
>           If the option --all or -a is given then all available commands are
>           printed. If a Git command is named this option will bring up the
>           manual page for that command.
>
>           Other options are available to control how the manual page is
>           displayed. See git-help(1) for more information, because git --help
>           ...  is converted internally into git help ....
>
>       -C <path>
>           Run as if git was started in <path> instead of the current working
>           directory. When multiple -C options are given, each subsequent
>           non-absolute -C <path> is interpreted relative to the preceding -C
>           <path>.
>
>           This option affects options that expect path name like --git-dir
>           and --work-tree in that their interpretations of the path names
>           would be made relative to the working directory caused by the -C
>           option. For example the following invocations are equivalent:
>
>               git --git-dir=a.git --work-tree=b -C c status
>               git --git-dir=c/a.git --work-tree=c/b status
>
>       -c <name>=<value>
>           Pass a configuration parameter to the command. The value given will
>           override values from configuration files. The <name> is expected in
>           the same format as listed by git config (subkeys separated by
>           dots).
>
>           Note that omitting the = in git -c foo.bar ...  is allowed and sets
>           foo.bar to the boolean true value (just like [foo]bar would in a
>           config file). Including the equals but with an empty value (like
>           git -c foo.bar= ...) sets foo.bar to the empty string which git
>           config --type=bool will convert to false.
>
>       --exec-path[=<path>]
>           Path to wherever your core Git programs are installed. This can
>           also be controlled by setting the GIT_EXEC_PATH environment
>           variable. If no path is given, git will print the current setting
>           and then exit.
>
>       --html-path
>           Print the path, without trailing slash, where Git's HTML
>           documentation is installed and exit.
>
>       --man-path
>           Print the manpath (see man(1)) for the man pages for this version
>           of Git and exit.
>
>       --info-path
>           Print the path where the Info files documenting this version of Git
>           are installed and exit.
>
>       -p, --paginate
>           Pipe all output into less (or if set, $PAGER) if standard output is
>           a terminal. This overrides the pager.<cmd> configuration options
>           (see the "Configuration Mechanism" section below).
>
>       -P, --no-pager
>           Do not pipe Git output into a pager.
>
>       --git-dir=<path>
>           Set the path to the repository. This can also be controlled by
>           setting the GIT_DIR environment variable. It can be an absolute
>           path or relative path to current working directory.
>
>       --work-tree=<path>
>           Set the path to the working tree. It can be an absolute path or a
>           path relative to the current working directory. This can also be
>           controlled by setting the GIT_WORK_TREE environment variable and
>           the core.worktree configuration variable (see core.worktree in git-
>           config(1) for a more detailed discussion).
>
>       --namespace=<path>
>           Set the Git namespace. See gitnamespaces(7) for more details.
>           Equivalent to setting the GIT_NAMESPACE environment variable.
>
>       --super-prefix=<path>
>           Currently for internal use only. Set a prefix which gives a path
>           from above a repository down to its root. One use is to give
>           submodules context about the superproject that invoked it.
>
>       --bare
>           Treat the repository as a bare repository. If GIT_DIR environment
>           is not set, it is set to the current working directory.
>
>       --no-replace-objects
>           Do not use replacement refs to replace Git objects. See git-
>           replace(1) for more information.
>
>       --literal-pathspecs
>           Treat pathspecs literally (i.e. no globbing, no pathspec magic).
>           This is equivalent to setting the GIT_LITERAL_PATHSPECS environment
>           variable to 1.
>
>       --glob-pathspecs
>           Add "glob" magic to all pathspec. This is equivalent to setting the
>           GIT_GLOB_PATHSPECS environment variable to 1. Disabling globbing on
>           individual pathspecs can be done using pathspec magic ":(literal)"
>
>       --noglob-pathspecs
>           Add "literal" magic to all pathspec. This is equivalent to setting
>           the GIT_NOGLOB_PATHSPECS environment variable to 1. Enabling
>           globbing on individual pathspecs can be done using pathspec magic
>           ":(glob)"
>
>       --icase-pathspecs
>           Add "icase" magic to all pathspec. This is equivalent to setting
>           the GIT_ICASE_PATHSPECS environment variable to 1.
>
>       --no-optional-locks
>           Do not perform optional operations that require locks. This is
>           equivalent to setting the GIT_OPTIONAL_LOCKS to 0.
>
>       --list-cmds=group[,group...]
>           List commands by group. This is an internal/experimental option and
>           may change or be removed in the future. Supported groups are:
>           builtins, parseopt (builtin commands that use parse-options), main
>           (all commands in libexec directory), others (all other commands in
>           $PATH that have git- prefix), list-<category> (see categories in
>           command-list.txt), nohelpers (exclude helper commands), alias and
>           config (retrieve command list from config variable
>           completion.commands)
>
>GIT COMMANDS
>       We divide Git into high level ("porcelain") commands and low level
>       ("plumbing") commands.
>
>HIGH-LEVEL COMMANDS (PORCELAIN)
>       We separate the porcelain commands into the main commands and some
>       ancillary user utilities.
>
>   Main porcelain commands
>       git-clone(1)
>           Clone a repository into a new directory.
>
>       git-init(1)
>           Create an empty Git repository or reinitialize an existing one.
>
>       git-add(1)
>           Add file contents to the index.
>
>       git-commit(1)
>           Record changes to the repository.
>
>       git-diff(1)
>           Show changes between commits, commit and working tree, etc.
>
>       git-status(1)
>           Show the working tree status.
>
>       git-log(1)
>           Show commit logs.
>
>       git-branch(1)
>           List, create, or delete branches.
>
>       git-checkout(1)
>           Switch branches or restore working tree files.
>
>       git-merge(1)
>           Join two or more development histories together.
>
>       gitk(1)
>           The Git repository browser.
>
>       git-pull(1)
>           Fetch from and integrate with another repository or a local branch.
>
>       git-fetch(1)
>           Download objects and refs from another repository.
>
>       git-format-patch(1)
>           Prepare patches for e-mail submission.
>
>       git-bisect(1)
>           Use binary search to find the commit that introduced a bug.
>
>       git-reset(1)
>           Reset current HEAD to the specified state.
>
>       git-rebase(1)
>           Reapply commits on top of another base tip.
>
>       git-tag(1)
>           Create, list, delete or verify a tag object signed with GPG.
>
>       git-push(1)
>           Update remote refs along with associated objects.
>
>       git-am(1)
>           Apply a series of patches from a mailbox.
>
>       git-revert(1)
>           Revert some existing commits.
>
>       git-grep(1)
>           Print lines matching a pattern.
>
>       git-show(1)
>           Show various types of objects.
>
>       git-submodule(1)
>           Initialize, update or inspect submodules.
>
>       git-cherry-pick(1)
>           Apply the changes introduced by some existing commits.
>
>       git-clean(1)
>           Remove untracked files from the working tree.
>
>       git-stash(1)
>           Stash the changes in a dirty working directory away.
>
>       git-rm(1)
>           Remove files from the working tree and from the index.
>
>       git-mv(1)
>           Move or rename a file, a directory, or a symlink.
>
>       git-gui(1)
>           A portable graphical interface to Git.
>
>       git-citool(1)
>           Graphical alternative to git-commit.
>
>       git-archive(1)
>           Create an archive of files from a named tree.
>
>       git-shortlog(1)
>           Summarize git log output.
>
>       git-describe(1)
>           Give an object a human readable name based on an available ref.
>
>       git-gc(1)
>           Cleanup unnecessary files and optimize the local repository.
>
>       git-notes(1)
>           Add or inspect object notes.
>
>       git-worktree(1)
>           Manage multiple working trees.
>
>       git-bundle(1)
>           Move objects and refs by archive.
>
>       git-range-diff(1)
>           Compare two commit ranges (e.g. two versions of a branch).
>
>   Ancillary Commands
>       Manipulators:
>
>       git-config(1)
>           Get and set repository or global options.
>
>       git-repack(1)
>           Pack unpacked objects in a repository.
>
>       git-prune(1)
>           Prune all unreachable objects from the object database.
>
>       git-reflog(1)
>           Manage reflog information.
>
>       git-remote(1)
>           Manage set of tracked repositories.
>
>       git-mergetool(1)
>           Run merge conflict resolution tools to resolve merge conflicts.
>
>       git-filter-branch(1)
>           Rewrite branches.
>
>       git-replace(1)
>           Create, list, delete refs to replace objects.
>
>       git-fast-export(1)
>           Git data exporter.
>
>       git-fast-import(1)
>           Backend for fast Git data importers.
>
>       git-pack-refs(1)
>           Pack heads and tags for efficient repository access.
>
>       Interrogators:
>
>       git-help(1)
>           Display help information about Git.
>
>       gitweb(1)
>           Git web interface (web frontend to Git repositories).
>
>       git-show-branch(1)
>           Show branches and their commits.
>
>       git-blame(1)
>           Show what revision and author last modified each line of a file.
>
>       git-annotate(1)
>           Annotate file lines with commit information.
>
>       git-instaweb(1)
>           Instantly browse your working repository in gitweb.
>
>       git-rerere(1)
>           Reuse recorded resolution of conflicted merges.
>
>       git-fsck(1)
>           Verifies the connectivity and validity of the objects in the
>           database.
>
>       git-whatchanged(1)
>           Show logs with difference each commit introduces.
>
>       git-difftool(1)
>           Show changes using common diff tools.
>
>       git-merge-tree(1)
>           Show three-way merge without touching index.
>
>       git-count-objects(1)
>           Count unpacked number of objects and their disk consumption.
>
>       git-verify-commit(1)
>           Check the GPG signature of commits.
>
>       git-verify-tag(1)
>           Check the GPG signature of tags.
>
>   Interacting with Others
>       These commands are to interact with foreign SCM and with other people
>       via patch over e-mail.
>
>       git-send-email(1)
>           Send a collection of patches as emails.
>
>       git-request-pull(1)
>           Generates a summary of pending changes.
>
>       git-cvsimport(1)
>           Salvage your data out of another SCM people love to hate.
>
>       git-cvsserver(1)
>           A CVS server emulator for Git.
>
>       git-cvsexportcommit(1)
>           Export a single commit to a CVS checkout.
>
>       git-svn(1)
>           Bidirectional operation between a Subversion repository and Git.
>
>       git-p4(1)
>           Import from and submit to Perforce repositories.
>
>       git-quiltimport(1)
>           Applies a quilt patchset onto the current branch.
>
>       git-archimport(1)
>           Import a GNU Arch repository into Git.
>
>       git-imap-send(1)
>           Send a collection of patches from stdin to an IMAP folder.
>
>LOW-LEVEL COMMANDS (PLUMBING)
>       Although Git includes its own porcelain layer, its low-level commands
>       are sufficient to support development of alternative porcelains.
>       Developers of such porcelains might start by reading about git-update-
>       index(1) and git-read-tree(1).
>
>       The interface (input, output, set of options and the semantics) to
>       these low-level commands are meant to be a lot more stable than
>       Porcelain level commands, because these commands are primarily for
>       scripted use. The interface to Porcelain commands on the other hand are
>       subject to change in order to improve the end user experience.
>
>       The following description divides the low-level commands into commands
>       that manipulate objects (in the repository, index, and working tree),
>       commands that interrogate and compare objects, and commands that move
>       objects and references between repositories.
>
>   Manipulation commands
>       git-update-index(1)
>           Register file contents in the working tree to the index.
>
>       git-write-tree(1)
>           Create a tree object from the current index.
>
>       git-read-tree(1)
>           Reads tree information into the index.
>
>       git-checkout-index(1)
>           Copy files from the index to the working tree.
>
>       git-merge-index(1)
>           Run a merge for files needing merging.
>
>       git-prune-packed(1)
>           Remove extra objects that are already in pack files.
>
>       git-apply(1)
>           Apply a patch to files and/or to the index.
>
>       git-merge-file(1)
>           Run a three-way file merge.
>
>       git-mktag(1)
>           Creates a tag object.
>
>       git-hash-object(1)
>           Compute object ID and optionally creates a blob from a file.
>
>       git-update-ref(1)
>           Update the object name stored in a ref safely.
>
>       git-symbolic-ref(1)
>           Read, modify and delete symbolic refs.
>
>       git-commit-tree(1)
>           Create a new commit object.
>
>       git-commit-graph(1)
>           Write and verify Git commit-graph files.
>
>       git-mktree(1)
>           Build a tree-object from ls-tree formatted text.
>
>       git-pack-objects(1)
>           Create a packed archive of objects.
>
>       git-unpack-objects(1)
>           Unpack objects from a packed archive.
>
>       git-index-pack(1)
>           Build pack index file for an existing packed archive.
>
>       git-multi-pack-index(1)
>           Write and verify multi-pack-indexes.
>
>   Interrogation commands
>       git-cat-file(1)
>           Provide content or type and size information for repository
>           objects.
>
>       git-ls-tree(1)
>           List the contents of a tree object.
>
>       git-ls-files(1)
>           Show information about files in the index and the working tree.
>
>       git-diff-files(1)
>           Compares files in the working tree and the index.
>
>       git-name-rev(1)
>           Find symbolic names for given revs.
>
>       git-cherry(1)
>           Find commits yet to be applied to upstream.
>
>       git-var(1)
>           Show a Git logical variable.
>
>       git-rev-list(1)
>           Lists commit objects in reverse chronological order.
>
>       git-rev-parse(1)
>           Pick out and massage parameters.
>
>       git-for-each-ref(1)
>           Output information on each ref.
>
>       git-show-ref(1)
>           List references in a local repository.
>
>       git-ls-remote(1)
>           List references in a remote repository.
>
>       git-diff-tree(1)
>           Compares the content and mode of blobs found via two tree objects.
>
>       git-diff-index(1)
>           Compare a tree to the working tree or index.
>
>       git-merge-base(1)
>           Find as good common ancestors as possible for a merge.
>
>       git-verify-pack(1)
>           Validate packed Git archive files.
>
>       git-pack-redundant(1)
>           Find redundant pack files.
>
>       git-unpack-file(1)
>           Creates a temporary file with a blob's contents.
>
>       git-show-index(1)
>           Show packed archive index.
>
>       git-get-tar-commit-id(1)
>           Extract commit ID from an archive created using git-archive.
>
>       In general, the interrogate commands do not touch the files in the
>       working tree.
>
>   Synching repositories
>       git-daemon(1)
>           A really simple server for Git repositories.
>
>       git-http-backend(1)
>           Server side implementation of Git over HTTP.
>
>       git-update-server-info(1)
>           Update auxiliary info file to help dumb servers.
>
>       git-send-pack(1)
>           Push objects over Git protocol to another repository.
>
>       git-fetch-pack(1)
>           Receive missing objects from another repository.
>
>       The following are helper commands used by the above; end users
>       typically do not use them directly.
>
>       git-shell(1)
>           Restricted login shell for Git-only SSH access.
>
>       git-parse-remote(1)
>           Routines to help parsing remote repository access parameters.
>
>       git-receive-pack(1)
>           Receive what is pushed into the repository.
>
>       git-upload-pack(1)
>           Send objects packed back to git-fetch-pack.
>
>       git-upload-archive(1)
>           Send archive back to git-archive.
>
>       git-http-fetch(1)
>           Download from a remote Git repository via HTTP.
>
>       git-http-push(1)
>           Push objects over HTTP/DAV to another repository.
>
>   Internal helper commands
>       These are internal helper commands used by other commands; end users
>       typically do not use them directly.
>
>       git-merge-one-file(1)
>           The standard helper program to use with git-merge-index.
>
>       git-sh-setup(1)
>           Common Git shell script setup code.
>
>       git-check-ref-format(1)
>           Ensures that a reference name is well formed.
>
>       git-check-ignore(1)
>           Debug gitignore / exclude files.
>
>       git-check-attr(1)
>           Display gitattributes information.
>
>       git-credential(1)
>           Retrieve and store user credentials.
>
>       git-credential-cache(1)
>           Helper to temporarily store passwords in memory.
>
>       git-credential-store(1)
>           Helper to store credentials on disk.
>
>       git-fmt-merge-msg(1)
>           Produce a merge commit message.
>
>       git-check-mailmap(1)
>           Show canonical names and email addresses of contacts.
>
>       git-mailsplit(1)
>           Simple UNIX mbox splitter program.
>
>       git-mailinfo(1)
>           Extracts patch and authorship from a single e-mail message.
>
>       git-interpret-trailers(1)
>           add or parse structured information in commit messages.
>
>       git-column(1)
>           Display data in columns.
>
>       git-stripspace(1)
>           Remove unnecessary whitespace.
>
>       git-patch-id(1)
>           Compute unique ID for a patch.
>
>       git-sh-i18n(1)
>           Git's i18n setup code for shell scripts.
>
>CONFIGURATION MECHANISM
>       Git uses a simple text format to store customizations that are per
>       repository and are per user. Such a configuration file may look like
>       this:
>
>           #
>           # A '#' or ';' character indicates a comment.
>           #
>
>           ; core variables
>           [core]
>                   ; Don't trust file modes
>                   filemode = false
>
>           ; user identity
>           [user]
>                   name = "Junio C Hamano"
>                   email = "gitster@pobox.com"
>
>       Various commands read from the configuration file and adjust their
>       operation accordingly. See git-config(1) for a list and more details
>       about the configuration mechanism.
>
>IDENTIFIER TERMINOLOGY
>       <object>
>           Indicates the object name for any type of object.
>
>       <blob>
>           Indicates a blob object name.
>
>       <tree>
>           Indicates a tree object name.
>
>       <commit>
>           Indicates a commit object name.
>
>       <tree-ish>
>           Indicates a tree, commit or tag object name. A command that takes a
>           <tree-ish> argument ultimately wants to operate on a <tree> object
>           but automatically dereferences <commit> and <tag> objects that
>           point at a <tree>.
>
>       <commit-ish>
>           Indicates a commit or tag object name. A command that takes a
>           <commit-ish> argument ultimately wants to operate on a <commit>
>           object but automatically dereferences <tag> objects that point at a
>           <commit>.
>
>       <type>
>           Indicates that an object type is required. Currently one of: blob,
>           tree, commit, or tag.
>
>       <file>
>           Indicates a filename - almost always relative to the root of the
>           tree structure GIT_INDEX_FILE describes.
>
>SYMBOLIC IDENTIFIERS
>       Any Git command accepting any <object> can also use the following
>       symbolic notation:
>
>       HEAD
>           indicates the head of the current branch.
>
>       <tag>
>           a valid tag name (i.e. a refs/tags/<tag> reference).
>
>       <head>
>           a valid head name (i.e. a refs/heads/<head> reference).
>
>       For a more complete list of ways to spell object names, see "SPECIFYING
>       REVISIONS" section in gitrevisions(7).
>
>FILE/DIRECTORY STRUCTURE
>       Please see the gitrepository-layout(5) document.
>
>       Read githooks(5) for more details about each hook.
>
>       Higher level SCMs may provide and manage additional information in the
>       $GIT_DIR.
>
>TERMINOLOGY
>       Please see gitglossary(7).
>
>ENVIRONMENT VARIABLES
>       Various Git commands use the following environment variables:
>
>   The Git Repository
>       These environment variables apply to all core Git commands. Nb: it is
>       worth noting that they may be used/overridden by SCMS sitting above Git
>       so take care if using a foreign front-end.
>
>       GIT_INDEX_FILE
>           This environment allows the specification of an alternate index
>           file. If not specified, the default of $GIT_DIR/index is used.
>
>       GIT_INDEX_VERSION
>           This environment variable allows the specification of an index
>           version for new repositories. It won't affect existing index files.
>           By default index file version 2 or 3 is used. See git-update-
>           index(1) for more information.
>
>       GIT_OBJECT_DIRECTORY
>           If the object storage directory is specified via this environment
>           variable then the sha1 directories are created underneath -
>           otherwise the default $GIT_DIR/objects directory is used.
>
>       GIT_ALTERNATE_OBJECT_DIRECTORIES
>           Due to the immutable nature of Git objects, old objects can be
>           archived into shared, read-only directories. This variable
>           specifies a ":" separated (on Windows ";" separated) list of Git
>           object directories which can be used to search for Git objects. New
>           objects will not be written to these directories.
>
>           Entries that begin with " (double-quote) will be interpreted as
>           C-style quoted paths, removing leading and trailing double-quotes
>           and respecting backslash escapes. E.g., the value
>           "path-with-\"-and-:-in-it":vanilla-path has two paths:
>           path-with-"-and-:-in-it and vanilla-path.
>
>       GIT_DIR
>           If the GIT_DIR environment variable is set then it specifies a path
>           to use instead of the default .git for the base of the repository.
>           The --git-dir command-line option also sets this value.
>
>       GIT_WORK_TREE
>           Set the path to the root of the working tree. This can also be
>           controlled by the --work-tree command-line option and the
>           core.worktree configuration variable.
>
>       GIT_NAMESPACE
>           Set the Git namespace; see gitnamespaces(7) for details. The
>           --namespace command-line option also sets this value.
>
>       GIT_CEILING_DIRECTORIES
>           This should be a colon-separated list of absolute paths. If set, it
>           is a list of directories that Git should not chdir up into while
>           looking for a repository directory (useful for excluding
>           slow-loading network directories). It will not exclude the current
>           working directory or a GIT_DIR set on the command line or in the
>           environment. Normally, Git has to read the entries in this list and
>           resolve any symlink that might be present in order to compare them
>           with the current directory. However, if even this access is slow,
>           you can add an empty entry to the list to tell Git that the
>           subsequent entries are not symlinks and needn't be resolved; e.g.,
>           GIT_CEILING_DIRECTORIES=/maybe/symlink::/very/slow/non/symlink.
>
>       GIT_DISCOVERY_ACROSS_FILESYSTEM
>           When run in a directory that does not have ".git" repository
>           directory, Git tries to find such a directory in the parent
>           directories to find the top of the working tree, but by default it
>           does not cross filesystem boundaries. This environment variable can
>           be set to true to tell Git not to stop at filesystem boundaries.
>           Like GIT_CEILING_DIRECTORIES, this will not affect an explicit
>           repository directory set via GIT_DIR or on the command line.
>
>       GIT_COMMON_DIR
>           If this variable is set to a path, non-worktree files that are
>           normally in $GIT_DIR will be taken from this path instead.
>           Worktree-specific files such as HEAD or index are taken from
>           $GIT_DIR. See gitrepository-layout(5) and git-worktree(1) for
>           details. This variable has lower precedence than other path
>           variables such as GIT_INDEX_FILE, GIT_OBJECT_DIRECTORY...
>
>   Git Commits
>       GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, GIT_AUTHOR_DATE, GIT_COMMITTER_NAME,
>       GIT_COMMITTER_EMAIL, GIT_COMMITTER_DATE, EMAIL
>           see git-commit-tree(1)
>
>   Git Diffs
>       GIT_DIFF_OPTS
>           Only valid setting is "--unified=??" or "-u??" to set the number of
>           context lines shown when a unified diff is created. This takes
>           precedence over any "-U" or "--unified" option value passed on the
>           Git diff command line.
>
>       GIT_EXTERNAL_DIFF
>           When the environment variable GIT_EXTERNAL_DIFF is set, the program
>           named by it is called, instead of the diff invocation described
>           above. For a path that is added, removed, or modified,
>           GIT_EXTERNAL_DIFF is called with 7 parameters:
>
>               path old-file old-hex old-mode new-file new-hex new-mode
>
>           where:
>
>       <old|new>-file
>           are files GIT_EXTERNAL_DIFF can use to read the contents of
>           <old|new>,
>
>       <old|new>-hex
>           are the 40-hexdigit SHA-1 hashes,
>
>       <old|new>-mode
>           are the octal representation of the file modes.
>
>           The file parameters can point at the user's working file (e.g.
>           new-file in "git-diff-files"), /dev/null (e.g.  old-file when a new
>           file is added), or a temporary file (e.g.  old-file in the index).
>           GIT_EXTERNAL_DIFF should not worry about unlinking the temporary
>           file --- it is removed when GIT_EXTERNAL_DIFF exits.
>
>           For a path that is unmerged, GIT_EXTERNAL_DIFF is called with 1
>           parameter, <path>.
>
>           For each path GIT_EXTERNAL_DIFF is called, two environment
>           variables, GIT_DIFF_PATH_COUNTER and GIT_DIFF_PATH_TOTAL are set.
>
>       GIT_DIFF_PATH_COUNTER
>           A 1-based counter incremented by one for every path.
>
>       GIT_DIFF_PATH_TOTAL
>           The total number of paths.
>
>   other
>       GIT_MERGE_VERBOSITY
>           A number controlling the amount of output shown by the recursive
>           merge strategy. Overrides merge.verbosity. See git-merge(1)
>
>       GIT_PAGER
>           This environment variable overrides $PAGER. If it is set to an
>           empty string or to the value "cat", Git will not launch a pager.
>           See also the core.pager option in git-config(1).
>
>       GIT_EDITOR
>           This environment variable overrides $EDITOR and $VISUAL. It is used
>           by several Git commands when, on interactive mode, an editor is to
>           be launched. See also git-var(1) and the core.editor option in git-
>           config(1).
>
>       GIT_SSH, GIT_SSH_COMMAND
>           If either of these environment variables is set then git fetch and
>           git push will use the specified command instead of ssh when they
>           need to connect to a remote system. The command-line parameters
>           passed to the configured command are determined by the ssh variant.
>           See ssh.variant option in git-config(1) for details.
>
>       + $GIT_SSH_COMMAND takes precedence over $GIT_SSH, and is interpreted
>       by the shell, which allows additional arguments to be included.
>       $GIT_SSH on the other hand must be just the path to a program (which
>       can be a wrapper shell script, if additional arguments are needed).
>
>       + Usually it is easier to configure any desired options through your
>       personal .ssh/config file. Please consult your ssh documentation for
>       further details.
>
>       GIT_SSH_VARIANT
>           If this environment variable is set, it overrides Git's
>           autodetection whether GIT_SSH/GIT_SSH_COMMAND/core.sshCommand refer
>           to OpenSSH, plink or tortoiseplink. This variable overrides the
>           config setting ssh.variant that serves the same purpose.
>
>       GIT_ASKPASS
>           If this environment variable is set, then Git commands which need
>           to acquire passwords or passphrases (e.g. for HTTP or IMAP
>           authentication) will call this program with a suitable prompt as
>           command-line argument and read the password from its STDOUT. See
>           also the core.askPass option in git-config(1).
>
>       GIT_TERMINAL_PROMPT
>           If this environment variable is set to 0, git will not prompt on
>           the terminal (e.g., when asking for HTTP authentication).
>
>       GIT_CONFIG_NOSYSTEM
>           Whether to skip reading settings from the system-wide
>           $(prefix)/etc/gitconfig file. This environment variable can be used
>           along with $HOME and $XDG_CONFIG_HOME to create a predictable
>           environment for a picky script, or you can set it temporarily to
>           avoid using a buggy /etc/gitconfig file while waiting for someone
>           with sufficient permissions to fix it.
>
>       GIT_FLUSH
>           If this environment variable is set to "1", then commands such as
>           git blame (in incremental mode), git rev-list, git log, git
>           check-attr and git check-ignore will force a flush of the output
>           stream after each record have been flushed. If this variable is set
>           to "0", the output of these commands will be done using completely
>           buffered I/O. If this environment variable is not set, Git will
>           choose buffered or record-oriented flushing based on whether stdout
>           appears to be redirected to a file or not.
>
>       GIT_TRACE
>           Enables general trace messages, e.g. alias expansion, built-in
>           command execution and external command execution.
>
>           If this variable is set to "1", "2" or "true" (comparison is case
>           insensitive), trace messages will be printed to stderr.
>
>           If the variable is set to an integer value greater than 2 and lower
>           than 10 (strictly) then Git will interpret this value as an open
>           file descriptor and will try to write the trace messages into this
>           file descriptor.
>
>           Alternatively, if the variable is set to an absolute path (starting
>           with a / character), Git will interpret this as a file path and
>           will try to append the trace messages to it.
>
>           Unsetting the variable, or setting it to empty, "0" or "false"
>           (case insensitive) disables trace messages.
>
>       GIT_TRACE_FSMONITOR
>           Enables trace messages for the filesystem monitor extension. See
>           GIT_TRACE for available trace output options.
>
>       GIT_TRACE_PACK_ACCESS
>           Enables trace messages for all accesses to any packs. For each
>           access, the pack file name and an offset in the pack is recorded.
>           This may be helpful for troubleshooting some pack-related
>           performance problems. See GIT_TRACE for available trace output
>           options.
>
>       GIT_TRACE_PACKET
>           Enables trace messages for all packets coming in or out of a given
>           program. This can help with debugging object negotiation or other
>           protocol issues. Tracing is turned off at a packet starting with
>           "PACK" (but see GIT_TRACE_PACKFILE below). See GIT_TRACE for
>           available trace output options.
>
>       GIT_TRACE_PACKFILE
>           Enables tracing of packfiles sent or received by a given program.
>           Unlike other trace output, this trace is verbatim: no headers, and
>           no quoting of binary data. You almost certainly want to direct into
>           a file (e.g., GIT_TRACE_PACKFILE=/tmp/my.pack) rather than
>           displaying it on the terminal or mixing it with other trace output.
>
>           Note that this is currently only implemented for the client side of
>           clones and fetches.
>
>       GIT_TRACE_PERFORMANCE
>           Enables performance related trace messages, e.g. total execution
>           time of each Git command. See GIT_TRACE for available trace output
>           options.
>
>       GIT_TRACE_SETUP
>           Enables trace messages printing the .git, working tree and current
>           working directory after Git has completed its setup phase. See
>           GIT_TRACE for available trace output options.
>
>       GIT_TRACE_SHALLOW
>           Enables trace messages that can help debugging fetching / cloning
>           of shallow repositories. See GIT_TRACE for available trace output
>           options.
>
>       GIT_TRACE_CURL
>           Enables a curl full trace dump of all incoming and outgoing data,
>           including descriptive information, of the git transport protocol.
>           This is similar to doing curl --trace-ascii on the command line.
>           This option overrides setting the GIT_CURL_VERBOSE environment
>           variable. See GIT_TRACE for available trace output options.
>
>       GIT_TRACE_CURL_NO_DATA
>           When a curl trace is enabled (see GIT_TRACE_CURL above), do not
>           dump data (that is, only dump info lines and headers).
>
>       GIT_REDACT_COOKIES
>           This can be set to a comma-separated list of strings. When a curl
>           trace is enabled (see GIT_TRACE_CURL above), whenever a "Cookies:"
>           header sent by the client is dumped, values of cookies whose key is
>           in that list (case-sensitive) are redacted.
>
>       GIT_LITERAL_PATHSPECS
>           Setting this variable to 1 will cause Git to treat all pathspecs
>           literally, rather than as glob patterns. For example, running
>           GIT_LITERAL_PATHSPECS=1 git log -- '*.c' will search for commits
>           that touch the path *.c, not any paths that the glob *.c matches.
>           You might want this if you are feeding literal paths to Git (e.g.,
>           paths previously given to you by git ls-tree, --raw diff output,
>           etc).
>
>       GIT_GLOB_PATHSPECS
>           Setting this variable to 1 will cause Git to treat all pathspecs as
>           glob patterns (aka "glob" magic).
>
>       GIT_NOGLOB_PATHSPECS
>           Setting this variable to 1 will cause Git to treat all pathspecs as
>           literal (aka "literal" magic).
>
>       GIT_ICASE_PATHSPECS
>           Setting this variable to 1 will cause Git to treat all pathspecs as
>           case-insensitive.
>
>       GIT_REFLOG_ACTION
>           When a ref is updated, reflog entries are created to keep track of
>           the reason why the ref was updated (which is typically the name of
>           the high-level command that updated the ref), in addition to the
>           old and new values of the ref. A scripted Porcelain command can use
>           set_reflog_action helper function in git-sh-setup to set its name
>           to this variable when it is invoked as the top level command by the
>           end user, to be recorded in the body of the reflog.
>
>       GIT_REF_PARANOIA
>           If set to 1, include broken or badly named refs when iterating over
>           lists of refs. In a normal, non-corrupted repository, this does
>           nothing. However, enabling it may help git to detect and abort some
>           operations in the presence of broken refs. Git sets this variable
>           automatically when performing destructive operations like git-
>           prune(1). You should not need to set it yourself unless you want to
>           be paranoid about making sure an operation has touched every ref
>           (e.g., because you are cloning a repository to make a backup).
>
>       GIT_ALLOW_PROTOCOL
>           If set to a colon-separated list of protocols, behave as if
>           protocol.allow is set to never, and each of the listed protocols
>           has protocol.<name>.allow set to always (overriding any existing
>           configuration). In other words, any protocol not mentioned will be
>           disallowed (i.e., this is a whitelist, not a blacklist). See the
>           description of protocol.allow in git-config(1) for more details.
>
>       GIT_PROTOCOL_FROM_USER
>           Set to 0 to prevent protocols used by fetch/push/clone which are
>           configured to the user state. This is useful to restrict recursive
>           submodule initialization from an untrusted repository or for
>           programs which feed potentially-untrusted URLS to git commands. See
>           git-config(1) for more details.
>
>       GIT_PROTOCOL
>           For internal use only. Used in handshaking the wire protocol.
>           Contains a colon : separated list of keys with optional values
>           key[=value]. Presence of unknown keys and values must be ignored.
>
>       GIT_OPTIONAL_LOCKS
>           If set to 0, Git will complete any requested operation without
>           performing any optional sub-operations that require taking a lock.
>           For example, this will prevent git status from refreshing the index
>           as a side effect. This is useful for processes running in the
>           background which do not want to cause lock contention with other
>           operations on the repository. Defaults to 1.
>
>       GIT_REDIRECT_STDIN, GIT_REDIRECT_STDOUT, GIT_REDIRECT_STDERR
>           Windows-only: allow redirecting the standard input/output/error
>           handles to paths specified by the environment variables. This is
>           particularly useful in multi-threaded applications where the
>           canonical way to pass standard handles via CreateProcess() is not
>           an option because it would require the handles to be marked
>           inheritable (and consequently every spawned process would inherit
>           them, possibly blocking regular Git operations). The primary
>           intended use case is to use named pipes for communication (e.g.
>           \\.\pipe\my-git-stdin-123).
>
>           Two special values are supported: off will simply close the
>           corresponding standard handle, and if GIT_REDIRECT_STDERR is 2>&1,
>           standard error will be redirected to the same handle as standard
>           output.
>
>       GIT_PRINT_SHA1_ELLIPSIS (deprecated)
>           If set to yes, print an ellipsis following an (abbreviated) SHA-1
>           value. This affects indications of detached HEADs (git-checkout(1))
>           and the raw diff output (git-diff(1)). Printing an ellipsis in the
>           cases mentioned is no longer considered adequate and support for it
>           is likely to be removed in the foreseeable future (along with the
>           variable).
>
>DISCUSSION
>       More detail on the following is available from the Git concepts chapter
>       of the user-manual[2] and gitcore-tutorial(7).
>
>       A Git project normally consists of a working directory with a ".git"
>       subdirectory at the top level. The .git directory contains, among other
>       things, a compressed object database representing the complete history
>       of the project, an "index" file which links that history to the current
>       contents of the working tree, and named pointers into that history such
>       as tags and branch heads.
>
>       The object database contains objects of three main types: blobs, which
>       hold file data; trees, which point to blobs and other trees to build up
>       directory hierarchies; and commits, which each reference a single tree
>       and some number of parent commits.
>
>       The commit, equivalent to what other systems call a "changeset" or
>       "version", represents a step in the project's history, and each parent
>       represents an immediately preceding step. Commits with more than one
>       parent represent merges of independent lines of development.
>
>       All objects are named by the SHA-1 hash of their contents, normally
>       written as a string of 40 hex digits. Such names are globally unique.
>       The entire history leading up to a commit can be vouched for by signing
>       just that commit. A fourth object type, the tag, is provided for this
>       purpose.
>
>       When first created, objects are stored in individual files, but for
>       efficiency may later be compressed together into "pack files".
>
>       Named pointers called refs mark interesting points in history. A ref
>       may contain the SHA-1 name of an object or the name of another ref.
>       Refs with names beginning ref/head/ contain the SHA-1 name of the most
>       recent commit (or "head") of a branch under development. SHA-1 names of
>       tags of interest are stored under ref/tags/. A special ref named HEAD
>       contains the name of the currently checked-out branch.
>
>       The index file is initialized with a list of all paths and, for each
>       path, a blob object and a set of attributes. The blob object represents
>       the contents of the file as of the head of the current branch. The
>       attributes (last modified time, size, etc.) are taken from the
>       corresponding file in the working tree. Subsequent changes to the
>       working tree can be found by comparing these attributes. The index may
>       be updated with new content, and new commits may be created from the
>       content stored in the index.
>
>       The index is also capable of storing multiple entries (called "stages")
>       for a given pathname. These stages are used to hold the various
>       unmerged version of a file when a merge is in progress.
>
>FURTHER DOCUMENTATION
>       See the references in the "description" section to get started using
>       Git. The following is probably more detail than necessary for a
>       first-time user.
>
>       The Git concepts chapter of the user-manual[2] and gitcore-tutorial(7)
>       both provide introductions to the underlying Git architecture.
>
>       See gitworkflows(7) for an overview of recommended workflows.
>
>       See also the howto[3] documents for some useful examples.
>
>       The internals are documented in the Git API documentation[4].
>
>       Users migrating from CVS may also want to read gitcvs-migration(7).
>
>AUTHORS
>       Git was started by Linus Torvalds, and is currently maintained by Junio
>       C Hamano. Numerous contributions have come from the Git mailing list
>       <git@vger.kernel.org[5]>.
>       http://www.openhub.net/p/git/contributors/summary gives you a more
>       complete list of contributors.
>
>       If you have a clone of git.git itself, the output of git-shortlog(1)
>       and git-blame(1) can show you the authors for specific parts of the
>       project.
>
>REPORTING BUGS
>       Report bugs to the Git mailing list <git@vger.kernel.org[5]> where the
>       development and maintenance is primarily done. You do not have to be
>       subscribed to the list to send a message there. See the list archive at
>       https://public-inbox.org/git for previous bug reports and other
>       discussions.
>
>       Issues which are security relevant should be disclosed privately to the
>       Git Security mailing list <git-security@googlegroups.com[6]>.
>
>SEE ALSO
>       gittutorial(7), gittutorial-2(7), giteveryday(7), gitcvs-migration(7),
>       gitglossary(7), gitcore-tutorial(7), gitcli(7), The Git User's
>       Manual[1], gitworkflows(7)
>
>GIT
>       Part of the git(1) suite
>
>NOTES
>        1. Git User's Manual
>           file:///home/frederik/share/doc/git-doc/user-manual.html
>
>        2. Git concepts chapter of the user-manual
>           file:///home/frederik/share/doc/git-doc/user-manual.html#git-concepts
>
>        3. howto
>           file:///home/frederik/share/doc/git-doc/howto-index.html
>
>        4. Git API documentation
>           file:///home/frederik/share/doc/git-doc/technical/api-index.html
>
>        5. git@vger.kernel.org
>           mailto:git@vger.kernel.org
>
>        6. git-security@googlegroups.com
>           mailto:git-security@googlegroups.com
>
>Git 2.21.0.rc1.9.g3f              02/18/2019                            GIT(1)


^ permalink raw reply	[relevance 0%]

* AW: AW: fast-import fails with case sensitive tags due to case insensitive lock files
  2019-03-04 15:29  9%     ` Johannes Schindelin
@ 2019-03-05  6:25 14%       ` Wendeborn, Jonathan
  0 siblings, 0 replies; 200+ results
From: Wendeborn, Jonathan @ 2019-03-05  6:25 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: brian m. carlson, git@vger.kernel.org

Hi Johannes,

>> One thing I still would like to encourage to is to improve the error 
>> message which is really misleading in this case.

> Well, that is at least something you could do. After all, you use a volunteer-driven project, so you could at least volunteer a little time to improving it yourself.

That's true :)

Thank you!
Jonathan

-----Ursprüngliche Nachricht-----
Von: Johannes Schindelin <Johannes.Schindelin@gmx.de> 
Gesendet: Montag, 4. März 2019 16:29
An: Wendeborn, Jonathan <Jonathan.Wendeborn@bruker.com>
Cc: brian m. carlson <sandals@crustytoothpaste.net>; git@vger.kernel.org
Betreff: Re: AW: fast-import fails with case sensitive tags due to case insensitive lock files

Hi Jonathan,

On Mon, 4 Mar 2019, Wendeborn, Jonathan wrote:

> > Right now, you have some choices: 
> > • Volunteer to implement reftable. 
> > • Since you're on Windows 10, set your Git repository directory as
> >   case-sensitive. 
> > • Use Windows Subsystem for Linux, which is case sensitive and 
> > creates
> >   directories with that flag (even on NTFS), to do your import. 
> > • If you control the fast-export output, adjust the arguments you 
> > pass
> >   such that the output does not contain one of the offending tags. 
> 
> Hi Brian,
> 
> Thank you very much for your answer!
> 
> Unfortunately I am stuck with Windows 10 1703 which neither supports 
> case-sensitivity nor any Linux subsystem from the Microsoft Store :(

Too bad.

> Also, my employer unfortunately doesn’t allow me to invest the time to 
> implement reftable,

Even worse!

> so I guess I go with manually leaving out the one conflicting label I 
> found and tagging it manually afterward.

That is a valid workaround.

> One thing I still would like to encourage to is to improve the error 
> message which is really misleading in this case.

Well, that is at least something you could do. After all, you use a volunteer-driven project, so you could at least volunteer a little time to improving it yourself.

Ciao,
Johannes

^ permalink raw reply	[relevance 14%]

* [PATCH 2/5] git-p4: match branches case insensitively if configured
  @ 2019-03-04 17:34  6% ` Mazo, Andrey
  0 siblings, 0 replies; 200+ results
From: Mazo, Andrey @ 2019-03-04 17:34 UTC (permalink / raw)
  To: git@vger.kernel.org
  Cc: Mazo, Andrey, luke@diamand.org, sunshine@sunshineco.com,
	gvanburgh@bloomberg.net, larsxschneider@gmail.com,
	miguel.torroja@gmail.com, merlorom@yahoo.fr, vitor.hda@gmail.com,
	aoakley@roku.com, szeder.dev@gmail.com, gitster@pobox.com

git-p4 knows how to handle case insensitivity in file paths
if core.ignorecase is set.
However, when determining a branch for a file,
it still does a case-sensitive prefix match.
This may result in some file changes to be lost on import.

For example, given the following commits
 1. add //depot/main/file1
 2. add //depot/DirA/file2
 3. add //depot/dira/file3
 4. add //depot/DirA/file4
and "branchList = main:DirA" branch mapping,
commit 3 will be lost.

So, do branch search case insensitively if running with core.ignorecase set.
Teach splitFilesIntoBranches() to use the p4PathStartsWith() function
for path prefix matches instead of always case-sensitive match.

Signed-off-by: Andrey Mazo <amazo@checkvideo.com>
---
 git-p4.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/git-p4.py b/git-p4.py
index c0a3068b6f..91c610f960 100755
--- a/git-p4.py
+++ b/git-p4.py
@@ -2721,11 +2721,11 @@ def splitFilesIntoBranches(self, commit):
                 relPath = self.stripRepoPath(path, self.depotPaths)
 
             for branch in self.knownBranches.keys():
                 # add a trailing slash so that a commit into qt/4.2foo
                 # doesn't end up in qt/4.2, e.g.
-                if relPath.startswith(branch + "/"):
+                if p4PathStartsWith(relPath, branch + "/"):
                     if branch not in branches:
                         branches[branch] = []
                     branches[branch].append(file)
                     break
 
-- 
2.19.2


^ permalink raw reply related	[relevance 6%]

* Re: AW: fast-import fails with case sensitive tags due to case insensitive lock files
  2019-03-04  7:55  9%   ` AW: " Wendeborn, Jonathan
@ 2019-03-04 15:29  9%     ` Johannes Schindelin
  2019-03-05  6:25 14%       ` AW: " Wendeborn, Jonathan
  0 siblings, 1 reply; 200+ results
From: Johannes Schindelin @ 2019-03-04 15:29 UTC (permalink / raw)
  To: Wendeborn, Jonathan; +Cc: brian m. carlson, git@vger.kernel.org

[-- Attachment #1: Type: text/plain, Size: 1313 bytes --]

Hi Jonathan,

On Mon, 4 Mar 2019, Wendeborn, Jonathan wrote:

> > Right now, you have some choices: 
> > • Volunteer to implement reftable. 
> > • Since you're on Windows 10, set your Git repository directory as 
> >   case-sensitive. 
> > • Use Windows Subsystem for Linux, which is case sensitive and creates 
> >   directories with that flag (even on NTFS), to do your import. 
> > • If you control the fast-export output, adjust the arguments you pass 
> >   such that the output does not contain one of the offending tags. 
> 
> Hi Brian,
> 
> Thank you very much for your answer!
> 
> Unfortunately I am stuck with Windows 10 1703 which neither supports
> case-sensitivity nor any Linux subsystem from the Microsoft Store :(

Too bad.

> Also, my employer unfortunately doesn’t allow me to invest the time to
> implement reftable,

Even worse!

> so I guess I go with manually leaving out the one conflicting label I
> found and tagging it manually afterward.

That is a valid workaround.

> One thing I still would like to encourage to is to improve the error
> message which is really misleading in this case.

Well, that is at least something you could do. After all, you use a
volunteer-driven project, so you could at least volunteer a little time to
improving it yourself.

Ciao,
Johannes

^ permalink raw reply	[relevance 9%]

* AW: fast-import fails with case sensitive tags due to case insensitive lock files
  2019-03-03  0:25 15% ` brian m. carlson
@ 2019-03-04  7:55  9%   ` Wendeborn, Jonathan
  2019-03-04 15:29  9%     ` Johannes Schindelin
  0 siblings, 1 reply; 200+ results
From: Wendeborn, Jonathan @ 2019-03-04  7:55 UTC (permalink / raw)
  To: brian m. carlson; +Cc: git@vger.kernel.org

> Right now, you have some choices: 
> • Volunteer to implement reftable. 
> • Since you're on Windows 10, set your Git repository directory as 
>   case-sensitive. 
> • Use Windows Subsystem for Linux, which is case sensitive and creates 
>   directories with that flag (even on NTFS), to do your import. 
> • If you control the fast-export output, adjust the arguments you pass 
>   such that the output does not contain one of the offending tags. 

Hi Brian,

Thank you very much for your answer!

Unfortunately I am stuck with Windows 10 1703 which neither supports case-sensitivity nor any Linux subsystem from the Microsoft Store :( Also, my employer unfortunately doesn’t allow me to invest the time to implement reftable, so I guess I go with manually leaving out the one conflicting label I found and tagging it manually afterward.

One thing I still would like to encourage to is to improve the error message which is really misleading in this case.

Best regards and thanks again,
Jonathan

^ permalink raw reply	[relevance 9%]

* Re: fast-import fails with case sensitive tags due to case insensitive lock files
  2019-03-01  6:19  8% fast-import fails with case sensitive tags due to case insensitive lock files Wendeborn, Jonathan
@ 2019-03-03  0:25 15% ` brian m. carlson
  2019-03-04  7:55  9%   ` AW: " Wendeborn, Jonathan
  0 siblings, 1 reply; 200+ results
From: brian m. carlson @ 2019-03-03  0:25 UTC (permalink / raw)
  To: Wendeborn, Jonathan; +Cc: git@vger.kernel.org

[-- Attachment #1: Type: text/plain, Size: 2415 bytes --]

On Fri, Mar 01, 2019 at 06:19:48AM +0000, Wendeborn, Jonathan wrote:
> Hi,
> 
> I have a problem with fast-import on an NTFS drive: If I try to import tags which are identical apart from their casing a failure due to identical lock file names occurs.
> 
> I am running git for windows 2.15.1.2 x64 on a Windows 10 machine (10.0.15063):
> $ git --version --build-options
> git version 2.15.1.windows.2
> built from commit: 5d5baf91824ec7750b103c8b7c4827ffac202feb
> sizeof-long: 4
> machine: x86_64
> 
> MCVE:
>  (echo "commit refs/heads/master" && 
>  echo "mark :1" &&
>  echo "committer me <> 0 +0000" &&
>  echo "data 0" &&
>  echo "" &&
>  echo "tag tag_A" &&
>  echo "from :1" &&
>  echo "tagger me <> 0 +0000" &&
>  echo "data 0" &&
>  echo "" &&
>  echo "tag tag_a" &&
>  echo "from :1" &&
>  echo "tagger me <> 0 +0000" &&
>  echo "data 0" &&
>  echo "") | git fast-import
> 
> Instead of having 1 commit with two tags ("tag_A" and "tag_a") I get his error message:
> Unpacking objects: 100% (4/4), done.
> error: cannot lock ref 'refs/tags/tag_a': Unable to create 'C:/tmp/.git/refs/tags/tag_a.lock': File exists.

The reason you're seeing this error is because refs can be stored in the
file system. In order to update a reference, Git takes a lock on it, and
as you've seen, Git can't take a lock on the same reference twice.

It's known that multiple references that differ only in case can't be
stored in a case-insensitive file system, and there is a design for a
different system (reftable) which nobody has yet implemented in Git but
does not have this problem.

Even if we accepted this situation in fast-import, we'd destroy one of
your tags, which would be undesirable.

Sometimes this happens to work because when we pack references, we store
them in a file instead, which does not suffer from case-sensitivity
problems.

Right now, you have some choices:

• Volunteer to implement reftable.
• Since you're on Windows 10, set your Git repository directory as
  case-sensitive.
• Use Windows Subsystem for Linux, which is case sensitive and creates
  directories with that flag (even on NTFS), to do your import.
• If you control the fast-export output, adjust the arguments you pass
  such that the output does not contain one of the offending tags.
-- 
brian m. carlson: Houston, Texas, US
OpenPGP: https://keybase.io/bk2204

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 868 bytes --]

^ permalink raw reply	[relevance 15%]

* fast-import fails with case sensitive tags due to case insensitive lock files
@ 2019-03-01  6:19  8% Wendeborn, Jonathan
  2019-03-03  0:25 15% ` brian m. carlson
  0 siblings, 1 reply; 200+ results
From: Wendeborn, Jonathan @ 2019-03-01  6:19 UTC (permalink / raw)
  To: git@vger.kernel.org

Hi,

I have a problem with fast-import on an NTFS drive: If I try to import tags which are identical apart from their casing a failure due to identical lock file names occurs.

I am running git for windows 2.15.1.2 x64 on a Windows 10 machine (10.0.15063):
$ git --version --build-options
git version 2.15.1.windows.2
built from commit: 5d5baf91824ec7750b103c8b7c4827ffac202feb
sizeof-long: 4
machine: x86_64

MCVE:
 (echo "commit refs/heads/master" && 
 echo "mark :1" &&
 echo "committer me <> 0 +0000" &&
 echo "data 0" &&
 echo "" &&
 echo "tag tag_A" &&
 echo "from :1" &&
 echo "tagger me <> 0 +0000" &&
 echo "data 0" &&
 echo "" &&
 echo "tag tag_a" &&
 echo "from :1" &&
 echo "tagger me <> 0 +0000" &&
 echo "data 0" &&
 echo "") | git fast-import

Instead of having 1 commit with two tags ("tag_A" and "tag_a") I get his error message:
Unpacking objects: 100% (4/4), done.
error: cannot lock ref 'refs/tags/tag_a': Unable to create 'C:/tmp/.git/refs/tags/tag_a.lock': File exists.

Another git process seems to be running in this repository, e.g.
an editor opened by 'git commit'. Please make sure all processes
are terminated then try again. If it still fails, a git process
may have crashed in this repository earlier:
remove the file manually to continue.

Best regards,
Jonathan

^ permalink raw reply	[relevance 8%]

* Re: [PATCH 0/1] de-alphabetize command list
  @ 2019-02-21 18:05  1%   ` frederik
  2019-03-11  9:04  0%     ` frederik
  0 siblings, 1 reply; 200+ results
From: frederik @ 2019-02-21 18:05 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Eric Sunshine, Jonathan Nieder,
	Theodore Y. Ts'o

[-- Attachment #1: Type: text/plain, Size: 2413 bytes --]

I realized that it would probably be easier to discuss this proposal
if I attached the final command listing and the rendered manual page.
Please find them attached to this message.

Thank you,

Frederick

On Tue, Feb 19, 2019 at 09:54:12AM -0800, Frederick Eaton wrote:
>This is a follow-up to my proposal to de-alphabetize the command
>listings in the git(1) manual page, from 6 July 2018.
>
>Some projects have manual page items listed in alphabetical order,
>some don't. As I argued in my proposal, I find it easier to learn from
>material which is not alphabetized. If this patch is accepted, I hope
>that it will make the Git documentation more accessible to myself and
>others.
>
>I produced the reordered command list in this patch using several
>sources, as indicated by comments in the new command-list.txt file.
>First, all the commands in the main part of "gittutorial(7)" appear in
>order, then the commands in giteveryday(7). Then appear additional
>commands from a friend's shell history, in reverse order of frequency.
>Then gittutorial-2(7), then gitcore-tutorial(7). After that there is a
>list of "guides", followed by about 100 commands not appearing in the
>earlier lists. I kept the guides and the remaining commands in their
>category groupings (guide, mainporcelain, ancillarymanipulators,
>etc.), but ordered the commands within each category according to my
>own judgment after skimming each manual page.
>
>To verify that the new list is a permutation of the most recent list,
>I use the following command (it should produce no output and exit 0):
>
>    diff <(git show master:command-list.txt | grep -v '^#' | sort ) <(cat command-list.txt | grep -v '^#' | sort)
>
>Note this patch changes the order of commands appearing in the
>generated file "command-list.h", which mostly seems to be used by
>"help.c". Probably due to the various occurrences of QSORT in
>"help.c", I think this reordering has no visible effect. I am willing
>to do any additional testing which may be recommended to ensure that
>this patch has no undesired consequences.
>
>Frederick Eaton (1):
>  Prioritize list of commands appearing in git(1), via command-list.txt.
>    Don't invoke 'sort' in Documentation/cmd-list.perl.
>
> Documentation/cmd-list.perl |   2 +-
> command-list.txt            | 295 +++++++++++++++++++-----------------
> 2 files changed, 158 insertions(+), 139 deletions(-)
>
>-- 
>2.20.1
>

[-- Attachment #2: reordered-command-list.txt --]
[-- Type: text/plain, Size: 12100 bytes --]

# Command classification list
# ---------------------------
# All supported commands, builtin or external, must be described in
# here. This info is used to list commands in various places. Each
# command is on one line followed by one or more attributes.
#
# The first attribute group is mandatory and indicates the command
# type. This group includes:
#
#   mainporcelain
#   ancillarymanipulators
#   ancillaryinterrogators
#   foreignscminterface
#   plumbingmanipulators
#   plumbinginterrogators
#   synchingrepositories
#   synchelpers
#   purehelpers
#
# The type names are self explanatory. But if you want to see what
# command belongs to what group to get a better picture, have a look
# at "git" man page, "GIT COMMANDS" section.
#
# Commands of type mainporcelain can also optionally have one of these
# attributes:
#
#   init
#   worktree
#   info
#   history
#   remote
#
# These commands are considered "common" and will show up in "git
# help" output in groups. Uncommon porcelain commands must not
# specify any of these attributes.
#
# "complete" attribute is used to mark that the command should be
# completable by git-completion.bash. Note that by default,
# mainporcelain commands are completable so you don't need this
# attribute.
#
# As part of the Git man page list, the man(5/7) guides are also
# specified here, which can only have "guide" attribute and nothing
# else.
#
# February 2019: This list had been sorted alphabetically but has been
# reordered to make it easier for people to learn from the main git(1)
# manual page. The new ordering is according to approximate usefulness
# / frequency of use / order of use, with some grouping by topic. The
# idea is to make it possible to read the manual page from beginning
# to end and see the most important commands first, rather than
# getting them in alphabetical order - in other words, to make the
# manual page more like a table of contents and less like an index.
# Please consider this when adding new commands.
#
### command list (do not change this line, also do not change alignment)
# command name                          category [category] [category]
# From gittutorial
git-help                                ancillaryinterrogators          complete
git-config                              ancillarymanipulators           complete
git-clone                               mainporcelain           init
git-init                                mainporcelain           init
git-add                                 mainporcelain           worktree
git-commit                              mainporcelain           history
git-diff                                mainporcelain           history
git-status                              mainporcelain           info
git-log                                 mainporcelain           info
git-branch                              mainporcelain           history
git-checkout                            mainporcelain           history
git-merge                               mainporcelain           history
gitk                                    mainporcelain
git-pull                                mainporcelain           remote
git-fetch                               mainporcelain           remote
# From tutorial NEXT STEPS
git-format-patch                        mainporcelain
git-bisect                              mainporcelain           info
giteveryday                             guide
gitworkflows                            guide
gitcvs-migration                        guide
# From giteveryday
git-reset                               mainporcelain           worktree
git-rebase                              mainporcelain           history
git-tag                                 mainporcelain           history
git-push                                mainporcelain           remote
git-send-email                          foreignscminterface             complete
git-request-pull                        foreignscminterface             complete
git-am                                  mainporcelain
git-revert                              mainporcelain
git-daemon                              synchingrepositories
git-shell                               synchelpers
git-http-backend                        synchingrepositories
gitweb                                  ancillaryinterrogators
# From user feedback
git-grep                                mainporcelain           info
git-show                                mainporcelain           info
git-submodule                           mainporcelain
git-cherry-pick                         mainporcelain
git-clean                               mainporcelain
# From gittutorial-2
git-cat-file                            plumbinginterrogators
git-ls-tree                             plumbinginterrogators
git-ls-files                            plumbinginterrogators
gitcore-tutorial                        guide
gitglossary                             guide
# From gitcore-tutorial
git-update-index                        plumbingmanipulators
git-diff-files                          plumbinginterrogators
git-write-tree                          plumbingmanipulators
git-read-tree                           plumbingmanipulators
git-checkout-index                      plumbingmanipulators
git-show-branch                         ancillaryinterrogators          complete
git-name-rev                            plumbinginterrogators
git-merge-index                         plumbingmanipulators
git-repack                              ancillarymanipulators           complete
git-prune-packed                        plumbingmanipulators
git-update-server-info                  synchingrepositories
git-prune                               ancillarymanipulators
git-cherry                              plumbinginterrogators          complete
# Guides, reordered
gittutorial                             guide
gittutorial-2                           guide
gitrevisions                            guide
gitignore                               guide
gitcli                                  guide
gitrepository-layout                    guide
gitdiffcore                             guide
gitmodules                              guide
githooks                                guide
gitnamespaces                           guide
gitattributes                           guide
# All other commands, sorted by man page category and then by
# approximate priority
git-stash                               mainporcelain
git-rm                                  mainporcelain           worktree
git-mv                                  mainporcelain           worktree
git-gui                                 mainporcelain
git-citool                              mainporcelain
git-archive                             mainporcelain
git-shortlog                            mainporcelain
git-describe                            mainporcelain
git-gc                                  mainporcelain
git-notes                               mainporcelain
git-worktree                            mainporcelain
git-bundle                              mainporcelain
git-range-diff                          mainporcelain
git-stage                                                               complete
git-reflog                              ancillarymanipulators           complete
git-remote                              ancillarymanipulators           complete
git-mergetool                           ancillarymanipulators           complete
git-filter-branch                       ancillarymanipulators
git-replace                             ancillarymanipulators           complete
git-fast-export                         ancillarymanipulators
git-fast-import                         ancillarymanipulators
git-pack-refs                           ancillarymanipulators
git-cvsimport                           foreignscminterface
git-cvsserver                           foreignscminterface
git-cvsexportcommit                     foreignscminterface
git-svn                                 foreignscminterface
git-p4                                  foreignscminterface
git-quiltimport                         foreignscminterface
git-archimport                          foreignscminterface
git-imap-send                           foreignscminterface
git-apply                               plumbingmanipulators            complete
git-merge-file                          plumbingmanipulators
git-mktag                               plumbingmanipulators
git-hash-object                         plumbingmanipulators
git-update-ref                          plumbingmanipulators
git-symbolic-ref                        plumbingmanipulators
git-commit-tree                         plumbingmanipulators
git-commit-graph                        plumbingmanipulators
git-mktree                              plumbingmanipulators
git-pack-objects                        plumbingmanipulators
git-unpack-objects                      plumbingmanipulators
git-index-pack                          plumbingmanipulators
git-multi-pack-index                    plumbingmanipulators
git-blame                               ancillaryinterrogators          complete
git-annotate                            ancillaryinterrogators
git-instaweb                            ancillaryinterrogators          complete
git-rerere                              ancillaryinterrogators
git-fsck                                ancillaryinterrogators          complete
git-whatchanged                         ancillaryinterrogators          complete
git-difftool                            ancillaryinterrogators          complete
git-merge-tree                          ancillaryinterrogators
git-count-objects                       ancillaryinterrogators
git-verify-commit                       ancillaryinterrogators
git-verify-tag                          ancillaryinterrogators
git-send-pack                           synchingrepositories
git-fetch-pack                          synchingrepositories
git-parse-remote                        synchelpers
git-receive-pack                        synchelpers
git-upload-pack                         synchelpers
git-upload-archive                      synchelpers
git-http-fetch                          synchelpers
git-http-push                           synchelpers
git-var                                 plumbinginterrogators
git-rev-list                            plumbinginterrogators
git-rev-parse                           plumbinginterrogators
git-for-each-ref                        plumbinginterrogators
git-show-ref                            plumbinginterrogators
git-ls-remote                           plumbinginterrogators
git-diff-tree                           plumbinginterrogators
git-diff-index                          plumbinginterrogators
git-merge-base                          plumbinginterrogators
git-verify-pack                         plumbinginterrogators
git-pack-redundant                      plumbinginterrogators
git-unpack-file                         plumbinginterrogators
git-show-index                          plumbinginterrogators
git-get-tar-commit-id                   plumbinginterrogators
git-merge-one-file                      purehelpers
git-sh-setup                            purehelpers
git-check-ref-format                    purehelpers
git-check-ignore                        purehelpers
git-check-attr                          purehelpers
git-credential                          purehelpers
git-credential-cache                    purehelpers
git-credential-store                    purehelpers
git-fmt-merge-msg                       purehelpers
git-check-mailmap                       purehelpers
git-mailsplit                           purehelpers
git-mailinfo                            purehelpers
git-interpret-trailers                  purehelpers
git-column                              purehelpers
git-stripspace                          purehelpers
git-patch-id                            purehelpers
git-sh-i18n                             purehelpers

[-- Attachment #3: reordered-git.1.new --]
[-- Type: text/plain, Size: 46921 bytes --]

GIT(1)                            Git Manual                            GIT(1)

NAME
       git - the stupid content tracker

SYNOPSIS
       git [--version] [--help] [-C <path>] [-c <name>=<value>]
           [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
           [-p|--paginate|-P|--no-pager] [--no-replace-objects] [--bare]
           [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]
           [--super-prefix=<path>]
           <command> [<args>]

DESCRIPTION
       Git is a fast, scalable, distributed revision control system with an
       unusually rich command set that provides both high-level operations and
       full access to internals.

       See gittutorial(7) to get started, then see giteveryday(7) for a useful
       minimum set of commands. The Git User's Manual[1] has a more in-depth
       introduction.

       After you mastered the basic concepts, you can come back to this page
       to learn what commands Git offers. You can learn more about individual
       Git commands with "git help command". gitcli(7) manual page gives you
       an overview of the command-line command syntax.

       A formatted and hyperlinked copy of the latest Git documentation can be
       viewed at https://git.github.io/htmldocs/git.html.

OPTIONS
       --version
           Prints the Git suite version that the git program came from.

       --help
           Prints the synopsis and a list of the most commonly used commands.
           If the option --all or -a is given then all available commands are
           printed. If a Git command is named this option will bring up the
           manual page for that command.

           Other options are available to control how the manual page is
           displayed. See git-help(1) for more information, because git --help
           ...  is converted internally into git help ....

       -C <path>
           Run as if git was started in <path> instead of the current working
           directory. When multiple -C options are given, each subsequent
           non-absolute -C <path> is interpreted relative to the preceding -C
           <path>.

           This option affects options that expect path name like --git-dir
           and --work-tree in that their interpretations of the path names
           would be made relative to the working directory caused by the -C
           option. For example the following invocations are equivalent:

               git --git-dir=a.git --work-tree=b -C c status
               git --git-dir=c/a.git --work-tree=c/b status

       -c <name>=<value>
           Pass a configuration parameter to the command. The value given will
           override values from configuration files. The <name> is expected in
           the same format as listed by git config (subkeys separated by
           dots).

           Note that omitting the = in git -c foo.bar ...  is allowed and sets
           foo.bar to the boolean true value (just like [foo]bar would in a
           config file). Including the equals but with an empty value (like
           git -c foo.bar= ...) sets foo.bar to the empty string which git
           config --type=bool will convert to false.

       --exec-path[=<path>]
           Path to wherever your core Git programs are installed. This can
           also be controlled by setting the GIT_EXEC_PATH environment
           variable. If no path is given, git will print the current setting
           and then exit.

       --html-path
           Print the path, without trailing slash, where Git's HTML
           documentation is installed and exit.

       --man-path
           Print the manpath (see man(1)) for the man pages for this version
           of Git and exit.

       --info-path
           Print the path where the Info files documenting this version of Git
           are installed and exit.

       -p, --paginate
           Pipe all output into less (or if set, $PAGER) if standard output is
           a terminal. This overrides the pager.<cmd> configuration options
           (see the "Configuration Mechanism" section below).

       -P, --no-pager
           Do not pipe Git output into a pager.

       --git-dir=<path>
           Set the path to the repository. This can also be controlled by
           setting the GIT_DIR environment variable. It can be an absolute
           path or relative path to current working directory.

       --work-tree=<path>
           Set the path to the working tree. It can be an absolute path or a
           path relative to the current working directory. This can also be
           controlled by setting the GIT_WORK_TREE environment variable and
           the core.worktree configuration variable (see core.worktree in git-
           config(1) for a more detailed discussion).

       --namespace=<path>
           Set the Git namespace. See gitnamespaces(7) for more details.
           Equivalent to setting the GIT_NAMESPACE environment variable.

       --super-prefix=<path>
           Currently for internal use only. Set a prefix which gives a path
           from above a repository down to its root. One use is to give
           submodules context about the superproject that invoked it.

       --bare
           Treat the repository as a bare repository. If GIT_DIR environment
           is not set, it is set to the current working directory.

       --no-replace-objects
           Do not use replacement refs to replace Git objects. See git-
           replace(1) for more information.

       --literal-pathspecs
           Treat pathspecs literally (i.e. no globbing, no pathspec magic).
           This is equivalent to setting the GIT_LITERAL_PATHSPECS environment
           variable to 1.

       --glob-pathspecs
           Add "glob" magic to all pathspec. This is equivalent to setting the
           GIT_GLOB_PATHSPECS environment variable to 1. Disabling globbing on
           individual pathspecs can be done using pathspec magic ":(literal)"

       --noglob-pathspecs
           Add "literal" magic to all pathspec. This is equivalent to setting
           the GIT_NOGLOB_PATHSPECS environment variable to 1. Enabling
           globbing on individual pathspecs can be done using pathspec magic
           ":(glob)"

       --icase-pathspecs
           Add "icase" magic to all pathspec. This is equivalent to setting
           the GIT_ICASE_PATHSPECS environment variable to 1.

       --no-optional-locks
           Do not perform optional operations that require locks. This is
           equivalent to setting the GIT_OPTIONAL_LOCKS to 0.

       --list-cmds=group[,group...]
           List commands by group. This is an internal/experimental option and
           may change or be removed in the future. Supported groups are:
           builtins, parseopt (builtin commands that use parse-options), main
           (all commands in libexec directory), others (all other commands in
           $PATH that have git- prefix), list-<category> (see categories in
           command-list.txt), nohelpers (exclude helper commands), alias and
           config (retrieve command list from config variable
           completion.commands)

GIT COMMANDS
       We divide Git into high level ("porcelain") commands and low level
       ("plumbing") commands.

HIGH-LEVEL COMMANDS (PORCELAIN)
       We separate the porcelain commands into the main commands and some
       ancillary user utilities.

   Main porcelain commands
       git-clone(1)
           Clone a repository into a new directory.

       git-init(1)
           Create an empty Git repository or reinitialize an existing one.

       git-add(1)
           Add file contents to the index.

       git-commit(1)
           Record changes to the repository.

       git-diff(1)
           Show changes between commits, commit and working tree, etc.

       git-status(1)
           Show the working tree status.

       git-log(1)
           Show commit logs.

       git-branch(1)
           List, create, or delete branches.

       git-checkout(1)
           Switch branches or restore working tree files.

       git-merge(1)
           Join two or more development histories together.

       gitk(1)
           The Git repository browser.

       git-pull(1)
           Fetch from and integrate with another repository or a local branch.

       git-fetch(1)
           Download objects and refs from another repository.

       git-format-patch(1)
           Prepare patches for e-mail submission.

       git-bisect(1)
           Use binary search to find the commit that introduced a bug.

       git-reset(1)
           Reset current HEAD to the specified state.

       git-rebase(1)
           Reapply commits on top of another base tip.

       git-tag(1)
           Create, list, delete or verify a tag object signed with GPG.

       git-push(1)
           Update remote refs along with associated objects.

       git-am(1)
           Apply a series of patches from a mailbox.

       git-revert(1)
           Revert some existing commits.

       git-grep(1)
           Print lines matching a pattern.

       git-show(1)
           Show various types of objects.

       git-submodule(1)
           Initialize, update or inspect submodules.

       git-cherry-pick(1)
           Apply the changes introduced by some existing commits.

       git-clean(1)
           Remove untracked files from the working tree.

       git-stash(1)
           Stash the changes in a dirty working directory away.

       git-rm(1)
           Remove files from the working tree and from the index.

       git-mv(1)
           Move or rename a file, a directory, or a symlink.

       git-gui(1)
           A portable graphical interface to Git.

       git-citool(1)
           Graphical alternative to git-commit.

       git-archive(1)
           Create an archive of files from a named tree.

       git-shortlog(1)
           Summarize git log output.

       git-describe(1)
           Give an object a human readable name based on an available ref.

       git-gc(1)
           Cleanup unnecessary files and optimize the local repository.

       git-notes(1)
           Add or inspect object notes.

       git-worktree(1)
           Manage multiple working trees.

       git-bundle(1)
           Move objects and refs by archive.

       git-range-diff(1)
           Compare two commit ranges (e.g. two versions of a branch).

   Ancillary Commands
       Manipulators:

       git-config(1)
           Get and set repository or global options.

       git-repack(1)
           Pack unpacked objects in a repository.

       git-prune(1)
           Prune all unreachable objects from the object database.

       git-reflog(1)
           Manage reflog information.

       git-remote(1)
           Manage set of tracked repositories.

       git-mergetool(1)
           Run merge conflict resolution tools to resolve merge conflicts.

       git-filter-branch(1)
           Rewrite branches.

       git-replace(1)
           Create, list, delete refs to replace objects.

       git-fast-export(1)
           Git data exporter.

       git-fast-import(1)
           Backend for fast Git data importers.

       git-pack-refs(1)
           Pack heads and tags for efficient repository access.

       Interrogators:

       git-help(1)
           Display help information about Git.

       gitweb(1)
           Git web interface (web frontend to Git repositories).

       git-show-branch(1)
           Show branches and their commits.

       git-blame(1)
           Show what revision and author last modified each line of a file.

       git-annotate(1)
           Annotate file lines with commit information.

       git-instaweb(1)
           Instantly browse your working repository in gitweb.

       git-rerere(1)
           Reuse recorded resolution of conflicted merges.

       git-fsck(1)
           Verifies the connectivity and validity of the objects in the
           database.

       git-whatchanged(1)
           Show logs with difference each commit introduces.

       git-difftool(1)
           Show changes using common diff tools.

       git-merge-tree(1)
           Show three-way merge without touching index.

       git-count-objects(1)
           Count unpacked number of objects and their disk consumption.

       git-verify-commit(1)
           Check the GPG signature of commits.

       git-verify-tag(1)
           Check the GPG signature of tags.

   Interacting with Others
       These commands are to interact with foreign SCM and with other people
       via patch over e-mail.

       git-send-email(1)
           Send a collection of patches as emails.

       git-request-pull(1)
           Generates a summary of pending changes.

       git-cvsimport(1)
           Salvage your data out of another SCM people love to hate.

       git-cvsserver(1)
           A CVS server emulator for Git.

       git-cvsexportcommit(1)
           Export a single commit to a CVS checkout.

       git-svn(1)
           Bidirectional operation between a Subversion repository and Git.

       git-p4(1)
           Import from and submit to Perforce repositories.

       git-quiltimport(1)
           Applies a quilt patchset onto the current branch.

       git-archimport(1)
           Import a GNU Arch repository into Git.

       git-imap-send(1)
           Send a collection of patches from stdin to an IMAP folder.

LOW-LEVEL COMMANDS (PLUMBING)
       Although Git includes its own porcelain layer, its low-level commands
       are sufficient to support development of alternative porcelains.
       Developers of such porcelains might start by reading about git-update-
       index(1) and git-read-tree(1).

       The interface (input, output, set of options and the semantics) to
       these low-level commands are meant to be a lot more stable than
       Porcelain level commands, because these commands are primarily for
       scripted use. The interface to Porcelain commands on the other hand are
       subject to change in order to improve the end user experience.

       The following description divides the low-level commands into commands
       that manipulate objects (in the repository, index, and working tree),
       commands that interrogate and compare objects, and commands that move
       objects and references between repositories.

   Manipulation commands
       git-update-index(1)
           Register file contents in the working tree to the index.

       git-write-tree(1)
           Create a tree object from the current index.

       git-read-tree(1)
           Reads tree information into the index.

       git-checkout-index(1)
           Copy files from the index to the working tree.

       git-merge-index(1)
           Run a merge for files needing merging.

       git-prune-packed(1)
           Remove extra objects that are already in pack files.

       git-apply(1)
           Apply a patch to files and/or to the index.

       git-merge-file(1)
           Run a three-way file merge.

       git-mktag(1)
           Creates a tag object.

       git-hash-object(1)
           Compute object ID and optionally creates a blob from a file.

       git-update-ref(1)
           Update the object name stored in a ref safely.

       git-symbolic-ref(1)
           Read, modify and delete symbolic refs.

       git-commit-tree(1)
           Create a new commit object.

       git-commit-graph(1)
           Write and verify Git commit-graph files.

       git-mktree(1)
           Build a tree-object from ls-tree formatted text.

       git-pack-objects(1)
           Create a packed archive of objects.

       git-unpack-objects(1)
           Unpack objects from a packed archive.

       git-index-pack(1)
           Build pack index file for an existing packed archive.

       git-multi-pack-index(1)
           Write and verify multi-pack-indexes.

   Interrogation commands
       git-cat-file(1)
           Provide content or type and size information for repository
           objects.

       git-ls-tree(1)
           List the contents of a tree object.

       git-ls-files(1)
           Show information about files in the index and the working tree.

       git-diff-files(1)
           Compares files in the working tree and the index.

       git-name-rev(1)
           Find symbolic names for given revs.

       git-cherry(1)
           Find commits yet to be applied to upstream.

       git-var(1)
           Show a Git logical variable.

       git-rev-list(1)
           Lists commit objects in reverse chronological order.

       git-rev-parse(1)
           Pick out and massage parameters.

       git-for-each-ref(1)
           Output information on each ref.

       git-show-ref(1)
           List references in a local repository.

       git-ls-remote(1)
           List references in a remote repository.

       git-diff-tree(1)
           Compares the content and mode of blobs found via two tree objects.

       git-diff-index(1)
           Compare a tree to the working tree or index.

       git-merge-base(1)
           Find as good common ancestors as possible for a merge.

       git-verify-pack(1)
           Validate packed Git archive files.

       git-pack-redundant(1)
           Find redundant pack files.

       git-unpack-file(1)
           Creates a temporary file with a blob's contents.

       git-show-index(1)
           Show packed archive index.

       git-get-tar-commit-id(1)
           Extract commit ID from an archive created using git-archive.

       In general, the interrogate commands do not touch the files in the
       working tree.

   Synching repositories
       git-daemon(1)
           A really simple server for Git repositories.

       git-http-backend(1)
           Server side implementation of Git over HTTP.

       git-update-server-info(1)
           Update auxiliary info file to help dumb servers.

       git-send-pack(1)
           Push objects over Git protocol to another repository.

       git-fetch-pack(1)
           Receive missing objects from another repository.

       The following are helper commands used by the above; end users
       typically do not use them directly.

       git-shell(1)
           Restricted login shell for Git-only SSH access.

       git-parse-remote(1)
           Routines to help parsing remote repository access parameters.

       git-receive-pack(1)
           Receive what is pushed into the repository.

       git-upload-pack(1)
           Send objects packed back to git-fetch-pack.

       git-upload-archive(1)
           Send archive back to git-archive.

       git-http-fetch(1)
           Download from a remote Git repository via HTTP.

       git-http-push(1)
           Push objects over HTTP/DAV to another repository.

   Internal helper commands
       These are internal helper commands used by other commands; end users
       typically do not use them directly.

       git-merge-one-file(1)
           The standard helper program to use with git-merge-index.

       git-sh-setup(1)
           Common Git shell script setup code.

       git-check-ref-format(1)
           Ensures that a reference name is well formed.

       git-check-ignore(1)
           Debug gitignore / exclude files.

       git-check-attr(1)
           Display gitattributes information.

       git-credential(1)
           Retrieve and store user credentials.

       git-credential-cache(1)
           Helper to temporarily store passwords in memory.

       git-credential-store(1)
           Helper to store credentials on disk.

       git-fmt-merge-msg(1)
           Produce a merge commit message.

       git-check-mailmap(1)
           Show canonical names and email addresses of contacts.

       git-mailsplit(1)
           Simple UNIX mbox splitter program.

       git-mailinfo(1)
           Extracts patch and authorship from a single e-mail message.

       git-interpret-trailers(1)
           add or parse structured information in commit messages.

       git-column(1)
           Display data in columns.

       git-stripspace(1)
           Remove unnecessary whitespace.

       git-patch-id(1)
           Compute unique ID for a patch.

       git-sh-i18n(1)
           Git's i18n setup code for shell scripts.

CONFIGURATION MECHANISM
       Git uses a simple text format to store customizations that are per
       repository and are per user. Such a configuration file may look like
       this:

           #
           # A '#' or ';' character indicates a comment.
           #

           ; core variables
           [core]
                   ; Don't trust file modes
                   filemode = false

           ; user identity
           [user]
                   name = "Junio C Hamano"
                   email = "gitster@pobox.com"

       Various commands read from the configuration file and adjust their
       operation accordingly. See git-config(1) for a list and more details
       about the configuration mechanism.

IDENTIFIER TERMINOLOGY
       <object>
           Indicates the object name for any type of object.

       <blob>
           Indicates a blob object name.

       <tree>
           Indicates a tree object name.

       <commit>
           Indicates a commit object name.

       <tree-ish>
           Indicates a tree, commit or tag object name. A command that takes a
           <tree-ish> argument ultimately wants to operate on a <tree> object
           but automatically dereferences <commit> and <tag> objects that
           point at a <tree>.

       <commit-ish>
           Indicates a commit or tag object name. A command that takes a
           <commit-ish> argument ultimately wants to operate on a <commit>
           object but automatically dereferences <tag> objects that point at a
           <commit>.

       <type>
           Indicates that an object type is required. Currently one of: blob,
           tree, commit, or tag.

       <file>
           Indicates a filename - almost always relative to the root of the
           tree structure GIT_INDEX_FILE describes.

SYMBOLIC IDENTIFIERS
       Any Git command accepting any <object> can also use the following
       symbolic notation:

       HEAD
           indicates the head of the current branch.

       <tag>
           a valid tag name (i.e. a refs/tags/<tag> reference).

       <head>
           a valid head name (i.e. a refs/heads/<head> reference).

       For a more complete list of ways to spell object names, see "SPECIFYING
       REVISIONS" section in gitrevisions(7).

FILE/DIRECTORY STRUCTURE
       Please see the gitrepository-layout(5) document.

       Read githooks(5) for more details about each hook.

       Higher level SCMs may provide and manage additional information in the
       $GIT_DIR.

TERMINOLOGY
       Please see gitglossary(7).

ENVIRONMENT VARIABLES
       Various Git commands use the following environment variables:

   The Git Repository
       These environment variables apply to all core Git commands. Nb: it is
       worth noting that they may be used/overridden by SCMS sitting above Git
       so take care if using a foreign front-end.

       GIT_INDEX_FILE
           This environment allows the specification of an alternate index
           file. If not specified, the default of $GIT_DIR/index is used.

       GIT_INDEX_VERSION
           This environment variable allows the specification of an index
           version for new repositories. It won't affect existing index files.
           By default index file version 2 or 3 is used. See git-update-
           index(1) for more information.

       GIT_OBJECT_DIRECTORY
           If the object storage directory is specified via this environment
           variable then the sha1 directories are created underneath -
           otherwise the default $GIT_DIR/objects directory is used.

       GIT_ALTERNATE_OBJECT_DIRECTORIES
           Due to the immutable nature of Git objects, old objects can be
           archived into shared, read-only directories. This variable
           specifies a ":" separated (on Windows ";" separated) list of Git
           object directories which can be used to search for Git objects. New
           objects will not be written to these directories.

           Entries that begin with " (double-quote) will be interpreted as
           C-style quoted paths, removing leading and trailing double-quotes
           and respecting backslash escapes. E.g., the value
           "path-with-\"-and-:-in-it":vanilla-path has two paths:
           path-with-"-and-:-in-it and vanilla-path.

       GIT_DIR
           If the GIT_DIR environment variable is set then it specifies a path
           to use instead of the default .git for the base of the repository.
           The --git-dir command-line option also sets this value.

       GIT_WORK_TREE
           Set the path to the root of the working tree. This can also be
           controlled by the --work-tree command-line option and the
           core.worktree configuration variable.

       GIT_NAMESPACE
           Set the Git namespace; see gitnamespaces(7) for details. The
           --namespace command-line option also sets this value.

       GIT_CEILING_DIRECTORIES
           This should be a colon-separated list of absolute paths. If set, it
           is a list of directories that Git should not chdir up into while
           looking for a repository directory (useful for excluding
           slow-loading network directories). It will not exclude the current
           working directory or a GIT_DIR set on the command line or in the
           environment. Normally, Git has to read the entries in this list and
           resolve any symlink that might be present in order to compare them
           with the current directory. However, if even this access is slow,
           you can add an empty entry to the list to tell Git that the
           subsequent entries are not symlinks and needn't be resolved; e.g.,
           GIT_CEILING_DIRECTORIES=/maybe/symlink::/very/slow/non/symlink.

       GIT_DISCOVERY_ACROSS_FILESYSTEM
           When run in a directory that does not have ".git" repository
           directory, Git tries to find such a directory in the parent
           directories to find the top of the working tree, but by default it
           does not cross filesystem boundaries. This environment variable can
           be set to true to tell Git not to stop at filesystem boundaries.
           Like GIT_CEILING_DIRECTORIES, this will not affect an explicit
           repository directory set via GIT_DIR or on the command line.

       GIT_COMMON_DIR
           If this variable is set to a path, non-worktree files that are
           normally in $GIT_DIR will be taken from this path instead.
           Worktree-specific files such as HEAD or index are taken from
           $GIT_DIR. See gitrepository-layout(5) and git-worktree(1) for
           details. This variable has lower precedence than other path
           variables such as GIT_INDEX_FILE, GIT_OBJECT_DIRECTORY...

   Git Commits
       GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, GIT_AUTHOR_DATE, GIT_COMMITTER_NAME,
       GIT_COMMITTER_EMAIL, GIT_COMMITTER_DATE, EMAIL
           see git-commit-tree(1)

   Git Diffs
       GIT_DIFF_OPTS
           Only valid setting is "--unified=??" or "-u??" to set the number of
           context lines shown when a unified diff is created. This takes
           precedence over any "-U" or "--unified" option value passed on the
           Git diff command line.

       GIT_EXTERNAL_DIFF
           When the environment variable GIT_EXTERNAL_DIFF is set, the program
           named by it is called, instead of the diff invocation described
           above. For a path that is added, removed, or modified,
           GIT_EXTERNAL_DIFF is called with 7 parameters:

               path old-file old-hex old-mode new-file new-hex new-mode

           where:

       <old|new>-file
           are files GIT_EXTERNAL_DIFF can use to read the contents of
           <old|new>,

       <old|new>-hex
           are the 40-hexdigit SHA-1 hashes,

       <old|new>-mode
           are the octal representation of the file modes.

           The file parameters can point at the user's working file (e.g.
           new-file in "git-diff-files"), /dev/null (e.g.  old-file when a new
           file is added), or a temporary file (e.g.  old-file in the index).
           GIT_EXTERNAL_DIFF should not worry about unlinking the temporary
           file --- it is removed when GIT_EXTERNAL_DIFF exits.

           For a path that is unmerged, GIT_EXTERNAL_DIFF is called with 1
           parameter, <path>.

           For each path GIT_EXTERNAL_DIFF is called, two environment
           variables, GIT_DIFF_PATH_COUNTER and GIT_DIFF_PATH_TOTAL are set.

       GIT_DIFF_PATH_COUNTER
           A 1-based counter incremented by one for every path.

       GIT_DIFF_PATH_TOTAL
           The total number of paths.

   other
       GIT_MERGE_VERBOSITY
           A number controlling the amount of output shown by the recursive
           merge strategy. Overrides merge.verbosity. See git-merge(1)

       GIT_PAGER
           This environment variable overrides $PAGER. If it is set to an
           empty string or to the value "cat", Git will not launch a pager.
           See also the core.pager option in git-config(1).

       GIT_EDITOR
           This environment variable overrides $EDITOR and $VISUAL. It is used
           by several Git commands when, on interactive mode, an editor is to
           be launched. See also git-var(1) and the core.editor option in git-
           config(1).

       GIT_SSH, GIT_SSH_COMMAND
           If either of these environment variables is set then git fetch and
           git push will use the specified command instead of ssh when they
           need to connect to a remote system. The command-line parameters
           passed to the configured command are determined by the ssh variant.
           See ssh.variant option in git-config(1) for details.

       + $GIT_SSH_COMMAND takes precedence over $GIT_SSH, and is interpreted
       by the shell, which allows additional arguments to be included.
       $GIT_SSH on the other hand must be just the path to a program (which
       can be a wrapper shell script, if additional arguments are needed).

       + Usually it is easier to configure any desired options through your
       personal .ssh/config file. Please consult your ssh documentation for
       further details.

       GIT_SSH_VARIANT
           If this environment variable is set, it overrides Git's
           autodetection whether GIT_SSH/GIT_SSH_COMMAND/core.sshCommand refer
           to OpenSSH, plink or tortoiseplink. This variable overrides the
           config setting ssh.variant that serves the same purpose.

       GIT_ASKPASS
           If this environment variable is set, then Git commands which need
           to acquire passwords or passphrases (e.g. for HTTP or IMAP
           authentication) will call this program with a suitable prompt as
           command-line argument and read the password from its STDOUT. See
           also the core.askPass option in git-config(1).

       GIT_TERMINAL_PROMPT
           If this environment variable is set to 0, git will not prompt on
           the terminal (e.g., when asking for HTTP authentication).

       GIT_CONFIG_NOSYSTEM
           Whether to skip reading settings from the system-wide
           $(prefix)/etc/gitconfig file. This environment variable can be used
           along with $HOME and $XDG_CONFIG_HOME to create a predictable
           environment for a picky script, or you can set it temporarily to
           avoid using a buggy /etc/gitconfig file while waiting for someone
           with sufficient permissions to fix it.

       GIT_FLUSH
           If this environment variable is set to "1", then commands such as
           git blame (in incremental mode), git rev-list, git log, git
           check-attr and git check-ignore will force a flush of the output
           stream after each record have been flushed. If this variable is set
           to "0", the output of these commands will be done using completely
           buffered I/O. If this environment variable is not set, Git will
           choose buffered or record-oriented flushing based on whether stdout
           appears to be redirected to a file or not.

       GIT_TRACE
           Enables general trace messages, e.g. alias expansion, built-in
           command execution and external command execution.

           If this variable is set to "1", "2" or "true" (comparison is case
           insensitive), trace messages will be printed to stderr.

           If the variable is set to an integer value greater than 2 and lower
           than 10 (strictly) then Git will interpret this value as an open
           file descriptor and will try to write the trace messages into this
           file descriptor.

           Alternatively, if the variable is set to an absolute path (starting
           with a / character), Git will interpret this as a file path and
           will try to append the trace messages to it.

           Unsetting the variable, or setting it to empty, "0" or "false"
           (case insensitive) disables trace messages.

       GIT_TRACE_FSMONITOR
           Enables trace messages for the filesystem monitor extension. See
           GIT_TRACE for available trace output options.

       GIT_TRACE_PACK_ACCESS
           Enables trace messages for all accesses to any packs. For each
           access, the pack file name and an offset in the pack is recorded.
           This may be helpful for troubleshooting some pack-related
           performance problems. See GIT_TRACE for available trace output
           options.

       GIT_TRACE_PACKET
           Enables trace messages for all packets coming in or out of a given
           program. This can help with debugging object negotiation or other
           protocol issues. Tracing is turned off at a packet starting with
           "PACK" (but see GIT_TRACE_PACKFILE below). See GIT_TRACE for
           available trace output options.

       GIT_TRACE_PACKFILE
           Enables tracing of packfiles sent or received by a given program.
           Unlike other trace output, this trace is verbatim: no headers, and
           no quoting of binary data. You almost certainly want to direct into
           a file (e.g., GIT_TRACE_PACKFILE=/tmp/my.pack) rather than
           displaying it on the terminal or mixing it with other trace output.

           Note that this is currently only implemented for the client side of
           clones and fetches.

       GIT_TRACE_PERFORMANCE
           Enables performance related trace messages, e.g. total execution
           time of each Git command. See GIT_TRACE for available trace output
           options.

       GIT_TRACE_SETUP
           Enables trace messages printing the .git, working tree and current
           working directory after Git has completed its setup phase. See
           GIT_TRACE for available trace output options.

       GIT_TRACE_SHALLOW
           Enables trace messages that can help debugging fetching / cloning
           of shallow repositories. See GIT_TRACE for available trace output
           options.

       GIT_TRACE_CURL
           Enables a curl full trace dump of all incoming and outgoing data,
           including descriptive information, of the git transport protocol.
           This is similar to doing curl --trace-ascii on the command line.
           This option overrides setting the GIT_CURL_VERBOSE environment
           variable. See GIT_TRACE for available trace output options.

       GIT_TRACE_CURL_NO_DATA
           When a curl trace is enabled (see GIT_TRACE_CURL above), do not
           dump data (that is, only dump info lines and headers).

       GIT_REDACT_COOKIES
           This can be set to a comma-separated list of strings. When a curl
           trace is enabled (see GIT_TRACE_CURL above), whenever a "Cookies:"
           header sent by the client is dumped, values of cookies whose key is
           in that list (case-sensitive) are redacted.

       GIT_LITERAL_PATHSPECS
           Setting this variable to 1 will cause Git to treat all pathspecs
           literally, rather than as glob patterns. For example, running
           GIT_LITERAL_PATHSPECS=1 git log -- '*.c' will search for commits
           that touch the path *.c, not any paths that the glob *.c matches.
           You might want this if you are feeding literal paths to Git (e.g.,
           paths previously given to you by git ls-tree, --raw diff output,
           etc).

       GIT_GLOB_PATHSPECS
           Setting this variable to 1 will cause Git to treat all pathspecs as
           glob patterns (aka "glob" magic).

       GIT_NOGLOB_PATHSPECS
           Setting this variable to 1 will cause Git to treat all pathspecs as
           literal (aka "literal" magic).

       GIT_ICASE_PATHSPECS
           Setting this variable to 1 will cause Git to treat all pathspecs as
           case-insensitive.

       GIT_REFLOG_ACTION
           When a ref is updated, reflog entries are created to keep track of
           the reason why the ref was updated (which is typically the name of
           the high-level command that updated the ref), in addition to the
           old and new values of the ref. A scripted Porcelain command can use
           set_reflog_action helper function in git-sh-setup to set its name
           to this variable when it is invoked as the top level command by the
           end user, to be recorded in the body of the reflog.

       GIT_REF_PARANOIA
           If set to 1, include broken or badly named refs when iterating over
           lists of refs. In a normal, non-corrupted repository, this does
           nothing. However, enabling it may help git to detect and abort some
           operations in the presence of broken refs. Git sets this variable
           automatically when performing destructive operations like git-
           prune(1). You should not need to set it yourself unless you want to
           be paranoid about making sure an operation has touched every ref
           (e.g., because you are cloning a repository to make a backup).

       GIT_ALLOW_PROTOCOL
           If set to a colon-separated list of protocols, behave as if
           protocol.allow is set to never, and each of the listed protocols
           has protocol.<name>.allow set to always (overriding any existing
           configuration). In other words, any protocol not mentioned will be
           disallowed (i.e., this is a whitelist, not a blacklist). See the
           description of protocol.allow in git-config(1) for more details.

       GIT_PROTOCOL_FROM_USER
           Set to 0 to prevent protocols used by fetch/push/clone which are
           configured to the user state. This is useful to restrict recursive
           submodule initialization from an untrusted repository or for
           programs which feed potentially-untrusted URLS to git commands. See
           git-config(1) for more details.

       GIT_PROTOCOL
           For internal use only. Used in handshaking the wire protocol.
           Contains a colon : separated list of keys with optional values
           key[=value]. Presence of unknown keys and values must be ignored.

       GIT_OPTIONAL_LOCKS
           If set to 0, Git will complete any requested operation without
           performing any optional sub-operations that require taking a lock.
           For example, this will prevent git status from refreshing the index
           as a side effect. This is useful for processes running in the
           background which do not want to cause lock contention with other
           operations on the repository. Defaults to 1.

       GIT_REDIRECT_STDIN, GIT_REDIRECT_STDOUT, GIT_REDIRECT_STDERR
           Windows-only: allow redirecting the standard input/output/error
           handles to paths specified by the environment variables. This is
           particularly useful in multi-threaded applications where the
           canonical way to pass standard handles via CreateProcess() is not
           an option because it would require the handles to be marked
           inheritable (and consequently every spawned process would inherit
           them, possibly blocking regular Git operations). The primary
           intended use case is to use named pipes for communication (e.g.
           \\.\pipe\my-git-stdin-123).

           Two special values are supported: off will simply close the
           corresponding standard handle, and if GIT_REDIRECT_STDERR is 2>&1,
           standard error will be redirected to the same handle as standard
           output.

       GIT_PRINT_SHA1_ELLIPSIS (deprecated)
           If set to yes, print an ellipsis following an (abbreviated) SHA-1
           value. This affects indications of detached HEADs (git-checkout(1))
           and the raw diff output (git-diff(1)). Printing an ellipsis in the
           cases mentioned is no longer considered adequate and support for it
           is likely to be removed in the foreseeable future (along with the
           variable).

DISCUSSION
       More detail on the following is available from the Git concepts chapter
       of the user-manual[2] and gitcore-tutorial(7).

       A Git project normally consists of a working directory with a ".git"
       subdirectory at the top level. The .git directory contains, among other
       things, a compressed object database representing the complete history
       of the project, an "index" file which links that history to the current
       contents of the working tree, and named pointers into that history such
       as tags and branch heads.

       The object database contains objects of three main types: blobs, which
       hold file data; trees, which point to blobs and other trees to build up
       directory hierarchies; and commits, which each reference a single tree
       and some number of parent commits.

       The commit, equivalent to what other systems call a "changeset" or
       "version", represents a step in the project's history, and each parent
       represents an immediately preceding step. Commits with more than one
       parent represent merges of independent lines of development.

       All objects are named by the SHA-1 hash of their contents, normally
       written as a string of 40 hex digits. Such names are globally unique.
       The entire history leading up to a commit can be vouched for by signing
       just that commit. A fourth object type, the tag, is provided for this
       purpose.

       When first created, objects are stored in individual files, but for
       efficiency may later be compressed together into "pack files".

       Named pointers called refs mark interesting points in history. A ref
       may contain the SHA-1 name of an object or the name of another ref.
       Refs with names beginning ref/head/ contain the SHA-1 name of the most
       recent commit (or "head") of a branch under development. SHA-1 names of
       tags of interest are stored under ref/tags/. A special ref named HEAD
       contains the name of the currently checked-out branch.

       The index file is initialized with a list of all paths and, for each
       path, a blob object and a set of attributes. The blob object represents
       the contents of the file as of the head of the current branch. The
       attributes (last modified time, size, etc.) are taken from the
       corresponding file in the working tree. Subsequent changes to the
       working tree can be found by comparing these attributes. The index may
       be updated with new content, and new commits may be created from the
       content stored in the index.

       The index is also capable of storing multiple entries (called "stages")
       for a given pathname. These stages are used to hold the various
       unmerged version of a file when a merge is in progress.

FURTHER DOCUMENTATION
       See the references in the "description" section to get started using
       Git. The following is probably more detail than necessary for a
       first-time user.

       The Git concepts chapter of the user-manual[2] and gitcore-tutorial(7)
       both provide introductions to the underlying Git architecture.

       See gitworkflows(7) for an overview of recommended workflows.

       See also the howto[3] documents for some useful examples.

       The internals are documented in the Git API documentation[4].

       Users migrating from CVS may also want to read gitcvs-migration(7).

AUTHORS
       Git was started by Linus Torvalds, and is currently maintained by Junio
       C Hamano. Numerous contributions have come from the Git mailing list
       <git@vger.kernel.org[5]>.
       http://www.openhub.net/p/git/contributors/summary gives you a more
       complete list of contributors.

       If you have a clone of git.git itself, the output of git-shortlog(1)
       and git-blame(1) can show you the authors for specific parts of the
       project.

REPORTING BUGS
       Report bugs to the Git mailing list <git@vger.kernel.org[5]> where the
       development and maintenance is primarily done. You do not have to be
       subscribed to the list to send a message there. See the list archive at
       https://public-inbox.org/git for previous bug reports and other
       discussions.

       Issues which are security relevant should be disclosed privately to the
       Git Security mailing list <git-security@googlegroups.com[6]>.

SEE ALSO
       gittutorial(7), gittutorial-2(7), giteveryday(7), gitcvs-migration(7),
       gitglossary(7), gitcore-tutorial(7), gitcli(7), The Git User's
       Manual[1], gitworkflows(7)

GIT
       Part of the git(1) suite

NOTES
        1. Git User's Manual
           file:///home/frederik/share/doc/git-doc/user-manual.html

        2. Git concepts chapter of the user-manual
           file:///home/frederik/share/doc/git-doc/user-manual.html#git-concepts

        3. howto
           file:///home/frederik/share/doc/git-doc/howto-index.html

        4. Git API documentation
           file:///home/frederik/share/doc/git-doc/technical/api-index.html

        5. git@vger.kernel.org
           mailto:git@vger.kernel.org

        6. git-security@googlegroups.com
           mailto:git-security@googlegroups.com

Git 2.21.0.rc1.9.g3f              02/18/2019                            GIT(1)

^ permalink raw reply	[relevance 1%]

* Re: Possible minor bug in Git
  @ 2019-02-08 17:43  0% ` Torsten Bögershausen
  0 siblings, 0 replies; 200+ results
From: Torsten Bögershausen @ 2019-02-08 17:43 UTC (permalink / raw)
  To: Giuseppe Crinò; +Cc: johannes.schindelin, angelomelonas, git

On Fri, Feb 08, 2019 at 04:18:23PM +0100, Giuseppe Crinò wrote:
> OK, I successfully built git on Windows (thanks Johannes!) and I'm now able to run it.
>
> As of 9f16cdd I can successfully reproduce the bug.
>
> Interestingly enough, I can reproduce the bug even for /usr/bin/git running inside Windows Subsystem for Linux. Part of the reason might be that both relies on the same lstat() call... (Note: `stat` inside the WSL is case _insensitive_).
>
> Now: what is the expected result for git running inside Windows? Should it die saying "fatal: pathspec ... did not match any files"?
>
> If that's the case, is the following a valid test case?
>
> 	diff --git a/t/t3700-add.sh b/t/t3700-add.sh
> 	index 8ee4fc70ad..fadd7c74f6 100755
> 	--- a/t/t3700-add.sh
> 	+++ b/t/t3700-add.sh
> 	@@ -61,6 +61,11 @@ test_expect_success 'git add: filemode=0 should not get confused by symlink' '
> 		test_mode_in_index 120000 xfoo2
> 	 '
>
> 	+test_expect_success 'git add: pathspec is case-sensitive' '
> 	+       echo new > file &&
> 	+       test_must_fail git add File
> 	+'
> 	+

In general, yes.
There are 2 comments:
This the "echo" line should have no ' ' after the '>':

	echo new >file &&

The other question is,
if we should move that test case into t0050-filesystem.sh,
but that is a matter of taste.

diff --git a/t/t0050-filesystem.sh b/t/t0050-filesystem.sh
index 192c94eccd..b8d6bad97a 100755
--- a/t/t0050-filesystem.sh
+++ b/t/t0050-filesystem.sh
@@ -106,6 +106,11 @@ test_expect_failure CASE_INSENSITIVE_FS 'add (with different case)' '
        test "z$(git cat-file blob :$camel)" = z1
	 '

+test_expect_success CASE_INSENSITIVE_FS 'add (with wrong case)' '
+       git reset --hard initial &&
+       test_must_fail git add CAMELCASE
+'
+


^ permalink raw reply related	[relevance 0%]

* Re: [PATCH v3 1/1] abspath_part_inside_repo: respect core.fileMode
  @ 2018-12-25  3:06  5%     ` Junio C Hamano
  0 siblings, 0 replies; 200+ results
From: Junio C Hamano @ 2018-12-25  3:06 UTC (permalink / raw)
  To: Johannes Schindelin via GitGitGadget; +Cc: git, Johannes Schindelin

"Johannes Schindelin via GitGitGadget" <gitgitgadget@gmail.com>
writes:

> diff --git a/setup.c b/setup.c
> index 1be5037f12..291bfb2128 100644
> --- a/setup.c
> +++ b/setup.c
> @@ -39,7 +39,7 @@ static int abspath_part_inside_repo(char *path)
>  	off = offset_1st_component(path);
>  
>  	/* check if work tree is already the prefix */
> -	if (wtlen <= len && !strncmp(path, work_tree, wtlen)) {
> +	if (wtlen <= len && !fspathncmp(path, work_tree, wtlen)) {
>  		if (path[wtlen] == '/') {
>  			memmove(path, path + wtlen + 1, len - wtlen);
>  			return 0;
> @@ -59,7 +59,7 @@ static int abspath_part_inside_repo(char *path)
>  		path++;
>  		if (*path == '/') {
>  			*path = '\0';
> -			if (strcmp(real_path(path0), work_tree) == 0) {
> +			if (fspathcmp(real_path(path0), work_tree) == 0) {
>  				memmove(path0, path + 1, len - (path - path0));
>  				return 0;
>  			}
> @@ -68,7 +68,7 @@ static int abspath_part_inside_repo(char *path)
>  	}
>  
>  	/* check whole path */
> -	if (strcmp(real_path(path0), work_tree) == 0) {
> +	if (fspathcmp(real_path(path0), work_tree) == 0) {
>  		*path0 = '\0';
>  		return 0;
>  	}

So the idea is that the path to the top level of the working tree
must be compared with fspath[n]cmp() to what was given.  After
stripping that prefix, the caller uses the result just like it uses
a non-absolute path, which is why the necessary changes are isolated
to this function.

Makes sense.

> diff --git a/t/t3700-add.sh b/t/t3700-add.sh
> index 37729ba258..be582a513b 100755
> --- a/t/t3700-add.sh
> +++ b/t/t3700-add.sh
> @@ -402,4 +402,11 @@ test_expect_success 'all statuses changed in folder if . is given' '
>  	test $(git ls-files --stage | grep ^100755 | wc -l) -eq 0
>  '
>  
> +test_expect_success CASE_INSENSITIVE_FS 'path is case-insensitive' '
> +	path="$(pwd)/BLUB" &&
> +	touch "$path" &&
> +	downcased="$(echo "$path" | tr A-Z a-z)" &&
> +	git add "$downcased"
> +'

One problem with the above test is that it leaves it unspecified if
the resulting index entry is "blub" or "BLUB".  Shouldn't we verify
that "git add" adds an expected path to the index, instead of
blindly trusting that it says "Yeah, I did as I was told" with its
exit status?  Would we be adding 'blub' as that is what we told
'git' to add, or would it be 'BLUB' as that is what exists on the
filesystem that is case insensitive but case preserving?

On a project whose participants all are on case insensitive
filesystems, the above does not matter by definition, but once a
project wants to work with their case sensitive friends, it starts
to matter.

Other than that, looks good to me.

Thanks.


> +
>  test_done

^ permalink raw reply	[relevance 5%]

* Re: [PATCH 1/1] worktree refs: fix case sensitivity for 'head'
  2018-12-14 17:38  0%                   ` Duy Nguyen
@ 2018-12-14 18:47  0%                     ` Jacob Keller
  0 siblings, 0 replies; 200+ results
From: Jacob Keller @ 2018-12-14 18:47 UTC (permalink / raw)
  To: Duy Nguyen
  Cc: Mike Rappazzo, Stefan Beller, gitgitgadget, Git mailing list,
	Junio C Hamano

On Fri, Dec 14, 2018 at 9:38 AM Duy Nguyen <pclouds@gmail.com> wrote:
>
> On Fri, Dec 14, 2018 at 6:22 PM Jacob Keller <jacob.keller@gmail.com> wrote:
> >
> > On Thu, Dec 13, 2018 at 11:38 PM Duy Nguyen <pclouds@gmail.com> wrote:
> > > Even with a new ref storage, I'm pretty sure pseudo refs like HEAD,
> > > FETCH_HEAD... will forever be backed by filesystem. HEAD for example
> > > is part of the repository signature and must exist as a file. We could
> > > also lookup pseudo refs with readdir() instead of lstat(). On
> > > case-preserving-and-insensitive filesystems, we can reject "head" this
> > > way. But that comes with a high cost.
> > > --
> > > Duy
> >
> > Once other refs are backed by something that doesn't depend on
> > filesystem case sensitivity, you could enforce that we only accept
> > call-caps HEAD as a psuedo ref, and always look up other spellings in
> > the other refs backend, though, right?
>
> Hmm.. yes. I don't know off hand if we have any pseudo refs in
> lowercase. Unlikely so yes this should work.
>

I think even if we had lowercase pseudo refs, as long as we know which
identifiers represent pseudo refs, and we don't have two variants
which match if compared case insensitively, we shouldn't have
ambiguity, since we'd distinguish whether to check a pseudo ref spot
before we actually check the file system.

> > So, yea the actual file may not
> > be case sensitive, but we would never create refs/head anymore for any
> > reason, so there would be no ambiguity if reading the refs/head vs
> > refs/HEAD on a case insensitive file system, since refs/head would no
> > longer be a legitimate ref stored as a file if you used a different
> > refs backend.
> >
> > Basically, we'd be looking up HEAD by checking the file, but we'd stop
> > looking up head, hEAd, etc in the files, and instead use whatever
> > other refs backend for non-pseudo refs. Thus, it wouldn't matter,
> > since we'd never actually lookup the other spellings of HEAD as a
> > file. Wouldn't that solve the ambiguity, at least once a repository
> > has fully switched to some alternative refs backend for non-pseudo
> > refs? (Unless I mis-understand and refs/head could be an added pseudo
> > ref?)
>
> No I think "pseudo refs" are those outside "refs" directory only. So
> "refs/head" would be a "normal" ref.
>

Right, I was a bit confused pre-coffee and forgot why a ref was a pseudo ref.

> > Jake
>
>
>
> --
> Duy

^ permalink raw reply	[relevance 0%]

* Re: [PATCH 1/1] worktree refs: fix case sensitivity for 'head'
  2018-12-14 17:22  5%                 ` Jacob Keller
@ 2018-12-14 17:38  0%                   ` Duy Nguyen
  2018-12-14 18:47  0%                     ` Jacob Keller
  0 siblings, 1 reply; 200+ results
From: Duy Nguyen @ 2018-12-14 17:38 UTC (permalink / raw)
  To: Jacob Keller
  Cc: Mike Rappazzo, Stefan Beller, gitgitgadget, Git Mailing List,
	Junio C Hamano

On Fri, Dec 14, 2018 at 6:22 PM Jacob Keller <jacob.keller@gmail.com> wrote:
>
> On Thu, Dec 13, 2018 at 11:38 PM Duy Nguyen <pclouds@gmail.com> wrote:
> > Even with a new ref storage, I'm pretty sure pseudo refs like HEAD,
> > FETCH_HEAD... will forever be backed by filesystem. HEAD for example
> > is part of the repository signature and must exist as a file. We could
> > also lookup pseudo refs with readdir() instead of lstat(). On
> > case-preserving-and-insensitive filesystems, we can reject "head" this
> > way. But that comes with a high cost.
> > --
> > Duy
>
> Once other refs are backed by something that doesn't depend on
> filesystem case sensitivity, you could enforce that we only accept
> call-caps HEAD as a psuedo ref, and always look up other spellings in
> the other refs backend, though, right?

Hmm.. yes. I don't know off hand if we have any pseudo refs in
lowercase. Unlikely so yes this should work.

> So, yea the actual file may not
> be case sensitive, but we would never create refs/head anymore for any
> reason, so there would be no ambiguity if reading the refs/head vs
> refs/HEAD on a case insensitive file system, since refs/head would no
> longer be a legitimate ref stored as a file if you used a different
> refs backend.
>
> Basically, we'd be looking up HEAD by checking the file, but we'd stop
> looking up head, hEAd, etc in the files, and instead use whatever
> other refs backend for non-pseudo refs. Thus, it wouldn't matter,
> since we'd never actually lookup the other spellings of HEAD as a
> file. Wouldn't that solve the ambiguity, at least once a repository
> has fully switched to some alternative refs backend for non-pseudo
> refs? (Unless I mis-understand and refs/head could be an added pseudo
> ref?)

No I think "pseudo refs" are those outside "refs" directory only. So
"refs/head" would be a "normal" ref.

> Jake



-- 
Duy

^ permalink raw reply	[relevance 0%]

* Re: [PATCH 1/1] worktree refs: fix case sensitivity for 'head'
  2018-12-14  7:37  0%               ` Duy Nguyen
@ 2018-12-14 17:22  5%                 ` Jacob Keller
  2018-12-14 17:38  0%                   ` Duy Nguyen
  0 siblings, 1 reply; 200+ results
From: Jacob Keller @ 2018-12-14 17:22 UTC (permalink / raw)
  To: Duy Nguyen
  Cc: Mike Rappazzo, Stefan Beller, gitgitgadget, Git mailing list,
	Junio C Hamano

On Thu, Dec 13, 2018 at 11:38 PM Duy Nguyen <pclouds@gmail.com> wrote:
> Even with a new ref storage, I'm pretty sure pseudo refs like HEAD,
> FETCH_HEAD... will forever be backed by filesystem. HEAD for example
> is part of the repository signature and must exist as a file. We could
> also lookup pseudo refs with readdir() instead of lstat(). On
> case-preserving-and-insensitive filesystems, we can reject "head" this
> way. But that comes with a high cost.
> --
> Duy

Once other refs are backed by something that doesn't depend on
filesystem case sensitivity, you could enforce that we only accept
call-caps HEAD as a psuedo ref, and always look up other spellings in
the other refs backend, though, right? So, yea the actual file may not
be case sensitive, but we would never create refs/head anymore for any
reason, so there would be no ambiguity if reading the refs/head vs
refs/HEAD on a case insensitive file system, since refs/head would no
longer be a legitimate ref stored as a file if you used a different
refs backend.

Basically, we'd be looking up HEAD by checking the file, but we'd stop
looking up head, hEAd, etc in the files, and instead use whatever
other refs backend for non-pseudo refs. Thus, it wouldn't matter,
since we'd never actually lookup the other spellings of HEAD as a
file. Wouldn't that solve the ambiguity, at least once a repository
has fully switched to some alternative refs backend for non-pseudo
refs? (Unless I mis-understand and refs/head could be an added pseudo
ref?)

Jake

^ permalink raw reply	[relevance 5%]

* Re: [PATCH 1/1] worktree refs: fix case sensitivity for 'head'
  2018-12-14  6:49  5%             ` Jacob Keller
@ 2018-12-14  7:37  0%               ` Duy Nguyen
  2018-12-14 17:22  5%                 ` Jacob Keller
  0 siblings, 1 reply; 200+ results
From: Duy Nguyen @ 2018-12-14  7:37 UTC (permalink / raw)
  To: Jacob Keller
  Cc: Mike Rappazzo, Stefan Beller, gitgitgadget, Git Mailing List,
	Junio C Hamano

On Fri, Dec 14, 2018 at 7:50 AM Jacob Keller <jacob.keller@gmail.com> wrote:
>
> On Thu, Dec 13, 2018 at 1:16 PM Mike Rappazzo <rappazzo@gmail.com> wrote:
> >
> > On Thu, Dec 13, 2018 at 3:48 PM Stefan Beller <sbeller@google.com> wrote:
> > >
> > > > > The current situation is definitely a problem.  If I am in a worktree,
> > > > > using "head" should be the same as "HEAD".
> > >
> > > By any chance, is your file system case insensitive?
> > > That is usually the source of confusion for these discussions.
> >
> > This behavior is the same for MacOS (High Sierra) and Windows 7.  I
> > assume other derivatives of those act the same.
> >
> > On CentOS "head" is an ambiguous ref.  If Windows and Mac resulted in
> > an ambiguous ref, that would also be OK, but as it is now, they return
> > the result of "HEAD" on the primary worktree.
> >
>
> Because refs are *not* case sensitive, and we know that "HEAD" should
> be per-worktree, it gets checked in the per-worktree refs section. But
> lowercase head is known to not be a per-worktree ref, so we then ask
> the main worktree about head. Since you happen to be on a case
> insensitive file system, it then finds refs/HEAD in the main refs
> worktree, and returns that.
>
> I don't understand why the CentOS shows it as ambiguous, unless you
> actually happen to have a ref named head. (possibly a branch?)

I think it's just our default answer when we can't decide

$ git rev-parse head
head
fatal: ambiguous argument 'head': unknown revision or path not in the
working tree.
Use '--' to separate paths from revisions, like this:
'git <command> [<revision>...] -- [<file>...]'
$ git rev-parse head --
fatal: bad revision 'head'

> I suspect we could improve things by attempting to figure out if our
> file system is case insensitive and warn users. However, I recall
> patches which tried this, and no suitable method was found. Partly
> because it's not just "case" that is the only problem. There might be
> things like unicode characters which don't get properly encoded, etc.
>
> The best solution would be to get a non-filesystem backed ref storage
> working that could be used in place of the filesystem.

Even with a new ref storage, I'm pretty sure pseudo refs like HEAD,
FETCH_HEAD... will forever be backed by filesystem. HEAD for example
is part of the repository signature and must exist as a file. We could
also lookup pseudo refs with readdir() instead of lstat(). On
case-preserving-and-insensitive filesystems, we can reject "head" this
way. But that comes with a high cost.
-- 
Duy

^ permalink raw reply	[relevance 0%]

* Re: [PATCH 1/1] worktree refs: fix case sensitivity for 'head'
  @ 2018-12-14  6:49  5%             ` Jacob Keller
  2018-12-14  7:37  0%               ` Duy Nguyen
  0 siblings, 1 reply; 200+ results
From: Jacob Keller @ 2018-12-14  6:49 UTC (permalink / raw)
  To: Mike Rappazzo
  Cc: Stefan Beller, Nguyễn Thái Ngọc, gitgitgadget,
	Git List, Junio C Hamano

On Thu, Dec 13, 2018 at 1:16 PM Mike Rappazzo <rappazzo@gmail.com> wrote:
>
> On Thu, Dec 13, 2018 at 3:48 PM Stefan Beller <sbeller@google.com> wrote:
> >
> > > > The current situation is definitely a problem.  If I am in a worktree,
> > > > using "head" should be the same as "HEAD".
> >
> > By any chance, is your file system case insensitive?
> > That is usually the source of confusion for these discussions.
>
> This behavior is the same for MacOS (High Sierra) and Windows 7.  I
> assume other derivatives of those act the same.
>
> On CentOS "head" is an ambiguous ref.  If Windows and Mac resulted in
> an ambiguous ref, that would also be OK, but as it is now, they return
> the result of "HEAD" on the primary worktree.
>

Because refs are *not* case sensitive, and we know that "HEAD" should
be per-worktree, it gets checked in the per-worktree refs section. But
lowercase head is known to not be a per-worktree ref, so we then ask
the main worktree about head. Since you happen to be on a case
insensitive file system, it then finds refs/HEAD in the main refs
worktree, and returns that.

I don't understand why the CentOS shows it as ambiguous, unless you
actually happen to have a ref named head. (possibly a branch?)

I suspect we could improve things by attempting to figure out if our
file system is case insensitive and warn users. However, I recall
patches which tried this, and no suitable method was found. Partly
because it's not just "case" that is the only problem. There might be
things like unicode characters which don't get properly encoded, etc.

The best solution would be to get a non-filesystem backed ref storage
working that could be used in place of the filesystem.

Thanks,
Jake

> >
> > Maybe in worktree code we have a spillover between path
> > resolution and ref namespace?

^ permalink raw reply	[relevance 5%]

* Re: [PATCH 1/1] worktree refs: fix case sensitivity for 'head'
  2018-12-13 20:43  5%       ` Duy Nguyen
    2018-12-13 21:07  0%         ` Mike Rappazzo
@ 2018-12-14  3:31  0%         ` Junio C Hamano
  2 siblings, 0 replies; 200+ results
From: Junio C Hamano @ 2018-12-14  3:31 UTC (permalink / raw)
  To: Duy Nguyen; +Cc: Mike Rappazzo, gitgitgadget, Git Mailing List

Duy Nguyen <pclouds@gmail.com> writes:

> If you make "head" work like "HEAD", then it should work for _all_
> commands, not just worktree, and "MASTER" should match
> "refs/heads/master" and so on. I don't think it's as simple as
> changing strcmp to strcasecmp. You would need to make ref management
> case-insensitive (and make sure if still is case-sensitive if
> configured so). I don't think anybody has managed that.

And it is unclear why anybody would even want to do so.

Thanks for a doze of sanity.

^ permalink raw reply	[relevance 0%]

* Re: [PATCH 1/1] worktree refs: fix case sensitivity for 'head'
  2018-12-13 20:43  5%       ` Duy Nguyen
  @ 2018-12-13 21:07  0%         ` Mike Rappazzo
  2018-12-14  3:31  0%         ` Junio C Hamano
  2 siblings, 0 replies; 200+ results
From: Mike Rappazzo @ 2018-12-13 21:07 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc; +Cc: gitgitgadget, Git List, Junio C Hamano

On Thu, Dec 13, 2018 at 3:43 PM Duy Nguyen <pclouds@gmail.com> wrote:
>
> On Thu, Dec 13, 2018 at 9:34 PM Mike Rappazzo <rappazzo@gmail.com> wrote:
> >
> > On Thu, Dec 13, 2018 at 3:23 PM Duy Nguyen <pclouds@gmail.com> wrote:
> > >
> > > On Thu, Dec 13, 2018 at 8:56 PM Michael Rappazzo via GitGitGadget
> > > <gitgitgadget@gmail.com> wrote:
> > > >
> > > > From: Michael Rappazzo <rappazzo@gmail.com>
> > > >
> > > > On a worktree which is not the primary, using the symbolic-ref 'head' was
> > > > incorrectly pointing to the main worktree's HEAD.  The same was true for
> > > > any other case of the word 'Head'.
> > > >
> > > > Signed-off-by: Michael Rappazzo <rappazzo@gmail.com>
> > > > ---
> > > >  refs.c                   | 8 ++++----
> > > >  t/t1415-worktree-refs.sh | 9 +++++++++
> > > >  2 files changed, 13 insertions(+), 4 deletions(-)
> > > >
> > > > diff --git a/refs.c b/refs.c
> > > > index f9936355cd..963e786458 100644
> > > > --- a/refs.c
> > > > +++ b/refs.c
> > > > @@ -579,7 +579,7 @@ int expand_ref(const char *str, int len, struct object_id *oid, char **ref)
> > > >                                 *ref = xstrdup(r);
> > > >                         if (!warn_ambiguous_refs)
> > > >                                 break;
> > > > -               } else if ((flag & REF_ISSYMREF) && strcmp(fullref.buf, "HEAD")) {
> > > > +               } else if ((flag & REF_ISSYMREF) && strcasecmp(fullref.buf, "HEAD")) {
> > >
> > > This is not going to work. How about ~40 other "strcmp.*HEAD"
> > > instances? All refs are case-sensitive and this probably will not
> > > change even when we introduce new ref backends.
> >
> > The current situation is definitely a problem.  If I am in a worktree,
> > using "head" should be the same as "HEAD".
>
> No "head" is not the same as "HEAD". It does not matter if you're in a
> worktree or not.

I was not aware of a difference.  Is that spelled out in the docs
somewhere?  It seems like a bad idea to have a magical symbolic ref
that _sometimes_ gives you a different answer depending on casing.
What should "head" do in a worktree?  Is it supposed to mean the HEAD
of the primary worktree?

>
> > I am not sure if you mean that the fix is too narrow or too wide.
> > Maybe it is only necessary in 'is_per_worktree_ref'.  On the other
> > side of the coin, I could change every strcmp to strcasecmp where the
> > comparison is against "HEAD".
>
> If you make "head" work like "HEAD", then it should work for _all_
> commands, not just worktree, and "MASTER" should match
> "refs/heads/master" and so on. I don't think it's as simple as
> changing strcmp to strcasecmp. You would need to make ref management
> case-insensitive (and make sure if still is case-sensitive if
> configured so). I don't think anybody has managed that.

I am all for making "head" work in all cases, not just worktree.  I
don't think that this situation applies to non-magical refs
(branches/tags).

> --
> Duy

^ permalink raw reply	[relevance 0%]

* Re: [PATCH 1/1] worktree refs: fix case sensitivity for 'head'
  2018-12-13 20:34  0%     ` Mike Rappazzo
@ 2018-12-13 20:43  5%       ` Duy Nguyen
                             ` (2 more replies)
  0 siblings, 3 replies; 200+ results
From: Duy Nguyen @ 2018-12-13 20:43 UTC (permalink / raw)
  To: Mike Rappazzo; +Cc: gitgitgadget, Git Mailing List, Junio C Hamano

On Thu, Dec 13, 2018 at 9:34 PM Mike Rappazzo <rappazzo@gmail.com> wrote:
>
> On Thu, Dec 13, 2018 at 3:23 PM Duy Nguyen <pclouds@gmail.com> wrote:
> >
> > On Thu, Dec 13, 2018 at 8:56 PM Michael Rappazzo via GitGitGadget
> > <gitgitgadget@gmail.com> wrote:
> > >
> > > From: Michael Rappazzo <rappazzo@gmail.com>
> > >
> > > On a worktree which is not the primary, using the symbolic-ref 'head' was
> > > incorrectly pointing to the main worktree's HEAD.  The same was true for
> > > any other case of the word 'Head'.
> > >
> > > Signed-off-by: Michael Rappazzo <rappazzo@gmail.com>
> > > ---
> > >  refs.c                   | 8 ++++----
> > >  t/t1415-worktree-refs.sh | 9 +++++++++
> > >  2 files changed, 13 insertions(+), 4 deletions(-)
> > >
> > > diff --git a/refs.c b/refs.c
> > > index f9936355cd..963e786458 100644
> > > --- a/refs.c
> > > +++ b/refs.c
> > > @@ -579,7 +579,7 @@ int expand_ref(const char *str, int len, struct object_id *oid, char **ref)
> > >                                 *ref = xstrdup(r);
> > >                         if (!warn_ambiguous_refs)
> > >                                 break;
> > > -               } else if ((flag & REF_ISSYMREF) && strcmp(fullref.buf, "HEAD")) {
> > > +               } else if ((flag & REF_ISSYMREF) && strcasecmp(fullref.buf, "HEAD")) {
> >
> > This is not going to work. How about ~40 other "strcmp.*HEAD"
> > instances? All refs are case-sensitive and this probably will not
> > change even when we introduce new ref backends.
>
> The current situation is definitely a problem.  If I am in a worktree,
> using "head" should be the same as "HEAD".

No "head" is not the same as "HEAD". It does not matter if you're in a
worktree or not.

> I am not sure if you mean that the fix is too narrow or too wide.
> Maybe it is only necessary in 'is_per_worktree_ref'.  On the other
> side of the coin, I could change every strcmp to strcasecmp where the
> comparison is against "HEAD".

If you make "head" work like "HEAD", then it should work for _all_
commands, not just worktree, and "MASTER" should match
"refs/heads/master" and so on. I don't think it's as simple as
changing strcmp to strcasecmp. You would need to make ref management
case-insensitive (and make sure if still is case-sensitive if
configured so). I don't think anybody has managed that.
-- 
Duy

^ permalink raw reply	[relevance 5%]

* Re: [PATCH 1/1] worktree refs: fix case sensitivity for 'head'
  2018-12-13 20:23  5%   ` Duy Nguyen
@ 2018-12-13 20:34  0%     ` Mike Rappazzo
  2018-12-13 20:43  5%       ` Duy Nguyen
  0 siblings, 1 reply; 200+ results
From: Mike Rappazzo @ 2018-12-13 20:34 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc; +Cc: gitgitgadget, Git List, Junio C Hamano

On Thu, Dec 13, 2018 at 3:23 PM Duy Nguyen <pclouds@gmail.com> wrote:
>
> On Thu, Dec 13, 2018 at 8:56 PM Michael Rappazzo via GitGitGadget
> <gitgitgadget@gmail.com> wrote:
> >
> > From: Michael Rappazzo <rappazzo@gmail.com>
> >
> > On a worktree which is not the primary, using the symbolic-ref 'head' was
> > incorrectly pointing to the main worktree's HEAD.  The same was true for
> > any other case of the word 'Head'.
> >
> > Signed-off-by: Michael Rappazzo <rappazzo@gmail.com>
> > ---
> >  refs.c                   | 8 ++++----
> >  t/t1415-worktree-refs.sh | 9 +++++++++
> >  2 files changed, 13 insertions(+), 4 deletions(-)
> >
> > diff --git a/refs.c b/refs.c
> > index f9936355cd..963e786458 100644
> > --- a/refs.c
> > +++ b/refs.c
> > @@ -579,7 +579,7 @@ int expand_ref(const char *str, int len, struct object_id *oid, char **ref)
> >                                 *ref = xstrdup(r);
> >                         if (!warn_ambiguous_refs)
> >                                 break;
> > -               } else if ((flag & REF_ISSYMREF) && strcmp(fullref.buf, "HEAD")) {
> > +               } else if ((flag & REF_ISSYMREF) && strcasecmp(fullref.buf, "HEAD")) {
>
> This is not going to work. How about ~40 other "strcmp.*HEAD"
> instances? All refs are case-sensitive and this probably will not
> change even when we introduce new ref backends.

The current situation is definitely a problem.  If I am in a worktree,
using "head" should be the same as "HEAD".

I am not sure if you mean that the fix is too narrow or too wide.
Maybe it is only necessary in 'is_per_worktree_ref'.  On the other
side of the coin, I could change every strcmp to strcasecmp where the
comparison is against "HEAD".


>
> >                         warning(_("ignoring dangling symref %s"), fullref.buf);
> >                 } else if ((flag & REF_ISBROKEN) && strchr(fullref.buf, '/')) {
> >                         warning(_("ignoring broken ref %s"), fullref.buf);
> --
> Duy

^ permalink raw reply	[relevance 0%]

* Re: [PATCH 1/1] worktree refs: fix case sensitivity for 'head'
  @ 2018-12-13 20:23  5%   ` Duy Nguyen
  2018-12-13 20:34  0%     ` Mike Rappazzo
  0 siblings, 1 reply; 200+ results
From: Duy Nguyen @ 2018-12-13 20:23 UTC (permalink / raw)
  To: gitgitgadget; +Cc: Git Mailing List, Junio C Hamano, Mike Rappazzo

On Thu, Dec 13, 2018 at 8:56 PM Michael Rappazzo via GitGitGadget
<gitgitgadget@gmail.com> wrote:
>
> From: Michael Rappazzo <rappazzo@gmail.com>
>
> On a worktree which is not the primary, using the symbolic-ref 'head' was
> incorrectly pointing to the main worktree's HEAD.  The same was true for
> any other case of the word 'Head'.
>
> Signed-off-by: Michael Rappazzo <rappazzo@gmail.com>
> ---
>  refs.c                   | 8 ++++----
>  t/t1415-worktree-refs.sh | 9 +++++++++
>  2 files changed, 13 insertions(+), 4 deletions(-)
>
> diff --git a/refs.c b/refs.c
> index f9936355cd..963e786458 100644
> --- a/refs.c
> +++ b/refs.c
> @@ -579,7 +579,7 @@ int expand_ref(const char *str, int len, struct object_id *oid, char **ref)
>                                 *ref = xstrdup(r);
>                         if (!warn_ambiguous_refs)
>                                 break;
> -               } else if ((flag & REF_ISSYMREF) && strcmp(fullref.buf, "HEAD")) {
> +               } else if ((flag & REF_ISSYMREF) && strcasecmp(fullref.buf, "HEAD")) {

This is not going to work. How about ~40 other "strcmp.*HEAD"
instances? All refs are case-sensitive and this probably will not
change even when we introduce new ref backends.

>                         warning(_("ignoring dangling symref %s"), fullref.buf);
>                 } else if ((flag & REF_ISBROKEN) && strchr(fullref.buf, '/')) {
>                         warning(_("ignoring broken ref %s"), fullref.buf);
-- 
Duy

^ permalink raw reply	[relevance 5%]

* [PATCH v2] l10n: update German translation
  2018-11-30 17:35  2% [PATCH] l10n: update German translation Ralf Thielow
@ 2018-12-04  6:54  1% ` Ralf Thielow
  0 siblings, 0 replies; 200+ results
From: Ralf Thielow @ 2018-12-04  6:54 UTC (permalink / raw)
  To: git; +Cc: Matthias Rüster, Phillip Szelat, Ralf Thielow

Signed-off-by: Ralf Thielow <ralf.thielow@gmail.com>
---
v2 updates the translation up to the latest update of git.pot.

range-diff:
1:  f0a6c76bf ! 1:  f8313495e l10n: update German translation
    @@ -205,13 +205,13 @@
     -msgstr ""
     +msgstr "Falsche Reihenfolge bei multi-pack-index Pack-Namen: '%s' vor '%s'"
      
      #: midx.c:205
      #, c-format
    - msgid "bad pack-int-id: %u (%u total packs"
    + msgid "bad pack-int-id: %u (%u total packs)"
     -msgstr ""
    -+msgstr "Fehlerhafte pack-int-id: %u (%u Pakete insgesamt)"
    ++msgstr "Ungültige pack-int-id: %u (%u Pakete insgesamt)"
      
      #: midx.c:246
      msgid "multi-pack-index stores a 64-bit offset, but off_t is too small"
     -msgstr ""
     +msgstr "multi-pack-index speichert einen 64-Bit Offset, aber off_t ist zu klein."
    @@ -364,31 +364,31 @@
     +#, c-format
      msgid "unable to join load_cache_entries thread: %s"
     -msgstr "kann Thread nicht erzeugen: %s"
     +msgstr "Kann Thread für load_cache_entries nicht erzeugen: %s"
      
    - #: read-cache.c:2200
    + #: read-cache.c:2201
     -#, fuzzy, c-format
     +#, c-format
      msgid "unable to create load_index_extensions thread: %s"
     -msgstr "kann Thread nicht erzeugen: %s"
     +msgstr "Kann Thread für load_index_extensions nicht erzeugen: %s"
      
    - #: read-cache.c:2227
    + #: read-cache.c:2228
     -#, fuzzy, c-format
     +#, c-format
      msgid "unable to join load_index_extensions thread: %s"
     -msgstr "kann Thread nicht erzeugen: %s"
    -+msgstr "Kann Thread für load_index_extensions nicht erzeugen: %s"
    ++msgstr "Kann Thread für load_index_extensions nicht beitreten: %s"
      
    - #: read-cache.c:2953 sequencer.c:4727 wrapper.c:658 builtin/merge.c:1086
    + #: read-cache.c:2982 sequencer.c:4727 wrapper.c:658 builtin/merge.c:1086
      #, c-format
      msgid "could not close '%s'"
     -msgstr "Konnte '%s' nicht schließen"
     +msgstr "Konnte '%s' nicht schließen."
      
    - #: read-cache.c:3026 sequencer.c:2203 sequencer.c:3592
    + #: read-cache.c:3055 sequencer.c:2203 sequencer.c:3592
      #, c-format
     @@
      msgstr "Konnte '%s' nicht entfernen."
      
      #: rebase-interactive.c:10
    @@ -802,14 +802,19 @@
      
      #: builtin/grep.c:1051
     -#, fuzzy
      msgid "invalid option combination, ignoring --threads"
     -msgstr "keine Unterstützung von Threads, --threads wird ignoriert"
    -+msgstr "ungültige Kombination von Optionen, --threads wird ignoriert"
    ++msgstr "Ungültige Kombination von Optionen, --threads wird ignoriert."
      
    - #: builtin/grep.c:1054 builtin/pack-objects.c:3395
    + #: builtin/grep.c:1054 builtin/pack-objects.c:3397
      msgid "no threads support, ignoring --threads"
    +-msgstr "keine Unterstützung von Threads, --threads wird ignoriert"
    ++msgstr "Keine Unterstützung für Threads, --threads wird ignoriert."
    + 
    + #: builtin/grep.c:1057 builtin/index-pack.c:1503 builtin/pack-objects.c:2716
    + #, c-format
     @@
      msgstr "Für '%s' wurde der Alias '%s' angelegt."
      
      #: builtin/help.c:444
     -#, fuzzy, c-format
    @@ -944,17 +949,17 @@
      #: builtin/pack-objects.c:2123
      msgid "suboptimal pack - out of memory"
     @@
      "packen"
      
    - #: builtin/pack-objects.c:3316
    + #: builtin/pack-objects.c:3318
     -#, fuzzy
      msgid "respect islands during delta compression"
     -msgstr "Größe des Fensters für die Delta-Kompression"
     +msgstr "Delta-Islands bei Delta-Kompression beachten"
      
    - #: builtin/pack-objects.c:3340
    + #: builtin/pack-objects.c:3342
      #, c-format
     @@
      "wurde nicht angefordert."
      
      #: builtin/pull.c:565
    @@ -964,10 +969,28 @@
     -msgstr "Konnte Commit '%s' nicht parsen."
     +msgstr "Konnte nicht auf Commit '%s' zugreifen."
      
      #: builtin/pull.c:843
      msgid "ignoring --verify-signatures for rebase"
    +@@
    + "config'."
    + 
    + #: builtin/push.c:168
    +-#, fuzzy, c-format
    ++#, c-format
    + msgid ""
    + "The upstream branch of your current branch does not match\n"
    + "the name of your current branch.  To push to the upstream branch\n"
    +@@
    + "Um auf den Branch mit demselben Namen im Remote-Repository zu versenden,\n"
    + "benutzen Sie:\n"
    + "\n"
    +-"    git push %s %s\n"
    ++"    git push %s HEAD\n"
    + "%s"
    + 
    + #: builtin/push.c:183
     @@
      msgstr "unpack-trees protokollieren"
      
      #: builtin/rebase.c:29
     -#, fuzzy
    @@ -1041,11 +1064,11 @@
     -#, fuzzy
      msgid "could not determine HEAD revision"
     -msgstr "Konnte HEAD nicht loslösen"
     +msgstr "Konnte HEAD-Commit nicht bestimmen."
      
    - #: builtin/rebase.c:752
    + #: builtin/rebase.c:753
     -#, fuzzy, c-format
     +#, c-format
      msgid ""
      "%s\n"
      "Please specify which branch you want to rebase against.\n"
    @@ -1060,11 +1083,11 @@
     +"Siehe git-rebase(1) für Details.\n"
     +"\n"
     +"    git rebase '<Branch>'\n"
     +"\n"
      
    - #: builtin/rebase.c:768
    + #: builtin/rebase.c:769
     -#, fuzzy, c-format
     +#, c-format
      msgid ""
      "If you wish to set tracking information for this branch you can do so with:\n"
      "\n"
    @@ -1078,212 +1101,212 @@
     +"können Sie dies tun mit:\n"
     +"\n"
     +"    git branch --set-upstream-to=%s/<Branch> %s\n"
     +"\n"
      
    - #: builtin/rebase.c:814
    + #: builtin/rebase.c:832
     -#, fuzzy
      msgid "rebase onto given branch instead of upstream"
     -msgstr "Branch %s kann nicht sein eigener Upstream-Branch sein."
     +msgstr "Rebase auf angegebenen Branch anstelle des Upstream-Branches ausführen"
      
    - #: builtin/rebase.c:816
    + #: builtin/rebase.c:834
     -#, fuzzy
      msgid "allow pre-rebase hook to run"
     -msgstr "Der \"pre-rebase hook\" hat den Rebase zurückgewiesen."
     +msgstr "Ausführung des pre-rebase-Hooks erlauben"
      
    - #: builtin/rebase.c:818
    + #: builtin/rebase.c:836
      msgid "be quiet. implies --no-stat"
     -msgstr ""
     +msgstr "weniger Ausgaben (impliziert --no-stat)"
      
    - #: builtin/rebase.c:821
    + #: builtin/rebase.c:839
      msgid "display a diffstat of what changed upstream"
     -msgstr ""
     +msgstr "Zusammenfassung der Unterschiede gegenüber dem Upstream-Branch anzeigen"
      
    - #: builtin/rebase.c:824
    + #: builtin/rebase.c:842
     -#, fuzzy
      msgid "do not show diffstat of what changed upstream"
     -msgstr "keine Zusammenfassung der Unterschiede am Schluss des Merges anzeigen"
     +msgstr "Zusammenfassung der Unterschiede gegenüber dem Upstream-Branch verbergen"
      
    - #: builtin/rebase.c:827
    + #: builtin/rebase.c:845
     -#, fuzzy
      msgid "add a Signed-off-by: line to each commit"
     -msgstr "der Commit-Beschreibung eine Signed-off-by Zeile hinzufügen"
     +msgstr "eine \"Signed-off-by:\"-Zeile zu jedem Commit hinzufügen"
      
    - #: builtin/rebase.c:829 builtin/rebase.c:833 builtin/rebase.c:835
    + #: builtin/rebase.c:847 builtin/rebase.c:851 builtin/rebase.c:853
      msgid "passed to 'git am'"
     -msgstr ""
     +msgstr "an 'git am' übergeben"
      
    - #: builtin/rebase.c:837 builtin/rebase.c:839
    + #: builtin/rebase.c:855 builtin/rebase.c:857
     -#, fuzzy
      msgid "passed to 'git apply'"
     -msgstr "an git-apply übergeben"
    -+msgstr "an 'git-apply' übergeben"
    ++msgstr "an 'git apply' übergeben"
      
    - #: builtin/rebase.c:841 builtin/rebase.c:844
    + #: builtin/rebase.c:859 builtin/rebase.c:862
      msgid "cherry-pick all commits, even if unchanged"
     -msgstr ""
     +msgstr "Cherry-Pick auf alle Commits ausführen, auch wenn diese unverändert sind"
      
    - #: builtin/rebase.c:846
    + #: builtin/rebase.c:864
     -#, fuzzy
      msgid "continue"
     -msgstr "Rebase fortsetzen"
     +msgstr "fortsetzen"
      
    - #: builtin/rebase.c:849
    + #: builtin/rebase.c:867
     -#, fuzzy
      msgid "skip current patch and continue"
     -msgstr "den aktuellen Patch auslassen"
     +msgstr "den aktuellen Patch auslassen und fortfahren"
      
    - #: builtin/rebase.c:851
    + #: builtin/rebase.c:869
     -#, fuzzy
      msgid "abort and check out the original branch"
     -msgstr ""
     -"  (benutzen Sie \"git rebase --abort\", um den ursprünglichen Branch "
     -"auszuchecken)"
     +msgstr "abbrechen und den ursprünglichen Branch auschecken"
      
    - #: builtin/rebase.c:854
    + #: builtin/rebase.c:872
     -#, fuzzy
      msgid "abort but keep HEAD where it is"
     -msgstr "Patch-Operation abbrechen, aber HEAD an aktueller Stelle belassen"
     +msgstr "abbrechen, aber HEAD an aktueller Stelle belassen"
      
    - #: builtin/rebase.c:855
    + #: builtin/rebase.c:873
     -#, fuzzy
      msgid "edit the todo list during an interactive rebase"
     -msgstr ""
     -"Die --edit-todo Aktion kann nur während eines interaktiven Rebase verwendet "
     -"werden."
     +msgstr "TODO-Liste während eines interaktiven Rebase bearbeiten"
      
    - #: builtin/rebase.c:858
    + #: builtin/rebase.c:876
     -#, fuzzy
      msgid "show the patch file being applied or merged"
     -msgstr "den Patch, der gerade angewendet wird, anzeigen"
     +msgstr "den Patch, der gerade angewendet oder zusammengeführt wird, anzeigen"
      
    - #: builtin/rebase.c:861
    + #: builtin/rebase.c:879
     -#, fuzzy
      msgid "use merging strategies to rebase"
     -msgstr "zu verwendende Merge-Strategie"
     +msgstr "Merge-Strategien beim Rebase verwenden"
      
    - #: builtin/rebase.c:865
    + #: builtin/rebase.c:883
      msgid "let the user edit the list of commits to rebase"
     -msgstr ""
     +msgstr "den Benutzer die Liste der Commits für den Rebase bearbeiten lassen"
      
    - #: builtin/rebase.c:869
    + #: builtin/rebase.c:887
      msgid "try to recreate merges instead of ignoring them"
     -msgstr ""
     +msgstr "versuchen, Merges wiederherzustellen anstatt sie zu ignorieren"
      
    - #: builtin/rebase.c:873
    - msgid "allow rerere to update index  with resolved conflict"
    + #: builtin/rebase.c:891
    + msgid "allow rerere to update index with resolved conflict"
     -msgstr ""
    -+msgstr "Rerere erlauben, den Index mit aufgelöstem Konflikt zu aktualisieren"
    ++msgstr "Rerere erlauben, den Index mit dem aufgelöstem Konflikt zu aktualisieren"
      
    - #: builtin/rebase.c:876
    + #: builtin/rebase.c:894
     -#, fuzzy
      msgid "preserve empty commits during rebase"
     -msgstr "ursprüngliche, leere Commits erhalten"
     +msgstr "leere Commits während des Rebase erhalten"
      
    - #: builtin/rebase.c:878
    + #: builtin/rebase.c:896
      msgid "move commits that begin with squash!/fixup! under -i"
     -msgstr ""
     +msgstr "bei -i Commits verschieben, die mit squash!/fixup! beginnen"
      
    - #: builtin/rebase.c:884
    + #: builtin/rebase.c:902
     -#, fuzzy
      msgid "automatically stash/stash pop before and after"
     -msgstr "automatischer Stash/Stash-Pop vor und nach eines Rebase"
     +msgstr "automatischer Stash/Stash-Pop davor und danach"
      
    - #: builtin/rebase.c:886
    + #: builtin/rebase.c:904
      msgid "add exec lines after each commit of the editable list"
     -msgstr ""
     +msgstr "exec-Zeilen nach jedem Commit der editierbaren Liste hinzufügen"
      
    - #: builtin/rebase.c:890
    + #: builtin/rebase.c:908
     -#, fuzzy
      msgid "allow rebasing commits with empty messages"
     -msgstr "Commits mit leerer Beschreibung erlauben"
     +msgstr "Rebase von Commits mit leerer Beschreibung erlauben"
      
    - #: builtin/rebase.c:893
    + #: builtin/rebase.c:911
      msgid "try to rebase merges instead of skipping them"
     -msgstr ""
     +msgstr "versuchen, Rebase mit Merges auszuführen, anstatt diese zu überspringen"
      
    - #: builtin/rebase.c:896
    + #: builtin/rebase.c:914
     -#, fuzzy
      msgid "use 'merge-base --fork-point' to refine upstream"
     -msgstr "git merge-base --fork-point <Referenz> [<Commit>]"
     +msgstr "'git merge-base --fork-point' benutzen, um Upstream-Branch zu bestimmen"
      
    - #: builtin/rebase.c:898
    + #: builtin/rebase.c:916
     -#, fuzzy
      msgid "use the given merge strategy"
     -msgstr "Option für Merge-Strategie"
     +msgstr "angegebene Merge-Strategie verwenden"
      
    - #: builtin/rebase.c:900 builtin/revert.c:111
    + #: builtin/rebase.c:918 builtin/revert.c:111
      msgid "option"
     @@
      
    - #: builtin/rebase.c:901
    + #: builtin/rebase.c:919
      msgid "pass the argument through to the merge strategy"
     -msgstr ""
     +msgstr "Argument zur Merge-Strategie durchreichen"
      
    - #: builtin/rebase.c:904
    + #: builtin/rebase.c:922
     -#, fuzzy
      msgid "rebase all reachable commits up to the root(s)"
     -msgstr "alle nicht erreichbaren Objekte von der Objektdatenbank entfernen"
     +msgstr "Rebase auf alle erreichbaren Commits bis zum Root-Commit ausführen"
      
    - #: builtin/rebase.c:920
    + #: builtin/rebase.c:938
     -#, fuzzy, c-format
     +#, c-format
      msgid "could not exec %s"
     -msgstr "konnte %s nicht parsen"
     +msgstr "Konnte 'exec %s' nicht ausführen."
      
    - #: builtin/rebase.c:938 git-legacy-rebase.sh:213
    + #: builtin/rebase.c:956 git-legacy-rebase.sh:213
      msgid "It looks like 'git am' is in progress. Cannot rebase."
     @@
      "mittels \"git add\" als aufgelöst markieren"
      
    - #: builtin/rebase.c:1026
    + #: builtin/rebase.c:1047
     -#, fuzzy
      msgid "could not discard worktree changes"
     -msgstr "Kann Änderungen im Arbeitsverzeichnis nicht löschen"
     +msgstr "Konnte Änderungen im Arbeitsverzeichnis nicht verwerfen."
      
    - #: builtin/rebase.c:1044
    + #: builtin/rebase.c:1066
     -#, fuzzy, c-format
     +#, c-format
      msgid "could not move back to %s"
     -msgstr "Konnte nicht zu $head_name zurückgehen"
     +msgstr "Konnte nicht zu %s zurückgehen."
      
    - #: builtin/rebase.c:1055 builtin/rm.c:368
    + #: builtin/rebase.c:1077 builtin/rm.c:368
      #, c-format
     @@
      msgstr "Konnte '%s' nicht löschen"
      
    - #: builtin/rebase.c:1081
    + #: builtin/rebase.c:1103
     -#, fuzzy, c-format
     +#, c-format
      msgid ""
      "It seems that there is already a %s directory, and\n"
      "I wonder if you are in the middle of another rebase.  If that is the\n"
    @@ -1302,220 +1325,228 @@
     +"\t%s\n"
      "und führen Sie diesen Befehl nochmal aus. Es wird angehalten, falls noch\n"
     -"etwas Schützenswertes vorhanden ist."
     +"etwas Schützenswertes vorhanden ist.\n"
      
    - #: builtin/rebase.c:1102
    + #: builtin/rebase.c:1124
     -#, fuzzy
      msgid "switch `C' expects a numerical value"
     -msgstr "Schalter '%c' erwartet einen numerischen Wert"
     +msgstr "Schalter `C' erwartet einen numerischen Wert."
      
    - #: builtin/rebase.c:1139
    + #: builtin/rebase.c:1161
     -#, fuzzy, c-format
     +#, c-format
      msgid "Unknown mode: %s"
     -msgstr "Unbekannter --patch Modus: %s"
     +msgstr "Unbekannter Modus: %s"
      
    - #: builtin/rebase.c:1161
    + #: builtin/rebase.c:1183
      msgid "--strategy requires --merge or --interactive"
     -msgstr ""
     +msgstr "--strategy erfordert --merge oder --interactive"
      
    - #: builtin/rebase.c:1204
    + #: builtin/rebase.c:1226
      #, c-format
     @@
      "error: cannot combine interactive options (--interactive, --exec, --rebase-"
      "merges, --preserve-merges, --keep-empty, --root + --onto) with am options "
      "(%s)"
     -msgstr ""
     +msgstr "Fehler: 'interactive'-Optionen (--interactive, --exec, --rebase-merges, --preserve-merges, --keep-empty, --root + --onto ) können nicht mit 'am'-Optionen (%s) kombiniert werden."
      
    - #: builtin/rebase.c:1209
    + #: builtin/rebase.c:1231
     -#, fuzzy, c-format
     +#, c-format
      msgid ""
      "error: cannot combine merge options (--merge, --strategy, --strategy-option) "
      "with am options (%s)"
     -msgstr ""
     -"Fehler: '--rebase-merges' und '--strategy-option' können nicht kombiniert "
     -"werden."
     +msgstr "Fehler: 'merge'-Optionen (--merge, --strategy, --strategy-option) können nicht mit 'am'-Optionen (%s) kombiniert werden."
      
    - #: builtin/rebase.c:1229 git-legacy-rebase.sh:528
    + #: builtin/rebase.c:1251 git-legacy-rebase.sh:536
     -#, fuzzy
      msgid "error: cannot combine '--preserve-merges' with '--rebase-merges'"
    - msgstr ""
    - "Fehler: '--preserve-merges' und '--rebase-merges' können nicht kombiniert "
    - "werden."
    +-msgstr ""
    +-"Fehler: '--preserve-merges' und '--rebase-merges' können nicht kombiniert "
    +-"werden."
    ++msgstr "Fehler: '--preserve-merges' und '--rebase-merges' können nicht kombiniert werden."
      
    - #: builtin/rebase.c:1234 git-legacy-rebase.sh:534
    + #: builtin/rebase.c:1256 git-legacy-rebase.sh:542
     -#, fuzzy
      msgid "error: cannot combine '--rebase-merges' with '--strategy-option'"
      msgstr ""
      "Fehler: '--rebase-merges' und '--strategy-option' können nicht kombiniert "
      "werden."
      
    - #: builtin/rebase.c:1237 git-legacy-rebase.sh:536
    + #: builtin/rebase.c:1259 git-legacy-rebase.sh:544
     -#, fuzzy
      msgid "error: cannot combine '--rebase-merges' with '--strategy'"
      msgstr ""
      "Fehler: '--rebase-merges' und '--strategy' können nicht kombiniert werden."
      
    - #: builtin/rebase.c:1261
    + #: builtin/rebase.c:1283
     -#, fuzzy, c-format
     +#, c-format
      msgid "invalid upstream '%s'"
     -msgstr "Ungültiger Pfad '%s'"
     +msgstr "Ungültiger Upstream '%s'"
      
    - #: builtin/rebase.c:1267
    + #: builtin/rebase.c:1289
     -#, fuzzy
      msgid "Could not create new root commit"
     -msgstr "Konnte neu erstellten Commit nicht analysieren."
     +msgstr "Konnte neuen Root-Commit nicht erstellen."
      
    - #: builtin/rebase.c:1285
    + #: builtin/rebase.c:1307
     -#, fuzzy, c-format
     +#, c-format
      msgid "'%s': need exactly one merge base"
     -msgstr "Brauche genau einen Commit-Bereich."
    -+msgstr "'%s': brauche genau eine Merge-Basis"
    ++msgstr "'%s': benötige genau eine Merge-Basis"
      
    - #: builtin/rebase.c:1292
    + #: builtin/rebase.c:1314
     -#, fuzzy, c-format
     +#, c-format
      msgid "Does not point to a valid commit '%s'"
     -msgstr "$onto_name zeigt auf keinen gültigen Commit"
     +msgstr "'%s' zeigt auf keinen gültigen Commit."
      
    - #: builtin/rebase.c:1317
    + #: builtin/rebase.c:1339
     -#, fuzzy, c-format
     +#, c-format
      msgid "fatal: no such branch/commit '%s'"
     -msgstr "fatal: Branch/Commit '$branch_name' nicht gefunden"
     +msgstr "fatal: Branch/Commit '%s' nicht gefunden"
      
    - #: builtin/rebase.c:1325 builtin/submodule--helper.c:37
    + #: builtin/rebase.c:1347 builtin/submodule--helper.c:37
      #: builtin/submodule--helper.c:1930
     @@
      msgstr "Referenz nicht gefunden: %s"
      
    - #: builtin/rebase.c:1337
    + #: builtin/rebase.c:1359
     -#, fuzzy
      msgid "Could not resolve HEAD to a revision"
     -msgstr "Konnte HEAD-Commit nicht auflösen."
     +msgstr "Konnte HEAD zu keinem Commit auflösen."
      
    - #: builtin/rebase.c:1377 git-legacy-rebase.sh:657
    + #: builtin/rebase.c:1399 git-legacy-rebase.sh:665
      msgid "Cannot autostash"
      msgstr "Kann automatischen Stash nicht erzeugen."
      
    - #: builtin/rebase.c:1380
    + #: builtin/rebase.c:1402
     -#, fuzzy, c-format
     +#, c-format
      msgid "Unexpected stash response: '%s'"
     -msgstr "Unerwartetes wanted-ref: '%s'"
     +msgstr "Unerwartete 'stash'-Antwort: '%s'"
      
    - #: builtin/rebase.c:1386
    + #: builtin/rebase.c:1408
     -#, fuzzy, c-format
     +#, c-format
      msgid "Could not create directory for '%s'"
     -msgstr "Konnte Verzeichnis '%s' nicht erstellen."
     +msgstr "Konnte Verzeichnis für '%s' nicht erstellen."
      
    - #: builtin/rebase.c:1389
    + #: builtin/rebase.c:1411
     -#, fuzzy, c-format
     +#, c-format
      msgid "Created autostash: %s\n"
     -msgstr "Automatischen Stash erzeugt: $stash_abbrev"
     +msgstr "Automatischen Stash erzeugt: %s\n"
      
    - #: builtin/rebase.c:1392
    + #: builtin/rebase.c:1414
     -#, fuzzy
      msgid "could not reset --hard"
     -msgstr "Konnte orig-head nicht lesen."
     +msgstr "Konnte 'reset --hard' nicht ausführen."
      
    - #: builtin/rebase.c:1393 builtin/reset.c:113
    + #: builtin/rebase.c:1415 builtin/reset.c:113
      #, c-format
     @@
      msgstr "Bitte committen Sie die Änderungen oder benutzen Sie \"stash\"."
      
    - #: builtin/rebase.c:1436
    + #: builtin/rebase.c:1458
     -#, fuzzy, c-format
     +#, c-format
      msgid "could not parse '%s'"
     -msgstr "konnte %s nicht parsen"
     +msgstr "Konnte '%s' nicht parsen."
      
    - #: builtin/rebase.c:1447
    + #: builtin/rebase.c:1470
     -#, fuzzy, c-format
     +#, c-format
      msgid "could not switch to %s"
     -msgstr "Konnte nicht nach '%s' schreiben."
     +msgstr "Konnte nicht zu %s wechseln."
      
    - #: builtin/rebase.c:1458 git-legacy-rebase.sh:689
    + #: builtin/rebase.c:1481 git-legacy-rebase.sh:697
      #, sh-format
     @@
      msgstr "HEAD ist aktuell."
      
    - #: builtin/rebase.c:1460
    + #: builtin/rebase.c:1483
     -#, fuzzy, c-format
     +#, c-format
      msgid "Current branch %s is up to date.\n"
     -msgstr "Aktueller Branch $branch_name ist auf dem neuesten Stand."
     +msgstr "Aktueller Branch %s ist auf dem neuesten Stand.\n"
      
    - #: builtin/rebase.c:1468 git-legacy-rebase.sh:699
    + #: builtin/rebase.c:1491 git-legacy-rebase.sh:707
      #, sh-format
     @@
      msgstr "HEAD ist aktuell, Rebase erzwungen."
      
    - #: builtin/rebase.c:1470
    + #: builtin/rebase.c:1493
     -#, fuzzy, c-format
     +#, c-format
      msgid "Current branch %s is up to date, rebase forced.\n"
     -msgstr ""
     -"Aktueller Branch $branch_name ist auf dem neuesten Stand, Rebase erzwungen."
     +msgstr "Aktueller Branch %s ist auf dem neuesten Stand, Rebase erzwungen.\n"
      
    - #: builtin/rebase.c:1478 git-legacy-rebase.sh:208
    + #: builtin/rebase.c:1501 git-legacy-rebase.sh:208
      msgid "The pre-rebase hook refused to rebase."
      msgstr "Der \"pre-rebase hook\" hat den Rebase zurückgewiesen."
      
    - #: builtin/rebase.c:1484
    + #: builtin/rebase.c:1508
    +-#, fuzzy, c-format
    ++#, c-format
    + msgid "Changes to %s:\n"
    +-msgstr "Änderungen von $mb zu $onto:"
    ++msgstr "Änderungen zu %s:\n"
    + 
    + #: builtin/rebase.c:1511
     -#, fuzzy, c-format
     +#, c-format
      msgid "Changes from %s to %s:\n"
     -msgstr "Änderungen von $mb zu $onto:"
     +msgstr "Änderungen von %s zu %s:\n"
      
    - #: builtin/rebase.c:1507
    + #: builtin/rebase.c:1536
     -#, fuzzy, c-format
     +#, c-format
      msgid "First, rewinding head to replay your work on top of it...\n"
     -msgstr ""
     -"Zunächst wird der Branch zurückgespult, um Ihre Änderungen\n"
     -"darauf neu anzuwenden ..."
     +msgstr "Zunächst wird der Branch zurückgespult, um Ihre Änderungen darauf neu anzuwenden...\n"
      
    - #: builtin/rebase.c:1513
    + #: builtin/rebase.c:1543
     -#, fuzzy
      msgid "Could not detach HEAD"
     -msgstr "Konnte HEAD nicht loslösen"
     +msgstr "Konnte HEAD nicht loslösen."
      
    - #: builtin/rebase.c:1522
    + #: builtin/rebase.c:1552
     -#, fuzzy, c-format
     +#, c-format
    - msgid "Fast-forwarded %s to %s. \n"
    + msgid "Fast-forwarded %s to %s.\n"
     -msgstr "Spule vor zu $sha1"
    -+msgstr "%s zu %s vorgespult.\n"
    ++msgstr "Spule %s vor zu %s.\n"
      
      #: builtin/rebase--interactive.c:24
     -#, fuzzy
      msgid "no HEAD?"
      msgstr "Kein HEAD?"
    @@ -1972,11 +2003,11 @@
     @@
      "nicht unterstützt."
      
      #: http.c:837
     -#, fuzzy
    - msgid "CURLSSLOPT_NO_REVOKE not suported with cURL < 7.44.0"
    + msgid "CURLSSLOPT_NO_REVOKE not supported with cURL < 7.44.0"
     -msgstr ""
     -"Das Anheften des öffentlichen Schlüssels wird mit cURL < 7.44.0\n"
     -"nicht unterstützt."
     +msgstr "CURLSSLOPT_NO_REVOKE wird mit cURL < 7.44.0 nicht unterstützt."
      
    @@ -2040,10 +2071,22 @@
     -msgstr "Git Commit Graph-Dateien schreiben und überprüfen"
     +msgstr "multi-pack-indexes schreiben und überprüfen"
      
      #: command-list.h:129
      msgid "Creates a tag object"
    +@@
    + "Aktueller Branch $branch_name ist auf dem neuesten Stand, Rebase erzwungen."
    + 
    + #: git-legacy-rebase.sh:723
    +-#, fuzzy, sh-format
    ++#, sh-format
    + msgid "Changes to $onto:"
    +-msgstr "Änderungen von $mb zu $onto:"
    ++msgstr "Änderungen zu $onto:"
    + 
    + #: git-legacy-rebase.sh:725
    + #, sh-format
     @@
      #: git-send-email.perl:525
      #, perl-format
      msgid "warning: sendmail alias with quotes is not supported: %s\n"
     -msgstr ""

 po/de.po | 845 +++++++++++++++++++++++++------------------------------
 1 file changed, 383 insertions(+), 462 deletions(-)

diff --git a/po/de.po b/po/de.po
index 899b95120..eb213d742 100644
--- a/po/de.po
+++ b/po/de.po
@@ -943,17 +943,17 @@ msgid ""
 "Use '\\!' for literal leading exclamation."
 msgstr ""
 "Verneinende Muster werden in Git-Attributen ignoriert.\n"
 "Benutzen Sie '\\!' für führende Ausrufezeichen."
 
 #: bisect.c:468
 #, c-format
 msgid "Badly quoted content in file '%s': %s"
-msgstr "Ungültiger Inhalt bzgl. Anführungsstriche in Datei '%s': %s"
+msgstr "Ungültiger Inhalt bzgl. Anführungszeichen in Datei '%s': %s"
 
 #: bisect.c:676
 #, c-format
 msgid "We cannot bisect more!\n"
 msgstr "Keine binäre Suche mehr möglich!\n"
 
 #: bisect.c:730
 #, c-format
@@ -1282,19 +1282,18 @@ msgstr "Das Paket speichert eine komplette Historie."
 #: bundle.c:201
 #, c-format
 msgid "The bundle requires this ref:"
 msgid_plural "The bundle requires these %d refs:"
 msgstr[0] "Das Paket benötigt diese Referenz:"
 msgstr[1] "Das Paket benötigt diese %d Referenzen:"
 
 #: bundle.c:267
-#, fuzzy
 msgid "unable to dup bundle descriptor"
-msgstr "Konnte Descriptor nicht umleiten."
+msgstr "Konnte dup für Descriptor des Pakets nicht ausführen."
 
 #: bundle.c:274
 msgid "Could not spawn pack-objects"
 msgstr "Konnte Paketobjekte nicht erstellen"
 
 #: bundle.c:285
 msgid "pack-objects died"
 msgstr "Erstellung der Paketobjekte abgebrochen"
@@ -1433,28 +1432,26 @@ msgid "could not find commit %s"
 msgstr "Konnte Commit %s nicht finden."
 
 #: commit-graph.c:617 builtin/pack-objects.c:2652
 #, c-format
 msgid "unable to get type of object %s"
 msgstr "Konnte Art von Objekt '%s' nicht bestimmen."
 
 #: commit-graph.c:651
-#, fuzzy
 msgid "Annotating commits in commit graph"
-msgstr "Zu viele Commits zum Schreiben des Graphen."
+msgstr "Annotiere Commits in Commit-Graphen"
 
 #: commit-graph.c:691
 msgid "Computing commit graph generation numbers"
-msgstr ""
+msgstr "Commit-Graph Generierungsnummern berechnen"
 
 #: commit-graph.c:803 commit-graph.c:826 commit-graph.c:852
-#, fuzzy
 msgid "Finding commits for commit graph"
-msgstr "Zu viele Commits zum Schreiben des Graphen."
+msgstr "Bestimme Commits für Commit-Graphen"
 
 #: commit-graph.c:812
 #, c-format
 msgid "error adding pack %s"
 msgstr "Fehler beim Hinzufügen von Paket %s."
 
 #: commit-graph.c:814
 #, c-format
@@ -1478,17 +1475,17 @@ msgstr "Konnte führende Verzeichnisse von '%s' nicht erstellen."
 #: commit-graph.c:1002
 msgid "the commit-graph file has incorrect checksum and is likely corrupt"
 msgstr ""
 "Die Commit-Graph-Datei hat eine falsche Prüfsumme und ist wahrscheinlich "
 "beschädigt."
 
 #: commit-graph.c:1046
 msgid "Verifying commits in commit graph"
-msgstr ""
+msgstr "Commit in Commit-Graph überprüfen"
 
 #: compat/obstack.c:405 compat/obstack.c:407
 msgid "memory exhausted"
 msgstr "Speicher verbraucht"
 
 #: config.c:123
 #, c-format
 msgid ""
@@ -2193,37 +2190,39 @@ msgstr[1] "%s, und %<PRIuMAX> Monaten"
 #, c-format
 msgid "%<PRIuMAX> year ago"
 msgid_plural "%<PRIuMAX> years ago"
 msgstr[0] "vor %<PRIuMAX> Jahr"
 msgstr[1] "vor %<PRIuMAX> Jahren"
 
 #: delta-islands.c:268
 msgid "Propagating island marks"
-msgstr ""
+msgstr "Erzeuge Delta-Island Markierungen"
 
 #: delta-islands.c:286
-#, fuzzy, c-format
+#, c-format
 msgid "bad tree object %s"
-msgstr "Konnte Objekt %s nicht lesen."
+msgstr "Ungültiges Tree-Objekt %s."
 
 #: delta-islands.c:330
-#, fuzzy, c-format
+#, c-format
 msgid "failed to load island regex for '%s': %s"
-msgstr "Fehler beim Finden des \"Tree\"-Objektes von %s."
+msgstr "Fehler beim Laden des regulären Ausdrucks des Delta-Island für '%s': %s"
 
 #: delta-islands.c:386
 #, c-format
 msgid "island regex from config has too many capture groups (max=%d)"
 msgstr ""
+"Regulärer Ausdruck des Delta-Island aus Konfiguration hat zu\n"
+"viele Capture-Gruppen (maximal %d)."
 
 #: delta-islands.c:462
 #, c-format
 msgid "Marked %d islands, done.\n"
-msgstr ""
+msgstr "%d Delta-Islands markiert, fertig.\n"
 
 #: diffcore-order.c:24
 #, c-format
 msgid "failed to read orderfile '%s'"
 msgstr "Fehler beim Lesen der Reihenfolgedatei '%s'."
 
 #: diffcore-rename.c:544
 msgid "Performing inexact rename detection"
@@ -2592,21 +2591,21 @@ msgstr "Unerwartete Acknowledgment-Zeile: '%s'"
 
 #: fetch-pack.c:1249
 #, c-format
 msgid "error processing acks: %d"
 msgstr "Fehler beim Verarbeiten von ACKS: %d"
 
 #: fetch-pack.c:1259
 msgid "expected packfile to be sent after 'ready'"
-msgstr ""
+msgstr "Erwartete Versand einer Packdatei nach 'ready'."
 
 #: fetch-pack.c:1261
 msgid "expected no other sections to be sent after no 'ready'"
-msgstr ""
+msgstr "Erwartete keinen Versand einer anderen Sektion ohne 'ready'."
 
 #: fetch-pack.c:1298
 #, c-format
 msgid "error processing shallow info: %d"
 msgstr "Fehler beim Verarbeiten von Shallow-Informationen: %d"
 
 #: fetch-pack.c:1314
 #, c-format
@@ -2746,26 +2745,25 @@ msgid "unsupported command listing type '%s'"
 msgstr "Nicht unterstützte Art zur Befehlsauflistung '%s'."
 
 #: help.c:408
 msgid "The common Git guides are:"
 msgstr "Die allgemeinen Git-Anleitungen sind:"
 
 #: help.c:517
 msgid "See 'git help <command>' to read about a specific subcommand"
-msgstr ""
+msgstr "Siehe 'git help <Befehl>', um mehr über einen spezifischen Unterbefehl zu lesen."
 
 #: help.c:522
-#, fuzzy
 msgid "External commands"
-msgstr "führe $command aus"
+msgstr "Externe Befehle"
 
 #: help.c:530
 msgid "Command aliases"
-msgstr ""
+msgstr "Alias-Befehle"
 
 #: help.c:594
 #, c-format
 msgid ""
 "'%s' appears to be a git command, but we were not\n"
 "able to execute it. Maybe git-%s is broken?"
 msgstr ""
 "'%s' scheint ein git-Befehl zu sein, konnte aber\n"
@@ -2892,19 +2890,18 @@ msgstr "Name besteht nur aus nicht erlaubten Zeichen: %s"
 msgid "invalid date format: %s"
 msgstr "Ungültiges Datumsformat: %s"
 
 #: list-objects-filter-options.c:35
 msgid "multiple filter-specs cannot be combined"
 msgstr "Mehrere filter-specs können nicht kombiniert werden."
 
 #: list-objects-filter-options.c:58
-#, fuzzy
 msgid "only 'tree:0' is supported"
-msgstr "Protokoll '%s' wird nicht unterstützt."
+msgstr "Es wird nur 'tree:0' unterstützt."
 
 #: list-objects-filter-options.c:137
 msgid "cannot change partial clone promisor remote"
 msgstr "Kann Remote-Repository für partielles Klonen nicht ändern."
 
 #: lockfile.c:151
 #, c-format
 msgid ""
@@ -3355,142 +3352,141 @@ msgstr "Merge hat keinen Commit zurückgegeben"
 msgid "Could not parse object '%s'"
 msgstr "Konnte Objekt '%s' nicht parsen."
 
 #: merge-recursive.c:3553 builtin/merge.c:691 builtin/merge.c:849
 msgid "Unable to write index."
 msgstr "Konnte Index nicht schreiben."
 
 #: midx.c:65
-#, fuzzy, c-format
+#, c-format
 msgid "multi-pack-index file %s is too small"
-msgstr "Graph-Datei %s ist zu klein."
+msgstr "multi-pack-index-Datei %s ist zu klein."
 
 #: midx.c:81
-#, fuzzy, c-format
+#, c-format
 msgid "multi-pack-index signature 0x%08x does not match signature 0x%08x"
-msgstr "Graph-Signatur %X stimmt nicht mit Signatur %X überein."
+msgstr "multi-pack-index-Signatur 0x%08x stimmt nicht mit Signatur 0x%08x überein."
 
 #: midx.c:86
 #, c-format
 msgid "multi-pack-index version %d not recognized"
-msgstr ""
+msgstr "multi-pack-index-Version %d nicht erkannt."
 
 #: midx.c:91
-#, fuzzy, c-format
+#, c-format
 msgid "hash version %u does not match"
-msgstr "Hash-Version %X stimmt nicht mit Version %X überein."
+msgstr "Hash-Version %u stimmt nicht überein."
 
 #: midx.c:105
 msgid "invalid chunk offset (too large)"
-msgstr ""
+msgstr "Ungültiger Chunk-Offset (zu groß)"
 
 #: midx.c:129
 msgid "terminating multi-pack-index chunk id appears earlier than expected"
-msgstr ""
+msgstr "Abschließende multi-pack-index Chunk-Id erscheint eher als erwartet."
 
 #: midx.c:142
 msgid "multi-pack-index missing required pack-name chunk"
-msgstr ""
+msgstr "multi-pack-index fehlt erforderlicher pack-name Chunk."
 
 #: midx.c:144
 msgid "multi-pack-index missing required OID fanout chunk"
-msgstr ""
+msgstr "multi-pack-index fehlt erforderlicher OID fanout Chunk."
 
 #: midx.c:146
 msgid "multi-pack-index missing required OID lookup chunk"
-msgstr ""
+msgstr "multi-pack-index fehlt erforderlicher OID lookup Chunk."
 
 #: midx.c:148
 msgid "multi-pack-index missing required object offsets chunk"
-msgstr ""
+msgstr "multi-pack-index fehlt erforderlicher object offset Chunk."
 
 #: midx.c:162
 #, c-format
 msgid "multi-pack-index pack names out of order: '%s' before '%s'"
-msgstr ""
+msgstr "Falsche Reihenfolge bei multi-pack-index Pack-Namen: '%s' vor '%s'"
 
 #: midx.c:205
 #, c-format
 msgid "bad pack-int-id: %u (%u total packs)"
-msgstr ""
+msgstr "Ungültige pack-int-id: %u (%u Pakete insgesamt)"
 
 #: midx.c:246
 msgid "multi-pack-index stores a 64-bit offset, but off_t is too small"
-msgstr ""
+msgstr "multi-pack-index speichert einen 64-Bit Offset, aber off_t ist zu klein."
 
 #: midx.c:271
 msgid "error preparing packfile from multi-pack-index"
-msgstr ""
+msgstr "Fehler bei Vorbereitung der Packdatei aus multi-pack-index."
 
 #: midx.c:407
-#, fuzzy, c-format
+#, c-format
 msgid "failed to add packfile '%s'"
-msgstr "Fehler beim Lesen der Reihenfolgedatei '%s'."
+msgstr "Fehler beim Hinzufügen von Packdatei'%s'."
 
 #: midx.c:413
-#, fuzzy, c-format
+#, c-format
 msgid "failed to open pack-index '%s'"
-msgstr "Fehler beim Öffnen von '%s'"
+msgstr "Fehler beim Öffnen von pack-index '%s'"
 
 #: midx.c:507
-#, fuzzy, c-format
+#, c-format
 msgid "failed to locate object %d in packfile"
-msgstr "Konnte Objekt %s nicht lesen."
+msgstr "Fehler beim Lokalisieren von Objekt %d in Packdatei."
 
 #: midx.c:943
-#, fuzzy, c-format
+#, c-format
 msgid "failed to clear multi-pack-index at %s"
-msgstr "Fehler beim Bereinigen des Index"
+msgstr "Fehler beim Löschen des multi-pack-index bei %s"
 
 #: midx.c:981
 #, c-format
 msgid ""
 "oid fanout out of order: fanout[%d] = %<PRIx32> > %<PRIx32> = fanout[%d]"
-msgstr ""
+msgstr "Ungültige oid fanout Reihenfolge: fanout[%d] = %<PRIx32> > %<PRIx32> = fanout[%d]"
 
 #: midx.c:992
 #, c-format
 msgid "oid lookup out of order: oid[%d] = %s >= %s = oid[%d]"
-msgstr ""
+msgstr "Ungültige oid lookup Reihenfolge: oid[%d] = %s >= %s = oid[%d]"
 
 #: midx.c:996
-#, fuzzy
 msgid "Verifying object offsets"
-msgstr "Schreibe Objekte"
+msgstr "Überprüfe Objekt-Offsets"
 
 #: midx.c:1004
-#, fuzzy, c-format
+#, c-format
 msgid "failed to load pack entry for oid[%d] = %s"
-msgstr "kann für %s keinen Eintrag in den Zwischenspeicher hinzufügen"
+msgstr "Fehler beim Laden des Pack-Eintrags für oid[%d] = %s"
 
 #: midx.c:1010
-#, fuzzy, c-format
+#, c-format
 msgid "failed to load pack-index for packfile %s"
-msgstr "Fehler beim Lesen der Reihenfolgedatei '%s'."
+msgstr "Fehler beim Laden des Pack-Index für Packdatei %s"
 
 #: midx.c:1019
 #, c-format
 msgid "incorrect object offset for oid[%d] = %s: %<PRIx64> != %<PRIx64>"
-msgstr ""
+msgstr "Falscher Objekt-Offset für oid[%d] = %s: %<PRIx64> != %<PRIx64>"
 
 #: name-hash.c:532
-#, fuzzy, c-format
+#, c-format
 msgid "unable to create lazy_dir thread: %s"
-msgstr "kann Thread nicht erzeugen: %s"
+msgstr "Kann lazy_dir Thread nicht erzeugen: %s"
 
 #: name-hash.c:554
-#, fuzzy, c-format
+#, c-format
 msgid "unable to create lazy_name thread: %s"
-msgstr "kann Thread nicht erzeugen: %s"
+msgstr "Kann lazy_name Thread nicht erzeugen: %s"
 
 #: name-hash.c:560
-#, fuzzy, c-format
+#, c-format
 msgid "unable to join lazy_name thread: %s"
-msgstr "kann Thread nicht erzeugen: %s"
+msgstr "Kann lazy_name Thread nicht beitreten: %s"
 
 #: notes-merge.c:275
 #, c-format
 msgid ""
 "You have not concluded your previous notes merge (%s exists).\n"
 "Please, use 'git notes merge --commit' or 'git notes merge --abort' to "
 "commit/abort the previous merge before you start a new notes merge."
 msgstr ""
@@ -3723,24 +3719,23 @@ msgid "protocol error: bad line length character: %.4s"
 msgstr "Protokollfehler: ungültiges Zeichen für Zeilenlänge: %.4s"
 
 #: pkt-line.c:337 pkt-line.c:342
 #, c-format
 msgid "protocol error: bad line length %d"
 msgstr "Protokollfehler: ungültige Zeilenlänge %d"
 
 #: preload-index.c:118
-#, fuzzy
 msgid "Refreshing index"
-msgstr "Konnte den Index nicht aktualisieren."
+msgstr "Aktualisiere Index"
 
 #: preload-index.c:137
-#, fuzzy, c-format
+#, c-format
 msgid "unable to create threaded lstat: %s"
-msgstr "kann Thread nicht erzeugen: %s"
+msgstr "Kann Thread für lstat nicht erzeugen: %s"
 
 #: pretty.c:962
 msgid "unable to parse --pretty format"
 msgstr "Konnte --pretty Format nicht parsen."
 
 #: range-diff.c:56
 msgid "could not start `log`"
 msgstr "Konnte `log` nicht starten."
@@ -3759,19 +3754,18 @@ msgid "failed to generate diff"
 msgstr "Fehler beim Generieren des Diffs."
 
 #: range-diff.c:455 range-diff.c:457
 #, c-format
 msgid "could not parse log for '%s'"
 msgstr "Konnte Log für '%s' nicht parsen."
 
 #: read-cache.c:1490
-#, fuzzy
 msgid "Refresh index"
-msgstr "Konnte den Index nicht aktualisieren."
+msgstr "Aktualisiere Index"
 
 #: read-cache.c:1604
 #, c-format
 msgid ""
 "index.version set, but the value is invalid.\n"
 "Using version %i"
 msgstr ""
 "index.version gesetzt, aber Wert ungültig.\n"
@@ -3784,50 +3778,50 @@ msgid ""
 "Using version %i"
 msgstr ""
 "GIT_INDEX_VERSION gesetzt, aber Wert ungültig.\n"
 "Verwende Version %i"
 
 #: read-cache.c:1792
 #, c-format
 msgid "malformed name field in the index, near path '%s'"
-msgstr ""
+msgstr "Ungültiges Namensfeld im Index, in der Nähe von Pfad '%s'."
 
 #: read-cache.c:1960 rerere.c:565 rerere.c:599 rerere.c:1111 builtin/add.c:458
 #: builtin/check-ignore.c:177 builtin/checkout.c:289 builtin/checkout.c:585
 #: builtin/checkout.c:953 builtin/clean.c:954 builtin/commit.c:343
 #: builtin/diff-tree.c:115 builtin/grep.c:489 builtin/mv.c:144
 #: builtin/reset.c:244 builtin/rm.c:270 builtin/submodule--helper.c:329
 msgid "index file corrupt"
 msgstr "Index-Datei beschädigt"
 
 #: read-cache.c:2101
-#, fuzzy, c-format
+#, c-format
 msgid "unable to create load_cache_entries thread: %s"
-msgstr "kann Thread nicht erzeugen: %s"
+msgstr "Kann Thread für load_cache_entries nicht erzeugen: %s"
 
 #: read-cache.c:2114
-#, fuzzy, c-format
+#, c-format
 msgid "unable to join load_cache_entries thread: %s"
-msgstr "kann Thread nicht erzeugen: %s"
+msgstr "Kann Thread für load_cache_entries nicht erzeugen: %s"
 
 #: read-cache.c:2201
-#, fuzzy, c-format
+#, c-format
 msgid "unable to create load_index_extensions thread: %s"
-msgstr "kann Thread nicht erzeugen: %s"
+msgstr "Kann Thread für load_index_extensions nicht erzeugen: %s"
 
 #: read-cache.c:2228
-#, fuzzy, c-format
+#, c-format
 msgid "unable to join load_index_extensions thread: %s"
-msgstr "kann Thread nicht erzeugen: %s"
+msgstr "Kann Thread für load_index_extensions nicht beitreten: %s"
 
 #: read-cache.c:2982 sequencer.c:4727 wrapper.c:658 builtin/merge.c:1086
 #, c-format
 msgid "could not close '%s'"
-msgstr "Konnte '%s' nicht schließen"
+msgstr "Konnte '%s' nicht schließen."
 
 #: read-cache.c:3055 sequencer.c:2203 sequencer.c:3592
 #, c-format
 msgid "could not stat '%s'"
 msgstr "Konnte '%s' nicht lesen."
 
 #: read-cache.c:3068
 #, c-format
@@ -3835,17 +3829,16 @@ msgid "unable to open git dir: %s"
 msgstr "konnte Git-Verzeichnis nicht öffnen: %s"
 
 #: read-cache.c:3080
 #, c-format
 msgid "unable to unlink: %s"
 msgstr "Konnte '%s' nicht entfernen."
 
 #: rebase-interactive.c:10
-#, fuzzy
 msgid ""
 "\n"
 "Commands:\n"
 "p, pick <commit> = use commit\n"
 "r, reword <commit> = use commit, but edit the commit message\n"
 "e, edit <commit> = use commit, but stop for amending\n"
 "s, squash <commit> = use commit, but meld into previous commit\n"
 "f, fixup <commit> = like \"squash\", but discard this commit's log message\n"
@@ -3862,26 +3855,25 @@ msgid ""
 "These lines can be re-ordered; they are executed from top to bottom.\n"
 msgstr ""
 "\n"
 "Befehle:\n"
 "p, pick <Commit> = Commit verwenden\n"
 "r, reword <Commit> = Commit verwenden, aber Commit-Beschreibung bearbeiten\n"
 "e, edit <Commit> = Commit verwenden, aber zum Nachbessern anhalten\n"
 "s, squash <Commit> = Commit verwenden, aber mit vorherigem Commit vereinen\n"
-"f, fixup <Commit> = wie \"squash\", aber diese Commit-Beschreibung "
-"verwerfen\n"
+"f, fixup <Commit> = wie \"squash\", aber diese Commit-Beschreibung verwerfen\n"
 "x, exec <Commit> = Befehl (Rest der Zeile) mittels Shell ausführen\n"
+"b, break = hier anhalten (Rebase später mit 'git rebase --continue' fortsetzen)\n"
 "d, drop <Commit> = Commit entfernen\n"
 "l, label <Label> = aktuellen HEAD mit Label versehen\n"
 "t, reset <Label> = HEAD zu einem Label umsetzen\n"
 "m, merge [-C <Commit> | -c <Commit>] <Label> [# <eineZeile>]\n"
 ".       Merge-Commit mit der originalen Merge-Commit-Beschreibung erstellen\n"
-".       (oder die eine Zeile, wenn keine originale Merge-Commit-"
-"Beschreibung\n"
+".       (oder die eine Zeile, wenn keine originale Merge-Commit-Beschreibung\n"
 ".       spezifiziert ist). Benutzen Sie -c <Commit> zum Bearbeiten der\n"
 ".       Commit-Beschreibung.\n"
 "\n"
 "Diese Zeilen können umsortiert werden; Sie werden von oben nach unten\n"
 "ausgeführt.\n"
 
 #: rebase-interactive.c:31 git-rebase--preserve-merges.sh:173
 msgid ""
@@ -4182,17 +4174,17 @@ msgstr "Fehlerhafter Feldname: %.*s"
 #, c-format
 msgid "unknown field name: %.*s"
 msgstr "Unbekannter Feldname: %.*s"
 
 #: ref-filter.c:539
 #, c-format
 msgid ""
 "not a git repository, but the field '%.*s' requires access to object data"
-msgstr ""
+msgstr "Kein Git-Repository, aber das Feld '%.*s' erfordert Zugriff auf Objektdaten."
 
 #: ref-filter.c:663
 #, c-format
 msgid "format: %%(if) atom used without a %%(then) atom"
 msgstr "format: %%(if) Atom ohne ein %%(then) Atom verwendet"
 
 #: ref-filter.c:726
 #, c-format
@@ -4446,113 +4438,111 @@ msgstr "doppelte ersetzende Referenz: %s"
 
 #: replace-object.c:73
 #, c-format
 msgid "replace depth too high for object %s"
 msgstr "Ersetzungstiefe zu hoch für Objekt %s"
 
 #: rerere.c:217 rerere.c:226 rerere.c:229
 msgid "corrupt MERGE_RR"
-msgstr ""
+msgstr "Fehlerhaftes MERGE_RR"
 
 #: rerere.c:264 rerere.c:269
-#, fuzzy
 msgid "unable to write rerere record"
-msgstr "Konnte Notiz-Objekt nicht schreiben"
+msgstr "Konnte Rerere-Eintrag nicht schreiben."
 
 #: rerere.c:485 rerere.c:692 sequencer.c:3136 sequencer.c:3162
 #, c-format
 msgid "could not write '%s'"
 msgstr "Konnte '%s' nicht schreiben."
 
 #: rerere.c:495
-#, fuzzy, c-format
+#, c-format
 msgid "there were errors while writing '%s' (%s)"
-msgstr "Lesefehler beim Indizieren von '%s'."
+msgstr "Fehler beim Schreiben von '%s' (%s)."
 
 #: rerere.c:498
-#, fuzzy, c-format
+#, c-format
 msgid "failed to flush '%s'"
-msgstr "Konnte '%s' nicht lesen"
+msgstr "Flush bei '%s' fehlgeschlagen."
 
 #: rerere.c:503 rerere.c:1039
-#, fuzzy, c-format
+#, c-format
 msgid "could not parse conflict hunks in '%s'"
-msgstr "Konnte Commit '%s' nicht parsen."
+msgstr "Konnte Konflikt-Blöcke in '%s' nicht parsen."
 
 #: rerere.c:684
-#, fuzzy, c-format
+#, c-format
 msgid "failed utime() on '%s'"
 msgstr "Fehler beim Aufruf von utime() auf '%s'."
 
 #: rerere.c:694
-#, fuzzy, c-format
+#, c-format
 msgid "writing '%s' failed"
-msgstr "Konnte '%s' nicht erstellen"
+msgstr "Schreiben von '%s' fehlgeschlagen."
 
 #: rerere.c:714
 #, c-format
 msgid "Staged '%s' using previous resolution."
-msgstr ""
+msgstr "'%s' mit vorheriger Konfliktauflösung zum Commit vorgemerkt."
 
 #: rerere.c:753
-#, fuzzy, c-format
+#, c-format
 msgid "Recorded resolution for '%s'."
-msgstr "aufgezeichnete Auflösung von Merge-Konflikten wiederverwenden"
+msgstr "Konfliktauflösung für '%s' aufgezeichnet."
 
 #: rerere.c:788
 #, c-format
 msgid "Resolved '%s' using previous resolution."
-msgstr ""
+msgstr "Konflikte in '%s' mit vorheriger Konfliktauflösung beseitigt."
 
 #: rerere.c:803
-#, fuzzy, c-format
+#, c-format
 msgid "cannot unlink stray '%s'"
-msgstr "kann symbolische Verknüpfung '%s' auf '%s' nicht erstellen"
+msgstr "Kann '%s' nicht löschen."
 
 #: rerere.c:807
-#, fuzzy, c-format
+#, c-format
 msgid "Recorded preimage for '%s'"
-msgstr "Konnte Log für '%s' nicht parsen."
+msgstr "Preimage für '%s' aufgezeichnet."
 
 #: rerere.c:881 submodule.c:1763 builtin/submodule--helper.c:1413
 #: builtin/submodule--helper.c:1423
 #, c-format
 msgid "could not create directory '%s'"
 msgstr "Konnte Verzeichnis '%s' nicht erstellen."
 
 #: rerere.c:1057
-#, fuzzy, c-format
+#, c-format
 msgid "failed to update conflicted state in '%s'"
-msgstr "Fehler beim Ausführen von 'git status' auf '%s'"
+msgstr "Fehler beim Aktualisieren des Konflikt-Status in '%s'."
 
 #: rerere.c:1068 rerere.c:1075
-#, fuzzy, c-format
+#, c-format
 msgid "no remembered resolution for '%s'"
-msgstr "Konnte Merge-Ergebnis von '%s' nicht hinzufügen."
+msgstr "Keine aufgezeichnete Konfliktauflösung für '%s'."
 
 #: rerere.c:1077
-#, fuzzy, c-format
+#, c-format
 msgid "cannot unlink '%s'"
-msgstr "kann Verweis '%s' nicht lesen"
+msgstr "Kann '%s' nicht löschen."
 
 #: rerere.c:1087
-#, fuzzy, c-format
+#, c-format
 msgid "Updated preimage for '%s'"
-msgstr "Ersetzende Referenz '%s' gelöscht."
+msgstr "Preimage für '%s' aktualisiert."
 
 #: rerere.c:1096
-#, fuzzy, c-format
+#, c-format
 msgid "Forgot resolution for '%s'\n"
-msgstr "Konnte Log für '%s' nicht parsen."
+msgstr "Aufgezeichnete Konfliktauflösung für '%s' gelöscht.\n"
 
 #: rerere.c:1199
-#, fuzzy
 msgid "unable to open rr-cache directory"
-msgstr "Konnte Cache-Verzeichnis nicht aktualisieren."
+msgstr "Konnte rr-cache Verzeichnis nicht öffnen."
 
 #: revision.c:2324
 msgid "your current branch appears to be broken"
 msgstr "Ihr aktueller Branch scheint fehlerhaft zu sein."
 
 #: revision.c:2327
 #, c-format
 msgid "your current branch '%s' does not have any commits yet"
@@ -4562,19 +4552,19 @@ msgstr "Ihr aktueller Branch '%s' hat noch keine Commits."
 msgid "--first-parent is incompatible with --bisect"
 msgstr "Die Optionen --first-parent und --bisect sind inkompatibel."
 
 #: run-command.c:740
 msgid "open /dev/null failed"
 msgstr "Öffnen von /dev/null fehlgeschlagen"
 
 #: run-command.c:1229
-#, fuzzy, c-format
+#, c-format
 msgid "cannot create async thread: %s"
-msgstr "kann Thread nicht erzeugen: %s"
+msgstr "Kann Thread für async nicht erzeugen: %s"
 
 #: run-command.c:1293
 #, c-format
 msgid ""
 "The '%s' hook was ignored because it's not set as executable.\n"
 "You can disable this warning with `git config advice.ignoredHook false`."
 msgstr ""
 "Der '%s' Hook wurde ignoriert, weil er nicht als ausführbar markiert ist.\n"
@@ -4715,59 +4705,59 @@ msgstr "%s: Konnte neue Index-Datei nicht schreiben"
 msgid "unable to update cache tree"
 msgstr "Konnte Cache-Verzeichnis nicht aktualisieren."
 
 #: sequencer.c:604
 msgid "could not resolve HEAD commit"
 msgstr "Konnte HEAD-Commit nicht auflösen."
 
 #: sequencer.c:684
-#, fuzzy, c-format
+#, c-format
 msgid "no key present in '%.*s'"
-msgstr "Konnte '%.*s' nicht parsen."
+msgstr "Kein Schlüssel in '%.*s' vorhanden."
 
 #: sequencer.c:695
-#, fuzzy, c-format
+#, c-format
 msgid "unable to dequote value of '%s'"
-msgstr "Konnte Remote-Helper für '%s' nicht finden."
+msgstr "Konnte Anführungszeichen von '%s' nicht entfernen."
 
 #: sequencer.c:732 wrapper.c:227 wrapper.c:397 builtin/am.c:719
 #: builtin/am.c:811 builtin/merge.c:1081
 #, c-format
 msgid "could not open '%s' for reading"
 msgstr "Konnte '%s' nicht zum Lesen öffnen."
 
 #: sequencer.c:742
 msgid "'GIT_AUTHOR_NAME' already given"
-msgstr ""
+msgstr "'GIT_AUTHOR_NAME' bereits angegeben."
 
 #: sequencer.c:747
 msgid "'GIT_AUTHOR_EMAIL' already given"
-msgstr ""
+msgstr "'GIT_AUTHOR_EMAIL' bereits angegeben."
 
 #: sequencer.c:752
 msgid "'GIT_AUTHOR_DATE' already given"
-msgstr ""
+msgstr "'GIT_AUTHOR_DATE' bereits angegeben."
 
 #: sequencer.c:756
-#, fuzzy, c-format
+#, c-format
 msgid "unknown variable '%s'"
-msgstr "Unbekanntes Archivformat '%s'"
+msgstr "Unbekannte Variable '%s'"
 
 #: sequencer.c:761
 msgid "missing 'GIT_AUTHOR_NAME'"
-msgstr ""
+msgstr "'GIT_AUTHOR_NAME' fehlt."
 
 #: sequencer.c:763
 msgid "missing 'GIT_AUTHOR_EMAIL'"
-msgstr ""
+msgstr "'GIT_AUTHOR_EMAIL' fehlt."
 
 #: sequencer.c:765
 msgid "missing 'GIT_AUTHOR_DATE'"
-msgstr ""
+msgstr "'GIT_AUTHOR_DATE' fehlt."
 
 #: sequencer.c:825
 #, c-format
 msgid "invalid date format '%s' in '%s'"
 msgstr "Ungültiges Datumsformat '%s' in '%s'"
 
 #: sequencer.c:842
 #, c-format
@@ -5310,38 +5300,38 @@ msgid ""
 "Your changes are safe in the stash.\n"
 "You can run \"git stash pop\" or \"git stash drop\" at any time.\n"
 msgstr ""
 "Anwendung des automatischen Stash resultierte in Konflikten.\n"
 "Ihre Änderungen sind im Stash sicher.\n"
 "Sie können jederzeit \"git stash pop\" oder \"git stash drop\" ausführen.\n"
 
 #: sequencer.c:3427
-#, fuzzy, c-format
+#, c-format
 msgid "could not checkout %s"
-msgstr "kann %s nicht auschecken"
+msgstr "Konnte %s nicht auschecken."
 
 #: sequencer.c:3441
-#, fuzzy, c-format
+#, c-format
 msgid "%s: not a valid OID"
-msgstr "%s ist kein gültiges Objekt"
+msgstr "%s: keine gültige OID"
 
 #: sequencer.c:3446 git-rebase--preserve-merges.sh:724
 msgid "could not detach HEAD"
 msgstr "Konnte HEAD nicht loslösen"
 
 #: sequencer.c:3461
-#, fuzzy, c-format
+#, c-format
 msgid "Stopped at HEAD\n"
-msgstr "Angehalten bei %s... %.*s\n"
+msgstr "Angehalten bei HEAD\n"
 
 #: sequencer.c:3463
-#, fuzzy, c-format
+#, c-format
 msgid "Stopped at %s\n"
-msgstr "Angehalten bei %s... %.*s\n"
+msgstr "Angehalten bei %s\n"
 
 #: sequencer.c:3471
 #, c-format
 msgid ""
 "Could not execute the todo command\n"
 "\n"
 "    %.*s\n"
 "It has been rescheduled; To edit the command before continuing, please\n"
@@ -5495,41 +5485,38 @@ msgid ""
 "continue'.\n"
 "Or you can abort the rebase with 'git rebase --abort'.\n"
 msgstr ""
 "Sie können das mit 'git rebase --edit-todo' beheben. Führen Sie danach\n"
 "'git rebase --continue' aus.\n"
 "Oder Sie können den Rebase mit 'git rebase --abort' abbrechen.\n"
 
 #: sequencer.c:4848 sequencer.c:4886
-#, fuzzy
 msgid "nothing to do"
-msgstr "nichts zu committen\n"
+msgstr "Nichts zu tun."
 
 #: sequencer.c:4852
-#, fuzzy, c-format
+#, c-format
 msgid "Rebase %s onto %s (%d command)"
 msgid_plural "Rebase %s onto %s (%d commands)"
-msgstr[0] "Rebase von $shortrevisions auf $shortonto ($todocount Kommando)"
-msgstr[1] "Rebase von $shortrevisions auf $shortonto ($todocount Kommandos)"
+msgstr[0] "Rebase von %s auf %s (%d Kommando)"
+msgstr[1] "Rebase von %s auf %s (%d Kommandos)"
 
 #: sequencer.c:4864
-#, fuzzy, c-format
+#, c-format
 msgid "could not copy '%s' to '%s'."
 msgstr "Konnte '%s' nicht nach '%s' kopieren."
 
 #: sequencer.c:4868 sequencer.c:4897
-#, fuzzy
 msgid "could not transform the todo list"
-msgstr "Konnte TODO-Liste nicht erzeugen."
+msgstr "Konnte die TODO-Liste nicht umwandeln."
 
 #: sequencer.c:4900
-#, fuzzy
 msgid "could not skip unnecessary pick commands"
-msgstr "nicht erforderliche \"pick\"-Befehle auslassen"
+msgstr "Konnte unnötige \"pick\"-Befehle nicht auslassen."
 
 #: sequencer.c:4983
 msgid "the script was already rearranged."
 msgstr "Das Script wurde bereits umgeordnet."
 
 #: setup.c:123
 #, c-format
 msgid "'%s' is outside repository"
@@ -6091,17 +6078,17 @@ msgstr "Ignoriere verdächtigen Submodulnamen: %s"
 
 #: submodule-config.c:296
 msgid "negative values not allowed for submodule.fetchjobs"
 msgstr "Negative Werte für submodule.fetchjobs nicht erlaubt."
 
 #: submodule-config.c:390
 #, c-format
 msgid "ignoring '%s' which may be interpreted as a command-line option: %s"
-msgstr ""
+msgstr "Ignoriere '%s', was als eine Befehlszeilenoption '%s' interpretiert werden würde."
 
 #: submodule-config.c:479
 #, c-format
 msgid "invalid value for %s"
 msgstr "Ungültiger Wert für %s"
 
 #: submodule-config.c:754
 #, c-format
@@ -6690,16 +6677,19 @@ msgid "Checking out files"
 msgstr "Checke Dateien aus"
 
 #: unpack-trees.c:368
 msgid ""
 "the following paths have collided (e.g. case-sensitive paths\n"
 "on a case-insensitive filesystem) and only one from the same\n"
 "colliding group is in the working tree:\n"
 msgstr ""
+"Die folgenden Pfade haben kollidiert (z.B. case-sensitive Pfade\n"
+"auf einem case-insensitiven Dateisystem) und nur einer von der\n"
+"selben Kollissionsgruppe ist im Arbeitsverzeichnis:\n"
 
 #: urlmatch.c:163
 msgid "invalid URL scheme name or missing '://' suffix"
 msgstr "Ungültiges URL-Schema oder Suffix '://' fehlt"
 
 #: urlmatch.c:187 urlmatch.c:346 urlmatch.c:405
 #, c-format
 msgid "invalid %XX escape sequence"
@@ -7571,17 +7561,17 @@ msgstr ""
 #, c-format
 msgid "To restore the original branch and stop patching, run \"%s --abort\"."
 msgstr ""
 "Um den ursprünglichen Branch wiederherzustellen und die Anwendung der "
 "Patches abzubrechen, führen Sie \"%s --abort\" aus."
 
 #: builtin/am.c:1196
 msgid "Patch sent with format=flowed; space at the end of lines might be lost."
-msgstr ""
+msgstr "Patch mit format=flowed versendet; Leerzeichen am Ende von Zeilen könnte verloren gehen."
 
 #: builtin/am.c:1224
 msgid "Patch is empty."
 msgstr "Patch ist leer."
 
 #: builtin/am.c:1290
 #, c-format
 msgid "invalid ident line: %.*s"
@@ -8584,19 +8574,18 @@ msgstr ""
 msgid ""
 "git cat-file (--batch | --batch-check) [--follow-symlinks] [--textconv | --"
 "filters]"
 msgstr ""
 "git cat-file (--batch | --batch-check) [--follow-symlinks] [--textconv | --"
 "filters]"
 
 #: builtin/cat-file.c:609
-#, fuzzy
 msgid "only one batch option may be specified"
-msgstr "Nur eine Aktion erlaubt."
+msgstr "Nur eine Batch-Option erlaubt."
 
 #: builtin/cat-file.c:627
 msgid "<type> can be one of: blob, tree, commit, tag"
 msgstr "<Art> kann sein: blob, tree, commit, tag"
 
 #: builtin/cat-file.c:628
 msgid "show object type"
 msgstr "Objektart anzeigen"
@@ -10363,19 +10352,18 @@ msgstr "globale Konfigurationsdatei verwenden"
 msgid "use system config file"
 msgstr "systemweite Konfigurationsdatei verwenden"
 
 #: builtin/config.c:127
 msgid "use repository config file"
 msgstr "Konfigurationsdatei des Repositories verwenden"
 
 #: builtin/config.c:128
-#, fuzzy
 msgid "use per-worktree config file"
-msgstr "Konfigurationsdatei des Repositories verwenden"
+msgstr "Konfigurationsdatei pro Arbeitsverzeichnis verwenden"
 
 #: builtin/config.c:129
 msgid "use given config file"
 msgstr "die angegebene Konfigurationsdatei verwenden"
 
 #: builtin/config.c:130
 msgid "blob-id"
 msgstr "Blob-Id"
@@ -10576,16 +10564,19 @@ msgid "$HOME not set"
 msgstr "$HOME nicht gesetzt."
 
 #: builtin/config.c:657
 msgid ""
 "--worktree cannot be used with multiple working trees unless the config\n"
 "extension worktreeConfig is enabled. Please read \"CONFIGURATION FILE\"\n"
 "section in \"git help worktree\" for details"
 msgstr ""
+"--worktree kann nicht mit mehreren Arbeitsverzeichnissen verwendet werden,\n"
+"außer die Konfigurationserweiterung worktreeConfig ist aktiviert. Bitte\n"
+"lesen Sie die Sektion \"CONFIGURATION_FILE\" in \"git help worktree\" für Details"
 
 #: builtin/config.c:687
 msgid "--get-color and variable type are incoherent"
 msgstr "Angabe von --get-color und Variablentyp sind ungültig."
 
 #: builtin/config.c:692
 msgid "only one action at a time"
 msgstr "Nur eine Aktion erlaubt."
@@ -11030,19 +11021,18 @@ msgstr "fordert von allen Remote-Repositories an"
 msgid "append to .git/FETCH_HEAD instead of overwriting"
 msgstr "an .git/FETCH_HEAD anhängen, anstatt zu überschreiben"
 
 #: builtin/fetch.c:119 builtin/pull.c:200
 msgid "path to upload pack on remote end"
 msgstr "Pfad des Programms zum Hochladen von Paketen auf der Gegenseite"
 
 #: builtin/fetch.c:120
-#, fuzzy
 msgid "force overwrite of local reference"
-msgstr "das Überschreiben von lokalen Branches erzwingen"
+msgstr "das Überschreiben einer lokalen Referenz erzwingen"
 
 #: builtin/fetch.c:122
 msgid "fetch from multiple remotes"
 msgstr "von mehreren Remote-Repositories anfordern"
 
 #: builtin/fetch.c:124 builtin/pull.c:204
 msgid "fetch all tags and associated objects"
 msgstr "alle Tags und verbundene Objekte anfordern"
@@ -11171,17 +11161,17 @@ msgstr "[Tag Aktualisierung]"
 
 #: builtin/fetch.c:731 builtin/fetch.c:771 builtin/fetch.c:787
 #: builtin/fetch.c:802
 msgid "unable to update local ref"
 msgstr "kann lokale Referenz nicht aktualisieren"
 
 #: builtin/fetch.c:735
 msgid "would clobber existing tag"
-msgstr ""
+msgstr "würde bestehende Tags verändern"
 
 #: builtin/fetch.c:757
 msgid "[new tag]"
 msgstr "[neues Tag]"
 
 #: builtin/fetch.c:760
 msgid "[new branch]"
 msgstr "[neuer Branch]"
@@ -11655,19 +11645,18 @@ msgstr "binäre Dateien als Text verarbeiten"
 msgid "don't match patterns in binary files"
 msgstr "keine Muster in Binärdateien finden"
 
 #: builtin/grep.c:822
 msgid "process binary files with textconv filters"
 msgstr "binäre Dateien mit \"textconv\"-Filtern verarbeiten"
 
 #: builtin/grep.c:824
-#, fuzzy
 msgid "search in subdirectories (default)"
-msgstr "lose Referenzen entfernen (Standard)"
+msgstr "in Unterverzeichnissen suchen (Standard)"
 
 #: builtin/grep.c:826
 msgid "descend at most <depth> levels"
 msgstr "höchstens <Tiefe> Ebenen durchlaufen"
 
 #: builtin/grep.c:830
 msgid "use extended POSIX regular expressions"
 msgstr "erweiterte reguläre Ausdrücke aus POSIX verwenden"
@@ -11817,23 +11806,22 @@ msgid "--no-index or --untracked cannot be used with revs"
 msgstr "--no-index oder --untracked können nicht mit Commits verwendet werden"
 
 #: builtin/grep.c:1020
 #, c-format
 msgid "unable to resolve revision: %s"
 msgstr "Konnte Commit nicht auflösen: %s"
 
 #: builtin/grep.c:1051
-#, fuzzy
 msgid "invalid option combination, ignoring --threads"
-msgstr "keine Unterstützung von Threads, --threads wird ignoriert"
+msgstr "Ungültige Kombination von Optionen, --threads wird ignoriert."
 
 #: builtin/grep.c:1054 builtin/pack-objects.c:3397
 msgid "no threads support, ignoring --threads"
-msgstr "keine Unterstützung von Threads, --threads wird ignoriert"
+msgstr "Keine Unterstützung für Threads, --threads wird ignoriert."
 
 #: builtin/grep.c:1057 builtin/index-pack.c:1503 builtin/pack-objects.c:2716
 #, c-format
 msgid "invalid number of threads specified (%d)"
 msgstr "ungültige Anzahl von Threads angegeben (%d)"
 
 #: builtin/grep.c:1080
 msgid "--open-files-in-pager only works on the worktree"
@@ -11993,19 +11981,19 @@ msgid "no info viewer handled the request"
 msgstr "kein Informations-Betrachter konnte mit dieser Anfrage umgehen"
 
 #: builtin/help.c:430 builtin/help.c:441 git.c:322
 #, c-format
 msgid "'%s' is aliased to '%s'"
 msgstr "Für '%s' wurde der Alias '%s' angelegt."
 
 #: builtin/help.c:444
-#, fuzzy, c-format
+#, c-format
 msgid "bad alias.%s string: %s"
-msgstr "Ungültiger branch.%s.mergeoptions String: %s"
+msgstr "Ungültiger alias.%s String: %s"
 
 #: builtin/help.c:473 builtin/help.c:503
 #, c-format
 msgid "usage: %s%s"
 msgstr "Verwendung: %s%s"
 
 #: builtin/help.c:487
 msgid "'git help config' for more information"
@@ -12460,17 +12448,17 @@ msgid "join whitespace-continued values"
 msgstr "durch Leerzeichen fortgesetzte Werte verbinden"
 
 #: builtin/interpret-trailers.c:107
 msgid "set parsing options"
 msgstr "Optionen für das Parsen setzen"
 
 #: builtin/interpret-trailers.c:109
 msgid "do not treat --- specially"
-msgstr ""
+msgstr "--- nicht speziell behandeln"
 
 #: builtin/interpret-trailers.c:110
 msgid "trailer"
 msgstr "Anhang"
 
 #: builtin/interpret-trailers.c:111
 msgid "trailer(s) to add"
 msgstr "Anhang/Anhänge hinzufügen"
@@ -12621,19 +12609,18 @@ msgstr "Basis-Commit sollte der Vorgänger der Revisionsliste sein."
 msgid "base commit shouldn't be in revision list"
 msgstr "Basis-Commit sollte nicht in der Revisionsliste enthalten sein."
 
 #: builtin/log.c:1418
 msgid "cannot get patch id"
 msgstr "kann Patch-Id nicht lesen"
 
 #: builtin/log.c:1470
-#, fuzzy
 msgid "failed to infer range-diff ranges"
-msgstr "Fehler beim Generieren des Diffs."
+msgstr "Fehler beim Ableiten des range-diff-Bereichs."
 
 #: builtin/log.c:1515
 msgid "use [PATCH n/m] even with a single patch"
 msgstr "[PATCH n/m] auch mit einzelnem Patch verwenden"
 
 #: builtin/log.c:1518
 msgid "use [PATCH] even with multiple patches"
 msgstr "[PATCH] auch mit mehreren Patches verwenden"
@@ -12781,30 +12768,28 @@ msgstr "eine Signatur aus einer Datei hinzufügen"
 msgid "don't print the patch filenames"
 msgstr "keine Dateinamen der Patches anzeigen"
 
 #: builtin/log.c:1584
 msgid "show progress while generating patches"
 msgstr "Forschrittsanzeige während der Erzeugung der Patches"
 
 #: builtin/log.c:1585
-#, fuzzy
 msgid "rev"
-msgstr "Revert"
+msgstr "Commit"
 
 #: builtin/log.c:1586
 msgid "show changes against <rev> in cover letter or single patch"
-msgstr ""
+msgstr "Änderungen gegenüber <Commit> im Deckblatt oder einzelnem Patch anzeigen"
 
 #: builtin/log.c:1589
 msgid "show changes against <refspec> in cover letter or single patch"
-msgstr ""
+msgstr "Änderungen gegenüber <Refspec> im Deckblatt oder einzelnem Patch anzeigen"
 
 #: builtin/log.c:1591
-#, fuzzy
 msgid "percentage by which creation is weighted"
 msgstr "Prozentsatz mit welchem Erzeugung gewichtet wird"
 
 #: builtin/log.c:1666
 #, c-format
 msgid "invalid ident line: %s"
 msgstr "Ungültige Identifikationszeile: %s"
 
@@ -12834,43 +12819,43 @@ msgstr "Standard-Ausgabe oder Verzeichnis, welches von beidem?"
 
 #: builtin/log.c:1729
 #, c-format
 msgid "Could not create directory '%s'"
 msgstr "Konnte Verzeichnis '%s' nicht erstellen."
 
 #: builtin/log.c:1816
 msgid "--interdiff requires --cover-letter or single patch"
-msgstr ""
+msgstr "--interdiff erfordert --cover-letter oder einzelnen Patch."
 
 #: builtin/log.c:1820
 msgid "Interdiff:"
-msgstr ""
+msgstr "Interdiff:"
 
 #: builtin/log.c:1821
 #, c-format
 msgid "Interdiff against v%d:"
-msgstr ""
+msgstr "Interdiff gegen v%d:"
 
 #: builtin/log.c:1827
 msgid "--creation-factor requires --range-diff"
-msgstr ""
+msgstr "--creation-factor erfordert --range-diff"
 
 #: builtin/log.c:1831
 msgid "--range-diff requires --cover-letter or single patch"
-msgstr ""
+msgstr "--range-diff erfordert --cover-letter oder einzelnen Patch."
 
 #: builtin/log.c:1839
 msgid "Range-diff:"
-msgstr ""
+msgstr "Range-Diff:"
 
 #: builtin/log.c:1840
 #, c-format
 msgid "Range-diff against v%d:"
-msgstr ""
+msgstr "Range-Diff gegen v%d:"
 
 #: builtin/log.c:1851
 #, c-format
 msgid "unable to read signature file '%s'"
 msgstr "Konnte Signatur-Datei '%s' nicht lesen"
 
 #: builtin/log.c:1887
 msgid "Generating patches"
@@ -13585,31 +13570,30 @@ msgid "allow missing objects"
 msgstr "fehlende Objekte erlauben"
 
 #: builtin/mktree.c:156
 msgid "allow creation of more than one tree"
 msgstr "die Erstellung von mehr als einem \"Tree\"-Objekt erlauben"
 
 #: builtin/multi-pack-index.c:8
 msgid "git multi-pack-index [--object-dir=<dir>] (write|verify)"
-msgstr ""
+msgstr "git multi-pack-index [--object-dir=<Verzeichnis>] (write|verify)"
 
 #: builtin/multi-pack-index.c:21
 msgid "object directory containing set of packfile and pack-index pairs"
-msgstr ""
+msgstr "Objekt-Verzeichnis, welches Paare von Packdateien und pack-index enthält"
 
 #: builtin/multi-pack-index.c:39
-#, fuzzy
 msgid "too many arguments"
 msgstr "Zu viele Argumente."
 
 #: builtin/multi-pack-index.c:48
-#, fuzzy, c-format
+#, c-format
 msgid "unrecognized verb: %s"
-msgstr "nicht erkanntes Argument: %s"
+msgstr "Nicht erkanntes Verb: %s"
 
 #: builtin/mv.c:17
 msgid "git mv [<options>] <source>... <destination>"
 msgstr "git mv [<Optionen>] <Quelle>... <Ziel>"
 
 #: builtin/mv.c:82
 #, c-format
 msgid "Directory %s is in index and no submodule?"
@@ -14268,19 +14252,19 @@ msgstr "Konnte Kopfbereich von Objekt '%s' nicht parsen."
 
 #: builtin/pack-objects.c:2083 builtin/pack-objects.c:2099
 #: builtin/pack-objects.c:2109
 #, c-format
 msgid "object %s cannot be read"
 msgstr "Objekt %s kann nicht gelesen werden."
 
 #: builtin/pack-objects.c:2086 builtin/pack-objects.c:2113
-#, fuzzy, c-format
+#, c-format
 msgid "object %s inconsistent object length (%<PRIuMAX> vs %<PRIuMAX>)"
-msgstr "Inkonsistente Objektlänge bei Objekt %s (%lu vs. %lu)"
+msgstr "Inkonsistente Objektlänge bei Objekt %s (%<PRIuMAX> vs %<PRIuMAX>)"
 
 #: builtin/pack-objects.c:2123
 msgid "suboptimal pack - out of memory"
 msgstr "ungünstiges Packet - Speicher voll"
 
 #: builtin/pack-objects.c:2451
 #, c-format
 msgid "Delta compression using up to %d threads"
@@ -14512,19 +14496,18 @@ msgstr "Behandlung für fehlende Objekte"
 
 #: builtin/pack-objects.c:3316
 msgid "do not pack objects in promisor packfiles"
 msgstr ""
 "keine Objekte aus Packdateien von partiell geklonten Remote-Repositories "
 "packen"
 
 #: builtin/pack-objects.c:3318
-#, fuzzy
 msgid "respect islands during delta compression"
-msgstr "Größe des Fensters für die Delta-Kompression"
+msgstr "Delta-Islands bei Delta-Kompression beachten"
 
 #: builtin/pack-objects.c:3342
 #, c-format
 msgid "delta chain depth %d is too deep, forcing %d"
 msgstr "Tiefe für Verkettung von Unterschieden %d ist zu tief, erzwinge %d"
 
 #: builtin/pack-objects.c:3347
 #, c-format
@@ -14735,19 +14718,19 @@ msgid ""
 "Your configuration specifies to merge with the ref '%s'\n"
 "from the remote, but no such ref was fetched."
 msgstr ""
 "Ihre Konfiguration gibt an, den Merge mit Referenz '%s'\n"
 "des Remote-Repositories durchzuführen, aber diese Referenz\n"
 "wurde nicht angefordert."
 
 #: builtin/pull.c:565
-#, fuzzy, c-format
+#, c-format
 msgid "unable to access commit %s"
-msgstr "Konnte Commit '%s' nicht parsen."
+msgstr "Konnte nicht auf Commit '%s' zugreifen."
 
 #: builtin/pull.c:843
 msgid "ignoring --verify-signatures for rebase"
 msgstr "Ignoriere --verify-signatures für Rebase"
 
 #: builtin/pull.c:891
 msgid "--[no-]autostash option is only valid with --rebase."
 msgstr "--[no-]autostash ist nur mit --rebase zulässig."
@@ -14824,17 +14807,17 @@ msgid ""
 "\n"
 "To choose either option permanently, see push.default in 'git help config'."
 msgstr ""
 "\n"
 "Um eine Variante permanent zu verwenden, siehe push.default in 'git help "
 "config'."
 
 #: builtin/push.c:168
-#, fuzzy, c-format
+#, c-format
 msgid ""
 "The upstream branch of your current branch does not match\n"
 "the name of your current branch.  To push to the upstream branch\n"
 "on the remote, use\n"
 "\n"
 "    git push %s HEAD:%s\n"
 "\n"
 "To push to the branch of the same name on the remote, use\n"
@@ -14846,17 +14829,17 @@ msgstr ""
 "aktuellen Branches überein. Um auf den Upstream-Branch in dem Remote-\n"
 "Repository zu versenden, benutzen Sie:\n"
 "\n"
 "    git push %s HEAD:%s\n"
 "\n"
 "Um auf den Branch mit demselben Namen im Remote-Repository zu versenden,\n"
 "benutzen Sie:\n"
 "\n"
-"    git push %s %s\n"
+"    git push %s HEAD\n"
 "%s"
 
 #: builtin/push.c:183
 #, c-format
 msgid ""
 "You are not currently on a branch.\n"
 "To push the history leading to the current (detached HEAD)\n"
 "state now, use\n"
@@ -15214,63 +15197,59 @@ msgstr "weder den Index, noch das Arbeitsverzeichnis aktualisieren"
 msgid "skip applying sparse checkout filter"
 msgstr "Anwendung des Filters für partielles Auschecken überspringen"
 
 #: builtin/read-tree.c:152
 msgid "debug unpack-trees"
 msgstr "unpack-trees protokollieren"
 
 #: builtin/rebase.c:29
-#, fuzzy
 msgid ""
 "git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] [<upstream>] "
 "[<branch>]"
-msgstr ""
-"git archive --remote <Repository> [--exec <Programm>] [<Optionen>] <Commit-"
-"Referenz> [<Pfad>...]"
+msgstr "git rebase [-i] [<Optionen>] [--exec <Programm>] [--onto <neue-Basis>] [<Upstream>] [<Branch>]"
 
 #: builtin/rebase.c:31
 msgid ""
 "git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] --root [<branch>]"
-msgstr ""
+msgstr "git rebase [-i] [<Optionen>] [--exec <Programm>] [--onto <neue-Basis>] --root [<Branch>]"
 
 #: builtin/rebase.c:33
-#, fuzzy
 msgid "git rebase --continue | --abort | --skip | --edit-todo"
-msgstr "git am [<Optionen>] (--continue | --skip | --abort)"
+msgstr "git rebase --continue | --abort | --skip | --edit-todo"
 
 #: builtin/rebase.c:119
-#, fuzzy, c-format
+#, c-format
 msgid "%s requires an interactive rebase"
-msgstr "interaktiv ausführen"
+msgstr "%s erfordert ein interaktives Rebase"
 
 #: builtin/rebase.c:171
-#, fuzzy, c-format
+#, c-format
 msgid "could not get 'onto': '%s'"
-msgstr "Konnte '%s' nicht zu '%s' setzen."
+msgstr "Konnte 'onto' nicht bestimmen: '%s'"
 
 #: builtin/rebase.c:186
-#, fuzzy, c-format
+#, c-format
 msgid "invalid orig-head: '%s'"
-msgstr "Ungültige Datei: '%s'"
+msgstr "Ungültiges orig-head: '%s'"
 
 #: builtin/rebase.c:214
 #, c-format
 msgid "ignoring invalid allow_rerere_autoupdate: '%s'"
-msgstr ""
+msgstr "Ignoriere ungültiges allow_rerere_autoupdate: '%s'"
 
 #: builtin/rebase.c:259
-#, fuzzy, c-format
+#, c-format
 msgid "Could not read '%s'"
-msgstr "Konnte '%s' nicht lesen"
+msgstr "Konnte '%s' nicht lesen."
 
 #: builtin/rebase.c:277
-#, fuzzy, c-format
+#, c-format
 msgid "Cannot store %s"
-msgstr "kann %s nicht speichern"
+msgstr "Kann %s nicht speichern."
 
 #: builtin/rebase.c:337
 msgid ""
 "Resolve all conflicts manually, mark them as resolved with\n"
 "\"git add/rm <conflicted_files>\", then run \"git rebase --continue\".\n"
 "You can instead skip this commit: run \"git rebase --skip\".\n"
 "To abort and get back to the state before \"git rebase\", run \"git rebase --"
 "abort\"."
@@ -15279,191 +15258,174 @@ msgstr ""
 "\"git add/rm <konfliktbehaftete_Dateien>\" und führen Sie dann\n"
 "\"git rebase --continue\" aus.\n"
 "Sie können auch stattdessen diesen Commit auslassen, indem\n"
 "Sie \"git rebase --skip\" ausführen.\n"
 "Um abzubrechen und zurück zum Zustand vor \"git rebase\" zu gelangen,\n"
 "führen Sie \"git rebase --abort\" aus."
 
 #: builtin/rebase.c:561
-#, fuzzy
 msgid "could not determine HEAD revision"
-msgstr "Konnte HEAD nicht loslösen"
+msgstr "Konnte HEAD-Commit nicht bestimmen."
 
 #: builtin/rebase.c:753
-#, fuzzy, c-format
+#, c-format
 msgid ""
 "%s\n"
 "Please specify which branch you want to rebase against.\n"
 "See git-rebase(1) for details.\n"
 "\n"
 "    git rebase '<branch>'\n"
 "\n"
 msgstr ""
-"Bitte geben Sie den Branch an, gegen welchen Sie \"rebase\" ausführen "
-"möchten."
+"%s\n"
+"Bitte geben Sie den Branch an, gegen welchen Sie \"rebase\" ausführen möchten.\n"
+"Siehe git-rebase(1) für Details.\n"
+"\n"
+"    git rebase '<Branch>'\n"
+"\n"
 
 #: builtin/rebase.c:769
-#, fuzzy, c-format
+#, c-format
 msgid ""
 "If you wish to set tracking information for this branch you can do so with:\n"
 "\n"
 "    git branch --set-upstream-to=%s/<branch> %s\n"
 "\n"
 msgstr ""
-"Wenn Sie Tracking-Informationen für diesen Branch setzen möchten, können "
-"Sie\n"
-"dies tun mit:"
+"Wenn Sie Tracking-Informationen für diesen Branch setzen möchten,\n"
+"können Sie dies tun mit:\n"
+"\n"
+"    git branch --set-upstream-to=%s/<Branch> %s\n"
+"\n"
 
 #: builtin/rebase.c:832
-#, fuzzy
 msgid "rebase onto given branch instead of upstream"
-msgstr "Branch %s kann nicht sein eigener Upstream-Branch sein."
+msgstr "Rebase auf angegebenen Branch anstelle des Upstream-Branches ausführen"
 
 #: builtin/rebase.c:834
-#, fuzzy
 msgid "allow pre-rebase hook to run"
-msgstr "Der \"pre-rebase hook\" hat den Rebase zurückgewiesen."
+msgstr "Ausführung des pre-rebase-Hooks erlauben"
 
 #: builtin/rebase.c:836
 msgid "be quiet. implies --no-stat"
-msgstr ""
+msgstr "weniger Ausgaben (impliziert --no-stat)"
 
 #: builtin/rebase.c:839
 msgid "display a diffstat of what changed upstream"
-msgstr ""
+msgstr "Zusammenfassung der Unterschiede gegenüber dem Upstream-Branch anzeigen"
 
 #: builtin/rebase.c:842
-#, fuzzy
 msgid "do not show diffstat of what changed upstream"
-msgstr "keine Zusammenfassung der Unterschiede am Schluss des Merges anzeigen"
+msgstr "Zusammenfassung der Unterschiede gegenüber dem Upstream-Branch verbergen"
 
 #: builtin/rebase.c:845
-#, fuzzy
 msgid "add a Signed-off-by: line to each commit"
-msgstr "der Commit-Beschreibung eine Signed-off-by Zeile hinzufügen"
+msgstr "eine \"Signed-off-by:\"-Zeile zu jedem Commit hinzufügen"
 
 #: builtin/rebase.c:847 builtin/rebase.c:851 builtin/rebase.c:853
 msgid "passed to 'git am'"
-msgstr ""
+msgstr "an 'git am' übergeben"
 
 #: builtin/rebase.c:855 builtin/rebase.c:857
-#, fuzzy
 msgid "passed to 'git apply'"
-msgstr "an git-apply übergeben"
+msgstr "an 'git apply' übergeben"
 
 #: builtin/rebase.c:859 builtin/rebase.c:862
 msgid "cherry-pick all commits, even if unchanged"
-msgstr ""
+msgstr "Cherry-Pick auf alle Commits ausführen, auch wenn diese unverändert sind"
 
 #: builtin/rebase.c:864
-#, fuzzy
 msgid "continue"
-msgstr "Rebase fortsetzen"
+msgstr "fortsetzen"
 
 #: builtin/rebase.c:867
-#, fuzzy
 msgid "skip current patch and continue"
-msgstr "den aktuellen Patch auslassen"
+msgstr "den aktuellen Patch auslassen und fortfahren"
 
 #: builtin/rebase.c:869
-#, fuzzy
 msgid "abort and check out the original branch"
-msgstr ""
-"  (benutzen Sie \"git rebase --abort\", um den ursprünglichen Branch "
-"auszuchecken)"
+msgstr "abbrechen und den ursprünglichen Branch auschecken"
 
 #: builtin/rebase.c:872
-#, fuzzy
 msgid "abort but keep HEAD where it is"
-msgstr "Patch-Operation abbrechen, aber HEAD an aktueller Stelle belassen"
+msgstr "abbrechen, aber HEAD an aktueller Stelle belassen"
 
 #: builtin/rebase.c:873
-#, fuzzy
 msgid "edit the todo list during an interactive rebase"
-msgstr ""
-"Die --edit-todo Aktion kann nur während eines interaktiven Rebase verwendet "
-"werden."
+msgstr "TODO-Liste während eines interaktiven Rebase bearbeiten"
 
 #: builtin/rebase.c:876
-#, fuzzy
 msgid "show the patch file being applied or merged"
-msgstr "den Patch, der gerade angewendet wird, anzeigen"
+msgstr "den Patch, der gerade angewendet oder zusammengeführt wird, anzeigen"
 
 #: builtin/rebase.c:879
-#, fuzzy
 msgid "use merging strategies to rebase"
-msgstr "zu verwendende Merge-Strategie"
+msgstr "Merge-Strategien beim Rebase verwenden"
 
 #: builtin/rebase.c:883
 msgid "let the user edit the list of commits to rebase"
-msgstr ""
+msgstr "den Benutzer die Liste der Commits für den Rebase bearbeiten lassen"
 
 #: builtin/rebase.c:887
 msgid "try to recreate merges instead of ignoring them"
-msgstr ""
+msgstr "versuchen, Merges wiederherzustellen anstatt sie zu ignorieren"
 
 #: builtin/rebase.c:891
 msgid "allow rerere to update index with resolved conflict"
-msgstr ""
+msgstr "Rerere erlauben, den Index mit dem aufgelöstem Konflikt zu aktualisieren"
 
 #: builtin/rebase.c:894
-#, fuzzy
 msgid "preserve empty commits during rebase"
-msgstr "ursprüngliche, leere Commits erhalten"
+msgstr "leere Commits während des Rebase erhalten"
 
 #: builtin/rebase.c:896
 msgid "move commits that begin with squash!/fixup! under -i"
-msgstr ""
+msgstr "bei -i Commits verschieben, die mit squash!/fixup! beginnen"
 
 #: builtin/rebase.c:902
-#, fuzzy
 msgid "automatically stash/stash pop before and after"
-msgstr "automatischer Stash/Stash-Pop vor und nach eines Rebase"
+msgstr "automatischer Stash/Stash-Pop davor und danach"
 
 #: builtin/rebase.c:904
 msgid "add exec lines after each commit of the editable list"
-msgstr ""
+msgstr "exec-Zeilen nach jedem Commit der editierbaren Liste hinzufügen"
 
 #: builtin/rebase.c:908
-#, fuzzy
 msgid "allow rebasing commits with empty messages"
-msgstr "Commits mit leerer Beschreibung erlauben"
+msgstr "Rebase von Commits mit leerer Beschreibung erlauben"
 
 #: builtin/rebase.c:911
 msgid "try to rebase merges instead of skipping them"
-msgstr ""
+msgstr "versuchen, Rebase mit Merges auszuführen, anstatt diese zu überspringen"
 
 #: builtin/rebase.c:914
-#, fuzzy
 msgid "use 'merge-base --fork-point' to refine upstream"
-msgstr "git merge-base --fork-point <Referenz> [<Commit>]"
+msgstr "'git merge-base --fork-point' benutzen, um Upstream-Branch zu bestimmen"
 
 #: builtin/rebase.c:916
-#, fuzzy
 msgid "use the given merge strategy"
-msgstr "Option für Merge-Strategie"
+msgstr "angegebene Merge-Strategie verwenden"
 
 #: builtin/rebase.c:918 builtin/revert.c:111
 msgid "option"
 msgstr "Option"
 
 #: builtin/rebase.c:919
 msgid "pass the argument through to the merge strategy"
-msgstr ""
+msgstr "Argument zur Merge-Strategie durchreichen"
 
 #: builtin/rebase.c:922
-#, fuzzy
 msgid "rebase all reachable commits up to the root(s)"
-msgstr "alle nicht erreichbaren Objekte von der Objektdatenbank entfernen"
+msgstr "Rebase auf alle erreichbaren Commits bis zum Root-Commit ausführen"
 
 #: builtin/rebase.c:938
-#, fuzzy, c-format
+#, c-format
 msgid "could not exec %s"
-msgstr "konnte %s nicht parsen"
+msgstr "Konnte 'exec %s' nicht ausführen."
 
 #: builtin/rebase.c:956 git-legacy-rebase.sh:213
 msgid "It looks like 'git am' is in progress. Cannot rebase."
 msgstr "'git-am' scheint im Gange zu sein. Kann Rebase nicht durchführen."
 
 #: builtin/rebase.c:997 git-legacy-rebase.sh:395
 msgid "No rebase in progress?"
 msgstr "Kein Rebase im Gange?"
@@ -15482,262 +15444,242 @@ msgstr "Kann HEAD nicht lesen"
 msgid ""
 "You must edit all merge conflicts and then\n"
 "mark them as resolved using git add"
 msgstr ""
 "Sie müssen alle Merge-Konflikte editieren und diese dann\n"
 "mittels \"git add\" als aufgelöst markieren"
 
 #: builtin/rebase.c:1047
-#, fuzzy
 msgid "could not discard worktree changes"
-msgstr "Kann Änderungen im Arbeitsverzeichnis nicht löschen"
+msgstr "Konnte Änderungen im Arbeitsverzeichnis nicht verwerfen."
 
 #: builtin/rebase.c:1066
-#, fuzzy, c-format
+#, c-format
 msgid "could not move back to %s"
-msgstr "Konnte nicht zu $head_name zurückgehen"
+msgstr "Konnte nicht zu %s zurückgehen."
 
 #: builtin/rebase.c:1077 builtin/rm.c:368
 #, c-format
 msgid "could not remove '%s'"
 msgstr "Konnte '%s' nicht löschen"
 
 #: builtin/rebase.c:1103
-#, fuzzy, c-format
+#, c-format
 msgid ""
 "It seems that there is already a %s directory, and\n"
 "I wonder if you are in the middle of another rebase.  If that is the\n"
 "case, please try\n"
 "\t%s\n"
 "If that is not the case, please\n"
 "\t%s\n"
 "and run me again.  I am stopping in case you still have something\n"
 "valuable there.\n"
 msgstr ""
-"Es sieht so aus, als ob es das Verzeichnis $state_dir_base bereits gibt\n"
+"Es sieht so aus, als ob es das Verzeichnis %s bereits gibt\n"
 "und es könnte ein anderer Rebase im Gange sein. Wenn das der Fall ist,\n"
 "probieren Sie bitte\n"
-"\t$cmd_live_rebase\n"
+"\t%s\n"
 "Wenn das nicht der Fall ist, probieren Sie bitte\n"
-"\t$cmd_clear_stale_rebase\n"
+"\t%s\n"
 "und führen Sie diesen Befehl nochmal aus. Es wird angehalten, falls noch\n"
-"etwas Schützenswertes vorhanden ist."
+"etwas Schützenswertes vorhanden ist.\n"
 
 #: builtin/rebase.c:1124
-#, fuzzy
 msgid "switch `C' expects a numerical value"
-msgstr "Schalter '%c' erwartet einen numerischen Wert"
+msgstr "Schalter `C' erwartet einen numerischen Wert."
 
 #: builtin/rebase.c:1161
-#, fuzzy, c-format
+#, c-format
 msgid "Unknown mode: %s"
-msgstr "Unbekannter --patch Modus: %s"
+msgstr "Unbekannter Modus: %s"
 
 #: builtin/rebase.c:1183
 msgid "--strategy requires --merge or --interactive"
-msgstr ""
+msgstr "--strategy erfordert --merge oder --interactive"
 
 #: builtin/rebase.c:1226
 #, c-format
 msgid ""
 "error: cannot combine interactive options (--interactive, --exec, --rebase-"
 "merges, --preserve-merges, --keep-empty, --root + --onto) with am options "
 "(%s)"
-msgstr ""
+msgstr "Fehler: 'interactive'-Optionen (--interactive, --exec, --rebase-merges, --preserve-merges, --keep-empty, --root + --onto ) können nicht mit 'am'-Optionen (%s) kombiniert werden."
 
 #: builtin/rebase.c:1231
-#, fuzzy, c-format
+#, c-format
 msgid ""
 "error: cannot combine merge options (--merge, --strategy, --strategy-option) "
 "with am options (%s)"
-msgstr ""
-"Fehler: '--rebase-merges' und '--strategy-option' können nicht kombiniert "
-"werden."
+msgstr "Fehler: 'merge'-Optionen (--merge, --strategy, --strategy-option) können nicht mit 'am'-Optionen (%s) kombiniert werden."
 
 #: builtin/rebase.c:1251 git-legacy-rebase.sh:536
-#, fuzzy
 msgid "error: cannot combine '--preserve-merges' with '--rebase-merges'"
-msgstr ""
-"Fehler: '--preserve-merges' und '--rebase-merges' können nicht kombiniert "
-"werden."
+msgstr "Fehler: '--preserve-merges' und '--rebase-merges' können nicht kombiniert werden."
 
 #: builtin/rebase.c:1256 git-legacy-rebase.sh:542
-#, fuzzy
 msgid "error: cannot combine '--rebase-merges' with '--strategy-option'"
 msgstr ""
 "Fehler: '--rebase-merges' und '--strategy-option' können nicht kombiniert "
 "werden."
 
 #: builtin/rebase.c:1259 git-legacy-rebase.sh:544
-#, fuzzy
 msgid "error: cannot combine '--rebase-merges' with '--strategy'"
 msgstr ""
 "Fehler: '--rebase-merges' und '--strategy' können nicht kombiniert werden."
 
 #: builtin/rebase.c:1283
-#, fuzzy, c-format
+#, c-format
 msgid "invalid upstream '%s'"
-msgstr "Ungültiger Pfad '%s'"
+msgstr "Ungültiger Upstream '%s'"
 
 #: builtin/rebase.c:1289
-#, fuzzy
 msgid "Could not create new root commit"
-msgstr "Konnte neu erstellten Commit nicht analysieren."
+msgstr "Konnte neuen Root-Commit nicht erstellen."
 
 #: builtin/rebase.c:1307
-#, fuzzy, c-format
+#, c-format
 msgid "'%s': need exactly one merge base"
-msgstr "Brauche genau einen Commit-Bereich."
+msgstr "'%s': benötige genau eine Merge-Basis"
 
 #: builtin/rebase.c:1314
-#, fuzzy, c-format
+#, c-format
 msgid "Does not point to a valid commit '%s'"
-msgstr "$onto_name zeigt auf keinen gültigen Commit"
+msgstr "'%s' zeigt auf keinen gültigen Commit."
 
 #: builtin/rebase.c:1339
-#, fuzzy, c-format
+#, c-format
 msgid "fatal: no such branch/commit '%s'"
-msgstr "fatal: Branch/Commit '$branch_name' nicht gefunden"
+msgstr "fatal: Branch/Commit '%s' nicht gefunden"
 
 #: builtin/rebase.c:1347 builtin/submodule--helper.c:37
 #: builtin/submodule--helper.c:1930
 #, c-format
 msgid "No such ref: %s"
 msgstr "Referenz nicht gefunden: %s"
 
 #: builtin/rebase.c:1359
-#, fuzzy
 msgid "Could not resolve HEAD to a revision"
-msgstr "Konnte HEAD-Commit nicht auflösen."
+msgstr "Konnte HEAD zu keinem Commit auflösen."
 
 #: builtin/rebase.c:1399 git-legacy-rebase.sh:665
 msgid "Cannot autostash"
 msgstr "Kann automatischen Stash nicht erzeugen."
 
 #: builtin/rebase.c:1402
-#, fuzzy, c-format
+#, c-format
 msgid "Unexpected stash response: '%s'"
-msgstr "Unerwartetes wanted-ref: '%s'"
+msgstr "Unerwartete 'stash'-Antwort: '%s'"
 
 #: builtin/rebase.c:1408
-#, fuzzy, c-format
+#, c-format
 msgid "Could not create directory for '%s'"
-msgstr "Konnte Verzeichnis '%s' nicht erstellen."
+msgstr "Konnte Verzeichnis für '%s' nicht erstellen."
 
 #: builtin/rebase.c:1411
-#, fuzzy, c-format
+#, c-format
 msgid "Created autostash: %s\n"
-msgstr "Automatischen Stash erzeugt: $stash_abbrev"
+msgstr "Automatischen Stash erzeugt: %s\n"
 
 #: builtin/rebase.c:1414
-#, fuzzy
 msgid "could not reset --hard"
-msgstr "Konnte orig-head nicht lesen."
+msgstr "Konnte 'reset --hard' nicht ausführen."
 
 #: builtin/rebase.c:1415 builtin/reset.c:113
 #, c-format
 msgid "HEAD is now at %s"
 msgstr "HEAD ist jetzt bei %s"
 
 #: builtin/rebase.c:1431 git-legacy-rebase.sh:674
 msgid "Please commit or stash them."
 msgstr "Bitte committen Sie die Änderungen oder benutzen Sie \"stash\"."
 
 #: builtin/rebase.c:1458
-#, fuzzy, c-format
+#, c-format
 msgid "could not parse '%s'"
-msgstr "konnte %s nicht parsen"
+msgstr "Konnte '%s' nicht parsen."
 
 #: builtin/rebase.c:1470
-#, fuzzy, c-format
+#, c-format
 msgid "could not switch to %s"
-msgstr "Konnte nicht nach '%s' schreiben."
+msgstr "Konnte nicht zu %s wechseln."
 
 #: builtin/rebase.c:1481 git-legacy-rebase.sh:697
 #, sh-format
 msgid "HEAD is up to date."
 msgstr "HEAD ist aktuell."
 
 #: builtin/rebase.c:1483
-#, fuzzy, c-format
+#, c-format
 msgid "Current branch %s is up to date.\n"
-msgstr "Aktueller Branch $branch_name ist auf dem neuesten Stand."
+msgstr "Aktueller Branch %s ist auf dem neuesten Stand.\n"
 
 #: builtin/rebase.c:1491 git-legacy-rebase.sh:707
 #, sh-format
 msgid "HEAD is up to date, rebase forced."
 msgstr "HEAD ist aktuell, Rebase erzwungen."
 
 #: builtin/rebase.c:1493
-#, fuzzy, c-format
+#, c-format
 msgid "Current branch %s is up to date, rebase forced.\n"
-msgstr ""
-"Aktueller Branch $branch_name ist auf dem neuesten Stand, Rebase erzwungen."
+msgstr "Aktueller Branch %s ist auf dem neuesten Stand, Rebase erzwungen.\n"
 
 #: builtin/rebase.c:1501 git-legacy-rebase.sh:208
 msgid "The pre-rebase hook refused to rebase."
 msgstr "Der \"pre-rebase hook\" hat den Rebase zurückgewiesen."
 
 #: builtin/rebase.c:1508
-#, fuzzy, c-format
+#, c-format
 msgid "Changes to %s:\n"
-msgstr "Änderungen von $mb zu $onto:"
+msgstr "Änderungen zu %s:\n"
 
 #: builtin/rebase.c:1511
-#, fuzzy, c-format
+#, c-format
 msgid "Changes from %s to %s:\n"
-msgstr "Änderungen von $mb zu $onto:"
+msgstr "Änderungen von %s zu %s:\n"
 
 #: builtin/rebase.c:1536
-#, fuzzy, c-format
+#, c-format
 msgid "First, rewinding head to replay your work on top of it...\n"
-msgstr ""
-"Zunächst wird der Branch zurückgespult, um Ihre Änderungen\n"
-"darauf neu anzuwenden ..."
+msgstr "Zunächst wird der Branch zurückgespult, um Ihre Änderungen darauf neu anzuwenden...\n"
 
 #: builtin/rebase.c:1543
-#, fuzzy
 msgid "Could not detach HEAD"
-msgstr "Konnte HEAD nicht loslösen"
+msgstr "Konnte HEAD nicht loslösen."
 
 #: builtin/rebase.c:1552
-#, fuzzy, c-format
+#, c-format
 msgid "Fast-forwarded %s to %s.\n"
-msgstr "Spule vor zu $sha1"
+msgstr "Spule %s vor zu %s.\n"
 
 #: builtin/rebase--interactive.c:24
-#, fuzzy
 msgid "no HEAD?"
 msgstr "Kein HEAD?"
 
 #: builtin/rebase--interactive.c:51
-#, fuzzy, c-format
+#, c-format
 msgid "could not create temporary %s"
-msgstr "konnte temporäre Datei nicht erstellen"
+msgstr "Konnte temporäres Verzeichnis '%s' nicht erstellen."
 
 #: builtin/rebase--interactive.c:57
-#, fuzzy
 msgid "could not mark as interactive"
-msgstr "Konnte nicht als interaktiven Rebase markieren."
+msgstr "Markierung auf interaktiven Rebase fehlgeschlagen."
 
 #: builtin/rebase--interactive.c:101
-#, fuzzy, c-format
+#, c-format
 msgid "could not open %s"
-msgstr "Konnte '%s' nicht öffnen"
+msgstr "Konnte '%s' nicht öffnen."
 
 #: builtin/rebase--interactive.c:114
-#, fuzzy
 msgid "could not generate todo list"
 msgstr "Konnte TODO-Liste nicht erzeugen."
 
 #: builtin/rebase--interactive.c:129
-#, fuzzy
 msgid "git rebase--interactive [<options>]"
-msgstr "git rebase--helper [<Optionen>]"
+msgstr "git rebase--interactive [<Optionen>]"
 
 #: builtin/rebase--interactive.c:148
 msgid "keep empty commits"
 msgstr "leere Commits behalten"
 
 #: builtin/rebase--interactive.c:150 builtin/revert.c:124
 msgid "allow commits with empty messages"
 msgstr "Commits mit leerer Beschreibung erlauben"
@@ -15747,41 +15689,37 @@ msgid "rebase merge commits"
 msgstr "Rebase auf Merge-Commits ausführen"
 
 #: builtin/rebase--interactive.c:153
 msgid "keep original branch points of cousins"
 msgstr "originale Branch-Punkte der Cousins behalten"
 
 #: builtin/rebase--interactive.c:155
 msgid "move commits that begin with squash!/fixup!"
-msgstr ""
+msgstr "Commits verschieben, die mit squash!/fixup! beginnen"
 
 #: builtin/rebase--interactive.c:156
-#, fuzzy
 msgid "sign commits"
-msgstr "Commits mit GPG signieren"
+msgstr "Commits signieren"
 
 #: builtin/rebase--interactive.c:158
 msgid "continue rebase"
 msgstr "Rebase fortsetzen"
 
 #: builtin/rebase--interactive.c:160
-#, fuzzy
 msgid "skip commit"
-msgstr "Commit"
+msgstr "Commit auslassen"
 
 #: builtin/rebase--interactive.c:161
-#, fuzzy
 msgid "edit the todo list"
-msgstr "die TODO-Liste prüfen"
+msgstr "die TODO-Liste bearbeiten"
 
 #: builtin/rebase--interactive.c:163
-#, fuzzy
 msgid "show the current patch"
-msgstr "den aktuellen Patch auslassen"
+msgstr "den aktuellen Patch anzeigen"
 
 #: builtin/rebase--interactive.c:166
 msgid "shorten commit ids in the todo list"
 msgstr "Commit-IDs in der TODO-Liste verkürzen"
 
 #: builtin/rebase--interactive.c:168
 msgid "expand commit ids in the todo list"
 msgstr "Commit-IDs in der TODO-Liste erweitern"
@@ -15795,104 +15733,89 @@ msgid "rearrange fixup/squash lines"
 msgstr "fixup/squash-Zeilen umordnen"
 
 #: builtin/rebase--interactive.c:174
 msgid "insert exec commands in todo list"
 msgstr "\"exec\"-Befehle in TODO-Liste einfügen"
 
 #: builtin/rebase--interactive.c:175
 msgid "onto"
-msgstr ""
+msgstr "auf"
 
 #: builtin/rebase--interactive.c:177
-#, fuzzy
 msgid "restrict-revision"
-msgstr "Commit"
+msgstr "Begrenzungscommit"
 
 #: builtin/rebase--interactive.c:177
-#, fuzzy
 msgid "restrict revision"
-msgstr "Commit"
+msgstr "Begrenzungscommit"
 
 #: builtin/rebase--interactive.c:178
-#, fuzzy
 msgid "squash-onto"
-msgstr "squash-onto schreiben"
+msgstr "squash-onto"
 
 #: builtin/rebase--interactive.c:179
-#, fuzzy
 msgid "squash onto"
-msgstr "squash-onto schreiben"
+msgstr "squash onto"
 
 #: builtin/rebase--interactive.c:181
-#, fuzzy
 msgid "the upstream commit"
-msgstr "Informationen zum Upstream-Branch entfernen"
+msgstr "der Upstream-Commit"
 
 #: builtin/rebase--interactive.c:182
-#, fuzzy
 msgid "head-name"
-msgstr "umbenennen"
+msgstr "head-Name"
 
 #: builtin/rebase--interactive.c:182
-#, fuzzy
 msgid "head name"
-msgstr "voraus "
+msgstr "head-Name"
 
 #: builtin/rebase--interactive.c:187
-#, fuzzy
 msgid "rebase strategy"
-msgstr "Merge-Strategie"
+msgstr "Rebase-Strategie"
 
 #: builtin/rebase--interactive.c:188
-#, fuzzy
 msgid "strategy-opts"
-msgstr "Strategie"
+msgstr "Strategie-Optionen"
 
 #: builtin/rebase--interactive.c:189
-#, fuzzy
 msgid "strategy options"
-msgstr "decorate-Optionen"
+msgstr "Strategie-Optionen"
 
 #: builtin/rebase--interactive.c:190
 msgid "switch-to"
-msgstr ""
+msgstr "wechseln zu"
 
 #: builtin/rebase--interactive.c:191
-#, fuzzy
 msgid "the branch or commit to checkout"
-msgstr "einzelnen Commit zu einem ausgecheckten CSV-Repository exportieren"
+msgstr "der Branch oder Commit zum Auschecken"
 
 #: builtin/rebase--interactive.c:192
-#, fuzzy
 msgid "onto-name"
-msgstr "Name"
+msgstr "onto-Name"
 
 #: builtin/rebase--interactive.c:192
-#, fuzzy
 msgid "onto name"
-msgstr "Name des Remote-Repositories"
+msgstr "onto-Name"
 
 #: builtin/rebase--interactive.c:193
-#, fuzzy
 msgid "cmd"
-msgstr "Programm"
+msgstr "Befehl"
 
 #: builtin/rebase--interactive.c:193
-#, fuzzy
 msgid "the command to run"
-msgstr "Keine Befehle ausgeführt."
+msgstr "auszuführender Befehl"
 
 #: builtin/rebase--interactive.c:220
 msgid "--[no-]rebase-cousins has no effect without --rebase-merges"
 msgstr "--[no-]rebase-cousins hat ohne --rebase-merges keine Auswirkung"
 
 #: builtin/rebase--interactive.c:226
 msgid "a base commit must be provided with --upstream or --onto"
-msgstr ""
+msgstr "Ein Basis-Commit muss mit --upstream oder --onto angegeben werden."
 
 #: builtin/receive-pack.c:33
 msgid "git receive-pack <git-dir>"
 msgstr "git receive-pack <Git-Verzeichnis>"
 
 #: builtin/receive-pack.c:830
 msgid ""
 "By default, updating the current branch in a non-bare repository\n"
@@ -16121,19 +16044,19 @@ msgstr "Konnte Fetch-Map für Refspec %s nicht bekommen"
 msgid "(matching)"
 msgstr "(übereinstimmend)"
 
 #: builtin/remote.c:455
 msgid "(delete)"
 msgstr "(lösche)"
 
 #: builtin/remote.c:629 builtin/remote.c:765 builtin/remote.c:864
-#, fuzzy, c-format
+#, c-format
 msgid "No such remote: '%s'"
-msgstr "Kein solches Remote-Repository '%s'"
+msgstr "Kein solches Remote-Repository: '%s'"
 
 #: builtin/remote.c:646
 #, c-format
 msgid "Could not rename config section '%s' to '%s'"
 msgstr "Konnte Sektion '%s' in Konfiguration nicht nach '%s' umbenennen"
 
 #: builtin/remote.c:666
 #, c-format
@@ -16519,19 +16442,18 @@ msgstr "git-update-server-info nicht ausführen"
 msgid "pass --local to git-pack-objects"
 msgstr "--local an git-pack-objects übergeben"
 
 #: builtin/repack.c:310
 msgid "write bitmap index"
 msgstr "Bitmap-Index schreiben"
 
 #: builtin/repack.c:312
-#, fuzzy
 msgid "pass --delta-islands to git-pack-objects"
-msgstr "--local an git-pack-objects übergeben"
+msgstr "--delta-islands an git-pack-objects übergeben"
 
 #: builtin/repack.c:313
 msgid "approxidate"
 msgstr "Datumsangabe"
 
 #: builtin/repack.c:314
 msgid "with -A, do not loosen objects older than this"
 msgstr "mit -A, keine Objekte älter als dieses Datum löschen"
@@ -16842,22 +16764,22 @@ msgid "git rerere [clear | forget <path>... | status | remaining | diff | gc]"
 msgstr "git rerere [clean | forget <Pfad>... | status | remaining | diff | gc]"
 
 #: builtin/rerere.c:60
 msgid "register clean resolutions in index"
 msgstr "saubere Auflösungen im Index registrieren"
 
 #: builtin/rerere.c:79
 msgid "'git rerere forget' without paths is deprecated"
-msgstr ""
+msgstr "'git rerere forget' ohne Pfade ist veraltet."
 
 #: builtin/rerere.c:111
-#, fuzzy, c-format
+#, c-format
 msgid "unable to generate diff for '%s'"
-msgstr "Fehler beim Generieren des Diffs."
+msgstr "Konnte kein Diff für '%s' generieren."
 
 #: builtin/reset.c:31
 msgid ""
 "git reset [--mixed | --soft | --hard | --merge | --keep] [-q] [<commit>]"
 msgstr ""
 "git reset [--mixed | --soft | --hard | --merge | --keep] [-q] [<Commit>]"
 
 #: builtin/reset.c:32
@@ -16971,16 +16893,21 @@ msgstr "Nicht zum Commit vorgemerkte Änderungen nach Zurücksetzung:"
 #: builtin/reset.c:390
 #, c-format
 msgid ""
 "\n"
 "It took %.2f seconds to enumerate unstaged changes after reset.  You can\n"
 "use '--quiet' to avoid this.  Set the config setting reset.quiet to true\n"
 "to make this the default.\n"
 msgstr ""
+"\n"
+"Es dauerte %.2f Sekunden, um über die nach einem Reset nicht zum Commit\n"
+"vorgemerkten Änderungen zu zählen. Sie können '--quiet' benutzen, um\n"
+"das zu verhindern. Setzen Sie die Konfigurationseinstellung reset.quiet\n"
+"auf \"true\", um das zum Standard zu machen.\n"
 
 #: builtin/reset.c:400
 #, c-format
 msgid "Could not reset index file to revision '%s'."
 msgstr "Konnte Index-Datei nicht zu Commit '%s' setzen."
 
 #: builtin/reset.c:404
 msgid "Could not write new index file."
@@ -17546,24 +17473,23 @@ msgstr ""
 msgid "Recurse into nested submodules"
 msgstr "Rekursion in verschachtelte Submodule durchführen"
 
 #: builtin/submodule--helper.c:568
 msgid "git submodule--helper foreach [--quiet] [--recursive] <command>"
 msgstr "git submodule--helper foreach [--quiet] [--recursive] <Befehl>"
 
 #: builtin/submodule--helper.c:595
-#, fuzzy, c-format
+#, c-format
 msgid ""
 "could not look up configuration '%s'. Assuming this repository is its own "
 "authoritative upstream."
 msgstr ""
-"Konnte Konfiguration '%s' nicht nachschlagen. Nehme an, dass dieses "
-"Repository\n"
-"sein eigenes verbindliches Upstream-Repository ist."
+"Konnte Konfiguration '%s' nicht nachschlagen. Nehme an, dass dieses\n"
+"Repository sein eigenes verbindliches Upstream-Repository ist."
 
 #: builtin/submodule--helper.c:663
 #, c-format
 msgid "Failed to register url for submodule path '%s'"
 msgstr ""
 "Fehler beim Eintragen der URL für Submodul-Pfad '%s' in die Konfiguration."
 
 #: builtin/submodule--helper.c:667
@@ -17768,28 +17694,24 @@ msgid "clone of '%s' into submodule path '%s' failed"
 msgstr "Klonen von '%s' in Submodul-Pfad '%s' fehlgeschlagen"
 
 #: builtin/submodule--helper.c:1433
 #, c-format
 msgid "could not get submodule directory for '%s'"
 msgstr "Konnte Submodul-Verzeichnis '%s' nicht finden."
 
 #: builtin/submodule--helper.c:1469
-#, fuzzy, c-format
+#, c-format
 msgid "Invalid update mode '%s' for submodule path '%s'"
-msgstr ""
-"Fehler bei Änderung des Aktualisierungsmodus für Submodul-Pfad '%s' in der\n"
-"Konfiguration."
+msgstr "Ungültiger Aktualisierungsmodus '%s' für Submodul-Pfad '%s'."
 
 #: builtin/submodule--helper.c:1473
-#, fuzzy, c-format
+#, c-format
 msgid "Invalid update mode '%s' configured for submodule path '%s'"
-msgstr ""
-"Fehler bei Änderung des Aktualisierungsmodus für Submodul-Pfad '%s' in der\n"
-"Konfiguration."
+msgstr "Ungültiger Aktualisierungsmodus '%s' für Submodul-Pfad '%s' konfiguriert."
 
 #: builtin/submodule--helper.c:1566
 #, c-format
 msgid "Submodule path '%s' not initialized"
 msgstr "Submodul-Pfad '%s' nicht initialisiert"
 
 #: builtin/submodule--helper.c:1570
 msgid "Maybe you want to use 'update --init'?"
@@ -17862,48 +17784,44 @@ msgstr "Fehlerhafter Wert für --update Parameter"
 msgid ""
 "Submodule (%s) branch configured to inherit branch from superproject, but "
 "the superproject is not on any branch"
 msgstr ""
 "Branch von Submodul (%s) ist konfiguriert, den Branch des Hauptprojektes\n"
 "zu erben, aber das Hauptprojekt befindet sich auf keinem Branch."
 
 #: builtin/submodule--helper.c:2057
-#, fuzzy, c-format
+#, c-format
 msgid "could not get a repository handle for submodule '%s'"
-msgstr "konnte Name für Submodul '%s' nicht nachschlagen"
+msgstr "Konnte kein Repository-Handle für Submodul '%s' erhalten."
 
 #: builtin/submodule--helper.c:2090
 msgid "recurse into submodules"
 msgstr "Rekursion in Submodule durchführen"
 
 #: builtin/submodule--helper.c:2096
 msgid "git submodule--helper embed-git-dir [<path>...]"
 msgstr "git submodule--helper embed-git-dir [<Pfad>...]"
 
 #: builtin/submodule--helper.c:2152
 msgid "check if it is safe to write to the .gitmodules file"
-msgstr ""
+msgstr "prüfen, ob es sicher ist, in die Datei .gitmodules zu schreiben"
 
 #: builtin/submodule--helper.c:2157
-#, fuzzy
 msgid "git submodule--helper config name [value]"
-msgstr "git submodule--helper name <Pfad>"
+msgstr "git submodule--helper config name [Wert]"
 
 #: builtin/submodule--helper.c:2158
-#, fuzzy
 msgid "git submodule--helper config --check-writeable"
-msgstr "git submodule--helper init [<Pfad>]"
+msgstr "git submodule--helper config --check-writeable"
 
 #: builtin/submodule--helper.c:2175 git-submodule.sh:169
-#, fuzzy, sh-format
+#, sh-format
 msgid "please make sure that the .gitmodules file is in the working tree"
-msgstr ""
-"Bitte merken Sie Ihre Änderungen in .gitmodules zum Commit vor oder\n"
-"benutzen Sie \"stash\", um fortzufahren."
+msgstr "Bitte stellen Sie sicher, dass sich die Datei .gitmodules im Arbeitsverzeichnis befindet."
 
 #: builtin/submodule--helper.c:2225
 #, c-format
 msgid "%s doesn't support --super-prefix"
 msgstr "%s unterstützt kein --super-prefix"
 
 #: builtin/submodule--helper.c:2231
 #, c-format
@@ -18587,33 +18505,39 @@ msgid "expire working trees older than <time>"
 msgstr "Arbeitsverzeichnisse älter als <Zeit> verfallen lassen"
 
 #: builtin/worktree.c:234
 #, c-format
 msgid "'%s' already exists"
 msgstr "'%s' existiert bereits"
 
 #: builtin/worktree.c:251
-#, fuzzy, c-format
+#, c-format
 msgid "unable to re-add worktree '%s'"
-msgstr "konnte \"Tree\"-Objekt (%s) nicht lesen"
+msgstr "Konnte Arbeitsverzeichnis '%s' nicht neu hinzufügen."
 
 #: builtin/worktree.c:256
 #, c-format
 msgid ""
 "'%s' is a missing but locked worktree;\n"
 "use 'add -f -f' to override, or 'unlock' and 'prune' or 'remove' to clear"
 msgstr ""
+"'%s' ist ein fehlendes, aber gesperrtes Arbeitsverzeichnis;\n"
+"Benutzen Sie 'add -f -f' zum Überschrieben, oder 'unlock' und 'prune'\n"
+"oder 'remove' zum Löschen."
 
 #: builtin/worktree.c:258
 #, c-format
 msgid ""
 "'%s' is a missing but already registered worktree;\n"
 "use 'add -f' to override, or 'prune' or 'remove' to clear"
 msgstr ""
+"'%s' ist ein fehlendes, aber bereits registriertes Arbeitsverzeichnis;\n"
+"Benutzen Sie 'add -f' zum Überschreiben, oder 'prune' oder 'remove' zum\n"
+"Löschen."
 
 #: builtin/worktree.c:309
 #, c-format
 msgid "could not create directory of '%s'"
 msgstr "Konnte Verzeichnis '%s' nicht erstellen."
 
 #: builtin/worktree.c:428 builtin/worktree.c:434
 #, c-format
@@ -18707,19 +18631,18 @@ msgstr "'%s' ist nicht gesperrt"
 
 #: builtin/worktree.c:743
 msgid "working trees containing submodules cannot be moved or removed"
 msgstr ""
 "Arbeitsverzeichnisse, die Submodule enthalten, können nicht verschoben oder\n"
 "entfernt werden."
 
 #: builtin/worktree.c:751
-#, fuzzy
 msgid "force move even if worktree is dirty or locked"
-msgstr "Löschen erzwingen, auch wenn das Arbeitsverzeichnis geändert wurde"
+msgstr "Verschieben erzwingen, auch wenn das Arbeitsverzeichnis geändert oder gesperrt ist"
 
 #: builtin/worktree.c:774 builtin/worktree.c:901
 #, c-format
 msgid "'%s' is a main working tree"
 msgstr "'%s' ist ein Hauptarbeitsverzeichnis"
 
 #: builtin/worktree.c:779
 #, c-format
@@ -18727,28 +18650,33 @@ msgid "could not figure out destination name from '%s'"
 msgstr "Konnte Zielname aus '%s' nicht bestimmen."
 
 #: builtin/worktree.c:785
 #, c-format
 msgid "target '%s' already exists"
 msgstr "Ziel '%s' existiert bereits."
 
 #: builtin/worktree.c:793
-#, fuzzy, c-format
+#, c-format
 msgid ""
 "cannot move a locked working tree, lock reason: %s\n"
 "use 'move -f -f' to override or unlock first"
-msgstr "Kann gesperrtes Arbeitsverzeichnis nicht verschieben, Sperrgrund: %s"
+msgstr ""
+"Kann kein gesperrtes Arbeitsverzeichnis verschieben, Sperrgrund: %s\n"
+"Benutzen Sie 'move -f -f' zum Überschreiben oder entsperren Sie zuerst\n"
+"das Arbeitsverzeichnis."
 
 #: builtin/worktree.c:795
-#, fuzzy
 msgid ""
 "cannot move a locked working tree;\n"
 "use 'move -f -f' to override or unlock first"
-msgstr "Kann gesperrtes Arbeitsverzeichnis nicht verschieben, Sperrgrund: %s"
+msgstr ""
+"Kann kein gesperrtes Arbeitsverzeichnis verschieben.\n"
+"Benutzen Sie 'move -f -f' zum Überschreiben oder entsperren Sie zuerst\n"
+"das Arbeitsverzeichnis."
 
 #: builtin/worktree.c:798
 #, c-format
 msgid "validation failed, cannot move working tree: %s"
 msgstr "Validierung fehlgeschlagen, kann Arbeitszeichnis nicht verschieben: %s"
 
 #: builtin/worktree.c:803
 #, c-format
@@ -18766,33 +18694,37 @@ msgid "'%s' is dirty, use --force to delete it"
 msgstr "'%s' ist verändert, benutzen Sie --force zum Löschen"
 
 #: builtin/worktree.c:860
 #, c-format
 msgid "failed to run 'git status' on '%s', code %d"
 msgstr "Fehler beim Ausführen von 'git status' auf '%s'. Code: %d"
 
 #: builtin/worktree.c:883
-#, fuzzy
 msgid "force removal even if worktree is dirty or locked"
-msgstr "Löschen erzwingen, auch wenn das Arbeitsverzeichnis geändert wurde"
+msgstr "Löschen erzwingen, auch wenn das Arbeitsverzeichnis geändert oder gesperrt ist"
 
 #: builtin/worktree.c:906
-#, fuzzy, c-format
+#, c-format
 msgid ""
 "cannot remove a locked working tree, lock reason: %s\n"
 "use 'remove -f -f' to override or unlock first"
-msgstr "Kann gesperrtes Arbeitsverzeichnis nicht löschen, Sperrgrund: %s"
+msgstr ""
+"Kann kein gesperrtes Arbeitsverzeichnis löschen, Sperrgrund: %s\n"
+"Benutzen Sie 'remove -f -f' zum Überschreiben oder entsperren Sie zuerst\n"
+"das Arbeitsverzeichnis."
 
 #: builtin/worktree.c:908
-#, fuzzy
 msgid ""
 "cannot remove a locked working tree;\n"
 "use 'remove -f -f' to override or unlock first"
-msgstr "Kann gesperrtes Arbeitsverzeichnis nicht löschen, Sperrgrund: %s"
+msgstr ""
+"Kann kein gesperrtes Arbeitsverzeichnis löschen.\n"
+"Benutzen Sie 'remove -f -f' zum Überschreiben oder entsperren Sie zuerst\n"
+"das Arbeitsverzeichnis."
 
 #: builtin/worktree.c:911
 #, c-format
 msgid "validation failed, cannot remove working tree: %s"
 msgstr "Validierung fehlgeschlagen, kann Arbeitsverzeichnis nicht löschen: %s"
 
 #: builtin/write-tree.c:14
 msgid "git write-tree [--missing-ok] [--prefix=<prefix>/]"
@@ -18826,24 +18758,23 @@ msgstr ""
 "\n"
 "auszuführen."
 
 #: credential-cache--daemon.c:271
 msgid "print debugging messages to stderr"
 msgstr "Meldungen zur Fehlersuche in Standard-Fehlerausgabe ausgeben"
 
 #: t/helper/test-reach.c:152
-#, fuzzy, c-format
+#, c-format
 msgid "commit %s is not marked reachable"
-msgstr "Commit %s hat keinen Eltern-Commit %d"
+msgstr "Commit %s ist nicht als erreichbar gekennzeichnet."
 
 #: t/helper/test-reach.c:162
-#, fuzzy
 msgid "too many commits marked reachable"
-msgstr "Zu viele Commits zum Schreiben des Graphen."
+msgstr "Zu viele Commits als erreichbar gekennzeichnet."
 
 #: git.c:27
 msgid ""
 "git [--version] [--help] [-C <path>] [-c <name>=<value>]\n"
 "           [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]\n"
 "           [-p | --paginate | -P | --no-pager] [--no-replace-objects] [--"
 "bare]\n"
 "           [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]\n"
@@ -18900,17 +18831,17 @@ msgstr "Kein Verzeichnis für -C angegeben.\n"
 #: git.c:300
 #, c-format
 msgid "unknown option: %s\n"
 msgstr "Unbekannte Option: %s\n"
 
 #: git.c:719
 #, c-format
 msgid "alias loop detected: expansion of '%s' does not terminate:%s"
-msgstr ""
+msgstr "Alias-Schleife erkannt: Erweiterung von '%s' schließt nicht ab:%s"
 
 #: git.c:802
 #, c-format
 msgid "expansion of alias '%s' failed; '%s' is not a git command\n"
 msgstr "Erweiterung von Alias '%s' fehlgeschlagen; '%s' ist kein Git-Befehl.\n"
 
 #: git.c:814
 #, c-format
@@ -18928,43 +18859,37 @@ msgstr "Kontrolle über Delegation wird mit cURL < 7.22.0 nicht unterstützt"
 
 #: http.c:404
 msgid "Public key pinning not supported with cURL < 7.44.0"
 msgstr ""
 "Das Anheften des öffentlichen Schlüssels wird mit cURL < 7.44.0\n"
 "nicht unterstützt."
 
 #: http.c:837
-#, fuzzy
 msgid "CURLSSLOPT_NO_REVOKE not supported with cURL < 7.44.0"
-msgstr ""
-"Das Anheften des öffentlichen Schlüssels wird mit cURL < 7.44.0\n"
-"nicht unterstützt."
+msgstr "CURLSSLOPT_NO_REVOKE wird mit cURL < 7.44.0 nicht unterstützt."
 
 #: http.c:910
-#, fuzzy
 msgid "Protocol restrictions not supported with cURL < 7.19.4"
-msgstr ""
-"Das Anheften des öffentlichen Schlüssels wird mit cURL < 7.44.0\n"
-"nicht unterstützt."
+msgstr "Protokollbeschränkungen werden mit cURL < 7.19.4 nicht unterstützt."
 
 #: http.c:1046
 #, c-format
 msgid "Unsupported SSL backend '%s'. Supported SSL backends:"
-msgstr ""
+msgstr "Nicht unterstütztes SSL-Backend '%s'. Unterstützte SSL-Backends:"
 
 #: http.c:1053
 #, c-format
 msgid "Could not set SSL backend to '%s': cURL was built without SSL backends"
-msgstr ""
+msgstr "Konnte SSL-Backend nicht zu '%s' setzen: cURL wurde ohne SSL-Backends gebaut."
 
 #: http.c:1057
-#, fuzzy, c-format
+#, c-format
 msgid "Could not set SSL backend to '%s': already set"
-msgstr "Konnte nicht zu $head_name zurückgehen"
+msgstr "Konnte SSL-Backend nicht zu '%s' setzen: bereits gesetzt"
 
 #: http.c:1921
 #, c-format
 msgid ""
 "unable to update url base from redirection:\n"
 "  asked for: %s\n"
 "   redirect: %s"
 msgstr ""
@@ -19017,19 +18942,18 @@ msgstr "eine Serie von Patches von einer Mailbox anwenden"
 msgid "Annotate file lines with commit information"
 msgstr "Zeilen der Datei mit Commit-Informationen versehen und anzeigen"
 
 #: command-list.h:53
 msgid "Apply a patch to files and/or to the index"
 msgstr "einen Patch auf Dateien und/oder den Index anwenden"
 
 #: command-list.h:54
-#, fuzzy
 msgid "Import a GNU Arch repository into Git"
-msgstr "ein Arch Repository in Git importieren"
+msgstr "ein GNU Arch Repository in Git importieren"
 
 #: command-list.h:55
 msgid "Create an archive of files from a named tree"
 msgstr "Dateiarchiv von angegebenem Verzeichnis erstellen"
 
 #: command-list.h:56
 msgid "Use binary search to find the commit that introduced a bug"
 msgstr ""
@@ -19105,19 +19029,18 @@ msgstr "ein Repository in einem neuen Verzeichnis klonen"
 msgid "Display data in columns"
 msgstr "Daten in Spalten anzeigen"
 
 #: command-list.h:73
 msgid "Record changes to the repository"
 msgstr "Änderungen in das Repository eintragen"
 
 #: command-list.h:74
-#, fuzzy
 msgid "Write and verify Git commit-graph files"
-msgstr "Git Commit Graph-Dateien schreiben und überprüfen"
+msgstr "Git Commit-Graph-Dateien schreiben und überprüfen"
 
 #: command-list.h:75
 msgid "Create a new commit object"
 msgstr "ein neues Commit-Objekt erstellen"
 
 #: command-list.h:76
 msgid "Get and set repository or global options"
 msgstr "repositoryweite oder globale Optionen lesen oder setzen"
@@ -19337,19 +19260,18 @@ msgid "Run merge conflict resolution tools to resolve merge conflicts"
 msgstr ""
 "Ausführen von Tools zur Auflösung von Merge-Konflikten zur Behebung dieser"
 
 #: command-list.h:127
 msgid "Show three-way merge without touching index"
 msgstr "3-Wege-Merge anzeigen ohne den Index zu verändern"
 
 #: command-list.h:128
-#, fuzzy
 msgid "Write and verify multi-pack-indexes"
-msgstr "Git Commit Graph-Dateien schreiben und überprüfen"
+msgstr "multi-pack-indexes schreiben und überprüfen"
 
 #: command-list.h:129
 msgid "Creates a tag object"
 msgstr "ein Tag-Objekt erstellen"
 
 #: command-list.h:130
 msgid "Build a tree-object from ls-tree formatted text"
 msgstr "Tree-Objekt aus ls-tree formattiertem Text erzeugen"
@@ -19980,19 +19902,19 @@ msgstr "Aktueller Branch $branch_name ist auf dem neuesten Stand."
 
 #: git-legacy-rebase.sh:709
 #, sh-format
 msgid "Current branch $branch_name is up to date, rebase forced."
 msgstr ""
 "Aktueller Branch $branch_name ist auf dem neuesten Stand, Rebase erzwungen."
 
 #: git-legacy-rebase.sh:723
-#, fuzzy, sh-format
+#, sh-format
 msgid "Changes to $onto:"
-msgstr "Änderungen von $mb zu $onto:"
+msgstr "Änderungen zu $onto:"
 
 #: git-legacy-rebase.sh:725
 #, sh-format
 msgid "Changes from $mb to $onto:"
 msgstr "Änderungen von $mb zu $onto:"
 
 #: git-legacy-rebase.sh:736
 msgid "First, rewinding head to replay your work on top of it..."
@@ -21348,18 +21270,17 @@ msgstr "Unbekanntes --suppress-cc Feld: '%s'\n"
 #: git-send-email.perl:497
 #, perl-format
 msgid "Unknown --confirm setting: '%s'\n"
 msgstr "Unbekannte --confirm Einstellung: '%s'\n"
 
 #: git-send-email.perl:525
 #, perl-format
 msgid "warning: sendmail alias with quotes is not supported: %s\n"
-msgstr ""
-"Warnung: sendemail Alias mit Anführungsstrichen wird nicht unterstützt: %s\n"
+msgstr "Warnung: sendemail-Alias mit Anführungszeichen wird nicht unterstützt: %s\n"
 
 #: git-send-email.perl:527
 #, perl-format
 msgid "warning: `:include:` not supported: %s\n"
 msgstr "Warnung: `:include:` wird nicht unterstützt: %s\n"
 
 #: git-send-email.perl:529
 #, perl-format
-- 
2.20.0.rc1.379.g1dd7ef354c


^ permalink raw reply related	[relevance 1%]

* [PATCH] l10n: update German translation
@ 2018-11-30 17:35  2% Ralf Thielow
  2018-12-04  6:54  1% ` [PATCH v2] " Ralf Thielow
  0 siblings, 1 reply; 200+ results
From: Ralf Thielow @ 2018-11-30 17:35 UTC (permalink / raw)
  To: git; +Cc: Matthias Rüster, Phillip Szelat, Ralf Thielow

Signed-off-by: Ralf Thielow <ralf.thielow@gmail.com>
---
 po/de.po | 827 +++++++++++++++++++++++++------------------------------
 1 file changed, 375 insertions(+), 452 deletions(-)

diff --git a/po/de.po b/po/de.po
index 3cf9405df..256b668a8 100644
--- a/po/de.po
+++ b/po/de.po
@@ -943,17 +943,17 @@ msgid ""
 "Use '\\!' for literal leading exclamation."
 msgstr ""
 "Verneinende Muster werden in Git-Attributen ignoriert.\n"
 "Benutzen Sie '\\!' für führende Ausrufezeichen."
 
 #: bisect.c:468
 #, c-format
 msgid "Badly quoted content in file '%s': %s"
-msgstr "Ungültiger Inhalt bzgl. Anführungsstriche in Datei '%s': %s"
+msgstr "Ungültiger Inhalt bzgl. Anführungszeichen in Datei '%s': %s"
 
 #: bisect.c:676
 #, c-format
 msgid "We cannot bisect more!\n"
 msgstr "Keine binäre Suche mehr möglich!\n"
 
 #: bisect.c:730
 #, c-format
@@ -1282,19 +1282,18 @@ msgstr "Das Paket speichert eine komplette Historie."
 #: bundle.c:201
 #, c-format
 msgid "The bundle requires this ref:"
 msgid_plural "The bundle requires these %d refs:"
 msgstr[0] "Das Paket benötigt diese Referenz:"
 msgstr[1] "Das Paket benötigt diese %d Referenzen:"
 
 #: bundle.c:267
-#, fuzzy
 msgid "unable to dup bundle descriptor"
-msgstr "Konnte Descriptor nicht umleiten."
+msgstr "Konnte dup für Descriptor des Pakets nicht ausführen."
 
 #: bundle.c:274
 msgid "Could not spawn pack-objects"
 msgstr "Konnte Paketobjekte nicht erstellen"
 
 #: bundle.c:285
 msgid "pack-objects died"
 msgstr "Erstellung der Paketobjekte abgebrochen"
@@ -1433,28 +1432,26 @@ msgid "could not find commit %s"
 msgstr "Konnte Commit %s nicht finden."
 
 #: commit-graph.c:617 builtin/pack-objects.c:2652
 #, c-format
 msgid "unable to get type of object %s"
 msgstr "Konnte Art von Objekt '%s' nicht bestimmen."
 
 #: commit-graph.c:651
-#, fuzzy
 msgid "Annotating commits in commit graph"
-msgstr "Zu viele Commits zum Schreiben des Graphen."
+msgstr "Annotiere Commits in Commit-Graphen"
 
 #: commit-graph.c:691
 msgid "Computing commit graph generation numbers"
-msgstr ""
+msgstr "Commit-Graph Generierungsnummern berechnen"
 
 #: commit-graph.c:803 commit-graph.c:826 commit-graph.c:852
-#, fuzzy
 msgid "Finding commits for commit graph"
-msgstr "Zu viele Commits zum Schreiben des Graphen."
+msgstr "Bestimme Commits für Commit-Graphen"
 
 #: commit-graph.c:812
 #, c-format
 msgid "error adding pack %s"
 msgstr "Fehler beim Hinzufügen von Paket %s."
 
 #: commit-graph.c:814
 #, c-format
@@ -1478,17 +1475,17 @@ msgstr "Konnte führende Verzeichnisse von '%s' nicht erstellen."
 #: commit-graph.c:1002
 msgid "the commit-graph file has incorrect checksum and is likely corrupt"
 msgstr ""
 "Die Commit-Graph-Datei hat eine falsche Prüfsumme und ist wahrscheinlich "
 "beschädigt."
 
 #: commit-graph.c:1046
 msgid "Verifying commits in commit graph"
-msgstr ""
+msgstr "Commit in Commit-Graph überprüfen"
 
 #: compat/obstack.c:405 compat/obstack.c:407
 msgid "memory exhausted"
 msgstr "Speicher verbraucht"
 
 #: config.c:123
 #, c-format
 msgid ""
@@ -2193,37 +2190,39 @@ msgstr[1] "%s, und %<PRIuMAX> Monaten"
 #, c-format
 msgid "%<PRIuMAX> year ago"
 msgid_plural "%<PRIuMAX> years ago"
 msgstr[0] "vor %<PRIuMAX> Jahr"
 msgstr[1] "vor %<PRIuMAX> Jahren"
 
 #: delta-islands.c:268
 msgid "Propagating island marks"
-msgstr ""
+msgstr "Erzeuge Delta-Island Markierungen"
 
 #: delta-islands.c:286
-#, fuzzy, c-format
+#, c-format
 msgid "bad tree object %s"
-msgstr "Konnte Objekt %s nicht lesen."
+msgstr "Ungültiges Tree-Objekt %s."
 
 #: delta-islands.c:330
-#, fuzzy, c-format
+#, c-format
 msgid "failed to load island regex for '%s': %s"
-msgstr "Fehler beim Finden des \"Tree\"-Objektes von %s."
+msgstr "Fehler beim Laden des regulären Ausdrucks des Delta-Island für '%s': %s"
 
 #: delta-islands.c:386
 #, c-format
 msgid "island regex from config has too many capture groups (max=%d)"
 msgstr ""
+"Regulärer Ausdruck des Delta-Island aus Konfiguration hat zu\n"
+"viele Capture-Gruppen (maximal %d)."
 
 #: delta-islands.c:462
 #, c-format
 msgid "Marked %d islands, done.\n"
-msgstr ""
+msgstr "%d Delta-Islands markiert, fertig.\n"
 
 #: diffcore-order.c:24
 #, c-format
 msgid "failed to read orderfile '%s'"
 msgstr "Fehler beim Lesen der Reihenfolgedatei '%s'."
 
 #: diffcore-rename.c:544
 msgid "Performing inexact rename detection"
@@ -2592,21 +2591,21 @@ msgstr "Unerwartete Acknowledgment-Zeile: '%s'"
 
 #: fetch-pack.c:1249
 #, c-format
 msgid "error processing acks: %d"
 msgstr "Fehler beim Verarbeiten von ACKS: %d"
 
 #: fetch-pack.c:1259
 msgid "expected packfile to be sent after 'ready'"
-msgstr ""
+msgstr "Erwartete Versand einer Packdatei nach 'ready'."
 
 #: fetch-pack.c:1261
 msgid "expected no other sections to be sent after no 'ready'"
-msgstr ""
+msgstr "Erwartete keinen Versand einer anderen Sektion ohne 'ready'."
 
 #: fetch-pack.c:1298
 #, c-format
 msgid "error processing shallow info: %d"
 msgstr "Fehler beim Verarbeiten von Shallow-Informationen: %d"
 
 #: fetch-pack.c:1314
 #, c-format
@@ -2746,26 +2745,25 @@ msgid "unsupported command listing type '%s'"
 msgstr "Nicht unterstützte Art zur Befehlsauflistung '%s'."
 
 #: help.c:408
 msgid "The common Git guides are:"
 msgstr "Die allgemeinen Git-Anleitungen sind:"
 
 #: help.c:517
 msgid "See 'git help <command>' to read about a specific subcommand"
-msgstr ""
+msgstr "Siehe 'git help <Befehl>', um mehr über einen spezifischen Unterbefehl zu lesen."
 
 #: help.c:522
-#, fuzzy
 msgid "External commands"
-msgstr "führe $command aus"
+msgstr "Externe Befehle"
 
 #: help.c:530
 msgid "Command aliases"
-msgstr ""
+msgstr "Alias-Befehle"
 
 #: help.c:594
 #, c-format
 msgid ""
 "'%s' appears to be a git command, but we were not\n"
 "able to execute it. Maybe git-%s is broken?"
 msgstr ""
 "'%s' scheint ein git-Befehl zu sein, konnte aber\n"
@@ -2892,19 +2890,18 @@ msgstr "Name besteht nur aus nicht erlaubten Zeichen: %s"
 msgid "invalid date format: %s"
 msgstr "Ungültiges Datumsformat: %s"
 
 #: list-objects-filter-options.c:35
 msgid "multiple filter-specs cannot be combined"
 msgstr "Mehrere filter-specs können nicht kombiniert werden."
 
 #: list-objects-filter-options.c:58
-#, fuzzy
 msgid "only 'tree:0' is supported"
-msgstr "Protokoll '%s' wird nicht unterstützt."
+msgstr "Es wird nur 'tree:0' unterstützt."
 
 #: list-objects-filter-options.c:137
 msgid "cannot change partial clone promisor remote"
 msgstr "Kann Remote-Repository für partielles Klonen nicht ändern."
 
 #: lockfile.c:151
 #, c-format
 msgid ""
@@ -3355,142 +3352,141 @@ msgstr "Merge hat keinen Commit zurückgegeben"
 msgid "Could not parse object '%s'"
 msgstr "Konnte Objekt '%s' nicht parsen."
 
 #: merge-recursive.c:3553 builtin/merge.c:691 builtin/merge.c:849
 msgid "Unable to write index."
 msgstr "Konnte Index nicht schreiben."
 
 #: midx.c:65
-#, fuzzy, c-format
+#, c-format
 msgid "multi-pack-index file %s is too small"
-msgstr "Graph-Datei %s ist zu klein."
+msgstr "multi-pack-index-Datei %s ist zu klein."
 
 #: midx.c:81
-#, fuzzy, c-format
+#, c-format
 msgid "multi-pack-index signature 0x%08x does not match signature 0x%08x"
-msgstr "Graph-Signatur %X stimmt nicht mit Signatur %X überein."
+msgstr "multi-pack-index-Signatur 0x%08x stimmt nicht mit Signatur 0x%08x überein."
 
 #: midx.c:86
 #, c-format
 msgid "multi-pack-index version %d not recognized"
-msgstr ""
+msgstr "multi-pack-index-Version %d nicht erkannt."
 
 #: midx.c:91
-#, fuzzy, c-format
+#, c-format
 msgid "hash version %u does not match"
-msgstr "Hash-Version %X stimmt nicht mit Version %X überein."
+msgstr "Hash-Version %u stimmt nicht überein."
 
 #: midx.c:105
 msgid "invalid chunk offset (too large)"
-msgstr ""
+msgstr "Ungültiger Chunk-Offset (zu groß)"
 
 #: midx.c:129
 msgid "terminating multi-pack-index chunk id appears earlier than expected"
-msgstr ""
+msgstr "Abschließende multi-pack-index Chunk-Id erscheint eher als erwartet."
 
 #: midx.c:142
 msgid "multi-pack-index missing required pack-name chunk"
-msgstr ""
+msgstr "multi-pack-index fehlt erforderlicher pack-name Chunk."
 
 #: midx.c:144
 msgid "multi-pack-index missing required OID fanout chunk"
-msgstr ""
+msgstr "multi-pack-index fehlt erforderlicher OID fanout Chunk."
 
 #: midx.c:146
 msgid "multi-pack-index missing required OID lookup chunk"
-msgstr ""
+msgstr "multi-pack-index fehlt erforderlicher OID lookup Chunk."
 
 #: midx.c:148
 msgid "multi-pack-index missing required object offsets chunk"
-msgstr ""
+msgstr "multi-pack-index fehlt erforderlicher object offset Chunk."
 
 #: midx.c:162
 #, c-format
 msgid "multi-pack-index pack names out of order: '%s' before '%s'"
-msgstr ""
+msgstr "Falsche Reihenfolge bei multi-pack-index Pack-Namen: '%s' vor '%s'"
 
 #: midx.c:205
 #, c-format
 msgid "bad pack-int-id: %u (%u total packs"
-msgstr ""
+msgstr "Fehlerhafte pack-int-id: %u (%u Pakete insgesamt)"
 
 #: midx.c:246
 msgid "multi-pack-index stores a 64-bit offset, but off_t is too small"
-msgstr ""
+msgstr "multi-pack-index speichert einen 64-Bit Offset, aber off_t ist zu klein."
 
 #: midx.c:271
 msgid "error preparing packfile from multi-pack-index"
-msgstr ""
+msgstr "Fehler bei Vorbereitung der Packdatei aus multi-pack-index."
 
 #: midx.c:407
-#, fuzzy, c-format
+#, c-format
 msgid "failed to add packfile '%s'"
-msgstr "Fehler beim Lesen der Reihenfolgedatei '%s'."
+msgstr "Fehler beim Hinzufügen von Packdatei'%s'."
 
 #: midx.c:413
-#, fuzzy, c-format
+#, c-format
 msgid "failed to open pack-index '%s'"
-msgstr "Fehler beim Öffnen von '%s'"
+msgstr "Fehler beim Öffnen von pack-index '%s'"
 
 #: midx.c:507
-#, fuzzy, c-format
+#, c-format
 msgid "failed to locate object %d in packfile"
-msgstr "Konnte Objekt %s nicht lesen."
+msgstr "Fehler beim Lokalisieren von Objekt %d in Packdatei."
 
 #: midx.c:943
-#, fuzzy, c-format
+#, c-format
 msgid "failed to clear multi-pack-index at %s"
-msgstr "Fehler beim Bereinigen des Index"
+msgstr "Fehler beim Löschen des multi-pack-index bei %s"
 
 #: midx.c:981
 #, c-format
 msgid ""
 "oid fanout out of order: fanout[%d] = %<PRIx32> > %<PRIx32> = fanout[%d]"
-msgstr ""
+msgstr "Ungültige oid fanout Reihenfolge: fanout[%d] = %<PRIx32> > %<PRIx32> = fanout[%d]"
 
 #: midx.c:992
 #, c-format
 msgid "oid lookup out of order: oid[%d] = %s >= %s = oid[%d]"
-msgstr ""
+msgstr "Ungültige oid lookup Reihenfolge: oid[%d] = %s >= %s = oid[%d]"
 
 #: midx.c:996
-#, fuzzy
 msgid "Verifying object offsets"
-msgstr "Schreibe Objekte"
+msgstr "Überprüfe Objekt-Offsets"
 
 #: midx.c:1004
-#, fuzzy, c-format
+#, c-format
 msgid "failed to load pack entry for oid[%d] = %s"
-msgstr "kann für %s keinen Eintrag in den Zwischenspeicher hinzufügen"
+msgstr "Fehler beim Laden des Pack-Eintrags für oid[%d] = %s"
 
 #: midx.c:1010
-#, fuzzy, c-format
+#, c-format
 msgid "failed to load pack-index for packfile %s"
-msgstr "Fehler beim Lesen der Reihenfolgedatei '%s'."
+msgstr "Fehler beim Laden des Pack-Index für Packdatei %s"
 
 #: midx.c:1019
 #, c-format
 msgid "incorrect object offset for oid[%d] = %s: %<PRIx64> != %<PRIx64>"
-msgstr ""
+msgstr "Falscher Objekt-Offset für oid[%d] = %s: %<PRIx64> != %<PRIx64>"
 
 #: name-hash.c:532
-#, fuzzy, c-format
+#, c-format
 msgid "unable to create lazy_dir thread: %s"
-msgstr "kann Thread nicht erzeugen: %s"
+msgstr "Kann lazy_dir Thread nicht erzeugen: %s"
 
 #: name-hash.c:554
-#, fuzzy, c-format
+#, c-format
 msgid "unable to create lazy_name thread: %s"
-msgstr "kann Thread nicht erzeugen: %s"
+msgstr "Kann lazy_name Thread nicht erzeugen: %s"
 
 #: name-hash.c:560
-#, fuzzy, c-format
+#, c-format
 msgid "unable to join lazy_name thread: %s"
-msgstr "kann Thread nicht erzeugen: %s"
+msgstr "Kann lazy_name Thread nicht beitreten: %s"
 
 #: notes-merge.c:275
 #, c-format
 msgid ""
 "You have not concluded your previous notes merge (%s exists).\n"
 "Please, use 'git notes merge --commit' or 'git notes merge --abort' to "
 "commit/abort the previous merge before you start a new notes merge."
 msgstr ""
@@ -3723,24 +3719,23 @@ msgid "protocol error: bad line length character: %.4s"
 msgstr "Protokollfehler: ungültiges Zeichen für Zeilenlänge: %.4s"
 
 #: pkt-line.c:337 pkt-line.c:342
 #, c-format
 msgid "protocol error: bad line length %d"
 msgstr "Protokollfehler: ungültige Zeilenlänge %d"
 
 #: preload-index.c:118
-#, fuzzy
 msgid "Refreshing index"
-msgstr "Konnte den Index nicht aktualisieren."
+msgstr "Aktualisiere Index"
 
 #: preload-index.c:137
-#, fuzzy, c-format
+#, c-format
 msgid "unable to create threaded lstat: %s"
-msgstr "kann Thread nicht erzeugen: %s"
+msgstr "Kann Thread für lstat nicht erzeugen: %s"
 
 #: pretty.c:962
 msgid "unable to parse --pretty format"
 msgstr "Konnte --pretty Format nicht parsen."
 
 #: range-diff.c:56
 msgid "could not start `log`"
 msgstr "Konnte `log` nicht starten."
@@ -3759,19 +3754,18 @@ msgid "failed to generate diff"
 msgstr "Fehler beim Generieren des Diffs."
 
 #: range-diff.c:455 range-diff.c:457
 #, c-format
 msgid "could not parse log for '%s'"
 msgstr "Konnte Log für '%s' nicht parsen."
 
 #: read-cache.c:1490
-#, fuzzy
 msgid "Refresh index"
-msgstr "Konnte den Index nicht aktualisieren."
+msgstr "Aktualisiere Index"
 
 #: read-cache.c:1604
 #, c-format
 msgid ""
 "index.version set, but the value is invalid.\n"
 "Using version %i"
 msgstr ""
 "index.version gesetzt, aber Wert ungültig.\n"
@@ -3784,50 +3778,50 @@ msgid ""
 "Using version %i"
 msgstr ""
 "GIT_INDEX_VERSION gesetzt, aber Wert ungültig.\n"
 "Verwende Version %i"
 
 #: read-cache.c:1792
 #, c-format
 msgid "malformed name field in the index, near path '%s'"
-msgstr ""
+msgstr "Ungültiges Namensfeld im Index, in der Nähe von Pfad '%s'."
 
 #: read-cache.c:1960 rerere.c:565 rerere.c:599 rerere.c:1111 builtin/add.c:458
 #: builtin/check-ignore.c:177 builtin/checkout.c:289 builtin/checkout.c:585
 #: builtin/checkout.c:953 builtin/clean.c:954 builtin/commit.c:343
 #: builtin/diff-tree.c:115 builtin/grep.c:489 builtin/mv.c:144
 #: builtin/reset.c:244 builtin/rm.c:270 builtin/submodule--helper.c:329
 msgid "index file corrupt"
 msgstr "Index-Datei beschädigt"
 
 #: read-cache.c:2101
-#, fuzzy, c-format
+#, c-format
 msgid "unable to create load_cache_entries thread: %s"
-msgstr "kann Thread nicht erzeugen: %s"
+msgstr "Kann Thread für load_cache_entries nicht erzeugen: %s"
 
 #: read-cache.c:2114
-#, fuzzy, c-format
+#, c-format
 msgid "unable to join load_cache_entries thread: %s"
-msgstr "kann Thread nicht erzeugen: %s"
+msgstr "Kann Thread für load_cache_entries nicht erzeugen: %s"
 
 #: read-cache.c:2200
-#, fuzzy, c-format
+#, c-format
 msgid "unable to create load_index_extensions thread: %s"
-msgstr "kann Thread nicht erzeugen: %s"
+msgstr "Kann Thread für load_index_extensions nicht erzeugen: %s"
 
 #: read-cache.c:2227
-#, fuzzy, c-format
+#, c-format
 msgid "unable to join load_index_extensions thread: %s"
-msgstr "kann Thread nicht erzeugen: %s"
+msgstr "Kann Thread für load_index_extensions nicht erzeugen: %s"
 
 #: read-cache.c:2953 sequencer.c:4727 wrapper.c:658 builtin/merge.c:1086
 #, c-format
 msgid "could not close '%s'"
-msgstr "Konnte '%s' nicht schließen"
+msgstr "Konnte '%s' nicht schließen."
 
 #: read-cache.c:3026 sequencer.c:2203 sequencer.c:3592
 #, c-format
 msgid "could not stat '%s'"
 msgstr "Konnte '%s' nicht lesen."
 
 #: read-cache.c:3039
 #, c-format
@@ -3835,17 +3829,16 @@ msgid "unable to open git dir: %s"
 msgstr "konnte Git-Verzeichnis nicht öffnen: %s"
 
 #: read-cache.c:3051
 #, c-format
 msgid "unable to unlink: %s"
 msgstr "Konnte '%s' nicht entfernen."
 
 #: rebase-interactive.c:10
-#, fuzzy
 msgid ""
 "\n"
 "Commands:\n"
 "p, pick <commit> = use commit\n"
 "r, reword <commit> = use commit, but edit the commit message\n"
 "e, edit <commit> = use commit, but stop for amending\n"
 "s, squash <commit> = use commit, but meld into previous commit\n"
 "f, fixup <commit> = like \"squash\", but discard this commit's log message\n"
@@ -3862,26 +3855,25 @@ msgid ""
 "These lines can be re-ordered; they are executed from top to bottom.\n"
 msgstr ""
 "\n"
 "Befehle:\n"
 "p, pick <Commit> = Commit verwenden\n"
 "r, reword <Commit> = Commit verwenden, aber Commit-Beschreibung bearbeiten\n"
 "e, edit <Commit> = Commit verwenden, aber zum Nachbessern anhalten\n"
 "s, squash <Commit> = Commit verwenden, aber mit vorherigem Commit vereinen\n"
-"f, fixup <Commit> = wie \"squash\", aber diese Commit-Beschreibung "
-"verwerfen\n"
+"f, fixup <Commit> = wie \"squash\", aber diese Commit-Beschreibung verwerfen\n"
 "x, exec <Commit> = Befehl (Rest der Zeile) mittels Shell ausführen\n"
+"b, break = hier anhalten (Rebase später mit 'git rebase --continue' fortsetzen)\n"
 "d, drop <Commit> = Commit entfernen\n"
 "l, label <Label> = aktuellen HEAD mit Label versehen\n"
 "t, reset <Label> = HEAD zu einem Label umsetzen\n"
 "m, merge [-C <Commit> | -c <Commit>] <Label> [# <eineZeile>]\n"
 ".       Merge-Commit mit der originalen Merge-Commit-Beschreibung erstellen\n"
-".       (oder die eine Zeile, wenn keine originale Merge-Commit-"
-"Beschreibung\n"
+".       (oder die eine Zeile, wenn keine originale Merge-Commit-Beschreibung\n"
 ".       spezifiziert ist). Benutzen Sie -c <Commit> zum Bearbeiten der\n"
 ".       Commit-Beschreibung.\n"
 "\n"
 "Diese Zeilen können umsortiert werden; Sie werden von oben nach unten\n"
 "ausgeführt.\n"
 
 #: rebase-interactive.c:31 git-rebase--preserve-merges.sh:173
 msgid ""
@@ -4182,17 +4174,17 @@ msgstr "Fehlerhafter Feldname: %.*s"
 #, c-format
 msgid "unknown field name: %.*s"
 msgstr "Unbekannter Feldname: %.*s"
 
 #: ref-filter.c:539
 #, c-format
 msgid ""
 "not a git repository, but the field '%.*s' requires access to object data"
-msgstr ""
+msgstr "Kein Git-Repository, aber das Feld '%.*s' erfordert Zugriff auf Objektdaten."
 
 #: ref-filter.c:663
 #, c-format
 msgid "format: %%(if) atom used without a %%(then) atom"
 msgstr "format: %%(if) Atom ohne ein %%(then) Atom verwendet"
 
 #: ref-filter.c:726
 #, c-format
@@ -4446,113 +4438,111 @@ msgstr "doppelte ersetzende Referenz: %s"
 
 #: replace-object.c:73
 #, c-format
 msgid "replace depth too high for object %s"
 msgstr "Ersetzungstiefe zu hoch für Objekt %s"
 
 #: rerere.c:217 rerere.c:226 rerere.c:229
 msgid "corrupt MERGE_RR"
-msgstr ""
+msgstr "Fehlerhaftes MERGE_RR"
 
 #: rerere.c:264 rerere.c:269
-#, fuzzy
 msgid "unable to write rerere record"
-msgstr "Konnte Notiz-Objekt nicht schreiben"
+msgstr "Konnte Rerere-Eintrag nicht schreiben."
 
 #: rerere.c:485 rerere.c:692 sequencer.c:3136 sequencer.c:3162
 #, c-format
 msgid "could not write '%s'"
 msgstr "Konnte '%s' nicht schreiben."
 
 #: rerere.c:495
-#, fuzzy, c-format
+#, c-format
 msgid "there were errors while writing '%s' (%s)"
-msgstr "Lesefehler beim Indizieren von '%s'."
+msgstr "Fehler beim Schreiben von '%s' (%s)."
 
 #: rerere.c:498
-#, fuzzy, c-format
+#, c-format
 msgid "failed to flush '%s'"
-msgstr "Konnte '%s' nicht lesen"
+msgstr "Flush bei '%s' fehlgeschlagen."
 
 #: rerere.c:503 rerere.c:1039
-#, fuzzy, c-format
+#, c-format
 msgid "could not parse conflict hunks in '%s'"
-msgstr "Konnte Commit '%s' nicht parsen."
+msgstr "Konnte Konflikt-Blöcke in '%s' nicht parsen."
 
 #: rerere.c:684
-#, fuzzy, c-format
+#, c-format
 msgid "failed utime() on '%s'"
 msgstr "Fehler beim Aufruf von utime() auf '%s'."
 
 #: rerere.c:694
-#, fuzzy, c-format
+#, c-format
 msgid "writing '%s' failed"
-msgstr "Konnte '%s' nicht erstellen"
+msgstr "Schreiben von '%s' fehlgeschlagen."
 
 #: rerere.c:714
 #, c-format
 msgid "Staged '%s' using previous resolution."
-msgstr ""
+msgstr "'%s' mit vorheriger Konfliktauflösung zum Commit vorgemerkt."
 
 #: rerere.c:753
-#, fuzzy, c-format
+#, c-format
 msgid "Recorded resolution for '%s'."
-msgstr "aufgezeichnete Auflösung von Merge-Konflikten wiederverwenden"
+msgstr "Konfliktauflösung für '%s' aufgezeichnet."
 
 #: rerere.c:788
 #, c-format
 msgid "Resolved '%s' using previous resolution."
-msgstr ""
+msgstr "Konflikte in '%s' mit vorheriger Konfliktauflösung beseitigt."
 
 #: rerere.c:803
-#, fuzzy, c-format
+#, c-format
 msgid "cannot unlink stray '%s'"
-msgstr "kann symbolische Verknüpfung '%s' auf '%s' nicht erstellen"
+msgstr "Kann '%s' nicht löschen."
 
 #: rerere.c:807
-#, fuzzy, c-format
+#, c-format
 msgid "Recorded preimage for '%s'"
-msgstr "Konnte Log für '%s' nicht parsen."
+msgstr "Preimage für '%s' aufgezeichnet."
 
 #: rerere.c:881 submodule.c:1763 builtin/submodule--helper.c:1413
 #: builtin/submodule--helper.c:1423
 #, c-format
 msgid "could not create directory '%s'"
 msgstr "Konnte Verzeichnis '%s' nicht erstellen."
 
 #: rerere.c:1057
-#, fuzzy, c-format
+#, c-format
 msgid "failed to update conflicted state in '%s'"
-msgstr "Fehler beim Ausführen von 'git status' auf '%s'"
+msgstr "Fehler beim Aktualisieren des Konflikt-Status in '%s'."
 
 #: rerere.c:1068 rerere.c:1075
-#, fuzzy, c-format
+#, c-format
 msgid "no remembered resolution for '%s'"
-msgstr "Konnte Merge-Ergebnis von '%s' nicht hinzufügen."
+msgstr "Keine aufgezeichnete Konfliktauflösung für '%s'."
 
 #: rerere.c:1077
-#, fuzzy, c-format
+#, c-format
 msgid "cannot unlink '%s'"
-msgstr "kann Verweis '%s' nicht lesen"
+msgstr "Kann '%s' nicht löschen."
 
 #: rerere.c:1087
-#, fuzzy, c-format
+#, c-format
 msgid "Updated preimage for '%s'"
-msgstr "Ersetzende Referenz '%s' gelöscht."
+msgstr "Preimage für '%s' aktualisiert."
 
 #: rerere.c:1096
-#, fuzzy, c-format
+#, c-format
 msgid "Forgot resolution for '%s'\n"
-msgstr "Konnte Log für '%s' nicht parsen."
+msgstr "Aufgezeichnete Konfliktauflösung für '%s' gelöscht.\n"
 
 #: rerere.c:1199
-#, fuzzy
 msgid "unable to open rr-cache directory"
-msgstr "Konnte Cache-Verzeichnis nicht aktualisieren."
+msgstr "Konnte rr-cache Verzeichnis nicht öffnen."
 
 #: revision.c:2324
 msgid "your current branch appears to be broken"
 msgstr "Ihr aktueller Branch scheint fehlerhaft zu sein."
 
 #: revision.c:2327
 #, c-format
 msgid "your current branch '%s' does not have any commits yet"
@@ -4562,19 +4552,19 @@ msgstr "Ihr aktueller Branch '%s' hat noch keine Commits."
 msgid "--first-parent is incompatible with --bisect"
 msgstr "Die Optionen --first-parent und --bisect sind inkompatibel."
 
 #: run-command.c:740
 msgid "open /dev/null failed"
 msgstr "Öffnen von /dev/null fehlgeschlagen"
 
 #: run-command.c:1229
-#, fuzzy, c-format
+#, c-format
 msgid "cannot create async thread: %s"
-msgstr "kann Thread nicht erzeugen: %s"
+msgstr "Kann Thread für async nicht erzeugen: %s"
 
 #: run-command.c:1293
 #, c-format
 msgid ""
 "The '%s' hook was ignored because it's not set as executable.\n"
 "You can disable this warning with `git config advice.ignoredHook false`."
 msgstr ""
 "Der '%s' Hook wurde ignoriert, weil er nicht als ausführbar markiert ist.\n"
@@ -4715,59 +4705,59 @@ msgstr "%s: Konnte neue Index-Datei nicht schreiben"
 msgid "unable to update cache tree"
 msgstr "Konnte Cache-Verzeichnis nicht aktualisieren."
 
 #: sequencer.c:604
 msgid "could not resolve HEAD commit"
 msgstr "Konnte HEAD-Commit nicht auflösen."
 
 #: sequencer.c:684
-#, fuzzy, c-format
+#, c-format
 msgid "no key present in '%.*s'"
-msgstr "Konnte '%.*s' nicht parsen."
+msgstr "Kein Schlüssel in '%.*s' vorhanden."
 
 #: sequencer.c:695
-#, fuzzy, c-format
+#, c-format
 msgid "unable to dequote value of '%s'"
-msgstr "Konnte Remote-Helper für '%s' nicht finden."
+msgstr "Konnte Anführungszeichen von '%s' nicht entfernen."
 
 #: sequencer.c:732 wrapper.c:227 wrapper.c:397 builtin/am.c:719
 #: builtin/am.c:811 builtin/merge.c:1081
 #, c-format
 msgid "could not open '%s' for reading"
 msgstr "Konnte '%s' nicht zum Lesen öffnen."
 
 #: sequencer.c:742
 msgid "'GIT_AUTHOR_NAME' already given"
-msgstr ""
+msgstr "'GIT_AUTHOR_NAME' bereits angegeben."
 
 #: sequencer.c:747
 msgid "'GIT_AUTHOR_EMAIL' already given"
-msgstr ""
+msgstr "'GIT_AUTHOR_EMAIL' bereits angegeben."
 
 #: sequencer.c:752
 msgid "'GIT_AUTHOR_DATE' already given"
-msgstr ""
+msgstr "'GIT_AUTHOR_DATE' bereits angegeben."
 
 #: sequencer.c:756
-#, fuzzy, c-format
+#, c-format
 msgid "unknown variable '%s'"
-msgstr "Unbekanntes Archivformat '%s'"
+msgstr "Unbekannte Variable '%s'"
 
 #: sequencer.c:761
 msgid "missing 'GIT_AUTHOR_NAME'"
-msgstr ""
+msgstr "'GIT_AUTHOR_NAME' fehlt."
 
 #: sequencer.c:763
 msgid "missing 'GIT_AUTHOR_EMAIL'"
-msgstr ""
+msgstr "'GIT_AUTHOR_EMAIL' fehlt."
 
 #: sequencer.c:765
 msgid "missing 'GIT_AUTHOR_DATE'"
-msgstr ""
+msgstr "'GIT_AUTHOR_DATE' fehlt."
 
 #: sequencer.c:825
 #, c-format
 msgid "invalid date format '%s' in '%s'"
 msgstr "Ungültiges Datumsformat '%s' in '%s'"
 
 #: sequencer.c:842
 #, c-format
@@ -5310,38 +5300,38 @@ msgid ""
 "Your changes are safe in the stash.\n"
 "You can run \"git stash pop\" or \"git stash drop\" at any time.\n"
 msgstr ""
 "Anwendung des automatischen Stash resultierte in Konflikten.\n"
 "Ihre Änderungen sind im Stash sicher.\n"
 "Sie können jederzeit \"git stash pop\" oder \"git stash drop\" ausführen.\n"
 
 #: sequencer.c:3427
-#, fuzzy, c-format
+#, c-format
 msgid "could not checkout %s"
-msgstr "kann %s nicht auschecken"
+msgstr "Konnte %s nicht auschecken."
 
 #: sequencer.c:3441
-#, fuzzy, c-format
+#, c-format
 msgid "%s: not a valid OID"
-msgstr "%s ist kein gültiges Objekt"
+msgstr "%s: keine gültige OID"
 
 #: sequencer.c:3446 git-rebase--preserve-merges.sh:724
 msgid "could not detach HEAD"
 msgstr "Konnte HEAD nicht loslösen"
 
 #: sequencer.c:3461
-#, fuzzy, c-format
+#, c-format
 msgid "Stopped at HEAD\n"
-msgstr "Angehalten bei %s... %.*s\n"
+msgstr "Angehalten bei HEAD\n"
 
 #: sequencer.c:3463
-#, fuzzy, c-format
+#, c-format
 msgid "Stopped at %s\n"
-msgstr "Angehalten bei %s... %.*s\n"
+msgstr "Angehalten bei %s\n"
 
 #: sequencer.c:3471
 #, c-format
 msgid ""
 "Could not execute the todo command\n"
 "\n"
 "    %.*s\n"
 "It has been rescheduled; To edit the command before continuing, please\n"
@@ -5495,41 +5485,38 @@ msgid ""
 "continue'.\n"
 "Or you can abort the rebase with 'git rebase --abort'.\n"
 msgstr ""
 "Sie können das mit 'git rebase --edit-todo' beheben. Führen Sie danach\n"
 "'git rebase --continue' aus.\n"
 "Oder Sie können den Rebase mit 'git rebase --abort' abbrechen.\n"
 
 #: sequencer.c:4848 sequencer.c:4886
-#, fuzzy
 msgid "nothing to do"
-msgstr "nichts zu committen\n"
+msgstr "Nichts zu tun."
 
 #: sequencer.c:4852
-#, fuzzy, c-format
+#, c-format
 msgid "Rebase %s onto %s (%d command)"
 msgid_plural "Rebase %s onto %s (%d commands)"
-msgstr[0] "Rebase von $shortrevisions auf $shortonto ($todocount Kommando)"
-msgstr[1] "Rebase von $shortrevisions auf $shortonto ($todocount Kommandos)"
+msgstr[0] "Rebase von %s auf %s (%d Kommando)"
+msgstr[1] "Rebase von %s auf %s (%d Kommandos)"
 
 #: sequencer.c:4864
-#, fuzzy, c-format
+#, c-format
 msgid "could not copy '%s' to '%s'."
 msgstr "Konnte '%s' nicht nach '%s' kopieren."
 
 #: sequencer.c:4868 sequencer.c:4897
-#, fuzzy
 msgid "could not transform the todo list"
-msgstr "Konnte TODO-Liste nicht erzeugen."
+msgstr "Konnte die TODO-Liste nicht umwandeln."
 
 #: sequencer.c:4900
-#, fuzzy
 msgid "could not skip unnecessary pick commands"
-msgstr "nicht erforderliche \"pick\"-Befehle auslassen"
+msgstr "Konnte unnötige \"pick\"-Befehle nicht auslassen."
 
 #: sequencer.c:4983
 msgid "the script was already rearranged."
 msgstr "Das Script wurde bereits umgeordnet."
 
 #: setup.c:123
 #, c-format
 msgid "'%s' is outside repository"
@@ -6091,17 +6078,17 @@ msgstr "Ignoriere verdächtigen Submodulnamen: %s"
 
 #: submodule-config.c:296
 msgid "negative values not allowed for submodule.fetchjobs"
 msgstr "Negative Werte für submodule.fetchjobs nicht erlaubt."
 
 #: submodule-config.c:390
 #, c-format
 msgid "ignoring '%s' which may be interpreted as a command-line option: %s"
-msgstr ""
+msgstr "Ignoriere '%s', was als eine Befehlszeilenoption '%s' interpretiert werden würde."
 
 #: submodule-config.c:479
 #, c-format
 msgid "invalid value for %s"
 msgstr "Ungültiger Wert für %s"
 
 #: submodule-config.c:754
 #, c-format
@@ -6690,16 +6677,19 @@ msgid "Checking out files"
 msgstr "Checke Dateien aus"
 
 #: unpack-trees.c:368
 msgid ""
 "the following paths have collided (e.g. case-sensitive paths\n"
 "on a case-insensitive filesystem) and only one from the same\n"
 "colliding group is in the working tree:\n"
 msgstr ""
+"Die folgenden Pfade haben kollidiert (z.B. case-sensitive Pfade\n"
+"auf einem case-insensitiven Dateisystem) und nur einer von der\n"
+"selben Kollissionsgruppe ist im Arbeitsverzeichnis:\n"
 
 #: urlmatch.c:163
 msgid "invalid URL scheme name or missing '://' suffix"
 msgstr "Ungültiges URL-Schema oder Suffix '://' fehlt"
 
 #: urlmatch.c:187 urlmatch.c:346 urlmatch.c:405
 #, c-format
 msgid "invalid %XX escape sequence"
@@ -7571,17 +7561,17 @@ msgstr ""
 #, c-format
 msgid "To restore the original branch and stop patching, run \"%s --abort\"."
 msgstr ""
 "Um den ursprünglichen Branch wiederherzustellen und die Anwendung der "
 "Patches abzubrechen, führen Sie \"%s --abort\" aus."
 
 #: builtin/am.c:1196
 msgid "Patch sent with format=flowed; space at the end of lines might be lost."
-msgstr ""
+msgstr "Patch mit format=flowed versendet; Leerzeichen am Ende von Zeilen könnte verloren gehen."
 
 #: builtin/am.c:1224
 msgid "Patch is empty."
 msgstr "Patch ist leer."
 
 #: builtin/am.c:1290
 #, c-format
 msgid "invalid ident line: %.*s"
@@ -8584,19 +8574,18 @@ msgstr ""
 msgid ""
 "git cat-file (--batch | --batch-check) [--follow-symlinks] [--textconv | --"
 "filters]"
 msgstr ""
 "git cat-file (--batch | --batch-check) [--follow-symlinks] [--textconv | --"
 "filters]"
 
 #: builtin/cat-file.c:609
-#, fuzzy
 msgid "only one batch option may be specified"
-msgstr "Nur eine Aktion erlaubt."
+msgstr "Nur eine Batch-Option erlaubt."
 
 #: builtin/cat-file.c:627
 msgid "<type> can be one of: blob, tree, commit, tag"
 msgstr "<Art> kann sein: blob, tree, commit, tag"
 
 #: builtin/cat-file.c:628
 msgid "show object type"
 msgstr "Objektart anzeigen"
@@ -10363,19 +10352,18 @@ msgstr "globale Konfigurationsdatei verwenden"
 msgid "use system config file"
 msgstr "systemweite Konfigurationsdatei verwenden"
 
 #: builtin/config.c:127
 msgid "use repository config file"
 msgstr "Konfigurationsdatei des Repositories verwenden"
 
 #: builtin/config.c:128
-#, fuzzy
 msgid "use per-worktree config file"
-msgstr "Konfigurationsdatei des Repositories verwenden"
+msgstr "Konfigurationsdatei pro Arbeitsverzeichnis verwenden"
 
 #: builtin/config.c:129
 msgid "use given config file"
 msgstr "die angegebene Konfigurationsdatei verwenden"
 
 #: builtin/config.c:130
 msgid "blob-id"
 msgstr "Blob-Id"
@@ -10576,16 +10564,19 @@ msgid "$HOME not set"
 msgstr "$HOME nicht gesetzt."
 
 #: builtin/config.c:657
 msgid ""
 "--worktree cannot be used with multiple working trees unless the config\n"
 "extension worktreeConfig is enabled. Please read \"CONFIGURATION FILE\"\n"
 "section in \"git help worktree\" for details"
 msgstr ""
+"--worktree kann nicht mit mehreren Arbeitsverzeichnissen verwendet werden,\n"
+"außer die Konfigurationserweiterung worktreeConfig ist aktiviert. Bitte\n"
+"lesen Sie die Sektion \"CONFIGURATION_FILE\" in \"git help worktree\" für Details"
 
 #: builtin/config.c:687
 msgid "--get-color and variable type are incoherent"
 msgstr "Angabe von --get-color und Variablentyp sind ungültig."
 
 #: builtin/config.c:692
 msgid "only one action at a time"
 msgstr "Nur eine Aktion erlaubt."
@@ -11030,19 +11021,18 @@ msgstr "fordert von allen Remote-Repositories an"
 msgid "append to .git/FETCH_HEAD instead of overwriting"
 msgstr "an .git/FETCH_HEAD anhängen, anstatt zu überschreiben"
 
 #: builtin/fetch.c:119 builtin/pull.c:200
 msgid "path to upload pack on remote end"
 msgstr "Pfad des Programms zum Hochladen von Paketen auf der Gegenseite"
 
 #: builtin/fetch.c:120
-#, fuzzy
 msgid "force overwrite of local reference"
-msgstr "das Überschreiben von lokalen Branches erzwingen"
+msgstr "das Überschreiben einer lokalen Referenz erzwingen"
 
 #: builtin/fetch.c:122
 msgid "fetch from multiple remotes"
 msgstr "von mehreren Remote-Repositories anfordern"
 
 #: builtin/fetch.c:124 builtin/pull.c:204
 msgid "fetch all tags and associated objects"
 msgstr "alle Tags und verbundene Objekte anfordern"
@@ -11171,17 +11161,17 @@ msgstr "[Tag Aktualisierung]"
 
 #: builtin/fetch.c:731 builtin/fetch.c:771 builtin/fetch.c:787
 #: builtin/fetch.c:802
 msgid "unable to update local ref"
 msgstr "kann lokale Referenz nicht aktualisieren"
 
 #: builtin/fetch.c:735
 msgid "would clobber existing tag"
-msgstr ""
+msgstr "würde bestehende Tags verändern"
 
 #: builtin/fetch.c:757
 msgid "[new tag]"
 msgstr "[neues Tag]"
 
 #: builtin/fetch.c:760
 msgid "[new branch]"
 msgstr "[neuer Branch]"
@@ -11655,19 +11645,18 @@ msgstr "binäre Dateien als Text verarbeiten"
 msgid "don't match patterns in binary files"
 msgstr "keine Muster in Binärdateien finden"
 
 #: builtin/grep.c:822
 msgid "process binary files with textconv filters"
 msgstr "binäre Dateien mit \"textconv\"-Filtern verarbeiten"
 
 #: builtin/grep.c:824
-#, fuzzy
 msgid "search in subdirectories (default)"
-msgstr "lose Referenzen entfernen (Standard)"
+msgstr "in Unterverzeichnissen suchen (Standard)"
 
 #: builtin/grep.c:826
 msgid "descend at most <depth> levels"
 msgstr "höchstens <Tiefe> Ebenen durchlaufen"
 
 #: builtin/grep.c:830
 msgid "use extended POSIX regular expressions"
 msgstr "erweiterte reguläre Ausdrücke aus POSIX verwenden"
@@ -11817,19 +11806,18 @@ msgid "--no-index or --untracked cannot be used with revs"
 msgstr "--no-index oder --untracked können nicht mit Commits verwendet werden"
 
 #: builtin/grep.c:1020
 #, c-format
 msgid "unable to resolve revision: %s"
 msgstr "Konnte Commit nicht auflösen: %s"
 
 #: builtin/grep.c:1051
-#, fuzzy
 msgid "invalid option combination, ignoring --threads"
-msgstr "keine Unterstützung von Threads, --threads wird ignoriert"
+msgstr "ungültige Kombination von Optionen, --threads wird ignoriert"
 
 #: builtin/grep.c:1054 builtin/pack-objects.c:3395
 msgid "no threads support, ignoring --threads"
 msgstr "keine Unterstützung von Threads, --threads wird ignoriert"
 
 #: builtin/grep.c:1057 builtin/index-pack.c:1503 builtin/pack-objects.c:2716
 #, c-format
 msgid "invalid number of threads specified (%d)"
@@ -11993,19 +11981,19 @@ msgid "no info viewer handled the request"
 msgstr "kein Informations-Betrachter konnte mit dieser Anfrage umgehen"
 
 #: builtin/help.c:430 builtin/help.c:441 git.c:322
 #, c-format
 msgid "'%s' is aliased to '%s'"
 msgstr "Für '%s' wurde der Alias '%s' angelegt."
 
 #: builtin/help.c:444
-#, fuzzy, c-format
+#, c-format
 msgid "bad alias.%s string: %s"
-msgstr "Ungültiger branch.%s.mergeoptions String: %s"
+msgstr "Ungültiger alias.%s String: %s"
 
 #: builtin/help.c:473 builtin/help.c:503
 #, c-format
 msgid "usage: %s%s"
 msgstr "Verwendung: %s%s"
 
 #: builtin/help.c:487
 msgid "'git help config' for more information"
@@ -12460,17 +12448,17 @@ msgid "join whitespace-continued values"
 msgstr "durch Leerzeichen fortgesetzte Werte verbinden"
 
 #: builtin/interpret-trailers.c:107
 msgid "set parsing options"
 msgstr "Optionen für das Parsen setzen"
 
 #: builtin/interpret-trailers.c:109
 msgid "do not treat --- specially"
-msgstr ""
+msgstr "--- nicht speziell behandeln"
 
 #: builtin/interpret-trailers.c:110
 msgid "trailer"
 msgstr "Anhang"
 
 #: builtin/interpret-trailers.c:111
 msgid "trailer(s) to add"
 msgstr "Anhang/Anhänge hinzufügen"
@@ -12621,19 +12609,18 @@ msgstr "Basis-Commit sollte der Vorgänger der Revisionsliste sein."
 msgid "base commit shouldn't be in revision list"
 msgstr "Basis-Commit sollte nicht in der Revisionsliste enthalten sein."
 
 #: builtin/log.c:1418
 msgid "cannot get patch id"
 msgstr "kann Patch-Id nicht lesen"
 
 #: builtin/log.c:1470
-#, fuzzy
 msgid "failed to infer range-diff ranges"
-msgstr "Fehler beim Generieren des Diffs."
+msgstr "Fehler beim Ableiten des range-diff-Bereichs."
 
 #: builtin/log.c:1515
 msgid "use [PATCH n/m] even with a single patch"
 msgstr "[PATCH n/m] auch mit einzelnem Patch verwenden"
 
 #: builtin/log.c:1518
 msgid "use [PATCH] even with multiple patches"
 msgstr "[PATCH] auch mit mehreren Patches verwenden"
@@ -12781,30 +12768,28 @@ msgstr "eine Signatur aus einer Datei hinzufügen"
 msgid "don't print the patch filenames"
 msgstr "keine Dateinamen der Patches anzeigen"
 
 #: builtin/log.c:1584
 msgid "show progress while generating patches"
 msgstr "Forschrittsanzeige während der Erzeugung der Patches"
 
 #: builtin/log.c:1585
-#, fuzzy
 msgid "rev"
-msgstr "Revert"
+msgstr "Commit"
 
 #: builtin/log.c:1586
 msgid "show changes against <rev> in cover letter or single patch"
-msgstr ""
+msgstr "Änderungen gegenüber <Commit> im Deckblatt oder einzelnem Patch anzeigen"
 
 #: builtin/log.c:1589
 msgid "show changes against <refspec> in cover letter or single patch"
-msgstr ""
+msgstr "Änderungen gegenüber <Refspec> im Deckblatt oder einzelnem Patch anzeigen"
 
 #: builtin/log.c:1591
-#, fuzzy
 msgid "percentage by which creation is weighted"
 msgstr "Prozentsatz mit welchem Erzeugung gewichtet wird"
 
 #: builtin/log.c:1666
 #, c-format
 msgid "invalid ident line: %s"
 msgstr "Ungültige Identifikationszeile: %s"
 
@@ -12834,43 +12819,43 @@ msgstr "Standard-Ausgabe oder Verzeichnis, welches von beidem?"
 
 #: builtin/log.c:1729
 #, c-format
 msgid "Could not create directory '%s'"
 msgstr "Konnte Verzeichnis '%s' nicht erstellen."
 
 #: builtin/log.c:1816
 msgid "--interdiff requires --cover-letter or single patch"
-msgstr ""
+msgstr "--interdiff erfordert --cover-letter oder einzelnen Patch."
 
 #: builtin/log.c:1820
 msgid "Interdiff:"
-msgstr ""
+msgstr "Interdiff:"
 
 #: builtin/log.c:1821
 #, c-format
 msgid "Interdiff against v%d:"
-msgstr ""
+msgstr "Interdiff gegen v%d:"
 
 #: builtin/log.c:1827
 msgid "--creation-factor requires --range-diff"
-msgstr ""
+msgstr "--creation-factor erfordert --range-diff"
 
 #: builtin/log.c:1831
 msgid "--range-diff requires --cover-letter or single patch"
-msgstr ""
+msgstr "--range-diff erfordert --cover-letter oder einzelnen Patch."
 
 #: builtin/log.c:1839
 msgid "Range-diff:"
-msgstr ""
+msgstr "Range-Diff:"
 
 #: builtin/log.c:1840
 #, c-format
 msgid "Range-diff against v%d:"
-msgstr ""
+msgstr "Range-Diff gegen v%d:"
 
 #: builtin/log.c:1851
 #, c-format
 msgid "unable to read signature file '%s'"
 msgstr "Konnte Signatur-Datei '%s' nicht lesen"
 
 #: builtin/log.c:1887
 msgid "Generating patches"
@@ -13585,31 +13570,30 @@ msgid "allow missing objects"
 msgstr "fehlende Objekte erlauben"
 
 #: builtin/mktree.c:156
 msgid "allow creation of more than one tree"
 msgstr "die Erstellung von mehr als einem \"Tree\"-Objekt erlauben"
 
 #: builtin/multi-pack-index.c:8
 msgid "git multi-pack-index [--object-dir=<dir>] (write|verify)"
-msgstr ""
+msgstr "git multi-pack-index [--object-dir=<Verzeichnis>] (write|verify)"
 
 #: builtin/multi-pack-index.c:21
 msgid "object directory containing set of packfile and pack-index pairs"
-msgstr ""
+msgstr "Objekt-Verzeichnis, welches Paare von Packdateien und pack-index enthält"
 
 #: builtin/multi-pack-index.c:39
-#, fuzzy
 msgid "too many arguments"
 msgstr "Zu viele Argumente."
 
 #: builtin/multi-pack-index.c:48
-#, fuzzy, c-format
+#, c-format
 msgid "unrecognized verb: %s"
-msgstr "nicht erkanntes Argument: %s"
+msgstr "Nicht erkanntes Verb: %s"
 
 #: builtin/mv.c:17
 msgid "git mv [<options>] <source>... <destination>"
 msgstr "git mv [<Optionen>] <Quelle>... <Ziel>"
 
 #: builtin/mv.c:82
 #, c-format
 msgid "Directory %s is in index and no submodule?"
@@ -14268,19 +14252,19 @@ msgstr "Konnte Kopfbereich von Objekt '%s' nicht parsen."
 
 #: builtin/pack-objects.c:2083 builtin/pack-objects.c:2099
 #: builtin/pack-objects.c:2109
 #, c-format
 msgid "object %s cannot be read"
 msgstr "Objekt %s kann nicht gelesen werden."
 
 #: builtin/pack-objects.c:2086 builtin/pack-objects.c:2113
-#, fuzzy, c-format
+#, c-format
 msgid "object %s inconsistent object length (%<PRIuMAX> vs %<PRIuMAX>)"
-msgstr "Inkonsistente Objektlänge bei Objekt %s (%lu vs. %lu)"
+msgstr "Inkonsistente Objektlänge bei Objekt %s (%<PRIuMAX> vs %<PRIuMAX>)"
 
 #: builtin/pack-objects.c:2123
 msgid "suboptimal pack - out of memory"
 msgstr "ungünstiges Packet - Speicher voll"
 
 #: builtin/pack-objects.c:2451
 #, c-format
 msgid "Delta compression using up to %d threads"
@@ -14512,19 +14496,18 @@ msgstr "Behandlung für fehlende Objekte"
 
 #: builtin/pack-objects.c:3314
 msgid "do not pack objects in promisor packfiles"
 msgstr ""
 "keine Objekte aus Packdateien von partiell geklonten Remote-Repositories "
 "packen"
 
 #: builtin/pack-objects.c:3316
-#, fuzzy
 msgid "respect islands during delta compression"
-msgstr "Größe des Fensters für die Delta-Kompression"
+msgstr "Delta-Islands bei Delta-Kompression beachten"
 
 #: builtin/pack-objects.c:3340
 #, c-format
 msgid "delta chain depth %d is too deep, forcing %d"
 msgstr "Tiefe für Verkettung von Unterschieden %d ist zu tief, erzwinge %d"
 
 #: builtin/pack-objects.c:3345
 #, c-format
@@ -14735,19 +14718,19 @@ msgid ""
 "Your configuration specifies to merge with the ref '%s'\n"
 "from the remote, but no such ref was fetched."
 msgstr ""
 "Ihre Konfiguration gibt an, den Merge mit Referenz '%s'\n"
 "des Remote-Repositories durchzuführen, aber diese Referenz\n"
 "wurde nicht angefordert."
 
 #: builtin/pull.c:565
-#, fuzzy, c-format
+#, c-format
 msgid "unable to access commit %s"
-msgstr "Konnte Commit '%s' nicht parsen."
+msgstr "Konnte nicht auf Commit '%s' zugreifen."
 
 #: builtin/pull.c:843
 msgid "ignoring --verify-signatures for rebase"
 msgstr "Ignoriere --verify-signatures für Rebase"
 
 #: builtin/pull.c:891
 msgid "--[no-]autostash option is only valid with --rebase."
 msgstr "--[no-]autostash ist nur mit --rebase zulässig."
@@ -15214,63 +15197,59 @@ msgstr "weder den Index, noch das Arbeitsverzeichnis aktualisieren"
 msgid "skip applying sparse checkout filter"
 msgstr "Anwendung des Filters für partielles Auschecken überspringen"
 
 #: builtin/read-tree.c:152
 msgid "debug unpack-trees"
 msgstr "unpack-trees protokollieren"
 
 #: builtin/rebase.c:29
-#, fuzzy
 msgid ""
 "git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] [<upstream>] "
 "[<branch>]"
-msgstr ""
-"git archive --remote <Repository> [--exec <Programm>] [<Optionen>] <Commit-"
-"Referenz> [<Pfad>...]"
+msgstr "git rebase [-i] [<Optionen>] [--exec <Programm>] [--onto <neue-Basis>] [<Upstream>] [<Branch>]"
 
 #: builtin/rebase.c:31
 msgid ""
 "git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] --root [<branch>]"
-msgstr ""
+msgstr "git rebase [-i] [<Optionen>] [--exec <Programm>] [--onto <neue-Basis>] --root [<Branch>]"
 
 #: builtin/rebase.c:33
-#, fuzzy
 msgid "git rebase --continue | --abort | --skip | --edit-todo"
-msgstr "git am [<Optionen>] (--continue | --skip | --abort)"
+msgstr "git rebase --continue | --abort | --skip | --edit-todo"
 
 #: builtin/rebase.c:119
-#, fuzzy, c-format
+#, c-format
 msgid "%s requires an interactive rebase"
-msgstr "interaktiv ausführen"
+msgstr "%s erfordert ein interaktives Rebase"
 
 #: builtin/rebase.c:171
-#, fuzzy, c-format
+#, c-format
 msgid "could not get 'onto': '%s'"
-msgstr "Konnte '%s' nicht zu '%s' setzen."
+msgstr "Konnte 'onto' nicht bestimmen: '%s'"
 
 #: builtin/rebase.c:186
-#, fuzzy, c-format
+#, c-format
 msgid "invalid orig-head: '%s'"
-msgstr "Ungültige Datei: '%s'"
+msgstr "Ungültiges orig-head: '%s'"
 
 #: builtin/rebase.c:214
 #, c-format
 msgid "ignoring invalid allow_rerere_autoupdate: '%s'"
-msgstr ""
+msgstr "Ignoriere ungültiges allow_rerere_autoupdate: '%s'"
 
 #: builtin/rebase.c:259
-#, fuzzy, c-format
+#, c-format
 msgid "Could not read '%s'"
-msgstr "Konnte '%s' nicht lesen"
+msgstr "Konnte '%s' nicht lesen."
 
 #: builtin/rebase.c:277
-#, fuzzy, c-format
+#, c-format
 msgid "Cannot store %s"
-msgstr "kann %s nicht speichern"
+msgstr "Kann %s nicht speichern."
 
 #: builtin/rebase.c:337
 msgid ""
 "Resolve all conflicts manually, mark them as resolved with\n"
 "\"git add/rm <conflicted_files>\", then run \"git rebase --continue\".\n"
 "You can instead skip this commit: run \"git rebase --skip\".\n"
 "To abort and get back to the state before \"git rebase\", run \"git rebase --"
 "abort\"."
@@ -15279,191 +15258,174 @@ msgstr ""
 "\"git add/rm <konfliktbehaftete_Dateien>\" und führen Sie dann\n"
 "\"git rebase --continue\" aus.\n"
 "Sie können auch stattdessen diesen Commit auslassen, indem\n"
 "Sie \"git rebase --skip\" ausführen.\n"
 "Um abzubrechen und zurück zum Zustand vor \"git rebase\" zu gelangen,\n"
 "führen Sie \"git rebase --abort\" aus."
 
 #: builtin/rebase.c:561
-#, fuzzy
 msgid "could not determine HEAD revision"
-msgstr "Konnte HEAD nicht loslösen"
+msgstr "Konnte HEAD-Commit nicht bestimmen."
 
 #: builtin/rebase.c:752
-#, fuzzy, c-format
+#, c-format
 msgid ""
 "%s\n"
 "Please specify which branch you want to rebase against.\n"
 "See git-rebase(1) for details.\n"
 "\n"
 "    git rebase '<branch>'\n"
 "\n"
 msgstr ""
-"Bitte geben Sie den Branch an, gegen welchen Sie \"rebase\" ausführen "
-"möchten."
+"%s\n"
+"Bitte geben Sie den Branch an, gegen welchen Sie \"rebase\" ausführen möchten.\n"
+"Siehe git-rebase(1) für Details.\n"
+"\n"
+"    git rebase '<Branch>'\n"
+"\n"
 
 #: builtin/rebase.c:768
-#, fuzzy, c-format
+#, c-format
 msgid ""
 "If you wish to set tracking information for this branch you can do so with:\n"
 "\n"
 "    git branch --set-upstream-to=%s/<branch> %s\n"
 "\n"
 msgstr ""
-"Wenn Sie Tracking-Informationen für diesen Branch setzen möchten, können "
-"Sie\n"
-"dies tun mit:"
+"Wenn Sie Tracking-Informationen für diesen Branch setzen möchten,\n"
+"können Sie dies tun mit:\n"
+"\n"
+"    git branch --set-upstream-to=%s/<Branch> %s\n"
+"\n"
 
 #: builtin/rebase.c:814
-#, fuzzy
 msgid "rebase onto given branch instead of upstream"
-msgstr "Branch %s kann nicht sein eigener Upstream-Branch sein."
+msgstr "Rebase auf angegebenen Branch anstelle des Upstream-Branches ausführen"
 
 #: builtin/rebase.c:816
-#, fuzzy
 msgid "allow pre-rebase hook to run"
-msgstr "Der \"pre-rebase hook\" hat den Rebase zurückgewiesen."
+msgstr "Ausführung des pre-rebase-Hooks erlauben"
 
 #: builtin/rebase.c:818
 msgid "be quiet. implies --no-stat"
-msgstr ""
+msgstr "weniger Ausgaben (impliziert --no-stat)"
 
 #: builtin/rebase.c:821
 msgid "display a diffstat of what changed upstream"
-msgstr ""
+msgstr "Zusammenfassung der Unterschiede gegenüber dem Upstream-Branch anzeigen"
 
 #: builtin/rebase.c:824
-#, fuzzy
 msgid "do not show diffstat of what changed upstream"
-msgstr "keine Zusammenfassung der Unterschiede am Schluss des Merges anzeigen"
+msgstr "Zusammenfassung der Unterschiede gegenüber dem Upstream-Branch verbergen"
 
 #: builtin/rebase.c:827
-#, fuzzy
 msgid "add a Signed-off-by: line to each commit"
-msgstr "der Commit-Beschreibung eine Signed-off-by Zeile hinzufügen"
+msgstr "eine \"Signed-off-by:\"-Zeile zu jedem Commit hinzufügen"
 
 #: builtin/rebase.c:829 builtin/rebase.c:833 builtin/rebase.c:835
 msgid "passed to 'git am'"
-msgstr ""
+msgstr "an 'git am' übergeben"
 
 #: builtin/rebase.c:837 builtin/rebase.c:839
-#, fuzzy
 msgid "passed to 'git apply'"
-msgstr "an git-apply übergeben"
+msgstr "an 'git-apply' übergeben"
 
 #: builtin/rebase.c:841 builtin/rebase.c:844
 msgid "cherry-pick all commits, even if unchanged"
-msgstr ""
+msgstr "Cherry-Pick auf alle Commits ausführen, auch wenn diese unverändert sind"
 
 #: builtin/rebase.c:846
-#, fuzzy
 msgid "continue"
-msgstr "Rebase fortsetzen"
+msgstr "fortsetzen"
 
 #: builtin/rebase.c:849
-#, fuzzy
 msgid "skip current patch and continue"
-msgstr "den aktuellen Patch auslassen"
+msgstr "den aktuellen Patch auslassen und fortfahren"
 
 #: builtin/rebase.c:851
-#, fuzzy
 msgid "abort and check out the original branch"
-msgstr ""
-"  (benutzen Sie \"git rebase --abort\", um den ursprünglichen Branch "
-"auszuchecken)"
+msgstr "abbrechen und den ursprünglichen Branch auschecken"
 
 #: builtin/rebase.c:854
-#, fuzzy
 msgid "abort but keep HEAD where it is"
-msgstr "Patch-Operation abbrechen, aber HEAD an aktueller Stelle belassen"
+msgstr "abbrechen, aber HEAD an aktueller Stelle belassen"
 
 #: builtin/rebase.c:855
-#, fuzzy
 msgid "edit the todo list during an interactive rebase"
-msgstr ""
-"Die --edit-todo Aktion kann nur während eines interaktiven Rebase verwendet "
-"werden."
+msgstr "TODO-Liste während eines interaktiven Rebase bearbeiten"
 
 #: builtin/rebase.c:858
-#, fuzzy
 msgid "show the patch file being applied or merged"
-msgstr "den Patch, der gerade angewendet wird, anzeigen"
+msgstr "den Patch, der gerade angewendet oder zusammengeführt wird, anzeigen"
 
 #: builtin/rebase.c:861
-#, fuzzy
 msgid "use merging strategies to rebase"
-msgstr "zu verwendende Merge-Strategie"
+msgstr "Merge-Strategien beim Rebase verwenden"
 
 #: builtin/rebase.c:865
 msgid "let the user edit the list of commits to rebase"
-msgstr ""
+msgstr "den Benutzer die Liste der Commits für den Rebase bearbeiten lassen"
 
 #: builtin/rebase.c:869
 msgid "try to recreate merges instead of ignoring them"
-msgstr ""
+msgstr "versuchen, Merges wiederherzustellen anstatt sie zu ignorieren"
 
 #: builtin/rebase.c:873
 msgid "allow rerere to update index  with resolved conflict"
-msgstr ""
+msgstr "Rerere erlauben, den Index mit aufgelöstem Konflikt zu aktualisieren"
 
 #: builtin/rebase.c:876
-#, fuzzy
 msgid "preserve empty commits during rebase"
-msgstr "ursprüngliche, leere Commits erhalten"
+msgstr "leere Commits während des Rebase erhalten"
 
 #: builtin/rebase.c:878
 msgid "move commits that begin with squash!/fixup! under -i"
-msgstr ""
+msgstr "bei -i Commits verschieben, die mit squash!/fixup! beginnen"
 
 #: builtin/rebase.c:884
-#, fuzzy
 msgid "automatically stash/stash pop before and after"
-msgstr "automatischer Stash/Stash-Pop vor und nach eines Rebase"
+msgstr "automatischer Stash/Stash-Pop davor und danach"
 
 #: builtin/rebase.c:886
 msgid "add exec lines after each commit of the editable list"
-msgstr ""
+msgstr "exec-Zeilen nach jedem Commit der editierbaren Liste hinzufügen"
 
 #: builtin/rebase.c:890
-#, fuzzy
 msgid "allow rebasing commits with empty messages"
-msgstr "Commits mit leerer Beschreibung erlauben"
+msgstr "Rebase von Commits mit leerer Beschreibung erlauben"
 
 #: builtin/rebase.c:893
 msgid "try to rebase merges instead of skipping them"
-msgstr ""
+msgstr "versuchen, Rebase mit Merges auszuführen, anstatt diese zu überspringen"
 
 #: builtin/rebase.c:896
-#, fuzzy
 msgid "use 'merge-base --fork-point' to refine upstream"
-msgstr "git merge-base --fork-point <Referenz> [<Commit>]"
+msgstr "'git merge-base --fork-point' benutzen, um Upstream-Branch zu bestimmen"
 
 #: builtin/rebase.c:898
-#, fuzzy
 msgid "use the given merge strategy"
-msgstr "Option für Merge-Strategie"
+msgstr "angegebene Merge-Strategie verwenden"
 
 #: builtin/rebase.c:900 builtin/revert.c:111
 msgid "option"
 msgstr "Option"
 
 #: builtin/rebase.c:901
 msgid "pass the argument through to the merge strategy"
-msgstr ""
+msgstr "Argument zur Merge-Strategie durchreichen"
 
 #: builtin/rebase.c:904
-#, fuzzy
 msgid "rebase all reachable commits up to the root(s)"
-msgstr "alle nicht erreichbaren Objekte von der Objektdatenbank entfernen"
+msgstr "Rebase auf alle erreichbaren Commits bis zum Root-Commit ausführen"
 
 #: builtin/rebase.c:920
-#, fuzzy, c-format
+#, c-format
 msgid "could not exec %s"
-msgstr "konnte %s nicht parsen"
+msgstr "Konnte 'exec %s' nicht ausführen."
 
 #: builtin/rebase.c:938 git-legacy-rebase.sh:213
 msgid "It looks like 'git am' is in progress. Cannot rebase."
 msgstr "'git-am' scheint im Gange zu sein. Kann Rebase nicht durchführen."
 
 #: builtin/rebase.c:979 git-legacy-rebase.sh:387
 msgid "No rebase in progress?"
 msgstr "Kein Rebase im Gange?"
@@ -15482,257 +15444,239 @@ msgstr "Kann HEAD nicht lesen"
 msgid ""
 "You must edit all merge conflicts and then\n"
 "mark them as resolved using git add"
 msgstr ""
 "Sie müssen alle Merge-Konflikte editieren und diese dann\n"
 "mittels \"git add\" als aufgelöst markieren"
 
 #: builtin/rebase.c:1026
-#, fuzzy
 msgid "could not discard worktree changes"
-msgstr "Kann Änderungen im Arbeitsverzeichnis nicht löschen"
+msgstr "Konnte Änderungen im Arbeitsverzeichnis nicht verwerfen."
 
 #: builtin/rebase.c:1044
-#, fuzzy, c-format
+#, c-format
 msgid "could not move back to %s"
-msgstr "Konnte nicht zu $head_name zurückgehen"
+msgstr "Konnte nicht zu %s zurückgehen."
 
 #: builtin/rebase.c:1055 builtin/rm.c:368
 #, c-format
 msgid "could not remove '%s'"
 msgstr "Konnte '%s' nicht löschen"
 
 #: builtin/rebase.c:1081
-#, fuzzy, c-format
+#, c-format
 msgid ""
 "It seems that there is already a %s directory, and\n"
 "I wonder if you are in the middle of another rebase.  If that is the\n"
 "case, please try\n"
 "\t%s\n"
 "If that is not the case, please\n"
 "\t%s\n"
 "and run me again.  I am stopping in case you still have something\n"
 "valuable there.\n"
 msgstr ""
-"Es sieht so aus, als ob es das Verzeichnis $state_dir_base bereits gibt\n"
+"Es sieht so aus, als ob es das Verzeichnis %s bereits gibt\n"
 "und es könnte ein anderer Rebase im Gange sein. Wenn das der Fall ist,\n"
 "probieren Sie bitte\n"
-"\t$cmd_live_rebase\n"
+"\t%s\n"
 "Wenn das nicht der Fall ist, probieren Sie bitte\n"
-"\t$cmd_clear_stale_rebase\n"
+"\t%s\n"
 "und führen Sie diesen Befehl nochmal aus. Es wird angehalten, falls noch\n"
-"etwas Schützenswertes vorhanden ist."
+"etwas Schützenswertes vorhanden ist.\n"
 
 #: builtin/rebase.c:1102
-#, fuzzy
 msgid "switch `C' expects a numerical value"
-msgstr "Schalter '%c' erwartet einen numerischen Wert"
+msgstr "Schalter `C' erwartet einen numerischen Wert."
 
 #: builtin/rebase.c:1139
-#, fuzzy, c-format
+#, c-format
 msgid "Unknown mode: %s"
-msgstr "Unbekannter --patch Modus: %s"
+msgstr "Unbekannter Modus: %s"
 
 #: builtin/rebase.c:1161
 msgid "--strategy requires --merge or --interactive"
-msgstr ""
+msgstr "--strategy erfordert --merge oder --interactive"
 
 #: builtin/rebase.c:1204
 #, c-format
 msgid ""
 "error: cannot combine interactive options (--interactive, --exec, --rebase-"
 "merges, --preserve-merges, --keep-empty, --root + --onto) with am options "
 "(%s)"
-msgstr ""
+msgstr "Fehler: 'interactive'-Optionen (--interactive, --exec, --rebase-merges, --preserve-merges, --keep-empty, --root + --onto ) können nicht mit 'am'-Optionen (%s) kombiniert werden."
 
 #: builtin/rebase.c:1209
-#, fuzzy, c-format
+#, c-format
 msgid ""
 "error: cannot combine merge options (--merge, --strategy, --strategy-option) "
 "with am options (%s)"
-msgstr ""
-"Fehler: '--rebase-merges' und '--strategy-option' können nicht kombiniert "
-"werden."
+msgstr "Fehler: 'merge'-Optionen (--merge, --strategy, --strategy-option) können nicht mit 'am'-Optionen (%s) kombiniert werden."
 
 #: builtin/rebase.c:1229 git-legacy-rebase.sh:528
-#, fuzzy
 msgid "error: cannot combine '--preserve-merges' with '--rebase-merges'"
 msgstr ""
 "Fehler: '--preserve-merges' und '--rebase-merges' können nicht kombiniert "
 "werden."
 
 #: builtin/rebase.c:1234 git-legacy-rebase.sh:534
-#, fuzzy
 msgid "error: cannot combine '--rebase-merges' with '--strategy-option'"
 msgstr ""
 "Fehler: '--rebase-merges' und '--strategy-option' können nicht kombiniert "
 "werden."
 
 #: builtin/rebase.c:1237 git-legacy-rebase.sh:536
-#, fuzzy
 msgid "error: cannot combine '--rebase-merges' with '--strategy'"
 msgstr ""
 "Fehler: '--rebase-merges' und '--strategy' können nicht kombiniert werden."
 
 #: builtin/rebase.c:1261
-#, fuzzy, c-format
+#, c-format
 msgid "invalid upstream '%s'"
-msgstr "Ungültiger Pfad '%s'"
+msgstr "Ungültiger Upstream '%s'"
 
 #: builtin/rebase.c:1267
-#, fuzzy
 msgid "Could not create new root commit"
-msgstr "Konnte neu erstellten Commit nicht analysieren."
+msgstr "Konnte neuen Root-Commit nicht erstellen."
 
 #: builtin/rebase.c:1285
-#, fuzzy, c-format
+#, c-format
 msgid "'%s': need exactly one merge base"
-msgstr "Brauche genau einen Commit-Bereich."
+msgstr "'%s': brauche genau eine Merge-Basis"
 
 #: builtin/rebase.c:1292
-#, fuzzy, c-format
+#, c-format
 msgid "Does not point to a valid commit '%s'"
-msgstr "$onto_name zeigt auf keinen gültigen Commit"
+msgstr "'%s' zeigt auf keinen gültigen Commit."
 
 #: builtin/rebase.c:1317
-#, fuzzy, c-format
+#, c-format
 msgid "fatal: no such branch/commit '%s'"
-msgstr "fatal: Branch/Commit '$branch_name' nicht gefunden"
+msgstr "fatal: Branch/Commit '%s' nicht gefunden"
 
 #: builtin/rebase.c:1325 builtin/submodule--helper.c:37
 #: builtin/submodule--helper.c:1930
 #, c-format
 msgid "No such ref: %s"
 msgstr "Referenz nicht gefunden: %s"
 
 #: builtin/rebase.c:1337
-#, fuzzy
 msgid "Could not resolve HEAD to a revision"
-msgstr "Konnte HEAD-Commit nicht auflösen."
+msgstr "Konnte HEAD zu keinem Commit auflösen."
 
 #: builtin/rebase.c:1377 git-legacy-rebase.sh:657
 msgid "Cannot autostash"
 msgstr "Kann automatischen Stash nicht erzeugen."
 
 #: builtin/rebase.c:1380
-#, fuzzy, c-format
+#, c-format
 msgid "Unexpected stash response: '%s'"
-msgstr "Unerwartetes wanted-ref: '%s'"
+msgstr "Unerwartete 'stash'-Antwort: '%s'"
 
 #: builtin/rebase.c:1386
-#, fuzzy, c-format
+#, c-format
 msgid "Could not create directory for '%s'"
-msgstr "Konnte Verzeichnis '%s' nicht erstellen."
+msgstr "Konnte Verzeichnis für '%s' nicht erstellen."
 
 #: builtin/rebase.c:1389
-#, fuzzy, c-format
+#, c-format
 msgid "Created autostash: %s\n"
-msgstr "Automatischen Stash erzeugt: $stash_abbrev"
+msgstr "Automatischen Stash erzeugt: %s\n"
 
 #: builtin/rebase.c:1392
-#, fuzzy
 msgid "could not reset --hard"
-msgstr "Konnte orig-head nicht lesen."
+msgstr "Konnte 'reset --hard' nicht ausführen."
 
 #: builtin/rebase.c:1393 builtin/reset.c:113
 #, c-format
 msgid "HEAD is now at %s"
 msgstr "HEAD ist jetzt bei %s"
 
 #: builtin/rebase.c:1409 git-legacy-rebase.sh:666
 msgid "Please commit or stash them."
 msgstr "Bitte committen Sie die Änderungen oder benutzen Sie \"stash\"."
 
 #: builtin/rebase.c:1436
-#, fuzzy, c-format
+#, c-format
 msgid "could not parse '%s'"
-msgstr "konnte %s nicht parsen"
+msgstr "Konnte '%s' nicht parsen."
 
 #: builtin/rebase.c:1447
-#, fuzzy, c-format
+#, c-format
 msgid "could not switch to %s"
-msgstr "Konnte nicht nach '%s' schreiben."
+msgstr "Konnte nicht zu %s wechseln."
 
 #: builtin/rebase.c:1458 git-legacy-rebase.sh:689
 #, sh-format
 msgid "HEAD is up to date."
 msgstr "HEAD ist aktuell."
 
 #: builtin/rebase.c:1460
-#, fuzzy, c-format
+#, c-format
 msgid "Current branch %s is up to date.\n"
-msgstr "Aktueller Branch $branch_name ist auf dem neuesten Stand."
+msgstr "Aktueller Branch %s ist auf dem neuesten Stand.\n"
 
 #: builtin/rebase.c:1468 git-legacy-rebase.sh:699
 #, sh-format
 msgid "HEAD is up to date, rebase forced."
 msgstr "HEAD ist aktuell, Rebase erzwungen."
 
 #: builtin/rebase.c:1470
-#, fuzzy, c-format
+#, c-format
 msgid "Current branch %s is up to date, rebase forced.\n"
-msgstr ""
-"Aktueller Branch $branch_name ist auf dem neuesten Stand, Rebase erzwungen."
+msgstr "Aktueller Branch %s ist auf dem neuesten Stand, Rebase erzwungen.\n"
 
 #: builtin/rebase.c:1478 git-legacy-rebase.sh:208
 msgid "The pre-rebase hook refused to rebase."
 msgstr "Der \"pre-rebase hook\" hat den Rebase zurückgewiesen."
 
 #: builtin/rebase.c:1484
-#, fuzzy, c-format
+#, c-format
 msgid "Changes from %s to %s:\n"
-msgstr "Änderungen von $mb zu $onto:"
+msgstr "Änderungen von %s zu %s:\n"
 
 #: builtin/rebase.c:1507
-#, fuzzy, c-format
+#, c-format
 msgid "First, rewinding head to replay your work on top of it...\n"
-msgstr ""
-"Zunächst wird der Branch zurückgespult, um Ihre Änderungen\n"
-"darauf neu anzuwenden ..."
+msgstr "Zunächst wird der Branch zurückgespult, um Ihre Änderungen darauf neu anzuwenden...\n"
 
 #: builtin/rebase.c:1513
-#, fuzzy
 msgid "Could not detach HEAD"
-msgstr "Konnte HEAD nicht loslösen"
+msgstr "Konnte HEAD nicht loslösen."
 
 #: builtin/rebase.c:1522
-#, fuzzy, c-format
+#, c-format
 msgid "Fast-forwarded %s to %s. \n"
-msgstr "Spule vor zu $sha1"
+msgstr "%s zu %s vorgespult.\n"
 
 #: builtin/rebase--interactive.c:24
-#, fuzzy
 msgid "no HEAD?"
 msgstr "Kein HEAD?"
 
 #: builtin/rebase--interactive.c:51
-#, fuzzy, c-format
+#, c-format
 msgid "could not create temporary %s"
-msgstr "konnte temporäre Datei nicht erstellen"
+msgstr "Konnte temporäres Verzeichnis '%s' nicht erstellen."
 
 #: builtin/rebase--interactive.c:57
-#, fuzzy
 msgid "could not mark as interactive"
-msgstr "Konnte nicht als interaktiven Rebase markieren."
+msgstr "Markierung auf interaktiven Rebase fehlgeschlagen."
 
 #: builtin/rebase--interactive.c:101
-#, fuzzy, c-format
+#, c-format
 msgid "could not open %s"
-msgstr "Konnte '%s' nicht öffnen"
+msgstr "Konnte '%s' nicht öffnen."
 
 #: builtin/rebase--interactive.c:114
-#, fuzzy
 msgid "could not generate todo list"
 msgstr "Konnte TODO-Liste nicht erzeugen."
 
 #: builtin/rebase--interactive.c:129
-#, fuzzy
 msgid "git rebase--interactive [<options>]"
-msgstr "git rebase--helper [<Optionen>]"
+msgstr "git rebase--interactive [<Optionen>]"
 
 #: builtin/rebase--interactive.c:148
 msgid "keep empty commits"
 msgstr "leere Commits behalten"
 
 #: builtin/rebase--interactive.c:150 builtin/revert.c:124
 msgid "allow commits with empty messages"
 msgstr "Commits mit leerer Beschreibung erlauben"
@@ -15742,41 +15686,37 @@ msgid "rebase merge commits"
 msgstr "Rebase auf Merge-Commits ausführen"
 
 #: builtin/rebase--interactive.c:153
 msgid "keep original branch points of cousins"
 msgstr "originale Branch-Punkte der Cousins behalten"
 
 #: builtin/rebase--interactive.c:155
 msgid "move commits that begin with squash!/fixup!"
-msgstr ""
+msgstr "Commits verschieben, die mit squash!/fixup! beginnen"
 
 #: builtin/rebase--interactive.c:156
-#, fuzzy
 msgid "sign commits"
-msgstr "Commits mit GPG signieren"
+msgstr "Commits signieren"
 
 #: builtin/rebase--interactive.c:158
 msgid "continue rebase"
 msgstr "Rebase fortsetzen"
 
 #: builtin/rebase--interactive.c:160
-#, fuzzy
 msgid "skip commit"
-msgstr "Commit"
+msgstr "Commit auslassen"
 
 #: builtin/rebase--interactive.c:161
-#, fuzzy
 msgid "edit the todo list"
-msgstr "die TODO-Liste prüfen"
+msgstr "die TODO-Liste bearbeiten"
 
 #: builtin/rebase--interactive.c:163
-#, fuzzy
 msgid "show the current patch"
-msgstr "den aktuellen Patch auslassen"
+msgstr "den aktuellen Patch anzeigen"
 
 #: builtin/rebase--interactive.c:166
 msgid "shorten commit ids in the todo list"
 msgstr "Commit-IDs in der TODO-Liste verkürzen"
 
 #: builtin/rebase--interactive.c:168
 msgid "expand commit ids in the todo list"
 msgstr "Commit-IDs in der TODO-Liste erweitern"
@@ -15790,104 +15730,89 @@ msgid "rearrange fixup/squash lines"
 msgstr "fixup/squash-Zeilen umordnen"
 
 #: builtin/rebase--interactive.c:174
 msgid "insert exec commands in todo list"
 msgstr "\"exec\"-Befehle in TODO-Liste einfügen"
 
 #: builtin/rebase--interactive.c:175
 msgid "onto"
-msgstr ""
+msgstr "auf"
 
 #: builtin/rebase--interactive.c:177
-#, fuzzy
 msgid "restrict-revision"
-msgstr "Commit"
+msgstr "Begrenzungscommit"
 
 #: builtin/rebase--interactive.c:177
-#, fuzzy
 msgid "restrict revision"
-msgstr "Commit"
+msgstr "Begrenzungscommit"
 
 #: builtin/rebase--interactive.c:178
-#, fuzzy
 msgid "squash-onto"
-msgstr "squash-onto schreiben"
+msgstr "squash-onto"
 
 #: builtin/rebase--interactive.c:179
-#, fuzzy
 msgid "squash onto"
-msgstr "squash-onto schreiben"
+msgstr "squash onto"
 
 #: builtin/rebase--interactive.c:181
-#, fuzzy
 msgid "the upstream commit"
-msgstr "Informationen zum Upstream-Branch entfernen"
+msgstr "der Upstream-Commit"
 
 #: builtin/rebase--interactive.c:182
-#, fuzzy
 msgid "head-name"
-msgstr "umbenennen"
+msgstr "head-Name"
 
 #: builtin/rebase--interactive.c:182
-#, fuzzy
 msgid "head name"
-msgstr "voraus "
+msgstr "head-Name"
 
 #: builtin/rebase--interactive.c:187
-#, fuzzy
 msgid "rebase strategy"
-msgstr "Merge-Strategie"
+msgstr "Rebase-Strategie"
 
 #: builtin/rebase--interactive.c:188
-#, fuzzy
 msgid "strategy-opts"
-msgstr "Strategie"
+msgstr "Strategie-Optionen"
 
 #: builtin/rebase--interactive.c:189
-#, fuzzy
 msgid "strategy options"
-msgstr "decorate-Optionen"
+msgstr "Strategie-Optionen"
 
 #: builtin/rebase--interactive.c:190
 msgid "switch-to"
-msgstr ""
+msgstr "wechseln zu"
 
 #: builtin/rebase--interactive.c:191
-#, fuzzy
 msgid "the branch or commit to checkout"
-msgstr "einzelnen Commit zu einem ausgecheckten CSV-Repository exportieren"
+msgstr "der Branch oder Commit zum Auschecken"
 
 #: builtin/rebase--interactive.c:192
-#, fuzzy
 msgid "onto-name"
-msgstr "Name"
+msgstr "onto-Name"
 
 #: builtin/rebase--interactive.c:192
-#, fuzzy
 msgid "onto name"
-msgstr "Name des Remote-Repositories"
+msgstr "onto-Name"
 
 #: builtin/rebase--interactive.c:193
-#, fuzzy
 msgid "cmd"
-msgstr "Programm"
+msgstr "Befehl"
 
 #: builtin/rebase--interactive.c:193
-#, fuzzy
 msgid "the command to run"
-msgstr "Keine Befehle ausgeführt."
+msgstr "auszuführender Befehl"
 
 #: builtin/rebase--interactive.c:220
 msgid "--[no-]rebase-cousins has no effect without --rebase-merges"
 msgstr "--[no-]rebase-cousins hat ohne --rebase-merges keine Auswirkung"
 
 #: builtin/rebase--interactive.c:226
 msgid "a base commit must be provided with --upstream or --onto"
-msgstr ""
+msgstr "Ein Basis-Commit muss mit --upstream oder --onto angegeben werden."
 
 #: builtin/receive-pack.c:33
 msgid "git receive-pack <git-dir>"
 msgstr "git receive-pack <Git-Verzeichnis>"
 
 #: builtin/receive-pack.c:830
 msgid ""
 "By default, updating the current branch in a non-bare repository\n"
@@ -16116,19 +16041,19 @@ msgstr "Konnte Fetch-Map für Refspec %s nicht bekommen"
 msgid "(matching)"
 msgstr "(übereinstimmend)"
 
 #: builtin/remote.c:455
 msgid "(delete)"
 msgstr "(lösche)"
 
 #: builtin/remote.c:629 builtin/remote.c:765 builtin/remote.c:864
-#, fuzzy, c-format
+#, c-format
 msgid "No such remote: '%s'"
-msgstr "Kein solches Remote-Repository '%s'"
+msgstr "Kein solches Remote-Repository: '%s'"
 
 #: builtin/remote.c:646
 #, c-format
 msgid "Could not rename config section '%s' to '%s'"
 msgstr "Konnte Sektion '%s' in Konfiguration nicht nach '%s' umbenennen"
 
 #: builtin/remote.c:666
 #, c-format
@@ -16514,19 +16439,18 @@ msgstr "git-update-server-info nicht ausführen"
 msgid "pass --local to git-pack-objects"
 msgstr "--local an git-pack-objects übergeben"
 
 #: builtin/repack.c:310
 msgid "write bitmap index"
 msgstr "Bitmap-Index schreiben"
 
 #: builtin/repack.c:312
-#, fuzzy
 msgid "pass --delta-islands to git-pack-objects"
-msgstr "--local an git-pack-objects übergeben"
+msgstr "--delta-islands an git-pack-objects übergeben"
 
 #: builtin/repack.c:313
 msgid "approxidate"
 msgstr "Datumsangabe"
 
 #: builtin/repack.c:314
 msgid "with -A, do not loosen objects older than this"
 msgstr "mit -A, keine Objekte älter als dieses Datum löschen"
@@ -16837,22 +16761,22 @@ msgid "git rerere [clear | forget <path>... | status | remaining | diff | gc]"
 msgstr "git rerere [clean | forget <Pfad>... | status | remaining | diff | gc]"
 
 #: builtin/rerere.c:60
 msgid "register clean resolutions in index"
 msgstr "saubere Auflösungen im Index registrieren"
 
 #: builtin/rerere.c:79
 msgid "'git rerere forget' without paths is deprecated"
-msgstr ""
+msgstr "'git rerere forget' ohne Pfade ist veraltet."
 
 #: builtin/rerere.c:111
-#, fuzzy, c-format
+#, c-format
 msgid "unable to generate diff for '%s'"
-msgstr "Fehler beim Generieren des Diffs."
+msgstr "Konnte kein Diff für '%s' generieren."
 
 #: builtin/reset.c:31
 msgid ""
 "git reset [--mixed | --soft | --hard | --merge | --keep] [-q] [<commit>]"
 msgstr ""
 "git reset [--mixed | --soft | --hard | --merge | --keep] [-q] [<Commit>]"
 
 #: builtin/reset.c:32
@@ -16966,16 +16890,21 @@ msgstr "Nicht zum Commit vorgemerkte Änderungen nach Zurücksetzung:"
 #: builtin/reset.c:390
 #, c-format
 msgid ""
 "\n"
 "It took %.2f seconds to enumerate unstaged changes after reset.  You can\n"
 "use '--quiet' to avoid this.  Set the config setting reset.quiet to true\n"
 "to make this the default.\n"
 msgstr ""
+"\n"
+"Es dauerte %.2f Sekunden, um über die nach einem Reset nicht zum Commit\n"
+"vorgemerkten Änderungen zu zählen. Sie können '--quiet' benutzen, um\n"
+"das zu verhindern. Setzen Sie die Konfigurationseinstellung reset.quiet\n"
+"auf \"true\", um das zum Standard zu machen.\n"
 
 #: builtin/reset.c:400
 #, c-format
 msgid "Could not reset index file to revision '%s'."
 msgstr "Konnte Index-Datei nicht zu Commit '%s' setzen."
 
 #: builtin/reset.c:404
 msgid "Could not write new index file."
@@ -17541,24 +17470,23 @@ msgstr ""
 msgid "Recurse into nested submodules"
 msgstr "Rekursion in verschachtelte Submodule durchführen"
 
 #: builtin/submodule--helper.c:568
 msgid "git submodule--helper foreach [--quiet] [--recursive] <command>"
 msgstr "git submodule--helper foreach [--quiet] [--recursive] <Befehl>"
 
 #: builtin/submodule--helper.c:595
-#, fuzzy, c-format
+#, c-format
 msgid ""
 "could not look up configuration '%s'. Assuming this repository is its own "
 "authoritative upstream."
 msgstr ""
-"Konnte Konfiguration '%s' nicht nachschlagen. Nehme an, dass dieses "
-"Repository\n"
-"sein eigenes verbindliches Upstream-Repository ist."
+"Konnte Konfiguration '%s' nicht nachschlagen. Nehme an, dass dieses\n"
+"Repository sein eigenes verbindliches Upstream-Repository ist."
 
 #: builtin/submodule--helper.c:663
 #, c-format
 msgid "Failed to register url for submodule path '%s'"
 msgstr ""
 "Fehler beim Eintragen der URL für Submodul-Pfad '%s' in die Konfiguration."
 
 #: builtin/submodule--helper.c:667
@@ -17763,28 +17691,24 @@ msgid "clone of '%s' into submodule path '%s' failed"
 msgstr "Klonen von '%s' in Submodul-Pfad '%s' fehlgeschlagen"
 
 #: builtin/submodule--helper.c:1433
 #, c-format
 msgid "could not get submodule directory for '%s'"
 msgstr "Konnte Submodul-Verzeichnis '%s' nicht finden."
 
 #: builtin/submodule--helper.c:1469
-#, fuzzy, c-format
+#, c-format
 msgid "Invalid update mode '%s' for submodule path '%s'"
-msgstr ""
-"Fehler bei Änderung des Aktualisierungsmodus für Submodul-Pfad '%s' in der\n"
-"Konfiguration."
+msgstr "Ungültiger Aktualisierungsmodus '%s' für Submodul-Pfad '%s'."
 
 #: builtin/submodule--helper.c:1473
-#, fuzzy, c-format
+#, c-format
 msgid "Invalid update mode '%s' configured for submodule path '%s'"
-msgstr ""
-"Fehler bei Änderung des Aktualisierungsmodus für Submodul-Pfad '%s' in der\n"
-"Konfiguration."
+msgstr "Ungültiger Aktualisierungsmodus '%s' für Submodul-Pfad '%s' konfiguriert."
 
 #: builtin/submodule--helper.c:1566
 #, c-format
 msgid "Submodule path '%s' not initialized"
 msgstr "Submodul-Pfad '%s' nicht initialisiert"
 
 #: builtin/submodule--helper.c:1570
 msgid "Maybe you want to use 'update --init'?"
@@ -17857,48 +17781,44 @@ msgstr "Fehlerhafter Wert für --update Parameter"
 msgid ""
 "Submodule (%s) branch configured to inherit branch from superproject, but "
 "the superproject is not on any branch"
 msgstr ""
 "Branch von Submodul (%s) ist konfiguriert, den Branch des Hauptprojektes\n"
 "zu erben, aber das Hauptprojekt befindet sich auf keinem Branch."
 
 #: builtin/submodule--helper.c:2057
-#, fuzzy, c-format
+#, c-format
 msgid "could not get a repository handle for submodule '%s'"
-msgstr "konnte Name für Submodul '%s' nicht nachschlagen"
+msgstr "Konnte kein Repository-Handle für Submodul '%s' erhalten."
 
 #: builtin/submodule--helper.c:2090
 msgid "recurse into submodules"
 msgstr "Rekursion in Submodule durchführen"
 
 #: builtin/submodule--helper.c:2096
 msgid "git submodule--helper embed-git-dir [<path>...]"
 msgstr "git submodule--helper embed-git-dir [<Pfad>...]"
 
 #: builtin/submodule--helper.c:2152
 msgid "check if it is safe to write to the .gitmodules file"
-msgstr ""
+msgstr "prüfen, ob es sicher ist, in die Datei .gitmodules zu schreiben"
 
 #: builtin/submodule--helper.c:2157
-#, fuzzy
 msgid "git submodule--helper config name [value]"
-msgstr "git submodule--helper name <Pfad>"
+msgstr "git submodule--helper config name [Wert]"
 
 #: builtin/submodule--helper.c:2158
-#, fuzzy
 msgid "git submodule--helper config --check-writeable"
-msgstr "git submodule--helper init [<Pfad>]"
+msgstr "git submodule--helper config --check-writeable"
 
 #: builtin/submodule--helper.c:2175 git-submodule.sh:169
-#, fuzzy, sh-format
+#, sh-format
 msgid "please make sure that the .gitmodules file is in the working tree"
-msgstr ""
-"Bitte merken Sie Ihre Änderungen in .gitmodules zum Commit vor oder\n"
-"benutzen Sie \"stash\", um fortzufahren."
+msgstr "Bitte stellen Sie sicher, dass sich die Datei .gitmodules im Arbeitsverzeichnis befindet."
 
 #: builtin/submodule--helper.c:2225
 #, c-format
 msgid "%s doesn't support --super-prefix"
 msgstr "%s unterstützt kein --super-prefix"
 
 #: builtin/submodule--helper.c:2231
 #, c-format
@@ -18582,33 +18502,39 @@ msgid "expire working trees older than <time>"
 msgstr "Arbeitsverzeichnisse älter als <Zeit> verfallen lassen"
 
 #: builtin/worktree.c:234
 #, c-format
 msgid "'%s' already exists"
 msgstr "'%s' existiert bereits"
 
 #: builtin/worktree.c:251
-#, fuzzy, c-format
+#, c-format
 msgid "unable to re-add worktree '%s'"
-msgstr "konnte \"Tree\"-Objekt (%s) nicht lesen"
+msgstr "Konnte Arbeitsverzeichnis '%s' nicht neu hinzufügen."
 
 #: builtin/worktree.c:256
 #, c-format
 msgid ""
 "'%s' is a missing but locked worktree;\n"
 "use 'add -f -f' to override, or 'unlock' and 'prune' or 'remove' to clear"
 msgstr ""
+"'%s' ist ein fehlendes, aber gesperrtes Arbeitsverzeichnis;\n"
+"Benutzen Sie 'add -f -f' zum Überschrieben, oder 'unlock' und 'prune'\n"
+"oder 'remove' zum Löschen."
 
 #: builtin/worktree.c:258
 #, c-format
 msgid ""
 "'%s' is a missing but already registered worktree;\n"
 "use 'add -f' to override, or 'prune' or 'remove' to clear"
 msgstr ""
+"'%s' ist ein fehlendes, aber bereits registriertes Arbeitsverzeichnis;\n"
+"Benutzen Sie 'add -f' zum Überschreiben, oder 'prune' oder 'remove' zum\n"
+"Löschen."
 
 #: builtin/worktree.c:309
 #, c-format
 msgid "could not create directory of '%s'"
 msgstr "Konnte Verzeichnis '%s' nicht erstellen."
 
 #: builtin/worktree.c:428 builtin/worktree.c:434
 #, c-format
@@ -18702,19 +18628,18 @@ msgstr "'%s' ist nicht gesperrt"
 
 #: builtin/worktree.c:743
 msgid "working trees containing submodules cannot be moved or removed"
 msgstr ""
 "Arbeitsverzeichnisse, die Submodule enthalten, können nicht verschoben oder\n"
 "entfernt werden."
 
 #: builtin/worktree.c:751
-#, fuzzy
 msgid "force move even if worktree is dirty or locked"
-msgstr "Löschen erzwingen, auch wenn das Arbeitsverzeichnis geändert wurde"
+msgstr "Verschieben erzwingen, auch wenn das Arbeitsverzeichnis geändert oder gesperrt ist"
 
 #: builtin/worktree.c:774 builtin/worktree.c:901
 #, c-format
 msgid "'%s' is a main working tree"
 msgstr "'%s' ist ein Hauptarbeitsverzeichnis"
 
 #: builtin/worktree.c:779
 #, c-format
@@ -18722,28 +18647,33 @@ msgid "could not figure out destination name from '%s'"
 msgstr "Konnte Zielname aus '%s' nicht bestimmen."
 
 #: builtin/worktree.c:785
 #, c-format
 msgid "target '%s' already exists"
 msgstr "Ziel '%s' existiert bereits."
 
 #: builtin/worktree.c:793
-#, fuzzy, c-format
+#, c-format
 msgid ""
 "cannot move a locked working tree, lock reason: %s\n"
 "use 'move -f -f' to override or unlock first"
-msgstr "Kann gesperrtes Arbeitsverzeichnis nicht verschieben, Sperrgrund: %s"
+msgstr ""
+"Kann kein gesperrtes Arbeitsverzeichnis verschieben, Sperrgrund: %s\n"
+"Benutzen Sie 'move -f -f' zum Überschreiben oder entsperren Sie zuerst\n"
+"das Arbeitsverzeichnis."
 
 #: builtin/worktree.c:795
-#, fuzzy
 msgid ""
 "cannot move a locked working tree;\n"
 "use 'move -f -f' to override or unlock first"
-msgstr "Kann gesperrtes Arbeitsverzeichnis nicht verschieben, Sperrgrund: %s"
+msgstr ""
+"Kann kein gesperrtes Arbeitsverzeichnis verschieben.\n"
+"Benutzen Sie 'move -f -f' zum Überschreiben oder entsperren Sie zuerst\n"
+"das Arbeitsverzeichnis."
 
 #: builtin/worktree.c:798
 #, c-format
 msgid "validation failed, cannot move working tree: %s"
 msgstr "Validierung fehlgeschlagen, kann Arbeitszeichnis nicht verschieben: %s"
 
 #: builtin/worktree.c:803
 #, c-format
@@ -18761,33 +18691,37 @@ msgid "'%s' is dirty, use --force to delete it"
 msgstr "'%s' ist verändert, benutzen Sie --force zum Löschen"
 
 #: builtin/worktree.c:860
 #, c-format
 msgid "failed to run 'git status' on '%s', code %d"
 msgstr "Fehler beim Ausführen von 'git status' auf '%s'. Code: %d"
 
 #: builtin/worktree.c:883
-#, fuzzy
 msgid "force removal even if worktree is dirty or locked"
-msgstr "Löschen erzwingen, auch wenn das Arbeitsverzeichnis geändert wurde"
+msgstr "Löschen erzwingen, auch wenn das Arbeitsverzeichnis geändert oder gesperrt ist"
 
 #: builtin/worktree.c:906
-#, fuzzy, c-format
+#, c-format
 msgid ""
 "cannot remove a locked working tree, lock reason: %s\n"
 "use 'remove -f -f' to override or unlock first"
-msgstr "Kann gesperrtes Arbeitsverzeichnis nicht löschen, Sperrgrund: %s"
+msgstr ""
+"Kann kein gesperrtes Arbeitsverzeichnis löschen, Sperrgrund: %s\n"
+"Benutzen Sie 'remove -f -f' zum Überschreiben oder entsperren Sie zuerst\n"
+"das Arbeitsverzeichnis."
 
 #: builtin/worktree.c:908
-#, fuzzy
 msgid ""
 "cannot remove a locked working tree;\n"
 "use 'remove -f -f' to override or unlock first"
-msgstr "Kann gesperrtes Arbeitsverzeichnis nicht löschen, Sperrgrund: %s"
+msgstr ""
+"Kann kein gesperrtes Arbeitsverzeichnis löschen.\n"
+"Benutzen Sie 'remove -f -f' zum Überschreiben oder entsperren Sie zuerst\n"
+"das Arbeitsverzeichnis."
 
 #: builtin/worktree.c:911
 #, c-format
 msgid "validation failed, cannot remove working tree: %s"
 msgstr "Validierung fehlgeschlagen, kann Arbeitsverzeichnis nicht löschen: %s"
 
 #: builtin/write-tree.c:14
 msgid "git write-tree [--missing-ok] [--prefix=<prefix>/]"
@@ -18821,24 +18755,23 @@ msgstr ""
 "\n"
 "auszuführen."
 
 #: credential-cache--daemon.c:271
 msgid "print debugging messages to stderr"
 msgstr "Meldungen zur Fehlersuche in Standard-Fehlerausgabe ausgeben"
 
 #: t/helper/test-reach.c:152
-#, fuzzy, c-format
+#, c-format
 msgid "commit %s is not marked reachable"
-msgstr "Commit %s hat keinen Eltern-Commit %d"
+msgstr "Commit %s ist nicht als erreichbar gekennzeichnet."
 
 #: t/helper/test-reach.c:162
-#, fuzzy
 msgid "too many commits marked reachable"
-msgstr "Zu viele Commits zum Schreiben des Graphen."
+msgstr "Zu viele Commits als erreichbar gekennzeichnet."
 
 #: git.c:27
 msgid ""
 "git [--version] [--help] [-C <path>] [-c <name>=<value>]\n"
 "           [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]\n"
 "           [-p | --paginate | -P | --no-pager] [--no-replace-objects] [--"
 "bare]\n"
 "           [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]\n"
@@ -18895,17 +18828,17 @@ msgstr "Kein Verzeichnis für -C angegeben.\n"
 #: git.c:300
 #, c-format
 msgid "unknown option: %s\n"
 msgstr "Unbekannte Option: %s\n"
 
 #: git.c:719
 #, c-format
 msgid "alias loop detected: expansion of '%s' does not terminate:%s"
-msgstr ""
+msgstr "Alias-Schleife erkannt: Erweiterung von '%s' schließt nicht ab:%s"
 
 #: git.c:802
 #, c-format
 msgid "expansion of alias '%s' failed; '%s' is not a git command\n"
 msgstr "Erweiterung von Alias '%s' fehlgeschlagen; '%s' ist kein Git-Befehl.\n"
 
 #: git.c:814
 #, c-format
@@ -18923,43 +18856,37 @@ msgstr "Kontrolle über Delegation wird mit cURL < 7.22.0 nicht unterstützt"
 
 #: http.c:404
 msgid "Public key pinning not supported with cURL < 7.44.0"
 msgstr ""
 "Das Anheften des öffentlichen Schlüssels wird mit cURL < 7.44.0\n"
 "nicht unterstützt."
 
 #: http.c:837
-#, fuzzy
 msgid "CURLSSLOPT_NO_REVOKE not suported with cURL < 7.44.0"
-msgstr ""
-"Das Anheften des öffentlichen Schlüssels wird mit cURL < 7.44.0\n"
-"nicht unterstützt."
+msgstr "CURLSSLOPT_NO_REVOKE wird mit cURL < 7.44.0 nicht unterstützt."
 
 #: http.c:910
-#, fuzzy
 msgid "Protocol restrictions not supported with cURL < 7.19.4"
-msgstr ""
-"Das Anheften des öffentlichen Schlüssels wird mit cURL < 7.44.0\n"
-"nicht unterstützt."
+msgstr "Protokollbeschränkungen werden mit cURL < 7.19.4 nicht unterstützt."
 
 #: http.c:1046
 #, c-format
 msgid "Unsupported SSL backend '%s'. Supported SSL backends:"
-msgstr ""
+msgstr "Nicht unterstütztes SSL-Backend '%s'. Unterstützte SSL-Backends:"
 
 #: http.c:1053
 #, c-format
 msgid "Could not set SSL backend to '%s': cURL was built without SSL backends"
-msgstr ""
+msgstr "Konnte SSL-Backend nicht zu '%s' setzen: cURL wurde ohne SSL-Backends gebaut."
 
 #: http.c:1057
-#, fuzzy, c-format
+#, c-format
 msgid "Could not set SSL backend to '%s': already set"
-msgstr "Konnte nicht zu $head_name zurückgehen"
+msgstr "Konnte SSL-Backend nicht zu '%s' setzen: bereits gesetzt"
 
 #: http.c:1921
 #, c-format
 msgid ""
 "unable to update url base from redirection:\n"
 "  asked for: %s\n"
 "   redirect: %s"
 msgstr ""
@@ -19012,19 +18939,18 @@ msgstr "eine Serie von Patches von einer Mailbox anwenden"
 msgid "Annotate file lines with commit information"
 msgstr "Zeilen der Datei mit Commit-Informationen versehen und anzeigen"
 
 #: command-list.h:53
 msgid "Apply a patch to files and/or to the index"
 msgstr "einen Patch auf Dateien und/oder den Index anwenden"
 
 #: command-list.h:54
-#, fuzzy
 msgid "Import a GNU Arch repository into Git"
-msgstr "ein Arch Repository in Git importieren"
+msgstr "ein GNU Arch Repository in Git importieren"
 
 #: command-list.h:55
 msgid "Create an archive of files from a named tree"
 msgstr "Dateiarchiv von angegebenem Verzeichnis erstellen"
 
 #: command-list.h:56
 msgid "Use binary search to find the commit that introduced a bug"
 msgstr ""
@@ -19100,19 +19026,18 @@ msgstr "ein Repository in einem neuen Verzeichnis klonen"
 msgid "Display data in columns"
 msgstr "Daten in Spalten anzeigen"
 
 #: command-list.h:73
 msgid "Record changes to the repository"
 msgstr "Änderungen in das Repository eintragen"
 
 #: command-list.h:74
-#, fuzzy
 msgid "Write and verify Git commit-graph files"
-msgstr "Git Commit Graph-Dateien schreiben und überprüfen"
+msgstr "Git Commit-Graph-Dateien schreiben und überprüfen"
 
 #: command-list.h:75
 msgid "Create a new commit object"
 msgstr "ein neues Commit-Objekt erstellen"
 
 #: command-list.h:76
 msgid "Get and set repository or global options"
 msgstr "repositoryweite oder globale Optionen lesen oder setzen"
@@ -19332,19 +19257,18 @@ msgid "Run merge conflict resolution tools to resolve merge conflicts"
 msgstr ""
 "Ausführen von Tools zur Auflösung von Merge-Konflikten zur Behebung dieser"
 
 #: command-list.h:127
 msgid "Show three-way merge without touching index"
 msgstr "3-Wege-Merge anzeigen ohne den Index zu verändern"
 
 #: command-list.h:128
-#, fuzzy
 msgid "Write and verify multi-pack-indexes"
-msgstr "Git Commit Graph-Dateien schreiben und überprüfen"
+msgstr "multi-pack-indexes schreiben und überprüfen"
 
 #: command-list.h:129
 msgid "Creates a tag object"
 msgstr "ein Tag-Objekt erstellen"
 
 #: command-list.h:130
 msgid "Build a tree-object from ls-tree formatted text"
 msgstr "Tree-Objekt aus ls-tree formattiertem Text erzeugen"
@@ -21338,18 +21262,17 @@ msgstr "Unbekanntes --suppress-cc Feld: '%s'\n"
 #: git-send-email.perl:497
 #, perl-format
 msgid "Unknown --confirm setting: '%s'\n"
 msgstr "Unbekannte --confirm Einstellung: '%s'\n"
 
 #: git-send-email.perl:525
 #, perl-format
 msgid "warning: sendmail alias with quotes is not supported: %s\n"
-msgstr ""
-"Warnung: sendemail Alias mit Anführungsstrichen wird nicht unterstützt: %s\n"
+msgstr "Warnung: sendemail-Alias mit Anführungszeichen wird nicht unterstützt: %s\n"
 
 #: git-send-email.perl:527
 #, perl-format
 msgid "warning: `:include:` not supported: %s\n"
 msgstr "Warnung: `:include:` wird nicht unterstützt: %s\n"
 
 #: git-send-email.perl:529
 #, perl-format
-- 
2.20.0.rc1.379.g1dd7ef354c


^ permalink raw reply related	[relevance 2%]

* [PATCH v1 1/1] t5601-99: Enable colliding file detection for MINGW
  2018-11-20 16:28  4% ` [PATCH] clone: fix colliding file detection on APFS Nguyễn Thái Ngọc Duy
  2018-11-20 19:20  0%   ` Ramsay Jones
@ 2018-11-22 17:59  5%   ` tboegi
  1 sibling, 0 replies; 200+ results
From: tboegi @ 2018-11-22 17:59 UTC (permalink / raw)
  To: git
  Cc: carenas, git, newren, pawelparuzel95, pclouds, peff, sandals,
	szeder.dev, tboegi, ramsay

From: Torsten Bögershausen <tboegi@web.de>

Commit b878579ae7 (clone: report duplicate entries on case-insensitive
filesystems - 2018-08-17) adds a warning to user when cloning a repo
with case-sensitive file names on a case-insensitive file system.

This test has never been enabled for MINGW.
It had been working since day 1, but I forget to report that to the
author.
Enable it after a re-test.

Signed-off-by: Torsten Bögershausen <tboegi@web.de>
---

The other day, I wanted to test Duys patch -
under MINGW - to see if the problem is catch(ed)
but hehe git am failed to apply - not a big desaster,
because is is already in master
Here is a follow-up, end we can end the match


 t/t5601-clone.sh | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh
index c28d51bd59..8bbc7068ac 100755
--- a/t/t5601-clone.sh
+++ b/t/t5601-clone.sh
@@ -628,7 +628,7 @@ test_expect_success 'clone on case-insensitive fs' '
 	)
 '
 
-test_expect_success !MINGW,CASE_INSENSITIVE_FS 'colliding file detection' '
+test_expect_success CASE_INSENSITIVE_FS 'colliding file detection' '
 	grep X icasefs/warning &&
 	grep x icasefs/warning &&
 	test_i18ngrep "the following paths have collided" icasefs/warning
-- 
2.19.0.271.gfe8321ec05


^ permalink raw reply related	[relevance 5%]

* Re: [PATCH] clone: fix colliding file detection on APFS
  2018-11-20 16:28  4% ` [PATCH] clone: fix colliding file detection on APFS Nguyễn Thái Ngọc Duy
@ 2018-11-20 19:20  0%   ` Ramsay Jones
  2018-11-22 17:59  5%   ` [PATCH v1 1/1] t5601-99: Enable colliding file detection for MINGW tboegi
  1 sibling, 0 replies; 200+ results
From: Ramsay Jones @ 2018-11-20 19:20 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy, gitster
  Cc: carenas, git, git, newren, pawelparuzel95, peff, sandals,
	szeder.dev, tboegi



On 20/11/2018 16:28, Nguyễn Thái Ngọc Duy wrote:
> Commit b878579ae7 (clone: report duplicate entries on case-insensitive
> filesystems - 2018-08-17) adds a warning to user when cloning a repo
> with case-sensitive file names on a case-insensitive file system. The
> "find duplicate file" check was doing by comparing inode number (and
> only fall back to fspathcmp() when inode is known to be unreliable
> because fspathcmp() can't cover all case folding cases).
> 
> The inode check is very simple, and wrong. It compares between a
> 32-bit number (sd_ino) and potentially a 64-bit number (st_ino). When
> an inode is larger than 2^32 (which seems to be the case for APFS), it
> will be truncated and stored in sd_ino, but comparing with itself will
> fail.
> 
> As a result, instead of showing a pair of files that have the same
> name, we show just one file (marked before the beginning of the
> loop). We fail to find the original one.
> 
> The fix could be just a simple type cast (*)
> 
>     dup->ce_stat_data.sd_ino == (unsigned int)st->st_ino
> 
> but this is no longer a reliable test, there are 4G possible inodes
> that can match sd_ino because we only match the lower 32 bits instead
> of full 64 bits.
> 
> There are two options to go. Either we ignore inode and go with
> fspathcmp() on Apple platform. This means we can't do accurate inode
> check on HFS anymore, or even on APFS when inode numbers are still
> below 2^32.
> 
> Or we just to to reduce the odds of matching a wrong file by checking
> more attributes, counting mostly on st_size because st_xtime is likely
> the same. This patch goes with this direction, hoping that false
> positive chances are too small to be seen in practice.
> 
> While at there, enable the test on Cygwin (verified working by Ramsay
> Jones)

Well, no, I tested the previous version of this patch. However, this
patch also passes the test. (Note _test_ singular - in order to check
that this patch doesn't cause a regression I would need to run the
whole test-suite - that takes 3.5 hours, if I'm not doing anything
else!)

> 
> (*) this is also already done inside match_stat_data()
> 
> Reported-by: Carlo Arenas <carenas@gmail.com>
> Helped-by: Ramsay Jones <ramsay@ramsayjones.plus.com>
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---
>  So I'm going with match_stat_data(). But I don't know, perhaps just
>  ignoring inode (like Carlo's original patch) is safer/better?
> 
>  Tested on case-insensitive JFS on Linux. But I don't think it really
>  matters because I'm not even sure if I could push inode above 2^32
>  with this. Hacking JFS for this test sounds fun, but no time for that.
> 
>  entry.c          | 4 ++--
>  t/t5601-clone.sh | 2 +-
>  2 files changed, 3 insertions(+), 3 deletions(-)
> 
> diff --git a/entry.c b/entry.c
> index 5d136c5d55..0a3c451f5f 100644
> --- a/entry.c
> +++ b/entry.c
> @@ -404,7 +404,7 @@ static void mark_colliding_entries(const struct checkout *state,
>  {
>  	int i, trust_ino = check_stat;
>  
> -#if defined(GIT_WINDOWS_NATIVE)
> +#if defined(GIT_WINDOWS_NATIVE) || defined(__CYGWIN__)

I was a little curious about this (but couldn't be bothered actually
read the code, post-application), so I removed this hunk from the
patch, rebuilt and ran the test again: it _passed_ the test. :-D

So, ...

ATB,
Ramsay Jones

>  	trust_ino = 0;
>  #endif
>  
> @@ -419,7 +419,7 @@ static void mark_colliding_entries(const struct checkout *state,
>  		if (dup->ce_flags & (CE_MATCHED | CE_VALID | CE_SKIP_WORKTREE))
>  			continue;
>  
> -		if ((trust_ino && dup->ce_stat_data.sd_ino == st->st_ino) ||
> +		if ((trust_ino && !match_stat_data(&dup->ce_stat_data, st)) ||
>  		    (!trust_ino && !fspathcmp(ce->name, dup->name))) {
>  			dup->ce_flags |= CE_MATCHED;
>  			break;
> diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh
> index f1a49e94f5..c28d51bd59 100755
> --- a/t/t5601-clone.sh
> +++ b/t/t5601-clone.sh
> @@ -628,7 +628,7 @@ test_expect_success 'clone on case-insensitive fs' '
>  	)
>  '
>  
> -test_expect_success !MINGW,!CYGWIN,CASE_INSENSITIVE_FS 'colliding file detection' '
> +test_expect_success !MINGW,CASE_INSENSITIVE_FS 'colliding file detection' '
>  	grep X icasefs/warning &&
>  	grep x icasefs/warning &&
>  	test_i18ngrep "the following paths have collided" icasefs/warning
> 

^ permalink raw reply	[relevance 0%]

* [PATCH] clone: fix colliding file detection on APFS
  @ 2018-11-20 16:28  4% ` Nguyễn Thái Ngọc Duy
  2018-11-20 19:20  0%   ` Ramsay Jones
  2018-11-22 17:59  5%   ` [PATCH v1 1/1] t5601-99: Enable colliding file detection for MINGW tboegi
  0 siblings, 2 replies; 200+ results
From: Nguyễn Thái Ngọc Duy @ 2018-11-20 16:28 UTC (permalink / raw)
  To: gitster
  Cc: carenas, git, git, newren, pawelparuzel95, pclouds, peff, sandals,
	szeder.dev, tboegi, Ramsay Jones

Commit b878579ae7 (clone: report duplicate entries on case-insensitive
filesystems - 2018-08-17) adds a warning to user when cloning a repo
with case-sensitive file names on a case-insensitive file system. The
"find duplicate file" check was doing by comparing inode number (and
only fall back to fspathcmp() when inode is known to be unreliable
because fspathcmp() can't cover all case folding cases).

The inode check is very simple, and wrong. It compares between a
32-bit number (sd_ino) and potentially a 64-bit number (st_ino). When
an inode is larger than 2^32 (which seems to be the case for APFS), it
will be truncated and stored in sd_ino, but comparing with itself will
fail.

As a result, instead of showing a pair of files that have the same
name, we show just one file (marked before the beginning of the
loop). We fail to find the original one.

The fix could be just a simple type cast (*)

    dup->ce_stat_data.sd_ino == (unsigned int)st->st_ino

but this is no longer a reliable test, there are 4G possible inodes
that can match sd_ino because we only match the lower 32 bits instead
of full 64 bits.

There are two options to go. Either we ignore inode and go with
fspathcmp() on Apple platform. This means we can't do accurate inode
check on HFS anymore, or even on APFS when inode numbers are still
below 2^32.

Or we just to to reduce the odds of matching a wrong file by checking
more attributes, counting mostly on st_size because st_xtime is likely
the same. This patch goes with this direction, hoping that false
positive chances are too small to be seen in practice.

While at there, enable the test on Cygwin (verified working by Ramsay
Jones)

(*) this is also already done inside match_stat_data()

Reported-by: Carlo Arenas <carenas@gmail.com>
Helped-by: Ramsay Jones <ramsay@ramsayjones.plus.com>
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 So I'm going with match_stat_data(). But I don't know, perhaps just
 ignoring inode (like Carlo's original patch) is safer/better?

 Tested on case-insensitive JFS on Linux. But I don't think it really
 matters because I'm not even sure if I could push inode above 2^32
 with this. Hacking JFS for this test sounds fun, but no time for that.

 entry.c          | 4 ++--
 t/t5601-clone.sh | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/entry.c b/entry.c
index 5d136c5d55..0a3c451f5f 100644
--- a/entry.c
+++ b/entry.c
@@ -404,7 +404,7 @@ static void mark_colliding_entries(const struct checkout *state,
 {
 	int i, trust_ino = check_stat;
 
-#if defined(GIT_WINDOWS_NATIVE)
+#if defined(GIT_WINDOWS_NATIVE) || defined(__CYGWIN__)
 	trust_ino = 0;
 #endif
 
@@ -419,7 +419,7 @@ static void mark_colliding_entries(const struct checkout *state,
 		if (dup->ce_flags & (CE_MATCHED | CE_VALID | CE_SKIP_WORKTREE))
 			continue;
 
-		if ((trust_ino && dup->ce_stat_data.sd_ino == st->st_ino) ||
+		if ((trust_ino && !match_stat_data(&dup->ce_stat_data, st)) ||
 		    (!trust_ino && !fspathcmp(ce->name, dup->name))) {
 			dup->ce_flags |= CE_MATCHED;
 			break;
diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh
index f1a49e94f5..c28d51bd59 100755
--- a/t/t5601-clone.sh
+++ b/t/t5601-clone.sh
@@ -628,7 +628,7 @@ test_expect_success 'clone on case-insensitive fs' '
 	)
 '
 
-test_expect_success !MINGW,!CYGWIN,CASE_INSENSITIVE_FS 'colliding file detection' '
+test_expect_success !MINGW,CASE_INSENSITIVE_FS 'colliding file detection' '
 	grep X icasefs/warning &&
 	grep x icasefs/warning &&
 	test_i18ngrep "the following paths have collided" icasefs/warning
-- 
2.19.1.1327.g328c130451.dirty


^ permalink raw reply related	[relevance 4%]

* Re: [PATCH v5] clone: report duplicate entries on case-insensitive filesystems
  2018-11-19 23:29  4%                     ` Ramsay Jones
@ 2018-11-19 23:54  0%                       ` Ramsay Jones
  0 siblings, 0 replies; 200+ results
From: Ramsay Jones @ 2018-11-19 23:54 UTC (permalink / raw)
  To: Duy Nguyen, Carlo Arenas
  Cc: Torsten Bögershausen, Git Mailing List, Jeff Hostetler,
	Junio C Hamano, Elijah Newren, Paweł Paruzel, Jeff King,
	brian m. carlson, SZEDER Gábor



On 19/11/2018 23:29, Ramsay Jones wrote:
> 
> 
> On 19/11/2018 21:03, Duy Nguyen wrote:
>> First of all, Ramsay, it would be great if you could test the below
>> patch and see if it works on Cygwin. I assume since Cygwin shares the
>> underlying filesystem, it will share the same "no trusting inode"
>> issue with native builds (or it calculates inodes anyway using some
>> other source?).
> 
> Hmm, I have no idea why you would like me to try this patch - care
> to explain? [I just saw, "Has this been tested on cygwin?" and, since
> it has been happily passing for some time, responded yes!]
> 
> Just for the giggles, I removed the !CYGWIN prerequisite from the
> test and when, as expected, the test failed, had a look around:
> 
> $ pwd
> /home/ramsay/git/t/trash directory.t5601-clone
> $ cat icasefs/warning 
> Cloning into 'bogus'...
> done.
> warning: the following paths have collided (e.g. case-sensitive paths
> on a case-insensitive filesystem) and only one from the same
> colliding group is in the working tree:
> 
>   'x'
> $ cd icasefs/bogus
> $ ls -l
> total 0
> -rw-r--r-- 1 ramsay None 0 Nov 19 22:40 x
> $ git ls-files --debug
> ignoring EOIE extension
> X
>   ctime: 1542667201:664036600
>   mtime: 1542667201:663055400
>   dev: 2378432	ino: 324352
>   uid: 1001	gid: 513
>   size: 0	flags: 0
> x
>   ctime: 1542667201:665026800
>   mtime: 1542667201:665026800
>   dev: 2378432	ino: 324352
>   uid: 1001	gid: 513
>   size: 0	flags: 0
> $ 
> 
> So, both X and x are in the index with the same inode number.
> 
> Does that help?

Well, I haven't even looked at the patch, but when I apply it to
the current 'pu' branch (just what I happened to have checked out)
and run that one test:

$ ./t5601-clone.sh
...
ok 96 - shallow clone locally
ok 97 - GIT_TRACE_PACKFILE produces a usable pack
ok 98 - clone on case-insensitive fs
ok 99 - colliding file detection
ok 100 - partial clone
ok 101 - partial clone: warn if server does not support object filtering
ok 102 - batch missing blob request during checkout
ok 103 - batch missing blob request does not inadvertently try to fetch gitlinks
# passed all 103 test(s)
# SKIP no web server found at '/usr/sbin/apache2'
1..103
$ 

... the colliding file detection test passes!

ATB,
Ramsay Jones



^ permalink raw reply	[relevance 0%]

* Re: [PATCH v5] clone: report duplicate entries on case-insensitive filesystems
  @ 2018-11-19 23:29  4%                     ` Ramsay Jones
  2018-11-19 23:54  0%                       ` Ramsay Jones
  0 siblings, 1 reply; 200+ results
From: Ramsay Jones @ 2018-11-19 23:29 UTC (permalink / raw)
  To: Duy Nguyen, Carlo Arenas
  Cc: Torsten Bögershausen, Git Mailing List, Jeff Hostetler,
	Junio C Hamano, Elijah Newren, Paweł Paruzel, Jeff King,
	brian m. carlson, SZEDER Gábor



On 19/11/2018 21:03, Duy Nguyen wrote:
> First of all, Ramsay, it would be great if you could test the below
> patch and see if it works on Cygwin. I assume since Cygwin shares the
> underlying filesystem, it will share the same "no trusting inode"
> issue with native builds (or it calculates inodes anyway using some
> other source?).

Hmm, I have no idea why you would like me to try this patch - care
to explain? [I just saw, "Has this been tested on cygwin?" and, since
it has been happily passing for some time, responded yes!]

Just for the giggles, I removed the !CYGWIN prerequisite from the
test and when, as expected, the test failed, had a look around:

$ pwd
/home/ramsay/git/t/trash directory.t5601-clone
$ cat icasefs/warning 
Cloning into 'bogus'...
done.
warning: the following paths have collided (e.g. case-sensitive paths
on a case-insensitive filesystem) and only one from the same
colliding group is in the working tree:

  'x'
$ cd icasefs/bogus
$ ls -l
total 0
-rw-r--r-- 1 ramsay None 0 Nov 19 22:40 x
$ git ls-files --debug
ignoring EOIE extension
X
  ctime: 1542667201:664036600
  mtime: 1542667201:663055400
  dev: 2378432	ino: 324352
  uid: 1001	gid: 513
  size: 0	flags: 0
x
  ctime: 1542667201:665026800
  mtime: 1542667201:665026800
  dev: 2378432	ino: 324352
  uid: 1001	gid: 513
  size: 0	flags: 0
$ 

So, both X and x are in the index with the same inode number.

Does that help?

ATB,
Ramsay Jones

^ permalink raw reply	[relevance 4%]

* Re: [PATCH v5] clone: report duplicate entries on case-insensitive filesystems
  2018-11-19 17:14  5%               ` Carlo Arenas
@ 2018-11-19 18:24  0%                 ` Duy Nguyen
    0 siblings, 1 reply; 200+ results
From: Duy Nguyen @ 2018-11-19 18:24 UTC (permalink / raw)
  To: Carlo Arenas
  Cc: Torsten Bögershausen, Git Mailing List, Jeff Hostetler,
	Junio C Hamano, Elijah Newren, Paweł Paruzel, Jeff King,
	brian m. carlson, SZEDER Gábor

On Mon, Nov 19, 2018 at 6:14 PM Carlo Arenas <carenas@gmail.com> wrote:
>
> On Mon, Nov 19, 2018 at 4:28 AM Torsten Bögershausen <tboegi@web.de> wrote:
> >
> > Did you test it on Mac ?
>
> macOS 10.14.1 but only using APFS, did you test my patch with HFS+?
>
> > So what exactly are you trying to fix ?
>
> I get
>
> not ok 99 - colliding file detection
> #
> # grep X icasefs/warning &&
> # grep x icasefs/warning &&
> # test_i18ngrep "the following paths have collided" icasefs/warning
> #
>
> and the output of "warning" only shows one of the conflicting files,
> instead of both:
>
> Cloning into 'bogus'...
> done.
> warning: the following paths have collided (e.g. case-sensitive paths
> on a case-insensitive filesystem) and only one from the same
> colliding group is in the working tree:
>
>   'x'
>
> Carlo

Could you send me the "index" file in  t/trash\
directory.t5601-clone/icasefs/bogus/.git/index ? Also the output of
"stat /path/to/icase/bogus/x"

My only explanation is somehow the inode value we save is not the same
one on disk, which is weird and could even cause other problems. I'd
like to know why this happens before trying to fix anything.
-- 
Duy

^ permalink raw reply	[relevance 0%]

* Re: [PATCH v5] clone: report duplicate entries on case-insensitive filesystems
  2018-11-19 12:28  5%             ` Torsten Bögershausen
  2018-11-19 17:14  5%               ` Carlo Arenas
@ 2018-11-19 17:21  0%               ` Ramsay Jones
  1 sibling, 0 replies; 200+ results
From: Ramsay Jones @ 2018-11-19 17:21 UTC (permalink / raw)
  To: Torsten Bögershausen, Carlo Marcelo Arenas Belón, git
  Cc: pclouds, git, gitster, newren, pawelparuzel95, peff, sandals,
	szeder.dev



On 19/11/2018 12:28, Torsten Bögershausen wrote:
> On 2018-11-19 09:20, Carlo Marcelo Arenas Belón wrote:
>> While I don't have an HFS+ volume to test, I suspect this patch should be
>> needed for both, even if I have to say thay even the broken output was
>> better than the current state.
>>
>> Travis seems to be using a case sensitive filesystem so wouldn't catch this.
>>
>> Was windows/cygwin tested?
>>
>> Carlo
>> -- >8 --
>> Subject: [PATCH] entry: fix t5061 on macOS
>>
>> b878579ae7 ("clone: report duplicate entries on case-insensitive filesystems",
>> 2018-08-17) was tested on Linux with an excemption for Windows that needs
>> to be expanded for macOS (using APFS), which then would show :
>>
>> $ git clone git://git.kernel.org/pub/scm/docs/man-pages/man-pages.git
>> warning: the following paths have collided (e.g. case-sensitive paths
>> on a case-insensitive filesystem) and only one from the same
>> colliding group is in the working tree:
>>
>>   'man2/_Exit.2'
>>   'man2/_exit.2'
>>   'man3/NAN.3'
>>   'man3/nan.3'
>>
>> Signed-off-by: Carlo Marcelo Arenas Belón <carenas@gmail.com>
>> ---
>>  entry.c | 2 +-
>>  1 file changed, 1 insertion(+), 1 deletion(-)
>>
>> diff --git a/entry.c b/entry.c
>> index 5d136c5d55..3845f570f7 100644
>> --- a/entry.c
>> +++ b/entry.c
>> @@ -404,7 +404,7 @@ static void mark_colliding_entries(const struct checkout *state,
>>  {
>>  	int i, trust_ino = check_stat;
>>  
>> -#if defined(GIT_WINDOWS_NATIVE)
>> +#if defined(GIT_WINDOWS_NATIVE) || defined(__APPLE__)
>>  	trust_ino = 0;
>>  #endif
>>  
>>
> 
> Sorry,
> but I can't reproduce your problem here.
> 
> Did you test it on Mac ?
> If I run t5601 on a case sensitive files system
> (Mac, mounted NFS, exported from Linux)
> I get:
> ok 99 # skip colliding file detection (missing CASE_INSENSITIVE_FS of
> !MINGW,!CYGWIN,CASE_INSENSITIVE_FS)

I tested v2.20.0-rc0 on cygwin last night and it passed just fine.
I just ran t5601-clone.sh on its own and got:

    $ ./t5601-clone.sh
    ...
    ok 98 - clone on case-insensitive fs
    ok 99 # skip colliding file detection (missing !CYGWIN of !MINGW,!CYGWIN,CASE_INSENSITIVE_FS)
    ok 100 - partial clone
    ok 101 - partial clone: warn if server does not support object filtering
    ok 102 - batch missing blob request during checkout
    ok 103 - batch missing blob request does not inadvertently try to fetch gitlinks
    # passed all 103 test(s)
    # SKIP no web server found at '/usr/sbin/apache2'
    1..103
    $ 

ATB,
Ramsay Jones

^ permalink raw reply	[relevance 0%]

* Re: [PATCH v5] clone: report duplicate entries on case-insensitive filesystems
  2018-11-19 12:28  5%             ` Torsten Bögershausen
@ 2018-11-19 17:14  5%               ` Carlo Arenas
  2018-11-19 18:24  0%                 ` Duy Nguyen
  2018-11-19 17:21  0%               ` Ramsay Jones
  1 sibling, 1 reply; 200+ results
From: Carlo Arenas @ 2018-11-19 17:14 UTC (permalink / raw)
  To: tboegi
  Cc: git, pclouds, git, gitster, newren, pawelparuzel95, peff, sandals,
	szeder.dev

On Mon, Nov 19, 2018 at 4:28 AM Torsten Bögershausen <tboegi@web.de> wrote:
>
> Did you test it on Mac ?

macOS 10.14.1 but only using APFS, did you test my patch with HFS+?

> So what exactly are you trying to fix ?

I get

not ok 99 - colliding file detection
#
# grep X icasefs/warning &&
# grep x icasefs/warning &&
# test_i18ngrep "the following paths have collided" icasefs/warning
#

and the output of "warning" only shows one of the conflicting files,
instead of both:

Cloning into 'bogus'...
done.
warning: the following paths have collided (e.g. case-sensitive paths
on a case-insensitive filesystem) and only one from the same
colliding group is in the working tree:

  'x'

Carlo

^ permalink raw reply	[relevance 5%]

* Re: Git Test Coverage Report (v2.20.0-rc0)
  2018-11-19  2:54  1% Git Test Coverage Report (v2.20.0-rc0) Derrick Stolee
@ 2018-11-19 15:40  3% ` Derrick Stolee
  0 siblings, 0 replies; 200+ results
From: Derrick Stolee @ 2018-11-19 15:40 UTC (permalink / raw)
  To: git@vger.kernel.org, Jeff King, Stefan Beller,
	Nguyễn Thái Ngọc Duy, Ben Peart

The test coverage reports started mid-way through this release cycle, so 
I thought it would be good to do a full review of the new uncovered code 
since the last release.

I eliminated most of the uncovered code due to the following cases:

1. Code was only moved or refactored.
2. Code was related to unusual error conditions (e.g. open_pack_index() 
fails)

The comments below are intended only to point out potential directions 
to improve test coverage. Some of it is for me to do!

Thanks,
-Stolee

On 11/18/2018 9:54 PM, Derrick Stolee wrote:
> 66ec0390e7 builtin/fsck.c 888) midx_argv[2] = "--object-dir";
> 66ec0390e7 builtin/fsck.c 889) midx_argv[3] = alt->path;
> 66ec0390e7 builtin/fsck.c 890) if (run_command(&midx_verify))
> 66ec0390e7 builtin/fsck.c 891) errors_found |= ERROR_COMMIT_GRAPH;
>

There are two things wrong here:

1. Not properly covering multi-pack-index fsck with alternates.
2. the ERROR_COMMIT_GRAPH flag when the multi-pack-index is being checked.

I'll submit a patch to fix this.

> 2fa233a554 builtin/pack-objects.c 1512) hashcpy(base_oid.hash, 
> base_sha1);
> 2fa233a554 builtin/pack-objects.c 1513) if 
> (!in_same_island(&delta->idx.oid, &base_oid))
> 2fa233a554 builtin/pack-objects.c 1514) return 0;

These lines are inside a block for the following if statement:

+       /*
+        * Otherwise, reachability bitmaps may tell us if the receiver 
has it,
+        * even if it was buried too deep in history to make it into the
+        * packing list.
+        */
+       if (thin && bitmap_has_sha1_in_uninteresting(bitmap_git, 
base_sha1)) {

Peff: is this difficult to test?

> 28b8a73080 builtin/pack-objects.c 2793) depth++;
> 108f530385 builtin/pack-objects.c 2797) oe_set_tree_depth(&to_pack, 
> ent, depth); 

This 'depth' variable is incremented as part of a for loop in this patch:

  static void show_object(struct object *obj, const char *name, void *data)
@@ -2686,6 +2706,19 @@ static void show_object(struct object *obj, const 
char *name, void *data)
         add_preferred_base_object(name);
         add_object_entry(&obj->oid, obj->type, name, 0);
         obj->flags |= OBJECT_ADDED;
+
+       if (use_delta_islands) {
+               const char *p;
+               unsigned depth = 0;
+               struct object_entry *ent;
+
+               for (p = strchr(name, '/'); p; p = strchr(p + 1, '/'))
+                       depth++;
+
+               ent = packlist_find(&to_pack, obj->oid.hash, NULL);
+               if (ent && depth > ent->tree_depth)
+                       ent->tree_depth = depth;
+       }
  }

And that 'ent->tree_depth = depth;' line is replaced with the 
oe_set_tree_depth() call in the report.

Since depth is never incremented, we are not covering this block. Is it 
possible to test?

> builtin/repack.c
> 16d75fa48d  48) use_delta_islands = git_config_bool(var, value);
> 16d75fa48d  49) return 0;

This is a simple config option check for "repack.useDeltaIslands". The 
logic it enables is tested, so this is an OK gap, in my opinion.

 > builtin/submodule--helper.c
> ee69b2a90c 1476) out->type = sub->update_strategy.type;
> ee69b2a90c 1477) out->command = sub->update_strategy.command;

This block was introduced by this part of the patch:

+       } else if (sub->update_strategy.type != SM_UPDATE_UNSPECIFIED) {
+               trace_printf("loaded thing");
+               out->type = sub->update_strategy.type;
+               out->command = sub->update_strategy.command;

Which seems to be an important case, as the other SM_UPDATE_* types seem 
like interesting cases.

Stefan: what actions would trigger this block? Is it easy to test?

> delta-islands.c
> c8d521faf7  53) memcpy(b, old, size);

This memcpy happens when the 'old' island_bitmap is passed to 
island_bitmap_new(), but...

> c8d521faf7 187) b->refcount--;
> c8d521faf7 188) b = kh_value(island_marks, pos) = island_bitmap_new(b);

This block has the only non-NULL caller to island_bitmap_new().

> c8d521faf7 212) obj = ((struct tag *)obj)->tagged;
> c8d521faf7 213) if (obj) {
> c8d521faf7 214) parse_object(the_repository, &obj->oid);
> c8d521faf7 215) marks = create_or_get_island_marks(obj);
> c8d521faf7 216) island_bitmap_set(marks, island_counter);

It appears that this block would happen if we placed a tag in the delta 
island.

> c8d521faf7 397) strbuf_addch(&island_name, '-');

This block is inside the following patch:

+       if (matches[ARRAY_SIZE(matches) - 1].rm_so != -1)
+               warning(_("island regex from config has "
+                         "too many capture groups (max=%d)"),
+                       (int)ARRAY_SIZE(matches) - 2);
+
+       for (m = 1; m < ARRAY_SIZE(matches); m++) {
+               regmatch_t *match = &matches[m];
+
+               if (match->rm_so == -1)
+                       continue;
+
+               if (island_name.len)
+                       strbuf_addch(&island_name, '-');
+
+               strbuf_add(&island_name, refname + match->rm_so, 
match->rm_eo - match->rm_so);
+       }

This likely means that ARRAY_SIZE(matches) is never more than two.


> c8d521faf7 433) continue;
> c8d521faf7 436) list[dst] = list[src];

These blocks are inside the following nested loop in deduplicate_islands():

+       for (ref = 0; ref + 1 < island_count; ref++) {
+               for (src = ref + 1, dst = src; src < island_count; src++) {
+                       if (list[ref]->hash == list[src]->hash)
+                               continue;
+
+                       if (src != dst)
+                               list[dst] = list[src];
+
+                       dst++;
+               }
+               island_count = dst;
+       }

This means that our "deduplication" logic is never actually doing 
anything meaningful.

> entry.c
> b878579ae7 402) static void mark_colliding_entries(const struct 
> checkout *state,

(there is interesting logic in this method, but it is only enabled on 
case-insensitive filesystems. This run was done on a case-sensitive file 
system. Related changes happen in unpack-trees.c.)

> help.c
> 26c7d06783 help.c         500) static int get_alias(const char *var, 
> const char *value, void *data)
> 26c7d06783 help.c         502) struct string_list *list = data;
> 26c7d06783 help.c         504) if (skip_prefix(var, "alias.", &var))
> 26c7d06783 help.c         505) string_list_append(list, var)->util = 
> xstrdup(value);
> 26c7d06783 help.c         507) return 0;
> 26c7d06783 help.c         530) printf("\n%s\n", _("Command aliases"));
> 26c7d06783 help.c         531) ALLOC_ARRAY(aliases, alias_list.nr + 1);
> 26c7d06783 help.c         532) for (i = 0; i < alias_list.nr; i++) {
> 26c7d06783 help.c         533) aliases[i].name = 
> alias_list.items[i].string;
> 26c7d06783 help.c         534) aliases[i].help = 
> alias_list.items[i].util;
> 26c7d06783 help.c         535) aliases[i].category = 1;
> 26c7d06783 help.c         537) aliases[alias_list.nr].name = NULL;
> 26c7d06783 help.c         538) print_command_list(aliases, 1, longest);
> 26c7d06783 help.c         539) free(aliases);

This logic introduces alias help in 'git help -a'. This seems like a 
simple thing for adding a test to ensure that this works now and in the 
future.

>
> http.c
The code in here seems to be logic for Windows-specific SSL backends, so 
is not covered by this report.

> preload-index.c
> ae9af12287  63) struct progress_data *pd = p->progress;
> ae9af12287  65) pthread_mutex_lock(&pd->mutex);
> ae9af12287  66) pd->n += last_nr - nr;
> ae9af12287  67) display_progress(pd->progress, pd->n);
> ae9af12287  68) pthread_mutex_unlock(&pd->mutex);
> ae9af12287  69) last_nr = nr;
> ae9af12287  83) struct progress_data *pd = p->progress;
> ae9af12287  85) pthread_mutex_lock(&pd->mutex);
> ae9af12287  86) display_progress(pd->progress, pd->n + last_nr);
> ae9af12287  87) pthread_mutex_unlock(&pd->mutex);
> ae9af12287 118) pd.progress = start_delayed_progress(_("Refreshing 
> index"), index->cache_nr);
> ae9af12287 119) pthread_mutex_init(&pd.mutex, NULL);
> ae9af12287 132) p->progress = &pd;

There's a lot of stuff going on with showing progress on index writes. 
While the commit message states the progress doesn't show up for 3 
seconds, perhaps that can be tweaked to be in the millisecond range for 
a test?

> read-cache.c

(There's a lot of progress stuff here, too.)

There are a lot of lines introduced by the IEOT extension in these commits:

 > Ben Peart      3255089ad: ieot: add Index Entry Offset Table (IEOT) 
extension
 > Ben Peart      3b1d9e045: eoie: add End of Index Entry (EOIE) extension
 > Ben Peart      77ff1127a: read-cache: load cache entries on worker 
threads
 > Ben Peart      abb4bb838: read-cache: load cache extensions on a 
worker thread
 > Ben Peart      c780b9cfe: config: add new index.threads config setting
 > Ben Peart      d1664e73a: add: speed up cmd_add() by utilizing 
read_cache_preload()
 > Ben Peart      fa655d841: checkout: optimize "git checkout -b 
<new_branch>"

> revision.c
> b45424181e 2951) c->object.flags |= UNINTERESTING;
> b45424181e 2957) mark_parents_uninteresting(c);

These blocks are currently unreachable because we do not use the new 
topo-order logic when there are UNINTERESTING commits. (This will be 
replaced after we have generation numbers v2.) I could force using this 
logic in a `git log --topo-order A..B` query when GIT_TEST_COMMIT_GRAPH 
is enabled.


^ permalink raw reply	[relevance 3%]

* Re: [PATCH v5] clone: report duplicate entries on case-insensitive filesystems
  2018-11-19  8:20  6%           ` Carlo Marcelo Arenas Belón
@ 2018-11-19 12:28  5%             ` Torsten Bögershausen
  2018-11-19 17:14  5%               ` Carlo Arenas
  2018-11-19 17:21  0%               ` Ramsay Jones
  0 siblings, 2 replies; 200+ results
From: Torsten Bögershausen @ 2018-11-19 12:28 UTC (permalink / raw)
  To: Carlo Marcelo Arenas Belón, git
  Cc: pclouds, git, gitster, newren, pawelparuzel95, peff, sandals,
	szeder.dev

On 2018-11-19 09:20, Carlo Marcelo Arenas Belón wrote:
> While I don't have an HFS+ volume to test, I suspect this patch should be
> needed for both, even if I have to say thay even the broken output was
> better than the current state.
> 
> Travis seems to be using a case sensitive filesystem so wouldn't catch this.
> 
> Was windows/cygwin tested?
> 
> Carlo
> -- >8 --
> Subject: [PATCH] entry: fix t5061 on macOS
> 
> b878579ae7 ("clone: report duplicate entries on case-insensitive filesystems",
> 2018-08-17) was tested on Linux with an excemption for Windows that needs
> to be expanded for macOS (using APFS), which then would show :
> 
> $ git clone git://git.kernel.org/pub/scm/docs/man-pages/man-pages.git
> warning: the following paths have collided (e.g. case-sensitive paths
> on a case-insensitive filesystem) and only one from the same
> colliding group is in the working tree:
> 
>   'man2/_Exit.2'
>   'man2/_exit.2'
>   'man3/NAN.3'
>   'man3/nan.3'
> 
> Signed-off-by: Carlo Marcelo Arenas Belón <carenas@gmail.com>
> ---
>  entry.c | 2 +-
>  1 file changed, 1 insertion(+), 1 deletion(-)
> 
> diff --git a/entry.c b/entry.c
> index 5d136c5d55..3845f570f7 100644
> --- a/entry.c
> +++ b/entry.c
> @@ -404,7 +404,7 @@ static void mark_colliding_entries(const struct checkout *state,
>  {
>  	int i, trust_ino = check_stat;
>  
> -#if defined(GIT_WINDOWS_NATIVE)
> +#if defined(GIT_WINDOWS_NATIVE) || defined(__APPLE__)
>  	trust_ino = 0;
>  #endif
>  
> 

Sorry,
but I can't reproduce your problem here.

Did you test it on Mac ?
If I run t5601 on a case sensitive files system
(Mac, mounted NFS, exported from Linux)
I get:
ok 99 # skip colliding file detection (missing CASE_INSENSITIVE_FS of
!MINGW,!CYGWIN,CASE_INSENSITIVE_FS)

And if I run it on a case-insensitive HFS+,
I get
ok 99 - colliding file detection

So what exactly are you trying to fix ?


^ permalink raw reply	[relevance 5%]

* [PATCH v5] clone: report duplicate entries on case-insensitive filesystems
  2018-08-17 16:16  9%         ` [PATCH v5] " Nguyễn Thái Ngọc Duy
  2018-08-17 17:20  0%           ` Junio C Hamano
  2018-08-17 19:46  0%           ` Torsten Bögershausen
@ 2018-11-19  8:20  6%           ` Carlo Marcelo Arenas Belón
  2018-11-19 12:28  5%             ` Torsten Bögershausen
  2 siblings, 1 reply; 200+ results
From: Carlo Marcelo Arenas Belón @ 2018-11-19  8:20 UTC (permalink / raw)
  To: git
  Cc: pclouds, git, gitster, newren, pawelparuzel95, peff, sandals,
	szeder.dev, tboegi

While I don't have an HFS+ volume to test, I suspect this patch should be
needed for both, even if I have to say thay even the broken output was
better than the current state.

Travis seems to be using a case sensitive filesystem so wouldn't catch this.

Was windows/cygwin tested?

Carlo
-- >8 --
Subject: [PATCH] entry: fix t5061 on macOS

b878579ae7 ("clone: report duplicate entries on case-insensitive filesystems",
2018-08-17) was tested on Linux with an excemption for Windows that needs
to be expanded for macOS (using APFS), which then would show :

$ git clone git://git.kernel.org/pub/scm/docs/man-pages/man-pages.git
warning: the following paths have collided (e.g. case-sensitive paths
on a case-insensitive filesystem) and only one from the same
colliding group is in the working tree:

  'man2/_Exit.2'
  'man2/_exit.2'
  'man3/NAN.3'
  'man3/nan.3'

Signed-off-by: Carlo Marcelo Arenas Belón <carenas@gmail.com>
---
 entry.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/entry.c b/entry.c
index 5d136c5d55..3845f570f7 100644
--- a/entry.c
+++ b/entry.c
@@ -404,7 +404,7 @@ static void mark_colliding_entries(const struct checkout *state,
 {
 	int i, trust_ino = check_stat;
 
-#if defined(GIT_WINDOWS_NATIVE)
+#if defined(GIT_WINDOWS_NATIVE) || defined(__APPLE__)
 	trust_ino = 0;
 #endif
 
-- 
2.20.0.rc0


^ permalink raw reply related	[relevance 6%]

* Git Test Coverage Report (v2.20.0-rc0)
@ 2018-11-19  2:54  1% Derrick Stolee
  2018-11-19 15:40  3% ` Derrick Stolee
  0 siblings, 1 reply; 200+ results
From: Derrick Stolee @ 2018-11-19  2:54 UTC (permalink / raw)
  To: git@vger.kernel.org

Here is a test coverage report for the uncovered lines introduced in 
v2.20.0-rc0 compared to v2.19.1.

Thanks,

-Stolee

[1] https://dev.azure.com/git/git/_build/results?buildId=263&view=logs

---


apply.c
eccb5a5f3d 4071) return get_oid_hex(p->old_oid_prefix, oid);
517fe807d6 4776) BUG_ON_OPT_NEG(unset);
735ca208c5 4830) return -1;

blame.c
a470beea39  113)  !strcmp(r->index->cache[-1 - pos]->name, path))
a470beea39  272) int pos = index_name_pos(r->index, path, len);
a470beea39  274) mode = r->index->cache[pos]->ce_mode;

builtin/add.c
d1664e73ad builtin/add.c 458) die(_("index file corrupt"));

builtin/am.c
2abf350385 1362) repo_init_revisions(the_repository, &rev_info, NULL);
fce5664805 2117) *opt_value = PATCH_FORMAT_UNKNOWN;

builtin/blame.c
517fe807d6 builtin/blame.c    759) BUG_ON_OPT_NEG(unset);

builtin/cat-file.c
98f425b453 builtin/cat-file.c  56) die("unable to stream %s to stdout", 
oid_to_hex(oid));
0eb8d3767c builtin/cat-file.c 609) return error(_("only one batch option 
may be specified"));

builtin/checkout.c
fa655d8411 builtin/checkout.c  539) return 0;
fa655d8411 builtin/checkout.c  953) return error(_("index file corrupt"));

builtin/difftool.c
4a7e27e957 441) if (oideq(&loid, &roid))

builtin/fast-export.c
4a7e27e957 builtin/fast-export.c  387) if (oideq(&ospec->oid, &spec->oid) &&

builtin/fetch.c
builtin/fsck.c
b29759d89a builtin/fsck.c 613) fprintf(stderr, "Checking %s link\n", 
head_ref_name);
b29759d89a builtin/fsck.c 618) return error("Invalid %s", head_ref_name);
454ea2e4d7 builtin/fsck.c 769) for (p = get_all_packs(the_repository); p;
66ec0390e7 builtin/fsck.c 888) midx_argv[2] = "--object-dir";
66ec0390e7 builtin/fsck.c 889) midx_argv[3] = alt->path;
66ec0390e7 builtin/fsck.c 890) if (run_command(&midx_verify))
66ec0390e7 builtin/fsck.c 891) errors_found |= ERROR_COMMIT_GRAPH;

builtin/gc.c
3029970275 builtin/gc.c 461) ret = error_errno(_("cannot stat '%s'"), 
gc_log_path);
3029970275 builtin/gc.c 470) ret = error_errno(_("cannot read '%s'"), 
gc_log_path);
fec2ed2187 builtin/gc.c 495) die(FAILED_RUN, pack_refs_cmd.argv[0]);
fec2ed2187 builtin/gc.c 498) die(FAILED_RUN, reflog.argv[0]);
3029970275 builtin/gc.c 585) exit(128);
fec2ed2187 builtin/gc.c 637) die(FAILED_RUN, repack.argv[0]);
fec2ed2187 builtin/gc.c 647) die(FAILED_RUN, prune.argv[0]);
fec2ed2187 builtin/gc.c 654) die(FAILED_RUN, prune_worktrees.argv[0]);
fec2ed2187 builtin/gc.c 658) die(FAILED_RUN, rerere.argv[0]);

builtin/grep.c
76e9bdc437 builtin/grep.c  424) grep_read_unlock();
fd6263fb73 builtin/grep.c 1051) warning(_("invalid option combination, 
ignoring --threads"));
fd6263fb73 builtin/grep.c 1057) die(_("invalid number of threads 
specified (%d)"), num_threads);

builtin/help.c
e6e76baaf4 builtin/help.c 429) if (!exclude_guides || alias[0] == '!') {
e6e76baaf4 builtin/help.c 430) printf_ln(_("'%s' is aliased to '%s'"), 
cmd, alias);
e6e76baaf4 builtin/help.c 431) free(alias);
e6e76baaf4 builtin/help.c 432) exit(0);
e6e76baaf4 builtin/help.c 441) fprintf_ln(stderr, _("'%s' is aliased to 
'%s'"), cmd, alias);
e6e76baaf4 builtin/help.c 442) count = split_cmdline(alias, &argv);
e6e76baaf4 builtin/help.c 443) if (count < 0)
e6e76baaf4 builtin/help.c 444) die(_("bad alias.%s string: %s"), cmd,
e6e76baaf4 builtin/help.c 446) free(argv);
e6e76baaf4 builtin/help.c 448) return alias;

builtin/log.c
517fe807d6 builtin/log.c 1196) BUG_ON_OPT_NEG(unset);
2e6fd71a52 builtin/log.c 1472) die(_("failed to infer range-diff ranges"));
ee6cbf712e builtin/log.c 1818) die(_("--interdiff requires 
--cover-letter or single patch"));
8631bf1cdd builtin/log.c 1828) else if (!rdiff_prev)
8631bf1cdd builtin/log.c 1829) die(_("--creation-factor requires 
--range-diff"));
40ce41604d builtin/log.c 1833) die(_("--range-diff requires 
--cover-letter or single patch"));

builtin/multi-pack-index.c
6d68e6a461 35) usage_with_options(builtin_multi_pack_index_usage,
6d68e6a461 39) die(_("too many arguments"));
6d68e6a461 48) die(_("unrecognized verb: %s"), argv[0]);

builtin/pack-objects.c
6a22d52126 builtin/pack-objects.c 1091) continue;
2fa233a554 builtin/pack-objects.c 1512) hashcpy(base_oid.hash, base_sha1);
2fa233a554 builtin/pack-objects.c 1513) if 
(!in_same_island(&delta->idx.oid, &base_oid))
2fa233a554 builtin/pack-objects.c 1514) return 0;
28b8a73080 builtin/pack-objects.c 2793) depth++;
108f530385 builtin/pack-objects.c 2797) oe_set_tree_depth(&to_pack, ent, 
depth);
454ea2e4d7 builtin/pack-objects.c 2981) p = get_all_packs(the_repository);

builtin/pack-redundant.c
454ea2e4d7 builtin/pack-redundant.c 580) struct packed_git *p = 
get_all_packs(the_repository);
454ea2e4d7 builtin/pack-redundant.c 595) struct packed_git *p = 
get_all_packs(the_repository);

builtin/pull.c
01a31f3bca 565) die(_("unable to access commit %s"),

builtin/rebase--interactive.c
53bbcfbde7 builtin/rebase--interactive2.c  24) return error(_("no HEAD?"));
53bbcfbde7 builtin/rebase--interactive2.c  51) return 
error_errno(_("could not create temporary %s"), path_state_dir());
53bbcfbde7 builtin/rebase--interactive2.c  57) return 
error_errno(_("could not mark as interactive"));
53bbcfbde7 builtin/rebase--interactive2.c  77) return -1;
53bbcfbde7 builtin/rebase--interactive2.c  81) return -1;
53bbcfbde7 builtin/rebase--interactive2.c  87) free(revisions);
53bbcfbde7 builtin/rebase--interactive2.c  88) free(shortrevisions);
53bbcfbde7 builtin/rebase--interactive2.c  90) return -1;
53bbcfbde7 builtin/rebase--interactive2.c  98) free(revisions);
53bbcfbde7 builtin/rebase--interactive2.c  99) free(shortrevisions);
53bbcfbde7 builtin/rebase--interactive2.c 101) return 
error_errno(_("could not open %s"), rebase_path_todo());
53bbcfbde7 builtin/rebase--interactive2.c 106) 
argv_array_push(&make_script_args, restrict_revision);
53bbcfbde7 builtin/rebase--interactive2.c 114) error(_("could not 
generate todo list"));
53bbcfbde7 builtin/rebase--interactive2.c 206) 
usage_with_options(builtin_rebase_interactive_usage, options);
53bbcfbde7 builtin/rebase--interactive2.c 220) 
warning(_("--[no-]rebase-cousins has no effect without "
0af129b2ed builtin/rebase--interactive2.c 226) die(_("a base commit must 
be provided with --upstream or --onto"));
34b47315d9 builtin/rebase--interactive.c  261) ret = rearrange_squash();
34b47315d9 builtin/rebase--interactive.c  262) break;
34b47315d9 builtin/rebase--interactive.c  264) ret = 
sequencer_add_exec_commands(cmd);
34b47315d9 builtin/rebase--interactive.c  265) break;

builtin/rebase.c
62c23938fa   55) return env;
55071ea248   65) strbuf_trim(&out);
55071ea248   66) ret = !strcmp("true", out.buf);
55071ea248   67) strbuf_release(&out);
002ee2fe68  119) die(_("%s requires an interactive rebase"), option);
f95736288a  152) return error_errno(_("could not read '%s'"), path);
f95736288a  166) return -1;
f95736288a  171) return error(_("could not get 'onto': '%s'"), buf.buf);
f95736288a  182) return -1;
f95736288a  183) } else if (read_one(state_dir_path("head", opts), &buf))
f95736288a  184) return -1;
f95736288a  186) return error(_("invalid orig-head: '%s'"), buf.buf);
f95736288a  190) return -1;
f95736288a  192) opts->flags &= ~REBASE_NO_QUIET;
73d51ed0a5  200) opts->signoff = 1;
73d51ed0a5  201) opts->flags |= REBASE_FORCE;
ead98c111b  208) return -1;
12026a412c  223) return -1;
ba1905a5fe  231) return -1;
ba1905a5fe  239) return -1;
6defce2b02  259) return error(_("Could not read '%s'"), path);
6defce2b02  277) res = error(_("Cannot store %s"), autostash.buf);
6defce2b02  281) return res;
bc24382c2b  379) argv_array_pushf(&child.args,
bc24382c2b  381) oid_to_hex(&opts->restrict_revision->object.oid));
ac7f467fef  515) struct strbuf dir = STRBUF_INIT;
6defce2b02  517) apply_autostash(opts);
ac7f467fef  518) strbuf_addstr(&dir, opts->state_dir);
ac7f467fef  519) remove_dir_recursively(&dir, 0);
ac7f467fef  520) strbuf_release(&dir);
ac7f467fef  521) die("Nothing to do");
3249c1251e  556) ret = -1;
3249c1251e  557) goto leave_reset_head;
bac2a1e36f  561) ret = error(_("could not determine HEAD revision"));
bac2a1e36f  562) goto leave_reset_head;
3249c1251e  580) ret = error(_("could not read index"));
3249c1251e  581) goto leave_reset_head;
bac2a1e36f  585) ret = error(_("failed to find tree of %s"), 
oid_to_hex(oid));
bac2a1e36f  586) goto leave_reset_head;
3249c1251e  590) ret = error(_("failed to find tree of %s"), 
oid_to_hex(oid));
3249c1251e  591) goto leave_reset_head;
ac7f467fef  603) ret = error(_("could not write index"));
3249c1251e  604) goto leave_reset_head;
ac7f467fef  621) } else if (old_orig)
ac7f467fef  622) delete_ref(NULL, "ORIG_HEAD", old_orig, 0);
bff014dac7  655) opts->flags &= !REBASE_DIFFSTAT;
9a48a615b4  689) return 1;
9a48a615b4  705) return 0;
55071ea248  916) const char *path = mkpath("%s/git-legacy-rebase",
55071ea248  919) if (sane_execvp(path, (char **)argv) < 0)
55071ea248  920) die_errno(_("could not exec %s"), path);
0eabf4b95c  938) die(_("It looks like 'git am' is in progress. Cannot 
rebase."));
f28d40d3a9  975) usage_with_options(builtin_rebase_usage,
f95736288a  995) die(_("Cannot read HEAD"));
f95736288a  999) die(_("could not read index"));
f95736288a 1013) exit(1);
122420c295 1026) die(_("could not discard worktree changes"));
122420c295 1029) exit(1);
5e5d96197c 1040) exit(1);
5e5d96197c 1044) die(_("could not move back to %s"),
5a61494539 1055) die(_("could not remove '%s'"), options.state_dir);
c54dacb50e 1074) const char *last_slash = strrchr(options.state_dir, '/');
c54dacb50e 1075) const char *state_dir_base =
c54dacb50e 1076) last_slash ? last_slash + 1 : options.state_dir;
c54dacb50e 1077) const char *cmd_live_rebase =
c54dacb50e 1079) strbuf_reset(&buf);
c54dacb50e 1080) strbuf_addf(&buf, "rm -fr \"%s\"", options.state_dir);
c54dacb50e 1081) die(_("It seems that there is already a %s directory, 
and\n"
3c3588c7d3 1138) else if (strcmp("no-rebase-cousins", rebase_merges))
3c3588c7d3 1139) die(_("Unknown mode: %s"), rebase_merges);
ba1905a5fe 1161) die(_("--strategy requires --merge or --interactive"));
cda614e489 1179) strbuf_addstr(&options.git_format_patch_opt, " 
--progress");
ac7f467fef 1188) options.state_dir = apply_dir();
ac7f467fef 1189) break;
ac7f467fef 1261) die(_("invalid upstream '%s'"), options.upstream_name);
9dba809a69 1267) die(_("Could not create new root commit"));
e65123a71d 1317) die(_("fatal: no such branch/commit '%s'"),
ac7f467fef 1325) die(_("No such ref: %s"), "HEAD");
ac7f467fef 1337) die(_("Could not resolve HEAD to a revision"));
e0333e5c63 1350) die(_("could not read index"));
6defce2b02 1377) die(_("Cannot autostash"));
6defce2b02 1380) die(_("Unexpected stash response: '%s'"),
6defce2b02 1386) die(_("Could not create directory for '%s'"),
6defce2b02 1392) die(_("could not reset --hard"));
e65123a71d 1436) ret = !!error(_("could not parse '%s'"),
e65123a71d 1438) goto cleanup;
e65123a71d 1447) ret = !!error(_("could not switch to "
1ed9c14ff2 1457)  resolve_ref_unsafe("HEAD", 0, NULL, &flag))
1ed9c14ff2 1458) puts(_("HEAD is up to date."));
9a48a615b4 1467)  resolve_ref_unsafe("HEAD", 0, NULL, &flag))
9a48a615b4 1468) puts(_("HEAD is up to date, rebase forced."));

builtin/reflog.c
c9ef0d95eb builtin/reflog.c 580) all_worktrees = 0;
c9ef0d95eb builtin/reflog.c 616) continue;

builtin/remote.c
5025425dff builtin/remote.c  864) return error(_("No such remote: 
'%s'"), name);

builtin/repack.c
16d75fa48d  48) use_delta_islands = git_config_bool(var, value);
16d75fa48d  49) return 0;
2f0c9e9a9b 239) die("repack: Expecting full hex object ID lines only 
from pack-objects.");
2f0c9e9a9b 411) die("repack: Expecting full hex object ID lines only 
from pack-objects.");

builtin/rerere.c
2373b65059 builtin/rerere.c  79) warning(_("'git rerere forget' without 
paths is deprecated"));
2373b65059 builtin/rerere.c 111) die(_("unable to generate diff for 
'%s'"), rerere_path(id, NULL));

builtin/rev-list.c
7c0fe330d5 builtin/rev-list.c 227) die("unexpected missing %s object '%s'",
7c0fe330d5 builtin/rev-list.c 228)     type_name(obj->type), 
oid_to_hex(&obj->oid));

builtin/show-branch.c
9001dc2a74 builtin/show-branch.c 430) if (get_oid(refname + ofs, &tmp) 
|| !oideq(&tmp, oid))
517fe807d6 builtin/show-branch.c 607) BUG_ON_OPT_NEG(unset);

builtin/show-ref.c
517fe807d6 builtin/show-ref.c 154) BUG_ON_OPT_NEG(unset);

builtin/submodule--helper.c
ee69b2a90c 1469) die(_("Invalid update mode '%s' for submodule path '%s'"),
ee69b2a90c 1473) die(_("Invalid update mode '%s' configured for 
submodule path '%s'"),
ee69b2a90c 1476) out->type = sub->update_strategy.type;
ee69b2a90c 1477) out->command = sub->update_strategy.command;
ee69b2a90c 1497) die("submodule--helper update-module-clone expects 
<just-cloned> <path> [<update>]");
e0a862fdaf 1648) url = sub->url;
74d4731da1 2057) die(_("could not get a repository handle for submodule 
'%s'"), path);

builtin/unpack-objects.c
4a7e27e957 builtin/unpack-objects.c 306) if (oideq(&info->base_oid, 
&obj_list[nr].oid) ||

builtin/update-index.c
4a7e27e957 builtin/update-index.c  672) if (oideq(&ce_2->oid, &ce_3->oid) &&

builtin/worktree.c
e5353bef55  60) error_errno(_("failed to delete '%s'"), sb.buf);
e19831c94f 251)     die(_("unable to re-add worktree '%s'"), path);
68a6b3a1bd 793) die(_("cannot move a locked working tree, lock reason: 
%s\nuse 'move -f -f' to override or unlock first"),
f4143101cb 906) die(_("cannot remove a locked working tree, lock reason: 
%s\nuse 'remove -f -f' to override or unlock first"),

bundle.c
2c8ee1f53c 267) error_errno(_("unable to dup bundle descriptor"));
2c8ee1f53c 268) child_process_clear(&pack_objects);
2c8ee1f53c 269) return -1;
2c8ee1f53c 478) rollback_lock_file(&lock);

cache-tree.c
combine-diff.c
0074c9110d  377) state->sline[state->nb-1].p_lno =
0074c9110d  378) xcalloc(state->num_parent, sizeof(unsigned long));

commit-graph.c
20fd6d5799   79) return 0;
6cc017431c  275) return 0;

commit-reach.c
5227c38566 134) return ret;
5227c38566 282) return 1;
5227c38566 314) return ret;
5227c38566 317) return ret;
1d614d41e5 395) return 0;
1d614d41e5 401) return 0;
1d614d41e5 405) return 0;
4fbcca4eff 538) return 1;
b67f6b26e3 559) continue;
b67f6b26e3 570) from->objects[i].item->flags |= assign_flag;
b67f6b26e3 571) continue;
b67f6b26e3 577) result = 0;
b67f6b26e3 578) goto cleanup;

config.c
c780b9cfe8 2303) return val;
c780b9cfe8 2306) if (is_bool)
c780b9cfe8 2307) return val ? 0 : 1;
c780b9cfe8 2309) return val;

date.c
c27cc94fad  904) tm->tm_mon = number-1;
c27cc94fad  908) else if (number > 69 && number < 100)
c27cc94fad  909) tm->tm_year = number;
c27cc94fad  910) else if (number < 38)
c27cc94fad  911) tm->tm_year = 100 + number;
c27cc94fad  952) pending_number(tm, num);

delta-islands.c
c8d521faf7  53) memcpy(b, old, size);
c8d521faf7  73) return 1;
c8d521faf7 118) return 0;
c8d521faf7 130) return 0;
c8d521faf7 187) b->refcount--;
c8d521faf7 188) b = kh_value(island_marks, pos) = island_bitmap_new(b);
c8d521faf7 202) continue;
c8d521faf7 212) obj = ((struct tag *)obj)->tagged;
c8d521faf7 213) if (obj) {
c8d521faf7 214) parse_object(the_repository, &obj->oid);
c8d521faf7 215) marks = create_or_get_island_marks(obj);
c8d521faf7 216) island_bitmap_set(marks, island_counter);
c8d521faf7 248) return;
c8d521faf7 268) progress_state = start_progress(_("Propagating island 
marks"), nr);
c8d521faf7 286) die(_("bad tree object %s"), oid_to_hex(&ent->idx.oid));
c8d521faf7 293) continue;
c8d521faf7 297) continue;
c8d521faf7 321) return config_error_nonbool(k);
c8d521faf7 330) die(_("failed to load island regex for '%s': %s"), k, 
re.buf);
c8d521faf7 386) warning(_("island regex from config has "
c8d521faf7 397) strbuf_addch(&island_name, '-');
c8d521faf7 433) continue;
c8d521faf7 436) list[dst] = list[src];

diff-lib.c
9001dc2a74 diff-lib.c 346)     (!oideq(oid, &old_entry->oid) || 
!oideq(&old_entry->oid, &new_entry->oid))) {

diff.c
b78ea5fc35 4130) add_external_diff_name(o->repo, &argv, other, two);

dir.c
8a2c174677  287) name = to_free = xmemdupz(name, namelen);
c46c406ae1 2282) trace_performance_leave("read directory %.*s", len, path);

entry.c
b878579ae7 402) static void mark_colliding_entries(const struct checkout 
*state,
b878579ae7 405) int i, trust_ino = check_stat;
b878579ae7 411) ce->ce_flags |= CE_MATCHED;
b878579ae7 413) for (i = 0; i < state->istate->cache_nr; i++) {
b878579ae7 414) struct cache_entry *dup = state->istate->cache[i];
b878579ae7 416) if (dup == ce)
b878579ae7 417) break;
b878579ae7 419) if (dup->ce_flags & (CE_MATCHED | CE_VALID | 
CE_SKIP_WORKTREE))
b878579ae7 420) continue;
b878579ae7 422) if ((trust_ino && dup->ce_stat_data.sd_ino == st->st_ino) ||
b878579ae7 423)     (!trust_ino && !fspathcmp(ce->name, dup->name))) {
b878579ae7 424) dup->ce_flags |= CE_MATCHED;
b878579ae7 425) break;
b878579ae7 428) }
b878579ae7 488) mark_colliding_entries(state, ce, &st);

fsck.c
fb8952077d  214) die_errno("Could not read '%s'", path);

git.c
a9a60b94cc 322) fprintf_ln(stderr, _("'%s' is aliased to '%s'"),
c6d75bc17a 735) string_list_clear(&cmd_list, 0);

gpg-interface.c
4de9394dcb 155) break;

help.c
26c7d06783 help.c         500) static int get_alias(const char *var, 
const char *value, void *data)
26c7d06783 help.c         502) struct string_list *list = data;
26c7d06783 help.c         504) if (skip_prefix(var, "alias.", &var))
26c7d06783 help.c         505) string_list_append(list, var)->util = 
xstrdup(value);
26c7d06783 help.c         507) return 0;
26c7d06783 help.c         530) printf("\n%s\n", _("Command aliases"));
26c7d06783 help.c         531) ALLOC_ARRAY(aliases, alias_list.nr + 1);
26c7d06783 help.c         532) for (i = 0; i < alias_list.nr; i++) {
26c7d06783 help.c         533) aliases[i].name = alias_list.items[i].string;
26c7d06783 help.c         534) aliases[i].help = alias_list.items[i].util;
26c7d06783 help.c         535) aliases[i].category = 1;
26c7d06783 help.c         537) aliases[alias_list.nr].name = NULL;
26c7d06783 help.c         538) print_command_list(aliases, 1, longest);
26c7d06783 help.c         539) free(aliases);

http.c
21084e84a4  316) free(http_ssl_backend);
21084e84a4  317) http_ssl_backend = xstrdup_or_null(value);
21084e84a4  318) return 0;
93aef7c79b  322) http_schannel_check_revoke = git_config_bool(var, value);
93aef7c79b  323) return 0;
b67d40adbb  327) http_schannel_use_ssl_cainfo = git_config_bool(var, value);
b67d40adbb  328) return 0;
93aef7c79b  833)     !http_schannel_check_revoke) {
93aef7c79b  835) curl_easy_setopt(result, CURLOPT_SSL_OPTIONS, 
CURLSSLOPT_NO_REVOKE);
b67d40adbb  883)     !http_schannel_use_ssl_cainfo) {
b67d40adbb  884) curl_easy_setopt(result, CURLOPT_CAINFO, NULL);

ident.c
501afcb8b0 172) strbuf_addstr(&git_default_email, email);
501afcb8b0 173) free((char *)email);

list-objects-filter-options.c
bc5975d24f  55) if (errbuf) {
bc5975d24f  56) strbuf_addstr(
bc5975d24f  60) return 1;
cc0b05a4cc  86) if (errbuf)

list-objects-filter.c
list-objects.c
f447a499db 200) ctx->show_object(obj, base->buf, ctx->show_data);

ll-merge.c
d64324cb60 380) marker_size = DEFAULT_CONFLICT_MARKER_SIZE;

log-tree.c
4a7e27e957 477) if (oideq(&parent->item->object.oid, oid))

mailinfo.c
3aa4d81f88  992) len--;
3aa4d81f88  998) handle_filter(mi, prev);
3aa4d81f88  999) strbuf_reset(prev);
3aa4d81f88 1090) handle_filter(mi, &prev);

midx.c
4d80560c54   58) error_errno(_("failed to read %s"), midx_name);
4d80560c54   59) goto cleanup_fail;
4d80560c54   65) error(_("multi-pack-index file %s is too small"), 
midx_name);
4d80560c54   66) goto cleanup_fail;
0d5b3a5ef7  146) die(_("multi-pack-index missing required OID lookup 
chunk"));
662148c435  148) die(_("multi-pack-index missing required object offsets 
chunk"));
4d80560c54  173) munmap(midx_map, midx_size);
4d80560c54  175) close(fd);
1dcd9f2043  184) return;
3715a6335c  266) return 0;
fe1ed56f5e  413) warning(_("failed to open pack-index '%s'"),
fe1ed56f5e  415) close_pack(packs->list[packs->nr]);
fe1ed56f5e  416) FREE_AND_NULL(packs->list[packs->nr]);
fe1ed56f5e  417) return;
a40498a126  490) return 1;
fe1ed56f5e  507) die(_("failed to locate object %d in packfile"), 
cur_object);
fc59e74844  769) die_errno(_("unable to create leading directories of %s"),
525e18c04b  943) die(_("failed to clear multi-pack-index at %s"), midx);
56ee7ff156  969) return 0;
cc6af73c02 1010) midx_report(_("failed to load pack-index for packfile %s"),
cc6af73c02 1011)     e.p->pack_name);
cc6af73c02 1012) break;

name-hash.c
2179045fd0 532) die(_("unable to create lazy_dir thread: %s"), 
strerror(err));
2179045fd0 554) die(_("unable to create lazy_name thread: %s"), 
strerror(err));
2179045fd0 560) die(_("unable to join lazy_name thread: %s"), 
strerror(err));

oidmap.c
cc00e5ce6b 11) return !oideq(&entry_->oid,

oidset.c
8b2f8cbcb1 29) kh_del_oid(&set->set, pos);
8b2f8cbcb1 30) return 1;

pack-bitmap.c
30cdc33fba 1130) return 0;

pack-objects.c
108f530385 172) REALLOC_ARRAY(pdata->tree_depth, pdata->nr_alloc);
fe0ac2fb7f 175) REALLOC_ARRAY(pdata->layer, pdata->nr_alloc);
108f530385 192) pdata->tree_depth[pdata->nr_objects - 1] = 0;
fe0ac2fb7f 195) pdata->layer[pdata->nr_objects - 1] = 0;

packfile.c
1127a98cce  117) return error("index file %s is too small", path);
1127a98cce  119) return error("empty data");
fe1ed56f5e  211) if (open_pack_index(p))
fe1ed56f5e  212) return 0;
fe1ed56f5e  213) level1_ofs = p->index_data;
17c35c8969  490) break;
17c35c8969  548) return 0;

preload-index.c
ae9af12287  63) struct progress_data *pd = p->progress;
ae9af12287  65) pthread_mutex_lock(&pd->mutex);
ae9af12287  66) pd->n += last_nr - nr;
ae9af12287  67) display_progress(pd->progress, pd->n);
ae9af12287  68) pthread_mutex_unlock(&pd->mutex);
ae9af12287  69) last_nr = nr;
ae9af12287  83) struct progress_data *pd = p->progress;
ae9af12287  85) pthread_mutex_lock(&pd->mutex);
ae9af12287  86) display_progress(pd->progress, pd->n + last_nr);
ae9af12287  87) pthread_mutex_unlock(&pd->mutex);
ae9af12287 118) pd.progress = start_delayed_progress(_("Refreshing 
index"), index->cache_nr);
ae9af12287 119) pthread_mutex_init(&pd.mutex, NULL);
ae9af12287 132) p->progress = &pd;
2179045fd0 137) die(_("unable to create threaded lstat: %s"), 
strerror(err));

read-cache.c
ae9af12287 1490) progress = start_delayed_progress(_("Refresh index"),
ae9af12287 1491)   istate->cache_nr);
ae9af12287 1539) display_progress(progress, i);
ae9af12287 1572) display_progress(progress, istate->cache_nr);
ae9af12287 1573) stop_progress(&progress);
252d079cbd 1784) const unsigned char *cp = (const unsigned char *)name;
252d079cbd 1788) strip_len = decode_varint(&cp);
77ff1127a4 1789) if (previous_ce) {
77ff1127a4 1790) previous_len = previous_ce->ce_namelen;
77ff1127a4 1791) if (previous_len < strip_len)
252d079cbd 1792) die(_("malformed name field in the index, near path '%s'"),
77ff1127a4 1793) previous_ce->name);
77ff1127a4 1794) copy_len = previous_len - strip_len;
252d079cbd 1796) name = (const char *)cp;
252d079cbd 1802) len += copy_len;
252d079cbd 1823) if (copy_len)
252d079cbd 1824) memcpy(ce->name, previous_ce->name, copy_len);
252d079cbd 1825) memcpy(ce->name + copy_len, name, len + 1 - copy_len);
252d079cbd 1826) *ent_size = (name - ((char *)ondisk)) + len + 1 - copy_len;
abb4bb8384 1959) munmap((void *)p->mmap, p->mmap_size);
abb4bb8384 1960) die(_("index file corrupt"));
77ff1127a4 2001) mem_pool_init(&istate->ce_mem_pool,
77ff1127a4 2039) static void *load_cache_entries_thread(void *_data)
77ff1127a4 2041) struct load_cache_entries_thread_data *p = _data;
77ff1127a4 2045) for (i = p->ieot_start; i < p->ieot_start + 
p->ieot_blocks; i++) {
77ff1127a4 2046) p->consumed += load_cache_entry_block(p->istate, 
p->ce_mem_pool,
77ff1127a4 2047) p->offset, p->ieot->entries[i].nr, p->mmap, 
p->ieot->entries[i].offset, NULL);
77ff1127a4 2048) p->offset += p->ieot->entries[i].nr;
77ff1127a4 2050) return NULL;
77ff1127a4 2053) static unsigned long load_cache_entries_threaded(struct 
index_state *istate, const char *mmap, size_t mmap_size,
77ff1127a4 2058) unsigned long consumed = 0;
77ff1127a4 2061) if (istate->name_hash_initialized)
77ff1127a4 2064) mem_pool_init(&istate->ce_mem_pool, 0);
77ff1127a4 2067) if (nr_threads > ieot->nr)
77ff1127a4 2068) nr_threads = ieot->nr;
77ff1127a4 2069) data = xcalloc(nr_threads, sizeof(*data));
77ff1127a4 2071) offset = ieot_start = 0;
77ff1127a4 2072) ieot_blocks = DIV_ROUND_UP(ieot->nr, nr_threads);
77ff1127a4 2073) for (i = 0; i < nr_threads; i++) {
77ff1127a4 2074) struct load_cache_entries_thread_data *p = &data[i];
77ff1127a4 2077) if (ieot_start + ieot_blocks > ieot->nr)
77ff1127a4 2078) ieot_blocks = ieot->nr - ieot_start;
77ff1127a4 2080) p->istate = istate;
77ff1127a4 2081) p->offset = offset;
77ff1127a4 2082) p->mmap = mmap;
77ff1127a4 2083) p->ieot = ieot;
77ff1127a4 2084) p->ieot_start = ieot_start;
77ff1127a4 2085) p->ieot_blocks = ieot_blocks;
77ff1127a4 2088) nr = 0;
77ff1127a4 2089) for (j = p->ieot_start; j < p->ieot_start + 
p->ieot_blocks; j++)
77ff1127a4 2090) nr += p->ieot->entries[j].nr;
77ff1127a4 2091) if (istate->version == 4) {
77ff1127a4 2092) mem_pool_init(&p->ce_mem_pool,
77ff1127a4 2095) mem_pool_init(&p->ce_mem_pool,
77ff1127a4 2099) err = pthread_create(&p->pthread, NULL, 
load_cache_entries_thread, p);
77ff1127a4 2100) if (err)
77ff1127a4 2101) die(_("unable to create load_cache_entries thread: 
%s"), strerror(err));
77ff1127a4 2104) for (j = 0; j < ieot_blocks; j++)
77ff1127a4 2105) offset += ieot->entries[ieot_start + j].nr;
77ff1127a4 2106) ieot_start += ieot_blocks;
77ff1127a4 2109) for (i = 0; i < nr_threads; i++) {
77ff1127a4 2110) struct load_cache_entries_thread_data *p = &data[i];
77ff1127a4 2112) err = pthread_join(p->pthread, NULL);
77ff1127a4 2113) if (err)
77ff1127a4 2114) die(_("unable to join load_cache_entries thread: %s"), 
strerror(err));
77ff1127a4 2115) mem_pool_combine(istate->ce_mem_pool, p->ce_mem_pool);
77ff1127a4 2116) consumed += p->consumed;
77ff1127a4 2119) free(data);
77ff1127a4 2121) return consumed;
abb4bb8384 2193) extension_offset = read_eoie_extension(mmap, mmap_size);
abb4bb8384 2194) if (extension_offset) {
abb4bb8384 2197) p.src_offset = extension_offset;
abb4bb8384 2198) err = pthread_create(&p.pthread, NULL, 
load_index_extensions, &p);
abb4bb8384 2199) if (err)
abb4bb8384 2200) die(_("unable to create load_index_extensions thread: 
%s"), strerror(err));
abb4bb8384 2202) nr_threads--;
77ff1127a4 2211) ieot = read_ieot_extension(mmap, mmap_size, 
extension_offset);
77ff1127a4 2214) src_offset += load_cache_entries_threaded(istate, mmap, 
mmap_size, src_offset, nr_threads, ieot);
77ff1127a4 2215) free(ieot);
abb4bb8384 2225) int ret = pthread_join(p.pthread, NULL);
abb4bb8384 2226) if (ret)
abb4bb8384 2227) die(_("unable to join load_index_extensions thread: 
%s"), strerror(ret));
3255089ada 2769) ieot_blocks = nr_threads;
77ff1127a4 2770) if (ieot_blocks > istate->cache_nr)
77ff1127a4 2771) ieot_blocks = istate->cache_nr;
3255089ada 2779) ieot = xcalloc(1, sizeof(struct index_entry_offset_table)
3255089ada 2780) + (ieot_blocks * sizeof(struct index_entry_offset)));
77ff1127a4 2781) ieot_entries = DIV_ROUND_UP(entries, ieot_blocks);
3255089ada 2787) free(ieot);
3b1d9e045e 2788) return -1;
3255089ada 2814) ieot->entries[ieot->nr].nr = nr;
3255089ada 2815) ieot->entries[ieot->nr].offset = offset;
3255089ada 2816) ieot->nr++;
3255089ada 2822) if (previous_name)
3255089ada 2823) previous_name->buf[0] = 0;
3255089ada 2824) nr = 0;
3255089ada 2825) offset = lseek(newfd, 0, SEEK_CUR);
3255089ada 2826) if (offset < 0) {
3255089ada 2827) free(ieot);
3255089ada 2828) return -1;
3255089ada 2830) offset += write_buffer_len;
3255089ada 2840) ieot->entries[ieot->nr].nr = nr;
3255089ada 2841) ieot->entries[ieot->nr].offset = offset;
3255089ada 2842) ieot->nr++;
3255089ada 2854) free(ieot);
3b1d9e045e 2855) return -1;
3255089ada 2868) struct strbuf sb = STRBUF_INIT;
3255089ada 2870) write_ieot_extension(&sb, ieot);
3255089ada 2871) err = write_index_ext_header(&c, &eoie_c, newfd, 
CACHE_EXT_INDEXENTRYOFFSETTABLE, sb.len) < 0
3255089ada 2872) || ce_write(&c, newfd, sb.buf, sb.len) < 0;
3255089ada 2873) strbuf_release(&sb);
3255089ada 2874) free(ieot);
3255089ada 2875) if (err)
3255089ada 2876) return -1;
3b1d9e045e 3363) static size_t read_eoie_extension(const char *mmap, 
size_t mmap_size)
3b1d9e045e 3381) if (mmap_size < sizeof(struct cache_header) + 
EOIE_SIZE_WITH_HEADER + the_hash_algo->rawsz)
3b1d9e045e 3382) return 0;
3b1d9e045e 3385) index = eoie = mmap + mmap_size - EOIE_SIZE_WITH_HEADER 
- the_hash_algo->rawsz;
3b1d9e045e 3386) if (CACHE_EXT(index) != CACHE_EXT_ENDOFINDEXENTRIES)
3b1d9e045e 3387) return 0;
3b1d9e045e 3388) index += sizeof(uint32_t);
3b1d9e045e 3391) extsize = get_be32(index);
3b1d9e045e 3392) if (extsize != EOIE_SIZE)
3b1d9e045e 3393) return 0;
3b1d9e045e 3394) index += sizeof(uint32_t);
3b1d9e045e 3400) offset = get_be32(index);
3b1d9e045e 3401) if (mmap + offset < mmap + sizeof(struct cache_header))
3b1d9e045e 3402) return 0;
3b1d9e045e 3403) if (mmap + offset >= eoie)
3b1d9e045e 3404) return 0;
3b1d9e045e 3405) index += sizeof(uint32_t);
3b1d9e045e 3416) src_offset = offset;
3b1d9e045e 3417) the_hash_algo->init_fn(&c);
3b1d9e045e 3418) while (src_offset < mmap_size - the_hash_algo->rawsz - 
EOIE_SIZE_WITH_HEADER) {
3b1d9e045e 3426) memcpy(&extsize, mmap + src_offset + 4, 4);
3b1d9e045e 3427) extsize = ntohl(extsize);
3b1d9e045e 3430) if (src_offset + 8 + extsize < src_offset)
3b1d9e045e 3431) return 0;
3b1d9e045e 3433) the_hash_algo->update_fn(&c, mmap + src_offset, 8);
3b1d9e045e 3435) src_offset += 8;
3b1d9e045e 3436) src_offset += extsize;
3b1d9e045e 3438) the_hash_algo->final_fn(hash, &c);
3b1d9e045e 3439) if (!hasheq(hash, (const unsigned char *)index))
3b1d9e045e 3440) return 0;
3b1d9e045e 3443) if (src_offset != mmap_size - the_hash_algo->rawsz - 
EOIE_SIZE_WITH_HEADER)
3b1d9e045e 3444) return 0;
3b1d9e045e 3446) return offset;
3255089ada 3465) static struct index_entry_offset_table 
*read_ieot_extension(const char *mmap, size_t mmap_size, size_t offset)
3255089ada 3467)        const char *index = NULL;
3255089ada 3473)        if (!offset)
3255089ada 3474)        return NULL;
3255089ada 3475)        while (offset <= mmap_size - 
the_hash_algo->rawsz - 8) {
3255089ada 3476)        extsize = get_be32(mmap + offset + 4);
3255089ada 3477)        if (CACHE_EXT((mmap + offset)) == 
CACHE_EXT_INDEXENTRYOFFSETTABLE) {
3255089ada 3478)        index = mmap + offset + 4 + 4;
3255089ada 3479)        break;
3255089ada 3481)        offset += 8;
3255089ada 3482)        offset += extsize;
3255089ada 3484)        if (!index)
3255089ada 3485)        return NULL;
3255089ada 3488)        ext_version = get_be32(index);
3255089ada 3489)        if (ext_version != IEOT_VERSION) {
3255089ada 3490)        error("invalid IEOT version %d", ext_version);
3255089ada 3491)        return NULL;
3255089ada 3493)        index += sizeof(uint32_t);
3255089ada 3496)        nr = (extsize - sizeof(uint32_t)) / 
(sizeof(uint32_t) + sizeof(uint32_t));
3255089ada 3497)        if (!nr) {
3255089ada 3498)        error("invalid number of IEOT entries %d", nr);
3255089ada 3499)        return NULL;
3255089ada 3501)        ieot = xmalloc(sizeof(struct 
index_entry_offset_table)
3255089ada 3502)        + (nr * sizeof(struct index_entry_offset)));
3255089ada 3503)        ieot->nr = nr;
3255089ada 3504)        for (i = 0; i < nr; i++) {
3255089ada 3505)        ieot->entries[i].offset = get_be32(index);
3255089ada 3506)        index += sizeof(uint32_t);
3255089ada 3507)        ieot->entries[i].nr = get_be32(index);
3255089ada 3508)        index += sizeof(uint32_t);
3255089ada 3511)        return ieot;
3255089ada 3514) static void write_ieot_extension(struct strbuf *sb, 
struct index_entry_offset_table *ieot)
3255089ada 3520)        put_be32(&buffer, IEOT_VERSION);
3255089ada 3521)        strbuf_add(sb, &buffer, sizeof(uint32_t));
3255089ada 3524)        for (i = 0; i < ieot->nr; i++) {
3255089ada 3527)        put_be32(&buffer, ieot->entries[i].offset);
3255089ada 3528)        strbuf_add(sb, &buffer, sizeof(uint32_t));
3255089ada 3531)        put_be32(&buffer, ieot->entries[i].nr);
3255089ada 3532)        strbuf_add(sb, &buffer, sizeof(uint32_t));
3255089ada 3534) }

rebase-interactive.c
64a43cbd5d 62) return error_errno(_("could not read '%s'."), todo_file);
64a43cbd5d 66) strbuf_release(&buf);
64a43cbd5d 67) return -1;
a9f5476fbc 75) return error_errno(_("could not read '%s'."), todo_file);
a9f5476fbc 79) strbuf_release(&buf);
a9f5476fbc 80) return -1;
64a43cbd5d 86) return -1;

ref-filter.c
f0062d3b74 1039) v->s = xstrdup("");
f0062d3b74 1302) free((char *)to_free);
f0062d3b74 1303) return xstrdup("");
f0062d3b74 1340) free((char *)to_free);
f0062d3b74 1341) return xstrdup("");
f0062d3b74 1391) *s = xstrdup("=");
f0062d3b74 1393) *s = xstrdup("<");
f0062d3b74 1518) ref->symref = xstrdup("");
f0062d3b74 1587) v->s = xstrdup("");

refs.c
3a3b9d8cde  661) return 0;
4a6067cda5 1431) return 0;

refs/files-backend.c
refs/packed-backend.c
9001dc2a74 1163) } else if (!oideq(&update->old_oid, iter->oid)) {

refs/ref-cache.c
9001dc2a74 275) if (!oideq(&ref1->u.value.oid, &ref2->u.value.oid))

remote.c
85daa01f6b 1219) continue;
85daa01f6b 1225) continue;

rerere.c
2373b65059  217) die(_("corrupt MERGE_RR"));
2373b65059  226) die(_("corrupt MERGE_RR"));
2373b65059  229) die(_("corrupt MERGE_RR"));
2373b65059  264) die(_("unable to write rerere record"));
2373b65059  269) die(_("unable to write rerere record"));
4af32207bc  376) break;
4af32207bc  380) strbuf_addbuf(&two, &conflict);
c0f16f8e14  384) break;
c0f16f8e14  388) break;
c0f16f8e14  392) break;
2373b65059  480) return error_errno(_("could not open '%s'"), path);
2373b65059  485) error_errno(_("could not write '%s'"), output);
2373b65059  495) error(_("there were errors while writing '%s' (%s)"),
2373b65059  498) io.io.wrerror = error_errno(_("failed to flush '%s'"), 
path);
2373b65059  565) return error(_("index file corrupt"));
2373b65059  599) return error(_("index file corrupt"));
2373b65059  684) warning_errno(_("failed utime() on '%s'"),
2373b65059  690) return error_errno(_("could not open '%s'"), path);
2373b65059  692) error_errno(_("could not write '%s'"), path);
2373b65059  694) return error_errno(_("writing '%s' failed"), path);
2373b65059  720) die(_("unable to write new index file"));
2373b65059  803) die_errno(_("cannot unlink stray '%s'"), path);
2373b65059 1057) error(_("failed to update conflicted state in '%s'"), 
path);
2373b65059 1075) error(_("no remembered resolution for '%s'"), path);
2373b65059 1077) error_errno(_("cannot unlink '%s'"), filename);
2373b65059 1111) return error(_("index file corrupt"));
2373b65059 1199) die_errno(_("unable to open rr-cache directory"));

revision.c
2abf350385 1538) if (ce_path_match(istate, ce, &revs->prune_data, NULL)) {
2abf350385 1544) while ((i+1 < istate->cache_nr) &&
2abf350385 1545)        ce_same_name(ce, istate->cache[i+1]))
b45424181e 2942) return;
b45424181e 2945) return;
b45424181e 2951) c->object.flags |= UNINTERESTING;
b45424181e 2954) return;
b45424181e 2957) mark_parents_uninteresting(c);
b45424181e 2980) return;
b45424181e 2983) return;
b45424181e 3048) continue;
f0d9cc4196 3097) if (!revs->ignore_missing_links)
f0d9cc4196 3098) die("Failed to traverse parents of commit %s",
f0d9cc4196 3099)     oid_to_hex(&commit->object.oid));
b45424181e 3107) continue;
4a7e27e957 3473)     oideq(&p->item->object.oid, &commit->object.oid))

run-command.c
2179045fd0 1229) error(_("cannot create async thread: %s"), strerror(err));

send-pack.c
c0e40a2d66 207) close(fd[1]);

sequencer.c
bcd33ec25f  683) np = strchrnul(buf, '\n');
bcd33ec25f  684) return error(_("no key present in '%.*s'"),
bcd33ec25f  695) return error(_("unable to dequote value of '%s'"),
bcd33ec25f  737) goto finish;
bcd33ec25f  742) name_i = error(_("'GIT_AUTHOR_NAME' already given"));
bcd33ec25f  747) email_i = error(_("'GIT_AUTHOR_EMAIL' already given"));
bcd33ec25f  752) date_i = error(_("'GIT_AUTHOR_DATE' already given"));
bcd33ec25f  756) err = error(_("unknown variable '%s'"),
bcd33ec25f  761) error(_("missing 'GIT_AUTHOR_NAME'"));
bcd33ec25f  763) error(_("missing 'GIT_AUTHOR_EMAIL'"));
bcd33ec25f  765) error(_("missing 'GIT_AUTHOR_DATE'"));
65850686cf 2329) return;
65850686cf 2426) write_file(rebase_path_quiet(), "%s\n", quiet);
2c58483a59 3427) return error(_("could not checkout %s"), commit);
4df66c40b0 3441) return error(_("%s: not a valid OID"), orig_head);
71f82465b1 3461) fprintf(stderr, _("Stopped at HEAD\n"));
b97e187364 4827) return -1;
b97e187364 4830) return -1;
b97e187364 4836) return error_errno(_("could not read '%s'."), todo_file);
b97e187364 4839) todo_list_release(&todo_list);
b97e187364 4840) return error(_("unusable todo list: '%s'"), todo_file);
b97e187364 4859) todo_list_release(&todo_list);
b97e187364 4860) return -1;
b97e187364 4864) return error(_("could not copy '%s' to '%s'."), todo_file,
b97e187364 4868) return error(_("could not transform the todo list"));
b97e187364 4897) return error(_("could not transform the todo list"));
b97e187364 4900) return error(_("could not skip unnecessary pick 
commands"));
b97e187364 4906) return -1;

setup.c
58b284a2e9  413) return config_error_nonbool(var);

sha1-file.c
67947c34ae sha1-file.c 2225) if (!hasheq(expected_sha1, real_sha1)) {

sha1-name.c
8aac67a174 sha1-name.c  162) return;

split-index.c
e3d837989e 335) ce->ce_flags |= CE_UPDATE_IN_BASE;

strbuf.c
f95736288a  127) --sb->len;

submodule-config.c
bcbc780d14 739) return CONFIG_INVALID_KEY;
45f5ef3d77 754) warning(_("Could not update .gitmodules entry %s"), key);

trace.c
c46c406ae1 189) now = getnanotime();
c46c406ae1 190) perf_start_times[perf_indent] = now;
c46c406ae1 191) if (perf_indent + 1 < ARRAY_SIZE(perf_start_times))
c46c406ae1 192) perf_indent++;
c46c406ae1 195) return now;
c46c406ae1 211) if (perf_indent >= strlen(space))
c46c406ae1 214) strbuf_addf(&buf, ":%.*s ", perf_indent, space);
c46c406ae1 317) void trace_performance_leave_fl(const char *file, int line,
c46c406ae1 323) if (perf_indent)
c46c406ae1 324) perf_indent--;
c46c406ae1 326) if (!format) /* Allow callers to leave without tracing 
anything */
c46c406ae1 327) return;
c46c406ae1 329) since = perf_start_times[perf_indent];
c46c406ae1 330) va_start(ap, format);
c46c406ae1 331) trace_performance_vprintf_fl(file, line, nanos - since, 
format, ap);
c46c406ae1 332) va_end(ap);
c46c406ae1 477) trace_performance_leave("git command:%s", command_line.buf);
c46c406ae1 485) if (!command_line.len)
c46c406ae1 490) trace_performance_enter();

transport.c
unpack-trees.c
b878579ae7  360) string_list_append(&list, ce->name);
b878579ae7  361) ce->ce_flags &= ~CE_MATCHED;
b878579ae7  368) warning(_("the following paths have collided (e.g. 
case-sensitive paths\n"
b878579ae7  372) for (i = 0; i < list.nr; i++)
b878579ae7  373) fprintf(stderr, "  '%s'\n", list.items[i].string);
f1e11c6510  777) free(tree_ce);
b4da37380b  778) return rc;
b4da37380b  785) printf("Unpacked %d entries from %s to %s using 
cache-tree\n",
b4da37380b  787)        o->src_index->cache[pos]->name,
b4da37380b  788)        o->src_index->cache[pos + nr_entries - 1]->name);

upload-pack.c
1d1243fe63 1403) deepen(INFINITE_DEPTH, data->deepen_relative, 
&data->shallows,

worktree.c
3a3b9d8cde 495) return -1;
3a3b9d8cde 508) return -1;
3a3b9d8cde 517) return -1;
ab3e1f78ae 537) break;

wt-status.c
f3bd35fa0d  671) s->committable = 1;
73ba5d78b4 1958) if (s->state.rebase_in_progress ||
73ba5d78b4 1959)     s->state.rebase_interactive_in_progress)
73ba5d78b4 1960) branch_name = s->state.onto;
73ba5d78b4 1961) else if (s->state.detached_from)
73ba5d78b4 1962) branch_name = s->state.detached_from;

xdiff-interface.c
xdiff/xutils.c
611e42a598 405) return -1;

Commits introducing uncovered code:
Ævar Arnfjörð Bjarmason      62c23938f: tests: add a special setup where 
rebase.useBuiltin is off
Alban Gruin      0af129b2e: rebase--interactive2: rewrite the submodes 
of interactive rebase in C
Alban Gruin      2c58483a5: rebase -i: rewrite setup_reflog_action() in C
Alban Gruin      34b47315d: rebase -i: move rebase--helper modes to 
rebase--interactive
Alban Gruin      4df66c40b: rebase -i: rewrite checkout_onto() in C
Alban Gruin      53bbcfbde: rebase -i: implement the main part of 
interactive rebase as a builtin
Alban Gruin      64a43cbd5: rebase -i: rewrite the edit-todo 
functionality in C
Alban Gruin      65850686c: rebase -i: rewrite write_basic_state() in C
Alban Gruin      a9f5476fb: sequencer: refactor append_todo_help() to 
write its message to a buffer
Alban Gruin      b97e18736: rebase -i: rewrite complete_action() in C
Antonio Ospite      45f5ef3d7: submodule: factor out a 
config_set_in_gitmodules_file_gently function
Antonio Ospite      76e9bdc43: submodule: support reading .gitmodules 
when it's not in the working tree
Antonio Ospite      bcbc780d1: submodule: add a 
print_config_from_gitmodules() helper
Ben Peart      3255089ad: ieot: add Index Entry Offset Table (IEOT) 
extension
Ben Peart      3b1d9e045: eoie: add End of Index Entry (EOIE) extension
Ben Peart      77ff1127a: read-cache: load cache entries on worker threads
Ben Peart      abb4bb838: read-cache: load cache extensions on a worker 
thread
Ben Peart      c780b9cfe: config: add new index.threads config setting
Ben Peart      d1664e73a: add: speed up cmd_add() by utilizing 
read_cache_preload()
Ben Peart      fa655d841: checkout: optimize "git checkout -b <new_branch>"
Brendan Forster      93aef7c79: http: add support for disabling SSL 
revocation checks in cURL
brian m. carlson      2f0c9e9a9: builtin/repack: replace hard-coded 
constants
brian m. carlson      eccb5a5f3: apply: rename new_sha1_prefix and 
old_sha1_prefix
Christian Couder      108f53038: pack-objects: move tree_depth into 
'struct packing_data'
Christian Couder      fe0ac2fb7: pack-objects: move 'layer' into 'struct 
packing_data'
Derrick Stolee      0d5b3a5ef: midx: write object ids in a chunk
Derrick Stolee      17c35c896: packfile: skip loading index if in 
multi-pack-index
Derrick Stolee      1d614d41e: commit-reach: move ref_newer from remote.c
Derrick Stolee      1dcd9f204: midx: close multi-pack-index on repack
Derrick Stolee      20fd6d579: commit-graph: not compatible with grafts
Derrick Stolee      3715a6335: midx: read objects from multi-pack-index
Derrick Stolee      454ea2e4d: treewide: use get_all_packs
Derrick Stolee      4d80560c5: multi-pack-index: load into memory
Derrick Stolee      4fbcca4ef: commit-reach: make can_all_from_reach... 
linear
Derrick Stolee      5227c3856: commit-reach: move walk methods from commit.c
Derrick Stolee      525e18c04: midx: clear midx on repack
Derrick Stolee      56ee7ff15: multi-pack-index: add 'verify' verb
Derrick Stolee      662148c43: midx: write object offsets
Derrick Stolee      66ec0390e: fsck: verify multi-pack-index
Derrick Stolee      6a22d5212: pack-objects: consider packs in 
multi-pack-index
Derrick Stolee      6cc017431: commit-reach: use can_all_from_reach
Derrick Stolee      6d68e6a46: multi-pack-index: provide more helpful 
usage info
Derrick Stolee      85daa01f6: remote: make add_missing_tags() linear
Derrick Stolee      8aac67a17: midx: use midx in abbreviation calculations
Derrick Stolee      a40498a12: midx: use existing midx when writing new one
Derrick Stolee      b45424181: revision.c: generation-based topo-order 
algorithm
Derrick Stolee      b67f6b26e: commit-reach: properly peel tags
Derrick Stolee      cc6af73c0: multi-pack-index: verify object offsets
Derrick Stolee      f0d9cc419: revision.c: begin refactoring 
--topo-order logic
Derrick Stolee      fc59e7484: midx: write header information to lockfile
Derrick Stolee      fe1ed56f5: midx: sort and deduplicate objects from 
packfiles
Duy Nguyen      b878579ae: clone: report duplicate entries on 
case-insensitive filesystems
Eric Sunshine      2e6fd71a5: format-patch: extend --range-diff to 
accept revision range
Eric Sunshine      40ce41604: format-patch: allow --range-diff to apply 
to a lone-patch
Eric Sunshine      68a6b3a1b: worktree: teach 'move' to override lock 
when --force given twice
Eric Sunshine      8631bf1cd: format-patch: add --creation-factor tweak 
for --range-diff
Eric Sunshine      e19831c94: worktree: teach 'add' to respect --force 
for registered but missing path
Eric Sunshine      e5353bef5: worktree: move delete_git_dir() earlier in 
file for upcoming new callers
Eric Sunshine      ee6cbf712: format-patch: allow --interdiff to apply 
to a lone-patch
Eric Sunshine      f4143101c: worktree: teach 'remove' to override lock 
when --force given twice
Jeff King      0074c9110: combine-diff: use an xdiff hunk callback
Jeff King      01a31f3bc: pull: handle --verify-signatures for unborn branch
Jeff King      0eb8d3767: cat-file: report an error on multiple --batch 
options
Jeff King      16d75fa48: repack: add delta-islands support
Jeff King      28b8a7308: pack-objects: add delta-islands support
Jeff King      2c8ee1f53: bundle: dup() output descriptor closer to 
point-of-use
Jeff King      2fa233a55: pack-objects: handle island check for 
"external" delta base
Jeff King      30cdc33fb: pack-bitmap: save "have" bitmap from walk
Jeff King      4a7e27e95: convert "oidcmp() == 0" to oideq()
Jeff King      517fe807d: assert NOARG/NONEG behavior of parse-options 
callbacks
Jeff King      611e42a59: xdiff: provide a separate emit callback for hunks
Jeff King      67947c34a: convert "hashcmp() != 0" to "!hasheq()"
Jeff King      735ca208c: apply: return -1 from option callback instead 
of calling exit(1)
Jeff King      8a2c17467: pathspec: handle non-terminated strings with 
:(attr)
Jeff King      9001dc2a7: convert "oidcmp() != 0" to "!oideq()"
Jeff King      98f425b45: cat-file: handle streaming failures consistently
Jeff King      c27cc94fa: approxidate: handle pending number for "specials"
Jeff King      c8d521faf: Add delta-islands.{c,h}
Jeff King      cc00e5ce6: convert hashmap comparison functions to oideq()
Jeff King      fce566480: am: handle --no-patch-format option
Johannes Schindelin      21084e84a: http: add support for selecting SSL 
backends at runtime
Johannes Schindelin      3249c1251: rebase: consolidate clean-up code 
before leaving reset_head()
Johannes Schindelin      501afcb8b: mingw: use domain information for 
default email
Johannes Schindelin      71f82465b: rebase -i: introduce the 'break' command
Johannes Schindelin      b67d40adb: http: when using Secure Channel, 
ignore sslCAInfo by default
Johannes Schindelin      bac2a1e36: built-in rebase: reinstate `checkout 
-q` behavior where appropriate
Johannes Schindelin      bc24382c2: builtin rebase: prepare for builtin 
rebase -i
Jonathan Nieder      302997027: gc: do not return error for prior errors 
in daemonized mode
Jonathan Nieder      fec2ed218: gc: exit with status 128 on failure
Jonathan Tan      1d1243fe6: upload-pack: make want_obj not global
Josh Steadmon      1127a98cc: fuzz: add fuzz testing for packfile indices.
Matthew DeVore      7c0fe330d: rev-list: handle missing tree objects 
properly
Matthew DeVore      bc5975d24: list-objects-filter: implement filter tree:0
Matthew DeVore      cc0b05a4c: list-objects-filter-options: do not 
over-strbuf_init
Matthew DeVore      f447a499d: list-objects: store common func args in 
struct
Michał Górny      4de9394dc: gpg-interface.c: obtain primary key 
fingerprint as well
Nguyễn Thái Ngọc Duy      2179045fd: Clean up pthread_create() error 
handling
Nguyễn Thái Ngọc Duy      252d079cb: read-cache.c: optimize reading 
index format v4
Nguyễn Thái Ngọc Duy      26c7d0678: help -a: improve and make --verbose 
default
Nguyễn Thái Ngọc Duy      2abf35038: revision.c: remove implicit 
dependency on the_index
Nguyễn Thái Ngọc Duy      3a3b9d8cd: refs: new ref types to make 
per-worktree refs visible to all worktrees
Nguyễn Thái Ngọc Duy      58b284a2e: worktree: add per-worktree config files
Nguyễn Thái Ngọc Duy      a470beea3: blame.c: rename "repo" argument to "r"
Nguyễn Thái Ngọc Duy      ab3e1f78a: revision.c: better error reporting 
on ref from different worktrees
Nguyễn Thái Ngọc Duy      ae9af1228: status: show progress bar if 
refreshing the index takes too long
Nguyễn Thái Ngọc Duy      b29759d89: fsck: check HEAD and reflog from 
other worktrees
Nguyễn Thái Ngọc Duy      b4da37380: unpack-trees: optimize walking same 
trees with cache-tree
Nguyễn Thái Ngọc Duy      b78ea5fc3: diff.c: reduce implicit dependency 
on the_index
Nguyễn Thái Ngọc Duy      c0e40a2d6: send-pack.c: move async's #ifdef 
NO_PTHREADS back to run-command.c
Nguyễn Thái Ngọc Duy      c46c406ae: trace.h: support nested performance 
tracing
Nguyễn Thái Ngọc Duy      c9ef0d95e: reflog expire: cover reflog from 
all worktrees
Nguyễn Thái Ngọc Duy      f1e11c651: unpack-trees: reduce malloc in 
cache-tree walk
Nguyễn Thái Ngọc Duy      fd6263fb7: grep: clean up num_threads handling
Olga Telezhnaya      f0062d3b7: ref-filter: free item->value and 
item->value->s
Phillip Wood      bcd33ec25: add read_author_script() to libgit
Pratik Karki      002ee2fe6: builtin rebase: support `keep-empty` option
Pratik Karki      0eabf4b95: builtin rebase: stop if `git am` is in progress
Pratik Karki      12026a412: builtin rebase: support `--gpg-sign` option
Pratik Karki      122420c29: builtin rebase: support --skip
Pratik Karki      1ed9c14ff: builtin rebase: support --force-rebase
Pratik Karki      3c3588c7d: builtin rebase: support 
--rebase-merges[=[no-]rebase-cousins]
Pratik Karki      55071ea24: rebase: start implementing it as a builtin
Pratik Karki      5a6149453: builtin rebase: support --quit
Pratik Karki      5e5d96197: builtin rebase: support --abort
Pratik Karki      6defce2b0: builtin rebase: support `--autostash` option
Pratik Karki      73d51ed0a: builtin rebase: support --signoff
Pratik Karki      9a48a615b: builtin rebase: try to fast forward when 
possible
Pratik Karki      9dba809a6: builtin rebase: support --root
Pratik Karki      ac7f467fe: builtin/rebase: support running "git rebase 
<upstream>"
Pratik Karki      ba1905a5f: builtin rebase: add support for custom 
merge strategies
Pratik Karki      bff014dac: builtin rebase: support the `verbose` and 
`diffstat` options
Pratik Karki      c54dacb50: builtin rebase: start a new rebase only if 
none is in progress
Pratik Karki      cda614e48: builtin rebase: show progress when 
connected to a terminal
Pratik Karki      e0333e5c6: builtin rebase: require a clean worktree
Pratik Karki      e65123a71: builtin rebase: support `git rebase 
<upstream> <switch-to>`
Pratik Karki      ead98c111: builtin rebase: support --rerere-autoupdate
Pratik Karki      f28d40d3a: builtin rebase: support --onto
Pratik Karki      f95736288: builtin rebase: support --continue
Rasmus Villemoes      a9a60b94c: git.c: handle_alias: prepend alias info 
when first argument is -h
Rasmus Villemoes      e6e76baaf: help: redirect to aliased commands for 
"git cmd --help"
René Scharfe      3aa4d81f8: mailinfo: support format=flowed
René Scharfe      8b2f8cbcb: oidset: use khash
René Scharfe      fb8952077: fsck: use strbuf_getline() to read skiplist 
file
Shulhan      5025425df: builtin/remote: quote remote name on error to 
display empty name
Stefan Beller      4a6067cda: refs.c: migrate internal ref iteration to 
pass thru repository argument
Stefan Beller      74d4731da: submodule--helper: replace 
connect-gitdir-workingtree by ensure-core-worktree
Stefan Beller      e0a862fda: submodule helper: convert relative URL to 
absolute URL if needed
Stefan Beller      ee69b2a90: submodule--helper: introduce new 
update-module-mode helper
Stephen P. Smith      73ba5d78b: roll wt_status_state into wt_status and 
populate in the collect phase
Stephen P. Smith      f3bd35fa0: wt-status.c: set the committable flag 
in the collect phase
SZEDER Gábor      e3d837989: split-index: don't compare cached data of 
entries already marked for split index
Thomas Gummerer      2373b6505: rerere: mark strings for translation
Thomas Gummerer      4af32207b: rerere: teach rerere to handle nested 
conflicts
Thomas Gummerer      c0f16f8e1: rerere: factor out handle_conflict function
Tim Schumacher      c6d75bc17: alias: add support for aliases of an alias
Torsten Bögershausen      d64324cb6: Make git_check_attr() a void function


^ permalink raw reply	[relevance 1%]

* Re: [RFC] Generation Number v2
  @ 2018-11-02 17:44  3%       ` Jakub Narebski
  0 siblings, 0 replies; 200+ results
From: Jakub Narebski @ 2018-11-02 17:44 UTC (permalink / raw)
  To: Derrick Stolee
  Cc: Ævar Arnfjörð Bjarmason, Junio C Hamano, Git List,
	Jeff King, Derrick Stolee

Derrick Stolee <stolee@gmail.com> writes:
> On 10/31/2018 8:54 AM, Ævar Arnfjörð Bjarmason wrote:
>> On Tue, Oct 30 2018, Junio C Hamano wrote:
>>> Derrick Stolee <stolee@gmail.com> writes:
>>>>
>>>> In contrast, maximum generation numbers and corrected commit
>>>> dates both performed quite well. They are frequently the top
>>>> two performing indexes, and rarely significantly different.
>>>>
>>>> The trade-off here now seems to be: which _property_ is more important,
>>>> locally-computable or backwards-compatible?
>>>
>>> Nice summary.
>>>
>>> As I already said, I personally do not think being compatible with
>>> currently deployed clients is important at all (primarily because I
>>> still consider the whole thing experimental), and there is a clear
>>> way forward once we correct the mistake of not having a version
>>> number in the file format that tells the updated clients to ignore
>>> the generation numbers.  For longer term viability, we should pick
>>> something that is immutable, reproducible, computable with minimum
>>> input---all of which would lead to being incrementally computable, I
>>> would think.
>>
>> I think it depends on what we mean by backwards compatibility. None of
>> our docs are saying this is experimental right now, just that it's
>> opt-in like so many other git-config(1) options.
>>
>> So if we mean breaking backwards compatibility in that we'll write a new
>> file or clobber the existing one with a version older clients can't use
>> as an optimization, fine.
>>
>> But it would be bad to produce a hard error on older clients, but
>> avoiding that seems as easy as just creating a "commit-graph2" file in
>> .git/objects/info/.
>
> Well, we have a 1-byte version number following the "CGPH" header in
> the commit-graph file, and clients will ignore the commit-graph file
> if that number is not "1". My hope for backwards-compatibility was
> to avoid incrementing this value and instead use the unused 8th byte.

How?  Some of the considered new generation numbers were backwards
compatibile in the sense that for graph traversal the old code for
(minimum) generation numbers could be used.  But that is for reading.
If the old client tried to update new generation number using old
generation number algorithm (assuming locality), it would write some
chimaera that may or may not work.

> However, it appears that we are destined to increment that version
> number, anyway. Here is my list for what needs to be in the next
> version of the commit-graph file format:

As a reminder, here is how the commit-graph format looks now [1]

  HEADER:
    4-byte signature:                  CGPH
    1-byte version number:             1
    1-byte Hash Version:               1 = SHA-1
    1-byte number (C) of "chunks":     3 or 4
    1-byte (reserved for later use)    <ignored>
  CHUNK LOOKUP:
    (C + 1) * 12 bytes listing the table of contents for the chunks
  CHUNK DATA:
    OIDF
    OIDL
    CDAT
    EDGE [optional, depends on graph structure]
  TRAILER:
    checksum

[1]: Documentation/technical/commit-graph-format.txt


> 1. A four-byte hash version.

Why do you need a four-byte hash version?  255 possible hash functions
is not enough?

> 2. File incrementality (split commit-graph).

This would probably require a segment lookup part, and one chunk of each
type per segment (or even one chunk lookup table per segment).  It may
or may not need generation number which can support segmenting (here
maximal generation numbers have the advantage); but you can assume that
if segment(A) > segment(B) then reach.ix(A) > reach.ix(B),
i.e. lexicographical-like sort.

> 3. Reachability Index versioning

I assume that you mean here versioning for the reachability index that
is stored in the CDAT section (if it is in a separate chunk, it should
be easy to version).

The easiest thing to do when encountering unknown or not supported
generation number (perhaps the commit-graph file was created in the past
by earlier version of Git, perahs it came from server with newer Git, or
perhaps the same repository is sometimes accessed using newer and
sometimes older Git version), is to set commit->generation_number to
GENERATION_NUMBER_ZERO, as you wrote in [2].

We could encode backward-compatibility (for using) somewhat, but I
wonder how much it would be of use.

[2]: https://public-inbox.org/git/61a829ce-0d29-81c9-880e-7aef1bec916e@gmail.com/

> Most of these changes will happen in the file header. The chunks
> themselves don't need to change, but some chunks may be added that
> only make sense in v2 commit-graphs.

What I would like to see in v2 commit-graph format would be some kind
convention for naming / categorizing chunks, so that Git would be able
to handle unknown chunks gracefully.

In PNG format there are various types of chunks.  Quoting Wikipedia:

  Chunk types [in PNG format] are given a four-letter case sensitive
  ASCII type/name; compare FourCC. The case of the different letters in
  the name (bit 5 of the numeric value of the character) is a bit field
  that provides the decoder with some information on the nature of
  chunks it does not recognize.

  The case of the first letter indicates whether the chunk is critical
  or not. If the first letter is uppercase, the chunk is critical; if
  not, the chunk is ancillary. Critical chunks contain information that
  is necessary to read the file. If a decoder encounters a critical
  chunk it does not recognize, it must abort reading the file or supply
  the user with an appropriate warning.

Currently this is solved with the format version.  For given format
version some chunks are necessary (critical); if there would be another
type of chunk that must be understood, we can simply increase format
version. 

  The case of the second letter indicates whether the chunk is "public"
  (either in the specification or the registry of special-purpose public
  chunks) or "private" (not standardised). Uppercase is public and
  lowercase is private. This ensures that public and private chunk names
  can never conflict with each other (although two private chunk names
  could conflict).

For Git this could be "official" and "experimental"... though I don't
see people sharing commit-graph files with experimental chunks that can
be used only by some local unpublished fork of Git.

  The third letter must be uppercase to conform to the PNG
  specification. It is reserved for future expansion. Decoders should
  treat a chunk with a lower case third letter the same as any other
  unrecognised chunk.

In short: reserved.  This could be the fate of all of those 4 flags that
we do not need.

  The case of the fourth letter indicates whether the chunk is safe to
  copy by editors that do not recognize it. If lowercase, the chunk may
  be safely copied regardless of the extent of modifications to the
  file. If uppercase, it may only be copied if the modifications have
  not touched any critical chunks.

If the data contained in the chunks is immutable, then it could be
copied when updating commit-graph file... but what to do with new
commits, with what to fill the data for a new commit for a copied
unknown chunk?  All zeros?  All ones?  Some value defined in the chunk
itself?

Best,
-- 
Jakub Narębski

^ permalink raw reply	[relevance 3%]

* Re: [PATCH] pretty: Add %(trailer:X) to display single trailer
  @ 2018-10-29 14:14  4% ` Jeff King
  0 siblings, 0 replies; 200+ results
From: Jeff King @ 2018-10-29 14:14 UTC (permalink / raw)
  To: Anders Waldenborg; +Cc: git, Junio C Hamano

On Sun, Oct 28, 2018 at 01:50:25PM +0100, Anders Waldenborg wrote:

> This new format placeholder allows displaying only a single
> trailer. The formatting done is similar to what is done for
> --decorate/%d using parentheses and comma separation.

Displaying a single trailer makes sense as a goal. It was one of the
things I considered when working on %(trailers), actually, but I ended
up needing something a bit more flexible (hence the ability to dump the
trailers in a parse-able format, where I feed them to another script).
But your ticket example makes sense for just ordinary log displays.

Junio's review already covered my biggest question, which is why not
something like "%(trailers:key=ticket)". And likewise making things like
comma-separation options.

But my second question is whether we want to provide something more
flexible than the always-parentheses that "%d" provides. That has been a
problem in the past when people want to format the decoration in some
other way.

We have formatting magic for "if this thing is non-empty, then show this
prefix" in the for-each-ref formatter, but I'm not sure that we do in
the commit pretty-printer beyond "% ". I wonder if we could/should add a
a placeholder for "if this thing is non-empty, put in a space and
enclose it in parentheses".

> diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt
> index 6109ef09aa..a46d0c0717 100644
> --- a/Documentation/pretty-formats.txt
> +++ b/Documentation/pretty-formats.txt
> @@ -211,6 +211,10 @@ endif::git-rev-list[]
>    If the `unfold` option is given, behave as if interpret-trailer's
>    `--unfold` option was given.  E.g., `%(trailers:only,unfold)` to do
>    both.
> +- %(trailer:<t>): display the specified trailer in parentheses (like
> +  %d does for refnames). If there are multiple entries of that trailer
> +  they are shown comma separated. If there are no matching trailers
> +  nothing is displayed.

It might be worth specifying how this match is done. I'm thinking
specifically of whether it's case-sensitive, but I wonder if there
should be any allowance for other normalization (e.g., allowing a regex
to match "coauthored-by" and "co-authored-by" or something).

-Peff

^ permalink raw reply	[relevance 4%]

* [PATCH 03/78] config.txt: move core.* to a separate file
  @ 2018-10-27  6:22  4% ` Nguyễn Thái Ngọc Duy
  0 siblings, 0 replies; 200+ results
From: Nguyễn Thái Ngọc Duy @ 2018-10-27  6:22 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/config.txt      | 595 +---------------------------------
 Documentation/config/core.txt | 594 +++++++++++++++++++++++++++++++++
 2 files changed, 595 insertions(+), 594 deletions(-)
 create mode 100644 Documentation/config/core.txt

diff --git a/Documentation/config.txt b/Documentation/config.txt
index 4d2e21b534..2ba70144c7 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -289,600 +289,7 @@ other popular tools, and describe them in your documentation.
 
 include::config/advice.txt[]
 
-core.fileMode::
-	Tells Git if the executable bit of files in the working tree
-	is to be honored.
-+
-Some filesystems lose the executable bit when a file that is
-marked as executable is checked out, or checks out a
-non-executable file with executable bit on.
-linkgit:git-clone[1] or linkgit:git-init[1] probe the filesystem
-to see if it handles the executable bit correctly
-and this variable is automatically set as necessary.
-+
-A repository, however, may be on a filesystem that handles
-the filemode correctly, and this variable is set to 'true'
-when created, but later may be made accessible from another
-environment that loses the filemode (e.g. exporting ext4 via
-CIFS mount, visiting a Cygwin created repository with
-Git for Windows or Eclipse).
-In such a case it may be necessary to set this variable to 'false'.
-See linkgit:git-update-index[1].
-+
-The default is true (when core.filemode is not specified in the config file).
-
-core.hideDotFiles::
-	(Windows-only) If true, mark newly-created directories and files whose
-	name starts with a dot as hidden.  If 'dotGitOnly', only the `.git/`
-	directory is hidden, but no other files starting with a dot.  The
-	default mode is 'dotGitOnly'.
-
-core.ignoreCase::
-	Internal variable which enables various workarounds to enable
-	Git to work better on filesystems that are not case sensitive,
-	like APFS, HFS+, FAT, NTFS, etc. For example, if a directory listing
-	finds "makefile" when Git expects "Makefile", Git will assume
-	it is really the same file, and continue to remember it as
-	"Makefile".
-+
-The default is false, except linkgit:git-clone[1] or linkgit:git-init[1]
-will probe and set core.ignoreCase true if appropriate when the repository
-is created.
-+
-Git relies on the proper configuration of this variable for your operating
-and file system. Modifying this value may result in unexpected behavior.
-
-core.precomposeUnicode::
-	This option is only used by Mac OS implementation of Git.
-	When core.precomposeUnicode=true, Git reverts the unicode decomposition
-	of filenames done by Mac OS. This is useful when sharing a repository
-	between Mac OS and Linux or Windows.
-	(Git for Windows 1.7.10 or higher is needed, or Git under cygwin 1.7).
-	When false, file names are handled fully transparent by Git,
-	which is backward compatible with older versions of Git.
-
-core.protectHFS::
-	If set to true, do not allow checkout of paths that would
-	be considered equivalent to `.git` on an HFS+ filesystem.
-	Defaults to `true` on Mac OS, and `false` elsewhere.
-
-core.protectNTFS::
-	If set to true, do not allow checkout of paths that would
-	cause problems with the NTFS filesystem, e.g. conflict with
-	8.3 "short" names.
-	Defaults to `true` on Windows, and `false` elsewhere.
-
-core.fsmonitor::
-	If set, the value of this variable is used as a command which
-	will identify all files that may have changed since the
-	requested date/time. This information is used to speed up git by
-	avoiding unnecessary processing of files that have not changed.
-	See the "fsmonitor-watchman" section of linkgit:githooks[5].
-
-core.trustctime::
-	If false, the ctime differences between the index and the
-	working tree are ignored; useful when the inode change time
-	is regularly modified by something outside Git (file system
-	crawlers and some backup systems).
-	See linkgit:git-update-index[1]. True by default.
-
-core.splitIndex::
-	If true, the split-index feature of the index will be used.
-	See linkgit:git-update-index[1]. False by default.
-
-core.untrackedCache::
-	Determines what to do about the untracked cache feature of the
-	index. It will be kept, if this variable is unset or set to
-	`keep`. It will automatically be added if set to `true`. And
-	it will automatically be removed, if set to `false`. Before
-	setting it to `true`, you should check that mtime is working
-	properly on your system.
-	See linkgit:git-update-index[1]. `keep` by default.
-
-core.checkStat::
-	When missing or is set to `default`, many fields in the stat
-	structure are checked to detect if a file has been modified
-	since Git looked at it.  When this configuration variable is
-	set to `minimal`, sub-second part of mtime and ctime, the
-	uid and gid of the owner of the file, the inode number (and
-	the device number, if Git was compiled to use it), are
-	excluded from the check among these fields, leaving only the
-	whole-second part of mtime (and ctime, if `core.trustCtime`
-	is set) and the filesize to be checked.
-+
-There are implementations of Git that do not leave usable values in
-some fields (e.g. JGit); by excluding these fields from the
-comparison, the `minimal` mode may help interoperability when the
-same repository is used by these other systems at the same time.
-
-core.quotePath::
-	Commands that output paths (e.g. 'ls-files', 'diff'), will
-	quote "unusual" characters in the pathname by enclosing the
-	pathname in double-quotes and escaping those characters with
-	backslashes in the same way C escapes control characters (e.g.
-	`\t` for TAB, `\n` for LF, `\\` for backslash) or bytes with
-	values larger than 0x80 (e.g. octal `\302\265` for "micro" in
-	UTF-8).  If this variable is set to false, bytes higher than
-	0x80 are not considered "unusual" any more. Double-quotes,
-	backslash and control characters are always escaped regardless
-	of the setting of this variable.  A simple space character is
-	not considered "unusual".  Many commands can output pathnames
-	completely verbatim using the `-z` option. The default value
-	is true.
-
-core.eol::
-	Sets the line ending type to use in the working directory for
-	files that have the `text` property set when core.autocrlf is false.
-	Alternatives are 'lf', 'crlf' and 'native', which uses the platform's
-	native line ending.  The default value is `native`.  See
-	linkgit:gitattributes[5] for more information on end-of-line
-	conversion.
-
-core.safecrlf::
-	If true, makes Git check if converting `CRLF` is reversible when
-	end-of-line conversion is active.  Git will verify if a command
-	modifies a file in the work tree either directly or indirectly.
-	For example, committing a file followed by checking out the
-	same file should yield the original file in the work tree.  If
-	this is not the case for the current setting of
-	`core.autocrlf`, Git will reject the file.  The variable can
-	be set to "warn", in which case Git will only warn about an
-	irreversible conversion but continue the operation.
-+
-CRLF conversion bears a slight chance of corrupting data.
-When it is enabled, Git will convert CRLF to LF during commit and LF to
-CRLF during checkout.  A file that contains a mixture of LF and
-CRLF before the commit cannot be recreated by Git.  For text
-files this is the right thing to do: it corrects line endings
-such that we have only LF line endings in the repository.
-But for binary files that are accidentally classified as text the
-conversion can corrupt data.
-+
-If you recognize such corruption early you can easily fix it by
-setting the conversion type explicitly in .gitattributes.  Right
-after committing you still have the original file in your work
-tree and this file is not yet corrupted.  You can explicitly tell
-Git that this file is binary and Git will handle the file
-appropriately.
-+
-Unfortunately, the desired effect of cleaning up text files with
-mixed line endings and the undesired effect of corrupting binary
-files cannot be distinguished.  In both cases CRLFs are removed
-in an irreversible way.  For text files this is the right thing
-to do because CRLFs are line endings, while for binary files
-converting CRLFs corrupts data.
-+
-Note, this safety check does not mean that a checkout will generate a
-file identical to the original file for a different setting of
-`core.eol` and `core.autocrlf`, but only for the current one.  For
-example, a text file with `LF` would be accepted with `core.eol=lf`
-and could later be checked out with `core.eol=crlf`, in which case the
-resulting file would contain `CRLF`, although the original file
-contained `LF`.  However, in both work trees the line endings would be
-consistent, that is either all `LF` or all `CRLF`, but never mixed.  A
-file with mixed line endings would be reported by the `core.safecrlf`
-mechanism.
-
-core.autocrlf::
-	Setting this variable to "true" is the same as setting
-	the `text` attribute to "auto" on all files and core.eol to "crlf".
-	Set to true if you want to have `CRLF` line endings in your
-	working directory and the repository has LF line endings.
-	This variable can be set to 'input',
-	in which case no output conversion is performed.
-
-core.checkRoundtripEncoding::
-	A comma and/or whitespace separated list of encodings that Git
-	performs UTF-8 round trip checks on if they are used in an
-	`working-tree-encoding` attribute (see linkgit:gitattributes[5]).
-	The default value is `SHIFT-JIS`.
-
-core.symlinks::
-	If false, symbolic links are checked out as small plain files that
-	contain the link text. linkgit:git-update-index[1] and
-	linkgit:git-add[1] will not change the recorded type to regular
-	file. Useful on filesystems like FAT that do not support
-	symbolic links.
-+
-The default is true, except linkgit:git-clone[1] or linkgit:git-init[1]
-will probe and set core.symlinks false if appropriate when the repository
-is created.
-
-core.gitProxy::
-	A "proxy command" to execute (as 'command host port') instead
-	of establishing direct connection to the remote server when
-	using the Git protocol for fetching. If the variable value is
-	in the "COMMAND for DOMAIN" format, the command is applied only
-	on hostnames ending with the specified domain string. This variable
-	may be set multiple times and is matched in the given order;
-	the first match wins.
-+
-Can be overridden by the `GIT_PROXY_COMMAND` environment variable
-(which always applies universally, without the special "for"
-handling).
-+
-The special string `none` can be used as the proxy command to
-specify that no proxy be used for a given domain pattern.
-This is useful for excluding servers inside a firewall from
-proxy use, while defaulting to a common proxy for external domains.
-
-core.sshCommand::
-	If this variable is set, `git fetch` and `git push` will
-	use the specified command instead of `ssh` when they need to
-	connect to a remote system. The command is in the same form as
-	the `GIT_SSH_COMMAND` environment variable and is overridden
-	when the environment variable is set.
-
-core.ignoreStat::
-	If true, Git will avoid using lstat() calls to detect if files have
-	changed by setting the "assume-unchanged" bit for those tracked files
-	which it has updated identically in both the index and working tree.
-+
-When files are modified outside of Git, the user will need to stage
-the modified files explicitly (e.g. see 'Examples' section in
-linkgit:git-update-index[1]).
-Git will not normally detect changes to those files.
-+
-This is useful on systems where lstat() calls are very slow, such as
-CIFS/Microsoft Windows.
-+
-False by default.
-
-core.preferSymlinkRefs::
-	Instead of the default "symref" format for HEAD
-	and other symbolic reference files, use symbolic links.
-	This is sometimes needed to work with old scripts that
-	expect HEAD to be a symbolic link.
-
-core.alternateRefsCommand::
-	When advertising tips of available history from an alternate, use the shell to
-	execute the specified command instead of linkgit:git-for-each-ref[1]. The
-	first argument is the absolute path of the alternate. Output must contain one
-	hex object id per line (i.e., the same as produce by `git for-each-ref
-	--format='%(objectname)'`).
-+
-Note that you cannot generally put `git for-each-ref` directly into the config
-value, as it does not take a repository path as an argument (but you can wrap
-the command above in a shell script).
-
-core.alternateRefsPrefixes::
-	When listing references from an alternate, list only references that begin
-	with the given prefix. Prefixes match as if they were given as arguments to
-	linkgit:git-for-each-ref[1]. To list multiple prefixes, separate them with
-	whitespace. If `core.alternateRefsCommand` is set, setting
-	`core.alternateRefsPrefixes` has no effect.
-
-core.bare::
-	If true this repository is assumed to be 'bare' and has no
-	working directory associated with it.  If this is the case a
-	number of commands that require a working directory will be
-	disabled, such as linkgit:git-add[1] or linkgit:git-merge[1].
-+
-This setting is automatically guessed by linkgit:git-clone[1] or
-linkgit:git-init[1] when the repository was created.  By default a
-repository that ends in "/.git" is assumed to be not bare (bare =
-false), while all other repositories are assumed to be bare (bare
-= true).
-
-core.worktree::
-	Set the path to the root of the working tree.
-	If `GIT_COMMON_DIR` environment variable is set, core.worktree
-	is ignored and not used for determining the root of working tree.
-	This can be overridden by the `GIT_WORK_TREE` environment
-	variable and the `--work-tree` command-line option.
-	The value can be an absolute path or relative to the path to
-	the .git directory, which is either specified by --git-dir
-	or GIT_DIR, or automatically discovered.
-	If --git-dir or GIT_DIR is specified but none of
-	--work-tree, GIT_WORK_TREE and core.worktree is specified,
-	the current working directory is regarded as the top level
-	of your working tree.
-+
-Note that this variable is honored even when set in a configuration
-file in a ".git" subdirectory of a directory and its value differs
-from the latter directory (e.g. "/path/to/.git/config" has
-core.worktree set to "/different/path"), which is most likely a
-misconfiguration.  Running Git commands in the "/path/to" directory will
-still use "/different/path" as the root of the work tree and can cause
-confusion unless you know what you are doing (e.g. you are creating a
-read-only snapshot of the same index to a location different from the
-repository's usual working tree).
-
-core.logAllRefUpdates::
-	Enable the reflog. Updates to a ref <ref> is logged to the file
-	"`$GIT_DIR/logs/<ref>`", by appending the new and old
-	SHA-1, the date/time and the reason of the update, but
-	only when the file exists.  If this configuration
-	variable is set to `true`, missing "`$GIT_DIR/logs/<ref>`"
-	file is automatically created for branch heads (i.e. under
-	`refs/heads/`), remote refs (i.e. under `refs/remotes/`),
-	note refs (i.e. under `refs/notes/`), and the symbolic ref `HEAD`.
-	If it is set to `always`, then a missing reflog is automatically
-	created for any ref under `refs/`.
-+
-This information can be used to determine what commit
-was the tip of a branch "2 days ago".
-+
-This value is true by default in a repository that has
-a working directory associated with it, and false by
-default in a bare repository.
-
-core.repositoryFormatVersion::
-	Internal variable identifying the repository format and layout
-	version.
-
-core.sharedRepository::
-	When 'group' (or 'true'), the repository is made shareable between
-	several users in a group (making sure all the files and objects are
-	group-writable). When 'all' (or 'world' or 'everybody'), the
-	repository will be readable by all users, additionally to being
-	group-shareable. When 'umask' (or 'false'), Git will use permissions
-	reported by umask(2). When '0xxx', where '0xxx' is an octal number,
-	files in the repository will have this mode value. '0xxx' will override
-	user's umask value (whereas the other options will only override
-	requested parts of the user's umask value). Examples: '0660' will make
-	the repo read/write-able for the owner and group, but inaccessible to
-	others (equivalent to 'group' unless umask is e.g. '0022'). '0640' is a
-	repository that is group-readable but not group-writable.
-	See linkgit:git-init[1]. False by default.
-
-core.warnAmbiguousRefs::
-	If true, Git will warn you if the ref name you passed it is ambiguous
-	and might match multiple refs in the repository. True by default.
-
-core.compression::
-	An integer -1..9, indicating a default compression level.
-	-1 is the zlib default. 0 means no compression,
-	and 1..9 are various speed/size tradeoffs, 9 being slowest.
-	If set, this provides a default to other compression variables,
-	such as `core.looseCompression` and `pack.compression`.
-
-core.looseCompression::
-	An integer -1..9, indicating the compression level for objects that
-	are not in a pack file. -1 is the zlib default. 0 means no
-	compression, and 1..9 are various speed/size tradeoffs, 9 being
-	slowest.  If not set,  defaults to core.compression.  If that is
-	not set,  defaults to 1 (best speed).
-
-core.packedGitWindowSize::
-	Number of bytes of a pack file to map into memory in a
-	single mapping operation.  Larger window sizes may allow
-	your system to process a smaller number of large pack files
-	more quickly.  Smaller window sizes will negatively affect
-	performance due to increased calls to the operating system's
-	memory manager, but may improve performance when accessing
-	a large number of large pack files.
-+
-Default is 1 MiB if NO_MMAP was set at compile time, otherwise 32
-MiB on 32 bit platforms and 1 GiB on 64 bit platforms.  This should
-be reasonable for all users/operating systems.  You probably do
-not need to adjust this value.
-+
-Common unit suffixes of 'k', 'm', or 'g' are supported.
-
-core.packedGitLimit::
-	Maximum number of bytes to map simultaneously into memory
-	from pack files.  If Git needs to access more than this many
-	bytes at once to complete an operation it will unmap existing
-	regions to reclaim virtual address space within the process.
-+
-Default is 256 MiB on 32 bit platforms and 32 TiB (effectively
-unlimited) on 64 bit platforms.
-This should be reasonable for all users/operating systems, except on
-the largest projects.  You probably do not need to adjust this value.
-+
-Common unit suffixes of 'k', 'm', or 'g' are supported.
-
-core.deltaBaseCacheLimit::
-	Maximum number of bytes to reserve for caching base objects
-	that may be referenced by multiple deltified objects.  By storing the
-	entire decompressed base objects in a cache Git is able
-	to avoid unpacking and decompressing frequently used base
-	objects multiple times.
-+
-Default is 96 MiB on all platforms.  This should be reasonable
-for all users/operating systems, except on the largest projects.
-You probably do not need to adjust this value.
-+
-Common unit suffixes of 'k', 'm', or 'g' are supported.
-
-core.bigFileThreshold::
-	Files larger than this size are stored deflated, without
-	attempting delta compression.  Storing large files without
-	delta compression avoids excessive memory usage, at the
-	slight expense of increased disk usage. Additionally files
-	larger than this size are always treated as binary.
-+
-Default is 512 MiB on all platforms.  This should be reasonable
-for most projects as source code and other text files can still
-be delta compressed, but larger binary media files won't be.
-+
-Common unit suffixes of 'k', 'm', or 'g' are supported.
-
-core.excludesFile::
-	Specifies the pathname to the file that contains patterns to
-	describe paths that are not meant to be tracked, in addition
-	to '.gitignore' (per-directory) and '.git/info/exclude'.
-	Defaults to `$XDG_CONFIG_HOME/git/ignore`.
-	If `$XDG_CONFIG_HOME` is either not set or empty, `$HOME/.config/git/ignore`
-	is used instead. See linkgit:gitignore[5].
-
-core.askPass::
-	Some commands (e.g. svn and http interfaces) that interactively
-	ask for a password can be told to use an external program given
-	via the value of this variable. Can be overridden by the `GIT_ASKPASS`
-	environment variable. If not set, fall back to the value of the
-	`SSH_ASKPASS` environment variable or, failing that, a simple password
-	prompt. The external program shall be given a suitable prompt as
-	command-line argument and write the password on its STDOUT.
-
-core.attributesFile::
-	In addition to '.gitattributes' (per-directory) and
-	'.git/info/attributes', Git looks into this file for attributes
-	(see linkgit:gitattributes[5]). Path expansions are made the same
-	way as for `core.excludesFile`. Its default value is
-	`$XDG_CONFIG_HOME/git/attributes`. If `$XDG_CONFIG_HOME` is either not
-	set or empty, `$HOME/.config/git/attributes` is used instead.
-
-core.hooksPath::
-	By default Git will look for your hooks in the
-	'$GIT_DIR/hooks' directory. Set this to different path,
-	e.g. '/etc/git/hooks', and Git will try to find your hooks in
-	that directory, e.g. '/etc/git/hooks/pre-receive' instead of
-	in '$GIT_DIR/hooks/pre-receive'.
-+
-The path can be either absolute or relative. A relative path is
-taken as relative to the directory where the hooks are run (see
-the "DESCRIPTION" section of linkgit:githooks[5]).
-+
-This configuration variable is useful in cases where you'd like to
-centrally configure your Git hooks instead of configuring them on a
-per-repository basis, or as a more flexible and centralized
-alternative to having an `init.templateDir` where you've changed
-default hooks.
-
-core.editor::
-	Commands such as `commit` and `tag` that let you edit
-	messages by launching an editor use the value of this
-	variable when it is set, and the environment variable
-	`GIT_EDITOR` is not set.  See linkgit:git-var[1].
-
-core.commentChar::
-	Commands such as `commit` and `tag` that let you edit
-	messages consider a line that begins with this character
-	commented, and removes them after the editor returns
-	(default '#').
-+
-If set to "auto", `git-commit` would select a character that is not
-the beginning character of any line in existing commit messages.
-
-core.filesRefLockTimeout::
-	The length of time, in milliseconds, to retry when trying to
-	lock an individual reference. Value 0 means not to retry at
-	all; -1 means to try indefinitely. Default is 100 (i.e.,
-	retry for 100ms).
-
-core.packedRefsTimeout::
-	The length of time, in milliseconds, to retry when trying to
-	lock the `packed-refs` file. Value 0 means not to retry at
-	all; -1 means to try indefinitely. Default is 1000 (i.e.,
-	retry for 1 second).
-
-core.pager::
-	Text viewer for use by Git commands (e.g., 'less').  The value
-	is meant to be interpreted by the shell.  The order of preference
-	is the `$GIT_PAGER` environment variable, then `core.pager`
-	configuration, then `$PAGER`, and then the default chosen at
-	compile time (usually 'less').
-+
-When the `LESS` environment variable is unset, Git sets it to `FRX`
-(if `LESS` environment variable is set, Git does not change it at
-all).  If you want to selectively override Git's default setting
-for `LESS`, you can set `core.pager` to e.g. `less -S`.  This will
-be passed to the shell by Git, which will translate the final
-command to `LESS=FRX less -S`. The environment does not set the
-`S` option but the command line does, instructing less to truncate
-long lines. Similarly, setting `core.pager` to `less -+F` will
-deactivate the `F` option specified by the environment from the
-command-line, deactivating the "quit if one screen" behavior of
-`less`.  One can specifically activate some flags for particular
-commands: for example, setting `pager.blame` to `less -S` enables
-line truncation only for `git blame`.
-+
-Likewise, when the `LV` environment variable is unset, Git sets it
-to `-c`.  You can override this setting by exporting `LV` with
-another value or setting `core.pager` to `lv +c`.
-
-core.whitespace::
-	A comma separated list of common whitespace problems to
-	notice.  'git diff' will use `color.diff.whitespace` to
-	highlight them, and 'git apply --whitespace=error' will
-	consider them as errors.  You can prefix `-` to disable
-	any of them (e.g. `-trailing-space`):
-+
-* `blank-at-eol` treats trailing whitespaces at the end of the line
-  as an error (enabled by default).
-* `space-before-tab` treats a space character that appears immediately
-  before a tab character in the initial indent part of the line as an
-  error (enabled by default).
-* `indent-with-non-tab` treats a line that is indented with space
-  characters instead of the equivalent tabs as an error (not enabled by
-  default).
-* `tab-in-indent` treats a tab character in the initial indent part of
-  the line as an error (not enabled by default).
-* `blank-at-eof` treats blank lines added at the end of file as an error
-  (enabled by default).
-* `trailing-space` is a short-hand to cover both `blank-at-eol` and
-  `blank-at-eof`.
-* `cr-at-eol` treats a carriage-return at the end of line as
-  part of the line terminator, i.e. with it, `trailing-space`
-  does not trigger if the character before such a carriage-return
-  is not a whitespace (not enabled by default).
-* `tabwidth=<n>` tells how many character positions a tab occupies; this
-  is relevant for `indent-with-non-tab` and when Git fixes `tab-in-indent`
-  errors. The default tab width is 8. Allowed values are 1 to 63.
-
-core.fsyncObjectFiles::
-	This boolean will enable 'fsync()' when writing object files.
-+
-This is a total waste of time and effort on a filesystem that orders
-data writes properly, but can be useful for filesystems that do not use
-journalling (traditional UNIX filesystems) or that only journal metadata
-and not file contents (OS X's HFS+, or Linux ext3 with "data=writeback").
-
-core.preloadIndex::
-	Enable parallel index preload for operations like 'git diff'
-+
-This can speed up operations like 'git diff' and 'git status' especially
-on filesystems like NFS that have weak caching semantics and thus
-relatively high IO latencies.  When enabled, Git will do the
-index comparison to the filesystem data in parallel, allowing
-overlapping IO's.  Defaults to true.
-
-core.createObject::
-	You can set this to 'link', in which case a hardlink followed by
-	a delete of the source are used to make sure that object creation
-	will not overwrite existing objects.
-+
-On some file system/operating system combinations, this is unreliable.
-Set this config setting to 'rename' there; However, This will remove the
-check that makes sure that existing object files will not get overwritten.
-
-core.notesRef::
-	When showing commit messages, also show notes which are stored in
-	the given ref.  The ref must be fully qualified.  If the given
-	ref does not exist, it is not an error but means that no
-	notes should be printed.
-+
-This setting defaults to "refs/notes/commits", and it can be overridden by
-the `GIT_NOTES_REF` environment variable.  See linkgit:git-notes[1].
-
-core.commitGraph::
-	If true, then git will read the commit-graph file (if it exists)
-	to parse the graph structure of commits. Defaults to false. See
-	linkgit:git-commit-graph[1] for more information.
-
-core.useReplaceRefs::
-	If set to `false`, behave as if the `--no-replace-objects`
-	option was given on the command line. See linkgit:git[1] and
-	linkgit:git-replace[1] for more information.
-
-core.multiPackIndex::
-	Use the multi-pack-index file to track multiple packfiles using a
-	single index. See link:technical/multi-pack-index.html[the
-	multi-pack-index design document].
-
-core.sparseCheckout::
-	Enable "sparse checkout" feature. See section "Sparse checkout" in
-	linkgit:git-read-tree[1] for more information.
-
-core.abbrev::
-	Set the length object names are abbreviated to.  If
-	unspecified or set to "auto", an appropriate value is
-	computed based on the approximate number of packed objects
-	in your repository, which hopefully is enough for
-	abbreviated object names to stay unique for some time.
-	The minimum length is 4.
+include::config/core.txt[]
 
 add.ignoreErrors::
 add.ignore-errors (deprecated)::
diff --git a/Documentation/config/core.txt b/Documentation/config/core.txt
new file mode 100644
index 0000000000..73430b635b
--- /dev/null
+++ b/Documentation/config/core.txt
@@ -0,0 +1,594 @@
+core.fileMode::
+	Tells Git if the executable bit of files in the working tree
+	is to be honored.
++
+Some filesystems lose the executable bit when a file that is
+marked as executable is checked out, or checks out a
+non-executable file with executable bit on.
+linkgit:git-clone[1] or linkgit:git-init[1] probe the filesystem
+to see if it handles the executable bit correctly
+and this variable is automatically set as necessary.
++
+A repository, however, may be on a filesystem that handles
+the filemode correctly, and this variable is set to 'true'
+when created, but later may be made accessible from another
+environment that loses the filemode (e.g. exporting ext4 via
+CIFS mount, visiting a Cygwin created repository with
+Git for Windows or Eclipse).
+In such a case it may be necessary to set this variable to 'false'.
+See linkgit:git-update-index[1].
++
+The default is true (when core.filemode is not specified in the config file).
+
+core.hideDotFiles::
+	(Windows-only) If true, mark newly-created directories and files whose
+	name starts with a dot as hidden.  If 'dotGitOnly', only the `.git/`
+	directory is hidden, but no other files starting with a dot.  The
+	default mode is 'dotGitOnly'.
+
+core.ignoreCase::
+	Internal variable which enables various workarounds to enable
+	Git to work better on filesystems that are not case sensitive,
+	like APFS, HFS+, FAT, NTFS, etc. For example, if a directory listing
+	finds "makefile" when Git expects "Makefile", Git will assume
+	it is really the same file, and continue to remember it as
+	"Makefile".
++
+The default is false, except linkgit:git-clone[1] or linkgit:git-init[1]
+will probe and set core.ignoreCase true if appropriate when the repository
+is created.
++
+Git relies on the proper configuration of this variable for your operating
+and file system. Modifying this value may result in unexpected behavior.
+
+core.precomposeUnicode::
+	This option is only used by Mac OS implementation of Git.
+	When core.precomposeUnicode=true, Git reverts the unicode decomposition
+	of filenames done by Mac OS. This is useful when sharing a repository
+	between Mac OS and Linux or Windows.
+	(Git for Windows 1.7.10 or higher is needed, or Git under cygwin 1.7).
+	When false, file names are handled fully transparent by Git,
+	which is backward compatible with older versions of Git.
+
+core.protectHFS::
+	If set to true, do not allow checkout of paths that would
+	be considered equivalent to `.git` on an HFS+ filesystem.
+	Defaults to `true` on Mac OS, and `false` elsewhere.
+
+core.protectNTFS::
+	If set to true, do not allow checkout of paths that would
+	cause problems with the NTFS filesystem, e.g. conflict with
+	8.3 "short" names.
+	Defaults to `true` on Windows, and `false` elsewhere.
+
+core.fsmonitor::
+	If set, the value of this variable is used as a command which
+	will identify all files that may have changed since the
+	requested date/time. This information is used to speed up git by
+	avoiding unnecessary processing of files that have not changed.
+	See the "fsmonitor-watchman" section of linkgit:githooks[5].
+
+core.trustctime::
+	If false, the ctime differences between the index and the
+	working tree are ignored; useful when the inode change time
+	is regularly modified by something outside Git (file system
+	crawlers and some backup systems).
+	See linkgit:git-update-index[1]. True by default.
+
+core.splitIndex::
+	If true, the split-index feature of the index will be used.
+	See linkgit:git-update-index[1]. False by default.
+
+core.untrackedCache::
+	Determines what to do about the untracked cache feature of the
+	index. It will be kept, if this variable is unset or set to
+	`keep`. It will automatically be added if set to `true`. And
+	it will automatically be removed, if set to `false`. Before
+	setting it to `true`, you should check that mtime is working
+	properly on your system.
+	See linkgit:git-update-index[1]. `keep` by default.
+
+core.checkStat::
+	When missing or is set to `default`, many fields in the stat
+	structure are checked to detect if a file has been modified
+	since Git looked at it.  When this configuration variable is
+	set to `minimal`, sub-second part of mtime and ctime, the
+	uid and gid of the owner of the file, the inode number (and
+	the device number, if Git was compiled to use it), are
+	excluded from the check among these fields, leaving only the
+	whole-second part of mtime (and ctime, if `core.trustCtime`
+	is set) and the filesize to be checked.
++
+There are implementations of Git that do not leave usable values in
+some fields (e.g. JGit); by excluding these fields from the
+comparison, the `minimal` mode may help interoperability when the
+same repository is used by these other systems at the same time.
+
+core.quotePath::
+	Commands that output paths (e.g. 'ls-files', 'diff'), will
+	quote "unusual" characters in the pathname by enclosing the
+	pathname in double-quotes and escaping those characters with
+	backslashes in the same way C escapes control characters (e.g.
+	`\t` for TAB, `\n` for LF, `\\` for backslash) or bytes with
+	values larger than 0x80 (e.g. octal `\302\265` for "micro" in
+	UTF-8).  If this variable is set to false, bytes higher than
+	0x80 are not considered "unusual" any more. Double-quotes,
+	backslash and control characters are always escaped regardless
+	of the setting of this variable.  A simple space character is
+	not considered "unusual".  Many commands can output pathnames
+	completely verbatim using the `-z` option. The default value
+	is true.
+
+core.eol::
+	Sets the line ending type to use in the working directory for
+	files that have the `text` property set when core.autocrlf is false.
+	Alternatives are 'lf', 'crlf' and 'native', which uses the platform's
+	native line ending.  The default value is `native`.  See
+	linkgit:gitattributes[5] for more information on end-of-line
+	conversion.
+
+core.safecrlf::
+	If true, makes Git check if converting `CRLF` is reversible when
+	end-of-line conversion is active.  Git will verify if a command
+	modifies a file in the work tree either directly or indirectly.
+	For example, committing a file followed by checking out the
+	same file should yield the original file in the work tree.  If
+	this is not the case for the current setting of
+	`core.autocrlf`, Git will reject the file.  The variable can
+	be set to "warn", in which case Git will only warn about an
+	irreversible conversion but continue the operation.
++
+CRLF conversion bears a slight chance of corrupting data.
+When it is enabled, Git will convert CRLF to LF during commit and LF to
+CRLF during checkout.  A file that contains a mixture of LF and
+CRLF before the commit cannot be recreated by Git.  For text
+files this is the right thing to do: it corrects line endings
+such that we have only LF line endings in the repository.
+But for binary files that are accidentally classified as text the
+conversion can corrupt data.
++
+If you recognize such corruption early you can easily fix it by
+setting the conversion type explicitly in .gitattributes.  Right
+after committing you still have the original file in your work
+tree and this file is not yet corrupted.  You can explicitly tell
+Git that this file is binary and Git will handle the file
+appropriately.
++
+Unfortunately, the desired effect of cleaning up text files with
+mixed line endings and the undesired effect of corrupting binary
+files cannot be distinguished.  In both cases CRLFs are removed
+in an irreversible way.  For text files this is the right thing
+to do because CRLFs are line endings, while for binary files
+converting CRLFs corrupts data.
++
+Note, this safety check does not mean that a checkout will generate a
+file identical to the original file for a different setting of
+`core.eol` and `core.autocrlf`, but only for the current one.  For
+example, a text file with `LF` would be accepted with `core.eol=lf`
+and could later be checked out with `core.eol=crlf`, in which case the
+resulting file would contain `CRLF`, although the original file
+contained `LF`.  However, in both work trees the line endings would be
+consistent, that is either all `LF` or all `CRLF`, but never mixed.  A
+file with mixed line endings would be reported by the `core.safecrlf`
+mechanism.
+
+core.autocrlf::
+	Setting this variable to "true" is the same as setting
+	the `text` attribute to "auto" on all files and core.eol to "crlf".
+	Set to true if you want to have `CRLF` line endings in your
+	working directory and the repository has LF line endings.
+	This variable can be set to 'input',
+	in which case no output conversion is performed.
+
+core.checkRoundtripEncoding::
+	A comma and/or whitespace separated list of encodings that Git
+	performs UTF-8 round trip checks on if they are used in an
+	`working-tree-encoding` attribute (see linkgit:gitattributes[5]).
+	The default value is `SHIFT-JIS`.
+
+core.symlinks::
+	If false, symbolic links are checked out as small plain files that
+	contain the link text. linkgit:git-update-index[1] and
+	linkgit:git-add[1] will not change the recorded type to regular
+	file. Useful on filesystems like FAT that do not support
+	symbolic links.
++
+The default is true, except linkgit:git-clone[1] or linkgit:git-init[1]
+will probe and set core.symlinks false if appropriate when the repository
+is created.
+
+core.gitProxy::
+	A "proxy command" to execute (as 'command host port') instead
+	of establishing direct connection to the remote server when
+	using the Git protocol for fetching. If the variable value is
+	in the "COMMAND for DOMAIN" format, the command is applied only
+	on hostnames ending with the specified domain string. This variable
+	may be set multiple times and is matched in the given order;
+	the first match wins.
++
+Can be overridden by the `GIT_PROXY_COMMAND` environment variable
+(which always applies universally, without the special "for"
+handling).
++
+The special string `none` can be used as the proxy command to
+specify that no proxy be used for a given domain pattern.
+This is useful for excluding servers inside a firewall from
+proxy use, while defaulting to a common proxy for external domains.
+
+core.sshCommand::
+	If this variable is set, `git fetch` and `git push` will
+	use the specified command instead of `ssh` when they need to
+	connect to a remote system. The command is in the same form as
+	the `GIT_SSH_COMMAND` environment variable and is overridden
+	when the environment variable is set.
+
+core.ignoreStat::
+	If true, Git will avoid using lstat() calls to detect if files have
+	changed by setting the "assume-unchanged" bit for those tracked files
+	which it has updated identically in both the index and working tree.
++
+When files are modified outside of Git, the user will need to stage
+the modified files explicitly (e.g. see 'Examples' section in
+linkgit:git-update-index[1]).
+Git will not normally detect changes to those files.
++
+This is useful on systems where lstat() calls are very slow, such as
+CIFS/Microsoft Windows.
++
+False by default.
+
+core.preferSymlinkRefs::
+	Instead of the default "symref" format for HEAD
+	and other symbolic reference files, use symbolic links.
+	This is sometimes needed to work with old scripts that
+	expect HEAD to be a symbolic link.
+
+core.alternateRefsCommand::
+	When advertising tips of available history from an alternate, use the shell to
+	execute the specified command instead of linkgit:git-for-each-ref[1]. The
+	first argument is the absolute path of the alternate. Output must contain one
+	hex object id per line (i.e., the same as produce by `git for-each-ref
+	--format='%(objectname)'`).
++
+Note that you cannot generally put `git for-each-ref` directly into the config
+value, as it does not take a repository path as an argument (but you can wrap
+the command above in a shell script).
+
+core.alternateRefsPrefixes::
+	When listing references from an alternate, list only references that begin
+	with the given prefix. Prefixes match as if they were given as arguments to
+	linkgit:git-for-each-ref[1]. To list multiple prefixes, separate them with
+	whitespace. If `core.alternateRefsCommand` is set, setting
+	`core.alternateRefsPrefixes` has no effect.
+
+core.bare::
+	If true this repository is assumed to be 'bare' and has no
+	working directory associated with it.  If this is the case a
+	number of commands that require a working directory will be
+	disabled, such as linkgit:git-add[1] or linkgit:git-merge[1].
++
+This setting is automatically guessed by linkgit:git-clone[1] or
+linkgit:git-init[1] when the repository was created.  By default a
+repository that ends in "/.git" is assumed to be not bare (bare =
+false), while all other repositories are assumed to be bare (bare
+= true).
+
+core.worktree::
+	Set the path to the root of the working tree.
+	If `GIT_COMMON_DIR` environment variable is set, core.worktree
+	is ignored and not used for determining the root of working tree.
+	This can be overridden by the `GIT_WORK_TREE` environment
+	variable and the `--work-tree` command-line option.
+	The value can be an absolute path or relative to the path to
+	the .git directory, which is either specified by --git-dir
+	or GIT_DIR, or automatically discovered.
+	If --git-dir or GIT_DIR is specified but none of
+	--work-tree, GIT_WORK_TREE and core.worktree is specified,
+	the current working directory is regarded as the top level
+	of your working tree.
++
+Note that this variable is honored even when set in a configuration
+file in a ".git" subdirectory of a directory and its value differs
+from the latter directory (e.g. "/path/to/.git/config" has
+core.worktree set to "/different/path"), which is most likely a
+misconfiguration.  Running Git commands in the "/path/to" directory will
+still use "/different/path" as the root of the work tree and can cause
+confusion unless you know what you are doing (e.g. you are creating a
+read-only snapshot of the same index to a location different from the
+repository's usual working tree).
+
+core.logAllRefUpdates::
+	Enable the reflog. Updates to a ref <ref> is logged to the file
+	"`$GIT_DIR/logs/<ref>`", by appending the new and old
+	SHA-1, the date/time and the reason of the update, but
+	only when the file exists.  If this configuration
+	variable is set to `true`, missing "`$GIT_DIR/logs/<ref>`"
+	file is automatically created for branch heads (i.e. under
+	`refs/heads/`), remote refs (i.e. under `refs/remotes/`),
+	note refs (i.e. under `refs/notes/`), and the symbolic ref `HEAD`.
+	If it is set to `always`, then a missing reflog is automatically
+	created for any ref under `refs/`.
++
+This information can be used to determine what commit
+was the tip of a branch "2 days ago".
++
+This value is true by default in a repository that has
+a working directory associated with it, and false by
+default in a bare repository.
+
+core.repositoryFormatVersion::
+	Internal variable identifying the repository format and layout
+	version.
+
+core.sharedRepository::
+	When 'group' (or 'true'), the repository is made shareable between
+	several users in a group (making sure all the files and objects are
+	group-writable). When 'all' (or 'world' or 'everybody'), the
+	repository will be readable by all users, additionally to being
+	group-shareable. When 'umask' (or 'false'), Git will use permissions
+	reported by umask(2). When '0xxx', where '0xxx' is an octal number,
+	files in the repository will have this mode value. '0xxx' will override
+	user's umask value (whereas the other options will only override
+	requested parts of the user's umask value). Examples: '0660' will make
+	the repo read/write-able for the owner and group, but inaccessible to
+	others (equivalent to 'group' unless umask is e.g. '0022'). '0640' is a
+	repository that is group-readable but not group-writable.
+	See linkgit:git-init[1]. False by default.
+
+core.warnAmbiguousRefs::
+	If true, Git will warn you if the ref name you passed it is ambiguous
+	and might match multiple refs in the repository. True by default.
+
+core.compression::
+	An integer -1..9, indicating a default compression level.
+	-1 is the zlib default. 0 means no compression,
+	and 1..9 are various speed/size tradeoffs, 9 being slowest.
+	If set, this provides a default to other compression variables,
+	such as `core.looseCompression` and `pack.compression`.
+
+core.looseCompression::
+	An integer -1..9, indicating the compression level for objects that
+	are not in a pack file. -1 is the zlib default. 0 means no
+	compression, and 1..9 are various speed/size tradeoffs, 9 being
+	slowest.  If not set,  defaults to core.compression.  If that is
+	not set,  defaults to 1 (best speed).
+
+core.packedGitWindowSize::
+	Number of bytes of a pack file to map into memory in a
+	single mapping operation.  Larger window sizes may allow
+	your system to process a smaller number of large pack files
+	more quickly.  Smaller window sizes will negatively affect
+	performance due to increased calls to the operating system's
+	memory manager, but may improve performance when accessing
+	a large number of large pack files.
++
+Default is 1 MiB if NO_MMAP was set at compile time, otherwise 32
+MiB on 32 bit platforms and 1 GiB on 64 bit platforms.  This should
+be reasonable for all users/operating systems.  You probably do
+not need to adjust this value.
++
+Common unit suffixes of 'k', 'm', or 'g' are supported.
+
+core.packedGitLimit::
+	Maximum number of bytes to map simultaneously into memory
+	from pack files.  If Git needs to access more than this many
+	bytes at once to complete an operation it will unmap existing
+	regions to reclaim virtual address space within the process.
++
+Default is 256 MiB on 32 bit platforms and 32 TiB (effectively
+unlimited) on 64 bit platforms.
+This should be reasonable for all users/operating systems, except on
+the largest projects.  You probably do not need to adjust this value.
++
+Common unit suffixes of 'k', 'm', or 'g' are supported.
+
+core.deltaBaseCacheLimit::
+	Maximum number of bytes to reserve for caching base objects
+	that may be referenced by multiple deltified objects.  By storing the
+	entire decompressed base objects in a cache Git is able
+	to avoid unpacking and decompressing frequently used base
+	objects multiple times.
++
+Default is 96 MiB on all platforms.  This should be reasonable
+for all users/operating systems, except on the largest projects.
+You probably do not need to adjust this value.
++
+Common unit suffixes of 'k', 'm', or 'g' are supported.
+
+core.bigFileThreshold::
+	Files larger than this size are stored deflated, without
+	attempting delta compression.  Storing large files without
+	delta compression avoids excessive memory usage, at the
+	slight expense of increased disk usage. Additionally files
+	larger than this size are always treated as binary.
++
+Default is 512 MiB on all platforms.  This should be reasonable
+for most projects as source code and other text files can still
+be delta compressed, but larger binary media files won't be.
++
+Common unit suffixes of 'k', 'm', or 'g' are supported.
+
+core.excludesFile::
+	Specifies the pathname to the file that contains patterns to
+	describe paths that are not meant to be tracked, in addition
+	to '.gitignore' (per-directory) and '.git/info/exclude'.
+	Defaults to `$XDG_CONFIG_HOME/git/ignore`.
+	If `$XDG_CONFIG_HOME` is either not set or empty, `$HOME/.config/git/ignore`
+	is used instead. See linkgit:gitignore[5].
+
+core.askPass::
+	Some commands (e.g. svn and http interfaces) that interactively
+	ask for a password can be told to use an external program given
+	via the value of this variable. Can be overridden by the `GIT_ASKPASS`
+	environment variable. If not set, fall back to the value of the
+	`SSH_ASKPASS` environment variable or, failing that, a simple password
+	prompt. The external program shall be given a suitable prompt as
+	command-line argument and write the password on its STDOUT.
+
+core.attributesFile::
+	In addition to '.gitattributes' (per-directory) and
+	'.git/info/attributes', Git looks into this file for attributes
+	(see linkgit:gitattributes[5]). Path expansions are made the same
+	way as for `core.excludesFile`. Its default value is
+	`$XDG_CONFIG_HOME/git/attributes`. If `$XDG_CONFIG_HOME` is either not
+	set or empty, `$HOME/.config/git/attributes` is used instead.
+
+core.hooksPath::
+	By default Git will look for your hooks in the
+	'$GIT_DIR/hooks' directory. Set this to different path,
+	e.g. '/etc/git/hooks', and Git will try to find your hooks in
+	that directory, e.g. '/etc/git/hooks/pre-receive' instead of
+	in '$GIT_DIR/hooks/pre-receive'.
++
+The path can be either absolute or relative. A relative path is
+taken as relative to the directory where the hooks are run (see
+the "DESCRIPTION" section of linkgit:githooks[5]).
++
+This configuration variable is useful in cases where you'd like to
+centrally configure your Git hooks instead of configuring them on a
+per-repository basis, or as a more flexible and centralized
+alternative to having an `init.templateDir` where you've changed
+default hooks.
+
+core.editor::
+	Commands such as `commit` and `tag` that let you edit
+	messages by launching an editor use the value of this
+	variable when it is set, and the environment variable
+	`GIT_EDITOR` is not set.  See linkgit:git-var[1].
+
+core.commentChar::
+	Commands such as `commit` and `tag` that let you edit
+	messages consider a line that begins with this character
+	commented, and removes them after the editor returns
+	(default '#').
++
+If set to "auto", `git-commit` would select a character that is not
+the beginning character of any line in existing commit messages.
+
+core.filesRefLockTimeout::
+	The length of time, in milliseconds, to retry when trying to
+	lock an individual reference. Value 0 means not to retry at
+	all; -1 means to try indefinitely. Default is 100 (i.e.,
+	retry for 100ms).
+
+core.packedRefsTimeout::
+	The length of time, in milliseconds, to retry when trying to
+	lock the `packed-refs` file. Value 0 means not to retry at
+	all; -1 means to try indefinitely. Default is 1000 (i.e.,
+	retry for 1 second).
+
+core.pager::
+	Text viewer for use by Git commands (e.g., 'less').  The value
+	is meant to be interpreted by the shell.  The order of preference
+	is the `$GIT_PAGER` environment variable, then `core.pager`
+	configuration, then `$PAGER`, and then the default chosen at
+	compile time (usually 'less').
++
+When the `LESS` environment variable is unset, Git sets it to `FRX`
+(if `LESS` environment variable is set, Git does not change it at
+all).  If you want to selectively override Git's default setting
+for `LESS`, you can set `core.pager` to e.g. `less -S`.  This will
+be passed to the shell by Git, which will translate the final
+command to `LESS=FRX less -S`. The environment does not set the
+`S` option but the command line does, instructing less to truncate
+long lines. Similarly, setting `core.pager` to `less -+F` will
+deactivate the `F` option specified by the environment from the
+command-line, deactivating the "quit if one screen" behavior of
+`less`.  One can specifically activate some flags for particular
+commands: for example, setting `pager.blame` to `less -S` enables
+line truncation only for `git blame`.
++
+Likewise, when the `LV` environment variable is unset, Git sets it
+to `-c`.  You can override this setting by exporting `LV` with
+another value or setting `core.pager` to `lv +c`.
+
+core.whitespace::
+	A comma separated list of common whitespace problems to
+	notice.  'git diff' will use `color.diff.whitespace` to
+	highlight them, and 'git apply --whitespace=error' will
+	consider them as errors.  You can prefix `-` to disable
+	any of them (e.g. `-trailing-space`):
++
+* `blank-at-eol` treats trailing whitespaces at the end of the line
+  as an error (enabled by default).
+* `space-before-tab` treats a space character that appears immediately
+  before a tab character in the initial indent part of the line as an
+  error (enabled by default).
+* `indent-with-non-tab` treats a line that is indented with space
+  characters instead of the equivalent tabs as an error (not enabled by
+  default).
+* `tab-in-indent` treats a tab character in the initial indent part of
+  the line as an error (not enabled by default).
+* `blank-at-eof` treats blank lines added at the end of file as an error
+  (enabled by default).
+* `trailing-space` is a short-hand to cover both `blank-at-eol` and
+  `blank-at-eof`.
+* `cr-at-eol` treats a carriage-return at the end of line as
+  part of the line terminator, i.e. with it, `trailing-space`
+  does not trigger if the character before such a carriage-return
+  is not a whitespace (not enabled by default).
+* `tabwidth=<n>` tells how many character positions a tab occupies; this
+  is relevant for `indent-with-non-tab` and when Git fixes `tab-in-indent`
+  errors. The default tab width is 8. Allowed values are 1 to 63.
+
+core.fsyncObjectFiles::
+	This boolean will enable 'fsync()' when writing object files.
++
+This is a total waste of time and effort on a filesystem that orders
+data writes properly, but can be useful for filesystems that do not use
+journalling (traditional UNIX filesystems) or that only journal metadata
+and not file contents (OS X's HFS+, or Linux ext3 with "data=writeback").
+
+core.preloadIndex::
+	Enable parallel index preload for operations like 'git diff'
++
+This can speed up operations like 'git diff' and 'git status' especially
+on filesystems like NFS that have weak caching semantics and thus
+relatively high IO latencies.  When enabled, Git will do the
+index comparison to the filesystem data in parallel, allowing
+overlapping IO's.  Defaults to true.
+
+core.createObject::
+	You can set this to 'link', in which case a hardlink followed by
+	a delete of the source are used to make sure that object creation
+	will not overwrite existing objects.
++
+On some file system/operating system combinations, this is unreliable.
+Set this config setting to 'rename' there; However, This will remove the
+check that makes sure that existing object files will not get overwritten.
+
+core.notesRef::
+	When showing commit messages, also show notes which are stored in
+	the given ref.  The ref must be fully qualified.  If the given
+	ref does not exist, it is not an error but means that no
+	notes should be printed.
++
+This setting defaults to "refs/notes/commits", and it can be overridden by
+the `GIT_NOTES_REF` environment variable.  See linkgit:git-notes[1].
+
+core.commitGraph::
+	If true, then git will read the commit-graph file (if it exists)
+	to parse the graph structure of commits. Defaults to false. See
+	linkgit:git-commit-graph[1] for more information.
+
+core.useReplaceRefs::
+	If set to `false`, behave as if the `--no-replace-objects`
+	option was given on the command line. See linkgit:git[1] and
+	linkgit:git-replace[1] for more information.
+
+core.multiPackIndex::
+	Use the multi-pack-index file to track multiple packfiles using a
+	single index. See link:technical/multi-pack-index.html[the
+	multi-pack-index design document].
+
+core.sparseCheckout::
+	Enable "sparse checkout" feature. See section "Sparse checkout" in
+	linkgit:git-read-tree[1] for more information.
+
+core.abbrev::
+	Set the length object names are abbreviated to.  If
+	unspecified or set to "auto", an appropriate value is
+	computed based on the approximate number of packed objects
+	in your repository, which hopefully is enough for
+	abbreviated object names to stay unique for some time.
+	The minimum length is 4.
-- 
2.19.1.647.g708186aaf9


^ permalink raw reply related	[relevance 4%]

* [PATCH 02/59] config.txt: move core.* to a separate file
  @ 2018-10-20 12:37  4% ` Nguyễn Thái Ngọc Duy
  0 siblings, 0 replies; 200+ results
From: Nguyễn Thái Ngọc Duy @ 2018-10-20 12:37 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/config.txt      | 595 +---------------------------------
 Documentation/core-config.txt | 594 +++++++++++++++++++++++++++++++++
 2 files changed, 595 insertions(+), 594 deletions(-)
 create mode 100644 Documentation/core-config.txt

diff --git a/Documentation/config.txt b/Documentation/config.txt
index a0b0ef582f..27c2a211c0 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -289,600 +289,7 @@ other popular tools, and describe them in your documentation.
 
 include::advice-config.txt[]
 
-core.fileMode::
-	Tells Git if the executable bit of files in the working tree
-	is to be honored.
-+
-Some filesystems lose the executable bit when a file that is
-marked as executable is checked out, or checks out a
-non-executable file with executable bit on.
-linkgit:git-clone[1] or linkgit:git-init[1] probe the filesystem
-to see if it handles the executable bit correctly
-and this variable is automatically set as necessary.
-+
-A repository, however, may be on a filesystem that handles
-the filemode correctly, and this variable is set to 'true'
-when created, but later may be made accessible from another
-environment that loses the filemode (e.g. exporting ext4 via
-CIFS mount, visiting a Cygwin created repository with
-Git for Windows or Eclipse).
-In such a case it may be necessary to set this variable to 'false'.
-See linkgit:git-update-index[1].
-+
-The default is true (when core.filemode is not specified in the config file).
-
-core.hideDotFiles::
-	(Windows-only) If true, mark newly-created directories and files whose
-	name starts with a dot as hidden.  If 'dotGitOnly', only the `.git/`
-	directory is hidden, but no other files starting with a dot.  The
-	default mode is 'dotGitOnly'.
-
-core.ignoreCase::
-	Internal variable which enables various workarounds to enable
-	Git to work better on filesystems that are not case sensitive,
-	like APFS, HFS+, FAT, NTFS, etc. For example, if a directory listing
-	finds "makefile" when Git expects "Makefile", Git will assume
-	it is really the same file, and continue to remember it as
-	"Makefile".
-+
-The default is false, except linkgit:git-clone[1] or linkgit:git-init[1]
-will probe and set core.ignoreCase true if appropriate when the repository
-is created.
-+
-Git relies on the proper configuration of this variable for your operating
-and file system. Modifying this value may result in unexpected behavior.
-
-core.precomposeUnicode::
-	This option is only used by Mac OS implementation of Git.
-	When core.precomposeUnicode=true, Git reverts the unicode decomposition
-	of filenames done by Mac OS. This is useful when sharing a repository
-	between Mac OS and Linux or Windows.
-	(Git for Windows 1.7.10 or higher is needed, or Git under cygwin 1.7).
-	When false, file names are handled fully transparent by Git,
-	which is backward compatible with older versions of Git.
-
-core.protectHFS::
-	If set to true, do not allow checkout of paths that would
-	be considered equivalent to `.git` on an HFS+ filesystem.
-	Defaults to `true` on Mac OS, and `false` elsewhere.
-
-core.protectNTFS::
-	If set to true, do not allow checkout of paths that would
-	cause problems with the NTFS filesystem, e.g. conflict with
-	8.3 "short" names.
-	Defaults to `true` on Windows, and `false` elsewhere.
-
-core.fsmonitor::
-	If set, the value of this variable is used as a command which
-	will identify all files that may have changed since the
-	requested date/time. This information is used to speed up git by
-	avoiding unnecessary processing of files that have not changed.
-	See the "fsmonitor-watchman" section of linkgit:githooks[5].
-
-core.trustctime::
-	If false, the ctime differences between the index and the
-	working tree are ignored; useful when the inode change time
-	is regularly modified by something outside Git (file system
-	crawlers and some backup systems).
-	See linkgit:git-update-index[1]. True by default.
-
-core.splitIndex::
-	If true, the split-index feature of the index will be used.
-	See linkgit:git-update-index[1]. False by default.
-
-core.untrackedCache::
-	Determines what to do about the untracked cache feature of the
-	index. It will be kept, if this variable is unset or set to
-	`keep`. It will automatically be added if set to `true`. And
-	it will automatically be removed, if set to `false`. Before
-	setting it to `true`, you should check that mtime is working
-	properly on your system.
-	See linkgit:git-update-index[1]. `keep` by default.
-
-core.checkStat::
-	When missing or is set to `default`, many fields in the stat
-	structure are checked to detect if a file has been modified
-	since Git looked at it.  When this configuration variable is
-	set to `minimal`, sub-second part of mtime and ctime, the
-	uid and gid of the owner of the file, the inode number (and
-	the device number, if Git was compiled to use it), are
-	excluded from the check among these fields, leaving only the
-	whole-second part of mtime (and ctime, if `core.trustCtime`
-	is set) and the filesize to be checked.
-+
-There are implementations of Git that do not leave usable values in
-some fields (e.g. JGit); by excluding these fields from the
-comparison, the `minimal` mode may help interoperability when the
-same repository is used by these other systems at the same time.
-
-core.quotePath::
-	Commands that output paths (e.g. 'ls-files', 'diff'), will
-	quote "unusual" characters in the pathname by enclosing the
-	pathname in double-quotes and escaping those characters with
-	backslashes in the same way C escapes control characters (e.g.
-	`\t` for TAB, `\n` for LF, `\\` for backslash) or bytes with
-	values larger than 0x80 (e.g. octal `\302\265` for "micro" in
-	UTF-8).  If this variable is set to false, bytes higher than
-	0x80 are not considered "unusual" any more. Double-quotes,
-	backslash and control characters are always escaped regardless
-	of the setting of this variable.  A simple space character is
-	not considered "unusual".  Many commands can output pathnames
-	completely verbatim using the `-z` option. The default value
-	is true.
-
-core.eol::
-	Sets the line ending type to use in the working directory for
-	files that have the `text` property set when core.autocrlf is false.
-	Alternatives are 'lf', 'crlf' and 'native', which uses the platform's
-	native line ending.  The default value is `native`.  See
-	linkgit:gitattributes[5] for more information on end-of-line
-	conversion.
-
-core.safecrlf::
-	If true, makes Git check if converting `CRLF` is reversible when
-	end-of-line conversion is active.  Git will verify if a command
-	modifies a file in the work tree either directly or indirectly.
-	For example, committing a file followed by checking out the
-	same file should yield the original file in the work tree.  If
-	this is not the case for the current setting of
-	`core.autocrlf`, Git will reject the file.  The variable can
-	be set to "warn", in which case Git will only warn about an
-	irreversible conversion but continue the operation.
-+
-CRLF conversion bears a slight chance of corrupting data.
-When it is enabled, Git will convert CRLF to LF during commit and LF to
-CRLF during checkout.  A file that contains a mixture of LF and
-CRLF before the commit cannot be recreated by Git.  For text
-files this is the right thing to do: it corrects line endings
-such that we have only LF line endings in the repository.
-But for binary files that are accidentally classified as text the
-conversion can corrupt data.
-+
-If you recognize such corruption early you can easily fix it by
-setting the conversion type explicitly in .gitattributes.  Right
-after committing you still have the original file in your work
-tree and this file is not yet corrupted.  You can explicitly tell
-Git that this file is binary and Git will handle the file
-appropriately.
-+
-Unfortunately, the desired effect of cleaning up text files with
-mixed line endings and the undesired effect of corrupting binary
-files cannot be distinguished.  In both cases CRLFs are removed
-in an irreversible way.  For text files this is the right thing
-to do because CRLFs are line endings, while for binary files
-converting CRLFs corrupts data.
-+
-Note, this safety check does not mean that a checkout will generate a
-file identical to the original file for a different setting of
-`core.eol` and `core.autocrlf`, but only for the current one.  For
-example, a text file with `LF` would be accepted with `core.eol=lf`
-and could later be checked out with `core.eol=crlf`, in which case the
-resulting file would contain `CRLF`, although the original file
-contained `LF`.  However, in both work trees the line endings would be
-consistent, that is either all `LF` or all `CRLF`, but never mixed.  A
-file with mixed line endings would be reported by the `core.safecrlf`
-mechanism.
-
-core.autocrlf::
-	Setting this variable to "true" is the same as setting
-	the `text` attribute to "auto" on all files and core.eol to "crlf".
-	Set to true if you want to have `CRLF` line endings in your
-	working directory and the repository has LF line endings.
-	This variable can be set to 'input',
-	in which case no output conversion is performed.
-
-core.checkRoundtripEncoding::
-	A comma and/or whitespace separated list of encodings that Git
-	performs UTF-8 round trip checks on if they are used in an
-	`working-tree-encoding` attribute (see linkgit:gitattributes[5]).
-	The default value is `SHIFT-JIS`.
-
-core.symlinks::
-	If false, symbolic links are checked out as small plain files that
-	contain the link text. linkgit:git-update-index[1] and
-	linkgit:git-add[1] will not change the recorded type to regular
-	file. Useful on filesystems like FAT that do not support
-	symbolic links.
-+
-The default is true, except linkgit:git-clone[1] or linkgit:git-init[1]
-will probe and set core.symlinks false if appropriate when the repository
-is created.
-
-core.gitProxy::
-	A "proxy command" to execute (as 'command host port') instead
-	of establishing direct connection to the remote server when
-	using the Git protocol for fetching. If the variable value is
-	in the "COMMAND for DOMAIN" format, the command is applied only
-	on hostnames ending with the specified domain string. This variable
-	may be set multiple times and is matched in the given order;
-	the first match wins.
-+
-Can be overridden by the `GIT_PROXY_COMMAND` environment variable
-(which always applies universally, without the special "for"
-handling).
-+
-The special string `none` can be used as the proxy command to
-specify that no proxy be used for a given domain pattern.
-This is useful for excluding servers inside a firewall from
-proxy use, while defaulting to a common proxy for external domains.
-
-core.sshCommand::
-	If this variable is set, `git fetch` and `git push` will
-	use the specified command instead of `ssh` when they need to
-	connect to a remote system. The command is in the same form as
-	the `GIT_SSH_COMMAND` environment variable and is overridden
-	when the environment variable is set.
-
-core.ignoreStat::
-	If true, Git will avoid using lstat() calls to detect if files have
-	changed by setting the "assume-unchanged" bit for those tracked files
-	which it has updated identically in both the index and working tree.
-+
-When files are modified outside of Git, the user will need to stage
-the modified files explicitly (e.g. see 'Examples' section in
-linkgit:git-update-index[1]).
-Git will not normally detect changes to those files.
-+
-This is useful on systems where lstat() calls are very slow, such as
-CIFS/Microsoft Windows.
-+
-False by default.
-
-core.preferSymlinkRefs::
-	Instead of the default "symref" format for HEAD
-	and other symbolic reference files, use symbolic links.
-	This is sometimes needed to work with old scripts that
-	expect HEAD to be a symbolic link.
-
-core.alternateRefsCommand::
-	When advertising tips of available history from an alternate, use the shell to
-	execute the specified command instead of linkgit:git-for-each-ref[1]. The
-	first argument is the absolute path of the alternate. Output must contain one
-	hex object id per line (i.e., the same as produce by `git for-each-ref
-	--format='%(objectname)'`).
-+
-Note that you cannot generally put `git for-each-ref` directly into the config
-value, as it does not take a repository path as an argument (but you can wrap
-the command above in a shell script).
-
-core.alternateRefsPrefixes::
-	When listing references from an alternate, list only references that begin
-	with the given prefix. Prefixes match as if they were given as arguments to
-	linkgit:git-for-each-ref[1]. To list multiple prefixes, separate them with
-	whitespace. If `core.alternateRefsCommand` is set, setting
-	`core.alternateRefsPrefixes` has no effect.
-
-core.bare::
-	If true this repository is assumed to be 'bare' and has no
-	working directory associated with it.  If this is the case a
-	number of commands that require a working directory will be
-	disabled, such as linkgit:git-add[1] or linkgit:git-merge[1].
-+
-This setting is automatically guessed by linkgit:git-clone[1] or
-linkgit:git-init[1] when the repository was created.  By default a
-repository that ends in "/.git" is assumed to be not bare (bare =
-false), while all other repositories are assumed to be bare (bare
-= true).
-
-core.worktree::
-	Set the path to the root of the working tree.
-	If `GIT_COMMON_DIR` environment variable is set, core.worktree
-	is ignored and not used for determining the root of working tree.
-	This can be overridden by the `GIT_WORK_TREE` environment
-	variable and the `--work-tree` command-line option.
-	The value can be an absolute path or relative to the path to
-	the .git directory, which is either specified by --git-dir
-	or GIT_DIR, or automatically discovered.
-	If --git-dir or GIT_DIR is specified but none of
-	--work-tree, GIT_WORK_TREE and core.worktree is specified,
-	the current working directory is regarded as the top level
-	of your working tree.
-+
-Note that this variable is honored even when set in a configuration
-file in a ".git" subdirectory of a directory and its value differs
-from the latter directory (e.g. "/path/to/.git/config" has
-core.worktree set to "/different/path"), which is most likely a
-misconfiguration.  Running Git commands in the "/path/to" directory will
-still use "/different/path" as the root of the work tree and can cause
-confusion unless you know what you are doing (e.g. you are creating a
-read-only snapshot of the same index to a location different from the
-repository's usual working tree).
-
-core.logAllRefUpdates::
-	Enable the reflog. Updates to a ref <ref> is logged to the file
-	"`$GIT_DIR/logs/<ref>`", by appending the new and old
-	SHA-1, the date/time and the reason of the update, but
-	only when the file exists.  If this configuration
-	variable is set to `true`, missing "`$GIT_DIR/logs/<ref>`"
-	file is automatically created for branch heads (i.e. under
-	`refs/heads/`), remote refs (i.e. under `refs/remotes/`),
-	note refs (i.e. under `refs/notes/`), and the symbolic ref `HEAD`.
-	If it is set to `always`, then a missing reflog is automatically
-	created for any ref under `refs/`.
-+
-This information can be used to determine what commit
-was the tip of a branch "2 days ago".
-+
-This value is true by default in a repository that has
-a working directory associated with it, and false by
-default in a bare repository.
-
-core.repositoryFormatVersion::
-	Internal variable identifying the repository format and layout
-	version.
-
-core.sharedRepository::
-	When 'group' (or 'true'), the repository is made shareable between
-	several users in a group (making sure all the files and objects are
-	group-writable). When 'all' (or 'world' or 'everybody'), the
-	repository will be readable by all users, additionally to being
-	group-shareable. When 'umask' (or 'false'), Git will use permissions
-	reported by umask(2). When '0xxx', where '0xxx' is an octal number,
-	files in the repository will have this mode value. '0xxx' will override
-	user's umask value (whereas the other options will only override
-	requested parts of the user's umask value). Examples: '0660' will make
-	the repo read/write-able for the owner and group, but inaccessible to
-	others (equivalent to 'group' unless umask is e.g. '0022'). '0640' is a
-	repository that is group-readable but not group-writable.
-	See linkgit:git-init[1]. False by default.
-
-core.warnAmbiguousRefs::
-	If true, Git will warn you if the ref name you passed it is ambiguous
-	and might match multiple refs in the repository. True by default.
-
-core.compression::
-	An integer -1..9, indicating a default compression level.
-	-1 is the zlib default. 0 means no compression,
-	and 1..9 are various speed/size tradeoffs, 9 being slowest.
-	If set, this provides a default to other compression variables,
-	such as `core.looseCompression` and `pack.compression`.
-
-core.looseCompression::
-	An integer -1..9, indicating the compression level for objects that
-	are not in a pack file. -1 is the zlib default. 0 means no
-	compression, and 1..9 are various speed/size tradeoffs, 9 being
-	slowest.  If not set,  defaults to core.compression.  If that is
-	not set,  defaults to 1 (best speed).
-
-core.packedGitWindowSize::
-	Number of bytes of a pack file to map into memory in a
-	single mapping operation.  Larger window sizes may allow
-	your system to process a smaller number of large pack files
-	more quickly.  Smaller window sizes will negatively affect
-	performance due to increased calls to the operating system's
-	memory manager, but may improve performance when accessing
-	a large number of large pack files.
-+
-Default is 1 MiB if NO_MMAP was set at compile time, otherwise 32
-MiB on 32 bit platforms and 1 GiB on 64 bit platforms.  This should
-be reasonable for all users/operating systems.  You probably do
-not need to adjust this value.
-+
-Common unit suffixes of 'k', 'm', or 'g' are supported.
-
-core.packedGitLimit::
-	Maximum number of bytes to map simultaneously into memory
-	from pack files.  If Git needs to access more than this many
-	bytes at once to complete an operation it will unmap existing
-	regions to reclaim virtual address space within the process.
-+
-Default is 256 MiB on 32 bit platforms and 32 TiB (effectively
-unlimited) on 64 bit platforms.
-This should be reasonable for all users/operating systems, except on
-the largest projects.  You probably do not need to adjust this value.
-+
-Common unit suffixes of 'k', 'm', or 'g' are supported.
-
-core.deltaBaseCacheLimit::
-	Maximum number of bytes to reserve for caching base objects
-	that may be referenced by multiple deltified objects.  By storing the
-	entire decompressed base objects in a cache Git is able
-	to avoid unpacking and decompressing frequently used base
-	objects multiple times.
-+
-Default is 96 MiB on all platforms.  This should be reasonable
-for all users/operating systems, except on the largest projects.
-You probably do not need to adjust this value.
-+
-Common unit suffixes of 'k', 'm', or 'g' are supported.
-
-core.bigFileThreshold::
-	Files larger than this size are stored deflated, without
-	attempting delta compression.  Storing large files without
-	delta compression avoids excessive memory usage, at the
-	slight expense of increased disk usage. Additionally files
-	larger than this size are always treated as binary.
-+
-Default is 512 MiB on all platforms.  This should be reasonable
-for most projects as source code and other text files can still
-be delta compressed, but larger binary media files won't be.
-+
-Common unit suffixes of 'k', 'm', or 'g' are supported.
-
-core.excludesFile::
-	Specifies the pathname to the file that contains patterns to
-	describe paths that are not meant to be tracked, in addition
-	to '.gitignore' (per-directory) and '.git/info/exclude'.
-	Defaults to `$XDG_CONFIG_HOME/git/ignore`.
-	If `$XDG_CONFIG_HOME` is either not set or empty, `$HOME/.config/git/ignore`
-	is used instead. See linkgit:gitignore[5].
-
-core.askPass::
-	Some commands (e.g. svn and http interfaces) that interactively
-	ask for a password can be told to use an external program given
-	via the value of this variable. Can be overridden by the `GIT_ASKPASS`
-	environment variable. If not set, fall back to the value of the
-	`SSH_ASKPASS` environment variable or, failing that, a simple password
-	prompt. The external program shall be given a suitable prompt as
-	command-line argument and write the password on its STDOUT.
-
-core.attributesFile::
-	In addition to '.gitattributes' (per-directory) and
-	'.git/info/attributes', Git looks into this file for attributes
-	(see linkgit:gitattributes[5]). Path expansions are made the same
-	way as for `core.excludesFile`. Its default value is
-	`$XDG_CONFIG_HOME/git/attributes`. If `$XDG_CONFIG_HOME` is either not
-	set or empty, `$HOME/.config/git/attributes` is used instead.
-
-core.hooksPath::
-	By default Git will look for your hooks in the
-	'$GIT_DIR/hooks' directory. Set this to different path,
-	e.g. '/etc/git/hooks', and Git will try to find your hooks in
-	that directory, e.g. '/etc/git/hooks/pre-receive' instead of
-	in '$GIT_DIR/hooks/pre-receive'.
-+
-The path can be either absolute or relative. A relative path is
-taken as relative to the directory where the hooks are run (see
-the "DESCRIPTION" section of linkgit:githooks[5]).
-+
-This configuration variable is useful in cases where you'd like to
-centrally configure your Git hooks instead of configuring them on a
-per-repository basis, or as a more flexible and centralized
-alternative to having an `init.templateDir` where you've changed
-default hooks.
-
-core.editor::
-	Commands such as `commit` and `tag` that let you edit
-	messages by launching an editor use the value of this
-	variable when it is set, and the environment variable
-	`GIT_EDITOR` is not set.  See linkgit:git-var[1].
-
-core.commentChar::
-	Commands such as `commit` and `tag` that let you edit
-	messages consider a line that begins with this character
-	commented, and removes them after the editor returns
-	(default '#').
-+
-If set to "auto", `git-commit` would select a character that is not
-the beginning character of any line in existing commit messages.
-
-core.filesRefLockTimeout::
-	The length of time, in milliseconds, to retry when trying to
-	lock an individual reference. Value 0 means not to retry at
-	all; -1 means to try indefinitely. Default is 100 (i.e.,
-	retry for 100ms).
-
-core.packedRefsTimeout::
-	The length of time, in milliseconds, to retry when trying to
-	lock the `packed-refs` file. Value 0 means not to retry at
-	all; -1 means to try indefinitely. Default is 1000 (i.e.,
-	retry for 1 second).
-
-core.pager::
-	Text viewer for use by Git commands (e.g., 'less').  The value
-	is meant to be interpreted by the shell.  The order of preference
-	is the `$GIT_PAGER` environment variable, then `core.pager`
-	configuration, then `$PAGER`, and then the default chosen at
-	compile time (usually 'less').
-+
-When the `LESS` environment variable is unset, Git sets it to `FRX`
-(if `LESS` environment variable is set, Git does not change it at
-all).  If you want to selectively override Git's default setting
-for `LESS`, you can set `core.pager` to e.g. `less -S`.  This will
-be passed to the shell by Git, which will translate the final
-command to `LESS=FRX less -S`. The environment does not set the
-`S` option but the command line does, instructing less to truncate
-long lines. Similarly, setting `core.pager` to `less -+F` will
-deactivate the `F` option specified by the environment from the
-command-line, deactivating the "quit if one screen" behavior of
-`less`.  One can specifically activate some flags for particular
-commands: for example, setting `pager.blame` to `less -S` enables
-line truncation only for `git blame`.
-+
-Likewise, when the `LV` environment variable is unset, Git sets it
-to `-c`.  You can override this setting by exporting `LV` with
-another value or setting `core.pager` to `lv +c`.
-
-core.whitespace::
-	A comma separated list of common whitespace problems to
-	notice.  'git diff' will use `color.diff.whitespace` to
-	highlight them, and 'git apply --whitespace=error' will
-	consider them as errors.  You can prefix `-` to disable
-	any of them (e.g. `-trailing-space`):
-+
-* `blank-at-eol` treats trailing whitespaces at the end of the line
-  as an error (enabled by default).
-* `space-before-tab` treats a space character that appears immediately
-  before a tab character in the initial indent part of the line as an
-  error (enabled by default).
-* `indent-with-non-tab` treats a line that is indented with space
-  characters instead of the equivalent tabs as an error (not enabled by
-  default).
-* `tab-in-indent` treats a tab character in the initial indent part of
-  the line as an error (not enabled by default).
-* `blank-at-eof` treats blank lines added at the end of file as an error
-  (enabled by default).
-* `trailing-space` is a short-hand to cover both `blank-at-eol` and
-  `blank-at-eof`.
-* `cr-at-eol` treats a carriage-return at the end of line as
-  part of the line terminator, i.e. with it, `trailing-space`
-  does not trigger if the character before such a carriage-return
-  is not a whitespace (not enabled by default).
-* `tabwidth=<n>` tells how many character positions a tab occupies; this
-  is relevant for `indent-with-non-tab` and when Git fixes `tab-in-indent`
-  errors. The default tab width is 8. Allowed values are 1 to 63.
-
-core.fsyncObjectFiles::
-	This boolean will enable 'fsync()' when writing object files.
-+
-This is a total waste of time and effort on a filesystem that orders
-data writes properly, but can be useful for filesystems that do not use
-journalling (traditional UNIX filesystems) or that only journal metadata
-and not file contents (OS X's HFS+, or Linux ext3 with "data=writeback").
-
-core.preloadIndex::
-	Enable parallel index preload for operations like 'git diff'
-+
-This can speed up operations like 'git diff' and 'git status' especially
-on filesystems like NFS that have weak caching semantics and thus
-relatively high IO latencies.  When enabled, Git will do the
-index comparison to the filesystem data in parallel, allowing
-overlapping IO's.  Defaults to true.
-
-core.createObject::
-	You can set this to 'link', in which case a hardlink followed by
-	a delete of the source are used to make sure that object creation
-	will not overwrite existing objects.
-+
-On some file system/operating system combinations, this is unreliable.
-Set this config setting to 'rename' there; However, This will remove the
-check that makes sure that existing object files will not get overwritten.
-
-core.notesRef::
-	When showing commit messages, also show notes which are stored in
-	the given ref.  The ref must be fully qualified.  If the given
-	ref does not exist, it is not an error but means that no
-	notes should be printed.
-+
-This setting defaults to "refs/notes/commits", and it can be overridden by
-the `GIT_NOTES_REF` environment variable.  See linkgit:git-notes[1].
-
-core.commitGraph::
-	If true, then git will read the commit-graph file (if it exists)
-	to parse the graph structure of commits. Defaults to false. See
-	linkgit:git-commit-graph[1] for more information.
-
-core.useReplaceRefs::
-	If set to `false`, behave as if the `--no-replace-objects`
-	option was given on the command line. See linkgit:git[1] and
-	linkgit:git-replace[1] for more information.
-
-core.multiPackIndex::
-	Use the multi-pack-index file to track multiple packfiles using a
-	single index. See link:technical/multi-pack-index.html[the
-	multi-pack-index design document].
-
-core.sparseCheckout::
-	Enable "sparse checkout" feature. See section "Sparse checkout" in
-	linkgit:git-read-tree[1] for more information.
-
-core.abbrev::
-	Set the length object names are abbreviated to.  If
-	unspecified or set to "auto", an appropriate value is
-	computed based on the approximate number of packed objects
-	in your repository, which hopefully is enough for
-	abbreviated object names to stay unique for some time.
-	The minimum length is 4.
+include::core-config.txt[]
 
 add.ignoreErrors::
 add.ignore-errors (deprecated)::
diff --git a/Documentation/core-config.txt b/Documentation/core-config.txt
new file mode 100644
index 0000000000..73430b635b
--- /dev/null
+++ b/Documentation/core-config.txt
@@ -0,0 +1,594 @@
+core.fileMode::
+	Tells Git if the executable bit of files in the working tree
+	is to be honored.
++
+Some filesystems lose the executable bit when a file that is
+marked as executable is checked out, or checks out a
+non-executable file with executable bit on.
+linkgit:git-clone[1] or linkgit:git-init[1] probe the filesystem
+to see if it handles the executable bit correctly
+and this variable is automatically set as necessary.
++
+A repository, however, may be on a filesystem that handles
+the filemode correctly, and this variable is set to 'true'
+when created, but later may be made accessible from another
+environment that loses the filemode (e.g. exporting ext4 via
+CIFS mount, visiting a Cygwin created repository with
+Git for Windows or Eclipse).
+In such a case it may be necessary to set this variable to 'false'.
+See linkgit:git-update-index[1].
++
+The default is true (when core.filemode is not specified in the config file).
+
+core.hideDotFiles::
+	(Windows-only) If true, mark newly-created directories and files whose
+	name starts with a dot as hidden.  If 'dotGitOnly', only the `.git/`
+	directory is hidden, but no other files starting with a dot.  The
+	default mode is 'dotGitOnly'.
+
+core.ignoreCase::
+	Internal variable which enables various workarounds to enable
+	Git to work better on filesystems that are not case sensitive,
+	like APFS, HFS+, FAT, NTFS, etc. For example, if a directory listing
+	finds "makefile" when Git expects "Makefile", Git will assume
+	it is really the same file, and continue to remember it as
+	"Makefile".
++
+The default is false, except linkgit:git-clone[1] or linkgit:git-init[1]
+will probe and set core.ignoreCase true if appropriate when the repository
+is created.
++
+Git relies on the proper configuration of this variable for your operating
+and file system. Modifying this value may result in unexpected behavior.
+
+core.precomposeUnicode::
+	This option is only used by Mac OS implementation of Git.
+	When core.precomposeUnicode=true, Git reverts the unicode decomposition
+	of filenames done by Mac OS. This is useful when sharing a repository
+	between Mac OS and Linux or Windows.
+	(Git for Windows 1.7.10 or higher is needed, or Git under cygwin 1.7).
+	When false, file names are handled fully transparent by Git,
+	which is backward compatible with older versions of Git.
+
+core.protectHFS::
+	If set to true, do not allow checkout of paths that would
+	be considered equivalent to `.git` on an HFS+ filesystem.
+	Defaults to `true` on Mac OS, and `false` elsewhere.
+
+core.protectNTFS::
+	If set to true, do not allow checkout of paths that would
+	cause problems with the NTFS filesystem, e.g. conflict with
+	8.3 "short" names.
+	Defaults to `true` on Windows, and `false` elsewhere.
+
+core.fsmonitor::
+	If set, the value of this variable is used as a command which
+	will identify all files that may have changed since the
+	requested date/time. This information is used to speed up git by
+	avoiding unnecessary processing of files that have not changed.
+	See the "fsmonitor-watchman" section of linkgit:githooks[5].
+
+core.trustctime::
+	If false, the ctime differences between the index and the
+	working tree are ignored; useful when the inode change time
+	is regularly modified by something outside Git (file system
+	crawlers and some backup systems).
+	See linkgit:git-update-index[1]. True by default.
+
+core.splitIndex::
+	If true, the split-index feature of the index will be used.
+	See linkgit:git-update-index[1]. False by default.
+
+core.untrackedCache::
+	Determines what to do about the untracked cache feature of the
+	index. It will be kept, if this variable is unset or set to
+	`keep`. It will automatically be added if set to `true`. And
+	it will automatically be removed, if set to `false`. Before
+	setting it to `true`, you should check that mtime is working
+	properly on your system.
+	See linkgit:git-update-index[1]. `keep` by default.
+
+core.checkStat::
+	When missing or is set to `default`, many fields in the stat
+	structure are checked to detect if a file has been modified
+	since Git looked at it.  When this configuration variable is
+	set to `minimal`, sub-second part of mtime and ctime, the
+	uid and gid of the owner of the file, the inode number (and
+	the device number, if Git was compiled to use it), are
+	excluded from the check among these fields, leaving only the
+	whole-second part of mtime (and ctime, if `core.trustCtime`
+	is set) and the filesize to be checked.
++
+There are implementations of Git that do not leave usable values in
+some fields (e.g. JGit); by excluding these fields from the
+comparison, the `minimal` mode may help interoperability when the
+same repository is used by these other systems at the same time.
+
+core.quotePath::
+	Commands that output paths (e.g. 'ls-files', 'diff'), will
+	quote "unusual" characters in the pathname by enclosing the
+	pathname in double-quotes and escaping those characters with
+	backslashes in the same way C escapes control characters (e.g.
+	`\t` for TAB, `\n` for LF, `\\` for backslash) or bytes with
+	values larger than 0x80 (e.g. octal `\302\265` for "micro" in
+	UTF-8).  If this variable is set to false, bytes higher than
+	0x80 are not considered "unusual" any more. Double-quotes,
+	backslash and control characters are always escaped regardless
+	of the setting of this variable.  A simple space character is
+	not considered "unusual".  Many commands can output pathnames
+	completely verbatim using the `-z` option. The default value
+	is true.
+
+core.eol::
+	Sets the line ending type to use in the working directory for
+	files that have the `text` property set when core.autocrlf is false.
+	Alternatives are 'lf', 'crlf' and 'native', which uses the platform's
+	native line ending.  The default value is `native`.  See
+	linkgit:gitattributes[5] for more information on end-of-line
+	conversion.
+
+core.safecrlf::
+	If true, makes Git check if converting `CRLF` is reversible when
+	end-of-line conversion is active.  Git will verify if a command
+	modifies a file in the work tree either directly or indirectly.
+	For example, committing a file followed by checking out the
+	same file should yield the original file in the work tree.  If
+	this is not the case for the current setting of
+	`core.autocrlf`, Git will reject the file.  The variable can
+	be set to "warn", in which case Git will only warn about an
+	irreversible conversion but continue the operation.
++
+CRLF conversion bears a slight chance of corrupting data.
+When it is enabled, Git will convert CRLF to LF during commit and LF to
+CRLF during checkout.  A file that contains a mixture of LF and
+CRLF before the commit cannot be recreated by Git.  For text
+files this is the right thing to do: it corrects line endings
+such that we have only LF line endings in the repository.
+But for binary files that are accidentally classified as text the
+conversion can corrupt data.
++
+If you recognize such corruption early you can easily fix it by
+setting the conversion type explicitly in .gitattributes.  Right
+after committing you still have the original file in your work
+tree and this file is not yet corrupted.  You can explicitly tell
+Git that this file is binary and Git will handle the file
+appropriately.
++
+Unfortunately, the desired effect of cleaning up text files with
+mixed line endings and the undesired effect of corrupting binary
+files cannot be distinguished.  In both cases CRLFs are removed
+in an irreversible way.  For text files this is the right thing
+to do because CRLFs are line endings, while for binary files
+converting CRLFs corrupts data.
++
+Note, this safety check does not mean that a checkout will generate a
+file identical to the original file for a different setting of
+`core.eol` and `core.autocrlf`, but only for the current one.  For
+example, a text file with `LF` would be accepted with `core.eol=lf`
+and could later be checked out with `core.eol=crlf`, in which case the
+resulting file would contain `CRLF`, although the original file
+contained `LF`.  However, in both work trees the line endings would be
+consistent, that is either all `LF` or all `CRLF`, but never mixed.  A
+file with mixed line endings would be reported by the `core.safecrlf`
+mechanism.
+
+core.autocrlf::
+	Setting this variable to "true" is the same as setting
+	the `text` attribute to "auto" on all files and core.eol to "crlf".
+	Set to true if you want to have `CRLF` line endings in your
+	working directory and the repository has LF line endings.
+	This variable can be set to 'input',
+	in which case no output conversion is performed.
+
+core.checkRoundtripEncoding::
+	A comma and/or whitespace separated list of encodings that Git
+	performs UTF-8 round trip checks on if they are used in an
+	`working-tree-encoding` attribute (see linkgit:gitattributes[5]).
+	The default value is `SHIFT-JIS`.
+
+core.symlinks::
+	If false, symbolic links are checked out as small plain files that
+	contain the link text. linkgit:git-update-index[1] and
+	linkgit:git-add[1] will not change the recorded type to regular
+	file. Useful on filesystems like FAT that do not support
+	symbolic links.
++
+The default is true, except linkgit:git-clone[1] or linkgit:git-init[1]
+will probe and set core.symlinks false if appropriate when the repository
+is created.
+
+core.gitProxy::
+	A "proxy command" to execute (as 'command host port') instead
+	of establishing direct connection to the remote server when
+	using the Git protocol for fetching. If the variable value is
+	in the "COMMAND for DOMAIN" format, the command is applied only
+	on hostnames ending with the specified domain string. This variable
+	may be set multiple times and is matched in the given order;
+	the first match wins.
++
+Can be overridden by the `GIT_PROXY_COMMAND` environment variable
+(which always applies universally, without the special "for"
+handling).
++
+The special string `none` can be used as the proxy command to
+specify that no proxy be used for a given domain pattern.
+This is useful for excluding servers inside a firewall from
+proxy use, while defaulting to a common proxy for external domains.
+
+core.sshCommand::
+	If this variable is set, `git fetch` and `git push` will
+	use the specified command instead of `ssh` when they need to
+	connect to a remote system. The command is in the same form as
+	the `GIT_SSH_COMMAND` environment variable and is overridden
+	when the environment variable is set.
+
+core.ignoreStat::
+	If true, Git will avoid using lstat() calls to detect if files have
+	changed by setting the "assume-unchanged" bit for those tracked files
+	which it has updated identically in both the index and working tree.
++
+When files are modified outside of Git, the user will need to stage
+the modified files explicitly (e.g. see 'Examples' section in
+linkgit:git-update-index[1]).
+Git will not normally detect changes to those files.
++
+This is useful on systems where lstat() calls are very slow, such as
+CIFS/Microsoft Windows.
++
+False by default.
+
+core.preferSymlinkRefs::
+	Instead of the default "symref" format for HEAD
+	and other symbolic reference files, use symbolic links.
+	This is sometimes needed to work with old scripts that
+	expect HEAD to be a symbolic link.
+
+core.alternateRefsCommand::
+	When advertising tips of available history from an alternate, use the shell to
+	execute the specified command instead of linkgit:git-for-each-ref[1]. The
+	first argument is the absolute path of the alternate. Output must contain one
+	hex object id per line (i.e., the same as produce by `git for-each-ref
+	--format='%(objectname)'`).
++
+Note that you cannot generally put `git for-each-ref` directly into the config
+value, as it does not take a repository path as an argument (but you can wrap
+the command above in a shell script).
+
+core.alternateRefsPrefixes::
+	When listing references from an alternate, list only references that begin
+	with the given prefix. Prefixes match as if they were given as arguments to
+	linkgit:git-for-each-ref[1]. To list multiple prefixes, separate them with
+	whitespace. If `core.alternateRefsCommand` is set, setting
+	`core.alternateRefsPrefixes` has no effect.
+
+core.bare::
+	If true this repository is assumed to be 'bare' and has no
+	working directory associated with it.  If this is the case a
+	number of commands that require a working directory will be
+	disabled, such as linkgit:git-add[1] or linkgit:git-merge[1].
++
+This setting is automatically guessed by linkgit:git-clone[1] or
+linkgit:git-init[1] when the repository was created.  By default a
+repository that ends in "/.git" is assumed to be not bare (bare =
+false), while all other repositories are assumed to be bare (bare
+= true).
+
+core.worktree::
+	Set the path to the root of the working tree.
+	If `GIT_COMMON_DIR` environment variable is set, core.worktree
+	is ignored and not used for determining the root of working tree.
+	This can be overridden by the `GIT_WORK_TREE` environment
+	variable and the `--work-tree` command-line option.
+	The value can be an absolute path or relative to the path to
+	the .git directory, which is either specified by --git-dir
+	or GIT_DIR, or automatically discovered.
+	If --git-dir or GIT_DIR is specified but none of
+	--work-tree, GIT_WORK_TREE and core.worktree is specified,
+	the current working directory is regarded as the top level
+	of your working tree.
++
+Note that this variable is honored even when set in a configuration
+file in a ".git" subdirectory of a directory and its value differs
+from the latter directory (e.g. "/path/to/.git/config" has
+core.worktree set to "/different/path"), which is most likely a
+misconfiguration.  Running Git commands in the "/path/to" directory will
+still use "/different/path" as the root of the work tree and can cause
+confusion unless you know what you are doing (e.g. you are creating a
+read-only snapshot of the same index to a location different from the
+repository's usual working tree).
+
+core.logAllRefUpdates::
+	Enable the reflog. Updates to a ref <ref> is logged to the file
+	"`$GIT_DIR/logs/<ref>`", by appending the new and old
+	SHA-1, the date/time and the reason of the update, but
+	only when the file exists.  If this configuration
+	variable is set to `true`, missing "`$GIT_DIR/logs/<ref>`"
+	file is automatically created for branch heads (i.e. under
+	`refs/heads/`), remote refs (i.e. under `refs/remotes/`),
+	note refs (i.e. under `refs/notes/`), and the symbolic ref `HEAD`.
+	If it is set to `always`, then a missing reflog is automatically
+	created for any ref under `refs/`.
++
+This information can be used to determine what commit
+was the tip of a branch "2 days ago".
++
+This value is true by default in a repository that has
+a working directory associated with it, and false by
+default in a bare repository.
+
+core.repositoryFormatVersion::
+	Internal variable identifying the repository format and layout
+	version.
+
+core.sharedRepository::
+	When 'group' (or 'true'), the repository is made shareable between
+	several users in a group (making sure all the files and objects are
+	group-writable). When 'all' (or 'world' or 'everybody'), the
+	repository will be readable by all users, additionally to being
+	group-shareable. When 'umask' (or 'false'), Git will use permissions
+	reported by umask(2). When '0xxx', where '0xxx' is an octal number,
+	files in the repository will have this mode value. '0xxx' will override
+	user's umask value (whereas the other options will only override
+	requested parts of the user's umask value). Examples: '0660' will make
+	the repo read/write-able for the owner and group, but inaccessible to
+	others (equivalent to 'group' unless umask is e.g. '0022'). '0640' is a
+	repository that is group-readable but not group-writable.
+	See linkgit:git-init[1]. False by default.
+
+core.warnAmbiguousRefs::
+	If true, Git will warn you if the ref name you passed it is ambiguous
+	and might match multiple refs in the repository. True by default.
+
+core.compression::
+	An integer -1..9, indicating a default compression level.
+	-1 is the zlib default. 0 means no compression,
+	and 1..9 are various speed/size tradeoffs, 9 being slowest.
+	If set, this provides a default to other compression variables,
+	such as `core.looseCompression` and `pack.compression`.
+
+core.looseCompression::
+	An integer -1..9, indicating the compression level for objects that
+	are not in a pack file. -1 is the zlib default. 0 means no
+	compression, and 1..9 are various speed/size tradeoffs, 9 being
+	slowest.  If not set,  defaults to core.compression.  If that is
+	not set,  defaults to 1 (best speed).
+
+core.packedGitWindowSize::
+	Number of bytes of a pack file to map into memory in a
+	single mapping operation.  Larger window sizes may allow
+	your system to process a smaller number of large pack files
+	more quickly.  Smaller window sizes will negatively affect
+	performance due to increased calls to the operating system's
+	memory manager, but may improve performance when accessing
+	a large number of large pack files.
++
+Default is 1 MiB if NO_MMAP was set at compile time, otherwise 32
+MiB on 32 bit platforms and 1 GiB on 64 bit platforms.  This should
+be reasonable for all users/operating systems.  You probably do
+not need to adjust this value.
++
+Common unit suffixes of 'k', 'm', or 'g' are supported.
+
+core.packedGitLimit::
+	Maximum number of bytes to map simultaneously into memory
+	from pack files.  If Git needs to access more than this many
+	bytes at once to complete an operation it will unmap existing
+	regions to reclaim virtual address space within the process.
++
+Default is 256 MiB on 32 bit platforms and 32 TiB (effectively
+unlimited) on 64 bit platforms.
+This should be reasonable for all users/operating systems, except on
+the largest projects.  You probably do not need to adjust this value.
++
+Common unit suffixes of 'k', 'm', or 'g' are supported.
+
+core.deltaBaseCacheLimit::
+	Maximum number of bytes to reserve for caching base objects
+	that may be referenced by multiple deltified objects.  By storing the
+	entire decompressed base objects in a cache Git is able
+	to avoid unpacking and decompressing frequently used base
+	objects multiple times.
++
+Default is 96 MiB on all platforms.  This should be reasonable
+for all users/operating systems, except on the largest projects.
+You probably do not need to adjust this value.
++
+Common unit suffixes of 'k', 'm', or 'g' are supported.
+
+core.bigFileThreshold::
+	Files larger than this size are stored deflated, without
+	attempting delta compression.  Storing large files without
+	delta compression avoids excessive memory usage, at the
+	slight expense of increased disk usage. Additionally files
+	larger than this size are always treated as binary.
++
+Default is 512 MiB on all platforms.  This should be reasonable
+for most projects as source code and other text files can still
+be delta compressed, but larger binary media files won't be.
++
+Common unit suffixes of 'k', 'm', or 'g' are supported.
+
+core.excludesFile::
+	Specifies the pathname to the file that contains patterns to
+	describe paths that are not meant to be tracked, in addition
+	to '.gitignore' (per-directory) and '.git/info/exclude'.
+	Defaults to `$XDG_CONFIG_HOME/git/ignore`.
+	If `$XDG_CONFIG_HOME` is either not set or empty, `$HOME/.config/git/ignore`
+	is used instead. See linkgit:gitignore[5].
+
+core.askPass::
+	Some commands (e.g. svn and http interfaces) that interactively
+	ask for a password can be told to use an external program given
+	via the value of this variable. Can be overridden by the `GIT_ASKPASS`
+	environment variable. If not set, fall back to the value of the
+	`SSH_ASKPASS` environment variable or, failing that, a simple password
+	prompt. The external program shall be given a suitable prompt as
+	command-line argument and write the password on its STDOUT.
+
+core.attributesFile::
+	In addition to '.gitattributes' (per-directory) and
+	'.git/info/attributes', Git looks into this file for attributes
+	(see linkgit:gitattributes[5]). Path expansions are made the same
+	way as for `core.excludesFile`. Its default value is
+	`$XDG_CONFIG_HOME/git/attributes`. If `$XDG_CONFIG_HOME` is either not
+	set or empty, `$HOME/.config/git/attributes` is used instead.
+
+core.hooksPath::
+	By default Git will look for your hooks in the
+	'$GIT_DIR/hooks' directory. Set this to different path,
+	e.g. '/etc/git/hooks', and Git will try to find your hooks in
+	that directory, e.g. '/etc/git/hooks/pre-receive' instead of
+	in '$GIT_DIR/hooks/pre-receive'.
++
+The path can be either absolute or relative. A relative path is
+taken as relative to the directory where the hooks are run (see
+the "DESCRIPTION" section of linkgit:githooks[5]).
++
+This configuration variable is useful in cases where you'd like to
+centrally configure your Git hooks instead of configuring them on a
+per-repository basis, or as a more flexible and centralized
+alternative to having an `init.templateDir` where you've changed
+default hooks.
+
+core.editor::
+	Commands such as `commit` and `tag` that let you edit
+	messages by launching an editor use the value of this
+	variable when it is set, and the environment variable
+	`GIT_EDITOR` is not set.  See linkgit:git-var[1].
+
+core.commentChar::
+	Commands such as `commit` and `tag` that let you edit
+	messages consider a line that begins with this character
+	commented, and removes them after the editor returns
+	(default '#').
++
+If set to "auto", `git-commit` would select a character that is not
+the beginning character of any line in existing commit messages.
+
+core.filesRefLockTimeout::
+	The length of time, in milliseconds, to retry when trying to
+	lock an individual reference. Value 0 means not to retry at
+	all; -1 means to try indefinitely. Default is 100 (i.e.,
+	retry for 100ms).
+
+core.packedRefsTimeout::
+	The length of time, in milliseconds, to retry when trying to
+	lock the `packed-refs` file. Value 0 means not to retry at
+	all; -1 means to try indefinitely. Default is 1000 (i.e.,
+	retry for 1 second).
+
+core.pager::
+	Text viewer for use by Git commands (e.g., 'less').  The value
+	is meant to be interpreted by the shell.  The order of preference
+	is the `$GIT_PAGER` environment variable, then `core.pager`
+	configuration, then `$PAGER`, and then the default chosen at
+	compile time (usually 'less').
++
+When the `LESS` environment variable is unset, Git sets it to `FRX`
+(if `LESS` environment variable is set, Git does not change it at
+all).  If you want to selectively override Git's default setting
+for `LESS`, you can set `core.pager` to e.g. `less -S`.  This will
+be passed to the shell by Git, which will translate the final
+command to `LESS=FRX less -S`. The environment does not set the
+`S` option but the command line does, instructing less to truncate
+long lines. Similarly, setting `core.pager` to `less -+F` will
+deactivate the `F` option specified by the environment from the
+command-line, deactivating the "quit if one screen" behavior of
+`less`.  One can specifically activate some flags for particular
+commands: for example, setting `pager.blame` to `less -S` enables
+line truncation only for `git blame`.
++
+Likewise, when the `LV` environment variable is unset, Git sets it
+to `-c`.  You can override this setting by exporting `LV` with
+another value or setting `core.pager` to `lv +c`.
+
+core.whitespace::
+	A comma separated list of common whitespace problems to
+	notice.  'git diff' will use `color.diff.whitespace` to
+	highlight them, and 'git apply --whitespace=error' will
+	consider them as errors.  You can prefix `-` to disable
+	any of them (e.g. `-trailing-space`):
++
+* `blank-at-eol` treats trailing whitespaces at the end of the line
+  as an error (enabled by default).
+* `space-before-tab` treats a space character that appears immediately
+  before a tab character in the initial indent part of the line as an
+  error (enabled by default).
+* `indent-with-non-tab` treats a line that is indented with space
+  characters instead of the equivalent tabs as an error (not enabled by
+  default).
+* `tab-in-indent` treats a tab character in the initial indent part of
+  the line as an error (not enabled by default).
+* `blank-at-eof` treats blank lines added at the end of file as an error
+  (enabled by default).
+* `trailing-space` is a short-hand to cover both `blank-at-eol` and
+  `blank-at-eof`.
+* `cr-at-eol` treats a carriage-return at the end of line as
+  part of the line terminator, i.e. with it, `trailing-space`
+  does not trigger if the character before such a carriage-return
+  is not a whitespace (not enabled by default).
+* `tabwidth=<n>` tells how many character positions a tab occupies; this
+  is relevant for `indent-with-non-tab` and when Git fixes `tab-in-indent`
+  errors. The default tab width is 8. Allowed values are 1 to 63.
+
+core.fsyncObjectFiles::
+	This boolean will enable 'fsync()' when writing object files.
++
+This is a total waste of time and effort on a filesystem that orders
+data writes properly, but can be useful for filesystems that do not use
+journalling (traditional UNIX filesystems) or that only journal metadata
+and not file contents (OS X's HFS+, or Linux ext3 with "data=writeback").
+
+core.preloadIndex::
+	Enable parallel index preload for operations like 'git diff'
++
+This can speed up operations like 'git diff' and 'git status' especially
+on filesystems like NFS that have weak caching semantics and thus
+relatively high IO latencies.  When enabled, Git will do the
+index comparison to the filesystem data in parallel, allowing
+overlapping IO's.  Defaults to true.
+
+core.createObject::
+	You can set this to 'link', in which case a hardlink followed by
+	a delete of the source are used to make sure that object creation
+	will not overwrite existing objects.
++
+On some file system/operating system combinations, this is unreliable.
+Set this config setting to 'rename' there; However, This will remove the
+check that makes sure that existing object files will not get overwritten.
+
+core.notesRef::
+	When showing commit messages, also show notes which are stored in
+	the given ref.  The ref must be fully qualified.  If the given
+	ref does not exist, it is not an error but means that no
+	notes should be printed.
++
+This setting defaults to "refs/notes/commits", and it can be overridden by
+the `GIT_NOTES_REF` environment variable.  See linkgit:git-notes[1].
+
+core.commitGraph::
+	If true, then git will read the commit-graph file (if it exists)
+	to parse the graph structure of commits. Defaults to false. See
+	linkgit:git-commit-graph[1] for more information.
+
+core.useReplaceRefs::
+	If set to `false`, behave as if the `--no-replace-objects`
+	option was given on the command line. See linkgit:git[1] and
+	linkgit:git-replace[1] for more information.
+
+core.multiPackIndex::
+	Use the multi-pack-index file to track multiple packfiles using a
+	single index. See link:technical/multi-pack-index.html[the
+	multi-pack-index design document].
+
+core.sparseCheckout::
+	Enable "sparse checkout" feature. See section "Sparse checkout" in
+	linkgit:git-read-tree[1] for more information.
+
+core.abbrev::
+	Set the length object names are abbreviated to.  If
+	unspecified or set to "auto", an appropriate value is
+	computed based on the approximate number of packed objects
+	in your repository, which hopefully is enough for
+	abbreviated object names to stay unique for some time.
+	The minimum length is 4.
-- 
2.19.1.647.g708186aaf9


^ permalink raw reply related	[relevance 4%]

* Git Test Coverage Report (Friday, Oct 12)
@ 2018-10-12 12:59  1% Derrick Stolee
  0 siblings, 0 replies; 200+ results
From: Derrick Stolee @ 2018-10-12 12:59 UTC (permalink / raw)
  To: Git List


In an effort to ensure new code is reasonably covered by the test suite, 
we now have contrib/coverage-diff.sh to combine the gcov output from 
'make coverage-test ; make coverage-report' with the output from 'git 
diff A B' to discover _new_ lines of code that are not covered.

This report takes the output of these results after running on four 
branches:

         pu: 13cafb433ca0dd31b3ea229a79cc8507aa89ee19 (tests are broken, 
so coverage not reported)

        jch: fda196ac82002ede984896861cf28a354044d1a0

       next: 76f2f5c1e34c4dbef1029e2984c2892894c444ce

     master: 04861070400d3ca9d487bd0d736ca818b9ffe371

      maint: cae598d9980661a978e2df4fb338518f7bf09572

I ran the test suite on each of these branches on an Ubuntu Linux VM, 
and I'm missing some dependencies (like apache, svn, and perforce) so 
not all tests are run.

I submit this output without comment. I'm taking a look especially at my 
own lines to see where coverage can be improved.

Thanks,

-Stolee

Uncovered code new in jch, compared to next. Build logs at [1]
--------------------------------------------------------------

builtin/archive.c
e001fd3a50 builtin/archive.c  78) die(_("git archive: expected ACK/NAK, 
got a flush packet"));
e001fd3a50 builtin/archive.c  80) if (starts_with(reader.line, "NACK "))
e001fd3a50 builtin/archive.c  81) die(_("git archive: NACK %s"), 
reader.line + 5);
e001fd3a50 builtin/archive.c  82) if (starts_with(reader.line, "ERR "))
e001fd3a50 builtin/archive.c  83) die(_("remote error: %s"), reader.line 
+ 4);
e001fd3a50 builtin/archive.c  84) die(_("git archive: protocol error"));
e001fd3a50 builtin/archive.c  89) die(_("git archive: expected a flush"));
fb19d32f05 builtin/archive.c  99) if (version != discover_version(&reader))
fb19d32f05 builtin/archive.c 100) die(_("git archive: received different 
protocol versions in subsequent requests"));

builtin/fetch.c
7bbc53a589 builtin/fetch.c  385) continue; /* can this happen??? */

builtin/rebase--interactive.c
53bbcfbde7 builtin/rebase--interactive2.c  24) return error(_("no HEAD?"));
53bbcfbde7 builtin/rebase--interactive2.c  51) return 
error_errno(_("could not create temporary %s"), path_state_dir());
53bbcfbde7 builtin/rebase--interactive2.c  57) return 
error_errno(_("could not mark as interactive"));
53bbcfbde7 builtin/rebase--interactive2.c  77) return -1;
53bbcfbde7 builtin/rebase--interactive2.c  81) return -1;
53bbcfbde7 builtin/rebase--interactive2.c  87) free(revisions);
53bbcfbde7 builtin/rebase--interactive2.c  88) free(shortrevisions);
53bbcfbde7 builtin/rebase--interactive2.c  90) return -1;
53bbcfbde7 builtin/rebase--interactive2.c  98) free(revisions);
53bbcfbde7 builtin/rebase--interactive2.c  99) free(shortrevisions);
53bbcfbde7 builtin/rebase--interactive2.c 101) return 
error_errno(_("could not open %s"), rebase_path_todo());
53bbcfbde7 builtin/rebase--interactive2.c 106) 
argv_array_push(&make_script_args, restrict_revision);
53bbcfbde7 builtin/rebase--interactive2.c 114) error(_("could not 
generate todo list"));
53bbcfbde7 builtin/rebase--interactive2.c 206) 
usage_with_options(builtin_rebase_interactive_usage, options);
53bbcfbde7 builtin/rebase--interactive2.c 220) 
warning(_("--[no-]rebase-cousins has no effect without "
0af129b2ed builtin/rebase--interactive2.c 226) die(_("a base commit must 
be provided with --upstream or --onto"));
34b47315d9 builtin/rebase--interactive.c  261) ret = rearrange_squash();
34b47315d9 builtin/rebase--interactive.c  262) break;
34b47315d9 builtin/rebase--interactive.c  264) ret = 
sequencer_add_exec_commands(cmd);
34b47315d9 builtin/rebase--interactive.c  265) break;
0af129b2ed builtin/rebase--interactive2.c 267) BUG("invalid command 
'%d'", command);

builtin/rebase.c
55071ea248   61) strbuf_trim(&out);
55071ea248   62) ret = !strcmp("true", out.buf);
55071ea248   63) strbuf_release(&out);
002ee2fe68  115) die(_("%s requires an interactive rebase"), option);
f95736288a  148) return error_errno(_("could not read '%s'"), path);
f95736288a  162) return -1;
f95736288a  167) return error(_("could not get 'onto': '%s'"), buf.buf);
f95736288a  178) return -1;
f95736288a  179) } else if (read_one(state_dir_path("head", opts), &buf))
f95736288a  180) return -1;
f95736288a  182) return error(_("invalid orig-head: '%s'"), buf.buf);
f95736288a  186) return -1;
f95736288a  188) opts->flags &= ~REBASE_NO_QUIET;
73d51ed0a5  196) opts->signoff = 1;
73d51ed0a5  197) opts->flags |= REBASE_FORCE;
ead98c111b  204) return -1;
12026a412c  219) return -1;
ba1905a5fe  227) return -1;
ba1905a5fe  235) return -1;
6defce2b02  255) return error(_("Could not read '%s'"), path);
6defce2b02  271) res = error(_("Cannot store %s"), autostash.buf);
6defce2b02  275) return res;
bc24382c2b  373) argv_array_pushf(&child.args,
bc24382c2b  375) oid_to_hex(&opts->restrict_revision->object.oid));
ac7f467fef  488) BUG("Unhandled rebase type %d", opts->type);
ac7f467fef  507) struct strbuf dir = STRBUF_INIT;
6defce2b02  509) apply_autostash(opts);
ac7f467fef  510) strbuf_addstr(&dir, opts->state_dir);
ac7f467fef  511) remove_dir_recursively(&dir, 0);
ac7f467fef  512) strbuf_release(&dir);
ac7f467fef  513) die("Nothing to do");
d4c569f8f4  540) BUG("Not a fully qualified branch: '%s'", 
switch_to_branch);
ac7f467fef  543) return -1;
ac7f467fef  547) rollback_lock_file(&lock);
ac7f467fef  548) return error(_("could not determine HEAD revision"));
ac7f467fef  565) rollback_lock_file(&lock);
ac7f467fef  566) return error(_("could not read index"));
ac7f467fef  570) error(_("failed to find tree of %s"), oid_to_hex(oid));
ac7f467fef  571) rollback_lock_file(&lock);
ac7f467fef  572) free((void *)desc.buffer);
ac7f467fef  573) return -1;
ac7f467fef  586) ret = error(_("could not write index"));
ac7f467fef  590) return ret;
ac7f467fef  606) } else if (old_orig)
ac7f467fef  607) delete_ref(NULL, "ORIG_HEAD", old_orig, 0);
bff014dac7  635) opts->flags &= !REBASE_DIFFSTAT;
9a48a615b4  669) return 1;
9a48a615b4  685) return 0;
55071ea248  893) const char *path = mkpath("%s/git-legacy-rebase",
55071ea248  896) if (sane_execvp(path, (char **)argv) < 0)
55071ea248  897) die_errno(_("could not exec %s"), path);
55071ea248  899) BUG("sane_execvp() returned???");
0eabf4b95c  915) die(_("It looks like 'git am' is in progress. Cannot 
rebase."));
f28d40d3a9  952) usage_with_options(builtin_rebase_usage,
f95736288a  972) die(_("Cannot read HEAD"));
f95736288a  976) die(_("could not read index"));
f95736288a  990) exit(1);
122420c295 1002) die(_("could not discard worktree changes"));
122420c295 1004) exit(1);
5e5d96197c 1015) exit(1);
5e5d96197c 1018) die(_("could not move back to %s"),
5a61494539 1028) die(_("could not remove '%s'"), options.state_dir);
51e9ea6da7 1042) BUG("action: %d", action);
c54dacb50e 1047) const char *last_slash = strrchr(options.state_dir, '/');
c54dacb50e 1048) const char *state_dir_base =
c54dacb50e 1049) last_slash ? last_slash + 1 : options.state_dir;
c54dacb50e 1050) const char *cmd_live_rebase =
c54dacb50e 1052) strbuf_reset(&buf);
c54dacb50e 1053) strbuf_addf(&buf, "rm -fr \"%s\"", options.state_dir);
c54dacb50e 1054) die(_("It seems that there is already a %s directory, 
and\n"
53f9e5be94 1078) strbuf_addstr(&options.git_am_opt, " --ignore-date");
53f9e5be94 1079) options.flags |= REBASE_FORCE;
7998dbe1ec 1091) strbuf_addf(&options.git_am_opt, " -C%d", opt_c);
3c3588c7d3 1123) else if (strcmp("no-rebase-cousins", rebase_merges))
3c3588c7d3 1124) die(_("Unknown mode: %s"), rebase_merges);
ba1905a5fe 1146) die(_("--strategy requires --merge or --interactive"));
ba1905a5fe 1156) BUG("unhandled rebase type (%d)", options.type);
cda614e489 1164) strbuf_addstr(&options.git_format_patch_opt, " 
--progress");
ac7f467fef 1173) options.state_dir = apply_dir();
ac7f467fef 1174) break;
ac7f467fef 1251) die(_("invalid upstream '%s'"), options.upstream_name);
9dba809a69 1257) die(_("Could not create new root commit"));
e65123a71d 1307) die(_("fatal: no such branch/commit '%s'"),
ac7f467fef 1315) die(_("No such ref: %s"), "HEAD");
ac7f467fef 1327) die(_("Could not resolve HEAD to a revision"));
e65123a71d 1329) BUG("unexpected number of arguments left to parse");
e0333e5c63 1340) die(_("could not read index"));
6defce2b02 1367) die(_("Cannot autostash"));
6defce2b02 1370) die(_("Unexpected stash response: '%s'"),
6defce2b02 1376) die(_("Could not create directory for '%s'"),
6defce2b02 1382) die(_("could not reset --hard"));
e65123a71d 1426) ret = !!error(_("could not parse '%s'"),
e65123a71d 1428) goto cleanup;
e65123a71d 1437) ret = !!error(_("could not switch to "
1ed9c14ff2 1447)  resolve_ref_unsafe("HEAD", 0, NULL, &flag))
1ed9c14ff2 1448) puts(_("HEAD is up to date."));
9a48a615b4 1457)  resolve_ref_unsafe("HEAD", 0, NULL, &flag))
9a48a615b4 1458) puts(_("HEAD is up to date, rebase forced."));

builtin/rev-list.c
7c0fe330d5 builtin/rev-list.c 227) die("unexpected missing %s object '%s'",
7c0fe330d5 builtin/rev-list.c 228)     type_name(obj->type), 
oid_to_hex(&obj->oid));
250edfa8c8 builtin/rev-list.c 434) bisect_flags |= BISECT_FIND_ALL;

builtin/stash.c
93871263d1 builtin/stash--helper.c  130) free_stash_info(info);
93871263d1 builtin/stash--helper.c  131) error(_("'%s' is not a 
stash-like commit"), revision);
93871263d1 builtin/stash--helper.c  132) exit(128);
93871263d1 builtin/stash--helper.c  165) free_stash_info(info);
93871263d1 builtin/stash--helper.c  166) fprintf_ln(stderr, _("No stash 
entries found."));
93871263d1 builtin/stash--helper.c  167) return -1;
93871263d1 builtin/stash--helper.c  202) free_stash_info(info);
31f109a361 builtin/stash--helper.c  229) return error(_("git stash clear 
with parameters is unimplemented"));
93871263d1 builtin/stash--helper.c  244) return -1;
93871263d1 builtin/stash--helper.c  252) return -1;
93871263d1 builtin/stash--helper.c  265) return -1;
93871263d1 builtin/stash--helper.c  268) return error(_("unable to write 
new index file"));
93871263d1 builtin/stash--helper.c  374) remove_path(stash_index_path.buf);
93871263d1 builtin/stash--helper.c  375) return -1;
93871263d1 builtin/stash--helper.c  402) return -1;
93871263d1 builtin/stash--helper.c  405) return error(_("Cannot apply a 
stash in the middle of a merge"));
93871263d1 builtin/stash--helper.c  415) strbuf_release(&out);
93871263d1 builtin/stash--helper.c  416) return -1;
93871263d1 builtin/stash--helper.c  422) return -1;
93871263d1 builtin/stash--helper.c  427) return -1;
93871263d1 builtin/stash--helper.c  434) return error(_("Could not 
restore untracked files from stash"));
93871263d1 builtin/stash--helper.c  465) return -1;
93871263d1 builtin/stash--helper.c  470) strbuf_release(&out);
93871263d1 builtin/stash--helper.c  475) strbuf_release(&out);
93871263d1 builtin/stash--helper.c  476) return -1;
31f109a361 builtin/stash--helper.c  551) return error(_("%s: Could not 
drop stash entry"),
b3513da4bd builtin/stash--helper.c  623) printf_ln(_("The stash entry is 
kept in case you need it again."));
8ceb24b2c3 builtin/stash--helper.c  754) free_stash_info(&info);
129f0b0a00 builtin/stash.c          755) 
usage_with_options(git_stash_show_usage, options);
0ac06fb81f builtin/stash--helper.c  808) fprintf_ln(stderr, _("\"git 
stash store\" requires one <commit> argument"));
0ac06fb81f builtin/stash--helper.c  809) return -1;
f6f191b3f2 builtin/stash--helper.c  884) return 1;
f6f191b3f2 builtin/stash--helper.c  925) ret = -1;
f6f191b3f2 builtin/stash--helper.c  926) goto done;
f6f191b3f2 builtin/stash--helper.c  931) ret = -1;
f6f191b3f2 builtin/stash--helper.c  932) goto done;
f6f191b3f2 builtin/stash--helper.c  937) ret = -1;
f6f191b3f2 builtin/stash--helper.c  938) goto done;
f6f191b3f2 builtin/stash--helper.c  966) ret = -1;
f6f191b3f2 builtin/stash--helper.c  967) goto done;
f6f191b3f2 builtin/stash--helper.c  978) ret = -1;
f6f191b3f2 builtin/stash--helper.c  979) goto done;
f6f191b3f2 builtin/stash--helper.c  984) ret = -1;
f6f191b3f2 builtin/stash--helper.c  985) goto done;
f6f191b3f2 builtin/stash--helper.c  992) ret = -1;
f6f191b3f2 builtin/stash--helper.c  993) goto done;
f6f191b3f2 builtin/stash--helper.c 1018) ret = -1;
f6f191b3f2 builtin/stash--helper.c 1019) goto done;
f6f191b3f2 builtin/stash--helper.c 1031) ret = -1;
f6f191b3f2 builtin/stash--helper.c 1032) goto done;
f6f191b3f2 builtin/stash--helper.c 1037) ret = -1;
f6f191b3f2 builtin/stash--helper.c 1038) goto done;
f6f191b3f2 builtin/stash--helper.c 1049) ret = -1;
f6f191b3f2 builtin/stash--helper.c 1050) goto done;
f6f191b3f2 builtin/stash--helper.c 1055) ret = -1;
f6f191b3f2 builtin/stash--helper.c 1056) goto done;
8002b9e626 builtin/stash--helper.c 1089) fprintf_ln(stderr, _("You do 
not have the initial commit yet"));
8002b9e626 builtin/stash--helper.c 1110) if (!quiet)
8002b9e626 builtin/stash--helper.c 1111) fprintf_ln(stderr, _("Cannot 
save the current index state"));
f6f191b3f2 builtin/stash--helper.c 1112) ret = -1;
f6f191b3f2 builtin/stash--helper.c 1113) *stash_msg = NULL;
f6f191b3f2 builtin/stash--helper.c 1114) goto done;
8002b9e626 builtin/stash--helper.c 1119) if (!quiet)
8002b9e626 builtin/stash--helper.c 1120) fprintf_ln(stderr, _("Cannot 
save the untracked files"));
f6f191b3f2 builtin/stash--helper.c 1121) ret = -1;
f6f191b3f2 builtin/stash--helper.c 1122) *stash_msg = NULL;
f6f191b3f2 builtin/stash--helper.c 1123) goto done;
8002b9e626 builtin/stash--helper.c 1131) if (!quiet)
8002b9e626 builtin/stash--helper.c 1132) fprintf_ln(stderr, _("Cannot 
save the current worktree state"));
f6f191b3f2 builtin/stash--helper.c 1133) goto done;
8002b9e626 builtin/stash--helper.c 1139) if (!quiet)
8002b9e626 builtin/stash--helper.c 1140) fprintf_ln(stderr, _("Cannot 
save the current worktree state"));
f6f191b3f2 builtin/stash--helper.c 1141) ret = -1;
f6f191b3f2 builtin/stash--helper.c 1142) *stash_msg = NULL;
f6f191b3f2 builtin/stash--helper.c 1143) goto done;
8002b9e626 builtin/stash--helper.c 1166) if (!quiet)
8002b9e626 builtin/stash--helper.c 1167) fprintf_ln(stderr, _("Cannot 
record working tree state"));
f6f191b3f2 builtin/stash--helper.c 1168) ret = -1;
f6f191b3f2 builtin/stash--helper.c 1169) goto done;
48c061fa44 builtin/stash--helper.c 1251) return -1;
8002b9e626 builtin/stash--helper.c 1260) if (!quiet)
8002b9e626 builtin/stash--helper.c 1261) fprintf_ln(stderr, _("Cannot 
initialize stash"));
48c061fa44 builtin/stash--helper.c 1262) return -1;
8002b9e626 builtin/stash--helper.c 1272) if (!quiet)
8002b9e626 builtin/stash--helper.c 1273) fprintf_ln(stderr, _("Cannot 
save the current status"));
48c061fa44 builtin/stash--helper.c 1274) ret = -1;
48c061fa44 builtin/stash--helper.c 1275) goto done;
48c061fa44 builtin/stash--helper.c 1292) ret = -1;
48c061fa44 builtin/stash--helper.c 1311) ret = -1;
48c061fa44 builtin/stash--helper.c 1312) goto done;
48c061fa44 builtin/stash--helper.c 1321) ret = -1;
48c061fa44 builtin/stash--helper.c 1322) goto done;
48c061fa44 builtin/stash--helper.c 1330) ret = -1;
48c061fa44 builtin/stash--helper.c 1339) ret = -1;
48c061fa44 builtin/stash--helper.c 1350) ret = -1;
48c061fa44 builtin/stash--helper.c 1359) ret = -1;
48c061fa44 builtin/stash--helper.c 1360) goto done;
48c061fa44 builtin/stash--helper.c 1368) ret = -1;
48c061fa44 builtin/stash--helper.c 1394) ret = -1;
129f0b0a00 builtin/stash.c         1524) 
usage_msg_opt(xstrfmt(_("unknown subcommand: %s"), argv[0]),
129f0b0a00 builtin/stash.c         1554) continue;

builtin/upload-archive.c
e001fd3a50 builtin/upload-archive.c 111) if (version == protocol_v0 || 
version == protocol_v1)
e001fd3a50 builtin/upload-archive.c 112) packet_write_fmt(1, "NACK 
unable to spawn subprocess\n");
e001fd3a50 builtin/upload-archive.c 113) else if (version == protocol_v2)
e001fd3a50 builtin/upload-archive.c 114) error_clnt("unable to spawn 
subprocess\n");

config.c
c780b9cfe8 2298) return val;
c780b9cfe8 2301) if (is_bool)
c780b9cfe8 2302) return val ? 0 : 1;
c780b9cfe8 2304) return val;

help.c
26c7d06783 help.c         500) static int get_alias(const char *var, 
const char *value, void *data)
26c7d06783 help.c         502) struct string_list *list = data;
26c7d06783 help.c         504) if (skip_prefix(var, "alias.", &var))
26c7d06783 help.c         505) string_list_append(list, var)->util = 
xstrdup(value);
26c7d06783 help.c         507) return 0;
26c7d06783 help.c         530) printf("\n%s\n", _("Command aliases"));
26c7d06783 help.c         531) ALLOC_ARRAY(aliases, alias_list.nr + 1);
26c7d06783 help.c         532) for (i = 0; i < alias_list.nr; i++) {
26c7d06783 help.c         533) aliases[i].name = alias_list.items[i].string;
26c7d06783 help.c         534) aliases[i].help = alias_list.items[i].util;
26c7d06783 help.c         535) aliases[i].category = 1;
26c7d06783 help.c         537) aliases[alias_list.nr].name = NULL;
26c7d06783 help.c         538) print_command_list(aliases, 1, longest);
26c7d06783 help.c         539) free(aliases);

http-backend.c
fb19d32f05 646) argv[1] = ".";
fb19d32f05 647) argv[2] = NULL;

list-objects-filter-options.c
bc5975d24f  55) if (errbuf) {
bc5975d24f  56) strbuf_addstr(
bc5975d24f  60) return 1;
cc0b05a4cc  86) if (errbuf)

list-objects-filter.c
696aa73905  47) BUG("unknown filter_situation: %d", filter_situation);
bc5975d24f 101) BUG("unknown filter_situation: %d", filter_situation);
696aa73905 152) BUG("unknown filter_situation: %d", filter_situation);
696aa73905 257) BUG("unknown filter_situation: %d", filter_situation);
696aa73905 438) BUG("invalid list-objects filter choice: %d",

list-objects.c
f447a499db 197) ctx->show_object(obj, base->buf, ctx->show_data);

midx.c
e43d2dcce1  285) if (hasheq(oid.hash,
e43d2dcce1  286)    p->bad_object_sha1 + the_hash_algo->rawsz * i))

oidset.c
8b2f8cbcb1 29) kh_del_oid(&set->set, pos);
8b2f8cbcb1 30) return 1;

packfile.c
fb571c5fa7  117) return error("index file %s is too small", path);
fb571c5fa7  119) return error("empty data");

preload-index.c
ae9af12287  73) struct progress_data *pd = p->progress;
ae9af12287  75) pthread_mutex_lock(&pd->mutex);
ae9af12287  76) pd->n += last_nr - nr;
ae9af12287  77) display_progress(pd->progress, pd->n);
ae9af12287  78) pthread_mutex_unlock(&pd->mutex);
ae9af12287  79) last_nr = nr;
ae9af12287  93) struct progress_data *pd = p->progress;
ae9af12287  95) pthread_mutex_lock(&pd->mutex);
ae9af12287  96) display_progress(pd->progress, pd->n + last_nr);
ae9af12287  97) pthread_mutex_unlock(&pd->mutex);
ae9af12287 128) pd.progress = start_delayed_progress(_("Refreshing 
index"), index->cache_nr);
ae9af12287 129) pthread_mutex_init(&pd.mutex, NULL);
ae9af12287 140) p->progress = &pd;

prio-queue.c
2d181390f3 94) return queue->array[queue->nr - 1].data;

read-cache.c
ae9af12287 1490) progress = start_delayed_progress(_("Refresh index"),
ae9af12287 1491)   istate->cache_nr);
ae9af12287 1533) display_progress(progress, i);
ae9af12287 1566) display_progress(progress, istate->cache_nr);
ae9af12287 1567) stop_progress(&progress);
252d079cbd 1778) const unsigned char *cp = (const unsigned char *)name;
252d079cbd 1782) strip_len = decode_varint(&cp);
77ff1127a4 1783) if (previous_ce) {
77ff1127a4 1784) previous_len = previous_ce->ce_namelen;
77ff1127a4 1785) if (previous_len < strip_len)
252d079cbd 1786) die(_("malformed name field in the index, near path '%s'"),
77ff1127a4 1787) previous_ce->name);
77ff1127a4 1788) copy_len = previous_len - strip_len;
77ff1127a4 1790) copy_len = 0;
252d079cbd 1792) name = (const char *)cp;
252d079cbd 1798) len += copy_len;
252d079cbd 1819) if (copy_len)
252d079cbd 1820) memcpy(ce->name, previous_ce->name, copy_len);
252d079cbd 1821) memcpy(ce->name + copy_len, name, len + 1 - copy_len);
252d079cbd 1822) *ent_size = (name - ((char *)ondisk)) + len + 1 - copy_len;
abb4bb8384 1959) munmap((void *)p->mmap, p->mmap_size);
abb4bb8384 1960) die(_("index file corrupt"));
77ff1127a4 2001) mem_pool_init(&istate->ce_mem_pool,
77ff1127a4 2041) static void *load_cache_entries_thread(void *_data)
77ff1127a4 2043) struct load_cache_entries_thread_data *p = _data;
77ff1127a4 2047) for (i = p->ieot_start; i < p->ieot_start + 
p->ieot_blocks; i++) {
77ff1127a4 2048) p->consumed += load_cache_entry_block(p->istate, 
p->ce_mem_pool,
77ff1127a4 2049) p->offset, p->ieot->entries[i].nr, p->mmap, 
p->ieot->entries[i].offset, NULL);
77ff1127a4 2050) p->offset += p->ieot->entries[i].nr;
77ff1127a4 2052) return NULL;
77ff1127a4 2055) static unsigned long load_cache_entries_threaded(struct 
index_state *istate, const char *mmap, size_t mmap_size,
77ff1127a4 2060) unsigned long consumed = 0;
77ff1127a4 2063) if (istate->name_hash_initialized)
77ff1127a4 2064) BUG("the name hash isn't thread safe");
77ff1127a4 2066) mem_pool_init(&istate->ce_mem_pool, 0);
77ff1127a4 2069) if (nr_threads > ieot->nr)
77ff1127a4 2070) nr_threads = ieot->nr;
77ff1127a4 2071) data = xcalloc(nr_threads, sizeof(*data));
77ff1127a4 2073) offset = ieot_start = 0;
77ff1127a4 2074) ieot_blocks = DIV_ROUND_UP(ieot->nr, nr_threads);
77ff1127a4 2075) for (i = 0; i < nr_threads; i++) {
77ff1127a4 2076) struct load_cache_entries_thread_data *p = &data[i];
77ff1127a4 2079) if (ieot_start + ieot_blocks > ieot->nr)
77ff1127a4 2080) ieot_blocks = ieot->nr - ieot_start;
77ff1127a4 2082) p->istate = istate;
77ff1127a4 2083) p->offset = offset;
77ff1127a4 2084) p->mmap = mmap;
77ff1127a4 2085) p->ieot = ieot;
77ff1127a4 2086) p->ieot_start = ieot_start;
77ff1127a4 2087) p->ieot_blocks = ieot_blocks;
77ff1127a4 2090) nr = 0;
77ff1127a4 2091) for (j = p->ieot_start; j < p->ieot_start + 
p->ieot_blocks; j++)
77ff1127a4 2092) nr += p->ieot->entries[j].nr;
77ff1127a4 2093) if (istate->version == 4) {
77ff1127a4 2094) mem_pool_init(&p->ce_mem_pool,
77ff1127a4 2097) mem_pool_init(&p->ce_mem_pool,
77ff1127a4 2101) err = pthread_create(&p->pthread, NULL, 
load_cache_entries_thread, p);
77ff1127a4 2102) if (err)
77ff1127a4 2103) die(_("unable to create load_cache_entries thread: 
%s"), strerror(err));
77ff1127a4 2106) for (j = 0; j < ieot_blocks; j++)
77ff1127a4 2107) offset += ieot->entries[ieot_start + j].nr;
77ff1127a4 2108) ieot_start += ieot_blocks;
77ff1127a4 2111) for (i = 0; i < nr_threads; i++) {
77ff1127a4 2112) struct load_cache_entries_thread_data *p = &data[i];
77ff1127a4 2114) err = pthread_join(p->pthread, NULL);
77ff1127a4 2115) if (err)
77ff1127a4 2116) die(_("unable to join load_cache_entries thread: %s"), 
strerror(err));
77ff1127a4 2117) mem_pool_combine(istate->ce_mem_pool, p->ce_mem_pool);
77ff1127a4 2118) consumed += p->consumed;
77ff1127a4 2121) free(data);
77ff1127a4 2123) return consumed;
77ff1127a4 2192) nr_threads = cpus;
abb4bb8384 2196) extension_offset = read_eoie_extension(mmap, mmap_size);
abb4bb8384 2197) if (extension_offset) {
abb4bb8384 2200) p.src_offset = extension_offset;
abb4bb8384 2201) err = pthread_create(&p.pthread, NULL, 
load_index_extensions, &p);
abb4bb8384 2202) if (err)
abb4bb8384 2203) die(_("unable to create load_index_extensions thread: 
%s"), strerror(err));
abb4bb8384 2205) nr_threads--;
77ff1127a4 2214) ieot = read_ieot_extension(mmap, mmap_size, 
extension_offset);
77ff1127a4 2217) src_offset += load_cache_entries_threaded(istate, mmap, 
mmap_size, src_offset, nr_threads, ieot);
77ff1127a4 2218) free(ieot);
abb4bb8384 2232) int ret = pthread_join(p.pthread, NULL);
abb4bb8384 2233) if (ret)
abb4bb8384 2234) die(_("unable to join load_index_extensions thread: 
%s"), strerror(ret));
3255089ada 2775) ieot_blocks = nr_threads;
77ff1127a4 2776) if (ieot_blocks > istate->cache_nr)
77ff1127a4 2777) ieot_blocks = istate->cache_nr;
3255089ada 2785) ieot = xcalloc(1, sizeof(struct index_entry_offset_table)
3255089ada 2786) + (ieot_blocks * sizeof(struct index_entry_offset)));
77ff1127a4 2787) ieot_entries = DIV_ROUND_UP(entries, ieot_blocks);
3255089ada 2794) free(ieot);
3b1d9e045e 2795) return -1;
3255089ada 2821) ieot->entries[ieot->nr].nr = nr;
3255089ada 2822) ieot->entries[ieot->nr].offset = offset;
3255089ada 2823) ieot->nr++;
3255089ada 2829) if (previous_name)
3255089ada 2830) previous_name->buf[0] = 0;
3255089ada 2831) nr = 0;
3255089ada 2832) offset = lseek(newfd, 0, SEEK_CUR);
3255089ada 2833) if (offset < 0) {
3255089ada 2834) free(ieot);
3255089ada 2835) return -1;
3255089ada 2837) offset += write_buffer_len;
3255089ada 2847) ieot->entries[ieot->nr].nr = nr;
3255089ada 2848) ieot->entries[ieot->nr].offset = offset;
3255089ada 2849) ieot->nr++;
3255089ada 2861) free(ieot);
3b1d9e045e 2862) return -1;
3255089ada 2876) struct strbuf sb = STRBUF_INIT;
3255089ada 2878) write_ieot_extension(&sb, ieot);
3255089ada 2879) err = write_index_ext_header(&c, &eoie_c, newfd, 
CACHE_EXT_INDEXENTRYOFFSETTABLE, sb.len) < 0
3255089ada 2880) || ce_write(&c, newfd, sb.buf, sb.len) < 0;
3255089ada 2881) strbuf_release(&sb);
3255089ada 2882) free(ieot);
3255089ada 2883) if (err)
3255089ada 2884) return -1;
3b1d9e045e 3372) static size_t read_eoie_extension(const char *mmap, 
size_t mmap_size)
3b1d9e045e 3390) if (mmap_size < sizeof(struct cache_header) + 
EOIE_SIZE_WITH_HEADER + the_hash_algo->rawsz)
3b1d9e045e 3391) return 0;
3b1d9e045e 3394) index = eoie = mmap + mmap_size - EOIE_SIZE_WITH_HEADER 
- the_hash_algo->rawsz;
3b1d9e045e 3395) if (CACHE_EXT(index) != CACHE_EXT_ENDOFINDEXENTRIES)
3b1d9e045e 3396) return 0;
3b1d9e045e 3397) index += sizeof(uint32_t);
3b1d9e045e 3400) extsize = get_be32(index);
3b1d9e045e 3401) if (extsize != EOIE_SIZE)
3b1d9e045e 3402) return 0;
3b1d9e045e 3403) index += sizeof(uint32_t);
3b1d9e045e 3409) offset = get_be32(index);
3b1d9e045e 3410) if (mmap + offset < mmap + sizeof(struct cache_header))
3b1d9e045e 3411) return 0;
3b1d9e045e 3412) if (mmap + offset >= eoie)
3b1d9e045e 3413) return 0;
3b1d9e045e 3414) index += sizeof(uint32_t);
3b1d9e045e 3425) src_offset = offset;
3b1d9e045e 3426) the_hash_algo->init_fn(&c);
3b1d9e045e 3427) while (src_offset < mmap_size - the_hash_algo->rawsz - 
EOIE_SIZE_WITH_HEADER) {
3b1d9e045e 3435) memcpy(&extsize, mmap + src_offset + 4, 4);
3b1d9e045e 3436) extsize = ntohl(extsize);
3b1d9e045e 3439) if (src_offset + 8 + extsize < src_offset)
3b1d9e045e 3440) return 0;
3b1d9e045e 3442) the_hash_algo->update_fn(&c, mmap + src_offset, 8);
3b1d9e045e 3444) src_offset += 8;
3b1d9e045e 3445) src_offset += extsize;
3b1d9e045e 3447) the_hash_algo->final_fn(hash, &c);
3b1d9e045e 3448) if (!hasheq(hash, (const unsigned char *)index))
3b1d9e045e 3449) return 0;
3b1d9e045e 3452) if (src_offset != mmap_size - the_hash_algo->rawsz - 
EOIE_SIZE_WITH_HEADER)
3b1d9e045e 3453) return 0;
3b1d9e045e 3455) return offset;
3255089ada 3475) static struct index_entry_offset_table 
*read_ieot_extension(const char *mmap, size_t mmap_size, size_t offset)
3255089ada 3477)        const char *index = NULL;
3255089ada 3483)        if (!offset)
3255089ada 3484)        return NULL;
3255089ada 3485)        while (offset <= mmap_size - 
the_hash_algo->rawsz - 8) {
3255089ada 3486)        extsize = get_be32(mmap + offset + 4);
3255089ada 3487)        if (CACHE_EXT((mmap + offset)) == 
CACHE_EXT_INDEXENTRYOFFSETTABLE) {
3255089ada 3488)        index = mmap + offset + 4 + 4;
3255089ada 3489)        break;
3255089ada 3491)        offset += 8;
3255089ada 3492)        offset += extsize;
3255089ada 3494)        if (!index)
3255089ada 3495)        return NULL;
3255089ada 3498)        ext_version = get_be32(index);
3255089ada 3499)        if (ext_version != IEOT_VERSION) {
3255089ada 3500)        error("invalid IEOT version %d", ext_version);
3255089ada 3501)        return NULL;
3255089ada 3503)        index += sizeof(uint32_t);
3255089ada 3506)        nr = (extsize - sizeof(uint32_t)) / 
(sizeof(uint32_t) + sizeof(uint32_t));
3255089ada 3507)        if (!nr) {
3255089ada 3508)        error("invalid number of IEOT entries %d", nr);
3255089ada 3509)        return NULL;
3255089ada 3511)        ieot = xmalloc(sizeof(struct 
index_entry_offset_table)
3255089ada 3512)        + (nr * sizeof(struct index_entry_offset)));
3255089ada 3513)        ieot->nr = nr;
3255089ada 3514)        for (i = 0; i < nr; i++) {
3255089ada 3515)        ieot->entries[i].offset = get_be32(index);
3255089ada 3516)        index += sizeof(uint32_t);
3255089ada 3517)        ieot->entries[i].nr = get_be32(index);
3255089ada 3518)        index += sizeof(uint32_t);
3255089ada 3521)        return ieot;
3255089ada 3524) static void write_ieot_extension(struct strbuf *sb, 
struct index_entry_offset_table *ieot)
3255089ada 3530)        put_be32(&buffer, IEOT_VERSION);
3255089ada 3531)        strbuf_add(sb, &buffer, sizeof(uint32_t));
3255089ada 3534)        for (i = 0; i < ieot->nr; i++) {
3255089ada 3537)        put_be32(&buffer, ieot->entries[i].offset);
3255089ada 3538)        strbuf_add(sb, &buffer, sizeof(uint32_t));
3255089ada 3541)        put_be32(&buffer, ieot->entries[i].nr);
3255089ada 3542)        strbuf_add(sb, &buffer, sizeof(uint32_t));
3255089ada 3544) }

rebase-interactive.c
64a43cbd5d 61) return error_errno(_("could not read '%s'."), todo_file);
64a43cbd5d 65) strbuf_release(&buf);
64a43cbd5d 66) return -1;
a9f5476fbc 74) return error_errno(_("could not read '%s'."), todo_file);
a9f5476fbc 78) strbuf_release(&buf);
a9f5476fbc 79) return -1;
64a43cbd5d 85) return -1;

revision.c
4943d28849 2931) return;
4943d28849 2934) return;
4943d28849 2937) c->object.flags |= UNINTERESTING;
4943d28849 2940) return;
4943d28849 2943) mark_parents_uninteresting(c);
4943d28849 2966) return;
4943d28849 2969) return;
4943d28849 2974) return;
4943d28849 3019) info->topo_queue.compare = compare_commits_by_commit_date;
4943d28849 3020) break;
4943d28849 3022) init_author_date_slab(&info->author_date);
4943d28849 3023) info->topo_queue.compare = compare_commits_by_author_date;
4943d28849 3024) info->topo_queue.cb_data = &info->author_date;
4943d28849 3025) break;
4943d28849 3038) continue;
4943d28849 3048) record_author_date(&info->author_date, c);
6c04ff3001 3086) if (!revs->ignore_missing_links)
6c04ff3001 3087) die("Failed to traverse parents of commit %s",
4943d28849 3088) oid_to_hex(&commit->object.oid));
4943d28849 3096) continue;

sequencer.c
65850686cf 2276) return;
65850686cf 2373) write_file(rebase_path_quiet(), "%s\n", quiet);
2c58483a59 3371) return error(_("could not checkout %s"), commit);
4df66c40b0 3385) return error(_("%s: not a valid OID"), orig_head);
b97e187364 4748) return -1;
b97e187364 4751) return -1;
b97e187364 4757) return error_errno(_("could not read '%s'."), todo_file);
b97e187364 4760) todo_list_release(&todo_list);
b97e187364 4761) return error(_("unusable todo list: '%s'"), todo_file);
b97e187364 4780) todo_list_release(&todo_list);
b97e187364 4781) return -1;
b97e187364 4785) return error(_("could not copy '%s' to '%s'."), todo_file,
b97e187364 4789) return error(_("could not transform the todo list"));
b97e187364 4818) return error(_("could not transform the todo list"));
b97e187364 4821) return error(_("could not skip unnecessary pick 
commands"));
b97e187364 4827) return -1;

split-index.c
568f3a6073 310) const unsigned int ondisk_flags =
568f3a6073 314) ce_flags = ce->ce_flags;
568f3a6073 315) base_flags = base->ce_flags;
568f3a6073 317) ce->ce_flags   &= ondisk_flags;
568f3a6073 318) base->ce_flags &= ondisk_flags;
568f3a6073 319) ret = memcmp(&ce->ce_stat_data, &base->ce_stat_data,
568f3a6073 322) ce->ce_flags = ce_flags;
568f3a6073 323) base->ce_flags = base_flags;
568f3a6073 324) if (ret)
568f3a6073 325) ce->ce_flags |= CE_UPDATE_IN_BASE;

strbuf.c
f95736288a  127) --sb->len;

transport-helper.c
fb19d32f05  643) if (!data->connect && !data->stateless_connect)

transport.c
99bcb883cb  299) BUG("buffer must be empty at the end of handshake()");

Commits introducing uncovered code:
Alban Gruin      0af129b2e: rebase--interactive2: rewrite the submodes 
of interactive rebase in C
Alban Gruin      2c58483a5: rebase -i: rewrite setup_reflog_action() in C
Alban Gruin      34b47315d: rebase -i: move rebase--helper modes to 
rebase--interactive
Alban Gruin      4df66c40b: rebase -i: rewrite checkout_onto() in C
Alban Gruin      53bbcfbde: rebase -i: implement the main part of 
interactive rebase as a builtin
Alban Gruin      64a43cbd5: rebase -i: rewrite the edit-todo 
functionality in C
Alban Gruin      65850686c: rebase -i: rewrite write_basic_state() in C
Alban Gruin      a9f5476fb: sequencer: refactor append_todo_help() to 
write its message to a buffer
Alban Gruin      b97e18736: rebase -i: rewrite complete_action() in C
Ben Peart      3255089ad: ieot: add Index Entry Offset Table (IEOT) 
extension
Ben Peart      3b1d9e045: eoie: add End of Index Entry (EOIE) extension
Ben Peart      77ff1127a: read-cache: load cache entries on worker threads
Ben Peart      abb4bb838: read-cache: load cache extensions on a worker 
thread
Ben Peart      c780b9cfe: config: add new index.threads config setting
Derrick Stolee      2d181390f: prio-queue: add 'peek' operation
Derrick Stolee      4943d2884: revision.c: refactor basic topo-order logic
Derrick Stolee      6c04ff300: revision.c: begin refactoring 
--topo-order logic
Harald Nordgren      250edfa8c: bisect: create 'bisect_flags' parameter 
in find_bisection()
Jeff King      e43d2dcce: more oideq/hasheq conversions
Joel Teichroeb      31f109a36: stash: convert drop and clear to builtin
Joel Teichroeb      93871263d: stash: convert apply to builtin
Joel Teichroeb      b3513da4b: stash: convert pop to builtin
Johannes Schindelin      bc24382c2: builtin rebase: prepare for builtin 
rebase -i
Jonathan Tan      99bcb883c: transport: allow skipping of ref listing
Josh Steadmon      e001fd3a5: archive: implement protocol v2 archive command
Josh Steadmon      fb19d32f0: archive: allow archive over HTTP(S) with 
proto v2
Josh Steadmon      fb571c5fa: fuzz: add fuzz testing for packfile indices
Junio C Hamano      7bbc53a58: fetch: replace string-list used as a 
look-up table with a hashmap
Matthew DeVore      696aa7390: list-objects-filter: use BUG rather than die
Matthew DeVore      7c0fe330d: rev-list: handle missing tree objects 
properly
Matthew DeVore      bc5975d24: list-objects-filter: implement filter tree:0
Matthew DeVore      cc0b05a4c: list-objects-filter-options: do not 
over-strbuf_init
Matthew DeVore      f447a499d: list-objects: store common func args in 
struct
Nguyễn Thái Ngọc Duy      252d079cb: read-cache.c: optimize reading 
index format v4
Nguyễn Thái Ngọc Duy      26c7d0678: help -a: improve and make --verbose 
default
Nguyễn Thái Ngọc Duy      ae9af1228: status: show progress bar if 
refreshing the index takes too long
Paul-Sebastian Ungureanu      0ac06fb81: stash: convert store to builtin
Paul-Sebastian Ungureanu      129f0b0a0: stash: convert 
`stash--helper.c` into `stash.c`
Paul-Sebastian Ungureanu      48c061fa4: stash: convert push to builtin
Paul-Sebastian Ungureanu      8002b9e62: stash: make push -q quiet
Paul-Sebastian Ungureanu      8ceb24b2c: stash: convert show to builtin
Paul-Sebastian Ungureanu      f6f191b3f: stash: convert create to builtin
Pratik Karki      002ee2fe6: builtin rebase: support `keep-empty` option
Pratik Karki      0eabf4b95: builtin rebase: stop if `git am` is in progress
Pratik Karki      12026a412: builtin rebase: support `--gpg-sign` option
Pratik Karki      122420c29: builtin rebase: support --skip
Pratik Karki      1ed9c14ff: builtin rebase: support --force-rebase
Pratik Karki      3c3588c7d: builtin rebase: support 
--rebase-merges[=[no-]rebase-cousins]
Pratik Karki      51e9ea6da: builtin rebase: support --edit-todo and 
--show-current-patch
Pratik Karki      53f9e5be9: builtin rebase: support `ignore-date` option
Pratik Karki      55071ea24: rebase: start implementing it as a builtin
Pratik Karki      5a6149453: builtin rebase: support --quit
Pratik Karki      5e5d96197: builtin rebase: support --abort
Pratik Karki      6defce2b0: builtin rebase: support `--autostash` option
Pratik Karki      73d51ed0a: builtin rebase: support --signoff
Pratik Karki      7998dbe1e: builtin rebase: support `-C` and 
`--whitespace=<type>`
Pratik Karki      9a48a615b: builtin rebase: try to fast forward when 
possible
Pratik Karki      9dba809a6: builtin rebase: support --root
Pratik Karki      ac7f467fe: builtin/rebase: support running "git rebase 
<upstream>"
Pratik Karki      ba1905a5f: builtin rebase: add support for custom 
merge strategies
Pratik Karki      bff014dac: builtin rebase: support the `verbose` and 
`diffstat` options
Pratik Karki      c54dacb50: builtin rebase: start a new rebase only if 
none is in progress
Pratik Karki      cda614e48: builtin rebase: show progress when 
connected to a terminal
Pratik Karki      d4c569f8f: builtin rebase: only store fully-qualified 
refs in `options.head_name`
Pratik Karki      e0333e5c6: builtin rebase: require a clean worktree
Pratik Karki      e65123a71: builtin rebase: support `git rebase 
<upstream> <switch-to>`
Pratik Karki      ead98c111: builtin rebase: support --rerere-autoupdate
Pratik Karki      f28d40d3a: builtin rebase: support --onto
Pratik Karki      f95736288: builtin rebase: support --continue
René Scharfe      8b2f8cbcb: oidset: use khash
SZEDER Gábor      568f3a607: split-index: don't compare stat data of 
entries already marked for split index

Uncovered code new in next, compared to master. Build logs at [2]
-----------------------------------------------------------------

blame.c
a470beea39  113)  !strcmp(r->index->cache[-1 - pos]->name, path))
a470beea39  272) int pos = index_name_pos(r->index, path, len);
a470beea39  274) mode = r->index->cache[pos]->ce_mode;

builtin/am.c
2abf350385 1414) repo_init_revisions(the_repository, &rev_info, NULL);

builtin/gc.c
3029970275 builtin/gc.c 461) ret = error_errno(_("cannot stat '%s'"), 
gc_log_path);
3029970275 builtin/gc.c 470) ret = error_errno(_("cannot read '%s'"), 
gc_log_path);
fec2ed2187 builtin/gc.c 495) die(FAILED_RUN, pack_refs_cmd.argv[0]);
fec2ed2187 builtin/gc.c 498) die(FAILED_RUN, reflog.argv[0]);a
3029970275 builtin/gc.c 585) exit(128);
fec2ed2187 builtin/gc.c 637) die(FAILED_RUN, repack.argv[0]);
fec2ed2187 builtin/gc.c 647) die(FAILED_RUN, prune.argv[0]);
fec2ed2187 builtin/gc.c 654) die(FAILED_RUN, prune_worktrees.argv[0]);
fec2ed2187 builtin/gc.c 658) die(FAILED_RUN, rerere.argv[0]);

builtin/pack-objects.c
2fa233a554 builtin/pack-objects.c 1512) hashcpy(base_oid.hash, base_sha1);
2fa233a554 builtin/pack-objects.c 1513) if 
(!in_same_island(&delta->idx.oid, &base_oid))
2fa233a554 builtin/pack-objects.c 1514) return 0;

commit-graph.c
20fd6d5799   79) return 0;

diff.c
b78ea5fc35 4117) add_external_diff_name(o->repo, &argv, other, two);

refs.c
4a6067cda5 1405) return 0;

revision.c
2abf350385 1526) if (ce_path_match(istate, ce, &revs->prune_data, NULL)) {
2abf350385 1532) while ((i+1 < istate->cache_nr) &&
2abf350385 1533)        ce_same_name(ce, istate->cache[i+1]))

wt-status.c
f3bd35fa0d  671) s->committable = 1;
73ba5d78b4 1953) if (s->state.rebase_in_progress ||
73ba5d78b4 1954)     s->state.rebase_interactive_in_progress)
73ba5d78b4 1955) branch_name = s->state.onto;
73ba5d78b4 1956) else if (s->state.detached_from)
73ba5d78b4 1957) branch_name = s->state.detached_from;

Commits introducing uncovered code:
Derrick Stolee      20fd6d579: commit-graph: not compatible with grafts
Jeff King      2fa233a55: pack-objects: handle island check for 
"external" delta base
Jonathan Nieder      302997027: gc: do not return error for prior errors 
in daemonized mode
Jonathan Nieder      fec2ed218: gc: exit with status 128 on failure
Nguyễn Thái Ngọc Duy      2abf35038: revision.c: remove implicit 
dependency on the_index
Nguyễn Thái Ngọc Duy      a470beea3: blame.c: rename "repo" argument to "r"
Nguyễn Thái Ngọc Duy      b78ea5fc3: diff.c: reduce implicit dependency 
on the_index
Stefan Beller      4a6067cda: refs.c: migrate internal ref iteration to 
pass thru repository argument
Stephen P. Smith      73ba5d78b: roll wt_status_state into wt_status and 
populate in the collect phase
Stephen P. Smith      f3bd35fa0: wt-status.c: set the committable flag 
in the collect phase


Uncovered code new in master, compared to maint. Build logs at [3]
------------------------------------------------------------------

builtin/checkout.c
fa655d8411 builtin/checkout.c  537) return 0;
fa655d8411 builtin/checkout.c  951) return error(_("index file corrupt"));

builtin/commit.c
859fdc0c3c builtin/commit.c 1657) 
write_commit_graph_reachable(get_object_directory(), 0);

builtin/difftool.c
4a7e27e957 441) if (oideq(&loid, &roid))

builtin/fsck.c
454ea2e4d7 builtin/fsck.c 743) for (p = get_all_packs(the_repository); p;
66ec0390e7 builtin/fsck.c 862) midx_argv[2] = "--object-dir";
66ec0390e7 builtin/fsck.c 863) midx_argv[3] = alt->path;
66ec0390e7 builtin/fsck.c 864) if (run_command(&midx_verify))
66ec0390e7 builtin/fsck.c 865) errors_found |= ERROR_COMMIT_GRAPH;

builtin/log.c
2e6fd71a52 builtin/log.c 1461) die(_("failed to infer range-diff ranges"));
ee6cbf712e builtin/log.c 1807) die(_("--interdiff requires 
--cover-letter or single patch"));
8631bf1cdd builtin/log.c 1817) else if (!rdiff_prev)
8631bf1cdd builtin/log.c 1818) die(_("--creation-factor requires 
--range-diff"));
40ce41604d builtin/log.c 1822) die(_("--range-diff requires 
--cover-letter or single patch"));

builtin/multi-pack-index.c
6d68e6a461 35) usage_with_options(builtin_multi_pack_index_usage,
6d68e6a461 39) die(_("too many arguments"));
6d68e6a461 48) die(_("unrecognized verb: %s"), argv[0]);

builtin/pack-objects.c
6a22d52126 builtin/pack-objects.c 1079) if (fill_midx_entry(oid, &e, m)) {
6a22d52126 builtin/pack-objects.c 1080) struct packed_git *p = e.p;
6a22d52126 builtin/pack-objects.c 1083) if (p == *found_pack)
6a22d52126 builtin/pack-objects.c 1084) offset = *found_offset;
6a22d52126 builtin/pack-objects.c 1086) offset = 
find_pack_entry_one(oid->hash, p);
6a22d52126 builtin/pack-objects.c 1088) if (offset) {
6a22d52126 builtin/pack-objects.c 1089) if (!*found_pack) {
6a22d52126 builtin/pack-objects.c 1090) if (!is_pack_valid(p))
6a22d52126 builtin/pack-objects.c 1091) continue;
6a22d52126 builtin/pack-objects.c 1092) *found_offset = offset;
6a22d52126 builtin/pack-objects.c 1093) *found_pack = p;
6a22d52126 builtin/pack-objects.c 1095) want = 
want_found_object(exclude, p);
6a22d52126 builtin/pack-objects.c 1096) if (want != -1)
6a22d52126 builtin/pack-objects.c 1097) return want;
28b8a73080 builtin/pack-objects.c 2778) depth++;
108f530385 builtin/pack-objects.c 2782) oe_set_tree_depth(&to_pack, ent, 
depth);
454ea2e4d7 builtin/pack-objects.c 2966) p = get_all_packs(the_repository);

builtin/pack-redundant.c
454ea2e4d7 builtin/pack-redundant.c 580) struct packed_git *p = 
get_all_packs(the_repository);
454ea2e4d7 builtin/pack-redundant.c 595) struct packed_git *p = 
get_all_packs(the_repository);

builtin/remote.c
5025425dff builtin/remote.c  864) return error(_("No such remote: 
'%s'"), name);

builtin/repack.c
16d75fa48d  48) use_delta_islands = git_config_bool(var, value);
16d75fa48d  49) return 0;

builtin/rerere.c
2373b65059 builtin/rerere.c  78) warning(_("'git rerere forget' without 
paths is deprecated"));
2373b65059 builtin/rerere.c 110) die(_("unable to generate diff for 
'%s'"), rerere_path(id, NULL));

builtin/show-branch.c
9001dc2a74 builtin/show-branch.c 430) if (get_oid(refname + ofs, &tmp) 
|| !oideq(&tmp, oid))

builtin/submodule--helper.c
ee69b2a90c 1462) die(_("Invalid update mode '%s' for submodule path '%s'"),
ee69b2a90c 1466) die(_("Invalid update mode '%s' configured for 
submodule path '%s'"),
ee69b2a90c 1469) trace_printf("loaded thing");
ee69b2a90c 1470) out->type = sub->update_strategy.type;
ee69b2a90c 1471) out->command = sub->update_strategy.command;
ee69b2a90c 1491) die("submodule--helper update-module-clone expects 
<just-cloned> <path> [<update>]");
74d4731da1 2033) BUG("submodule--helper connect-gitdir-workingtree 
<name> <path>");
74d4731da1 2039) BUG("We could get the submodule handle before?");
74d4731da1 2042) die(_("could not get a repository handle for submodule 
'%s'"), path);

builtin/unpack-objects.c
4a7e27e957 builtin/unpack-objects.c 306) if (oideq(&info->base_oid, 
&obj_list[nr].oid) ||

builtin/update-index.c
4a7e27e957 builtin/update-index.c  672) if (oideq(&ce_2->oid, &ce_3->oid) &&

builtin/worktree.c
e5353bef55  60) error_errno(_("failed to delete '%s'"), sb.buf);
e19831c94f 251)     die(_("unable to re-add worktree '%s'"), path);
68a6b3a1bd 793) die(_("cannot move a locked working tree, lock reason: 
%s\nuse 'move -f -f' to override or unlock first"),
f4143101cb 906) die(_("cannot remove a locked working tree, lock reason: 
%s\nuse 'remove -f -f' to override or unlock first"),

cache-tree.c
4592e6080f 762) BUG("%s with flags 0x%x should not be in cache-tree",
4592e6080f 770) BUG("bad subtree '%.*s'", entlen, name);
4592e6080f 785) BUG("cache-tree for path %.*s does not match. "

commit-graph.c
6cc017431c  247) return 0;

commit-reach.c
5227c38566  63) BUG("bad generation skip %8x > %8x at %s",
5227c38566 134) return ret;
5227c38566 282) return 1;
5227c38566 314) return ret;
5227c38566 317) return ret;
5227c38566 323) return ret;
1d614d41e5 395) return 0;
1d614d41e5 401) return 0;
1d614d41e5 405) return 0;
920f93ca1c 459) return CONTAINS_NO;
920f93ca1c 484) cutoff = c->generation;
b67f6b26e3 559) continue;
b67f6b26e3 569) from->objects[i].item->flags |= assign_flag;
b67f6b26e3 570) continue;
b67f6b26e3 576) result = 0;
b67f6b26e3 577) goto cleanup;

delta-islands.c
c8d521faf7  53) memcpy(b, old, size);
c8d521faf7  73) return 1;
c8d521faf7 118) return 0;
c8d521faf7 130) return 0;
c8d521faf7 187) b->refcount--;
c8d521faf7 188) b = kh_value(island_marks, pos) = island_bitmap_new(b);
c8d521faf7 202) continue;
c8d521faf7 212) obj = ((struct tag *)obj)->tagged;
c8d521faf7 213) if (obj) {
c8d521faf7 214) parse_object(the_repository, &obj->oid);
c8d521faf7 215) marks = create_or_get_island_marks(obj);
c8d521faf7 216) island_bitmap_set(marks, island_counter);
c8d521faf7 248) return;
c8d521faf7 268) progress_state = start_progress(_("Propagating island 
marks"), nr);
c8d521faf7 286) die(_("bad tree object %s"), oid_to_hex(&ent->idx.oid));
c8d521faf7 293) continue;
c8d521faf7 297) continue;
c8d521faf7 321) return config_error_nonbool(k);
c8d521faf7 330) die(_("failed to load island regex for '%s': %s"), k, 
re.buf);
c8d521faf7 386) warning(_("island regex from config has "
c8d521faf7 397) strbuf_addch(&island_name, '-');
c8d521faf7 433) continue;
c8d521faf7 436) list[dst] = list[src];

diff-lib.c
9001dc2a74 diff-lib.c 345)     (!oideq(oid, &old_entry->oid) || 
!oideq(&old_entry->oid, &new_entry->oid))) {

dir.c
c46c406ae1 2275) trace_performance_leave("read directory %.*s", len, path);

entry.c
b878579ae7 402) static void mark_colliding_entries(const struct checkout 
*state,
b878579ae7 405) int i, trust_ino = check_stat;
b878579ae7 411) ce->ce_flags |= CE_MATCHED;
b878579ae7 413) for (i = 0; i < state->istate->cache_nr; i++) {
b878579ae7 414) struct cache_entry *dup = state->istate->cache[i];
b878579ae7 416) if (dup == ce)
b878579ae7 417) break;
b878579ae7 419) if (dup->ce_flags & (CE_MATCHED | CE_VALID | 
CE_SKIP_WORKTREE))
b878579ae7 420) continue;
b878579ae7 422) if ((trust_ino && dup->ce_stat_data.sd_ino == st->st_ino) ||
b878579ae7 423)     (!trust_ino && !fspathcmp(ce->name, dup->name))) {
b878579ae7 424) dup->ce_flags |= CE_MATCHED;
b878579ae7 425) break;
b878579ae7 428) }
b878579ae7 488) mark_colliding_entries(state, ce, &st);

fsck.c
fb8952077d  214) die_errno("Could not read '%s'", path);

ll-merge.c
d64324cb60 379) marker_size = DEFAULT_CONFLICT_MARKER_SIZE;

log-tree.c
4a7e27e957 477) if (oideq(&parent->item->object.oid, oid))

mailinfo.c
3aa4d81f88  992) len--;
3aa4d81f88  998) handle_filter(mi, prev);
3aa4d81f88  999) strbuf_reset(prev);
3aa4d81f88 1090) handle_filter(mi, &prev);

midx.c
4d80560c54   58) error_errno(_("failed to read %s"), midx_name);
4d80560c54   59) goto cleanup_fail;
4d80560c54   65) error(_("multi-pack-index file %s is too small"), 
midx_name);
4d80560c54   66) goto cleanup_fail;
0d5b3a5ef7  146) die(_("multi-pack-index missing required OID lookup 
chunk"));
662148c435  148) die(_("multi-pack-index missing required object offsets 
chunk"));
4d80560c54  173) munmap(midx_map, midx_size);
4d80560c54  175) close(fd);
a40498a126  188) close_pack(m->packs[i]);
a40498a126  189) free(m->packs);
3715a6335c  262) return 0;
3715a6335c  278) return 0;
c39b02ae0a  283) nth_midxed_object_oid(&oid, m, pos);
c39b02ae0a  284) for (i = 0; i < p->num_bad_objects; i++)
c39b02ae0a  285) if (!hashcmp(oid.hash,
c39b02ae0a  286)      p->bad_object_sha1 + the_hash_algo->rawsz * i))
c39b02ae0a  287) return 0;
c4d25228eb  341) return 1;
396f257018  398) warning(_("failed to add packfile '%s'"),
396f257018  400) return;
fe1ed56f5e  404) warning(_("failed to open pack-index '%s'"),
fe1ed56f5e  406) close_pack(packs->list[packs->nr]);
fe1ed56f5e  407) FREE_AND_NULL(packs->list[packs->nr]);
fe1ed56f5e  408) return;
a40498a126  481) return 1;
fe1ed56f5e  498) die(_("failed to locate object %d in packfile"), 
cur_object);
32f3c541e3  611) BUG("incorrect pack-file order: %s before %s",
0d5b3a5ef7  673) BUG("OIDs not in order: %s >= %s",
662148c435  700) BUG("object %s requires a large offset (%"PRIx64") but 
the MIDX is not writing large offsets!",
fc59e74844  754) die_errno(_("unable to create leading directories of %s"),
a40498a126  784) goto cleanup;
32f3c541e3  843) BUG("incorrect chunk offsets: %"PRIu64" before %"PRIu64,
32f3c541e3  848) BUG("chunk offset %"PRIu64" is not properly aligned",
32f3c541e3  860) BUG("incorrect chunk offset (%"PRIu64" != %"PRIu64") 
for chunk id %"PRIx32,
32f3c541e3  887) BUG("trying to write unknown chunk id %"PRIx32,
32f3c541e3  893) BUG("incorrect final offset %"PRIu64" != %"PRIu64,
525e18c04b  923) die(_("failed to clear multi-pack-index at %s"), midx);
56ee7ff156  949) return 0;
cc6af73c02  990) midx_report(_("failed to load pack-index for packfile %s"),
cc6af73c02  991)     e.p->pack_name);
cc6af73c02  992) break;

pack-bitmap.c
30cdc33fba 1130) return 0;

pack-objects.c
108f530385 169) REALLOC_ARRAY(pdata->tree_depth, pdata->nr_alloc);
fe0ac2fb7f 172) REALLOC_ARRAY(pdata->layer, pdata->nr_alloc);
108f530385 189) pdata->tree_depth[pdata->nr_objects - 1] = 0;
fe0ac2fb7f 192) pdata->layer[pdata->nr_objects - 1] = 0;

packfile.c
fe1ed56f5e  205) if (open_pack_index(p))
fe1ed56f5e  206) return 0;
fe1ed56f5e  207) level1_ofs = p->index_data;
17c35c8969  479) break;
17c35c8969  483) return error("packfile %s index unavailable", 
p->pack_name);
17c35c8969  537) return 0;

refs/packed-backend.c
9001dc2a74 1163) } else if (!oideq(&update->old_oid, iter->oid)) {

refs/ref-cache.c
9001dc2a74 275) if (!oideq(&ref1->u.value.oid, &ref2->u.value.oid))

rerere.c
2373b65059  216) die(_("corrupt MERGE_RR"));
2373b65059  225) die(_("corrupt MERGE_RR"));
2373b65059  228) die(_("corrupt MERGE_RR"));
2373b65059  263) die(_("unable to write rerere record"));
2373b65059  268) die(_("unable to write rerere record"));
4af32207bc  375) break;
4af32207bc  379) strbuf_addbuf(&two, &conflict);
c0f16f8e14  383) break;
c0f16f8e14  387) break;
c0f16f8e14  391) break;
2373b65059  476) return error_errno(_("could not open '%s'"), path);
2373b65059  481) error_errno(_("could not write '%s'"), output);
2373b65059  491) error(_("there were errors while writing '%s' (%s)"),
2373b65059  494) io.io.wrerror = error_errno(_("failed to flush '%s'"), 
path);
2373b65059  560) return error(_("index file corrupt"));
2373b65059  593) return error(_("index file corrupt"));
2373b65059  676) warning_errno(_("failed utime() on '%s'"),
2373b65059  682) return error_errno(_("could not open '%s'"), path);
2373b65059  684) error_errno(_("could not write '%s'"), path);
2373b65059  686) return error_errno(_("writing '%s' failed"), path);
2373b65059  712) die(_("unable to write new index file"));
2373b65059  794) die_errno(_("cannot unlink stray '%s'"), path);
2373b65059 1043) error(_("failed to update conflicted state in '%s'"), 
path);
2373b65059 1061) error(_("no remembered resolution for '%s'"), path);
2373b65059 1063) error_errno(_("cannot unlink '%s'"), filename);
2373b65059 1097) return error(_("index file corrupt"));
2373b65059 1185) die_errno(_("unable to open rr-cache directory"));

revision.c
4a7e27e957 3241)     oideq(&p->item->object.oid, &commit->object.oid))

sha1-file.c
67947c34ae sha1-file.c 2216) if (!hasheq(expected_sha1, real_sha1)) {

sha1-name.c
8aac67a174 sha1-name.c  154) static void unique_in_midx(struct 
multi_pack_index *m,
8aac67a174 sha1-name.c  157) uint32_t num, i, first = 0;
8aac67a174 sha1-name.c  158) const struct object_id *current = NULL;
8aac67a174 sha1-name.c  159) num = m->num_objects;
8aac67a174 sha1-name.c  161) if (!num)
8aac67a174 sha1-name.c  162) return;
8aac67a174 sha1-name.c  164) bsearch_midx(&ds->bin_pfx, m, &first);
8aac67a174 sha1-name.c  171) for (i = first; i < num && !ds->ambiguous; 
i++) {
8aac67a174 sha1-name.c  173) current = nth_midxed_object_oid(&oid, m, i);
8aac67a174 sha1-name.c  174) if (!match_sha(ds->len, ds->bin_pfx.hash, 
current->hash))
8aac67a174 sha1-name.c  175) break;
8aac67a174 sha1-name.c  176) update_candidates(ds, current);
8aac67a174 sha1-name.c  212)      m = m->next)
8aac67a174 sha1-name.c  213) unique_in_midx(m, ds);
8aac67a174 sha1-name.c  573) return;

trace.c
c46c406ae1 189) now = getnanotime();
c46c406ae1 190) perf_start_times[perf_indent] = now;
c46c406ae1 191) if (perf_indent + 1 < ARRAY_SIZE(perf_start_times))
c46c406ae1 192) perf_indent++;
c46c406ae1 194) BUG("Too deep indentation");
c46c406ae1 195) return now;
c46c406ae1 211) if (perf_indent >= strlen(space))
c46c406ae1 212) BUG("Too deep indentation");
c46c406ae1 214) strbuf_addf(&buf, ":%.*s ", perf_indent, space);
c46c406ae1 317) void trace_performance_leave_fl(const char *file, int line,
c46c406ae1 323) if (perf_indent)
c46c406ae1 324) perf_indent--;
c46c406ae1 326) if (!format) /* Allow callers to leave without tracing 
anything */
c46c406ae1 327) return;
c46c406ae1 329) since = perf_start_times[perf_indent];
c46c406ae1 330) va_start(ap, format);
c46c406ae1 331) trace_performance_vprintf_fl(file, line, nanos - since, 
format, ap);
c46c406ae1 332) va_end(ap);
c46c406ae1 477) trace_performance_leave("git command:%s", command_line.buf);
c46c406ae1 485) if (!command_line.len)
c46c406ae1 490) trace_performance_enter();

unpack-trees.c
b878579ae7  360) string_list_append(&list, ce->name);
b878579ae7  361) ce->ce_flags &= ~CE_MATCHED;
b878579ae7  368) warning(_("the following paths have collided (e.g. 
case-sensitive paths\n"
b878579ae7  372) for (i = 0; i < list.nr; i++)
b878579ae7  373) fprintf(stderr, "  '%s'\n", list.items[i].string);
b4da37380b  715) BUG("This is a directory and should not exist in index");
b4da37380b  719) BUG("pos must point at the first entry in this directory");
b4da37380b  740) BUG("We need cache-tree to do this optimization");
f1e11c6510  777) free(tree_ce);
b4da37380b  778) return rc;
b4da37380b  785) printf("Unpacked %d entries from %s to %s using 
cache-tree\n",
b4da37380b  787)        o->src_index->cache[pos]->name,
b4da37380b  788)        o->src_index->cache[pos + nr_entries - 1]->name);
b4da37380b  811) BUG("Wrong condition to get here buddy");

Commits introducing uncovered code:
Ben Peart      fa655d841: checkout: optimize "git checkout -b <new_branch>"
Christian Couder      108f53038: pack-objects: move tree_depth into 
'struct packing_data'
Christian Couder      fe0ac2fb7: pack-objects: move 'layer' into 'struct 
packing_data'
Derrick Stolee      0d5b3a5ef: midx: write object ids in a chunk
Derrick Stolee      17c35c896: packfile: skip loading index if in 
multi-pack-index
Derrick Stolee      1d614d41e: commit-reach: move ref_newer from remote.c
Derrick Stolee      32f3c541e: multi-pack-index: write pack names in chunk
Derrick Stolee      3715a6335: midx: read objects from multi-pack-index
Derrick Stolee      396f25701: multi-pack-index: read packfile list
Derrick Stolee      454ea2e4d: treewide: use get_all_packs
Derrick Stolee      4d80560c5: multi-pack-index: load into memory
Derrick Stolee      5227c3856: commit-reach: move walk methods from commit.c
Derrick Stolee      525e18c04: midx: clear midx on repack
Derrick Stolee      56ee7ff15: multi-pack-index: add 'verify' verb
Derrick Stolee      662148c43: midx: write object offsets
Derrick Stolee      66ec0390e: fsck: verify multi-pack-index
Derrick Stolee      6a22d5212: pack-objects: consider packs in 
multi-pack-index
Derrick Stolee      6cc017431: commit-reach: use can_all_from_reach
Derrick Stolee      6d68e6a46: multi-pack-index: provide more helpful 
usage info
Derrick Stolee      859fdc0c3: commit-graph: define GIT_TEST_COMMIT_GRAPH
Derrick Stolee      8aac67a17: midx: use midx in abbreviation calculations
Derrick Stolee      920f93ca1: commit-reach: move commit_contains from 
ref-filter
Derrick Stolee      a40498a12: midx: use existing midx when writing new one
Derrick Stolee      b67f6b26e: commit-reach: properly peel tags
Derrick Stolee      c39b02ae0: midx: mark bad packed objects
Derrick Stolee      c4d25228e: config: create core.multiPackIndex setting
Derrick Stolee      cc6af73c0: multi-pack-index: verify object offsets
Derrick Stolee      fc59e7484: midx: write header information to lockfile
Derrick Stolee      fe1ed56f5: midx: sort and deduplicate objects from 
packfiles
Duy Nguyen      b878579ae: clone: report duplicate entries on 
case-insensitive filesystems
Eric Sunshine      2e6fd71a5: format-patch: extend --range-diff to 
accept revision range
Eric Sunshine      40ce41604: format-patch: allow --range-diff to apply 
to a lone-patch
Eric Sunshine      68a6b3a1b: worktree: teach 'move' to override lock 
when --force given twice
Eric Sunshine      8631bf1cd: format-patch: add --creation-factor tweak 
for --range-diff
Eric Sunshine      e19831c94: worktree: teach 'add' to respect --force 
for registered but missing path
Eric Sunshine      e5353bef5: worktree: move delete_git_dir() earlier in 
file for upcoming new callers
Eric Sunshine      ee6cbf712: format-patch: allow --interdiff to apply 
to a lone-patch
Eric Sunshine      f4143101c: worktree: teach 'remove' to override lock 
when --force given twice
Jeff King      16d75fa48: repack: add delta-islands support
Jeff King      28b8a7308: pack-objects: add delta-islands support
Jeff King      30cdc33fb: pack-bitmap: save "have" bitmap from walk
Jeff King      4a7e27e95: convert "oidcmp() == 0" to oideq()
Jeff King      67947c34a: convert "hashcmp() != 0" to "!hasheq()"
Jeff King      9001dc2a7: convert "oidcmp() != 0" to "!oideq()"
Jeff King      c8d521faf: Add delta-islands.{c,h}
Nguyễn Thái Ngọc Duy      4592e6080: cache-tree: verify valid cache-tree 
in the test suite
Nguyễn Thái Ngọc Duy      b4da37380: unpack-trees: optimize walking same 
trees with cache-tree
Nguyễn Thái Ngọc Duy      c46c406ae: trace.h: support nested performance 
tracing
Nguyễn Thái Ngọc Duy      f1e11c651: unpack-trees: reduce malloc in 
cache-tree walk
René Scharfe      3aa4d81f8: mailinfo: support format=flowed
René Scharfe      fb8952077: fsck: use strbuf_getline() to read skiplist 
file
Shulhan      5025425df: builtin/remote: quote remote name on error to 
display empty name
Stefan Beller      74d4731da: submodule--helper: replace 
connect-gitdir-workingtree by ensure-core-worktree
Stefan Beller      ee69b2a90: submodule--helper: introduce new 
update-module-mode helper
Thomas Gummerer      2373b6505: rerere: mark strings for translation
Thomas Gummerer      4af32207b: rerere: teach rerere to handle nested 
conflicts
Thomas Gummerer      c0f16f8e1: rerere: factor out handle_conflict function
Torsten Bögershausen      d64324cb6: Make git_check_attr() a void function


[1] https://dev.azure.com/git/git/_build/results?buildId=172&view=logs
     Build running coverage-test for 'jch' and coverage-diff.sh against 
'next'

[2] https://git.visualstudio.com/git/_build/results?buildId=166&view=logs
     Build running coverage-test for 'next' and coverage-diff.sh against 
'master'

[3] https://dev.azure.com/git/git/_build/results?buildId=171&view=logs
     Build running coverage-test for 'master' and coverage-diff.sh 
against 'maint'

^ permalink raw reply	[relevance 1%]

* [GSoC][PATCH v8 12/20] rebase -i: remove unused modes and functions
  @ 2018-09-27 21:56  6%             ` Alban Gruin
  0 siblings, 0 replies; 200+ results
From: Alban Gruin @ 2018-09-27 21:56 UTC (permalink / raw)
  To: git
  Cc: Stefan Beller, Christian Couder, Pratik Karki,
	Johannes Schindelin, phillip.wood, gitster, Alban Gruin

This removes the modes `--skip-unnecessary-picks`, `--append-todo-help`,
and `--checkout-onto` from rebase--helper.c, the functions of
git-rebase--interactive.sh that were rendered useless by the rewrite of
complete_action(), and append_todo_help_to_file() from
rebase-interactive.c.

skip_unnecessary_picks() and checkout_onto() becomes static, as they are
only used inside of the sequencer.

Signed-off-by: Alban Gruin <alban.gruin@gmail.com>
---
No changes since v7.

 builtin/rebase--helper.c   | 23 ++----------------
 git-rebase--interactive.sh | 50 --------------------------------------
 rebase-interactive.c       | 22 -----------------
 rebase-interactive.h       |  1 -
 sequencer.c                |  8 +++---
 sequencer.h                |  4 ---
 6 files changed, 6 insertions(+), 102 deletions(-)

diff --git a/builtin/rebase--helper.c b/builtin/rebase--helper.c
index 01b3333958..e1460136f5 100644
--- a/builtin/rebase--helper.c
+++ b/builtin/rebase--helper.c
@@ -17,9 +17,8 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
 	int abbreviate_commands = 0, rebase_cousins = -1;
 	enum {
 		CONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_OIDS, EXPAND_OIDS,
-		CHECK_TODO_LIST, SKIP_UNNECESSARY_PICKS, REARRANGE_SQUASH,
-		ADD_EXEC, APPEND_TODO_HELP, EDIT_TODO, PREPARE_BRANCH,
-		CHECKOUT_ONTO, COMPLETE_ACTION
+		CHECK_TODO_LIST, REARRANGE_SQUASH, ADD_EXEC, EDIT_TODO, PREPARE_BRANCH,
+		COMPLETE_ACTION
 	} command = 0;
 	struct option options[] = {
 		OPT_BOOL(0, "ff", &opts.allow_ff, N_("allow fast-forward")),
@@ -44,21 +43,15 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
 			N_("expand commit ids in the todo list"), EXPAND_OIDS),
 		OPT_CMDMODE(0, "check-todo-list", &command,
 			N_("check the todo list"), CHECK_TODO_LIST),
-		OPT_CMDMODE(0, "skip-unnecessary-picks", &command,
-			N_("skip unnecessary picks"), SKIP_UNNECESSARY_PICKS),
 		OPT_CMDMODE(0, "rearrange-squash", &command,
 			N_("rearrange fixup/squash lines"), REARRANGE_SQUASH),
 		OPT_CMDMODE(0, "add-exec-commands", &command,
 			N_("insert exec commands in todo list"), ADD_EXEC),
-		OPT_CMDMODE(0, "append-todo-help", &command,
-			    N_("insert the help in the todo list"), APPEND_TODO_HELP),
 		OPT_CMDMODE(0, "edit-todo", &command,
 			    N_("edit the todo list during an interactive rebase"),
 			    EDIT_TODO),
 		OPT_CMDMODE(0, "prepare-branch", &command,
 			    N_("prepare the branch to be rebased"), PREPARE_BRANCH),
-		OPT_CMDMODE(0, "checkout-onto", &command,
-			    N_("checkout a commit"), CHECKOUT_ONTO),
 		OPT_CMDMODE(0, "complete-action", &command,
 			    N_("complete the action"), COMPLETE_ACTION),
 		OPT_END()
@@ -94,26 +87,14 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
 		return !!transform_todos(flags);
 	if (command == CHECK_TODO_LIST && argc == 1)
 		return !!check_todo_list();
-	if (command == SKIP_UNNECESSARY_PICKS && argc == 1) {
-		struct object_id oid;
-		int ret = skip_unnecessary_picks(&oid);
-
-		if (!ret)
-			puts(oid_to_hex(&oid));
-		return !!ret;
-	}
 	if (command == REARRANGE_SQUASH && argc == 1)
 		return !!rearrange_squash();
 	if (command == ADD_EXEC && argc == 2)
 		return !!sequencer_add_exec_commands(argv[1]);
-	if (command == APPEND_TODO_HELP && argc == 1)
-		return !!append_todo_help_to_file(0, keep_empty);
 	if (command == EDIT_TODO && argc == 1)
 		return !!edit_todo_list(flags);
 	if (command == PREPARE_BRANCH && argc == 2)
 		return !!prepare_branch_to_be_rebased(&opts, argv[1]);
-	if (command == CHECKOUT_ONTO && argc == 4)
-		return !!checkout_onto(&opts, argv[1], argv[2], argv[3]);
 	if (command == COMPLETE_ACTION && argc == 6)
 		return !!complete_action(&opts, flags, argv[1], argv[2], argv[3],
 					 argv[4], argv[5], autosquash);
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index 59dc4888a6..0d66c0f8b8 100644
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -16,56 +16,6 @@ todo="$state_dir"/git-rebase-todo
 GIT_CHERRY_PICK_HELP="$resolvemsg"
 export GIT_CHERRY_PICK_HELP
 
-comment_char=$(git config --get core.commentchar 2>/dev/null)
-case "$comment_char" in
-'' | auto)
-	comment_char="#"
-	;;
-?)
-	;;
-*)
-	comment_char=$(echo "$comment_char" | cut -c1)
-	;;
-esac
-
-die_abort () {
-	apply_autostash
-	rm -rf "$state_dir"
-	die "$1"
-}
-
-has_action () {
-	test -n "$(git stripspace --strip-comments <"$1")"
-}
-
-git_sequence_editor () {
-	if test -z "$GIT_SEQUENCE_EDITOR"
-	then
-		GIT_SEQUENCE_EDITOR="$(git config sequence.editor)"
-		if [ -z "$GIT_SEQUENCE_EDITOR" ]
-		then
-			GIT_SEQUENCE_EDITOR="$(git var GIT_EDITOR)" || return $?
-		fi
-	fi
-
-	eval "$GIT_SEQUENCE_EDITOR" '"$@"'
-}
-
-expand_todo_ids() {
-	git rebase--helper --expand-ids
-}
-
-collapse_todo_ids() {
-	git rebase--helper --shorten-ids
-}
-
-get_missing_commit_check_level () {
-	check_level=$(git config --get rebase.missingCommitsCheck)
-	check_level=${check_level:-ignore}
-	# Don't be case sensitive
-	printf '%s' "$check_level" | tr 'A-Z' 'a-z'
-}
-
 # Initiate an action. If the cannot be any
 # further action it  may exec a command
 # or exit and not return.
diff --git a/rebase-interactive.c b/rebase-interactive.c
index 4a9a10eff4..0f4119cbae 100644
--- a/rebase-interactive.c
+++ b/rebase-interactive.c
@@ -52,28 +52,6 @@ void append_todo_help(unsigned edit_todo, unsigned keep_empty,
 	}
 }
 
-int append_todo_help_to_file(unsigned edit_todo, unsigned keep_empty)
-{
-	struct strbuf buf = STRBUF_INIT;
-	FILE *todo;
-	int ret;
-
-	todo = fopen_or_warn(rebase_path_todo(), "a");
-	if (!todo)
-		return -1;
-
-	append_todo_help(edit_todo, keep_empty, &buf);
-
-	ret = fputs(buf.buf, todo);
-	if (ret < 0)
-		error_errno(_("could not append help text to '%s'"), rebase_path_todo());
-
-	fclose(todo);
-	strbuf_release(&buf);
-
-	return ret;
-}
-
 int edit_todo_list(unsigned flags)
 {
 	struct strbuf buf = STRBUF_INIT;
diff --git a/rebase-interactive.h b/rebase-interactive.h
index d33f3176b7..971da03776 100644
--- a/rebase-interactive.h
+++ b/rebase-interactive.h
@@ -3,7 +3,6 @@
 
 void append_todo_help(unsigned edit_todo, unsigned keep_empty,
 		      struct strbuf *buf);
-int append_todo_help_to_file(unsigned edit_todo, unsigned keep_empty);
 int edit_todo_list(unsigned flags);
 
 #endif
diff --git a/sequencer.c b/sequencer.c
index 0104c0ad01..e3de1fbb2d 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -3319,9 +3319,9 @@ int prepare_branch_to_be_rebased(struct replay_opts *opts, const char *commit)
 	return 0;
 }
 
-int checkout_onto(struct replay_opts *opts,
-		  const char *onto_name, const char *onto,
-		  const char *orig_head)
+static int checkout_onto(struct replay_opts *opts,
+			 const char *onto_name, const char *onto,
+			 const char *orig_head)
 {
 	struct object_id oid;
 	const char *action = reflog_message(opts, "start", "checkout %s", onto_name);
@@ -4597,7 +4597,7 @@ static int rewrite_file(const char *path, const char *buf, size_t len)
 }
 
 /* skip picking commits whose parents are unchanged */
-int skip_unnecessary_picks(struct object_id *output_oid)
+static int skip_unnecessary_picks(struct object_id *output_oid)
 {
 	const char *todo_file = rebase_path_todo();
 	struct strbuf buf = STRBUF_INIT;
diff --git a/sequencer.h b/sequencer.h
index d51fe2c576..30bd9c12ac 100644
--- a/sequencer.h
+++ b/sequencer.h
@@ -96,7 +96,6 @@ int sequencer_add_exec_commands(const char *command);
 int transform_todos(unsigned flags);
 enum missing_commit_check_level get_missing_commit_check_level(void);
 int check_todo_list(void);
-int skip_unnecessary_picks(struct object_id *output_oid);
 int complete_action(struct replay_opts *opts, unsigned flags,
 		    const char *shortrevisions, const char *onto_name,
 		    const char *onto, const char *orig_head, const char *cmd,
@@ -126,9 +125,6 @@ void commit_post_rewrite(const struct commit *current_head,
 			 const struct object_id *new_head);
 
 int prepare_branch_to_be_rebased(struct replay_opts *opts, const char *commit);
-int checkout_onto(struct replay_opts *opts,
-		  const char *onto_name, const char *onto,
-		  const char *orig_head);
 
 #define SUMMARY_INITIAL_COMMIT   (1 << 0)
 #define SUMMARY_SHOW_AUTHOR_DATE (1 << 1)
-- 
2.19.0


^ permalink raw reply related	[relevance 6%]

* Import/Export as a fast way to purge files from Git?
@ 2018-09-23 13:04  4% Lars Schneider
  0 siblings, 0 replies; 200+ results
From: Lars Schneider @ 2018-09-23 13:04 UTC (permalink / raw)
  To: git; +Cc: Jeff King, Taylor Blau, brian m. carlson

Hi,

I recently had to purge files from large Git repos (many files, many commits). 
The usual recommendation is to use `git filter-branch --index-filter` to purge 
files. However, this is *very* slow for large repos (e.g. it takes 45min to
remove the `builtin` directory from git core). I realized that I can remove
files *way* faster by exporting the repo, removing the file references, 
and then importing the repo (see Perl script below, it takes ~30sec to remove
the `builtin` directory from git core). Do you see any problem with this 
approach?

Thank you,
Lars



#!/usr/bin/perl
#
# Purge paths from Git repositories.
#
# Usage:
#     git-purge-path [path-regex1] [path-regex2] ...
#
# Examples:
#    Remove the file "test.bin" from all directories:
#    git-purge-path "/test.bin$"
#
#    Remove all "*.bin" files from all directories:
#    git-purge-path "\.bin$"
#
#    Remove all files in the "/foo" directory:
#    git-purge-path "^/foo/$"
#
# Attention:
#     You want to run this script on a case sensitive file-system (e.g.
#     ext4 on Linux). Otherwise the resulting Git repository will not
#     contain changes that modify the casing of file paths.
#

use strict;
use warnings;

open( my $pipe_in, "git fast-export --progress=100 --no-data HEAD |" ) or die $!;
open( my $pipe_out, "| git fast-import --force --quiet" ) or die $!;

LOOP: while ( my $cmd = <$pipe_in> ) {
    my $data = "";
    if ( $cmd =~ /^data ([0-9]+)$/ ) {
        # skip data blocks
        my $skip_bytes = $1;
        read($pipe_in, $data, $skip_bytes);
    }
    elsif ( $cmd =~ /^M [0-9]{6} [0-9a-f]{40} (.+)$/ ) {
        my $pathname = $1;
        foreach (@ARGV) {
            next LOOP if ("/" . $pathname) =~ /$_/
        }
    }
    print {$pipe_out} $cmd . $data;
}


^ permalink raw reply	[relevance 4%]

* Re: Multiple GIT Accounts & HTTPS Client Certificates - Config
  2018-09-10 13:29  5% ` Randall S. Becker
@ 2018-09-11  7:29  0%   ` Sergei Haller
  0 siblings, 0 replies; 200+ results
From: Sergei Haller @ 2018-09-11  7:29 UTC (permalink / raw)
  To: Randall S. Becker; +Cc: git

no, using SSH is not an option. I have no control over the server
setup whatsoever.

Thx!

On Mon, Sep 10, 2018 at 3:29 PM, Randall S. Becker
<rsbecker@nexbridge.com> wrote:
> On September 10, 2018 4:09 AM, Sergei Haller wrote:
>> my problem is basically the following: my git server (https) requires
>> authentication using a clent x509 certificate.
>>
>> And I have multiple x509 certificates that match the server.
>>
>> when I access the https server using a browser, the browser asks which
>> certificate to use and everything is fine.
>>
>> When I try to access the git server from the command line (git pull or similar),
>> the git will pick one of the available certificates (randomly or alphabetically)
>> and try to access the server with that client certificate. Ending in the
>> situation that git picks the wrong certificate.
>>
>> I can workaround by deleting all client certificates from the windows
>> certificate store except the "correct" one => then git command line will pick
>> the correct certificate (the only one available) and everything works as
>> expected.
>>
>> Workaround is a workaround, I need to use all of the certificates repeatedly
>> for different repos and different other aplications (non-git), so I've been
>> deliting and reinstalling the certificates all the time in the last weeks...
>>
>> How can I tell git cmd (per config option??) to use a particular client
>> certificate for authenticating to the https server (I could provide fingerprint
>> or serial number or sth like that)
>>
>> current environment: windows 10 and git version 2.18.0.windows.1
>>
>> Would be absolutely acceptable if git would ask interactively which client
>> certificate to use (in case its not configurable)
>>
>> (I asked this question here before:
>> https://stackoverflow.com/questions/51952568/multiple-git-accounts-
>> https-client-certificates-config
>> )
>
> Would you consider using SSH to authenticate? You can control which private key you use based on your ~/.ssh/config entries, which are case sensitive. You can choose the SSH key to use by playing with the case of the host name, like:
>
> github.com
> Github.com
> gitHub.com
>
> even if your user is "git" in all cases above. It is a bit hacky but it is part of the SSH spec and is supported by git and EGit (as of 5.x).
>
> Cheers,
> Randall
>
> --
> Randall S. Becker
> Managing Director, Nexbridge Inc.
> LinkedIn.com/in/randallbecker
> +1.416.984.9826
>
>
>



-- 
sergei@sergei-haller.de
.

^ permalink raw reply	[relevance 0%]

* [ANNOUNCE] Git v2.19.0
@ 2018-09-10 20:11  1% Junio C Hamano
  0 siblings, 0 replies; 200+ results
From: Junio C Hamano @ 2018-09-10 20:11 UTC (permalink / raw)
  To: git; +Cc: Linux Kernel, git-packagers

The latest feature release Git v2.19.0 is now available at the
usual places.  It is comprised of 769 non-merge commits since
v2.18.0, contributed by 72 people, 16 of which are new faces.

The tarballs are found at:

    https://www.kernel.org/pub/software/scm/git/

The following public repositories all have a copy of the 'v2.19.0'
tag and the 'master' branch that the tag points at:

  url = https://kernel.googlesource.com/pub/scm/git/git
  url = git://repo.or.cz/alt-git.git
  url = https://github.com/gitster/git

New contributors whose contributions weren't in v2.18.0 are as follows.
Welcome to the Git development community!

  Aleksandr Makarov, Andrei Rybak, Chen Bin, Henning Schild,
  Isabella Stephens, Josh Steadmon, Jules Maselbas, Kana Natsuno,
  Marc Strapetz, Masaya Suzuki, Nicholas Guriev, Raphaël Hertzog,
  Samuel Maftoul, Sebastian Kisela, Vladimir Parfinenko, and
  William Chargin.

Returning contributors who helped this release are as follows.
Thanks for your continued support.

  Aaron Schrab, Ævar Arnfjörð Bjarmason, Alban Gruin, Alejandro
  R. Sedeño, Alexander Shopov, Anthony Sottile, Antonio Ospite,
  Beat Bolli, Ben Peart, Brandon Williams, brian m. carlson,
  Christian Couder, Christopher Díaz Riveros, Derrick Stolee,
  Dimitriy Ryazantcev, Elia Pinto, Elijah Newren, Eric Sunshine,
  Han-Wen Nienhuys, Jameson Miller, Jean-Noël Avila, Jeff
  Hostetler, Jeff King, Jiang Xin, Johannes Schindelin, Johannes
  Sixt, Jonathan Nieder, Jonathan Tan, Junio C Hamano, Kim Gybels,
  Kirill Smelkov, Kyle Meyer, Luis Marsano, Łukasz Stelmach,
  Luke Diamand, Martin Ågren, Max Kirillov, Michael Barabanov,
  Mike Hommey, Nguyễn Thái Ngọc Duy, Olga Telezhnaya, Peter
  Krefting, Phillip Wood, Prathamesh Chavan, Ralf Thielow, Ramsay
  Jones, René Scharfe, Stefan Beller, SZEDER Gábor, Taylor Blau,
  Thomas Rast, Tobias Klauser, Todd Zullinger, Trần Ngọc Quân,
  Ville Skyttä, and Xiaolong Ye.

----------------------------------------------------------------

Git 2.19 Release Notes
======================

Updates since v2.18
-------------------

UI, Workflows & Features

 * "git diff" compares the index and the working tree.  For paths
   added with intent-to-add bit, the command shows the full contents
   of them as added, but the paths themselves were not marked as new
   files.  They are now shown as new by default.

   "git apply" learned the "--intent-to-add" option so that an
   otherwise working-tree-only application of a patch will add new
   paths to the index marked with the "intent-to-add" bit.

 * "git grep" learned the "--column" option that gives not just the
   line number but the column number of the hit.

 * The "-l" option in "git branch -l" is an unfortunate short-hand for
   "--create-reflog", but many users, both old and new, somehow expect
   it to be something else, perhaps "--list".  This step warns when "-l"
   is used as a short-hand for "--create-reflog" and warns about the
   future repurposing of the it when it is used.

 * The userdiff pattern for .php has been updated.

 * The content-transfer-encoding of the message "git send-email" sends
   out by default was 8bit, which can cause trouble when there is an
   overlong line to bust RFC 5322/2822 limit.  A new option 'auto' to
   automatically switch to quoted-printable when there is such a line
   in the payload has been introduced and is made the default.

 * "git checkout" and "git worktree add" learned to honor
   checkout.defaultRemote when auto-vivifying a local branch out of a
   remote tracking branch in a repository with multiple remotes that
   have tracking branches that share the same names.
   (merge 8d7b558bae ab/checkout-default-remote later to maint).

 * "git grep" learned the "--only-matching" option.

 * "git rebase --rebase-merges" mode now handles octopus merges as
   well.

 * Add a server-side knob to skip commits in exponential/fibbonacci
   stride in an attempt to cover wider swath of history with a smaller
   number of iterations, potentially accepting a larger packfile
   transfer, instead of going back one commit a time during common
   ancestor discovery during the "git fetch" transaction.
   (merge 42cc7485a2 jt/fetch-negotiator-skipping later to maint).

 * A new configuration variable core.usereplacerefs has been added,
   primarily to help server installations that want to ignore the
   replace mechanism altogether.

 * Teach "git tag -s" etc. a few configuration variables (gpg.format
   that can be set to "openpgp" or "x509", and gpg.<format>.program
   that is used to specify what program to use to deal with the format)
   to allow x.509 certs with CMS via "gpgsm" to be used instead of
   openpgp via "gnupg".

 * Many more strings are prepared for l10n.

 * "git p4 submit" learns to ask its own pre-submit hook if it should
   continue with submitting.

 * The test performed at the receiving end of "git push" to prevent
   bad objects from entering repository can be customized via
   receive.fsck.* configuration variables; we now have gained a
   counterpart to do the same on the "git fetch" side, with
   fetch.fsck.* configuration variables.

 * "git pull --rebase=interactive" learned "i" as a short-hand for
   "interactive".

 * "git instaweb" has been adjusted to run better with newer Apache on
   RedHat based distros.

 * "git range-diff" is a reimplementation of "git tbdiff" that lets us
   compare individual patches in two iterations of a topic.

 * The sideband code learned to optionally paint selected keywords at
   the beginning of incoming lines on the receiving end.

 * "git branch --list" learned to take the default sort order from the
   'branch.sort' configuration variable, just like "git tag --list"
   pays attention to 'tag.sort'.

 * "git worktree" command learned "--quiet" option to make it less
   verbose.


Performance, Internal Implementation, Development Support etc.

 * The bulk of "git submodule foreach" has been rewritten in C.

 * The in-core "commit" object had an all-purpose "void *util" field,
   which was tricky to use especially in library-ish part of the
   code.  All of the existing uses of the field has been migrated to a
   more dedicated "commit-slab" mechanism and the field is eliminated.

 * A less often used command "git show-index" has been modernized.
   (merge fb3010c31f jk/show-index later to maint).

 * The conversion to pass "the_repository" and then "a_repository"
   throughout the object access API continues.

 * Continuing with the idea to programatically enumerate various
   pieces of data required for command line completion, teach the
   codebase to report the list of configuration variables
   subcommands care about to help complete them.

 * Separate "rebase -p" codepath out of "rebase -i" implementation to
   slim down the latter and make it easier to manage.

 * Make refspec parsing codepath more robust.

 * Some flaky tests have been fixed.

 * Continuing with the idea to programmatically enumerate various
   pieces of data required for command line completion, the codebase
   has been taught to enumerate options prefixed with "--no-" to
   negate them.

 * Build and test procedure for netrc credential helper (in contrib/)
   has been updated.

 * Remove unused function definitions and declarations from ewah
   bitmap subsystem.

 * Code preparation to make "git p4" closer to be usable with Python 3.

 * Tighten the API to make it harder to misuse in-tree .gitmodules
   file, even though it shares the same syntax with configuration
   files, to read random configuration items from it.

 * "git fast-import" has been updated to avoid attempting to create
   delta against a zero-byte-long string, which is pointless.

 * The codebase has been updated to compile cleanly with -pedantic
   option.
   (merge 2b647a05d7 bb/pedantic later to maint).

 * The character display width table has been updated to match the
   latest Unicode standard.
   (merge 570951eea2 bb/unicode-11-width later to maint).

 * test-lint now looks for broken use of "VAR=VAL shell_func" in test
   scripts.

 * Conversion from uchar[40] to struct object_id continues.

 * Recent "security fix" to pay attention to contents of ".gitmodules"
   while accepting "git push" was a bit overly strict than necessary,
   which has been adjusted.

 * "git fsck" learns to make sure the optional commit-graph file is in
   a sane state.

 * "git diff --color-moved" feature has further been tweaked.

 * Code restructuring and a small fix to transport protocol v2 during
   fetching.

 * Parsing of -L[<N>][,[<M>]] parameters "git blame" and "git log"
   take has been tweaked.

 * lookup_commit_reference() and friends have been updated to find
   in-core object for a specific in-core repository instance.

 * Various glitches in the heuristics of merge-recursive strategy have
   been documented in new tests.

 * "git fetch" learned a new option "--negotiation-tip" to limit the
   set of commits it tells the other end as "have", to reduce wasted
   bandwidth and cycles, which would be helpful when the receiving
   repository has a lot of refs that have little to do with the
   history at the remote it is fetching from.

 * For a large tree, the index needs to hold many cache entries
   allocated on heap.  These cache entries are now allocated out of a
   dedicated memory pool to amortize malloc(3) overhead.

 * Tests to cover various conflicting cases have been added for
   merge-recursive.

 * Tests to cover conflict cases that involve submodules have been
   added for merge-recursive.

 * Look for broken "&&" chains that are hidden in subshell, many of
   which have been found and corrected.

 * The singleton commit-graph in-core instance is made per in-core
   repository instance.

 * "make DEVELOPER=1 DEVOPTS=pedantic" allows developers to compile
   with -pedantic option, which may catch more problematic program
   constructs and potential bugs.

 * Preparatory code to later add json output for telemetry data has
   been added.

 * Update the way we use Coccinelle to find out-of-style code that
   need to be modernised.

 * It is too easy to misuse system API functions such as strcat();
   these selected functions are now forbidden in this codebase and
   will cause a compilation failure.

 * Add a script (in contrib/) to help users of VSCode work better with
   our codebase.

 * The Travis CI scripts were taught to ship back the test data from
   failed tests.
   (merge aea8879a6a sg/travis-retrieve-trash-upon-failure later to maint).

 * The parse-options machinery learned to refrain from enclosing
   placeholder string inside a "<bra" and "ket>" pair automatically
   without PARSE_OPT_LITERAL_ARGHELP.  Existing help text for option
   arguments that are not formatted correctly have been identified and
   fixed.
   (merge 5f0df44cd7 rs/parse-opt-lithelp later to maint).

 * Noiseword "extern" has been removed from function decls in the
   header files.

 * A few atoms like %(objecttype) and %(objectsize) in the format
   specifier of "for-each-ref --format=<format>" can be filled without
   getting the full contents of the object, but just with the object
   header.  These cases have been optimized by calling
   oid_object_info() API (instead of reading and inspecting the data).

 * The end result of documentation update has been made to be
   inspected more easily to help developers.

 * The API to iterate over all objects learned to optionally list
   objects in the order they appear in packfiles, which helps locality
   of access if the caller accesses these objects while as objects are
   enumerated.

 * Improve built-in facility to catch broken &&-chain in the tests.

 * The more library-ish parts of the codebase learned to work on the
   in-core index-state instance that is passed in by their callers,
   instead of always working on the singleton "the_index" instance.

 * A test prerequisite defined by various test scripts with slightly
   different semantics has been consolidated into a single copy and
   made into a lazily defined one.
   (merge 6ec633059a wc/make-funnynames-shared-lazy-prereq later to maint).

 * After a partial clone, repeated fetches from promisor remote would
   have accumulated many packfiles marked with .promisor bit without
   getting them coalesced into fewer packfiles, hurting performance.
   "git repack" now learned to repack them.

 * Partially revert the support for multiple hash functions to regain
   hash comparison performance; we'd think of a way to do this better
   in the next cycle.

 * "git help --config" (which is used in command line completion)
   missed the configuration variables not described in the main
   config.txt file but are described in another file that is included
   by it, which has been corrected.

 * The test linter code has learned that the end of here-doc mark
   "EOF" can be quoted in a double-quote pair, not just in a
   single-quote pair.


Fixes since v2.18
-----------------

 * "git remote update" can take both a single remote nickname and a
   nickname for remote groups, and the completion script (in contrib/)
   has been taught about it.
   (merge 9cd4382ad5 ls/complete-remote-update-names later to maint).

 * "git fetch --shallow-since=<cutoff>" that specifies the cut-off
   point that is newer than the existing history used to end up
   grabbing the entire history.  Such a request now errors out.
   (merge e34de73c56 nd/reject-empty-shallow-request later to maint).

 * Fix for 2.17-era regression around `core.safecrlf`.
   (merge 6cb09125be as/safecrlf-quiet-fix later to maint).

 * The recent addition of "partial clone" experimental feature kicked
   in when it shouldn't, namely, when there is no partial-clone filter
   defined even if extensions.partialclone is set.
   (merge cac1137dc4 jh/partial-clone later to maint).

 * "git send-pack --signed" (hence "git push --signed" over the http
   transport) did not read user ident from the config mechanism to
   determine whom to sign the push certificate as, which has been
   corrected.
   (merge d067d98887 ms/send-pack-honor-config later to maint).

 * "git fetch-pack --all" used to unnecessarily fail upon seeing an
   annotated tag that points at an object other than a commit.
   (merge c12c9df527 jk/fetch-all-peeled-fix later to maint).

 * When user edits the patch in "git add -p" and the user's editor is
   set to strip trailing whitespaces indiscriminately, an empty line
   that is unchanged in the patch would become completely empty
   (instead of a line with a sole SP on it).  The code introduced in
   Git 2.17 timeframe failed to parse such a patch, but now it learned
   to notice the situation and cope with it.
   (merge f4d35a6b49 pw/add-p-recount later to maint).

 * The code to try seeing if a fetch is necessary in a submodule
   during a fetch with --recurse-submodules got confused when the path
   to the submodule was changed in the range of commits in the
   superproject, sometimes showing "(null)".  This has been corrected.

 * Bugfix for "rebase -i" corner case regression.
   (merge a9279c6785 pw/rebase-i-keep-reword-after-conflict later to maint).

 * Recently added "--base" option to "git format-patch" command did
   not correctly generate prereq patch ids.
   (merge 15b76c1fb3 xy/format-patch-prereq-patch-id-fix later to maint).

 * POSIX portability fix in Makefile to fix a glitch introduced a few
   releases ago.
   (merge 6600054e9b dj/runtime-prefix later to maint).

 * "git filter-branch" when used with the "--state-branch" option
   still attempted to rewrite the commits whose filtered result is
   known from the previous attempt (which is recorded on the state
   branch); the command has been corrected not to waste cycles doing
   so.
   (merge 709cfe848a mb/filter-branch-optim later to maint).

 * Clarify that setting core.ignoreCase to deviate from reality would
   not turn a case-incapable filesystem into a case-capable one.
   (merge 48294b512a ms/core-icase-doc later to maint).

 * "fsck.skipList" did not prevent a blob object listed there from
   being inspected for is contents (e.g. we recently started to
   inspect the contents of ".gitmodules" for certain malicious
   patterns), which has been corrected.
   (merge fb16287719 rj/submodule-fsck-skip later to maint).

 * "git checkout --recurse-submodules another-branch" did not report
   in which submodule it failed to update the working tree, which
   resulted in an unhelpful error message.
   (merge ba95d4e4bd sb/submodule-move-head-error-msg later to maint).

 * "git rebase" behaved slightly differently depending on which one of
   the three backends gets used; this has been documented and an
   effort to make them more uniform has begun.
   (merge b00bf1c9a8 en/rebase-consistency later to maint).

 * The "--ignore-case" option of "git for-each-ref" (and its friends)
   did not work correctly, which has been fixed.
   (merge e674eb2528 jk/for-each-ref-icase later to maint).

 * "git fetch" failed to correctly validate the set of objects it
   received when making a shallow history deeper, which has been
   corrected.
   (merge cf1e7c0770 jt/connectivity-check-after-unshallow later to maint).

 * Partial clone support of "git clone" has been updated to correctly
   validate the objects it receives from the other side.  The server
   side has been corrected to send objects that are directly
   requested, even if they may match the filtering criteria (e.g. when
   doing a "lazy blob" partial clone).
   (merge a7e67c11b8 jt/partial-clone-fsck-connectivity later to maint).

 * Handling of an empty range by "git cherry-pick" was inconsistent
   depending on how the range ended up to be empty, which has been
   corrected.
   (merge c5e358d073 jk/empty-pick-fix later to maint).

 * "git reset --merge" (hence "git merge ---abort") and "git reset --hard"
   had trouble working correctly in a sparsely checked out working
   tree after a conflict, which has been corrected.
   (merge b33fdfc34c mk/merge-in-sparse-checkout later to maint).

 * Correct a broken use of "VAR=VAL shell_func" in a test.
   (merge 650161a277 jc/t3404-one-shot-export-fix later to maint).

 * "git rev-parse ':/substring'" did not consider the history leading
   only to HEAD when looking for a commit with the given substring,
   when the HEAD is detached.  This has been fixed.
   (merge 6b3351e799 wc/find-commit-with-pattern-on-detached-head later to maint).

 * Build doc update for Windows.
   (merge ede8d89bb1 nd/command-list later to maint).

 * core.commentchar is now honored when preparing the list of commits
   to replay in "rebase -i".

 * "git pull --rebase" on a corrupt HEAD caused a segfault.  In
   general we substitute an empty tree object when running the in-core
   equivalent of the diff-index command, and the codepath has been
   corrected to do so as well to fix this issue.
   (merge 3506dc9445 jk/has-uncommitted-changes-fix later to maint).

 * httpd tests saw occasional breakage due to the way its access log
   gets inspected by the tests, which has been updated to make them
   less flaky.
   (merge e8b3b2e275 sg/httpd-test-unflake later to maint).

 * Tests to cover more D/F conflict cases have been added for
   merge-recursive.

 * "git gc --auto" opens file descriptors for the packfiles before
   spawning "git repack/prune", which would upset Windows that does
   not want a process to work on a file that is open by another
   process.  The issue has been worked around.
   (merge 12e73a3ce4 kg/gc-auto-windows-workaround later to maint).

 * The recursive merge strategy did not properly ensure there was no
   change between HEAD and the index before performing its operation,
   which has been corrected.
   (merge 55f39cf755 en/dirty-merge-fixes later to maint).

 * "git rebase" started exporting GIT_DIR environment variable and
   exposing it to hook scripts when part of it got rewritten in C.
   Instead of matching the old scripted Porcelains' behaviour,
   compensate by also exporting GIT_WORK_TREE environment as well to
   lessen the damage.  This can harm existing hooks that want to
   operate on different repository, but the current behaviour is
   already broken for them anyway.
   (merge ab5e67d751 bc/sequencer-export-work-tree-as-well later to maint).

 * "git send-email" when using in a batched mode that limits the
   number of messages sent in a single SMTP session lost the contents
   of the variable used to choose between tls/ssl, unable to send the
   second and later batches, which has been fixed.
   (merge 636f3d7ac5 jm/send-email-tls-auth-on-batch later to maint).

 * The lazy clone support had a few places where missing but promised
   objects were not correctly tolerated, which have been fixed.

 * One of the "diff --color-moved" mode "dimmed_zebra" that was named
   in an unusual way has been deprecated and replaced by
   "dimmed-zebra".
   (merge e3f2f5f9cd es/diff-color-moved-fix later to maint).

 * The wire-protocol v2 relies on the client to send "ref prefixes" to
   limit the bandwidth spent on the initial ref advertisement.  "git
   clone" when learned to speak v2 forgot to do so, which has been
   corrected.
   (merge 402c47d939 bw/clone-ref-prefixes later to maint).

 * "git diff --histogram" had a bad memory usage pattern, which has
   been rearranged to reduce the peak usage.
   (merge 79cb2ebb92 sb/histogram-less-memory later to maint).

 * Code clean-up to use size_t/ssize_t when they are the right type.
   (merge 7726d360b5 jk/size-t later to maint).

 * The wire-protocol v2 relies on the client to send "ref prefixes" to
   limit the bandwidth spent on the initial ref advertisement.  "git
   fetch $remote branch:branch" that asks tags that point into the
   history leading to the "branch" automatically followed sent to
   narrow prefix and broke the tag following, which has been fixed.
   (merge 2b554353a5 jt/tag-following-with-proto-v2-fix later to maint).

 * When the sparse checkout feature is in use, "git cherry-pick" and
   other mergy operations lost the skip_worktree bit when a path that
   is excluded from checkout requires content level merge, which is
   resolved as the same as the HEAD version, without materializing the
   merge result in the working tree, which made the path appear as
   deleted.  This has been corrected by preserving the skip_worktree
   bit (and not materializing the file in the working tree).
   (merge 2b75fb601c en/merge-recursive-skip-fix later to maint).

 * The "author-script" file "git rebase -i" creates got broken when
   we started to move the command away from shell script, which is
   getting fixed now.
   (merge 5522bbac20 es/rebase-i-author-script-fix later to maint).

 * The automatic tree-matching in "git merge -s subtree" was broken 5
   years ago and nobody has noticed since then, which is now fixed.
   (merge 2ec4150713 jk/merge-subtree-heuristics later to maint).

 * "git fetch $there refs/heads/s" ought to fetch the tip of the
   branch 's', but when "refs/heads/refs/heads/s", i.e. a branch whose
   name is "refs/heads/s" exists at the same time, fetched that one
   instead by mistake.  This has been corrected to honor the usual
   disambiguation rules for abbreviated refnames.
   (merge 60650a48c0 jt/refspec-dwim-precedence-fix later to maint).

 * Futureproofing a helper function that can easily be misused.
   (merge 65bb21e77e es/want-color-fd-defensive later to maint).

 * The http-backend (used for smart-http transport) used to slurp the
   whole input until EOF, without paying attention to CONTENT_LENGTH
   that is supplied in the environment and instead expecting the Web
   server to close the input stream.  This has been fixed.
   (merge eebfe40962 mk/http-backend-content-length later to maint).

 * "git merge --abort" etc. did not clean things up properly when
   there were conflicted entries in the index in certain order that
   are involved in D/F conflicts.  This has been corrected.
   (merge ad3762042a en/abort-df-conflict-fixes later to maint).

 * "git diff --indent-heuristic" had a bad corner case performance.
   (merge 301ef85401 sb/indent-heuristic-optim later to maint).

 * The "--exec" option to "git rebase --rebase-merges" placed the exec
   commands at wrong places, which has been corrected.

 * "git verify-tag" and "git verify-commit" have been taught to use
   the exit status of underlying "gpg --verify" to signal bad or
   untrusted signature they found.
   (merge 4e5dc9ca17 jc/gpg-status later to maint).

 * "git mergetool" stopped and gave an extra prompt to continue after
   the last path has been handled, which did not make much sense.
   (merge d651a54b8a ng/mergetool-lose-final-prompt later to maint).

 * Among the three codepaths we use O_APPEND to open a file for
   appending, one used for writing GIT_TRACE output requires O_APPEND
   implementation that behaves sensibly when multiple processes are
   writing to the same file.  POSIX emulation used in the Windows port
   has been updated to improve in this area.
   (merge d641097589 js/mingw-o-append later to maint).

 * "git pull --rebase -v" in a repository with a submodule barfed as
   an intermediate process did not understand what "-v(erbose)" flag
   meant, which has been fixed.
   (merge e84c3cf3dc sb/pull-rebase-submodule later to maint).

 * Recent update to "git config" broke updating variable in a
   subsection, which has been corrected.
   (merge bff7df7a87 sb/config-write-fix later to maint).

 * When "git rebase -i" is told to squash two or more commits into
   one, it labeled the log message for each commit with its number.
   It correctly called the first one "1st commit", but the next one
   was "commit #1", which was off-by-one.  This has been corrected.
   (merge dd2e36ebac pw/rebase-i-squash-number-fix later to maint).

 * "git rebase -i", when a 'merge <branch>' insn in its todo list
   fails, segfaulted, which has been (minimally) corrected.
   (merge bc9238bb09 pw/rebase-i-merge-segv-fix later to maint).

 * "git cherry-pick --quit" failed to remove CHERRY_PICK_HEAD even
   though we won't be in a cherry-pick session after it returns, which
   has been corrected.
   (merge 3e7dd99208 nd/cherry-pick-quit-fix later to maint).

 * In a recent update in 2.18 era, "git pack-objects" started
   producing a larger than necessary packfiles by missing
   opportunities to use large deltas.  This has been corrected.

 * The meaning of the possible values the "core.checkStat"
   configuration variable can take were not adequately documented,
   which has been fixed.
   (merge 9bf5d4c4e2 nd/config-core-checkstat-doc later to maint).

 * Recent "git rebase -i" update started to write bogusly formatted
   author-script, with a matching broken reading code.  These are
   fixed.

 * Recent addition of "directory rename" heuristics to the
   merge-recursive backend makes the command susceptible to false
   positives and false negatives.  In the context of "git am -3",
   which does not know about surrounding unmodified paths and thus
   cannot inform the merge machinery about the full trees involved,
   this risk is particularly severe.  As such, the heuristic is
   disabled for "git am -3" to keep the machinery "more stupid but
   predictable".

 * "git merge-base" in 2.19-rc1 has performance regression when the
   (experimental) commit-graph feature is in use, which has been
   mitigated.

 * Code cleanup, docfix, build fix, etc.
   (merge aee9be2ebe sg/update-ref-stdin-cleanup later to maint).
   (merge 037714252f jc/clean-after-sanity-tests later to maint).
   (merge 5b26c3c941 en/merge-recursive-cleanup later to maint).
   (merge 0dcbc0392e bw/config-refer-to-gitsubmodules-doc later to maint).
   (merge bb4d000e87 bw/protocol-v2 later to maint).
   (merge 928f0ab4ba vs/typofixes later to maint).
   (merge d7f590be84 en/rebase-i-microfixes later to maint).
   (merge 81d395cc85 js/rebase-recreate-merge later to maint).
   (merge 51d1863168 tz/exclude-doc-smallfixes later to maint).
   (merge a9aa3c0927 ds/commit-graph later to maint).
   (merge 5cf8e06474 js/enhanced-version-info later to maint).
   (merge 6aaded5509 tb/config-default later to maint).
   (merge 022d2ac1f3 sb/blame-color later to maint).
   (merge 5a06a20e0c bp/test-drop-caches-for-windows later to maint).
   (merge dd61cc1c2e jk/ui-color-always-to-auto later to maint).
   (merge 1e83b9bfdd sb/trailers-docfix later to maint).
   (merge ab29f1b329 sg/fast-import-dump-refs-on-checkpoint-fix later to maint).
   (merge 6a8ad880f0 jn/subtree-test-fixes later to maint).
   (merge ffbd51cc60 nd/pack-objects-threading-doc later to maint).
   (merge e9dac7be60 es/mw-to-git-chain-fix later to maint).
   (merge fe583c6c7a rs/remote-mv-leakfix later to maint).
   (merge 69885ab015 en/t3031-title-fix later to maint).
   (merge 8578037bed nd/config-blame-sort later to maint).
   (merge 8ad169c4ba hn/config-in-code-comment later to maint).
   (merge b7446fcfdf ar/t4150-am-scissors-test-fix later to maint).
   (merge a8132410ee js/typofixes later to maint).
   (merge 388d0ff6e5 en/update-index-doc later to maint).
   (merge e05aa688dd jc/update-index-doc later to maint).
   (merge 10c600172c sg/t5310-empty-input-fix later to maint).
   (merge 5641eb9465 jh/partial-clone-doc later to maint).
   (merge 2711b1ad5e ab/submodule-relative-url-tests later to maint).
   (merge ce528de023 ab/unconditional-free-and-null later to maint).
   (merge bbc072f5d8 rs/opt-updates later to maint).
   (merge 69d846f053 jk/use-compat-util-in-test-tool later to maint).
   (merge 1820703045 js/larger-timestamps later to maint).
   (merge c8b35b95e1 sg/t4051-fix later to maint).
   (merge 30612cb670 sg/t0020-conversion-fix later to maint).
   (merge 15da753709 sg/t7501-thinkofix later to maint).
   (merge 79b04f9b60 sg/t3903-missing-fix later to maint).
   (merge 2745817028 sg/t3420-autostash-fix later to maint).
   (merge 7afb0d6777 sg/test-rebase-editor-fix later to maint).
   (merge 6c6ce21baa es/freebsd-iconv-portability later to maint).

----------------------------------------------------------------

Changes since v2.18.0 are as follows:

Aaron Schrab (1):
      sequencer: use configured comment character

Alban Gruin (4):
      rebase: introduce a dedicated backend for --preserve-merges
      rebase: strip unused code in git-rebase--preserve-merges.sh
      rebase: use the new git-rebase--preserve-merges.sh
      rebase: remove -p code from git-rebase--interactive.sh

Alejandro R. Sedeño (1):
      Makefile: tweak sed invocation

Aleksandr Makarov (1):
      for-each-ref: consistently pass WM_IGNORECASE flag

Alexander Shopov (1):
      l10n: bg.po: Updated Bulgarian translation (3958t)

Andrei Rybak (2):
      Documentation: fix --color option formatting
      t4150: fix broken test for am --scissors

Anthony Sottile (1):
      config.c: fix regression for core.safecrlf false

Antonio Ospite (6):
      config: move config_from_gitmodules to submodule-config.c
      submodule-config: add helper function to get 'fetch' config from .gitmodules
      submodule-config: add helper to get 'update-clone' config from .gitmodules
      submodule-config: make 'config_from_gitmodules' private
      submodule-config: pass repository as argument to config_from_gitmodules
      submodule-config: reuse config_from_gitmodules in repo_read_gitmodules

Beat Bolli (10):
      builtin/config: work around an unsized array forward declaration
      unicode: update the width tables to Unicode 11
      connect.h: avoid forward declaration of an enum
      refs/refs-internal.h: avoid forward declaration of an enum
      convert.c: replace "\e" escapes with "\033".
      sequencer.c: avoid empty statements at top level
      string-list.c: avoid conversion from void * to function pointer
      utf8.c: avoid char overflow
      Makefile: add a DEVOPTS flag to get pedantic compilation
      packfile: ensure that enum object_type is defined

Ben Peart (3):
      convert log_ref_write_fd() to use strbuf
      handle lower case drive letters on Windows
      t3507: add a testcase showing failure with sparse checkout

Brandon Williams (15):
      commit: convert commit_graft_pos() to handle arbitrary repositories
      commit: convert register_commit_graft to handle arbitrary repositories
      commit: convert read_graft_file to handle arbitrary repositories
      test-pkt-line: add unpack-sideband subcommand
      docs: link to gitsubmodules
      upload-pack: implement ref-in-want
      upload-pack: test negotiation with changing repository
      fetch: refactor the population of peer ref OIDs
      fetch: refactor fetch_refs into two functions
      fetch: refactor to make function args narrower
      fetch-pack: put shallow info in output parameter
      fetch-pack: implement ref-in-want
      clone: send ref-prefixes when using protocol v2
      fetch-pack: mark die strings for translation
      pack-protocol: mention and point to docs for protocol v2

Chen Bin (1):
      git-p4: add the `p4-pre-submit` hook

Christian Couder (1):
      t9104: kosherly remove remote refs

Christopher Díaz Riveros (1):
      l10n: es.po v2.19.0 round 2

Derrick Stolee (46):
      ref-filter: fix outdated comment on in_commit_list
      commit: add generation number to struct commit
      commit-graph: compute generation numbers
      commit: use generations in paint_down_to_common()
      commit-graph: always load commit-graph information
      ref-filter: use generation number for --contains
      commit: use generation numbers for in_merge_bases()
      commit: add short-circuit to paint_down_to_common()
      commit: use generation number in remove_redundant()
      merge: check config before loading commits
      commit-graph.txt: update design document
      commit-graph: fix UX issue when .lock file exists
      ewah/bitmap.c: delete unused 'bitmap_clear()'
      ewah/bitmap.c: delete unused 'bitmap_each_bit()'
      ewah_bitmap: delete unused 'ewah_and()'
      ewah_bitmap: delete unused 'ewah_and_not()'
      ewah_bitmap: delete unused 'ewah_not()'
      ewah_bitmap: delete unused 'ewah_or()'
      ewah_io: delete unused 'ewah_serialize()'
      t5318-commit-graph.sh: use core.commitGraph
      commit-graph: UNLEAK before die()
      commit-graph: fix GRAPH_MIN_SIZE
      commit-graph: parse commit from chosen graph
      commit: force commit to parse from object database
      commit-graph: load a root tree from specific graph
      commit-graph: add 'verify' subcommand
      commit-graph: verify catches corrupt signature
      commit-graph: verify required chunks are present
      commit-graph: verify corrupt OID fanout and lookup
      commit-graph: verify objects exist
      commit-graph: verify root tree OIDs
      commit-graph: verify parent list
      commit-graph: verify generation number
      commit-graph: verify commit date
      commit-graph: test for corrupted octopus edge
      commit-graph: verify contents match checksum
      fsck: verify commit-graph
      commit-graph: use string-list API for input
      commit-graph: add '--reachable' option
      gc: automatically write commit-graph files
      commit-graph: update design document
      commit-graph: fix documentation inconsistencies
      coccinelle: update commit.cocci
      commit: use timestamp_t for author_date_slab
      config: fix commit-graph related config docs
      commit: don't use generation numbers if not needed

Dimitriy Ryazantcev (1):
      l10n: ru.po: update Russian translation

Elia Pinto (1):
      worktree: add --quiet option

Elijah Newren (66):
      t6036, t6042: use test_create_repo to keep tests independent
      t6036, t6042: use test_line_count instead of wc -l
      t6036, t6042: prefer test_path_is_file, test_path_is_missing
      t6036, t6042: prefer test_cmp to sequences of test
      t6036: prefer test_when_finished to manual cleanup in following test
      merge-recursive: fix miscellaneous grammar error in comment
      merge-recursive: fix numerous argument alignment issues
      merge-recursive: align labels with their respective code blocks
      merge-recursive: clarify the rename_dir/RENAME_DIR meaning
      merge-recursive: rename conflict_rename_*() family of functions
      merge-recursive: add pointer about unduly complex looking code
      git-rebase.txt: document incompatible options
      git-rebase.sh: update help messages a bit
      t3422: new testcases for checking when incompatible options passed
      git-rebase: error out when incompatible options passed
      git-rebase.txt: address confusion between --no-ff vs --force-rebase
      directory-rename-detection.txt: technical docs on abilities and limitations
      git-rebase.txt: document behavioral differences between modes
      t3401: add directory rename testcases for rebase and am
      git-rebase: make --allow-empty-message the default
      t3418: add testcase showing problems with rebase -i and strategy options
      Fix use of strategy options with interactive rebases
      git-rebase--merge: modernize "git-$cmd" to "git $cmd"
      apply: fix grammar error in comment
      t5407: fix test to cover intended arguments
      read-cache.c: move index_has_changes() from merge.c
      index_has_changes(): avoid assuming operating on the_index
      t6044: verify that merges expected to abort actually abort
      t6036: add a failed conflict detection case with symlink modify/modify
      t6036: add a failed conflict detection case with symlink add/add
      t6036: add a failed conflict detection case with submodule modify/modify
      t6036: add a failed conflict detection case with submodule add/add
      t6036: add a failed conflict detection case with conflicting types
      t6042: add testcase covering rename/add/delete conflict type
      t6042: add testcase covering rename/rename(2to1)/delete/delete conflict
      t6042: add testcase covering long chains of rename conflicts
      t6036: add lots of detail for directory/file conflicts in recursive case
      t6036: add a failed conflict detection case: regular files, different modes
      t6044: add a testcase for index matching head, when head doesn't match HEAD
      merge-recursive: make sure when we say we abort that we actually abort
      merge-recursive: fix assumption that head tree being merged is HEAD
      t6044: add more testcases with staged changes before a merge is invoked
      merge-recursive: enforce rule that index matches head before merging
      merge: fix misleading pre-merge check documentation
      t7405: add a file/submodule conflict
      t7405: add a directory/submodule conflict
      t7405: verify 'merge --abort' works after submodule/path conflicts
      merge-recursive: preserve skip_worktree bit when necessary
      t1015: demonstrate directory/file conflict recovery failures
      read-cache: fix directory/file conflict handling in read_index_unmerged()
      t3031: update test description to mention desired behavior
      t7406: fix call that was failing for the wrong reason
      t7406: simplify by using diff --name-only instead of diff --raw
      t7406: avoid having git commands upstream of a pipe
      t7406: prefer test_* helper functions to test -[feds]
      t7406: avoid using test_must_fail for commands other than git
      git-update-index.txt: reword possibly confusing example
      Add missing includes and forward declarations
      alloc: make allocate_alloc_state and clear_alloc_state more consistent
      Move definition of enum branch_track from cache.h to branch.h
      urlmatch.h: fix include guard
      compat/precompose_utf8.h: use more common include guard style
      Remove forward declaration of an enum
      t3401: add another directory rename testcase for rebase and am
      merge-recursive: add ability to turn off directory rename detection
      am: avoid directory rename detection when calling recursive merge machinery

Eric Sunshine (55):
      t: use test_might_fail() instead of manipulating exit code manually
      t: use test_write_lines() instead of series of 'echo' commands
      t: use sane_unset() rather than 'unset' with broken &&-chain
      t: drop unnecessary terminating semicolon in subshell
      t/lib-submodule-update: fix "absorbing" test
      t5405: use test_must_fail() instead of checking exit code manually
      t5406: use write_script() instead of birthing shell script manually
      t5505: modernize and simplify hard-to-digest test
      t6036: fix broken "merge fails but has appropriate contents" tests
      t7201: drop pointless "exit 0" at end of subshell
      t7400: fix broken "submodule add/reconfigure --force" test
      t7810: use test_expect_code() instead of hand-rolled comparison
      t9001: fix broken "invoke hook" test
      t9814: simplify convoluted check that command correctly errors out
      t0000-t0999: fix broken &&-chains
      t1000-t1999: fix broken &&-chains
      t2000-t2999: fix broken &&-chains
      t3000-t3999: fix broken &&-chains
      t3030: fix broken &&-chains
      t4000-t4999: fix broken &&-chains
      t5000-t5999: fix broken &&-chains
      t6000-t6999: fix broken &&-chains
      t7000-t7999: fix broken &&-chains
      t9000-t9999: fix broken &&-chains
      t9119: fix broken &&-chains
      t6046/t9833: fix use of "VAR=VAL cmd" with a shell function
      t/check-non-portable-shell: stop being so polite
      t/check-non-portable-shell: make error messages more compact
      t/check-non-portable-shell: detect "FOO=bar shell_func"
      t/test-lib: teach --chain-lint to detect broken &&-chains in subshells
      t/Makefile: add machinery to check correctness of chainlint.sed
      t/chainlint: add chainlint "basic" test cases
      t/chainlint: add chainlint "whitespace" test cases
      t/chainlint: add chainlint "one-liner" test cases
      t/chainlint: add chainlint "nested subshell" test cases
      t/chainlint: add chainlint "loop" and "conditional" test cases
      t/chainlint: add chainlint "cuddled" test cases
      t/chainlint: add chainlint "complex" test cases
      t/chainlint: add chainlint "specialized" test cases
      diff: --color-moved: rename "dimmed_zebra" to "dimmed-zebra"
      mw-to-git/t9360: fix broken &&-chain
      t/chainlint.sed: drop extra spaces from regex character class
      sequencer: fix "rebase -i --root" corrupting author header
      sequencer: fix "rebase -i --root" corrupting author header timezone
      sequencer: fix "rebase -i --root" corrupting author header timestamp
      sequencer: don't die() on bogus user-edited timestamp
      color: protect against out-of-bounds reads and writes
      chainlint: match arbitrary here-docs tags rather than hard-coded names
      chainlint: match 'quoted' here-doc tags
      chainlint: recognize multi-line $(...) when command cuddled with "$("
      chainlint: let here-doc and multi-line string commence on same line
      chainlint: recognize multi-line quoted strings more robustly
      chainlint: add test of pathological case which triggered false positive
      chainlint: match "quoted" here-doc tags
      config.mak.uname: resolve FreeBSD iconv-related compilation warning

Han-Wen Nienhuys (2):
      config: document git config getter return value
      sideband: highlight keywords in remote sideband output

Henning Schild (9):
      builtin/receive-pack: use check_signature from gpg-interface
      gpg-interface: make parse_gpg_output static and remove from interface header
      gpg-interface: add new config to select how to sign a commit
      t/t7510: check the validation of the new config gpg.format
      gpg-interface: introduce an abstraction for multiple gpg formats
      gpg-interface: do not hardcode the key string len anymore
      gpg-interface: introduce new config to select per gpg format program
      gpg-interface: introduce new signature format "x509" using gpgsm
      gpg-interface t: extend the existing GPG tests with GPGSM

Isabella Stephens (2):
      blame: prevent error if range ends past end of file
      log: prevent error if line range ends past end of file

Jameson Miller (8):
      read-cache: teach refresh_cache_entry to take istate
      read-cache: teach make_cache_entry to take object_id
      block alloc: add lifecycle APIs for cache_entry structs
      mem-pool: only search head block for available space
      mem-pool: add life cycle management functions
      mem-pool: fill out functionality
      block alloc: allocate cache entries from mem_pool
      block alloc: add validations around cache_entry lifecyle

Jean-Noël Avila (3):
      i18n: fix mistakes in translated strings
      l10n: fr.po v2.19.0 rnd 1
      l10n: fr.po v2.19.0 rnd 2

Jeff Hostetler (1):
      json_writer: new routines to create JSON data

Jeff King (50):
      make show-index a builtin
      show-index: update documentation for index v2
      fetch-pack: don't try to fetch peel values with --all
      ewah: drop ewah_deserialize function
      ewah: drop ewah_serialize_native function
      t3200: unset core.logallrefupdates when testing reflog creation
      t: switch "branch -l" to "branch --create-reflog"
      branch: deprecate "-l" option
      config: turn die_on_error into caller-facing enum
      config: add CONFIG_ERROR_SILENT handler
      config: add options parameter to git_config_from_mem
      fsck: silence stderr when parsing .gitmodules
      t6300: add a test for --ignore-case
      ref-filter: avoid backend filtering with --ignore-case
      t5500: prettify non-commit tag tests
      sequencer: handle empty-set cases consistently
      sequencer: don't say BUG on bogus input
      has_uncommitted_changes(): fall back to empty tree
      fsck: split ".gitmodules too large" error from parse failure
      fsck: downgrade gitmodulesParse default to "info"
      blame: prefer xsnprintf to strcpy for colors
      check_replace_refs: fix outdated comment
      check_replace_refs: rename to read_replace_refs
      add core.usereplacerefs config option
      reencode_string: use st_add/st_mult helpers
      reencode_string: use size_t for string lengths
      strbuf: use size_t for length in intermediate variables
      strbuf_readlink: use ssize_t
      pass st.st_size as hint for strbuf_readlink()
      strbuf_humanise: use unsigned variables
      automatically ban strcpy()
      banned.h: mark strcat() as banned
      banned.h: mark sprintf() as banned
      banned.h: mark strncpy() as banned
      score_trees(): fix iteration over trees with missing entries
      add a script to diff rendered documentation
      t5552: suppress upload-pack trace output
      for_each_*_object: store flag definitions in a single location
      for_each_*_object: take flag arguments as enum
      for_each_*_object: give more comprehensive docstrings
      for_each_packed_object: support iterating in pack-order
      t1006: test cat-file --batch-all-objects with duplicates
      cat-file: rename batch_{loose,packed}_object callbacks
      cat-file: support "unordered" output for --batch-all-objects
      cat-file: use oidset check-and-insert
      cat-file: split batch "buf" into two variables
      cat-file: use a single strbuf for all output
      for_each_*_object: move declarations to object-store.h
      test-tool.h: include git-compat-util.h
      hashcmp: assert constant hash size

Jiang Xin (4):
      l10n: zh_CN: review for git 2.18.0
      l10n: git.pot: v2.19.0 round 1 (382 new, 30 removed)
      l10n: git.pot: v2.19.0 round 2 (3 new, 5 removed)
      l10n: zh_CN: for git v2.19.0 l10n round 1 to 2

Johannes Schindelin (41):
      Makefile: fix the "built from commit" code
      merge: allow reading the merge commit message from a file
      rebase --rebase-merges: add support for octopus merges
      rebase --rebase-merges: adjust man page for octopus support
      vcbuild/README: update to accommodate for missing common-cmds.h
      t7406: avoid failures solely due to timing issues
      contrib: add a script to initialize VS Code configuration
      vscode: hard-code a couple defines
      cache.h: extract enum declaration from inside a struct declaration
      mingw: define WIN32 explicitly
      vscode: only overwrite C/C++ settings
      vscode: wrap commit messages at column 72 by default
      vscode: use 8-space tabs, no trailing ws, etc for Git's source code
      vscode: add a dictionary for cSpell
      vscode: let cSpell work on commit messages, too
      pull --rebase=<type>: allow single-letter abbreviations for the type
      t3430: demonstrate what -r, --autosquash & --exec should do
      git-compat-util.h: fix typo
      remote-curl: remove spurious period
      rebase --exec: make it work with --rebase-merges
      linear-assignment: a function to solve least-cost assignment problems
      Introduce `range-diff` to compare iterations of a topic branch
      range-diff: first rudimentary implementation
      range-diff: improve the order of the shown commits
      range-diff: also show the diff between patches
      range-diff: right-trim commit messages
      range-diff: indent the diffs just like tbdiff
      range-diff: suppress the diff headers
      range-diff: adjust the output of the commit pairs
      range-diff: do not show "function names" in hunk headers
      range-diff: use color for the commit pairs
      color: add the meta color GIT_COLOR_REVERSE
      diff: add an internal option to dual-color diffs of diffs
      range-diff: offer to dual-color the diffs
      range-diff --dual-color: skip white-space warnings
      range-diff: populate the man page
      completion: support `git range-diff`
      range-diff: left-pad patch numbers
      range-diff: make --dual-color the default mode
      range-diff: use dim/bold cues to improve dual color mode
      chainlint: fix for core.autocrlf=true

Johannes Sixt (1):
      mingw: enable atomic O_APPEND

Jonathan Nieder (12):
      object: add repository argument to grow_object_hash
      object: move grafts to object parser
      commit: add repository argument to commit_graft_pos
      commit: add repository argument to register_commit_graft
      commit: add repository argument to read_graft_file
      commit: add repository argument to prepare_commit_graft
      commit: add repository argument to lookup_commit_graft
      subtree test: add missing && to &&-chain
      subtree test: simplify preparation of expected results
      doc hash-function-transition: pick SHA-256 as NewHash
      partial-clone: render design doc using asciidoc
      Revert "Merge branch 'sb/submodule-core-worktree'"

Jonathan Tan (28):
      list-objects: check if filter is NULL before using
      fetch-pack: split up everything_local()
      fetch-pack: clear marks before re-marking
      fetch-pack: directly end negotiation if ACK ready
      fetch-pack: use ref adv. to prune "have" sent
      fetch-pack: make negotiation-related vars local
      fetch-pack: move common check and marking together
      fetch-pack: introduce negotiator API
      pack-bitmap: remove bitmap_git global variable
      pack-bitmap: add free function
      fetch-pack: write shallow, then check connectivity
      fetch-pack: support negotiation tip whitelist
      upload-pack: send refs' objects despite "filter"
      clone: check connectivity even if clone is partial
      revision: tolerate promised targets of tags
      tag: don't warn if target is missing but promised
      negotiator/skipping: skip commits during fetch
      commit-graph: refactor preparing commit graph
      object-store: add missing include
      commit-graph: add missing forward declaration
      commit-graph: add free_commit_graph
      commit-graph: store graph in struct object_store
      commit-graph: add repo arg to graph readers
      t5702: test fetch with multiple refspecs at a time
      fetch: send "refs/tags/" prefix upon CLI refspecs
      fetch-pack: unify ref in and out param
      repack: refactor setup of pack-objects cmd
      repack: repack promisor objects if -a or -A is set

Josh Steadmon (1):
      protocol-v2 doc: put HTTP headers after request

Jules Maselbas (1):
      send-email: fix tls AUTH when sending batch

Junio C Hamano (23):
      tests: clean after SANITY tests
      ewah: delete unused 'rlwit_discharge_empty()'
      Prepare to start 2.19 cycle
      First batch for 2.19 cycle
      Second batch for 2.19 cycle
      fixup! connect.h: avoid forward declaration of an enum
      fixup! refs/refs-internal.h: avoid forward declaration of an enum
      t3404: fix use of "VAR=VAL cmd" with a shell function
      Third batch for 2.19 cycle
      Fourth batch for 2.19 cycle
      remote: make refspec follow the same disambiguation rule as local refs
      Fifth batch for 2.19 cycle
      update-index: there no longer is `apply --index-info`
      gpg-interface: propagate exit status from gpg back to the callers
      Sixth batch for 2.19 cycle
      config.txt: clarify core.checkStat
      Seventh batch for 2.19 cycle
      sideband: do not read beyond the end of input
      Git 2.19-rc0
      Getting ready for -rc1
      Git 2.19-rc1
      Git 2.19-rc2
      Git 2.19

Kana Natsuno (2):
      t4018: add missing test cases for PHP
      userdiff: support new keywords in PHP hunk header

Kim Gybels (1):
      gc --auto: release pack files before auto packing

Kirill Smelkov (1):
      fetch-pack: test explicitly that --all can fetch tag references pointing to non-commits

Kyle Meyer (1):
      range-diff: update stale summary of --no-dual-color

Luis Marsano (2):
      git-credential-netrc: use in-tree Git.pm for tests
      git-credential-netrc: fix exit status when tests fail

Luke Diamand (6):
      git-p4: python3: replace <> with !=
      git-p4: python3: replace dict.has_key(k) with "k in dict"
      git-p4: python3: remove backticks
      git-p4: python3: basestring workaround
      git-p4: python3: use print() function
      git-p4: python3: fix octal constants

Marc Strapetz (1):
      Documentation: declare "core.ignoreCase" as internal variable

Martin Ågren (1):
      refspec: initalize `refspec_item` in `valid_fetch_refspec()`

Masaya Suzuki (2):
      builtin/send-pack: populate the default configs
      doc: fix want-capability separator

Max Kirillov (5):
      http-backend: cleanup writing to child process
      http-backend: respect CONTENT_LENGTH as specified by rfc3875
      unpack-trees: do not fail reset because of unmerged skipped entry
      http-backend: respect CONTENT_LENGTH for receive-pack
      http-backend: allow empty CONTENT_LENGTH

Michael Barabanov (1):
      filter-branch: skip commits present on --state-branch

Mike Hommey (1):
      fast-import: do not call diff_delta() with empty buffer

Nguyễn Thái Ngọc Duy (100):
      commit-slab.h: code split
      commit-slab: support shared commit-slab
      blame: use commit-slab for blame suspects instead of commit->util
      describe: use commit-slab for commit names instead of commit->util
      shallow.c: use commit-slab for commit depth instead of commit->util
      sequencer.c: use commit-slab to mark seen commits
      sequencer.c: use commit-slab to associate todo items to commits
      revision.c: use commit-slab for show_source
      bisect.c: use commit-slab for commit weight instead of commit->util
      name-rev: use commit-slab for rev-name instead of commit->util
      show-branch: use commit-slab for commit-name instead of commit->util
      show-branch: note about its object flags usage
      log: use commit-slab in prepare_bases() instead of commit->util
      merge: use commit-slab in merge remote desc instead of commit->util
      commit.h: delete 'util' field in struct commit
      diff: ignore --ita-[in]visible-in-index when diffing worktree-to-tree
      diff: turn --ita-invisible-in-index on by default
      t2203: add a test about "diff HEAD" case
      apply: add --intent-to-add
      parse-options: option to let --git-completion-helper show negative form
      completion: suppress some -no- options
      Add and use generic name->id mapping code for color slot parsing
      grep: keep all colors in an array
      fsck: factor out msg_id_info[] lazy initialization code
      help: add --config to list all available config
      fsck: produce camelCase config key names
      advice: keep config name in camelCase in advice_config[]
      am: move advice.amWorkDir parsing back to advice.c
      completion: drop the hard coded list of config vars
      completion: keep other config var completion in camelCase
      completion: support case-insensitive config vars
      log-tree: allow to customize 'grafted' color
      completion: complete general config vars in two steps
      upload-pack: reject shallow requests that would return nothing
      completion: collapse extra --no-.. options
      pack-objects: fix performance issues on packing large deltas
      Update messages in preparation for i18n
      archive-tar.c: mark more strings for translation
      archive-zip.c: mark more strings for translation
      builtin/config.c: mark more strings for translation
      builtin/grep.c: mark strings for translation
      builtin/pack-objects.c: mark more strings for translation
      builtin/replace.c: mark more strings for translation
      commit-graph.c: mark more strings for translation
      config.c: mark more strings for translation
      connect.c: mark more strings for translation
      convert.c: mark more strings for translation
      dir.c: mark more strings for translation
      environment.c: mark more strings for translation
      exec-cmd.c: mark more strings for translation
      object.c: mark more strings for translation
      pkt-line.c: mark more strings for translation
      refs.c: mark more strings for translation
      refspec.c: mark more strings for translation
      replace-object.c: mark more strings for translation
      sequencer.c: mark more strings for translation
      sha1-file.c: mark more strings for translation
      transport.c: mark more strings for translation
      transport-helper.c: mark more strings for translation
      pack-objects: document about thread synchronization
      apply.h: drop extern on func declaration
      attr.h: drop extern from function declaration
      blame.h: drop extern on func declaration
      cache-tree.h: drop extern from function declaration
      convert.h: drop 'extern' from function declaration
      diffcore.h: drop extern from function declaration
      diff.h: remove extern from function declaration
      line-range.h: drop extern from function declaration
      rerere.h: drop extern from function declaration
      repository.h: drop extern from function declaration
      revision.h: drop extern from function declaration
      submodule.h: drop extern from function declaration
      config.txt: reorder blame stuff to keep config keys sorted
      Makefile: add missing dependency for command-list.h
      diff.c: move read_index() code back to the caller
      cache-tree: wrap the_index based wrappers with #ifdef
      attr: remove an implicit dependency on the_index
      convert.c: remove an implicit dependency on the_index
      dir.c: remove an implicit dependency on the_index in pathspec code
      preload-index.c: use the right index instead of the_index
      ls-files: correct index argument to get_convert_attr_ascii()
      unpack-trees: remove 'extern' on function declaration
      unpack-trees: add a note about path invalidation
      unpack-trees: don't shadow global var the_index
      unpack-trees: convert clear_ce_flags* to avoid the_index
      unpack-trees: avoid the_index in verify_absent()
      pathspec.c: use the right index instead of the_index
      submodule.c: use the right index instead of the_index
      entry.c: use the right index instead of the_index
      attr: remove index from git_attr_set_direction()
      grep: use the right index instead of the_index
      archive.c: avoid access to the_index
      archive-*.c: use the right repository
      resolve-undo.c: use the right index instead of the_index
      apply.c: pass struct apply_state to more functions
      apply.c: make init_apply_state() take a struct repository
      apply.c: remove implicit dependency on the_index
      blame.c: remove implicit dependency on the_index
      cherry-pick: fix --quit not deleting CHERRY_PICK_HEAD
      generate-cmdlist.sh: collect config from all config.txt files

Nicholas Guriev (1):
      mergetool: don't suggest to continue after last file

Olga Telezhnaya (5):
      ref-filter: add info_source to valid_atom
      ref-filter: fill empty fields with empty values
      ref-filter: initialize eaten variable
      ref-filter: merge get_obj and get_object
      ref-filter: use oid_object_info() to get object

Peter Krefting (2):
      l10n: sv.po: Update Swedish translation(3608t0f0u)
      l10n: sv.po: Update Swedish translation (3958t0f0u)

Phillip Wood (7):
      add -p: fix counting empty context lines in edited patches
      sequencer: do not squash 'reword' commits when we hit conflicts
      sequencer: handle errors from read_author_ident()
      sequencer: fix quoting in write_author_script
      rebase -i: fix numbering in squash message
      t3430: add conflicting commit
      rebase -i: fix SIGSEGV when 'merge <branch>' fails

Prathamesh Chavan (4):
      submodule foreach: correct '$path' in nested submodules from a subdirectory
      submodule foreach: document '$sm_path' instead of '$path'
      submodule foreach: document variable '$displaypath'
      submodule: port submodule subcommand 'foreach' from shell to C

Ralf Thielow (1):
      l10n: de.po: translate 108 new messages

Ramsay Jones (3):
      fsck: check skiplist for object in fsck_blob()
      t6036: fix broken && chain in sub-shell
      t5562: avoid non-portable "export FOO=bar" construct

Raphaël Hertzog (1):
      l10n: fr: fix a message seen in git bisect

René Scharfe (10):
      remote: clear string_list after use in mv()
      add, update-index: fix --chmod argument help
      difftool: remove angular brackets from argument help
      pack-objects: specify --index-version argument help explicitly
      send-pack: specify --force-with-lease argument help explicitly
      shortlog: correct option help for -w
      parse-options: automatically infer PARSE_OPT_LITERAL_ARGHELP
      checkout-index: improve argument help for --stage
      remote: improve argument help for add --mirror
      parseopt: group literal string alternatives in argument help

SZEDER Gábor (30):
      update-ref --stdin: use skip_prefix()
      t7510-signed-commit: use 'test_must_fail'
      tests: make forging GPG signed commits and tags more robust
      t5541: clean up truncating access log
      t/lib-httpd: add the strip_access_log() helper function
      t/lib-httpd: avoid occasional failures when checking access.log
      t5608: fix broken &&-chain
      t9300: wait for background fast-import process to die after killing it
      travis-ci: run Coccinelle static analysis with two parallel jobs
      travis-ci: fail if Coccinelle static analysis found something to transform
      coccinelle: mark the 'coccicheck' make target as .PHONY
      coccinelle: use $(addsuffix) in 'coccicheck' make target
      coccinelle: exclude sha1dc source files from static analysis
      coccinelle: put sane filenames into output patches
      coccinelle: extract dedicated make target to clean Coccinelle's results
      travis-ci: include the trash directories of failed tests in the trace log
      t5318: use 'test_cmp_bin' to compare commit-graph files
      t5318: avoid unnecessary command substitutions
      t5310-pack-bitmaps: fix bogus 'pack-objects to file can use bitmap' test
      tests: use 'test_must_be_empty' instead of '! test -s'
      tests: use 'test_must_be_empty' instead of 'test ! -s'
      tests: use 'test_must_be_empty' instead of 'test_cmp /dev/null <out>'
      tests: use 'test_must_be_empty' instead of 'test_cmp <empty> <out>'
      t7501-commit: drop silly command substitution
      t0020-crlf: check the right file
      t4051-diff-function-context: read the right file
      t6018-rev-list-glob: fix 'empty stdin' test
      t3903-stash: don't try to grep non-existing file
      t3420-rebase-autostash: don't try to grep non-existing files
      t/lib-rebase.sh: support explicit 'pick' commands in 'fake_editor.sh'

Samuel Maftoul (1):
      branch: support configuring --sort via .gitconfig

Sebastian Kisela (2):
      git-instaweb: support Fedora/Red Hat apache module path
      git-instaweb: fix apache2 config with apache >= 2.4

Stefan Beller (87):
      repository: introduce parsed objects field
      object: add repository argument to create_object
      alloc: add repository argument to alloc_blob_node
      alloc: add repository argument to alloc_tree_node
      alloc: add repository argument to alloc_commit_node
      alloc: add repository argument to alloc_tag_node
      alloc: add repository argument to alloc_object_node
      alloc: add repository argument to alloc_report
      alloc: add repository argument to alloc_commit_index
      object: allow grow_object_hash to handle arbitrary repositories
      object: allow create_object to handle arbitrary repositories
      alloc: allow arbitrary repositories for alloc functions
      object-store: move object access functions to object-store.h
      shallow: add repository argument to set_alternate_shallow_file
      shallow: add repository argument to register_shallow
      shallow: add repository argument to check_shallow_file_for_update
      shallow: add repository argument to is_repository_shallow
      cache: convert get_graft_file to handle arbitrary repositories
      path.c: migrate global git_path_* to take a repository argument
      shallow: migrate shallow information into the object parser
      commit: allow prepare_commit_graft to handle arbitrary repositories
      commit: allow lookup_commit_graft to handle arbitrary repositories
      refs/packed-backend.c: close fd of empty file
      submodule--helper: plug mem leak in print_default_remote
      sequencer.c: plug leaks in do_pick_commit
      submodule: fix NULL correctness in renamed broken submodules
      t5526: test recursive submodules when fetching moved submodules
      submodule: unset core.worktree if no working tree is present
      submodule: ensure core.worktree is set after update
      submodule deinit: unset core.worktree
      submodule.c: report the submodule that an error occurs in
      sequencer.c: plug mem leak in git_sequencer_config
      .mailmap: merge different spellings of names
      object: add repository argument to parse_object
      object: add repository argument to lookup_object
      object: add repository argument to parse_object_buffer
      object: add repository argument to object_as_type
      blob: add repository argument to lookup_blob
      tree: add repository argument to lookup_tree
      commit: add repository argument to lookup_commit_reference_gently
      commit: add repository argument to lookup_commit_reference
      commit: add repository argument to lookup_commit
      commit: add repository argument to parse_commit_buffer
      commit: add repository argument to set_commit_buffer
      commit: add repository argument to get_cached_commit_buffer
      tag: add repository argument to lookup_tag
      tag: add repository argument to parse_tag_buffer
      tag: add repository argument to deref_tag
      object: allow object_as_type to handle arbitrary repositories
      object: allow lookup_object to handle arbitrary repositories
      blob: allow lookup_blob to handle arbitrary repositories
      tree: allow lookup_tree to handle arbitrary repositories
      commit: allow lookup_commit to handle arbitrary repositories
      tag: allow lookup_tag to handle arbitrary repositories
      tag: allow parse_tag_buffer to handle arbitrary repositories
      commit.c: allow parse_commit_buffer to handle arbitrary repositories
      commit-slabs: remove realloc counter outside of slab struct
      commit.c: migrate the commit buffer to the parsed object store
      commit.c: allow set_commit_buffer to handle arbitrary repositories
      commit.c: allow get_cached_commit_buffer to handle arbitrary repositories
      object.c: allow parse_object_buffer to handle arbitrary repositories
      object.c: allow parse_object to handle arbitrary repositories
      tag.c: allow deref_tag to handle arbitrary repositories
      commit.c: allow lookup_commit_reference_gently to handle arbitrary repositories
      commit.c: allow lookup_commit_reference to handle arbitrary repositories
      xdiff/xdiff.h: remove unused flags
      xdiff/xdiffi.c: remove unneeded function declarations
      t4015: avoid git as a pipe input
      diff.c: do not pass diff options as keydata to hashmap
      diff.c: adjust hash function signature to match hashmap expectation
      diff.c: add a blocks mode for moved code detection
      diff.c: decouple white space treatment from move detection algorithm
      diff.c: factor advance_or_nullify out of mark_color_as_moved
      diff.c: add white space mode to move detection that allows indent changes
      diff.c: offer config option to control ws handling in move detection
      xdiff/xhistogram: pass arguments directly to fall_back_to_classic_diff
      xdiff/xhistogram: factor out memory cleanup into free_index()
      xdiff/xhistogram: move index allocation into find_lcs
      Documentation/git-interpret-trailers: explain possible values
      xdiff/histogram: remove tail recursion
      t1300: document current behavior of setting options
      xdiff: reduce indent heuristic overhead
      config: fix case sensitive subsection names on writing
      git-config: document accidental multi-line setting in deprecated syntax
      git-submodule.sh: accept verbose flag in cmd_update to be non-quiet
      t7410: update to new style
      builtin/submodule--helper: remove stray new line

Taylor Blau (9):
      Documentation/config.txt: camel-case lineNumber for consistency
      grep.c: expose {,inverted} match column in match_line()
      grep.[ch]: extend grep_opt to allow showing matched column
      grep.c: display column number of first match
      builtin/grep.c: add '--column' option to 'git-grep(1)'
      grep.c: add configuration variables to show matched option
      contrib/git-jump/git-jump: jump to exact location
      grep.c: extract show_line_header()
      grep.c: teach 'git grep --only-matching'

Thomas Rast (1):
      range-diff: add tests

Tobias Klauser (1):
      git-rebase--preserve-merges: fix formatting of todo help message

Todd Zullinger (4):
      git-credential-netrc: minor whitespace cleanup in test script
      git-credential-netrc: make "all" default target of Makefile
      gitignore.txt: clarify default core.excludesfile path
      dir.c: fix typos in core.excludesfile comment

Trần Ngọc Quân (1):
      l10n: vi.po(3958t): updated Vietnamese translation v2.19.0 round 2

Ville Skyttä (1):
      Documentation: spelling and grammar fixes

Vladimir Parfinenko (1):
      rebase: fix documentation formatting

William Chargin (2):
      sha1-name.c: for ":/", find detached HEAD commits
      t: factor out FUNNYNAMES as shared lazy prereq

Xiaolong Ye (1):
      format-patch: clear UNINTERESTING flag before prepare_bases

brian m. carlson (21):
      send-email: add an auto option for transfer encoding
      send-email: accept long lines with suitable transfer encoding
      send-email: automatically determine transfer-encoding
      docs: correct RFC specifying email line length
      sequencer: pass absolute GIT_WORK_TREE to exec commands
      cache: update object ID functions for the_hash_algo
      tree-walk: replace hard-coded constants with the_hash_algo
      hex: switch to using the_hash_algo
      commit: express tree entry constants in terms of the_hash_algo
      strbuf: allocate space with GIT_MAX_HEXSZ
      sha1-name: use the_hash_algo when parsing object names
      refs/files-backend: use the_hash_algo for writing refs
      builtin/update-index: convert to using the_hash_algo
      builtin/update-index: simplify parsing of cacheinfo
      builtin/fmt-merge-msg: make hash independent
      builtin/merge: switch to use the_hash_algo
      builtin/merge-recursive: make hash independent
      diff: switch GIT_SHA1_HEXSZ to use the_hash_algo
      log-tree: switch GIT_SHA1_HEXSZ to the_hash_algo->hexsz
      sha1-file: convert constants to uses of the_hash_algo
      pretty: switch hard-coded constants to the_hash_algo

Ævar Arnfjörð Bjarmason (45):
      checkout tests: index should be clean after dwim checkout
      checkout.h: wrap the arguments to unique_tracking_name()
      checkout.c: introduce an *_INIT macro
      checkout.c: change "unique" member to "num_matches"
      checkout: pass the "num_matches" up to callers
      builtin/checkout.c: use "ret" variable for return
      checkout: add advice for ambiguous "checkout <branch>"
      checkout & worktree: introduce checkout.defaultRemote
      refspec: s/refspec_item_init/&_or_die/g
      refspec: add back a refspec_item_init() function
      doc hash-function-transition: note the lack of a changelog
      receive.fsck.<msg-id> tests: remove dead code
      config doc: don't describe *.fetchObjects twice
      config doc: unify the description of fsck.* and receive.fsck.*
      config doc: elaborate on what transfer.fsckObjects does
      config doc: elaborate on fetch.fsckObjects security
      transfer.fsckObjects tests: untangle confusing setup
      fetch: implement fetch.fsck.*
      fsck: test & document {fetch,receive}.fsck.* config fallback
      fsck: add stress tests for fsck.skipList
      fsck: test and document unknown fsck.<msg-id> values
      tests: make use of the test_must_be_empty function
      tests: make use of the test_must_be_empty function
      fetch tests: change "Tag" test tag to "testTag"
      push tests: remove redundant 'git push' invocation
      push tests: fix logic error in "push" test assertion
      push tests: add more testing for forced tag pushing
      push tests: assert re-pushing annotated tags
      negotiator: unknown fetch.negotiationAlgorithm should error out
      fetch doc: cross-link two new negotiation options
      sha1dc: update from upstream
      push: use PARSE_OPT_LITERAL_ARGHELP instead of unbalanced brackets
      fetch tests: correct a comment "remove it" -> "remove them"
      pull doc: fix a long-standing grammar error
      submodule: add more exhaustive up-path testing
      refactor various if (x) FREE_AND_NULL(x) to just FREE_AND_NULL(x)
      t2024: mark test using "checkout -p" with PERL prerequisite
      tests: fix and add lint for non-portable head -c N
      tests: fix and add lint for non-portable seq
      tests: fix comment syntax in chainlint.sed for AIX sed
      tests: use shorter labels in chainlint.sed for AIX sed
      tests: fix version-specific portability issue in Perl JSON
      tests: fix and add lint for non-portable grep --file
      tests: fix non-portable "${var:-"str"}" construct
      tests: fix non-portable iconv invocation

Łukasz Stelmach (1):
      completion: complete remote names too


^ permalink raw reply	[relevance 1%]

* RE: Multiple GIT Accounts & HTTPS Client Certificates - Config
  @ 2018-09-10 13:29  5% ` Randall S. Becker
  2018-09-11  7:29  0%   ` Sergei Haller
  0 siblings, 1 reply; 200+ results
From: Randall S. Becker @ 2018-09-10 13:29 UTC (permalink / raw)
  To: 'Sergei Haller', git

On September 10, 2018 4:09 AM, Sergei Haller wrote:
> my problem is basically the following: my git server (https) requires
> authentication using a clent x509 certificate.
> 
> And I have multiple x509 certificates that match the server.
> 
> when I access the https server using a browser, the browser asks which
> certificate to use and everything is fine.
> 
> When I try to access the git server from the command line (git pull or similar),
> the git will pick one of the available certificates (randomly or alphabetically)
> and try to access the server with that client certificate. Ending in the
> situation that git picks the wrong certificate.
> 
> I can workaround by deleting all client certificates from the windows
> certificate store except the "correct" one => then git command line will pick
> the correct certificate (the only one available) and everything works as
> expected.
> 
> Workaround is a workaround, I need to use all of the certificates repeatedly
> for different repos and different other aplications (non-git), so I've been
> deliting and reinstalling the certificates all the time in the last weeks...
> 
> How can I tell git cmd (per config option??) to use a particular client
> certificate for authenticating to the https server (I could provide fingerprint
> or serial number or sth like that)
> 
> current environment: windows 10 and git version 2.18.0.windows.1
> 
> Would be absolutely acceptable if git would ask interactively which client
> certificate to use (in case its not configurable)
> 
> (I asked this question here before:
> https://stackoverflow.com/questions/51952568/multiple-git-accounts-
> https-client-certificates-config
> )

Would you consider using SSH to authenticate? You can control which private key you use based on your ~/.ssh/config entries, which are case sensitive. You can choose the SSH key to use by playing with the case of the host name, like:

github.com
Github.com
gitHub.com

even if your user is "git" in all cases above. It is a bit hacky but it is part of the SSH spec and is supported by git and EGit (as of 5.x).

Cheers,
Randall

--
Randall S. Becker
Managing Director, Nexbridge Inc.
LinkedIn.com/in/randallbecker
+1.416.984.9826




^ permalink raw reply	[relevance 5%]

* Re: [PATCH 2/2] submodule: munge paths to submodule git directories
  2018-08-29 18:10  5%             ` Stefan Beller
@ 2018-08-29 21:03  0%               ` Jeff King
  0 siblings, 0 replies; 200+ results
From: Jeff King @ 2018-08-29 21:03 UTC (permalink / raw)
  To: Stefan Beller; +Cc: Brandon Williams, git

On Wed, Aug 29, 2018 at 11:10:51AM -0700, Stefan Beller wrote:

> > Do you care about case-folding issues (e.g., submodules "FOO" and "foo"
> > colliding)?
> 
> I do. :(
> 
> 2d84f13dcb6 (config: fix case sensitive subsection names on writing, 2018-08-08)
> explains the latest episode of case folding with submodules involved.
> 
> > I'm OK if the answer is "no", but if you do want to deal with it, the
> > time is probably now.
> 
> Good point. But as soon as we start discussing case sensitivity, we
> are drawn down the rabbit hole of funny file names. (Try naming
> a submodule "CON1" and obtain it on Windows for example)
> So we would need to have a file system specific encoding function for
> submodule names, which sounds like a maintenance night mare.

Hmph. I'd hoped that simply escaping metacharacters and doing some
obvious case-folding would be enough. And I think that would cover most
accidental cases. But yeah, Windows reserved names are basically
indistinguishable from reasonable names. They'd probably need
special-cased.

OTOH, I'm not sure how we handle those for entries in the actual tree.
Poking around git-for-windows/git, I think it uses the magic "\\?"
marker to tell the OS to interpret the name literally.

So I wonder if it might be sufficient to just deal with the more obvious
folding issues. Or as you noted, if we just choose lowercase names as
the normalized form, that might also be enough. :)

> So if I was thinking in the scheme presented above, we could just
> have another rule that is
> 
>   [A-Z]  -> _[a-z]
> 
> (lowercase capital letters and escape them with an underscore)

Yes, that makes even the capitalized "CON" issues go away. It's not a
one-to-one mapping, though ("foo-" and "foo_" map to the same entity).

If we want that, too, I think something like url-encoding is fine, with
the caveat that we simply urlencode _more_ things (i.e., anything not in
[a-z_]).

-Peff

^ permalink raw reply	[relevance 0%]

* Re: [PATCH 2/2] submodule: munge paths to submodule git directories
  @ 2018-08-29 18:10  5%             ` Stefan Beller
  2018-08-29 21:03  0%               ` Jeff King
  0 siblings, 1 reply; 200+ results
From: Stefan Beller @ 2018-08-29 18:10 UTC (permalink / raw)
  To: Jeff King; +Cc: Brandon Williams, git

On Tue, Aug 28, 2018 at 10:25 PM Jeff King <peff@peff.net> wrote:
>
> On Tue, Aug 28, 2018 at 02:35:25PM -0700, Stefan Beller wrote:
>
> > 3) (optional) instead of putting it all in modules/, use another
> >    directory gitmodules/ for example. this will make sure we can tell
> >    if a repository has been converted or is stuck with a setup of a
> >    current git.
>
> I actually kind of like that idea, as it makes the interaction between
> old and new names much simpler to reason about.
>
> And since old code won't know about the new names anyway, there's in
> theory no downside. In practice, of course, the encoding may often be a
> noop, and lazy scripts would continue to work most of the time if you
> didn't change out the prefix directory. I'm not sure if that is an
> argument for the scheme (because it will suss out broken scripts more
> consistently) or against it (because 99% of the time those old scripts
> would just happen to work).
>
> > > This is exactly the reason why I wanted to get some opinions on what the
> > > best thing to do here would be.  I _think_ the best thing would probably
> > > be to write a specific routine to do the conversion, and it wouldn't
> > > even have to be all that complex.  Basically I'm just interested in
> > > converting '/' characters so that things no longer behave like
> > > nested directories.
> >
> > Yeah, then let's just convert '/' with as little overhead as possible.
>
> Do you care about case-folding issues (e.g., submodules "FOO" and "foo"
> colliding)?

I do. :(

2d84f13dcb6 (config: fix case sensitive subsection names on writing, 2018-08-08)
explains the latest episode of case folding with submodules involved.

> I'm OK if the answer is "no", but if you do want to deal with it, the
> time is probably now.

Good point. But as soon as we start discussing case sensitivity, we
are drawn down the rabbit hole of funny file names. (Try naming
a submodule "CON1" and obtain it on Windows for example)
So we would need to have a file system specific encoding function for
submodule names, which sounds like a maintenance night mare.

The CON1 example shows that URL encoding may not be enough
on Windows and we'd have to extend the encoding if we care about
FS issues.

Another example would be "a" and "a\b" which would be a mess
in Windows as the '\' would work as a dir separator whereas these
two names were ok on linux. This would be fixed with url encoding.

URL encoding would not fix the case-folding issue that you
mentioned above.

So if I was thinking in the scheme presented above, we could just
have another rule that is

  [A-Z]  -> _[a-z]

(lowercase capital letters and escape them with an underscore)

But with that rule added, we are inventing a really complicated
encoding scheme already.

^ permalink raw reply	[relevance 5%]

* Re: [PATCH 2/2] submodule: munge paths to submodule git directories
  2018-08-14 18:04  0%       ` Brandon Williams
  2018-08-14 18:57  0%         ` Jonathan Nieder
  2018-08-14 18:58  0%         ` Jeff King
@ 2018-08-28 21:35  0%         ` Stefan Beller
    2 siblings, 1 reply; 200+ results
From: Stefan Beller @ 2018-08-28 21:35 UTC (permalink / raw)
  To: Brandon Williams; +Cc: Jeff King, git

> > > -           echo "gitdir: ../../../.git/modules/sub3/modules/dirdir/subsub" >./sub3/dirdir/subsub/.git_expect
> > > +           echo "gitdir: ../../../.git/modules/sub3/modules/dirdir%2fsubsub" >./sub3/dirdir/subsub/.git_expect
> >
> > One interesting thing about url-encoding is that it's not one-to-one.
> > This case could also be %2F, which is a different file (on a
> > case-sensitive filesystem). I think "%20" and "+" are similarly
> > interchangeable.
> >
> > If we were decoding the filenames, that's fine. The round-trip is
> > lossless.
> >
> > But that's not quite how the new code behaves. We encode the input and
> > then check to see if it matches an encoding we previously performed. So
> > if our urlencode routines ever change, this will subtly break.

And this is the problem:
a) we have a 'complicated' encoding here, which must never change
b) the "encode and check if it matches", will produce ugly code going forward,
    as it tries to differentiate between submodules named "url_encoded(a)"
    and "a" (e.g. "a%20b" and "a b" would conflict and we have to resolve
    the conflict, although those two names are perfectly fine as they do not
    have the original problem of having slashes)

Hence I would propose a simpler encoding:

1)    / -> _ ( replace a slash by an underscore)
2)    _ -> __ (replace any underscore by 2 underscores, this is just the
          escaping mechanism to differentiate a/b and a_b)

3) (optional) instead of putting it all in modules/, use another
directory gitmodules/
    for example. this will make sure we can tell if a repository has
been converted
    or is stuck with a setup of a current git.

> This is exactly the reason why I wanted to get some opinions on what the
> best thing to do here would be.  I _think_ the best thing would probably
> be to write a specific routine to do the conversion, and it wouldn't
> even have to be all that complex.  Basically I'm just interested in
> converting '/' characters so that things no longer behave like
> nested directories.

Yeah, then let's just convert '/' with as little overhead as possible.

Thanks,
Stefan

^ permalink raw reply	[relevance 0%]

* [GSoC][PATCH v7 12/20] rebase -i: remove unused modes and functions
  @ 2018-08-28 12:10  6%           ` Alban Gruin
    1 sibling, 0 replies; 200+ results
From: Alban Gruin @ 2018-08-28 12:10 UTC (permalink / raw)
  To: git
  Cc: Stefan Beller, Christian Couder, Pratik Karki,
	Johannes Schindelin, phillip.wood, gitster, Alban Gruin

This removes the modes `--skip-unnecessary-picks`, `--append-todo-help`,
and `--checkout-onto` from rebase--helper.c, the functions of
git-rebase--interactive.sh that were rendered useless by the rewrite of
complete_action(), and append_todo_help_to_file() from
rebase-interactive.c.

skip_unnecessary_picks() and checkout_onto() becomes static, as they are
only used inside of the sequencer.

Signed-off-by: Alban Gruin <alban.gruin@gmail.com>
---
No changes since v6.

 builtin/rebase--helper.c   | 23 ++----------------
 git-rebase--interactive.sh | 50 --------------------------------------
 rebase-interactive.c       | 22 -----------------
 rebase-interactive.h       |  1 -
 sequencer.c                |  8 +++---
 sequencer.h                |  4 ---
 6 files changed, 6 insertions(+), 102 deletions(-)

diff --git a/builtin/rebase--helper.c b/builtin/rebase--helper.c
index 01b3333958..e1460136f5 100644
--- a/builtin/rebase--helper.c
+++ b/builtin/rebase--helper.c
@@ -17,9 +17,8 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
 	int abbreviate_commands = 0, rebase_cousins = -1;
 	enum {
 		CONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_OIDS, EXPAND_OIDS,
-		CHECK_TODO_LIST, SKIP_UNNECESSARY_PICKS, REARRANGE_SQUASH,
-		ADD_EXEC, APPEND_TODO_HELP, EDIT_TODO, PREPARE_BRANCH,
-		CHECKOUT_ONTO, COMPLETE_ACTION
+		CHECK_TODO_LIST, REARRANGE_SQUASH, ADD_EXEC, EDIT_TODO, PREPARE_BRANCH,
+		COMPLETE_ACTION
 	} command = 0;
 	struct option options[] = {
 		OPT_BOOL(0, "ff", &opts.allow_ff, N_("allow fast-forward")),
@@ -44,21 +43,15 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
 			N_("expand commit ids in the todo list"), EXPAND_OIDS),
 		OPT_CMDMODE(0, "check-todo-list", &command,
 			N_("check the todo list"), CHECK_TODO_LIST),
-		OPT_CMDMODE(0, "skip-unnecessary-picks", &command,
-			N_("skip unnecessary picks"), SKIP_UNNECESSARY_PICKS),
 		OPT_CMDMODE(0, "rearrange-squash", &command,
 			N_("rearrange fixup/squash lines"), REARRANGE_SQUASH),
 		OPT_CMDMODE(0, "add-exec-commands", &command,
 			N_("insert exec commands in todo list"), ADD_EXEC),
-		OPT_CMDMODE(0, "append-todo-help", &command,
-			    N_("insert the help in the todo list"), APPEND_TODO_HELP),
 		OPT_CMDMODE(0, "edit-todo", &command,
 			    N_("edit the todo list during an interactive rebase"),
 			    EDIT_TODO),
 		OPT_CMDMODE(0, "prepare-branch", &command,
 			    N_("prepare the branch to be rebased"), PREPARE_BRANCH),
-		OPT_CMDMODE(0, "checkout-onto", &command,
-			    N_("checkout a commit"), CHECKOUT_ONTO),
 		OPT_CMDMODE(0, "complete-action", &command,
 			    N_("complete the action"), COMPLETE_ACTION),
 		OPT_END()
@@ -94,26 +87,14 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
 		return !!transform_todos(flags);
 	if (command == CHECK_TODO_LIST && argc == 1)
 		return !!check_todo_list();
-	if (command == SKIP_UNNECESSARY_PICKS && argc == 1) {
-		struct object_id oid;
-		int ret = skip_unnecessary_picks(&oid);
-
-		if (!ret)
-			puts(oid_to_hex(&oid));
-		return !!ret;
-	}
 	if (command == REARRANGE_SQUASH && argc == 1)
 		return !!rearrange_squash();
 	if (command == ADD_EXEC && argc == 2)
 		return !!sequencer_add_exec_commands(argv[1]);
-	if (command == APPEND_TODO_HELP && argc == 1)
-		return !!append_todo_help_to_file(0, keep_empty);
 	if (command == EDIT_TODO && argc == 1)
 		return !!edit_todo_list(flags);
 	if (command == PREPARE_BRANCH && argc == 2)
 		return !!prepare_branch_to_be_rebased(&opts, argv[1]);
-	if (command == CHECKOUT_ONTO && argc == 4)
-		return !!checkout_onto(&opts, argv[1], argv[2], argv[3]);
 	if (command == COMPLETE_ACTION && argc == 6)
 		return !!complete_action(&opts, flags, argv[1], argv[2], argv[3],
 					 argv[4], argv[5], autosquash);
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index 59dc4888a6..0d66c0f8b8 100644
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -16,56 +16,6 @@ todo="$state_dir"/git-rebase-todo
 GIT_CHERRY_PICK_HELP="$resolvemsg"
 export GIT_CHERRY_PICK_HELP
 
-comment_char=$(git config --get core.commentchar 2>/dev/null)
-case "$comment_char" in
-'' | auto)
-	comment_char="#"
-	;;
-?)
-	;;
-*)
-	comment_char=$(echo "$comment_char" | cut -c1)
-	;;
-esac
-
-die_abort () {
-	apply_autostash
-	rm -rf "$state_dir"
-	die "$1"
-}
-
-has_action () {
-	test -n "$(git stripspace --strip-comments <"$1")"
-}
-
-git_sequence_editor () {
-	if test -z "$GIT_SEQUENCE_EDITOR"
-	then
-		GIT_SEQUENCE_EDITOR="$(git config sequence.editor)"
-		if [ -z "$GIT_SEQUENCE_EDITOR" ]
-		then
-			GIT_SEQUENCE_EDITOR="$(git var GIT_EDITOR)" || return $?
-		fi
-	fi
-
-	eval "$GIT_SEQUENCE_EDITOR" '"$@"'
-}
-
-expand_todo_ids() {
-	git rebase--helper --expand-ids
-}
-
-collapse_todo_ids() {
-	git rebase--helper --shorten-ids
-}
-
-get_missing_commit_check_level () {
-	check_level=$(git config --get rebase.missingCommitsCheck)
-	check_level=${check_level:-ignore}
-	# Don't be case sensitive
-	printf '%s' "$check_level" | tr 'A-Z' 'a-z'
-}
-
 # Initiate an action. If the cannot be any
 # further action it  may exec a command
 # or exit and not return.
diff --git a/rebase-interactive.c b/rebase-interactive.c
index 4a9a10eff4..0f4119cbae 100644
--- a/rebase-interactive.c
+++ b/rebase-interactive.c
@@ -52,28 +52,6 @@ void append_todo_help(unsigned edit_todo, unsigned keep_empty,
 	}
 }
 
-int append_todo_help_to_file(unsigned edit_todo, unsigned keep_empty)
-{
-	struct strbuf buf = STRBUF_INIT;
-	FILE *todo;
-	int ret;
-
-	todo = fopen_or_warn(rebase_path_todo(), "a");
-	if (!todo)
-		return -1;
-
-	append_todo_help(edit_todo, keep_empty, &buf);
-
-	ret = fputs(buf.buf, todo);
-	if (ret < 0)
-		error_errno(_("could not append help text to '%s'"), rebase_path_todo());
-
-	fclose(todo);
-	strbuf_release(&buf);
-
-	return ret;
-}
-
 int edit_todo_list(unsigned flags)
 {
 	struct strbuf buf = STRBUF_INIT;
diff --git a/rebase-interactive.h b/rebase-interactive.h
index d33f3176b7..971da03776 100644
--- a/rebase-interactive.h
+++ b/rebase-interactive.h
@@ -3,7 +3,6 @@
 
 void append_todo_help(unsigned edit_todo, unsigned keep_empty,
 		      struct strbuf *buf);
-int append_todo_help_to_file(unsigned edit_todo, unsigned keep_empty);
 int edit_todo_list(unsigned flags);
 
 #endif
diff --git a/sequencer.c b/sequencer.c
index 241195a8be..fe0b52f08e 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -3175,9 +3175,9 @@ int prepare_branch_to_be_rebased(struct replay_opts *opts, const char *commit)
 	return 0;
 }
 
-int checkout_onto(struct replay_opts *opts,
-		  const char *onto_name, const char *onto,
-		  const char *orig_head)
+static int checkout_onto(struct replay_opts *opts,
+			 const char *onto_name, const char *onto,
+			 const char *orig_head)
 {
 	struct object_id oid;
 	const char *action = reflog_message(opts, "start", "checkout %s", onto_name);
@@ -4424,7 +4424,7 @@ static int rewrite_file(const char *path, const char *buf, size_t len)
 }
 
 /* skip picking commits whose parents are unchanged */
-int skip_unnecessary_picks(struct object_id *output_oid)
+static int skip_unnecessary_picks(struct object_id *output_oid)
 {
 	const char *todo_file = rebase_path_todo();
 	struct strbuf buf = STRBUF_INIT;
diff --git a/sequencer.h b/sequencer.h
index d58766c6d7..02e3d7940e 100644
--- a/sequencer.h
+++ b/sequencer.h
@@ -91,7 +91,6 @@ int sequencer_add_exec_commands(const char *command);
 int transform_todos(unsigned flags);
 enum missing_commit_check_level get_missing_commit_check_level(void);
 int check_todo_list(void);
-int skip_unnecessary_picks(struct object_id *output_oid);
 int complete_action(struct replay_opts *opts, unsigned flags,
 		    const char *shortrevisions, const char *onto_name,
 		    const char *onto, const char *orig_head, const char *cmd,
@@ -114,9 +113,6 @@ void commit_post_rewrite(const struct commit *current_head,
 			 const struct object_id *new_head);
 
 int prepare_branch_to_be_rebased(struct replay_opts *opts, const char *commit);
-int checkout_onto(struct replay_opts *opts,
-		  const char *onto_name, const char *onto,
-		  const char *orig_head);
 
 #define SUMMARY_INITIAL_COMMIT   (1 << 0)
 #define SUMMARY_SHOW_AUTHOR_DATE (1 << 1)
-- 
2.18.0


^ permalink raw reply related	[relevance 6%]

* What's cooking in git.git (Aug 2018, #05; Mon, 20)
@ 2018-08-20 22:15  1% Junio C Hamano
  0 siblings, 0 replies; 200+ results
From: Junio C Hamano @ 2018-08-20 22:15 UTC (permalink / raw)
  To: git

Here are the topics that have been cooking.  Commits prefixed with
'-' are only in 'pu' (proposed updates) while commits prefixed with
'+' are in 'next'.  The ones marked with '.' do not appear in any of
the integration branches, but I am still holding onto them.

An early preview of the upcoming 2.19 release of Git has been tagged
as v2.19.0-rc0; before -rc1, I plan to merge three more topics to
'master' from 'next'.

You can find the changes described here in the integration branches
of the repositories listed at

    http://git-blame.blogspot.com/p/git-public-repositories.html

--------------------------------------------------
[Graduated to "master"]

* ab/checkout-default-remote (2018-08-18) 1 commit
  (merged to 'next' on 2018-08-20 at 21fff26f74)
 + t2024: mark test using "checkout -p" with PERL prerequisite

 Test fix.


* ab/fetch-tags-noclobber (2018-08-13) 7 commits
  (merged to 'next' on 2018-08-15 at eca0ac8afa)
 + pull doc: fix a long-standing grammar error
 + fetch tests: correct a comment "remove it" -> "remove them"
 + push tests: assert re-pushing annotated tags
 + push tests: add more testing for forced tag pushing
 + push tests: fix logic error in "push" test assertion
 + push tests: remove redundant 'git push' invocation
 + fetch tests: change "Tag" test tag to "testTag"

 Test and doc clean-ups.


* ab/newhash-is-sha256 (2018-08-07) 2 commits
  (merged to 'next' on 2018-08-15 at 2e808d75d3)
 + doc hash-function-transition: pick SHA-256 as NewHash
 + doc hash-function-transition: note the lack of a changelog

 Documentation update.


* ab/submodule-relative-url-tests (2018-08-14) 1 commit
  (merged to 'next' on 2018-08-17 at 17b28d8262)
 + submodule: add more exhaustive up-path testing

 Test updates.


* ab/test-must-be-empty-for-master (2018-07-30) 1 commit
  (merged to 'next' on 2018-08-15 at 17652a77fb)
 + tests: make use of the test_must_be_empty function

 Test updates.


* ds/commit-graph-fsck (2018-08-13) 1 commit
  (merged to 'next' on 2018-08-15 at a2f82d3cbd)
 + t5318: use 'test_cmp_bin' to compare commit-graph files

 Test fix.


* en/incl-forward-decl (2018-08-15) 6 commits
  (merged to 'next' on 2018-08-17 at 04fc9c11bb)
 + Remove forward declaration of an enum
 + compat/precompose_utf8.h: use more common include guard style
 + urlmatch.h: fix include guard
 + Move definition of enum branch_track from cache.h to branch.h
 + alloc: make allocate_alloc_state and clear_alloc_state more consistent
 + Add missing includes and forward declarations

 Code hygiene improvement for the header files.


* en/t7406-fixes (2018-08-08) 5 commits
  (merged to 'next' on 2018-08-15 at c6a740d828)
 + t7406: avoid using test_must_fail for commands other than git
 + t7406: prefer test_* helper functions to test -[feds]
 + t7406: avoid having git commands upstream of a pipe
 + t7406: simplify by using diff --name-only instead of diff --raw
 + t7406: fix call that was failing for the wrong reason

 Test fixes.


* en/update-index-doc (2018-08-08) 1 commit
  (merged to 'next' on 2018-08-15 at 3ee0ae14dc)
 + git-update-index.txt: reword possibly confusing example

 Doc update.


* es/chain-lint-more (2018-08-13) 6 commits
  (merged to 'next' on 2018-08-15 at bb5150ee96)
 + chainlint: add test of pathological case which triggered false positive
 + chainlint: recognize multi-line quoted strings more robustly
 + chainlint: let here-doc and multi-line string commence on same line
 + chainlint: recognize multi-line $(...) when command cuddled with "$("
 + chainlint: match 'quoted' here-doc tags
 + chainlint: match arbitrary here-docs tags rather than hard-coded names

 Improve built-in facility to catch broken &&-chain in the tests.


* hn/highlight-sideband-keywords (2018-08-18) 2 commits
  (merged to 'next' on 2018-08-20 at ec6f953f8c)
 + sideband: do not read beyond the end of input
  (merged to 'next' on 2018-08-15 at f8945f3be5)
 + sideband: highlight keywords in remote sideband output

 The sideband code learned to optionally paint selected keywords at
 the beginning of incoming lines on the receiving end.


* jc/gpg-status (2018-08-09) 1 commit
  (merged to 'next' on 2018-08-15 at 824781761a)
 + gpg-interface: propagate exit status from gpg back to the callers

 "git verify-tag" and "git verify-commit" have been taught to use
 the exit status of underlying "gpg --verify" to signal bad or
 untrusted signature they found.


* jc/update-index-doc (2018-08-08) 1 commit
  (merged to 'next' on 2018-08-15 at 055994ccca)
 + update-index: there no longer is `apply --index-info`

 Doc update.


* jh/partial-clone-doc (2018-08-15) 1 commit
  (merged to 'next' on 2018-08-15 at cf09e8be6a)
 + partial-clone: render design doc using asciidoc

 Doc updates.


* jk/for-each-object-iteration (2018-08-14) 11 commits
  (merged to 'next' on 2018-08-15 at e2558810ff)
 + for_each_*_object: move declarations to object-store.h
 + cat-file: use a single strbuf for all output
 + cat-file: split batch "buf" into two variables
 + cat-file: use oidset check-and-insert
 + cat-file: support "unordered" output for --batch-all-objects
 + cat-file: rename batch_{loose,packed}_object callbacks
 + t1006: test cat-file --batch-all-objects with duplicates
 + for_each_packed_object: support iterating in pack-order
 + for_each_*_object: give more comprehensive docstrings
 + for_each_*_object: take flag arguments as enum
 + for_each_*_object: store flag definitions in a single location

 The API to iterate over all objects learned to optionally list
 objects in the order they appear in packfiles, which helps locality
 of access if the caller accesses these objects while as objects are
 enumerated.


* js/chain-lint-attrfix (2018-08-15) 1 commit
  (merged to 'next' on 2018-08-15 at e9ad19a848)
 + chainlint: fix for core.autocrlf=true

 Test fix.


* js/mingw-o-append (2018-08-13) 1 commit
  (merged to 'next' on 2018-08-15 at 284527a0fb)
 + mingw: enable atomic O_APPEND

 Among the three codepaths we use O_APPEND to open a file for
 appending, one used for writing GIT_TRACE output requires O_APPEND
 implementation that behaves sensibly when multiple processes are
 writing to the same file.  POSIX emulation used in the Windows port
 has been updated to improve in this area.


* js/range-diff (2018-08-13) 21 commits
  (merged to 'next' on 2018-08-15 at 8d56067806)
 + range-diff: use dim/bold cues to improve dual color mode
 + range-diff: make --dual-color the default mode
 + range-diff: left-pad patch numbers
 + completion: support `git range-diff`
 + range-diff: populate the man page
 + range-diff --dual-color: skip white-space warnings
 + range-diff: offer to dual-color the diffs
 + diff: add an internal option to dual-color diffs of diffs
 + color: add the meta color GIT_COLOR_REVERSE
 + range-diff: use color for the commit pairs
 + range-diff: add tests
 + range-diff: do not show "function names" in hunk headers
 + range-diff: adjust the output of the commit pairs
 + range-diff: suppress the diff headers
 + range-diff: indent the diffs just like tbdiff
 + range-diff: right-trim commit messages
 + range-diff: also show the diff between patches
 + range-diff: improve the order of the shown commits
 + range-diff: first rudimentary implementation
 + Introduce `range-diff` to compare iterations of a topic branch
 + linear-assignment: a function to solve least-cost assignment problems
 (this branch is used by es/format-patch-rangediff and sb/range-diff-colors.)

 "git tbdiff" that lets us compare individual patches in two
 iterations of a topic has been rewritten and made into a built-in
 command.


* js/rebase-merges-exec-fix (2018-08-09) 2 commits
  (merged to 'next' on 2018-08-15 at 9de975d92d)
 + rebase --exec: make it work with --rebase-merges
 + t3430: demonstrate what -r, --autosquash & --exec should do

 The "--exec" option to "git rebase --rebase-merges" placed the exec
 commands at wrong places, which has been corrected.


* js/typofixes (2018-08-08) 2 commits
  (merged to 'next' on 2018-08-15 at ce3932254a)
 + remote-curl: remove spurious period
 + git-compat-util.h: fix typo

 Comment update.


* jt/commit-graph-per-object-store (2018-08-13) 1 commit
  (merged to 'next' on 2018-08-15 at 5d6db738d8)
 + t5318: avoid unnecessary command substitutions

 Test update.


* jt/fetch-negotiator-skipping (2018-08-10) 1 commit
  (merged to 'next' on 2018-08-15 at 3cf8fa32f5)
 + t5552: suppress upload-pack trace output

 Test fix.


* jt/repack-promisor-packs (2018-08-09) 2 commits
  (merged to 'next' on 2018-08-17 at 6869b53a69)
 + repack: repack promisor objects if -a or -A is set
 + repack: refactor setup of pack-objects cmd

 After a partial clone, repeated fetches from promisor remote would
 have accumulated many packfiles marked with .promisor bit without
 getting them coalesced into fewer packfiles, hurting performance.
 "git repack" now learned to repack them.


* nd/cherry-pick-quit-fix (2018-08-16) 1 commit
  (merged to 'next' on 2018-08-17 at b270179855)
 + cherry-pick: fix --quit not deleting CHERRY_PICK_HEAD

 "git cherry-pick --quit" failed to remove CHERRY_PICK_HEAD even
 though we won't be in a cherry-pick session after it returns, which
 has been corrected.


* nd/no-the-index (2018-08-13) 24 commits
  (merged to 'next' on 2018-08-15 at 41e53dc53b)
 + blame.c: remove implicit dependency on the_index
 + apply.c: remove implicit dependency on the_index
 + apply.c: make init_apply_state() take a struct repository
 + apply.c: pass struct apply_state to more functions
 + resolve-undo.c: use the right index instead of the_index
 + archive-*.c: use the right repository
 + archive.c: avoid access to the_index
 + grep: use the right index instead of the_index
 + attr: remove index from git_attr_set_direction()
 + entry.c: use the right index instead of the_index
 + submodule.c: use the right index instead of the_index
 + pathspec.c: use the right index instead of the_index
 + unpack-trees: avoid the_index in verify_absent()
 + unpack-trees: convert clear_ce_flags* to avoid the_index
 + unpack-trees: don't shadow global var the_index
 + unpack-trees: add a note about path invalidation
 + unpack-trees: remove 'extern' on function declaration
 + ls-files: correct index argument to get_convert_attr_ascii()
 + preload-index.c: use the right index instead of the_index
 + dir.c: remove an implicit dependency on the_index in pathspec code
 + convert.c: remove an implicit dependency on the_index
 + attr: remove an implicit dependency on the_index
 + cache-tree: wrap the_index based wrappers with #ifdef
 + diff.c: move read_index() code back to the caller

 The more library-ish parts of the codebase learned to work on the
 in-core index-state instance that is passed in by their callers,
 instead of always working on the singleton "the_index" instance.


* ng/mergetool-lose-final-prompt (2018-08-13) 1 commit
  (merged to 'next' on 2018-08-15 at f8f7ac365b)
 + mergetool: don't suggest to continue after last file

 "git mergetool" stopped and gave an extra prompt to continue after
 the last path has been handled, which did not make much sense.


* pw/rebase-i-merge-segv-fix (2018-08-16) 2 commits
  (merged to 'next' on 2018-08-17 at c8823e4511)
 + rebase -i: fix SIGSEGV when 'merge <branch>' fails
 + t3430: add conflicting commit

 "git rebase -i", when a 'merge <branch>' insn in its todo list
 fails, segfaulted, which has been (minimally) corrected.


* pw/rebase-i-squash-number-fix (2018-08-15) 1 commit
  (merged to 'next' on 2018-08-17 at ac54dfa51a)
 + rebase -i: fix numbering in squash message

 When "git rebase -i" is told to squash two or more commits into
 one, it labeled the log message for each commit with its number.
 It correctly called the first one "1st commit", but the next one
 was "commit #1", which was off-by-one.  This has been corrected.


* sb/config-write-fix (2018-08-08) 3 commits
  (merged to 'next' on 2018-08-17 at 7d9c7ce81f)
 + git-config: document accidental multi-line setting in deprecated syntax
 + config: fix case sensitive subsection names on writing
 + t1300: document current behavior of setting options

 Recent update to "git config" broke updating variable in a
 subsection, which has been corrected.


* sb/pull-rebase-submodule (2018-08-14) 1 commit
  (merged to 'next' on 2018-08-15 at 07c7b55cc9)
 + git-submodule.sh: accept verbose flag in cmd_update to be non-quiet

 "git pull --rebase -v" in a repository with a submodule barfed as
 an intermediate process did not understand what "-v(erbose)" flag
 meant, which has been fixed.


* sb/submodule-cleanup (2018-08-16) 2 commits
  (merged to 'next' on 2018-08-17 at ca9d8aaef4)
 + builtin/submodule--helper: remove stray new line
 + t7410: update to new style

 A few preliminary minor clean-ups in the area around submodules.


* sg/t5310-empty-input-fix (2018-08-14) 1 commit
  (merged to 'next' on 2018-08-15 at c3c03973a0)
 + t5310-pack-bitmaps: fix bogus 'pack-objects to file can use bitmap' test

 Test fix.


* sk/instaweb-rh-update (2018-08-08) 2 commits
  (merged to 'next' on 2018-08-15 at ce5f1115e9)
 + git-instaweb: fix apache2 config with apache >= 2.4
 + git-instaweb: support Fedora/Red Hat apache module path

 "git instaweb" has been adjusted to run better with newer Apache on
 RedHat based distros.


* wc/make-funnynames-shared-lazy-prereq (2018-08-06) 1 commit
  (merged to 'next' on 2018-08-17 at b932a0894b)
 + t: factor out FUNNYNAMES as shared lazy prereq

 A test prerequisite defined by various test scripts with slightly
 different semantics has been consolidated into a single copy and
 made into a lazily defined one.

--------------------------------------------------
[Stalled]

* sl/commit-dry-run-with-short-output-fix (2018-07-30) 4 commits
 . commit: fix exit code when doing a dry run
 . wt-status: teach wt_status_collect about merges in progress
 . wt-status: rename commitable to committable
 . t7501: add coverage for flags which imply dry runs

 "git commit --dry-run" gave a correct exit status even during a
 conflict resolution toward a merge, but it did not with the
 "--short" option, which has been corrected.

 Seems to break 7512, 3404 and 7060 in 'pu'.


* ma/wrapped-info (2018-05-28) 2 commits
 - usage: prefix all lines in `vreportf()`, not just the first
 - usage: extract `prefix_suffix_lines()` from `advise()`

 An attempt to help making multi-line messages fed to warning(),
 error(), and friends more easily translatable.

 Will discard and wait for a cleaned-up rewrite.
 cf. <20180529213957.GF7964@sigill.intra.peff.net>


* hn/bisect-first-parent (2018-04-21) 1 commit
 - bisect: create 'bisect_flags' parameter in find_bisection()

 Preliminary code update to allow passing more flags down the
 bisection codepath in the future.

 We do not add random code that does not have real users to our
 codebase, so let's have it wait until such a real code materializes
 before too long.


* av/fsmonitor-updates (2018-01-04) 6 commits
 - fsmonitor: use fsmonitor data in `git diff`
 - fsmonitor: remove debugging lines from t/t7519-status-fsmonitor.sh
 - fsmonitor: make output of test-dump-fsmonitor more concise
 - fsmonitor: update helper tool, now that flags are filled later
 - fsmonitor: stop inline'ing mark_fsmonitor_valid / _invalid
 - dir.c: update comments to match argument name

 Code clean-up on fsmonitor integration, plus optional utilization
 of the fsmonitor data in diff-files.

 Waiting for an update.
 cf. <alpine.DEB.2.21.1.1801042335130.32@MININT-6BKU6QN.europe.corp.microsoft.com>


* pb/bisect-helper-2 (2018-07-23) 8 commits
 - t6030: make various test to pass GETTEXT_POISON tests
 - bisect--helper: `bisect_start` shell function partially in C
 - bisect--helper: `get_terms` & `bisect_terms` shell function in C
 - bisect--helper: `bisect_next_check` shell function in C
 - bisect--helper: `check_and_set_terms` shell function in C
 - wrapper: move is_empty_file() and rename it as is_empty_or_missing_file()
 - bisect--helper: `bisect_write` shell function in C
 - bisect--helper: `bisect_reset` shell function in C

 Expecting a reroll.
 cf. <0102015f5e5ee171-f30f4868-886f-47a1-a4e4-b4936afc545d-000000@eu-west-1.amazonses.com>

 I just rebased the topic to a newer base as it did not build
 standalone with the base I originally queued the topic on, but
 otherwise there is no update to address any of the review comments
 in the thread above---we are still waiting for a reroll.


* jk/drop-ancient-curl (2017-08-09) 5 commits
 - http: #error on too-old curl
 - curl: remove ifdef'd code never used with curl >=7.19.4
 - http: drop support for curl < 7.19.4
 - http: drop support for curl < 7.16.0
 - http: drop support for curl < 7.11.1

 Some code in http.c that has bitrot is being removed.

 Expecting a reroll.


* mk/use-size-t-in-zlib (2017-08-10) 1 commit
 . zlib.c: use size_t for size

 The wrapper to call into zlib followed our long tradition to use
 "unsigned long" for sizes of regions in memory, which have been
 updated to use "size_t".

 Needs resurrecting by making sure the fix is good and still applies
 (or adjusted to today's codebase).

--------------------------------------------------
[Cooking]

* ep/worktree-quiet-option (2018-08-17) 1 commit
 - worktree: add --quiet option

 "git worktree" command learned "--quiet" option to make it less
 verbose.

 Will merge to 'next'.


* nd/config-core-checkstat-doc (2018-08-17) 1 commit
 - config.txt: clarify core.checkStat

 The meaning of the possible values the "core.checkStat"
 configuration variable can take were not adequately documented,
 which has been fixed.

 Will merge to 'next'.


* sm/branch-sort-config (2018-08-16) 1 commit
 - branch: support configuring --sort via .gitconfig

 "git branch --list" learned to take the default sort order from the
 'branch.sort' configuration variable, just like "git tag --list"
 pays attention to 'tag.sort'.

 Will merge to 'next'.


* ab/unconditional-free-and-null (2018-08-17) 1 commit
 - refactor various if (x) FREE_AND_NULL(x) to just FREE_AND_NULL(x)

 Code clean-up.

 Will merge to 'next'.


* bp/checkout-new-branch-optim (2018-08-16) 1 commit
 - checkout: optimize "git checkout -b <new_branch>"

 "git checkout -b newbranch [HEAD]" should not have to do as much as
 checking out a commit different from HEAD.  An attempt is made to
 optimize this special case.

 So... what is the status of this thing?  Is the other "optimize
 unpack-trees" effort turning out to be a safer and less hacky way
 to achieve similar gain and this no longer is needed?


* ao/submodule-wo-gitmodules-checked-out (2018-08-14) 7 commits
 - submodule: support reading .gitmodules even when it's not checked out
 - t7506: clean up .gitmodules properly before setting up new scenario
 - submodule: use the 'submodule--helper config' command
 - submodule--helper: add a new 'config' subcommand
 - t7411: be nicer to future tests and really clean things up
 - submodule: factor out a config_set_in_gitmodules_file_gently function
 - submodule: add a print_config_from_gitmodules() helper

 The submodule support has been updated to read from the blob at
 HEAD:.gitmodules when the .gitmodules file is missing from the
 working tree.

 I find the design a bit iffy in that our usual "missing in the
 working tree?  let's use the latest blob" fallback is to take it
 from the index, not from the HEAD.


* bw/submodule-name-to-dir (2018-08-10) 2 commits
 - submodule: munge paths to submodule git directories
 - submodule: create helper to build paths to submodule gitdirs

 In modern repository layout, the real body of a cloned submodule
 repository is held in .git/modules/ of the superproject, indexed by
 the submodule name.  URLencode the submodule name before computing
 the name of the directory to make sure they form a flat namespace.

 Will merge to 'next'.


* cc/delta-islands (2018-08-16) 7 commits
 - pack-objects: move 'layer' into 'struct packing_data'
 - pack-objects: move tree_depth into 'struct packing_data'
 - t5320: tests for delta islands
 - repack: add delta-islands support
 - pack-objects: add delta-islands support
 - pack-objects: refactor code into compute_layer_order()
 - Add delta-islands.{c,h}

 Lift code from GitHub to restrict delta computation so that an
 object that exists in one fork is not made into a delta against
 another object that does not appear in the same forked repository.

 What's the doneness of this topic?


* md/filter-trees (2018-08-16) 6 commits
 - list-objects-filter: implement filter tree:0
 - revision: mark non-user-given objects instead
 - rev-list: handle missing tree objects properly
 - list-objects: always parse trees gently
 - list-objects: refactor to process_tree_contents
 - list-objects: store common func args in struct

 The "rev-list --filter" feature learned to exclude all trees via
 "tree:0" filter.


* ng/status-i-short-for-ignored (2018-08-09) 1 commit
 - status: -i shorthand for --ignored command line option


* pk/rebase-in-c-2-basic (2018-08-10) 11 commits
 - builtin rebase: support `git rebase <upstream> <switch-to>`
 - builtin rebase: only store fully-qualified refs in `options.head_name`
 - builtin rebase: start a new rebase only if none is in progress
 - builtin rebase: support --force-rebase
 - builtin rebase: try to fast forward when possible
 - builtin rebase: require a clean worktree
 - builtin rebase: support the `verbose` and `diffstat` options
 - builtin rebase: support --quiet
 - builtin rebase: handle the pre-rebase hook (and add --no-verify)
 - builtin rebase: support `git rebase --onto A...B`
 - builtin rebase: support --onto
 (this branch is used by pk/rebase-in-c-3-acts, pk/rebase-in-c-4-opts, pk/rebase-in-c-5-test and pk/rebase-in-c-6-final; uses pk/rebase-in-c.)


* pk/rebase-in-c-3-acts (2018-08-10) 7 commits
 - builtin rebase: stop if `git am` is in progress
 - builtin rebase: actions require a rebase in progress
 - builtin rebase: support --edit-todo and --show-current-patch
 - builtin rebase: support --quit
 - builtin rebase: support --abort
 - builtin rebase: support --skip
 - builtin rebase: support --continue
 (this branch is used by pk/rebase-in-c-4-opts, pk/rebase-in-c-5-test and pk/rebase-in-c-6-final; uses pk/rebase-in-c and pk/rebase-in-c-2-basic.)


* pk/rebase-in-c-4-opts (2018-08-10) 18 commits
 - builtin rebase: support --root
 - builtin rebase: add support for custom merge strategies
 - builtin rebase: support `fork-point` option
 - merge-base --fork-point: extract libified function
 - builtin rebase: support --rebase-merges[=[no-]rebase-cousins]
 - builtin rebase: support `--allow-empty-message` option
 - builtin rebase: support `--exec`
 - builtin rebase: support `--autostash` option
 - builtin rebase: support `-C` and `--whitespace=<type>`
 - builtin rebase: support `--gpg-sign` option
 - builtin rebase: support `--autosquash`
 - builtin rebase: support `keep-empty` option
 - builtin rebase: support `ignore-date` option
 - builtin rebase: support `ignore-whitespace` option
 - builtin rebase: support --committer-date-is-author-date
 - builtin rebase: support --rerere-autoupdate
 - builtin rebase: support --signoff
 - builtin rebase: allow selecting the rebase "backend"
 (this branch is used by pk/rebase-in-c-5-test and pk/rebase-in-c-6-final; uses pk/rebase-in-c, pk/rebase-in-c-2-basic and pk/rebase-in-c-3-acts.)


* pk/rebase-in-c-5-test (2018-08-10) 6 commits
 - builtin rebase: error out on incompatible option/mode combinations
 - builtin rebase: use no-op editor when interactive is "implied"
 - builtin rebase: show progress when connected to a terminal
 - builtin rebase: fast-forward to onto if it is a proper descendant
 - builtin rebase: optionally pass custom reflogs to reset_head()
 - builtin rebase: optionally auto-detect the upstream
 (this branch is used by pk/rebase-in-c-6-final; uses pk/rebase-in-c, pk/rebase-in-c-2-basic, pk/rebase-in-c-3-acts and pk/rebase-in-c-4-opts.)


* pk/rebase-in-c-6-final (2018-08-10) 1 commit
 - rebase: default to using the builtin rebase
 (this branch uses pk/rebase-in-c, pk/rebase-in-c-2-basic, pk/rebase-in-c-3-acts, pk/rebase-in-c-4-opts and pk/rebase-in-c-5-test.)

 With "rebase -i" machinery being rewritten to C, with a different
 interface between "rebase" proper and its backends, this and the
 other topics need a bit more work to play with each other better.


* ps/stash-in-c (2018-08-08) 26 commits
 - stash: replace all "git apply" child processes with API calls
 - stash: replace all `write-tree` child processes with API calls
 - stash: optimize `get_untracked_files()` and `check_changes()`
 - stash: convert `stash--helper.c` into `stash.c`
 - stash: convert save to builtin
 - stash: replace spawning `git ls-files` child process
 - stash: add tests for `git stash push -q`
 - stash: make push to be quiet
 - stash: convert push to builtin
 - stash: avoid spawning a "diff-index" process
 - stash: replace spawning a "read-tree" process
 - stash: convert create to builtin
 - stash: convert store to builtin
 - stash: update `git stash show` documentation
 - stash: refactor `show_stash()` to use the diff API
 - stash: change `git stash show` usage text and documentation
 - stash: convert show to builtin
 - stash: implement the "list" command in the builtin
 - stash: convert pop to builtin
 - stash: convert branch to builtin
 - stash: convert drop and clear to builtin
 - stash: convert apply to builtin
 - stash: renamed test cases to be more descriptive
 - stash: update test cases conform to coding guidelines
 - stash: improve option parsing test coverage
 - sha1-name.c: added 'get_oidf', which acts like 'get_oid'


* nd/clone-case-smashing-warning (2018-08-17) 1 commit
 - clone: report duplicate entries on case-insensitive filesystems

 Running "git clone" against a project that contain two files with
 pathnames that differ only in cases on a case insensitive
 filesystem would result in one of the files lost because the
 underlying filesystem is incapable of holding both at the same
 time.  An attempt is made to detect such a case and warn.

 Will merge to 'next'.


* nd/unpack-trees-with-cache-tree (2018-08-18) 7 commits
 - cache-tree: verify valid cache-tree in the test suite
 - unpack-trees: add missing cache invalidation
 - unpack-trees: reuse (still valid) cache-tree from src_index
 - unpack-trees: reduce malloc in cache-tree walk
 - unpack-trees: optimize walking same trees with cache-tree
 - unpack-trees: add performance tracing
 - trace.h: support nested performance tracing

 The unpack_trees() API used in checking out a branch and merging
 walks one or more trees along with the index.  When the cache-tree
 in the index tells us that we are walking a tree whose flattened
 contents is known (i.e. matches a span in the index), as linearly
 scanning a span in the index is much more efficient than having to
 open tree objects recursively and listing their entries, the walk
 can be optimized, which is done in this topic.

 Will merge to and cook in 'next'.


* sb/range-diff-colors (2018-08-14) 8 commits
 - diff.c: rewrite emit_line_0 more understandably
 - diff.c: omit check for line prefix in emit_line_0
 - diff: use emit_line_0 once per line
 - diff.c: add set_sign to emit_line_0
 - diff.c: reorder arguments for emit_line_ws_markup
 - diff.c: simplify caller of emit_line_0
 - t3206: add color test for range-diff --dual-color
 - test_decode_color: understand FAINT and ITALIC

 The color output support for recently introduced "range-diff"
 command got tweaked a bit.


* sg/t1404-update-ref-test-timeout (2018-08-01) 1 commit
 - t1404: increase core.packedRefsTimeout to avoid occasional test failure

 An attempt to unflake a test a bit.


* pw/rebase-i-author-script-fix (2018-08-07) 2 commits
 - sequencer: fix quoting in write_author_script
 - sequencer: handle errors from read_author_ident()

 Recent "git rebase -i" update started to write bogusly formatted
 author-script, with a matching broken reading code.  These are
 being fixed.

 Undecided.
 Is it the list consensus to favor this "with extra code, read the
 script written by bad writer" approach?


* pw/add-p-select (2018-07-26) 4 commits
 - add -p: optimize line selection for short hunks
 - add -p: allow line selection to be inverted
 - add -p: select modified lines correctly
 - add -p: select individual hunk lines

 "git add -p" interactive interface learned to let users choose
 individual added/removed lines to be used in the operation, instead
 of accepting or rejecting a whole hunk.

 Will hold.
 cf. <d622a95b-7302-43d4-4ec9-b2cf3388c653@talktalk.net>
 I found the feature to be hard to explain, and may result in more
 end-user complaints, but let's see.


* ds/commit-graph-with-grafts (2018-07-19) 8 commits
  (merged to 'next' on 2018-08-02 at 0ee624e329)
 + commit-graph: close_commit_graph before shallow walk
 + commit-graph: not compatible with uninitialized repo
 + commit-graph: not compatible with grafts
 + commit-graph: not compatible with replace objects
 + test-repository: properly init repo
 + commit-graph: update design document
 + refs.c: upgrade for_each_replace_ref to be a each_repo_ref_fn callback
 + refs.c: migrate internal ref iteration to pass thru repository argument

 The recently introduced commit-graph auxiliary data is incompatible
 with mechanisms such as replace & grafts that "breaks" immutable
 nature of the object reference relationship.  Disable optimizations
 based on its use (and updating existing commit-graph) when these
 incompatible features are in use in the repository.

 Eject and replace with another reroll when it comes.
 cf. <85c6eb4c-a083-4fb7-4860-b01a8ce9fa4f@gmail.com>


* ds/reachable (2018-07-20) 18 commits
 - commit-reach: use can_all_from_reach
 - commit-reach: make can_all_from_reach... linear
 - commit-reach: replace ref_newer logic
 - test-reach: test commit_contains
 - test-reach: test can_all_from_reach_with_flags
 - test-reach: test reduce_heads
 - test-reach: test get_merge_bases_many
 - test-reach: test is_descendant_of
 - test-reach: test in_merge_bases
 - test-reach: create new test tool for ref_newer
 - commit-reach: move can_all_from_reach_with_flags
 - upload-pack: generalize commit date cutoff
 - upload-pack: refactor ok_to_give_up()
 - upload-pack: make reachable() more generic
 - commit-reach: move commit_contains from ref-filter
 - commit-reach: move ref_newer from remote.c
 - commit.h: remove method declarations
 - commit-reach: move walk methods from commit.c

 The code for computing history reachability has been shuffled,
 obtained a bunch of new tests to cover them, and then being
 improved.

 Will merge to and cook in 'next'.


* es/format-patch-interdiff (2018-07-23) 6 commits
 - format-patch: allow --interdiff to apply to a lone-patch
 - log-tree: show_log: make commentary block delimiting reusable
 - interdiff: teach show_interdiff() to indent interdiff
 - format-patch: teach --interdiff to respect -v/--reroll-count
 - format-patch: add --interdiff option to embed diff in cover letter
 - format-patch: allow additional generated content in make_cover_letter()
 (this branch is used by es/format-patch-rangediff.)

 "git format-patch" learned a new "--interdiff" option to explain
 the difference between this version and the previous atttempt in
 the cover letter (or after the tree-dashes as a comment).

 Stuck in review?
 cf. <CAPig+cSuYUYSPTuKx08wcmQM-G12_-W2T4BS07fA=6grM1b8Gw@mail.gmail.com>


* es/format-patch-rangediff (2018-08-14) 10 commits
 - format-patch: allow --range-diff to apply to a lone-patch
 - format-patch: add --creation-factor tweak for --range-diff
 - format-patch: teach --range-diff to respect -v/--reroll-count
 - format-patch: extend --range-diff to accept revision range
 - format-patch: add --range-diff option to embed diff in cover letter
 - range-diff: relieve callers of low-level configuration burden
 - range-diff: publish default creation factor
 - range-diff: respect diff_option.file rather than assuming 'stdout'
 - Merge branch 'es/format-patch-interdiff' into es/format-patch-rangediff
 - Merge branch 'js/range-diff' into es/format-patch-rangediff
 (this branch uses es/format-patch-interdiff.)

 "git format-patch" learned a new "--range-diff" option to explain
 the difference between this version and the previous atttempt in
 the cover letter (or after the tree-dashes as a comment).

 Need to wait for the prereq topics to solidify a bit more.


* nd/pack-deltify-regression-fix (2018-07-23) 1 commit
  (merged to 'next' on 2018-08-02 at f3b2bf0fef)
 + pack-objects: fix performance issues on packing large deltas

 In a recent update in 2.18 era, "git pack-objects" started
 producing a larger than necessary packfiles by missing
 opportunities to use large deltas.

 Will merge to 'master'.


* jh/structured-logging (2018-07-25) 25 commits
 - structured-logging: add config data facility
 - structured-logging: t0420 tests for interacitve child_summary
 - structured-logging: t0420 tests for child process detail events
 - structured-logging: add child process classification
 - structured-logging: add detail-events for child processes
 - structured-logging: add structured logging to remote-curl
 - structured-logging: t0420 tests for aux-data
 - structured-logging: add aux-data for size of sparse-checkout file
 - structured-logging: add aux-data for index size
 - structured-logging: add aux-data facility
 - structured-logging: t0420 tests for timers
 - structured-logging: add timer around preload_index
 - structured-logging: add timer around wt-status functions
 - structured-logging: add timer around do_write_index
 - structured-logging: add timer around do_read_index
 - structured-logging: add timer facility
 - structured-logging: add detail-event for lazy_init_name_hash
 - structured-logging: add detail-event facility
 - structured-logging: t0420 basic tests
 - structured-logging: set sub_command field for checkout command
 - structured-logging: set sub_command field for branch command
 - structured-logging: add session-id to log events
 - structured-logging: add structured logging framework
 - structured-logging: add STRUCTURED_LOGGING=1 to Makefile
 - structured-logging: design document

 Will merge to 'next'.


* jn/gc-auto (2018-07-17) 3 commits
 - gc: do not return error for prior errors in daemonized mode
 - gc: exit with status 128 on failure
 - gc: improve handling of errors reading gc.log

 "gc --auto" ended up calling exit(-1) upon error, which has been
 corrected to use exit(1).  Also the error reporting behaviour when
 daemonized has been updated to exit with zero status when stopping
 due to a previously discovered error (which implies there is no
 point running gc to improve the situation); we used to exit with
 failure in such a case.

 Stuck in review?
 cf. <20180717201348.GD26218@sigill.intra.peff.net>


* sb/submodule-update-in-c (2018-08-14) 7 commits
  (merged to 'next' on 2018-08-17 at 23c81e5ff7)
 + submodule--helper: introduce new update-module-mode helper
 + submodule--helper: replace connect-gitdir-workingtree by ensure-core-worktree
 + builtin/submodule--helper: factor out method to update a single submodule
 + builtin/submodule--helper: store update_clone information in a struct
 + builtin/submodule--helper: factor out submodule updating
 + git-submodule.sh: rename unused variables
 + git-submodule.sh: align error reporting for update mode to use path

 "git submodule update" is getting rewritten piece-by-piece into C.

 Will merge to 'master'.


* tg/rerere (2018-08-06) 11 commits
  (merged to 'next' on 2018-08-17 at 919a958cdc)
 + rerere: recalculate conflict ID when unresolved conflict is committed
 + rerere: teach rerere to handle nested conflicts
 + rerere: return strbuf from handle path
 + rerere: factor out handle_conflict function
 + rerere: only return whether a path has conflicts or not
 + rerere: fix crash with files rerere can't handle
 + rerere: add documentation for conflict normalization
 + rerere: mark strings for translation
 + rerere: wrap paths in output in sq
 + rerere: lowercase error messages
 + rerere: unify error messages when read_cache fails

 Fixes to "git rerere" corner cases, especially when conflict
 markers cannot be parsed in the file.

 Will merge to 'master'.


* ag/rebase-i-in-c (2018-08-10) 20 commits
 - rebase -i: move rebase--helper modes to rebase--interactive
 - rebase -i: remove git-rebase--interactive.sh
 - rebase--interactive2: rewrite the submodes of interactive rebase in C
 - rebase -i: implement the main part of interactive rebase as a builtin
 - rebase -i: rewrite init_basic_state() in C
 - rebase -i: rewrite write_basic_state() in C
 - rebase -i: rewrite the rest of init_revisions_and_shortrevisions() in C
 - rebase -i: implement the logic to initialize $revisions in C
 - rebase -i: remove unused modes and functions
 - rebase -i: rewrite complete_action() in C
 - t3404: todo list with commented-out commands only aborts
 - sequencer: change the way skip_unnecessary_picks() returns its result
 - sequencer: refactor append_todo_help() to write its message to a buffer
 - rebase -i: rewrite checkout_onto() in C
 - rebase -i: rewrite setup_reflog_action() in C
 - sequencer: add a new function to silence a command, except if it fails
 - rebase -i: rewrite the edit-todo functionality in C
 - editor: add a function to launch the sequence editor
 - rebase -i: rewrite append_todo_help() in C
 - sequencer: make three functions and an enum from sequencer.c public

 Rewrite of the remaining "rebase -i" machinery in C.

 With "rebase -i" machinery being rewritten to C, with a different
 interface between "rebase" proper and its backends, this and the
 other topics need a bit more work to play with each other better.


* lt/date-human (2018-07-09) 1 commit
 - Add 'human' date format

 A new date format "--date=human" that morphs its output depending
 on how far the time is from the current time has been introduced.
 "--date=auto" can be used to use this new format when the output is
 goint to the pager or to the terminal and otherwise the default
 format.


* pk/rebase-in-c (2018-08-06) 3 commits
 - builtin/rebase: support running "git rebase <upstream>"
 - rebase: refactor common shell functions into their own file
 - rebase: start implementing it as a builtin
 (this branch is used by pk/rebase-in-c-2-basic, pk/rebase-in-c-3-acts, pk/rebase-in-c-4-opts, pk/rebase-in-c-5-test and pk/rebase-in-c-6-final.)

 Rewrite of the "rebase" machinery in C.


* jk/branch-l-1-repurpose (2018-06-22) 1 commit
  (merged to 'next' on 2018-08-08 at d2a08dd08e)
 + branch: make "-l" a synonym for "--list"

 Updated plan to repurpose the "-l" option to "git branch".

 Will cook in 'next'.


* ds/multi-pack-index (2018-07-20) 23 commits
  (merged to 'next' on 2018-08-08 at 1a56c52967)
 + midx: clear midx on repack
 + packfile: skip loading index if in multi-pack-index
 + midx: prevent duplicate packfile loads
 + midx: use midx in approximate_object_count
 + midx: use existing midx when writing new one
 + midx: use midx in abbreviation calculations
 + midx: read objects from multi-pack-index
 + config: create core.multiPackIndex setting
 + midx: write object offsets
 + midx: write object id fanout chunk
 + midx: write object ids in a chunk
 + midx: sort and deduplicate objects from packfiles
 + midx: read pack names into array
 + multi-pack-index: write pack names in chunk
 + multi-pack-index: read packfile list
 + packfile: generalize pack directory list
 + t5319: expand test data
 + multi-pack-index: load into memory
 + midx: write header information to lockfile
 + multi-pack-index: add 'write' verb
 + multi-pack-index: add builtin
 + multi-pack-index: add format details
 + multi-pack-index: add design document

 When there are too many packfiles in a repository (which is not
 recommended), looking up an object in these would require
 consulting many pack .idx files; a new mechanism to have a single
 file that consolidates all of these .idx files is introduced.

 Will cook in 'next'.

--------------------------------------------------
[Discarded]

* am/sequencer-author-script-fix (2018-07-18) 1 commit
 . sequencer.c: terminate the last line of author-script properly

 The author-script that records the author information created by
 the sequencer machinery lacked the closing single quote on the last
 entry.

 Superseded by another topic.


* jc/push-cas-opt-comment (2018-08-01) 1 commit
 . push: comment on a funny unbalanced option help

 Code clarification.

 Superseded by another topic.


* cc/remote-odb (2018-08-02) 9 commits
 . Documentation/config: add odb.<name>.promisorRemote
 . t0410: test fetching from many promisor remotes
 . Use odb.origin.partialclonefilter instead of core.partialclonefilter
 . Use remote_odb_get_direct() and has_remote_odb()
 . remote-odb: add remote_odb_reinit()
 . remote-odb: implement remote_odb_get_many_direct()
 . remote-odb: implement remote_odb_get_direct()
 . Add initial remote odb support
 . fetch-object: make functions return an error code

 Implement lazy fetches of missing objects to complement the
 experimental partial clone feature.

 Ejected; seems to break existing repositories that use partialclone
 repository extension.

 I haven't seen much interest in this topic on list.  What's the
 doneness of this thing?

 I do not particularly mind adding code to support a niche feature
 as long as it is cleanly made and it is clear that the feature
 won't negatively affect those who do not use it, so a review from
 that point of view may also be appropriate.

^ permalink raw reply	[relevance 1%]

* [ANNOUNCE] Git v2.19.0-rc0
@ 2018-08-20 22:13  1% Junio C Hamano
  0 siblings, 0 replies; 200+ results
From: Junio C Hamano @ 2018-08-20 22:13 UTC (permalink / raw)
  To: git; +Cc: Linux Kernel, git-packagers

An early preview release Git v2.19.0-rc0 is now available for
testing at the usual places.  It is comprised of 707 non-merge
commits since v2.18.0, contributed by 60 people, 14 of which are
new faces.

The tarballs are found at:

    https://www.kernel.org/pub/software/scm/git/testing/

The following public repositories all have a copy of the
'v2.19.0-rc0' tag and the 'master' branch that the tag points at:

  url = https://kernel.googlesource.com/pub/scm/git/git
  url = git://repo.or.cz/alt-git.git
  url = https://github.com/gitster/git

New contributors whose contributions weren't in v2.18.0 are as follows.
Welcome to the Git development community!

  Aleksandr Makarov, Andrei Rybak, Chen Bin, Henning Schild,
  Isabella Stephens, Josh Steadmon, Jules Maselbas, Kana Natsuno,
  Marc Strapetz, Masaya Suzuki, Nicholas Guriev, Sebastian Kisela,
  Vladimir Parfinenko, and William Chargin.

Returning contributors who helped this release are as follows.
Thanks for your continued support.

  Aaron Schrab, Ævar Arnfjörð Bjarmason, Alban Gruin, Alejandro
  R. Sedeño, Anthony Sottile, Antonio Ospite, Beat Bolli, Ben
  Peart, Brandon Williams, brian m. carlson, Christian Couder,
  Derrick Stolee, Elijah Newren, Eric Sunshine, Han-Wen Nienhuys,
  Jameson Miller, Jeff Hostetler, Jeff King, Johannes Schindelin,
  Johannes Sixt, Jonathan Nieder, Jonathan Tan, Junio C Hamano,
  Kim Gybels, Kirill Smelkov, Luis Marsano, Łukasz Stelmach,
  Luke Diamand, Martin Ågren, Max Kirillov, Michael Barabanov,
  Mike Hommey, Nguyễn Thái Ngọc Duy, Olga Telezhnaya,
  Phillip Wood, Prathamesh Chavan, Ramsay Jones, René Scharfe,
  Stefan Beller, SZEDER Gábor, Taylor Blau, Thomas Rast, Tobias
  Klauser, Todd Zullinger, Ville Skyttä, and Xiaolong Ye.

----------------------------------------------------------------

Git 2.19 Release Notes (draft)
==============================

Updates since v2.18
-------------------

UI, Workflows & Features

 * "git diff" compares the index and the working tree.  For paths
   added with intent-to-add bit, the command shows the full contents
   of them as added, but the paths themselves were not marked as new
   files.  They are now shown as new by default.

   "git apply" learned the "--intent-to-add" option so that an
   otherwise working-tree-only application of a patch will add new
   paths to the index marked with the "intent-to-add" bit.

 * "git grep" learned the "--column" option that gives not just the
   line number but the column number of the hit.

 * The "-l" option in "git branch -l" is an unfortunate short-hand for
   "--create-reflog", but many users, both old and new, somehow expect
   it to be something else, perhaps "--list".  This step warns when "-l"
   is used as a short-hand for "--create-reflog" and warns about the
   future repurposing of the it when it is used.

 * The userdiff pattern for .php has been updated.

 * The content-transfer-encoding of the message "git send-email" sends
   out by default was 8bit, which can cause trouble when there is an
   overlong line to bust RFC 5322/2822 limit.  A new option 'auto' to
   automatically switch to quoted-printable when there is such a line
   in the payload has been introduced and is made the default.

 * "git checkout" and "git worktree add" learned to honor
   checkout.defaultRemote when auto-vivifying a local branch out of a
   remote tracking branch in a repository with multiple remotes that
   have tracking branches that share the same names.
   (merge 8d7b558bae ab/checkout-default-remote later to maint).

 * "git grep" learned the "--only-matching" option.

 * "git rebase --rebase-merges" mode now handles octopus merges as
   well.

 * Add a server-side knob to skip commits in exponential/fibbonacci
   stride in an attempt to cover wider swath of history with a smaller
   number of iterations, potentially accepting a larger packfile
   transfer, instead of going back one commit a time during common
   ancestor discovery during the "git fetch" transaction.
   (merge 42cc7485a2 jt/fetch-negotiator-skipping later to maint).

 * A new configuration variable core.usereplacerefs has been added,
   primarily to help server installations that want to ignore the
   replace mechanism altogether.

 * Teach "git tag -s" etc. a few configuration variables (gpg.format
   that can be set to "openpgp" or "x509", and gpg.<format>.program
   that is used to specify what program to use to deal with the format)
   to allow x.509 certs with CMS via "gpgsm" to be used instead of
   openpgp via "gnupg".

 * Many more strings are prepared for l10n.

 * "git p4 submit" learns to ask its own pre-submit hook if it should
   continue with submitting.

 * The test performed at the receiving end of "git push" to prevent
   bad objects from entering repository can be customized via
   receive.fsck.* configuration variables; we now have gained a
   counterpart to do the same on the "git fetch" side, with
   fetch.fsck.* configuration variables.

 * "git pull --rebase=interactive" learned "i" as a short-hand for
   "interactive".

 * "git instaweb" has been adjusted to run better with newer Apache on
   RedHat based distros.

 * "git range-diff" is a reimplementation of "git tbdiff" that lets us
   compare individual patches in two iterations of a topic.

 * The sideband code learned to optionally paint selected keywords at
   the beginning of incoming lines on the receiving end.


Performance, Internal Implementation, Development Support etc.

 * The bulk of "git submodule foreach" has been rewritten in C.

 * The in-core "commit" object had an all-purpose "void *util" field,
   which was tricky to use especially in library-ish part of the
   code.  All of the existing uses of the field has been migrated to a
   more dedicated "commit-slab" mechanism and the field is eliminated.

 * A less often used command "git show-index" has been modernized.
   (merge fb3010c31f jk/show-index later to maint).

 * The conversion to pass "the_repository" and then "a_repository"
   throughout the object access API continues.

 * Continuing with the idea to programatically enumerate various
   pieces of data required for command line completion, teach the
   codebase to report the list of configuration variables
   subcommands care about to help complete them.

 * Separate "rebase -p" codepath out of "rebase -i" implementation to
   slim down the latter and make it easier to manage.

 * Make refspec parsing codepath more robust.

 * Some flaky tests have been fixed.

 * Continuing with the idea to programmatically enumerate various
   pieces of data required for command line completion, the codebase
   has been taught to enumerate options prefixed with "--no-" to
   negate them.

 * Build and test procedure for netrc credential helper (in contrib/)
   has been updated.

 * The conversion to pass "the_repository" and then "a_repository"
   throughout the object access API continues.

 * Remove unused function definitions and declarations from ewah
   bitmap subsystem.

 * Code preparation to make "git p4" closer to be usable with Python 3.

 * Tighten the API to make it harder to misuse in-tree .gitmodules
   file, even though it shares the same syntax with configuration
   files, to read random configuration items from it.

 * "git fast-import" has been updated to avoid attempting to create
   delta against a zero-byte-long string, which is pointless.

 * The codebase has been updated to compile cleanly with -pedantic
   option.
   (merge 2b647a05d7 bb/pedantic later to maint).

 * The character display width table has been updated to match the
   latest Unicode standard.
   (merge 570951eea2 bb/unicode-11-width later to maint).

 * test-lint now looks for broken use of "VAR=VAL shell_func" in test
   scripts.

 * Conversion from uchar[40] to struct object_id continues.

 * Recent "security fix" to pay attention to contents of ".gitmodules"
   while accepting "git push" was a bit overly strict than necessary,
   which has been adjusted.

 * "git fsck" learns to make sure the optional commit-graph file is in
   a sane state.

 * "git diff --color-moved" feature has further been tweaked.

 * Code restructuring and a small fix to transport protocol v2 during
   fetching.

 * Parsing of -L[<N>][,[<M>]] parameters "git blame" and "git log"
   take has been tweaked.

 * lookup_commit_reference() and friends have been updated to find
   in-core object for a specific in-core repository instance.

 * Various glitches in the heuristics of merge-recursive strategy have
   been documented in new tests.

 * "git fetch" learned a new option "--negotiation-tip" to limit the
   set of commits it tells the other end as "have", to reduce wasted
   bandwidth and cycles, which would be helpful when the receiving
   repository has a lot of refs that have little to do with the
   history at the remote it is fetching from.

 * For a large tree, the index needs to hold many cache entries
   allocated on heap.  These cache entries are now allocated out of a
   dedicated memory pool to amortize malloc(3) overhead.

 * Tests to cover various conflicting cases have been added for
   merge-recursive.

 * Tests to cover conflict cases that involve submodules have been
   added for merge-recursive.

 * Look for broken "&&" chains that are hidden in subshell, many of
   which have been found and corrected.

 * The singleton commit-graph in-core instance is made per in-core
   repository instance.

 * "make DEVELOPER=1 DEVOPTS=pedantic" allows developers to compile
   with -pedantic option, which may catch more problematic program
   constructs and potential bugs.

 * Preparatory code to later add json output for telemetry data has
   been added.

 * Update the way we use Coccinelle to find out-of-style code that
   need to be modernised.

 * It is too easy to misuse system API functions such as strcat();
   these selected functions are now forbidden in this codebase and
   will cause a compilation failure.

 * Add a script (in contrib/) to help users of VSCode work better with
   our codebase.

 * The Travis CI scripts were taught to ship back the test data from
   failed tests.
   (merge aea8879a6a sg/travis-retrieve-trash-upon-failure later to maint).

 * The parse-options machinery learned to refrain from enclosing
   placeholder string inside a "<bra" and "ket>" pair automatically
   without PARSE_OPT_LITERAL_ARGHELP.  Existing help text for option
   arguments that are not formatted correctly have been identified and
   fixed.
   (merge 5f0df44cd7 rs/parse-opt-lithelp later to maint).

 * Noiseword "extern" has been removed from function decls in the
   header files.

 * A few atoms like %(objecttype) and %(objectsize) in the format
   specifier of "for-each-ref --format=<format>" can be filled without
   getting the full contents of the object, but just with the object
   header.  These cases have been optimized by calling
   oid_object_info() API (instead of reading and inspecting the data).

 * The end result of documentation update has been made to be
   inspected more easily to help developers.

 * The API to iterate over all objects learned to optionally list
   objects in the order they appear in packfiles, which helps locality
   of access if the caller accesses these objects while as objects are
   enumerated.

 * Improve built-in facility to catch broken &&-chain in the tests.

 * The more library-ish parts of the codebase learned to work on the
   in-core index-state instance that is passed in by their callers,
   instead of always working on the singleton "the_index" instance.

 * A test prerequisite defined by various test scripts with slightly
   different semantics has been consolidated into a single copy and
   made into a lazily defined one.
   (merge 6ec633059a wc/make-funnynames-shared-lazy-prereq later to maint).

 * After a partial clone, repeated fetches from promisor remote would
   have accumulated many packfiles marked with .promisor bit without
   getting them coalesced into fewer packfiles, hurting performance.
   "git repack" now learned to repack them.


Fixes since v2.18
-----------------

 * "git remote update" can take both a single remote nickname and a
   nickname for remote groups, and the completion script (in contrib/)
   has been taught about it.
   (merge 9cd4382ad5 ls/complete-remote-update-names later to maint).

 * "git fetch --shallow-since=<cutoff>" that specifies the cut-off
   point that is newer than the existing history used to end up
   grabbing the entire history.  Such a request now errors out.
   (merge e34de73c56 nd/reject-empty-shallow-request later to maint).

 * Fix for 2.17-era regression around `core.safecrlf`.
   (merge 6cb09125be as/safecrlf-quiet-fix later to maint).

 * The recent addition of "partial clone" experimental feature kicked
   in when it shouldn't, namely, when there is no partial-clone filter
   defined even if extensions.partialclone is set.
   (merge cac1137dc4 jh/partial-clone later to maint).

 * "git send-pack --signed" (hence "git push --signed" over the http
   transport) did not read user ident from the config mechanism to
   determine whom to sign the push certificate as, which has been
   corrected.
   (merge d067d98887 ms/send-pack-honor-config later to maint).

 * "git fetch-pack --all" used to unnecessarily fail upon seeing an
   annotated tag that points at an object other than a commit.
   (merge c12c9df527 jk/fetch-all-peeled-fix later to maint).

 * When user edits the patch in "git add -p" and the user's editor is
   set to strip trailing whitespaces indiscriminately, an empty line
   that is unchanged in the patch would become completely empty
   (instead of a line with a sole SP on it).  The code introduced in
   Git 2.17 timeframe failed to parse such a patch, but now it learned
   to notice the situation and cope with it.
   (merge f4d35a6b49 pw/add-p-recount later to maint).

 * The code to try seeing if a fetch is necessary in a submodule
   during a fetch with --recurse-submodules got confused when the path
   to the submodule was changed in the range of commits in the
   superproject, sometimes showing "(null)".  This has been corrected.

 * "git submodule" did not correctly adjust core.worktree setting that
   indicates whether/where a submodule repository has its associated
   working tree across various state transitions, which has been
   corrected.
   (merge 984cd77ddb sb/submodule-core-worktree later to maint).

 * Bugfix for "rebase -i" corner case regression.
   (merge a9279c6785 pw/rebase-i-keep-reword-after-conflict later to maint).

 * Recently added "--base" option to "git format-patch" command did
   not correctly generate prereq patch ids.
   (merge 15b76c1fb3 xy/format-patch-prereq-patch-id-fix later to maint).

 * POSIX portability fix in Makefile to fix a glitch introduced a few
   releases ago.
   (merge 6600054e9b dj/runtime-prefix later to maint).

 * "git filter-branch" when used with the "--state-branch" option
   still attempted to rewrite the commits whose filtered result is
   known from the previous attempt (which is recorded on the state
   branch); the command has been corrected not to waste cycles doing
   so.
   (merge 709cfe848a mb/filter-branch-optim later to maint).

 * Clarify that setting core.ignoreCase to deviate from reality would
   not turn a case-incapable filesystem into a case-capable one.
   (merge 48294b512a ms/core-icase-doc later to maint).

 * "fsck.skipList" did not prevent a blob object listed there from
   being inspected for is contents (e.g. we recently started to
   inspect the contents of ".gitmodules" for certain malicious
   patterns), which has been corrected.
   (merge fb16287719 rj/submodule-fsck-skip later to maint).

 * "git checkout --recurse-submodules another-branch" did not report
   in which submodule it failed to update the working tree, which
   resulted in an unhelpful error message.
   (merge ba95d4e4bd sb/submodule-move-head-error-msg later to maint).

 * "git rebase" behaved slightly differently depending on which one of
   the three backends gets used; this has been documented and an
   effort to make them more uniform has begun.
   (merge b00bf1c9a8 en/rebase-consistency later to maint).

 * The "--ignore-case" option of "git for-each-ref" (and its friends)
   did not work correctly, which has been fixed.
   (merge e674eb2528 jk/for-each-ref-icase later to maint).

 * "git fetch" failed to correctly validate the set of objects it
   received when making a shallow history deeper, which has been
   corrected.
   (merge cf1e7c0770 jt/connectivity-check-after-unshallow later to maint).

 * Partial clone support of "git clone" has been updated to correctly
   validate the objects it receives from the other side.  The server
   side has been corrected to send objects that are directly
   requested, even if they may match the filtering criteria (e.g. when
   doing a "lazy blob" partial clone).
   (merge a7e67c11b8 jt/partial-clone-fsck-connectivity later to maint).

 * Handling of an empty range by "git cherry-pick" was inconsistent
   depending on how the range ended up to be empty, which has been
   corrected.
   (merge c5e358d073 jk/empty-pick-fix later to maint).

 * "git reset --merge" (hence "git merge ---abort") and "git reset --hard"
   had trouble working correctly in a sparsely checked out working
   tree after a conflict, which has been corrected.
   (merge b33fdfc34c mk/merge-in-sparse-checkout later to maint).

 * Correct a broken use of "VAR=VAL shell_func" in a test.
   (merge 650161a277 jc/t3404-one-shot-export-fix later to maint).

 * "git rev-parse ':/substring'" did not consider the history leading
   only to HEAD when looking for a commit with the given substring,
   when the HEAD is detached.  This has been fixed.
   (merge 6b3351e799 wc/find-commit-with-pattern-on-detached-head later to maint).

 * Build doc update for Windows.
   (merge ede8d89bb1 nd/command-list later to maint).

 * core.commentchar is now honored when preparing the list of commits
   to replay in "rebase -i".

 * "git pull --rebase" on a corrupt HEAD caused a segfault.  In
   general we substitute an empty tree object when running the in-core
   equivalent of the diff-index command, and the codepath has been
   corrected to do so as well to fix this issue.
   (merge 3506dc9445 jk/has-uncommitted-changes-fix later to maint).

 * httpd tests saw occasional breakage due to the way its access log
   gets inspected by the tests, which has been updated to make them
   less flaky.
   (merge e8b3b2e275 sg/httpd-test-unflake later to maint).

 * Tests to cover more D/F conflict cases have been added for
   merge-recursive.

 * "git gc --auto" opens file descriptors for the packfiles before
   spawning "git repack/prune", which would upset Windows that does
   not want a process to work on a file that is open by another
   process.  The issue has been worked around.
   (merge 12e73a3ce4 kg/gc-auto-windows-workaround later to maint).

 * The recursive merge strategy did not properly ensure there was no
   change between HEAD and the index before performing its operation,
   which has been corrected.
   (merge 55f39cf755 en/dirty-merge-fixes later to maint).

 * "git rebase" started exporting GIT_DIR environment variable and
   exposing it to hook scripts when part of it got rewritten in C.
   Instead of matching the old scripted Porcelains' behaviour,
   compensate by also exporting GIT_WORK_TREE environment as well to
   lessen the damage.  This can harm existing hooks that want to
   operate on different repository, but the current behaviour is
   already broken for them anyway.
   (merge ab5e67d751 bc/sequencer-export-work-tree-as-well later to maint).

 * "git send-email" when using in a batched mode that limits the
   number of messages sent in a single SMTP session lost the contents
   of the variable used to choose between tls/ssl, unable to send the
   second and later batches, which has been fixed.
   (merge 636f3d7ac5 jm/send-email-tls-auth-on-batch later to maint).

 * The lazy clone support had a few places where missing but promised
   objects were not correctly tolerated, which have been fixed.

 * One of the "diff --color-moved" mode "dimmed_zebra" that was named
   in an unusual way has been deprecated and replaced by
   "dimmed-zebra".
   (merge e3f2f5f9cd es/diff-color-moved-fix later to maint).

 * The wire-protocol v2 relies on the client to send "ref prefixes" to
   limit the bandwidth spent on the initial ref advertisement.  "git
   clone" when learned to speak v2 forgot to do so, which has been
   corrected.
   (merge 402c47d939 bw/clone-ref-prefixes later to maint).

 * "git diff --histogram" had a bad memory usage pattern, which has
   been rearranged to reduce the peak usage.
   (merge 79cb2ebb92 sb/histogram-less-memory later to maint).

 * Code clean-up to use size_t/ssize_t when they are the right type.
   (merge 7726d360b5 jk/size-t later to maint).

 * The wire-protocol v2 relies on the client to send "ref prefixes" to
   limit the bandwidth spent on the initial ref advertisement.  "git
   fetch $remote branch:branch" that asks tags that point into the
   history leading to the "branch" automatically followed sent to
   narrow prefix and broke the tag following, which has been fixed.
   (merge 2b554353a5 jt/tag-following-with-proto-v2-fix later to maint).

 * When the sparse checkout feature is in use, "git cherry-pick" and
   other mergy operations lost the skip_worktree bit when a path that
   is excluded from checkout requires content level merge, which is
   resolved as the same as the HEAD version, without materializing the
   merge result in the working tree, which made the path appear as
   deleted.  This has been corrected by preserving the skip_worktree
   bit (and not materializing the file in the working tree).
   (merge 2b75fb601c en/merge-recursive-skip-fix later to maint).

 * The "author-script" file "git rebase -i" creates got broken when
   we started to move the command away from shell script, which is
   getting fixed now.
   (merge 5522bbac20 es/rebase-i-author-script-fix later to maint).

 * The automatic tree-matching in "git merge -s subtree" was broken 5
   years ago and nobody has noticed since then, which is now fixed.
   (merge 2ec4150713 jk/merge-subtree-heuristics later to maint).

 * "git fetch $there refs/heads/s" ought to fetch the tip of the
   branch 's', but when "refs/heads/refs/heads/s", i.e. a branch whose
   name is "refs/heads/s" exists at the same time, fetched that one
   instead by mistake.  This has been corrected to honor the usual
   disambiguation rules for abbreviated refnames.
   (merge 60650a48c0 jt/refspec-dwim-precedence-fix later to maint).

 * Futureproofing a helper function that can easily be misused.
   (merge 65bb21e77e es/want-color-fd-defensive later to maint).

 * The http-backend (used for smart-http transport) used to slurp the
   whole input until EOF, without paying attention to CONTENT_LENGTH
   that is supplied in the environment and instead expecting the Web
   server to close the input stream.  This has been fixed.
   (merge eebfe40962 mk/http-backend-content-length later to maint).

 * "git merge --abort" etc. did not clean things up properly when
   there were conflicted entries in the index in certain order that
   are involved in D/F conflicts.  This has been corrected.
   (merge ad3762042a en/abort-df-conflict-fixes later to maint).

 * "git diff --indent-heuristic" had a bad corner case performance.
   (merge 301ef85401 sb/indent-heuristic-optim later to maint).

 * The "--exec" option to "git rebase --rebase-merges" placed the exec
   commands at wrong places, which has been corrected.

 * "git verify-tag" and "git verify-commit" have been taught to use
   the exit status of underlying "gpg --verify" to signal bad or
   untrusted signature they found.
   (merge 4e5dc9ca17 jc/gpg-status later to maint).

 * "git mergetool" stopped and gave an extra prompt to continue after
   the last path has been handled, which did not make much sense.
   (merge d651a54b8a ng/mergetool-lose-final-prompt later to maint).

 * Among the three codepaths we use O_APPEND to open a file for
   appending, one used for writing GIT_TRACE output requires O_APPEND
   implementation that behaves sensibly when multiple processes are
   writing to the same file.  POSIX emulation used in the Windows port
   has been updated to improve in this area.
   (merge d641097589 js/mingw-o-append later to maint).

 * "git pull --rebase -v" in a repository with a submodule barfed as
   an intermediate process did not understand what "-v(erbose)" flag
   meant, which has been fixed.
   (merge e84c3cf3dc sb/pull-rebase-submodule later to maint).

 * Recent update to "git config" broke updating variable in a
   subsection, which has been corrected.
   (merge bff7df7a87 sb/config-write-fix later to maint).

 * When "git rebase -i" is told to squash two or more commits into
   one, it labeled the log message for each commit with its number.
   It correctly called the first one "1st commit", but the next one
   was "commit #1", which was off-by-one.  This has been corrected.
   (merge dd2e36ebac pw/rebase-i-squash-number-fix later to maint).

 * "git rebase -i", when a 'merge <branch>' insn in its todo list
   fails, segfaulted, which has been (minimally) corrected.
   (merge bc9238bb09 pw/rebase-i-merge-segv-fix later to maint).

 * "git cherry-pick --quit" failed to remove CHERRY_PICK_HEAD even
   though we won't be in a cherry-pick session after it returns, which
   has been corrected.
   (merge 3e7dd99208 nd/cherry-pick-quit-fix later to maint).

 * Code cleanup, docfix, build fix, etc.
   (merge aee9be2ebe sg/update-ref-stdin-cleanup later to maint).
   (merge 037714252f jc/clean-after-sanity-tests later to maint).
   (merge 5b26c3c941 en/merge-recursive-cleanup later to maint).
   (merge 0dcbc0392e bw/config-refer-to-gitsubmodules-doc later to maint).
   (merge bb4d000e87 bw/protocol-v2 later to maint).
   (merge 928f0ab4ba vs/typofixes later to maint).
   (merge d7f590be84 en/rebase-i-microfixes later to maint).
   (merge 81d395cc85 js/rebase-recreate-merge later to maint).
   (merge 51d1863168 tz/exclude-doc-smallfixes later to maint).
   (merge a9aa3c0927 ds/commit-graph later to maint).
   (merge 5cf8e06474 js/enhanced-version-info later to maint).
   (merge 6aaded5509 tb/config-default later to maint).
   (merge 022d2ac1f3 sb/blame-color later to maint).
   (merge 5a06a20e0c bp/test-drop-caches-for-windows later to maint).
   (merge dd61cc1c2e jk/ui-color-always-to-auto later to maint).
   (merge 1e83b9bfdd sb/trailers-docfix later to maint).
   (merge ab29f1b329 sg/fast-import-dump-refs-on-checkpoint-fix later to maint).
   (merge 6a8ad880f0 jn/subtree-test-fixes later to maint).
   (merge ffbd51cc60 nd/pack-objects-threading-doc later to maint).
   (merge e9dac7be60 es/mw-to-git-chain-fix later to maint).
   (merge fe583c6c7a rs/remote-mv-leakfix later to maint).
   (merge 69885ab015 en/t3031-title-fix later to maint).
   (merge 8578037bed nd/config-blame-sort later to maint).
   (merge 8ad169c4ba hn/config-in-code-comment later to maint).
   (merge b7446fcfdf ar/t4150-am-scissors-test-fix later to maint).
   (merge a8132410ee js/typofixes later to maint).
   (merge 388d0ff6e5 en/update-index-doc later to maint).
   (merge e05aa688dd jc/update-index-doc later to maint).
   (merge 10c600172c sg/t5310-empty-input-fix later to maint).
   (merge 5641eb9465 jh/partial-clone-doc later to maint).
   (merge 2711b1ad5e ab/submodule-relative-url-tests later to maint).

----------------------------------------------------------------

Changes since v2.18.0 are as follows:

Aaron Schrab (1):
      sequencer: use configured comment character

Alban Gruin (4):
      rebase: introduce a dedicated backend for --preserve-merges
      rebase: strip unused code in git-rebase--preserve-merges.sh
      rebase: use the new git-rebase--preserve-merges.sh
      rebase: remove -p code from git-rebase--interactive.sh

Alejandro R. Sedeño (1):
      Makefile: tweak sed invocation

Aleksandr Makarov (1):
      for-each-ref: consistently pass WM_IGNORECASE flag

Andrei Rybak (2):
      Documentation: fix --color option formatting
      t4150: fix broken test for am --scissors

Anthony Sottile (1):
      config.c: fix regression for core.safecrlf false

Antonio Ospite (6):
      config: move config_from_gitmodules to submodule-config.c
      submodule-config: add helper function to get 'fetch' config from .gitmodules
      submodule-config: add helper to get 'update-clone' config from .gitmodules
      submodule-config: make 'config_from_gitmodules' private
      submodule-config: pass repository as argument to config_from_gitmodules
      submodule-config: reuse config_from_gitmodules in repo_read_gitmodules

Beat Bolli (10):
      builtin/config: work around an unsized array forward declaration
      unicode: update the width tables to Unicode 11
      connect.h: avoid forward declaration of an enum
      refs/refs-internal.h: avoid forward declaration of an enum
      convert.c: replace "\e" escapes with "\033".
      sequencer.c: avoid empty statements at top level
      string-list.c: avoid conversion from void * to function pointer
      utf8.c: avoid char overflow
      Makefile: add a DEVOPTS flag to get pedantic compilation
      packfile: ensure that enum object_type is defined

Ben Peart (3):
      convert log_ref_write_fd() to use strbuf
      handle lower case drive letters on Windows
      t3507: add a testcase showing failure with sparse checkout

Brandon Williams (15):
      commit: convert commit_graft_pos() to handle arbitrary repositories
      commit: convert register_commit_graft to handle arbitrary repositories
      commit: convert read_graft_file to handle arbitrary repositories
      test-pkt-line: add unpack-sideband subcommand
      docs: link to gitsubmodules
      upload-pack: implement ref-in-want
      upload-pack: test negotiation with changing repository
      fetch: refactor the population of peer ref OIDs
      fetch: refactor fetch_refs into two functions
      fetch: refactor to make function args narrower
      fetch-pack: put shallow info in output parameter
      fetch-pack: implement ref-in-want
      clone: send ref-prefixes when using protocol v2
      fetch-pack: mark die strings for translation
      pack-protocol: mention and point to docs for protocol v2

Chen Bin (1):
      git-p4: add the `p4-pre-submit` hook

Christian Couder (1):
      t9104: kosherly remove remote refs

Derrick Stolee (43):
      ref-filter: fix outdated comment on in_commit_list
      commit: add generation number to struct commit
      commit-graph: compute generation numbers
      commit: use generations in paint_down_to_common()
      commit-graph: always load commit-graph information
      ref-filter: use generation number for --contains
      commit: use generation numbers for in_merge_bases()
      commit: add short-circuit to paint_down_to_common()
      commit: use generation number in remove_redundant()
      merge: check config before loading commits
      commit-graph.txt: update design document
      commit-graph: fix UX issue when .lock file exists
      ewah/bitmap.c: delete unused 'bitmap_clear()'
      ewah/bitmap.c: delete unused 'bitmap_each_bit()'
      ewah_bitmap: delete unused 'ewah_and()'
      ewah_bitmap: delete unused 'ewah_and_not()'
      ewah_bitmap: delete unused 'ewah_not()'
      ewah_bitmap: delete unused 'ewah_or()'
      ewah_io: delete unused 'ewah_serialize()'
      t5318-commit-graph.sh: use core.commitGraph
      commit-graph: UNLEAK before die()
      commit-graph: fix GRAPH_MIN_SIZE
      commit-graph: parse commit from chosen graph
      commit: force commit to parse from object database
      commit-graph: load a root tree from specific graph
      commit-graph: add 'verify' subcommand
      commit-graph: verify catches corrupt signature
      commit-graph: verify required chunks are present
      commit-graph: verify corrupt OID fanout and lookup
      commit-graph: verify objects exist
      commit-graph: verify root tree OIDs
      commit-graph: verify parent list
      commit-graph: verify generation number
      commit-graph: verify commit date
      commit-graph: test for corrupted octopus edge
      commit-graph: verify contents match checksum
      fsck: verify commit-graph
      commit-graph: use string-list API for input
      commit-graph: add '--reachable' option
      gc: automatically write commit-graph files
      commit-graph: update design document
      commit-graph: fix documentation inconsistencies
      coccinelle: update commit.cocci

Elijah Newren (63):
      t6036, t6042: use test_create_repo to keep tests independent
      t6036, t6042: use test_line_count instead of wc -l
      t6036, t6042: prefer test_path_is_file, test_path_is_missing
      t6036, t6042: prefer test_cmp to sequences of test
      t6036: prefer test_when_finished to manual cleanup in following test
      merge-recursive: fix miscellaneous grammar error in comment
      merge-recursive: fix numerous argument alignment issues
      merge-recursive: align labels with their respective code blocks
      merge-recursive: clarify the rename_dir/RENAME_DIR meaning
      merge-recursive: rename conflict_rename_*() family of functions
      merge-recursive: add pointer about unduly complex looking code
      git-rebase.txt: document incompatible options
      git-rebase.sh: update help messages a bit
      t3422: new testcases for checking when incompatible options passed
      git-rebase: error out when incompatible options passed
      git-rebase.txt: address confusion between --no-ff vs --force-rebase
      directory-rename-detection.txt: technical docs on abilities and limitations
      git-rebase.txt: document behavioral differences between modes
      t3401: add directory rename testcases for rebase and am
      git-rebase: make --allow-empty-message the default
      t3418: add testcase showing problems with rebase -i and strategy options
      Fix use of strategy options with interactive rebases
      git-rebase--merge: modernize "git-$cmd" to "git $cmd"
      apply: fix grammar error in comment
      t5407: fix test to cover intended arguments
      read-cache.c: move index_has_changes() from merge.c
      index_has_changes(): avoid assuming operating on the_index
      t6044: verify that merges expected to abort actually abort
      t6036: add a failed conflict detection case with symlink modify/modify
      t6036: add a failed conflict detection case with symlink add/add
      t6036: add a failed conflict detection case with submodule modify/modify
      t6036: add a failed conflict detection case with submodule add/add
      t6036: add a failed conflict detection case with conflicting types
      t6042: add testcase covering rename/add/delete conflict type
      t6042: add testcase covering rename/rename(2to1)/delete/delete conflict
      t6042: add testcase covering long chains of rename conflicts
      t6036: add lots of detail for directory/file conflicts in recursive case
      t6036: add a failed conflict detection case: regular files, different modes
      t6044: add a testcase for index matching head, when head doesn't match HEAD
      merge-recursive: make sure when we say we abort that we actually abort
      merge-recursive: fix assumption that head tree being merged is HEAD
      t6044: add more testcases with staged changes before a merge is invoked
      merge-recursive: enforce rule that index matches head before merging
      merge: fix misleading pre-merge check documentation
      t7405: add a file/submodule conflict
      t7405: add a directory/submodule conflict
      t7405: verify 'merge --abort' works after submodule/path conflicts
      merge-recursive: preserve skip_worktree bit when necessary
      t1015: demonstrate directory/file conflict recovery failures
      read-cache: fix directory/file conflict handling in read_index_unmerged()
      t3031: update test description to mention desired behavior
      t7406: fix call that was failing for the wrong reason
      t7406: simplify by using diff --name-only instead of diff --raw
      t7406: avoid having git commands upstream of a pipe
      t7406: prefer test_* helper functions to test -[feds]
      t7406: avoid using test_must_fail for commands other than git
      git-update-index.txt: reword possibly confusing example
      Add missing includes and forward declarations
      alloc: make allocate_alloc_state and clear_alloc_state more consistent
      Move definition of enum branch_track from cache.h to branch.h
      urlmatch.h: fix include guard
      compat/precompose_utf8.h: use more common include guard style
      Remove forward declaration of an enum

Eric Sunshine (53):
      t: use test_might_fail() instead of manipulating exit code manually
      t: use test_write_lines() instead of series of 'echo' commands
      t: use sane_unset() rather than 'unset' with broken &&-chain
      t: drop unnecessary terminating semicolon in subshell
      t/lib-submodule-update: fix "absorbing" test
      t5405: use test_must_fail() instead of checking exit code manually
      t5406: use write_script() instead of birthing shell script manually
      t5505: modernize and simplify hard-to-digest test
      t6036: fix broken "merge fails but has appropriate contents" tests
      t7201: drop pointless "exit 0" at end of subshell
      t7400: fix broken "submodule add/reconfigure --force" test
      t7810: use test_expect_code() instead of hand-rolled comparison
      t9001: fix broken "invoke hook" test
      t9814: simplify convoluted check that command correctly errors out
      t0000-t0999: fix broken &&-chains
      t1000-t1999: fix broken &&-chains
      t2000-t2999: fix broken &&-chains
      t3000-t3999: fix broken &&-chains
      t3030: fix broken &&-chains
      t4000-t4999: fix broken &&-chains
      t5000-t5999: fix broken &&-chains
      t6000-t6999: fix broken &&-chains
      t7000-t7999: fix broken &&-chains
      t9000-t9999: fix broken &&-chains
      t9119: fix broken &&-chains
      t6046/t9833: fix use of "VAR=VAL cmd" with a shell function
      t/check-non-portable-shell: stop being so polite
      t/check-non-portable-shell: make error messages more compact
      t/check-non-portable-shell: detect "FOO=bar shell_func"
      t/test-lib: teach --chain-lint to detect broken &&-chains in subshells
      t/Makefile: add machinery to check correctness of chainlint.sed
      t/chainlint: add chainlint "basic" test cases
      t/chainlint: add chainlint "whitespace" test cases
      t/chainlint: add chainlint "one-liner" test cases
      t/chainlint: add chainlint "nested subshell" test cases
      t/chainlint: add chainlint "loop" and "conditional" test cases
      t/chainlint: add chainlint "cuddled" test cases
      t/chainlint: add chainlint "complex" test cases
      t/chainlint: add chainlint "specialized" test cases
      diff: --color-moved: rename "dimmed_zebra" to "dimmed-zebra"
      mw-to-git/t9360: fix broken &&-chain
      t/chainlint.sed: drop extra spaces from regex character class
      sequencer: fix "rebase -i --root" corrupting author header
      sequencer: fix "rebase -i --root" corrupting author header timezone
      sequencer: fix "rebase -i --root" corrupting author header timestamp
      sequencer: don't die() on bogus user-edited timestamp
      color: protect against out-of-bounds reads and writes
      chainlint: match arbitrary here-docs tags rather than hard-coded names
      chainlint: match 'quoted' here-doc tags
      chainlint: recognize multi-line $(...) when command cuddled with "$("
      chainlint: let here-doc and multi-line string commence on same line
      chainlint: recognize multi-line quoted strings more robustly
      chainlint: add test of pathological case which triggered false positive

Han-Wen Nienhuys (2):
      config: document git config getter return value
      sideband: highlight keywords in remote sideband output

Henning Schild (9):
      builtin/receive-pack: use check_signature from gpg-interface
      gpg-interface: make parse_gpg_output static and remove from interface header
      gpg-interface: add new config to select how to sign a commit
      t/t7510: check the validation of the new config gpg.format
      gpg-interface: introduce an abstraction for multiple gpg formats
      gpg-interface: do not hardcode the key string len anymore
      gpg-interface: introduce new config to select per gpg format program
      gpg-interface: introduce new signature format "x509" using gpgsm
      gpg-interface t: extend the existing GPG tests with GPGSM

Isabella Stephens (2):
      blame: prevent error if range ends past end of file
      log: prevent error if line range ends past end of file

Jameson Miller (8):
      read-cache: teach refresh_cache_entry to take istate
      read-cache: teach make_cache_entry to take object_id
      block alloc: add lifecycle APIs for cache_entry structs
      mem-pool: only search head block for available space
      mem-pool: add life cycle management functions
      mem-pool: fill out functionality
      block alloc: allocate cache entries from mem_pool
      block alloc: add validations around cache_entry lifecyle

Jeff Hostetler (1):
      json_writer: new routines to create JSON data

Jeff King (48):
      make show-index a builtin
      show-index: update documentation for index v2
      fetch-pack: don't try to fetch peel values with --all
      ewah: drop ewah_deserialize function
      ewah: drop ewah_serialize_native function
      t3200: unset core.logallrefupdates when testing reflog creation
      t: switch "branch -l" to "branch --create-reflog"
      branch: deprecate "-l" option
      config: turn die_on_error into caller-facing enum
      config: add CONFIG_ERROR_SILENT handler
      config: add options parameter to git_config_from_mem
      fsck: silence stderr when parsing .gitmodules
      t6300: add a test for --ignore-case
      ref-filter: avoid backend filtering with --ignore-case
      t5500: prettify non-commit tag tests
      sequencer: handle empty-set cases consistently
      sequencer: don't say BUG on bogus input
      has_uncommitted_changes(): fall back to empty tree
      fsck: split ".gitmodules too large" error from parse failure
      fsck: downgrade gitmodulesParse default to "info"
      blame: prefer xsnprintf to strcpy for colors
      check_replace_refs: fix outdated comment
      check_replace_refs: rename to read_replace_refs
      add core.usereplacerefs config option
      reencode_string: use st_add/st_mult helpers
      reencode_string: use size_t for string lengths
      strbuf: use size_t for length in intermediate variables
      strbuf_readlink: use ssize_t
      pass st.st_size as hint for strbuf_readlink()
      strbuf_humanise: use unsigned variables
      automatically ban strcpy()
      banned.h: mark strcat() as banned
      banned.h: mark sprintf() as banned
      banned.h: mark strncpy() as banned
      score_trees(): fix iteration over trees with missing entries
      add a script to diff rendered documentation
      t5552: suppress upload-pack trace output
      for_each_*_object: store flag definitions in a single location
      for_each_*_object: take flag arguments as enum
      for_each_*_object: give more comprehensive docstrings
      for_each_packed_object: support iterating in pack-order
      t1006: test cat-file --batch-all-objects with duplicates
      cat-file: rename batch_{loose,packed}_object callbacks
      cat-file: support "unordered" output for --batch-all-objects
      cat-file: use oidset check-and-insert
      cat-file: split batch "buf" into two variables
      cat-file: use a single strbuf for all output
      for_each_*_object: move declarations to object-store.h

Johannes Schindelin (41):
      Makefile: fix the "built from commit" code
      merge: allow reading the merge commit message from a file
      rebase --rebase-merges: add support for octopus merges
      rebase --rebase-merges: adjust man page for octopus support
      vcbuild/README: update to accommodate for missing common-cmds.h
      t7406: avoid failures solely due to timing issues
      contrib: add a script to initialize VS Code configuration
      vscode: hard-code a couple defines
      cache.h: extract enum declaration from inside a struct declaration
      mingw: define WIN32 explicitly
      vscode: only overwrite C/C++ settings
      vscode: wrap commit messages at column 72 by default
      vscode: use 8-space tabs, no trailing ws, etc for Git's source code
      vscode: add a dictionary for cSpell
      vscode: let cSpell work on commit messages, too
      pull --rebase=<type>: allow single-letter abbreviations for the type
      t3430: demonstrate what -r, --autosquash & --exec should do
      git-compat-util.h: fix typo
      remote-curl: remove spurious period
      rebase --exec: make it work with --rebase-merges
      linear-assignment: a function to solve least-cost assignment problems
      Introduce `range-diff` to compare iterations of a topic branch
      range-diff: first rudimentary implementation
      range-diff: improve the order of the shown commits
      range-diff: also show the diff between patches
      range-diff: right-trim commit messages
      range-diff: indent the diffs just like tbdiff
      range-diff: suppress the diff headers
      range-diff: adjust the output of the commit pairs
      range-diff: do not show "function names" in hunk headers
      range-diff: use color for the commit pairs
      color: add the meta color GIT_COLOR_REVERSE
      diff: add an internal option to dual-color diffs of diffs
      range-diff: offer to dual-color the diffs
      range-diff --dual-color: skip white-space warnings
      range-diff: populate the man page
      completion: support `git range-diff`
      range-diff: left-pad patch numbers
      range-diff: make --dual-color the default mode
      range-diff: use dim/bold cues to improve dual color mode
      chainlint: fix for core.autocrlf=true

Johannes Sixt (1):
      mingw: enable atomic O_APPEND

Jonathan Nieder (11):
      object: add repository argument to grow_object_hash
      object: move grafts to object parser
      commit: add repository argument to commit_graft_pos
      commit: add repository argument to register_commit_graft
      commit: add repository argument to read_graft_file
      commit: add repository argument to prepare_commit_graft
      commit: add repository argument to lookup_commit_graft
      subtree test: add missing && to &&-chain
      subtree test: simplify preparation of expected results
      doc hash-function-transition: pick SHA-256 as NewHash
      partial-clone: render design doc using asciidoc

Jonathan Tan (28):
      list-objects: check if filter is NULL before using
      fetch-pack: split up everything_local()
      fetch-pack: clear marks before re-marking
      fetch-pack: directly end negotiation if ACK ready
      fetch-pack: use ref adv. to prune "have" sent
      fetch-pack: make negotiation-related vars local
      fetch-pack: move common check and marking together
      fetch-pack: introduce negotiator API
      pack-bitmap: remove bitmap_git global variable
      pack-bitmap: add free function
      fetch-pack: write shallow, then check connectivity
      fetch-pack: support negotiation tip whitelist
      upload-pack: send refs' objects despite "filter"
      clone: check connectivity even if clone is partial
      revision: tolerate promised targets of tags
      tag: don't warn if target is missing but promised
      negotiator/skipping: skip commits during fetch
      commit-graph: refactor preparing commit graph
      object-store: add missing include
      commit-graph: add missing forward declaration
      commit-graph: add free_commit_graph
      commit-graph: store graph in struct object_store
      commit-graph: add repo arg to graph readers
      t5702: test fetch with multiple refspecs at a time
      fetch: send "refs/tags/" prefix upon CLI refspecs
      fetch-pack: unify ref in and out param
      repack: refactor setup of pack-objects cmd
      repack: repack promisor objects if -a or -A is set

Josh Steadmon (1):
      protocol-v2 doc: put HTTP headers after request

Jules Maselbas (1):
      send-email: fix tls AUTH when sending batch

Junio C Hamano (18):
      tests: clean after SANITY tests
      ewah: delete unused 'rlwit_discharge_empty()'
      Prepare to start 2.19 cycle
      First batch for 2.19 cycle
      Second batch for 2.19 cycle
      fixup! connect.h: avoid forward declaration of an enum
      fixup! refs/refs-internal.h: avoid forward declaration of an enum
      t3404: fix use of "VAR=VAL cmd" with a shell function
      Third batch for 2.19 cycle
      Fourth batch for 2.19 cycle
      remote: make refspec follow the same disambiguation rule as local refs
      Fifth batch for 2.19 cycle
      update-index: there no longer is `apply --index-info`
      gpg-interface: propagate exit status from gpg back to the callers
      Sixth batch for 2.19 cycle
      Seventh batch for 2.19 cycle
      sideband: do not read beyond the end of input
      Git 2.19-rc0

Kana Natsuno (2):
      t4018: add missing test cases for PHP
      userdiff: support new keywords in PHP hunk header

Kim Gybels (1):
      gc --auto: release pack files before auto packing

Kirill Smelkov (1):
      fetch-pack: test explicitly that --all can fetch tag references pointing to non-commits

Luis Marsano (2):
      git-credential-netrc: use in-tree Git.pm for tests
      git-credential-netrc: fix exit status when tests fail

Luke Diamand (6):
      git-p4: python3: replace <> with !=
      git-p4: python3: replace dict.has_key(k) with "k in dict"
      git-p4: python3: remove backticks
      git-p4: python3: basestring workaround
      git-p4: python3: use print() function
      git-p4: python3: fix octal constants

Marc Strapetz (1):
      Documentation: declare "core.ignoreCase" as internal variable

Martin Ågren (1):
      refspec: initalize `refspec_item` in `valid_fetch_refspec()`

Masaya Suzuki (2):
      builtin/send-pack: populate the default configs
      doc: fix want-capability separator

Max Kirillov (4):
      http-backend: cleanup writing to child process
      http-backend: respect CONTENT_LENGTH as specified by rfc3875
      unpack-trees: do not fail reset because of unmerged skipped entry
      http-backend: respect CONTENT_LENGTH for receive-pack

Michael Barabanov (1):
      filter-branch: skip commits present on --state-branch

Mike Hommey (1):
      fast-import: do not call diff_delta() with empty buffer

Nguyễn Thái Ngọc Duy (98):
      commit-slab.h: code split
      commit-slab: support shared commit-slab
      blame: use commit-slab for blame suspects instead of commit->util
      describe: use commit-slab for commit names instead of commit->util
      shallow.c: use commit-slab for commit depth instead of commit->util
      sequencer.c: use commit-slab to mark seen commits
      sequencer.c: use commit-slab to associate todo items to commits
      revision.c: use commit-slab for show_source
      bisect.c: use commit-slab for commit weight instead of commit->util
      name-rev: use commit-slab for rev-name instead of commit->util
      show-branch: use commit-slab for commit-name instead of commit->util
      show-branch: note about its object flags usage
      log: use commit-slab in prepare_bases() instead of commit->util
      merge: use commit-slab in merge remote desc instead of commit->util
      commit.h: delete 'util' field in struct commit
      diff: ignore --ita-[in]visible-in-index when diffing worktree-to-tree
      diff: turn --ita-invisible-in-index on by default
      t2203: add a test about "diff HEAD" case
      apply: add --intent-to-add
      parse-options: option to let --git-completion-helper show negative form
      completion: suppress some -no- options
      Add and use generic name->id mapping code for color slot parsing
      grep: keep all colors in an array
      fsck: factor out msg_id_info[] lazy initialization code
      help: add --config to list all available config
      fsck: produce camelCase config key names
      advice: keep config name in camelCase in advice_config[]
      am: move advice.amWorkDir parsing back to advice.c
      completion: drop the hard coded list of config vars
      completion: keep other config var completion in camelCase
      completion: support case-insensitive config vars
      log-tree: allow to customize 'grafted' color
      completion: complete general config vars in two steps
      upload-pack: reject shallow requests that would return nothing
      completion: collapse extra --no-.. options
      Update messages in preparation for i18n
      archive-tar.c: mark more strings for translation
      archive-zip.c: mark more strings for translation
      builtin/config.c: mark more strings for translation
      builtin/grep.c: mark strings for translation
      builtin/pack-objects.c: mark more strings for translation
      builtin/replace.c: mark more strings for translation
      commit-graph.c: mark more strings for translation
      config.c: mark more strings for translation
      connect.c: mark more strings for translation
      convert.c: mark more strings for translation
      dir.c: mark more strings for translation
      environment.c: mark more strings for translation
      exec-cmd.c: mark more strings for translation
      object.c: mark more strings for translation
      pkt-line.c: mark more strings for translation
      refs.c: mark more strings for translation
      refspec.c: mark more strings for translation
      replace-object.c: mark more strings for translation
      sequencer.c: mark more strings for translation
      sha1-file.c: mark more strings for translation
      transport.c: mark more strings for translation
      transport-helper.c: mark more strings for translation
      pack-objects: document about thread synchronization
      apply.h: drop extern on func declaration
      attr.h: drop extern from function declaration
      blame.h: drop extern on func declaration
      cache-tree.h: drop extern from function declaration
      convert.h: drop 'extern' from function declaration
      diffcore.h: drop extern from function declaration
      diff.h: remove extern from function declaration
      line-range.h: drop extern from function declaration
      rerere.h: drop extern from function declaration
      repository.h: drop extern from function declaration
      revision.h: drop extern from function declaration
      submodule.h: drop extern from function declaration
      config.txt: reorder blame stuff to keep config keys sorted
      Makefile: add missing dependency for command-list.h
      diff.c: move read_index() code back to the caller
      cache-tree: wrap the_index based wrappers with #ifdef
      attr: remove an implicit dependency on the_index
      convert.c: remove an implicit dependency on the_index
      dir.c: remove an implicit dependency on the_index in pathspec code
      preload-index.c: use the right index instead of the_index
      ls-files: correct index argument to get_convert_attr_ascii()
      unpack-trees: remove 'extern' on function declaration
      unpack-trees: add a note about path invalidation
      unpack-trees: don't shadow global var the_index
      unpack-trees: convert clear_ce_flags* to avoid the_index
      unpack-trees: avoid the_index in verify_absent()
      pathspec.c: use the right index instead of the_index
      submodule.c: use the right index instead of the_index
      entry.c: use the right index instead of the_index
      attr: remove index from git_attr_set_direction()
      grep: use the right index instead of the_index
      archive.c: avoid access to the_index
      archive-*.c: use the right repository
      resolve-undo.c: use the right index instead of the_index
      apply.c: pass struct apply_state to more functions
      apply.c: make init_apply_state() take a struct repository
      apply.c: remove implicit dependency on the_index
      blame.c: remove implicit dependency on the_index
      cherry-pick: fix --quit not deleting CHERRY_PICK_HEAD

Nicholas Guriev (1):
      mergetool: don't suggest to continue after last file

Olga Telezhnaya (5):
      ref-filter: add info_source to valid_atom
      ref-filter: fill empty fields with empty values
      ref-filter: initialize eaten variable
      ref-filter: merge get_obj and get_object
      ref-filter: use oid_object_info() to get object

Phillip Wood (5):
      add -p: fix counting empty context lines in edited patches
      sequencer: do not squash 'reword' commits when we hit conflicts
      rebase -i: fix numbering in squash message
      t3430: add conflicting commit
      rebase -i: fix SIGSEGV when 'merge <branch>' fails

Prathamesh Chavan (4):
      submodule foreach: correct '$path' in nested submodules from a subdirectory
      submodule foreach: document '$sm_path' instead of '$path'
      submodule foreach: document variable '$displaypath'
      submodule: port submodule subcommand 'foreach' from shell to C

Ramsay Jones (3):
      fsck: check skiplist for object in fsck_blob()
      t6036: fix broken && chain in sub-shell
      t5562: avoid non-portable "export FOO=bar" construct

René Scharfe (7):
      remote: clear string_list after use in mv()
      add, update-index: fix --chmod argument help
      difftool: remove angular brackets from argument help
      pack-objects: specify --index-version argument help explicitly
      send-pack: specify --force-with-lease argument help explicitly
      shortlog: correct option help for -w
      parse-options: automatically infer PARSE_OPT_LITERAL_ARGHELP

SZEDER Gábor (19):
      update-ref --stdin: use skip_prefix()
      t7510-signed-commit: use 'test_must_fail'
      tests: make forging GPG signed commits and tags more robust
      t5541: clean up truncating access log
      t/lib-httpd: add the strip_access_log() helper function
      t/lib-httpd: avoid occasional failures when checking access.log
      t5608: fix broken &&-chain
      t9300: wait for background fast-import process to die after killing it
      travis-ci: run Coccinelle static analysis with two parallel jobs
      travis-ci: fail if Coccinelle static analysis found something to transform
      coccinelle: mark the 'coccicheck' make target as .PHONY
      coccinelle: use $(addsuffix) in 'coccicheck' make target
      coccinelle: exclude sha1dc source files from static analysis
      coccinelle: put sane filenames into output patches
      coccinelle: extract dedicated make target to clean Coccinelle's results
      travis-ci: include the trash directories of failed tests in the trace log
      t5318: use 'test_cmp_bin' to compare commit-graph files
      t5318: avoid unnecessary command substitutions
      t5310-pack-bitmaps: fix bogus 'pack-objects to file can use bitmap' test

Sebastian Kisela (2):
      git-instaweb: support Fedora/Red Hat apache module path
      git-instaweb: fix apache2 config with apache >= 2.4

Stefan Beller (87):
      repository: introduce parsed objects field
      object: add repository argument to create_object
      alloc: add repository argument to alloc_blob_node
      alloc: add repository argument to alloc_tree_node
      alloc: add repository argument to alloc_commit_node
      alloc: add repository argument to alloc_tag_node
      alloc: add repository argument to alloc_object_node
      alloc: add repository argument to alloc_report
      alloc: add repository argument to alloc_commit_index
      object: allow grow_object_hash to handle arbitrary repositories
      object: allow create_object to handle arbitrary repositories
      alloc: allow arbitrary repositories for alloc functions
      object-store: move object access functions to object-store.h
      shallow: add repository argument to set_alternate_shallow_file
      shallow: add repository argument to register_shallow
      shallow: add repository argument to check_shallow_file_for_update
      shallow: add repository argument to is_repository_shallow
      cache: convert get_graft_file to handle arbitrary repositories
      path.c: migrate global git_path_* to take a repository argument
      shallow: migrate shallow information into the object parser
      commit: allow prepare_commit_graft to handle arbitrary repositories
      commit: allow lookup_commit_graft to handle arbitrary repositories
      refs/packed-backend.c: close fd of empty file
      submodule--helper: plug mem leak in print_default_remote
      sequencer.c: plug leaks in do_pick_commit
      submodule: fix NULL correctness in renamed broken submodules
      t5526: test recursive submodules when fetching moved submodules
      submodule: unset core.worktree if no working tree is present
      submodule: ensure core.worktree is set after update
      submodule deinit: unset core.worktree
      submodule.c: report the submodule that an error occurs in
      sequencer.c: plug mem leak in git_sequencer_config
      .mailmap: merge different spellings of names
      object: add repository argument to parse_object
      object: add repository argument to lookup_object
      object: add repository argument to parse_object_buffer
      object: add repository argument to object_as_type
      blob: add repository argument to lookup_blob
      tree: add repository argument to lookup_tree
      commit: add repository argument to lookup_commit_reference_gently
      commit: add repository argument to lookup_commit_reference
      commit: add repository argument to lookup_commit
      commit: add repository argument to parse_commit_buffer
      commit: add repository argument to set_commit_buffer
      commit: add repository argument to get_cached_commit_buffer
      tag: add repository argument to lookup_tag
      tag: add repository argument to parse_tag_buffer
      tag: add repository argument to deref_tag
      object: allow object_as_type to handle arbitrary repositories
      object: allow lookup_object to handle arbitrary repositories
      blob: allow lookup_blob to handle arbitrary repositories
      tree: allow lookup_tree to handle arbitrary repositories
      commit: allow lookup_commit to handle arbitrary repositories
      tag: allow lookup_tag to handle arbitrary repositories
      tag: allow parse_tag_buffer to handle arbitrary repositories
      commit.c: allow parse_commit_buffer to handle arbitrary repositories
      commit-slabs: remove realloc counter outside of slab struct
      commit.c: migrate the commit buffer to the parsed object store
      commit.c: allow set_commit_buffer to handle arbitrary repositories
      commit.c: allow get_cached_commit_buffer to handle arbitrary repositories
      object.c: allow parse_object_buffer to handle arbitrary repositories
      object.c: allow parse_object to handle arbitrary repositories
      tag.c: allow deref_tag to handle arbitrary repositories
      commit.c: allow lookup_commit_reference_gently to handle arbitrary repositories
      commit.c: allow lookup_commit_reference to handle arbitrary repositories
      xdiff/xdiff.h: remove unused flags
      xdiff/xdiffi.c: remove unneeded function declarations
      t4015: avoid git as a pipe input
      diff.c: do not pass diff options as keydata to hashmap
      diff.c: adjust hash function signature to match hashmap expectation
      diff.c: add a blocks mode for moved code detection
      diff.c: decouple white space treatment from move detection algorithm
      diff.c: factor advance_or_nullify out of mark_color_as_moved
      diff.c: add white space mode to move detection that allows indent changes
      diff.c: offer config option to control ws handling in move detection
      xdiff/xhistogram: pass arguments directly to fall_back_to_classic_diff
      xdiff/xhistogram: factor out memory cleanup into free_index()
      xdiff/xhistogram: move index allocation into find_lcs
      Documentation/git-interpret-trailers: explain possible values
      xdiff/histogram: remove tail recursion
      t1300: document current behavior of setting options
      xdiff: reduce indent heuristic overhead
      config: fix case sensitive subsection names on writing
      git-config: document accidental multi-line setting in deprecated syntax
      git-submodule.sh: accept verbose flag in cmd_update to be non-quiet
      t7410: update to new style
      builtin/submodule--helper: remove stray new line

Taylor Blau (9):
      Documentation/config.txt: camel-case lineNumber for consistency
      grep.c: expose {,inverted} match column in match_line()
      grep.[ch]: extend grep_opt to allow showing matched column
      grep.c: display column number of first match
      builtin/grep.c: add '--column' option to 'git-grep(1)'
      grep.c: add configuration variables to show matched option
      contrib/git-jump/git-jump: jump to exact location
      grep.c: extract show_line_header()
      grep.c: teach 'git grep --only-matching'

Thomas Rast (1):
      range-diff: add tests

Tobias Klauser (1):
      git-rebase--preserve-merges: fix formatting of todo help message

Todd Zullinger (4):
      git-credential-netrc: minor whitespace cleanup in test script
      git-credential-netrc: make "all" default target of Makefile
      gitignore.txt: clarify default core.excludesfile path
      dir.c: fix typos in core.excludesfile comment

Ville Skyttä (1):
      Documentation: spelling and grammar fixes

Vladimir Parfinenko (1):
      rebase: fix documentation formatting

William Chargin (2):
      sha1-name.c: for ":/", find detached HEAD commits
      t: factor out FUNNYNAMES as shared lazy prereq

Xiaolong Ye (1):
      format-patch: clear UNINTERESTING flag before prepare_bases

brian m. carlson (21):
      send-email: add an auto option for transfer encoding
      send-email: accept long lines with suitable transfer encoding
      send-email: automatically determine transfer-encoding
      docs: correct RFC specifying email line length
      sequencer: pass absolute GIT_WORK_TREE to exec commands
      cache: update object ID functions for the_hash_algo
      tree-walk: replace hard-coded constants with the_hash_algo
      hex: switch to using the_hash_algo
      commit: express tree entry constants in terms of the_hash_algo
      strbuf: allocate space with GIT_MAX_HEXSZ
      sha1-name: use the_hash_algo when parsing object names
      refs/files-backend: use the_hash_algo for writing refs
      builtin/update-index: convert to using the_hash_algo
      builtin/update-index: simplify parsing of cacheinfo
      builtin/fmt-merge-msg: make hash independent
      builtin/merge: switch to use the_hash_algo
      builtin/merge-recursive: make hash independent
      diff: switch GIT_SHA1_HEXSZ to use the_hash_algo
      log-tree: switch GIT_SHA1_HEXSZ to the_hash_algo->hexsz
      sha1-file: convert constants to uses of the_hash_algo
      pretty: switch hard-coded constants to the_hash_algo

Ævar Arnfjörð Bjarmason (36):
      checkout tests: index should be clean after dwim checkout
      checkout.h: wrap the arguments to unique_tracking_name()
      checkout.c: introduce an *_INIT macro
      checkout.c: change "unique" member to "num_matches"
      checkout: pass the "num_matches" up to callers
      builtin/checkout.c: use "ret" variable for return
      checkout: add advice for ambiguous "checkout <branch>"
      checkout & worktree: introduce checkout.defaultRemote
      refspec: s/refspec_item_init/&_or_die/g
      refspec: add back a refspec_item_init() function
      doc hash-function-transition: note the lack of a changelog
      receive.fsck.<msg-id> tests: remove dead code
      config doc: don't describe *.fetchObjects twice
      config doc: unify the description of fsck.* and receive.fsck.*
      config doc: elaborate on what transfer.fsckObjects does
      config doc: elaborate on fetch.fsckObjects security
      transfer.fsckObjects tests: untangle confusing setup
      fetch: implement fetch.fsck.*
      fsck: test & document {fetch,receive}.fsck.* config fallback
      fsck: add stress tests for fsck.skipList
      fsck: test and document unknown fsck.<msg-id> values
      tests: make use of the test_must_be_empty function
      tests: make use of the test_must_be_empty function
      fetch tests: change "Tag" test tag to "testTag"
      push tests: remove redundant 'git push' invocation
      push tests: fix logic error in "push" test assertion
      push tests: add more testing for forced tag pushing
      push tests: assert re-pushing annotated tags
      negotiator: unknown fetch.negotiationAlgorithm should error out
      fetch doc: cross-link two new negotiation options
      sha1dc: update from upstream
      push: use PARSE_OPT_LITERAL_ARGHELP instead of unbalanced brackets
      fetch tests: correct a comment "remove it" -> "remove them"
      pull doc: fix a long-standing grammar error
      submodule: add more exhaustive up-path testing
      t2024: mark test using "checkout -p" with PERL prerequisite

Łukasz Stelmach (1):
      completion: complete remote names too


^ permalink raw reply	[relevance 1%]

* Re: What's cooking in git.git (Aug 2018, #04; Fri, 17)
  2018-08-17 22:44  1% What's cooking in git.git (Aug 2018, #04; Fri, 17) Junio C Hamano
@ 2018-08-20 18:11  0% ` Stefan Beller
  0 siblings, 0 replies; 200+ results
From: Stefan Beller @ 2018-08-20 18:11 UTC (permalink / raw)
  To: Junio C Hamano, Duy Nguyen, Antonio Ospite; +Cc: git

On Fri, Aug 17, 2018 at 3:44 PM Junio C Hamano <gitster@pobox.com> wrote:
>
> Usually, I refrain from merging larger topics in 'next' down to
> 'master' when we get close to -rc0, but I am wondering if it is
> better to merge all of them to 'master', even the ones on the larger
> and possibly undercooked side, expecting that we collectively spend
> effort on hunting and fixing bugs in them during the pre-release
> freeze period.  If we were to go that route, I'd want everybody's
> buy-in and I'll promise to ignore any shiny new toys that appear on
> list that are not regression fixes to topics merged to 'master'
> since the end of the previous cycle to make sure people are not
> distracted.

Speaking of releases, linux for example has some releases that are
more stable than others, as most distros pick the same release for
their 'stable' release, whereas the regular and new releases are just
off of the latest release of linux.

Would a similar model be an interesting thought to entertain?

I guess I could buy into a few weeks of bug fixing.

> * nd/config-blame-sort (2018-08-06) 1 commit
[...]
> * nd/no-extern (2018-08-03) 12 commits
[...]

Thanks Duy for these two series!

I would have expected the no-extern series to
have a bit of merge conflicts similar to  sb/object-store-lookup
as it touches a lot of headers, but so far this looks like smooth sailing?

> * sb/submodule-cleanup (2018-08-16) 2 commits
>   (merged to 'next' on 2018-08-17 at ca9d8aaef4)
>  + builtin/submodule--helper: remove stray new line
>  + t7410: update to new style
>
>  A few preliminary minor clean-ups in the area around submodules.
>
>  Will merge to 'master'.

Oh, that is the prologue of
https://public-inbox.org/git/20180816023100.161626-1-sbeller@google.com/
which I will resend to build on top. Given your question to hunt more bugs,
I'd delay its resend until after the release.

>
> * ao/submodule-wo-gitmodules-checked-out (2018-08-14) 7 commits
>  - submodule: support reading .gitmodules even when it's not checked out
>  - t7506: clean up .gitmodules properly before setting up new scenario
>  - submodule: use the 'submodule--helper config' command
>  - submodule--helper: add a new 'config' subcommand
>  - t7411: be nicer to future tests and really clean things up
>  - submodule: factor out a config_set_in_gitmodules_file_gently function
>  - submodule: add a print_config_from_gitmodules() helper
>
>  The submodule support has been updated to read from the blob at
>  HEAD:.gitmodules when the .gitmodules file is missing from the
>  working tree.
>
>  I find the design a bit iffy in that our usual "missing in the
>  working tree?  let's use the latest blob" fallback is to take it
>  from the index, not from the HEAD.

I am not sure; why does it feel iffy?

> * bw/submodule-name-to-dir (2018-08-10) 2 commits
>  In modern repository layout, the real body of a cloned submodule
>  repository is held in .git/modules/ of the superproject, indexed by
>  the submodule name.  URLencode the submodule name before computing
>  the name of the directory to make sure they form a flat namespace.
>
>  Will merge to 'next'.

Cool! Is the discussion on top of it still going whether to use a
new config for special cases or how we distinguish between a/b/
and a%2fb as submodule names?

> * md/filter-trees (2018-08-16) 6 commits
>  - list-objects-filter: implement filter tree:0
>  - revision: mark non-user-given objects instead
>  - rev-list: handle missing tree objects properly
>  - list-objects: always parse trees gently
>  - list-objects: refactor to process_tree_contents
>  - list-objects: store common func args in struct
>
>  The "rev-list --filter" feature learned to exclude all trees via
>  "tree:0" filter.

I gave this a read and think it is good to go.

> * sb/config-write-fix (2018-08-08) 3 commits
>   (merged to 'next' on 2018-08-17 at 7d9c7ce81f)
>  + git-config: document accidental multi-line setting in deprecated syntax
>  + config: fix case sensitive subsection names on writing
>  + t1300: document current behavior of setting options
>
>  Recent update to "git config" broke updating variable in a
>  subsection, which has been corrected.
>
>  Will merge to 'master'.

Thanks!

>
>
> * sb/range-diff-colors (2018-08-14) 8 commits
>  - diff.c: rewrite emit_line_0 more understandably
>  - diff.c: omit check for line prefix in emit_line_0
>  - diff: use emit_line_0 once per line
>  - diff.c: add set_sign to emit_line_0
>  - diff.c: reorder arguments for emit_line_ws_markup
>  - diff.c: simplify caller of emit_line_0
>  - t3206: add color test for range-diff --dual-color
>  - test_decode_color: understand FAINT and ITALIC
>  (this branch uses js/range-diff; is tangled with es/format-patch-rangediff.)
>
>  The color output support for recently introduced "range-diff"
>  command got tweaked a bit.

No, this series doesn't tweak the colored range-diff.
This might be:

  Add more test coverage to colored range-diff and
  refactor the diff machinery to be more readable.
  The test coverage proves no user visible changes
  are made.

The tweaking comes in on top of this via
https://public-inbox.org/git/20180817204354.108625-1-sbeller@google.com/

Thanks,
Stefan

^ permalink raw reply	[relevance 0%]

* What's cooking in git.git (Aug 2018, #04; Fri, 17)
@ 2018-08-17 22:44  1% Junio C Hamano
  2018-08-20 18:11  0% ` Stefan Beller
  0 siblings, 1 reply; 200+ results
From: Junio C Hamano @ 2018-08-17 22:44 UTC (permalink / raw)
  To: git

Here are the topics that have been cooking.  Commits prefixed with
'-' are only in 'pu' (proposed updates) while commits prefixed with
'+' are in 'next'.  The ones marked with '.' do not appear in any of
the integration branches, but I am still holding onto them.

Quite a many topics have graduated to 'master', and also a handful
of topics have entered 'next'.  I am planning to tag -rc0 over the
weekend, and some topics that are in 'next' and marked for 'master'
in this issue of "What's cooking" report may be reclassified to cook
in 'next' during the pre-release period when that happens.

Usually, I refrain from merging larger topics in 'next' down to
'master' when we get close to -rc0, but I am wondering if it is
better to merge all of them to 'master', even the ones on the larger
and possibly undercooked side, expecting that we collectively spend
effort on hunting and fixing bugs in them during the pre-release
freeze period.  If we were to go that route, I'd want everybody's
buy-in and I'll promise to ignore any shiny new toys that appear on
list that are not regression fixes to topics merged to 'master'
since the end of the previous cycle to make sure people are not
distracted.

You can find the changes described here in the integration branches
of the repositories listed at

    http://git-blame.blogspot.com/p/git-public-repositories.html

--------------------------------------------------
[Graduated to "master"]

* ab/fetch-nego (2018-08-01) 3 commits
  (merged to 'next' on 2018-08-08 at 87662bb344)
 + fetch doc: cross-link two new negotiation options
 + negotiator: unknown fetch.negotiationAlgorithm should error out
 + Merge branch 'jt/fetch-nego-tip' into ab/fetch-nego

 Update to a few other topics around 'git fetch'.


* ab/fsck-transfer-updates (2018-07-27) 10 commits
  (merged to 'next' on 2018-08-08 at d92085269f)
 + fsck: test and document unknown fsck.<msg-id> values
 + fsck: add stress tests for fsck.skipList
 + fsck: test & document {fetch,receive}.fsck.* config fallback
 + fetch: implement fetch.fsck.*
 + transfer.fsckObjects tests: untangle confusing setup
 + config doc: elaborate on fetch.fsckObjects security
 + config doc: elaborate on what transfer.fsckObjects does
 + config doc: unify the description of fsck.* and receive.fsck.*
 + config doc: don't describe *.fetchObjects twice
 + receive.fsck.<msg-id> tests: remove dead code

 The test performed at the receiving end of "git push" to prevent
 bad objects from entering repository can be customized via
 receive.fsck.* configuration variables; we now have gained a
 counterpart to do the same on the "git fetch" side, with
 fetch.fsck.* configuration variables.


* ab/sha1dc (2018-08-02) 1 commit
  (merged to 'next' on 2018-08-08 at 920c190941)
 + sha1dc: update from upstream

 AIX portability update for the SHA1DC hash, imported from upstream.


* ab/test-must-be-empty (2018-07-30) 1 commit
  (merged to 'next' on 2018-08-08 at 06599ebd1f)
 + tests: make use of the test_must_be_empty function

 Test updates.


* ar/t4150-am-scissors-test-fix (2018-08-06) 1 commit
  (merged to 'next' on 2018-08-08 at e639183205)
 + t4150: fix broken test for am --scissors

 Test fix.


* en/abort-df-conflict-fixes (2018-07-31) 2 commits
  (merged to 'next' on 2018-08-08 at a19cad0bb7)
 + read-cache: fix directory/file conflict handling in read_index_unmerged()
 + t1015: demonstrate directory/file conflict recovery failures

 "git merge --abort" etc. did not clean things up properly when
 there were conflicted entries in the index in certain order that
 are involved in D/F conflicts.  This has been corrected.


* en/t3031-title-fix (2018-08-06) 1 commit
  (merged to 'next' on 2018-08-08 at 3913b03884)
 + t3031: update test description to mention desired behavior

 Test fix.


* es/rebase-i-author-script-fix (2018-07-31) 4 commits
  (merged to 'next' on 2018-08-08 at 6b34261b72)
 + sequencer: don't die() on bogus user-edited timestamp
 + sequencer: fix "rebase -i --root" corrupting author header timestamp
 + sequencer: fix "rebase -i --root" corrupting author header timezone
 + sequencer: fix "rebase -i --root" corrupting author header
 (this branch is used by pw/rebase-i-author-script-fix.)

 The "author-script" file "git rebase -i" creates got broken when
 we started to move the command away from shell script, which is
 getting fixed now.


* es/want-color-fd-defensive (2018-08-03) 1 commit
  (merged to 'next' on 2018-08-08 at a11d90d26f)
 + color: protect against out-of-bounds reads and writes

 Futureproofing a helper function that can easily be misused.


* hn/config-in-code-comment (2018-08-06) 1 commit
  (merged to 'next' on 2018-08-08 at 1fae946a0f)
 + config: document git config getter return value

 Header update.


* jk/diff-rendered-docs (2018-08-06) 1 commit
  (merged to 'next' on 2018-08-08 at fe6e1b4dbe)
 + add a script to diff rendered documentation

 The end result of documentation update has been made to be
 inspected more easily to help developers.


* jk/merge-subtree-heuristics (2018-08-02) 1 commit
  (merged to 'next' on 2018-08-08 at 5126c2d717)
 + score_trees(): fix iteration over trees with missing entries

 The automatic tree-matching in "git merge -s subtree" was broken 5
 years ago and nobody has noticed since then, which is now fixed.


* js/pull-rebase-type-shorthand (2018-08-06) 1 commit
  (merged to 'next' on 2018-08-08 at 9213756b36)
 + pull --rebase=<type>: allow single-letter abbreviations for the type

 "git pull --rebase=interactive" learned "i" as a short-hand for
 "interactive".


* jt/refspec-dwim-precedence-fix (2018-08-02) 1 commit
  (merged to 'next' on 2018-08-08 at 34d0484d3a)
 + remote: make refspec follow the same disambiguation rule as local refs

 "git fetch $there refs/heads/s" ought to fetch the tip of the
 branch 's', but when "refs/heads/refs/heads/s", i.e. a branch whose
 name is "refs/heads/s" exists at the same time, fetched that one
 instead by mistake.  This has been corrected to honor the usual
 disambiguation rules for abbreviated refnames.


* mk/http-backend-content-length (2018-07-30) 4 commits
  (merged to 'next' on 2018-08-08 at 0091062ec4)
 + t5562: avoid non-portable "export FOO=bar" construct
 + http-backend: respect CONTENT_LENGTH for receive-pack
 + http-backend: respect CONTENT_LENGTH as specified by rfc3875
 + http-backend: cleanup writing to child process

 The http-backend (used for smart-http transport) used to slurp the
 whole input until EOF, without paying attention to CONTENT_LENGTH
 that is supplied in the environment and instead expecting the Web
 server to close the input stream.  This has been fixed.


* nd/complete-config-vars (2018-08-06) 1 commit
  (merged to 'next' on 2018-08-08 at ffc8e1a3cd)
 + Makefile: add missing dependency for command-list.h

 Build fix.


* nd/config-blame-sort (2018-08-06) 1 commit
  (merged to 'next' on 2018-08-08 at 34ebb9888f)
 + config.txt: reorder blame stuff to keep config keys sorted

 Doc fix.


* nd/no-extern (2018-08-03) 12 commits
  (merged to 'next' on 2018-08-08 at bcce75766b)
 + submodule.h: drop extern from function declaration
 + revision.h: drop extern from function declaration
 + repository.h: drop extern from function declaration
 + rerere.h: drop extern from function declaration
 + line-range.h: drop extern from function declaration
 + diff.h: remove extern from function declaration
 + diffcore.h: drop extern from function declaration
 + convert.h: drop 'extern' from function declaration
 + cache-tree.h: drop extern from function declaration
 + blame.h: drop extern on func declaration
 + attr.h: drop extern from function declaration
 + apply.h: drop extern on func declaration
 (this branch is used by nd/no-the-index.)

 Noiseword "extern" has been removed from function decls in the
 header files.


* ot/ref-filter-object-info (2018-07-17) 5 commits
  (merged to 'next' on 2018-08-08 at 9ed619941b)
 + ref-filter: use oid_object_info() to get object
 + ref-filter: merge get_obj and get_object
 + ref-filter: initialize eaten variable
 + ref-filter: fill empty fields with empty values
 + ref-filter: add info_source to valid_atom

 A few atoms like %(objecttype) and %(objectsize) in the format
 specifier of "for-each-ref --format=<format>" can be filled without
 getting the full contents of the object, but just with the object
 header.  These cases have been optimized by calling
 oid_object_info() API (instead of reading and inspecting the data).


* rs/parse-opt-lithelp (2018-08-03) 7 commits
  (merged to 'next' on 2018-08-08 at 3a4e0142fe)
 + parse-options: automatically infer PARSE_OPT_LITERAL_ARGHELP
 + shortlog: correct option help for -w
 + send-pack: specify --force-with-lease argument help explicitly
 + pack-objects: specify --index-version argument help explicitly
 + difftool: remove angular brackets from argument help
 + add, update-index: fix --chmod argument help
 + push: use PARSE_OPT_LITERAL_ARGHELP instead of unbalanced brackets

 The parse-options machinery learned to refrain from enclosing
 placeholder string inside a "<bra" and "ket>" pair automatically
 without PARSE_OPT_LITERAL_ARGHELP.  Existing help text for option
 arguments that are not formatted correctly have been identified and
 fixed.


* sb/indent-heuristic-optim (2018-08-01) 1 commit
  (merged to 'next' on 2018-08-08 at 539dcc967a)
 + xdiff: reduce indent heuristic overhead

 "git diff --indent-heuristic" had a bad corner case performance.

--------------------------------------------------
[New Topics]

* ep/worktree-quiet-option (2018-08-17) 1 commit
 - worktree: add --quiet option

 "git worktree" command learned "--quiet" option to make it less
 verbose.

 Will merge to 'next'.


* nd/cherry-pick-quit-fix (2018-08-16) 1 commit
  (merged to 'next' on 2018-08-17 at b270179855)
 + cherry-pick: fix --quit not deleting CHERRY_PICK_HEAD

 "git cherry-pick --quit" failed to remove CHERRY_PICK_HEAD even
 though we won't be in a cherry-pick session after it returns, which
 has been corrected.

 Will merge to 'master'.


* nd/config-core-checkstat-doc (2018-08-17) 1 commit
 - config.txt: clarify core.checkStat

 The meaning of the possible values the "core.checkStat"
 configuration variable can take were not adequately documented,
 which has been fixed.

 Will merge to 'next'.


* pw/rebase-i-merge-segv-fix (2018-08-16) 2 commits
  (merged to 'next' on 2018-08-17 at c8823e4511)
 + rebase -i: fix SIGSEGV when 'merge <branch>' fails
 + t3430: add conflicting commit

 "git rebase -i", when a 'merge <branch>' insn in its todo list
 fails, segfaulted, which has been (minimally) corrected.

 Will merge to 'master'.


* sb/submodule-cleanup (2018-08-16) 2 commits
  (merged to 'next' on 2018-08-17 at ca9d8aaef4)
 + builtin/submodule--helper: remove stray new line
 + t7410: update to new style

 A few preliminary minor clean-ups in the area around submodules.

 Will merge to 'master'.


* sm/branch-sort-config (2018-08-16) 1 commit
 - branch: support configuring --sort via .gitconfig

 "git branch --list" learned to take the default sort order from the
 'branch.sort' configuration variable, just like "git tag --list"
 pays attention to 'tag.sort'.

 Will merge to 'next'.


* ab/unconditional-free-and-null (2018-08-17) 1 commit
 - refactor various if (x) FREE_AND_NULL(x) to just FREE_AND_NULL(x)

 Code clean-up.

 Will merge to 'next'.

--------------------------------------------------
[Stalled]

* sl/commit-dry-run-with-short-output-fix (2018-07-30) 4 commits
 . commit: fix exit code when doing a dry run
 . wt-status: teach wt_status_collect about merges in progress
 . wt-status: rename commitable to committable
 . t7501: add coverage for flags which imply dry runs

 "git commit --dry-run" gave a correct exit status even during a
 conflict resolution toward a merge, but it did not with the
 "--short" option, which has been corrected.

 Seems to break 7512, 3404 and 7060 in 'pu'.


* ma/wrapped-info (2018-05-28) 2 commits
 - usage: prefix all lines in `vreportf()`, not just the first
 - usage: extract `prefix_suffix_lines()` from `advise()`

 An attempt to help making multi-line messages fed to warning(),
 error(), and friends more easily translatable.

 Will discard and wait for a cleaned-up rewrite.
 cf. <20180529213957.GF7964@sigill.intra.peff.net>


* hn/bisect-first-parent (2018-04-21) 1 commit
 - bisect: create 'bisect_flags' parameter in find_bisection()

 Preliminary code update to allow passing more flags down the
 bisection codepath in the future.

 We do not add random code that does not have real users to our
 codebase, so let's have it wait until such a real code materializes
 before too long.


* av/fsmonitor-updates (2018-01-04) 6 commits
 - fsmonitor: use fsmonitor data in `git diff`
 - fsmonitor: remove debugging lines from t/t7519-status-fsmonitor.sh
 - fsmonitor: make output of test-dump-fsmonitor more concise
 - fsmonitor: update helper tool, now that flags are filled later
 - fsmonitor: stop inline'ing mark_fsmonitor_valid / _invalid
 - dir.c: update comments to match argument name

 Code clean-up on fsmonitor integration, plus optional utilization
 of the fsmonitor data in diff-files.

 Waiting for an update.
 cf. <alpine.DEB.2.21.1.1801042335130.32@MININT-6BKU6QN.europe.corp.microsoft.com>


* pb/bisect-helper-2 (2018-07-23) 8 commits
 - t6030: make various test to pass GETTEXT_POISON tests
 - bisect--helper: `bisect_start` shell function partially in C
 - bisect--helper: `get_terms` & `bisect_terms` shell function in C
 - bisect--helper: `bisect_next_check` shell function in C
 - bisect--helper: `check_and_set_terms` shell function in C
 - wrapper: move is_empty_file() and rename it as is_empty_or_missing_file()
 - bisect--helper: `bisect_write` shell function in C
 - bisect--helper: `bisect_reset` shell function in C

 Expecting a reroll.
 cf. <0102015f5e5ee171-f30f4868-886f-47a1-a4e4-b4936afc545d-000000@eu-west-1.amazonses.com>

 I just rebased the topic to a newer base as it did not build
 standalone with the base I originally queued the topic on, but
 otherwise there is no update to address any of the review comments
 in the thread above---we are still waiting for a reroll.


* jk/drop-ancient-curl (2017-08-09) 5 commits
 - http: #error on too-old curl
 - curl: remove ifdef'd code never used with curl >=7.19.4
 - http: drop support for curl < 7.19.4
 - http: drop support for curl < 7.16.0
 - http: drop support for curl < 7.11.1

 Some code in http.c that has bitrot is being removed.

 Expecting a reroll.


* mk/use-size-t-in-zlib (2017-08-10) 1 commit
 . zlib.c: use size_t for size

 The wrapper to call into zlib followed our long tradition to use
 "unsigned long" for sizes of regions in memory, which have been
 updated to use "size_t".

 Needs resurrecting by making sure the fix is good and still applies
 (or adjusted to today's codebase).

--------------------------------------------------
[Cooking]

* bp/checkout-new-branch-optim (2018-08-16) 1 commit
 - checkout: optimize "git checkout -b <new_branch>"

 "git checkout -b newbranch [HEAD]" should not have to do as much as
 checking out a commit different from HEAD.  An attempt is made to
 optimize this special case.

 So... what is the status of this thing?  Is the other "optimize
 unpack-trees" effort turning out to be a safer and less hacky way
 to achieve similar gain and this no longer is needed?


* en/t7406-fixes (2018-08-08) 5 commits
  (merged to 'next' on 2018-08-15 at c6a740d828)
 + t7406: avoid using test_must_fail for commands other than git
 + t7406: prefer test_* helper functions to test -[feds]
 + t7406: avoid having git commands upstream of a pipe
 + t7406: simplify by using diff --name-only instead of diff --raw
 + t7406: fix call that was failing for the wrong reason

 Test fixes.

 Will merge to 'master'.


* en/update-index-doc (2018-08-08) 1 commit
  (merged to 'next' on 2018-08-15 at 3ee0ae14dc)
 + git-update-index.txt: reword possibly confusing example

 Doc update.

 Will merge to 'master'.


* jc/update-index-doc (2018-08-08) 1 commit
  (merged to 'next' on 2018-08-15 at 055994ccca)
 + update-index: there no longer is `apply --index-info`

 Doc update.

 Will merge to 'master'.


* js/typofixes (2018-08-08) 2 commits
  (merged to 'next' on 2018-08-15 at ce3932254a)
 + remote-curl: remove spurious period
 + git-compat-util.h: fix typo

 Comment update.

 Will merge to 'master'.


* jt/repack-promisor-packs (2018-08-09) 2 commits
  (merged to 'next' on 2018-08-17 at 6869b53a69)
 + repack: repack promisor objects if -a or -A is set
 + repack: refactor setup of pack-objects cmd

 After a partial clone, repeated fetches from promisor remote would
 have accumulated many packfiles marked with .promisor bit without
 getting them coalesced into fewer packfiles, hurting performance.
 "git repack" now learned to repack them.

 Will merge to 'master'.


* sk/instaweb-rh-update (2018-08-08) 2 commits
  (merged to 'next' on 2018-08-15 at ce5f1115e9)
 + git-instaweb: fix apache2 config with apache >= 2.4
 + git-instaweb: support Fedora/Red Hat apache module path

 "git instaweb" has been adjusted to run better with newer Apache on
 RedHat based distros.

 Will merge to 'master'.


* ab/submodule-relative-url-tests (2018-08-14) 1 commit
  (merged to 'next' on 2018-08-17 at 17b28d8262)
 + submodule: add more exhaustive up-path testing

 Test updates.

 Will merge to 'master'.


* ao/submodule-wo-gitmodules-checked-out (2018-08-14) 7 commits
 - submodule: support reading .gitmodules even when it's not checked out
 - t7506: clean up .gitmodules properly before setting up new scenario
 - submodule: use the 'submodule--helper config' command
 - submodule--helper: add a new 'config' subcommand
 - t7411: be nicer to future tests and really clean things up
 - submodule: factor out a config_set_in_gitmodules_file_gently function
 - submodule: add a print_config_from_gitmodules() helper

 The submodule support has been updated to read from the blob at
 HEAD:.gitmodules when the .gitmodules file is missing from the
 working tree.

 I find the design a bit iffy in that our usual "missing in the
 working tree?  let's use the latest blob" fallback is to take it
 from the index, not from the HEAD.


* bw/submodule-name-to-dir (2018-08-10) 2 commits
 - submodule: munge paths to submodule git directories
 - submodule: create helper to build paths to submodule gitdirs

 In modern repository layout, the real body of a cloned submodule
 repository is held in .git/modules/ of the superproject, indexed by
 the submodule name.  URLencode the submodule name before computing
 the name of the directory to make sure they form a flat namespace.

 Will merge to 'next'.


* cc/delta-islands (2018-08-16) 7 commits
 - pack-objects: move 'layer' into 'struct packing_data'
 - pack-objects: move tree_depth into 'struct packing_data'
 - t5320: tests for delta islands
 - repack: add delta-islands support
 - pack-objects: add delta-islands support
 - pack-objects: refactor code into compute_layer_order()
 - Add delta-islands.{c,h}

 Lift code from GitHub to restrict delta computation so that an
 object that exists in one fork is not made into a delta against
 another object that does not appear in the same forked repository.

 What's the doneness of this topic?


* ds/commit-graph-fsck (2018-08-13) 1 commit
  (merged to 'next' on 2018-08-15 at a2f82d3cbd)
 + t5318: use 'test_cmp_bin' to compare commit-graph files

 Test fix.

 Will merge to 'master'.


* en/incl-forward-decl (2018-08-15) 6 commits
  (merged to 'next' on 2018-08-17 at 04fc9c11bb)
 + Remove forward declaration of an enum
 + compat/precompose_utf8.h: use more common include guard style
 + urlmatch.h: fix include guard
 + Move definition of enum branch_track from cache.h to branch.h
 + alloc: make allocate_alloc_state and clear_alloc_state more consistent
 + Add missing includes and forward declarations

 Code hygiene improvement for the header files.

 Will merge to 'master'.


* es/chain-lint-more (2018-08-13) 6 commits
  (merged to 'next' on 2018-08-15 at bb5150ee96)
 + chainlint: add test of pathological case which triggered false positive
 + chainlint: recognize multi-line quoted strings more robustly
 + chainlint: let here-doc and multi-line string commence on same line
 + chainlint: recognize multi-line $(...) when command cuddled with "$("
 + chainlint: match 'quoted' here-doc tags
 + chainlint: match arbitrary here-docs tags rather than hard-coded names

 Improve built-in facility to catch broken &&-chain in the tests.

 Will merge to 'master'.


* jc/gpg-status (2018-08-09) 1 commit
  (merged to 'next' on 2018-08-15 at 824781761a)
 + gpg-interface: propagate exit status from gpg back to the callers

 Will merge to 'master'.


* jh/partial-clone-doc (2018-08-15) 1 commit
  (merged to 'next' on 2018-08-15 at cf09e8be6a)
 + partial-clone: render design doc using asciidoc

 Doc updates.

 Will merge to 'master'.


* jk/for-each-object-iteration (2018-08-14) 11 commits
  (merged to 'next' on 2018-08-15 at e2558810ff)
 + for_each_*_object: move declarations to object-store.h
 + cat-file: use a single strbuf for all output
 + cat-file: split batch "buf" into two variables
 + cat-file: use oidset check-and-insert
 + cat-file: support "unordered" output for --batch-all-objects
 + cat-file: rename batch_{loose,packed}_object callbacks
 + t1006: test cat-file --batch-all-objects with duplicates
 + for_each_packed_object: support iterating in pack-order
 + for_each_*_object: give more comprehensive docstrings
 + for_each_*_object: take flag arguments as enum
 + for_each_*_object: store flag definitions in a single location

 The API to iterate over all objects learned to optionally list
 objects in the order they appear in packfiles, which helps locality
 of access if the caller accesses these objects while as objects are
 enumerated.

 Will merge to 'master'.


* js/chain-lint-attrfix (2018-08-15) 1 commit
  (merged to 'next' on 2018-08-15 at e9ad19a848)
 + chainlint: fix for core.autocrlf=true

 Test fix.

 Will merge to 'master'.


* js/mingw-o-append (2018-08-13) 1 commit
  (merged to 'next' on 2018-08-15 at 284527a0fb)
 + mingw: enable atomic O_APPEND

 Among the three codepaths we use O_APPEND to open a file for
 appending, one used for writing GIT_TRACE output requires O_APPEND
 implementation that behaves sensibly when multiple processes are
 writing to the same file.  POSIX emulation used in the Windows port
 has been updated to improve in this area.

 Will merge to 'master'.


* jt/commit-graph-per-object-store (2018-08-13) 1 commit
  (merged to 'next' on 2018-08-15 at 5d6db738d8)
 + t5318: avoid unnecessary command substitutions

 Test update.

 Will merge to 'master'.


* jt/fetch-negotiator-skipping (2018-08-10) 1 commit
  (merged to 'next' on 2018-08-15 at 3cf8fa32f5)
 + t5552: suppress upload-pack trace output

 Test fix.

 Will merge to 'master'.


* md/filter-trees (2018-08-16) 6 commits
 - list-objects-filter: implement filter tree:0
 - revision: mark non-user-given objects instead
 - rev-list: handle missing tree objects properly
 - list-objects: always parse trees gently
 - list-objects: refactor to process_tree_contents
 - list-objects: store common func args in struct

 The "rev-list --filter" feature learned to exclude all trees via
 "tree:0" filter.


* nd/no-the-index (2018-08-13) 24 commits
  (merged to 'next' on 2018-08-15 at 41e53dc53b)
 + blame.c: remove implicit dependency on the_index
 + apply.c: remove implicit dependency on the_index
 + apply.c: make init_apply_state() take a struct repository
 + apply.c: pass struct apply_state to more functions
 + resolve-undo.c: use the right index instead of the_index
 + archive-*.c: use the right repository
 + archive.c: avoid access to the_index
 + grep: use the right index instead of the_index
 + attr: remove index from git_attr_set_direction()
 + entry.c: use the right index instead of the_index
 + submodule.c: use the right index instead of the_index
 + pathspec.c: use the right index instead of the_index
 + unpack-trees: avoid the_index in verify_absent()
 + unpack-trees: convert clear_ce_flags* to avoid the_index
 + unpack-trees: don't shadow global var the_index
 + unpack-trees: add a note about path invalidation
 + unpack-trees: remove 'extern' on function declaration
 + ls-files: correct index argument to get_convert_attr_ascii()
 + preload-index.c: use the right index instead of the_index
 + dir.c: remove an implicit dependency on the_index in pathspec code
 + convert.c: remove an implicit dependency on the_index
 + attr: remove an implicit dependency on the_index
 + cache-tree: wrap the_index based wrappers with #ifdef
 + diff.c: move read_index() code back to the caller

 The more library-ish parts of the codebase learned to work on the
 in-core index-state instance that is passed in by their callers,
 instead of always working on the singleton "the_index" instance.

 Will merge to 'master'.


* ng/mergetool-lose-final-prompt (2018-08-13) 1 commit
  (merged to 'next' on 2018-08-15 at f8f7ac365b)
 + mergetool: don't suggest to continue after last file

 "git mergetool" stopped and gave an extra prompt to continue after
 the last path has been handled, which did not make much sense.

 Will merge to 'master'.


* ng/status-i-short-for-ignored (2018-08-09) 1 commit
 - status: -i shorthand for --ignored command line option


* pk/rebase-in-c-2-basic (2018-08-10) 11 commits
 - builtin rebase: support `git rebase <upstream> <switch-to>`
 - builtin rebase: only store fully-qualified refs in `options.head_name`
 - builtin rebase: start a new rebase only if none is in progress
 - builtin rebase: support --force-rebase
 - builtin rebase: try to fast forward when possible
 - builtin rebase: require a clean worktree
 - builtin rebase: support the `verbose` and `diffstat` options
 - builtin rebase: support --quiet
 - builtin rebase: handle the pre-rebase hook (and add --no-verify)
 - builtin rebase: support `git rebase --onto A...B`
 - builtin rebase: support --onto
 (this branch is used by pk/rebase-in-c-3-acts, pk/rebase-in-c-4-opts, pk/rebase-in-c-5-test and pk/rebase-in-c-6-final; uses pk/rebase-in-c.)


* pk/rebase-in-c-3-acts (2018-08-10) 7 commits
 - builtin rebase: stop if `git am` is in progress
 - builtin rebase: actions require a rebase in progress
 - builtin rebase: support --edit-todo and --show-current-patch
 - builtin rebase: support --quit
 - builtin rebase: support --abort
 - builtin rebase: support --skip
 - builtin rebase: support --continue
 (this branch is used by pk/rebase-in-c-4-opts, pk/rebase-in-c-5-test and pk/rebase-in-c-6-final; uses pk/rebase-in-c and pk/rebase-in-c-2-basic.)


* pk/rebase-in-c-4-opts (2018-08-10) 18 commits
 - builtin rebase: support --root
 - builtin rebase: add support for custom merge strategies
 - builtin rebase: support `fork-point` option
 - merge-base --fork-point: extract libified function
 - builtin rebase: support --rebase-merges[=[no-]rebase-cousins]
 - builtin rebase: support `--allow-empty-message` option
 - builtin rebase: support `--exec`
 - builtin rebase: support `--autostash` option
 - builtin rebase: support `-C` and `--whitespace=<type>`
 - builtin rebase: support `--gpg-sign` option
 - builtin rebase: support `--autosquash`
 - builtin rebase: support `keep-empty` option
 - builtin rebase: support `ignore-date` option
 - builtin rebase: support `ignore-whitespace` option
 - builtin rebase: support --committer-date-is-author-date
 - builtin rebase: support --rerere-autoupdate
 - builtin rebase: support --signoff
 - builtin rebase: allow selecting the rebase "backend"
 (this branch is used by pk/rebase-in-c-5-test and pk/rebase-in-c-6-final; uses pk/rebase-in-c, pk/rebase-in-c-2-basic and pk/rebase-in-c-3-acts.)


* pk/rebase-in-c-5-test (2018-08-10) 6 commits
 - builtin rebase: error out on incompatible option/mode combinations
 - builtin rebase: use no-op editor when interactive is "implied"
 - builtin rebase: show progress when connected to a terminal
 - builtin rebase: fast-forward to onto if it is a proper descendant
 - builtin rebase: optionally pass custom reflogs to reset_head()
 - builtin rebase: optionally auto-detect the upstream
 (this branch is used by pk/rebase-in-c-6-final; uses pk/rebase-in-c, pk/rebase-in-c-2-basic, pk/rebase-in-c-3-acts and pk/rebase-in-c-4-opts.)


* pk/rebase-in-c-6-final (2018-08-10) 1 commit
 - rebase: default to using the builtin rebase
 (this branch uses pk/rebase-in-c, pk/rebase-in-c-2-basic, pk/rebase-in-c-3-acts, pk/rebase-in-c-4-opts and pk/rebase-in-c-5-test.)

 With "rebase -i" machinery being rewritten to C, with a different
 interface between "rebase" proper and its backends, this and the
 other topics need a bit more work to play with each other better.


* ps/stash-in-c (2018-08-08) 26 commits
 - stash: replace all "git apply" child processes with API calls
 - stash: replace all `write-tree` child processes with API calls
 - stash: optimize `get_untracked_files()` and `check_changes()`
 - stash: convert `stash--helper.c` into `stash.c`
 - stash: convert save to builtin
 - stash: replace spawning `git ls-files` child process
 - stash: add tests for `git stash push -q`
 - stash: make push to be quiet
 - stash: convert push to builtin
 - stash: avoid spawning a "diff-index" process
 - stash: replace spawning a "read-tree" process
 - stash: convert create to builtin
 - stash: convert store to builtin
 - stash: update `git stash show` documentation
 - stash: refactor `show_stash()` to use the diff API
 - stash: change `git stash show` usage text and documentation
 - stash: convert show to builtin
 - stash: implement the "list" command in the builtin
 - stash: convert pop to builtin
 - stash: convert branch to builtin
 - stash: convert drop and clear to builtin
 - stash: convert apply to builtin
 - stash: renamed test cases to be more descriptive
 - stash: update test cases conform to coding guidelines
 - stash: improve option parsing test coverage
 - sha1-name.c: added 'get_oidf', which acts like 'get_oid'


* pw/rebase-i-squash-number-fix (2018-08-15) 1 commit
  (merged to 'next' on 2018-08-17 at ac54dfa51a)
 + rebase -i: fix numbering in squash message

 When "git rebase -i" is told to squash two or more commits into
 one, it labeled the log message for each commit with its number.
 It correctly called the first one "1st commit", but the next one
 was "commit #1", which was off-by-one.  This has been corrected.

 Will merge to 'master'.


* sb/pull-rebase-submodule (2018-08-14) 1 commit
  (merged to 'next' on 2018-08-15 at 07c7b55cc9)
 + git-submodule.sh: accept verbose flag in cmd_update to be non-quiet

 "git pull --rebase -v" in a repository with a submodule barfed as
 an intermediate process did not understand what "-v(erbose)" flag
 meant, which has been fixed.

 Will merge to 'master'.


* sg/t5310-empty-input-fix (2018-08-14) 1 commit
  (merged to 'next' on 2018-08-15 at c3c03973a0)
 + t5310-pack-bitmaps: fix bogus 'pack-objects to file can use bitmap' test

 Test fix.

 Will merge to 'master'.


* js/rebase-merges-exec-fix (2018-08-09) 2 commits
  (merged to 'next' on 2018-08-15 at 9de975d92d)
 + rebase --exec: make it work with --rebase-merges
 + t3430: demonstrate what -r, --autosquash & --exec should do

 The "--exec" option to "git rebase --rebase-merges" placed the exec
 commands at wrong places, which has been corrected.

 Will merge to 'master'.


* wc/make-funnynames-shared-lazy-prereq (2018-08-06) 1 commit
  (merged to 'next' on 2018-08-17 at b932a0894b)
 + t: factor out FUNNYNAMES as shared lazy prereq

 A test prerequisite defined by various test scripts with slightly
 different semantics has been consolidated into a single copy and
 made into a lazily defined one.

 Will merge to 'master'.


* ab/test-must-be-empty-for-master (2018-07-30) 1 commit
  (merged to 'next' on 2018-08-15 at 17652a77fb)
 + tests: make use of the test_must_be_empty function

 Test updates.

 Will merge to 'master'.


* hn/highlight-sideband-keywords (2018-08-17) 2 commits
 - sideband: do not read beyond the end of input
  (merged to 'next' on 2018-08-15 at f8945f3be5)
 + sideband: highlight keywords in remote sideband output

 The sideband code learned to optionally paint selected keywords at
 the beginning of incoming lines on the receiving end.

 Will merge to 'master'.


* ab/fetch-tags-noclobber (2018-08-13) 7 commits
  (merged to 'next' on 2018-08-15 at eca0ac8afa)
 + pull doc: fix a long-standing grammar error
 + fetch tests: correct a comment "remove it" -> "remove them"
 + push tests: assert re-pushing annotated tags
 + push tests: add more testing for forced tag pushing
 + push tests: fix logic error in "push" test assertion
 + push tests: remove redundant 'git push' invocation
 + fetch tests: change "Tag" test tag to "testTag"

 Test and doc clean-ups.

 Will merge to 'master'.


* nd/clone-case-smashing-warning (2018-08-17) 1 commit
 - clone: report duplicate entries on case-insensitive filesystems

 Running "git clone" against a project that contain two files with
 pathnames that differ only in cases on a case insensitive
 filesystem would result in one of the files lost because the
 underlying filesystem is incapable of holding both at the same
 time.  An attempt is made to detect such a case and warn.

 Will merge to 'next'.


* nd/unpack-trees-with-cache-tree (2018-08-13) 5 commits
 - unpack-trees: reuse (still valid) cache-tree from src_index
 - unpack-trees: reduce malloc in cache-tree walk
 - unpack-trees: optimize walking same trees with cache-tree
 - unpack-trees: add performance tracing
 - trace.h: support nested performance tracing

 The unpack_trees() API used in checking out a branch and merging
 walks one or more trees along with the index.  When the cache-tree
 in the index tells us that we are walking a tree whose flattened
 contents is known (i.e. matches a span in the index), as linearly
 scanning a span in the index is much more efficient than having to
 open tree objects recursively and listing their entries, the walk
 can be optimized, which is done in this topic.

 Will merge to and cook in 'next'.


* sb/config-write-fix (2018-08-08) 3 commits
  (merged to 'next' on 2018-08-17 at 7d9c7ce81f)
 + git-config: document accidental multi-line setting in deprecated syntax
 + config: fix case sensitive subsection names on writing
 + t1300: document current behavior of setting options

 Recent update to "git config" broke updating variable in a
 subsection, which has been corrected.

 Will merge to 'master'.


* sb/range-diff-colors (2018-08-14) 8 commits
 - diff.c: rewrite emit_line_0 more understandably
 - diff.c: omit check for line prefix in emit_line_0
 - diff: use emit_line_0 once per line
 - diff.c: add set_sign to emit_line_0
 - diff.c: reorder arguments for emit_line_ws_markup
 - diff.c: simplify caller of emit_line_0
 - t3206: add color test for range-diff --dual-color
 - test_decode_color: understand FAINT and ITALIC
 (this branch uses js/range-diff; is tangled with es/format-patch-rangediff.)

 The color output support for recently introduced "range-diff"
 command got tweaked a bit.


* sg/t1404-update-ref-test-timeout (2018-08-01) 1 commit
 - t1404: increase core.packedRefsTimeout to avoid occasional test failure

 An attempt to unflake a test a bit.


* pw/rebase-i-author-script-fix (2018-08-07) 2 commits
 - sequencer: fix quoting in write_author_script
 - sequencer: handle errors from read_author_ident()

 Recent "git rebase -i" update started to write bogusly formatted
 author-script, with a matching broken reading code.  These are
 being fixed.

 Undecided.
 Is it the list consensus to favor this "with extra code, read the
 script written by bad writer" approach?


* pw/add-p-select (2018-07-26) 4 commits
 - add -p: optimize line selection for short hunks
 - add -p: allow line selection to be inverted
 - add -p: select modified lines correctly
 - add -p: select individual hunk lines

 "git add -p" interactive interface learned to let users choose
 individual added/removed lines to be used in the operation, instead
 of accepting or rejecting a whole hunk.

 Will hold.
 cf. <d622a95b-7302-43d4-4ec9-b2cf3388c653@talktalk.net>
 I found the feature to be hard to explain, and may result in more
 end-user complaints, but let's see.


* ds/commit-graph-with-grafts (2018-07-19) 8 commits
  (merged to 'next' on 2018-08-02 at 0ee624e329)
 + commit-graph: close_commit_graph before shallow walk
 + commit-graph: not compatible with uninitialized repo
 + commit-graph: not compatible with grafts
 + commit-graph: not compatible with replace objects
 + test-repository: properly init repo
 + commit-graph: update design document
 + refs.c: upgrade for_each_replace_ref to be a each_repo_ref_fn callback
 + refs.c: migrate internal ref iteration to pass thru repository argument

 The recently introduced commit-graph auxiliary data is incompatible
 with mechanisms such as replace & grafts that "breaks" immutable
 nature of the object reference relationship.  Disable optimizations
 based on its use (and updating existing commit-graph) when these
 incompatible features are in use in the repository.

 Eject and replace with another reroll when it comes.
 cf. <85c6eb4c-a083-4fb7-4860-b01a8ce9fa4f@gmail.com>


* ds/reachable (2018-07-20) 18 commits
 - commit-reach: use can_all_from_reach
 - commit-reach: make can_all_from_reach... linear
 - commit-reach: replace ref_newer logic
 - test-reach: test commit_contains
 - test-reach: test can_all_from_reach_with_flags
 - test-reach: test reduce_heads
 - test-reach: test get_merge_bases_many
 - test-reach: test is_descendant_of
 - test-reach: test in_merge_bases
 - test-reach: create new test tool for ref_newer
 - commit-reach: move can_all_from_reach_with_flags
 - upload-pack: generalize commit date cutoff
 - upload-pack: refactor ok_to_give_up()
 - upload-pack: make reachable() more generic
 - commit-reach: move commit_contains from ref-filter
 - commit-reach: move ref_newer from remote.c
 - commit.h: remove method declarations
 - commit-reach: move walk methods from commit.c

 The code for computing history reachability has been shuffled,
 obtained a bunch of new tests to cover them, and then being
 improved.

 Will merge to and cook in 'next'.


* es/format-patch-interdiff (2018-07-23) 6 commits
 - format-patch: allow --interdiff to apply to a lone-patch
 - log-tree: show_log: make commentary block delimiting reusable
 - interdiff: teach show_interdiff() to indent interdiff
 - format-patch: teach --interdiff to respect -v/--reroll-count
 - format-patch: add --interdiff option to embed diff in cover letter
 - format-patch: allow additional generated content in make_cover_letter()
 (this branch is used by es/format-patch-rangediff.)

 "git format-patch" learned a new "--interdiff" option to explain
 the difference between this version and the previous atttempt in
 the cover letter (or after the tree-dashes as a comment).

 Stuck in review?
 cf. <CAPig+cSuYUYSPTuKx08wcmQM-G12_-W2T4BS07fA=6grM1b8Gw@mail.gmail.com>


* es/format-patch-rangediff (2018-08-14) 10 commits
 - format-patch: allow --range-diff to apply to a lone-patch
 - format-patch: add --creation-factor tweak for --range-diff
 - format-patch: teach --range-diff to respect -v/--reroll-count
 - format-patch: extend --range-diff to accept revision range
 - format-patch: add --range-diff option to embed diff in cover letter
 - range-diff: relieve callers of low-level configuration burden
 - range-diff: publish default creation factor
 - range-diff: respect diff_option.file rather than assuming 'stdout'
 - Merge branch 'es/format-patch-interdiff' into es/format-patch-rangediff
 - Merge branch 'js/range-diff' into es/format-patch-rangediff
 (this branch uses es/format-patch-interdiff and js/range-diff; is tangled with sb/range-diff-colors.)

 "git format-patch" learned a new "--range-diff" option to explain
 the difference between this version and the previous atttempt in
 the cover letter (or after the tree-dashes as a comment).

 Need to wait for the prereq topics to solidify a bit more.


* nd/pack-deltify-regression-fix (2018-07-23) 1 commit
  (merged to 'next' on 2018-08-02 at f3b2bf0fef)
 + pack-objects: fix performance issues on packing large deltas

 In a recent update in 2.18 era, "git pack-objects" started
 producing a larger than necessary packfiles by missing
 opportunities to use large deltas.

 Will cook in 'next'.


* ab/newhash-is-sha256 (2018-08-07) 2 commits
  (merged to 'next' on 2018-08-15 at 2e808d75d3)
 + doc hash-function-transition: pick SHA-256 as NewHash
 + doc hash-function-transition: note the lack of a changelog

 Documentation update.

 Will merge to 'master'.


* jh/structured-logging (2018-07-25) 25 commits
 - structured-logging: add config data facility
 - structured-logging: t0420 tests for interacitve child_summary
 - structured-logging: t0420 tests for child process detail events
 - structured-logging: add child process classification
 - structured-logging: add detail-events for child processes
 - structured-logging: add structured logging to remote-curl
 - structured-logging: t0420 tests for aux-data
 - structured-logging: add aux-data for size of sparse-checkout file
 - structured-logging: add aux-data for index size
 - structured-logging: add aux-data facility
 - structured-logging: t0420 tests for timers
 - structured-logging: add timer around preload_index
 - structured-logging: add timer around wt-status functions
 - structured-logging: add timer around do_write_index
 - structured-logging: add timer around do_read_index
 - structured-logging: add timer facility
 - structured-logging: add detail-event for lazy_init_name_hash
 - structured-logging: add detail-event facility
 - structured-logging: t0420 basic tests
 - structured-logging: set sub_command field for checkout command
 - structured-logging: set sub_command field for branch command
 - structured-logging: add session-id to log events
 - structured-logging: add structured logging framework
 - structured-logging: add STRUCTURED_LOGGING=1 to Makefile
 - structured-logging: design document

 Will merge to 'next'.


* jn/gc-auto (2018-07-17) 3 commits
 - gc: do not return error for prior errors in daemonized mode
 - gc: exit with status 128 on failure
 - gc: improve handling of errors reading gc.log

 "gc --auto" ended up calling exit(-1) upon error, which has been
 corrected to use exit(1).  Also the error reporting behaviour when
 daemonized has been updated to exit with zero status when stopping
 due to a previously discovered error (which implies there is no
 point running gc to improve the situation); we used to exit with
 failure in such a case.

 Stuck in review?
 cf. <20180717201348.GD26218@sigill.intra.peff.net>


* sb/submodule-update-in-c (2018-08-14) 7 commits
  (merged to 'next' on 2018-08-17 at 23c81e5ff7)
 + submodule--helper: introduce new update-module-mode helper
 + submodule--helper: replace connect-gitdir-workingtree by ensure-core-worktree
 + builtin/submodule--helper: factor out method to update a single submodule
 + builtin/submodule--helper: store update_clone information in a struct
 + builtin/submodule--helper: factor out submodule updating
 + git-submodule.sh: rename unused variables
 + git-submodule.sh: align error reporting for update mode to use path

 "git submodule update" is getting rewritten piece-by-piece into C.

 Will cook in 'next'.


* tg/rerere (2018-08-06) 11 commits
  (merged to 'next' on 2018-08-17 at 919a958cdc)
 + rerere: recalculate conflict ID when unresolved conflict is committed
 + rerere: teach rerere to handle nested conflicts
 + rerere: return strbuf from handle path
 + rerere: factor out handle_conflict function
 + rerere: only return whether a path has conflicts or not
 + rerere: fix crash with files rerere can't handle
 + rerere: add documentation for conflict normalization
 + rerere: mark strings for translation
 + rerere: wrap paths in output in sq
 + rerere: lowercase error messages
 + rerere: unify error messages when read_cache fails

 Fixes to "git rerere" corner cases, especially when conflict
 markers cannot be parsed in the file.

 Will cook in 'next'.


* ag/rebase-i-in-c (2018-08-10) 20 commits
 - rebase -i: move rebase--helper modes to rebase--interactive
 - rebase -i: remove git-rebase--interactive.sh
 - rebase--interactive2: rewrite the submodes of interactive rebase in C
 - rebase -i: implement the main part of interactive rebase as a builtin
 - rebase -i: rewrite init_basic_state() in C
 - rebase -i: rewrite write_basic_state() in C
 - rebase -i: rewrite the rest of init_revisions_and_shortrevisions() in C
 - rebase -i: implement the logic to initialize $revisions in C
 - rebase -i: remove unused modes and functions
 - rebase -i: rewrite complete_action() in C
 - t3404: todo list with commented-out commands only aborts
 - sequencer: change the way skip_unnecessary_picks() returns its result
 - sequencer: refactor append_todo_help() to write its message to a buffer
 - rebase -i: rewrite checkout_onto() in C
 - rebase -i: rewrite setup_reflog_action() in C
 - sequencer: add a new function to silence a command, except if it fails
 - rebase -i: rewrite the edit-todo functionality in C
 - editor: add a function to launch the sequence editor
 - rebase -i: rewrite append_todo_help() in C
 - sequencer: make three functions and an enum from sequencer.c public

 Rewrite of the remaining "rebase -i" machinery in C.

 With "rebase -i" machinery being rewritten to C, with a different
 interface between "rebase" proper and its backends, this and the
 other topics need a bit more work to play with each other better.


* js/range-diff (2018-08-13) 21 commits
  (merged to 'next' on 2018-08-15 at 8d56067806)
 + range-diff: use dim/bold cues to improve dual color mode
 + range-diff: make --dual-color the default mode
 + range-diff: left-pad patch numbers
 + completion: support `git range-diff`
 + range-diff: populate the man page
 + range-diff --dual-color: skip white-space warnings
 + range-diff: offer to dual-color the diffs
 + diff: add an internal option to dual-color diffs of diffs
 + color: add the meta color GIT_COLOR_REVERSE
 + range-diff: use color for the commit pairs
 + range-diff: add tests
 + range-diff: do not show "function names" in hunk headers
 + range-diff: adjust the output of the commit pairs
 + range-diff: suppress the diff headers
 + range-diff: indent the diffs just like tbdiff
 + range-diff: right-trim commit messages
 + range-diff: also show the diff between patches
 + range-diff: improve the order of the shown commits
 + range-diff: first rudimentary implementation
 + Introduce `range-diff` to compare iterations of a topic branch
 + linear-assignment: a function to solve least-cost assignment problems
 (this branch is used by es/format-patch-rangediff and sb/range-diff-colors.)

 "git tbdiff" that lets us compare individual patches in two
 iterations of a topic has been rewritten and made into a built-in
 command.

 Will merge to 'master'.


* lt/date-human (2018-07-09) 1 commit
 - Add 'human' date format

 A new date format "--date=human" that morphs its output depending
 on how far the time is from the current time has been introduced.
 "--date=auto" can be used to use this new format when the output is
 goint to the pager or to the terminal and otherwise the default
 format.


* pk/rebase-in-c (2018-08-06) 3 commits
 - builtin/rebase: support running "git rebase <upstream>"
 - rebase: refactor common shell functions into their own file
 - rebase: start implementing it as a builtin
 (this branch is used by pk/rebase-in-c-2-basic, pk/rebase-in-c-3-acts, pk/rebase-in-c-4-opts, pk/rebase-in-c-5-test and pk/rebase-in-c-6-final.)

 Rewrite of the "rebase" machinery in C.


* jk/branch-l-1-repurpose (2018-06-22) 1 commit
  (merged to 'next' on 2018-08-08 at d2a08dd08e)
 + branch: make "-l" a synonym for "--list"

 Updated plan to repurpose the "-l" option to "git branch".

 Will cook in 'next'.


* ds/multi-pack-index (2018-07-20) 23 commits
  (merged to 'next' on 2018-08-08 at 1a56c52967)
 + midx: clear midx on repack
 + packfile: skip loading index if in multi-pack-index
 + midx: prevent duplicate packfile loads
 + midx: use midx in approximate_object_count
 + midx: use existing midx when writing new one
 + midx: use midx in abbreviation calculations
 + midx: read objects from multi-pack-index
 + config: create core.multiPackIndex setting
 + midx: write object offsets
 + midx: write object id fanout chunk
 + midx: write object ids in a chunk
 + midx: sort and deduplicate objects from packfiles
 + midx: read pack names into array
 + multi-pack-index: write pack names in chunk
 + multi-pack-index: read packfile list
 + packfile: generalize pack directory list
 + t5319: expand test data
 + multi-pack-index: load into memory
 + midx: write header information to lockfile
 + multi-pack-index: add 'write' verb
 + multi-pack-index: add builtin
 + multi-pack-index: add format details
 + multi-pack-index: add design document

 When there are too many packfiles in a repository (which is not
 recommended), looking up an object in these would require
 consulting many pack .idx files; a new mechanism to have a single
 file that consolidates all of these .idx files is introduced.

 Will cook in 'next'.

--------------------------------------------------
[Discarded]

* am/sequencer-author-script-fix (2018-07-18) 1 commit
 . sequencer.c: terminate the last line of author-script properly

 The author-script that records the author information created by
 the sequencer machinery lacked the closing single quote on the last
 entry.

 Superseded by another topic.


* jc/push-cas-opt-comment (2018-08-01) 1 commit
 . push: comment on a funny unbalanced option help

 Code clarification.

 Superseded by another topic.


* cc/remote-odb (2018-08-02) 9 commits
 . Documentation/config: add odb.<name>.promisorRemote
 . t0410: test fetching from many promisor remotes
 . Use odb.origin.partialclonefilter instead of core.partialclonefilter
 . Use remote_odb_get_direct() and has_remote_odb()
 . remote-odb: add remote_odb_reinit()
 . remote-odb: implement remote_odb_get_many_direct()
 . remote-odb: implement remote_odb_get_direct()
 . Add initial remote odb support
 . fetch-object: make functions return an error code

 Implement lazy fetches of missing objects to complement the
 experimental partial clone feature.

 Ejected; seems to break existing repositories that use partialclone
 repository extension.

 I haven't seen much interest in this topic on list.  What's the
 doneness of this thing?

 I do not particularly mind adding code to support a niche feature
 as long as it is cleanly made and it is clear that the feature
 won't negatively affect those who do not use it, so a review from
 that point of view may also be appropriate.

^ permalink raw reply	[relevance 1%]

* Re: [PATCH v5] clone: report duplicate entries on case-insensitive filesystems
  2018-08-17 16:16  9%         ` [PATCH v5] " Nguyễn Thái Ngọc Duy
  2018-08-17 17:20  0%           ` Junio C Hamano
@ 2018-08-17 19:46  0%           ` Torsten Bögershausen
  2018-11-19  8:20  6%           ` Carlo Marcelo Arenas Belón
  2 siblings, 0 replies; 200+ results
From: Torsten Bögershausen @ 2018-08-17 19:46 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy
  Cc: git, git, gitster, newren, pawelparuzel95, peff, sandals,
	szeder.dev

On Fri, Aug 17, 2018 at 06:16:45PM +0200, Nguyễn Thái Ngọc Duy wrote:

The whole patch looks good to me.
(I was just sending a different version, but your version is better :-)

One minor remark, should the line
warning: the following paths have collided 
start with a capital letter:
Warning: the following paths have collided 

> Paths that only differ in case work fine in a case-sensitive
> filesystems, but if those repos are cloned in a case-insensitive one,
> you'll get problems. The first thing to notice is "git status" will
> never be clean with no indication what exactly is "dirty".
> 
> This patch helps the situation a bit by pointing out the problem at
> clone time. Even though this patch talks about case sensitivity, the
> patch makes no assumption about folding rules by the filesystem. It
> simply observes that if an entry has been already checked out at clone
> time when we're about to write a new path, some folding rules are
> behind this.
> 
> In the case that we can't rely on filesystem (via inode number) to do
> this check, fall back to fspathcmp() which is not perfect but should
> not give false positives.
> 
> This patch is tested with vim-colorschemes and Sublime-Gitignore
> repositories on a JFS partition with case insensitive support on
> Linux.

Now even tested under Mac OS/HFS+

[]
>  '
>  
> +test_expect_success !MINGW,!CYGWIN,CASE_INSENSITIVE_FS 'colliding file detection' '

My ambition is to run the test under Windows (both CYGWIN and native) next week,
so that we can remove !MINGW and !CYGWIN 


^ permalink raw reply	[relevance 0%]

* Re: [PATCH v5] clone: report duplicate entries on case-insensitive filesystems
  2018-08-17 17:20  0%           ` Junio C Hamano
@ 2018-08-17 18:00  9%             ` Duy Nguyen
  0 siblings, 0 replies; 200+ results
From: Duy Nguyen @ 2018-08-17 18:00 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: git, git, newren, pawelparuzel95, peff, sandals, szeder.dev,
	tboegi

On Fri, Aug 17, 2018 at 10:20:36AM -0700, Junio C Hamano wrote:
> I highly suspect that the above was written in that way to reduce
> the indentation level, but the right way to reduce the indentation
> level, if it bothers readers too much, is to make the whole thing
> inside the above if (o->clone) into a dedicated helper function
> "void report_collided_checkout(void)", I would think.

I read my mind. I thought of separating into a helper function too,
but was not happy that the clearing CE_MATCHED in preparation for this
test is in check_updates(), but the cleaning up CE_MATCHED() is in the
helper function.

So here is the version that separates _both_ phases into helper
functions.

-- 8< --
Subject: [PATCH v6] clone: report duplicate entries on case-insensitive filesystems

Paths that only differ in case work fine in a case-sensitive
filesystems, but if those repos are cloned in a case-insensitive one,
you'll get problems. The first thing to notice is "git status" will
never be clean with no indication what exactly is "dirty".

This patch helps the situation a bit by pointing out the problem at
clone time. Even though this patch talks about case sensitivity, the
patch makes no assumption about folding rules by the filesystem. It
simply observes that if an entry has been already checked out at clone
time when we're about to write a new path, some folding rules are
behind this.

In the case that we can't rely on filesystem (via inode number) to do
this check, fall back to fspathcmp() which is not perfect but should
not give false positives.

This patch is tested with vim-colorschemes and Sublime-Gitignore
repositories on a JFS partition with case insensitive support on
Linux.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/clone.c  |  1 +
 cache.h          |  1 +
 entry.c          | 31 +++++++++++++++++++++++++++++++
 t/t5601-clone.sh |  8 +++++++-
 unpack-trees.c   | 47 +++++++++++++++++++++++++++++++++++++++++++++++
 unpack-trees.h   |  1 +
 6 files changed, 88 insertions(+), 1 deletion(-)

diff --git a/builtin/clone.c b/builtin/clone.c
index 5c439f1394..0702b0e9d0 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -747,6 +747,7 @@ static int checkout(int submodule_progress)
 	memset(&opts, 0, sizeof opts);
 	opts.update = 1;
 	opts.merge = 1;
+	opts.clone = 1;
 	opts.fn = oneway_merge;
 	opts.verbose_update = (option_verbosity >= 0);
 	opts.src_index = &the_index;
diff --git a/cache.h b/cache.h
index 8b447652a7..6d6138f4f1 100644
--- a/cache.h
+++ b/cache.h
@@ -1455,6 +1455,7 @@ struct checkout {
 	unsigned force:1,
 		 quiet:1,
 		 not_new:1,
+		 clone:1,
 		 refresh_cache:1;
 };
 #define CHECKOUT_INIT { NULL, "" }
diff --git a/entry.c b/entry.c
index b5d1d3cf23..8766e27255 100644
--- a/entry.c
+++ b/entry.c
@@ -399,6 +399,34 @@ static int check_path(const char *path, int len, struct stat *st, int skiplen)
 	return lstat(path, st);
 }
 
+static void mark_colliding_entries(const struct checkout *state,
+				   struct cache_entry *ce, struct stat *st)
+{
+	int i, trust_ino = check_stat;
+
+#if defined(GIT_WINDOWS_NATIVE)
+	trust_ino = 0;
+#endif
+
+	ce->ce_flags |= CE_MATCHED;
+
+	for (i = 0; i < state->istate->cache_nr; i++) {
+		struct cache_entry *dup = state->istate->cache[i];
+
+		if (dup == ce)
+			break;
+
+		if (dup->ce_flags & (CE_MATCHED | CE_VALID | CE_SKIP_WORKTREE))
+			continue;
+
+		if ((trust_ino && dup->ce_stat_data.sd_ino == st->st_ino) ||
+		    (!trust_ino && !fspathcmp(ce->name, dup->name))) {
+			dup->ce_flags |= CE_MATCHED;
+			break;
+		}
+	}
+}
+
 /*
  * Write the contents from ce out to the working tree.
  *
@@ -455,6 +483,9 @@ int checkout_entry(struct cache_entry *ce,
 			return -1;
 		}
 
+		if (state->clone)
+			mark_colliding_entries(state, ce, &st);
+
 		/*
 		 * We unlink the old file, to get the new one with the
 		 * right permissions (including umask, which is nasty
diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh
index 0b62037744..f2eb73bc74 100755
--- a/t/t5601-clone.sh
+++ b/t/t5601-clone.sh
@@ -624,10 +624,16 @@ test_expect_success 'clone on case-insensitive fs' '
 			git hash-object -w -t tree --stdin) &&
 		c=$(git commit-tree -m bogus $t) &&
 		git update-ref refs/heads/bogus $c &&
-		git clone -b bogus . bogus
+		git clone -b bogus . bogus 2>warning
 	)
 '
 
+test_expect_success !MINGW,!CYGWIN,CASE_INSENSITIVE_FS 'colliding file detection' '
+	grep X icasefs/warning &&
+	grep x icasefs/warning &&
+	test_i18ngrep "the following paths have collided" icasefs/warning
+'
+
 partial_clone () {
 	       SERVER="$1" &&
 	       URL="$2" &&
diff --git a/unpack-trees.c b/unpack-trees.c
index cd0680f11e..213da8bbb4 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -345,6 +345,46 @@ static struct progress *get_progress(struct unpack_trees_options *o)
 	return start_delayed_progress(_("Checking out files"), total);
 }
 
+static void setup_collided_checkout_detection(struct checkout *state,
+					      struct index_state *index)
+{
+	int i;
+
+	state->clone = 1;
+	for (i = 0; i < index->cache_nr; i++)
+		index->cache[i]->ce_flags &= ~CE_MATCHED;
+}
+
+static void report_collided_checkout(struct index_state *index)
+{
+	struct string_list list = STRING_LIST_INIT_NODUP;
+	int i;
+
+	for (i = 0; i < index->cache_nr; i++) {
+		struct cache_entry *ce = index->cache[i];
+
+		if (!(ce->ce_flags & CE_MATCHED))
+			continue;
+
+		string_list_append(&list, ce->name);
+		ce->ce_flags &= ~CE_MATCHED;
+	}
+
+	list.cmp = fspathcmp;
+	string_list_sort(&list);
+
+	if (list.nr) {
+		warning(_("the following paths have collided (e.g. case-sensitive paths\n"
+			  "on a case-insensitive filesystem) and only one from the same\n"
+			  "colliding group is in the working tree:\n"));
+
+		for (i = 0; i < list.nr; i++)
+			fprintf(stderr, "  '%s'\n", list.items[i].string);
+	}
+
+	string_list_clear(&list, 0);
+}
+
 static int check_updates(struct unpack_trees_options *o)
 {
 	unsigned cnt = 0;
@@ -359,6 +399,9 @@ static int check_updates(struct unpack_trees_options *o)
 	state.refresh_cache = 1;
 	state.istate = index;
 
+	if (o->clone)
+		setup_collided_checkout_detection(&state, index);
+
 	progress = get_progress(o);
 
 	if (o->update)
@@ -423,6 +466,10 @@ static int check_updates(struct unpack_trees_options *o)
 	errs |= finish_delayed_checkout(&state);
 	if (o->update)
 		git_attr_set_direction(GIT_ATTR_CHECKIN, NULL);
+
+	if (o->clone)
+		report_collided_checkout(index);
+
 	return errs != 0;
 }
 
diff --git a/unpack-trees.h b/unpack-trees.h
index c2b434c606..d940f1c5c2 100644
--- a/unpack-trees.h
+++ b/unpack-trees.h
@@ -42,6 +42,7 @@ struct unpack_trees_options {
 	unsigned int reset,
 		     merge,
 		     update,
+		     clone,
 		     index_only,
 		     nontrivial_merge,
 		     trivial_merges_only,
-- 
2.18.0.1004.g6639190530

-- 8< --
--
Duy

^ permalink raw reply related	[relevance 9%]

* Re: [PATCH v5] clone: report duplicate entries on case-insensitive filesystems
  2018-08-17 16:16  9%         ` [PATCH v5] " Nguyễn Thái Ngọc Duy
@ 2018-08-17 17:20  0%           ` Junio C Hamano
  2018-08-17 18:00  9%             ` Duy Nguyen
  2018-08-17 19:46  0%           ` Torsten Bögershausen
  2018-11-19  8:20  6%           ` Carlo Marcelo Arenas Belón
  2 siblings, 1 reply; 200+ results
From: Junio C Hamano @ 2018-08-17 17:20 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy
  Cc: git, git, newren, pawelparuzel95, peff, sandals, szeder.dev,
	tboegi

Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:

>  I still don't trust magic st_ino zero, or core.checkStat being zero
>  on Windows, so the #if condition still remains but it covers smallest
>  area possible and I tested it by manually make it "#if 1"
>
>  The fallback with fspathcmp() is only done when inode can't be
>  trusted because strcmp is more expensive and when fspathcmp() learns
>  more about real world in the future, it could become even more
>  expensive.
>
>  The output sorting is the result of Sublime-Gitignore repo being
>  reported recently. It's not perfect but it should help seeing the
>  groups in normal case.

Looks small and safe.

> +
> +	if (o->clone) {
> +		struct string_list list = STRING_LIST_INIT_NODUP;
> +		int i;
> +
> +		for (i = 0; i < index->cache_nr; i++) {
> +			struct cache_entry *ce = index->cache[i];
> +
> +			if (!(ce->ce_flags & CE_MATCHED))
> +				continue;
> +
> +			string_list_append(&list, ce->name);
> +			ce->ce_flags &= ~CE_MATCHED;
> +		}
> +
> +		list.cmp = fspathcmp;
> +		string_list_sort(&list);
> +
> +		if (list.nr)
> +			warning(_("the following paths have collided (e.g. case-sensitive paths\n"
> +				  "on a case-insensitive filesystem) and only one from the same\n"
> +				  "colliding group is in the working tree:\n"));
> +
> +		for (i = 0; i < list.nr; i++)
> +			fprintf(stderr, "  '%s'\n", list.items[i].string);
> +
> +		string_list_clear(&list, 0);

I would have written the "sort, show warning, and list" all inside
"if (list.nr)" block, leaving list-clear outside, which would have
made the logic a bit cleaner.  The reader does not have to bother
thinking "ah, when list.nr==0, this is a no-op anyway" to skip them
if written that way.

I highly suspect that the above was written in that way to reduce
the indentation level, but the right way to reduce the indentation
level, if it bothers readers too much, is to make the whole thing
inside the above if (o->clone) into a dedicated helper function
"void report_collided_checkout(void)", I would think.

^ permalink raw reply	[relevance 0%]

* [PATCH v5] clone: report duplicate entries on case-insensitive filesystems
  2018-08-12  9:07  9%       ` [PATCH v4] " Nguyễn Thái Ngọc Duy
                           ` (2 preceding siblings ...)
  2018-08-15 19:08  0%         ` Torsten Bögershausen
@ 2018-08-17 16:16  9%         ` Nguyễn Thái Ngọc Duy
  2018-08-17 17:20  0%           ` Junio C Hamano
                             ` (2 more replies)
  3 siblings, 3 replies; 200+ results
From: Nguyễn Thái Ngọc Duy @ 2018-08-17 16:16 UTC (permalink / raw)
  To: pclouds
  Cc: git, git, gitster, newren, pawelparuzel95, peff, sandals,
	szeder.dev, tboegi

Paths that only differ in case work fine in a case-sensitive
filesystems, but if those repos are cloned in a case-insensitive one,
you'll get problems. The first thing to notice is "git status" will
never be clean with no indication what exactly is "dirty".

This patch helps the situation a bit by pointing out the problem at
clone time. Even though this patch talks about case sensitivity, the
patch makes no assumption about folding rules by the filesystem. It
simply observes that if an entry has been already checked out at clone
time when we're about to write a new path, some folding rules are
behind this.

In the case that we can't rely on filesystem (via inode number) to do
this check, fall back to fspathcmp() which is not perfect but should
not give false positives.

This patch is tested with vim-colorschemes and Sublime-Gitignore
repositories on a JFS partition with case insensitive support on
Linux.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 v5 respects core.checkStat and sorts the output case-insensitively.

 I still don't trust magic st_ino zero, or core.checkStat being zero
 on Windows, so the #if condition still remains but it covers smallest
 area possible and I tested it by manually make it "#if 1"

 The fallback with fspathcmp() is only done when inode can't be
 trusted because strcmp is more expensive and when fspathcmp() learns
 more about real world in the future, it could become even more
 expensive.

 The output sorting is the result of Sublime-Gitignore repo being
 reported recently. It's not perfect but it should help seeing the
 groups in normal case.

 builtin/clone.c  |  1 +
 cache.h          |  1 +
 entry.c          | 31 +++++++++++++++++++++++++++++++
 t/t5601-clone.sh |  8 +++++++-
 unpack-trees.c   | 35 +++++++++++++++++++++++++++++++++++
 unpack-trees.h   |  1 +
 6 files changed, 76 insertions(+), 1 deletion(-)

diff --git a/builtin/clone.c b/builtin/clone.c
index 5c439f1394..0702b0e9d0 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -747,6 +747,7 @@ static int checkout(int submodule_progress)
 	memset(&opts, 0, sizeof opts);
 	opts.update = 1;
 	opts.merge = 1;
+	opts.clone = 1;
 	opts.fn = oneway_merge;
 	opts.verbose_update = (option_verbosity >= 0);
 	opts.src_index = &the_index;
diff --git a/cache.h b/cache.h
index 8b447652a7..6d6138f4f1 100644
--- a/cache.h
+++ b/cache.h
@@ -1455,6 +1455,7 @@ struct checkout {
 	unsigned force:1,
 		 quiet:1,
 		 not_new:1,
+		 clone:1,
 		 refresh_cache:1;
 };
 #define CHECKOUT_INIT { NULL, "" }
diff --git a/entry.c b/entry.c
index b5d1d3cf23..8766e27255 100644
--- a/entry.c
+++ b/entry.c
@@ -399,6 +399,34 @@ static int check_path(const char *path, int len, struct stat *st, int skiplen)
 	return lstat(path, st);
 }
 
+static void mark_colliding_entries(const struct checkout *state,
+				   struct cache_entry *ce, struct stat *st)
+{
+	int i, trust_ino = check_stat;
+
+#if defined(GIT_WINDOWS_NATIVE)
+	trust_ino = 0;
+#endif
+
+	ce->ce_flags |= CE_MATCHED;
+
+	for (i = 0; i < state->istate->cache_nr; i++) {
+		struct cache_entry *dup = state->istate->cache[i];
+
+		if (dup == ce)
+			break;
+
+		if (dup->ce_flags & (CE_MATCHED | CE_VALID | CE_SKIP_WORKTREE))
+			continue;
+
+		if ((trust_ino && dup->ce_stat_data.sd_ino == st->st_ino) ||
+		    (!trust_ino && !fspathcmp(ce->name, dup->name))) {
+			dup->ce_flags |= CE_MATCHED;
+			break;
+		}
+	}
+}
+
 /*
  * Write the contents from ce out to the working tree.
  *
@@ -455,6 +483,9 @@ int checkout_entry(struct cache_entry *ce,
 			return -1;
 		}
 
+		if (state->clone)
+			mark_colliding_entries(state, ce, &st);
+
 		/*
 		 * We unlink the old file, to get the new one with the
 		 * right permissions (including umask, which is nasty
diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh
index 0b62037744..f2eb73bc74 100755
--- a/t/t5601-clone.sh
+++ b/t/t5601-clone.sh
@@ -624,10 +624,16 @@ test_expect_success 'clone on case-insensitive fs' '
 			git hash-object -w -t tree --stdin) &&
 		c=$(git commit-tree -m bogus $t) &&
 		git update-ref refs/heads/bogus $c &&
-		git clone -b bogus . bogus
+		git clone -b bogus . bogus 2>warning
 	)
 '
 
+test_expect_success !MINGW,!CYGWIN,CASE_INSENSITIVE_FS 'colliding file detection' '
+	grep X icasefs/warning &&
+	grep x icasefs/warning &&
+	test_i18ngrep "the following paths have collided" icasefs/warning
+'
+
 partial_clone () {
 	       SERVER="$1" &&
 	       URL="$2" &&
diff --git a/unpack-trees.c b/unpack-trees.c
index cd0680f11e..4338fee3b7 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -359,6 +359,12 @@ static int check_updates(struct unpack_trees_options *o)
 	state.refresh_cache = 1;
 	state.istate = index;
 
+	if (o->clone) {
+		state.clone = 1;
+		for (i = 0; i < index->cache_nr; i++)
+			index->cache[i]->ce_flags &= ~CE_MATCHED;
+	}
+
 	progress = get_progress(o);
 
 	if (o->update)
@@ -423,6 +429,35 @@ static int check_updates(struct unpack_trees_options *o)
 	errs |= finish_delayed_checkout(&state);
 	if (o->update)
 		git_attr_set_direction(GIT_ATTR_CHECKIN, NULL);
+
+	if (o->clone) {
+		struct string_list list = STRING_LIST_INIT_NODUP;
+		int i;
+
+		for (i = 0; i < index->cache_nr; i++) {
+			struct cache_entry *ce = index->cache[i];
+
+			if (!(ce->ce_flags & CE_MATCHED))
+				continue;
+
+			string_list_append(&list, ce->name);
+			ce->ce_flags &= ~CE_MATCHED;
+		}
+
+		list.cmp = fspathcmp;
+		string_list_sort(&list);
+
+		if (list.nr)
+			warning(_("the following paths have collided (e.g. case-sensitive paths\n"
+				  "on a case-insensitive filesystem) and only one from the same\n"
+				  "colliding group is in the working tree:\n"));
+
+		for (i = 0; i < list.nr; i++)
+			fprintf(stderr, "  '%s'\n", list.items[i].string);
+
+		string_list_clear(&list, 0);
+	}
+
 	return errs != 0;
 }
 
diff --git a/unpack-trees.h b/unpack-trees.h
index c2b434c606..d940f1c5c2 100644
--- a/unpack-trees.h
+++ b/unpack-trees.h
@@ -42,6 +42,7 @@ struct unpack_trees_options {
 	unsigned int reset,
 		     merge,
 		     update,
+		     clone,
 		     index_only,
 		     nontrivial_merge,
 		     trivial_merges_only,
-- 
2.18.0.1004.g6639190530


^ permalink raw reply related	[relevance 9%]

* Re: What's cooking in git.git (Aug 2018, #03; Wed, 15)
  2018-08-16  2:35  0% ` Stefan Beller
@ 2018-08-16 14:58  0%   ` Junio C Hamano
  0 siblings, 0 replies; 200+ results
From: Junio C Hamano @ 2018-08-16 14:58 UTC (permalink / raw)
  To: Stefan Beller; +Cc: git

Stefan Beller <sbeller@google.com> writes:

>>
>> * sb/config-write-fix (2018-08-08) 3 commits
>>  - git-config: document accidental multi-line setting in deprecated syntax
>>  - config: fix case sensitive subsection names on writing
>>  - t1300: document current behavior of setting options
>>
>>  Recent update to "git config" broke updating variable in a
>>  subsection, which has been corrected.
>>
>>  Expecting a reroll.
>>  cf. <CAGZ79kZ1R8sxmtfgPOQcpoWM7GWV1qiRaqMq_zhGyKBB3ARLjg@mail.gmail.com>
>
> That reroll happened and you picked it up,
> cf. https://public-inbox.org/git/20180808195020.37374-1-sbeller@google.com/

Thanks for a quick update.  I do not think I saw any other issues
raised and a quick rescan of the patches does not raise any flags,
so perhaps we should mark it to be merged to 'next' soonish.


^ permalink raw reply	[relevance 0%]

* Re: "Changes not staged for commit" after cloning a repo on macOS
  2018-08-16  6:23  5% ` Torsten Bögershausen
@ 2018-08-16  7:28  0%   ` Hadi Safari
  0 siblings, 0 replies; 200+ results
From: Hadi Safari @ 2018-08-16  7:28 UTC (permalink / raw)
  To: Torsten Bögershausen; +Cc: git

On 25/5/1397 AP 10:53 AM, Torsten Bögershausen wrote:
> This repo seams not ment to be cloned onto a file system, which is case-insensitive.
> For example, (see below), this 2 files a different in the repo, but the file system
> can not have 'WordPress' and 'Wordpres's as different files or directories at the same
> time.
> This affects typically Mac OS and Windows users.
> 
> There is actually some work going on right now to inform the user about this problem.
> (Thanks Duy !)
> If I clone it with a patched Git, I get the following:
> 
> 
> git clone https://github.com/kevinxucs/Sublime-Gitignore.git
> Cloning into 'Sublime-Gitignore'...
> remote: Counting objects: 515, done.
> remote: Total 515 (delta 0), reused 0 (delta 0), pack-reused 515
> Receiving objects: 100% (515/515), 102.16 KiB | 35.00 KiB/s, done.
> Resolving deltas: 100% (143/143), done.
> warning: the following paths have collided (e.g. case-sensitive paths
> on a case-insensitive filesystem) and only one from the same
> colliding group is in the working tree:
> 
>    'boilerplates/Gcov.gitignore'
>    'boilerplates/Nanoc.gitignore'
>    'boilerplates/OpenCart.gitignore'
>    'boilerplates/SASS.gitignore'
>    'boilerplates/Sass.gitignore'
>    'boilerplates/Stella.gitignore'
>    'boilerplates/WordPress.gitignore'
>    'boilerplates/Wordpress.gitignore'
>    'boilerplates/gcov.gitignore'
>    'boilerplates/nanoc.gitignore'
>    'boilerplates/opencart.gitignore'
>    'boilerplates/stella.gitignore'
> 
> Would this text help you ?
> 
> I am asking because the development is still ongoing, so things can be improved.
> 

Yes, thank you. It's clear and helpful. I got what is happening.

-- 
Hadi Safari
http://hadisafari.ir/

^ permalink raw reply	[relevance 0%]

* Re: "Changes not staged for commit" after cloning a repo on macOS
  @ 2018-08-16  6:23  5% ` Torsten Bögershausen
  2018-08-16  7:28  0%   ` Hadi Safari
  0 siblings, 1 reply; 200+ results
From: Torsten Bögershausen @ 2018-08-16  6:23 UTC (permalink / raw)
  To: Hadi Safari; +Cc: git

On Thu, Aug 16, 2018 at 12:25:24AM +0430, Hadi Safari wrote:
> Hi everyone!
> 
> I encountered a strange situation on OS X recently. I cloned a repository
> (https://github.com/kevinxucs/Sublime-Gitignore.git), went to folder, and
> saw "Changes not staged for commit" message for four specific files. It
> happened every time I repeated the procedure. I even was able to commit
> those to the repo.
> At first I thought there's something wrong with repo, but I cloned it on
> Ubuntu 16.04 and everything was OK; no "Changes not staged for commit"
> message.
> 
> Does anyone have any idea?
> 
> Thank you.
> 
> Log:
> 
> ```
> $ git clone https://github.com/kevinxucs/Sublime-Gitignore.git
> Cloning into 'Sublime-Gitignore'...
> remote: Counting objects: 515, done.
> remote: Total 515 (delta 0), reused 0 (delta 0), pack-reused 515
> Receiving objects: 100% (515/515), 102.14 KiB | 48.00 KiB/s, done.
> Resolving deltas: 100% (143/143), done.
> $ cd Sublime-Gitignore/
> $ git status
> On branch master
> Your branch is up to date with 'origin/master'.
> 
> Changes not staged for commit:
>   (use "git add <file>..." to update what will be committed)
>   (use "git checkout -- <file>..." to discard changes in working directory)
> 
>     modified:   boilerplates/Nanoc.gitignore
>     modified:   boilerplates/OpenCart.gitignore
>     modified:   boilerplates/SASS.gitignore
>     modified:   boilerplates/WordPress.gitignore
> 
> no changes added to commit (use "git add" and/or "git commit -a")
> ```
> 
> The rest of the log is available at
> https://github.com/kevinxucs/Sublime-Gitignore/issues/6. (I don't want this
> email to become too long.)
> 
> -- 
> Hadi Safari
> http://hadisafari.ir/


This repo seams not ment to be cloned onto a file system, which is case-insensitive.
For example, (see below), this 2 files a different in the repo, but the file system
can not have 'WordPress' and 'Wordpres's as different files or directories at the same
time.
This affects typically Mac OS and Windows users.

There is actually some work going on right now to inform the user about this problem.
(Thanks Duy !)
If I clone it with a patched Git, I get the following:


git clone https://github.com/kevinxucs/Sublime-Gitignore.git
Cloning into 'Sublime-Gitignore'...
remote: Counting objects: 515, done.
remote: Total 515 (delta 0), reused 0 (delta 0), pack-reused 515
Receiving objects: 100% (515/515), 102.16 KiB | 35.00 KiB/s, done.
Resolving deltas: 100% (143/143), done.
warning: the following paths have collided (e.g. case-sensitive paths
on a case-insensitive filesystem) and only one from the same
colliding group is in the working tree:

  'boilerplates/Gcov.gitignore'
  'boilerplates/Nanoc.gitignore'
  'boilerplates/OpenCart.gitignore'
  'boilerplates/SASS.gitignore'
  'boilerplates/Sass.gitignore'
  'boilerplates/Stella.gitignore'
  'boilerplates/WordPress.gitignore'
  'boilerplates/Wordpress.gitignore'
  'boilerplates/gcov.gitignore'
  'boilerplates/nanoc.gitignore'
  'boilerplates/opencart.gitignore'
  'boilerplates/stella.gitignore'

Would this text help you ?

I am asking because the development is still ongoing, so things can be improved.

^ permalink raw reply	[relevance 5%]

* Re: What's cooking in git.git (Aug 2018, #03; Wed, 15)
  2018-08-15 23:01  1% What's cooking in git.git (Aug 2018, #03; Wed, 15) Junio C Hamano
@ 2018-08-16  2:35  0% ` Stefan Beller
  2018-08-16 14:58  0%   ` Junio C Hamano
  0 siblings, 1 reply; 200+ results
From: Stefan Beller @ 2018-08-16  2:35 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

>
> * sb/config-write-fix (2018-08-08) 3 commits
>  - git-config: document accidental multi-line setting in deprecated syntax
>  - config: fix case sensitive subsection names on writing
>  - t1300: document current behavior of setting options
>
>  Recent update to "git config" broke updating variable in a
>  subsection, which has been corrected.
>
>  Expecting a reroll.
>  cf. <CAGZ79kZ1R8sxmtfgPOQcpoWM7GWV1qiRaqMq_zhGyKBB3ARLjg@mail.gmail.com>

That reroll happened and you picked it up,
cf. https://public-inbox.org/git/20180808195020.37374-1-sbeller@google.com/

^ permalink raw reply	[relevance 0%]

* What's cooking in git.git (Aug 2018, #03; Wed, 15)
@ 2018-08-15 23:01  1% Junio C Hamano
  2018-08-16  2:35  0% ` Stefan Beller
  0 siblings, 1 reply; 200+ results
From: Junio C Hamano @ 2018-08-15 23:01 UTC (permalink / raw)
  To: git

Here are the topics that have been cooking.  Commits prefixed with
'-' are only in 'pu' (proposed updates) while commits prefixed with
'+' are in 'next'.  The ones marked with '.' do not appear in any of
the integration branches, but I am still holding onto them.

You can find the changes described here in the integration branches
of the repositories listed at

    http://git-blame.blogspot.com/p/git-public-repositories.html

--------------------------------------------------
[Graduated to "master"]

* bb/make-developer-pedantic (2018-07-25) 1 commit
  (merged to 'next' on 2018-08-02 at c738a84b7e)
 + Makefile: add a DEVOPTS flag to get pedantic compilation

 "make DEVELOPER=1 DEVOPTS=pedantic" allows developers to compile
 with -pedantic option, which may catch more problematic program
 constructs and potential bugs.


* bb/redecl-enum-fix (2018-07-26) 1 commit
  (merged to 'next' on 2018-08-06 at 828dc4b156)
 + packfile: ensure that enum object_type is defined

 Compilation fix.


* bw/clone-ref-prefixes (2018-07-20) 1 commit
  (merged to 'next' on 2018-08-02 at c8ad140ab0)
 + clone: send ref-prefixes when using protocol v2

 The wire-protocol v2 relies on the client to send "ref prefixes" to
 limit the bandwidth spent on the initial ref advertisement.  "git
 clone" when learned to speak v2 forgot to do so, which has been
 corrected.


* bw/fetch-pack-i18n (2018-07-23) 1 commit
  (merged to 'next' on 2018-08-02 at df72001755)
 + fetch-pack: mark die strings for translation

 i18n updates.


* bw/protocol-v2 (2018-07-24) 1 commit
  (merged to 'next' on 2018-08-02 at f4076b3e94)
 + pack-protocol: mention and point to docs for protocol v2

 Doc update.


* cb/p4-pre-submit-hook (2018-08-01) 1 commit
  (merged to 'next' on 2018-08-06 at e40ae4af80)
 + git-p4: add the `p4-pre-submit` hook

 "git p4 submit" learns to ask its own pre-submit hook if it should
 continue with submitting.


* en/merge-recursive-skip-fix (2018-07-27) 2 commits
  (merged to 'next' on 2018-08-06 at 9ab321a15c)
 + merge-recursive: preserve skip_worktree bit when necessary
 + t3507: add a testcase showing failure with sparse checkout

 When the sparse checkout feature is in use, "git cherry-pick" and
 other mergy operations lost the skip_worktree bit when a path that
 is excluded from checkout requires content level merge, which is
 resolved as the same as the HEAD version, without materializing the
 merge result in the working tree, which made the path appear as
 deleted.  This has been corrected by preserving the skip_worktree
 bit (and not materializing the file in the working tree).


* es/diff-color-moved-fix (2018-07-25) 1 commit
  (merged to 'next' on 2018-08-02 at 233bccfbfb)
 + diff: --color-moved: rename "dimmed_zebra" to "dimmed-zebra"

 One of the "diff --color-moved" mode "dimmed_zebra" that was named
 in an unusual way has been deprecated and replaced by
 "dimmed-zebra".


* es/mw-to-git-chain-fix (2018-07-31) 1 commit
  (merged to 'next' on 2018-08-06 at c10246e1c8)
 + mw-to-git/t9360: fix broken &&-chain

 Test fix.


* hs/gpgsm (2018-07-20) 7 commits
  (merged to 'next' on 2018-08-02 at db28bffe4f)
 + gpg-interface t: extend the existing GPG tests with GPGSM
 + gpg-interface: introduce new signature format "x509" using gpgsm
 + gpg-interface: introduce new config to select per gpg format program
 + gpg-interface: do not hardcode the key string len anymore
 + gpg-interface: introduce an abstraction for multiple gpg formats
 + t/t7510: check the validation of the new config gpg.format
 + gpg-interface: add new config to select how to sign a commit

 Teach "git tag -s" etc. a few configuration variables (gpg.format
 that can be set to "openpgp" or "x509", and gpg.<format>.program
 that is used to specify what program to use to deal with the format)
 to allow x.509 certs with CMS via "gpgsm" to be used instead of
 openpgp via "gnupg".


* jh/json-writer (2018-07-16) 1 commit
  (merged to 'next' on 2018-08-02 at d841450c7d)
 + json_writer: new routines to create JSON data
 (this branch is used by jh/structured-logging.)

 Preparatory code to later add json output for telemetry data.


* jk/banned-function (2018-07-26) 5 commits
  (merged to 'next' on 2018-08-06 at 3dcd1999df)
 + banned.h: mark strncpy() as banned
 + banned.h: mark sprintf() as banned
 + banned.h: mark strcat() as banned
 + automatically ban strcpy()
 + Merge branch 'sb/blame-color' into jk/banned-function

 It is too easy to misuse system API functions such as strcat();
 these selected functions are now forbidden in this codebase and
 will cause a compilation failure.


* jk/core-use-replace-refs (2018-07-18) 3 commits
  (merged to 'next' on 2018-08-02 at 90fb6b1056)
 + add core.usereplacerefs config option
 + check_replace_refs: rename to read_replace_refs
 + check_replace_refs: fix outdated comment

 A new configuration variable core.usereplacerefs has been added,
 primarily to help server installations that want to ignore the
 replace mechanism altogether.


* jk/size-t (2018-07-24) 6 commits
  (merged to 'next' on 2018-08-02 at 6f861e05f0)
 + strbuf_humanise: use unsigned variables
 + pass st.st_size as hint for strbuf_readlink()
 + strbuf_readlink: use ssize_t
 + strbuf: use size_t for length in intermediate variables
 + reencode_string: use size_t for string lengths
 + reencode_string: use st_add/st_mult helpers

 Code clean-up to use size_t/ssize_t when they are the right type.


* jk/ui-color-always-to-auto (2018-07-18) 1 commit
  (merged to 'next' on 2018-08-02 at 1a054baf0e)
 + Documentation: fix --color option formatting

 Doc formatting fix.


* jn/subtree-test-fixes (2018-07-30) 2 commits
  (merged to 'next' on 2018-08-06 at 62f21c328f)
 + subtree test: simplify preparation of expected results
 + subtree test: add missing && to &&-chain

 Test fix.


* js/t7406-recursive-submodule-update-order-fix (2018-07-23) 1 commit
  (merged to 'next' on 2018-08-02 at 217ea36a37)
 + t7406: avoid failures solely due to timing issues

 Test fix.


* js/vscode (2018-07-30) 9 commits
  (merged to 'next' on 2018-08-06 at 5c578b63a8)
 + vscode: let cSpell work on commit messages, too
 + vscode: add a dictionary for cSpell
 + vscode: use 8-space tabs, no trailing ws, etc for Git's source code
 + vscode: wrap commit messages at column 72 by default
 + vscode: only overwrite C/C++ settings
 + mingw: define WIN32 explicitly
 + cache.h: extract enum declaration from inside a struct declaration
 + vscode: hard-code a couple defines
 + contrib: add a script to initialize VS Code configuration

 Add a script (in contrib/) to help users of VSCode work better with
 our codebase.


* jt/connectivity-check-after-unshallow (2018-08-01) 1 commit
  (merged to 'next' on 2018-08-06 at 1932418f46)
 + fetch-pack: unify ref in and out param

 "git fetch" sometimes failed to update the remote-tracking refs,
 which has been corrected.


* jt/tag-following-with-proto-v2-fix (2018-07-24) 2 commits
  (merged to 'next' on 2018-08-02 at d9eabdea95)
 + fetch: send "refs/tags/" prefix upon CLI refspecs
 + t5702: test fetch with multiple refspecs at a time

 The wire-protocol v2 relies on the client to send "ref prefixes" to
 limit the bandwidth spent on the initial ref advertisement.  "git
 fetch $remote branch:branch" that asks tags that point into the
 history leading to the "branch" automatically followed sent to
 narrow prefix and broke the tag following, which has been fixed.


* ms/http-proto-doc (2018-07-30) 1 commit
  (merged to 'next' on 2018-08-06 at df1cac9945)
 + doc: fix want-capability separator

 Doc fix.


* nd/i18n (2018-07-23) 23 commits
  (merged to 'next' on 2018-08-02 at 904a22a5d1)
 + transport-helper.c: mark more strings for translation
 + transport.c: mark more strings for translation
 + sha1-file.c: mark more strings for translation
 + sequencer.c: mark more strings for translation
 + replace-object.c: mark more strings for translation
 + refspec.c: mark more strings for translation
 + refs.c: mark more strings for translation
 + pkt-line.c: mark more strings for translation
 + object.c: mark more strings for translation
 + exec-cmd.c: mark more strings for translation
 + environment.c: mark more strings for translation
 + dir.c: mark more strings for translation
 + convert.c: mark more strings for translation
 + connect.c: mark more strings for translation
 + config.c: mark more strings for translation
 + commit-graph.c: mark more strings for translation
 + builtin/replace.c: mark more strings for translation
 + builtin/pack-objects.c: mark more strings for translation
 + builtin/grep.c: mark strings for translation
 + builtin/config.c: mark more strings for translation
 + archive-zip.c: mark more strings for translation
 + archive-tar.c: mark more strings for translation
 + Update messages in preparation for i18n

 Many more strings are prepared for l10n.


* nd/pack-objects-threading-doc (2018-07-30) 1 commit
  (merged to 'next' on 2018-08-06 at cc8c305191)
 + pack-objects: document about thread synchronization

 Doc fix.


* rs/remote-mv-leakfix (2018-08-01) 1 commit
  (merged to 'next' on 2018-08-06 at 999fe6d3e5)
 + remote: clear string_list after use in mv()

 Leakfix.


* sb/histogram-less-memory (2018-07-23) 4 commits
  (merged to 'next' on 2018-08-02 at cfb02aa3b5)
 + xdiff/histogram: remove tail recursion
 + xdiff/xhistogram: move index allocation into find_lcs
 + xdiff/xhistogram: factor out memory cleanup into free_index()
 + xdiff/xhistogram: pass arguments directly to fall_back_to_classic_diff

 "git diff --histogram" had a bad memory usage pattern, which has
 been rearranged to reduce the peak usage.


* sb/trailers-docfix (2018-07-20) 1 commit
  (merged to 'next' on 2018-08-02 at ba348fafcd)
 + Documentation/git-interpret-trailers: explain possible values

 Doc update.


* sg/coccicheck-updates (2018-07-23) 5 commits
  (merged to 'next' on 2018-08-02 at b5548ff3a9)
 + coccinelle: extract dedicated make target to clean Coccinelle's results
 + coccinelle: put sane filenames into output patches
 + coccinelle: exclude sha1dc source files from static analysis
 + coccinelle: use $(addsuffix) in 'coccicheck' make target
 + coccinelle: mark the 'coccicheck' make target as .PHONY

 Update the way we use Coccinelle to find out-of-style code that
 need to be modernised.


* sg/fast-import-dump-refs-on-checkpoint-fix (2018-07-20) 1 commit
  (merged to 'next' on 2018-08-02 at f5c05b5a2c)
 + t9300: wait for background fast-import process to die after killing it

 Test update.


* sg/travis-cocci-diagnose-failure (2018-07-23) 2 commits
  (merged to 'next' on 2018-08-02 at 54808a8778)
 + travis-ci: fail if Coccinelle static analysis found something to transform
 + travis-ci: run Coccinelle static analysis with two parallel jobs

 Update the way we run static analysis tool at TravisCI to make it
 easier to use its findings.


* sg/travis-retrieve-trash-upon-failure (2018-08-01) 1 commit
  (merged to 'next' on 2018-08-06 at d67def2a92)
 + travis-ci: include the trash directories of failed tests in the trace log

 The Travis CI scripts were taught to ship back the test data from
 failed tests.

--------------------------------------------------
[New Topics]

* en/t7406-fixes (2018-08-08) 5 commits
  (merged to 'next' on 2018-08-15 at c6a740d828)
 + t7406: avoid using test_must_fail for commands other than git
 + t7406: prefer test_* helper functions to test -[feds]
 + t7406: avoid having git commands upstream of a pipe
 + t7406: simplify by using diff --name-only instead of diff --raw
 + t7406: fix call that was failing for the wrong reason

 Test fixes.

 Will merge to 'master'.


* en/update-index-doc (2018-08-08) 1 commit
  (merged to 'next' on 2018-08-15 at 3ee0ae14dc)
 + git-update-index.txt: reword possibly confusing example

 Doc update.

 Will merge to 'master'.


* jc/update-index-doc (2018-08-08) 1 commit
  (merged to 'next' on 2018-08-15 at 055994ccca)
 + update-index: there no longer is `apply --index-info`

 Doc update.

 Will merge to 'master'.


* js/typofixes (2018-08-08) 2 commits
  (merged to 'next' on 2018-08-15 at ce3932254a)
 + remote-curl: remove spurious period
 + git-compat-util.h: fix typo

 Comment update.

 Will merge to 'master'.


* jt/repack-promisor-packs (2018-08-09) 2 commits
 - repack: repack promisor objects if -a or -A is set
 - repack: refactor setup of pack-objects cmd

 After a partial clone, repeated fetches from promisor remote would
 have accumulated many packfiles marked with .promisor bit without
 getting them coalesced into fewer packfiles, hurting performance.
 "git repack" now learned to repack them.

 Will merge to 'next'.


* sk/instaweb-rh-update (2018-08-08) 2 commits
  (merged to 'next' on 2018-08-15 at ce5f1115e9)
 + git-instaweb: fix apache2 config with apache >= 2.4
 + git-instaweb: support Fedora/Red Hat apache module path

 "git instaweb" has been adjusted to run better with newer Apache on
 RedHat based distros.

 Will merge to 'master'.


* ab/submodule-relative-url-tests (2018-08-14) 1 commit
 - submodule: add more exhaustive up-path testing

 Test updates.

 Will merge to 'next'.


* ao/submodule-wo-gitmodules-checked-out (2018-08-14) 7 commits
 - submodule: support reading .gitmodules even when it's not checked out
 - t7506: clean up .gitmodules properly before setting up new scenario
 - submodule: use the 'submodule--helper config' command
 - submodule--helper: add a new 'config' subcommand
 - t7411: be nicer to future tests and really clean things up
 - submodule: factor out a config_set_in_gitmodules_file_gently function
 - submodule: add a print_config_from_gitmodules() helper

 The submodule support has been updated to read from the blob at
 HEAD:.gitmodules when the .gitmodules file is missing from the
 working tree.

 I find the design a bit iffy in that our usual "missing in the
 working tree?  let's use the latest blob" fallback is to take it
 from the index, not from the HEAD.


* bw/submodule-name-to-dir (2018-08-10) 2 commits
 - submodule: munge paths to submodule git directories
 - submodule: create helper to build paths to submodule gitdirs

 In modern repository layout, the real body of a cloned submodule
 repository is held in .git/modules/ of the superproject, indexed by
 the submodule name.  URLencode the submodule name before computing
 the name of the directory to make sure they form a flat namespace.

 Will merge to 'next'.


* cc/delta-islands (2018-08-10) 8 commits
 - pack-objects: move 'layer' into 'struct packing_data'
 - pack-objects: move tree_depth into 'struct packing_data'
 - t5320: delta islands tests
 - repack: add delta-islands support
 - pack-objects: add delta-islands support
 - pack-objects: refactor code into compute_layer_order()
 - Add delta-islands.{c,h}
 - packfile: make get_delta_base() non static

 Lift code from GitHub to restrict delta computation so that an
 object that exists in one fork is not made into a delta against
 another object that does not appear in the same forked repository.

 What's the doneness of this topic?


* ds/commit-graph-fsck (2018-08-13) 1 commit
  (merged to 'next' on 2018-08-15 at a2f82d3cbd)
 + t5318: use 'test_cmp_bin' to compare commit-graph files

 Test fix.

 Will merge to 'master'.


* en/incl-forward-decl (2018-08-15) 6 commits
 - Remove forward declaration of an enum
 - compat/precompose_utf8.h: use more common include guard style
 - urlmatch.h: fix include guard
 - Move definition of enum branch_track from cache.h to branch.h
 - alloc: make allocate_alloc_state and clear_alloc_state more consistent
 - Add missing includes and forward declarations

 Code hygiene improvement for the header files.

 Will merge to 'next'.


* es/chain-lint-more (2018-08-13) 6 commits
  (merged to 'next' on 2018-08-15 at bb5150ee96)
 + chainlint: add test of pathological case which triggered false positive
 + chainlint: recognize multi-line quoted strings more robustly
 + chainlint: let here-doc and multi-line string commence on same line
 + chainlint: recognize multi-line $(...) when command cuddled with "$("
 + chainlint: match 'quoted' here-doc tags
 + chainlint: match arbitrary here-docs tags rather than hard-coded names

 Improve built-in facility to catch broken &&-chain in the tests.

 Will merge to 'master'.


* jc/gpg-status (2018-08-09) 1 commit
  (merged to 'next' on 2018-08-15 at 824781761a)
 + gpg-interface: propagate exit status from gpg back to the callers

 Will merge to 'master'.


* jh/partial-clone-doc (2018-08-15) 1 commit
  (merged to 'next' on 2018-08-15 at cf09e8be6a)
 + partial-clone: render design doc using asciidoc

 Doc updates.

 Will merge to 'master'.


* jk/for-each-object-iteration (2018-08-14) 11 commits
  (merged to 'next' on 2018-08-15 at e2558810ff)
 + for_each_*_object: move declarations to object-store.h
 + cat-file: use a single strbuf for all output
 + cat-file: split batch "buf" into two variables
 + cat-file: use oidset check-and-insert
 + cat-file: support "unordered" output for --batch-all-objects
 + cat-file: rename batch_{loose,packed}_object callbacks
 + t1006: test cat-file --batch-all-objects with duplicates
 + for_each_packed_object: support iterating in pack-order
 + for_each_*_object: give more comprehensive docstrings
 + for_each_*_object: take flag arguments as enum
 + for_each_*_object: store flag definitions in a single location

 The API to iterate over all objects learned to optionally list
 objects in the order they appear in packfiles, which helps locality
 of access if the caller accesses these objects while as objects are
 enumerated.

 Will merge to 'master'.


* js/chain-lint-attrfix (2018-08-15) 1 commit
  (merged to 'next' on 2018-08-15 at e9ad19a848)
 + chainlint: fix for core.autocrlf=true

 Test fix.

 Will merge to 'master'.


* js/mingw-o-append (2018-08-13) 1 commit
  (merged to 'next' on 2018-08-15 at 284527a0fb)
 + mingw: enable atomic O_APPEND

 Among the three codepaths we use O_APPEND to open a file for
 appending, one used for writing GIT_TRACE output requires O_APPEND
 implementation that behaves sensibly when multiple processes are
 writing to the same file.  POSIX emulation used in the Windows port
 has been updated to improve in this area.

 Will merge to 'master'.


* jt/commit-graph-per-object-store (2018-08-13) 1 commit
  (merged to 'next' on 2018-08-15 at 5d6db738d8)
 + t5318: avoid unnecessary command substitutions

 Test update.

 Will merge to 'master'.


* jt/fetch-negotiator-skipping (2018-08-10) 1 commit
  (merged to 'next' on 2018-08-15 at 3cf8fa32f5)
 + t5552: suppress upload-pack trace output

 Test fix.

 Will merge to 'master'.


* md/filter-trees (2018-08-15) 6 commits
 - list-objects-filter: implement filter tree:0
 - revision: mark non-user-given objects instead
 - rev-list: handle missing tree objects properly
 - list-objects: always parse trees gently
 - list-objects: refactor to process_tree_contents
 - list-objects: store common func args in struct

 The "rev-list --filter" feature learned to exclude all trees via
 "tree:0" filter.

 Expecting a reroll.


* nd/no-the-index (2018-08-13) 24 commits
  (merged to 'next' on 2018-08-15 at 41e53dc53b)
 + blame.c: remove implicit dependency on the_index
 + apply.c: remove implicit dependency on the_index
 + apply.c: make init_apply_state() take a struct repository
 + apply.c: pass struct apply_state to more functions
 + resolve-undo.c: use the right index instead of the_index
 + archive-*.c: use the right repository
 + archive.c: avoid access to the_index
 + grep: use the right index instead of the_index
 + attr: remove index from git_attr_set_direction()
 + entry.c: use the right index instead of the_index
 + submodule.c: use the right index instead of the_index
 + pathspec.c: use the right index instead of the_index
 + unpack-trees: avoid the_index in verify_absent()
 + unpack-trees: convert clear_ce_flags* to avoid the_index
 + unpack-trees: don't shadow global var the_index
 + unpack-trees: add a note about path invalidation
 + unpack-trees: remove 'extern' on function declaration
 + ls-files: correct index argument to get_convert_attr_ascii()
 + preload-index.c: use the right index instead of the_index
 + dir.c: remove an implicit dependency on the_index in pathspec code
 + convert.c: remove an implicit dependency on the_index
 + attr: remove an implicit dependency on the_index
 + cache-tree: wrap the_index based wrappers with #ifdef
 + diff.c: move read_index() code back to the caller
 (this branch uses nd/no-extern.)

 The more library-ish parts of the codebase learned to work on the
 in-core index-state instance that is passed in by their callers,
 instead of always working on the singleton "the_index" instance.

 Will merge to 'master'.


* ng/mergetool-lose-final-prompt (2018-08-13) 1 commit
  (merged to 'next' on 2018-08-15 at f8f7ac365b)
 + mergetool: don't suggest to continue after last file

 "git mergetool" stopped and gave an extra prompt to continue after
 the last path has been handled, which did not make much sense.

 Will merge to 'master'.


* ng/status-i-short-for-ignored (2018-08-09) 1 commit
 - status: -i shorthand for --ignored command line option


* pk/rebase-in-c-2-basic (2018-08-10) 11 commits
 - builtin rebase: support `git rebase <upstream> <switch-to>`
 - builtin rebase: only store fully-qualified refs in `options.head_name`
 - builtin rebase: start a new rebase only if none is in progress
 - builtin rebase: support --force-rebase
 - builtin rebase: try to fast forward when possible
 - builtin rebase: require a clean worktree
 - builtin rebase: support the `verbose` and `diffstat` options
 - builtin rebase: support --quiet
 - builtin rebase: handle the pre-rebase hook (and add --no-verify)
 - builtin rebase: support `git rebase --onto A...B`
 - builtin rebase: support --onto
 (this branch is used by pk/rebase-in-c-3-acts, pk/rebase-in-c-4-opts, pk/rebase-in-c-5-test and pk/rebase-in-c-6-final; uses pk/rebase-in-c.)


* pk/rebase-in-c-3-acts (2018-08-10) 7 commits
 - builtin rebase: stop if `git am` is in progress
 - builtin rebase: actions require a rebase in progress
 - builtin rebase: support --edit-todo and --show-current-patch
 - builtin rebase: support --quit
 - builtin rebase: support --abort
 - builtin rebase: support --skip
 - builtin rebase: support --continue
 (this branch is used by pk/rebase-in-c-4-opts, pk/rebase-in-c-5-test and pk/rebase-in-c-6-final; uses pk/rebase-in-c and pk/rebase-in-c-2-basic.)


* pk/rebase-in-c-4-opts (2018-08-10) 18 commits
 - builtin rebase: support --root
 - builtin rebase: add support for custom merge strategies
 - builtin rebase: support `fork-point` option
 - merge-base --fork-point: extract libified function
 - builtin rebase: support --rebase-merges[=[no-]rebase-cousins]
 - builtin rebase: support `--allow-empty-message` option
 - builtin rebase: support `--exec`
 - builtin rebase: support `--autostash` option
 - builtin rebase: support `-C` and `--whitespace=<type>`
 - builtin rebase: support `--gpg-sign` option
 - builtin rebase: support `--autosquash`
 - builtin rebase: support `keep-empty` option
 - builtin rebase: support `ignore-date` option
 - builtin rebase: support `ignore-whitespace` option
 - builtin rebase: support --committer-date-is-author-date
 - builtin rebase: support --rerere-autoupdate
 - builtin rebase: support --signoff
 - builtin rebase: allow selecting the rebase "backend"
 (this branch is used by pk/rebase-in-c-5-test and pk/rebase-in-c-6-final; uses pk/rebase-in-c, pk/rebase-in-c-2-basic and pk/rebase-in-c-3-acts.)


* pk/rebase-in-c-5-test (2018-08-10) 6 commits
 - builtin rebase: error out on incompatible option/mode combinations
 - builtin rebase: use no-op editor when interactive is "implied"
 - builtin rebase: show progress when connected to a terminal
 - builtin rebase: fast-forward to onto if it is a proper descendant
 - builtin rebase: optionally pass custom reflogs to reset_head()
 - builtin rebase: optionally auto-detect the upstream
 (this branch is used by pk/rebase-in-c-6-final; uses pk/rebase-in-c, pk/rebase-in-c-2-basic, pk/rebase-in-c-3-acts and pk/rebase-in-c-4-opts.)


* pk/rebase-in-c-6-final (2018-08-10) 1 commit
 - rebase: default to using the builtin rebase
 (this branch uses pk/rebase-in-c, pk/rebase-in-c-2-basic, pk/rebase-in-c-3-acts, pk/rebase-in-c-4-opts and pk/rebase-in-c-5-test.)


* ps/stash-in-c (2018-08-08) 26 commits
 - stash: replace all "git apply" child processes with API calls
 - stash: replace all `write-tree` child processes with API calls
 - stash: optimize `get_untracked_files()` and `check_changes()`
 - stash: convert `stash--helper.c` into `stash.c`
 - stash: convert save to builtin
 - stash: replace spawning `git ls-files` child process
 - stash: add tests for `git stash push -q`
 - stash: make push to be quiet
 - stash: convert push to builtin
 - stash: avoid spawning a "diff-index" process
 - stash: replace spawning a "read-tree" process
 - stash: convert create to builtin
 - stash: convert store to builtin
 - stash: update `git stash show` documentation
 - stash: refactor `show_stash()` to use the diff API
 - stash: change `git stash show` usage text and documentation
 - stash: convert show to builtin
 - stash: implement the "list" command in the builtin
 - stash: convert pop to builtin
 - stash: convert branch to builtin
 - stash: convert drop and clear to builtin
 - stash: convert apply to builtin
 - stash: renamed test cases to be more descriptive
 - stash: update test cases conform to coding guidelines
 - stash: improve option parsing test coverage
 - sha1-name.c: added 'get_oidf', which acts like 'get_oid'


* pw/rebase-i-squash-number-fix (2018-08-15) 2 commits
 - squash??? if this is easier to read
 - rebase -i: fix numbering in squash message

 When "git rebase -i" is told to squash two or more commits into
 one, it labeled the log message for each commit with its number.
 It correctly called the first one "1st commit", but the next one
 was "commit #1", which was off-by-one.  This has been corrected.

 Will merge to 'next' after dropping the clean-up at the tip.


* sb/pull-rebase-submodule (2018-08-14) 1 commit
  (merged to 'next' on 2018-08-15 at 07c7b55cc9)
 + git-submodule.sh: accept verbose flag in cmd_update to be non-quiet

 "git pull --rebase -v" in a repository with a submodule barfed as
 an intermediate process did not understand what "-v(erbose)" flag
 meant, which has been fixed.

 Will merge to 'master'.


* sg/t5310-empty-input-fix (2018-08-14) 1 commit
  (merged to 'next' on 2018-08-15 at c3c03973a0)
 + t5310-pack-bitmaps: fix bogus 'pack-objects to file can use bitmap' test

 Test fix.

 Will merge to 'master'.

--------------------------------------------------
[Stalled]

* bp/checkout-new-branch-optim (2018-07-31) 1 commit
 - checkout: optimize "git checkout -b <new_branch>"

 "git checkout -b newbranch [HEAD]" should not have to do as much as
 checking out a commit different from HEAD.  An attempt is made to
 optimize this special case.

 So... what is the status of this thing?  Is the other "optimize
 unpack-trees" effort turning out to be a safer and less hacky way
 to achieve similar gain and this no longer is needed?


* sl/commit-dry-run-with-short-output-fix (2018-07-30) 4 commits
 . commit: fix exit code when doing a dry run
 . wt-status: teach wt_status_collect about merges in progress
 . wt-status: rename commitable to committable
 . t7501: add coverage for flags which imply dry runs

 "git commit --dry-run" gave a correct exit status even during a
 conflict resolution toward a merge, but it did not with the
 "--short" option, which has been corrected.

 Seems to break 7512, 3404 and 7060 in 'pu'.


* ma/wrapped-info (2018-05-28) 2 commits
 - usage: prefix all lines in `vreportf()`, not just the first
 - usage: extract `prefix_suffix_lines()` from `advise()`

 An attempt to help making multi-line messages fed to warning(),
 error(), and friends more easily translatable.

 Will discard and wait for a cleaned-up rewrite.
 cf. <20180529213957.GF7964@sigill.intra.peff.net>


* hn/bisect-first-parent (2018-04-21) 1 commit
 - bisect: create 'bisect_flags' parameter in find_bisection()

 Preliminary code update to allow passing more flags down the
 bisection codepath in the future.

 We do not add random code that does not have real users to our
 codebase, so let's have it wait until such a real code materializes
 before too long.


* av/fsmonitor-updates (2018-01-04) 6 commits
 - fsmonitor: use fsmonitor data in `git diff`
 - fsmonitor: remove debugging lines from t/t7519-status-fsmonitor.sh
 - fsmonitor: make output of test-dump-fsmonitor more concise
 - fsmonitor: update helper tool, now that flags are filled later
 - fsmonitor: stop inline'ing mark_fsmonitor_valid / _invalid
 - dir.c: update comments to match argument name

 Code clean-up on fsmonitor integration, plus optional utilization
 of the fsmonitor data in diff-files.

 Waiting for an update.
 cf. <alpine.DEB.2.21.1.1801042335130.32@MININT-6BKU6QN.europe.corp.microsoft.com>


* pb/bisect-helper-2 (2018-07-23) 8 commits
 - t6030: make various test to pass GETTEXT_POISON tests
 - bisect--helper: `bisect_start` shell function partially in C
 - bisect--helper: `get_terms` & `bisect_terms` shell function in C
 - bisect--helper: `bisect_next_check` shell function in C
 - bisect--helper: `check_and_set_terms` shell function in C
 - wrapper: move is_empty_file() and rename it as is_empty_or_missing_file()
 - bisect--helper: `bisect_write` shell function in C
 - bisect--helper: `bisect_reset` shell function in C

 Expecting a reroll.
 cf. <0102015f5e5ee171-f30f4868-886f-47a1-a4e4-b4936afc545d-000000@eu-west-1.amazonses.com>

 I just rebased the topic to a newer base as it did not build
 standalone with the base I originally queued the topic on, but
 otherwise there is no update to address any of the review comments
 in the thread above---we are still waiting for a reroll.


* jk/drop-ancient-curl (2017-08-09) 5 commits
 - http: #error on too-old curl
 - curl: remove ifdef'd code never used with curl >=7.19.4
 - http: drop support for curl < 7.19.4
 - http: drop support for curl < 7.16.0
 - http: drop support for curl < 7.11.1

 Some code in http.c that has bitrot is being removed.

 Expecting a reroll.


* mk/use-size-t-in-zlib (2017-08-10) 1 commit
 . zlib.c: use size_t for size

 The wrapper to call into zlib followed our long tradition to use
 "unsigned long" for sizes of regions in memory, which have been
 updated to use "size_t".

 Needs resurrecting by making sure the fix is good and still applies
 (or adjusted to today's codebase).

--------------------------------------------------
[Cooking]

* js/rebase-merges-exec-fix (2018-08-09) 2 commits
  (merged to 'next' on 2018-08-15 at 9de975d92d)
 + rebase --exec: make it work with --rebase-merges
 + t3430: demonstrate what -r, --autosquash & --exec should do

 The "--exec" option to "git rebase --rebase-merges" placed the exec
 commands at wrong places, which has been corrected.

 Will merge to 'master'.


* nd/no-extern (2018-08-03) 12 commits
  (merged to 'next' on 2018-08-08 at bcce75766b)
 + submodule.h: drop extern from function declaration
 + revision.h: drop extern from function declaration
 + repository.h: drop extern from function declaration
 + rerere.h: drop extern from function declaration
 + line-range.h: drop extern from function declaration
 + diff.h: remove extern from function declaration
 + diffcore.h: drop extern from function declaration
 + convert.h: drop 'extern' from function declaration
 + cache-tree.h: drop extern from function declaration
 + blame.h: drop extern on func declaration
 + attr.h: drop extern from function declaration
 + apply.h: drop extern on func declaration
 (this branch is used by nd/no-the-index.)

 Noiseword "extern" has been removed from function decls in the
 header files.

 Will merge to 'master'.


* ar/t4150-am-scissors-test-fix (2018-08-06) 1 commit
  (merged to 'next' on 2018-08-08 at e639183205)
 + t4150: fix broken test for am --scissors

 Test fix.

 Will merge to 'master'.


* en/t3031-title-fix (2018-08-06) 1 commit
  (merged to 'next' on 2018-08-08 at 3913b03884)
 + t3031: update test description to mention desired behavior

 Test fix.

 Will merge to 'master'.


* hn/config-in-code-comment (2018-08-06) 1 commit
  (merged to 'next' on 2018-08-08 at 1fae946a0f)
 + config: document git config getter return value

 Header update.

 Will merge to 'master'.


* jk/diff-rendered-docs (2018-08-06) 1 commit
  (merged to 'next' on 2018-08-08 at fe6e1b4dbe)
 + add a script to diff rendered documentation

 Developer support to allow the end result of documentation update
 to be inspected more easily.

 Will merge to 'master'.


* js/pull-rebase-type-shorthand (2018-08-06) 1 commit
  (merged to 'next' on 2018-08-08 at 9213756b36)
 + pull --rebase=<type>: allow single-letter abbreviations for the type

 "git pull --rebase=interactive" learned "i" as a short-hand for
 "interactive".

 Will merge to 'master'.


* nd/complete-config-vars (2018-08-06) 1 commit
  (merged to 'next' on 2018-08-08 at ffc8e1a3cd)
 + Makefile: add missing dependency for command-list.h

 Build fix.

 Will merge to 'master'.


* nd/config-blame-sort (2018-08-06) 1 commit
  (merged to 'next' on 2018-08-08 at 34ebb9888f)
 + config.txt: reorder blame stuff to keep config keys sorted

 Doc fix.

 Will merge to 'master'.


* wc/make-funnynames-shared-lazy-prereq (2018-08-06) 1 commit
 - t: factor out FUNNYNAMES as shared lazy prereq

 A test prerequisite defined by various test scripts with slightly
 different sematics has been consolidated into a single copy and
 made into a lazily defined one.

 Will merge to 'next'.


* ab/fsck-transfer-updates (2018-07-27) 10 commits
  (merged to 'next' on 2018-08-08 at d92085269f)
 + fsck: test and document unknown fsck.<msg-id> values
 + fsck: add stress tests for fsck.skipList
 + fsck: test & document {fetch,receive}.fsck.* config fallback
 + fetch: implement fetch.fsck.*
 + transfer.fsckObjects tests: untangle confusing setup
 + config doc: elaborate on fetch.fsckObjects security
 + config doc: elaborate on what transfer.fsckObjects does
 + config doc: unify the description of fsck.* and receive.fsck.*
 + config doc: don't describe *.fetchObjects twice
 + receive.fsck.<msg-id> tests: remove dead code

 The test performed at the receiving end of "git push" to prevent
 bad objects from entering repository can be customized via
 receive.fsck.* configuration variables; we now have gained a
 counterpart to do the same on the "git fetch" side, with
 fetch.fsck.* configuration variables.

 Will merge to 'master'.


* ab/test-must-be-empty (2018-07-30) 1 commit
  (merged to 'next' on 2018-08-08 at 06599ebd1f)
 + tests: make use of the test_must_be_empty function

 Test updates.

 Will merge to 'master'.


* ab/test-must-be-empty-for-master (2018-07-30) 1 commit
  (merged to 'next' on 2018-08-15 at 17652a77fb)
 + tests: make use of the test_must_be_empty function

 Test updates.

 Will merge to 'master'.


* es/rebase-i-author-script-fix (2018-07-31) 4 commits
  (merged to 'next' on 2018-08-08 at 6b34261b72)
 + sequencer: don't die() on bogus user-edited timestamp
 + sequencer: fix "rebase -i --root" corrupting author header timestamp
 + sequencer: fix "rebase -i --root" corrupting author header timezone
 + sequencer: fix "rebase -i --root" corrupting author header
 (this branch is used by pw/rebase-i-author-script-fix.)

 The "author-script" file "git rebase -i" creates got broken when
 we started to move the command away from shell script, which is
 getting fixed now.

 Will merge to 'master'.


* hn/highlight-sideband-keywords (2018-08-08) 1 commit
  (merged to 'next' on 2018-08-15 at f8945f3be5)
 + sideband: highlight keywords in remote sideband output

 The sideband code learned to optionally paint selected keywords at
 the beginning of incoming lines on the receiving end.

 Will merge to 'master'.


* sb/indent-heuristic-optim (2018-08-01) 1 commit
  (merged to 'next' on 2018-08-08 at 539dcc967a)
 + xdiff: reduce indent heuristic overhead

 "git diff --indent-heuristic" had a bad corner case performance.

 Will merge to 'master'.


* ab/fetch-nego (2018-08-01) 3 commits
  (merged to 'next' on 2018-08-08 at 87662bb344)
 + fetch doc: cross-link two new negotiation options
 + negotiator: unknown fetch.negotiationAlgorithm should error out
 + Merge branch 'jt/fetch-nego-tip' into ab/fetch-nego

 Update to a few other topics.

 Will merge to 'master'.


* ab/fetch-tags-noclobber (2018-08-13) 7 commits
  (merged to 'next' on 2018-08-15 at eca0ac8afa)
 + pull doc: fix a long-standing grammar error
 + fetch tests: correct a comment "remove it" -> "remove them"
 + push tests: assert re-pushing annotated tags
 + push tests: add more testing for forced tag pushing
 + push tests: fix logic error in "push" test assertion
 + push tests: remove redundant 'git push' invocation
 + fetch tests: change "Tag" test tag to "testTag"

 Test and doc clean-ups.

 Will merge to 'master'.


* jk/merge-subtree-heuristics (2018-08-02) 1 commit
  (merged to 'next' on 2018-08-08 at 5126c2d717)
 + score_trees(): fix iteration over trees with missing entries

 The automatic tree-matching in "git merge -s subtree" was broken 5
 years ago and nobody has noticed since then, which is now fixed.

 Will merge to 'master'.


* jt/refspec-dwim-precedence-fix (2018-08-02) 1 commit
  (merged to 'next' on 2018-08-08 at 34d0484d3a)
 + remote: make refspec follow the same disambiguation rule as local refs

 "git fetch $there refs/heads/s" ought to fetch the tip of the
 branch 's', but when "refs/heads/refs/heads/s", i.e. a branch whose
 name is "refs/heads/s" exists at the same time, fetched that one
 instead by mistake.  This has been corrected to honor the usual
 disambiguation rules for abbreviated refnames.

 Will merge to 'master'.


* nd/clone-case-smashing-warning (2018-08-14) 2 commits
 - mark_colliding_entries(): fix incorrect #if...#endif guard
 - clone: report duplicate entries on case-insensitive filesystems

 Running "git clone" against a project that contain two files with
 pathnames that differ only in cases on a case insensitive
 filesystem would result in one of the files lost because the
 underlying filesystem is incapable of holding both at the same
 time.  An attempt is made to detect such a case and warn.

 Expecting a new round of discussion.
 cf. <20180815190816.GA26521@tor.lan>


* nd/unpack-trees-with-cache-tree (2018-08-13) 5 commits
 - unpack-trees: reuse (still valid) cache-tree from src_index
 - unpack-trees: reduce malloc in cache-tree walk
 - unpack-trees: optimize walking same trees with cache-tree
 - unpack-trees: add performance tracing
 - trace.h: support nested performance tracing

 The unpack_trees() API used in checking out a branch and merging
 walks one or more trees along with the index.  When the cache-tree
 in the index tells us that we are walking a tree whose flattened
 contents is known (i.e. matches a span in the index), as linearly
 scanning a span in the index is much more efficient than having to
 open tree objects recursively and listing their entries, the walk
 can be optimized, which is done in this topic.

 Will merge to and cook in 'next'.


* sb/config-write-fix (2018-08-08) 3 commits
 - git-config: document accidental multi-line setting in deprecated syntax
 - config: fix case sensitive subsection names on writing
 - t1300: document current behavior of setting options

 Recent update to "git config" broke updating variable in a
 subsection, which has been corrected.

 Expecting a reroll.
 cf. <CAGZ79kZ1R8sxmtfgPOQcpoWM7GWV1qiRaqMq_zhGyKBB3ARLjg@mail.gmail.com>


* sb/range-diff-colors (2018-08-14) 8 commits
 - diff.c: rewrite emit_line_0 more understandably
 - diff.c: omit check for line prefix in emit_line_0
 - diff: use emit_line_0 once per line
 - diff.c: add set_sign to emit_line_0
 - diff.c: reorder arguments for emit_line_ws_markup
 - diff.c: simplify caller of emit_line_0
 - t3206: add color test for range-diff --dual-color
 - test_decode_color: understand FAINT and ITALIC
 (this branch uses js/range-diff; is tangled with es/format-patch-rangediff.)

 The color output support for recently introduced "range-diff"
 command got tweaked a bit.


* sg/t1404-update-ref-test-timeout (2018-08-01) 1 commit
 - t1404: increase core.packedRefsTimeout to avoid occasional test failure

 An attempt to unflake a test a bit.


* ab/sha1dc (2018-08-02) 1 commit
  (merged to 'next' on 2018-08-08 at 920c190941)
 + sha1dc: update from upstream

 AIX portability update for SHA1DC hash, imported from upstream.

 Will merge to 'master'.


* es/want-color-fd-defensive (2018-08-03) 1 commit
  (merged to 'next' on 2018-08-08 at a11d90d26f)
 + color: protect against out-of-bounds reads and writes

 Futureproofing a helper function that can easily misused.

 Will merge to 'master'.


* pw/rebase-i-author-script-fix (2018-08-07) 2 commits
 - sequencer: fix quoting in write_author_script
 - sequencer: handle errors from read_author_ident()
 (this branch uses es/rebase-i-author-script-fix.)

 Recent "git rebase -i" update started to write bogusly formatted
 author-script, with a matching broken reading code.  These are
 being fixed.

 Undecided.
 Is it the list consensus to favor this "with extra code, read the
 script written by bad writer" approach?


* rs/parse-opt-lithelp (2018-08-03) 7 commits
  (merged to 'next' on 2018-08-08 at 3a4e0142fe)
 + parse-options: automatically infer PARSE_OPT_LITERAL_ARGHELP
 + shortlog: correct option help for -w
 + send-pack: specify --force-with-lease argument help explicitly
 + pack-objects: specify --index-version argument help explicitly
 + difftool: remove angular brackets from argument help
 + add, update-index: fix --chmod argument help
 + push: use PARSE_OPT_LITERAL_ARGHELP instead of unbalanced brackets

 The parse-options machinery learned to refrain from enclosing
 placeholder string inside a "<bra" and "ket>" pair automatically
 without PARSE_OPT_LITERAL_ARGHELP.  Existing help text for option
 arguments that are not formatted correctly have been identified and
 fixed.

 Will merge to 'master'.


* pw/add-p-select (2018-07-26) 4 commits
 - add -p: optimize line selection for short hunks
 - add -p: allow line selection to be inverted
 - add -p: select modified lines correctly
 - add -p: select individual hunk lines

 "git add -p" interactive interface learned to let users choose
 individual added/removed lines to be used in the operation, instead
 of accepting or rejecting a whole hunk.

 Will hold.
 cf. <d622a95b-7302-43d4-4ec9-b2cf3388c653@talktalk.net>
 I found the feature to be hard to explain, and may result in more
 end-user complaints, but let's see.


* mk/http-backend-content-length (2018-07-30) 4 commits
  (merged to 'next' on 2018-08-08 at 0091062ec4)
 + t5562: avoid non-portable "export FOO=bar" construct
 + http-backend: respect CONTENT_LENGTH for receive-pack
 + http-backend: respect CONTENT_LENGTH as specified by rfc3875
 + http-backend: cleanup writing to child process

 The http-backend (used for smart-http transport) used to slurp the
 whole input until EOF, without paying attention to CONTENT_LENGTH
 that is supplied in the environment and instead expecting the Web
 server to close the input stream.  This has been fixed.

 Will merge to 'master'.


* ds/commit-graph-with-grafts (2018-07-19) 8 commits
  (merged to 'next' on 2018-08-02 at 0ee624e329)
 + commit-graph: close_commit_graph before shallow walk
 + commit-graph: not compatible with uninitialized repo
 + commit-graph: not compatible with grafts
 + commit-graph: not compatible with replace objects
 + test-repository: properly init repo
 + commit-graph: update design document
 + refs.c: upgrade for_each_replace_ref to be a each_repo_ref_fn callback
 + refs.c: migrate internal ref iteration to pass thru repository argument

 The recently introduced commit-graph auxiliary data is incompatible
 with mechanisms such as replace & grafts that "breaks" immutable
 nature of the object reference relationship.  Disable optimizations
 based on its use (and updating existing commit-graph) when these
 incompatible features are in use in the repository.

 Perhaps eject and replace with another reroll when it comes.
 cf. <a3640919-95cf-cca4-d552-4715a031dd7f@gmail.com>
 cf. <86bmap7l7a.fsf@gmail.com>


* ds/reachable (2018-07-20) 18 commits
 - commit-reach: use can_all_from_reach
 - commit-reach: make can_all_from_reach... linear
 - commit-reach: replace ref_newer logic
 - test-reach: test commit_contains
 - test-reach: test can_all_from_reach_with_flags
 - test-reach: test reduce_heads
 - test-reach: test get_merge_bases_many
 - test-reach: test is_descendant_of
 - test-reach: test in_merge_bases
 - test-reach: create new test tool for ref_newer
 - commit-reach: move can_all_from_reach_with_flags
 - upload-pack: generalize commit date cutoff
 - upload-pack: refactor ok_to_give_up()
 - upload-pack: make reachable() more generic
 - commit-reach: move commit_contains from ref-filter
 - commit-reach: move ref_newer from remote.c
 - commit.h: remove method declarations
 - commit-reach: move walk methods from commit.c

 The code for computing history reachability has been shuffled,
 obtained a bunch of new tests to cover them, and then being
 improved.

 Will merge to and cook in 'next'.


* es/format-patch-interdiff (2018-07-23) 6 commits
 - format-patch: allow --interdiff to apply to a lone-patch
 - log-tree: show_log: make commentary block delimiting reusable
 - interdiff: teach show_interdiff() to indent interdiff
 - format-patch: teach --interdiff to respect -v/--reroll-count
 - format-patch: add --interdiff option to embed diff in cover letter
 - format-patch: allow additional generated content in make_cover_letter()
 (this branch is used by es/format-patch-rangediff.)

 "git format-patch" learned a new "--interdiff" option to explain
 the difference between this version and the previous atttempt in
 the cover letter (or after the tree-dashes as a comment).

 Stuck in review?
 cf. <CAPig+cSuYUYSPTuKx08wcmQM-G12_-W2T4BS07fA=6grM1b8Gw@mail.gmail.com>


* es/format-patch-rangediff (2018-08-14) 10 commits
 - format-patch: allow --range-diff to apply to a lone-patch
 - format-patch: add --creation-factor tweak for --range-diff
 - format-patch: teach --range-diff to respect -v/--reroll-count
 - format-patch: extend --range-diff to accept revision range
 - format-patch: add --range-diff option to embed diff in cover letter
 - range-diff: relieve callers of low-level configuration burden
 - range-diff: publish default creation factor
 - range-diff: respect diff_option.file rather than assuming 'stdout'
 - Merge branch 'es/format-patch-interdiff' into es/format-patch-rangediff
 - Merge branch 'js/range-diff' into es/format-patch-rangediff
 (this branch uses es/format-patch-interdiff and js/range-diff; is tangled with sb/range-diff-colors.)

 "git format-patch" learned a new "--range-diff" option to explain
 the difference between this version and the previous atttempt in
 the cover letter (or after the tree-dashes as a comment).

 Need to wait for the prereq topics to solidify a bit more.


* nd/pack-deltify-regression-fix (2018-07-23) 1 commit
  (merged to 'next' on 2018-08-02 at f3b2bf0fef)
 + pack-objects: fix performance issues on packing large deltas

 In a recent update in 2.18 era, "git pack-objects" started
 producing a larger than necessary packfiles by missing
 opportunities to use large deltas.

 Will cook in 'next'.


* ab/newhash-is-sha256 (2018-08-07) 2 commits
  (merged to 'next' on 2018-08-15 at 2e808d75d3)
 + doc hash-function-transition: pick SHA-256 as NewHash
 + doc hash-function-transition: note the lack of a changelog

 Documentation update.

 Will merge to 'master'.


* jh/structured-logging (2018-07-25) 25 commits
 - structured-logging: add config data facility
 - structured-logging: t0420 tests for interacitve child_summary
 - structured-logging: t0420 tests for child process detail events
 - structured-logging: add child process classification
 - structured-logging: add detail-events for child processes
 - structured-logging: add structured logging to remote-curl
 - structured-logging: t0420 tests for aux-data
 - structured-logging: add aux-data for size of sparse-checkout file
 - structured-logging: add aux-data for index size
 - structured-logging: add aux-data facility
 - structured-logging: t0420 tests for timers
 - structured-logging: add timer around preload_index
 - structured-logging: add timer around wt-status functions
 - structured-logging: add timer around do_write_index
 - structured-logging: add timer around do_read_index
 - structured-logging: add timer facility
 - structured-logging: add detail-event for lazy_init_name_hash
 - structured-logging: add detail-event facility
 - structured-logging: t0420 basic tests
 - structured-logging: set sub_command field for checkout command
 - structured-logging: set sub_command field for branch command
 - structured-logging: add session-id to log events
 - structured-logging: add structured logging framework
 - structured-logging: add STRUCTURED_LOGGING=1 to Makefile
 - structured-logging: design document

 Will merge to 'next'.


* en/abort-df-conflict-fixes (2018-07-31) 2 commits
  (merged to 'next' on 2018-08-08 at a19cad0bb7)
 + read-cache: fix directory/file conflict handling in read_index_unmerged()
 + t1015: demonstrate directory/file conflict recovery failures

 "git merge --abort" etc. did not clean things up properly when
 there were conflicted entries in certain order that are involved
 in D/F conflicts.  This has been corrected.

 Will merge to 'master'.


* jn/gc-auto (2018-07-17) 3 commits
 - gc: do not return error for prior errors in daemonized mode
 - gc: exit with status 128 on failure
 - gc: improve handling of errors reading gc.log

 "gc --auto" ended up calling exit(-1) upon error, which has been
 corrected to use exit(1).  Also the error reporting behaviour when
 daemonized has been updated to exit with zero status when stopping
 due to a previously discovered error (which implies there is no
 point running gc to improve the situation); we used to exit with
 failure in such a case.

 Stuck in review?
 cf. <20180717201348.GD26218@sigill.intra.peff.net>


* sb/submodule-update-in-c (2018-08-14) 7 commits
 - submodule--helper: introduce new update-module-mode helper
 - submodule--helper: replace connect-gitdir-workingtree by ensure-core-worktree
 - builtin/submodule--helper: factor out method to update a single submodule
 - builtin/submodule--helper: store update_clone information in a struct
 - builtin/submodule--helper: factor out submodule updating
 - git-submodule.sh: rename unused variables
 - git-submodule.sh: align error reporting for update mode to use path

 "git submodule update" is getting rewritten piece-by-piece into C.

 Will merge to and cook in 'next'.


* tg/rerere (2018-08-06) 11 commits
 - rerere: recalculate conflict ID when unresolved conflict is committed
 - rerere: teach rerere to handle nested conflicts
 - rerere: return strbuf from handle path
 - rerere: factor out handle_conflict function
 - rerere: only return whether a path has conflicts or not
 - rerere: fix crash with files rerere can't handle
 - rerere: add documentation for conflict normalization
 - rerere: mark strings for translation
 - rerere: wrap paths in output in sq
 - rerere: lowercase error messages
 - rerere: unify error messages when read_cache fails

 Fixes to "git rerere" corner cases, especially when conflict
 markers cannot be parsed in the file.

 Will merge to and cook in 'next'.


* ag/rebase-i-in-c (2018-08-10) 20 commits
 - rebase -i: move rebase--helper modes to rebase--interactive
 - rebase -i: remove git-rebase--interactive.sh
 - rebase--interactive2: rewrite the submodes of interactive rebase in C
 - rebase -i: implement the main part of interactive rebase as a builtin
 - rebase -i: rewrite init_basic_state() in C
 - rebase -i: rewrite write_basic_state() in C
 - rebase -i: rewrite the rest of init_revisions_and_shortrevisions() in C
 - rebase -i: implement the logic to initialize $revisions in C
 - rebase -i: remove unused modes and functions
 - rebase -i: rewrite complete_action() in C
 - t3404: todo list with commented-out commands only aborts
 - sequencer: change the way skip_unnecessary_picks() returns its result
 - sequencer: refactor append_todo_help() to write its message to a buffer
 - rebase -i: rewrite checkout_onto() in C
 - rebase -i: rewrite setup_reflog_action() in C
 - sequencer: add a new function to silence a command, except if it fails
 - rebase -i: rewrite the edit-todo functionality in C
 - editor: add a function to launch the sequence editor
 - rebase -i: rewrite append_todo_help() in C
 - sequencer: make three functions and an enum from sequencer.c public

 Rewrite of the remaining "rebase -i" machinery in C.

 Will merge to 'next'.


* js/range-diff (2018-08-13) 21 commits
  (merged to 'next' on 2018-08-15 at 8d56067806)
 + range-diff: use dim/bold cues to improve dual color mode
 + range-diff: make --dual-color the default mode
 + range-diff: left-pad patch numbers
 + completion: support `git range-diff`
 + range-diff: populate the man page
 + range-diff --dual-color: skip white-space warnings
 + range-diff: offer to dual-color the diffs
 + diff: add an internal option to dual-color diffs of diffs
 + color: add the meta color GIT_COLOR_REVERSE
 + range-diff: use color for the commit pairs
 + range-diff: add tests
 + range-diff: do not show "function names" in hunk headers
 + range-diff: adjust the output of the commit pairs
 + range-diff: suppress the diff headers
 + range-diff: indent the diffs just like tbdiff
 + range-diff: right-trim commit messages
 + range-diff: also show the diff between patches
 + range-diff: improve the order of the shown commits
 + range-diff: first rudimentary implementation
 + Introduce `range-diff` to compare iterations of a topic branch
 + linear-assignment: a function to solve least-cost assignment problems
 (this branch is used by es/format-patch-rangediff and sb/range-diff-colors.)

 "git tbdiff" that lets us compare individual patches in two
 iterations of a topic has been rewritten and made into a built-in
 command.

 Will merge to 'master'.


* lt/date-human (2018-07-09) 1 commit
 - Add 'human' date format

 A new date format "--date=human" that morphs its output depending
 on how far the time is from the current time has been introduced.
 "--date=auto" can be used to use this new format when the output is
 goint to the pager or to the terminal and otherwise the default
 format.


* ot/ref-filter-object-info (2018-07-17) 5 commits
  (merged to 'next' on 2018-08-08 at 9ed619941b)
 + ref-filter: use oid_object_info() to get object
 + ref-filter: merge get_obj and get_object
 + ref-filter: initialize eaten variable
 + ref-filter: fill empty fields with empty values
 + ref-filter: add info_source to valid_atom

 A few atoms like %(objecttype) and %(objectsize) in the format
 specifier of "for-each-ref --format=<format>" can be filled without
 getting the full contents of the object, but just with the object
 header.  These cases have been optimzied by calling
 oid_object_info() API.

 Will merge to 'master'.


* pk/rebase-in-c (2018-08-06) 3 commits
 - builtin/rebase: support running "git rebase <upstream>"
 - rebase: refactor common shell functions into their own file
 - rebase: start implementing it as a builtin
 (this branch is used by pk/rebase-in-c-2-basic, pk/rebase-in-c-3-acts, pk/rebase-in-c-4-opts, pk/rebase-in-c-5-test and pk/rebase-in-c-6-final.)

 Rewrite of the "rebase" machinery in C.


* jk/branch-l-1-repurpose (2018-06-22) 1 commit
  (merged to 'next' on 2018-08-08 at d2a08dd08e)
 + branch: make "-l" a synonym for "--list"

 Updated plan to repurpose the "-l" option to "git branch".

 Will merge to and cook in 'next'.


* ds/multi-pack-index (2018-07-20) 23 commits
  (merged to 'next' on 2018-08-08 at 1a56c52967)
 + midx: clear midx on repack
 + packfile: skip loading index if in multi-pack-index
 + midx: prevent duplicate packfile loads
 + midx: use midx in approximate_object_count
 + midx: use existing midx when writing new one
 + midx: use midx in abbreviation calculations
 + midx: read objects from multi-pack-index
 + config: create core.multiPackIndex setting
 + midx: write object offsets
 + midx: write object id fanout chunk
 + midx: write object ids in a chunk
 + midx: sort and deduplicate objects from packfiles
 + midx: read pack names into array
 + multi-pack-index: write pack names in chunk
 + multi-pack-index: read packfile list
 + packfile: generalize pack directory list
 + t5319: expand test data
 + multi-pack-index: load into memory
 + midx: write header information to lockfile
 + multi-pack-index: add 'write' verb
 + multi-pack-index: add builtin
 + multi-pack-index: add format details
 + multi-pack-index: add design document

 When there are too many packfiles in a repository (which is not
 recommended), looking up an object in these would require
 consulting many pack .idx files; a new mechanism to have a single
 file that consolidates all of these .idx files is introduced.

 Will merge to and cook in 'next'.

--------------------------------------------------
[Discarded]

* am/sequencer-author-script-fix (2018-07-18) 1 commit
 . sequencer.c: terminate the last line of author-script properly

 The author-script that records the author information created by
 the sequencer machinery lacked the closing single quote on the last
 entry.

 Superseded by another topic.


* jc/push-cas-opt-comment (2018-08-01) 1 commit
 . push: comment on a funny unbalanced option help

 Code clarification.

 Superseded by another topic.


* cc/remote-odb (2018-08-02) 9 commits
 . Documentation/config: add odb.<name>.promisorRemote
 . t0410: test fetching from many promisor remotes
 . Use odb.origin.partialclonefilter instead of core.partialclonefilter
 . Use remote_odb_get_direct() and has_remote_odb()
 . remote-odb: add remote_odb_reinit()
 . remote-odb: implement remote_odb_get_many_direct()
 . remote-odb: implement remote_odb_get_direct()
 . Add initial remote odb support
 . fetch-object: make functions return an error code

 Implement lazy fetches of missing objects to complement the
 experimental partial clone feature.

 Ejected; seems to break existing repositories that use partialclone
 repository extension.

 I haven't seen much interest in this topic on list.  What's the
 doneness of this thing?

 I do not particularly mind adding code to support a niche feature
 as long as it is cleanly made and it is clear that the feature
 won't negatively affect those who do not use it, so a review from
 that point of view may also be appropriate.

^ permalink raw reply	[relevance 1%]

* Re: [PATCH v4] clone: report duplicate entries on case-insensitive filesystems
  2018-08-12  9:07  9%       ` [PATCH v4] " Nguyễn Thái Ngọc Duy
  2018-08-13 15:32  0%         ` Jeff Hostetler
  2018-08-13 17:18  0%         ` Junio C Hamano
@ 2018-08-15 19:08  0%         ` Torsten Bögershausen
  2018-08-17 16:16  9%         ` [PATCH v5] " Nguyễn Thái Ngọc Duy
  3 siblings, 0 replies; 200+ results
From: Torsten Bögershausen @ 2018-08-15 19:08 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy
  Cc: git, git, gitster, newren, pawelparuzel95, peff, sandals,
	SZEDER Gábor

On Sun, Aug 12, 2018 at 11:07:14AM +0200, Nguyễn Thái Ngọc Duy wrote:
> Paths that only differ in case work fine in a case-sensitive
> filesystems, but if those repos are cloned in a case-insensitive one,
> you'll get problems. The first thing to notice is "git status" will
> never be clean with no indication what exactly is "dirty".
> 
> This patch helps the situation a bit by pointing out the problem at
> clone time. Even though this patch talks about case sensitivity, the
> patch makes no assumption about folding rules by the filesystem. It
> simply observes that if an entry has been already checked out at clone
> time when we're about to write a new path, some folding rules are
> behind this.
> 
> This patch is tested with vim-colorschemes repository on a JFS partition
> with case insensitive support on Linux. This repository has two files
> darkBlue.vim and darkblue.vim.
> 
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---
>  v4 removes nr_duplicates (and fixes that false warning Szeder
>  reported). It also hints about case insensitivity as a cause of
>  problem because it's most likely the case when this warning shows up.
> 
>  builtin/clone.c  |  1 +
>  cache.h          |  1 +
>  entry.c          | 28 ++++++++++++++++++++++++++++
>  t/t5601-clone.sh |  8 +++++++-
>  unpack-trees.c   | 28 ++++++++++++++++++++++++++++
>  unpack-trees.h   |  1 +
>  6 files changed, 66 insertions(+), 1 deletion(-)
> 
> diff --git a/builtin/clone.c b/builtin/clone.c
> index 5c439f1394..0702b0e9d0 100644
> --- a/builtin/clone.c
> +++ b/builtin/clone.c
> @@ -747,6 +747,7 @@ static int checkout(int submodule_progress)
>  	memset(&opts, 0, sizeof opts);
>  	opts.update = 1;
>  	opts.merge = 1;
> +	opts.clone = 1;
>  	opts.fn = oneway_merge;
>  	opts.verbose_update = (option_verbosity >= 0);
>  	opts.src_index = &the_index;
> diff --git a/cache.h b/cache.h
> index 8b447652a7..6d6138f4f1 100644
> --- a/cache.h
> +++ b/cache.h
> @@ -1455,6 +1455,7 @@ struct checkout {
>  	unsigned force:1,
>  		 quiet:1,
>  		 not_new:1,
> +		 clone:1,
>  		 refresh_cache:1;
>  };
>  #define CHECKOUT_INIT { NULL, "" }
> diff --git a/entry.c b/entry.c
> index b5d1d3cf23..c70340df8e 100644
> --- a/entry.c
> +++ b/entry.c
> @@ -399,6 +399,31 @@ static int check_path(const char *path, int len, struct stat *st, int skiplen)
>  	return lstat(path, st);
>  }
>  
> +static void mark_colliding_entries(const struct checkout *state,
> +				   struct cache_entry *ce, struct stat *st)
> +{
> +	int i;
> +
> +	ce->ce_flags |= CE_MATCHED;
> +
> +#if !defined(GIT_WINDOWS_NATIVE) /* inode is always zero on Windows */
> +	for (i = 0; i < state->istate->cache_nr; i++) {
> +		struct cache_entry *dup = state->istate->cache[i];
> +
> +		if (dup == ce)
> +			break;
> +
> +		if (dup->ce_flags & (CE_MATCHED | CE_VALID | CE_SKIP_WORKTREE))
> +			continue;
> +

Should the following be protected by core.checkstat ? 
	if (check_stat) {


> +		if (dup->ce_stat_data.sd_ino == st->st_ino) {
> +			dup->ce_flags |= CE_MATCHED;
> +			break;
> +		}
> +	}
> +#endif

Another thing is that we switch of the ASCII case-folding-detection-logic
off for Windows users, even if we otherwise rely on icase.
I think we can use fspathcmp() as a fallback. when inodes fail,
because we may be on a network file system.
(I don't have a test setup at the moment, but what happens with inodes
when a Windows machine exports a share to Linux or Mac ?)

Is there a chance to get the fspathcmp() back, like this ?

static void mark_colliding_entries(const struct checkout *state,
				   struct cache_entry *ce, struct stat *st)
{
	int i;
	ce->ce_flags |= CE_MATCHED;

	for (i = 0; i < state->istate->cache_nr; i++) {
		struct cache_entry *dup = state->istate->cache[i];
		int folded = 0;

		if (dup == ce)
			break;

		if (dup->ce_flags & (CE_MATCHED | CE_VALID | CE_SKIP_WORKTREE))
			continue;

		if (!fspathcmp(dup->name, ce->name))
			folded = 1;

#if !defined(GIT_WINDOWS_NATIVE) /* inode is always zero on Windows */
		if (check_stat && (dup->ce_stat_data.sd_ino == st->st_ino))
			folded = 1;
#endif
		if (folded) {
			dup->ce_flags |= CE_MATCHED;
			break;
		}
	}
}


^ permalink raw reply	[relevance 0%]

* Re: [PATCH 2/2] submodule: munge paths to submodule git directories
  2018-08-14 18:57  0%         ` Jonathan Nieder
@ 2018-08-14 21:08  0%           ` Stefan Beller
  0 siblings, 0 replies; 200+ results
From: Stefan Beller @ 2018-08-14 21:08 UTC (permalink / raw)
  To: Jonathan Nieder; +Cc: Brandon Williams, Jeff King, git

On Tue, Aug 14, 2018 at 11:57 AM Jonathan Nieder <jrnieder@gmail.com> wrote:
>
> Hi,
>
> Brandon Williams wrote:
> > On 08/09, Jeff King wrote:
>
> >> One interesting thing about url-encoding is that it's not one-to-one.
> >> This case could also be %2F, which is a different file (on a
> >> case-sensitive filesystem). I think "%20" and "+" are similarly
> >> interchangeable.
> >>
> >> If we were decoding the filenames, that's fine. The round-trip is
> >> lossless.
> >>
> >> But that's not quite how the new code behaves. We encode the input and
> >> then check to see if it matches an encoding we previously performed. So
> >> if our urlencode routines ever change, this will subtly break.
> >>
> >> I don't know how much it's worth caring about. We're not that likely to
> >> change the routines ourself (though certainly a third-party
> >> implementation would need to know our exact url-encoding decisions).
> >
> > This is exactly the reason why I wanted to get some opinions on what the
> > best thing to do here would be.  I _think_ the best thing would probably
> > be to write a specific routine to do the conversion, and it wouldn't
> > even have to be all that complex.  Basically I'm just interested in
> > converting '/' characters so that things no longer behave like
> > nested directories.
>
> First of all, I think the behavior with this patch is already much
> better than the previous status quo.  I'm using the patch now and am
> very happy with it.
>
> Second, what if we store the pathname in config?  We already store the
> URL there:
>
>         [submodule "plugins/hooks"]
>                 url = https://gerrit.googlesource.com/plugins/hooks
>
> So we could (as a followup patch) do something like
>
>         [submodule "plugins/hooks"]
>                 url = https://gerrit.googlesource.com/plugins/hooks
>                 gitdirname = plugins%2fhooks
>
> and use that for lookups instead of regenerating the directory name.
> What do you think?

As I just looked at worktree code, this sounds intriguing for the wrong
reason (again), as a user may want to point the gitdirname to a repository
that they have already on disk outside the actual superproject. They
would be reinventing worktrees in the submodule space. ;-)

This would open up the security hole that we just had, again.
So we'd have to make sure that the gitdirname (instead of the
now meaningless subsection name) is proof to ../ attacks.

I feel uneasy about this as then the user might come in
and move submodules and repoint the gitdirname...
to a not url encoded path. Exposing this knob just
asks for trouble, no?

On the other hand, the only requirement for the "name" is
now uniqueness, and that is implied with subsections,
so I guess it looks elegant.

What would happen if gitdirname is changed as part of
history? (The same problem we have now with changing
the subsection name)

The more I think about it the less appealing this is, but it looks
elegant.

Stefan

^ permalink raw reply	[relevance 0%]

* Re: [PATCH 2/2] submodule: munge paths to submodule git directories
  2018-08-14 18:04  0%       ` Brandon Williams
  2018-08-14 18:57  0%         ` Jonathan Nieder
@ 2018-08-14 18:58  0%         ` Jeff King
  2018-08-28 21:35  0%         ` Stefan Beller
  2 siblings, 0 replies; 200+ results
From: Jeff King @ 2018-08-14 18:58 UTC (permalink / raw)
  To: Brandon Williams; +Cc: git

On Tue, Aug 14, 2018 at 11:04:06AM -0700, Brandon Williams wrote:

> > I think this backwards-compatibility is necessary to avoid pain. But
> > until it goes away, I don't think this is helping the vulnerability from
> > 0383bbb901. Because there the issue was that the submodule name pointed
> > back into the working tree, so this access() would find the untrusted
> > working tree code and say "ah, an old-fashioned name!".
> [...]
> 
> Oh I know that this doesn't help with that vulnerability.  As you've
> said we fix it and now disallow ".." at the submodule-config level so
> really this path is simply about using what we get out of
> submodule-config in a more sane manor.

OK, I'm alright with that as long as we are all on the same page. I
think I mistook "this addresses the vulnerability" from your commit
message the wrong way. I took it as "this patch", but reading it again,
you simply mean "the '..' handling we already did".

I do think eventually dropping this back-compatibility could save us
from another directory-escape problem, but it's hard to justify the
real-world pain for a hypothetical benefit. Maybe in a few years we
could get rid of it in a major version bump.

> > One interesting thing about url-encoding is that it's not one-to-one.
> > This case could also be %2F, which is a different file (on a
> > case-sensitive filesystem). I think "%20" and "+" are similarly
> > interchangeable.
> > 
> > If we were decoding the filenames, that's fine. The round-trip is
> > lossless.
> > 
> > But that's not quite how the new code behaves. We encode the input and
> > then check to see if it matches an encoding we previously performed. So
> > if our urlencode routines ever change, this will subtly break.
> > 
> > I don't know how much it's worth caring about. We're not that likely to
> > change the routines ourself (though certainly a third-party
> > implementation would need to know our exact url-encoding decisions).
> 
> This is exactly the reason why I wanted to get some opinions on what the
> best thing to do here would be.  I _think_ the best thing would probably
> be to write a specific routine to do the conversion, and it wouldn't
> even have to be all that complex.  Basically I'm just interested in
> converting '/' characters so that things no longer behave like
> nested directories.

I think we benefit from catching names that would trigger filesystem
case-folding, too. If I have submodules with names "foo" and "FOO", we
would not want to confuse them (or at least we should confuse them
equally on all platforms). I doubt you can do anything malicious, but it
might simply be annoying.

That implies to me using a custom function (even if its encoded form
ends up being understandable as url-encoding).

-Peff

^ permalink raw reply	[relevance 0%]

* Re: [PATCH 2/2] submodule: munge paths to submodule git directories
  2018-08-14 18:04  0%       ` Brandon Williams
@ 2018-08-14 18:57  0%         ` Jonathan Nieder
  2018-08-14 21:08  0%           ` Stefan Beller
  2018-08-14 18:58  0%         ` Jeff King
  2018-08-28 21:35  0%         ` Stefan Beller
  2 siblings, 1 reply; 200+ results
From: Jonathan Nieder @ 2018-08-14 18:57 UTC (permalink / raw)
  To: Brandon Williams; +Cc: Jeff King, git

Hi,

Brandon Williams wrote:
> On 08/09, Jeff King wrote:

>> One interesting thing about url-encoding is that it's not one-to-one.
>> This case could also be %2F, which is a different file (on a
>> case-sensitive filesystem). I think "%20" and "+" are similarly
>> interchangeable.
>>
>> If we were decoding the filenames, that's fine. The round-trip is
>> lossless.
>>
>> But that's not quite how the new code behaves. We encode the input and
>> then check to see if it matches an encoding we previously performed. So
>> if our urlencode routines ever change, this will subtly break.
>>
>> I don't know how much it's worth caring about. We're not that likely to
>> change the routines ourself (though certainly a third-party
>> implementation would need to know our exact url-encoding decisions).
>
> This is exactly the reason why I wanted to get some opinions on what the
> best thing to do here would be.  I _think_ the best thing would probably
> be to write a specific routine to do the conversion, and it wouldn't
> even have to be all that complex.  Basically I'm just interested in
> converting '/' characters so that things no longer behave like
> nested directories.

First of all, I think the behavior with this patch is already much
better than the previous status quo.  I'm using the patch now and am
very happy with it.

Second, what if we store the pathname in config?  We already store the
URL there:

	[submodule "plugins/hooks"]
		url = https://gerrit.googlesource.com/plugins/hooks

So we could (as a followup patch) do something like

	[submodule "plugins/hooks"]
		url = https://gerrit.googlesource.com/plugins/hooks
		gitdirname = plugins%2fhooks

and use that for lookups instead of regenerating the directory name.
What do you think?

Thanks,
Jonathan

^ permalink raw reply	[relevance 0%]

* Re: [PATCH 2/2] submodule: munge paths to submodule git directories
  2018-08-09 21:26  3%     ` Jeff King
@ 2018-08-14 18:04  0%       ` Brandon Williams
  2018-08-14 18:57  0%         ` Jonathan Nieder
                           ` (2 more replies)
  0 siblings, 3 replies; 200+ results
From: Brandon Williams @ 2018-08-14 18:04 UTC (permalink / raw)
  To: Jeff King; +Cc: git

On 08/09, Jeff King wrote:
> On Wed, Aug 08, 2018 at 03:33:23PM -0700, Brandon Williams wrote:
> 
> > Commit 0383bbb901 (submodule-config: verify submodule names as paths,
> > 2018-04-30) introduced some checks to ensure that submodule names don't
> > include directory traversal components (e.g. "../").
> > 
> > This addresses the vulnerability identified in 0383bbb901 but the root
> > cause is that we use submodule names to construct paths to the
> > submodule's git directory.  What we really should do is munge the
> > submodule name before using it to construct a path.
> > 
> > Teach "submodule_name_to_gitdir()" to munge a submodule's name (by url
> > encoding it) before using it to build a path to the submodule's gitdir.
> 
> I like this approach very much, and I think using url encoding is much
> better than an opaque hash (purely because it makes debugging and
> inspection saner).
> 
> Two thoughts, though:
> 
> > +	modules_len = buf->len;
> >  	strbuf_addstr(buf, submodule_name);
> > +
> > +	/*
> > +	 * If the submodule gitdir already exists using the old-fashioned
> > +	 * location (which uses the submodule name as-is, without munging it)
> > +	 * then return that.
> > +	 */
> > +	if (!access(buf->buf, F_OK))
> > +		return;
> 
> I think this backwards-compatibility is necessary to avoid pain. But
> until it goes away, I don't think this is helping the vulnerability from
> 0383bbb901. Because there the issue was that the submodule name pointed
> back into the working tree, so this access() would find the untrusted
> working tree code and say "ah, an old-fashioned name!".
> 
> In theory a fresh clone could set a config option for "I only speak
> use new-style modules". And there could even be a conversion program
> that moves the modules as appropriate, fixes up the .git files in the
> working tree, and then sets that config.
> 
> In fact, I think that config option _could_ be done by bumping
> core.repositoryformatversion and then setting extensions.submodulenames
> to "url" or something. Then you could never run into the confusing case
> where you have a clone done by a new version of git (using new-style
> names), but using an old-style version gets confused because it can't
> find the module directories (instead, it would barf and say "I don't
> know about that extension").
> 
> I don't know if any of that is worth it, though. We already fixed the
> problem from 0383bbb901. There may be a _different_ "break out of the
> modules directory" vulnerability, but since we disallow ".." it's hard
> to see what it would be (the best I could come up with is maybe pointing
> one module into the interior of another module, but I think you'd have
> to trouble overwriting anything useful).
> 
> And while an old-style version of Git being confused might be annoying,
> I suspect that bumping the repository version would be even _more_
> annoying, because it would hit every command, not just ones that try to
> touch those submodules.

Oh I know that this doesn't help with that vulnerability.  As you've
said we fix it and now disallow ".." at the submodule-config level so
really this path is simply about using what we get out of
submodule-config in a more sane manor.

> 
> > diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh
> > index 2c2c97e144..963693332c 100755
> > --- a/t/t7400-submodule-basic.sh
> > +++ b/t/t7400-submodule-basic.sh
> > @@ -933,7 +933,7 @@ test_expect_success 'recursive relative submodules stay relative' '
> >  		cd clone2 &&
> >  		git submodule update --init --recursive &&
> >  		echo "gitdir: ../.git/modules/sub3" >./sub3/.git_expect &&
> > -		echo "gitdir: ../../../.git/modules/sub3/modules/dirdir/subsub" >./sub3/dirdir/subsub/.git_expect
> > +		echo "gitdir: ../../../.git/modules/sub3/modules/dirdir%2fsubsub" >./sub3/dirdir/subsub/.git_expect
> 
> One interesting thing about url-encoding is that it's not one-to-one.
> This case could also be %2F, which is a different file (on a
> case-sensitive filesystem). I think "%20" and "+" are similarly
> interchangeable.
> 
> If we were decoding the filenames, that's fine. The round-trip is
> lossless.
> 
> But that's not quite how the new code behaves. We encode the input and
> then check to see if it matches an encoding we previously performed. So
> if our urlencode routines ever change, this will subtly break.
> 
> I don't know how much it's worth caring about. We're not that likely to
> change the routines ourself (though certainly a third-party
> implementation would need to know our exact url-encoding decisions).

This is exactly the reason why I wanted to get some opinions on what the
best thing to do here would be.  I _think_ the best thing would probably
be to write a specific routine to do the conversion, and it wouldn't
even have to be all that complex.  Basically I'm just interested in
converting '/' characters so that things no longer behave like
nested directories.

> 
> Some possible actions:
> 
>  0. Do nothing, and cross our fingers. ;)
> 
>  1. Don't use strbuf_addstr_urlencode(), but rather our own munging
>     function which we know will remain stable (or alternatively, a flag
>     to strbuf_addstr_urlencode to get the consistent behavior).
> 
>  2. Make sure we have tests which cover this, so at least somebody
>     changing the urlencode decisions will see a breakage. Your test here
>     covers the upper/lowercase one, but we might want one that covers
>     "+". (There may be more ambiguous cases, but those are the ones I
>     know about).
> 
>  3. Rather than check for the existence of names, decode what's actually
>     in the modules/ directory to create an in-memory index of names.
> 
>     I hesitate to suggest that, because it's obviously way more
>     complicated, and may perform worse if you have a lot of modules
>     (since you have to readdir() and decode the whole directory just to
>     look up one module).
> 
>     But I think it also gives a more elegant solution to the
>     backwards-compatibility problem, since we could recognize both new
>     and old-style names. There's some ambiguity (e.g., is "foo%2fbar"
>     "foo/bar", or did somebody really have a name with a percent in
>     it?),. but in theory you could respect either name (giving
>     preference to new-style in case of a conflict).
> 
>     And I think the result would be immune to any directory-escape
>     vulnerabilities, because we'd always start with what actually exists
>     in $GIT_DIR/modules/, which we know _we_ will have written.
> 
>     Again, I'm not sure if it's worth the effort, but I thought I'd
>     throw it out there.
> 
> -Peff

-- 
Brandon Williams

^ permalink raw reply	[relevance 0%]

* Re: [PATCH v4] clone: report duplicate entries on case-insensitive filesystems
  2018-08-12  9:07  9%       ` [PATCH v4] " Nguyễn Thái Ngọc Duy
  2018-08-13 15:32  0%         ` Jeff Hostetler
@ 2018-08-13 17:18  0%         ` Junio C Hamano
  2018-08-15 19:08  0%         ` Torsten Bögershausen
  2018-08-17 16:16  9%         ` [PATCH v5] " Nguyễn Thái Ngọc Duy
  3 siblings, 0 replies; 200+ results
From: Junio C Hamano @ 2018-08-13 17:18 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy
  Cc: git, git, newren, pawelparuzel95, peff, sandals, tboegi,
	SZEDER Gábor

Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:

> Paths that only differ in case work fine in a case-sensitive
> filesystems, but if those repos are cloned in a case-insensitive one,
> you'll get problems. The first thing to notice is "git status" will
> never be clean with no indication what exactly is "dirty".
>
> This patch helps the situation a bit by pointing out the problem at
> clone time. Even though this patch talks about case sensitivity, the
> patch makes no assumption about folding rules by the filesystem. It
> simply observes that if an entry has been already checked out at clone
> time when we're about to write a new path, some folding rules are
> behind this.
>
> This patch is tested with vim-colorschemes repository on a JFS partition
> with case insensitive support on Linux. This repository has two files
> darkBlue.vim and darkblue.vim.
>
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---
>  v4 removes nr_duplicates (and fixes that false warning Szeder
>  reported). It also hints about case insensitivity as a cause of
>  problem because it's most likely the case when this warning shows up.

Ah, you no longer have that counter and the pointer to the counter,
as you do not even report how many paths collide ;-)  Makes sense.

> diff --git a/entry.c b/entry.c
> index b5d1d3cf23..c70340df8e 100644
> --- a/entry.c
> +++ b/entry.c
> @@ -399,6 +399,31 @@ static int check_path(const char *path, int len, struct stat *st, int skiplen)
>  	return lstat(path, st);
>  }
>  
> +static void mark_colliding_entries(const struct checkout *state,
> +				   struct cache_entry *ce, struct stat *st)
> +{
> +	int i;
> +
> +	ce->ce_flags |= CE_MATCHED;
> +
> +#if !defined(GIT_WINDOWS_NATIVE) /* inode is always zero on Windows */
> +	for (i = 0; i < state->istate->cache_nr; i++) {
> +		struct cache_entry *dup = state->istate->cache[i];
> +
> +		if (dup == ce)
> +			break;
> +
> +		if (dup->ce_flags & (CE_MATCHED | CE_VALID | CE_SKIP_WORKTREE))
> +			continue;
> +
> +		if (dup->ce_stat_data.sd_ino == st->st_ino) {
> +			dup->ce_flags |= CE_MATCHED;
> +			break;
> +		}
> +	}
> +#endif
> +}

OK.  The whole loop might want to become a call to a helper function
whose implementation is platform dependent in the future, but that
should be kept outside the topic and left for a future enhancement.

> @@ -455,6 +480,9 @@ int checkout_entry(struct cache_entry *ce,
>  			return -1;
>  		}
>  
> +		if (state->clone)
> +			mark_colliding_entries(state, ce, &st);

OK.  I haven't carefully looked at the codepath but is it more
involved to instead *not* check out this ce (and leave the working
tree file that is already there for another path in the index
alone)?  I suspect it won't be as simple as

		if (state->clone) {
			mark_colliding_entries(state, ce, &st);
			return -1;
		}

but I think it would give much more pleasant end-user experience if
we can do so, especially on GIT_WINDOWS_NATIVE.  I would imagine
that the first thing those who see the message "foo.txt have
collided with something else we are not telling you" would want to
do is to see what "foo.txt" contains---and it may be obvious to
human that it contains the contents intended for "Foo.txt" instead,
if we somehow refrained from overwriting it here, which would
compensate for the lack of "this is the other path that collided
with your file."

It is perfectly an acceptable answer if it is "I looked at it, and
it is a lot more involved as there are these fallouts from the
codepaths that the control flows later from this point you haven't
checked and considered---let's keep overwriting, it is much safer."

> diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh
> index 0b62037744..f2eb73bc74 100755
> --- a/t/t5601-clone.sh
> +++ b/t/t5601-clone.sh
> @@ -624,10 +624,16 @@ test_expect_success 'clone on case-insensitive fs' '
>  			git hash-object -w -t tree --stdin) &&
>  		c=$(git commit-tree -m bogus $t) &&
>  		git update-ref refs/heads/bogus $c &&
> -		git clone -b bogus . bogus
> +		git clone -b bogus . bogus 2>warning
>  	)
>  '
>  
> +test_expect_success !MINGW,!CYGWIN,CASE_INSENSITIVE_FS 'colliding file detection' '
> +	grep X icasefs/warning &&
> +	grep x icasefs/warning &&
> +	test_i18ngrep "the following paths have collided" icasefs/warning
> +'
> +

Ah, I was wondering why possible error message needs to be hidden in
the previous test---it is not hiding; it is capturing to look for
the paths in the message.  Makes sense.


^ permalink raw reply	[relevance 0%]

* Re: [PATCH v4] clone: report duplicate entries on case-insensitive filesystems
  2018-08-12  9:07  9%       ` [PATCH v4] " Nguyễn Thái Ngọc Duy
@ 2018-08-13 15:32  0%         ` Jeff Hostetler
  2018-08-13 17:18  0%         ` Junio C Hamano
                           ` (2 subsequent siblings)
  3 siblings, 0 replies; 200+ results
From: Jeff Hostetler @ 2018-08-13 15:32 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy
  Cc: git, gitster, newren, pawelparuzel95, peff, sandals, tboegi,
	SZEDER Gábor



On 8/12/2018 5:07 AM, Nguyễn Thái Ngọc Duy wrote:
> Paths that only differ in case work fine in a case-sensitive
> filesystems, but if those repos are cloned in a case-insensitive one,
> you'll get problems. The first thing to notice is "git status" will
> never be clean with no indication what exactly is "dirty".
> 
> This patch helps the situation a bit by pointing out the problem at
> clone time. Even though this patch talks about case sensitivity, the
> patch makes no assumption about folding rules by the filesystem. It
> simply observes that if an entry has been already checked out at clone
> time when we're about to write a new path, some folding rules are
> behind this.
> 
> This patch is tested with vim-colorschemes repository on a JFS partition
> with case insensitive support on Linux. This repository has two files
> darkBlue.vim and darkblue.vim.
> 
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---
>   v4 removes nr_duplicates (and fixes that false warning Szeder
>   reported). It also hints about case insensitivity as a cause of
>   problem because it's most likely the case when this warning shows up.
> 
>   builtin/clone.c  |  1 +
>   cache.h          |  1 +
>   entry.c          | 28 ++++++++++++++++++++++++++++
>   t/t5601-clone.sh |  8 +++++++-
>   unpack-trees.c   | 28 ++++++++++++++++++++++++++++
>   unpack-trees.h   |  1 +
>   6 files changed, 66 insertions(+), 1 deletion(-)
> 
> diff --git a/builtin/clone.c b/builtin/clone.c
> index 5c439f1394..0702b0e9d0 100644
> --- a/builtin/clone.c
> +++ b/builtin/clone.c
> @@ -747,6 +747,7 @@ static int checkout(int submodule_progress)
>   	memset(&opts, 0, sizeof opts);
>   	opts.update = 1;
>   	opts.merge = 1;
> +	opts.clone = 1;
>   	opts.fn = oneway_merge;
>   	opts.verbose_update = (option_verbosity >= 0);
>   	opts.src_index = &the_index;
> diff --git a/cache.h b/cache.h
> index 8b447652a7..6d6138f4f1 100644
> --- a/cache.h
> +++ b/cache.h
> @@ -1455,6 +1455,7 @@ struct checkout {
>   	unsigned force:1,
>   		 quiet:1,
>   		 not_new:1,
> +		 clone:1,
>   		 refresh_cache:1;
>   };
>   #define CHECKOUT_INIT { NULL, "" }
> diff --git a/entry.c b/entry.c
> index b5d1d3cf23..c70340df8e 100644
> --- a/entry.c
> +++ b/entry.c
> @@ -399,6 +399,31 @@ static int check_path(const char *path, int len, struct stat *st, int skiplen)
>   	return lstat(path, st);
>   }
>   
> +static void mark_colliding_entries(const struct checkout *state,
> +				   struct cache_entry *ce, struct stat *st)
> +{
> +	int i;
> +
> +	ce->ce_flags |= CE_MATCHED;
> +
> +#if !defined(GIT_WINDOWS_NATIVE) /* inode is always zero on Windows */
> +	for (i = 0; i < state->istate->cache_nr; i++) {
> +		struct cache_entry *dup = state->istate->cache[i];
> +
> +		if (dup == ce)
> +			break;
> +
> +		if (dup->ce_flags & (CE_MATCHED | CE_VALID | CE_SKIP_WORKTREE))
> +			continue;
> +
> +		if (dup->ce_stat_data.sd_ino == st->st_ino) {
> +			dup->ce_flags |= CE_MATCHED;
> +			break;
> +		}
> +	}
> +#endif
> +}
> +
>   /*
>    * Write the contents from ce out to the working tree.
>    *
> @@ -455,6 +480,9 @@ int checkout_entry(struct cache_entry *ce,
>   			return -1;
>   		}
>   
> +		if (state->clone)
> +			mark_colliding_entries(state, ce, &st);
> +
>   		/*
>   		 * We unlink the old file, to get the new one with the
>   		 * right permissions (including umask, which is nasty
> diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh
> index 0b62037744..f2eb73bc74 100755
> --- a/t/t5601-clone.sh
> +++ b/t/t5601-clone.sh
> @@ -624,10 +624,16 @@ test_expect_success 'clone on case-insensitive fs' '
>   			git hash-object -w -t tree --stdin) &&
>   		c=$(git commit-tree -m bogus $t) &&
>   		git update-ref refs/heads/bogus $c &&
> -		git clone -b bogus . bogus
> +		git clone -b bogus . bogus 2>warning
>   	)
>   '
>   
> +test_expect_success !MINGW,!CYGWIN,CASE_INSENSITIVE_FS 'colliding file detection' '
> +	grep X icasefs/warning &&
> +	grep x icasefs/warning &&
> +	test_i18ngrep "the following paths have collided" icasefs/warning
> +'
> +
>   partial_clone () {
>   	       SERVER="$1" &&
>   	       URL="$2" &&
> diff --git a/unpack-trees.c b/unpack-trees.c
> index cd0680f11e..443df048ef 100644
> --- a/unpack-trees.c
> +++ b/unpack-trees.c
> @@ -359,6 +359,12 @@ static int check_updates(struct unpack_trees_options *o)
>   	state.refresh_cache = 1;
>   	state.istate = index;
>   
> +	if (o->clone) {
> +		state.clone = 1;
> +		for (i = 0; i < index->cache_nr; i++)
> +			index->cache[i]->ce_flags &= ~CE_MATCHED;
> +	}
> +
>   	progress = get_progress(o);
>   
>   	if (o->update)
> @@ -423,6 +429,28 @@ static int check_updates(struct unpack_trees_options *o)
>   	errs |= finish_delayed_checkout(&state);
>   	if (o->update)
>   		git_attr_set_direction(GIT_ATTR_CHECKIN, NULL);
> +
> +	if (o->clone) {
> +		int printed_warning = 0;
> +
> +		for (i = 0; i < index->cache_nr; i++) {
> +			struct cache_entry *ce = index->cache[i];
> +
> +			if (!(ce->ce_flags & CE_MATCHED))
> +				continue;
> +
> +			if (!printed_warning) {
> +				warning(_("the following paths have collided (e.g. case-sensitive paths\n"
> +					  "on a case-insensitive filesystem) and only one from the same\n"
> +					  "colliding group is in the working tree:\n"));
> +				printed_warning = 1;
> +			}
> +
> +			fprintf(stderr, "  '%s'\n", ce->name);
> +			ce->ce_flags &= ~CE_MATCHED;
> +		}
> +	}
> +

If I'm reading this correctly, on Linux and friends, you'll print the
names of the files where the collision was detected and the paths of
any peers found from the inum matching.  And because of the #ifdef'ing
on Windows, we'll just get the former (at least for now).

That sounds fine.
Thanks
Jeff


>   	return errs != 0;
>   }
>   
> diff --git a/unpack-trees.h b/unpack-trees.h
> index c2b434c606..d940f1c5c2 100644
> --- a/unpack-trees.h
> +++ b/unpack-trees.h
> @@ -42,6 +42,7 @@ struct unpack_trees_options {
>   	unsigned int reset,
>   		     merge,
>   		     update,
> +		     clone,
>   		     index_only,
>   		     nontrivial_merge,
>   		     trivial_merges_only,
> 

^ permalink raw reply	[relevance 0%]

* [PATCH v4] clone: report duplicate entries on case-insensitive filesystems
    2018-08-10 15:36  4%       ` [PATCH v3 1/1] clone: report duplicate entries on case-insensitive filesystems Nguyễn Thái Ngọc Duy
@ 2018-08-12  9:07  9%       ` Nguyễn Thái Ngọc Duy
  2018-08-13 15:32  0%         ` Jeff Hostetler
                           ` (3 more replies)
  1 sibling, 4 replies; 200+ results
From: Nguyễn Thái Ngọc Duy @ 2018-08-12  9:07 UTC (permalink / raw)
  To: pclouds
  Cc: git, git, gitster, newren, pawelparuzel95, peff, sandals, tboegi,
	SZEDER Gábor

Paths that only differ in case work fine in a case-sensitive
filesystems, but if those repos are cloned in a case-insensitive one,
you'll get problems. The first thing to notice is "git status" will
never be clean with no indication what exactly is "dirty".

This patch helps the situation a bit by pointing out the problem at
clone time. Even though this patch talks about case sensitivity, the
patch makes no assumption about folding rules by the filesystem. It
simply observes that if an entry has been already checked out at clone
time when we're about to write a new path, some folding rules are
behind this.

This patch is tested with vim-colorschemes repository on a JFS partition
with case insensitive support on Linux. This repository has two files
darkBlue.vim and darkblue.vim.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 v4 removes nr_duplicates (and fixes that false warning Szeder
 reported). It also hints about case insensitivity as a cause of
 problem because it's most likely the case when this warning shows up.

 builtin/clone.c  |  1 +
 cache.h          |  1 +
 entry.c          | 28 ++++++++++++++++++++++++++++
 t/t5601-clone.sh |  8 +++++++-
 unpack-trees.c   | 28 ++++++++++++++++++++++++++++
 unpack-trees.h   |  1 +
 6 files changed, 66 insertions(+), 1 deletion(-)

diff --git a/builtin/clone.c b/builtin/clone.c
index 5c439f1394..0702b0e9d0 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -747,6 +747,7 @@ static int checkout(int submodule_progress)
 	memset(&opts, 0, sizeof opts);
 	opts.update = 1;
 	opts.merge = 1;
+	opts.clone = 1;
 	opts.fn = oneway_merge;
 	opts.verbose_update = (option_verbosity >= 0);
 	opts.src_index = &the_index;
diff --git a/cache.h b/cache.h
index 8b447652a7..6d6138f4f1 100644
--- a/cache.h
+++ b/cache.h
@@ -1455,6 +1455,7 @@ struct checkout {
 	unsigned force:1,
 		 quiet:1,
 		 not_new:1,
+		 clone:1,
 		 refresh_cache:1;
 };
 #define CHECKOUT_INIT { NULL, "" }
diff --git a/entry.c b/entry.c
index b5d1d3cf23..c70340df8e 100644
--- a/entry.c
+++ b/entry.c
@@ -399,6 +399,31 @@ static int check_path(const char *path, int len, struct stat *st, int skiplen)
 	return lstat(path, st);
 }
 
+static void mark_colliding_entries(const struct checkout *state,
+				   struct cache_entry *ce, struct stat *st)
+{
+	int i;
+
+	ce->ce_flags |= CE_MATCHED;
+
+#if !defined(GIT_WINDOWS_NATIVE) /* inode is always zero on Windows */
+	for (i = 0; i < state->istate->cache_nr; i++) {
+		struct cache_entry *dup = state->istate->cache[i];
+
+		if (dup == ce)
+			break;
+
+		if (dup->ce_flags & (CE_MATCHED | CE_VALID | CE_SKIP_WORKTREE))
+			continue;
+
+		if (dup->ce_stat_data.sd_ino == st->st_ino) {
+			dup->ce_flags |= CE_MATCHED;
+			break;
+		}
+	}
+#endif
+}
+
 /*
  * Write the contents from ce out to the working tree.
  *
@@ -455,6 +480,9 @@ int checkout_entry(struct cache_entry *ce,
 			return -1;
 		}
 
+		if (state->clone)
+			mark_colliding_entries(state, ce, &st);
+
 		/*
 		 * We unlink the old file, to get the new one with the
 		 * right permissions (including umask, which is nasty
diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh
index 0b62037744..f2eb73bc74 100755
--- a/t/t5601-clone.sh
+++ b/t/t5601-clone.sh
@@ -624,10 +624,16 @@ test_expect_success 'clone on case-insensitive fs' '
 			git hash-object -w -t tree --stdin) &&
 		c=$(git commit-tree -m bogus $t) &&
 		git update-ref refs/heads/bogus $c &&
-		git clone -b bogus . bogus
+		git clone -b bogus . bogus 2>warning
 	)
 '
 
+test_expect_success !MINGW,!CYGWIN,CASE_INSENSITIVE_FS 'colliding file detection' '
+	grep X icasefs/warning &&
+	grep x icasefs/warning &&
+	test_i18ngrep "the following paths have collided" icasefs/warning
+'
+
 partial_clone () {
 	       SERVER="$1" &&
 	       URL="$2" &&
diff --git a/unpack-trees.c b/unpack-trees.c
index cd0680f11e..443df048ef 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -359,6 +359,12 @@ static int check_updates(struct unpack_trees_options *o)
 	state.refresh_cache = 1;
 	state.istate = index;
 
+	if (o->clone) {
+		state.clone = 1;
+		for (i = 0; i < index->cache_nr; i++)
+			index->cache[i]->ce_flags &= ~CE_MATCHED;
+	}
+
 	progress = get_progress(o);
 
 	if (o->update)
@@ -423,6 +429,28 @@ static int check_updates(struct unpack_trees_options *o)
 	errs |= finish_delayed_checkout(&state);
 	if (o->update)
 		git_attr_set_direction(GIT_ATTR_CHECKIN, NULL);
+
+	if (o->clone) {
+		int printed_warning = 0;
+
+		for (i = 0; i < index->cache_nr; i++) {
+			struct cache_entry *ce = index->cache[i];
+
+			if (!(ce->ce_flags & CE_MATCHED))
+				continue;
+
+			if (!printed_warning) {
+				warning(_("the following paths have collided (e.g. case-sensitive paths\n"
+					  "on a case-insensitive filesystem) and only one from the same\n"
+					  "colliding group is in the working tree:\n"));
+				printed_warning = 1;
+			}
+
+			fprintf(stderr, "  '%s'\n", ce->name);
+			ce->ce_flags &= ~CE_MATCHED;
+		}
+	}
+
 	return errs != 0;
 }
 
diff --git a/unpack-trees.h b/unpack-trees.h
index c2b434c606..d940f1c5c2 100644
--- a/unpack-trees.h
+++ b/unpack-trees.h
@@ -42,6 +42,7 @@ struct unpack_trees_options {
 	unsigned int reset,
 		     merge,
 		     update,
+		     clone,
 		     index_only,
 		     nontrivial_merge,
 		     trivial_merges_only,
-- 
2.18.0.1004.g6639190530


^ permalink raw reply related	[relevance 9%]

* Re: [PATCH v3 1/1] clone: report duplicate entries on case-insensitive filesystems
  2018-08-11 10:09  0%         ` SZEDER Gábor
@ 2018-08-11 13:16  0%           ` Duy Nguyen
  0 siblings, 0 replies; 200+ results
From: Duy Nguyen @ 2018-08-11 13:16 UTC (permalink / raw)
  To: SZEDER Gábor
  Cc: Jeff Hostetler, Git Mailing List, Junio C Hamano, Elijah Newren,
	Paweł Paruzel, Jeff King, brian m. carlson,
	Torsten Bögershausen

On Sat, Aug 11, 2018 at 12:09 PM SZEDER Gábor <szeder.dev@gmail.com> wrote:
>
>
> > Paths that only differ in case work fine in a case-sensitive
> > filesystems, but if those repos are cloned in a case-insensitive one,
> > you'll get problems. The first thing to notice is "git status" will
> > never be clean with no indication what exactly is "dirty".
> >
> > This patch helps the situation a bit by pointing out the problem at
> > clone time. Even though this patch talks about case sensitivity, the
> > patch makes no assumption about folding rules by the filesystem. It
> > simply observes that if an entry has been already checked out at clone
> > time when we're about to write a new path, some folding rules are
> > behind this.
> >
> > This patch is tested with vim-colorschemes repository on a JFS partition
> > with case insensitive support on Linux. This repository has two files
> > darkBlue.vim and darkblue.vim.
> >
> > Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
>
> This patch makes 'clone http repository' in
> 't5551-http-fetch-smart.sh' fail with:
>
>   --- exp 2018-08-11 02:29:45.216641851 +0000
>   +++ actual.smudged      2018-08-11 02:29:45.264642318 +0000
>   @@ -15,3 +15,5 @@
>    < Pragma: no-cache
>    < Cache-Control: no-cache, max-age=0, must-revalidate
>    < Content-Type: application/x-git-upload-pack-result
>   +> warning: the following paths have collided and only one from the same
>   +> colliding group is in the working tree:

I was careless and checked the wrong variable (should have checked
nr_duplicates not state.nr_duplicates; the second is a pointer). So we
always get this warning (and with no following list of files)

>     I also notice that this patch doesn't add any tests... :)

This is platform specific and I was to be frank a bit lazy. Will
consider adding a test with CASE_INSENSITIVE_FS after this.
-- 
Duy

^ permalink raw reply	[relevance 0%]

* Re: [PATCH v3 1/1] clone: report duplicate entries on case-insensitive filesystems
  2018-08-10 15:36  4%       ` [PATCH v3 1/1] clone: report duplicate entries on case-insensitive filesystems Nguyễn Thái Ngọc Duy
@ 2018-08-11 10:09  0%         ` SZEDER Gábor
  2018-08-11 13:16  0%           ` Duy Nguyen
  0 siblings, 1 reply; 200+ results
From: SZEDER Gábor @ 2018-08-11 10:09 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy
  Cc: SZEDER Gábor, git, git, gitster, newren, pawelparuzel95,
	peff, sandals, tboegi


> Paths that only differ in case work fine in a case-sensitive
> filesystems, but if those repos are cloned in a case-insensitive one,
> you'll get problems. The first thing to notice is "git status" will
> never be clean with no indication what exactly is "dirty".
> 
> This patch helps the situation a bit by pointing out the problem at
> clone time. Even though this patch talks about case sensitivity, the
> patch makes no assumption about folding rules by the filesystem. It
> simply observes that if an entry has been already checked out at clone
> time when we're about to write a new path, some folding rules are
> behind this.
> 
> This patch is tested with vim-colorschemes repository on a JFS partition
> with case insensitive support on Linux. This repository has two files
> darkBlue.vim and darkblue.vim.
> 
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>

This patch makes 'clone http repository' in
't5551-http-fetch-smart.sh' fail with:

  --- exp 2018-08-11 02:29:45.216641851 +0000
  +++ actual.smudged      2018-08-11 02:29:45.264642318 +0000
  @@ -15,3 +15,5 @@
   < Pragma: no-cache
   < Cache-Control: no-cache, max-age=0, must-revalidate
   < Content-Type: application/x-git-upload-pack-result
  +> warning: the following paths have collided and only one from the same
  +> colliding group is in the working tree:


This highlights a few issues:

  - This test runs

      GIT_TRACE_CURL=true git clone --quiet <URL> 2>err

    i.e. the curl trace and any errors or warnings from 'git clone'
    end up in the same file.  This test then removes a lot of
    uninteresting headers before the comparison with 'test_cmp', but
    the warning remains and then triggers the test failure.

    Several other tests run a command like this, but those don't use
    'test_cmp', but only grep the output to verify the presence or
    absence of certain headers.

    I'm inclined to think that it would be prudent to change all these
    tests to send the curl trace to a dedicated file (and then
    '--quiet' can be removed as well).  Though, arguably, had that
    been already the case, this test wouldn't have failed, and we
    probably wouldn't have noticed that something is wrong.

  - But what triggered this warning in the first place?  'git clone'
    didn't print anything after the that warning, even when I re-run
    the test with that '--quiet' option removed.  Furthermore, the
    cloned repository contains a single file, so there could be no
    case/folding collision among multiple files.

    I also notice that this patch doesn't add any tests... :)

  - I didn't understand this warning, I had to read the corresponding
    commit message to figure out what it's all about.



^ permalink raw reply	[relevance 0%]

* Re: de-alphabetizing the documentation
  @ 2018-08-11  2:30  1%             ` frederik
  0 siblings, 0 replies; 200+ results
From: frederik @ 2018-08-11  2:30 UTC (permalink / raw)
  To: Jonathan Nieder; +Cc: Eric Sunshine, git, Robert P. J. Day

[-- Attachment #1: Type: text/plain, Size: 2021 bytes --]

Hi Jonathan and Git developers,

I poked around today and figured out how to reorder the command
listings in the manual page, they are taken from git/command-list.txt
so I just reorder the lines in that file (after disabling sorting in
git/Documentation/cmd-list.perl).

I haven't reordered the whole list yet. I could only get one computer
friend to send me his subcommand frequencies from his shell history. I
reordered the commands partly based on that, and partly based on their
order of occurrence in the various tutorial man pages.

I'm attaching a preliminary patch (not ready to be applied) just to
make sure this is going to be OK with the maintainer (who is the
maintainer?) before I continue. I made it with "git format-patch".

It seems fairly straightforward, but please let me know if there are
any problems. By the way, the command I used to check that the new
command list contains all the original commands is as follows:

diff =(sort command-list.txt | grep -v '^#') =(git cat-file blob e1c26c1bb7e618f6f372d9a568e7cab75612d2db | grep -v '^#' | sort)

Thanks,

Frederick

On Tue, Jul 24, 2018 at 02:11:46PM -0700, Jonathan Nieder wrote:
> Hi,
> 
> frederik@ofb.net wrote:
> 
> > Next week I should have time to send you a patch with the manual page
> > reordered...
> 
> Yay!
> 
> >               although, unless you have a special 'diff' which can
> > detect when text has been moved from one place to another, I'm
> > guessing it would take you even longer to check the validity of the
> > patch than it would for me to create it.
> 
> Fortunately we have "git diff --color-moved".
> 
> > However, I'm happy to do this or whatever other small projects you
> > would like to delegate as far as improving readability of your
> > documentation. I just need to know what is likely to be accepted.
> 
> Starting with this one seems fine.  Maybe people on list will have
> ideas for followups on top, or maybe you'll have ideas for ways others
> can help you, or both. ;-)
> 
> Thanks again,
> Jonathan
> 

[-- Attachment #2: 0001-First-test-of-command-reordering.patch --]
[-- Type: text/plain, Size: 14176 bytes --]

From 7aabe1060eabee16fac239ce49b8f9749be11adb Mon Sep 17 00:00:00 2001
From: Frederick Eaton <frederik@ofb.net>
Date: Fri, 10 Aug 2018 18:54:28 -0700
Subject: [PATCH] First test of command reordering

---
 Documentation/cmd-list.perl |   2 +-
 command-list.txt            | 107 +++++++++++++++++++++++++-------------------
 2 files changed, 61 insertions(+), 48 deletions(-)

diff --git a/Documentation/cmd-list.perl b/Documentation/cmd-list.perl
index 5aa73cfe4..62c32f58d 100755
--- a/Documentation/cmd-list.perl
+++ b/Documentation/cmd-list.perl
@@ -43,7 +43,7 @@ sub format_one {
 }
 
 my %cmds = ();
-for (sort <>) {
+for (<>) {
 	next if /^#/;
 
 	chomp;
diff --git a/command-list.txt b/command-list.txt
index e1c26c1bb..94a15fd42 100644
--- a/command-list.txt
+++ b/command-list.txt
@@ -43,35 +43,83 @@
 # specified here, which can only have "guide" attribute and nothing
 # else.
 #
+# August 2018: The list has been reordered for didactic purposes,
+# basically according to approximate usefulness / frequency of use /
+# order of use. This is to make it possible for a beginner to read the
+# manual page "straight through" and see the most important commands
+# first, rather than getting them in alphabetical order. Please
+# consider this when adding new commands.
+#
 ### command list (do not change this line, also do not change alignment)
 # command name                          category [category] [category]
+# From tutorial
+git-help                                ancillaryinterrogators          complete
+git-config                              ancillarymanipulators           complete
+git-clone                               mainporcelain           init
+git-init                                mainporcelain           init
 git-add                                 mainporcelain           worktree
-git-am                                  mainporcelain
+git-commit                              mainporcelain           history
+git-diff                                mainporcelain           history
+git-status                              mainporcelain           info
+git-log                                 mainporcelain           info
+git-branch                              mainporcelain           history
+git-checkout                            mainporcelain           history
+git-merge                               mainporcelain           history
+gitk                                    mainporcelain
+git-pull                                mainporcelain           remote
+git-fetch                               mainporcelain           remote
+# From frequencies
+git-grep                                mainporcelain           info
+git-show                                mainporcelain           info
+git-push                                mainporcelain           remote
+git-submodule                           mainporcelain
+git-reset                               mainporcelain           worktree
+git-cherry-pick                         mainporcelain
+git-tag                                 mainporcelain           history
+git-clean                               mainporcelain
+# From tutorial NEXT STEPS
+git-format-patch                        mainporcelain
+git-bisect                              mainporcelain           info
+gitworkflows                            guide
+giteveryday                             guide
+gitcvs-migration                        guide
+# From tutorial-2 (+ls-remote)
+git-cat-file                            plumbinginterrogators
+git-ls-tree                             plumbinginterrogators
+git-ls-files                            plumbinginterrogators
+git-ls-remote                           plumbinginterrogators
+gitcore-tutorial                        guide
+gitglossary                             guide
+# From gitcore-tutorial
+git-update-index                        plumbingmanipulators
+git-diff-files                          plumbinginterrogators
+git-write-tree                          plumbingmanipulators
+git-read-tree                           plumbingmanipulators
+git-checkout-index                      plumbingmanipulators
+git-show-branch                         ancillaryinterrogators          complete
+git-name-rev                            plumbinginterrogators
+git-merge-index                         plumbingmanipulators
+git-repack                              ancillarymanipulators           complete
+git-prune-packed                        plumbingmanipulators
+git-update-server-info                  synchingrepositories
+git-prune                               ancillarymanipulators
+git-cherry                              ancillaryinterrogators          complete
+# Remaining unsorted (alphabetized) commands
 git-annotate                            ancillaryinterrogators
 git-apply                               plumbingmanipulators            complete
 git-archimport                          foreignscminterface
 git-archive                             mainporcelain
-git-bisect                              mainporcelain           info
 git-blame                               ancillaryinterrogators          complete
-git-branch                              mainporcelain           history
+git-am                                  mainporcelain
 git-bundle                              mainporcelain
-git-cat-file                            plumbinginterrogators
 git-check-attr                          purehelpers
 git-check-ignore                        purehelpers
 git-check-mailmap                       purehelpers
-git-checkout                            mainporcelain           history
-git-checkout-index                      plumbingmanipulators
 git-check-ref-format                    purehelpers
-git-cherry                              ancillaryinterrogators          complete
-git-cherry-pick                         mainporcelain
 git-citool                              mainporcelain
-git-clean                               mainporcelain
-git-clone                               mainporcelain           init
 git-column                              purehelpers
-git-commit                              mainporcelain           history
 git-commit-graph                        plumbingmanipulators
 git-commit-tree                         plumbingmanipulators
-git-config                              ancillarymanipulators           complete
 git-count-objects                       ancillaryinterrogators
 git-credential                          purehelpers
 git-credential-cache                    purehelpers
@@ -81,52 +129,37 @@ git-cvsimport                           foreignscminterface
 git-cvsserver                           foreignscminterface
 git-daemon                              synchingrepositories
 git-describe                            mainporcelain
-git-diff                                mainporcelain           history
-git-diff-files                          plumbinginterrogators
 git-diff-index                          plumbinginterrogators
 git-diff-tree                           plumbinginterrogators
 git-difftool                            ancillaryinterrogators          complete
 git-fast-export                         ancillarymanipulators
 git-fast-import                         ancillarymanipulators
-git-fetch                               mainporcelain           remote
 git-fetch-pack                          synchingrepositories
 git-filter-branch                       ancillarymanipulators
 git-fmt-merge-msg                       purehelpers
 git-for-each-ref                        plumbinginterrogators
-git-format-patch                        mainporcelain
 git-fsck                                ancillaryinterrogators          complete
 git-gc                                  mainporcelain
 git-get-tar-commit-id                   ancillaryinterrogators
-git-grep                                mainporcelain           info
 git-gui                                 mainporcelain
 git-hash-object                         plumbingmanipulators
-git-help                                ancillaryinterrogators          complete
 git-http-backend                        synchingrepositories
 git-http-fetch                          synchelpers
 git-http-push                           synchelpers
 git-imap-send                           foreignscminterface
 git-index-pack                          plumbingmanipulators
-git-init                                mainporcelain           init
 git-instaweb                            ancillaryinterrogators          complete
 git-interpret-trailers                  purehelpers
-gitk                                    mainporcelain
-git-log                                 mainporcelain           info
-git-ls-files                            plumbinginterrogators
-git-ls-remote                           plumbinginterrogators
-git-ls-tree                             plumbinginterrogators
 git-mailinfo                            purehelpers
 git-mailsplit                           purehelpers
-git-merge                               mainporcelain           history
 git-merge-base                          plumbinginterrogators
 git-merge-file                          plumbingmanipulators
-git-merge-index                         plumbingmanipulators
 git-merge-one-file                      purehelpers
 git-mergetool                           ancillarymanipulators           complete
 git-merge-tree                          ancillaryinterrogators
 git-mktag                               plumbingmanipulators
 git-mktree                              plumbingmanipulators
 git-mv                                  mainporcelain           worktree
-git-name-rev                            plumbinginterrogators
 git-notes                               mainporcelain
 git-p4                                  foreignscminterface
 git-pack-objects                        plumbingmanipulators
@@ -134,21 +167,14 @@ git-pack-redundant                      plumbinginterrogators
 git-pack-refs                           ancillarymanipulators
 git-parse-remote                        synchelpers
 git-patch-id                            purehelpers
-git-prune                               ancillarymanipulators
-git-prune-packed                        plumbingmanipulators
-git-pull                                mainporcelain           remote
-git-push                                mainporcelain           remote
 git-quiltimport                         foreignscminterface
-git-read-tree                           plumbingmanipulators
 git-rebase                              mainporcelain           history
 git-receive-pack                        synchelpers
 git-reflog                              ancillarymanipulators           complete
 git-remote                              ancillarymanipulators           complete
-git-repack                              ancillarymanipulators           complete
 git-replace                             ancillarymanipulators           complete
 git-request-pull                        foreignscminterface             complete
 git-rerere                              ancillaryinterrogators
-git-reset                               mainporcelain           worktree
 git-revert                              mainporcelain
 git-rev-list                            plumbinginterrogators
 git-rev-parse                           ancillaryinterrogators
@@ -157,25 +183,18 @@ git-send-email                          foreignscminterface             complete
 git-send-pack                           synchingrepositories
 git-shell                               synchelpers
 git-shortlog                            mainporcelain
-git-show                                mainporcelain           info
-git-show-branch                         ancillaryinterrogators          complete
 git-show-index                          plumbinginterrogators
 git-show-ref                            plumbinginterrogators
 git-sh-i18n                             purehelpers
 git-sh-setup                            purehelpers
 git-stash                               mainporcelain
 git-stage                                                               complete
-git-status                              mainporcelain           info
 git-stripspace                          purehelpers
-git-submodule                           mainporcelain
 git-svn                                 foreignscminterface
 git-symbolic-ref                        plumbingmanipulators
-git-tag                                 mainporcelain           history
 git-unpack-file                         plumbinginterrogators
 git-unpack-objects                      plumbingmanipulators
-git-update-index                        plumbingmanipulators
 git-update-ref                          plumbingmanipulators
-git-update-server-info                  synchingrepositories
 git-upload-archive                      synchelpers
 git-upload-pack                         synchelpers
 git-var                                 plumbinginterrogators
@@ -185,14 +204,9 @@ git-verify-tag                          ancillaryinterrogators
 gitweb                                  ancillaryinterrogators
 git-whatchanged                         ancillaryinterrogators          complete
 git-worktree                            mainporcelain
-git-write-tree                          plumbingmanipulators
 gitattributes                           guide
 gitcli                                  guide
-gitcore-tutorial                        guide
-gitcvs-migration                        guide
 gitdiffcore                             guide
-giteveryday                             guide
-gitglossary                             guide
 githooks                                guide
 gitignore                               guide
 gitmodules                              guide
@@ -201,4 +215,3 @@ gitrepository-layout                    guide
 gitrevisions                            guide
 gittutorial-2                           guide
 gittutorial                             guide
-gitworkflows                            guide
-- 
2.13.1


[-- Attachment #3: new-git.1.out --]
[-- Type: text/plain, Size: 46751 bytes --]

GIT(1)                            Git Manual                            GIT(1)

NAME
       git - the stupid content tracker

SYNOPSIS
       git [--version] [--help] [-C <path>] [-c <name>=<value>]
           [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
           [-p|--paginate|-P|--no-pager] [--no-replace-objects] [--bare]
           [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]
           [--super-prefix=<path>]
           <command> [<args>]

DESCRIPTION
       Git is a fast, scalable, distributed revision control system with an
       unusually rich command set that provides both high-level operations and
       full access to internals.

       See gittutorial(7) to get started, then see giteveryday(7) for a useful
       minimum set of commands. The Git User's Manual[1] has a more in-depth
       introduction.

       After you mastered the basic concepts, you can come back to this page
       to learn what commands Git offers. You can learn more about individual
       Git commands with "git help command". gitcli(7) manual page gives you
       an overview of the command-line command syntax.

       A formatted and hyperlinked copy of the latest Git documentation can be
       viewed at https://git.github.io/htmldocs/git.html.

OPTIONS
       --version
           Prints the Git suite version that the git program came from.

       --help
           Prints the synopsis and a list of the most commonly used commands.
           If the option --all or -a is given then all available commands are
           printed. If a Git command is named this option will bring up the
           manual page for that command.

           Other options are available to control how the manual page is
           displayed. See git-help(1) for more information, because git --help
           ...  is converted internally into git help ....

       -C <path>
           Run as if git was started in <path> instead of the current working
           directory. When multiple -C options are given, each subsequent
           non-absolute -C <path> is interpreted relative to the preceding -C
           <path>.

           This option affects options that expect path name like --git-dir
           and --work-tree in that their interpretations of the path names
           would be made relative to the working directory caused by the -C
           option. For example the following invocations are equivalent:

               git --git-dir=a.git --work-tree=b -C c status
               git --git-dir=c/a.git --work-tree=c/b status

       -c <name>=<value>
           Pass a configuration parameter to the command. The value given will
           override values from configuration files. The <name> is expected in
           the same format as listed by git config (subkeys separated by
           dots).

           Note that omitting the = in git -c foo.bar ...  is allowed and sets
           foo.bar to the boolean true value (just like [foo]bar would in a
           config file). Including the equals but with an empty value (like
           git -c foo.bar= ...) sets foo.bar to the empty string which git
           config --bool will convert to false.

       --exec-path[=<path>]
           Path to wherever your core Git programs are installed. This can
           also be controlled by setting the GIT_EXEC_PATH environment
           variable. If no path is given, git will print the current setting
           and then exit.

       --html-path
           Print the path, without trailing slash, where Git's HTML
           documentation is installed and exit.

       --man-path
           Print the manpath (see man(1)) for the man pages for this version
           of Git and exit.

       --info-path
           Print the path where the Info files documenting this version of Git
           are installed and exit.

       -p, --paginate
           Pipe all output into less (or if set, $PAGER) if standard output is
           a terminal. This overrides the pager.<cmd> configuration options
           (see the "Configuration Mechanism" section below).

       -P, --no-pager
           Do not pipe Git output into a pager.

       --git-dir=<path>
           Set the path to the repository. This can also be controlled by
           setting the GIT_DIR environment variable. It can be an absolute
           path or relative path to current working directory.

       --work-tree=<path>
           Set the path to the working tree. It can be an absolute path or a
           path relative to the current working directory. This can also be
           controlled by setting the GIT_WORK_TREE environment variable and
           the core.worktree configuration variable (see core.worktree in git-
           config(1) for a more detailed discussion).

       --namespace=<path>
           Set the Git namespace. See gitnamespaces(7) for more details.
           Equivalent to setting the GIT_NAMESPACE environment variable.

       --super-prefix=<path>
           Currently for internal use only. Set a prefix which gives a path
           from above a repository down to its root. One use is to give
           submodules context about the superproject that invoked it.

       --bare
           Treat the repository as a bare repository. If GIT_DIR environment
           is not set, it is set to the current working directory.

       --no-replace-objects
           Do not use replacement refs to replace Git objects. See git-
           replace(1) for more information.

       --literal-pathspecs
           Treat pathspecs literally (i.e. no globbing, no pathspec magic).
           This is equivalent to setting the GIT_LITERAL_PATHSPECS environment
           variable to 1.

       --glob-pathspecs
           Add "glob" magic to all pathspec. This is equivalent to setting the
           GIT_GLOB_PATHSPECS environment variable to 1. Disabling globbing on
           individual pathspecs can be done using pathspec magic ":(literal)"

       --noglob-pathspecs
           Add "literal" magic to all pathspec. This is equivalent to setting
           the GIT_NOGLOB_PATHSPECS environment variable to 1. Enabling
           globbing on individual pathspecs can be done using pathspec magic
           ":(glob)"

       --icase-pathspecs
           Add "icase" magic to all pathspec. This is equivalent to setting
           the GIT_ICASE_PATHSPECS environment variable to 1.

       --no-optional-locks
           Do not perform optional operations that require locks. This is
           equivalent to setting the GIT_OPTIONAL_LOCKS to 0.

       --list-cmds=group[,group...]
           List commands by group. This is an internal/experimental option and
           may change or be removed in the future. Supported groups are:
           builtins, parseopt (builtin commands that use parse-options), main
           (all commands in libexec directory), others (all other commands in
           $PATH that have git- prefix), list-<category> (see categories in
           command-list.txt), nohelpers (exclude helper commands), alias and
           config (retrieve command list from config variable
           completion.commands)

GIT COMMANDS
       We divide Git into high level ("porcelain") commands and low level
       ("plumbing") commands.

HIGH-LEVEL COMMANDS (PORCELAIN)
       We separate the porcelain commands into the main commands and some
       ancillary user utilities.

   Main porcelain commands
       git-clone(1)
           Clone a repository into a new directory.

       git-init(1)
           Create an empty Git repository or reinitialize an existing one.

       git-add(1)
           Add file contents to the index.

       git-commit(1)
           Record changes to the repository.

       git-diff(1)
           Show changes between commits, commit and working tree, etc.

       git-status(1)
           Show the working tree status.

       git-log(1)
           Show commit logs.

       git-branch(1)
           List, create, or delete branches.

       git-checkout(1)
           Switch branches or restore working tree files.

       git-merge(1)
           Join two or more development histories together.

       gitk(1)
           The Git repository browser.

       git-pull(1)
           Fetch from and integrate with another repository or a local branch.

       git-fetch(1)
           Download objects and refs from another repository.

       git-grep(1)
           Print lines matching a pattern.

       git-show(1)
           Show various types of objects.

       git-push(1)
           Update remote refs along with associated objects.

       git-submodule(1)
           Initialize, update or inspect submodules.

       git-reset(1)
           Reset current HEAD to the specified state.

       git-cherry-pick(1)
           Apply the changes introduced by some existing commits.

       git-tag(1)
           Create, list, delete or verify a tag object signed with GPG.

       git-clean(1)
           Remove untracked files from the working tree.

       git-format-patch(1)
           Prepare patches for e-mail submission.

       git-bisect(1)
           Use binary search to find the commit that introduced a bug.

       git-archive(1)
           Create an archive of files from a named tree.

       git-am(1)
           Apply a series of patches from a mailbox.

       git-bundle(1)
           Move objects and refs by archive.

       git-citool(1)
           Graphical alternative to git-commit.

       git-describe(1)
           Give an object a human readable name based on an available ref.

       git-gc(1)
           Cleanup unnecessary files and optimize the local repository.

       git-gui(1)
           A portable graphical interface to Git.

       git-mv(1)
           Move or rename a file, a directory, or a symlink.

       git-notes(1)
           Add or inspect object notes.

       git-rebase(1)
           Reapply commits on top of another base tip.

       git-revert(1)
           Revert some existing commits.

       git-rm(1)
           Remove files from the working tree and from the index.

       git-shortlog(1)
           Summarize git log output.

       git-stash(1)
           Stash the changes in a dirty working directory away.

       git-worktree(1)
           Manage multiple working trees.

   Ancillary Commands
       Manipulators:

       git-config(1)
           Get and set repository or global options.

       git-repack(1)
           Pack unpacked objects in a repository.

       git-prune(1)
           Prune all unreachable objects from the object database.

       git-fast-export(1)
           Git data exporter.

       git-fast-import(1)
           Backend for fast Git data importers.

       git-filter-branch(1)
           Rewrite branches.

       git-mergetool(1)
           Run merge conflict resolution tools to resolve merge conflicts.

       git-pack-refs(1)
           Pack heads and tags for efficient repository access.

       git-reflog(1)
           Manage reflog information.

       git-remote(1)
           Manage set of tracked repositories.

       git-replace(1)
           Create, list, delete refs to replace objects.

       Interrogators:

       git-help(1)
           Display help information about Git.

       git-show-branch(1)
           Show branches and their commits.

       git-cherry(1)
           Find commits yet to be applied to upstream.

       git-annotate(1)
           Annotate file lines with commit information.

       git-blame(1)
           Show what revision and author last modified each line of a file.

       git-count-objects(1)
           Count unpacked number of objects and their disk consumption.

       git-difftool(1)
           Show changes using common diff tools.

       git-fsck(1)
           Verifies the connectivity and validity of the objects in the
           database.

       git-get-tar-commit-id(1)
           Extract commit ID from an archive created using git-archive.

       git-instaweb(1)
           Instantly browse your working repository in gitweb.

       git-merge-tree(1)
           Show three-way merge without touching index.

       git-rerere(1)
           Reuse recorded resolution of conflicted merges.

       git-rev-parse(1)
           Pick out and massage parameters.

       git-verify-commit(1)
           Check the GPG signature of commits.

       git-verify-tag(1)
           Check the GPG signature of tags.

       gitweb(1)
           Git web interface (web frontend to Git repositories).

       git-whatchanged(1)
           Show logs with difference each commit introduces.

   Interacting with Others
       These commands are to interact with foreign SCM and with other people
       via patch over e-mail.

       git-archimport(1)
           Import an Arch repository into Git.

       git-cvsexportcommit(1)
           Export a single commit to a CVS checkout.

       git-cvsimport(1)
           Salvage your data out of another SCM people love to hate.

       git-cvsserver(1)
           A CVS server emulator for Git.

       git-imap-send(1)
           Send a collection of patches from stdin to an IMAP folder.

       git-p4(1)
           Import from and submit to Perforce repositories.

       git-quiltimport(1)
           Applies a quilt patchset onto the current branch.

       git-request-pull(1)
           Generates a summary of pending changes.

       git-send-email(1)
           Send a collection of patches as emails.

       git-svn(1)
           Bidirectional operation between a Subversion repository and Git.

LOW-LEVEL COMMANDS (PLUMBING)
       Although Git includes its own porcelain layer, its low-level commands
       are sufficient to support development of alternative porcelains.
       Developers of such porcelains might start by reading about git-update-
       index(1) and git-read-tree(1).

       The interface (input, output, set of options and the semantics) to
       these low-level commands are meant to be a lot more stable than
       Porcelain level commands, because these commands are primarily for
       scripted use. The interface to Porcelain commands on the other hand are
       subject to change in order to improve the end user experience.

       The following description divides the low-level commands into commands
       that manipulate objects (in the repository, index, and working tree),
       commands that interrogate and compare objects, and commands that move
       objects and references between repositories.

   Manipulation commands
       git-update-index(1)
           Register file contents in the working tree to the index.

       git-write-tree(1)
           Create a tree object from the current index.

       git-read-tree(1)
           Reads tree information into the index.

       git-checkout-index(1)
           Copy files from the index to the working tree.

       git-merge-index(1)
           Run a merge for files needing merging.

       git-prune-packed(1)
           Remove extra objects that are already in pack files.

       git-apply(1)
           Apply a patch to files and/or to the index.

       git-commit-graph(1)
           Write and verify Git commit graph files.

       git-commit-tree(1)
           Create a new commit object.

       git-hash-object(1)
           Compute object ID and optionally creates a blob from a file.

       git-index-pack(1)
           Build pack index file for an existing packed archive.

       git-merge-file(1)
           Run a three-way file merge.

       git-mktag(1)
           Creates a tag object.

       git-mktree(1)
           Build a tree-object from ls-tree formatted text.

       git-pack-objects(1)
           Create a packed archive of objects.

       git-symbolic-ref(1)
           Read, modify and delete symbolic refs.

       git-unpack-objects(1)
           Unpack objects from a packed archive.

       git-update-ref(1)
           Update the object name stored in a ref safely.

   Interrogation commands
       git-cat-file(1)
           Provide content or type and size information for repository
           objects.

       git-ls-tree(1)
           List the contents of a tree object.

       git-ls-files(1)
           Show information about files in the index and the working tree.

       git-ls-remote(1)
           List references in a remote repository.

       git-diff-files(1)
           Compares files in the working tree and the index.

       git-name-rev(1)
           Find symbolic names for given revs.

       git-diff-index(1)
           Compare a tree to the working tree or index.

       git-diff-tree(1)
           Compares the content and mode of blobs found via two tree objects.

       git-for-each-ref(1)
           Output information on each ref.

       git-merge-base(1)
           Find as good common ancestors as possible for a merge.

       git-pack-redundant(1)
           Find redundant pack files.

       git-rev-list(1)
           Lists commit objects in reverse chronological order.

       git-show-index(1)
           Show packed archive index.

       git-show-ref(1)
           List references in a local repository.

       git-unpack-file(1)
           Creates a temporary file with a blob's contents.

       git-var(1)
           Show a Git logical variable.

       git-verify-pack(1)
           Validate packed Git archive files.

       In general, the interrogate commands do not touch the files in the
       working tree.

   Synching repositories
       git-update-server-info(1)
           Update auxiliary info file to help dumb servers.

       git-daemon(1)
           A really simple server for Git repositories.

       git-fetch-pack(1)
           Receive missing objects from another repository.

       git-http-backend(1)
           Server side implementation of Git over HTTP.

       git-send-pack(1)
           Push objects over Git protocol to another repository.

       The following are helper commands used by the above; end users
       typically do not use them directly.

       git-http-fetch(1)
           Download from a remote Git repository via HTTP.

       git-http-push(1)
           Push objects over HTTP/DAV to another repository.

       git-parse-remote(1)
           Routines to help parsing remote repository access parameters.

       git-receive-pack(1)
           Receive what is pushed into the repository.

       git-shell(1)
           Restricted login shell for Git-only SSH access.

       git-upload-archive(1)
           Send archive back to git-archive.

       git-upload-pack(1)
           Send objects packed back to git-fetch-pack.

   Internal helper commands
       These are internal helper commands used by other commands; end users
       typically do not use them directly.

       git-check-attr(1)
           Display gitattributes information.

       git-check-ignore(1)
           Debug gitignore / exclude files.

       git-check-mailmap(1)
           Show canonical names and email addresses of contacts.

       git-check-ref-format(1)
           Ensures that a reference name is well formed.

       git-column(1)
           Display data in columns.

       git-credential(1)
           Retrieve and store user credentials.

       git-credential-cache(1)
           Helper to temporarily store passwords in memory.

       git-credential-store(1)
           Helper to store credentials on disk.

       git-fmt-merge-msg(1)
           Produce a merge commit message.

       git-interpret-trailers(1)
           add or parse structured information in commit messages.

       git-mailinfo(1)
           Extracts patch and authorship from a single e-mail message.

       git-mailsplit(1)
           Simple UNIX mbox splitter program.

       git-merge-one-file(1)
           The standard helper program to use with git-merge-index.

       git-patch-id(1)
           Compute unique ID for a patch.

       git-sh-i18n(1)
           Git's i18n setup code for shell scripts.

       git-sh-setup(1)
           Common Git shell script setup code.

       git-stripspace(1)
           Remove unnecessary whitespace.

CONFIGURATION MECHANISM
       Git uses a simple text format to store customizations that are per
       repository and are per user. Such a configuration file may look like
       this:

           #
           # A '#' or ';' character indicates a comment.
           #

           ; core variables
           [core]
                   ; Don't trust file modes
                   filemode = false

           ; user identity
           [user]
                   name = "Junio C Hamano"
                   email = "gitster@pobox.com"

       Various commands read from the configuration file and adjust their
       operation accordingly. See git-config(1) for a list and more details
       about the configuration mechanism.

IDENTIFIER TERMINOLOGY
       <object>
           Indicates the object name for any type of object.

       <blob>
           Indicates a blob object name.

       <tree>
           Indicates a tree object name.

       <commit>
           Indicates a commit object name.

       <tree-ish>
           Indicates a tree, commit or tag object name. A command that takes a
           <tree-ish> argument ultimately wants to operate on a <tree> object
           but automatically dereferences <commit> and <tag> objects that
           point at a <tree>.

       <commit-ish>
           Indicates a commit or tag object name. A command that takes a
           <commit-ish> argument ultimately wants to operate on a <commit>
           object but automatically dereferences <tag> objects that point at a
           <commit>.

       <type>
           Indicates that an object type is required. Currently one of: blob,
           tree, commit, or tag.

       <file>
           Indicates a filename - almost always relative to the root of the
           tree structure GIT_INDEX_FILE describes.

SYMBOLIC IDENTIFIERS
       Any Git command accepting any <object> can also use the following
       symbolic notation:

       HEAD
           indicates the head of the current branch.

       <tag>
           a valid tag name (i.e. a refs/tags/<tag> reference).

       <head>
           a valid head name (i.e. a refs/heads/<head> reference).

       For a more complete list of ways to spell object names, see "SPECIFYING
       REVISIONS" section in gitrevisions(7).

FILE/DIRECTORY STRUCTURE
       Please see the gitrepository-layout(5) document.

       Read githooks(5) for more details about each hook.

       Higher level SCMs may provide and manage additional information in the
       $GIT_DIR.

TERMINOLOGY
       Please see gitglossary(7).

ENVIRONMENT VARIABLES
       Various Git commands use the following environment variables:

   The Git Repository
       These environment variables apply to all core Git commands. Nb: it is
       worth noting that they may be used/overridden by SCMS sitting above Git
       so take care if using a foreign front-end.

       GIT_INDEX_FILE
           This environment allows the specification of an alternate index
           file. If not specified, the default of $GIT_DIR/index is used.

       GIT_INDEX_VERSION
           This environment variable allows the specification of an index
           version for new repositories. It won't affect existing index files.
           By default index file version 2 or 3 is used. See git-update-
           index(1) for more information.

       GIT_OBJECT_DIRECTORY
           If the object storage directory is specified via this environment
           variable then the sha1 directories are created underneath -
           otherwise the default $GIT_DIR/objects directory is used.

       GIT_ALTERNATE_OBJECT_DIRECTORIES
           Due to the immutable nature of Git objects, old objects can be
           archived into shared, read-only directories. This variable
           specifies a ":" separated (on Windows ";" separated) list of Git
           object directories which can be used to search for Git objects. New
           objects will not be written to these directories.

               Entries that begin with `"` (double-quote) will be interpreted
               as C-style quoted paths, removing leading and trailing
               double-quotes and respecting backslash escapes. E.g., the value
               `"path-with-\"-and-:-in-it":vanilla-path` has two paths:
               `path-with-"-and-:-in-it` and `vanilla-path`.

       GIT_DIR
           If the GIT_DIR environment variable is set then it specifies a path
           to use instead of the default .git for the base of the repository.
           The --git-dir command-line option also sets this value.

       GIT_WORK_TREE
           Set the path to the root of the working tree. This can also be
           controlled by the --work-tree command-line option and the
           core.worktree configuration variable.

       GIT_NAMESPACE
           Set the Git namespace; see gitnamespaces(7) for details. The
           --namespace command-line option also sets this value.

       GIT_CEILING_DIRECTORIES
           This should be a colon-separated list of absolute paths. If set, it
           is a list of directories that Git should not chdir up into while
           looking for a repository directory (useful for excluding
           slow-loading network directories). It will not exclude the current
           working directory or a GIT_DIR set on the command line or in the
           environment. Normally, Git has to read the entries in this list and
           resolve any symlink that might be present in order to compare them
           with the current directory. However, if even this access is slow,
           you can add an empty entry to the list to tell Git that the
           subsequent entries are not symlinks and needn't be resolved; e.g.,
           GIT_CEILING_DIRECTORIES=/maybe/symlink::/very/slow/non/symlink.

       GIT_DISCOVERY_ACROSS_FILESYSTEM
           When run in a directory that does not have ".git" repository
           directory, Git tries to find such a directory in the parent
           directories to find the top of the working tree, but by default it
           does not cross filesystem boundaries. This environment variable can
           be set to true to tell Git not to stop at filesystem boundaries.
           Like GIT_CEILING_DIRECTORIES, this will not affect an explicit
           repository directory set via GIT_DIR or on the command line.

       GIT_COMMON_DIR
           If this variable is set to a path, non-worktree files that are
           normally in $GIT_DIR will be taken from this path instead.
           Worktree-specific files such as HEAD or index are taken from
           $GIT_DIR. See gitrepository-layout(5) and git-worktree(1) for
           details. This variable has lower precedence than other path
           variables such as GIT_INDEX_FILE, GIT_OBJECT_DIRECTORY...

   Git Commits
       GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, GIT_AUTHOR_DATE, GIT_COMMITTER_NAME,
       GIT_COMMITTER_EMAIL, GIT_COMMITTER_DATE, EMAIL
           see git-commit-tree(1)

   Git Diffs
       GIT_DIFF_OPTS
           Only valid setting is "--unified=??" or "-u??" to set the number of
           context lines shown when a unified diff is created. This takes
           precedence over any "-U" or "--unified" option value passed on the
           Git diff command line.

       GIT_EXTERNAL_DIFF
           When the environment variable GIT_EXTERNAL_DIFF is set, the program
           named by it is called, instead of the diff invocation described
           above. For a path that is added, removed, or modified,
           GIT_EXTERNAL_DIFF is called with 7 parameters:

               path old-file old-hex old-mode new-file new-hex new-mode

           where:

       <old|new>-file
           are files GIT_EXTERNAL_DIFF can use to read the contents of
           <old|new>,

       <old|new>-hex
           are the 40-hexdigit SHA-1 hashes,

       <old|new>-mode
           are the octal representation of the file modes.

           The file parameters can point at the user's working file (e.g.
           new-file in "git-diff-files"), /dev/null (e.g.  old-file when a new
           file is added), or a temporary file (e.g.  old-file in the index).
           GIT_EXTERNAL_DIFF should not worry about unlinking the temporary
           file --- it is removed when GIT_EXTERNAL_DIFF exits.

           For a path that is unmerged, GIT_EXTERNAL_DIFF is called with 1
           parameter, <path>.

           For each path GIT_EXTERNAL_DIFF is called, two environment
           variables, GIT_DIFF_PATH_COUNTER and GIT_DIFF_PATH_TOTAL are set.

       GIT_DIFF_PATH_COUNTER
           A 1-based counter incremented by one for every path.

       GIT_DIFF_PATH_TOTAL
           The total number of paths.

   other
       GIT_MERGE_VERBOSITY
           A number controlling the amount of output shown by the recursive
           merge strategy. Overrides merge.verbosity. See git-merge(1)

       GIT_PAGER
           This environment variable overrides $PAGER. If it is set to an
           empty string or to the value "cat", Git will not launch a pager.
           See also the core.pager option in git-config(1).

       GIT_EDITOR
           This environment variable overrides $EDITOR and $VISUAL. It is used
           by several Git commands when, on interactive mode, an editor is to
           be launched. See also git-var(1) and the core.editor option in git-
           config(1).

       GIT_SSH, GIT_SSH_COMMAND
           If either of these environment variables is set then git fetch and
           git push will use the specified command instead of ssh when they
           need to connect to a remote system. The command-line parameters
           passed to the configured command are determined by the ssh variant.
           See ssh.variant option in git-config(1) for details.

       + $GIT_SSH_COMMAND takes precedence over $GIT_SSH, and is interpreted
       by the shell, which allows additional arguments to be included.
       $GIT_SSH on the other hand must be just the path to a program (which
       can be a wrapper shell script, if additional arguments are needed).

       + Usually it is easier to configure any desired options through your
       personal .ssh/config file. Please consult your ssh documentation for
       further details.

       GIT_SSH_VARIANT
           If this environment variable is set, it overrides Git's
           autodetection whether GIT_SSH/GIT_SSH_COMMAND/core.sshCommand refer
           to OpenSSH, plink or tortoiseplink. This variable overrides the
           config setting ssh.variant that serves the same purpose.

       GIT_ASKPASS
           If this environment variable is set, then Git commands which need
           to acquire passwords or passphrases (e.g. for HTTP or IMAP
           authentication) will call this program with a suitable prompt as
           command-line argument and read the password from its STDOUT. See
           also the core.askPass option in git-config(1).

       GIT_TERMINAL_PROMPT
           If this environment variable is set to 0, git will not prompt on
           the terminal (e.g., when asking for HTTP authentication).

       GIT_CONFIG_NOSYSTEM
           Whether to skip reading settings from the system-wide
           $(prefix)/etc/gitconfig file. This environment variable can be used
           along with $HOME and $XDG_CONFIG_HOME to create a predictable
           environment for a picky script, or you can set it temporarily to
           avoid using a buggy /etc/gitconfig file while waiting for someone
           with sufficient permissions to fix it.

       GIT_FLUSH
           If this environment variable is set to "1", then commands such as
           git blame (in incremental mode), git rev-list, git log, git
           check-attr and git check-ignore will force a flush of the output
           stream after each record have been flushed. If this variable is set
           to "0", the output of these commands will be done using completely
           buffered I/O. If this environment variable is not set, Git will
           choose buffered or record-oriented flushing based on whether stdout
           appears to be redirected to a file or not.

       GIT_TRACE
           Enables general trace messages, e.g. alias expansion, built-in
           command execution and external command execution.

           If this variable is set to "1", "2" or "true" (comparison is case
           insensitive), trace messages will be printed to stderr.

           If the variable is set to an integer value greater than 2 and lower
           than 10 (strictly) then Git will interpret this value as an open
           file descriptor and will try to write the trace messages into this
           file descriptor.

           Alternatively, if the variable is set to an absolute path (starting
           with a / character), Git will interpret this as a file path and
           will try to write the trace messages into it.

           Unsetting the variable, or setting it to empty, "0" or "false"
           (case insensitive) disables trace messages.

       GIT_TRACE_FSMONITOR
           Enables trace messages for the filesystem monitor extension. See
           GIT_TRACE for available trace output options.

       GIT_TRACE_PACK_ACCESS
           Enables trace messages for all accesses to any packs. For each
           access, the pack file name and an offset in the pack is recorded.
           This may be helpful for troubleshooting some pack-related
           performance problems. See GIT_TRACE for available trace output
           options.

       GIT_TRACE_PACKET
           Enables trace messages for all packets coming in or out of a given
           program. This can help with debugging object negotiation or other
           protocol issues. Tracing is turned off at a packet starting with
           "PACK" (but see GIT_TRACE_PACKFILE below). See GIT_TRACE for
           available trace output options.

       GIT_TRACE_PACKFILE
           Enables tracing of packfiles sent or received by a given program.
           Unlike other trace output, this trace is verbatim: no headers, and
           no quoting of binary data. You almost certainly want to direct into
           a file (e.g., GIT_TRACE_PACKFILE=/tmp/my.pack) rather than
           displaying it on the terminal or mixing it with other trace output.

           Note that this is currently only implemented for the client side of
           clones and fetches.

       GIT_TRACE_PERFORMANCE
           Enables performance related trace messages, e.g. total execution
           time of each Git command. See GIT_TRACE for available trace output
           options.

       GIT_TRACE_SETUP
           Enables trace messages printing the .git, working tree and current
           working directory after Git has completed its setup phase. See
           GIT_TRACE for available trace output options.

       GIT_TRACE_SHALLOW
           Enables trace messages that can help debugging fetching / cloning
           of shallow repositories. See GIT_TRACE for available trace output
           options.

       GIT_TRACE_CURL
           Enables a curl full trace dump of all incoming and outgoing data,
           including descriptive information, of the git transport protocol.
           This is similar to doing curl --trace-ascii on the command line.
           This option overrides setting the GIT_CURL_VERBOSE environment
           variable. See GIT_TRACE for available trace output options.

       GIT_TRACE_CURL_NO_DATA
           When a curl trace is enabled (see GIT_TRACE_CURL above), do not
           dump data (that is, only dump info lines and headers).

       GIT_REDACT_COOKIES
           This can be set to a comma-separated list of strings. When a curl
           trace is enabled (see GIT_TRACE_CURL above), whenever a "Cookies:"
           header sent by the client is dumped, values of cookies whose key is
           in that list (case-sensitive) are redacted.

       GIT_LITERAL_PATHSPECS
           Setting this variable to 1 will cause Git to treat all pathspecs
           literally, rather than as glob patterns. For example, running
           GIT_LITERAL_PATHSPECS=1 git log -- '*.c' will search for commits
           that touch the path *.c, not any paths that the glob *.c matches.
           You might want this if you are feeding literal paths to Git (e.g.,
           paths previously given to you by git ls-tree, --raw diff output,
           etc).

       GIT_GLOB_PATHSPECS
           Setting this variable to 1 will cause Git to treat all pathspecs as
           glob patterns (aka "glob" magic).

       GIT_NOGLOB_PATHSPECS
           Setting this variable to 1 will cause Git to treat all pathspecs as
           literal (aka "literal" magic).

       GIT_ICASE_PATHSPECS
           Setting this variable to 1 will cause Git to treat all pathspecs as
           case-insensitive.

       GIT_REFLOG_ACTION
           When a ref is updated, reflog entries are created to keep track of
           the reason why the ref was updated (which is typically the name of
           the high-level command that updated the ref), in addition to the
           old and new values of the ref. A scripted Porcelain command can use
           set_reflog_action helper function in git-sh-setup to set its name
           to this variable when it is invoked as the top level command by the
           end user, to be recorded in the body of the reflog.

       GIT_REF_PARANOIA
           If set to 1, include broken or badly named refs when iterating over
           lists of refs. In a normal, non-corrupted repository, this does
           nothing. However, enabling it may help git to detect and abort some
           operations in the presence of broken refs. Git sets this variable
           automatically when performing destructive operations like git-
           prune(1). You should not need to set it yourself unless you want to
           be paranoid about making sure an operation has touched every ref
           (e.g., because you are cloning a repository to make a backup).

       GIT_ALLOW_PROTOCOL
           If set to a colon-separated list of protocols, behave as if
           protocol.allow is set to never, and each of the listed protocols
           has protocol.<name>.allow set to always (overriding any existing
           configuration). In other words, any protocol not mentioned will be
           disallowed (i.e., this is a whitelist, not a blacklist). See the
           description of protocol.allow in git-config(1) for more details.

       GIT_PROTOCOL_FROM_USER
           Set to 0 to prevent protocols used by fetch/push/clone which are
           configured to the user state. This is useful to restrict recursive
           submodule initialization from an untrusted repository or for
           programs which feed potentially-untrusted URLS to git commands. See
           git-config(1) for more details.

       GIT_PROTOCOL
           For internal use only. Used in handshaking the wire protocol.
           Contains a colon : separated list of keys with optional values
           key[=value]. Presence of unknown keys and values must be ignored.

       GIT_OPTIONAL_LOCKS
           If set to 0, Git will complete any requested operation without
           performing any optional sub-operations that require taking a lock.
           For example, this will prevent git status from refreshing the index
           as a side effect. This is useful for processes running in the
           background which do not want to cause lock contention with other
           operations on the repository. Defaults to 1.

       GIT_REDIRECT_STDIN, GIT_REDIRECT_STDOUT, GIT_REDIRECT_STDERR
           Windows-only: allow redirecting the standard input/output/error
           handles to paths specified by the environment variables. This is
           particularly useful in multi-threaded applications where the
           canonical way to pass standard handles via CreateProcess() is not
           an option because it would require the handles to be marked
           inheritable (and consequently every spawned process would inherit
           them, possibly blocking regular Git operations). The primary
           intended use case is to use named pipes for communication (e.g.
           \\.\pipe\my-git-stdin-123).

           Two special values are supported: off will simply close the
           corresponding standard handle, and if GIT_REDIRECT_STDERR is 2>&1,
           standard error will be redirected to the same handle as standard
           output.

       GIT_PRINT_SHA1_ELLIPSIS (deprecated)
           If set to yes, print an ellipsis following an (abbreviated) SHA-1
           value. This affects indications of detached HEADs (git-checkout(1))
           and the raw diff output (git-diff(1)). Printing an ellipsis in the
           cases mentioned is no longer considered adequate and support for it
           is likely to be removed in the foreseeable future (along with the
           variable).

DISCUSSION
       More detail on the following is available from the Git concepts chapter
       of the user-manual[2] and gitcore-tutorial(7).

       A Git project normally consists of a working directory with a ".git"
       subdirectory at the top level. The .git directory contains, among other
       things, a compressed object database representing the complete history
       of the project, an "index" file which links that history to the current
       contents of the working tree, and named pointers into that history such
       as tags and branch heads.

       The object database contains objects of three main types: blobs, which
       hold file data; trees, which point to blobs and other trees to build up
       directory hierarchies; and commits, which each reference a single tree
       and some number of parent commits.

       The commit, equivalent to what other systems call a "changeset" or
       "version", represents a step in the project's history, and each parent
       represents an immediately preceding step. Commits with more than one
       parent represent merges of independent lines of development.

       All objects are named by the SHA-1 hash of their contents, normally
       written as a string of 40 hex digits. Such names are globally unique.
       The entire history leading up to a commit can be vouched for by signing
       just that commit. A fourth object type, the tag, is provided for this
       purpose.

       When first created, objects are stored in individual files, but for
       efficiency may later be compressed together into "pack files".

       Named pointers called refs mark interesting points in history. A ref
       may contain the SHA-1 name of an object or the name of another ref.
       Refs with names beginning ref/head/ contain the SHA-1 name of the most
       recent commit (or "head") of a branch under development. SHA-1 names of
       tags of interest are stored under ref/tags/. A special ref named HEAD
       contains the name of the currently checked-out branch.

       The index file is initialized with a list of all paths and, for each
       path, a blob object and a set of attributes. The blob object represents
       the contents of the file as of the head of the current branch. The
       attributes (last modified time, size, etc.) are taken from the
       corresponding file in the working tree. Subsequent changes to the
       working tree can be found by comparing these attributes. The index may
       be updated with new content, and new commits may be created from the
       content stored in the index.

       The index is also capable of storing multiple entries (called "stages")
       for a given pathname. These stages are used to hold the various
       unmerged version of a file when a merge is in progress.

FURTHER DOCUMENTATION
       See the references in the "description" section to get started using
       Git. The following is probably more detail than necessary for a
       first-time user.

       The Git concepts chapter of the user-manual[2] and gitcore-tutorial(7)
       both provide introductions to the underlying Git architecture.

       See gitworkflows(7) for an overview of recommended workflows.

       See also the howto[3] documents for some useful examples.

       The internals are documented in the Git API documentation[4].

       Users migrating from CVS may also want to read gitcvs-migration(7).

AUTHORS
       Git was started by Linus Torvalds, and is currently maintained by Junio
       C Hamano. Numerous contributions have come from the Git mailing list
       <git@vger.kernel.org[5]>.
       http://www.openhub.net/p/git/contributors/summary gives you a more
       complete list of contributors.

       If you have a clone of git.git itself, the output of git-shortlog(1)
       and git-blame(1) can show you the authors for specific parts of the
       project.

REPORTING BUGS
       Report bugs to the Git mailing list <git@vger.kernel.org[5]> where the
       development and maintenance is primarily done. You do not have to be
       subscribed to the list to send a message there.

       Issues which are security relevant should be disclosed privately to the
       Git Security mailing list <git-security@googlegroups.com[6]>.

SEE ALSO
       gittutorial(7), gittutorial-2(7), giteveryday(7), gitcvs-migration(7),
       gitglossary(7), gitcore-tutorial(7), gitcli(7), The Git User's
       Manual[1], gitworkflows(7)

GIT
       Part of the git(1) suite

NOTES
        1. Git User's Manual
           file:///home/frederik/pkg-tmp/git/../gitbuild//share/doc/git-doc/user-manual.html

        2. Git concepts chapter of the user-manual
           file:///home/frederik/pkg-tmp/git/../gitbuild//share/doc/git-doc/user-manual.html#git-concepts

        3. howto
           file:///home/frederik/pkg-tmp/git/../gitbuild//share/doc/git-doc/howto-index.html

        4. Git API documentation
           file:///home/frederik/pkg-tmp/git/../gitbuild//share/doc/git-doc/technical/api-index.html

        5. git@vger.kernel.org
           mailto:git@vger.kernel.org

        6. git-security@googlegroups.com
           mailto:git-security@googlegroups.com

Git 2.18.0.548.g7aabe1            08/10/2018                            GIT(1)

[-- Attachment #4: command-list.txt --]
[-- Type: text/plain, Size: 11695 bytes --]

# Command classification list
# ---------------------------
# All supported commands, builtin or external, must be described in
# here. This info is used to list commands in various places. Each
# command is on one line followed by one or more attributes.
#
# The first attribute group is mandatory and indicates the command
# type. This group includes:
#
#   mainporcelain
#   ancillarymanipulators
#   ancillaryinterrogators
#   foreignscminterface
#   plumbingmanipulators
#   plumbinginterrogators
#   synchingrepositories
#   synchelpers
#   purehelpers
#
# The type names are self explanatory. But if you want to see what
# command belongs to what group to get a better picture, have a look
# at "git" man page, "GIT COMMANDS" section.
#
# Commands of type mainporcelain can also optionally have one of these
# attributes:
#
#   init
#   worktree
#   info
#   history
#   remote
#
# These commands are considered "common" and will show up in "git
# help" output in groups. Uncommon porcelain commands must not
# specify any of these attributes.
#
# "complete" attribute is used to mark that the command should be
# completable by git-completion.bash. Note that by default,
# mainporcelain commands are completable so you don't need this
# attribute.
#
# As part of the Git man page list, the man(5/7) guides are also
# specified here, which can only have "guide" attribute and nothing
# else.
#
# August 2018: The list has been reordered for didactic purposes,
# basically according to approximate usefulness / frequency of use /
# order of use. This is to make it possible for a beginner to read the
# manual page "straight through" and see the most important commands
# first, rather than getting them in alphabetical order. Please
# consider this when adding new commands.
#
### command list (do not change this line, also do not change alignment)
# command name                          category [category] [category]
# From tutorial
git-help                                ancillaryinterrogators          complete
git-config                              ancillarymanipulators           complete
git-clone                               mainporcelain           init
git-init                                mainporcelain           init
git-add                                 mainporcelain           worktree
git-commit                              mainporcelain           history
git-diff                                mainporcelain           history
git-status                              mainporcelain           info
git-log                                 mainporcelain           info
git-branch                              mainporcelain           history
git-checkout                            mainporcelain           history
git-merge                               mainporcelain           history
gitk                                    mainporcelain
git-pull                                mainporcelain           remote
git-fetch                               mainporcelain           remote
# From frequencies
git-grep                                mainporcelain           info
git-show                                mainporcelain           info
git-push                                mainporcelain           remote
git-submodule                           mainporcelain
git-reset                               mainporcelain           worktree
git-cherry-pick                         mainporcelain
git-tag                                 mainporcelain           history
git-clean                               mainporcelain
# From tutorial NEXT STEPS
git-format-patch                        mainporcelain
git-bisect                              mainporcelain           info
gitworkflows                            guide
giteveryday                             guide
gitcvs-migration                        guide
# From tutorial-2 (+ls-remote)
git-cat-file                            plumbinginterrogators
git-ls-tree                             plumbinginterrogators
git-ls-files                            plumbinginterrogators
git-ls-remote                           plumbinginterrogators
gitcore-tutorial                        guide
gitglossary                             guide
# From gitcore-tutorial
git-update-index                        plumbingmanipulators
git-diff-files                          plumbinginterrogators
git-write-tree                          plumbingmanipulators
git-read-tree                           plumbingmanipulators
git-checkout-index                      plumbingmanipulators
git-show-branch                         ancillaryinterrogators          complete
git-name-rev                            plumbinginterrogators
git-merge-index                         plumbingmanipulators
git-repack                              ancillarymanipulators           complete
git-prune-packed                        plumbingmanipulators
git-update-server-info                  synchingrepositories
git-prune                               ancillarymanipulators
git-cherry                              ancillaryinterrogators          complete
# Remaining unsorted (alphabetized) commands
git-annotate                            ancillaryinterrogators
git-apply                               plumbingmanipulators            complete
git-archimport                          foreignscminterface
git-archive                             mainporcelain
git-blame                               ancillaryinterrogators          complete
git-am                                  mainporcelain
git-bundle                              mainporcelain
git-check-attr                          purehelpers
git-check-ignore                        purehelpers
git-check-mailmap                       purehelpers
git-check-ref-format                    purehelpers
git-citool                              mainporcelain
git-column                              purehelpers
git-commit-graph                        plumbingmanipulators
git-commit-tree                         plumbingmanipulators
git-count-objects                       ancillaryinterrogators
git-credential                          purehelpers
git-credential-cache                    purehelpers
git-credential-store                    purehelpers
git-cvsexportcommit                     foreignscminterface
git-cvsimport                           foreignscminterface
git-cvsserver                           foreignscminterface
git-daemon                              synchingrepositories
git-describe                            mainporcelain
git-diff-index                          plumbinginterrogators
git-diff-tree                           plumbinginterrogators
git-difftool                            ancillaryinterrogators          complete
git-fast-export                         ancillarymanipulators
git-fast-import                         ancillarymanipulators
git-fetch-pack                          synchingrepositories
git-filter-branch                       ancillarymanipulators
git-fmt-merge-msg                       purehelpers
git-for-each-ref                        plumbinginterrogators
git-fsck                                ancillaryinterrogators          complete
git-gc                                  mainporcelain
git-get-tar-commit-id                   ancillaryinterrogators
git-gui                                 mainporcelain
git-hash-object                         plumbingmanipulators
git-http-backend                        synchingrepositories
git-http-fetch                          synchelpers
git-http-push                           synchelpers
git-imap-send                           foreignscminterface
git-index-pack                          plumbingmanipulators
git-instaweb                            ancillaryinterrogators          complete
git-interpret-trailers                  purehelpers
git-mailinfo                            purehelpers
git-mailsplit                           purehelpers
git-merge-base                          plumbinginterrogators
git-merge-file                          plumbingmanipulators
git-merge-one-file                      purehelpers
git-mergetool                           ancillarymanipulators           complete
git-merge-tree                          ancillaryinterrogators
git-mktag                               plumbingmanipulators
git-mktree                              plumbingmanipulators
git-mv                                  mainporcelain           worktree
git-notes                               mainporcelain
git-p4                                  foreignscminterface
git-pack-objects                        plumbingmanipulators
git-pack-redundant                      plumbinginterrogators
git-pack-refs                           ancillarymanipulators
git-parse-remote                        synchelpers
git-patch-id                            purehelpers
git-quiltimport                         foreignscminterface
git-rebase                              mainporcelain           history
git-receive-pack                        synchelpers
git-reflog                              ancillarymanipulators           complete
git-remote                              ancillarymanipulators           complete
git-replace                             ancillarymanipulators           complete
git-request-pull                        foreignscminterface             complete
git-rerere                              ancillaryinterrogators
git-revert                              mainporcelain
git-rev-list                            plumbinginterrogators
git-rev-parse                           ancillaryinterrogators
git-rm                                  mainporcelain           worktree
git-send-email                          foreignscminterface             complete
git-send-pack                           synchingrepositories
git-shell                               synchelpers
git-shortlog                            mainporcelain
git-show-index                          plumbinginterrogators
git-show-ref                            plumbinginterrogators
git-sh-i18n                             purehelpers
git-sh-setup                            purehelpers
git-stash                               mainporcelain
git-stage                                                               complete
git-stripspace                          purehelpers
git-svn                                 foreignscminterface
git-symbolic-ref                        plumbingmanipulators
git-unpack-file                         plumbinginterrogators
git-unpack-objects                      plumbingmanipulators
git-update-ref                          plumbingmanipulators
git-upload-archive                      synchelpers
git-upload-pack                         synchelpers
git-var                                 plumbinginterrogators
git-verify-commit                       ancillaryinterrogators
git-verify-pack                         plumbinginterrogators
git-verify-tag                          ancillaryinterrogators
gitweb                                  ancillaryinterrogators
git-whatchanged                         ancillaryinterrogators          complete
git-worktree                            mainporcelain
gitattributes                           guide
gitcli                                  guide
gitdiffcore                             guide
githooks                                guide
gitignore                               guide
gitmodules                              guide
gitnamespaces                           guide
gitrepository-layout                    guide
gitrevisions                            guide
gittutorial-2                           guide
gittutorial                             guide

^ permalink raw reply related	[relevance 1%]

* [GSoC][PATCH v6 12/20] rebase -i: remove unused modes and functions
  @ 2018-08-10 16:51  6%         ` Alban Gruin
    1 sibling, 0 replies; 200+ results
From: Alban Gruin @ 2018-08-10 16:51 UTC (permalink / raw)
  To: git
  Cc: Stefan Beller, Christian Couder, Pratik Karki,
	Johannes Schindelin, phillip.wood, gitster, Alban Gruin

This removes the modes `--skip-unnecessary-picks`, `--append-todo-help`,
and `--checkout-onto` from rebase--helper.c, the functions of
git-rebase--interactive.sh that were rendered useless by the rewrite of
complete_action(), and append_todo_help_to_file() from
rebase-interactive.c.

skip_unnecessary_picks() and checkout_onto() becomes static, as they are
only used inside of the sequencer.

Signed-off-by: Alban Gruin <alban.gruin@gmail.com>
---
No changes since v5.

 builtin/rebase--helper.c   | 23 ++----------------
 git-rebase--interactive.sh | 50 --------------------------------------
 rebase-interactive.c       | 22 -----------------
 rebase-interactive.h       |  1 -
 sequencer.c                |  8 +++---
 sequencer.h                |  4 ---
 6 files changed, 6 insertions(+), 102 deletions(-)

diff --git a/builtin/rebase--helper.c b/builtin/rebase--helper.c
index 01b3333958..e1460136f5 100644
--- a/builtin/rebase--helper.c
+++ b/builtin/rebase--helper.c
@@ -17,9 +17,8 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
 	int abbreviate_commands = 0, rebase_cousins = -1;
 	enum {
 		CONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_OIDS, EXPAND_OIDS,
-		CHECK_TODO_LIST, SKIP_UNNECESSARY_PICKS, REARRANGE_SQUASH,
-		ADD_EXEC, APPEND_TODO_HELP, EDIT_TODO, PREPARE_BRANCH,
-		CHECKOUT_ONTO, COMPLETE_ACTION
+		CHECK_TODO_LIST, REARRANGE_SQUASH, ADD_EXEC, EDIT_TODO, PREPARE_BRANCH,
+		COMPLETE_ACTION
 	} command = 0;
 	struct option options[] = {
 		OPT_BOOL(0, "ff", &opts.allow_ff, N_("allow fast-forward")),
@@ -44,21 +43,15 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
 			N_("expand commit ids in the todo list"), EXPAND_OIDS),
 		OPT_CMDMODE(0, "check-todo-list", &command,
 			N_("check the todo list"), CHECK_TODO_LIST),
-		OPT_CMDMODE(0, "skip-unnecessary-picks", &command,
-			N_("skip unnecessary picks"), SKIP_UNNECESSARY_PICKS),
 		OPT_CMDMODE(0, "rearrange-squash", &command,
 			N_("rearrange fixup/squash lines"), REARRANGE_SQUASH),
 		OPT_CMDMODE(0, "add-exec-commands", &command,
 			N_("insert exec commands in todo list"), ADD_EXEC),
-		OPT_CMDMODE(0, "append-todo-help", &command,
-			    N_("insert the help in the todo list"), APPEND_TODO_HELP),
 		OPT_CMDMODE(0, "edit-todo", &command,
 			    N_("edit the todo list during an interactive rebase"),
 			    EDIT_TODO),
 		OPT_CMDMODE(0, "prepare-branch", &command,
 			    N_("prepare the branch to be rebased"), PREPARE_BRANCH),
-		OPT_CMDMODE(0, "checkout-onto", &command,
-			    N_("checkout a commit"), CHECKOUT_ONTO),
 		OPT_CMDMODE(0, "complete-action", &command,
 			    N_("complete the action"), COMPLETE_ACTION),
 		OPT_END()
@@ -94,26 +87,14 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
 		return !!transform_todos(flags);
 	if (command == CHECK_TODO_LIST && argc == 1)
 		return !!check_todo_list();
-	if (command == SKIP_UNNECESSARY_PICKS && argc == 1) {
-		struct object_id oid;
-		int ret = skip_unnecessary_picks(&oid);
-
-		if (!ret)
-			puts(oid_to_hex(&oid));
-		return !!ret;
-	}
 	if (command == REARRANGE_SQUASH && argc == 1)
 		return !!rearrange_squash();
 	if (command == ADD_EXEC && argc == 2)
 		return !!sequencer_add_exec_commands(argv[1]);
-	if (command == APPEND_TODO_HELP && argc == 1)
-		return !!append_todo_help_to_file(0, keep_empty);
 	if (command == EDIT_TODO && argc == 1)
 		return !!edit_todo_list(flags);
 	if (command == PREPARE_BRANCH && argc == 2)
 		return !!prepare_branch_to_be_rebased(&opts, argv[1]);
-	if (command == CHECKOUT_ONTO && argc == 4)
-		return !!checkout_onto(&opts, argv[1], argv[2], argv[3]);
 	if (command == COMPLETE_ACTION && argc == 6)
 		return !!complete_action(&opts, flags, argv[1], argv[2], argv[3],
 					 argv[4], argv[5], autosquash);
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index 59dc4888a6..0d66c0f8b8 100644
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -16,56 +16,6 @@ todo="$state_dir"/git-rebase-todo
 GIT_CHERRY_PICK_HELP="$resolvemsg"
 export GIT_CHERRY_PICK_HELP
 
-comment_char=$(git config --get core.commentchar 2>/dev/null)
-case "$comment_char" in
-'' | auto)
-	comment_char="#"
-	;;
-?)
-	;;
-*)
-	comment_char=$(echo "$comment_char" | cut -c1)
-	;;
-esac
-
-die_abort () {
-	apply_autostash
-	rm -rf "$state_dir"
-	die "$1"
-}
-
-has_action () {
-	test -n "$(git stripspace --strip-comments <"$1")"
-}
-
-git_sequence_editor () {
-	if test -z "$GIT_SEQUENCE_EDITOR"
-	then
-		GIT_SEQUENCE_EDITOR="$(git config sequence.editor)"
-		if [ -z "$GIT_SEQUENCE_EDITOR" ]
-		then
-			GIT_SEQUENCE_EDITOR="$(git var GIT_EDITOR)" || return $?
-		fi
-	fi
-
-	eval "$GIT_SEQUENCE_EDITOR" '"$@"'
-}
-
-expand_todo_ids() {
-	git rebase--helper --expand-ids
-}
-
-collapse_todo_ids() {
-	git rebase--helper --shorten-ids
-}
-
-get_missing_commit_check_level () {
-	check_level=$(git config --get rebase.missingCommitsCheck)
-	check_level=${check_level:-ignore}
-	# Don't be case sensitive
-	printf '%s' "$check_level" | tr 'A-Z' 'a-z'
-}
-
 # Initiate an action. If the cannot be any
 # further action it  may exec a command
 # or exit and not return.
diff --git a/rebase-interactive.c b/rebase-interactive.c
index 4a9a10eff4..0f4119cbae 100644
--- a/rebase-interactive.c
+++ b/rebase-interactive.c
@@ -52,28 +52,6 @@ void append_todo_help(unsigned edit_todo, unsigned keep_empty,
 	}
 }
 
-int append_todo_help_to_file(unsigned edit_todo, unsigned keep_empty)
-{
-	struct strbuf buf = STRBUF_INIT;
-	FILE *todo;
-	int ret;
-
-	todo = fopen_or_warn(rebase_path_todo(), "a");
-	if (!todo)
-		return -1;
-
-	append_todo_help(edit_todo, keep_empty, &buf);
-
-	ret = fputs(buf.buf, todo);
-	if (ret < 0)
-		error_errno(_("could not append help text to '%s'"), rebase_path_todo());
-
-	fclose(todo);
-	strbuf_release(&buf);
-
-	return ret;
-}
-
 int edit_todo_list(unsigned flags)
 {
 	struct strbuf buf = STRBUF_INIT;
diff --git a/rebase-interactive.h b/rebase-interactive.h
index d33f3176b7..971da03776 100644
--- a/rebase-interactive.h
+++ b/rebase-interactive.h
@@ -3,7 +3,6 @@
 
 void append_todo_help(unsigned edit_todo, unsigned keep_empty,
 		      struct strbuf *buf);
-int append_todo_help_to_file(unsigned edit_todo, unsigned keep_empty);
 int edit_todo_list(unsigned flags);
 
 #endif
diff --git a/sequencer.c b/sequencer.c
index ae1f44de2f..3800439c10 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -3175,9 +3175,9 @@ int prepare_branch_to_be_rebased(struct replay_opts *opts, const char *commit)
 	return 0;
 }
 
-int checkout_onto(struct replay_opts *opts,
-		  const char *onto_name, const char *onto,
-		  const char *orig_head)
+static int checkout_onto(struct replay_opts *opts,
+			 const char *onto_name, const char *onto,
+			 const char *orig_head)
 {
 	struct object_id oid;
 	const char *action = reflog_message(opts, "start", "checkout %s", onto_name);
@@ -4424,7 +4424,7 @@ static int rewrite_file(const char *path, const char *buf, size_t len)
 }
 
 /* skip picking commits whose parents are unchanged */
-int skip_unnecessary_picks(struct object_id *output_oid)
+static int skip_unnecessary_picks(struct object_id *output_oid)
 {
 	const char *todo_file = rebase_path_todo();
 	struct strbuf buf = STRBUF_INIT;
diff --git a/sequencer.h b/sequencer.h
index d58766c6d7..02e3d7940e 100644
--- a/sequencer.h
+++ b/sequencer.h
@@ -91,7 +91,6 @@ int sequencer_add_exec_commands(const char *command);
 int transform_todos(unsigned flags);
 enum missing_commit_check_level get_missing_commit_check_level(void);
 int check_todo_list(void);
-int skip_unnecessary_picks(struct object_id *output_oid);
 int complete_action(struct replay_opts *opts, unsigned flags,
 		    const char *shortrevisions, const char *onto_name,
 		    const char *onto, const char *orig_head, const char *cmd,
@@ -114,9 +113,6 @@ void commit_post_rewrite(const struct commit *current_head,
 			 const struct object_id *new_head);
 
 int prepare_branch_to_be_rebased(struct replay_opts *opts, const char *commit);
-int checkout_onto(struct replay_opts *opts,
-		  const char *onto_name, const char *onto,
-		  const char *orig_head);
 
 #define SUMMARY_INITIAL_COMMIT   (1 << 0)
 #define SUMMARY_SHOW_AUTHOR_DATE (1 << 1)
-- 
2.18.0


^ permalink raw reply related	[relevance 6%]

* [PATCH v3 1/1] clone: report duplicate entries on case-insensitive filesystems
  @ 2018-08-10 15:36  4%       ` Nguyễn Thái Ngọc Duy
  2018-08-11 10:09  0%         ` SZEDER Gábor
  2018-08-12  9:07  9%       ` [PATCH v4] " Nguyễn Thái Ngọc Duy
  1 sibling, 1 reply; 200+ results
From: Nguyễn Thái Ngọc Duy @ 2018-08-10 15:36 UTC (permalink / raw)
  To: pclouds; +Cc: git, git, gitster, newren, pawelparuzel95, peff, sandals, tboegi

Paths that only differ in case work fine in a case-sensitive
filesystems, but if those repos are cloned in a case-insensitive one,
you'll get problems. The first thing to notice is "git status" will
never be clean with no indication what exactly is "dirty".

This patch helps the situation a bit by pointing out the problem at
clone time. Even though this patch talks about case sensitivity, the
patch makes no assumption about folding rules by the filesystem. It
simply observes that if an entry has been already checked out at clone
time when we're about to write a new path, some folding rules are
behind this.

This patch is tested with vim-colorschemes repository on a JFS partition
with case insensitive support on Linux. This repository has two files
darkBlue.vim and darkblue.vim.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/clone.c |  1 +
 cache.h         |  2 ++
 entry.c         | 32 ++++++++++++++++++++++++++++++++
 unpack-trees.c  | 22 ++++++++++++++++++++++
 unpack-trees.h  |  1 +
 5 files changed, 58 insertions(+)

diff --git a/builtin/clone.c b/builtin/clone.c
index 9ebb5acf56..38d5609282 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -748,6 +748,7 @@ static int checkout(int submodule_progress)
 	memset(&opts, 0, sizeof opts);
 	opts.update = 1;
 	opts.merge = 1;
+	opts.clone = 1;
 	opts.fn = oneway_merge;
 	opts.verbose_update = (option_verbosity >= 0);
 	opts.src_index = &the_index;
diff --git a/cache.h b/cache.h
index 8dc7134f00..cdf0984707 100644
--- a/cache.h
+++ b/cache.h
@@ -1515,9 +1515,11 @@ struct checkout {
 	const char *base_dir;
 	int base_dir_len;
 	struct delayed_checkout *delayed_checkout;
+	int *nr_duplicates;
 	unsigned force:1,
 		 quiet:1,
 		 not_new:1,
+		 clone:1,
 		 refresh_cache:1;
 };
 #define CHECKOUT_INIT { NULL, "" }
diff --git a/entry.c b/entry.c
index b5d1d3cf23..f2d73e6255 100644
--- a/entry.c
+++ b/entry.c
@@ -399,6 +399,35 @@ static int check_path(const char *path, int len, struct stat *st, int skiplen)
 	return lstat(path, st);
 }
 
+static void mark_duplicate_entries(const struct checkout *state,
+				   struct cache_entry *ce, struct stat *st)
+{
+	int i;
+	int *count = state->nr_duplicates;
+
+	if (!count)
+		BUG("state->nr_duplicates must not be NULL");
+
+	ce->ce_flags |= CE_MATCHED;
+	(*count)++;
+
+#if !defined(GIT_WINDOWS_NATIVE) /* inode is always zero on Windows */
+	for (i = 0; i < state->istate->cache_nr; i++) {
+		struct cache_entry *dup = state->istate->cache[i];
+
+		if (dup->ce_flags & (CE_MATCHED | CE_VALID | CE_SKIP_WORKTREE))
+			continue;
+
+		if (ce_uptodate(dup) &&
+		    dup->ce_stat_data.sd_ino == st->st_ino) {
+			dup->ce_flags |= CE_MATCHED;
+			(*count)++;
+			break;
+		}
+	}
+#endif
+}
+
 /*
  * Write the contents from ce out to the working tree.
  *
@@ -455,6 +484,9 @@ int checkout_entry(struct cache_entry *ce,
 			return -1;
 		}
 
+		if (state->clone)
+			mark_duplicate_entries(state, ce, &st);
+
 		/*
 		 * We unlink the old file, to get the new one with the
 		 * right permissions (including umask, which is nasty
diff --git a/unpack-trees.c b/unpack-trees.c
index f9efee0836..d4fece913c 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -344,12 +344,20 @@ static int check_updates(struct unpack_trees_options *o)
 	struct index_state *index = &o->result;
 	struct checkout state = CHECKOUT_INIT;
 	int i;
+	int nr_duplicates = 0;
 
 	state.force = 1;
 	state.quiet = 1;
 	state.refresh_cache = 1;
 	state.istate = index;
 
+	if (o->clone) {
+		state.clone = 1;
+		state.nr_duplicates = &nr_duplicates;
+		for (i = 0; i < index->cache_nr; i++)
+			index->cache[i]->ce_flags &= ~CE_MATCHED;
+	}
+
 	progress = get_progress(o);
 
 	if (o->update)
@@ -414,6 +422,20 @@ static int check_updates(struct unpack_trees_options *o)
 	errs |= finish_delayed_checkout(&state);
 	if (o->update)
 		git_attr_set_direction(GIT_ATTR_CHECKIN, NULL);
+
+	if (o->clone && state.nr_duplicates) {
+		warning(_("the following paths have collided and only one from the same\n"
+			  "colliding group is in the working tree:\n"));
+		for (i = 0; i < index->cache_nr; i++) {
+			struct cache_entry *ce = index->cache[i];
+
+			if (!(ce->ce_flags & CE_MATCHED))
+				continue;
+			fprintf(stderr, "  '%s'\n", ce->name);
+			ce->ce_flags &= ~CE_MATCHED;
+		}
+	}
+
 	return errs != 0;
 }
 
diff --git a/unpack-trees.h b/unpack-trees.h
index c2b434c606..d940f1c5c2 100644
--- a/unpack-trees.h
+++ b/unpack-trees.h
@@ -42,6 +42,7 @@ struct unpack_trees_options {
 	unsigned int reset,
 		     merge,
 		     update,
+		     clone,
 		     index_only,
 		     nontrivial_merge,
 		     trivial_merges_only,
-- 
2.18.0.915.gd571298aae


^ permalink raw reply related	[relevance 4%]

* Re: [PATCH 2/2] submodule: munge paths to submodule git directories
  @ 2018-08-09 21:26  3%     ` Jeff King
  2018-08-14 18:04  0%       ` Brandon Williams
  0 siblings, 1 reply; 200+ results
From: Jeff King @ 2018-08-09 21:26 UTC (permalink / raw)
  To: Brandon Williams; +Cc: git

On Wed, Aug 08, 2018 at 03:33:23PM -0700, Brandon Williams wrote:

> Commit 0383bbb901 (submodule-config: verify submodule names as paths,
> 2018-04-30) introduced some checks to ensure that submodule names don't
> include directory traversal components (e.g. "../").
> 
> This addresses the vulnerability identified in 0383bbb901 but the root
> cause is that we use submodule names to construct paths to the
> submodule's git directory.  What we really should do is munge the
> submodule name before using it to construct a path.
> 
> Teach "submodule_name_to_gitdir()" to munge a submodule's name (by url
> encoding it) before using it to build a path to the submodule's gitdir.

I like this approach very much, and I think using url encoding is much
better than an opaque hash (purely because it makes debugging and
inspection saner).

Two thoughts, though:

> +	modules_len = buf->len;
>  	strbuf_addstr(buf, submodule_name);
> +
> +	/*
> +	 * If the submodule gitdir already exists using the old-fashioned
> +	 * location (which uses the submodule name as-is, without munging it)
> +	 * then return that.
> +	 */
> +	if (!access(buf->buf, F_OK))
> +		return;

I think this backwards-compatibility is necessary to avoid pain. But
until it goes away, I don't think this is helping the vulnerability from
0383bbb901. Because there the issue was that the submodule name pointed
back into the working tree, so this access() would find the untrusted
working tree code and say "ah, an old-fashioned name!".

In theory a fresh clone could set a config option for "I only speak
use new-style modules". And there could even be a conversion program
that moves the modules as appropriate, fixes up the .git files in the
working tree, and then sets that config.

In fact, I think that config option _could_ be done by bumping
core.repositoryformatversion and then setting extensions.submodulenames
to "url" or something. Then you could never run into the confusing case
where you have a clone done by a new version of git (using new-style
names), but using an old-style version gets confused because it can't
find the module directories (instead, it would barf and say "I don't
know about that extension").

I don't know if any of that is worth it, though. We already fixed the
problem from 0383bbb901. There may be a _different_ "break out of the
modules directory" vulnerability, but since we disallow ".." it's hard
to see what it would be (the best I could come up with is maybe pointing
one module into the interior of another module, but I think you'd have
to trouble overwriting anything useful).

And while an old-style version of Git being confused might be annoying,
I suspect that bumping the repository version would be even _more_
annoying, because it would hit every command, not just ones that try to
touch those submodules.

> diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh
> index 2c2c97e144..963693332c 100755
> --- a/t/t7400-submodule-basic.sh
> +++ b/t/t7400-submodule-basic.sh
> @@ -933,7 +933,7 @@ test_expect_success 'recursive relative submodules stay relative' '
>  		cd clone2 &&
>  		git submodule update --init --recursive &&
>  		echo "gitdir: ../.git/modules/sub3" >./sub3/.git_expect &&
> -		echo "gitdir: ../../../.git/modules/sub3/modules/dirdir/subsub" >./sub3/dirdir/subsub/.git_expect
> +		echo "gitdir: ../../../.git/modules/sub3/modules/dirdir%2fsubsub" >./sub3/dirdir/subsub/.git_expect

One interesting thing about url-encoding is that it's not one-to-one.
This case could also be %2F, which is a different file (on a
case-sensitive filesystem). I think "%20" and "+" are similarly
interchangeable.

If we were decoding the filenames, that's fine. The round-trip is
lossless.

But that's not quite how the new code behaves. We encode the input and
then check to see if it matches an encoding we previously performed. So
if our urlencode routines ever change, this will subtly break.

I don't know how much it's worth caring about. We're not that likely to
change the routines ourself (though certainly a third-party
implementation would need to know our exact url-encoding decisions).

Some possible actions:

 0. Do nothing, and cross our fingers. ;)

 1. Don't use strbuf_addstr_urlencode(), but rather our own munging
    function which we know will remain stable (or alternatively, a flag
    to strbuf_addstr_urlencode to get the consistent behavior).

 2. Make sure we have tests which cover this, so at least somebody
    changing the urlencode decisions will see a breakage. Your test here
    covers the upper/lowercase one, but we might want one that covers
    "+". (There may be more ambiguous cases, but those are the ones I
    know about).

 3. Rather than check for the existence of names, decode what's actually
    in the modules/ directory to create an in-memory index of names.

    I hesitate to suggest that, because it's obviously way more
    complicated, and may perform worse if you have a lot of modules
    (since you have to readdir() and decode the whole directory just to
    look up one module).

    But I think it also gives a more elegant solution to the
    backwards-compatibility problem, since we could recognize both new
    and old-style names. There's some ambiguity (e.g., is "foo%2fbar"
    "foo/bar", or did somebody really have a name with a percent in
    it?),. but in theory you could respect either name (giving
    preference to new-style in case of a conflict).

    And I think the result would be immune to any directory-escape
    vulnerabilities, because we'd always start with what actually exists
    in $GIT_DIR/modules/, which we know _we_ will have written.

    Again, I'm not sure if it's worth the effort, but I thought I'd
    throw it out there.

-Peff

^ permalink raw reply	[relevance 3%]

* [PATCH 2/3] config: fix case sensitive subsection names on writing
  2018-08-08 19:50  6% [PATCH 0/3] Resending sb/config-write-fix Stefan Beller
  2018-08-08 19:50  7% ` [PATCH 1/3] t1300: document current behavior of setting options Stefan Beller
@ 2018-08-08 19:50 20% ` Stefan Beller
  1 sibling, 0 replies; 200+ results
From: Stefan Beller @ 2018-08-08 19:50 UTC (permalink / raw)
  To: gitster; +Cc: git, Stefan Beller

A user reported a submodule issue regarding a section mix-up,
but it could be boiled down to the following test case:

  $ git init test  && cd test
  $ git config foo."Bar".key test
  $ git config foo."bar".key test
  $ tail -n 3 .git/config
  [foo "Bar"]
        key = test
        key = test

Sub sections are case sensitive and we have a test for correctly reading
them. However we do not have a test for writing out config correctly with
case sensitive subsection names, which is why this went unnoticed in
6ae996f2acf (git_config_set: make use of the config parser's event
stream, 2018-04-09)

Unfortunately we have to make a distinction between old style configuration
that looks like

  [foo.Bar]
        key = test

and the new quoted style as seen above. The old style is documented as
case-agnostic, hence we need to keep 'strncasecmp'; although the
resulting setting for the old style config differs from the configuration.
That will be fixed in a follow up patch.

Reported-by: JP Sugarbroad <jpsugar@google.com>
Signed-off-by: Stefan Beller <sbeller@google.com>
---
 config.c          | 12 +++++++++++-
 t/t1300-config.sh |  1 +
 2 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/config.c b/config.c
index 02050529788..27e800c7ce8 100644
--- a/config.c
+++ b/config.c
@@ -35,6 +35,7 @@ struct config_source {
 	int eof;
 	struct strbuf value;
 	struct strbuf var;
+	unsigned subsection_case_sensitive : 1;
 
 	int (*do_fgetc)(struct config_source *c);
 	int (*do_ungetc)(int c, struct config_source *conf);
@@ -603,6 +604,7 @@ static int get_value(config_fn_t fn, void *data, struct strbuf *name)
 
 static int get_extended_base_var(struct strbuf *name, int c)
 {
+	cf->subsection_case_sensitive = 0;
 	do {
 		if (c == '\n')
 			goto error_incomplete_line;
@@ -639,6 +641,7 @@ static int get_extended_base_var(struct strbuf *name, int c)
 
 static int get_base_var(struct strbuf *name)
 {
+	cf->subsection_case_sensitive = 1;
 	for (;;) {
 		int c = get_next_char();
 		if (cf->eof)
@@ -2328,14 +2331,21 @@ static int store_aux_event(enum config_event_t type,
 	store->parsed[store->parsed_nr].type = type;
 
 	if (type == CONFIG_EVENT_SECTION) {
+		int (*cmpfn)(const char *, const char *, size_t);
+
 		if (cf->var.len < 2 || cf->var.buf[cf->var.len - 1] != '.')
 			return error("invalid section name '%s'", cf->var.buf);
 
+		if (cf->subsection_case_sensitive)
+			cmpfn = strncasecmp;
+		else
+			cmpfn = strncmp;
+
 		/* Is this the section we were looking for? */
 		store->is_keys_section =
 			store->parsed[store->parsed_nr].is_keys_section =
 			cf->var.len - 1 == store->baselen &&
-			!strncasecmp(cf->var.buf, store->key, store->baselen);
+			!cmpfn(cf->var.buf, store->key, store->baselen);
 		if (store->is_keys_section) {
 			store->section_seen = 1;
 			ALLOC_GROW(store->seen, store->seen_nr + 1,
diff --git a/t/t1300-config.sh b/t/t1300-config.sh
index c15c19bf78d..77c5899d000 100755
--- a/t/t1300-config.sh
+++ b/t/t1300-config.sh
@@ -1260,6 +1260,7 @@ test_expect_success 'setting different case sensitive subsections ' '
 		Qc = v2
 		[d "e"]
 		f = v1
+		[d "E"]
 		Qf = v2
 	EOF
 	# exact match
-- 
2.18.0.597.ga71716f1ad-goog


^ permalink raw reply related	[relevance 20%]

* [PATCH 1/3] t1300: document current behavior of setting options
  2018-08-08 19:50  6% [PATCH 0/3] Resending sb/config-write-fix Stefan Beller
@ 2018-08-08 19:50  7% ` Stefan Beller
  2018-08-08 19:50 20% ` [PATCH 2/3] config: fix case sensitive subsection names on writing Stefan Beller
  1 sibling, 0 replies; 200+ results
From: Stefan Beller @ 2018-08-08 19:50 UTC (permalink / raw)
  To: gitster; +Cc: git, Stefan Beller

This documents current behavior of the config machinery, when changing
the value of some settings. This patch just serves to provide a baseline
for the follow up that will fix some issues with the current behavior.

Signed-off-by: Stefan Beller <sbeller@google.com>
---
 t/t1300-config.sh | 86 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 86 insertions(+)

diff --git a/t/t1300-config.sh b/t/t1300-config.sh
index e43982a9c1f..c15c19bf78d 100755
--- a/t/t1300-config.sh
+++ b/t/t1300-config.sh
@@ -1188,6 +1188,92 @@ test_expect_success 'last one wins: three level vars' '
 	test_cmp expect actual
 '
 
+test_expect_success 'old-fashioned settings are case insensitive' '
+	test_when_finished "rm -f testConfig testConfig_expect testConfig_actual" &&
+
+	cat >testConfig_actual <<-EOF &&
+		[V.A]
+		r = value1
+	EOF
+	q_to_tab >testConfig_expect <<-EOF &&
+		[V.A]
+		Qr = value2
+	EOF
+	git config -f testConfig_actual "v.a.r" value2 &&
+	test_cmp testConfig_expect testConfig_actual &&
+
+	cat >testConfig_actual <<-EOF &&
+		[V.A]
+		r = value1
+	EOF
+	q_to_tab >testConfig_expect <<-EOF &&
+		[V.A]
+		QR = value2
+	EOF
+	git config -f testConfig_actual "V.a.R" value2 &&
+	test_cmp testConfig_expect testConfig_actual &&
+
+	cat >testConfig_actual <<-EOF &&
+		[V.A]
+		r = value1
+	EOF
+	q_to_tab >testConfig_expect <<-EOF &&
+		[V.A]
+		r = value1
+		Qr = value2
+	EOF
+	git config -f testConfig_actual "V.A.r" value2 &&
+	test_cmp testConfig_expect testConfig_actual &&
+
+	cat >testConfig_actual <<-EOF &&
+		[V.A]
+		r = value1
+	EOF
+	q_to_tab >testConfig_expect <<-EOF &&
+		[V.A]
+		r = value1
+		Qr = value2
+	EOF
+	git config -f testConfig_actual "v.A.r" value2 &&
+	test_cmp testConfig_expect testConfig_actual
+'
+
+test_expect_success 'setting different case sensitive subsections ' '
+	test_when_finished "rm -f testConfig testConfig_expect testConfig_actual" &&
+
+	cat >testConfig_actual <<-EOF &&
+		[V "A"]
+		R = v1
+		[K "E"]
+		Y = v1
+		[a "b"]
+		c = v1
+		[d "e"]
+		f = v1
+	EOF
+	q_to_tab >testConfig_expect <<-EOF &&
+		[V "A"]
+		Qr = v2
+		[K "E"]
+		Qy = v2
+		[a "b"]
+		Qc = v2
+		[d "e"]
+		f = v1
+		Qf = v2
+	EOF
+	# exact match
+	git config -f testConfig_actual a.b.c v2 &&
+	# match section and subsection, key is cased differently.
+	git config -f testConfig_actual K.E.y v2 &&
+	# section and key are matched case insensitive, but subsection needs
+	# to match; When writing out new values only the key is adjusted
+	git config -f testConfig_actual v.A.r v2 &&
+	# subsection is not matched:
+	git config -f testConfig_actual d.E.f v2 &&
+	test_cmp testConfig_expect testConfig_actual
+'
+
 for VAR in a .a a. a.0b a."b c". a."b c".0d
 do
 	test_expect_success "git -c $VAR=VAL rejects invalid '$VAR'" '
-- 
2.18.0.597.ga71716f1ad-goog


^ permalink raw reply related	[relevance 7%]

* [PATCH 0/3] Resending sb/config-write-fix
@ 2018-08-08 19:50  6% Stefan Beller
  2018-08-08 19:50  7% ` [PATCH 1/3] t1300: document current behavior of setting options Stefan Beller
  2018-08-08 19:50 20% ` [PATCH 2/3] config: fix case sensitive subsection names on writing Stefan Beller
  0 siblings, 2 replies; 200+ results
From: Stefan Beller @ 2018-08-08 19:50 UTC (permalink / raw)
  To: gitster; +Cc: git, Stefan Beller

This is a resend of sb/config-write-fix, with a slightly
better commit message and a renamed variable.

Thanks,
Stefan


Stefan Beller (3):
  t1300: document current behavior of setting options
  config: fix case sensitive subsection names on writing
  git-config: document accidental multi-line setting in deprecated
    syntax

 Documentation/git-config.txt | 21 +++++++++
 config.c                     | 12 ++++-
 t/t1300-config.sh            | 87 ++++++++++++++++++++++++++++++++++++
 3 files changed, 119 insertions(+), 1 deletion(-)

./git-range-diff origin/sb/config-write-fix...HEAD >>0000-cover-letter.patch 
2.18.0.597.ga71716f1ad-goog

1:  999d9026272 ! 1:  e40f57f3da1 t1300: document current behavior of setting options
    @@ -7,7 +7,6 @@
         for the follow up that will fix some issues with the current behavior.
     
         Signed-off-by: Stefan Beller <sbeller@google.com>
    -    Signed-off-by: Junio C Hamano <gitster@pobox.com>
     
      diff --git a/t/t1300-config.sh b/t/t1300-config.sh
      --- a/t/t1300-config.sh
2:  c667e555066 ! 2:  f01cb1d9dae config: fix case sensitive subsection names on writing
    @@ -2,8 +2,8 @@
     
         config: fix case sensitive subsection names on writing
     
    -    A use reported a submodule issue regarding strange case indentation
    -    issues, but it could be boiled down to the following test case:
    +    A user reported a submodule issue regarding a section mix-up,
    +    but it could be boiled down to the following test case:
     
           $ git init test  && cd test
           $ git config foo."Bar".key test
    @@ -32,7 +32,6 @@
     
         Reported-by: JP Sugarbroad <jpsugar@google.com>
         Signed-off-by: Stefan Beller <sbeller@google.com>
    -    Signed-off-by: Junio C Hamano <gitster@pobox.com>
     
      diff --git a/config.c b/config.c
      --- a/config.c
    @@ -41,7 +40,7 @@
      	int eof;
      	struct strbuf value;
      	struct strbuf var;
    -+	unsigned section_name_old_dot_style : 1;
    ++	unsigned subsection_case_sensitive : 1;
      
      	int (*do_fgetc)(struct config_source *c);
      	int (*do_ungetc)(int c, struct config_source *conf);
    @@ -49,7 +48,7 @@
      
      static int get_extended_base_var(struct strbuf *name, int c)
      {
    -+	cf->section_name_old_dot_style = 0;
    ++	cf->subsection_case_sensitive = 0;
      	do {
      		if (c == '\n')
      			goto error_incomplete_line;
    @@ -57,7 +56,7 @@
      
      static int get_base_var(struct strbuf *name)
      {
    -+	cf->section_name_old_dot_style = 1;
    ++	cf->subsection_case_sensitive = 1;
      	for (;;) {
      		int c = get_next_char();
      		if (cf->eof)
    @@ -70,7 +69,7 @@
      		if (cf->var.len < 2 || cf->var.buf[cf->var.len - 1] != '.')
      			return error("invalid section name '%s'", cf->var.buf);
      
    -+		if (cf->section_name_old_dot_style)
    ++		if (cf->subsection_case_sensitive)
     +			cmpfn = strncasecmp;
     +		else
     +			cmpfn = strncmp;
3:  6749bb283a8 ! 3:  6b5ad773490 git-config: document accidental multi-line setting in deprecated syntax
    @@ -29,7 +29,6 @@
         spend time on fixing the behavior and just document it instead.
     
         Signed-off-by: Stefan Beller <sbeller@google.com>
    -    Signed-off-by: Junio C Hamano <gitster@pobox.com>
     
      diff --git a/Documentation/git-config.txt b/Documentation/git-config.txt
      --- a/Documentation/git-config.txt

^ permalink raw reply	[relevance 6%]

* [PATCH v2] clone: report duplicate entries on case-insensitive filesystems
  2018-07-30 15:27  4% ` [PATCH/RFC] clone: report duplicate entries on case-insensitive filesystems Nguyễn Thái Ngọc Duy
                     ` (2 preceding siblings ...)
  @ 2018-08-07 19:01  3%   ` Nguyễn Thái Ngọc Duy
    3 siblings, 1 reply; 200+ results
From: Nguyễn Thái Ngọc Duy @ 2018-08-07 19:01 UTC (permalink / raw)
  To: pclouds
  Cc: git, pawelparuzel95, peff, sandals, Elijah Newren, tboegi,
	Junio C Hamano, git

Paths that only differ in case work fine in a case-sensitive
filesystems, but if those repos are cloned in a case-insensitive one,
you'll get problems. The first thing to notice is "git status" will
never be clean with no indication what exactly is "dirty".

This patch helps the situation a bit by pointing out the problem at
clone time. Even though this patch talks about case sensitivity, the
patch makes no assumption about folding rules by the filesystem. It
simply observes that if an entry has been already checked out at clone
time when we're about to write a new path, some folding rules are
behind this.

I do not make any suggestions to fix or workaround the problem because
there are many different options, especially when the problem comes
from folding rules other than case (e.g. UTF-8 normalization, Windows
special paths...)

In the previous iteration, inode has been suggested to find the
matching entry. But it is platform specific, and because we already
have a common function for matching stat, the function is used here
even if it's more expensive. Bonus point is we don't need some "#ifdef
platform" around this code.

The cost goes higher when we find duplicated entries at the bottom of
the index, but the number of these entries should be very small that
total extra cost should not be really noticeable.

This patch is tested with vim-colorschemes repository on a JFS partion
with case insensitive support on Linux. This repository has two files
darkBlue.vim and darkblue.vim.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 v2 has completely different approach so no point in sending
 interdiff.

 One nice thing about this is we don't need platform specific code for
 detecting the duplicate entries. I think ce_match_stat() works even
 on Windows. And it's now equally expensive on all platforms :D

 builtin/clone.c |  1 +
 cache.h         |  2 ++
 entry.c         | 44 ++++++++++++++++++++++++++++++++++++++++++++
 unpack-trees.c  | 23 +++++++++++++++++++++++
 unpack-trees.h  |  1 +
 5 files changed, 71 insertions(+)

diff --git a/builtin/clone.c b/builtin/clone.c
index 9ebb5acf56..38d5609282 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -748,6 +748,7 @@ static int checkout(int submodule_progress)
 	memset(&opts, 0, sizeof opts);
 	opts.update = 1;
 	opts.merge = 1;
+	opts.clone = 1;
 	opts.fn = oneway_merge;
 	opts.verbose_update = (option_verbosity >= 0);
 	opts.src_index = &the_index;
diff --git a/cache.h b/cache.h
index 8dc7134f00..cdf0984707 100644
--- a/cache.h
+++ b/cache.h
@@ -1515,9 +1515,11 @@ struct checkout {
 	const char *base_dir;
 	int base_dir_len;
 	struct delayed_checkout *delayed_checkout;
+	int *nr_duplicates;
 	unsigned force:1,
 		 quiet:1,
 		 not_new:1,
+		 clone:1,
 		 refresh_cache:1;
 };
 #define CHECKOUT_INIT { NULL, "" }
diff --git a/entry.c b/entry.c
index b5d1d3cf23..3917bfc874 100644
--- a/entry.c
+++ b/entry.c
@@ -399,6 +399,47 @@ static int check_path(const char *path, int len, struct stat *st, int skiplen)
 	return lstat(path, st);
 }
 
+static void mark_duplicate_entries(const struct checkout *state,
+				   struct cache_entry *ce, struct stat *st)
+{
+	int i;
+	int *count = state->nr_duplicates;
+
+	if (!count)
+		BUG("state->nr_duplicates must not be NULL");
+
+	ce->ce_flags |= CE_MATCHED;
+	(*count)++;
+
+	if (!state->refresh_cache)
+		BUG("We need this to narrow down the set of updated entries");
+
+	for (i = 0; i < state->istate->cache_nr; i++) {
+		struct cache_entry *dup = state->istate->cache[i];
+
+		/*
+		 * This entry has not been checked out yet, otherwise
+		 * its stat info must have been updated. And since we
+		 * check out from top to bottom, the rest is guaranteed
+		 * not checked out. Stop now.
+		 */
+		if (!ce_uptodate(dup))
+			break;
+
+		if (dup->ce_flags & CE_MATCHED)
+			continue;
+
+		if (ce_match_stat(dup, st,
+				  CE_MATCH_IGNORE_VALID |
+				  CE_MATCH_IGNORE_SKIP_WORKTREE))
+			continue;
+
+		dup->ce_flags |= CE_MATCHED;
+		(*count)++;
+		break;
+	}
+}
+
 /*
  * Write the contents from ce out to the working tree.
  *
@@ -455,6 +496,9 @@ int checkout_entry(struct cache_entry *ce,
 			return -1;
 		}
 
+		if (state->clone)
+			mark_duplicate_entries(state, ce, &st);
+
 		/*
 		 * We unlink the old file, to get the new one with the
 		 * right permissions (including umask, which is nasty
diff --git a/unpack-trees.c b/unpack-trees.c
index f9efee0836..1b0c11142a 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -344,12 +344,20 @@ static int check_updates(struct unpack_trees_options *o)
 	struct index_state *index = &o->result;
 	struct checkout state = CHECKOUT_INIT;
 	int i;
+	int nr_duplicates = 0;
 
 	state.force = 1;
 	state.quiet = 1;
 	state.refresh_cache = 1;
 	state.istate = index;
 
+	if (o->clone) {
+		state.clone = 1;
+		state.nr_duplicates = &nr_duplicates;
+		for (i = 0; i < index->cache_nr; i++)
+			index->cache[i]->ce_flags &= ~CE_MATCHED;
+	}
+
 	progress = get_progress(o);
 
 	if (o->update)
@@ -414,6 +422,21 @@ static int check_updates(struct unpack_trees_options *o)
 	errs |= finish_delayed_checkout(&state);
 	if (o->update)
 		git_attr_set_direction(GIT_ATTR_CHECKIN, NULL);
+
+	if (o->clone && state.nr_duplicates) {
+		warning(_("the following paths in this repository only differ in case\n"
+			  "from another path and will cause problems because you have cloned\n"
+			  "it on an case-insensitive filesytem:\n"));
+		for (i = 0; i < index->cache_nr; i++) {
+			struct cache_entry *ce = index->cache[i];
+
+			if (!(ce->ce_flags & CE_MATCHED))
+				continue;
+			fprintf(stderr, "  '%s'\n", ce->name);
+			ce->ce_flags &= ~CE_MATCHED;
+		}
+	}
+
 	return errs != 0;
 }
 
diff --git a/unpack-trees.h b/unpack-trees.h
index c2b434c606..d940f1c5c2 100644
--- a/unpack-trees.h
+++ b/unpack-trees.h
@@ -42,6 +42,7 @@ struct unpack_trees_options {
 	unsigned int reset,
 		     merge,
 		     update,
+		     clone,
 		     index_only,
 		     nontrivial_merge,
 		     trivial_merges_only,
-- 
2.18.0.915.gd571298aae


^ permalink raw reply related	[relevance 3%]

* What's cooking in git.git (Aug 2018, #02; Mon, 6)
@ 2018-08-06 22:35  1% Junio C Hamano
  0 siblings, 0 replies; 200+ results
From: Junio C Hamano @ 2018-08-06 22:35 UTC (permalink / raw)
  To: git

Here are the topics that have been cooking.  Commits prefixed with
'-' are only in 'pu' (proposed updates) while commits prefixed with
'+' are in 'next'.  The ones marked with '.' do not appear in any of
the integration branches, but I am still holding onto them.

You can find the changes described here in the integration branches
of the repositories listed at

    http://git-blame.blogspot.com/p/git-public-repositories.html

--------------------------------------------------
[New Topics]

* js/rebase-merges-exec-fix (2018-08-06) 2 commits
 - rebase --exec: make it work with --rebase-merges
 - t3430: demonstrate what -r, --autosquash & --exec should do

 The "--exec" option to "git rebase --rebase-merges" placed the exec
 commands at wrong places, which has been corrected.
 
 cf. <pull.13.v2.git.gitgitgadget@gmail.com>


* nd/no-extern (2018-08-03) 12 commits
 - submodule.h: drop extern from function declaration
 - revision.h: drop extern from function declaration
 - repository.h: drop extern from function declaration
 - rerere.h: drop extern from function declaration
 - line-range.h: drop extern from function declaration
 - diff.h: remove extern from function declaration
 - diffcore.h: drop extern from function declaration
 - convert.h: drop 'extern' from function declaration
 - cache-tree.h: drop extern from function declaration
 - blame.h: drop extern on func declaration
 - attr.h: drop extern from function declaration
 - apply.h: drop extern on func declaration

 Noiseword "extern" has been removed from function decls in the
 header files.

 Will merge to 'next'.


* ar/t4150-am-scissors-test-fix (2018-08-06) 1 commit
 - t4150: fix broken test for am --scissors

 Test fix.

 Will merge to 'next'.


* en/t3031-title-fix (2018-08-06) 1 commit
 - t3031: update test description to mention desired behavior

 Test fix.

 Will merge to 'next'.


* hn/config-in-code-comment (2018-08-06) 1 commit
 - config: document git config getter return value

 Header update.

 Will merge to 'next'.


* jk/diff-rendered-docs (2018-08-06) 1 commit
 - add a script to diff rendered documentation

 Developer support to allow the end result of documentation update
 to be inspected more easily.

 Will merge to 'next'.


* js/pull-rebase-type-shorthand (2018-08-06) 1 commit
 - pull --rebase=<type>: allow single-letter abbreviations for the type

 "git pull --rebase=interactive" learned "i" as a short-hand for
 "interactive".

 Will merge to 'next'.


* nd/complete-config-vars (2018-08-06) 1 commit
 - Makefile: add missing dependency for command-list.h

 Build fix.

 Will merge to 'next'.


* nd/config-blame-sort (2018-08-06) 1 commit
 - config.txt: reorder blame stuff to keep config keys sorted

 Doc fix.

 Will merge to 'next'.


* wc/make-funnynames-shared-lazy-prereq (2018-08-06) 1 commit
 - t: factor out FUNNYNAMES as shared lazy prereq

 A test prerequisite defined by various test scripts with slightly
 different sematics has been consolidated into a single copy and
 made into a lazily defined one.



--------------------------------------------------
[Stalled]

* ma/wrapped-info (2018-05-28) 2 commits
 - usage: prefix all lines in `vreportf()`, not just the first
 - usage: extract `prefix_suffix_lines()` from `advise()`

 An attempt to help making multi-line messages fed to warning(),
 error(), and friends more easily translatable.

 Will discard and wait for a cleaned-up rewrite.
 cf. <20180529213957.GF7964@sigill.intra.peff.net>


* hn/bisect-first-parent (2018-04-21) 1 commit
 - bisect: create 'bisect_flags' parameter in find_bisection()

 Preliminary code update to allow passing more flags down the
 bisection codepath in the future.

 We do not add random code that does not have real users to our
 codebase, so let's have it wait until such a real code materializes
 before too long.


* av/fsmonitor-updates (2018-01-04) 6 commits
 - fsmonitor: use fsmonitor data in `git diff`
 - fsmonitor: remove debugging lines from t/t7519-status-fsmonitor.sh
 - fsmonitor: make output of test-dump-fsmonitor more concise
 - fsmonitor: update helper tool, now that flags are filled later
 - fsmonitor: stop inline'ing mark_fsmonitor_valid / _invalid
 - dir.c: update comments to match argument name

 Code clean-up on fsmonitor integration, plus optional utilization
 of the fsmonitor data in diff-files.

 Waiting for an update.
 cf. <alpine.DEB.2.21.1.1801042335130.32@MININT-6BKU6QN.europe.corp.microsoft.com>


* pb/bisect-helper-2 (2018-07-23) 8 commits
 - t6030: make various test to pass GETTEXT_POISON tests
 - bisect--helper: `bisect_start` shell function partially in C
 - bisect--helper: `get_terms` & `bisect_terms` shell function in C
 - bisect--helper: `bisect_next_check` shell function in C
 - bisect--helper: `check_and_set_terms` shell function in C
 - wrapper: move is_empty_file() and rename it as is_empty_or_missing_file()
 - bisect--helper: `bisect_write` shell function in C
 - bisect--helper: `bisect_reset` shell function in C

 Expecting a reroll.
 cf. <0102015f5e5ee171-f30f4868-886f-47a1-a4e4-b4936afc545d-000000@eu-west-1.amazonses.com>

 I just rebased the topic to a newer base as it did not build
 standalone with the base I originally queued the topic on, but
 otherwise there is no update to address any of the review comments
 in the thread above---we are still waiting for a reroll.


* jk/drop-ancient-curl (2017-08-09) 5 commits
 - http: #error on too-old curl
 - curl: remove ifdef'd code never used with curl >=7.19.4
 - http: drop support for curl < 7.19.4
 - http: drop support for curl < 7.16.0
 - http: drop support for curl < 7.11.1

 Some code in http.c that has bitrot is being removed.

 Expecting a reroll.


* mk/use-size-t-in-zlib (2017-08-10) 1 commit
 . zlib.c: use size_t for size

 The wrapper to call into zlib followed our long tradition to use
 "unsigned long" for sizes of regions in memory, which have been
 updated to use "size_t".

 Needs resurrecting by making sure the fix is good and still applies
 (or adjusted to today's codebase).

--------------------------------------------------
[Cooking]

* ab/fsck-transfer-updates (2018-07-27) 10 commits
 - fsck: test and document unknown fsck.<msg-id> values
 - fsck: add stress tests for fsck.skipList
 - fsck: test & document {fetch,receive}.fsck.* config fallback
 - fetch: implement fetch.fsck.*
 - transfer.fsckObjects tests: untangle confusing setup
 - config doc: elaborate on fetch.fsckObjects security
 - config doc: elaborate on what transfer.fsckObjects does
 - config doc: unify the description of fsck.* and receive.fsck.*
 - config doc: don't describe *.fetchObjects twice
 - receive.fsck.<msg-id> tests: remove dead code

 The test performed at the receiving end of "git push" to prevent
 bad objects from entering repository can be customized via
 receive.fsck.* configuration variables; we now have gained a
 counterpart to do the same on the "git fetch" side, with
 fetch.fsck.* configuration variables.

 Will merge to 'next'.


* ab/test-must-be-empty (2018-07-30) 1 commit
 - tests: make use of the test_must_be_empty function

 Test updates.

 Will merge to 'next'.


* ab/test-must-be-empty-for-master (2018-07-30) 1 commit
 - tests: make use of the test_must_be_empty function

 Test updates.

 Did anybody spot incorrect conversion in this yet?


* cb/p4-pre-submit-hook (2018-08-01) 1 commit
  (merged to 'next' on 2018-08-06 at e40ae4af80)
 + git-p4: add the `p4-pre-submit` hook

 "git p4 submit" learns to ask its own pre-submit hook if it should
 continue with submitting.

 Will merge to 'master'.


* es/rebase-i-author-script-fix (2018-07-31) 4 commits
 - sequencer: don't die() on bogus user-edited timestamp
 - sequencer: fix "rebase -i --root" corrupting author header timestamp
 - sequencer: fix "rebase -i --root" corrupting author header timezone
 - sequencer: fix "rebase -i --root" corrupting author header
 (this branch is used by pw/rebase-i-author-script-fix.)

 The "author-script" file "git rebase -i" creates got broken when
 we started to move the command away from shell script, which is
 getting fixed now.

 Will merge to 'next'.


* hn/highlight-sideband-keywords (2018-08-06) 2 commits
 - SQUASH???
 - sideband: highlight keywords in remote sideband output

 The sideband code learned to optionally paint selected keywords at
 the beginning of incoming lines on the receiving end.


* jn/subtree-test-fixes (2018-07-30) 2 commits
  (merged to 'next' on 2018-08-06 at 62f21c328f)
 + subtree test: simplify preparation of expected results
 + subtree test: add missing && to &&-chain

 Test fix.

 Will merge to 'master'.


* ms/http-proto-doc (2018-07-30) 1 commit
  (merged to 'next' on 2018-08-06 at df1cac9945)
 + doc: fix want-capability separator

 Doc fix.

 Will merge to 'master'.


* nd/pack-objects-threading-doc (2018-07-30) 1 commit
  (merged to 'next' on 2018-08-06 at cc8c305191)
 + pack-objects: document about thread synchronization

 Doc fix.

 Will merge to 'master'.


* sb/indent-heuristic-optim (2018-08-01) 1 commit
 - xdiff: reduce indent heuristic overhead

 "git diff --indent-heuristic" had a bad corner case performance.

 Will merge to 'next'.


* ab/fetch-nego (2018-08-01) 3 commits
 - fetch doc: cross-link two new negotiation options
 - negotiator: unknown fetch.negotiationAlgorithm should error out
 - Merge branch 'jt/fetch-nego-tip' into ab/fetch-nego

 Update to a few other topics.

 Will merge to 'next'.


* ab/fetch-tags-noclobber (2018-07-31) 10 commits
 - fetch: stop clobbering existing tags without --force
 - pull doc: fix a long-standing grammar error
 - fetch tests: add a test clobbering tag behavior
 - fetch tests: correct a comment "remove it" -> "remove them"
 - push doc: correct lies about how push refspecs work
 - push tests: assert re-pushing annotated tags
 - push tests: add more testing for forced tag pushing
 - push tests: fix logic error in "push" test assertion
 - push tests: remove redundant 'git push' invocation
 - fetch tests: change "Tag" test tag to "testTag"

 "git fetch" used to apply the same "fast-forward" rule and allow
 tags to move without "--force" option, which made little sense,
 which has been corrected.

 Expecting a reroll.
 cf. <xmqq4lgfcn5a.fsf@gitster-ct.c.googlers.com>
 cf. <xmqqzhy7b7v9.fsf@gitster-ct.c.googlers.com>


* bp/checkout-new-branch-optim (2018-07-31) 1 commit
 - checkout: optimize "git checkout -b <new_branch>"

 "git checkout -b newbranch [HEAD]" should not have to do as much as
 checking out a commit different from HEAD.  An attempt is made to
 optimize this special case.

 Waiting for review comments to be responded.
 cf. <CACsJy8DMEMsDnKZc65K-0EJcm2udXZ7OKY=xoFmX4COM0dSH=g@mail.gmail.com>


* es/mw-to-git-chain-fix (2018-07-31) 1 commit
  (merged to 'next' on 2018-08-06 at c10246e1c8)
 + mw-to-git/t9360: fix broken &&-chain

 Test fix.

 Will merge to 'master'.


* jk/merge-subtree-heuristics (2018-08-02) 1 commit
 - score_trees(): fix iteration over trees with missing entries

 The automatic tree-matching in "git merge -s subtree" was broken 5
 years ago and nobody has noticed since then, which is now fixed.

 Will merge to 'next'.


* jt/refspec-dwim-precedence-fix (2018-08-02) 1 commit
 - remote: make refspec follow the same disambiguation rule as local refs

 "git fetch $there refs/heads/s" ought to fetch the tip of the
 branch 's', but when "refs/heads/refs/heads/s", i.e. a branch whose
 name is "refs/heads/s" exists at the same time, fetched that one
 instead by mistake.  This has been corrected to honor the usual
 disambiguation rules for abbreviated refnames.

 Will merge to 'next'.


* nd/clone-case-smashing-warning (2018-07-31) 1 commit
 - clone: report duplicate entries on case-insensitive filesystems

 Running "git clone" against a project that contain two files with
 pathnames that differ only in cases on a case insensitive
 filesystem would result in one of the files lost because the
 underlying filesystem is incapable of holding both at the same
 time.  An attempt is made to detect such a case and warn.

 Discussion getting petered out.
 Doing this portably and extending it to UTF-8 normalization issue
 HFS+ has would be costly.

 cf. <20180728095659.GA21450@sigill.intra.peff.net>
 cf. <xmqq1sbh7phx.fsf@gitster-ct.c.googlers.com>


* nd/unpack-trees-with-cache-tree (2018-08-06) 4 commits
 - unpack-trees: cheaper index update when walking by cache-tree
 - unpack-trees: reduce malloc in cache-tree walk
 - unpack-trees: optimize walking same trees with cache-tree
 - unpack-trees: add performance tracing

 The unpack_trees() API used in checking out a branch and merging
 walks one or more trees along with the index.  When the cache-tree
 in the index tells us that we are walking a tree whose flattened
 contents is known (i.e. matches a span in the index), as linearly
 scanning a span in the index is much more efficient than having to
 open tree objects recursively and listing their entries, the walk
 can be optimized, which is done in this topic.


* rs/remote-mv-leakfix (2018-08-01) 1 commit
  (merged to 'next' on 2018-08-06 at 999fe6d3e5)
 + remote: clear string_list after use in mv()

 Leakfix.

 Will merge to 'master'.


* sb/config-write-fix (2018-08-01) 3 commits
 - git-config: document accidental multi-line setting in deprecated syntax
 - config: fix case sensitive subsection names on writing
 - t1300: document current behavior of setting options

 Recent update to "git config" broke updating variable in a
 subsection, which has been corrected.

 Expecting a reroll.
 cf. <CAGZ79kZ1R8sxmtfgPOQcpoWM7GWV1qiRaqMq_zhGyKBB3ARLjg@mail.gmail.com>


* sb/range-diff-colors (2018-08-01) 9 commits
 - fixup! t3206: add color test for range-diff --dual-color
 - diff.c: rewrite emit_line_0 more understandably
 - diff.c: compute reverse locally in emit_line_0
 - diff: use emit_line_0 once per line
 - diff.c: add set_sign to emit_line_0
 - diff.c: reorder arguments for emit_line_ws_markup
 - diff.c: simplify caller of emit_line_0
 - t3206: add color test for range-diff --dual-color
 - test_decode_color: understand FAINT and ITALIC
 (this branch uses js/range-diff; is tangled with es/format-patch-rangediff.)


* sg/t1404-update-ref-test-timeout (2018-08-01) 1 commit
 - t1404: increase core.packedRefsTimeout to avoid occasional test failure

 An attempt to unflake a test a bit.


* sg/travis-retrieve-trash-upon-failure (2018-08-01) 1 commit
  (merged to 'next' on 2018-08-06 at d67def2a92)
 + travis-ci: include the trash directories of failed tests in the trace log

 The Travis CI scripts were taught to ship back the test data from
 failed tests.

 Will merge to 'master'.


* jt/connectivity-check-after-unshallow (2018-08-01) 1 commit
  (merged to 'next' on 2018-08-06 at 1932418f46)
 + fetch-pack: unify ref in and out param

 "git fetch" sometimes failed to update the remote-tracking refs,
 which has been corrected.

 Will merge to 'master'.


* ab/sha1dc (2018-08-02) 1 commit
 - sha1dc: update from upstream

 AIX portability update for SHADC hash, imported from upstream.

 Will merge to 'next'.


* es/want-color-fd-defensive (2018-08-03) 1 commit
 - color: protect against out-of-bounds reads and writes

 Futureproofing a helper function that can easily misused.

 Will merge to 'next'.


* pw/rebase-i-author-script-fix (2018-08-02) 2 commits
 - sequencer: fix quoting in write_author_script
 - sequencer: handle errors in read_author_ident()
 (this branch uses es/rebase-i-author-script-fix.)

 Recent "git rebase -i" update started to write bogusly formatted
 author-script, with a matching broken reading code.  These are
 being fixed.

 Undecided.
 Is it the list consensus to favor this "with extra code, read the
 script written by bad writer" approach?


* rs/parse-opt-lithelp (2018-08-03) 7 commits
 - parse-options: automatically infer PARSE_OPT_LITERAL_ARGHELP
 - shortlog: correct option help for -w
 - send-pack: specify --force-with-lease argument help explicitly
 - pack-objects: specify --index-version argument help explicitly
 - difftool: remove angular brackets from argument help
 - add, update-index: fix --chmod argument help
 - push: use PARSE_OPT_LITERAL_ARGHELP instead of unbalanced brackets

 The parse-options machinery learned to refrain from enclosing
 placeholder string inside a "<bra" and "ket>" pair automatically
 without PARSE_OPT_LITERAL_ARGHELP.  Existing help text for option
 arguments that are not formatted correctly have been identified and
 fixed.

 Will merge to 'next'.


* es/diff-color-moved-fix (2018-07-25) 1 commit
  (merged to 'next' on 2018-08-02 at 233bccfbfb)
 + diff: --color-moved: rename "dimmed_zebra" to "dimmed-zebra"

 One of the "diff --color-moved" mode "dimmed_zebra" that was named
 in an unusual way has been deprecated and replaced by
 "dimmed-zebra".

 Will merge to 'master'.


* pw/add-p-select (2018-07-26) 4 commits
 - add -p: optimize line selection for short hunks
 - add -p: allow line selection to be inverted
 - add -p: select modified lines correctly
 - add -p: select individual hunk lines

 "git add -p" interactive interface learned to let users choose
 individual added/removed lines to be used in the operation, instead
 of accepting or rejecting a whole hunk.

 Will hold.
 cf. <d622a95b-7302-43d4-4ec9-b2cf3388c653@talktalk.net>
 I found the feature to be hard to explain, and may result in more
 end-user complaints, but let's see.


* mk/http-backend-content-length (2018-07-30) 4 commits
 - t5562: avoid non-portable "export FOO=bar" construct
 - http-backend: respect CONTENT_LENGTH for receive-pack
 - http-backend: respect CONTENT_LENGTH as specified by rfc3875
 - http-backend: cleanup writing to child process

 The http-backend (used for smart-http transport) used to slurp the
 whole input until EOF, without paying attention to CONTENT_LENGTH
 that is supplied in the environment and instead expecting the Web
 server to close the input stream.  This has been fixed.

 Will merge to 'next'.


* ds/commit-graph-with-grafts (2018-07-19) 8 commits
  (merged to 'next' on 2018-08-02 at 0ee624e329)
 + commit-graph: close_commit_graph before shallow walk
 + commit-graph: not compatible with uninitialized repo
 + commit-graph: not compatible with grafts
 + commit-graph: not compatible with replace objects
 + test-repository: properly init repo
 + commit-graph: update design document
 + refs.c: upgrade for_each_replace_ref to be a each_repo_ref_fn callback
 + refs.c: migrate internal ref iteration to pass thru repository argument

 The recently introduced commit-graph auxiliary data is incompatible
 with mechanisms such as replace & grafts that "breaks" immutable
 nature of the object reference relationship.  Disable optimizations
 based on its use (and updating existing commit-graph) when these
 incompatible features are in use in the repository.

 Will cook in 'next'.


* jk/core-use-replace-refs (2018-07-18) 3 commits
  (merged to 'next' on 2018-08-02 at 90fb6b1056)
 + add core.usereplacerefs config option
 + check_replace_refs: rename to read_replace_refs
 + check_replace_refs: fix outdated comment

 A new configuration variable core.usereplacerefs has been added,
 primarily to help server installations that want to ignore the
 replace mechanism altogether.

 Will merge to 'master'.


* nd/i18n (2018-07-23) 23 commits
  (merged to 'next' on 2018-08-02 at 904a22a5d1)
 + transport-helper.c: mark more strings for translation
 + transport.c: mark more strings for translation
 + sha1-file.c: mark more strings for translation
 + sequencer.c: mark more strings for translation
 + replace-object.c: mark more strings for translation
 + refspec.c: mark more strings for translation
 + refs.c: mark more strings for translation
 + pkt-line.c: mark more strings for translation
 + object.c: mark more strings for translation
 + exec-cmd.c: mark more strings for translation
 + environment.c: mark more strings for translation
 + dir.c: mark more strings for translation
 + convert.c: mark more strings for translation
 + connect.c: mark more strings for translation
 + config.c: mark more strings for translation
 + commit-graph.c: mark more strings for translation
 + builtin/replace.c: mark more strings for translation
 + builtin/pack-objects.c: mark more strings for translation
 + builtin/grep.c: mark strings for translation
 + builtin/config.c: mark more strings for translation
 + archive-zip.c: mark more strings for translation
 + archive-tar.c: mark more strings for translation
 + Update messages in preparation for i18n

 Many more strings are prepared for l10n.

 Will merge to 'master'.


* sb/histogram-less-memory (2018-07-23) 4 commits
  (merged to 'next' on 2018-08-02 at cfb02aa3b5)
 + xdiff/histogram: remove tail recursion
 + xdiff/xhistogram: move index allocation into find_lcs
 + xdiff/xhistogram: factor out memory cleanup into free_index()
 + xdiff/xhistogram: pass arguments directly to fall_back_to_classic_diff

 "git diff --histogram" had a bad memory usage pattern, which has
 been rearranged to reduce the peak usage.

 Will merge to 'master'.


* bb/make-developer-pedantic (2018-07-25) 1 commit
  (merged to 'next' on 2018-08-02 at c738a84b7e)
 + Makefile: add a DEVOPTS flag to get pedantic compilation

 "make DEVELOPER=1 DEVOPTS=pedantic" allows developers to compile
 with -pedantic option, which may catch more problematic program
 constructs and potential bugs.

 Will merge to 'master'.


* bw/clone-ref-prefixes (2018-07-20) 1 commit
  (merged to 'next' on 2018-08-02 at c8ad140ab0)
 + clone: send ref-prefixes when using protocol v2

 The wire-protocol v2 relies on the client to send "ref prefixes" to
 limit the bandwidth spent on the initial ref advertisement.  "git
 clone" when learned to speak v2 forgot to do so, which has been
 corrected.

 Will merge to 'master'.


* bw/fetch-pack-i18n (2018-07-23) 1 commit
  (merged to 'next' on 2018-08-02 at df72001755)
 + fetch-pack: mark die strings for translation

 i18n updates.

 Will merge to 'master'.


* bw/protocol-v2 (2018-07-24) 1 commit
  (merged to 'next' on 2018-08-02 at f4076b3e94)
 + pack-protocol: mention and point to docs for protocol v2

 Doc update.

 Will merge to 'master'.


* ds/reachable (2018-07-20) 18 commits
 - commit-reach: use can_all_from_reach
 - commit-reach: make can_all_from_reach... linear
 - commit-reach: replace ref_newer logic
 - test-reach: test commit_contains
 - test-reach: test can_all_from_reach_with_flags
 - test-reach: test reduce_heads
 - test-reach: test get_merge_bases_many
 - test-reach: test is_descendant_of
 - test-reach: test in_merge_bases
 - test-reach: create new test tool for ref_newer
 - commit-reach: move can_all_from_reach_with_flags
 - upload-pack: generalize commit date cutoff
 - upload-pack: refactor ok_to_give_up()
 - upload-pack: make reachable() more generic
 - commit-reach: move commit_contains from ref-filter
 - commit-reach: move ref_newer from remote.c
 - commit.h: remove method declarations
 - commit-reach: move walk methods from commit.c

 The code for computing history reachability has been shuffled,
 obtained a bunch of new tests to cover them, and then being
 improved.

 Will merge to and cook in 'next'.


* en/merge-recursive-skip-fix (2018-07-27) 2 commits
  (merged to 'next' on 2018-08-06 at 9ab321a15c)
 + merge-recursive: preserve skip_worktree bit when necessary
 + t3507: add a testcase showing failure with sparse checkout

 When the sparse checkout feature is in use, "git cherry-pick" and
 other mergy operations lost the skip_worktree bit when a path that
 is excluded from checkout requires content level merge, which is
 resolved as the same as the HEAD version, without materializing the
 merge result in the working tree, which made the path appear as
 deleted.  This has been corrected by preserving the skip_worktree
 bit (and not materializing the file in the working tree).

 Will merge to 'master'.


* es/format-patch-interdiff (2018-07-23) 6 commits
 - format-patch: allow --interdiff to apply to a lone-patch
 - log-tree: show_log: make commentary block delimiting reusable
 - interdiff: teach show_interdiff() to indent interdiff
 - format-patch: teach --interdiff to respect -v/--reroll-count
 - format-patch: add --interdiff option to embed diff in cover letter
 - format-patch: allow additional generated content in make_cover_letter()
 (this branch is used by es/format-patch-rangediff.)

 "git format-patch" learned a new "--interdiff" option to explain
 the difference between this version and the previous atttempt in
 the cover letter (or after the tree-dashes as a comment).

 Stuck in review?
 cf. <CAPig+cSuYUYSPTuKx08wcmQM-G12_-W2T4BS07fA=6grM1b8Gw@mail.gmail.com>


* es/format-patch-rangediff (2018-07-30) 10 commits
 - format-patch: allow --range-diff to apply to a lone-patch
 - format-patch: add --creation-factor tweak for --range-diff
 - format-patch: teach --range-diff to respect -v/--reroll-count
 - format-patch: extend --range-diff to accept revision range
 - format-patch: add --range-diff option to embed diff in cover letter
 - range-diff: relieve callers of low-level configuration burden
 - range-diff: publish default creation factor
 - range-diff: respect diff_option.file rather than assuming 'stdout'
 - Merge branch 'es/format-patch-interdiff' into es/format-patch-rangediff
 - Merge branch 'js/range-diff' into es/format-patch-rangediff
 (this branch uses es/format-patch-interdiff and js/range-diff; is tangled with sb/range-diff-colors.)

 "git format-patch" learned a new "--range-diff" option to explain
 the difference between this version and the previous atttempt in
 the cover letter (or after the tree-dashes as a comment).

 Need to wait for the prereq topics to solidify a bit more.


* jk/banned-function (2018-07-26) 5 commits
  (merged to 'next' on 2018-08-06 at 3dcd1999df)
 + banned.h: mark strncpy() as banned
 + banned.h: mark sprintf() as banned
 + banned.h: mark strcat() as banned
 + automatically ban strcpy()
 + Merge branch 'sb/blame-color' into jk/banned-function

 It is too easy to misuse system API functions such as strcat();
 these selected functions are now forbidden in this codebase and
 will cause a compilation failure.

 Will merge to 'master'.


* jk/size-t (2018-07-24) 6 commits
  (merged to 'next' on 2018-08-02 at 6f861e05f0)
 + strbuf_humanise: use unsigned variables
 + pass st.st_size as hint for strbuf_readlink()
 + strbuf_readlink: use ssize_t
 + strbuf: use size_t for length in intermediate variables
 + reencode_string: use size_t for string lengths
 + reencode_string: use st_add/st_mult helpers

 Code clean-up to use size_t/ssize_t when they are the right type.

 Will merge to 'master'.


* js/t7406-recursive-submodule-update-order-fix (2018-07-23) 1 commit
  (merged to 'next' on 2018-08-02 at 217ea36a37)
 + t7406: avoid failures solely due to timing issues

 Test fix.

 Will merge to 'master'.


* js/vscode (2018-07-30) 9 commits
  (merged to 'next' on 2018-08-06 at 5c578b63a8)
 + vscode: let cSpell work on commit messages, too
 + vscode: add a dictionary for cSpell
 + vscode: use 8-space tabs, no trailing ws, etc for Git's source code
 + vscode: wrap commit messages at column 72 by default
 + vscode: only overwrite C/C++ settings
 + mingw: define WIN32 explicitly
 + cache.h: extract enum declaration from inside a struct declaration
 + vscode: hard-code a couple defines
 + contrib: add a script to initialize VS Code configuration

 Add a script (in contrib/) to help users of VSCode work better with
 our codebase.

 Will merge to 'master'.


* jt/tag-following-with-proto-v2-fix (2018-07-24) 2 commits
  (merged to 'next' on 2018-08-02 at d9eabdea95)
 + fetch: send "refs/tags/" prefix upon CLI refspecs
 + t5702: test fetch with multiple refspecs at a time

 The wire-protocol v2 relies on the client to send "ref prefixes" to
 limit the bandwidth spent on the initial ref advertisement.  "git
 fetch $remote branch:branch" that asks tags that point into the
 history leading to the "branch" automatically followed sent to
 narrow prefix and broke the tag following, which has been fixed.

 Will merge to 'master'.


* nd/pack-deltify-regression-fix (2018-07-23) 1 commit
  (merged to 'next' on 2018-08-02 at f3b2bf0fef)
 + pack-objects: fix performance issues on packing large deltas

 In a recent update in 2.18 era, "git pack-objects" started
 producing a larger than necessary packfiles by missing
 opportunities to use large deltas.

 Will cook in 'next'.


* sb/trailers-docfix (2018-07-20) 1 commit
  (merged to 'next' on 2018-08-02 at ba348fafcd)
 + Documentation/git-interpret-trailers: explain possible values

 Doc update.

 Will merge to 'master'.


* sg/coccicheck-updates (2018-07-23) 5 commits
  (merged to 'next' on 2018-08-02 at b5548ff3a9)
 + coccinelle: extract dedicated make target to clean Coccinelle's results
 + coccinelle: put sane filenames into output patches
 + coccinelle: exclude sha1dc source files from static analysis
 + coccinelle: use $(addsuffix) in 'coccicheck' make target
 + coccinelle: mark the 'coccicheck' make target as .PHONY

 Update the way we use Coccinelle to find out-of-style code that
 need to be modernised.

 Will merge to 'master'.


* sg/fast-import-dump-refs-on-checkpoint-fix (2018-07-20) 1 commit
  (merged to 'next' on 2018-08-02 at f5c05b5a2c)
 + t9300: wait for background fast-import process to die after killing it

 Test update.

 Will merge to 'master'.


* sg/travis-cocci-diagnose-failure (2018-07-23) 2 commits
  (merged to 'next' on 2018-08-02 at 54808a8778)
 + travis-ci: fail if Coccinelle static analysis found something to transform
 + travis-ci: run Coccinelle static analysis with two parallel jobs

 Update the way we run static analysis tool at TravisCI to make it
 easier to use its findings.

 Will merge to 'master'.


* ab/newhash-is-sha256 (2018-08-06) 2 commits
 - doc hash-function-transition: pick SHA-256 as NewHash
 - doc hash-function-transition: note the lack of a changelog

 Documentation update.

 Will Merge to 'next'.


* bb/redecl-enum-fix (2018-07-26) 1 commit
  (merged to 'next' on 2018-08-06 at 828dc4b156)
 + packfile: ensure that enum object_type is defined

 Compilation fix.

 Will merge to 'master'.


* jh/structured-logging (2018-07-25) 25 commits
 - structured-logging: add config data facility
 - structured-logging: t0420 tests for interacitve child_summary
 - structured-logging: t0420 tests for child process detail events
 - structured-logging: add child process classification
 - structured-logging: add detail-events for child processes
 - structured-logging: add structured logging to remote-curl
 - structured-logging: t0420 tests for aux-data
 - structured-logging: add aux-data for size of sparse-checkout file
 - structured-logging: add aux-data for index size
 - structured-logging: add aux-data facility
 - structured-logging: t0420 tests for timers
 - structured-logging: add timer around preload_index
 - structured-logging: add timer around wt-status functions
 - structured-logging: add timer around do_write_index
 - structured-logging: add timer around do_read_index
 - structured-logging: add timer facility
 - structured-logging: add detail-event for lazy_init_name_hash
 - structured-logging: add detail-event facility
 - structured-logging: t0420 basic tests
 - structured-logging: set sub_command field for checkout command
 - structured-logging: set sub_command field for branch command
 - structured-logging: add session-id to log events
 - structured-logging: add structured logging framework
 - structured-logging: add STRUCTURED_LOGGING=1 to Makefile
 - structured-logging: design document
 (this branch uses jh/json-writer.)

 Will merge to 'next'.


* en/abort-df-conflict-fixes (2018-07-31) 2 commits
 - read-cache: fix directory/file conflict handling in read_index_unmerged()
 - t1015: demonstrate directory/file conflict recovery failures

 "git merge --abort" etc. did not clean things up properly when
 there were conflicted entries in certain order that are involved
 in D/F conflicts.  This has been corrected.

 Will merge to 'next'.


* hs/gpgsm (2018-07-20) 7 commits
  (merged to 'next' on 2018-08-02 at db28bffe4f)
 + gpg-interface t: extend the existing GPG tests with GPGSM
 + gpg-interface: introduce new signature format "x509" using gpgsm
 + gpg-interface: introduce new config to select per gpg format program
 + gpg-interface: do not hardcode the key string len anymore
 + gpg-interface: introduce an abstraction for multiple gpg formats
 + t/t7510: check the validation of the new config gpg.format
 + gpg-interface: add new config to select how to sign a commit

 Teach "git tag -s" etc. a few configuration varaibles (gpg.format
 that can be set to "openpgp" or "x509", and gpg.<format>.program
 that is used to specify what program to use to deal with the format)
 to allow x.509 certs with CMS via "gpgsm" to be used instead of
 openpgp via "gnupg".

 Will merge to 'master'.


* jn/gc-auto (2018-07-17) 3 commits
 - gc: do not return error for prior errors in daemonized mode
 - gc: exit with status 128 on failure
 - gc: improve handling of errors reading gc.log

 "gc --auto" ended up calling exit(-1) upon error, which has been
 corrected to use exit(1).  Also the error reporting behaviour when
 daemonized has been updated to exit with zero status when stopping
 due to a previously discovered error (which implies there is no
 point running gc to improve the situation); we used to exit with
 failure in such a case.

 Stuck in review?
 cf. <20180717201348.GD26218@sigill.intra.peff.net>


* sb/submodule-update-in-c (2018-08-03) 7 commits
 - submodule--helper: introduce new update-module-mode helper
 - submodule--helper: replace connect-gitdir-workingtree by ensure-core-worktree
 - builtin/submodule--helper: factor out method to update a single submodule
 - builtin/submodule--helper: store update_clone information in a struct
 - builtin/submodule--helper: factor out submodule updating
 - git-submodule.sh: rename unused variables
 - git-submodule.sh: align error reporting for update mode to use path

 "git submodule update" is getting rewritten piece-by-piece into C.

 Will merge to and cook in 'next'.


* sl/commit-dry-run-with-short-output-fix (2018-07-30) 4 commits
 . commit: fix exit code when doing a dry run
 . wt-status: teach wt_status_collect about merges in progress
 . wt-status: rename commitable to committable
 . t7501: add coverage for flags which imply dry runs

 "git commit --dry-run" gave a correct exit status even during a
 conflict resolution toward a merge, but it did not with the
 "--short" option, which has been corrected.

 Seems to break 7512, 3404 and 7060 in 'pu'.


* tg/rerere (2018-08-06) 11 commits
 - rerere: recalculate conflict ID when unresolved conflict is committed
 - rerere: teach rerere to handle nested conflicts
 - rerere: return strbuf from handle path
 - rerere: factor out handle_conflict function
 - rerere: only return whether a path has conflicts or not
 - rerere: fix crash with files rerere can't handle
 - rerere: add documentation for conflict normalization
 - rerere: mark strings for translation
 - rerere: wrap paths in output in sq
 - rerere: lowercase error messages
 - rerere: unify error messages when read_cache fails

 Fixes to "git rerere" corner cases, especially when conflict
 markers cannot be parsed in the file.

 Will merge to and cook in 'next'.


* jk/ui-color-always-to-auto (2018-07-18) 1 commit
  (merged to 'next' on 2018-08-02 at 1a054baf0e)
 + Documentation: fix --color option formatting

 Doc formatting fix.

 Will merge to 'master'.


* jh/json-writer (2018-07-16) 1 commit
  (merged to 'next' on 2018-08-02 at d841450c7d)
 + json_writer: new routines to create JSON data
 (this branch is used by jh/structured-logging.)

 Preparatory code to later add json output for telemetry data.

 Will merge to 'master'.


* ag/rebase-i-in-c (2018-07-31) 20 commits
 - rebase -i: move rebase--helper modes to rebase--interactive
 - rebase -i: remove git-rebase--interactive.sh
 - rebase--interactive2: rewrite the submodes of interactive rebase in C
 - rebase -i: implement the main part of interactive rebase as a builtin
 - rebase -i: rewrite init_basic_state() in C
 - rebase -i: rewrite write_basic_state() in C
 - rebase -i: rewrite the rest of init_revisions_and_shortrevisions() in C
 - rebase -i: implement the logic to initialize $revisions in C
 - rebase -i: remove unused modes and functions
 - rebase -i: rewrite complete_action() in C
 - t3404: todo list with commented-out commands only aborts
 - sequencer: change the way skip_unnecessary_picks() returns its result
 - sequencer: refactor append_todo_help() to write its message to a buffer
 - rebase -i: rewrite checkout_onto() in C
 - rebase -i: rewrite setup_reflog_action() in C
 - sequencer: add a new function to silence a command, except if it fails
 - rebase -i: rewrite the edit-todo functionality in C
 - editor: add a function to launch the sequence editor
 - rebase -i: rewrite append_todo_help() in C
 - sequencer: make two functions and an enum from sequencer.c public

 Rewrite of the remaining "rebase -i" machinery in C.

 Will merge to 'next'.


* js/range-diff (2018-07-30) 21 commits
 - range-diff: use dim/bold cues to improve dual color mode
 - range-diff: make --dual-color the default mode
 - range-diff: left-pad patch numbers
 - completion: support `git range-diff`
 - range-diff: populate the man page
 - range-diff --dual-color: fix bogus white-space warning
 - range-diff: offer to dual-color the diffs
 - diff: add an internal option to dual-color diffs of diffs
 - color: add the meta color GIT_COLOR_REVERSE
 - range-diff: use color for the commit pairs
 - range-diff: add tests
 - range-diff: do not show "function names" in hunk headers
 - range-diff: adjust the output of the commit pairs
 - range-diff: suppress the diff headers
 - range-diff: indent the diffs just like tbdiff
 - range-diff: right-trim commit messages
 - range-diff: also show the diff between patches
 - range-diff: improve the order of the shown commits
 - range-diff: first rudimentary implementation
 - Introduce `range-diff` to compare iterations of a topic branch
 - linear-assignment: a function to solve least-cost assignment problems
 (this branch is used by es/format-patch-rangediff and sb/range-diff-colors.)

 "git tbdiff" that lets us compare individual patches in two
 iterations of a topic has been rewritten and made into a built-in
 command.

 It seems there will another hopefully the final reroll coming.
 cf. <nycvar.QRO.7.76.6.1808011800570.71@tvgsbejvaqbjf.bet>


* lt/date-human (2018-07-09) 1 commit
 - Add 'human' date format

 A new date format "--date=human" that morphs its output depending
 on how far the time is from the current time has been introduced.
 "--date=auto" can be used to use this new format when the output is
 goint to the pager or to the terminal and otherwise the default
 format.


* ot/ref-filter-object-info (2018-07-17) 5 commits
 - ref-filter: use oid_object_info() to get object
 - ref-filter: merge get_obj and get_object
 - ref-filter: initialize eaten variable
 - ref-filter: fill empty fields with empty values
 - ref-filter: add info_source to valid_atom

 A few atoms like %(objecttype) and %(objectsize) in the format
 specifier of "for-each-ref --format=<format>" can be filled without
 getting the full contents of the object, but just with the object
 header.  These cases have been optimzied by calling
 oid_object_info() API.

 Will merge to 'next'.


* pk/rebase-in-c (2018-08-06) 3 commits
 - builtin/rebase: support running "git rebase <upstream>"
 - rebase: refactor common shell functions into their own file
 - rebase: start implementing it as a builtin

 Rewrite of the "rebase" machinery in C.


* jk/branch-l-1-repurpose (2018-06-22) 1 commit
 - branch: make "-l" a synonym for "--list"

 Updated plan to repurpose the "-l" option to "git branch".

 Will merge to and cook in 'next'.


* cc/remote-odb (2018-08-02) 9 commits
 - Documentation/config: add odb.<name>.promisorRemote
 - t0410: test fetching from many promisor remotes
 - Use odb.origin.partialclonefilter instead of core.partialclonefilter
 - Use remote_odb_get_direct() and has_remote_odb()
 - remote-odb: add remote_odb_reinit()
 - remote-odb: implement remote_odb_get_many_direct()
 - remote-odb: implement remote_odb_get_direct()
 - Add initial remote odb support
 - fetch-object: make functions return an error code

 Implement lazy fetches of missing objects to complement the
 experimental partial clone feature.

 I haven't seen much interest in this topic on list.  What's the
 doneness of this thing?

 I do not particularly mind adding code to support a niche feature
 as long as it is cleanly made and it is clear that the feature
 won't negatively affect those who do not use it, so a review from
 that point of view may also be appropriate.


* ds/multi-pack-index (2018-07-20) 23 commits
 - midx: clear midx on repack
 - packfile: skip loading index if in multi-pack-index
 - midx: prevent duplicate packfile loads
 - midx: use midx in approximate_object_count
 - midx: use existing midx when writing new one
 - midx: use midx in abbreviation calculations
 - midx: read objects from multi-pack-index
 - config: create core.multiPackIndex setting
 - midx: write object offsets
 - midx: write object id fanout chunk
 - midx: write object ids in a chunk
 - midx: sort and deduplicate objects from packfiles
 - midx: read pack names into array
 - multi-pack-index: write pack names in chunk
 - multi-pack-index: read packfile list
 - packfile: generalize pack directory list
 - t5319: expand test data
 - multi-pack-index: load into memory
 - midx: write header information to lockfile
 - multi-pack-index: add 'write' verb
 - multi-pack-index: add builtin
 - multi-pack-index: add format details
 - multi-pack-index: add design document

 When there are too many packfiles in a repository (which is not
 recommended), looking up an object in these would require
 consulting many pack .idx files; a new mechanism to have a single
 file that consolidates all of these .idx files is introduced.

 Will merge to and cook in 'next'.

--------------------------------------------------
[Discarded]

* am/sequencer-author-script-fix (2018-07-18) 1 commit
 . sequencer.c: terminate the last line of author-script properly

 The author-script that records the author information created by
 the sequencer machinery lacked the closing single quote on the last
 entry.

 Superseded by another topic.


* jc/push-cas-opt-comment (2018-08-01) 1 commit
 . push: comment on a funny unbalanced option help

 Code clarification.

 Superseded by another topic.

^ permalink raw reply	[relevance 1%]

* Re: [PATCH v5 2/2] sideband: highlight keywords in remote sideband output
  2018-08-06 17:21  4%   ` Junio C Hamano
@ 2018-08-06 17:42  0%     ` Han-Wen Nienhuys
  0 siblings, 0 replies; 200+ results
From: Han-Wen Nienhuys @ 2018-08-06 17:42 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Eric Sunshine, Jonathan Nieder, git

On Mon, Aug 6, 2018 at 7:21 PM Junio C Hamano <gitster@pobox.com> wrote:
>
> Han-Wen Nienhuys <hanwen@google.com> writes:
>
> > The Git push process itself prints lots of non-actionable messages
> > (eg. bandwidth statistics, object counters for different phases of the
> > process), which obscures actionable error messages that servers may
>
> s/which obscures/which obscure/, as I think that "which" refers to
> "messages" above.
>
Done.

> > The highlighting is done on the client rather than server side, so
> > servers don't have to grow capabilities to understand terminal escape
> > codes and terminal state. It also consistent with the current state
> > where Git is control of the local display (eg. prefixing messages with
> > "remote: ").
>
> Yup.
>
> When we introduce "the receiving end asks messages to be sent with
> such and such decoration" protocol support, we would want a lot more
> than just painting messages in color, e.g. i18n, verbosity, and even
> "Hey, I am a script, send them in json".
>
> Until that happens, let's keep things simpler.  No i18n messages and
> no colored output over the wire.

Ack.

>
> > +color.remote::
> > +     If set, keywords at the start of the line are highlighted. The
> > +     keywords are "error", "warning", "hint" and "success", and are
> > +     matched case-insensitively. Maybe set to `always`, `false` (or
> > +     `never`) or `auto` (or `true`). If unset, then the value of
> > +     `color.ui` is used (`auto` by default).
>
> Reads much better.
>
> I am still trying to find a concise way to help readers who saw a
> line that begins with "Warnings: foo bar bla" and accept that it is
> OK the early 7 chars are not painted.  "... case-insensitively and
> honoring word boundary" is the best I came up so far, but  I am
> afraid that is adding more words without hinting what I want to convey
> strongly enough, so I am not going to suggest that (at least not yet).

I would suggest that the phrase "keyword" implies a tokenization, so
I'd leave as is.

> > +     for (i = 0; i < ARRAY_SIZE(keywords); i++) {
> > +             strbuf_reset(&sb);
> > +             strbuf_addf(&sb, "%s.%s", key, keywords[i].keyword);
>
> This design means future enhancement to allow more keywords will
> have to be done in the form of adding more "color.remote.<key>",
> which means a few restrictions on them are cast in stone at the
> end-user facing design level, which we need to be careful about.
>
>         Side note. I do not worry about the implementation level
>         limitation at all.  For example, the current code will not
>         allow end-users and projects to add new keywords to be
>         painted, as it relies on the keywords[] static array we can
>         see above.  But that implementation detail does not prevent
>         us from improving it later to support more code in this
>         codepath that notices "color.remote.failure" in config file
>         and paint a line that begins with "failure:".
>
> Because the third-level "variable" name is case insensive, matching
> of any future keyword MUST be also done case insensitively.
>
> Also, as you mentioned elsewhere in this patch, the string that can
> be in the keyword MUST begin with an alphabetic and consists only of
> alphanumeric or dash.
>
> I do not think these limitations imposed by the design decision this
> patch is making are particularly bad ones---we just need to be aware
> of and firm about them.  When somebody else comes in the future and
> wants to recognize "F A I L" as a custom keyword case sensitively,
> we must be able to comfortably say "no" to it.
>
>         Side note. We _could_ instead use "remotemsg.<key>.color"
>         namespace, as the subsection names at the second level is a
>         lot looser, but I do not think it is a good idea to use in
>         this application, as they are case sensitive.
>
> The above discussion may deserve to be in the log message as a
> record to tell future ourselves why we decided to use
> color.remote.<key>.

I added a note about case insensitivity.

> > +             if (git_config_get_string(sb.buf, &value))
> > +                     continue;
> > +             if (color_parse(value, keywords[i].color))
> > +                     die(_("config value %s is not a color: %s"), sb.buf, value);
>
> That's a bit inconsistent, isn't it?  If the configuration is not
> even a string, we ignore it and continue, but if it is a string, we
> insist that it names a color and otherwise die?

Done; added test.

> > + * Optionally highlight one keyword in remote output if it appears at the start
> > + * of the line. This should be called for a single line only, which must be
> > + * passed as the first N characters of the SRC array.
> > + */
>
> Saying "MUST be" is cheap, but do we have anybody who polices that
> requirement?

rephrased.

> I think the code is OK without any assert() or BUG(), and that is
> because the design is "we just paint the keyword at the beginning of
> what the other side of the sideband wants us to show as a single
> unit".  If the other side sends a payload with an embedded LF in a
> single packet, that's their choice and we are free not to paint the
> beginning of the second line after that LF.  So from that point of
> view, perhaps we shouldn't even talk about "a single line only".

I don't understand this remark. Isn't the call to strpbrk() meant to
split the input on line endings?

> >  #define ANSI_SUFFIX "\033[K"
> > -#define DUMB_SUFFIX "        "
> > +#define DUMB_SUFFIX "             "
> >
>
> Was this change intended and if so for what purpose?  I can drop
> this hunk if this is a mere finger-slip without proofreading, but I
> do not want to do so without making sure I am not missing anything
> and not discarding a meaningful change.

This was my poor use of the tabify function. It would be nice if this
were more explicit though, maybe using a format string of

  sprintf(.. , "%*s", 8, "");

but we can leave that for another time.

> Noticing the dash in "<<-", I would have expected all of the above
> lines to be indented with a tab to align with 'w' in 'write_script'.

Done.

> > +     chmod +x .git/hooks/update &&
>
> No need for this "chmod +x"; that's one of the points in using
> write_script.

Done.

--

Google Germany GmbH, Erika-Mann-Strasse 33, 80636 Munich

Registergericht und -nummer: Hamburg, HRB 86891

Sitz der Gesellschaft: Hamburg

Geschäftsführer: Paul Manicle, Halimah DeLaine Prado

^ permalink raw reply	[relevance 0%]

* Re: [PATCH v5 2/2] sideband: highlight keywords in remote sideband output
  @ 2018-08-06 17:21  4%   ` Junio C Hamano
  2018-08-06 17:42  0%     ` Han-Wen Nienhuys
  0 siblings, 1 reply; 200+ results
From: Junio C Hamano @ 2018-08-06 17:21 UTC (permalink / raw)
  To: Han-Wen Nienhuys; +Cc: sunshine, jrn, git

Han-Wen Nienhuys <hanwen@google.com> writes:

> The Git push process itself prints lots of non-actionable messages
> (eg. bandwidth statistics, object counters for different phases of the
> process), which obscures actionable error messages that servers may

s/which obscures/which obscure/, as I think that "which" refers to
"messages" above.

> The highlighting is done on the client rather than server side, so
> servers don't have to grow capabilities to understand terminal escape
> codes and terminal state. It also consistent with the current state
> where Git is control of the local display (eg. prefixing messages with
> "remote: ").

Yup.

When we introduce "the receiving end asks messages to be sent with
such and such decoration" protocol support, we would want a lot more
than just painting messages in color, e.g. i18n, verbosity, and even
"Hey, I am a script, send them in json".

Until that happens, let's keep things simpler.  No i18n messages and
no colored output over the wire.

> +color.remote::
> +	If set, keywords at the start of the line are highlighted. The
> +	keywords are "error", "warning", "hint" and "success", and are
> +	matched case-insensitively. Maybe set to `always`, `false` (or
> +	`never`) or `auto` (or `true`). If unset, then the value of
> +	`color.ui` is used (`auto` by default).

Reads much better.

I am still trying to find a concise way to help readers who saw a
line that begins with "Warnings: foo bar bla" and accept that it is
OK the early 7 chars are not painted.  "... case-insensitively and
honoring word boundary" is the best I came up so far, but  I am
afraid that is adding more words without hinting what I want to convey
strongly enough, so I am not going to suggest that (at least not yet).

> diff --git a/help.h b/help.h
> index f8b15323a6..9eab6a3f89 100644
> --- a/help.h
> +++ b/help.h
> @@ -83,6 +83,7 @@ void list_config_color_diff_slots(struct string_list *list, const char *prefix);
>  void list_config_color_grep_slots(struct string_list *list, const char *prefix);
>  void list_config_color_interactive_slots(struct string_list *list, const char *prefix);
>  void list_config_color_status_slots(struct string_list *list, const char *prefix);
> +void list_config_color_sideband_slots(struct string_list *list, const char *prefix);
>  void list_config_fsck_msg_ids(struct string_list *list, const char *prefix);
>  
>  #endif /* HELP_H */
> diff --git a/sideband.c b/sideband.c
> index 325bf0e974..239be2ec85 100644
> --- a/sideband.c
> +++ b/sideband.c
> @@ -1,6 +1,108 @@
>  #include "cache.h"
> +#include "color.h"
> +#include "config.h"
>  #include "pkt-line.h"
>  #include "sideband.h"
> +#include "help.h"
> +
> +struct keyword_entry {
> +	/*
> +	 * We use keyword as config key so it should be a single alphanumeric word.
> +	 */
> +	const char *keyword;
> +	char color[COLOR_MAXLEN];
> +};
> +
> +static struct keyword_entry keywords[] = {
> +	{ "hint",	GIT_COLOR_YELLOW },
> +	{ "warning",	GIT_COLOR_BOLD_YELLOW },
> +	{ "success",	GIT_COLOR_BOLD_GREEN },
> +	{ "error",	GIT_COLOR_BOLD_RED },
> +};
> +/* Returns a color setting (GIT_COLOR_NEVER, etc). */
> +static int use_sideband_colors(void)
> +{
> +	static int use_sideband_colors_cached = -1;
> +
> +	const char *key = "color.remote";
> +	struct strbuf sb = STRBUF_INIT;
> +	char *value;
> +	int i;
> +
> +	if (use_sideband_colors_cached >= 0)
> +		return use_sideband_colors_cached;
> +
> +	if (!git_config_get_string(key, &value)) {
> +		use_sideband_colors_cached = git_config_colorbool(key, value);
> +	} else if (!git_config_get_string("color.ui", &value)) {
> +		use_sideband_colors_cached = git_config_colorbool("color.ui", value);
> +	} else {
> +		use_sideband_colors_cached = GIT_COLOR_AUTO;
> +	}
> +
> +	for (i = 0; i < ARRAY_SIZE(keywords); i++) {
> +		strbuf_reset(&sb);
> +		strbuf_addf(&sb, "%s.%s", key, keywords[i].keyword);

This design means future enhancement to allow more keywords will
have to be done in the form of adding more "color.remote.<key>",
which means a few restrictions on them are cast in stone at the
end-user facing design level, which we need to be careful about.

	Side note. I do not worry about the implementation level
	limitation at all.  For example, the current code will not
	allow end-users and projects to add new keywords to be
	painted, as it relies on the keywords[] static array we can
	see above.  But that implementation detail does not prevent
	us from improving it later to support more code in this
	codepath that notices "color.remote.failure" in config file
	and paint a line that begins with "failure:".

Because the third-level "variable" name is case insensive, matching
of any future keyword MUST be also done case insensitively.

Also, as you mentioned elsewhere in this patch, the string that can
be in the keyword MUST begin with an alphabetic and consists only of
alphanumeric or dash.

I do not think these limitations imposed by the design decision this
patch is making are particularly bad ones---we just need to be aware
of and firm about them.  When somebody else comes in the future and
wants to recognize "F A I L" as a custom keyword case sensitively,
we must be able to comfortably say "no" to it.

	Side note. We _could_ instead use "remotemsg.<key>.color"
	namespace, as the subsection names at the second level is a
	lot looser, but I do not think it is a good idea to use in
	this application, as they are case sensitive.

The above discussion may deserve to be in the log message as a
record to tell future ourselves why we decided to use
color.remote.<key>.

> +		if (git_config_get_string(sb.buf, &value))
> +			continue;
> +		if (color_parse(value, keywords[i].color))
> +			die(_("config value %s is not a color: %s"), sb.buf, value);

That's a bit inconsistent, isn't it?  If the configuration is not
even a string, we ignore it and continue, but if it is a string, we
insist that it names a color and otherwise die?

> +	}
> +	strbuf_release(&sb);
> +	return use_sideband_colors_cached;
> +}

> +/*
> + * Optionally highlight one keyword in remote output if it appears at the start
> + * of the line. This should be called for a single line only, which must be
> + * passed as the first N characters of the SRC array.
> + */

Saying "MUST be" is cheap, but do we have anybody who polices that
requirement?

I think the code is OK without any assert() or BUG(), and that is
because the design is "we just paint the keyword at the beginning of
what the other side of the sideband wants us to show as a single
unit".  If the other side sends a payload with an embedded LF in a
single packet, that's their choice and we are free not to paint the
beginning of the second line after that LF.  So from that point of
view, perhaps we shouldn't even talk about "a single line only".

> +static void maybe_colorize_sideband(struct strbuf *dest, const char *src, int n)
> +{
> +	int i;
> +
> +	if (!want_color_stderr(use_sideband_colors())) {
> +		strbuf_add(dest, src, n);
> +		return;
> +	}
> +
> +	while (isspace(*src)) {
> +		strbuf_addch(dest, *src);
> +		src++;
> +		n--;
> +	}
> +
> +	for (i = 0; i < ARRAY_SIZE(keywords); i++) {
> +		struct keyword_entry *p = keywords + i;
> +		int len = strlen(p->keyword);
> +		/*
> +		 * Match case insensitively, so we colorize output from existing
> +		 * servers regardless of the case that they use for their
> +		 * messages. We only highlight the word precisely, so
> +		 * "successful" stays uncolored.
> +		 */
> +		if (!strncasecmp(p->keyword, src, len) && !isalnum(src[len])) {
> +			strbuf_addstr(dest, p->color);
> +			strbuf_add(dest, src, len);
> +			strbuf_addstr(dest, GIT_COLOR_RESET);
> +			n -= len;
> +			src += len;
> +			break;
> +		}
> +	}
> +
> +	strbuf_add(dest, src, n);
> +}
> +
>  
>  /*
>   * Receive multiplexed output stream over git native protocol.
> @@ -16,7 +118,7 @@
>  #define DISPLAY_PREFIX "remote: "
>  
>  #define ANSI_SUFFIX "\033[K"
> -#define DUMB_SUFFIX "        "
> +#define DUMB_SUFFIX "	     "
>  

Was this change intended and if so for what purpose?  I can drop
this hunk if this is a mere finger-slip without proofreading, but I
do not want to do so without making sure I am not missing anything
and not discarding a meaningful change.

>  int recv_sideband(const char *me, int in_stream, int out)
>  {


> diff --git a/t/t5409-colorize-remote-messages.sh b/t/t5409-colorize-remote-messages.sh
> new file mode 100755
> index 0000000000..a9afb55ef1
> --- /dev/null
> +++ b/t/t5409-colorize-remote-messages.sh
> @@ -0,0 +1,80 @@
> +#!/bin/sh
> +
> +test_description='remote messages are colorized on the client'
> +
> +. ./test-lib.sh
> +
> +test_expect_success 'setup' '
> +	mkdir .git/hooks &&
> +	write_script .git/hooks/update <<-\EOF &&
> +echo error: error
> +echo ERROR: also highlighted
> +echo hint: hint
> +echo hinting: not highlighted
> +echo success: success
> +echo warning: warning
> +echo prefixerror: error
> +echo "  error: leading space"
> +exit 0
> +EOF

Noticing the dash in "<<-", I would have expected all of the above
lines to be indented with a tab to align with 'w' in 'write_script'.

> +	chmod +x .git/hooks/update &&

No need for this "chmod +x"; that's one of the points in using
write_script.

Thanks.

^ permalink raw reply	[relevance 4%]

* Re: [PATCH 2/2] sideband: highlight keywords in remote sideband output
  2018-08-02 18:22  4%   ` Junio C Hamano
@ 2018-08-06 14:06  0%     ` Han-Wen Nienhuys
  0 siblings, 0 replies; 200+ results
From: Han-Wen Nienhuys @ 2018-08-06 14:06 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

On Thu, Aug 2, 2018 at 8:22 PM Junio C Hamano <gitster@pobox.com> wrote:
> >
> > Helped-by: Duy Nguyen <pclouds@gmail.com>
> > Signed-off-by: Han-Wen Nienhuys <hanwen@google.com>
> > ---
> >  Documentation/config.txt            |   9 +++
> >  help.c                              |   1 +
> >  help.h                              |   1 +
> >  sideband.c                          | 119 +++++++++++++++++++++++++---
> >  t/t5409-colorize-remote-messages.sh |  47 +++++++++++
> >  5 files changed, 168 insertions(+), 9 deletions(-)
> >  create mode 100644 t/t5409-colorize-remote-messages.sh
>
> I'll "chmod +x" while queuing.
>
Done.

> If your "make test" did not catch this as an error, then we may need
> to fix t/Makefile, as it is supposed to run test-lint.

I've been running tests individually as

 sh t5409-colorize-remote-messages.sh  -v -d

> > +color.remote::
> > +     A boolean to enable/disable colored remote output. If unset,
> > +     then the value of `color.ui` is used (`auto` by default).
>
> Nobody tells the end-users what "colored remote output" does;
> arguably they can find it out themselves by enabling the feature and
> observing remote messages, but that is not user friendly.

expanded doc.

> > +color.remote.<slot>::
> > +     Use customized color for each remote keywords. `<slot>` may be
>
> Isn't 'each' a singular, i.e. "for each remote keyword"?  If so I do
> not mind dropping 's' myself while queuing.

Done.

>
> > +     `hint`, `warning`, `success` or `error` which match the
> > +     corresponding keyword.
>
> We need to say that keywords are painted case insensitively
> somewhere in the doc.  Either do that here, or in the updated
> description of `color.remote`---I am not sure which one results in
> more readable text offhand.

Done.

> > +void maybe_colorize_sideband(struct strbuf *dest, const char *src, int n)
> > +{
> > +     int i;
>
> In a block with a dozen more more lines, it is easier to have a
> blank line between decls and the first statement, i.e. here.

Done.

> > +     if (!want_color_stderr(use_sideband_colors())) {
>
> The above line is indented with SP followed by HT; don't.

Fixed. It would be great if there were a pre-commit hook that I could
install to prevent this from ever getting committed.


> > +             struct kwtable* p = keywords + i;
>
>         struct kwtable *p = keywords + i;

Done.

> > +             int len = strlen(p->keyword);
> > +                /*
> > +                 * Match case insensitively, so we colorize output from existing
> > +                 * servers regardless of the case that they use for their
> > +                 * messages. We only highlight the word precisely, so
> > +                 * "successful" stays uncolored.
> > +                 */
>
> Indent with tabs, not a run of spaces, i.e.

Done.

> Use write_script, i.e. instead of all the above, say

Done.


> Our tests are not written to demonstrate that our code works as
> written.  It is to protect our code from getting broken by others
> who may not share vision of the original author.  Make sure that you
> cast what you care about in stone, e.g. include "echo ERROR: bad" or
> something in the above to ensure that future updates to the code
> will not turn the match into a case sensitive one without breaking
> the test suite.

Add some more cases.

> > +     echo 1 >file &&
> > +     git add file &&
> > +     git commit -m 1 &&
> > +     git clone . child &&
> > +     cd child &&
> > +     echo 2 > file &&
> > +     git commit -a -m 2
>
> Don't chdir the whole testing environment like this.  Depending on
> the success and failure in the middle of the above &&-chain, the
> next test will start at an unexpected place, which is bad.
>
> Instead, do something like
>
>         git clone . child &&
>         echo 2 >child/file &&
>         git -C child commit -a -m 2
>
> or
>
Done.

> > +test_expect_success 'push' '
> > +     git -c color.remote=always push -f origin HEAD:refs/heads/newbranch 2>output &&
> > +     test_decode_color <output >decoded &&
> > +     grep "<BOLD;RED>error<RESET>:" decoded &&
> > +     grep "<YELLOW>hint<RESET>:" decoded &&
> > +     grep "<BOLD;GREEN>success<RESET>:" decoded &&
> > +     grep "<BOLD;YELLOW>warning<RESET>:" decoded &&
> > +     grep "prefixerror: error" decoded
>
> A comment before this test (which covers both of these two) that
> explains why many "grep" invocations are necessary, instead of a
> comparison with a single multi-line expected result file.  I am
> guessing that it is *not* because you cannot rely on the order of
> these lines coming out from the update hook, but because the remote
> output have lines other than what is given by the update hook and
> we cannot afford to write them in the expected result file.

Comparing exact outputs is IMO an antipattern in general. It makes the
test more fragile than they need to be. (what if we change the
"remote: " prefix to something else for example?).

If using a golden output, the second test would either require a
repetitive, too long golden file, or it would use loose greps anyway.

Added a comment.

--
Google Germany GmbH, Erika-Mann-Strasse 33, 80636 Munich
Registergericht und -nummer: Hamburg, HRB 86891
Sitz der Gesellschaft: Hamburg
Geschäftsführer: Paul Manicle, Halimah DeLaine Prado

^ permalink raw reply	[relevance 0%]

* Re: What's cooking in git.git (Aug 2018, #01; Thu, 2)
  2018-08-03  0:05  0% ` Stefan Beller
@ 2018-08-03 16:08  0%   ` Junio C Hamano
  0 siblings, 0 replies; 200+ results
From: Junio C Hamano @ 2018-08-03 16:08 UTC (permalink / raw)
  To: Stefan Beller; +Cc: git

Stefan Beller <sbeller@google.com> writes:

> On Thu, Aug 2, 2018 at 4:02 PM Junio C Hamano <gitster@pobox.com> wrote:
>
>> * sb/config-write-fix (2018-08-01) 3 commits
>>  - git-config: document accidental multi-line setting in deprecated syntax
>>  - config: fix case sensitive subsection names on writing
>>  - t1300: document current behavior of setting options
>>
>>  Recent update to "git config" broke updating variable in a
>>  subsection, which has been corrected.
>>
>>  Not quite?
>>  cf. <xmqq4lgc1rbv.fsf@gitster-ct.c.googlers.com>
>
> I'd rather point to
> https://public-inbox.org/git/xmqqftzx67vo.fsf@gitster-ct.c.googlers.com/
> https://public-inbox.org/git/xmqqva8t4s63.fsf@gitster-ct.c.googlers.com/
> instead (reason: shoddiness),

Thanks; I do not think the series was particulary shoddy, but does
deserve a bit more polishing.

> Personally I do not want to care about the old notation
> and by implementing it the way the series is, the
> old notation doesn't see any *changes*.

Yup, I agree that it is good enough.

>> * ds/commit-graph-with-grafts (2018-07-19) 8 commits
>>   (merged to 'next' on 2018-08-02 at 0ee624e329)
>>  + commit-graph: close_commit_graph before shallow walk
>>  + commit-graph: not compatible with uninitialized repo
>>  + commit-graph: not compatible with grafts
>>  + commit-graph: not compatible with replace objects
>>  + test-repository: properly init repo
>>  + commit-graph: update design document
>>  + refs.c: upgrade for_each_replace_ref to be a each_repo_ref_fn callback
>>  + refs.c: migrate internal ref iteration to pass thru repository argument
>>
>>  The recently introduced commit-graph auxiliary data is incompatible
>>  with mechanisms such as replace & grafts that "breaks" immutable
>>  nature of the object reference relationship.  Disable optimizations
>>  based on its use (and updating existing commit-graph) when these
>>  incompatible features are in use in the repository.
>
> Makes sense as a whole, but I dislike the first 2 patches
> (they were my suggestion) for the refs API. I plan to re send patches
> https://public-inbox.org/git/20180730194731.220191-1-sbeller@google.com/
> but fixed for real.
>
> (do not let this stop you from merging down this series)

Well, I do not think we are in a particular hurry to get this down
to 'master', and honestly I'd feel safer to cook a topic that has
potential impact to the core longer in 'next' than other things like
this one (the distinction between the "core" and "other" being how
many things are potentially affected, and because commit-graph is
being integrated into history walking, a bug in the subsystem has a
lot bigger impact than say "rebase -i" that breaks "rebase -i
--root" by producing a malformed author-script file, whose impact
may be severe but limited).

>> * sb/submodule-update-in-c (2018-07-18) 6 commits
>>  - submodule--helper: introduce new update-module-mode helper
>>  - builtin/submodule--helper: factor out method to update a single submodule
>>  - builtin/submodule--helper: store update_clone information in a struct
>>  - builtin/submodule--helper: factor out submodule updating
>>  - git-submodule.sh: rename unused variables
>>  - git-submodule.sh: align error reporting for update mode to use path
>>
>>  "git submodule update" is getting rewritten piece-by-piece into C.
>>
>>  Will merge to 'next'.
>
> Please do not, AFAICT this is still breaking in combination with the
> series merged at 7e25437d35a (Merge branch 'sb/submodule-core-worktree',
> 2018-07-18) and I do not recall fixing the interaction between those two.

Thanks for stopping me.

^ permalink raw reply	[relevance 0%]

* Re: [PATCH 2/3] config: fix case sensitive subsection names on writing
  2018-08-03  0:30  8%                       ` Stefan Beller
@ 2018-08-03 15:51  8%                         ` Junio C Hamano
  0 siblings, 0 replies; 200+ results
From: Junio C Hamano @ 2018-08-03 15:51 UTC (permalink / raw)
  To: Stefan Beller; +Cc: Brandon Williams, git, Johannes Schindelin, peff

Stefan Beller <sbeller@google.com> writes:

> And *technically* the two level is old style, so I figured it's ok.

If we call the bit not after the recentness of the style but after
what it is about, e.g. "is the section name as a whole (including
its possible subsection part) case insensitive?", then yes, a
two-level name can be treated exactly the same way as the old style
names with a subsection.  Perhaps we should do that rename to save
us from future confusion.

>> > -                     !strncasecmp(cf->var.buf, store->key, store->baselen);
>> > +                     !cmpfn(cf->var.buf, store->key, store->baselen);
>>
>> OK.  Section names should still be case insensitive (only the case
>> sensitivity of subsection names is special), but presumably that's
>> already normalized by the caller so we do not have to worry when we
>> use strncmp()?  Can we add a test to demonstrate that it works
>> correctly?
>
> That was already demonstrated (but not tested) in
> https://public-inbox.org/git/20180730230443.74416-4-sbeller@google.com/

Yeah, I also manually tried when I was playing with the old-style
names to see how well it works.  We would want to make sure this
won't get broken in the future, still.

Thanks.

^ permalink raw reply	[relevance 8%]

* Re: [PATCH/RFC] clone: report duplicate entries on case-insensitive filesystems
  2018-08-02 14:43  0%                 ` Duy Nguyen
  2018-08-02 16:27  0%                   ` Junio C Hamano
@ 2018-08-03 14:28  0%                   ` Torsten Bögershausen
  1 sibling, 0 replies; 200+ results
From: Torsten Bögershausen @ 2018-08-03 14:28 UTC (permalink / raw)
  To: Duy Nguyen, Junio C Hamano
  Cc: Jeff King, Elijah Newren, Git Mailing List, Paweł Paruzel,
	brian m. carlson

On 2018-08-02 15:43, Duy Nguyen wrote:
> On Wed, Aug 1, 2018 at 11:20 PM Junio C Hamano <gitster@pobox.com> wrote:
>>
>> Junio C Hamano <gitster@pobox.com> writes:
>>
>>> Jeff King <peff@peff.net> writes:
>>>
>>>>> Presumably we are already in an error codepath, so if it is
>>>>> absolutely necessary, then we can issue a lstat() to grab the inum
>>>>> for the path we are about to create, iterate over the previously
>>>>> checked out paths issuing lstat() and see which one yields the same
>>>>> inum, to find the one who is the culprit.
>>>>
>>>> Yes, this is the cleverness I was missing in my earlier response.
>>>>
>>>> So it seems do-able, and I like that this incurs no cost in the
>>>> non-error case.
>>>
>>> Not so fast, unfortunately.
>>>
>>> I suspect that some filesystems do not give us inum that we can use
>>> for that "identity" purpose, and they tend to be the ones with the
>>> case smashing characteristics where we need this code in the error
>>> path the most X-<.
>>
>> But even if inum is unreliable, we should be able to use other
>> clues, perhaps the same set of fields we use for cached stat
>> matching optimization we use for "diff" plumbing commands, to
>> implement the error report.  The more important part of the idea is
>> that we already need to notice that we need to remove a path that is
>> in the working tree while doing the checkout, so the alternative
>> approach won't incur any extra cost for normal cases where the
>> project being checked out does not have two files whose pathnames
>> are only different in case (or checking out such an offending
>> project to a case sensitive filesytem, of course).
>>
>> So I guess it still _is_ workable.  Any takers?
> 
> OK so we're going back to the original way of checking that we check
> out the different files on the same place (because fs is icase) and
> try to collect all paths for reporting, yes?

I would say: Yes.

> I can give it another go
> (but of course if anybody else steps up, I'd very gladly hand this
> over)
> 

Not at the moment.



^ permalink raw reply	[relevance 0%]

* [PATCH 2/3] config: fix case sensitive subsection names on writing
  2018-08-03  0:34  7%                   ` [PATCH 0/3] Reroll of sb/config-write-fix Stefan Beller
  2018-08-03  0:34  7%                     ` [PATCH 1/3] t1300: document current behavior of setting options Stefan Beller
@ 2018-08-03  0:34 20%                     ` Stefan Beller
  1 sibling, 0 replies; 200+ results
From: Stefan Beller @ 2018-08-03  0:34 UTC (permalink / raw)
  To: sbeller; +Cc: bmwill, git, gitster, johannes.schindelin, peff

A user reported a submodule issue regarding a section mix-up,
but it could be boiled down to the following test case:

  $ git init test  && cd test
  $ git config foo."Bar".key test
  $ git config foo."bar".key test
  $ tail -n 3 .git/config
  [foo "Bar"]
        key = test
        key = test

Sub sections are case sensitive and we have a test for correctly reading
them. However we do not have a test for writing out config correctly with
case sensitive subsection names, which is why this went unnoticed in
6ae996f2acf (git_config_set: make use of the config parser's event
stream, 2018-04-09)

Unfortunately we have to make a distinction between old style configuration
that looks like

  [foo.Bar]
        key = test

and the new quoted style as seen above. The old style is documented as
case-agnostic, hence we need to keep 'strncasecmp'; although the
resulting setting for the old style config differs from the configuration.
That will be fixed in a follow up patch.

Reported-by: JP Sugarbroad <jpsugar@google.com>
Signed-off-by: Stefan Beller <sbeller@google.com>
---
 config.c          | 12 +++++++++++-
 t/t1300-config.sh |  1 +
 2 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/config.c b/config.c
index 66645047eb3..ffc2ffeafeb 100644
--- a/config.c
+++ b/config.c
@@ -37,6 +37,7 @@ struct config_source {
 	int eof;
 	struct strbuf value;
 	struct strbuf var;
+	unsigned section_name_old_dot_style : 1;
 
 	int (*do_fgetc)(struct config_source *c);
 	int (*do_ungetc)(int c, struct config_source *conf);
@@ -605,6 +606,7 @@ static int get_value(config_fn_t fn, void *data, struct strbuf *name)
 
 static int get_extended_base_var(struct strbuf *name, int c)
 {
+	cf->section_name_old_dot_style = 0;
 	do {
 		if (c == '\n')
 			goto error_incomplete_line;
@@ -641,6 +643,7 @@ static int get_extended_base_var(struct strbuf *name, int c)
 
 static int get_base_var(struct strbuf *name)
 {
+	cf->section_name_old_dot_style = 1;
 	for (;;) {
 		int c = get_next_char();
 		if (cf->eof)
@@ -2364,14 +2367,21 @@ static int store_aux_event(enum config_event_t type,
 	store->parsed[store->parsed_nr].type = type;
 
 	if (type == CONFIG_EVENT_SECTION) {
+		int (*cmpfn)(const char *, const char *, size_t);
+
 		if (cf->var.len < 2 || cf->var.buf[cf->var.len - 1] != '.')
 			return error("invalid section name '%s'", cf->var.buf);
 
+		if (cf->section_name_old_dot_style)
+			cmpfn = strncasecmp;
+		else
+			cmpfn = strncmp;
+
 		/* Is this the section we were looking for? */
 		store->is_keys_section =
 			store->parsed[store->parsed_nr].is_keys_section =
 			cf->var.len - 1 == store->baselen &&
-			!strncasecmp(cf->var.buf, store->key, store->baselen);
+			!cmpfn(cf->var.buf, store->key, store->baselen);
 		if (store->is_keys_section) {
 			store->section_seen = 1;
 			ALLOC_GROW(store->seen, store->seen_nr + 1,
diff --git a/t/t1300-config.sh b/t/t1300-config.sh
index e87cfc1804f..4976e2fcd3f 100755
--- a/t/t1300-config.sh
+++ b/t/t1300-config.sh
@@ -1290,6 +1290,7 @@ test_expect_success 'setting different case sensitive subsections ' '
 		Qc = v2
 		[d "e"]
 		f = v1
+		[d "E"]
 		Qf = v2
 	EOF
 	# exact match
-- 
2.18.0.132.g195c49a2227


^ permalink raw reply related	[relevance 20%]

* [PATCH 1/3] t1300: document current behavior of setting options
  2018-08-03  0:34  7%                   ` [PATCH 0/3] Reroll of sb/config-write-fix Stefan Beller
@ 2018-08-03  0:34  7%                     ` Stefan Beller
  2018-08-03  0:34 20%                     ` [PATCH 2/3] config: fix case sensitive subsection names on writing Stefan Beller
  1 sibling, 0 replies; 200+ results
From: Stefan Beller @ 2018-08-03  0:34 UTC (permalink / raw)
  To: sbeller; +Cc: bmwill, git, gitster, johannes.schindelin, peff

This documents current behavior of the config machinery, when changing
the value of some settings. This patch just serves to provide a baseline
for the follow up that will fix some issues with the current behavior.

Signed-off-by: Stefan Beller <sbeller@google.com>
---
 t/t1300-config.sh | 86 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 86 insertions(+)

diff --git a/t/t1300-config.sh b/t/t1300-config.sh
index 24706ba4125..e87cfc1804f 100755
--- a/t/t1300-config.sh
+++ b/t/t1300-config.sh
@@ -1218,6 +1218,92 @@ test_expect_success 'last one wins: three level vars' '
 	test_cmp expect actual
 '
 
+test_expect_success 'old-fashioned settings are case insensitive' '
+	test_when_finished "rm -f testConfig testConfig_expect testConfig_actual" &&
+
+	cat >testConfig_actual <<-EOF &&
+		[V.A]
+		r = value1
+	EOF
+	q_to_tab >testConfig_expect <<-EOF &&
+		[V.A]
+		Qr = value2
+	EOF
+	git config -f testConfig_actual "v.a.r" value2 &&
+	test_cmp testConfig_expect testConfig_actual &&
+
+	cat >testConfig_actual <<-EOF &&
+		[V.A]
+		r = value1
+	EOF
+	q_to_tab >testConfig_expect <<-EOF &&
+		[V.A]
+		QR = value2
+	EOF
+	git config -f testConfig_actual "V.a.R" value2 &&
+	test_cmp testConfig_expect testConfig_actual &&
+
+	cat >testConfig_actual <<-EOF &&
+		[V.A]
+		r = value1
+	EOF
+	q_to_tab >testConfig_expect <<-EOF &&
+		[V.A]
+		r = value1
+		Qr = value2
+	EOF
+	git config -f testConfig_actual "V.A.r" value2 &&
+	test_cmp testConfig_expect testConfig_actual &&
+
+	cat >testConfig_actual <<-EOF &&
+		[V.A]
+		r = value1
+	EOF
+	q_to_tab >testConfig_expect <<-EOF &&
+		[V.A]
+		r = value1
+		Qr = value2
+	EOF
+	git config -f testConfig_actual "v.A.r" value2 &&
+	test_cmp testConfig_expect testConfig_actual
+'
+
+test_expect_success 'setting different case sensitive subsections ' '
+	test_when_finished "rm -f testConfig testConfig_expect testConfig_actual" &&
+
+	cat >testConfig_actual <<-EOF &&
+		[V "A"]
+		R = v1
+		[K "E"]
+		Y = v1
+		[a "b"]
+		c = v1
+		[d "e"]
+		f = v1
+	EOF
+	q_to_tab >testConfig_expect <<-EOF &&
+		[V "A"]
+		Qr = v2
+		[K "E"]
+		Qy = v2
+		[a "b"]
+		Qc = v2
+		[d "e"]
+		f = v1
+		Qf = v2
+	EOF
+	# exact match
+	git config -f testConfig_actual a.b.c v2 &&
+	# match section and subsection, key is cased differently.
+	git config -f testConfig_actual K.E.y v2 &&
+	# section and key are matched case insensitive, but subsection needs
+	# to match; When writing out new values only the key is adjusted
+	git config -f testConfig_actual v.A.r v2 &&
+	# subsection is not matched:
+	git config -f testConfig_actual d.E.f v2 &&
+	test_cmp testConfig_expect testConfig_actual
+'
+
 for VAR in a .a a. a.0b a."b c". a."b c".0d
 do
 	test_expect_success "git -c $VAR=VAL rejects invalid '$VAR'" '
-- 
2.18.0.132.g195c49a2227


^ permalink raw reply related	[relevance 7%]

* [PATCH 0/3] Reroll of sb/config-write-fix
  2018-08-01 19:34  4%                 ` [PATCH 0/3] sb/config-write-fix done without robbing Peter Stefan Beller
  2018-08-01 19:34  7%                   ` [PATCH 1/3] t1300: document current behavior of setting options Stefan Beller
  2018-08-01 19:34 20%                   ` [PATCH 2/3] config: fix case sensitive subsection names on writing Stefan Beller
@ 2018-08-03  0:34  7%                   ` Stefan Beller
  2018-08-03  0:34  7%                     ` [PATCH 1/3] t1300: document current behavior of setting options Stefan Beller
  2018-08-03  0:34 20%                     ` [PATCH 2/3] config: fix case sensitive subsection names on writing Stefan Beller
  2 siblings, 2 replies; 200+ results
From: Stefan Beller @ 2018-08-03  0:34 UTC (permalink / raw)
  To: sbeller; +Cc: bmwill, git, gitster, johannes.schindelin, peff

Only fix was in the commit message of the second patch:

2:  c667e555066 ! 1749:  38e5f6f335d config: fix case sensitive subsection names on writing
    @@ -2,8 +2,8 @@
     
         config: fix case sensitive subsection names on writing
     
    -    A use reported a submodule issue regarding strange case indentation
    -    issues, but it could be boiled down to the following test case:
    +    A user reported a submodule issue regarding a section mix-up,
    +    but it could be boiled down to the following test case:

previous version at
https://public-inbox.org/git/20180801193413.146994-1-sbeller@google.com/

Stefan Beller (3):
  t1300: document current behavior of setting options
  config: fix case sensitive subsection names on writing
  git-config: document accidental multi-line setting in deprecated
    syntax

 Documentation/git-config.txt | 21 +++++++++
 config.c                     | 12 ++++-
 t/t1300-config.sh            | 87 ++++++++++++++++++++++++++++++++++++
 3 files changed, 119 insertions(+), 1 deletion(-)

-- 
2.18.0.132.g195c49a2227


^ permalink raw reply	[relevance 7%]

* Re: [PATCH 2/3] config: fix case sensitive subsection names on writing
  2018-08-01 22:51  7%                     ` Junio C Hamano
@ 2018-08-03  0:30  8%                       ` Stefan Beller
  2018-08-03 15:51  8%                         ` Junio C Hamano
  0 siblings, 1 reply; 200+ results
From: Stefan Beller @ 2018-08-03  0:30 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Brandon Williams, git, Johannes Schindelin, peff

On Wed, Aug 1, 2018 at 3:51 PM Junio C Hamano <gitster@pobox.com> wrote:
>
> Stefan Beller <sbeller@google.com> writes:
>
> > A use reported a submodule issue regarding strange case indentation
> > issues, but it could be boiled down to the following test case:
>
> Perhaps
>
> s/use/user/
> s/case indentation issues/section mix-up/

will be fixed in a reroll

>
> > ... However we do not have a test for writing out config correctly with
> > case sensitive subsection names, which is why this went unnoticed in
> > 6ae996f2acf (git_config_set: make use of the config parser's event
> > stream, 2018-04-09)
>
> s/unnoticed in \(.*04-09)\)/unnoticed when \1 broke it./
>
> This is why I asked if the patch is a "FIX" for an issue introduced
> by the cited commit.

I did not check further down the history if it was a recent brakage.

> >  static int get_base_var(struct strbuf *name)
> >  {
> > +     cf->section_name_old_dot_style = 1;
> >       for (;;) {
> >               int c = get_next_char();
> >               if (cf->eof)
>
> OK, let me rephrase.  The basic parse structure is that
>
>  * upon seeing '[', we call get_base_var(), which stuffs the
>    "section" (including subsection, if exists) in the strbuf.
>
>  * get_base_var() upon seeing a space after "[section ", calls
>    get_extended_base_var().  This space can never exist in an
>    old-style three-level names, where it is spelled as
>    "[section.subsection]".  This space cannot exist in two-level
>    names, either.  The closing ']' is eaten by this function before
>    it returns.
>
>  * get_extended_base_var() grabs the "double quoted" subsection name
>    and eats the closing ']' before it returns.
>
> So you set the new bit (section_name_old_dot_style) at the beginning
> of get_base_var(), i.e. declare that you assume we are reading old
> style, but upon entering get_extended_base_var(), unset it, because
> now you know we are parsing a modern style three-level name(s).
>
> Feels quite sensible way to keep track of old/new styles.
>
> When parsing two-level names, old-style bit is set, which we may
> need to be careful, thoguh.

I considered setting it only when seeing the dot, but then we'd have
to make sure it is properly initialized.

And *technically* the two level is old style, so I figured it's ok.

> > -                     !strncasecmp(cf->var.buf, store->key, store->baselen);
> > +                     !cmpfn(cf->var.buf, store->key, store->baselen);
>
> OK.  Section names should still be case insensitive (only the case
> sensitivity of subsection names is special), but presumably that's
> already normalized by the caller so we do not have to worry when we
> use strncmp()?  Can we add a test to demonstrate that it works
> correctly?

That was already demonstrated (but not tested) in
https://public-inbox.org/git/20180730230443.74416-4-sbeller@google.com/

^ permalink raw reply	[relevance 8%]

* Re: What's cooking in git.git (Aug 2018, #01; Thu, 2)
  2018-08-02 23:02  1% What's cooking in git.git (Aug 2018, #01; Thu, 2) Junio C Hamano
@ 2018-08-03  0:05  0% ` Stefan Beller
  2018-08-03 16:08  0%   ` Junio C Hamano
  0 siblings, 1 reply; 200+ results
From: Stefan Beller @ 2018-08-03  0:05 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

On Thu, Aug 2, 2018 at 4:02 PM Junio C Hamano <gitster@pobox.com> wrote:

> * sb/config-write-fix (2018-08-01) 3 commits
>  - git-config: document accidental multi-line setting in deprecated syntax
>  - config: fix case sensitive subsection names on writing
>  - t1300: document current behavior of setting options
>
>  Recent update to "git config" broke updating variable in a
>  subsection, which has been corrected.
>
>  Not quite?
>  cf. <xmqq4lgc1rbv.fsf@gitster-ct.c.googlers.com>

I'd rather point to
https://public-inbox.org/git/xmqqftzx67vo.fsf@gitster-ct.c.googlers.com/
https://public-inbox.org/git/xmqqva8t4s63.fsf@gitster-ct.c.googlers.com/
instead (reason: shoddiness),

as the message you refer to points out *another*
bug, using the old notation, that was there before that
series and still is there after the series.

Personally I do not want to care about the old notation
and by implementing it the way the series is, the
old notation doesn't see any *changes*.

>
> * ds/commit-graph-with-grafts (2018-07-19) 8 commits
>   (merged to 'next' on 2018-08-02 at 0ee624e329)
>  + commit-graph: close_commit_graph before shallow walk
>  + commit-graph: not compatible with uninitialized repo
>  + commit-graph: not compatible with grafts
>  + commit-graph: not compatible with replace objects
>  + test-repository: properly init repo
>  + commit-graph: update design document
>  + refs.c: upgrade for_each_replace_ref to be a each_repo_ref_fn callback
>  + refs.c: migrate internal ref iteration to pass thru repository argument
>
>  The recently introduced commit-graph auxiliary data is incompatible
>  with mechanisms such as replace & grafts that "breaks" immutable
>  nature of the object reference relationship.  Disable optimizations
>  based on its use (and updating existing commit-graph) when these
>  incompatible features are in use in the repository.

Makes sense as a whole, but I dislike the first 2 patches
(they were my suggestion) for the refs API. I plan to re send patches
https://public-inbox.org/git/20180730194731.220191-1-sbeller@google.com/
but fixed for real.

(do not let this stop you from merging down this series)

> * sb/histogram-less-memory (2018-07-23) 4 commits
>   (merged to 'next' on 2018-08-02 at cfb02aa3b5)
>  + xdiff/histogram: remove tail recursion
>  + xdiff/xhistogram: move index allocation into find_lcs
>  + xdiff/xhistogram: factor out memory cleanup into free_index()
>  + xdiff/xhistogram: pass arguments directly to fall_back_to_classic_diff
>
>  "git diff --histogram" had a bad memory usage pattern, which has
>  been rearranged to reduce the peak usage.
>

Reminder to self: I need to work on the documentation patches
for diffing, too.

>
> * sb/submodule-update-in-c (2018-07-18) 6 commits
>  - submodule--helper: introduce new update-module-mode helper
>  - builtin/submodule--helper: factor out method to update a single submodule
>  - builtin/submodule--helper: store update_clone information in a struct
>  - builtin/submodule--helper: factor out submodule updating
>  - git-submodule.sh: rename unused variables
>  - git-submodule.sh: align error reporting for update mode to use path
>
>  "git submodule update" is getting rewritten piece-by-piece into C.
>
>  Will merge to 'next'.

Please do not, AFAICT this is still breaking in combination with the
series merged at 7e25437d35a (Merge branch 'sb/submodule-core-worktree',
2018-07-18) and I do not recall fixing the interaction between those two.

Stefan

^ permalink raw reply	[relevance 0%]

* What's cooking in git.git (Aug 2018, #01; Thu, 2)
@ 2018-08-02 23:02  1% Junio C Hamano
  2018-08-03  0:05  0% ` Stefan Beller
  0 siblings, 1 reply; 200+ results
From: Junio C Hamano @ 2018-08-02 23:02 UTC (permalink / raw)
  To: git

Here are the topics that have been cooking.  Commits prefixed with
'-' are only in 'pu' (proposed updates) while commits prefixed with
'+' are in 'next'.  The ones marked with '.' do not appear in any of
the integration branches, but I am still holding onto them.

Many topics have moved to 'master' and 'next' from 'next' to 'pu'
respectively.

You can find the changes described here in the integration branches
of the repositories listed at

    http://git-blame.blogspot.com/p/git-public-repositories.html

--------------------------------------------------
[Graduated to "master"]

* ab/checkout-default-remote (2018-06-11) 8 commits
  (merged to 'next' on 2018-07-24 at 6ef645f485)
 + checkout & worktree: introduce checkout.defaultRemote
 + checkout: add advice for ambiguous "checkout <branch>"
 + builtin/checkout.c: use "ret" variable for return
 + checkout: pass the "num_matches" up to callers
 + checkout.c: change "unique" member to "num_matches"
 + checkout.c: introduce an *_INIT macro
 + checkout.h: wrap the arguments to unique_tracking_name()
 + checkout tests: index should be clean after dwim checkout
 (this branch is used by ab/test-must-be-empty.)

 "git checkout" and "git worktree add" learned to honor
 checkout.defaultRemote when auto-vivifying a local branch out of a
 remote tracking branch in a repository with multiple remotes that
 have tracking branches that share the same names.


* bc/object-id (2018-07-16) 16 commits
  (merged to 'next' on 2018-07-24 at 23680778a9)
 + pretty: switch hard-coded constants to the_hash_algo
 + sha1-file: convert constants to uses of the_hash_algo
 + log-tree: switch GIT_SHA1_HEXSZ to the_hash_algo->hexsz
 + diff: switch GIT_SHA1_HEXSZ to use the_hash_algo
 + builtin/merge-recursive: make hash independent
 + builtin/merge: switch to use the_hash_algo
 + builtin/fmt-merge-msg: make hash independent
 + builtin/update-index: simplify parsing of cacheinfo
 + builtin/update-index: convert to using the_hash_algo
 + refs/files-backend: use the_hash_algo for writing refs
 + sha1-name: use the_hash_algo when parsing object names
 + strbuf: allocate space with GIT_MAX_HEXSZ
 + commit: express tree entry constants in terms of the_hash_algo
 + hex: switch to using the_hash_algo
 + tree-walk: replace hard-coded constants with the_hash_algo
 + cache: update object ID functions for the_hash_algo

 Conversion from uchar[40] to struct object_id continues.


* bc/sequencer-export-work-tree-as-well (2018-07-16) 1 commit
  (merged to 'next' on 2018-07-24 at 0b83ade721)
 + sequencer: pass absolute GIT_WORK_TREE to exec commands

 "git rebase" started exporting GIT_DIR environment variable and
 exposing it to hook scripts when part of it got rewritten in C.
 Instead of matching the old scripted Porcelains' behaviour,
 compensate by also exporting GIT_WORK_TREE environment as well to
 lessen the damage.  This can harm existing hooks that want to
 operate on different repository, but the current behaviour is
 already broken for them anyway.


* bp/test-drop-caches-for-windows (2018-07-12) 1 commit
  (merged to 'next' on 2018-07-24 at 257bb336c6)
 + handle lower case drive letters on Windows

 A test helper update for Windows.


* ds/commit-graph-fsck (2018-07-16) 23 commits
  (merged to 'next' on 2018-07-24 at 6a802adc7a)
 + coccinelle: update commit.cocci
 + commit-graph: update design document
 + gc: automatically write commit-graph files
 + commit-graph: add '--reachable' option
 + commit-graph: use string-list API for input
 + fsck: verify commit-graph
 + commit-graph: verify contents match checksum
 + commit-graph: test for corrupted octopus edge
 + commit-graph: verify commit date
 + commit-graph: verify generation number
 + commit-graph: verify parent list
 + commit-graph: verify root tree OIDs
 + commit-graph: verify objects exist
 + commit-graph: verify corrupt OID fanout and lookup
 + commit-graph: verify required chunks are present
 + commit-graph: verify catches corrupt signature
 + commit-graph: add 'verify' subcommand
 + commit-graph: load a root tree from specific graph
 + commit: force commit to parse from object database
 + commit-graph: parse commit from chosen graph
 + commit-graph: fix GRAPH_MIN_SIZE
 + commit-graph: UNLEAK before die()
 + t5318-commit-graph.sh: use core.commitGraph
 (this branch is used by ds/commit-graph-with-grafts, ds/reachable and jt/commit-graph-per-object-store.)

 "git fsck" learns to make sure the optional commit-graph file is in
 a sane state.


* en/dirty-merge-fixes (2018-07-11) 9 commits
  (merged to 'next' on 2018-07-24 at 7b6ca3507c)
 + merge: fix misleading pre-merge check documentation
 + merge-recursive: enforce rule that index matches head before merging
 + t6044: add more testcases with staged changes before a merge is invoked
 + merge-recursive: fix assumption that head tree being merged is HEAD
 + merge-recursive: make sure when we say we abort that we actually abort
 + t6044: add a testcase for index matching head, when head doesn't match HEAD
 + t6044: verify that merges expected to abort actually abort
 + index_has_changes(): avoid assuming operating on the_index
 + read-cache.c: move index_has_changes() from merge.c

 The recursive merge strategy did not properly ensure there was no
 change between HEAD and the index before performing its operation,
 which has been corrected.


* en/t6036-merge-recursive-tests (2018-07-11) 6 commits
  (merged to 'next' on 2018-07-24 at 75055cb6e1)
 + t6036: add a failed conflict detection case: regular files, different modes
 + t6036: add a failed conflict detection case with conflicting types
 + t6036: add a failed conflict detection case with submodule add/add
 + t6036: add a failed conflict detection case with submodule modify/modify
 + t6036: add a failed conflict detection case with symlink add/add
 + t6036: add a failed conflict detection case with symlink modify/modify

 Tests to cover various conflicting cases have been added for
 merge-recursive.


* en/t6036-recursive-corner-cases (2018-07-12) 2 commits
  (merged to 'next' on 2018-07-24 at b7b3514ef4)
 + t6036: fix broken && chain in sub-shell
 + t6036: add lots of detail for directory/file conflicts in recursive case

 Tests to cover more D/F conflict cases have been added for
 merge-recursive.


* en/t6042-insane-merge-rename-testcases (2018-07-03) 3 commits
  (merged to 'next' on 2018-07-24 at 65c80f72da)
 + t6042: add testcase covering long chains of rename conflicts
 + t6042: add testcase covering rename/rename(2to1)/delete/delete conflict
 + t6042: add testcase covering rename/add/delete conflict type

 Various glitches in the heuristics of merge-recursive strategy have
 been documented in new tests.

 I am not sure if there is a single "correct" answer everybody can
 agree on for each of these "insane" cases, though.


* en/t7405-recursive-submodule-conflicts (2018-07-11) 3 commits
  (merged to 'next' on 2018-07-24 at 6cb7d02298)
 + t7405: verify 'merge --abort' works after submodule/path conflicts
 + t7405: add a directory/submodule conflict
 + t7405: add a file/submodule conflict

 Tests to cover conflict cases that involve submodules have been
 added for merge-recursive.


* es/chain-lint-in-subshell (2018-07-31) 11 commits
  (merged to 'next' on 2018-07-31 at 4ce2a8faa4)
 + t/chainlint.sed: drop extra spaces from regex character class
  (merged to 'next' on 2018-07-24 at 9370bbdfaf)
 + t/chainlint: add chainlint "specialized" test cases
 + t/chainlint: add chainlint "complex" test cases
 + t/chainlint: add chainlint "cuddled" test cases
 + t/chainlint: add chainlint "loop" and "conditional" test cases
 + t/chainlint: add chainlint "nested subshell" test cases
 + t/chainlint: add chainlint "one-liner" test cases
 + t/chainlint: add chainlint "whitespace" test cases
 + t/chainlint: add chainlint "basic" test cases
 + t/Makefile: add machinery to check correctness of chainlint.sed
 + t/test-lib: teach --chain-lint to detect broken &&-chains in subshells
 (this branch uses es/test-fixes.)

 Look for broken "&&" chains that are hidden in subshell, many of
 which have been found and corrected.


* es/test-fixes (2018-07-17) 26 commits
  (merged to 'next' on 2018-07-24 at fd6796a3ef)
 + t5608: fix broken &&-chain
 + t9119: fix broken &&-chains
 + t9000-t9999: fix broken &&-chains
 + t7000-t7999: fix broken &&-chains
 + t6000-t6999: fix broken &&-chains
 + t5000-t5999: fix broken &&-chains
 + t4000-t4999: fix broken &&-chains
 + t3030: fix broken &&-chains
 + t3000-t3999: fix broken &&-chains
 + t2000-t2999: fix broken &&-chains
 + t1000-t1999: fix broken &&-chains
 + t0000-t0999: fix broken &&-chains
 + t9814: simplify convoluted check that command correctly errors out
 + t9001: fix broken "invoke hook" test
 + t7810: use test_expect_code() instead of hand-rolled comparison
 + t7400: fix broken "submodule add/reconfigure --force" test
 + t7201: drop pointless "exit 0" at end of subshell
 + t6036: fix broken "merge fails but has appropriate contents" tests
 + t5505: modernize and simplify hard-to-digest test
 + t5406: use write_script() instead of birthing shell script manually
 + t5405: use test_must_fail() instead of checking exit code manually
 + t/lib-submodule-update: fix "absorbing" test
 + t: drop unnecessary terminating semicolon in subshell
 + t: use sane_unset() rather than 'unset' with broken &&-chain
 + t: use test_write_lines() instead of series of 'echo' commands
 + t: use test_might_fail() instead of manipulating exit code manually
 (this branch is used by es/chain-lint-in-subshell.)

 Test clean-up and corrections.


* is/parsing-line-range (2018-06-15) 2 commits
  (merged to 'next' on 2018-07-24 at a06b453f32)
 + log: prevent error if line range ends past end of file
 + blame: prevent error if range ends past end of file

 Parsing of -L[<N>][,[<M>]] parameters "git blame" and "git log"
 take has been tweaked.


* jk/fsck-gitmodules-gently (2018-07-16) 6 commits
  (merged to 'next' on 2018-07-24 at 5b15c800db)
 + fsck: downgrade gitmodulesParse default to "info"
 + fsck: split ".gitmodules too large" error from parse failure
 + fsck: silence stderr when parsing .gitmodules
 + config: add options parameter to git_config_from_mem
 + config: add CONFIG_ERROR_SILENT handler
 + config: turn die_on_error into caller-facing enum

 Recent "security fix" to pay attention to contents of ".gitmodules"
 while accepting "git push" was a bit overly strict than necessary,
 which has been adjusted.


* jk/has-uncommitted-changes-fix (2018-07-11) 1 commit
  (merged to 'next' on 2018-07-24 at 2ea14c0afb)
 + has_uncommitted_changes(): fall back to empty tree

 "git pull --rebase" on a corrupt HEAD caused a segfault.  In
 general we substitute an empty tree object when running the in-core
 equivalent of the diff-index command, and the codepath has been
 corrected to do so as well to fix this issue.


* jm/cache-entry-from-mem-pool (2018-07-03) 8 commits
  (merged to 'next' on 2018-07-24 at 9be51a88dc)
 + block alloc: add validations around cache_entry lifecyle
 + block alloc: allocate cache entries from mem_pool
 + mem-pool: fill out functionality
 + mem-pool: add life cycle management functions
 + mem-pool: only search head block for available space
 + block alloc: add lifecycle APIs for cache_entry structs
 + read-cache: teach make_cache_entry to take object_id
 + read-cache: teach refresh_cache_entry to take istate

 For a large tree, the index needs to hold many cache entries
 allocated on heap.  These cache entries are now allocated out of a
 dedicated memory pool to amortize malloc(3) overhead.

 This makes each cache-entry larger by either 4 or 8 bytes, which is
 a bit sad, though.


* jm/send-email-tls-auth-on-batch (2018-07-16) 1 commit
  (merged to 'next' on 2018-07-24 at fb3e653f44)
 + send-email: fix tls AUTH when sending batch

 "git send-email" when using in a batched mode that limits the
 number of messages sent in a single SMTP session lost the contents
 of the variable used to choose between tls/ssl, unable to send the
 second and later batches, which has been fixed.

 This is marked to be merged to 'next' already, but I do not mind
 getting an updated version with an improved log message before that
 happens.


* js/rebase-merge-octopus (2018-07-11) 3 commits
  (merged to 'next' on 2018-07-24 at 14ad8699de)
 + rebase --rebase-merges: adjust man page for octopus support
 + rebase --rebase-merges: add support for octopus merges
 + merge: allow reading the merge commit message from a file

 "git rebase --rebase-merges" mode now handles octopus merges as
 well.


* jt/commit-graph-per-object-store (2018-07-17) 7 commits
  (merged to 'next' on 2018-07-24 at 090d1a4d59)
 + commit-graph: add repo arg to graph readers
 + commit-graph: store graph in struct object_store
 + commit-graph: add free_commit_graph
 + commit-graph: add missing forward declaration
 + object-store: add missing include
 + commit-graph: refactor preparing commit graph
 + Merge branch 'ds/commit-graph-fsck' into jt/commit-graph-per-object-store
 (this branch is used by ds/commit-graph-with-grafts and ds/reachable; uses ds/commit-graph-fsck and sb/object-store-lookup.)

 The singleton commit-graph in-core instance is made per in-core
 repository instance.


* jt/fetch-nego-tip (2018-07-03) 1 commit
  (merged to 'next' on 2018-07-24 at a9e299006d)
 + fetch-pack: support negotiation tip whitelist
 (this branch is used by ab/fetch-nego; uses jt/fetch-pack-negotiator; is tangled with jt/fetch-negotiator-skipping.)

 "git fetch" learned a new option "--negotiation-tip" to limit the
 set of commits it tells the other end as "have", to reduce wasted
 bandwidth and cycles, which would be helpful when the receiving
 repository has a lot of refs that have little to do with the
 history at the remote it is fetching from.


* jt/fetch-negotiator-skipping (2018-07-16) 1 commit
  (merged to 'next' on 2018-07-24 at 8e25a49405)
 + negotiator/skipping: skip commits during fetch
 (this branch is used by ab/fetch-nego; uses jt/fetch-pack-negotiator; is tangled with jt/fetch-nego-tip.)

 Add a server-side knob to skip commits in exponential/fibbonacci
 stride in an attempt to cover wider swath of history with a smaller
 number of iterations, potentially accepting a larger packfile
 transfer, instead of going back one commit a time during common
 ancestor discovery during the "git fetch" transaction.


* jt/fetch-pack-negotiator (2018-06-15) 7 commits
  (merged to 'next' on 2018-07-24 at 438efcd6b1)
 + fetch-pack: introduce negotiator API
 + fetch-pack: move common check and marking together
 + fetch-pack: make negotiation-related vars local
 + fetch-pack: use ref adv. to prune "have" sent
 + fetch-pack: directly end negotiation if ACK ready
 + fetch-pack: clear marks before re-marking
 + fetch-pack: split up everything_local()
 (this branch is used by ab/fetch-nego, jt/fetch-nego-tip and jt/fetch-negotiator-skipping.)

 Code restructuring and a small fix to transport protocol v2 during
 fetching.


* jt/tags-to-promised-blobs-fix (2018-07-16) 2 commits
  (merged to 'next' on 2018-07-24 at 8d7e78a671)
 + tag: don't warn if target is missing but promised
 + revision: tolerate promised targets of tags

 The lazy clone support had a few places where missing but promised
 objects were not correctly tolerated, which have been fixed.


* kg/gc-auto-windows-workaround (2018-07-09) 1 commit
  (merged to 'next' on 2018-07-24 at 71c05d27b6)
 + gc --auto: release pack files before auto packing

 "git gc --auto" opens file descriptors for the packfiles before
 spawning "git repack/prune", which would upset Windows that does
 not want a process to work on a file that is open by another
 process.  The issue has been worked around.


* sb/diff-color-move-more (2018-07-19) 10 commits
  (merged to 'next' on 2018-07-24 at 89c893cab2)
 + diff.c: offer config option to control ws handling in move detection
 + diff.c: add white space mode to move detection that allows indent changes
 + diff.c: factor advance_or_nullify out of mark_color_as_moved
 + diff.c: decouple white space treatment from move detection algorithm
 + diff.c: add a blocks mode for moved code detection
 + diff.c: adjust hash function signature to match hashmap expectation
 + diff.c: do not pass diff options as keydata to hashmap
 + t4015: avoid git as a pipe input
 + xdiff/xdiffi.c: remove unneeded function declarations
 + xdiff/xdiff.h: remove unused flags

 "git diff --color-moved" feature has further been tweaked.


* sb/object-store-lookup (2018-06-29) 33 commits
  (merged to 'next' on 2018-07-24 at dd96e29376)
 + commit.c: allow lookup_commit_reference to handle arbitrary repositories
 + commit.c: allow lookup_commit_reference_gently to handle arbitrary repositories
 + tag.c: allow deref_tag to handle arbitrary repositories
 + object.c: allow parse_object to handle arbitrary repositories
 + object.c: allow parse_object_buffer to handle arbitrary repositories
 + commit.c: allow get_cached_commit_buffer to handle arbitrary repositories
 + commit.c: allow set_commit_buffer to handle arbitrary repositories
 + commit.c: migrate the commit buffer to the parsed object store
 + commit-slabs: remove realloc counter outside of slab struct
 + commit.c: allow parse_commit_buffer to handle arbitrary repositories
 + tag: allow parse_tag_buffer to handle arbitrary repositories
 + tag: allow lookup_tag to handle arbitrary repositories
 + commit: allow lookup_commit to handle arbitrary repositories
 + tree: allow lookup_tree to handle arbitrary repositories
 + blob: allow lookup_blob to handle arbitrary repositories
 + object: allow lookup_object to handle arbitrary repositories
 + object: allow object_as_type to handle arbitrary repositories
 + tag: add repository argument to deref_tag
 + tag: add repository argument to parse_tag_buffer
 + tag: add repository argument to lookup_tag
 + commit: add repository argument to get_cached_commit_buffer
 + commit: add repository argument to set_commit_buffer
 + commit: add repository argument to parse_commit_buffer
 + commit: add repository argument to lookup_commit
 + commit: add repository argument to lookup_commit_reference
 + commit: add repository argument to lookup_commit_reference_gently
 + tree: add repository argument to lookup_tree
 + blob: add repository argument to lookup_blob
 + object: add repository argument to object_as_type
 + object: add repository argument to parse_object_buffer
 + object: add repository argument to lookup_object
 + object: add repository argument to parse_object
 + Merge branch 'sb/object-store-grafts' into sb/object-store-lookup
 (this branch is used by ds/commit-graph-with-grafts, ds/reachable and jt/commit-graph-per-object-store.)

 lookup_commit_reference() and friends have been updated to find
 in-core object for a specific in-core repository instance.


* sg/httpd-test-unflake (2018-07-12) 3 commits
  (merged to 'next' on 2018-07-24 at b7df820256)
 + t/lib-httpd: avoid occasional failures when checking access.log
 + t/lib-httpd: add the strip_access_log() helper function
 + t5541: clean up truncating access log

 httpd tests saw occasional breakage due to the way its access log
 gets inspected by the tests, which has been updated to make them
 less flaky.


* tb/grep-only-matching (2018-07-09) 2 commits
  (merged to 'next' on 2018-07-24 at 7e878b9d95)
 + grep.c: teach 'git grep --only-matching'
 + grep.c: extract show_line_header()

 "git grep" learned the "--only-matching" option.

--------------------------------------------------
[New Topics]

* ab/fsck-transfer-updates (2018-07-27) 10 commits
 - fsck: test and document unknown fsck.<msg-id> values
 - fsck: add stress tests for fsck.skipList
 - fsck: test & document {fetch,receive}.fsck.* config fallback
 - fetch: implement fetch.fsck.*
 - transfer.fsckObjects tests: untangle confusing setup
 - config doc: elaborate on fetch.fsckObjects security
 - config doc: elaborate on what transfer.fsckObjects does
 - config doc: unify the description of fsck.* and receive.fsck.*
 - config doc: don't describe *.fetchObjects twice
 - receive.fsck.<msg-id> tests: remove dead code

 The test performed at the receiving end of "git push" to prevent
 bad objects from entering repository can be customized via
 receive.fsck.* configuration variables; we now have gained a
 counterpart to do the same on the "git fetch" side, with
 fetch.fsck.* configuration variables.

 Will merge to 'next'.


* ab/test-must-be-empty (2018-07-30) 1 commit
 - tests: make use of the test_must_be_empty function

 Test updates.

 Will merge to 'next'.


* ab/test-must-be-empty-for-master (2018-07-30) 1 commit
 - tests: make use of the test_must_be_empty function

 Test updates.

 Did anybody spot incorrect conversion in this yet?


* cb/p4-pre-submit-hook (2018-08-01) 1 commit
 - git-p4: add the `p4-pre-submit` hook

 "git p4 submit" learns to ask its own pre-submit hook if it should
 continue with submitting.

 Will merge to 'next'.


* es/rebase-i-author-script-fix (2018-07-31) 4 commits
 - sequencer: don't die() on bogus user-edited timestamp
 - sequencer: fix "rebase -i --root" corrupting author header timestamp
 - sequencer: fix "rebase -i --root" corrupting author header timezone
 - sequencer: fix "rebase -i --root" corrupting author header
 (this branch is used by pw/rebase-i-author-script-fix.)

 The "author-script" file "git rebase -i" creates got broken when
 we started to move the command away from shell script, which is
 getting fixed now.

 Will merge to 'next'.


* hn/highlight-sideband-keywords (2018-07-31) 1 commit
 - sideband: highlight keywords in remote output

 The sideband code learned to optionally paint selected keywords at
 the beginning of incoming lines on the receiving end.


* jn/subtree-test-fixes (2018-07-30) 2 commits
 - subtree test: simplify preparation of expected results
 - subtree test: add missing && to &&-chain

 Test fix.

 Will merge to 'next'.


* ms/http-proto-doc (2018-07-30) 1 commit
 - doc: fix want-capability separator

 Doc fix.

 Will merge to 'next'.


* nd/pack-objects-threading-doc (2018-07-30) 1 commit
 - pack-objects: document about thread synchronization

 Doc fix.

 Will merge to 'next'.


* sb/indent-heuristic-optim (2018-08-01) 1 commit
 - xdiff: reduce indent heuristic overhead

 "git diff --indent-heuristic" had a bad corner case performance.

 Will merge to 'next'.


* ab/fetch-nego (2018-08-01) 3 commits
 - fetch doc: cross-link two new negotiation options
 - negotiator: unknown fetch.negotiationAlgorithm should error out
 - Merge branch 'jt/fetch-nego-tip' into ab/fetch-nego

 Update to a few other topics.

 Will merge to 'next'.


* ab/fetch-tags-noclobber (2018-07-31) 10 commits
 - fetch: stop clobbering existing tags without --force
 - pull doc: fix a long-standing grammar error
 - fetch tests: add a test clobbering tag behavior
 - fetch tests: correct a comment "remove it" -> "remove them"
 - push doc: correct lies about how push refspecs work
 - push tests: assert re-pushing annotated tags
 - push tests: add more testing for forced tag pushing
 - push tests: fix logic error in "push" test assertion
 - push tests: remove redundant 'git push' invocation
 - fetch tests: change "Tag" test tag to "testTag"

 "git fetch" used to apply the same "fast-forward" rule and allow
 tags to move without "--force" option, which made little sense,
 which has been corrected.

 Expecting a reroll.
 cf. <xmqq4lgfcn5a.fsf@gitster-ct.c.googlers.com>
 cf. <xmqqzhy7b7v9.fsf@gitster-ct.c.googlers.com>


* bp/checkout-new-branch-optim (2018-07-31) 1 commit
 - checkout: optimize "git checkout -b <new_branch>"

 "git checkout -b newbranch [HEAD]" should not have to do as much as
 checking out a commit different from HEAD.  An attempt is made to
 optimize this special case.

 Waiting for review comments to be responded.
 cf. <CACsJy8DMEMsDnKZc65K-0EJcm2udXZ7OKY=xoFmX4COM0dSH=g@mail.gmail.com>


* es/mw-to-git-chain-fix (2018-07-31) 1 commit
 - mw-to-git/t9360: fix broken &&-chain

 Test fix.

 Will merge to 'next'.


* jk/merge-subtree-heuristics (2018-08-02) 1 commit
 - score_trees(): fix iteration over trees with missing entries

 The automatic tree-matching in "git merge -s subtree" was broken 5
 years ago and nobody has noticed since then, which is now fixed.

 Will merge to 'next'.


* jt/connectivity-check-after-unshallow (2018-08-01) 1 commit
 - fetch-pack: unify ref in and out param

 Recent update to the transport layer broke ref updates after "git
 fetch", which is now fixed.

 Will merge to 'next'.


* jt/refspec-dwim-precedence-fix (2018-08-02) 1 commit
 - remote: make refspec follow the same disambiguation rule as local refs

 "git fetch $there refs/heads/s" ought to fetch the tip of the
 branch 's', but when "refs/heads/refs/heads/s", i.e. a branch whose
 name is "refs/heads/s" exists at the same time, fetched that one
 instead by mistake.  This has been corrected to honor the usual
 disambiguation rules for abbreviated refnames.

 Will merge to 'next'.


* nd/clone-case-smashing-warning (2018-07-31) 1 commit
 - clone: report duplicate entries on case-insensitive filesystems

 Running "git clone" against a project that contain two files with
 pathnames that differ only in cases on a case insensitive
 filesystem would result in one of the files lost because the
 underlying filesystem is incapable of holding both at the same
 time.  An attempt is made to detect such a case and warn.

 Discussion getting petered out.
 Doing this portably and extending it to UTF-8 normalization issue
 HFS+ has would be costly.

 cf. <20180728095659.GA21450@sigill.intra.peff.net>
 cf. <xmqq1sbh7phx.fsf@gitster-ct.c.googlers.com>


* nd/unpack-trees-with-cache-tree (2018-07-31) 4 commits
 - unpack-trees: cheaper index update when walking by cache-tree
 - unpack-trees: reduce malloc in cache-tree walk
 - unpack-trees: optimize walking same trees with cache-tree
 - unpack-trees.c: add performance tracing

 The unpack_trees() API used in checking out a branch and merging
 walks one or more trees along with the index.  When the cache-tree
 in the index tells us that we are walking a tree whose flattened
 contents is known (i.e. matches a span in the index), as linearly
 scanning a span in the index is much more efficient than having to
 open tree objects recursively and listing their entries, the walk
 can be optimized, which is done in this topic.


* rs/remote-mv-leakfix (2018-08-01) 1 commit
 - remote: clear string_list after use in mv()

 Leakfix.

 Will merge to 'next'.


* sb/config-write-fix (2018-08-01) 3 commits
 - git-config: document accidental multi-line setting in deprecated syntax
 - config: fix case sensitive subsection names on writing
 - t1300: document current behavior of setting options

 Recent update to "git config" broke updating variable in a
 subsection, which has been corrected.

 Not quite?
 cf. <xmqq4lgc1rbv.fsf@gitster-ct.c.googlers.com>


* sb/range-diff-colors (2018-08-01) 9 commits
 - fixup! t3206: add color test for range-diff --dual-color
 - diff.c: rewrite emit_line_0 more understandably
 - diff.c: compute reverse locally in emit_line_0
 - diff: use emit_line_0 once per line
 - diff.c: add set_sign to emit_line_0
 - diff.c: reorder arguments for emit_line_ws_markup
 - diff.c: simplify caller of emit_line_0
 - t3206: add color test for range-diff --dual-color
 - test_decode_color: understand FAINT and ITALIC
 (this branch uses js/range-diff; is tangled with es/format-patch-rangediff.)


* sg/t1404-update-ref-test-timeout (2018-08-01) 1 commit
 - t1404: increase core.packedRefsTimeout to avoid occasional test failure

 An attempt to unflake a test a bit.


* sg/travis-retrieve-trash-upon-failure (2018-08-01) 1 commit
 - travis-ci: include the trash directories of failed tests in the trace log

 The Travis CI scripts were taught to ship back the test data from
 failed tests.

 Will merge to 'next'.


* jt/fetch-follow-fix (2018-08-01) 1 commit
 - fetch-pack: unify ref in and out param

 "git fetch" sometimes failed to update the remote-tracking refs,
 which has been corrected.

 Will merge to 'next'.


* ab/sha1dc (2018-08-02) 1 commit
 - sha1dc: update from upstream

 AIX portability update for SHADC hash, imported from upstream.

 Will merge to 'next'.


* es/want-color-fd-defensive (2018-08-02) 1 commit
 - color: protect against out-of-bounds array access/assignment

 Futureproofing a helper function that can easily misused.

 Will merge to 'next'.


* pw/rebase-i-author-script-fix (2018-08-02) 2 commits
 - sequencer: fix quoting in write_author_script
 - sequencer: handle errors in read_author_ident()
 (this branch uses es/rebase-i-author-script-fix.)

 Recent "git rebase -i" update started to write bogusly formatted
 author-script, with a matching broken reading code.  These are
 being fixed.

 Undecided.
 Is it the list consensus to favor this "with extra code, read the
 script written by bad writer" approach?


* rs/parse-opt-lithelp (2018-08-02) 6 commits
 - parse-options: automatically infer PARSE_OPT_LITERAL_ARGHELP
 - shortlog: correct option help for -w
 - send-pack: specify --force-with-lease argument help explicitly
 - pack-objects: specify --index-version argument help explicitly
 - difftool: remove angular brackets from argument help
 - add, update-index: fix --chmod argument help

 The parse-options machinery learned to refrain from enclosing
 placeholder string inside a "<bra" and "ket>" pair automatically
 without PARSE_OPT_LITERAL_ARGHELP.  Existing help text for option
 arguments that are not formatted correctly have been identified and
 fixed.

 Will merge to 'next'.

--------------------------------------------------
[Stalled]

* ma/wrapped-info (2018-05-28) 2 commits
 - usage: prefix all lines in `vreportf()`, not just the first
 - usage: extract `prefix_suffix_lines()` from `advise()`

 An attempt to help making multi-line messages fed to warning(),
 error(), and friends more easily translatable.

 Will discard and wait for a cleaned-up rewrite.
 cf. <20180529213957.GF7964@sigill.intra.peff.net>

* hn/bisect-first-parent (2018-04-21) 1 commit
 - bisect: create 'bisect_flags' parameter in find_bisection()

 Preliminary code update to allow passing more flags down the
 bisection codepath in the future.

 We do not add random code that does not have real users to our
 codebase, so let's have it wait until such a real code materializes
 before too long.


* av/fsmonitor-updates (2018-01-04) 6 commits
 - fsmonitor: use fsmonitor data in `git diff`
 - fsmonitor: remove debugging lines from t/t7519-status-fsmonitor.sh
 - fsmonitor: make output of test-dump-fsmonitor more concise
 - fsmonitor: update helper tool, now that flags are filled later
 - fsmonitor: stop inline'ing mark_fsmonitor_valid / _invalid
 - dir.c: update comments to match argument name

 Code clean-up on fsmonitor integration, plus optional utilization
 of the fsmonitor data in diff-files.

 Waiting for an update.
 cf. <alpine.DEB.2.21.1.1801042335130.32@MININT-6BKU6QN.europe.corp.microsoft.com>


* pb/bisect-helper-2 (2018-07-23) 8 commits
 - t6030: make various test to pass GETTEXT_POISON tests
 - bisect--helper: `bisect_start` shell function partially in C
 - bisect--helper: `get_terms` & `bisect_terms` shell function in C
 - bisect--helper: `bisect_next_check` shell function in C
 - bisect--helper: `check_and_set_terms` shell function in C
 - wrapper: move is_empty_file() and rename it as is_empty_or_missing_file()
 - bisect--helper: `bisect_write` shell function in C
 - bisect--helper: `bisect_reset` shell function in C

 Expecting a reroll.
 cf. <0102015f5e5ee171-f30f4868-886f-47a1-a4e4-b4936afc545d-000000@eu-west-1.amazonses.com>

 I just rebased the topic to a newer base as it did not build
 standalone with the base I originally queued the topic on, but
 otherwise there is no update to address any of the review comments
 in the thread above---we are still waiting for a reroll.


* jk/drop-ancient-curl (2017-08-09) 5 commits
 - http: #error on too-old curl
 - curl: remove ifdef'd code never used with curl >=7.19.4
 - http: drop support for curl < 7.19.4
 - http: drop support for curl < 7.16.0
 - http: drop support for curl < 7.11.1

 Some code in http.c that has bitrot is being removed.

 Expecting a reroll.


* mk/use-size-t-in-zlib (2017-08-10) 1 commit
 . zlib.c: use size_t for size

 The wrapper to call into zlib followed our long tradition to use
 "unsigned long" for sizes of regions in memory, which have been
 updated to use "size_t".

 Needs resurrecting by making sure the fix is good and still applies
 (or adjusted to today's codebase).

--------------------------------------------------
[Cooking]

* es/diff-color-moved-fix (2018-07-25) 1 commit
  (merged to 'next' on 2018-08-02 at 233bccfbfb)
 + diff: --color-moved: rename "dimmed_zebra" to "dimmed-zebra"

 One of the "diff --color-moved" mode "dimmed_zebra" that was named
 in an unusual way has been deprecated and replaced by
 "dimmed-zebra".

 Will merge to 'master'.


* pw/add-p-select (2018-07-26) 4 commits
 - add -p: optimize line selection for short hunks
 - add -p: allow line selection to be inverted
 - add -p: select modified lines correctly
 - add -p: select individual hunk lines

 "git add -p" interactive interface learned to let users choose
 individual added/removed lines to be used in the operation, instead
 of accepting or rejecting a whole hunk.

 Will merge to and cook in 'next'.

 I found the feature to be hard to explain, and may result in more
 end-user complaints, but let's see.


* mk/http-backend-content-length (2018-07-30) 4 commits
 - t5562: avoid non-portable "export FOO=bar" construct
 - http-backend: respect CONTENT_LENGTH for receive-pack
 - http-backend: respect CONTENT_LENGTH as specified by rfc3875
 - http-backend: cleanup writing to child process

 The http-backend (used for smart-http transport) used to slurp the
 whole input until EOF, without paying attention to CONTENT_LENGTH
 that is supplied in the environment and instead expecting the Web
 server to close the input stream.  This has been fixed.

 Will merge to 'next'.


* ds/commit-graph-with-grafts (2018-07-19) 8 commits
  (merged to 'next' on 2018-08-02 at 0ee624e329)
 + commit-graph: close_commit_graph before shallow walk
 + commit-graph: not compatible with uninitialized repo
 + commit-graph: not compatible with grafts
 + commit-graph: not compatible with replace objects
 + test-repository: properly init repo
 + commit-graph: update design document
 + refs.c: upgrade for_each_replace_ref to be a each_repo_ref_fn callback
 + refs.c: migrate internal ref iteration to pass thru repository argument

 The recently introduced commit-graph auxiliary data is incompatible
 with mechanisms such as replace & grafts that "breaks" immutable
 nature of the object reference relationship.  Disable optimizations
 based on its use (and updating existing commit-graph) when these
 incompatible features are in use in the repository.

 Will merge to 'master'.


* jk/core-use-replace-refs (2018-07-18) 3 commits
  (merged to 'next' on 2018-08-02 at 90fb6b1056)
 + add core.usereplacerefs config option
 + check_replace_refs: rename to read_replace_refs
 + check_replace_refs: fix outdated comment

 A new configuration variable core.usereplacerefs has been added,
 primarily to help server installations that want to ignore the
 replace mechanism altogether.

 Will merge to 'master'.


* nd/i18n (2018-07-23) 23 commits
  (merged to 'next' on 2018-08-02 at 904a22a5d1)
 + transport-helper.c: mark more strings for translation
 + transport.c: mark more strings for translation
 + sha1-file.c: mark more strings for translation
 + sequencer.c: mark more strings for translation
 + replace-object.c: mark more strings for translation
 + refspec.c: mark more strings for translation
 + refs.c: mark more strings for translation
 + pkt-line.c: mark more strings for translation
 + object.c: mark more strings for translation
 + exec-cmd.c: mark more strings for translation
 + environment.c: mark more strings for translation
 + dir.c: mark more strings for translation
 + convert.c: mark more strings for translation
 + connect.c: mark more strings for translation
 + config.c: mark more strings for translation
 + commit-graph.c: mark more strings for translation
 + builtin/replace.c: mark more strings for translation
 + builtin/pack-objects.c: mark more strings for translation
 + builtin/grep.c: mark strings for translation
 + builtin/config.c: mark more strings for translation
 + archive-zip.c: mark more strings for translation
 + archive-tar.c: mark more strings for translation
 + Update messages in preparation for i18n

 Many more strings are prepared for l10n.

 Will merge to 'master'.


* sb/histogram-less-memory (2018-07-23) 4 commits
  (merged to 'next' on 2018-08-02 at cfb02aa3b5)
 + xdiff/histogram: remove tail recursion
 + xdiff/xhistogram: move index allocation into find_lcs
 + xdiff/xhistogram: factor out memory cleanup into free_index()
 + xdiff/xhistogram: pass arguments directly to fall_back_to_classic_diff

 "git diff --histogram" had a bad memory usage pattern, which has
 been rearranged to reduce the peak usage.

 Will merge to 'master'.


* bb/make-developer-pedantic (2018-07-25) 1 commit
  (merged to 'next' on 2018-08-02 at c738a84b7e)
 + Makefile: add a DEVOPTS flag to get pedantic compilation

 "make DEVELOPER=1 DEVOPTS=pedantic" allows developers to compile
 with -pedantic option, which may catch more problematic program
 constructs and potential bugs.

 Will merge to 'master'.


* bw/clone-ref-prefixes (2018-07-20) 1 commit
  (merged to 'next' on 2018-08-02 at c8ad140ab0)
 + clone: send ref-prefixes when using protocol v2

 The wire-protocol v2 relies on the client to send "ref prefixes" to
 limit the bandwidth spent on the initial ref advertisement.  "git
 clone" when learned to speak v2 forgot to do so, which has been
 corrected.

 Will merge to 'master'.


* bw/fetch-pack-i18n (2018-07-23) 1 commit
  (merged to 'next' on 2018-08-02 at df72001755)
 + fetch-pack: mark die strings for translation

 i18n updates.

 Will merge to 'master'.


* bw/protocol-v2 (2018-07-24) 1 commit
  (merged to 'next' on 2018-08-02 at f4076b3e94)
 + pack-protocol: mention and point to docs for protocol v2

 Doc update.

 Will merge to 'master'.


* ds/reachable (2018-07-20) 18 commits
 - commit-reach: use can_all_from_reach
 - commit-reach: make can_all_from_reach... linear
 - commit-reach: replace ref_newer logic
 - test-reach: test commit_contains
 - test-reach: test can_all_from_reach_with_flags
 - test-reach: test reduce_heads
 - test-reach: test get_merge_bases_many
 - test-reach: test is_descendant_of
 - test-reach: test in_merge_bases
 - test-reach: create new test tool for ref_newer
 - commit-reach: move can_all_from_reach_with_flags
 - upload-pack: generalize commit date cutoff
 - upload-pack: refactor ok_to_give_up()
 - upload-pack: make reachable() more generic
 - commit-reach: move commit_contains from ref-filter
 - commit-reach: move ref_newer from remote.c
 - commit.h: remove method declarations
 - commit-reach: move walk methods from commit.c

 The code for computing history reachability has been shuffled,
 obtained a bunch of new tests to cover them, and then being
 improved.

 Will merge to and cook in 'next'.


* en/merge-recursive-skip-fix (2018-07-27) 2 commits
 - merge-recursive: preserve skip_worktree bit when necessary
 - t3507: add a testcase showing failure with sparse checkout

 When the sparse checkout feature is in use, "git cherry-pick" and
 other mergy operations lost the skip_worktree bit when a path that
 is excluded from checkout requires content level merge, which is
 resolved as the same as the HEAD version, without materializing the
 merge result in the working tree, which made the path appear as
 deleted.  This has been corrected by preserving the skip_worktree
 bit (and not materializing the file in the working tree).

 Will merge to 'next'.


* es/format-patch-interdiff (2018-07-23) 6 commits
 - format-patch: allow --interdiff to apply to a lone-patch
 - log-tree: show_log: make commentary block delimiting reusable
 - interdiff: teach show_interdiff() to indent interdiff
 - format-patch: teach --interdiff to respect -v/--reroll-count
 - format-patch: add --interdiff option to embed diff in cover letter
 - format-patch: allow additional generated content in make_cover_letter()
 (this branch is used by es/format-patch-rangediff.)

 "git format-patch" learned a new "--interdiff" option to explain
 the difference between this version and the previous atttempt in
 the cover letter (or after the tree-dashes as a comment).

 Stuck in review?
 cf. <CAPig+cSuYUYSPTuKx08wcmQM-G12_-W2T4BS07fA=6grM1b8Gw@mail.gmail.com>


* es/format-patch-rangediff (2018-07-30) 10 commits
 - format-patch: allow --range-diff to apply to a lone-patch
 - format-patch: add --creation-factor tweak for --range-diff
 - format-patch: teach --range-diff to respect -v/--reroll-count
 - format-patch: extend --range-diff to accept revision range
 - format-patch: add --range-diff option to embed diff in cover letter
 - range-diff: relieve callers of low-level configuration burden
 - range-diff: publish default creation factor
 - range-diff: respect diff_option.file rather than assuming 'stdout'
 - Merge branch 'es/format-patch-interdiff' into es/format-patch-rangediff
 - Merge branch 'js/range-diff' into es/format-patch-rangediff
 (this branch uses es/format-patch-interdiff and js/range-diff; is tangled with sb/range-diff-colors.)

 "git format-patch" learned a new "--range-diff" option to explain
 the difference between this version and the previous atttempt in
 the cover letter (or after the tree-dashes as a comment).

 Need to wait for the prereq topics to solidify a bit more.


* jk/banned-function (2018-07-26) 5 commits
 - banned.h: mark strncpy() as banned
 - banned.h: mark sprintf() as banned
 - banned.h: mark strcat() as banned
 - automatically ban strcpy()
 - Merge branch 'sb/blame-color' into jk/banned-function

 It is too easy to misuse system API functions such as strcat();
 these selected functions are now forbidden in this codebase and
 will cause a compilation failure.

 Will merge to 'next'.


* jk/size-t (2018-07-24) 6 commits
  (merged to 'next' on 2018-08-02 at 6f861e05f0)
 + strbuf_humanise: use unsigned variables
 + pass st.st_size as hint for strbuf_readlink()
 + strbuf_readlink: use ssize_t
 + strbuf: use size_t for length in intermediate variables
 + reencode_string: use size_t for string lengths
 + reencode_string: use st_add/st_mult helpers

 Code clean-up to use size_t/ssize_t when they are the right type.

 Will merge to 'master'.


* js/t7406-recursive-submodule-update-order-fix (2018-07-23) 1 commit
  (merged to 'next' on 2018-08-02 at 217ea36a37)
 + t7406: avoid failures solely due to timing issues

 Test fix.

 Will merge to 'master'.


* js/vscode (2018-07-30) 9 commits
 - vscode: let cSpell work on commit messages, too
 - vscode: add a dictionary for cSpell
 - vscode: use 8-space tabs, no trailing ws, etc for Git's source code
 - vscode: wrap commit messages at column 72 by default
 - vscode: only overwrite C/C++ settings
 - mingw: define WIN32 explicitly
 - cache.h: extract enum declaration from inside a struct declaration
 - vscode: hard-code a couple defines
 - contrib: add a script to initialize VS Code configuration

 Add a script (in contrib/) to help users of VSCode work better with
 our codebase.

 Will merge to 'next'.


* jt/tag-following-with-proto-v2-fix (2018-07-24) 2 commits
  (merged to 'next' on 2018-08-02 at d9eabdea95)
 + fetch: send "refs/tags/" prefix upon CLI refspecs
 + t5702: test fetch with multiple refspecs at a time

 The wire-protocol v2 relies on the client to send "ref prefixes" to
 limit the bandwidth spent on the initial ref advertisement.  "git
 fetch $remote branch:branch" that asks tags that point into the
 history leading to the "branch" automatically followed sent to
 narrow prefix and broke the tag following, which has been fixed.

 Will merge to 'master'.


* nd/pack-deltify-regression-fix (2018-07-23) 1 commit
  (merged to 'next' on 2018-08-02 at f3b2bf0fef)
 + pack-objects: fix performance issues on packing large deltas

 In a recent update in 2.18 era, "git pack-objects" started
 producing a larger than necessary packfiles by missing
 opportunities to use large deltas.

 Will cook in 'next'.


* sb/trailers-docfix (2018-07-20) 1 commit
  (merged to 'next' on 2018-08-02 at ba348fafcd)
 + Documentation/git-interpret-trailers: explain possible values

 Doc update.

 Will merge to 'master'.


* sg/coccicheck-updates (2018-07-23) 5 commits
  (merged to 'next' on 2018-08-02 at b5548ff3a9)
 + coccinelle: extract dedicated make target to clean Coccinelle's results
 + coccinelle: put sane filenames into output patches
 + coccinelle: exclude sha1dc source files from static analysis
 + coccinelle: use $(addsuffix) in 'coccicheck' make target
 + coccinelle: mark the 'coccicheck' make target as .PHONY

 Update the way we use Coccinelle to find out-of-style code that
 need to be modernised.

 Will merge to 'master'.


* sg/fast-import-dump-refs-on-checkpoint-fix (2018-07-20) 1 commit
  (merged to 'next' on 2018-08-02 at f5c05b5a2c)
 + t9300: wait for background fast-import process to die after killing it

 Test update.

 Will merge to 'master'.


* sg/travis-cocci-diagnose-failure (2018-07-23) 2 commits
  (merged to 'next' on 2018-08-02 at 54808a8778)
 + travis-ci: fail if Coccinelle static analysis found something to transform
 + travis-ci: run Coccinelle static analysis with two parallel jobs

 Update the way we run static analysis tool at TravisCI to make it
 easier to use its findings.

 Will merge to 'master'.


* ab/newhash-is-sha256 (2018-07-26) 2 commits
 - doc hash-function-transition: pick SHA-256 as NewHash
 - doc hash-function-transition: note the lack of a changelog

 Documentation update.

 Will merge to 'next'.


* bb/redecl-enum-fix (2018-07-26) 1 commit
 - packfile: ensure that enum object_type is defined

 Compilation fix.

 Will merge to 'next'.


* jh/structured-logging (2018-07-25) 25 commits
 - structured-logging: add config data facility
 - structured-logging: t0420 tests for interacitve child_summary
 - structured-logging: t0420 tests for child process detail events
 - structured-logging: add child process classification
 - structured-logging: add detail-events for child processes
 - structured-logging: add structured logging to remote-curl
 - structured-logging: t0420 tests for aux-data
 - structured-logging: add aux-data for size of sparse-checkout file
 - structured-logging: add aux-data for index size
 - structured-logging: add aux-data facility
 - structured-logging: t0420 tests for timers
 - structured-logging: add timer around preload_index
 - structured-logging: add timer around wt-status functions
 - structured-logging: add timer around do_write_index
 - structured-logging: add timer around do_read_index
 - structured-logging: add timer facility
 - structured-logging: add detail-event for lazy_init_name_hash
 - structured-logging: add detail-event facility
 - structured-logging: t0420 basic tests
 - structured-logging: set sub_command field for checkout command
 - structured-logging: set sub_command field for branch command
 - structured-logging: add session-id to log events
 - structured-logging: add structured logging framework
 - structured-logging: add STRUCTURED_LOGGING=1 to Makefile
 - structured-logging: design document
 (this branch uses jh/json-writer.)

 Will merge to 'next'.


* en/abort-df-conflict-fixes (2018-07-31) 2 commits
 - read-cache: fix directory/file conflict handling in read_index_unmerged()
 - t1015: demonstrate directory/file conflict recovery failures

 "git merge --abort" etc. did not clean things up properly when
 there were conflicted entries in certain order that are involved
 in D/F conflicts.  This has been corrected.

 Will merge to 'next'.


* hs/gpgsm (2018-07-20) 7 commits
  (merged to 'next' on 2018-08-02 at db28bffe4f)
 + gpg-interface t: extend the existing GPG tests with GPGSM
 + gpg-interface: introduce new signature format "x509" using gpgsm
 + gpg-interface: introduce new config to select per gpg format program
 + gpg-interface: do not hardcode the key string len anymore
 + gpg-interface: introduce an abstraction for multiple gpg formats
 + t/t7510: check the validation of the new config gpg.format
 + gpg-interface: add new config to select how to sign a commit

 Teach "git tag -s" etc. a few configuration varaibles (gpg.format
 that can be set to "openpgp" or "x509", and gpg.<format>.program
 that is used to specify what program to use to deal with the format)
 to allow x.509 certs with CMS via "gpgsm" to be used instead of
 openpgp via "gnupg".

 Will merge to 'master'.


* jn/gc-auto (2018-07-17) 3 commits
 - gc: do not return error for prior errors in daemonized mode
 - gc: exit with status 128 on failure
 - gc: improve handling of errors reading gc.log

 "gc --auto" ended up calling exit(-1) upon error, which has been
 corrected to use exit(1).  Also the error reporting behaviour when
 daemonized has been updated to exit with zero status when stopping
 due to a previously discovered error (which implies there is no
 point running gc to improve the situation); we used to exit with
 failure in such a case.

 Stuck in review?
 cf. <20180717201348.GD26218@sigill.intra.peff.net>


* sb/submodule-update-in-c (2018-07-18) 6 commits
 - submodule--helper: introduce new update-module-mode helper
 - builtin/submodule--helper: factor out method to update a single submodule
 - builtin/submodule--helper: store update_clone information in a struct
 - builtin/submodule--helper: factor out submodule updating
 - git-submodule.sh: rename unused variables
 - git-submodule.sh: align error reporting for update mode to use path

 "git submodule update" is getting rewritten piece-by-piece into C.

 Will merge to 'next'.


* sl/commit-dry-run-with-short-output-fix (2018-07-30) 4 commits
 . commit: fix exit code when doing a dry run
 . wt-status: teach wt_status_collect about merges in progress
 . wt-status: rename commitable to committable
 . t7501: add coverage for flags which imply dry runs

 "git commit --dry-run" gave a correct exit status even during a
 conflict resolution toward a merge, but it did not with the
 "--short" option, which has been corrected.

 Seems to break 7512, 3404 and 7060 in 'pu'.


* tg/rerere (2018-07-16) 11 commits
 - rerere: recalculate conflict ID when unresolved conflict is committed
 - rerere: teach rerere to handle nested conflicts
 - rerere: return strbuf from handle path
 - rerere: factor out handle_conflict function
 - rerere: only return whether a path has conflicts or not
 - rerere: fix crash when conflict goes unresolved
 - rerere: add documentation for conflict normalization
 - rerere: mark strings for translation
 - rerere: wrap paths in output in sq
 - rerere: lowercase error messages
 - rerere: unify error messages when read_cache fails

 Fixes to "git rerere" corner cases, especially when conflict
 markers cannot be parsed in the file.

 I am not sure about the "nested" stuff, though.


* jk/ui-color-always-to-auto (2018-07-18) 1 commit
  (merged to 'next' on 2018-08-02 at 1a054baf0e)
 + Documentation: fix --color option formatting

 Doc formatting fix.

 Will merge to 'master'.


* jh/json-writer (2018-07-16) 1 commit
  (merged to 'next' on 2018-08-02 at d841450c7d)
 + json_writer: new routines to create JSON data
 (this branch is used by jh/structured-logging.)

 Preparatory code to later add json output for telemetry data.

 Will merge to 'master'.


* ag/rebase-i-in-c (2018-07-31) 20 commits
 - rebase -i: move rebase--helper modes to rebase--interactive
 - rebase -i: remove git-rebase--interactive.sh
 - rebase--interactive2: rewrite the submodes of interactive rebase in C
 - rebase -i: implement the main part of interactive rebase as a builtin
 - rebase -i: rewrite init_basic_state() in C
 - rebase -i: rewrite write_basic_state() in C
 - rebase -i: rewrite the rest of init_revisions_and_shortrevisions() in C
 - rebase -i: implement the logic to initialize $revisions in C
 - rebase -i: remove unused modes and functions
 - rebase -i: rewrite complete_action() in C
 - t3404: todo list with commented-out commands only aborts
 - sequencer: change the way skip_unnecessary_picks() returns its result
 - sequencer: refactor append_todo_help() to write its message to a buffer
 - rebase -i: rewrite checkout_onto() in C
 - rebase -i: rewrite setup_reflog_action() in C
 - sequencer: add a new function to silence a command, except if it fails
 - rebase -i: rewrite the edit-todo functionality in C
 - editor: add a function to launch the sequence editor
 - rebase -i: rewrite append_todo_help() in C
 - sequencer: make two functions and an enum from sequencer.c public

 Rewrite of the remaining "rebase -i" machinery in C.

 Will merge to 'next'.


* js/range-diff (2018-07-30) 21 commits
 - range-diff: use dim/bold cues to improve dual color mode
 - range-diff: make --dual-color the default mode
 - range-diff: left-pad patch numbers
 - completion: support `git range-diff`
 - range-diff: populate the man page
 - range-diff --dual-color: fix bogus white-space warning
 - range-diff: offer to dual-color the diffs
 - diff: add an internal option to dual-color diffs of diffs
 - color: add the meta color GIT_COLOR_REVERSE
 - range-diff: use color for the commit pairs
 - range-diff: add tests
 - range-diff: do not show "function names" in hunk headers
 - range-diff: adjust the output of the commit pairs
 - range-diff: suppress the diff headers
 - range-diff: indent the diffs just like tbdiff
 - range-diff: right-trim commit messages
 - range-diff: also show the diff between patches
 - range-diff: improve the order of the shown commits
 - range-diff: first rudimentary implementation
 - Introduce `range-diff` to compare iterations of a topic branch
 - linear-assignment: a function to solve least-cost assignment problems
 (this branch is used by es/format-patch-rangediff and sb/range-diff-colors.)

 "git tbdiff" that lets us compare individual patches in two
 iterations of a topic has been rewritten and made into a built-in
 command.

 It seems there will another hopefully the final reroll coming.
 cf. <nycvar.QRO.7.76.6.1808011800570.71@tvgsbejvaqbjf.bet>


* lt/date-human (2018-07-09) 1 commit
 - Add 'human' date format

 A new date format "--date=human" that morphs its output depending
 on how far the time is from the current time has been introduced.
 "--date=auto" can be used to use this new format when the output is
 goint to the pager or to the terminal and otherwise the default
 format.


* ot/ref-filter-object-info (2018-07-17) 5 commits
 - ref-filter: use oid_object_info() to get object
 - ref-filter: merge get_obj and get_object
 - ref-filter: initialize eaten variable
 - ref-filter: fill empty fields with empty values
 - ref-filter: add info_source to valid_atom

 A few atoms like %(objecttype) and %(objectsize) in the format
 specifier of "for-each-ref --format=<format>" can be filled without
 getting the full contents of the object, but just with the object
 header.  These cases have been optimzied by calling
 oid_object_info() API.

 Will merge to 'next'.


* pk/rebase-in-c (2018-07-30) 3 commits
 - builtin/rebase: support running "git rebase <upstream>"
 - rebase: refactor common shell functions into their own file
 - rebase: start implementing it as a builtin

 Rewrite of the "rebase" machinery in C.

 Will merge to 'next'.


* jk/branch-l-1-repurpose (2018-06-22) 1 commit
 - branch: make "-l" a synonym for "--list"

 Updated plan to repurpose the "-l" option to "git branch".

 Will hold in 'pu' until jk/branch-l-0-deprecation progresses sufficiently.


* cc/remote-odb (2018-08-02) 9 commits
 - Documentation/config: add odb.<name>.promisorRemote
 - t0410: test fetching from many promisor remotes
 - Use odb.origin.partialclonefilter instead of core.partialclonefilter
 - Use remote_odb_get_direct() and has_remote_odb()
 - remote-odb: add remote_odb_reinit()
 - remote-odb: implement remote_odb_get_many_direct()
 - remote-odb: implement remote_odb_get_direct()
 - Add initial remote odb support
 - fetch-object: make functions return an error code

 Implement lazy fetches of missing objects to complement the
 experimental partial clone feature.

 I haven't seen much interest in this topic on list.  What's the
 doneness of this thing?

 I do not particularly mind adding code to support a niche feature
 as long as it is cleanly made and it is clear that the feature
 won't negatively affect those who do not use it, so a review from
 that point of view may also be appropriate.


* ds/multi-pack-index (2018-07-20) 23 commits
 - midx: clear midx on repack
 - packfile: skip loading index if in multi-pack-index
 - midx: prevent duplicate packfile loads
 - midx: use midx in approximate_object_count
 - midx: use existing midx when writing new one
 - midx: use midx in abbreviation calculations
 - midx: read objects from multi-pack-index
 - config: create core.multiPackIndex setting
 - midx: write object offsets
 - midx: write object id fanout chunk
 - midx: write object ids in a chunk
 - midx: sort and deduplicate objects from packfiles
 - midx: read pack names into array
 - multi-pack-index: write pack names in chunk
 - multi-pack-index: read packfile list
 - packfile: generalize pack directory list
 - t5319: expand test data
 - multi-pack-index: load into memory
 - midx: write header information to lockfile
 - multi-pack-index: add 'write' verb
 - multi-pack-index: add builtin
 - multi-pack-index: add format details
 - multi-pack-index: add design document

 When there are too many packfiles in a repository (which is not
 recommended), looking up an object in these would require
 consulting many pack .idx files; a new mechanism to have a single
 file that consolidates all of these .idx files is introduced.

 Will merge to and cook in 'next'.


--------------------------------------------------
[Discarded]

* am/sequencer-author-script-fix (2018-07-18) 1 commit
 . sequencer.c: terminate the last line of author-script properly

 The author-script that records the author information created by
 the sequencer machinery lacked the closing single quote on the last
 entry.

 Superseded by another topic.


* jc/push-cas-opt-comment (2018-08-01) 1 commit
 . push: comment on a funny unbalanced option help

 Code clarification.

 Superseded by another topic.

^ permalink raw reply	[relevance 1%]

* Re: [PATCH 2/2] sideband: highlight keywords in remote sideband output
  @ 2018-08-02 18:22  4%   ` Junio C Hamano
  2018-08-06 14:06  0%     ` Han-Wen Nienhuys
  0 siblings, 1 reply; 200+ results
From: Junio C Hamano @ 2018-08-02 18:22 UTC (permalink / raw)
  To: Han-Wen Nienhuys; +Cc: git

Han-Wen Nienhuys <hanwen@google.com> writes:

> The colorization is controlled with the config setting "color.remote".
> ...
> Finally, this solution is backwards compatible: many servers already
> prefix their messages with "error", and they will benefit from this
> change without requiring a server update. By contrast, a server-side
> solution would likely require plumbing the TERM variable through the
> git protocol, so it would require changes to both server and client.

Thanks; quite readable.

>
> Helped-by: Duy Nguyen <pclouds@gmail.com>
> Signed-off-by: Han-Wen Nienhuys <hanwen@google.com>
> ---
>  Documentation/config.txt            |   9 +++
>  help.c                              |   1 +
>  help.h                              |   1 +
>  sideband.c                          | 119 +++++++++++++++++++++++++---
>  t/t5409-colorize-remote-messages.sh |  47 +++++++++++
>  5 files changed, 168 insertions(+), 9 deletions(-)
>  create mode 100644 t/t5409-colorize-remote-messages.sh

I'll "chmod +x" while queuing.

	Side note: When all problems pointed out are "I'll fix it
	this way while queuing"-kind, and if you agree to the way I
	plan to fix them up, then just saying so is sufficient and
	you do not have to send a new version of the patch(es).

If your "make test" did not catch this as an error, then we may need
to fix t/Makefile, as it is supposed to run test-lint.

> diff --git a/Documentation/config.txt b/Documentation/config.txt
> index 43b2de7b5..0783323be 100644
> --- a/Documentation/config.txt
> +++ b/Documentation/config.txt
> @@ -1229,6 +1229,15 @@ color.push::
>  color.push.error::
>  	Use customized color for push errors.
>  
> +color.remote::
> +	A boolean to enable/disable colored remote output. If unset,
> +	then the value of `color.ui` is used (`auto` by default).

Nobody tells the end-users what "colored remote output" does;
arguably they can find it out themselves by enabling the feature and
observing remote messages, but that is not user friendly.

	When running commands like `git fetch` and `git push` that
	interact with a remote repository, certain keywords (see
	`color.remote.<slot>`) that appear at the beginning of a
	line of message from the remote end can be painted in color.

	This configuration variable is a boolean to enable/disable
	the feature.  If unset...
	
or something like that, perhaps?

You use `always` in your tests to a good effect, but what the value
means is not described here.

> +color.remote.<slot>::
> +	Use customized color for each remote keywords. `<slot>` may be

Isn't 'each' a singular, i.e. "for each remote keyword"?  If so I do
not mind dropping 's' myself while queuing.

> +	`hint`, `warning`, `success` or `error` which match the
> +	corresponding keyword.

We need to say that keywords are painted case insensitively
somewhere in the doc.  Either do that here, or in the updated
description of `color.remote`---I am not sure which one results in
more readable text offhand.

> diff --git a/sideband.c b/sideband.c
> index 325bf0e97..5c72db83c 100644
> --- a/sideband.c
> +++ b/sideband.c
> @@ -1,6 +1,103 @@
>  #include "cache.h"
> +#include "color.h"
> +#include "config.h"
>  #include "pkt-line.h"
>  #include "sideband.h"
> +#include "help.h"
> +
> +struct kwtable {
> +	/*
> +	 * We use keyword as config key so it can't contain funny chars like
> +	 * spaces. When we do that, we need a separate field for slot name in
> +	 * load_sideband_colors().
> +	 */
> +	const char *keyword;
> +	char color[COLOR_MAXLEN];
> +};
> +
> +static struct kwtable keywords[] = {
> +	{ "hint",	GIT_COLOR_YELLOW },
> +	{ "warning",	GIT_COLOR_BOLD_YELLOW },
> +	{ "success",	GIT_COLOR_BOLD_GREEN },
> +	{ "error",	GIT_COLOR_BOLD_RED },
> +};
> +
> +// Returns a color setting (GIT_COLOR_NEVER, etc).
> +static int use_sideband_colors(void)
> +{
> +	static int use_sideband_colors_cached = -1;
> +
> +	const char *key = "color.remote";
> +	struct strbuf sb = STRBUF_INIT;
> +	char *value;
> +	int i;
> +
> +	if (use_sideband_colors_cached >= 0)
> +		return use_sideband_colors_cached;
> +
> +	if (!git_config_get_string(key, &value))
> +		use_sideband_colors_cached = git_config_colorbool(key, value);
> +
> +	for (i = 0; i < ARRAY_SIZE(keywords); i++) {
> +		strbuf_reset(&sb);
> +		strbuf_addf(&sb, "%s.%s", key, keywords[i].keyword);
> +		if (git_config_get_string(sb.buf, &value))
> +			continue;
> +		if (color_parse(value, keywords[i].color))
> +			die(_("expecting a color: %s"), value);
> +	}
> +	strbuf_release(&sb);
> +	return use_sideband_colors_cached;
> +}
> +
> +void list_config_color_sideband_slots(struct string_list *list, const char *prefix)
> +{
> +	int i;
> +
> +	for (i = 0; i < ARRAY_SIZE(keywords); i++)
> +		list_config_item(list, prefix, keywords[i].keyword);
> +}
> +
> +/*
> + * Optionally highlight some keywords in remote output if they appear at the
> + * start of the line. This should be called for a single line only.
> + */
> +void maybe_colorize_sideband(struct strbuf *dest, const char *src, int n)
> +{
> +	int i;

In a block with a dozen more more lines, it is easier to have a
blank line between decls and the first statement, i.e. here.

> + 	if (!want_color_stderr(use_sideband_colors())) {

The above line is indented with SP followed by HT; don't.

> +		strbuf_add(dest, src, n);
> +		return;
> +	}
> +
> +	while (isspace(*src)) {
> +		strbuf_addch(dest, *src);
> +		src++;
> +		n--;
> +	}
> +
> +	for (i = 0; i < ARRAY_SIZE(keywords); i++) {
> +		struct kwtable* p = keywords + i;

	struct kwtable *p = keywords + i;

> +		int len = strlen(p->keyword);
> +                /*
> +                 * Match case insensitively, so we colorize output from existing
> +                 * servers regardless of the case that they use for their
> +                 * messages. We only highlight the word precisely, so
> +                 * "successful" stays uncolored.
> +                 */

Indent with tabs, not a run of spaces, i.e. 

		int len = strlen(p->keyword);
		/*
		 * Match case insensitively, so we colorize output from existing
		 * servers regardless of the case that they use for their
		 * messages. We only highlight the word precisely, so
		 * "successful" stays uncolored.
		 */
		if (!strncasecmp(p->keyword, src, len) && !isalnum(src[len])) {

The code in this function looked OK otherwise.

> diff --git a/t/t5409-colorize-remote-messages.sh b/t/t5409-colorize-remote-messages.sh
> new file mode 100644
> index 000000000..4e1bd421f
> --- /dev/null
> +++ b/t/t5409-colorize-remote-messages.sh
> @@ -0,0 +1,47 @@
> +#!/bin/sh
> +
> +test_description='remote messages are colorized on the client'
> +
> +. ./test-lib.sh
> +
> +test_expect_success 'setup' '
> +	mkdir .git/hooks &&

> +	cat << EOF > .git/hooks/update &&
> +#!/bin/sh
> +echo error: error
> +echo hint: hint
> +echo success: success
> +echo warning: warning
> +echo prefixerror: error
> +exit 0
> +EOF
> +	chmod +x .git/hooks/update &&

Use write_script, i.e. instead of all the above, say

	write_script .git/hooks/update <<-\EOF &&
	echo error: error
	echo hint: hint
	...
	exit 0
	EOF

so that you do not have to know or care the case where /bin/sh is
not the builder's preferred shell, etc.

Our tests are not written to demonstrate that our code works as
written.  It is to protect our code from getting broken by others
who may not share vision of the original author.  Make sure that you
cast what you care about in stone, e.g. include "echo ERROR: bad" or
something in the above to ensure that future updates to the code
will not turn the match into a case sensitive one without breaking
the test suite.

> +	echo 1 >file &&
> +	git add file &&
> +	git commit -m 1 &&
> +	git clone . child &&
> +	cd child &&
> +	echo 2 > file &&
> +	git commit -a -m 2

Don't chdir the whole testing environment like this.  Depending on
the success and failure in the middle of the above &&-chain, the
next test will start at an unexpected place, which is bad.

Instead, do something like

	git clone . child &&
	echo 2 >child/file &&
	git -C child commit -a -m 2

or

	git clone . child &&
	(
		cd child &&
		echo 2 >file &&
		git commit -a -m 2
	)

> +'
> +
> +test_expect_success 'push' '
> +	git -c color.remote=always push -f origin HEAD:refs/heads/newbranch 2>output &&
> +	test_decode_color <output >decoded &&
> +	grep "<BOLD;RED>error<RESET>:" decoded &&
> +	grep "<YELLOW>hint<RESET>:" decoded &&
> +	grep "<BOLD;GREEN>success<RESET>:" decoded &&
> +	grep "<BOLD;YELLOW>warning<RESET>:" decoded &&
> +	grep "prefixerror: error" decoded

A comment before this test (which covers both of these two) that
explains why many "grep" invocations are necessary, instead of a
comparison with a single multi-line expected result file.  I am
guessing that it is *not* because you cannot rely on the order of
these lines coming out from the update hook, but because the remote
output have lines other than what is given by the update hook and
we cannot afford to write them in the expected result file.

A more readable alternative might be to prepare the message that
come from the other side more parseable, e.g.

	write_script .git/hooks/update <<-\EOF &&
	echo 'BEGIN HOOK OUTPUT
	error: error
	Error: another error
	...
	END HOOK OUTPUT'
	EOF

and then

	sed -ne "/BEGIN HOOK OUTPUT/,/END HOOK OUTPUT/p" output |
	test_decode_color >decoded &&
	cat >expect <<-\EOF &&
	BEGIN HOOK OUTPUT
	<BOLD;RED>error<RESET>: error
	<BOLD;RED>Error<RESET>: another error
	...
	END HOOK OUTPUT
	EOF
	test_cmp expect decoded

Then we do not have to make readers puzzled why we are using bunch
of "grep" and do not explain with an extra comment.

Thanks.

^ permalink raw reply	[relevance 4%]

* Re: [PATCH/RFC] clone: report duplicate entries on case-insensitive filesystems
  2018-08-02 14:43  0%                 ` Duy Nguyen
@ 2018-08-02 16:27  0%                   ` Junio C Hamano
  2018-08-03 14:28  0%                   ` Torsten Bögershausen
  1 sibling, 0 replies; 200+ results
From: Junio C Hamano @ 2018-08-02 16:27 UTC (permalink / raw)
  To: Duy Nguyen
  Cc: Jeff King, Elijah Newren, Git Mailing List, Paweł Paruzel,
	brian m. carlson

Duy Nguyen <pclouds@gmail.com> writes:

>> But even if inum is unreliable, we should be able to use other
>> clues, perhaps the same set of fields we use for cached stat
>> matching optimization we use for "diff" plumbing commands, to
>> implement the error report.  The more important part of the idea is
>> that we already need to notice that we need to remove a path that is
>> in the working tree while doing the checkout, so the alternative
>> approach won't incur any extra cost for normal cases where the
>> project being checked out does not have two files whose pathnames
>> are only different in case (or checking out such an offending
>> project to a case sensitive filesytem, of course).
>>
>> So I guess it still _is_ workable.  Any takers?
>
> OK so we're going back to the original way of checking that we check
> out the different files on the same place (because fs is icase) and
> try to collect all paths for reporting, yes? I can give it another go
> (but of course if anybody else steps up, I'd very gladly hand this
> over)

Detect and report, definitely yes; I am not sure about collect all
(personally I am OK if we stopped at reporting "I tried to check out
X but your project tree has something else that is turned to X by
your pathname-smashing filesystem" without making it a requirement
to report what the other one that conflict with X is.  Of course,
reporting the other side _is_ nicer and I'd be happier if we can do
so without too much ugly code, but I do not think it is a hard
requirement.

Thanks.

^ permalink raw reply	[relevance 0%]

* Re: [PATCH/RFC] clone: report duplicate entries on case-insensitive filesystems
  2018-08-01 21:20  5%               ` Junio C Hamano
@ 2018-08-02 14:43  0%                 ` Duy Nguyen
  2018-08-02 16:27  0%                   ` Junio C Hamano
  2018-08-03 14:28  0%                   ` Torsten Bögershausen
  0 siblings, 2 replies; 200+ results
From: Duy Nguyen @ 2018-08-02 14:43 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Jeff King, Elijah Newren, Git Mailing List, Paweł Paruzel,
	brian m. carlson

On Wed, Aug 1, 2018 at 11:20 PM Junio C Hamano <gitster@pobox.com> wrote:
>
> Junio C Hamano <gitster@pobox.com> writes:
>
> > Jeff King <peff@peff.net> writes:
> >
> >>> Presumably we are already in an error codepath, so if it is
> >>> absolutely necessary, then we can issue a lstat() to grab the inum
> >>> for the path we are about to create, iterate over the previously
> >>> checked out paths issuing lstat() and see which one yields the same
> >>> inum, to find the one who is the culprit.
> >>
> >> Yes, this is the cleverness I was missing in my earlier response.
> >>
> >> So it seems do-able, and I like that this incurs no cost in the
> >> non-error case.
> >
> > Not so fast, unfortunately.
> >
> > I suspect that some filesystems do not give us inum that we can use
> > for that "identity" purpose, and they tend to be the ones with the
> > case smashing characteristics where we need this code in the error
> > path the most X-<.
>
> But even if inum is unreliable, we should be able to use other
> clues, perhaps the same set of fields we use for cached stat
> matching optimization we use for "diff" plumbing commands, to
> implement the error report.  The more important part of the idea is
> that we already need to notice that we need to remove a path that is
> in the working tree while doing the checkout, so the alternative
> approach won't incur any extra cost for normal cases where the
> project being checked out does not have two files whose pathnames
> are only different in case (or checking out such an offending
> project to a case sensitive filesytem, of course).
>
> So I guess it still _is_ workable.  Any takers?

OK so we're going back to the original way of checking that we check
out the different files on the same place (because fs is icase) and
try to collect all paths for reporting, yes? I can give it another go
(but of course if anybody else steps up, I'd very gladly hand this
over)
-- 
Duy

^ permalink raw reply	[relevance 0%]

* Re: [PATCH 2/3] config: fix case sensitive subsection names on writing
  2018-08-01 19:34 20%                   ` [PATCH 2/3] config: fix case sensitive subsection names on writing Stefan Beller
  2018-08-01 21:01  8%                     ` Ramsay Jones
@ 2018-08-01 22:51  7%                     ` Junio C Hamano
  2018-08-03  0:30  8%                       ` Stefan Beller
  1 sibling, 1 reply; 200+ results
From: Junio C Hamano @ 2018-08-01 22:51 UTC (permalink / raw)
  To: Stefan Beller; +Cc: bmwill, git, johannes.schindelin, peff

Stefan Beller <sbeller@google.com> writes:

> A use reported a submodule issue regarding strange case indentation
> issues, but it could be boiled down to the following test case:

Perhaps

s/use/user/
s/case indentation issues/section mix-up/

> ... However we do not have a test for writing out config correctly with
> case sensitive subsection names, which is why this went unnoticed in
> 6ae996f2acf (git_config_set: make use of the config parser's event
> stream, 2018-04-09)

s/unnoticed in \(.*04-09)\)/unnoticed when \1 broke it./

This is why I asked if the patch is a "FIX" for an issue introduced
by the cited commit.

> Unfortunately we have to make a distinction between old style configuration
> that looks like
>
>   [foo.Bar]
>         key = test
>
> and the new quoted style as seen above. The old style is documented as
> case-agnostic, hence we need to keep 'strncasecmp'; although the
> resulting setting for the old style config differs from the configuration.
> That will be fixed in a follow up patch.
>
> Reported-by: JP Sugarbroad <jpsugar@google.com>
> Signed-off-by: Stefan Beller <sbeller@google.com>
> ---
>  config.c          | 12 +++++++++++-
>  t/t1300-config.sh |  1 +
>  2 files changed, 12 insertions(+), 1 deletion(-)
>
> diff --git a/config.c b/config.c
> index 7968ef7566a..1d3bf9b5fc0 100644
> --- a/config.c
> +++ b/config.c
> @@ -37,6 +37,7 @@ struct config_source {
>  	int eof;
>  	struct strbuf value;
>  	struct strbuf var;
> +	unsigned section_name_old_dot_style : 1;
>  
>  	int (*do_fgetc)(struct config_source *c);
>  	int (*do_ungetc)(int c, struct config_source *conf);
> @@ -605,6 +606,7 @@ static int get_value(config_fn_t fn, void *data, struct strbuf *name)
>  
>  static int get_extended_base_var(struct strbuf *name, int c)
>  {
> +	cf->section_name_old_dot_style = 0;
>  	do {
>  		if (c == '\n')
>  			goto error_incomplete_line;
> @@ -641,6 +643,7 @@ static int get_extended_base_var(struct strbuf *name, int c)
>  
>  static int get_base_var(struct strbuf *name)
>  {
> +	cf->section_name_old_dot_style = 1;
>  	for (;;) {
>  		int c = get_next_char();
>  		if (cf->eof)

OK, let me rephrase.  The basic parse structure is that

 * upon seeing '[', we call get_base_var(), which stuffs the
   "section" (including subsection, if exists) in the strbuf.

 * get_base_var() upon seeing a space after "[section ", calls
   get_extended_base_var().  This space can never exist in an
   old-style three-level names, where it is spelled as
   "[section.subsection]".  This space cannot exist in two-level
   names, either.  The closing ']' is eaten by this function before
   it returns.

 * get_extended_base_var() grabs the "double quoted" subsection name
   and eats the closing ']' before it returns.

So you set the new bit (section_name_old_dot_style) at the beginning
of get_base_var(), i.e. declare that you assume we are reading old
style, but upon entering get_extended_base_var(), unset it, because
now you know we are parsing a modern style three-level name(s).

Feels quite sensible way to keep track of old/new styles.

When parsing two-level names, old-style bit is set, which we may
need to be careful, thoguh.

> @@ -2355,14 +2358,21 @@ static int store_aux_event(enum config_event_t type,
>  	store->parsed[store->parsed_nr].type = type;
>  
>  	if (type == CONFIG_EVENT_SECTION) {
> +		int (*cmpfn)(const char *, const char *, size_t);
> +
>  		if (cf->var.len < 2 || cf->var.buf[cf->var.len - 1] != '.')
>  			return error("invalid section name '%s'", cf->var.buf);
>  
> +		if (cf->section_name_old_dot_style)
> +			cmpfn = strncasecmp;
> +		else
> +			cmpfn = strncmp;
> +
>  		/* Is this the section we were looking for? */
>  		store->is_keys_section =
>  			store->parsed[store->parsed_nr].is_keys_section =
>  			cf->var.len - 1 == store->baselen &&
> -			!strncasecmp(cf->var.buf, store->key, store->baselen);
> +			!cmpfn(cf->var.buf, store->key, store->baselen);

OK.  Section names should still be case insensitive (only the case
sensitivity of subsection names is special), but presumably that's
already normalized by the caller so we do not have to worry when we
use strncmp()?  Can we add a test to demonstrate that it works
correctly?

Thanks.


^ permalink raw reply	[relevance 7%]

* Re: [PATCH 2/3] config: fix case sensitive subsection names on writing
  2018-08-01 21:01  8%                     ` Ramsay Jones
@ 2018-08-01 22:26  8%                       ` Junio C Hamano
  0 siblings, 0 replies; 200+ results
From: Junio C Hamano @ 2018-08-01 22:26 UTC (permalink / raw)
  To: Ramsay Jones; +Cc: Stefan Beller, bmwill, git, johannes.schindelin, peff

Ramsay Jones <ramsay@ramsayjones.plus.com> writes:

> On 01/08/18 20:34, Stefan Beller wrote:
>> A use reported a submodule issue regarding strange case indentation
>
> s/use/user/ ?

True.  Also s/indentation/something else/ ?

Insufficient proofreading, perhaps?


^ permalink raw reply	[relevance 8%]

* Re: [PATCH/RFC] clone: report duplicate entries on case-insensitive filesystems
  @ 2018-08-01 21:20  5%               ` Junio C Hamano
  2018-08-02 14:43  0%                 ` Duy Nguyen
  0 siblings, 1 reply; 200+ results
From: Junio C Hamano @ 2018-08-01 21:20 UTC (permalink / raw)
  To: Jeff King
  Cc: Elijah Newren, Nguyễn Thái Ngọc Duy,
	Git Mailing List, pawelparuzel95, brian m. carlson

Junio C Hamano <gitster@pobox.com> writes:

> Jeff King <peff@peff.net> writes:
>
>>> Presumably we are already in an error codepath, so if it is
>>> absolutely necessary, then we can issue a lstat() to grab the inum
>>> for the path we are about to create, iterate over the previously
>>> checked out paths issuing lstat() and see which one yields the same
>>> inum, to find the one who is the culprit.
>>
>> Yes, this is the cleverness I was missing in my earlier response.
>>
>> So it seems do-able, and I like that this incurs no cost in the
>> non-error case.
>
> Not so fast, unfortunately.  
>
> I suspect that some filesystems do not give us inum that we can use
> for that "identity" purpose, and they tend to be the ones with the
> case smashing characteristics where we need this code in the error
> path the most X-<.

But even if inum is unreliable, we should be able to use other
clues, perhaps the same set of fields we use for cached stat
matching optimization we use for "diff" plumbing commands, to
implement the error report.  The more important part of the idea is
that we already need to notice that we need to remove a path that is
in the working tree while doing the checkout, so the alternative
approach won't incur any extra cost for normal cases where the
project being checked out does not have two files whose pathnames
are only different in case (or checking out such an offending
project to a case sensitive filesytem, of course).

So I guess it still _is_ workable.  Any takers?

^ permalink raw reply	[relevance 5%]

* Re: [PATCH 2/3] config: fix case sensitive subsection names on writing
  2018-08-01 19:34 20%                   ` [PATCH 2/3] config: fix case sensitive subsection names on writing Stefan Beller
@ 2018-08-01 21:01  8%                     ` Ramsay Jones
  2018-08-01 22:26  8%                       ` Junio C Hamano
  2018-08-01 22:51  7%                     ` Junio C Hamano
  1 sibling, 1 reply; 200+ results
From: Ramsay Jones @ 2018-08-01 21:01 UTC (permalink / raw)
  To: Stefan Beller, gitster; +Cc: bmwill, git, johannes.schindelin, peff



On 01/08/18 20:34, Stefan Beller wrote:
> A use reported a submodule issue regarding strange case indentation

s/use/user/ ?

ATB,
Ramsay Jones


^ permalink raw reply	[relevance 8%]

* [PATCH 2/3] config: fix case sensitive subsection names on writing
  2018-08-01 19:34  4%                 ` [PATCH 0/3] sb/config-write-fix done without robbing Peter Stefan Beller
  2018-08-01 19:34  7%                   ` [PATCH 1/3] t1300: document current behavior of setting options Stefan Beller
@ 2018-08-01 19:34 20%                   ` Stefan Beller
  2018-08-01 21:01  8%                     ` Ramsay Jones
  2018-08-01 22:51  7%                     ` Junio C Hamano
  2018-08-03  0:34  7%                   ` [PATCH 0/3] Reroll of sb/config-write-fix Stefan Beller
  2 siblings, 2 replies; 200+ results
From: Stefan Beller @ 2018-08-01 19:34 UTC (permalink / raw)
  To: gitster; +Cc: bmwill, git, johannes.schindelin, peff, sbeller

A use reported a submodule issue regarding strange case indentation
issues, but it could be boiled down to the following test case:

  $ git init test  && cd test
  $ git config foo."Bar".key test
  $ git config foo."bar".key test
  $ tail -n 3 .git/config
  [foo "Bar"]
        key = test
        key = test

Sub sections are case sensitive and we have a test for correctly reading
them. However we do not have a test for writing out config correctly with
case sensitive subsection names, which is why this went unnoticed in
6ae996f2acf (git_config_set: make use of the config parser's event
stream, 2018-04-09)

Unfortunately we have to make a distinction between old style configuration
that looks like

  [foo.Bar]
        key = test

and the new quoted style as seen above. The old style is documented as
case-agnostic, hence we need to keep 'strncasecmp'; although the
resulting setting for the old style config differs from the configuration.
That will be fixed in a follow up patch.

Reported-by: JP Sugarbroad <jpsugar@google.com>
Signed-off-by: Stefan Beller <sbeller@google.com>
---
 config.c          | 12 +++++++++++-
 t/t1300-config.sh |  1 +
 2 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/config.c b/config.c
index 7968ef7566a..1d3bf9b5fc0 100644
--- a/config.c
+++ b/config.c
@@ -37,6 +37,7 @@ struct config_source {
 	int eof;
 	struct strbuf value;
 	struct strbuf var;
+	unsigned section_name_old_dot_style : 1;
 
 	int (*do_fgetc)(struct config_source *c);
 	int (*do_ungetc)(int c, struct config_source *conf);
@@ -605,6 +606,7 @@ static int get_value(config_fn_t fn, void *data, struct strbuf *name)
 
 static int get_extended_base_var(struct strbuf *name, int c)
 {
+	cf->section_name_old_dot_style = 0;
 	do {
 		if (c == '\n')
 			goto error_incomplete_line;
@@ -641,6 +643,7 @@ static int get_extended_base_var(struct strbuf *name, int c)
 
 static int get_base_var(struct strbuf *name)
 {
+	cf->section_name_old_dot_style = 1;
 	for (;;) {
 		int c = get_next_char();
 		if (cf->eof)
@@ -2355,14 +2358,21 @@ static int store_aux_event(enum config_event_t type,
 	store->parsed[store->parsed_nr].type = type;
 
 	if (type == CONFIG_EVENT_SECTION) {
+		int (*cmpfn)(const char *, const char *, size_t);
+
 		if (cf->var.len < 2 || cf->var.buf[cf->var.len - 1] != '.')
 			return error("invalid section name '%s'", cf->var.buf);
 
+		if (cf->section_name_old_dot_style)
+			cmpfn = strncasecmp;
+		else
+			cmpfn = strncmp;
+
 		/* Is this the section we were looking for? */
 		store->is_keys_section =
 			store->parsed[store->parsed_nr].is_keys_section =
 			cf->var.len - 1 == store->baselen &&
-			!strncasecmp(cf->var.buf, store->key, store->baselen);
+			!cmpfn(cf->var.buf, store->key, store->baselen);
 		if (store->is_keys_section) {
 			store->section_seen = 1;
 			ALLOC_GROW(store->seen, store->seen_nr + 1,
diff --git a/t/t1300-config.sh b/t/t1300-config.sh
index ced13012409..a93f966f128 100755
--- a/t/t1300-config.sh
+++ b/t/t1300-config.sh
@@ -1290,6 +1290,7 @@ test_expect_success 'setting different case sensitive subsections ' '
 		Qc = v2
 		[d "e"]
 		f = v1
+		[d "E"]
 		Qf = v2
 	EOF
 	# exact match
-- 
2.18.0.132.g195c49a2227


^ permalink raw reply related	[relevance 20%]

* [PATCH 1/3] t1300: document current behavior of setting options
  2018-08-01 19:34  4%                 ` [PATCH 0/3] sb/config-write-fix done without robbing Peter Stefan Beller
@ 2018-08-01 19:34  7%                   ` Stefan Beller
  2018-08-01 19:34 20%                   ` [PATCH 2/3] config: fix case sensitive subsection names on writing Stefan Beller
  2018-08-03  0:34  7%                   ` [PATCH 0/3] Reroll of sb/config-write-fix Stefan Beller
  2 siblings, 0 replies; 200+ results
From: Stefan Beller @ 2018-08-01 19:34 UTC (permalink / raw)
  To: gitster; +Cc: bmwill, git, johannes.schindelin, peff, sbeller

This documents current behavior of the config machinery, when changing
the value of some settings. This patch just serves to provide a baseline
for the follow up that will fix some issues with the current behavior.

Signed-off-by: Stefan Beller <sbeller@google.com>
---
 t/t1300-config.sh | 86 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 86 insertions(+)

diff --git a/t/t1300-config.sh b/t/t1300-config.sh
index 03c223708eb..ced13012409 100755
--- a/t/t1300-config.sh
+++ b/t/t1300-config.sh
@@ -1218,6 +1218,92 @@ test_expect_success 'last one wins: three level vars' '
 	test_cmp expect actual
 '
 
+test_expect_success 'old-fashioned settings are case insensitive' '
+	test_when_finished "rm -f testConfig testConfig_expect testConfig_actual" &&
+
+	cat >testConfig_actual <<-EOF &&
+		[V.A]
+		r = value1
+	EOF
+	q_to_tab >testConfig_expect <<-EOF &&
+		[V.A]
+		Qr = value2
+	EOF
+	git config -f testConfig_actual "v.a.r" value2 &&
+	test_cmp testConfig_expect testConfig_actual &&
+
+	cat >testConfig_actual <<-EOF &&
+		[V.A]
+		r = value1
+	EOF
+	q_to_tab >testConfig_expect <<-EOF &&
+		[V.A]
+		QR = value2
+	EOF
+	git config -f testConfig_actual "V.a.R" value2 &&
+	test_cmp testConfig_expect testConfig_actual &&
+
+	cat >testConfig_actual <<-EOF &&
+		[V.A]
+		r = value1
+	EOF
+	q_to_tab >testConfig_expect <<-EOF &&
+		[V.A]
+		r = value1
+		Qr = value2
+	EOF
+	git config -f testConfig_actual "V.A.r" value2 &&
+	test_cmp testConfig_expect testConfig_actual &&
+
+	cat >testConfig_actual <<-EOF &&
+		[V.A]
+		r = value1
+	EOF
+	q_to_tab >testConfig_expect <<-EOF &&
+		[V.A]
+		r = value1
+		Qr = value2
+	EOF
+	git config -f testConfig_actual "v.A.r" value2 &&
+	test_cmp testConfig_expect testConfig_actual
+'
+
+test_expect_success 'setting different case sensitive subsections ' '
+	test_when_finished "rm -f testConfig testConfig_expect testConfig_actual" &&
+
+	cat >testConfig_actual <<-EOF &&
+		[V "A"]
+		R = v1
+		[K "E"]
+		Y = v1
+		[a "b"]
+		c = v1
+		[d "e"]
+		f = v1
+	EOF
+	q_to_tab >testConfig_expect <<-EOF &&
+		[V "A"]
+		Qr = v2
+		[K "E"]
+		Qy = v2
+		[a "b"]
+		Qc = v2
+		[d "e"]
+		f = v1
+		Qf = v2
+	EOF
+	# exact match
+	git config -f testConfig_actual a.b.c v2 &&
+	# match section and subsection, key is cased differently.
+	git config -f testConfig_actual K.E.y v2 &&
+	# section and key are matched case insensitive, but subsection needs
+	# to match; When writing out new values only the key is adjusted
+	git config -f testConfig_actual v.A.r v2 &&
+	# subsection is not matched:
+	git config -f testConfig_actual d.E.f v2 &&
+	test_cmp testConfig_expect testConfig_actual
+'
+
 for VAR in a .a a. a.0b a."b c". a."b c".0d
 do
 	test_expect_success "git -c $VAR=VAL rejects invalid '$VAR'" '
-- 
2.18.0.132.g195c49a2227


^ permalink raw reply related	[relevance 7%]

* [PATCH 0/3] sb/config-write-fix done without robbing Peter
  2018-07-31 15:16  8%               ` [PATCH 0/3] " Junio C Hamano
@ 2018-08-01 19:34  4%                 ` Stefan Beller
  2018-08-01 19:34  7%                   ` [PATCH 1/3] t1300: document current behavior of setting options Stefan Beller
                                     ` (2 more replies)
  0 siblings, 3 replies; 200+ results
From: Stefan Beller @ 2018-08-01 19:34 UTC (permalink / raw)
  To: gitster; +Cc: bmwill, git, johannes.schindelin, peff, sbeller

> Am I correct to understand that this patch is a "FIX" for breakage
> introduced by that commit?  The phrasing is not helping me to pick
> a good base to queue these patches on.

Please pick 4f4d0b42bae (Merge branch 'js/empty-config-section-fix', 2018-05-08)
as the base of this new series (am needs -3 to apply), although I developed this
series on origin/master.

> Even though I hate to rob Peter to pay Paul (or vice versa)

Yeah me, too. Here is a proper fix (i.e. only pay Paul, without the robbery),
and a documentation of the second bug that we discovered.

The first patch stands as is unchanged, and the second and third patch
are different enough that range-diff doesn't want to show a diff.

Thanks,
Stefan

Stefan Beller (3):
  t1300: document current behavior of setting options
  config: fix case sensitive subsection names on writing
  git-config: document accidental multi-line setting in deprecated
    syntax

 Documentation/git-config.txt | 21 +++++++++
 config.c                     | 12 ++++-
 t/t1300-config.sh            | 87 ++++++++++++++++++++++++++++++++++++
 3 files changed, 119 insertions(+), 1 deletion(-)

^ permalink raw reply	[relevance 4%]

* Re: [PATCH/RFC] clone: report duplicate entries on case-insensitive filesystems
  @ 2018-08-01 15:16  5%     ` Duy Nguyen
  0 siblings, 0 replies; 200+ results
From: Duy Nguyen @ 2018-08-01 15:16 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Jeff King, Git Mailing List, Paweł Paruzel, brian m. carlson

On Tue, Jul 31, 2018 at 9:13 PM Junio C Hamano <gitster@pobox.com> wrote:
>
> Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:
>
> > Another thing we probably should do is catch in "git checkout" too,
> > not just "git clone" since your linux/unix colleage colleague may
> > accidentally add some files that your mac/windows machine is not very
> > happy with.
>
> Then you would catch it not in checkout but in add, no?

No because the guy who added these may have done it on a
case-sensitive filesystem. Only later when his friend fetches new
changes on a case-insensitive filesytem, the problem becomes real. If
in this scenario, core.ignore is enforced on all machines, then yes we
could catch it at "git add" (and we should already do that or we have
a bug). At least in open source project setting, I think enforcing
core.ignore will not work.
-- 
Duy

^ permalink raw reply	[relevance 5%]

* Re: [PATCH 2/3] config: fix case sensitive subsection names on writing
  2018-07-30 23:04 24%               ` [PATCH 2/3] config: fix case sensitive subsection names on writing Stefan Beller
@ 2018-07-31 20:16  8%                 ` Junio C Hamano
  0 siblings, 0 replies; 200+ results
From: Junio C Hamano @ 2018-07-31 20:16 UTC (permalink / raw)
  To: Stefan Beller; +Cc: johannes.schindelin, bmwill, git, peff

Stefan Beller <sbeller@google.com> writes:

> A use reported a submodule issue regarding strange case indentation
> issues, but it could be boiled down to the following test case:
>
>   $ git init test  && cd test
>   $ git config foo."Bar".key test
>   $ git config foo."bar".key test
>   $ tail -n 3 .git/config
>   [foo "Bar"]
>         key = test
>         key = test
>
> Sub sections are case sensitive and we have a test for correctly reading
> them. However we do not have a test for writing out config correctly with
> case sensitive subsection names, which is why this went unnoticed in
> 6ae996f2acf (git_config_set: make use of the config parser's event
> stream, 2018-04-09)

Am I correct to understand that this patch is a "FIX" for breakage
introduced by that commit?  The phrasing is not helping me to pick
a good base to queue these patches on.

Thanks.

^ permalink raw reply	[relevance 8%]

* Re: [PATCH/RFC] clone: report duplicate entries on case-insensitive filesystems
  2018-07-30 15:27  4% ` [PATCH/RFC] clone: report duplicate entries on case-insensitive filesystems Nguyễn Thái Ngọc Duy
  2018-07-31 18:23  0%   ` Torsten Bögershausen
@ 2018-07-31 18:44  0%   ` Elijah Newren
      2018-08-07 19:01  3%   ` [PATCH v2] " Nguyễn Thái Ngọc Duy
  3 siblings, 1 reply; 200+ results
From: Elijah Newren @ 2018-07-31 18:44 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy
  Cc: Jeff King, Git Mailing List, pawelparuzel95, brian m. carlson

On Mon, Jul 30, 2018 at 8:27 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
> Paths that only differ in case work fine in a case-sensitive
> filesystems, but if those repos are cloned in a case-insensitive one,
> you'll get problems. The first thing to notice is "git status" will
> never be clean with no indication what's exactly is "dirty".

"what" rather than "what's"?

> This patch helps the situation a bit by pointing out the problem at
> clone time. I have not suggested any way to work around or fix this
> problem. But I guess we could probably have a section in
> Documentation/ dedicated to this problem and point there instead of
> a long advice in this warning.
>
> Another thing we probably should do is catch in "git checkout" too,
> not just "git clone" since your linux/unix colleage colleague may

drop "colleage", keep "colleague"?

> accidentally add some files that your mac/windows machine is not very
> happy with. But then there's another problem, once the problem is
> known, we probably should stop spamming this warning at every
> checkout, but how?

Good questions.  I have no answers.

>
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---
>  builtin/clone.c | 41 +++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 41 insertions(+)
>
> diff --git a/builtin/clone.c b/builtin/clone.c
> index 5c439f1394..32738c2737 100644
> --- a/builtin/clone.c
> +++ b/builtin/clone.c
> @@ -711,6 +711,33 @@ static void update_head(const struct ref *our, const struct ref *remote,
>         }
>  }
>
> +static void find_duplicate_icase_entries(struct index_state *istate,
> +                                        struct string_list *dup)
> +{
> +       struct string_list list = STRING_LIST_INIT_NODUP;
> +       int i;
> +
> +       for (i = 0; i < istate->cache_nr; i++)
> +               string_list_append(&list, istate->cache[i]->name);
> +
> +       list.cmp = fspathcmp;
> +       string_list_sort(&list);

So, you sort the list by fspathcmp to get the entries that differ in
case only next to each other.  Makes sense...

> +
> +       for (i = 1; i < list.nr; i++) {
> +               const char *cur = list.items[i].string;
> +               const char *prev = list.items[i - 1].string;
> +
> +               if (dup->nr &&
> +                   !fspathcmp(cur, dup->items[dup->nr - 1].string)) {
> +                       string_list_append(dup, cur);

If we have at least one duplicate in dup (and currently we'd have to
have at least two), and we now hit yet another (i.e. a third or
fourth...) way of spelling the same path, then we add it.

> +               } else if (!fspathcmp(cur, prev)) {
> +                       string_list_append(dup, prev);
> +                       string_list_append(dup, cur);
> +               }

...otherwise, if we find a duplicate, we add both spellings to the dup list.

> +       }
> +       string_list_clear(&list, 0);
> +}
> +
>  static int checkout(int submodule_progress)
>  {
>         struct object_id oid;
> @@ -761,6 +788,20 @@ static int checkout(int submodule_progress)
>         if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
>                 die(_("unable to write new index file"));
>
> +       if (ignore_case) {
> +               struct string_list dup = STRING_LIST_INIT_DUP;
> +               int i;
> +
> +               find_duplicate_icase_entries(&the_index, &dup);
> +               if (dup.nr) {
> +                       warning(_("the following paths in this repository only differ in case and will\n"

Perhaps I'm being excessively pedantic, but what if there are multiple
pairs of paths differing in case?  E.g. if someone has readme.txt,
README.txt, foo.txt, and FOO.txt, you'll list all four files but
readme.txt and foo.txt do not only differ in case.

Maybe something like "...only differ in case from another path and
will... " or is that too verbose and annoying?

> +                                 "cause problems because you have cloned it on an case-insensitive filesytem:\n"));
> +                       for (i = 0; i < dup.nr; i++)
> +                               fprintf(stderr, "\t%s\n", dup.items[i].string);
> +               }
> +               string_list_clear(&dup, 0);
> +       }
> +
>         err |= run_hook_le(NULL, "post-checkout", sha1_to_hex(null_sha1),
>                            oid_to_hex(&oid), "1", NULL);

Is it worth attempting to also warn about paths that only differ in
UTF-normalization on relevant MacOS systems?

^ permalink raw reply	[relevance 0%]

* Re: [PATCH/RFC] clone: report duplicate entries on case-insensitive filesystems
  2018-07-30 15:27  4% ` [PATCH/RFC] clone: report duplicate entries on case-insensitive filesystems Nguyễn Thái Ngọc Duy
@ 2018-07-31 18:23  0%   ` Torsten Bögershausen
  2018-07-31 18:44  0%   ` Elijah Newren
                     ` (2 subsequent siblings)
  3 siblings, 0 replies; 200+ results
From: Torsten Bögershausen @ 2018-07-31 18:23 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy; +Cc: peff, git, pawelparuzel95, sandals

On Mon, Jul 30, 2018 at 05:27:55PM +0200, Nguyễn Thái Ngọc Duy wrote:
> Paths that only differ in case work fine in a case-sensitive
> filesystems, but if those repos are cloned in a case-insensitive one,
> you'll get problems. The first thing to notice is "git status" will
> never be clean with no indication what's exactly is "dirty".
> 
> This patch helps the situation a bit by pointing out the problem at
> clone time. I have not suggested any way to work around or fix this
> problem. But I guess we could probably have a section in
> Documentation/ dedicated to this problem and point there instead of
> a long advice in this warning.
> 
> Another thing we probably should do is catch in "git checkout" too,
> not just "git clone" since your linux/unix colleage colleague may
> accidentally add some files that your mac/windows machine is not very
> happy with. But then there's another problem, once the problem is
> known, we probably should stop spamming this warning at every
> checkout, but how?
> 
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---
>  builtin/clone.c | 41 +++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 41 insertions(+)
> 
> diff --git a/builtin/clone.c b/builtin/clone.c
> index 5c439f1394..32738c2737 100644
> --- a/builtin/clone.c
> +++ b/builtin/clone.c
> @@ -711,6 +711,33 @@ static void update_head(const struct ref *our, const struct ref *remote,
>  	}
>  }
>  
> +static void find_duplicate_icase_entries(struct index_state *istate,
> +					 struct string_list *dup)
> +{
> +	struct string_list list = STRING_LIST_INIT_NODUP;
> +	int i;
> +
> +	for (i = 0; i < istate->cache_nr; i++)
> +		string_list_append(&list, istate->cache[i]->name);
> +
> +	list.cmp = fspathcmp;
> +	string_list_sort(&list);
> +
> +	for (i = 1; i < list.nr; i++) {
> +		const char *cur = list.items[i].string;
> +		const char *prev = list.items[i - 1].string;
> +
> +		if (dup->nr &&
> +		    !fspathcmp(cur, dup->items[dup->nr - 1].string)) {
> +			string_list_append(dup, cur);
> +		} else if (!fspathcmp(cur, prev)) {
> +			string_list_append(dup, prev);
> +			string_list_append(dup, cur);
> +		}
> +	}
> +	string_list_clear(&list, 0);
> +}
> +
>  static int checkout(int submodule_progress)
>  {
>  	struct object_id oid;
> @@ -761,6 +788,20 @@ static int checkout(int submodule_progress)
>  	if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
>  		die(_("unable to write new index file"));
>  
> +	if (ignore_case) {
> +		struct string_list dup = STRING_LIST_INIT_DUP;
> +		int i;
> +
> +		find_duplicate_icase_entries(&the_index, &dup);
> +		if (dup.nr) {
> +			warning(_("the following paths in this repository only differ in case and will\n"
> +				  "cause problems because you have cloned it on an case-insensitive filesytem:\n"));

Thanks for the patch.
I wonder if we can tell the users more about the "problems"
and how to avoid them, or to live with them.

This is more loud thinking:

"The following paths only differ in case\n"
"One a case-insensitive file system only one at a time can be present\n"
"You may rename one like this:\n"
"git checkout <file> && git mv <file> <file>.1\n"

> +				fprintf(stderr, "\t%s\n", dup.items[i].string);

Another question:
Do we need any quote_path() here ?
(This may be overkill, since typically the repos with conflicting names
only use ASCII.)

> +		}
> +		string_list_clear(&dup, 0);
> +	}
> +
>  	err |= run_hook_le(NULL, "post-checkout", sha1_to_hex(null_sha1),
>  			   oid_to_hex(&oid), "1", NULL);
>  
> -- 
> 2.18.0.656.gda699b98b3
> 

^ permalink raw reply	[relevance 0%]

* [GSoC][PATCH v5 12/20] rebase -i: remove unused modes and functions
  @ 2018-07-31 17:59  6%       ` Alban Gruin
    1 sibling, 0 replies; 200+ results
From: Alban Gruin @ 2018-07-31 17:59 UTC (permalink / raw)
  To: git
  Cc: Stefan Beller, Christian Couder, Pratik Karki,
	Johannes Schindelin, phillip.wood, gitster, Alban Gruin

This removes the modes `--skip-unnecessary-picks`, `--append-todo-help`,
and `--checkout-onto` from rebase--helper.c, the functions of
git-rebase--interactive.sh that were rendered useless by the rewrite of
complete_action(), and append_todo_help_to_file() from
rebase-interactive.c.

skip_unnecessary_picks() and checkout_onto() becomes static, as they are
only used inside of the sequencer.

Signed-off-by: Alban Gruin <alban.gruin@gmail.com>
---
No changes since v4.

 builtin/rebase--helper.c   | 23 ++----------------
 git-rebase--interactive.sh | 50 --------------------------------------
 rebase-interactive.c       | 22 -----------------
 rebase-interactive.h       |  1 -
 sequencer.c                |  8 +++---
 sequencer.h                |  4 ---
 6 files changed, 6 insertions(+), 102 deletions(-)

diff --git a/builtin/rebase--helper.c b/builtin/rebase--helper.c
index d7fa5a5062..6085527b2b 100644
--- a/builtin/rebase--helper.c
+++ b/builtin/rebase--helper.c
@@ -17,9 +17,8 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
 	int abbreviate_commands = 0, rebase_cousins = -1;
 	enum {
 		CONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_OIDS, EXPAND_OIDS,
-		CHECK_TODO_LIST, SKIP_UNNECESSARY_PICKS, REARRANGE_SQUASH,
-		ADD_EXEC, APPEND_TODO_HELP, EDIT_TODO, PREPARE_BRANCH,
-		CHECKOUT_ONTO, COMPLETE_ACTION
+		CHECK_TODO_LIST, REARRANGE_SQUASH, ADD_EXEC, EDIT_TODO, PREPARE_BRANCH,
+		COMPLETE_ACTION
 	} command = 0;
 	struct option options[] = {
 		OPT_BOOL(0, "ff", &opts.allow_ff, N_("allow fast-forward")),
@@ -44,21 +43,15 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
 			N_("expand commit ids in the todo list"), EXPAND_OIDS),
 		OPT_CMDMODE(0, "check-todo-list", &command,
 			N_("check the todo list"), CHECK_TODO_LIST),
-		OPT_CMDMODE(0, "skip-unnecessary-picks", &command,
-			N_("skip unnecessary picks"), SKIP_UNNECESSARY_PICKS),
 		OPT_CMDMODE(0, "rearrange-squash", &command,
 			N_("rearrange fixup/squash lines"), REARRANGE_SQUASH),
 		OPT_CMDMODE(0, "add-exec-commands", &command,
 			N_("insert exec commands in todo list"), ADD_EXEC),
-		OPT_CMDMODE(0, "append-todo-help", &command,
-			    N_("insert the help in the todo list"), APPEND_TODO_HELP),
 		OPT_CMDMODE(0, "edit-todo", &command,
 			    N_("edit the todo list during an interactive rebase"),
 			    EDIT_TODO),
 		OPT_CMDMODE(0, "prepare-branch", &command,
 			    N_("prepare the branch to be rebased"), PREPARE_BRANCH),
-		OPT_CMDMODE(0, "checkout-onto", &command,
-			    N_("checkout a commit"), CHECKOUT_ONTO),
 		OPT_CMDMODE(0, "complete-action", &command,
 			    N_("complete the action"), COMPLETE_ACTION),
 		OPT_END()
@@ -94,26 +87,14 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
 		return !!transform_todos(flags);
 	if (command == CHECK_TODO_LIST && argc == 1)
 		return !!check_todo_list();
-	if (command == SKIP_UNNECESSARY_PICKS && argc == 1) {
-		struct object_id oid;
-		int ret = skip_unnecessary_picks(&oid);
-
-		if (!ret)
-			puts(oid_to_hex(&oid));
-		return !!ret;
-	}
 	if (command == REARRANGE_SQUASH && argc == 1)
 		return !!rearrange_squash();
 	if (command == ADD_EXEC && argc == 2)
 		return !!sequencer_add_exec_commands(argv[1]);
-	if (command == APPEND_TODO_HELP && argc == 1)
-		return !!append_todo_help_to_file(0, keep_empty);
 	if (command == EDIT_TODO && argc == 1)
 		return !!edit_todo_list(flags);
 	if (command == PREPARE_BRANCH && argc == 2)
 		return !!prepare_branch_to_be_rebased(&opts, argv[1]);
-	if (command == CHECKOUT_ONTO && argc == 4)
-		return !!checkout_onto(&opts, argv[1], argv[2], argv[3]);
 	if (command == COMPLETE_ACTION && argc == 6)
 		return !!complete_action(&opts, flags, argv[1], argv[2], argv[3],
 					 argv[4], argv[5], autosquash);
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index 59dc4888a6..0d66c0f8b8 100644
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -16,56 +16,6 @@ todo="$state_dir"/git-rebase-todo
 GIT_CHERRY_PICK_HELP="$resolvemsg"
 export GIT_CHERRY_PICK_HELP
 
-comment_char=$(git config --get core.commentchar 2>/dev/null)
-case "$comment_char" in
-'' | auto)
-	comment_char="#"
-	;;
-?)
-	;;
-*)
-	comment_char=$(echo "$comment_char" | cut -c1)
-	;;
-esac
-
-die_abort () {
-	apply_autostash
-	rm -rf "$state_dir"
-	die "$1"
-}
-
-has_action () {
-	test -n "$(git stripspace --strip-comments <"$1")"
-}
-
-git_sequence_editor () {
-	if test -z "$GIT_SEQUENCE_EDITOR"
-	then
-		GIT_SEQUENCE_EDITOR="$(git config sequence.editor)"
-		if [ -z "$GIT_SEQUENCE_EDITOR" ]
-		then
-			GIT_SEQUENCE_EDITOR="$(git var GIT_EDITOR)" || return $?
-		fi
-	fi
-
-	eval "$GIT_SEQUENCE_EDITOR" '"$@"'
-}
-
-expand_todo_ids() {
-	git rebase--helper --expand-ids
-}
-
-collapse_todo_ids() {
-	git rebase--helper --shorten-ids
-}
-
-get_missing_commit_check_level () {
-	check_level=$(git config --get rebase.missingCommitsCheck)
-	check_level=${check_level:-ignore}
-	# Don't be case sensitive
-	printf '%s' "$check_level" | tr 'A-Z' 'a-z'
-}
-
 # Initiate an action. If the cannot be any
 # further action it  may exec a command
 # or exit and not return.
diff --git a/rebase-interactive.c b/rebase-interactive.c
index d8b9465597..f99e596d28 100644
--- a/rebase-interactive.c
+++ b/rebase-interactive.c
@@ -52,28 +52,6 @@ void append_todo_help(unsigned edit_todo, unsigned keep_empty,
 	}
 }
 
-int append_todo_help_to_file(unsigned edit_todo, unsigned keep_empty)
-{
-	struct strbuf buf = STRBUF_INIT;
-	FILE *todo;
-	int ret;
-
-	todo = fopen_or_warn(rebase_path_todo(), "a");
-	if (!todo)
-		return 1;
-
-	append_todo_help(edit_todo, keep_empty, &buf);
-
-	ret = fputs(buf.buf, todo);
-	if (ret < 0)
-		error_errno(_("could not append help text to '%s'"), rebase_path_todo());
-
-	fclose(todo);
-	strbuf_release(&buf);
-
-	return ret;
-}
-
 int edit_todo_list(unsigned flags)
 {
 	struct strbuf buf = STRBUF_INIT;
diff --git a/rebase-interactive.h b/rebase-interactive.h
index d33f3176b7..971da03776 100644
--- a/rebase-interactive.h
+++ b/rebase-interactive.h
@@ -3,7 +3,6 @@
 
 void append_todo_help(unsigned edit_todo, unsigned keep_empty,
 		      struct strbuf *buf);
-int append_todo_help_to_file(unsigned edit_todo, unsigned keep_empty);
 int edit_todo_list(unsigned flags);
 
 #endif
diff --git a/sequencer.c b/sequencer.c
index 8102e24b19..58107932fd 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -3175,9 +3175,9 @@ int prepare_branch_to_be_rebased(struct replay_opts *opts, const char *commit)
 	return 0;
 }
 
-int checkout_onto(struct replay_opts *opts,
-		  const char *onto_name, const char *onto,
-		  const char *orig_head)
+static int checkout_onto(struct replay_opts *opts,
+			 const char *onto_name, const char *onto,
+			 const char *orig_head)
 {
 	struct object_id oid;
 	const char *action = reflog_message(opts, "start", "checkout %s", onto_name);
@@ -4424,7 +4424,7 @@ static int rewrite_file(const char *path, const char *buf, size_t len)
 }
 
 /* skip picking commits whose parents are unchanged */
-int skip_unnecessary_picks(struct object_id *output_oid)
+static int skip_unnecessary_picks(struct object_id *output_oid)
 {
 	const char *todo_file = rebase_path_todo();
 	struct strbuf buf = STRBUF_INIT;
diff --git a/sequencer.h b/sequencer.h
index 3ab3791c8a..de9d4cf430 100644
--- a/sequencer.h
+++ b/sequencer.h
@@ -88,7 +88,6 @@ int sequencer_add_exec_commands(const char *command);
 int transform_todos(unsigned flags);
 enum missing_commit_check_level get_missing_commit_check_level(void);
 int check_todo_list(void);
-int skip_unnecessary_picks(struct object_id *output_oid);
 int complete_action(struct replay_opts *opts, unsigned flags,
 		    const char *shortrevisions, const char *onto_name,
 		    const char *onto, const char *orig_head, const char *cmd,
@@ -111,9 +110,6 @@ void commit_post_rewrite(const struct commit *current_head,
 			 const struct object_id *new_head);
 
 int prepare_branch_to_be_rebased(struct replay_opts *opts, const char *commit);
-int checkout_onto(struct replay_opts *opts,
-		  const char *onto_name, const char *onto,
-		  const char *orig_head);
 
 #define SUMMARY_INITIAL_COMMIT   (1 << 0)
 #define SUMMARY_SHOW_AUTHOR_DATE (1 << 1)
-- 
2.18.0


^ permalink raw reply related	[relevance 6%]

* Re: [PATCH 0/3] config: fix case sensitive subsection names on writing
  2018-07-30 23:04 12%             ` [PATCH 0/3] " Stefan Beller
  2018-07-30 23:04  7%               ` [PATCH 1/3] t1300: document current behavior of setting options Stefan Beller
  2018-07-30 23:04 24%               ` [PATCH 2/3] config: fix case sensitive subsection names on writing Stefan Beller
@ 2018-07-31 15:16  8%               ` Junio C Hamano
  2018-08-01 19:34  4%                 ` [PATCH 0/3] sb/config-write-fix done without robbing Peter Stefan Beller
  2 siblings, 1 reply; 200+ results
From: Junio C Hamano @ 2018-07-31 15:16 UTC (permalink / raw)
  To: Stefan Beller; +Cc: johannes.schindelin, bmwill, git, peff

Stefan Beller <sbeller@google.com> writes:

> It turns out it doesn't quite do that;
> The parsing code takes the old notation into account and translates any
>   [V.A]
>     r = ...
> into a lower cased "v.a." for ease of comparison. That happens in
> get_base_var, which would call further into get_extended_base_var
> if the new notation is used.
>
> The code in store_aux_event however is written without the consideration
> of the old code and has no way of knowing the capitalization of the
> section or subsection (which were forced to lowercase in the old
> dot notation). 
>
> So either we have to do some major surgery, or the old notation gets
> some regression while fixing the new notation.

As long as

 - the regression is documented clearly (i.e. what unexpected thing
   happens, just like you have a good description in the log message
   of PATCH 2/3 for "[foo "Bar"] key = ..."),

 - users are nudged to use the new style instead, and

 - writing with "git config" (or "git init/clone" for that matter)
   won't produce these old-style sections

I think it is OK to punt.  I would expect, as Git's userbase is a
lot wider than 10 years ago, there is at least some people who did
exploit the fact that "[V.A] r = one" and "[v.a] r = two" give a
single "v.a.r" variable that is multi-valued, and it indeed would be
a regression to them, to which no good workaround exists.  

But breaking '[V "a"] r = ...' is a more grave sin.  Even though I
hate to rob Peter to pay Paul (or vice versa), in this case it is
justified to fix the more basic form sooner and wait for an actual
complaint before fixing the new and deliberate regression introduced
while fixing it to the old-style variable.



^ permalink raw reply	[relevance 8%]

* [PATCH 2/3] config: fix case sensitive subsection names on writing
  2018-07-30 23:04 12%             ` [PATCH 0/3] " Stefan Beller
  2018-07-30 23:04  7%               ` [PATCH 1/3] t1300: document current behavior of setting options Stefan Beller
@ 2018-07-30 23:04 24%               ` Stefan Beller
  2018-07-31 20:16  8%                 ` Junio C Hamano
  2018-07-31 15:16  8%               ` [PATCH 0/3] " Junio C Hamano
  2 siblings, 1 reply; 200+ results
From: Stefan Beller @ 2018-07-30 23:04 UTC (permalink / raw)
  To: johannes.schindelin; +Cc: bmwill, git, gitster, peff, sbeller

A use reported a submodule issue regarding strange case indentation
issues, but it could be boiled down to the following test case:

  $ git init test  && cd test
  $ git config foo."Bar".key test
  $ git config foo."bar".key test
  $ tail -n 3 .git/config
  [foo "Bar"]
        key = test
        key = test

Sub sections are case sensitive and we have a test for correctly reading
them. However we do not have a test for writing out config correctly with
case sensitive subsection names, which is why this went unnoticed in
6ae996f2acf (git_config_set: make use of the config parser's event
stream, 2018-04-09)

Make the subsection case sensitive and provide a test for writing.
The test for reading is just above this test.

Reported-by: JP Sugarbroad <jpsugar@google.com>
Signed-off-by: Stefan Beller <sbeller@google.com>
---
 config.c          | 2 +-
 t/t1300-config.sh | 3 +++
 2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/config.c b/config.c
index 7968ef7566a..de646d2c56f 100644
--- a/config.c
+++ b/config.c
@@ -2362,7 +2362,7 @@ static int store_aux_event(enum config_event_t type,
 		store->is_keys_section =
 			store->parsed[store->parsed_nr].is_keys_section =
 			cf->var.len - 1 == store->baselen &&
-			!strncasecmp(cf->var.buf, store->key, store->baselen);
+			!strncmp(cf->var.buf, store->key, store->baselen);
 		if (store->is_keys_section) {
 			store->section_seen = 1;
 			ALLOC_GROW(store->seen, store->seen_nr + 1,
diff --git a/t/t1300-config.sh b/t/t1300-config.sh
index ced13012409..e5d16f53ee1 100755
--- a/t/t1300-config.sh
+++ b/t/t1300-config.sh
@@ -1250,6 +1250,7 @@ test_expect_success 'old-fashioned settings are case insensitive' '
 	q_to_tab >testConfig_expect <<-EOF &&
 		[V.A]
 		r = value1
+		[V "A"]
 		Qr = value2
 	EOF
 	git config -f testConfig_actual "V.A.r" value2 &&
@@ -1262,6 +1263,7 @@ test_expect_success 'old-fashioned settings are case insensitive' '
 	q_to_tab >testConfig_expect <<-EOF &&
 		[V.A]
 		r = value1
+		[v "A"]
 		Qr = value2
 	EOF
 	git config -f testConfig_actual "v.A.r" value2 &&
@@ -1290,6 +1292,7 @@ test_expect_success 'setting different case sensitive subsections ' '
 		Qc = v2
 		[d "e"]
 		f = v1
+		[d "E"]
 		Qf = v2
 	EOF
 	# exact match
-- 
2.18.0.132.g195c49a2227


^ permalink raw reply related	[relevance 24%]

* [PATCH 1/3] t1300: document current behavior of setting options
  2018-07-30 23:04 12%             ` [PATCH 0/3] " Stefan Beller
@ 2018-07-30 23:04  7%               ` Stefan Beller
  2018-07-30 23:04 24%               ` [PATCH 2/3] config: fix case sensitive subsection names on writing Stefan Beller
  2018-07-31 15:16  8%               ` [PATCH 0/3] " Junio C Hamano
  2 siblings, 0 replies; 200+ results
From: Stefan Beller @ 2018-07-30 23:04 UTC (permalink / raw)
  To: johannes.schindelin; +Cc: bmwill, git, gitster, peff, sbeller

This documents current behavior of the config machinery, when changing
the value of some settings. This patch just serves to provide a baseline
for the follow up that will fix some issues with the current behavior.

Signed-off-by: Stefan Beller <sbeller@google.com>
---
 t/t1300-config.sh | 86 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 86 insertions(+)

diff --git a/t/t1300-config.sh b/t/t1300-config.sh
index 03c223708eb..ced13012409 100755
--- a/t/t1300-config.sh
+++ b/t/t1300-config.sh
@@ -1218,6 +1218,92 @@ test_expect_success 'last one wins: three level vars' '
 	test_cmp expect actual
 '
 
+test_expect_success 'old-fashioned settings are case insensitive' '
+	test_when_finished "rm -f testConfig testConfig_expect testConfig_actual" &&
+
+	cat >testConfig_actual <<-EOF &&
+		[V.A]
+		r = value1
+	EOF
+	q_to_tab >testConfig_expect <<-EOF &&
+		[V.A]
+		Qr = value2
+	EOF
+	git config -f testConfig_actual "v.a.r" value2 &&
+	test_cmp testConfig_expect testConfig_actual &&
+
+	cat >testConfig_actual <<-EOF &&
+		[V.A]
+		r = value1
+	EOF
+	q_to_tab >testConfig_expect <<-EOF &&
+		[V.A]
+		QR = value2
+	EOF
+	git config -f testConfig_actual "V.a.R" value2 &&
+	test_cmp testConfig_expect testConfig_actual &&
+
+	cat >testConfig_actual <<-EOF &&
+		[V.A]
+		r = value1
+	EOF
+	q_to_tab >testConfig_expect <<-EOF &&
+		[V.A]
+		r = value1
+		Qr = value2
+	EOF
+	git config -f testConfig_actual "V.A.r" value2 &&
+	test_cmp testConfig_expect testConfig_actual &&
+
+	cat >testConfig_actual <<-EOF &&
+		[V.A]
+		r = value1
+	EOF
+	q_to_tab >testConfig_expect <<-EOF &&
+		[V.A]
+		r = value1
+		Qr = value2
+	EOF
+	git config -f testConfig_actual "v.A.r" value2 &&
+	test_cmp testConfig_expect testConfig_actual
+'
+
+test_expect_success 'setting different case sensitive subsections ' '
+	test_when_finished "rm -f testConfig testConfig_expect testConfig_actual" &&
+
+	cat >testConfig_actual <<-EOF &&
+		[V "A"]
+		R = v1
+		[K "E"]
+		Y = v1
+		[a "b"]
+		c = v1
+		[d "e"]
+		f = v1
+	EOF
+	q_to_tab >testConfig_expect <<-EOF &&
+		[V "A"]
+		Qr = v2
+		[K "E"]
+		Qy = v2
+		[a "b"]
+		Qc = v2
+		[d "e"]
+		f = v1
+		Qf = v2
+	EOF
+	# exact match
+	git config -f testConfig_actual a.b.c v2 &&
+	# match section and subsection, key is cased differently.
+	git config -f testConfig_actual K.E.y v2 &&
+	# section and key are matched case insensitive, but subsection needs
+	# to match; When writing out new values only the key is adjusted
+	git config -f testConfig_actual v.A.r v2 &&
+	# subsection is not matched:
+	git config -f testConfig_actual d.E.f v2 &&
+	test_cmp testConfig_expect testConfig_actual
+'
+
 for VAR in a .a a. a.0b a."b c". a."b c".0d
 do
 	test_expect_success "git -c $VAR=VAL rejects invalid '$VAR'" '
-- 
2.18.0.132.g195c49a2227


^ permalink raw reply related	[relevance 7%]

* [PATCH 0/3] config: fix case sensitive subsection names on writing
  2018-07-30 12:49 14%           ` Johannes Schindelin
@ 2018-07-30 23:04 12%             ` Stefan Beller
  2018-07-30 23:04  7%               ` [PATCH 1/3] t1300: document current behavior of setting options Stefan Beller
                                 ` (2 more replies)
  0 siblings, 3 replies; 200+ results
From: Stefan Beller @ 2018-07-30 23:04 UTC (permalink / raw)
  To: johannes.schindelin; +Cc: bmwill, git, gitster, peff, sbeller

On Mon, Jul 30, 2018 at 5:50 AM Johannes Schindelin <Johannes.Schindelin@gmx.de> wrote:
> Thanks for the patch!
>
> The only thing that was not clear to me from the patch and from the commit
> message was: the first part *is* case insensitive, right?

right.

> How does the
> patch take care of that? Is it relying on `git_config_parse_key()` to do
> that? If so, I don't see it...

It turns out it doesn't quite do that;
The parsing code takes the old notation into account and translates any
  [V.A]
    r = ...
into a lower cased "v.a." for ease of comparison. That happens in
get_base_var, which would call further into get_extended_base_var
if the new notation is used.

The code in store_aux_event however is written without the consideration
of the old code and has no way of knowing the capitalization of the
section or subsection (which were forced to lowercase in the old
dot notation). 

So either we have to do some major surgery, or the old notation gets
some regression while fixing the new notation.

> > I would still hold the judgment on "all except only this one"
> > myself.  That's a bit too early in my mind.
>
> Agreed. I seem to remember that I had a funny problem "in the reverse",
> where http.<url>.* is case-sensitive, but in an unexpected way: if the URL
> contains upper-case characters, the <url> part of the config key needs to
> be downcased, otherwise the setting won't be picked up.

I wrote some patches that show more of what is happening.

I suggest to drop the last patch and only take the first two,
or if we decide we want to be fully correct, we'd want to discuss
how to make it happen. (where to store the information that we are
dealing with an old notation)

Thanks,
Stefan

Stefan Beller (3):
  t1300: document current behavior of setting options
  config: fix case sensitive subsection names on writing
  config: treat section case insensitive in store_aux_event

 config.c          | 16 ++++++++-
 t/t1300-config.sh | 89 +++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 104 insertions(+), 1 deletion(-)

-- 
2.18.0.132.g195c49a2227


^ permalink raw reply	[relevance 12%]

* [PATCH/RFC] clone: report duplicate entries on case-insensitive filesystems
  @ 2018-07-30 15:27  4% ` Nguyễn Thái Ngọc Duy
  2018-07-31 18:23  0%   ` Torsten Bögershausen
                     ` (3 more replies)
  0 siblings, 4 replies; 200+ results
From: Nguyễn Thái Ngọc Duy @ 2018-07-30 15:27 UTC (permalink / raw)
  To: peff; +Cc: git, pawelparuzel95, pclouds, sandals

Paths that only differ in case work fine in a case-sensitive
filesystems, but if those repos are cloned in a case-insensitive one,
you'll get problems. The first thing to notice is "git status" will
never be clean with no indication what's exactly is "dirty".

This patch helps the situation a bit by pointing out the problem at
clone time. I have not suggested any way to work around or fix this
problem. But I guess we could probably have a section in
Documentation/ dedicated to this problem and point there instead of
a long advice in this warning.

Another thing we probably should do is catch in "git checkout" too,
not just "git clone" since your linux/unix colleage colleague may
accidentally add some files that your mac/windows machine is not very
happy with. But then there's another problem, once the problem is
known, we probably should stop spamming this warning at every
checkout, but how?

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/clone.c | 41 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 41 insertions(+)

diff --git a/builtin/clone.c b/builtin/clone.c
index 5c439f1394..32738c2737 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -711,6 +711,33 @@ static void update_head(const struct ref *our, const struct ref *remote,
 	}
 }
 
+static void find_duplicate_icase_entries(struct index_state *istate,
+					 struct string_list *dup)
+{
+	struct string_list list = STRING_LIST_INIT_NODUP;
+	int i;
+
+	for (i = 0; i < istate->cache_nr; i++)
+		string_list_append(&list, istate->cache[i]->name);
+
+	list.cmp = fspathcmp;
+	string_list_sort(&list);
+
+	for (i = 1; i < list.nr; i++) {
+		const char *cur = list.items[i].string;
+		const char *prev = list.items[i - 1].string;
+
+		if (dup->nr &&
+		    !fspathcmp(cur, dup->items[dup->nr - 1].string)) {
+			string_list_append(dup, cur);
+		} else if (!fspathcmp(cur, prev)) {
+			string_list_append(dup, prev);
+			string_list_append(dup, cur);
+		}
+	}
+	string_list_clear(&list, 0);
+}
+
 static int checkout(int submodule_progress)
 {
 	struct object_id oid;
@@ -761,6 +788,20 @@ static int checkout(int submodule_progress)
 	if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
 		die(_("unable to write new index file"));
 
+	if (ignore_case) {
+		struct string_list dup = STRING_LIST_INIT_DUP;
+		int i;
+
+		find_duplicate_icase_entries(&the_index, &dup);
+		if (dup.nr) {
+			warning(_("the following paths in this repository only differ in case and will\n"
+				  "cause problems because you have cloned it on an case-insensitive filesytem:\n"));
+			for (i = 0; i < dup.nr; i++)
+				fprintf(stderr, "\t%s\n", dup.items[i].string);
+		}
+		string_list_clear(&dup, 0);
+	}
+
 	err |= run_hook_le(NULL, "post-checkout", sha1_to_hex(null_sha1),
 			   oid_to_hex(&oid), "1", NULL);
 
-- 
2.18.0.656.gda699b98b3


^ permalink raw reply related	[relevance 4%]

* Re: [PATCH] config: fix case sensitive subsection names on writing
  2018-07-28  1:37  8%         ` Junio C Hamano
@ 2018-07-30 12:49 14%           ` Johannes Schindelin
  2018-07-30 23:04 12%             ` [PATCH 0/3] " Stefan Beller
  0 siblings, 1 reply; 200+ results
From: Johannes Schindelin @ 2018-07-30 12:49 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Stefan Beller, git, bmwill, peff

Hi,

On Fri, 27 Jul 2018, Junio C Hamano wrote:

> Stefan Beller <sbeller@google.com> writes:
>
> [...]

Thanks for the patch!

The only thing that was not clear to me from the patch and from the commit
message was: the first part *is* case insensitive, right? How does the
patch take care of that? Is it relying on `git_config_parse_key()` to do
that? If so, I don't see it...

> I would still hold the judgment on "all except only this one"
> myself.  That's a bit too early in my mind.

Agreed. I seem to remember that I had a funny problem "in the reverse",
where http.<url>.* is case-sensitive, but in an unexpected way: if the URL
contains upper-case characters, the <url> part of the config key needs to
be downcased, otherwise the setting won't be picked up.

Ciao,
Dscho

^ permalink raw reply	[relevance 14%]

* Re: [PATCH] config: fix case sensitive subsection names on writing
  2018-07-28  3:52  8%           ` Stefan Beller
@ 2018-07-28 10:53  7%             ` Jeff King
  0 siblings, 0 replies; 200+ results
From: Jeff King @ 2018-07-28 10:53 UTC (permalink / raw)
  To: Stefan Beller
  Cc: Junio C Hamano, git, Brandon Williams, peff, Johannes Schindelin

On Fri, Jul 27, 2018 at 08:52:59PM -0700, Stefan Beller wrote:

> > > +     for key in "v.a.r" "V.A.r" "v.A.r" "V.a.r"
> > > +     do
> > > +             cp testConfig testConfig_actual &&
> > > +             git config -f testConfig_actual v.a.r value2 &&
> > > +             test_cmp testConfig_expect testConfig_actual
> > > +     done
> > > +'
> >
> > I think you meant to use "$key" when setting the variable to value2.
> >
> > When the test_cmp fails with "v.a.r" but later succeeds and most
> > importantly succeeds with "V.a.r" (i.e. the last one), wouldn't the
> > whole thing suceed?  I think the common trick people use is to end
> > the last one with "|| return 1" in a loop inside test_expect_success.
> 
> Hah, of course this patch is not as easy.
> 
> The problem is in git_parse_source (config.c, line 755) when we call
> get_base_var it normalizes both styles, (and lower cases the dot style).
> 
> So in case of dot style, the strncasecmp is correct, but for the new
> extended style we need to strncmp.
> 
> So this is correct for extended but not any more for the former dot style.

Hmm, it looks like this has always been broken. Before your patch (but
after v2.18.0), running "git config v.A.r value2" writes:

  [V.A]
  r = value1
  r = value2

which is obviously broken (we decided it was the right section, but
didn't match the variables, leading to a weird duplicate). After your
patch we write:

  [V.A]
  r = value1
  [v "A"]
  r = value2

I.e., we do not consider them the same value at all. But that's actually
what happened before v2.18, too. I think you could almost justify that
behavior as "The section.subsection syntax is deprecated, so we match it
case-insensitively on reading but not on writing; since we never write
such sections ourselves, you are on your own for modifying such entries
if you choose to write them manually".

I dunno. Maybe that is too harsh. But this syntax has been deprecated
for 7 years, and nobody has noticed the bug until now (when _still_
nobody wants to use it, but rather we're poking at it as "gee, I wonder
if this even works").

> I wonder if we want to either (a) add another CONFIG_EVENT_SECTION
> that indicates the difference between the two, or have a flag in 'cf' that
> tells us what kind of section style we have, such that we can check
> appropriately for the case.

I'd think that the parse_event_data could carry type-specific
information. But...

> Or we could encode it in the 'cf->var' value to distinguish the old
> and new style.

Even if we fix the section-matching here, I suspect that would just
trigger a separate bug later when we match the full key (so we might add
to the correct section, but we would not correctly replace an existing
key). To fix that, the information does need to be carried for the whole
lifetime of cf->var, not just during the header event.

-Peff

^ permalink raw reply	[relevance 7%]

* Re: Git clone and case sensitivity
  2018-07-28  4:36  0%   ` Duy Nguyen
@ 2018-07-28  4:45  0%     ` Duy Nguyen
  0 siblings, 0 replies; 200+ results
From: Duy Nguyen @ 2018-07-28  4:45 UTC (permalink / raw)
  To: brian m. carlson; +Cc: Paweł Paruzel, Git Mailing List

On Sat, Jul 28, 2018 at 6:36 AM Duy Nguyen <pclouds@gmail.com> wrote:
>
> On Fri, Jul 27, 2018 at 08:59:09PM +0000, brian m. carlson wrote:
> > On Fri, Jul 27, 2018 at 11:59:33AM +0200, Paweł Paruzel wrote:
> > > Hi,
> > >
> > > Lately, I have been wondering why my test files in repo are modified
> > > after I clone it. It turned out to be two files: boolStyle_t_f and
> > > boolStyle_T_F.
> > > The system that pushed those files was case sensitive while my mac
> > > after High Sierra update had APFS which is by default
> > > case-insensitive. I highly suggest that git clone threw an exception
> > > when files are case sensitive and being cloned to a case insensitive
> > > system. This has caused problems with overriding files for test cases
> > > without any warning.
> >
> > If we did what you proposed, it would be impossible to clone such a
> > repository on a case-insensitive system.
>
> I agree throwing a real exception would be bad. But how about detecting
> the problem and trying our best to keep the repo in somewhat usable
> state like this?
>
> This patch uses sparse checkout to hide all those paths that we fail
> to checkout, so you can still have a clean worktree to do things, as
> long as you don't touch those paths.

Side note. There may still be problems with this patch. Let's use
vim-colorschemes.git as an example, which has darkBlue.vim and
darkblue.vim.

Say we have checked out darkBlue.vim and hidden darkblue.vim. When you
update darkBlue.vim on worktree and then update the index, are we sure
we will update darkBlue.vim entry and not (hidden) darkblue.vim? I am
not sure. I don't think our lookup function is prepared to deal with
this. Maybe it's best to hide both of them.
-- 
Duy

^ permalink raw reply	[relevance 0%]

* Re: Git clone and case sensitivity
  2018-07-27 20:59  0% ` brian m. carlson
@ 2018-07-28  4:36  0%   ` Duy Nguyen
  2018-07-28  4:45  0%     ` Duy Nguyen
  0 siblings, 1 reply; 200+ results
From: Duy Nguyen @ 2018-07-28  4:36 UTC (permalink / raw)
  To: brian m. carlson; +Cc: Paweł Paruzel, git

On Fri, Jul 27, 2018 at 08:59:09PM +0000, brian m. carlson wrote:
> On Fri, Jul 27, 2018 at 11:59:33AM +0200, Paweł Paruzel wrote:
> > Hi,
> > 
> > Lately, I have been wondering why my test files in repo are modified
> > after I clone it. It turned out to be two files: boolStyle_t_f and
> > boolStyle_T_F.
> > The system that pushed those files was case sensitive while my mac
> > after High Sierra update had APFS which is by default
> > case-insensitive. I highly suggest that git clone threw an exception
> > when files are case sensitive and being cloned to a case insensitive
> > system. This has caused problems with overriding files for test cases
> > without any warning.
> 
> If we did what you proposed, it would be impossible to clone such a
> repository on a case-insensitive system.

I agree throwing a real exception would be bad. But how about detecting
the problem and trying our best to keep the repo in somewhat usable
state like this?

This patch uses sparse checkout to hide all those paths that we fail
to checkout, so you can still have a clean worktree to do things, as
long as you don't touch those paths.

-- 8< --
diff --git a/builtin/clone.c b/builtin/clone.c
index 1d939af9d8..a6b5e2c948 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -711,6 +711,30 @@ static void update_head(const struct ref *our, const struct ref *remote,
 	}
 }
 
+static int enable_sparse_checkout_on_icase_fs(struct index_state *istate)
+{
+	int i;
+	int skip_count = 0;
+	FILE *fp = fopen(git_path("info/sparse"), "a+");
+
+	for (i = 0; i < istate->cache_nr; i++) {
+		struct cache_entry *ce = istate->cache[i];
+		if (!ce_skip_worktree(ce))
+			continue;
+		if (!skip_count) {
+			git_config_set_multivar_gently("core.sparseCheckout",
+						       "true",
+						       CONFIG_REGEX_NONE, 0);
+			fprintf(fp, "# List of paths hidden by 'git clone'\n");
+		}
+		fprintf(fp, "/%s\n", ce->name);
+		skip_count++;
+	}
+	fclose(fp);
+
+	return skip_count;
+}
+
 static int checkout(int submodule_progress)
 {
 	struct object_id oid;
@@ -751,6 +775,7 @@ static int checkout(int submodule_progress)
 	opts.verbose_update = (option_verbosity >= 0);
 	opts.src_index = &the_index;
 	opts.dst_index = &the_index;
+	opts.clone_checkout = 1;
 
 	tree = parse_tree_indirect(&oid);
 	parse_tree(tree);
@@ -761,6 +786,12 @@ static int checkout(int submodule_progress)
 	if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
 		die(_("unable to write new index file"));
 
+	if (enable_sparse_checkout_on_icase_fs(&the_index))
+		warning("Paths that differ only in case are detected "
+			"and will not work correctly on this case-insensitive "
+			"filesystem. Sparse checkout has been enabled to hide "
+			"these paths.");
+
 	err |= run_hook_le(NULL, "post-checkout", sha1_to_hex(null_sha1),
 			   oid_to_hex(&oid), "1", NULL);
 
diff --git a/cache.h b/cache.h
index 8b447652a7..9ecf7ad952 100644
--- a/cache.h
+++ b/cache.h
@@ -1455,6 +1455,7 @@ struct checkout {
 	unsigned force:1,
 		 quiet:1,
 		 not_new:1,
+		 set_skipworktree_on_updated:1,
 		 refresh_cache:1;
 };
 #define CHECKOUT_INIT { NULL, "" }
diff --git a/entry.c b/entry.c
index b5d1d3cf23..ba21db63e7 100644
--- a/entry.c
+++ b/entry.c
@@ -447,6 +447,11 @@ int checkout_entry(struct cache_entry *ce,
 
 		if (!changed)
 			return 0;
+		if (state->set_skipworktree_on_updated) {
+			ce->ce_flags |= CE_SKIP_WORKTREE;
+			state->istate->cache_changed |= CE_ENTRY_CHANGED;
+			return 0;
+		}
 		if (!state->force) {
 			if (!state->quiet)
 				fprintf(stderr,
diff --git a/unpack-trees.c b/unpack-trees.c
index 66741130ae..a8a24e0b13 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -358,6 +358,7 @@ static int check_updates(struct unpack_trees_options *o)
 	state.quiet = 1;
 	state.refresh_cache = 1;
 	state.istate = index;
+	state.set_skipworktree_on_updated = o->clone_checkout;
 
 	progress = get_progress(o);
 
diff --git a/unpack-trees.h b/unpack-trees.h
index c2b434c606..8ebe2e2ec5 100644
--- a/unpack-trees.h
+++ b/unpack-trees.h
@@ -49,6 +49,7 @@ struct unpack_trees_options {
 		     aggressive,
 		     skip_unmerged,
 		     initial_checkout,
+		     clone_checkout,
 		     diff_index_cached,
 		     debug_unpack,
 		     skip_sparse_checkout,
-- 8< --

> While this might be fine for a closed system such as inside a
> company, this would make many open source repositories unusable,
> even when the files differing in case are nonfunctional (like README
> and readme).
> 
> This is actually one of a few ways people can make repositories that
> will show as modified on Windows or macOS systems, due to limitations in
> those OSes.  If you want to be sure that your repository is unmodified
> after clone, you can ensure that the output of git status --porcelain is
> empty, such as by checking for a zero exit from
> "test $(git status --porcelain | wc -l) -eq 0".
> -- 
> brian m. carlson: Houston, Texas, US
> OpenPGP: https://keybase.io/bk2204



^ permalink raw reply related	[relevance 0%]

* Re: [PATCH] config: fix case sensitive subsection names on writing
  2018-07-28  1:01  8%         ` Junio C Hamano
@ 2018-07-28  3:52  8%           ` Stefan Beller
  2018-07-28 10:53  7%             ` Jeff King
  0 siblings, 1 reply; 200+ results
From: Stefan Beller @ 2018-07-28  3:52 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Brandon Williams, peff, Johannes Schindelin

On Fri, Jul 27, 2018 at 6:01 PM Junio C Hamano <gitster@pobox.com> wrote:
>
> Stefan Beller <sbeller@google.com> writes:
>
> > A use reported a submodule issue regarding strange case indentation
>
> s/use/&r/;  Is this "indentation" issue?

eh case sensitivity*

> > +     q_to_tab >testConfig_expect <<-EOF &&
> > +             # insensitive:
> > +             [V.A]
> > +             Qr = value2
> > +     EOF
>
> It is unfortunate that we hardcode the exact indentation
> in the test to make it care.  Perhaps a wrapper around test_cmp that
> is used locally in this file to first strip the leading HT from both
> sides of the comparison would make it more robust?

I think this is valuable for the second test to see where it was replaced.

>
> > +     for key in "v.a.r" "V.A.r" "v.A.r" "V.a.r"
> > +     do
> > +             cp testConfig testConfig_actual &&
> > +             git config -f testConfig_actual v.a.r value2 &&
> > +             test_cmp testConfig_expect testConfig_actual
> > +     done
> > +'
>
> I think you meant to use "$key" when setting the variable to value2.
>
> When the test_cmp fails with "v.a.r" but later succeeds and most
> importantly succeeds with "V.a.r" (i.e. the last one), wouldn't the
> whole thing suceed?  I think the common trick people use is to end
> the last one with "|| return 1" in a loop inside test_expect_success.
>

Hah, of course this patch is not as easy.

The problem is in git_parse_source (config.c, line 755) when we call
get_base_var it normalizes both styles, (and lower cases the dot style).

So in case of dot style, the strncasecmp is correct, but for the new
extended style we need to strncmp.

So this is correct for extended but not any more for the former dot style.

I wonder if we want to either (a) add another CONFIG_EVENT_SECTION
that indicates the difference between the two, or have a flag in 'cf' that
tells us what kind of section style we have, such that we can check
appropriately for the case.
Or we could encode it in the 'cf->var' value to distinguish the old
and new style.

Thanks,
Stefan

^ permalink raw reply	[relevance 8%]

* Re: [PATCH] config: fix case sensitive subsection names on writing
  2018-07-27 23:36 21%       ` Stefan Beller
  2018-07-27 23:37  8%         ` Stefan Beller
  2018-07-28  1:01  8%         ` Junio C Hamano
@ 2018-07-28  1:37  8%         ` Junio C Hamano
  2018-07-30 12:49 14%           ` Johannes Schindelin
  2 siblings, 1 reply; 200+ results
From: Junio C Hamano @ 2018-07-28  1:37 UTC (permalink / raw)
  To: Stefan Beller; +Cc: git, bmwill, peff, Johannes.Schindelin

Stefan Beller <sbeller@google.com> writes:

> I really appreciate the work by DScho (and Peff as I recall him as an active
> reviewer there) on 4f4d0b42bae (Merge branch 'js/empty-config-section-fix',
> 2018-05-08), as the corner cases are all correct, modulo the one line fix
> in this patch.

Amen to the early part of that ;--) Even though it was merely
cosmetic, and did not affect correctness, that longstandng bug was
very annoying bug to a lot of people.  I too am very happy to see it
disappear.

I would still hold the judgment on "all except only this one"
myself.  That's a bit too early in my mind.

^ permalink raw reply	[relevance 8%]

* Re: [PATCH] config: fix case sensitive subsection names on writing
  2018-07-27 23:36 21%       ` Stefan Beller
  2018-07-27 23:37  8%         ` Stefan Beller
@ 2018-07-28  1:01  8%         ` Junio C Hamano
  2018-07-28  3:52  8%           ` Stefan Beller
  2018-07-28  1:37  8%         ` Junio C Hamano
  2 siblings, 1 reply; 200+ results
From: Junio C Hamano @ 2018-07-28  1:01 UTC (permalink / raw)
  To: Stefan Beller; +Cc: git, bmwill, peff, Johannes.Schindelin

Stefan Beller <sbeller@google.com> writes:

> A use reported a submodule issue regarding strange case indentation

s/use/&r/;  Is this "indentation" issue?

> issues, but it could be boiled down to the following test case:
> ...

> +test_expect_success 'old-fashioned settings are case insensitive' '
> +	test_when_finished "rm -f testConfig testConfig_expect testConfig_actual" &&
> +
> +	cat >testConfig <<-EOF &&
> +		# insensitive:
> +		[V.A]
> +		r = value1
> +	EOF
> +	q_to_tab >testConfig_expect <<-EOF &&
> +		# insensitive:
> +		[V.A]
> +		Qr = value2
> +	EOF

It is unfortunate that we hardcode the exact indentation
in the test to make it care.  Perhaps a wrapper around test_cmp that
is used locally in this file to first strip the leading HT from both
sides of the comparison would make it more robust?

> +	for key in "v.a.r" "V.A.r" "v.A.r" "V.a.r"
> +	do
> +		cp testConfig testConfig_actual &&
> +		git config -f testConfig_actual v.a.r value2 &&
> +		test_cmp testConfig_expect testConfig_actual
> +	done
> +'

I think you meant to use "$key" when setting the variable to value2.

When the test_cmp fails with "v.a.r" but later succeeds and most
importantly succeeds with "V.a.r" (i.e. the last one), wouldn't the
whole thing suceed?  I think the common trick people use is to end
the last one with "|| return 1" in a loop inside test_expect_success.


^ permalink raw reply	[relevance 8%]

* Re: [PATCH] config: fix case sensitive subsection names on writing
  2018-07-27 23:36 21%       ` Stefan Beller
@ 2018-07-27 23:37  8%         ` Stefan Beller
  2018-07-28  1:01  8%         ` Junio C Hamano
  2018-07-28  1:37  8%         ` Junio C Hamano
  2 siblings, 0 replies; 200+ results
From: Stefan Beller @ 2018-07-27 23:37 UTC (permalink / raw)
  To: git, Jeff King; +Cc: Brandon Williams, Johannes Schindelin, Junio C Hamano

I cc'd the wrong peff. Sorry about that.

On Fri, Jul 27, 2018 at 4:36 PM Stefan Beller <sbeller@google.com> wrote:
....

^ permalink raw reply	[relevance 8%]

* [PATCH] config: fix case sensitive subsection names on writing
  2018-07-27 23:35  8%     ` Stefan Beller
@ 2018-07-27 23:36 21%       ` Stefan Beller
  2018-07-27 23:37  8%         ` Stefan Beller
                           ` (2 more replies)
  0 siblings, 3 replies; 200+ results
From: Stefan Beller @ 2018-07-27 23:36 UTC (permalink / raw)
  To: git; +Cc: bmwill, peff, Johannes.Schindelin, gitster, Stefan Beller

A use reported a submodule issue regarding strange case indentation
issues, but it could be boiled down to the following test case:

  $ git init test  && cd test
  $ git config foo."Bar".key test
  $ git config foo."bar".key test
  $ tail -n 3 .git/config
  [foo "Bar"]
        key = test
        key = test

Sub sections are case sensitive and we have a test for correctly reading
them. However we do not have a test for writing out config correctly with
case sensitive subsection names, which is why this went unnoticed in
6ae996f2acf (git_config_set: make use of the config parser's event
stream, 2018-04-09)

Make the subsection case sensitive and provide a test for writing.
The test for reading is just above this test.

Reported-by: JP Sugarbroad <jpsugar@google.com>
Signed-off-by: Stefan Beller <sbeller@google.com>
---

I really appreciate the work by DScho (and Peff as I recall him as an active
reviewer there) on 4f4d0b42bae (Merge branch 'js/empty-config-section-fix',
2018-05-08), as the corner cases are all correct, modulo the one line fix
in this patch.

Thanks,
Stefan

 config.c          |  2 +-
 t/t1300-config.sh | 57 +++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 58 insertions(+), 1 deletion(-)

diff --git a/config.c b/config.c
index 7968ef7566a..de646d2c56f 100644
--- a/config.c
+++ b/config.c
@@ -2362,7 +2362,7 @@ static int store_aux_event(enum config_event_t type,
 		store->is_keys_section =
 			store->parsed[store->parsed_nr].is_keys_section =
 			cf->var.len - 1 == store->baselen &&
-			!strncasecmp(cf->var.buf, store->key, store->baselen);
+			!strncmp(cf->var.buf, store->key, store->baselen);
 		if (store->is_keys_section) {
 			store->section_seen = 1;
 			ALLOC_GROW(store->seen, store->seen_nr + 1,
diff --git a/t/t1300-config.sh b/t/t1300-config.sh
index 03c223708eb..710e2b04ad8 100755
--- a/t/t1300-config.sh
+++ b/t/t1300-config.sh
@@ -1218,6 +1218,63 @@ test_expect_success 'last one wins: three level vars' '
 	test_cmp expect actual
 '
 
+test_expect_success 'old-fashioned settings are case insensitive' '
+	test_when_finished "rm -f testConfig testConfig_expect testConfig_actual" &&
+
+	cat >testConfig <<-EOF &&
+		# insensitive:
+		[V.A]
+		r = value1
+	EOF
+	q_to_tab >testConfig_expect <<-EOF &&
+		# insensitive:
+		[V.A]
+		Qr = value2
+	EOF
+
+	for key in "v.a.r" "V.A.r" "v.A.r" "V.a.r"
+	do
+		cp testConfig testConfig_actual &&
+		git config -f testConfig_actual v.a.r value2 &&
+		test_cmp testConfig_expect testConfig_actual
+	done
+'
+
+test_expect_success 'setting different case sensitive subsections ' '
+	test_when_finished "rm -f testConfig testConfig_expect testConfig_actual" &&
+
+	cat >testConfig <<-EOF &&
+		# V insensitive A sensitive:
+		[V "A"]
+		r = value1
+		# same as above:
+		[V "a"]
+		r = value2
+	EOF
+
+	git config -f testConfig v.a.r value3 &&
+	q_to_tab >testConfig_expect <<-EOF &&
+		# V insensitive A sensitive:
+		[V "A"]
+		r = value1
+		# same as above:
+		[V "a"]
+		Qr = value3
+	EOF
+	test_cmp testConfig_expect testConfig &&
+
+	git config -f testConfig v.A.r value4 &&
+	q_to_tab >testConfig_expect <<-EOF &&
+		# V insensitive A sensitive:
+		[V "A"]
+		Qr = value4
+		# same as above:
+		[V "a"]
+		Qr = value3
+	EOF
+	test_cmp testConfig_expect testConfig
+'
+
 for VAR in a .a a. a.0b a."b c". a."b c".0d
 do
 	test_expect_success "git -c $VAR=VAL rejects invalid '$VAR'" '
-- 
2.18.0.345.g5c9ce644c3-goog


^ permalink raw reply related	[relevance 21%]

* Re: [PATCH] config: fix case sensitive subsection names on writing
  2018-07-27 21:39  8%   ` Junio C Hamano
@ 2018-07-27 23:35  8%     ` Stefan Beller
  2018-07-27 23:36 21%       ` Stefan Beller
  0 siblings, 1 reply; 200+ results
From: Stefan Beller @ 2018-07-27 23:35 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Brandon Williams, git

On Fri, Jul 27, 2018 at 2:39 PM Junio C Hamano <gitster@pobox.com> wrote:
>
> Brandon Williams <bmwill@google.com> writes:
>
> > Either way you're probably going to need to be careful about how you do
> > string comparison against the different parts.
>
> Good suggestion.

The suggestion is a rabit hole and was a waste of time.

However I did some more manual testing and inspected the code
with trace_printf debugging, and it turns out the strings compared
are brought into the correct form already.

> >> +    # v.a.r and v.A.r are not the same variable, as the middle
> >> +    # level of a three-level configuration variable name is
> >> +    # case sensitive.
>
> In other words, perhaps add
>
>         # "V.a.r" and "v.a.R" are the same variable, though
>
> and corresponding test here?

I removed that section and went for a shorter,
more concise expression.

patch to follow.

^ permalink raw reply	[relevance 8%]

* Re: [PATCH] config: fix case sensitive subsection names on writing
  2018-07-27 21:21 15% ` Brandon Williams
@ 2018-07-27 21:39  8%   ` Junio C Hamano
  2018-07-27 23:35  8%     ` Stefan Beller
  0 siblings, 1 reply; 200+ results
From: Junio C Hamano @ 2018-07-27 21:39 UTC (permalink / raw)
  To: Brandon Williams; +Cc: Stefan Beller, git

Brandon Williams <bmwill@google.com> writes:

> Either way you're probably going to need to be careful about how you do
> string comparison against the different parts.

Good suggestion.

>> diff --git a/t/t1300-config.sh b/t/t1300-config.sh
>> index 03c223708eb..8325d4495f4 100755
>> --- a/t/t1300-config.sh
>> +++ b/t/t1300-config.sh
>> @@ -1218,6 +1218,24 @@ test_expect_success 'last one wins: three level vars' '
>>  	test_cmp expect actual
>>  '
>>  
>> +test_expect_success 'setting different case subsections ' '
>> +	test_when_finished "rm -f caseSens caseSens_actual caseSens_expect" &&
>> +
>> +	# v.a.r and v.A.r are not the same variable, as the middle
>> +	# level of a three-level configuration variable name is
>> +	# case sensitive.

In other words, perhaps add

	# "V.a.r" and "v.a.R" are the same variable, though

and corresponding test here?

>> +	git config -f caseSens v."A".r VAL &&
>> +	git config -f caseSens v."a".r val &&
>> +
>> +	echo VAL >caseSens_expect &&
>> +	git config -f caseSens v."A".r >caseSens_actual &&
>> +	test_cmp caseSens_expect caseSens_actual &&
>> +
>> +	echo val >caseSens_expect &&
>> +	git config -f caseSens v."a".r >caseSens_actual &&
>> +	test_cmp caseSens_expect caseSens_actual
>> +'
>> +
>>  for VAR in a .a a. a.0b a."b c". a."b c".0d
>>  do
>>  	test_expect_success "git -c $VAR=VAL rejects invalid '$VAR'" '
>> -- 
>> 2.18.0.345.g5c9ce644c3-goog
>> 

^ permalink raw reply	[relevance 8%]

* Re: [PATCH] config: fix case sensitive subsection names on writing
  2018-07-27 20:51 20% [PATCH] config: fix case sensitive subsection names on writing Stefan Beller
  2018-07-27 21:21 15% ` Brandon Williams
@ 2018-07-27 21:37  8% ` Junio C Hamano
  1 sibling, 0 replies; 200+ results
From: Junio C Hamano @ 2018-07-27 21:37 UTC (permalink / raw)
  To: Stefan Beller; +Cc: git

Stefan Beller <sbeller@google.com> writes:

> A use reported a submodule issue regarding strange case indentation
> issues, but it could be boiled down to the following test case:
>
>   $ git init test  && cd test
>   $ git config foo."Bar".key test
>   $ git config foo."bar".key test
>   $ tail -n 3 .git/config
>   [foo "Bar"]
>         key = test
>         key = test
>
> Sub sections are case sensitive and we have a test for correctly reading
> them. However we do not have a test for writing out config correctly with
> case sensitive subsection names, which is why this went unnoticed in
> 6ae996f2acf (git_config_set: make use of the config parser's event
> stream, 2018-04-09)

Thanks for finding this bug and fixing it; yes it would have been
even nicer if we caught it before it hit 'master', but we can only
do what we can X-<.

> diff --git a/config.c b/config.c
> index 3aacddfec4b..3ded92b678b 100644
> --- a/config.c
> +++ b/config.c
> @@ -2374,7 +2374,7 @@ static int store_aux_event(enum config_event_t type,
>  		store->is_keys_section =
>  			store->parsed[store->parsed_nr].is_keys_section =
>  			cf->var.len - 1 == store->baselen &&
> -			!strncasecmp(cf->var.buf, store->key, store->baselen);
> +			!strncmp(cf->var.buf, store->key, store->baselen);
>  		if (store->is_keys_section) {
>  			store->section_seen = 1;
>  			ALLOC_GROW(store->seen, store->seen_nr + 1,
> diff --git a/t/t1300-config.sh b/t/t1300-config.sh
> index 03c223708eb..8325d4495f4 100755
> --- a/t/t1300-config.sh
> +++ b/t/t1300-config.sh
> @@ -1218,6 +1218,24 @@ test_expect_success 'last one wins: three level vars' '
>  	test_cmp expect actual
>  '
>  
> +test_expect_success 'setting different case subsections ' '
> +	test_when_finished "rm -f caseSens caseSens_actual caseSens_expect" &&
> +
> +	# v.a.r and v.A.r are not the same variable, as the middle
> +	# level of a three-level configuration variable name is
> +	# case sensitive.
> +	git config -f caseSens v."A".r VAL &&
> +	git config -f caseSens v."a".r val &&

It probably is easier to read to write these as "v.A.r" and "v.a.r"
respectively.

> +
> +	echo VAL >caseSens_expect &&
> +	git config -f caseSens v."A".r >caseSens_actual &&
> +	test_cmp caseSens_expect caseSens_actual &&
> +
> +	echo val >caseSens_expect &&
> +	git config -f caseSens v."a".r >caseSens_actual &&
> +	test_cmp caseSens_expect caseSens_actual
> +'
> +
>  for VAR in a .a a. a.0b a."b c". a."b c".0d
>  do
>  	test_expect_success "git -c $VAR=VAL rejects invalid '$VAR'" '

^ permalink raw reply	[relevance 8%]

* Re: [PATCH] config: fix case sensitive subsection names on writing
  2018-07-27 20:51 20% [PATCH] config: fix case sensitive subsection names on writing Stefan Beller
@ 2018-07-27 21:21 15% ` Brandon Williams
  2018-07-27 21:39  8%   ` Junio C Hamano
  2018-07-27 21:37  8% ` [PATCH] " Junio C Hamano
  1 sibling, 1 reply; 200+ results
From: Brandon Williams @ 2018-07-27 21:21 UTC (permalink / raw)
  To: Stefan Beller; +Cc: git

On 07/27, Stefan Beller wrote:
> A use reported a submodule issue regarding strange case indentation
> issues, but it could be boiled down to the following test case:
> 
>   $ git init test  && cd test
>   $ git config foo."Bar".key test
>   $ git config foo."bar".key test
>   $ tail -n 3 .git/config
>   [foo "Bar"]
>         key = test
>         key = test
> 
> Sub sections are case sensitive and we have a test for correctly reading
> them. However we do not have a test for writing out config correctly with
> case sensitive subsection names, which is why this went unnoticed in
> 6ae996f2acf (git_config_set: make use of the config parser's event
> stream, 2018-04-09)
> 
> Make the subsection case sensitive and provide a test for both reading
> and writing.
> 
> Reported-by: JP Sugarbroad <jpsugar@google.com>
> Signed-off-by: Stefan Beller <sbeller@google.com>
> ---
>  config.c          |  2 +-
>  t/t1300-config.sh | 18 ++++++++++++++++++
>  2 files changed, 19 insertions(+), 1 deletion(-)
> 
> diff --git a/config.c b/config.c
> index 3aacddfec4b..3ded92b678b 100644
> --- a/config.c
> +++ b/config.c
> @@ -2374,7 +2374,7 @@ static int store_aux_event(enum config_event_t type,
>  		store->is_keys_section =
>  			store->parsed[store->parsed_nr].is_keys_section =
>  			cf->var.len - 1 == store->baselen &&
> -			!strncasecmp(cf->var.buf, store->key, store->baselen);
> +			!strncmp(cf->var.buf, store->key, store->baselen);

I've done some work in the config part of our codebase but I don't
really know whats going on here due to two things: this is a callback
function and it relies on global state...

I can say though that we might want to be careful about completely
converting this to a case sensitive compare.  Our config is a key
value store with the following format: 'section.subsection.key'.  IIRC
both section and key are always compared case insensitively.  The
subsection can be case sensitive or insensitive based on how its
stored in the config files itself:

  # Case insensitive
  [section.subsection]
      key = value

  # Case sensitive 
  [section "subsection"]
      key = value

But I don't know how you distinguish between these cases when a config
is specified on a single line (section.subsection.key).  Do we always
assume the sensitive version because the insensitive version is
documented to be deprecated?

Either way you're probably going to need to be careful about how you do
string comparison against the different parts.

>  		if (store->is_keys_section) {
>  			store->section_seen = 1;
>  			ALLOC_GROW(store->seen, store->seen_nr + 1,
> diff --git a/t/t1300-config.sh b/t/t1300-config.sh
> index 03c223708eb..8325d4495f4 100755
> --- a/t/t1300-config.sh
> +++ b/t/t1300-config.sh
> @@ -1218,6 +1218,24 @@ test_expect_success 'last one wins: three level vars' '
>  	test_cmp expect actual
>  '
>  
> +test_expect_success 'setting different case subsections ' '
> +	test_when_finished "rm -f caseSens caseSens_actual caseSens_expect" &&
> +
> +	# v.a.r and v.A.r are not the same variable, as the middle
> +	# level of a three-level configuration variable name is
> +	# case sensitive.
> +	git config -f caseSens v."A".r VAL &&
> +	git config -f caseSens v."a".r val &&
> +
> +	echo VAL >caseSens_expect &&
> +	git config -f caseSens v."A".r >caseSens_actual &&
> +	test_cmp caseSens_expect caseSens_actual &&
> +
> +	echo val >caseSens_expect &&
> +	git config -f caseSens v."a".r >caseSens_actual &&
> +	test_cmp caseSens_expect caseSens_actual
> +'
> +
>  for VAR in a .a a. a.0b a."b c". a."b c".0d
>  do
>  	test_expect_success "git -c $VAR=VAL rejects invalid '$VAR'" '
> -- 
> 2.18.0.345.g5c9ce644c3-goog
> 

-- 
Brandon Williams

^ permalink raw reply	[relevance 15%]

* Re: Git clone and case sensitivity
  2018-07-27  9:59  6% Git clone and case sensitivity Paweł Paruzel
@ 2018-07-27 20:59  0% ` brian m. carlson
  2018-07-28  4:36  0%   ` Duy Nguyen
  0 siblings, 1 reply; 200+ results
From: brian m. carlson @ 2018-07-27 20:59 UTC (permalink / raw)
  To: Paweł Paruzel; +Cc: git

[-- Attachment #1: Type: text/plain, Size: 1399 bytes --]

On Fri, Jul 27, 2018 at 11:59:33AM +0200, Paweł Paruzel wrote:
> Hi,
> 
> Lately, I have been wondering why my test files in repo are modified
> after I clone it. It turned out to be two files: boolStyle_t_f and
> boolStyle_T_F.
> The system that pushed those files was case sensitive while my mac
> after High Sierra update had APFS which is by default
> case-insensitive. I highly suggest that git clone threw an exception
> when files are case sensitive and being cloned to a case insensitive
> system. This has caused problems with overriding files for test cases
> without any warning.

If we did what you proposed, it would be impossible to clone such a
repository on a case-insensitive system.  While this might be fine for a
closed system such as inside a company, this would make many open source
repositories unusable, even when the files differing in case are
nonfunctional (like README and readme).

This is actually one of a few ways people can make repositories that
will show as modified on Windows or macOS systems, due to limitations in
those OSes.  If you want to be sure that your repository is unmodified
after clone, you can ensure that the output of git status --porcelain is
empty, such as by checking for a zero exit from
"test $(git status --porcelain | wc -l) -eq 0".
-- 
brian m. carlson: Houston, Texas, US
OpenPGP: https://keybase.io/bk2204

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 867 bytes --]

^ permalink raw reply	[relevance 0%]

* [PATCH] config: fix case sensitive subsection names on writing
@ 2018-07-27 20:51 20% Stefan Beller
  2018-07-27 21:21 15% ` Brandon Williams
  2018-07-27 21:37  8% ` [PATCH] " Junio C Hamano
  0 siblings, 2 replies; 200+ results
From: Stefan Beller @ 2018-07-27 20:51 UTC (permalink / raw)
  To: git; +Cc: Stefan Beller

A use reported a submodule issue regarding strange case indentation
issues, but it could be boiled down to the following test case:

  $ git init test  && cd test
  $ git config foo."Bar".key test
  $ git config foo."bar".key test
  $ tail -n 3 .git/config
  [foo "Bar"]
        key = test
        key = test

Sub sections are case sensitive and we have a test for correctly reading
them. However we do not have a test for writing out config correctly with
case sensitive subsection names, which is why this went unnoticed in
6ae996f2acf (git_config_set: make use of the config parser's event
stream, 2018-04-09)

Make the subsection case sensitive and provide a test for both reading
and writing.

Reported-by: JP Sugarbroad <jpsugar@google.com>
Signed-off-by: Stefan Beller <sbeller@google.com>
---
 config.c          |  2 +-
 t/t1300-config.sh | 18 ++++++++++++++++++
 2 files changed, 19 insertions(+), 1 deletion(-)

diff --git a/config.c b/config.c
index 3aacddfec4b..3ded92b678b 100644
--- a/config.c
+++ b/config.c
@@ -2374,7 +2374,7 @@ static int store_aux_event(enum config_event_t type,
 		store->is_keys_section =
 			store->parsed[store->parsed_nr].is_keys_section =
 			cf->var.len - 1 == store->baselen &&
-			!strncasecmp(cf->var.buf, store->key, store->baselen);
+			!strncmp(cf->var.buf, store->key, store->baselen);
 		if (store->is_keys_section) {
 			store->section_seen = 1;
 			ALLOC_GROW(store->seen, store->seen_nr + 1,
diff --git a/t/t1300-config.sh b/t/t1300-config.sh
index 03c223708eb..8325d4495f4 100755
--- a/t/t1300-config.sh
+++ b/t/t1300-config.sh
@@ -1218,6 +1218,24 @@ test_expect_success 'last one wins: three level vars' '
 	test_cmp expect actual
 '
 
+test_expect_success 'setting different case subsections ' '
+	test_when_finished "rm -f caseSens caseSens_actual caseSens_expect" &&
+
+	# v.a.r and v.A.r are not the same variable, as the middle
+	# level of a three-level configuration variable name is
+	# case sensitive.
+	git config -f caseSens v."A".r VAL &&
+	git config -f caseSens v."a".r val &&
+
+	echo VAL >caseSens_expect &&
+	git config -f caseSens v."A".r >caseSens_actual &&
+	test_cmp caseSens_expect caseSens_actual &&
+
+	echo val >caseSens_expect &&
+	git config -f caseSens v."a".r >caseSens_actual &&
+	test_cmp caseSens_expect caseSens_actual
+'
+
 for VAR in a .a a. a.0b a."b c". a."b c".0d
 do
 	test_expect_success "git -c $VAR=VAL rejects invalid '$VAR'" '
-- 
2.18.0.345.g5c9ce644c3-goog


^ permalink raw reply related	[relevance 20%]

* Git clone and case sensitivity
@ 2018-07-27  9:59  6% Paweł Paruzel
  2018-07-27 20:59  0% ` brian m. carlson
  0 siblings, 1 reply; 200+ results
From: Paweł Paruzel @ 2018-07-27  9:59 UTC (permalink / raw)
  To: git

Hi,

Lately, I have been wondering why my test files in repo are modified after I clone it. It turned out to be two files: boolStyle_t_f and boolStyle_T_F.
The system that pushed those files was case sensitive while my mac after High Sierra update had APFS which is by default case-insensitive. I highly suggest that git clone threw an exception when files are case sensitive and being cloned to a case insensitive system. This has caused problems with overriding files for test cases without any warning.

Thanks in advance.
Regards,
Pawel Paruzel

^ permalink raw reply	[relevance 6%]

* [GSoC][PATCH v4 12/20] rebase -i: remove unused modes and functions
  @ 2018-07-24 16:32  6%     ` Alban Gruin
    1 sibling, 0 replies; 200+ results
From: Alban Gruin @ 2018-07-24 16:32 UTC (permalink / raw)
  To: git
  Cc: Stefan Beller, Christian Couder, Pratik Karki,
	Johannes Schindelin, phillip.wood, gitster, Alban Gruin

This removes the modes `--skip-unnecessary-picks`, `--append-todo-help`,
and `--checkout-onto` from rebase--helper.c, the functions of
git-rebase--interactive.sh that were rendered useless by the rewrite of
complete_action(), and append_todo_help_to_file() from
rebase-interactive.c.

skip_unnecessary_picks() and checkout_onto() becomes static, as they are
only used inside of the sequencer.

Signed-off-by: Alban Gruin <alban.gruin@gmail.com>
---
 builtin/rebase--helper.c   | 23 ++----------------
 git-rebase--interactive.sh | 50 --------------------------------------
 rebase-interactive.c       | 22 -----------------
 rebase-interactive.h       |  1 -
 sequencer.c                |  8 +++---
 sequencer.h                |  4 ---
 6 files changed, 6 insertions(+), 102 deletions(-)

diff --git a/builtin/rebase--helper.c b/builtin/rebase--helper.c
index d7fa5a5062..6085527b2b 100644
--- a/builtin/rebase--helper.c
+++ b/builtin/rebase--helper.c
@@ -17,9 +17,8 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
 	int abbreviate_commands = 0, rebase_cousins = -1;
 	enum {
 		CONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_OIDS, EXPAND_OIDS,
-		CHECK_TODO_LIST, SKIP_UNNECESSARY_PICKS, REARRANGE_SQUASH,
-		ADD_EXEC, APPEND_TODO_HELP, EDIT_TODO, PREPARE_BRANCH,
-		CHECKOUT_ONTO, COMPLETE_ACTION
+		CHECK_TODO_LIST, REARRANGE_SQUASH, ADD_EXEC, EDIT_TODO, PREPARE_BRANCH,
+		COMPLETE_ACTION
 	} command = 0;
 	struct option options[] = {
 		OPT_BOOL(0, "ff", &opts.allow_ff, N_("allow fast-forward")),
@@ -44,21 +43,15 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
 			N_("expand commit ids in the todo list"), EXPAND_OIDS),
 		OPT_CMDMODE(0, "check-todo-list", &command,
 			N_("check the todo list"), CHECK_TODO_LIST),
-		OPT_CMDMODE(0, "skip-unnecessary-picks", &command,
-			N_("skip unnecessary picks"), SKIP_UNNECESSARY_PICKS),
 		OPT_CMDMODE(0, "rearrange-squash", &command,
 			N_("rearrange fixup/squash lines"), REARRANGE_SQUASH),
 		OPT_CMDMODE(0, "add-exec-commands", &command,
 			N_("insert exec commands in todo list"), ADD_EXEC),
-		OPT_CMDMODE(0, "append-todo-help", &command,
-			    N_("insert the help in the todo list"), APPEND_TODO_HELP),
 		OPT_CMDMODE(0, "edit-todo", &command,
 			    N_("edit the todo list during an interactive rebase"),
 			    EDIT_TODO),
 		OPT_CMDMODE(0, "prepare-branch", &command,
 			    N_("prepare the branch to be rebased"), PREPARE_BRANCH),
-		OPT_CMDMODE(0, "checkout-onto", &command,
-			    N_("checkout a commit"), CHECKOUT_ONTO),
 		OPT_CMDMODE(0, "complete-action", &command,
 			    N_("complete the action"), COMPLETE_ACTION),
 		OPT_END()
@@ -94,26 +87,14 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
 		return !!transform_todos(flags);
 	if (command == CHECK_TODO_LIST && argc == 1)
 		return !!check_todo_list();
-	if (command == SKIP_UNNECESSARY_PICKS && argc == 1) {
-		struct object_id oid;
-		int ret = skip_unnecessary_picks(&oid);
-
-		if (!ret)
-			puts(oid_to_hex(&oid));
-		return !!ret;
-	}
 	if (command == REARRANGE_SQUASH && argc == 1)
 		return !!rearrange_squash();
 	if (command == ADD_EXEC && argc == 2)
 		return !!sequencer_add_exec_commands(argv[1]);
-	if (command == APPEND_TODO_HELP && argc == 1)
-		return !!append_todo_help_to_file(0, keep_empty);
 	if (command == EDIT_TODO && argc == 1)
 		return !!edit_todo_list(flags);
 	if (command == PREPARE_BRANCH && argc == 2)
 		return !!prepare_branch_to_be_rebased(&opts, argv[1]);
-	if (command == CHECKOUT_ONTO && argc == 4)
-		return !!checkout_onto(&opts, argv[1], argv[2], argv[3]);
 	if (command == COMPLETE_ACTION && argc == 6)
 		return !!complete_action(&opts, flags, argv[1], argv[2], argv[3],
 					 argv[4], argv[5], autosquash);
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index 59dc4888a6..0d66c0f8b8 100644
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -16,56 +16,6 @@ todo="$state_dir"/git-rebase-todo
 GIT_CHERRY_PICK_HELP="$resolvemsg"
 export GIT_CHERRY_PICK_HELP
 
-comment_char=$(git config --get core.commentchar 2>/dev/null)
-case "$comment_char" in
-'' | auto)
-	comment_char="#"
-	;;
-?)
-	;;
-*)
-	comment_char=$(echo "$comment_char" | cut -c1)
-	;;
-esac
-
-die_abort () {
-	apply_autostash
-	rm -rf "$state_dir"
-	die "$1"
-}
-
-has_action () {
-	test -n "$(git stripspace --strip-comments <"$1")"
-}
-
-git_sequence_editor () {
-	if test -z "$GIT_SEQUENCE_EDITOR"
-	then
-		GIT_SEQUENCE_EDITOR="$(git config sequence.editor)"
-		if [ -z "$GIT_SEQUENCE_EDITOR" ]
-		then
-			GIT_SEQUENCE_EDITOR="$(git var GIT_EDITOR)" || return $?
-		fi
-	fi
-
-	eval "$GIT_SEQUENCE_EDITOR" '"$@"'
-}
-
-expand_todo_ids() {
-	git rebase--helper --expand-ids
-}
-
-collapse_todo_ids() {
-	git rebase--helper --shorten-ids
-}
-
-get_missing_commit_check_level () {
-	check_level=$(git config --get rebase.missingCommitsCheck)
-	check_level=${check_level:-ignore}
-	# Don't be case sensitive
-	printf '%s' "$check_level" | tr 'A-Z' 'a-z'
-}
-
 # Initiate an action. If the cannot be any
 # further action it  may exec a command
 # or exit and not return.
diff --git a/rebase-interactive.c b/rebase-interactive.c
index d8b9465597..f99e596d28 100644
--- a/rebase-interactive.c
+++ b/rebase-interactive.c
@@ -52,28 +52,6 @@ void append_todo_help(unsigned edit_todo, unsigned keep_empty,
 	}
 }
 
-int append_todo_help_to_file(unsigned edit_todo, unsigned keep_empty)
-{
-	struct strbuf buf = STRBUF_INIT;
-	FILE *todo;
-	int ret;
-
-	todo = fopen_or_warn(rebase_path_todo(), "a");
-	if (!todo)
-		return 1;
-
-	append_todo_help(edit_todo, keep_empty, &buf);
-
-	ret = fputs(buf.buf, todo);
-	if (ret < 0)
-		error_errno(_("could not append help text to '%s'"), rebase_path_todo());
-
-	fclose(todo);
-	strbuf_release(&buf);
-
-	return ret;
-}
-
 int edit_todo_list(unsigned flags)
 {
 	struct strbuf buf = STRBUF_INIT;
diff --git a/rebase-interactive.h b/rebase-interactive.h
index d33f3176b7..971da03776 100644
--- a/rebase-interactive.h
+++ b/rebase-interactive.h
@@ -3,7 +3,6 @@
 
 void append_todo_help(unsigned edit_todo, unsigned keep_empty,
 		      struct strbuf *buf);
-int append_todo_help_to_file(unsigned edit_todo, unsigned keep_empty);
 int edit_todo_list(unsigned flags);
 
 #endif
diff --git a/sequencer.c b/sequencer.c
index 28082fe13e..1c035ceec7 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -3173,9 +3173,9 @@ int prepare_branch_to_be_rebased(struct replay_opts *opts, const char *commit)
 	return 0;
 }
 
-int checkout_onto(struct replay_opts *opts,
-		  const char *onto_name, const char *onto,
-		  const char *orig_head)
+static int checkout_onto(struct replay_opts *opts,
+			 const char *onto_name, const char *onto,
+			 const char *orig_head)
 {
 	struct object_id oid;
 	const char *action = reflog_message(opts, "start", "checkout %s", onto_name);
@@ -4420,7 +4420,7 @@ static int rewrite_file(const char *path, const char *buf, size_t len)
 }
 
 /* skip picking commits whose parents are unchanged */
-int skip_unnecessary_picks(struct object_id *output_oid)
+static int skip_unnecessary_picks(struct object_id *output_oid)
 {
 	const char *todo_file = rebase_path_todo();
 	struct strbuf buf = STRBUF_INIT;
diff --git a/sequencer.h b/sequencer.h
index 3ab3791c8a..de9d4cf430 100644
--- a/sequencer.h
+++ b/sequencer.h
@@ -88,7 +88,6 @@ int sequencer_add_exec_commands(const char *command);
 int transform_todos(unsigned flags);
 enum missing_commit_check_level get_missing_commit_check_level(void);
 int check_todo_list(void);
-int skip_unnecessary_picks(struct object_id *output_oid);
 int complete_action(struct replay_opts *opts, unsigned flags,
 		    const char *shortrevisions, const char *onto_name,
 		    const char *onto, const char *orig_head, const char *cmd,
@@ -111,9 +110,6 @@ void commit_post_rewrite(const struct commit *current_head,
 			 const struct object_id *new_head);
 
 int prepare_branch_to_be_rebased(struct replay_opts *opts, const char *commit);
-int checkout_onto(struct replay_opts *opts,
-		  const char *onto_name, const char *onto,
-		  const char *orig_head);
 
 #define SUMMARY_INITIAL_COMMIT   (1 << 0)
 #define SUMMARY_SHOW_AUTHOR_DATE (1 << 1)
-- 
2.18.0


^ permalink raw reply related	[relevance 6%]

* Re: [PATCH 00/16] Consolidate reachability logic
  2018-07-18 12:28  0%         ` Johannes Schindelin
@ 2018-07-18 15:01  0%           ` Duy Nguyen
  0 siblings, 0 replies; 200+ results
From: Duy Nguyen @ 2018-07-18 15:01 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Derrick Stolee, Eric Sunshine, Stefan Beller, Ramsay Jones,
	gitgitgadget, Git Mailing List, Junio C Hamano

On Wed, Jul 18, 2018 at 2:30 PM Johannes Schindelin
<Johannes.Schindelin@gmx.de> wrote:
>
> Hi,
>
> On Mon, 16 Jul 2018, Derrick Stolee wrote:
>
> > On 7/16/2018 2:44 PM, Eric Sunshine wrote:
> > > On Mon, Jul 16, 2018 at 1:27 PM Stefan Beller <sbeller@google.com> wrote:
> > > > Another pain point of the Gadget is that CC's in the cover letter
> > > > do not work as I would imagine. The line
> > > >
> > > > CC: sbeller@google.com
> > > >
> > > > did not put that email into the cc field.
> > > gitgitgadget recognizes case-sensitive "Cc:" only[1].
> > >
> > > [1]:
> > > https://github.com/gitgitgadget/gitgitgadget/blob/c4805370f59532aa438283431b8ea7d4484c530f/lib/patch-series.ts#L188
> >
> > Thanks for everyone's patience while we improve gitgitgadget (and - in this
> > case - I learn how to use it).
>
> And let's please stop pretending that this GitGitGadget project is
> somebody else's problem.
>
> It is our best shot at addressing the *constant* pain point that is the code
> contribution process of Git.
>
> In other words: if you see something that you don't like about
> GitGitGadget, get your butts off the ground and contribute a fix.

Thank you for the frank words. I will choose to not review any mails
coming from GitGitGadget.
-- 
Duy

^ permalink raw reply	[relevance 0%]

* Re: [PATCH 00/16] Consolidate reachability logic
  2018-07-16 18:47  0%       ` Derrick Stolee
@ 2018-07-18 12:28  0%         ` Johannes Schindelin
  2018-07-18 15:01  0%           ` Duy Nguyen
  0 siblings, 1 reply; 200+ results
From: Johannes Schindelin @ 2018-07-18 12:28 UTC (permalink / raw)
  To: Derrick Stolee
  Cc: Eric Sunshine, Stefan Beller, Ramsay Jones, gitgitgadget,
	Git List, Junio C Hamano

Hi,

On Mon, 16 Jul 2018, Derrick Stolee wrote:

> On 7/16/2018 2:44 PM, Eric Sunshine wrote:
> > On Mon, Jul 16, 2018 at 1:27 PM Stefan Beller <sbeller@google.com> wrote:
> > > Another pain point of the Gadget is that CC's in the cover letter
> > > do not work as I would imagine. The line
> > >
> > > CC: sbeller@google.com
> > >
> > > did not put that email into the cc field.
> > gitgitgadget recognizes case-sensitive "Cc:" only[1].
> >
> > [1]:
> > https://github.com/gitgitgadget/gitgitgadget/blob/c4805370f59532aa438283431b8ea7d4484c530f/lib/patch-series.ts#L188
> 
> Thanks for everyone's patience while we improve gitgitgadget (and - in this
> case - I learn how to use it).

And let's please stop pretending that this GitGitGadget project is
somebody else's problem.

It is our best shot at addressing the *constant* pain point that is the code
contribution process of Git.

In other words: if you see something that you don't like about
GitGitGadget, get your butts off the ground and contribute a fix. The code
contribution process of GitGitGadget is very easy: open a PR at
https://github.com/gitgitgadget/gitgitgadget

Ciao,
Johannes

^ permalink raw reply	[relevance 0%]

* Re: [PATCH v4 2/7] t/t7510: check the validation of the new config gpg.format
  2018-07-17 21:31  5%   ` Junio C Hamano
@ 2018-07-18 10:36  0%     ` Henning Schild
  0 siblings, 0 replies; 200+ results
From: Henning Schild @ 2018-07-18 10:36 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: git, Eric Sunshine, Martin Ågren, Ben Toews, Jeff King,
	Taylor Blau, brian m . carlson

Am Tue, 17 Jul 2018 14:31:56 -0700
schrieb Junio C Hamano <gitster@pobox.com>:

> Henning Schild <henning.schild@siemens.com> writes:
> 
> > Test setting gpg.format to both invalid and valid values.
> >
> > Signed-off-by: Henning Schild <henning.schild@siemens.com>
> > ---
> >  t/t7510-signed-commit.sh | 9 +++++++++
> >  1 file changed, 9 insertions(+)
> >
> > diff --git a/t/t7510-signed-commit.sh b/t/t7510-signed-commit.sh
> > index 6e2015ed9..7bdad570c 100755
> > --- a/t/t7510-signed-commit.sh
> > +++ b/t/t7510-signed-commit.sh
> > @@ -227,4 +227,13 @@ test_expect_success GPG 'log.showsignature
> > behaves like --show-signature' ' grep "gpg: Good signature" actual
> >  '
> >  
> > +test_expect_success GPG 'check config gpg.format values' '
> > +	test_config gpg.format openpgp &&
> > +	git commit -S --amend -m "success" &&
> > +	test_config gpg.format OpEnPgP &&
> > +	test_must_fail git commit -S --amend -m "fail" &&  
> 
> This second one is a good demonstration that the value for this
> variable is case sensitive.
> 
> > +	test_config gpg.format malformed &&
> > +	test_must_fail git commit -S --amend -m "fail"  
> 
> And there is no longer a good reason to try another one.  Let's drop
> this last/third case.

Done.

Henning

> > +'
> > +
> >  test_done  


^ permalink raw reply	[relevance 0%]

* Re: [PATCH v4 2/7] t/t7510: check the validation of the new config gpg.format
  @ 2018-07-17 21:31  5%   ` Junio C Hamano
  2018-07-18 10:36  0%     ` Henning Schild
  0 siblings, 1 reply; 200+ results
From: Junio C Hamano @ 2018-07-17 21:31 UTC (permalink / raw)
  To: Henning Schild
  Cc: git, Eric Sunshine, Martin Ågren, Ben Toews, Jeff King,
	Taylor Blau, brian m . carlson

Henning Schild <henning.schild@siemens.com> writes:

> Test setting gpg.format to both invalid and valid values.
>
> Signed-off-by: Henning Schild <henning.schild@siemens.com>
> ---
>  t/t7510-signed-commit.sh | 9 +++++++++
>  1 file changed, 9 insertions(+)
>
> diff --git a/t/t7510-signed-commit.sh b/t/t7510-signed-commit.sh
> index 6e2015ed9..7bdad570c 100755
> --- a/t/t7510-signed-commit.sh
> +++ b/t/t7510-signed-commit.sh
> @@ -227,4 +227,13 @@ test_expect_success GPG 'log.showsignature behaves like --show-signature' '
>  	grep "gpg: Good signature" actual
>  '
>  
> +test_expect_success GPG 'check config gpg.format values' '
> +	test_config gpg.format openpgp &&
> +	git commit -S --amend -m "success" &&
> +	test_config gpg.format OpEnPgP &&
> +	test_must_fail git commit -S --amend -m "fail" &&

This second one is a good demonstration that the value for this
variable is case sensitive.

> +	test_config gpg.format malformed &&
> +	test_must_fail git commit -S --amend -m "fail"

And there is no longer a good reason to try another one.  Let's drop
this last/third case.

> +'
> +
>  test_done

^ permalink raw reply	[relevance 5%]

* [PATCH v4 0/7] X509 (gpgsm) commit signing support
@ 2018-07-17 12:50  3% Henning Schild
    0 siblings, 1 reply; 200+ results
From: Henning Schild @ 2018-07-17 12:50 UTC (permalink / raw)
  To: git
  Cc: Eric Sunshine, Junio C Hamano, Martin Ågren, Ben Toews,
	Jeff King, Taylor Blau, brian m . carlson, Henning Schild

Changes in v4:
 - make gpg.format matching case sensitive
 - (final?) coding style and wording changes

Changes in v3:
 - drop patches 1 and 2 known from < v3, see pu hs/push-cert-check-cleanup
 - dropped some testcases from p6, added two t7004 bad key/sig
 - ship a binary x509 certificate for tests, no generation anymore
 - silence gpgsm in tests
 - switch all tests to use test_config instead of "git config"
 - p4 deal with invalid input
 - p3 several refactorings, see "PATCH v2 5/9" discussion

Changes in v2:
 - removed trailing commas in array initializers and add leading space
 - replaced assert(0) with BUG in p5
 - consolidated 2 format lookups reusing get_format_data p5
 - changed from format "PGP" to "openpgp", later X509 to "x509"
 - use strcasecmp instead of strcmp for format matching
 - introduce gpg.<format>.program in p8, no gpg.programX509 anymore
 - moved t/7510 patch inbetween the two patches changing validation code
 - changed t/7510 patch to use test_config and test_must_fail

This series adds support for signing commits with gpgsm.

The first 5 patches prepare for the introduction of the actual feature in
patch 6.
Finally patch 7 extends the testsuite to cover the new feature.

This series can be seen as a follow up of a series that appeared under
the name "gpg-interface: Multiple signing tools" in april 2018 [1]. After
that series was not merged i decided to get my patches ready. The
original series aimed at being generic for any sort of signing tool, while
this series just introduced the X509 variant of gpg. (gpgsm)
I collected authors and reviewers of that first series and already put them
on cc.

[1] https://public-inbox.org/git/20180409204129.43537-1-mastahyeti@gmail.com/

Henning Schild (7):
  gpg-interface: add new config to select how to sign a commit
  t/t7510: check the validation of the new config gpg.format
  gpg-interface: introduce an abstraction for multiple gpg formats
  gpg-interface: do not hardcode the key string len anymore
  gpg-interface: introduce new config to select per gpg format program
  gpg-interface: introduce new signature format "x509" using gpgsm
  gpg-interface t: extend the existing GPG tests with GPGSM

 Documentation/config.txt   |  10 +++++
 gpg-interface.c            | 108 +++++++++++++++++++++++++++++++++++++--------
 t/lib-gpg.sh               |  28 +++++++++++-
 t/lib-gpg/gpgsm-gen-key.in |   8 ++++
 t/lib-gpg/gpgsm_cert.p12   | Bin 0 -> 2652 bytes
 t/t4202-log.sh             |  39 ++++++++++++++++
 t/t5534-push-signed.sh     |  52 ++++++++++++++++++++++
 t/t7004-tag.sh             |  13 ++++++
 t/t7030-verify-tag.sh      |  33 ++++++++++++++
 t/t7510-signed-commit.sh   |   9 ++++
 10 files changed, 281 insertions(+), 19 deletions(-)
 create mode 100644 t/lib-gpg/gpgsm-gen-key.in
 create mode 100644 t/lib-gpg/gpgsm_cert.p12

-- 
2.16.4


^ permalink raw reply	[relevance 3%]

* Re: [PATCH v2 7/9] gpg-interface: introduce new config to select per gpg format program
  2018-07-16 21:35  6%         ` Jeff King
@ 2018-07-16 21:56  0%           ` Junio C Hamano
  0 siblings, 0 replies; 200+ results
From: Junio C Hamano @ 2018-07-16 21:56 UTC (permalink / raw)
  To: Jeff King
  Cc: brian m. carlson, Henning Schild, git, Eric Sunshine,
	Martin Ågren, Ben Toews, Taylor Blau

Jeff King <peff@peff.net> writes:

> On Sat, Jul 14, 2018 at 06:13:47PM +0000, brian m. carlson wrote:
>
>> On Tue, Jul 10, 2018 at 12:56:38PM -0400, Jeff King wrote:
>> > On Tue, Jul 10, 2018 at 12:54:13PM -0400, Jeff King wrote:
>> > 
>> > > Should we allow:
>> > > 
>> > >   [gpg "OpenPGP"]
>> > >   program = whatever
>> > > 
>> > > given that we allow:
>> > > 
>> > >   [gpg]
>> > >   format = OpenPGP
>> > > 
>> > > ? I think just using strcasecmp() here would be sufficient. But I wonder
>> > > if it is a symptom of using the wrong tool (subsections) when we don't
>> > > need it.
>> > 
>> > I did just read the discussion in response to v1, where everybody told
>> > you the opposite. ;)
>> > 
>> > So I guess my question/points are more for brian and Junio.
>> 
>> I'm okay with us forcing "openpgp".  That seems sane enough for now, and
>> if people scream loudly, we can loosen it.
>
> Well, I specifically meant "are you sure subsections like this are a
> good idea". But it seems like people think so?

I admit that I did not even consider that there may be better tool
than using subsections to record this information.  What are the
possibilities you have in mind (if you have one)?

>
> If so, then I think the best route is just dictating that yes,
> gpg.format is case-sensitive because it is referencing
> gpg.<format>.program, which is itself case-sensitive (and "openpgp" is
> the right spelling).
>
> -Peff

^ permalink raw reply	[relevance 0%]

* Re: [PATCH v2 7/9] gpg-interface: introduce new config to select per gpg format program
  @ 2018-07-16 21:35  6%         ` Jeff King
  2018-07-16 21:56  0%           ` Junio C Hamano
  0 siblings, 1 reply; 200+ results
From: Jeff King @ 2018-07-16 21:35 UTC (permalink / raw)
  To: brian m. carlson, Henning Schild, git, Eric Sunshine,
	Junio C Hamano, Martin Ågren, Ben Toews, Taylor Blau

On Sat, Jul 14, 2018 at 06:13:47PM +0000, brian m. carlson wrote:

> On Tue, Jul 10, 2018 at 12:56:38PM -0400, Jeff King wrote:
> > On Tue, Jul 10, 2018 at 12:54:13PM -0400, Jeff King wrote:
> > 
> > > Should we allow:
> > > 
> > >   [gpg "OpenPGP"]
> > >   program = whatever
> > > 
> > > given that we allow:
> > > 
> > >   [gpg]
> > >   format = OpenPGP
> > > 
> > > ? I think just using strcasecmp() here would be sufficient. But I wonder
> > > if it is a symptom of using the wrong tool (subsections) when we don't
> > > need it.
> > 
> > I did just read the discussion in response to v1, where everybody told
> > you the opposite. ;)
> > 
> > So I guess my question/points are more for brian and Junio.
> 
> I'm okay with us forcing "openpgp".  That seems sane enough for now, and
> if people scream loudly, we can loosen it.

Well, I specifically meant "are you sure subsections like this are a
good idea". But it seems like people think so?

If so, then I think the best route is just dictating that yes,
gpg.format is case-sensitive because it is referencing
gpg.<format>.program, which is itself case-sensitive (and "openpgp" is
the right spelling).

-Peff

^ permalink raw reply	[relevance 6%]

* Re: [PATCH v3 2/7] t/t7510: check the validation of the new config gpg.format
  @ 2018-07-16 20:15  5%   ` Junio C Hamano
  0 siblings, 0 replies; 200+ results
From: Junio C Hamano @ 2018-07-16 20:15 UTC (permalink / raw)
  To: Henning Schild
  Cc: git, Eric Sunshine, Martin Ågren, Ben Toews, Jeff King,
	Taylor Blau, brian m . carlson

Henning Schild <henning.schild@siemens.com> writes:

> Test setting gpg.format to both invalid and valid values.
>
> Signed-off-by: Henning Schild <henning.schild@siemens.com>
> ---
>  t/t7510-signed-commit.sh | 9 +++++++++
>  1 file changed, 9 insertions(+)
>
> diff --git a/t/t7510-signed-commit.sh b/t/t7510-signed-commit.sh
> index 6e2015ed9..1b6a1dd90 100755
> --- a/t/t7510-signed-commit.sh
> +++ b/t/t7510-signed-commit.sh
> @@ -227,4 +227,13 @@ test_expect_success GPG 'log.showsignature behaves like --show-signature' '
>  	grep "gpg: Good signature" actual
>  '
>  
> +test_expect_success GPG 'check config gpg.format values' '
> +	test_config gpg.format openpgp &&
> +	git commit -S --amend -m "success" &&
> +	test_config gpg.format OpEnPgP &&
> +	git commit -S --amend -m "success" &&
> +	test_config gpg.format malformed &&
> +	test_must_fail git commit -S --amend -m "fail"
> +'
> +
>  test_done

Since we'd be doing case-sensitive value for gpg.format, this would
have to change in a later iteration, I guess.

^ permalink raw reply	[relevance 5%]

* Re: [PATCH 00/16] Consolidate reachability logic
  2018-07-16 18:44  5%     ` Eric Sunshine
@ 2018-07-16 18:47  0%       ` Derrick Stolee
  2018-07-18 12:28  0%         ` Johannes Schindelin
  0 siblings, 1 reply; 200+ results
From: Derrick Stolee @ 2018-07-16 18:47 UTC (permalink / raw)
  To: Eric Sunshine, Stefan Beller
  Cc: Ramsay Jones, Johannes Schindelin, gitgitgadget, Git List,
	Junio C Hamano

On 7/16/2018 2:44 PM, Eric Sunshine wrote:
> On Mon, Jul 16, 2018 at 1:27 PM Stefan Beller <sbeller@google.com> wrote:
>> Another pain point of the Gadget is that CC's in the cover letter
>> do not work as I would imagine. The line
>>
>> CC: sbeller@google.com
>>
>> did not put that email into the cc field.
> gitgitgadget recognizes case-sensitive "Cc:" only[1].
>
> [1]: https://github.com/gitgitgadget/gitgitgadget/blob/c4805370f59532aa438283431b8ea7d4484c530f/lib/patch-series.ts#L188

Thanks for everyone's patience while we improve gitgitgadget (and - in 
this case - I learn how to use it).

Thanks,

-Stolee


^ permalink raw reply	[relevance 0%]

* Re: [PATCH 00/16] Consolidate reachability logic
  @ 2018-07-16 18:44  5%     ` Eric Sunshine
  2018-07-16 18:47  0%       ` Derrick Stolee
  0 siblings, 1 reply; 200+ results
From: Eric Sunshine @ 2018-07-16 18:44 UTC (permalink / raw)
  To: Stefan Beller
  Cc: Ramsay Jones, Johannes Schindelin, gitgitgadget, Git List,
	Junio C Hamano

On Mon, Jul 16, 2018 at 1:27 PM Stefan Beller <sbeller@google.com> wrote:
> Another pain point of the Gadget is that CC's in the cover letter
> do not work as I would imagine. The line
>
> CC: sbeller@google.com
>
> did not put that email into the cc field.

gitgitgadget recognizes case-sensitive "Cc:" only[1].

[1]: https://github.com/gitgitgadget/gitgitgadget/blob/c4805370f59532aa438283431b8ea7d4484c530f/lib/patch-series.ts#L188

^ permalink raw reply	[relevance 5%]

* Re: [PATCH v2 7/9] gpg-interface: introduce new config to select per gpg format program
  @ 2018-07-10 16:54  4%   ` Jeff King
    0 siblings, 1 reply; 200+ results
From: Jeff King @ 2018-07-10 16:54 UTC (permalink / raw)
  To: Henning Schild
  Cc: git, Eric Sunshine, Junio C Hamano, Martin Ågren, Ben Toews,
	Taylor Blau, brian m . carlson

On Tue, Jul 10, 2018 at 10:52:29AM +0200, Henning Schild wrote:

> diff --git a/Documentation/config.txt b/Documentation/config.txt
> index ac373e3f4..c0bd80954 100644
> --- a/Documentation/config.txt
> +++ b/Documentation/config.txt
> @@ -1832,6 +1832,11 @@ gpg.format::
>  	Specifies which key format to use when signing with `--gpg-sign`.
>  	Default is "openpgp", that is also the only supported value.
>  
> +gpg.<format>.program::
> +	Use this to customize the program used for the signing format you
> +	chose. (see gpg.program) gpg.openpgp.program is a synonym for the
> +	legacy gpg.program.

This seems like a good step forward. This is similar to the
signingtool.$name.program I proposed earlier, but keeping it specific to
gpg, which makes sense.

On the other hand, do we anticipate the user ever being able to add
gpg.foo.program? I don't think so; we'll provide a limited set of
options. So we _could_ go with "gpg.openpgpProgram" or similar, and
later add "gpg.x509Program".

And one reason to do so might be...

> diff --git a/gpg-interface.c b/gpg-interface.c
> index ac2df498d..65098430f 100644
> --- a/gpg-interface.c
> +++ b/gpg-interface.c
> @@ -179,7 +179,7 @@ int git_gpg_config(const char *var, const char *value, void *cb)
>  		return git_config_string(&gpg_format, var, value);
>  	}
>  
> -	if (!strcmp(var, "gpg.program"))
> +	if (!strcmp(var, "gpg.program") || !strcmp(var, "gpg.openpgp.program"))
>  		return git_config_string(&gpg_formats[PGP_FMT].program, var,
>  					 value);

We normally match config keys with strcmp() because the config machinery
will have already normalized them to lowercase. But in Git's config
format, the subsection (the middle in a three-dot name) is less
restricted and is case-sensitive.

Should we allow:

  [gpg "OpenPGP"]
  program = whatever

given that we allow:

  [gpg]
  format = OpenPGP

? I think just using strcasecmp() here would be sufficient. But I wonder
if it is a symptom of using the wrong tool (subsections) when we don't
need it.

-Peff

^ permalink raw reply	[relevance 4%]

* [GSoC][PATCH v3 11/13] rebase--interactive: remove unused modes and functions
  @ 2018-07-10 12:15  5%   ` Alban Gruin
    1 sibling, 0 replies; 200+ results
From: Alban Gruin @ 2018-07-10 12:15 UTC (permalink / raw)
  To: git
  Cc: Stefan Beller, Christian Couder, Pratik Karki,
	Johannes Schindelin, phillip.wood, gitster, Alban Gruin

This removes the modes `--skip-unnecessary-picks`, `--append-todo-help`,
`--checkout-onto`, `--shorten-ids` and `--expand-ids` from
rebase--helper.c, the functions of git-rebase--interactive.sh that were
rendered useless by the rewrite of complete_action(), and
append_todo_help_to_file() from rebase-interactive.c.

skip_unnecessary_picks() and checkout_onto() becomes static, as they are
only used inside of the sequencer.

Signed-off-by: Alban Gruin <alban.gruin@gmail.com>
---
 builtin/rebase--helper.c   | 32 +++---------------------
 git-rebase--interactive.sh | 50 --------------------------------------
 rebase-interactive.c       | 22 -----------------
 rebase-interactive.h       |  1 -
 sequencer.c                |  8 +++---
 sequencer.h                |  4 ---
 6 files changed, 7 insertions(+), 110 deletions(-)

diff --git a/builtin/rebase--helper.c b/builtin/rebase--helper.c
index bb3698dba..8ab808da4 100644
--- a/builtin/rebase--helper.c
+++ b/builtin/rebase--helper.c
@@ -15,13 +15,10 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
 	struct replay_opts opts = REPLAY_OPTS_INIT;
 	unsigned flags = 0, keep_empty = 0, rebase_merges = 0, verbose = 0,
 		autosquash = 0;
-	int abbreviate_commands = 0, rebase_cousins = -1, ret;
-	const char *oid = NULL;
+	int abbreviate_commands = 0, rebase_cousins = -1;
 	enum {
-		CONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_OIDS, EXPAND_OIDS,
-		CHECK_TODO_LIST, SKIP_UNNECESSARY_PICKS, REARRANGE_SQUASH,
-		ADD_EXEC, APPEND_TODO_HELP, EDIT_TODO, PREPARE_BRANCH,
-		CHECKOUT_ONTO, COMPLETE_ACTION
+		CONTINUE = 1, ABORT, MAKE_SCRIPT, CHECK_TODO_LIST, REARRANGE_SQUASH,
+		ADD_EXEC, EDIT_TODO, PREPARE_BRANCH, COMPLETE_ACTION
 	} command = 0;
 	struct option options[] = {
 		OPT_BOOL(0, "ff", &opts.allow_ff, N_("allow fast-forward")),
@@ -40,27 +37,17 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
 				ABORT),
 		OPT_CMDMODE(0, "make-script", &command,
 			N_("make rebase script"), MAKE_SCRIPT),
-		OPT_CMDMODE(0, "shorten-ids", &command,
-			N_("shorten commit ids in the todo list"), SHORTEN_OIDS),
-		OPT_CMDMODE(0, "expand-ids", &command,
-			N_("expand commit ids in the todo list"), EXPAND_OIDS),
 		OPT_CMDMODE(0, "check-todo-list", &command,
 			N_("check the todo list"), CHECK_TODO_LIST),
-		OPT_CMDMODE(0, "skip-unnecessary-picks", &command,
-			N_("skip unnecessary picks"), SKIP_UNNECESSARY_PICKS),
 		OPT_CMDMODE(0, "rearrange-squash", &command,
 			N_("rearrange fixup/squash lines"), REARRANGE_SQUASH),
 		OPT_CMDMODE(0, "add-exec-commands", &command,
 			N_("insert exec commands in todo list"), ADD_EXEC),
-		OPT_CMDMODE(0, "append-todo-help", &command,
-			    N_("insert the help in the todo list"), APPEND_TODO_HELP),
 		OPT_CMDMODE(0, "edit-todo", &command,
 			    N_("edit the todo list during an interactive rebase"),
 			    EDIT_TODO),
 		OPT_CMDMODE(0, "prepare-branch", &command,
 			    N_("prepare the branch to be rebased"), PREPARE_BRANCH),
-		OPT_CMDMODE(0, "checkout-onto", &command,
-			    N_("checkout a commit"), CHECKOUT_ONTO),
 		OPT_CMDMODE(0, "complete-action", &command,
 			    N_("complete the action"), COMPLETE_ACTION),
 		OPT_END()
@@ -81,7 +68,6 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
 	flags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;
 	flags |= rebase_merges ? TODO_LIST_REBASE_MERGES : 0;
 	flags |= rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;
-	flags |= command == SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0;
 
 	if (rebase_cousins >= 0 && !rebase_merges)
 		warning(_("--[no-]rebase-cousins has no effect without "
@@ -93,28 +79,16 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
 		return !!sequencer_remove_state(&opts);
 	if (command == MAKE_SCRIPT && argc > 1)
 		return !!sequencer_make_script(stdout, argc, argv, flags);
-	if ((command == SHORTEN_OIDS || command == EXPAND_OIDS) && argc == 1)
-		return !!transform_todos(flags);
 	if (command == CHECK_TODO_LIST && argc == 1)
 		return !!check_todo_list();
-	if (command == SKIP_UNNECESSARY_PICKS && argc == 1) {
-		ret = !!skip_unnecessary_picks(&oid);
-		if (!ret && oid)
-			puts(oid);
-		return ret;
-	}
 	if (command == REARRANGE_SQUASH && argc == 1)
 		return !!rearrange_squash();
 	if (command == ADD_EXEC && argc == 2)
 		return !!sequencer_add_exec_commands(argv[1]);
-	if (command == APPEND_TODO_HELP && argc == 1)
-		return !!append_todo_help_to_file(0, keep_empty);
 	if (command == EDIT_TODO && argc == 1)
 		return !!edit_todo_list(flags);
 	if (command == PREPARE_BRANCH && argc == 2)
 		return !!prepare_branch_to_be_rebased(&opts, argv[1]);
-	if (command == CHECKOUT_ONTO && argc == 4)
-		return !!checkout_onto(&opts, argv[1], argv[2], argv[3]);
 	if (command == COMPLETE_ACTION && argc == 6)
 		return !!complete_action(&opts, flags, argv[1], argv[2], argv[3],
 					 argv[4], argv[5], autosquash, keep_empty);
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index 59dc4888a..0d66c0f8b 100644
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -16,56 +16,6 @@ todo="$state_dir"/git-rebase-todo
 GIT_CHERRY_PICK_HELP="$resolvemsg"
 export GIT_CHERRY_PICK_HELP
 
-comment_char=$(git config --get core.commentchar 2>/dev/null)
-case "$comment_char" in
-'' | auto)
-	comment_char="#"
-	;;
-?)
-	;;
-*)
-	comment_char=$(echo "$comment_char" | cut -c1)
-	;;
-esac
-
-die_abort () {
-	apply_autostash
-	rm -rf "$state_dir"
-	die "$1"
-}
-
-has_action () {
-	test -n "$(git stripspace --strip-comments <"$1")"
-}
-
-git_sequence_editor () {
-	if test -z "$GIT_SEQUENCE_EDITOR"
-	then
-		GIT_SEQUENCE_EDITOR="$(git config sequence.editor)"
-		if [ -z "$GIT_SEQUENCE_EDITOR" ]
-		then
-			GIT_SEQUENCE_EDITOR="$(git var GIT_EDITOR)" || return $?
-		fi
-	fi
-
-	eval "$GIT_SEQUENCE_EDITOR" '"$@"'
-}
-
-expand_todo_ids() {
-	git rebase--helper --expand-ids
-}
-
-collapse_todo_ids() {
-	git rebase--helper --shorten-ids
-}
-
-get_missing_commit_check_level () {
-	check_level=$(git config --get rebase.missingCommitsCheck)
-	check_level=${check_level:-ignore}
-	# Don't be case sensitive
-	printf '%s' "$check_level" | tr 'A-Z' 'a-z'
-}
-
 # Initiate an action. If the cannot be any
 # further action it  may exec a command
 # or exit and not return.
diff --git a/rebase-interactive.c b/rebase-interactive.c
index d8b946559..f99e596d2 100644
--- a/rebase-interactive.c
+++ b/rebase-interactive.c
@@ -52,28 +52,6 @@ void append_todo_help(unsigned edit_todo, unsigned keep_empty,
 	}
 }
 
-int append_todo_help_to_file(unsigned edit_todo, unsigned keep_empty)
-{
-	struct strbuf buf = STRBUF_INIT;
-	FILE *todo;
-	int ret;
-
-	todo = fopen_or_warn(rebase_path_todo(), "a");
-	if (!todo)
-		return 1;
-
-	append_todo_help(edit_todo, keep_empty, &buf);
-
-	ret = fputs(buf.buf, todo);
-	if (ret < 0)
-		error_errno(_("could not append help text to '%s'"), rebase_path_todo());
-
-	fclose(todo);
-	strbuf_release(&buf);
-
-	return ret;
-}
-
 int edit_todo_list(unsigned flags)
 {
 	struct strbuf buf = STRBUF_INIT;
diff --git a/rebase-interactive.h b/rebase-interactive.h
index c0ba83be3..47025c47a 100644
--- a/rebase-interactive.h
+++ b/rebase-interactive.h
@@ -6,7 +6,6 @@
 
 void append_todo_help(unsigned edit_todo, unsigned keep_empty,
 		      struct strbuf *buf);
-int append_todo_help_to_file(unsigned edit_todo, unsigned keep_empty);
 int edit_todo_list(unsigned flags);
 
 #endif
diff --git a/sequencer.c b/sequencer.c
index da975285d..6ceada4a9 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -3166,9 +3166,9 @@ int prepare_branch_to_be_rebased(struct replay_opts *opts, const char *commit)
 	return 0;
 }
 
-int checkout_onto(struct replay_opts *opts,
-		  const char *onto_name, const char *onto,
-		  const char *orig_head)
+static int checkout_onto(struct replay_opts *opts,
+			 const char *onto_name, const char *onto,
+			 const char *orig_head)
 {
 	struct object_id oid;
 	const char *action = reflog_message(opts, "start", "checkout %s", onto_name);
@@ -4396,7 +4396,7 @@ static int rewrite_file(const char *path, const char *buf, size_t len)
 }
 
 /* skip picking commits whose parents are unchanged */
-int skip_unnecessary_picks(const char **output_oid)
+static int skip_unnecessary_picks(const char **output_oid)
 {
 	const char *todo_file = rebase_path_todo();
 	struct strbuf buf = STRBUF_INIT;
diff --git a/sequencer.h b/sequencer.h
index 02772b927..b9f9e7827 100644
--- a/sequencer.h
+++ b/sequencer.h
@@ -88,7 +88,6 @@ int sequencer_add_exec_commands(const char *command);
 int transform_todos(unsigned flags);
 enum missing_commit_check_level get_missing_commit_check_level(void);
 int check_todo_list(void);
-int skip_unnecessary_picks(const char **output_oid);
 int complete_action(struct replay_opts *opts, unsigned flags,
 		    const char *shortrevisions, const char *onto_name,
 		    const char *onto, const char *orig_head, const char *cmd,
@@ -111,9 +110,6 @@ void commit_post_rewrite(const struct commit *current_head,
 			 const struct object_id *new_head);
 
 int prepare_branch_to_be_rebased(struct replay_opts *opts, const char *commit);
-int checkout_onto(struct replay_opts *opts,
-		  const char *onto_name, const char *onto,
-		  const char *orig_head);
 
 #define SUMMARY_INITIAL_COMMIT   (1 << 0)
 #define SUMMARY_SHOW_AUTHOR_DATE (1 << 1)
-- 
2.18.0


^ permalink raw reply related	[relevance 5%]

* Re: [PATCH v4] Documentation: declare "core.ignoreCase" as internal variable
  2018-06-28 11:21  6% ` [PATCH v4] Documentation: declare "core.ignoreCase" " Marc Strapetz
@ 2018-06-28 16:58  0%   ` Torsten Bögershausen
  0 siblings, 0 replies; 200+ results
From: Torsten Bögershausen @ 2018-06-28 16:58 UTC (permalink / raw)
  To: Marc Strapetz, git; +Cc: sunshine, gitster, bturner

On 28.06.18 13:21, Marc Strapetz wrote:
> The current description of "core.ignoreCase" reads like an option which
> is intended to be changed by the user while it's actually expected to
> be set by Git on initialization only. Subsequently, Git relies on the
> proper configuration of this variable, as noted by Bryan Turner [1]:
> 
>     Git on a case-insensitive filesystem (APFS, HFS+, FAT32, exFAT,
>     vFAT, NTFS, etc.) is not designed to be run with anything other
>     than core.ignoreCase=true.
> 
> [1] https://marc.info/?l=git&m=152998665813997&w=2
>     mid:CAGyf7-GeE8jRGPkME9rHKPtHEQ6P1+ebpMMWAtMh01uO3bfy8w@mail.gmail.com
> 
> Signed-off-by: Marc Strapetz <marc.strapetz@syntevo.com>
> ---
>  Documentation/config.txt | 9 ++++++---
>  1 file changed, 6 insertions(+), 3 deletions(-)
> 
> diff --git a/Documentation/config.txt b/Documentation/config.txt
> index 1cc18a828..c70cfe956 100644
> --- a/Documentation/config.txt
> +++ b/Documentation/config.txt
> @@ -390,16 +390,19 @@ core.hideDotFiles::
>  	default mode is 'dotGitOnly'.
>  
>  core.ignoreCase::
> -	If true, this option enables various workarounds to enable
> +	Internal variable which enables various workarounds to enable
>  	Git to work better on filesystems that are not case sensitive,
> -	like FAT. For example, if a directory listing finds
> -	"makefile" when Git expects "Makefile", Git will assume
> +	like APFS, HFS+, FAT, NTFS, etc. For example, if a directory listing
> +	finds "makefile" when Git expects "Makefile", Git will assume
>  	it is really the same file, and continue to remember it as
>  	"Makefile".
>  +
>  The default is false, except linkgit:git-clone[1] or linkgit:git-init[1]
>  will probe and set core.ignoreCase true if appropriate when the repository
>  is created.
> ++
> +Git relies on the proper configuration of this variable for your operating
> +and file system. Modifying this value may result in unexpected behavior.
>  
>  core.precomposeUnicode::
>  	This option is only used by Mac OS implementation of Git.
> 

Looks good to me

^ permalink raw reply	[relevance 0%]

* [PATCH v4] Documentation: declare "core.ignoreCase" as internal variable
      2018-06-26 21:50  6% ` [PATCH v3] " Marc Strapetz
@ 2018-06-28 11:21  6% ` Marc Strapetz
  2018-06-28 16:58  0%   ` Torsten Bögershausen
  2 siblings, 1 reply; 200+ results
From: Marc Strapetz @ 2018-06-28 11:21 UTC (permalink / raw)
  To: git; +Cc: Marc Strapetz, sunshine, gitster, bturner

The current description of "core.ignoreCase" reads like an option which
is intended to be changed by the user while it's actually expected to
be set by Git on initialization only. Subsequently, Git relies on the
proper configuration of this variable, as noted by Bryan Turner [1]:

    Git on a case-insensitive filesystem (APFS, HFS+, FAT32, exFAT,
    vFAT, NTFS, etc.) is not designed to be run with anything other
    than core.ignoreCase=true.

[1] https://marc.info/?l=git&m=152998665813997&w=2
    mid:CAGyf7-GeE8jRGPkME9rHKPtHEQ6P1+ebpMMWAtMh01uO3bfy8w@mail.gmail.com

Signed-off-by: Marc Strapetz <marc.strapetz@syntevo.com>
---
 Documentation/config.txt | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/Documentation/config.txt b/Documentation/config.txt
index 1cc18a828..c70cfe956 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -390,16 +390,19 @@ core.hideDotFiles::
 	default mode is 'dotGitOnly'.
 
 core.ignoreCase::
-	If true, this option enables various workarounds to enable
+	Internal variable which enables various workarounds to enable
 	Git to work better on filesystems that are not case sensitive,
-	like FAT. For example, if a directory listing finds
-	"makefile" when Git expects "Makefile", Git will assume
+	like APFS, HFS+, FAT, NTFS, etc. For example, if a directory listing
+	finds "makefile" when Git expects "Makefile", Git will assume
 	it is really the same file, and continue to remember it as
 	"Makefile".
 +
 The default is false, except linkgit:git-clone[1] or linkgit:git-init[1]
 will probe and set core.ignoreCase true if appropriate when the repository
 is created.
++
+Git relies on the proper configuration of this variable for your operating
+and file system. Modifying this value may result in unexpected behavior.
 
 core.precomposeUnicode::
 	This option is only used by Mac OS implementation of Git.
-- 
2.17.0.rc0.3.gb1b5a51b2


^ permalink raw reply related	[relevance 6%]

* Re: [PATCH v3] Documentation: declare "core.ignorecase" as internal variable
  2018-06-26 21:50  6% ` [PATCH v3] " Marc Strapetz
@ 2018-06-27 19:11  0%   ` Junio C Hamano
  0 siblings, 0 replies; 200+ results
From: Junio C Hamano @ 2018-06-27 19:11 UTC (permalink / raw)
  To: git; +Cc: sunshine, bturner, Marc Strapetz

Marc Strapetz <marc.strapetz@syntevo.com> writes:

> [1. text/plain]
> The current description of "core.ignoreCase" reads like an option which
> is intended to be changed by the user while it's actually expected to
> be set by Git on initialization only. Subsequently, Git relies on the
> proper configuration of this variable, as noted by Bryan Turner [1]:
> 
>     Git on a case-insensitive filesystem (APFS, HFS+, FAT32, exFAT,
>     vFAT, NTFS, etc.) is not designed to be run with anything other
>     than core.ignoreCase=true.
> 
> [1] https://marc.info/?l=git&m=152998665813997&w=2
>     mid:CAGyf7-GeE8jRGPkME9rHKPtHEQ6P1+ebpMMWAtMh01uO3bfy8w@mail.gmail.com
> 
> Signed-off-by: Marc Strapetz <marc.strapetz@syntevo.com>
> ---
>  Documentation/config.txt | 9 ++++++---
>  1 file changed, 6 insertions(+), 3 deletions(-)
> 

Hmph.  Do other people have difficulty applying this patch to their
trees?  It is just several lines long so I could retype it myself,
but I guess "Content-Type: text/plain; charset=utf-8; format=flowed"
has destroyed formatting of the patch rather badly.

> diff --git a/Documentation/config.txt b/Documentation/config.txt
> index 1cc18a828..c70cfe956 100644
> --- a/Documentation/config.txt
> +++ b/Documentation/config.txt
> @@ -390,16 +390,19 @@ core.hideDotFiles::
>  	default mode is 'dotGitOnly'.
>
>  core.ignoreCase::
> -	If true, this option enables various workarounds to enable
> +	Internal variable which enables various workarounds to enable
>  	Git to work better on filesystems that are not case sensitive,
> -	like FAT. For example, if a directory listing finds
> -	"makefile" when Git expects "Makefile", Git will assume
> +	like APFS, HFS+, FAT, NTFS, etc. For example, if a directory listing
> +	finds "makefile" when Git expects "Makefile", Git will assume
>  	it is really the same file, and continue to remember it as
>  	"Makefile".
>  +
>  The default is false, except linkgit:git-clone[1] or linkgit:git-init[1]
>  will probe and set core.ignoreCase true if appropriate when the repository
>  is created.
> ++
> +Git relies on the proper configuration of this variable for your operating
> +and file system. Modifying this value may result in unexpected behavior.
>
>  core.precomposeUnicode::
>  	This option is only used by Mac OS implementation of Git.

^ permalink raw reply	[relevance 0%]

* [PATCH v3] Documentation: declare "core.ignorecase" as internal variable
    @ 2018-06-26 21:50  6% ` Marc Strapetz
  2018-06-27 19:11  0%   ` Junio C Hamano
  2018-06-28 11:21  6% ` [PATCH v4] Documentation: declare "core.ignoreCase" " Marc Strapetz
  2 siblings, 1 reply; 200+ results
From: Marc Strapetz @ 2018-06-26 21:50 UTC (permalink / raw)
  To: git; +Cc: sunshine, gitster, bturner

The current description of "core.ignoreCase" reads like an option which
is intended to be changed by the user while it's actually expected to
be set by Git on initialization only. Subsequently, Git relies on the
proper configuration of this variable, as noted by Bryan Turner [1]:

     Git on a case-insensitive filesystem (APFS, HFS+, FAT32, exFAT,
     vFAT, NTFS, etc.) is not designed to be run with anything other
     than core.ignoreCase=true.

[1] https://marc.info/?l=git&m=152998665813997&w=2
     mid:CAGyf7-GeE8jRGPkME9rHKPtHEQ6P1+ebpMMWAtMh01uO3bfy8w@mail.gmail.com

Signed-off-by: Marc Strapetz <marc.strapetz@syntevo.com>
---
  Documentation/config.txt | 9 ++++++---
  1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/Documentation/config.txt b/Documentation/config.txt
index 1cc18a828..c70cfe956 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -390,16 +390,19 @@ core.hideDotFiles::
  	default mode is 'dotGitOnly'.

  core.ignoreCase::
-	If true, this option enables various workarounds to enable
+	Internal variable which enables various workarounds to enable
  	Git to work better on filesystems that are not case sensitive,
-	like FAT. For example, if a directory listing finds
-	"makefile" when Git expects "Makefile", Git will assume
+	like APFS, HFS+, FAT, NTFS, etc. For example, if a directory listing
+	finds "makefile" when Git expects "Makefile", Git will assume
  	it is really the same file, and continue to remember it as
  	"Makefile".
  +
  The default is false, except linkgit:git-clone[1] or linkgit:git-init[1]
  will probe and set core.ignoreCase true if appropriate when the repository
  is created.
++
+Git relies on the proper configuration of this variable for your operating
+and file system. Modifying this value may result in unexpected behavior.

  core.precomposeUnicode::
  	This option is only used by Mac OS implementation of Git.
-- 
2.17.0.rc0.3.gb1b5a51b2


^ permalink raw reply related	[relevance 6%]

* Re: [BUG] url schemes should be case-insensitive
  2018-06-26 17:09  5%     ` Junio C Hamano
@ 2018-06-26 18:27  0%       ` Jeff King
  0 siblings, 0 replies; 200+ results
From: Jeff King @ 2018-06-26 18:27 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

On Tue, Jun 26, 2018 at 10:09:58AM -0700, Junio C Hamano wrote:

> > It may also interact in a funny way with our allowed-protocol code, if
> > "SSH" gets a pass as "ssh" under the default config, but actually runs
> > the otherwise-disallowed git-remote-SSH (though one would _hope_ if you
> > have such a git-remote-SSH that it behaves just like an ssh remote).
> 
> True.  I did not offhand recall how protocol whitelist matches the
> protocol name with config, but transport.c::get_protocol_config()
> seems to say that the <name> part of "protocol.<name>.allow" is case
> sensitive, and we match known-safe (and known-unsafe "ext::")
> protocols with strcmp() not strcasecmp().  We need to figure out the
> implications of allowing SSH:// not to error out but pretending as
> if it were ssh:// on those who have protocol.ssh.allow defined.

That function is actually a little tricky, because we feed it mostly
from string literals (so we end up in the ssh code path, and then feed
it "ssh"). But I think for remote-helpers we feed it literally from the
URL we got fed.

So yeah, we would not want to allow EXT::"rm -rf /" to slip past the
known-unsafe match. Any normalization should happen before then
(probably right in transport_helper_init).

Come to think of it, that's already sort-of an issue now. If you have a
case-insensitive filesystem, then EXT:: is going to pass this check, but
still run git-remote-ext. We're saved there somewhat by the fact that
the default is to reject unknown helpers in submodules (otherwise, we'd
have that horrible submodule bug all over again).

That goes beyond just cases, too. On HFS+ I wonder if I could ask for
"\u{0200}ext::" and run git-remote-ext.

> > I think it doesn't help much if the user does not know what a remote
> > helper is, or why Git is looking for one.
> 
> True.  
> 
> 	$ git clone SSH://example.com/repo.git
> 	fatal: unable to handle URL that begins with SSH://
> 
> would be clear enough, perhaps?  At least this line of change is a
> small first step that would improve the situation without potential
> to break anybody who has been abusing the case sensitivity loophole.

Yeah, certainly the advice is orthogonal to any behavior changes. The
original report complained of:

  $ git clone SSH://...
  fatal: 'remote-SSH' is not a git command. See 'git --help'.

but since 6b02de3b9d in 2010 we say:

  fatal: Unable to find remote helper for 'SSH'

So I actually wonder if there is something else going on. I find it hard
to believe the OP is using something older than Git v1.7.0. They did
appear to be on Windows, though. Is it possible our ENOENT detection
from start_command() is not accurate on Windows?

-Peff

^ permalink raw reply	[relevance 0%]

* Re: [BUG] url schemes should be case-insensitive
  @ 2018-06-26 17:09  5%     ` Junio C Hamano
  2018-06-26 18:27  0%       ` Jeff King
  0 siblings, 1 reply; 200+ results
From: Junio C Hamano @ 2018-06-26 17:09 UTC (permalink / raw)
  To: Jeff King; +Cc: git

Jeff King <peff@peff.net> writes:

>> > We seem to match url schemes case-sensitively:
>> >
>> >   $ git clone SSH://example.com/repo.git
>> >   Cloning into 'repo'...
>> >   fatal: Unable to find remote helper for 'SSH'
>> >
>> > whereas rfc3986 is clear that the scheme portion is case-insensitive.
>> > We probably ought to match at least our internal ones with strcasecmp.
>> 
>> That may break if somebody at DevToolGroup@$BIGCOMPANY got cute and
>> named their custom remote helper SSH:// that builds on top of the
>> normal ssh:// protocol with something extra and gave it to their
>> developers (and they named the http counterpart that has the same
>> extra HTTP://, of course).
>
> True, though I am on the fence whether that is a property worth
> maintaining. AFAIK it was not planned and is just a "this is how it
> happened to work" case that is (IMHO) doing the wrong thing.

FWIW, I fully agree with the assessment; sorry for not saying that
together with the devil's advocate comment to save a round-tip.

> It may also interact in a funny way with our allowed-protocol code, if
> "SSH" gets a pass as "ssh" under the default config, but actually runs
> the otherwise-disallowed git-remote-SSH (though one would _hope_ if you
> have such a git-remote-SSH that it behaves just like an ssh remote).

True.  I did not offhand recall how protocol whitelist matches the
protocol name with config, but transport.c::get_protocol_config()
seems to say that the <name> part of "protocol.<name>.allow" is case
sensitive, and we match known-safe (and known-unsafe "ext::")
protocols with strcmp() not strcasecmp().  We need to figure out the
implications of allowing SSH:// not to error out but pretending as
if it were ssh:// on those who have protocol.ssh.allow defined.

>> > We could probably also give an advise() message in the above output,
>> > suggesting that the problem is likely one of:
>> >
>> >   1. They misspelled the scheme.
>> >
>> >   2. They need to install the appropriate helper.
>> >
>> > This may be a good topic for somebody looking for low-hanging fruit to
>> > get involved in development (I'd maybe call it a #leftoverbits, but
>> > since I didn't start on it, I'm not sure if it counts as "left over" ;)).
>> [..]
>> It may probably be a good idea to do an advice, but I'd think
>> "Untable to find remote helper for 'SSH'" may be clear enough.  If
>> anything, perhaps saying "remote helper for 'SSH' protocol" would
>> make it even clear?  I dunno.
>
> I think it doesn't help much if the user does not know what a remote
> helper is, or why Git is looking for one.

True.  

	$ git clone SSH://example.com/repo.git
	fatal: unable to handle URL that begins with SSH://

would be clear enough, perhaps?  At least this line of change is a
small first step that would improve the situation without potential
to break anybody who has been abusing the case sensitivity loophole.


^ permalink raw reply	[relevance 5%]

* Re: Unexpected ignorecase=false behavior on Windows
  2018-06-25 16:34  6%       ` Junio C Hamano
@ 2018-06-25 17:38  6%         ` Bryan Turner
  0 siblings, 0 replies; 200+ results
From: Bryan Turner @ 2018-06-25 17:38 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Marc Strapetz, j6t, Git Users

On Mon, Jun 25, 2018 at 9:34 AM Junio C Hamano <gitster@pobox.com> wrote:
>
> Bryan Turner <bturner@atlassian.com> writes:
>
> > Git on Windows is not designed to run with anything other than
> > core.ignoreCase=true, and attempting to do so will cause unexpected
> > behavior.
>
> Even though I fully agree with your conclusion that the document
> must make it crystal clear that core.ignoreCase must be set to
> reflect the reality, I found the above statement misleading and do
> not want it to be used as the basis of a documentation update.  But
> it is possible that I am misunderstanding the current state of
> affairs.
>
> Is the case insensitivity that deeply ingrained in the Git for
> Windows code?
>
> IOW, even if the code used to build Git for Windows were executed on
> a case sensitive filesystem, is there a case-smashing code on _our_
> side that kicks in to cause unexpected behaviour, _even_ when
> core.ignorecase is set to false to match (hypothetical) reality?
>
> To put it yet another way, if a case sensitive filesystem were
> available, wouldn't running "git init" from Git for Windows in a
> directory on such a filesytem set core.ignoreCase to false in the
> resulting repository and from then on wouldn't everything work fine?
>
> If my suspicion (i.e. the code for Git for Windows is perfectly
> fine---it is just the users are not running with case sensitive
> filesystems and flipping core.ignoreCase to true does not make case
> incapable filesystems suddenly capable) is correct, then it is not
> "Git on Windows is not designed to run" from two angles.  (1) it is
> not just Git for Windows---Git running on UNIX that mounts VFAT, or
> Git running on macOS with default HFS+, would exhibit the same
> symptom, and (2) it is not "Git is not designed to run"---it is
> core.ignoreCase that is not designed to be a way to make case
> incapable filesystems suddenly capable of distinguishing cases in
> filesystems.

Apologies for the unclear word choice. Given Git was designed first to
work with case-sensitive filesystems, certainly the obvious (and
correct) conclusion is that Git itself is fine in a case-sensitive
environment. It wasn't my intention to suggest otherwise.

Note that my word choice was not "Git _for_ Windows", however; it was
"Git _on_ Windows". (This still doesn't change the correctness of your
clarification, let me be quick to add. I'm only pointing it out
because it's relevant to what I intended the comment to say.) On
Windows, the default filesystem is NTFS, and NTFS is not case
sensitive. Hence, Git on Windows (by which I'm implying Git on NTFS),
is not designed to run with anything other than core.ignoreCase=true,
because that setting aligns Git's expectations with how the underlying
filesystem actually works. In other words, Git on a case-insensitive
filesystem (APFS, HFS+, FAT32, exFAT, vFAT, NTFS, etc.) is not
designed to be run with anything other than core.ignoreCase=true.

Bryan

>
> Thanks.

^ permalink raw reply	[relevance 6%]

* Re: Unexpected ignorecase=false behavior on Windows
  @ 2018-06-25 16:34  6%       ` Junio C Hamano
  2018-06-25 17:38  6%         ` Bryan Turner
  0 siblings, 1 reply; 200+ results
From: Junio C Hamano @ 2018-06-25 16:34 UTC (permalink / raw)
  To: Bryan Turner; +Cc: marc.strapetz, j6t, Git Users

Bryan Turner <bturner@atlassian.com> writes:

> Git on Windows is not designed to run with anything other than
> core.ignoreCase=true, and attempting to do so will cause unexpected
> behavior.

Even though I fully agree with your conclusion that the document
must make it crystal clear that core.ignoreCase must be set to
reflect the reality, I found the above statement misleading and do
not want it to be used as the basis of a documentation update.  But
it is possible that I am misunderstanding the current state of
affairs.

Is the case insensitivity that deeply ingrained in the Git for
Windows code?

IOW, even if the code used to build Git for Windows were executed on
a case sensitive filesystem, is there a case-smashing code on _our_
side that kicks in to cause unexpected behaviour, _even_ when
core.ignorecase is set to false to match (hypothetical) reality?

To put it yet another way, if a case sensitive filesystem were
available, wouldn't running "git init" from Git for Windows in a
directory on such a filesytem set core.ignoreCase to false in the
resulting repository and from then on wouldn't everything work fine?

If my suspicion (i.e. the code for Git for Windows is perfectly
fine---it is just the users are not running with case sensitive
filesystems and flipping core.ignoreCase to true does not make case
incapable filesystems suddenly capable) is correct, then it is not
"Git on Windows is not designed to run" from two angles.  (1) it is
not just Git for Windows---Git running on UNIX that mounts VFAT, or
Git running on macOS with default HFS+, would exhibit the same
symptom, and (2) it is not "Git is not designed to run"---it is
core.ignoreCase that is not designed to be a way to make case
incapable filesystems suddenly capable of distinguishing cases in
filesystems.

Thanks.

^ permalink raw reply	[relevance 6%]

* Re: [PATCH v2] Documentation: declare "core.ignorecase" as internal variable
  @ 2018-06-24 13:47  0%   ` Torsten Bögershausen
  0 siblings, 0 replies; 200+ results
From: Torsten Bögershausen @ 2018-06-24 13:47 UTC (permalink / raw)
  To: Marc Strapetz; +Cc: git, sunshine

On Sun, Jun 24, 2018 at 12:44:26PM +0200, Marc Strapetz wrote:
> The current description of "core.ignoreCase" reads like an option which
> is intended to be changed by the user while it's actually expected to
> be set by Git on initialization only. This is especially important for
> Git for Windows, as noted by Bryan Turner [1]:
> 
>     Git on Windows is not designed to run with anything other than
>     core.ignoreCase=true, and attempting to do so will cause
>     unexpected behavior. In other words, it's not a behavior toggle so
>     user's can request the functionality to work one way or the other;
>     it's an implementation detail that `git init` and `git clone` set
>     when a repository is created purely so they don't have to probe
>     the file system each time you run a `git` command.

This is a nice explanation, thanaks for that,
Some users of Mac OS or SAMBA will see core.ignoreCase=true, and are not
supposed to change it.

The same explanation (Git for Windows)
is alse valid for HFS+ and APFS under Mac OS and VFAT under all OS.
(or even an ext4 file system under Linux exported to Mac OS using SAMBA)

May be something like this?

     Git on a case insensitve file system (Windows, Mac OS, VFAT, SAMBA)
     is not designed to run with anything other than
     core.ignoreCase=true, and attempting to do so will cause
     unexpected behavior. In other words, it's not a behavior toggle so
    .....



> 
> [1] https://marc.info/?l=git&m=152972992729761&w=2
> 
> Signed-off-by: Marc Strapetz <marc.strapetz@syntevo.com>
> ---
>  Documentation/config.txt | 4 ++--
>  1 file changed, 2 insertions(+), 2 deletions(-)
> 
> diff --git a/Documentation/config.txt b/Documentation/config.txt
> index ab641bf5a..c25693828 100644
> --- a/Documentation/config.txt
> +++ b/Documentation/config.txt
> @@ -390,7 +390,7 @@ core.hideDotFiles::
>  	default mode is 'dotGitOnly'.
> 
>  core.ignoreCase::
> -	If true, this option enables various workarounds to enable
> +	Internal variable which enables various workarounds to enable
>  	Git to work better on filesystems that are not case sensitive,
>  	like FAT. For example, if a directory listing finds
>  	"makefile" when Git expects "Makefile", Git will assume
> @@ -399,7 +399,7 @@ core.ignoreCase::
>  +
>  The default is false, except linkgit:git-clone[1] or linkgit:git-init[1]
>  will probe and set core.ignoreCase true if appropriate when the repository
> -is created.
> +is created. Modifying this value afterwards may result in unexpected
> behavior.
> 
>  core.precomposeUnicode::
>  	This option is only used by Mac OS implementation of Git.
> -- 
> 2.17.0.rc0.3.gb1b5a51b2

^ permalink raw reply	[relevance 0%]

Results 201-400 of ~2000   |  | reverse | options above
-- pct% links below jump to the message on this page, permalinks otherwise --
2018-06-22 12:04     Unexpected ignorecase=false behavior on Windows Marc Strapetz
2018-06-22 17:36     ` Johannes Sixt
2018-06-22 20:45       ` Marc Strapetz
2018-06-22 20:58         ` Bryan Turner
2018-06-25 16:34  6%       ` Junio C Hamano
2018-06-25 17:38  6%         ` Bryan Turner
2018-06-24  8:56     [BUG] url schemes should be case-insensitive Jeff King
2018-06-25 18:19     ` Junio C Hamano
2018-06-26 12:21       ` Jeff King
2018-06-26 17:09  5%     ` Junio C Hamano
2018-06-26 18:27  0%       ` Jeff King
2018-06-24 10:05     [PATCH] Documentation: declare "core.ignorecase" as internal variable Marc Strapetz
2018-06-24 10:44     ` [PATCH v2] " Marc Strapetz
2018-06-24 13:47  0%   ` Torsten Bögershausen
2018-06-26 21:50  6% ` [PATCH v3] " Marc Strapetz
2018-06-27 19:11  0%   ` Junio C Hamano
2018-06-28 11:21  6% ` [PATCH v4] Documentation: declare "core.ignoreCase" " Marc Strapetz
2018-06-28 16:58  0%   ` Torsten Bögershausen
2018-07-02 10:57     [GSoC][PATCH v2 0/7] rebase -i: rewrite some parts in C Alban Gruin
2018-07-10 12:15     ` [GSoC][PATCH v3 00/13] " Alban Gruin
2018-07-10 12:15  5%   ` [GSoC][PATCH v3 11/13] rebase--interactive: remove unused modes and functions Alban Gruin
2018-07-24 16:32       ` [GSoC][PATCH v4 00/20] rebase -i: rewrite in C Alban Gruin
2018-07-24 16:32  6%     ` [GSoC][PATCH v4 12/20] rebase -i: remove unused modes and functions Alban Gruin
2018-07-31 17:59         ` [GSoC][PATCH v5 00/20] rebase -i: rewrite in C Alban Gruin
2018-07-31 17:59  6%       ` [GSoC][PATCH v5 12/20] rebase -i: remove unused modes and functions Alban Gruin
2018-08-10 16:51           ` [GSoC][PATCH v6 00/20] rebase -i: rewrite in C Alban Gruin
2018-08-10 16:51  6%         ` [GSoC][PATCH v6 12/20] rebase -i: remove unused modes and functions Alban Gruin
2018-08-28 12:10             ` [GSoC][PATCH v7 00/20] rebase -i: rewrite in C Alban Gruin
2018-08-28 12:10  6%           ` [GSoC][PATCH v7 12/20] rebase -i: remove unused modes and functions Alban Gruin
2018-09-27 21:55               ` [GSoC][PATCH v8 00/20] rebase -i: rewrite in C Alban Gruin
2018-09-27 21:56  6%             ` [GSoC][PATCH v8 12/20] rebase -i: remove unused modes and functions Alban Gruin
2018-07-06 21:32     de-alphabetizing the documentation Eric Sunshine
2018-07-06 21:18     ` Jonathan Nieder
2018-07-06 23:21       ` frederik
2018-07-06 23:47         ` Jonathan Nieder
2018-07-08  1:09           ` frederik
2018-07-24 19:52             ` frederik
2018-07-24 21:11               ` Jonathan Nieder
2018-08-11  2:30  1%             ` frederik
2018-07-10  8:52     [PATCH v2 0/9] X509 (gpgsm) commit signing support Henning Schild
2018-07-10  8:52     ` [PATCH v2 7/9] gpg-interface: introduce new config to select per gpg format program Henning Schild
2018-07-10 16:54  4%   ` Jeff King
2018-07-10 16:56         ` Jeff King
2018-07-14 18:13           ` brian m. carlson
2018-07-16 21:35  6%         ` Jeff King
2018-07-16 21:56  0%           ` Junio C Hamano
2018-07-13  8:41     [PATCH v3 0/7] X509 (gpgsm) commit signing support Henning Schild
2018-07-13  8:41     ` [PATCH v3 2/7] t/t7510: check the validation of the new config gpg.format Henning Schild
2018-07-16 20:15  5%   ` Junio C Hamano
2018-07-16 13:00     [PATCH 00/16] Consolidate reachability logic Derrick Stolee via GitGitGadget
2018-07-16 13:54     ` Ramsay Jones
2018-07-16 17:26       ` Stefan Beller
2018-07-16 18:44  5%     ` Eric Sunshine
2018-07-16 18:47  0%       ` Derrick Stolee
2018-07-18 12:28  0%         ` Johannes Schindelin
2018-07-18 15:01  0%           ` Duy Nguyen
2018-07-17 12:50  3% [PATCH v4 0/7] X509 (gpgsm) commit signing support Henning Schild
2018-07-17 12:50     ` [PATCH v4 2/7] t/t7510: check the validation of the new config gpg.format Henning Schild
2018-07-17 21:31  5%   ` Junio C Hamano
2018-07-18 10:36  0%     ` Henning Schild
2018-07-27  9:59  6% Git clone and case sensitivity Paweł Paruzel
2018-07-27 20:59  0% ` brian m. carlson
2018-07-28  4:36  0%   ` Duy Nguyen
2018-07-28  4:45  0%     ` Duy Nguyen
2018-07-27 20:51 20% [PATCH] config: fix case sensitive subsection names on writing Stefan Beller
2018-07-27 21:21 15% ` Brandon Williams
2018-07-27 21:39  8%   ` Junio C Hamano
2018-07-27 23:35  8%     ` Stefan Beller
2018-07-27 23:36 21%       ` Stefan Beller
2018-07-27 23:37  8%         ` Stefan Beller
2018-07-28  1:01  8%         ` Junio C Hamano
2018-07-28  3:52  8%           ` Stefan Beller
2018-07-28 10:53  7%             ` Jeff King
2018-07-28  1:37  8%         ` Junio C Hamano
2018-07-30 12:49 14%           ` Johannes Schindelin
2018-07-30 23:04 12%             ` [PATCH 0/3] " Stefan Beller
2018-07-30 23:04  7%               ` [PATCH 1/3] t1300: document current behavior of setting options Stefan Beller
2018-07-30 23:04 24%               ` [PATCH 2/3] config: fix case sensitive subsection names on writing Stefan Beller
2018-07-31 20:16  8%                 ` Junio C Hamano
2018-07-31 15:16  8%               ` [PATCH 0/3] " Junio C Hamano
2018-08-01 19:34  4%                 ` [PATCH 0/3] sb/config-write-fix done without robbing Peter Stefan Beller
2018-08-01 19:34  7%                   ` [PATCH 1/3] t1300: document current behavior of setting options Stefan Beller
2018-08-01 19:34 20%                   ` [PATCH 2/3] config: fix case sensitive subsection names on writing Stefan Beller
2018-08-01 21:01  8%                     ` Ramsay Jones
2018-08-01 22:26  8%                       ` Junio C Hamano
2018-08-01 22:51  7%                     ` Junio C Hamano
2018-08-03  0:30  8%                       ` Stefan Beller
2018-08-03 15:51  8%                         ` Junio C Hamano
2018-08-03  0:34  7%                   ` [PATCH 0/3] Reroll of sb/config-write-fix Stefan Beller
2018-08-03  0:34  7%                     ` [PATCH 1/3] t1300: document current behavior of setting options Stefan Beller
2018-08-03  0:34 20%                     ` [PATCH 2/3] config: fix case sensitive subsection names on writing Stefan Beller
2018-07-27 21:37  8% ` [PATCH] " Junio C Hamano
2018-07-29  9:28     Git clone and case sensitivity Jeff King
2018-07-30 15:27  4% ` [PATCH/RFC] clone: report duplicate entries on case-insensitive filesystems Nguyễn Thái Ngọc Duy
2018-07-31 18:23  0%   ` Torsten Bögershausen
2018-07-31 18:44  0%   ` Elijah Newren
2018-07-31 19:12         ` Junio C Hamano
2018-07-31 19:29           ` Jeff King
2018-07-31 20:12             ` Junio C Hamano
2018-07-31 20:37               ` Jeff King
2018-07-31 20:57                 ` Junio C Hamano
2018-08-01 21:20  5%               ` Junio C Hamano
2018-08-02 14:43  0%                 ` Duy Nguyen
2018-08-02 16:27  0%                   ` Junio C Hamano
2018-08-03 14:28  0%                   ` Torsten Bögershausen
2018-07-31 19:13       ` Junio C Hamano
2018-08-01 15:16  5%     ` Duy Nguyen
2018-08-07 19:01  3%   ` [PATCH v2] " Nguyễn Thái Ngọc Duy
2018-08-10 15:36         ` [PATCH v3 0/1] clone: warn on colidding entries on checkout Nguyễn Thái Ngọc Duy
2018-08-10 15:36  4%       ` [PATCH v3 1/1] clone: report duplicate entries on case-insensitive filesystems Nguyễn Thái Ngọc Duy
2018-08-11 10:09  0%         ` SZEDER Gábor
2018-08-11 13:16  0%           ` Duy Nguyen
2018-08-12  9:07  9%       ` [PATCH v4] " Nguyễn Thái Ngọc Duy
2018-08-13 15:32  0%         ` Jeff Hostetler
2018-08-13 17:18  0%         ` Junio C Hamano
2018-08-15 19:08  0%         ` Torsten Bögershausen
2018-08-17 16:16  9%         ` [PATCH v5] " Nguyễn Thái Ngọc Duy
2018-08-17 17:20  0%           ` Junio C Hamano
2018-08-17 18:00  9%             ` Duy Nguyen
2018-08-17 19:46  0%           ` Torsten Bögershausen
2018-11-19  8:20  6%           ` Carlo Marcelo Arenas Belón
2018-11-19 12:28  5%             ` Torsten Bögershausen
2018-11-19 17:14  5%               ` Carlo Arenas
2018-11-19 18:24  0%                 ` Duy Nguyen
2018-11-19 21:03                       ` Duy Nguyen
2018-11-19 23:29  4%                     ` Ramsay Jones
2018-11-19 23:54  0%                       ` Ramsay Jones
2018-11-19 17:21  0%               ` Ramsay Jones
2018-08-02 11:47     [PATCH 0/2 v4] sideband: highlight keywords in remote sideband output Han-Wen Nienhuys
2018-08-02 11:47     ` [PATCH 2/2] " Han-Wen Nienhuys
2018-08-02 18:22  4%   ` Junio C Hamano
2018-08-06 14:06  0%     ` Han-Wen Nienhuys
2018-08-02 23:02  1% What's cooking in git.git (Aug 2018, #01; Thu, 2) Junio C Hamano
2018-08-03  0:05  0% ` Stefan Beller
2018-08-03 16:08  0%   ` Junio C Hamano
2018-08-06 14:33     [PATCH v5 0/2] sideband: highlight keywords in remote sideband output Han-Wen Nienhuys
2018-08-06 14:33     ` [PATCH v5 2/2] " Han-Wen Nienhuys
2018-08-06 17:21  4%   ` Junio C Hamano
2018-08-06 17:42  0%     ` Han-Wen Nienhuys
2018-08-06 22:35  1% What's cooking in git.git (Aug 2018, #02; Mon, 6) Junio C Hamano
2018-08-07 23:06     [RFC] submodule: munge paths to submodule git directories Brandon Williams
2018-08-08 22:33     ` [PATCH 0/2] munge submodule names Brandon Williams
2018-08-08 22:33       ` [PATCH 2/2] submodule: munge paths to submodule git directories Brandon Williams
2018-08-09 21:26  3%     ` Jeff King
2018-08-14 18:04  0%       ` Brandon Williams
2018-08-14 18:57  0%         ` Jonathan Nieder
2018-08-14 21:08  0%           ` Stefan Beller
2018-08-14 18:58  0%         ` Jeff King
2018-08-28 21:35  0%         ` Stefan Beller
2018-08-29  5:25               ` Jeff King
2018-08-29 18:10  5%             ` Stefan Beller
2018-08-29 21:03  0%               ` Jeff King
2018-08-08 19:50  6% [PATCH 0/3] Resending sb/config-write-fix Stefan Beller
2018-08-08 19:50  7% ` [PATCH 1/3] t1300: document current behavior of setting options Stefan Beller
2018-08-08 19:50 20% ` [PATCH 2/3] config: fix case sensitive subsection names on writing Stefan Beller
2018-08-13 18:17     de-alphabetizing the documentation Junio C Hamano
2019-02-19 17:54     ` [PATCH 0/1] de-alphabetize command list Frederick Eaton
2019-02-21 18:05  1%   ` frederik
2019-03-11  9:04  0%     ` frederik
2019-03-11 14:38  0%       ` Jacob Keller
2018-08-15 19:55     "Changes not staged for commit" after cloning a repo on macOS Hadi Safari
2018-08-16  6:23  5% ` Torsten Bögershausen
2018-08-16  7:28  0%   ` Hadi Safari
2018-08-15 23:01  1% What's cooking in git.git (Aug 2018, #03; Wed, 15) Junio C Hamano
2018-08-16  2:35  0% ` Stefan Beller
2018-08-16 14:58  0%   ` Junio C Hamano
2018-08-17 22:44  1% What's cooking in git.git (Aug 2018, #04; Fri, 17) Junio C Hamano
2018-08-20 18:11  0% ` Stefan Beller
2018-08-20 22:13  1% [ANNOUNCE] Git v2.19.0-rc0 Junio C Hamano
2018-08-20 22:15  1% What's cooking in git.git (Aug 2018, #05; Mon, 20) Junio C Hamano
2018-09-10  8:09     Multiple GIT Accounts & HTTPS Client Certificates - Config Sergei Haller
2018-09-10 13:29  5% ` Randall S. Becker
2018-09-11  7:29  0%   ` Sergei Haller
2018-09-10 20:11  1% [ANNOUNCE] Git v2.19.0 Junio C Hamano
2018-09-23 13:04  4% Import/Export as a fast way to purge files from Git? Lars Schneider
2018-10-12 12:59  1% Git Test Coverage Report (Friday, Oct 12) Derrick Stolee
2018-10-20 12:37     [PATCH 00/59] Split config.txt Nguyễn Thái Ngọc Duy
2018-10-20 12:37  4% ` [PATCH 02/59] config.txt: move core.* to a separate file Nguyễn Thái Ngọc Duy
2018-10-27  6:22     [PATCH 00/78] nd/config-split reroll Nguyễn Thái Ngọc Duy
2018-10-27  6:22  4% ` [PATCH 03/78] config.txt: move core.* to a separate file Nguyễn Thái Ngọc Duy
2018-10-28 12:50     [PATCH] pretty: Add %(trailer:X) to display single trailer Anders Waldenborg
2018-10-29 14:14  4% ` Jeff King
2018-10-29 16:55     [RFC] Generation Number v2 Derrick Stolee
2018-10-30  3:59     ` Junio C Hamano
2018-10-31 12:54       ` Ævar Arnfjörð Bjarmason
2018-10-31 13:04         ` Derrick Stolee
2018-11-02 17:44  3%       ` Jakub Narebski
2018-11-19  2:54  1% Git Test Coverage Report (v2.20.0-rc0) Derrick Stolee
2018-11-19 15:40  3% ` Derrick Stolee
2018-11-20  2:22     [PATCH v5] clone: report duplicate entries on case-insensitive filesystems Junio C Hamano
2018-11-20 16:28  4% ` [PATCH] clone: fix colliding file detection on APFS Nguyễn Thái Ngọc Duy
2018-11-20 19:20  0%   ` Ramsay Jones
2018-11-22 17:59  5%   ` [PATCH v1 1/1] t5601-99: Enable colliding file detection for MINGW tboegi
2018-11-30 17:35  2% [PATCH] l10n: update German translation Ralf Thielow
2018-12-04  6:54  1% ` [PATCH v2] " Ralf Thielow
2018-12-13 19:54     [PATCH 0/1] worktree refs: fix case sensitivity for 'head' Michael Rappazzo via GitGitGadget
2018-12-13 19:54     ` [PATCH 1/1] " Michael Rappazzo via GitGitGadget
2018-12-13 20:23  5%   ` Duy Nguyen
2018-12-13 20:34  0%     ` Mike Rappazzo
2018-12-13 20:43  5%       ` Duy Nguyen
2018-12-13 20:47             ` Stefan Beller
2018-12-13 21:14               ` Mike Rappazzo
2018-12-14  6:49  5%             ` Jacob Keller
2018-12-14  7:37  0%               ` Duy Nguyen
2018-12-14 17:22  5%                 ` Jacob Keller
2018-12-14 17:38  0%                   ` Duy Nguyen
2018-12-14 18:47  0%                     ` Jacob Keller
2018-12-13 21:07  0%         ` Mike Rappazzo
2018-12-14  3:31  0%         ` Junio C Hamano
2018-12-24 22:34     [PATCH v2 0/1] Make abspath() aware of case-insensitive filesystems Johannes Schindelin via GitGitGadget
2018-12-24 22:56     ` [PATCH v3 " Johannes Schindelin via GitGitGadget
2018-12-24 22:56       ` [PATCH v3 1/1] abspath_part_inside_repo: respect core.fileMode Johannes Schindelin via GitGitGadget
2018-12-25  3:06  5%     ` Junio C Hamano
2019-02-07 19:41     Possible minor bug in Git Johannes Schindelin
2019-02-08 17:43  0% ` Torsten Bögershausen
2019-03-01  6:19  8% fast-import fails with case sensitive tags due to case insensitive lock files Wendeborn, Jonathan
2019-03-03  0:25 15% ` brian m. carlson
2019-03-04  7:55  9%   ` AW: " Wendeborn, Jonathan
2019-03-04 15:29  9%     ` Johannes Schindelin
2019-03-05  6:25 14%       ` AW: " Wendeborn, Jonathan
2019-03-04 17:34     [PATCH 0/5] git-p4: a few assorted fixes for branches, excludes Mazo, Andrey
2019-03-04 17:34  6% ` [PATCH 2/5] git-p4: match branches case insensitively if configured Mazo, Andrey
2019-03-21 22:32     [PATCH v2 0/7] git-p4: a few assorted fixes for branches, excludes Mazo, Andrey
2019-03-21 22:32  6% ` [PATCH v2 2/7] git-p4: match branches case insensitively if configured Mazo, Andrey
2019-03-23  9:15  0%   ` Luke Diamand
2019-03-25 17:20  0%     ` Mazo, Andrey
2019-04-01 18:02     ` [PATCH v3 0/8] git-p4: a few assorted fixes for branches, excludes Mazo, Andrey
2019-04-01 18:02  5%   ` [PATCH v3 3/8] git-p4: match branches case insensitively if configured Mazo, Andrey
2019-05-19  5:07     Git ransom campaign incident report - May 2019 Jeff King
2019-05-19  5:10     ` [PATCH 1/3] transport_anonymize_url(): support retaining username Jeff King
2019-05-20 16:36  5%   ` Johannes Schindelin
2019-06-04  2:13     [RFC/PATCH 0/5] Fix fetch regression with transport helpers Felipe Contreras
2019-06-05  8:12  4% ` Johannes Schindelin
2019-06-05 11:27  4%   ` Jeff King
2019-06-05 12:22  0%     ` Duy Nguyen
2019-06-06 13:07  0%       ` Johannes Schindelin
2019-06-08 14:43     [PATCH 0/1] Fix a test on NTFS (and probably HFS+) Johannes Schindelin via GitGitGadget
     [not found]     ` <pull.151.v2.git.gitgitgadget@gmail.com>
     [not found]       ` <c2fdcf28e725c91a1a48c34226223866ad14bc0a.1560978437.git.gitgitgadget@gmail.com>
2019-06-21 14:34         ` [PATCH v2 1/1] t0001: fix on case-insensitive filesystems Johannes Schindelin
2019-06-21 17:31           ` Junio C Hamano
2019-06-24 10:13             ` Johannes Schindelin
2019-06-24 17:09               ` Junio C Hamano
2019-06-24 17:38  5%             ` Johannes Schindelin
2019-06-24 19:22  0%               ` Junio C Hamano
2019-09-12 22:12     [PATCH v3 00/12] Fix some git clean issues Elijah Newren
2019-09-17 16:34     ` [PATCH v4 " Elijah Newren
2019-09-17 16:34       ` [PATCH v4 04/12] dir: also check directories for matching pathspecs Elijah Newren
2019-09-25 20:39         ` [BUG] git is segfaulting, was " Denton Liu
2019-09-27  1:09           ` SZEDER Gábor
2019-09-27  2:17  6%         ` SZEDER Gábor
2019-09-27 17:10  0%           ` Denton Liu
2019-09-17  8:52  4% [PATCH] gitk: rename zh_CN.po to zh_cn.po Denton Liu
2019-09-17 16:48  0% ` Junio C Hamano
2019-10-01 18:40     [PATCH v2] dir: special case check for the possibility that pathspec is NULL Denton Liu
2019-10-01 18:55     ` [PATCH v3] " Elijah Newren
2019-10-07 18:04  5%   ` SZEDER Gábor
2019-10-29 10:00     [PATCH 00/10] [Outreachy] Move doc to header files Heba Waly via GitGitGadget
2019-11-06  9:59     ` [PATCH v2 00/20] " Heba Waly via GitGitGadget
2019-11-06  9:59  4%   ` [PATCH v2 15/20] parse-options: move doc to parse-options.h Heba Waly via GitGitGadget
2019-11-11 21:27       ` [PATCH v3 00/21] [Outreachy] Move doc to header files Heba Waly via GitGitGadget
2019-11-11 21:27  4%     ` [PATCH v3 15/21] parse-options: move doc to parse-options.h Heba Waly via GitGitGadget
2019-11-15  9:53         ` [PATCH v4 00/21] [Outreachy] Move doc to header files Heba Waly via GitGitGadget
2019-11-15  9:53  4%       ` [PATCH v4 15/21] parse-options: move doc to parse-options.h Heba Waly via GitGitGadget
2019-11-23 20:50     [PATCH 0/8] Drop support for git rebase --preserve-merges Johannes Schindelin via GitGitGadget
2019-11-23 20:50  2% ` [PATCH 5/8] rebase: drop support for `--preserve-merges` Johannes Schindelin via GitGitGadget
2019-12-09 19:42     [PATCH 0/1] sparse-checkout: respect core.ignoreCase in cone mode Derrick Stolee via GitGitGadget
2019-12-09 19:43     ` [PATCH 1/1] " Derrick Stolee via GitGitGadget
2019-12-11 18:44  5%   ` Junio C Hamano
2019-12-11 19:11  6%     ` Derrick Stolee
2019-12-11 20:00  5%       ` Junio C Hamano
2019-12-11 20:29  0%         ` Derrick Stolee
2019-12-11 21:37  4%           ` Junio C Hamano
2019-12-12  2:51  0%             ` Derrick Stolee
2019-12-10  2:33     [PATCH 0/6] configuration-based hook management Emily Shaffer
2020-04-14  0:54     ` [RFC PATCH v2 0/2] configuration-based hook management (was: [TOPIC 2/17] Hooks in the future) Emily Shaffer
2020-04-14 15:15       ` [RFC PATCH v2 0/2] configuration-based hook management Phillip Wood
2020-04-14 19:24         ` Emily Shaffer
2020-04-14 20:27  5%       ` Jeff King
2019-12-17 12:01     [PATCH 00/15] t: replace incorrect test_must_fail usage (part 1) Denton Liu
2019-12-17 12:01  6% ` [PATCH 06/15] t0003: don't use `test_must_fail attr_check` Denton Liu
2019-12-19 22:22     ` [PATCH v2 00/16] t: replace incorrect test_must_fail usage (part 1) Denton Liu
2019-12-19 22:22  6%   ` [PATCH v2 06/16] t0003: don't use `test_must_fail attr_check` Denton Liu
2019-12-20 18:15       ` [PATCH v3 00/15] t: replace incorrect test_must_fail usage (part 1) Denton Liu
2019-12-20 18:15  6%     ` [PATCH v3 05/15] t0003: don't use `test_must_fail attr_check` Denton Liu
2020-01-17 15:31     [PATCH v3 0/4] config: allow user to know scope of config options Matthew Rogers via GitGitGadget
2020-01-24  0:21     ` [PATCH v4 0/6] " Matthew Rogers via GitGitGadget
2020-01-24  0:21  6%   ` [PATCH v4 2/6] t1300: fix over-indented HERE-DOCs Matthew Rogers via GitGitGadget
2020-01-25  0:39       ` [PATCH v5 0/6] config: allow user to know scope of config options Matthew Rogers via GitGitGadget
2020-01-25  0:39  6%     ` [PATCH v5 2/6] t1300: fix over-indented HERE-DOCs Matthew Rogers via GitGitGadget
2020-01-29  3:34         ` [PATCH v6 0/6] config: allow user to know scope of config options Matthew Rogers via GitGitGadget
2020-01-29  3:34  6%       ` [PATCH v6 2/6] t1300: fix over-indented HERE-DOCs Matthew Rogers via GitGitGadget
2020-02-10  0:30           ` [PATCH v7 00/10] config: allow user to know scope of config options Matthew Rogers via GitGitGadget
2020-02-10  0:30  6%         ` [PATCH v7 02/10] t1300: fix over-indented HERE-DOCs Matthew Rogers via GitGitGadget
2020-01-23 19:41     [PATCH 0/5] Reftable support git-core Han-Wen Nienhuys via GitGitGadget
2020-01-27 14:22  1% ` [PATCH v2 " Han-Wen Nienhuys via GitGitGadget
2020-01-27 14:22  1%   ` [PATCH v2 4/5] Add reftable library Han-Wen Nienhuys via GitGitGadget
2020-02-04 20:27       ` [PATCH v3 0/6] Reftable support git-core Han-Wen Nienhuys via GitGitGadget
2020-02-04 20:27  1%     ` [PATCH v3 5/6] Add reftable library Han-Wen Nienhuys via GitGitGadget
2020-02-06 22:55         ` [PATCH v4 0/5] Reftable support git-core Han-Wen Nienhuys via GitGitGadget
2020-02-06 22:55  1%       ` [PATCH v4 4/5] Add reftable library Han-Wen Nienhuys via GitGitGadget
2020-02-10 14:14           ` [PATCH v5 0/5] Reftable support git-core Han-Wen Nienhuys via GitGitGadget
2020-02-10 14:14  1%         ` [PATCH v5 4/5] Add reftable library Han-Wen Nienhuys via GitGitGadget
2020-02-18  8:43             ` [PATCH v6 0/5] Reftable support git-core Han-Wen Nienhuys via GitGitGadget
2020-02-18  8:43  1%           ` [PATCH v6 4/5] Add reftable library Han-Wen Nienhuys via GitGitGadget
2020-02-26  8:49  1%           ` [PATCH v7 0/6] Reftable support git-core Han-Wen Nienhuys via GitGitGadget
2020-01-24 22:33     [PATCH 0/3] git gui: improve German translation Christian Stimming via GitGitGadget
2020-01-24 22:33  1% ` [PATCH 1/3] git-gui: update german translation to most recently created pot templates Christian Stimming via GitGitGadget
2020-01-24 22:33  1% ` [PATCH 2/3] git-gui: update german translation Christian Stimming via GitGitGadget
2020-02-09 22:00  1% ` [PATCH v2 0/3] git gui: improve German translation Christian Stimming via GitGitGadget
2020-02-09 22:00  1%   ` [PATCH v2 1/3] git-gui: update pot template and German translation to current source code Christian Stimming via GitGitGadget
2020-02-09 22:00  1%   ` [PATCH v2 3/3] git-gui: update German translation Christian Stimming via GitGitGadget
2020-03-17  7:38     Allowing only blob filtering was: [TOPIC 5/17] Partial Clone Christian Couder
2020-03-17 20:39     ` [RFC PATCH 0/2] upload-pack.c: limit allowed filter choices Taylor Blau
2020-03-18 10:18       ` Jeff King
2020-03-18 18:26         ` Re*: " Junio C Hamano
2020-03-19 17:03  5%       ` Jeff King
2020-04-06 22:45     [PATCH v12 0/5] bugreport: add tool to generate debugging info Emily Shaffer
2020-04-06 22:45     ` [PATCH v12 3/5] bugreport: gather git version and build info Emily Shaffer
2020-04-06 23:17       ` Junio C Hamano
2020-04-07 18:42         ` Emily Shaffer
2020-04-07 20:05  4%       ` Junio C Hamano
2020-04-07 20:34  0%         ` Emily Shaffer

Code repositories for project(s) associated with this public inbox

	https://80x24.org/mirrors/git.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).