Git_Day.2

조현기·4일 전

Git_Day.2

브랜치(생성, 전환, 삭제, 병합), GitHub 같은 원격 저장소 연결(remote, push), 원격 변경 가져오기(fetch, pull), merge와 rebase의 차이.

Day.1에서는 내 컴퓨터 안(로컬)에서만 커밋하고 되돌렸다. 오늘은 브랜치로 작업 흐름을 나누고, 원격 저장소(GitHub)와 연결해서 올리고 내려받는 것까지 배웠다.


1. 브랜치

브랜치(branch)는 커밋 이력에서 갈라져 나온 작업 흐름이다. 기능을 만들거나 실험할 때 원래 흐름(main)을 건드리지 않고 따로 작업하고, 끝나면 합친다.

1-1. 목록, 생성, 전환

$ git branch                 # 브랜치 목록 (* 가 현재 브랜치)
* main

$ git branch login           # 현재 커밋을 기준으로 'login' 브랜치 생성
$ git branch
* main
  login

$ git switch login           # login 브랜치로 전환
$ git branch
* login
  main
명령어의미
git branch로컬 브랜치 목록을 표시. *가 붙은 것이 현재 브랜치
git branch 이름현재 커밋을 기준으로 새 브랜치 생성 (전환은 하지 않는다)
git switch 이름해당 브랜치로 전환 (작업 디렉토리의 파일이 그 브랜치 상태로 바뀐다)
git switch -c 이름브랜치 생성과 동시에 전환 (-c는 create)

브랜치를 만들 때 git branch와 git switch를 따로 하는 것보다 git switch -c가 한 번에 끝나서 편하다.

1-2. 브랜치마다 이력이 따로 쌓인다

login 브랜치에서 커밋하고, main으로 돌아와 다른 커밋을 해 보았다.

$ git switch login
$ echo l > login.txt && git add . && git commit -m "B: login"

$ git switch main
$ echo m > m.txt && git add . && git commit -m "C: main"

main으로 돌아오면 login.txt는 보이지 않는다. 브랜치마다 자기 커밋까지의 파일 상태만 보이기 때문이다.

$ git log --oneline --branches
cdec193 B: login
d6eb0bc C: main
e15eebc A: a.txt

$ git log --oneline --branches --graph
* cdec193 B: login
| * d6eb0bc C: main
|/
* e15eebc A: a.txt
명령어의미
git log --oneline --branches모든 브랜치의 커밋을 한 줄씩 표시
git log --oneline --branches --graph모든 브랜치의 커밋을 그래프 형태로 표시

--graph를 붙이면 A에서 두 갈래(B: login, C: main)로 갈라진 모양이 눈에 보인다. Day.1에서 "--branches는 브랜치가 여러 개일 때 의미가 생긴다"고 했는데, 이렇게 브랜치가 여러 개일 때 결과가 달라진다.

1-3. 커밋이 없을 때는 브랜치를 만들 수 없다

git init만 하고 커밋이 하나도 없는 상태에서 git branch 이름을 하면 에러가 난다.

$ git branch test
fatal: not a valid object name: 'main'

브랜치는 "어떤 커밋을 기준으로" 만들어지는데, 기준이 될 커밋이 없기 때문이다. 이때는 아래 두 명령어가 에러 없이 동작한다.

$ git switch -c test       # 브랜치 생성과 전환을 동시에 (git switch + create)
Switched to a new branch 'test'

$ git checkout -b feat     # 마찬가지로 생성과 전환을 동시에
Switched to a new branch 'feat'

git checkout 이름은 브랜치 전환(또는 특정 커밋으로 이동)을 하는 예전부터 쓰던 명령어다. -b를 붙이면 git branch를 같이 실행한다. 지금은 브랜치 전환 기능만 떼어 낸 git switch를 권장한다. 둘이 같은 일을 하는 부분이 있으니 둘 다 알아 두면 된다.

1-4. 병합과 삭제

$ git switch main
$ git merge login -m "merge login"       # login의 작업을 main에 합친다

병합 후에는 쓴 브랜치를 삭제한다.

$ git branch -d login         # 병합된 경우에만 삭제
Deleted branch login (was cdec193).
명령어의미
git branch -d 이름병합된 경우에만 삭제 허용. 병합되지 않았으면 삭제를 거부한다
git branch -D 이름병합 여부와 관계없이 무조건 삭제

병합하지 않은 브랜치에 -d를 쓰면 이렇게 거부한다.

$ git branch -d wip
error: the branch 'wip' is not fully merged.
If you are sure you want to delete it, run 'git branch -D wip'

$ git branch -D wip
Deleted branch wip (was aebc13c).

-D는 합치지 않은 커밋이 사라질 수 있으니 정말 필요 없는 브랜치에만 쓴다. 평소에는 -d가 안전장치 역할을 한다.


2. 원격 저장소 (GitHub)

로컬 저장소만 있으면 내 컴퓨터가 고장났을 때 이력도 같이 사라지고, 다른 사람과 공유할 수도 없다. 원격 저장소(GitHub 같은 서버에 있는 저장소)에 올려 두면 백업과 협업이 된다. (GitHub 저장소를 만드는 화면 설명은 원본 노트의 다른 페이지에 있어서 여기서는 명령어 중심으로 정리한다.)

아래 실습은 GitHub 대신 내 컴퓨터 안의 빈 원격 저장소(remote.git)를 만들어서 같은 방식으로 진행한 결과다. 명령어는 GitHub에 연결할 때와 같고, 주소(URL)만 다르다.

2-1. 연결하기

$ git branch -M main                                    # 현재 브랜치 이름을 main으로 바꾼다
$ git remote add origin https://github.com/계정/저장소.git
$ git remote -v                                         # 등록된 원격 저장소 확인
origin	https://github.com/계정/저장소.git (fetch)
origin	https://github.com/계정/저장소.git (push)
명령어의미
git branch -M 이름현재 브랜치를 입력한 이름으로 바꾼다 (이미 있어도 강제로)
git remote add origin URL로컬 저장소에 origin이라는 이름으로 원격 저장소 URL을 연결
git remote -v등록된 원격 저장소를 URL과 함께 표시 (보통 연결 확인용)

origin은 원격 저장소에 붙이는 관례적인 별명이다. 매번 긴 URL을 쓰지 않으려고 이름을 붙여 둔 것이다.

2-2. 올리기: git push

$ git push -u origin main
To /tmp/g2/remote.git
 * [new branch]      main -> main
branch 'main' set up to track 'origin/main'.

git push -u origin main은 로컬의 main 브랜치를 원격 origin에 올리고, 업스트림 브랜치로 설정한다. -u가 업스트림 설정이다.

업스트림 브랜치: 로컬 브랜치가 추적하는 원격 저장소의 브랜치. 로컬 브랜치와 연결된 원격 브랜치를 말한다.

연결된 상태는 git branch -vv로 볼 수 있다.

$ git branch -vv
* main 1128a71 [origin/main] merge login        # main 이 origin/main 을 추적 중
명령어의미
git push -u origin mainmain을 origin에 푸시하고 업스트림으로 설정 (처음 한 번)
git push origin mainmain을 origin의 main으로 푸시 (브랜치를 직접 지정)
git push현재 브랜치를 설정된 업스트림으로 푸시. 업스트림이 설정돼 있어야 쓸 수 있다
git push -f origin main원격 저장소를 로컬 상태로 강제로 덮어쓴다

업스트림이 없는 새 브랜치에서 git push만 하면 이런 에러가 난다.

$ git switch -c nb
$ git push
fatal: The current branch nb has no upstream branch.
To push the current branch and set the remote as upstream, use

    git push --set-upstream origin nb

$ git push -u origin nb          # -u 로 한 번 연결하면 이후에는 git push 만 쓰면 된다

2-3. push -f는 위험하다

로컬 이력이 원격과 달라서 git push가 거부될 때 -f로 밀어붙이면 원격의 커밋이 사라진다.

$ git push origin main            # 거부된다 (원격에 내 로컬에 없는 커밋이 있음)
hint: its remote counterpart. If you want to integrate the remote changes,
hint: use 'git pull' before pushing again.

$ git push -f origin main
 + 007b801...3360ff6 main -> main (forced update)

forced update라는 표시와 함께 원격의 007b801 커밋이 사라지고 3360ff6으로 덮어써졌다. 혼자 쓰는 저장소에서 내 이력을 정리할 때가 아니면 쓰지 않는다. 다른 사람이 같이 쓰는 브랜치에는 특히 쓰면 안 된다. 거부될 때는 먼저 git pull로 원격 변경을 가져와서 합치는 것이 정석이다.


3. 원격 변경 가져오기: fetch, pull

다른 컴퓨터(home)에서 파일 t3.txt를 추가하고 원격에 푸시했다고 하자. 내 컴퓨터(demo)는 아직 그 사실을 모른다.

3-1. git fetch: 가져오기만 한다

$ git fetch
From /tmp/g2/remote
   1128a71..ba806d4  main       -> origin/main

$ ls -al
a.txt  login.txt  m.txt          # t3.txt가 보이지 않는다
$ git log --oneline
1128a71 merge login               # 내 로컬 이력은 그대로
$ git status
On branch main
Your branch is behind 'origin/main' by 1 commit, and can be fast-forwarded.

git fetch는 원격 저장소의 최신 상태를 가져오기만 하고 내 브랜치에는 반영하지 않는다. 그래서 파일은 그대로고, git status가 "원격보다 1커밋 뒤처져 있다(behind)"고 알려 준다. 가져온 내용은 origin/main이라는 이름으로 따로 보관된다.

가져온 것과 내 것의 차이는 git diff로 비교한다.

$ git diff HEAD origin/main       # 현재 최신 커밋(HEAD)과 원격에서 가져온 커밋의 차이
diff --git a/t3.txt b/t3.txt
new file mode 100644
+t3

필기에는 git diff만으로 "최신 커밋과 원격에서 가져온 커밋의 차이를 본다"고 적어 두었는데, 인자 없는 git diff는 작업 디렉토리의 수정 내용(스테이징 전)을 보여 준다. 위 상황에서 git diff만 치면 아무것도 안 나온다. (출력 줄 수 0) git diff HEAD origin/main처럼 비교할 두 대상을 적어야 원격과의 차이가 나온다.

3-2. git merge: 가져온 변경을 합친다

$ git merge origin/main           # 원격에서 가져온 커밋을 로컬 브랜치에 합친다
 t3.txt | 1 +
 1 file changed, 1 insertion(+)

$ git log --oneline
ba806d4 D: t3.txt (home)
1128a71 merge login
...
$ ls
a.txt  login.txt  m.txt  t3.txt  # 이제 t3.txt가 보인다

3-3. git pull: fetch + merge

$ git pull origin main

git pull은 git fetch와 git merge를 한꺼번에 해 준다. origin의 main에서 최신 변경 사항을 가져와 현재 브랜치에 합친다.

명령어동작
git fetch원격 변경을 가져오기만 (로컬 브랜치는 그대로)
git merge origin/main가져온 변경을 로컬 브랜치에 합침
git pullfetch + merge 를 한 번에

pull/push의 기준은 로컬이다. push는 로컬에서 원격으로 보내고, pull은 원격에서 로컬로 가져온다.

pull은 편하지만 바로 합쳐 버린다. 가져오기 전에 먼저 어떤 변경이 있는지 보고 싶을 때는 fetch → git diff HEAD origin/main → merge 순서로 나눠서 하면 된다.

참고: echo로 파일 만들기

필기에 echo : "HI" > t1.txt라고 적어 둔 부분이 있다. 이렇게 쓰면 :도 같이 들어가서 : HI가 출력됐다. 실습용 파일을 만드는 echo "HI" > t1.txt로 쓰면 파일 내용이 HI다.

  • >는 파일을 새로 쓴다(덮어쓴다).
  • >>는 파일 끝에 이어 쓴다.

4. merge vs rebase

두 브랜치의 작업을 합치는 방법이 두 가지다. 같은 상황에서 두 방법의 이력이 어떻게 다른지 확인했다.

상황: A에서 갈라져서
  me   브랜치: D
  main 브랜치: B → C

merge: 두 브랜치를 합치는 새 커밋(머지 커밋)을 만든다.

$ git switch me
$ git merge main -m "Merge main into me"
$ git log --oneline --graph
*   51e107b Merge main into me
|\
| * 2524baf C
| * 62e9ea1 B
* | bc7203a D
|/
* ebbd00c A

rebase: 내 브랜치의 시작점을 바꿔서, 최신 커밋 위에 내 커밋을 다시 이어 붙인다.

$ git switch me
$ git rebase main
Successfully rebased and updated refs/heads/me.
$ git log --oneline --graph
* f30f238 D
* 2524baf C
* 62e9ea1 B
* ebbd00c A
구분mergerebase
방식두 브랜치를 합치는 새 커밋(merge) 생성브랜치의 시작점을 최신 커밋 위로 옮겨 다시 이어 붙임
기존 커밋그대로 보존D가 bc7203a → f30f238로 새 커밋이 된다
로그 모양갈라졌다 합쳐지는 흔적이 남는다일직선

rebase 후 D의 해시가 바뀐 것처럼, rebase는 커밋을 다시 만드는 작업이다. 그래서 이미 push해서 다른 사람이 쓰고 있는 커밋을 rebase하면 이력이 어긋난다. 혼자 쓰는 내 브랜치를 정리할 때 쓰는 것이 안전하다. (rebase는 다음 노트에서 더 다룬다)


5. 명령어 정리

브랜치

명령어설명
git branch로컬 브랜치 목록
git branch 이름현재 커밋 기준으로 브랜치 생성
git switch 이름브랜치 전환
git switch -c 이름브랜치 생성 + 전환
git checkout 이름 / git checkout -b 이름브랜치 전환 / 생성 + 전환
git merge 이름해당 브랜치를 현재 브랜치에 병합
git branch -d 이름병합된 브랜치만 삭제
git branch -D 이름병합 여부와 관계없이 삭제
git log --oneline --branches --graph모든 브랜치를 그래프로 표시

원격 저장소

명령어설명
git remote add origin URL원격 저장소 연결
git remote -v연결된 원격 저장소 확인
git branch -M main현재 브랜치 이름을 main으로 변경
git push -u origin main푸시 + 업스트림 설정
git push업스트림으로 푸시 (업스트림 설정 필요)
git push -f origin main원격을 로컬 상태로 강제 덮어쓰기 (주의)
git fetch원격 변경을 가져오기만
git pull origin mainfetch + merge
git diff HEAD origin/main내 최신 커밋과 원격 커밋의 차이

마무리

  • 브랜치는 작업 흐름을 나누는 것이고, 합칠 때는 merge, 다 쓴 브랜치는 -d(병합된 것만)로 지운다. -D는 무조건 삭제라 신중하게 쓴다.
  • 커밋이 하나도 없으면 git branch 이름은 에러가 나지만, git switch -c와 git checkout -b는 된다.
  • 원격 저장소는 remote add origin URL로 연결하고, 처음에는 push -u로 업스트림을 설정해 두면 이후에는 git push만 쓸 수 있다.
  • fetch는 가져오기만, pull은 가져오기 + 합치기다. 합치기 전에 확인하려면 git diff HEAD origin/main을 쓴다.
  • push -f는 원격 이력을 덮어쓰므로 공유 브랜치에는 쓰지 않는다.
  • merge는 이력이 갈라지는 흔적을 남기고, rebase는 일직선으로 만들되 커밋을 새로 만든다.

Tags: Git GitHub branch merge rebase push pull fetch 원격저장소 개발자 학습기록

profile
난 뭘까

0개의 댓글