마루옵스서버 · 네트워크 · AI 기술 노트

소개문의

[Git·GitOps 06] 이력 읽는 법: 커밋 그래프·HEAD·diff·bisect

어두운 곳에서 조명이 켜진 노트북 키보드

참고 사진 · 어두운 곳에서 조명이 켜진 노트북 키보드 · 사진: Markus Petritz petritzdesigns / 원본 / CC0 · 목록용 크기 조정

Git을 오래 쓰게 만드는 기능은 과거 파일을 꺼내는 것보다 변경의 경로를 읽는 기능에 가깝습니다. 언제 설정이 바뀌었는지, 어떤 브랜치에서 합쳐졌는지 확인할 수 있어요. 문제가 생겼을 때도 최근 커밋을 순서 없이 지우는 대신 원인을 좁힐 수 있습니다.

5편에서 만든 gitops-demo처럼 커밋이 두 개 이상 있는 저장소를 사용합니다. 이 글의 조회 명령은 작업 파일을 바꾸지 않습니다. 체크아웃이나 bisect를 시작하기 전에는 미완료 작업을 커밋하거나 별도로 보관합니다.

Git·GitOps 전체 연재 · 06 / 24

이 글의 목차

전체 그래프와 개별 변경을 따로 본다

git status --short --branch
git log --oneline --graph --decorate --all -15
git show --stat HEAD
git show HEAD -- README.md
git diff HEAD~1 HEAD -- README.md

--graph는 부모 관계를 그려 주고, --decorate는 브랜치나 태그가 가리키는 위치를 표시합니다. 해시가 시간순 번호가 아니므로 글자 크기나 숫자로 먼저 만든 커밋을 판단하지 않습니다. HEAD~1은 현재 커밋의 첫 번째 부모이며 첫 커밋에는 존재하지 않습니다.

특정 파일의 변경을 따라갈 때에는 경로를 지정합니다. -- 뒤는 경로라는 뜻이므로 브랜치 이름과 파일 이름이 겹쳐도 모호함을 줄일 수 있어요.

git log --oneline -- README.md
git log -p -2 -- README.md
git blame README.md

blame은 현재 줄이 어느 커밋에서 왔는지 보여줍니다. 담당자를 비난하거나 버그의 원인을 확정하는 도구는 아닙니다. 줄을 이동하거나 형식을 바꾼 커밋이 표시될 수 있으므로 해당 커밋의 설명과 앞뒤 변경도 읽습니다.

커밋이 그래프가 되는 이유

Git은 파일 내용인 blob, 파일 이름과 구성을 담는 tree, 그 tree와 부모 등을 가리키는 commit 객체를 사용합니다. 커밋은 이전 커밋을 참조하므로 이력이 방향을 가진 비순환 그래프, DAG를 이룹니다. 병합 커밋은 부모를 둘 이상 가질 수 있습니다.

git rev-parse HEAD
git cat-file -t HEAD
git cat-file -p HEAD

직접 본 객체에는 tree, parent, 작성 정보와 메시지가 나타납니다. 객체 ID는 내용에 기반하므로 커밋 메시지나 부모가 바뀌어도 새 ID가 됩니다. 이 때문에 rebase로 변경을 다시 쌓으면 파일 내용이 비슷해도 커밋 해시가 달라집니다.

브랜치는 이 객체를 통째로 복사한 폴더가 아니라 특정 커밋을 가리키는 이름입니다. 보통 HEAD는 현재 브랜치를 가리키며, 새 커밋을 만들면 그 브랜치가 앞으로 이동합니다.

과거 상태를 보기 위한 detached HEAD

git status --porcelain
git switch --detach HEAD~1
# Inspect files here. Do not start unrecorded work.
git switch main

브랜치 대신 커밋을 직접 체크아웃하면 detached HEAD 상태입니다. 이 상태에서도 커밋은 만들 수 있지만, 돌아갈 이름을 만들지 않고 이동하면 찾기 어려워질 수 있어요. 여기서 작업을 보존하려면 이동 전에 git switch -c inspect-fix처럼 브랜치를 만듭니다.

문제가 시작된 커밋을 절반씩 좁힌다

커밋이 수백 개라면 git bisect가 유용합니다. 정상 커밋과 문제 커밋 사이를 나누어 검사하므로 후보를 선형으로 하나씩 읽는 것보다 적은 판단으로 좁힐 수 있습니다. 단, 같은 테스트가 같은 기준으로 성공·실패해야 합니다.

# Replace GOOD_COMMIT with a known-good commit ID.
git bisect start
git bisect bad HEAD
git bisect good GOOD_COMMIT
# Test the selected commit, then mark one result:
git bisect good
# Repeat with good or bad until Git identifies a commit.
git bisect reset

위의 good은 실제 테스트가 통과했을 때만 표시합니다. 자동화는 git bisect run 테스트명령을 사용할 수 있지만, 테스트 실행 불가와 실패를 구분해야 합니다. 아직 이력이 짧다면 지금은 log → show → diff 순서를 익히는 것으로 충분합니다.

객체 구조는 Git Internals: Git Objects, 탐색 방법은 git bisect를 근거로 설명했습니다.

이전 글 · [Git·GitOps 05] 첫 저장소 만들기: add·commit·status·diff를 쓰는 순서 · 다음 글 · [Git·GitOps 07] 되돌리기 전에 고르는 명령: restore·reset·revert · 연재 목록과 예제 파일

댓글

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

닉네임·이메일·댓글 내용·IP 주소·브라우저 정보는 댓글 게시 및 관리에 사용됩니다. 이메일과 접속 정보는 공개 댓글에 표시하지 않습니다. 개인정보 요청은 운영자 이메일로 보내 주세요. 개인정보처리방침