实例:Git在版本控制中的基本使用

git谁都用过,但简单的pull, add, commit, push并不能应对稍微复杂一点的项目,多人开发、迭代开发等需要掌握更多的git技能

参考链接
Git Book:有时间建议重点学习此书
Git教程 - 易百教程
Git 教程 - 廖雪峰

一、基本概念

1. 分支

1.1 简单介绍

分支可以说是Git的“必杀技”了,熟练得运用分支可以让你轻松管理版本库、老版本bug修复与现版本开发并行、等等。

commit可以视作一个提交对象,其中包含一个指向上次提交对象(父对象)的指针。
Git 的分支,其实本质上仅仅是指向提交对象的可变指针, 分支会在每次提交时自动向前移动。
HEAD 指向当前所在的分支。

“Git 的 master 分支并不是一个特殊分支。 它就跟其它分支完全没有区别。 之所以几乎每一个仓库都有 master 分支,是因为 git init 命令默认创建它,并且大多数人都懒得去改动它。”

借用一张Git Book的图,展示提交、分支及其关系:
实例:Git在版本控制中的基本使用_第1张图片

1.2 基本命令

查看分支
git branch
git branch -a

创建本地分支:
git branch my-branch

  • 切换分支:
    git checkout my-branch

  • 创建并切换到本地分支:
    git checkout -b my-branch

创建远程分支:

两种:

  • 创建本地分支、推送到远程:
    git checkout -b my-branch
    git push origin my-branch:my-branch
  • 远程先开好分支然后拉到本地:
    git checkout -b my-branch origin/my-branch

合并分支到当前分支
git merge

删除分支
git branch -d

删除远程分支(不会删除本地分支)
git branch -r -d origin/
git push origin :

2. 标签

2.1 简单介绍

标签用来标记版本库,可通过标签直接找到指定的版本。相对于commit,标签是指向某个commit的指针,通过标签名查找commit要比直接查找commit方便得多。

2.2 基本命令

查看标签:
git tag

创建标签:
git tag v0.1

创建附注标签:
git tag -a v0.1 -m "my version 0.1"

推送标签:
git push origin v0.1

删除本地标签:
git tag -d v0.1

删除远程标签:
git push origin :v0.1

3. 变基

3.1 基本命令

git rebase命令 - 易百教程

语法

git rebase [-i | --interactive] [options] [--exec <cmd>] [--onto <newbase>]
    [<upstream> [<branch>]]
git rebase [-i | --interactive] [options] [--exec <cmd>] [--onto <newbase>]
    --root [<branch>]
git rebase --continue | --skip | --abort | --quit | --edit-todo

--continue: 在rebase的过程中,也许会出现冲突(conflict)。在这种情况,Git会停止rebase并会让你去解决冲突。在解决完冲突后,用 git add 命令去更新这些内容的索引(index),然后,无需执行 git commit,只要执行 git rebase --continue 这样git会继续应用(apply)余下的补丁。
--edit-todo: 重新查看和编辑。
--abort: 在任何时候,可以用 --abort 参数来终止rebase的操作,分支会回到rebase开始前的状态。

3.2 应用

  • rebase vs merge

“在 Git 中整合来自不同分支的修改主要有两种方法:merge 以及 rebase。” —— Git - 变基

示例:将experiment分支整合到master分支
实例:Git在版本控制中的基本使用_第2张图片

方法一:使用 merge

git checkout master
git merge experiment
实例:Git在版本控制中的基本使用_第3张图片
方法二:使用 rebase

变基rebase是有别于merge的一种分支整合方式:提取在分支中引入的补丁和修改,然后在目标分支的基础上应用一次

检出 experiment 分支,然后将它变基到 master 分支上:
git checkout experiment
git rebase master
实例:Git在版本控制中的基本使用_第4张图片
接着,回到 master 分支,进行一次快进合并:
git checkout master
git merge experiment
实例:Git在版本控制中的基本使用_第5张图片

  • 对比

“无论是通过变基,还是通过三方合并,整合的最终结果所指向的快照始终是一样的,只不过提交历史不同罢了。 变基是将一系列提交按照原有次序依次应用到另一分支上,而合并是把最终结果合在一起。

这两种整合方法的最终结果没有任何区别,但是变基使得提交历史更加整洁。 你在查看一个经过变基的分支的历史记录时会发现,尽管实际的开发工作是并行的, 但它们看上去就像是串行的一样,提交历史是一条直线没有分叉。

一般我们这样做的目的是为了确保在向远程分支推送时能保持提交历史的整洁——例如向某个其他人维护的项目贡献代码时。 在这种情况下,你首先在自己的分支里进行开发,当开发完成时你需要先将你的代码变基到 origin/master 上,然后再向主项目提交修改。 这样的话,该项目的维护者就不再需要进行整合工作,只需要快进合并便可。”

  • 拷贝其它分支的部分改动

示例:取出 client 分支,找出它从 server 分支分歧之后的补丁, 然后把这些补丁在 master 分支上重放一遍,让 client 看起来像直接基于 master 修改一样

git rebase --onto master server client
  • git cherry-pick

此需求,git cherry-pick 也能实现,它的作用就是将指定的提交(commit)应用于其他分支

git cherry-pick 教程 - 阮一峰的网络日志

转移一个提交:git cherry-pick
转移多个提交:git cherry-pick
转移从 A 到 B 的所有提交:git cherry-pick A..B
转移从 A(不包含A) 到 B 的所有提交:git cherry-pick A^..B

虽然都能实现,但从理解难易程度以及使用习惯上来讲,个人更常用的是后者


二、常用场景

1. 临时任务

开发中会出现一种情况,手头上的功能还没完成,就有计划外的任务出现,并且优先级更高。例如:bug修复、更紧急的功能等等

我们希望实现的一般是:暂停当前开发任务 --> 处理临时任务 --> 然后回来继续开发任务。若该任务对当前开发有影响,处理之。

这里只讨论如何暂停、继续当前开发任务

  • git stash

简单的方法写在前面

使用git stash,更规范的方法来存储本地更改。

git stash 用法总结和注意点

注意
git stash 只能存储本地未提交的更改
恢复存储的代码时,可能会产生冲突,需手动处理冲突

  • 分支

本地存储是个非常便利的办法,但如果两个任务需要并行开发,那就无法使用本地存储了。

简单高效可靠的办法Git早就告诉我们了,运用分支。

“Git切换分支时,如果本地有未提交的代码,这部分改动会被移动到切换后分支上。”

基于这个特性,如果起点刚好为上一次提交,可以直接创建并切换到本地分支:git checkout -b <分支名>

在指定commit创建分支:git branch <分支名>
在指定tag创建分支:git branch <分支名>

  • 创建远程分支(仅暂存本地代码时,无需创建远程分支):

git checkout -b my-branch v0.1.23
git push origin my-branch:my-branch

  • 切换到临时分支

git checkout my-branch

  • 更改代码、提交

git add .
git commit -m "commit on my-branch"
git push origin my-branch

  • 切换回开发分支

git checkout dev

  • 合并临时分支

git merge my-branch

  • 如果有冲突,解决冲突、提交更改
  • 任务完成后,删除临时分支

删除远程临时分支
git branch -r -d origin/m-branch
git push origin :m-branch
删除本地临时分支
git branch -d m-branch

注意
临时分支随着任务结束,要及时删除。若推送到了远程代码仓库,自己不删,别人不确认无用后,也不敢删。
长期存在的分支都有各自的用处,不及时清理临时分支的话,会给版本控制带来不必要的麻烦。

2. 撤销操作 reset

参考:

git代码提交了怎么撤回提交
git 删除/回退本地提交

  • 撤销本地暂存的文件(未git add)

git checkout -- demo.js

这样,demo.js的更改会被撤销(无法再找回

  • 撤销已暂存未提交的文件

git reset HEAD demo.js

这样,demo.js的暂存会回到工作区,文件内容不会被改动

  • 已提交

参数 --hard --soft --mixed
git reset --mixed [提交id]
git reset --mixed HEAD~1
git reset --mixed commit_id

3. 修改commit

GIT 修改commit message

4. 合并多条commit

Git 合并多个 commit,保持历史简洁
git 合并多个commit

git rebase -i [commitid] 会合并此次提交之后所有的提交为一个提交, 注意此次提交不会包含在内

说明:
-i(–interactive):弹出交互式的界面进行编辑合并
[commitid]:要合并多个版本之前的版本号,注意:[commitid] 本身不参与合并

指令解释(交互编辑时使用):
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
d: drop = remove commit

注意:执行合并命令后,并不会直接合并,会弹出交互式的界面,需要手动处理,编辑合并信息:

vim相关命令:
i 进入编辑模式,可以移动光标,编辑
Esc 退出编辑模式
输入 :wq 保存并退出(需先退出编辑模式)

例如:

$ git rebase -i 02a213d
pick 08ad2fb message2
pick a616604 message3
pick a1626f5 message4

# 后面还有注释信息,不用编辑

编辑后:

$ git rebase -i 02a213d
pick 08ad2fb 合并提交
f a616604 message3
f a1626f5 message4

# 后面还有注释信息,不用编辑

在 git rebase 过程中,可能会存在冲突,需要解决冲突
解决冲突之后,本地提交: git add .
继续rebase: git rebase --continue
在任何时候,可以用 --abort 参数来终止rebase的操作,分支会回到rebase开始前的状态(git rebase --abort


三、实践

以下是个简单的实践,展示分支的基本使用

1. Git上新建一个项目,克隆到本地

git clone https://github.com/zymbth/demo_git_usage.git

2. 添加文件text.txt,push到git上

git add .
git commit -m "first commit"
git push

实例:Git在版本控制中的基本使用_第6张图片

3. 创建分支

git checkout -b my-branch
git push origin my-branch:my-branch

4. 切换到分支my-branch上,更改文件text.txt

git add .
git commit -m "commit on my-branch"
git push origin my-branch

实例:Git在版本控制中的基本使用_第7张图片

5. 合并分支

git checkout master
切换到master后,文件回到了未在my-branch上更改前的版本
实例:Git在版本控制中的基本使用_第8张图片

合并分支
git merge my-branch
合并后,记得提交合并后的代码。此时可以根据需要选择删除my-branch(见3.6)。

下图中可以看到,远程代码库中的分支my-branch已经被删除,而在分支my-branch上提交的commit“commit on my-branch”依然在合并后的master分支中(分支是指针)。

实例:Git在版本控制中的基本使用_第9张图片

实例:Git在版本控制中的基本使用_第10张图片

6. 删除无用分支

git branch -d my-branch
git push origin :my-branch

实例:Git在版本控制中的基本使用_第11张图片

7. 合并冲突

接着之前的例子,删除my-branch后,在master上更改提交了几次代码。模拟版本v1.0,v1.1,v2.0,v2.1
实例:Git在版本控制中的基本使用_第12张图片
2.0为开发版本,线上版本(v1.1)出现了一个bug。

这时,master分支指向最新的v2.1 commit,如果在master分支上修改,那么线上版本需要发什么,v2.2?

v2.0, v2.1的代码都可能没有经过测试,或者需要后续开发才是一个完整的可以发布的功能。

实际上,当前的需求就是修复v1.1前的代码,与之后的版本没有关系。所以,我们可以在线上版本的最后一次commit处创建一个分支。
git checkout commitId -b m-branch
实例:Git在版本控制中的基本使用_第13张图片
git log查看提交记录,git checkout commitId切换到指定commit的代码库。

通过commitId创建分支,不是那么方便,可以借助标签来实现或提前创建分支。

演示一个简单的合并冲突,重复操作不再演示:

  • 在v1.1处创建分支m-branch
  • 切换到分支m-branch
  • 修改代码并提交到分支m-branch

实例:Git在版本控制中的基本使用_第14张图片

如图,修改了v1.1的内容,并增加了v1.2

  • 合并分支

确认修改无误后,就可以将代码合并到master分支了(不将该改动合并到master,那之后发 v2.x 版本,该bug仍然在)。
实例:Git在版本控制中的基本使用_第15张图片
提示合并冲突,自动合并失败,需要手动解决冲突,然后再commit合并后的代码。

  • 解决冲突

查看下冲突代码吧
实例:Git在版本控制中的基本使用_第16张图片
就这几行“代码”,就已经看得一头雾水了,可想而知在真实项目中的状况。
“<<<<<<< HEAD”, “========”, ">>>>>>> m-branch"的解释参照Git Book - 分支的新建与合并:
实例:Git在版本控制中的基本使用_第17张图片
可以把这些标记划分成上下两半部分,分别表示当前分支和待合并分支的冲突之处。简单得可能只需要二选一,但也可能需要自行逐行对比合并。
实例:Git在版本控制中的基本使用_第18张图片
上半部分是master分支内容,下半部分是m-branch分支。
冲突在于,m-branch更改了v1.1的代码,且在v2.0前增加了v1.2的代码。
以上两处的处理:替换,插入。

处理后的内容:
实例:Git在版本控制中的基本使用_第19张图片

  • commit并提交合并后的代码到远程代码库

  • 分支图

整个过程的分支图如下:
实例:Git在版本控制中的基本使用_第20张图片

8. 分支的反复使用与合并

合并分支后,该分支仍然存在,只是指向了该分支的最后一次提交。

后续对线上版本(v1.x)的更改可以使用此分支。如需从v1.1处或特定位置更改代码,那可以从该处重新创建分支(此例中,m-branch已经指向了名为“v1.2”的commit)。

m-branch继续使用:
实例:Git在版本控制中的基本使用_第21张图片
再次合并:
实例:Git在版本控制中的基本使用_第22张图片

9. 注意

  • 删除本地tag/branch:
    git tag -d m-tag
    git branch -d m-branch

  • 推送代码到tag/branch:
    git push origin m-tag
    git push origin m-branch

  • 删除远程tag/branch:
    git push origin :m-tag
    git push origin :m-branch
    省略本地标签名/分支名,则表示删除指定的远程标签/分支,因为这等同于推送一个空的本地标签/分支到远程标签/分支,等同于 git push origin –delete m-name

  • 切换分支前,需要检查当前分支是否有未提交的改动


四、最后

实操比什么教程都有用,不要懒得动手。

你可能感兴趣的:(Git,git)