개발 서버 한 대에서 여럿이 같은 폴더를 고치고 있었다. 거기서 커밋하니 내가 안 만진 파일이 같이 들어갔다.
Table of contents
Open Table of contents
증상 — 무엇이 들어갔는지 봤다
커밋에 무엇이 담겼는지 뽑았다.
$ git show --stat HEAD
app/Http/Controllers/OrderController.php | 42 +++--
app/Services/FeeService.php | 18 ++-
resources/views/product/edit.blade.php | 210 ++++++++++++++++++
config/queue.php | 6 +-
edit.blade.php 와 queue.php 는 내가 안 만졌고 다른 사람이 작업 중이던 것이었다.
git add . 을 했기 때문인데 공용 폴더에서 그 명령은 누가 만든 것이든 그 시점의 모든 변경을 담는다.
딸려 온 것 중에는 아직 동작하지 않는 중간 상태도 있어서 그대로 두면 다음 사람이 그 상태를 기준으로 작업하게 된다.
edit.blade.php 는 210줄이 통째로 들어간 새 파일이었다. 작업하던 사람은 아직 올릴 생각이 없었을 것이다.
조치 — 되돌리는 커밋으로
이미 올라가 남이 받아 간 뒤였다.
$ git revert --no-commit HEAD
$ git checkout HEAD -- app/Http/Controllers/OrderController.php app/Services/FeeService.php
$ git commit -m "다른 작업 파일이 섞여 들어간 것을 되돌린다"
git revert 뒤에 git checkout HEAD -- 로 내 파일 둘은 되살리고 나머지만 되돌렸다.
rebase 나 --amend 로 이력을 고치는 방법도 있지만 이미 pull 해 간 것이라 쓰면 안 됐다. 받아 간 쪽의 기준이 사라지면 다음 병합에서 문제가 더 커진다.
이력이 지저분해지는 것과 남의 기준이 무너지는 것 중에서 이력이 지저분해지는 쪽을 골랐다.
되돌리는 커밋은 무엇이 왜 되돌려졌는지가 이력에 남는다는 이점도 있다. 이력을 고쳐 쓰면 그 사고가 있었다는 사실 자체가 사라진다.
담을 것을 지정하게 바꿨다
바로 할 수 있는 조치는 담을 것을 매번 적는 것이었다.
$ git add app/Http/Controllers/OrderController.php app/Services/FeeService.php
$ git commit
git add 에 . 을 안 쓰고 파일 이름을 매번 적는다.
담기 전에 목록을 보는 것도 붙였다.
$ git status -s
M app/Http/Controllers/OrderController.php
M app/Services/FeeService.php
M resources/views/product/edit.blade.php ← 남의 것
git status -s 목록을 커밋 전에 보는 것을 습관으로 뒀다.
다만 이것은 조심으로 막는 방식이라 언젠가 다시 나는데 급할 때는 확인을 건너뛰고 새로 온 사람에게는 그 습관이 없다.
한 사람이 규칙을 지켜도 같은 폴더를 쓰는 다른 사람이 안 지키면 소용이 없다. 공용 자리에서는 규칙이 전원에게 동시에 지켜져야 효과가 난다.
원인 — 공용 폴더라는 구조
근본은 개발 서버의 한 폴더를 여럿이 함께 고치고 있다는 구조 쪽이었다.
개발자 각자 로컬 작업 공간 + 자기 브랜치
개발 서버 배포된 것만. 여기서 파일을 고치지 않는다
각자 자기 feature/ 에서 작업하고 개발 서버에는 배포된 것만 있게 했다.
개발 서버에서 직접 고치는 것이 편해서 그렇게 하고 있었는데 편한 만큼 섞였고 그 편의를 포기해야 사고가 없어진다.
고치고 바로 화면에서 확인할 수 있다는 것이 그 편의의 실체였다. 각자 자리에서 작업하면 그 확인에 한 단계가 더 붙는다.
조심으로 막는 문제는 구조로 막아야 다시 안 난다.
설정 — 브랜치 규칙과 배포 표시
각자 feature/ 를 쓰기로 하니 이름과 병합 규칙이 함께 필요했다.
master 배포된 것
develop 다음 배포 대상
feature/<작업> 각자 작업
hotfix/<작업> 급한 수정. master에서 갈라져 master와 develop 둘 다로 간다
$ git checkout -b feature/order-fee develop
...
$ git push -u origin feature/order-fee
develop 으로 병합할 때는 요청을 올려 한 사람이 더 보고 넣기로 했다.
혼자 넣으면 이번 같은 것이 그대로 통과한다. 보는 사람이 하나 더 있으면 남의 파일이 섞인 것은 대개 그 자리에서 걸린다.
여럿이 고치던 때는 개발 서버가 어느 판을 물고 있는지 아무도 몰랐다.
$ cat /srv/app/REVISION
develop@8f2a1c3 2018-06-10 13:20 by jenkins
REVISION 에 배포할 때마다 남기고 화면에서도 보이게 했다.
내 코드가 올라갔느냐고 묻지 않고 보게 됐고 구조를 바꾸면 그에 딸린 규칙과 표시가 함께 따라온다.
검증 — 서버에서 고친 것 잡기
규칙을 정해도 급하면 서버에서 고친다.
$ cd /srv/app && git status --porcelain
M app/Services/FeeService.php
서버에서 고친 것이 다음 배포에 조용히 덮여 사라지는 것이 문제였다.
if [ -n "$(git status --porcelain)" ]; then
echo "서버에서 직접 고친 파일이 있습니다:"; git status --porcelain; exit 1
fi
git status --porcelain 이 비어 있지 않으면 배포가 멈춘다.
일부러 파일 하나를 고쳐 놓고 이 검사가 멈추는지 봤는데 안 멈추면 그 검사는 아무것도 안 하는 것이다.
멈추는 것을 확인하고 나서야 그 검사를 믿을 수 있었다. 통과만 보고 작동한다고 판단하면 있으나 마나 한 것을 그대로 두게 된다.
정리
- 공용 폴더에서
git add .은 그 시점의 모든 변경을 담는다 - 딸려 온 것 중에 중간 상태가 섞이면 그 기준으로 다음 작업이 쌓인다
- 이미 받아 간 커밋은 이력을 고치지 말고 되돌리는 커밋으로 처리한다
- 이력이 지저분해지는 것보다 남의 기준이 무너지는 쪽이 크다
- 담을 파일을 매번 지정하고 커밋 전에 목록을 본다
- 조심으로 막는 문제는 구조로 막아야 다시 안 난다
- 공용 폴더를 여럿이 고치는 구조 자체를 바꾼다
- 편한 만큼 섞이므로 그 편의를 포기해야 한다
- 구조를 바꾸면 브랜치 이름과 병합 방식도 함께 정한다
- 배포된 표시를 파일과 화면에 남긴다
- 서버에서 직접 고친 것이 있으면 배포가 멈추게 한다
- 일부러 고쳐 놓고 멈추는지 확인한다