이 글은 ASAC 06기를 수강하며 강의 자료 참고 및 추가 자료 수집을 통해 작성된 글입니다.
Git 버전 관리
- 일반적인 버전관리는 파일 기반의 변경이 발생한다. 중복된 내용이 계속 쌓여가기 때문에 용량 비효율적이다.
- Git 버전 관리는 변경사항(Diff) 기반의 변경이 발생한다. 변경된 내용만 쌓여가기 때문에 용량 효율적이다.
- commit은 직전 버전 기준 변경사항 저장 (히스토리 조각. 단위)
코드는 로컬에서, 그리고 중앙(원격)에서 관리된다.
- Git : 로컬 코드 관리
- GitHub : 중앙 코드 관리
Git 내 영역
- remote : 원격 리포지토리
- local : working directory 작업공간
- tracked
- git이 추적하는 파일(알고있는 파일)
- untracked
- git이 추적하지 못하는 파일
- ex) 새로 추가된 파일 혹은 의도적으로 추적하지 못하게 한 파일(.gitignore)
- unstaged
- git이 추적하는 파일 중 수정된 파일들의 집합(아직 staging, commit을 하지 않음)
- Staging Area, Index
- commit이 되기 위해 대기중이인 파일 (대기조) -> git commit
- add를 해서 staging area에 올린다.
- commit 명령어 시 여기에 있는 파일들이 commit 된다. (스냅샷 찍힘)
- cache = index = stage 혹은 cached = indexed = staged
DS_Store
Desktop에서 작업 시 자동으로 생기는 파일
DS_Store를 제외시키거나, Desktop에서 작업하지 않기
Commit 단위를 나누는 기준
commit의 작은 단위 = hunk
hunk를 나눌 때 어떤 기준으로 나눠야 할까?
한 파일에서 2개의 메서드를 개발하는 경우 -> 하나의 메서드에 대해서만 hunk로 나누어 commit
- 일반적으로 Controller 내부의 각각의 Request Mapping을 기준으로 hunk를 나누어 커밋한다.
- hunk는 메서드 단위로만 나눈다. (관련된 클래스들 함께)
- 코드 추적이 좋기 때문에 커밋을 세부 단위로 나누어서 해야 한다.
- 작업이 잘못 되었을 때 커밋 단위 롤백을 해야하므로, 세분화 하면 할 수록 특정 커밋에 대해서만 롤백할 수 있다.
- 롤백의 용이성
- 리뷰의 용이성
Git 시작하기
Git의 활성화 여부는 .git 디렉토리의 유무에 따라서 결정된다. 반드시 최상위 작업 디렉토리 내부에 .git 디렉토리가 존재해야만 정상적으로 버전 관리가 가능하다.
방식은 두 가지이다.
- 로컬 -> 원격 : 로컬에서 .git 디렉토리를 만들고 git 작업을 하다가, 원격 리포지토리 생성 후 로컬과 연결하기
- 원격 -> 로컬 : 원격 리포지토리를 clone하여 로컬에서 작업하기
1. 로컬 -> 원격 방식
로컬에서 .git 디렉토리 생성
git bash를 이용한 터미널 작업을 할 것이다.
.git 디렉토리를 생성하기 위해서 먼저 원하는 작업 디렉토리 경로로 간다.
그리고 git init 명령어를 사용하여 .git 디렉토리를 생성하고 git을 활성화할 수 있다.
cd 작업경로 // 원하는 디렉토리로 이동
git init // .git 디렉토리 생성
원격 리포지토리 생성
로컬에서 작업한 커밋 내역들을 원격 리포지토리에 올리기 위해서는 원격 리포지토리 생성이 필요하다.
이미 .git 디렉토리가 존재하기 때문에, README 파일을 생성하도록 하면 GitHub에서 .git 디렉토리를 하나 더 생성하게 되어 충돌이 발생한다.
README 파일을 체크하지 않고 리포지토리명과 공개 범위 설정 후 Create Repository 버튼을 눌러 원격 리포지토리를 생성한다.
원격 리포지토리 연결
git remote add origin 원격리포지토리주소 // 원격 주소지 alias 추가
git branch -M main // default 브랜치 변경
git push -u origin main // git push --set-upstream origin main과 동일. 원격 리포지토리에 push
git push -u 원격alias 브랜치명 // 원격 리포지토리와 브랜치를 따로 지정하여 push
- remote는 원격 리포지토리 주소지들을 관리한다. origin 이외에도 원하는 이름으로 원격 주소지의 alias를 설정할 수 있지만, 기본적으로 origin을 사용한다.
- 처음 리포지토리 생성 시 default 브랜치명이 master로 되어있을 것이다. 보편적으로 main이라는 이름으로 많이 사용하기 때문에, -M 옵션을 주고 main이라는 이름으로 default 브랜치명을 변경한다.
- 이제 원격 리포지토리에 로컬 작업 내역을 올릴 수 있다.
2. 원격 -> 로컬 방식
원격 리포지토리 clone
다음과 같은 방식으로 원격에 올려진 리포지토리를 로컬로 가져와서 사용할 수 있다.
먼저 원하는 원격 저장소에서 Code를 클릭한다.
SSH 탭에서 하단의 주소를 복사한다.
그리고 git bash에서 다음 명령어를 통해서 clone을 한다.
git clone SSH주소
이제 원격 리포지토리의 모든 내용을 가져와서 사용할 수 있다.
이 경우에는 이미 .git 파일이 존재하기 때문에, git init 명령어를 사용하지 말고 그대로 사용하면 된다.
Git 명령어 정리
원격 alias, 주소 확인
git remote
git remote -v // 원격 alias, 원격 주소 함께 확인
원격 주소 추가
git remote add 원격alias url // 원격 주소 추가
git remote set-url 원격alias url // 원격 주소 수정
git remote set-url --push alias url // 원격 push 주소만 변경
push 주소는 로컬의 내용을 원격으로 올리기 위해 사용되고, fetch 주소는 원격의 내용을 가져오기 위해 사용된다.
같은 원격alias라도 fetch 주소와 push 주소가 다를 수 있다. (기본적으로는 같으나, 의도적으로 달리하기가 가능.)
원격 주소 삭제
git remote remove alias // 원격 주소 삭제
브랜치 목록 확인
git branch -r // 모든 원격 브랜치 확인
git branch (-l) // 모든 로컬 브랜치 확인
git branch -a // 모든 원격 + 로컬 브랜치 확인
로컬 브랜치 생성 및 전환
git checkout -b 새브랜치명 기준브랜치명
// working directory 바꾸기
// -b 옵션 -> 브랜치를 복사 및 생성
// 기준 브랜치를 복사하여, 새 브랜치명으로 브랜치를 생성하고 새 브랜치로 전환한다.
// 커밋 로그는 복제 대상이 되는 브랜치의 로그를 따름.
git checkout -b 새브랜치명
git checkout -c 새브랜치명
// 기준 브랜치명을 주지 않는 경우, 현재 있는 브랜치를 복사하여 새 브랜치를 생성하고 전환한다.
// -b와 -c는 기능적으로 동일함.
git checkout -b 새브랜치명 원격alias/기준브랜치명
// 원격 브랜치를 복사하여 로컬에 새 브랜치 생성 후 전환한다.
git checkout 브랜치명
// 이미 존재하는 브랜치로 전환
브랜치 삭제
git branch --delete 브랜치명
// 로컬에서 브랜치 삭제하기
// 반드시 삭제하려는 브랜치가 아닌 다른 브랜치로 checkout 해야만 삭제 가능
git push --delete 원격alias 브랜치명
// 원격 브랜치 삭제
// git branch -r에서 나오는 브랜치명으로 입력. ('remotes/' 포함하지 않음. ex) origin/main-1)
Fetch
Fetch는 원격 저장소의 추가되거나 수정된 브랜치를 모두 가져오는 명령어이다.
git fetch
// 원격의 모든 브랜치 가져오기
git fetch -p
// 원격에서 삭제된 브랜치 반영 후, 로컬로 가져오기
// prune 옵션
- prune
- 로컬에서 쓸모없는(원격에 더 이상 존재하지 않는) 브랜치들은 삭제하고 fetch함.
- 만약 원격의 브랜치가 삭제되면, 반드시 -p 옵션을 주어서 fetch를 해야 로컬에서도 삭제 가능
Pull
Pull은 fetch 후 merge를 시도하는 명령어이다.
git merge
- HEAD : 현재 내가 위치한 브랜치의 가장 마지막. 로컬 브랜치의 최상위 커밋
- FETCH_HEAD : 원격 브랜치 레퍼런스의 최상위 커밋
- REMOTE HEAD => remotes/origin/HEAD -> origin/main
- merge : 다른 브랜치와 현재 브랜치를 병합하는 것이다.
HEAD와 FETCH_HEAD가 다른 경우에 conflict가 발생한다. both modified 된 파일들에 대하여 conflict를 해결해야만 merge가 가능하다.
- fetch : 추가되거나 수정된 브랜치를 모두 가져온다.
Pull과 Pull Request
- Local
- PULL = FETCH + MERGE : 하나의 MERGE 커밋을 만든다.
- REMOTE
- PULL REQUEST = FETCH + MERGE
Conflict 해결 방식
- Rebase
- 상대방의 작업을 존중한다. 그 위에 다시 내 커밋을 만들어 쌓아 합친다. 커밋을 보기는 깔끔해진다.
- 단점 : 내가 작업했던 커밋들이 다시 생성되기에, 히스토리가 리셋된다.
- 리베이스를 하게 되면, 커밋 히스토리가 몰아서 기록됨. (꾸준함이 성과 평가에 큰 기준이 된다면, 리베이스를 함부로 하지 않기)
- Merge
- 두 작업을 유지한 채 합쳐졌다는 것을 의미하는 Merge 커밋을 추가하여 합친다.
- 단점 : Merge는 쓰레기 커밋이 나올 수밖에 없다.
- 3way Merge Commit의 단점 => Merge commit이 반드시 남기 때문에 로그 보기가 힘들다.
- Merge 전략 3가지
- 2-1. fast-forward
- --ff 옵션
- HEAD를 FETCH_HEAD로 돌리기 (FETCH_HEAD를 바라보도록)
- 2-2. 3-way merge
- --no-ff 옵션
- 충돌 발생 시 충돌을 해결한 merge commit 생성 (쓰레기, 부산물)
- 3발. 충돌 커밋 2개와 머지 커밋 1개
- 2-3. squash and merge
- --squash 옵션
- 상대방의 원격의 작업을 모두 부순다. 그리고 그 작업들을 모두 뭉쳐서 내 바로 다음 커밋으로 만든다.
- 2-1. fast-forward
- Rebase
- 부모 브랜치의 최신 HEAD를 그대로 둔 채, 그 기점부터 새로 commit 쌓기
merge를 롤백하기 위해서는 다음 명령어를 사용할 수 있다.
git merge --abort
merge head => merge 하기 위한 커밋을 가리키고 있음
rebase 커밋 갯수만큼
rebase 옵션
- continue
작업 다 끝났으니 바로 다음 작업을 조회 - skip
이 작업 안한다 다음 작업으로 skip - abort
포기
rebase 시 conflict 난 커밋은 시간 바뀌고, conflict 나지 않은 커밋은 시간 안바뀜
임시 저장
stash = 임시 저장
커밋을 원하지는 않으나, 당장에 다른 브랜치에서 작업이 필요한 경우 stash를 사용해 임시 저장한다.
git stash // 임시 저장
git stash -m "원하는 메세지" // 스태시 메세지 남기기
git stash list // stash 목록을 보기
git stash apply // stash 적용 (뒤에 번호 명시하지 않으면, 가장 최근 stash 적용)
git stash drop // stash 삭제 (뒤에 번호 명시하지 않으면, 가장 최근 stash 삭제)
git stash pop // stash 적용 후 삭제 (뒤에 번호 명시하지 않으면, 가장 최근 stash 적용 후 삭제)
커밋 접근 (절대, 상대 표현법)
커밋 접근은 해시 값을 사용하는 절대 표현법과, HEAD를 사용하는 상대 표현법이 존재한다.
- 절대 표현법
- git log - 전체 해시 값
- git reflog - 부분 해시 값
- 상대 표현법 (HEAD)
- ~ : 조상
- ^ : 부모
- @ : reflog 기반
git show
커밋 메세지 + 상세 수정 내용 기반 조회
명령어 뒤에 커밋지정을 하여 특정 커밋을 조회할 수 있으며, 지정하지 않는 경우 가장 최근 커밋을 조회한다.
커밋 로그 확인
git log
git log를 사용하여 커밋 메세지 기반의 커밋 내역을 확인할 수 있다. 커밋의 전체 hash를 포함한다.
Reflog
git reflog
Reference Log의 약어이다.
이전의 모든 HEAD들을 기록한다. 해시값, 커밋 메세지 등을 확인할 수 있다.
단, reflog의 항목들은 일정 시간이 지나면 삭제된다. 커밋의 수정이나 접근 시간으로부터 기본적으로는 90일의 보존 기간을 가진다.
git config --global gc.reflogExpire <시간>
git config --global gc.reflogExpireUnreachable <시간>
위 명령어를 통해서 reflog의 expire time을 조정할 수 있다.
- 일 단위: 숫자.days
- 시간 단위: 숫자.hours
- 분 단위: 숫자.minutes
- 년 단위: 숫자.years
HEAD 상대 표현법
RESET, Rebase 등의 명령에서 특정 커밋에 접근하기 위한 표현 방법으로 HEAD 상대 표현법을 사용할 수 있다.
- HEAD
- ~ (Tilde) : N번째 조상 - 수직
- ex) HEAD~1 가장 최근 커밋. 수직 방향으로 이동
- ^ (Caret) : N번째 부모 - 수평
- ex) 여러 커밋이 병합된 3way merge와 같은 상황에서, N번째 부모를 찾는 경우.
- @ (At Sign) : Reflog 기반. N번 이전 HEAD
- ~ (Tilde) : N번째 조상 - 수직
단일 커밋 삭제
git RESET
단일 커밋 수정
커밋에서 파일을 빠뜨린 경우, 혹은 커밋 메세지 수정이 필요한 경우에 새 커밋을 만들기보다는 다음 방법을 사용하자.
git commit --amend
// => 직전 커밋에 파일을 합쳐줌. 어맨드 시 커밋 메세지를 고치느냐고 물어봄
그러나 원격에 이미 push한 경우에는 amend는 권장되지 않는다.
=> 원격의 HEAD와 커밋 히스토리가 달라져서 push 불가능. pull 한 후 conflict 해결 후 다시 push 해야 한다.
=> 차라리 새로운 커밋을 만드는 것이 나음.
다수 커밋 수정
git rebase -i 커밋지정 // 커밋은 상대 표현법 혹은 절대 표현법(hash) 사용하여 명시
git rebase --continue // 특정 커밋 수정 완료 후, 다음 커밋 수정으로 넘어가는 명령어
만약 rebase 시 HEAD~4를 지정한다면, 위 사진과 같이 HEAD로부터 4개 이전 커밋까지 보여준다.
각각의 커밋은 처음에 pick이라는 키워드가 쓰여있고 수정을 원하는 커밋의 pick 자리에 다음과 같이 키워드를 넣어서 저장 후 수정을 시작할 수 있다. (vi 사용) 원하는 커밋들만 선택하여 수정하는 Cherry-Picking이 가능하다.
- reword : 커밋 메세지 수정
- drop : 해당 커밋을 삭제
- edit : 커밋 수정 내용 다시 수정하여 커밋.
- continue 시 conflict 발생하므로, merge 하려면 충돌(conflict)이 발생한 파일에 들어가서 충돌 해결 후, add .
- squash : 커밋 두개 합치기. 특정 커밋에 squash 하면, 해당 커밋의 이전 커밋과 합쳐짐
RESET 명령어
- --mix : add, commit 전으로
- --soft : add는 되었지만 commit 전으로
- --hard : 아예 모든 수정을 없애고 직전 커밋 상태로 되돌림
아무 옵션도 주지 않는 경우, mixed 옵션이 기본적으로 적용된다
커밋 복구
git reflog // 원하는 커밋의 해시값 복사
git checkout 해시값 // 임시 브랜치로 전환
git switch -c main-backup // 새 브랜치에 복사
git checkout main
git merge main-backup // 다시 main으로 checkout 후, merge
커밋 롤백
되돌리는 방법은 2가지
- git restore --staged
- Staging Area → Unstaged (git add 이전)
- 만약 new file이라면, git restore --staged 했을 때 untracked로 돌아감
- git restore
- Unstaged → (Unmodifed) (수정 이전)
- 아예 이전 커밋으로 돌아가기
파일 삭제
git rm 파일명 => staging area로 돌아감
rm 파일명 => unstaged로 돌아감
git rm --cached 파일명 => untracked로 돌아감
원격에 이미 push한 경우에는 원격에 남아있다.
그런 경우엔 Github 공식적으로 BFG Repo-Cleaner 를 통해 파일과 히스토리를 삭제할것을 권유
=> https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/removing-sensitive-data-from-a-repository#using-the-bfg
브랜치 및 테스트 분류
브랜치 분류
- production : = main, master. 가장 문제없이 완벽한 코드.
- 오류 발생 시 바로 롤백할 수 있는 상태들을 가지고 있는 완벽한 브랜치이다.
- staging : 배포 직전 develop보다 큰 환경에서 테스트를 위해 사용하는 브랜치.
- 쿼리 속도 체크, 예측하지 못한 오류를 발견할 수 있다.
- 프로젝트 규모가 큰 경우 대부분 스테이징 존으로 마지막 테스트를 하게 된다.
- develop : 실제 배포 전에 테스트 과정에 사용하는 브랜치. feature를 merge하게 된다. 가장 최신 상태 코드.
- feature : 기능을 만들 때 사용하는 브랜치. local 개발 시 사용한다.
feature 브랜치를 만들 때 기반이 되는 브랜치는 develop이어야 한다. main이면 안됨
가장 최신의 브랜치는 develop이기 때문에. main은 develop이 완료되고 나서 merge되기 때문에 develop이 더 최신 버전이다.
테스트 분류
- local 테스트 : 로컬 환경에서 내가 테스트
- develop 테스트 : 남이 테스트
- staging zone 테스트 : 남이 테스트
- local, develop의 테스트 환경은 private network에 숨겨져 있다.
- production 실제 배포 환경은 public network로 노출된다.
develop과 staging 차이
=> 환경의 차이가 존재한다.
- 네트워크 및 인프라 스트럭쳐 (database, 정적 리소스 저장소(스토리지), dns)
- 데이터의 양
데이터의 양 비율은 대략 다음과 같다.
local : develop : production = 1 : 10 : 10000
데이터 양의 차이 때문에 실제 production 과정에서 오류가 발생할 수 있다.
develop 과 production 간의 격차 때문에, staging 존에서 격차 해결을 위해 마지막 테스트를 거치게 된다.
local : develop : staging : production = 1 : 10 : 1000 : 10000
staging zone에서는 쿼리 속도 체크하고, 예측하지 못한 오류를 발견할 수 있다.
이처럼 git flow와 zone은 연결되어 있다.
staging을 먼저 배포한 후에, 3일간 테스트 후 안정적이면 main으로 배포하는 경우도 있다.
이처럼 데이터 양이 많아지면, 수많은 경우의 수를 cover하게 되며 test coverage 자체가 올라간다.
local 데이터베이스 테스트
- 개발자 간 데이터베이스 내 데이터 일관성을 위해 migration sql 구문을 주고받음. 동일한 데이터 기반 실행
ex) git에 올리거나 해서 버전 관리 - 혹은 csv 파일을 주고 받을 수 있으나, 파일 크기가 크다.
초기 개발 단계에서는 로컬로 데이터베이스 사용하고, 이후에 develop zone에 데이터베이스 두고 해당 인스턴스를 공용으로 사용한다.
zone 마다 인스턴스, 버전이 다르기도 하다. production zone에서는 안정성을 위해 구버전 db 인스턴스 사용
on-premise로 사용할 일은 없다. DB도 aws rds와 같은 클라우드 서비스 사용
develop 데이터베이스 테스트
로컬에서 최대한 테스트케이스 많이 만들어서 테스트 하고 develop에 올리는 것이 좋다. (최대한 완성된 코드)
테스트의 종류
- Unit Test : 함수 하나하나당 테스트
- Integration Test : 통합 테스트
- Stress Test : 부하 테스트. 트래픽이 몰렸을 때 서버가 죽지 않는지 확인. 따로 툴이 존재한다.
백엔드의 Integration Test
- Postman 사용
- 브라우저 사용(프론트엔드와 연결하여)
test 시 usecase에 대한 문서가 존재해야 한다.
프론트엔드의 API Test
프론트엔드 개발자가 매번 백엔드와 소통하여 테스트 하는 것은 비효율적이다.
그래서 API 명세를 위해서 사용하는 것이 Swagger이다. Swagger는 request, response를 통틀어 명세한다.
요즘에는 Postman의 Document도 자주 사용한다.
- 필드가 삭제되는 경우
- => 새로운 api를 만들어야 한다.
- v1 -> v2 새로 만들기
- v1와 v2가 공존
- 필드가 추가되는 경우
- => 기존 api를 사용해도 됨
- 프론트엔드가 변경 사항에 대해서 알지 못해도 오류가 발생하지 않는다.
Sanitize와 Sync
Staging Zone에서는 Sanitize와 Sync 과정을 거친다.
- production zone의 데이터들을 sanitize(개인 정보 보호 문제 때문에, 정보를 사람이 읽을 수 없게끔 지움(암호화))
- 그리고 sanitize 된 정보들을 sync(동기화)
vpc => virtual private cloud. 역시 develop, staging, production 마다 분리됨
모두 private vpc를 사용함. (production vpc는 게이트웨이를 사용하여 production으로 연결)
ticket
작은 작업 단위들을 task 혹은 ticket이라고 한다. (Github에서는 issue로도 표현)
ticket을 하나하나 발행
ticket 관리를 위해서 Notion, Atlassian, Github Project 등의 툴들을 사용한다.
각각의 티켓에 대해서 로컬 브랜치를 만들어 작업한다.
- 로컬 브랜치 => feature/티켓명
- 브랜치명에 개발자 이름을 같이 쓰기도 한다.
- ex) aaron/login-with-oauth
커밋 메세지 규칙
- 앞에는 기능을 붙인다. ex) feat, fix, hotfix ...
- 어떤 작업인지 요약 제목
- 세부 작업이 있는지 나열하는 것이 좋다.
'ASAC' 카테고리의 다른 글
[ASAC 06] 자바스크립트 일급함수, Hoisting, Scope Chaining (0) | 2024.10.16 |
---|---|
[ASAC 06] AWS VPC와 서브넷 구성 / Bastion Tunneling, NAT (6) | 2024.10.08 |
[ASAC 06] 세션과 JWT, CORS 관련 질문 정리 (0) | 2024.09.12 |
[ASAC 06] Shell, Linux 명령어 (0) | 2024.09.12 |
[ASAC 06] CORS (1) | 2024.09.11 |