3

Say there is a squash of 3 commits into a single commit like so:

pick abc Jan 1 stuff squash def Jan 2 stuff squash ghi Jan 3 stuff pick jkl Jan 4 stuff pick mno Jan 5 stuff 

In the above, my understanding is that commits for Jan 1, 2, and 3 are squashed into a single commit. The resulting single commit is dated Jan 1 from the oldest of the 3.

Is there a way to squash in the other direction of the timestamp? Could Jan 1, 2, and 3 commits be squashed into a single commit, which is dated Jan 3?

1
  • You can only squash "up". To avoid potential conflicts I typically just cherry-pick the commit I want and purposefully create an empty commit with it. Then I interactive rebase and move that commit up to the position I wish to rebase into. Here's an example where the question asked how to squash "down" into the most recent commit. Commented Jun 27 at 1:22

3 Answers 3

5

You can reorder commits:

pick ghi Jan 3 stuff squash abc Jan 1 stuff squash def Jan 2 stuff 

Git recognizes commits in the list not by their order but by commit IDs (abc, def and ghj in your example).

Sign up to request clarification or add additional context in comments.

Comments

2

If reordering the commits works without conflicts, then @phd's answer works right out.

Otherwise: you can edit the author date after the facts, by setting GIT_AUTHOR_DATE=... or by using git commit --date=....

For example:

# in the interactive rebase todo script: pick abc Jan 1 stuff squash def Jan 2 stuff squash ghi Jan 3 stuff # <- write down the hash or short hash for 'ghi' break # <- the rebase will pause here after squashing together the 3 commits pick jkl Jan 4 stuff pick mno Jan 5 stuff # save and close # when stopped after squashing: get the commit date from commit 'ghi' $ git log -1 --format="%ad" ghi $ git commit --date="<the date above>" --amend # in one go: $ git commit --date="$(git log -1 --format="%ad" ghi)" --amend # if all is to your liking: $ git rebase --continue 

Comments

1

You can do this non-interactively by:

  1. Calling the editor in the script to do the initial editing of commands
  2. Doing the non-interactive edits in the rest of the script

You do this with a script which you let git-rebase(1) call:

  • E.g. rebase-script
  • GIT_SEQUENCE_EDITOR=./rebase-script git rebase -i origin/main

For the non-interactive part:

  • Go through the todo list
  • Record where the last pick was (this doesn’t work with other commands)
  • Record the last squash/fixup seen
  • When you have seen the last squash/fixup: record an exec after the pick
#!/usr/bin/env bash # ./rebase-script get_commit () { set -u # arg: line line="$1" echo "$line" | cut -d' ' -f2 } process_if_previous () { set -u # arg1: prev prev="$1" # arg2: squash squash="$2" # arg3: new file new="$3" if test -n "$prev" then if test -z "$squash_commit" then return 0 fi date=$(git log --format='%ai' -1 "$squash") ed -s "$new" <<EOE /$prev a exec git commit --amend --no-edit --date="$date" . w q EOE fi } set -u # arg1: file file="$1" # First edit interactively editor=$(git var GIT_EDITOR) && "$editor" "$file" && # Now edit non-interactively new=$(mktemp) && cat "$file" >"$new" && prev="" squash_commit="" while IFS= read -r line do first="${line:0:1}" && if test "$first" = p then process_if_previous "$prev" "$squash_commit" "$new" && prev=$(get_commit "$line") && squash_commit='' elif test "$first" = s || test "$first" = f then squash_commit=$(get_commit "$line") elif test -z "$line" then # done with the todo list process_if_previous "$prev" "$squash_commit" "$new" break fi done <"$file" && cat "$new" >"$file" 

1 Comment

Presumably the middle way here is to manually edit the todo list, but paste in a exec git commit --amend --no-edit --date="..." line with the desired date.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.