Skip to content

Commit

Permalink
Git Tutorial (#59)
Browse files Browse the repository at this point in the history
* fix: add venv/ to .gitignore

* add: git-tourial

* update: update index

* fix: fix index

* update: update git tutorial

* style: add space to git-tutorial.md

* fix typo

* update git tutorial

* fix typo

---------

Co-authored-by: Shiroki Satsuki <[email protected]>
  • Loading branch information
SoraShu and shirok1 authored May 6, 2024
1 parent bfb986d commit 4f35046
Show file tree
Hide file tree
Showing 3 changed files with 264 additions and 8 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,7 @@ site/
# autocorrect need node involved on Windows
node_modules
yarn.lock
package.json
package.json

# pyhon venv
venv/
253 changes: 253 additions & 0 deletions docs/guide-for-beginner/git-tutorial.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
# Git 教程

<!-- > 本教程有别于其他 Git 教程,它专注于常见的工程流程。若您需要更多的 Git 基础知识,请参考 [Pro Git](https://git-scm.com/book/zh/v2)。 -->

## 初识 Git

Git 是一个分布式版本控制系统,用于跟踪文件的变化并协作开发。它是由 Linus Torvalds 于 2005 年创建的。

在 Windows 上,可以通过 [Git for Windows](https://gitforwindows.org/) 安装 Git,在 Linux 上,可以通过包管理器安装 Git,例如:

```bash
# Debian/Ubuntu
sudo apt install git
```

在 MacOS 上,可以通过 [Homebrew](https://brew.sh/) 安装 Git,或者使用 Xcode 自带的 Git。

对于刚下载的 Git,需要进行一些配置:

```bash
git config --global user.name "Your Name"
git config --global user.email "[email protected]"
```

这里的 `--global` 选项表示这些配置将应用于所有的 Git 仓库。若要对特定仓库进行配置,可以去掉 `--global` 选项。

## Git 基础概念

### 仓库

Git 的管理是基于仓库(Repository)的,仓库可以看成一个储存了 Git 元信息的目录。可以在需要进行版本控制的目录下执行 `git init` 命令,将其初始化为一个 Git 仓库。

### 工作区与暂存区

一个文件在 Git 中有三种状态:已提交(committed)、已修改(modified)和已暂存(staged),这三种状态对应 Git 三个工作区域的概念:

- 工作区(Working Directory):即我们的工作目录,我们对文件的修改都发生在这里。
- 暂存区(Staging Area):即我们的缓存区,我们可以通过 `git add` 命令将工作区的文件(或文件的变动)添加到暂存区。暂存区存储了我们对文件的修改。
- 仓库(Repository):即我们的版本库,我们可以通过 `git commit` 命令将暂存区的文件提交到仓库。

值得注意的是,由于我们对文件的编辑都发生在工作目录下,所以我们增删文件或对文件进行修改,都只会影响工作区,而不会影响暂存区和仓库。所以我们可以很容易地撤销在工作区发生的修改,这也是 Git 的优势之一。 我们真正想要在仓库中修改一个文件,实际上需要经历三个步骤:

1. 在工作目录中修改文件。
2. 使用 `git add` 将修改的文件添加到暂存区。
3. 使用 `git commit` 将暂存区的文件提交到仓库。

### Commit 与 HEAD 与 Reset

在上一小结的内容中,我们将文件提交到仓库的操作是 `git commit`,也就是说我们对仓库的每一次变动都会产生一次 Commit。
Commit 是 Git 中最小的版本单元,它包含了一个快照(snapshot)和一个指向该快照的指针。每次 Commit 都会生成一个唯一的 SHA-1 值,这个值可以作为这个 Commit 的标识。

我们可以将目前的 Git 仓库想象为一个链状结构,每个 Commit 都是一个节点,每个节点都有一个指向父节点的指针。这样就形成了一个有向无环图。当前的 Commit 节点通过一个名为 `HEAD` 的指针指出。通过这样的树状结构,我们可以很方便地查看并回溯历史记录。

回溯历史记录的方法为使用 `git reset` 命令,它有三种模式:

- `--soft`:仅仅将 `HEAD` 指针移动到指定的 Commit,不改变暂存区和工作区。
- `--mixed`:将 `HEAD` 指针移动到指定的 Commit,同时重置暂存区,但不改变工作区。这是默认的模式。
- `--hard`:将 `HEAD` 指针移动到指定的 Commit,同时重置暂存区和工作区。

实际使用方法如下所示:

```bash
# 根据 Commit ID
git reset --mixed COMMITID
# 用当前位置进行标识
# 如回到上上个 Commit
git reset HEAD~2
```

我们可以看见,只有 `--hard` 模式会改变工作区。如果我们希望回溯历史记录时同时改变工作区,记得加上 `--hard` 选项。但这同时也会删除工作区的修改,所以当你想要回溯历史记录时,记得先将工作区的修改 `commit` 到仓库,这也是我们说的保持工作区干净的原因。

当你想要回到原来的位置时,也很简单,例如使用分支名:

```bash
git reset --hard master
```

对于分支的详细介绍将会在下一小节。

### 分支与切换

分支实际上与 HEAD 指针类似,它也指向一个 Commit。可以说分支的存在是为了更简单地在不同的 Commit 之间切换,在 Git 为基础的协同开发中,分支一般代表不同的开发任务与进度。例如在开发一个新功能时,我们在 `master` 分支上新建一个 `feature-a` 分支,然后在 `feature-a` 分支上进行开发,这时开发这个 `feature-a` 的人可以在不影响 `master` 分支的情况下进行开发,并且可以随时切换回 `master` 分支。

下图就是一个分支的示意图,由于需要处理 issue53 所以在 master 分支上新建了一个 `iss53` 分支并且有 C3 这一个 Commit;同时,由于需要进行线上的热修复,所以在 master 分支上新建了一个 `hotfix` 分支,并且有 C5 这一个 Commit。

![分支示意图](https://github.com/hitszosa/wiki/assets/73573254/7f56ddea-e0d4-4ad8-a29f-383f7df5aa63)

上述的修改可以用如下操作完成。

```bash
# 新建 iss53 分支处理 issue53
git checkout -b iss53
# 进行开发并提交到暂存区
some-development xxx
# 提交 C3
git commit

# 回到 master 分支
git checkout master
# 新建 hotfix 处理热修复
git checkout -b hotfix
# 进行热修复并提交到暂存区
some-fix
# 提交 C4
git commit
```

在 Git 中,分支的切换是通过 `git checkout` 命令实现的,由于分支本身只是一个指针,所以也可以使用 `git checkout commit-id` 来切换到某个 Commit,这样我们就可以在不同的 Commit 之间切换。


事实上,`git checkout` 命令还可以作用于文件,所以在新版本的 Git 中,切换分支有一个新的命令 `git switch`,它只能作用于分支。可以通过 `git switch --help` 查看相关信息,这里先按下不表。

### Merge 与 Rebase

这里可以算是 Git 中最复杂的部分了。许多大型项目进行一次 Rebase 可能要花费数小时,甚至数天的时间。

延续上述场景,当 `hotfix` 紧急修复完成后,我们需要将 `hotfix` 分支上完成的工作合并到 `master` 分支上。我们使用 `git merge` 命令来合并分支:

```bash
# 切换到 master 分支
git checkout master
# 合并 hotfix 分支
git merge hotfix
```

这样就能将 `hotfix` 分支上的工作合并到 `master` 分支上。但这样的合并会产生一个新的 Commit,这个 Commit 会有两个父节点,一个是 `master` 分支的 Commit,一个是 `hotfix` 分支的 Commit。这样的合并方式叫做合并提交(Merge Commit)。

不过我们可以发现,此次合并实际上并不需要产生新的 Commit,只需要将 `master` 的指针移动到 `hotfix` 指向的位置。这是因为 `hotfix` 分支是 `master` 分支的直接后继,Git 对这种情况会特殊处理,称为 Fast-forward 合并。合并效果如图所示:

![image](https://github.com/hitszosa/wiki/assets/73573254/d4b7e1a4-5057-4d0d-84e3-c828105c657f)

`iss53` 分支进行开发,产生多次提交之后也需要合入 `master` 分支,合并效果如图:

![image](https://github.com/hitszosa/wiki/assets/73573254/d6d2b259-a65c-48a4-9b1c-2cbb73a5dae0)

![image](https://github.com/hitszosa/wiki/assets/73573254/e32fda27-e39d-44f1-8a4b-c52ae1411bf1)

另一种合并方式是 Rebase,Rebase 会将 `iss53` 分支上的 Commit 逐个应用到 `master` 分支上,这样的合并方式会产生新的 Commit,但这些 Commit 不会有两个父节点,这样的合并方式叫做变基(Rebase)。

```bash
# 切换到 master 分支
git checkout master
# 变基 iss53 分支
git rebase iss53
```

Rebase 的好处是可以保持 Commit 的线性,这样的 Commit 更容易理解,但 Rebase 也有一个缺点,那就是 Rebase 会改变 Commit 的 SHA-1 值,如上述操作可以说 Rebase 是逐个将修改应用到 master 分支上,这样会重写 master 分支的历史记录,于是当进行多人协作时,就会发现两个人的 master 分支不一致。所以在协作中多使用 Merge 合并。不过也有另一种操作,即先 Rebase 后 Merge,这样可以保持 Commit 的线性,又不会改变历史记录。

```bash
# 在 iss53 分支上变基
git rebase master
# 切换到 master 分支
git checkout master
# 合并 iss53 分支
git merge iss53
```

由于 Rebase 后的 `iss53``master` 的直接后继,所以这里会使用 Fast-forward 合并。

在合并之后,若不再需要 `feature-a` 分支,可以使用 `git branch -d feature-a` 删除分支。

### 远程仓库

在多人协作中,通常会有一个远程仓库,这个远程仓库是所有人共享的。这里以 Gitee 为例,Gitee 是一个提供 Git 仓库托管服务的网站。远程仓库有三种协议:HTTP、SSH 和 Git,为了方便起见,我们这里使用 SSH 协议。

首先需要正确配置 SSH 密钥。可以通过 `ssh-keygen` 命令生成 SSH 密钥:

```bash
ssh-keygen -t rsa -b 4096 -C "comment"
```

这里的 "comment" 是注释,用来标识这个密钥,帮助你区分不同的密钥。这次生成的密钥会保存在 `$HOME/.ssh/id_rsa``$HOME/.ssh/id_rsa.pub` 文件中(`$HOME$` 代表家目录)。`id_rsa` 是私钥,`id_rsa.pub` 是公钥。复制 `id_rsa.pub` 中的内容,然后在 Gitee 的设置中添加 SSH 密钥。为了测试是否配置成功,使用

```bash
ssh -T [email protected]
```

验证是否配置成功。若配置成功,将出现 `Hi userxxx! You've successfully authenticated, but GITEE.COM does not provide shell access.` 的提示。

我们在 Gitee 上新建一个仓库,按照仓库创建后的提示即可将本地仓库与远程仓库关联:

```bash
# 关联远程仓库 使用 SSH 协议
git remote add origin [email protected]:userxxx/test-project.git
git push -u origin master
```

这样就将本地仓库与远程仓库关联起来了。事实上,目前只是将本地仓库的 `master` 分支推送到了远程仓库的 `master` 分支上,并且 `-u` 选项表示将本地的 `master` 分支与远程的 `master` 分支关联起来,作为这个分支的默认远程分支。当后续对 `master` 分支进行修改后,可以直接使用 `git push` 命令将修改推送到远程仓库。同理可以将其他分支推送到远程仓库。

```bash
# 推送 feature-a 分支到远程仓库
git checkout feature-a
git push origin feature-a
```

当远程仓库发生了修改,可以通过 `git pull` 命令将远程仓库的修改拉取到本地仓库。

```bash
# 切换到 master 分支
git checkout master
# 拉取远程仓库的修改
git pull
```

除了将本地仓库与远程仓库关联,还可以通过 `git clone` 命令将远程仓库克隆到本地:

```bash
git clone [email protected]:userxxx/test-project.git
```

上述操作会将仓库克隆下来,变成当前目录的一个子目录。

## Git 协同开发工作流

在你了解了 Git 的基本概念后,你可以开始使用 Git 进行协同开发了。在学习 Git 基础知识时,你已经了解到 Git 稍显繁杂的操作,但你真正入门 Git 协同开发只需要掌握几个基本操作。这里推荐一种简单的基于 Feature 分支的工作流。

### 主分支只接受合并请求

在这种工作流中,主分支(`master`)只接受来自其他分支的合并,不允许直接向主分支提交代码。这样可以保证主分支代码的稳定性。

### 每个 Feature 都有一个独立的分支

在这种工作流中,每个 Feature 都有一个独立的分支,这样可以保证不同 Feature 之间的代码不会相互影响。在给项目添加新功能时,从主分支新建一个 Feature 分支,然后在这个分支上进行开发。

```bash
# 切换到 master 分支
git checkout master
# 更新 确保 master 分支是最新的
git pull
# 新建一个 Feature 分支
git checkout -b feature-a
# 进行开发
some-development
```

### 合并请求

当 Feature 开发完成后,需要将 Feature 分支合并到主分支上。在协作开发中,这种合并在某个人的本地仓库中进行并不方便审阅,我们一般在远程仓库中进行合并请求(Pull Request)。

在 Gitee 的仓库页面上点击 `Pull Request`,新建一个合并请求,源分支选择 Feature 分支,目标分支选择主分支,然后填写合并请求的标题与描述。这样就可以发起一个合并请求了。你们可以在这个合并请求中讨论代码的修改,审阅代码,最终在页面中点击合并按钮将 Feature 分支合并到主分支上。这种合并方式可以避免复杂的合并操作,并且当多个 feature 同时进行开发时也不容易产生合并冲突。在完成合并后,可以删除 Feature 分支。

## Git 技巧

这里列举几个不错的教程:

- [git - 简明指南 (中文翻译)](https://rogerdudler.github.io/git-guide/index.zh.html):篇幅较短,适合入门
- [Git 的奇技淫巧 (中文翻译)](https://github.com/521xueweihan/git-tips):一些 Git 的奇怪技巧,但有时候很有用
- [Git - Book](https://git-scm.com/book/zh/v2):Git 官方的指南,很详细,推荐阅读。

同时推荐在熟悉 Git 的基本概念后使用 Git 的图形化工具,如 VsCode 的 Git 插件、JetBrains 的 Git 集成等。

更多的 Git 使用技巧等待你在使用中发现。

14 changes: 7 additions & 7 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ theme:
- search.highlight
- content.tabs.link
palette:
- media: "(prefers-color-scheme: light)"
- media: "(prefers-color-scheme: light)"
scheme: default
toggle:
icon: material/lightbulb-outline
name: Switch to dark mode
- media: "(prefers-color-scheme: dark)"
- media: "(prefers-color-scheme: dark)"
scheme: slate
toggle:
icon: material/lightbulb
Expand All @@ -41,7 +41,7 @@ nav:
- 主页: index.md
- 关于我们: about.md
- 参与指引:
- Markdown 规范: contribute-guide/markdown.md
- Markdown 规范: contribute-guide/markdown.md
- Customized Debian:
- 基本说明与变更日志: customized-debian/principle-and-changelogs.md
- 快速上手: customized-debian/quick-start.md
Expand All @@ -54,6 +54,7 @@ nav:
- WSL 安装教程: guide-for-beginner/wsl-install-guide.md
- VirtualBox + Ubuntu 安装教程: guide-for-beginner/vbox-ubuntu-guide.md
- Windows C/C++ 开发环境配置实用指南: guide-for-beginner/practical-guide-to-cppdev-on-win.md
- Git 使用指南: guide-for-beginner/git-tutorial.md
- 科普:
- 编译,静态链接,可执行文件的生成: popular-science/compile.md
- 程序的机器级表示:
Expand Down Expand Up @@ -85,12 +86,11 @@ nav:
- Anaconda3 & Jupyter 环境配置: hpc-doc/anaconda.md
- PyTorch on SLURM: hpc-doc/pytorch.md
- 非官方实验环境指南:
- 计算机组成原理lab1: unofficial-lab-env/comp-organ-lab1.md

- 计算机组成原理lab1: unofficial-lab-env/comp-organ-lab1.md

plugins:
- search:
lang:
lang:
- en
# 选择日语来建立中文索引
- ja
Expand All @@ -113,7 +113,7 @@ markdown_extensions:
- pymdownx.tasklist:
custom_checkbox: true
- pymdownx.tabbed:
alternate_style: true
alternate_style: true
- pymdownx.tilde
- admonition
- toc:
Expand Down

0 comments on commit 4f35046

Please sign in to comment.