由于没有人能够解释 git 中涉及的对象,我无法自己解决我的问题(与其他源代码控制系统相反)。
我已经开始在git clone repository
创建的一些克隆存储库中工作(这就是为什么我在下面提到"未命名分支"):
1) 我执行了更改
2)我做了git add
和git commit
3)我决定在不打扰同事的情况下支持这一点。所以我做了git branch SomeUniqueName
git checkout SomeUniqueName
.现在我想知道如果我做一个git push
会发生什么.如果更改最终不是在指定的分支(SomeUniqueName)中,而是在未命名的分支中,我该如何更改?我已经在尝试使用另一个克隆的存储库并复制我的更改。但是在这种情况下,我不知道如何使两个存储库匹配相同的起点(在时间上)-其他开发人员可能已经更改了存储库,同时使我的更改不起作用。
有人声称,这个问题是另一个问题的重复。我无法与另一个问题联系起来,因为我已经不知道如何从 git 中提取更改列表。我怀疑这与提交列表有关,并且可以使用"git log"来完成(顺便说一句 - 在查看 git-log 的手册页时,我对大多数解释的参数感到困惑)。但是在做"git log"时,我会收到其他人执行的提交,他们无权访问我的工作目录。所以不知何故,我记得(当我需要内存时,在 copputer 上运行 SCCS 需要什么?)我在顶部执行的所有更改都是仅在我的工作目录中本地执行的更改。所以我想我会在执行工作目录备份后尝试git reset --hard HEAD~8; git checkout SomeUniqueName
。瞧,我所有的改变都消失了。因此,我必须解压缩刚刚制作的备份并手动复制我的更改。
好的,假设你擅长图论并且已经通读了 Git for Computer Scientists:
- 所有图形节点都由哈希 ID 标识。 (哈希目前是 SHA-1 在以类型加大小加 NUL 字节为前缀的对象内容上,除了正确性之外,这并不重要。
- 除了所谓的浅层克隆(我们将在此处忽略)之外,每个 Git 存储库都有每个可访问的提交以及该提交下的每个可访问对象。
- 通过从第二个引用名称数据库中获得分支名称、标记名称或任何其他外部来源的名称,可以访问提交。 分支名称必须指向提交对象。 (其他名称,尤其是标记名称,可以指向其他对象类型,但这对于我们在这里的目的并不重要。 分支名称是这里最有趣的情况,因为这就是我们构建提交并将其从一个存储库传输到另一个存储库的方式 - 但还有第二种名称,远程跟踪名称,这也是关键。
-
因此,对于一个简单的线性情况,我们可以像这样绘制提交图:
A <-B <-C <--master
外部名称
master
包含提交C
的哈希 ID。 此提交对象本身包含提交B
的哈希 ID ;B
包含A
的哈希 ID。A
没有传出弧(没有 Git 所说的父级),所以它是一个根提交,操作都在这里停止。 - 所有对象始终是只读的。无法到达的对象可能会被垃圾回收。
请注意,没有内部对象的 ID 为C
,因此C
是可访问的,因此链被保留的事实,只是因为外部名称refs/heads/master
(分支master
)而发生。
如果我们添加新的分支名称,例如dev
,我们会得到:
A--B--C <-- master, dev
(由于提交对象的只读性质,图中的内部箭头仍然向后,但这在文本中绘制太痛苦了,所以我不打扰)。 现在 Git 需要一种方法来知道在进行新提交时要调整哪个名称,因此它将标签HEAD
附加到某个分支名称。HEAD
只能附加到分支名称! 让我们把它画进去:
A--B--C <-- master, dev (HEAD)
为了进行新的提交,Git 将存储在索引中的所有 blob 哈希打包到一个新的树对象中,然后创建一个指向树的新提交对象,如上面的"Git for CSists"链接中所述。新提交将指向间接指向HEAD
提交的任何提交:
A--B--C
D
然后 Git 将覆盖HEAD
附加到的任何名称,因此名称为:
A--B--C <-- master
D <-- dev (HEAD)
推送和获取传输对象,然后设置名称
我们现在几乎拥有了解git push
和git fetch
所需的一切。 让我们先看一下push
,因为您更关心它。
您的 Git 将调用其他一些 Git 并交出一个哈希 ID,例如您的新提交D
的哈希 ID。 另一个 Git 还没有此 ID的名称,它只是检查它是否具有哈希 ID。 如果没有,它需要提交(可能还需要树和 blob 对象),所以它说"向我发送这些对象"。 你的 Git 将它们打包并发送过来。 他们将提交D
及其子对象放入他们的图中,但到目前为止,他们还没有这个对象的名称。
现在,您的 Git 会发送一个名称,例如refs/heads/dev
。 他们的 Git 现在查看他们是否可以设置此名称。 有两种情况:
他们没有
refs/heads/dev
分支:创建它是非常安全的,所以他们可能会这样做。 (您可以在接收方设置关于允许或拒绝什么的花哨规则,因此我们在这里只能说"可能"。他们确实有一个
refs/heads/dev
:他们将检查是否从现在拥有的任何哈希中更改它,指向提交D
,将使所有可访问的提交对象仍然可访问。这很容易做到:他们的dev
所指向的承诺现在是D
的祖先吗? 如果是这样,则推送是可以的。 否则,推送将作为非快进被拒绝。
使用git fetch
几乎是对称的,但不完全对称。 当您从其他存储库git fetch
时,默认情况下,您的 Git 会列出其所有分支名称和哈希 ID。 然后,您的 Git 会询问他们拥有而您没有的所有提交,以及他们拥有而您没有的所有历史记录。 但是,最后,您的 Git 不会创建或调整本地分支名称,而是设置远程跟踪名称,例如origin/master
和origin/dev
。
(从技术上讲,这些是refs/remotes/origin/master
,在单独的命名空间中形成分支名称,因此没有冲突的机会。 实际上,只要你不命名自己的分支origin/whatever
反正这不是问题。
最初最令人困惑的最后一点
如果您的存储库最初是通过克隆(内部执行git fetch
)某个现有存储库创建的,则从以下开始:
...--o--...--o <-- origin/master
o--...--o <-- origin/dev
等等。 这些远程跟踪名称使所有提交都可访问,因此它们不会全部被垃圾回收。 但是,您当地的分支机构master
从何而来?
诀窍是这样的:git checkout
将通过解构git fetch
对其他 Git 的分支名称所做的重命名来创建新的本地分支名称。 我们知道我们的origin/master
一定是他们的master
,所以git checkout master
,当我们没有主人时,会寻找origin/master
。 如果存在,Git 只是在分支名称空间中创建了一个新标签,为我们提供了:
...--o--...--o <-- master (HEAD), origin/master
o--...--o <-- origin/dev
要创建自己的新标签,请使用git branch <name> <hash-id-or-other-commit-specifier>
,这将创建指向给定提交的refs/heads/<name>
名称。 您还可以使用git checkout -b <name> <specifier>
,它创建名称并立即将新名称git checkout
以附加到其HEAD
。
清理
还有一点需要知道,通常不会立即涵盖,因为这个图表的东西让每个人都不知所措。 这是refspec语法。git push
和git fetch
都使用refspecs,尽管它们对待它们的方式不同。
refspec通常只是一对用冒号分隔的引用名称。 左侧的名称是源名称,右侧的名称是目标名称。 还有一个可选的前导加号,表示"强制":即使更新不是快进,也要更新名称。
使用git fetch origin
时,默认的 refspec 为:
+refs/heads/*:refs/remotes/origin/*
这意味着您的 Git 与其他 Git 的分支名称匹配 (refs/heads/*
),但将所有这些名称转换为您自己的远程跟踪名称(在refs/remotes/
下,此外在origin/
下 - 这为其他远程留出了空间)。
如果在git fetch
命令中省略目标名称,Git 不会写入任何名称。 这使得获取的提交受到垃圾回收,但存在延迟,因为 Git 首先将哈希 ID 记录到.git/FETCH_HEAD
中,您可以在其中检索它们以及它们充当临时保留器。FETCH_HEAD
内容会被下一次提取覆盖,因此它们不如名称到 ID 数据库中的真实姓名。
但是,对于git push
,默认值通常是将分支名称推送到另一个 Git 上的相同名称。 也就是说,git push origin master
真正意味着git push origin master:master
(当 Git 发现master
是分支名称时,它会填写refs/heads/
部分)。 有关 Git 如何查找这些名称的更多信息,请参阅 gitrevisions 文档。
当您在没有其他参数的情况下键入git push
时,更改将转到本地分支跟踪的特定远程分支。如果没有远程跟踪分支,那么什么都不会发生,git 会为此抱怨。
远程跟踪只是"此分支将与远程分支保持同步origin/xzy
"的另一种说法。当您使用表单git checkout remotebranchname
签出远程分支时,Git 将自动设置此同步。但是,如果您只是创建一个本地分支(就像您所做的那样),git 不会自动设置任何远程跟踪分支。
因此,在这种背景下,让我们考虑一下您的示例。您已使用 git将自动设置为跟踪origin/UnnamedBranch
git checkout UnnamedBranch
签出了远程分支。然后,您使用git branch SomeUniqueName
在UnnamedBranch
上创建新分支。Git不会自动设置此分支来跟踪任何远程分支。
然后回答你的问题。此时,当你从SomeUniqueName
git push
时,什么都不会发生,git 会向你抱怨。
现在,如果您出于某种原因设法将更改从本地SomeUniqueName
推送到origin/UnnamedBranch
(这是可能的,请参阅其他形式的 git push),那么您可以恢复它。
git checkout UnnamedBranch
git push -f
这将重置远程分支以匹配您的本地版本UnnamedBranch
。请记住,这将影响在您的错误和修复它之间从UnnamedBranch
中提取更新的任何人的分支历史记录。只有仔细地与同事协商才能做到这一点。