ADR-009: init merges the template commit into the branch¶
Status: accepted
Context¶
git tpl init renders the template and creates refs/tpl/<id>. But the
generated files also have to reach the user's branch. Options:
- Check the files out into the worktree and let the user commit them.
- Do nothing — create the ref and print
git tpl merge. - Merge the orphan template commit into the current branch, allowing unrelated histories.
Option 1 is what a generator does, and it is the trap. The files arrive on main
as a commit with no relationship to G0. When the first git tpl update creates
G1 and the user merges it, Git looks for a common ancestor of main and G1
and finds none — so every difference between the generated files and the ones
the user has edited since comes back as a conflict, on a merge the user expected
to be routine.
That failure is the exact thing ADR-001 exists to prevent, and it would appear on the first update, which is the worst possible first impression.
Decision¶
init creates G0 as an orphan commit on refs/tpl/<id>, then merges it into
the current branch with unrelated histories allowed.
In a repository with no commits, G0 becomes the first commit on the branch
directly — there is nothing to merge it with, and the result is the cleanest
possible history for a generated project.
--no-merge stops after creating the ref.
Consequences¶
G0 is an ancestor of main, so every subsequent update has a genuine merge
base. The first update behaves like the hundredth.
The history is honest: git log --graph shows the template entering the project
as a merged parent, which is what happened.
Nothing is hidden and no history is rewritten. The merge commit is a normal merge commit.
The cost is that init requires a clean worktree, because it performs a merge.
That is the same requirement git merge has, and the error message says so.
Users unfamiliar with --allow-unrelated-histories may find the merge surprising
the first time. The documentation leads with it, because understanding it is
understanding the tool.
A correction¶
Earlier revisions of this ADR said an unrelated-histories merge "treats every file as added on both sides and conflicts on every line of every file". That is wrong, and it mattered: it made attaching a template to an existing project look impossible, and nearly bought a second command to work around a problem that does not exist.
Git compares content, not ancestry. With no merge base it still runs a real
line-level diff, so a file identical on both sides merges silently, a file that
differs by one line conflicts on that one line, and a file only the template has
is simply added and staged. tests/init.rs now asserts all three — see
a_file_that_differs_conflicts_only_on_the_differing_lines.
The decision above is unchanged, because the merge base is still what makes the second update cheap. Only the description of the cost was overstated.
The cost stated accurately: without a merge base Git cannot tell your edits from
the template's, so everything that differs conflicts — including a file you
customised that the template never changed. With one, only genuinely overlapping
edits conflict. That is demonstrated by
without_a_merge_base_a_customisation_conflicts_the_template_never_touched, and
its counterpart resolving_the_first_merge_leaves_the_next_update_clean shows
the same customisation surviving untouched once the base exists.