Git版本控制、分支策略与代码评审

Git版本控制、分支策略与代码评审

Git介绍

  • Git是一种免费的、开源的分布式版本控制系统(DVCS)
  • Git是目前使用最为普遍的版本控制系统
  • Git是由Linus Torvalds开发的 (这位IT界的大神同时也是Linux的做者)
  • Git的第一个版本(v0.99)在2005年7月发布,最新版本是v2.17(截止2018年4月)
  • Git支持Windows, Linux/Unix, Mac
  • 和商业版本控制系统相比,Git免费、开源且更强大、稳定、快速
  • 和传统版本控制系统(好比CVS, SVN)相比,Git具备支持轻量级分支、分布式开发、高性能等优势。
  • Git支持命令行操做,能够很是方便地集成到CI/CD pipeline中

参考文档:html

Git工具与代码托管网站

在Windows上安装使用Git

登陆Git官网: https://git-scm.com/ 下载Git for Windows,安装后有3个程序:github

  • Git Bash (模拟Linux终端)
  • Git CMD (Windows终端)
  • Git GUI (Windows界面)

推荐使用Git Bash,Git CMD和Git GUI能够忽略。web

在Linux/Unix和Mac上使用Git和在Windows上使用Git Bash同样,再也不赘述。编程

IDE支持Git

如今主流的IDE(好比Intellij Idea和Eclipse) 都对Git有很是好的支持。segmentfault

推荐经过IDE + Git Bash 方法来使用Git。windows

代码托管网站

如何选择?bash

  • 土豪本身搭建GitHub Enterprise
  • Atlassian铁粉用BitBucket
  • 中产用GitLab Enterprise
  • 屌丝本身搭建GitLab CE (我是屌丝我自豪)
  • 最屌丝用国内的代码托管网站…

Git原理

Git工做流

下图列出了一个简明的Git工做流:分布式

这里写图片描述

Git 分支

概述

  • 分支(branch)能够理解为是仓库(repository)的一种隔离视图。
  • 分支的目的为了支持多人、多版本开发。

  • Git支持多分支开发,并鼓励使用本地多分支,这些分支之间是彼此彻底独立的。

  • 和传统的版本控制工具(好比CSV, SVN)相比,Git对分支的隔离是逻辑隔离而不是物理隔离,这意味着建立分支很是快速、存储分支不会占用过多磁盘空间
  • 常见的分支操做包括:建立分支、切换分支、合并分支、删除分支。

参考文档:

理解如何使用Git分支

这里以GitHub Flow为例 来演示常见的分支操做。

打开GitHub Flow ,点击动画上的不一样区域来理解常见的分支操做:

  1. 建立分支

    • 给分支名取一个简短且有意义的名字
  2. 提交改动

    • 填写有意义的commit message: what & why

    • 推荐的commit message格式为 <Item Id>: <Description> ,好比:

      512: 增长了对NFS的支持

  3. 发起合并请求

    • 同时发起code review和讨论
    • 不通过review的代码不容许合并进public branch
  4. 代码评审和讨论

    • 审查代码是否知足功能、规范、测试结果、测试覆盖等要求
    • 审查合格才容许合并合进public branch,不合格的代码退回从新修改
    • 鼓励使用同行评审(Peer Review)而不是上下级评审,鼓励积极反馈、互动
    • 评审对事不对人,多建议,不批评,不指责
    • 评审过程对reviewer和代码提交者双方都是一个学习提高的过程
  5. 部署

    • 使用通过完整测试的分支来部署到生产环境
    • 部署后发生问题须要回退时,用上一个版本进行回退
  6. 合并回master分支

    • 部署成功,没有问题后才合并到master分支

GitHub Flow是一种很是好的Git分支策略,但不是惟一的Git分支策略。

在后面的分支策略中会对Git分支策略做深刻探讨。

Git经常使用命令

参考文档

除了查看网上的参考文档以外,还能够在Git Bash中执行git --helpgit <command> --help 来查看Git帮助文档。

Git使用场景

下面列出经常使用的Git使用场景。

场景1: 在GitLab上新建一个repository

若是不想让公众都能看到你的代码,须要将repository设置为private,并将相应权限授给团队成员。

强烈推荐为每一个repository建立README.md和.gitignore文件。

强烈推荐对repository的public branch进行保护,只有经过merge request才能合并代码进public branch。

场景2:将GitLab上一个repository clone到本地

GitLab支持SSH和HTTP(S)两种方式。

  • SSH方式须要将本地的SSH public key添加到GitLab帐号的setting中
  • HTTP(S)方式第一次push到GitLab时须要提供用户名和密码登录

设置git config的user.nameuser.email :

# 查看git config
git config --list

# 设置Global的user.name和user.email
git config --global user.name <user_name>
git config --global user.email <user_email>

# 查看git config确认
git config --list

从GitLab上clone一个repository到本地:

# SSH 方式
git clone <repo_git_url>

# HTTP 方式
git clone <repo_http_url>

克隆后的缺省分支是GitLab repository上的缺省分支,通常是master分支。

场景3:建立一个本地分支

Git建立分支时,缺省从当前分支(base branch)来建立,若是当前分支不是想要的base branch,须要先切换分支。

# 切换当前分支(base branch)
git checkout <base_branch>

# 建立一个新分支,并切换到该分支
git checkout -b <new_branch>

场景4:将本地分支的改动push到GitLab

# 更新working directory
git pull

# 改动代码...此处省略2048个字

# 查看working tree状态
git status

# 比较改动先后,查看改动了什么 (用IDE来比较更加直观)
git diff <file>

# 添加新建的文件到Git Stage中
git add <file>

# 或添加所有新建的文件到Git Stage中
git add .

# 提交改动到local repository
git commit -m "<commit_message>"

# 推送到remote repository,建立remote new branch,并设置upstream reference
git push -u origin <new_branch>

若是第一次push时不经过-u 来push新的branch,在git pull时会提示要先设置upstream reference。

# 设置upstream reference
git branch --set-upstream-to=origin/<branch> <branch>

以后的push能够不须要再加-u参数:

git push origin <branch>

场景5:用户A/B同时修改同一个文件,A先提交,B后提交,解决冲突后push到GitLab

此时B在git push 时会遇到错误,错误信息为:

error: failed to push some refs to …
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., ‘git pull …’) before pushing again.
hint: See the ‘Note about fast-forwards’ in ‘git push –help’ for details.

B执行git pull 试图自动合并A先前提交的改动,自动合并失败的状况下须要手工合并:

Auto-merging …
CONFLICT (content): Merge conflict in …
Automatic merge failed; fix conflicts and then commit the result.

B打开并编辑发生冲突的文件,手工修改后,保存文件。

参照场景4,从新提交完成合并冲突(合并冲突也是一种改动)的文件到GitLab。

场景6:在本地改动后,在git add 前回退

撤销本地改动:

git checkout -- <file>

推荐使用IDE的revert功能,更加简单直观。

Intellij Idea: Version Control -> Local Changes -> Right click the file -> Revert

场景7:在本地改动后,在git push 前回退

用远程repository强制替换本地文件:

git fetch origin
git reset --hard origin/<branch>

推荐使用IDE的Reset功能,更加简单直观。

Intellij Idea: Version Control -> Log -> Right click the last commit marked with remote branch (origin/) -> Reset current branch to here -> Select “Hard”

场景8:在本地改动后,在git push后回退

方法一:

手工回退后,从新提交改动去覆盖remote branch。

方法二:

使用IDE的revert功能。

Intellij Idea: Version Control -> Log -> Right click the commit -> Revert, and then commit and push a “revert commit” to override both remote and local branches.

方法三:

# 查看git commit log,复制要回退的版本commit id
git log --graph --oneline --decorate --all

# 将本地回退到指定版本
git reset --hard <commit_id>

# 查看working tree状态,此时本地落后于远程1个commit
git status

# 强制用本地覆盖远程(危险操做!!)
git push -f origin <branch>

# 再次查看working tree状态,此时本地和远程应该同步
git status

推荐使用方法二

方法三比较危险,不推荐使用;强烈建议经过protect branch来防止强制用本地覆盖远程分支。

场景9:查看文件修改历史

# 按树形结构显示
git log --graph --oneline --decorate --all [file]

# 按列表显示
git log --pretty=oneline [file]

# 简单粗暴显示
git log [file]

推荐使用IDE的查看历史功能。

Intellij Idea:

  • 查看所有文件修改历史:Version Control -> Log
  • 查看单个文件修改历史:Right click the file -> Git -> Show history

场景10:在GitLab上设置分支保护,要求只有经过merge request才能合并代码

用Admin或Owner或Master帐号登陆,打开指定project -> Settings -> Repository -> Protected Branches

选择要protect的branch (支持wildcard):

  • 要求每一个人都要经过merge request才能合并代码进入public branch:选择”Allowed to merge”为”Developers + Masters”,选择”Allow to push” 为”No one“
  • 对public branch都要设置protect branch

参考文档:

https://docs.gitlab.com/ee/user/project/protected_branches.html

在我使用的GitLab CE 10.6.4 貌似有一个bug,当选择”Allowed to
push”为”Masters“时,Developer角色的用户仍然能够push。
这个问题的缘由多是在向gitlab做git push时,读取的是在Windows上Manage WindowsCredentials中的另外一个较高权限的username,因此gitlab才容许做git push

场景11:本地改动后,push到本地分支,而后在GitLab上发起merge request

参照场景4,建立一个新的本地分支,改动后push到远程的该分支。

而后在GitLab上发起merge request:

  1. 打开项目,点击左边的Merge Requests,选择New merge request
  2. 选择Source branch和Target branch
  3. 点击”Compare branches and continue“
  4. 选择Assignee (不要选择Assign to me)
  5. 若是是临时branch,勾选“Remove source branch when merge request is accepted”
  6. 检查无误后,点击“Submit merge request”按钮

GitLab CE貌似不能作到防止本身来merge本身的merge request,这一点和GitHub Enterprise仍是有差距的。

因此只能流程上要求你们不要merge本身的merge request。

若是发起merge request后,在该merge request关闭以前,继续往source branch分支push代码,这些commit也会被自动包含在该merge request里。

场景12:代码审查,经过merge request

  1. 打开项目,点击左边的Merge Request,选择Open查看Open状态的merge request

  2. 点击Commit查看详细信息

  3. 点击Changes查看具体的改动

  4. 写comment,并和代码提交者讨论。

  5. 审查经过后,点击”Merge”按钮

场景13:代码审查,拒绝merge request

若是审查不经过,写清楚comment,并在和代码提交者讨论后,要求其进行修改。

场景14:将本地的一个新项目放在GitLab上

在GitLab上建立一个同名的空project (不包含任何文件),设置好用户权限。

用Owner/Master角色帐号将本地项目push到GitLab:

# 设置Global的user.name和user.email (若是须要)
git config --global user.name <user_name>
git config --global user.email <user_email>

# 将本地项目转成Git项目
git init

# 检查对应的remote repository
git remote -v

# 添加远程的remote repository
git remote add origin <repo_git_url>

# 确认remote repository
git remote -v

# 推送到remote branch
git status
git add .
git commit -m "Inital commit"
git push -u origin <branch>

在我使用的GitLab CE 10.6.4 有一个bug,当建立好一个空的项目后,用Owner帐号将本地项目做git push,会遇到”You are not allowed to push code to this project“的错。

这个问题的缘由是在向gitlab做git push时,读取的是在Windows上Manage Windows Credentials中的另外一个错误的username,因此被gitlab拒绝了。

解决的方法是,在Manage Windows Credentials中删除原来的credentials,做git push时会提示输入用户名和密码,输入owner的用户名和密码后,就能够git push成功了。

这应该是GitLab的一个设计缺陷,也就是GitLab假设在一个GitLab上,你老是只用一个帐号来登录。

参考文档:https://gitlab.com/gitlab-com/support-forum/issues/207

使用GitHub来学习提高

打开GitHub Explore 来查看GitHub上最流行的项目,也能够经过搜索来查看好比最流行的Java项目、最流行的JavaScript项目等等。

国内有一些走歪道的程序员上淘宝买GitHub加star服务(鄙视),因此看到一些流行的中文项目时,要持保留态度。

大部分开源软件都把代码托管在GitHub上,能够经过GitHub来clone开源软件的代码到本地做源代码研读,是提高编程的内功的有效方法。若是想加快clone速度能够在git clone后加上--depth=1 的参数来只clone最新版本。

你也能够把你的点子变成一个项目放到GitHub上,分享给开源社区。

分支策略

分支策略就是在哪一个分支上开发、在哪一个分支上部署、分支同步和分支合并的原则。

项目团队应该遵循统一的分支策略。

现代主流的分支策略包括:

注意:这里讲的分支不包括我的的private branch,无论哪一种分支策略都要求先在private branch开发测试后再发起merge request/pull request, 经过代码评审后才能合并进入public branch。

主流分支策略比较

下表对上面的三种现代主流的分支策略进行了比较:

这里写图片描述

Trunk based development官网将Trunk based development和一些现代主流的分支策略进行了比较:

https://trunkbaseddevelopment.com/alternative-branching-models/

下面这篇文章也详细地论述了比较了不一样的分支策略:

https://www.continuousdeliveryconsulting.com/blog/version-control-strategies/

如何选择分支策略

分支策略没有好坏,适合本身的才最重要。

一般的建议是,项目团队选择一种比较符合本身团队的主流分支策略,在实际工做中加以调整,最终发展成符合团队开发节奏的分支策略。

分支越多,复杂度越大,分支同步与分支合并的成本越大,团队内部的沟通成本也越大。

选择分支策略的原则:

  • 始终保持master处在可发布状态
  • 始终保持构建处在成功状态
  • 下降代码合并的复杂度和风险
  • 下降团队沟通成本
  • 符合团队的现状

代码评审

前面章节也已经描述了如何做代码评审,这里再对代码评审的一些原则做小结:

  • Four eyes check
  • 不要既当运动员,又当裁判员

  • 审查代码是否知足功能、规范、测试结果、测试覆盖等等

  • 借助工具来提高代码审查的效率
  • 审查合格才容许合进public branch,不合格的代码退回从新修改
  • 不怕犯错,可是不要犯重复的错
  • 提交审查前,本身先检查一遍
  • 鼓励使用同行评审(Peer Review)而不是上下级评审,鼓励积极反馈、互动
  • 评审对事不对人,多建议,不批评
  • 己所不欲,勿施于人
  • 评审过程对reviewer和代码提交者双方都是一个学习提高的过程
  • 保持开放的心态,相互学习,共同成长

思考: 什么文件不该该放在Git上?

你应该把几乎一切文件都做版本控制,除了:

  • 敏感信息,好比用户名、密码、Token等
  • 编译后产生的二进制文件,好比jar/war包、class文件
  • 测试结果
  • 日志
  • 临时文件
  • IDE自动生成的配置文件

  • 空文件夹(若是必定要加入Git,在该目录下添加一个.gitkeep 文件)

使用.gitignore来让Git忽略上述的文件和文件夹。

参考文档: