Subject: Re: [PATCH/RFC 4/5] test-tool: add a "historian" subcommand for building merge fixtures

From: Johannes Schindelin <[email-masked]>
Date: Sun, 17 May 2026 13:40:03 +0200 (CEST)

Hi Toon,

On Tue, 12 May 2026, Toon Claes wrote:

> "Johannes Schindelin via GitGitGadget" <[email-masked]> writes:
> 
> > diff --git a/t/helper/test-historian.c b/t/helper/test-historian.c
> > new file mode 100644
> > index 0000000000..2250d420c0
> > --- /dev/null
> > +++ b/t/helper/test-historian.c
> > @@ -0,0 +1,189 @@
> > +/*
> > + * Build a small history out of a tiny declarative input. Used by tests
> > + * that need specific merge topologies without long sequences of
> > + * plumbing commands or fragile shell helpers.
> > + *
> > + * The historian reads stdin line by line and emits an equivalent
> > + * stream to a `git fast-import` child process. It also allocates marks
> > + * for named objects so tests can refer to commits and blobs by name.
> 
> Really appreciate you're introducing this command. I'm actually
> surprised no else did before.

Heh. I am not surprised, though. Given that it is _really_ hard to come up
with a decent Domain-Specific Language to describe commit history for
defining test fixtures (testament to which are your comments below), I was
debating whether to go for range-diff's/libgit2's approach and simply
adding a bare repository or a fast-import script as a fixture. But that
would make the test code even harder to follow! And I did not want to add
to the amount of hard-to-follow test code.

> > + *
> > + * Input directives (one per line, shell-style quoting):
> > + *
> > + *	blob NAME LINE1 LINE2 ...
> > + *	    Each LINE becomes a content line in the blob; lines are
> > + *	    joined with '\n' and the blob ends with a final '\n'. With
> > + *	    no LINEs, the blob is empty.
> > + *
> > + *	commit NAME BRANCH SUBJECT [from=PARENT] [merge=PARENT]... [PATH=BLOB]...
> 
> I'm not sure how I feel about mixing named arguments (like `from=PARENT`) with
> the `PATH=BLOB` arguments? Obviously this tool isn't made for anything
> that's even close to production, but still feels strange. How about
> putting a double dash (`--`) before the paths, or using the `PATH:BLOB`
> syntax instead?

Okay, I can see that's a better design. To be honest, I did not really
optimize for unambiguity here, but for precision of defining a test
fixture. As such, I am mostly interested in keeping the definition as
small as possible without losing readability. Changing that `=` to a `:`
to disambiguate keeps the same length while clarifying the structure. I
like it!

> > + *	    Creates a commit on refs/heads/BRANCH using the listed
> > + *	    file=blob mappings as the entire tree (no inheritance from
> > + *	    parents). Up to one `from=` and any number of `merge=`
> > + *	    parents may be given. `from=` defaults to the current branch
> > + *	    tip; if BRANCH has no tip yet, the commit becomes a root.
> 
> At GitLab in our Gitaly suite we have a similar tool as what you're
> introducing here, but there you have to specify the parent(s) for each
> commit and if you want to assign a ref to a commit, you have to be
> explicit about it. So I would replace `from=` and `merge=` with
> `parent=` and allow that to be occur zero or more times (this would also
> allow creating unrelated histories). And remove the mandatory argument
> BRANCH, and instead allow the command to accept a `branch=` argument.
> 
> If we'd take an example from the follow-up commit:
> 
>         # Setup:
>         #       A (a) --- C (a, h) ----+--- M (a, g, h)
>         #        \                    /
>         #         +-- B (a, g) ------+
>         #
>         # Topic touches `g` only; main touches `h` only. The auto-merge
>         # at M is clean.
>         blob a "shared content"
>         blob g guarded
>         blob h host
>         commit A main "A" a=a
>         commit B topic "B (introduces g)" from=A a=a g=g
>         commit C main "C (introduces h)" a=a h=h
>         commit M main "Merge topic" merge=B a=a g=g h=h
> 
> I would suggest to rewrite that to:
> 
>         blob a "shared content"
>         blob g guarded
>         blob h host
>         commit A "A" a:a
>         commit B "B (introduces g)" parent=A branch=topic a:a g:g
>         commit C "C (introduces h)" parent=A a:a h:h
>         commit M "Merge topic" parent=A parent=B ref=main a:a g:g h:h
> 
> I realize this is less alike to git-fast-import(1), so I'd understand if
> you'd reject this idea.

That makes sense to me!

I'm not interested in keeping the `historian` code as small as possible, I
am interested in reducing the cognitive load required to read and write
those test fixtures. Therefore, the `parent=` way is much preferable.

Thank you!
Johannes