Resources

Simple Git tutorial for beginners | Nulab

Learn Git Branching

Pro Git - Git

Mergetools: Stop doing three-way merges!

So You Think You Know Git - FOSDEM 2024 - YouTube

Reference

git-config

和 jj 不一样,git使用的是 ini 风格的配置

git-branch

重命名当前分支

git branch -m 新分支名称

重命名其他分支

git branch -m 旧分支名称 新分支名称

git-checkout

git checkout <branch_name>
git checkout abc123

git-add

-p 有这么个东西

可以对每个 hunk 交互式选择是否 stage

git-clone

--depth 1 很好用

也可以写成 --depth=1

限制了深度的 clone 叫做浅克隆

浅克隆 (git clone —depth=N) 得到的历史只到某个深度,这导致本地的 master(或其他分支)缺少 older commits。

Git 允许你在此基础上提交并 push,前提是你只基于已有的浅历史产生新提交且远端 tip 仍是你浅克隆时的父提交;否则 push 会失败并提示你需要更多历史。

一旦远端出现你本地没有的旧提交(例如你想基于一个更早的 commit rebase),你需要先运行 git fetch —unshallow 或 git fetch —depth=M 来补齐缺失历史,再进行操作。

如果仓库开启了“接收浅更新”限制(大多数默认允许),无需额外设置;但若服务器禁用了 receive.shallowUpdate,浅仓库 push 会被拒绝,需要管理员解除限制或改用完整克隆。

git-commit

Angular提交信息规范 - Git Guide

如何规范你的Git commit?

How to Write a Git Commit Message

因为我在搞 majutsu 的 jjdescription 部分,所以我大概需要对 message 信息有深刻的理解

fixup

How to mask meaningless “fix typo” commit messages | by Daniel Megyesi | Infr…

trailer

git-log

—grep

内置的对 message 的搜索功能

—graph

原来 git 也是有图的输出的

git-reflog

和我讲讲 git-reflog,这个命令有什么用?

git reflog 用来查看 *Git 在本地记录的“引用(ref)移动历史”*:比如 =HEAD=、某个分支指针在什么时候从哪个提交移动到了哪个提交。它是你在“误操作后找回提交/分支”的最常用工具之一。

它解决什么问题(有什么用)

  1. 找回“丢了”的提交

    • 例如你 git reset --hard=、=rebase=、=commit --amend 后,分支指针被改写,旧提交看似“不见了”。
    • reflog 仍能列出之前 =HEAD=/分支指向过的提交哈希,让你回退或恢复。
  2. 撤销/回滚危险操作

    • reset --hard 回不去?用 reflog 找到 reset 之前的那条记录,再 =git reset —hard <hash>=。
    • rebase 搞乱了?用 reflog 找到 rebase 前的提交点,reset 回去或新建分支。
  3. 定位 HEAD 的历史位置

    • HEAD@{n} 语法可以引用“n 次移动之前的 HEAD”。
    • 也支持按时间:=HEAD@{yesterday}=、=HEAD@{2.hours.ago}=(具体格式因版本略有差异)。

常用命令

  • 看 HEAD 的移动历史(最常用):
    git reflog
  • 看某个分支的 reflog:
    git reflog show main
  • 更清晰一点(带一行提交信息):
    git reflog --date=iso

典型救援例子

  • 1)reset —hard 之后找回

    1. 先看 reflog:
      git reflog
      你会看到类似:
      a1b2c3d HEAD@{0}: reset: moving to HEAD~1
      9f8e7d6 HEAD@{1}: commit: add feature
    2. 回到 reset 前的位置:
      git reset --hard 9f8e7d6
  • 2)把“丢了的提交”捞出来建个新分支

    git branch rescue 9f8e7d6

重要特点/限制

  • *reflog 是本地的*:默认不会随 push 共享到远端。
  • *不是永久保存*:旧记录会过期并被 GC 清理(通常提交 reflog 90 天、不可达对象更短;可配置)。
  • 它记录的是“引用怎么移动”,不是所有对象的完整历史。

git-grep

在工作树中的已跟踪文件、索引文件中注册的 blobs 或给定树对象中的 blobs 中查找指定的 pattern。

pattern 是由换行符分隔的一个或多个搜索表达式组成的列表。

空字符串作为搜索表达式将匹配所有行。

git-diff

diff --git 头里的 index <old>..<new> <mode> 表示该文件在“变更前/变更后”的 blob 对象 ID(缩写),以及文件模式(权限)。

  • =<old>=:变更前文件内容对应的 blob 哈希(不是 commit)
  • =<new>=:变更后文件内容对应的 blob 哈希
  • <mode>=:文件类型/权限,例如 =100644 普通文件,=100755= 可执行文件,=120000= 符号链接,=160000= 子模块(gitlink)

新建/删除文件时常见:=index 0000000..<new> 100644= 或 =index <old>..0000000 100644=。

  • git diff 默认等价 =-p/—patch=,输出的是“git diff”格式(unified diff + Git 额外头部)。
  • 结构大致是:

    diff --git a/file b/file +

    扩展头(=old/new mode=、=rename from/to=、=copy from/to=、=index <hash>..<hash> <mode>=)+

    ---/+++ +

    @@ hunk。
  • unified diff 用 --- /dev/null+++ /dev/null 表示新增/删除,但 diff --git 头仍使用 a/b/ 前缀路径。

combined diff

  • standard combined diff

    combined diff 只显示“被所有父提交都改过的文件”(man 里写的是 modified from all parents)。

    • -c--cc 生成(=git show= 显示 merge 时默认就是 combined diff)。
    • 头部会是 diff --combined <path> 或 =diff —cc <path>=。
    • 扩展头示例:=index <p1>,<p2>..<dst>=、=mode <m1>,<m2>..<dst>=;创建/删除用 new file mode / =deleted file mode=。
    • from/to 头通常是两行:=--- a/file= 与 =+++ b/file=;若加 =—combined-all-paths=,会变成 N+1 行(N 为父提交数)。
    • hunk 头使用 =@@@=,数量为 =父提交数 + 1=,用于提示这是 combined diff,不建议直接喂给 =patch -p1=。
    • 行前会有“每个父提交一列”的标记:列中 - 表示该父提交有此行但结果没有,=+= 表示结果有此行但该父提交没有。
    • 只有 merge commit 才有 combined diff;fast-forward merge 不会产生 merge commit,因此看不到 combined diff。
    • --cc 是“压缩版”,只保留“所有父提交都改到的行”,干净合并时常见空输出;想看完整 combined diff 用 -c 或 =—diff-merges=combined=。
    • 若两个分支改动互不重叠(文件层面),combined diff 可能为空,但 git show -m <merge> 仍能分别看到相对每个父提交的 diff。
  • dense combined diff

    • --cc 等价于 --diff-merges=dense-combined -p=(见 =git show 手册)。
    • 基于 combined diff:仍然只列出“所有父提交都改过的文件”,并用 diff --cc / @@@ / 多列前缀的格式呈现。
    • 进一步“压缩”输出:会省略那些“父提交只有两种版本,而合并结果不做修改只是选了其中一个版本”的 hunk。
      • 直观理解:如果合并结果只是“挑一边”,就被认为不够“有信息量”,会被折叠掉。
      • 因此干净合并时常见空输出;真正发生冲突并手动解决时更容易看到内容。
    • 想看完整细节改用 =-c=;想看每个父提交各自的 diff 用 =-m=。
  • 直观理解

    #!/usr/bin/env bash
    set -euo pipefail
     
    workdir="$(mktemp -d)"
    git -C "$workdir" init -q
    git -C "$workdir" config user.name "demo"
    git -C "$workdir" config user.email "demo@example.com"
     
    cat > "$workdir/f.txt" <<'EOF'
    L01
    L02
    L03
    L04
    L05
    L06
    L07
    L08
    L09
    L10
    L11
    L12
    L13
    L14
    L15
    L16
    L17
    L18
    L19
    L20
    EOF
     
    git -C "$workdir" add f.txt
    git -C "$workdir" commit -q -m "base"
     
    git -C "$workdir" checkout -q -b left
    sed -i 's/^L03$/L03-left/' "$workdir/f.txt"
    sed -i 's/^L15$/L15-left/' "$workdir/f.txt"
    git -C "$workdir" commit -q -am "left"
     
    git -C "$workdir" checkout -q -b right master
    sed -i 's/^L03$/L03-right/' "$workdir/f.txt"
    sed -i 's/^L15$/L15-right/' "$workdir/f.txt"
    git -C "$workdir" commit -q -am "right"
     
    git -C "$workdir" checkout -q left
    git -C "$workdir" merge -q right || true
     
    # 冲突解决:一处“选左边”,另一处写新内容
    cat > "$workdir/f.txt" <<'EOF'
    L01
    L02
    L03-left
    L04
    L05
    L06
    L07
    L08
    L09
    L10
    L11
    L12
    L13
    L14
    L15-merged
    L16
    L17
    L18
    L19
    L20
    EOF
     
    git -C "$workdir" add f.txt
    git -C "$workdir" commit -q -m "merge resolve"
     
    echo "=== -c (combined) ==="
    git -C "$workdir" show -c HEAD
     
    echo
    echo "=== --cc (dense combined) ==="
    git -C "$workdir" show --cc HEAD
    Auto-merging f.txt
    CONFLICT (content): Merge conflict in f.txt
    Automatic merge failed; fix conflicts and then commit the result.
    === -c (combined) ===
    commit c632e6e359ca7d9f65ba44791ad484bca762bdc4
    Merge: 9892f30 6ba6d59
    Author: demo <demo@example.com>
    Date:   Sun Dec 21 19:16:36 2025 +0800
     
        merge resolve
     
    diff --combined f.txt
    index 4b70f4c,5db724c..4c4eb9f
    --- a/f.txt
    +++ b/f.txt
    @@@ -1,6 -1,6 +1,6 @@@
      L01
      L02
     -L03-right
     +L03-left
      L04
      L05
      L06
    @@@ -12,7 -12,7 +12,7 @@@ L1
      L12
      L13
      L14
    - L15-left
     -L15-right
    ++L15-merged
      L16
      L17
      L18
     
    === --cc (dense combined) ===
    commit c632e6e359ca7d9f65ba44791ad484bca762bdc4
    Merge: 9892f30 6ba6d59
    Author: demo <demo@example.com>
    Date:   Sun Dec 21 19:16:36 2025 +0800
     
        merge resolve
     
    diff --cc f.txt
    index 4b70f4c,5db724c..4c4eb9f
    --- a/f.txt
    +++ b/f.txt
    @@@ -12,7 -12,7 +12,7 @@@ L1
      L12
      L13
      L14
    - L15-left
     -L15-right
    ++L15-merged
      L16
      L17
      L18

git-reset

git reset --hard HEAD~1 用来回退一个 commit

git-notes

Git Notes: git’s coolest, most unloved feature - Tyler Cipriani

magit 支持这个功能 Notes (Magit User Manual)

git-blame

  • --reverse <start>..<end>

    Walk history forward instead of backward. Instead of showing the

    revision in which a line appeared, this shows the last revision in

    which a line has existed. This requires a range of revision like

    <start>..<end> where the path to blame exists in <start>. git blame

    —reverse <start> is taken as git blame —reverse <start>..HEAD for

    convenience.

git-remote

添加远程仓库

使用 git remote add 命令:

# 基本语法
git remote add <远程名> <仓库URL>
 
# 示例
git remote add origin https://github.com/user/repo.git
git remote add origin git@github.com:user/repo.git

或者编辑 .git/config 文件:

[remote "origin"]
    url = https://github.com/user/repo.git
    fetch = +refs/heads/*:refs/remotes/origin/*

常用命令

# 查看现有远程仓库
git remote -v
 
# 修改远程仓库 URL
git remote set-url origin new-url
 
# 删除远程仓库
git remote remove origin
 
# 重命名远程仓库
git remote rename old-name new-name

注意事项:

  • 确保 URL 格式正确
  • 检查访问权限
  • HTTPS 或 SSH 协议选择
  • 远程名称通常用 “origin”

验证配置

# 测试连接
git fetch origin
 
# 查看远程分支
git branch -r
 
# 查看详细配置
git remote show origin

git-fetch

遇到了 jujutsu 在 git fetch 的时候给我的警告:

Warning: Ignored refspec `+refs/pull/*/head:refs/pullreqs/*` from `origin`: only refs/heads/ is supported for refspec sources

在这里解释一下

这条 +refs/pull/*/head:refs/pullreqs/* 针对的是 GitHub 暴露的 PR 头引用 refs/pull/<ID>/head,属于 GitHub 的命名约定,并非通用 Git 特性。

(docs.github.com (https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/checking-out-pull-requests-locally))

Git 本身不定义 PR 引用,各托管商各用一套:如果远端不是 GitHub,这条 refspec 会抓不到任何东西。

GitLab 的等价路径是 refs/merge-requests/*/head,常见写法:fetch = +refs/merge-requests/*/head:refs/remotes/origin/merge-requests/*。

https://docs.gitlab.com/user/project/merge_requests/merge_request_troubleshooting/

Azure DevOps(Azure Repos)通常提供 refs/pull/*/merge(试合并结果)甚至 refs/pull/*/head,对应 refspec 可写成 +refs/pull/*/merge:refs/remotes/origin/pull/*。

https://stackoverflow.com/questions/67715404/git-fetch-pull-requests-from-azure-devops

  • 在 refspec 里写成 +<src>:<dst>,前面的 + 表示“允许强制更新”这条映射,不做快进检查。
    • 没有 + 时,如果 <dst> 需要回退(非 fast-forward),git fetch 会拒绝更新该引用并报错。
    • 默认的远端抓取 refspec 就带 +,这样远端即便被强推重写历史,本地的远端追踪分支也会被同步回退。
    • CLI 等价:git fetch origin —force 会对所有 refspec 生效;而前缀 + 是按条目单独生效。
    • 推送时的 refspec 也用同样语法:+master:master 表示只对这一条执行 force-push。

git-push

#更新远程分支:
git push origin 新分支名称
# 删除远程的旧分支:
git push origin --delete 旧分支名称
#设置上游分支:
git push --set-upstream origin 新分支名称

git-pull

因为 jj 没有区分 pull 和 fetch,所以我其实不太了解这个东西

Integrate changes from a remote repository into the current branch.

First, git pull runs git fetch with the same arguments (excluding merge

options) to fetch remote branch(es). Then it decides which remote branch

to integrate: if you run git pull with no arguments this defaults to the

upstream for the current branch. Then it integrates that branch into the

current branch.

git-cherry-pick

git-diff

Git has four main diff algorithms: Myers, Minimal, Patience, and Histogram, with Myers being the default.

Patch

补丁就是用 diff 生成出来的

所以 jj-diff 无法提供这样可以 apply 的 patch 吗?

Algorithms

  • Minimal

    Myers 算法的改进版本,它尝试生成尽可能小的 diff,即使计算时间更长。

  • Patience

    一种更高级的算法,可以产生比 Myers 更易读的差异比较结果。

  • Histogram

    耐心算法的扩展,尝试识别频繁出现的常见行并降低其优先级,对于某些类型的更改,可以产生更易读的差异比较结果。

组成

  • about

    这个东西之前没注意到过,然后 jj 的 diff —git 输出没有这个功能

    about 不是 Magit 新加的东西,它对应的是 unified diff 里 hunk 头这行的可选尾巴:

    @@ -k,l +n,m @@ TEXT

    这里的 TEXT 在 git help diff 里就写明了:hunk header 会“提到该 hunk 所在的函数名”。

    它的来源更早,跟 GNU diff -p/“show C function” 那套传统一致(所以不是近期 Git 才加入的)。

    你对它没印象通常是因为它不一定会出现:只有当 Git 能用 diff.<driver>.xfuncname(见 git help gitattributes “Defining a custom hunk-header”)

    从改动位置向上找到匹配的“函数/章节行”时才会填;匹配不到就空着,所以 about 常常是 nil。

git-apply

Apply a patch to files and/or to the index

可以用 --check 来查看一个 patch 的合法性

可以用 --stat 来从一个 patch 中提取 stat

git-rebase

.

git-revert

.

git-fsck

git-format-patch

要是哪天我能用上这东西我也就出息了

git-hook

Git hooks 是在特定 Git 事件发生时自动执行的脚本,位于 .git/hooks/ 目录。

客户端 hooks

  • pre-commit

    提交前执行,用于代码检查、格式化等。退出非零值会中止提交。

  • prepare-commit-msg

    在编辑器打开前修改提交信息模板。

  • commit-msg

    检查提交信息格式,常用于强制提交信息规范。

  • post-commit

    提交完成后执行,用于通知或日志记录。

  • pre-push

    推送前执行,可用于运行测试或检查。

服务端 hooks

  • pre-receive

    接收推送前执行,可拒绝整个推送。

  • update

    对每个分支执行,可单独拒绝某个分支的更新。

  • post-receive

    推送完成后执行,常用于部署、通知等。

使用方法

# hooks 位于
.git/hooks/
 
# 创建 hook(去掉 .sample 后缀)
mv .git/hooks/pre-commit.sample .git/hooks/pre-commit
chmod +x .git/hooks/pre-commit

注意事项

  • hooks 不会被 git 跟踪,需要手动分发或使用工具管理
  • 可以用 core.hooksPath 配置自定义 hooks 目录
  • 常用工具:husky、pre-commit 等

git-submodule

Resources

Working with submodules - The GitHub Blog

Git - Submodules

gitattributes

Defining attributes per path

git-bundle

概述

git bundle 用于把提交历史打包成单个 .bundle 文件,适合离线传输和网络受限场景。

常用命令

  • 创建 bundle

    # 打包整个分支
    git bundle create repo.bundle main
     
    # 打包增量(接收方已包含 old-tip)
    git bundle create update.bundle old-tip..main
  • 导入 bundle

    # 直接克隆
    git clone /path/to/repo.bundle repo-b
     
    # 或在已有仓库拉取
    git fetch /path/to/repo.bundle main:bundle-main
    git checkout bundle-main
  • 检查 bundle

    git bundle list-heads /path/to/repo.bundle
    git bundle verify /path/to/repo.bundle

注意事项

  • *增量包有前置依赖*:A..B 形式要求接收方已具备 A
  • *bundle 可作为只读 remote*:可 fetch/pull,不可 push
  • *建议单独 remote 名称*:例如 git remote add bundle /path/to/repo.bundle,避免覆盖常规 origin
  • *bundle 不是动态远端*:有新提交时需要重新生成并传输新的 bundle 文件。

Git Internals

Packfiles

https://git-scm.com/book/en/v2/Git-Internals-Packfiles

git extensions

git-annex

git-annex: 使用 git 管理文件,而无需将其内容提交到 git 中。

Git-Annex | Hacker News

Git Annex Is The Coolest Program You’ve Never Heard Of - YouTube

git-lfs

Git Large File Storage | Git Large File Storage (LFS) replaces large files su…

Learning About Git Large File System (LFS) | by Amy Li | The Startup | Medium

Managing large files - GitHub Docs

lfs-s3

把大文件托管到代码托管平台貌似不是一件便利的事情

但是我们可以用自己配置的存储后端!

这个项目是最新的项目,而且实现方式貌似更加简单

Introduction chat

下面是关于 Git LFS(Git Large File Storage)的详尽介绍,帮助你了解它的原理、使用场景、安装配置及常用命令等。

  1. 什么是 Git LFS

    • Git LFS 是 Git 的一个扩展,用于在版本库中管理大体积文件(如音视频、二进制资源、数据集等)。
    • 它将大文件本身存储在专门的存储服务器(LFS Server)上,而在 Git 仓库里只保留一个指向这些大文件的“指针”文件,从而避免 Git 仓库体积暴涨。
  2. 为什么需要 Git LFS

    • 传统 Git 会把所有文件都完整存入 .git 目录,导致仓库体积随大文件增加而迅速膨胀。
    • 克隆、拉取、推送都会变得很慢,也不容易回滚到历史版本。
    • Git LFS 可显著减小仓库大小,加快 Git 操作。
  3. Git LFS 的工作原理

    1)在本地提交大文件时:

    • Git LFS 钩子(pre-push 或 clean filter)会拦截符合规则的大文件,替换为一个小型的指针文件(包含文件 ID、大小、下载地址等元信息)。

    2)推送到远程时:

    • 指针文件随 Git 普通流程推到 Git 服务器。
    • 大文件内容则通过 LFS 协议上传到 LFS 服务器(可以是 GitHub/GitLab 内置的 LFS 服务,也可以自行部署)。

    3)克隆或检出时:

    • Git 拉取指针文件后,Git LFS 会自动(或按需)下载实际的文件内容到工作区。
  4. 安装与初始化

    1)安装

    • macOS(Homebrew):

      brew install git-lfs
    • Windows(Chocolatey 或官方安装包):

      choco install git-lfs
    • Linux(包管理器或二进制包):

      apt-get install git-lfs

      或下载并解压官方 Release。

    2)配置全局钩子

    执行一次:

    git lfs install

    该命令会在 ~/.gitconfig 或仓库 .git/config 中添加相应的 filter 钩子。

  5. 常用命令

    1)指定跟踪某类文件

    git lfs track “.psd”

    git lfs track “data/
    .zip”

    该命令会在仓库根目录生成或更新 .gitattributes 文件。

    2)查看跟踪规则

    git lfs track

    3)移除跟踪

    git lfs untrack “*.psd”

    4)添加、提交、推送同普通 Git

    git add .gitattributes

    git add path/to/large.file

    git commit -m “add large file”

    git push

    5)查看 LFS 状态

    git lfs status

    6)列出已上传的大文件引用

    git lfs ls-files

    7)下载/拉取 LFS 文件

    • 在 clone 后自动下载(默认行为)。
    • 或者手动执行:

      git lfs pull

      git lfs fetch —all
  6. 与远程仓库的配合

    • GitHub、GitLab、Bitbucket 等平台都对 Git LFS 提供官方支持,你只需在仓库中开启 LFS 功能即可。
    • 免费额度和计费:

      这些平台通常对 LFS 存储和带宽有免费额度,超过后可能需要付费。
    • 私有部署:

      可结合开源项目(如 Git LFS Server、Minio 等)搭建自建 LFS 服务。
  7. 注意事项

    • .gitattributes 文件要纳入版本控制,确保团队成员都使用相同的跟踪规则。
    • LFS 对历史大文件的清理需要配合工具(如 git lfs prune、git gc 或 BFG Repo-Cleaner 等)。
    • 在切换分支、执行 git checkout 时,LFS 会自动下载对应版本的大文件,可能造成网络请求或延迟。
    • 避免频繁修改同一个大文件,否则 LFS 存储也会不断累积多份历史版本。
  8. 常见问题

    • “LFS objects missing from server”

      请检查远程 LFS 服务是否启用、访问凭据是否正确。
    • 带宽/存储超限

      评估团队使用场景,必要时升级配额或自建存储。
    • 大文件未被正确追踪

      确认 .gitattributes 规则匹配到文件,并重新 git add 后提交。
  9. 参考资源

通过以上介绍,你应该对 Git LFS 的动机、原理、使用方法与注意事项有了全面的了解。开始使用时,可先在测试仓库里试验一遍,熟悉工作流后再在正式项目中推广。

git-flow

Using git-flow to automate your git branching workflow

gitflow-avh

GitHub - nvie/gitflow: Git extensions to provide high-level repository operat…

gitflow-cjs

AUR (en) - gitflow-cjs

GitHub - CJ-Systems/gitflow-cjs: CJS Edition of the git extensions to provide…

avh 版本已经不再维护了,现在这个是主流版本

为什么 GitFlow”失宠”了?

CI/CD 兴起:持续部署让复杂分支变得不必要

敏捷开发:快速迭代不需要复杂流程

小团队增多:独立开发者和小团队更喜欢简单方案

云服务普及:部署成本降低,可以更频繁发布

git-filter-repo

Contribute to Git

https://git-scm.com/docs/SubmittingPatches

或者有这个变通办法?

gitgitgadget

Platforms providing Git hosting

github

sourcehut

gitea

gitlab

Codeberg

Gitee

sourceware

Forks and reimplementations

git

git

jgit

gix

Events

Git Merge

https://git-merge.com/

有很多视频可以在 gitbutler 的频道看到