개발 · 자동화기초 · 15분 읽기

Git 작업 트리·커밋·브랜치와 협업

Git은 파일의 상태와 변경 이력을 관리합니다. 작업 트리, 준비 영역, 커밋과 원격 저장소를 구분하면 변경을 안전하게 검토할 수 있습니다.

개념부터 찾아보기

정의·역할·동작 원리를 독립 페이지에서 확인하세요. 본문의 밑줄 용어에도 설명이 있습니다.

이 노트에서 설명할 수 있게 되는 것

  1. 작업 트리·인덱스·커밋의 파일 내용 차이를 추적한다.
  2. add 이후 재편집한 내용이 커밋에 자동 포함되지 않는 이유를 설명한다.
  3. 브랜치와 원격 상태를 구분해 검토 가능한 변경을 만든다.

01작업 트리·index·HEAD라는 세 상태

  • 작업 트리는 편집 중인 파일, index는 다음 으로 기록할 스냅샷, 는 현재 기준 을 나타낸다.
  • add는 파일 이름만 예약하는 것이 아니라 그 시점의 내용을 index에 반영한다.
  • 이후 같은 파일을 다시 편집하면 index에는 이전 내용, 작업 트리에는 더 새 내용이 동시에 존재할 수 있다.
  • 은 일반적으로 index의 상태를 기록한다.
  • 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, 부모 , 작성자·기록자·시각·메시지 등을 담는다.
  • 같은 내용은 객체를 재사용할 수 있으므로 모든 마다 전체 파일을 별도 사본으로 중복 저장한다는 뜻은 아니다.
    • diff는 두 스냅샷을 비교하여 계산한 표현이다.
  • ID는 내용과 메타데이터에 의존한다. 파일 내용이 같아도 부모나 메시지를 바꾸면 다른 이 된다.
  • --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 에서는 가 브랜치 대신 을 직접 가리킨다.
  • 이 상태에서도 을 만들 수 있지만 새 작업을 이름 있는 브랜치로 연결하지 않고 떠나면 일반 브랜치 목록에서 찾기 어려울 수 있다.
  • reflog는 로컬 참조 이동 기록을 통해 최근 위치를 찾는 데 도움을 주지만 영구 원격 백업은 아니다.
  • 참조에서 도달하지 못하는 객체는 보존 기간과 garbage collection에 따라 제거될 수 있다.

04merge base·fast-forward·세 방향 병합

  • 현재 브랜치 끝이 대상의 조상이면 참조를 앞으로 옮기는 fast-forward로 통합할 수 있다.
  • 양쪽에 독립 이 있으면 공통 조상인 merge base와 양쪽 끝 상태를 비교한다.
  • 서로 다른 부분의 변경은 자동 결합할 수 있고 같은 줄의 다른 수정이나 삭제/수정 충돌은 사람의 의미 판단이 필요할 수 있다.
  • merge 은 보통 두 부모를 가져 어느 역사가 합쳐졌는지 보존한다.
  • rebase는 선택한 변경을 새 기반 위에 다시 적용하여 새 ID를 만든다.
  • 선형 이력을 만드는 장점과 공유된 ID를 바꾸는 비용을 함께 이해한다.
  • 같은 충돌이라도 rebase에서는 각 을 재적용하는 여러 단계에서 반복해서 나타날 수 있다.
원리 심화

05충돌 시 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 줄을 모두 남기면 문법상 저장은 되어도 의미가 틀릴 수 있다.
  • 해결된 파일을 add한 뒤에는 그 경로가 stage 0의 최종 내용으로 정리된다. ls-files --unmerged에 계속 나오면 아직 병합 미해결 자료가 남아 있다.
  • add 이후 파일을 다시 고쳤다면 새 수정은 아직 index와 다를 수 있다. 최종 전에 작업 트리 차이와 staged 차이를 각각 읽어 실제 포함될 결과를 확인한다.
단계예시 내용역할
1port=8080공통 조상
2port=8081현재 merge 쪽
3port=9090합칠 쪽
해결 뒤 0port=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에 해결 결과를 기록한다.
  • merge는 , rebase는 --continue로 진행하며 --abort는 해당 통합 시도를 되돌리는 동작이다.
  • rebase --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은 서버의 브랜치 자체가 아니라 마지막 fetch 등으로 관찰한 원격 상태를 나타내는 로컬 참조다.
  • fetch는 필요한 객체와 원격 참조 정보를 가져오지만 현재 작업 브랜치를 자동 병합하지 않는다.
  • pull은 fetch 뒤 설정에 따른 merge·rebase·fast-forward 통합을 수행한다.
  • origin과 upstream은 관례적 이름일 뿐 특별한 전송 프로토콜 키워드가 아니다.
  • push는 원격에 객체를 보내고 참조 갱신을 요청한다.
  • 원격에 로컬이 모르는 새 이 있으면 단순 전진이 아니라서 거부될 수 있다.
    • 먼저 fetch해 양쪽 차이를 확인하는 이유다.
  • --force-with-lease는 예상한 원격 상태가 바뀌면 덮어쓰기를 막는 조건이지만 공유 이력 변경이 무해해지는 것은 아니다.
  • 은 로컬 기록이며 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는 작업 트리나 index 내용을 특정 기준으로 바꾸는 기능이고 reset은 모드에 따라 참조·index·작업 트리에 다른 영향을 준다.
  • revert는 과거 변경을 상쇄하는 새 을 만들어 공유 이력을 보존하면서 수정할 때 유용하다.
  • 삭제할 변경의 위치가 어느 상태에 있는지 먼저 결정해야 원치 않은 작업을 잃지 않는다.
  • gitignore는 보통 추적하지 않는 파일의 추가 후보를 줄이는 규칙이며 이미 된 비밀을 역사에서 지우는 기능은 아니다.
  • 노출된 비밀은 키 회수·와 별도로 과거 자료 처리가 필요하다.
  • signed 은 키 기반 서명이고 -s의 Signed-off-by 문구는 프로젝트 절차상 확인 표시라 암호학적 서명과 다르다.
  • GitHub PR·리뷰·CI는 객체 전송 위에 구축된 협업 과정으로 이해한다.

11검토 가능한 변경

  • 커밋은 하나의 의도를 중심으로 만들고 바뀐 동작과 이유를 설명한다.
  • 상태 조회에서 추적 여부, index에 준비된 변경, 작업 트리에만 남은 변경을 구분하고 실제 diff에서 예상하지 못한 생성 파일·환경 설정·비밀값을 확인한다.
    • 아래 조회와 비교표를 연결해 이번 기록에 들어갈 내용을 검토한다.
  • 비밀의 분리·와 이미 공유한 이력의 처리는 앞의 되돌리기·ignore 절에서 설명한 경계를 따른다.
  • 저장소에는 공개 가능한 예시 설정을 두고 실제 비밀은 별도 경로에서 공급한다.
구성·관찰 대상확인하는 값구분할 문제
작업 트리현재 편집 내용아직 준비되지 않은 수정
인덱스다음 커밋의 내용add 뒤 재편집 차이
·브랜치현재 커밋 참조이력과 작업 파일의 차이
원격 추적마지막 가져온 원격 상태로컬 커밋과 원격 전송
bash
git status --short
git diff --stat
git log -5 --oneline

현재 저장소의 변경 요약과 최근 커밋을 조회한다. 파일과 이력을 변경하지 않는다.

12add 뒤 다시 고쳤을 때 커밋되는 내용

  • 의 설정 파일이 A 상태이고 작업 트리에서 이를 B로 고쳤다고 가정한다.
  • add는 그 시점의 B 내용을 인덱스에 준비한다.
  • 이후 파일을 C로 다시 고쳐도 인덱스는 별도의 add가 없는 한 B를 유지한다.
  • 일반적인 커밋은 인덱스의 내용을 기록하므로 새 커밋에는 B가 들어가고 작업 트리의 C는 남은 변경으로 보인다.
  • 파일 이름을 한 번 준비했으니 이후 모든 수정이 자동 포함될 것이라는 생각은 틀리다.
  1. 세 영역이 A로 일치한다.
  2. 인덱스가 add 시점의 B를 담는다.
  3. 다시 add하지 않았으므로 인덱스는 B다.
  4. 새 커밋은 B를 기록하고 C는 작업 트리 변경으로 남는다.

설명과 함께 보는 보조 도식

처리 과정

A → B → C 편집과 인덱스의 B

일반적인 add와 commit 동작이며 -a 같은 추가 옵션은 사용하지 않는 사례다.

전체 단계를 한 번에 읽기
  1. 01

    처음

    세 영역이 A로 일치한다.

    HEAD
    A
    작업 트리
    A
    인덱스
    A
  2. 02

    B로 편집 후 add

    인덱스가 add 시점의 B를 담는다.

    작업 트리
    B
    인덱스
    B
  3. 03

    C로 다시 편집

    다시 add하지 않았으므로 인덱스는 B다.

    작업 트리
    C
    인덱스
    B
  4. 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와 비밀값 포함 여부를 검토한다.

함께 확인할 공식 자료