Git refs: An overview
우주를 테마로 한 자습서를 통해 Git의 기본 내용을 배워보세요.
커밋을 참조하는 다양한 방법을 이해하면 이러한 모든 명령을 훨씬 더 강력하게 만들 수 있습니다. 이 챕터에서는 커밋을 참조하는 여러 방법을 살펴보고 git checkout
, git branch
및 git push
와 같은 일반적인 명령의 내부 작동 방식에 대해 알아보겠습니다.
또한 Git의 reflog 메커니즘을 통해 액세스하여 “손실된” 것처럼 보이는 커밋을 되살리는 방법도 살펴보겠습니다.
해시
커밋을 참조하는 가장 직접적인 방법은 SHA-1 해시를 사용하는 것입니다. 이 해시는 각 커밋의 고유한 ID 역할을 합니다. git log
출력에서 모든 커밋의 해시를 찾을 수 있습니다.
commit 0c708fdec272bc4446c6cabea4f0022c2b616eba Author: Mary Johnson Date: Wed Jul 9 16:37:42 2014 -0500 Some commit message
커밋을 다른 Git 명령에 전달할 때는 커밋을 고유하게 식별할 수 있을 정도의 문자만 지정하면 됩니다. 예를 들어, 다음 명령을 실행하여 git show
로 위의 커밋을 검사할 수 있습니다.
git show 0c708f
브랜치, 태그 또는 다른 간접 참조를 해당 커밋 해시로 확인해야 하는 경우가 있습니다. 이 경우에는 git rev-parse
명령을 사용할 수 있습니다. 다음은 main
브랜치가 가리키는 커밋의 해시를 반환합니다.
관련 자료
전체 Git 리포지토리를 이동하는 방법
솔루션 보기
Bitbucket Cloud에서 Git에 대해 알아보기
git rev-parse main
이것은 커밋 참조를 허용하는 사용자 지정 스크립트를 작성할 때 특히 유용합니다. 커밋 참조를 수동으로 구문 분석하는 대신 git rev-parse
가 입력을 정규화하도록 할 수 있습니다.
참조
ref는 커밋을 간접적으로 참조하는 방식으로 커밋 해시에 대한 사용자 친화적인 별칭으로 생각할 수 있습니다. 브랜치와 태그를 나타내는 Git의 내부 메커니즘입니다.
참조는 .git/refs
디렉터리에 일반 텍스트 파일로 저장되며, 여기서 .git
은 보통 .git
이라고 부릅니다. 리포지토리에서 참조를 살펴보려면 .git/refs
로 이동합니다. 다음과 같은 구조가 표시되어야 하지만 리포지토리에 있는 브랜치, 태그 및 원격에 따라 다른 파일이 포함됩니다.
.git/refs/ heads/ main some-feature remotes/ origin/ main tags/ v0.9
heads
디렉터리는 리포지토리의 모든 로컬 브랜치를 정의합니다. 각 파일 이름은 해당 브랜치의 이름과 일치하며 파일 안에는 커밋 해시가 있습니다. 이 커밋 해시는 브랜치 팁의 위치입니다. 이것을 확인하기 위해 Git 리포지토리의 루트에서 다음 2개의 명령을 실행해 보겠습니다.
# Output the contents of `refs/heads/main` file: cat .git/refs/heads/main # Inspect the commit at the tip of the `main` branch: git log -1 main
cat
명령으로 반환되는 커밋 해시는 git log
에 표시된 커밋 ID와 일치해야 합니다.
main
브랜치의 위치를 변경하려면 Git은 refs/heads/main
파일의 콘텐츠를 변경하기만 하면 됩니다. 마찬가지로 새 브랜치를 만들려면 새 파일에 커밋 해시를 작성하기만 하면 됩니다. 이것이 Git 브랜치가 SVN에 비해 매우 가벼운 이유 중 하나입니다.
tags
디렉터리는 똑같은 방식으로 작동하지만 브랜치 대신 태그를 포함합니다. remotes
디렉터리에는 git remote
로 만든 모든 원격 리포지토리가 별도의 하위 디렉터리로 나열됩니다. 각각의 내부에는 리포지토리로 가져온 모든 원격 브랜치가 있습니다.
Specifying refs
Git 명령에 참조를 전달할 때 참조의 전체 이름을 정의하거나 짧은 이름을 사용하여 Git이 일치하는 참조를 검색하도록 할 수 있습니다. 참조의 짧은 이름은 브랜치를 이름으로 참조할 때마다 사용하는 것이므로 이미 익숙할 것입니다.
git show some-feature
위 명령의 some-feature
인수는 사실 브랜치의 짧은 이름입니다. Git은 이것을 사용하기 전에 refs/heads/some-feature
로 확인합니다. 명령줄에서 다음과 같이 전체 참조를 지정할 수도 있습니다.
git show refs/heads/some-feature
이렇게 하면 참조의 위치에 대한 모호성을 방지할 수 있습니다. 예를 들어, some-feature
라는 태그와 브랜치가 둘 다 있는 경우 이 참조가 필요합니다. 그러나 적절한 명명 규칙을 사용하는 경우 태그와 브랜치 간의 모호성은 일반적으로 문제가 되지 않습니다.
Refspecs 섹션에서 더 많은 전체 참조 이름을 살펴보겠습니다.
Packed refs
대규모 리포지토리의 경우 Git은 주기적으로 가비지 컬렉션을 수행하여 불필요한 개체를 제거하고 참조를 단일 파일로 압축하여 성능을 높입니다. 가비지 컬렉션 명령을 사용하여 이 압축을 강제할 수 있습니다.
git gc
이렇게 하면 refs
폴더에 있는 모든 개별 브랜치와 태그 파일이 .git
디렉터리 상단에 있는 packed-refs
라는 단일 파일로 이동합니다. 이 파일을 열면 커밋 해시가 참조에 매핑된 것을 찾을 수 있습니다.
00f54250cf4e549fdfcafe2cf9a2c90bc3800285 refs/heads/feature 0e25143693cfe9d5c2e83944bbaf6d3c4505eb17 refs/heads/main bb883e4c91c870b5fed88fd36696e752fb6cf8e6 refs/tags/v0.9
외부에서 일반적인 Git 기능은 영향을 받지 않습니다. 하지만 .git/refs
폴더가 비어 있는 이유가 궁금하다면 여기에서 참조를 찾을 수 있습니다.
Special refs
refs
디렉터리 외에도 최상위 수준의 .git
디렉터리에 있는 몇 가지 특수 참조가 있으며 다음과 같습니다.
HEAD
– 현재 체크아웃한 커밋/브랜치.FETCH_HEAD
– 원격 리포지토리에서 가장 최근에 가져온 브랜치.ORIG_HEAD
–HEAD
에 대한 급격한 변경 이전의 백업 참조.MERGE_HEAD
–git merge
를 사용하여 현재 브랜치에 병합하는 커밋.CHERRY_PICK_HEAD
– 현재 cherry pick하는 커밋.
이러한 참조는 모두 필요한 경우 Git에서 만들고 업데이트합니다. 예를 들어, git pull
명령은 먼저 git fetch
를 실행하며 이 명령은 FETCH_HEAD
참조를 업데이트합니다. 그런 다음 git merge FETCH_HEAD
를 실행하여 가져온 브랜치를 리포지토리로 풀하는 작업을 완료합니다. 물론 HEAD
로 작업한 것처럼 이 모든 것을 다른 참조처럼 사용할 수 있습니다.
이러한 파일에는 유형과 리포지토리 상태에 따라 다른 콘텐츠가 포함되어 있습니다. HEAD
참조에는 커밋 해시 대신 다른 참조에 대한 참조인 심볼릭 참조 또는 커밋 해시가 포함될 수 있습니다. 예를 들어, main
브랜치에 있을 때 HEAD
의 콘텐츠를 살펴봅시다.
git checkout main cat .git/HEAD
그러면 ref: refs/heads/main
이 출력되는데, HEAD
가 refs/heads/main
참조를 가리킨다는 뜻입니다. 이것이 바로 Git이 main
브랜치가 현재 체크아웃되었음을 아는 방법입니다. 다른 브랜치로 전환하면 HEAD
의 콘텐츠가 새 브랜치를 반영하도록 업데이트됩니다. 그러나 브랜치 대신 커밋을 체크아웃하면 HEAD
에는 심볼릭 참조 대신 커밋 해시가 포함됩니다. 그러면 Git은 리포지토리가 분리된 HEAD 상태라는 것을 알게 됩니다.
대부분의 경우 HEAD
가 직접 사용하게 될 유일한 참조입니다. 나머지는 일반적으로 Git의 내부 작업에 연결해야 하는 하위 수준의 스크립트를 작성할 때만 유용합니다.
Refspecs
refspec은 로컬 리포지토리의 브랜치를 원격 리포지토리의 브랜치에 매핑합니다. 이렇게 하면 로컬 Git 명령을 사용하여 원격 브랜치를 관리하고 일부 고급 git push
및 git fetch
동작을 구성할 수 있습니다.
refspec은 [+]
<src>
:
<dst>
로 지정됩니다. <src>
매개 변수는 로컬 리포지토리의 소스 브랜치이며, <dst>
매개 변수는 원격 리포지토리의 대상 브랜치입니다. 선택 사항인 +
기호는 원격 리포지토리가 빨리 감기가 아닌 업데이트를 수행하도록 강제합니다.
Refspecs를 git push
명령과 함께 사용하여 원격 브랜치에 다른 이름을 지정할 수 있습니다. 예를 들어, 다음 명령은 일반적인 git push
처럼 main
브랜치를 origin
원격 리포지토리에 푸시하지만 origin
리포지토리의 브랜치 이름으로 qa-main
을 사용합니다. 이것은 자체 브랜치를 원격 리포지토리로 푸시해야 하는 QA 팀에 유용합니다.
git push origin main:refs/heads/qa-main
You can also use refspecs for deleting remote branches. This is a common situation for feature-branch workflows that push the feature branches to a remote repo (e.g., for backup purposes). The remote feature branches still reside in the remote repo after they are deleted from the local repo, so you get a build-up of dead feature branches as your project progresses. You can delete them by pushing a refspec that has an empty parameter, like so:
git push origin :some-feature
원격 리포지토리에 로그인하여 원격 브랜치를 수동으로 삭제할 필요가 없으므로 매우 편리합니다. Git v1.7.0부터는 위의 방법 대신 --delete
플래그를 사용할 수 있습니다. 다음은 위의 명령과 동일한 영향을 미칩니다.
git push origin --delete some-feature
Git 구성 파일에 몇 줄을 추가하면 refspecs를 사용하여 git fetch
의 동작을 변경할 수 있습니다. 기본적으로 git fetch
는 원격 리포지토리의 모든 브랜치를 가져옵니다. 그 이유는 .git/config
파일의 다음 섹션에 있습니다.
[remote "origin"] url = https://git@github.com:mary/example-repo.git fetch = +refs/heads/*:refs/remotes/origin/*
fetch
줄은 git fetch
에 origin
리포지토리에서 모든 브랜치를 다운로드하도록 지시합니다. 그러나 일부 워크플로에는 모든 브랜치가 필요하지 않습니다. 예를 들어, 많은 지속적 통합 워크플로는 main
브랜치에만 관심이 있습니다. main
브랜치만 가져오려면 fetch
줄을 다음과 일치하도록 변경합니다.
[remote "origin"] url = https://git@github.com:mary/example-repo.git fetch = +refs/heads/main:refs/remotes/origin/main
git push
도 비슷한 방식으로 구성할 수 있습니다. 예를 들어, 항상 origin
원격에서 main
브랜치를 qa-main
으로 푸시하고 싶다면 구성 파일을 다음과 같이 변경하면 됩니다(위에서 설명한 것처럼).
[remote "origin"] url = https://git@github.com:mary/example-repo.git fetch = +refs/heads/main:refs/remotes/origin/main push = refs/heads/main:refs/heads/qa-main
Refspec을 사용하면 다양한 Git 명령으로 리포지토리 간에 브랜치를 전송하는 방법을 완전히 제어할 수 있습니다. 로컬 리포지토리에서 브랜치의 이름을 변경하고, 브랜치를 삭제하며, 이름이 다른 브랜치를 가져오거나 푸시하고, 사용자가 원하는 브랜치에서만 작동하도록 git push
및 git fetch
를 구성할 수 있습니다.
Relative refs
또한 다른 커밋과 관련된 커밋을 참조할 수도 있습니다. ~
문자를 사용하면 상위 커밋에 도달할 수 있습니다. 예를 들어, 다음은 최상위 HEAD
를 표시합니다.
git show HEAD~2
그러나 병합 커밋으로 작업할 때는 상황이 조금 더 복잡해집니다. 병합 커밋에는 상위 항목이 두 개 이상 있으므로 따라갈 수 있는 경로도 두 개 이상 있습니다. 3방향 병합의 경우 첫 번째 상위 항목은 병합을 수행할 때 있던 브랜치의 것이고, 두 번째 상위 항목은 git merge
명령에 전달한 브랜치의 것입니다.
~
문자는 항상 병합 커밋의 첫 번째 상위 항목 다음에 옵니다. 다른 상위 항목을 따라가려면 ^
문자를 사용하여 상위 항목을 지정해야 합니다. 예를 들어, HEAD
가 병합 커밋이면 다음은 HEAD
의 두 번째 상위 항목을 반환합니다.
git show HEAD^2
^
문자를 두 개 이상 사용하여 둘 이상의 세대를 이동할 수 있습니다. 예를 들어, 이것은 HEAD
의 최상위 항목(병합 커밋이라고 가정)을 표시하며 이 항목은 두 번째 상위 항목에 있습니다.
git show HEAD^2^1
~
및 ^
의 작동 방식을 명확히 하기 위해 다음 그림에서 상대 참조를 사용하여 A
에서 커밋에 도달하는 방법을 보여드리겠습니다. 경우에 따라 커밋에 도달하는 방법은 여러 가지가 있습니다.
상대 참조는 일반 참조와 동일한 명령으로 사용할 수 있습니다. 예를 들어, 다음 명령은 모두 상대 참조를 사용합니다.
# Only list commits that are parent of the second parent of a merge commit git log HEAD^2 # Remove the last 3 commits from the current branch git reset HEAD~3 # Interactively rebase the last 3 commits on the current branch git rebase -i HEAD~3
The reflog
reflog는 Git의 안전망입니다. 스냅샷을 커밋했는지 여부에 관계없이 리포지토리에서 수행하는 거의 모든 변경 사항을 기록합니다. 로컬 리포지토리에서 수행한 모든 작업을 시간순으로 기록한 것이라고 생각할 수 있습니다. reflog를 보려면 git reflog
명령을 실행합니다. 그러면 다음과 비슷하게 출력될 것입니다.
400e4b7 HEAD@{0}: checkout: moving from main to HEAD~2 0e25143 HEAD@{1}: commit (amend): Integrate some awesome feature into `main` 00f5425 HEAD@{2}: commit (merge): Merge branch ';feature'; ad8621a HEAD@{3}: commit: Finish the feature
이것은 다음과 같이 번역할 수 있습니다.
- 방금
HEAD~2
을 체크아웃했습니다 - 그 전에 커밋 메시지를 수정했습니다
- 그 전에
feature
브랜치를main
에 병합했습니다 - 그 전에 스냅샷을 커밋했습니다
HEAD{
구문을 사용하면 reflog에 저장된 커밋을 참조할 수 있습니다. 이전 섹션의 HEAD~
참조와 매우 비슷하게 작동하지만
은 커밋 기록 대신 reflog의 항목을 참조합니다.
이것을 사용하여 손실될 수도 있었던 상태를 되돌릴 수 있습니다. 예를 들어, git reset
으로 새 기능을 삭제했다고 가정해 봅시다. reflog는 다음과 비슷하게 표시될 것입니다.
ad8621a HEAD@{0}: reset: moving to HEAD~3 298eb9f HEAD@{1}: commit: Some other commit message bbe9012 HEAD@{2}: commit: Continue the feature 9cb79fa HEAD@{3}: commit: Start a new feature
git reset
이전의 커밋 3개는 이제 필요하지 않으므로 reflog를 통하지 않고는 참조할 방법이 없습니다. 이제 이 모든 작업을 버리지 말았어야 했다는 것을 깨달았다고 가정해 보겠습니다. HEAD@{1}
커밋을 체크아웃하고 git reset
을 실행하기 전의 리포지토리 상태로 돌아가기만 하면 됩니다.
git checkout HEAD@{1}
그러면 분리된 HEAD
상태가 됩니다. 여기서부터 새 브랜치를 만들고 기능에 대한 작업을 계속할 수 있습니다.
요약
이제 Git 리포지토리에서 커밋을 참조하는 것이 꽤 편하게 느껴질 것입니다. 이 문서에서는 브랜치와 태그가 .git
하위 디렉터리에 참조로 저장되는 방법, packed-refs
파일을 읽는 방법, HEAD
가 표현되는 방법, 고급 푸시와 가져오기에 refspecs를 사용하는 방법, 상대적 ~
및 ^
연산자를 사용하여 브랜치 계층 구조를 거치는 방법을 알아봤습니다.
다른 방법으로는 사용할 수 없는 커밋을 참조하는 방법인 reflog도 살펴봤습니다. 이것은 “이런, 그렇게 하지 말았어야 했는데”와 같은 상황에서 회복할 수 있는 좋은 방법입니다.
요점은 어떤 개발 시나리오에서든 필요한 커밋을 정확히 골라낼 수 있다는 것이었습니다. git log
, git show
, git checkout
, git reset
, git revert
, git rebase
등을 포함하여 가장 일반적인 명령 중 일부가 참조를 인수로 허용하기 때문에 이 문서에서 배운 기술을 기존 Git 지식과 비교하여 손쉽게 활용할 수 있습니다.
이 문서 공유
다음 토픽
여러분께 도움을 드릴 자료를 추천합니다.
이러한 리소스에 책갈피를 지정하여 DevOps 팀의 유형에 대해 알아보거나 Atlassian에서 DevOps에 대한 지속적인 업데이트를 확인하세요.