15.3 强制非快进式推送
现在用户user1和user2的工作区是相同的。思考一个问题:如果两人各自在本地版本库中进行独立的提交,然后再分别向共享版本库推送,会互相覆盖么?为了回答这个问题,进行下面的实践。
首先,用户user1先在本地版本库中进行提交,然后将本地的提交推送到“远程”共享版本库中,操作步骤如下。
(1)用户user1创建team/user1.txt文件。
假设这个项目约定:每个开发者在team目录下写一个自述文件。于是用户user1创建文件team/user1.txt。
$cd/path/to/user1/workspace/project/
$mkdir team
$echo "I'm user1.">team/user1.txt
$git add team
$git commit-m "user1's profile."
[master b4f3ae0]user1's profile.
1 files changed,1 insertions(+),0 deletions(-)
create mode 100644 team/user1.txt
(2)用户user1将本地提交推送到服务器上。
$git push
Counting objects:5,done.
Delta compression using up to 2 threads.
Compressing objects:100%(2/2),done.
Writing objects:100%(4/4),327 bytes,done.
Total 4(delta 0),reused 0(delta 0)
Unpacking objects:100%(4/4),done.
To file:///path/to/repos/shared.git
5174bf3..b4f3ae0 master->master
(3)查看当前user1版本库中的日志。
$git log—oneline—graph
*b4f3ae0 user1's profile.
*5174bf3 initial commit.
通过上面的操作步骤,可以看到用户user1成功地更新了“远程”共享版本库。如果用户user2不知道用户user1所做的上述操作,仍在基于“远程”版本库旧数据同步而来的本地版本库中进行改动,然后用户user2向“远程”共享版本库推送,会有什么结果呢?用下面的操作验证一下。
(1)用户user2创建team/user2.txt文件。
$cd/path/to/user2/workspace/project/
$mkdir team
$echo "I'm user1?">team/user2.txt
$git add team
$git commit-m "user2's profile."
[master 8409e4c]user2's profile.
1 files changed,1 insertions(+),0 deletions(-)
create mode 100644 team/user2.txt
(2)用户user2将本地提交推送到服务器时出错。
$git push
To file:///path/to/repos/shared.git
![rejected]master->master(non-fast-forward)
error:failed to push some refs to 'file:///path/to/repos/shared.git'
To prevent you from losing history,non-fast-forward updates were rejected
Merge the remote changes(e.g.'git pull')before pushing again.See the
'Note about fast-forwards' section of'git push—help'for details.
(3)将用户user2推送失败的错误日志翻译如下:
到版本库file:///path/to/repos/shared.git
![被拒绝]master->master(非快进)
错误:部分引用向'file:///path/to/repos/shared.git'推送失败
为防止您丢失历史数据,非快进式更新被拒绝。
在推送前请先合并远程改动,例如执行'git pull'。
用户user2推送失败了。但这不是坏事,反倒是一件好事情,因为这避免了用户提交的相互覆盖。Git通过检查推送操作是不是“快进式”的操作,从而保证用户的提交不会相互覆盖。一般情况下,推送只允许“快进式”推送。所谓快进式推送,就是要推送的本地版本库的提交是建立在远程版本库相应分支的现有提交基础上的,即远程版本库相应分支的最新提交是本地版本库最新提交的祖先提交。但现在用户user2执行的推送并非如此,是一个非快进式的推送。
此时用户user2本地版本库的最新提交及其历史提交可以用git rev-list命令显示,如下所示:
$git rev-list HEAD
8409e4c72388a18ea89eecb86d68384212c5233f
5174bf33ab31a3999a6242fdcb1ec237e8f3f91a
用git ls-remote命令显示远程版本库的引用对应的SHA1哈希值,会发现远程版本库所包含的最新提交的SHA1哈希值是b4f3ae0……,不是本地最新提交的祖先提交。
$git ls-remote origin
b4f3ae0fcadce8c343f3cdc8a69c33cc98c98dfd HEAD
b4f3ae0fcadce8c343f3cdc8a69c33cc98c98dfd refs/heads/master
实际上当用户user2执行推送的时候,Git就是利用类似方法判断出当前的推送不是一个快进式推送,于是产生警告并终止。
那么如何才能成功推送呢?一个不一定正确的解决方案是:强制推送。
在推送命令的后面使用-f参数可以进行强制推送,即使是非快进式的推送也会成功执行。用户user2执行强制推送,会强制刷新服务器中的版本。
$git push-f
Counting objects:7,done.
Delta compression using up to 2 threads.
Compressing objects:100%(3/3),done.
Writing objects:100%(7/7),503 bytes,done.
Total 7(delta 0),reused 3(delta 0)
Unpacking objects:100%(7/7),done.
To file:///path/to/repos/shared.git
+b4f3ae0…8409e4c master->master(forced update)
注意到了么,在强制推送的最后一行输出中显示了“强制更新(forced update)”字样。这样用户user1向版本库推送的提交由于用户user2的强制推送被覆盖了。实际上在这种情况下user1也可以强制推送,从而用自己(user1)的提交再去覆盖用户user2的提交。这样的工作模式不是协同,而是战争!
在上面用户user2使用非快进式推送强制更新版本库,实际上是危险的。滥用非快进式推送可能造成提交覆盖大战(战争是霸权的滥用)。正确地使用非快进式推送,应该是在不会造成提交覆盖“战争”的前提下,对历史提交进行修补。
下面的操作也许是一个使用非快进式推送的更好的例子。
(1)用户user2改正之前错误的录入。
细心的读者可能已经发现,用户user2在创建个人描述的文件中把自己的名字写错了。假设用户user2在刚刚完成向服务器的推送操作后也发现了这个错误,于是user2进行了下面的更改。
$echo "I'm user2.">team/user2.txt
$git diff
diff—git a/team/user2.txt b/team/user2.txt
index 27268e2..2dcb7b6 100644
—-a/team/user2.txt
+++b/team/user2.txt
@@-1+1@@
-I'm user1?
+I'm user2.
(2)然后用户user2将修改好的文件提交到本地版本库中。
采用直接提交还是使用修补式提交,这是一个问题。因为前次的提交已经被推送到共享版本库中,如果采用修补提交会造成前一次提交被新提交抹掉,从而在下次推送时造成非快进式推送。这时用户user2就要评估“战争”的风险:“我刚刚推送的提交,有没有可能被其他人获取了(通过git pull、git fetch或git clone操作)?”如果确认不会有他人获取,例如现在公司里只有user2自己一个人在加班,那么可以放心地进行修补操作。
$git add-u
$git commit—amend-m "user2's profile."
[master 6b1a7a0]user2's profile.
1 files changed,1 insertions(+),0 deletions(-)
create mode 100644 team/user2.txt
(3)采用强制推送,更新远程共享版本库中的提交。这个操作越早越好,在他人还没有来得及和服务器同步前将修补提交强制更新到服务器上。
$git push-f
Counting objects:5,done.
Delta compression using up to 2 threads.
Compressing objects:100%(2/2),done.
Writing objects:100%(4/4),331 bytes,done.
Total 4(delta 0),reused 0(delta 0)
Unpacking objects:100%(4/4),done.
To file:///path/to/repos/shared.git
+8409e4c…6b1a7a0 master->master(forced update)