현록

Git reset과 revert 차이

잘못 만든 Git 커밋을 되돌릴 때 resetrevert를 자주 사용한다.
두 명령은 모두 이전 변경을 취소하는 데 사용할 수 있지만 커밋 이력을 다루는 방식은 반대에 가깝다.

reset은 현재 브랜치가 가리키는 커밋을 옮겨 기존 이력을 다시 작성한다.
revert는 취소할 변경의 반대 내용을 새 커밋으로 기록해 기존 이력을 보존한다.
이미 다른 사람과 공유한 커밋인지가 가장 중요한 선택 기준이다.

커밋 그래프의 차이

현재 main 브랜치가 커밋 C를 가리키는 상태를 보자.

A---B---C  main, HEAD

reset 뒤
A---B  main, HEAD
     \
      C  현재 브랜치에서 도달할 수 없음

revert 뒤
A---B---C---R  main, HEAD

reset 뒤에는 mainHEADB를 가리킨다.
C는 현재 브랜치 이력에서 빠지지만 ORIG_HEAD나 reflog 기록을 통해 잠시 다시 찾을 수 있다.

revert는 C를 없애지 않고 반대 변경을 담은 새 커밋 R을 만든다.
로그에는 문제가 생긴 변경과 그 변경을 취소한 기록이 모두 남는다.
그래서 이미 원격에 올라가 다른 사람이 기준으로 삼을 수 있는 커밋에는 revert가 안전한 기본 선택이다.

reset mode의 차이

커밋을 지정하는 형태의 git reset은 현재 브랜치의 끝인 HEAD를 대상 커밋으로 옮긴다.
선택한 mode에 따라 staging area라고 부르는 index와 working tree도 함께 바꾼다.

modeHEADindexworking tree마지막 커밋의 변경
--soft이동유지유지staged 상태로 남음
--mixed이동대상 커밋에 맞춤유지unstaged 상태로 남음
--hard이동대상 커밋에 맞춤대상 커밋에 맞춤파일 변경까지 버림
--soft
HEAD
이동
index
유지
working tree
유지
마지막 커밋의 변경
staged 상태로 남음
--mixed
HEAD
이동
index
대상 커밋에 맞춤
working tree
유지
마지막 커밋의 변경
unstaged 상태로 남음
--hard
HEAD
이동
index
대상 커밋에 맞춤
working tree
대상 커밋에 맞춤
마지막 커밋의 변경
파일 변경까지 버림

mode를 생략하면 기본값은 --mixed다.
같은 HEAD~1을 대상으로 해도 어떤 mode를 선택하는지에 따라 보존되는 작업이 달라진다.

git reset --soft HEAD~1
git reset --mixed HEAD~1
git reset --hard HEAD~1

--soft는 브랜치 포인터만 옮기므로 마지막 커밋의 변경이 staged 상태로 남는다.
커밋 메시지나 커밋 경계를 다시 구성할 때 유용하다.

--mixed는 index도 대상 커밋에 맞추지만 working tree는 유지한다.
마지막 커밋의 변경을 unstaged 상태로 돌려 다시 나누고 싶을 때 적합하다.

--hard는 index와 working tree까지 대상 커밋에 맞춘다.
파일 변경도 버리므로 세 mode 가운데 데이터 손실 위험이 가장 크다.

파일 경로를 지정하는 reset은 동작이 다르다.
git reset -- src/config.js 형태는 브랜치의 HEAD를 옮기지 않고 해당 경로의 index만 갱신한다.
최근 Git에서는 staged 파일을 내리는 의도를 더 분명하게 표현하는 git restore --staged src/config.js도 사용할 수 있다.

reset 전 확인 사항

대상 커밋 이후의 tracked file 변경은 사라진다.
대상 tree를 쓰는 데 방해되는 untracked file이나 directory도 삭제될 수 있다.

커밋한 작업은 reflog에서 다시 찾을 가능성이 있지만 한 번도 커밋하지 않은 working tree 변경은 Git으로 복구하기 어렵다.
--hard를 빠른 정리 명령처럼 습관적으로 사용하면 안 되는 이유다.

git status
git diff
git diff --staged
git log --oneline --decorate -5
git branch backup-before-reset

마지막 명령으로 만든 브랜치는 reset 전 커밋을 계속 가리킨다.
잘못 판단했을 때 돌아갈 명시적인 기준점이 필요하다면 먼저 이런 임시 브랜치를 남긴다.
이미 staged 변경이 있다면 soft reset 뒤에도 섞여 남으므로 git diff --staged 결과를 확인한다.

revert의 동작과 충돌 처리

git revert <commit>은 지정한 커밋이 도입한 patch의 반대 변경을 적용하고 그 결과를 새 커밋으로 기록한다.

git log --oneline
git revert a1b2c3d
git status
git add <resolved-file>
git revert --continue

revert 대상 커밋은 이력에 그대로 남는다.
새 커밋은 해당 변경을 취소했다는 사실을 보여주고 일반 push로 공유할 수 있다.

revert는 저장소 전체를 과거 시점으로 단순 복사하지 않는다.
대상 커밋 이후에 같은 줄이 바뀌었다면 현재 코드에 반대 patch를 적용하는 과정에서 충돌할 수 있다.

공식 문서는 HEAD 대비 수정이 없는 clean working tree에서 시작하는 흐름을 전제로 한다.
관련 없는 변경을 먼저 커밋하거나 별도로 보관한 뒤 실행하는 편이 안전하다.

충돌이 생기면 파일을 수정하고 stage한 뒤 --continue로 진행한다.
작업 전체를 취소하려면 --abort를 사용한다.
충돌을 해결할 때는 단순히 conflict marker를 지우는 데서 끝내지 않고 취소하려는 기능과 이후 변경이 모두 의도대로 동작하는지 확인해야 한다.

merge commit의 revert

merge commit은 부모가 둘 이상이므로 일반 커밋처럼 바로 revert할 기준이 하나로 정해지지 않는다.
이때는 -m으로 어느 부모를 mainline으로 볼지 지정해야 한다.

git revert -m 1 <merge-commit>

숫자 1은 무조건 main 브랜치를 뜻하는 값이 아니라 merge commit의 첫 번째 부모를 뜻한다.
부모 번호를 잘못 선택하면 의도와 다른 쪽의 변경을 취소할 수 있다.

merge revert는 이후 같은 브랜치를 다시 merge할 때 결과에도 영향을 줄 수 있다.
git show <merge-commit>과 커밋 그래프를 확인하고 팀의 통합 흐름을 이해한 뒤 실행해야 한다.
merge commit이 만들어지는 방식은 Git merge와 rebase 차이에서 이어서 볼 수 있다.

공유 브랜치의 선택 기준

reset은 로컬 브랜치 포인터를 옮길 뿐 원격 브랜치를 자동으로 바꾸지 않는다.
이미 push한 커밋을 reset한 뒤 같은 브랜치를 push하면 일반적으로 non-fast-forward로 거절된다.

이를 강제 push로 덮어쓰면 다른 사람이 그 커밋을 기준으로 만든 작업과 충돌할 수 있다.
--force-with-lease가 무조건 안전을 보장하는 것도 아니므로 공유 브랜치의 잘못된 커밋을 고치는 기본 절차로 삼으면 안 된다.

아직 push하지 않은 개인 브랜치의 마지막 커밋을 다시 구성하려면 reset을 사용할 수 있다.
이미 main이나 여러 사람이 사용하는 브랜치에 올라간 변경을 취소하려면 revert로 새 기록을 남기는 편이 안전하다.

공유 여부가 확실하지 않다면 git fetch origin 뒤에 git log --graph --oneline --decorate --all로 원격 상태를 확인한다.
원격 변경을 확인하고 통합하는 흐름은 Git fetch와 pull 차이에서 자세히 정리했다.

상황별 선택

아직 공유하지 않은 마지막 커밋만 취소하고 변경을 staged 상태로 유지하려면 git reset --soft HEAD~1을 사용한다.
변경을 unstaged 상태로 다시 나누고 싶다면 git reset HEAD~1을 사용한다.

로컬 커밋과 파일 변경을 모두 정말로 버려야 한다면 상태와 대상 커밋을 확인한 뒤에만 git reset --hard를 검토한다.
보존해야 할 변경이 조금이라도 있다면 먼저 브랜치나 커밋으로 기준점을 남긴다.

이미 공유한 커밋의 변경을 취소하면서 이력을 유지하려면 git revert <commit>을 사용한다.
커밋하지 않은 파일 하나를 원래 상태로 되돌리는 목적이라면 revert가 아니라 git restore의 대상과 데이터 손실 가능성을 확인한다.

결정하기 전에 커밋이 원격에 공유되었는지와 working tree에 보존해야 할 변경이 있는지 확인한다.
특히 reset --hard와 강제 push는 되돌릴 대상을 정확히 확인한 뒤에만 사용해야 한다.

참고 자료

관련 포스트
HTTP Cache-Control no-cache와 no-store 차이 thumbnail
HTTP Cache-Control no-cache와 no-store 차이
HTTP Cache-Control의 no-cache와 no-store 차이를 저장 여부, 재검증, ETag와 304 응답, private와 max-age 조합 기준으로 정리합니다.
HTML disabled와 readonly 차이 thumbnail
HTML disabled와 readonly 차이
HTML disabled와 readonly의 차이를 사용자 입력, 포커스, 폼 제출, 유효성 검사, 적용 가능한 요소를 기준으로 정리합니다.
Git fetch와 pull 차이 thumbnail
Git fetch와 pull 차이
Git fetch와 pull이 원격 변경을 가져오고 현재 브랜치에 통합하는 방식을 정리합니다. remote-tracking branch, upstream, fast-forward, merge와 rebase 선택 기준을 함께 봅니다.
HTTP Content-Type과 Accept 차이 thumbnail
HTTP Content-Type과 Accept 차이
HTTP Content-Type과 Accept 헤더의 방향, JSON 요청과 응답 예제, 콘텐츠 협상과 406·415 상태 코드의 차이를 정리합니다.
HTTP PUT과 PATCH 차이 thumbnail
HTTP PUT과 PATCH 차이
HTTP PUT과 PATCH의 차이를 리소스 교체와 변경 명령, 멱등성, JSON Patch와 JSON Merge Patch, ETag를 이용한 동시 수정 방지 기준으로 정리합니다.
HTML label과 input 연결 방법 thumbnail
HTML label과 input 연결 방법
HTML label과 input을 for와 id로 연결하는 방법, 암시적 연결, 폼 그룹과 보조 설명, 접근성 오류 패턴을 정리합니다.
HTML section과 article 차이 thumbnail
HTML section과 article 차이
HTML section과 article의 의미, 독립성 판단 기준, 중첩 구조, 제목과 접근성을 고려한 선택 방법을 정리합니다.
HTML div와 span 차이 thumbnail
HTML div와 span 차이
HTML div와 span의 차이를 기본 display, 담을 수 있는 내용, 시맨틱 태그, CSS와 JavaScript에서 그룹화하는 기준으로 정리합니다.
HTTP 상태 코드 정리 thumbnail
HTTP 상태 코드 정리
HTTP 상태 코드의 2xx, 3xx, 4xx, 5xx 의미와 200, 201, 204, 301, 304, 400, 401, 403, 404, 500, 502, 503 차이를 정리합니다.
HTML id와 class 차이 thumbnail
HTML id와 class 차이
HTML id와 class의 차이를 문서 내 유일성, 여러 값 사용, CSS 선택자, JavaScript 탐색, fragment 링크와 이름 작성 기준으로 정리합니다.
HTML button과 a 태그 차이 thumbnail
HTML button과 a 태그 차이
HTML button과 a 태그의 차이를 이동과 동작의 의미, href, type 속성, 폼 제출, 키보드 접근성, 잘못된 사용 패턴으로 정리합니다.
HTTP GET과 POST 차이 thumbnail
HTTP GET과 POST 차이
HTTP GET과 POST의 차이를 초보자 기준으로 정리합니다. 조회와 변경, query string과 request body, 캐시, safe, idempotent, form과 fetch 사용 기준을 함께 봅니다.
npm outdated와 npm update 차이 thumbnail
npm outdated와 npm update 차이
npm outdated와 npm update의 차이를 초보자 기준으로 정리합니다. Current, Wanted, Latest의 의미와 semver 범위 안에서 업데이트되는 방식, package-lock.json 변화까지 함께 봅니다.
npm install -g와 npx 차이 thumbnail
npm install -g와 npx 차이
npm install -g와 npx의 차이를 초보자 기준으로 정리합니다. 전역 설치, 로컬 설치, 일회성 실행, 프로젝트 scripts에 넣는 기준까지 함께 봅니다.
package.json에서 ^와 ~ 차이 thumbnail
package.json에서 ^와 ~ 차이
package.json 의존성 버전 앞에 붙는 ^와 ~의 차이를 초보자 기준으로 정리합니다. semantic versioning, 버전 범위, 0.x 예외, package-lock.json과의 관계까지 함께 봅니다.
Git merge와 rebase 차이 thumbnail
Git merge와 rebase 차이
Git merge와 rebase가 커밋 그래프를 어떻게 바꾸는지 정리합니다. fast-forward, merge commit, rebase의 커밋 ID 변경, 충돌 처리, 공유 브랜치에서의 안전한 사용 기준까지 살펴봅니다.
npx와 npm exec 차이 thumbnail
npx와 npm exec 차이
npx와 npm exec가 어떤 명령어인지 초보자 기준으로 정리합니다. 로컬 패키지 실행, 원격 패키지 임시 실행, --package 옵션, -- 인자 전달 차이까지 함께 봅니다.
npm scripts와 npm run 정리 thumbnail
npm scripts와 npm run 정리
npm scripts란 무엇인지, package.json scripts와 npm run의 관계를 초보자 기준으로 정리합니다. npm run dev, npm start, npm test, node_modules/.bin, -- 인자 전달 방식까지 함께 봅니다.
dependencies와 devDependencies 차이 thumbnail
dependencies와 devDependencies 차이
package.json의 dependencies와 devDependencies 차이를 초보자 기준으로 정리합니다. npm install과 npm install -D, 배포 환경 설치, package-lock.json과의 관계까지 함께 봅니다.
npm ci와 npm install 차이 thumbnail
npm ci와 npm install 차이
npm ci와 npm install의 차이를 초보자 기준으로 정리합니다. clean install의 의미, package-lock.json 조건, CI에서 npm ci를 쓰는 이유, .npmrc 플래그 주의점까지 함께 봅니다.
.npmrc 파일이란? thumbnail
.npmrc 파일이란?
storybook을 사용해보려고 하다 마주한 이슈의 해결법을 알아보다가 등장한 .npmrc라는 파일에 대해 공부해보았다. .npmrc 파일이란? .npmrc 파일은 npm에 대한 config 파일이다. (npm에 대한 rc 파일) 프로젝트별 registry, install 옵션, 인증 토큰처럼 npm CLI가 읽는 설정을 관리할 때 사용한다.
스토리북이란? thumbnail
스토리북이란?
Storybook은 UI 컴포넌트를 독립적으로 개발하고, 문서화하고, 테스트하기 위한 프론트엔드 워크샵입니다. Storybook 10.4 기준 설치 흐름과 stories, 문서화, 테스트 활용 방식을 정리합니다.
TTV와 TTI 차이 thumbnail
TTV와 TTI 차이
TTV와 TTI의 차이를 초보자 기준으로 정리합니다. 사용자가 화면을 보는 시점, 상호작용 가능한 시점, FCP, LCP, INP, Core Web Vitals와의 관계까지 함께 봅니다.
Maria DB 외부 접속 설정하기 thumbnail
Maria DB 외부 접속 설정하기
안녕하세요. 오늘은 Maria DB 초기 세팅 시, 외부에서 접속이 안될 때 매뉴얼을 작성해보겠습니다. dotenv 패키지를 통해서 환경변수로 관리한다면, 별도의 추가 작업을 할 일이 없으실 겁니다.
package-lock.json 파일의 역할 thumbnail
package-lock.json 파일의 역할
안녕하세요. 오늘은 node 환경의 개발자라면 한번쯤 궁금했을만한 package-lock.json의 역할에 대해 알아보겠습니다. 우리는 node 환경에서 개발을 할 때 다양한 패키지들을 설치하여 활용하곤 합니다. 우리가 설치하는 패키지 또한 다른 npm 패키지를 활용하여 만든 패키지들이고 이들 또한 설치를 하게 됩니다. 이렇게 직간접적으로 설치된 패키지들은 대부분 호환성을 "^1.1.5"와 같이 표현하여, 범위로 지정해두고 있습니다.
Linux 환경 배포 자동화 체험해보기 thumbnail
Linux 환경 배포 자동화 체험해보기
안녕하세요. 요즘 포트폴리오를 만들면서 서버 상에 자주 반영할 일이 생겼는데, 매번 명령어들을 타이핑하는 것이 비효율적이라 생각이 들어 배포 자동화를 생각해보게 되었습니다. 현재 레벨에서는 단순히 명령어들만 단축시켜도 효율적이라 생각이 들어 간단한 쉘 스크립트만 작성하였습니다. 정말 간단하니 여러분도 도전해보시길 바랍니다.
협업 필수품. Prettier thumbnail
협업 필수품. Prettier
안녕하세요. 오늘은 Prettier이라는 도구에 대해 알려드리고자 합니다. 개발자는 각자의 코딩스타일이 존재합니다. 그러다보니 같은 프로젝트에서도 작성하는 소스마다 스타일이 제각기 다르기 일쑤입니다. 그럴 때 도입하면 좋은 것이 Prettier입니다. 프로젝트 root 폴더에 .prettierrc 라는 파일을 생성한 뒤, 위 예시와 같이 원하는 옵션을 JSON 형식으로 작성해주면 됩니다.