git 使用小結

艾倫先生發表於2017-12-14

寫在前面

有關Git的誕生故事以及Git的強大,這裡無須贅述。寫這篇文章的原因是因為,習慣了用Git桌面工具向Github提交程式碼的我,換了一臺筆記本後突然發現我連最基本的Git命令列都不會了。。。羞愧至極,所以就開始我的學習之路。

我的學習方法是,先大概瞭解Git的相關指令,然後簡單試驗之後,開始蒐集各類部落格資料,彙總之後,再次對試驗過程進行整理,我整理了一些高質量的博文在附錄中或者在文中。

本文是一篇階段性的記錄文章,只記錄我容易遺忘的知識點,引用的文章圖片都標註了出處,好了下面開始本文

為什麼很多人推薦從svn轉向git

從使用者角度分析:

  1. svn下載原始碼慢。在git中一個幾個G的版本庫,一般一二十分鐘就能下載完畢,但是在svn中要一個小時左右;
  2. svn隨時都得要與伺服器互動,無論是檢視log,還是檢視以往的版本你必須跟伺服器相連,並且速度奇慢無比,而git做這些幾乎是瞬間的事;
  3. 各個分支之間的補丁遷移麻煩,在git上只要兩三個命令就可以完事的(其實一個命令,因為需要查詢與分支切換),但是在svn上你必須要下載每個分支的程式碼,然後比較修改,再上傳;

從伺服器角度說為什麼要用git:

  1. git版本庫佔用空間小(幾乎是svn的分支數之一也就是說如果有四個分支,svn的版本庫的體積將接近git的四倍),SVN每個分支都是一份程式碼的copy,而git每個分支只是各個提交點的hash值的集合。分支幾乎不佔用什麼空間;
  2. git是分散式管理系統,完全可以不對程式碼進行備份,但SVN不行,一旦伺服器的硬碟掛掉整個程式碼庫就完了;
  3. git不用時時聯網查詢,並且對檔案進行壓縮,使得檔案體積大大減小,並且傳輸速度快,svn是單個檔案,git是壓縮後的,在使用svn時我已經碰到過好幾次伺服器無響應了。由於git很多都可以在本地操作的,所以大大降低了客戶端對伺服器的連線,出現這種情況的概率會大大減小;
  4. 如果客戶端離伺服器端非常遠,在網速糟糕的情況下,用svn下載程式碼速度遠不上git.

推薦文章 http://www.cnblogs.com/common1140/p/3952948.html

git 工作原理

git 使用小結

git跟蹤並管理的是修改,而非檔案。而且git只能文字資訊的修改和恢復,對於二進位制檔案,比如word或者圖片,只能監聽到改動卻無法對改動進行恢復。

git 分支概念

每次提交(git commit),git都會把這些提交串成一個時間線(後文中管這個時間線叫做分支)

預設情況下,使用git init初始化的專案只有一條分支,叫做master分支。可以理解成有一個叫做master的指標指向著當前最新一次提交。還有一個概念就是存在一個HEAD指標,而且請記住HEAD永遠指向當前的分支。HEAD指向的是哪個分支,當前我們就向那分支提交程式碼。

下圖展示了一個普通git專案的分支結構

git 使用小結

可見,當前專案只有一個分支,就是master分支,有三次提交。然後HEAD指標,指向當前的master分支。

因分支必須指向某一次commit,所以必須先有commit才有後來的分支。某個分支必須至少有一次提交git commit之後,才會在git branch指令中顯示出來

隨著你的每次提交,master分支都會向前移動,HEAD指標也同樣跟著向前移動。

當某個場景中,我們從當前分支master建立了新的分支,比如叫做dev。下面這條指令是建立並切換到dev分支。

git branch -b dev
複製程式碼

背地裡,Git會新建一個指標叫做dev,指向master指向的相同的提交,然後再把HEAD指向dev,就表示當前分支在dev上。

git 使用小結

可以看出,git建立一個分支很快,因為除了增加一個dev指標,改動HEAD指標指向以外,工作區的檔案沒有變化。

從現在開始,對工作區的修改和提交就是針對dev分支了。比如新提交一次後,dev指標就會往後移動一步,但是注意,master指標不變。

git 使用小結

git分支合併

假如我們在dev分支上的開發工作完成了,就可以吧dev分支上的程式碼合併到master分支上。有兩種情況

  • 快進模式 Fast forward 簡稱FF模式 本模式下的前提是:master分支,在dev分支拉出後,沒有過提交。這樣的合併,Git直接就把master指向dev指向的當前分提交即可,完成了快速合併

      git checkout master
      git merge dev
    複製程式碼

git 使用小結

合併完dev分支後,dev分支可以刪掉了,刪掉dev分支的操作,其實就是把dev指標給刪掉就好了。現在就只剩下一個master指標指向當前提交了

    git branch -d dev
複製程式碼

如果沒有提交,就刪除分支,git會提示你,你需要先提交在刪除。但是可以強制刪除,使用

    git branch -D dev
複製程式碼
  • 非快進模式

本模式發生的情況就是,分支dev對某個檔案,比如readme.txt檔案修改後並提交,此時切換回master,發現master分支也修改了readme.txt並提交。 這樣master分支和dev分支都對同一個檔案進行了修改並提交,在master分支合併dev分支時,會提示衝突。如下所示

git 使用小結

這個時候Git無法實現快速合併,只能先嚐試著將各自的修改合併後提交,但是這個時候經常伴隨著衝突,比如readme.txt檔案出現了衝突,檔案被修改成瞭如下內容

    <<<<<<< HEAD
    Creating a new branch is quick
    Creating a new branch is quick & simple.
    =======
    Creating a new branch is quick AND simple.
    >>>>>>> dev
複製程式碼

=== 分割衝突程式碼,上面HEAD標記的是當前分支的內容,下面dev標記的是dev分支合併過來的內容,自己進行取捨

解決完衝突後,將衝突檔案 git add git commit提交,這時我們的分支圖變成這個樣子

git 使用小結

最後檢視一下分支的合併情況

git log --graph --pretty=oneline --abbrev-commit
複製程式碼

git 使用小結

上面的截圖可以看出,解決衝突的部分被單獨劃歸出來了

禁用快速合併模式(簡稱no-ff模式)保留分支commit資訊

前面提到過快速合併,快速合併的場景下,快速合併沒有衝突。但是有個缺點就是,在合併後的master的log日誌中看不到本次合併的dev分支的commit的id和描述資訊

git 使用小結

即,合併完成後,一旦刪除了dev分支,我們既無法知道分支存在過,也無法區分那些修改是在分支上進行的。下面看一個禁止掉快速合併的情況

git merge --no-ff -m "merge with no-ff" dev
複製程式碼

git 使用小結

上面可以看到 分支合併歷史中記錄了合併過來的分支的commitid

如果不適用no-ff模式,單純使用git reflog檢視一下日誌,你能看出來的,那些分支合併過來了嗎

git 使用小結

git stash

當前情況:此時有兩個分支,master和dev,dev編輯到一半,並未成功,所以不能提交。但此時master有一個bug需要馬上去修復,但因為dev無法提交,所以用stash儲存現場。

git stash
複製程式碼

轉去master去把bug修復完後,checkout到dev開發分支,應該先merge master 然後再

git stash pop 
複製程式碼

恢復dev開發現場

多人協作

當你從遠端倉庫克隆時,實際上Git自動把本地的master分支和遠端的master分支關聯起來,並且,遠端倉庫的預設名稱是origin。

  • 檢視遠端倉庫的名稱

      git remote
    複製程式碼
  • 推送分支

    推送分支,就是把該分支上的所有本地提交推送到遠端庫。推送時,要指定本地分支,這樣,Git就會把該分支推送到遠端庫對應的遠端分支上:

      git push origin master
    複製程式碼
  • 抓取分支

      git clone git@github.com:yourname/learngit.git
    複製程式碼

    從遠端庫中獲取分支時,預設只能獲取到master分支,使用下面指令建立並關聯遠端的dev分支

      git checkout -b dev origin/dev
    複製程式碼

    然後提交dev分支

      git push origin dev
    複製程式碼
  • 解決遠端衝突

    如果你提交之前,恰好有小夥伴對當前的dev的分支也做了某些修改,則可能提示你提交失敗,這個時候,你需要使用

      git pull
    複製程式碼

    來把最新的提交從origin/dev上抓取下來,然後本地合併,解決衝突中,再次提交

  • 刪除遠端庫

    git push origin :

因此,多人協作的工作模式通常是這樣:

  1. 可以試圖用git push origin branch-name 推送自己的修改;

  2. 如果推送失敗,則因為遠端分支比你的本地更新,需要先用git pull試圖合併;

  3. 如果合併有衝突,則解決衝突,並在本地提交;

  4. 沒有衝突或者解決掉衝突後,再用git push origin branch-name推送就能成功!

注:如果git pull提示“no tracking information”,則說明本地分支和遠端分支的連結關係沒有建立,用命令git branch --set-upstream-to=origin/dev dev

更多內容參考阮一峰老師的這篇討論git工作流程的文章 經典文章

git 後悔藥系列

git diff 分析程式碼差異

diff --git a/readme.txt b/readme.txt
index 46d49bf..9247db6 100644
--- a/readme.txt
+++ b/readme.txt
@@ -1,2 +1,2 @@
-Git is a version control system.
+Git is a distributed version control system.
 Git is free software.
複製程式碼

git diff #是工作區(work dict)和暫存區(stage)的比較 git diff --cached #是暫存區(stage)和分支(master)的比較

git 錯誤回退

  • 將還在工作區內未新增到快取區的檔案恢復到上次add之前的狀態

      git checkout -- filename
    複製程式碼
  • 將提交到快取區的檔案回退到工作區,將快取區的該檔案恢復到上次commit之前的狀態

      git reset HEAD filename
    複製程式碼
  • 已經commit了,想回退到之前的某次提交

    • 回退至某次快照(commit提交)

        git reset --hard 3628164(此數字為某次commit的log日誌前七位)
      複製程式碼
    • commitid可以通過如下命令查詢,翻看歷史操作記錄

        git reflog
      複製程式碼
  • 已經提交到遠端庫了

    無法回退

git 刪除檔案

使用 git rm filename 刪除指定檔案

執行完上面這條指令,相當於先刪掉了檔案,然後執行了git add,將刪除的結果新增到了暫存區,所以如果想恢復檔案的話,需要進行如下操作

git reset HEAD filename #恢復暫存區
git checkout -- filename #恢復工作區
複製程式碼

刪除歷史檔案(它會從HEAD所在的分支歷史中查詢並刪除檔案)

git filter-branch --tree-filter 'rm -f <FILE>' HEAD
複製程式碼

關聯github

先建立本地庫,關聯遠端庫

  • 生成祕鑰

      ssh-keygen -t rsa -C "youremail@example.com"
    複製程式碼

    以在使用者主目錄裡找到.ssh目錄,裡面有id_rsa和id_rsa.pub兩個檔案,這兩個就是SSH Key的祕鑰對,id_rsa是私鑰,不能洩露出去,id_rsa.pub是公鑰,可以放心地告訴任何人。

  • 在github填寫公鑰

    登陸GitHub,開啟“Account settings”,“SSH Keys”頁面:然後,點“Add SSH Key”,填上任意Title,在Key文字框裡貼上id_rsa.pub檔案的內容:

  • 將本地git專案關聯遠端倉儲

      $ git remote add origin https://github.com/yourname/learngit.git
    複製程式碼
  • 將本地改動推送到遠端倉庫

      git push -u origin master    #將當前分支推送到遠端,第二次開始不用使用-u
    複製程式碼

先建立遠端庫,用本地庫關聯

  • 克隆遠端庫

      git clone https://github.com/yourname/learngit.git
    複製程式碼

GitHub給出的地址不止一個,還可以用https://github.com/yourname/gitskills.git這樣的地址。實際上,Git支援多種協議,預設的git://使用ssh,但也可以使用https等其他協議。

使用https除了速度慢以外,還有個最大的麻煩是每次推送都必須輸入口令,但是在某些只開放http埠的公司內部就無法使用ssh協議而只能用https。

git 標籤處理

釋出版本時,我們通常會給版本庫打一個標籤(tag)。標籤是以一個更讓人理解的方式來標記commit,

比如說請給我找出4.5.6版本的提交,而不是請給我找出commit id為5f54f5sd的提交。“找到了:git show v4.5.6”

類似IP和域名的關係。

git中標籤雖然是版本庫的快照,也是一個指向當前commit的指標。(和分支很像,但是分支可以移動,標籤不能移動),所以建立和刪除標籤都是瞬間完成的。

  • 在當前分支直接使用如下指令建立tag

      git tag v1.0#可以是任意有意義的字串
    複製程式碼

注:然後使用 git tag查詢所有標籤。tag不是按時間順序排布的,而是按照名字排序的

  • 標籤是預設打在最新一次提交上的,新增commitid來對任意提交打tag

      git tag v1.0 commitid
    複製程式碼
  • 使用下面的指令可以用來建立帶有說明的tag

      git tag -a v0.1 -m "version 0.1 released" 3628164
    複製程式碼
  • 使用 git show 可以檢視tag的說明

  • git push origin <tagname>可以推送一個本地標籤(標籤預設儲存在本地)

  • git push origin --tags可以推送全部未推送過的本地標籤

  • git tag -d <tagname>可以刪除一個本地標籤;

  • git push origin :refs/tags/<tagname>可以刪除一個遠端標籤。

提高效率的小指令

  • 彩色的git輸出

      git config color.ui true
    複製程式碼
  • 讓快照的log資訊緊湊輸出

      git log --pretty=oneline
    複製程式碼
  • 以圖表的形式檢視提交歷史

      git log --graph
    複製程式碼
  • 只檢視commit id的精確位數(一般前7位就能識別)

      git log --abbrev-commit
    複製程式碼
  • 給常常的指令設定別名

      git config --global alias.st status
      git st
    
      git config --global alias.unstage 'reset HEAD'
      git unstage test.py
    複製程式碼
  • 只檢視最近一次的log

      git log -1
    複製程式碼
  • 獲取本地公鑰的快捷方式(文字軟體開啟有可能出錯!)

    mac pbcopy < ~/.ssh/id_rsa.pub

    windows clip < ~/.ssh/id_rsa.pub

    linux sudo apt-get install xclip xclip -sel clip < ~/.ssh/id_rsa.pub

精彩理解

我覺得這svn和git兩個工具主要的區別在於歷史版本維護的位置

Git本地倉庫包含程式碼庫還有歷史庫,在本地的環境開發就可以記錄歷史 而SVN的歷史庫存在於中央倉庫,每次對比與提交程式碼都必須連線到中央倉庫才能進行

這樣的好處在於: 1、自己可以在離線環境檢視開發的版本歷史 2、多人開發時如果充當中央倉庫的Git倉庫掛了,任何一個開發者的倉庫都可以作為中央倉庫進行服務

svn中央伺服器掛了,那我一樣可以將本地的專案重新搭建一個伺服器呢?

答:不行,你的本地沒有歷史版本

答!! 斷網了,看看能不能檢視歷史版本?看看能不能提交程式碼?

關於git commit

git commit命令確認的是最近的一次git add,如果檔案最近的內容修改沒有被git add,那麼在git commit時,最近的檔案修改內容不會被提交。

  • 指定檔案可以提交未儲存到暫存區(unstaged) vi readme.txt git commit readme.txt -m "commit with unstaged modify" vi readme.txt git commit -a -m "commit all file will commit unstaged modify"

  • 不使用其他引數不會提交unstaged modify git commit -m "commit without param will not commit unstaged modify"

是否可以把公鑰私鑰一起給別人呢

只需要給公鑰。

原始資料經過私鑰加密後只能用公鑰解密,換句話說,別人收到經過加密的資料後,如果用你的公鑰能夠解密,那麼他就可以確認這些資料是你傳送的 如果把私鑰給別人的話,別人就可以冒充你給別人發東西了

關於回退操作

對於沒提交到stage的修改;

刪除後,重新恢復,修改的內容是會直接消失的;比如你在檔案中新增一個字元:‘1’;不用git add file 新增到stage;直接用rm刪除後,再用git checkout -- file恢復;恢復過來後,去看檔案是沒有這個字元:‘1’的。

印證了 git checkout -- file恢復的是已經新增到stage的內容;

而使用git rm刪除的就是stage的內。git reset HEAD -- file會從master中將被刪的stage的內容拷貝過去。如果你使用了git rm之後接著使用git commit -m “remove file”則會刪除master裡的內容;

所以,關於一次回退流程是這樣的

  • git reset --hard HEAD^可以將刪除的master從回收站恢復過來;
  • 然後利用git reset HEAD -- file 從master中拷貝到stage中;
  • 最後再用git checkout -- file 從stage中拷貝到工作目錄中。

關於未提交的修改

現象: 在分支修並提交後,切到主幹,主幹的工作區是乾淨的;在分支修改不提交,切回主幹,主幹工作區是被修改過未提交的狀態

解釋: 這樣做的好處可能是你本來想對DEV分支進行想改,但是你忘了切換到dev你還在master就已經改了工作區,如果這時切換到dev修改的工作區內容沒了,豈不是很操蛋。只有commit之後才確定修改的內容屬於哪個分支。

未commit的工作區檔案和stage檔案是可以靈活地在且僅在任一branch存在的。這是前提。

  • 在工作區做了修改,提交到DEV的分支,再切換回master

    這時候,對master來說,工作區沒有任何未提交的修正(因為所有修正都已經commit)。則工作區內容應該是與master分支最後一次提交的內容一致。(處於任何其他時間點,都意味著工作區可能存在修正,這就出現了矛盾)

  • 在工作區做了修改,沒有提交到DEV分支,即切換回master 這個時候,對master來說,工作區有了修正,那麼就保持工作區的現有狀態即可。

pull&& push

pull:本地 <-- 遠端 push:本地 --> 遠端

本質上都是同步commit

如果你本地落後遠端,必然要pull 如果你本地超前遠端,必然要push

課後檢視

  • 為什麼會產生衝突?什麼時候產生衝突
  • 上面的截圖可以看出,解決衝突的部分被單獨劃歸出來了
  • 圖形介面操作
  • 斷網情況下檢視一下svn的操作

附錄

相關文章