Dev: Git의 개념과 기본 명령어

Heechan
HcleeDev
Published in
12 min readMar 6, 2021
Photo by Yancy Min on Unsplash

컴퓨터공학과 전공을 수강하거나, 개발에 대해 조금이라도 공부하다보면 필연적으로 Git에 대해 들어보게 된다. 하다못해 구글링을 하다 github라는 사이트 정도는 들어가보게 될 것이다. 실제로 현업에서 일을 하게 되면 대부분의 경우 Git을 필수적으로 사용하게 되는데, 처음에 Git의 개념이나 명령어가 굴러가는 방식에 대해 이해하지 못해 애먹었던 기억이 난다.

이번엔 Git에 대해 공부한 내용을 정리하면서 소개하는 글을 써보기로 했다. 하지만 Git이란 것이 워낙 방대하다보니 한 번에 모두 적진 못하고, 크게 세 파트로 나누어 작성하기로 정했다. 첫 번째는 Git의 기초와 기본적인 명령어, 두 번째는 협업에 사용하는 Git, 세 번째는 Git 심화 기능으로 나눌 것이다.

이 내용은 Git Document에 기반을 두고 공부한 내용이다.

Git은 분산 버전 관리 시스템(DVCS)

이 세상엔 다양한 버전 관리 시스템(VCS)가 있다. 버전 관리 시스템은 특정 시점에서 파일의 상태, 변화를 기록해두었다가 나중에 해당 버전을 다시 꺼내와 비교, 반영할 수 있도록 하는 시스템을 말한다.

기존의 버전 관리 시스템은 로컬부터 시작했는데, 이는 로컬 컴퓨터에 시점마다 파일의 상태를 저장해두고 버전화해 관리하는 방식이었다. 하지만 이는 로컬 사용자만 사용할 수 있고, 매번 다른 곳으로 파일이나 버전 관리 정보를 그대로 다 옮겨줘야 해 협업하기엔 단점이 있었다.

이 방법 말고는 중앙 서버에서 관리하는 방법도 있다. 중앙 서버에서 파일과 버전을 관리하고, 사용자들은 파일을 받아와서 사용하고 다시 서버로 보내 관리하는 방식이다. 이 방법은 협업은 보다 쉽게 가능하겠지만, 중앙 서버에 연결할 수 없는 상황의 경우엔 파일에 접근할 수 없다는 문제가 있다.

하지만 가장 보편적으로 사용되고 있는 Git은 ‘분산’ 버전 관리 시스템으로, 각 호스트들이 모두 파일 및 버전 정보를 가지고, 서버(원격 저장소)가 버전 정보를 가지고 있다.(서비스에 따라 파일 정보도 가지고 있다) 따라서 협업할 때는 서로 파일 변화만 맞춰주면 되고, 인터넷이 불가능하거나 호스트 하나가 다운되더라도 문제없이 파일을 사용할 수 있다.

출처: Git — 버전 관리란? (git-scm.com)

위 사진이 분산 버전 관리 시스템을 잘 설명해주고 있다. 서버 컴퓨터가 위에서 말한 원격 저장소라고 생각할 수 있다. 흔히 알고 있는 Github가 저런 서버를 제공하는 서비스라고 생각하면 된다. Github 말고도 비슷한 서비스의 다른 사이트도 많다.

Git은 어떻게 동작할까?

대부분의 기존 버전 관리 시스템은 ‘파일의 변화’에 주목했다. 이 방법은 가장 근간이 되는 파일이 있고, 각 시점(버전)마다 첫 파일으로부터 어떤 어떤 변화가 있었는지 기록한다. 즉, 변화(delta)를 저장한다고 생각할 수 있다.

하지만 Git은 버전을 바라보는 시선 자체를 다르다. Git은 파일 변화를 하나하나 기록하기보다는, 그 순간의 ‘데이터의 스냅샷’을 찍어 저장한다. 이 스냅샷은 그 크기가 굉장히 작아 저장 및 버전간의 전환이 빠르다.

출처: Git — Git 기초 (git-scm.com)

위 사진이 버전에 따른 파일 스냅샷을 표현한 것인데, 점선으로 된 것은 변화가 없어 딱히 저장하지 않고 이전 파일에 대한 링크만 저장한 것을 표현한다. 위처럼 버전을 관리하는 것을 ‘스냅샷의 스트림’이라고 부른다.

그런데 생각해보면 스냅샷으로 파일을 매번 찍으면 오히려 공간이 부족할 것 같은데 왜 크기가 작다는 것인지 의문이 들기도 한다. Git은 기록할 필요가 없는 파일의 경우(이전 버전과 차이가 없는 경우) 위에서 말했듯 이전 파일에 대한 링크만 제공하고 직접 저장하지 않는다. 전체 파일 시스템 중 변화가 있었던 파일만 스냅샷으로 그 형태를 저장한다.

다만 이 방식이 델타보다 공간적 이득이 있다는 말은 아니다. 당연히 파일의 일부 변화만 저장하는 델타 방식에 비하면 공간을 더 차지하긴 한다. 실제로 Git은 적당한 시점에 Garbage Collection을 이용해 연속된 버전들 중에 일부를 스냅샷에서 델타로 변경해 공간을 정리한다. 또한, Git이 아주 작은 변화처럼 스냅샷을 찍는 것이 더 손해일 수 있는 경우에는 알아서 판단해 델타로 저장할 때도 있다고 한다. 이런 기능들이 합쳐져 ‘Git은 저장소의 크기가 작다’고 말할 수 있는 것이다.

왼쪽은 처음 스냅샷들만 저장한 상태, 오른쪽은 GC 이후 최종 파일을 제외하면 delta로 변환해 저장소 크기를 줄인 상태

그리고, Git이 파일을 여타 VCS와 파일을 바라보는 방식이 다른 점은 차후 Branch를 이용한 협업 등에서 진면목을 발휘한다. 관련 내용은 다음 포스팅에서 다룰 예정이다.

분산 시스템이라는 것을 고려해 다시 생각해보면, 이런 변화를 로컬 컴퓨터에 있는 Git 시스템에 모두 저장해두었다가, 원격 저장소(서버)나 다른 컴퓨터들과 소통하면서 해당 변화들을 맞추어간다고 생각할 수 있다.

Git은 약간 조선시대 실록 사관들이랑 비슷한 점이 있어서, 기록이 삭제되는 일이 없다시피 한다. 뭘 하든 계속 추가만 된다. 이걸 삭제해줘! 라고 말하는 것마저 기록된다. 데이터 변화를 commit하기만 했다면, 그 기록은 Git 데이터베이스에 반드시 저장된다.

Git은 파일의 상태를 크게 3가지로 나눈다.

  • 첫 번째는 Modified다. 파일이 뭔가 변경되었고, 아직 로컬 Git 데이터베이스에 올라가지 않은 상태다.
  • 두 번째는 Staged다. 변경사항이 git add 명령어에 의해 Stage에 올라왔음을 말한다. Stage에 올라온 것들을 데이터베이스에 commit할 수 있다.
  • 세 번째는 Committed다. 변경된 데이터가 어떤 버전으로 로컬 Git 데이터베이스에 잘 저장된 상태를 말한다.
출처: Git — Git 기초 (git-scm.com)

Working Directory가 일반적으로 우리가 수정, 저장 등을 하는 디렉터리라고 보면 된다. 여기서 변경 사항이 생기고, Staging Area에 파일, 변경사항을 추가한 후 commit을 통해 .git이라는 저장소에 그 정보를 저장한다. 이 Git 디렉터리는 git init 을 실행하는 순간 폴더에 생긴다. 대부분의 경우엔 Staging Area에 파일이 존재하는 순간은 굉장히 짧다. 그냥 git add 후 바로 git commit 을 쳐버리기 때문에 Staging Area는 commit 전 제대로 올라갈 파일이 추가되었는지 확인하는 정도로 쓰인다.

Git의 기본 명령어

일단 이 명령어들은 터미널, CLI 환경에서 작성하는 것을 기준으로 설명한다. GUI는 SourceTree 밖에 안써봐서 잘 모르는지라…

완전 처음에 Git을 사용할 때는 어디에 이 명령어를 써야 하는지도 몰랐는데, 필요로 하는 디렉터리로 접근해서 사용하면 된다. 당연히 알고 있겠지만 터미널에서 디렉터리 사이를 뛰어다니는 명령어는 cd 디렉터리주소 다.

  • git init : 저장소 만들기

어떤 디렉터리에 들어가서 이 명령어를 사용하면 그 디렉터리에 .git 이라는 이름의 디렉터리(git 로컬 저장소)가 생기고, 현재 디렉터리와 그 하위 모든 디렉터리의 파일과 변화를 감지하기 시작한다.

  • git clone : 기존 저장소를 Clone하기

주로 Github 같은 곳에서 링크를 알아내서, 해당 저장소를 내 로컬 저장소로 그대로 가져오고 싶다면 이 명령어를 사용하면 된다. 대충 아래처럼 사용한다.

git clone https://github.com/{주소}
  • git status : 파일 상태 확인

이 명령어를 사용하면 현재 특이사항이 있는 파일의 상태를 알려준다. git status 를 입력하면 아래처럼 어떤 파일이 아직 Stage에 올라오지 않았는지, 어떤 파일이 Stage에 올라왔는지 확인할 수 있다. 실제론 터미널에서 색깔도 입혀서 보여주니 좀 더 알아보기 쉽다.

On branch feature/Onboarding
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
new file: RootView.swift
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: MainView.swift
modified: MainViewModel.swift
  • git add : 새로운 파일 추적 + Modified인 상태의 파일 Staged로 올리기

아직 Git이 Track하지 않는 파일을 Git이 Track하도록 설정할 수 있고, 변경사항을 Stage로 올려 commit을 할 수 있도록 준비시키는 명령어다. 주로 git add . 을 사용해 status 명령어에서 나오는 changes not staged 파일을 전부 Stage로 보내는 경우가 많다.

  • git diff : 단순히 상태만을 보는 것이 아닌 변경 내용까지 확인하기

바로 이전 commit과 현재 변경된 점의 차이를 보여준다. 가장 최근에 저장된 스냅샷과 현 시점을 찍은 스냅샷을 비교하고 그 결과를 알려주는 명령어다.

  • git commit : 변경된 파일 commit하기

이 명령어로 commit을 올릴 수 있다. 이걸로 로컬 데이터베이스에 올려버리면 파일 ‘버전’을 하나 만들었다고 생각하면 된다. 근데 그냥 git commit 만 쳐버리면 뭔가 입력하라는 창이 뜨는데, 각 commit을 설명하는 commit 메세지를 남겨달라는 창이다. 그래서 그런 창을 안보기 위해, 주로 commit할 때는 git commit -m "아무튼 무슨 메세지" 이런 식으로 한다.

지금까지 본 명령어로 파일을 수정하고 commit하는 것까지 흐름을 대충 알 수 있다.

$ git add . //변경된 파일 전체 Stage에 올리기
$ git status //제대로 Stage에 올라갔는지 상태를 확인
$ git commit -m "Something Fixed" //메세지와 함께 commit을 Git 로컬 디렉터리에 제출
출처: Git — 수정하고 저장소에 저장하기 (git-scm.com)
  • git commit --amend : commit 덮어쓰기

너무 일찍 commit 했거나 어떤 파일을 빼먹었거나 살짝 실수를 할 때 해당 변화를 직전 commit에 바로 통합시키는 방법이 있다. 아래 명령어대로 입력하면 빼먹은 부분이 직전 commit에 반영된다.

$ git commit -m 'initial commit' //실수로 일찍 commit함
$ git add forgotten_file //빼먹은 파일을 다시 Stage에 올림
$ git commit --amend //Stage에 있는 파일을 이전 commit에 통합
  • git rm : 파일 삭제하기

말그대로 파일을 삭제하는데, git에서 그만 track한다는 것뿐만 아니라 실제 디렉터리에서도 삭제해버린다. 만약 Git에서 track만 그만하게 하고 싶고 디렉터리에는 남기고 싶다면 git rm --cached 파일명 이런 식으로 사용하면 된다.

  • git log : Commit 로그 보기

Git 로컬 저장소에서 commit이 제출된 히스토리를 보여준다. 주로 HEAD에서 어떤 commit이 어느정도 떨어져있는지 확인할 때 사용한다.

  • git reset : Stage된 파일은 Unstaged 상태로 내리기

git reset HEAD 파일명 을 사용하면 이미 Stage된 파일을 Unstaged 상태로 내릴 수 있다.

  • .gitignore 파일 : Git 시스템이 무시하는 파일 목록을 담은 파일

이건 명령어는 아닌데, .gitignore라는 이름의 파일이 있다. 굳이 Git이 관리할 필요가 없거나, Github에 올리지 않고 싶은 정보의 경우 이용한다. 예를 들어, 어떤 서버에 접근하기 위한 API key 정보가 코드에 하드코딩되어있을 경우, 온라인에 해당 코드를 공개하기엔 힘든 점이 있다. 이럴 때 따로 파일을 만들고(예시 파일명으로 privacy.swift라고 하자), 해당 파일에 let APIKey = "아무튼 API Key" 를 선언해두고 다른 파일에서는 해당 상수명으로 API key를 사용하도록 한다. 그리고 .gitignore 파일에 privacy.swift를 넣어두면 공개된 원격 저장소에는 올라가지 않는다.

# 확장자가 .a인 파일 무시
*.a
# 윗 라인에서 확장자가 .a인 파일은 무시하게 했지만 lib.a는 무시하지 않음
!lib.a
# 현재 디렉토리에 있는 TODO파일은 무시하고 subdir/TODO처럼 하위디렉토리에 있는 파일은 무시하지 않음
/TODO
# build/ 디렉토리에 있는 모든 파일은 무시
build/
# doc/notes.txt 파일은 무시하고 doc/server/arch.txt 파일은 무시하지 않음
doc/*.txt
# doc 디렉토리 아래의 모든 .pdf 파일을 무시
doc/**/*.pdf

위는 도큐먼트에 있는 예제다. #의 경우 주석이다.

이외에도 원격 저장소를 활용하는 명령어도 많은데, 이는 다음 글에서 협업에 관해 다룰 때 설명하겠다.

결론

Git을 잘 모르고 단순히 add, status, commit 정도만 쓰고 있었는데, Git에 대한 개념을 좀 더 명확히 알고 쓰니까 명령어 쓸 때마다 생긴 근심 걱정이 많이 줄어들었다. 다음에는 Branch, Remote, Merge 등의 협업과 관련된 기초적인 내용을 다룰 수 있도록 하겠다.

참고한 것

Git — Book (git-scm.com)

#dogfeet — Git: 델타와 스냅샷

--

--

Heechan
HcleeDev

Junior iOS Developer / Front Web Developer, major in Computer Science