1. 程式人生 > >Linux/Android系統知識之repo git知識篇

Linux/Android系統知識之repo git知識篇

想必有不少朋友一聽到git,第一時間就會想到近來火遍大江南北大名鼎鼎的GitHub,如果你沒在上面註冊過賬號,估計都不敢說自己是位可愛程式猿/媛o(╯□╰)o對於初學者來說時常會滿腦子疑惑:Git、GitHub、Repo這三者之間有關係嗎?區別是啥尼~?

介一下先:git是一個廣泛運用於linux等大型專案的原始碼管理工具,可以讓你輕鬆掌控所有程式碼的修改歷程並輕鬆回退到任何時候的狀態。GitHub是一個著名的程式碼託管網站,依託其高效靈活地程式碼託管方式和獨特的Project共享導向,讓全球的程式設計愛好者能夠輕鬆參與世界上最新最有趣的原始碼工程,從而最火遍全球,該網站的原始碼管理方式依託的就是git

repo是一個指令碼,當某個project的程式碼量達到了巨量級(Android原始碼就是由上百個git庫組合到一起產生的),那麼一個個git來進行管理顯然就會力不從心了,於是Android Project就開發了repo指令碼來批量操作git。

網上針對git發展由來的故事講的十分詳盡有趣,對此就不再贅述了,我的目的只是為了讓新老朋友能儘快從工程實戰角度入手,快速搞定這些知識點。對於這類工具類的知識,我一向主張從實戰出發,先了解其骨架,再通過自己動手操作來熟悉其筋絡,之後若遇到問題,再去查詢手冊查漏補缺即可,針對從事code manager工作或git原理十分感興趣的朋友,此時才有深入研究官方文件的必要。

來不及解釋了,快和我一起進入git的世界吧~

先建立一個空的目錄gitwork作為我們Project的根目錄:

promote:working bevis$ mkdir gitwork
promote:working bevis$ cd gitwork/
promote:gitwork bevis$ ll
total 0
drwxr-xr-x  2 bevis  staff   68  7 10 23:35 .
drwxr-xr-x  5 bevis  staff  170  7 10 23:35 ..

此時整個目錄是完全乾淨的,在沒有git的情況下,若想建立一個hello.c的工程檔案,我們會這樣做:

promote:gitwork bevis$ vim hello.c
#include<stdio.h>
int main(void)
{
        printf("hello world\n");
        return 0;
}
~

然後我們編譯執行:

promote:gitwork bevis$ gcc -o hello hello.c
promote:gitwork bevis$ ./hello
hello world

執行成功!  突然張老闆給我打了個電話,說天天就知道列印“hello world”,都老掉牙了,敢不敢來個特別點的!!!額~ 好吧,老闆的話必須聽,於是我滿(qu)心(ni)歡(mei)喜(de)重新編輯了hello.c

#include<stdio.h>
int main(void)
{
        printf("no world say hello!\n");
        return 0;
}

看到效果後張老闆很開森,對我連連稱讚。路過的產品經理看到我倆對著螢幕那猥瑣的滿面笑容,湊過來看了看,我去!!你倆幹啥呢,這麼挫的列印log也好意思在我司的Apple電腦上執行???趕緊改!!!!

於是我又。。

#include<stdio.h>
int main(void)
{
        printf("I am a big big boy in the big big world!\n");
        return 0;
}

產品經理和張老闆這下都開心了,我們開開心心的下了班。第三天產品經理神經兮兮地告訴我,昨晚深夜難眠突然覺得上次的輸出內容很有創意,讓我再改回去。我勒個去~沒備份啊,我也很無奈啊!三人面面面相覷,低頭不語~

有了git時間就不一樣了,它可以幫我們記錄所有的改動訊息,如果需要的話,也可以幫我們退回到記錄過的狀態,這就是程式碼管理軟體的重要職責(當然它還提供了一套方法來解決多部門多使用者的程式碼協同問題)。

要使用git,我們需要先初始化一個git倉庫(repository),這個倉庫會被git用來存放各種管理資料。

promote:gitwork bevis$ git init
Initialized empty Git repository in /Users/bevis/working/gitwork/.git/
我們看看git倉庫長什麼樣:
promote:gitwork bevis$ ls -a
.	..	.git	

可以看到當前目錄下出現了個.git資料夾,git通過它來管理所有/Users/bevis/working/gitwork/ 目錄中的檔案改動(.git所在同級目錄過更深目錄)。

promote:gitwork bevis$ tree .git/
.git/
├── HEAD
├── branches
├── config
├── description
├── hooks
│   ├── applypatch-msg.sample
│   ├── commit-msg.sample
│   ├── post-update.sample
│   ├── pre-applypatch.sample
│   ├── pre-commit.sample
│   ├── pre-push.sample
│   ├── pre-rebase.sample
│   ├── pre-receive.sample
│   ├── prepare-commit-msg.sample
│   └── update.sample
├── info
│   └── exclude
├── objects
│   ├── info
│   └── pack
└── refs
    ├── heads
    └── tags

9 directories, 14 files
其中的內容處於使用者來說大家不必關心,看看長啥樣就行,大可簡單將其認為是隱形的。
promote:gitwork bevis$ vim hello.c
promote:gitwork bevis$ cat hello.c
nt main(void)
{
        printf("hello world\n");
        return 0;
}
編輯完畢後,我們開始正式使用git命令:git status 來檢視git所管理的檔案的狀態
promote:gitwork bevis$ git status
On branch master

Initial commit

Untracked files:
  (use "git add <file>..." to include in what will be committed)

	hello.c

nothing added to commit but untracked files present (use "git add" to track)
可以看到有個檔案hello.c是untracked狀態,並告訴我們使用git add命令去將其變為tracked(cached)狀態。

先不管這兩個狀態是啥,我們試試效果再說:

promote:gitwork bevis$ git add hello.c
promote:gitwork bevis$ git status
On branch master

Initial commit

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)

	new file:   hello.c

哈哈,變成了tracked(cached)狀態。那我們再新建個檔案會變什麼狀態呢?試試唄
promote:gitwork bevis$ echo "hello my world" > secondFile.c
promote:gitwork bevis$ git status
On branch master

Initial commit

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)

	new file:   hello.c

Untracked files:
  (use "git add <file>..." to include in what will be committed)

	secondFile.c

和前面一樣,新建的檔案又成為了未追蹤(untracked)狀態,而以前hello.c的還是在tracked(cached)狀態不變。

我們先看一個新的命令:git log 可以用來檢視對原始碼的詳細歷史記錄。試試看:

promote:gitwork bevis$ git log
fatal: your current branch 'master' does not have any commits yet
報錯了!這是因為我們前面的檔案最多隻是到了tracked(cached)狀態,並沒有真正被commit,所以我們看不到任何git log記錄。

可以使用命令:git commit 來對tracked(cached)的檔案做一次commit動作,這樣以後就能通過git log看到完整的commit過程了。

Untracked files:
  (use "git add <file>..." to include in what will be committed)

	secondFile.c
promote:gitwork bevis$ git log
fatal: your current branch 'master' does not have any commits yet
promote:gitwork bevis$ git commit -m "init hello.c for my project"
[master (root-commit) 8c392ae] init hello.c for my project
 Committer: bevis <[email protected]>
Your name and email address were configured automatically based
on your username and hostname. Please check that they are accurate.
You can suppress this message by setting them explicitly. Run the
following command and follow the instructions in your editor to edit
your configuration file:

    git config --global --edit

After doing this, you may fix the identity used for this commit with:

    git commit --amend --reset-author

 1 file changed, 5 insertions(+)
 create mode 100644 hello.c
promote:gitwork bevis$ git status
On branch master
Untracked files:
  (use "git add <file>..." to include in what will be committed)

	secondFile.c

nothing added to commit but untracked files present (use "git add" to track)
promote:gitwork bevis$ git log
commit 8c392ae20fb4d6b7d3c53121e69c88c32de0740b
Author: bevis <[email protected]>
Date:   Tue Jul 11 00:39:13 2017 +0800

    init hello.c for my project
可以發現兩個重要問題:1,cached的檔案已經消失,進入commit狀態;2,untracked檔案不受任何影響。

順便留意一下上面的提示訊息:

your configuration file:
    git config --global --edit
After doing this, you may fix the identity used for this commit with:
    git commit --amend --reset-author
其實第一次使用git時,大家需要通過git config --global命令先進行下自己的git資料錄入,我由於沒有錄入,所以git自己幫我用電腦名稱填了一個。即使沒配置或配置錯誤,大家隨時可以用上面的兩天提示訊息對錄入資料進行更正。針對配置部分的詳細請查詢git官網資料:初次執行-Git-前的配置即可。這部分不是我們的重點,也不會影響我們的學習過程,所以大家可以自行研究下。

下面我們試著把secondFile.c也commit上去:

promote:gitwork bevis$ git add secondFile.c
 
promote:gitwork bevis$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	new file:   secondFile.c
promote:gitwork bevis$ git commit -m "add secondFile.c for appendage"
[master 9e32e68] add secondFile.c for appendage
 1 file changed, 1 insertion(+)
 create mode 100644 secondFile.c
 
promote:gitwork bevis$ git status
On branch master
nothing to commit, working tree clean
 
promote:gitwork bevis$ git log
commit 9e32e68f0f55005303937d7217dcbcc0d275f5af
Author: bevis <[email protected]>
Date:   Tue Jul 11 00:52:46 2017 +0800

    add secondFile.c for appendage

commit 46b1b13347428e7d63628031efbce0c27aa8eb93
Author: bevis <[email protected]>
Date:   Tue Jul 11 00:42:58 2017 +0800

    init hello.c for my project
很清楚可以看到,我們的secondFile.c是如何一步步從untracked狀態 --> tracked(cached)狀態 -->commited狀態的。

我們現在可以用git show命令去看看某次commit的檔案究竟改動了什麼

promote:gitwork bevis$ git show 9e32e68f
commit 9e32e68f0f55005303937d7217dcbcc0d275f5af
Author: bevis <[email protected]>
Date:   Tue Jul 11 00:52:46 2017 +0800

    add secondFile.c for appendage

diff --git a/secondFile.c b/secondFile.c
new file mode 100644
index 0000000..1a0672b
--- /dev/null
+++ b/secondFile.c
@@ -0,0 +1 @@
+hello my world

可以很清楚的看到我們在該檔案中加入了+hello my world字元。

一般情況下我們只需要粘取需要檢視的commit id的前6位就足以在上萬個檔案的工程中檢視到你想要的提交了,歸功於雜湊的特異性,即使在linux這種超大型工程專案的原始碼中,一般擷取12位的commit id就足以去重了。

最後我們修改一下secondFile.c檔案的內容看看file的狀態會如何轉變

promote:gitwork bevis$ vim secondFile.c
hello my girl
you are the one!
~
promote:gitwork bevis$ git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

	modified:   secondFile.c

no changes added to commit (use "git add" and/or "git commit -a")
可以發現即使是tracked過的檔案的修改,也提示我們需要使用git add命令來為commite做好準備。

此時我們可以通過命令: git diff 來double check下我們的修改是否滿足預期,然後就可以進行又一次的commite。

promote:gitwork bevis$ git diff
diff --git a/secondFile.c b/secondFile.c
index 1a0672b..c4b50cc 100644
--- a/secondFile.c
+++ b/secondFile.c
@@ -1 +1,2 @@
-hello my world
+hello my girl
+you are the one! 
promote:gitwork bevis$ git add . 

我們可以使用git add . 來指代所有的檔案,從而簡化每個檔案都要貼上一次才能add的情況。

promote:gitwork bevis$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	modified:   secondFile.c
promote:gitwork bevis$ git commit -m "change secondFile.c"
[master 72f1eff] change secondFile.c
 1 file changed, 2 insertions(+), 1 deletion(-) 
promote:gitwork bevis$ git log
commit 72f1eff040fbddccdc826a151de1026763a7b8dc
Author: bevis <[email protected]>
Date:   Tue Jul 11 01:13:24 2017 +0800

    change secondFile.c

commit 9e32e68f0f55005303937d7217dcbcc0d275f5af
Author: bevis <[email protected]>
Date:   Tue Jul 11 00:52:46 2017 +0800

    add secondFile.c for appendage

commit 46b1b13347428e7d63628031efbce0c27aa8eb93
Author: bevis <[email protected]>
Date:   Tue Jul 11 00:42:58 2017 +0800

    init hello.c for my project

  • 簡單做個小總結:

1: Git 有三種狀態,你的檔案可能處於其中之一:已提交(committed)、已修改(modified)和已暫存(staged)。

2:Git 常用操作流程如下:

  1. 在工作目錄中修改檔案。

  2. 暫存檔案:使用 git add。

  3. 提交更新:使用 git commit。

有了前面的知識基礎,對照下列在實際工作中會比較常用到的命令,相信就足以滿足您的正常使用了:

  • 基本命令類:

git add: 將處於已修改(modified)狀態的檔案加入已暫存(staged)狀態,為後續的commit動作做好準備。Tips:使用git add . 和 git add -u可以快速將所有處於已修改(modified)的檔案快速變為已暫存(staged)狀態。

git commit: 將處於已暫存(staged)狀態的檔案變為已提交(committed)狀態。Tips:使用git commit -m <message> 可以免於使用編輯器編輯commit訊息的麻煩。

git status: 檢視當前git工作目錄中所有檔案的狀態。

git log: 檢視所有歷史的commit操作記錄。

git show:檢視某條commit的具體修改細節。Tips:使用git show <commit id> 檢視某條記錄時,可以只貼上id號碼的前幾位即可。

git diff:檢視處於已修改(modified)檔案的具體改動。Tips:可以接兩條commit id用來比較兩條commit直接的差異。

git reset:git reset命令分為--hard和--soft兩種用法,前者會從硬碟上徹底抹去某些commit的記錄,而後者會玩了個小把戲,將從某條選定的commit id以後的所有檔案改動,全部打回已修改(modified)狀態並丟掉之後的commit message,由於後者的使用不當可能會當時破壞遠端資料庫中的commit訊息從而最終影響到某個協同工作的團體的資料共享同步,所以後者一般用得比較少。我們比較常用到的第一種git reset --hard <commit id> 會將commit id之後的所有更新的commit訊息全部抹除(不能恢復),常常用在developer本地修改驗證某否bug後發現無效,想快速回退到程式碼原始狀態時使用。Tips:既可以使用git reset --hard HEAD來指代最新的commit id,也可以使用HEAD~1來指代次新的commit id,聰明的你應該已經知到HEAD~N就是最新commit id倒數N條記錄了~所有可以用commit id的地方都可以用HEAD~N來代替的哦!

git revert:使用git reset故然可以保證commit訊息的乾淨整潔,可當你的原始碼來自遠端伺服器時,你就不太可能隨意在本地reset原始碼然後push回遠端程式碼庫了,因為這樣做的代價將會是永久丟失歷史commit資訊。此時我們可以使用git revert <commit id>來恢復某條commit提交之前的樣子,並保留revert記錄。

promote:gitwork bevis$ git log
commit 72f1eff040fbddccdc826a151de1026763a7b8dc
Author: bevis <[email protected]>
Date:   Tue Jul 11 01:13:24 2017 +0800

    change secondFile.c

commit 9e32e68f0f55005303937d7217dcbcc0d275f5af
Author: bevis <[email protected]>
Date:   Tue Jul 11 00:52:46 2017 +0800

    add secondFile.c for appendage

commit 46b1b13347428e7d63628031efbce0c27aa8eb93
Author: bevis <[email protected]>
Date:   Tue Jul 11 00:42:58 2017 +0800

    init hello.c for my project
promote:gitwork bevis$ git revert HEAD
[master 41be36d] Revert "change secondFile.c"
 1 file changed, 1 insertion(+), 2 deletions(-)
Revert "change secondFile.c"

This reverts commit 72f1eff040fbddccdc826a151de1026763a7b8dc.

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch master
# Changes to be committed:
#       modified:   secondFile.c
#
再看commit訊息發現自動出現一個revert的commit id,若用git show檢視會看到該條commit的動作和前一條完全相反,所以revert命令通過反向操作回到原始狀態。
promote:gitwork bevis$ git log
commit 41be36dcb9e7bc66259ec8055c8f4e62e3ea7df5
Author: bevis <[email protected]>
Date:   Wed Jul 12 00:12:23 2017 +0800

    Revert "change secondFile.c"

    This reverts commit 72f1eff040fbddccdc826a151de1026763a7b8dc.

commit 72f1eff040fbddccdc826a151de1026763a7b8dc
Author: bevis <[email protected]>
Date:   Tue Jul 11 01:13:24 2017 +0800

    change secondFile.c

commit 9e32e68f0f55005303937d7217dcbcc0d275f5af
Author: bevis <[email protected]>
Date:   Tue Jul 11 00:52:46 2017 +0800

    add secondFile.c for appendage

commit 46b1b13347428e7d63628031efbce0c27aa8eb93
Author: bevis <[email protected]>
Date:   Tue Jul 11 00:42:58 2017 +0800

    init hello.c for my project

  • 分支操作類:

git branch:檢視所有的branch有哪些並同時可以知道當前正在使用的工作分支(branch)。Tips:預設的branch名為origin或master。

git checkout: 一般我們使用git checkout -b <branch name>來新建一個branch(該命令會同時將當前使用的branch自動切換到新branch),若我們想回到某個曾經的branch,只需使用git checkout <branch name>就可以切換過去了。

  • 遠端git庫操作類:

git clone

git remote 

git push

git fetch

git pull

  • patch應用類:

git format-patch

git apply

git cherry-pick

對於遠端git庫操作類和patch應用類的command,建議由需要的朋友可以去git官網git pro中文版自行學習即可

下面我們來看看GitHub網站的常用知識點,由於這部分網上資源特別豐富,在這裡我只會簡單地拋磚引玉,帶大家快速瞭解入門。

建議大家都去github的官網https://github.com註冊個自己的賬號,即使你並不準備進行程式碼共享,也可以非常容易的實時獲取世界各地最優秀程式碼貢獻者的最新動向,使用人家的勞動成果。嘿嘿~

下面是我在網站上新建的一個Project,名為firsttest。


如果想把原始碼sync到我們的電腦上進行測試和修改,只需要點選圖片中右上角的Clone or download按鈕,然後複製框框中的地址。

在我們本地電腦的命令列中,新建一個資料夾為sync程式碼做準備(我取名資料夾為123~),cd進去後敲入:

promote:123 bevis$ git clone https://github.com/happybevis/firsttest.git
Cloning into 'firsttest'...
remote: Counting objects: 9, done.
remote: Total 9 (delta 0), reused 0 (delta 0), pack-reused 8
Unpacking objects: 100% (9/9), done.
promote:123 bevis$ ls -a
....gitfirsttest
看,遠端的原始碼就神器地同步到本地了,並且還幫我們建立好了git倉庫。
promote:123 bevis$ git branch
* master
可以看到我們預設的branch名稱為master,遠端資訊如下
promote:123 bevis$ git remote -v
origin	[email protected]:/home/bevis/gitrepo/123 (fetch)
origin	[email protected]:/home/bevis/gitrepo/123 (push)
於是大家可以結合前面的知識進行修改、commit等一系列動作了,做好以後使用git push命令即可將最新的commit訊息提交到github中自己的倉庫內。操作十分便捷。

最後我們看看repo是怎麼被玩轉的,如果你下過Android的原始碼,就會發現一個悲慘的事實,一份程式碼少則40G起,由於眾所周知的原因,我們的下載用時常常為3天以上,甚至有朋友需要一週到幾周。那為何Android原始碼這麼大呢?一個git能裝得下嗎?

由於後面的Android入門課還沒開始,所以我先直接給出簡單的結論。Android原始碼是由三四十個資料夾組合起來形成的一個十分龐大的集合,大名鼎鼎的linux原始碼只是三四十個資料夾中的一個而已,其程式碼量的龐大程度就此可見一斑了。然後是一個git究竟能裝下多大的程式碼呢?就我自己工作中所看的的程式碼量看,git原則上可以裝下不限大小所有的原始碼,不過當程式碼量達到一定程度後(例如裝了30份android原始碼),其某些查詢和切換操作明顯會感覺有些變慢,所以不建議一個git放海量的程式碼(個人心目中是上100G)。

前面提到了,Android的程式碼由非常龐大的程式碼集合而成,開發者角色互不相同,有些主要負責kernel,有些負責audio,有些負責jni,另有些負責framwork... 如果僅由一個git來管理,那麼多開發者共同協作下必然會一團糟,所以Android原始碼中例如kernel、第三方ssh庫,selinux庫,android framwork等程式碼目錄都由獨立的git倉庫管理,最終一份Android原始碼便由幾百個git倉庫組合而成了。

問題來了,我們如果要下一份Android原始碼,難不成要敲幾百遍git clone嗎?於是懶惰的developer開發了repo工具對git命令進行了一次封裝,從而可以讓使用者用一條命令就可以sync整份Android原始碼。

原理如下:

如果想下載Android原始碼,我們需要先使用repo init -u +原始碼所在url來初始化一份manifest。

例如使用

repo init -u ssh://192.168.1.1/manifest.git
我們就可以從遠端server獲取一份程式碼Android原始碼git清單的manifest檔案。

然後我們就可以敲入下面命令

repo sync -c -d

於是命令列中就開始翻江倒海地進行原始碼sync工作了。

有興趣的朋友可以檢視repo init工作目錄下.repo資料夾中的default.xml檔案,裡面是幾百條git的sync資訊。

說白了,repo就是一個指令碼,他會遍歷.repo目錄下的manifest檔案,然後按照其中的說明,遍歷呼叫git clone命令對git倉庫進行逐個sync。

剩下的一些repo 命令我個人工作中幾乎不用,一般會在修改後的某個git倉庫中(例如kernel)直接使用更熟悉的git命令進行commit、push等操作,效果其實是完全等效的,repo主要在我用來做程式碼sync動作時才會用到。

不過有兩個例外,我偶爾也會用到,他們分別是:

repo status 和 repo forall -c "shell command1;shell command2 ;..."

repo status是git status的遍歷版本,執行後會遍歷所有Android原始碼中的git倉庫,並逐個呼叫git status。所以就是git status的遍歷版本,當修改的原始碼涉及到的git倉庫過多時,很容易導致自己都忘了改到過哪些git庫,所以這條命令可以提醒我們。

repo forall -c "shell command1;shell command2 ;..."也是遍歷版本的shell命令操作,只不過我們可以自己定製想要在每個git倉庫中進行操作的命令。所以repo status其實是可以用repo forall -c "git status"完美替代的哦~!

通過這篇文章的學習,希望大家能夠對git有個基本的瞭解,然後對照git官方操作指南,動手進行一些實際的練習。這種操作性的知識必須動起手來才能越發爛熟於心,真正成為你的好幫手!

本人能力有限,難免有疏漏和不足的地方,對此十分歡迎大家幫忙批評指正,共同學習。