Git 롤백 실습

Git으로 형상관리를 하다보면 특정 커밋까지 롤백해야 하는 경우가 빈번하다. Git의 Reset과 Revert 명령어에 대한 정확한 이해를 돕기 위해 아래와 같은 순서로 실습을 해본다.

reset vs. revert

reset과 revert는 원하는 커밋까지 롤백시키는 명령어로 다음과 같은 차이점이 있다.

  • reset은 커밋 이력(Histroy)을 남기지 않고 특정 커밋까지 되돌린다.
  • revert는 커밋 이력을 남기고 특정 커밋까지 되돌린다.

reset 명령

롤백 히스토리를 남기지 않고 커밋을 삭제한다. 이 경우는 혼자만 사용하는 리포지토리이거나 또는 팀 작업을 할 경우 다른 팀원이 롤백할 커밋을 pull로 당겨가지 않았다는 것을 확인한 경우에만 사용해야 한다.

  • {헤시값} 바로 전(이전)까지 되돌린다.
1
$ git reset --hard {헤시값}
  • 현재 HEAD에서 3개의 커밋을 롤백한다. (3개의 커밋을 롤백)
1
$ git reset --hard HEAD~3..
  • 위와 동일한 의미이며 N번까지 되돌릴 경우 ^를 N번 반복한다.
1
$ git reset --hard HEAD^^^
  • 원격 저장소에 push는 다음과 같이 -f 또는 -force 옵션을 사용해야 한다.
1
$ git push -f origin master

revert 명령

reset과는 다르게 롤백 명령어 자체를 하나의 커밋으로 취급한다. 따라서 다른 팀원과 롤백할 커밋의 내용을 공유한다.

1
2
3
$ git revert --no-commit HEAD~3.. # Revert Commit ~ 3
$ git commit -m 'Revert "Commit 4,3,2"'
$ git push

실습환경 설정

다음과 같이 실습환경을 만들고 reset과 revert의 차이점을 확인해 볼 수 있다.

  1. 자신의 GitHub 사이트에서 새로운 리포지토리를 생성한다. 리포지토리 명은 “RepoTest”로 정한다. 리포지토리는 Public 또는 Private으로 설정해도 무방하다.

  2. PC 또는 노트북에 원격저장소(remote repo)에 연결할 로컬폴더를 생성한다.

    1
    2
    3
    $ cd GitHub
    $ mkdir RepoTest
    $ cd RepoTest
  3. 깃을 초기화하고 원격 리포지토리를 해당 폴더에 연결한다.

    git remote add origin https://github.com/계정명/리포지토리명.git

    1
    2
    3
    4
    $ git init
    /Users/jaehyunlee/GitHub/RepoTest/.git/ 안의 빈 깃 저장소를 다시 초기화했습니다

    $ git remote add origin https://github.com/IndieGameMaker/RepoTest
  4. 원격 리포지토리의 내용을 pull 명령어로 내려받는다.

    git pull origin master

    1
    2
    3
    4
    5
    6
    7
    8
    9
    $ git pull origin master

    remote: Enumerating objects: 3, done.
    remote: Counting objects: 100% (3/3), done.
    remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
    오브젝트 묶음 푸는 중: 100% (3/3), 582 bytes | 291.00 KiB/s, 완료.
    https://github.com/IndieGameMaker/RepoTest URL에서
    * branch master -> FETCH_HEAD
    * [새로운 브랜치] master -> origin/master

콜라보레이터 지정 및 초기 설정

  1. 테스트를 위해 다른 Git 유저에게 콜라보레이터 권한을 설정한다. GitHub 사이트에 접속해 메뉴 Settings -> Manage Access를 선택한다. (예전의 Collaborator 메뉴는 없어졌다.)

    Invate Collaborator 버튼을 클릭해 협업할 유저의 이메일 또는 깃허브 계정명을 입력해 선택한다.

  2. 콜라보레이터로 지정된 유저는 지정된 이메일로 초대 메일을 수신한 후 확인 View Invitation 링크를 클릭한 후 Accept Invitation을 클릭하면 협업자 등록이 완료된다.

  3. 콜라보레이터의 PC에서 깃허브로 사용할 폴더로 이동한 후 다음과 같이 리포지토리를 초기화 한다.

    1
    2
    3
    4
    5
    6
    7
    $ cd GitHub
    $ mkdir RepoTest
    $ cd RepoTest

    $ git init
    $ git remote add origin https://github.com/IndieGameMaker/RepoTest
    $ git pull origin master

복수의 커밋 처리

  1. 리포지토리 생성자가 복수개의 커밋을 처리한다. 다음과 같이 3개의 파일을 생성하고 각각 별도로 커밋 & Push 한다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    $ echo "# Text 1" >> a.txt
    $ echo "# Text 2" >> b.txt
    $ echo "# Text 3" >> c.txt

    $ git add a.txt
    $ git commit -m "Commit 1 - a.txt"

    $ git add b.txt
    $ git commit -m "Commit 2 - b.txt"

    $ git add c.txt
    $ git commit -m "Commit 3 - c.txt"
  2. git log 명령어를 통해 현재 커밋된 내역을 확인한다.

  3. git push 명령어를 통해 원격 리포지토리(Remote Repository)로 발행한다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    $ git push -u orgin master

    오브젝트 나열하는 중: 10, 완료.
    오브젝트 개수 세는 중: 100% (10/10), 완료.
    Delta compression using up to 8 threads
    오브젝트 압축하는 중: 100% (6/6), 완료.
    오브젝트 쓰는 중: 100% (9/9), 721 bytes | 721.00 KiB/s, 완료.
    Total 9 (delta 2), reused 0 (delta 0)
    remote: Resolving deltas: 100% (2/2), done.
    To https://github.com/IndieGameMaker/RepoTest
    b0d9750..2fe74aa master -> master
    'master' 브랜치가 리모트의 'master' 브랜치를 ('origin'에서) 따라가도록 설정되었습니다.

    push한 후 git log로 확인하면 다음과 같이 HEADorigin/master가 동일한 위치에 있는 것을 확인할 수 있다.

git reset 명령어

reset 명령어는 로컬 또는 원격 리포지토리의 커밋을 되돌리는 것으로 커밋의 히스토리까지 삭제한다. 앞서 3개의 커밋을 롤백하기 위해서 다음과 같은 명령어를 사용할 수 있다.

1
2
3
4
$ git reset --hard HEAD~3..
HEAD의 현재 위치는 b0d9750입니다 Initial commit

$ git push -f origin master

git push의 -f 또는 --force 옵셩을 사용하지 않고 git push origin master로 사용할 경우 다음과 같은 오류가 발생한다.

1
2
3
4
5
6
7
8
9
10
$ git push origin master

To https://github.com/IndieGameMaker/RepoTest
! [rejected] master -> master (non-fast-forward)
error: 레퍼런스를 'https://github.com/IndieGameMaker/RepoTest'에 푸시하는데 실패했습니다
힌트: 현재 브랜치의 끝이 리모트 브랜치보다 뒤에 있으므로 업데이트가
힌트: 거부되었습니다. 푸시하기 전에 ('git pull ...' 등 명령으로) 리모트
힌트: 변경 사항을 포함하십시오.
힌트: 자세한 정보는 'git push --help'의 "Note about fast-forwards' 부분을
힌트: 참고하십시오.

이 오류 메시지는 git reset 명령어로 로컬 리포지토리의 3개의 커밋이 삭제되어 HEAD는 첫번째 커밋을 가리키는 반면에 원격 리포지토리는 3개가 그대로 남아있기 때문에 push 할 수 없다는 메시지다. 따라서 강제로 원격 리포지토리에 저장하는 옵션인 -f 또는 --force 옵션을 추가해야 한다.

1
$ git push -f origin master

로그를 확인하면 다음과 같이 제일 처음 커밋으로 되돌렸으며 로컬 폴더에 추가했던 3개의 txt파일이 삭제된것을 확인할 수 있다.

git reset 명령의 문제점

git reset은 커밋 히스토리까지 깔끔하게 지우고 롤백시키는 기능이기에 편리하게 사용할 수 있지만 1인 개발이 아니라 여러명이 협업을 할 경우 사용하기 전에 다음과 같은 사항을 고려해야 한다.

  • reset할 커밋의 내용을 다른 협업자가 pull로 내려받지 않았을 때만 사용해야 한다. 만약 다른 협업자가 내려받았을 경우 본인이 reset 후 push를 했다고 하더라도 다른 협업자가 무언가를 수정한 후 push하면 reset 되었던 커밋이 다시 생성된다.
  1. 테스트하기 위해 다음과 같이 3개의 파일을 다시 생성한 후 commit, push 한다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    $ echo "# Text 1" >> a.txt
    $ echo "# Text 2" >> b.txt
    $ echo "# Text 3" >> c.txt

    $ git add a.txt
    $ git commit -m "Commit 1 - a.txt"

    $ git add b.txt
    $ git commit -m "Commit 2 - b.txt"

    $ git add c.txt
    $ git commit -m "Commit 3 - c.txt"

    $ git push -u origin master
  2. 협업자도 변경된 내용을 내려받는다.

    1
    $ git pull origin master
  3. 리포지토리 권한자가 3개의 커밋을 reset 한다.

    1
    2
    $ git reset --hard HEAD~3..
    $ git push --force origin master

    git log를 통해 삭제된 커밋의 목록을 확인하면 다음과 같다.

    1
    2
    3
    4
    5
    6
    $ git log
    commit b0d975021e0bf0ed06ca97f300b4807a1b3c4184 (HEAD -> master, origin/master)
    Author: Lee JaeHyun <myleje@gmail.com>
    Date: Mon Mar 23 11:31:44 2020 +0900

    Initial commit
  4. 협업자 PC에서 새로운 파일을 생성하고 commit & push 명령어를 실행해본다.

    1
    2
    3
    4
    $ echo "# Text 4" >> d.txt
    $ git add d.txt
    $ git commit -m "Commit 4 - d.txt"
    $ git push -u origin master
  5. 이제 리포지토리 권한자의 터미널에서 pull 명령어로 원격 리포지토리의 변경사항을 내려받는다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    $ git pull origin master

    remote: Enumerating objects: 13, done.
    remote: Counting objects: 100% (13/13), done.
    remote: Compressing objects: 100% (5/5), done.
    remote: Total 12 (delta 3), reused 12 (delta 3), pack-reused 0
    오브젝트 묶음 푸는 중: 100% (12/12), 918 bytes | 153.00 KiB/s, 완료.
    https://github.com/IndieGameMaker/RepoTest URL에서
    * branch master -> FETCH_HEAD
    b0d9750..cdc5f95 master -> origin/master
    업데이트 중 b0d9750..cdc5f95
    Fast-forward
    a.txt | 1 +
    b.txt | 1 +
    c.txt | 1 +
    d.txt | 1 +
    4 files changed, 4 insertions(+)
    create mode 100644 a.txt
    create mode 100644 b.txt
    create mode 100644 c.txt
    create mode 100644 d.txt

    앞서 reset했던 a.txt, b.txt, c.txt를 비롯해 협업자가 push한 d.txt도 올라온것을 알수있다. git log를 통해 커밋 목록을 확인해 보자.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    $ git log

    commit cdc5f953aa569adaf97a9c6b324db1b273064a25 (HEAD -> master, origin/master)
    Author: JasonLee <myleje@gmail.com>
    Date: Mon Mar 23 15:25:48 2020 +0900

    Commit 4 - d.txt

    commit 29e23192a2ad47862872eed993f30dd5e1412f5a
    Author: Lee JaeHyun <myleje@gmail.com>
    Date: Mon Mar 23 15:08:27 2020 +0900

    Commit 3 - c.txt

    commit d0ff0dad2654e06c803d3f3e59be1eeecb8594ff
    Author: Lee JaeHyun <myleje@gmail.com>
    Date: Mon Mar 23 15:08:20 2020 +0900

    Commit 2 - b.txt

    commit 9cbd9374a3987bef271ddae68487f8cf810d048b
    Author: Lee JaeHyun <myleje@gmail.com>
    Date: Mon Mar 23 15:08:13 2020 +0900

    Commit 1 - a.txt

    commit b0d975021e0bf0ed06ca97f300b4807a1b3c4184
    Author: Lee JaeHyun <myleje@gmail.com>
    Date: Mon Mar 23 11:31:44 2020 +0900

    Initial commit

git revert 명령어

앞서 git reset을 통해 커밋을 롤백했을 때 이미 협업자가 pull했을 때 롤백했던것이 다시 살아나는 문제를 해결하기 위해서 git revert 명령어를 사용한다. git revert 명령은 롤백 시키는 행위자체를 커밋으로 생성해 이미 pull로 내려받은 협업자에게도 롤백 정보를 전달해 동기화 시키는 방식이다.

  1. 리포지토리 권한자 터미널에서 다음과 같이 revert 명령을 사용한다.

    1
    2
    3
    $ git revert --no-commit HEAD~3..
    $ git commit -m "Revert Commit 4, 3, 2"
    $ git push origin master
  2. git log로 히스토리를 확인하면 Revert 명령어가 커밋된것을 알 수 있다.

  3. 협업자의 터미널에서 새로운 파일을 생성하고 push 하면 e.txt 파일은 push한다.

    1
    2
    3
    4
    $ echo "# Text E" >> e.txt
    $ git add e.txt
    $ git commit -m "Commit 5 - e.txt"
    $ git push origin master

    명령어를 실행하면 원격 리포지토리와 맞지 않기 때문에 먼서 git pull 명령어를 먼저 사용하라는 메시지를 출력한다. 다음과 같이 pull명령어를 실행한다.

    1
    $ git pull origin master
  4. 마지막으로 권한자의 터미널에서 git pull 해서 최종 변경된 사항을 확인해본다.

    1
    2
    $ git pull origin master
    $ git log

참조 사이트

  1. https://medium.com/nonamedeveloper/초보용-git-되돌리기-reset-revert-d572b4cb0bd5

댓글