Copier 总报错?一篇讲透排查、升级、治理和团队落地
如果你已经能跑copier copy但一到check-update、update就反复踩坑这通常不是工具本身不稳定而是缺少一套可复用的工程闭环。本文把最核心的 5 个问题合并成一篇最小闭环怎么跑、升级为什么失败、报错怎么排查、为什么要打 tag、如何接入团队 CI。1. 先把最小闭环跑通Copier 在团队里要稳定最低闭环是copy - 模板发布(tag) - check-update - update - 验证提交基础检查copier-vgit--version最小命令copier copy ./my_copier_template ./destination-dproject_namedemo copier check-update ./destination copier update ./destination--defaults成功判定cd./destinationgitstatus2. 为什么升级经常失败高频不是“命令拼错”而是以下四类问题路径混用./destination和../destination在不同 cwd 下可能是两个目录。引用丢失.copier-answers.yml缺少可追踪模板版本信息。版本漂移模板仓库没打 tag更新来源不稳定。变量断档新增必填问题没有 default也没在命令里-d传值。建议固定排查顺序路径 - answers - 版本(tag) - 变量(default/-d)3. 报错场景与直接修复场景 1Cannot obtain old template references先检查目标目录是否正确。检查.copier-answers.yml是否完整、是否被手工破坏。场景 2Question is required在模板里给新变量加default。或执行更新时补参数-d keyvalue。场景 3更新后冲突检查是否出现.rej或冲突标记。冲突必须人工处理处理后再提交。4. 模板仓库为什么必须打 tag不打 tag 也许“偶尔可用”但不适合团队长期维护。打 tag 的价值可追踪明确目标项目基于哪个模板版本。可回滚出问题时能回到稳定版本。可协作多人对版本语义有共识。推荐发布动作gitadd.gitcommit-mtemplate: release v0.0.5gittag v0.0.5gitpush --follow-tags5. 团队落地培训 CI 自动化并行个人可用不等于团队可用。建议双线并行培训线统一路径、统一版本规则、统一排障顺序。自动化线定时check-update有更新再update有冲突就阻断。脚本入口可以统一放在scripts/copier-update-check.sh完整脚本如下可直接复制保存为scripts/copier-update-check.sh#!/usr/bin/env bashset-euopipefailDESTINATION_PATH./destinationCONFLICTinlinePRERELEASESfalseSKIP_TASKSfalseCHECK_ONLYfalseDATA_FILEDATA_PAIRS()usage(){catEOF Usage: copier-update-check.sh [options] Options: --destination-path path Target project path (default: ./destination) -d, --data keyvalue Repeatable data pair for copier update --data-file path YAML/JSON data file for copier update --conflict inline|rej Conflict strategy (default: inline) --prereleases Include prerelease versions --skip-tasks Skip copier tasks during update --check-only Only check update availability -h, --help Show this help EOF}require_command(){localname$1if!command-v$name/dev/null21;thenechoERROR: Required command not found:$name2exit1fi}run_copier(){sete copier$localcode$?set-ereturn$code}while[[$#-gt0]];docase$1in--destination-path)DESTINATION_PATH$2;shift2;;-d|--data)DATA_PAIRS($2);shift2;;--data-file)DATA_FILE$2;shift2;;--conflict)CONFLICT$2;shift2;;--prereleases)PRERELEASEStrue;shift;;--skip-tasks)SKIP_TASKStrue;shift;;--check-only)CHECK_ONLYtrue;shift;;-h|--help)usage;exit0;;*)echoERROR: Unknown option:$12;usage2;exit1;;esacdonerequire_command copier require_commandgit[[-d$DESTINATION_PATH]]||{echoERROR: Destination path not found:$DESTINATION_PATH2;exit1;}[[$CONFLICTinline||$CONFLICTrej]]||{echoERROR: --conflict must be inline or rej2;exit1;}check_args(check-update$DESTINATION_PATH--quiet)[[$PRERELEASEStrue]]check_args(--prereleases)ifrun_copier${check_args[]};thencheck_code0;elsecheck_code$?;fiif[[$check_code-eq0]];thenechoNo template update availableexit0fiif[[$check_code-ne2]];thenechoWARN: check-update returned unexpected code:$check_code2diag_args(check-update$DESTINATION_PATH--output-format plain)[[$PRERELEASEStrue]]diag_args(--prereleases)sete;copier${diag_args[]};set-eexit1fiechoTemplate update available[[$CHECK_ONLYtrue]]exit2update_args(update$DESTINATION_PATH--defaults--conflict$CONFLICT)[[$PRERELEASEStrue]]update_args(--prereleases)[[$SKIP_TASKStrue]]update_args(--skip-tasks)[[-n$DATA_FILE]]update_args(--data-file$DATA_FILE)forpairin${DATA_PAIRS[]};doupdate_args(-d$pair);doneifrun_copier${update_args[]};thenupdate_code0;elseupdate_code$?;fi[[$update_code-eq0]]||{echoERROR: copier update failed with code$update_code2;exit1;}answers_file$DESTINATION_PATH/.copier-answers.yml[[-f$answers_file]]||{echoERROR: Answers file missing after update:$answers_file2;exit1;}grep-Eq^_commit:[[:space:]]*[^[:space:]]$answers_file||{echoERROR: Answers file does not contain a valid _commit entry2;exit1;}rej_files$(find$DESTINATION_PATH-typef-name*.rej-print)if[[-n$rej_files]];thenechoERROR: Found .rej files after update. Resolve and remove them before merge.2printf%s\n$rej_filesexit1fipushd$DESTINATION_PATH/dev/nullsetegitgrep-n--.marker_code$?set-epopd/dev/null[[$marker_code-eq0]]{echoERROR: Inline merge conflict markers detected after update2;exit1;}[[$marker_code-gt1]]echoWARN: Unable to scan conflict markers with git grep2echoUpdate completed successfullygit-C$DESTINATION_PATHstatus--short--branch执行示例# 首次使用前赋予执行权限chmodx ./scripts/copier-update-check.sh# 只检查是否有新版本./scripts/copier-update-check.sh --destination-path ./destination --check-only# 自动更新并执行内置检查./scripts/copier-update-check.sh --destination-path ./destination目标是让升级过程“可重复、可追踪、可审计”。6. 结论与可执行清单如果你只做三件事优先级如下固定升级路径不混用相对目录。模板发布必须 commit tag。升级流程固定为check-update - update - 验证。做到这三点Copier 基本就能从“能跑”变成“可治理”。