Git hook과 커밋 서명은 각각 무엇을 확인하나요?
Git hook은 커밋 전후 같은 시점에 로컬 검사를 실행하는 장치이고, 커밋 서명은 작성 이력의 서명을 확인하는 장치입니다. 둘은 역할이 다릅니다.
적용 조건·범위: Git hooks는 commit·push 전후에 로컬 명령을 자동 실행하는 장치이고 commit 서명은 작성 주체와 무결성을 검증하는 수단입니다. 둘 다 CI나 코드 리뷰를 대체하지 않습니다.
본문 근거: git-scm.com/docs/githooks · git-scm.com/docs/git-verify-commit · docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification
독립 실습: Git 설정 없이 hook 검사 명령 확인
새 연습 폴더에 다음 두 파일을 만듭니다. Node.js와 Git Bash가 필요합니다. 이 단계는 hook 자동 설치나 commit을 하지 않고, hook으로 사용할 셸 파일의 종료 코드를 직접 확인합니다.
example.mjs 전체 코드
export const message = 'Hook practice';
console.log(message);
.githooks/pre-commit 전체 코드
#!/bin/sh
set -eu
node --check example.mjs
sh -n .githooks/pre-commit
sh .githooks/pre-commit
printf "exit=%s\n" "$?"
예상: 올바른 문법이면 exit=0입니다. 별도 사본에서 닫는 괄호를 빠뜨리면 node --check가 오류를 내며 hook도 실패합니다. 실제 Git hook 활성화와 서명 검증은 아래 적용 절에서 필요할 때 진행합니다.
핵심 원리
Git hooks는 commit·push 전후에 로컬 명령을 자동 실행하는 장치이고 commit 서명은 작성 주체와 무결성을 검증하는 수단입니다. 둘 다 CI나 코드 리뷰를 대체하지 않습니다.
독립 학습 변형 과제: example.mjs를 잘못된 문법으로 바꾼 사본에서 hook 명령이 0이 아닌 종료 코드를 내는지 확인합니다. hook 실패와 unsigned commit 검증 실패를 서로 다른 원인으로 설명합니다.
선택 실습: 같은 프로젝트로 연습하기
이 선택 실습은 TaskFlow 개발 과정을 Git 저장소와 협업 기록으로 남기는 연습입니다. Git 이력은 직접 명령을 실행해 만듭니다.
이번 주제: Git hooks와 commit 서명 기초: 로컬 검사와 Verified commit 이해.
선택: 연재 프로젝트와 전체 자료
연재 프로젝트를 이어 만드는 분을 위한 자료입니다. 이 글의 개념과 예제만 찾으셨다면 필요한 파일과 실행 방법만 펼쳐보세요.
TaskFlow 적용
시작 체크포인트: 이전 글의 프로젝트 적용과 완료 확인을 마친 본인의 Git 저장소에서 이어갑니다. 다운로드 ZIP은 문서·설정 참고 자료이며 commit 이력·branch·remote·PR 상태를 복원하지 않습니다. 현재 저장소의 status·log와 아래 시작 조건을 직접 대조합니다.
적용 기준
TaskFlow의 13편 완료 상태에서 이 기능이 필요한지 먼저 판단합니다. 적용한 경우 설정 범위와 관찰 결과를 기록하고, 사용하지 않으면 필수 과정의 완료 상태를 그대로 유지합니다.
기능을 사용할지 판단할 때는 “가능한가”보다 “현재 프로젝트에 이 복잡도가 필요한가”를 먼저 묻습니다. 팀원 모두가 같은 도구를 사용해야 하는지, CI나 배포 환경에 추가 설정이 필요한지도 확인합니다.
독자 질문과 결과 확인
질문: 로컬 hook이 통과하거나 Verified가 표시되면 팀 검증이 끝난 것인가요?
본인의 연습용 저장소에서 순서대로 실행합니다.
git config --show-origin --get core.hooksPath
pnpm lint
git log --show-signature -1
git verify-commit HEAD
예상 결과: hook 설치 위치, 직접 실행한 lint, 서명 유무와 로컬 검증 상태를 각각 분리해 기록합니다. GitHub Verified는 서명 상태이며 코드 품질·CI 성공 증거가 아닙니다.
오류 점검: hook 경로가 없으면 로컬 설정은 활성화되지 않은 상태입니다. unsigned commit 또는 서명 신뢰 설정 부재로 verify-commit이 실패할 수 있으므로 원인을 구분합니다.
검증 기록: 위 명령은 이 편집 작업에서 실행하지 않았습니다. Git 이력과 원격 상태는 독자 환경에서 확인해야 하므로 미확인입니다.
다음 학습: 선택 심화까지 마칩니다. 운영 기준은 10~11회차의 CI·main 보호 규칙으로 돌아가 확인합니다.
로컬 hook과 서명 검증의 경계
13편 이후 필요할 때 선택합니다. 제공한 .githooks/pre-commit은 TaskFlow의 pnpm lint를 실행하는 예제입니다. 적용 전에 내용을 읽고 기존 core.hooksPath와 충돌하지 않는지 확인합니다. hook은 로컬 명령 실행 권한이므로 신뢰하지 않는 파일을 자동 활성화하지 않습니다.
git config --show-origin --get core.hooksPath
설정이 없으면 출력이 없을 수 있습니다. 독자가 예제를 적용하기로 결정한 경우에만 현재 저장소에 hooksPath를 지정합니다. Unix/Git Bash 환경에서는 실행 비트도 필요합니다.
git config --local core.hooksPath .githooks
chmod +x .githooks/pre-commit
git add .githooks/pre-commit
git update-index --chmod=+x .githooks/pre-commit
update-index 대상은 먼저 git add .githooks/pre-commit으로 추적한 파일이어야 합니다. 실제 commit 전에 pnpm lint를 직접 실행해 앱 환경이 준비됐는지 확인합니다. hook이 실패하면 commit을 막지만 사용자가 우회하거나 hook을 설치하지 않을 수 있으므로 CI와 required check가 필요합니다. clone만으로 이 로컬 설정이 자동 활성화되지 않습니다.
commit 서명은 hook과 다른 기능입니다. GitHub는 GPG, SSH, S/MIME 서명을 지원하며 키 등록·작성자 이메일·로컬 검증 신뢰 설정이 맞아야 결과를 해석할 수 있습니다. 이 글은 개인키 생성·교체나 전역 서명 설정을 자동 실행하지 않습니다. 독자는 공식 안내에서 본인의 키 유형을 선택하여 설정한 뒤 명시적 git commit -S로 서명할 수 있습니다.
git log --show-signature -1
git verify-commit HEAD
HEAD가 unsigned이거나 검증 키·신뢰 설정이 없으면 위 명령이 성공하지 않을 수 있습니다. 특히 SSH 서명의 로컬 검증에는 allowedSigners 설정이 필요할 수 있습니다. 실패를 숨기거나 이를 코드 오류라고 단정하지 말고 서명 유무와 검증 환경을 분리합니다. GitHub의 Verified 표시는 서명 검증 상태이며 코드가 안전하거나 테스트를 통과했다는 보증이 아닙니다.
완료 기준은 hook의 설치 범위·우회 가능성과 서명/CI/리뷰의 역할 차이를 설명하고 본인 환경에서 확인한 결과만 기록하는 것입니다. 필수 과정은 13편에서 완료되며 선택 심화 14~17편은 서로 필수 선수가 아닙니다.
공식 근거: https://git-scm.com/docs/githooks · https://git-scm.com/docs/git-verify-commit · https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification
13편 완료 상태 또는 해당 기본 Git 개념. 14~17편 사이에는 필수 순서가 없습니다.
필수 과정의 git-step-13-final과 git-step-13-troubleshooting은 같은 완료 상태의 이름입니다. 이 글은 다른 선택 심화 글의 완료를 요구하지 않습니다.
이전 체크포인트:
git-step-13-troubleshooting
주의할 점
- 기능 목적을 이해하지 않고 설정만 복사하지 않습니다.
- 팀의 공통 Git 흐름과 충돌하지 않는지 확인합니다.
- 로컬 기능을 CI/보안 보증으로 과장하지 않습니다.
완료 체크리스트
- 핵심 목적을 설명할 수 있습니다.
- 필요한 경우에만 직접 적용할 수 있습니다.
- 기존 Git 작업 흐름을 깨지 않는지 확인했습니다.
전체 파일·코드 보기
실습 프로젝트 전체 코드
이 회차는 Git 명령 실습을 위한 문서·설정 자료입니다. ZIP에는 실제 Git 이력이나 앱 전체가 없습니다. 아래 Markdown 문서는 자동 실행 스크립트가 아니며, 각 명령의 전제와 예상 상태를 읽고 본인의 연습 환경에서 단계별로 확인합니다.
.githooks/pre-commit
#!/bin/sh
set -eu
if ! command -v pnpm >/dev/null 2>&1; then
printf '%s\n' 'pnpm이 필요합니다. 프로젝트의 packageManager 버전을 확인하세요.' >&2
exit 1
fi
# 현재 작업 트리 전체를 검사합니다. staged snapshot만 검사하는 hook은 아닙니다.
pnpm lint
.github/PULL_REQUEST_TEMPLATE.md
## 변경 목적
이 변경으로 해결하는 문제와 사용자에게 달라지는 동작을 설명해 주세요.
## 검증
- [ ] 변경 범위의 검증 명령과 실제 결과를 기록했습니다.
- [ ] CI 검사 결과를 확인했습니다.
- [ ] 실패하거나 확인하지 못한 항목과 이유를 기록했습니다.
## 리뷰 참고
검토가 필요한 부분, 연관 이슈, 적용 시 주의할 점을 적어 주세요.
.github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
permissions:
contents: read
jobs:
verify:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version: "24.x"
- name: Setup pnpm
uses: pnpm/action-setup@v6
with:
version: "11.19.0"
cache: true
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Lint
run: pnpm lint
- name: Build
run: pnpm build
README.md
# git-step-17-hooks-signing 문서 체크포인트
이 ZIP은 Git·GitHub 실습 안내와 관련 설정 파일을 모은 자료입니다. `.git` 이력, 실제 branch/tag/stash/worktree, 원격 저장소, 사용자 계정이나 TaskFlow 앱 전체를 포함하지 않습니다. 별도의 개발환경 과정 TaskFlow 프로젝트에서 실습 안내를 읽고 명령을 단계별로 적용하세요. 이 폴더 자체를 완성된 Git 저장소로 간주하거나 명령을 한꺼번에 실행하지 마세요.
13편 필수 과정 문서와 이 보조 회차만 포함합니다. 다른 보조 회차의 완료를 요구하지 않습니다.
.github/PULL_REQUEST_TEMPLATE.md는 실제 프로젝트에 검토하여 복사할 PR 작성 양식입니다. CODEOWNERS의 실제 사용자·팀은 저장소 권한에 맞춰 직접 정하며 가짜 소유자를 넣은 파일은 포함하지 않습니다.
.github/workflows/ci.yml은 개발환경 과정 TaskFlow의 pnpm@11.19.0, Node 24.x, lint/build 스크립트를 기준으로 합니다. 기존 package.json/pnpm-lock.yaml이 있는 프로젝트 루트에 적용합니다. 이 ZIP 루트에서는 pnpm install/build를 실행하는 구조가 아닙니다. 원격 Actions 실행·보호 규칙 설정은 검증하지 않았습니다.
.githooks/pre-commit은 현재 작업 트리에 pnpm lint를 실행하는 선택 검사입니다. staged snapshot만 검사하지 않으며 CI와 서명 검증을 대체하지 않습니다. 실행 권한과 core.hooksPath 활성화는 본인 환경에서 별도 확인합니다. 이 작업에서는 hook이나 실제 Git 설정을 활성화하지 않았습니다.
검증 범위는 명령 문법·설정 형식·공식 문서 및 기존 프로젝트 스크립트 대조입니다. 실제 init/commit/branch/stash/worktree/reset/rebase/merge/push/PR/Release/서명 작업을 수행했다는 기록이 아닙니다. 개인 키·토큰·비밀번호는 포함하지 않습니다.
docs/git-step-01.md
# Git·GitHub 01 실습 안내
이 파일은 독자가 상태를 확인하며 따라갈 Markdown 문서입니다. 자동 실행 스크립트나 검수 과정에서 실행한 Git 이력 로그가 아닙니다.
## 첫 저장소부터 원격 연결까지
Git 공식 설치 안내에서 운영체제에 맞는 Git을 설치합니다. Windows 명령 예제는 Git Bash 기준입니다. 이후 회차도 같은 셸을 사용합니다. 먼저 개발환경 과정에서 만든 TaskFlow 폴더를 편집기로 열고 그 폴더에서 터미널을 엽니다. 이미 .git이 있는 프로젝트라면 아래 init을 반복하기보다 현재 이력과 브랜치를 먼저 확인합니다.
```bash
git --version
git init -b main
git status --short
```
이름과 이메일은 이 저장소의 commit 작성자 정보입니다. GitHub 인증 비밀번호가 아닙니다. 공개 이메일을 원하지 않으면 GitHub Settings의 Emails에서 본인의 noreply 주소를 확인합니다. 아래 입력은 실제 값을 화면에 재출력하지 않고 저장소 로컬 설정에만 기록합니다.
```bash
read -r -p "commit 작성자 이름: " GIT_AUTHOR_LABEL
read -r -p "commit 작성자 이메일 또는 본인 noreply 주소: " GIT_AUTHOR_MAIL
if [ -n "$GIT_AUTHOR_LABEL" ] && [ -n "$GIT_AUTHOR_MAIL" ]; then
git config --local user.name "$GIT_AUTHOR_LABEL"
git config --local user.email "$GIT_AUTHOR_MAIL"
else
printf '%s\n' '빈 작성자 정보로 진행하지 않습니다.'
fi
```
편집기에서 README.md에 TaskFlow의 목적 한 줄을 작성합니다. 기존 파일은 덮어쓰지 않고 내용을 읽고 수정합니다. .gitignore에는 기존 개발환경의 node_modules, 빌드 산출물, 실제 환경변수 파일 제외 규칙을 유지합니다. 첫 commit은 README만 명시적으로 선택합니다.
```bash
git status --short
git add README.md
git diff --cached
git commit -m "docs: describe TaskFlow"
git log --oneline -1
```
처음에는 README가 untracked라서 일반 git diff에 나오지 않을 수 있습니다. add 후 cached diff는 첫 commit에 들어갈 내용을 보여 줍니다. 이제 README에 실행 방법 한 줄을 추가해 add하고, commit하기 전에 다른 설명 한 줄을 더 수정합니다.
```bash
git add README.md
git status --short
git diff
git diff --cached
```
이때 예상하는 MM의 첫 M은 staging, 두 번째 M은 staging 이후 작업 트리 변경입니다. 두 diff를 비교한 뒤 두 변경 모두 포함하려면 다시 add하고 두 번째 commit을 만듭니다.
```bash
git add README.md
git diff --cached
git commit -m "docs: add TaskFlow usage"
```
GitHub에서는 본인 계정에 빈 저장소를 만들고 README나 초기 commit을 추가하지 않습니다. 저장소 화면의 HTTPS 또는 SSH 주소를 복사합니다. HTTPS는 Git Credential Manager 등 공식 인증 흐름을, SSH는 이미 등록한 키를 사용합니다. 비밀번호나 토큰을 URL에 넣지 않습니다. 아래는 독자가 본인 원격 저장소를 연결할 때 실행하는 절차이며 이 글의 검수에서 push한 결과가 아닙니다.
```bash
read -r -p "본인의 빈 GitHub 저장소 clone 주소: " TASKFLOW_REMOTE
if [ -n "$TASKFLOW_REMOTE" ]; then
git remote add origin "$TASKFLOW_REMOTE"
else
printf '%s\n' '원격 주소가 없어 연결하지 않습니다.'
fi
git remote -v
```
origin이 이미 존재하면 add 실패를 무시하지 말고 기존 주소가 본인의 대상인지 확인합니다. 주소와 commit 내용을 검토한 뒤에만 첫 push를 실행합니다.
```bash
git push -u origin main
git status -sb
git rev-parse HEAD
git ls-remote origin refs/heads/main
```
마지막 두 출력의 commit ID가 같고 GitHub 파일 화면이 일치하면 첫 연결 기준을 충족합니다. 빈 원격이 아니어서 rejected가 발생했다면 강제 push하지 않고 4편의 fetch/이력 비교로 이동합니다. init 전에는 status가 실패하고 첫 commit 전에는 log가 실패하는 것이 정상일 수 있습니다.
공식 근거: https://git-scm.com/docs/git-init · https://git-scm.com/docs/git-add · https://git-scm.com/docs/git-commit · https://docs.github.com/en/get-started/git-basics/set-up-git
## 체크포인트 기준
git-step-01-first-push: 위 실습의 관찰 기준을 본인의 연습 환경에서 확인한 뒤 사용합니다. 이 ZIP을 내려받았다는 사실만으로 실습 완료가 성립하지 않습니다.
docs/git-step-02.md
# Git·GitHub 02 실습 안내
이 파일은 독자가 상태를 확인하며 따라갈 Markdown 문서입니다. 자동 실행 스크립트나 검수 과정에서 실행한 Git 이력 로그가 아닙니다.
## 목적별 commit을 만드는 실습
1편 이후 README.md에 서로 떨어진 두 문단을 수정합니다. 하나는 실행 방법, 다른 하나는 오타 수정으로 구분합니다. 이미 공유한 commit을 재작성하지 않도록 이 실습에서는 아직 push하지 않은 새 변경만 사용합니다.
```bash
git diff --stat
git diff -- README.md
git add -p README.md
git diff --cached
git diff -- README.md
```
대화형 질문에서 y는 현재 hunk를 staging하고 n은 남깁니다. 한 hunk에 섞였으면 s로 분할할 수 있는지 확인하고, 분할되지 않으면 편집기에서 변경을 다시 나눕니다. cached diff에 첫 목적만 남았을 때 commit하고 나머지 변경을 두 번째 commit으로 만듭니다.
```bash
git commit -m "docs: clarify TaskFlow startup"
git add README.md
git diff --cached
git commit -m "docs: correct wording"
git log --oneline -2
```
이어서 README 한 줄을 다시 수정하고 git add README.md로 stage합니다. 이 추가 변경을 다음 명령으로 index에서만 되돌립니다. 작업 트리의 수정은 남아 있는지 status와 diff로 확인합니다.
```bash
git add README.md
git restore --staged README.md
git status --short
git diff -- README.md
```
마지막 commit의 메시지만 바꾸려면 staged 변경이 없는지 확인한 다음 실행합니다. 방금 unstage한 작업 트리 수정은 이 amend에 포함되지 않고 그대로 남습니다. amend는 새 commit을 만들어 ID를 바꾸며 이미 push한 이력에는 기본 절차로 적용하지 않습니다. --no-edit도 staged 내용까지 기존 commit에 합칠 수 있습니다.
```bash
git diff --cached
git rev-parse HEAD
git commit --amend -m "docs: fix TaskFlow guide wording"
git rev-parse HEAD
git show --stat HEAD
```
.gitignore는 미추적 파일의 추가를 막는 규칙입니다. 이미 추적한 일반 파일을 추적에서 빼는 git rm --cached는 별도의 삭제 commit을 만들므로 대상과 영향을 확인해야 합니다. 비밀이 이미 공개됐다면 ignore 변경만으로 과거 이력이 사라지지 않습니다. 우선 자격증명을 폐기·교체하고 저장소 관리 절차로 대응합니다. 학습 중 실제 비밀을 만들거나 commit하지 않습니다.
공식 근거: https://git-scm.com/docs/git-add · https://git-scm.com/docs/git-restore · https://git-scm.com/docs/git-commit · https://git-scm.com/docs/gitignore
## 체크포인트 기준
git-step-02-clean-commit: 위 실습의 관찰 기준을 본인의 연습 환경에서 확인한 뒤 사용합니다. 이 ZIP을 내려받았다는 사실만으로 실습 완료가 성립하지 않습니다.
docs/git-step-03.md
# Git·GitHub 03 실습 안내
이 파일은 독자가 상태를 확인하며 따라갈 Markdown 문서입니다. 자동 실행 스크립트나 검수 과정에서 실행한 Git 이력 로그가 아닙니다.
## fast-forward와 merge commit 비교 실습
working tree가 깨끗한지 먼저 확인합니다. 아래 학습용 branch 이름이 이미 있으면 중단하고 기존 작업을 확인합니다. 먼저 feature/a를 만들고 편집기에서 새 문서 docs/branch-a.md에 기능 설명을 작성합니다. docs 폴더가 없으면 편집기로 만듭니다.
```bash
git status --short
git switch -c feature/a
```
파일 작성 후 해당 파일만 commit하고 main으로 돌아와 병합합니다. 이 사이 main에 다른 commit을 만들지 않습니다.
```bash
git add docs/branch-a.md
git commit -m "docs: add branch A notes"
git switch main
git merge --ff-only feature/a
git log --graph --oneline --decorate -5
```
새 merge commit 없이 main이 feature/a의 끝으로 이동하는 것이 fast-forward입니다. 다음에는 같은 main에서 feature/b를 만들고 docs/branch-b.md를 작성해 commit합니다.
```bash
git switch -c feature/b
```
파일 작성 뒤 다음을 실행합니다.
```bash
git add docs/branch-b.md
git commit -m "docs: add branch B notes"
git switch main
```
main에서는 다른 파일 docs/main-notes.md를 작성해 commit합니다. 양쪽에 서로 다른 commit이 생긴 다음 feature/b를 병합합니다.
```bash
git add docs/main-notes.md
git commit -m "docs: add main notes"
git merge feature/b -m "Merge branch feature/b"
git log --graph --oneline --decorate --all -10
git show --no-patch --format='%h %p' HEAD
```
마지막 commit의 부모 ID가 두 개라면 merge commit입니다. --no-ff는 fast-forward가 가능해도 merge commit을 만들도록 선택하는 옵션이며, 이번 두 번째 예제는 이미 갈라진 이력이므로 없어도 merge commit이 생깁니다. 병합을 확인한 branch만 git branch -d로 정리할 수 있지만 삭제는 실습 완료의 필수 조건이 아닙니다.
공식 근거: https://git-scm.com/docs/git-switch · https://git-scm.com/docs/git-merge · https://git-scm.com/docs/git-branch
## 체크포인트 기준
git-step-03-branch-merge: 위 실습의 관찰 기준을 본인의 연습 환경에서 확인한 뒤 사용합니다. 이 ZIP을 내려받았다는 사실만으로 실습 완료가 성립하지 않습니다.
docs/git-step-04.md
# Git·GitHub 04 실습 안내
이 파일은 독자가 상태를 확인하며 따라갈 Markdown 문서입니다. 자동 실행 스크립트나 검수 과정에서 실행한 Git 이력 로그가 아닙니다.
## 원격 이력을 확인한 뒤 동기화하기
origin은 저장소 주소의 별명이고 origin/main은 마지막 fetch 시점에 관찰한 원격 main입니다. 실시간 서버 상태 자체가 아닙니다. 기존 TaskFlow 저장소에서 먼저 다음을 확인합니다.
```bash
git status -sb
git remote -v
git branch -vv
git fetch origin
git log --graph --oneline --decorate --all -15
git log --left-right --oneline main...origin/main
```
fetch는 원격 정보를 가져오지만 현재 작업 트리를 자동 병합하지 않습니다. 마지막 비교에서 왼쪽은 local에만, 오른쪽은 원격 추적 branch에만 있는 commit입니다. working tree가 깨끗하고 local main에 고유 commit이 없을 때만 다음처럼 fast-forward 동기화를 선택합니다.
```bash
git switch main
git pull --ff-only origin main
git status -sb
```
양쪽 이력이 갈라지면 --ff-only는 실패합니다. 이때 실패를 강제 push로 덮지 않습니다. 3편의 merge 또는 8편의 공유 여부를 고려한 rebase 중 팀 정책에 맞는 절차를 정합니다. branch의 upstream이 없다면 대상이 맞는지 확인한 뒤 git branch --set-upstream-to=origin/main main을 사용할 수 있습니다.
clone을 비교하려면 기존 프로젝트 안이 아닌 별도 상위 폴더에서 진행합니다. taskflow-peer 폴더가 이미 있으면 실행하지 않습니다. 본인 원격의 읽기 권한을 확인하고 실제 clone 주소를 입력합니다.
```bash
read -r -p "본인의 GitHub clone 주소: " TASKFLOW_REMOTE
if [ -n "$TASKFLOW_REMOTE" ] && [ ! -e taskflow-peer ]; then
git clone "$TASKFLOW_REMOTE" taskflow-peer
else
printf '%s\n' '주소와 새 대상 폴더를 확인하세요.'
fi
```
두 clone의 branch와 remote-tracking 상태를 비교합니다. push rejected를 재현하려면 본인의 연습용 원격에 한쪽 clone에서 변경을 먼저 push한 뒤 다른 clone에서 이전 상태를 기준으로 push하는 순서를 사용합니다. 이는 원격을 변경하는 선택 실습입니다. 검수 과정에서는 원격을 만들거나 push하지 않았습니다.
공식 근거: https://git-scm.com/docs/git-fetch · https://git-scm.com/docs/git-pull · https://git-scm.com/docs/git-clone · https://git-scm.com/docs/git-push
## 체크포인트 기준
git-step-04-remote-sync: 위 실습의 관찰 기준을 본인의 연습 환경에서 확인한 뒤 사용합니다. 이 ZIP을 내려받았다는 사실만으로 실습 완료가 성립하지 않습니다.
docs/git-step-05.md
# Git·GitHub 05 실습 안내
이 파일은 독자가 상태를 확인하며 따라갈 Markdown 문서입니다. 자동 실행 스크립트나 검수 과정에서 실행한 Git 이력 로그가 아닙니다.
## 같은 줄의 충돌을 재현하고 해결하기
독자의 연습용 저장소에서만 진행합니다. clean main의 README.md에 같은 기준 문장이 commit되어 있어야 합니다. feature/conflict-demo 이름이 이미 있으면 기존 작업을 확인한 뒤 새 이름을 선택합니다.
```bash
git switch main
git status --short
git switch -c feature/conflict-demo
```
편집기에서 README의 한 문장을 기능 설명으로 바꾸고 commit합니다. main으로 돌아와 같은 원래 문장을 다른 설명으로 수정하고 별도 commit합니다. 두 문장을 서로 다른 줄에 추가하면 충돌이 발생하지 않을 수 있으므로 같은 줄을 바꿉니다.
```bash
git add README.md
git commit -m "docs: explain feature behavior"
git switch main
```
main 쪽의 같은 줄 수정 후 다음을 실행합니다.
```bash
git add README.md
git commit -m "docs: clarify main behavior"
git switch feature/conflict-demo
git merge main
git status
git diff --name-only --diff-filter=U
git diff -- README.md
```
충돌이 있다면 README에서 <<<<<<<, =======, >>>>>>>가 구분하는 두 의도를 읽습니다. 최종 요구에 맞는 한 문장 또는 두 설명으로 편집하고 marker를 제거합니다. 문서를 확인한 다음 해당 파일만 stage하고 병합 commit을 만듭니다.
```bash
git add README.md
git diff --cached
git commit -m "docs: resolve explanation conflict"
git status --short
```
해결 방향이 불명확하면 진행 중인 merge에서만 git merge --abort를 선택합니다. 이미 병합을 완료한 뒤 abort하는 명령이 아닙니다. merge 시작 전 미완료 변경이 있으면 abort가 원래 상태를 완전히 복원하지 못할 수 있으므로 clean 상태로 시작합니다.
본인 원격과 공개할 변경을 검토한 뒤 feature/conflict-demo를 push하고 GitHub에서 base main, compare feature/conflict-demo인 PR을 만듭니다. Files changed에서 marker가 남지 않았는지 확인하고 프로젝트의 lint/build를 실행합니다. 자신의 PR은 스스로 승인하는 리뷰를 남길 수 없으므로 혼자 학습하면 self-review 결과만 기록하고, 실제 승인 검증은 다른 권한 있는 리뷰어가 있을 때 수행합니다. 병합 후 local main은 fetch/pull로 별도 갱신합니다.
공식 근거: https://git-scm.com/docs/git-merge · https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/addressing-merge-conflicts/resolving-a-merge-conflict-using-the-command-line
## 체크포인트 기준
git-step-05-conflict-pr: 위 실습의 관찰 기준을 본인의 연습 환경에서 확인한 뒤 사용합니다. 이 ZIP을 내려받았다는 사실만으로 실습 완료가 성립하지 않습니다.
docs/git-step-06.md
# Git·GitHub 06 실습 안내
이 파일은 독자가 상태를 확인하며 따라갈 Markdown 문서입니다. 자동 실행 스크립트나 검수 과정에서 실행한 Git 이력 로그가 아닙니다.
## 복구 대상별로 명령을 선택하기
이 절은 모든 명령을 연속 실행하는 절차가 아닙니다. 현재 작업을 보존한 연습용 복제본에서 한 상황씩 비교합니다. 파일 내용, staging, branch 위치, 공유 이력은 서로 다른 복구 대상입니다.
| 대상 | 선택 | 남는 상태 |
| --- | --- | --- |
| staging만 해제 | git restore --staged README.md | 작업 트리 수정 유지 |
| 추적 파일의 미commit 수정 취소 | git restore README.md | 기본적으로 index 내용으로 복원, 수정 유실 가능 |
| branch만 이전 commit으로 | git reset --soft HEAD~1 | index·작업 트리 유지 |
| branch와 index를 이전 commit으로 | git reset --mixed HEAD~1 | 작업 트리 유지, staging 해제 |
| branch·index·작업 트리를 이전 commit으로 | git reset --hard HEAD~1 | 추적 파일 수정 유실 가능 |
| 공유 commit의 효과 취소 | git revert | 취소 변경을 새 commit으로 추가 |
soft/mixed/hard 비교는 각각 같은 세-commit 시작 상태의 별도 복제본에서 수행해야 합니다. 순서대로 실행하면 매번 HEAD가 달라져 비교가 성립하지 않습니다. --hard와 clean은 실제 작업을 잃을 수 있으므로 이 자료의 필수 실행 절차에 넣지 않습니다.
공유 commit을 취소하는 예제는 단일 부모의 일반 commit 하나를 대상으로 합니다. 먼저 log에서 대상 ID를 직접 확인한 뒤 입력합니다.
```bash
git status --short
git log --oneline -8
read -r -p "취소할 일반 commit ID: " REVERT_COMMIT
if [ -n "$REVERT_COMMIT" ]; then
git show --stat "$REVERT_COMMIT"
fi
```
출력에서 대상·내용을 확인하고 clean 상태일 때만 아래를 선택합니다. merge commit은 mainline 선택이 필요한 별도 경우라 이 예제에 넣지 않습니다.
```bash
if [ -n "$REVERT_COMMIT" ]; then
git revert "$REVERT_COMMIT"
fi
git log --oneline -3
git status --short
```
충돌이 나면 파일을 해결·stage한 후 git revert --continue를 사용하거나 중단하려면 git revert --abort를 선택합니다. 둘을 연속 실행하지 않습니다. 보호된 main은 revert 변경도 별도 branch와 PR로 검토합니다.
잃어버린 commit은 reflog에서 찾고 show로 내용을 확인한 다음 새 구조용 branch에 이름을 붙여 보존할 수 있습니다. reflog는 로컬 기록이며 영구 백업이 아닙니다.
```bash
git reflog --date=local -15
read -r -p "보존할 commit ID: " RECOVERY_COMMIT
if [ -n "$RECOVERY_COMMIT" ]; then
git show --stat "$RECOVERY_COMMIT"
fi
```
확인한 ID가 맞을 때만 git branch rescue/recovered "$RECOVERY_COMMIT"로 새 이름을 붙입니다. 같은 branch가 있으면 덮어쓰지 않습니다. status/log로 결과를 다시 확인합니다.
공식 근거: https://git-scm.com/docs/git-restore · https://git-scm.com/docs/git-reset · https://git-scm.com/docs/git-revert · https://git-scm.com/docs/git-reflog
## 체크포인트 기준
git-step-06-recovery: 위 실습의 관찰 기준을 본인의 연습 환경에서 확인한 뒤 사용합니다. 이 ZIP을 내려받았다는 사실만으로 실습 완료가 성립하지 않습니다.
docs/git-step-07.md
# Git·GitHub 07 실습 안내
이 파일은 독자가 상태를 확인하며 따라갈 Markdown 문서입니다. 자동 실행 스크립트나 검수 과정에서 실행한 Git 이력 로그가 아닙니다.
## 임시 변경과 commit을 따로 다루는 실습
clean main에서 feature/context-demo를 만들고 README.md를 아직 commit하지 않은 상태로 수정합니다. 새 branch 이름이 이미 있으면 먼저 기존 작업을 확인합니다.
```bash
git switch -c feature/context-demo
```
README 수정 후 상태를 확인하고 stash합니다. 기본 stash는 추적 파일의 변경을 대상으로 하며 새 파일도 포함하려면 의도한 파일인지 먼저 확인한 후 -u를 선택합니다. 실제 비밀 파일을 stash에 넣지 않습니다.
```bash
git status --short
git stash push -m "TaskFlow context exercise"
git stash list
git stash show -p 'stash@{0}'
git switch main
git switch -c hotfix/context-demo
```
편집기로 docs/hotfix-note.md를 작성하고 작은 hotfix commit을 만듭니다. 그 ID를 show/log로 확인한 뒤 feature branch로 돌아옵니다.
```bash
git add docs/hotfix-note.md
git commit -m "docs: record hotfix note"
HOTFIX_COMMIT=$(git rev-parse HEAD)
git show --stat "$HOTFIX_COMMIT"
git switch feature/context-demo
git cherry-pick "$HOTFIX_COMMIT"
```
cherry-pick은 원래 commit을 없애거나 이동하지 않고 해당 변경을 현재 branch에 적용해 commit을 만듭니다. 상태에 따라 ID가 달라질 수 있습니다. 충돌이 생기면 해결 후 stage하고 git cherry-pick --continue, 중단하려면 --abort를 선택합니다.
먼저 cherry-pick을 끝내고 clean 상태를 확인한 다음 보관한 README 변경을 복원합니다.
```bash
git status --short
git stash list
git stash apply 'stash@{0}'
git diff -- README.md
```
apply는 보관 항목을 남깁니다. pop은 성공적으로 적용하면 항목을 제거하지만 충돌하면 남을 수 있습니다. 따라서 본 실습은 apply로 확인하고 의도한 변경을 commit한 뒤, 정확한 stash 항목을 재확인한 경우에만 drop을 선택합니다. 다른 작업의 stash를 일괄 삭제하지 않습니다.
공식 근거: https://git-scm.com/docs/git-stash · https://git-scm.com/docs/git-cherry-pick
## 체크포인트 기준
git-step-07-context-switch: 위 실습의 관찰 기준을 본인의 연습 환경에서 확인한 뒤 사용합니다. 이 ZIP을 내려받았다는 사실만으로 실습 완료가 성립하지 않습니다.
docs/git-step-08.md
# Git·GitHub 08 실습 안내
이 파일은 독자가 상태를 확인하며 따라갈 Markdown 문서입니다. 자동 실행 스크립트나 검수 과정에서 실행한 Git 이력 로그가 아닙니다.
## 공유하지 않은 branch의 이력 정리
이 실습은 다른 사람이 기반으로 사용하지 않고 아직 push하지 않은 feature branch에서 수행합니다. main에 직접 적용하지 않습니다. clean 상태에서 feature/rebase-demo를 만들고 README의 작은 변경을 세 번 나누어 commit합니다. 각 commit은 파일을 실제로 수정한 뒤 add와 commit을 실행해 만듭니다.
```bash
git switch main
git switch -c feature/rebase-demo
```
세 commit을 만든 다음 log에서 대상 세 개를 확인합니다. 안전한 비교 기준으로 branch를 하나 보존할 수 있습니다. 이름이 이미 있으면 재사용하지 않습니다.
```bash
git status --short
git log --oneline -4
git branch backup/rebase-demo
git rebase -i HEAD~3
```
편집기에서 첫 commit은 pick으로 남기고 두 번째는 squash 또는 fixup으로 첫 commit에 합치며 세 번째는 pick으로 유지하면 세 commit을 두 개로 정리할 수 있습니다. squash는 메시지를 편집하고 fixup은 합쳐지는 commit의 메시지를 버립니다. drop은 변경을 제외하므로 이번 실습에서는 선택하지 않습니다. 저장·종료 뒤 diff와 log로 최종 파일 내용이 유지됐는지 확인합니다.
```bash
git log --graph --oneline --decorate -6
git diff backup/rebase-demo HEAD
```
내용 변경 없이 이력만 정리했다면 마지막 diff는 비어 있어야 합니다. 최신 main 위로 재배치할 필요가 있으면 fetch 후 feature branch에서 다음을 선택합니다.
```bash
git fetch origin
git rebase origin/main
```
충돌한 경우에만 파일을 편집하고 해당 파일을 add한 뒤 git rebase --continue를 실행합니다. 작업을 포기하려면 진행 중인 rebase에서 --abort를 선택합니다. 성공 후 --continue와 --abort를 연속 실행하는 절차가 아닙니다. rebase에서 ours/theirs의 의미를 일반 merge와 기계적으로 동일시하지 않습니다.
이미 공유한 branch의 이력 재작성은 별도 합의가 필요합니다. --force-with-lease는 원격의 예상 상태를 검사하지만 무조건 안전한 강제 push가 아닙니다. 백그라운드 fetch 등의 영향과 다른 사용자의 작업을 확인해야 하며, 본 실습은 강제 push 없이 로컬 비교로 완료합니다.
공식 근거: https://git-scm.com/docs/git-rebase · https://git-scm.com/docs/git-push
## 체크포인트 기준
git-step-08-rebase: 위 실습의 관찰 기준을 본인의 연습 환경에서 확인한 뒤 사용합니다. 이 ZIP을 내려받았다는 사실만으로 실습 완료가 성립하지 않습니다.
docs/git-step-09.md
# Git·GitHub 09 실습 안내
이 파일은 독자가 상태를 확인하며 따라갈 Markdown 문서입니다. 자동 실행 스크립트나 검수 과정에서 실행한 Git 이력 로그가 아닙니다.
## Draft PR부터 리뷰 결과까지
작은 변경이 commit된 feature branch에서 시작합니다. 먼저 최신 base를 확인하고 PR에 포함될 차이를 읽습니다.
```bash
git fetch origin
git status --short
git diff origin/main...HEAD
git log origin/main..HEAD --oneline
git branch --show-current
```
현재 branch가 의도한 feature인지, diff에 비밀값이나 무관한 변경이 없는지 확인합니다. 원격 반영을 원하는 독자는 다음처럼 현재 branch 이름을 확인하여 push합니다. 빈 이름은 detached HEAD이므로 중단합니다.
```bash
PR_BRANCH=$(git branch --show-current)
if [ -n "$PR_BRANCH" ] && [ "$PR_BRANCH" != main ]; then
git push -u origin "$PR_BRANCH"
else
printf '%s\n' '검토한 feature branch에서 진행하세요.'
fi
```
GitHub 저장소의 Pull requests에서 base main과 compare 현재 branch를 선택하고 Draft로 생성합니다. 제공한 .github/PULL_REQUEST_TEMPLATE.md를 참고해 변경 이유, 검증 명령과 실제 결과, 영향 범위를 적습니다. 수행하지 않은 테스트를 통과했다고 쓰지 않습니다.
Files changed에서 줄별 self-review를 하고 필요한 수정 commit을 같은 branch에 push하면 PR이 갱신됩니다. 검토 준비가 끝나면 Ready for review로 전환합니다. 다른 리뷰어는 Comment, Approve, Request changes 중 목적에 맞는 결과를 남깁니다. 작성자는 자신의 PR을 승인할 수 없습니다. 대화 해결 표시는 실제 요청 반영과 구분하여 확인합니다.
merge commit은 branch 구조를 남기고, squash merge는 PR 변경을 한 commit으로 정리하며, rebase merge는 base 위에 commit들을 새로 재배치합니다. 허용 방식과 보호 규칙을 확인한 뒤 한 방식을 선택합니다. 병합 후 local main을 fetch와 --ff-only pull로 갱신합니다.
CODEOWNERS에는 실제 저장소 쓰기 권한이 있는 사용자 또는 접근 가능한 팀을 지정합니다. .github/CODEOWNERS 등의 지원 위치에 파일을 두고 실제 파일 패턴과 owner를 설정해야 하며 가짜 계정을 복사해서는 안 됩니다. base branch의 CODEOWNERS가 리뷰 요청에 사용되고, 승인 강제는 별도의 보호 규칙이 필요합니다. CODEOWNERS 자체는 파일 접근 권한을 부여하거나 변경을 차단하는 보안 경계가 아닙니다.
공식 근거: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request · https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
## 체크포인트 기준
git-step-09-reviewed-pr: 위 실습의 관찰 기준을 본인의 연습 환경에서 확인한 뒤 사용합니다. 이 ZIP을 내려받았다는 사실만으로 실습 완료가 성립하지 않습니다.
docs/git-step-10.md
# Git·GitHub 10 실습 안내
이 파일은 독자가 상태를 확인하며 따라갈 Markdown 문서입니다. 자동 실행 스크립트나 검수 과정에서 실행한 Git 이력 로그가 아닙니다.
## 실제 package script에 맞춘 CI 구성
이 과정이 이어받는 개발환경 TaskFlow는 pnpm 11.19.0, Node 24 이상, dev/build/lint script를 사용합니다. 따라서 존재하지 않는 npm test나 typecheck를 실행하지 않습니다. test script를 별도로 추가한 프로젝트만 해당 검사를 workflow에 넣습니다. 기존 프로젝트가 npm이라면 lockfile과 package manager를 그대로 유지하고 별도 조정해야 합니다.
먼저 package.json의 packageManager, engines, scripts와 pnpm-lock.yaml을 확인합니다. 독자의 준비된 앱에서 아래 검사가 각각 성공하는지 확인하고 실패 단계와 첫 오류를 기록합니다.
```bash
node --version
pnpm --version
pnpm install --frozen-lockfile
pnpm lint
pnpm build
```
전체 코드에 제공한 .github/workflows/ci.yml을 저장소 루트의 같은 경로에 추가합니다. checkout과 setup-node는 공식 v7, pnpm/action-setup은 공식 v6를 사용합니다. setup-node가 Node 24를 준비한 다음 pnpm을 설치하고 캐시를 구성합니다. cache hit 여부와 관계없이 frozen-lockfile install은 실행합니다. permissions의 contents: read는 이 검증 workflow에 필요한 읽기 권한만 부여합니다.
workflow는 pull_request와 main push를 검사하며 job ID는 verify입니다. PR마다 install → lint → build가 순서대로 실행됩니다. 이 예제는 실제 배포 단계를 포함하지 않습니다. pull_request_target으로 바꿔 신뢰하지 않는 PR 코드를 높은 권한으로 실행하지 않습니다. 비밀값은 YAML이나 로그에 넣지 않습니다.
독자가 본인 feature branch에 파일을 commit·push하면 PR의 Checks 또는 Actions에서 workflow를 열 수 있습니다. 첫 실패 step을 펼쳐 package manager·lockfile·lint·build 중 어느 단계인지 확인합니다. 수정 commit을 같은 PR에 올려 다시 성공하는지 확인합니다. 검수를 위해 원격 Actions를 실행하지 않았으므로 이 글에 성공 run URL을 가정하지 않습니다.
공식 근거: https://github.com/actions/checkout · https://github.com/actions/setup-node · https://github.com/pnpm/action-setup · https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions
## 체크포인트 기준
git-step-10-ci: 위 실습의 관찰 기준을 본인의 연습 환경에서 확인한 뒤 사용합니다. 이 ZIP을 내려받았다는 사실만으로 실습 완료가 성립하지 않습니다.
docs/git-step-11.md
# Git·GitHub 11 실습 안내
이 파일은 독자가 상태를 확인하며 따라갈 Markdown 문서입니다. 자동 실행 스크립트나 검수 과정에서 실행한 Git 이력 로그가 아닙니다.
## required check가 실제로 병합을 막는지 확인하기
먼저 저장소의 공개/비공개 상태, 계정 요금제, 규칙 편집 권한을 확인합니다. Rulesets는 공개 저장소에서는 Free에서도 사용할 수 있지만 비공개 저장소의 사용 가능 범위는 요금제에 따라 다릅니다. 관리자 또는 규칙 편집 권한 없이 설정을 바꿀 수 있다고 가정하지 않습니다.
10편 workflow가 본인 저장소에서 한 번 실행된 뒤 Settings의 Rules → Rulesets에서 branch ruleset을 구성합니다. 실제 UI와 사용 가능한 옵션은 저장소 권한을 기준으로 확인합니다.
1. 대상 branch를 main으로 지정하고 실습 대상 저장소가 맞는지 확인합니다.
2. Pull Request 경유, force push 차단, 필요한 경우 branch 삭제 차단을 선택합니다.
3. Required status checks에는 실제 실행 결과에 나타난 verify check를 선택합니다. 파일 이름 ci.yml이나 존재하지 않는 test 이름을 입력하는 것으로 대체하지 않습니다.
4. 승인 리뷰 수는 실제 독립 리뷰어가 있는 경우에 맞춥니다. 혼자 학습하면서 자기 PR을 승인할 수 있다고 가정하지 않습니다.
5. bypass 주체와 기존 branch protection을 함께 확인하고 검토한 정책을 Active로 설정합니다. 운영 저장소 설정 변경은 별도 권한과 합의가 필요한 독자 작업입니다.
실패한 PR에서 merge가 차단되고 같은 PR을 수정하여 check가 성공했을 때 조건이 충족되는지 확인합니다. bypass 권한으로 병합한 결과는 정책이 정상 강제됐다는 증거가 아닙니다. 기준 branch 최신 상태 요구는 strict/loose 정책의 차이이며 stale review와 conversation resolution도 서로 다른 조건입니다.
규칙은 여러 ruleset과 기존 protection이 겹쳐 적용될 수 있습니다. 누적된 요구와 가장 제한적인 조건을 확인합니다. check 이름을 바꾸면 ruleset도 실제 이름과 다시 맞춰야 합니다. 서명된 commit 요구는 17편의 키 설정과 검증 범위를 이해한 뒤 선택하고 본 실습에서 무조건 활성화하지 않습니다.
공식 근거: https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/about-rulesets · https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/available-rules-for-rulesets
## 체크포인트 기준
git-step-11-protected-main: 위 실습의 관찰 기준을 본인의 연습 환경에서 확인한 뒤 사용합니다. 이 ZIP을 내려받았다는 사실만으로 실습 완료가 성립하지 않습니다.
docs/git-step-12.md
# Git·GitHub 12 실습 안내
이 파일은 독자가 상태를 확인하며 따라갈 Markdown 문서입니다. 자동 실행 스크립트나 검수 과정에서 실행한 Git 이력 로그가 아닙니다.
## 검증한 commit에 버전 기준점 남기기
실습 버전은 v0.1.0으로 통일합니다. 실제 프로젝트가 이미 사용 중인 버전이면 덮어쓰지 않고 프로젝트의 다음 버전을 정합니다. SemVer는 공개 API 호환성 약속을 전제로 하며 0.y.z는 초기 개발 단계라는 점도 함께 설명합니다.
먼저 본인의 검증된 main을 동기화하고 working tree와 대상 commit을 확인합니다. CI 성공 여부는 해당 commit의 GitHub 결과에서 확인해야 합니다.
```bash
git switch main
git fetch origin
git pull --ff-only origin main
git status --short
git log --oneline -1
git tag --list v0.1.0
```
마지막 출력에 같은 이름이 없고 대상 commit이 맞을 때만 annotated tag를 만듭니다. tag는 branch와 달리 새 commit을 자동으로 따라가지 않는 이름입니다.
```bash
git tag -a v0.1.0 -m "TaskFlow v0.1.0"
git show v0.1.0
git rev-parse 'v0.1.0^{commit}'
git rev-parse HEAD
```
마지막 두 commit ID가 일치하는지 확인합니다. 원격 반영을 원하는 독자는 모든 tag를 한꺼번에 보내는 --tags보다 검토한 하나의 tag를 명시합니다.
```bash
git push origin v0.1.0
git ls-remote --tags origin 'refs/tags/v0.1.0*'
```
annotated tag는 tag object와 실제 commit을 가리키는 peeled 항목(^{} 접미사)이 함께 보일 수 있습니다. commit 비교는 peeled 값 기준으로 합니다. GitHub Releases에서 해당 tag를 선택하고 변경 요약·관련 PR·실제 검증 결과·알려진 제한을 적어 게시합니다. Release 게시와 호스팅 deployment 실행은 별개입니다.
이후 작은 수정이 merge된 검증 commit은 다음 버전 기준점을 만들 수 있습니다. 이미 공유한 v0.1.0을 이동시키지 않습니다. 이 문서 검수에서는 실제 tag push나 Release 게시를 수행하지 않았습니다.
공식 근거: https://git-scm.com/docs/git-tag · https://docs.github.com/en/repositories/releasing-projects-on-github/managing-releases-in-a-repository · https://semver.org/
## 체크포인트 기준
git-step-12-release: 위 실습의 관찰 기준을 본인의 연습 환경에서 확인한 뒤 사용합니다. 이 ZIP을 내려받았다는 사실만으로 실습 완료가 성립하지 않습니다.
docs/git-step-13.md
# Git·GitHub 13 실습 안내
이 파일은 독자가 상태를 확인하며 따라갈 Markdown 문서입니다. 자동 실행 스크립트나 검수 과정에서 실행한 Git 이력 로그가 아닙니다.
## 세 가지 복구 시나리오를 기록하기
이 절은 학습자의 별도 연습 branch에서 선택해 수행합니다. 운영 main을 일부러 망가뜨리지 않습니다. 각 상황에 대해 관찰한 상태, 선택한 명령, 남은 변경, 재검증 결과를 한 줄씩 기록합니다.
### detached HEAD의 작업 보존
git branch --show-current가 빈 값을 반환하면 detached HEAD인지 status로 확인합니다. 그 상태에서 만든 commit을 보존하려면 이동하기 전에 현재 HEAD에 새 branch 이름을 붙입니다. 구조용 이름이 이미 존재하면 다른 새 이름을 정합니다.
```bash
git status
git log --oneline -3
git switch -c rescue/detached-work
git branch --show-current
```
이미 다른 위치로 이동했다면 reflog에서 commit ID를 찾아 6편의 확인·보존 절차를 사용합니다. reflog에 없다고 모든 작업이 백업된 상태라고 가정하지 않습니다.
### push rejected의 원인 구분
```bash
git fetch origin
git status -sb
git branch -vv
git log --left-right --oneline HEAD...origin/main
```
main의 non-fast-forward인지, feature의 다른 대상인지, 인증·권한·보호 규칙 문제인지 오류와 원격 대상을 함께 봅니다. 위 origin/main 비교는 main을 대상으로 한 예시입니다. fetch 후에도 실제 push 대상이 다른 branch면 해당 upstream과 비교합니다. 이력 차이는 merge/rebase 정책으로 해결하고 권한 문제를 강제 push로 해결하려 하지 않습니다.
### 진행 중인 충돌과 staging 실수
```bash
git status
git diff --name-only --diff-filter=U
git diff
git diff --cached
```
status가 merge 중이라고 하면 5편의 해결·commit 또는 merge --abort 중 하나를 선택합니다. rebase 중이면 8편의 해결·continue 또는 rebase --abort 중 하나를 선택합니다. staging만 잘못했다면 restore --staged로 작업 트리를 남깁니다. 같은 파일의 변경이 공유됐는지에 따라 revert와 local 수정의 경계를 6편에서 다시 확인합니다.
완료 시 현재 branch, 남은 diff, commit log, 실제 프로젝트 lint/build를 점검합니다. 원격 PR·CI·보호 규칙·release tag의 정상 여부는 독자가 본인 저장소에서 확인한 증거로 기록합니다.
필수 과정의 최종 체크포인트 이름은 git-step-13-final입니다. 사용자 작업 순서에서 쓰는 git-step-13-troubleshooting은 같은 13편 완료 상태를 가리키는 호환 별칭입니다. 두 이름이 다른 저장소 상태나 별도 실습 단계를 뜻하지 않습니다. 선택 심화 14~17편은 이 상태에서 각각 필요에 따라 분기합니다.
공식 근거: https://git-scm.com/docs/git-switch · https://git-scm.com/docs/git-reflog · https://git-scm.com/docs/git-push · https://git-scm.com/docs/git-rebase
## 체크포인트 기준
git-step-13-final: 위 실습의 관찰 기준을 본인의 연습 환경에서 확인한 뒤 사용합니다. 이 ZIP을 내려받았다는 사실만으로 실습 완료가 성립하지 않습니다.
docs/git-step-17.md
# Git·GitHub 17 실습 안내
이 파일은 독자가 상태를 확인하며 따라갈 Markdown 문서입니다. 자동 실행 스크립트나 검수 과정에서 실행한 Git 이력 로그가 아닙니다.
## 로컬 hook과 서명 검증의 경계
13편 이후 필요할 때 선택합니다. 제공한 .githooks/pre-commit은 TaskFlow의 pnpm lint를 실행하는 예제입니다. 적용 전에 내용을 읽고 기존 core.hooksPath와 충돌하지 않는지 확인합니다. hook은 로컬 명령 실행 권한이므로 신뢰하지 않는 파일을 자동 활성화하지 않습니다.
```bash
git config --show-origin --get core.hooksPath
```
설정이 없으면 출력이 없을 수 있습니다. 독자가 예제를 적용하기로 결정한 경우에만 현재 저장소에 hooksPath를 지정합니다. Unix/Git Bash 환경에서는 실행 비트도 필요합니다.
```bash
git config --local core.hooksPath .githooks
chmod +x .githooks/pre-commit
git add .githooks/pre-commit
git update-index --chmod=+x .githooks/pre-commit
```
update-index 대상은 먼저 git add .githooks/pre-commit으로 추적한 파일이어야 합니다. 실제 commit 전에 pnpm lint를 직접 실행해 앱 환경이 준비됐는지 확인합니다. hook이 실패하면 commit을 막지만 사용자가 우회하거나 hook을 설치하지 않을 수 있으므로 CI와 required check가 필요합니다. clone만으로 이 로컬 설정이 자동 활성화되지 않습니다.
commit 서명은 hook과 다른 기능입니다. GitHub는 GPG, SSH, S/MIME 서명을 지원하며 키 등록·작성자 이메일·로컬 검증 신뢰 설정이 맞아야 결과를 해석할 수 있습니다. 이 글은 개인키 생성·교체나 전역 서명 설정을 자동 실행하지 않습니다. 독자는 공식 안내에서 본인의 키 유형을 선택하여 설정한 뒤 명시적 git commit -S로 서명할 수 있습니다.
```bash
git log --show-signature -1
git verify-commit HEAD
```
HEAD가 unsigned이거나 검증 키·신뢰 설정이 없으면 위 명령이 성공하지 않을 수 있습니다. 특히 SSH 서명의 로컬 검증에는 allowedSigners 설정이 필요할 수 있습니다. 실패를 숨기거나 이를 코드 오류라고 단정하지 말고 서명 유무와 검증 환경을 분리합니다. GitHub의 Verified 표시는 서명 검증 상태이며 코드가 안전하거나 테스트를 통과했다는 보증이 아닙니다.
완료 기준은 hook의 설치 범위·우회 가능성과 서명/CI/리뷰의 역할 차이를 설명하고 본인 환경에서 확인한 결과만 기록하는 것입니다. 필수 과정은 13편에서 완료되며 선택 심화 14~17편은 서로 필수 선수가 아닙니다.
공식 근거: https://git-scm.com/docs/githooks · https://git-scm.com/docs/git-verify-commit · https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification
## 체크포인트 기준
git-step-17-hooks-signing: 위 실습의 관찰 기준을 본인의 연습 환경에서 확인한 뒤 사용합니다. 이 ZIP을 내려받았다는 사실만으로 실습 완료가 성립하지 않습니다.
실행·검증 범위
이 자료는 독자가 실행할 절차와 기대 관찰 기준입니다. 제공 ZIP에는 Git 이력·실제 원격 저장소·CI 성공 기록이 포함되지 않습니다. 이번 원문 검수에서 branch/worktree/stash/reset/삭제/commit/push 또는 보안·서명 설정을 실행하지 않았습니다. 실제 수행 결과는 독자가 자신의 연습용 저장소와 권한을 확인하여 기록합니다.
완료 체크포인트
git-step-17-hooks-signing
이 글로 Git·GitHub 보조 심화까지 마무리합니다. 핵심 필수 과정은 13회차에서 이미 완성됐으며 14~17회차는 필요에 따라 선택해서 사용합니다.
체크포인트 다운로드
git-step-17-hooks-signing 문서·설정 ZIP 다운로드
ZIP은 명령 안내와 설정 자료이며 Git 이력·브랜치·원격 저장소 상태를 복원하지 않습니다.
글 소개·학습 준비·전체 목차

Git·GitHub · 17 / 17 · 보조 심화
Git hooks와 commit 서명 기초 로컬 검사 Verified commit 이해 보조
이번 글에서 배우는 내용
- hook 파일 검토와 로컬 설치
- 실행 권한과 CI의 역할
- 서명 유무와 검증 신뢰 환경, Verified 의미
선수 지식
로컬 Git 설정·commit·CLI 종료 코드를 이해하고 Node.js가 설치되어 있으면 아래 hook 원리를 단독 확인할 수 있습니다. 개인 서명키는 최소 실습에 필요하지 않습니다.
Git 설치·셸·첫 commit 준비가 필요하면 Git 기초의 독립 실습에서 준비합니다. 누적 TaskFlow 파일은 상단 학습의 선수가 아닙니다.
목차
이 글이 도움이 되었나요?
읽음 기록
읽음 — / 전체 17개 · 필수 13개
새 글 받아보기
RSS 리더에서 BlogFlow의 새 글을 확인할 수 있습니다.