如何在 'git push --set-upstream origin master' 之后恢复代码

How to recover code after 'git push --set-upstream origin master'

我试图将我的代码推送到一个新的存储库,但以典型的 git 方式,如果您不了解每个细节,学习和内爆代码就像火箭手术一样容易。好的,我做了一些事情,现在我的大部分代码都不见了,那么我该如何取消删除我的代码或取回它呢?

那么如何撤销 bash git 上的错误?

为什么没有“git 撤消”?

我(不小心)输入的是“git push --set-upstream origin master”,现在我的代码被清除了。好吧,我希望它仍然在一个文件夹中,但由于任何 'because git' 原因而没有显示。

我已经尝试了三年来理解 git 但它对我来说仍然是 90% 的胡言乱语。

'git reflog' 是 google 搜索显示的内容,但它只显示了这个信息。

f668709 (HEAD, origin/master) HEAD@{0}: rebase: checkout origin/master
5061c5e (master) HEAD@{1}: commit (initial): first new commit

Why isn't there a "git undo" ?

有……嗯,有点。这里有很多收获。

What I typed in ( by accident ) was "git push --set-upstream origin master"

这不会对您自己存储库中的任何提交进行任何更改。这也是一种“安全”的推送,因此它不会破坏 other Git 存储库中的任何内容,在 origin 上。应该没有任何 撤消。

如果推送失败——我怀疑它失败了——你可能会被 Git 鼓励到 运行 git pullgit pull --rebase 或类似的。 不过,这可能是个错误。

I've tried for three years to understand git and it still 90% gibberish to me.

您可能需要不同的方法。 :-) 你熟悉图论吗?如果是,或者即使不是,请考虑仔细阅读网站 Think Like (a) Git。但这是在更基本的事情之后完成的:意识到 Git 经常 隐藏 什么 Git 有,并向你展示完全是别的东西。

'git reflog' is what google search is showing but it just shows me this info.

f668709 (HEAD, origin/master) HEAD@{0}: rebase: checkout origin/master
5061c5e (master) HEAD@{1}: commit (initial): first new commit

这强烈表明(尽管本身并不能证明):

  • 您的 Git 存储库中可能只有两个提交(至少有两个);和
  • 不是 git push origin master 引起了麻烦,而是 git rebase

如果您运行 git status,它可能会告诉您您正处于变基过程中。在 git push origin master.

之后,你必须有 运行 东西

如果您 处于变基过程中,您可以终止该过程并将事情恢复到 git rebase --abort 时的状态。但是请注意,如果您已经完成了一些您想要保存的工作,此 --abort 操作可能会将其丢弃:首先复制您想要保存的任何文件!

长:你的文件去了哪里

我会在此处写一篇 long-ish(对于 来说不会那么长)描述你的文件去了哪里,以及它们在 [= =25=],假设他们真的回来了。我认为,根据你在问题中所说的,他们会的。您可以跳到下一部分,但最好通读整个内容。

你理解 Git

的第一个关键点是什么

如果您要使用 Git(当然,没有人说您 ),花一些时间了解它在做什么可能是明智的,以及为什么它如此顽固。原因是 Git 并不是真正关于 文件 .

当您工作时,您可能会处理文件。毕竟,这就是您的计算机存储的内容:组织到目录/文件夹中的文件。所以你会期望 Git 与这些一起工作。它的确如此——有点。但它坚持这样做它的方式。

Git 所做的是存储 提交 。这些是编号的实体。您的 git reflog 输出显示了两个提交:

  • 5061c5e:这是你自己在本地制作的;
  • f668709:某人——可能不是你——制作了这个,可能在其他Git存储库中。

这两个提交可能相互关联也可能不相关。要找出答案,您可以 运行 git log --all --decorate --graph。相互关联的提交连接在一起,如下所示:

* commit 72c4083ddf91b489b7b7b812df67ee8842177d98 (HEAD -> master, origin/master, origin/HEAD)
| Author: Junio C Hamano <gitster pobox.com>
| Date:   Wed Jan 6 23:22:15 2021 -0800
| 
|     The first batch in 2.31 cycle
|     
|     Signed-off-by: Junio C Hamano <gitster pobox.com>
|   
*   commit d3aff11c3eadf3c496859180d453ce07cde72b44
|\  Merge: cf4b0714f7 5bc12c11cc
| | Author: Junio C Hamano <gitster pobox.com>

(这种 git log 通常显示完整的提交编号,而不是 human-friendly1 您在 git reflog 输出。)

这表明提交 2c4083ddf... 是早期 parentchild提交 d3aff11c3...。 (parent 提交本身是一些更老祖先的 child,它们是更老祖先的 children,依此类推。)

最后,提交是 编号的 ,这些丑陋的大字符串 letters-and-digits,并且可以有某种家庭关系。这一切都很好,但仍然没有告诉你它们是什么,或者它们有什么好处。

的提交分为两部分。 任何提交的所有部分都是 read-only: 任何提交中的任何内容都不能更改。您 可以 从 Git 中提交,处理/使用它,然后放回修改后的版本,但您得到的是 new commit,使用新编号。具有旧编号的旧提交保持不变。同时,这两个部分是:

  • 提交的主体是 Git 知道的每个文件的已保存快照,其形式为 in Git 在你或任何人创造它的时候。这是我们将文件放入 Git 的地方:它们存在于提交中。但是您无法直接查看这些文件。

    其中一个原因是它们的存储方式与您的计算机存储文件的方式不同。相反,它们被压缩和冻结,并且——这样你的 Git 存储库就不会很快变得非常庞大——de-duplicated。大多数提交主要是 re-use 他们以前提交的大部分文件。所以,代替d 将每个文件的新副本放入每个新提交中,Git ar运行 基础文件数据 共享 所有 次提交。

    Git 让这个共享的确切机制在不同的地方展示出来。这可能是好事也可能是坏事,具体取决于您查看本应只是抽象的实现细节的喜好。为了避免这个答案变得太长,我不会在这里详细介绍;请记住,Git 往往对其细节过于自豪,时不时地用它们来打你的脸。我们稍后会在 Git 的 index.

    中再次看到这一点
  • 提交的其余部分——实际存储的部分作为提交;文件被间接存储以实现 de-duplication 技巧——包含元数据:关于提交本身的信息,例如提交人、时间和说明提交原因的日志消息。

    提交的“家庭关系”存储在此元数据中。 Git 实现此目的的方法是存储此提交的 parent 的 提交编号 。其他一切都必须从这个 I am the child of ____ stuff stored in each commit.2

因此,提交允许您存储文件——事实上,它们要求您永久保存每个文件——并且还存储元数据,包括parent年龄。这就是 b运行ches 进来的地方。


1或者我应该说少一点human-hostile.

2您可能想知道为什么 parent 不记录他们的 children。这是因为提交的这个 read-only 方面。当我们进行新的提交时——child 中的一些 parent——child 知道它的 parent 是谁。但是当我们制作child时,我们还不知道itschildren会是什么。他们稍后将获得一个唯一的 random-looking hash-ID 号码。实际数字根本不是 运行dom:它们是提交内容的加密校验和。但是它们 不可预测的,因为,例如,提交包括那些 time-stamps。您必须知道 何时 您将进行 child 提交,以及其中的所有其他内容,以预测其哈希 ID。 (还有另一个问题,但仅此一项就够难了。)


B运行ches 只是一种记住提交编号的方法

当你第一次提交时——在你的例子中,5061c5e(你给出了日志消息 first new commit)——它是 stand-alone。它只是您使用 git add 告诉 Git 的所有文件的快照。它有一个丑陋的大哈希 ID,对于这个特定的提交是唯一的,永远不会再在任何 Git 存储库中的任何地方使用。这个在这里简称为5061c5e,但让我们更短一点,称之为“commit A”,然后画出来:

A

如果您要更改一些文件,git add 它们,然后再次 运行 git commit,这将创建一个 new 快照每个文件——A 中的所有文件,根据你所做的任何更改进行更新,然后 git add-ed——并从中进行新的提交。新提交的parent,我们简称为B,将是现有提交A,因此B 在其元数据中保存 A 的 hash-ID 号码 (5061c5e)。

我们说提交 B 指向 提交 A 并像这样绘制:

A <-B

随着我们添加越来越多的提交,我们建立了一个简单的 backwards-looking 提交链:

A <-B <-C <-D

现在,这里的技巧是 Git 只能 找到 提交 按他们的 hash-ID 数字,在 Git 的最低水平。但数字很漂亮 human-hostile,即使是缩写。这就是为什么我一直在使用字母!如果我们不得不告诉 Git 提交的原始数量 D 只是为了使用它,那将是无法容忍的。因此,Git 将一组 名称 添加到 提交 和它保存的支持内容的集合中。这些名称如 master:例如 b运行ch 名称。3 Git 选择让这些名称只存储一个哈希 ID。由于提交 D 是,在我们这里的插图中,last 提交 master,这就是名称的 number将存储:

A <-B <-C <-D   <--master

每当我们进行 new 提交时,Git:

  1. 使新提交指向当前提交:新提交E将指向D;然后
  2. new 提交编号写入 b运行ch name

所以如果我们进行新的提交 E 我们最终会得到:

A <-B <-C <-D <-E   <--master

这就是我们增长一个b运行ch的方式,一次一个提交。我们使用 git checkoutgit switch 到 select b运行ch name 从而 last comit 在 b运行ch 上,名字本身指向它。


master这样的b运行ch名称实际上是较长的“全名”的缩写:refs/heads/master。这允许 Git 为 tag 名称使用相同的存储系统,这些名称在 refs/tags/ 下,以及其他类型的名称。


你的文件去了哪里

让我们回过头来考虑一下这些提交。每个存储 所有 您的文件,但以一种奇特的 twisted-up、read-only、de-duplicated 方式存储。这些文件几乎无法使用。它们仅作为 存档 有用。必须先 提取 它们才能处理或使用它们。

然后,提取这些文件至少是许多 Git 命令的一部分。其中最明显的一个是 git checkout。我们可以 git checkout a b运行ch name,或者我们可以使用 not a b运行 ch 名称——包括任何历史提交的原始哈希 ID 号——到 git checkout 该历史提交。

为此,Git 需要一个可以从提交中填写 的区域。该区域也是您工作的地方。大多数时候,这个工作区你的。但是,当您使用 git checkout 或其他 Git 命令明确 覆盖 您的工作区文件时,您告诉 Git:删除我现在这里的文件,并用其他文件替换它们。

一些 Git 命令在执行此操作时要小心,不要破坏任何 未提交的 文件。如果您告诉其他人这样做,他们会很高兴地破坏您所有未保存的文件。幸运的是,您在这里使用的命令几乎肯定是更安全的品种。4

无论如何,我们称您工作的这个区域为您的工作树work-tree。在这里,您的文件是 个文件 。他们有他们普通的日常形式。您实际上可以 使用 它们(想象一下!)。

如果您使用git checkout的(安全版本)选择一些commit来工作from,Git 已从提交的 中保存的文件中填写了您的 work-tree。 git rebase 命令在变基开始时执行此操作。你想要拥有的文件发生了什么,它们被安全地存储在other提交:提交5061c5e中。您现在 使用 提交 f668709.

运行 git rebase --abort 会告诉 Git 停止变基,然后返回提交 5061c5e。这将首先清除所有未保存的工作(!——这里的假设是你的 --abort 标志意味着 是的,我开始了这个但我想永远放弃它 ),然后做一个git checkout 的(安全版本)您之前 的提交,通过 git checkout master.


4如果 Git 有一个“安全”和“不安全”命令的列表可能会有所帮助。不幸的是,这个列表令人抓狂:

  • git checkout 是安全的,但它也不安全。这种特殊的疯狂导致了 Git 2.23 中新的 git switchgit restore 命令,让人们将他们的旧 git checkout 习惯分成两个单独的命令,一个安全的和一个不安全的。不过,我还没有对自己的手指进行重新编程,您可能使用的是旧的 Git,它只有 all-in-one git checkout.
  • git reset 大多数情况下是不安全的,除非你告诉它是安全的。
  • 不过,
  • git rebase 几乎总是安全的。

由于您正处于变基过程中,我们可以假设这里是安全的。


这里还有一件事要知道

您可能会认为,使用这种设置 - 当前提交 包含 frozen-forever 文件,以及那些文件已被复制的工作树您可以处理它们——这意味着当您在 Git 存储库中工作时,您总是有 每个文件的两个版本 可用。的确如此——许多 other 版本控制系统就是这样工作的——但它对 Git.

来说还不够

相反,Git 添加每个文件的 第三个 版本。我喜欢将每个文件的第三个副本视为“位于”冻结的已提交副本(我通常将其放在左侧)和可用副本(我通常将其放在右侧)之间。这给了我们一个中间副本。

那个中间副本存在于Git不同的地方,索引,或者暂存区,或者——现在很少见了——缓存。为什么这东西有三个名字?可能是因为 index 没有任何意义, cache 太具体了。 暂存区 一词反映了您作为一个人可以并且将会使用它的方式。这可能是对它最好的形容词。不过,它也是最新的,是在 Git 流行几年后发明的。

当Git 首先做了一些提交的 git checkout,它真正做的是:

  • 选择 that commit 作为 current commit,通过附加特殊名称 HEAD它以某种方式:您在 git reflog 输出中看到它,其中包括行:5

    f668709 (HEAD, origin/master)
    
  • 将提交保存的文件复制到Git的索引/staging-area;和

  • 将保存的文件复制到您的 work-tree,以便您可以查看和处理它们。

复制到 Git 的索引和您的 work-tree,需要从 Git 的索引和您的 [=] 中删除 585=] 安全存储在某些 other 提交中的任何文件。

现在,我们从其他版本控制系统的例子中知道,索引/staging-area 不是必需的。这个索引 / staging-area 的要点可能是让 Git 运行得更快(它确实做到了),或者可能是它启用的特殊技巧(它确实启用了特殊技巧)。我不太清楚原点 是什么。关于它的事情是你不能忽视它。如果你尝试,Git 会时不时地伸出手来打你的脸:你好!你现在必须对我的索引大惊小怪了!所以有必要学会忍受它,即使你永远不会喜欢它,只要你会使用Git.

索引内容很多。特别是,如果您正在进行合并(或任何足以 merge-like 的事情)并且存在 合并冲突 ,Git 将 扩展 索引,这样它就不再只是一个暂存区。

不过大多数时候,描述 Git 索引的一个好方法是它是您 提议的下一次提交 。 Git 只需从 current 提交中填写此提议的 next 提交。然后,每次更新工作树中的某些文件时,您需要告诉 Git:将我更新的文件复制回您的索引,以便我对 有不同的建议next commit. 这就是 git add 所做的:它制作某个文件的索引副本,与该文件的工作树副本相匹配。

然后,当您 运行 git commit、Git 可以而且确实会打包 索引中的任何文件——它们'以特殊的 Git-only 形式存储在索引中——这些是 Git 知道的文件。这就是快照的来源:当您或任何人 运行 git commit.

时,它就是 Git 索引中的任何内容

5这是一个奇怪的例子,因为 Git 处于 Git 调用的 detached HEAD 模式.此模式用于实现 git rebase,但它不是您通常使用的模式。通常 HEAD 会被 附加 。不过,恐怕我要跳过这里的大部分内容。


结论

作为要点列表:

  • Git 存储 提交 ,而不是文件。然后提交存储文件。这使得每个提交都可以说是一个 all-or-nothing 交易。

  • Git 发现 通过哈希 ID 提交:内部提交编号。

  • Git 使用 b运行ch names 找到 last (最最近)在一些提交链中提交。这样你通常不需要使用又大又丑的哈希 ID。

  • 通常,Git 通过 添加新提交 来工作。新的提交应该指向现有的提交,以便它们添加。如果他们指向后方,那么关于 b运行ch names 找到 last 提交的事情,以及 Git从那里向后工作,停止工作。

  • 提交中的文件仅可用作存档(以及差异化,以及我们可以对 frozen-for-all-time 文件执行的所有其他操作)。要处理/使用文件,您必须提取 提交。

  • 使用 git checkout 提取提交会填充 Git 的索引和您的 work-tree。您可以查看和处理/使用的文件在您的 work-tree 中,并且是 您的,而不是 Git 的.它们实际上 不在 存储库中:当您从复制出来的内容中更改它们时,Git 对此一无所知。

  • 事实上,您的 work-tree 文件不在 Git 中——它们就在旁边——这就是为什么你必须 git add; add 仅更新 提议的下一次提交 的事实是您必须 git commit.

    的原因

其他一切都建立在上面之上。