git的学习与使用

git Learning

git 安装和使用以及多场景应用

1.背景

Git是什么?

Git是世界上最先进的分布式版本控制系统,由Linus Torvalds编写,用于高效处理从小型到大型项目的版本管理。Git最初设计用于Linux内核开发,现已成为开源和商业软件开发中最流行的版本控制系统。

Tip

版本控制系统:又叫修订控制或源控制系统,是一种软件实用程序,用于跟踪和管理对文件系统的更改。VCS 还提供协作实用程序,用于与其他 VCS 用户共享和集成这些对文件系统的更改。

VSC

VCS

Linus花了两周时间自己用C写了一个分布式版本控制系统,这就是Git!一个月之内,Linux系统的源码已经由Git管理了!

Git迅速成为最流行的分布式版本控制系统,尤其是2008年,GitHub网站上线了,它为开源项目免费提供Git存储,无数开源项目开始迁移至GitHub,包括jQuery,PHP,Ruby等等。

与分布式系统对应的就是集中式版本控制系统

集中式版本控制系统:集中式版本控制系统(CVCS)使用单一的中央服务器来存储所有文件的所有版本。用户从中央服务器检出文件的最新版本进行工作,并在完成后将更改提交回中央服务器。

Note

集中式版本控制系统的缺点:

  1. 单点故障:如果中央服务器出现故障,整个团队都无法进行版本控制操作。
  2. 网络依赖:用户必须连接到中央服务器才能进行检出、提交等操作。
  3. 性能瓶颈:随着团队规模的增大,中央服务器的性能可能成为瓶颈。
  4. 数据安全性:所有数据都存储在中央服务器上,一旦服务器被攻击或损坏,数据可能丢失。

分布式版本控制系统根本没有“中央服务器”,每个人的电脑上都是一个完整的版本库

在实际使用分布式版本控制系统的时候,其实很少在两人之间的电脑上推送版本库的修改,通常也有一台充当“中央服务器”的电脑,但这个服务器的作用仅仅是用来方便“交换”大家的修改,没有它大家也一样干活,只是交换修改不方便而已。

2. 安装Git

最早Git是在Linux上开发的,很长一段时间内,Git也只能在Linux和Unix系统上跑。不过,现在Git可以在Linux、Unix、Mac和Windows这几大平台上正常运行了。

2.1 在Linux上安装Git

在大多数Linux发行版上,尝试输入git,如果提醒系统没有安装Git,就可以通过包管理器安装它。


# ubuntu / debian
sudo apt install git

2.2 在MacOS上安装Git

使用homebrew安装

homebrew:MacOS 软件管理的利器

brew install git

2.3 在Windows上安装Git

  1. scoop安装

Windows也有软件管理器scoop,scoop github页面有安装说明,主要是使用普通用户打开powershell之后,输入命令安装

# 1. 设置执行策略
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser

# 2. 安装scoop
Invoke-RestMethod -Uri https://get.scoop.sh | Invoke-Expression

使用 C:\> scoop install git

  1. 本地安装

进入git官网 git-scm.com,下载Windows版本的安装包,双击安装即可。

3. 基本配置

安装完成后,建议进行一些基本配置,比如用户和邮箱

# 设置用户名
git config --global user.name "Your Name"

# 设置用户邮箱
git config --global user.email "

因为Git是分布式版本控制系统,所以,每个机器都必须自报家门:你的名字和Email地址,有冒充的也是有办法可查的。

Tip

如果没有配置用户名和邮箱,Git会拒绝提交操作,提交的时候会报错无法提交,无论你修改什么文件内容都无法提交上去

4. 本地创建版本库/仓库

4.1 创建版本库

什么是版本库?版本库又名仓库(Repository),可以简单理解为一个目录,这个目录里面所有的文件都被git管理起来,每个文件的增删查改git都可以追踪,是为了任何时刻都可以追踪历史,有的也叫本地库(local git repository),其实是.git的文件夹

创建版本库有两种方式: - 在本地创建一个新的版本库 - 从远程版本库克隆一个版本库到本地

创建一个库非常简单,选择自己想要创建库的目录,然后在该目录下执行下面的命令,下面都是按照MacOS和WSL2来介绍:

# 创建一个名为 gitlearn 的目录
mkdir gitlearn

# 进入 gitlearn 目录
cd gitlearn

# 初始化一个新的 Git 版本库
git init

瞬间Git就把仓库建好了,在当前目录下会生成一个隐藏的.git目录,这个目录就是Git用来管理版本库的核心目录,里面存放了所有的版本信息和配置文件。

下面是git文件的结构

[4.0K]  .
├── [  23]  HEAD
├── [4.0K]  branches
├── [  92]  config
├── [  73]  description
├── [4.0K]  hooks
│   ├── [ 478]  applypatch-msg.sample
│   ├── [ 896]  commit-msg.sample
│   ├── [4.5K]  fsmonitor-watchman.sample
│   ├── [ 189]  post-update.sample
│   ├── [ 424]  pre-applypatch.sample
│   ├── [1.6K]  pre-commit.sample
│   ├── [ 416]  pre-merge-commit.sample
│   ├── [1.3K]  pre-push.sample
│   ├── [4.8K]  pre-rebase.sample
│   ├── [ 544]  pre-receive.sample
│   ├── [1.5K]  prepare-commit-msg.sample
│   ├── [2.7K]  push-to-checkout.sample
│   └── [3.6K]  update.sample
├── [4.0K]  info
│   └── [ 240]  exclude
├── [4.0K]  objects
│   ├── [4.0K]  info
│   └── [4.0K]  pack
└── [4.0K]  refs
    ├── [4.0K]  heads
    └── [4.0K]  tags

9 directories, 17 files
  • HEAD 是文件,表示现在指针,指向当前所在的分支或提交
  • config 是Git的配置文件,包含remote(远程仓库),username(用户名)等信息,分支策略,hooks配置等

[remote “origin”] 当你执行 git clone 命令时,Git 会默认帮你做一件事:把你克隆的那个远程服务器地址起个“外号”叫 origin。

  • hooks 目录包含了一些脚本,这些脚本可以在特定的Git事件发生时自动执行,比如提交代码前后,推送代码前后等
  • info 中包含exclude 只对当前仓库有效,不会被提交
  • objects 目录存储了所有的Git对象,包括提交对象、树对象和blob对象
    • info
    • pack 压缩后的对象包,提高性能
    • xx/yyy, xx是对象的前两位哈希值,yyy是后面的哈希值
  • refs 目录存储了所有的引用,包括分支和标签
    • heads 分支引用
    • tags 标签引用
  • index 文件,暂存区

git到底是怎么存的?Git是一个内容寻址的对象数据库,所有的文件和目录都被存储为对象,每个对象都有一个唯一的SHA-1哈希值作为标识。

类型 含义
blob 文件内容
tree 目录结构
commit 提交
tag 标签
Tip

先暂时了解,后续学习之后就能完全了解这些概念

4.2 把文件添加到版本库

所有的版本控制系统,只能追踪文本文件的改动,二进制文件是无法追踪的,比如图片、视频、音频等文件,Git只能把它们当作一个整体来管理,无法追踪它们的改动内容。

图片、视频这些二进制文件,虽然也能由版本控制系统管理,但没法跟踪文件的变化,只能把二进制文件每次改动串起来

Microsoft的Word格式是二进制格式,因此,版本控制系统是没法跟踪Word文件的改动的

Important

文本是由编码的,中文有GBK编码,日文有Shift_JIS编码,如果没有历史遗留问题,强烈建议使用标准的UTF-8编码,所有语言使用同一种编码,既没有冲突,又被所有平台所支持。

使用Windows的童鞋要特别注意:千万不要使用Windows自带的记事本编辑任何文本文件,它把每个文件开头添加了0xefbbbf(十六进制)的字符

在gitlearn目录中,创建一个名为hello.txt的文本文件,内容如下:

Hello, Git!

第一步,用命令git add告诉git,将文件添加到仓库

git add hello.txt

git add 命令并不会立即把文件添加到版本库中,而是把文件添加到一个叫做“暂存区”(Staging Area)的地方,只有当你执行 git commit 命令时,文件才会真正被添加到版本库中。这个暂存区就是上面的index

Important

暂存区给了你后悔的机会,理解成草稿纸就可以了,减少代码提交的混乱,提高安全性

当使用git add之后,在.git中会出现index文件(二进制文件),使用git ls-files --stage命令,可以查看暂存区的内容,如下所示:

100644 a0423896973644771497bdc03eb99d5281615b51 0 readme.text

  • 100644: 文件的权限模式
  • a0423896973644771497bdc03eb99d5281615b51: 文件的SHA-1哈希值
  • 0: 文件在暂存区中的索引
  • readme.text: 文件名

第二步,用git commit命令,把暂存区的内容提交到版本库

git commit -m "Initial commit"

-m 后面输入的是本次提交的说明,可以输入任意内容,当然最好是有意义的,这样你就能从历史记录里方便地找到改动记录。 git commit 可以一次提交多个文件,可以多次git add添加到缓存区

git commit之后,.git文件出现了两类文件:以两位16进制命名的文件夹和info/pack文件夹

├── 61
│   └── fc8d453f1f12826b8635964800c1864d14aa11
├── 7a
│   └── 51e2c88288026c3b8cd6ad44acfa087c762f33
├── a0
│   └── 423896973644771497bdc03eb99d5281615b51 blob文件内容 `git ls-files --stage`
├── info
└── pack
  • Blob 对象 (Binary Large Object)
    • 文件的具体内容,存储在以文件内容的 SHA-1 哈希值命名的文件中
    • 在执行git add已经生成了
  • Tree 对象
    • 目录结构,包含文件名、权限和对应的 Blob 对象的 SHA-1 哈希值
    • 在执行git commit时生成
  • Commit 对象
    • 提交信息,包含作者信息、提交时间、父提交的 SHA-1
    • 在执行git commit时生成
  • pack:存放的是“打包好的”、为了性能优化的历史数据。
  • info:存放的是“地图”,告诉 Git 怎么找这些数据。

4.3 查看提交历史

按照上一步,我们创建了一个readme.txt文件,并提交到了版本库,现在我们对这个文件进行修改,继续添加一些内容:

Hello, Git!

使用git status命令查看当前状态:

git status

git status

上图告诉我们,readme.txt文件被修改了,但是还没有提交到版本库。git status只能看到文件被修改了,但是看不到具体修改了什么内容,这时候可以使用git diff命令查看具体修改内容:

diff --git a/readme.text b/readme.text
index a042389..3f2fbea 100644
--- a/readme.text
+++ b/readme.text
@@ -1 +1,2 @@
 hello world!
+Hello, git!

上图显示了readme.txt文件的修改内容,-表示删除的内容,+表示新增的内容。可以看到,git本质上不记录行修改,而是记录快照+用比较算法生成行差异

blob: 文件内容 readme.txt的内容hash -> blob hash tree: 目录结构,tree对象包含文件名readme.txt和对应的blob hash commit: 提交信息,包含作者信息、提交时间、父提交的 SHA-1

# git log查看提交历史记录
git log
# commit 7a51e2c88288026c3b8cd6ad44acfa087c762f33 (HEAD -> master)

# 顺着commit查看tree内容
git cat-file -p 7a51e2c88288026c3b8cd6ad44acfa087c762f33
# tree 61fc8d453f1f12826b8635964800c1864d14aa11 显示tree内容

# 顺着tree查看blob内容
git cat-file -p 61fc8d453f1f12826b8635964800c1864d14aa11

# 显示blob内容
# 100644 blob a0423896973644771497bdc03eb99d5281615b51  readme.text
Important

Myers Diff Algorithm(经典算法): 它能找出两个文本之间的最短编辑序列(最小操作数),并输出插入/删除/修改的行。

  • - 表示删除
  • + 表示新增
  • @@ 表示行号范围
  1. Blob的存储是按SHA-1或SHA-256哈希存储的,文件内容相同–哈希相同–只存一份(多次提交同一份内容,git只存一次);
  2. 压缩packfile,当仓库赠长时,git会把对象打包成packfile
  3. 相似的文件只存差异,比如一个文件有90%的内容和另一个文件相同,git只存储10%的差异部分

明白了这些,就能明白git的强大之处!

使用git log --oneline进行查看,commit历史,如下所示:

d28a37a (HEAD -> master) add git
7a51e2c add readme

每一行显示一个提交记录,前面的字符串是提交的SHA-1哈希值的简短形式,后面是提交说明。

使用git cat-file -p d28a37a命令查看某次提交的详细信息:

tree 0c15bf9e0442e22c2045cc8abf9c1c055b441a4c
parent 7a51e2c88288026c3b8cd6ad44acfa087c762f33
author Qiao <erwin@pigeon.cn> 1769091343 +0800
committer Qiao <erwin@pigeon.cn> 1769091343 +0800

同时在.git/objects目录下会生成对应的对象文件夹

4.4 版本回退

现在我们明白了如何使用

  • git add 把文件添加到暂存区
  • git commit 把暂存区的内容提交到版本库
  • git log 查看提交历史
  • git diff 查看文件修改内容
  • git cat-file 查看对象内容

git最强大的地方可以版本回退,当你觉得文件修改到一定程度的时候,就可以保存一个快照(add+commit),一旦文件改乱了,或者误删了文件,就可以从最近一个commit恢复

commit 的哈希 = 对「commit 对象的完整字节内容」做哈希(SHA-1 / SHA-256)得到的结果

首先,git必须知道自己当前的版本,在git中,用HEAD 来表示当前版本,HEAD其实是一个指针,指向当前分支的最新提交。

d3ac97a (HEAD -> master) git reset
d28a37a add git
7a51e2c add readme

有个简单的回退方式,上一个版本就是HEAD^,上上一个版本就是HEAD^^,使用git reset --hard HEAD^命令回退到上一个版本

  • –hard 表示强制回退,工作区和暂存区都会被重置到指定的版本
  • –soft 表示只回退HEAD指针,工作区和暂存区保持不变
  • –mixed 表示回退HEAD指针和暂存区,工作区保持不变(默认选项)
Tip

如果要回退到最开始的版本,你可以找到之前的commit哈希值,然后使用git reset --hard <commit-hash>命令进行回退。

Git的版本回退速度非常快,因为Git在内部有个指向当前版本的HEAD指针,当你回退版本的时候,git仅仅是把HEAD指针改向

.Git也被称为版本库Repository,版本库里存了很多东西,其中最重要的就是称为stage(或者叫index)的暂存区,还有Git为我们自动创建的第一个分支master,以及指向master的一个指针叫HEAD

.git

.git
Tip
  • git restore <file>:将文件恢复到工作区的最后一次提交状态,丢弃工作区的修改。·
  • git restore --staged <file>:将文件从暂存区移除,但保留工作区的修改。

4.5 git删除文件

之前讲了git增减改,现在是文件的删除,常规文件处理上,使用rm readme.txt,然后使用git status查看状态

On branch master
Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
    deleted:    readme.text

删除完之后,工作区和版本库就不同了,这时候就提示了不同。

文件删除后,就像被放在垃圾桶里面一样,还可以从.git仓库中恢复,同样是使用git restore readme.txt命令恢复文件;如果要将文件从版本库中删除(.git),需要使用git rm readme.txt命令。

Important

从来没有被添加到版本库就被删除的文件,是无法恢复的!

5. 远程仓库

Git是分布式版本控制系统,每个人的电脑上都是一个完整的版本库,但是在实际使用中,通常会有一台服务器充当“中央服务器”,方便大家交换修改。同一个Git仓库不同电脑,同一电脑不同目录都可以git。

找一台电脑24小时开机,其他人都能从这个服务器仓库克隆一份到自己电脑上,之后修改后把各自的提交推送到服务器仓库里。完全可以自己搭建一台运行git的服务器,现在阶段为了学习git搭建服务器其实脱离主流了,现在的主流就是github,gitlab,bitbucket这些网站,都是提供git远程仓库服务的网站。

5.1 注册github

登陆github官网,进行常规注册

github

github

5.2 创建SSH Key

2021年之前Github已经停用了HTTPS协议的密码认证方式,改成了SSH Key认证方式,SSH不是加密Git

加密本地和Github之间的通信通道

SSH用的是公钥和私钥对,你本地有一对钥匙

🔑 私钥(private key)  → 只在你电脑
🗝 公钥(public key)   → 上传到 GitHub

第一步:创建SSH key

在用户目录下,查看.ssh目录,如果有,再看看这个目录下有没有id_rsaid_rsa.pub文件,如果没有,就创建一对新的SSH key

Github当前之前的算法有

  • ed25519 (推荐)
  • rsa
# 生成ed25519算法的SSH key
ssh-keygen -t ed25519 -C "@your_email.com"

如果提示ssh-keygen命令不存在,可以先安装OpenSSH工具包,然后再执行上面的命令。一路输入Enter就好,直到创建好,出现了id_ed25519id_ed25519.pub两个文件,id_rsa是私钥,不能泄露出去,id_rsa.pub是公钥,可以放心地告诉任何人。

第二步:登陆Github

从头像处进入点击设置按钮,进入个人设置界面,点击SSH和GPG keys,进入SSH Key管理界面,点击New SSH key按钮,添加新的SSH Key

github-ssh

github-ssh

在Title输入框中,输入这个Key的名称,比如My Laptop,然后打开id_rsa.pub文件,把里面的内容全部复制到Key输入框中,最后点击Add SSH key按钮,完成SSH Key的添加。

Tip

记得把id_ed25519.pub中的全部内容复制进去

5.3 远程仓库与本地仓库交互

现在的情况是,你在本地使用git init创建了本地仓库,现在想托管在github上,方便随时随地访问和备份。这样Github上仓库既可以作为备份,又可以让其他人通过该仓库来协作。

找到New按钮,创建一个新的远程仓库,进入页面之后,按需创建即可

github-new-repo

github-new-repo
  • Repository name:填写仓库名称
  • Description:填写仓库描述(可选)
  • Public/Private:选择仓库的可见性,Public表示公开,Private表示私有
  • Initialize this repository with a README:是否初始化仓库,勾选后会自动创建一个README文件
  • .gitignore:选择一个.gitignore模板,忽略不需要跟踪的文件(可选)
  • License:选择一个许可证(可选)

创建好本地仓库和远程仓库之后,接下来就是将本地推送到远程或者把远程仓库克隆到本地

(一) 本地推送到远程

假设你已经在本地创建了一个Git仓库,并且已经有了一些提交记录,现在你想把这个本地仓库推送到刚刚创建的远程仓库。

git remote就是用来管理远程仓库的命令,使用git remote add <name> <url>命令添加一个远程仓库

  • 添加
  • 删除 remove
  • 重命名
  • 查看
# 更改brach的名称,强制修改
git branch -M main

# 添加远程仓库(远程仓库就是自己创建的github仓库)
git remote add origin https://github.com/beautyhubcode/gitLearn.git

执行完之后什么都没有上传,只是Git记住了一条配置,origin → https://github.com/beautyhubcode/gitLearn.git,可以使用git remote -v

最后使用git push -u origin main命令把本地的main分支推送到远程仓库的main分支

添加后,远程库的名字就是origin,这是Git默认的叫法,也可以改成别的,但是origin这个名字一看就知道是远程库。

Tip

要把https://github.com/后面的账户名正确,否则本地关联能连接上,但是推送远程仓库是不可能的,因为SSH key公钥不在添加列表

不想要远程仓库了,可以使用git remote rm origin

(二) 从远程克隆到本地

现在假设我们从0开发,最好的方式就是先创建远程仓库,然后从远程仓库克隆

按照上述的方式,在github创建仓库之后,选择创建README.md文件,这时候远程库就创建好了

找一个目标目录,使用git clone https://github.com/beautyhubcode/gitLearn.git,会自动下载文件,然后切换文件夹就可以看到文件了

Git支持多种协议,包括https,但ssh协议速度最快。

6. 分支管理

分支才是Git最强大的功能,分支是开发的利器。当你在处理一个文档的时候,另外一个你在处理另外一个文档,处理同一个文档最容易产生冲突,要小心。

理解了分支才能更好的明白git的强大,防止被冲突劝退,尽量更改不同的文件入门,后续明白机制了就能同时开发很多内容了。你创建了一个属于你自己的分支,别人看不到,还继续在原来的分支上正常工作,而你在自己的分支上干活,想提交就提交,直到开发完毕后,再一次性合并到原来的分支上,这样,既安全,又不影响别人工作。

6.1 理解分支

之前所有的测试和编写都是只有一条时间线,这条线也是分支线的一种被称为主分支,就像正方形是长方形的一种一样,之前被叫做master分支,由于设计种族歧视现在已经改成main分支了。而HEAD不是指向提交。而是指向main的,main才指向提交

* 00b6859 (HEAD -> main, origin/main) feat: 初始化Git学习资料仓库
* f1fea1c git track
* 413adc5 git test
* d3ac97a git reset
* d28a37a add git
* 7a51e2c add readme

另一种表现形式是

A ---- B ---- C
              ↑
            main
              ↑
            HEAD
  • A B C:提交(commit)
  • main:一个分支指针,指向提交 C
  • HEAD:当前所在位置,通常指向当前分支(这里是 main)
Tip

HEAD在.git文件中的HEAD文件,对应者ref/heads,每当commit的时候,HEAD指向commit

当使用git checkout <commit_hash>,HEAD指向这个commit

当我们创建新的分支,例如dev时,Git新建了一个指针叫dev,指向main相同的提交,再把HEAD指向dev,就表示当前分支在dev

A ---- B ---- C --- D --- E
              ↑
            main
                          ↑
                        dev
                          ↑
                        HEAD

切换到分支之后,新提交一次后,dev指针往前走一步,而main指针不变。

假如在dev上的工作完成了,我们就可以把dev合并到main上。Git是怎么合并呢?最简单的方法,就是直接把main指向dev的当前提交,就完成了合并,也就是把main的指向dev的当前提交

                           HEAD
                             │
                             ▼
                          main
                             │
                             ▼
┌───┐    ┌───┐    ┌───┐    ┌───┐
│   │───▶│   │───▶│   │───▶│   │
└───┘    └───┘    └───┘    └───┘
                             ▲
                             │
                            dev

6.2 创建分支

首先我们创建dev分支,然后切换到dev 分支

# 创建dev分支,然后切换
git checkout -b dev

# 查看所有分支
git branch

git branch命令会列出所有分支,当前分支前面会标一个*号。

更改好之后,切换回main分支,再查看修改过的文件,发现添加的内容不见了,因为刚才是在dev分支上,而master分支此刻提交点还没有变,现在开始把dev合并到master分支上

git merge dev

Updating f4e6462..a18e1f6
Fast-forward
 README.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

git merge命令用于合并指定分支到当前分支。注意到上面的Fast-forward,这次合并是快进模式,就是dev在mian前面,把dev的合并到main上面,往前移动指针。

这种是mian不动,dev动;如果出现main动,dev动这种情况就得另一方式了

Note

现在使用git switch -c dev 用来切换更加方便,之前方式git checkout <branch>容易和git checkout --<file>混淆

小结一下: - 查看分支 git branch - 创建分支 git branch <name> - 切换分支 git checkout <name>git switch <name> - 创建+切换 git checkout -b <name>git switch -c <name> - 合并切换当前分支 git merge <name> - 删除分支 git branch -d <name>

6.3 冲突问题

前面已经解释过了git文件存储原理就是对文件进行快照,发现不同之后进行差异算法分析,然后精准到行变动,不是对文件进行哈希,而是对文件内容哈希,如果在不同的分支上同一文件同一位置进行了修改就会引发冲突

这种情况下,git实行合并,会出发冲突报错,这是底层逻辑

<<<<<<< HEAD
2 line (main change)
=======
2 line (dev change)
>>>>>>> dev

Git用<<<<<<<,=======,>>>>>>>标记出不同分支的内容

Important

当Git无法自动合并分支时,就必须首先解决冲突。解决冲突后,再提交,合并完成。

分支策略

首先main是像游戏主线一样的,保持稳定,防止项目出错之后无法挽回,一般不允许直接修改;修改呢,一般都在dev上,dev是不稳定的,主要是发布的时候将dev分支合并到master上;dev分支也可以都有自己的分支

branchs

branchs

使用Fast forward模式下,删除分支后,会丢掉分支的信息,强制禁用Fast forward模式,merge的时候生成一个新的commit,从分类的历史上看到分支信息

git merge --no-ff -m "merge with no-ff" dev

$ git log --graph --pretty=oneline --abbrev-commit
*   e1e9c68 (HEAD -> master) merge with no-ff
|\  
| * f52c633 (dev) add merge
|/  
*   cf810e4 conflict fixed

6.4 git stash功能

在各个流程中,bug是常有的时候,由于分支的强大,所以每个bug都可以通过一个新的临时分支来修复,修复后合并分支,然后删除临时分支。

但是如果在处理一件事过程中,临时来了另外一件事,当前工作还没完成无法提交,这个时候怎么处理呢?

之前想的是切换分支出去,但是Git 不允许你在带着未提交改动的情况下,随意切分支 如果切换过去,没有隔绝当前工作,会导致污染

并不是你不想提交,而是工作只进行到一半,还没法提交,预计完成还需1天时间。但是,必须在两个小时内修复该bug,怎么办?

git stash功能,可以把当前工作现场打包到.git里面,等恢复之后继续工作

使用之后,git status查看

On branch dev
Your branch is up to date with 'origin/dev'.

nothing to commit, working tree clean
  1. 确定在哪个分支上修复bug,假定在main分支上修复,临时构建分支
# 查看分支
git branch

# 切换branch
git switch main

# 创建临时git
git switch -c issue-01

# 修改后提交
git add readme.txt
git commit -m "fix bug 01"

# 切换到main
git switch main
git merge --no-ff -m "merged bug fix 101" issue-01
  1. 结束后,工作区干净了,刚才保存的现场需要找出来

git stash list查看得到stash@{0}: WIP on dev: b0aea18 docs(update basic.md)文件存储的位置

  • 使用git stash apply恢复,但是恢复后,stash内容并不删除,需要git stash drop删除
  • 使用git stash pop,恢复的时间同时把stash的内容删除了
On branch main
Your branch is up to date with 'origin/dev'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
    modified:   "01-\345\237\272\347\241\200\346\225\231\347\250\213.md"

no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (04a0d6be2bd32cfcb98b3ea92c436810741c5c6b)
  1. 上一步在main上修改了文件,你的dev也是为了解决main的问题,所以dev其实也要同步main的修改,要不就是把改的内容复制到dev上去,为了方便操作,git还专门提供了cherry-pick命令,让我们复制一个特定的提交到当前分支

git cherry-pick 4c805e2,因为这两个commit只是改动相同,但确实是两个不同的commit。

6.5 多人协作

之前学习了分支的创建和冲突解决,这些都是多人协作的基础,多人协作是git另一个灵魂。

origin表示源头的意思

# 查看远程库的信息
git remote

# 查看远程库的详细信息
git remote -V

推送分支

推送分支,就是把该分支上的本地提交推送到远程库

Tip

本地 Git 仓库并不是把 .git 整个文件夹“拷贝”到 GitHub 而是通过 Git 协议(走 SSH / HTTPS)只传输 Git 对象(commit / tree / blob)和引用(refs)

project/
├── .git/
│   ├── objects/   ← 所有 commit / tree / blob
│   ├── refs/      ← 分支指针
│   ├── HEAD
│   └── config
└── 工作区文件
# 推送main
git push origin master

## 推送分支
git push origin dev

抓取分支

github默认抓取的是main分支,你是无法git clone下来dev的直接,现在你需要在dev上开发,就必须创建远程origindev分支到本地

# 创建并切换
git stiwch -c dev origin/dev

# 另一种写法
git checkout -c dev origin/dev
  • git switch -c dev 只创建本地分支,不知道远程 dev 在哪
  • git switch -c dev origin/dev 创建本地 dev,并且“从远程 dev 复制 + 建立追踪关系”

git pull下来远端的内容,现在就可以修改,不停的推送了

6.6 Rebase

多人在同一分支上协作时,很容易出现冲突,即使没有冲突,后来者也必须pull,在本地合并之后,才能push成功

*   a1b2c3d (HEAD -> dev) Merge branch 'feature01'
|\  
| * e4f5g6h (feature01) add feature part 2
| * c3d4e5f add feature part 1
* | 9a8b7c6 fix dev bug
|/  
* 1234567 (main) init project

为什么Git的提交历史不能是一条干净的直线? Git有一种称为rebase的操作

rebase操作可以把本地未push的分叉提交历史整理成直线; rebase的目的是使得我们在查看历史提交的变化时更容易,因为分叉的提交需要三方对比。

假设当前结构是:

main:  A --- B
                \
dev:             C --- D

# 现在main有了新的提交
main:  A --- B --- E --- F
                \
dev:             C --- D

# 你想让dev 接着main 

git switch devgit rebase main,结果就变成了

main:  A --- B --- E --- F
                          \
dev:                      C' --- D'

C和D就被复制成C‘和D’,有了新的commit id

7. 标签管理

发布软件或者库时,通常可以在版本库中打一个标签tag,将来取某个标签的版本,就是把那个打标签时刻的历史版本取出来,标签可以作为版本库的一个快照

Git的标签虽然是版本库的快照,但是其实他是某个commit的指针,但是分支的commit可以移动,标签是不能移动的,就像插个旗子在某个点,所以标签的创建和删除都是瞬间完成的

# 切换到分支
git switch dev

# 给分支打标签
git tag v1.0

使用命令查看git log标签 b0aea18 (HEAD -> main, tag: v1.0, origin/main) docs(update basic.md)

如果要对某一历史的commit id搭上标签,也可以使用commit id打标签,git tag v0.9 f52c633 ,同样可以创建带说明的标签

git tag -a v0.1 -m "version 0.1 released" 1094adb
  • a 指定标签名
  • m 指定说明文字

7.1 操作标签

标签打错了,可以删除

git tag -d v 0.1

创建的标签都只存储在本地,不会自动推送到远程,就像commit id一样,还是需要推送到远程,git push origin v1.0 ,或者一次性推送全部尚未推送到远程的本地标签 git push origin --tags

从远程删除标签,命令也是push

git push origin :refs/tags/v0.9

8. Github使用

学完了上述的git全部顶层逻辑和命令,你现在应该学会了常规流程

  1. git init 初始化当前目录
  2. git add . 把本地文件添加到本地仓库.git
  3. git commit -m "message" 把.git提交,形成目录和文件,commit树
  4. git tag 给当前本地库打标签
  5. git switch -c dev 创建dev分支并切换
  6. git pull 拉取远程仓库
  7. git merge dev 合并dev的分支到main主支
  8. git push 推送到远端仓库
  9. git log/reflog 查看commit历史
  10. git remote 设计远端仓库
  11. git resotre/git reset 修改回退

现在你已经了解GitHub了,强大的开源才是git的用武之地

如何参加一个项目呢?首先要访问它的项目主页,点击fork的图标,在自己账户下创建一个仓库的克隆 git fork

创建名称,增加描述,创建分支的名称 git fork2

如果你想修复克隆仓库的bug,或者增加一个新功能,推送到自己的github;希望你fork的仓库接受你的修改,你可以在github上发起pull requeset

git fork

pull request

8.1 忽略文件

有些时候,你必须把某些文件放到Git工作目录中,但又不能提交它们,比如保存了数据库密码的配置文件

在Git工作区的根目录下创建一个特殊的.gitignore文件,然后把要忽略的文件名填进去,Git就会自动忽略这些文件。

Note

.gitignore文件本身应该提交给Git管理,这样可以确保所有人在同一项目下都使用相同的.gitignore文件。

8.2 搭建Git服务器

远程仓库实际上和本地仓库没啥不同,就是不会中断,一直开机。GitHub就是一个免费托管开源代码的远程仓库。但是对于某些视源代码如生命的商业公司来说,既不想公开源代码,又舍不得给GitHub交保护费,那就只能自己搭建一台Git服务器作为私有仓库使用。

第一步 安装git

sudo apt install git

第二步 创建git用户,用来运行git服务

sudo adduser git

第三步 创建证书登陆

收集所有需要登录的用户的公钥,就是他们自己的id_rsa.pub文件,,把所有公钥导入到 ~/git/.ssh/authorized_keys文件中,一行一个

第四步 初始化Git仓库

先选定一个目录作为Git仓库,在目录下输入命令

sudo git init --bare data.git

Git就会创建一个裸仓库,裸仓库没有工作区,因为服务器上的Git仓库纯粹是为了共享,所以不让用户直接登录到服务器上去改工作区,并且服务器上的Git仓库通常都以.git结尾。然后,把owner改为git,sudo chown -R git:git sample.git.

第五步 禁用shell登陆

处于安全考虑,git用户不允许登陆shell,通过/etc/passwd文件进行修改

如果是git:x:1001:1001:,,,:/home/git:/bin/bash要改成 git:x:1001:1001:,,,:/home/git:/usr/bin/git-shell仅仅允许我们为git用户指定的git-shell每次一登录就自动退出。

第六步 克隆远程仓库

使用命令克隆到本地git clone git@server:/srv/sample.git

  • git
  • server 是服务器
  • 存放git的文件夹

全部命令

目的 命令
日常操作
初始化仓库 git init
克隆仓库 git clone <url>
查看状态 git status
添加到暂存区 git add <file>
添加所有 git add .
提交 git commit -m "message"
提交所有已跟踪修改 git commit -am "message"
查看历史 git log
图形化历史 git log --oneline --graph --all
查看差异(未暂存) git diff
查看差异(已暂存) git diff --cached
分支与合并
创建分支 git branch <name>
切换分支 git switch <name>
创建并切换分支 git switch -c <name>
删除分支 git branch -d <name>
强制删除分支 git branch -D <name>
合并分支 git merge <branch>
squash 合并 git merge --squash <branch>
变基 git rebase <branch>
交互式变基 git rebase -i HEAD~n
取消变基 git rebase --abort
远程协作
添加远程 git remote add origin <url>
查看远程 git remote -v
删除远程 git remote remove origin
推送 git push origin <branch>
推送并设置上游 git push -u origin <branch>
强制推送 git push --force-with-lease
拉取(fetch + merge) git pull
只 fetch git fetch
pull 时 rebase git pull --rebase
查看远程分支状态 git remote show origin
stash(临时保存)
暂存当前修改 git stash
暂存并加备注 git stash push -m "message"
查看 stash git stash list
应用 stash git stash apply
应用并删除 stash git stash pop
删除 stash git stash drop
清空 stash git stash clear
回滚/修复
软重置(保留工作区) git reset --soft HEAD~1
混合重置(默认) git reset HEAD~1
硬重置(丢弃修改) git reset --hard HEAD~1
恢复文件到最新提交 git restore <file>
从暂存区移除(保留修改) git restore --staged <file>
查看 reflog git reflog
回退到某个提交(保留修改) git reset --soft <commit>
回退到某个提交(丢弃修改) git reset --hard <commit>
选择性提交 cherry-pick git cherry-pick <commit>
查找引入 bug 的提交 git bisect start / git bisect good / git bisect bad / git bisect reset
标签(版本管理)
查看标签 git tag
创建标签 git tag v1.0.0
注释标签 git tag -a v1.0.0 -m "message"
推送标签 git push origin --tags
删除本地标签 git tag -d v1.0.0
删除远程标签 git push origin :refs/tags/v1.0.0
子模块
添加子模块 git submodule add <url> <path>
初始化子模块 git submodule init
更新子模块 git submodule update
克隆带子模块 git clone --recurse-submodules <url>
高级操作(更少用但很重要)
查看对象类型 git cat-file -t <hash>
查看对象内容 git cat-file -p <hash>
查看当前 HEAD git rev-parse HEAD
查看某分支指向 git rev-parse <branch>
生成 patch 文件 git format-patch <base>
应用 patch git am <patch>
重新写历史(危险) git filter-branch
新版替代命令(更快) git filter-repo
绑定多个工作树 git worktree add ../path <branch>
删除工作树 git worktree remove ../path
创建 bundle(离线备份/迁移) git bundle create repo.bundle --all
克隆 bundle git clone repo.bundle <dir>