番外篇 Git 的原理与使用

embedded/2024/12/23 15:40:32/

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└── tags17 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.namegit 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>github 和 gitee(码云),其中 github>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/标签名

http://www.ppmy.cn/embedded/147706.html

相关文章

云连POS-ERP管理系统 download.action存在任意文件读取漏洞

免责声明: 本文旨在提供有关特定漏洞的深入信息,帮助用户充分了解潜在的安全风险。发布此信息的目的在于提升网络安全意识和推动技术进步,未经授权访问系统、网络或应用程序,可能会导致法律责任或严重后果。因此,作者不对读者基于本文内容所采取的任何行为承担责任。读者在…

医学AI领域高分热点综述|文献速递·24-12-20

小罗碎碎念 推文速览 第一篇文章系统回顾了人工智能在免疫肿瘤学中预测免疫检查点抑制剂疗效的应用和进展。 第二篇文章综述了新兴的临床样本、模型系统、技术和计算工具在早期癌症研究中的应用&#xff0c;并探讨了如何将这些新见解转化为早期癌症检测和预防的策略。 第三篇…

[数据结构] 链表

目录 1.链表的基本概念 2.链表的实现 -- 节点的构造和链接 节点如何构造? 如何将链表关联起来? 3.链表的方法(功能) 1).display() -- 链表的遍历 2).size() -- 求链表的长度 3).addFirst(int val) -- 头插法 4).addLast(int val) -- 尾插法 5).addIndex -- 在任意位置…

C# 批量替换html里面的http链接

C# 批量替换html里面的http链接 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.IO; using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.T…

PyQt5学习笔记

P95 绝对布局 绝对布局&#xff0c;使用move方法&#xff0c;操作坐标来控件控件的位置。 import sys from PyQt5.QtWidgets import *绝对布局&#xff0c;使用move方法&#xff0c;操作坐标来控件控件的位置。class MyWin(QWidget):def __init__(self):super().__init__()#…

webgame.one 在线红白机FC游戏平台技术架构分析

前言 还记得小时候守在电视机前&#xff0c;手握红白机手柄&#xff0c;沉浸在《魂斗罗》紧张刺激的战斗、《超级马里奥兄弟》奇妙的冒险世界&#xff0c;或是与小伙伴一起在《坦克大战》里并肩作战的美好时光吗&#xff1f;那些经典的 FC 游戏&#xff0c;承载着我们童年最纯…

多线程设计模式-保护性暂停之面向对象

保护性暂停模式&#xff0c;也是多线程的一种固定模式&#xff0c;使用着这种模式时候&#xff0c;当线程在访问某个对象时&#xff0c;发现条件不满足时&#xff0c;就暂时挂起等待条件满足时再次访问。 并且能够防止虚假唤醒 那我们不禁要思考&#xff0c;在多线程终止模式…

NV12 、NV21 和 BGR转换

文章目录 前置阅读&#xff1a;YUV格式效果源码 opencv没有提供BGR转NV12或者NV21的方法&#xff0c;这里借助中间过程实现一下。 前置阅读&#xff1a;YUV格式 https://blog.csdn.net/qq_40622955/article/details/144427710 效果 原图 NV12 NV12转BGR NV21 NV21转…