commit: preserve commit hash on a no-op amend
cover commit: preserve commit hash on a no-op amend
git commit --amend --no-edit rewrote the commit and moved the branch tip even when nothing changed, because the committer date was reset to "now". Reuse the existing committer date so a no-op amend keeps the commit hash and leaves the branch untouched.
A real change (tree, message, author, committer, or signing) still rewrites as before.
Harald Nordgren (2):
commit: extract commit_index_files_or_die() helper
commit: keep the commit on a no-op amend
Documentation/git-commit.adoc | 6 ++ builtin/commit.c | 69 ++++++++++++++- t/t7501-commit-basic-functionality.sh | 119 ++++++++++++++++++++++++++ 3 files changed, 190 insertions(+), 4 deletions(-)
base-commit: ea97ad8d017de0c9037451a78008a0fd60abea0c
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2334%2FHaraldNordgren%2Famend-noop-keeps-commit-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2334/HaraldNordgren/amend-noop-keeps-commit-v1
Pull-Request: https://github.com/git/git/pull/2334
Signature · 1 line
gitgitgadget
PATCH 1/2 commit: extract commit_index_files_or_die() helper
From: Harald Nordgren <[email protected]>
A later change adds a second caller that commits the index lock and dies on failure, so wrap that into a helper to avoid duplicating its message.
No functional change intended.
--- builtin/commit.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-)
builtin/commit.c +9 -4
@@ -248,6 +248,14 @@ static int commit_index_files(void)return err; } +static void commit_index_files_or_die (void ) +{ +if (commit_index_files ()) +die (_ ("repository has been updated, but unable to write\n" +"new index file. Check that disk is not full and quota is\n" +"not exceeded, and then \"git restore --staged :/\" to recover." )); +} +/* * Take a union of paths in the index and the named tree (typically, "HEAD"), * and return the paths that match the given pattern in list.
@@ -1954,10 +1962,7 @@ int cmd_commit(int argc,unlink (git_path_merge_mode (the_repository));unlink (git_path_squash_msg (the_repository)); -if (commit_index_files ()) -die (_ ("repository has been updated, but unable to write\n" -"new index file. Check that disk is not full and quota is\n" -"not exceeded, and then \"git restore --staged :/\" to recover." )); +commit_index_files_or_die ();git_test_write_commit_graph_or_die (the_repository->objects->sources);
Signature · 1 line
gitgitgadget
PATCH 2/2 commit: keep the commit on a no-op amend
In reply to Harald Nordgren via GitGitGadgetFrom: Harald Nordgren <[email protected]>
"git commit --amend --no-edit" reset the committer date to "now" and rewrote the commit even when nothing else changed, moving the branch tip to a new hash for an effective no-op.
Build the amended commit reusing the existing committer date: if that reproduces the current commit, leave the branch alone, report "nothing to amend", and skip the reflog entry and the post-commit and post-rewrite hooks.
Signing always rewrites the commit, since its signature cannot reproduce the original, so it skips this detection.
--- Documentation/git-commit.adoc | 6 ++ builtin/commit.c | 56 ++++++++++++ t/t7501-commit-basic-functionality.sh | 119 ++++++++++++++++++++++++++ 3 files changed, 181 insertions(+)
Documentation/git-commit.adoc +6 -0
@@ -282,6 +282,12 @@ variable (see linkgit:git-config[1]). parents and author as the current one (the `--reset-author` option can countermand this). + +If the amended commit would be identical to the original (its tree, +message, author, parents, and committer are all unchanged), the original +committer date is kept so that the commit, and thus the branch tip, is +left untouched. A commit that is being signed (`-S`, or `commit.gpgsign`) +is always rewritten, since its signature cannot reproduce the original. ++ -- It is a rough equivalent for:
builtin/commit.c +56 -0
74 lines not on this page · raw message
t/t7501-commit-basic-functionality.sh +119 -0
131 lines not on this page · raw message
Signature · 1 line
gitgitgadget
Am 13.06.26 um 11:16 schrieb Harald Nordgren via GitGitGadget:
Quoted 4 lines
git commit --amend --no-edit rewrote the commit and moved the branch tip even when nothing changed, because the committer date was reset to "now". Reuse the existing committer date so a no-op amend keeps the commit hash and leaves the branch untouched.
git commit --amend --no-edit is a way to set the committer timestamp to the current time without changing other aspects of the commit. This takes away this ability, doesn't it?
Is this keyed to --no-edit? Why is this mode special? Wouldn't it be an identical case when the commit message is passed to the editor, but comes back unchanged?
An invocation of git commit asks to "please make a new commit". But in the suggested mode, no new commit is created. Shouldn't this then be regarded as failure?
What happens with the current branch? Is it left unchanged (no ref update occurs) or is it changed (a ref update occurs, but it happens to be a no-op)? And does this then generate a reflog entry?
The updated documentation says about signed commits (note: I am totally clueless about commit signing procedures):
Quoted 2 lines
A commit that is being signed (
-S, orcommit.gpgsign)
is always rewritten, since its signature cannot reproduce the original.
But if the commit doesn't change in any way, why should the signature be invalidated, rewritten, or updated?
-- Hannes
Quoted 11 lines · Johannes Sixt
Am 13.06.26 um 11:16 schrieb Harald Nordgren via GitGitGadget:
Quoted 4 lines
git commit --amend --no-edit rewrote the commit and moved the branch tip even when nothing changed, because the committer date was reset to "now". Reuse the existing committer date so a no-op amend keeps the commit hash and leaves the branch untouched.
git commit --amend --no-editis a way to set the committer timestamp to the current time without changing other aspects of the commit. This takes away this ability, doesn't it?
Indeed. This is a convenient formula to force CI re-runs in certain environments, and so on.
Quoted 7 lines · Harald Nordgren via GitGitGadget
git commit --amend --no-edit rewrote the commit and moved the branch tip even when nothing changed, because the committer date was reset to "now". Reuse the existing committer date so a no-op amend keeps the commit hash and leaves the branch untouched.
A real change (tree, message, author, committer, or signing) still rewrites as before.
I think this change brings nothing but regression.
Isn't it obvious that "commit --amend --no-edit" without updating any tree contents would record exactly the same contents as before, without a "real change" (as you said above), to any and all users, expert and casual alike?
The end-user who runs such a command must have a reason to do so. The *ONLY* valid reason anybody might want to such an amend is to make sure the result is a new object, even if it records otherwise the same content.
Why would they want to do so? Perhaps it is so that future merges of the topic branch that contains the commit will work more smoothly into an integration branch that had earlier merged the topic branch, and then that earlier merge was reverted. This change will rob an effective way to ensure a successful final merge in a workflow to
(1) merge a topic, (2) revert the topic, (3) update near the tip of the topic while keeping earlier topic intact, and then (4) merge the result again.
So, no. I do not think this is a good change.
Let's digress and imagine an alternate universe where rebase/commit --amend/history were "smart" from day one. These command in such a hypothetical world may not be capable of refreshing an existing commit without making any "real change".
Making a change to these commands to _optionally_ allow them to recreate an otherwise unchanged commit, so that it will get a new object name, would be a welcome change that would allow users who would use "commit --amend --no-edit" with today's system for such a use case.
And that would have been a logical evolution of the system in such a hypothetical world.
But the thing is, we do not live in such a world.
If we still think that alternate hypothetical world is a better place, we'd need to actively move things around, carefully designing the transition to avoid harming existing users along the way, to get there. Changing the behaviour all of a sudden and breaking existing workflows is not something we do around here.
One way to get to such a world might be:
* Introduce an "committer timestamp is a trashable information" option, and teach commands like "commit --amend", "rebase", and "history" to cheat and yield the existing commit without refreshing when they are asked to recreate an existing commit while the option is in effect. Give people the opposite "committer timestamp is not trashable information" option, so an earlier "is trashable" option on the command line can be countermanded by giving it later on the command line.
* Have users discuss if "is trashable" is a better default, and gain consensus to make it the default in a future version of Git. Advertise the fact that we achieved consensus LOUDLY, while telling dissidents that "is not trashable" option will forever be available for them.
* At a big version boundary, switch the default.
And I do not think I in principle would object to the first step of such a three step process.
Thanks.
Interesting discussions! This sounds like showstopper, seems more reasonable to leave this topic for now.
I just want to share that I've been running this for years to re-trigger CI (because up until a few days ago I didn't realize that the hash did indeed change even when nothing had changed), I had the wrong mental model for commit hashes:
git commit --amend --no-edit --date="now"
Harald