git实战杂记

来源:互联网 发布:电池技术 知乎 编辑:程序博客网 时间:2024/05/01 17:11


将分支推送到远程仓库通常二个目的: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。