Git archive 导出提交快照:工作区改了,压缩包为什么仍是旧内容

10-01 4阅读

先说清交付的是哪个版本

把源码发给同事时,直接压缩当前目录容易混进临时文件,也可能带上尚未验证的修改。git archive 可以按指定提交导出文件树。关键在于它读取的是选定版本:即使编辑器里已经改成新内容,只要指定的提交没有变化,导出的文件仍然来自那个提交。

因此验收不能只问“目录里有没有这个文件”,还要问“它是否存在于目标提交中”。已跟踪文件的未提交修改不会覆盖提交内容;刚加入暂存区、尚未提交的新文件也不在旧提交里。未跟踪文件默认不会加入,但命令另有显式追加文件的选项,本文不使用这些扩展。

用导出规则控制包里留下什么

仓库可能需要保存测试资料,却不需要把它们放进交付包。可以在 .gitattributes 中为相应路径设置 export-ignore。它控制归档是否包含该路径,不负责删除仓库文件,也不会让已经提交的历史内容消失。需要修复误提交的秘密时,排除导出远远不够。

默认情况下,归档读取目标树中的 .gitattributes。只在工作区临时新增一条排除规则,不能期待它改变旧提交的默认导出。若明确需要使用工作区属性,可以选择 --worktree-attributes;但这会使导出依赖另一份输入,交付记录也应说明所用规则来自哪里。

在临时仓库完整验证一次

下面需要 Bash、Git、tar 和常见命令行工具,保存为脚本后用 bash 运行。脚本只在新建临时目录操作,使用演示身份创建本地提交,不连接远端。归档与解压目录都在仓库外;退出时只清理本次创建的目录,因此演示结果主要通过输出与断言验收。

脚本先提交一个正文文件、一个内部资料文件和两条导出规则,再制造工作区修改、暂存新增文件与未跟踪文件。最后还在工作区追加“排除正文”的规则。导出时固定首次提交编号,实际检验它是否仍使用该提交里的内容与规则。

Git archive 导出提交快照:工作区改了,压缩包为什么仍是旧内容

AI生成概念示意图,非真实界面

#!/usr/bin/env bash
set -euo pipefail

demo_root=$(mktemp -d "${TMPDIR:-/tmp}/git-archive-demo.XXXXXX")
cleanup() {
  local status=$?
  trap - EXIT
  rm -rf -- "$demo_root"
  exit "$status"
}
trap cleanup EXIT

unset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE GIT_COMMON_DIR
unset GIT_OBJECT_DIRECTORY GIT_ALTERNATE_OBJECT_DIRECTORIES GIT_CONFIG_COUNT
export GIT_CONFIG_NOSYSTEM=1
export GIT_CONFIG_GLOBAL=/dev/null
export GIT_ATTR_NOSYSTEM=1
repo="$demo_root/repo"
git -c init.defaultBranch=main init -q --template= "$repo"
git -C "$repo" config user.name 'Archive Demo'
git -C "$repo" config user.email 'demo@example.invalid'
git -C "$repo" config core.attributesFile /dev/null
git -C "$repo" config core.autocrlf false

printf 'committed-v1\n' > "$repo/app.txt"
printf 'internal fixture\n' > "$repo/private.txt"
printf 'private.txt export-ignore\n.gitattributes export-ignore\n' > "$repo/.gitattributes"
git -C "$repo" add app.txt private.txt .gitattributes
git -C "$repo" -c commit.gpgSign=false commit -qm 'Create demo snapshot'
revision=$(git -C "$repo" rev-parse HEAD)

printf 'working-copy-v2\n' > "$repo/app.txt"
printf 'staged but uncommitted\n' > "$repo/staged.txt"
git -C "$repo" add staged.txt
printf 'untracked note\n' > "$repo/scratch.txt"
printf 'app.txt export-ignore\n' >> "$repo/.gitattributes"

git -C "$repo" archive --format=tar --prefix=demo/ \
  --output="$demo_root/snapshot.tar" "$revision"
mkdir "$demo_root/unpacked"
tar -tf "$demo_root/snapshot.tar"
tar -xf "$demo_root/snapshot.tar" -C "$demo_root/unpacked"

git -C "$repo" show "$revision:app.txt" > "$demo_root/expected.txt"
cmp "$demo_root/expected.txt" "$demo_root/unpacked/demo/app.txt"
test "$(cat "$demo_root/unpacked/demo/app.txt")" = 'committed-v1'
for absent in private.txt staged.txt scratch.txt .gitattributes .git; do
  test ! -e "$demo_root/unpacked/demo/$absent"
done
printf 'exported: %s\n' "$(cat "$demo_root/unpacked/demo/app.txt")"
printf 'archive checks passed\n'

成员列表应只有 demo/ 和 demo/app.txt,随后输出 exported: committed-v1 与 archive checks passed。正文文件仍是首次提交的内容,说明工作区修改没有混入;内部资料没有进入包,说明已提交的排除规则生效;工作区追加的规则没有把正文排除,说明默认属性来源仍是目标版本。

版本来源与交付完整性分别检查

保存完整提交编号比只写分支名称更明确,因为分支可以继续前进。正式交付时可以同时记录提交编号、归档参数与文件摘要,接收方才知道自己验收的是哪一份材料。这里比较解压正文与 git show 读出的目标内容,直接检查最关键的来源关系。

export-ignore 更适合按具体文件或明确的路径规则配置。属性文件的目录匹配规则与忽略文件规则存在差异;排除目录内的文件时,应核对官方规则与实际成员清单,不能只凭目录名看起来匹配便认定整个子树都处理好了。

另一个边界是本地额外属性:仓库的 info/attributes 等来源也可能影响归档结果。示例创建全新仓库并关闭全局配置与系统属性影响,便于观察提交内规则。生产构建若追求稳定输出,同样应控制执行环境,不能只固定提交编号便宣称所有字节必然一致。

源码快照也不等于可以直接运行的发布包。构建产物、依赖、子模块内容以及依赖外部存储的文件,需要按照项目实际交付流程另行处理。应在干净目录里验证接收方所需的入口文件与说明是否齐全,再决定这个归档是否已经满足交付要求。

最后把“文件应该存在”和“文件应该缺席”都写进检查。正文存在但版本错误,以及临时资料意外进入包,是两种不同失败。小型演示用逐项断言即可,实际项目可以保存允许交付的成员清单,让后续版本变更也能被认真复核。

参考资料

文章版权声明:除非注明,否则均为云鹊BLOG原创文章,转载或复制请以超链接形式并注明出处。