TheUnknownBlog

Back

Learning Hub

Back to all courses

Git Learning

5 modules · 11 lessons

You have completed the Shell training, and I believe you now have a deeper understanding of the command line! Now, we are going to learn the version control system Git.

You might ask: Isn’t a version control system just a piece of software? Why do we need to go to great lengths to learn it? In fact, version control systems play a crucial role in modern software development. By learning Git, you will master how to effectively manage codebases, handle conflicts, and collaborate with team members.

We will only cover the basics of Git. In fact, the content we teach is basically sufficient to cover all your needs from now until your sophomore year; however, Git is a very powerful and complex tool, far beyond this, and you will encounter more usage scenarios in future development. We hope that Git Dojo will allow you not to panic when you encounter problems in the future, understand how Git works, and know how to consult documentation and find solutions.

There are 5 modules, click the buttons below or the table of contents to navigate between them.

Setup

欢迎来到 Git 学习之旅的第一站!在深入学习 Git 的强大功能之前,我们需要先为你的开发环境进行基础配置。我已经让你们在课前安装好了 Git,你们在课上也略微了解了一些 Git 的基本概念。

在这个模块中,你将学习如何配置 Git 的基本设置,包括设置你的用户名和邮箱地址,这样每次提交代码时,Git 都能准确记录是谁做出了这些更改。

在这个教程中我假定你已经安装好了 Git。如果你没有安装,我推荐 Windows 用户在 WSL 中安装(以避免经典的CRLF 和 LF 换行符问题),使用你们在上一个 Shell 教程中学到的 sudo apt install git。如果是 Mac 用户,可以先安装 Homebrew 然后使用 brew install git

现在,它的目的主要是来解决你们课上在运行 git commit -m 时它报的错误,你在当时可能看到它是这样说的

Author identity unknown

*** Please tell me who you are.

Run

  git config --global user.email "you@example.com"
  git config --global user.name "Your Name"

to set your account's default identity.
Omit --global to set the identity only in this repository.
bash

对于一些学习能力强的人来说他们看到报错可能已经去问过AI来解决问题了,不过我在这里还是想再讲一下:它到底干了什么,然后顺便再提一些 git 中别的配置。

Git 的配置是非常重要的,尤其是用户信息的配置。通过 git config 命令,你可以设置许多选项,包括用户的姓名和电子邮件地址。这些信息将会被 Git 用来标识每个提交的作者。

例如,运行我课上讲的 git log 命令,你可能会看到如下类似的东西

commit 9fceb02c0ee4b1a5c0e4b1a5c0ee4b1a5c0ee4b1a5
Author: Your Name <you@example.com>
Date:   Mon Sep 28 12:00:00 2021 +0800

    Your commit message
bash

这里的 Author 字段就是从你通过 git config 设置的信息中获取的。如果你没有正确配置这些信息,Git 将无法识别你的身份,这可能会导致提交记录中缺少作者信息,影响团队协作和代码追踪。怎么配置?很简单,Git 在上面都教你了

git config --global user.name "Your Name"
git config --global user.email "you@example.com"
bash

--global 选项表示这些配置将应用于你系统上的所有 Git 仓库。如果你想为某个特定的仓库设置不同的用户信息,可以在该仓库目录下运行相同的命令,但不加 --global 选项。记得一定不要粘贴上面的命令中的 Your Nameyou@example.com,而是要替换成你自己的信息。

除了用户信息,git config 还允许你设置其他许多选项。例如,你可以配置 Git 的颜色输出,使其在终端中更易读

git config --global color.ui auto
bash

此外,Git 还支持配置别名,还记得我之前教的 Bash 别名吗?Git 也有类似的功能。刘祎禹学长在他的博客中提到了一些有用的 Git 别名,例如

  • unstage:相当于 git reset HEAD --,用于取消暂存区的更改

    git config --global alias.unstage 'reset HEAD --'
    bash
  • full-pull:相当于 git pull --recurse-submodules,用于拉取包含子模块的仓库

    git config --global alias.full-pull 'pull --recurse-submodules'
    bash

… 诸如此类的别名可以极大地提高你的工作效率,建议根据自己的习惯进行配置。

最后,所有这些配置都会被存储在一个名为 .gitconfig 的文件中,位于你的用户主目录下。你可以直接编辑这个文件来查看或修改你的配置。

你可以使用以下命令来查看当前的 Git 配置

git config --list
bash

这将显示所有当前的配置选项及其值。如果你只配置了用户名和邮箱地址,你应该会看到类似如下的输出

[user]
    name = Your Name
    email = you@example.com
bash

本章不设置挑战。作为阅读章节即可。如果你真能通过某些手段在本关正确拿到 flag 并提交,第一位正确的提交者将会获得一杯奶茶的奖励。

Local

既然你已经完成了 Git 的基础配置,现在是时候学习如何在本地环境中创建和管理你的第一个 Git 仓库了。这个模块将带你深入了解 Git 的核心工作流程,从创建仓库到追踪和提交更改。

在这里,你将学会如何将一个普通的文件夹转变为受 Git 版本控制的仓库,理解什么是暂存区以及它在 Git 工作流程中的重要作用。你将掌握如何精确地选择要提交的更改,如何编写有意义的提交信息,以及如何查看项目的当前状态。

这些本地操作技能是所有 Git 工作的基础。无论你将来是独自开发项目还是与团队协作,理解如何在本地环境中有效管理代码更改都是必不可少的。通过这个模块的学习,你将建立起对 Git 工作原理的直观理解,为后续学习远程仓库协作打下坚实的基础。

在开始使用 Git 之前,你需要先初始化一个 Git 仓库。

让我们从最基础的命令开始:git init

什么是 Git 仓库?

Git 仓库(repository)是一个包含项目所有文件和版本历史的目录。它就像一个时光机,可以让你:

  • 追踪文件的每一次修改
  • 回到任何历史版本
  • 与其他开发者协作
  • 管理不同的开发分支

每个 Git 仓库都有一个隐藏的 .git 目录,这里存储着所有的版本信息和配置。还记得吗?你可以用 ls -la 来查看隐藏文件。

git init 命令

git init 是创建新 Git 仓库的命令。它会在当前目录下创建一个新的 Git 仓库。

基本用法
# 在当前目录初始化 Git 仓库
git init

# 创建新目录并初始化 Git 仓库
git init my-project
bash

TIP 运行 git init 后,你会看到一条消息:“Initialized empty Git repository in /path/to/your/directory/.git/” 不要慌张,这只是告诉你当前目录是空的,并且已经成功初始化了一个 Git 仓库。

验证仓库初始化

初始化仓库后,让我们验证一下是否成功:

# 查看隐藏的 .git 目录
ls -la

# 检查 Git 状态
git status
bash

ls -la 命令会显示所有文件,包括隐藏文件。你应该能看到一个 .git 目录。

我们后续会详细介绍 git status 命令的输出内容,但现在你只需要知道它可以告诉你当前仓库的状态。在新初始化的仓库中,你会看到类似这样的输出:

On branch main

No commits yet

nothing to commit (create/copy files and use "git add" to track)
plaintext

.git 目录是 Git 的核心,包含了:

  • HEAD: 指向当前分支的指针
  • config: 仓库的配置信息
  • objects/: 存储所有的 Git 对象(commits, trees, blobs)
  • refs/: 存储分支和标签的引用

⚠️ 警告: 永远不要手动修改 .git 目录中的文件!这可能会损坏你的仓库。

让我们创建一个简单的项目并初始化 Git 仓库:

# 创建新的项目目录
mkdir my-first-repo
cd my-first-repo

# 初始化 Git 仓库
git init

# 创建一个简单的文件
echo "# My First Git Repository" > README.md

# 查看状态
git status
bash

运行 git status 会显示 README.md 是一个未跟踪的文件:

On branch main

No commits yet

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        README.md

nothing added to commit but untracked files present (use "git add" to track)
plaintext

初始化选项

git init 命令还有一些有用的选项:

# 指定初始分支名称
git init --initial-branch=main
# 或简写为
git init -b main
bash

TIP 从 Git 2.28 开始,你可以配置默认分支名称,避免每次都指定。

你可能会问…

Q: 我可以在已有文件的目录中运行 git init 吗? A: 当然可以!git init 不会删除任何现有文件,它只会创建 .git 目录。

Q: 如果我在错误的目录运行了 git init 怎么办? A: 你可以简单地删除 .git 目录:rm -rf .git

RECAP

在本章中,你学习了 Git 版本控制的第一步:初始化仓库。

核心概念
  • Git 仓库: 包含项目文件和版本历史的目录
  • .git 目录: 存储所有 Git 数据的隐藏目录
  • 分布式版本控制: 每个仓库都是完整的项目副本
关键命令
  • git init : 在当前目录初始化新的 Git 仓库
  • git init <目录名> : 创建新目录并初始化 Git 仓库
  • git init -b <分支名> : 初始化仓库并指定初始分支名称
命令速查表
  • git init : 初始化当前目录为 Git 仓库
  • git init my-project : 创建并初始化新项目
  • ls -la : 查看包括 .git 在内的所有文件
一些 Tips
  • 初始化仓库后会创建 .git 目录
  • 不要手动修改 .git 目录中的文件,会变得不幸!

任务卡

现在轮到你亲手初始化第一个仓库了。我们已经把环境收拾干净,请按顺序完成以下步骤:

  1. 在 Home 目录下创建目录 dojo-init,并初始化其为 Git 仓库,让默认分支名就是我们习惯使用的 main

如果你已经忘了 Home 目录是什么,请你看一下你学过的 Shell Dojo,或者问一问 AI。

  1. 创建一个 README.md,首行写上 # dojo-init
  2. git status 验证一下仓库里只有一个未跟踪文件。
  3. 上述都准备好后运行 /challenge/submit,验证你的仓库配置是否正确。

做到这里,你就已经完成了 Git 学习旅程中的第一小步!

Remote

掌握了本地仓库的管理技能后,现在你将步入 Git 最强大的领域之一:远程协作。这个模块将教会你如何与托管在 GitHub、GitLab 等平台上的远程仓库进行交互,真正体验现代软件开发的协作方式。

你将学习如何从远程位置克隆现有的项目到本地,这是参与开源项目或加入新团队时最常见的第一步操作。更重要的是,你将掌握本地仓库与远程仓库之间的同步机制,学会如何将你的本地更改推送到远程仓库与他人分享,以及如何从远程仓库拉取其他开发者的更新。

> **注意**:本模块的两个教程是有先后顺序的, 请务必按照顺序完成. 如果你遇到了问题, 重启不会清除你在 `/home/hacker` 的操作. 你可以 `rm -rf` 重新来过.

你有没有尝试过去部署别人的项目? 开源世界有着相当多优质的项目; 而在本机上使用它们的第一步往往就是使用 git clone 命令将它们克隆到本地.

在本挑战中, 我们将学习如何使用 git clone 命令来克隆远程仓库.

Git clone 的基本语法是 git clone <repository-url>, 其中 <repository-url> 填写你想要克隆的远程仓库的 URL.

如果你想要克隆的仓库在 GitHub 上, 你可以在该仓库的主页上找到绿色的 “Code” 按钮, 点击它会显示出仓库的 URL. 你可以选择使用 HTTPS 或 SSH 的 URL.

git clone https://github.com/RayZh-hs/neofrp.git
bash

执行上述命令后, 你会在当前目录下看到一个名为 neofrp 的文件夹, 里面包含了该仓库的所有内容.

如果你想要使用 SSH, 你需要确保你的本地机器已经配置了 SSH key 并将其添加到你的 GitHub 账户中. 如果你不知道怎么做, 可以参阅 GitHub 的官方文档. 这一块本教程不做要求.

git clone 命令提供了一些可选参数. 常用的有:

  • git clone <remote-url> <new_path> 可以指定克隆到的目录名, 而不是使用默认的仓库名.
  • git clone --recursive 如果仓库中包含子模块 (git 仓库中的 git 子仓库), 你可以使用这个选项来同时克隆所有子模块.
  • git clone -b <branch-name> <remote-url> 如果你只想克隆某个特定分支 (branch), 可以使用这个选项. (分支在后面会提到)

在这个挑战中, 你需要克隆我们准备的一个远程仓库 (点击下面的链接访问 GitHub 页面):

Git Sample Dojo

将它克隆在你的主目录 /home/hacker 下, 不要重命名, 然后运行 /challenge/submit 命令提交你的结果.

Good Practices

在掌握了本地仓库管理和远程协作的基础技能后,现在是时候学习一些能够让你成为更专业开发者的 Git 高级技巧了。这个模块将教会你三个在实际项目中不可或缺的重要概念,它们将极大提升你的开发效率和代码质量。

你将学习如何使用 `.gitignore` 文件来精确控制哪些文件应该被版本控制系统追踪,避免将临时文件、构建产物或敏感信息意外提交到仓库中。接下来,你将掌握分支的强大功能,学会如何同时开发多个功能特性而不相互干扰,这是现代软件开发团队必备的工作方式。

最后,你将学会如何利用 Git 作为"时光机"的能力,通过重置和回退操作来灵活地管理项目历史。这些技能建立在你之前学到的提交、推送、拉取等操作基础之上。

不想让测试点与编译产物出现在 github 仓库中怎么办?

可以使用 .gitignore 文件让 Git “选择性失明”。

什么是.gitignore文件?其作用是什么?

.gitignore 文件是 Git 仓库中的一个文本文件,告诉 Git 哪些文件或目录不需要被跟踪和提交到版本库中。它的主要作用是防止不必要的文件,包括测试点、编译产物等被添加到 Git 仓库中,从而保持仓库的整洁和高效。

例 1 : 某些大作业的测试点大小可能高达 500 MB ,如果我们将测试点解压到 Git 仓库 main 分支的 testcases/ 目录下并提交,会遭遇如下问题:

git add .
git commit -m "chore: add testcases."
git push origin main

remote: Resolving deltas: 100% (1/1), done.
......
remote: error: File testcases/your_large_file.dat is 1.00 GB; this exceeds GitHub's file size limit of 100.00 MB
......
bash

而我们可以通过在 .gitignore 文件中添加 testcases/ 目录避免这个问题:

# 忽略 testcases 目录
testcases/
plaintext

例 2 : 与其将C++编译出的 .o 等编译产物提交到 Git 仓库中乃至提交到 github 上,不如只用 Git 管理并提交源代码文件,通过编译重新生成可执行代码。

# 忽略所有 .o 文件
*.o
plaintext

注意: .gitignore 文件只会对 Git 未跟踪的文件起效。如果你已经将某个文件通过 git add 添加到 Git 仓库中(即已经被跟踪),那么即使你在 .gitignore 文件中忽略它,Git 仍然会继续跟踪该文件的更改。

例子:

# 创建两个日志文件
touch a.log b.log

# 将 a.log 添加到暂存区,使其变为“已跟踪”状态
git add a.log

# 查看当前状态,a.log 已暂存,b.log 未跟踪
git status

# 输出(部分省略):
Changes to be committed:
       new file:   a.log

Untracked files:
       b.log

# 创建 .gitignore 文件,忽略所有 .log 文件
echo "*.log" >> .gitignore

# 再次查看状态
git status

# 输出:
Changes to be committed:
        new file:   a.log  

Untracked files:
        .gitignore 

# 解释:a.log 依然被跟踪,而b.log 因为从未被跟踪成功被.gitignore忽略。.gitignore 文件本身是新文件
bash

.gitignore文件常用语法

.gitignore 文件中的每一行都指定了一个忽略的对象。

  1. 注释: 以 # 开头的行是注释,并无实际作用。

    # 这是一个注释
    plaintext
  2. 忽略目录: 在模式的末尾加上斜杠 / 表示一个目录。Git 会忽略该目录下的所有文件和子目录。

    # 忽略 build/ 目录
    build/
    plaintext
  3. 使用通配符:

  • * 匹配零个或多个字符。
    # 忽略所有以 .log 结尾的文件
    *.log 
    
    # 忽略所有以 .o 结尾的文件
    *.o
    plaintext
  • ? 匹配一个任意字符。
    # 忽略 a.cpp, b.cpp, 但不忽略 abc.cpp
    ?.cpp
    plaintext
  • ** 匹配任意中间目录。
    # 忽略任何目录下名为 logs 的文件夹
    **/logs/
    
    # 忽略任何目录下以 .o 结尾的文件
    **/*.o
    plaintext
  1. 否定模式 (例外规则): 在模式前加上感叹号 ! 表示不忽略,可用于为一个更广泛的忽略规则设置例外。
    # 忽略所有 .log 文件...
    *.log
    
    # ...但 important.log 文件除外
    !important.log
    plaintext

    注意:如果一个文件的父目录已经被忽略,那么你无法通过否定模式来单独保留这个文件。例如,build/ 被忽略后,!build/main.o 是无效的。

RECAP

核心概念
  • .gitignore 的作用: 定义 Git 在扫描工作目录时应该忽略的文件和目录模式,防止不必要的文件进入版本控制。
  • 只对未跟踪文件有效: .gitignore 无法忽略已经通过 git add 命令纳入版本控制的文件,因此最好在项目开始时设置好 .gitignore 文件。
关键操作
  • 创建 .gitignore 文件: 在项目根目录创建名为 .gitignore 的文本文件。
  • 添加忽略规则: 每行写一个模式,可以使用 # 添加注释,使用 * 等通配符等。
.gitignore语法速查表
  • *[suffix,包括.log/.o/.exe等]: 忽略特定后缀文件。
  • [case,如build]/: 忽略特定目录及内部所有内容。
  • ![file]: 不要忽略某个文件。

任务卡

修改.gitignore文件,使仓库正确忽略指定的文件和目录,并确保重要文件仍然被跟踪和提交。

具体步骤如下:

  1. 进入仓库~/dojo-ignore并编辑仓库中已存在的 .gitignore 文件,从而使如下文件被忽略:

    • important.log 外所有以.log结尾的文件。
    • temp/ 目录下所有内容。
    • src 下的 .o 文件。
    • ignore.txt

    同时请保证其他文件不被忽略。

  2. 进行一次commit提交其他文件,请确保提交后工作区无未提交或未跟踪文件。

  3. 运行 /challenge/submit 检查 .gitignore 是否生效,效果是否正确。

Oh Shit

在实际开发中,即使是最有经验的开发者也会遇到各种意外情况。这个最终模块将教会你如何优雅地处理那些让人头疼的 Git 问题,让你在面对"Oh Shit"的时刻能够冷静应对。

你们如果是因为标题吸引进来的,跳过了前面的介绍部分,建议先回去看看前面的内容。这里我要讲一些黑魔法,来减少你们 “删掉仓库重开” 的频率。

在这一章节我主要介绍:如果你在 Commit 之后发现问题(比如你不小心把密钥提交上去,又或者说你想要修改刚刚的 Commit 信息),你该怎么办?你显然不太希望别人看到你的错误,新建一个 Commit 是掩耳盗铃,毕竟 Git 是时光机。

修改最后一次提交

如果你刚刚提交了代码,但发现了一些小问题,比如拼写错误、忘记添加文件,或者想要修改提交信息,你可以使用:

git commit --amend
bash

这个命令会打开文本编辑器让你修改提交信息,取决于你的配置,默认应该是 Vim。

如果你还想添加一些文件(或者修改一些文件)到这次提交中,你可以在运行 git commit --amend 之前先将文件添加到暂存区:

git add forgotten_file.txt
git commit --amend
bash

这样,forgotten_file.txt 会被添加到上一次的提交中。

以下内容不需要掌握,但是了解一下总没有坏处。你事实上有可能在以后用到它们。

撤销提交但保留更改

如果你想撤销最后一次提交,但保留文件的修改:

git reset --soft HEAD~1
bash

这样提交就被撤销了,但你的更改还在暂存区中。

完全撤销提交和更改

如果你想完全回到上一次提交的状态:

git reset --hard HEAD~1
bash

⚠️ 警告:这会永久删除你的更改,请谨慎使用!

修改历史提交信息

如果你需要修改更早的提交信息,可以使用交互式 rebase:

git rebase -i HEAD~n
bash

其中 n 是你想要回溯的提交数量。这会打开一个文本编辑器,列出最近的 n 次提交。你可以将 pick 改为 reword 来修改提交信息,或者 edit 来修改提交内容。

在最后我还是要提一嘴,如果你已经把错误的提交推送到远程仓库,并且其他人可能已经基于这些提交进行了工作,修改历史会导致问题。 因为你事实上不是简单的“修改了提交内容”而是“创建了新的提交替换了旧的提交”,还记得 Git 的提交总有 “PARENT” 这回事吗?如果其他人基于错误的 PARENT 进行了工作,你修改了 PARENT 之后,他们的工作就会变得不可用。

RECAP

在本章中,你学习了如何在不丢失工作成果的情况下修补提交历史。

核心概念
  • git commit --amend:重写最新一次提交的内容和信息。
  • git reset 变体--soft / --hard 分别回滚提交但保留或丢弃工作区改动。
  • 历史重写的影响:每次重写都会生成新的提交 ID,需要与团队同步!改改自己的历史就算了,千万不能改别人的!
命令速查表
  • git commit --amend:修正刚刚的提交信息或内容。
  • git add README.md && git commit --amend:把漏掉的文件(这里是README.md)补进上一条提交。
  • git reset --soft HEAD~1:撤销提交但保留暂存区。
  • git reset --hard HEAD~1:彻底回滚到上一版本。
一些 Tips
  • 先把修正内容 git add--amend,否则不会被包含。
  • git reset --hard 会删除工作区改动,务必确认无关键信息遗失。
  • 修改历史会改变提交哈希,推送前确保没有人基于旧提交继续开发。这真的很重要,重要的事情说三遍!

任务卡

我们在 ~/dojo-amend 中准备好了一个只有一条提交的小仓库。那次提交的信息写得太含糊,而且 README 也不好。请按照下面的步骤修复它:

打开 README.md,把内容改成 # ACM Dojo。并且把提交信息改成 feat: I love dojo

确保仓库依旧只有一条提交、工作区干干净净,然后运行 /challenge/submit 验证结果。