2010年11月6日星期六

Git详解(一)

我用git最开始是在github,每次提交代码以后,右上角的部分就会显示这些信息,类似于下面的:
commit fa6f27b7de063c2f301b0e7148b5bd5e813faa98
tree 5e7a19c158b89fbc52a078771a833ee839727404
parent 76f31606376180ca88efa12be341dbb14fb06fdf

咋一看,这40位的乱码挺吓人的,但是你了解它的作用就不会被吓到了。 这是object name,是作为你每次提交的信息标识。这是用SHA1加密hash函数根据你的对象的内容算出来的。Git的一些优点:
Git通过简单比较object name就可以快速的确定这两个对象是否相等。
因为object name在每个repository里都是以相同的方法来计算的,所以在不同的两个repository的同一个内容都会被存储在相同的object name下。
Git可以在读取对象的时候检查错误,检查该对象的名称是否还是它的内容的SHA1 hash。

The Objects
每个对象都由三个东东组成:type,size,content。size当然是content的size了,而content却是依赖于这个对象是个什么type了。而type又有四类:“blob”,”tree”, “commit”, “tag”。
blob - 通常被用来存储文件数据,一般是个文件。
tree - 基本上像个目录结构,比如x / yyy
commit - 它指向一个单独的tree,它标记了一个时刻的项目的面貌,就像给这个project照了个艳照一样。它包含那个时刻的元信息,例如时间戳,改变本次提交的作者,前一个提交的指向标等等。
tag - 专门用来指定某些特殊行为的commits。

和svn的不同是?
传统的SCM系统,比如svn,CVS, perforce, Mercuial等,它们存储的都是本次和下次提交的differents。 而Git不同,这是关键的一点。Git就像一个狗仔队的记者一样,把你项目的所有文件以及每次提交时候的tree 结构都拍了个照, 把这个照片存储下来而已。这点对于理解Git来说,相当重要!

Blob Object
刚才说了,这是指文件数据。
git show 命令可以帮助你看type,具体用法可以参照(http://www.kernel.org/pub/software/scm/git/docs/git-show.html)例如:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 git show
commit cf70117f3cc497fba42890171d29ae404061d25f
Author: Alex <[email protected]>
Date: Sun Dec 7 05:55:46 2008 +0800


modify .gitignore

diff --git a/.gitignore b/.gitignore
index deb5b7c..77573ab 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,2 @@
-log/
+/log
.DS_Store


因为blob是由它的数据决定的实体,如果在目录tree里有两个内容相同的blob,那么它们就会共享同一个blob对象。该对象是完全独立于它在目录tree里的位置的,并且你重命名文件的话也不会改变与这个文件相关联的那个blob对象。

Tree Object
这是个简单的对象,它就是指向一堆blob对象和其他的tree目录。它一般表现的是目录和子目录的内容。你用git ls-tree命令可以把tree 对象看的更详细。例如:
git ls-tree cf701
100644 blob 77573ab40c4f010c0e050f1bbff2ad3ab78f1428 .gitignore
100644 blob f8cd794a2d06e6c409737d5bcd39a20abb62db26 README
100644 blob 3bb0e8592a41ae3185ee32266c860714980dbed7 Rakefile
040000 tree ee99c7a2e9842acbab05fcd02ec510ea917218b2 app
040000 tree 0aff84690f37df01a7cac96529657222cbeeebf6 config
040000 tree 92aaeee021e9c7da661bac141ab55c17a3ae79ee db
040000 tree 0269300738b048a5cc34769d1436d9f228499018 doc
040000 tree 1e384157d8bc3cd7aab5b0b5605e9a78d0612426 features
040000 tree 9f87e761776449b7d43ea112573f8ae27faaf826 lib
040000 tree 4875662d9a7e07a7b95b68958e518fdd65235124 public
040000 tree 11741373949f86364c10af17db764d1af3bc048f script
040000 tree 20433690c23ae3a299b5a02147f68fdf94820370 spec
040000 tree 646d8b1458d23ba0ec396ef5748205977b023f3f stories
040000 tree cc48cdef8eba2b5333c6a01d5795bb6efb43182f test
040000 tree e85767f4716fd20a53d7d6455972c6ec6571c833 vendor

cf701.。。是上一个tree对象,是整个app的tree对象,熟悉Rails的人就可以看得出来这个tree目录。

请注意这些文件到mode为644或755,以保证git执行时候有足够到权限。

Commit Object
你可以用git show或git log加–pretty=raw参数来检查你的提交,例如:1
2
3
4
5
6
7
8 git show -s --pretty=raw cf701
commit cf70117f3cc497fba42890171d29ae404061d25f
tree 6ccb7edcdb14205861feb263a7607b8921c75fa3
parent 3e794f3e4dcb8d362bfb11464c849070bd04a35f
author Alex <[email protected]> 1228600546 +0800
committer Alex <[email protected]> 1228600546 +0800

modify .gitignore


看见没有?
有个parent, 这基本上是每个commit都有的,但是当一个commit没有parent的时候,那么就叫root commit,这是初始化工程第一次提交才有的。每个project必然会有一个root commit,有的有多个root commit,不过很少见(project 套project ?)。

author是这次提交的人。
committer实际创建这次提交的人,它和author有可能不同,例如,我写的代码发email给你,你帮我commit的,但实际author并不是你,对不?
comment是对这次提交的描述。这里没有我不知道为什么 。。。

Tag Object
到这里可以看到如何创建和验证一个object对象。
http://www.kernel.org/pub/software/scm/git/docs/git-tag.html


Git Directory and Working Directory

1. The Git Directory
git directory是存储你工程所有Git历史和元信息的目录。包括所有的对象,所有到不同分支的指向。
每个project仅有一个git directory。这个目录就是你工程根目录下的那个.git。例如:1
2
3
4 cd .git
~/work/mars/.git&gt;ls
COMMIT_EDITMSG branches description index logs refs
HEAD config hooks info objects


2.The Working Directory
工作目录自然就是你当前检出文件的目录了,大白话就是项目的目录,你要在这里面工作的。。。

The Git Index

如果把你当前修改过的这些文件比作是你从你的部队你精选出来的突击队的话,把commit比作深入敌后的话,那么这个git index就是你这支突击队在深入敌后之前的排练场,你需要在这里点兵,看看有没有怕死的逃兵漏掉。因为你要提交的只是你修改的文件,而不是全部文件,所有你只需要有一个修改文件集合就可以了。
用这个命令来看:1
2
3 git status
# On branch master
nothing to commit (working directory clean)


我距离上次提交没有改动过任何文件,自然不会出现什么文件列表了。我无耻的抄个例子:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 $git status
# On branch master
# Your branch is behind 'origin/master' by 11 commits, and can be fast-forwarded.
#
# Changes to be committed:
# (use "git reset HEAD &lt;file&gt;..." to unstage)
#
# modified: daemon.c
#
# Changed but not updated:
# (use "git add &lt;file&gt;..." to update what will be committed)
#
# modified: grep.c
# modified: grep.h
#
# Untracked files:
# (use "git add &lt;file&gt;..." to include in what will be committed)
#
# blametree
# blametree-init
# git-gui/git-citool


用过svn的人也很容易懂把。

Setup and Initialization

安装就不说了,用mac或linux的人都会安装,用windows的人我只能无耻的抄一句别人的话来骂你:
who fucking need to run a separate software just for using git。

Git Config1
2
3
4
5
6
7
8 $ git config --global user.name "Alex Zhang"
$ git config --global user.email “[email protected]"
然后你
vi ~/.gitconfig
就会看到:
[user]
name = Alex Zhang
email = [email protected]


如果你想为指定的项目用别的git 配置,那么你可以使用git config命令来修改你的信息,不需要加--global参数。

Getting a Git Repository
那么现在我需要一个Git仓库了。怎么办?

两个办法:
克隆一个已经存在的。
初始化一个新的。

如果你要clone的话,你需要知道一个project的git url。git操作跨越多个协议,例如:
git url : git clone git://git.kernel.org/pub/scm/git/git.git
或者
http : git clone http://www.kernel.org/pub/scm/git/git.git

请记住git:// 协议是更快的。但是当你有防火墙或其他原因的时候,还得用http。

Initializing a New Repository
假如你有个工程叫project,那么:1
2
3
4 cd project
git init
则会输出:
Initialized empty Git repository in .git/


你可以去这里看git的视频:
http://www.gitcasts.com/

Normal Workflow
一般的工作流程就是:
1.增加一些新的文件
$ git add file1 file2 file3 把新文件加到git index里。
2. git diff –cached
你可能需要显示你刚add到git index里的那些改变。如果没有--cached,那么就不会显示刚add到index的改变。 你也可以用git status来看。
git commit 你可能需要提交了。
git commit -a 把新近的改变加到index里顺便提交。
为每次commit写message是最佳实践。
svn或其他的版本控制工具也有add方法,它们只是把新加的文件加到index里来跟踪。但是git的add更加强大, git add,不仅仅是增加新文件到index里,还包括最新的修改。

Basic Branching and Merging
一个单一的git仓库就可以提供多个分支给开发者。创建一个新的分支:1
2
3
4
5 git branch experimental
<pre lang="bash" line="1">
如果你现在运行:
<pre lang="bash" line="1">
git branch


你就会得到一个所有分支的列表。1
2 experimental
* master


experimenta是刚刚创建的那个,那个master是默认的分支。星号标识的是你当前所处的分支。1 git checkout experimental


来选择那个experimental的分支内容。如果修改了一个文件,commit这个改变,并且选择回到master 分支:
(edit file)1
2 $ git commit -a
git branch


输出1
2 * experimental
master
1
2 $ git checkout master
git branch


输出:1
2 experimental
master


然后你这master分支里做了一个不同的改变,然后提交。 那么现在master和experimental这两分支里都有了不同的修改,那么我们来合并这些不同:
git merge experimental
注意,你现在是这master里,把其他分支的不同合并到master里来。
如果没有冲突还好,那么有冲突怎么办?
git diff 呀

查看不同,然后解决冲突,再提交一次。

命令行里输入:
gitk
会出来一个x-windows工具,很挫的,截图给大家看,太难看,github上有个好看的工具介绍,我忘了名字了。

来看看如何解决冲突:1
2
3
4
5 $ git merge next
100% (4/4) done
Auto-merged file.txt
CONFLICT (content): Merge conflict in file.txt
Automatic merge failed; fix conflicts and then commit the result.


merge的时候有冲突,会有警告信息,这可真墨迹。

git status来看冲突的地方:
<<<<<<< HEAD:file.txt
Hello world
=======
Goodbye
>>>>>>> 77976da35a11db4580b80ae27e8d65caf5208086:file.txt
那么你需要编辑文件来解决冲突,然后:1
2 $ git add file.txt
$ git commit


具体可以看gitcasts的视频六:GitCast #6: Branching and Merging

查看历史:
git log 版本号

加各种参数可以让log格式化输入便于阅读。

以上都是简单的情况。 大体命令都去查文档吧。

Distributed Workflows
麻烦的情况怎么处理呢?
Alice有个新的工程,存储这一个git仓库:/home/alice/project, 但是Bob,在同一台机器上也有一个home目录。

Bob:1 $ git clone /home/alice/project myrepo


创建了一个新的project,叫myrepo。
Bob修改了一些文件,然后提交它们:
(edit files)1
2 $ git commit -a
(repeat as necessary)


当他准备好以后,他告诉Alice去更新这些改变从他的项目里:/home/bob/myrepo

那么Alice她是这么做的:1
2 $ cd /home/alice/project
$ git pull /home/bob/myrepo master (此命令,master参数不是必须的)


这个操作把bob的master分支合并到了Alice的当前的master分支里。但是如果此时Alice有自己的改变,有可能会出现冲突。因此pull命令是两个操作:
从远程分支取一些改变。
merge到本地当前分支。

为了避免这种麻烦,我们可以这么做:1 $ git remote add bob /home/bob/myrepo


然后先取回来bob的修改,但是并不去merge1 $ git fetch bob


然后看看日志:1 $ git log -p master..bob/master


然后就会显示bob的所有改变,再检查完这些改变以后,Alice就可以合并这些改变到她的分支里了:1 $ git merge bob/master


这一步也可以这么做:1 $ git pull . remotes/bob/master


之后, Bob更新Alice最新的改变就可以用:1 $ git pull


没有alice的路径是因为,是bob clone的alice的代码。你可以用这个命令查看:1
2 $ git config --get remote.origin.url
/home/alice/project


Git也可以做一份Alice master分支的原始拷贝在“origin/master”下:1 $ git branch -r


输出
origin/master

Public git repositories
使用一个公共的仓库,这是通用的做法,这样可以把一些私有的工作过程清晰的和版本库清晰的分离出来。你只需要在你私人的仓库里完成你的工作,然后把你做的改变提交到你的那个公共仓库里,别的开发者可以从你的公共仓库里更新代码。同样,别的开发者push他的改变到他的公共仓库,你从他的公共仓库获取更新。

you push
your personal repo ——————> your public repo
^ |
| |
| you pull | they pull
| |
| |
| they push V
their public repo <——————- their repo

Pushing changes to a public repository1
2
3 $ git push ssh://yourserver.com/~you/proj.git master:master
或者是
$ git push ssh://yourserver.com/~you/proj.git master


当然你可以设置.git/config:
$ cat >>.git/config <<EOF
[remote "public-repo"]
url = ssh://yourserver.com/~you/proj.git
EOF

这样你就可以使用命令:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 $ git push public-repo master
</pr>

以GitHub为例子, 保存在github上的一个项目, 我查看它的config:
[remote "origin"]
url = [email protected]:blackanger/mars.git
fetch = +refs/heads/*:refs/remotes/origin/*

这样,我每次往github push代码就是用命令:
git push origin master

详细的可以去看 www.gitcasts.com

<em><strong>Git Tag</strong></em>

1.Lightweight Tags
~/work/mars&gt;git tag stable-1 cf701
~/work/mars&gt;git show stable-1
commit cf70117f3cc497fba42890171d29ae404061d25f
Author: Alex &lt;[email protected]&gt;
Date: Sun Dec 7 05:55:46 2008 +0800

modify .gitignore
。。。
使用tag和一个object关联起来。这是创建一个轻量级的tag。

Tag Objects
如果加上-a,-s或-u &lt;key-id&gt;参数,则是创建一个tag对象。
<pre lang="bash" line="1">
$ git tag -a stable-1 1b2e1d63ff

没有评论:

发表评论