Git 부트캠프와 치트 시트

참고

이 절에서는 CPython 워크플로의 일반적인 작업에 관한 지침을 제공합니다. 이 절은 Git과 GitHub에 어느 정도 익숙한 신규 기여자를 돕기 위해 작성되었습니다.

Git과 GitHub를 처음 사용한다면 풀 리퀘스트를 제출하기 전에 이 지침에 익숙해지십시오. Git과 GitHub로 이러한 작업을 수행하는 방법은 여러 가지이므로, 이 절에서는 신규 기여자에게 적합한 한 가지 방법을 설명합니다. 숙련된 기여자는 다른 방식을 원할 수도 있습니다.

이 절에서는 CPython 워크플로와 관련하여 자주 사용되는 몇 가지 Git 명령을 살펴봅니다.

참고

자주 하는 작업에 Git 별칭을 설정해 두면 유용할 수 있습니다. 자세한 내용은 Git 문서에서 확인할 수 있습니다.

CPython GitHub 저장소 포크하기

이 작업은 한 번만 하면 됩니다.

  1. 다음 주소로 이동하십시오: https://github.com/python/cpython

  2. 페이지 오른쪽 상단 근처에 있는 Fork를 누르십시오.

  3. “main 브랜치만 복사”의 선택을 해제하십시오.

  4. Create fork 버튼을 누르십시오.

  5. 포크한 CPython 저장소는 https://github.com/<username>/cpython에 생성됩니다.

포크한 CPython 저장소 클론하기

이 작업은 컴퓨터마다 한 번만 하면 됩니다. 명령줄에서 다음을 실행하십시오.:

$ git clone git@github.com:<username>/cpython.git

upstream 원격 저장소도 구성할 것을 권장합니다.:

$ cd cpython
$ git remote add upstream https://github.com/python/cpython

SSH 기반 또는 HTTPS 기반 URL을 사용할 수도 있습니다.

원격 저장소 구성하기

upstream 원격 저장소에서 main을 풀하도록 git을 구성하십시오.:

$ git config --local branch.main.remote upstream

upstream으로는 절대 푸시해서는 안 되므로, 항상 origin으로 푸시하도록 git을 구성하십시오.:

$ git remote set-url --push upstream git@github.com:<username>/cpython.git

원격 저장소 나열하기

구성된 원격 저장소를 URL과 함께 나열하려면 다음을 실행하십시오.:

$ git remote -v

원격 저장소는 두 개여야 합니다. 포크한 CPython 저장소를 가리키는 origin과 공식 CPython 저장소를 가리키는 upstream입니다:

origin  git@github.com:<username>/cpython.git (fetch)
origin  git@github.com:<username>/cpython.git (push)
upstream        https://github.com/python/cpython (fetch)
upstream        git@github.com:<username>/cpython.git (push)

main의 업스트림을 확인하려면 다음을 실행하십시오.:

$ git config branch.main.remote

upstream이 출력되어야 합니다. 이는 main의 변경 사항을 upstream 원격 저장소에서 추적하고 풀한다는 뜻입니다.

확인이 끝나면 업스트림 브랜치로 로컬 클론을 업데이트하십시오.:

$ git fetch upstream

이름과 이메일 주소 설정하기

$ git config --global user.name "Your Name"
$ git config --global user.email your.email@example.com

--global 플래그는 이 매개변수들을 전역으로 설정하고, --local 플래그는 현재 프로젝트에만 설정합니다.

Windows에서 autocrlf 활성화하기

autocrlf 옵션은 Windows 고유의 줄 끝 문자를 자동으로 수정합니다. 공개 저장소에는 잘못된 줄 끝 문자가 들어 있는 커밋을 모두 거부하는 훅이 있으므로, Windows에서는 이 옵션을 활성화해야 합니다:

$ git config --global core.autocrlf input

브랜치 생성 및 전환

중요

main 브랜치에 절대 직접 커밋하지 마십시오.

main에서 새 브랜치를 만들고 그 브랜치로 전환하십시오:

$ git switch -c <branch-name> main

이는 다음과 동일합니다:

$ # create a new branch from main
$ git branch <branch-name> main
$ # switch to the new branch
$ git switch <branch-name>

현재 있는 브랜치를 확인하려면:

$ git branch

현재 브랜치는 브랜치 이름 옆에 별표가 표시됩니다. 이 명령은 로컬 브랜치만 모두 나열한다는 점에 유의하십시오.

원격 브랜치를 포함한 모든 브랜치를 나열하려면:

$ git branch -a

다른 브랜치로 전환하려면:

$ git switch <another-branch-name>

다른 릴리스들은 저장소 안의 브랜치일 뿐입니다. 예를 들어, upstream 원격 저장소의 3.12 릴리스에서 작업하려면:

$ git switch -c 3.12 upstream/3.12

브랜치 삭제하기

더 이상 필요하지 않은 로컬 브랜치를 삭제하려면:

$ git switch main
$ git branch -D <branch-name>

원격 브랜치를 삭제하려면:

$ git push origin -d <branch-name>

삭제할 브랜치를 두 개 이상 지정할 수 있습니다.

브랜치 이름 변경하기

CPython 저장소의 기본 브랜치 이름은 Python 3.10b1 릴리스 이후 master에서 main으로 변경되었습니다.

이름 변경 전에 만든 GitHub 포크(CPython GitHub 저장소 포크하기에서 설명한 것)가 있다면, 포크의 GitHub 페이지를 방문하여 그곳에서 브랜치 이름을 변경해야 합니다. 이 작업은 한 번만 하면 됩니다. GitHub에서 이를 위한 대화 상자를 제공할 것입니다. 대화 상자가 나타나지 않거나 이미 닫았다면, 다음 GitHub 안내에 따라 포크의 브랜치 이름을 수동으로 변경할 수 있습니다.

포크의 브랜치 이름을 변경한 후에는 로컬 클론도 모두 업데이트해야 합니다. 이 작업은 클론마다 한 번만 하면 됩니다:

$ git branch -m master main
$ git fetch origin
$ git branch -u origin/main main
$ git remote set-head origin -a

(브랜치 이름을 변경하고 나면 GitHub에서도 이 안내를 제공합니다.)

GitHub에 포크가 없고 브랜치 이름 변경 전에 메인 저장소를 직접 클론했다면, 이 경우에도 로컬 클론을 업데이트해야 합니다. 이 작업 역시 클론마다 한 번만 하면 됩니다. 이 경우 다음과 같이 로컬 브랜치의 이름을 변경할 수 있습니다:

$ git branch -m master main
$ git fetch upstream
$ git branch -u upstream/main main

파일 스테이징 및 커밋하기

  1. 현재 변경 사항을 표시하려면:

    $ git status
    
  2. 커밋에 포함할 파일을 스테이징하려면:

    $ git add -p  # to review and add changes to existing files
    $ git add <filename1> <filename2>  # to add new files
    
  3. 스테이징된 파일을 커밋하려면(2단계에서 수행한 파일):

    git commit -m "This is the commit message."
    

변경 사항 되돌리기

아직 커밋하지 않은 파일의 변경 사항을 되돌리려면:

$ git checkout <filename>

변경 사항을 이미 커밋했고, 이제 origin의 현재 상태로 재설정하려면:

$ git reset --hard HEAD

변경 사항 스태시하기

아직 커밋할 준비가 되지 않은 변경 사항을 스태시해 두려면:

$ git stash

마지막으로 스태시한 변경 사항을 다시 적용하려면:

$ git stash pop

변경 사항 비교하기

커밋하지 않은 모든 변경 사항 보기:

$ git diff

main 브랜치와 비교하기:

$ git diff main

attr pathspec을 사용하여 diff에서 생성된 파일 제외하기(작은따옴표에 유의하십시오):

$ git diff main ':(attr:!generated)'

기본적으로 diff에서 생성된 파일 제외하기:

$ git config diff.generated.binary true

generated 속성은 저장소 루트에 있는 .gitattributes에 정의되어 있습니다.

변경 사항 푸시하기

변경 사항을 리뷰받거나 풀 리퀘스트를 만들 준비가 되면, 원격 저장소로 푸시해야 합니다.

$ git switch <branch-name>
$ git push origin <branch-name>

풀 리퀘스트 만들기

  1. https://github.com/python/cpython 으로 이동하십시오.

  2. New pull request 버튼을 누르십시오.

  3. compare across forks 링크를 클릭하십시오.

  4. 베이스 저장소로 python/cpython을, 베이스 브랜치로 main을 선택하십시오.

  5. 헤드 저장소로 <username>/cpython을, 헤드 브랜치로 변경 사항이 담긴 브랜치를 선택하십시오.

  6. Create pull request 버튼을 누르십시오.

PR 제목에 gh-NNNNN: <PR Title> 형식으로 이슈 번호를 포함해야 합니다.

이슈와 풀 리퀘스트에 링크하기

gh-NNNNN을 사용하여 이슈와 풀 리퀘스트에 링크할 수 있습니다(#NNNNN보다 이 형식을 권장합니다). 참조가 목록 안에 나타나면, 링크가 확장되어 이슈/PR의 상태와 제목을 보여줍니다.

제목에 gh-NNNNN이 포함된 PR을 만들면, bedevere가 첫 번째 메시지에 이슈 링크를 자동으로 추가합니다.

또한 풀 리퀘스트는 이슈에 링크하고 PR이 병합될 때 해당 이슈를 자동으로 닫는 데 사용할 수 있는 special keywords를 지원합니다. 하지만 이슈를 닫기 전에 여러 PR이 필요한 경우가 많으므로(예를 들어, 다른 브랜치로의 백포트), 이 기능은 하나의 PR만으로 이슈를 해결하고 닫기에 충분하다고 확신할 때만 유용합니다.

CPython 포크 업데이트하기

시나리오:

  • 얼마 전에 CPython 저장소를 포크했습니다.

  • 시간이 흐릅니다.

  • 업스트림 CPython 저장소에 새 커밋이 추가되었습니다.

  • 포크한 CPython 저장소는 더 이상 최신 상태가 아닙니다.

  • 이제 포크한 CPython 저장소를 업스트림 CPython 저장소와 같은 상태로 업데이트하려고 합니다.

python:main에서 <username>:main으로 풀 리퀘스트를 만들어 이 문제를 해결하려고 하지 마십시오. 풀 리퀘스트 작성자들에게 불필요한 알림이 가게 됩니다.

해결 방법:

$ git switch main
$ git pull upstream main
$ git push origin main

참고

위 명령이 동작하려면 소스 코드 받기 절의 지침을 따르십시오.

다른 시나리오:

  • 얼마 전에 some-branch를 만들었습니다.

  • 시간이 흐릅니다.

  • some-branch에 몇 개의 커밋을 했습니다.

  • 그동안 업스트림 CPython 저장소에 최근 변경 사항이 생겼습니다.

  • 업스트림 CPython 저장소의 최근 변경 사항을 some-branch에 반영하려고 합니다.

해결 방법:

$ git switch some-branch
$ git fetch upstream
$ git merge upstream/main
$ git push origin some-branch

git merge upstream/main을 실행하면 “CONFLICT”나 “Automatic merge failed;”(자동 병합 실패)와 같은 오류 메시지가 표시될 수 있습니다.

이 경우 충돌을 해결해야 합니다. 충돌 해결에 관해서는 다음 문서를 참고하십시오:

Git에 패치 적용하기

시나리오:

  • 패치는 있지만 그에 대한 풀 리퀘스트가 없습니다.

해결 방법:

  1. 패치를 로컬에 내려받으십시오.

  2. 패치를 적용하십시오.:

    $ git apply /path/to/patch.diff
    

    오류가 발생하면 패치가 만들어진 시점의 리비전으로 업데이트한 다음 git apply를 다시 시도하십시오.:

    $ git checkout $(git rev-list -n 1 --before="yyyy-mm-dd hh:mm:ss" main)
    $ git apply /path/to/patch.diff
    

    그래도 패치가 적용되지 않으면 패치 도구로는 패치를 적용할 수 없으므로 직접 다시 구현해야 합니다.

  3. 적용에 성공했다면 새 브랜치를 만들고 그 브랜치로 전환하십시오.

  4. 변경 사항을 스테이징하고 커밋하십시오.

  5. 패치를 오래된 리비전에 적용했다면 최신 상태로 업데이트하고 병합 충돌을 해결해야 합니다.:

    $ git rebase main
    $ git mergetool
    

    아주 오래된 변경의 경우, 충돌 해결 측면에서는 리베이스보다 git merge --no-ff가 더 쉬울 수 있습니다.

  6. 변경 사항을 푸시하고 풀 리퀘스트를 여십시오.

다른 사람의 풀 리퀘스트 체크아웃하기

시나리오:

  • 한 기여자가 CPython에 풀 리퀘스트를 만들었습니다.

  • 병합하기 전에 그 변경 사항을 로컬에서 테스트해 보려고 합니다.

GitHub CLI 또는 hub가 설치되어 있다면 다음과 같이 할 수 있습니다:

$ gh co <pr_number>  # GitHub CLI
$ hub pr checkout <pr_number>  # hub

두 도구 모두 브랜치의 원격 URL을 구성하므로, 풀 리퀘스트 작성자가 풀 리퀘스트를 만들 때 “Allow edits from maintainers”를 선택했다면 git push를 할 수 있습니다.

그렇지 않다면 다음 명령을 실행할 수 있습니다:

$ git fetch upstream pull/NNNNN/head:pr_NNNNN
$ git switch pr_NNNNN

또는 Git 별칭을 설정하십시오:

git config --global alias.pr '!sh -c "git fetch upstream pull/${1}/head:pr_${1} && git switch pr_${1}" -'
git config --global alias.pr "!sh -c 'git fetch upstream pull/${1}/head:pr_${1} && git switch pr_${1}' -"
git config --global alias.pr '!f() { git fetch upstream pull/$1/head:pr_$1 && git checkout pr_$1; }; f'

별칭은 한 번만 설정하면 됩니다. 별칭을 설정한 후에는 다음과 같이 풀 리퀘스트의 로컬 사본을 가져올 수 있습니다:

$ git pr <pr_number>

풀 리퀘스트 수락 및 병합하기

풀 리퀘스트는 Python 코어 개발자가 수락하고 병합할 수 있습니다. 변경을 수락하기 전에 무엇을 확인해야 하는지는 여기에서 더 자세히 읽을 수 있습니다.

모든 풀 리퀘스트에는 변경을 병합하기 전에 통과해야 하는 필수 검사가 있습니다. 검사가 녹색으로 바뀌도록 도울 수 있는 몇 가지 간단한 방법은 “CI를 녹색으로 유지하기”를 참조하십시오.

코어 개발자는 언제든지 회색 Enable auto-merge (squash) 버튼을 클릭하여 변경의 자동 병합을 예약할 수 있습니다. 이 버튼은 풀 리퀘스트 페이지 하단에 있습니다. 자동 병합은 모든 필수 검사를 통과한 경우에만 이루어지지만, 자동 병합이 성공하기 위해 PR이 승인되어 있을 필요는 없습니다.

리뷰 중인 PR의 모든 필수 검사가 이미 끝났다면, 회색 Enable auto-merge 버튼 대신 녹색 Squash and merge 버튼이 표시됩니다.

어느 경우든 커밋 메시지를 적절히 수정하고 정리하십시오.

✅ 다음은 좋은 커밋 메시지의 예입니다:

gh-12345: Improve the spam module (GH-777)

* Add method A to the spam module
* Update the documentation of the spam module

❌ 다음은 나쁜 커밋 메시지의 예입니다:

gh-12345: Improve the spam module (#777)

* Improve the spam module
* merge from main
* adjust code based on review comment
* rebased

나쁜 예에는 PR 생애 주기에서 직접 생겨난 글머리 기호 항목들이 들어 있는데, 이 항목들은 최종 변경과는 관련이 없습니다.

참고

How to Write a Git Commit Message는 좋은 커밋 메시지를 작성하는 방법을 설명하는 훌륭한 글입니다.

마지막으로 Confirm squash and merge 버튼을 누르십시오.

자동 병합 취소하기

수락되어 자동 병합이 활성화된 풀 리퀘스트에서 문제를 발견했더라도, GitHub가 변경을 자동으로 병합하기 전이라면 여전히 워크플로를 취소할 수 있습니다.

자동 병합을 완전히 비활성화하려면 풀 리퀘스트 페이지 하단에 있는 회색 Disable auto-merge 버튼을 누르십시오. 이 방법을 권장합니다.

자동 병합을 일시 중지하려면 PR에 “DO-NOT-MERGE” 레이블을 붙이거나 변경을 요청하는 리뷰를 제출하십시오. 후자의 경우 PR에 “awaiting changes” 레이블이 붙으며, 이 레이블은 “DO-NOT-MERGE”와 마찬가지로 자동 병합을 일시 중지합니다. 작성자가 수정 사항을 제출하고 리뷰를 다시 요청하면, 승인 리뷰를 제출하거나 변경을 요청했던 이전 리뷰를 기각하여 자동 병합 과정을 재개할 수 있습니다.

자동 병합 흐름이 활성화된 뒤에 새 변경 사항을 푸시해도 자동 병합은 중지되지 않는다는 점에 유의하십시오.

병합된 변경 사항 백포트하기

풀 리퀘스트가 main에 병합된 후에는 하나 이상의 유지 보수 또는 보안 브랜치로 백포트해야 할 수 있습니다. 이는 풀 리퀘스트에 붙은 needs backport to X.Y 레이블로 표시됩니다.

miss-islington은 이 레이블이 가리키는 버전에 대해 백포트 PR을 자동으로 생성하려고 시도합니다. miss-islington이 충돌 때문에 백포트 PR을 생성하지 못하면, cherry-picker 도구를 사용하여 백포트를 생성하고 충돌을 직접 해결할 수 있습니다.

main 브랜치에 병합된 스쿼시 커밋의 커밋 해시가 필요합니다. miss-islington은 백포트를 자동으로 생성하지 못한 경우 전체 명령과 커밋 해시가 포함된 댓글을 남길 것입니다. 그런 댓글이 없다면 병합된 풀 리퀘스트에서 다음과 비슷한 이벤트를 찾으십시오:

<core_developer> merged commit <commit_sha1> into python:main <sometime> ago.

<commit_sha1> 링크를 따라가면 전체 커밋 해시를 확인할 수 있습니다.

또는 다음 Git 명령으로도 커밋 해시를 얻을 수 있습니다:

$ git fetch upstream
$ git rev-parse ":/gh-<PR number>"

이 명령들은 메시지에 gh-<PR number>가 포함된 커밋의 해시를 출력합니다.

그런 다음 커밋 해시와 cherry-picker 도구를 사용하여 백포트를 생성할 수 있습니다. 다음 명령에서 <branch>는 대상 유지 보수 브랜치입니다(예: 3.12):

$ cherry_picker <commit_sha1> <branch>

그런 다음 제공된 안내를 따르십시오. 충돌이 발생한 파일을 찾아 수정하고, 필요하면 빌드한 후 해당 테스트를 실행해야 합니다. 작업을 마치면 수정한 모든 파일에 대해 git add를 실행하고 cherry_picker --continue를 실행하여 백포트를 푸시하십시오.

이 도구는 보통 커밋 메시지를 자동으로 생성합니다. 그렇지 않은 경우 다음 형식을 사용하십시오: 백포트 풀 리퀘스트 번호((#XXXXX))를 제거하는 것 외에는 원래 커밋 메시지를 그대로 유지하십시오. 메시지 끝에 (cherry picked from commit <commit_sha1>) 줄을 추가하십시오.

올바른 백포트 커밋 메시지의 형식은 다음과 같습니다:

 [<branch>] gh-XXXXX: <original commit title> (GH-XXXXX)

 <original commit body>

 (cherry picked from commit <commit_sha1>)

여기서 gh-XXXXX는 GitHub 이슈 번호이고, (GH-XXXXX)는 원래 풀 리퀘스트 번호입니다.

잘못된 백포트 커밋 메시지의 예는 다음과 같습니다:

 gh-XXXXX: Custom title (GH-XXXXX) (#XXXXX)

 * Custom message

백포트 PR을 열 때 PR 제목은 커밋 제목과 같은 형식을 따라야 합니다. 즉, [<branch>] 접두사로 시작하고 (GH-XXXXX) 접미사로 원래 PR을 참조해야 합니다. 예를 들면 다음과 같습니다:

 [3.15] gh-12345: Fix the spam module (GH-24680)

백포트 PR이 열리면 miss-islington이 이를 원래 PR에 연결하고 해당 백포트 레이블을 제거합니다.

병합 전에 풀 리퀘스트 편집하기

풀 리퀘스트 제출자가 Allow edits from maintainers 옵션을 활성화한 경우, Python 코어 개발자는 제출자에게 요청하지 않고 병합 전에 필요한 나머지 수정을 직접 하기로 결정할 수 있습니다. 이는 남은 변경 사항이 NEWS 항목 갱신처럼 관리성 작업일 때 특히 적절할 수 있습니다.

main을 대상으로 하는 열린 풀 리퀘스트를 편집하려면 다음과 같이 하십시오:

  1. 풀 리퀘스트 페이지의 설명 아래에는 기여자가 포크한 CPython 저장소와 브랜치 이름에 대한 정보가 있으며, 이 정보는 나중에 유용하게 쓰입니다:

<contributor> wants to merge 1 commit into python:main from <contributor>:<branch_name>
  1. git pr 별칭을 사용하여 풀 리퀘스트를 가져오십시오:

    $ git pr <pr_number>
    

    이렇게 하면 기여자의 브랜치가 <pr_number>에 체크아웃됩니다.

  2. 해당 브랜치에서 변경 사항을 만들고 커밋하십시오. 예를 들어, PR이 제출된 이후 main에 적용된 변경 사항을 병합하십시오(모든 병합 커밋은 나중에 변경 사항을 수락할 때 Squash and Merge로 제거됩니다):

    $ git fetch upstream
    $ git merge upstream/main
    $ git add <filename>
    $ git commit -m "<message>"
    
  3. 변경 사항을 기여자의 PR 브랜치로 다시 푸시하십시오:

    $ git push git@github.com:<contributor>/cpython <pr_number>:<branch_name>
    
  4. 선택적으로, PR 브랜치를 삭제하십시오.

GitHub CLI

GitHub CLI는 GitHub 이슈와 풀 리퀘스트를 생성하고, 갱신하고, 확인할 수 있게 해 주는 명령줄 인터페이스입니다.

이 안내를 따라 GitHub CLI를 설치할 수 있습니다. 설치한 후에는 인증해야 합니다:

$ gh auth login

유용한 명령의 예:

  • PR 생성하기:

    $ gh pr create
    
  • 다른 PR 체크아웃하기:

    $ gh co <pr-id>
    
  • ssh를 Git 프로토콜로 설정하기:

    $ gh config set git_protocol ssh
    
  • 브라우저 설정하기:

    $ gh config set browser <browser-path>
    

Git 워크트리

Git 워크트리를 사용하면 하나의 저장소(.git 디렉터리)에 연결된, 서로 격리된 여러 작업 트리를 가질 수 있습니다. 이를 통해 서로 다른 버전 브랜치에서 동시에 작업할 수 있으므로, 각각 따로 유지하고 갱신해야 하는 여러 개의 독립적인 클론이 필요하지 않습니다. 또한 클론에 드는 부담을 줄이고 디스크 공간을 절약합니다.

Git 워크트리 설정하기

기존 CPython 클론(포크한 CPython 저장소 클론하기 참조)이 있는 상태에서 cpython 디렉터리의 이름을 main으로 바꾸고 새 cpython 디렉터리 안으로 옮겨 다음과 같은 구조를 만드십시오:

cpython
└── main (.git is here)

다음으로, 다른 브랜치에 대한 워크트리를 생성하십시오:

$ cd cpython/main
$ git worktree add -b 3.11 ../3.11 upstream/3.11
$ git worktree add -b 3.12 ../3.12 upstream/3.12

그러면 다음과 같이 각 브랜치의 코드가 각자의 디렉터리에 체크아웃된 구조가 됩니다:

cpython
├── 3.11
├── 3.12
└── main

Git 워크트리 사용하기

예를 들어, 다음과 같이 워크트리 목록을 확인하십시오:

$ git worktree list
/Users/my-name/cpython/main  b3d24c40df [main]
/Users/my-name/cpython/3.11  da1736b06a [3.11]
/Users/my-name/cpython/3.12  cf29a2f25e [3.12]

해당 브랜치에서 작업하려면 그 디렉터리로 이동하십시오. 예를 들면 다음과 같습니다:

$ cd ../3.12
$ git switch -c my-3.12-bugfix-branch  # create new branch
$ # make changes, test them, commit
$ git push origin my-3.12-bugfix-branch
$ # create PR
$ git switch 3.12  # switch back to the 3.12 branch
...