git --version
git config --global user.name "홍길동"
git config --global user.email "[email protected]"
git config --global init.defaultBranch main
git config --global core.editor vim
git config --global pull.rebase false # 초반엔 merge pull 권장
git config --global -l
mkdir acme-notes && cd acme-notes
git init
echo "# Acme Notes" > README.md
mkdir notes
git status
echo "- 첫 노트" > notes/001.txt
git status
git add .
git status
git commit -m "init: seed project with README and first note"
git status
git log --oneline --graph --all
.gitignore 추가:
mkdir -p tmp && echo "scratch" > tmp/a.txt
git status
echo ".DS_Store" >> .gitignore
echo "tmp/" >> .gitignore
git add .gitignore
git commit -m "chore: add basic .gitignore"
git status
git log --oneline --graph --all
tmp/a.txt는 어떻게 된 것인지 확인해보기
GitHub에서 빈 원격 저장소 acme-notes 생성(README 없음)
git remote add origin https://github.com/<USER>/acme-notes
git remote -v # origin이 뭔지 확인해볼 것
git branch -l
git branch -M main # 로컬 브랜치를 강제로 "main" 브랜치를 사용하겠다는 의미
git push origin main # upstream으로 origin을 선택하고, "main" 브랜치로 push
# 다른 컴퓨터나 다른 디렉토리에서 다음 명령 실행해보기
cd ..
git clone https://github.com/<USER>/acme-notes acme-notes2
cd acme-notes2
git branch -l
git status
git log --oneline --graph --all
# 원래 작업하던 디렉토리에서
cd ../acme-notes
echo "- 두 번째 노트" > notes/002.txt
git add notes/002.txt
git status
git commit -m "feat: add second note"
git status
git push -u origin main # 위 예제에서 -u 옵션을 안 줬더니 문제가 생김. 여기서 default upstream을 origin으로 선택
# 다른 clone에서 확인해보기
cd ../acme-notes2
git pull
git log --oneline --graph --all
브랜치란 나무의 가지가 각각 다른 방향으로 자라듯이 저장소의 형상이 다르게 분화하는 것을 의미함
다시 합칠 수도 있고, 잘라서 붙일 수도 있고, 가지치기할 수도 있음
git switch -c master
git branch testing
브랜치가 있다고 내가 어떤 브랜치에 작업 중인지는 알 수 없음.
내가 작업 중인 브랜치를 가리키는 것은 HEAD
git log --oneline --graph --all
git switch testing
touch test.txt
git add test.txt
git commit -m "made a change"
git log --oneline --graph --all
git switch master
echo "change" > test.txt
git add test.txt
git commit -m "made another change"
git log --oneline --graph --all
이와 달리 마음대로 브랜치를 만들 수 있음. 그러나 팀에서는 보통 유명한 브랜치 컨벤션/전략을 채택함
초기 상태 - HEAD가 master를 가리키고 있음
git switch master
git switch -c iss53
echo "another file" > test2.txt
git add test2.txt
git commit -m "issue: new file"
갑자기 hotfix가 필요함
git switch master
git switch -c hotfix
echo "- 추가사항" >> test.txt
git add test.txt
git commit -m "hotfix: append new content"
git switch master
git merge hotfix
특별히 옵션을 지정하지 않아서 Fast-Forward 모드로 동작함
master 브랜치의 포인터가 hotfix로 이동함
hotfix가 끝났으므로 삭제
git branch -d hotfix
git switch iss53
echo "made third change" >> test2.txt
git add test2.txt
git commit -m "change: another content"
git switch master
git merge iss53
git log --oneline --graph --all
C6 커밋을 만들고 3-way merge를 수행함
3-way merge: 공통 조상인 C3와 각 브랜치가 가리키는 C4, C5 커밋을 합쳐서 C6를 만들어냄
master는 C6로 이동함
불필요한 이슈는 삭제함
git branch -d iss53
git log --oneline --graph --all
브랜치 네트워크가 사라지는 것은 아니고 브랜치를 가리키는 포인터가 사라진 것을 알 수 있음
git branch iss1
git branch iss2
git switch iss1
echo "new file" > test3.txt
git add test3.txt
git commit -m "feature: new file"
git switch iss2
echo "temporary file" > test3.txt
git add test3.txt
git commit -m "feature: temporary file"
git switch master
git merge iss1
git merge iss2
충돌 발생
자동 병합: test3.txt
충돌 (추가/추가): test3.txt에 병합 충돌
자동 병합이 실패했습니다. 충돌을 바로잡고 결과물을 커밋하십시오.
# 파일 열어 <<<<<<<, =======, >>>>>>> 구간 수동 수정
vi test3.txt
git add test3.txt
git commit -m "feature: final file"
origin으로 지정된 원격 저장소의 브랜치를 clone으로 복제하면 다음과 같은 형상이 됨
origin은 "최초의" 원격 저장소를 지칭하는 이름일 뿐이고, 다른 추가 저장소를 추가해서 사용할 수 있음
여기서 다른 누군가가 변경 작업을 하고 commit/push하게 되면 master는 더 전진하게 됨
나도 변경 작업을 하고 commit을 하면 원격 브랜치 origin/master보다 나의 master가 더 전진하게 됨
git fetch를 하게 되면 pull과 달리 브랜치 정보만 업데이트함
git remote add teamone https://git.team1.ourcompany.com/repo # 이 저장소 URL은 유효하지 않음
git remote -v
git fetch teamone
나눠진 브랜치를 합치는 방법에는 merge와 rebase 두 가지가 있음
git switch master
git merge experiment
공통 조상과 각 브랜치의 커밋을 이용하여 3-way merge
git switch experiment
git rebase master # 일반적인 merge와 방향이 다름에 주의
이 명령을 실행하면 내부적으로 다음과 같은 변경이 수행됨
우선 experiment 브랜치가 가리키던 커밋들을 근본(base)이 되는 master 브랜치 뒤에 재 적용하고 experiment 포인터가 이동
추가적으로 master에 experiment를 merge하면 다음과 같이 rebase에 의한 merge 작업이 완료됨
이미 공개 저장소에 push한 커밋을 rebase하면 안 됨. 공유 브랜치를 대상으로 rebase하면 안됨
git switch -c feature/tags # 신규 브랜치 생성
git branch # 현재 브랜치 확인
git branch -vv # 로컬 브랜치와 원격지 브랜치를 같이 확인
echo "- 태그 기능 설계 문서" > docs_tags.md
git add docs_tags.md
git commit -m "docs: draft tag feature design"
git push origin feature/tags # (동일한 이름의) 원격지 브랜치에 push
git branch -r # 원격지 브랜치 목록 확인
git branch -vv
git switch main
git branch -vv
git pull # --ff-only 옵션을 추가하는 게 안전함
git merge --no-ff feature/tags
git log --oneline --graph --all
git push
git log --oneline --graph --all
git branch -d feature/tags
git push origin --delete feature/tags
git log --oneline --graph --all
merge할 때 --no-ff 옵션을 지정하면 feature 브랜치가 남지만, 옵션을 지정하지 않으면 main 브랜치의 연장선에 놓임
협업을 위해서는 --no-ff 옵션을 이용할 것
로컬 작업 브랜치 생성
clone: origin -> local
git clone https://github.com/team/repo
git switch -c feature/tags
기능 구현 후 커밋
vi docs_tags.md
git add docs_tags.md
git commit -m "docs: draft tag feature design"
원격에 브랜치 push
push: local feature/tags -> origin feature/tags
git push origin feature/tags
Github에서 PR 생성
origin feature/tags -> origin main리뷰 의견 반영하여 수정
git commit -am "fix: apply review"
git push
리뷰를 마치고 CI를 통과하면 main에 merge
오픈소스 프로젝트의 저장소를 fork
upstream -> origin이 저장소를 clone해서 로컬에서 개발
clone: origin -> local
git clone https://github.com/me/forked_repo
원본 저장소를 upstream이라는 이름의 remote로 관리
git remote add upstream https://github.com/owner/original_repo
작업 브랜치 생성
push: local fix/typo -> origin fix/typo
git switch -c fix/typo
vi ...
git push -u origin fix/typo
GitHub에서 Pull Request 생성
origin fix/typo -> upstream main코드리뷰 → 요구사항 반영 → CI 통과 → 리뷰어/owner가 Merge
PR이 끝나고 저장소 동기화
fetch - merge - push: upstream main -> local main -> origin main
fetch-merge 방식 대신에 pull --rebase 방식도 있음
# 원본 저장소에서 받아서
git fetch upstream
# 내 로컬 main 브랜치에 원본 저장소의 main 브랜치를 merge
git switch main
git merge upstream/main
# 내 origin의 main 브랜치에 갱신
git push origin main
git diff # 변경 사항을 비교
git diff --staged # 스테이지에 올라간 변경 사항을 비교
git blame README.md # 소스코드의 행별 변경자를 확인
git restore --staged README.md # 실수로 add한 파일 되돌리기
git restore README.md # 워킹 디렉토리 변경 취소
git stash push -m "WIP: batch edit" # 수정 중인 내용을 잠깐 숨겨두기
git stash list # 숨겨둔 내용 목록 확인하기
git stash pop # 가장 최근에 숨겨둔 내용을 꺼내오기
git revert <commit_sha> # 안전하게 되돌리기 (예전 커밋의 내용을 가져다가 새로운 커밋으로 만들기)
vi test.txt
git commit
git commit --amend # 직전 커밋에 대해 추가 메시지 작성
# 스쿼시 준비(fixup)
git commit -m "fix: typo in 002" # 잘게 나눠 커밋
git commit -m "style: rewrap 002"
# 인터랙티브 리베이스(로컬 전용, 원격 공유 전에)
git rebase -i HEAD~3
# pick → squash/fixup 로 정리, 메시지 정돈
fixup 옵션으로 자잘한 수정사항임을 표지
rebase하면서 squash를 통해 단일 커밋으로 합침
rebase는 반드시 로컬/개인 브랜치를 대상으로만 실행할 것
git switch -c feature/branch
echo "v1" > a.txt
git add a.txt
git commit -m "feat: add a"
echo "v1 fixed" > a.txt
git add a.txt
git commit --fixup=HEAD # 이 순간 HEAD는 직전 커밋(feat)을 가리킴
sed -i '' 's/fixed/fixed!!!/' a.txt
git add a.txt
git commit --fixup=HEAD~1 # 이 순간 HEAD~1은 전전 커밋(feat)을 가리킴
git rebase -i --autosquash HEAD~3 # 이 순간 HEAD~3는 전전전전 커밋(feat 이전 상태)을 가리키나 존재하지 않음
# 결국 가장 최신 커밋 3개를 포함한 구간을 의미
git push -f origin feature/branch # rebase 후 push는 강제로 이력을 변경해야 함
rebase & push는 main이나 develop, release 등의 공유하는 브랜치를 대상으로는 수행하면 안 됨
위 예제와 rebase까지능 동일하게 진행 가능함
-f 옵션 없이 push
git push -u origin feature/branch
Github에서 PR 과정에서 리뷰 후 squash and merge 실행
revert는 커밋의 변경 효과를 반대로 적용한 새로운 커밋을 만듬
변경 내역이 커밋으로 남기 때문에 공유 브랜치에서 협업할 때는 revert를 사용하는 게 적합함
git revert <commit sha>
# revert하면 충돌이 생길 수도 있음. 이 경우 충돌 해소하고 revert --continue 필요
vi test4.txt
git add test4.txt
git revert --continue # commit 대신
파괴적 복구 방법 - 주의해서 사용할 것. 개인 브랜치에서만 사용할 것
git log --oneline --graph --all
git reset --soft HEAD~2 # 최근 두 개의 커밋을 되돌리고 다시 메시지 작성해서 커밋할 계획
git log --oneline --graph --all
soft 옵션으로 reset한 상태라면 작업 디렉토리에 HEAD와 HEAD~2 사이의 변경은 온전히 남아 있으므로 다시 되돌리기 쉬움
현재 작업 디렉토리를 git add/commit하면 됨
git log --oneline --graph --all
git reset --hard HEAD~2 # 최근 두 개의 커밋을 버림
git log --oneline --graph --all
git reset --hard origin/master # 다 버리고 origin/master에 일치시킴
git log --oneline --graph --all
hard 옵션으로 reset한 상태라면 작업 디렉토리에 HEAD와 HEAD~2 사이의 변경이 사라지므로 git reflog를 보고 git reset이나 git checkout를 이용해서 되돌리는 수 밖에 없음
reset은 HEAD와 develop을 과거 커밋으로 이동하고
checkout은 HEAD만 과거 커밋으로 이동함
| HEAD | Index | Workdir | WD Safe? | |
|---|---|---|---|---|
| Commit Level | ||||
reset --soft [commit] |
REF | NO | NO | YES |
reset [commit] |
REF | YES | NO | YES |
reset --hard [commit] |
REF | YES | YES | NO |
checkout <commit> |
HEAD | YES | YES | YES |
| File Level | ||||
reset [commit] <paths> |
NO | YES | NO | YES |
checkout [commit] <paths> |
NO | YES | YES | NO |
이진 탐색으로 버그가 존재하는 커밋 찾아내기
git bisect start
git bisect bad HEAD
git bisect good v1.0.0
git bisect bad # 버그와 관련된 테스트가 실패한다면 bad로 표지
git bisect good
# 반복
# 0123abcd is the first bad commit 이런 메시지가 나오면 수동으로 종료
git bisect reset
자동화할 수도 있는데, test.sh이 0, 1 또는 125를 반환하여 실패, 성공, 빌드실패로 인한 건너뜀을 표지하도록 함
git bisect start
git bisect bad HEAD
git bisect good v1.0.0
git bisect run ./test.sh