为什么我不能合并这些远程分支?
Why can't I merge these remote branches?
我正在学习 git 并尝试将我的 origin/dev 分支合并到我的 origin/master 分支中,因为 origin/dev 有最新的更新:
我正在输入:
git pull origin dev:temp
git push origin temp:master
但我收到这条消息
To bitbucket.org:fakename/app-ios.git
! [rejected] temp -> master (non-fast-forward)
error: failed to push some refs to 'git@bitbucket.org:fakename/app-ios.git'
hint: Updates were rejected because a pushed branch tip is behind its remote
hint: counterpart. Check out this branch and integrate the remote changes
hint: (e.g. 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
这是我当前的图表(此帖子的提交代码是假的)
* 23f6a52 (origin/master, origin/HEAD) finalizing merge
|\
| * gab012e (HEAD -> dev, origin/dev, temp) added .gitignore to dev branch
我希望 origin/master 拥有 origin/dev 的数据,但不确定为什么它没有合并?
我已经备份了所有内容...
谢谢!
git checkout temp
git pull -r origin master
git push temp
应该可以解决问题。
TL;DR
您在评论中提到了 xcuserdatad/UserInterfaceState.xcuserstate
的问题。不应提交此文件。您将要删除该文件并进行新的提交以忽略它。您是否要返回并用 new-and-improved 提交替换所有 existing 提交 also omit 文件已启动给你。这个过程可能会很痛苦,但不这样做有其自身的痛苦。
(注意:我不使用 Xcode 并且我不确定如果 Git 从您的 中删除文件会有多大问题工作树,虽然我怀疑 Xcode 只会 re-create 它,即,不是那么糟糕。我将“不要提交它”基于其他答案,例如 this one。)
长
这个:
* 23f6a52 (origin/master, origin/HEAD) finalizing merge
|\
| * gab012e (HEAD -> dev, origin/dev, temp) added .gitignore to dev branch
表明已经有一个合并提交——即 23f6a52
——合并了提交 gab012e
(那个似乎是假哈希,因为真正的哈希 ID 表示为 hexadecimal数字)与其他一些提交,我们看不到其哈希 ID,因为它位于屏幕底部。我在猜测,但也许这就是您想要进行的合并,已经进行了。
您自己的存储库通过名称 origin/master
调用提交 23f6a52
,这是您的 Git 对 origin
的 master
分支的记忆。假设这是最新的,那肯定可以解释这里的错误:
To bitbucket.org:fakename/app-ios.git
! [rejected] temp -> master (non-fast-forward)
你的 git push
打电话给 bitbucket 的 Git 并询问他们是否知道提交 gab012e
。他们会有,所以他们会说 我已经有了那个 并且 git push
的第一部分已经完成。其余部分包括一个礼貌的请求:请,如果可以,请将您的 master
设置为指向提交哈希 ID gab012e
。
他们说:但那不行。如果我这样做,我将失去我的承诺,23f6a52
,在我的 master
. 的末尾(他们并没有字面上这么说,他们只是说 不,它不是 fast-forward 所以它不正常 但这就是 的意思。)
I want origin/master
to have the data of origin/dev
为此,您可能必须删除 此合并。
but not sure why it isn't merging?
它(apparently)已经合并,通过提交23f6a52
。
(请注意 23f6a52
中的快照不需要 匹配 gab012e
中的快照 ,因为合并并不意味着“相同”。它不是从您在此处显示的内容可以判断出这两个快照中的内容。也无法判断这与您自己的 master
有何关系,如果有的话。)
不该做什么
I am learning git and trying to merge my origin/dev branch into my origin/master branch ...
git pull origin dev:temp
运行使用此命令可能不是一个好主意,尤其是如果您仍在学习 Git。这是一个非常复杂的组合。
不是每个人都同意我的看法,但我建议新手避免git pull
。它所做的是 运行 两个 Git 命令:
首先,git pull
运行s git fetch
。几乎你传递给 git pull
的每个参数都会直接传递给 git fetch
,在这种情况下,git pull origin dev:temp
运行s git fetch origin dev:temp
。这是一个复杂的命令,因为它使用了一个完整的 refspec,dev:temp
,指示 Git 创建或 fast-forward 你自己的(本地)分支 temp
基于获取 origin
的 dev
.
的结果
然后,假设 git fetch
成功,git pull
继续 运行 第二个命令。第二条命令的选择本身就有些复杂。如果您从未配置过任何其他内容,则第二个命令(通常)是 git merge
。如果你配置了 pull to 运行 rebase,它通常是 git rebase
。您可以在 运行 git pull
时覆盖这些(尽管您没有,所以它可能使用 git merge
,最终可能成为 no-op)。
这些不同的步骤中的每一个都可能有些复杂。在某些情况下很难预测结果,您 观察 的结果可能具有误导性。如果您还是初学者,我认为最好将各个命令分开运行,然后观察它们的作用。这使您能够更好地理解您可能需要哪个第二个命令(如果有的话!),它们的作用,以及何时使用每个.
做什么
从 git fetch origin
开始(或简称 git fetch
,默认为 origin
)。1 这个:
- 使用短名称
origin
查找 URL;
- 使用生成的 URL 调用其他一些 Git;
- 询问其他人 Git 他们 有哪些分支(和其他名称),以及这些名称对应的提交;
- 带来他们拥有的、您没有的、您的 Git 想要的任何提交——通常是“所有这些”;2 最后
- 更新你的remote-tracking名字,比如
origin/master
和origin/dev
,基于他们[=491]的提交=] 名称标识。
(有限的获取,如 git fetch origin dev
,仅更新 origin/dev
,因为它不会费心从他们的 master
获取任何新提交。
现在你有了他们的提交,看看你有没有他们没有的提交,你希望他们现在拥有。例如,如果你的 dev
严格领先于他们的 dev
——比如说,你有两个他们没有的新提交,而他们没有你没有的提交——你想要他们保存这两个提交,以防您的笔记本电脑在您快速休息时突然起火,3 您可以使用 git push
发送这些提交。请参阅下面关于 git push
的注释。如果您愿意等待,或者您的 Git 既领先 又落后 他们的 Git,请参阅下面的合并说明。
(您已经知道如何使用 git log --all --decorate --oneline --graph
或类似的方式查看哪些提交。)
git fetch
的真正好处是你可以随时做。它所做的就是获得任何new 从其他 Git 提交,然后更新您自己的 remote-tracking (origin/*
) 名称。它不会影响您正在做的任何事情——好吧,只要您不使用 dev:temp
样式语法即可。
1实际默认是当前分支的远程,如果它有的话。如果不是,则默认为文字字符串 origin
。如果您只有一个遥控器,名为 origin
,origin
将是正确的,在更简单的设置中,您将只有一个遥控器。
2这里的例外是 single-branch 克隆和其他一些特殊情况。另外,如果你使用 git fetch <em>remote somebranch</em>
,例如,你的 Git 只会麻烦带来新的提交它需要更新 <em>remote</em>/<em>somebranch</em>
。这意味着稍后的 git fetch
将需要带来此 git fetch
跳过的任何提交。如果您的 Internet 连接速度较慢,有时这是值得的,但通常最好一次将所有内容都带过来,因为这样压缩效果更好:如果 git fetch xyz br1
需要 10 分钟,而 git fetch xyz br2
也需要 10分钟,git fetch xyz
可能 不 需要 20 分钟,而更像是 12 到 15 分钟。这些数字是当场编造的(比如所有统计数据的 37.12%),但总的想法是正确的:如果你分期付款,随着时间的推移,你最终支付的费用会比你只预先支付全部费用要多。
3参见,例如,https://abcnews.go.com/WNT/video/charging-laptop-bursts-flames-moment-caught-home-camera-45312573
如果你完全落后,考虑 fast-forward 合并
正如您可能已经收集到的那样:
每个 Git 提交都有一个哈希 ID。此哈希 ID 唯一标识一个特定的提交。此 ID 在 每个 Git 存储库中都是唯一的,并且 Git 保证每个 Git 都会同意 that commit 获取 that ID,通过将哈希 ID 计算为提交内容的加密校验和。
每个 Git 提交都有 Git 知道的所有文件的完整快照。
每个提交也有一些元数据,即关于提交的信息。这包括提交人(“作者”)的姓名和电子邮件地址,以及 date-and-time-stamp。它包括其他信息,例如日志消息。而且——对于 Git 至关重要——它包括一个 previous 或 parent 提交的列表。 parent 链接是您在 git log --graph
输出中看到的提交图的形式。
如果你自己的 dev
落后于 origin/dev
而不是 领先于 origin/dev
并且没有分歧 - 那表示有人在 origin
上向 dev
发送了新的提交。如果您希望这些提交出现在您自己的 dev
中,现在是时候了:
git checkout dev
git merge origin/dev
或更简单的:
git checkout dev && git merge
第二个版本假定 origin/dev
设置为 dev
的 upstream:git merge
命令将默认使用 upstream , 如果你没有在这里指定一些东西。我实际上喜欢在这里添加 --ff-only
标志,因为我只希望合并在 可以 作为 fast-forward 完成时成功。 (我有一个别名,git mff
,那个 运行s git merge --ff-only
,这就是我使用的。)这让我有点粗心/漫不经心,只是 运行 命令没有首先检查它是否 会 执行 fast-forward.
如果你同时领先和落后,你通常会想要合并或变基
如果您有他们没有的新提交,和他们有您没有的新提交,您可能需要将您的工作与他们的工作结合起来。这是 git pull
运行s 出现的第二个命令:
git merge
将进行完整的 three-way 合并(不仅仅是 fast-forward)。
git rebase
将获取您现有的提交并将它们复制到 new-and-supposedly-improved 提交。如果复制全部有效,则 rebase 将放弃旧的(和糟糕的?)提交对于新的(和改进的?)。
变基比合并复杂得多,因为它在 commit-by-commit 基础上工作。它使用 git cherry-pick
或相当接近等效的东西,4 用于 each 需要复制的提交。 5 这些 cherry-pick 操作中的每一个都可以在合并冲突的中间停止,因此如果 git rebase
需要复制 5 次提交,则有 5 次机会停止。合并只有一次机会因合并冲突而在中间停止。 (另一方面,一个冲突可能很大,而 5 个冲突较小。)
这里是合并还是变基由您选择。不过,我认为关于 git pull
的其中一件事是你必须 做出 这个选择 在 你看到是否合并将是 fast-forward 或真正的合并。最近,Git 增加了使 git pull
只进行合并的选项 如果 是 fast-forward 合并。我声称这是正确的默认值(但它不是当前的默认值,所以我仍然建议避免 git pull
)。
4现代 Git 使用 cherry-pick 但内置了变基。 Git 的旧版本有时使用 cherry-pick,有时使用 git am
,rebase 是一个 shell 脚本。您可以通过添加 -m
and/or -s
参数或进行交互式变基,使 Git 的那些版本使用变基; interactive rebase literally 运行s git cherry-pick
.
5复制的提交集是......复杂的,尤其是在现代Git中带有--fork-point
选项。然而,在正常的简单情况下,这是您拥有的每个提交,而他们没有。
Full-blown合并
要进行 full-blown three-way 合并,我建议您使用(本地)分支,例如 dev
或 master
(或 main
如果你从 master
切换到 main
):
运行 git checkout
或 git switch
(在 Git 2.23 或更高版本中)以及要用于新合并提交的分支名称.这让你“在”那个分支上,因为 git status
现在会说 on branch dev
或其他什么。
运行 git 合并 <em>name</em>
or git merge <em>commit-hash-ID</em>
开始合并过程。在此处使用名称,如 git checkout master && git merge dev
,设置默认提交消息以使用分支名称(例如,merge branch dev
)。除此之外,哈希 ID 与名称一样好。哈希 ID 很难输入,因此您可能希望使用该名称。您应该意识到 Git 在这里 不需要 名称。它需要一个哈希ID,它只是将名称转换为一个哈希ID来进行实际的合并。
合并本身通过找到一个 shared 提交来工作。假设我们有一个这样的提交图:
I--J <-- master (HEAD)
/
...--G--H
\
K--L <-- dev
表示 master
上有两个不在 dev
上的提交,反之亦然。在本例中,两个 分支上的最佳提交是提交H
。 (请记住,后来的提交向右,较早的提交向左,并且 Git 从两个 end-point 提交开始并向后工作。因此提交 H
是第一个 shared 提交,在两个分支上,因此是 best 共享提交。)
合并过程现在通过将提交 H
中的快照与两个选定提交中的两个快照中的每一个进行比较来工作:
git diff --find-renames <hash-of-H> <hash-of-J> # what "we" did on master
git diff --find-renames <hash-of-H> <hash-of-L> # what "they" did on dev
Merge 通过组合这两组更改来工作,将组合的更改应用到提交中的快照 H
——这在添加“他们的”的同时保留“我们的”更改——并根据结果进行新的提交.或者,如果合并出错,合并会在中间停止,在您的工作树和 Git 的索引中留下一团糟,您的工作变成修复混乱并继续合并。
假设一切顺利,Git 进行新的 merge 提交,这是一个包含两个 parent 的提交。6 我们可以这样画:
I--J
/ \
...--G--H M <-- master (HEAD)
\ /
K--L <-- dev
请注意,如果提交 J
是 origin/master
的提示(现在仍然是),您现在可以 git push origin master
将提交 M
发送到 origin
.此提交将严格领先于他们自己的 master
——就他们而言,快进——因此他们很可能会接受此推送。
6从技术上讲,合并提交是具有两个 或更多 parent 的任何提交,但您无能为力处理超过两个你不能用额外合并做的事情。事实上,这有点相反:有些事情你 不能 使用 so-called 章鱼合并,有 3 个或更多 parents,您 可以 进行多个 two-parent 合并。这是它们的主要价值:如果您遇到这种情况,您可能会认为它是这些更简单的合并之一。一个真正的 Git 绝地大师(或西斯尊主)可能会做出违反这些期望的事情,虽然.
git push
一旦你有一个本地分支,与 origin
(或其他一些远程 Git 上的相同分支名称相比,你可以 运行 git push
将新提交发送到他们的 Git。与 git fetch
一样,这首先调用他们的 Git——但这次你是发送者,而不是接收者,并且你的 Git 列出了你的某些提交的哈希 ID选择.
在上面的示例中,我们刚刚进行了新提交 M
,因此这是我们唯一拥有而他们没有的提交,当我们 运行 git push origin master
—请注意,我们应该在这里使用 所有四个词,并且不要遗漏 origin
或 master
7——我们的 Git 将提供他们的 Git 新提交 M
,他们将接受,然后提供 M
的两个 parent,他们将拒绝 不用了,我已经有了。然后我们将打包提交 M
,包括它的文件,并将包发送过来。然后我们的Git会礼貌的向他们的Git请教,请,如果可以的话,把你的master
设置为_____,填在空白处使用提交的哈希 ID M
.
他们现在将检查此操作对他们来说是否是 fast-forward。 如果是,他们将接受请求,8 现在他们的 master
将提交 M
。我们的也会,因为我们在制作时将它放在 master
上。
7如果你 运行 git push
没有参数,Git 以同样的方式填写 origin
部分不带参数为 git fetch
做。然后它根据 push.default
填写 分支名称 部分。 Git版本2(所有版本2版本)中的push.default
设置为simple
,即使用当前分支。但是,Git 版本 1 中的默认设置是 matching
。这有时是您想要的,但对于新手来说很少如此,这就是为什么他们在 Git 2.x.
中更改了它
我自己实际上倾向于使用 git push origin HEAD
。 HEAD
转换为 当前分支 。在 GitHub 上进行 pull requests 的特殊情况下,我有时会使用 git push origin HEAD:<em>new branch name 我当场编的</em>
。然后我重命名本地分支,在输入 git push
命令时弄清楚了我 想要 为本地分支使用什么名称,但没有更早。 :-)
8根据站点的不同,存储库所有者通常可以添加各种规则来保护特定的分支名称,以便只有列入白名单的用户才能直接向他们推送。在这种情况下,您可能需要将自己加入白名单。
删除并忽略不应该提交的文件
重要的是要记住,在 Git 中,提交 是 Git 知道的所有文件的快照 在您(或任何人)制作该快照时。一旦创建,该快照将无法更改。事实上,任何现有提交都无法更改。这是由于 Git 使用的散列方案。
由于每个提交都以计算机其余部分无法使用的形式存储,因此每个提交都必须提取到普通的日常文件中。当 Git 执行此操作时,它会获取 提交的 文件,将它们扩展为可用形式,并将它们粘贴到工作区中。 Git 将此工作区称为您的 工作树 。
由于您的工作树是一个充满文件的普通目录(或者如果您喜欢这些术语,则为一组“文件和文件夹”),您现在可以在此处创建新 文件。 Git 不会知道他们。 Git 它知道的文件列表保存在 Git 称为它的 index.
中
索引不只是 列出 文件。它实际上以 pre-Git-ified 形式保存了其中的 快照 :压缩和 de-duplicated,准备进入 next 提交.当您使用 git add
时,您是在告诉 Git:将此文件复制到您的索引中。 如果之前有一个文件,它会被踢出(即,不是 overwritten 因为它们是存档形式),而是新文件进入。如果文件 之前 Git 的索引中没有 ,那么现在它连同它的名称。
当您 运行 git commit
时,Git 保存 其索引 中的那些文件,格式为 然后 在索引中。这成为新的快照。因此 Git 的索引始终是您建议的 next 快照。9 如果您避免将文件 添加到 Git 的索引,它不会进入提交。
每次更改文件时都手动添加每个文件,这既烦人又痛苦。于是就有了一个en-masse add everything操作(其实是多次这样的操作)。但是如果你这样做,这会添加你可能永远不需要的文件提交。
为了防止那个,Git有.gitignore
。这个文件的名字有点不对。它不会忽略 已经在Git 的索引中的文件。它主要做的事情是抑制来自git status
的投诉,关于未跟踪文件Git提醒您添加。该文件也许应该命名为 .git-do-not-complain-about-these-untracked-files
。但是,它也会阻止那些 en-masse add a bunch of files 操作添加那些未跟踪的文件。所以更好的名字是.git-do-not-complain-about-these-untracked-files-and-do-not-auto-add-them-during-an-en-masse-add-operation
。这仍然不完全准确,但比 .gitignore
更接近。但这也很荒谬,所以.gitignore
它是。
这里的关键是 在 .gitignore
中列出一个文件不会导致它被忽略 。提交 中 文件的缺失导致它被忽略。当文件不在 Git 的索引中时,它不会在您的 next 提交中。但是如果它在某个 existing 提交中,并且您检查了该提交,Git 会将提交的文件 复制到 它的索引,然后将该文件(并将其展开为有用的形式)复制到您的工作树。
要获取已经在 Git 的索引 out 的 Git 的索引中的文件,我们使用 git rm
:
git rm file-that-should-not-have-been-committed
这会删除 索引副本 和 工作树副本。为避免立即删除工作树副本,我们可以使用:
git rm --cached file-that-should-not-have-been-committed
仅删除 index 副本,单独留下工作树副本。然后我们可以进行新的提交,新的提交将缺少文件。
但是:没有 existing 提交可以更改。因此 old 提交将 拥有 文件。旧提交和新提交之间的区别会说删除文件。对旧提交的任何使用都会为您提供文件的旧副本。任何切换从旧提交,到一个缺少文件的新提交,删除文件.
这就是为什么删除一开始就不应该提交的文件会很痛苦的原因。
可以获取某个存储库中的所有提交并将其中的 所有 复制到缺少某些文件的 new-and-improved 提交,但结果是new repository 需要丢弃旧存储库的所有副本。你 可以 这样做,尤其是在某些项目开始的时候,如果你知道每个人都有旧的提交并且可以说服他们全部切换。
9冲突合并中出现异常。在这里,Git 的索引被扩展了,您实际上 不能 从中创建快照,直到您通过解决冲突将其缩小回其正常形式。
我正在学习 git 并尝试将我的 origin/dev 分支合并到我的 origin/master 分支中,因为 origin/dev 有最新的更新:
我正在输入:
git pull origin dev:temp
git push origin temp:master
但我收到这条消息
To bitbucket.org:fakename/app-ios.git
! [rejected] temp -> master (non-fast-forward)
error: failed to push some refs to 'git@bitbucket.org:fakename/app-ios.git'
hint: Updates were rejected because a pushed branch tip is behind its remote
hint: counterpart. Check out this branch and integrate the remote changes
hint: (e.g. 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
这是我当前的图表(此帖子的提交代码是假的)
* 23f6a52 (origin/master, origin/HEAD) finalizing merge
|\
| * gab012e (HEAD -> dev, origin/dev, temp) added .gitignore to dev branch
我希望 origin/master 拥有 origin/dev 的数据,但不确定为什么它没有合并?
我已经备份了所有内容...
谢谢!
git checkout temp
git pull -r origin master
git push temp
应该可以解决问题。
TL;DR
您在评论中提到了 xcuserdatad/UserInterfaceState.xcuserstate
的问题。不应提交此文件。您将要删除该文件并进行新的提交以忽略它。您是否要返回并用 new-and-improved 提交替换所有 existing 提交 also omit 文件已启动给你。这个过程可能会很痛苦,但不这样做有其自身的痛苦。
(注意:我不使用 Xcode 并且我不确定如果 Git 从您的 中删除文件会有多大问题工作树,虽然我怀疑 Xcode 只会 re-create 它,即,不是那么糟糕。我将“不要提交它”基于其他答案,例如 this one。)
长
这个:
* 23f6a52 (origin/master, origin/HEAD) finalizing merge |\ | * gab012e (HEAD -> dev, origin/dev, temp) added .gitignore to dev branch
表明已经有一个合并提交——即 23f6a52
——合并了提交 gab012e
(那个似乎是假哈希,因为真正的哈希 ID 表示为 hexadecimal数字)与其他一些提交,我们看不到其哈希 ID,因为它位于屏幕底部。我在猜测,但也许这就是您想要进行的合并,已经进行了。
您自己的存储库通过名称 origin/master
调用提交 23f6a52
,这是您的 Git 对 origin
的 master
分支的记忆。假设这是最新的,那肯定可以解释这里的错误:
To bitbucket.org:fakename/app-ios.git ! [rejected] temp -> master (non-fast-forward)
你的 git push
打电话给 bitbucket 的 Git 并询问他们是否知道提交 gab012e
。他们会有,所以他们会说 我已经有了那个 并且 git push
的第一部分已经完成。其余部分包括一个礼貌的请求:请,如果可以,请将您的 master
设置为指向提交哈希 ID gab012e
。
他们说:但那不行。如果我这样做,我将失去我的承诺,23f6a52
,在我的 master
. 的末尾(他们并没有字面上这么说,他们只是说 不,它不是 fast-forward 所以它不正常 但这就是 的意思。)
I want
origin/master
to have the data oforigin/dev
为此,您可能必须删除 此合并。
but not sure why it isn't merging?
它(apparently)已经合并,通过提交23f6a52
。
(请注意 23f6a52
中的快照不需要 匹配 gab012e
中的快照 ,因为合并并不意味着“相同”。它不是从您在此处显示的内容可以判断出这两个快照中的内容。也无法判断这与您自己的 master
有何关系,如果有的话。)
不该做什么
I am learning git and trying to merge my origin/dev branch into my origin/master branch ...
git pull origin dev:temp
运行使用此命令可能不是一个好主意,尤其是如果您仍在学习 Git。这是一个非常复杂的组合。
不是每个人都同意我的看法,但我建议新手避免git pull
。它所做的是 运行 两个 Git 命令:
首先,
的结果git pull
运行sgit fetch
。几乎你传递给git pull
的每个参数都会直接传递给git fetch
,在这种情况下,git pull origin dev:temp
运行sgit fetch origin dev:temp
。这是一个复杂的命令,因为它使用了一个完整的 refspec,dev:temp
,指示 Git 创建或 fast-forward 你自己的(本地)分支temp
基于获取origin
的dev
.然后,假设
git fetch
成功,git pull
继续 运行 第二个命令。第二条命令的选择本身就有些复杂。如果您从未配置过任何其他内容,则第二个命令(通常)是git merge
。如果你配置了 pull to 运行 rebase,它通常是git rebase
。您可以在 运行git pull
时覆盖这些(尽管您没有,所以它可能使用git merge
,最终可能成为 no-op)。
这些不同的步骤中的每一个都可能有些复杂。在某些情况下很难预测结果,您 观察 的结果可能具有误导性。如果您还是初学者,我认为最好将各个命令分开运行,然后观察它们的作用。这使您能够更好地理解您可能需要哪个第二个命令(如果有的话!),它们的作用,以及何时使用每个.
做什么
从 git fetch origin
开始(或简称 git fetch
,默认为 origin
)。1 这个:
- 使用短名称
origin
查找 URL; - 使用生成的 URL 调用其他一些 Git;
- 询问其他人 Git 他们 有哪些分支(和其他名称),以及这些名称对应的提交;
- 带来他们拥有的、您没有的、您的 Git 想要的任何提交——通常是“所有这些”;2 最后
- 更新你的remote-tracking名字,比如
origin/master
和origin/dev
,基于他们[=491]的提交=] 名称标识。
(有限的获取,如 git fetch origin dev
,仅更新 origin/dev
,因为它不会费心从他们的 master
获取任何新提交。
现在你有了他们的提交,看看你有没有他们没有的提交,你希望他们现在拥有。例如,如果你的 dev
严格领先于他们的 dev
——比如说,你有两个他们没有的新提交,而他们没有你没有的提交——你想要他们保存这两个提交,以防您的笔记本电脑在您快速休息时突然起火,3 您可以使用 git push
发送这些提交。请参阅下面关于 git push
的注释。如果您愿意等待,或者您的 Git 既领先 又落后 他们的 Git,请参阅下面的合并说明。
(您已经知道如何使用 git log --all --decorate --oneline --graph
或类似的方式查看哪些提交。)
git fetch
的真正好处是你可以随时做。它所做的就是获得任何new 从其他 Git 提交,然后更新您自己的 remote-tracking (origin/*
) 名称。它不会影响您正在做的任何事情——好吧,只要您不使用 dev:temp
样式语法即可。
1实际默认是当前分支的远程,如果它有的话。如果不是,则默认为文字字符串 origin
。如果您只有一个遥控器,名为 origin
,origin
将是正确的,在更简单的设置中,您将只有一个遥控器。
2这里的例外是 single-branch 克隆和其他一些特殊情况。另外,如果你使用 git fetch <em>remote somebranch</em>
,例如,你的 Git 只会麻烦带来新的提交它需要更新 <em>remote</em>/<em>somebranch</em>
。这意味着稍后的 git fetch
将需要带来此 git fetch
跳过的任何提交。如果您的 Internet 连接速度较慢,有时这是值得的,但通常最好一次将所有内容都带过来,因为这样压缩效果更好:如果 git fetch xyz br1
需要 10 分钟,而 git fetch xyz br2
也需要 10分钟,git fetch xyz
可能 不 需要 20 分钟,而更像是 12 到 15 分钟。这些数字是当场编造的(比如所有统计数据的 37.12%),但总的想法是正确的:如果你分期付款,随着时间的推移,你最终支付的费用会比你只预先支付全部费用要多。
3参见,例如,https://abcnews.go.com/WNT/video/charging-laptop-bursts-flames-moment-caught-home-camera-45312573
如果你完全落后,考虑 fast-forward 合并
正如您可能已经收集到的那样:
每个 Git 提交都有一个哈希 ID。此哈希 ID 唯一标识一个特定的提交。此 ID 在 每个 Git 存储库中都是唯一的,并且 Git 保证每个 Git 都会同意 that commit 获取 that ID,通过将哈希 ID 计算为提交内容的加密校验和。
每个 Git 提交都有 Git 知道的所有文件的完整快照。
每个提交也有一些元数据,即关于提交的信息。这包括提交人(“作者”)的姓名和电子邮件地址,以及 date-and-time-stamp。它包括其他信息,例如日志消息。而且——对于 Git 至关重要——它包括一个 previous 或 parent 提交的列表。 parent 链接是您在
git log --graph
输出中看到的提交图的形式。
如果你自己的 dev
落后于 origin/dev
而不是 领先于 origin/dev
并且没有分歧 - 那表示有人在 origin
上向 dev
发送了新的提交。如果您希望这些提交出现在您自己的 dev
中,现在是时候了:
git checkout dev
git merge origin/dev
或更简单的:
git checkout dev && git merge
第二个版本假定 origin/dev
设置为 dev
的 upstream:git merge
命令将默认使用 upstream , 如果你没有在这里指定一些东西。我实际上喜欢在这里添加 --ff-only
标志,因为我只希望合并在 可以 作为 fast-forward 完成时成功。 (我有一个别名,git mff
,那个 运行s git merge --ff-only
,这就是我使用的。)这让我有点粗心/漫不经心,只是 运行 命令没有首先检查它是否 会 执行 fast-forward.
如果你同时领先和落后,你通常会想要合并或变基
如果您有他们没有的新提交,和他们有您没有的新提交,您可能需要将您的工作与他们的工作结合起来。这是 git pull
运行s 出现的第二个命令:
git merge
将进行完整的 three-way 合并(不仅仅是 fast-forward)。git rebase
将获取您现有的提交并将它们复制到 new-and-supposedly-improved 提交。如果复制全部有效,则 rebase 将放弃旧的(和糟糕的?)提交对于新的(和改进的?)。
变基比合并复杂得多,因为它在 commit-by-commit 基础上工作。它使用 git cherry-pick
或相当接近等效的东西,4 用于 each 需要复制的提交。 5 这些 cherry-pick 操作中的每一个都可以在合并冲突的中间停止,因此如果 git rebase
需要复制 5 次提交,则有 5 次机会停止。合并只有一次机会因合并冲突而在中间停止。 (另一方面,一个冲突可能很大,而 5 个冲突较小。)
这里是合并还是变基由您选择。不过,我认为关于 git pull
的其中一件事是你必须 做出 这个选择 在 你看到是否合并将是 fast-forward 或真正的合并。最近,Git 增加了使 git pull
只进行合并的选项 如果 是 fast-forward 合并。我声称这是正确的默认值(但它不是当前的默认值,所以我仍然建议避免 git pull
)。
4现代 Git 使用 cherry-pick 但内置了变基。 Git 的旧版本有时使用 cherry-pick,有时使用 git am
,rebase 是一个 shell 脚本。您可以通过添加 -m
and/or -s
参数或进行交互式变基,使 Git 的那些版本使用变基; interactive rebase literally 运行s git cherry-pick
.
5复制的提交集是......复杂的,尤其是在现代Git中带有--fork-point
选项。然而,在正常的简单情况下,这是您拥有的每个提交,而他们没有。
Full-blown合并
要进行 full-blown three-way 合并,我建议您使用(本地)分支,例如 dev
或 master
(或 main
如果你从 master
切换到 main
):
运行
git checkout
或git switch
(在 Git 2.23 或更高版本中)以及要用于新合并提交的分支名称.这让你“在”那个分支上,因为git status
现在会说on branch dev
或其他什么。运行
git 合并 <em>name</em>
orgit merge <em>commit-hash-ID</em>
开始合并过程。在此处使用名称,如git checkout master && git merge dev
,设置默认提交消息以使用分支名称(例如,merge branch dev
)。除此之外,哈希 ID 与名称一样好。哈希 ID 很难输入,因此您可能希望使用该名称。您应该意识到 Git 在这里 不需要 名称。它需要一个哈希ID,它只是将名称转换为一个哈希ID来进行实际的合并。
合并本身通过找到一个 shared 提交来工作。假设我们有一个这样的提交图:
I--J <-- master (HEAD)
/
...--G--H
\
K--L <-- dev
表示 master
上有两个不在 dev
上的提交,反之亦然。在本例中,两个 分支上的最佳提交是提交H
。 (请记住,后来的提交向右,较早的提交向左,并且 Git 从两个 end-point 提交开始并向后工作。因此提交 H
是第一个 shared 提交,在两个分支上,因此是 best 共享提交。)
合并过程现在通过将提交 H
中的快照与两个选定提交中的两个快照中的每一个进行比较来工作:
git diff --find-renames <hash-of-H> <hash-of-J> # what "we" did on master
git diff --find-renames <hash-of-H> <hash-of-L> # what "they" did on dev
Merge 通过组合这两组更改来工作,将组合的更改应用到提交中的快照 H
——这在添加“他们的”的同时保留“我们的”更改——并根据结果进行新的提交.或者,如果合并出错,合并会在中间停止,在您的工作树和 Git 的索引中留下一团糟,您的工作变成修复混乱并继续合并。
假设一切顺利,Git 进行新的 merge 提交,这是一个包含两个 parent 的提交。6 我们可以这样画:
I--J
/ \
...--G--H M <-- master (HEAD)
\ /
K--L <-- dev
请注意,如果提交 J
是 origin/master
的提示(现在仍然是),您现在可以 git push origin master
将提交 M
发送到 origin
.此提交将严格领先于他们自己的 master
——就他们而言,快进——因此他们很可能会接受此推送。
6从技术上讲,合并提交是具有两个 或更多 parent 的任何提交,但您无能为力处理超过两个你不能用额外合并做的事情。事实上,这有点相反:有些事情你 不能 使用 so-called 章鱼合并,有 3 个或更多 parents,您 可以 进行多个 two-parent 合并。这是它们的主要价值:如果您遇到这种情况,您可能会认为它是这些更简单的合并之一。一个真正的 Git 绝地大师(或西斯尊主)可能会做出违反这些期望的事情,虽然.
git push
一旦你有一个本地分支,与 origin
(或其他一些远程 Git 上的相同分支名称相比,你可以 运行 git push
将新提交发送到他们的 Git。与 git fetch
一样,这首先调用他们的 Git——但这次你是发送者,而不是接收者,并且你的 Git 列出了你的某些提交的哈希 ID选择.
在上面的示例中,我们刚刚进行了新提交 M
,因此这是我们唯一拥有而他们没有的提交,当我们 运行 git push origin master
—请注意,我们应该在这里使用 所有四个词,并且不要遗漏 origin
或 master
7——我们的 Git 将提供他们的 Git 新提交 M
,他们将接受,然后提供 M
的两个 parent,他们将拒绝 不用了,我已经有了。然后我们将打包提交 M
,包括它的文件,并将包发送过来。然后我们的Git会礼貌的向他们的Git请教,请,如果可以的话,把你的master
设置为_____,填在空白处使用提交的哈希 ID M
.
他们现在将检查此操作对他们来说是否是 fast-forward。 如果是,他们将接受请求,8 现在他们的 master
将提交 M
。我们的也会,因为我们在制作时将它放在 master
上。
7如果你 运行 git push
没有参数,Git 以同样的方式填写 origin
部分不带参数为 git fetch
做。然后它根据 push.default
填写 分支名称 部分。 Git版本2(所有版本2版本)中的push.default
设置为simple
,即使用当前分支。但是,Git 版本 1 中的默认设置是 matching
。这有时是您想要的,但对于新手来说很少如此,这就是为什么他们在 Git 2.x.
我自己实际上倾向于使用 git push origin HEAD
。 HEAD
转换为 当前分支 。在 GitHub 上进行 pull requests 的特殊情况下,我有时会使用 git push origin HEAD:<em>new branch name 我当场编的</em>
。然后我重命名本地分支,在输入 git push
命令时弄清楚了我 想要 为本地分支使用什么名称,但没有更早。 :-)
8根据站点的不同,存储库所有者通常可以添加各种规则来保护特定的分支名称,以便只有列入白名单的用户才能直接向他们推送。在这种情况下,您可能需要将自己加入白名单。
删除并忽略不应该提交的文件
重要的是要记住,在 Git 中,提交 是 Git 知道的所有文件的快照 在您(或任何人)制作该快照时。一旦创建,该快照将无法更改。事实上,任何现有提交都无法更改。这是由于 Git 使用的散列方案。
由于每个提交都以计算机其余部分无法使用的形式存储,因此每个提交都必须提取到普通的日常文件中。当 Git 执行此操作时,它会获取 提交的 文件,将它们扩展为可用形式,并将它们粘贴到工作区中。 Git 将此工作区称为您的 工作树 。
由于您的工作树是一个充满文件的普通目录(或者如果您喜欢这些术语,则为一组“文件和文件夹”),您现在可以在此处创建新 文件。 Git 不会知道他们。 Git 它知道的文件列表保存在 Git 称为它的 index.
中索引不只是 列出 文件。它实际上以 pre-Git-ified 形式保存了其中的 快照 :压缩和 de-duplicated,准备进入 next 提交.当您使用 git add
时,您是在告诉 Git:将此文件复制到您的索引中。 如果之前有一个文件,它会被踢出(即,不是 overwritten 因为它们是存档形式),而是新文件进入。如果文件 之前 Git 的索引中没有 ,那么现在它连同它的名称。
当您 运行 git commit
时,Git 保存 其索引 中的那些文件,格式为 然后 在索引中。这成为新的快照。因此 Git 的索引始终是您建议的 next 快照。9 如果您避免将文件 添加到 Git 的索引,它不会进入提交。
每次更改文件时都手动添加每个文件,这既烦人又痛苦。于是就有了一个en-masse add everything操作(其实是多次这样的操作)。但是如果你这样做,这会添加你可能永远不需要的文件提交。
为了防止那个,Git有.gitignore
。这个文件的名字有点不对。它不会忽略 已经在Git 的索引中的文件。它主要做的事情是抑制来自git status
的投诉,关于未跟踪文件Git提醒您添加。该文件也许应该命名为 .git-do-not-complain-about-these-untracked-files
。但是,它也会阻止那些 en-masse add a bunch of files 操作添加那些未跟踪的文件。所以更好的名字是.git-do-not-complain-about-these-untracked-files-and-do-not-auto-add-them-during-an-en-masse-add-operation
。这仍然不完全准确,但比 .gitignore
更接近。但这也很荒谬,所以.gitignore
它是。
这里的关键是 在 .gitignore
中列出一个文件不会导致它被忽略 。提交 中 文件的缺失导致它被忽略。当文件不在 Git 的索引中时,它不会在您的 next 提交中。但是如果它在某个 existing 提交中,并且您检查了该提交,Git 会将提交的文件 复制到 它的索引,然后将该文件(并将其展开为有用的形式)复制到您的工作树。
要获取已经在 Git 的索引 out 的 Git 的索引中的文件,我们使用 git rm
:
git rm file-that-should-not-have-been-committed
这会删除 索引副本 和 工作树副本。为避免立即删除工作树副本,我们可以使用:
git rm --cached file-that-should-not-have-been-committed
仅删除 index 副本,单独留下工作树副本。然后我们可以进行新的提交,新的提交将缺少文件。
但是:没有 existing 提交可以更改。因此 old 提交将 拥有 文件。旧提交和新提交之间的区别会说删除文件。对旧提交的任何使用都会为您提供文件的旧副本。任何切换从旧提交,到一个缺少文件的新提交,删除文件.
这就是为什么删除一开始就不应该提交的文件会很痛苦的原因。
可以获取某个存储库中的所有提交并将其中的 所有 复制到缺少某些文件的 new-and-improved 提交,但结果是new repository 需要丢弃旧存储库的所有副本。你 可以 这样做,尤其是在某些项目开始的时候,如果你知道每个人都有旧的提交并且可以说服他们全部切换。
9冲突合并中出现异常。在这里,Git 的索引被扩展了,您实际上 不能 从中创建快照,直到您通过解决冲突将其缩小回其正常形式。