Git
Git Alias
# [alias]
s = status
l = log --graph --all --pretty=format:'%C(yellow)%h%C(cyan)%d%Creset %s %Cgreen(%cr) %C(magenta)<%an>%Creset'
up = !git fetch origin master && git rebase origin/master
co = checkout
ca = !git add -A && git commit -m
cad = !git add -A && git commit -m "."
c = commit
b = branch
list = stash list
save = stash save
pop = stash pop
apply = stash apply
rc = rebase —continue
get = "!f(){ git fetch && git checkout $1 && git reset --hard origin/$1; };f"
new = "!f(){ git co -b ellie-$1 origin/master && git pull; };f"
st = status
h = log --graph --pretty=format:'%C(yellow)[%ad]%C(reset) %C(green)[%h]%C(reset) | %C(white)%s %C(bold red){{%an}}%C(reset) %C(blue)%d%C(reset)' --date=short
ha = log --graph --all --pretty=format:'%C(yellow)[%ad]%C(reset) %C(green)[%h]%C(reset) | %C(white)%s %C(bold red){{%an}}%C(reset) %C(blue)%d%C(reset)' --date=short
wip = !git add -A && git commit -m \"wip: saving work\" && git push origin $(git rev-parse --abbrev-ref HEAD)
# [commit]
template = /Users/jace/.gitmessage.txtGitMessage
### 제목
# 커밋 타입: 작업내용 (제목과 본문은 한 줄 띄워주세요)
##### 제목은 최대 50 글자까지만 입력 ############## -> |
### 본문 - 한 줄에 최대 72 글자까지만 입력하기
# 무엇을, 왜, 어떻게 했는지
######## 본문은 한 줄에 최대 72 글자까지만 입력 ########################### -> |
# 꼬리말
# (선택) 이슈번호 작성
Closes
# [커밋 타입] 리스트
# feat : 기능 (새로운 기능)
# fix : 버그 (버그 수정)
# design : CSS 등 사용자 UI 디자인 변경
# refactor : 리팩토링
# style : 스타일 (코드 컨벤션 - 코드 형식, 세미콜론 추가: 비즈니스 로직에 변경 없음)
# docs : 문서 (문서 추가, 수정, 삭제)
# test : 테스트 (테스트 코드 추가, 수정, 삭제: 비즈니스 로직에 변경 없음)
# chore : 기타 변경사항 (빌드 스크립트 수정 등)
# post : 블로그 포스트 추가 (신규 포스트 작성 및 수정)
# rename : 파일 혹은 폴더명을 수정하거나 옮기는 작업만 하는 경우
# remove : 파일을 삭제하는 작업만 수행한 경우
# ------------------
# [체크리스트]
# 제목 첫 글자는 대문자로 작성했나요?
# 제목은 명령문으로 작성했나요?
# 제목 끝에 마침표(.) 금지
# 제목과 본문을 한 줄 띄워 분리하기
# 본문은 "어떻게" 보다 "무엇을", "왜"를 설명한다
# 본문에 여러줄의 메시지를 작성할 땐 "-"로 구분했나요?
# ------------------Github Cli
brew install gh
gh auth login # 지시에 따라 브라우저 인증# 1. ship
# (혼자 작업용: Push -> PR -> Merge -> Main 복귀)
# (이슈번호 + 본문 Closes + 자동 라벨)
gh alias set ship '!f() { \
BRANCH=$(git rev-parse --abbrev-ref HEAD); \
TYPE=$(echo $BRANCH | cut -d/ -f1); \
ISSUE=$(echo $BRANCH | grep -E "^[^/]+/[0-9]+/" | cut -d/ -f2); \
case "$TYPE" in \
feat) LABEL="enhancement"; COLOR="a2eeef" ;; \
fix) LABEL="bug"; COLOR="d73a4a" ;; \
docs) LABEL="documentation"; COLOR="0075ca" ;; \
*) LABEL="" ;; \
esac; \
if [ -n "$LABEL" ]; then gh label create "$LABEL" --color "$COLOR" --force 2>/dev/null; fi; \
if [ -n "$ISSUE" ]; then \
SUBJECT=$(echo $BRANCH | cut -d/ -f3- | tr "-" " " | perl -ne "print ucfirst"); \
TITLE="[#$ISSUE] $TYPE: $SUBJECT"; \
BODY="Closes #$ISSUE"; \
elif [ $(echo $BRANCH | tr -cd "/" | wc -c) -eq 2 ]; then \
SCOPE=$(echo $BRANCH | cut -d/ -f2); \
SUBJECT=$(echo $BRANCH | cut -d/ -f3- | tr "-" " " | perl -ne "print ucfirst"); \
TITLE="$TYPE($SCOPE): $SUBJECT"; \
else \
SUBJECT=$(echo $BRANCH | cut -d/ -f2- | tr "-" " " | perl -ne "print ucfirst"); \
TITLE="$TYPE: $SUBJECT"; \
fi; \
git push -u origin $BRANCH; \
gh pr create --title "$TITLE" --body "$BODY" --fill --label "$LABEL" 2>/dev/null || gh pr edit --title "$TITLE" --label "$LABEL" 2>/dev/null; \
PR_DATA=$(gh pr view --json number,url); \
PR_NUMBER=$(echo $PR_DATA | jq -r .number); \
PR_URL=$(echo $PR_DATA | jq -r .url); \
echo "🔗 PR URL: $PR_URL (#$PR_NUMBER)"; \
gh pr ready "$PR_URL" 2>/dev/null; \
gh pr merge "$PR_URL" --squash --subject "$TITLE (#$PR_NUMBER)" --delete-branch && git checkout main && git pull origin main; \
}; f'
# 2. pr-only
# (협업용: Push -> PR -> 클립보드 복사 -> 알림)
# (이슈번호 + 본문 Closes + 자동 라벨 + 클립보드)
gh alias set pr-only '!f() { \
BRANCH=$(git rev-parse --abbrev-ref HEAD); \
TYPE=$(echo $BRANCH | cut -d/ -f1); \
ISSUE=$(echo $BRANCH | grep -E "^[^/]+/[0-9]+/" | cut -d/ -f2); \
case "$TYPE" in \
feat) LABEL="enhancement"; COLOR="a2eeef" ;; \
fix) LABEL="bug"; COLOR="d73a4a" ;; \
docs) LABEL="documentation"; COLOR="0075ca" ;; \
*) LABEL="" ;; \
esac; \
if [ -n "$LABEL" ]; then gh label create "$LABEL" --color "$COLOR" --force 2>/dev/null; fi; \
if [ -n "$ISSUE" ]; then \
SUBJECT=$(echo $BRANCH | cut -d/ -f3- | tr "-" " " | perl -ne "print ucfirst"); \
TITLE="[#$ISSUE] $TYPE: $SUBJECT"; \
BODY="Closes #$ISSUE"; \
elif [ $(echo $BRANCH | tr -cd "/" | wc -c) -eq 2 ]; then \
SCOPE=$(echo $BRANCH | cut -d/ -f2); \
SUBJECT=$(echo $BRANCH | cut -d/ -f3- | tr "-" " " | perl -ne "print ucfirst"); \
TITLE="$TYPE($SCOPE): $SUBJECT"; \
else \
SUBJECT=$(echo $BRANCH | cut -d/ -f2- | tr "-" " " | perl -ne "print ucfirst"); \
TITLE="$TYPE: $SUBJECT"; \
fi; \
git push -u origin $BRANCH; \
gh pr create --title "$TITLE" --body "$BODY" --fill --label "$LABEL" 2>/dev/null || gh pr edit --title "$TITLE" --label "$LABEL" 2>/dev/null; \
PR_DATA=$(gh pr view --json number,url); \
PR_NUMBER=$(echo $PR_DATA | jq -r .number); \
PR_URL=$(echo $PR_DATA | jq -r .url); \
echo "$PR_URL" | pbcopy; \
echo "🚀 PR Created & URL Copied: $PR_URL (#$PR_NUMBER)"; \
osascript -e "display notification \"PR #$PR_NUMBER URL이 클립보드에 복사되었습니다.\" with title \"GitHub PR 생성 완료\""; \
}; f'PR title 자동 생성
# PR title 자동
gh alias set ship '!gh pr create --fill && gh pr merge --auto --squash --delete-branch'
# PR title: branch 이름
gh alias set ship '!BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD) && gh pr create --title "$BRANCH_NAME" --body "Merged from $BRANCH_NAME" && gh pr merge --auto --squash --delete-branch'
# --fill 옵션을 섞어 제목은 강제하고 본문은 자동 생성
gh alias set ship '!gh pr create --title "$(git rev-parse --abbrev-ref HEAD)" --fill && gh pr merge --auto --squash --delete-branch'
# 수정된 별칭 (자동 Push 포함)
gh alias set ship '!git push -u origin $(git rev-parse --abbrev-ref HEAD) && BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD) && gh pr create --title "$BRANCH_NAME" --body "Merged from $BRANCH_NAME" && gh pr merge --auto --squash --delete-branch'Squash Merge vs Fast-forward vs No-FF
| 방식 | 특징 | 히스토리 모양 |
|---|---|---|
| No-FF (Git Flow) | 모든 커밋을 유지하며 머지 커밋을 생성 | 거미줄 같은 복잡한 갈래가 남음 |
| Fast-forward | 커밋들을 그대로 main 끝에 이어 붙임 | 작업 커밋(b1, b2)이 main에 그대로 노출 |
| Squash Merge (현재) | 여러 커밋을 '하나'로 합쳐서 main에 새로 생성 | 완벽한 일직선 (Clean & Linear) |
gh merge-no-ff(별칭 생성): 정말 중요한 큰 작업이라 모든 커밋 기록을 남겨야 할 때용git log --graph --oneline: 일직선으로 펴진 아름다운 히스토리 감상하기
MonoRepo에서 Git
모노레포는 여러 앱(apps/*)과 패키지(packages/*)가 섞여 있으므로, 브랜치 이름에 어디를 고치는지 명시하면 나중에 GitHub PR 목록만 봐도 한눈에 들어옵니다.
| 추천 브랜치 이름 | PR 제목 (자동 생성 예시) | 수정 범위 |
|---|---|---|
feat/web/profile-ui | feat(web): Profile ui | 웹 서비스 UI |
fix/api/auth-logic | fix(api): Auth logic | API 서버 인증 |
refactor/shared/utils | refactor(shared): Utils | 공통 패키지 |
1단계: 작업 시작 전 (Sync)
항상 main 브랜치가 최신 상태인지 확인하고 시작합니다.
git checkout main
git pull origin main🛠 2단계: 기능 개발 시작 (Branching)
새로운 기능을 만들 때는 항상 main에서 브랜치를 따서 시작합니다.
- 최신 코드 가져오기:
git checkout main && git pull - 브랜치 생성:
git checkout -b feat/user-api - 로컬 개발:
- 특정 앱만 실행:
pnpm turbo dev --filter=web - 수정 중인 패키지만 테스트:
pnpm turbo test --filter=api
- 특정 앱만 실행:
🚢 3단계: 작업 완료 및 배포 요청 (The "Ship" Step)
코딩이 끝나면 터미널에서 다음 세 줄만 입력하면 됩니다.
-
변경사항 저장:
git add . -
커밋:
git commit -m "feat(api): 사용자 조회 API 구현" -
원격 전송 및 자동 병합 예약:
gh ship- 무슨 일이 일어나나요?
- GitHub에 브랜치가 푸시됩니다.
- 커밋 메시지를 기반으로 PR이 자동으로 생성됩니다.
- 핵심: GitHub Actions(CI)가 돌기 시작하며, 테스트가 통과하면 자동으로
main에 병합됩니다.
- 무슨 일이 일어나나요?
🔄 4단계: 다음 작업으로 바로 전환 (Non-Stop)
gh ship을 치는 순간 검증 책임은 GitHub 서버로 넘어갔습니다. 테스트가 끝날 때까지 기다릴 필요가 없습니다.
- 다시 메인으로:
git checkout main - 새 작업 시작:
git checkout -b feat/next-task- 이전 작업이 서버에서 테스트 되는 동안 Jace님은 새 코드를 짜면 됩니다.
🧹 5단계: 로컬 환경 정리 (가끔 한 번씩)
작업이 몇 개 완료된 후, 로컬에 쌓인 브랜치들을 한 번에 정리합니다.
- 병합된 코드 반영:
git checkout main && git pull - 찌꺼기 브랜치 삭제:
git remote prune origin # 서버에서 지워진 브랜치 반영 # 이미 병합된 로컬 브랜치 삭제 (선택 사항) git branch --merged | grep -v "\*" | xargs git branch -d
PR 제목을 직접 수정
# -t는 제목(title), -b는 본문(body)
gh pr create -t "정말 중요한 웹 로그인 기능 구현" -b "이러저러한 로직을 수정했습니다." --auto --squash