Иногда мне нужно разбить коммит, который касается нескольких файлов, на отдельные коммиты.
Например, предположим, у меня есть коммит, который касается файлов foo
и bar
, и я хочу разделить на два коммитаодин из которых касается только foo
, а другой - только bar
. Предположим, что коммит, который я пытаюсь разделить, является коммитом-подсказкой.
Например, предположим, что это история моего репозитория
commit a78143999ebc8fb1d696832ed5ef2654a3325bb4
Author: Gregory Nisbet <>
Date: Fri Oct 25 23:12:55 2019 -0700
add both foo and bar
commit 760208789956815d471ccd1ce9b642b32299cbb3
Author: Gregory Nisbet <>
Date: Fri Oct 25 23:12:25 2019 -0700
empty foo and bar
И я хочу изменить его на что-то более похожееэто. Там, где я добавляю новый коммит сверху, это касается только foo
. Второй коммит, в этом конкретном примере, касается только bar
. В идеале я хотел бы иметь возможность выполнять такое разделение, просто нацелив исходный коммит с помощью foo и bar и указав пути для добавления нового коммита.
commit 938b52c57511f1150ca0f1958458ece069205631
Author: Gregory Nisbet <>
Date: Fri Oct 25 23:18:14 2019 -0700
add foo only
commit d9c09b249f105ffd65f35392b26ac6a08f7effc2
Author: Gregory Nisbet <>
Date: Fri Oct 25 23:12:55 2019 -0700
add bar only
commit 760208789956815d471ccd1ce9b642b32299cbb3
Author: Gregory Nisbet <>
Date: Fri Oct 25 23:12:25 2019 -0700
empty foo and bar
Я пытался написатьскрипт, который выполняет это поведение расщепления путем создания двух возвратов и внесения поправок первого возврата в исходный коммит.
Скрипт отвратителен, но он работает в тех немногих случаях, которые я пробовал, и имеет правильный API.
Мне кажется, что должен быть более прямой способ достижения того, что я пытаюсь сделать.
#!/bin/bash
while getopts "m:" opt; do
case "$opt" in
m)
message="$OPTARG" ;;
esac
done
shift "$((OPTIND-1))"
if test -z "$message"; then
1>&2 printf '%s\n' 'message cannot be empty'
exit 20
fi
branch="$(python -c 'import uuid; print(str(uuid.uuid4()))')"
if 1>/dev/null 2>/dev/null git diff --exit-code; then
: "do nothing"
else
1>&2 printf '%s\n' 'working directory not clear'
exit 30
fi
hash="$(git rev-parse --verify HEAD)"
# revert initial commit, write revert to index
git revert --no-commit -- "$hash" || exit 10
# remove paths specified on command line from index
git reset HEAD "$@" || { git revert --abort; exit 20; }
git checkout -- "$@" || { git revert --abort; exit 30; }
# commit partial revert
git commit --allow-empty-message --no-edit || exit 40
# revert the just-committed partial revert, keep
# the new double revert in the index
git revert --no-commit HEAD || exit 50
# checkout an unused branch
git checkout -b "$branch" || exit 60
# commit double revert
git commit --allow-empty-message --no-edit || exit 70
# go back to previous branch
git checkout - || exit 80
# uncommit the latest change (the first revert)
git reset --soft HEAD^ || exit 90
# amend first revert onto previous commit
git commit --amend --no-edit || exit 10
# go to scratch branch
git checkout "$branch" || exit 20
# revert latest change in scratch branch, keep it in the index
git reset --soft HEAD^ || exit 30
# go to previous branch
git checkout - || exit 40
# use our commit message to create a new commit with that message
git commit --no-edit --message="$message" || exit 50
# delete temporary branch
git branch -D "$branch" || exit 60
Используя приведенный выше сценарий, я могу настроить таргетинг только на подмножествопутей в коммите tip, используя gitsplit -m 'second commit' foo
.
$ git init
Initialized empty Git repository in /tmp/gitdir/.git/
$ echo foo > foo
$ echo bar > bar
$ git add foo bar
$ git commit -m 'initial commit'
[master (root-commit) 4472c3c] initial commit
2 files changed, 2 insertions(+)
create mode 100644 bar
create mode 100644 foo
$ gitsplit -m 'second commit' foo
Unstaged changes after reset:
D foo
[master 9a5d357] Revert "initial commit"
1 file changed, 1 deletion(-)
delete mode 100644 bar
A bar
Switched to a new branch 'b70d1c24-aca5-4676-a287-d35dbe14f790'
[b70d1c24-aca5-4676-a287-d35dbe14f790 abb559a]
1 file changed, 1 insertion(+)
create mode 100644 bar
Switched to branch 'master'
[master 5d95965] initial commit
Date: Fri Oct 25 23:36:26 2019 -0700
1 file changed, 1 insertion(+)
create mode 100644 foo
Switched to branch 'b70d1c24-aca5-4676-a287-d35dbe14f790'
A bar
Switched to branch 'master'
[master 2f64de7] second commit
1 file changed, 1 insertion(+)
create mode 100644 bar
Deleted branch b70d1c24-aca5-4676-a287-d35dbe14f790 (was 9a5d357).
$ git --no-pager show --format=short HEAD
commit 2f64de7dc9df58323db21b676251df27c46b924f
Author: Gregory Nisbet <>
second commit
diff --git a/bar b/bar
new file mode 100644
index 0000000..5716ca5
--- /dev/null
+++ b/bar
@@ -0,0 +1 @@
+bar
$ git --no-pager show --format=short HEAD^
commit 5d9596507023a4af4dd7536e49e97359b35632d6
Author: Gregory Nisbet <>
initial commit
diff --git a/foo b/foo
new file mode 100644
index 0000000..257cc56
--- /dev/null
+++ b/foo
@@ -0,0 +1 @@
+foo