앞에서 커맨드를 사용해 저장소 생성, 커밋, 푸쉬하는 법을 다뤘습니다. 여기서는 브랜치 생성, 머지하는 방법을 설명합니다.

브랜치 확인

명령어를 통해 현재 어떤 브랜치가 있고 활성화된 브랜치가 무엇인지 확인할 수 있습니다.

$ git branch=

* master
(END)


현재 master 브랜치만 존재합니다. *가 붙어있는 브랜치가 현재 활성화된 브랜치라고 볼 수 있습니다.


옵션 -v를 추가하면 브랜치마다 마지막 커밋 메시지를 확인할 수 있습니다.

$ git branch -v

* master 48fc51f [ahead 1] test2
(END)


원격 저장소의 브랜치를 확인하려면 -r 옵션을 추가하면 됩니다.

$ git branch -r

  origin/HEAD -> origin/master
  origin/master
(END)


또한 현재 활성화된 브랜치를 기준으로 머지가 된 브랜치인지 아닌지 확인할 수 있습니다. 이 예를 들기 위해, test라는 브랜치에서 별도의 작업 후 커밋까지 완료를 했습니다.

$ git branch --merged

* master
(END)


test 브랜치에서의 작업 내용이 현재 master 브랜치에 머지가 되지 않아서 리스트에 나오지 않습니다. 위 명령어와 반대로 머지가 되지 않은 리스트 조회는 아래와 같이 요청합니다.

 $ git branch --no-merged

 test
(END)


위 test 브랜치는 머지가 되지 않은 목록으로 조회가 가능합니다.

브랜치 삭제

현재 활성화된 브랜치를 제외하고 삭제를 진행할 수 있습니다.

$ git branch

* master
  test
  test1
(END)

$ git branch -d test1

Deleted branch test1 (was 48fc51f).

$ git branch

* master
  test
(END)


test1은 현재 활성화된 브랜치와 코드가 동일하므로 삭제가 되지만 test 브랜치는 master브랜치와 머지가 되지 않아 해당 명령어로 삭제를 진행하면 에러 메시지를 출력합니다.

$ git branch -d test

error: The branch 'test' is not fully merged.
If you are sure you want to delete it, run 'git branch -D test'.


위 설명처럼 강제로 삭제하려면 -D 옵션을 사용해야 합니다.

$ git branch -D test

Deleted branch test (was 3d1be1a).

브랜치 생성

브랜치 생성 방법은 2가지가 있습니다.


브랜치를 생성한 다음, 생성한 브랜치로 이동하는 방법은 아래와 같습니다.

$ git branch test
$ git checkout test

Switched to branch 'test'


checkout 의 옵션인 -b를 사용해 생성 후, 바로 이동하도록 할 수 있습니다.

$ git checkout -b test1

Switched to a new branch 'test1'

브랜치 머지하기

git merge

머지란 다른 브랜치의 변경점을 현재 활성화된 브랜치에 합치는 의미입니다. 만약 활성화된 브랜치에 다른 브랜치를 머지하고 싶으면 아래와 같이 진행합니다.

$ git merge test

Updating de1f0ad..be3a368
Fast-forward
 test.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)


만약 머지를 하기 전, 변경사항을 확인하고 싶으면 아래와 같이 진행합니다.

$ git merge master test

diff --git a/test.txt b/test.txt
index 7d3340a..4439738 100644
--- a/test.txt
+++ b/test.txt
@@ -1,2 +1 @@
 머지 테ㅡ트
-test2에서 test1을 <S-F6마스터에서:
(END)


master 브랜치에서 test 브랜치를 머지하면 test2에서 test1을 <S-F6마스터에서:  문장이 제거된다는 의미입니다.


머지가 완료되면 원격 저장소에 아래와 같이 반영합니다.

$ git push origin master

Counting objects: 12, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (9/9), done.
Writing objects: 100% (12/12), 1.02 KiB | 523.00 KiB/s, done.
Total 12 (delta 0), reused 0 (delta 0)
To https://github.com/brownbears1/test.git
   6acb1a3..3262bf7  master -> master

git rebase

rebase를 사용하여 두 브랜치를 합칠 수 있습니다. rebase는 merge와 다르게 한 줄로만 커밋 메시지를 출력할 수 있습니다. 또한 master 브랜치에 test1 브랜치를 합친다 하면 아래와 같은 순서로 진행해야 합니다. 

$ git checkout test1
$ git rebase master

First, rewinding head to replay your work on top of it...
Applying: rebase 테스트

$ git checkout master
$ git rebase test1

First, rewinding head to replay your work on top of it...
Fast-forwarded master to test1.


rebase는 말 그대로 base 브랜치를 재구축하는 형태입니다. 위에서 test 브랜치를 활성화하고 rebase master를 진행하면 'master 브랜치를 바탕으로 현재 브랜치를 재구축' 이라는 의미입니다. 


merge와 rebase의 차이점

$ git log --graph


위 명령으로 브랜치의 커밋이나 머지를 그래프로 확인할 수 있습니다. 

아래는 merge 후 출력한 이미지 입니다.

master에서 test브랜치가 생성된 후, 'meerge test'라는 커밋 메시지로 커밋이 되었습니다. 이후 master 브랜치로 머지가 된 것을 확인할 수 있습니다. 여기서 보다시피 브랜치의 파생된 내역을 전부 확인할 수 있습니다.


반면 rebase를 진행하면 아래와 같이 보입니다.


rebase를 진행하면 브랜치의 base를 재구축하여 하나로 합치므로 브랜치의 파생내역을 볼 수 없습니다. 

위는 test1 브랜치에서 master 브랜치로 rebase한 다음, master에서 머지를 한 결과입니다. test1 브랜치에서 rebase를 한 시점에서 test1 브랜치가 master의 최신 커밋을 기반으로 재구축했기 때문에 위와 같은 순서를 보여줍니다. 이후, master 브랜치에서 test1브랜치를 merge 명령어로 합친다 해도, test1 브랜치의 base가 동일하기 때문에 1개의 라인으로 커밋 메시지를 확인할 수 있습니다.

파이썬에서는 2진수, 8진수, 10진수, 16진수로 쉽게 변환할 수 있도록 내장함수가 포함되어 있습니다. 하지만 3진수나 4진수와 같이 다른 진수로 변환하고자 하면 직접 구현을 해야 합니다. (조사를 했지만 파이썬에서는 따로 제공하지 않는 것으로 확인)

이러한 요구사항 때문에 아래에 2진수부터 16진수까지 변환하는 코드를 작성했습니다.

NOTATION = '0123456789ABCDEF'


def numeral_system(number, base):
    q, r = divmod(number, base)
    n = NOTATION[r]
    return numeral_system(q, base) + n if q else n


# 2진수
result = numeral_system(18, 2)
# 4진수
result1 = numeral_system(18, 2)
# 11진수
result2 = numeral_system(18, 11)

print(result)
print(result1)
print(result2)


# 10010
# 102
# 17


만약 16진수보다 더 큰 범위를 표현하고자 하면 아래와 같이 알파벳을 늘려주면 됩니다. (다만 16진수 이후 알파벳 표현식이 맞는지는 확실치 않음) 숫자 0~9까지 10개와 알파벳 26개를 더해 총 36진수까지 표현할 수 있습니다.

import string NOTATION = string.digits + string.ascii_uppercase def numeral_system(number, base): q, r = divmod(number, base) n = NOTATION[r] return numeral_system(q, base) + n if q else n result = numeral_system(18, 16) result1 = numeral_system(17, 18) result2 = numeral_system(36, 36) print(result) print(result1) print(result2) # 12 # H # 10


먼저 파이썬은 기본 10진수이기 때문에 다른 진수는 아래와 같이 접두어가 붙습니다.

  • 2진수: 0b
  • 8진수: 0o
  • 16진수: 0x

10진수에서 2진수, 8진수, 16진수 변환

bin(), oct(), hex() 내장함수 사용

파이썬에서 제공하는 내장함수를 사용하면 쉽게 변환 할 수 있습니다.

value = 60

b = bin(value)
o = oct(value)
h = hex(value)

print(b)
print(o)
print(h)


# 0b111100
# 0o74
# 0x3c


결과는 전부 문자열 타입입니다.

format() 내장함수 사용

forrmat() 내장함수를 사용하여 위 결과와 마찬가지로 변환할 수 있습니다.

value = 60


b = format(value, '#b')
o = format(value, '#o')
h = format(value, '#x')

print(b)
print(o)
print(h)


# 0b111100
# 0o74
# 0x3c


두 번째 인자에서 #을 제거하면 접두어가 빠진 결과로 나오게 됩니다. 변환된 값 그 자체만 필요하게 될 경우, 유용하게 사용할 수 있습니다.

value = 60


b = format(value, 'b')
o = format(value, 'o')
h = format(value, 'x')

print(b)
print(o)
print(h)


# 111100
# 74
# 3c

다른 진수 형태에서 10진수로 변환

2진수, 8진수, 16진수에서 10진수로 변환하는 방법입니다. 여기서 변환하고자 하는 진수의 타입은 문자열이며 반환되는 10진수 결과는 정수 타입입니다.

b = int('0b111100', 2)
o = int('0o74', 8)
h = int('0x3c', 16)

print(b)
print(o)
print(h)


# 60
# 60
# 60


여기서 int 함수의 첫 번째 인자는 변환하고자 하는 진수이고 두 번째 인자는 첫 번째 인자의 진수 형태입니다. 만약 진수 형태를 잘못 입력하면 에러가 발생합니다.

다른 진수 형태에서 다른 진수로 변환

2진수에서 8진수로 변환하는 것과 같이 변환하는 방법입니다. 여기서 변환하고자 하는 진수의 타입은 정수이며 반환되는 타입은 문자열입니다.

o=oct(0b111100)
h=hex(0b111100)
s=str(0b111100)

print(o)
print(h)
print(s)

# 0o74
# 0x3c
# 60

문자열.format() 를 사용한 진수 변환

문자열 타입에서 제공하는 format() 메소드를 사용하여 변환하는 방법입니다.

s = "2진수: {0:#b}, 8진수: {0:#o}, 10진수: {0:#d}, 16진수: {0:#x}".format(60)

print(s)

# 2진수: 0b111100, 8진수: 0o74, 10진수: 60, 16진수: 0x3c


위에서 설명한 내장함수 format()과 마찬가지로 #을 제거하면 접두어가 빠진 형태로 반환됩니다.

s = "2진수: {0:b}, 8진수: {0:o}, 10진수: {0:d}, 16진수: {0:x}".format(60)

print(s)

# 2진수: 111100, 8진수: 74, 10진수: 60, 16진수: 3c


외부 저장소를 fork를 하고 내용을 수정한 다음, 해당 저장소의 master에게 내가 변경한 사항을 확인하고 머지해 주세요 라는 의미로 pull request를 요청할 수 있습니다. 또한 원본 저장소에서 변경된 사항이 있으면 fork를 받은 내 저장소에는 반영이 되지 않으므로 동기화를 시켜줘야 합니다.

외부 저장소 fork하기


fork 할 저장소에 들어간 다음, star 버튼 옆에 있는 fork 버튼을 누릅니다.


organization이 여러개일 경우, 아래와 같이 팝업이 나오는데 fork할 위치를 지정해 줍니다.


fork에 성공하면 아래와 같이 계정에 새로운 저장소가 생성됩니다.


fork를 진행한 다음, 로컬에 해당 저장소의 파일을 내려받습니다.

$ git clone https://github.com/brownbears1/flit.git


Cloning into 'flit'...
remote: Enumerating objects: 67, done.
remote: Counting objects: 100% (67/67), done.
remote: Compressing objects: 100% (45/45), done.
remote: Total 3408 (delta 34), reused 46 (delta 22), pack-reused 3341
Receiving objects: 100% (3408/3408), 702.39 KiB | 953.00 KiB/s, done.
Resolving deltas: 100% (2290/2290), done.


pull request

pull request를 요청하기 전에 아래와 같이 작업을 진행해야 합니다.


위와 같이 fork 저장소를 clone 했다면 origin 이라는 이름에 fork 저장소가 지정되어 있습니다. 원본 저장소의 주소도 저장해야 하기 때문에 아래와 같이 명령어를 입력한 다음 확인합니다.

$ git remote -v
origin	https://github.com/brownbears1/flit.git (fetch)
origin	https://github.com/brownbears1/flit.git (push)


$ git remote add origin-flit https://github.com/takluyver/flit.git
$ git remote -v
origin	https://github.com/brownbears1/flit.git (fetch)
origin	https://github.com/brownbears1/flit.git (push)
origin-flit	https://github.com/takluyver/flit.git (fetch)
origin-flit	https://github.com/takluyver/flit.git (push)


파일 생성/변경이 전부 끝났다면 이제 fork 저장소에 push를 진행합니다.

$ git add .
$ git commit -m 'test'
$ git push origin master


push가 완료되었다면 이제 본인의 fork 저장소 페이지에 들어간 후, code 탭 옆의 Pull Request를 누른 다음 New Pull Request 버튼을 클릭합니다.



누르면 내가 push한 내용을 확인할 수 있고 원본 저장소의 어느 브랜치로 머지할지, 내 fork 저장소의 어느 브랜치로 pull request를 요청할지 고를 수 있습니다.


완료되었으면 pull request의 내용을 작성해야 하기 때문에 Create pull request 버튼을 클릭합니다.


제목과 내용을 작성한 후, Create pull request 버튼을 누르면 원본 저장소에 pull request 요청이 완료됩니다.

fork 저장소 동기화

위 순서대로 진행했으면 원 저장소의 주소가 입력되어 있지만 만약 아닐 시, 아래 명령어로 추가해 줍니다.

$ git remote add origin-flit https://github.com/takluyver/flit.git
$ git remote -v
origin	https://github.com/brownbears1/flit.git (fetch)
origin	https://github.com/brownbears1/flit.git (push)
origin-flit	https://github.com/takluyver/flit.git (fetch)
origin-flit	https://github.com/takluyver/flit.git (push)


다음 외부 저장소의 최신 내용을 가져옵니다.

$ git fetch origin-flit


다음 merge를 할 브랜치로 변경한 다음, 위에서 가져온 최신 내용을 merge 해줍니다.

$ git checkout master
$ git merge origin-flit/master


위 작업이 끝났으면 내 fork 저장소에도 반영을 해줍니다.

$ git push origin master


지금까지 gitlab이나 github을 사용할 때, 커맨드를 쓰지 않고 source tree에서 제공하는 GUI로 처리했습니다. GUI가 편리하긴 하지만, 프로젝트의 크기가 커지고 브랜치가 많아지는 경우 살짝 느려지는 경향이 있습니다. 이를 보완하고 git 커맨드에 친숙해지고자 커맨드를 사용하는 방법을 설명합니다.

git 저장소 생성

기존 폴더를 git 저장소로 추가

$ git init


Initialized empty Git repository in /.../test_git/.git/


test_git이라는 폴더를 저장소로 git 저장소로 추가한 방법입니다. 해당 폴더에 들어간 후, $ls -la | grep .git을 하면 .git 파일이 추가된 것을 확인할 수 있습니다. 로컬에서 .git을 추가한 것이므로 파일을 추가, 커밋, 푸쉬까지 이뤄져야 서버에도 반영이 됩니다. 해당 내용은 아래에서 천천히 설명합니다.

서버에 있는 저장소를 clone

gitlab이나 github에 있는 저장소를 로컬에 추가하고 싶을 때 사용합니다.

$ git clone https://github.com/brownbears1/test.git


Cloning into 'test'...
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), done.


test라는 프로젝트를 로컬에 내려받습니다. 이 때, 프로젝트명인 test라는 폴더에 생성이 됩니다. 만약 폴더명을 임의로 추가하고 싶으면 아래와 같이 지정하면 됩니다.

$ git clone https://github.com/brownbears1/test.git custom_test

파일 추가/수정 후 저장

위 test 프로젝트를 내려받았다면 자동으로 checkout이 된 상태입니다. 여기서 파일을 추가하고 수정한 다음, 해당 서버에 올리는 동작을 설명합니다.

tracked/untracked 이해하기

git에서 모든 파일은 tracked 와 untracked 상태로 나눌 수 있습니다.

tracked 상태는 스냅샷에 포함된 파일이고 unmodified(변경된 사항 없음), modified(변경됨), staged(커밋으로 저장소에 기록) 상태로 또 나눌 수 있습니다.

이외의 파일들은 untracked 상태이며 해당 상태의 파일들은 스냅샷에 포함된 것이 아니고 stage 대상도 아닙니다.

여기서 스냅샷의 의미는 git 저장소에 해당 파일이 있는지의 유무입니다. 스냅샷에 포함된 파일이라 하면, git 저장소에 존재하는 파일입니다.

unmodified 상태면 변경된 사항이 없는 상태이고

modified 상태면 git에 존재하는 파일의 내용을 수정한 상태이고 (이 상태는 배포 준비가 되지 않음)

staging은 modified의 상태에서 배포를 하겠다 라고 stage에 추가한 상태입니다. (배포시, 서버에 적용)

파일의 추가/수정 추적하기

프로젝트 내에 README.md 파일이 있어서 해당 내용을 수정한다고 가정합니다. 이후, 파일을 추적하기 전에 상태값을 확인하도록 합니다.

$ git status


On branch master
Your branch is up to date with 'origin/master'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

	modified:   README.md

no changes added to commit (use "git add" and/or "git commit -a")


여기서 git status는 변경/추가된 파일들을 보여줍니다. 위 상태는 README.md 파일에 변경이 있지만 아직 stage에 올라가지 않은 상태입니다. stage에 올리도록 아래와 같이 add를 한 후, 다시 status로 확인합니다.

$ git add README.md
$ git status
On branch master
Your branch is up to date with 'origin/master'.

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	modified:   README.md


Changes to be committed 문구와 같이 stage 상태로 올라간 것을 확인할 수 있습니다.

이번에는 새로운 파일인 test.txt를 만들고 위와 같이 똑같은 절차를 진행합니다.

$ git status


On branch master
Your branch is up to date with 'origin/master'.

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	modified:   README.md

Untracked files:
  (use "git add <file>..." to include in what will be committed)

	test.txt


$ git add test.txt
$ git status


On branch master
Your branch is up to date with 'origin/master'.

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	modified:   README.md
	new file:   test.txt


만약 위 stage 상태를 만든 다음, test.txt를 수정하게 된다면 파일의 상태가 unstage 상태로 됩니다. 이때, 수정한 내용은 커밋에 반영이 안되므로 다시 git add를 해야 합니다.


만약 생성/변경한 모든 파일을 stage로 추가하고 싶으면 아래와 같이 .을 사용합니다.

$ git add .

변경사항 커밋하기

위에서 설명한 것과 같이 stage 상태인 내용들을 커밋하도록 합니다.

$ git commit -m '커밋 테스트'

[master 457f83a] 커밋 테스트
 2 files changed, 2 insertions(+)
 create mode 100644 test.txt


여기서 커밋은 변경사항을 현재 내 컴퓨터에 저장하는 것이고 원격 저장소에는 반영이 되지 않은 상태입니다. -m '커밋 테스트' 에는 커밋 메시지를 작성하도록 합니다.


만약 git add 이후 stage 상태인 파일을 또 수정하게 된다면 다시 add를 해줘야 합니다. 이러한 번거로움을 덜고자 아래와 같이 사용하여 tracked 상태의 파일들이 변경사항이 있을 때, 자동으로 stage 상태로 추가하고 커밋하도록 할 수 있습니다.

$ git commit -am '커밋 테스트'

stage된 파일 제거하기

commit까지 이룬 상황에서 부득이하게 서버에 반영을 하지 않고자 하면 해당 파일을 제거해야 합니다. 

$ git rm test.txt


$ ll
README.md


위 명령어만 입력하면 stage 상태에서 제거되는 것 뿐만 아니라 실제 파일도 지워지게 됩니다. 아래 옵션을 사용하여 파일은 그대로 두되, git이 추적만 하지 않도록 진행할 수 있습니다.

$ git rm --cached README.md


rm 'README.md'


$ git status


On branch master
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	deleted:    README.md

Untracked files:
  (use "git add <file>..." to include in what will be committed)

	README.md

커밋된 내용 서버에 반영하기

커밋된 내용을 push 명령어를 사용해 원격 저장소에 반영할 수 있습니다. 

$ push -u origin master


Counting objects: 3, done.
Writing objects: 100% (3/3), 265 bytes | 265.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To https://github.com/brownbears1/test.git
   e4c4c0f..6acb1a3  master -> master
Branch 'master' set up to track remote branch 'master' from 'origin'.


여기서 origin은 원격 저장소의 이름이고 master는 브랜치 이름입니다. clone을 하면 보통 원격 저장소의 이름이 origin으로 지정됩니다. -u 옵션은 원격 저장소에서 업데이트를 받은 다음, push를 하는 의미입니다. 

이 옵션을 사용하는 이유는 해당 프로젝트를 복제한 사람들이 여러 명 있을 때, 다른 사람이 먼저 push를 하면 그 다음 사람들은 push를 할 수 없기 때문에 -u 옵션을 사용하여 최신 내용을 내려받아 머지한 다음, push를 해야 합니다.

이러한 이유 때문에 -u를 사용하지만 push를 하기 전, 프로젝트의 최신 내용을 먼저 pull 받은 다음 진행하는 것이 좋습니다.


만약 master에 반영하는 것이 아닌, 다른 브랜치에 반영을 하려면 master 대신 다른 브랜치의 이름을 지정하면 됩니다.


여기까지는 기존에 존재하는 원격 저장소를 복제한 다음 진행한 방법이고 새로운 폴더를 git 저장소로 추가한 경우 아래와 같이 원격 저장소의 주소를 추가해야 push가 가능합니다.

$ git remote add origin (원격 저장소 주소)

내용 되돌리기

모든 단계에서 생성/변경 한 파일을 되돌릴 수 있습니다. 여기서 주의할 점은 한 번 되돌린 내용은 다시 복구할 수가 없습니다.

commit 되돌리기

이미 커밋이 된 상태에서 커밋 메세지를 변경하거나, 다른 파일도 추가하여 커밋을 하고 싶을 때 사용할 수 있습니다.

$ git commit --amend -m '메시지'


만약 커밋을 한 다음, 다른 파일을 생성/추가가 안된 상태에서 위 명령어를 입력한다면 커밋 메시지만 변경한다는 의미입니다. 

unstage로 되돌리기

git add . 으로 모든 파일이 stage 상태가 되었는데 특정 파일을 unstage로 바꾼다고 할 때, 아래와 같이 사용할 수 있습니다.

$ git status


On branch master
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	modified:   README.md
	modified:   test.txt


$ git reset HEAD test.txt

Unstaged changes after reset:
M	test.txt


$ git status


On branch master
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	modified:   README.md

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

	modified:   test.txt

modified 상태 파일 원복하기

기존에 존재하는 파일을 수정했다가 최근 원격 저장소에서 내려 받은 파일로 원복하는 방법은 아래와 같습니다.

git checkout -- README.md


이 방법으로 초기 파일 상태로 돌릴 수 있지만, 수정한 파일 위에 덮어쓰이는 방법 때문에 수정한 이력이 날라가 다시 복원을 할 수 없습니다.

원격 저장소에서 최신 내용 내려받기

만약 누군가가 master에 내용을 변경해서 푸쉬를 했다면, 현재 내 로컬 저장소의 master와 내용이 다릅니다. 이를 맞춰주기 위해 원격 저장소의 브랜치를 내려 받아 로컬 저장소에 합칩니다.

$ git pull


pull 명령을 사용하면 원격 저장소의 변경 내용이 로컬에 받아지고(fetch), 이를 합칩니다. (merge)

충돌 관리하기

충돌은 같은 파일을 여러 사람이 수정한 다음, 한 명이 push하고 다음 사람이 push할 때 일어날 수 있거나 pull을 받을 때 발생할 수도 있습니다.


만약 충돌이 났다면 아래와 같은 메시지를 확인할 수 있습니다.

First, rewinding head to replay your work on top of it... 
Applying: 더 보기 버튼 추가 
Using index info to reconstruct a base tree... 
M index.html Falling back to patching base and 3-way merge... 
Auto-merging index.html 
CONFLICT (content): Merge conflict in index.html 
Failed to merge in the changes. 
Patch failed at 0001 더 보기 버튼 추가 
The copy of the patch that failed is found in: 
  /path/to/.git/rebase-apply/patch 

When you have resolved this problem, run "git rebase --continue". 
If you prefer to skip this patch, run "git rebase --skip" instead. 
To check out the original branch and stop rebasing, run "git rebase --abort".


여기서 핵심은 아래 3줄입니다. 충돌 문제를 해결했으면 git rebase --continue , 해당 파일을 건너뛰려면 git rebase --skip ,  중단하려면 git rebase --abort 의 명령어를 용도에 맞게 사용하면 됩니다.


충돌한 파일을 열어보면 아래와 같이 구성되어 있습니다.

<<<<<<< HEAD
  <div>
  <p> 내가 수정한 내용 </p>  
  </div>
=======
  <div>
  <p> 현재 원격 저장소에 누군가 올린 내용 </p>  
  </div>
>>>>>>>


<<<< HEAD 부터 ===== 까지가 내가 수정한 내용이고 ==== 부터 >>>>까지 누군가가 수정한 내용입니다. 이 부분은 사용자가 잘 판단하여 아래와 같이 정상적인 파일로 만들어 줍니다.

<div>
  <p> 내가 수정한 내용 </p>
</div>


충돌 해결을 했으면 서버에 반영해야 하므로 아래와 같이 명령을 진행합니다.

$ git add index.html
$ git rebase --continue


장고의 DRF를 사용하여 viewset을 구현하면 제목과 같은 기능이 필요할 때가 있습니다. 기본 ORM을 사용한다면 쉽지만 유효성 검사를 직접 구현해줘야 합니다. DRF의 serializer를 사용하여 처리를 하는 방식을 아래에서 설명합니다.

bulk create

bulk create는 큰 구현이 필요하지 않습니다. viewset에서 def list() 함수를 상속 받은 다음, 요청 데이터 타입이 list일 때만 serializer의 파라미터에 {'many': True}를 주면 끝납니다. 

class AdminViewSet(viewsets.ModelViewSet):
	def create(self, request, *args, **kwargs):
    	kwargs["many"] = isinstance(request.data, list)
	    serializer = self.get_serializer(data=request.data, **kwargs)
	    serializer.is_valid(raise_exception=True)
	    serializer.save()
	    return Response(serializer.data, status=status.HTTP_201_CREATED)


이후 해당 serializer 에서 특별한 작업없이 동작합니다.

bulk update

bulk update의 경우는 해당 serializer에 작업이 필요합니다. 아래는 create 부분과 같이 viewset의 def partial_update()를 수정합니다. patch 메소드를 사용하므로 {'partial': True} 이 들어가 있지만 put 메소드를 사용하여 def update()를 구현할 경우에는 필요없는 파라미터입니다. 

class AdminViewSet(viewsets.ModelViewSet):
	def partial_update(self, request, *args, **kwargs):
    	kwargs["partial"] = True
	    if kwargs.pop("pk", None):
    	    serializer = self.get_serializer(
        	    instance=self.get_object(), data=request.data, **kwargs
	        )
    	else:
        	kwargs["many"] = isinstance(request.data, list)
	        serializer = self.get_serializer(
    	        self.get_queryset(), data=request.data, **kwargs
	        )
    	serializer.is_valid(raise_exception=True)


/admin/ 과 같은 API가 있다고 가정할 때 위 코드는 /admin/4/ 와 같이 단일 수정 건이나 /admin/ 경로의 body에 다중 업데이트 건이 들어올 경우 수용할 수 있도록 처리한 코드입니다. 

예를 들어 아래와 같은 형식입니다.

단일 수정

PATCH: /admin/4/

{
"admin_name": "lee"
}


복수개 수정

PATCH: /admin/

[{
"admin_pk": "4",
"admin_name": "lee"
},
{
"admin_pk": "5",
"admin_name": "park"
},
{
"admin_pk": "7",
"admin_name": "kim"
}]


다음은 해당 serializer파일에 아래와 같은 코드를 추가합니다.

import inspect
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
from rest_framework.exceptions import NotFound


class BulkListSerializer(serializers.ListSerializer):
    def update(self, queryset, validated_data):
        id_attr = getattr(self.child.Meta, 'update_lookup_field')
        update_data = {i.get(id_attr): i for i in validated_data}

        if not all((bool(i) and not inspect.isclass(i) for i in update_data.keys())):
            raise NotFound(_('Could not find all objects to update.'))

        objects = queryset.filter(**{'{}__in'.format(id_attr): update_data.keys()})

        if len(update_data) != objects.count():
            raise NotFound(_('Could not find all objects to update.'))

        ret = []
        for id, data in update_data.items():
            for obj in objects:
                if str(getattr(obj, id_attr)) == str(id):
                    ret.append(self.child.update(obj, data))

        return ret


다음 다중 업데이트를 처리할 serializer의 class Meta: 부분에 아래를 추가합니다.

class AdminSerializer(serializers.ModelSerializer):
    admin_pk = serializers.IntegerField()
    
    class Meta:
        model = Admin
        fields = '__all__'
        update_lookup_field = 'admin_pk'
        list_serializer_class = BulkListSerializer


class Meta: 하위의 update_lookup_field는 pk 변수명을 작성합니다. 만약 API에서 pk가 아닌 다른 유니크한 키를 기준으로 업데이트를 하려고 하면 해당되는 변수명을 작성하면 됩니다.

그 아래의 list_serializer_class에는 위에서 정의한 클래스 명을 작성합니다. 


bulk update는 위와 같은 순서만 따라하면 쉽게 구현할 수 있습니다. bulk update의 동작은 리스트 내의 데이터들을 한 번에 전부 처리하는 것이 아닌 반복문을 돌면서 단 건으로 처리합니다. 

+ Random Posts