16  GitHub 원격 작업

버전 제어(version control)는 다른 사람과 협업할 때 진정으로 그 가치를 발휘한다. 우리는 이미 버전 제어를 위해 필요한 대부분의 작업을 수행했다. 한 가지 빠진 것은 한 저장소에서 다른 저장소로 변경 사항을 복사하는 것이다.

Git 같은 시스템은 임의 두 저장소 사이에 작업 내용을 옮길 수 있는 기능을 제공한다. 하지만 실무에서는 다른 사람의 노트북이나 PC보다는 중앙 허브에 웹 방식으로 하나의 원본을 두고 사용하는 것이 가장 쉽다.
대부분의 프로그래머는 프로그램 마스터 원본을 GitHub, BitBucket, GitLab 호스팅 서비스에 두고 사용한다. 이번 장 마지막 부분에서 이러한 접근법의 장점과 단점을 살펴본다.

16.1 원격 저장소 생성

세상 사람들과 현재 프로젝트에서 변경한 사항을 공유하는 것에서부터 시작해보자. GitHub에 로그인하고 나서, 우측 상단 아이콘을 클릭해서 planets 이름으로 신규 저장소를 생성한다.

그림 16.1: (1단계) GitHub 저장소 생성

저장소 이름을 “planets”으로 만들고 “Create Repository”를 클릭한다.

그림 16.2: (2단계) GitHub 저장소 생성

저장소가 생성되자마자, GitHub는 URL을 가진 페이지와 로컬 저장소 환경 설정 방법에 대한 정보를 화면에 출력한다.

그림 16.3: (3단계) GitHub 저장소 생성

다음 명령어가 실제로 GitHub 서버에서 자동으로 수행된다.

$ mkdir planets
$ cd planets
$ git init

mars.txt 파일을 추가하고 커밋한 이전 을 상기한다면, 로컬 저장소는 다음과 같이 도식적으로 표현할 수 있다.

그림 16.4: Git 준비영역(Staging) 로컬 저장소

이제 저장소가 두 개로 늘어서, 도식적으로 표현하면 다음과 같다.

그림 16.5: 신선한 신규 GitHub 저장소

현재 로컬 저장소는 여전히 mars.txt 파일에 대한 이전 작업 정보를 담고 있다. 하지만 GitHub의 원격 저장소에는 아직 어떠한 파일도 담고 있지 않다.

16.2 로컬 저장소를 원격 저장소 연결

다음 단계는 두 저장소를 연결하는 것이다. 로컬 저장소를 위해서 GitHub 저장소를 원격(remote)으로 만들어 두 저장소를 연결한다. GitHub의 저장소 홈페이지에 식별하는 데 필요한 문자열이 포함되어 있다.

GitHub 저장소 URL 발견장소

GitHub 저장소 URL 발견장소

HTTPS에서 SSH로 프로토콜(protocol)을 변경하려면 ‘SSH’ 링크를 클릭한다.

브라우저에서 해당 URL을 복사한 후, 로컬 planets 저장소로 이동하여 아래 명령어를 실행한다.

$ git remote add origin git@github.com:vlad/planets.git

단, Vlad의 저장소 URL이 아닌 여러분의 저장소 URL을 사용해야 한다. 유일한 차이점은 vlad 대신 사용자 이름을 사용해야 한다는 것이다.

origin이라는 이름은 원격 저장소에 대한 로컬 별명이다. 원한다면 다른 명칭을 사용할 수도 있지만, origin이 가장 일반적인 선택이다.

git remote -v 명령어를 실행해서 명령어가 제대로 작동했는지 확인한다.

$ git remote -v

origin   git@github.com:vlad/planets.git (fetch)
origin   git@github.com:vlad/planets.git (push)
HTTPS vs. SSH

여기서 SSH를 사용하는데, 이는 추가적인 설정이 필요하지만 많은 애플리케이션에서 널리 사용되는 보안 프로토콜이기 때문이다. 아래 단계는 GitHub를 위한 최소한의 수준에서 SSH를 설명하고 있다.

GitHub 저장소 URL 변경

GitHub 저장소 URL 변경

16.3 SSH 배경과 환경설정

드라큘라가 원격 저장소에 연결하기 전에, 컴퓨터가 GitHub와 인증할 수 있는 방법을 설정해야 한다. 이를 통해 GitHub은 원격 저장소에 연결하려는 사람이 바로 드라큘라 본인임을 알 수 있다.

많은 다양한 서비스에서 명령줄(command line)에 대한 접근을 인증하기 위해 일반적으로 사용되는 방법을 설정할 것이다. 방법을 보안 쉘 프로토콜(Secure Shell Protocol, SSH)이라고 한다. SSH는 보안이 안전하지 않은 네트워크에서도 컴퓨터 간에 안전한 통신을 가능하게 하는 암호화 네트워크 프로토콜이다.

SSH는 키 쌍(key pair)이라는 것을 사용한다. 접근을 검증하기 위해 함께 작동하는 두 개의 키이다. 하나의 키는 공개적으로 알려져 있으며 공개 키(public key)라고 하고, 다른 키는 비밀 키(private key)라고 하며 비공개로 유지된다. 명칭 자체가 매우 직관적이다.

공개 키는 자물쇠로, 본인만이 자물쇠를 열 수 있는 키(비밀 키)를 가지고 있다고 생각할 수 있다. GitHub 계정과 같이 안전한 통신 방법이 필요한 곳에 공개 키를 사용한다. 이 자물쇠, 즉 공개 키를 GitHub에 제공하고 “내 계정에 대한 통신을 이 키로 잠그세요. 그러면 내 비밀 키를 가진 컴퓨터만이 통신을 잠금 해제하고 내 GitHub 계정으로 git 명령을 보낼 수 있습니다.”라고 말한다.

지금 할 일은 SSH 키를 설정하고 공개 키를 GitHub 계정에 추가하는 데 필요한 최소한의 작업이다. 가장 먼저 할 일은 현재 사용 중인 컴퓨터에서 해당 작업이 이미 완료되었는지 확인하는 것이다. 일반적으로 이러한 설정은 한 번만 이루어지면 되고, 이후에는 신경 쓸 필요가 없기 때문이다.

키 안전하게 보관하기

SSH 키는 계정을 안전하게 유지하기 때문에 실제로 잊어서는 안 된다. 특히 여러 컴퓨터에서 계정에 접속하는 경우라면 가끔씩 보안 쉘 키를 점검하는 것이 좋은 습관이다.

컴퓨터에 이미 존재하는 키 쌍을 확인하기 위해 ls 명령어를 실행한다.

ls -al ~/.ssh

출력 결과는 사용자별로 사용 중인 컴퓨터에 SSH가 설정되어 있는지 여부에 따라 약간 달라진다. 드라큘라는 컴퓨터에 SSH를 설정한 적이 전혀 없으므로, 출력 결과는 다음과 같다.

ls: cannot access '/c/Users/Vlad Dracula/.ssh': No such file or directory

만약 사용 중인 컴퓨터에 SSH가 설정되어 있다면, 공개 키와 비밀 키 쌍이 나열될 것이다. 파일 이름은 키 쌍이 설정된 방식에 따라 id_ed25519/id_ed25519.pub 또는 id_rsa/id_rsa.pub 중 하나다. 드라큘라 컴퓨터에는 이러한 키 쌍이 존재하지 않으므로, 다음 명령어를 사용하여 키 쌍을 생성한다.

16.3.1 SSH 키 쌍 생성

SSH 키 쌍을 생성하기 위해 드라큘라는 다음 명령어를 사용하는데, 여기서 -t 옵션은 사용할 알고리즘 유형을 지정하고 -C는 키에 주석(여기서는 드라큘라의 이메일)을 첨부한다.

$ ssh-keygen -t ed25519 -C "vlad@tran.sylvan.ia"

만약 Ed25519 알고리즘을 지원하지 않는 레거시 시스템을 사용 중이라면, 다음 명령어(ssh-keygen -t rsa -b 4096 -C "your_email@example.com")를 사용한다.

Generating public/private ed25519 key pair.
Enter file in which to save the key (/c/Users/Vlad Dracula/.ssh/id_ed25519):

기본 파일을 사용하고 싶으므로, 그냥 Enter 키를 누른다.

Created directory '/c/Users/Vlad Dracula/.ssh'.
Enter passphrase (empty for no passphrase):

이제 드라큘라에게 암호 문구(Passphrase)1를 입력하라고 요청한다. 다른 사람들이 가끔 접근할 수 있는 연구실 노트북을 사용하고 있기 때문에, 암호 문구를 생성하고 싶어 한다. “비밀번호 재설정” 옵션이 없으므로 기억하기 쉬운 것을 사용하거나 암호 문구를 어딘가에 저장해 두어야 한다.

Enter same passphrase again:

동일한 패스프레이즈를 한 번 더 입력한 후, 확인 메시지를 받는다.

Your identification has been saved in /c/Users/Vlad Dracula/.ssh/id_ed25519
Your public key has been saved in /c/Users/Vlad Dracula/.ssh/id_ed25519.pub
The key fingerprint is:
SHA256:SMSPIStNyA00KPxuYu94KpZgRAYjgt9g4BA4kFy3g1o vlad@tran.sylvan.ia
The key's randomart image is:
+--[ED25519 256]--+
|^B== o.          |
|%*=.*.+          |
|+=.E =.+         |
| .=.+.o..        |
|....  . S        |
|.+ o             |
|+ =              |
|.o.o             |
|oo+.             |
+----[SHA256]-----+

“identification”은 실제로 비밀 키이다. 비밀 키를 절대 공유해서는 안 된다. 공개 키는 적절하게 명명되었다. “key fingerprint”는 공개 키의 짧은 버전이다.

이제 SSH 키를 생성했으므로, 확인해 보면 SSH 파일들을 찾을 수 있다.

$ ls -al ~/.ssh

drwxr-xr-x 1 Vlad Dracula 197121   0 Jul 16 14:48 ./
drwxr-xr-x 1 Vlad Dracula 197121   0 Jul 16 14:48 ../
-rw-r--r-- 1 Vlad Dracula 197121 419 Jul 16 14:48 id_ed25519
-rw-r--r-- 1 Vlad Dracula 197121 106 Jul 16 14:48 id_ed25519.pub

16.3.2 공개 키를 GitHub에 복사

이제 SSH 키 쌍을 가지고 있고, GitHub 인증여부를 확인하기 위해 다음 명령어를 실행할 수 있다.

$ ssh -T git@github.com

The authenticity of host 'github.com (192.30.255.112)' can't be established.
RSA key fingerprint is SHA256:nThbg6kXUpJWGl7E1IGOCspRomTxdCARLviKw6E5SY8.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])? y
Please type 'yes', 'no' or the fingerprint: yes
Warning: Permanently added 'github.com' (RSA) to the list of known hosts.
git@github.com: Permission denied (publickey).

맞다. GitHub에 공개 키를 제공해야 한다는 것을 잊었다!

먼저, 공개 키를 복사해야 한다. 그렇지 않으면 비밀 키와 혼동을 일으킬 수 있기 때문에 .pub 확장자를 반드시 포함해야 한다.

$ cat ~/.ssh/id_ed25519.pub

ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDmRA3d51X0uu9wXek559gfn6UFNF69yZjChyBIU2qKI vlad@tran.sylvan.ia

이제 <GitHub.com>에 접속하여, 오른쪽 상단 모서리에 있는 프로필 아이콘을 클릭하여 드롭다운 메뉴를 연다. “Settings”를 클릭하고, 설정 페이지에서 왼쪽의 “Account settings” 메뉴에서 “SSH and GPG keys”를 클릭한다. 오른쪽의 “New SSH key” 버튼을 클릭한다. 이제 제목을 추가할 수 있다(드라큘라는 원래 키 쌍 파일이 어디에 있는지 기억할 수 있도록 “Vlad’s Lab Laptop”이라는 제목을 사용한다). SSH 키를 필드에 붙여넣고 “Add SSH key”를 클릭하여 설정을 완료한다.

이제 설정이 완료되었으니, 명령줄에서 인증을 다시 확인해 보자.

$ ssh -T git@github.com

Hi Vlad! You've successfully authenticated, but GitHub does not provide shell access.

좋다! 출력결과는 SSH 키가 의도한 대로 작동함을 확인해 준다. 이제 작업 내용을 원격 저장소에 푸시할 준비가 되었다.

16.4 로컬 변경사항 원격 저장소 푸시

이제 인증 설정이 완료되었으므로, 원격 저장소로 돌아갈 수 있다. 다음 명령어는 로컬 저장소에서의 변경 사항을 GitHub 저장소로 푸시한다.

$ git push origin main

드라큘라가 암호 문구(passphrase)를 설정했기 때문에, 명령어 실행 시 암호 문구를 입력하라는 프롬프트가 표시된다. 만약 인증에 대한 고급 설정을 완료했다면, 암호 문구를 요구하지 않을 것이다.

Enumerating objects: 16, done.
Counting objects: 100% (16/16), done.
Delta compression using up to 8 threads.
Compressing objects: 100% (11/11), done.
Writing objects: 100% (16/16), 1.45 KiB | 372.00 KiB/s, done.
Total 16 (delta 2), reused 0 (delta 0)
remote: Resolving deltas: 100% (2/2), done.
To https://github.com/vlad/planets.git
 * [new branch]      main -> main
프록시(Proxy)

만약 연결된 네트워크가 프록시를 사용한다면, “Could not resolve hostname” 오류 메시지로 인해서 마지막 명령어가 실패할 가능성이 있다. 이 문제를 해결하기 위해서는 프록시에 대한 정보를 Git에 전달할 필요가 있다.

$ git config --global http.proxy http://user:password@proxy.url
$ git config --global https.proxy http://user:password@proxy.url

프록시를 사용하지 않는 또 다른 네트워크에 연결될 때는 Git에게 프록시 기능을 사용하지 않도록 다음 명령어를 사용하여 알려준다.

$ git config --global --unset http.proxy
$ git config --global --unset https.proxy
비밀번호 관리자

운영체제에 비밀번호 관리자(password manager)가 설정되어 있다면, 사용자이름(username)과 비밀번호(password)가 필요할 때, git push 명령어가 이를 사용하려 한다. “Git Bash on Windows”를 사용하면 기본 디폴트 행동이다. 관리자 비밀번호를 사용하는 대신에, 터미널에서 사용자이름과 비밀번호를 입력하려면, git push를 실행하기 전에 터미널에서 다음과 같이 타이핑한다.

$ unset SSH_ASKPASS

git uses SSH_ASKPASS for all credential entry에도 불구하고, SSH나 HTTPS를 경유하여 Git을 사용하든 SSH_ASKPASS를 설정하고 싶지 않을 수도 있다.

~/.bashrc 파일 하단에 unset SSH_ASKPASS를 추가해서 Git으로 하여금 사용자명과 비밀번호를 사용하도록 기본설정으로 둘 수도 있다.

이제 로컬 저장소와 원격 저장소는 다음과 같은 상태가 된다.

그림 16.6: 첫 번째 푸시(Push) 다음 GitHub 저장소
‘-u’ 플래그(flag)

Git 문서에서 git push와 함께 사용되는 -u 옵션을 볼 수 있다. git branch 명령어에 대한 --set-upstream-to 옵션과 동의어에 해당되는 옵션이다. 원격 브랜치를 현재 브랜치와 연결시키는 데 사용된다. 그래서 git pull 명령어가 아무런 인자 없이 사용될 수 있다. 원격 저장소가 설정되면, git push -u origin master 명령어만 실행시키면 연결 작업이 완료된다.

또한, 원격 저장소에서 로컬 저장소로 변경 사항을 풀(pull)해서 가져올 수도 있다.

$ git pull origin main

From https://github.com/vlad/planets
 * branch            master     -> FETCH_HEAD
Already up-to-date.

이 경우 가져오기 하는 풀(pull)은 아무런 결과가 없는데, 이유는 두 저장소가 이미 동기화되어 있기 때문이다. 하지만 만약 누군가 GitHub 저장소에 변경 사항을 푸시했다면, 상기 명령어는 변경된 사항을 로컬 저장소로 다운로드한다.

GitHub 브라우저에서 직접 파일 업로드

GitHub에서는 명령줄(command line)을 거치지 않고 브라우저를 떠나지 않은 채로 파일을 직접 저장소에 업로드할 수 있는 기능도 제공한다. 두 가지 방법이 있다. 첫째는 파일 트리 상단의 툴바에 있는 “Upload files” 버튼을 클릭하는 것이고, 둘째는 데스크톱에서 파일을 파일 트리로 드래그 앤 드롭하는 것이다. 이에 대한 자세한 내용은 GitHub 페이지에서 확인할 수 있다.

GitHub GUI

GitHub 웹사이트에서 planets 저장소를 찾아간다. Code 탭 아래 “XX commits”(“XX”는 숫자) 텍스트를 클릭한다. 각 커밋 우측의 버튼 세 개를 여기저기 둘러보고, 클릭해 본다. 버튼을 눌러서 어떤 정보를 모을 수 있거나 탐색할 수 있는가? 쉘에서 동일한 정보를 어떻게 얻을 수 있을까?

(클립보드 그림을 갖는) 가장 좌측 버튼은 클립보드에 커밋 식별자 전체를 복사한다. 쉘에서 git log 명령어가 각 커밋에 대한 전체 커밋 식별자를 보여준다.

중간 버튼을 클릭하게 되면, 특정 커밋으로 변경한 내용 전체를 확인할 수 있다. 녹색 음영선은 추가를 붉은색 음영선은 삭제를 의미한다. 쉘에서 동일한 작업을 git diff로 할 수 있다. 특히, git diff ID1..ID2(ID1와 ID2는 커밋 식별자다) 명령어(즉, git diff a3bf1e5..041e637)는 두 커밋 사이 차이를 보여준다.

가장 우측 버튼은 커밋 당시에 저장소의 모든 파일을 보여준다. 쉘로 이런 작업을 수행하려면, 해당 시점의 저장소를 checkout 해야 한다. 쉘에서 git checkout ID(여기서 ID는 살펴보려고 하는 커밋 식별자) 명령어를 실행하면 된다. checkout 하게 되면, 나중에 저장소를 올바른 상태로 되돌려 놓아야 된다는 것을 기억해야 한다.

GitHub 시간도장

GitHub에 원격 저장소를 생성한다. 로컬 저장소의 콘텐츠를 원격 저장소로 푸시한다. 로컬 저장소에 변경 사항을 만들고, 변경 사항을 푸시한다.

방금 생성한 GitHub 저장소로 가서 GitHub 변경 사항에 대한 시간도장(timestamps)을 살펴본다. GitHub이 시간 정보를 어떻게 기록하는가? 왜 그런가?

GitHub은 시간도장을 사람이 읽기 쉬운 형태로 표시한다(즉, “22 hours ago” 혹은 “three weeks ago”). 하지만 시간도장을 이리저리 살펴보면, 파일의 마지막 변경이 발생된 정확한 시간을 볼 수 있다.

푸시(Push) vs. 커밋(Commit)

이번 장에서, “git push” 명령어를 소개했다. “git push” 명령어가 “git commit” 명령어와 어떻게 다른가?

변경 사항을 푸시하면, 로컬에서 변경한 사항을 원격 저장소와 상호 협의하여 최신 상태로 갱신한다. (흔히 다른 사람이 변경시킨 것을 공유하는 것도 이에 해당된다.) 커밋은 로컬 저장소만 갱신한다는 점에서 차이가 난다.

원격 설정 고치기

원격 URL에 오탈자가 발생되는 일이 실무에서 흔히 발생한다. 이번 연습문제는 이런 유형의 이슈를 어떻게 고칠 수 있는지에 대한 것이다. 먼저 잘못된 URL을 원격(remote)에 추가하면서 시작해 보자.

git remote add broken https://github.com/this/url/is/invalid

git remote로 추가할 때 오류를 받았나요? 원격 URL이 적법한지 확인해 주는 명령어를 생각해 낼 수 있나요? URL을 어떻게 수정할 수 있을까요? (팁: git remote -h를 사용한다.) 이번 연습문제를 수행한 다음에 원격(remote)을 지워버리는 것을 잊지 말자.

원격(remote)를 추가할 때 어떤 오류 메시지도 볼 수 없다. (원격 remote를 추가하는 것은 Git에게 알려주기만 할 뿐 아직 사용하지는 않았기 때문이다.)
git push 명령어를 사용하자마자, 오류 메시지를 보게 된다. git remote set-url 명령어를 통해서 잘못된 원격 URL을 바꿔 문제를 해결하게 된다.

GitHub 라이선스와 README 파일

이번 장에서 GitHub에 원격 저장소를 생성하는 방법을 배웠다. 하지만 GitHub 저장소를 초기화할 때 README.md 혹은 라이선스 파일을 추가하지 않았다. 로컬 저장소와 원격 저장소를 연결시킬 때 두 파일을 갖게 되면 무슨 일이 발생될 것으로 생각하는가?

이런 경우, 관련없는 이력 때문에 병합 충돌(merge conflict)이 발생한다. GitHub에서 README.md 파일을 생성시키고 원격 저장소에서 커밋 작업을 수행한다. 로컬 저장소로 원격 저장소를 풀(pull)로 땡겨오면, Git이 origin과 공유되지 않는 이력을 탐지하고 병합(merge)을 거부해 버린다.

$ git pull origin master
 
From https://github.com/vlad/planets
  * branch            master     -> FETCH_HEAD
  * [new branch]      master     -> origin/master
fatal: refusing to merge unrelated histories

--allow-unrelated-histories 옵션으로 두 저장소를 강제로 병합(merge)시킬 수 있다. 이런 옵션을 사용할 때는 주의해야 한다. 병합하기 전에 로컬 저장소와 원격 저장소의 콘텐츠를 면밀히 조사해야 한다.

$ git pull --allow-unrelated-histories origin master
 
From https://github.com/vlad/planets
  * branch            master     -> FETCH_HEAD
Merge made by the 'recursive' strategy.
README.md | 1 +
1 file changed, 1 insertion(+)
create mode 100644 README.md

  1. 암호 문구는 암호(Password)와 비슷하지만 띄어쓰기를 포함한 더 긴 문장이나 구절을 사용한다는 점이 다르다. 예를 들어, “I love coding with ChatGPT!”와 같이 여러 단어로 이루어진 문장을 Passphrase로 사용할 수 있다. 암호 문구가 암호보다 길기 때문에 보안성이 더 높다고 여겨진다. 추측하거나 무작위로 대입하기가 더 어렵기 때문으로 IT 분야에서 암호 대신 Passphrase를 사용하는 것이 점점 더 일반화되고 있는 추세다.↩︎