Git 부트캠프와 치트 시트

참고

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

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

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

참고

일반적인 작업을 위한 Git 별칭을 설정하면 유용할 수 있습니다. 자세한 내용은 Git 문서에서 확인할 수 있습니다.

CPython GitHub 저장소 포크하기

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

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

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

  3. “Copy the main branch only”의 선택을 해제하십시오.

  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을 사용할 수도 있습니다.

원격 저장소 구성하기

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

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

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

$ 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이 출력되어야 하며, 이는 upstream 원격 저장소에서 main의 변경 사항을 추적하거나 풀한다는 의미입니다.

이를 확인한 후 업스트림 브랜치로 로컬 복제본을 업데이트하십시오.:

$ 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>

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

브랜치 이름 변경

Python 3.10b1 릴리스 이후 CPython 저장소의 기본 브랜치 이름이 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 attribute는 저장소 루트에 있는 .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 수명 주기의 직접적인 결과로 생긴 글머리 기호 항목이 포함되어 있습니다.

참고

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

마지막으로 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 옵션을 활성화한 경우, 파이썬 코어 개발자는 제출자에게 요청하는 대신 병합 전에 필요한 나머지 편집을 직접 수행할 수 있습니다. 남은 변경 사항이 뉴스 항목 업데이트와 같은 관리 작업인 경우 특히 적절할 수 있습니다.

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>
    
  • Git 프로토콜로 ssh 설정:

    $ 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
...