一、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 ""
,可以修改上一次的提交信息,并把暂存区有变化的文件一并提交。(相当于重新提交)
2、暂存修改
当我们在某个分支上工作进行到一半时,需要切换分支进行工作,这是我们不想提交代码,可以通过git stash
命令,将工作现场“储藏”起来。
用<font style="background-color:rgb(250, 250, 250);">git stash list</font>
命令可以查看储存的内容:
$ 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 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:
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 的历史长成这样:
* 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
按下回车后,我们会进入到这么一个界面:
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 具体做什么,下面的注释写得非常清楚。为了完成我们的需求,我们可以关注到这两个命令:
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
,于是它看起来像这样:
pick e7ba81d Commit-1
squash 5756e15 Commit-2
squash b1b8189 Commit-3
当然,因为我很懒,所以通常我会使用它的缩写:
pick e7ba81d Commit-1
s 5756e15 Commit-2
s b1b8189 Commit-3
完成后,使用 :wq
保存并退出。这个时候,我们进入到了下一个界面:
# 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。为了完成我们的需求,我们修改成这样:
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:
* 2d7b687 - (HEAD -> master) Commit-1
* 5d39ff2 - Commit-0
任务完成!
三、git分支管理
在git中创建和切换分支的代价是非常小的,我们应该多用分支。
1、日常开发中使用分支策略
当我们开发时,应该根据当前任务创建一个新分支进行开发,而不是在dev
分支上进行开发,开发完成后再将开发完成的分支合并到dev
分支。
比如:我们接到一个新的需求:
(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>
标记出不同分支的内容,我们修改好再提交即可。
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 分支规范
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、提交规范
- 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
的基础上我们可以添加编译好的二进制文件等,如、等给特定的版本提供更多的信息,方便用户,也方便后期查找特定版本的程序。