- 발행일
파일 33개를 한 커밋에 넣었다 — 제목은 한 가지 일이라고 말하고, 안에는 다섯 가지가 있었다
파일 33개를 한 커밋에 넣었다
이 글은 2026년 9월에 당시 커밋 이력을 다시 보며 정리한 것입니다. 날짜는 작업한 날 기준입니다.
2025년 7월 17일 오후에 올린 커밋의 제목입니다.
fix: replace `multiSelectBox.js` with `dualMultiSelect.js` in templates
한 줄만 읽으면 스크립트 파일 하나의 이름을 바꾸고 템플릿에서 참조를 고친 것으로 보입니다. 통계는 이렇습니다.
33 files changed, 1228 insertions(+), 823 deletions(-)
이름 하나 바꾸는 데 1,228줄이 필요할 리 없습니다. 이 글은 그 커밋을 지금 다시 열어 안에 일이 몇 개 들어 있었는지 세어 본 기록입니다.
세어 보니 다섯 가지였다
| 종류 | 파일 | 무엇 |
|---|---|---|
| ① 이름 바꾸기 | 5 | concept-class-tree.js→conceptClassTree.js, meta-class-tree.js→metaClassTree.js (kebab→camel), toogleBtn.js→toggleBtn.js (오타), multiSelectBox.js→dualMultiSelect.js, 문서 .md 하나 |
| ② 새 파일 | 5 | oidClassTree.js 502줄, commonClassTreeUtils.js 207줄, formsetManager.js 131줄, formFieldHandler.js 37줄, formLeaveWarning.js 31줄 |
| ③ 인라인 스크립트를 파일로 | 3 | item_set.html.j2에서 243줄이 움직임 — reindexFormset·deleteIndividualItem·deleteAllItems가 템플릿 밖으로 |
| ④ 설정 | 3 | ESLint 전역 30여 개 추가, INTERNAL_IPS에 로컬 주소, Jinja에 add_hash 필터 신설 + base.html에 디버그용 no-cache 메타 |
| ⑤ 뷰 정리 | 1 | scheme.py에서 상세 화면 버튼 정의를 걷어냄(22줄 변경) |
나머지 16개는 위 다섯 가지 때문에 참조를 고친 템플릿입니다.
제목에 적힌 ①의 multiSelectBox는 실제 diff에서 10줄짜리 변화였습니다. 33개 파일 중 가장 작은 일이 제목이 된 셈이에요. 6월에 만든 트리 두 개(conceptClassTree·metaClassTree)에서 각각 310줄·293줄어치를 걷어 내 commonClassTreeUtils.js로 모은 게 이 커밋의 진짜 몸통이었습니다.
// 공통된 트리 유틸즈
// CRUD 공통된 URL 처리
const url = (type, classURI) => {
switch (type) {
case "meta":
return window.metaClassListUrl + encodeURIComponent(classURI) + "/";
case "concept":
return window.conceptListUrl + encodeURIComponent(classURI) + "/";
}
};
왜 이렇게 한 번에 올렸는지는 커밋에 남아 있지 않습니다. 이 커밋 바로 앞 커밋이 엿새 전 팀원 것이라, 그 엿새 동안 한 일을 한 번에 올린 것으로 보입니다. 저자 이름도 다른 커밋과 달리 HyoJin Lim으로 찍혀 있어서, 다른 환경에서 작업하다 올린 것 같습니다. 이것도 추측입니다.
대가 하나 — 정적 파일에 Jinja를 넣었다
②의 formFieldHandler.js는 이렇게 시작합니다.
// 현재 폼의 모드
const mode = "{{ mode }}";
// 현재 모드에 따라 비활성화되어야 하는 필드 목록
const disableFields = JSON.parse(
`{{ (disable_fields if disable_fields else []) | tojson }}`,
);
템플릿 안에 있던 인라인 스크립트를 그대로 잘라 static/js/로 옮긴 겁니다. 그런데 정적 파일은 Jinja를 거치지 않습니다. 브라우저에는 {{ mode }}라는 글자가 그대로 갑니다. JSON.parse가 첫 줄에서 던지고, 그 아래 날짜 변환과 제출 처리가 전부 안 돌아요.
이 파일은 두 달 뒤인 9월 10일에 팀원이 "Workflow Form 관련 javascript 정상 동작하지 않는 이슈 수정"이라는 커밋으로 삭제했습니다. 일주일 뒤 제가 다시 만들었고요. 33개 파일짜리 커밋 안에 37줄 파일 하나가 묻혀 있으면 아무도 안 봅니다. 저도 안 봤습니다.
대가 둘 — 캐시 버스터를 끼워 넣었다
④의 add_hash는 이 커밋에서 처음 생겼습니다.
def add_hash(url_str):
return f'{url_str}?k={str(round(time.time() * 1000))}'
src="{{ 'js/utils.js' | static | add_hash }}"
JS를 고쳐도 브라우저가 옛 파일을 쓰길래 주소 뒤에 시각을 붙인 겁니다. 그 자체는 흔한 방법인데, 스크립트 정리 커밋에 캐시 정책 변경이 같이 실렸다는 게 문제였습니다. 제목에도 없고, 나중에 이 필터가 왜 생겼는지 찾으려면 33개 파일 diff를 다 뒤져야 합니다.
그리고 이 필터는 1년 뒤 버그로 돌아왔습니다. 상수만 받는 필터 체인을 Jinja가 컴파일 시점에 접어 버려서, 시각이 서버 기동 시각에 굳어 있었어요. 그 이야기는 별도 글에 있습니다. 여기서 말하고 싶은 건 그 버그가 아니라, 1년 뒤 그 글을 쓰면서 git log -S add_hash를 돌렸을 때 나온 커밋이 이 33개짜리였다는 겁니다. 원인을 찾는 사람이 제목만 보고 지나칠 수 있는 자리에 있었습니다.
1년 뒤에는 어떻게 했나
2026년 8월에 용어 하나를 96개 파일 667곳에서 바꾼 일이 있습니다. 그때는 추가와 삭제가 정확히 같은 순수 치환만 한 커밋에 넣고, 코드 식별자나 DB 값처럼 성격이 다른 건 별건으로 뗐습니다. 그 글에서 "어려운 건 어디서 멈출지 정하는 쪽"이라고 썼는데, 2025년 7월의 저는 멈추는 지점을 정하지 않았습니다.
같은 해 12월에 제 저장소 2년치 커밋 메시지를 검증해 본 글도 있습니다. 거기서 세운 기준으로 이 커밋을 채점하면, 제목이 본문을 대표하지 않는 커밋의 전형입니다.
검증
- 당시에는 검증 기록이 없습니다. 33개 파일을 올리고 화면이 뜨는 것만 봤을 겁니다.
- 이 글을 쓰며 확인한 것:
git show --stat로 33개 파일 분류,-M옵션으로 이름 바꾸기 5건이 순수 개명인지,formFieldHandler.js의 삭제·재생성 이력,add_hash의 첫 등장 커밋. dualMultiSelect.js는 개명 뒤 10줄 변경이 맞습니다. 파일 자체는 414줄입니다.
남은 것 · 한계
- 왜 한 번에 올렸는지 모릅니다. 엿새치 작업이라는 것도 앞뒤 커밋 간격으로 짐작한 것입니다.
formFieldHandler.js가 두 달 동안 깨진 채로 있었는데 아무도 몰랐다는 건, 그 파일이 하는 일(모드별 필드 비활성화, 날짜 변환)이 그 기간 화면에서 안 보여도 티가 안 났다는 뜻입니다. 왜 안 보였는지는 확인하지 않았습니다.- 이름 바꾸기 5건과 새 모듈 5건을 따로 커밋했다면 어땠을지는 이제 알 수 없습니다. 다만 두 달 뒤 팀원이 문제 파일을 찾는 데 얼마나 걸렸는지는 그 커밋 메시지가 "정상 동작하지 않는 이슈"라는 한 줄뿐이라 역시 알 수 없습니다.
- ESLint 전역에
preventDefault·stopPropagation을 전역 함수처럼 등록했습니다. 그건 메서드지 전역이 아니에요. 그 줄이 언제 정리됐는지는 추적하지 않았습니다.
관련 글: 용어 하나를 96개 파일 667곳에서 바꿨다 — 그리고 일부러 안 바꾼 세 경계 · Git 커밋 메시지 잘 쓰는 법 — 그리고 내 저장소 2년치로 검증해 본 결과 · Jinja가 캐시 버스터를 컴파일 시점에 굳혔다 · 분류체계와 메타클래스 트리 공통 로직을 헬퍼 4종으로 추출한 회고 · jquery-formset을 넣고 두 달 뒤 인덱스를 직접 다시 매겼다 · handleAjaxError를 다시 고민했다 · Next 14에서 15로 올리던 날 jest를 지웠다