Skip to content
This page has been auto-translated and may contain errors.View in English

커밋 루프

docs.scrimba.com

지난 20분간 weather-app을 작업했다고 가정해보세요. src/index.js의 실제 버그를 수정했습니다. 온도 변환이 잘못되었었고, 그 과정에서 styles.css의 스타일링도 시작했지만 그 변경사항은 완성되지 않았으며 다른 사람에게 보여줄 준비가 되지 않았습니다. 지금 바로 버그 수정을 자신의 체크포인트로 저장하고 싶지만, 완성되지 않은 스타일링을 함께 포함하고 싶지는 않습니다.

이것이 바로 커밋 루프가 존재하는 이유입니다. 아직 저장소를 설정하지 않았다면, Your first repository에서 git init과 클로닝에 대해 설명합니다. 이제부터 이 장은 저장소가 열려있다고 가정합니다.

세 개의 정류장: 작업 디렉토리, 스테이징 영역, 커밋

Git의 모든 변경사항은 영구 기록이 되기 전에 세 개의 정류장을 지나갑니다. 작업 디렉토리는 지금 디스크에 있는 프로젝트이며, 현재 편집 중인 파일들입니다. **스테이징 영역**은 다음에 저장하고 싶은 변경사항을 정확히 배치하는 임시 보관 장소입니다. 커밋은 저장된 스냅샷 자체이며, 작성하면 이를 설명하는 메시지와 함께 저장됩니다.

이사를 할 때 짐을 싸는 것으로 생각해보세요. 전체 집은 작업 디렉토리입니다: 현재 상태가 어떤 것이든 당신이 소유한 모든 것입니다. 문 앞에 짐을 싸서 준비해놓은 상자들은 스테이징 영역입니다: 당신이 의도적으로 선택한 것만입니다. 그 상자들을 싣고 떠나는 이사 트럭은 커밋입니다: 정확히 무엇이 떠났는지, 언제 떠났는지에 대한 기록이며, 무엇이 들어있는지 설명하는 라벨이 붙어있습니다.

이 정신적 모델이 실제 터미널에서 어떻게 나타나는지 보겠습니다. weather-app의 두 파일을 모두 편집한 후, git status는 아무것도 변경하지 않고 현재 상태를 읽습니다:

bash
$ git status
On branch main
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   src/index.js
        modified:   styles.css

no changes added to commit (use "git add" to commit)

파일 편집은 작업 디렉토리만 변경하기 때문에 두 파일 모두 "스테이징되지 않은" 상태로 표시됩니다. git add로 Git에게 해당 파일을 스테이징 영역에 놓으라고 지시할 때까지는 아무것도 스테이징 영역으로 이동하지 않습니다. 다음 섹션에서 이에 대해 설명합니다.

이것이 스테이징 영역이 존재하는 이유가 명확해지는 부분입니다. 스테이징 영역을 통해 작업 디렉토리의 다른 변경사항과 무관하게 커밋에 정확히 무엇이 들어갈지 선택할 수 있습니다. 스테이징 영역이 없다면, 체크포인트를 저장할 때마다 디스크의 모든 변경사항을 한 번에 가져가야 하므로 준비되지 않은 것도 함께 포함될 것입니다.

스테이징 영역이 있으면, src/index.js의 완성된 버그 수정만 스테이징하고, 완성되지 않은 styles.css는 제외한 후, 실제로 완성된 부분만 커밋할 수 있습니다. 스테이징과 작업 디렉토리가 동시에 다른 내용을 유지할 수 있다는 이 한 가지 세부사항은 첫 주에 거의 모든 사람을 혼란스럽게 합니다. 일단 이것을 이해하면, Git의 나머지 일상적인 워크플로우가 훨씬 더 명확해집니다.

일상적인 작업에서, addcommit 전에 git status를 루틴적인 단계로 삼으세요. 뭔가 이상하다고 느껴지지 않더라도 말입니다. 비용이 들지 않고, 프로젝트를 변경하지 않으며, 의도한 것보다 더 많은 것을 커밋하는 일반적인 실수를 방지합니다. 상태 확인, 스테이징, 커밋 전 상태 재확인의 습관을 들이세요: 몇 초만 걸리며 커밋의 메시지가 실제로 들어간 것과 항상 일치합니다.

스테이징 영역은 **인덱스**라고 불리는 실제 파일이며, 저장소 내의 .git/index에 위치합니다. git add를 실행할 때마다 Git은 어떤 파일의 어떤 버전이 스테이징되었는지 기록하도록 그 파일을 업데이트합니다. git commit을 실행하면, Git은 작업 디렉토리를 다시 확인하지 않고 인덱스가 나타내는 것에서만 새로운 커밋을 만듭니다. 이것이 앞서 git status에서 본 분리의 메커니즘 전부입니다: 디스크에 편집된 두 파일이 있지만 인덱스는 아직 어느 것도 추적하지 않으므로, 그 다음 커밋은 현재 아무것도 포함하지 않을 것입니다.

JunoGit의 세 영역 작업 디렉토리는 지금 있는 파일들입니다. 스테이징 영역은 다음에 저장하고 싶은 변경사항을 정확히 배치하는 곳이며, 커밋은 작성하면 저장되는 스냅샷입니다. 파일을 편집해도 자동으로 스테이징되지 않으며, git add로 무엇을 포함할지 선택하고, 이것이 완성된 변경사항 하나를 저장하면서 완성되지 않은 것을 제외할 수 있게 해줍니다.
JunoGit의 세 영역 작업 디렉토리, 스테이징 영역, 커밋: 편집은 첫 번째에서 일어나고, git add는 정확히 선택한 것을 두 번째로 이동하며, git commit은 두 번째를 영구 스냅샷으로 저장합니다. 스테이징 전과 커밋 전에 git status를 실행하세요. 비용이 들지 않으며 커밋이 저장하려던 것과 일치하도록 유지합니다.
JunoGit의 세 영역 스테이징 영역은 .git/index의 인덱스라는 실제 파일이며, git commit은 작업 디렉토리에서 직접가 아니라 인덱스에서 스냅샷을 만듭니다. 따라서 편집된 두 파일이 모두 스테이징되지 않은 상태로 표시될 수 있습니다: 인덱스가 아직 둘 다 알지 못하기 때문입니다. 인덱스를 이렇게 이해하면 모든 스테이징 명령이 마법 같은 느낌보다는 한 파일을 읽고 쓰는 것처럼 느껴집니다.

변경사항 스테이징: git add

git add는 변경사항을 작업 디렉토리에서 스테이징 영역으로 이동합니다. 다음 커밋에 포함하고 싶은 파일을 지정하세요:

bash
$ git add src/index.js
$ git status
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   src/index.js

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

src/index.js는 "커밋할 변경사항"으로 이동했습니다. 버그 수정은 스테이징되었고 준비되었습니다. styles.css는 여전히 "스테이징되지 않은" 아래에 나열되어 있으며, 이것이 리스타일링이 완성되지 않았을 때 원하는 상태입니다. git add styles.css도 실행하면 역시 스테이징됩니다; git add .은 현재 폴더의 모든 변경된 파일을 한 번에 스테이징하며, 디스크의 모든 것이 실제로 준비되었을 때 편리합니다.

때로 한 파일에 저장하고 싶은 변경사항과 원하지 않는 변경사항이 모두 있으며, 두 파일로 나누는 것은 실용적이지 않습니다. git add -p <file> (--patch의 약자)는 파일의 변경사항을 훅이라고 불리는 작은 청크 단위로 살펴보고 각각에 대해 결정하도록 요청합니다:

bash
$ git add -p src/index.js
@@ -12,7 +12,7 @@ function celsiusToFahrenheit(celsius) {
-  return celsius * (9 / 5 + 32)
+  return (celsius * 9) / 5 + 32
Stage this hunk [y,n,q,a,d,e,?]?

해당 훅을 스테이징하려면 y로, 나중을 위해 남겨두려면 n으로, 중단하려면 q로 답하세요. 이것은 한 파일 내에서도 커밋 크기 수준의 제어를 제공하며, "이 파일의 절반은 완료됨"이라는 상황에 처할 때마다 사용할 가치가 있습니다.

git add는 두 가지를 동시에 수행하며, 둘 다 아는 것이 나중에 경험할 문제를 설명합니다. 첫째, 파일의 현재 내용을 새로운 **블롭**에 씁니다. 블롭은 Git이 영구적으로 저장하는 객체이며, 커밋과 동일한 방식으로 해시로 식별되며, 한 파일의 정확한 내용을 한 순간에 보유합니다. 둘째, 인덱스가 그 파일의 그 블롭을 가리키도록 업데이트합니다. add 후에는 아무것도 파일을 다시 살펴보지 않습니다.

따라서 git add src/index.js를 실행한 후 커밋 전에 같은 파일을 계속 편집하면, git status는 스테이징되었으면서도 수정된 것으로 동시에 표시합니다: 인덱스는 add를 실행했을 때의 블롭을 여전히 가리키고 있는 반면, 작업 디렉토리는 계속 변경되었습니다. git commit은 스테이징된 블롭만 보므로, 저장하기 전에 더 새로운 편집을 끌어오려면 git add를 다시 실행해야 합니다.

Juno변경사항 스테이징git add <file>은 해당 파일의 현재 변경사항을 스테이징 영역으로 이동하며, 다음 커밋을 준비합니다. 아직 추가하지 않은 파일은 남아있으므로 한 가지를 완료하고 다른 것은 중간에 둘 수 있습니다. git add .은 모든 변경된 파일을 한 번에 스테이징하며, 모든 것이 실제로 준비되었을 때 편리합니다.
Juno변경사항 스테이징git add <file>은 전체 파일을 스테이징하며, git add -p <file>은 파일의 일부만 준비되었을 때 훅 단위로 스테이징할 수 있게 해줍니다. 두 번째 것이 "이 파일의 절반이 완료됨"이 맞을 때 사용할 도구입니다.
Juno변경사항 스테이징git add는 파일의 현재 내용을 블롭에 쓰고 인덱스가 그것을 가리키도록 하며, 이후로 파일을 감시하지 않습니다. 커밋 전에 파일을 다시 편집하면 status는 스테이징되었으면서도 수정된 것으로 표시합니다. 인덱스가 여전히 더 오래된 블롭을 보유하기 때문입니다. 더 새로운 편집을 끌어오려면 add를 다시 실행하세요. 처음 이 상황에 부딪혔을 때 저를 10분간 혼란스럽게 했습니다.

스냅샷 저장: git commit

스테이징 영역이 원하는 것을 보유하면, git commit은 이를 영구 스냅샷으로 저장합니다. -m 플래그 (message의 약자)는 함께 전달되는 설명을 제공합니다:

bash
$ git commit -m "온도 변환 공식에서 섭씨-화씨 변환 버그 수정"
[main 4f2a1c9] 온도 변환 공식에서 섭씨-화씨 변환 버그 수정
 1 file changed, 1 insertion(+), 1 deletion(-)

스테이징한 것만 들어갔으므로 src/index.js만 들어갔습니다. styles.css는 여전히 작업 디렉토리에 편집된 채로 남아있으며, 리스타일링이 완료될 때까지 기다리고 있습니다. 짧은 아이디인 4f2a1c9는 정확히 이 커밋의 이름이며, History 장에서 이와 같은 프로젝트의 전체 커밋 목록을 읽는 방법을 다룹니다.

명령형으로 커밋 메시지를 작성하세요. "온도 변환 버그 수정"은 Git 자체가 사용하는 표현 ("이 커밋이...") 과 일치하며, 커밋을 한 줄에 나열하는 도구에서 깔끔하게 읽힙니다. 한 줄 수정 이상이 필요한 경우, 변경이 필요한 이유를 설명하는 본문을 추가하세요. diff는 이미 무엇이 변경되었는지 보여주므로, 본문을 그 뒤 논리를 위해 남겨두세요:

bash
$ git commit -m "온도 변환 공식에서 섭씨-화씨 변환 버그 수정

공식이 잘못된 순서로 연산자 우선순위를 적용하고 있어서
UI의 따뜻한 온도를 1도씩 내림차순으로 반올림했습니다."

커밋 직후에 오타나 놓친 세부사항을 발견하면, git commit --amend는 위에 또 다른 것을 추가하는 대신 가장 최근의 커밋을 새로운 것으로 교체합니다:

bash
$ git commit --amend -m "온도 변환 공식에서 섭씨-화씨 변환 버그 수정"

아직 어디서도 공유하지 않은 커밋만 수정하세요. 일단 푸시하면 (프로젝트의 공유 복사본으로 보내면), 수정은 다른 사람들이 이미 가지고 있을 수 있는 기록을 다시 쓰며, Undoing things는 그 후 커밋을 고치는 안전한 방법을 다룹니다.

커밋 자체는 세 부분이 있는 작은 객체입니다: **트리**에 대한 포인터, 작성자와 메시지, 그리고 부모 커밋에 대한 포인터. 트리는 그 순간 전체 프로젝트의 디렉토리 구조의 스냅샷입니다: 모든 파일과 폴더에 대해, 이름과 블롭 (파일의 내용) 또는 다른 트리 (하위폴더)에 대한 포인터를 기록합니다. git add를 실행하면 한 번에 한 파일씩 블롭을 쓰고 인덱스를 업데이트합니다; git commit은 현재 인덱스를 하나의 완성된 트리로 바꾸고 커밋 객체로 감싸는 순간입니다.

동일한 내용을 가진 두 파일은 서로 다른 폴더에 있어도 정확히 같은 블롭을 가리키므로, 커밋은 그 바이트를 두 번 저장하지 않습니다. 그 구조는 또한 두 커밋을 비교하는 것을 효율적으로 만듭니다: Git은 두 트리를 나란히 걸으며 포인터가 실제로 다른 블롭만 열고, 그대로 유지된 모든 파일은 건너뜁니다.

Juno커밋 저장git commit -m "메시지"는 현재 스테이징된 것을 영구 스냅샷으로 저장하며, 제공한 메시지를 사용합니다. 스테이징하지 않은 것은 남아있고 편집 가능합니다. 메시지를 짧고 명확하게 유지하세요. 커밋이 무엇을 하는지 미래의 당신이 예상보다 더 자주 읽을 것입니다.
Juno커밋 저장 커밋 메시지를 명령형으로 작성하세요. "수정됨" 대신 "수정", 그리고 diff만으로는 왜 변경했는지 알기 어려울 때마다 본문을 추가합니다. git commit --amend는 새로운 것을 스택하는 대신 마지막 커밋을 교체하며, 메시지 오타에는 완벽하고, 그 커밋을 아직 어디든 푸시하지 않은 경우입니다.
Juno커밋 저장 커밋은 트리를 가리키며, 트리는 이름을 블롭과 추가 트리로 매핑하며, 커밋 시점에 인덱스가 보유한 것에서 만든 프로젝트의 완전한 중첩 스냅샷입니다. add는 블롭을 쓰고 commit은 인덱스를 완성된 트리로 바꾸며, 프로젝트의 어디서든 동일한 내용은 두 번 저장되는 대신 하나의 블롭을 공유합니다. 그 모양을 그려보면 두 가지가 더 이상 마법 같지 않습니다: 아무것도 중복되지 않는 이유, 그리고 두 커밋을 비교하는 것이 모든 파일을 다시 읽는 대신 두 트리를 나란히 걷는 이유.