여태 배웠던 거에서 약간의 부수적인 것들을 더 알아보자.
의미: 파일 단위가 아닌 더 작은 단위의 변경사항
우리는 여태 커밋을 하나의 파일 단위로 했었다.
그런데 만약에 하나의 파일에서도 라인별로 다른 커밋을 하고 싶을 때가 있다.
이건 기존의 파일 단위 커밋으로는 힘들다.
이럴 때 필요한 개념이 hunk
이다.
실습을 통해 알아보자.
$ touch testFile.txt
$ vim testFile.txt
$ cat testFile.txt
including text for elastic develop
including text for docker config
including text for linux bash config
$ git add .; git commit -m 'commit hunk test file' # 일단 커밋
$ vim testFile.txt # 편집! 모든 문자열 뒤에 "changed" 라는 문자열을 추가해줬다.
$ cat testFile.txt
including text for elastic develop - changed!!!
including text for docker config - changed!!!
including text for linux bash config - changed!!
# 이 상태에서 하나의 파일을 단위로 작업하는게 아니라 라인별로 add, commit 따로 해보자.
$ git add -p # 이렇게 입력하면... 아래처럼 화면이 나온다.
현재 [y, n, q, a, d, s, e, ?]
중 하나를 입력하라고 하는데,
도통 뭔소린지 모르겠다. 이럴 때는 ?
를 입력하고 엔터한다.
도움말을 보니 대충 상황을 알았다.
(1/1)
이라는 표시는 현재 hunk 가 그냥 통으로 다 잡혔다는 뜻이다.
나는 각 라인별 변화가 hunk
로 잡히길 바라서 s
줬다.
그 이후에 각 hunk 에 대해서 staging 여부를 y/n
으로 결정했다.
여기서는 elastic, bash config 와 관련된 라인에 대해 add를 했다.
이 상태에서 git status
명령어를 입력하면 위처럼 나온다.
git diff --staged
를 통해서 현재 staging 된 것과 리포지토리 커밋을 비교해보자.
마지막으로 git commit -v
를 통해서 커밋을 진행한다. 그러면 위와 같은 커밋 메세지
작성 창이 나온다. 여기서 맨 첫줄에 적절한 메세지를 적어주고 :wq
로 나와주자.
커밋 내용을 보면 방금까지 봤던 git diff --staged
와 같은 내용이 있음을 알 수 있다.
지금까지 add 와 commit을 더 세세하게 하는 법을 알아봤다.
커밋하기 애매한 것들을 잠시 임시공간(stack)에 치워두는 기능
어떤 일을 하다보면 갑자기 선임자가 일을 부탁해서 하던 작업을 중단하고 변경사항을 저장해야 할 때가 있다.
문제는 이걸 commit으로 하기에는 굉장히 애매하다는 것이다.
이럴 때 유용한게 git stash
이다.
git stash
를 사용하면 우리가 작업하던 변경 파일들을 모두 다른 곳에 잠시 저장할 수 있다.
$ ls
a1.txt b1.txt
$ git log --all --decorate --oneline --graph
* 6c0d0b8 (HEAD -> main) First Commit
$ vim b1.txt # 파일 내용 변경
$ git status # modified: b1.txt 출력
$ git stash # 이건 git stash save 와 같은 의미다.
# 참고로 git stash -p 를 하면 hunk 로도 스테시할 수 있다.
Saved working directory and index state WIP on main: 6c0d0b8 First Commit
$ git stash list
stash@{0}: WIP on main: 6c0d0b8 First Commit
$ git stash apply # stash 한 내용 재 적용
$ git stash list # 그런데 재적용해도 stash 기록은 삭제 되지 않는다.
stash@{0}: WIP on main: 6c0d0b8 First Commit
$ git stash drop stash@{0} # 이렇게 해야 지워진다.
$ # 만약 재적용과 동시에 drop 까지 할려면 pop을 쓰면 된다.
# apply + drop = pop
### 참고로 다른 브랜치에서 switch를 통해 옮기려는 브랜치에
### 충돌이 날 파일이 있으면 switch가 안된다. 이때야 말로 정말 stash가 빛을 발휘한다.
$ vim a1.txt
$ git commit -am 'main branch change!'
$ git checkout HEAD^
$ git switch -c dev-branch
Switched to a new branch 'dev-branch'
$ git log --all --decorate --oneline --graph
* 7600c34 (main) main branch change!
* df6dd91 (HEAD -> dev-branch) second commit
* 6c0d0b8 First Commit
$ vim a1.txt
$ git status
On branch dev-branch
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: a1.txt
no changes added to commit (use "git add" and/or "git commit -a")
$ git switch main
error: Your local changes to the following files would be overwritten by checkout:
a1.txt
Please commit your changes or stash them before you switch branches.
Aborting
$ git stash
$ git switch main # 스위칭이 된다!
참고:
git stash -u
를 하면 untracked 파일도 stash 해준다.git stash -a
를 하면untracked + ignore
파일도 stash 해준다.git stash -p
를 하면 hunk 단위로 stash가 가능하다.git stash clear
로 모든 stash 정보를 지운다.
마지막 커밋 수정
git commit --amend -m '수정할 메세지 작성~'
## tip. 파일 수정 + 커밋 메세지는 변경 X 시에는 아래처럼.
git add changelog.md # 수정한 파일 add
git commit --amend --no-edit # 수정한 파일 aded 한 후에 기존 커밋에 덧붙임.
마지막 커밋뿐만 아니라 더 과거의 커밋 내역도 수정 가능
$ git log --oneline
1fc940e (HEAD -> main) FINAL COMMIT
4da18cd 이 커밋 왜함? # 3. 이 커밋은 정말 지우고 싶음
50ab6ab fourth commit
250ebda third 부분 추가 # 2.third commit 으로 하나로 합치고 싶음
558f8b5 third commit
ae178a6 something... # 1.이름을 바꾸고 싶음!
2d85548 second commit
7c3d90a FIRST COMMIT
# git rebase -i {커밋 해시 }: 여기서 커밋 해시값은 고치고 싶은 커밋의 바로 앞 커밋!
# 일단 "1. 이름을 바꾸고 싶음" 부터 해결해보자.
git rebase -i 2d85548
git rebase -i 에 의해서 아래 화면이 나온다.
위 그림을 자세히 보면 맨 위의 문단에 pick... pick... 로 표기되어 있는 게 보인다.
이거는 각 커밋에 대해서 어떻게 작업을 할 것인지 지정하는 것이다.
지금처럼 pick(= p) 로 커밋 해시 앞에 적혀 있으면,
해당 커밋은 그냥 기존 커밋이 유지되는 것이다.
하지만 우리가 지금 하려는 건 "커밋 이름 수정"이다.
이때는 위 그림처럼 pick
대신 reword(= r)
로 수정해주면 된다.
그리고 나서 :wq
로 빠져나온다.
빠져나오면 바로 커밋 메세지 작성 화면이 나온다.
이때 커밋 메세지를 수정하면 된다. 나는 BUG FIXED 라고 수정하겠다.
git log로 과거의 커밋이 수정된 걸 확인할 수 있다.
$ git log --oneline
e078397 (HEAD -> main) FINAL COMMIT
6394e3c 이 커밋 왜함?
40a91a8 fourth commit
c199501 third 부분 추가
9fcc6ad third commit
17a92bd BUG FIXED ## 커밋 메세지 수정 성공!
2d85548 second commit
7c3d90a FIRST COMMIT
이번에는 바로 이어서 "이 커밋 왜함?" 커밋을 없애보겠다.
$ git rebase -i 40a91a8
###################################################
# rebase -i 화면이 나오면 맨 윗 문단을 아래처럼 수정
d 6394e3c 이 커밋 왜함? # pick => d 로 수정!!
pick e078397 FINAL COMMIT
####################################################
# 참고: 만약에 충돌이 나면 해결한 후, git add .; git rebase --continue!
# "이 커밋 왜함?" 커밋이 삭제된 것을 확인
$ git log --oneline
57cef05 (HEAD -> main) FINAL COMMIT
40a91a8 fourth commit
c199501 third 부분 추가
9fcc6ad third commit
17a92bd BUG FIXED
2d85548 second commit
7c3d90a FIRST COMMIT
##### 이번에는 "third 부분 추가" 커밋을 "third commit" 에 통합시키자.
###################################################
# rebase -i 화면이 나오면 맨 윗 문단을 아래처럼 수정
pick 9fcc6ad third commit
s c199501 third 부분 추가 # pick => s 로 수정!!
pick 40a91a8 fourth commit
pick 57cef05 FINAL COMMIT
###################################################
# 위처럼 하면 바로 앞의 커밋에 합친다(squash)는 것이다.
그러면 아래와 같은 화면이 나오는데, 여기서 상단에 보면 두 개의 커밋 메세지가 보인다.
여기서 우리가 지우고자 하는 것을 삭제하고 :wq
로 나가면 끝이다.
참고로 여기서 남기는 하나의 커밋 메세지는 수정도 가능하다!
- 삭제 전
- 삭제 후
마지막으로 확인하면... 정상적으로 합쳐진 것을 확인할 수 있다!
$ git log --oneline
f1933c7 (HEAD -> main) FINAL COMMIT
44009cc fourth commit
4181e05 third commit # 합쳐졌다!
17a92bd BUG FIXED
2d85548 second commit
7c3d90a FIRST COMMIT
그런데 만약에 하나의 커밋을 둘로 나누고 싶으면 어떨까?
rebase -i
는 이것 조차도 가능하게 해준다.
## 예를 들어서 우리가 마지막 FINAL COMMIT에 외부 팀의 디자인과 javascript 를
## 통합하는 과정이라고 해보자.
$ vim design.txt # 외부팀의 디자인 통합
$ vim javascript.txt # 외부팀의 javascript 기능 통합
$ git add .
$ git commit -m 'INTEGRATING'
$ git log --oneline
460760c (HEAD -> main) INTEGRATING
0a67414 FINAL COMMIT
44009cc fourth commit
4181e05 third commit
17a92bd BUG FIXED
2d85548 second commit
7c3d90a FIRST COMMIT
# 그런데 막상하니 design 과 javascript 는 하나의 커밋에 두기 애매하다고 판단된다.
# 그래서 두 개의 커밋으로 나눌 것이다.
# 'INTEGRATING - DESIGN', 'INTEGRATING - JS' 로 나눌 예정이다.
$ git rebase -i 0a67414
###################################################
# rebase -i 화면이 나오면 맨 윗 문단을 아래처럼 수정
e 460760c INTEGRATING # pick ==> e 로 수정
###################################################
# 여기서 git bash 맨 끝에보면 rebase 중임을 알 수 있다.
# devToroko@DESKTOP MINGW64 /c/study/rebase_i (main|REBASE 1/1)
$ git reset HEAD^ # 한 단계 전으로 돌아가서...
$ git status
interactive rebase in progress; onto 0a67414
Last command done (1 command done):
edit 460760c INTEGRATING
No commands remaining.
You are currently editing a commit while rebasing branch 'main' on '0a67414'.
(use "git commit --amend" to amend the current commit)
(use "git rebase --continue" once you are satisfied with your changes)
Untracked files:
(use "git add <file>..." to include in what will be committed)
design.txt
javascript.txt
##### 여기서부터 커밋을 나누면 된다! #####
$ git add design.txt
$ git commit -m 'INTEGRATING - DESIGN'
$ git add javascript.txt
$ git commit -m 'INTEGRATING - JS'
$ git rebase --continue
$ git log --oneline # 나뉜 것을 확인!
d26a17d (HEAD -> main) INTEGRATING - JS
59c464b INTEGRATING - DESIGN
0a67414 FINAL COMMIT
44009cc fourth commit
4181e05 third commit
17a92bd BUG FIXED
2d85548 second commit
7c3d90a FIRST COMMIT
HEAD 는 브랜치이다. 그리고 브랜치는 무언가를 가리키는 "포인터"다.
HEAD 는 다른 브랜치를 가리키는 포인터다.
이 말이 이해가 안되면 Pro Git에서 제공하는 개념을 조금 숙지하고 오자.
개념만 알았으면 응용하는 법을 알아보자.
일단 아래처럼 git commit history를 완성시켰다.
세세한 커밋의 내용물은 중요하지 않다.
# 현재 HEAD 포인터는 main 브랜치에 붙어 있다.
# HEAD 를 이동시키면서 파일 상태를 스스로 확인해보자.
$ git switch dev-01 # HEAD 가 dev-01 브랜치로 이동
$ ls -al
$ git checkout HEAD^
# HEAD~, HEAD~1 모두 가능, HEAD가 현재 가리키는 커밋의 바로 이전 커밋을 가리키게 된다.
# 위와 같이 입력하면 결과적으로 HEAD는 "dev-01 branch First commit" 가리키게 된다.
$ git log --oneline
58645ab (HEAD) dev-01 branch First commit
f7b4a46 Second commit
c440e26 First Commit
$ git checkout f7b4a46 # 참고로 이렇게 HEAD 기준이 아닌 커밋 해시로 이동도 가능!
Previous HEAD position was 58645ab dev-01 branch First commit
HEAD is now at f7b4a46 Second commit
$ git log --oneline
f7b4a46 (HEAD) Second commit
c440e26 First Commit
# 그런데 이상태에서 새로운 커밋을 생성하려면?
# 간단하다! 새브랜치를 만들고 새로운 커밋을 만들면 그만이다.
$ git branch hotfix
$ git switch hotfix
# git branch hotfix + git switch hotfix = git switch -c hotfix
$ vim headTest.txt
$ git commit -am 'hot fix # 001 done'
결과
위처럼 HEAD를 옮기고 나서 특정 커밋에 태그도 달 수 있다.
태그와 관련된 명령어는 아래와 같다.
$ git tag elastic-search/v1.0.0 # 태그 생성
$ git tag -d elastic-search/v1.0.0 # 태그 삭제
$ git tag -a elastic-search/v1.0.0 # 상세한 태그 생성
$ git tag elastic-search/v1.0.2 -m '태그 메세지'
$ git tag # 이미 있는 태그 조회
$ git show elastic-search/v1.0.0
$ git tag elastic-search/v1.0.0-milestone {커밋 해시} -m '태그 메세지'
$ git checkout elastic-search/v1.0.0 # 특정 태그로 HEAD 옮기기
### 원격에 태그 업데이트
$ git push origin elastic-search/v1.0.0 # 태그명을 준다
$ git push --delete origin elastic-search/v1.0.0 # 원격 태그 지우기
$ git push --tags # 로컬에 모든 태그 원격에 업데이트
git push origin elastic-search/v1.0.0
를 실행한 후의 github의 Tags 상태
릴리즈(release)는 원격저장소의 태그가 달린 특정 커밋의 작업물을
간단하게 다운로드 받을 수 있도록 해준다.
Visual Studio Code 의 release에 가보면 클릭만하면 소스가 다운로드 되는 것을 확인할 수 있다.
우리만의 릴리즈를 한번 만들어보자.
git push origin {tag 명}
실행 후에, github 가면 위 그림처럼
tag
버튼 앞에 숫자가 달라진다. 이 버튼을 클릭해준다.
위 그림처럼 릴리즈로 내놓고 싶은 태그명 옆의 Create release
버튼 클릭
위처럼 태그 제목, 내용을 작성해준다.
추가적인 바이너리 파일는 아래 드래그 & 드롭해준다.
그리고 This is a pre-release
를 체크하면 아직 제품화 단계까지는 아님을 알려준다.
테스트니까 그냥 체크하지 않고 , Publish release
버튼을 클릭한다.
결과
알아두면 유용한 git 설정들을 몇가지 알아두자.
git config --list # 현재 git 설정들 목록조회, --global 옵션으로 글로벌 설정 목록조회
git config -e # git 설정을 에디터로 편집, --global 옵션을 줘서 글로벌 설정 편집
git config --global core.autocrlf true # 윈도우면 "true" , Mac 이면 input
# 개행 문자 차이 때문에 이런 설정을 해줘야 한다.
git config pull.rebase {false/true}
# git pull 명령어 기본 동작 merge/rebase 설정
git config --global push.default current
# push 시 로컬과 동일한 브랜치 명으로 원격에 push 하도록 설정
# ex) main 브랜치에서 push 하면 기본으로 origin/main 으로 push!
git config --global alias.(단축키) "명령어"
# 새로운 커스텀 명령어 생성, 가장 대표적인 것이 로그 이쁘게 출력하는 명령어다.
# "git log 예쁘게" 라고 검색하면 많이 나온다.
git config --global --unset alias.(단축키)
# 커스텀 명령어 삭제
git untracked 파일을 제거
$ git status
On branch main
Untracked files:
(use "git add <file>..." to include in what will be committed)
some-directory/
something01
something02
something03
something04
something05
nothing added to commit but untracked files present (use "git add" to track)
$ git clean # i ,n , f 중 옵션 하나를 무조건 줘야 한다.
fatal: clean.requireForce defaults to true and neither -i, -n, nor -f given; refusing to clean
$ git clean -f
Removing something01
Removing something02
Removing something03
Removing something04
Removing something05
$ git status
On branch main
Untracked files:
(use "git add <file>..." to include in what will be committed)
some-directory/
nothing added to commit but untracked files present (use "git add" to track)
$ git clean -df # 폴더도 지우고 싶다면 -df
Removing some-directory/
$ git status
On branch main
nothing to commit, working tree clean
$ # git clean -dn 을 하면 삭제될 것들이 뭔지 쭉 다 볼 수 있다.
reset, revert 말고도 restore에 대해 알아보자.
그리고 reset 한 것을 되돌리는 것도 알아보자.
$ cat > restore_test.txt << EOF
> i am gonna change
> EOF
$ git add .; git commit -m 'some commit'
$ echo "more change" >> restore_test.txt;\
git commit -am 'some more commit'
$ echo "more more change" >> restore_test.txt; \
git commit -am 'some more more commit'
$ echo "final change" >> restore_test.txt; git commit -am 'final commit'
$ git log -4 --oneline
0a9df42 (HEAD -> main) final commit
f90a648 some more more commit
5046f4e some more commit
ea22ebd some commit
$ cat restore_test.txt
i am gonna change
more change
more more change
final change
# 여기서 커밋해시 => f90a648 로 복구 restore_test.txt 를 복구 시켜보겠다.
$ git restore --source=f90a648 restore_test.txt
$ cat restore_test.txt
i am gonna change
more change
more more change
# 기존에 있었던 "final change" 라는 문구가 사라졌다.
$ git status # 하지만 특정 커밋의 상태의 내용으로 되돌려놓았을 뿐,
# 커밋자체 바뀌는 건 아니다. 그러니 다시 add + commit을 해서 최종 반영을 해야한다.
# ...로그 일부 생략...
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: restore_test.txt
### 이번에는 stage 에 있는 것을 working directory 로 다시 되돌려 놔보자.
$ git reset --hard # 테스트를 위해 원복
$ echo 'blabla~~ blabla~~' >> restore_test.txt # 고의적으로 변경
$ git add . # staging!
$ git status -s
M restore_test.txt
# 이 staging 을 취소하겠다!
$ git restore --staged restore_test.txt
$ git status -s
M restore_test.txt
### 더 나아가서 working directory 의 변경된 내용도 기존으로 복구 시키고 싶다면?
$ git restore restore_test.txt
$ git status -s
# 끝
정말 말도 안되지만 실수로 git reset --hard
로 맨 처음 커밋으로 되돌려 버리는 실수를
범했을 때 어떻게 해야할까? 이때는 reflog 와 reset을 같이 쓰면 해결할 수 있다.
$ git log --all --oneline --decorate --graph
# ... 중간 커밋들 모두 생략 ...
* 5fbf5d2 Update kakao.txt
* 74668fb naver.txt changed
* b5d3d86 First Commit ## 여기로 reset 해보자.
$ git reset --hard b5d3d86
$ git log --oneline
b5d3d86 (HEAD -> main) First Commit
$ git reflog
b5d3d86 (HEAD -> main) HEAD@{0}: reset: moving to b5d3d86
0a9df42 HEAD@{1}: reset: moving to HEAD
0a9df42 HEAD@{2}: commit: final commit
f90a648 HEAD@{3}: commit: some more more commit
5046f4e HEAD@{4}: commit: some more commit
ea22ebd HEAD@{5}: commit: some commit
# ... 나머지 로그는 생략 ...
## reflog의 내용 중 제일 위에 것을 보면 우리가 마지막에 했던 reset 이 보인다.
## 이때 봐야 될 것은 그 reset을 하기 이전의 커밋 해시이다.
## 여기에서는 0a9df42 이다. 이 시점으로 되돌리면 된다!
## 되돌리는 방법은 아래와 같다.
$ git reset --hard 0a9df42
$ git log --all --oneline --decorate --graph
# 모든게 되돌아 온 것을 확인할 수 있다.