Refuse a commit that sets a file back
A repository on one machine can hold one shared working tree that every session commits whole. When that tree holds a stale copy of a file, one a session wrote from an old read or a tool restored, a commit of the whole tree takes it in as if it were new work, and with it takes back what landed since: a build script loses the list it generated, a module split comes back half done, and the tree stops compiling for everyone until someone notices and repairs it, which the next whole-tree commit can undo again.
Every stado quality check, the pre-push check a Git dispatcher
runs (--pre-push) and the check of one revision (--revision), therefore
reads, before any gate runs, each file the checked commit rewrites and asks
the history of its first parent whether that file ever held the content the
commit writes and something later replaced it. A commit that sets files
back is refused, naming every such file with the commit that wrote that
content and the one that replaced it, so one repair takes them all, and
nothing else of that commit is checked:
cause: stado.quality.check.content-set-back
<revision> writes <count> file(s) back to content a later commit of its history replaced
<path> as it stood at <introduced>, which <replaced> replaced (blob <blob>; the first parent holds <current>); …
The repair is in the refusal: take each file's current content back into
the tree (git checkout <parent> -- <path>) and merge into it the change
that was meant, or, when taking the content back is the change, say so in
the commit. A revert whose message says This reverts commit <replaced> (as
git revert writes it) passes whole. A rework that sets some files back
while it moves their later content elsewhere states each in a message line
of its own:
Sets back: src/cli/work/failures/mod.rs, src/cli/workload/commands
A line names paths or directories, separated by commas; a directory covers every file below it. Every other file the commit sets back is still refused, and the refusal names only those. A path the message mentions anywhere else does not count: a whole-tree commit's message names files for what it changes in them, not to take their content back.
What the check does not judge:
- a merge commit, which brings its second parent's content, older or not; the commits it joins are judged when each is checked;
- a file the commit adds or deletes: only a rewritten file can repeat content its own path held before;
- content the path never held, however old it is elsewhere.
The history of the rewritten files is read in as few walks as the system's
argument limit allows (ARG_MAX, less what the environment takes): one for
an ordinary commit, a few for a whole-tree commit of tens of thousands of
files, which one command line cannot name. Real tests:
tests/quality/gate_kind/check.rs (a_commit_that_sets_a_file_back_is_refused,
which sets two files back and expects both named,
a_revert_that_says_so_and_a_new_change_pass_the_set_back_check, and
a_set_back_the_message_states_passes_and_one_it_does_not_is_named, which
states one of two files and expects only the other refused).
Source: this website