VCS Tool: Git & GitHub

I. Git 与 GitHub 基础命令

涵盖 90% 的日常开发工作流

1.1 初始配置化

首次配置时记录好即可。
1. 配置用户名:git config --global user.name "Your Name"
2. 配置邮箱:git config --global user.email "[email protected]"
3. 查看当前配置:git config --list

1.2 日常工作流

1.2.1 自建仓库版

  1. 初始化仓库:git init
  2. 在 GitHub 上创建一个新的空仓库,不要勾选 README、.gitignore 等选项。复制仓库 URL。
  3. 关联远程仓库git remote add origin <github-url>
  4. 添加文件:git add <file>git add .(添加所有修改)
  5. 提交暂存区:git commit -m "Commit message"
  6. 提交暂存区但是不覆盖上一条 commit message: "git commit --amend --no-edit"
  7. 首次推送:git push -u origin main,注意首次推送时要带上 -u 参数绑定分支。
  8. 后续推送:git push
  9. 拉取远程更新:git pull

1.2.2 克隆仓库版

  1. 克隆仓库:git clone <github-url>
  2. 进入项目目录:cd <repo-name>
  3. 添加/修改文件,提交并推送同上。

Tip

如果该仓库是别人的仓库,想要提交修改需要先 Fork 到自己的 GitHub 账号下,然后克隆自己的 Fork 仓库,修改后推送到自己的仓库,再通过 GitHub 发起 Pull Request 给原仓库。
如果该仓库是自己的仓库,直接克隆后修改提交推送即可。

1.3. 分支管理

  1. 查看所有分支:git branch (带 * 的是当前分支)
  2. 创建新分支:git branch <branch-name>
  3. 切换分支:git switch <branch-name> (旧版使用 git checkout <branch-name>
  4. 创建并直接切换分支:git switch -c <branch-name>
  5. 合并分支:git merge <branch-name> (将指定分支合并到当前分支)
  6. 删除本地分支:git branch -d <branch-name>

分支与推送/拉取

git push/pull:默认操作当前分支,即将本地/远程的当前分支远程/本地对应分支进行同步。
git push/pull origin <branch-name>:操纵指定分支 <branch-name>,即将本地/远程的 <branch-name> 分支与远程/本地对应分支进行同步。

II. Git 与 GitHub 高级操作

在基础版之上,用于解决代码冲突、版本回退、历史重写及复杂项目管理。
待更新

2.1 VS Code 集成终端

VS Code 中 ctrl + ` 默认启动终端为:
- powershell(Windows)
- bash(Linux/Mac)

一般来说不建议直接在 powershell 中使用 git 命令,建议切换到 Git 工具安装时默认提供的 GitBash,在 VS Code 中点击 + 新建终端时可以选择 GitBash 打开,在GitBash中管理 Git 仓库。

GitBash 提供了更完整的 Git 命令支持和更友好的命令行体验(例如自带 vim 工具),尤其在处理复杂的 Git 操作时更为方便。

2.2 .gitignore

在使用 Git 管理项目时,通常会有一些文件或文件夹不希望被提交到版本库中,例如数据集、.vscode/、编译产物等。这时候就需要使用 .gitignore 文件来告诉 Git 哪些文件或目录应该被忽略。

直接新建 .gitignore 文件在其中写入不希望跟踪的文件/文件夹即可,支持正则匹配。

如果我们不小心提交了某些文件进入版本库,后续又不想跟踪此文件时,可以使用以下命令将其从版本库中移除:
1. 从版本库中移除文件git rm --cached <file>
2. 提交修改:git commit -m "移除不需要跟踪的文件"
3. 在.gitignore中添加该文件或文件夹的路径,确保后续不再被跟踪。
4. 推送修改

2.3 远程仓库变更

当远程仓库名字变更时,需要更新本地仓库的远程地址:
1. 查看当前远程仓库地址:git remote -v
2. 更新远程仓库地址git remote set-url origin <new-github-url>
3. 再次查看确认:git remote -v

2.4 大文件管理: LFS(Large File Storage)

在 Git 的世界里,纯文本代码的管理可以说是如鱼得水,但如果你的项目里包含了大型二进制文件(音视频 .mp4、压缩包 .zip、或者动辄几个 G 的机器学习数据集和模型文件),Git 就会变得极其臃肿和缓慢。

Git LFS 采用了一种“狸猫换太子”的策略:
它把真正的大文件存在一个专门的 LFS 服务器上。在你的本地 Git 仓库历史中,只保留一个体积不到 100 字节的文本指针。只有当你 checkout 到具体的某个分支/提交时,LFS 才会根据指针去下载那个特定版本的大文件实体。历史版本的庞大文件不再强制塞进每个人的电脑里。

使用 Git LFS 的步骤:
1. 安装 Git LFSgit lfs install(一般来说,安装 Git 时会一并安装 LFS)
2. 跟踪大文件类型git lfs track "*.mp4"(以 mp4 为例,其他类型同理)

!!! note 注意
    此行命令过后,Git 会在仓库根目录生成或修改一个 `.gitattributes` 文件,里面会增加这一句:
    `*.mp4 filter=lfs diff=lfs merge=lfs -text`
    `.gitattributes` 文件是 Git 中的属性配置文件,它告诉 Git 哪些文件要被特殊对待以及如何特殊对待。
  1. 保存配置并提交

git add .gitattributes
git commit -m "配置 LFS 追踪大文件"


  1. 正常添加和提交大文件
git add video.mp4
git commit -m "添加宣传视频"
git push origin main

2.5 Huggingface Hub 集成

一般来说,即便有 lfs 工具帮助我们处理大文件,像机器学习数据集、模型参数文件也不建议直接放在 GitHub 仓库里。

更推荐的做法是将这些大文件上传到 Huggingface Hub 这样的专门平台上,而 git 工具作为一个通用的版本控制系统,可以直接 clone Huggingface 仓库以及推送提交。
例如:
1. clone Huggingface 模型仓库:
git clone https://huggingface.co/username/repo-name
2. clone Huggingface 数据集仓库:
git clone https://huggingface.co/datasets/username/dataset-name
3. 在本地修改后提交并推送到 Huggingface 仓库:

git add .
git commit -m "更新模型参数"
git push origin main


注意,这里推送时会涉及到 Huggingface 访问令牌,需要输入 Access Token 进行身份验证。

2.6 Tag & Release

2.6.1 Tag

Tag(标签) 是 Git 中用于标记特定提交的工具,常用于标记版本发布点。Tag 可以分为两种类型:轻量级标签(lightweight tag)和附注标签(annotated tag)。轻量级标签只是一个指向特定提交的指针,而附注标签则包含了更多的信息,如作者、日期和标签信息。

使用 annotated tag 可以很方便的管理版本发布,其他用户很容易看出当前版本的关键版本信息,方便下载对应版本的代码。

发布 tag 的方式如下:
1. 确保与远程仓库同步
2. 创建 tag
- Method 1: git tag v1.0,这是创建一个轻量级标签
- Method 2: git tag -a v1.0 -m "Release version 1.0",这里 -a 表示创建一个附注标签,-m 后面跟的是标签信息
- Method 3: git tag -a v1.0,在 GitBash 中执行后默认进入 vim 编辑器,在其中输入标签信息,ESC + :wq保存退出即可。
3. 推送 tag 到远程仓库git push origin v1.0,之后即可在 GitHub 仓库的 tags 页面看到这个版本标签。

Tip

一般来说,tag 的命名方式会遵循语义化版本控制(Semantic Versioning)的规范,例如 v1.0.0v2.1.3 等,这样可以清晰地表达版本之间的关系和更新内容。vx.y.z 中,修改 z 表示小修小补的 bug 修复,修改 y 表示增加了新功能但不破坏兼容性,修改 x 则表示有重大更新可能会破坏兼容性
此外,也可以根据项目需求使用其他命名方式,但建议保持一致性和可读性。尤其是要写好标签信息

还有几条关于 tag 的常用命令:
1. 查看当前仓库的所有 tag:git tag
2. 查看某个 tag 的详细信息:git show v1.0
3. 删除本地 tag:git tag -d v1.0

2.6.2 Release

GitHub 上的 Release 是基于 Tag 的一个功能,它允许我们在 GitHub 上为每个版本发布一个专门的页面,包含版本说明、下载链接等信息。通过 Release,我们可以更方便地管理和分发项目的不同版本。
创建 Release 的步骤如下:
1. 创建 Tag:首先需要在本地创建一个 Tag,并推送到远程仓库(如上所述)。
- 直接创建: git tag -a vx.y.z, git push origin vx.y.z
- 不覆盖原始 tag: git tag -a <Release Name> vx.y.z^{} -m "<Release Notes>" -f, git push origin <Release Name>,这个操作是想要保留原始 tag 而同时与该 tag 相同的版本 Release 的方法,其中 <Release Name> 就是你想要创建的 Release 的名字,vx.y.z 是你想要关联的 Tag 的名字。
2. 在 GitHub 上创建 Release:登录 GitHub,进入你的仓库,点击 "Releases" 标签页,然后点击 "Draft a new release"。
3. 选择 Tag:在 "Tag: Select tag" 下拉菜单中选择刚刚的 <Release Name>
4. 填写 Release Notes信息
5. 点击 Publish release

Release 还有更多的方法,例如创建多操作系统的 assets,或者通过 GitHub API 自动化发布流程等。

2.7 版本回退

2.7.0 未提交到暂存区的修改

有时候我们在修改代码的过程中,可能会不小心把代码改坏了,或者做了一些尝试性的修改,结果发现这些修改并不理想,并且此时我们没有add这些文件到暂存区,这个时候如果想要撤回这些修改回到之前commit的状态:

git restore <file> # 撤销指定文件的修改
git restore .      # 撤销所有文件的修改

如果我们还新建了一些文件或文件夹,这些新文件还没有被 Git 追踪过,restore 是管不到它们的,这时候需要加一条:

git clean -fd


-f 表示强制删除,-d 表示同时删除未追踪的文件夹。

2.7.1 撤回暂存区

有时候我们不小心 git add 了某些文件到暂存区,但又不想提交它们了,这时候可以使用以下命令将它们从暂存区撤回:

git reset <file>

如果想要撤回所有文件,可以使用:

git reset

2.7.2 回到上一个 commit

有时候我们 commit 了一个版本后,又做了一些修改,然后发现当前修改把项目改坏了,想要强制恢复到上一个commit版本,完全舍弃当前所有修改,可以这样做:

(1) 抹除所有以追踪的文件的修改(包括add到暂存区的)

git reset --hard HEAD


HEAD 代表当前分支的最后一次 commit--hard 是一个威力巨大的参数,它会强行把工作区和暂存区全部拉回 HEAD 的状态,刚才写坏的代码会瞬间灰飞烟灭

(2) 清理所有未追踪的新文件/新文件夹(可选)
如果在改废代码的过程中,还新建了一些文件或文件夹(这些文件还没被 Git 追踪过),上面的 reset 是管不到它们的。还需要加一条:

git clean -fd


-f 表示强制删除,-d 表示同时删除未追踪的文件夹。

执行完这两部,当前分支就完全回退到上一个 commit 的状态了,之前的修改和新建的文件都不见了。

Warning

这个操作非常危险,reset --hardclean -fd 属于 Git 中少有的不可逆操作!
一旦敲下回车,改坏的那些代码将永远从硬盘上消失,连 Git 也救不回来(因为它们根本还没进入 Git 的版本库

所以无论当前的修改是否真的损坏了项目代码,都建议在执行此命令前将当前整个仓库备份一下,这是绝对稳妥的保底策略。

Git 最大的设计哲学之一就是“去中心化”和“一切皆在本地”。项目里的所有历史版本、分支信息、甚至与 GitHub 的远程绑定关系(origin),都在项目根目录下一个名为 .git 的隐藏文件夹里。当我们连同这个 .git/ 文件夹一起复制时,我们不仅复制了代码,还原封不动地复制了整个 Git 数据库。只要 .git/还在,就算整个仓库的代码被扬了,Git 也能救回来,这就是 Git 带来的强大安心感。

2.7.3 撤回上一个 commit 及其推送

(1) git revert

适用场景:公共分支、多人协作的分支
原理:不删除历史记录,而是生成一个新的提交,来 “反做” 掉那一次错误提交的内容。

  1. git log 查找该次 commit 的哈希值(hash),例如 abc1234
  2. git revert abc1234,Git 会自动生成一个新的 commit 来撤销 abc1234 的修改
  3. git push origin main 将这个新的撤销提交推送到远程仓库

(2) 暴力删除
适用场景:只有你一个人使用的分支,或者你确定能搞定团队成员的代码同步。
原理:直接将分支指针移动到错误提交之前,就像什么都没发生过。

  1. git reset --hard HEAD~1: HEAD~1 表示撤销最近的1次提交
  2. git push -f origin main: 这一步会直接擦除远程服务器上的历史!

Warning

暴力删除时会导致仓库被重写,因此在撤销 commit 前,最好将当前代码备份一下,无论是不是真的损坏了项目代码。

2.8 垃圾清理

git gc --prune=now

git gc (Garbage Collection,垃圾回收) 是一个纯本地的数据库优化命令。它就像是给你的本地电脑磁盘做了一次“碎片整理”。它压缩了文件、删除了你本地那些悬空的废弃对象(比如曾经 git add 但后来又撤销的文件)。
既然它只操作本地数据库的底层结构,不改变任何代码逻辑和文件内容,所以不需要(也无法)被 commit 或 push
(注:GitHub 的远端服务器会在后台定期自动运行它自己的 git gc,所以不用操心远端。)

2.9 hook 机制

2.9.1 pre-commit hook

有些时候,我们希望在 git commit 时,git 或者其他什么东西能够对当前的目录或者暂存区中的修改做一些检查,这些检查例如源码的版本号与文档的版本号有没有对齐,更新日期有没有同步到今天等。如果检查不通过,git 就会阻止这次提交,提示我们修改。

事实上,git 确实有这种功能,在 git 中,实现这个功能的机制叫做 Git Hooks(Git 钩子)。下面我们介绍 pre-commit hook 的创建和使用方法。这里我们以版本号和日期检查为例。

(1)第一步:找到 Git Hooks 的藏身之处

在项目根目录下,有一个隐藏文件夹 .git
进入 .git/hooks 文件夹,会看到一堆以 .sample 结尾的文件,比如 pre-commit.sample。这些是 Git 官方给出的示例代码。

(2)第二步:创建自己的 pre-commit 脚本

.git/hooks 文件夹下,新建一个文件,名字必须精确叫 pre-commit(注意:绝对不能有任何后缀名,不能叫 pre-commit.pypre-commit.sh)。

(3)第三步:编写拦截逻辑(用 Python 直接写)

虽然钩子文件没有 .py 后缀,但你可以通过在第一行指定解释器(Shebang)来让 Git 知道这是一个 Python 脚本:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

用代码编辑器打开这个没有任何后缀的 pre-commit 文件,写入拦截逻辑。这里不展示示例脚本。可以参考项目 SonicBolt 下的拦截脚本:interception.py

(4)第四步:赋予执行权限(👑 必须做!)

默认创建的文件可能没有执行权限,Git 会直接忽略它。
打开你的 Git Bash 终端,确保当前在项目根目录,运行以下命令赋予其可执行权限:

chmod +x .git/hooks/pre-commit

现在拦截器已经生效了!以后每次执行 git commit时,git 都会先执行这个审查脚本,无误后才会允许提交。

Note

  1. 着急提交,强制放弃审查
    git commit -m "紧急修复" --no-verify
  2. 团队协作
    .git/hooks 文件夹不会被 Git 追踪并 push 到远程仓库。这意味着合作者 clone 下来项目后,是没有这个拦截器的。
    如果要在团队中强制推行,前端界通常会使用 Husky 工具,而 Python / C++ 界通常会使用一个叫 pre-commit 的第三方开源框架,它可以通过一个配置文件(.pre-commit-config.yaml)让团队里所有人自动安装并统一这些拦截规则。

🚀 基于 Python 的 pre-commit 框架——团队协作开发
1. 在项目根目录下新建一个文件夹,比如叫 Scripts/
2. 在 Scripts/ 文件夹下创建 python 脚本,编写拦截逻辑,例如 pre_commit.py
3. 在项目根目录下创建 .pre-commit-config.yaml 配置文件,内容如下:

repos:
- repo: local
   hooks:
      - id: custom-version-and-date-check
      name: Version and V-File Date Check
      entry: python -X utf8 Scripts/pre_commit.py
      language: system
      pass_filenames: false
      always_run: true


  1. 安装 pre-commit 框架:pip install pre-commit
  2. 在项目根目录下运行 pre-commit install,它会自动在 .git/hooks/ 文件夹下创建一个 pre-commit 文件,并且这个文件会调用 pre-commit 框架来执行我们在 .pre-commit-config.yaml 中配置的钩子。
  3. 之后每次修改 Scripts/pre_commit.py 中的拦截逻辑后,需要重新 pre-commit install 来更新 .git/hooks/pre-commit 文件中的调用逻辑。

2.10 多分支并行开发

假设我们目前只有一个主分支 main, 接下来我们一起看看如何新建开发分支 dev 并协同 main 进行独立开发

(1) 创建分支
基于 main 分支创建一个新的分支 dev

git switch -c dev

其中参数 -c 表示创建并切换到新分支。

(2) 分支切换

我们想要从当前的 dev 分支切换回 main 分支:git switch main , 同理,从 main 切回 devgit switch dev

但是,如果我们当前对 dev 分支做出了一些修改,并且这些修改还没有被 add 到暂存区或者 commit,这时候直接切换分支会出现一些问题:

  1. 情况一: 修改前 devmain 的差异本身已经很大了,如果你当前未提交的修改,恰好改动了那些 dev 和 main 内容本来就不一样的文件。此时执行 git switch 时,Git 会提示有冲突,无法切换分支。
  2. 情况二(概率很小,一般出现在刚刚创建分支时):如果你修改的文件,在 devmain 两个分支中目前的内容是一模一样的(比如你刚好修改了一个两个分支都没怎么动过的公共配置文件),此时,Git 会允许切换,并且把这些“未提交的修改” 直接带到 main 分支!此时在 main 分支里执行 git status 时就会看到这些修改。但是不用担心,这些修改还没有被提交,我们的 main 分支没有被污染,只需要 git restore . 就能恢复(但同时也会丢失这些未提交的修改)。

因此,无论是哪种情况,我们都不建议在含有未提交修改的情况下直接切换分支,正确的做法是:

  1. 提交修改git add . + git commit -m "保存当前修改",此时再切换,就不会有任何问题,两个分支绝对干净隔离。
  2. 暂存修改:如果你不想提交这些修改,可以先把它们暂存起来:git stash切换回这个分支后git stash pop 把修改拿回来。

(3) Tag 与 Release
需要注意的是,Tag 和 Release 是仓库级别的,而非分支级别的,也就是说,无论你当前在哪个分支上创建 Tag 或 Release,这些 Tag 和 Release 都是对整个仓库的版本进行标记的,其他分支也能看到这些 Tag 和 Release。

因此,如果不同分支都有 tag 和 release 的需要,建议在命名时做好标记隔离,例如加上分支名前缀:dev-v1.0main-v1.0 等等。




Enjoy Reading This Article?

Here are some more articles you might like to read next: