ProGit这本书讲的挺不错。循序渐进。有几个命令书中语焉不详,卡住了挺长时间。记录一下。
remote branch
每一个remote branch都会在本地表现为一个不可改变的静态branch。使用git branch -a可以看到。红色的就是remote branch。不能够对这些branch进行改动,但是可以创建一个这些remote branch的tracking branch:
git checkout -b b1 origin/b1
# or
git checkout --tracking origin/b1
这时候,创建出来的local branch就会被git看作是对应的remote branch的tracking branch。在执行git push的时候,local branch的内容就会自动被push到它的tracking branch。
缺省的master就是origin/master的tracking branch。
本地的branch只能够通过向remote branch推送(push)数据的方式来和remote branch交互。如果想创建一个remote branch,就需要创建一个branch,然后
git branch b2
git push origin b2
这两条命令创建一个本地branch b2,然后将它增加到remote branch。这时候运行 git branch -a,能看到有了一个新的remote branch。
git fetch
git fetch [remote_repo] :这个是将remote repo所有的数据:包括更新的文件,新增/减的分支,tag,等等,全部下载到本地的local repo。但是,不会做merge。也就是说,master分支数据可能是旧的,但是origin/master上的数据已经是新的了。可以进一步运行
git checkout master
git merge origin/master
来将两个分支进行merge。或者更好使用git push来进行merge(前提是master确实是origin/master的tracking branch)
git pull
git pull [remote_repo branch_name]:这个命令直接从指定remote_repo的指定branch拉取相应的数据。并将远程branch的更新和本地的tracking branch做merge。
注意。这时候,并不会把这个branch之外的数据拉下来。比如,如果远程的另一个branch有更新,或者增加了一个新的branch,这个命令并不会把这些数据拉下来。
如果直接执行git pull [remote_repo],则会将所有数据拉下来,包括其它分支的更新,包括新增的分支。同时还会将当前branch与它tracking的branch做merge。
git push
git push [remote_repo local branch:remote branch]。缺省情况下,将当前branch的改动push到缺省repo中它track的branch。也可以加repo和branch,将当前分支的数据push到任何一个repo的任何一个branch
删除远程分支
如果需要删除一个远程分支,则需要git push origin :b1根据 git push的定义,就是把空push到远程的b1 branch。也就是删除了。
但是,如果别人已经在b1删除之前执行了fetch或者pull,在本地有了b1这个branch,再次执行fetch或者pull并不会删除这个branch。运行git branch -a也不能看出这个branch被删除了。这时候需要运行
$ git remote show origin
* remote origin
Fetch URL: git@github.com:xxx/xxx.git
Push URL: git@github.com:xxx/xxx.git
HEAD branch: master
Remote branches:
master tracked
refs/remotes/origin/b1 stale (use 'git remote prune' to remove)
Local branch configured for 'git pull':
master merges with remote master
Local ref configured for 'git push':
master pushes to master (up to date)
这时候能够看到b1是stale的,可以使用git remote prune origin将它从本地repo也去掉。
Rebase
rebase就是re-base。git每个commit都有一个parent指针指向当前提交的parent,即base。rebase则是改变commit的parent,这样的好处是可以减少版本树的分支和复杂度,坏处是用不好的话会给别人带来很多麻烦。
假设有一个master:7,branch f1是从master:7上cut出来的一个branch。f1是一个local的branch,用来开发一个feature。
在开发feature的时候,有人将一些修改commit到了master上。
在feature做了一个阶段之后,master已经有了版本10。f1有了版本5。这时候想把f1上的修改合并到master上。首先执行git pull把master上的更新都拉到本地来。然后可以使用rebase命令将f1上的改动都rebase(gitpro上翻译为衍合,我觉得拟合也挺好,至少是个现有的词)到master上,然后进行提交。
git checkout f1
git rebase master
#or
git rebase master f1
这时候,git会先找到f1和master的交点,也就是cut f1的时候的版本(master:7),从这个版本开始,git将f1每次的提交作为一个diff文件保存起来。然后checkout master,将这些diff一个个应用到master上。
看起来很像是merge,不过merge会产生一个新的提交,而rebase是re-base,也就是改变f1的每个提交:改变每个提交的parent,改变每个提交指向的文件快照。让整个过程看起来好像就没有f1一样,所有的改动都是直接在master上来的。Rebase过之后,如果feature已经做完,f1可以直接被删掉。
merge则会产生一个新的提交,不改变已有的提交。好处是比较安全,历史不会被改动。不好之处就是版本树会比较乱。
rebase很好很清爽。需要注意的一点是“永远不要rebase那些已经推送到远程repo的更新(commit)”
rebase会改变commits的内容,也就是改变历史,对于没有push到server上的commits无所谓,反正只有本地一个人在用。这时候使用rebase将commits rebase到公共branch(master),可以让本地的历史更清爽。
对于一个已经push到remote repo的commits。比如f1如果已经在remote repo上了,而且,f1:1到f1:3已经push到repo上了。这时候如果再将f1:1到f1:5 rebase到master上,然后强制push到repo上,结果就是f1这个branch的历史被改动。当然,如果这个remote branch只有一个人用,也还无所谓。如果有人在f1:3的时候cut出来一个branch,这时候那个人就悲剧了。再次pull的时候,将会收到rebase后改动的commits,以及原来就有的commits,情况会变的异常糟糕。
如果是push到master上,影响将更大。
总之,remote repo上的commit永远不要改动。也就是说,永远不要将已经push出去的commit再rebase一下。rebase也无所谓,切不要push出去也不会对别人造成影响。如果push出去了,这样改变了history,改变了server上的版本树,情况就糟糕了。
具体图和例子请参照Git Pro