GitLab使用手册

 

Git简介


git是一个分布式版本控制软件,最初由林纳斯·托瓦兹创作,于2005年以GPL发布。最初目的是为更好地管理Linux内核开发而设计。
git手册progit_v2.1.17.pdf

主要功能

git是用于Linux内核开发的版本控制工具。与CVS、Subversion一类的集中式版本控制工具不同,它采用了分布式版本库的作法,不需要服务器端软件,就可以运作版本控制,使得源代码的发布和交流极其方便。git的速度很快,这对于诸如Linux内核这样的大项目来说自然很重要。git最为出色的是它的合并追踪(merge tracing)能力。

目录库

  • hooks:存储钩子的文件夹
  • logs:存储日志的文件夹
  • refs:存储指向各个分支的指针(SHA-1标识)文件
  • objects:存放git对象
  • config:存放各种设置文档
  • HEAD:指向当前所在分支的指针文件路径,一般指向refs下的某文件

Git工作区

Git项目有三个工作区域:

  • Git仓库:仓库目录是Git用来保存项目元数据和对象数据的地方,这是Git最重要的部分,其他计算机克隆仓库时,拷贝的就是这里的数据。
  • 工作目录:工作目录是对项目某个版本独立提取出来的内容。这些从Git仓库的压缩数据库中提取出来的文件放在本地磁盘中以供使用或修改。
  • 暂存区域:暂存区域是一个文件,保存了下次将要提交的文件列表信息,一般在Git仓库目录中。

Git文件状态

Git中的文件一般有三种状态,每个一文件都会处于这其中之一,根据状态的不同他们也将处于不同的工作区域。

  • 已提交(committed):已提交表示数据已经安全的保存在本地Git仓库中。
  • 已修改(modified):已修改表示修改了文件,但是还没有保存到Git仓库中。
  • 已暂存(staged):已暂存表示对一个已经修改的文件的当前版本做了标记,会在下次的提交快找中。
    状态之间的转换可以参考下图:

Git自定义设置


用户信息

用户信息可以设置全局或者依赖仓库设置。

1
2
3
4
5
6
# 全局用户信息
$ git config --global user.name "your name" #设置用户名
$ git config --global user.email your email #设置用户邮箱
# 根据项目设置用户信息
$ git config user.name "your name" #设置用户名
$ git config user.email your email #设置用户邮箱

 

如果你有多个不同的项目,并且洗完Git使用不同的用户信息,那么可以在项目目录下执行不带有–global的命令。

外部的合并与比较工

Git允许使用外部工具来替代内置的diff来合并解决冲突,将外部工具封装到shell脚本中,然后Git通过调用脚本的方式来唤起外部工具。
这样如果需要替换外部工具,只需要修改脚本就可以了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
## 封装脚本 
#### 合并脚本 
$ cat /usr/local/bin/extMerge   
#!/bin/sh
/Applications/kdiff3.app/Contents/MacOS/kdiff3 $*
$ cat /usr/local/bin/extDiff

### 比较脚本
#!/bin/sh
[ $# -eq 7 ] && /usr/local/bin/extMerge "$2" "$5"

## 添加可执行权限
sudo chmod +x /usr/local/bin/extMerge
sudo chmod +x /usr/local/bin/extDiff

## Git配置
$ git config --global merge.tool extMerge
$ git config --global mergetool.extMerge.cmd 
'extMerge "$BASE" "$LOCAL" "$REMOTE" "$MERGED"'
$ git config --global mergetool.extMerge.trustExitCode false
$ git config --global diff.external extDiff

 

设置完成后,执行git diff查看效果,Git唤起kdiff3:

回车和换行

windows中使用回车(CR)、换行(LF)两个字符来结束一行,linux、mac使用换行(LF)一个字符。
这回扰乱多人跨平台协作,Git中使用core.autocrlf来避免这个问题。
windows系统中将core.autocrlf设置成true,检出时换行会被替换成回车和换行,提交时回车和换行会被替换成换行。
linux、mac中将core.autocrlf设置成input,提交时回车和换行被替换成换行,检出时不转换。

1
2
$ git config --global core.autocrlf true
$ git config --global core.autocrlf input

 

设置注释模版

使用注释模版的方式来规范提交的注释内容,创建一个文件,每次提交时Git引用这个文件添加到注释中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
## 50/72格式
$ cat ~/.gitmessage
# 少于50字符的主题
#
# 72个字符或者跟长的描述。可以从以下几个方面回答:
#
# * 为什么需要这么做?
# * 如何解决这个问题的?
# * 这样做可能带来的副作用?
#
# 如果有的话,带上问题来源的链接
#
# 添加有关开发人员的信息,如果你是和其他人一起完成的工作
#
# 相关开发人员: 全名 <email@example.com>
# 相关开发人员: 全名 <email@example.com>

##设置commit.template
$ git config --global commit.template ~/.gitmessage

 

每次执行git commit,效果如下:

设置过滤文件

忽略文件

可以编辑工作目录下的.gitignore文件来过滤不需要纳入版本控制的文件,并且支持shell正则表达式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
$ cat .gitignore
/target/
!.mvn/wrapper/maven-wrapper.jar

## STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache

## IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr

## NetBeans ###
/nbproject/private/
/build/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/

 

配置ssh key

生成rsa密钥,如果已生成一般存放在~/.ssh目录下默认名称为id_rsa(私钥)、id_rsa.pub(公钥),我们只需要使用到公钥,将公钥内容复制并粘贴到Key文本框中,Title可以自定义。注意:复制公钥时以ssh-rsa开头。

Git基本命令


获取仓库

在Git中有两种方法创建仓库:

  • git init创建本地仓库

    1
    2
    3
    4
    5
    6
    
    ## 在已有的目录中创建仓库
    $ mkdir workdir
    $ cd workdir
    $ git init 
    ## 或者直接创建仓库并新建目录
    $ git init workdir
    
  • git clone克隆远程仓库

    1
    2
    
    ## 通过git协议clone仓库到本地,这里重命名本地仓库名称为spring-demo,默认为origin
    $ git clone git@gitlab.com:proclinux/spring-demo.git spring-demo
    

    这种方法需要有一个远程中心仓库。

更新仓库

工作目录下的每一个文件都会处于已跟踪或未跟踪。已跟踪的文件是指那些已经被纳入版本控制的文件,未跟踪是指那些未被纳入版本控制的文件。它们的变化周期如下图:

查看文件状态

git status可以查看哪些文件处于什么状态。-s参数会使其得到一种更加紧凑的格式输出。

1
2
3
4
5
6
7
$ git status    
$ git status -s
 M README
MM Rakefile
A  lib/git.rb
M  lib/simplegit.rb
?? LICENSE.txt

 

?? 表示文件未被跟踪,需要add,A表示新的文件已放入暂存区域,右侧表示修改了文件未放入暂存区域,左侧M表示修改了文件并放入了暂存区域,MM表示修改了文件放到暂存取后又被修改了。

跟踪新文件

git add可以将文件纳入git版本控制中中,新增、修改后的文件都需要被跟踪

1
$ git add newfile

 

提交更新

1
2
$ git commit -m "this is a pice of comment" #添加注释
$ git commit    #未添加注释时,会自动弹出文本编辑框要求填写

查看已暂存和未暂存的修改

git diff可以通过文件补丁的格式显示具体哪些行发生了改变。不加参数查看的是尚未暂存的文件更新了那些部分,若要查看已暂存的将要添加到下次提交里的内容,可以添加参数--staged

1
2
$ git diff
$ git diff --staged

 

在解决合并冲突时可以使用git config --global merge.tool vimdiff设置差异分析工具。
Git可以理解 kdiff3,tkdiff,meld,xxdiff,emerge,vimdiff,gvimdiff,ecmerge,和 opendiff 等合并工具的输出信息。

移除文件

git rm可以从git中移除文件,需要将文件从跟踪清单中移除,并删除本地文件:

1
2
$ rm filename
$ git rm filename

 

下次提交时,就不会纳入版本管理中。如果文件已经放到暂存区域可使用git rm cached

1
$ git rm cached filename

 

移动文件

git mv可以移动或重命名Git中的文件。

1
2
3
4
5
$ git mv filename newfilename
## 等同于
$ mv filename newfilename
$ git rm filename
$ git add newfilename

 

存储

存储工作

git stash可以暂时保存当前的工作,将当前工作入栈,在此之前可以使用git status查看当前工作的状态。

1
2
3
4
5
$ git stash #暂存工作
$ git stash list    #查看存储的列表
$ git stash apply stash@{0} #应用存储 stash@{0}是一个快捷应用,数字可以改变的,如果不添加,默认应用最新的
$ git stash pop stash@{0} #重新应用存储,并出栈
$ git stash drop stash@{0}  #移除存粗

 

git stash apply会将原来存储的工作区域、暂存区域的内容都恢复到工作区,如果加上–index会尝试将暂存区域恢复到暂存区域

取消应用存储

git stash show -p stash@{0} | git apply -R可以通过取消存粗补丁的方式,在应用存储后进行了一些修改,取消之前应用存储的修改。

1
$ git stash show -p stash@{0} | git apply -R`

 

创建存储分支

git stash branch可以在存储的基础上创建新的分支,当你存储工作后,又继续在当前分支工作,这时应用存储,发现存储的某个文件在后面的工作中被修改了,这样就产生了冲突,为此我们可以在存储的基础上创建新的分支,并愉快的在新的分支工作。

1
$ git stash branch newbranch

 

查看提交历史

git log可以查看每次提交的历史,同时也支持很多用法,可以参考help。

1
$ git log

 

撤销操作

覆盖提交

git commit --admend可以撤销上一次提交动作,在commit之后,如果未做任何的修改,运行带有–amend参数可以重新提交,并覆盖上一次提交。

1
2
3
$ git commit -m "this is comment"
$ git add newfile
$ git commit --admend

 

取消暂存文件

git reset可以取消已经暂存的文件

1
$ git reset HEAD filename

 

撤销对文件的修改

git checkout -- filename可以撤销对文件的修改,对这个文件的所有修改都会丢失,使用另外一个文件来覆盖它。

1
$ git checkout -- filename

 

远程仓库的使用

查看远程仓库

git remote可以查看远程仓库。

1
2
3
$ git remote -v 
origin  git@gitlab.com:proclinux/spring-demo.git (fetch)
origin  git@gitlab.com:proclinux/spring-demo.git (push)

 

添加远程库

git remote add可以添加远程仓库

1
2
3
4
## 添加远程仓库,并命名别名
$ git remote add spring-demo git@gitlab.com:proclinux/spring-demo.git
## 拉取数据
$ git fetch spring-demo

 

拉取取数据

git fetch可以从远程仓库拉取数据到你的本地仓库中,但并不会合并到你的工作目录中,因此需要手动合并。

1
$ git fetch origin develop

 

执行完成之后,你会拥有develop远程分支的引用,可以随意合并或查看。
如果你有一个本地分支设置跟踪了一个远程分支,可以使用git pull来自动抓取并合并远程分支到当前分支。git clone 默认会设置master分支跟踪远程仓库的master分支。

推送到远程仓库

git push可以在项目完成后将其推送到远程中心仓库。

1
$ git push origin develop

 

git会根据远程仓库develop分支的更新情况来决定是否允许推送,可以根据相应的提示来解决不能推送的问题,一般都是因为远程仓库的版本比本地更新的问题,因此在推送之前需要将远程仓库fetch下来并合并到本地仓库,在合并之前最好diff一下查看一下差异。

查看远程仓库

git remote show可以查看远程仓库的信息。

1
$ git remote show origin

 

origin是远程仓库的别名,clone默认会使用origin。

远程仓库移除与重命名

git remote rename可以重命名引用的名字。

1
$ git remote rename origin spring-demo

 

需要注意的是这样同样会修改你的远程分支名称,如果你不在想使用这个远程仓库了,可以使用git remote rm:

1
$ git remote rm spring-demo

 

标签管理

通常在git中使用tag来控制版本信息

列出标签

git tag可以查看所有的标签。

1
2
3
$ git tag
## 使用-l 参数可以过滤标签
$ git tag -l 'v1.0*'

 

查看远程的tag可以使用git branch -r

创建标签

git tag可以创建标签,标签分为:轻量标签、附柱标签,轻量标签只是一个特定提交的引用,附注标签是存储在git中的一个完整的对象。

1
2
3
4
## 附注标签
$ git tag -a v1.0.0 -m 'my version 1.0.0'
## 轻量标签
$ git tag v1.0.0

 

使用git show <tag>查看两者的区别。
如果需要对过去的提交打标签,需要查找历史提交的commit-id,通常取其前面的7位就够了。

1
2
3
4
5
6
7
$ git log --pretty=oneline
7b08e0d0d2ca9bfa6241d9f7fe9b927466b6b45 (HEAD -> master) Merge branch 'hotfix/v1.0.0' of gitlab.com:proclinux/spring-demo
d1cf546bd6a5def3e35f33583e3f9bf48948700c test
dae3bfe2823054265272ce5da7d6250d5a81c164 (tag: v1.0.0, origin/release, origin/hotfix/v1.0.0, release, hotfix/v1.0.0) bug fixed
dc528ec2ce4c1c4448acc6e956b9085cd770d45c (origin/master, origin/HEAD, feature/user-center) Update pom.xml
bcdfbfd57c8f3cd6cd65998464bb71a562d49948 Initial template creation
$ git tag -a v1.0.1 dae3bfe

 

共享标签

git push origin [--tags|tagname]可以推送标签到远程仓库。

1
2
$ git push origin v1.0.0 #推送指定标签
$ git push origin --tags #推送所有标签 --detele 也可以删除标签

 

删除标签

git tag -d <tagname>可以删除本地仓库的标签:

1
2
$ git tag -d v1.0.0 #该命令不会删除远程仓库的标签
$ git push origin :refs/tags/v1.0.0 #

 

检出标签

git checkout可以检出tag标签所指的版本,但是会是仓库处于detatched head的状态。

1
$ git checkout v1.0.0

 

detached head状态会有一些副作用,在这种状态下修改一些问题并提交,标签不会发生变化,也不所任何分支,因此也无法访问。一般我们通过新建一个分支,然后在此基础上进行修改。

1
$ git checkout -b version1.0.0 v1.0.0

 

分支管理

查看分支

git branch可以查看本地分支,带*的是当前分支,参数-v可以查看每个分支最后一次提交,–merged –no-merged可以用来过滤已经合并或尚未合并到当前分支的分支,-r可以查看远程分支

1
2
3
4
$ git branch
$ git branch -v
$ git branch --merged # --no-merged
$ git branch -r

 

创建分支

git branch branchname可以创建本地分支

1
$ git branch feature

 

推送分支

git push可以推送本地分支到远程仓库。

1
$ git push origin feature:feature

 

当其他人fetch origin时会将此分支拉到本地仓库,但是不会合并到当前分支,如果想要在此分支工作,需要创建新的分支并建立远程跟踪分支。

1
$ git checkout -b featrue origin/feature

 

跟踪分支

git checkout -b --track可以从远程跟踪分支检出一个本地分支会自动创建一个跟踪分支,这样pull、fetch、push就不需要显示的指定远程分支跟本地分支了,git能自动识别。

1
2
3
4
5
$ git checkout -b --track feature orgin/feature # 新版的git末尾加上远程分支会自动加上--track
## 修改远程跟踪分支
$ git branch -u origin/develop
## 关联远程仓库分支
git branch --set-upstream-to=origin/develop  develop

 

删除远程分支

git push origin --delete可以删除远程分支,在完成远程分支的工作,并且这个分支是临时分支,那么在合并之后可以删除该分支。

1
2
$ git push origin --delete feature  #删除远程分支
$ git branch -d feature #删除本地分支

 

变基

变基操作有点神奇,运用之前必须了解它到底做了什么。一般我们通过merge来合并版本。

使用merge合并之后是这样的:

使用rebase变基之后是这样的:
 

1
2
3
4
$ git checkout experiment
$ git rebase master
$ git checkout master
$ git merge experiment

 


这样做能确保提交历史更加整洁,没有分叉。
一定不要对已经push到远程仓库的分支进行变基。

GitLab管理


GitLab是由GitLab Inc.开发,使用MIT许可证的基于网络的Git仓库管理工具,且具有wiki和issue跟踪功能。

分支

在GitLab中创建以下几个分支,master、develop是长期分支,hotfix、feature是临时分支,feature可以不用push到远程仓库中。

  • master分支
    master为主分支,用于部署生产环境的分支,一般由develop分支合并。
    任何时间都不能直接修改master代码。

  • develop分支
    develop为开发分支,始终保持最新完成以及bug修复后的代码。
    用于开发人员合并代码并提测,测试完成后由管理员合并到master分支并打标签。

  • hotfix分支
    hotfix/开头的为修复分支,当线上出现需要紧急修复的问题时,以master分支为基线创建hotfix分支,修复完成后,需要合并到master分支和develop分支。

  • feature分支
    feature/开头的为特性分支,开发新功能时,以master为基线,创建feature分支。
    开发完成后合并到develop分支。

开发流程

  1. 创建项目(需要管理权限)
    gitlab首页点击新建项目,我们以Spring为模版创建一个示例项目。

    然后在Projects页面我们可以看到刚刚创建的示例项目:

    这是一个master分支,然后创建一个develop分支。

  2. 克隆项目
    在本地环境,新建一个目录用来作为项目的工作目录:

    在使用ssh克隆之前,需要在gitlab中配置ssh key

    1
    2
    3
    4
    5
    6
    
    $ git clone git@gitlab.com:proclinux/spring-demo.git
    Cloning into 'spring-demo'...
    remote: Enumerating objects: 1, done.
    remote: Counting objects: 100% (1/1), done.
    remote: Total 28 (delta 0), reused 0 (delta 0)
    Receiving objects: 100% (28/28), 47.41 KiB | 19.00 KiB/s, done.
    
  3. 新建develop开发分支
    新建develop分支并关联远程develop分支

    1
    2
    3
    
    ## 新建develop分支,个人在此分支做开发
    $ git checkout -b develop origin/develop
    Switched to a new branch 'develop'
    
  4. 新建feature特性分支
    feature分支存在于本地,不需要推送到远程仓库。切换到master分支,基于master分支创建feature分支。

    1
    2
    3
    
    $ git checkout master
    $ git checkout -b feature/login-module
    Switched to a new branch 'feature/login-module'
    
  5. 基于feature分支进行开发工作
    开发完成后,将代码进行提交。

    1
    2
    
    $ git add .
    $ git commit
    
  6. 合并feature分支到develop分支
    合并之前需要更新develop分支,然后再合并。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    
    ## 切换到develop分支
    $ git checkout develop
    Switched to branch 'develop'
    $ git fetch origin develop  #拉取远程develop分支数据
    From gitlab.com:proclinux/spring-demo
     * branch            develop    -> FETCH_HEAD
    $ git merge FETCH_HEAD --no-ff  #合并最新的develop分支
    Already up to date.
    $ git merge feature/login-module --no-ff   #合并feature分支
    Updating 05fbcad..2d44a4b
    Fast-forward
     README.md      |  3 ++-
     README.md.orig | 13 +++++++++++++
     2 files changed, 15 insertions(+), 1 deletion(-)
     create mode 100644 README.md.orig
    

如果合并的过程中产生冲突请使用git mergetool,解决后再提交。

  1. 推送develop分支

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
    $ git push  #推送本地develop分支到远程仓库
    Counting objects: 4, done.
    Delta compression using up to 4 threads.
    Compressing objects: 100% (4/4), done.
    Writing objects: 100% (4/4), 647 bytes | 647.00 KiB/s, done.
    Total 4 (delta 2), reused 0 (delta 0)
    remote:
    remote: To create a merge request for develop, visit:
    remote:   https://gitlab.com/proclinux/spring-demo/merge_requests/new?merge_request%5Bsource_branch%5D=develop
    remote:
    To gitlab.com:proclinux/spring-demo.git
       05fbcad..2d44a4b  develop -> develop
    
  2. 合并master分支(需要管理权限)
    测试完成后,将develop分支合并到master分支,在此之前需要更新master、develop分支

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    
    $ git checkout develop #切换到develop分支
    $ git fetch origin develop  #拉取最新的远程仓库develop分支数据
    From gitlab.com:proclinux/spring-demo
     * branch            develop    -> FETCH_HEAD
    $ git merge FETCH_HEAD --no-ff  #合并develop
    Already up to date.
    $ git checkout master   #切换到master分支
    $ git fetch origin master   #拉取最新的远程仓库master分支数据
    From gitlab.com:proclinux/spring-demo
     * branch            master     -> FETCH_HEAD
    $ git merge FETCH_HEAD --no-ff  #合并master分支
    Already up to date.
    $ git merge develop --no-ff #合并develop分支到master,遇到冲突
    Auto-merging README.md
    CONFLICT (content): Merge conflict in README.md
    Automatic merge failed; fix conflicts and then commit the result.
    $ git mergetool #解决冲突
    Merging:
    README.md
    
    Normal merge conflict for 'README.md':
      {local}: modified file
      {remote}: modified file
    2019-03-21 14:49:28.799 kdiff3[29340:315661] modalSession has been exited prematurely - check for a reentrant call to endModalSession:
    2019-03-21 14:49:31.019 kdiff3[29340:315661] modalSession has been exited prematurely - check for a reentrant call to endModalSession:
    2019-03-21 14:49:33.226 kdiff3[29340:315661] modalSession has been exited prematurely - check for a reentrant call to endModalSession:
    2019-03-21 14:49:34.564 kdiff3[29340:315661] modalSession has been exited prematurely - check for a reentrant call to endModalSession:
    README.md seems unchanged.
    $ git status    #冲突解决后查看状态,确认效果
    On branch master
    Your branch is up to date with 'origin/master'.
    
    All conflicts fixed but you are still merging.
      (use "git commit" to conclude merge)
    
    Changes to be committed:
    
        modified:   README.md
        new file:   README.md.orig
    
    Changes not staged for commit:
      (use "git add <file>..." to update what will be committed)
      (use "git checkout -- <file>..." to discard changes in working directory)
    
        modified:   README.md.orig
    
    Untracked files:
      (use "git add <file>..." to include in what will be committed)
    
        README_REMOTE_29425.md.orig
    $ git commit    #重新提交完成合并
    [master 37052f2] Merge branch 'develop'
    

kdiff3会生成其他文件,需要可以删除,kdiff3的页面如下图:

  1. 推送master分支到远程仓库(需要管理员权限)

    1
    2
    3
    4
    5
    6
    7
    8
    
    $ git push
    Counting objects: 3, done.
    Delta compression using up to 4 threads.
    Compressing objects: 100% (3/3), done.
    Writing objects: 100% (3/3), 358 bytes | 358.00 KiB/s, done.
    Total 3 (delta 2), reused 0 (delta 0)
    To gitlab.com:proclinux/spring-demo.git
       15dd409..37052f2  master -> master
    
  2. 打版本号(需要管理员权限)
    这一步骤可以合并到步骤9

    1
    2
    3
    4
    5
    6
    7
    
    $ git tag -a v1.0.1 -m "version 1.0.1"
    $ git push --tags
    Counting objects: 1, done.
    Writing objects: 100% (1/1), 159 bytes | 159.00 KiB/s, done.
    Total 1 (delta 0), reused 0 (delta 0)
    To gitlab.com:proclinux/spring-demo.git
     * [new tag]         v1.0.2 -> v1.0.2
    

紧急bug修复

  1. 查看当前工作空间状态是否干净,如果干净可以直接进入bug修复,否则需要将当前工作存储起来。

    1
    2
    
    $ git status    #查看当前是否有未提交的工作,如果有则暂存,没有则略过
    $ git stash     #存储工作
    
  2. 拉取指定版本master分支,如果是当前版本可以跳过。

    1
    2
    3
    4
    5
    
    $ git fetch origin master       #获取最新的master分支
    $ git merger --no-ff FETCH_HEAD #合并master
    $ git tag                       #查看所有的tag
    $ git show vx.x.x               #查看标签详情 查看需要回退的commit-id
    $ git reset --hard commit-id    #回退版本
    
  3. 基于指定版本master分支创建hotfix分支

    1
    2
    
    ## 创建分支
    $ git checkout -b hotfix/vx.x.x
    
  4. 修复bug
    修复完成之后提交hotfix分支

    1
    2
    3
    
    $ git add .
    $ git commit
    $ git push origin hotfix/vx.x.x:hotfix/vx.x.x
    
  5. 重新发布版本
    hotfix分支测试完成之后,重新发布新的版本

    1
    2
    3
    
    ## 重新打标签
    $ git tag -a vx.x.xy -m "The Vx.x.y"
    $ git push origin hotfix/vx.x.x --tags
    
  6. 合并master分支

    1
    2
    3
    4
    5
    
    $ git checkout master
    $ git push origin master
    $ git merge hotfix/vx.x.x --no-ff
    $ git mergetool #解决冲突
    $ git commit
    
  7. 还原工作区域

    1
    2
    
    $ git stash list            #查看存储工作列表 找到需要恢复的存储
    $ git stash pop stash@{0}
    

使用规范


准则

  • 严格遵守开发流程
  • 设置真实的用户信息
  • 仓库代码统一使用换行结束一行
  • 严格按照注释模版填写注释
  • 禁止使用git commit -m 的方式添加注释
  • 合并代码使用git merge –no-ff
  • 使用mergetool合并和解决冲突,外部工具使用kdiff3
  • 尽快提交已完成功能
  • 禁止提交其他无关文件到远程仓库
  • 禁止直接在远程仓库修改文件
  • 所有冲突必须在本地解决

语义化版本 2.0.0


语义化版本控制的规范是由 Gravatars 创办者兼 GitHub 共同创办者 Tom Preston-Werner 所建立。以下内容摘自https://semver.org/lang/zh-CN/

摘要

版本格式:主版本号.次版本号.修订号,版本号递增规则如下:

  1. 主版本号:当你做了不兼容的 API 修改,
  2. 次版本号:当你做了向下兼容的功能性新增,
  3. 修订号:当你做了向下兼容的问题修正。
  4. 先行版本号及版本编译元数据可以加到“主版本号.次版本号.修订号”的后面,作为延伸。

简介

在软件管理的领域里存在着被称作“依赖地狱”的死亡之谷,系统规模越大,加入的包越多,你就越有可能在未来的某一天发现自己已深陷绝望之中。

在依赖高的系统中发布新版本包可能很快会成为噩梦。如果依赖关系过高,可能面临版本控制被锁死的风险(必须对每一个依赖包改版才能完成某次升级)。而如果依赖关系过于松散,又将无法避免版本的混乱(假设兼容于未来的多个版本已超出了合理数量)。当你专案的进展因为版本依赖被锁死或版本混乱变得不够简便和可靠,就意味着你正处于依赖地狱之中。

作为这个问题的解决方案之一,我提议用一组简单的规则及条件来约束版本号的配置和增长。这些规则是根据(但不局限于)已经被各种封闭、开放源码软件所广泛使用的惯例所设计。为了让这套理论运作,你必须先有定义好的公共 API 。这可以透过文件定义或代码强制要求来实现。无论如何,这套 API 的清楚明了是十分重要的。一旦你定义了公共 API,你就可以透过修改相应的版本号来向大家说明你的修改。考虑使用这样的版本号格式:X.Y.Z (主版本号.次版本号.修订号)修复问题但不影响API 时,递增修订号;API 保持向下兼容的新增及修改时,递增次版本号;进行不向下兼容的修改时,递增主版本号。

我称这套系统为“语义化的版本控制”,在这套约定下,版本号及其更新方式包含了相邻版本间的底层代码和修改内容的信息。

语义化版本控制规范(SemVer)

以下关键词 MUST、MUST NOT、REQUIRED、SHALL、SHALL NOT、SHOULD、SHOULD NOT、 RECOMMENDED、MAY、OPTIONAL 依照 RFC 2119 的叙述解读。(译注:为了保持语句顺畅, 以下文件遇到的关键词将依照整句语义进行翻译,在此先不进行个别翻译。)

  1. 使用语义化版本控制的软件必须(MUST)定义公共 API。该 API 可以在代码中被定义或出现于严谨的文件内。无论何种形式都应该力求精确且完整。
  2. 标准的版本号必须(MUST)采用 X.Y.Z 的格式,其中 X、Y 和 Z 为非负的整数,且禁止(MUST NOT)在数字前方补零。X 是主版本号、Y 是次版本号、而 Z 为修订号。每个元素必须(MUST)以数值来递增。例如:1.9.1 -> 1.10.0 -> 1.11.0。
  3. 标记版本号的软件发行后,禁止(MUST NOT)改变该版本软件的内容。任何修改都必须(MUST)以新版本发行。
  4. 主版本号为零(0.y.z)的软件处于开发初始阶段,一切都可能随时被改变。这样的公共 API 不应该被视为稳定版。
  5. 1.0.0 的版本号用于界定公共 API 的形成。这一版本之后所有的版本号更新都基于公共 API 及其修改内容。
  6. 修订号 Z(x.y.Z | x > 0)必须(MUST)在只做了向下兼容的修正时才递增。这里的修正指的是针对不正确结果而进行的内部修改。
  7. 次版本号 Y(x.Y.z | x > 0)必须(MUST)在有向下兼容的新功能出现时递增。在任何公共 API 的功能被标记为弃用时也必须(MUST)递增。也可以(MAY)在内部程序有大量新功能或改进被加入时递增,其中可以(MAY)包括修订级别的改变。每当次版本号递增时,修订号必须(MUST)归零。
  8. 主版本号 X(X.y.z | X > 0)必须(MUST)在有任何不兼容的修改被加入公共 API 时递增。其中可以(MAY)包括次版本号及修订级别的改变。每当主版本号递增时,次版本号和修订号必须(MUST)归零。
  9. 先行版本号可以(MAY)被标注在修订版之后,先加上一个连接号再加上一连串以句点分隔的标识符来修饰。标识符必须(MUST)由 ASCII 字母数字和连接号 [0-9A-Za-z-] 组成,且禁止(MUST NOT)留白。数字型的标识符禁止(MUST NOT)在前方补零。先行版的优先级低于相关联的标准版本。被标上先行版本号则表示这个版本并非稳定而且可能无法满足预期的兼容性需求。范例:1.0.0-alpha、1.0.0-alpha.1、1.0.0-0.3.7、1.0.0-x.7.z.92。
  10. 版本编译元数据可以(MAY)被标注在修订版或先行版本号之后,先加上一个加号再加上一连串以句点分隔的标识符来修饰。标识符必须(MUST)由 ASCII 字母数字和连接号 [0-9A-Za-z-] 组成,且禁止(MUST NOT)留白。当判断版本的优先层级时,版本编译元数据可(SHOULD)被忽略。因此当两个版本只有在版本编译元数据有差别时,属于相同的优先层级。范例:1.0.0-alpha+001、1.0.0+20130313144700、1.0.0-beta+exp.sha.5114f85。
  11. 版本的优先层级指的是不同版本在排序时如何比较。判断优先层级时,必须(MUST)把版本依序拆分为主版本号、次版本号、修订号及先行版本号后进行比较(版本编译元数据不在这份比较的列表中)。由左到右依序比较每个标识符,第一个差异值用来决定优先层级:主版本号、次版本号及修订号以数值比较,例如:1.0.0 < 2.0.0 < 2.1.0 < 2.1.1。当主版本号、次版本号及修订号都相同时,改以优先层级比较低的先行版本号决定。例如:1.0.0-alpha < 1.0.0。有相同主版本号、次版本号及修订号的两个先行版本号,其优先层级必须(MUST)透过由左到右的每个被句点分隔的标识符来比较,直到找到一个差异值后决定:只有数字的标识符以数值高低比较,有字母或连接号时则逐字以 ASCII 的排序来比较。数字的标识符比非数字的标识符优先层级低。若开头的标识符都相同时,栏位比较多的先行版本号优先层级比较高。范例:1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0。

为什么要使用语义化的版本控制?

这并不是一个新的或者革命性的想法。实际上,你可能已经在做一些近似的事情了。问题在于只是“近似”还不够。如果没有某个正式的规范可循,版本号对于依赖的管理并无实质意义。将上述的想法命名并给予清楚的定义,让你对软件使用者传达意向变得容易。一旦这些意向变得清楚,弹性(但又不会太弹性)的依赖规范就能达成。

举个简单的例子就可以展示语义化的版本控制如何让依赖地狱成为过去。假设有个名为“救火车”的函式库,它需要另一个名为“梯子”并已经有使用语义化版本控制的包。当救火车创建时,梯子的版本号为 3.1.0。因为救火车使用了一些版本 3.1.0 所新增的功能, 你可以放心地指定依赖于梯子的版本号大等于 3.1.0 但小于 4.0.0。这样,当梯子版本 3.1.1 和 3.2.0 发布时,你可以将直接它们纳入你的包管理系统,因为它们能与原有依赖的软件兼容。

作为一位负责任的开发者,你理当确保每次包升级的运作与版本号的表述一致。现实世界是复杂的,我们除了提高警觉外能做的不多。你所能做的就是让语义化的版本控制为你提供一个健全的方式来发行以及升级包,而无需推出新的依赖包,节省你的时间及烦恼。

如果你对此认同,希望立即开始使用语义化版本控制,你只需声明你的函式库正在使用它并遵循这些规则就可以了。请在你的 README 文件中保留此页连结,让别人也知道这些规则并从中受益。

FAQ

在 0.y.z 初始开发阶段,我该如何进行版本控制?
最简单的做法是以 0.1.0 作为你的初始化开发版本,并在后续的每次发行时递增次版本号。

如何判断发布 1.0.0 版本的时机?

当你的软件被用于正式环境,它应该已经达到了 1.0.0 版。如果你已经有个稳定的 API 被使用者依赖,也会是 1.0.0 版。如果你很担心向下兼容的问题,也应该算是 1.0.0 版了。

这不会阻碍快速开发和迭代吗?

主版本号为零的时候就是为了做快速开发。如果你每天都在改变 API,那么你应该仍在主版本号为零的阶段(0.y.z),或是正在下个主版本的独立开发分支中。

对于公共 API,若即使是最小但不向下兼容的改变都需要产生新的主版本号,岂不是很快就达到 42.0.0 版?

这是开发的责任感和前瞻性的问题。不兼容的改变不应该轻易被加入到有许多依赖代码的软件中。升级所付出的代价可能是巨大的。要递增主版本号来发行不兼容的改版,意味着你必须为这些改变所带来的影响深思熟虑,并且评估所涉及的成本及效益比。

为整个公共 API 写文件太费事了!

为供他人使用的软件编写适当的文件,是你作为一名专业开发者应尽的职责。保持专案高效一个非常重要的部份是掌控软件的复杂度,如果没有人知道如何使用你的软件或不知道哪些函数的调用是可靠的,要掌控复杂度会是困难的。长远来看,使用语义化版本控制以及对于公共 API 有良好规范的坚持,可以让每个人及每件事都运行顺畅。

万一不小心把一个不兼容的改版当成了次版本号发行了该怎么办?

一旦发现自己破坏了语义化版本控制的规范,就要修正这个问题,并发行一个新的次版本号来更正这个问题并且恢复向下兼容。即使是这种情况,也不能去修改已发行的版本。可以的话,将有问题的版本号记录到文件中,告诉使用者问题所在,让他们能够意识到这是有问题的版本。

如果我更新了自己的依赖但没有改变公共 API 该怎么办?

由于没有影响到公共 API,这可以被认定是兼容的。若某个软件和你的包有共同依赖,则它会有自己的依赖规范,作者也会告知可能的冲突。要判断改版是属于修订等级或是次版等级,是依据你更新的依赖关系是为了修复问题或是加入新功能。对于后者,我经常会预期伴随着更多的代码,这显然会是一个次版本号级别的递增。

如果我变更了公共 API 但无意中未遵循版本号的改动怎么办呢?(意即在修订等级的发布中,误将重大且不兼容的改变加到代码之中)

自行做最佳的判断。如果你有庞大的使用者群在依照公共 API 的意图而变更行为后会大受影响,那么最好做一次主版本的发布,即使严格来说这个修复仅是修订等级的发布。记住, 语义化的版本控制就是透过版本号的改变来传达意义。若这些改变对你的使用者是重要的,那就透过版本号来向他们说明。

我该如何处理即将弃用的功能?

弃用现存的功能是软件开发中的家常便饭,也通常是向前发展所必须的。当你弃用部份公共 API 时,你应该做两件事:(1)更新你的文件让使用者知道这个改变,(2)在适当的时机将弃用的功能透过新的次版本号发布。在新的主版本完全移除弃用功能前,至少要有一个次版本包含这个弃用信息,这样使用者才能平顺地转移到新版 API。

语义化版本对于版本的字串长度是否有限制呢?

没有,请自行做适当的判断。举例来说,长到 255 个字元的版本已过度夸张。再者,特定的系统对于字串长度可能会有他们自己的限制。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐