英文:
Is there a way to split a commit into fixups for the different commits it affects?
问题
假设我的存储库有两个文件,f1
和 f2
,它们的每一行都被多个不同的提交更改过。现在假设我的头提交,c3,更改了f1
的第L_1_1行到L_1_2行以及f2
的第L_2_1行到L_2_2行,它们分别是在提交c1和c2中最后一次更改的。为简单起见,让我们假设每个完整的行范围都是由相应的提交完全更改的。
现在,我想将这个提交分成两个单独的更改,并将每个更改作为修复分别应用到c1和c2,以便它们各自获取我在c3中对相关文件所做的更改,而不是在单独的提交中进行更改。
当然,我可以手动完成这个操作:对c3~进行软重置,分别提交每个文件,并在重新基础时编辑待办列表以对应我想要的修复。但这有点繁琐,特别是如果不仅仅是2个文件,而是更多文件的情况。
有什么更方便的方法可以实现这个更改吗?(如果可能的话。)
备注:
- 当然,我希望不仅仅能够对2个文件进行这样的操作,还能对多个文件进行操作。
- c3、c2 和 c1 不是连续的提交,也不是唯一的提交。c3 是当前的头提交,这是您唯一能够假定的。
英文:
Suppose my repository has two files, f1
and f2
, lines of each of which have been changed by multiple different commits. Now suppose my head commit, c3, changes line L_1_1 through L_1_2 of f1
and L_2_1 through L_2_2 of f2
, which were last altered in commits c1 and c2 respectively. For simplicity, let's assume each complete range of lines was altered in its entirety by its corresponding commit.
Now, I want to break this commit up into two separate changes, and apply each of them as a fixup to c1 and c2, so that they each get the changes I made to the relevant files in c3, at the point where the relevant lines were last changed rather than in a separate commit.
Naturally, I can do this manually: A soft reset to c3~, committing each of the files separately, and rebasing with editing the todo-list to correspond to the fixups I want. But this is a bit cumbersome, especially if it's not just the case of 2 files, but a lot more.
How can I affect this change more conveniently? (If it's at all possible.)
Notes:
- Naturally, I want to be able to do this not just for 2 files, but for many files.
- c3, c2 and c1 are not consecutive commits, nor the only commits. c3 is the head, that's all you can assume.
答案1
得分: 1
我相信答案是肯定的,这可以自动化。以下算法应该可以完成你所要求的任务:
- 从一个干净的
git status
开始(没有待处理的文件)。 - 重置到混合(mixed)状态,后退一个提交。现在,该提交(
c3
)中的所有更改都将处于待处理状态。 - 遍历通过
git diff --name-only
呈现的每个待处理文件。 - 对于每个文件:将其暂存(stage it),并使用
--fixup
选项提交,指定你想要将这个新提交合并到的提交ID。你可以使用一个函数来确定该提交ID。我首先想到的第一个合理的函数是在c3
之前修改文件的最后一个提交ID,例如:
git log --format="%H" -n1 <file-name> # 修改<file-name>的最后一个提交ID
- 现在执行一个交互式的重置(rebase),使用autosquash,并将编辑器设置为空,这样你就不会提示保存和继续。这将自动将新提交与它们各自的先前提交使用fixup合并。
以下是一个使用Bash脚本演示的最小重现例(MRE),其中有5个文件(但任何数量都应该有效):
#!/bin/bash
git init
git config core.safecrlf false # 不显示换行警告
# 创建一些文件,每个文件在自己的提交中
for i in {1..5}
do
echo "Creating file$i.txt..."
echo "This is file $i" > file$i.txt
git add .
git commit -m "Add file$i.txt"
done
# 修改每个文件
for i in {1..5}
do
echo "Modifying file$i.txt..."
echo "Another line to file $i" >> file$i.txt
done
# 创建一个包含所有文件修改的单个提交
git add .
git commit -m "Modify all files"
echo ""
echo "显示当前日志:"
git --no-pager log --oneline # 显示当前历史记录
echo ""
# 重置混合状态回退1个提交
git reset @~1
# 遍历待处理文件并创建一个fixup提交,以合并该文件最后一个修改的提交
git diff --name-only | \
while read file;
do
# 只暂存这个文件
git add $file
# 使用fixup提交最近修改此文件的提交
git commit --fixup $(git log --format="%H" -n1 $file) # 这可以是一个函数
echo ""
done
echo ""
echo "显示当前日志:"
git --no-pager log --oneline # 显示当前历史记录
echo ""
# 使用autosquash进行交互式重置,而不提示编辑器
GIT_SEQUENCE_EDITOR=: git rebase -i --root --autosquash
echo ""
echo "显示当前日志:"
git --no-pager log --oneline # 显示当前历史记录
echo ""
英文:
I believe the answer is yes, this can be automated. The following algorithm should do what you're asking:
- Start with a clean
git status
. (No pending files.) - Reset mixed, back one commit. Now all of the changes in that commit (
c3
) will be pending. - Loop through each of the pending files presented by
git diff --name-only
. - For each file: stage it, and commit it, using the
--fixup
option, and specify the commit ID you wish to squash this new commit into. You can use a function to determine that commit ID. The first sensible function that comes to mind is the last commit ID that modified the file beforec3
, for example:
git log --format="%H" -n1 <file-name> # last commit ID to modify <file-name>
- Now do an interactive rebase using autosquash, and set the editor to null so you aren't prompted to save and continue. This will automatically squash the new commits into their respective prior commits using fixup.
Here's an MRE in a Bash script that demonstrates with 5 files (though any number should work):
#!/bin/bash
git init
git config core.safecrlf false # don't display line ending warnings
# create some files, each in their own commit
for i in {1..5}
do
echo "Creating file$i.txt..."
echo "This is file $i" > file$i.txt
git add .
git commit -m "Add file$i.txt"
done
# modify each file
for i in {1..5}
do
echo "Modifying file$i.txt..."
echo "Another line to file $i" >> file$i.txt
done
# create a single commit with all of the file modifications
git add .
git commit -m "Modify all files"
echo ""
echo "Show current log:"
git --no-pager log --oneline # show current history
echo ""
# reset mixed back 1 commit
git reset @~1
# loop through pending files and create a fixup commit to the last commit modifying the file
git diff --name-only | \
while read file;
do
# stage only this file
git add $file
# commit the add with fixup to the most recent commit modifying this file
git commit --fixup $(git log --format="%H" -n1 $file) # this could be a function
echo ""
done
echo ""
echo "Show current log:"
git --no-pager log --oneline # show current history
echo ""
# interactive rebase with autosquash, without prompting for an editor
GIT_SEQUENCE_EDITOR=: git rebase -i --root --autosquash
echo ""
echo "Show current log:"
git --no-pager log --oneline # show current history
echo ""
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论