我有 3 个未暂存的文件。
我想要的是提交并推送其中的 2 个并将第三个保存在私有分支中。
如果我为其中 2 个执行 git add/git commit ,然后 git checkout 分支名称,然后 git checkout master 和 THEN git pull ,然后是 git push 我是否正确?
1 回答
具体而言,您在master
,并且您有三个文件:
.../file1
.../file2
.../file3
其中...
零件是回购中的任何路径,我将它们留在下面。
前两个文件是新的(您刚刚创建它们)或现有的(您修改了它们),在这种情况下无关紧要。
同时file3
也是新的或修改的,但这可能会有所不同。
第一部分绝对是正确的:git add file1 file2; git commit
将进行一个新的提交,其中包含两个新的或修改的文件(以及 repo 中已经存在的所有其他文件),但不包含file3
(如果它是新的),或者不包含对file3
(如果它已经存在)。
一旦使用file1
and提交file2
完成,您就可以推送新的master
:
git push origin master
file3
当您执行上述操作时,您还没有做任何事情,或者您在哪个分支上都没有关系;通过指定要将本地分支推master
送到origin
's master
,您至少可以随时尝试此推送。(这就是两个额外参数的用途。origin
部分是“它要去哪里”,该master
部分的含义与 相同master:master
,意思是“推送本地master
-> 远程master
”。)或者,如果你愿意,你可以延迟这个,我们待会儿就会讲到。更重要的是,这可能会因“拒绝”消息而失败,在这种情况下,您几乎必须file3
在某处提交时延迟它。
现在,至于file3
,情况有点复杂。您想“将其保存在私有分支中”,好吧,我们可以这样做,但是:它是一个全新的文件,还是已经存在?如果全新的事情很容易。如果它已经存在,那么还有一两个问题:这个私有分支是新分支,还是现有(私有)分支?如果它是一个现有的分支,那么文件在“你现在所在的任何分支”和“你想要它的另一个分支”之间是否有所不同?
对于新分支或新文件,这里不会有任何问题。做就是了:
git checkout -b new_private_branch # create new private branch
git add file3
git commit
或者:
git checkout existing_private_branch
git add file3
git commit
如果您现在所在的任何分支与现有的私有分支之间没有变化,那么第二个命令也将起作用。但是,如果file3
在您当前的分支中,并且也在 中existing_private_branch
,并且两者之间不同,您将获得:
error: ... would be overwritten by checkout:
file3
当您尝试移动到现有的私有分支时。这是因为 git 需要覆盖工作树版本file3
才能移动到该分支。
如果是这种情况,您需要将修改的file3
(或对 的更改file3
)保存在git checkout
不会覆盖的地方,然后使工作目录file3
干净或不存在。有许多方法可以做到这一点(例如,mv file3 another_name
对git stash
),具有各种不同的优点和缺点。
我猜file3
是新的,和/或私有分支是新的,所以这里没有必要详细介绍。无论如何,让我们假设您已正确保存file3
在private_branch
. 这意味着您git commit
在该私有分支上做了一个并git status
表明一切都是干净的。
git pull、git fetch、git merge 和/或 git rebase
一切都在本地清理干净,您现在可以执行以下操作:
git checkout master
回到主人身上。
如果其他人打败了你git push
,而你git push origin master
现在尝试(而不是更早),你会得到一个错误:
! [rejected] master -> master (non-fast-forward)
这意味着,他们打败了你。他们把一些master
你还没有的东西放进去。你需要得到它,你如何得到它git fetch
。
如果你没有得到一个rejected
,没有人能打败你,你的状态很好,你就完蛋了!所以你不需要任何pull
或fetch
-然后-merge
或任何东西。如果你git push origin master
早一点这样做,你更有可能击败其他人。但是,如果您之前尝试过但失败了,或者您现在尝试但失败了,那么现在是时候做点什么了。
git 拉
该git pull
命令实际上意味着git fetch
跟随git merge
(除非您将其配置为进行变基)。我不喜欢使用git pull
太多——我喜欢单独fetch
的 -then-decide 步骤。即使您仍然使用git pull
,让我们将其分解为fetch
和merge
部分。
获取
我也喜欢在origin
这里输入部分:
git fetch origin
除非你已经配置了一堆 git,否则无论如何fetch
都会假设origin
,所以你可以省略origin
部分;我只是喜欢明确。
这就是提交的 ASCII 图表派上用场的地方。在第一次git push
尝试失败(被“拒绝”)并且您执行 agit fetch
之后,这就是您最终的结果。每个字母代表一些提交。提交A
并且B
是你开始的。CommitC
是您为file1
and所做的file2
。提交D
是“他们”(无论谁打败你)所做的。您的私有分支也“从 B 中脱落”,但我不会费心在这里画它。
... - A - B - D <-- origin/master
\
C <-- master, HEAD=master
推送被“拒绝”,因为您要求遥控器移动origin/master
到指向C
. From C
,历史可以追溯到B
,而 git 只“向上和/或向后”遵循这些行,因此没有来自B
to的链接D
。那会让所有的承诺D
变得孤独和被遗忘。
此时,您有两个简单的(ish)选项。你可以git merge
。既然你现在就在master
轨道master
上origin/master
,这意味着与长形式相同:
git merge origin/master
这告诉 git 将您当前的分支(即master
)与您刚刚“获取”-ed 的分支合并origin/master
。如果合并顺利,它会自动git commit
-s 合并,现在你有了远程会满意的东西。从图表上看,它使这个新的提交 E:
... - A - B - D <-- origin/master
\ \
C - E <-- master, HEAD=master
现在git push origin master
会成功的。指向origin/master
是可以的,E
因为E
指向和 。但是,这确实意味着你已经做了一个你本可以避免的合并提交。你是否应该避免它是一个政策决定,而不是一个技术决定(所以除了你的老板之外没有人可以告诉你以这样或那样的方式做这件事:-))。C
D
您的另一个选择是使用git rebase
而不是git merge
:
git rebase origin/master
(与 一样git merge
,您实际上可以省略origin/master
部分,因为master
正在跟踪origin/master
。)
这样做的目的是获取您之前所做的提交——添加file1
和file2
——并“找出在该过程中发生了哪些变化”(即,您在这些文件中所做的更改),然后对origin/master
. 如果一切顺利,它会留下你的旧提交(它仍然存在,git 将所有内容保存很长时间)并进行新的提交。结果是这样的:
... - A - B - D <-- origin/master
\ \
\ C2 <-- master, HEAD=master
\
C [no label]
old-commit- C
(file1
和file2
) 中的更改现在也在 new-commit-C2
中。不像C
,C2
是“在上面” origin/master
。所以现在git push origin master
会成功,每个人(包括你)最终都会忘记一切C
,只是使用C2
,历史看起来就像A - B - D - C2
. 它并没有真正比合并版本“更好”,但它更易于查看和(有时)考虑。