Git 与 GitHub 终端操作指南

快速导航(Obsidian 中可点击跳转;下同)


1 安装 Git ^nav-1

平台命令
Debian/Ubuntusudo apt update && sudo apt install git
Arch Linuxsudo pacman -S git
Fedorasudo dnf install git
macOS (Homebrew)brew install git
Windows (winget)winget install Git.Git
Windows (手动)下载 https://git-scm.com/download/win

安装后验证:

git --version

推荐使用终端原生 Git。Windows 用户安装时选择 “Git from the command line and also from 3rd-party software” 并选 “Use native Windows Secure Channel library”。


2 初始配置 ^nav-2

# 必设:用户名和邮箱(显示在 commit 中)
git config --global user.name "Your Name"
git config --global user.email "your@email.com"
 
# 推荐设置
git config --global init.defaultBranch main
git config --global core.editor "vim"     # 或 nano / code --wait
git config --global pull.rebase true      # pull 默认用 rebase
 
# 查看配置
git config --list

3 基础操作 ^nav-3

# 初始化新仓库
mkdir my-project && cd my-project
git init
 
# 克隆现有仓库
git clone https://github.com/用户名/仓库名.git
git clone git@github.com:用户名/仓库名.git   # SSH 方式(无需输密码)
 
# 查看状态
git status                # 简洁模式: git status -s
 
# 暂存与提交
git add file.md           # 暂存单个文件
git add .                 # 暂存所有变更
git add -p                # 交互式分段暂存
 
git commit -m "feat: 添加xxx功能"
git commit -am "fix: 修复xxx"   # 跳过 add,仅对已跟踪文件有效
 
# 查看历史
git log                   # 完整日志
git log --oneline         # 一行一条
git log --oneline --graph # 带分支图
git log -5                # 最近 5 条
 
# 查看变更
git diff                  # 工作区 vs 暂存区
git diff --staged         # 暂存区 vs 上次 commit
git diff HEAD~1           # 与上一条 commit 对比
 
# 撤销操作
git restore file.md       # 丢弃工作区修改
git restore --staged file # 取消暂存
git commit --amend        # 修改上次 commit 信息或内容
git reset HEAD~1          # 撤销上次 commit,保留修改
git reset --hard HEAD~1   # 彻底撤销(慎用,无法恢复)

4 分支管理 ^nav-4

# 查看分支
git branch                # 本地分支
git branch -a             # 所有分支(含远程)
 
# 新建与切换
git branch feature-x      # 新建分支
git checkout feature-x    # 切换
git switch feature-x      # 新语法,切换
git switch -c feature-x   # 新建并切换
 
# 合并
git switch main
git merge feature-x       # 将 feature-x 合并到 main
 
# 变基(rebase)—— 保持线性历史
git switch feature-x
git rebase main           # 将 feature-x 的基移动到 main 顶端
 
# 合并 vs 变基
# merge: 产生合并节点,保留真实分支历史
# rebase: 线性历史,更整洁但改写 commit hash
 
# 删除分支
git branch -d feature-x           # 删除本地(已合并)
git branch -D feature-x           # 强制删除(未合并也删)
git push origin --delete feature-x # 删除远程分支
 
# 解决冲突
# 合并冲突时,编辑冲突文件 → 去掉 <<<< / ==== / >>>> → 保存
git add . && git commit           # 合并后提交
# rebase 冲突时: 解决冲突 → git add . → git rebase --continue

5 远程仓库 ^nav-5

# 查看远程
git remote -v
 
# 添加远程
git remote add origin https://github.com/用户名/仓库名.git
 
# 推送与拉取
git push origin main             # 推送到远程 main
git push -u origin feature-x     # 首次推送,建立跟踪
git pull                         # 拉取并合并(pull = fetch + merge)
git fetch                        # 仅拉取,不合并
 
# 多远程(常用于 Fork)
git remote add upstream https://github.com/原作者/仓库名.git
git fetch upstream
git merge upstream/main          # 从上游合并更新
 
# 查看远程分支
git branch -r
git checkout -b local-branch origin/remote-branch

6 .gitignore ^nav-6

在仓库根目录创建 .gitignore,写入不需要跟踪的文件模式:

# 编译产物
*.o
*.exe
build/

# 系统文件
.DS_Store
Thumbs.db

# IDE 配置
.vscode/
.idea/

# 环境与依赖
.env
node_modules/
# .gitignore 生效的前提:文件尚未被跟踪
# 如果已经跟踪,需先删除缓存
git rm --cached 文件名

RootStack 推荐 .gitignore:

.DS_Store
Thumbs.db
.obsidian/workspace
*.exe
*.o
build/
temp/

7 Fork & PR 实战 ^nav-7

以下是从 Fork 到提交 Pull Request 的完整流程:

# 第一步:在 GitHub 网页上 Fork 目标仓库
 
# 第二步:克隆自己的 Fork
git clone https://github.com/你的用户名/仓库名.git
cd 仓库名
 
# 第三步:添加上游仓库(用于同步原项目更新)
git remote add upstream https://github.com/原作者/仓库名.git
 
# 第四步:创建功能分支(永远不在 main 上改)
git checkout -b my-fix
 
# 第五步:修改文件并提交
# vim file.md    ← 编辑文件
git add .
git commit -m "fix: 修正xxx错误"
 
# 第六步:推送到自己的 Fork
git push origin my-fix
 
# 第七步:去 GitHub 网页
# 你的仓库页面会出现 "Compare & pull request" 按钮,点击即可
 
# 第八步:同步上游更新(下次贡献前做)
git checkout main
git fetch upstream
git merge upstream/main
git push origin main

PR 自检清单

  • commit 信息清晰,包含改动说明
  • 只改了目标文件,没有混入无关修改
  • 如果修改代码,已经自己测试过
  • 分支名有意义(fix/typo, feat/add-xxx

跨仓库 PR(Fork → 原项目)

最典型的场景:你 fork 了别人的仓库,在自己的 master 上改了代码,想向原仓库的 clean-main 分支提交 PR。

# 确保本地 master 与原仓库的 clean-main 同步
git remote add upstream https://github.com/原作者/仓库名.git
git fetch upstream
git checkout master
git merge upstream/clean-main
 
# 推送你的改动到自己的 fork
git push origin master
 
# 用 gh 发起 PR(指定目标仓库、目标分支、来源分支)
gh pr create \
  --repo 原作者/仓库名 \
  --base clean-main \
  --head 你的用户名:master \
  --title "PR 标题" \
  --body "描述改动内容"

关键参数:

参数含义示例
--repoPR 要提交到的目标仓库(原项目)--repo 原作者/仓库名
--base目标仓库的分支(你要合并到哪)--base clean-main
--head你的 fork 和分支(你的用户名:分支名--head 你的用户名:master

8 作为仓库主人:审查与合并 PR ^nav-8

# 查看所有 PR 列表
gh pr list
 
# 在终端查看某个 PR 的改动
gh pr view 42                           # 查看 PR #42
gh pr checkout 42                        # 切换到 PR 分支本地审查
git fetch origin pull/42/head:pr-42
git checkout pr-42
 
# 审查后合并
git checkout main
git merge pr-42                         # 方式一:命令行合并
git push origin main
 
# 或使用 gh 合并
gh pr merge 42 --merge                  # merge 方式
gh pr merge 42 --squash                 # squash 方式(压成一条 commit)
gh pr merge 42 --rebase                 # rebase 方式
 
# 关闭 PR(不合并)
gh pr close 42
 
# 撤销已合并的 PR(git revert)
# 如果合并后发现问题,用 revert 创建一个"反 commit"来撤销,而非 reset
git revert -m 1 <merge-commit的hash>
# -m 1 表示保留主分支,撤销合并进来的内容
# 推荐用 revert 而非 reset,因为 revert 不改写历史,多人协作更安全
 
# 在原仓库网页版也可以直接点 "Merge pull request"

三种合并方式

方式命令历史
Merge commitgh pr merge --merge保留所有 commit,加一个 merge 节点
Squashgh pr merge --squash所有 commit 压成一条
Rebasegh pr merge --rebase线性合并,不产生 merge 节点

9 GitHub CLI (gh) ^nav-9

gh 是 GitHub 官方命令行工具,覆盖从 Issue 到 PR 到 Actions 的完整操作。

# 安装
# Linux: https://github.com/cli/cli/blob/trunk/docs/install_linux.md
sudo apt install gh          # Debian/Ubuntu
sudo pacman -S github-cli    # Arch
brew install gh              # macOS
winget install GitHub.cli    # Windows
 
# 登录
gh auth login                # 按提示选择浏览器登录或 token 登录
 
# Issue 操作
gh issue list
gh issue create
gh issue view 42
 
# PR 操作
gh pr list
gh pr create                 # 交互式创建 PR
gh pr create --title "xxx" --body "yyy"
gh pr checkout 42
gh pr review 42 --approve
gh pr merge 42
 
# 仓库操作
gh repo view
gh repo clone 用户名/仓库名
gh repo create 新仓库名               # 在当前账号下创建仓库(交互式)
gh repo create 新仓库名 --public      # 直接指定公开
gh repo create 新仓库名 --private     # 直接指定私有
gh repo create 组织名/新仓库名        # 在组织下创建仓库
gh repo create 新仓库名 --clone       # 创建后自动 clone 到本地
 
# 仓库可见性切换(私→公 or 公→私)
gh repo edit --visibility public      # 改为公开
gh repo edit --visibility private     # 改为私有
 
# 邀请 Collaborator(通过 gh api)
gh api repos/用户名/仓库名/collaborators/被邀请的用户名 -X PUT
# 查看已邀请的 Collaborator
gh api repos/用户名/仓库名/collaborators
 
# 转移仓库到组织(通过 gh api)
gh api repos/用户名/仓库名/transfer -X POST \
  -f new_owner=组织名
 
# 创建组织(gh 无直接命令,通过 API 需要特殊 token)
# 推荐在网页端操作:右上角 + → New organization
 
gh fork                      # 命令行 fork 当前仓库
 
# 查看 CI 状态
gh run list
gh run watch

10 情景现场 ^nav-10

场景一:参与大项目(无仓库权限,Fork + 跨仓库 PR)

你发现某个开源项目有个 bug,想修掉它。你没有该项目仓库的写入权限,所以需要 Fork。

# ---- 第 1 步:Fork ----
# 在浏览器打开 GitHub 项目页,点击右上角 Fork
# 或在终端(已安装 gh):
gh repo clone 原作者/大项目 --fork    # 自动 fork 并 clone 你的 fork
 
# 如果已经手动 fork,用 clone 拉自己的副本
git clone https://github.com/你的用户名/大项目.git
cd 大项目
 
# 添加上游仓库(原项目),用于后续同步
git remote add upstream https://github.com/原作者/大项目.git
 
# ---- 第 2 步:建分支 ----
git switch -c fix/login-bug
# 原则:永远不在 master/main 上直接改
 
# ---- 第 3 步:修改 + 测试 ----
# vim src/login.c        ← 修改代码
# make test              ← 本地测试通过
git diff                 # 确认改动正确
 
# ---- 第 4 步:提交 + 推送 ----
git add src/login.c
git commit -m "fix: 登录模块空指针检查异常"
git push origin fix/login-bug
 
# ---- 第 5 步:发起 PR ----
gh pr create \
  --repo 原作者/大项目 \
  --base clean-main \
  --head 你的用户名:fix/login-bug \
  --title "fix: 登录模块空指针检查异常" \
  --body "在特定输入下 login() 会走空指针分支,增加了 NULL 检查。"
 
# PR 提交后,项目维护者会看到你的 PR,审查后合并或要求修改。
 
# ---- 第 6 步:同步上游(下次贡献前必做) ----
git checkout main
git fetch upstream
git merge upstream/clean-main
git push origin main

场景二:项目参与者(有仓库权限,同仓库 PR)

你在团队项目中有一个新功能要做,你有仓库的写入权限,直接在同一仓库内操作。

# ---- 第 1 步:拉取最新代码 ----
git clone https://github.com/团队名/项目.git
cd 项目
# 或如果已 clone:git pull origin clean-main
 
# ---- 第 2 步:建分支 ----
git switch -c feat/user-avatar
 
# ---- 第 3 步:修改 + 测试 ----
# vim src/user/avatar.c
# go test ./...          ← 测试通过
git status               # 确认改了什么
 
# ---- 第 4 步:提交 + 推送 ----
git add .
git commit -m "feat: 用户头像上传功能"
git push origin feat/user-avatar
 
# ---- 第 5 步:发起 PR ----
# 目标分支是团队仓库的 clean-main,来源是刚才推送的 feat/user-avatar
gh pr create \
  --base clean-main \
  --head feat/user-avatar \
  --title "feat: 用户头像上传功能" \
  --body "实现了头像裁剪、压缩和 CDN 上传"
 
# ---- 第 6 步:审查后修改 ----
# 如果审查者要求修改
# vim src/user/avatar.c
git add .
git commit -m "fix: 根据审查意见调整压缩参数"
git push origin feat/user-avatar   # PR 自动更新
 
# ---- 第 7 步:PR 合并后删分支 ----
git checkout clean-main
git pull origin clean-main
git branch -d feat/user-avatar
git push origin --delete feat/user-avatar

11 常见问题 ^nav-11

Q: 提交时发现漏了一个文件怎么办?

git add 漏掉的文件
git commit --amend --no-edit    # 合并到上一个 commit
# 如果已推送,需加 --force-with-lease
git push --force-with-lease

Q: 如何撤销最近一次推送?

git reset --hard HEAD~1
git push --force-with-lease origin main
# 警告:如果其他人已拉取此分支,不要 force push

Q: 提交信息写错了?

git commit --amend -m "新的正确信息"

Q: 冲突了怎么办?

冲突标记格式:

<<<<<<< HEAD
当前分支的内容
=======
合并进来的内容
>>>>>>> feature-x
  1. 手动编辑文件,保留你想要的版本,删掉 <<<<<=====>>>>>
  2. 保存文件
  3. git add .
  4. git commit

Q: 不小心在 main 分支改了代码怎么办?

# 把 main 上的改动搬到新分支
git switch -c feature-x
git switch main
git reset --hard origin/main    # 把 main 重置到远程状态

Q: 如何撤回暂存区的文件?

git restore --staged file.md

Q: 想扔掉所有未提交的修改?

git restore .                  # 丢弃工作区所有修改
git clean -fd                  # 删除未跟踪的文件和目录


12 版本标签与发布 ^nav-12

语义化版本 (Semantic Versioning)

版本号格式 MAJOR.MINOR.PATCH,例如 v1.2.3

说明例子
MAJOR主版本不兼容的 API 变更 / 重大重构v1.0.0v2.0.0
MINOR次版本向下兼容的新功能 / 新增内容v1.0.0v1.1.0
PATCH修订号向下兼容的 bug 修复 / 小修正v1.0.0v1.0.1

RootStack 是文档项目,按里程碑手动打 tag:

# 查看当前版本(最近的 tag)
git describe --tags
 
# 打 lightweight tag(只是一个标记)
git tag v0.1.0
 
# 推荐:annotated tag(包含作者、日期、信息)
git tag -a v0.1.0 -m "首个可读版本:Phase 1-12 内容就绪"
 
# 推送 tag 到远程(默认不会随 git push 推送)
git push origin v0.1.0          # 推送单个 tag
git push origin --tags          # 推送所有本地 tag
 
# 列出所有 tag
git tag -l "v0.*"
 
# 删除 tag
git tag -d v0.1.0               # 删除本地
git push origin --delete v0.1.0 # 删除远程

网页端操作 tag(GitHub 网页)

# 1. 打开仓库 https://github.com/用户名/仓库名
# 2. 点击页面中间的 Tags(或 Releases)
# 3. 点击 "Create a new release" → "Choose a tag" → 输入新 tag 名
#    如果没有这个 tag,GitHub 会自动创建
# 4. 填写 Release title 和描述
# 5. 点击 "Publish release"
操作终端命令网页入口
创建 taggit tag -a v0.1.0 -m "msg" + git push origin v0.1.0仓库页 → Releases → Draft a new release
查看 taggit tag -l仓库页 → Tags
删除 taggit tag -d v0.1.0 + git push --delete origin v0.1.0Releases → 对应 release → Delete
发布 Releasegh release create v0.1.0 --title "v0.1.0" --notes "changelog"创建 release 页面 → Publish release

13 GitHub 网页端操作 ^nav-13

创建仓库

  1. 登录 GitHub,点击右上角 +New repository
  2. 填仓库名(例如 my-project),写描述
  3. 选择 Public(公开)或 Private(私有)
  4. 可选:初始化 README、.gitignore、license
  5. 点击 Create repository
  6. 创建后按页面提示 push 本地代码

仓库设置(Settings tab)

进入仓库后点顶部 Settings

设置项位置说明
Repository nameSettings → General改仓库名
DescriptionSettings → General仓库描述(显示在仓库页顶部)
Topics仓库页顶部齿轮图标 (齿轮图标) 或 Settings → General加标签如 c cpp algorithm obsidian
VisibilitySettings → Danger Zone → Change visibility公开/私有切换
ArchiveSettings → Danger Zone → Archive this repository归档(只读)
DeleteSettings → Danger Zone → Delete this repository删除(不可恢复)

添加 Collaborator

Settings → Collaborators → Add people → 输入 GitHub 用户名 → Add

被添加的人有仓库的写入权限,可以 push、创建分支、合并 PR。

网页端 Fork + PR

# Fork(在自己账号下创建副本)
打开目标仓库 → 点右上角 "Fork" → Create fork

# 提交 Pull Request
自己的 Fork 仓库 → 点 "Pull Request" tab → "New pull request"
  → base repository: 原仓库 → base branch: clean-main
  → head repository: 自己的 Fork → compare branch: 自己的分支
  → Create pull request → 填写标题和描述 → Create

网页端 Issue 管理

仓库页 → Issues tab → New issue
  → 写标题 + 描述(支持 Markdown)
  → 右侧 Assignees: 指派给谁
  → Labels: 加标签(bug / enhancement / question)
  → Projects: 关联项目看板
  → Submit new issue

Issue 操作:Comment(评论)、Close(关闭)、Reopen(重新打开)、Pin(置顶)、Lock(锁定讨论)。

网页端 PR Review

PR 页面 → Files changed tab → 逐行浏览改动 → 点击行号前的 + 发表评论
→ Finish your review → 三种结果:

选项说明
Comment普通评论(不阻止合并)
Approve批准(同意合并)
Request changes要求修改(阻止合并直到解决)

14 签名提交 ^nav-14

SSH key(免密推送)

SSH 方式 clone 和 push 不需要输密码:

# 检查已有 key
ls -la ~/.ssh/id_ed25519.pub
 
# 生成新 key
ssh-keygen -t ed25519 -C "your@email.com"
# 一路回车,可选设置 passphrase
 
# 查看公钥
cat ~/.ssh/id_ed25519.pub
# 复制输出内容(以 ssh-ed25519 开头)

添加到 GitHub(网页端):

Settings → SSH and GPG keys → New SSH key
  → Title: 起个名字(比如 "我的笔记本")
  → Key: 粘贴刚才复制的公钥
  → Add SSH key

之后 clone 用 SSH 地址:

git clone git@github.com:用户名/仓库名.git

GPG 签名(Verified 标记)

让 commit 显示绿色的 Verified 徽章:

# 1. 安装 GPG
# Debian/Ubuntu
sudo apt install gnupg
# Arch
sudo pacman -S gnupg
 
# 2. 生成 GPG 密钥(交互式)
gpg --full-generate-key
# 选 RSA and RSA → 4096 bits → 1y(或 0 永不过期)
# 填姓名和邮箱(必须与 git config user.email 一致)
# 设置密码
 
# 3. 列出密钥,记下指纹(sec 行后的长串)
gpg --list-secret-keys --keyid-format LONG
# 例如 sec   rsa4096/AAAAAAAAAAAAAAAA  2026-01-01
# 指纹就是 AAAAAAAAAAAAAAAA
 
# 4. 导出公钥
gpg --armor --export AAAAAAAAAAAAAAAA
# 复制输出(从 -----BEGIN PGP PUBLIC KEY BLOCK----- 开始)

添加到 GitHub(网页端):

Settings → SSH and GPG keys → New GPG key
  → Key: 粘贴上面导出的公钥
  → Add GPG key
# 5. 配置 git 使用该密钥签名
git config --global user.signingkey AAAAAAAAAAAAAAAA
git config --global commit.gpgsign true   # 默认所有 commit 签名
 
# 6. 签名提交
git commit -S -m "feat: 添加xxx功能"       # -S 指定签名
# 或全局已启用时正常 commit 即可
 
# 7. 验证签名
git log --show-signature -1
认证方式用途设置位置
SSH keyclone/push 免密Settings → SSH and GPG keys → New SSH key
GPG keycommit 显示 VerifiedSettings → SSH and GPG keys → New GPG key
Personal tokenAPI 访问 / HTTPS cloneSettings → Developer settings → Personal access tokens

15 子模块 (Submodule) ^nav-15

子模块允许你在一个 Git 仓库中嵌入另一个 Git 仓库的特定版本。

子模块的本质

子模块不是文件拷贝,而是指针引用——Git 记录的是目标仓库的 commit hash(模式 160000),而非实际文件内容:

# 查看子模块的 gitlink 条目(160000 表示子模块)
git ls-tree HEAD red_team/
# → 160000 commit a1b2c3d4...  red_team
  • GitHub 仓库页面上,子模块目录会显示 -> 箭头(如 red_team ->
  • 箭头后面的 commit hash 指向被嵌入仓库的某个版本
  • 子模块信息存储在仓库根目录的 .gitmodules 文件中

添加子模块

git submodule add https://github.com/用户名/外部仓库.git 本地目录名

执行后:

  1. 创建 .gitmodules 文件(记录子模块路径和 URL)
  2. 在 Git 索引中添加一个 160000 类型的 gitlink 条目
  3. 自动 clone 外部仓库到指定目录

克隆包含子模块的仓库

# 方式一:克隆时一并拉取子模块
git clone --recursive https://github.com/用户名/仓库名.git
 
# 方式二:已克隆后初始化子模块
git clone https://github.com/用户名/仓库名.git
cd 仓库名
git submodule update --init         # 拉取所有子模块
git submodule update --init --recursive  # 嵌套子模块也拉取

如果没有 --recursive 或不执行 submodule update,子模块目录在本地就是空的。

更新子模块到最新版本

# 进入子模块目录,拉取最新
cd 子模块目录
git pull origin main
cd ..
 
# 提交更新后的子模块指针
git add 子模块目录
git commit -m "chore: 更新子模块到最新"

子模块解耦为普通文件

当不需要子模块的独立性(如想直接在父仓库中管理内容)时,将子模块转为普通文件:

# 1. 删除子模块的 gitlink(从索引移除,保留工作区文件)
git rm --cached 子模块目录
 
# 2. 删除 .gitmodules 中的对应条目(如果不再需要任何子模块)
# rm .gitmodules
 
# 3. 删除子模块元数据
rm -rf .git/modules/子模块目录
 
# 4. 将实际文件添加到父仓库
git add 子模块目录
git commit -m "子模块解耦为普通文件"

执行后,GitHub 上的 -> 箭头消失,clone 也能直接拉下全部文件。

删除子模块

# 1. 从索引和 .gitmodules 移除
git submodule deinit -f 子模块目录
# 2. 从文件系统删除
rm -rf 子模块目录
# 3. 从 Git 跟踪中移除
git rm -f 子模块目录
# 4. 提交
git commit -m "移除子模块"

子模块常见问题

问题原因解决
克隆后子模块目录是空的未执行 submodule update --init执行 git submodule update --init --recursive
子模块在 GitHub 上显示 -> 箭头子模块是 git link,不是普通文件执行解耦操作(见上)
修改了子模块内容但父仓库不认需要在子模块目录内先 commit进入子模块目录 git add && git commit,再回父仓库 git add && git commit
子模块 detached HEAD子模块默认处于分离头指针状态进入子模块目录 git checkout main