-
Notifications
You must be signed in to change notification settings - Fork 0
/
git-conflict.qmd
488 lines (361 loc) · 17 KB
/
git-conflict.qmd
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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
# 충돌 {#git-conflict}
\index{충돌}
\index{git!conflict}
\index{git!resolve}
사람들이 병렬로 작업을 할 수 있게 됨에 따라,
누군가 다른 사람 작업 영역에 발을 들여 넣을 가능성이 생겼다.
혼자서 작업할 경우에도 이런 현상이 발생한다.
만약 개인 노트북과 연구실 서버에서 소프트웨어 개발을 한다면,
각 작업본에 다른 변경 사항을 만들 수 있다.
버전 제어(version control)는 겹치는 변경 사항을
**해결(resolve)**하는 도구를 제공함으로써
이러한 **충돌(conflicts)**을 관리할 수 있게 돕는다.
충돌을 어떻게 해소할 수 있는지 확인하기 위해서
먼저 파일을 하나 생성하자.
`mars.txt` 파일은 현재 두 협업하는 사람의 `planets` 저장소 사본에서 다음과 같이 보인다.
``` bash
$ cat mars.txt
Cold and dry, but everything is my favorite color
The two moons may be a problem for Wolfman
But the Mummy will appreciate the lack of humidity
```
파트너 사본에만 한 줄을 추가하자.
``` bash
$ nano mars.txt
$ cat mars.txt
Cold and dry, but everything is my favorite color
The two moons may be a problem for Wolfman
But the Mummy will appreciate the lack of humidity
This line added to Wolfman's copy
```
그리고 나서 변경 사항을 GitHub에 푸시하자.
``` bash
$ git add mars.txt
$ git commit -m "Add a line in our home copy"
[main 5ae9631] Add a line in our home copy
1 file changed, 1 insertion(+)
```
``` bash
$ git push origin main
Counting objects: 5, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 352 bytes, done.
Total 3 (delta 1), reused 0 (delta 0)
To https://github.com/vlad/planets
29aba7c..dabb4c8 main -> main
```
이제 다른 파트너가 GitHub에서 갱신(update)하지 않고
본인 사본에 다른 변경 사항을 작업한다.
``` bash
$ nano mars.txt
$ cat mars.txt
Cold and dry, but everything is my favorite color
The two moons may be a problem for Wolfman
But the Mummy will appreciate the lack of humidity
We added a different line in the other copy
```
로컬 저장소에 변경 사항을 커밋할 수 있다.
``` bash
$ git add mars.txt
$ git commit -m "Add a line in my copy"
[main 07ebc69] Add a line in my copy
1 file changed, 1 insertion(+)
```
하지만 Git이 GitHub에는 푸시할 수 없게 한다.
``` bash
$ git push origin main
To https://github.com/vlad/planets.git
! [rejected] main -> main (non-fast-forward)
error: failed to push some refs to 'https://github.com/vlad/planets.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Merge the remote changes (e.g. 'git pull')
hint: before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
```
![충돌하는 변경사항](images/git/conflict.svg){#fig-git-conflict-change}
Git이 푸시를 거절한다.
이유는 로컬 브랜치로 반영되지 않는 새로운 업데이트가 원격 저장소에 있음을 Git이 탐지했기 때문이다.
즉, 본인이 작업한 변경 사항이 다른 사람이 작업한 변경 사항과 중첩되는 것을 Git이 탐지해서
앞에서 작업한 것을 덮어쓰지 않도록 정지시킨다.
이제 해야 할 작업은 GitHub에서 변경 사항을 풀(Pull)해서 가져오고
현재 작업 중인 작업본과 **병합(merge)**해서 푸시한다.
풀(Pull)부터 시작하자.
\index{병합}
\index{git!merge}
\index{git!pull}
``` bash
$ git pull origin main
remote: Counting objects: 5, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 1), reused 3 (delta 1)
Unpacking objects: 100% (3/3), done.
From https://github.com/vlad/planets
* branch main -> FETCH_HEAD
Auto-merging mars.txt
CONFLICT (content): Merge conflict in mars.txt
Automatic merge failed; fix conflicts and then commit the result.
```
`git pull` 명령어는 로컬 저장소를 갱신할 때 원격 저장소에 이미 반영된 변경 사항을 포함시키도록 한다.
원격 저장소 브랜치에서 변경 사항을 가져온(fetch) 후에
로컬 저장소 사본의 변경 사항이 원격 저장소 사본과 겹치는 것을 탐지해냈다.
따라서 앞서 작업한 것이 덮어쓰지 않도록 서로 다른 두 버전의 병합(merge)을 승인하지 않고 거절한 것이다.
해당 파일에 충돌나는 부분을 다음과 같이 표시해 놓는다.
``` bash
$ cat mars.txt
Cold and dry, but everything is my favorite color
The two moons may be a problem for Wolfman
But the Mummy will appreciate the lack of humidity
<<<<<<< HEAD
We added a different line in the other copy
=======
This line added to Wolfman's copy
>>>>>>> dabb4c8c450e8475aee9b14b4383acc99f42af1d
```
`<<<<<<< HEAD`로 시작되는 부분에 본인 변경 사항이 나와 있다.
Git이 자동으로 `=======`를 넣어 충돌나는 변경 사항 사이에 구분자로 넣고,
`>>>>>>>`기호는 GitHub에서 다운로드된 파일 내용의 마지막을 표시한다.
(`>>>>>>> `표시자 다음에 문자와 숫자로 구성된 문자열은 방금 다운로드한 커밋 번호의 식별자다.)
파일을 편집해서 표시자/구분자를 제거하고 변경 사항을 일치시키는 것은 전적으로 여러분에게 달려 있다.
원하는 것이면 무엇이든 할 수 있다.
예를 들어 로컬 저장소의 변경 사항을 반영하든,
원격 저장소의 변경 사항을 반영하든,
로컬과 원격 저장소의 내용을 대체하는 새로운 것을 작성하든,
혹은 변경 사항을 완전히 제거하는 것도 가능하다.
로컬과 원격 모두 교체해서 다음과 같이 파일이 보이도록 하자.
``` bash
$ cat mars.txt
Cold and dry, but everything is my favorite color
The two moons may be a problem for Wolfman
But the Mummy will appreciate the lack of humidity
We removed the conflict on this line
```
병합을 마무리하기 위해
병합으로 생성된 변경 사항을 mars.txt 파일에 추가하고 커밋한다.
``` bash
$ git add mars.txt
$ git status
On branch main
All conflicts fixed but you are still merging.
(use "git commit" to conclude merge)
Changes to be committed:
modified: mars.txt
```
``` bash
$ git commit -m "Merge changes from GitHub"
[main 2abf2b1] Merge changes from GitHub
```
이제 변경 사항을 GitHub에 푸시할 수 있다.
``` bash
$ git push origin main
Counting objects: 10, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (6/6), done.
Writing objects: 100% (6/6), 697 bytes, done.
Total 6 (delta 2), reused 0 (delta 0)
To https://github.com/vlad/planets.git
dabb4c8..2abf2b1 main -> main
```
Git이 병합하면서 수행한 것을 모두 추적하고 있어서
수작업으로 다시 고칠 필요는 없다.
처음 변경 사항을 만든 협력자 프로그래머가 다시 풀하게 되면 다음과 같은 결과를 얻는다.
``` bash
$ git pull origin main
remote: Counting objects: 10, done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 6 (delta 2), reused 6 (delta 2)
Unpacking objects: 100% (6/6), done.
From https://github.com/vlad/planets
* branch main -> FETCH_HEAD
Updating dabb4c8..2abf2b1
Fast-forward
mars.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
```
병합된 파일을 얻게 된다.
``` bash
$ cat mars.txt
Cold and dry, but everything is my favorite color
The two moons may be a problem for Wolfman
But the Mummy will appreciate the lack of humidity
We removed the conflict on this line
```
다시 병합할 필요는 없는데,
다른 누군가 작업을 했다는 것을 Git이 알기 때문이다.
충돌을 해소하는 Git 기능은 매우 유용하지만,
충돌 해소에는 시간과 노력이 수반되고, 충돌이 올바르게 해소되지 않으면
오류가 스며들게 된다.
프로젝트 와중에 상당량의 충돌을 해소하는 데 시간을 쓰고 있다고 생각되면,
충돌을 줄일 수 있는 기술적인 접근법도 고려해 보는 것이 좋다.
좀 더 자주 upstream을 풀(Pull)하기, 특히 새로운 작업을 시작하기 전이라면 더욱 그렇다.
작업을 구별하기 위해 토픽 브랜치를 사용해서 작업을 완료하면 메인(main) 브랜치에 병합시킨다.
좀 더 작게 원자 수준 커밋을 한다.
논리적으로 적절하다면 큰 파일을 좀 더 작은 것으로 쪼갠다. 그렇게 함으로써 두 저자가 동시에 동일한 파일을 변경하는 것을 줄일 수 있을 듯 싶다.
프로젝트 관리 전략으로 충돌을 최소화할 수도 있다.
프로젝트 관리 전략으로 충돌을 최소화할 수도 있다.
- 동료 협력자와 누가 어떤 분야에 책임이 있는지 명확히 한다.
- 동료 협력자와 작업 순서를 협의해서 동일한 라인에 변경 사항이 있을 수 있는 작업이 동시에 작업되지 않게 시간차를 둔다.
- 충돌이 문체 변동(탭 vs 2 공백) 때문이라면 프로젝트 관례를 수립하고 코딩 스타일 도구(`htmltidy`, `perltidy`, `rubocop` 등)를 사용해서 필요한 경우 강제한다.
::: callout-tip
### 본인이 생성한 충돌 해소하기
강사가 생성한 저장소를 복제하세요.
저장소에 새 파일을 추가하고
기존 파일을 변경하세요. (강사가 변경할 기존 파일이 어느 것인지 알려줄 것이다.)
강사의 말에 따라 충돌을 생성하는 연습을 위해
저장소에서 변경 사항을 가져오도록 풀(Pull)하세요.
그리고 충돌을 해소하고 해결해 보세요.
:::
::::: callout-tip
### 텍스트 파일이 아닌 충돌
버전 제어 저장소의 이미지 파일이나 혹은 다른 텍스트가 아닌 파일에서 충돌이 발생할 때
Git은 무엇을 하나요?
::: {.callout-caution collapse="true"}
### 해답 1/3
먼저 시도해 보자.
드라큘라가 화성 표면에서 사진을 찍어 `mars.jpg`로 저장했다고 가정한다.
화성 이미지 파일이 없다면 다음과 같이 더미 바이너리 파일을 생성할 수도 있다.
``` bash
$ head --bytes 1024 /dev/urandom > mars.jpg
$ ls -lh mars.jpg
-rw-r--r-- 1 vlad 57095 1.0K Mar 8 20:24 mars.jpg
```
`ls` 명령어를 사용해서 파일 크기가 1 킬로바이트임이 확인된다.
`/dev/urandom` 특수 파일에서 불러온 임의 바이트로 꽉 차있다.
이제, 드라큘라가 `mars.jpg` 파일을 본인 저장소에 저장한다고 상정한다:
``` bash
$ git add mars.jpg
$ git commit -m "Add picture of Martian surface"
[main 8e4115c] Add picture of Martian surface
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 mars.jpg
```
늑대인간도 비슷한 시점에 유사한 사진을 추가했다고 가정한다.
늑대인간의 사진은 화성 하늘 사진인데, 이름도 `mars.jpg`로 동일하다.
드라큘라가 푸시하게 되면 유사한 메시지를 받게 된다.
``` bash
$ git push origin main
To https://github.com/vlad/planets.git
! [rejected] main -> main (fetch first)
error: failed to push some refs to 'https://github.com/vlad/planets.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
```
:::
::: {.callout-caution collapse="true"}
### 해답 2/3
풀을 먼저 한 뒤에 충돌나는 것을 해소한다는 것을 학습했다.
``` bash
$ git pull origin main
```
이미지나 기타 바이너리 파일에 충돌이 생길 때,
Git은 다음과 같은 메시지를 출력한다:
``` bash
$ git pull origin main
remote: Counting objects: 3, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From https://github.com/vlad/planets.git
* branch main -> FETCH_HEAD
6a67967..439dc8c main -> origin/main
warning: Cannot merge binary files: mars.jpg (HEAD vs. 439dc8c08869c342438f6dc4a2b615b05b93c76e)
Auto-merging mars.jpg
CONFLICT (add/add): Merge conflict in mars.jpg
Automatic merge failed; fix conflicts and then commit the result.
```
이번에도 충돌 메시지가 `mars.txt`에 나온 것과 거의 동일하다.
하지만 중요한 추가 라인 한 줄이 있다.
``` bash
warning: Cannot merge binary files: mars.jpg (HEAD vs. 439dc8c08869c342438f6dc4a2b615b05b93c76e)
```
Git은 자동으로 텍스트 파일에 했던 것처럼 이미지 파일에 충돌 지점 표식을
끼워 넣을 수 없다.
그래서 이미지 파일을 편집하는 대신에
간직하고자 하는 버전을 체크아웃(checkout)하고 나서 해당 버전을 추가(add)하고 커밋한다.
중요한 라인에 `mars.jpg`의 두 가지 버전에 대해
커밋 식별자(commit identifier)를 Git이 제시하고 있다.
현재 작업 버전은 `HEAD`이고, 늑대인간 작업 버전은 `439dc8c0...`이다.
본인 작업 버전을 사용하고자 하면 `git checkout` 명령어를 사용한다.
``` bash
$ git checkout HEAD mars.jpg
$ git add mars.jpg
$ git commit -m "Use image of surface instead of sky"
[main 21032c3] Use image of surface instead of sky
```
:::
::: {.callout-caution collapse="true"}
### 해답 3/3
대신에 늑대인간 버전을 사용하려고 하면
`git checkout` 명령어를 늑대인간 `439dc8c0` 커밋 식별자와 함께 사용하면 된다.
``` bash
$ git checkout 439dc8c0 mars.jpg
$ git add mars.jpg
$ git commit -m "Use image of sky instead of surface"
[main da21b34] Use image of sky instead of surface
```
이미지 모두 보관할 수도 있다.
동일한 이미지명으로 보관할 수는 없다는 것이 중요하다.
순차적으로 각 버전을 체크아웃(checkout)하고 나서 이미지명을 변경한다.
그리고 나서 이름을 변경한 버전을 추가한다.
먼저 각 이미지를 체크아웃하고 이름을 변경하자.
``` bash
$ git checkout HEAD mars.jpg
$ git mv mars.jpg mars-surface.jpg
$ git checkout 439dc8c0 mars.jpg
$ mv mars.jpg mars-sky.jpg
```
그리고 나서 `mars.jpg` 이전 파일을 삭제하고
새로운 파일 두 개를 추가한다.
``` bash
$ git rm mars.jpg
$ git add mars-surface.jpg
$ git add mars-sky.jpg
$ git commit -m "Use two images: surface and sky"
[main 94ae08c] Use two images: surface and sky
2 files changed, 0 insertions(+), 0 deletions(-)
create mode 100644 mars-sky.jpg
rename mars.jpg => mars-surface.jpg (100%)
```
이제 화성 이미지 파일 두 개가 저장소에서 확인되지만 `mars.jpg` 파일은 더 이상 존재하지 않는다.
:::
:::::
::: callout-tip
### 일반적인 작업 시간
원격 Git 저장소를 활용하여 공동 프로젝트로 작업하는 컴퓨터 앞에 앉아 있다.
작업 시간 동안에 다음 동작을 취하지만 작업 순서는 다르다.
- *변경한다(make change)*: `numbers.txt` 텍스트 파일에 숫자 `100`을 추가.
- *원격 저장소 갱신시키기(Update remote)*: 로컬 저장소와 매칭되어 동기화시킴.
- *축하하기(Celebrate)*: 맥주로 성공을 자축함.
- *로컬 저장소 갱신시키기(Update local)*: 원격 저장소와 매칭되어 동기화시킴.
- *변경 사항 준비영역으로 보내기(Stage change)*: 커밋 대상으로 추가하기.
- *변경 사항 커밋하기(Commit change)*: 로컬 저장소에 커밋하기.
어떤 순서로 작업을 수행해야 충돌이 날 가능성을 최소화할 수 있을까?
아래 표의 *action* 칼럼에 순서대로 상기 명령어를 적어 본다.
작업 순서를 정했으면 *command* 칼럼에 대응되는 명령어를 적어 본다.
일부 단계를 시작하는 데 도움이 되도록 채워져 있다.
|order|action . . . . . . . . . . |command . . . . . . . . . . |
|-----|---------------------------|----------------------------|
|1 | | |
|2 | | `echo 100 >> numbers.txt` |
|3 | | |
|4 | | |
|5 | | |
|6 | Celebrate! | `AFK` |
::: {.callout-caution collapse="true"}
### 해답
|order|action . . . . . . |command . . . . . . . . . . . . . . . . . . . |
|-----|-------------------|----------------------------------------------|
|1 | Update local | `git pull origin main` |
|2 | Make changes | `echo 100 >> numbers.txt` |
|3 | Stage changes | `git add numbers.txt` |
|4 | Commit changes | `git commit -m "Add 100 to numbers.txt"` |
|5 | Update remote | `git push origin main` |
|6 | Celebrate! | `AFK` [^afk] |
:::
:::
[^afk]: AFK는 "Away From Keyboard"의 약자로
사용자가 컴퓨터 앞에 있지 않다는 것을 나타내는 데 사용된다.
주로 게임이나 채팅 중에 자리를 비울 때 다른 사용자들에게 알리기 위해 사용된다.