概述:本章主要讲解了git的,停止与开始跟踪某个文件;暂存或者提交某些更新;如何过滤某些不想跟踪的文件,如何撤销小的错误,浏览项目的更新历史,查看两次更改之间的差异,如何与远程数据库进行互动。
1.取得项目的git仓库
1.1从当前目录进行初始化
如果我们需要备份自己的代码,或者在异地进行办公而想对同一份代码进行编辑,此时往往选用这种模式。执行
$git init
初始化后,在当前目录下会出现一个名为 .git 的目录,所有 Git 需要的数据和资源都存放在这个目录中。不过目前,仅仅是按照既有的结构框架初始化好了里边所有的文件和目录,但我们还没有开始跟踪管理项目中的任何一个文件。
将一些文件加入其中并提交:
$vim a.c b.c README
$git add *.c README
$git commit -m "the first commit"
1.2从现有项目clone
$ git clone git://github.com/schacon/grit.git
2.检查仓库中文件的状态
使用git status命令:
$ git status
# On branch master
nothing to commit (working directory clean)
_________________________
现在让我们用 vim 编辑一个新文件 README,保存退出后运行 git status 会看到该文件出现在未跟踪文件列表中:
$ vim README
$ git status
# On branch master
# Untracked files:
#
(use "git add <file>..." to include in what will be committed)
#
# README
nothing added to commit but untracked files present (use "git add" to track)
2.1跟踪新文件
接着上面,我们想要跟踪README,需要输入:
$git add README
检查:
$git status
# On branch master
# Changes to be committed:
#
(use "git reset HEAD <file>..." to unstage)
#
# new file:
README
#
2.2暂存已修改的文件
在我们修改下之前已跟踪过的文件 benchmarks.rb,然后再次运行 status 命令,会看到这样的状态报告:
$ git status
# On branch master
# Changes to be committed:
#
(use "git reset HEAD <file>..." to unstage)
#
# new file:
README
#
# Changed but not updated:
#
(use "git add <file>..." to update what will be committed)
#
# modified:
benchmarks.rb
#
文件 benchmarks.rb 出现在 “Changed but not updated” 这行下面,说明已跟踪文件的内容发生了变化,但还没有放到暂存区。要暂存这次更新,需要运行 git add 命令(这是个多功能命令,根据目标文件的状态不同,此命令的效果也不同:可以用它开始跟踪新文件,或者把已跟踪的文件放到暂存区,还能用于合并时把有冲突的文件标记为已解决状态等)。
现在让我们运行 git add 将 benchmarks.rb 放到暂存区,然后再看看 git status的输出:
$ git add benchmarks.rb
$ git status
# On branch master
# Changes to be committed:
#
(use "git reset HEAD <file>..." to unstage)
#
# new file: README
# modified: benchmarks.rb
16
Scott Chacon Pro Git
2.2节 记录每次更新到仓库
#现在两个文件都已暂存,下次提交时就会一并记录到仓库。假设此时,你想要在 bench-marks.rb 里再加条注释,重新编辑存盘后,准备好提交。不过且慢,再运行 git status看看:
$ vim benchmarks.rb
$ git status
# On branch master
# Changes to be committed:
#
(use "git reset HEAD <file>..." to unstage)
#
# new file: README
# modified: benchmarks.rb
#
# Changed but not updated:
#
(use "git add <file>..." to update what will be committed)
#
# modified:
benchmarks.rb
#
见鬼!benchmarks.rb 文件出现了两次!一次算未暂存,一次算已暂存,这怎么可能呢?好吧,实际上 Git 只不过暂存了你运行 git add 命令时的版本,如果现在提交,那么提交的是添加注释前的版本,而非当前工作目录中的版本。所以,运行了 git add 之后又作了修订的文件,需要重新运行 git add 把最新版本重新暂存起来:
$ git add benchmarks.rb
$ git status
# On branch master
# Changes to be committed:
#
(use "git reset HEAD <file>..." to unstage)
#
# new file: README
# modified: benchmarks.rb
2.3忽略某些文件
一般我们总会有些文件无需纳入 Git 的管理,也不希望它们总出现在未跟踪文件列表。通常都是些自动生成的文件,像是日志或者编译过程中创建的等等。我们可以创建一个名为.gitignore 的文件,列出要忽略的文件模式,来看一个简单的例子:
$ cat .gitignore
*.[oa]
*~
Scott Chacon Pro Git
第一行告诉 Git 忽略所有以 .o 或 .a 结尾的文件。一般这类对象文件和存档文件都是编译过程中出现的,我们用不着跟踪它们的版本。第二行告诉 Git 忽略所有以波浪符(~)结尾的文件,许多文本编辑软件(比如 Emacs)都用这样的文件名保存副本。此外,你可能
还需要忽略 log,tmp 或者 pid 目录,以及自动生成的文档等等。要养成一开始就设置好.gitignore 文件的习惯,以免将来误提交这类无用的文件。
文件 .gitignore 的格式规范如下:
• 所有空行或者以注释符号 # 开头的行都会被 Git 忽略。
• 可以使用标准的 glob 模式匹配。
• 匹配模式最后跟反斜杠(/)说明要忽略的是目录。
• 要忽略指定模式以外的文件或目录,可以在模式前加上惊叹号(!)取反。
所谓的 glob 模式是指 shell 所使用的简化了的正则表达式。星号(*)匹配零个或多个任意字符;[abc] 匹配任何一个列在方括号中的字符(这个例子要么匹配一个 a,要么匹配一个 b,要么匹配一个 c);问号(?)只匹配一个任意字符;如果在方括号中使用短划线分隔两个字符,表示所有在这两个字符范围内的都可以匹配(比如 [0-9] 表示匹配所有 0 到9 的数字)。
我们再看一个 .gitignore 文件的例子:
# 此为注释 – 将被 Git 忽略
*.a # 忽略所有 .a 结尾的文件
!lib.a # 但 lib.a 除外
/TODO # 仅仅忽略项目根目录下的 TODO 文件,不包括 subdir/TODO
build/ # 忽略 build/ 目录下的所有文件
doc/*.txt # 会忽略 doc/notes.txt 但不包括 doc/server/arch.txt
2.4查看已经暂存和未暂存的更新
git status仅仅查看的是哪些文件发生了变化,比较粗糙;如果想以补丁的形似查看具体更新的信息,可以使用git diff
其中默认的git diff对比是当前工作目录和缓存区,git diff --cached对比暂存区和已提交的不同。
2.5提交已经暂存的更新
git commit 以互动方式让用户输入commit信息,其中第一行是标题,空行,然后接着解释;或者用git commit -m将所有信息包涵在一行之中。
2.6跳过使用暂存区域:git commit -a
2.7移除文件:git rm
目标一:不再想跟踪某个文件,将它从当前工作目录和stage状态下移除
方法:git rm
结果:如果file同时存在在stage和wd之下,那么git rm将会失败,提示错误:
error: 'hyk' has changes staged in the index
(use --cached to keep the file, or -f to force removal)
说明;如果一个文件已经改变了stage状态,那么是不能用git rm直接删除的
混淆:如果直接rm,那么显示为“deleted but not updated”,因为此时它仍然处在跟踪状态
目标二:把文件暂存区域中删除,但是保留在当前工作目录下
方法:$git rm --cached file
目标二:把文件暂存区域中删除,而且保留在当前工作目录下
方法:$git rm --f file
2.8移动文件
git-mv - Move or rename a file, a directory, or a symlink
目标:将文件README.txt 更名为README,stage和wd之下的文件同时改变
方法:git mv README.txt README
或者:mv README.txt README; git rm README.txt; git add README
3查看提交历史
git log
查看修改的详细信息:git log -p
详细显示最近的n次修改:git log -p -n
仅仅统计修改的行数:git log --stat
图形化显示:git log --graph
3.1限制输出长度
1)需求:时间限制,最近两周的修改
方法:$git log --since=2.weeks
拓展:选项 说明
-(n) 仅显示最近的 n 条提交
--since, --after 仅显示指定时间之后的提交。
--until, --before 仅显示指定时间之前的提交。
--author 仅显示指定作者相关的提交。
--committer 仅显示指定提交者相关的提交。
3.2使用图形化工具查询提交历史
gitk工具
用法:在当前目录和分支下输入: $gitk
4.撤销操作
4.1修改最后一次提交
情景:提交完毕,发现漏掉几个文件,或者提交信息写错了
方法:$git commit --amend
说明:此命令将使用当前的暂存区域快照提交。如果刚才提交完没有作任何改动,直接运行此命
令的话,相当于有机会重新编辑提交说明,而所提交的文件快照和之前的一样。
情景2:如果刚才提交时忘了暂存某些修改,可以先补上暂存操作,然后再运行 --amend 提交:
$ git commit -m 'initial commit'
$ git add forgotten_file
$ git commit --amend
上面的三条命令最终得到一个提交,第二个提交命令修正了第一个的提交内容。
解释:git commit --amend 相当于用当前暂存区的文件来修改上一次的提交。总之,这个撤销操作是撤销从暂存区到本地仓库的这一步
4.2取消已经暂存的操作
情景一:现在有两个文件,已经被修改而且都提交到暂存区域,如果我们用git commit,那么这两个文件将同时被提交。现在,我想分两次提交,应该怎办法?
$ git status
# On branch master
# Changes to be committed:
#
(use "git reset HEAD <file>..." to unstage)
#
# modified: README.txt
# modified: benchmarks.rb
方法:
$ git reset HEAD benchmarks.rb
benchmarks.rb: locally modified
$ git status
# On branch master
# Changes to be committed:
#
(use "git reset HEAD <file>..." to unstage)
#
#
modified:
README.txt
#
# Changed but not updated:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
#
#
说明:也就是说,git reset将当前暂存区的某些文件撤销到工作目录之中
4.3取消对文件的修改
情景一:觉得刚才对 benchmarks.rb 的修改完全没有必要,该如何取消修改,回到之前的状态(也就是修改之前的版本)呢?
方法:$ git checkout -- benchmarks.rb
$ git status
# On branch master
# Changes to be committed:
#
(use "git reset HEAD <file>..." to unstage)
#
#
modified:
README.txt
#
注意事项:git checkout --benchmarks.rb是用当前已经提交的文件重写工作目录下的文件,有很大的危险性。因为git可以撤销任何已经提交的操作,但是没有提交过的却会丢失。
5.远程仓库
管理远程仓库的工作,包括添加远程库,移除废弃的远程库,管理各式远程库分支,定义是否跟踪这些分支,等等。本节我们将详细讨论远程库的管理和使用。
5.1查看远程仓库
要查看当前配置有哪些远程仓库,可以用 git remote 命令
需求一:查看远程仓库地址
方法:git remote -v
5.2添加远程仓库
方法:运行 git remote add [shortname] [url]:
5.3从远程仓库抓取数据
方法:git fetch
说明:如果是克隆了一个仓库,此命令会自动将远程仓库归于 origin 名下。所以,git fetchorigin 会抓取从你上次克隆以来别人上传到此远程仓库中的所有更新(或是上次 fetch 以来别人提交的更新)。有一点很重要,需要记住,fetch 命令只是将远端的数据拉到本地仓库,并不自动合并到当前工作分支,只有当你确实准备好了,才能手工合并。
注意:如果设置了某个分支用于跟踪某个远端仓库的分支(参见下节及第三章的内容),可以使用 git pull 命令自动抓取数据下来,然后将远端分支自动合并到本地仓库中当前分支。在日常工作中我们经常这么用,既快且好。实际上,默认情况下 git clone 命令本质上就是自动创建了本地的 master 分支用于跟踪远程仓库中的 master 分支(假设远程仓库确实有master 分支)。所以一般我们运行 git pull,目的都是要从原始克隆的远端仓库中抓取数据后,合并到工作目录中当前分支。
5.4推送数据到远程仓库
方法:git push [remote-name] [branch-name]
注意:只有在所克隆的服务器上有写权限,或者同一时刻没有其他人在推数据,这条命令才会如期完成任务。如果在你推数据前,已经有其他人推送了若干更新,那你的推送操作就会被驳回。你必须先把他们的更新抓取到本地,并到自己的项目中,然后才可以再次推送。有关推送数据到远程仓库的详细内容见第三章。
5.5查看远程仓库信息
* remote origin
URL: git@github.com:defunkt/github.git
Remote branch merged with 'git pull' while on branch issues
issues
Remote branch merged with 'git pull' while on branch master
master
New remote branches (next fetch will store in remotes/origin)
caching
Stale tracking branches (use 'git remote prune')
libwalker
walker2
Tracked remote branches
acl
apiv2
dashboard2
issues
master
postgres
Local branch pushed with 'git push'
master:master
5.6远程仓库的删除和重命名
6.标签
本节我们一起来学习如何列出所有可用的标签,如何新建标签,以及各种不同类型标签之间的差别。
6.1显示标签
6.2新建标签
签署或验证。一般我们都建议使用含附注型的标签,以便保留相关信息;当然,如果只是临时性加注标签,或者不需要旁注额外信息,用轻量级标签也没问题。
6.3验证标签
6.4后期加注标签
15027957951b64cf874c3557a0f3547bd83b3ff6 Merge branch 'experiment'
a6b4c97498bd301d84096da251c98a07c7723e65 beginning write support
0d52aaab4479697da7686c15f77a3d64d9165190 one more thing
6d52a271eda8725415634dd79daabbc4d9b6008e Merge branch 'exper
6.5分享标签
7.技巧和窍门
7.1自动补全
7.2 Git 命令别名
Git 并不会推断你输入的几个字符将会是哪条命令,不过如果想偷懒,少敲几个命令的字符,可以用 git config 为命令设置别名。来看看下面的例子:$ 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 commit 只需键入 git ci 即可。而随着 Git 使用的深入,会有很多经常要用到的命令,遇到这种情况,不妨建个别名提高效率。使用这种技术还可以创造出新的命令,比方说取消暂存文件时的输入比较繁琐,可以自己设置一下:
$ git config --global alias.unstage 'reset HEAD --'
这样一来,下面的两条命令完全等同:
$ git unstage fileA
$ git reset HEAD fileA
显然,使用别名的方式看起来更清楚。另外,我们还经常设置 last 命令:
$ git config --global alias.last 'log -1 HEAD'
然后要看最后一次的提交信息,就变得简单多了:
$ git last
commit 66938dae3329c7aebe598c2246a8e6af90d04646
Author: Josh Goebel <dreamer3@example.com>
Date:
Tue Aug 26 19:48:51 2008 +0800