[FYI]
a> Google Talk: Linus Torvalds on git
http://www.youtube.com/watch?v=4XpnKHJAok8
b> Google Talk: Randal Schwartz on git
http://www.youtube.com/watch?v=8dhZ9BXQgc4
1, Linus的git tree,AKA 'mainline kernel':
$ git-clonegit://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git
其他的git trees可以在http://git.kernel.org/找到,点击进去就能看到GIT URL。
2, 安装gitdocs
要有asciidoc、wish命令,安装tcl/tk和asciidoc. wish命令在tk-x.xx.xx包中。
$ sudo make install-doc
3, TERMsin git
SHA1ID : 由一个commit的内容和msg共同产生的一个全局唯一的ID。
用前5个字母就够了,git-describe列出来的就是。
HEAD : 当前branch的lastest commit,也叫tip。 (remember 'hg tip'?)
HEAD的SHA1 ID可以用git-show HEAD来产看
ORIG_HEAD : 原来的HEAD。 可以用git-show ORIG_HEAD来查看其SHA1 ID。
4,使用git
[FYI]自从git-1.5.4,'git-xyz'这种用法就不提倡了,而推荐'git xyz'风格。 git的后续版本中将在makeinstall
时不再安装'git-xyz'这些hardlinks。
当如果执行git--exec-path输出的目录中依然有git-xyz这些脚本,你还是可以把这个路径加到PATH环境变量中,
这样还能够使用git-xyz形式的脚本。
config
------
我的一些简单的配置:
$ git-config user.name "Jike Song"
$ git-config user.email albcamus@gmail.com
$ git-config core.editor vim
$ git-config core.pager "less -N"
$ git-config color.difftrue //显示diff时色彩高亮
$ git-config alias.cocheckout //给git checkout取个别名,这样只输入git co即可
$ git-config sendemail.smtpserver /usr/bin/msmtp
注意,这会在当前repository目录下的.git/config中写入配置信息。如果git-config加了--global
选项,配置信息就会写入到~/.gitconfig文件中。 因为你可能用不同的身份参与不同的项目,而多个
项目都用git管理,所以建议不用--global配置。
$ git-val-l //列出git变量
init
----
$git-init-db //创建一个.git/目录,初始化一个空的git仓库
//这个目录在git-clone时也会创建。也就是说clone时会自动初始化git
//仓库里需要的东西
clone
-----
$ git-clonegit://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git[dir name]
[dir name]是你想让这个仓库叫什么名字。 如果不指定,就会等同于目标仓库的名字。
注意,这种git server形式的repository,都有一个filename.git文件;而对于*.git的操作,也可以
针对.git所在的目录:
$ mkdir tmp/
$ cd tmp/
$ git-clone ~/Sources/linux-2.6
或者通过ssh:
$ git-clone arc@host.xyz.com:/home/arc/Sources/linux-2.6
此时当前目录下有一个.git/目录. 以下我们都在linux-2.6/下演示:
pull
----
$git-pull //更新本地的git tree。 如果自从你clone了linus tree之后,linus tree
//有新的改动,那么把这些更改更新到你的本地tree中
//类似于cvs update
FYI: git-clone和git-pull都会默认调用git-merge。
FYI: 每天git-pull更新技巧:
1) git-describe,例如目前是v2.6.26-rc8-12
2) git-pull -v
3) git-describe,例如是v2.6.26-rc8-22
4) git-log -p -10查看变化
diff
----
$git-diff
选项:
--color diff语法高亮(可以git-config color.diff true)
--ignore-space-at-eol 忽略行尾的whitespace
-b
--ignore-space-change 忽略行尾的whitespace,并且认为所有的whitespace都是一样的
-w
--ignore-all-space 比较两行的时候,完全忽略whitespace。这样,即使是一行有很多
whitespaces,另一行文字一样但是没有whitespace,git也认为这两
行内容一致。
FYI: diff不同的branches
$ git-diff jike..master
$ git-diff jike master
$ git-diff jike...master
git-diff的详细用法:
列出自己的tree HEAD和某一个tag的不同:
$ git-diff v2.6.22
列出某一个文件,和以前某个tag的该文件的不同:
$ git diff v2.6.20 init/main.c
注意结果中+表示自己的tree,-表示2.6.20的。
列出两个tags之间的不同:
$ git-diff v2.6.20..v2.6.21-rc1
列出两个commits之间的不同(Notes,是这两次commits之间的commits引入的所有改动):
$ git-diff2a062ab483f5afd764fb20631ee960672946a4be..a44008f2372684bacfab03de5039f68b613c5b53
列出两个tags的某一文件的不同:
$ git-diff v2.6.23 v2.6.24-rc1 init/main.c
or:
$ git-diff v2.6.25-rc3:kernel/module.cv2.6.25-rc4:kernel/module.c
or:
$ git-diff v2.6.25-rc3:kernel/module.c HEAD:kernel/module.c
事实上,git-log,git-whatchanged等命令都可以这么用。
apply
-----
$git-apply 相当于patch(1)命令,不过git-apply专门用来apply那些用git-diff生成的补丁
--check //不真正打补丁,而只是检查补丁是否能完美的打上
-v //verbose模式
-R //reverse模式,也就是拉出这个补丁来(而不是打进去)
gui
---
$ git-gui
or:
$gitk //GUI模式。还有一些不在git包中的git GUI前端。 我觉得基于Qt的qgit最好用
revision list
-------------
$ git-rev-list<ID> 以时间为顺序,反向列出revision ID。 也就是先列最新的commit ID。
也可以指定列出的数目,例如:
$ git-rev-list -2 971a71bdc9b42e74a5a8ed0433ac27ae92291024
log
---
查看某一文件都被哪些补丁改动过:
$ git-whatchanged -p security/Kconfig
查看某一文件的每一行的作者和Revision ID:
$ git-blame security/Kconfig
or:
$ git-annotate security/Kconfig
查看某一版本的某一文件:
$ git-show v2.6.22:init/main.c
查看两个tags之间的log:(git-shortlog也可以这么用)
$ git-log v2.6.25-rc3..v2.6.25-rc4
和:
$ git-log -p v2.6.24..v2.6.25-rc1 arch/x86/kernel/smp_64.c
和:
$ git-whatchanged v2.6.24..v2.6.25-rc1arch/x86/kernel/smp_64.c
查看某版本之后的log:
$ git-log v2.6.25-rc4..HEAD //HEAD可以省略,直接写成git-log v2.6.25-rc4..
撤销最近的commit
----------------
$ git-resetHEAD~1 //HEAD~1这种方式也可以表示为HEAD^
$ git-diff |git-apply -R -v
注意:1)git-reset和git-revert不同,后者是把补丁作为另一个commit反向打入tree中,而reset
是真正的撤销; 2) 如果撤销最近的n次commits,就用git-resetHEAD~<n>,例如HEAD~2
回退到某个tag或commit
--------------------
例如目前我的linux-2.6里是:
$ git-desribe
v2.6.26-3465-g5b664cb
我想让它回退到v2.6.26时的状态(程序和ref log一起回退)。 git-log搜索"Linux 2.6.26"的commitID为:
bce7f793daec3e65ec5c5705d2457b81fe7b5725
那么:
$ git-reset --hard bce7f793daec3e65ec5c5705d2457b81fe7b5725
--hard不但会reset你的working tree,而且联indexfiles一起reset:整个回到你指定的commit状态。
reset/reflog
------------
举例来说,reset你的当前branch的tip(HEAD所在的那个commit)
$ git-reset HEAD^
选项:
--mixed 只撤销掉index中的commit,却保留那个commit对应的内容
--hard index和working dir全撤销,也就是commit消失,并且其内容也消失
--soft index和working dir都不动,只"require them to be in a good order" (
这是man git-reset中的话,什么意思?) 。这会让该commit修改的文件变
成"dded but not yet committed"的状态。
如果不加这3个参数之一,那git-reset默认就是--mixed方式。
[FYI] git-reset --hard之后又想恢复
在git中,除非你运行了git-gc --prune,否则历史是永远不会被擦除的,你可以随意恢复到任何历史状态。 下面就是
一个恢复被git-reset --hard擦除了的commit的例子:
//reset最近的commit
$ git-reset --hard HEAD^
HEAD is now at bc45eb8 Mergegit://git.kernel.org/pub/scm/linux/kernel/git/herbert/crypto-2.6
//查看reflog
$ git-reflog
bc45eb8... HEAD@{0}: HEAD^: updating HEAD
a7dc750... HEAD@{1}: commit: [PCI] code cleanup : remove deadpci_remove_device_safe()
bc45eb8... HEAD@{2}: checkout: moving from jike tobc45eb8950b8c14487385cfd2bda1613ca8d9703
//reset回去
$ git-reset --hardHEAD@{1} //注意,HEAD@{1}就是我们要恢复的那个状态
HEAD is now at a7dc750 [PCI] code cleanup : remove deadpci_remove_device_safe()
注意也可以把reflog列出来的历史状态checkout出来:
$ git-checkout HEAD@{30} -b new_branch
stash
-----
git-stash是个很有趣的功能,它可以让你把目前的dirty内容"隐藏"起来,使得你的仓库看起来是干净的。
这在两种情况下很有用:
1,修改了一个文件,尚未commit,此时又想去修改别的文件;
2,切换到别的branch之前,把尚未commit的改动stash一下,以防git-checkout命令丢弃了你的
未提交的修改。
用法:
$ git-stash
参数:
list //列出所有stash了的项目,注意会显示stash的ID
show [-p][stash@{<id>}] //查看某个stash,-p会显示补丁。 如果不指定id,则show最近stash的那个
apply[stash@{<id>}] //恢复某个stash。 如果不指定id,则apply最近stash的那个
save //stash当前的未提交的改动
clear //销毁所有stash了的未提交改动
不加参数的话,默认行为就是save。
例如:
$ git-stash list
stash@{0}: WIP on test: 42bb109... a commit to fix. --fixed ingit-rebase, "edit"
stash@{1}: WIP on test2: 10253e6... Merge branch 'test' intotest2
stash@{2}: WIP on test2: 10253e6... Merge branch 'test' intotest2
注意'test'和'test2'都是branch。
$ git-stash show -p stash@{2}
$ git-stash apply stash@{2}
注意,$ git-stash pop相当于从stash 栈里头apply一个stash,亦即:git-stash applystash@{0}
可以把任一分支的stash给apply到当前分支上。
add
---
新加文件到index中,使得git可以跟踪它:
$ git-add<filename> //类似于cvs add <filename>。如果<filename>是目录,那么git-add
//会自动去递归地添加该目录下的所有文件和子目录
$ git-add-a //本目录下所有文件和子目录
git-add 实际上是对git-update-index的调用,git-add hello.c就相当于:
$ git-update-index --add hello.c
commit
------
$ git-commit -e -s -a
-s 增加Signed-off-by行
-e 调用vim(我在.git/config里制定的editor)进行编辑 commit message
-a all
-v 在vim中编辑commit msg时,连补丁本身也显示在其中
delete
------
删除文件:
$ git-rm aa.c
$ git-commit
撤销一次commit
--------------
$git-log
$ git-revertadb2f08089edac8bf1912a618a74485ab42f2b86 //指定导致删除操作的commit ID,进行撤消
列出某一个commit ID对应的补丁
-----------------------------
$ git-log -1 -p 721151d004dcf01a71b12bb6b893f9160284cf6e
-1的意思是只显示一个commit。如果你想显示5个,就-5。不指定的话,git log会从该commit一直往后显示。
选项:
--color diff语法高亮
or:
$ git-format-patch --stdout -1721151d004dcf01a71b12bb6b893f9160284cf6e
--stdout指定git写到标准输出,否则会写入到磁盘中。
or:
$ git-show 721151d004dcf01a71b12bb6b893f9160284cf6e
git-shortlog
------------
类似于git-log,只是简略。
git-bisect的用法
----------------
$ git-bisect start
$ git-bisectbad //该版本的kernel标记为bad
或者有针对性的:
$ git-bisect bad v2.6.22-rc1
$ git-bisect good v2.6.22-rc2
LABEL: 在你指定了bad和good之后,如果这两个版本之间有1000个revisions,git就默认剔除了500个,你应该在此时测试该版本:
创建一个临时性的output目录:
$ make ../git_bisect_output/
编译:
$ make O=../git_bisect_output/ menuconfig
$ make O=../git_bisect_output/ V=1 -j4
$ sudo make O=../git_bisect_output/ V=1 modules_installinstall
注意,最好在menuconfig时,给local version加上一个string,例如take1、take2等。
启动新编译的kernel,如果还有BUG:
$ git-bisect bad
如果没有BUG了:
$ git-bisect good
goto LABEL;
直到某个时候,只剩下一个revision可以怀疑,那时候就可以确认是它引入了BUG。
当bisect结束,恢复到master版本:
# git-bisect reset
[注意]
git bisect是一个漫长而痛苦的过程。我在DellOptiplex745(2G内存/Core2双核2G)机器上足足做了一天,才定位到一个BUG。
[replay的用法]
如果应该输入git-bisect good的时候,不小心输入了git-bisect bad; 或者本应该输入git-bisectbad的时候不小心写成了
git-bisect good, 则可以这样回退:
1) git-bisect log | tee ../git.bisect.log
2) 修改../git.bisect.log,删掉最后两行 -- 也就是回退1步。 如果需要回退N步,那就删掉N个最后两行
3) git-bisect replay ../git.bisect.log
[visualize的用法]
git-bisect的时间很长,因为可能需要编译N次内核。 在此期间,可以用:
$ git-bisect visualize
来在gitk中查看目前还待检验的那些Revs。
FYI: 如果你象我一样更喜欢qgit,可以修改`whichgit-bisect`脚本,将'gitk'字样替换成'qgit'。
修改上次commit的log message
---------------------------
//下面这个方法真土..事实上,git-citool只是git-commit的GUI形式
$git-citool GUI界面的git-commit。 不但可以提交,而且可以编辑上次commit的信息。
or:
//修改
$ git-commit --amend
同理,使用git-commit的--amend模式还可以修改last commit的 *内容*.
->git-stash //藏起未提交的工作,make your branch looks `clean'
-> 修改文件,直到你满意
-> git-commit -a--amend //编辑msg
-> git-stashapply //undo the stash
修改某个commit的msg和内容!!
---------------------------
[WARNING]修改一个已经不在HEAD位置的commit,无论是修改其msg还是内容,都必须发生在 *该commit尚未被
第2人看到* 的情况下,也就是说,没有push给别人,也没有被别人pull过。
否则,这种修改会造成开发社区的混乱(think about that...)。
假定我们同时修改msg和内容:
[FYI]对于只修改msg的情形,也走这个步骤,但是注意"5, rebase"的时候,选edit(而不是squash)即可。
1, 记住那个要修改的commit ID,SHA1串的前5个字母即可。 例如它是27675;
2, 记住27675的前一个commit,例如它是534e3;
3, git-show 27675看看它的内容,并用Vim修改相应的文件,改到自己满意为止;
4, 提交改动
$ git-commit -a -m "a new commit, just to be combined with27675"
假设其ID是12345。
5, rebase
$ git-rebase -i 534e3
这时git会调用vim来让你编辑,你会看到一行行"pick 1acb3e <commitmsg>",这些commits都是发生在
我们要在git-rebase 指定的534e3之后的。 此时:
-> 如果你删除一行,那么git-rebase就会让这个commit消失;
-> 如果你删除所有行,那么git-rebase就放弃这次操作;
-> 如果你把某一行的"pick"改成了"squash",那么git-rebase就会把这个commit*合并* 到它的上一行
的commit里,并再次调用Vim让你编辑合并后的commit msg。
-> 如果你把某一行的"pick"改成"edit",那么git就会让你执行: a) git-commit--amend; b) git-rebase
--continue,这样可以修改其msg。 注意SHA1 ID会改变。
我们需要的就是上面的第3种情形。 找到12345那行,把pick改成squash,然后dd删除该行,p命令粘贴在27675
那行的下面,:wq退出Vim。
马上Git会再次调用Vim,让你编辑合并后的commit的msg。 编辑后保存退出。
6, 再次查看合并后的commit
$ git-log 查找msg 或者 git-whatchanged -p<file>查找补丁
这时你发现新commit的ID变了,既不是27675也不是12345,而是一个新的SHA1 ID。
desribe
-------
$ git-describe
v2.6.25-rc2-347-g97f7905
merge
-----
git-pull/git-push会默认调用git-merge,一般会成功; 如果失败,那就要:
1, 该命令必然失败,因为confilicts存在:
$ git-merge
2, 查看冲突(冲突的地方已经像CVSupdate冲突那样,在冲突文件中用>>>>和<<<<标记了):
$ git-diff
3, 修改冲突文件,改成你想要的样子
4, 更新index
$ git-update-index
5, commit这次merge
$ git-commit -a -e -s
archive
-------
可以把当前版本(HEAD所处的位置)给export出来。 e.g.
$ git-describe
v2.6.25-rc4-155-g10c36be
$ mkdir ../linux-2.6.25-rc2-347-g97f7905
$ git-archive -v v2.6.25-rc2-347-g97f7905 | (cd../linux-2.6.25-rc2-347-g97f7905/&& tar xf -)
$ head -4 ../linux-2.6.25-rc2-347-g97f7905/Makefile
VERSION = 2
PATCHLEVEL = 6
SUBLEVEL = 25
EXTRAVERSION = -rc2
从本地git仓库中提取某个版本的kernel:
$ git-archive -v v2.6.18 | (cd ../linux-2.6.18/&& tar xf -)
-v表示--verbose,注意'v2.6.18'可以是git-tag -l列出来的tags中的一个,也可以是其他RevID例如HEAD等。
导出最新的kernel:
$ git-archive -v HEAD | (cd ../linux-HEAD/&& tar xf -)
或者打成tarball:
$ git-archive -v --format=tar v2.6.24 |bzip2 >../linux-2.6.24.tar.bz2
tag
---
列出当前已有的tags:
$ git-tag [-l]
添加tag:
$ git-tag v2.6.26-test
删除:
$ git-tag -d v2.6.26-test
branch/checkout
---------------
添加/删除分支:
添加一个叫jike的分支:
$ git-branch jike
删除该branch:
$ git-branch -d jike
列出所有分支:
$git-branch //列出所有本地分支
$ git-branch-a //列出所有分支,包括remote 和 local branches
$ git-branch-r //列出remote branches
查看目前在哪个branch上
$ git-branch
jike
*master
切换到某个branch:
$ git-checkoutjike
-f 表示覆盖jike分支里未提交的内容(FIXME:是不是连master分支里未提交的内容也都丢失了?)
例子:从remote的分支checkout到一个新的本地branch:
例如我clone了一个远程的仓库,进入那个目录,看看有哪些branches:
$ git-branch -a
* master
origin/multiple-msi-20080711
这时候我有可能想把origin/multiple-msi-20080711这个remotebranch给checkout出来,并
为它新建一个叫做msi的本地branch:
$ git-checkout origin/multiple-msi-20080711 -b msi
再看看:
$ git-branch -a
master
* msi
origin/multiple-msi-20080711
Linux自从2.6.24-rc1,就开始把i386和x86-64合并为x86的工作,有时候需要track旧的代码。
(say, 我不知道git-whatchanged -parch/x86/kernel/apic_32.c怎么能够连以前的arch/i386/kernel/apic.c
也跟踪到) 这时候可以有一个workaround: checkout v2.6.23这个tag:
$ git-checkout v2.6.23
注意,只有在git 1.5.x以及更新的版本上才支持这个操作。 这样checkout之后,HEAD就detach掉了,
亦即:你不在任何branch上。git-branch会告诉你你在(no branch)上。
那么,怎么切换到原来的状态? 再次git-checkout master即可。
在1.5之前,也包括现在,还可以为v2.6.23创建一个branch,用于查看i386或者x86-64的版本历史:
$ git-checkout v2.6.23 -b v2.6.23
注意,有了master和jike两个分支之后,所有的修改、commit、revert工作,都在jike这个branch
上进行;而master这个branch,只用来每天git pull保持和upstream的同步。
rebase
------
当jike这个分支有自己的commit、自己的改动,要重新和master分支同步一下
(将自己的local commits重新rebase在master里新引入的改动之上):
$ git-checkout jike
$ git-rebase -i master
-i,表示interactive
一个git-rebase的例子:
//目前有master和jike两个branches
[arc@localhost linux-2.6]$ git-branch
jike
* master
//master分支比jike新
[arc@localhost linux-2.6]$ git-describe
v2.6.27-rc5-361-g82a28c7
[arc@localhost linux-2.6]$ git-checkout jike
Switched to branch "jike"
[arc@localhost linux-2.6]$ git-describe
v2.6.27-rc5-320-gf8a561a
//jike分支的HEAD位于一个local commit处
[arc@localhost linux-2.6]$ git log -1
1 commit f8a561aa5fef94becc76a5509a369b742f925058
2 Author: Jike Song<albcamus@gmail.com>
3 Date: Mon Sep 8 22:26:14 2008+0800
4
5 PCI: utilize calculated results when detecting MSI features
6
7 in function msi_capability_init, we can make use of thecalculated
8 results instead of calling is_mask_bit_support andis_64bit_address
9 twice, in spite of the fact that they are macros.
10
11 Signed-off-by: Jike Song<albcamus@gmail.com>
//在jike分支里执行git-rebase,-i表示interactive
[arc@localhost linux-2.6]$ git-rebase -i master
Successfully rebased and updated refs/heads/jike.
//已经rebase了master分支,现在jike分支除了作为HEAD的localcommit之外,和master一样了
//注意,这里jike分支是rc5-362,此时master分支是rc5-361,正好多一个commit,就是我本地的这个
[arc@localhost linux-2.6]$ git-describe
v2.6.27-rc5-362-gedaa7ca
[arc@localhost linux-2.6]$ git log -1
1 commit edaa7ca47705cc6ca695e267f88f91dbe958da44
2 Author: Jike Song<albcamus@gmail.com>
3 Date: Mon Sep 8 22:26:14 2008+0800
4
5 PCI: utilize calculated results when detecting MSI features
6
7 in function msi_capability_init, we can make use of thecalculated
8 results instead of calling is_mask_bit_support andis_64bit_address
9 twice, in spite of the fact that they are macros.
10