pro git 阅读笔记1 基本操作
Git 收取的是项目历史的所有数据(每一个文件的每一个版本),服务器上有的数据克隆之后本地也都有了。实际上,即便服务器的磁盘发生故障,用任何一个克隆出来的客户端都可以重建服务器上的仓库,回到当初克隆时的状态。
在 git add
后面可以指明要跟踪的文件或目录路径。如果是目录的话,就说明要递归跟踪该目录下的所有文件。
其实 git add
的潜台词就是把目标文件快照放入暂存区域,也就是 add file into staged area,同时未曾跟踪过的文件标记为需要跟踪。这样就好理解后续 add 操作的实际意义了。
git add 是个多功能命令,根据目标文件的状态不同,此命令的效果也不同:可以用它开始跟踪新文件,或者把已跟踪的文件放到暂存区,还能用于合并时把有冲突的文件标记为已解决状态等
* 匹配模式最后跟反斜杠(/
)说明要忽略的是目录。 * 要忽略指定模式以外的文件或目录,可以在模式前加上惊叹号(!
)取反。
git diff 此命令比较的是工作目录中当前文件和暂存区域快照之间的差异,也就是修改之后还没有暂存起来的变化内容。 changed<->staged
若要看已经暂存起来的文件和上次提交时的快照之间的差异,可以用 git diff --staged
请注意,单单 git diff
不过是显示还没有暂存起来的改动,而不是这次工作和上次提交之间的差异。所以有时候你一下子暂存了所有更新过的文件后,运行 git diff
后却什么也没有,就是这个原因。
暂存区域已经准备妥当可以提交: 请一定要确认还有什么修改过的或新建的文件还没有 git add
过,否则提交的时候不会记录这些还没暂存起来的变化。所以,每次准备提交前,先用 git status
看下,是不是都已暂存起来了,然后再运行提交命令 git commit
使用 git config --global core.editor
命令设定你喜欢的编辑软件
提交时记录的是放在暂存区域的快照,任何还未暂存的仍然保持已修改状态,可以在下次提交时纳入版本管理。每一次运行提交操作,都是对你项目作一次快照,以后可以回到这个状态,或者进行比较。
在提交的时候,给 git commit
加上 -a
选项,Git 就会自动把所有已经跟踪过的文件暂存起来一并提交,从而跳过 git add
步骤。
移除文件
要从 Git 中移除某个文件,就必须要从已跟踪文件清单中移除(确切地说,是从暂存区域移除),然后提交。可以用 git rm
命令完成此项工作,并连带从工作目录中删除指定的文件,这样以后就不会出现在未跟踪文件清单中了。
如果只是简单地从工作目录中手工删除文件,运行 git status
时就会在 “Changed but not updated” 部分(也就是_未暂存_清单)看到:
$ rm grit.gemspec $ git status # On branch master # # Changed but not updated: # (use "git add/rm <file>..." to update what will be committed) # # deleted: grit.gemspec #
然后再运行 git rm
记录此次移除文件的操作:
$ git rm grit.gemspec rm 'grit.gemspec' $ git status # On branch master # # Changes to be committed: # (use "git reset HEAD <file>..." to unstage) # # deleted: grit.gemspec #
最后提交的时候,该文件就不再纳入版本管理了。如果删除之前修改过并且已经放到暂存区域的话,则必须要用强制删除选项 -f
(译注:即 force 的首字母),以防误删除文件后丢失修改的内容。
另外一种情况是,我们想把文件从 Git 仓库中删除(亦即从暂存区域移除),但仍然希望保留在当前工作目录中。换句话说,仅是从跟踪清单中删除。比如一些大型日志文件或者一堆 .a
编译文件,不小心纳入仓库后,要移除跟踪但不删除文件,以便稍后在 .gitignore
文件中补上,用 --cached
选项即可:
$ git rm --cached readme.txt
后面可以列出文件或者目录的名字,也可以使用 glob 模式。比方说:
$ git rm log/\*.log
注意到星号 *
之前的反斜杠 \
,因为 Git 有它自己的文件模式扩展匹配方式,所以我们不用 shell 来帮忙展开(译注:实际上不加反斜杠也可以运行,只不过按照 shell 扩展的话,仅仅删除指定目录下的文件而不会递归匹配。上面的例子本来就指定了目录,所以效果等同,但下面的例子就会用递归方式匹配,所以必须加反斜杠。)。此命令删除所有 log/
目录下扩展名为 .log
的文件。类似的比如:
$ git rm \*~
会递归删除当前目录及其子目录中所有 ~
结尾的文件。
移动文件
不像其他的 VCS 系统,Git 并不跟踪文件移动操作。如果在 Git 中重命名了某个文件,仓库中存储的元数据并不会体现出这是一次改名操作。不过 Git 非常聪明,它会推断出究竟发生了什么,至于具体是如何做到的,我们稍后再谈。
既然如此,当你看到 Git 的 mv
命令时一定会困惑不已。要在 Git 中对文件改名,可以这么做:
$ git mv file_from file_to
它会恰如预期般正常工作。实际上,即便此时查看状态信息,也会明白无误地看到关于重命名操作的说明:
$ git mv README.txt README $ git status # On branch master # Your branch is ahead of 'origin/master' by 1 commit. # # Changes to be committed: # (use "git reset HEAD <file>..." to unstage) # # renamed: README.txt -> README #
其实,运行 git mv
就相当于运行了下面三条命令:
$ mv README.txt README $ git rm README.txt $ git add README
如此分开操作,Git 也会意识到这是一次改名,所以不管何种方式都一样。当然,直接用 git mv
轻便得多,不过有时候用其他工具批处理改名的话,要记得在提交前删除老的文件名,再添加新的文件名。
在提交了若干更新之后,又或者克隆了某个项目,想回顾下提交历史,可以使用 git log
命令查看。
我们常用 -p
选项展开显示每次提交的内容差异,用 -2
则仅显示最近的两次更新:
git log --stat
,仅显示简要的增改行数统计
git log --pretty=oneline
比如用 oneline
将每个提交放在一行显示,这在提交数很大时非常有用。
用 oneline 或 format 时结合 --graph
选项,可以看到开头多出一些 ASCII 字符串表示的简单图形,形象地展示了每个提交所在的分支及其分化衍合情况。
还有按照时间作限制的选项,比如 --since
和 --until
。下面的命令列出所有最近两周内的提交:
$ git log --since=2.weeks
还可以给出若干搜索条件,列出符合的提交。用 --author
选项显示指定作者的提交,用 --grep
选项搜索提交说明中的关键字。(请注意,如果要得到同时满足这两个选项搜索条件的提交,就必须用 --all-match
选项。)
来看一个实际的例子,如果要查看 Git 仓库中,2008 年 10 月期间,Junio Hamano 提交的但未合并的测试脚本(位于项目的 t/ 目录下的文件),可以用下面的查询命令:
$ git log --pretty="%h - %s" --author=gitster --since="2008-10-01" \ --before="2008-11-01" --no-merges -- t/
5610e3b - Fix testcase failure when extended attribute
acd3b9e - Enhance hold_lock_file_for_{update,append}()
f563754 - demonstrate breakage of detached checkout wi
d1a43f2 - reset --hard/read-tree --reset -u: remove un
51a94af - Fix "checkout --track -b newbranch" on detac
b0ad11e - pull: allow "git pull origin $something:$cur
撤消操作:
修改最后一次提交
有时候我们提交完了才发现漏掉了几个文件没有加,或者提交信息写错了。想要撤消刚才的提交操作,可以使用 --amend
选项重新提交
此命令将使用当前的暂存区域快照提交。如果刚才提交完没有作任何改动,直接运行此命令的话,相当于有机会重新编辑提交说明,但将要提交的文件快照和之前的一样。
启动文本编辑器后,会看到上次提交时的说明,编辑它确认没问题后保存退出,就会使用新的提交说明覆盖刚才失误的提交。
如果刚才提交时忘了暂存某些修改,可以先补上暂存操作,然后再运行 --amend
提交:
取消已经暂存的文件
git reset HEAD benchmarks.rb
取消对文件的修改
如果觉得刚才对 benchmarks.rb 的修改完全没有必要,该如何取消修改,回到之前的状态:
git checkout -- benchmarks.rb
这条命令有些危险,所有对文件的修改都没有了,因为我们刚刚把之前版本的文件复制过来重写了此文件。所以在用这条命令前,请务必确定真的不再需要保留刚才的修改。
如果只是想回退版本,同时保留刚才的修改以便将来继续工作,可以用下章介绍的 stashing 和分支来处理,应该会更好些。
任何已经提交到 Git 的都可以被恢复。即便在已经删除的分支中的提交,或者用 --amend
重新改写的提交,都可以被恢复(关于数据恢复的内容见第九章)。
所以,你可能失去的数据,仅限于没有提交过的,对 Git 来说它们就像从未存在过一样。
远程仓库
要参与任何一个 Git 项目的协作,必须要了解该如何管理远程仓库。
远程仓库是指托管在网络上的项目仓库,可能会有好多个,其中有些你只能读,另外有些可以写。
同他人协作开发某个项目时,需要管理这些远程仓库,以便推送或拉取数据,分享各自的工作进展。
管理远程仓库的工作,包括添加远程库,移除废弃的远程库,管理各式远程库分支,定义是否跟踪这些分支,等等。
可以认为每个项目对应多个远程仓库,而每个远程仓库是每个开发者的一个网络托管。。。 别人可以读,但只有自己能够修改自己的origin仓库
查看当前的远程库
git remote
命令,它会列出每个远程库的简短名字。在克隆完某个项目后,至少可以看到一个名为 origin 的远程库,Git 默认使用这个名字来标识你所克隆的原始仓库:
也可以加上 -v
选项(译注:此为 --verbose
的简写,取首字母),显示对应的克隆地址:
如果有多个远程仓库,此命令将全部列出。
只有 origin 用的是 SSH URL 链接,所以也只有这个仓库我能推送数据上去
添加远程仓库
要添加一个新的远程仓库,可以指定一个简单的名字,以便将来引用,运行 git remote add [shortname] [url]
: 相当于我引用了一个别人对这个项目的托管地址,易购就可以用别名来访问、fetch、pull了。
git remote add pb git://github.com/paulboone/ticgit.git
现在可以用字串 pb 指代对应的仓库地址了。比如说,要抓取所有 Paul 有的,但本地仓库没有的信息,可以运行 git fetch pb
:
现在,Paul 的主干分支(master)已经完全可以在本地访问了,对应的名字是 pb/master
,你可以将它合并到自己的某个分支,或者切换到这个分支,看看有些什么有趣的更新。
从远程仓库抓取数据
git fetch [remote-name]
此命令会到远程仓库中拉取所有你本地仓库中还没有的数据。运行完成后,你就可以在本地访问该远程仓库中的所有分支,将其中某个分支合并到本地,或者只是取出某个分支,一探究竟。
如果是克隆了一个仓库,此命令会自动将远程仓库归于 origin 名下。
所以,git fetch origin
会抓取从你上次克隆以来别人上传到此远程仓库中的所有更新。有一点很重要,需要记住,fetch 命令只是将远端的数据拉到本地仓库,并不自动合并到当前工作分支,只有当你确实准备好了,才能手工合并。
如果设置了某个分支用于跟踪某个远端仓库的分支,可以使用 git pull
命令自动抓取数据下来,然后将远端分支自动合并到本地仓库中当前分支。在日常工作中我们经常这么用,既快且好。实际上,默认情况下 git clone
命令本质上就是自动创建了本地的 master 分支用于跟踪远程仓库中的 master 分支。所以一般我们运行 git pull
,目的都是要从原始克隆的远端仓库中抓取数据后,合并到工作目录中的当前分支。
推送数据到远程仓库
项目进行到一个阶段,要同别人分享目前的成果,可以将本地仓库中的数据推送到远程仓库。实现这个任务的命令很简单: git push [remote-name] [branch-name]
。如果要把本地的 master 分支推送到 origin
服务器上(再次说明下,克隆操作会自动使用默认的 master 和 origin 名字),可以运行下面的命令:
$ git push origin master
只有在所克隆的服务器上有写权限,或者同一时刻没有其他人在推数据,这条命令才会如期完成任务。如果在你推数据前,已经有其他人推送了若干更新,那你的推送操作就会被驳回。你必须先把他们的更新抓取到本地,合并到自己的项目中,然后才可以再次推送。
查看远程仓库信息
git remote show origin
远程仓库的删除和重命名
碰到远端仓库服务器迁移,或者原来的克隆镜像不再使用,又或者某个参与者不再贡献代码,那么需要移除对应的远端仓库,可以运行 git remote rm
命令:
$ git remote rm paul
$ git remote
origin
打标签
同大多数 VCS 一样,Git 也可以对某一时间点上的版本打上标签。人们在发布某个软件版本(比如 v1.0 等等)的时候,经常这么做。
列显已有的标签
git tag
我们可以用特定的搜索模式列出符合条件的标签。
$ git tag -l 'v1.4.2.*'
新建标签
轻量级的(lightweight): 轻量级标签就像是个不会变化的分支,实际上它就是个指向特定提交对象的引用。
含附注的(annotated):而含附注标签,实际上是存储在仓库中的一个独立对象,它有自身的校验和信息,包含着标签的名字,电子邮件地址和日期,以及标签说明,标签本身也允许使用 GNU Privacy Guard (GPG) 来签署或验证。
含附注的标签
创建一个含附注类型的标签非常简单,用 -a
(译注:取 annotated
的首字母)指定标签名字即可:
$ git tag -a v1.4 -m 'my version 1.4'
而 -m
选项则指定了对应的标签说明,Git 会将此说明一同保存在标签对象中。如果没有给出该选项,Git 会启动文本编辑软件供你输入标签说明。
可以使用 git show
命令查看相应标签的版本信息,并连同显示打标签时的提交对象。
签署标签
如果你有自己的私钥,还可以用 GPG 来签署标签,只需要把之前的 -a
改为 -s
(译注: 取 signed
的首字母)即可:
现在再运行 git show
会看到对应的 GPG 签名也附在其内:
轻量级标签
轻量级标签实际上就是一个保存着对应提交对象的校验和信息的文件。
要创建这样的标签,一个 -a
,-s
或 -m
选项都不用,直接给出标签名字即可:
验证标签
后期加注标签
你甚至可以在后期对早先的某次提交加注标签。比如在下面展示的提交历史中:
我们忘了在提交 “updated rakefile” 后为此项目打上版本号 v1.2,没关系,现在也能做。只要在打标签的时候跟上对应提交对象的校验和(或前几位字符)即可:
$ git tag -a v1.2 9fceb02
可以看到我们已经补上了标签:
分享标签
默认情况下,git push
并不会把标签传送到远端服务器上,只有通过显式命令才能分享标签到远端仓库。其命令格式如同推送分支,运行 git push origin [tagname]
即可:
如果要一次推送所有本地新增的标签上去,可以使用 --tags
选项:
现在,其他人克隆共享仓库或拉取数据同步后,也会看到这些标签。
自动完成 centos可以用啊。。。
如果你用的是 Bash shell,可以试试看 Git 提供的自动完成脚本。下载 Git 的源代码,进入contrib/completion
目录,会看到一个 git-completion.bash
文件。将此文件复制到你自己的用户主目录中(译注:按照下面的示例,还应改名加上点:cp git-completion.bash ~/.git-completion.bash
),并把下面一行内容添加到你的 .bashrc
文件中:
source ~/.git-completion.bash
也可以为系统上所有用户都设置默认使用此脚本。Mac 上将此脚本复制到/opt/local/etc/bash_completion.d
目录中,Linux 上则复制到 /etc/bash_completion.d/
目录中。这两处目录中的脚本,都会在 Bash 启动时自动加载。
如果在 Windows 上安装了 msysGit,默认使用的 Git Bash 就已经配好了这个自动完成脚本,可以直接使用。
在输入 Git 命令的时候可以敲两次跳格键(Tab),就会看到列出所有匹配的可用命令建议:
$ git co<tab><tab> commit config
此例中,键入 git co 然后连按两次 Tab 键,会看到两个相关的建议(命令) commit 和 config。继而输入m<tab>
会自动完成 git commit
命令的输入。
命令的选项也可以用这种方式自动完成,其实这种情况更实用些。比如运行 git log
的时候忘了相关选项的名字,可以输入开头的几个字母,然后敲 Tab 键看看有哪些匹配的:
$ git log --s<tab> --shortstat --since= --src-prefix= --stat --summary
这个技巧不错吧,可以节省很多输入和查阅文档的时间。