Git
本文内容基于 The Missing Semester of Your CS Education - 版本控制(Git) 改编
Git 相关学习资料看这里
一、理论知识
版本控制系统 (VCSs)
- Version Control Systems
- 用于追踪源代码(或其他文件、文件夹)改动的工具
- 管理代码的修改历史
- 让协作编码变得更方便
- VCS
- 通过一系列的快照将某个文件夹及其内容保存了起来
- 每个快照都包含了文件或文件夹的完整状态
- 同时它还维护了快照创建者的信息以及每个快照的相关信息
关于版本控制: https://git-scm.com/book/zh/v2/起步-关于版本控制
Git
Git 是什么?
直接记录快照,而非差异比较
Wikipedia :Git (/ɡɪt/) is free and open source software for distributed version control
- 很多时候只能死记硬背一些命令行,然后像使用魔法一样使用它们。
- 一旦出现问题,就只能像这幅漫画里说的那样去处理了😅
- 毕竟只知道硬背命令,不知道所处的情况,遇到报错最简单的处理方式莫过于保存现在的工作,然后删掉混乱的仓库,重新克隆仓库,再粘回去
Git 的数据模型
快照
- Git 将顶级目录中的文件和文件夹作为集合,并通过一系列快照来管理其历史记录。
- 文件被称作Blob对象(数据对象),也就是一组数据。
- 目录则被称之为 tree,它将名字与 Blob 对象或树对象进行映射(使得目录中可以包含其他目录)。
- 快照则是被追踪的最顶层的树。
1 2 3 4 5 6 7
| <root> (tree) | +- foo (tree) | | | + bar.txt (blob, contents = "hello world") | +- baz.txt (blob, contents = "git is wonderful")
|
历史记录建模:关联快照
- 有向无环图
- Git 中的每个快照都有一系列的“父辈”,也就是其之前的一系列快照。
- 注意,快照具有多个“父辈”而非一个,因为某个快照可能由多个父辈而来。
可视化展示一下:
1 2 3 4
| o <-- o <-- o <-- o ^ \ --- o <-- o
|
- o 表示一次提交(快照)
- 箭头指向当前提交的父辈
- 第三次提交之后,历史记录分岔成了两条独立的分支
- 可能因为此时需要同时开发两个不同的特性,他们之间是相互独立的
1 2 3 4
| o <-- o <-- o <-- o <---- o ^ / \ v --- o <-- o
|
- 开发完成后,这些分支可能会被合并并创建一个新的提交,这个新的提交会同时包含这些特性。
数据模型的伪代码表示
1 2 3 4 5 6 7 8 9 10 11 12 13
| type blob = array<byte>
type tree = map<string, tree | blob>
type commit = struct { parent: array<commit> author: string message: string snapshot: tree }
|
对象和内存寻址
1 2 3 4 5 6 7 8 9 10 11 12 13
| type object = blob | tree | commit
objects = map<string, object>
def store(object): id = sha1(object) objects[id] = object
def load(id): return objects[id]
|
例如上面例子中的树:
1 2 3 4 5 6 7
| # 698281bc680d1995c5f4caaf3359721a5a58d48d 是顶层树的哈希值 ❯ git cat-file -p 698281bc680d1995c5f4caaf3359721a5a58d48d 100644 blob 4448adbf7ecd394f42ae135bbeed9676e894af85 baz.txt 040000 tree c68d233a33c5c06e0340e4c224f0afca87c8ce87 foo # 可以通过哈希读取baz.txt文件的内容 ❯ git cat-file -p 4448adbf7ecd394f42ae135bbeed9676e894af85 git is wonderful
|
引用
- 所有的快照可通过哈希值标记,但是40位的16进制字符串难以记忆
- 给这些哈希值赋予人类可读的名字,也就是引用(references)
- 引用是指向提交的指针
- 引用可以被更新,指向新的提交
- master 引用通常会指向主分支的最新一次提交
- 当前的位置有一个特殊的索引: “HEAD”
1 2 3 4 5 6 7 8 9 10 11 12 13
| references = map<string, string>
def update_reference(name, id): references[name] = id
def read_reference(name): return references[name]
def load_reference(name_or_id): if name_or_id in references: return load(references[name_or_id]) else: return load(name_or_id)
|
仓库
- 粗略的Git仓库定义:对象 和 引用
- 在硬盘上,Git 仅存储对象和引用
版本库、工作区、暂存区
版本库、工作区与暂存区
我们把文件往Git版本库里添加的时候,是分两步执行的:
- 第一步是用
git add
把文件添加进去,实际上就是把文件修改添加到暂存区;
- 第二步是用
git commit
提交更改,实际上就是把暂存区的所有内容提交到当前分支。
协议
服务器上的Git - 协议
- 本地协议(Local protocol): 其中的远程版本库就是同一主机上的另一个目录。
git clone /srv/git/project.git
- HTTP 协议:
git clone https://github.com/git/git.git
- SSH 协议:
git clone ssh://[user@]server/project.git
- Git 协议:
git clone git@github.com:git/git.git
📌 Remove my password from list so hackers won't be able to hack me
查看讨论: https://github.com/danielmiessler/SecLists/pull/155
二、基本操作
基础命令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| $ git --version $ git help <command> $ git config --global user.name ex7l0it && git config --global user.email abc@abc.com $ git config --global core.editor vim $ git config --global core.pager ''
$ git init $ git add . $ git status $ git commit -m "first commit" $ git log --all --graph --decorate --oneline $ git diff $ git restore baz.txt $ git reset --hard/--soft/--mixed HEAD^ $ git branch dev $ git switch dev $ git merge dev
$ git clone <url> $ git remote add origin <url> $ git push origin master $ git pull $ git fetch $ git merge
|
git config
相关
- 通过
git config --local
进行的配置将保存在当前 git 仓库的 .git/config
文件中
- 通过
git config --global
进行的配置将保存在当前用户家目录的 .gitconfig
文件中
第一次提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| cd /tmp mkdir git_learning cd git_learning git init # 初始化git仓库 ls -al mkdir foo echo "hello world" > foo/bar.txt echo "git is wonderful" > baz.txt echo "# README\n" > README.md git status git add . # 将当前目录下所有文件加入暂存区 git status git commit -m "first commit" git status git log
|
创建一个分支
1 2 3 4 5 6 7 8 9
| git switch -c dev git branch echo "#/usr/bin/python3\nprint('Hello world')" > hello.py echo "add hello.py" >> README.md git add hello.py git commit -m "dev 0.1" git status -s # -s 以简短的形式输出 git commit -am "dev 0.1.1" # 自动把所有已经跟踪过的文件暂存起来一并提交 git status
|
📌 关于 git checkout
与 git switch
和 git restore
git checkout
这个命令既被用来切换分支,又被用来恢复工作区文件,对用户的认知造成了较大的困惑。Git自2.23版本开始引入两个新的命令: git switch
和 git restore
,用来替代 git checkout
git switch
: 负责分支管理
git switch <branch_name>
切换至指定分支
git switch -c <branch_name>
创建新的分支并切换至该分支
git restore
: 负责文件恢复
合并分支
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| # 正常合并不出错的情况: git switch master echo test > foo/a.txt git add foo/a.txt git commit -m "add a.txt" git log --all --graph git merge dev git log --graph
# 合并分支时出现冲突的情况: echo "name=input('your name: ')\nprint('hello, {}'.format(name))" > hello.py git add hello.py git commit -m "update hello.py" git switch dev echo "name=input('your name: ')\nprint('hello, {}'.format(name))" >> hello.py git commit -am "dev 0.2: update hello.py" git switch master git merge dev git status vim hello.py # 修复冲突 git commit -a / git merge --continue
|
查看差异
1 2 3 4
| echo checkofdiff > baz.txt git diff # 查看工作区/暂存区的差异 git add baz.txt git diff --staged # 查看暂存区/上一次commit的差异
|
回退
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| # 尚未添加到暂存区时 git status echo junkdata >> baz.txt git diff git restore baz.txt # 取回暂存区的版本到工作区 cat baz.txt
# 修改内容已添加到暂存区时 echo junkdata >> baz.txt git add baz.txt git reset HEAD baz.txt # 暂存区的修改撤销,移回到工作区 git diff git restore baz.txt # 再取回暂存区版本到工作区,彻底放弃暂存区的修改
# 修改内容已经提交到了仓库时 echo junkdata >> baz.txt git commit -am "add junkdata" git reset --hard HEAD^ # 回退仓库和所有区,HEAD^表示第一个父级提交(也就是最近的一次提交) --soft HEAD^ # 仅回退仓库 --mixed HEAD^ # 回退仓库和暂存区
git reset --hard <commit id> # 回退的指定的某个提交状态,之后的记录全部删除
|
远程仓库
1 2 3 4 5 6 7 8
| git remote add origin git@github.com:ex7l0it/git_learning.git git push -u origin main
cd /tmp git clone https://github.com/ex7l0it/git_learning.git remote_git_learning cd remote_git_learning git fetch # 将远程主机的最新内容拉到本 git pull # 将远程主机的最新内容拉下来后直接合并, 等于 git fetch + git merge
|
Github相关
三、更多命令及应用场景
1. 当你已经更改了很多内容,突然发现需要把修改的内容移动到新分支
1 2 3
| git stash # 保存当前工作区修改的内容 git switch -c dev # 创建并切换到新分支 git stash pop # 将暂存的修改内容还原到新分支上
|
git stash
相关命令:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| # 保存工作现场 git stash git stash save # 加不加 save 都一样的效果
# 将最新的 stash 内容应用到当前工作区,并从列表中移除 git stash pop # 查看所有保存的 stash 列表 git stash list # 将最新的 stash 内容应用到当前工作区,但不从列表中移除 git stash apply # 删除掉最新的 stash git stash drop # 删除掉列表中所有的 stash git stash clear
|
TODO