Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Git 사용하기

Rust 프로젝트는 소스 코드를 관리하기 위해 Git을 사용합니다. 기여하려면 변경 사항이 컴파일러에 반영될 수 있도록 Git의 기능에 어느 정도 익숙해야 합니다.

이 페이지의 목표는 새 기여자가 흔히 겪는 질문과 문제 몇 가지를 다루는 것입니다. 여기서 Git의 기초도 일부 다루지만, 그래도 진행이 조금 빠르다고 느껴진다면 Atlassian의 이 튜토리얼의 Beginner 및 Getting started 섹션과 같은 Git 입문 자료를 먼저 읽어 보는 것이 좋습니다. GitHub에서도 초보자를 위한 문서와 가이드를 제공하며, 더 깊이 있는 Git 공식 책을 참고할 수도 있습니다.

이 가이드는 아직 완전하지 않습니다. 이 페이지로 해결되지 않는 git 문제를 겪는다면, 해결 방법을 문서화할 수 있도록 이슈를 열어 주십시오.

사전 준비 사항

여러분이 Git을 설치하고, rust-lang/rust를 포크한 뒤, 포크한 저장소를 PC에 클론했다고 가정합니다. 여기서는 Git을 다룰 때 명령줄 인터페이스를 사용하지만, 대체로 같은 작업을 할 수 있는 GUI와 IDE 통합 도구도 많이 있습니다.

포크를 클론했다면 로컬 저장소에서 origin으로 포크를 참조할 수 있습니다. 공식 rust-lang/rust 저장소에 대한 원격 저장소도 다음과 같이 설정해 두면 도움이 될 수 있습니다.

git remote add upstream https://github.com/rust-lang/rust.git

위는 HTTPS를 사용하는 경우이고,

git remote add upstream git@github.com:rust-lang/rust.git

위는 SSH를 사용하는 경우입니다.

참고: 이 페이지는 rust-lang/rust의 워크플로를 다루지만, Rust 프로젝트의 다른 저장소에 기여할 때도 유용할 것입니다.

표준 절차

다음은 대부분의 사소한 변경 사항과 PR에 사용하게 될 일반적인 절차입니다.

  1. main 위에서 변경 작업을 하고 있는지 확인하십시오: git checkout main.
  2. Rust 저장소에서 최신 변경 사항을 가져오십시오: git pull upstream main --ff-only. (이에 대한 자세한 내용은 병합 금지 정책을 참고하십시오.)
  3. 변경 사항을 위한 새 브랜치를 만드십시오: git checkout -b issue-12345-fix.
  4. 저장소를 변경하고 테스트하십시오.
  5. git add src/changed/file.rs src/another/change.rs로 변경 사항을 스테이징한 다음 git commit으로 커밋하십시오. 물론 중간 커밋을 만드는 것도 좋은 방법일 수 있습니다. git add .는 서브모듈 업데이트처럼 커밋하면 안 되는 변경 사항을 의도치 않게 커밋하기 너무 쉬우므로 사용하지 마십시오. git status를 사용하면 스테이징을 잊은 파일이 있는지 확인할 수 있습니다.
  6. 변경 사항을 포크에 푸시하십시오: git push --set-upstream origin issue-12345-fix (커밋을 추가한 후에는 git push를, 리베이스하거나 pull 후 리베이스한 후에는 git push --force-with-lease를 사용할 수 있습니다).
  7. 포크에서 rust-lang/rust의 main 브랜치로 PR을 여십시오.

리베이스가 필요한데 충돌이 발생한다면 리베이스를 참고하십시오. 오래 걸리는 기능이나 이슈를 작업하면서 업스트림을 추적하고 싶다면 최신 상태 유지하기를 참고하십시오.

리뷰어가 변경을 요청하면, 그 변경을 위한 절차도 몇몇 단계를 건너뛸 뿐 거의 같습니다.

  1. 코드의 최신 버전에서 변경 작업을 하고 있는지 확인하십시오: git checkout issue-12345-fix.
  2. 이전과 마찬가지로 추가 변경 사항을 만들고, 스테이징하고, 커밋하십시오.
  3. 변경 사항을 포크에 푸시하십시오: git push.

git 문제 해결

rust-lang/rust가 오래되었더라도 처음부터 다시 클론할 필요는 없습니다! 돌이킬 수 없을 정도로 망가뜨렸다고 생각하더라도, 저장소 전체를 다시 내려받지 않고 git 상태를 고치는 방법이 있습니다. 다음은 자주 겪을 수 있는 몇 가지 문제입니다:

실수로 병합 커밋을 만들었습니다.

Git에서 브랜치를 최신 변경 사항으로 갱신하는 방법은 병합과 리베이스, 두 가지가 있습니다. Rust는 리베이스를 사용합니다. 병합 커밋을 만들었더라도 고치기는 그리 어렵지 않습니다: git rebase -i upstream/main.

리베이스에 대한 자세한 내용은 리베이스를 참고하십시오.

GitHub에서 포크를 삭제했습니다!

git의 관점에서는 문제가 되지 않습니다. git remote -v를 실행하면 다음과 같이 출력됩니다:

$ git remote -v
origin  git@github.com:jyn514/rust.git (fetch)
origin  git@github.com:jyn514/rust.git (push)
upstream        https://github.com/rust-lang/rust (fetch)
upstream        https://github.com/rust-lang/rust (fetch)

포크의 이름을 바꿨다면 다음과 같이 URL을 변경할 수 있습니다:

git remote set-url origin <URL>

여기서 <URL>은 새 포크의 URL입니다.

실수로 서브모듈을 변경했습니다

보통은 rustbot이 GitHub에 cargo가 수정되었다는 댓글을 남길 때 이를 알아차립니다:

웹 UI에서 충돌을 발견할 수도 있습니다:

가장 흔한 원인은 변경 후 리베이스를 하고 나서, 서브모듈을 갱신하는 x를 먼저 실행하지 않은 채 git add .를 실행한 경우입니다. 또는 x fmt 대신 cargo fmt를 실행하여 서브모듈 안의 파일을 수정한 뒤 그 변경 사항을 커밋했을 수도 있습니다.

이를 고치려면 다음 단계를 수행하십시오(cargo가 아닌 다른 서브모듈을 변경했다면 src/tools/cargo를 해당 서브모듈의 경로로 바꾸십시오):

  1. 실수로 인한 변경 사항이 어느 커밋에 있는지 확인하십시오: git log --stat -n1 src/tools/cargo
  2. 해당 커밋의 변경 사항을 되돌리십시오: git checkout <my-commit>~ src/tools/cargo. ~는 그대로 입력하되 <my-commit>은 1단계의 출력으로 바꾸십시오.
  3. git에 변경 사항을 커밋하도록 지시하십시오: git commit --fixup <my-commit>
  4. 수정한 모든 서브모듈에 대해 1~3단계를 반복하십시오.
    • 여러 커밋에 걸쳐 서브모듈을 수정했다면, 수정한 각 커밋마다 1~3단계를 반복해야 합니다. git log 명령이 본인이 작성하지 않은 커밋을 보여 주면 멈추면 됩니다.
  5. 변경 사항을 기존 커밋에 스쿼시하십시오: git rebase --autosquash -i upstream/main
  6. 변경 사항을 푸시하십시오.

리베이스하려고 하면 “error: cannot rebase“가 표시됩니다

리베이스할 때 흔히 볼 수 있는 두 가지 오류는 다음과 같습니다:

error: cannot rebase: Your index contains uncommitted changes.
error: Please commit or stash them.
error: cannot rebase: You have unstaged changes.
error: Please commit or stash them.

(둘의 차이점은 https://git-scm.com/book/en/v2/Getting-Started-What-is-Git%3F#_the_three_states를 참조하십시오.)

이는 마지막으로 커밋한 이후에 변경한 내용이 있다는 뜻입니다. 리베이스하려면 변경 사항을 커밋하거나, “stash“라는 임시 커밋을 만들어 리베이스를 마친 뒤에도 변경 사항이 커밋되지 않은 상태로 남아 있게 하십시오. git이 이 “stash“를 자동으로 만들도록 설정할 수도 있으며, 이렇게 하면 거의 모든 경우에 “cannot rebase” 오류(리베이스할 수 없음)를 방지할 수 있습니다:

git config --global rebase.autostash true

stash에 대한 자세한 내용은 https://git-scm.com/book/en/v2/Git-Tools-Stashing-and-Cleaning을 참조하십시오.

’Untracked Files: src/stdarch’가 표시됩니까?

이는 library/ 디렉터리로 옮기는 과정에서 남은 것입니다. 안타깝게도 git rebase는 서브모듈의 이름 변경을 추적하지 않으므로, 디렉터리를 직접 삭제해야 합니다:

rm -r src/stdarch

<<< HEAD가 표시됩니까?

아마도 리베이스나 병합 충돌을 처리하던 중이었을 것입니다. 충돌을 해결하는 방법은 충돌을 참조하십시오. 변경 사항이 필요 없고 저장소의 깨끗한 사본을 되찾기만 하면 된다면 git reset을 사용할 수 있습니다:

# WARNING: this throws out any local changes you've made! Consider resolving the conflicts instead.
git reset --hard main

일부 ref를 푸시하지 못했습니다

git push가 제대로 동작하지 않고 다음과 같은 메시지를 출력합니다:

 ! [rejected]        issue-xxxxx -> issue-xxxxx (non-fast-forward)
error: failed to push some refs to 'https://github.com/username/rust.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

이 메시지가 제시하는 조언은 잘못되었습니다! Rust의 “병합 금지” 정책 때문에 git pull이 만드는 병합 커밋은 리베이스의 목적 자체를 무너뜨릴 뿐 아니라 최종 PR에서도 허용되지 않습니다! 대신 git push --force-with-lease를 사용하십시오.

직접 작성하지 않은 커밋을 Git이 리베이스하려고 합니까?

리베이스 목록에 커밋이 많거나, 병합 커밋이 있거나, 직접 작성하지 않은 다른 사람의 커밋이 보인다면 잘못된 브랜치 위로 리베이스하려는 것일 가능성이 높습니다. 예를 들어 rust-lang/rust를 가리키는 원격 저장소 upstream이 있는데, git rebase upstream/main 대신 git rebase origin/main을 실행했을 수 있습니다. 해결 방법은 리베이스를 중단하고 올바른 브랜치를 사용하는 것입니다:

git rebase --abort
git rebase --interactive upstream/main
잘못된 브랜치 위로 리베이스한 예를 보려면 여기를 클릭하십시오

서브모듈에 관한 간단한 참고 사항

git pull로 로컬 저장소를 업데이트하다 보면, 한 번도 편집하지 않은 파일을 수정했다고 Git이 알려 주는 경우가 가끔 있습니다. 예를 들어 git status를 실행하면 다음과 같은 결과가 나옵니다(new commits라는 언급에 주목하십시오):

On branch main
Your branch is up to date with 'origin/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/llvm-project (new commits)
	modified:   src/tools/cargo (new commits)

no changes added to commit (use "git add" and/or "git commit -a")

이 변경 사항은 파일의 변경이 아니라 서브모듈의 변경입니다(자세한 내용은 뒤에서 다룹니다). 이를 없애려면 다음과 같이 하십시오:

git submodule update

일부 서브모듈은 실제로는 필요하지 않습니다. 예를 들어 download-ci-llvm을 사용한다면 src/llvm-project를 체크아웃할 필요가 없습니다. 해당 서브모듈의 히스토리를 계속 가져오지 않으려면 git submodule deinit -f src/llvm-project를 사용할 수 있습니다. 이렇게 하면 이 서브모듈이 다시 수정됨으로 표시되는 일도 막을 수 있습니다.

리베이스와 충돌

로컬에서 코드를 편집하면, 기능 브랜치를 만들 당시에 존재하던 버전의 rust-lang/rust에 변경을 가하는 것입니다. 따라서 PR을 제출할 때, 그 이후 rust-lang/rust에 이루어진 변경 사항 중 일부가 여러분이 만든 변경 사항과 충돌할 수 있습니다. 이런 경우에는 변경 사항이 병합되기 전에 충돌을 해결해야 합니다. 그렇게 하려면 작업한 내용을 rust-lang/rust 위로 리베이스해야 합니다.

리베이스

기능 브랜치를 rust-lang/rust의 main 브랜치 최신 버전 위로 리베이스하려면, 브랜치를 체크아웃한 다음 이 명령을 실행하십시오.

git pull --rebase https://github.com/rust-lang/rust.git main

다음과 같은 오류가 발생한다면:

error: cannot pull with rebase: Your index contains uncommitted changes.
error: please commit or stash them.

작업 트리에 커밋되지 않은 작업이 있다는 뜻입니다. 이 경우 리베이스하기 전에 git stash를 실행하고, 리베이스를 마치고 모든 충돌을 해결한 뒤에 git stash pop을 실행하십시오.

브랜치를 main 위로 리베이스하면, 브랜치의 모든 변경 사항이 main의 가장 최신 버전에 다시 적용됩니다. 다시 말해, Git은 여러분이 main의 이전 버전에 가한 변경 사항을 마치 main의 새 버전에 가한 것처럼 처리하려고 합니다. 이 과정에서 적어도 한 번은 “리베이스 충돌“을 겪게 될 것이라고 예상해야 합니다. 이는 여러분의 변경 사항이 이미 이루어진 다른 변경 사항과 충돌하여 Git이 변경 사항을 다시 적용하는 데 실패할 때 발생합니다. 이런 일이 일어났는지는 출력에서 다음과 같은 줄이 보이는지로 알 수 있습니다.

CONFLICT (content): Merge conflict in file.rs

이 파일들을 열면 다음과 같은 형태의 부분이 보입니다.

<<<<<<< HEAD
Original code
=======
Your code
>>>>>>> 8fbf656... Commit fixes 12345

이는 파일에서 Git이 어떻게 리베이스해야 할지 알아내지 못한 줄들을 나타냅니다. <<<<<<< HEAD와 ======= 사이의 부분에는 main의 코드가 있고, 다른 쪽에는 여러분 버전의 코드가 있습니다. 충돌을 어떻게 처리할지 결정해야 합니다. 여러분의 변경 사항을 유지하거나, main의 변경 사항을 유지하거나, 둘을 합칠 수 있습니다.

일반적으로 충돌 해결은 두 단계로 이루어집니다. 먼저, 해당 충돌을 수정하십시오. 원하는 변경 사항이 반영되도록 파일을 편집하고, 그 과정에서 <<<<<<<, =======, >>>>>>> 줄을 제거하십시오. 둘째로, 주변 코드를 확인하십시오. 충돌이 있었다면 논리적인 오류도 주변에 남아 있을 가능성이 높습니다! 이 시점에서 x check를 실행하여 눈에 띄는 오류가 없는지 확인하는 것이 좋습니다.

충돌을 모두 해결했으면, 충돌이 있었던 파일들을 git add로 스테이징해야 합니다. 그런 다음 git rebase --continue를 실행하여, 충돌을 해결했으니 리베이스를 마무리하라고 Git에 알리십시오.

리베이스가 성공하면 git push --force-with-lease로 포크의 해당 브랜치를 업데이트하십시오.

최신 상태 유지하기

위 섹션은 작업을 리베이스하고 병합 충돌을 처리하는 방법을 구체적으로 안내합니다. 다음은 로컬 저장소를 업스트림 변경 사항에 맞춰 최신 상태로 유지하는 방법에 대한 일반적인 조언입니다:

로컬 main 브랜치에서 git pull upstream main을 정기적으로 실행하면 이 브랜치를 최신 상태로 유지할 수 있습니다. 기능 브랜치도 최신 상태로 유지하는 것이 좋습니다. 풀한 후에는 기능 브랜치를 체크아웃하고 리베이스할 수 있습니다:

git checkout main
git pull upstream main --ff-only # to make certain there are no merge commits
git rebase main feature_branch
git push --force-with-lease # (set origin to be the same as local)

병합 금지 정책에 따라 병합을 피하려면 git config pull.ff only를 사용하는 것이 좋습니다(이 설정은 로컬 저장소에만 적용됩니다). 이렇게 하면 매번 --ff-only나 --rebase를 넘기지 않아도 git pull할 때 Git이 병합 커밋을 만들지 않습니다.

main에서 git push --force-with-lease를 실행하여 기능 브랜치가 GitHub 쪽 상태와 동기화되어 있는지 다시 확인할 수도 있습니다.

고급 리베이스

커밋 스쿼시하기

커밋들을 서로 “스쿼시“하면 하나의 커밋으로 합쳐집니다. 이렇게 하면 히스토리가 단순해지며, 이는 장점이자 단점입니다. 변경이 어떤 단계를 거쳐 이루어졌는지 추적할 수 없게 되는 반면, 히스토리는 다루기 더 쉬워집니다.

rust-lang/rust 저장소의 PR에서 커밋을 스쿼시하는 가장 쉬운 방법은 PR에 댓글로 @bors squash 명령을 남기는 것입니다. 기본적으로 bors는 PR의 모든 커밋 메시지를 합쳐 스쿼시된 커밋 메시지로 만듭니다. 커밋 메시지를 직접 지정하려면 @bors squash msg="<commit message>"를 사용하십시오. 예를 들면 @bors squash msg="Improve diagnostics for missing lifetime parameter"와 같습니다.

로컬 git 작업으로 커밋을 스쿼시하려면 아래 내용을 계속 읽으십시오.

충돌이 없고 히스토리를 정리하기 위해 스쿼시만 하는 경우에는 git rebase --interactive --keep-base main을 사용하십시오. 이렇게 하면 PR의 분기 지점이 그대로 유지되므로, 여러 번의 리베이스에 걸쳐 무엇이 바뀌었는지 diff를 검토하기가 더 쉬워집니다.

스쿼시는 충돌 해석 과정의 일부로도 유용할 수 있습니다. 브랜치에 같은 코드를 연속해서 여러 번 고쳐 쓴 커밋이 있거나 리베이스 충돌이 매우 심각한 경우, git rebase --interactive main을 사용하면 이 과정을 더 세밀하게 제어할 수 있습니다. 이 명령을 사용하면 커밋을 건너뛰거나, 건너뛰지 않은 커밋을 편집하거나, 커밋이 적용되는 순서를 바꾸거나, 커밋들을 서로 “스쿼시“할 수 있습니다.

또는 다음과 같이 커밋 히스토리를 포기할 수도 있습니다:

# squash all the changes into one commit so you only have to worry about conflicts once
git rebase --interactive --keep-base main  # and squash all changes along the way
git rebase main
# fix all merge conflicts
git rebase --continue

마지막 몇 개의 커밋만 함께 스쿼시하고 싶을 수도 있습니다. 해당 커밋들이 실제 변경이 아니라 “수정(fixup)“일 뿐인 경우가 그렇습니다. 예를 들어 git rebase --interactive HEAD~2를 사용하면 마지막 두 커밋만 편집할 수 있습니다.

git range-diff

리베이스를 마친 뒤 변경 사항을 푸시하기 전에, 이전 브랜치와 새 브랜치 사이의 변경 사항을 검토하는 것이 좋습니다. 이는 git range-diff main @{upstream} HEAD로 할 수 있습니다.

range-diff의 첫 번째 인자(이 경우 main)는 이전 브랜치와 새 브랜치를 비교할 때 기준이 되는 기준 리비전입니다. 두 번째 인자는 브랜치의 이전 버전입니다. 이 경우 @upstream은 GitHub에 푸시한 버전을 뜻하며, 이는 사람들이 풀 리퀘스트에서 보게 될 내용과 같습니다. 마지막으로 range-diff의 세 번째 인자는 브랜치의 새 버전입니다. 이 경우 HEAD이며, 이는 로컬 저장소에서 현재 체크아웃된 커밋입니다.

이와 동일한 축약형인 git range-diff main @{u} HEAD도 사용할 수 있다는 점에 유의하십시오.

일반적인 Git diff와 달리, range-diff 출력에서는 - 또는 + 옆에 또 다른 - 또는 +가 표시됩니다. 왼쪽 표시는 이전 브랜치와 새 브랜치 사이의 변경 사항을 나타내고, 오른쪽 표시는 여러분이 커밋한 변경 사항을 나타냅니다. 따라서 range-diff는 이전 diff와 새 diff 사이의 차이를 보여 주므로 “diff의 diff“라고 생각할 수 있습니다.

다음은 git range-diff 출력의 예시입니다(Git 문서에서 발췌):

-:  ------- > 1:  0ddba11 Prepare for the inevitable!
1:  c0debee = 2:  cab005e Add a helpful message at the start
2:  f00dbal ! 3:  decafe1 Describe a bug
    @@ -1,3 +1,3 @@
     Author: A U Thor <author@example.com>

    -TODO: Describe a bug
    +Describe a bug
    @@ -324,5 +324,6
      This is expected.

    -+What is unexpected is that it will also crash.
    ++Unexpectedly, it also crashes. This is a bug, and the jury is
    ++still out there how to fix it best. See ticket #314 for details.

      Contact
3:  bedead < -:  ------- TO-UNDO

(터미널에서 보는 git range-diff 출력은 색상이 적용되므로 아마 이 예시보다 읽기 쉬울 것입니다.)

git range-diff의 또 다른 기능은 git diff와 달리 커밋 메시지도 비교한다는 점입니다. 이 기능은 여러 커밋 메시지를 수정할 때 올바른 부분을 변경했는지 확인하는 데 유용할 수 있습니다.

git range-diff는 매우 유용한 명령이지만, 출력 형식에 익숙해지기까지 시간이 걸릴 수 있다는 점에 유의하십시오. 이 명령에 관한 Git 문서, 특히 “Examples” 섹션도 유용할 것입니다.

병합 금지 정책

rust-lang/rust 저장소는 이른바 “리베이스 워크플로“를 사용합니다. 이는 PR에 병합 커밋을 허용하지 않는다는 뜻입니다. 따라서 로컬에서 git merge를 실행하고 있다면, 대신 리베이스를 해야 할 가능성이 높습니다. 물론 항상 그런 것은 아닙니다. git pull이 보통 수행하는 병합처럼 병합이 단순히 fast-forward라면 병합 커밋이 생성되지 않으므로 걱정할 필요가 없습니다. git config merge.ff only를 한 번 실행하면(이 설정은 로컬 저장소에 적용됩니다) 여러분이 수행하는 모든 병합이 이러한 유형이 되므로 실수할 일이 없습니다.

이렇게 결정한 데에는 여러 이유가 있으며, 다른 모든 결정과 마찬가지로 이 결정도 트레이드오프입니다. 주된 장점은 커밋 기록이 대체로 선형이라는 것입니다. 이 덕분에 bisect가 훨씬 간단해지고, 기록과 커밋 로그를 따라가고 이해하기가 훨씬 쉬워집니다.

리뷰를 위한 팁

참고: 이 섹션은 PR을 작성하는 것이 아니라 리뷰하는 것에 관한 내용입니다.

공백 숨기기

GitHub에는 공백 변경을 숨기는 버튼이 있어 유용하게 쓸 수 있습니다. 로컬에서 git diff -w origin/main을 사용하여 변경 사항을 볼 수도 있습니다.

PR 가져오기

로컬에서 PR을 체크아웃하려면 git fetch upstream pull/NNNNN/head && git checkout FETCH_HEAD를 사용할 수 있습니다.

GitHub의 CLI 도구를 사용할 수도 있습니다. GitHub는 PR에 로컬에서 체크아웃하는 명령을 복사해 붙여 넣을 수 있는 버튼을 표시합니다. 자세한 내용은 https://cli.github.com/를 참고하십시오.

GitHub dev 사용하기

GitHub 웹 UI의 대안으로, GitHub Dev는 저장소와 PR을 탐색할 수 있는 웹 기반 편집기를 제공합니다. URL에서 github.com을 github.dev로 바꾸거나 GitHub 페이지에서 .을 누르면 열 수 있습니다. 자세한 내용은 github.dev 편집기 문서를 참고하십시오.

큰 코드 영역 이동하기

파일 내에서 이루어진 대규모 이동에 대한 Git과 GitHub의 기본 diff 보기는 상당히 불편합니다. 모든 줄을 삭제된 것으로, 또 모든 줄을 추가된 것으로 표시하므로 각 줄을 직접 비교해야 합니다. Git에는 이동된 줄을 다른 색으로 표시하는 옵션이 있습니다:

git log -p --color-moved=dimmed-zebra --color-moved-ws=allow-indentation-change

자세한 내용은 --color-moved 문서를 참고하십시오.

range-diff

PR 작성자를 위한 관련 섹션을 참고하십시오. 이는 force-push된 코드를 비교하여 예상치 못한 변경이 없는지 확인할 때 유용합니다.

특정 파일의 변경 사항 무시하기

저장소에 있는 많은 대용량 파일은 자동으로 생성된 것입니다. 해당 파일들의 변경 사항을 무시한 diff를 보려면 다음 구문을 사용할 수 있습니다(예: Cargo.lock):

git log -p ':!Cargo.lock'

임의의 패턴도 지원됩니다(예: :!compiler/*). 패턴은 .gitignore와 같은 구문을 사용하며, 패턴임을 나타내기 위해 앞에 :를 붙입니다.

Git 서브모듈

참고: 서브모듈은 알아 두면 좋지만, rustc에 기여하기 위한 절대적인 전제 조건은 아닙니다. Git을 처음 사용한다면, 이 섹션을 읽기 전에 Git의 주요 개념에 먼저 익숙해지는 것이 좋습니다.

rust-lang/rust 저장소는 rust 저장소 안에서 다른 Rust 프로젝트를 사용하기 위한 방법으로 Git 서브모듈을 사용합니다. 예로는 Rust가 포크한 llvm-project, cargo, 그리고 stdarch와 backtrace 같은 라이브러리가 있습니다.

이러한 프로젝트는 별도의 Git(및 GitHub) 저장소에서 개발 및 유지 관리되며, 자체 Git 히스토리/커밋, 이슈 트래커, PR을 가지고 있습니다. 서브모듈을 사용하면 rust 저장소 안에 일종의 내장된 하위 저장소를 만들고, 이를 rust 저장소의 디렉터리처럼 사용할 수 있습니다.

llvm-project를 예로 들어 보겠습니다. llvm-project는 rust-lang/llvm-project 저장소에서 유지 관리되지만, rust-lang/rust에서 컴파일러가 코드 생성과 최적화를 위해 사용합니다. 우리는 이를 src/llvm-project 폴더에 서브모듈로 두어 rust에 가져옵니다.

서브모듈의 내용은 Git에서 무시됩니다. 서브모듈은 어떤 의미에서 저장소의 나머지 부분과 격리되어 있습니다. 하지만 cd src/llvm-project를 실행한 다음 git status를 실행해 보면:

HEAD detached at 9567f08afc943
nothing to commit, working tree clean

git의 관점에서 보면, 여러분은 더 이상 rust 저장소가 아니라 llvm-project 저장소에 있습니다. “detached HEAD” 상태, 즉 브랜치가 아니라 특정 커밋에 있다는 것을 알 수 있습니다.

이는 다른 의존성과 마찬가지로, 어떤 버전을 사용할지 제어할 수 있기를 원하기 때문입니다. 서브모듈은 바로 이것을 가능하게 합니다. 모든 서브모듈은 특정 커밋에 “고정“되며, 이 커밋은 수동으로 수정하지 않는 한 바뀌지 않습니다. llvm-project 디렉터리에서 git checkout <commit>을 사용한 뒤 rust 디렉터리로 돌아가면, 예를 들어 git add src/llvm-project를 실행하여 이 변경 사항을 다른 변경 사항과 마찬가지로 스테이징할 수 있습니다. (변경 사항을 커밋하도록 스테이징하지 않으면, x를 실행할 때 서브모듈을 자동으로 “업데이트“하면서 이전 커밋으로 되돌아가 변경 사항이 그냥 취소될 위험이 있다는 점에 유의하십시오.)

이러한 버전 선택은 보통 프로젝트 메인테이너가 수행하며, 이와 같은 모습입니다.

Git 서브모듈은 익숙해지는 데 시간이 좀 걸리므로, 아직 완전히 이해되지 않더라도 걱정하지 마십시오. 서브모듈을 직접 사용해야 하는 경우는 드물며, 다시 말하지만 Rust에 기여하기 위해 서브모듈에 대한 모든 것을 알 필요는 없습니다. 서브모듈이 존재한다는 것, 그리고 서브모듈은 Git이 우리 대신 깔끔하고 꽤 편리하게 처리해 주는 일종의 내장된 하위 저장소 의존성에 해당한다는 것만 알아 두십시오.

서브모듈 하드 리셋하기

때때로 (git status를 실행하면) 다음과 같은 상황을 마주칠 수 있습니다

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)
  (commit or discard the untracked or modified content in submodules)
        modified:   src/llvm-project (new commits, modified content)

그리고 git submodule update를 실행하려고 하면 다음과 같은 오류와 함께 심각하게 실패합니다

error: RPC failed; curl 92 HTTP/2 stream 7 was not closed cleanly: CANCEL (err 8)
error: 2782 bytes of body are still expected
fetch-pack: unexpected disconnect while reading sideband packet
fatal: early EOF
fatal: fetch-pack: invalid index-pack output
fatal: Fetched in submodule path 'src/llvm-project', but it did not contain 5a5152f653959d14d68613a3a8a033fb65eec021. Direct fetching of that commit failed.

(new commits, modified content)가 보이면 다음을 실행할 수 있습니다

git submodule foreach git reset --hard

그런 다음 git submodule update를 다시 시도하십시오.

git 서브모듈 deinit하기

그래도 해결되지 않으면 모든 git 서브모듈을 deinit해 볼 수 있습니다…

git submodule deinit -f --all

안타깝게도 때로는 어떤 이유로 로컬 git 서브모듈 설정이 완전히 엉망이 될 수 있습니다.

fatal: not a git repository: <submodule>/../../.git/modules/<submodule> 해결하기

때때로 도무지 알 수 없는 이유로 다음과 같은 상황을 마주칠 수 있습니다

fatal: not a git repository: src/gcc/../../.git/modules/src/gcc

이 경우 해당 서브모듈 경로, 즉 이 예시에서는 <submodule_path> = src/gcc에 대해 다음을 수행해야 합니다:

  1. rm -rf <submodule_path>/.git
  2. rm -rf .git/modules/<submodule_path>/config
  3. 어떤 이유로 .gitconfig 잠금이 고아 상태가 되었다면 rm -rf .gitconfig.lock

그런 다음 ./x fmt와 같은 명령을 실행하여 bootstrap이 서브모듈 체크아웃을 대신 관리하도록 하십시오.

git blame 중에 커밋 무시하기

일부 커밋에는 기능은 변경하지 않는 대규모 서식 재정리 변경 사항이 포함되어 있습니다. 이러한 커밋은 .git-blame-ignore-revs를 통해 git blame이 무시하도록 지정할 수 있습니다:

  1. .git-blame-ignore-revs를 무시할 커밋 목록으로 사용하도록 git blame을 설정하십시오: git config blame.ignorerevsfile .git-blame-ignore-revs
  2. git blame이 무시하기를 원하는 적절한 커밋을 추가하십시오.

.git-blame-ignore-revs에 추가하는 커밋에는 사람들이 해당 커밋이 무시되는 이유를 쉽게 파악할 수 있도록 주석을 포함하십시오.