앗! 모르고 깃헙(GitHub)에 올렸어요!

weekwith.me
당근 테크 블로그
22 min readApr 13, 2023

들어가며

안녕하세요! 인터널 팀 서버 엔지니어 월터(Walter. Lee)예요. 인터널 팀에서 당근마켓 구성원들이 더 재미있고 능률적으로 일할 수 있도록, 사내에서 사용하는 도구를 기획하고 만드는 일을 하고 있답니다.

오늘은 최근 개발을 진행하며 생긴 실수와 이를 어떻게 빨리 파악하고 대응할 수 있었는지, 언제든 생길 수 있는 인적 오류(Human Error)를 방지하는 기술은 어떤 게 있는지 공유해 드리려고 해요.

사건의 발단

사건은 3월 31일 금요일에 발생했어요. 퇴근 직전 작업한 내용을 깃헙 저장소에 PR(Pull Request)하는 과정에서 노출되면 안 되는 값을 모르고 함께 올리게 되었어요. 다행히 몇 분 지나지 않아 바로 상황을 파악했고 곧바로 커밋을 초기화하여 노출된 부분을 없앨 수 있었어요.

당시 보안팀 구성원 분들께 공유드렸던 상황을 각색한 메세지

다행히 회사 차원이 아닌 제 개인 계정으로 사용 중인 값이어서 큰일은 없었지만, 로컬 저장소에서 초기화한 내용과 깃헙에 반영되는 건 독립적이라는 사실을 이번에 처음 알게 되었고 혹시나 이런 상황이 재발하지 않도록 기술적으로 방지할 방법은 없을지 고민이 되어 찾아본 내용들을 공유해 드리려 해요.

문득 여기까지 이야기 들으셨을 때, 아무리 주니어 개발자라지만 노출되면 안 되는 값을 커밋했다는 게 이해가 되지 않으실 수 있을 것 같아요. 환경변수 값을 하드 코딩으로 처리한 것인지, 로컬 테스트를 위한 환경변수 값을 .env 같은 파일에 관리한다고 했을 때 이를 .gitignore 파일에 등록하지 않은 것인지 등이 궁금하실 것 같아요.

하나씩 우선 답변드리면, 환경변수 값을 따로 하드 코딩에서 사용 중이지 않아요! .envrc 파일을 통해 로컬 테스트를 위한 환경변수 값을 관리 중에 있고 이를 .gitignore 파일에 등록하여 커밋되지 않게 하고 있답니다. 그렇다면 어쩌다가 노출된 것일까요? 이후에 한 번 더 블로그에 글로 작성할 예정이지만 서버리스 애플리케이션을 개발하는 과정에서 조금 더 쉬운 로컬 테스트 환경과 배포 환경을 구축하기 위해 AWS SAM(Serverless Application Model)을 사용 중이었고, 그 과정에서 로컬 테스트를 위해 Lambda를 호출(Invoke)시킬 특정 이벤트를 JSON 파일로 관리했어요. 해당 작업을 공식 문서를 바탕으로 처음 진행해보면서 이벤트를 정의한 JSON 파일에 로컬 테스트용 제 개인 계정의 값을 노출했고, 이를 .gitignore 파일에 등록하는 걸 잊게 됐고요.

시도한 해결 방법

사건이 발생하고 나서 저는 우선 침착하게 로컬 저장소에서 해당 커밋을 지우기로 결심했어요. 깃 명령어 중에 커밋을 되돌리는 명령어는 크게 두 가지가 있어요. 바로 revertreset 이에요.

revert 의 경우 ‘되돌아가다’라는 뜻을 가지고 있는 영어 단어로, 그 의미에서 알 수 있듯 커밋을 되돌리는 명령어예요. 다만, 이전 커밋을 되돌렸다는 내역까지 커밋으로 남게 돼요. 따라서 커밋해서는 안 되는 파일이나 내용을 커밋한 경우 그 내용은 여전히 히스토리에 남아 있기 때문에

reset 의 경우 완전히 커밋을 삭제하는 명령어에요. 해당 명령어를 사용할 때는 --soft , --mixed , --hard 옵션 중 하나를 사용할 수 있는데 아무것도 입력하지 않으면 기본적으로 --mixed 옵션이 사용돼요. 각각의 옵션은 깃이 작동하는 세 가지 단계 중 어느 단계까지 초기화시킬 건지 결정하는 것인데 이는 바로 이야기해 볼 깃의 작동 방식 부분에서 더 자세히 살펴볼게요.

결국 revert 명령어와 reset 명령어의 가장 큰 차이점은 되돌리고자 하는 커밋 내역을 보존하는 지 여부예요. 저는 커밋 내역을 완전히 없애면서 동시에 저장소 자체에는 여전히 작업 중이던 파일을 남겨둬야 하는 상황이었기에 reset 명령어를 --soft 옵션과 함께 사용했어요. 그리고 이전 커밋 내역이 사라진다는 것은 다시 말해 원격 저장소의 커밋 내역과 현재 내 로컬 저장소의 커밋 내역이 다르다는 걸 의미하기 때문에 로컬 저장소의 내역을 원격 저장소에 강제로 적용하기 위해 push 명령어를 사용할 때 --force 옵션을 주었어요.

정상적으로 커밋과 함께 파일도 사라진 것을 확인했고, 이전의 내가 알던 방식으로 잘 해결한 것으로 여겼던 그때! 알고보니 로컬 저장소와 원격 저장소 모두 그 파일의 내용을 찾아볼 수 있었어요. 왜 그런 걸까요? 깃의 내부 작동 방식을 하나씩 살펴보며 이를 알아볼게요.

깃의 작동 방식

init 명령어를 사용하거나 특정 저장소를 clone 명령어를 활용해 로컬 저장소로 복제하면 내부에 .git 이라는 디렉터리가 생성돼요. 아무런 커밋도 이루어지지 않은 초기의 .git 디렉터리는 아래와 같아요.

├── HEAD         # 현재 체크아웃(Checkout)한 브랜치(Branch)를 가리키는 파일
├── config # 이 프로젝트에만 적용되는 설정 옵션
├── description # GitWeb 프로그램에서 사용하는 파일
├── hooks # 클라이언트 훅 또는 서버 훅 스크립트 파일이 존재하는 디렉터리
├── info # .gitignore 파일처럼 무시할 파일의 패턴을 적어두는 디렉터리
├── objects # 모든 파일의 내용을 저장하는 일종의 데이터베이스 역할의 디렉터리
└── refs # 커밋 개체의 포인터를 저장하는 디렉터리

각각의 파일과 디렉터리가 어떤 역할을 하는지에 대해서는 옆에 주석을 통해 설명한 바와 같아요. 이중에서도 중요한 건 HEAD 파일과 objects , 그리고 refs 디렉터리예요. hooks 디렉터리 또한 중요한데 글의 마지막쯤에 확인해볼게요!

만약 하나의 파일을 새로 작성하고 add 명령어를 실행하면 아래와 같이 objects 디렉터리에 새로운 디렉터리가 생기고 index 파일이 생겨요. objects 디렉터리 내부에는 모든 콘텐츠가 일종의 데이터베이스처럼 저장된다고 하였죠? 그래서 새로 작성한 파일이 objects 디렉터리에 저장된 거예요. 이때 깃은 40자 길이의 체크섬 해시를 생성하여 파일의 이름으로 지정하는데, 해시의 첫 두 글자는 디렉터리 이름으로 사용하고 나머지 38글자를 파일 이름에 사용해요.

├── HEAD
├── index
├── objects
│ └── e3
│ └── 9e3dfc2b608453846cbf0e74aa69a1c0b7f311
└── refs

이제 cat-file 명령어에 -p 옵션을 준 뒤 내용을 보기 원하는 파일의 40자 길이의 해시값을 전달하면 파일 내용이 출력돼요. cat-file 은 저장소 개체의 파일 내용이나 타입, 그리고 크기 등을 제공하는 명령어고 -p 옵션은 pretty-print 의 준말로 해시값을 전달하면 파일의 내용을 출력해주는 것을 의미해요. 몇 가지 이해를 돕기 위해 추가적인 설명을 덧붙이면, 깃은 파일을 저장할 때 Tree 또는 Blob 개체로 저장하며 이때 Tree는 쉽게 디렉터리를 의미하고 Blob는 일반 파일을 의미한다고 생각하면 돼요. 만약 -p 옵션을 주지 않고 파일의 내용을 출력하고 싶다면 타입과 함께 해시값을 전달하면 된답니다! 여담이지만 Tree 또는 Blob 외에도 Commit과 Tag 개체가 존재해요.

여기서 잠깐! cat-file 과 같이 깃 자체의 내부 작동과 관련된 명령어를 Plumbing 명령어라 부르며, 기타 사용자의 편의성을 위해 존재하는 add , commit 같은 명령어를 Porcelain 명령어라 불러요.

예를 들어 위 해시값을 출력해보면 아래와 같은 결과를 얻을 수 있어요.

$ git cat-file -p e39e3dfc2b608453846cbf0e74aa69a1c0b7f311

# 앗! 모르고 깃헙(GitHub)에 올렸어요!

당근마켓 미디엄(Medium) 블로그에 작성된 글의 이해를 돕기 위한 레포지토리입니다.

위에서 알게 된 사실을 바탕으로 blob 개체 타입이라는 정보를 명령어의 인자로 넘겨주면 -p 옵션을 사용하지 않아도 되겠죠?

$ git cat-file blob e39e3dfc2b608453846cbf0e74aa69a1c0b7f311

# 앗! 모르고 깃헙(GitHub)에 올렸어요!

당근마켓 미디엄(Medium) 블로그에 작성된 글의 이해를 돕기 위한 레포지토리입니다.

objects 디렉터리 내부에 생긴 디렉터리와 파일 말고도 index 라는 파일도 새로 생겼죠? index 파일은 스테이징 영역(Staging Area)의 정보를 저장하는 파일이에요. 깃은 기본적으로 파일을 Committed, Modified, Staged라는 세 가지 상태로 관리해요. 이 중에서 Staged 상태는 커밋하기 직전의 상태를 의미하며, 다시 말해 스테이징 영역에 있는 파일들은 모두 커밋하기 직전의 파일들이고 Staged 상태이게 되는 거죠. 결국 커밋 직전의 파일들의 정보를 저장하고 있는 역할을 담당한다고 생각하면 돼요. 여담이지만 파일의 상태를 결정짓는 세 가지 단계를 도식화해보면 아래 이미지와 같아요.

이제 commit 명령어를 통해 저장소(Repostiory), 다시 말해 .git 디렉터리에 스냅샵을 저장하여 파일을 Committed 상태로 만들게 되면 아래와 같이 objects 디렉터리에는 새로운 두 개의 디렉터리가 추가로 생기게 돼요.

└── objects
├── 96
│ └── 1542716074164d36ef610a9ff8812125bc22b6
├── d3
│ └── f37c5ead49882286c036fc0306a78f392fc2dd
└── e3
└── 9e3dfc2b608453846cbf0e74aa69a1c0b7f311

이 중 하나는 cat-file 명령어를 통해 출력해보면 Tree 타입의 개체로 스테이징 영역에 존재하던 파일의 타입과 해시값, 그리고 실제 파일명을 저장하고 있어요. 해당 Tree 타입은 앞서 이야기한 것처럼 일종의 디렉터리 역할을 담당하기 때문에 여러 파일을 한 번에 스테이징 영역에 올리고 커밋할 경우 해당 파일이 모두 이 Tree 개체의 노드로 연결돼요.

$ git cat-file -p d3f37c5ead49882286c036fc0306a78f392fc2dd

100644 blob e39e3dfc2b608453846cbf0e74aa69a1c0b7f311 README.md

다른 하나는 Commit 개체예요. 아래와 같이 커밋의 대상이 되는 Tree 개체와 함께 커밋과 관련된 메타 데이터인 사용자 정보와 타임스탬프, 그리고 커밋 메시지가 저장되어 있어요.

$ git cat-file - p 961542716074164d36ef610a9ff8812125bc22b6

tree d3f37c5ead49882286c036fc0306a78f392fc2dd
author taehyun <0417taehyun@gmail.com> 1680790118 +0900
committer taehyun <0417taehyun@gmail.com> 1680790118 +0900

feat (main): Initial Commit

그리고 우리가 log 명령어를 통해 보게 되는 히스토리는 결국 이 커밋 개체들의 내용들이에요. log 명령어를 입력하면 아래와 같이 앞서 cat-file 명령어를 통해 확인한 내용이 조금 더 보기 좋게 정리되어 나온답니다.

$ git log

commit 961542716074164d36ef610a9ff8812125bc22b6 (HEAD -> main)
Author: taehyun <0417taehyun@gmail.com>
Date: Thu Apr 6 23:08:38 2023 +0900

feat (main): Initial Commit

revert 명령어의 작동 방식

그러면 먼저 revert 명령어가 어떻게 작동하는지 자세하게 살펴볼까요? test-revert 라는 브랜치(Branch)를 하나 생성하고 체크아웃한 뒤 .git 디렉터리 내부에 있는 HEAD 파일을 출력해보면 아래와 같이 포인터가 이제 test-revert 브랜치를 가리키고 있는 걸 확인할 수 있어요.

$ cat HEAD

ref: refs/heads/test-revert

HEAD 파일은 현재 체크아웃한 브랜치를 가리키는 파일로 포인터 역할을 한다고 생각하면 편해요. 그러면 현재 하나의 커밋만 존재하는 상황에서 main 브랜치와 새로 만든 test-revert 브랜치는 동일한 커밋을 가리키고 있고, test-revert 브랜치로 체크아웃한 상황이기 때문에 HEAD 파일이 출력한 것처럼 test-revert 브랜치를 포인터로 가리키고 있을 거예요.

이때 refs 디렉터리의 구조도 살펴보면 내부적으로 heads 디렉터리에 test-revert 라는 파일이 하나 생긴 것을 볼 수 있어요.

└── refs
└── heads
├── main
└── test-revert

즉, HEAD 라는 포인터는 test-revert 라는 브랜치로 체크아웃한 순간 refs/heads/ 디렉터리 내부에 있는 test-revert 파일을 가리키게 되고 해당 파일이 가지고 있는 값은 아래 출력되는 것과 같이 현재의 커밋 해시값이에요. 매번 해시값을 토대로 포인터를 이동하면서 작업하는 데 어려움이 있기 때문에 외우기 쉬운 이름으로 변환되어 관리되고 이것을 관리하는 게 바로 refs 디렉터리의 역할이에요. 참고로 refs 라는 이름은 참조를 의미하는 영어단어 Reference의 준말이랍니다.

$ cat main && cat test-revert

961542716074164d36ef610a9ff8812125bc22b6
961542716074164d36ef610a9ff8812125bc22b6

그러면 .env 파일을 하나 새로 생성한 뒤 노출되면 안 되는 값을 입력했다고 가정하고 새로운 커밋을 하나 추가하면, 아래와 같이 refs 디렉터리 내부에서 가리키는 해시값도 달라지고 HEAD 또한 결국 그 값을 가리키는 포인터의 역할을 하므로 이전 main 브랜치 때와는 다른 곳을 가리키겠죠?

$ cat main && cat test-revert

961542716074164d36ef610a9ff8812125bc22b6
df5543870b2ed1450d57b96a97f15302b794b1dc

이를 도식화하면 아래 이미지와 같아요.

이제 revert명령어를 통해 해당 커밋 내역을 되돌리면 결국 커밋 기록은 아래와 같이 남아요.

그리고 이를 푸쉬한 원격 저장소에서 그 결과를 확인해보면 최종적으로 .env 파일은 삭제가 된 상태로 병합될 수 있지만, 커밋 내역 자체는 남아 있기 때문에 깃헙에서는 해당 커밋 해시값을 URL에 입력해 이미 노출된 값을 쉽게 확인할 수 있어요.

reset 명령어의 작동 방식

다음으로 reset 명령어는 앞서 설명드렸던 것처럼 커밋 내역을 지워요. 사용하는 옵션에 따라 지우는 단계가 달라지는데, 깃이 작동하는 세 단계 중 Working Directory 내용까지 모두 지우는 게 --hard 옵션이고, Working Directory 내용까지는 남겨두는 게 기본값인 --mixed 옵션이며, Staging Area 단계까지 남겨두는 게 --soft 옵션이에요.

revert 명령어 때와 마찬가지로 test-reset 브랜치를 하나 새로 생성한 다음 .env 파일을 만들어 커밋을 하고 원격 저장소에 올린 뒤, reset --hard 명령어를 통해 커밋 내용을 초기화하고 다시 원격 저장소에 push 명령어를 실행하면 아래와 같은 non-fast-forward 오류가 발생해요.

$ git push origin test-reset

To <https://github.com/0417taehyun/git-practice>
! [rejected] test-reset -> test-reset (non-fast-forward)
error: failed to push some refs to '<https://github.com/0417taehyun/git-practice>'

쉽게 원격 저장소에서 가리키는 HEAD 포인터와 현재 로컬 저장소가 가리키는 HEAD 포인터가 다르다는 걸 의미해요. 원격 저장소와 로컬 저장소의 각 HEAD가 가리키는 커밋을 보면 아래 이미지와 같이 원격 저장소의 커밋이 하나 더 앞서 나가 있는 상황이기 때문에 로컬 저장소의 커밋을 원격 저장소에 적용할 수 없는 거죠.

그래서 이를 해결하기 위해서는 강제로 원격 저장소를 로컬 저장소에 맞출 수밖에 없고 push 명령어에 --force 옵션을 붙여 소스 코드를 올리면 돼요. 그 결과 노출되면 안 되는 파일은 그대로 삭제가 되었고 로컬과 원격 저장소 또한 커밋 내역이 로컬 저장소에 맞춰졌기 때문에 문제를 해결했다고 생각할 수 있어요. 하지만 force-pushed 라는 내역은 깃헙에 남아 있고, 이전 잘못된 커밋 해시값을 URL에 입력해보면 여전히 노출된 값을 확인할 수 있다는 걸 알수 있어요.

로컬 저장소 또한 문제는 여전해요. 커밋 내역과 해당 삭제 파일 내용이 아직 남아 있기 때문이에요! 지속해 커밋을 수행하는 동안 우리가 확인하지 않았지만 .git 디렉터리 내부에 있는 objects 디렉터리에는 모든 파일의 내용이 계속 저장되고 있었어요. reset 자체는 결국 커밋의 히스토리를 수정하는 과정에 불과하기 때문에 일종의 데이터베이스 역할을 하는 objects 디렉터리에는 파일이 남아있게 된 거예요. reflog 명령어를 사용하면 HEAD 포인터가 업데이트된 기록을 볼 수 있는데, 이를 통해 삭제된 커밋 해시값을 얻을 수 있고 다시 해당 커밋 해시값을 토대로 Tree 개체의 해시값과 Blob 개체의 해시값을 얻어내 cat-file 명령어를 사용하면 아래와 같이 지운 줄만 알았던 내역과 파일을 그대로 복구할 수 있어요.

$ git cat-file blob 02be1113685ab60573e6af0796197b737a704b58

AWS_ACCESS_KEY=961542716074164d36ef610a9ff8812125bc22b6

그렇다면 로컬 저장소에 남아 있는 커밋 내역과 파일을 지우려면 어떻게 해야 할까요? HEAD 포인터가 업데이트된 기록, 다시 말해 참조 기록(Reference Log)을 삭제한 뒤 가비지 컬렉터(Garbage Collector, GC)를 사용하여 불필요한 개체를 자동으로 삭제하면 돼요. 먼저 참조 기록을 삭제하기 위해서는 앞서 사용했던 reflog 명령어에 expire --all --expire=now 명령어를 함께 사용해야 해요. 이는 지금 시간 이전의 모든 참조 기록을 만료시킨다는 의미로 해당 명령어를 수행한 뒤 reflog 명령어를 수행하면 HEAD 포인터의 참조 기록이 하나도 남지 않은 것을 알 수 있어요. 다음으로 gc 명령어를 사용해서 불필요한 개체를 삭제시켜야 하는데 이때 --prune 옵션의 인자로 now 을 전달해서 사용해야 해요. 그러면 아래와 같이 불필요한 개체가 삭제된 것을 확인할 수 있어요.

$ git reflog expire --all --expire=now && git gc --prune=now

Enumerating objects: 7, done.
Counting objects: 100% (7/7), done.
Delta compression using up to 12 threads
Compressing objects: 100% (5/5), done.
Writing objects: 100% (7/7), done.
Total 7 (delta 1), reused 5 (delta 1), pack-reused 0

이제 cat-file 명령어를 통해 아까 해시값을 다시 한번 입력해보면 이제는 개체 삭제되어 해당 파일은 로컬에서 완전히 지워진 걸 알 수 있어요.

$ git cat-file blob 02be1113685ab60573e6af0796197b737a704b58

fatal: Not a valid object name 02be1113685ab60573e6af0796197b737a704b58

revert 명령어를 사용해야 할 때와 reset 명령어를 사용해야 할 때

그렇다면 언제 revert 명령어와 reset 명령어를 사용해야 할까요?

revert 명령어는 되돌리고자 하는 커밋도 보관한 채 커밋을 되돌린 내역을 기록으로 남기기 때문에 협업에 있어 히스토리 관리가 잘 된다는 장점이 있어요. 하지만 그만큼 너무 많은 사용은 오히려 히스토리 관리를 힘들게 하고, 노출되면 안 되는 값을 커밋한 경우 그 내역 또한 그대로 남아 있기 때문에 문제가 될 수 있죠.

반대로 reset 명령어의 경우 커밋 자체를 초기화하기 때문에 노출되면 안 되는 값을 커밋한 경우 그 내역을 지울 수 있다는 장점과 함께 커밋 히스토리를 단순화할 수 있는 장점은 있지만, 여전히 그 내역을 호스팅하는 플랫폼에는 남아 있을 수 있기 때문에 별도의 주의가 추가로 필요하다는 단점과 함께 협업에 있어 충돌(Conflict)을 발생시킬 상황이 자주 일어날 수 있어요.

따라서 기본적으로 보안을 고려할 때는 깃헙 등에서의 내역을 먼저 직접 지우고 해당 내용을 로컬 저장소로 가져오는 방식으로 작업을 진행하는 게 좋으며, 원격 저장소에 작업 내용을 올리기 전에는 보안 관련 문제가 아닐 경우 협업을 고려하여 revert 명령어를, 보안을 고려할 때는 reset 명령어를 사용하는 게 좋아요.

물론 커밋 내역을 없애기 위해서는 이외에도 squash 등의 다양한 방식을 사용할 수도 있고, 프로젝트를 진행하는 데 있어 마주할 수 있는 상황은 너무나도 다양하기 때문에 본인의 상황을 고려하는 게 제일 좋겠죠?

사전에 문제를 방지하는 방법

문득 여기까지 이야기를 들으시며 이러한 오류는 충분히 사람이 발생시킬 수 있는 수준의 인적 오류(Human Error)라는 생각이 드셨을 거예요. 그렇다면 과연 이를 어떻게 기술적으로 해결할 수 있을까요? 먼저 제 상황의 경우 깃가디언(GitGuardian)이라는 위험 탐지 프로그램을 깃헙에 연동시켜 두었기에 곧바로 상황을 인지할 수 있었어요. 문제는 상황이 발생한 뒤에 해결하기에는 만약 저장소가 공개 저장소(Public Repository)인 경우 너무 늦는다는 점이에요. 더욱이 저처럼 로컬 저장소에서의 삭제가 곧 완벽한 호환으로 이루어질 것으로 생각한 경우 상황은 걷잡을 수 없이 악화할 수 있고요. 애초에 원격 저장소에 소스 코드를 올리기 전에 문제를 해결할 수는 없을까요? 로컬 저장소 영역에서 커밋 때 비밀 값들이 노출되는 경우 알림을 받을 수는 없을까요?

깃 또한 특정 이벤트 발생에 따라 정해진 스크립트를 실행시킬 수 있어요. 이 훅은 클라이언트 사이드와 서버 사이드로 구분되는데, 로컬 저장소에서 commit 명령어 등을 통해 작동시키는 훅이 클라이언트 사이드고 push 명령어 등을 통해 원격 저장소로 소스 코드를 전달할 때 작동시키는 훅이 서버 사이드예요. 결국 클라이언트 사이드 훅인 pre-commit 을 사용하여 커밋 전에 스크립트를 훅으로 작동시켜 노출되는 비밀 값을 사전에 탐지하고 알려줄 수 있어요. 만약 pre-commit 이 익숙하지 않다면 이전에 제가 쓴 글인 파이썬을 처음 사용하는 동료와 효율적으로 일하는 방법을 한번 읽어보시는 걸 추천해 드려요! 파이썬의 경우 특히 pre-commit 패키지와 함께 .pre-commit-config.yaml 같은 YAML 파일을 통해 깃 훅을 편하게 쓸 수 있어요. 깃가디언 또한 The pre-commit framework라는 제목으로 pre-commit 패키지를 사용하여 커밋 단계에서 보안을 강화하는 방법을 공식 문서로 제공 중이에요.

한 걸음 더 나아가볼까요? 기본적으로 .git 디렉터리 내에 존재하는 hooks 디렉터리에 스크립트 파일을 저장하여 훅을 작동시킬 수 있는데 이는 결국 개별 프로젝트별로 설정해줘야 함을 의미해요. 그렇다면 전역으로 훅을 관리할 방법은 없을까요? 이를테면 전역 범위(Global Scope)에서 자동으로 커밋에 대한 훅 스크립트를 인지하여 어떤 프로젝트에라도 깃가디언이 작동해 실수로라도 중요한 값들이 노출되지 않게 하는 거죠.

$ git config --global init.templatedir '~/.git-templates'
$ mkdir -p ~/.git-templates/hooks
$ vim ~/.git-templates/pre-commit

이렇게 템플릿을 활용하는 방식 외에도 core.hooksPath 같은 전역 범위 설정값을 지정해 훅이 실행되는 기본 경로를 설정해줄 수도 있어요. 깃 버전에 따라 제공되는 config 값이 다르기 때문에 본인의 깃 버전과 함께 공식 문서를 확인해보시는 걸 추천해 드려요!

마치며

reset 명령어를 쓰면 커밋 내역이 삭제되기 때문에 원격 저장소에도 반영이 된다는 잘못된 사실은 여러 블로그 글을 보며 학습한 것이었어요. 이번 일화를 통해 정보를 습득하는 데 있어 신뢰성 있는 출처, 다시 말해 공식 문서나 실제 소스 코드 등을 살펴보는 게 얼마나 중요한지 깨닫게 됐어요. 깃을 많이 쓴다고 해서 잘 쓸 수 있다는 걸 의미하지 않는다는 것도 다시 한번 느끼게 되었고, 보안에 있어서는 항상 예민하고 확실한 태도를 취해야 한다는 것도 배울 수 있었고요.

이번에는 실수로 시작해 새로 배우게 된 것들을 공유했지만, 다음에는 성장과 성취를 바탕으로 더 많은 인사이트를 담아 전해보겠습니다.

참고

--

--

weekwith.me
당근 테크 블로그

Software engineer who thrives on having excellent teamwork and understanding the product’s domain