Skip to content

一、git原理

  • git只能跟踪文本文件的变化,比如txt文件、程序代码等。而对于图片、视频等二进制文件的变化,就无法知道变化了哪里。
  • git 分支的理解:如下图所示,git分支可以理解为是一个指针以及提交历史组成。

不管git有多少分支,整个git可以理解为是一颗树(包含很多枝杈),不同的分支仅仅是对于不同枝杈的一份快照。

画板

二、git基本操作

1、撤销修改

  • 撤销工作区的修改,用命令<font style="background-color:rgb(250, 250, 250);">git checkout -- file</font>
  • 撤销暂存区的修改:当你不但改乱了工作区某个文件的内容,还添加到了暂存区时,想丢弃修改,分两步,第一步用命令<font style="background-color:rgb(250, 250, 250);">git reset HEAD <file></font>,就回到了场景1,第二步按场景1操作。
  • 丢弃提交:已经提交了不合适的修改到版本库时,想要撤销本次提交,使用命令<font style="background-color:rgb(250, 250, 250);">git reset --hard commit_id</font>
  • 撤销提交git revert HEAD。在当前提交后面,新增一次提交,抵消掉上一次提交导致的所有变化。它不会改变过去的历史,所以是首选方式,没有任何丢失代码的风险。
  • 替换上一次提交:提交以后,发现提交信息写错了,这时可以使git commit --amend -m "",可以修改上一次的提交信息,并把暂存区有变化的文件一并提交。(相当于重新提交)

如何撤销 Git 操作? - 阮一峰的网络日志

2、暂存修改

当我们在某个分支上工作进行到一半时,需要切换分支进行工作,这是我们不想提交代码,可以通过git stash命令,将工作现场“储藏”起来。

<font style="background-color:rgb(250, 250, 250);">git stash list</font>命令可以查看储存的内容:

git
$ git stash list
stash@{0}: WIP on dev: f52c633 add merge

当我们需要恢复工作现场时,使用**<font style="background-color:rgb(250, 250, 250);">git stash pop</font>**,这样在恢复的同时把stash内容也删了。

git
$ git stash pop
On branch dev
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	new file:   hello.py

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.txt

Dropped refs/stash@{0} (5d677e2ee266f39ea296182fb2354265b91b3b2a)

3、复制其他分支的提交修改

比如我们在master分支上修复的bug,想要合并到当前dev分支,可以用**<font style="background-color:rgb(250, 250, 250);">git cherry-pick <commit></font>**命令,把bug提交的修改“复制”到当前分支,避免重复劳动。

4、标签管理

git 标签是指向与某次commit的指针的,在一些重要的代码提交上打上标签,可以帮助我们更好的识别和管理我们代码提交的历史。

  • 创建标签:git tag -a <name>
  • 创建带描述信息的标签:git tag -a <name> -m <msg>
  • 推送一个本地标签:git push origin <tagname>
  • 推送全部未推送过的本地标签:git push origin --tags
  • 删除一个本地标签:git tag -d <tagname>
  • 删除一个远程标签:git push origin :refs/tags/<tagname>

5、 如何合并多个 Commit

我为什么要写这篇文章呢?因为实在太多同学跑来问我「到底怎么合并 commit?」了,每次都重复讲一遍这种做法完全不符合程序猿的风格啊!

那么,就先让我们来看这么一个情况,我们执行以下命令获得四个 Commit:

plain
mkdir test
cd test

git init

echo "0" >> a
git add a
git commit -m "Commit-0"

echo "1" >> a
git add a
git commit -m "Commit-1"

echo "2" >> a
git add a
git commit -m "Commit-2"

echo "3" >> a
git add a
git commit -m "Commit-3"

我们可以看到 Git 的历史长成这样:

plain
* b1b8189 - (HEAD -> master) Commit-3
* 5756e15 - Commit-2
* e7ba81d - Commit-1
* 5d39ff2 - Commit-0

那么问题来了,如何把 e7ba81d(Commit-1)5756e15(Commit-2)b1b8189(Commit-3) 合并到一起,并且只保留 e7ba81d(Commit-1) 的 Git message Commit-1 呢?

这个时候我们就要祭出我们这篇文章的主角—— git rebase -i 了!
这里我不想直接搬出写文档的那套,把所有的选项都介绍完,我们就把这次要用到的讲一下。

-i 实际上就是 --interactive 的简写,在使用 git rebase -i 时,我们要在后面再添加一个参数,这个参数应该是 最新的一个想保留的 Commit。这句话读起来有点坳口,所以这个情况下通常需要举个例子。就我们前面提到的那个例子中,这个「最新的一个想保留的 Commit」就是 5d39ff2(Commit-0),于是我们的命令看起来就长这样:

git rebase -i 5d39ff2

当然,我们也可以通过 HEAD~3 来指定该 Commit:

git rebase -i HEAD~3

按下回车后,我们会进入到这么一个界面:

plain
pick e7ba81d Commit-1
pick 5756e15 Commit-2
pick b1b8189 Commit-3

# Rebase 5d39ff2..b1b8189 onto 5d39ff2 (3 command(s))
#
# 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

前面三行是我们需要操作的三个 Commit,每行最前面的是对该 Commit 操作的 Command。关于每个 Command 具体做什么,下面的注释写得非常清楚。为了完成我们的需求,我们可以关注到这两个命令:

plain
s, squash = use commit, but meld into previous commit
f, fixup = like "squash", but discard this commit's log message

为了让大家看得更明白,我不厌其烦地翻译一下:

  • squash:使用该 Commit,但会被合并到前一个 Commit 当中
  • fixup:就像 squash 那样,但会抛弃这个 Commit 的 Commit message

看样子两个命令都可以完成我们的需求,那么让我们先试一下 squash!由于我们是想把三个 Commit 都合并在一起,并且使 Commit Message 写成 Commit-1,所以我们需要把 5756e15(Commit-2)b1b8189(Commit-3) 前面的 pick 都改为squash,于是它看起来像这样:

plain
pick e7ba81d Commit-1
squash 5756e15 Commit-2
squash b1b8189 Commit-3

当然,因为我很懒,所以通常我会使用它的缩写:

plain
pick e7ba81d Commit-1
s 5756e15 Commit-2
s b1b8189 Commit-3

完成后,使用 :wq 保存并退出。这个时候,我们进入到了下一个界面:

plain
# This is a combination of 3 commits.
# The first commit's message is:
Commit-1

# This is the 2nd commit message:

Commit-2

# This is the 3rd commit message:

Commit-3

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date:      Tue Jan 5 23:27:22 2016 +0800
#
# rebase in progress; onto 5d39ff2
# You are currently editing a commit while rebasing branch 'master' on '5d39ff2'.
#
# Changes to be committed:
#   modified:   a

通过下面的注释,我们可以知道,这里其实就是一个编写 Commit Message 的界面,带 # 的行会被忽略掉,其余的行就会作为我们的新 Commit Message。为了完成我们的需求,我们修改成这样:

plain
Commit-1

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date:      Tue Jan 5 23:27:22 2016 +0800
#
# rebase in progress; onto 5d39ff2
# You are currently editing a commit while rebasing branch 'master' on '5d39ff2'.
#
# Changes to be committed:
#   modified:   a

使用 :wq 后,再看一下我们的 log:

plain
* 2d7b687 - (HEAD -> master) Commit-1
* 5d39ff2 - Commit-0

任务完成!

三、git分支管理

在git中创建和切换分支的代价是非常小的,我们应该多用分支。

1、日常开发中使用分支策略

当我们开发时,应该根据当前任务创建一个新分支进行开发,而不是在dev分支上进行开发,开发完成后再将开发完成的分支合并到dev分支。

比如:我们接到一个新的需求:

git
(dev)$: git checkout -b feature/xxx            # 从dev建立特性分支
(feature/xxx)$: blabla                         # 开发
(feature/xxx)$: git add xxx
(feature/xxx)$: git commit -m 'commit comment'
(dev)$: git merge feature/xxx --no-ff          # 把特性分支合并到dev

2、解决分支合并冲突

当合并代码出现冲突时,必须解决冲突后才能继续提交。

git 使用<font style="background-color:rgb(250, 250, 250);"><<<<<<<</font><font style="background-color:rgb(250, 250, 250);">=======</font><font style="background-color:rgb(250, 250, 250);">>>>>>>></font>标记出不同分支的内容,我们修改好再提交即可。

plain
Git is a distributed version control system.
Git is free software distributed under the GPL.
Git has a mutable index called stage.
Git tracks changes of files.
<<<<<<< HEAD
Creating a new branch is quick & simple.
=======
Creating a new branch is quick AND simple.
>>>>>>> feature1

四、git实践

1、git 分支规范

git
master
dev
feat/xxx
release/xxx
hotfix/xxx

每个仓库有且仅有以下的 分支:

Branch: master 、 dev 、 feat 、 release 、 hotfix

其中:

  • master 受保护,不存放源代码,不直接提交代码,所有的 上线文件 需要推送到此分支。
  • dev 受保护,主分支,不能直接提交代码,在这个分支只能增加从 feat 合并 过来的 commit。紧急bug除外,紧急bug处理方式看后文bugfix。
  • feat 分支需要从 dev 切出,然后开发完成后,提交合并请求到 release 分支进行提测。
  • release 分支需要从 dev 切出,用来对项目中各个需求进行合并提测,改 bug 等均在此分支进行,测试成功后,提交合并请求到 dev。
  • hotfix 分支需要 dev 切出,待 bug 修复完成后,提交合并请求到 dev。

2、git 标签提交规范

  • 对应每个发布版本的源代码 tag。tag版本号与需求版本一致,从dev分支打tag,命名 release_版本号_日期,如:release_1.0_20200426
  • 对应每个发布版本的上线文件 tag。tag版本号与需求版本一致,命名 dist_版本号_日期,如:dist_1.0_20200426

3、提交规范

git
  - type
    - 用于说明 `commit` 的类别,只允许使用下面10个标识。
      - feat:新功能(feature)
      - fix:修补bug
      - docs:文档(documentation)
      - style: 格式(不影响代码运行的变动)
      - refactor:重构(即不是新增功能,也不是修改bug的代码变动)
      - perf:性能优化
      - test:增加测试
      - chore:构建过程或辅助工具的变动
      - revert:回退
      - build:打包
  - scope(可选)
    - 用于说明 `commit` 影响的范围,比如Button组件、store、首页、路由等等,视项目不同而不同。
  - subject(可选))
    - 是 `commit` 目的的简短描述,不超过50个字符。
      - 以动词开头,使用第一人称现在时,比如change,而不是changed或changes
      - 第一个字母小写
      - 结尾不加句号(.)

4、发行版提交(release)

在Github、码云等源码托管商,提供了release的概念,可以提交我们打包后的项目文件,在标签tag的基础上我们可以添加编译好的二进制文件等,如等给特定的版本提供更多的信息,方便用户,也方便后期查找特定版本的程序。

参考: