1.0 Git基础
Git 更像是把数据看作是对小型文件系统的一组快照。 每次你提交更新,或在 Git 中保存项目状态时,它主要对当时的全部文件制作一个快照并保存这个快照的索引。 为了高效,如果文件没有修改,Git 不再重新存储该文件,而是只保留一个链接指向之前存储的文件。 Git 对待数据更像是一个快照流
。如下图所示
Git 数据库中保存的信息都是以文件内容的哈希值来索引,而不是文件名。SHA-1 散列(hash,哈希),这是一个由 40 个十六进制字符(0-9 和 a-f)组成字符串
三种状态
文件在git中有3种状态,已提交(committed),已修改(modified),已暂存(staged),对应了git中的三个工作区域概念:
git仓库,工作区域,暂存区域。见图:
Git 仓库目录
是 Git 用来保存项目的元数据和对象数据库的地方。 这是 Git 中最重要的部分,从其它计算机克隆仓库时,拷贝的就是这里的数据。
工作目录
是对项目的某个版本独立提取出来的内容。 这些从 Git 仓库的压缩数据库中提取出来的文件,放在磁盘上供你使用或修改。
暂存区域
是一个文件,保存了下次将提交的文件列表信息,一般在 Git 仓库目录中。 有时候也被称作“索引”,不过一般说法还是叫暂存区域。
基本的 Git 工作流程如下:
1.在工作目录中修改文件。
2.暂存文件,将文件的快照放入暂存区域。
3.提交更新,找到暂存区域的文件,将快照永久性存储到 Git 仓库目录。
忽略文件
在master目录下编辑.gitignore
文件可以添加需要忽略的文件,.gitignore
文件规范为:
所有空行或者以
#
开头的行都会被 Git 忽略。可以使用标准的 glob 模式匹配。
匹配模式可以以(
/
)开头防止递归。匹配模式可以以(
/
)结尾指定目录。要忽略指定模式以外的文件或目录,可以在模式前加上惊叹号(
!
)取反。
所谓的 glob 模式是指 shell 所使用的简化了的正则表达式。 星号(*
)匹配零个或多个任意字符;[abc]
匹配任何一个列在方括号中的字符(这个例子要么匹配一个 a,要么匹配一个 b,要么匹配一个 c);问号(?
)只匹配一个任意字符;如果在方括号中使用短划线分隔两个字符,表示所有在这两个字符范围内的都可以匹配(比如 [0-9]
表示匹配所有 0 到 9 的数字)。 使用两个星号(*
) 表示匹配任意中间目录,比如a/**/z
可以匹配 a/z
, a/b/z
或 a/b/c/z
等。
例子:
# no .a files
*.a
# but do track lib.a, even though you're ignoring .a files above
!lib.a
# only ignore the TODO file in the current directory, not subdir/TODO
/TODO
# ignore all files in the build/ directory
build/
# ignore doc/notes.txt, but not doc/server/arch.txt
doc/*.txt
# ignore all .pdf files in the doc/ directory
doc/**/*.pdf</pre>
git diff
-
git diff --color-words
或者git diff --word-diff
可以显示具体哪些词发生了变化。 -
git diff
: Changes in the working tree not yet staged for the next commit. 显示还没有staged的部分的变化。add 之后就放入stage了。 -
git diff --cached
: Changes between the index and your last commit; what you would be committing if you run "git commit" without "-a" option. staged的部分与上次commit之间的差异。如果运行git commit
会提交这部分。 -
git diff HEAD
: Changes in the working tree since your last commit; what you would be committing if you run "git commit -a" . 当前工作路径下与上次COMMIT的差异,如果运行git commit -a
会提交这些更改。
git add
git add 执行之后会对修改过的文件照一个快照(snapshot), 然后把内容stage,并建立索引,以便commit的时候提交。
git commit
- git commit -a: 相当于
git add; git commit -m
移除文件 git rm
git rm 命令已跟踪文件清单中移除并连带从工作目录中删除指定的文件。
如果删除之前修改过并且已经放到暂存区域的话,则必须要用强制删除选项 -f
(译注:即 force 的首字母)。 这是一种安全特性,用于防止误删还没有添加到快照的数据,这样的数据不能被 Git 恢复。
另外一种情况是,我们想把文件从 Git 仓库中删除(亦即从暂存区域移除),但仍然希望保留在当前工作目录中。 换句话说,你想让文件保留在磁盘,但是并不想让 Git 继续跟踪。 当你忘记添加 .gitignore
文件,不小心把一个很大的日志文件或一堆 .a
这样的编译生成文件添加到暂存区时,这一做法尤其有用。 为达到这一目的,使用 --cached
选项:
移除暂存区文件:
git rm --cached README
git rm
命令后面可以列出文件或者目录的名字,也可以使用 glob
模式。 比方说:
git rm log/\*.log
**注意到星号 *
之前的反斜杠 \
, 因为 Git 有它自己的文件模式扩展匹配方式,所以我们不用 shell 来帮忙展开。
**
移动文件
命令git mv file_from file_to
相当于以下三条命令:
mv file_from file_to
git rm file_from
git add file_to
有时候用其他工具批处理改名的话,要记得在提交前删除老的文件名,再添加新的文件名。
git log
-
git log
: 查看各次commit的信息,最新的在最上面。 -
git log --oneline
可以查看简化版本,在一行内显示 -
git log --stat
列出了详细的信息。 -
git log --patch|-p
更为详细的信息,还可以添加-2
参数以获得最近2个commit的对比。 -
git log --graph
以图形形式显示 -
git log --pretty
,后面可以跟oneline,format(后跟指定格式),例如:
git log --pretty=oneline
,git log --pretty=format:"%h %s"
选项 | 说明 |
---|---|
%H |
提交对象(commit)的完整哈希字串 |
%h |
提交对象的简短哈希字串 |
%T |
树对象(tree)的完整哈希字串 |
%t |
树对象的简短哈希字串 |
%P |
父对象(parent)的完整哈希字串 |
%p |
父对象的简短哈希字串 |
%an |
作者(author)的名字 |
%ae |
作者的电子邮件地址 |
%ad |
作者修订日期(可以用 --date= 选项定制格式) |
%ar |
作者修订日期,按多久以前的方式显示 |
%cn |
提交者(committer)的名字 |
%ce |
提交者的电子邮件地址 |
%cd |
提交日期 |
%cr |
提交日期,按多久以前的方式显示 |
%s |
提交说明 |
可以接受的格式为:
选项 | 说明 |
---|---|
%H |
提交对象(commit)的完整哈希字串 |
%h |
提交对象的简短哈希字串 |
%T |
树对象(tree)的完整哈希字串 |
%t |
树对象的简短哈希字串 |
%P |
父对象(parent)的完整哈希字串 |
%p |
父对象的简短哈希字串 |
%an |
作者(author)的名字 |
%ae |
作者的电子邮件地址 |
%ad |
作者修订日期(可以用 --date= 选项定制格式) |
%ar |
作者修订日期,按多久以前的方式显示 |
%cn |
提交者(committer)的名字 |
%ce |
提交者的电子邮件地址 |
%cd |
提交日期 |
%cr |
提交日期,按多久以前的方式显示 |
%s |
提交说明 |
当format与graph命令结合时很有用
git log --pretty=format:"%h %s" --graph
撤销操作
重新提交
有时候我们提交完了才发现漏掉了几个文件没有添加,或者提交信息写错了。 此时,可以运行带有 --amend
选项的提交命令尝试重新提交:例如,你提交后发现忘记了暂存某些需要的修改,可以像下面这样操作:
git commit -m 'initial commit'
$ git add forgotten_file
$ git commit --amend
最终你只会有一个提交 - 第二次提交将代替第一次提交的结果
取消暂存的文件
例如,你已经修改了两个文件并且想要将它们作为两次独立的修改提交,但是却意外地输入了 git add *
暂存了它们两个。 如何只取消暂存两个中的一个呢?
git reset HEAD filename
远程仓库使用
git remote -v
显示远程仓库列表
git fetch [remote-name]
将远程仓库内容拉取到本地,它并不会自动合并或修改你当前的工作。 当准备好时你必须手动将其合并入你的工作。
git pull [remote-name]
将远程仓库内容拉取到本地,并进行合并到当前分支。
git push [remote-name] [branch-name]
将分支推送到远程
只有当你有所克隆服务器的写入权限,并且之前没有人推送过时,这条命令才能生效。 当你和其他人在同一时间克隆,他们先推送到上游然后你再推送到上游,你的推送就会毫无疑问地被拒绝。 你必须先将他们的工作拉取下来并将其合并进你的工作后才能推送。
git remote show [remote-name]
会显示远程的更多信息,同样会列出远程仓库的 URL 与跟踪分支的信息。
打标签
使用打标签的方式来记录重要的发布节点
git tag
列出标签
git使用两种标签,轻量(lightweight)标签和附注(annotated)标签,推荐使用annotated标签,信息比较多。
git tag -a v1.54 -m "my version v1.54"
打annotated标签
git shown v1.54
显示tag的信息
后期打标签
git tag -a v1.2 9fceb02
只需要后面跟部分校验码就可以了。
共享标签
git push
命令不会将标签传送到远程,可以使用下面的命令显示推送某个标签
git push [remote-name] [tag-name]
如果要推送多个标签,使用:
git push [remote-name] --tags
别名
git config --global alias.unstage 'reset HEAD --'
git config --global alias.last 'log -1 HEAD'
可以看出,Git 只是简单地将别名替换为对应的命令。 然而,你可能想要执行外部命令,而不是一个 Git 子命令。 如果是那样的话,可以在命令前面加入 !
符号。 如果你自己要写一些与 Git 仓库协作的工具的话,那会很有用。
分支
暂存操作会为每一个文件计算校验和(使用我们在 起步 中提到的 SHA-1 哈希算法),然后会把当前版本的文件快照保存到 Git 仓库中(Git 使用 blob 对象来保存它们),最终将校验和加入到暂存区域等待提交。
当使用 git commit
进行提交操作时,Git 会先计算每一个子目录(本例中只有项目根目录)的校验和,然后在 Git 仓库中这些校验和保存为树对象。随后,Git 便会创建一个提交对象,它除了包含上面提到的那些信息外,还包含指向这个树对象(项目根目录)的指针。如此一来,Git 就可以在需要的时候重现此次保存的快照。
看图
在一次提交后,包含3类文件,提交对象commit,目录的树对象tree,文件的blob对象。tree和blob是git对当前文件状态的一次快照snapshot。因此,每次提交产生的commit指向了一次snapshot。
在多次提交后,后来提交的commit对象会指向上一次提交的父对象。分支就是一个指针,指向某次提交,另外git中还有一个默认的指针,HEAD,指向当前分支。关系如下:
当使用
git checkout bname
后,HEAD指针就指向bname了。
分支切换会改变你工作目录中的文件
在切换分支时,一定要注意你工作目录里的文件会被改变。 如果是切换到一个较旧的分支,你的工作目录会恢复到该分支最后一次提交时的样子。 如果 Git 不能干净利落地完成这个任务,它将禁止切换分支。
git log --oneline --decorate --graph --all
输出你的提交历史、各个分支的指向以及项目的分支分叉情况。
git branch
-
git branch bname
: 创建分支bname -
git branch
列出所有分支,星号表示当前所在分支 -
git checkout bname
: 切换到bname分支 -
git merge bname
: 将bname合并到当前分支(可能是主分支)。如果存在矛盾(conflict),git会提示。使用git diff
可以看到,编辑矛盾的文件(矛盾的地方会标记出来),然后git commit -a
即可完成merge. -
gitk
,可以以图形形式查看 -
git branch -d bname
: 删除bname分支。
远程分支
远程跟踪分支是远程分支状态的引用。 它们是你不能移动的本地引用,当你做任何网络通信操作时,它们会自动移动。 远程跟踪分支像是你上次连接到远程仓库时,那些分支所处状态的书签。
它们以 (remote)/(branch)
形式命名。,本地无法修改,一般在进行网络操作时自动修改。
像下图这样
本地的master和远程的master分支不同,如果要将本地master推送到远程是不行的,需要首先进行合并:
首先抓取
git fetch origin
然后合并
git merge origin\master
然后将master分支推送上去
git push origin master
具体参见第5部分,分布式git。
git clone
-
git clone remote local
: 将repository remote 克隆到 local。local克隆的是remote的当前分支,即如果remote处于branch状态,那么local克隆的也是branch状态,并且不包含主分支。 - 当在local上完成改动并commit之后,需要请求remote一方
pull
local一方的改动。
git pull local master
: 完成了两个动作,首先把local中的master 分支fetch到了,然后将改动merge到当前branch中。 - 在执行pull之前,要将当前branch的内容commit。
- 一个更保险的做法是先手动fetch,比较一下不同再决定是否merge(可以再pull一下或者merge:
git merge FETCH_HEAD
)。使用下面的命令:
git fetch local
,fetch得到的节点会以FETCH_HEAD标记。
git log -p HEAD..FETCH_HEAD
: 其中"HEAD..FETCH_HEAD" means "show everything that is reachable from the FETCH_HEAD but exclude anything that is reachable from HEAD"显示只有FETCH_HEAD有而HEAD没有的内容。
也可以使用HEAD...FETCH_HEAD
标记,表示show everything that is reachable from either one, but exclude anything that is reachable from both of them,只显示差异不显示相同的。
以上命令都可以使用gitk进行图形查看。