首页 > 技术文章 > Git版本控制 — 日常使用(二)

aiwz 2015-02-14 15:47 原文

本地使用

 

以下是我的一些日常操作。

 

(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/

 

推荐阅读