개발 · 자동화기초 · 15분 읽기
Git 작업 트리·커밋·브랜치와 협업
Git은 파일의 상태와 변경 이력을 관리합니다. 작업 트리, 준비 영역, 커밋과 원격 저장소를 구분하면 변경을 안전하게 검토할 수 있습니다.
개념부터 찾아보기
정의·역할·동작 원리를 독립 페이지에서 확인하세요. 본문의 밑줄 용어에도 설명이 있습니다.
이 노트에서 설명할 수 있게 되는 것
- 작업 트리·인덱스·커밋의 파일 내용 차이를 추적한다.
- add 이후 재편집한 내용이 커밋에 자동 포함되지 않는 이유를 설명한다.
- 브랜치와 원격 상태를 구분해 검토 가능한 변경을 만든다.
01작업 트리·index·HEAD라는 세 상태
- 작업 트리는 편집 중인 파일, index는 다음 으로 기록할 스냅샷, 는 현재 기준 을 나타낸다.
- Add 시점의 스냅샷 add는 파일 이름만 예약하는 것이 아니라 그 시점의 내용을 index에 반영한다.
- index·작업 트리이후 같은 파일을 다시 편집하면 index에는 이전 내용, 작업 트리에는 더 새 내용이 동시에 존재할 수 있다.
- ·index 은 일반적으로 index의 상태를 기록한다.
- diff·작업 트리 diff는 기본적으로 작업 트리와 index, diff --staged는 index와 를 비교한다.
- 그래서 같은 파일에 staged와 unstaged 변경이 동시에 있는 것은 오류가 아니라 서로 다른 두 단계의 차이다.
- 변경 조각 선택 add -p는 변경 조각을 골라 index에 넣어 서로 다른 목적의 수정을 분리할 수 있다.
- 로컬 기록 범위아래 흐름은 로컬 기록이며 원격 서버에 전송하는 작업은 포함하지 않는다.
bash
git status --short
git diff
git add -p
git diff --staged
git commit -m "docs: explain service startup"교육용 예시. 실제 원격 접속·설정 변경·배포는 실행하지 않았다.
02blob·tree·commit과 스냅샷
- blob은 파일 내용이고 tree는 이름·모드와 blob 또는 하위 tree의 ID를 연결한다.
- ·tree은 루트 tree, 부모 , 작성자·기록자·시각·메시지 등을 담는다.
- 객체 재사용같은 내용은 객체를 재사용할 수 있으므로 모든 마다 전체 파일을 별도 사본으로 중복 저장한다는 뜻은 아니다.
- diff는 두 스냅샷을 비교하여 계산한 표현이다.
- ID의 결정 요소 ID는 내용과 메타데이터에 의존한다. 파일 내용이 같아도 부모나 메시지를 바꾸면 다른 이 된다.
- rebase·--amend는 기존 객체를 내부에서 수정하는 대신 새 객체를 만들어 참조를 바꾸는 동작이고 rebase도 새 부모 위에서 을 다시 만든다.
- 스냅샷packfile의 delta 압축은 저장 최적화이며 스냅샷이라는 논리 모델과 모순되지 않는다.
bash
git cat-file -t HEAD
git cat-file -p HEAD
git ls-tree HEAD
git show --stat HEAD교육용 예시. 실제 원격 접속·설정 변경·배포는 실행하지 않았다.
03브랜치·HEAD·detached HEAD의 상태 변화
- 브랜치·브랜치는 특정 을 가리키는 이름 있는 참조다.
- 브랜치·정상적인 브랜치 작업에서 는 브랜치를 가리키고 새 을 만들면 그 브랜치 참조가 새 으로 이동한다.
- 브랜치브랜치 생성은 모든 파일을 복사한 독립 디렉터리를 만드는 것과 다르다.
- 작업 트리작업 트리를 바꾸는 switch는 선택한 스냅샷과 현재 변경의 관계를 고려한다.
- detached detached 에서는 가 브랜치 대신 을 직접 가리킨다.
- 이 상태에서도 을 만들 수 있지만 새 작업을 이름 있는 브랜치로 연결하지 않고 떠나면 일반 브랜치 목록에서 찾기 어려울 수 있다.
- reflogreflog는 로컬 참조 이동 기록을 통해 최근 위치를 찾는 데 도움을 주지만 영구 원격 백업은 아니다.
- 도달 불가능한 객체참조에서 도달하지 못하는 객체는 보존 기간과 garbage collection에 따라 제거될 수 있다.
04merge base·fast-forward·세 방향 병합
- 브랜치현재 브랜치 끝이 대상의 조상이면 참조를 앞으로 옮기는 fast-forward로 통합할 수 있다.
- 공통 조상과 양쪽 변경양쪽에 독립 이 있으면 공통 조상인 merge base와 양쪽 끝 상태를 비교한다.
- 충돌서로 다른 부분의 변경은 자동 결합할 수 있고 같은 줄의 다른 수정이나 삭제/수정 충돌은 사람의 의미 판단이 필요할 수 있다.
- 병합 커밋의 부모merge 은 보통 두 부모를 가져 어느 역사가 합쳐졌는지 보존한다.
- rebaserebase는 선택한 변경을 새 기반 위에 다시 적용하여 새 ID를 만든다.
- 선형 이력선형 이력을 만드는 장점과 공유된 ID를 바꾸는 비용을 함께 이해한다.
- 충돌·rebase같은 충돌이라도 rebase에서는 각 을 재적용하는 여러 단계에서 반복해서 나타날 수 있다.
원리 심화
05충돌 시 index는 경로 하나에 세 버전을 담을 수 있다
- 정상 index보통 해결된 파일 경로는 index의 stage 0에 커밋할 내용을 연결한다. 작업 파일을 저장한 것만으로 이 스냅샷이 항상 최신으로 바뀌는 것은 아니다.
- 병합 충돌일반적인 두 갈래 merge에서 충돌한 경로는 stage 1에 공통 조상, stage 2에 현재 쪽, stage 3에 합칠 쪽 내용을 보관할 수 있다. 추가·삭제 충돌에서는 모든 단계가 존재하지 않을 수 있다.
- 작업 파일의 역할충돌 마커가 들어 있는 작업 파일은 사람이 해결할 편집 공간이다. index의 세 버전과 같은 파일 세 개가 디렉터리에 자동 생성됐다는 뜻은 아니다.
- 해결의 기록최종 내용을 편집하고 add하면 그 경로의 해결된 내용을 stage 0에 기록한다. 이후 또는 진행 중 작업의 continue가 그 결과를 이력으로 연결한다.
- 작업 종류 구분rebase의 ours/theirs는 일반 merge에서 생각한 내 브랜치·상대 브랜치와 직관이 달라질 수 있다. 번호만 외우지 않고 현재 어떤 커밋을 어떤 기반 위에 적용 중인지 확인한다.
예제로 추적
06같은 포트 줄을 다르게 바꾼 세 방향 병합
- 조상과 두 변경가상 service.conf의 공통 조상은 port=8080이다. 현재 브랜치는 8081로, 합칠 브랜치는 9090으로 같은 줄을 바꿨다고 가정한다.
- 충돌 상태병합 도중 이 경로의 stage 1은 8080, stage 2는 8081, stage 3은 9090을 가리킨다. 충돌은 이 어느 포트가 업무상 옳은지 결정할 수 없다는 신호다.
- 해결 내용서버 리스너·프록시 목적지·방화벽의 합의가 9090이라면 해당 값을 선택하고 관련 설정을 함께 확인한다. 마커만 지우고 두 port 줄을 모두 남기면 문법상 저장은 되어도 의미가 틀릴 수 있다.
- index 전환해결된 파일을 add한 뒤에는 그 경로가 stage 0의 최종 내용으로 정리된다. ls-files --unmerged에 계속 나오면 아직 병합 미해결 자료가 남아 있다.
- 커밋 경계add 이후 파일을 다시 고쳤다면 새 수정은 아직 index와 다를 수 있다. 최종 전에 작업 트리 차이와 staged 차이를 각각 읽어 실제 포함될 결과를 확인한다.
| 단계 | 예시 내용 | 역할 |
|---|---|---|
| 1 | port=8080 | 공통 조상 |
| 2 | port=8081 | 현재 merge 쪽 |
| 3 | port=9090 | 합칠 쪽 |
| 해결 뒤 0 | port=9090 | 선택한 최종 스냅샷 |
bash
git ls-files --unmerged -- service.conf
git show :1:service.conf
git show :2:service.conf
git show :3:service.conf일반 merge 충돌 상태에서 버전을 읽는 교육용 명령. 경로와 해당 stage가 존재해야 하며 이 페이지 작성 중 실행하지 않았다.
검증·예외
07작업 파일·객체·참조의 손실을 구분한다
- 미저장과 미커밋편집기에만 있는 내용, 디스크에 저장된 작업 파일, index 내용, 객체는 다른 보존 단계다. 이력에서 찾을 수 있는 범위도 단계에 따라 다르다.
- 부모 변경rebase로 부모가 바뀌면 파일 내용이 같아도 새 ID가 생길 수 있다. blob 재사용과 동일성을 서로 다른 수준으로 읽는다.
- 참조의 이동브랜치가 새 을 가리키는 것과 옛 객체가 즉시 삭제되는 것은 다르다. reflog는 로컬 참조 이동을 찾는 단서지만 영구 원격 백업을 대신하지 않는다.
- 미해결 경로충돌 마커 문자열 검색만으로 해결 여부를 판정하지 않는다. 바이너리·삭제 충돌처럼 마커가 없는 문제도 있으므로 index의 unmerged 상태와 현재 작업 상태를 함께 읽는다.
- 이력 공유로컬 merge 해결이 끝났어도 원격으로 push되지 않았다면 다른 사람이 그 을 가진 것은 아니다. 로컬 상태와 의 최신성, 실제 공유 완료를 구분한다.
08충돌 마커에서 최종 프로그램 의미를 복원하기
- 충돌 마커<<<<<<<, =======, >>>>>>>는 자동 병합이 결론을 내리지 못한 두 내용을 보여 준다.
- 최종 동작의 통합위나 아래를 기계적으로 선택하는 것이 아니라 두 변경의 목적을 이해하여 최종 내용을 작성해야 한다.
- 충돌텍스트 마커가 없더라도 서로 다른 파일의 호환성이 깨지는 의미 충돌은 가능하므로 빌드·실행 검증이 필요하다.
- add·index해결한 내용을 add하면 index에 해결 결과를 기록한다.
- ·rebasemerge는 , rebase는 --continue로 진행하며 --abort는 해당 통합 시도를 되돌리는 동작이다.
- rebaserebase --skip은 문제를 자동 고치는 것이 아니라 해당 을 건너뛰는 선택이므로 그 변경을 버려도 되는지 판단해야 한다.
- 텍스트 외 충돌바이너리·잠금 파일·생성물은 원본 설정을 합친 뒤 도구로 다시 생성하는 방식이 필요할 수 있다.
bash
git status
git diff --name-only --diff-filter=U
# 충돌 파일을 편집한 다음
git add path/to/resolved-file
git diff --staged
# 진행 중인 작업 종류에 따라 git commit 또는 git rebase --continue교육용 예시. 실제 원격 접속·설정 변경·배포는 실행하지 않았다.
09fetch·pull·push와 원격 추적 참조
- origin/main·브랜치origin/main은 서버의 브랜치 자체가 아니라 마지막 fetch 등으로 관찰한 원격 상태를 나타내는 로컬 참조다.
- fetchfetch는 필요한 객체와 원격 참조 정보를 가져오지만 현재 작업 브랜치를 자동 병합하지 않는다.
- pull·fetchpull은 fetch 뒤 설정에 따른 merge·rebase·fast-forward 통합을 수행한다.
- origin과 upstreamorigin과 upstream은 관례적 이름일 뿐 특별한 전송 프로토콜 키워드가 아니다.
- pushpush는 원격에 객체를 보내고 참조 갱신을 요청한다.
- 원격에 로컬이 모르는 새 이 있으면 단순 전진이 아니라서 거부될 수 있다.
- 먼저 fetch해 양쪽 차이를 확인하는 이유다.
- --force-with-lease--force-with-lease는 예상한 원격 상태가 바뀌면 덮어쓰기를 막는 조건이지만 공유 이력 변경이 무해해지는 것은 아니다.
- ·push은 로컬 기록이며 push 전에는 GitHub에 자동 업로드되지 않는다.
bash
git remote -v
git branch -vv
git log --oneline --graph --decorate --all
git log --oneline --left-right main...origin/main교육용 예시. 실제 원격 접속·설정 변경·배포는 실행하지 않았다.
10되돌리기·ignore·서명이 보호하는 범위
- restore·작업 트리restore는 작업 트리나 index 내용을 특정 기준으로 바꾸는 기능이고 reset은 모드에 따라 참조·index·작업 트리에 다른 영향을 준다.
- revertrevert는 과거 변경을 상쇄하는 새 을 만들어 공유 이력을 보존하면서 수정할 때 유용하다.
- 되돌릴 상태의 위치삭제할 변경의 위치가 어느 상태에 있는지 먼저 결정해야 원치 않은 작업을 잃지 않는다.
- gitignoregitignore는 보통 추적하지 않는 파일의 추가 후보를 줄이는 규칙이며 이미 된 비밀을 역사에서 지우는 기능은 아니다.
- 노출된 비밀노출된 비밀은 키 회수·와 별도로 과거 자료 처리가 필요하다.
- ·서명signed 은 키 기반 서명이고 -s의 Signed-off-by 문구는 프로젝트 절차상 확인 표시라 암호학적 서명과 다르다.
- GitHub PR·리뷰·CIGitHub PR·리뷰·CI는 객체 전송 위에 구축된 협업 과정으로 이해한다.
11검토 가능한 변경
- 커밋커밋은 하나의 의도를 중심으로 만들고 바뀐 동작과 이유를 설명한다.
- index·작업 트리상태 조회에서 추적 여부, index에 준비된 변경, 작업 트리에만 남은 변경을 구분하고 실제 diff에서 예상하지 못한 생성 파일·환경 설정·비밀값을 확인한다.
- 아래 조회와 비교표를 연결해 이번 기록에 들어갈 내용을 검토한다.
- 비밀값과 공유 이력비밀의 분리·와 이미 공유한 이력의 처리는 앞의 되돌리기·ignore 절에서 설명한 경계를 따른다.
- 설정과 비밀값 분리저장소에는 공개 가능한 예시 설정을 두고 실제 비밀은 별도 경로에서 공급한다.
| 구성·관찰 대상 | 확인하는 값 | 구분할 문제 |
|---|---|---|
| 작업 트리 | 현재 편집 내용 | 아직 준비되지 않은 수정 |
| 인덱스 | 다음 커밋의 내용 | add 뒤 재편집 차이 |
| ·브랜치 | 현재 커밋 참조 | 이력과 작업 파일의 차이 |
| 원격 추적 | 마지막 가져온 원격 상태 | 로컬 커밋과 원격 전송 |
bash
git status --short
git diff --stat
git log -5 --oneline현재 저장소의 변경 요약과 최근 커밋을 조회한다. 파일과 이력을 변경하지 않는다.
12add 뒤 다시 고쳤을 때 커밋되는 내용
- 의 설정 파일이 A 상태이고 작업 트리에서 이를 B로 고쳤다고 가정한다.
- add·인덱스 add는 그 시점의 B 내용을 인덱스에 준비한다.
- 인덱스·add이후 파일을 C로 다시 고쳐도 인덱스는 별도의 add가 없는 한 B를 유지한다.
- 커밋·인덱스일반적인 커밋은 인덱스의 내용을 기록하므로 새 커밋에는 B가 들어가고 작업 트리의 C는 남은 변경으로 보인다.
- 파일 이름파일 이름을 한 번 준비했으니 이후 모든 수정이 자동 포함될 것이라는 생각은 틀리다.
- 처음세 영역이 A로 일치한다.
- B로 편집 후 add인덱스가 add 시점의 B를 담는다.
- C로 다시 편집다시 add하지 않았으므로 인덱스는 B다.
- 새 커밋은 B를 기록하고 C는 작업 트리 변경으로 남는다.
설명과 함께 보는 보조 도식
처리 과정
A → B → C 편집과 인덱스의 B
일반적인 add와 commit 동작이며 -a 같은 추가 옵션은 사용하지 않는 사례다.
01HEAD의 파일시작 시 A 상태
02작업 트리직접 편집하는 파일
03인덱스다음 커밋에 넣을 내용
04새 커밋인덱스의 스냅샷을 기록
01
처음
세 영역이 A로 일치한다.
- HEAD
- A
- 작업 트리
- A
- 인덱스
- A
전체 단계를 한 번에 읽기
- 01
처음
세 영역이 A로 일치한다.
- HEAD
- A
- 작업 트리
- A
- 인덱스
- A
- 02
B로 편집 후 add
인덱스가 add 시점의 B를 담는다.
- 작업 트리
- B
- 인덱스
- B
- 03
C로 다시 편집
다시 add하지 않았으므로 인덱스는 B다.
- 작업 트리
- C
- 인덱스
- B
- 04
commit
새 커밋은 B를 기록하고 C는 작업 트리 변경으로 남는다.
- 새 HEAD
- B
- 남은 차이
- B → C
READ · OBSERVE · CONNECT
함께 보는 학습 자료
아래 공식 자료를 본문의 개념·예제와 함께 읽어 보세요. 자료별로 확인할 위치와 관찰할 내용을 적었습니다.
Git blob·tree·commit 객체 그래프
Tree Objects와 Objects를 읽고 Simple version of the data model 그림에서 화살표를 따라간다. 이름·파일 내용·부모 이력이 어느 객체에 기록되는지 구별할 수 있다.
읽으면서 확인할 것
- blob에 파일 이름이 아닌 내용이 저장되는 구조
- tree가 파일 이름·모드와 객체 ID를 연결하는 방식
- 에서 tree와 부모 으로 이어지는 참조
학습 확인
이해했는지 확인하기
- 작업 트리와 인덱스에 들어 있는 변경을 구분한다.
- 로컬 커밋과 원격 전송 상태를 확인한다.
- 커밋 전에 실제 diff와 비밀값 포함 여부를 검토한다.
