Posts Tagged ‘vcs’

GIT日常命令20来条

May 3rd, 2008

[基本仓库]: 拥有 GIT 仓库的人需要的命令——也就是所有人,因为 git 的每个工作拷贝都是一个仓库。

之后,[个人开发者 (独立工作)]: 任何需要进行 commit 的人都需要的命令,即使是一个人工作的情况。

如果你和其他人一起工作,你还需要列在[个人开发者 (参与者)]小节的命令。

扮演[集成者]角色的人还需要学习这一节中的命令。

[仓库管理]命令是给哪些负责维护 GIT 仓库的系统管理员的。

基本仓库

所有人都可以用这些命令来维护 git 仓库。

实例:

检查健康状况并去除无用的内容 (译注:cruft 不知如此翻译是否妥当,git-gc 基本是这个含义)。

$ git fsck (1)
$ git count-objects (2)
$ git gc (3)
  1. 不加–full参数的情况下,这个命令一般会以非常低廉的代价确保仓库在一个不错的健康状态之中。

  2. 统计有多少松散的对象,没有 repack 的对象消耗了多少硬盘空间。

  3. 在本地仓库进行 repack,并进行其他日常维护工作。

将一个小项目 repack 进入一个单独的 pack。

$ git gc (1)
  1. 将所有可以从 refs 访问到的对象都打包添加进一个 pack,然后删除其他 pack。

个人开发者 (独立工作)

独立工作的个人开发者不需要和其他人交换补丁,在单一的仓库中独自工作,通常会用到这些命令。

  • git-show-branch(1): 查看当前的分支位置。

  • git-log(1):查看发生了些什么。

  • git-checkout(1)git-branch(1):在分支间切换。

  • git-add(1):管理索引文件。

  • git-diff(1)git-status(1):查看正在改动些什么。

  • git-commit(1):向前推进当前分支。

  • git-reset(1) 和 (带有路径参数的)git-checkout(1):撤销变更。

  • git-merge(1):合并两个本地分支。

  • git-rebase(1):维护局部分支。(译注:git rebase 的工作大致是这样:原来在本地基于上游版本v,进行了一些改进,现在上游版本进化到了v+n,这时,在本地将基础首先进化到 v+n,然后把对 v 提交的、但还没进入 v+n 的 commit 加入到 v+n,其中可能会有冲突,这可能需要进行一些冲突处理,这里的这个在上游版本基础上添加内容而成的分支称为 topic 分支,这里翻译为局部分支。)

  • git-tag(1):标记标签。

实例:

以一个 tarball 来作为新仓库的起点。

$ tar zxf frotz.tar.gz$ cd frotz$ git-init$ git add . (1)
$ git commit -m "import of frotz source tree."
$ git tag v2.43 (2)
  1. 将当前目录的所有内容加入仓库。

  2. 添加一个轻量级的,没有注释的标签。

简历一个局部分支并进行开发。

$ git checkout -b alsa-audio (1)
$ edit/compile/test
$ git checkout -- curses/ux_audio_oss.c (2)
$ git add curses/ux_audio_alsa.c (3)
$ edit/compile/test
$ git diff HEAD (4)
$ git commit -a -s (5)
$ edit/compile/test
$ git reset --soft HEAD^ (6)
$ edit/compile/test
$ git diff ORIG_HEAD (7)
$ git commit -a -c ORIG_HEAD (8)
$ git checkout master (9)
$ git merge alsa-audio (10)
$ git log --since='3 days ago' (11)
$ git log v2.43.. curses/ (12)
  1. 创建一个新的局部分支。

  2. 取消对curses/ux_audio_oss.c的修改。

  3. 如果添加了文件的话,需要告诉 git;而删除或修改文件的话,一会的 git commit -a 就可以处理了。

  4. 查看将会提交哪些变更。

  5. 签署并提交所有你测试过的内容。

  6. 回到上一个 commit,但保存工作拷贝中的内容。

  7. 查看自从上一个我们取回的不成熟提交以来的所有变更。

  8. 重新进行上一步中没有完成的提交,使用你自己写的提交信息。

  9. 切换到 master 分支。

  10. 将局部分支合并入你的主分支。

  11. 检查提交日志;其他的可以一起用上的输出选择方式包括–max-count=10 (最多显示10个commit), –until=2005-12-10 等等。

  12. 只查看curses/ 目录中,v2.43标签以来的变更。

个人开发者 (参与者)

在一个小组中参与协同工作的开发者需要学会下面一些命令,以和他人沟通,同时也需要独立的开发者们会用到的命令。

实例:

克隆上游源,并在此基础上工作。最后,将变更送回到上游。

$ git clone git://git.kernel.org/pub/scm/.../torvalds/linux-2.6 my2.6$ cd my2.6$ edit/compile/test; git commit -a -s (1)
$ git format-patch origin (2)
$ git pull (3)
$ git log -p ORIG_HEAD.. arch/i386 include/asm-i386 (4)
$ git pull git://git.kernel.org/pub/.../jgarzik/libata-dev.git ALL (5)
$ git reset --hard ORIG_HEAD (6)
$ git gc (7)
$ git fetch --tags (8)
  1. 若干次类似操作。

  2. 从本地分支输出 email 形式的补丁用于提交。

  3. git pull从origin取出更新并合并如当前分支。

  4. 取出之后,查看上次取出后上游产生的变更,只观察我们感兴趣的部分。

  5. 从某仓库的某分支取出变更并合并。

  6. 回退上一次取出的内容。

  7. 用垃圾回收去掉上次回退造成的残余物。

  8. 时常从origin取出标签信息,并放到.git/refs/tags/目录下。

推送入另一个仓库。

satellite$ git clone mothership:frotz frotz (1)
satellite$ cd frotz
satellite$ git config --get-regexp '^(remote|branch)\.' (2)
remote.origin.url mothership:frotz
remote.origin.fetch refs/heads/*:refs/remotes/origin/*
branch.master.remote origin
branch.master.merge refs/heads/master
satellite$ git config remote.origin.push \
           master:refs/remotes/satellite/master (3)
satellite$ edit/compile/test/commit
satellite$ git push origin (4)

mothership$ cd frotz
mothership$ git checkout master
mothership$ git merge satellite/master (5)
  1. mothership 主机上的你的归属目录中有一个 frotz 仓库,从那里克隆仓库到 satellite 主机上来。

  2. 克隆缺省会设置这些配置变量。这样,git pull 命令就会从 mothership 取出内容,并放到到本地的remotes/origin/*目录下的跟踪分支来。

  3. 设置git push命令将本地的master分支推送到 mothership 主机的 remotes/satellite/master分支去。

  4. push 将会把我们的工作存储到 mothership 主机的 remotes/satellite/master 跟踪分支上。你可以把这作为是一种备份手段。

  5. 在 mothership 主机上,合并 satellite 主机上来的工作到 master 分支。

从某个标签开始分支。

$ git checkout -b private2.6.14 v2.6.14 (1)
$ edit/compile/test; git commit -a
$ git checkout master
$ git format-patch -k -m --stdout v2.6.14..private2.6.14 |
  git am -3 -k (2)
  1. 基于一个众所周知的(但在什么东西后面的)标签创建一个私有分支。

  2. 不适用正式的 "merge",将 private2.6.14 分支上的改动移植到 master 分支上来。

集成者

项目中的中心人物会扮演一个集成者的角色,收集他人提供的变更,查看并集成在一起,对外发布结果。与一般参与者相比,集成者还需要用到这样一些命令。

实例:

我的典型 GIT 一天。

$ git status (1)
$ git show-branch (2)
$ mailx (3)
& s 2 3 4 5 ./+to-apply
& s 7 8 ./+hold-linus
& q
$ git checkout -b topic/one master
$ git am -3 -i -s -u ./+to-apply (4)
$ compile/test
$ git checkout -b hold/linus && git am -3 -i -s -u ./+hold-linus (5)
$ git checkout topic/one && git rebase master (6)
$ git checkout pu && git reset --hard next (7)
$ git merge topic/one topic/two && git merge hold/linus (8)
$ git checkout maint
$ git cherry-pick master~4 (9)
$ compile/test
$ git tag -s -m "GIT 0.99.9x" v0.99.9x (10)
$ git fetch ko && git show-branch master maint 'tags/ko-*' (11)
$ git push ko (12)
$ git push ko v0.99.9x (13)
  1. 查看有什么正在做、没提交的东西。

  2. 查看有哪些局部分支,思考他们是否成熟了。

  3. 阅读邮件,保存那些可以用的,也保存那些还不是很成熟的。

  4. 交互式打补丁,使用自己的签名。

  5. 如果需要,建立局部分支,并用自己的签名打补丁。

  6. 将内部的还没有并入 master 也没有成为稳定分支的一部分的局部分支用 rebase 移植到 master 上。

  7. reset pu,让它总是从 next 开始。

  8. 将仍在工作中的分支们合并在一起。

  9. 对先前的版本进行问题修正。

  10. 建立一个有签名的标签。

  11. 确信我没有偶然将 master 退回到已经发布出去的版本的后面了。ko 是我在 kernel.org 的仓库的简写,看起来类似这样:

    $ cat .git/remotes/koURL: kernel.org:/pub/scm/git/git.gitPull: master:refs/tags/ko-masterPull: next:refs/tags/ko-nextPull: maint:refs/tags/ko-maintPush: masterPush: nextPush: +puPush: maint

    在 git show-branch 的输出中, master 应该包含 ko-master 的所有内容, 而 next 应该包含ko-next 的所有内容。

  12. 推送出最新版本。

  13. 把标签也推送出去。

仓库管理

仓库管理员会使用如下工具来设置和维护开发者们对仓库的访问方法。

  • git-daemon(1):允许匿名下载仓库。

  • git-shell(1) 可以被用于restricted login shell 来将中心仓库共享给用户。

update hook howto 有一个不错的关于管理一个共享的中心仓库的例子。

实例:

我们假设如下内容位于 /etc/services 文件之内:

$ grep 9418 /etc/servicesgit             9418/tcp                # Git Version Control System

用 git-daemon 通过 inetd 提供 /pub/scm 服务。

$ grep git /etc/inetd.confgit     stream  tcp     nowait  nobody \  /usr/bin/git-daemon git-daemon --inetd --export-all /pub/scm

实际配置文件中应该是一行。

用 git-daemon 通过 xinetd 提供 /pub/scm 服务。

$ cat /etc/xinetd.d/git-daemon# default: off# description: The git server offers access to git repositoriesservice git{        disable = no        type            = UNLISTED        port            = 9418        socket_type     = stream        wait            = no        user            = nobody        server          = /usr/bin/git-daemon        server_args     = --inetd --export-all --base-path=/pub/scm        log_on_failure  += USERID}

设置时请参考你的 xinetd(8) 文档,上述设置适用于 Fedora 系统。其他系统可能有所不同。

仅提供 push/pull 权限给开发者。

$ grep git /etc/passwd (1)
alice:x:1000:1000::/home/alice:/usr/bin/git-shell
bob:x:1001:1001::/home/bob:/usr/bin/git-shell
cindy:x:1002:1002::/home/cindy:/usr/bin/git-shell
david:x:1003:1003::/home/david:/usr/bin/git-shell
$ grep git /etc/shells (2)
/usr/bin/git-shell
  1. 将 login shell 设置为 /usr/bin/git-shell, 这样,只有git push 和 git pull 操作才被允许。用户如果要访问计算机,需要通 ssh 访问权限。

  2. in many distributions /etc/shells needs to list what is used as the login shell. 很多发布版中,只有 /etc/shells 列出的程序才能作为 login shell.

CVS 风格的共享仓库。

$ grep git /etc/group (1)
git:x:9418:alice,bob,cindy,david
$ cd /home/devo.git
$ ls -l (2)
  lrwxrwxrwx   1 david git    17 Dec  4 22:40 HEAD -> refs/heads/master
  drwxrwsr-x   2 david git  4096 Dec  4 22:40 branches
  -rw-rw-r--   1 david git    84 Dec  4 22:40 config
  -rw-rw-r--   1 david git    58 Dec  4 22:40 description
  drwxrwsr-x   2 david git  4096 Dec  4 22:40 hooks
  -rw-rw-r--   1 david git 37504 Dec  4 22:40 index
  drwxrwsr-x   2 david git  4096 Dec  4 22:40 info
  drwxrwsr-x   4 david git  4096 Dec  4 22:40 objects
  drwxrwsr-x   4 david git  4096 Nov  7 14:58 refs
  drwxrwsr-x   2 david git  4096 Dec  4 22:40 remotes
$ ls -l hooks/update (3)
  -r-xr-xr-x   1 david git  3536 Dec  4 22:40 update
$ cat info/allowed-users (4)
refs/heads/master       alice\|cindy
refs/heads/doc-update   bob
refs/tags/v[0-9]*       david
  1. 将开发者们都放入 git 组。

  2. 并让整个共享仓库对组可写。

  3. 使用 Carl 写的 Documentation/howto 中的 update-hook 例子用于分支策略控制。

  4. alice 和 cindy 可以向 master 中 push 内容,只有 bob 可以向 doc-update 中 push 内容。david 是对外发布管理员,是惟一可以创建并 push 版本标签的人。

支持哑协议传输的 HTTP Server.

dev$ git update-server-info (1)
dev$ ftp user@isp.example.com (2)
ftp> cp -r .git /home/user/myproject.git
  1. 确认 info/refs 和 objects/info/packs 的内容是更新过的。

  2. 上传到你的 ISP 提供的公共 HTTP 服务器。

原文最新更新: 23-Apr-2008 16:08:38 UTC

翻译时间:2008年5月3日,王旭 (gnawux<at>gmail.com)

[译文] git 简要教程 (适用于 1.5.1 或更新版本)

May 2nd, 2008

原文链接:http://www.kernel.org/pub/software/scm/git/docs/tutorial.html

这个教程将介绍如何将一个新的项目导入到 git 之中,如何修改项目并如何将这些变更与其他开发者分享。

如果你更感兴趣如何用 git 取出一个项目,比如,测试软件的最新版本,你可能更应该看看The Git User’s Manual的前两章。

首先,记住你可以用 man 来获取 git 的文档,比如 "git diff" 的文档可以用如下命令察看:

$ man git-diff

在做任何改动之前,最好把自己的名字和 email 地址介绍给大家,最简单的方法就是:

$ git config --global user.name "Your Name Comes Here"$ git config --global user.email you@yourdomain.example.com

导入一个新项目

假设你有一个名为 project.tar.gz 的 tarball 作为项目的初始内容。你可以如下操作来把它至于 git 版本控制之下。

$ tar xzf project.tar.gz$ cd project$ git init

Git 将会如下回复:

Initialized empty Git repository in .git/

现在,你已经初始化了工作目录——你可能已经注意到了名为 ".git" 的一个新目录了。

下一步就是使用 git-add(1) 命令告诉 git 当前目录的所有文件 (注意这个点:.) 全是项目的一个快照:

$ git add .

这个快照目前存放在一个临时区域之中,在 git 中称为 "index"(索引)。使用 git-commit(1) 命令,你可以把 index 的所有内容永久性地存放到软件仓库之中:

$ git commit

这条命令会向你提示输入版本变更信息。这样,你的项目的第一个版本就已经存入 git 之中了。

进行修改

修改一些文件之后,你可以将更新这些内容到 index 之中:

$ git add file1 file2 file3

现在,你已经准备就绪,可以提交了。现在你可以使用 git-diff(1) 命令的 –cache 参数:

$ git diff --cached

(如果不使用 –cached 参数,git-diff(1)会显示所有还没添加进 index 的已经做出的改动。) 你也可以使用 git-status(1)来获得一些当前状况的概要信息:

$ git status# On branch master# Changes to be committed:#   (use "git reset HEAD <file>..." to unstage)##       modified:   file1#       modified:   file2#       modified:   file3#

如果你需要进行更多改动,现在就可以进行,然后可以添加到 index 之中。最后,使用如下命令提交改动:

$ git commit

这将再次要求你输入关于这次改动内容的描述性信息,之后记录下的项目新版本。

此外,如果想省掉提交之前的git add命令,你可以直接用

$ git commit -a

这样会自动检测所有修改过的文件 (不包括新文件) ,并一气呵成地将它们添到 index 之中,并提交。

关于提交的描述信息: 虽然这个信息不是必须的,但提交信息描述最好以一行不超过50个字符的概要性信息来开头,在一个空行之后再进行更多的描述。比如那些将 commit 转化为 email 的工具就会把这个第一行作为邮件标题,其余的提交内容则放在邮件内部。

Git 跟踪内容而不是文件

很多版本控制系统提供了一个 "add" 命令用来记录一个新文件。而 git 的 "add" 命令更加简单也更加强大: git add既用于新文件也用于新近改动的文件,在所有这些情况下,它在 index 中对所有的文件与状态进行一次快照,这样就可以在下一次 commit 命令中进行提交。

Viewing project history

在任何时候,你都可以如下查看所有你进行过的改动

$ git log

你可能还想看到每一步改进中的所有完整的 diff ,这可以使用如下命令

$ git log -p

浏览改动的概要对于获得每一步修改的情况常常是比较有用的,可以使用如下命令

$ git log --stat --summary

管理分支

一个 git 仓库可以包含多个开发分支。使用如下命令可以建立一个称为 "experimental" 的新分支

$ git branch experimental

如果你运行命令

$ git branch

你将可以得到类似下面的已有分支的列表

  experimental* master

"experimental" 就是你刚刚建立的那个分支,而 "master" 分支则是建立仓库的时候自动创建的缺省分支,里面的星号表示你当前所在的分支;输入命令

$ git checkout experimental

就可以切换到 experimental 分支。现在修改一个文件,并提交改变,然后重新回到 master 分支:

(edit file)$ git commit -a$ git checkout master

你会发现,刚才的变更已经不可见了,这是因为这个改变是发生于 experimental 分支的,而你现在已经回到 master 分支了。

现在,你可以在 master 分支上做一些不同的变更:

(edit file)$ git commit -a

这里,两个分支已经产生不同了,每个分支上都发生了不同的改动。要把 experimental 中的改变也合并到 master 之中,运行命令

$ git merge experimental

如果两者的改变并不冲突,那么就算是完成了。而如果这里有冲突,有问题的文件左边会显示出标记,以表明这个文件发生了冲突;

$ git diff

上述命令将会列出具体的冲突。一旦你编辑文件解决了冲突,

$ git commit -a

这个命令将把合并的结果提交。最终,

$ gitk

会显示出漂亮的图标以展示历史变革。

这里你可以使用如下命令删除 experimental 分支。

$ git branch -d experimental

这个命令会确定 experimental 中的所有改动已经在当前分支当中了。

如果你在 crazy-idea 分支中进行开发,然后又后悔了,你可以用如下命令删除分支

$ git branch -D crazy-idea

分支操作十分简单而且代价低廉,所以适合于尝试一些东西。

使用 git 进行协作

假设 Alice 在 /home/alice/project 中的 git 仓库启动了一个新项目,而在本机中也拥有 home 目录的 Bob 想要贡献一些代码。

他可以以如下工作开始:

$ git clone /home/alice/project myrepo

这会新建一个名为 "myrepo" 的目录,里面包含了 Alice 的仓库的一份克隆。这份克隆与原始项目完全一致,可以处理自己的一份原始项目历史。

之后,Bob 进行了一些变更并提交了这些变动:

(edit files)$ git commit -a(repeat as necessary)

当他完成的时候,他告诉 Alice 将 /home/bob/myrepo 之中的变动导入到原始仓库之中。她使用如下命令来完成这一工作:

$ cd /home/alice/project$ git pull /home/bob/myrepo master

这会合并 Bob 的 "master" 分支到 Alice 的当前分支。如果 Alice 也已经修改了某些内容,她需要手工修复冲突。(注意,"master" 参数实际上并不是必要的,因为这是缺省分支。)

"pull" 命令包括两个操作: 从远端分支中取出改动,然后合并到当前分支之中。

当你只在一个很小的小组里工作的时候,通常不会频繁地访问同一个仓库。通过定义仓库的快捷方式,可以让访问远程仓库更方便一些:

$ git remote add bob /home/bob/myrepo

这样,Alice 可以如下用 "git fetch" 命令仅取出改动,而不把它们合并到当前分支之中:

$ git fetch bob

和长格式不同,当 Alice 使用 git remote设置的快捷方式从 Bob 的仓库中获取内容的时候,取出的内容存储在一个 remote tracking 分支之中,在本例中是 bob/master。所以,如下操作:

$ git log -p master..bob/master

将会列出从 Bob 从 Alice 的主分支中分支出去以后的所有改动。

检查了这些变动之后,Alice 可以将这些变动合并到自己的 master 分支中:

$ git merge bob/master

这个合并也可以通过从自己的 remote tracking 分支中 pull 来做到,如:

$ git pull . remotes/bob/master

注意,git pull 总是合并进当前的分支,不论命令行给出的是什么。

之后,Bob 可以如下使用 Alice 的最近改动更新自己的仓库

$ git pull

这里,他不需要给出 Alice 的仓库的位置;当 Bob 克隆了 Alice 的仓库的时候,git 在仓库设置中保存了她的仓库的位置,即 pull 所使用的位置:

$ git config --get remote.origin.url/home/alice/project

(git-clone 创建的完整配置信息可以用 "git config -l" 获得,git-config(1)的 man page 解释了所有选项的含义。)

Git 也在 "origin/master" 分支保存了一份 Alice 的主分支的原始拷贝:

$ git branch -r  origin/master

如果其后 Bob 决定转到另一台主机上工作,他还可以通过 ssh 来克隆原始仓库:

$ git clone alice.org:/home/alice/project myrepo

此外,git 本身也有远程协议,并且可以使用 rsync 或 http,详细情况可以查看 git-pull(1)的 man page。

Git 也可以使用类似 CVS 的工作方式,使用一个中心仓库,所有用户将改动推送到仓库之中,相关内容可以查阅 git-push(1)的手册页或git for CVS users

浏览历史

Git 的历史是通过一系列相互关联的 commit 构成的。我们已经通过 git log 命令看到了这些提交的列表。注意,每个 git log 条目的第一行是那次提交的名称:

$ git logcommit c82a22c39cbc32576f64f5c6b3f24b99ea8149c7Author: Junio C Hamano <junkio@cox.net>Date:   Tue May 16 17:18:22 2006 -0700

    merge-base: Clarify the comments on post processing.

把这个名称用于 git show 命令,可以得到提交的详情。

$ git show c82a22c39cbc32576f64f5c6b3f24b99ea8149c7

不过还有其他办法来指代这次提交。你可以只使用名称的开始部分,只要它足够长,保证在所有提交中是惟一的就行了:

$ git show c82a22c39c   # the first few characters of the name are                        # usually enough$ git show HEAD         # the tip of the current branch$ git show experimental # the tip of the "experimental" branch

每一次提交通常都有一次提交作为 "parent" ,它是项目的前一个状态:

$ git show HEAD^  # to see the parent of HEAD$ git show HEAD^^ # to see the grandparent of HEAD$ git show HEAD~4 # to see the great-great grandparent of HEAD

要注意,合并提交可能会有多个 "parent":

$ git show HEAD^1 # show the first parent of HEAD (same as HEAD^)$ git show HEAD^2 # show the second parent of HEAD

你还可以给你的提交一个名字; 命令

$ git-tag v2.5 1b2e1d63ff

让你可以使用 "v2.5" 来指代 1b2e1d63ff。如果你想把这个名字与他人共享 (比如标记一个发布版本),你应该建立一个 "tag" 对象,可能还需要签署它; 详情请查看 git-tag(1)的 man page。

任何 git 命令都可以使用上述任何一种名字。比如:

$ git diff v2.5 HEAD     # compare the current HEAD to v2.5$ git branch stable v2.5 # start a new branch named "stable" based                         # at v2.5$ git reset --hard HEAD^ # reset your current branch and working                         # directory to its state at HEAD^

小心使用上述最后一个命令: 这将丢失工作目录中的所有改动,他还会清除本分支内随后的所有提交。如果这个分支是包含这些提交的惟一分支,它们将永远地丢失了。此外,不要对一个公众可见的、有其他开发者从中 pull 内容的分支使用 "git reset" 命令,这将导致一些不必要的合并来清除其他开发者的历史信息。如果你需要取消已经推送的改动,可以使用git-revert(1)命令。

git grep 可以在项目的所有版本历史中寻找字符串,如下命令

$ git grep "hello" v2.5

会在版本 v2.5 中寻找所有 "hello" 的踪迹。

如果你不提供 commit 名称,git grep 会在你当前的目录中搜索所有由 git 管理的文件。于是,如下命令

$ git grep "hello"

是搜索 git 跟踪的所有文件的便捷的方式。

很多 git 命令可以处理一组提交,可以通过多种方式来指定版本。这里是一些 git log 的例子:

$ git log v2.5..v2.6            # commits between v2.5 and v2.6$ git log v2.5..                # commits since v2.5$ git log --since="2 weeks ago" # commits from the last 2 weeks$ git log v2.5.. Makefile       # commits since v2.5 which modify                                # Makefile

你给出的范围的上下边界不一定是严格的时间先后关系,比如,"stable-release" 分支可能会在 "master" 分支之后相当长一段时间才会引入同一个提交内容,这样

$ git log stable..experimental

将会列出 expermental 分支之中已经有的,而 stable 分支却还没有的提交,而命令

$ git log experimental..stable

将会列出 stable 中已有、但 experimental 却没有的提交。

"git log" 命令有一个弱点:必须将所有提交在一个列表中呈现出来。当项目历史中有多个不同开发分支并最终合并到一起时,"git log" 中呈现出来的顺序可能没什么意义。

大部分有大量开发者的项目 (比如 linux kernel 或 git 本身) 都经常合并分支,gitk 可以更好地将这些合并变化展示出来。比如,

$ gitk --since="2 weeks ago" drivers/

这个命令允许你浏览过去两个星期中在 "drivers" 目录之中的任意提交。(注意: 你可以按住 ctrl 键然后用 "-" 和 "+" 来调整 gitk 的字体大小。)

最后,大部分命令可以带有文件名,这可以用于指定某次提交中的某个文件,从而指定某个文件的某个版本:

$ git diff v2.5:Makefile HEAD:Makefile.in

你还可以用 "git show" 命令去查看任意文件的任意版本:

$ git show v2.5:Makefile

下一步学习

这个教程应该足够你的项目进行基本的项目发布版本管理。不过,要完全深入地理解 git 的强大功能可能还需要理解两个简单的概念:

  • 对象数据库是个相当阳春的系统,用于存储你的项目的历史,包括文件、目录以及各次提交。

  • 索引文件是目录树的状态的缓存,用于建立提交、取出工作目录并保存一次合并中包含的不同的目录树。

本教程的第二部分解释了对象数据库、索引文件以及一些其他你在使用 git 中所需要了解的零七八碎的概念。

如果你不想在这条路上继续下去,还有一些细枝末节的东西可能十分有趣:

  • git-format-patch(1), git-am(1): 用于将一系列 git 提交转化成 email 发送的补丁或反之,对于 linux 内核这样的高度依赖于邮件发送的补丁的项目来说十分有用。

  • git-bisect(1): 当你的项目发生问题 (功能或性能上的退步) 的时候,一个跟踪发现错误的方法就是通过历史发现那个罪魁祸首的提交。Git bisect 可以帮助你进行二分查找发现那个提交。它能在一个有很多分支合并的具有复杂非线性历史的项目中十分灵巧地进行接近最优的搜索。

  • Everyday GIT with 20 Commands Or So

  • git for CVS users.

原文最后更新 25-Aug-2007 03:53:15 UTC

译文初稿:2007年7月,王旭 (gnawux<at>gmail.com)

译文最后更新:2008年5月2日,王旭 (gnawux<at>gmail.com)

GIT简要教程:第二部分

May 1st, 2008

阅读本教程之前,你应该已经阅读过《GIT简要教程》(译注:本人曾经翻译过)了。

本教程将介绍GIT架构中的两个基础概念——对象数据库和索引文件——并为读者提供阅读其他GIT文档所需的基础知识。

GIT对象数据库

让我们从一个新项目开始,并加入少量的变更历史:

$ mkdir test-project$ cd test-project$ git initInitialized empty Git repository in .git/$ echo 'hello world' > file.txt$ git add .$ git commit -a -m "initial commit"Created initial commit 54196cc2703dc165cbd373a65a4dcf22d50ae7f7 create mode 100644 file.txt$ echo 'hello world!' >file.txt$ git commit -a -m "add emphasis"Created commit c4d59f390b9cfd4318117afde11d601c1085f241

git 响应 commit 时给出的 40 位 16 进制数是什么?

在教程的第一部分里我们就看到,commit 都有类似的 40 位 16 进制的名字。 git的版本历史中,每个对象都被使用这样一个字,它是对象内容的 SHA1 哈希结果;对于变更和其他各种东西来说,这样的名字保证了 git 永远不会把同样的东西保存两次 (因为同样的内容会产生同样的 SHA1 结果),并且,一个 git 对象的内容的永远都不会改变 (因为内容改变了名字也就肯定会跟着变了)。

当然,如果你重复上面的例子的话,将会得到不一样的 SHA1 哈希结果,因为创建 commit 的时间和人都不一样。

我们可以用 cat-file 命令来向 git 查询这个对象。不要用上面那 40 位数字,应该用你自己的 commit 名字来做这个操作。事实上,你可以用开头几位数字,而不一定要敲全整个 40 位:

$ git-cat-file -t 54196cc2commit$ git-cat-file commit 54196cc2tree 92b8b694ffb1675e5975148e1121810081dbdffeauthor J. Bruce Fields <bfields@puzzle.fieldses.org> 1143414668 -0500committer J. Bruce Fields <bfields@puzzle.fieldses.org> 1143414668 -0500

initial commit

一个 tree 可以对应一个或多个 blob 对象,每个都对应于一个文件。此外,一个 tree 还可以对应于多个 tree 对象,从而可以创建一个目录树。可以通过 ls-tree 命令来查看树中的内容 (注意,一个足够长的 tree 名字的开始部分就足矣了):

$ git ls-tree 92b8b694100644 blob 3b18e512dba79e4c8300dd08aeb37f8e728b8dad    file.txt

这样,我们看到这个 tree 里面有一个文件。文件对应的 SHA1 哈希值是该文件内容的索引:

$ git cat-file -t 3b18e512blob

blob 对应着文件数据,我们可以用 cat-file 来看其中的内容:

$ git cat-file blob 3b18e512hello world

注意,这是那个老文件的内容;即 initial tree 这个 commit 对象里所对应的 tree 是当时记录下来的目录的状态。

所有的对象都在 git 目录之中,以其 SHA1 名称存放:

$ find .git/objects/.git/objects/.git/objects/pack.git/objects/info.git/objects/3b.git/objects/3b/18e512dba79e4c8300dd08aeb37f8e728b8dad.git/objects/92.git/objects/92/b8b694ffb1675e5975148e1121810081dbdffe.git/objects/54.git/objects/54/196cc2703dc165cbd373a65a4dcf22d50ae7f7.git/objects/a0.git/objects/a0/423896973644771497bdc03eb99d5281615b51.git/objects/d0.git/objects/d0/492b368b66bdabf2ac1fd8c92b39d3db916e59.git/objects/c4.git/objects/c4/d59f390b9cfd4318117afde11d601c1085f241

并且,这些文件的内容只是压缩数据和用来标识它们的类型和长度的头部。可能的类型包括 blob, tree, commit 和 tag。

最容易被找到的 commit 是 HEAD commit,可以通过 .git/HEAD 找到:

$ cat .git/HEADref: refs/heads/master

可以看到,这个命令给出了我们的当前分支,并且给出了 .git 目录下的一个文件名,该文件中包含着指向对应的 commit 对象的 SHA1 名字,这样我们就可以通过 cat-file 命令来查看这个 commit:

$ cat .git/refs/heads/masterc4d59f390b9cfd4318117afde11d601c1085f241$ git cat-file -t c4d59f39commit$ git cat-file commit c4d59f39tree d0492b368b66bdabf2ac1fd8c92b39d3db916e59parent 54196cc2703dc165cbd373a65a4dcf22d50ae7f7author J. Bruce Fields <bfields@puzzle.fieldses.org> 1143418702 -0500committer J. Bruce Fields <bfields@puzzle.fieldses.org> 1143418702 -0500

add emphasis

这里的 tree 对象对应着新的 tree 的状态:

$ git ls-tree d0492b36100644 blob a0423896973644771497bdc03eb99d5281615b51    file.txt$ git cat-file blob a0423896hello world!

而 parent 对象对应着上一个 commit:

$ git-cat-file commit 54196cc2tree 92b8b694ffb1675e5975148e1121810081dbdffeauthor J. Bruce Fields <bfields@puzzle.fieldses.org> 1143414668 -0500committer J. Bruce Fields <bfields@puzzle.fieldses.org> 1143414668 -0500

initial commit

这里,tree 对象就是我们开始时看过的那个,这个 commit 比较特殊,没有 parent。

大部分的 commit 都有且仅有一个 parent,不过一个 commit 有几个 parent 的情况也不少见。当一个 commit 表示一次合并的时候,parent 将指向被合并的各个分支的头部。

介绍了 blob, tree 和 commit,惟一还没有介绍的对象类型就是 tag 了,这个我们就不在这里介绍了,可以参考它的手册页 git-tag(1)

现在,我们总结一下 git 的版本历史中如何使用对象数据库:

  • commit 对象会指向一个 tree 对象,即当时的目录树的镜像,还会指向 parent commit,以表征项目的版本历史。

  • tree 对象代表一个目录的一个状态,将目录名与包含文件内容的 blob 对象和包含子目录信息的 tree 对象联系在一起。

  • blob 对象包含文件内容,没有其他结构。

  • 每个分支的头部的 commit 对象的引用位于 .git/ref/heads/ 目录。

  • 当前分支的名字存储在 .git/HEAD 文件之中。

顺便提一句,很多命令都用一个 tree 作为参数。不过上面已经看到,tree 可以有多种指代方式——tree 的 SHA1 名称、指向该 tree 的 commit 名称,当该 tree 恰好是某分支头部时,该分支的名称,等等——大部分这样的命令都接受这些名字作为参数。

在命令格式描述中,tree-ish 有时被用于表示这样的参数。

索引文件

上文中用于提交 commit 的主要方法是 "git commit -a",这条命令将所有我们的工作拷贝中改动过的文件做成一个 commit。不过,如果我们只想提交部分文件怎么办?部分文件中的部分改动呢?

如果看看幕后 commit 是怎么产生的,那我们将可以得到生成 commit 的更灵活的方法。

继续我们的测试项目,现在再次修改 file.txt:

$ echo "hello world, again" >>file.txt

不过这次,我们不立刻 commit,让我们多做一个中间步骤,并查看 diff,看看都发生了什么:

$ git diff--- a/file.txt+++ b/file.txt@@ -1 +1,2 @@ hello world!+hello world, again$ git add file.txt$ git diff

最后一个 diff 是空的,不过还没有进行 commit 呢,head 还没有包含这行新的内容呢:

$ git-diff HEADdiff --git a/file.txt b/file.txtindex a042389..513feba 100644--- a/file.txt+++ b/file.txt@@ -1 +1,2 @@ hello world!+hello world, again

所以,git diff 实际是在和 head 之外的什么东西比较。实际上,这个被比较的东西是索引文件,它以二进制形式存储在 .git/index 之中,其内容可以用 ls-files 查看:

$ git ls-files --stage100644 513feba2e53ebbd2532419ded848ba19de88ba00 0       file.txt$ git cat-file -t 513feba2blob$ git cat-file blob 513feba2hello world!hello world, again

我们的 git add 的实际工作是存储一个新的 blob ,之后将其索引信息放到索引文件中。如果我们再次修改文件,将会在 git-diff 中看到这些变更:

$ echo 'again?' >>file.txt$ git diffindex 513feba..ba3da7b 100644--- a/file.txt+++ b/file.txt@@ -1,2 +1,3 @@ hello world! hello world, again+again?

使用正确的参数,git diff 也可以显示出上次 commit 以来的变更或是索引和最后一次 commit 之间的变更:

$ git diff HEADdiff --git a/file.txt b/file.txtindex a042389..ba3da7b 100644--- a/file.txt+++ b/file.txt@@ -1 +1,3 @@ hello world!+hello world, again+again?$ git diff --cacheddiff --git a/file.txt b/file.txtindex a042389..513feba 100644--- a/file.txt+++ b/file.txt@@ -1 +1,2 @@ hello world!+hello world, again

我们随时都可以使用 (没有 -a 参数的) git commit 创建一个新的 commit,只提交包含在索引文件中的变更,而不包含工作拷贝中的其他变更:

$ git commit -m "repeat"$ git diff HEADdiff --git a/file.txt b/file.txtindex 513feba..ba3da7b 100644--- a/file.txt+++ b/file.txt@@ -1,2 +1,3 @@ hello world! hello world, again+again?

缺省地,git commit 使用索引文件创建 commit,而不是工作拷贝;-a 参数的提交是首先使用工作拷贝中的全部变更更新索引,然后提交 commit。

最后,有必要看看 git add 对索引文件的效果:

$ echo "goodbye, world" >closing.txt$ git add closing.txt

The effect of the "git add" was to add one entry to the index file:

$ git ls-files --stage100644 8b9743b20d4b15be3955fc8d5cd2b09cd2336138 0       closing.txt100644 513feba2e53ebbd2532419ded848ba19de88ba00 0       file.txt

并且,由于能够通过 cat-file 查看,新的一项指向了当前的文件内容:

$ git cat-file blob 8b9743b2goodbye, world

status 命令是一个很有用的快速查看状态的方法:

$ git status# On branch master# Changes to be committed:#   (use "git reset HEAD <file>..." to unstage)##       new file: closing.txt## Changed but not updated:#   (use "git add <file>..." to update what will be committed)##       modified: file.txt#

由于 closing.txt 已经被缓存在索引文件中了,所以被列为将要提交的改动。而 file.txt 已经改动了但没被包含在索引中,它被标记为改动了但没有更新的。这时使用 git commit 将创建一个 commit 来添加 closing.txt (使用它的新内容),但不会修改 file.txt 。

此外,请记住 git diff 可以显示 file.txt 的变化,但不会显示 closing.txt 的变更,因为当前的 closing.txt 和索引文件中的是完全一致的。

除了作为新 commit 的中间环节,索引文件还在 check out 一个新分支的时候从对象数据库中取出,也用于合并操作时保存相关的分支。这里请参考相关手册页和核心教程

下面是?

现在,你已经了解了阅读 git 所有手册页所需的全部知识;一个不错的起点是学习每日 git中的命令。不知道的名词应该可以从词汇表中找到.

Git 手册包含了 git 的更全面介绍。

CVS 迁移 文档解释了如何将一个 CVS 仓库加入到 git 中,以及如何以 CVS 的方式使用 git。

对于一些有趣的 git 例子,可以看看HOWTO

对于开发者,核心教程 深度介绍了 git 的底层机制,比如,创建一个新的 commit。

原文最后更新时间: 20-May-2007 09:08:18 UTC

王旭 (gnawux<at>gmail.com) 2008年5月1日 翻译

Switch to our mobile site