Files
zhuangang/topics/devops/git/examples/git-lab/lab.py
T

86 lines
3.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Git 练习场:在临时目录里自动演示并验证常用操作。
覆盖:功能分支 + 合并、rebase 整理、revert 撤销已推送的提交、reset 后用 reflog 找回、cherry-pick 热修复。
运行:python lab.py(只需要本机安装 git,不会改动你的任何仓库)
"""
import subprocess
import tempfile
from pathlib import Path
def git(repo: Path, *args: str) -> str:
out = subprocess.run(["git", *args], cwd=repo, capture_output=True, text=True,
env={"GIT_AUTHOR_NAME": "dev", "GIT_AUTHOR_EMAIL": "dev@example.com",
"GIT_COMMITTER_NAME": "dev", "GIT_COMMITTER_EMAIL": "dev@example.com",
"HOME": str(repo), "PATH": "/usr/bin:/bin:/usr/local/bin"})
if out.returncode != 0:
raise RuntimeError(f"git {' '.join(args)} 失败:{out.stderr}")
return out.stdout.strip()
def commit(repo: Path, name: str, content: str, msg: str) -> str:
(repo / name).write_text(content)
git(repo, "add", name)
git(repo, "commit", "-q", "-m", msg)
return git(repo, "rev-parse", "--short", "HEAD")
def log(repo: Path, ref: str = "HEAD") -> list[str]:
return git(repo, "log", "--format=%s", ref).splitlines()
def main() -> None:
with tempfile.TemporaryDirectory() as tmp:
repo = Path(tmp)
git(repo, "init", "-q", "-b", "main")
commit(repo, "app.txt", "v1\n", "feat: 初始化项目")
# 1. 功能分支开发,期间 main 有新提交
git(repo, "switch", "-q", "-c", "feat/login")
commit(repo, "login.txt", "login\n", "feat(login): 登录表单")
commit(repo, "login.txt", "login\nvalidate\n", "fix: 拼写")
git(repo, "switch", "-q", "main")
commit(repo, "readme.txt", "readme\n", "docs: 说明文档")
# 2. 在功能分支上 rebase 到最新 main:历史变成一条直线,不产生合并提交
git(repo, "switch", "-q", "feat/login")
git(repo, "rebase", "-q", "main")
assert log(repo)[:3] == ["fix: 拼写", "feat(login): 登录表单", "docs: 说明文档"]
print("rebase 后:", log(repo))
# 3. 合并回 main(--no-ff 保留一个合并提交,记录“这里合入了一个功能”)
git(repo, "switch", "-q", "main")
git(repo, "merge", "-q", "--no-ff", "feat/login", "-m", "merge: 登录功能")
assert log(repo)[0] == "merge: 登录功能"
# 4. 已推送的错误提交:用 revert 生成一个反向提交,不改写历史
bad = commit(repo, "app.txt", "v2-bug\n", "feat: 有问题的改动")
git(repo, "revert", "--no-edit", bad)
assert (repo / "app.txt").read_text() == "v1\n"
assert log(repo)[0].startswith('Revert "feat: 有问题的改动"')
print("revert 后 app.txt =", (repo / "app.txt").read_text().strip())
# 5. 误用 reset --hard 丢掉了提交,用 reflog 找回
lost = commit(repo, "important.txt", "重要数据\n", "feat: 重要改动")
git(repo, "reset", "-q", "--hard", "HEAD~1")
assert not (repo / "important.txt").exists()
reflog = git(repo, "reflog", "--format=%h %gs")
found = next(line.split()[0] for line in reflog.splitlines() if "feat: 重要改动" in line)
assert found == lost
git(repo, "reset", "-q", "--hard", found)
assert (repo / "important.txt").read_text() == "重要数据\n"
print("reflog 找回提交", found)
# 6. 热修复:在 release 分支修复后 cherry-pick 回 main
git(repo, "switch", "-q", "-c", "release/1.0", "HEAD~1")
fix = commit(repo, "hotfix.txt", "fix\n", "fix: 线上紧急修复")
git(repo, "switch", "-q", "main")
git(repo, "cherry-pick", fix)
assert log(repo)[0] == "fix: 线上紧急修复" and (repo / "hotfix.txt").exists()
print("cherry-pick 后 main:", log(repo)[:2])
print("Git 练习全部通过")
if __name__ == "__main__":
main()