
Git 설치 및 초기 환경 세팅
1. Git 설치
2. 설치 후 Git bash 열기
3. git bash 에서 환경설정 하기(프로젝트마다 다르게 할 거면 `--global` 빼면 된다.)
① 유저 이름 설정(******) - 현재 사용자의 아이디를 "your_name"으로 설정(커밋할 때 필요한 정보)
git config --global user.name "your_name"
② 유저 이메일 설정(github 가입 시 사용한 이메일 써야 함. ********@gmail.com)
git config --global user.email "your_email"
③ 정보 확인하기(뭐 어떻게 설정해놨는지 다 뜬다)
git config --list
핵심 검색 키워드: "특정"
기본 flow
working directory ⏩ staging area ⏩ local repository ⏩ remote repository
- working directory는 현재 내가 작업중인 프로젝트 디렉토리를 의미(`working tree`, `work tree`라고도 불린다.)
- staging area는 파일 변경 사항을 적재해두는 공간(`index`라고 부를 때도 있다.)
- local repository는 적재해둔 변경사항의 내용을 commit하여 로컬에서 관리 및 저장하는 공간(`.git` 디렉토리를 의미함.)
- remote repository는 local repo와 연결된 원격 저장소
bash 기본 명령어
리눅스 명령어와 크게 다르지 않다.
| command | meaning | option | annotation | example |
| touch <파일명.확장자명> | 비어있는 파일 만들기 | a.txt 라는 이름을 가지고 있는 비어있는 파일 만들기 : touch a.txt | ||
| mkdir | 비어있는 폴더 생성 | `mkdir dir` : dir 이라는 폴더를 생성 | ||
| git --version | git 버전 확인 명령어 | |||
| pwd | 현재 경로 확인하기 | print working directory의 약자 | ||
| dir | 하위 폴더명 확인하기 | directory | ||
| clear | git bash창 로그 삭제 | - git bash 창 쌓인 로그들 전부 지워준다. - cls 는 terminal 명령어 // 쌓인 터미널 메시지들 다 지워준다. |
||
| ls | git 명령어로 현재 디렉토리에 있는 파일을 출력해달라는 명령어 | `-a` : 숨김 파일/폴더까지 전부 다 출력 `-al` : 숨김 파일/폴더까지 전부 다 출력하되 만든 계정, 용량, 만든 시간 등 기타 정보도 출력된다. `-p`: 폴더는 폴더대로 강조 표시해준다.(default) |
해당 디렉토리 내부의 폴더 및 파일들을 볼 수 있다. | |
| cd | 디렉토리 이동 명령어 | - `..` : 이전 디렉토리로 이동 - `cd <폴더명>` : 특정 폴더 내부로 이동 |
`cd ..` : 부모 폴더로 이동 `cd dir` : dir 이라는 폴더로 이동 상대 경로를 연계해서 적어도 된다. `cd ../dir` 현재 경로에서 부모 폴더로 이동 후 dir 폴더로 이동 |
초기 git 환경설정 (git init, rm)
`rm -r` 과 `rm -rf`는 무엇인가를 삭제하는 것이므로 신중하게 사용하는 것이 좋다.
어떤 저장소의 파일을 삭제하는지 옵션설명을 잘 보고 입력해야 한다.
| command | meaning | option | annotation | example |
| git init | 해당 폴더를 git이 버전 관리할 로컬 폴더로 선언한다. | |||
| rm -r .git | 현재 디렉토리 내부의 `.git` 폴더가 삭제되며 git 로컬 저장소 지정을 해제함. | - `rm` === remove - `-r` 옵션(recursive): 원래는 파일만 지목할 수 있지만, 폴더를 지목할 수 있다.하위 디렉토리를 포함하여 모든 내용을 삭제 - `-f` 옵션 (force): 권한이 요구되는 경우 강제로 파일이나 디렉토리를 삭제. |
- git init 취소해준다. - 🚨주의 : 원격 레포까지 날려버린다. 원격 레포가 연결되어 있다면, 기존 원격 레포지토리 연결을 먼저 끊은 후에 하는 것이 좋다. |
|
| rm -rf 폴더명 | git이 관리하고 있는 해당 폴더 내부의 .git 폴더 삭제와 더불어 로컬 폴더 내부의 모든 파일을 삭제한다. 그냥 커밋기록부터 시작해서 파일까지 전부 날려버릴 수 있는 기능이라고 보면 된다. |
위 옵션을 그대로 가져와서 .git이 아닌 다른 폴더에 반영한 것이다. | - 🚨주의 : 원격 레포까지 날려버린다. 원격 레포가 연결되어 있다면, 기존 원격 레포지토리 연결을 먼저 끊은 후에 하는 것이 좋다. | `rm -rf 어쩌구` 어쩌구 폴더 내부를 싹 날려버린다. |
추가할 파일 체크(추가할 목록 Staging Area 에 올리기, git add)
| command | meaning | option | annotation | example |
| git add <특정 파일명> | 특정 파일 스테이징 | 1. `.` : `git add .` // 변경된 모든 파일을 Staging Area에 올린다. |
working directory 전역에 걸쳐서 공유되는 stage area
특정 브랜치에서 add로 stage area에 올리는 것만으로는 해당 브랜치 작업물에 반영되지 않는다.
가령, "main" 브랜치에서 add로 stage area에 올리고 "sub" 브랜치로 이동 후 commit 하였다면, main 브랜치로 다시 이동했을 때 (열려있는) 작업물이 삭제되었다고 표시(vs code editor라고 가정)되고 sub 브랜치에만 작업내용이 반영된 모습을 확인할 수 있다.
Staging Area 에서 드랍하기(git restore)
| command | meaning | option | annotation | example |
| git restore --staged <file> | 특정 파일 스테이지에서 내림. | `git restore --staged .` 👉전부 스테이징에서 내림 | tracking 중인 파일과 디렉토리에 대해서만 실행 가능하다. | `git restore --staged index.html` : "index.html" 파일만 스테이지에서 내림 |
아직 untracking중인(한 번도 `git add`하지 않은) 파일을 staging area에 올린 것이라면 `git reset`으로 내릴 수 있다.
원격[로컬] 저장소 파일 삭제 명령(git rm)⭐
| command | meaning | annotation |
| git rm <file name> | rm은 remove를 뜻함. 원격 저장소와 로컬 저장소에 있는 파일을 추적하지 않고 삭제도 한다. |
|
| git rm --cached <file name> | 로컬 저장소의 인덱스(Staging Area)에서 파일을 제거한다. 이 명령을 실행하면, 해당 파일은 Git의 추적 대상에서 제외되지만 워킹 디렉토리에는 그대로 남아 있게 된다. 즉, 파일은 로컬에 보존되면서 Git의 추적에서만 제외된다. |
github에는 올라가면 안되지만 내 폴더에서는 지워지면 안되는 private한 파일을 다룰 때 유용하게 사용한다. |
예시 - 특히 .gitignore 파일 설정 전에 올라가버린 파일을 drop할 때 많이 사용 ⭐
`.gitignore`파일은 오직 untracked 파일(한 번도 git add로 추적되지 않은 파일)에만 규칙이 적용된다.
그렇기 때문에 이미 레포지토리로 커밋된 디렉토리의 파일들은 unstage하고, 커밋을 만들고, 이를 github에 push해야 한다. 아래 예시와 같은 절차를 따라야 한다.
# 원격저장소 .idea/modules.xml 파일 삭제
$ git rm --cached .idea/modules.xml
$ git commit -m 'Remove the now ignored directory "some-directory"'
$ git push origin main
# 원격저장소 .idea 폴더 하위의 모든 파일 삭제
$ git rm --cached -r .idea/
$ git commit -m 'Remove the now ignored directory "some-directory"'
$ git push origin main
# 원격저장소 모든 파일 삭제
$ git rm --cached -r .
$ git commit -m 'Remove the now ignored directory "some-directory"'
$ git push origin main
`git rm`과 리눅스 `rm`명령어의 효과는 동일하다.
참고로 `git rm -r --cached 어쩌구`를 했을 때 아래와 같은 문구들이 뜬다. 이는 레포지토리로부터 오는 피드백이라고 생각하면 된다. '아직 working directory에 해당 파일과 디렉토리가 남아있다.'는 뜻이다.
rm 'some-directory/product/cache/1/small_image/130x130/small_image.jpg'
rm 'some-directory/product/cache/1/small_image/135x/small_image.jpg'
rm 'some-directory/.htaccess'
rm 'some-directory/logo.jpg'
이외에 변경내용을 취소할 때는 웬만하면 `git revert`를 사용해야 한다.
상태확인(git status, diff)
| command | meaning | option | annotation |
| git status | stage 상태, commit log 상태 등을 알려줌 | ||
| git diff | 지금까지 작업 디렉토리에서의 변경사항과 가장 최신커밋의 차이를 보여주는 명령어(- 와 + 로 변경사항 나타내줌) | - `--staged` : 이미 스테이지에 올라가버린 변경사항과 최신(last) 커밋을 비교하는 명령어 - `git diff [커밋해시1] [커밋해시2]` : 차이점 확인(두 커밋의 차이점을 보여주는)하는 명령어 |
- 주의사항 : 커밋해시의 순서가 중요하다. - `커밋해시2`를 기준으로 커밋해시2가 `커밋해시1`에 비해서 뭐가 달라졌는지 나옴. |
`git diff` 는 있다가 밑에서 따로 자세하게 다룸.
커밋 히스토리 만들기(git commit) ⭐
git commit 은 메모장 같은 것 `""` 큰따옴표로 감싸줘야 함!!!
커밋 메시지 제목 쓰기: git commit -m "first commit"
| command | meaning | option | annotation |
| git commit | 커밋 메시지 작성할 수 있는 VI 편집기가 나타남 | 1. `-m` : `git commit -m "<커밋 제목 메시지 작성>"` VI 편집기 대신 그냥 커밋 제목만 작성 git commit -m "FEAT: first commit" 2. `--amend` : `git commit --amend` 최근 커밋 메시지 수정(VI 편집기 열린다) |
'amend'는 '수정하다' 라는 뜻을 가지고 있음. |
| git commit -m "<커밋 메시지 작성>" | |||
| git commit -am "<커밋 메시지>" | 이전에 한 번이라도 `git add`를 통해 tracked 상태에 있는 파일/디렉토리를 Staging Area 에 추가와 동시에 커밋을 한다. | `-a`는`--all`의 단축표현이다. 이전에 한 번이라도 commit을 했거나 git이 변경사항을 추적하고 있는 파일에 대해서만 수행한다.(범위가 제한된 기능이라는 뜻) |
|
| git commit --amend | 마지막 커밋 메시지를 수정할 수 있도록 VI 편집기가 열린다. amend는 영어로 '수정하다' 라는 뜻을 가지고 있다. |
1. `-m` : `git commit --amend -m "<변경할 메시지>"` VI 편집기 대신 그냥 커밋 제목 변경만 한다. |
- 한계: 이미 원격 저장소에 올라간 커밋은 수정하지 못함. - 의의: 아직 push되지 않은 이전 커밋 메시지 수정 가능 |
마지막 이전 commit 메시지 수정하기 ⭐
마지막 이전에 작성한 commit은 어떻게 접근하고 수정할까?
마지막 이전에 작성했던 commit 메시지를 수정하려면 `rebase`명령어를 사용해야 한다.
해당하는 commit으로 이동하여 commit 메시지를 수정하고 해당 branch에 다시 merge 하는 방식으로 진행하게 된다.
rebase이기 때문에 merge commit도 생성되지 않는다.
git rebase -i HEAD~3
`rebase` 명령어와 `-i`를 함께 사용하면 rebase를 대화형으로 수행할 수 있다.
이 방식으로 commit들의 순서를 바꾸거나 commit history를 변경 또는 삭제할 수 있다.
`HEAD~3`은 최근 commit log 중 3개를 불러온다는 뜻이다.
예시로 보자.


위 커밋에서 `git rebase -i HEAD~3`를 입력하면 위와 같이 가져온다.
`i` 명령어로 VI 편집기를 insert모드로 변경하여 이 중 수정하고 싶은 commit의 pick을 reword라는 단어로 수정하고 `esc` 눌러서 Command 모드로 전환후에 `:wq`를 입력하면 된다.

그러면 아래처럼 `reword`를 부여한 커밋이 열리는데, 이 때 수정하고 저장하면 된다. 그럼 끝이다.


이미 push한 commit 메시지 수정하기
지양해야 하는 방법이다. 웬만하면 자잘한 수정 그냥 커밋 메시지를 하나 더 만들자...!
git push --force <branch_name>
원격의 내용을 덮어쓰기 때문에 다른 팀원들이 이미 local에서 가지고 있는 commit log 메시지는 수동으로 수정해야하는 경우가 발생할 수 있다.
VI 편집기 사용법
| command | meaning | annotation |
| `a` 또는 `i` | 커밋 내용 작성할 수 있게 해준다. | 커밋 메시지의 맨 첫 번째 줄은 제목, 제목이랑 한 줄 띄우고 그 다음 줄부터 내용이다. |
| o | 커밋 메시지 작성 시작할 때 한 줄 띄우고 쓸 수 있게 해준다. | |
| esc | 작성 취소 상태 및 명령 모드로 전환 | VI 편집기 나가지지는 않는다. INSERT모드가 꺼진다. |
| `:w` + `ENTER` 키 | 작성한 것이 저장된다.(명령 모드에서만 가능하다) | `:` 눌러야 하는 명령어 잘 볼 것. `:w` 먼저 입력 후 Enter 키 누르면 됨. |
| `:q` + `ENTER` 키 | VI 편집기가 닫힌다. | |
| `:wq` + `ENTER` 키 | 위 두 명령어를 한꺼번에 실행한다.(저장하고 닫겠다.) | |
| ESC | Command 모드로 전환 |
commit 로그 확인하기(git log)
| command | meaning | option | annotation | example |
| git log | 지금까지 만든 커밋 버전들의 목록을 확인 | 1. `--oneline` : 한 줄 간편 확인(커밋 해쉬도 짧게 나옴. = 단축 해쉬) 2. `-p` / `--patch` : 지금까지 만든 버전목록의 파일내용 변동사항을 포함하여 확인 3. `--graph` : 소스트리처럼 브랜치가 뻗어나가는 것을 시각화해서 보고 싶다면 사용. 4, `--branches`: 앞질러있는 모든 다른 브랜치의 커밋까지 확인할 수 있다. |
옵션 조합이 가능하다. 브랜치를 만들고 브랜치에서 작업했을 경우 앞질러 있는 브랜치의 커밋을 못 보는 경우가 있는데 이 때 `--branches`를 유용하게 사용할 수 있다. |
`git log -p --oneline --graph` : 그래프 형태로 간편확인하되 변동사항을 포함하여 보여줘. |
| git log <확인할 해쉬 번호> -n <확인할 해쉬 번호 포함 이전 커밋 개수> | 확인할 해쉬 번호 커밋내용을 포함한 이전의 커밋들을 보여준다. | `git log 65c27ac -n 5`: 커밋 해쉬가 65c27ac인 커밋 + 이전 4개의 커밋을 보여준다. |
`git log --pretty=oneline --al --graph`와 `git log --oneline --branches --graph`는 같은 역할을 수행하는 명령어다.
- HEAD와 브랜치 차이점 이해하기 ⭐
어떤 커밋을 가리키는 존재를 포인터라고 한다.
포인터는 HEAD와 브랜치(branch)로 나눠볼 수 있다.
HEAD는 브랜치를 통해 커밋을 간접적으로 가리키는 포인터다.(커밋을 직접 바라보게 할 수도 있긴 하다.)
브랜치(branch)는 커밋을 직접적으로 가리키는 포인터다.
- `(HEAD -> master)` : 현재 HEAD(로컬작업위치)포인터는 로컬 master 브랜치를 가리키고 있다는 뜻
- `(HEAD -> main)` : 현재 HEAD(로컬 작업 위치)포인터는 로컬 main 브랜치를 가리키고 있다는 뜻
- `(origin/main)` : 내가 붙인 origin 이라는 별명을 가진 원격 저장소의 main 브랜치를 뜻함.
- `(HEAD -> main, origin/main)` : 현재 HEAD(로컬작업위치)포인터는 로컬 main 브랜치와 origin이라는 별명을 가진 원격 저장소의 main 브랜치를 가리키고 있다는 뜻.
참고로 `HEAD`는 git 1.8.5 버전 이후로 `@`로도 표현가능하다.
HEAD가 브랜치를 가리키지 않고 직접 커밋을 가리키게 하려면? 🤔❓
`checkout`을 통해 특정 커밋을 직접 가리키면 된다.
git checkout 9033

이렇게 브랜치를 통해서 커밋을 가리키는 게 아니라 본인이 직접 커밋을 가리키고 있는 상태의 HEAD를 특별히 Detached HEAD라고 부른다.
보통 과거의 특정 커밋 시점에서 새로운 브랜치를 만들고 싶을 때 이 방식을 사용한다.
git branch premium

이 때 HEAD가 새로 같은 커밋을 바라보고 있는 premium으로 `checkout`을 해주면, Detached HEAD에서 아래와 같이 다시 브랜치를 가리키는 HEAD 즉, 정상적인 HEAD 상태로 돌아온다.

- commit에 태그 달기(git tag)
git tag [태그명] [commit hash 복사붙여넣기]
ex) git log 로 commit id 먼저 확인하고 `git tag v0.0.1 f88e6cb16690e26988830013977b2e1ba07b1d46`
- commit 태그 떼기
git tag --delete [특정 태그명]
또는
git tag -d [특정 태그명]
ex) `git tag --delete v0.0.1`
당연하게도 커밋을 삭제하면 태그도 자동으로 떼어진다.
- 태그 목록 조회하기
git tag
특정 commit 정보 자세히 보기(git show)
`git log`를 통해 커밋 해쉬를 획득했다면, `git show`를 통해 커밋 정보를 자세히 볼 수 있다.
| command | meaning | annotation | example |
| git show | 현재 브랜치의 가장 최근 커밋 정보를 확인 | ||
| git show 커밋해시값 | 특정 커밋 정보를 확인 | ||
| git show HEAD | HEAD 포인터가 가리키는 커밋정보를 확인 | ||
| git show 커밋해시값^ 또는 HEAD^ | - 마지막에 `^` 표시가 있다. - `^`표시의 개수로 얼만큼 이전으로 돌아갈 것인지 나타낸다. |
- `^`표시 한 개면, 한 개 전. - `^`표시 두 개면, 두 개 전 |
- `git show asdfcc^`: asdfcc에서 한 커밋 전 - `git show HEAD^^`: 현재 HEAD에서 두 커밋 전 |
| git show 커밋해시값~숫자 또는 HEAD~숫자 | - `~숫자`는 명시적으로 몇 개 전인지 표시한다. - 숫자 없이 `~`만 사용하면 한 칸 전 커밋으로 표시한다. |
- 숫자로 명시할 수 있으므로 간단하기 때문에 `^`표시보다 더 자주 사용한다.(솔직히 ^는 그냥 안 쓴다.) - `~`와 `^`를 혼용할 수 있다. 근데 그렇게 하면 헷갈리므로 잘 안 한다. |
`git show HEAD~3`: 현재 HEAD 포인터에서 세 칸 전의 커밋을 보여줘라 |
git show HEAD~3
git show asdf33^^
git show asdf33~3
특정 커밋만 가져와서 현재 브랜치에 추가하기(git cherry-pick) ⭐
영어로 cherry picker는 본인에게 유리한 것만 선택하는 사람을 의미한다.
그래서 `git cherry-pick`을 사용하면, 자신이 원하는 작업이 들어있는 커밋들만 가져와서 현재 브랜치에 추가할 수 있다.
git cherry-pick <커밋 해쉬 번호>
만약 연속적인 커밋이어서 git cherry-pick <시작 커밋 해쉬>...<종료 커밋 해시>를 한다면 내가 적용하고자 하는 가장 마지막 해시의 다음 값을 지정해야 한다.
(e.g. git cherry-pick a1...a3 을 하면 a1부터 a2까지만 가져오게 됩니다.)
머지 커밋 체리픽 (merge commit cherry pick)
만약 merge commit에 대해 cherry-pick을 원한다면 아래와 같이 진행하면 됩니다.
git cherry-pick -m 1 <머지 커밋 해시>
체리픽도 커밋을 가져오는 것이기에 마찬가지로 충돌이 발생할 수 있는데 충돌을 해결한 코드를 스태이징(`add`) 처리 후에 `--continue 옵션`(`git cherry-pick --continue`)을 붙여서 계속 진행을 해서 체리픽을 완료하면 된다.
만약 체리픽을 중단하고 싶다면 `git cherry-pick --abort`로 체리픽을 중단하고 이전 상태로 돌아갈 수 있다.
원격 저장소와 상호작용하기
원격 저장소, 즉, 원격 레포지토리와 상호작용하는 여러 방법들을 알아본다.
특정 브랜치만 골라서 클론해오기(git clone) ⭐
| command | meaning | option | annotation |
| git clone | 저장소 복제 | `--branch` : | 1. 숨김 폴더까지 전부 복제된다. 2. 원격 저장소에 쌓여있는 커밋 목록도 가져와진다. |
| git clone --branch <remote branchname> <remote-repo-url> | 모든 브랜치들을 복제 후 특정 브랜치로 체크아웃 한다. 그 특정 브랜치가 로컬 브랜치명으로 설정된다. | `-b` : `--branch`대신 쓸 수 있는 단축어이다. --branch 자리에 대신 사용하자. | 특정 브랜치만 클론하는 것이 아니기 때문에 활용성이 떨어진다. |
| git clone --branch <remote branchname> --single-branch <remote-repo-url> | 특정 브랜치만 복제하고 추적한다. 그 특정 브랜치가 로컬 브랜치명으로 설정된다. `git clone --branch <remote branchname> --single-branch <remote-repo-url> <local branch name>` 으로 하면 특정 로컬 브랜치로 클론한다. |
`-b` : `--branch` 위 설명이랑 동일. | `--single-branch`라는 옵션이 붙어있는 것을 알 수 있다. Git 버전 1.7.10 에서 소개되었다. 이 옵션을 붙이게 되면 정확하게 특정한 브랜치의 파일들만 페치하고 다른 브랜치에서는 페치를 하지 않는다. |
git clone --branch <branchname> <remote-repo-url>
` git clone -b passwordless-auth git@github.com:BolajiAyodeji/nextjs-blog.git`

아래처럼 클론을 받아올 원격 저장소의 모든 브랜치를 추적하게 된다. 그래서 쓸모없다고 한 것임.(이럴 바에는 그냥 클론을...)

git clone --branch <branchname> --single-branch <remote-repo-url>
`git clone -b passwordless-auth --single-branch git@github.com:BolajiAyodeji/nextjs-blog.git`

아래처럼 클론을 받아올 원격 저장소의 특정 브랜치만 추적하게 된다. 🚀쓸모있다.

특정 브랜치만 골라서 Pull 해오기 (pull) ⭐
- 패치(fetch): 원격 저장소를 일단 가져만 오기.
- 풀(pull): 원격 저장소를 가져와서 합치기.
| command | meaning | example |
| git pull <remote repo alias> <remote branch name> | 원격 저장소에서 pull을 받아온다. | `git pull origin main`: 원격 저장소의 main 브랜치에서 pull을 받는다. |
| git pull <remote repo alias> <remote branch>:<local branch> | 원격 저장소에서 지정한 remote 브랜치를 지정한 local 브랜치로 pull 받아온다. 주의할 점이 있다. 현재 체크아웃 되어있는 브랜치로도 병합되고 지정한 local branch로도 병합된다. 만약 현재 체크아웃되어 있는 브랜치가 아니라 특정 브랜치로만 pull을 받기를 원한다면 `fetch`를 쓰면 된다. 예를 들어 현재 작업중인 로컬 브랜치가 part1일 때 part2로 pull을 받고 싶다면 `git fetch origin main:part2`를 사용하면 된다. |
`git pull origin main:part2`: 원격저장소의 main 브랜치를 현재 체크아웃 되어 있는 브랜치로 병합하고, 또한 part2라는 이름의 로컬 브랜치로도 pull 받는다. |
(로컬 브랜치의 이름은 foo인 상황) 원격 저장소에 없는 브랜치에서 pull을 받아오려고 하면 다음과 같은 에러가 뜬다.

즉 `git pull origin`다음에는 원격 저장소 브랜치의 이름을 적어줘야 함을 확인할 수 있다.
원격 저장소에 있는 브랜치로 `git pull origin main`을 정확하게 입력해주면 pull이 정상적으로 작동한다.

원격 저장소 연결하기(git remote add)
| command | meaning | option | annotation | example |
| git remote add <원격 저장소 별명> <원격 저장소 주소> | 원격 저장소에 별명을 붙이고 로컬 레포지토리와 연결시킴. | 참고로 `add`라는 뜻이서 보면 알 수 있듯이 여러 원격 레포를 추가 연결할 수 있음. | `git remote add origin https://github.com/Vxxxxx/practice.git` 연결할 원격레포에 origin이라는 별명을 붙이고 연결함. |
연결된 원격 저장소 확인하기(git remote show)
| command | meaning | option | annotation | example |
| git remote | 연결된 저장소의 별명만 확인 | |||
| git remote show | 위랑 동일한 역할 수행 | |||
| git remote show <원격 저장소 별명> | 해당 별명을 가진 원격 저장소의 세부 정보를 보여준다. | 해당 원격 저장소에서 추적하고 있는 브랜치까지 확인 가능하다. 로컬 저장소와 원격 저장소를 비교해서 최신 상태인지도 확인 가능하다.(checking up to date) |

기존 원격 리포지토리 연결 끊기(git remote remove)
| command | meaning | option | annotation | example |
| git remote remove <원격 저장소 별명> | 해당 별명을 가진 원격 레포지토리 연결을 끊는다. | `git remote remove origin` : origin 이라는 별명을 가진 원격 저장소와의 연결을 끊는다. |
기존 원격 레포지토리 이름을 바꿨을 경우 바뀐 레포 url 적용하기
| command | meaning | option | annotation | example |
| git remote set-url <원격 레포 별명> <바뀐 레포 url> | github에서 연결된 원격 레포 이름을 변경했을 때 유용하게 사용 가능하다. | 권장하지는 않지만, 기존과 아예 파일 구조가 다른 원격 레포로 갈아탈 수도 있다. | `git remote set-url origin "hi.git.com"` : 기존 origin 별명 가진 원격 레포 주소가 "hi.git.com"으로 변경된다. |
Master 브랜치명을 main 브랜치로 바꾸기
master 브랜치는 예전에 기본적으로 생성되는 원격 저장소의 초기의 메인 브랜치였는데 노예제도를 연상시키는 단어라고 지적받아서 근래에 들어서는 초기 브랜치명이 `main`브랜치로 정착되었다.
그러나 아직 로컬 레포에서 기본 베이스 브랜치는 master 브랜치로 생성이 된다.
`git branch -M main` 명령어를 통해 master브랜치를 main브랜치로 변경할 수 있다.
원격 저장소로 파일 보내기(push)
| command | meaning | option | annotation | example |
| git push <remote url alias> <remote url branch> | 원격 레포지토리의 특정 브랜치로 로컬에서 현재 checkout 되어있는 브랜치의 커밋 내용을 푸쉬한다. | `-u` : `--set-upstream`이라는 옵션의 약자이다. `-u`옵션을 주면 로컬 레포지토리에 있는 `A`브랜치가 `origin`에 있는 `A`브랜치를 tracking(추적)하는 걸로 설정된다. 추적하고 있는 브랜치명은 다를 수 있지만 보통 99%는 같다. 추적당하고 있는 원격 레포지토리 `B`브랜치를 로컬 `A`브랜치의 `upstream branch`라고 한다. 이전 푸쉬 명령어를 기억하여 `git push`뒤의 추가 텍스트를 편리하게 생략할 수 있게 해준다. `-u` 옵션 활용 후에는 다음번 push 부터는 `git push`라고만 명령어를 입력해도 된다. |
(현재 브랜치가 foo일 때) `git push origin main` : origin 원격 저장소의 main 브랜치로 현재 위치한 foo 브랜치의 커밋 내용을 push 한다. `git push -u origin main` : 현재 체크아웃되어 있는 로컬 브랜치가 원격 `main`브랜치를 `upstream branch`로 설정하고 추적하며 push를 한다. |
|
| git push <remote url alias> <local branch>:<remote url branch> | 특정 로컬 브랜치의 커밋 내용을 원격 레포의 특정 브랜치로 push한다. | `-u` : 위와 동일 | `git push -u origin foo:main` `-u`의 역할 자체는 위와 동일 |
(현재 브랜치가 foo일 때) `git push origin bar:main` : origin 원격 저장소의 main 브랜치로 현재 위치한 foo 브랜치와 상관없이 bar 브랜치의 커밋 내용을 push 한다. |
편리하게 -u 옵션을 많이 사용한다.
단, 하나의 브랜치는 하나의 브랜치만 추적할 수 있다.
그렇기에 `main`브랜치에서 `-u`옵션을 사용하고, `secondary`브랜치로 체크아웃 했다고 가정하자.
`secondary`브랜치는 추적하고 있는 원격 브랜치가 없다. 그냥 `push`로는 `push`를 할 수 없다. `secondary`브랜치도 `-u`를 통해 원격 브랜치를 tracking을 해야 한다.(보통 같은 브랜치를 추적한다. 여기서는 원격 `secondary`브랜치를 추적해야 한다.)
# secondary 브랜치 체크아웃 되어 있는 상태
git push -u origin secondary
특정 브랜치를 특정 원격 브랜치로 보내기 ⭐
위 테이블을 참고하자!
클론해온 파일을 push 하는 경우⭐
정확히 말하자면, fork해온 원격저장소를 클론한 상태라서, 새로운 브랜치를 만들어서 파일 수정 후 내 원격저장소에 다시 push 하려는 경우. push 할 때도 새로운 브랜치명에다가 push를 해주는 게 좋다.
git push origin [메인브랜치가 아닌 다른 브랜치명]
이렇게 하는 이유는 내 원격 레포지토리의 main 브랜치의 내용이 변경되지 않게 하기 위해서다.
내 원격 레포지토리는 fork해온 원격 레포지토리의 버전을 유지하고 있어야 한다. 왜냐하면 fork 해온 원격 레포지토리에서 아직 승인이 되지 않았기 때문에 내 마음대로 클론해서 만든 브랜치에서 수정한 내용이 내 원격 레포지토리의 main을 수정하면 안 되기 때문이다. 새로운 브랜치가 만들어진 원격 레포지토리에 접속해서 fork해온 원격 레포지토리에 pull request를 보내고 승인이 되면 sync fork로 버전을 비로소 맞추면 되는 것이다.
브랜치 만들기 & 체크아웃(이동)하기 & 특정 원격 브랜치 삭제하기 (git branch & checkout) ⭐
| command | meaning | option | annotation | example |
| git checkout <브랜치명> | 이미 생성되어 있는 브랜치들 중 특정 브랜치로 체크아웃[이동] 한다. | `-b`: 브랜치를 만듦과 동시에 체크아웃한다. | git log를 찍어보면 로컬의 Head가 현재 바라보고 있는 branch로 이동했음[가리키고 있음]을 확인할 수 있다. | `git checkout foo`: foo 브랜치로 이동 `git checkout -b foo`: foo 브랜치를 생성함과 동시에 foo브랜치로 체크아웃한다. |
| git branch <브랜치명> | 특정 브랜치를 생성한다. | `-a`: --all의 줄임말. 원격과 로컬의 모든 브랜치를 확인한다. | ||
| git branch <new-branch> <base-branch> | base branch위에 새로운 branch를 생성한다. | |||
| git branch -d foo | 특정 브랜치 삭제 | |||
| git push <원격 저장소 이름> -d <원격 브랜치 이름> | 원격 저장소의 특정 브랜치 삭제 | `-d`: --delete의 줄임말 | ||
| git branch | 로컬 레포지토리의 생성된 모든 브랜치들을 확인한다. | `-r` : 원격 브랜치 목록 조회. `-a` : 모든 브랜치 목록 조회 |
❓git branch 명령어로 새로운 브랜치가 안 만들어져요
working directory 작업만 하고 아직 로컬 레포지토리에 베이스 가지가 생성되어 있지 않아서 그렇다.
commit을 통해 base branch부터 먼저 생성해주자.
원격 저장소 브랜치를 가져와서 그 위에 로컬 브랜치 새로 생성하기(--track)
1. 생성되는 새로운 로컬 브랜치에 이름 붙이고 싶은 경우
$ git branch --track <new-branch> origin/<base-branch>
2. 원격 브랜치와 동일하게 브랜치명을 갖는 새로운 로컬 브랜치 생성하기
$ git checkout --track origin/<base-branch>
checkout 명령어의 대체 명령어
checkout 명령어는 브랜치를 이동하거나 워킹 트리의 파일을 복원하는 등 다양한 기능을 할 수 있다.
하나의 명령어가 너무 많은기능을 수행하면 휴먼 에러를 범할 수 있기 때문에 `switch`와 `restore`로 명령어가 분리되었다. 각각 switch는 브랜치를 이동하는 명령어, restore는 워킹 트리의 파일을 복원하는 명령어이다.
기존 checkout과의 호환을 위해 checkout에서 해당 기능이 사라진 것은 아니므로 checkout만으로도 수행이 가능한 기능들이다. 필요하다면, switch와 restore를 따로 검색해보자!
앞질러있는 브랜치의 커밋까지 확인하기.(--branches)
git log --branches
어떤 브랜치에서 있든 간에 모든 브랜치의 커밋을 다 볼 수 있음.
작업 내용 전후 차이를 확인하기 위한 명령어(git diff)⭐
working directory는 현재 내가 작업중인 브랜치 디렉토리를 의미한다.
| command | meaning | option | annotation | example |
| git diff | Working Directory와 Staging Area 사이의 차이 확인 | 이미 staging area에 올라갔으면 비교 불가능하다. working directory에 추가내용이 생겼다면 ++, 오히려 지워졌다면 --를 표시한다. |
||
| git diff HEAD | local repository HEAD 가 바라보고 있는 Commit과 working directory의 차이점 확인 | HEAD라는 옵션을 사용 | 이미 staging area에 올라갔더라도 차이를 확인할 수 있다. | |
| git diff --staged | staging area와 local repository HEAD가 바라보고 commit과의 차이점 확인 | --staged 라는 옵션을 사용 | staging되지 않은 working directory의 변경사항은 비교대상에서 제외된다. 보통 이걸 많이 사용하는 |
|
| git diff [option] <file name> | 위 옵션을 바탕으로 특정파일만 비교할 수 있도록 할 수 있다. | `git diff --staged foo.txt` : staging area와 local repository를 비교하되, foo.txt 파일의 변경사항만 비교한다. `git diff foo.txt` : working directory와 staging area 를 비교하되, foo.txt 파일의 변경사항만 비교한다. |
||
| git diff <branch1> <branch2> 또는 git diff <branch1>..<branch2> | branch2를 기준으로 branch1에서 얼마나 달라졌는지 보여준다. | `git diff main foo`: foo 브랜치가 main브랜치에 비해 얼마나 달라졌는지 비교한다. foo 브랜치의 작업내용이 더 많아졌다면 ++가 뜨고, 지워졌다면 --가 뜬다. |
||
| `git diff <commitHash1> <commitHash2>` 또는 `git diff <commitHash1>..<commitHash2>` | commit hash2를 기준으로 commit hash1에 비해서 얼마나 달라졌는지 보여준다. | 커밋 해쉬는 git log로 확인하자. 단축 해쉬로도 가능하다. |
git diff
git diff는 working directory와 staging area 사이의 차이를 확인하기 위해 사용된다.

diff --git a/c.js b/c.js # 두 파일 사이의 다른 점을 출력
index af43be9..48a6948 100644 # 파일의 고유 번호
--- a/c.js # ---a : 기존 파일 (staging area에 있거나 commit 메시지 작성 완료된 상황)
+++ b/c.js # ---b : 바뀐 파일 (working directory 변경사항)
@@ -1,2 +1,3 @@
console.log('bar');
console.log('foo again');
+console.log('bar again'); # 바뀐 내용
여기서 🚨주의할 점은 working directory의 변경 사항이 git add를 통해 이미 Staging Area로 넘어갔으면 `git diff` 명령어를 사용해도 아무것도 나타나지 않는다는 점이다.
fetch 명령어
- 패치(fetch): 원격 저장소를 일단 가져만 오기.
- 풀(pull): 원격 저장소를 가져와서 합치기.(커밋 전부 딸려온다.)
git fetch로 현재 원격 저장소의 상태를 그대로 가져올 수 있다. 근데 가져오기만 하고 코드에 반영은 하지 않는다.
git fetch
git fetch만으로는 merge가 되지 않는다. 현재 작업중인 working area에 영향을 끼치지 않는다. 이 말은 원격 레포의 최신 파일을 손에 들고는 있지만 로컬의 커밋 히스토리가 업데이트 되지 않는다는 뜻이다. 안보이는 곳에 원격 레포의 최신 상태를 쟁여둔다고 생각하자.
fetch로 로컬 레포지토리의 commit history를 업데이트 하기 위해서는 다음과 같은 과정이 필요하다.
애초에 pull을 사용하든가
# from local
git pull origin local
혹은 다음과 같은 두 단계로 pull과 동일한 효과를 낼 수 있다.
# from local
git fetch origin
git merge origin/local
origin 이라는 별명을 가진 원격레포에서 현재 상태를 fetch 받아온 후(일단 가져온 후), origin/local 을 merge 하는 것이다.
만약 특정 파일만 fetch받고 싶다면?
git fetch <remote alias> # 혹은 origin 이면 remote alias 생략
git checkout <remote alias>/<remote branch> <remote filepath>
git fetch origin
git checkout origin/main 알고리즘/스택.js
remote filepath는 로컬 저장소에서의 상대경로가 아니라 원격 저장소에서 root에서부터의 상대경로를 적어야 한다.
브랜치에서 작업한 커밋 히스토리를 메인 브랜치와 병합하기(merge)
git merge <병합할 브랜치>
'현재 체크아웃 되어있는 브랜치에서 `병합할 브랜치`를 집어삼킨다' 라고 생각하면 된다.
git merge dev1
특정 파일만 병합(merge)하기 ⭐
브랜치 전체를 병합하는 것이 아니라 특정 파일만 병합하고 싶을 때가 있다.(예를 들어 유료 무료 서비스)
`B`브랜치를 `A`브랜치에 merge 하려는데 특정 파일(`./a.js`)만 병합하고 싶을 상황에서 쓸 수 있는 방법이다.
방법1
`merge`키워드를 사용하지 않지만 아래 방법으로 특정 파일만 합치는 것이 가능하다.
main 브랜치와 tertiary 브랜치가 있다.
`main`브랜치로 체크아웃한 상태에서 아래 명령을 입력한다.
git checkout -p tertiary tester.js
`-p`는 p플래그로 `--patch`의 약자이다. 즉, patch 옵션을 나타낸다.
명령을 통해 패치하겠다는 명령을 주면, 변경 내용 출력과 함께 하단에 지금 작업에 반영하겠냐는 질문이 뜨는데 `y`를 입력해주면 된다.
$ git checkout -p tertiary tester.js
diff --git b/tester.js a/tester.js
new file mode 100644
index 0000000..cbf92a5
--- /dev/null
+++ a/tester.js
@@ -0,0 +1 @@
+console.log(123)
(1/1) Apply addition to index and worktree [y,n,q,a,d,e,?]?
여러 번 질문이 나올 수 있는데 `a`를 입력하면 모든 질문에 `y`를 입력하는 것과 동일한 효과를 얻을 수 있다.
여기서 `[y,n,q,a,d,j,J,g,/,e,?]`의 의미는 아래와 같다
y - stage this hunk
n - do not stage this hunk
q - quit; do not stage this hunk nor any of the remaining ones
a - stage this hunk and all later hunks in the file
d - do not stage this hunk nor any of the later hunks in the file
g - select a hunk to go to
/ - search for a hunk matching the given regex
j - leave this hunk undecided, see next undecided hunk
J - leave this hunk undecided, see next hunk
k - leave this hunk undecided, see previous undecided hunk
K - leave this hunk undecided, see previous hunk
s - split the current hunk into smaller hunks
e - manually edit the current hunk
? - print help
참고로 패치만 했을 뿐 main 브랜치의 커밋 히스토리에는 기록되지 않는다.
자동으로 working tree와 staging area까지만 패치되는데, 커밋은 직접 해야 한다.
방법2
merge를 사용한다.
다수의 파일을 합칠 때 유용하며, 모든 변경 사항을 가져오고 특정 파일의 변경 사항을 제외시켜 특정 파일 외의 모든 변경사항을 합치고 싶을 때 사용한다.
현재 작업중인 `main`브랜치에서 `a.js`파일과 `b.js`파일은 남겨두고 나머지 `tertiary` 브랜치의 모든 변경 사항은 합치고 싶다면 `main`브랜치로 체크아웃 후에 아래의 과정을 거치면 된다.
$ git merge --no-commit --no-ff tertiary -X theirs
$ git reset HEAD a.js b.js
$ git clean -fd
$ git commit
git merge 충돌 발생할 때 (포기하고) merge 취소하기(git merge --abort) ⭐
working directory가 충돌 발생 이전, 즉, merge 이전으로 돌아간다.
git merge 취소하고 되돌리기
git reset --merge ORIG_HEAD
작업 되돌리는 명령어(revert, reset) ⭐
작업을 되돌릴 수 있는 방법으로 크게 두 가지가 있다. `revert` 와 `reset`
✔ revert는 이전의 커밋을 취소하기 + 새로운 Revert 커밋을 만들어내는 방식.
커밋을 협업중인 원격 저장소에 이미 push해버린 경우, 로컬 저장소에서 커밋을 강제로 취소해버리면 원격 저장소와 상태가 틀어져버립니다. 이럴 때 원격 저장소에서 force push를 금지하게 되는데, 이 때문에 로컬 저장소의 변경사항을 push할 수 없게 되어버립니다. (=== git reset으로 강제 reset을 하고 시도할 경우 git pull을 하라고 경고가 뜨게 될 것입니다. 이 때 git pull을 하게 되면 계속 이전에 실수한 커밋을 push 해버린 내용으로 되돌아가게 됩니다.) 이런 경우에는 git revert로 특정 커밋의 내용을 되돌리는 커밋을 하는 방법을 사용해야합니다. 그래야 git pull 경고가 뜨지 않고 바뀐 커밋 내역을 새로 적용할 수 있습니다.
개인적으로 헷갈렸던 점
git revert는 취소할 커밋 해시를 입력해야 하는데(취소의 개념),
git reset은 되돌아갈 커밋 해시를 입력해야 한다는 점이다.(이동의 개념)
1. git revert (원격 레포에 이미 올라갔을 때 작업 되돌리기) ⭐
git revert [취소할커밋해시]
git revert는 working directory, 로컬 저장소, 스테이지에 모두 영향을 미칩니다. 특정 commit을 revert할 경우 그 커밋을 취소하는 것이므로 그 커밋에 해당하는 작업 전으로 돌아갑니다. 예를 들어 코드 작성 커밋이었으면 그 코드가 삭제됨.
코드 작성 ⏩ git add ⏩ git commit (해시 123 생성) ⏩ git revert 123 ⏩ 코드 작성한 거 삭제됨.(취소의 개념)
실행하면 VI 편집기가 뜸. 기본으로 revert 커밋 메시지가 작성되어있음.
ex) Revert "commit fourth"
내가 뭘 잘못해서 revert한다. 적어준다.
`:wq`
명령어 입력해서 저장하고 닫으면 된다.
[merge가 완료된 상태에서 revert 하기]
git log <확인할 커밋 해쉬 번호> -n <확인할 커밋 해쉬 번호 포함 + 이전 커밋 개수>
# 예시
git log 9aed3a99358ccc5bbfd2dd99d29a82273115801a -n 5
# git log {확인할 커밋 해시} -n {출력할 커밋 개수} <--- 이 명령어로 확인할 수 있다.
commit 9aed3a99358ccc5bbfd2dd99d29a82273115801a (HEAD -> master)
Merge: ccdd474 9796ce6
예를 들어 다음과 같은 커밋 로그가 있을 때
Merge 된 커밋 내역 중 ccdd474가 이전 커밋 1 이고, 9796ce6이 이후 커밋 2 이다.
1이 더 예전 커밋이라고 보면 된다.
2개뿐만이 아니라 여러 개가 더 있을 수 있다.
git revert [merge 커밋해쉬] -m [revert할 커밋 number]
git revert 9aed3a99358ccc5bbfd2dd99d29a82273115801a -m 1
이런 식으로 해주면 2번의 내용이 합쳐지기 전으로 돌아갈 수 있다. 1번만 남고 해당 브랜치 working directory에서 2번의 merge 내용이 사라진다.
[여러 커밋 일괄 revert] ⭐
git revert [되돌아갈 커밋 해시]..[되돌리기 시작할 커밋 해시]
이런 식으로 적으면 된다.
되돌리기 시작할 커밋 해시부터 포함하여 되돌아갈 커밋 해시 전까지의 커밋을 날려버린다.
(순서를 두 눈 뜨고 크게 보자.)
예시
아래와 같은 커밋 히스토리가 있다고 가정하자.
버전 0.02로 돌아가고 싶다.
* 0768f7e (HEAD -> main) FEAT: 리드미 v0.04v
* c433f4a FEAT: 리드미 v0.03
* 8fdbef7 FEAT: 리드미 v0.02
그러면 아래와 같이 입력해줘야 한다. 순서를 주의깊게 보자. 당연히 무조건 위에서 아래방향이다.
git revert 8fdbef7..0768f7e
revert하는 커밋마다 자동으로 revert 커밋이 뜬다.
revert 후의 커밋 히스토리는 다음과 같다.(커밋이 역순으로 revert 됐음을 눈여겨 보자.)
* 61b06de (HEAD -> main) Revert "FEAT: 리드미 v0.03"
* bbd17fc Revert "FEAT: 리드미 v0.04v"
* 0768f7e FEAT: 리드미 v0.04v
* c433f4a FEAT: 리드미 v0.03
* 8fdbef7 FEAT: 리드미 v0.02
v0.02로 안전하게 돌아왔다.

자동 커밋이 싫다면...
git revert ccdd474c9fa68823c3c31a2b4c1fc88ba345c501..b028478c25f5e85d8ca4015223161bece00a40a7
위에서 revert는 커밋 메시지가 생성이 된다고 하였다.
커밋된 역순으로 revert 커밋이 자동으로 생성되는데 만약 자동 commit을 사용하지 않으려면 `-n` 옵션을 추가해주면 된다.
(그러면 stage까지만 올라가기 때문에 commit만 수동으로 해주면 된다. 근데 굳이? 그럴 필요가 있을까 싶다.)
2. git reset ⭐
✔ reset 은 시간을 되돌리듯이 기존의 버전을 만들었던 그 시점으로 되돌아가는 방식이다. 단, reset 했다는 커밋은 만들어지지 않음. 원격에 이미 커밋을 반영 후 로컬을 reset 하고 다시 보내면, 내용이 다르고 reset했다는 커밋도 따로 생성되어있지 않기 때문에 거절당함. 그럴 때는 reset을 사용하는 게 아니라 revert를 사용해야 함. 즉, reset은 아직 원격에 push되지 않은 로컬 레포의 커밋을 되돌릴 때 사용하는 것임.
reset 에는 3가지 방식이 있음. `soft`, `mixed`, `hard`
표로 정리
| git reset [옵션] <커밋해쉬> | working directory | staging area | repository |
| `--soft` | 안 바뀜 | 안 바뀜 | HEAD가 <커밋해쉬> 가리킴 |
| `--mixed` | 안 바뀜 | <커밋해쉬>처럼 바뀜 | HEAD가 <커밋해쉬> 가리킴 |
| `--hard` | <커밋해쉬>처럼 바뀜 | <커밋해쉬>처럼 바뀜 | HEAD가 <커밋해쉬> 가리킴 |
그림으로 이해하기
맨 위는 가장 최신의 상태이다.

풀어서 정리
mixed reset(default)
git reset [되돌아갈커밋해시] # 혹은 git reset --mixed [되돌아갈커밋해시]
코드 작성 ⏩ git add ⏩ git commit (해시123 생성)⏩ git reset 123 ⏩ 아무일도 안일어남.(현재 마지막 위치 123으로 그대로 이동했기 때문. 이동의 개념). 뭔가 일이 생기려면 123 이전으로 이동해야 함.
커밋을 했다라고 하는 그 사실을 되돌리고 스테이지는 그 돌아간 시점의 커밋의 스테이지와 동일한 환경이 됨.
작업 디렉토리는 아무런 영향을 받지 않음.
softed reset
git reset --soft [되돌아갈커밋해시]
HEAD가 현재 커밋한 내용을 유지하며 특정 커밋(과거 또는 미래)을 새롭게 가리키게 됨.
현재 작업 중인 working directory와 staging area는 아무런 영향을 받지 않음.
몸만 이동한다고 생각하면 됨.
hard reset
git reset --hard [되돌아갈커밋해시]
ex) git reset --hard 3d26de5
되돌아갈 커밋해시에 해당하는 커밋으로 되돌아가고 그 이후의 작업들에 대해서는 작업 디렉토리를 변경했다라고 하는 사실 그리고 스테이지에 추가했다라고 하는 사실까지도 되돌아가짐. 말 그대로 hard 초기화. 다 초기화됨.
git reset --soft는 대체 어디다 써먹는 것일까? 🤔❓
여러 커밋을 하나의 커밋으로 만들 때 써먹을 수 있다.
정확히 말하자면 불필요한 커밋을 덮어쓸 때 사용할 수 있다.
문제의 상황(예시)
예를 들어 아래와 같이 불필요하게 재귀함수로 메모리 공간을 연속적으로 사용하는 함수가 있다.
def factorial(n):
if n == 1:
return n
else:
return n * factorial(n-1)
이 상태로 커밋을 해보자.(`git commit history`는 내가 설정한 alias config다. 맨 밑 부록 참고.)
$ git add .
$ git commit -m "FEAT: inefficient factorial func"
[main ff70184] FEAT: inefficient factorial func
2 files changed, 8 insertions(+)
create mode 100644 factorial.py
Admin@DESKTOP-J2F58HN MINGW64 ~/Desktop/git-study (main)
$ git history
* d6ed293 (HEAD -> main) FEAT: efficient factorial func
* ff70184 FEAT: inefficient factorial func
* 87bdcdc FEAT: stash used
.
.
.
그런데 더 좋은 코드를 생각해냈다.
def factorial(n):
num = 1
while n >= 1:
num = num * n
n = n - 1
return num
마찬가지로 커밋을 한다.
Admin@DESKTOP-J2F58HN MINGW64 ~/Desktop/git-study (main)
$ git add .
Admin@DESKTOP-J2F58HN MINGW64 ~/Desktop/git-study (main)
$ git commit -m "FEAT: efficient factorial func"
[main d6ed293] FEAT: efficient factorial func
1 file changed, 6 insertions(+), 4 deletions(-)
Admin@DESKTOP-J2F58HN MINGW64 ~/Desktop/git-study (main)
$ git history
* d6ed293 (HEAD -> main) FEAT: efficient factorial func
* ff70184 FEAT: inefficient factorial func
* 87bdcdc FEAT: stash used
.
.
.
그런데 이럴거면 굳이 중간에 `FEAT:inefficient factorial func`이라는 커밋이 필요할까?
이럴 때 `git reset --soft`를 유용하게 사용할 수 있다.
우리는 `FEAT: inefficient factorial func`전으로 돌아갈 것이다. 하지만, working directory와 staging area는 그대로 남겨야 한다. 왜냐하면 이미 최신의 좋은 코드로 업데이트 되어있기 때문에 working directory와 staging area가 바뀔 필요는 없기 때문이다.
Admin@DESKTOP-J2F58HN MINGW64 ~/Desktop/git-study (main)
$ git reset --soft 87bdcdc
Admin@DESKTOP-J2F58HN MINGW64 ~/Desktop/git-study (main)
$ git history
* 87bdcdc (HEAD -> main) FEAT: stash used
확인해보면 working directory도 그대로이고, staging area가 가장 최근(`FEAT: efficient factorial func`)커밋 당시의 그대로임을 확인할 수 있다. 그래서 바로 커밋을 해줘서 불필요해 보였던 커밋을 제거했다.
결론적으로 `inefficient...`를 없애고 `efficient...`를 이용해서 마치 하나의 커밋처럼 만들었다.
커밋 히스토리에는 `FEAT: factorial function`만 남게 된다.
Admin@DESKTOP-J2F58HN MINGW64 ~/Desktop/git-study (main)
$ git status
On branch main
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
new file: factorial.py
modified: test.js
Admin@DESKTOP-J2F58HN MINGW64 ~/Desktop/git-study (main)
$ git commit -m "FEAT: factorial function"
[main 1e4c7d0] FEAT: factorial function
2 files changed, 10 insertions(+)
create mode 100644 factorial.py
Admin@DESKTOP-J2F58HN MINGW64 ~/Desktop/git-study (main)
$ git history
* 1e4c7d0 (HEAD -> main) FEAT: factorial function
* 87bdcdc FEAT: stash used
git merge 혹은 git pull 취소하고 되돌리기(feat. ORIG_HEAD, @, HEAD~) 🌟
🔗HEAD and ORIG_HEAD in Git - stackoverflow
커밋해시로 돌아가는 것 말고 편리하게 뒤로 가기는 없을까? 라는 생각에 검색후에 알아낸 내용들이다.
(그냥 git reset --hard <커밋해시>로 해도 상관 없다는 뜻)
ORIG_HEAD에 대하여
"pull"이나 "merge"는 git에서 위험한 명령어로 취급되어 항상 현재 브랜치의 위험 명령어 수행 직전의 상태를 `ORIG_HEAD`에 남긴다고 한다. 어떻게 보면, `ORIG_HEAD`는 `HEAD`의 이전 상태라고 할 수 있다. 그러나 Git 1.8.5 버전 이후로 `HEAD`는 `@`로도 표현가능해지면서 그 사용성이 불분명해졌다.
`ORIG_HEAD`는 현재 덜 유용하다고 할 수 있다. 왜냐하면 이는 신문법 `HEAD@{1}`로 대체될 수 있기 때문이다.
완전히 같은 뜻은 아니다. 차이점이 있다면, `HEAD@{1}`은 항상 `HEAD`의 마지막 값을 가리키고, `ORIG_HEAD`는 위험한 명령어(pull, merge 등) 전의 `HEAD`의 마지막 값을 가리킨다고 보면 된다. 내가 위험한 명령어를 수행한 지 모르고 커밋을 계속했다면 각각 `HEAD@{1}`과 `ORIG_HEAD`를 사용하여 reset을 했을 때 돌아간 커밋 위치는 차이가 있게 된다.
재밌는 점은 `ORIG_HEAD`과 `git reset HEAD@{1}`이 무한정 뒤로 가는 개념이 아니라 계속 이전 HEAD 작업을 가리켜서 순환하게 된다.
git reset --hard ORIG_HEAD
git reset --hard HEAD@{1}

그럴 때 다음의 명령어를 사용하면 계속해서 이전 커밋으로 후퇴할 수 있다.
git reset --hard HEAD~

한 칸 후퇴가 아니라 여러 칸을 건너뛰고 돌아가려면 아래 명령어를 사용하면 된다.
git reset --hard HEAD~숫자

`git reset --hard HEAD~3`을 입력해줬다.

반대로 reset을 취소하는 방법(reflog 활용하기) ⭐
`reflog`는 reference log의 줄임말이다.
`git reflog`를 사용하면 이때까지 `HEAD`가 가리켜왔던 커밋들을 한 눈에 볼 수 있다.
아래 명령어를 통해 업데이트 했던 명령어에 참조를 걸고 있는 리스트들을 볼 수 있다.
`git reflog` 결과에서 `:` 오른쪽의 해당 명령어를 수행한 이후의 시점이다.
git reflog
# 특정 브랜치의 reflog만 보고 싶다면
git reflog show <브랜치명>
git reflog show main
# -p 옵션을 통해 변동 내용까지 확인할 수 있다.
$ git reflog
a04dcb3 (HEAD -> main) HEAD@{0}: reset: moving to HEAD~
36c2ef9 HEAD@{1}: reset: moving to HEAD~
2032075 (foo) HEAD@{2}: reset: moving to HEAD@{1}
a04dcb3 (HEAD -> main) HEAD@{3}: reset: moving to a04dcb32d5f551f8442d2aa4db8e1750258964db
2032075 (foo) HEAD@{4}: reset: moving to HEAD@{1}
a04dcb3 (HEAD -> main) HEAD@{5}: reset: moving to HEAD@{1}
...
위의 reflog를 자세히 보면 `HEAD@{n}`이 등장한다.
맨 위는 0번이다. 가장 최근의 HEAD를 가리킨다. 즉 현재를 가리킨다.
`HEAD@{1}`을 통해 한 칸 이전을 가리키고, `HEAD@{2}`는 현재로부터 2칸 이전을 가리킨다. 나머지도 동일한 방식이다.
`git reset --hard HEAD@{n}` 를 사용하면 명령 수행 전 어느 시점이든 돌아갈 수 있다.
하지만 이렇게 보는 것은 너무 지저분해 보인다. 중간에 다른 명령어와 수행이 껴 있을 수 있다.
특정 커밋시점을 보고 돌아갈 수 있는 방법은 없을까? 대안이 필요하다.
git log --graph --decorate --oneline $(git rev-list -g --all)

위 명령어를 사용하면 `--branches`로도 볼 수 없는 reset으로 인해 사라진 커밋 해쉬도 볼 수 있다.
커밋 시점만 보고 돌아갈 커밋에 `git reset --hard <커밋 메시지>`를 입력하면 된다.
reflog 다 지우기(git reflog expire)
recover를 위해 reflog는 지우지 않는 것을 권장하지만
과도하게 너무 오래된 reflog는 지워줄 필요가 있다.
90일 이전의 reflog만 지우고 싶다면 아래와 같은 커멘드를 사용한다.
git reflog expire --expire=90.days.ago --expire-unreachable=now --all
모든 reflog를 지우고 싶다면 아래의 커멘드를 사용한다.
git reflog expire --expire=now --all
🚨reflog 지우는 것은 웬만하면 권장하지는 않는다.
작업 임시 저장하기(stash) ⭐
기본적으로 브랜치에서 작성해놓은 내용은 다른 브랜치로 넘어갈 때 commit 해놓지 않으면 다음과 같은 메시지를 보게 될 것이다.
$ git checkout foo
error: Your local changes to the following files would be overwritten by checkout:
c.js
Please commit your changes or stash them before you switch branches.
Aborting
커밋을 하거나 stash를 하라고 하는데 작업 임시 저장하기(stash)를 해보도록 하자.
현재 작업 임시 저장하기
working directory와 staging area의 작업 내용을 stash 할 수 있음.(staging area도 stash할 수 있다는 점을 눈여겨 보자.)
임시 저장된 작업은 파일상에서 사라짐.
# 메시지 없이 저장하면 가장 최근의 커밋 메시지가 스태시 메시지가 됨
git stash
# 혹은 스태시 메시지와 함께 임시 저장
git stash save "스테시 메시지 작성"
git stash -m "스테시 메시지 작성"
임시 저장 작업 내역 조회(list, show)
git stash list
예시
$ git stash list
stash@{0}: WIP on main: 6c5468c Merge pull request #2 from Vegatality/min
stash@{1}: WIP on main: 6c5468c Merge pull request #2 from Vegatality/min
{0} 번이 가장 최근의 스테이시이다.
특정 스테이시 번호 확인하기
$ git stash show stash@{스테시번호}
$ git stash show stash@{0}
c.js | 1 +
1 file changed, 1 insertion(+)
임시 저장 작업 다시 작업 디렉토리에 적용하기(apply, pop)
git stash apply
🚨주의할 점은 다시 작업 디렉토리에 적용한다고 해서 stash list에서 지워지지 않는다는 점이다.
👉수동으로 지워야 함.
git stash apply stash@{스테시번호}
ex) $ `git stash apply stash@{1}`
git stash pop
apply와 drop 명령어의 조합이다
stack을 생각하면 된다.
- stash@{0}을 꺼내온다. (apply)
- stash@{0}을 삭제한다. (drop)
# 인자를 주면 그 인자에 해당하는 stash를 pop한다.
git stash pop stash@{2}
# 인자를 주지 않으면 제일 위 {0}을 pop한다.
git stash pop
스테이징 상태까지 같이 복원하기 ( --index) ⭐
옵션 `--index`와 같이 활용하면,
git stash pop 이후에 따로 staging을 하지 않아도 자동으로 staging이 된다.
# --index 옵션 없을 때 (staging이 안 되어 있음)
$ git stash pop
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: README.md
modified: index.html
no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (1248c07e9784f15a5dfa8df78e50239fe083041f)
# --index 옵션 있을 때 (staging이 되어 있음)
$ git stash pop --index
On branch master
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: app.js
Dropped refs/stash@{0} (7e830776d5734398a8ec4e4b11112d2222670418)
임시 저장 항목 삭제하기(drop, clear)
🚨주의할 점은 지울 때마다 스테시 번호가 당겨진다는 것임.
git stash drop stash@{스테시번호}
전체 삭제는
git stash clear
rebase 하기 ⭐
브랜치가 뻗어 나온 위치를 조정할 수 있음
어떤 상황에 쓸 수 있냐면, 내가 브랜치를 만들어서 새로 파일을 만들었을 때 동시에 내가 뻗어 나온 그 메인 브랜치도 파일을 새로 만든다고 가정하자. 이 때 뻗어나온 자식 브랜치를 checkout하여 두 브랜치의 working directory를 합치고 싶을 때 쓴다. 베이스로 할 브랜치[main or master 등]의 최신 커밋을 자식 branch가 포함하게 된다.
git rebase [베이스로 할 브랜치]

# foo 브랜치에 있는 상태
git rebase main
# conflict 있으면 conflict 해결 후
git rebase --continue
그러면 아래 사진처럼 foo가 main의 커밋내용을 포함하여 앞지르게 된다.
눈여겨 볼 점은 foo 브랜치와 main 브랜치가 한 줄에 있다는 점이다.

이후 main branch로 checkout하여 foo를 merge해주면 3-way-merge가 아닌 fast-foward merge가 된다.
git checkout main
git merge foo
rebase 하려는데 (conflict)에러가 뜨는 경우
rebase하려는데 에러가 떠서 rebase를 취소하려면 아래 명령어를 사용하면 된다.
git rebase --abort
혹은 에러를 해결하고 rebase를 하고 싶은 경우
(conflict) 에러를 해결 후에 commit을 하면 다음과 같은 상태가 될 것이다.

`(foo|REBASE 1/2)`는 rebase 되기 전까지 아직 한 단계가 부족하다는 얘기다. 더 정확히는 성공적인 rebase를 위한 전체 절차 2개 중 1개만 수행했다는 뜻이다.
상태를 확인해보자
$ git status
On branch foo
Last command done (1 command done):
pick 7b9b29c FEAT: foo branch base
Next command to do (1 remaining command):
pick 81665b8 FEAT: D added
(use "git rebase --edit-todo" to view and edit)
You are currently editing a commit while rebasing branch 'foo' on '828bd60'.
(use "git commit --amend" to amend the current commit)
(use "git rebase --continue" once you are satisfied with your changes)
nothing to commit, working tree clean
현재 완료한 작업과 다음으로 해야 할 작업을 제시해준다.
여기서 더 수정하려면 `--amend` 옵션을 붙여주면 되고, 만족했으면 `--continue`를 붙여주면 비로소 rebase가 끝나게 된다.
rebase한 foo 브랜치 머지하기
`foo`브랜치가 파생되어 나온 `main`브랜치로 이동 후 `foo`브랜치 머지를 한다.
git checkout main
git merge foo
어차피 머지할거면 Merge랑 Rebase랑 뭐가 다른걸까? (브랜치 병합 전략)
Rebase는 브랜치를 병합할 때 Merge와는 다르게 Merge commit을 남기지 않는다.
마치 다른 브랜치는 애초에 없었던 것처럼 프로젝트의 작업 내용이 하나의 흐름으로 유지된다.
Merge를 쓰든 Rebase를 쓰든 정답은 없다. 프로젝트나 팀의 상황에 따라 다른 전략을 사용할 수 있다.
다만, 로컬에서는 히스토리 정리를 위해 Rebase를 할 수도 있지만 이미 원격 저장소에 Push된 커밋은 Rebase 하지 않는 것이 일반적이다.
Rebase 관련해서 아주 좋은 글이 있어 🔗링크를 남긴다.
github에 잘못 올라간 파일 삭제하기(원격만 삭제 혹은 로컬 원격 둘 다 삭제)⭐
이미 원격 저장소에 올라가면 안되는 파일이 올라갔을 때의 상황이다. 여기서는 삭제가 목적이다. 삭제가 아니라 파일 내용 되돌리기만 할거면 revert나 reset을 고려하자.
원격 저장소에서 파일 삭제하기
이미 github remote에 push를 했기 때문에 로컬의 저장소에서 직접 파일을 삭제해도, 원격 저장소에서는 삭제되지 않는다. 따라서 git 명령어를 통해 명령을 전달하는 방식으로 삭제해야 한다.
두 가지 선택이 있다.
# 원격 저장소와 로컬 저장소에 있는 파일을 삭제한다.
$ git rm [File Name]
# 원격 저장소에 있는 파일을 삭제한다. 로컬 저장소에 있는 파일은 삭제하지 않는다.
$ git rm --cached [File Name]
원격 저장소에서 삭제 후 다음 번에는 실수로 올라가지 않도록 .gitignore 파일을 적절히 세팅해두도록 해야 한다.
💥 기타 에러 해결법 모음
1. unrelated histories 에러
$ git pull https://github.com/Vegatality/Test-4-TodoBook.git
From https://github.com/Vegatality/Test-4-TodoBook
* branch HEAD -> FETCH_HEAD
fatal: refusing to merge unrelated histories
깃허브 만들고 리드미 작성 후에 연결하고 나서 push했을 때 보게 된 에러.
[해결 코드]
git pull origin 브런치명 --allow-unrelated-histories
이미 존재하는 두 프로젝트의 기록을 저장할 때 사용. git 에서 관련된 기록이 없는 두 프로젝트를 병합할 때 기본적으로 거부하도록 되어 있다. 이것을 허용한다고 명령하는 것임.
2. permission denied 에러
$ git push origin main
remote: Permission to hangheTinder/HanghaeTinder_FE.git denied to Vegatality.
fatal: unable to access 'https://github.com/hangheTinder/HanghaeTinder_FE.git/': The requested URL returned error: 403
[해결 코드]
아직 연결된 레포가 없다면(혹은 원격 연결을 끊었다면)
git remote add origin https://<GeneratedToken>@github.com/<UserName>/<RepositoryName>.git
만약 이미 연결된 레포가 있다면 그 레포지토리 주소를 토큰을 적용시킨 원격 레포 url로 바꿔준다.
git remote set-url origin https://<GeneratedToken>@github.com/<UserName>/<RepositoryName>.git
[토큰 발급 받는 방법]
혹시 토큰 발급 받지 않았거나 발급 받는 방법을 모르는 경우 다음 단계(사진 必 참고)를 따라하면 된다.
1. Settings
2. Developer Settings
3. personal access tokens
3. Tokens(classic)
4. Generate new token
5. Note 남기기
6. Select scopes에서 repo 체크박스 선택
7. Generate Tokens 버튼 클릭.




Generate tokens를 하면 토큰이 나오는데 보여줄 수는 없어서 글로 대체한다.
그 토큰은 재발급 받을 수 없으니 어디에 잘 보관해두어야 한다. (잊어버리면 이전 토큰 대신 아예 새롭게 다시 발급 받아도 됨)
Personal Access Token은 깃헙에서 발급받으면 자동으로 모든 레포에서 이 생성된 토큰을 바라볼 수 있게 된다.
부록
1. 복잡하고 긴 커맨드에 alias 설정하기
커맨드가 너무 긴데 자주 사용한다면 나만의 별명을 붙여서 짧게 명령을 입력하여 같은 효과를 얻을 수 있다.
이 글의 맨 위에서 사용했던 사용자 git 설정 `git config`를 통해 `alias`를 설정할 수 있다.
aliasing 방법
`alias`뒤에 `.`을 붙여주고 그 뒤에 내가 붙일 별명을 적어주면 된다.
방법은 무척 간단하니 예시로 이해하자.
예를 들어 `git log --oneline --graph --branches -p` 라는 명령어가 있다고 가정하자.
git config alias.history 'log --oneline --graph --branches -p'
다음부터는 내가 만든 `git history`를 통해 `git log --oneline --graph --branches -p`와 동일한 표시를 하도록 할 수 있다.
내가 설정한 config 리스트 보기
아래 명령어를 통해 config 세팅을 볼 수 있다.
git config --list
.
.
.
init.defaultbranch=master
user.email=어쩌구저쩌구@gmail.com
user.name=Vegatality
filter.lfs.required=true
filter.lfs.clean=git-lfs clean -- %f
filter.lfs.smudge=git-lfs smudge -- %f
filter.lfs.process=git-lfs filter-process
core.repositoryformatversion=0
core.filemode=false
core.bare=false
core.logallrefupdates=true
core.symlinks=false
core.ignorecase=true
alias.history=log --oneline --graph --branches
맨 밑에서 내가 설정한 alias config를 찾을 수 있다.
혹은 현재 로컬 레포지토리의 config 폴더, 즉, `.git/config`폴더에서 config를 살펴볼 수 있다.
아래 명령어를 통해 쉽게 찾아볼 수 있다.
git config --list --show-origin
.
.
.
file:.git/config core.bare=false
file:.git/config core.logallrefupdates=true
file:.git/config core.symlinks=false
file:.git/config core.ignorecase=true
file:.git/config alias.history=log --oneline --graph --branches
마찬가지로 맨 밑에서 찾아볼 수 있다.
2. 누가 작성한 코드인지 알아내기(blame, show) 👤🔫🔫🔫
# blame
git blame 파일명
# show
git show 커밋해쉬
`blame`을 사용하면 아래 처럼 해당 파일에 대한 커밋해쉬랑 작성자명까지 나온다.
범인 찾을 때 사용하자🤣
$ git blame bee.js
2c05bad1 (Vegatality 2023-11-08 18:05:09 +0900 1) console.log('bee');
레퍼런스
1. https://hackmd.io/@oW_dDxdsRoSpl0M64Tfg2g/ByfwpNJ-K
2. https://teamsparta.notion.site/Bonus-Git-Cheat-Sheet-3836f048021442fea8e9685ec8489784