第 7 章 Git大师技
到现在,你应该有能力查阅 githelp 页,并理解几乎所有东西。然而,查明解决特定问题需要的确切命令可能是乏味的。或许我可以省你点功夫:以下是我过去曾经用到 的一些技巧。
源码发布
就我的项目而言,Git完全跟踪了我想打包并发布给用户的文件。如需创建一个源码包,我 会运行:
$ git archive --format=tar --prefix=proj-1.2.3/ HEAD
提交变更
对特定项目而言,告诉Git你增加,删除和重命名了一些文件很麻烦。而键入如下命令会容易 的多:
$ git add . $ git add -u
Git将查找当前目录的文件并计算出所有更改过的内容。除了第二个add命令,如果你也打 算同时提交,则可以运行`git commit -a`。关于如何指定应被忽略的文件,参见 git help ignore 。
你也可以用一行命令完成以上任务:
$ git ls-files -d -m -o -z | xargs -0 git update-index --add --remove
这里 -z 和 -0 选项可以消除包含特殊字符的文件名引起的不良副作用。注意这个 命令也会添加本应被忽略的文件,这时你可能需要加上 -x
或 -X
选项。
别丢了你的HEAD
HEAD好似一个游标,通常指向最新提交,随最新提交向前移动。一些Git命令可让你来移动 它。 例如:
$ git reset HEAD~3
这将立即将HEAD向回移动三个提交。这样所有Git命令都表现得好似你没有做那最后三个提交, 然而你的文件保持在现在的状态。具体应用参见帮助页。
但如何回到将来呢?过去的提交对将来一无所知。
如果你有原先Head的SHA1值,那么:
$ git reset 1b6d
但假设你从来没有记下呢?别担心,在这些命令里面,Git会将原先的Head保存为一个叫做 ORIG_HEAD的标记,你可以安全体面的返回那里:
$ git reset ORIG_HEAD
HEAD捕猎
或许ORIG_HEAD还不够;或许你刚认识到你犯了个历史性的错误,你需要回到一个早已忘记 分支上一个远古的提交。
默认的,Git将会将一个提交保存至少两星期,即使你命令Git摧毁该提交所在的分支。难点 是找到相应的哈希值。你可以查看在.git/objects里所有的哈希值并尝试找到你期望的提 交。但这里有一个更简单的办法。
Git把算出的提交哈希值记录在“.git/logs”。这个子目录引用包括所有分支上所有活 动的历史,同时文件HEAD显示它曾经有过的所有哈希值。后者可用来发现分支上一些不 小心丢掉提交的哈希值。
命令reflog为访问这些日志文件提供了友好的接口,可以试试
$ git reflog
而不是从reflog拷贝粘贴哈希值,试一下:
$ git checkout "@{10 minutes ago}"
或者捡出后五次访问过的提交,通过:
$ git checkout "@{5}"
更多内容参见 git help rev-parse 的‘`Specifying Revisions’'部分。
你或许期望去为已删除的提交设置一个更长的保存周期。例如:
$ git config gc.pruneexpire "30 days"
意思是一个被删除的提交会在删除30天并运行 git gc 以后,被永久丢弃。
你或许还想关掉 git gc 的自动运行:
$ git config gc.auto 0
在这种情况下提交将只在你手工运行 git gc 的情况下才永久删除。
基于Git构建
依照真正的UNIX风格设计,Git允许其易于用作其他程序的底层组件,比如图形界面, Web界面,可选择的命令行界面,补丁管理工具,导入和转换工具等等。实际上,一些 Git命令它们自己就是站在巨人肩膀上的脚本。通过一点修补,你可以定制Git适应你的 偏好。
一个简单的技巧是,用Git内建alias命令来缩短你最常使用命令:
$ git config --global alias.co checkout $ git config --global --get-regexp alias # 显示当前别名 alias.co checkout $ git co foo # 和“git checkout foo”一样
另一个技巧,在提示符或窗口标题上打印当前分支。调用:
$ git symbolic-ref HEAD
显示当前分支名。在实际应用中,你可能最想去掉“refs/heads/”并忽略错误:
$ git symbolic-ref HEAD 2> /dev/null | cut -b 12-
子目录 contrib
是一个基于Git工具的宝库。它们中的一些命令不时的会被提升为官方 命令。在Debian和Ubuntu,这个目录位于 /usr/share/doc/git-core/contrib
。
一个受欢迎的居民是 workdir/git-new-workdir
。通过聪明的符号链接,这个脚本创 建一个新的工作目录,其历史与原来的仓库共享:
$ git-new-workdir an/existing/repo new/directory
这个新的目录和其中的文件可被视为一个克隆,除了历史是共享的,两者的树会自动保持 同步,而不必合并,推入或拉出。
大胆的特技
最近以来,Git努力使用户因意外而销毁数据变得更困难。但如若你知道你在做什么,你可 以突破为通用命令所设的保障措施。
*Checkout*:未提交的变更会导致捡出失败。销毁你的变更,并无论如何都checkout一 个指定的提交,使用强制标记:
$ git checkout -f HEAD^
另外,如果你为捡出指定特别路径,那就没有安全检查了。提供的路径将被不加提示地 覆盖。如你使用这种方式的检出,要小心。
Reset: 如有未提交变更重置也会失败。强制其通过,运行:
$ git reset --hard 1b6d
Branch: 引起变更丢失的分支删除会失败。强制删除,键入:
$ git branch -D dead_branch # instead of -d
类似,通过移动试图覆盖分支,如果随之而来有数据丢失,那么覆盖也会失败。强制移动 分支,键入:
$ git branch -M source target # 而不是 -m
不像checkout和重置,这两个命令将延迟数据销毁。这个变更仍然存储在.git的子目录里, 并且可以通过恢复.git/logs里的相应哈希值获取(参见上面 上面“HEAD猎捕”)。默 认情况下,这些数据会保存至少两星期。
Clean: 一些Git命令拒绝执行,因为它们担心会重装未纳入管理的文件。如果你确信 所有未纳入管理的文件都是消耗品,那就无情地删除它们,使用:
$ git clean -f -d
下次,那个讨厌的命令就会工作!
阻止坏提交
愚蠢的错误会污染我的代码库。最可怕的是由于忘记 git add 而引起的文件丢失。较小 的错误则是行末追加空格而引发合并冲突:尽管危害少,我希望这些永远不要出现在公开 记录里。
不过我购买了傻瓜保险,通过使用一个_钩子_来提醒我这些问题:
$ cd .git/hooks $ cp pre-commit.sample pre-commit # 对旧版本Git,先运行chmod +x
现在如果Git检测到无用的空格或未解决的合并冲突,它将放弃合并。
对本文档,我最终添加以下到 pre-commit 钩子的前面,来防止缺魂儿的事:
if git ls-files -o | grep '\.txt$'; thenecho FAIL! Untracked .txt files.exit 1 fi
一部分其他的Git操作也支持钩子;参见 git help hooks 。我们早先激活了作为例子的 post-update 钩子,当讨论基于HTTP的Git的时候。无论head何时移动,这个钩子都会 运行。例子中的脚本post-update会在Git面对并不知晓的传输协议,诸如HTTP时,更新自己的 资料库,以确保它有能力交换所需的文件。