본문 바로가기
학부 생활 + 랩실/Git, GitHub

Git Tutorial(3) - Git / GitHub 기초(2)

by 프롭 2026. 3. 17.

 

Git 개념 정리

이전 글에서 사용한 git 명령어들을 이해하기 쉽게 개념을 다루려한다.

 

  • 파일의 상태

위 그림을 보자. 위쪽 사각형은 파일의 상태를 나타내고 화살표에 쓰인 단어는 'edit'을 제외하고 모두 git verb다. git 명령어는 주어 동사 목적어와 같이 git <verb> <options>형태이다. 그렇기에 중간의 동작을 나타내는 인자를 verb라 한다.

 

Git 저장소 안에서 파일은 그림처럼 네가지 상태를 가진다.

1. Untracked : 아직 Git으로 버전 관리되지 않은 상태다.

2. Staged : git add를 실행하면 Untracked 혹은 Modified 상태의 파일들이 Staged 된다. Stage 한 후의 파일 상태가 commit할 때 저장된다. 그렇기에 add 후 수정이 이루어지더라도 add 했을 당시의 상태를 commit해 저장하게 된다.

3. Modified : Stage 하지 않은 변경 사항이 있는 상태다. Staged 상태의 파일을 수정하면 Stage 이후의 변경 사항은 Modified에 속해서 하나의 파일이 Staged 이면서 동시에 Modified 상태가 된다.

4. Unmodified : Staged 상태의 파일을 commit 하면 Unmodified 상태가 된다. Git은 commit 단위로 해쉬를 할당하고 버전을 관리하기에 언제든 과거에 commit한 상태로 돌아갈 수 있다.

 

원격(Remote) 저장소와 교류하는 동작(verb)은 네 가지가 있다.

1. pull : 원격 저장소의 commit을 로컬 저장소에 다운로드 받고 내용을 합친다.

2. push : 로컬 저장소의 commit을 원격 저장소로 올려 합친다. 로컬 저장소에 없는 commit이 원격 저장소에 있다면 먼저 pull을 실행해야 push 할 수 있다.

3. clone : 원겨 저장소를 복사한 로컬 저장소를 만든다.

4. fetch : pull은 사실 원격 저장소의 commit을 내려받는 fetch와 내용을 합치는 merge 두 명령어를 한 번에 실행한 것이다. fetch만 할 경우 현재 로컬 저장소의 파일에는 변화가 생기지 않는다.

 

  • 저장소 공간

파일의 관점에서 상태 변화를 알아봤다. 다음 그림은 Git 저장소 내부의 세 가지 공간을 나타낸 것이다.

1. 작업 트리(Work Tree) : 현재 사용자에게 보이는 파일들이 있는 공간. 다양한 상태의 파일들이 있다.

2. 인덱스(Index) : 가장 최근 commit에 Stage 한 내용까지 반영된 공간. 파일을 add 하면 그 당시의 상태가 이곳으로 복사된다. commit 하기 전까지 add 한 변경 사항이 이곳에 쌓인다.

3. 저장소(Repository) : commit을 하게 되면 Staged 파일들이 새로운 commit으로 저장되고 Head가 새 commit으로 변경된다. 저장소에는 commit들이 쌓인다.

4. Head : 저장소 대신 Head가 쓰이기도 하는데 Head는 현재 작업하는 로컬 브랜치의 최신 commit을 가리키는 포인터다.

 

git 활용
  • 다른 곳에서 이어서 작업하기 (데스크톱)

git/github은 여럿이 협업할 때 가장 유용하지만 혼자 작업할 때도 유용하다. 일단 Git을 통해 기본적인 버전 관리를 할 수 있는 것은 물론이고 마치 파일 클라우드에 올려 놓으면 어느 PC에서나 파일에 접근할 수 있듯이 깃헙을 사용하면 어디서나 소스코드를 받아서 작업을 이어서 할 수 있다. 여기서는 여러 PC에서 작업을 이어서 하는 방법과 다양한 명령어를 추가로 알아볼 것이다.

 

예를들어 노트북과 데스크톱 두 PC에서 작업한다 하자. 방급 작업했던 ~/workspace/notebook경로를 노트북의 로컬 저장소로 생각하고 ~/workspace/desktop 경로를 데스크톱 로컬 저장소로 여기고 두 개의 로컬 저장소를 옮겨가면서 작업하는 것을 해보고자 한다. (예로 두 장소를 가정했지만 사실 두 사람 협업한느 것으로 봐도 동일하다)

 

~/workspace/desktop에서 프로젝트에 새로운 내용을 추가하기위해 원격 저장소를 새로운 이름으로 복제하고 스크립트를 작성하겠다. (추가할 스크립트는 생략하겠다.)

$ cd ~/workspace
$ git clone https://github.com/<your-repository> desktop
$ cd ~/workspace/desktop
$ gedit add.py

그리고 이전 장에서 작성했던 anything.py의 출력을 바꾸어 실행보자.

 

  • git diff

diff는 두 상태의 차이를 보여주는 verb다 변경 사항을 확인할 때 유용하다.

git diff : 작업 트리와 인덱스의 차이점 보기 (Unstaged 변경 사항 보기)

git diff --cached : 인덱스와 저장소(HEAD)의 차이점 보기 (Staged 변경 사항 보기)

git diff HEAD : 작업 트리와 저장소(HEAD)의 차이점 보기

git diff <start> [end] : start로부터의 변경 사항이나 start와 end 사이의 변경 사항을 본다. start와 end는 commit hash나 HEAD~n, 브랜치 명, 태그 명이 될 수 있다.

 

git add 하지 않은 변경사항은 git diff로 보고 git add가 된 변경사항은 git diff --cached로 확인할 수 있다.

변경 사항 확인 후 커밋까지 하자.

~/workspace/desktop$ git diff
+from add_lists import add_lists
# 내용 작성 예시
if __name__ == "__main__":
-    print("hello python, hi ros.")
+    print("hi")
# 저장 후 종료

~/workspace/desktop$ git add .
~/workspace/desktop$ git diff
~/workspace/desktop$ git diff --cached
~/workspace/desktop$ git status
...
	new file:   __pycache__/add.cpython-36.pyc
	new file:   add.py
	modified:   anything.py

~/workspace/desktop$ git commit -m 'change output '

git status로 상태를 봤을 때 불필요한 pyc 파일이 추가된 것을 볼 수 있다. 이렇게 소스코드 외에 부가적인 파일들이 생기면 저장소의 용량이 커지고 명령어를 처리하는 시간도 길어진다. 그래서 가급적 소스코드만 남기고 나머지는 삭제 하거나 무시하는 게 좋다. 뒤에서 .gitignore을 통해 이를 처리하는 것을 알아보겠다.

 

  • git rm

rm은 파일을 삭제하고 삭제한 상태를 stage한다. 즉 파일을 삭제한 후 git add . 한 것과 같다.

Git으로 버전 관리되는 파일은 가급적 git rm을 이용해 삭제하는 것이 좋다.

git rm <file_name> : 지정한 파일을 삭제하고 삭제한 상태를 stage한다.

git rm <file_pattern> : 패턴과 일치하는 모든 파일을 삭제하고 stage한다.

예를들어 git rm *.txt 라고 하면 모든 텍스트 파일을 삭제하는 것이다.

git rm -rf <dir_name> : 디렉토리를 삭제할 때는 -rf 옵션을 줘야한다.

git rm --cached <file_name> : 실제 파일은 삭제하지 않고 파일을 인덱스에서 제외하여 Untracked 상태로 만든다.

pyc 파일이 들어있는 __pycache__ 디렉토리를 삭제하자. 그냥 rm으로 삭제하기보다 git rm으로 삭제하는 것이 낫다.

~/workspace/desktop$ git rm -rf __pycache__
~/workspace/desktop$ ls
 

 

  • .gitignore

삭제를 하더라도 파이썬 코드를 다시 실행하면 pyc파일이 다시 생길것이다. 계속 삭제해야하는 번거러움을 피하기 위해 이를 무시하도록 필터링을 해야한다. 이때 무시할 파일(이나 디렉토리)의 이름 목록을 .gitignore파일에 저장한다.

~/workspace/desktop$ gedit .gitignore
# 파일 내용 작성 후 닫기
__pycache__
*.pyc

~/workspace/desktop$ python3 anything.py
~/workspace/desktop$ ls
~/workspace/desktop$ git status
...
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	deleted:    __pycache__/add_lists.cpython-36.pyc

Untracked files:
  (use "git add <file>..." to include in what will be committed)

	.gitignore

이제 데스크톱에서의 작업이 끝났다. 이를 원격 저장소로 push하자

~/workspace/desktop$ git add .
~/workspace/desktop$ git commit -m 'remove and ignore pyc files'
~/workspace/desktop$ git push origin main
 

 

  • 다시 다른 곳에서 작업하기(노트북)

다른 환경에서 작업을 재개하고자 한다. 아직 데스크톱에서 작업하여 원격 저장소에 올린 내용을 노트북 로컬 저장소에는 반영하지 않았기에 원격 저장소의 업데이트 한 내용을 내려받아야한다. 그전에 노트북 로컬 저장소에서 간단한 수정 후 커밋을 해보자.

$ cd ~/workspace/notebook
~/workspace/notebook$ gedit anything.py
# 수정 예시
if __name__ == "__main__":
    print("hello python, hi ros.")
    print("Python is the best language for ros")

~/workspace/notebookl$ git status
~/workspace/notebook$ git diff
~/workspace/notebook$ git add .
~/workspace/notebook$ git commit -m 'edit anything.py'

이제 업데이트 한 내용을 노트북 로컬 저장소에 반영하려한다. git pull을 활용한다

 

  • git pull

원격 저장소의 새로운 변경 사항(commit)들을 로컬 저장소에 내려받고 작업 트리에 그 내용을 반영한다. Pull을 실행하기 전에 반드시 로컬 저장소의 상태는 모든 것이 commit이 된 “Unmodified” 상태여야 pull을 할 수 있다. Pull은 사실 모든 commit을 내려받는 git fetch와 내려받은 commit들과 현재 로컬 파일에 반영하는 (합치는) git merge FETCH_HEAD 두 명령어를 결합한 것이다. 따라서 pull에는 merge와 관련된 옵션들이 있다.

 

git pull : 원격 저장소의 모든 브랜치의 commit들을 로컬 저장소에 받고 각 브랜치를 모두 merge 한다. 원격의 main은 로컬의 main와 합치고 원격의 some_branch는 로컬의 some_branch와 합친다.

git pull <remote> <local_branch> : 특정 local_branch만 변경 사항(commit)을 내려받고 합친다.

git pull [--ff / --no-ff / --only-ff] : merge 방식에 fast-forward 방식과 non-fast-forward 방식이 있는데 --only-ff는 fast-forward 방식이 가능할 때만 merge를 하라는 것이다.

 

이때 git pull을 하면 두 저장소의 수정사항이 서로 다르기에 "CONFLICT"라는 메시지가 뜬다.

# main의 경우는 'git pull'만 해도 된다.
~/workspace/notebook$ git pull origin main
...
From https://github.com/<your repository>/notebook
   834f2d3..512fb8d  main     -> origin/main
Auto-merging anything.py
CONFLICT (content): Merge conflict in anything.py
Automatic merge failed; fix conflicts and then commit the result.

anything.py파일에 충돌이 일어났으니 충돌을 고치고 커밋을 하라는 것이다. 파일의 상태를 확인해보자

충돌 표시가 된 곳은 같은 줄을 서로 다르게 수정했기 때문에 어느 저장소의 코드가 최종버전인지를 사용자가 선택해서 코드를 완성해줘야 한다. =======을 기준으로 위쪽(HEAD)은 현재 저장소에서 수정한 내용이고 아래쪽은 원격 저장소의 내용이다. 원격 저장소의 내용을 선택하여 anything.py를 수정하여 커밋을 다시 해준다.

~/workspace/notebook$ git status
~/workspace/notebook$ git add .
~/workspace/notebook$ git commit -m 'merge conflict'
 

 

  • git mv

mv는 move의 약자로 파일을 이동하고 상태를 stage한다. 버전 관리되는 파일을 A 디렉토리에서 B 디렉토리로 옮겨버리면 A에서는 파일이 삭제되고 B에는 새 파일이 추가된 것으로 인식한다. 이를 stage하기 위해서는 역시 git add -A를 해줘야한다. 그러므로 파일을 이동할 때는 가급적 mv를 이용하는 것이 좋다.

mv는 또한 단순히 파일의 이름을 바꾸는데도 사용된다. 같은 경로에 다른 이름으로 옮기면 파일명 변경이 된다.

git mv <src_file> <dst_file> : src_file을 dst_file로 이름을 바꾼다.

git mv <src_file> <dst_dir> : src_file을 dst_dir 디렉토리로 옮긴다.

git mv <src_file> <dst_dir/dst_file> : src_file을 dst_dir 디렉토리 아래 dst_file이란 이름으로 옮긴다.

 

파일들의 이름을 수정하여 git_practice라는 디렉토리로 옮겨보자

~/workspace/notebook$ git mv anything.py main.py
~/workspace/notebook$ mkdir git_practice
~/workspace/notebook$ git mv main.py git_practice
~/workspace/notebook$ git mv add.py git_practice
~/workspace/notebook$ git status
...
	renamed:    add.py -> git_practice/add.py
	renamed:    anything.py -> git_practice/main.py

git mvgit rm은 리눅스 명령어와 사용법이 같다. 단지 변경사항을 스테이지 해주는 것만 다르다.

 

마지막으로 최종본을 원격 저장소에 올리고 이것을 데스크톱 로컬 저장소에도 적용해보자.

~/workspace/notebookl$ git commit -m 'move files to git practice'
~/workspace/notebook$ git push origin main

~/workspace/notebook$ cd ~/workspace/desktop
~/workspace/desktop$ git pull
~/workspace/desktop$ tree
.
├── git_practice
│   ├── add.py
│   └── main.py
 
 

두 내용이 다르게 수정하여 충돌하는 내용이 없기에 git pull을 하면 바로 변경사항이 로컬 저장소에 반영이 된다.