Why Git?
- 分布式,与服务器断开后仍然可以commit和查看历史等
- 比SVN分支切换更快
- 比SVN节省存储空间
理由不够充分?好吧,I like it.
Git术语
- working directory: 工作区
- staging area: 暂存区
- git directory/repository: 仓库
- remote branch: 远程分支,是对远程仓库中的分支的索引,它们是一些无法移动的本地分支。
Git基础
- Git存储
Git 和其他版本控制系统的主要差别在于,Git 只关心文件数据的整体是否发生变化,而大多数其他系统则只关心文件内容的具体差异。这类系统(CVS,Subversion,Perforce,Bazaar 等等)每次记录有哪些文件作了更新,以及都更新了哪些行的什么内容。Git 保存的不是文件差异或者变化量,而只是一系列文件快照。 - 文件的3种状态
对于任何一个文件,在 Git 内都只有三种状态:已提交(committed),已修改(modified)和已暂存(staged)。已提交表示该文件已经被安全地保存在本地仓库中了;已修改表示修改了某个文件,但还没有提交保存;已暂存表示把已修改的文件放在下次提交时要保存的清单中。
由此我们看到 Git 管理项目时,文件流转的三个工作区域:Git 的工作目录,暂存区域,以及本地仓库。
上图:工作目录,暂存区域,以及本地仓库
所谓的暂存区域只不过是个简单的文件,一般都放在 Git 目录中。有时候人们会把这个文件叫做索引文件,不过标准说法还是叫暂存区域。
基本的 Git 工作流程如下:
- 在工作目录中修改某些文件。
- 对修改后的文件进行快照,然后保存到暂存区域。
- 提交更新,将保存在暂存区域的文件快照永久转储到 Git 目录中。
- 配置文件
/etc/gitconfig
文件:系统中对所有用户都普遍适用的配置。若使用git config
时用--system
选项,读写的就是这个文件。~/.gitconfig
文件:用户目录下的配置文件只适用于该用户。若使用git config
时用--global
选项,读写的就是这个文件。- 当前项目的 git 目录中的配置文件(也就是工作目录中的
.git/config
文件):这里的配置仅仅针对当前项目有效。每一个级别的配置都会覆盖上层的相同配置,所以.git/config
里的配置会覆盖/etc/gitconfig
中的同名变量。
- 配置文件-忽略某些文件(.gitignore)
工作目录及其子目录下每个目录都可以维护一个.gitignore文件,用于将某些文件排除在版本控制外,如eclipse项目的.project文件
- 所有空行或者以注释符号
#
开头的行都会被 Git 忽略。 - 可以使用标准的 glob 模式匹配。
- 匹配模式最后跟反斜杠(
/
)说明要忽略的是目录。 - 要忽略指定模式以外的文件或目录,可以在模式前加上惊叹号(
!
)取反。
所谓的 glob 模式是指 shell 所使用的简化了的正则表达式。星号(
*
)匹配零个或多个任意字符;[abc]
匹配任何一个列在方括号中的字符(这个例子要么匹配一个 a,要么匹配一个 b,要么匹配一个 c);问号(?
)只匹配一个任意字符;如果在方括号中使用短划线分隔两个字符,表示所有在这两个字符范围内的都可以匹配(比如[0-9]
表示匹配所有 0 到 9 的数字)。我们再看一个
.gitignore
文件的例子:
# 此为注释 – 将被 Git 忽略 # 忽略所有 .a 结尾的文件 *.a # 但 lib.a 除外 !lib.a # 仅仅忽略项目根目录下的 TODO 文件,不包括 subdir/TODO /TODO # 忽略 build/ 目录下的所有文件 build/ # 会忽略 doc/notes.txt 但不包括 doc/server/arch.txt
- 所有空行或者以注释符号
- 分支(branch)
在很多版本控制系统中,这是个昂贵的过程,常常需要创建一个源代码目录的完整副本,对大型项目来说会花费很长时间。和许多其他版本控制系统不同,Git 鼓励在工作流程中频繁使用分支与合并,哪怕一天之内进行许多次都没有关系。
工作区保存着一个名为 HEAD 的特别指针,它是一个指向你正在工作中的本地分支的指针(可以将 HEAD 想象为当前分支的别名。)。
使用git branch命令不仅可以查看所有分支列表,还可以看到分支名前带有*就是当前分支,当然使用git status也可以做到。
master是Git中默认创建的一个分支
远程分支(remote branch)是对远程仓库中的分支的索引。它们是一些无法移动的本地分支;只有在 Git 进行网络交互时才会更新。
远程分支就像是书签,提醒着你上次连接远程仓库时上面各分支的位置。我们用(远程仓库名)/(分支名)
这样的形式表示远程分支。
从远程分支checkout
出来的本地分支,称为 跟踪分支 (tracking branch)。跟踪分支是一种和某个远程分支有直接联系的本地分支。在跟踪分支里输入git push
,Git 会自行推断应该向哪个服务器的哪个分支推送数据。同样,在这些分支里运行git pull
会获取所有远程索引,并把它们的数据都合并到本地分支中来。
在克隆仓库时,Git 通常会自动创建一个名为master
的分支来跟踪origin/master
。这正是git push
和git pull
一开始就能正常工作的原因。
Git工作流(workflow)
// TODO
Git常用命令
- git help
查询子命令或子命令帮助信息
git help:列出所有子命令
git help config:列出config子命令的帮助信息 - git config
管理配置信息
git config --system user.name "Brody Cai"
git config --global user.email brody.cai@mycompany.com
git config --list
git config user.name
git help config - git add
这是个多功能命令,根据目标文件的状态不同,此命令的效果也不同:可以用它开始跟踪新文件,或者把已跟踪的文件放到暂存区,还能用于合并时把有冲突的文件标记为已解决状态等 - git status
该命令仅仅列出了修改过的文件(包括已暂存和未暂存的更新),如果要查看具体修改了什么地方,可以用git diff
命令 - git diff
git diff: 此命令比较的是工作目录中当前文件和暂存区域快照之间的差异,也就是修改之后还没有暂存起来的变化内容
git diff --staged(或git diff --cached): 此命令比较的是已暂存起来的文件和上次提交时的快照之间的差异
请注意,单单git diff
不过是显示还没有暂存起来的改动,而不是这次工作和上次提交之间的差异。所以有时候你一下子暂存了所有更新过的文件后,运行git diff
后却什么也没有,就是这个原因。 - git commit
git commit -m "your commit comments": 提交时记录的是放在暂存区域的快照,任何还未暂存的仍然保持已修改状态。也就是说只有暂存区的变更(staged)会被提交,工作区的变更(modified)不提交
git commit -a -m "commit all": 跳过暂存区自动把所有已经跟踪过的文件暂存起来一并提交。-a参数会把暂存区和工作区的变更一并提交,相当于在工作区自动执行了git add命令。需要注意的是“已经跟踪过的文件”意思是指新建的文件从未被add到暂存区的文件不会自动提交。 - git rm
移除文件
git rm readme.txt: 将暂存区的指定文件删除,同时删除对应的工作区文件。最后提交的时候,该文件就不再纳入版本管理了。如果删除之前修改过并且已经放到暂存区域的话,则必须要用强制删除选项-f
(译注:即 force 的首字母),以防误删除文件后丢失修改的内容。
git rm --cached readme.txt: 把指定文件从 Git 仓库中删除(亦即从暂存区域移除),但指定文件保留在当前工作目录中。如:不小心把eclipse的.project文件提交到了Git仓库中,使用该命令即可将.project从仓库中移除,同时工作区的.project仍保留 - git mv
移动/重命名工作区和暂存区文件
git mv file_from file_to - git log
查看提交历史git log -p -2:
-p
选项展开显示每次提交的内容差异,用-2
则仅显示最近的两次更新
git log --stat: 仅显示简要的增改行数统计
git log --pretty=oneline: 将每个提交放在一行显示,这在提交数很大时非常有用。pretty可选值还有short
,full
和fuller
git log --pretty=format:"%h - %an, %ar : %s": 定制要显示的记录格式,这样的输出便于后期编程提取分析
如下列出了常用的格式占位符写法及其代表的意义。选项 说明 %H 提交对象(commit)的完整哈希字串 %h 提交对象的简短哈希字串 %T 树对象(tree)的完整哈希字串 %t 树对象的简短哈希字串 %P 父对象(parent)的完整哈希字串 %p 父对象的简短哈希字串 %an 作者(author)的名字 %ae 作者的电子邮件地址 %ad 作者修订日期(可以用 -date= 选项定制格式) %ar 作者修订日期,按多久以前的方式显示 %cn 提交者(committer)的名字 %ce 提交者的电子邮件地址 %cd 提交日期 %cr 提交日期,按多久以前的方式显示 %s 提交说明
git log --pretty=format:"%h %s" --graph: 用ASCII 字符串表示的简单图形,形象地展示了每个提交所在的分支及其分化衍合情况
下表列出了git log一些其他常用的选项及其释义
选项 说明 -p 按补丁格式显示每个更新之间的差异。 --stat 显示每次更新的文件修改统计信息。 --shortstat 只显示 --stat 中最后的行数修改添加移除统计。 --name-only 仅在提交信息后显示已修改的文件清单。 --name-status 显示新增、修改、删除的文件清单。 --abbrev-commit 仅显示 SHA-1 的前几个字符,而非所有的 40 个字符。 --relative-date 使用较短的相对时间显示(比如,“2 weeks ago”)。 --graph 显示 ASCII 图形表示的分支合并历史。 --pretty 使用其他格式显示历史提交信息。可用的选项包括 oneline,short,full,fuller 和 format(后跟指定格式)。 -(n) 仅显示最近的 n 条提交 --since, --after 仅显示指定时间之后的提交。 --until, --before 仅显示指定时间之前的提交。 --author 仅显示指定作者相关的提交。 --committer 仅显示指定提交者相关的提交。
- git branch
git branch: 给出当前所有分支的清单
git branch -v: 查看各个分支最后一个提交对象的信息
git branch -r: 查看远程分支
git branch --merged: 查看哪些分支已被并入当前分支(译注:也就是说哪些分支是当前分支的直接上游。)
git branch --no-merged: 查看哪些分支还未被并入当前分支。简单地用 git branch -d
删除未合并分支会提示错误,因为那样做会丢失数据。强制删除需要使用git branch -D。
git branch iss53: 创建一个分支,命名为iss53
git checkout -b iss53: 创建一个分支,命名为iss53,并checkout该分支
此命令相当于执行了如下两个命令:
git branch iss53
git checkout iss53
git checkout -b serverfix origin/serverfix: 在远程分支serverfix的基础上分化出一个新的分支serverfix到本地仓库
git checkout --track origin/serverfix: 效果同上,使用远程分支的名字作为本地分支名
git branch -d branch1: 删除branch1这个分支
- git merge
git checkout master
git merge iss53
checkout master分支,将iss53这个分支merge到master。如遇到冲突,需要手动修改冲突文件,修改后使用git add命令标记为已解决,之后就可以commit提交了
git merge origin/serverfix: 把origin仓库中的远程分支serverfix的内容合并到当前分支 - git push
git push origin serverfix: 取出我在本地的 serverfix 分支,推送到远程仓库的 serverfix 分支中去。与git push origin serverfix:serverfix效果等同
git push origin :serverfix: 删除远程分支serverfix
git push [远程名] [本地分支]:[远程分支]
,如果省略[本地分支]
,那就等于是在说“在这里提取空白然后把它变成[远程分支]
”。 - git rebase
衍合,把在一个分支里提交的改变移到另一个分支里重放一遍
git checkout experiment
git rebase master
它的原理是回到两个分支最近的共同祖先,根据当前分支(也就是要进行衍合的分支experiment
)后续的历次提交对象,生成一系列文件补丁,然后以基底分支(也就是主干分支master
)最后一个提交对象为新的出发点,逐个应用之前准备好的补丁文件,最后会生成一个新的合并提交对象,从而改写experiment
的提交历史,使它成为master
分支的直接下游
git rebase --onto master server client
取出client
分支,找出client
分支和server
分支的共同祖先之后的变化,然后把它们在master
上重演一遍。 现在可以快进(fast forward)master
分支了:git checkout master git merge client
衍和的原则:一旦分支中的提交对象发布到公共仓库,就千万不要对该分支进行衍合操作。
Git FAQ
-
创建新Git项目
- 将已存在项目转换为Git项目
cd existing-project
git init
git add --all
git commit -m"first commit"
git remote add origin http://your_host/your_git_path/your_project.git
git push origin master
- 将本地Git项目推送到远程仓库
cd existing-git-project
git remote add origin http://your_host/your_git_path/your_project.git
git push origin master
显然,除了origin我们可以维护多个远端仓库,取名和地址不同就可以了
- 将已存在项目转换为Git项目
-
克隆项目
git clone git://github.com/schacon/grit.git
git clone git://github.com/schacon/grit.git mygritgit
git clone类似与svn checkout,clone形象地表达了Git分布式的特性。上面两个命令的区别是第一个默认创建一个grit目录作为本地仓库,第二个命令手动指定了目录名为mygrit。
Git默认使用origin这个名字来标识你所克隆的原始仓库 。默认情况下git clone
命令本质上就是自动创建了本地的 master 分支用于跟踪远程仓库中的 master 分支(假设远程仓库确实有 master 分支) -
清除版本库信息(即脱离Git版本库控制)
一般都是用来将原有项目托管到新版本库时才会用到
cd your_git_project
git rm --cached -r ./
rm -r .git
-
如何撤销
修改最后一次提交
有时候我们提交完了才发现漏掉了几个文件没有加,或者提交信息写错了。想要撤消刚才的提交操作,可以使用
--amend
选项重新提交:$ git commit --amend
此命令将使用当前的暂存区域快照提交。如果刚才提交完没有作任何改动,直接运行此命令的话,相当于有机会重新编辑提交说明,但将要提交的文件快照和之前的一样。
启动文本编辑器后,会看到上次提交时的说明,编辑它确认没问题后保存退出,就会使用新的提交说明覆盖刚才失误的提交。
如果刚才提交时忘了暂存某些修改,可以先补上暂存操作,然后再运行
--amend
提交:$ git commit -m 'initial commit'
$ git add forgotten_file
$ git commit --amend上面的三条命令最终只是产生一个提交,第二个提交命令修正了第一个的提交内容。
取消已经暂存的文件
git reset HEAD <file>...: 回归到之前已修改未暂存的状态git checkout -- <file>...: 回归到之前未修改的状态删除未跟踪(untracked)文件
untracked,即从未add到过暂存区的文件git clean -nd 测试删除(dry run)git clean -fd 真实删除当然也可以使用linux rm命令删除 -
远程仓库的使用
查看当前的远程库
git remote: 只显示远程仓库简称git remote -v: 显示远程仓库简称和对应的克隆地址添加远程仓库
git remote add [shortname] [url]
demo: git remote add pb git://github.com/paulboone/ticgit.git
git fetch [remote-name]此命令会到远程仓库中拉取所有你本地仓库中还没有的数据(从你上次clone以来别人上传到此远程仓库中的所有更新或是上次 fetch 以来别人提交的更新)。fetch 命令只是将远端的数据拉到本地仓库,并不自动合并到当前工作分支,只有当你确实准备好了,才能手工合并。demo: git fetch origin推送数据到远程仓库
git push [remote-name] [branch-name]只有在所克隆的服务器上有写权限,或者同一时刻没有其他人在推数据,这条命令才会如期完成任务。如果在你推数据前,已经有其他人推送了若干更新,那你的推送操作就会被驳回。你必须先把他们的更新抓取到本地,合并到自己的项目中,然后才可以再次推送。查看远程仓库信息
git remote show [remote-name]git remote rm [remote-name]git remote rename [old-remote-name] [new-remote-name]
-
打标签(tag)
列显已有的标签
git taggit tag -l 'v1.4.2.*'新建标签
Git 使用的标签有两种类型:轻量级的(lightweight)和含附注的(annotated)。轻量级标签就像是个不会变化的分支,实际上它就是个指向特定提交对象的引用。而含附注标签,实际上是存储在仓库中的一个独立对象,它有自身的校验和信息,包含着标签的名字,电子邮件地址和日期,以及标签说明,标签本身也允许使用 GNU Privacy Guard (GPG) 来签署或验证。含附注的标签:git tag -a v1.4 -m 'my version 1.4'轻量级的标签:git tag v1.4-lw查看标签信息:git show v1.4-lw后期加注标签
我们忘了在提交 “updated rakefile” 后为此项目打上版本号 v1.2,没关系,现在也能做。只要在打标签的时候跟上对应提交对象的校验和(或前几位字符)即可$ git log --pretty=oneline9fceb02d0ae598e95dc970b74767f19372d61af8 updated rakefile964f16d36dfccde844893cac5b347e7b3d44abbc commit the todo8a5cbc430f1a9c3d00faaeffd07798508422908a updated readme$ git tag -a v1.2 9fceb02默认情况下,git push
并不会把标签传送到远端服务器上,只有通过显式命令才能分享标签到远端仓库。其命令格式如同推送分支,运行git push origin [tagname]
即可。如果要一次推送所有本地新增的标签上去,可以使用git push origin --tags
小技巧
-
Git命令别名
为常用命令设置别名
$ git config --global alias.co checkout
$ git config --global alias.br branch
$ git config --global alias.ci commit
$ git config --global alias.st status
取消暂存文件
$ git config --global alias.unstage 'reset HEAD --'
这样一来,下面的两条命令完全等同:
$ git unstage fileA
$ git reset HEAD fileA
查看最后一次提交信息
$ git config --global alias.last 'log -1 HEAD'
然后要看最后一次的提交信息,就变得简单多了:
$ git last
取消储藏(Un-applying a Stash)
git config --global alias.stash-unapply '!git stash show -p | git apply -R'
-
Git坑
在windows下的文件的权限因为无法和linux上完全一致,所以用Git检出的文件权限可能显示为被更改。另外因为windows下的换行和linux上也不一样,协作开发时也容易出问题。所以在windows上使用Git的同学需要加上以下2行配置参数:
git config --global core.filemode false git config --global core.autocrlf true 第一句是忽略文件权限的改动。 第二句是将文件checkout时自动把LF转成CRLF,check in 时自动把CRLF转成LF
-
临时性储藏你的工作
经常有这样的事情发生,当你正在进行项目中某一部分的工作,里面的东西处于一个比较杂乱的状态,而你想转到其他分支上进行一些工作。问题是,你不想提交进行了一半的工作,否则以后你无法回到这个工作点。解决这个问题的办法就是git stash
命令。
git stash: 储藏当前工作区
git stash list: 查看现有的储藏
git stash apply: 应用最近的储藏到工作区
git stash apply stash@{2}: 将stash@{2}这个储藏应用到工作区
对文件的变更被重新应用,但是被暂存的文件没有重新被暂存。想那样的话,你必须在运行git stash apply
命令时带上一个--index
的选项来告诉命令重新应用被暂存的变更。
git stash pop:
重新应用储藏,同时立刻将其从堆栈中移走
git stash drop: 移除储藏