브랜치 병합
브랜치 병합의 개념
브랜치 병합이란 2개의 브랜치를 합치는 것을 지칭한다. 병합을 진행해도 기존 두 브랜치가 사라지지는 않으며, 다만 두 개의 브랜치가 특정 커밋을 함께 가리키도록 브랜치 포인터를 이동하는 동작방식을 가지고 있다.
브랜치 병합의 종류
종류 | 설명 |
---|---|
fast-forward | - 같은 흐름에 있는 조상 브랜치와 자손 브랜치를 병합하는 것 - 전제조건 : 자손(최신) 브랜치의 커밋 이력이 조상(과거) 브랜치의 커밋 이력을 모두 포함하는 경우 - 명령어 : git merge <child branch> - 동작방식 : 조상 브랜치를 자손 브랜치 위치로 이동하는 것 ( 단순히 브랜치 포인터의 이동 )(1) HEAD를 조상 브랜치로 이동시킨다. (2) HEAD와 조상 브랜치를 자손 브랜치 위치로 이동시킨다. (3) 병합 후에도 HEAD는 조상 브랜치였던 브랜치를 가리킨다. |
3-way | - 한 커밋에서 갈라져 나온 두 브랜치를 병합하는 것 - 전제조건 : 병합하려는 두 브랜치가 공통 조상 커밋을 가지며, 두 브랜치가 공통조상 이후 서로 다른 변경사항을 가진 경우 - 명령어 : git merge <branch> - 동작방식 : 3-way 병합을 실행하면 새로운 커밋이 생성되며, HEAD와 현재 브랜치가 새로운 커밋으로 이동된다. (1) 공통 조상 커밋을 기준으로, 두 브랜치의 변경사항을 비교하고 새 병합 커밋을 생성한다. (2) HEAD->현재브랜치가 새로운 커밋으로 이동한다. (3) 병합 커밋은 양쪽 브랜치의 변경 이력이 포함되어 기록된다. 충돌이 있을 경우 사용자가 수동으로 해결.(4) 병합 대상으로 지정된 브랜치와 커밋은 그대로 유지된 채, 새로 생긴 커밋과 연결만 된다. |
squash | - 여러 개의 커밋을 하나의 커밋으로 압축해 병합하는 방식 - 깔끔한 커밋 히스토리를 유지하고자 할 때 사용됨 - 명령어 : git merge --squash <branch> - 동작 방식 : 대상 커밋들의 모든 변경사항을 포함하나, 병합 커밋 없이 하나의 새 커밋으로 압축한다. (1) 병합 대상 브랜치의 모든 변경사항을 현재 작업 디렉터리에 반영한다. (2) 병합 대상들의 커밋 히스토리는 포함하지 않고, unstaged 상태로 병합한다. (3) 병합되는 내용과 변경사항을 확인하고, 사용자가 수동으로 새 커밋을 생성한다. |
브랜치 병합 확인 명령어 - git branch
명령어 설명
브랜치 병합 확인과 관련한 명령어들이다.
기본 사용법
1
git branch [option]
옵션
옵션 | full name | 설명 |
---|---|---|
--merged |
현재 작업 브랜치를 기준으로 병합된(도달 가능한) 브랜치 목록을 표시 | |
--merged <branch name> |
지정 브랜치를 기준으로 병합된(도달 가능한) 브랜치 목록을 표시 | |
--no-merged |
현재 작업 브랜치를 기준으로 아직 병합되지 않은(도달 불가한) 브랜치 목록을 표시 | |
--no-merged <branch name> |
지정 브랜치를 기준으로 아직 병합되지 않은(도달 불가한) 브랜치 목록을 표시 |
예시
예시 저장소
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 첫 번째 커밋 : 파일 생성, 수정 및 커밋 이력 만들기
echo '#!/bin/bash' > test.sh
git add ./test.sh
git commit -m "first commit"
echo "echo Hello Linux" >> test.sh
git commit -am "second commit"
echo 'echo $0 $1 $2' >> test.sh
git commit -am "third commit"
echo "diff test" >> test.sh
echo "diff test - unstaged" >> test.sh
git commit -am "final commit"
# 브랜치 생성
git switch -c some_branch
echo "switch test1" >> ./test.sh
git commit -am ./test.sh
git switch -c third_branch HEAD~2
echo "is merged? test" >> ./test.sh
git commit -am "is_merged_test"
echo "additional commit" >> ./test.sh
git commit -am "4th branch"
git branch -m "4th_branch"
git switch -d HEAD~
git branch "3rd_branch"
git switch 4th_branch
# 저장소 상태 (이전 포스팅과 ID 달라짐)
git log --all --oneline --graph
>> * 94a38c4 (HEAD -> 4th_branch) 4th branch
>> * 759ebef (3rd_branch) is_merged_test
>> | * ee3fae4 (some_branch) ./test.sh
>> | * 6187d11 (main) final commit
>> |/
>> * 3b89005 third commit
>> * 5d3fbb8 second commit
>> * cb545cf first commit
(1) –merged
1
2
3
git branch --merged
>> 3rd_branch
>> * 4th_branch
**(2) –merged
1
2
3
git branch --merged some_branch
>> main
>> some_branch
(3) –no-merged
1
2
3
git branch --no-merged
>> main
>> some_branch
**(4) –no-merged
1
2
3
git branch --no-merged some_branch
>> 3rd_branch
>> * 4th_branch
브랜치 병합 명령어 - git merge
명령어 설명
브랜치 병합과 관련한 명령어들이다.
기본 사용법
1
git merge [option] <branch>
옵션
옵션 | full name | 설명 |
---|---|---|
없음 | - 기본 기능으로 fast-forward 또는 3-way 병합을 실행 - fast-forward가 가능하다면 이를 우선적으로 실행한다. |
|
-e or --edit |
- 병합 커밋 메시지를 편집할 수 있도록 하는 옵션 | |
-m "message" |
- 병합 커밋 메시지를 지정하는 옵션 | |
--no-ff |
- fast-forward 병합을 하지 않고, 항상 병합 커밋을 생성하는 옵션 - 3-way 병합을 하는 것과 동일하다. |
|
--squash |
- squash 병합을 하는 옵션 - 지정 브랜치의 변경사항을 압축해 작업디렉터리에 적용한다. - 적용 내용을 보고 사용자가 직접 커밋을 진행해야 한다. |
예시
예시 저장소 상태
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 첫 번째 커밋 : 파일 생성, 수정 및 커밋 이력 만들기
echo '#!/bin/bash' > test.sh
git add ./test.sh
git commit -m "first commit"
echo "echo Hello Linux" >> test.sh
git commit -am "second commit"
echo 'echo $0 $1 $2' >> test.sh
git commit -am "third commit"
echo "diff test" >> test.sh
echo "diff test - unstaged" >> test.sh
git commit -am "final commit"
# 브랜치 생성
git switch -c some_branch
echo "switch test1" >> ./test.sh
git commit -am ./test.sh
git switch -c third_branch HEAD~2
echo "is merged? test" >> ./test.sh
git commit -am "is_merged_test"
echo "additional commit" >> ./test.sh
git commit -am "4th branch"
git branch -m "4th_branch"
git switch -d HEAD~
git branch "3rd_branch"
git switch 4th_branch
# 저장소 상태 (이전 포스팅과 ID 달라짐)
git log --all --oneline --graph
>> * 94a38c4 (HEAD -> 4th_branch) 4th branch
>> * 759ebef (3rd_branch) is_merged_test
>> | * ee3fae4 (some_branch) ./test.sh
>> | * 6187d11 (main) final commit
>> |/
>> * 3b89005 third commit
>> * 5d3fbb8 second commit
>> * cb545cf first commit
(1) fast-forward 병합
main 브랜치와 some_branch 브랜치의 커밋 이력 내에 포함되므로 fast-forward 병합이 가능하다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 조상 브랜치 (main) 로 이동
git switch main
# merge
git merge some_branch
>> Updating e133ba0..c664a98
>> Fast-forward
>> test.sh | 1 +
>> 1 file changed, 1 insertion(+)
# git log
git log --all --graph --oneline
>> * 9cfcad4 (4th_branch) 4th branch
>> * b569f07 (3rd_branch) is_merged_test
>> | * 4988b30 (HEAD -> main, some_branch) ./test.sh
>> | * aca59fe final commit
>> |/
>> * 432f85e third commit
>> * 7f1ad68 second commit
>> * b70568e first commit
# 브랜치 삭제
git branch -d some_branch
>> Deleted branch some_branch (was c664a98).
# git log
git log --all --graph --oneline
>> * 94a38c4 (4th_branch) 4th branch
>> * 759ebef (3rd_branch) is_merged_test
>> | * ee3fae4 (HEAD -> main) ./test.sh
>> | * 6187d11 final commit
>> |/
>> * 3b89005 third commit
>> * 5d3fbb8 second commit
>> * cb545cf first commit
(2) 3-way 병합
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# git log
git log --all --graph --oneline
>> * 94a38c4 (4th_branch) 4th branch
>> * 759ebef (3rd_branch) is_merged_test
>> | * ee3fae4 (HEAD -> main) ./test.sh
>> | * 6187d11 final commit
>> |/
>> * 3b89005 third commit
>> * 5d3fbb8 second commit
>> * cb545cf first commit
# main 브랜치로 이동
git switch main
# main 브랜치와 4th_branch 병합 - merge failed
git merge -m "main-4th_branch merge" 4th_branch
>> Auto-merging test.sh
>> CONFLICT (content): Merge conflict in test.sh
>> Automatic merge failed; fix conflicts and then commit the result.
# git status 로 상황 확인 - conflict (both modified) 발생
git status
>> On branch main
>> You have unmerged paths.
>> (fix conflicts and run "git commit")
>> (use "git merge --abort" to abort the merge)
>> Unmerged paths:
>> (use "git add <file>..." to mark resolution)
>> both modified: test.sh
>> no changes added to commit (use "git add" and/or "git commit -a")
# 충돌이 발생한 파일 확인 및 수정
vi ./test.sh
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
\#!/bin/bash
echo Hello Linux
echo $0 $1 $2
<<<<<<< HEAD # (Current Change)
diff test
diff test - unstaged
switch test1
======= # (Conflict Separator)
is merged? test
additional commit
>>>>>>> 4th_branch # (Incoming Change)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# 수정 파일 내용 확인
cat ./test.sh
>> \#!/bin/bash
>> echo Hello Linux
>> echo $0 $1 $2
>> newline_test
>> -- history --
>> \#is merged? test
>> \#additional commit
>> \#diff test
>> \#diff test - unstaged
>> \#switch test1
# git add
git add ./test.sh
# git commit
git commit -m "conflict resolution"
[main d40b0ce] conflict resolution
# git log
git log --oneline --all --graph
>> * d40b0ce (HEAD -> main) conflict resolution
>> |\
>> | * 94a38c4 (4th_branch) 4th branch
>> | * 759ebef (3rd_branch) is_merged_test
>> * | ee3fae4 ./test.sh
>> * | 6187d11 final commit
>> |/
>> * 3b89005 third commit
>> * 5d3fbb8 second commit
>> * cb545cf first commit
(3) sqush 병합
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# main 과 다른 흐름의 커밋 생성
git switch 4th_branch
git merge -m "4th to main" main
git log --oneline --all --graph
>> * d40b0ce (HEAD -> 4th_branch, main) conflict resolution
>> |\
>> | * 94a38c4 4th branch
>> | * 759ebef (3rd_branch) is_merged_test
>> * | ee3fae4 ./test.sh
>> * | 6187d11 final commit
>> |/
>> * 3b89005 third commit
>> * 5d3fbb8 second commit
>> * cb545cf first commit
# 4th_branch 에 변경 사항을 줌
echo "squash test" >> squash.txt
git add ./squash.txt
git commit -m "squash_test1"
echo "squash_test additional" >> ./squash.txt
git commit -am "squash_test2"
git log --oneline --all --graph
>> * f8aaf29 (HEAD -> 4th_branch) squash_test2
>> * 7b770bd squash_test1
>> * d40b0ce (main) conflict resolution
>> |\
>> | * 94a38c4 4th branch
>> | * 759ebef (3rd_branch) is_merged_test
>> * | ee3fae4 ./test.sh
>> * | 6187d11 final commit
>> |/
>> * 3b89005 third commit
>> * 5d3fbb8 second commit
>> * cb545cf first commit
# main 브랜치에 변경사항을 주고 커밋
git switch main
echo "squash test at main" >> ./test.sh
git commit -am sqt
# squash 병합
git merge 4th_branch --squash
>> Squash commit -- not updating HEAD
>> Automatic merge went well; stopped before committing as requested
# 병합 후 상태 확인
git status
>> On branch main
>> Changes to be committed:
>> (use "git restore --staged <file>..." to unstage)
>> new file: squash.txt
# 커밋 및 상태 확인
git commit -am "sq_merged"
git log --oneline --all --graph
>> * d8b3f26 (HEAD -> main) sq_merged
>> * 7fb15fa sqt # 병합 히스토리가 남지 않음
>> | * f8aaf29 (4th_branch) squash_test2
>> | * 7b770bd squash_test1
>> |/
>> * d40b0ce conflict resolution
>> |\
>> | * 94a38c4 4th branch
>> | * 759ebef (3rd_branch) is_merged_test
>> * | ee3fae4 ./test.sh
>> * | 6187d11 final commit
>> |/
>> * 3b89005 third commit
>> * 5d3fbb8 second commit
>> * cb545cf first commit
3-way 와 squash 병합의 차이점
구분 | 3-way Merge | Squash Merge |
---|---|---|
병합 방식 | ||
커밋 히스토리 | 병합 대상 브랜치의 모든 커밋 기록을 보존. | 병합 대상 브랜치의 커밋 히스토리는 보존되지 않고 삭제 |
병합 커밋 | 병합 커밋이 생성됨. | 병합 커밋이 생성되지 않은 대기상태.커밋은 수동으로 생성해야 함. |
작업 디렉터리 | 병합 후 자동으로 커밋 상태가 됨. | 병합 대상 브랜치의 변경사항만 작업 디렉토리에 반영. |
충돌 처리 | 충돌 시 수동으로 해결 후, 병합 커밋을 생성함. | 충돌 시 수동으로 해결 후, 병합된 변경사항을 하나의 커밋으로 생성해야 함. |
사용 목적 | - 세부 커밋 히스토리를 보존해야 할 때. - 협업 프로젝트에서 병합 과정을 추적하려고 할 때. |
- 간결한 히스토리를 유지해야 할 때. - 간단한 기능 개발 완료 후 깔끔히 통합하려고 할 때. |
결과물 자체는 두 병합방식 모두 크게 차이는 없다. 가장 중요한 차이점이라면, 커밋 히스토리를 남기느냐 남기지 않느냐 정도의 차이로 보인다.