likes
comments
collection
share

Git 实践规范分享

作者站长头像
站长
· 阅读数 6

招聘时间

咳咳,在进入正题之前,发一个团队的招聘小广告:

想要参与万亿级别的项目吗?想要跟来自海内外大厂、顶尖高校的大佬们做同事吗?想要和字节一起跳动吗?

欢迎加入抖音电商前端团队,在这里你可以接触到业内前沿的前端技术,找到自己感兴趣的方向进行深研,和公司、团队一起成长。

如果感兴趣的话,欢迎点击岗位信息 进行投递~

期待你的加入~

一、背景

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

Git 有许多优势,目前是许多团队(当然也包括作者的团队)唯一使用的代码版本控制软件,为了让大家更加方便、规范地使用 Git,本文整理了作者的团队的 Git 规范、常见场景实践、注意事项、常见问题。

二、强制规范

My-Git Flow 工作流

  1. 有且仅有 master 分支用于生产环境的部署,所有部署生产环境的 SCM 包分支来源必须是 master
  1. master 是受保护的分支,任何人都不能推送代码至 master
  1. 任何新变更都需要从 master 派生出一个分支,并且为其起一个描述新变更内容的名字:比如 feat/2198234-add-error-boundary
  1. 在本地提交该新分支变更,并且应经常性的向服务器端该同名分支推送变更
  1. 在新分支可以合并(即需求完成测试将要上线)的时候,新建一个 merge request
  1. 只有在其他人 review 通过之后,新分支才允许合并到 master 分支

分支管理

分支命名

分支命名应该遵循尽量语义化,如果有关联的 meego,尽量在分支命名中体现出来。

// Good cases
feat/2198234-add-error-boundary
fix/api-error-message

// Bad cases
text
hotfix-xiaowang
dev-xiaowang

Commit 规范

Commit Message

Commit Message 应遵循 Conventional commits 规范

团队的项目中应该接入 Commitizen Commitlint 来对 commit message 进行规范和校验。

<type>(<scope>): <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>

整体格式说明:

  1. Commit message 都包括三个部分:Header,Body 和 Footer。
  1. 其中,Header 是必需的,Body 和 Footer 可以省略
  1. Header,Body 和 Footer 之前用空行分隔。
  1. 每一行内容长度都不能超过100个字符。
# header: <type>(<scope>): <subject>,100-character line
# - type: feat, fix, docs, style, refactor, test, chore
# - scope: can be empty (eg. if the change is a global or difficult to assign to a single component)
# - subject: start with verb (such as 'change')
#
# body: 100-character wrapped. This should answer:
# * Why was this change necessary?
# * How does it address the problem?
# * Are there any side effects?
#
# footer: reserved field
# - BREAKING CHANGE: description
# - Closes #123, #245, #992
type(必需)

type 用来说明 commit 的类别,只允许使用下面的标识:

Changelog 配置参考:如果 typefeatfix ,则该 commit 信息将肯定出现在 change log 之中。其他情况(docschorestylerefactortest)再定,建议是不要。

feat: A new feature

fix: A bug fix(code、UI)

docs: Documentation only changes

perf: A code change that improves performance

refactor: A code change that neither fixes a bug nor adds a feature

style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)

test: Adding missing tests or correcting existing tests

build: Changes that affect the build system or external dependencies

ci: Changes to our CI configuration files and scripts

chore : Other changes that don't modify src or test files

revert: Reverts a previous commit. If the commit reverts a previous commit, it should begin with revert: , followed by the header of the reverted commit. In the body it should say: This reverts commit <hash>., where the hash is the SHA of the commit being reverted.
scope(可选)

scope 用于说明 commit 影响的范围,比如影响哪个模块或者功能等。推荐按功能描述。

subject(必需)

subject 是关于 commit 信息的简短描述。

Commit message 示例
// good case
feat(页面A): 新增 B 模块

- 增加统一的面包屑布局

- 抽象公共 ProTable 组件,封装了筛选栏、分页器、数据获取

- 升级 antd 组件库版本号至 2.9.0


// bad case
fix: xiaowang

chore: 修改文案

fix: 修复线上问题

Commit 原子性

倡导最小粒度 commit 原则,原则上每个独立的改动对应一个 commit。

为了更好的跟踪提交历史以及回溯,要确保 commit 的“原子性”,每个 commit 要以适当的粒度包含且仅包含“单项”改动,避免过多的临时 commit;提交代码时应该让每个 commit 都更具有意义,而不是散乱随意的 commit。

判断原则参考:commit 粒度尽量小,且只提交该单个 commit 时功能能够正常运行。

Squash Commits

基于 commit 原子性原则,应避免将过于零散的 commit 提交合并到主干分支。对于需求或者功能的 commit,尤其是在协作开发时,如果 commit 过于临时或零散,应整合成一个 commit 再提交到主干确保主干历史简洁有用

squash commits 能有效减少 rebase 方式合并时的冲突,能简化解决冲突的过程;频繁临时的 commit 导致多个无意义提交,容易引起他人困惑。

注意:已经合并到主干的 commit,不能进行 squash,改变主干(协作)分支历史会导致其他人无法正常同步。

临时 commits 的解决方案

如果你的工作经常被打断,导致出现许多临时的 commits,那么你可能需要善用 git stash 技巧。

三、操作建议

Rebase vs Merge

关于 Rebase 和 Merge 的讨论,可以参考这篇文章

异同总结

  • Rebase 和 Merge 都可以用来合并不同分支的 commits
  • Merge 可以保持修改内容的历史记录,但是历史记录会很复杂,关注点在于真实的操作记录
  • Rebase 历史记录简单(线性),是在原有提交的基础上将差异内容反映进去
MergeRebase
Git 实践规范分享Git 实践规范分享

推荐操作

  • 合并 master 分支的最新代码至本地分支,请使用 git rebase master
  • 将本地代码合入公共分支,请使用 merge(提交 merge request)

四、Git 配置及工具

必要的全局配置

  • git config --global user.name [YOUR_NAME]
  • git config --global user.email [YOUR_EMAIL]
  • git config --global core.ignorecase false

按照自己的习惯和喜好的配置

  • git config --global push.default current
  • git config --global alias.co checkout
  • git config --global alias.ci commit
  • git config --global alias.br branch
  • git config --global alias.rb rebase
  • git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"

设置 Git 的 SSH-Keys

  1. 访问个人主页的 SSH Keys 设置页面
  1. 在本地生成 SSH 公钥

    1. 先确认是否已有 SSH 公钥(默认情况下,用户的 SSH 密钥存储在其 ~/.ssh 目录下。 进入该目录并列出其中内容,你便可以快速确认自己是否已拥有密钥)
    2. ls ~/.ssh
      // 如果有 id_rsa、id_rsa.pub 这两个文件,则已存在 SSH 公钥,将 id_rsa.pub 的内容拷贝至 SSH Keys 设置页面即可
      // windows 上是 id_ecdsa 和 id_ecdsa.pub 文件
      
    3. 如果上一步没找到 SSH 公钥,则手动生成
    4. ssh-keygen
      // 这个指令会询问生成的位置、密钥口令等,直接默认就行
      // windows 上:ssh-keygen -t ecdsa -C "xxxxxx@bytedance.com"
      
    5. 拷贝公钥至 SSH Keys 的设置页面
    6. cat ~/.ssh/id_rsa.pub
      // 形如下:
      ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAklOUpkDHrfHY17SbrmTIpNLTGK9Tjom/BWDSU
      GPl+nafzlHDTYW7hdI4yZ5ew18JH4JW9jbhUFrviQzM7xlELEVf4h9lFX5QVkbPppSwg0cda3
      Pbv7kOdJMTasjhfashjfgasjhfgahsjfgashjfgahjsgashjfgahjsfgaahfjsasfhj=@mylaptop.local
      

安装 Git Commit 命令行工具 —— Commitizen

yarn global add commitizen cz-conventional-changelog
echo '{ "path": "cz-conventional-changelog" }' > ~/.czrc

使用效果

Git 实践规范分享

VSCode 插件

GitLens —— 查看 Git 提交记录,找到代码的提交者

Git 可视化工具

直接用命令行查看 git 的 diff 是非常高效的,并且也非常符合高端程序员的气质,但是需要记住的命令也是非常多。作为一个懒惰的程序员,我们就需要一些可视化工具来辅助我们查看 diff 和解决冲突。SourceTree 是一个界面优美、功能强大且免费的工具。请注意,SourceTree 应该是一个辅助工具,它帮助我们更方便查看代码,我们不应该依赖它去提交代码。

五、常见场景

接下来,让我们看一些工作中使用 Git 常见的场景。

基本场景:

新拉取项目

git clone git@code.byted.org:XXX/XXXX.git

新建分支

// 从当前分支新建
git checkout -b NEW_BRANCH_NAME

// 基于远端分支新建分支
git checkout -b NEW_BRANCH_NAME origin/REMOTE_BRANCH_NAME(远程分支名称)

推送代码至远端分支

git push origin REMOTE_BRANCH_NAME

拉取远端变更

// 拉取远端所有变更
git fetch
// 把远程分支上的内容都拉取到本地
git pull origin REMOTE_BRANCH_NAME [--rebase]

合并远程分支到本地

常见于远程的 master 或其他分支有变更,而且这部分变更需要合入当前分支。(此处以 master 为例)

git checkout master
git pull --rebase
git checkout YOUR_BRANCH
git rebase master (此处也可以使用 merge)
// rebase 之后,将代码 push 到远端,由于此处改变了当前分支的提交历史,因此可能需要 --force-with-lease
git push origin YOUR_BRANCH --force-with-lease

合并分支到 master

  1. 在 gitlab 上手动提交 MergeRequest

  2. 在别人 Approve 之后,点击 merge 合入 master(建议勾选 「Squash commits」)

Git 实践规范分享

暂存及恢复修改(stash)

常见于临时切换分支,且不想放弃或提交当前的修改。

// 将当前修改放入暂存区,可以在 save 之后加入暂存的信息
git stash [save][message] 
// 查看当前暂存区清单
git stash list
// 恢复暂存区的修改,仅使用"git stash pop" 将恢复到最新的操作。指定stash ID (如:stash@{1} ),则可以恢复特定的操作。
git stash pop [ID]
// 删除暂存的操作, 如果使用 "git stash drop",会删除最新的操作。指定stash ID (如:stash@{1} ),则可以删除特定的操作。
git stash drop [ID]

配置远程目录

git remote add origin git@code.byted.org:xxx/XXX.git

新建项目

// 1. 新建 .git 文件
git init
// 2. 配置远程目录
git remote add origin git@code.byted.org:xxx/XXX.git
// 3. 推送文件至远端的 master 分支(完成后需要去远端设置 master 为受保护分支)
git push -u origin master

\

复杂场景

压缩(合并)提交

常见于本地分支有多次 commits,其中全部或部分 commits 可以合并为一次有语义的 commit

// 1. 利用 rebase -i 合并 commits,其中的 COMMIT_HASH 是需要合并的 commits 的边界(不包含)
git rebase -i COMMIT_HASH
// 2. 在 vim 界面编辑需要保留和 squash 的 commits,可以压缩修改成 s

压缩前的 git log 信息:

Git 实践规范分享

假如我们想合并 e75a64c ~ cc21dbe 这部分提交:

git rebase -i 2701084

修改需要保存的 commit 信息

Git 实践规范分享

合并后的 git log 信息

Git 实践规范分享

解决冲突

解决冲突常见于在本地 rebase 或 merge master 的变更,此处以 rebase master 为例:

// 为了减少解决冲突的次数,建议本地先 squash commits(通过上面的合并提交)
git rebase -i COMMIT_HASH
// rebase master 的变更
git rebase master
// 手动解决冲突

// 解决完冲突之后,继续 rebase
git add .
git rebase --continue

注意:解决冲突遇到困难时(比如不知道该采用谁的提交),建议直接找到代码的提交者,当面解决。

// 暂停当前的合并操作
git rebase --abort

Revert 代码

通常情况下,代码合入 master 分支的时机是在上线前。由于误操作、提前合入后被告知无法发布等原因导致代码被误合入 master,为了避免影响后续上线同学和线上环境,我们可能需要及时 revert 已经合入 master 的代码。

Revert 的原理就是提交一个逆向 commit,对之前的 commits 进行 undo 操作,而不是抹除之前的 commits 在 git 中的记录

  1. 进入需要 revert 的 merge request 链接,直接点击 Revert 按钮。

Git 实践规范分享

  1. 如往常提交 Merge Request 一样,在这里提交 revert 信息

Git 实践规范分享

刚刚的 revert 操作帮我们做了三件事:

  • 生成新分支:revert-COMMIT_HASH
  • 提交 undo 的 commit 「fix: revert "Merge branch 'chore/competitor-shop' into 'master'"」
  • 提交 merge request
  1. 我们可以在新生成的 revert-COMMIT_HASH 上进行修改操作,也可以在别人 Approve 之后将 merge request 合入 master,合入 master 之后,本次 revert 操作就大功告成了。
  1. 恢复 revert:经过上面的操作后,我们的代码已经从 master 分支上消失了,但是我们的提交记录还在 master 分支上。如果此时再从原来的分支提交 merge request,你会发现 「0 file changed」。如果想重新找回之前修改的代码,此时需要对 revert 进行 revert, 原因见这篇文章。步骤同上。

撤销提交

// 撤销最近一次提交,并保留代码,撤销 add 的状态
git reset --mixed HEAD~1

// 撤销最近一次提交,并保留代码,保留 add 的状态
git reset --soft HEAD~1

// 撤销最近一次提交,并删除代码
git reset --hard HEAD~1

修改提交

情形一:重写最近的提交消息
git commit --amend
情形二:修改旧提交或者多个提交的消息,使用 rebase -i(类似于合并提交)
git rebase -i COMMIT_HASH

此列表将类似于以下内容:

pick e499d89 Delete CNAME
pick 0c39034 Better README
pick f7fde4a Change the commit message but push the same commit.

# Rebase 9fdb3bd..f7fde4a onto 9fdb3bd
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

在要更改的每个提交消息的前面,用 reword 替换 pick

pick e499d89 chore: Delete CNAME
reword 0c39034 fix: Better README
reword f7fde4a feat: Change the commit message but push the same commit.

多人协作

如果有一个功能需要多个人协同开发,请使用一个约定的公共分支随时同步你们的代码,以免冲突并且方便联调。且应该像对待 master 一样对待你们的公共分支(即不能直接推送代码至公共分支)。假设有 A,B 两个人一起开发一个新功能叫 achievement

初始化本地分支

在 A 的电脑上:

git checkout master
git pull --rebase
git checkout -b feat/achievement
git push
git checkout -b feat/achievement-a

在 B 的电脑上:

git fetch
git checkout feat/achievement
git checkout -b feat/achievement-b
A 和 B 需要经常更新代码至远端,并合入公共分支

在 A 这边:

// on branch feat/achievement-a
git checkout feat/achievement
git pull --rebase
git checkout feat/achievement-a
git rebase feat/achievement // 可能要解决冲突
git push
// 提交 merge request,将 feat/achievement-a 的提交合入公共分支

在 B 这边:

// on branch feat/achievement-b
git checkout feat/achievement
git pull --rebase
git checkout feat/achievement-b
git rebase feat/achievement //可能要解决冲突
git push
// 提交 merge request,将 feat/achievement-b 的提交合入公共分支

协作开发的时候冲突是十分常见的。不要害怕冲突,应该尽早解决冲突。养成频繁 rebase ,频繁更新公共分支的习惯。

六、注意事项

切换 git 账号的 SSH-Key

实现方法参考这篇文章

在公司电脑上进行这样的操作十分危险,强烈建议不要在公司的电脑上进行自己的 github 开发!!!

七、常见问题

此处整理一些使用 Git 时的常见问题:

未初始化 git

场景还原:fatal: not a git repository (or any of the parent directories): .git

报错原因:未初始化 git(没有 .git 目录)

解决方案:git init

未关联远程分支

场景还原:git push 时报错——fatal: The current branch xxx has no upstream branch.

报错原因:未关联远程分支

解决方案:git push --set-upstream origin xxx

落后远程分支

场景还原:git push 时报错——pdates were rejected because the tip of your current branch is behind its remote counterpart

报错原因:当前分支落后于远程分支

解决方案:需要先拉取远端的更新,再进行 push

git pull --rebase origin YOUR_BRANCH
git push origin YOUR_BRANCH

附:

Git 学习工具:learngitbranching.js.org/?locale=zh_…