我在 GIT 分支中只有一个提交。如何删除那个提交?
I have only one commit in a GIT branch. How to delete that one commit?
我想从只包含一个提交的分支中删除一个提交。
我尝试使用
删除那个提交
git reset --soft HEAD~1
git push origin +dev --force
我能够删除所有提交。但是无法删除最后一次提交。
首先,您应该到您的分行结账 git checkout yourbranch
其次,查看命令 git log --oneline
的输出,它以 [HASH] commit message
格式显示提交列表。您应该在要删除的那个之前(在列表下方)复制提交的提交哈希。
第三,执行 git reset --hard [copied hash]
,您的分支将重置为之前的提交。
现在,如果需要,您可以强制将分支推送到服务器。
使用 git log
获取您提交的哈希值
然后去另一个分支最有可能的主要git checkout main
将你的提交暂时放在那里
git cherry-pick [commit-hash]
现在您可以简单地删除您的分支
// delete your branch locally
git branch -d [branch-name]
// delete your branch remotely
git push origin --delete [branch-name]
现在您可以使用
git reset --soft HEAD~1
并重新创建您的分支
[Using git reset
,] I was able to delete all the commits. But unable to delete the last commit.
您实际上无法删除 任何 提交。你实际上并没有删除它们,你只是让它们很难找到。您不能为 last 提交执行此操作的原因很简单:分支名称 always selects 一些 提交。在 Git.
中没有空分支这样的东西
事实上,在 Git 中, 分支名称 并不重要。它们使我们(人类和 Git 两者)有可能 find 提交,但重要的是 commits。要正确理解这一点,我们必须看看 Git 如何“看到”提交。
Git 存储库的核心是数据库的集合:
有一个数据库,通常是迄今为止最大的数据库,用于存储提交和其他支持 object。这就是 git clone
副本:克隆是大数据库的副本。
(这个数据库是作为数据库实现的。它是一个简单的key-value store,其中的键是散列ID或 object ID,git log
打印为提交编号的那些又大又丑的 random-looking 东西。有不止一种类型的内部 object ,但提交是您一直看到的内容。)
有一些较小的包含 names 等。这些目前是一种俗气、蹩脚的实现,不是合适的数据库,但它们大多像简单的 key-value 存储一样工作,其中名称是键,值是那些丑陋的大哈希 ID 之一。
这意味着每个名称——在这种情况下,每个分支名称——只包含 one 哈希 ID,对于 one commit .
commits 构成了实际...好吧,这里常用的词是 branches,但我们正在努力定义 branch,所以让我们避免这个词,只说“我们关心的东西”。我们知道,基于以上内容,每个提交都是 编号 和哈希 ID。这些哈希 ID 对于提交是唯一的:没有两个提交可以具有相同的哈希 ID。1 我们需要知道的其余部分是:
每个提交存储两件事:数据——每个文件的完整快照——和元数据。
在元数据中,Git存储了一些previous commit(或commits,复数,for merge commits,or for at至少一个特例,没有 之前的提交)。
这使得提交形式 backwards-looking chains。也就是说,假设我们有一个只有三个提交的小型存储库。这三个提交具有 random-looking 哈希 ID,但要 绘制 它们,我们将只使用单个大写字母:A
表示我们所做的第一个提交,B
第二个,C
第三个。
提交 C
,在此 Git 存储库中,存储提交 B
的 实际 哈希 ID(无论它是什么),它在我们制作 C
时存在。所以我们说C
指向B
:
B <-C
但是提交 B
在其内部存储了早期提交 A
的实际哈希 ID。所以 B
指向 A
:
A <-B <-C
提交 A
,作为有史以来的第一个提交,不存储 任何 更早的哈希 ID。 (Git 称其为 root 提交。)它只是独立存在。
要使用这三个提交,我们需要能够找到它们的哈希 ID。所以 Git 为我们创建了一个 分支名称 就像 main
或 dev
:
A--B--C <-- dev (HEAD)
在这里我懒得在提交之间绘制内部箭头:不过没关系,因为每个提交的所有部分都是 read-only,一直冻结,包括向后指向的箭头。由于它们不能改变,我们知道它们指向后方。某些未来提交的哈希 ID 是未知且不可预测的,2 而过去提交的哈希 ID 是一成不变的,因此可以将 [=202= 的哈希 ID 刻在石头上]旧提交。
如果我们强制名称 dev
向后退一步,会发生以下情况:
C
/
A--B <-- dev (HEAD)
请注意,提交 C
未 删除。只是我们 find 通过 Git 将名称(例如 dev
)转换为哈希 ID 来提交。 names 中存储的哈希ID可以更改,现在dev
找到B
,而不是C
. B
向后指向 A
,所以我们只 看到 提交 B
和 C
.
再这样做一次会产生:
B--C
/
A <-- dev (HEAD)
同样,没有任何提交,但现在我们只看到 提交A
。
我们无法完全停止看到 A
:如果我们强制 dev
返回提交 B
,它会重新出现,但 A
是仍然存在,如果我们强制 dev
返回提交C
,所有三个提交都回来了,A
仍然存在。或者,我们可以使用现有三个中的任何一个作为其 parent 进行一些新的提交 D
。使用 A
作为它的 parent 给我们:
B--C
/
A
\
D <-- dev (HEAD)
换句话说,根提交似乎很特别。事实上它确实如此,但它并不 那么 我们 不能 对此做任何事情。有一个标志,--orphan
,我们可以给 git checkout
或 git switch
,使我们进入特殊模式。
1Git 只能随机保证这种唯一性。这就是哈希 ID 如此大和丑陋的原因:两个 Git 哈希 ID 之间只有 1-in-2160 意外碰撞的可能性,它们是“保证”是独一无二的。 pigeonhole principle 告诉我们这种方法 必须 会失败 总有一天 ,但是哈希的大小将那一天放在足够远的未来避免需要关心它。或者至少,这就是这里的希望:但这完全是另一个话题。
2实际哈希 ID 是加密哈希函数 运行 在所有(元)数据 in[=287= 上的输出] 提交。这包括不可预测的输入,例如 date-and-time-stamp 将在 上 下一次提交。由于哈希本身对每个输入位都很敏感,而我们不知道输入位是什么,我们也不知道未来的哈希 ID 是什么。 (这也是 为什么 我们不能更改提交中的任何数据:这样做会破坏散列。Git 验证散列 ID 我们 用于检索数据在对检索到的数据进行哈希处理时与获得的哈希ID相匹配,从而自动检测任何错误。)
创建根提交
让我们考虑一下新的 totally-empty 存储库的情况:
[no commits at all]
完全没有提交,我们的初始分支名称——main
或 master
或其他可能的名称——如何指向某个现有的提交?
答案是:不能。规则是:分支名称 必须 指向某个提交。所以分支名称不能满足它的规则。
Git对这个问题的解决方法很简单:不要创建分支名称。这意味着我们在某个分支 main
或 master
上, 不存在 。 Git 将其称为 孤儿分支 或 未出生的分支 。
当我们处于这种状态时,运行ning git commit
将——如果成功的话——写出一个 root 提交和 创建 分支:
A <-- main (HEAD)
现在我们在分支 main
上,它现在已经存在,现在我们有了根提交 A
。
假设我们做了三个提交,然后做了一个 dev
分支(它也指向 C
)然后强制 dev
一直回到 A
:
B--C <-- main
/
A <-- dev (HEAD)
如果我们现在想创建一个新的 根提交,我们需要在一个尚不存在的分支上创建相同的 状态。我们需要一个未出生的或 孤儿,分支:
git checkout --orphan newbranch
现在我们可以正常工作并进行新的提交。新提交将是 新根提交。现有的三个提交继续存在:
B--C <-- main
/
A <-- dev
但是我们有另一个新提交,D
,它在我们的新分支上:
D <-- newbranch (HEAD)
并且newbranch
(仍然)是我们当前的分支。
您不能删除提交,但可以放弃它们
让我们来 repository-so-far:
B--C <-- main
/
A <-- dev
D <-- newbranch (HEAD)
和强制名称dev
和main
指向提交D
,像这样:
git branch -f dev HEAD
git branch -f main HEAD
现在我们有:
A--B--C
D <-- dev, main, newbranch (HEAD)
All 名称查找提交 D
。我们现在可以切换到 dev
或 main
并删除名称 newbranch
,如果我们愿意:它不再需要任何东西,因为其他两个名称找到提交 D
.
那三个 A-B-C
提交呢?该问题的答案是:它们仍在存储库中,但除非您知道或可以找到它们的哈希 ID,否则您甚至 看不到它们。他们被遗弃了。
Git 将——最终,有一天,也许——垃圾收集 (git gc
) 放弃提交。这里的细节取决于很多因素。一些托管站点,例如 GitHub,在清除废弃的提交方面非常糟糕;其他人可能更擅长。在您自己的笔记本电脑上,您可以强制 Git 加速通常的垃圾收集,但默认情况下,放弃的提交将至少保留 30 天,以防您想要取回它们。
挂在“已删除”提交上的机制称为 reflog,git reflog
将向您显示已保存的哈希 ID。 (这是存储库中的另一个数据库或一系列数据库。您不应该过分依赖这些 name-to-ID 数据库中任何一个的确切实现,因为 Git 核心组现在正在研究处理它们的新方法。旧的方法在很长一段时间内都运作良好——大约二十年了——但压力在某些地方显现出来。)
结论
您不能从分支中“弹出”最后一次提交,因为分支名称——这是我们找到构成分支的提交的方式(或“DAGlet”,具体取决于分支一词的含义)首先)——必须指向某个提交。所以没有一个分支是真正的 empty.
通常当我们查看一个分支时,我们希望查看该分支的一些selected子集:我们可以找到的提交,从一个通过分支名称找到的,然后向后工作直到......某个时候。通过仔细选择截止点,我们可以假装我们有一个空分支:
...--G--H <-- main, dev
如果我们列出“在”main
或 dev
上的提交,它是相同的列表。例如,如果我们要求 在 dev
上提交,但不在 main
上提交——我们通过 git log main..dev
获得;注意这里的两个点——然后我们会看到一个空列表。一旦我们 git checkout dev
并添加 new 提交:
...--G--H <-- main
\
I--J <-- dev (HEAD)
然后 main..dev
将 select 只是那两个提交,J
和 I
,在 Git通常的倒序。
我想从只包含一个提交的分支中删除一个提交。
我尝试使用
删除那个提交git reset --soft HEAD~1
git push origin +dev --force
我能够删除所有提交。但是无法删除最后一次提交。
首先,您应该到您的分行结账 git checkout yourbranch
其次,查看命令 git log --oneline
的输出,它以 [HASH] commit message
格式显示提交列表。您应该在要删除的那个之前(在列表下方)复制提交的提交哈希。
第三,执行 git reset --hard [copied hash]
,您的分支将重置为之前的提交。
现在,如果需要,您可以强制将分支推送到服务器。
使用 git log
然后去另一个分支最有可能的主要git checkout main
将你的提交暂时放在那里
git cherry-pick [commit-hash]
现在您可以简单地删除您的分支
// delete your branch locally
git branch -d [branch-name]
// delete your branch remotely
git push origin --delete [branch-name]
现在您可以使用
git reset --soft HEAD~1
并重新创建您的分支
[Using
git reset
,] I was able to delete all the commits. But unable to delete the last commit.
您实际上无法删除 任何 提交。你实际上并没有删除它们,你只是让它们很难找到。您不能为 last 提交执行此操作的原因很简单:分支名称 always selects 一些 提交。在 Git.
中没有空分支这样的东西事实上,在 Git 中, 分支名称 并不重要。它们使我们(人类和 Git 两者)有可能 find 提交,但重要的是 commits。要正确理解这一点,我们必须看看 Git 如何“看到”提交。
Git 存储库的核心是数据库的集合:
有一个数据库,通常是迄今为止最大的数据库,用于存储提交和其他支持 object。这就是
git clone
副本:克隆是大数据库的副本。(这个数据库是作为数据库实现的。它是一个简单的key-value store,其中的键是散列ID或 object ID,
git log
打印为提交编号的那些又大又丑的 random-looking 东西。有不止一种类型的内部 object ,但提交是您一直看到的内容。)有一些较小的包含 names 等。这些目前是一种俗气、蹩脚的实现,不是合适的数据库,但它们大多像简单的 key-value 存储一样工作,其中名称是键,值是那些丑陋的大哈希 ID 之一。
这意味着每个名称——在这种情况下,每个分支名称——只包含 one 哈希 ID,对于 one commit .
commits 构成了实际...好吧,这里常用的词是 branches,但我们正在努力定义 branch,所以让我们避免这个词,只说“我们关心的东西”。我们知道,基于以上内容,每个提交都是 编号 和哈希 ID。这些哈希 ID 对于提交是唯一的:没有两个提交可以具有相同的哈希 ID。1 我们需要知道的其余部分是:
每个提交存储两件事:数据——每个文件的完整快照——和元数据。
在元数据中,Git存储了一些previous commit(或commits,复数,for merge commits,or for at至少一个特例,没有 之前的提交)。
这使得提交形式 backwards-looking chains。也就是说,假设我们有一个只有三个提交的小型存储库。这三个提交具有 random-looking 哈希 ID,但要 绘制 它们,我们将只使用单个大写字母:A
表示我们所做的第一个提交,B
第二个,C
第三个。
提交 C
,在此 Git 存储库中,存储提交 B
的 实际 哈希 ID(无论它是什么),它在我们制作 C
时存在。所以我们说C
指向B
:
B <-C
但是提交 B
在其内部存储了早期提交 A
的实际哈希 ID。所以 B
指向 A
:
A <-B <-C
提交 A
,作为有史以来的第一个提交,不存储 任何 更早的哈希 ID。 (Git 称其为 root 提交。)它只是独立存在。
要使用这三个提交,我们需要能够找到它们的哈希 ID。所以 Git 为我们创建了一个 分支名称 就像 main
或 dev
:
A--B--C <-- dev (HEAD)
在这里我懒得在提交之间绘制内部箭头:不过没关系,因为每个提交的所有部分都是 read-only,一直冻结,包括向后指向的箭头。由于它们不能改变,我们知道它们指向后方。某些未来提交的哈希 ID 是未知且不可预测的,2 而过去提交的哈希 ID 是一成不变的,因此可以将 [=202= 的哈希 ID 刻在石头上]旧提交。
如果我们强制名称 dev
向后退一步,会发生以下情况:
C
/
A--B <-- dev (HEAD)
请注意,提交 C
未 删除。只是我们 find 通过 Git 将名称(例如 dev
)转换为哈希 ID 来提交。 names 中存储的哈希ID可以更改,现在dev
找到B
,而不是C
. B
向后指向 A
,所以我们只 看到 提交 B
和 C
.
再这样做一次会产生:
B--C
/
A <-- dev (HEAD)
同样,没有任何提交,但现在我们只看到 提交A
。
我们无法完全停止看到 A
:如果我们强制 dev
返回提交 B
,它会重新出现,但 A
是仍然存在,如果我们强制 dev
返回提交C
,所有三个提交都回来了,A
仍然存在。或者,我们可以使用现有三个中的任何一个作为其 parent 进行一些新的提交 D
。使用 A
作为它的 parent 给我们:
B--C
/
A
\
D <-- dev (HEAD)
换句话说,根提交似乎很特别。事实上它确实如此,但它并不 那么 我们 不能 对此做任何事情。有一个标志,--orphan
,我们可以给 git checkout
或 git switch
,使我们进入特殊模式。
1Git 只能随机保证这种唯一性。这就是哈希 ID 如此大和丑陋的原因:两个 Git 哈希 ID 之间只有 1-in-2160 意外碰撞的可能性,它们是“保证”是独一无二的。 pigeonhole principle 告诉我们这种方法 必须 会失败 总有一天 ,但是哈希的大小将那一天放在足够远的未来避免需要关心它。或者至少,这就是这里的希望:但这完全是另一个话题。
2实际哈希 ID 是加密哈希函数 运行 在所有(元)数据 in[=287= 上的输出] 提交。这包括不可预测的输入,例如 date-and-time-stamp 将在 上 下一次提交。由于哈希本身对每个输入位都很敏感,而我们不知道输入位是什么,我们也不知道未来的哈希 ID 是什么。 (这也是 为什么 我们不能更改提交中的任何数据:这样做会破坏散列。Git 验证散列 ID 我们 用于检索数据在对检索到的数据进行哈希处理时与获得的哈希ID相匹配,从而自动检测任何错误。)
创建根提交
让我们考虑一下新的 totally-empty 存储库的情况:
[no commits at all]
完全没有提交,我们的初始分支名称——main
或 master
或其他可能的名称——如何指向某个现有的提交?
答案是:不能。规则是:分支名称 必须 指向某个提交。所以分支名称不能满足它的规则。
Git对这个问题的解决方法很简单:不要创建分支名称。这意味着我们在某个分支 main
或 master
上, 不存在 。 Git 将其称为 孤儿分支 或 未出生的分支 。
当我们处于这种状态时,运行ning git commit
将——如果成功的话——写出一个 root 提交和 创建 分支:
A <-- main (HEAD)
现在我们在分支 main
上,它现在已经存在,现在我们有了根提交 A
。
假设我们做了三个提交,然后做了一个 dev
分支(它也指向 C
)然后强制 dev
一直回到 A
:
B--C <-- main
/
A <-- dev (HEAD)
如果我们现在想创建一个新的 根提交,我们需要在一个尚不存在的分支上创建相同的 状态。我们需要一个未出生的或 孤儿,分支:
git checkout --orphan newbranch
现在我们可以正常工作并进行新的提交。新提交将是 新根提交。现有的三个提交继续存在:
B--C <-- main
/
A <-- dev
但是我们有另一个新提交,D
,它在我们的新分支上:
D <-- newbranch (HEAD)
并且newbranch
(仍然)是我们当前的分支。
您不能删除提交,但可以放弃它们
让我们来 repository-so-far:
B--C <-- main
/
A <-- dev
D <-- newbranch (HEAD)
和强制名称dev
和main
指向提交D
,像这样:
git branch -f dev HEAD
git branch -f main HEAD
现在我们有:
A--B--C
D <-- dev, main, newbranch (HEAD)
All 名称查找提交 D
。我们现在可以切换到 dev
或 main
并删除名称 newbranch
,如果我们愿意:它不再需要任何东西,因为其他两个名称找到提交 D
.
那三个 A-B-C
提交呢?该问题的答案是:它们仍在存储库中,但除非您知道或可以找到它们的哈希 ID,否则您甚至 看不到它们。他们被遗弃了。
Git 将——最终,有一天,也许——垃圾收集 (git gc
) 放弃提交。这里的细节取决于很多因素。一些托管站点,例如 GitHub,在清除废弃的提交方面非常糟糕;其他人可能更擅长。在您自己的笔记本电脑上,您可以强制 Git 加速通常的垃圾收集,但默认情况下,放弃的提交将至少保留 30 天,以防您想要取回它们。
挂在“已删除”提交上的机制称为 reflog,git reflog
将向您显示已保存的哈希 ID。 (这是存储库中的另一个数据库或一系列数据库。您不应该过分依赖这些 name-to-ID 数据库中任何一个的确切实现,因为 Git 核心组现在正在研究处理它们的新方法。旧的方法在很长一段时间内都运作良好——大约二十年了——但压力在某些地方显现出来。)
结论
您不能从分支中“弹出”最后一次提交,因为分支名称——这是我们找到构成分支的提交的方式(或“DAGlet”,具体取决于分支一词的含义)首先)——必须指向某个提交。所以没有一个分支是真正的 empty.
通常当我们查看一个分支时,我们希望查看该分支的一些selected子集:我们可以找到的提交,从一个通过分支名称找到的,然后向后工作直到......某个时候。通过仔细选择截止点,我们可以假装我们有一个空分支:
...--G--H <-- main, dev
如果我们列出“在”main
或 dev
上的提交,它是相同的列表。例如,如果我们要求 在 dev
上提交,但不在 main
上提交——我们通过 git log main..dev
获得;注意这里的两个点——然后我们会看到一个空列表。一旦我们 git checkout dev
并添加 new 提交:
...--G--H <-- main
\
I--J <-- dev (HEAD)
然后 main..dev
将 select 只是那两个提交,J
和 I
,在 Git通常的倒序。