Git push fetch merge pull

똘이주인·2021년 8월 5일
0

push

git push는 원격 저장소(remote repository)에 코드 변경분을 업로드하기 위해서 사용하는 Git 명령어 이다.

git commit vs. git push

git commit 명령어는 로컬 저장소(local repository)에 코드 변경 이력을 남기기 위해서 사용됩니다. 여기서 로컬 저장소란 git clone 명령어를 통해서 내 컴퓨터에 복제해둔 원격 저장소의 복사본을 의마한ㄷㅏ. 따라서, git commit를 통해 로컬 저장소에 아무리 많은 코드 변경 이력을 남기더라도 원격 저장소에서는 알 길이 없다. 반드시 명시적으로 git push를 날려줘야, 그 동안 로컬 저장소에서 남겨놓은 코드 변경 이력들이 원격 저장소로 전송이 된다.

사용법

git push 명령어는 기본적으로 원격 저장소명과 브랜치명을 인자로 받는다.

$ git push <저장소명> <브랜치명>

my-feature라는 브랜치에 남겨놓은 코드 변경 이력을 origin라는 원격 저장소에 올리기 위한 git push 명령어는 다음과 같다.

$ git push origin my-feature

원격 저장소명은 git clone을 통해 저장소를 복제를 했다면 일반적으로 origin이며 git remote 명령어를 통해서 정확한 저장소명을 알아낼 수 있다.

$ git remote
origin

인자 생략 하기 1

git push 명령어를 날릴 때 마다 매번 저장소명과 브랜치명을 입력하는 게 귀찮게 느껴질 수 있다. 이럴 경우,
-u 옵션을 사용하면 최초에 한 번만 저장소명과 브랜치명을 입력하고 그 이후에는 모든 인자를 생략할 수 있다.
예를 들어, 저장소명과 브랜치명을 넘기면서 -u 옵션과 함께 git push 명령어를 날리면,

$ git push -u origin my-feature

그리고 커밋한 코드 변경분을 원격 저장소에 올릴 때는 인자없이 git push 명령어만 날리면 된다.

인자 생략 하기 2

여러 브랜치를 넘나 들면서 작업을 하는 경우에는 최초에 한 번 인자를 넘기는 것도 귀찮게 느껴질 수 있다. 대부분의 경우에는 로컬 저장소와 원격 저장소에서 동일한 브랜치 이름을 사용하기 때문에 항상 현재 브랜치를 기준으로 git push 명령어가 작동한다면 매우 편리할 것 같다.

이를 위해서는 약간의 설정이 필요한데, push.default 설정을 current로 설정해준다.

$ git config --global push.default current

이제부터는 어느 브랜치에서 작업을 하든 git push만 날리면 원격 저장소에 동일한 브랜치로 코드 변경분이 업로드된다.

$ git push

변경이력 덮어쓰기

로컬 저장소에서는 자유롭게 코드 변경 이력을 수정해도 내 컴퓨터 밖에 모르는 일이기 때문에 뭐라고 할 동료가 없다. 하지만 일단 원격 저장소에 코드 변경분을 올린 이후에는 더 이상 해당 코드 변경분은 순전히 나의 코드가 아니기 때문에 함부로 변경 이력을 수정하면 안 된다. 왜냐하면 동료가 해당 코드 변경분을 내려 받았는데, 내가 그 코드 변경 이력을 수정해서 다시 올리면, 그 동료에게 코드 충돌이 발생할 것이기 때문이다

그렇기 때문에, git push로 원격 저장소에 올린 코드 변경분은 절대 덮어쓰지 않는 것이 원칙입니다 개인 프로젝트에서는 이러한 원칙이 큰 의미가 없기 때문에, 간혹 코드 변경 이력을 덮어쓰고 싶을 때가 있다.

이럴 때는 -f 옵션을 사용하면, 원격 저장소 내의 해당 브랜치의 코드 변경 이력을 로컬 저장소의 코드 변경 이력으로 덮어써준다.

$ git push -f origin my-feature

pull & fetch

원격 저장소에서 로컬 저장소로 소스를 가져오는 명령어로는 pull과 fetch가 있다. fetch와 pull의 차이는 가져온 소소를 merge 하느냐 안하느냐의 차이가 있다. pull 명령어는 원격 저장소의 소스를 가져오고 해당 소스가 현재 내 소스보다 더 최신 버전이라고 하면 지금의 버전을 해당 소스에 맞춰 올린다. merge 명령어를 사용하는 것이다.

하지만 fetch의 경우 단지 소스를 가져올 뿐 merge 하지는 않는다.

다른 프로젝트에서 해당 파일을 업데이트 후 git으로 push하였습니다. 이제 fetch명령어를 통해 변경된 내용을 가져와 보도록 한다. git fetch <remote> 명령어 이다.

$ git fetch origin

아래 명령어로 HEAD와 origin/master가 가리키고 있는 위치가 다른것을 확인할 수 있다.

$ git log --decorate --all --oneline

git log with fetch

그리고 git diff 명령어를 통해 어디가 변경되었는지도 알 수 있다.

$ git diff HEAD origin/master

git diff

pull 명령어는 원격저장소에서 fetch명령어로 가져온 후 merge까지 한번에 해 실제 파일의 내용이 변경되는 명령어이다. 명령어는 git pull <원격 저장소 명> <branch 명>입니다.

$ git pull origin master

git pull

fast-forward 업데이트를 진행한다고 로그에 출력된 걸 볼 수 있습니다. 이제 로그를 본다.

$ git log --decorate --all --oneline

git log with git pull

HEAD와 origin/master가 동일한 위치를 가리키고 있는 것을 확인할 수 있다. 파일 역시 최신으로 업데이트 된 것을 확인할 수 있다.

merge

머지는 각 분기된 커밋을 하나의 커밋으로 다시 합치고 싶을 때 사용하는 명령어임

merge 기본 사용방법

기본적으로 merge를 하기위해 기본이 되는 커밋을 선택해 해당 커밋으로 체크아웃을 한다.

#merge 방법

$ git merge 합병원하는브랜치이름

merge에는 크게 두 가지 상황이 존재한다.

1. 서로 다른 파일을 수정했을 때

2. 서로 같은 파일을 수정했을 때

1번

만약 A라는 사람이 A.java 파일을 수정했고 B라는 사람이 B.java 파일을 수정했을 때 merge 한다면 git이 자동으로 소스코드를 합쳐줄 것이다. (충돌 없음)

2번의 상황은 또 다시 세부적으로 두가지 경우로 나뉠 수 있다.

2-1) 서로 같은 이름의 파일을 수정했지만 수정한 부분이 다를 때

이 상황은 A라는 사람이 X라는 파일의 1~200번째 줄까지 수정했고 B라는 사람이 X라는 파일의 400~600번째 줄까지 수정했다면 이 역시도 1번 상황과 마찬가지로 git이 자동으로 소스코드를 합쳐줄 것이다.

2-2) 서로 같은 이름의 파일을 수정하고 수정한 부분이 겹칠 때

이 상황은 A라는 사람이 X라는 파일의 특정 메서드 f()의 특정 부분을 수정했다고 하고, B라는 사람이 똑같이 X라는 파일의 특정 메서드f()의 특정 부분을 수정했다면 (최소 한줄이라도 같은 부분을 수정했다고 가정) git은 자동으로 merge를 해줄 수 없는 상황이라고 사용자에게 알리고 사용자가 그 부분을 수정해주기를 위임한다.

이런 상황을 conflict 충돌이 일어났다고 한다.

충돌이 일어나면 개발자가 직접 해당 부분의 소스코드를 고쳐야 한다.

master브랜치와 exp브랜치가 서로 공유하는 mergetest.txt라는 파일이 있다고 가정하고

// exp브랜치에서는 아래와 같이 소스를 수정하고 commit
function a(x, y){
    console.log(x);
    console.log(y);
    console.log(x+y);
    return x+y;
}

// master브랜치에서는 아래와 같이 소스를 수정하고 commit을 했다.
function a(x, y){
    console.log(y);// x -> y
    console.log(y);
    return x*y;// x+y -> x*y
}

이 master브랜치에서 exp브랜치를 merge하기 위해 "git merge exp" 라는 명령어를 치면, conflict message를 주고 git status 명령어에서도 알려줌. 특히, conflict가 발생한 파일을 열어보면 쉽게 알 수 있다.

function a(x, y){
				console.log(y);
<<<<<<< HEAD
				console.log(y);
				return x*y;
=======
				console.log(x+y);
>>>>>>> exp
}

<<<<<< HEAD 부터 >>>>>>까지가 충돌이 일어난 부분이고, <<<<<< HEAD 부터 ======까지는 master브랜치의 소스코드고, ======부터 >>>>>> exp까지는 exp브랜치의 소스코드다.

git이 이 부분을 알려주니 그 부분의 코드를 잘 수정해서 파일을 저장한 후 다시 commit을 해주면 올바르게 merge가 된다.

3way merge

서로 다른 브랜치의 커밋을 머지할 경우 발생하는 충돌에 대해서 깃은 3way merge라는 방법을 통해 충돌을 해결한다. 이를 조금 더 자세히 살펴보면

  1. 3way merge는 base의 내용을 참고한다. (base에서 해당 파일이 어떤 내용을 담고 있었는지를 파악한다.)
  2. 한 브랜치의 파일만 바뀐 경우 바뀐 쪽으로 변경이 된다.
  3. 두개 다 바뀌지 않았을 때는 base의 내용을 그대로 따른다.
  4. 이름이 같은 두 파일의 내용이 서로 다르게 바뀌었을 때는 사용자가 직접 수정을 해야 한다.

merge에서 메세지 파악하기

  1. fast-foward : 빨리감기라는 뜻으로 예를 들어 A브랜치에서 급하게 다른 것을 만들어보려고 B브랜치를 생성하고 B브랜치는 여러번의 commit이 있었다.
    이런 상황에서 A브랜치에서 B브랜치를 merge한다고 가정할 때, A브랜치에서 B브랜치를 분기해준 것외에 별 다른 commit이 없었으므로 A브랜치는 B브랜치만큼 그냥 "빨리감기"하면 되는 상황이다.

    그래서 이런 상황일 때 별도의 commit log없이 B브랜치가 만든 최신의 commit log를 가리키게 만드는 것을 fast-foward라고 한다.
  2.  recursive strategy : 재귀적인 전략으로 예를 들어 A브랜치에서 B브랜치를 생성했다. 그 후 A브랜치도 commit이 여러번 있었고 B브랜치도 commit이 여러번 있었을 때 A브랜치에서 B브랜치를 merge한다.

    이러면 fast-foward할 수 없고 git은 A브랜치와 B브랜치의 조상 commit을 찾아 내부적으로 3way merge라는 방법을 이용하여 merge를 해준다.

실전사용법[충돌예방]

혼자 개발하는 것이 아니기 때문에 충돌을 줄이기 위해 프로젝트의 큰 흐름인 master브랜치로 내맘대로 merge할 수는 없다.

그렇다고 merge 없이 1개월이든 6개월이든 내 branch안에서 개발만해서도 안됨, merge 때 conflict 수정이 엄청나게 많아지기 때문임
따라서 내 브랜치에 충돌을 최대한 예방하는 방법을 사용해야 한다.
즉, master 브랜치의 변화를 지속적으로 내 브랜치로 가져와서 충돌나는 부분을 매번 제거하면서 내 브랜치를 만들어야 먼 훗날에 master 브랜치로 병합할 때 충돌이 적게할 수 있다.

0개의 댓글