git的学习与使用
git Learning
1.背景
Git是什么?
Git是世界上最先进的分布式版本控制系统,由Linus Torvalds编写,用于高效处理从小型到大型项目的版本管理。Git最初设计用于Linux内核开发,现已成为开源和商业软件开发中最流行的版本控制系统。
版本控制系统:又叫修订控制或源控制系统,是一种软件实用程序,用于跟踪和管理对文件系统的更改。VCS 还提供协作实用程序,用于与其他 VCS 用户共享和集成这些对文件系统的更改。
Linus花了两周时间自己用C写了一个分布式版本控制系统,这就是Git!一个月之内,Linux系统的源码已经由Git管理了!
Git迅速成为最流行的分布式版本控制系统,尤其是2008年,GitHub网站上线了,它为开源项目免费提供Git存储,无数开源项目开始迁移至GitHub,包括jQuery,PHP,Ruby等等。
与分布式系统对应的就是集中式版本控制系统
集中式版本控制系统:集中式版本控制系统(CVCS)使用单一的中央服务器来存储所有文件的所有版本。用户从中央服务器检出文件的最新版本进行工作,并在完成后将更改提交回中央服务器。
集中式版本控制系统的缺点:
- 单点故障:如果中央服务器出现故障,整个团队都无法进行版本控制操作。
- 网络依赖:用户必须连接到中央服务器才能进行检出、提交等操作。
- 性能瓶颈:随着团队规模的增大,中央服务器的性能可能成为瓶颈。
- 数据安全性:所有数据都存储在中央服务器上,一旦服务器被攻击或损坏,数据可能丢失。
分布式版本控制系统根本没有“中央服务器”,每个人的电脑上都是一个完整的版本库
在实际使用分布式版本控制系统的时候,其实很少在两人之间的电脑上推送版本库的修改,通常也有一台充当“中央服务器”的电脑,但这个服务器的作用仅仅是用来方便“交换”大家的修改,没有它大家也一样干活,只是交换修改不方便而已。
2. 安装Git
最早Git是在Linux上开发的,很长一段时间内,Git也只能在Linux和Unix系统上跑。不过,现在Git可以在Linux、Unix、Mac和Windows这几大平台上正常运行了。
2.1 在Linux上安装Git
在大多数Linux发行版上,尝试输入git,如果提醒系统没有安装Git,就可以通过包管理器安装它。
# ubuntu / debian
sudo apt install git2.2 在MacOS上安装Git
使用homebrew安装
homebrew:MacOS 软件管理的利器
brew install git2.3 在Windows上安装Git
- 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
- 本地安装
进入git官网 git-scm.com,下载Windows版本的安装包,双击安装即可。
3. 基本配置
安装完成后,建议进行一些基本配置,比如用户和邮箱
# 设置用户名
git config --global user.name "Your Name"
# 设置用户邮箱
git config --global user.email "因为Git是分布式版本控制系统,所以,每个机器都必须自报家门:你的名字和Email地址,有冒充的也是有办法可查的。
如果没有配置用户名和邮箱,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 | 标签 |
先暂时了解,后续学习之后就能完全了解这些概念
4.2 把文件添加到版本库
所有的版本控制系统,只能追踪文本文件的改动,二进制文件是无法追踪的,比如图片、视频、音频等文件,Git只能把它们当作一个整体来管理,无法追踪它们的改动内容。
图片、视频这些二进制文件,虽然也能由版本控制系统管理,但没法跟踪文件的变化,只能把二进制文件每次改动串起来
Microsoft的Word格式是二进制格式,因此,版本控制系统是没法跟踪Word文件的改动的
文本是由编码的,中文有GBK编码,日文有Shift_JIS编码,如果没有历史遗留问题,强烈建议使用标准的UTF-8编码,所有语言使用同一种编码,既没有冲突,又被所有平台所支持。
使用Windows的童鞋要特别注意:千万不要使用Windows自带的记事本编辑任何文本文件,它把每个文件开头添加了0xefbbbf(十六进制)的字符
在gitlearn目录中,创建一个名为hello.txt的文本文件,内容如下:
Hello, Git!
第一步,用命令git add告诉git,将文件添加到仓库
git add hello.txtgit add 命令并不会立即把文件添加到版本库中,而是把文件添加到一个叫做“暂存区”(Staging Area)的地方,只有当你执行 git commit 命令时,文件才会真正被添加到版本库中。这个暂存区就是上面的index
暂存区给了你后悔的机会,理解成草稿纸就可以了,减少代码提交的混乱,提高安全性
当使用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命令查看当前状态:

上图告诉我们,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.textMyers Diff Algorithm(经典算法): 它能找出两个文本之间的最短编辑序列(最小操作数),并输出插入/删除/修改的行。
-表示删除+表示新增@@表示行号范围
- Blob的存储是按SHA-1或SHA-256哈希存储的,文件内容相同–哈希相同–只存一份(多次提交同一份内容,git只存一次);
- 压缩packfile,当仓库赠长时,git会把对象打包成packfile
- 相似的文件只存差异,比如一个文件有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指针和暂存区,工作区保持不变(默认选项)
如果要回退到最开始的版本,你可以找到之前的commit哈希值,然后使用git reset --hard <commit-hash>命令进行回退。
Git的版本回退速度非常快,因为Git在内部有个指向当前版本的HEAD指针,当你回退版本的时候,git仅仅是把HEAD指针改向
.Git也被称为版本库Repository,版本库里存了很多东西,其中最重要的就是称为stage(或者叫index)的暂存区,还有Git为我们自动创建的第一个分支master,以及指向master的一个指针叫HEAD。

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命令。
从来没有被添加到版本库就被删除的文件,是无法恢复的!
5. 远程仓库
Git是分布式版本控制系统,每个人的电脑上都是一个完整的版本库,但是在实际使用中,通常会有一台服务器充当“中央服务器”,方便大家交换修改。同一个Git仓库不同电脑,同一电脑不同目录都可以git。
找一台电脑24小时开机,其他人都能从这个服务器仓库克隆一份到自己电脑上,之后修改后把各自的提交推送到服务器仓库里。完全可以自己搭建一台运行git的服务器,现在阶段为了学习git搭建服务器其实脱离主流了,现在的主流就是github,gitlab,bitbucket这些网站,都是提供git远程仓库服务的网站。
5.1 注册github
登陆github官网,进行常规注册

5.2 创建SSH Key
2021年之前Github已经停用了HTTPS协议的密码认证方式,改成了SSH Key认证方式,SSH不是加密Git
加密本地和Github之间的通信通道
SSH用的是公钥和私钥对,你本地有一对钥匙
🔑 私钥(private key) → 只在你电脑
🗝 公钥(public key) → 上传到 GitHub
第一步:创建SSH key
在用户目录下,查看.ssh目录,如果有,再看看这个目录下有没有id_rsa和id_rsa.pub文件,如果没有,就创建一对新的SSH key
Github当前之前的算法有
- ed25519 (推荐)
- rsa
# 生成ed25519算法的SSH key
ssh-keygen -t ed25519 -C "@your_email.com"如果提示ssh-keygen命令不存在,可以先安装OpenSSH工具包,然后再执行上面的命令。一路输入Enter就好,直到创建好,出现了id_ed25519和id_ed25519.pub两个文件,id_rsa是私钥,不能泄露出去,id_rsa.pub是公钥,可以放心地告诉任何人。
第二步:登陆Github
从头像处进入点击设置按钮,进入个人设置界面,点击SSH和GPG keys,进入SSH Key管理界面,点击New SSH key按钮,添加新的SSH Key

在Title输入框中,输入这个Key的名称,比如My Laptop,然后打开id_rsa.pub文件,把里面的内容全部复制到Key输入框中,最后点击Add SSH key按钮,完成SSH Key的添加。
记得把id_ed25519.pub中的全部内容复制进去
5.3 远程仓库与本地仓库交互
现在的情况是,你在本地使用git init创建了本地仓库,现在想托管在github上,方便随时随地访问和备份。这样Github上仓库既可以作为备份,又可以让其他人通过该仓库来协作。
找到New按钮,创建一个新的远程仓库,进入页面之后,按需创建即可

- 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这个名字一看就知道是远程库。
要把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)
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 branchgit 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动这种情况就得另一方式了
现在使用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用<<<<<<<,=======,>>>>>>>标记出不同分支的内容
当Git无法自动合并分支时,就必须首先解决冲突。解决冲突后,再提交,合并完成。
分支策略
首先main是像游戏主线一样的,保持稳定,防止项目出错之后无法挽回,一般不允许直接修改;修改呢,一般都在dev上,dev是不稳定的,主要是发布的时候将dev分支合并到master上;dev分支也可以都有自己的分支

使用
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
- 确定在哪个分支上修复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- 结束后,工作区干净了,刚才保存的现场需要找出来
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)
- 上一步在
main上修改了文件,你的dev也是为了解决main的问题,所以dev其实也要同步main的修改,要不就是把改的内容复制到dev上去,为了方便操作,git还专门提供了cherry-pick命令,让我们复制一个特定的提交到当前分支
git cherry-pick 4c805e2,因为这两个commit只是改动相同,但确实是两个不同的commit。
6.5 多人协作
之前学习了分支的创建和冲突解决,这些都是多人协作的基础,多人协作是git另一个灵魂。
origin表示源头的意思
# 查看远程库的信息
git remote
# 查看远程库的详细信息
git remote -V推送分支
推送分支,就是把该分支上的本地提交推送到远程库
本地 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上开发,就必须创建远程origin的dev分支到本地
# 创建并切换
git stiwch -c dev origin/dev
# 另一种写法
git checkout -c dev origin/devgit 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 dev和git 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.98. Github使用
学完了上述的git全部顶层逻辑和命令,你现在应该学会了常规流程
git init初始化当前目录git add .把本地文件添加到本地仓库.gitgit commit -m "message"把.git提交,形成目录和文件,commit树git tag给当前本地库打标签git switch -c dev创建dev分支并切换git pull拉取远程仓库git merge dev合并dev的分支到main主支git push推送到远端仓库git log/reflog查看commit历史git remote设计远端仓库git resotre/git reset修改回退
现在你已经了解GitHub了,强大的开源才是git的用武之地
如何参加一个项目呢?首先要访问它的项目主页,点击fork的图标,在自己账户下创建一个仓库的克隆 
创建名称,增加描述,创建分支的名称 
如果你想修复克隆仓库的bug,或者增加一个新功能,推送到自己的github;希望你fork的仓库接受你的修改,你可以在github上发起pull requeset

8.1 忽略文件
有些时候,你必须把某些文件放到Git工作目录中,但又不能提交它们,比如保存了数据库密码的配置文件
在Git工作区的根目录下创建一个特殊的.gitignore文件,然后把要忽略的文件名填进去,Git就会自动忽略这些文件。
.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.gitGit就会创建一个裸仓库,裸仓库没有工作区,因为服务器上的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> |