我不清楚您最初是如何创建本地存储库的,但我怀疑您是这样做的:
mkdir new-repo
cd new-repo
git init
然后创建一些文件并git add
编辑它们并提交结果。这将为您留下一个新的存储库,其中只有一个提交,并且没有origin
远程,因此您将不得不运行:
git remote add origin ssh://git@github.com/your/repo.git
或类似的。
我不清楚你是如何your/repo.git
在 GitHub 上创建的(或者它的路径可能是什么),但我怀疑你使用了“创建一个包含 README 文件的新存储库”按钮。
如果所有这些“嫌疑人”都是正确的,这就解释了一切。您的初始错误:
fatal: 'origin/master' is not a commit and ...
发生是因为您尚未将 Git 连接到 GitHub 的 Git,该 Git 包含您在 GitHub 上创建的存储库。
然后你运行git fetch origin
,这让你的 Git 连接到他们的 Git 并获得他们的(单个)提交,其中包含一个 README 文件。您的 Git 将他们的提交复制到您的存储库中。然后,您的 Git 创建了您自己的origin/master
远程跟踪名称,以记住他们(单个)提交的哈希 ID。
您现在有两个初始(从无到有)提交,Git 称之为根提交。如果我们绘制您的存储库的图表,它可能如下所示:
A <-- master
B <-- origin/master
你的第一个提交,在这里表示为A
——无论它的实际哈希 ID 可能是什么——保存你提交的文件。您的分支名称master
标识了这一提交。
他们的第一次提交,在这里表示为B
,保存了他们提交的文件。您的远程跟踪名称origin/master
标识了这一提交。
你现在要求你的 Git 创建一个master
指向 commit的新分支名称B
,使用你的origin/master
,通过失败的命令:
git checkout --track origin/master
fatal: A branch named 'master' already exists.
错误消息的原因很清楚:您已经有一个分支名称master
,指向现有的提交A
;无法创建master
指向现有 commit的新但相同的分支名称B
。
该怎么办
从这里开始,您有多种选择。它们主要涉及创建一个新的提交C
。
您可以使用git merge
. 为此,您将需要该--allow-unrelated-histories
选项,除非您的 Git 版本太旧而没有该选项。一个古老的 Git 只是假设--allow-unrelated-histories
应该总是被暗示。
如果他们的唯一提交B
是自动生成的,那么像这样合并历史有点愚蠢。完全放弃他们的承诺会更有意义。从他们的提交中获取您认为有用的任何内容,B
并进行自己的新常规提交C
:
A <-C <-- master
B <-- origin/master
您的新提交C
将仅指向您现有的A
. 您现在可以强制他们放弃他们的提交B
,向他们发送您的新的C
.
或者,您可以继续合并两个提交:
A
\
C <-- master
/
B <-- origin/master
您现在可以向他们发送提交C
并要求他们master
记住C
,它同时记住A
和B
。
要向他们发送新的提交C
(这将带来它A
,并指向A
,并且可能还指向B
取决于您的制作方式C
),请使用git push origin master
. 这会发送C
(and A
) 并以礼貌的请求结束,如果他们愿意,请让他们的名字master
指向C
。如果您故意抛出 B
,而不进行合并,则需要将礼貌请求升级为命令:git push --force origin master
。
当这一切都完成后,他们 master
还将指向(他们现在共享的副本)提交C
。您的 Git 将更新您自己的 Gitorigin/master
以记住他们的master
remembers C
,并且您都会同意这C
是您和他们的分支的最后一次提交,这两个分支都被命名为master
.
同时,您自己的master
不会origin/master
设置为它的upstream。没有理由必须这样做,但如果您愿意,请参阅为什么我需要一直执行 `--set-upstream`?—您可以使用git branch --set-upstream-to
,或者您可以将您的选项git push
与以下-u
选项结合使用:
git push -u origin master
或者:
git push --force -u origin master
(--force
仅当您告诉他们时才需要该选项: *forget about your commit B
,它没用,改用 my ——如果您的点返回C
,则不需要)。C
B
除了合并或覆盖之外,您还有更多选项,它们也会创建一个新的提交C
。你可以:
A
在他们的B
, using ,git rebase -i --root
或
- 将您复制到位于其顶部
A
的新提交,在未命名的新分支上。C
B
master
rebase 通过复制来工作,然后放弃你的原件A
,转而使用这个新的C
:
A [abandoned / lost - will eventually be garbage collected]
B <-- origin/master
\
C <-- master
或者:
A <-- master
B <-- origin/master
\
C <-- newbranch
然后,您可以重命名您的master
,例如,old-master
并重命名newbranch
为master
:
A <-- old-master
B <-- origin/master
\
C <-- master
最后,您现在git push -u origin master
可以向他们发送 commit C
(这一次A
根本不发送)并要求他们将其master
指向新的 commit C
。
如何理解这一切
回过头来重新检查所有早期的图表,并考虑这样的事情:
- 对 Git 来说重要的是commits,它永远不会改变(但可以被放弃并最终完全抛弃)。
- 名称,如
master
and origin/master
,仅用于查找某个提交序列中的最后一个提交。
- The hash ID of any commit—you'll see these big ugly hash IDs in
git log
output—is how Git finds a commit. By storing the last hash ID in each name, Git can find all the last ones. Each commit itself stores the big ugly hash ID of its immediate predecessor: commit C
points back to commit A
, or commit B
, or both, depending on how we make it.
It's the commits that actually matter. Git gives you the names, because names mean something to humans (who can't remember big ugly hash IDs), and then uses the names to find the commits. Those commits that Git can find by name, store hash IDs of earlier commits. Those earlier commits store hash IDs of even-earlier commits, and so on. The git log
command just starts at the current end and works backwards, showing you commits as it goes.