番外篇 Git 的原理与使用

发布于:2024-12-21 ⋅ 阅读:(10) ⋅ 点赞:(0)

PS:本篇是个长篇,但是阅读完,可以基本了解 Git 在实际开发中的绝大部分常用操作。

前言:什么是Git

我们在日常工作 / 学习时,对于某些文档 / 代码,可能会存在多个版本需要维护,但是随着版本的增多,我们很难记得每个版本都修改了什么,难以维护好这每一个版本。

这时候就需要版本控制器来更便于我们管理这些不同版本的文件;所谓版本控制器,就是一个可以记录工程的每一次改动与版本迭代的管理系统,同时也便于多人协同作业

当前最主流的版本控制器,就是 Git。  

Git 可以控制电脑上所有格式的文件,对于开发人员来说,Git 最主要的就是可以帮助我们管理软件开发项目中的源代码文件。

(PS:所有的版本控制系统,都只能跟踪文本文件的改动,即版本控制系统可以告诉我们文本每次的改动;但对于类似图片、视频这些二进制文件,虽然也能管理,但只能知道大小的改变,具体改动了什么,不得而知) 

一、Git 的基本操作

1、创建 Git 本地仓库

只有在仓库中的文件,才能被 Git 进行追踪管理,因此我们在对文件进行版本控制之前,必须创建一个仓库出来。(为了便于管理,这个本地仓库也应该放置在与需要版本控制的文件的目录下)

创建 Git 本地仓库的命令是:

git init

这个命令的作用是在当前目录下创建一个 Git 本地仓库,因此这个命令建议放在需要版本控制的文件的目录下。 

调用 git init 命令后,当前目录下就会多出一个 ,git 的隐藏目录,这个隐藏目录是 Git 用来跟踪管理仓库的,不要手动修改这个目录中的文件,否则会造成错误!  

多出一个.git 隐藏目录

2、配置 Git 本地仓库

在创建完本地仓库后,首先要做的事情就是配置用户名邮箱地址这两个配置项,以防出错

配置用户名和邮箱地址的命令是:

# 配置用户名
git config [--global] user.name "你的用户名"

# 配置邮箱地址
git config [--global] user.email "你的邮箱地址"

配置完毕,也可以删除对应的配置,删除配置的命令是:

# 删除用户名配置
git config [--global] --unset user.name

# 删除邮箱配置
git config [--global] --unset user.email

其中 --global 是一个可选选项。如果使用了该选项,这台机器上的所有 git 仓库都会使用该配置;如果不希望所有的 git 仓库都用这个配置,就不要添加 --global 选项。

(值得注意的是,如果某一配置项添加了 --global 成为全局配置项,在删除配置的时候也必须添加 --global 指令方可删除)

(PS:执行配置命令的时候,也必须要在 .git 隐藏目录所在的目录下执行) 

配置完后,可以查看配置,查看配置的命令为:

git config -l

3、Git原理:认识工作区、暂存区、版本库

Git 各区域关系图

从图中我们可以看到,Git 内部主要细分为工作区版本库,其中版本库内部又有许多细分。

3.1  工作区

工作区其实就是我们创建的 .git 隐藏目录所在的那个目录,即我们存放欲进行版本控制的代码 / 文件的所在目录。

3.2  版本库

所谓版本库其实就是那个 .git 隐藏目录,因此又称作仓库,其不属于工作区,是真正意义上的 git 仓库。版本库中的所有文件都可以被 git 管理起来;每个文件的修改与删除,git 也都可以跟踪,以便在任何时刻都能追踪 / 还原。

Git 的版本库内部存放着很多东西,主要有:暂存区(即图中的 stage,又称 index)、HEAD指针唯一 master 分支、还有图中未标明的 objects 对象库

暂存区:

又称索引,一般存放在 .git 目录下的 index 文件中,是版本库中最重要的组成部分在我们对工作区中修改 / 新增 /删除的文件执行了 git add 命令后,工作区中所有修改了的内容都会被添加进版本库的暂存区中,暂存区目录树的文件索引会被更新。

(PS:新建的 .git 本地仓库中是没有暂存区的,必须在第一次 git add 之后,本地仓库中才会出现暂存区目录)

objects 对象库:

objects 对象库中存放着大量的 git 对象。在执行了 git add 命令后,工作区中修改的内容会被写入对象库中的一个新的 git 对象中,进而实现所有版本的控制与维护。

master 分支与 HEAD指针:

在创建 Git 版本库时,git 会为我们自动创建一个唯一的 master 分支,以及指向 master 分支的一个 HEAD指针。这个后面会详述。

当执行提交操作 git commit 后,master 分支会做响应的更新,这时暂存区的目录树才会被真正写入 git 仓库中进行维护。

 从上述描述中可知,往工作区中添加文件,其实并没有真正添加进 git 仓库,必须通过 git add 命令添加进仓库的暂存区中,再使用 git commit 命令真正把文件添加进 git 仓库进行管理! 

 (PS:我们不能去手动修改 .git 仓库目录,只能通过 git add + git commit 进行添加,因为内部需要维护索引结构,手动修改极易导致整个仓库的损坏!)

4、添加文件进 Git 仓库

添加文件进 Git 仓库分为两步:

1、使用 git add 命令把修改后的文件添加进版本库的暂存区:

# 添加一个/多个文件到暂存区(git add 后面可以跟一个/多个文件)
git add 文件名1 文件名2 ……

# 添加某个子目录到暂存区
git add 子目录名

# 添加当前目录下的所有文件到暂存区
git add .

2、再使用 git commit 命令把暂存区的内容真正添加进本地仓库中:

# 把暂存区中的所有内容提交进本地仓库
git commit -m "message"

# 把暂存区中的指定文件提交进本地仓库(git commit 后面可以一次性指定一个/多个文件)
git commit 文件名1 文件名2 …… -m "message"

值得注意的是,这个 message 是用来记录每一次提交的提交细节的(比如版本修改了什么等等),绝对不能省略,并且要好好描述,这是给开发人员看的。

我们还可以多次 git add 不同的文件,然后只需要 git commit 一次即可提交所有的文件(因为需要提交的文件被全部放在了暂存区中,git commit -m "message" 是把暂存区中的所有文件提交到本地仓库)

多次 git add,一次 commit 即可

 在提交至本地仓库后,我们还可以通过 git log 命令来查看历史提交记录

可以显示谁提交的,提交时间,提交细节

值得注意的是,第一行会有一长串十六进制数字,这是 commit id,每次提交都会有一个独特的 commit id,是一个经过哈希计算后的独特十六进制数字。 

如果嫌输出信息太多,我们可以加上 --pretty=oneline 参数,把提交记录一行打印,但会省略掉一些细节:

日志信息打印在了一行上

5、如何通过 Git 查看哪些文件发生了修改

Git 相较于其它版本控制器优秀的地方,就是 Git 跟踪并管理的是修改,而非文件,只有对文件发生了修改,Git 才会跟踪并管理到。

当我们对文件进行修改后,可以通过 git status 命令查看在上次提交后,工作区是否对文件进行了修改。

(PS:只能查看哪些文件被修改了,但是修改的内容不得而知)

为file1添加一段内容,可以通过 git status 命令得知 file1 被修改了,但是没有提交

使用 git status 命令,只能知道文件被修改,想知道哪些文件被修改,就要使用 git diff 命令

# 以 Unix 通用的 diff 格式显示暂存区和工作区文件的差异
git diff 文件名


# 以 Unix 通用的 diff 格式查看版本库文件和工作区文件的区别
git diff HEAD -- 文件名

5.1  git diff 命令示例

查看 file1 的修改情况

让我们逐行解析这段 `diff` 输出:

第一行:`diff --git a/file1 b/file1`

diff --git :这是 Git 特有的标记,表示接下来的差异是由 Git 生成的。
a/file1 和 b/file1 :这里的 `a/` 和 `b/` 分别表示旧版本和新版本的文件路径。`file1` 是文件名,表示这个差异是针对 `file1` 文件的。

第二行:`index e69de29..2e3c1c7 100644`

index:表示文件的 Git 对象 ID(SHA-1 校验和),用于唯一标识文件的内容。
e69de29:这是旧版本文件的校验和(在 `a/file1` 中)。
2e3c1c7:这是新版本文件的校验和(在 `b/file1` 中)。
100644:这是文件的权限模式,表示这是一个普通文件(非执行)。`100644` 是八进制表示的权限,相当于 `rw-r--r--`。

第三行:--- a/file1

---:表示旧版本文件的开头。`a/file1` 表示这是旧版本的 `file1`。

第四行:`+++ b/file1`

+++:表示新版本文件的开头。`b/file1` 表示这是新版本的 `file1`。

第五行:`@@ -0,0 +1 @@`

@@:表示差异块的开始。
0,0 :表示旧版本文件中从第 0 行开始的 0 行内容(即旧文件为空)。
+1:表示新版本文件中从第 1 行开始的 1 行内容被添加了。
@@:整个行表示这是一个差异块的上下文信息,帮助定位差异的具体位置。

第六行:`+hello world:`

- **`+`**:表示这一行是在新版本文件中添加的。
- **`hello world:`**:这是实际添加的内容,即新版本文件的第一行是 `hello world:`。

git add + git commit 后,再 git status 就发现没有文件需要提交了
此时再次 git diff,暂存区和工作区、版本库之间也没有差异了

6、.git 目录结构介绍

我们给出 .git 目录的树状结构

tree .git/
.git/
├── branches
├── COMMIT_EDITMSG
├── config
├── description
├── HEAD
├── hooks
│   ├── applypatch-msg.sample
│   ├── commit-msg.sample
│   ├── post-update.sample
│   ├── pre-applypatch.sample
│   ├── pre-commit.sample
│   ├── prepare-commit-msg.sample
│   ├── pre-push.sample
│   ├── pre-rebase.sample
│   └── update.sample
├── index
├── info
│   └── exclude
├── logs
│   ├── HEAD
│   └── refs
│       └── heads
│           └── master
├── objects
│   ├── 48
│   │   └── 03c48e7fe98a55687af5c3270265a46ab69ab7
│   ├── 74
│   │   └── 5172e8950af7753d61460930102e01a5685e62
│   ├── af
│   │   └── b1be315f37830ae2f2ea32e9f11b4437a09df3
│   ├── c7
│   │   └── fba63dbbd842e27890a361ec6ccb68645e539b
│   ├── e6
│   │   └── 9de29bb2d1d6434b8b29ae775ad8c2e48c5391
│   ├── info
│   └── pack
└── refs
    ├── heads
    │   └── master
    └── tags

17 directories, 23 files

其中:

index:就是暂存区,git add 后,修改的内容都添加在这里。

HEAD:默认指向 master 分支的指针

可以发现,HEAD 指针指向 master 分支
master 内部存放的则是最后一次提交的文件的commit id,前两位是文件夹名

 知道 commit id 后,我们便可以通过 git cat-file -p 命令来查看版本库对象的内容:

第一行存放修改的内容,第二行是上次提交的 commit id

7、如何通过 Git 回退历史版本(重要)

版本控制器重要的能力就是能够管理文件的历史版本,可以实现历史版本的回退。

7.1 Git 中的回退指令:git reset

Git 中的回退指令叫做 git reset值得注意的是,Git 中的版本回退,本质上是把版本库中的内容进行了回退,至于工作区和暂存区中的内容是否回退,要根据 git reset 的命令参数决定:

# git reset 版本回退命令的语法格式
git reset --mixed/--soft/--hard HEAD + [文件名]

 我们介绍一下各个参数:

__mixed:

默认选项,使用该参数,会把版本库暂存区中的内容回退至指定版本,工作区文件保持不变(由于 --mixed 是默认选项,因此如果想使用 --mixed,可以不带参数)

--soft:

只把版本库回退到指定版本,工作区和暂存区的内容都保持不变。

--hard:

把版本库、暂存区、工作区全都退回至指定版本 (使用的时候一定要谨慎,如果工作区中还有代码,以 --hard 参数进行版本回退,工作区中未提交的代码都会消失!)

HEAD:

可以直接写 commit id,表示指定退回的版本(通常与 git log 命令搭配使用,通过 git log 查询历史提交记录来找到各个版本的 commit id) 

直接写 HEAD,表示回退到当前版本;HEAD^,表示回退到上一版本;HEAD^^,表示回退到上上个版本,以此类推。(也可以用数字来表示,HEAD~0  表示当前版本,HEAD~1 表示上一版本,HEAD~2表示上上个版本,以此类推)

文件名:

文件名可以加可以不加,如果加了文件名,就只是把这个文件回退到指定版本,而不是整个项目。

如果在回退之后,突然后悔了怎么办?

只要有回退前版本的 commit id,就可以回退到回退前的版本

那么这个回退前版本应该怎么找呢?

1、从终端往上找找之前的记录,看看是否能找到。

2、git reflog 命令,这个命令记录了本地的每一次命令,可以通过 git reflog 来补救。

git reflog 中,commit id 并没有完整记录下来,但是版本回退支持用这个部分 commit id 进行回退

7.2  Git 回退指令执行很快,其原理是什么?

所谓版本回退,其实就是HEAD指针指向之前的 commit id 的 master 分支

8、如何通过 Git 撤销修改,恢复至之前版本 

场景1:只对工作区修改了代码,没有 git add 和 git commit

1、可以直接删除修改的代码 / 通过 git diff 对比删除,但是在遇到代码量较大的情况下,其实很麻烦。

2、Git 为我们提供了一种更好的方式,可以让工作区的文件恢复到最近一次 add / commit 时的状态

# 让文件恢复到最近一次 add / commit 的状态
git checkout -- 文件名

 值得注意的是,-- 不可省略,如果省略这个命令就变成别的意思了

场景2:已经把代码 add 到暂存区中了,但是还没有 commit 到版本库中

这个时候,我们就可以使用前面所说的 git reset --mixed HEAD + [文件名]进行回退了。 

场景3:已经把代码 add 到暂存区中,也 commit 到版本库中了

这个时候,能回退的前提必须是代码没有 push 到远端仓库才能回退,因为日后控制代码大部分都是在远端仓库上,回退的目的就是为了不影响远端仓库,因此只能在本地仓库上回退。

回退同样是用 git reset 进行回退,git reset -- head HEAD + [ 文件名 ]

9、删除文件

前言:删除也是修改操作。

如果我们直接使用 rm 命令删除文件,实际上仅删除了工作区文件,版本库中的文件并没有删除。因此,应该以 rm 命令删除工作区文件搭配 git rm 命令把版本库和暂存区中的文件都删除,最后不要忘记 git commit,因为删除也是修改。

二、Git 基本操作命令总结

1、创建本地 git 仓库

git init

2、配置 git 的用户名和邮箱两大配置项

git config [--global] user.name "用户名"

git config [--global] user.email "邮箱地址"

3、删除 git 的用户名和邮箱两大配置项

git config [--global] --unset user.name

git config [--global] --unset user.email

4、查看 Git 仓库配置

git config -l

5、把工作区修改文件提交进暂存区 + 把暂存区文件真正提交至本地仓库(通常搭配使用,重要)

# 把工作区修改文件提交进暂存区
# 1、添加一个/多个修改文件进暂存区
git add 文件名1 文件名2……

# 2、添加某个子目录进暂存区
git add 目录名

# 3、添加当前目录下的所有修改文件进暂存区
git add .


# 把暂存区文件真正提交至本地仓库
# 1、提交暂存区中的全部内容提交进本地仓库
git commit -m "提交细节"

# 2、提交暂存区中的指定文件至本地仓库
git commit 文件名1 文件名2…… -m "提交细节"

 6、查看历史提交记录

git log

7、查看版本库中某个对象的内容(须知道其 commit id)

# -p 是更优雅的打印,也可以不写
git cat-file -p 对象的commit_id

8、查看仓库状态

# 只能查看哪些文件修改了,但是修改的具体内容不得而知
# 可以查看上次提交之后,是否对文件还进行了修改
git status

9、查看某个文件在暂存区和工作区的区别

git diff 文件名

10、查看某个文件版本库和工作区的区别

git diff HEAD -- 文件名

 11、版本回退(重要)

git reset --soft/--mixed/--hard HEAD [文件名]
# 文件名可以不写,就是整个工程回退
# HEAD可以写commit id,也可以通过 HEAD^ / HEAD~0等等不同版本回退
# --soft:默认选项,回退版本库和暂存区的内容
# --mixed:只回退版本库的内容
# --hard:版本库、暂存区、工作区的内容全部回退

12、把工作区的文件回到最近一次 add / commit 的状态

git checkout -- 文件名

13、把文件从暂存区和工作区中删除

git rm 文件名

三、Git 的分支管理

1、什么是分支

在之前的 Git 学习中,我们知道了,每次的 Git 提交,Git 都会把它们穿成一条提交时间线,而这一条提交时间线可以称作一个分支。

之前,我们只有一条提交时间线,也就是只有一条分支,这条分支叫做 master 分支(主分支) 

master 分支和 HEAD 指针

而 HEAD 指针指向的就是当前正在工作的 master  分支(HEAD 指针指向的是当前正在工作的分支,可以是 master 分支,也可以是其它) 

每次在 master 分支中提交,master 分支都会向前移动一步,这样随着不断提交,master 的分支就会越来越长。

HEAD 指针指向此时正在工作的 master 分支;master 分支指向最近一次提交的 commit  id

2、如何创建一个新的分支

Git 支持我们创建和查看分支,对应的命令是:

# 查看分支信息
git branch

# 基于当前分支新的一个分支
git branch 分支名

创建 dev1 分支后,查看分支信息,* 号所在行的分支是当前工作分支

新建的分支,是基于当前的分支创建的,所以一开始指向的 commit id 与当前分支一致

dev 分支基于 master 分支创建,master 分支和  dev 分支指向同一个 commit id

 3、如何切换分支

# 切换到对应分支 
git checkout 分支名

可以发现, 之前把工作区的文件回到最近一次 add / commit 的状态是 git checkout  -- 文件名,而 git checkout 分支名,就是切换分支。

切换分支后,HEAD 指针也指向了切换到的分支

 我们在 dev 分支上为 file 文件写一段内容,然后切换回 master 主分支:

可以发现,dev 分支上提交的代码,并不会在 master 分支中展现: 

不同的分支,指向的都是自己这个分支最近的那次提交

4、合并分支

在前文中,我们发现, 不同分支的提交互相是看不到的。

为了让 dev 分支上提交的信息可以让 master 分支看见,我们需要把 dev 分支合并到 master 分支上。(要把 dev 分支合并到 master 分支上,就要切换到 master 分支上进行合并)

# 合并某个分支到当前分支上
git merge 分支名
合并分支后,就能看见 dev 分支上的修改了

我们可以发现,merge 之后,有一 Fast-forward 字段,这是一种合并模式,叫做“快进模式”,快进模式下的合并,是直接把 master 分支指向 dev 分支指向的 commit id,合并速度很快。

(还有其它的合并模式,后文会介绍) 

Fast-forward 快速提交模式

 5、删除分支

合并分支后,dev 分支就没什么用了,可以删除了:(注意,删除某一分支,不能在当前分支下)

git branch -d 分支名
dev 分支成功删除

 

由于创建、合并与删除分支比较简单,所以推荐使用分支完成某个任务,然后合并、删除分支,这样和在 master 分支上工作是一样的,但是过程比较安全。 

6、合并冲突

在实际合并的时候,  其实并不是想合并成功就能合并成功的,因为有时候会出现代码冲突问题。

我们创建一个 dev1 分支,在 dev1 分支和 master 分支的同一行写下不同的代码:

(注意:如果在当前分支上做了修改但还没有提交,Git 会在你切换分支时保留这些未提交的更改。这意味着你可以在不同分支之间看到相同的未提交文件,除非这些文件在目标分支上有冲突)

# -b 选项可以完成创建并切换至 dev 分支的效果
git checkout -b dev
master 冲突和 dev 冲突在代码上有冲突,合并失败

 这时,我们用 cat file 查看一下文件:

Git 会用 <<<<<<< ======= >>>>>>>来标记冲突地点, <<<<<<< 至 ======= 范围是当前分支,======= 至 >>>>>>> 范围是 dev 分支

 这时候只能手动修改冲突代码,然后再次提交(一定要再次提交!)

冲突解决,成功合并
此时冲突虽然解决,但是 dev 还是指向它自己的提交

此时可以使用 git log 的一种方法,进行可视化展示分支提交时间图

# --graph:可视化选项
# --pretty=oneline:简洁的单行模式打印
# --abbrev-commit:缩短commit id的格式
git log --graph --pretty=oneline --abbrev-commit
不同分支下提交代码,可以很清楚地看清

7、分支合并模式

 通常进行分支合并的时候,Git 会尽可能采用  Fast-forward 模式。

Fast-forward 模式示意图

在  Fast-forward 模式下,分支合并后,在查看分支历史时,会丢掉分支信息,从分支信息无法得知这个提交是由其它分支合并而来,还是自己提交的。  

我们在合并的时候,可以添加 --no-ff 选项,表示非快速模式:

# --no-ff:非 Fast-forward 模式
# -m:禁用Fast-forward模式后,合并会创建一个新的提交,因此要 -m 后面需要写提交信息
git merge --no-ff -m "提交信息(建议同时写上合并信息)" 分支名

 在普通模式提交后,查看分支历史,就可以看见来自哪里的信息。

8、分支使用策略

 在实际的开发过程中,master 分支通常被认为是稳定的,仅用来发布新版本,而不用来进行研发;研发都在 dev 分支上,dev 分支被认为是不稳定的。

因此,我们在开发中,也应该在 dev 分支上进行开发,等到代码稳定了,再合并到 master 分支上。

9、修复 bug 的建议和bug 分支:储藏工作区信息

9.1 建立临时 bug 分支来修复 bug

如果在某一个分支上进行开发的过程中,我们被告知 master 分支上有 bug,应该如何解决?

在 Git 中,每一个 bug 都可以通过建立一个临时的 bug 分支来解决;bug 修复后合并到 master 分支上,然后把这个临时分支进行删除。

那么在原来那个分支上的代码正在开发,还不能提交,这时又要去修 bug ,那这些个代码应该怎么办呢?

我们在 dev2 上的代码还没写完,不能提交,却又要去修 bug,怎么办?

Git 为我们提供了 git stash 命令,可以把当前工作区中的信息保存到 stash 文件中,在未来需要的时候还可以恢复出来。 

(PS:保存的这些工作区文件必须曾经被 add, commit 过,否则 Git 无法追踪管理)

# 把工作区文件储存起来
git stash

# 查看 stash 文件中储存了哪些文件
git stash list
储存完毕后,工作区就干净了

 bug 修复后,如何恢复工作区的文件呢?

# 恢复工作区文件,并在恢复的同时把 stash 也删除
git stash pop

# 恢复工作区文件,但是不删除 stash 文件
git stash apply

# 删除 stash 文件
git stash drop
成功恢复了

不过值得注意的是,在 dev2 分支恢复后,修复bug的内容并不会在 dev2 上显示。因为 Master 分支目前最新的提交,时间线上是要领先于新建 dev2 分支时的 master 的分支的:

9.2  主 master 分支合并 dev 分支时的建议

我们的最终目的是让主 master 分支合并 dev 分支的,但是由于 dev 分支时基于 bug 尚未修复时期的 master 建立的,所以由一定风险会把错误代码合并到 master 分支上,造成合并冲突:

解决这个问题,有一个好的建议:

最好在自己的分支上合并下 master,再让自己的这个分支去合并 dev ,这样有冲突可以在自己的这个本地分支上去解决,不会影响到 master 主稳定分支。 

10、删除临时分支

如果某些临时分支,干到一半不要了,又不能提交,这个时候,git branch -d 命令是无法删除的,只会在指定分支已经成功合并到当前分支的情况下删除该分支。如果分支未合并,Git 会阻止删除操作,以避免丢失工作。

我们可以使用 git branch -D 命令强制删除。 

11、分支的总结

分支在实际中有什么用呢?

假设准备开发⼀个新功能,但是需要两周才能完成,第⼀周写了 50% 的代码,如果立刻提交,由于代码还没写完,不完整的代码库会导致别人不能工作了。如果等代码全部写完再一次提交,又存在丢失每天进度的巨大风险。

现在有了分支,就不用怕了。

我们创建⼀个属于自己的分支,不合并之前,别人看不到,还继续在原来的分支上正常工作;而我们在自己的分支上工作,想提交就提交,直到开发完毕后,再一次性合并到原来的分支上,这样,既安全,又不影响别人工作。

并且 Git 无论创建、切换和删除分支,Git 在1秒钟之内就能完成!无论版本库中有一个文件还是一万个文件。

13、分支常用命令总结

# 查看分支信息
git branch

# 基于当前分支新的一个分支
git branch 分支名

# 切换到对应分支
git checkout 分支名

# 创建并切换到该分支
git checkout -b 分支名

# 合并分支(快速模式)
git merge 分支名

# 合并分支(普通模式)
git merge --no-ff 分支名

# 合并并提交(快速模式)
git merge -m "提交信息" 分支名

# 合并并提交(普通模式)
git merge --no-ff "提交信息" 分支名

# 可视化展示 git 提交时间图
git log --graph --pretty=oneline --abbrev-commit

# 暂存工作区文件
git stash

# 查看 stash 文件中暂存了哪些文件
git stash list

# 恢复工作区文件并删除 stash
git stash pop

# 恢复工作区文件,但不删除 stash
git stash apply

# 删除 stash 文件
git stash drop

# 删除分支(该分支已被当前分支合并)
git branch -d 分支名

# 强制删除未合并分支
git branch -D 分支名

四、Git 的远程操作

1、前言:理解分布式版本控制系统

之前所介绍的版本控制系统,都是在本地上的,而 Git 实际上是一个分布式的版本控制系统

分布式版本控制系统示意图

所谓分布式版本控制系统,其实就是同一个仓库,可以分布到不同机器的版本控制系统。

分布式版本控制系统有一个一直不断电的中央服务器,内部有着自己的中央服务器仓库,并不保存在本地(因此又称远程仓库) 

每一个本地服务器,每一台主机都可以把远程仓库克隆到本地,也可以自己的修改推送到中央服务器仓库中,从而达成不同用户之间互相不影响,也便于交流与修改。x

远程仓库是不是需要我们自己搭建一个中央服务器呢?其实不需要,在互联网上为我们提供了许多基于 Git 的代码托管平台,这些都可以算作远程仓库。

其中最有名的就是 github 和 gitee(码云),其中 github 由于是国外网站,速度较慢,所以笔者用的最多的是 gitee,后面演示也使用 gitee。

2、新建远程仓库

gitee 的新建仓库页面

我们来逐行介绍

仓库名称:

和本地仓库一样,远程仓库同样要有自己的名称,起名要围绕这个仓库的代码内容进行。

归属:

给仓库在网站一个后缀

仓库介绍:

介绍这个仓库里面的代码内容,可以开源,可以私有。

初始化仓库:

选择语言:为这个仓库里面的代码选择语言。

.gitignore 文件:可以选择模板,也可以自己写,主要用来让某些文件不被 git 追踪,上传的时候会被忽视,增强代码安全性和可读性,后面会详细介绍

开源许可证:仓库开源的时候有用。

设置模板:

Readme 文件:介绍项目。

Issue 模板文件 / pull request 模板文件:可以帮助用户快速填写必要的信息,减少沟通成本,加快问题解决和代码审核的速度。

选择分支模型:

与本地仓库的分支一样,可以提前设置好多个分支,也可以只设置一个 master 主分支,后续再创建。 这里只创建 master 主分支。

创建成功了

只有一个 master 主分支

3、克隆远程仓库到本地

克隆到本地,需要 git clone 命令:

git clone 远端仓库链接

 远端仓库链接可以直接从仓库里面找到:

克隆仓库的时候,Git 提供了多种数据传输协议,其中最常见的是 SSH 协议 和 HTTPS 协议。

SSH 协议克隆

SSH协议为了保证其安全性和实用性,使用了公钥加密和公钥登录机制,因此,使用此协议需要我们将公钥放到远端仓库进行管理:

1、在本地仓库创建 SSH 秘钥

我们先在用户主目录下查看有没有 .ssh 目录,如果有,再查看这个目录下有没有 id_rsa(私钥)id_rsa.pub(公钥)两个文件。如果没有,需要创建 SSH 秘钥。

# 创建 SSH 秘钥
ssh-keygen -t rsa -C "配置的邮箱"
创建成功后,可以在用户主目录下看见公钥和私钥文件
把公钥复制下来,添加到远端 gitee 仓库中,就可以使用 SSH协议克隆了

# SSH 协议克隆
git clone 远端仓库提供的SSH协议链接

找到一个合适的目录,把远端仓库克隆下来:

可以发现,远端仓库的文件都被我们克隆下来了,名字就是创建远端仓库时候的名字

https 协议克隆

使用 https 协议克隆的时候,不需要配置秘钥,直接克隆即可

不需要配置秘钥,但是要输入远端仓库的账号密码

4、向远端仓库推送文件

在把原地仓库克隆下来后,我们可以进行修改,git add,git commit,那么应该如何推送至远端呢?就需要使用到 git push 命令

# 把本地克隆仓库的某个分支上传到远程仓库的某个分支并合并(远程主机名默认叫做 origin)
git push 远程主机名 本地分支名:远程分支名

# 如果本地分支名和远程分支名相同,也可以省略“:远程分支名”
git push 远程主机名 本地分支名
我们新建了一个文本文件,并提交至远程仓库
远程仓库出现了我们的代码!

在推送的时候,SSH 协议和 HTTPS 协议也有所区别:

SSH协议:因为提前配置好了秘钥,所以每次推送不需要输入账号密码

HTTPS协议:每次推送都要输入账号密码,相对而言有些麻烦。

5、从远端仓库拉取新版本文件

如果我们在远端仓库在线修改了文件(强烈不建议,最好是克隆到本地修改再推送上去)或者其它开发者更新了这个仓库中的文件,这个时候远端仓库就领先于本地仓库一个版本。

Git 提供了 git pull 命令,用来从远端获取代码并合并本地版本

# 从远端获取代码合并到本地某个分支上
git pull 远程主机名 拉取的远端分支名:想合并到本地哪个分支上

# 如果拉取的远程分支是和当前所在分支合并,可以省略“:想合并到本地哪个分支上”
git pull 远程主机名 拉取的远端分支名
我们在远端仓库在线修改文件 (强烈不建议,最好是克隆到本地修改再推送上去,这里仅做演示)
成功从远端拉取了新版本文件并合并了

 6、查看远端仓库信息

git 为我们提供了两个命令,来查看克隆到本地后的远端仓库信息

# 仅查看远端仓库名
git remote

# 查看远端仓库详细信息
git remote -v

这个 origin ,就是远端仓库名,在推送和拉取的时候都要用到。 

后面跟着的 fetch 和 push,只有拥有这两个权限的本地克隆仓库,才有权拉取和推送。  

远端仓库名也可以更改:

git remote rename 原远端仓库名 新远端仓库名

 7、忽略特殊文件:.gitignore 文件

前文我们已经说过,我们有些文件不想 / 不应该提交到远端(比如保存了密码的配置文件),这时候我们就需要在 Git 工作区的根目录下创建一个 .gitignore 文件,然后把要忽略的文件名填进去,Git 就会自动忽略这些文件了。

可以不从头写,gitee 在创建仓库的时候选择模板可以为我们生成:

 如果当时没有选择这个,我们可以在工作区创建一个,然后推送到远端:

我们在克隆仓库工作区目录写下这些忽略

推送到远端
我们创建 ini 结尾的文件和 apk 结尾的文件,可以发现,并不被 Git 所追踪了,被其忽略

 不过,我们也可以使用 gid add -f 命令把被 Git 忽略的文件强制添加。

如果有时候,我们发现某些文件被忽略了,想知道是 ,gitignore 文件中的哪个规则导致的,可以使用 git check-ignore -v 被忽略的文件名 来检查:  

GIt 会告知我们,是 .gitignore 的第十五行忽略了该文件,便于修订规则

我们也可以把指定的文件排除在 .gitignore 规则外,可以在 .gitignore 文件里面写

# 假设不排除 .gitignore
!.gitignore

# 排除对应文件
!文件名

8、给命令配置别名

在使用 Git 的时候,有些命令会比较长,敲起来比较麻烦,这个时候我们可以为命令起个别名

(比如之前的可视化展示提交时间图:git log --graph --pretty=oneline --abbrev-commit)

核心命令叫做 alias:

# 简化命令
# --global 代表全局生效
# 注意:如果原命令包含空格 / 比较复杂,要拿 '' 括起来
git config [--global] alias.要起的别名 原命令名

# 重点注意:只能为命令取别名,也就是说命令别名只能跟在 git 后面

别名和原命令是一样的

 但是,对于初学者,不建议取别名,多练习练习 git 命令总归是有好处的。

9、给某一次提交加标签

提交的标识是 commit id,比较复杂且难记;这时候,我们可以为这次提交打个 tag(标签),给予容易记住且有意义的名字,也便于定位版本进行回退。

1、切换到需要打标签的分支上

2、git tag 标签名 即可打上标签

# 打标签
git tag 标签名

# 查看所有标签
git tag

# 查看标签对应提交的具体信息
git show 标签名

 标签默认是打在最新提交的 commit 上的,如果要给指定的某次提交打标签,就需要查到其 commit id:

# 给某一个特定提交打标签
git tag 标签名 commit_id

Git 还提供了创建带有说明的标签:

# 创建带有说明的信息
git tag -a 标签名 -m "标签说明" [commit_id]

如果标签打错了,也可以删除:

git tag -d 想删除的标签名

因为创建的标签都只存储在本地,不会自动推送到远程,所以打错的标签可以在本地手动安全删除。

如果想要推送某个标签到远程:

git push 默认主机名(origin) 标签名

成功提交到远程

 也可以一次性全部推送:

# 一次性推送全部标签到远端
git push origin --tags

如果标签已经推送到远程,想删除,就要先从本地删除,再:

# 删除远端标签
git push origin :refs/tags/标签名

当然也可以在 gitee 下直接删除,不过不建议这么做。

10、Git 远端操作命令合集

前提:先在 gitee 上创建好远程仓库,SSH协议需要提前构建好秘钥,HTTPS不需要

# HTTPS协议克隆远端仓库至本地
git clone gitee提供的HTTPS链接

# SSH协议克隆准备:用户主目录下创建SSH公钥和私钥
ssh-keygen -t -rsa --C "邮箱"

# SSH协议克隆远端仓库至本地
git clone gitee提供的SSH克隆链接

# 查看远端仓库名
git remote

# 查看远端仓库详细信息
git remote -v

# gid add,git commit 之后向远端仓库推送某个分支合并到远端某个分支 
git push 远程主机名 本地分支名:远程分支名

# 如果相同,可以省略远程分支名
git push 远程主机名 本地分支名

# 拉取远程仓库的某个分支合并到本地某个分支上
git pull 远程主机名 远程分支名:本地分支名

# 如果相同,可以省略本地分支名
git push 远程主机名 远程分支名

# 检查某个文件因为 .gitignore 文件中的哪些规则被忽略
git check-ignore -v a.so

# 强制添加某个文件(无视.gitignore文件限制)
git add -f 文件名

# 给命令配置别名(别名必须跟在 git 之后,不能分开取别名;命令间有空格需要‘’扩住)
git config [--global] alias.别名 原命令名

# 给某个提交打标签(不加 commit id 默认给最近一次提交打)
git tag 标签名 commit_id

# 查看所有标签
git tag

# 查看某个标签的详细信息
git show 标签名

# 创建带有说明的标签
git tag -a 标签名 -m "说明信息" [commit_id]

# 删除标签
git tag -d 标签名

# 把标签推送到远端仓库
git push 远端主机名(origin) 标签名

# 把远端仓库的标签删除(先删除本地标签)
git push 远端主机名(origin) :refs/tags/标签名

网站公告

今日签到

点亮在社区的每一天
去签到