3

我有的:

---A----B-----C-----D--------*-----E-------> (master)
                     \      /
                      1----2 (foo)

我需要的:

---A---------------D--------*-----E-------> (master)
                    \      /
                     1----2 (foo)

不久前,我做了两个提交,我想从我的 git repo 中删除。我尝试了各种不同的变基“教程”,所有这些都以奇怪的 git 历史结尾,所以我创建了一个示例 repo,结果不是我所期望的。谁能帮我理解我错过了什么?

我有两个分支,master并且foo. 我使用要删除的单个文件提交B ,并在我修改此文件的位置提交C。在其他提交中,我再也没有碰过这个文件。

提交 ID:

A: f0e0796
B: 5ccb371
C: a46df1c
D: 8eb025b
E: b763a46
1: f5b0116
2: 175e01f

所以我使用rebase -i f0e0796和删除B 5ccb371C a46df1c,对吗?如果我正确解释了结果,这就是gitk我的 repo 显示的内容,尽管git branches仍然列出了第二个分支。

---A-----1----2---E------> (master)

谁能告诉我这里发生了什么?

编辑: 这是如何从第一张图重新创建回购:

git init foo
cd foo

touch A
git add A
git commit -m "add A"

touch B
git add B
git commit -m "add B"

echo "modify" > B
git add B
git commit -m "modify B"

touch C
git add C
git commit -m "add C"

git checkout -b foo

touch 1
git add 1
git commit -m "add 1"

touch 2
git add 2
git commit -m "add 2"

git switch master
git merge foo --no-ff

touch E
git add E
git commit -m "add E"
4

5 回答 5

3

git rebase默认情况下,只变基为单一的提交历史沿袭,因为这更通常是人们想要的。如果您不另外告诉它,它将为您签出的分支执行此操作(在您的情况下是master)。这就是为什么你最终得到了一个重新定位的master分支,其中foo提交移植而不是合并,并且它foo本身没有改变并且不再连接。

如果你有 git 2.18 或更高版本,你可以使用--rebase-merges option* 告诉 git 重新创建合并历史,而不是像默认情况下那样线性化它。重新定位的历史将具有相同的分支和合并回。下面我将引导您完成实现您想要使用的内容的步骤--rebase-merges

这些步骤假定您在问题中显示的确切回购。

  1. git checkout master
  2. git rebase -i --rebase-merges f0e0796
  3. 在交互式变基todo文件中:
    • 删除您想要删除的两个提交(或将它们注释掉,或更改pickdropor d
    • 在 line 之后的新行上label foo,添加以下内容:
    exec git branch -f foo head
    
    (解释见下文)
  4. 保存并关闭待办事项文件,瞧,git 将重新设置提交的基础,图形看起来完全符合您的要求。


文件todo解释

git rebase只是自动化了一系列您可以手动执行的步骤。此步骤序列在todo文件中表示。git rebase --interactive允许您在执行之前修改序列。

我会用解释来注释它,包括你将如何手动进行(良好的学习经验)。如果您在未来进行大量 rebase,那么了解这一点很重要,因此当发生合并冲突时,或者当您告诉 rebase 在某些点暂停以便您可以进行一些手动 mods 时,您会有很好的方位。

label onto                  // labels "rebase onto" commit (f0e0796)
                            // this is what you would do in your head
                            // if doing this manually
# Branch foo
reset onto                  // git reset --hard <onto>
drop 5ccb371 add B          // skip this commit
drop a46df1c modify B       // skip this commit
pick 8eb025b add C          // git cherry-pick 8eb025b
label branch-point          // label this commit so we can reset back to it later
pick f5b0116 add 1          // git cherry-pick f5b0116
pick 175e01f add 2          // git cherry-pick 175e01f
label foo                   // label this commit so we can merge it later
                            //   This is just a rebase internal label. 
                            //   It does not affect the `foo` branch ref.
exec git branch -f foo head // point the `foo` branch ref to this commit 

reset branch-point # add C  // git reset --hard <branch-point>
merge -C b763a46 foo # Merge branch 'foo'  // git merge --no-ff foo
                                           // use comment from b763a46

exec git branch -f foo head解释

正如我上面提到的, git rebase 只在一个分支上运行。该exec命令所做的是将 ref 更改foo为指向 current head。正如您在 todo 文件中的序列中看到的那样,您告诉它在提交foo分支的最后一次提交(“add 2”)后立即执行此操作,这在 todo 文件中很方便地标记label foo

如果您不再需要fooref(例如,它是一个功能分支,这是它的最终合并),您可以跳过将此行添加到 todo 文件。

您也可以跳过添加此行并foo在 rebase 完成后单独重新指向您想要的提交:

git branch -f foo <hash of the rebased commit that should be the new head of `foo`>

如果您有任何问题,请告诉我。


*如果您有旧版本的 git,您可以使用现已弃用的--preserve-merges选项,尽管它与 rebase 的交互模式不兼容。

于 2020-04-24T15:31:04.520 回答
2

虽然我的提议会给你一个干净的、线性的历史;这就是 rebase 本质上应该做的。但是,我希望这为您提供了一种从提交历史记录中删除 B 和 B' 的方法。解释如下:

Repo recreation output:
---A----B-----B'-----C--------D-------> (master)
                      \      /
                       1----2 (foo)

git log --graph --all --oneline --decorate #initial view the git commit graph
* dfa0f63 (HEAD -> master) add E
*   843612e Merge branch 'foo'
|\  
| * 3fd261f (foo) add 2
| * ed338bb add 1
|/  
* bf79650 add C
* ff94039 modify B
* 583110a add B
* cd8f6cd add A

git rebase -i HEAD~5 #here you drop 583110a/add B and ff94039/modify B from
foo branch.

git log --graph --all --oneline --decorate
$ git rebase -i HEAD~5
* 701d9e7 (HEAD -> master) add E
* 5a4be4f add 2
* 75b43d5 add 1
* 151742d add C
| * 3fd261f (foo) add 2
| * ed338bb add 1
| * bf79650 add C
| * ff94039 modify B
| * 583110a add B
|/  
* cd8f6cd add A

$ git rebase -i master foo #drop 583110a/add B and ff94039/modify B again

$ git log --graph --all --oneline --decorate #view the git commit graph

* 701d9e7 (HEAD -> foo, master) add E
* 5a4be4f add 2
* 75b43d5 add 1
* 151742d add C
* cd8f6cd add A

最后,最终出局的顺序可能不是您预期的 A--C--1---2---E。但是,您可以再次在交互模式中重新排列顺序。试试 git rebase -i HEAD~n。

注意:最好避免更改提交/发布历史。我是一个新手,正在探索 git,希望上述解决方案能够坚持下去。也就是说,我确信网上还有很多其他更简单的解决方案。我发现这篇文章很有帮助,供大家将来参考。

于 2020-04-12T18:50:39.290 回答
2
于 2020-04-24T20:10:47.967 回答
1

首先要理解的是提交是不可变的对象。当你按照你的提议重写历史时,你最终会得到一组完全不同的提交。父级是每个提交的不可变哈希的一部分,除此之外您无法更改。如果你按照你的建议去做,你的历史将如下所示:

     D'-----E'-----> (master)
    /
---A----B-----C-----D--------E-------> (abandoned)
                     \      /
                      1----2 (foo)

要实现这一点,您只需重新D..E定位A并重置masterE'. 您可以(但实际上不必)然后重新定位1..fooD'.

一种更简单且在我看来是正确的方法是在新提交中删除文件:

---A----B-----C-----D--------E-----F-----> (master)
                     \      /
                      1----2 (foo)

F是 的结果git rm that_file。git 的目的是维护历史。仅仅因为它看起来不漂亮而修剪它是没有效率的(再次,我的观点)。我唯一一次推荐前一个选项是有问题的文件中包含敏感信息,如密码。

另一方面,如果您想要清理文件,您将不得不采取更极端的措施。例如:如何从 Git 历史记录中删除文件?

于 2020-04-11T15:51:15.840 回答
1

要重新排列提交历史记录,有几种方法。

rebase当您想要更改整个 repo 的历史时,问题在于它一次只移动一个分支。此外,它在处理合并方面存在问题,因此您不能简单地重新定位D并保留当前存在的最新历史(因为是合并)。EAE

可以解决所有这些问题,但该方法复杂且容易出错。有一些工具是为完全 repo 重写而设计的。您可能想查看filter-repo(替换 的工具filter-branch)-但听起来您只是想从历史记录中清除特定文件,这(1)可能是 BFG Repo Cleaner 的好工作,或(2)实际上是一个很容易的任务filter-branch

(如果你想研究 BFG,https ://rtyley.github.io/bfg-repo-cleaner/ ;如果你想研究filter-repohttps://github.com/newren/git-filter-repo

用于filter-branch此目的

git filter-branch --index-filter 'git rm --cached --ignore-unmatch path/to/file' --prune-empty -- --all

但是- 您表示您需要该文件不在存储库中(作为对某人建议从下一次提交中删除它的反对)。因此,您需要了解 git 不会那么轻易地放弃信息。使用任何这些技术后,您仍然可以从 repo 中提取文件。

这是一个很大的话题,已经在关于 SO 的各种问题/答案中讨论了无数次,所以我建议搜索你真正需要问的问题:如何永久删除不应该在源代码下的文件控制。

几点注意事项:

1 - 如果有密码并且它们曾经被推送到共享遥控器,那么这些密码就会被泄露。您对此无能为力;更改密码。

2 - 每个 repo(远程和每个克隆)都必须被故意擦洗,或者扔掉和更换。(如果某人不想合作,您就不能强迫他们这样做,这是(1)的原因之一。)

3 - 在您进行修复的本地 repo 中,您必须删除 reflogs(以及如果您使用类似工具可能已创建的备份 refs filter-branch),然后运行gc​​. 或者,重新克隆到仅获取分支的新版本的新存储库可能更容易。

4 - 甚至可能无法清理遥控器,具体取决于它的托管方式。有时您能做的最好的事情就是核对遥控器,然后从头开始重新创建它。

于 2020-04-11T18:29:46.680 回答