跳到正文

我所理解的 Git 最佳实践(7):没有远端时怎样用 Git 维护项目

我所理解的 Git 最佳实践

  1. 从工作区到一条可审阅的提交
  2. 什么时候用 rebase,什么时候用 merge
  3. 利用 amend 和 rebase 保持提交简洁
  4. 在 GitHub 和 GitLab 配置 PR 的合入方式
  5. 共享测试分支与按需求部署
  6. 弄清工作树、嵌套仓库和远端的归属
  7. 没有远端时怎样用 Git 维护项目(当前)
  8. 为什么切到 orphan 分支后源码不见了
  9. 先量清仓库体积,再决定怎么迁移
  10. 用 Node.js 检查 Git 路径,提前发现 Windows 检出问题

手上有个小项目,暂时没有可用的远端,也可以在本地用 Git 提交、切分支和查看历史。少了远端副本,换机器、磁盘损坏或误删目录时就得靠另外保存的文件恢复。内部项目尤其要确认备份能放到哪里,公网仓库即使设为私有,也要遵守公司的资料存储规则。

在本地照常提交和切分支

新项目可以用 git init -b main 初始化。先把依赖、构建产物和本机配置排除,再用 git status --short、git add 和 git diff --cached 核对要提交的改动。试验可以用 git switch -c experiment 单独开分支,阶段成果也可以用 tag 标记。这些操作只需要本地仓库,日常提交以后可以用 git log 和 git show 回看。

想把项目搬到另一台机器继续开发时,先决定要带的是当前文件、一版已提交的源码,还是能接着用的历史。

目录、提交快照和仓库历史

恢复需求 文件形式 保存范围
解压后得到当前目录 普通 zip 由压缩工具的包含和排除规则决定,可以保留尚未跟踪的文件
交付某个已提交的版本 git archive 选定提交的文件树,可用 export-ignore 排除交付不需要的受管文件
恢复后继续查看和维护历史 git bundle 被选中引用及其可达 Git 对象,具体覆盖范围取决于打包参数

目录压缩、Git Archive 和 Git Bundle 的保存范围

普通压缩工具按目录收集文件,不会读取 .gitignore。这一点在备份未提交的笔记或脚本时很有用,也容易把 .git、本机配置、凭据和依赖一起带进去。我会在项目外生成文件,并先检查压缩包的文件清单。如果接收方只要某个已审过的源码版本,可以直接从指定提交导出。

git status --short
git archive --format=zip --output=../project-source.zip HEAD

这里的 HEAD 是已提交的版本。工作树和索引里尚未提交的改动都不会进入包。已经提交的文件也不会因为出现在 .gitignore 里就被排除,交付专用的排除规则可以写在目标版本的 .gitattributes 中,用 export-ignore 指定。交付前检查目标提交,并核对解压后的文件清单。如果仓库有 submodule,还要为其中需要交付的源码另设入口。

用 Bundle 保留可以继续开发的版本

在有完整历史的本地仓库里,先用 git status --short --branch 看还有哪些工作没有提交。应当进入历史的改动可以先按范围提交,尚未准备提交的内容另存一份,别为了生成备份把凭据或缓存加进历史。下面从项目顶层运行,假定 main 已有提交,且输出文件名尚未使用。打包期间避免移动需要备份的引用,以免记录的提交与文件里的分支不一致。

git rev-parse --verify refs/heads/main &&
git bundle create ../project-backup.bundle --all &&
git bundle verify ../project-backup.bundle &&
git bundle list-heads ../project-backup.bundle

第一条记录备份时的 main,用于恢复后对照。如果项目使用其他主分支名,下面的恢复命令也要一起调整。

--all 会包含本机引用所能到达的历史。除了项目分支和 tag,也要留意远端跟踪引用、当前 refs/stash 和工具保存的本地引用。旧 stash 如果只剩 reflog 记录,就不能指望它出现在包里。只想交接某个分支时,可用 refs/heads/main 替换 --all,并核对包里是否还有恢复需要的其他分支或标签。浅克隆或部分克隆要先补齐所需历史和对象,或者换一份完整仓库做备份。共享前还要检查打进去的历史是否适合接收者查看。

用一份独立克隆验证恢复

verify 会检查格式,以及执行它的仓库是否具备包所要求的前置提交。如果为了减小体积只打了一个提交区间,源仓库本来就有那些前置提交,在那里验证通过,换一台新机器却可能无法独立克隆。准备单文件离线备份时,我会从这份文件克隆到新的空目录,再核对目标分支。

git clone -b main ../project-backup.bundle ../project-restored &&
git -C ../project-restored fsck --full &&
git -C ../project-restored rev-parse HEAD &&
git -C ../project-restored branch -a &&
git -C ../project-restored tag --list

这里假定备份里有 main。将恢复后的提交 ID 与备份时的 main 对照,再检查要用的文件和 tag。普通 clone 主要面向日常使用的分支和标签,其他引用要按实际恢复目标另行检查和取回。恢复副本的 origin 可能指向本地 bundle 文件,未来要推到团队仓库时,需要重新配置获准的远端。

如果是向已有仓库找回一条旧分支,先确认该仓库具备增量包所需的前置提交,再把包里的分支取到单独的引用命名空间,检查后才决定如何合回。以下假定 refs/remotes/bundle/ 专供这次恢复使用。

git fetch ../project-backup.bundle 'refs/heads/*:refs/remotes/bundle/*'

工作树里未提交的修改、本地配置和 hooks 需要独立保存。子模块的仓库也是独立的,主仓库只记录它所指向的提交。Git LFS 则把实际媒体放在 Git 对象库之外,恢复时除了检查指针,还要打开依赖的实际文件。

更新备份先保住上一份可用版本

公司允许把该项目存到企业飞书时,个人备份可以放云盘,交接给同事时也可以附在项目说明旁,写清对应分支、提交和恢复方法。我会用项目名加日期来辨认版本。脚本先把每次输出写到自己的临时目录,生成和校验都成功后再上传。只检查「日期文件名还不存在」再写文件,挡不住两个定时任务同时开始。

如果远端只保留最新备份,也先留着上一份可用文件。等新文件上传并核对,从存储位置取回后还能恢复,再按项目和版本淘汰旧文件。这样生成失败、传输中断或上传错目录时,仍有上一份可用。

如果只需要迁移到最新版本,保留最近一次确认可用的备份就能满足目标。如果还要能回到某次交付之前,按需要保留多个版本,或者明确保留当时的 tag 和所需对象。多人开始经常同时改代码时,再用有分支权限、评审和 CI 的共享仓库组织日常协作。文件备份仍可以留作独立恢复入口。