Wednesday, July 21, 2010

git笔记

git是Linus亲自操刀设计实现的版本控制工具(据说Linus在两个礼拜内搞定了git,教主威武).git功能上比SVN要强,但是学习曲线也陡了很多. 这里是我对SVN的笔记


文档

关于git的教程和手册网上很多, 但很多写的都不好.最推荐:
Git Workflow (这里的diagram非常给力)
How to version projects with Git 图文并茂,有木有?
git quick reference

这几个也不错
Git Magic 中文,英文
Pro Git(中文)

其他的还有:
Understanding Git Conceptually
Git User Manual写的比较晦涩
Git - SVN Crash Course. 我觉得帮助不是很大, 因为git和svn在用法上区别是在太大了
GIT cheat sheet这个不错
Basic Branch Merging
关于stash, log, 以及gitconfig的用法

用法

初始化一个workspace
从远端克隆一个repository到本地
$git clone git://path/to/your/git/repo
注意git是一个分布式的版本控制系统. 如果你从server1上clone的repo, 那么以后server1就相当于你的源.如果以后server2再clone了你的repo, 你就相当于server2的源.我发现这种结构很方便调试 -- 在其他机器上可以从我本地repo来pull 文件. clone的时候除了git协议还可以使用ssh, 比如
$git clone ssh://hostname/path/to/your/git/repo

工作流程
进入你的工作目录,通常大家首先将本地repository更新到源上的最新版本(大致相当于svn update, 但是因为git是分布式的设计,更新到源上最新未必是全局最新). 下面的命令从origin 这个remote 更新 master这个branch的update.
$git pull origin master
未必需要从自己所在的branch来更新. 比如你实际在master这个branch, 你也可以更新branch foo的东西,即使你还是留在master这个branch里:
$git pull origin foo
有时候每次都是默认同样的remote同样的branch,那么我们可以修改工作目录下的.git/config文件,
[branch "master"]
    remote = origin
    merge = refs/heads/master
这样就可以直接用下面命令来更新而无须指定remote和branch
$git pull


做了修改后, 为了查看当前工作目录的状态,比如哪些文件被改动, 哪些文件没有commit, 可以使用status (大致相当于svn st)
$git status
如果需要git status的时候忽略某些文件,比如.o文件或者.pyc文件, 我们可以在.gitignore这个文件中加入两行*.o以及*.pyc.

修改了本地的文件后, 需要将其用add命令将其加入index. svn里没有index的概念,而且add这个命令在svn中是将一个文件加入版本控制. 而git里, 一个文件每次的修改都会需要你用add加入index后才能commit.
$git add file1 file2
然后将index的修改commit到本地.
$git commit file1 file2 -m "go! commit file1, file2"
以上两步(add, commit)也可以被合并成一步
$git commit -a file1 file2 -m "go! commit file1, file2"
如果需要更改上一次commit的信息
$git commit --amend -m "this is the right commit!"

这个时候文件只是在本地commit, 如果希望更新到远端的repositiory中, 还需要push(和更新源到本地的pull命令对应). 默认git push的话是push到origin的master分支
$git push
你也可以指定remote repo以及branch
$git push repo5 branch12
如果当前工作目录下checkout了多个branch, 但是你一般只会push正在tracking的这个branch,可以在config文件(比如.git/config)里设置
[push]
    default = tracking
或者使用命令
$git config push.default tracking

提取给定版本 checkout
checkout最新commit里的foo
$git checkout foo
如果需要提前两个版本的foo,可以
$git checkout HEAD~2 foo
从最新的stash(stash@{0})里checkout foo这个文件
$git checkout stash@{0} foo

撤销更改 revert, checkout, reset
比如你刚刚修改了一个文件foo, 但现在想撤销这个更改. 这需要根据foo的状态来选择不同的方法:
  • Changed but not updated: 如果一个文件foo被删除了, 或者被修改了但完全还在本地workspace中,还没有使用git add把foo加入index当中, 那么我们只需要重新checkout 这个文件
    $git checkout foo
    如果你不幸有一个branch也叫foo
    $git checkout -- foo
    如果你需要恢复当前目录所有文件
    $git checkout . 
    有时候需要把当前workspace中所有的修改都撤销,可以简单的使用:
    $git stash
  • Changes to be committed: 这时候修改已经加入index当中,但是还没有commit 提交到本地repo,我们可以使用reset使的当前workspace回到上一个commit的时候, 然后使用checkout恢复修改.
    $git reset HEAD foo
    $git checkout foo
    撤销所有index当中的修改:
    $git reset HEAD
  • 如果已经使用git commit把修改提交到了本地repo当中, 我们可以把本地repo rollback到上一次commit前的状态:
    $git reset --hard HEAD~1
    --hard选项会overwrite的所有的改动.

    如果我们只需要undo某一次的commit,可以使用git revert:
    $git revert b38155cbf671d55ceb027687c39508de8cef2463
    这样实际上是提交了另一个commit来抵消指定的commit.

查看日志 log
当前branch的commit日志
$git log
当前branch的commit日志,显示内容
$git log -p
当前branch的commit日志,但只显示上3次提交的内容
$git log -3
当前branch的commit日志,但只显示被修改的文件名
$git log --name-only

处理冲突 conflict
参考手册http://www.kernel.org/pub/software/scm/git/docs/user-manual.html
以及 http://www.kernel.org/pub/software/scm/git/docs/git-push.html

pull的时候如果有本地修改会导致无法成功. 如果希望能把远端的commit merge到本地
$git checkout -m foo
Auto-merging foo
有可能会失败,这时候就需要你手动来编辑这个文件来merge了

push的时候, 当前commit到远端repo的时候,有时候会出现如下问题:
$git push
To git@example.come:example.git
 ! [rejected]        master -> master (non-fast-forward)
error: failed to push some refs to 'git@example.com:example.git'
To prevent you from losing history, non-fast-forward updates were rejected
Merge the remote changes before pushing again.  See the 'Note about
fast-forwards' section of 'git push --help' for details.
这是由于你的工作目录没有update到最新的版本.换言之有人在你上一次pull之后又push了新的commit进去.所以如果你的push生效, 可能会导致别人push的commit失效. 解决方法有两种:
$git pull
$git push
这种方法先把当前工作目录更新到最新 -- 这一步可能会产生冲突. 解决冲突后把更新提交. 这种方法会产生两个commit信息. 一次是说你自己的更新,另一次是将你的分支和repo上分支的merge.
如果不希望产生两次commit信息, 可以用第二种方法:
$git pull --rebase
$git push
这种方法直接将你的commit重新rebase到最新的版本上, 然后再进行提交.

分支 branch
一个repo可以有不同的分支,branch命令查看本地已有的分支, *表示当前使用的branch
$git branch
* master
  testbranch
可以用branch -r 查看remote所有的分支
$git branch -r
也可以用branch -a 同时查看local和remote所有的分支
$git branch -a
以当前branch的HEAD为起始点,新建新的branch叫newbranch
$git branch newbranch
也可以指定起始点(比如testbranch), 创建一个新的分支叫newbranch
$git branch newbranch testbranch

将当前分支从master切换到testbranch
$git checkout testbranch
$git branch
  master
* testbranch

删除一个local的branch
$git branch -d branch_to_delete
删除一个remote的branch
$git branch push origin --delete branch_to_delete
远端 remote
查看当前branch有哪些remote
$git remote
origin
查看当前branch有哪些remote,显示具体一些的信息
$git remote -v
origin git@example.com:bar.git (fetch)
origin git@example.com:bar.git (push)

查看origin这个remote的具体信息
$git remote show origin
一个repo可以有多个remote.比如给分支branch_foo添加一个新的remote:
$git remote add branch_foo git@host-for-new-remote:repo-name.git
将新添加的remote添加为tracking的branch
$git branch --set-upstream branch_foo   remote_bar/branch_foo

查看历史版本
查看某个版本(some-sha1)的某个文件
$git show some-sha1-number:some-file
查看某个版本(some-sha1)的和最新版本(HEAD)之间的所有区别
$git diff some-sha1 HEAD
查看某个版本(some-sha1)的和最新版本(HEAD)之间的某个文件的区别
$git diff some-sha1 HEAD --somefile

比较当前branch和另外一个branch比如叫foo的区别
$git diff foo
比较branch foo和另外一个branch bar的区别
$git diff foo bar
查看远端repo里的文件
查看当前HEAD中所有加入git管理的个文件
$ git ls-tree -r  HEAD

其他相关工具
tig - text-mode interface for git 设置git ignore的方法: http://help.github.com/ignore-files/

No comments: