git实战杂记
将分支推送到远程仓库通常二个目的:1.多人协作开发 2.远程备份
其实commit也是一种"备份",只是在本地,可以使用git reflog反悔。如果没有做备份,不要轻易使用git reset HEAD --hard or git checkout -f这样非常危险的命令
commit的重要性还体现在git rebase, git diff, git log.....都是围绕commit对象。
多人协作分支主要分为: 管理员本地创建开发分支并推送到远程 & 开发人员配置开发分支
1.本地创建分支并推送到仓库
1.1.保证本地的分支都是可以编译通过的,最好是reset HEAD --hard + git clean -dxf后的代码。
遇到过这样的情况:本地的master的修改需要通过patch提交到远程的svn上的master,而远程svn的代码会滞后同步到git服务器上的master分支, 滞后会导致: git的master分支暂时更新不到最新的代码,而工作区中的代码仍然存在于stage区域,如果此时想基于本地的master打开发分支,就需要将这些改动去掉,否则将来在开发分支上执行git rebase master的时候,最新的master(经过svn同步到git)会与开发分支中误包含的文件导致冲突。
如果不想基于本地的master建开发分支,可以使用 git checkout -b login_site origin/master来创建
1.2.git checkout -b login_invite 基于本地的base branch创建的新分支并切换到新分支
1.3.git push branches login_invite:login_invite 推送到远程的仓库,注意远程分支并不会与本地分支关联
1.4.git branch --set-upstream login_invite branches/login_invite 本地分支关联到远程分支
2.开发人员的操作:
2.1 git remote add branches root@app220:/home/git.gold/×××-dev (如果开发人员没有远程库,需要添加,如果有可跳过)
2.2 git fetch branches ( or git pull branches)
2.3 git branch --set-upstream login_invite branches/login_invite (本身会创建分支,还可以使用git checkout -b localBranchName repositoryName/remoteBrancheName 或者git checkout --track localBranchName repositoryName/remoteBranchName)
2.4 git checkout login_invite
如何基本本地或远程创建分支:
1.基于本地分支新建一个分支并checkout该分支(管理员push到远程仓库之前使用)
git checkout -b newbranch master
2.基于远程分支在本地新建一个开发分支并checkout该分支(开发人员跟踪管理员创建的远程分支)
git checkout -b localBranchName repositoryName/remoteBrancheName
开发分支rebase:
在开发分支git rebase master,原理是将当前分支的HEAD置为master对应的reference,然后将开发分支自独立出来的每个提交逐个打到HEAD上。这样开发分支就是线性提交。
此时如果想把分支的内容合并到master,只需切换到master,然后git merge dev_branch即可实现快速合并。
遇到过的情况: 并不做git merge dev_branch,而是git diff master dev_branch,生成的patch通过reivew后打patch打上svn上的master。
注意,分支之间的diff顺序。如果生成master的patch,不要使用git diff dev_branch master。否则生成的patch只能patch -R -p1 < patch来反打。
rebase与merge的区别:
1.git merge合并结果不是线性的,会存在多个父提交。与git rebase相比,会多出一次commit.
2.stackoverflow的一个讨论:
Assuming you start out with a tree that looks like this
A---B---C topic
/
D---E---F---G master
You'll want to run 2 commands git rebase master
to make it look like this
A'--B'--C' topic
/
D---E---F---G master
then from master
run git merge topic
, which will do a fast forward merge, and you'll end up with a tree that looks like this
topic
/
D---E---F---G---A---B---C -- master
Can I just checkout master and run git rebase topic??
Running that command would result in a tree that looks like this
topic
/
D---A---B---C---E---F---G -- master
This is the (somewhat edited) git documentation for rebase
, hopefully this will help:
All changes made by commits in the current branch but that are not in upstream are saved to a temporary area.
If you are on topic
and run git rebase master
, that means commits A, B and C are going into a temporary area.
The current branch is reset to upstream. This has the exact same effect as git reset --hard upstream.
At this point the temporary state of the tree looks like this
topic
/
D---E---F---G -- master
The commits that were previously saved into the temporary area are then reapplied to the current branch, one by one, in order.
Commits A, B, C that were saved into the temporary area are "recommitted" to the branch you are currently on topic
.
topic
/
D---E---F---G---A---B---C -- master
Running git rebase topic
from master
would perform the exact same steps, except instead of putting commits A, B, C into the temporary area, it would put commits E, F G into the temporary area.
一个完整开发分支rebase示例:包括新建一个rebase分支,将开发分支的多个commit合并成一个commit打到rebase分支上。如果需要review代码,还需要最终生成一个diff文件。
假如创建开发分支code的基础是master的m_old节点。
1.切换到master分支,reset到m_old节点,创建并切换到分支rebasecode. (创建该分支的目的就是将之前开发分支的多个commit一次性打到rebasecode上,避化rebase的操作)
2.git diff rebasecode code> 1.diff
3.patch -p1 <1.diff
4.git commit (打完1.diff只会体现在工作区中,git rebase master合并针对都是commit对象,所以必须提交)
5.切换到master并pull最新代码
6.切换到rebasecode并执行git rebase master
7.resolve conflict (在rebase的过程中,如果执行git branch,显示的是no branch,如果git rebase --abort或者rebase完成之后,则git branch会显示为rebasecode)
git add -u
git rebase continue
8.git diff master rebasecode > 2.diff (该文件就是用来codereview的diff)
9.review的修改就可以直接在rebasecode上提交,而不是最初的开发分支code, 如果在code上提交,也可以git diff v_old v_now出一个diff文件,patch到rebasecode上。
10.review完成后,再次git rebase master,git diff master rebasecode> complete.diff
11.如果是在svn上提交,可以直接patch -p0 < complete.diff
git diff与反patch
经常出现误提交并push到远程分支的情况,遇到这样的情况,可以通过下面由难到易三种方式解决:
1.新建一个分支(最好保留老分支)
2.在远程分支上reset到上一版本。
3.打反patch 比如现状是v1-----v2, 误提交的节点是v2, 通过git diff v2 v1 > rpatch.diff ,再通过pathch -p1 < rpatch.diff清除错误提交。结果是v1-----v2-----v3. v3的状态应该与v1一致。
git config:
编辑git ini文件的命令,与vim相比,git config可以在git工程下的任何目录执行。
git ini文件分为版本库级别的配置(.git/config) > 全局配置(.gitconfig) > 系统级配置文件(/etc下)
maven的settings.xml也有类似的覆盖策略。
示例1:
git config --unset的使用:用来重置git/config中的key-value。
比如git config --unset remote.origin.url root@172.16.10.220:/home/git.gold/daodao-site
git config --unset remote.origin.url则会直接删除。
如果要删除节点[remote "origin"]下面的全部内容。git config --unset remote.origin 是错误的,删除节点使用git config --remove-section remote.origin
示例2:通常user.name user.email设置为global,删除user.name:
git config --unset --global user.name
远程仓库查看与添加:
git remote -v 查看本地配置的远程仓库 git remote add
什么时候删除分支:分支代码被merge或者分支的patch已经checkin
1.删除本地分支
git branch -D branch
2.删除远程的分支,注意:前面的空格
git push remote : branch
为什么git reset HEAD --hard之后,还不能恢复以前的环境:
因git reset HEAD --hard只以还原被git跟踪过的文件。而之前patch打过之后包含了新增文件,这些是未被git跟踪过的。
解决:
1.rm -rf 新增文件,然后再打patch。否则再打patch的时候,新增的文件会阻止打patch。
2.git clean -dxf,其实也是执行rm命令,但是该命令会把工作区下untrack的所有文件删除,有些eclipse工程会在执行后受到影响。
示例如下:
ygao@pts/ttys000 $ git status
# On branch master
# Changes to be committed:
.......
# Changes not staged for commit:
......
# Untracked files:
# test11
ygao@pts/ttys000 $ git clean -dxf
Removing test11
ygao@pts/ttys000 $ git status
# On branch master
# Changes to be committed:
......
# Changes not staged for commit:
......
冲突的场景:
1.打patch可能冲突,使用linux的patch命令也一样,另外merge也可能冲突
2.rebase不同的分支冲突
2.1 rebase冲突内容格式分析
rebase的过程中,<<<<<的部分应该是current patch打完之后工作区代码======表示patch old打到文件的代码>>>>>>
比如下面的示例:HEAD表示rebase先打的"add batch api" patch,接着rebase打了一个"refactor" 的patch,二个patch修改的是同一段代码导致冲突,此时<<<<<<< HEAD ...... =======表示refactor打上后,本地工作区的代码。
而====== ........>>>>>>> add batch api表示之前的patch打的代码,处理的顺序当然是删除旧的patch的代码。
<<<<<<< HEAD return 0; ======= if (o1.getLocationid() == o2.getLocationid()) { return 0; } >>>>>>> add batch api 合并之后结果: return 0;
2.2 rebase之后,查看是否正确,否则git checkout --conflict=merge file接着重新解决。
3.复杂冲突解决过程:
<<<<<<< HEAD <<<<<<< HEAD DBLocationElement ele = DBLocationStore.getInstance().getLocationElement(locid); while (ele != null && !ele.isGeographic()) { ele = ele.getParentLocation(); } if(ele == null) { return -1; } ======= DBLocationElement ele = DBLocationStore.getInstance().getLocationElement(locid); ======= DBLocationElement ele = DBLocationStore.getInstance().getLocationElement(locid); >>>>>>> commit revise script while (ele != null && !ele.isGeographic()) { ele = ele.getParentLocation(); } if(ele == null) { return -1; } <<<<<<< HEAD >>>>>>> Revise data migration script ======= >>>>>>> commit revise script 变化1: <<<<<<< HEAD DBLocationElement ele = DBLocationStore.getInstance().getLocationElement(locid); while (ele != null && !ele.isGeographic()) { ele = ele.getParentLocation(); } if(ele == null) { return -1; } ======= DBLocationElement ele = DBLocationStore.getInstance().getLocationElement(locid); while (ele != null && !ele.isGeographic()) { ele = ele.getParentLocation(); } if(ele == null) { return -1; } <<<<<<< HEAD >>>>>>> Revise data migration script ======= >>>>>>> commit revise script 变化2: <<<<<<< HEAD DBLocationElement ele = DBLocationStore.getInstance().getLocationElement(locid); while (ele != null && !ele.isGeographic()) { ele = ele.getParentLocation(); } if(ele == null) { return -1; } ======= DBLocationElement ele = DBLocationStore.getInstance().getLocationElement(locid); while (ele != null && !ele.isGeographic()) { ele = ele.getParentLocation(); } if(ele == null) { return -1; } >>>>>>> commit revise script 变化3: DBLocationElement ele = DBLocationStore.getInstance().getLocationElement(locid); while (ele != null && !ele.isGeographic()) { ele = ele.getParentLocation(); } if(ele == null) { return -1; } if(ele.isGeographic()) { geoId = ele.getLocationID(); } return geoId;
区分二种不同的冲突是因为冲突的解决方式不一样
1.打patch冲突或merge冲突,编辑冲突跟平时的修改代码没什么差异。修改完成后,都是要把修改添加到缓存git add,然后git commit。
2.rebase的冲突解决:
2.1解决完一个补丁的应用冲突,git add -u用来将已track的文件修改加入stage。
2.2接着执行git rebase --continue直接另一个补丁制造了冲突
重复2.1,2.2直至rebase完成,注意,rebase不需要显式地执行commit。commit是git自动完成。
3.查看冲突可以使用git diff, git diff在不同的场景,使用的含义不同。
git pull:
相当于git fetch+git merge,git merge如果属于快速合并,会形成一个提交点。
git merge对应的commit点可以使用MERGE-HEAD(FETCH-HEAD)指代
git push对应的commit点可以对应的是HEAD
git add:
与传统的SVN不同,add操作不仅仅是把新文件加入到版本控制,对于文件内容的修改,git也需要add,这说明git add并不是象svn一样,针对文件名,而是针对文件的内容。
git stash & git stash pop的使用场景:
示例:
master分支被别人修改并push,而自己的本地代码并没有commit,此时pull远程的代码会提示本地有未提交的修改文件。
此时就是git stash很好的使用场合:
1.如果git stash+git pull+git stash pop没有冲突,就表示自动合并,这很有用,不需要在git pull之前,通过git checkout放弃已有的修改。
2.如果制造了冲突(通过打Patch制造),解决冲突,但是发生的可能性小,1的场合比较常见。
3.如果制造冲突,还有一种办法是本地的代码在pull之前,commit到head,但不push(也push不了,因为与远程冲突),git pull远程的代码做merge时,会与本地的代码冲突。实质是merge制造的冲突。
附:git stash pop与下面的操作类似:
1.先将本地的代码基于旧的version生成一个diff文件。
2.git reset HEAD --hard 抛弃本地工作区的修改。
3.git pull
4.尝试打diff制造冲突。
git blame与git log比较
git blame 细粒度,在类或文件中查看某个方法或代码块的历史。git log主要用来查看文件或类的历史。是粗粒度的。
示例:git blame -L 10,50 Test.java -L用来指定要跟踪的代码块或方法的起止范围, 后面必须指定是在哪个类或文件中。
git log --author ygao -p file --author可以用来查询某个人的提交记录,-p是显示diff文件,file可以指定跟踪的是哪个文件。
git+svn模式:git server只能pull,不能push,svn server通过打patch checkin
git本地检出的master分支不能push,只能pull.
一旦push之后就会形成一个commit点,当仓库的master分支从svn的服务器同步到了最新的代码时,会发现仓库中的HEAD指向的commit对象并不是上一次同步的时候,pull形成的commit,而是一个领先的commit,此时merge就是Non Fast Forward。如果在Non Fast Forward的情况下做merge操作,就会出现二个parent的情况,也会出现non line history的问题。
non line history就象是传统的svn提交的commit都是一条直线。通常git merge会形成non line history而推荐使用git rebase。