5

I know how to manually split a commit using git rebase -i, but how can I automatically split every commit in a branch by file?

For instance, commit A modified 3 files, f1, f2 and f3. After the split, there are 3 commits A-f1, A-f2 and A-f3.

I want to do this to make a major rewriting easier as I will only have to squash some small commits.

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
Julien__
  • 1,962
  • 1
  • 15
  • 25
  • 1
    It doesn't really make sense to split commits by file, because then you'll have many commits where one file changed its interface but its collaborators don't reflect that change. If you don't care about the detail, err on the side of squashing not splitting. – jonrsharpe Nov 19 '16 at 22:34

2 Answers2

10

The Script

The following script splits HEAD by file:

#!/usr/bin/env bash
set -e

SHA=$(git rev-parse --short HEAD)

# Change to repo root directory
cd $(git rev-parse --show-toplevel)

git reset HEAD^

git diff-tree --no-commit-id --name-only -r $SHA | while read -r f; do
  git add "$f"
  GIT_EDITOR="echo '0a\n$SHA $f\n\n.\nw' | ed -s" git commit -c $SHA
done

The generated commit messages are of the form:

<original SHA> <file name>

<original commit message>

Usage

The following assumes that you can run above script as git-split.

If you want to split all commits in a range by file, use it like this:

git rebase --interactive --exec git-split <branch>

If you want to split a single commit during an interactive rebase, use it like this:

p Commit to split
x git-split

Any improvements to the script are welcome.

raphinesse
  • 19,068
  • 6
  • 39
  • 48
  • Interesting script, more detailed than my answer. +1 – VonC May 30 '17 at 19:20
  • @VonC Your answer gave a good outline of what to do and got me started. All in all I would prefer a copy-paste friendly one-liner instead of my script, though :) – raphinesse May 30 '17 at 19:32
  • Great script, just the `GIT_EDITOR` line didn't work for me. I changed it to `git commit -m "$SHA: ${f}"` for a simpler commit message, and now the script works wonderfully =3 – Ancurio Jun 16 '20 at 16:23
  • @Ancurio Glad you liked the script. Which OS are you on? Is `ed` installed (run `which ed` to check)? – raphinesse Jun 16 '20 at 21:04
  • @raphinesse yes `which ed` responds positively. I am on Fedora 31. – Ancurio Jun 17 '20 at 17:16
  • @Ancurio Hm. Maybe the `echo '0a\n$SHA $f\n\n.\nw'` isn't as portable as I thought. Could you try replacing that with `printf '0a\n$SHA $f\n\n.\nw\n'` (mind the extra `\n` at the end)? – raphinesse Jun 18 '20 at 13:33
  • @raphinesse didn't work either, but this time I got an "there was a problem with the editor 'nano'" message. – Ancurio Jun 18 '20 at 21:34
  • @Ancurio Strange. Why would it try to call nano? I'm out of ideas. But thanks for trying out my suggestions. – raphinesse Jun 18 '20 at 21:49
  • @raphinesse no idea. to me, the only important part was knowing which commit affected which file so I could properly bisect. Everything else was just fluff. – Ancurio Jun 19 '20 at 14:24
2

For every commit, you would need

Do that in a working tree of a dedicated branch, and for every file extracted, add and commit.

Then you can reset your current branch by that new one built commit-file by commit-file.

Community
  • 1
  • 1
VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • 1
    Note to self: That was my **16000th answer** on Stack Overflow (in 98 months), less than 6 months after the [15000th answer](http://stackoverflow.com/a/37539529/6309). Before that [14000th answer](http://stackoverflow.com/a/34327286/6309), [1300th answer](http://stackoverflow.com/a/31640408/6309). [12000th answer](http://stackoverflow.com/a/28412501/6309); [1100th answer](http://stackoverflow.com/a/25821796/6309), [10000th answer](http://stackoverflow.com/a/23909654/6309), [9000th answer](http://stackoverflow.com/a/20683667/6309), [8000th answer](http://stackoverflow.com/a/17569094/6309), ... – VonC Mar 27 '18 at 11:15