Git版本控制 — 日常使用(二)
本地使用
以下是我的一些日常操作。
(1) 创建版本库
# cd /proj
# git init
Initialized empty Git repository in /proj/.git/
(2) 查看状态
# git status
staged:文件被暂存了
modified, unstaged:文件被修改了但是没有被暂存
untracked:文件没有被跟踪
(3) 初次提交
# git add * //首先提交到index
# git commit -m "initial import" //再提交到本地仓库
和svn的一步到位不同,git是分成两步的。
(4) 显示提交内容
# git show 9b1796 //查看提交的具体内容
# git show -s 9b1796 //显示提交的概要
# git show 9b1796 --shortstat // 显示提交的改动量
# git ls-tree 9b1796 //查看commit指向的tree包含的tree和blob
# git cat-file -p 9b1796 //查看提交的基本信息
(5) 清除暂存区
如果你后悔了,可以清除index中的内容。
# git reset HEAD <file>
注意,这个时候暂存区的内容被清除了,但工作目录内容不变。
(6) 建立一个裸仓库
裸仓库(bare repository)相当于SVN的服务器。
proj是我们自己的本地仓库,现在要建立一个可供远程访问的裸仓库proj.git。
# git clone --bare proj proj.git
裸仓库proj.git里面只有git目录包含的文件,没有工作目录。
(7) 克隆一个仓库
可以把公共Git仓库复制一份到本地使用,使用ssh来clone一个仓库。
# git clone user@IP:/path/proj.git proj
当然git也支持其它协议,比如http和git。
(8) 查看日志
日志是版本控制中非常重要的一部分,它描述了你的所有操作。
# git log // 显示提交日志
# git log -p // 显示日志,并显示提交内容
# git log file // 只显示有修改file的commit
# git log --stat // 显示统计信息
# git log --graph // 用ASCII字符画图显示提交历史线,这个功能很不错
(9) 比较变更
我们经常要查看对工作目录的内容作了哪些修改,以确定这些修改是否合理。
# git diff --cached // 查看将要提交的内容,这时候的比较的是:暂存区 vs HEAD
# git diff // 当前你所做的,但是没有提交到暂存区的修改,也就是这时候比较:前工作目录 vs HEAD
# git diff file // 比较单个文件
# git diff commit1:path/file commit2:path/file // 比较不同提交中的同一个文件
# git diff --stat // 查看统计信息
(10) 一步提交
如果你嫌提交分两步走太麻烦,可以跳过提交到暂存区的步骤git add,直接提交到本地仓库。
# git commit -am "log"
自动把所有内容被修改的文件(不包括新创建的未跟踪文件)都添加到暂存区中,并且同时把它们提交。
(11) 注释技巧
commit注释最好以一行短句作为开头,来简要描述一下这次commit所做的修改(最好不要超过50个字符)。
然后空一行,再把详细注释写清楚。这样可以很方便的用工具把commit注释变成email通知,第一行作为标题,
剩下的部分就作为email的正文。
(12) 提交到暂存区
git add 不但用来添加不在版本控制中的文件,也用于添加已在版本控制中但是刚修改过的文件,在这两种情况下,
git都会获得当前文件的快照并且把内容暂存(stage)到暂存区(index)中,为下一次commit做好准备。
(13) 分支的创建和删除
项目一般不会只有一个主分支,通常情况下还会有其它分支。
# git branch // 得到当前仓库中存在的所有分支列表,星号表示当前所在分支
# git branch new // 创建new分支
# git checkout new // 切换到new分支上
# git checkout -b new // 创建new分支并切换到new分支上
# git branch -d new // 删除已被合并的new分支
# git branch -D new // 强制删除分支new
(14) 合并分支
分支一般用于开发新feature,当开发完成后就要合并到主分支了。
# git diff new master // 从new到master所做的变化
# git diff master new // 从master到new所做的变化
# git diff new // 等价于git diff new master
# git merge new // 实际上是把git diff master new添加到master中
合并分支的提交会带有merge信息(指明合并了哪两个分支),SVN上则无此功能!
(15) 解决冲突
多人开发的项目,提交时很肯能发现有冲突。
git status显示unmerged paths。
这是一个冲突文件示例:
1 <<<<<<< HEAD
2 This is file in master
3 =======
4 This is a test file for branch.
5 >>>>>>> test
编辑冲突文件来解决冲突,比如我们保留test的修改。
然后再重新:
# git add file
# git commit -m "merge master and test"
(16) 回到最新提交
签出master分支时,工作目录是master分支的最新提交。
# git checkout master
(17) 匿名分支
可以使用匿名分支回到过去的某个提交中,进行修改,最后把修改合并到主分支中。
# git checkout 1b8754 // 切换到某一个commit
这时候git branch会显示 *(no branch),切换到匿名分支。
在匿名分支上所做的修改,master或其它分支是看不到的。
所以我们需要为匿名分支命名:
# git branch develop
# git checkout develop
或者直接
# git checkout -b develop
这样匿名分支就成为develop分支。
然后我们切换回master分支,并合并develop中的修改:
# git checkout master
# git diff master develop
# git merge develop
好了,现在master分支就包含develop分支上的修改。
最后可以删除develop分支:
# git branch -d develop
(18) 重命名和删除
# git mv file new // 重命名文件
# git rm -f file // 删除文件
# git branch -m oldbranch newbranch // 重命名分支
(19) 抹除提交
如果你想要放弃某个提交之后的所有修改,可以用git reset。
# git reset --hard 1b8754
加载此commit并抹除此提交之后的所有提交。
(20) 显示当前分支和提交
# git branch -v // 显示当前分支、当前提交
(21) 修改之前提交
可以修改以前的提交,并且修改后在以后的版本中也有效,Git世界里是有后悔药的。
# git rebase 7daa17^ --interactive // 退回到要修改的7daa17的前一个提交
执行后,git会调用vi显示7daa17到最新提交的记录,把我们要修改的7daa17的pick改成edit。
进行修改后:
# git add <file>
# git commit --amend // 修改这个commit
# git rebase --continue // 提交修改后的commit并且返回到原来的head处
取消修改操作:
# git rebase --abort
(22) 忽略文件
编辑.gitignore,可以指定要忽略哪些目录和文件,这些文件就对Git隐形了。
比如指定忽略proj下的patches目录:
/patches
(23) 标签
git中有两种主要的标签:轻量级标签(lightweight)和带注释的标签(annotated)。
轻量级标签是针对某个特定提交的指针,带注释的标签是git仓库中的对象,包含更多信息。
轻量级标签:
# git tag v1.0 // 把最新commit做成v1.0标签
带注释的标签:
# git tag -a v1.0 -m "describe what is v1.0" // 也可以不指定-m,直接用vi编辑
显示标签列表:
# git tag -l
删除标签:
# git tag -d v1.0
显示某个标签的内容:
# git show v1.0
(24) 查看包含某个commit的最早版本
查看包含某个提交的最早版本。
# git describe --contains <commit id>
(25) 放弃工作目录中的修改
修改了工作目录中的文件,还没提交到暂存区时,想把工作目录文件复原。
# git checkout -- <file>
(26) 获取分支中的某个文件
你在一个分支中工作,但是像获取另一个分支中的某个文件。
# git checkout <branch> -- <file>
(27) 把一个分支的所有更新应用到另一个分支
假如有两个分支A和B,它们在提交m处分叉了。
现在B更新到了n,要把B分支从m到n的提交也应用到A,这就是使用场景。
# git rebase master branch
相当于:
# git checkout branch
# git rebase master
这样一来,branch就包含了master的后续更新了。
(28) 把某个分支的一个提交应用到另一个分支上
git rebase可以应用其它分支的所有后续提交,但如果我们只想要其中的某一个提交呢?
git cherry-pick可用于把某个分支的一个commit应用到另一个分支。
假设master分支上有个commit,现在想把它应用到branch分支。
git checkout branch
git cherry-pick <commit id>
(29) 获取远程分支
# git branch -r // 查看都有哪些远程分支
# git checkout -b local_name origin/remote_name // 把远程分支映射为本地分支
(30) 生成Git格式的Patch
# git format-patch -o patches -1 c7fafc0ec // Patch只包含一个commit
从commit1开始(不包含),到commit2(包含),每个commit生成一个patch,放在patches目录下:
# git format-patch -o patches <commit1>..<commit2>
(31) 合并多个提交为一个
把最后3个提交合并为一个,并修改日志,还是用git rebase。
# git rebase -i HEAD~3
执行以上命令后,自动生成一个文件,包含三个commit的条目:
把第一个commit前的pick,改为reword。这个是最早提交的,reword表示要修改日志。
把第二个、第三个commit前的pick,改为squash,表示要把提交融合掉。
保存退出后,自动生成一个文件,可以修改下最终的日志。
合并成功。
(32) 修改最后一次提交的日志
# git commit --amend
(33) 撤销之前的提交
如果发现最后一个提交有错误,想撤销提交,但是不想放弃修改,可以使用git reset。
# git reset HEAD^
这样一来,上次提交就又回到了暂存区了。可以进行修改,然后重新提交。
如果使用git reset --hard HEAD^,则会直接放弃最后一个commit,而不把修改放回暂存区。
git reset还可以用来合并多个commit,来直接作为一个patch:
# git reset HEAD~3 // 撤销最近的3次提交,把修改存到暂存区
然后重新提交,这样一来最近的3次提交,就变为一个commit。
(34) 撤销本地所有修改
git checkout .
远程交互
我们用三台机器的交互操作,来说明Git的分布式管理。
@远程机器A
首先在远程机器A上创建一个裸仓库。
# mkdir proj.git
# cd proj.git
# git init --bare // 建立一个裸仓库
@本地机器B
在本地机器B上存在一个叫做test的本地仓库。
# cd test
定义远程分支的本地缩写:
# git remote add far ssh://user@IP:port/path/proj.git
删除远程分支的本地缩写:
# git remote rm far
然后将test推送到远程机器A的裸仓库proj.git:
# git push far master // 将本地的master分支推送到far的master分支
操作等价于:
# git push far master:master // 从本地master分支推送到far的master分支
或者:
# git push far test:master // 从本地test分支推送到far的master分支
# git push far test:test // 从本地test分支推送到far的test分支
分支操作:
# git branch // 列出本地分支
# git branch -r // 列出远程分支
# git branch -a // 列出所有分支
# git branch new // 创建一个新的本地分支,但不进行切换
# git branch -m | -M oldbranch newbranch // 重命名分支,如果newbranch名字已经存在,需用-M强制重命名
# git branch -d | -D new // 删除new分支,-D表示强制删除尚未合并的分支
# git branch -d -r new // 删除远程new分支
# git branch new <start-point> // 从start-point创建new分支
总而言之,现在把本地机器B的test项目,推送到了远程机器A的proj.git裸仓库中了。
@本地机器C
从远程机器A 的裸仓库克隆proj,修改proj,最后把修改推送到远程机器A的裸仓库中。
# git clone ssh://user@IP:port/path/proj.git proj // 当然也可以用其它协议
这时候我们可以看下.git/config:
[remote "origin"]
url = ssh://user@IP:port/path/proj.git
这里origin代表远程机器A上的裸仓库,以后可以直接使用这个缩写:)
修改master分支,然后推送到远程机器A的裸仓库中:
# git push origin master
@本地机器B
本地机器C进行修改后,本地机器B就要进行更新,以便及时获取这些修改。
git pull命令执行两个操作:
git pull从远程分支抓取修改内容,然后把它合并到当前的分支。git pull类似于svn update。
# git pull far master
如果只想抓取远程分支的修改内容,但不自动合并这些修改呢?
git fetch用于执行git pull的前半部工作,但是不会把抓下来的分支(far/master)合并到当前分支中。
# git fetch far master
查看本地master分支和抓取的远程分支的差异:
# git diff master far/master
或者
git log -p master..far/master
最后手动合并:
# git merge far/master
可能遇到的问题:
bash: git-receive-pack: command not found
git的安装路径不是默认路径。
ln -s /your_path/git-receive-pack /usr/bin/git-receive-pack
git-upload-pack同理。
Git协议公共仓库
我们想建立一个公共裸仓库,以供他人访问。据说用git协议的访问速度是最快的,只需要启动git-daemon。
它的监听端口为9418,它允许含有git-daemon-export-ok的git目录被读,但是默认不允许写(可以配成允许)。
git-daemon
A really simple server for git repositories.
This is ideally suited for read-only updates, i.e., pulling from git repositories.
建立公共裸仓库:
# git clone --bare /path/proj proj.git // proj的默认裸仓库名字为proj.git
# touch proj.git/git-daemon-export-ok // 允许git服务器读取
然后就是配置Git服务器,最后启动Git服务器。
git服务器常用参数:
--port = <port>
重新选定监听端口
--export-all
Allow pulling from all directories that look like GIT repositories.
even if they do not have the git-daemon-export-ok file.
--base-path = <path>
Remap all the path requests as relative to the given path. 指定git公共仓库的默认基路径。
--verbose
Log details about the incoming connections and requested files.
--reuseaddr
Use SO_REUSEADDR when binding the listening socket. This allows the server to
restart without waiting for old connections to timeout.
如果想配置成可以写的(允许push):
git daemon需要加--enable=receive-pack。
allowing anonymous push. It is disabled by default, as there is no authentication in the protocol.
This is soley meant for a closed LAN setting where everybody is friendly.
了解了以上信息,可以启动git服务器了:
(proj.git放在/path目录下)
# git daemon --verbose --reuseaddr --base-path=/path --enable=receive-pack &>> /path/log &
从公共Git仓库获取proj:
# git clone git://IP/proj.git // 默认端口是9418,默认基路径为之前设置的/path
Author
zhangskd @ csdn blog
Reference
[1] Git Community Book.
[2]. http://www.open-open.com/lib/view/open1356608472385.html
[3]. http://eikke.com/importing-a-git-tree-into-a-subversion-repository/
[4]. http://john.albin.net/git/convert-subversion-to-git
[5]. http://smilejay.com/2011/12/git-daemon/