← Git 강의 목록으로
🔀
Git·GitHub 심화
clone · push · pull · fetch · 자격증명

4단원 — 원격 저장소

로컬 Git은 혼자만의 버전 관리입니다. GitHub 같은 원격 저장소를 연결하면 코드를 안전하게 백업하고, 어디서든 내려받을 수 있으며, 팀원과 함께 작업할 수 있습니다. 이 단원에서는 `clone`, `push`, `pull`, `fetch`의 차이를 명확히 구분하고 PAT(Personal Access Token)와 SSH 키 인증까지 설정합니다.

clonepushpullPAT/SSH
소요 시간
1~2시간
난이도
📊 초급
선수 조건
🎯 3단원
결과물
원격 저장소 동기화와 자격증명

이 강의에서 배우는 것

  • 1`git clone`으로 원격 저장소를 복제할 수 있다.
  • 2`git push`·`git pull`·`git fetch`의 동작 차이를 설명할 수 있다.
  • 3`git remote add/rename/remove`로 원격 연결을 관리할 수 있다.
  • 4GitHub PAT 또는 SSH 키로 인증을 설정할 수 있다.
  • 5로컬 브랜치와 원격 브랜치(`origin/main`)의 관계를 이해한다.

핵심 개념

1) git clone

bash
git clone https://github.com/user/repo.git         # HTTPS
git clone git@github.com:user/repo.git             # SSH
git clone https://github.com/user/repo.git mydir   # 다른 이름으로
git clone --depth=1 https://github.com/user/repo.git  # 얕은 복제

2) push / pull / fetch

text
로컬                    원격(origin)
main ──push──▶  origin/main
main ◀──pull──  origin/main  (fetch + merge)
     ──fetch──▶ origin/main  (로컬 브랜치에 영향 없음)
bash
git push origin main          # 로컬 main → 원격 main
git push -u origin main       # 업스트림 설정 후 push (이후 git push 만으로 가능)
git pull origin main          # fetch + merge
git fetch origin              # 원격 정보만 업데이트, 병합 안 함
git fetch origin main         # 특정 브랜치만

3) remote 관리

bash
git remote -v                     # 원격 목록 + URL 확인
git remote add origin URL         # 원격 추가
git remote rename origin upstream # 이름 변경
git remote remove origin          # 원격 삭제
git remote set-url origin NEW_URL # URL 변경

4) 원격 브랜치 추적

bash
git branch -vv              # 로컬 브랜치와 원격 추적 브랜치 확인
git switch -c feature origin/feature  # 원격 브랜치를 로컬로 체크아웃
git push origin --delete feature      # 원격 브랜치 삭제

5) 자격증명 설정

**PAT (Personal Access Token) — HTTPS 방식**

bash
# GitHub → Settings → Developer settings → Personal access tokens → Generate
# 클론/push 시 비밀번호 대신 토큰 입력
git config --global credential.helper store  # 저장 (평문 주의)

**SSH 키 방식**

bash
ssh-keygen -t ed25519 -C "your@email.com"   # 키 생성
cat ~/.ssh/id_ed25519.pub                    # 공개 키 복사
# GitHub → Settings → SSH and GPG keys → New SSH key
ssh -T git@github.com                        # 연결 테스트

예제로 보기

예제 1 — `ex01_clone.sh` : 로컬 bare 저장소를 clone 해 두 저장소 시뮬레이션

bash
#!/usr/bin/env bash
# GitHub 실습은 네트워크가 필요하므로 로컬 bare 저장소로 시뮬레이션합니다.
WORKDIR=$(mktemp -d)
cd "$WORKDIR"

# "원격" 역할의 bare 저장소 생성
git init --bare remote.git
echo "bare 저장소 생성: $WORKDIR/remote.git"

# 로컬 작업 저장소 생성 후 push
git init local_a
cd local_a
git config user.name "작업자A" && git config user.email "a@example.com"
echo "Hello" > README.md
git add . && git commit -q -m "init"
git remote add origin "$WORKDIR/remote.git"
git push -u origin main

echo ""
echo "=== clone ==="
cd "$WORKDIR"
git clone remote.git local_b
cd local_b
git log --oneline

rm -rf "$WORKDIR"

**실행 결과**

text
bare 저장소 생성: /tmp/tmp.XXX/remote.git

=== clone ===
Cloning into 'local_b'...
a1b2c3d init

핵심: `git clone`은 원격 저장소의 전체 이력을 복사하고 `origin` 원격을 자동 설정한다.

예제 2 — `ex02_remote.sh` : remote 추가·확인·변경

bash
#!/usr/bin/env bash
REPO=$(mktemp -d)
cd "$REPO"
git init -q
git config user.name "실습용" && git config user.email "demo@example.com"

echo "=== remote 추가 ==="
git remote add origin https://github.com/example/repo.git
git remote add upstream https://github.com/original/repo.git

echo ""
echo "=== remote 목록 ==="
git remote -v

echo ""
echo "=== origin URL 변경 ==="
git remote set-url origin https://github.com/example/new-repo.git
git remote -v

echo ""
echo "=== upstream 제거 ==="
git remote remove upstream
git remote -v

rm -rf "$REPO"

**실행 결과**

text
=== remote 추가 ===

=== remote 목록 ===
origin    https://github.com/example/repo.git (fetch)
origin    https://github.com/example/repo.git (push)
upstream  https://github.com/original/repo.git (fetch)
...

=== origin URL 변경 ===
origin    https://github.com/example/new-repo.git (fetch)
...

핵심: `git remote -v` 로 연결된 모든 원격 저장소와 URL을 확인할 수 있다.

예제 3 — `ex03_push_pull.sh` : 로컬 bare 저장소로 push/pull 시뮬레이션

bash
#!/usr/bin/env bash
WORKDIR=$(mktemp -d)

git init --bare "$WORKDIR/remote.git" -q

# 저장소 A
git clone "$WORKDIR/remote.git" "$WORKDIR/repo_a" -q
cd "$WORKDIR/repo_a"
git config user.name "작업자A" && git config user.email "a@example.com"
echo "A의 파일" > a.txt
git add . && git commit -q -m "feat: A adds file"
git push origin main -q

# 저장소 B
git clone "$WORKDIR/remote.git" "$WORKDIR/repo_b" -q
cd "$WORKDIR/repo_b"
git config user.name "작업자B" && git config user.email "b@example.com"

echo "=== B에서 pull 전 로그 ==="
git log --oneline

git pull origin main -q
echo ""
echo "=== B에서 pull 후 로그 ==="
git log --oneline

rm -rf "$WORKDIR"

**실행 결과**

text
=== B에서 pull 전 로그 ===
(없음)

=== B에서 pull 후 로그 ===
a1b2c3d feat: A adds file

핵심: `git pull`은 `fetch` + `merge`를 한 번에 수행한다.

예제 4 — `ex04_credentials.sh` : SSH 키 생성 및 안내

bash
#!/usr/bin/env bash
set -euo pipefail

echo "=== SSH 키 생성 방법 안내 ==="
echo ""
echo "1. 키 생성:"
echo "   ssh-keygen -t ed25519 -C \"your@email.com\""
echo ""
echo "2. 공개 키 확인:"
echo "   cat ~/.ssh/id_ed25519.pub"
echo ""
echo "3. GitHub 등록:"
echo "   GitHub → Settings → SSH and GPG keys → New SSH key"
echo "   위에서 복사한 공개 키 붙여넣기"
echo ""
echo "4. 연결 테스트:"
echo "   ssh -T git@github.com"
echo "   # Hi username! You've successfully authenticated..."
echo ""

if [ -f "$HOME/.ssh/id_ed25519.pub" ]; then
    echo "=== 현재 공개 키 ==="
    cat "$HOME/.ssh/id_ed25519.pub"
else
    echo "(~/.ssh/id_ed25519.pub 파일이 없습니다. 위 1번 명령으로 생성하세요)"
fi

**실행 결과**

text
=== SSH 키 생성 방법 안내 ===
1. 키 생성: ssh-keygen -t ed25519 ...
...
(~/.ssh/id_ed25519.pub 파일이 없습니다. 위 1번 명령으로 생성하세요)

핵심: SSH 키 방식은 매번 PAT를 입력할 필요 없이 안전하게 인증할 수 있다.

다른 시각으로 보기

Git 명령비유
`git clone`원본 문서를 USB에 복사
`git push`내 수정본을 서버에 업로드
`git fetch`서버 변경 내역을 미리보기
`git pull`서버 변경 내역 받아서 즉시 적용
`origin`내 저장소와 연결된 서버 별명

자주 하는 실수

  1. **`git push` 실패 후 `--force` 남용** — 팀원의 커밋이 사라질 수 있다; `--force-with-lease`를 사용한다.
  2. **`git pull` 대신 `git fetch` 잊기** — fetch 후 diff를 확인하면 예상치 못한 충돌을 줄일 수 있다.
  3. **HTTPS URL에 비밀번호 사용** — 2021년 8월부터 GitHub는 비밀번호 인증을 지원하지 않는다; PAT 또는 SSH를 사용한다.
  4. **원격 브랜치 삭제 후 로컬에 남은 추적 브랜치** — `git fetch --prune`으로 정리한다.
  5. **`-u` 없이 첫 push** — 업스트림이 설정되지 않아 이후 `git push`가 동작하지 않는다.

정리

  • `git clone URL`으로 원격 저장소를 복제하고 `origin`이 자동 설정된다.
  • `git push -u origin <브랜치>`로 업스트림을 설정하면 이후 `git push`만 입력해도 된다.
  • `git fetch`는 로컬 브랜치를 변경하지 않고, `git pull`은 fetch + merge를 한 번에 한다.
  • SSH 키 방식이 HTTPS PAT보다 편리하고 안전하다.

직접 해 보기

bash
cd 04_원격_저장소/src
chmod +x ex01_clone.sh ex02_remote.sh ex03_push_pull.sh ex04_credentials.sh
./ex01_clone.sh
./ex02_remote.sh
./ex03_push_pull.sh
./ex04_credentials.sh

응용:

  1. `git fetch origin` 후 `git diff HEAD origin/main` 으로 원격과의 차이를 확인해 보세요.
  2. `git push origin --delete <브랜치명>` 으로 원격 브랜치를 삭제해 보세요.

💻 예제 (examples)

실제로 실행해 결과를 확인할 수 있는 예제입니다.

ex01_clone.sh로컬 bare 저장소를 clone 해 두 저장소 시뮬레이션
CODE
#!/usr/bin/env bash
set -euo pipefail

WORKDIR=$(mktemp -d)
echo "작업 디렉토리: $WORKDIR"

# 원격 역할의 bare 저장소 생성
git init --bare -b main "$WORKDIR/remote.git" -q
echo "bare 저장소 생성 완료"

# 최초 커밋 (local_a 에서 push)
git clone "$WORKDIR/remote.git" "$WORKDIR/local_a" -q
cd "$WORKDIR/local_a"
git config user.name "작업자A" && git config user.email "a@example.com"
    git config commit.gpgsign false
echo "# 원격 저장소 실습" > README.md
git add . && git commit -q -m "docs: initial README"
git push -u origin main -q
echo "local_a 에서 push 완료"

# local_b 에서 clone
echo ""
echo "=== git clone ==="
git clone "$WORKDIR/remote.git" "$WORKDIR/local_b"

cd "$WORKDIR/local_b"
echo ""
echo "=== clone 후 로그 ==="
git log --oneline

echo ""
echo "=== remote 확인 ==="
git remote -v

rm -rf "$WORKDIR"
▶ 실행 결과
bare 저장소 생성: /tmp/tmp.XXX/remote.git

=== clone ===
Cloning into 'local_b'...
a1b2c3d init
ex02_remote.shremote 추가·확인·변경
CODE
#!/usr/bin/env bash
set -euo pipefail

REPO=$(mktemp -d)
cd "$REPO"
git init -q -b main
git config user.name "실습용" && git config user.email "demo@example.com"
    git config commit.gpgsign false

echo "=== remote 추가 ==="
git remote add origin  https://github.com/example/my-repo.git
git remote add upstream https://github.com/original/my-repo.git
git remote -v

echo ""
echo "=== origin URL 변경 ==="
git remote set-url origin https://github.com/example/new-name.git
git remote -v

echo ""
echo "=== upstream 이름 → source 로 변경 ==="
git remote rename upstream source
git remote -v

echo ""
echo "=== source 제거 ==="
git remote remove source
git remote -v

rm -rf "$REPO"
▶ 실행 결과
=== remote 추가 ===

=== remote 목록 ===
origin    https://github.com/example/repo.git (fetch)
origin    https://github.com/example/repo.git (push)
upstream  https://github.com/original/repo.git (fetch)
...

=== origin URL 변경 ===
origin    https://github.com/example/new-repo.git (fetch)
...
ex03_push_pull.sh로컬 bare 저장소로 push/pull 시뮬레이션
CODE
#!/usr/bin/env bash
set -euo pipefail

WORKDIR=$(mktemp -d)

git init --bare -b main "$WORKDIR/remote.git" -q

setup_repo() {
    local path="$1" name="$2" email="$3"
    git clone "$WORKDIR/remote.git" "$path" -q
    git -C "$path" config user.name "$name"
    git -C "$path" config user.email "$email"
    git -C "$path" config commit.gpgsign false
}

# 저장소 A: 초기 커밋 + push
setup_repo "$WORKDIR/repo_a" "작업자A" "a@example.com"
cd "$WORKDIR/repo_a"
echo "A의 첫 커밋" > a.txt
git add . && git commit -q -m "feat: A's first commit"
git push origin main -q
echo "A → push 완료"

# 저장소 B: B에서 커밋 추가 + push
setup_repo "$WORKDIR/repo_b" "작업자B" "b@example.com"
cd "$WORKDIR/repo_b"
git pull origin main -q
echo "B의 커밋" > b.txt
git add . && git commit -q -m "feat: B's commit"
git push origin main -q
echo "B → push 완료"

# 저장소 A: pull 해서 B의 변경 받기
cd "$WORKDIR/repo_a"
echo ""
echo "=== A가 pull 전 로그 ==="
git log --oneline

git pull origin main -q
echo ""
echo "=== A가 pull 후 로그 (B의 커밋이 포함됨) ==="
git log --oneline

rm -rf "$WORKDIR"
▶ 실행 결과
=== B에서 pull 전 로그 ===
(없음)

=== B에서 pull 후 로그 ===
a1b2c3d feat: A adds file
ex04_credentials.shSSH 키 생성 및 안내
CODE
#!/usr/bin/env bash
set -euo pipefail

echo "============================================"
echo " GitHub 자격증명 설정 안내"
echo "============================================"
echo ""
echo "--- 방법 1: SSH 키 (권장) ---"
echo ""
echo "1. 키 생성:"
echo "   ssh-keygen -t ed25519 -C \"your@email.com\""
echo ""
echo "2. ssh-agent 에 등록:"
echo "   eval \"\$(ssh-agent -s)\""
echo "   ssh-add ~/.ssh/id_ed25519"
echo ""
echo "3. 공개 키 복사:"
echo "   cat ~/.ssh/id_ed25519.pub"
echo "   (GitHub → Settings → SSH and GPG keys → New SSH key 에 붙여넣기)"
echo ""
echo "4. 연결 테스트:"
echo "   ssh -T git@github.com"
echo ""
echo "--- 방법 2: HTTPS + PAT ---"
echo ""
echo "1. PAT 생성:"
echo "   GitHub → Settings → Developer settings"
echo "   → Personal access tokens → Tokens (classic)"
echo "   → Generate new token"
echo "   (repo 권한 체크)"
echo ""
echo "2. 자격증명 저장:"
echo "   git config --global credential.helper store"
echo "   (첫 push/pull 시 사용자명과 PAT 입력 → 저장됨)"
echo ""
echo "--- 현재 SSH 키 상태 ---"
if [ -f "$HOME/.ssh/id_ed25519.pub" ]; then
    echo "공개 키 발견:"
    cat "$HOME/.ssh/id_ed25519.pub"
elif [ -f "$HOME/.ssh/id_rsa.pub" ]; then
    echo "RSA 공개 키 발견:"
    cat "$HOME/.ssh/id_rsa.pub"
else
    echo "(SSH 공개 키 없음 — 위 1번 방법으로 생성하세요)"
fi
▶ 실행 결과
=== SSH 키 생성 방법 안내 ===
1. 키 생성: ssh-keygen -t ed25519 ...
...
(~/.ssh/id_ed25519.pub 파일이 없습니다. 위 1번 명령으로 생성하세요)

📝 과제 (exercises)

직접 풀어보고, 막힐 때 정답을 펼쳐 비교해보세요.

과제 1

문제 1 (hw01.sh)

목표: 로컬 bare 저장소를 "원격"으로 사용하는 두 저장소(repo_a, repo_b) 시나리오를 구현하세요.

요구사항
  • 파일명: hw01.sh
입출력 예시
# 예상 출력 (핵심)
=== repo_a fetch + merge 후 ===
b2c3d4e (HEAD -> main, origin/main) feat: B's file
a1b2c3d feat: A's file
정답 코드 펼치기 / 접기
SOLUTION
#!/usr/bin/env bash
set -euo pipefail

WORKDIR=$(mktemp -d)

git init --bare -b main "$WORKDIR/remote.git" -q

# repo_a
git clone "$WORKDIR/remote.git" "$WORKDIR/repo_a" -q
git -C "$WORKDIR/repo_a" config user.name "작업자A"
git -C "$WORKDIR/repo_a" config user.email "a@example.com"
git -C "$WORKDIR/repo_a" config commit.gpgsign false
echo "A의 파일" > "$WORKDIR/repo_a/a.txt"
git -C "$WORKDIR/repo_a" add .
git -C "$WORKDIR/repo_a" commit -q -m "feat: A's file"
git -C "$WORKDIR/repo_a" push origin main -q
echo "repo_a push 완료"

# repo_b: clone 후 A의 커밋 확인
git clone "$WORKDIR/remote.git" "$WORKDIR/repo_b" -q
git -C "$WORKDIR/repo_b" config user.name "작업자B"
git -C "$WORKDIR/repo_b" config user.email "b@example.com"
git -C "$WORKDIR/repo_b" config commit.gpgsign false

echo ""
echo "=== repo_b 에서 로그 (A의 커밋이 보여야 함) ==="
git -C "$WORKDIR/repo_b" log --oneline

echo "B의 파일" > "$WORKDIR/repo_b/b.txt"
git -C "$WORKDIR/repo_b" add .
git -C "$WORKDIR/repo_b" commit -q -m "feat: B's file"
git -C "$WORKDIR/repo_b" push origin main -q
echo "repo_b push 완료"

# repo_a: fetch + merge
git -C "$WORKDIR/repo_a" fetch origin -q
echo ""
echo "=== repo_a fetch + merge 후 ==="
git -C "$WORKDIR/repo_a" merge origin/main -q
git -C "$WORKDIR/repo_a" log --oneline

rm -rf "$WORKDIR"
▶ 실행 결과
# 예상 출력 (핵심)
=== repo_a fetch + merge 후 ===
b2c3d4e (HEAD -> main, origin/main) feat: B's file
a1b2c3d feat: A's file
과제 2

문제 2 (hw02.sh)

목표: `git remote` 관리 스크립트를 작성하세요.

요구사항
  • 파일명: hw02.sh
정답 코드 펼치기 / 접기
SOLUTION
#!/usr/bin/env bash
set -euo pipefail

REPO=$(mktemp -d)
cd "$REPO"
git init -q -b main
git config user.name "실습용" && git config user.email "demo@example.com"
    git config commit.gpgsign false

git remote add origin https://github.com/example/project.git
git remote add backup https://backup.example.com/project.git

echo "=== 초기 remote 목록 ==="
git remote -v

git remote set-url origin https://github.com/example/project-v2.git
git remote remove backup

echo ""
echo "=== 최종 remote 목록 ==="
git remote -v

rm -rf "$REPO"
예제 코드 / 강의 자료

전체 강의 자료와 예제 코드는 GitHub에서 자유롭게 받아볼 수 있습니다.

GitHub에서 보기 ↗