Communitygithub.com

lguidolin/커밋-히스토리-재작성

기존 저장소의 커밋 히스토리가 복잡하여 release-please 도입 전에 conventional commits 형식으로 맞추거나, 중간에 포함된 WIP/fixup/병합 커밋을 정리해야 할 때 사용합니다.

커밋-히스토리-재작성란 무엇인가요?

이 스킬은 기존 저장소의 커밋 히스토리를 conventional commits 형식으로 재작성하여 릴리스 자동화 도구나 `git bisect`, `git blame` 같은 기능이 정상 작동하도록 돕습니다. 각 커밋의 메시지 내용과 변경 파일 패턴을 분석하여 feat, fix, refactor, chore, test, docs, ci 등으로 분류하고, WIP나 fixup 같은 의미 없는 커밋은 squash 후보로 지정합니다. 50개 미만의 커밋에는 대화형 리베이스, 그 이상에는 git-filter-repo를 사용하며, 백업 브랜치 생성과 사용자 승인 절차를 반드시 거쳐 안전하게 실행합니다. 스킬은 코드 내용은 전혀 바꾸지 않고 메시지만 변경하므로, 재작성 전후 파일 트리가 동일한지 검증하는 단계가 핵심입니다. 최종 푸시는 `--force-with-lease`를 사용하여 동시 원격 변경을 덮어쓰지 않도록 보호합니다.

지원 대상~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/lguidolin/agent-skills/tree/main/skills/commit-history-rewrite

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

커밋 히스토리 재작성 (Commit History Rewrite)

기존 저장소의 커밋 히스토리를 conventional commits 형식으로 재작성합니다. 커밋을 분석하고, 유형별로 분류하며, squash 후보를 식별한 후, 완전한 안전 장치와 함께 재작성을 실행합니다.

개요

히스토리는 문서입니다. main 브랜치의 모든 커밋 메시지는 미래의 엔지니어(및 에이전트)가 무엇이 변경되었고 왜 변경되었는지 이해하는 데 사용하는 영구 기록입니다. "fix", "update", "wip" 같은 메시지는 이 문서를 망가뜨립니다. Release-please는 버전 범프를 결정하기 위해 커밋 메시지를 파싱합니다. 형식에 맞지 않는 메시지는 잘못된 릴리스 또는 릴리스 누락을 초래합니다.

코드는 부채입니다. 하이럼의 법칙은 git 히스토리에도 적용됩니다. 커밋 메시지에 의존하는 충분한 도구(체인지로그, 릴리스 자동화, git bisect, git blame)가 있다면, 해당 메시지의 형식은 계약이 됩니다. 자동화를 도입하기 전에 히스토리를 정리하면 이전 메시지가 모든 하위 도구를 오염시키는 것을 방지할 수 있습니다.

이 스킬은 메시지를 재작성하며, 코드는 변경하지 않습니다. 파일 트리는 재작성 전후로 동일해야 합니다. 변경되는 것은 커밋 메타데이터뿐입니다.

사용 시기

  • 기존에 형식을 따르지 않는 히스토리가 있는 저장소에 conventional commits 도입
  • 커밋 로그가 WIP, fixup, 병합, 또는 의미 없는 메시지로 오염됨
  • release-please(작동에 conventional commits가 필요함)를 위해 저장소 준비
  • 오픈소스화 또는 저장소 이전 전에 정리

사용하지 말아야 할 경우

  • 저장소가 이미 conventional commits로 게시된 경우(재작성할 내용 없음)
  • 통보되지 않은 활성 협력자가 있는 공유 브랜치
  • 마지막 몇 개의 커밋만 수정하면 되는 경우(git rebase -i HEAD~N 직접 사용)
  • 업스트림에서 커밋 형식을 제어하는 포크 저장소

프로세스

1단계: 저장소 평가

# 전체 커밋 수 계산
git rev-list --count HEAD

# 커밋 메시지 요약 보기
git log --oneline --no-merges

# 협력자 확인 (작성자 다양성)
git shortlog -sne --no-merges

# 병합 커밋 밀도 파악
git log --merges --oneline | wc -l

판단 지점: 커밋 수가 50개 미만이면 대화형 리베이스를 사용합니다. 50개 이상이면 git-filter-repo를 사용합니다.

2단계: 기존 커밋 분류

각 커밋을 두 가지 신호(메시지 텍스트와 변경된 파일)를 사용하여 분석합니다. 메시지가 모호할 때(예: "update stuff")는 diff가 진실의 원천입니다.

신호 1: 메시지 내용

메시지 신호할당된 유형
"add", "new", "implement", "create", "introduce", "support"feat
"fix", "bug", "patch", "resolve", "correct", "handle error"fix
"refactor", "restructure", "reorganize", "simplify", "extract", "move"refactor
"update dep", "bump", "upgrade", "version", "config", "setup"chore
"test", "spec", "coverage", "assert"test
"doc", "readme", "comment", "typo in doc"docs
"ci", "workflow", "pipeline", "action", "deploy"ci

신호 2: 변경된 파일 (메시지가 모호할 때)

git show --stat --format="" <hash>
파일 패턴할당된 유형
*.md, docs/**만 포함docs
*test*, *spec*, __tests__/**만 포함test
.github/workflows/**, .gitlab-ci.yml, Jenkinsfile만 포함ci
package.json, *.lock, requirements.txt, Cargo.toml만 포함chore
설정 파일(.eslintrc, tsconfig.json, .prettierrc)만 포함chore

Squash 후보

신호조치
메시지가 "WIP", "wip", "temp", "save", "checkpoint", 단일 단어, 또는 비어 있음가장 가까운 의미 있는 커밋에 squash
메시지가 "fixup", "fix typo", "oops", "lint", "formatting"이전 커밋에 squash
병합 커밋(squash 병합이 아닌 경우)squash 후보 — 내용이 이미 main에 있는지 평가

모호한 커밋의 경우 전체 diff(git show <hash>)를 확인하여 내용에서 유형을 결정합니다.

3단계: 양질의 메시지 작성

재작성된 메시지는 커밋 메시지 표준을 따라야 합니다.

<type>[optional scope]: <short imperative description>

[optional body explaining WHY, not what]

좋은 재작성 예:

"initial setup"           →  "chore: initial project setup"
"added login"             →  "feat: add user login"
"fix bug"                 →  "fix: resolve login redirect loop"
"Update README.md"        →  "docs: update README with setup instructions"
"updated deps"            →  "chore(deps): update dependencies"

나쁜 재작성 예 (하지 말 것):

"stuff"                   →  "chore: stuff"          ← 여전히 의미 없음
"changes"                 →  "feat: changes"         ← 설명 없음
"fix"                     →  "fix: fix"              ← 중복

원본 메시지가 의미 없을 경우, diff를 읽고 적절한 설명을 작성합니다. diff가 너무 크거나 모호하면 사용자에게 해당 커밋의 목적을 물어봅니다.

4단계: 재작성 계획 제시

파괴적 행동을 하기 전에, 사용자에게 완전한 계획을 보여줍니다.

재작성 계획
============
전체 커밋 수: 47
재작성할 커밋: 32
Squash할 커밋: 8
이미 형식을 따르는 커밋: 7
보존된 작성자: 3명 (Alice <alice@co>, Bob <bob@co>, CI Bot <bot@co>)

제안된 변경사항:
  abc1234  "initial setup"           →  "chore: initial project setup"
  def5678  "added login"             →  "feat: add user login"
  ghi9012  "wip"                     →  [def5678에 SQUASH]
  jkl3456  "fix bug"                 →  "fix: resolve login redirect loop"
  mno7890  "Update README.md"        →  "docs: update README with setup instructions"
  pqr1234  "feat: add search"        →  [유지 — 이미 형식 준수]
  ...

안전:
  ✓ 백업 브랜치: backup/pre-rewrite-<timestamp>
  ⚠ 재작성 후 강제 푸시 필요
  ⚠ 모든 협력자는 푸시 후 다시 클론하거나 리셋해야 함

변경 없음:
  - 파일 트리 (내용 변경 제로)
  - 모든 커밋의 작성자 이름, 이메일, 날짜
  - 커밋 순서 및 부모 관계 (squash된 커밋 제외)

진행하기 전에 명시적인 사용자 승인을 기다리십시오. "좋아요" 같은 모호한 답변으로 진행하지 말고, "yes" 또는 이에 상당하는 명확한 승인을 요구합니다.

5단계: 백업 생성

# 현재 HEAD에 백업 브랜치 생성
git branch "backup/pre-rewrite-$(date +%Y%m%d-%H%M%S)"

# 백업 존재 확인
git branch -l 'backup/*'

# 선택적으로 추가 안전을 위해 원격에 백업 푸시
git push origin "backup/pre-rewrite-$(date +%Y%m%d-%H%M%S)"

6단계: 재작성 실행

옵션 A: 대화형 리베이스 (50개 미만 커밋)

# 루트부터 리베이스
git rebase -i --root

편집기에서:

  • squash 후보는 squash 또는 fixup으로 표시
  • 메시지 재작성은 reword로 표시
  • 커밋 순서 유지

작성자 보존: 대화형 리베이스는 기본적으로 작성자 이름, 이메일, 날짜를 보존합니다. 특별히 요청하지 않는 한 --reset-author를 사용하지 마십시오. 특별히 요청받지 않는 한 --committer-date-is-author-date를 사용하지 마십시오.

옵션 B: git-filter-repo (50개 이상 커밋)

이전 메시지를 새 메시지로 매핑하는 Python 콜백 스크립트(rewrite-messages.py)를 생성합니다.

import re

REWRITES = {
    b'old hash prefix': b'new message',
    # ... 계획에서 생성
}

def rewrite(commit, metadata):
    # 원본 메시지 또는 해시로 조회
    if commit.original_id in REWRITES:
        commit.message = REWRITES[commit.original_id]

적용:

git filter-repo --commit-callback "$(cat rewrite-messages.py)" --force

작성자 보존: git-filter-repo는 기본적으로 작성자를 보존합니다. 사용자가 명시적으로 요청하지 않는 한 --mailmap 또는 작성자 재작성 플래그를 사용하지 마십시오.

커밋 Squashing

squash 후보의 경우, 가장 가까운 의미 있는 커밋에 병합합니다. 결과 커밋:

  • 의미 있는 커밋의 conventional commit 메시지를 사용
  • 의미 있는 커밋의 원본 작성자를 보존 (squash 대상의 작성자가 아님)
  • squash된 범위의 모든 변경 사항을 결합

7단계: 검증

모든 검증 확인을 실행합니다. 푸시를 진행하기 전에 모든 확인이 통과해야 합니다.

# 1. 재작성된 로그 검토
git log --oneline --no-merges

# 2. 모든 메시지가 conventional commits 형식과 일치하는지 검증
FAILURES=$(git log --format="%s" --no-merges | grep -cvE "^(feat|fix|docs|chore|refactor|test|ci)(\(.+\))?!?: .+")
if [[ "$FAILURES" -gt 0 ]]; then
  echo "실패: $FAILURES개의 형식 미준수 커밋 발견:"
  git log --format="%h %s" --no-merges | grep -vE "^[a-f0-9]+ (feat|fix|docs|chore|refactor|test|ci)(\(.+\))?!?: .+"
  exit 1
fi
echo "통과: 모든 커밋이 형식을 준수합니다"

# 3. 내용 변경이 전혀 없는지 확인 (중요 — 메시지만 변경되었음을 증명)
DIFF=$(git diff "backup/pre-rewrite-"*..HEAD --stat)
if [[ -n "$DIFF" ]]; then
  echo "실패: 내용 차이 발견 — 재작성 과정에서 코드가 변경됨"
  echo "$DIFF"
  exit 1
fi
echo "통과: 파일 트리 동일"

# 4. 작성자 보존 확인
echo "=== 재작성 전 작성자 ==="
git -C . log backup/pre-rewrite-* --format="%an <%ae>" --no-merges | sort -u
echo "=== 재작성 후 작성자 ==="
git log --format="%an <%ae>" --no-merges | sort -u
# 두 목록이 반드시 동일해야 함

어느 하나라도 실패하면 푸시하지 마십시오. 백업으로 리셋하고 조사합니다.

git reset --hard backup/pre-rewrite-*

8단계: 푸시 (확인 포함)

이는 원격 협력자에게 파괴적입니다. 사용자에게 확인합니다.

⚠ 강제 푸시 경고
  브랜치: main
  원격: origin
  작업: git push --force-with-lease origin main

  이 푸시 후, 모든 협력자는 다음을 실행해야 합니다:
    git fetch origin
    git reset --hard origin/main

  계속하려면 "yes"를 입력하십시오:
git push --force-with-lease origin main

동시 원격 변경을 덮어쓰지 않도록 --force 대신 --force-with-lease를 사용합니다. --force-with-lease가 거부되면 재작성 이후 누군가가 브랜치에 푸시한 것입니다. 재시도하기 전에 조사하십시오.

재작성 후 정리

성공적인 푸시 및 협력자 통보 후:

# 원격이 로컬과 일치하는지 확인
git log --oneline -5
git log --oneline -5 origin/main
# 이 두 결과가 동일해야 함

# 백업 브랜치는 최소 2주간 유지한 후 삭제
# git branch -D backup/pre-rewrite-*
# git push origin --delete backup/pre-rewrite-*

합리화에 대한 대응

합리화현실
"예전 히스토리는 중요하지 않아"Release-please는 커밋 메시지를 파싱하여 버전을 결정합니다. 형식에 맞지 않는 히스토리는 잘못된 릴리스나 릴리스 누락을 초래합니다. git bisect, git blame, git log --grep 모두 의미 있는 메시지에 의존합니다.
"이 커밋부터 새로 시작하면 돼"blame, 작성자, 맥락을 잃게 됩니다. --orphan 브랜치는 개발 내러티브를 파괴합니다. 재작성은 나쁜 메시지를 제외한 모든 것을 보존합니다.
"재작성하기엔 너무 위험해"백업 브랜치로 완전히 되돌릴 수 있습니다. diff에 내용 변경이 전혀 없음을 검증하십시오. 문제가 발생하면 git reset --hard backup/pre-rewrite-*.
"나중에 필요할 때 하면 돼"히스토리 재작성은 더 많은 사람이 클론하고 브랜치를 만들수록 시간이 지남에 따라 더 어려워집니다. 더 많은 하위 의존성이 생기기 전에 지금 하십시오.
"강제 푸시는 위험해"--force-with-lease는 다른 사람의 작업을 덮어쓰지 않도록 보호합니다. 백업 브랜치와 결합하면 안전합니다.
"메시지가 거의 비슷해""거의 비슷하다"는 release-please가 파싱하지 못한다는 의미입니다. Conventional commits는 엄격한 형식입니다. 메시지는 형식을 따르거나 따르지 않습니다.
"수동으로 릴리스 태그를 달면 돼"수동 태그는 표류하고, 잊히며, 체인지로그 통합이 부족합니다. 핵심은 자동화입니다. 그러나 자동화에는 파싱 가능한 입력이 필요합니다.

위험 신호

  • 백업 브랜치 생성 건너뛰기
  • --force-with-lease 대신 --force 사용
  • 통보 없이 다른 사람이 활발히 작업 중인 브랜치 재작성
  • 7단계 diff에서 내용 차이 발생 (재작성이 메시지뿐만 아니라 코드도 변경했음을 의미)
  • 재작성 후 작성자 수 변경 (작성자가 보존되지 않았음을 의미)
  • 재작성 계획에 대한 사용자 승인 없이 진행
  • 여전히 의미 없는 재작성 메시지 (예: "chore: stuff")
  • 다른 작성자 간의 커밋을 논의 없이 squash
  • 재작성 후 최소 2주 동안 백업 브랜치를 유지하지 않음

Individual skills in this repo

This repo contains 18 individual skills — each has its own dedicated page.

lguidolin/change-hygiene-and-code-craft

Use when writing or refactoring code, structuring a commit or PR, or deciding whether to abstract duplication. Symptoms — mixing reorg with logic changes, a PR doing several things at once, a file growing large, the second copy of similar code, or unsure whether to DRY something up.

lguidolin/cloud-delivery-aks

Use when deploying to Kubernetes or Azure Kubernetes Service (AKS), configuring cloud secrets, setting up progressive rollout/canary, per-PR ephemeral environments, or k8s health probes. Keywords — Kubernetes, AKS, Key Vault, Argo Rollouts, Flagger, canary, blue-green, liveness, readiness, PodDisruptionBudget, HPA, rollback, GHCR.

lguidolin/conventional-commits-and-releases

Use when committing, writing a commit message, opening a PR that will be squash-merged, or configuring automated versioning/changelogs. Keywords — conventional commits, release-please, semver, feat/fix/chore, breaking change, changelog.

lguidolin/defense-in-depth-security

Use when handling untrusted input, secrets, authentication/authorization, or dependencies — or threat-modeling a new surface. Keywords — STRIDE, threat model, least privilege, secrets management, supply chain, dependency scanning, input validation, audit log, defense in depth.

lguidolin/designing-before-building

Use when starting a feature, fixing a non-trivial bug, or about to write implementation code — before any code exists. Symptoms you need this: "this is simple, I'll just code it", reaching for the editor before a design is approved, or an idea that hasn't been turned into a spec and plan.

lguidolin/engineering-constitution

Use when starting work in a project that follows the engineering constitution, orienting to its rules, or deciding which engineering practice applies to a task — spec writing, commits, testing, security, deploys, database, or UI work.

lguidolin/graphql-contract-testing

Use when writing a GraphQL query/mutation that the UI and a test will share, or building route/schema contract or smoke tests. Symptoms — copying a query into a test, a test asserting on query text, schema change that didn't break the UI build, or RLS/permission drift. Keywords — graphql-codegen, typed document, contract test, route smoke test.

lguidolin/init-repo-CI

Use when setting up a new repository with conventional commits, release-please, and CI automation, or when retrofitting an existing repository that lacks automated versioning and PR validation workflows.

lguidolin/interface-craft-and-accessibility

Use when building or styling UI — components, layouts, forms, design tokens — or making accessibility decisions. Keywords — a11y, WCAG, keyboard navigation, focus state, contrast, design system, minimalist UI, component reuse, ARIA, semantic HTML.

lguidolin/merge-gates-and-automation

Use when setting up or changing CI, pre-push hooks, or a task runner, or deciding what must pass before merge. Symptoms — tempted to put authoritative checks only in a local hook, skip CI, bypass with --no-verify, or unsure what gates a merge vs. runs locally.

lguidolin/observability-and-slos

Use when adding logging, metrics, tracing, health checks, SLOs, or alerting — or when building a service surface that needs to be operable and debuggable. Keywords — structured logs, OpenTelemetry, correlation id, RED metrics, liveness, readiness, SLI, SLO, error budget, alerting.

lguidolin/performance-and-scale

Use when working on hot paths, list endpoints, pagination, data-access in loops, or public interfaces/schemas. Symptoms — unbounded queries, N+1 access, no latency budget, optimizing without measuring, or changing an interface many consumers depend on. Keywords — pagination, N+1, Hyrum's Law, performance budget, bundle size.

lguidolin/postgres-postgraphile-rls-and-sql

Use when writing PostgreSQL, PostGraphile config, Row-Level Security policies, SQL schema files, or working on the Browser→App→PostGraphile→Postgres data path. Keywords — RLS, SECURITY DEFINER, search_path, pgSettings, grants, roles, GraphQL depth limit, query cost, statement_timeout, SQL file organization.

lguidolin/recording-decisions

Use when a design or architecture decision has been made and needs to be captured — writing a decision record or ADR, updating a decision index, noting a deferred idea, or superseding a past decision. Keywords — ADR, decision record, rationale, rejected alternatives, dependency index.

lguidolin/resilience-and-deploy-safety

Use when planning a deploy, designing a rollback, or responding to an incident or writing a postmortem. Keywords — deploy safety, rollback, immutable artifact, progressive delivery, canary, blast radius, incident response, blameless postmortem, error budget.

lguidolin/ship-it

Use when the user wants to ship work — push, PR, archive decision records, merge, and clean up. Handles the full lifecycle from committing final changes through post-merge cleanup including converting specs/plans to compact decision records.

lguidolin/tests-as-a-control

Use when writing or modifying tests, when a test breaks during a refactor, or when testing permission/role rules. Symptoms — tempted to edit a test to make it pass, testing only the happy path, a deny-test that started passing, flaky tests, or unsure what to assert.

lguidolin/zero-downtime-migrations

Use when changing a database schema where data must survive the change — adding/removing/renaming columns, constraints, indexes, or backfilling. Symptoms — a destructive migration bundled with a code deploy, a NOT NULL column with a backfill, a table-locking UPDATE, or a rename. Keywords — expand/contract, parallel change, backfill, NOT VALID, CREATE INDEX CONCURRENTLY, graphile-migrate.

관련 스킬