版本控制实战与自测

项目目标

在系统临时目录中的全新隔离仓库管理“提示词实验记录”,留下至少三个内容提交、一个基线证据提交、分支分叉、冲突解决、三类撤销和历史追踪的可核查证据。核心项目不连接远程,不使用真实数据,也不练 reset --hard、clean -fd、rebase 或强推。

本知识库的实际验证边界

本轮知识库建设没有执行以下项目,因为执行会创建嵌套仓库和 Git 提交,与当前任务“不创建提交”的约束冲突。PowerShell 围栏已做 AST 语法检查,Git 命令语义按 2.55.0 官方文档复核;学习者运行时仍要逐步检查实际输出。

实验分层

环境允许操作必需护栏验收
当前真实仓库status、diff、log、show 等只读审查不 add、不 commit、不切分支、不联网操作前后状态不变
核心临时仓库init、add、commit、branch、merge、restore、revert%TEMP% 下全新空目录;根路径相等;remote 为空至少三个内容提交、一次合并、三类撤销
离线远程实验bare remote、两个本地 clone、fetch/push另一个唯一临时目录;无账户与凭据能解释 push rejection 与远程跟踪引用
高风险可选branch -D 后用 reflog 恢复;hard reset、clean、rebase、force-with-lease仅可丢弃副本;每项需独立护栏只选做分支入口恢复,其余不在主线验收

第 0 阶段:创建并证明隔离边界

0.1 创建唯一空目录

完整复制下面这一块,而不是从 git init 单独开始:

$tempRoot = [IO.Path]::GetFullPath([IO.Path]::GetTempPath())
$labPath = Join-Path $tempRoot ("git-core-lab-" + [Guid]::NewGuid().ToString("N"))
if (Test-Path -LiteralPath $labPath) {
    throw "实验目录已存在,停止。"
}
New-Item -ItemType Directory -Path $labPath | Out-Null
$resolvedLab = (Resolve-Path -LiteralPath $labPath).Path
if (-not $resolvedLab.StartsWith($tempRoot, [StringComparison]::OrdinalIgnoreCase)) {
    throw "实验目录不在系统临时目录中,停止。"
}
if (@(Get-ChildItem -LiteralPath $resolvedLab -Force).Count -ne 0) {
    throw "实验目录不是空目录,停止。"
}
Set-Location -LiteralPath $resolvedLab
git rev-parse --show-toplevel 2>$null
if ($LASTEXITCODE -eq 0) {
    throw "实验目录已位于其他 Git worktree 内,停止。"
}
$resolvedLab

最后一行必须打印一个位于系统临时目录的全新绝对路径。若任何 throw 触发,不要绕过。

0.2 初始化确定的默认分支并复核根目录

git init --initial-branch=main
if ($LASTEXITCODE -ne 0) { throw "初始化实验仓库失败,停止。" }
git config set user.name "Git Learner"
git config set user.email "learner@example.test"
if ($LASTEXITCODE -ne 0) { throw "设置仓库局部身份失败,停止。" }
$gitRoot = [IO.Path]::GetFullPath((git rev-parse --show-toplevel).Trim())
if (-not [String]::Equals($gitRoot, $resolvedLab, [StringComparison]::OrdinalIgnoreCase)) {
    throw "Git 根目录与实验目录不一致,停止。"
}
$remoteNames = @(git remote)
if ($remoteNames.Count -ne 0) {
    throw "核心实验不应配置 remote,停止。"
}
git config get user.name
git config get user.email
git status --short --branch

这里使用仓库级身份,不修改全局 Git 配置。example.test 是保留测试域,不是真实邮箱。

第 1 阶段:建立最小项目、三个内容提交与基线证据

1.1 文件结构

创建以下虚构资产:

README.md
.gitignore
prompts/
  summary.md
eval/
  cases.json
evidence.md

PowerShell 建立目录与初始文件:

New-Item -ItemType Directory -Path prompts, eval | Out-Null
Set-Content -LiteralPath README.md -Encoding utf8 -Value @(
    "# Prompt history lab"
    ""
    "Only fictional data is allowed."
)
Set-Content -LiteralPath .gitignore -Encoding utf8 -Value @(
    ".env"
    ".venv/"
    "__pycache__/"
)
Set-Content -LiteralPath prompts/summary.md -Encoding utf8 -Value @(
    "# Summary prompt"
    ""
    "Return a concise summary in no more than 120 Chinese characters."
)
Set-Content -LiteralPath eval/cases.json -Encoding utf8 -Value @(
    "["
    "  {`"input`": `"Fictional meeting note A`", `"expected`": [`"decision`", `"owner`"]},"
    "  {`"input`": `"Fictional meeting note B`", `"expected`": [`"risk`", `"deadline`"]}"
    "]"
)
Set-Content -LiteralPath evidence.md -Encoding utf8 -Value "# Evidence log"

先验证 JSON 与秘密边界:

Get-Content -LiteralPath eval/cases.json -Raw | ConvertFrom-Json | Out-Null
git status --short
git check-ignore -v -- .env
git ls-files -- .env

最后一条应无输出,因为 .env 不应被跟踪。

1.2 三个原子内容提交

第一次提交初始结构:

git add -- README.md .gitignore prompts/summary.md eval/cases.json evidence.md
git diff --staged
git diff --staged --check
git commit -m "docs: initialize prompt history lab"
if ($LASTEXITCODE -ne 0) { throw "初始提交失败,停止。" }
git rev-parse HEAD

第二次只新增一个虚构评测样例,验证 JSON 后提交。第三次只改 README 的实验说明并提交。每次都执行:

git status --short
git diff
git diff --staged
git diff --check
git diff --staged --check
git log --oneline --decorate -3

把三次 commit ID、意图和验证结果写入 evidence.md,并用第四个纯文档提交保存基线证据;若同时改了无关文件,先重新拆分暂存区:

git add -- evidence.md
git diff --staged --check
git commit -m "docs: record baseline evidence"
if ($LASTEXITCODE -ne 0) { throw "基线证据提交失败,停止。" }
$pending = @(git status --porcelain)
if ($pending.Count -ne 0) {
    throw "进入分支练习前工作区必须干净,停止。"
}

第 2 阶段:确定地制造并解决冲突

2.1 形成分叉

创建功能分支,把 prompts/summary.md 的长度规则改为 80 字并提交:

git switch -c experiment/short-output
git add -- prompts/summary.md
git diff --staged
git commit -m "experiment: shorten summary limit"

切回确定的 main,把同一行改为 200 字并提交:

git switch main
git add -- prompts/summary.md
git diff --staged
git commit -m "experiment: expand summary limit"
git log --graph --oneline --decorate --all

2.2 合并、检查和退出路径

git merge experiment/short-output
if ($LASTEXITCODE -eq 0) {
    throw "预期的文本冲突没有发生,停止并检查两条分支是否修改了同一行。"
}
git status
$unmerged = @(git diff --name-only --diff-filter=U)
if ($unmerged.Count -ne 1 -or $unmerged[0] -ne "prompts/summary.md") {
    if (Test-Path -LiteralPath .git/MERGE_HEAD) { git merge --abort }
    throw "未解决路径不符合预期,已尝试中止合并。"
}
git diff

预期只有 prompts/summary.md 未解决。若分支方向、文件或前置状态不符合预期,立即执行:

git merge --abort
git status --short --branch

若继续,人工把长度规则改成一个有理由的最终值,并在 evidence.md 解释两边意图。确认冲突标记消失:

$markers = Select-String -LiteralPath prompts/summary.md -Pattern '^(<<<<<<<|=======|>>>>>>>)'
if ($markers) {
    throw "仍有冲突标记,停止提交。"
}
git add -- prompts/summary.md evidence.md
git diff --staged --check
git diff --staged
git commit -m "merge: reconcile summary length rules"
git log --merges --oneline
git log --graph --oneline --decorate --all

“没有冲突标记”只是语法完成;最终规则还必须与 eval/cases.json 和实验说明一致。

第 3 阶段:验证三类撤销

3.1 丢弃未暂存的工作区修改

在 README.md 末尾加入一句虚构文本,观察后恢复:

git diff -- README.md
git restore -- README.md
git status --short

说明:默认 restore 从 index 恢复工作区;此时 index 与 HEAD 相同,所以结果也等于当前提交版本。

3.2 取消暂存但保留内容

再次修改 README.md:

git add -- README.md
git diff --staged -- README.md
git restore --staged -- README.md
git diff -- README.md
git diff --staged -- README.md

工作区修改应仍在,staged diff 应为空。先保存输出,再用 git restore -- README.md 清理这次实验修改;可以把证据集中到后续文档提交,不能带着未提交的 evidence.md 修改进入 revert。

3.3 用 revert 撤销专门的普通提交

先创建一个明确错误、非 merge 的专用提交,再撤销它:

$pending = @(git status --porcelain)
if ($pending.Count -ne 0) {
    throw "revert 练习前工作区必须干净,停止。"
}
Add-Content -LiteralPath prompts/summary.md -Encoding utf8 -Value "Temporary wrong rule: expose secret tokens."
git add -- prompts/summary.md
git commit -m "experiment: add intentionally wrong rule"
if ($LASTEXITCODE -ne 0) { throw "错误规则提交失败,停止。" }
$revertTarget = (git rev-parse HEAD).Trim()
git show --stat --oneline $revertTarget
git revert --no-edit $revertTarget
if ($LASTEXITCODE -ne 0) {
    git status
    git revert --abort
    throw "revert 未按预期完成,已尝试中止。"
}
git log --oneline --decorate -3

验证原提交和反向提交都在历史中,而错误内容已从当前文件消失。若误选 merge commit 或遇到不理解的冲突,使用 git revert --abort,不要猜 -m 参数。

第 4 阶段:历史追踪与恢复入口

git log -- prompts/summary.md
git blame -- prompts/summary.md
git log -G "summary" --all -- prompts/summary.md
git log --graph --oneline --decorate --all
git reflog

在 evidence.md 回答:哪个提交引入 80 字规则、哪个提交引入 200 字规则、哪个提交解决冲突、哪个提交被 revert,以及每个结论的 patch 证据。

可选的 reflog 恢复练习只在本隔离仓库进行。这个步骤会强删一个带独有提交的实验分支,目的是制造“分支入口丢失”场景;不要在真实仓库照做:

$pending = @(git status --porcelain)
if ($pending.Count -ne 0) { throw "reflog 练习前工作区必须干净,停止。" }
git switch -c recovery/demo
Set-Content -LiteralPath recovery-note.md -Encoding utf8 -Value "Recover this fictional commit."
git add -- recovery-note.md
git commit -m "experiment: create recovery target"
if ($LASTEXITCODE -ne 0) { throw "恢复目标提交失败,停止。" }
$lostCommit = (git rev-parse HEAD).Trim()
git switch main
git branch -D recovery/demo
git reflog
$headReflog = @(git reflog --format='%H')
if ($lostCommit -notin $headReflog) {
    throw "HEAD reflog 中未找到目标提交,停止。"
}
git branch recovery/found $lostCommit
git show --stat --oneline recovery/found

git branch -D 是这里刻意限制在临时仓库的高风险动作。删除分支会删除该分支自己的 reflog;恢复线索来自切换和提交时留下的 HEAD reflog。不要 hard reset,目标是重新创建引用而不是移动当前分支。reflog 本地且会过期,不是永久备份。

只读真实仓库审查任务

若你有一个真实项目,只运行下列只读命令,写一份“不改变状态”的审查结论:

git rev-parse --show-toplevel
git status --short --branch
git diff --stat
git diff --staged --stat
git branch -vv
git remote
git log --graph --oneline --decorate -10

不要为了让输出好看而 add、commit、切分支、pull 或清理。若需要查看 URL,先确认不会泄露 token 或私人服务器信息。

远程扩展项目

核心项目刻意没有 remote。完成后可进入 远程协作与离线双克隆实验,用本地 bare remote 和两个 clone 模拟 fetch、push rejection 与整合;不需要账户、网络或真实凭据。

提交前真实任务清单

□ git status --short 的每一项都能解释
□ git diff 检查未暂存改动
□ git diff --staged 检查将提交内容
□ 没有凭据、个人信息、大文件、缓存和环境目录
□ 提交范围只包含一个清楚意图
□ 提交信息说明“做什么”,必要时正文解释“为什么”
□ 相关测试或文档检查已执行并记录结果

.gitignore 只影响未跟踪路径选择,不会从历史删除已提交文件。发现凭据已提交时,应停止分享、轮换凭据并按组织流程评估历史清理;不能只追加 ignore 规则。

自测

  1. working tree、index、对象数据库和 HEAD 各是什么?
  2. git diff、git diff --staged 与 git status 各补足哪类证据?
  3. 分支为什么可看作可移动引用,而不是目录副本?
  4. fast-forward 与三方合并的历史结构有什么不同?
  5. 冲突解决为什么需要业务判断和测试?
  6. restore、revert、reset 分别主要影响哪一层?
  7. 为什么本项目要求 revert 专门的普通提交?
  8. reflog 能帮助恢复什么,为什么不等于永久备份?
  9. .gitignore 不能解决哪类秘密泄露?
  10. origin/main 为什么不是服务器端实时状态?
  11. push rejection 为什么不能直接用强推“解决”?
  12. Agent 生成代码后,谁负责检查范围、凭据、测试和最终提交?

评分与验收

项目分值通过证据
隔离与秘密边界20临时绝对路径、根路径相等、remote 为空、虚构数据与局部身份
原子提交15至少三次清楚提交,均有 staged diff 与验证记录
分支与冲突20确定分叉、一次 merge 冲突、语义化解决、merge commit
三类撤销20restore 工作区、restore staged、revert 普通提交的前后证据
历史追踪15log、blame、-G 与 reflog 能回答具体问题
复盘质量10能用状态模型解释结果、限制和未验证项

达到 80/100 且“隔离与秘密边界”满分才算完成;安全项不能用其他分数抵消。

完成后回到 Git 目录。

参考资料

按 Git 2.55.0 官方文档核验,获取日期:2026-07-14。