Linux/Android系统知识之repo git知识篇

想必有不少朋友一听到git,第一时间就会想到近来火遍大江南北大名鼎鼎的GitHub,如果你没在上面注册过账号,估计都不敢说自己是位可爱程序猿/媛o(╯□╰)o对于初学者来说时常会满脑子疑惑:Git、GitHub、Repo这三者之间有关系吗?区别是啥尼~?

介一下先:git是一个广泛运用于linux等大型项目的源码管理工具,可以让你轻松掌控所有代码的修改历程并轻松回退到任何时候的状态。GitHub是一个著名的代码托管网站,依托其高效灵活地代码托管方式和独特的Project共享导向,让全球的编程爱好者能够轻松参与世界上最新最有趣的源码工程,从而最火遍全球,该网站的源码管理方式依托的就是gitrepo是一个脚本,当某个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
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
int main(void)
{
        printf("no world say hello!\n");
        return 0;
}

看到效果后张老板很开森,对我连连称赞。路过的产品经理看到我俩对着屏幕那猥琐的满面笑容,凑过来看了看,我去!!你俩干啥呢,这么挫的打印log也好意思在我司的Apple电脑上运行???赶紧改!!!!

于是我又。。

#include
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 ..." 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 ..." 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 ..." to unstage)

	new file:   hello.c

Untracked files:
  (use "git add ..." 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 ..." 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 
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 ..." 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 
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 ..." 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 
Date:   Tue Jul 11 00:52:46 2017 +0800

    add secondFile.c for appendage

commit 46b1b13347428e7d63628031efbce0c27aa8eb93
Author: bevis 
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 
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 ..." to update what will be committed)
  (use "git checkout -- ..." 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 ..." 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 
Date:   Tue Jul 11 01:13:24 2017 +0800

    change secondFile.c

commit 9e32e68f0f55005303937d7217dcbcc0d275f5af
Author: bevis 
Date:   Tue Jul 11 00:52:46 2017 +0800

    add secondFile.c for appendage

commit 46b1b13347428e7d63628031efbce0c27aa8eb93
Author: bevis 
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 可以免于使用编辑器编辑commit讯息的麻烦。

git status: 查看当前git工作目录中所有文件的状态。

git log: 查看所有历史的commit操作记录。

git show:查看某条commit的具体修改细节。Tips:使用git show 查看某条记录时,可以只粘贴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讯息全部抹除(不能恢复),常常用在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提交之前的样子,并保留revert记录。

promote:gitwork bevis$ git log
commit 72f1eff040fbddccdc826a151de1026763a7b8dc
Author: bevis 
Date:   Tue Jul 11 01:13:24 2017 +0800

    change secondFile.c

commit 9e32e68f0f55005303937d7217dcbcc0d275f5af
Author: bevis 
Date:   Tue Jul 11 00:52:46 2017 +0800

    add secondFile.c for appendage

commit 46b1b13347428e7d63628031efbce0c27aa8eb93
Author: bevis 
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 
Date:   Wed Jul 12 00:12:23 2017 +0800

    Revert "change secondFile.c"

    This reverts commit 72f1eff040fbddccdc826a151de1026763a7b8dc.

commit 72f1eff040fbddccdc826a151de1026763a7b8dc
Author: bevis 
Date:   Tue Jul 11 01:13:24 2017 +0800

    change secondFile.c

commit 9e32e68f0f55005303937d7217dcbcc0d275f5af
Author: bevis 
Date:   Tue Jul 11 00:52:46 2017 +0800

    add secondFile.c for appendage

commit 46b1b13347428e7d63628031efbce0c27aa8eb93
Author: bevis 
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(该命令会同时将当前使用的branch自动切换到新branch),若我们想回到某个曾经的branch,只需使用git checkout 就可以切换过去了。


  • 远程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。

Linux/Android系统知识之repo git知识篇_第1张图片
如果想把源码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
.		..		.git		firsttest
看,远端的源码就神器地同步到本地了,并且还帮我们建立好了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官方操作指南,动手进行一些实际的练习。这种操作性的知识必须动起手来才能越发烂熟于心,真正成为你的好帮手!

本人能力有限,难免有疏漏和不足的地方,对此十分欢迎大家帮忙批评指正,共同学习。









你可能感兴趣的:(Linux/Android系统知识之repo git知识篇)