Git Community Book
对象名是SHA值
每个对象(object) 包括三个部分:类型,大小和内容。大小就是指内容的大小,内容取决于对象的类型,有四种类型的对象:"blob"、"tree"、 "commit" 和"tag"。
•“blob”用来存储文件数据,通常是一个文件。
·•“tree”有点像一个目录,它管理一些“tree”或是“blob”(就像文件和子目录)
• 一个“commit”只指向一个"tree",它用来标记项目某一个特定时间点的状态。它包括一些关于时间点的元数据,如时间戳、最近一次提交的作者、指向上次提交(commits)的指针等等。
• 一个“tag”是来标记某一个提交(commit) 的方法。
几乎所有的Git功能都是使用这四个简单的对象类型来完成的。它就像是在你本机的文件系统之上构建一个小的文件系统。
SVN是使用“增量文件系统”存储每次提交之间的差异。Git是每次提交的文件的全部内容都会记录下来。
$>tree -L 1
|-- HEAD # 这个git项目当前处在哪个分支里
|-- config # 项目的配置信息,git config命令会改动它
|-- description # 项目的描述信息
|-- hooks/ # 系统默认钩子脚本目录
|-- index # 索引文件
|-- logs/ # 各个refs的历史信息
|-- objects/ # Git本地仓库的所有对象 (commits, trees, blobs, tags)
`-- refs/ # 标识你项目里的每个分支指向了哪个提交(commit)。
工作目录只用来临时保存签出(checkout) 文件的地方,你可以编辑工作目录的文件直到下次提交(commit)为止
Git索引是一个在你的工作目录和项目仓库间的暂存区(staging area). 有了它, 你可以把许多内容的修改一起提交(commit). 如果你创建了一个提交(commit), 那么提交的是当前索引(index)里的内容, 而不是工作目录中的内容.
查看索引:git status 命令
Git安装与初始化
$ git config --global user.name "Scott Chacon"
$ git config --global user.email "[email protected]"
Clone一个仓库
git clone git://git.kernel.org/pub/scm/git/git.git
git clone http://www.kernel.org/pub/scm/git/git.git
初始化一个新的仓库
$ tar xzf project.tar.gz
$ cd project
$ git init
你可以使用 git diff 命令再加上 --cached 参数 ,看看哪些文件将被提交(commit)。
$ git diff --cached
(如果没有--cached参数,git diff 会显示当前你所有已做的但没有加入到索引里的修改.) 你也可以用git status命令来获得当前项目的一个状况
除了用git add 命令,还可以用
$ git commit -a
这会自动把所有内容被修改的文件(不包括新创建的文件)都添加到索引中,并且同时把它们提交。
创建一个新的叫”experimental”的分支:
$ git branch experimental
得到当前仓库中存在的所有分支列表:
$ git branch
切换到”experimental”分支:
$ git checkout experimental
通过下面的命令来合并“experimental”和“master”两个分支:
$ git merge experimental
如有有冲突,输入下面的命令就可以查看当前有哪些文件产生了冲突:
$ git diff
当你编辑了有冲突的文件,解决了冲突后就可以提交了:
$ git commit -a
提交(commit)了合并的内容后就可查看一下:
$ gitk
删除掉“experimental”分支了:
$ git branch -d experimental
git branch -d只能删除那些已经被当前分支的合并的分支. 如果你要强制删除某个分支的话就用git branch –D
合并分支,把branchname分支合并到当前分支:
$ git merge branchname
撒销一个合并
如果你觉得你合并后的状态是一团乱麻,想把当前的修改都放弃,你可以用下面的命令回到合并之前的状态:
$ git reset --hard HEAD
或者你已经把合并后的代码提交,但还是想把它们撒销:
$ git reset --hard ORIG_HEAD
git log命令可以显示所有的提交(commit)
$ git log v2.5.. # commits since (not reachable from) v2.5
$ git log test..master # commits reachable from master but not test
$ git log master..test # commits reachable from test but not master
$ git log master...test # commits reachable from either test or
# master, but not both
$ git log --since="2 weeks ago" # commits from the last 2 weeks
$ git log Makefile # commits that modify Makefile
$ git log fs/ # commits that modify any file under fs/
$ git log -S'foo()' # commits that add or remove any file data
# matching the string 'foo()'
$ git log --no-merges # dont show merge commits
当然你也可以组合上面的命令选项;下面的命令就是找出所有从"v2.5“开 始在fs目录下的所有Makefile的修改
$ git log v2.5.. Makefile fs/
日志统计
$ git log –stat
格式化日志
$ git log --pretty=oneline
$ git log --pretty=short
可以用'--graph'选项来可视化你的提交图(commit graph),就像下面这样:
$ git log --pretty=format:'%h : %s' --graph
你可以用 git diff 来比较项目中任意两个版本的差异。
$ git diff master..test
上面这条命令只显示两个分支间的差异,如果你想找出‘master’,‘test’的共有父分支和'test'分支之间的差异,你用3个‘.'来取代前面的两个'.' 。
$ git diff master...test
通常用git diff来找你当前工作目录和上次提交与本地索引间的差异。
$ git diff
上面的命令会显示在当前的工作目录里的,没有 staged(添加到索引中),且在下次提交时不会被提交的修改。
如果你要看在下次提交时要提交的内容(staged,添加到索引中),你可以运行:
$ git diff --cached
上面的命令会显示你当前的索引和上次提交间的差异;这些内容在不带"-a"参数运行 "git commit"命令时就会被提交。
$ git diff HEAD
上面这条命令会显示你工作目录与上次提交时之间的所有差别,这条命令所显示的 内容都会在执行"git commit -a"命令时被提交。
查看当前的工作目录与另外一个分支的差别,你可以用下面的命令执行:
$ git diff test
这会显示你当前工作目录与另外一个叫'test'分支的差别。你也以加上路径限定符,来只比较某一个文件或目录。
$ git diff HEAD -- ./lib
上面这条命令会显示你当前工作目录下的lib目录与上次提交之间的差别(或者更准确的说是在当前分支)。
如果不是查看每个文件的详细差别,而是统计一下有哪些文件被改动,有多少行被改动,就可以使用‘--stat' 参数。
$>git diff --stat
git pull命令执行两个操作: 它从远程分支(remote branch)抓取修改的内容,然后把它合并进当前的分支。
见《Git Community Book》
用你本地的"master" 分支去更新远程的"master"分支,执行下面的命令:
$ git push ssh://yourserver.com/~you/proj.git master:master
or just
或是:
$ git push ssh://yourserver.com/~you/proj.git master
我们可以用 git tag不带任何参数创建一个标签(tag)指定某个提交(commit):
$ git tag stable-1 1b2e1d63ff
前面这样创建的是一个"轻量级标签",这种分支通常是从来不移动的。
如果你想为一个标签(tag)添加注释,或是为它添加一个签名(sign it cryptographically), 那么我们就需要创建一个"标签对象"
如果有 "-a", "-s" 或是 "-u " 中间的一个命令参数被指定,那么就会创建 一个标签对象,并且需要一个标签消息(tagmessage). 如果没有"-m " 或是 "-F " 这些参数,那么就会启动一个编辑器来让用户输入标签消息(tag message).
在你的 .git/config 或 ~.gitconfig里配好key.
下面是示例:
[user]
signingkey =
你也可以用命令行来配置:
$ git config (--global) user.signingkey
现在你可以直接用"-s" 参数来创“签名的标签”。
$ git tag -s stable-1 1b2e1d63ff
如果没有在配置文件中配GPG key,你可以用"-u“ 参数直接指定。
$ git tag -u
你可以在你的顶层工作目录中添加一个叫".gitignore"的文件,来告诉Git系统要忽略掉哪些文件,下面是文件内容的示例:
# 以'#' 开始的行,被视为注释.
# 忽略掉所有文件名是 foo.txt 的文件.
foo.txt
# 忽略所有生成的 html 文件,
*.html
# foo.html是手工维护的,所以例外.
!foo.html
# 忽略所有.o 和 .a文件.
*.[oa]
你可以点这里gitignore查看一下详细的语法解释. 你也可以把".gitignore" 这个文件放到工作树(working tree)里的其它目录中,这就会在它和它的子目录起忽略(ignore) 指定文件的作用。.gitignor文件同样可以像其它文件一样加到项目仓库里(直接用 git add .gitignore 和 git commit等命令), 这样项目里的其它开发者也能共享同一套忽略文件规则。
如果你想忽略规则只对特定的仓库起作用,你可以把这些忽略规则写到你的仓库下 .git/info/exclude文件中,或是写在Git配置变量core.excludesfile中指定的文件里。有些Git命令也可在命令行参数中指定忽略规则,你可以在这里:gitignore查看详细的用法。
假设你现在基于远程分支"origin",创建一个叫"mywork"的分支。
$ git checkout -b mywork origin
现在我们在这个分支做一些修改,然后生成两个提交(commit).
但是与此同时,有些人也在"origin"分支上做了一些修改并且做了提交了。这就意味着"origin"和"mywork"这两个分支各自"前进"了,它们之间"分叉"了。
在这里,你可以用"pull"命令把"origin"分支上的修改拉下来并且和你的修改合并; 结果看起来就像一个新的"合并的提交"(merge commit):
但是,如果你想让"mywork"分支历史看起来像没有经过任何合并一样,你也许可以用git rebase:
$ git checkout mywork
$ git rebase origin
这些命令会把你的"mywork"分支里的每个提交(commit)取消掉,并且把它们临时保存为补丁(patch)(这些补丁放到".git/rebase"目录中),然后把"mywork"分支更新 到最新的"origin"分支,最后把保存的这些补丁应用到"mywork"分支上。
当'mywork'分支更新之后,它会指向这些新创建的提交(commit),而那些老的提交会被丢弃。
若出现冲突,解决后:
$ git rebase --continue
在任何时候,你可以用--abort参数来终止rebase的行动,并且"mywork" 分支会回到rebase开始前的状态。
$ git rebase --abort
当你正在做一项复杂的工作时, 发现了一个和当前工作不相关但是又很讨厌的bug. 你这时想先修复bug再做手头的工作, 那么就可以用 git stash 来保存当前的工作状态, 等你修复完bug后,执行'反储藏'(unstash)操作就可以回到之前的工作里。
$ git stash "work in progress for foo feature"
上面这条命令会保存你的本地修改到储藏(stash)中, 然后将你的工作目录和索引里的内容全部重置, 回到你当前所在分支的上次提交时的状态.好了, 你现在就可以开始你的修复工作了.
... edit and test ...
$ git commit -a -m "blorpl: typofix"
当你修复完bug后, 你可以用git stash apply来回复到以前的工作状态.
$ git stash apply
你也可多次使用'git stash'命令,每执行一次就会把针对当前修改的‘储藏’(stash)添加到储藏队列中. 用'git stash list'命令可以查看你保存的'储藏'(stashes):
$>git stash list
不用40个字节长的SHA串来表示一个提交(commit)或是其它git对象
你可以使用分支,remote或标签名来代替SHA串名, 它们只是指向某个对象的指针
Git的引用日志(Ref Log)可以让你做一些‘相对'查询操作:
master@{yesterday}
master@{1 month ago}
上面的第一条命令是:'master分支的昨天状态(head)的缩写‘. 注意: 即使在两个有相同master分支指向的仓库上执行这条命令, 但是如果这个两个仓库在不同机器上,那么执行结果也很可能会不一样
这种格式用来表达某点前面的第N个提交(ref).
master@{5}
上面的表达式代表着master前面的第5个提交(ref).
这能告诉你某个提交的第N个直接父提交(parent). 这种格式在合并提交(merge commits)时特别有用, 这样就可以使提交对象(commit object)有多于一个直接父对象(direct parent).
译者注:假设master是由a和b两个分支合并的,那么 master^1是指分支a, master^2就是指分支b.
master^2
波浪号用来标识一个提交对象(commit object)的第N级嫡(祖)父对象(Nth grandparent). 例如:
master~2
就代表master所指向的提交对象的第一个父对象的第一个父对象(译者:你可以理解成是嫡系爷爷:)). 它和下面的这个表达式是等价的:
master^^
你也可以把这些‘标识符'(spec)叠加起来,下面这个3个表达式都是指向同一个提交(commit):
master^^^^^^
master~3^~2
master~6
如果大家对第一章Git对象模型还有印象的话, 就记得提交对象(commit object)是指向一个树对象(tree object)的. 假如你要得到一个提交对象(commit object)指向的树对象(tree object)的sha串名, 你就可以在‘树名'的后面加上'{tree}'来得到它:
master^{tree}
如果你要某个二次制对象(blob)的sha串名,你可以在'树名'(treeish)后添加二次制对象(blob)对应的文件路径来得到它.
master:/path/to/file
最后,你可以用".."来指两个提交(commit)之间的区间。下面的命令会给出你在"7b593b5" 和"51bea1"之间除了"7b593b5外"的所有提交(commit)(注意:51bea1是最近的提交).
7b593b5..51bea1
这会包括所有从7b593b开始的提交(commit). 译者注:相当于7b593b..HEAD
7b593b..
在Git中'追踪分支'是用与联系本地分支和远程分支的。如果你在'追踪分支'(Tracking Branches)上执行推送(push)或拉取(pull)时,它会自动推送(push)或拉取(pull)到关联的远程分支上。
如果你经常要从远程仓库里拉取(pull)分支到本地,并且不想很麻烦的使用"git pull "这种格式; 那么就应当使用'追踪分支'(Tracking Branches)。
‘git clone‘命令会自动在本地建立一个'master'分支,它是'origin/master'的‘追踪分支’. 而'origin/master'就是被克隆(clone)仓库的'master'分支
你可以在使用'git branch'命令时加上'--track'参数, 来手动创建一个'追踪分支'.
git branch --track experimental origin/experimental
当你运行下命令时:
$ git pull experimental
它会自动从'origin'抓取(fetch)内容,再把远程的'origin/experimental'分支合并进(merge)本地的'experimental'分支.
当要把修改推送(push)到origin时, 它会将你本地的'experimental'分支中的修改推送到origin的'experimental'分支里,而无需指定它(origin)
用git grep 命令查找Git库里面的某段文字是很方便的. 当然, 你也可以用unix下的'grep'命令进行搜索, 但是'git grep'命令能让你不用签出(checkout)历史文件, 就能查找它们.
例如, 你要看 git.git 这个仓库里每个使用'xmmap'函数的地方, 你可以运行下面的命令:
$ git grep xmmap
如果你要显示行号, 你可以添加'-n'选项:
$>git grep -n xmmap
如果我们想只显示文件名, 我们可以使用'--name-onley'选项:
$>git grep --name-only xmmap
我们可以用'-c'选项,可以查看每个文件里有多少行匹配内容(line matches):
$>git grep -c xmmap
现在, 如果我们要查找git仓库里某个特定版本里的内容, 我们可以像下面一样在命令行末尾加上标签名(tag reference):
$ git grep xmmap v1.5.0
我们也可以组合一些搜索条件, 下面的命令就是查找我们在仓库的哪个地方定义了'SORT_DIRENT'.
$ git grep -e '#define' --and -e SORT_DIRENT
builtin-fsck.c:#define SORT_DIRENT 0
builtin-fsck.c:#define SORT_DIRENT 1
我不但可以进行“与"(both)条件搜索操作,也可以进行"或"(either)条件搜索操作.
$ git grep --all-match -e '#define' -e SORT_DIRENT
我们也可以查找出符合一个条件(term)且符合两个条件(terms)之一的文件行。例如我们要找出名字中含有'PATH'或是'MAX'的常量定义:
$ git grep -e '#define' --and \( -e PATH -e MAX \)
如果你现在的工作目录(work tree)里搞的一团乱麻, 但是你现在还没有把它们提交; 你可以通过下面的命令, 让工作目录回到上次提交时的状态(last committed state):
$ git reset --hard HEAD
这条件命令会把你工作目录中所有未提交的内容清空(当然这不包括未置于版控制下的文件 untracked files). 从另一种角度来说, 这会让"git diff" 和"git diff --cached"命令的显示法都变为空.
如果你只是要恢复一个文件,如"hello.rb", 你就要使用 git checkout
$ git checkout -- hello.rb
创建新提交来修复错误:
$ git revert HEAD
下面这条命令就是撤消“上上次”(next-to-last)的提交:
$ git revert HEAD^
修改提交来修复错误:
如果你刚刚做了某个提交(commit), 但是你又想马上修改这个提交; git commit 现在支持一个叫--amend的参数,它能让你修改刚才的这个提交(HEAD commit). 这项机制能让你在代码发布前,添加一些新的文件或是修改你的提交注释(commit message).
如果你在老提交(older commit)里发现一个错误, 但是现在还没有发布到代码服务器上。你可以使用 git rebase命令的交互模式, "git rebase -i"会提示你在编辑中做相关的修改。这样其实就是让你在rebase的过程来修改提交
在大的仓库中, git靠压缩历史信息来节约磁盘和内存空间:(比较耗时)
$ git gc
保持可靠性: git fsck 运行一些仓库的一致性检查, 如果有任何问题就会报告. 这项操作也有点耗时, 通常报的警告就是“悬空对象"(dangling objects).
$ git fsck
假设你个人的仓库在目录 ~/proj. 我们先克隆一个新的“裸仓库“,并且创建一个标志文件告诉git-daemon这是个公共仓库.
$ git clone --bare ~/proj proj.git
$ touch proj.git/git-daemon-export-ok
上面的命令创建了一个proj.git目录, 这个目录里有一个“裸git仓库" -- 即只有'.git'目录里的内容,没有任何签出(checkedout)的文件.
在偶尔的情况下,你可能会想要保留那些与你的代码没有共同祖先的分支。例如在这些分支上保留生成的文档或者其他一些东西。如果你需要创建一个不使用当前代码库作为父提交的分支,你可以用如下的方法创建一个空分支:
git symbolic-ref HEAD refs/heads/newbranch
rm .git/index
git clean -fdx
git add your files
git commit -m 'Initial commit'