코드를 작성하고 원격 저장소로 push를 하면, 이게 로컬이 아닌 서버 pc에서 문제없이 작동할지에 대한 의문을 갖게 된다. 코드 통합과 배포를 도와주는 CI/CD 툴을 사용하지 않고, Git 자체 기능을 활용해서 branch merge 이전에 테스트 코드를 실행시킬 수 있는 방법을 찾아보았다.

 

hook

GitLab 같은 CI/CD 도구를 사용하지 않고도, git bare repository에서 누군가 push를 했을 때, 이를 감지하여 자동으로 명령을 실행하는 스크립트를 작성할 수 있는데, 이것이 Git의 "hook" 이라는 기능이다. Git hooks는 특정 이벤트(ex, push, commit, merge)가 발생할 때 자동으로 실행되는 스크립트를 작성할 수 있는 Git의 내장 기능이다. Git의 hook 기능은 다양한 Git 이벤트에 대해 트리거를 설정할 수 있다. hook은 로컬에서 발생하는 이벤트에 대해 동작하는 client side hook과 bare repository에서 발생하는 server side hook으로 나눌 수 있다.

 

Client side hook

클라이언트 사이드에서 작동하는 hook은 개발자의 로컬 환경에서 발생하는 이벤트에 반응한다. 클라이언트 사이드 hook은 주로 commit이나 checkout과 같은 작업이 발생할 때 실행된다.

  • pre-commit: git commit을 실행하기 전에 트리거된다.
  • prepare-commit-msg: 커밋 메시지가 생성되기 전에 실행된다.
  • commit-msg: 커밋 메시지가 입력된 후 실행되며, 커밋 메시지를 검사하거나 수정하는 데 사용할 수 있다.
  • post-commit: 커밋 후에 실행되며, 로그를 남기거나 알림을 설정할 때 유용하다.
  • pre-rebase: git rebase를 시작하기 전에 실행된다.
  • post-checkout: 브랜치 변경 또는 특정 커밋으로 체크아웃한 후 실행된다.
  • post-merge: git merge 후에 실행된다.

클라이언트 사이드 훅의 예시로, 커밋 컨벤션에 어긋나는 커밋 메시지를 걸러내는 검사 코드를 보자.

#!/bin/sh
# commit-msg hook 예시
# 커밋 메시지가 규칙에 맞지 않으면 거부합니다.

COMMIT_MSG_FILE=$1
COMMIT_MSG=$(cat $COMMIT_MSG_FILE)

if ! echo "$COMMIT_MSG" | grep -qE "^(feat|fix|chore|docs): .+"; then
  echo "Invalid commit message format. Use 'feat|fix|chore|docs: <message>'"
  exit 1
fi

 

Server side hook

서버 사이드 훅은, git bare repository에서 특정 이벤트가 발생할 때 실행된다. 주로 코드가 푸시되었을 때, 배포와 관련된 작업을 자동화하는 데 사용된다.

  • pre-receive: 푸시된 내용이 서버에 적용되기 전에 트리거된다.
  • update: 특정 브랜치에 푸시가 발생했을 때 트리거된다. 개별 참조에 대한 푸시를 검사하거나 처리할 수 있다.
  • post-receive: 푸시된 내용이 서버에 저장된 후 트리거된다. 푸시된 코드에 대해 자동화된 테스트, 배포 등의 작업을 처리할 수 있다.
  • post-update: 업데이트된 브랜치나 태그에 대해 후처리 작업을 할 때 사용된다.
  • pre-rebase, post-rebase: 리베이스 작업 전후로 트리거된다.

서버 사이드 훅의 예시로, 푸시 이후 자동으로 배포 과정을 거치는 코드를 살펴보자.

#!/bin/bash
# post-receive hook 예시
# 푸시 후, 자동으로 서버에서 최신 코드를 pull하고 배포합니다.

# 배포 디렉터리로 이동
cd /path/to/deployment

# 최신 코드 가져오기
git pull origin main

# 빌드 및 배포
npm install
npm run build

 

post-receive hook을 이용해서 스크립트를 실행하는 예시

먼저 Git bare repository로 이동한다.

cd /path/to/your/bare/repository.git
cd hooks

그리고 post-receive hook을 작성해보자. post-receive라는 파일을 생성하고 실행 가능하도록 권한을 부여하자, push가 발생한 후 실행될 스크립트를 작성한다. push된 코드를 pull하여 로컬 서버에 배포하고, 테스트 스크립트를 실행하는 내용으로 예시를 들어 보겠다.

nano post-receive
chmod +x post-receive
#!/bin/bash

# 로컬 서버로 이동
cd /path/to/deployment/directory

# 저장소에서 최신 코드 가져오기
git pull /path/to/your/bare/repository.git main

# 필요한 경우, 빌드 또는 배포 명령 실행
# 예: npm install && npm run build
# npm install
# npm run build

# 테스트 실행
# 예: npm test 또는 curl을 이용한 간단한 서버 상태 체크
npm test

# 테스트 결과가 실패하면 경고 메시지 또는 알림 전송 (예: Slack, 이메일)
728x90

문제상황

개발을 진행하던 중, feat-a 라는 브랜치에서 코드 작업을 하다가 여러 commit이 진행되었다. 그러다 문제가 발생해서 2개의 commit만큼 working tree를 되돌리기 위해 git reset --hard HEAD^ 명령어를 두 번 수행했다. 그럼 두 번 되돌리던 중 앞서 지나간 commit은 어떻게 되는 것이며, 어떻게 되돌릴 수 있을까?

 

해결방법

git reset --hard HEAD^ 명령을 여러 번 수행하면, 현재 브랜치의 HEAD를 과거 커밋으로 이동시키고, 그 시점 이후의 모든 변경 사항을 working tree와 staging area에서 제거한다. 즉, 최근의 커밋 이력이 없어진 것처럼 보이게 된다. 하지만, 앞서 존재했던 커밋 이력은 영구적으로 삭제된 것은 아니다. Git은 이를 dangling commit(떠도는 커밋)이라고 부르며, Git의 내부 데이터 구조에 여전히 남아있다. 이 커밋들은 Git이 자동으로 저장하는 reflog를 통해 추적 가능하다. 커밋 이력을 복구하는 방법을 알알보자.

git reflog 명령어를 사용하면, 브랜치가 이동한 모든 기록을 볼 수 있다. 이전 커밋으로 reset하기 전의 상태를 찾고, 해당 커밋 해시를 복사한다. 그리고 git reset --hard <커밋 해시> 명령어를 사용하여 해당 커밋으로 돌아갈 수 있다.

나의 경우에는, 개발 중 문제가 생긴 코드가 꼬여서 하나하나 해결하기 어려워 이전 커밋으로 되돌린 상태에서 기존의 작업물이 아까워서 나중에 언젠가 도움이 될까 싶어 브랜치로 남겨 두려는 욕심이 있었기에, 새로운 브랜치를 따서 해당 커밋을 HEAD로 잡아두어 코드를 보관하고자 하였다. 따라서 해당 작업을 명령어로 남겨둔다.

git reflog // 여기서 원하는 커밋 해시 찾기 ex.abcd12
git checkout -b temp-feat abcde123 // temp-feat라는 로컬 저장소에 abcd12라는 이력 보관
728x90

+ Recent posts