標籤:

git實戰雜記

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 master2.基於遠程分支在本地新建一個開發分支並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_oldv_now出一個diff文件,patch到rebasecode上。

10.review完成後,再次git rebasemaster,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-sitegit 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已經checkin1.刪除本地分支git branch -D branch2.刪除遠程的分支,注意:前面的空格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:# test11ygao@pts/ttys000 $ git clean -dxfRemoving test11ygao@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,這說明gitadd並不是象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 pull4.嘗試打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。

推薦閱讀:

實戰營 入營須知
[轉載]奇門應用實戰—陰宅葬山巽向乾
鐵板神數的基本演演算法則和秘數的實戰運用  鐵板宗師
太極拳實戰要義探討

TAG:實戰 |