使用git进行团队协作

2017/09/26

版本控制

版本控制是一种记录一个或若干文件内容变化,以便将来查阅特定版本修订情况的系统。如果你需要保存某一幅图片或文件的所有修订版本,采用版本控制系统(VCS)是个明智的选择。 有了它你就可以将某个文件回溯到之前的状态,甚至将整个项目都回退到过去某个时间点的状态,你可以比较文件的变化细节,查出最后是谁修改了哪个地方,从而找出导致怪异问题出现的原因,又是谁在何时报告了某个功能缺陷等等。 使用版本控制系统通常还意味着,就算你乱来一气把整个项目中的文件改的改删的删,你也照样可以轻松恢复到原先的样子。 但额外增加的工作量却微乎其微。

本地版本控制系统

许多人习惯用复制整个项目目录的方式来保存不同的版本,或许还会改名加上备份时间以示区别。 这么做唯一的好处就是简单,但是特别容易犯错。 有时候会混淆所在的工作目录,一不小心会写错文件或者覆盖意想外的文件。为了解决这个问题,人们很久以前就开发了许多种本地版本控制系统,大多都是采用某种简单的数据库来记录文件的历次更新差异。其中最流行的一种叫做 RCS,现今许多计算机系统上都还看得到它的踪影。 甚至在流行的 Mac OS X 系统上安装了开发者工具包之后,也可以使用 rcs 命令。 它的工作原理是在硬盘上保存补丁集(补丁是指文件修订前后的变化);通过应用所有的补丁,可以重新计算出各个版本的文件内容。

集中化的版本控制系统

接下来人们又遇到一个问题,如何让在不同系统上的开发者协同工作? 于是,集中化的版本控制系统(Centralized Version Control Systems,简称 CVCS)应运而生。 这类系统,诸如 CVS、Subversion 以及 Perforce 等,都有一个单一的集中管理的服务器,保存所有文件的修订版本,而协同工作的人们都通过客户端连到这台服务器,取出最新的文件或者提交更新。 多年以来,这已成为版本控制系统的标准做法。

这种做法带来了许多好处,特别是相较于老式的本地 VCS 来说。 现在,每个人都可以在一定程度上看到项目中的其他人正在做些什么。 而管理员也可以轻松掌控每个开发者的权限,并且管理一个 CVCS 要远比在各个客户端上维护本地数据库来得轻松容易。 事分两面,有好有坏。 这么做最显而易见的缺点是中央服务器的单点故障。 如果宕机一小时,那么在这一小时内,谁都无法提交更新,也就无法协同工作。 如果中心数据库所在的磁盘发生损坏,又没有做恰当备份,毫无疑问你将丢失所有数据——包括项目的整个变更历史,只剩下人们在各自机器上保留的单独快照。 本地版本控制系统也存在类似问题,只要整个项目的历史记录被保存在单一位置,就有丢失所有历史更新记录的风险。

分布式版本控制系统

于是分布式版本控制系统(Distributed Version Control System,简称 DVCS)面世了。 在这类系统中,像 Git、Mercurial、Bazaar 以及 Darcs 等,客户端并不只提取最新版本的文件快照,而是把代码仓库完整地镜像下来。 这么一来,任何一处协同工作用的服务器发生故障,事后都可以用任何一个镜像出来的本地仓库恢复。 因为每一次的克隆操作,实际上都是一次对代码仓库的完整备份。

许多这类系统都可以指定和若干不同的远端代码仓库进行交互。籍此,你就可以在同一个项目中,分别和不同工作小组的人相互协作。 你可以根据需要设定不同的协作流程,比如层次模型式的工作流,而这在以前的集中式系统中是无法实现的。

git简介

Git 是一款免费、开源的分布式版本控制系统,用于敏捷高效地处理任何或小或大的项目。因为在开发过程中需要跟踪代码,文档,项目等信息中的变化,版本控制则成为项目管理中的重中之重,且它对跨平台时遇到的一些问题有很不错的支持,所以在全球范围都广受欢迎。

仓库

在 Git 的概念中,仓库,就是你存在.git目录的那个文件夹内的所有文件,包括隐藏的文件,Git程序会在当前目录以及上级目录查找是否存在.git文件,如果存在,则会将.git目录存在的文件夹开始下的所有文件当成你需要管理的文件,所以,我们如果想将某个文件夹当做一个Git仓库,你可以在那个文件夹下通过终端(Window为Cmd或者PoewrShell或者Bash)来执行

git init

版本

在Git中,计数基础是提交,即我们常说的Commit,我们每做一点更改便可以产生一次提交,当提交累计起来,可以作为产品定型时,就在当前的Commit上打上一个标记,将这个标记我们称之为版本多少多少,那么就算完成了一个版本,标记本身被称之为Tag,请注意,在Git中,版本仅仅只是某一个提交的标签,并没有其他意义,Git本身也仅有打标签的功能,并没有版本功能,版本功能是根据Tag来扩展的,Git本身并没有

分支

这是Git中最重要的也是最常用的概念和功能之一,分支功能解决了正在开发的版本与上线版本稳定性冲突的问题在Git使用过程中,我们的默认分支一般是Master,当然,这是可以修改的,我们在Master完成一次开发,生成了一个稳定版本,那么当我们需要添加新功能或者做修改时,只需要新建一个分支,然后在该分支上开发,完成后合并到主分支即可

提交

Git对于版本的管理其实是对于提交的管理,在整个Git仓库中,代码存在的形式并不是一分一分的代码,而是一个一个的提交,Git使用四十个字节长度的16进制字符串来标识每一个提交,这基本保证了每一个提交的标识是唯一的,然后通过组织一个按照时间排序的提交列表,就组成了我们所说的分支,请注意,分支在本质上只是一个索引,所以,我们可以任意回退,修正,即使因为某些原因丢失了,也可以重建另外,关于Git的储存方式:Git是仅仅只储存有修改的部分,并不会储存整个文件,所以,请不要删除文件夹整个文件夹的内容,除非你确定你不再需要他,否则请勿删除

同步

同步,也可以称之为拉取,在Git中是非常频繁的操作,和SVN不同,Git的所有仓库之间是平等的,所以,为了保证代码一致性,尽可能的在每次操作前进行一次同步操作。

推送

你代码有更新时,你需要更新到远程仓库,这个动作被称之为推送。

冲突

冲突合并一般是因为自己的本地做的提交和服务器上的提交有差异,并且这些差异中的文件改动,Git不能自动合并,那么就需要用户手动进行合并。

合并

合并这个命令通常情况下是用于两个分支的合并,一般用于本地分支,远程分支多用Pull命令,该命令的功能是将待合并分支与目标分支合并在一起,注意,这个命令只会合并当前版本之前的差异,两个分支的提交历史会根据提交时间重新组织索引,故只可能会产生一次冲突但是会生成一个提交,如果你不想生成这次提交,加上 –base参数即可

暂存

这个既是一个概念也是一个命令,其含义就是字面上的,作用就是可以将你当前正在进行的工作暂时存起来,然后在此基础上干别的事情,等你别的事情干完后,再转回来继续,注意,暂存只是针对你最后一次改动而言,即针对当前所在的版本的所有改动都算。

撤销

撤销命令使用是非常频繁的,因为某些原因,我们不再需要我们的改动或者新的改动有点问题,我们需要回退到某个版本,这时就需要用到撤销命令,或者说这个应该翻译成重置更加恰当。具体命令如下:

HEAD 用来标记当前所在分支的。

工作区,暂存区,历史记录区

详细查看:http://www.jianshu.com/p/3c2ba8a9bd25

git使用

Git的安装

yum -y install git

配置用户信息

git config --global user.name "opspy"
git config --global user.email "admin@opspy.com"

配置默认的编辑器

git config --global core.editor vim

git 彩色命令

# ui 高亮模式为 auto
git config --global color.ui auto
# status 高亮模式为 auto
git config --global color.status auto
# branch 高亮模式为 auto
git config --global color.branch auto

git 登录凭证存储,会生成一个 ~/.git-credentials 文件

git config --global credential.helper store

查看git的配置

git config --list

检查 Git 的某一项配置

git config user.name

初始化,你可以在任何地方生成repository目录

mkdir -p /opt/opspy_repo
cd /opt/opspy_repo
git init
#查看git仓库状态
git status

提交文件到仓库

#创建一个测试文件
echo "line1 第一行" > 01.txt

#添加单个文件到仓库
git add  01.txt

#提交前确认一下
git status

#创建多个文件
cp 01.txt 02.txt

#提交所有文件到暂存区
git add .

#正式提交
git commit -m "1 commit"

#查看提交情况
git log

修改文件

#第一次修改,加入一行
echo "line2 第二行" >> 01.txt

#查看状态
git status

#添加文件到暂存区
git add 01.txt

#查看状态
git status -s

#撤销暂存区
git checkout -- 01.txt

#再次添加到暂存区
git add 01.txt

#再次查看状态,
git status

#提交
git commit -m "append line2"

#查看日志
git log

查看修改的具体内容

#第二次修改,覆盖文件,此时暂未添加到暂存区
echo "line3 第三行" >> 01.txt

#查看尚未暂存的文件更新了哪些部分,不加参数直接输入 git diff:
git diff

#添加到暂存区
git add 01.txt

#比较暂存区和版本库差异(Git 1.6.1 及更高版本还允许使用 git diff --staged)
git diff --cached
git diff --staged

#提交
git commit -m "append line3"

文件改名或移动,使用git提供的mv命令

git mv 01.txt 03.txt

#相当于,你自己做三步操作
mv 01.txt 03.txt
git rm 01.txt
git add 03.txt

#查看
git status

#提交
git commit -m "change filename 01.txt -> 03.txt"

git回滚

#错误清空某个文件
> 03.txt

#未提交时,还原文件到最近活跃的版本
git checkout HEAD 03.txt

#提交
> 03.txt
git commit -a -m "clear file 03.txt"


#提交之后的撤销,首先查看log

[root@node1 opspy_repo]# git log
commit 723bf6184534b28a37dbcef277fc8c6e40d1d52a
Author: opspy <admin@opspy.com>
Date:   Thu Sep 21 00:04:26 2017 +0800

    clear file 03.txt

commit ba2871a388f111e26ebdae37bcc1ec737ed2fc1d
Author: opspy <admin@opspy.com>
Date:   Wed Sep 20 16:17:08 2017 +0800

    change file  01.txt -> 03.txt

commit 2b1ecc797f04aedc649615f1a3a5a6d49a321bd9
Author: opspy <admin@opspy.com>
Date:   Wed Sep 20 15:33:56 2017 +0800

    append line3

commit 3d8fde8ca292d086730efbc4c9c2b8290911ca2f
Author: opspy <admin@opspy.com>
Date:   Wed Sep 20 15:24:54 2017 +0800

    append line2

commit 33a52af1d92d29b3b237d3107e8cf5c45762e2df
Author: opspy <admin@opspy.com>
Date:   Wed Sep 20 15:21:37 2017 +0800

    1 commit


#使用 git revert 命令用来撤销一个已经提交的快照,通过 HEAD 撤销最近的一次提交
git revert HEAD

#此时查看 git log 会新增一次提交,他不会移除之前的提交历史
[root@node1 opspy_repo]# git log 
commit f259031dd6e943dca20f79c84e0e0a816b5d7336
Author: opspy <admin@opspy.com>
Date:   Thu Sep 21 00:07:22 2017 +0800

    Revert "clear file 03.txt"
    
    This reverts commit 723bf6184534b28a37dbcef277fc8c6e40d1d52a.

commit 723bf6184534b28a37dbcef277fc8c6e40d1d52a
Author: opspy <admin@opspy.com>
Date:   Thu Sep 21 00:04:26 2017 +0800

    clear file 03.txt

commit ba2871a388f111e26ebdae37bcc1ec737ed2fc1d
Author: opspy <admin@opspy.com>
Date:   Wed Sep 20 16:17:08 2017 +0800

    change file  01.txt -> 03.txt

commit 2b1ecc797f04aedc649615f1a3a5a6d49a321bd9
Author: opspy <admin@opspy.com>
Date:   Wed Sep 20 15:33:56 2017 +0800

    append line3

commit 3d8fde8ca292d086730efbc4c9c2b8290911ca2f
Author: opspy <admin@opspy.com>
Date:   Wed Sep 20 15:24:54 2017 +0800

    append line2

commit 33a52af1d92d29b3b237d3107e8cf5c45762e2df
Author: opspy <admin@opspy.com>
Date:   Wed Sep 20 15:21:37 2017 +0800

    1 commit

#使用 reset 进行重置,--hard 标记告诉 Git 清除所有未提交的更改,所以在使用前确定你想扔掉你所有本地的开发。

#我们重置一次,一个^表示回退版本1次,2个^表示两次,以此类推,或者使用 HEAD^2 表示两次

[root@node1 opspy_repo]# git reset  --hard HEAD^
HEAD is now at 723bf61 clear file 03.txt


#再次查看一下日志,此时已经没有 Revert "clear file 03.txt" 的日志了
[root@node1 opspy_repo]# git log
commit 723bf6184534b28a37dbcef277fc8c6e40d1d52a
Author: opspy <admin@opspy.com>
Date:   Thu Sep 21 00:04:26 2017 +0800

    clear file 03.txt

commit ba2871a388f111e26ebdae37bcc1ec737ed2fc1d
Author: opspy <admin@opspy.com>
Date:   Wed Sep 20 16:17:08 2017 +0800

    change file  01.txt -> 03.txt

commit 2b1ecc797f04aedc649615f1a3a5a6d49a321bd9
Author: opspy <admin@opspy.com>
Date:   Wed Sep 20 15:33:56 2017 +0800

    append line3

commit 3d8fde8ca292d086730efbc4c9c2b8290911ca2f
Author: opspy <admin@opspy.com>
Date:   Wed Sep 20 15:24:54 2017 +0800

    append line2

commit 33a52af1d92d29b3b237d3107e8cf5c45762e2df
Author: opspy <admin@opspy.com>
Date:   Wed Sep 20 15:21:37 2017 +0800

    1 commit


#如果想再次回到之前的版本
[root@node1 opspy_repo]# git reflog
723bf61 HEAD@{0}: reset: moving to HEAD^
f259031 HEAD@{1}: revert: Revert "clear file 03.txt"
723bf61 HEAD@{2}: commit: clear file 03.txt
ba2871a HEAD@{3}: commit (amend): change file 01.txt -> 03.txt
213c1f4 HEAD@{4}: commit (amend): change file 01.txt -> 03.txt
737c21b HEAD@{5}: commit: change filename 01.txt -> 03.txt
2b1ecc7 HEAD@{6}: commit: append line3
3d8fde8 HEAD@{7}: commit: append line2
33a52af HEAD@{8}: commit (initial): 1 commit


#选一个编号
[root@node1 opspy_repo]# git reset --hard f259031
HEAD is now at f259031 Revert "clear file 03.txt"


#查看是否已经回退
[root@node1 opspy_repo]# git log --oneline
f259031 Revert "clear file 03.txt"
723bf61 clear file 03.txt
ba2871a change file  01.txt -> 03.txt
2b1ecc7 append line3
3d8fde8 append line2
33a52af 1 commit

[root@node1 opspy_repo]# cat 03.txt
line1 第一行
line2 第二行
line3 第三行

重写项目历史

#编辑内容
echo 'line4 第四行' >> 03.txt
echo 'line2 第二行' >> 02.txt

#我们想提交两个文件,但是少提交了一个 02.txt
git add 03.txt
git commit -m "Add 03.txt and 02.txt"

#想起来少提交了02.txt,--no-edit 标记会修复提交但不修改提交信息。
git add 02.txt
git commit --amend --no-edit

git分支

#新建分支
git branch branch-1

#切换分支
git checkout branch-1

#新建分支,切换分支使用一条命令完成
git checkout -b branch-1

#查看所有分支,带*号的表示当前分支
[root@node1 opspy_repo]# git branch -a
* branch-1
  master

#修改当前分支名称
git branch -m branch-new

#修改分支内容,并提交
echo "branch-new" > branch.txt
git add .
git status -s
git commit -m "branch test"

#指定基础分支,创建一个新的分支,如果不加基础分支分支,则默认以当前分支作为基础分支,创建新分支
git checkout  -b branch-2 master

#切换分支
git checkout branch-2

#查看文件数量和 master 分支一致
[root@node1 opspy_repo]# ls
02.txt  03.txt

#删除分支,Git 会阻止你删除包含未合并更改的分支
git branch -d branch-2

#强制删除分支
git checkout master
git branch -D branch-2

git 合并

#把 branch-new 分支合并到当前分支
git checkout master
git merge branch-new

#或者一行搞定
git merge branch-new master

#在master新提交东西
git checkout master
echo "master test txt" > master.txt
git add .
git commit -m "add master.txt"

#使用 rebase 将 master 同步到 branch-new
git rebase master branch-new

Rebase 的黄金法则:
绝不要在公共的分支上使用它。
比如说,如果你把 master 分支 rebase 到你的 feature 分支,rebase 将 master 分支上的所有提交都移到了 feature 分支后面。问题是它只发生在你的代码仓库中,其他所有的开发者还在原来的 master 上工作。因为 rebase 引起了新的提交,Git 会认为你的 master 分支和其他人的 master 已经分叉了。

merge 和 rebase 详细区别: https://git-scm.com/book/zh/v2/Git-%E5%88%86%E6%94%AF-%E5%8F%98%E5%9F%BA

git 冲突

#在 master 分支修改文件
git checkout master
echo "冲突测试1" >> master.txt
git add  master.txt
git commit  -m "冲突测试"

#在分支 branch-new 修改文件
git checkout branch-new
echo "冲突测试2" >> master.txt
git add  master.txt
git commit  -m "冲突测试"

#回到 master 分支合并 branch-new 分支造成冲突
git checkout master
git merge branch-new 

#git diff 查看冲突的地方
[root@node1 opspy_repo]# git diff 
diff --cc master.txt
index d933269,476621a..0000000
--- a/master.txt
+++ b/master.txt
@@@ -1,2 -1,2 +1,6 @@@
  master test txt
++<<<<<<< HEAD
 +冲突测试1
++=======
+ 冲突测试2
++>>>>>>> branch-new


#手动删除保留正确的一行数据 重新提交
[root@node1 opspy_repo]# vim master.txt
[root@node1 opspy_repo]# git commit -a -m "冲突修复"

在 github 或其他公共仓库上注册仓库,记下你的仓库地址 http://10.0.7.1/opspy/test.git,这里使用 gogs部署

#部署gogs
docker run  -d -p 80:3000 gogs/gogs

#进入之前的目录
cd /opt/opspy_repo/

#查看远程分支
git remote -v

#添加分支,并推送,-u 参数指定一个默认主机,以后可直接使用 git push 不加任何参数。
git remote add origin http://10.0.7.1/opspy/test.git
git push -u origin master

#拉取远程仓库
git clone  http://10.0.7.1/opspy/test.git

#将远程所有分支更新取回本地
cd test
git fetch http://10.0.7.1/opspy/test.git

#将远程 master 分支更新取回本地,名称为 origin/master
git fetch origin master

#将远程的 master 分支,拉取到本地的 dev 分支
git  fetch origin master:dev

#查看所有远程分支
git branch -r

#查看所有分支
git branch -a

#将dev分支推送到远程仓库
git push origin dev

#建立追踪关系。比如,在 git clone 的时候,所有本地分支默认与远程主机的同名分支,建立追踪关系,本地的master分支自动"追踪"origin/master分支。
#手动指定 master 分支追踪 origin/dev 分支。
git branch --set-upstream-to=origin/dev

#如果只有一个远程分支的情况下直接使用 git pull 获取更新
git pull

#如果有多个远程主机,指定追踪的主机名
git pull origin

#使用 --rebase 方式更新,后面接的参数为`远程主机`,`远程分支:本地分支`
git pull --rebase origin  dev:master

#同步时删除远程已经删除的分支
git pull -p

git 标签

#为 dev 分支最新的一次提交,打上 1.0 标签
git tag 1.0

#查看tag
git tag

#查看 git log
[root@node1 test]# git log  --oneline 
3879e52 冲突修复
d344935 冲突测试
23f38ca 冲突测试
dd2f6fa add master.txt
45e0dce branch test
da0d9e0 Add 03.txt and 02.txt
0158d40 Revert "clear file 03.txt"
b58d368 clear file 03.txt
51f77c0 change filename 01.txt -> 03.txt
8db62df append line3
1e671e6 append line2
b51a724 1 commit

# 新建一个 tag 在指定 commit
git tag 0.1 23f38ca

# 查看 git tag 信息
git show 0.1

#删除本地 tag
git tag -d 0.1

#提交指定tag
git push origin 1.0

## 提交所有tag
git push origin --tags

#新建一个分支,指向某个tag
git checkout -b release-v1 1.0

git 团队协作

一般使用三种分支模型,GitHub Flow 和 Git flow 和 GitLab flow。

Git Flow

一共5个分支:

分支 周期 功能
master 长期 用于存放对外发布的版本,任何时候在这个分支拿到的,都是稳定的分布版
develop 长期 用于日常开发,存放最新的开发版
feature 短期 功能分支,用来开发一个新的功能,开发完成后合并到Develop分支进入Release
release 短期 预发分支,发布新的 Release
hotfix 短期 补丁分支,修复 Bug

长期分支长期存在,短期分支一旦完成开发,它们就会被合并进develop或master,然后被删除。

GitHub Flow

只有一个长期分支 master,工作流程如下:

第一步:根据需求,从master拉出新分支,不区分功能分支或补丁分支。

第二步:新分支开发完成后,或者需要讨论的时候,就向master发起一个pull request(简称PR)。

第三步:Pull Request既是一个通知,让别人注意到你的请求,又是一种对话机制,大家一起评审和讨论你的代码。对话过程中,你还可以不断提交代码。

第四步:你的Pull Request被接受,合并进master,重新部署后,原来你拉出来的那个分支就被删除。(先部署再合并也可)

GitLab Flow

Gitlab flow 的最大原则叫做”上游优先”(upsteam first),即只存在一个主分支master,它是所有其他分支的”上游”。只有上游分支采纳的代码变化,才能应用到其他分支。

对于”持续发布”的项目,它建议在master分支以外,再建立不同的环境分支。比如,”开发环境”的分支是master,”预发环境”的分支是pre-production,”生产环境”的分支是production。

开发分支是预发分支的”上游”,预发分支又是生产分支的”上游”。代码的变化,必须由”上游”向”下游”发展。比如,生产环境出现了bug,这时就要新建一个功能分支,先把它合并到master,确认没有问题,再cherry-pick到pre-production,这一步也没有问题,才进入production。

只有紧急情况,才允许跳过上游,直接合并到下游分支。

对于”版本发布”的项目,建议的做法是每一个稳定版本,都要从master分支拉出一个分支,比如2-3-stable、2-4-stable等等。 以后,只有修补bug,才允许将代码合并到这些分支,并且此时要更新小版本号。

gitflow 练习

初始化仓库,master分支

#新建 gltflow 仓库
mkdir gitflow
cd gitflow
touch README.md
git init
git add README.md
git commit -m "first commit"
git remote add origin http://10.0.7.1/opspy/gitflow.git
git push -u origin master

develop分支

#创建 develop 分支
git branch develop
git push -u origin develop

feature分支

#创建 feature 分支
git checkout -b myfeature develop
#创建后提交到远程,(可选)
git push -u origin myfeature

#进行功能开发
touch myfeature.txt
git add myfeature.txt
git commit -m "add myfeature.txt"

#完成功能开发后,合并到 develop
git pull origin develop
git checkout develop
git merge --no-ff myfeature
git push origin develop

#合并成功之后,删除功能分支
git branch -D myfeature

# 如果刚才已经提交到远程(刚才的可选步骤),删除远程功能分支
git push origin --delete myfeature

release分支

#创建 release 分支
git checkout -b release-v1.0 develop
#提交到远程
git push -u origin release-v1.0

#合并 release 分支到 master
git checkout master
git merge --no-ff release-v1.0
git push

#合并 release 分支到 develop
git checkout develop
git merge --no-ff release-v1.0
git push

#删除预发布分支
git branch -d release-v1.0

#如果提交到远程,删除远程的预发分支
git push origin --delete release-v1.0

#为主分支打上里程碑标签
git tag -a v1.0 master

#推送标签
git push --tags

hotfix分支

#创建hotfix分支
git checkout -b hotfix-v1.0.1 master

#修复问题
touch hotfix.txt
git add .
git commit  -m "add hotfix.txt"

#合并到 master 分支,并推送到远程
git checkout master
git merge --no-ff hotfix-v1.0.1
git push

#合并到 devlop 分支,并推送到远程
git checkout develop
git merge --no-ff hotfix-v1.0.1
git push

#删除
git branch -d hotfix-v1.0.1

#添加标记并推送到远程
git tag -a v1.0.1 master
git push --tags

git文章推荐

git文章 地址
git 官方教材 https://git-scm.com/book/zh/v2/
git学习资料大全 https://github.com/xirong/my-git

强烈推荐:
git最佳实践中文版 https://github.com/geeeeeeeeek/git-recipes/wiki
git开发工作流指南 https://github.com/xirong/my-git/blob/master/git-workflow-tutorial.md

Post Directory