etc./StackOverFlow

Git 병합 대신 Git 리베이스를 언제 사용합니까?

청렴결백한 만능 재주꾼 2021. 12. 31. 03:33
반응형

질문자 :Coocoo4Cocoa


Git rebase 대 Git 병합을 사용하는 것이 권장되는 경우는 언제입니까?

성공적인 리베이스 후에도 여전히 병합해야 합니까?



짧은 버전

  • 병합은 한 분기의 모든 변경 사항을 가져와 한 커밋에서 다른 분기에 병합합니다.
  • Rebase는 내가 분기한 지점이 새로운 시작 지점으로 이동하기를 원한다고 말합니다.

그럼 언제 둘 중 하나를 사용합니까?

병합

  • 단일 기능을 개발할 목적으로 분기를 생성했다고 가정해 보겠습니다. 이러한 변경 사항을 다시 마스터로 가져오려면 병합을 원할 것입니다(모든 중간 커밋을 유지 관리하는 데 신경 쓰지 않음).

리베이스

  • 두 번째 시나리오는 일부 개발을 시작한 다음 다른 개발자가 관련 없는 변경을 수행한 경우입니다. 리포지토리의 현재 버전에서 변경 사항을 기반으로 하기 위해 풀(pull)한 다음 리베이스(rebase)하기를 원할 것입니다.

Rob Di Marco

간단 해. rebase를 사용하면 다른 분기를 작업의 새 기반 으로 사용한다고 말합니다.

예를 들어 분기 master 있는 경우 새 기능을 구현하기 위해 분기를 만들고 이름을 cool-feature 라고 말하면 물론 마스터 분기가 새 기능의 기반이 됩니다.

master 브랜치에서 구현한 새 기능을 추가하려고 합니다. master 전환하고 cool-feature 분기를 병합할 수 있습니다.

 $ git checkout master $ git merge cool-feature

그러나 이런 식으로 새로운 더미 커밋이 추가됩니다. 스파게티 역사를 피하려면 rebase 할 수 있습니다.

 $ git checkout cool-feature $ git rebase master

그런 다음 master 병합하십시오.

 $ git checkout master $ git merge cool-feature

이번에는 topic 브랜치에 동일한 master 커밋과 새로운 기능이 포함된 커밋이 있기 때문에 병합은 그냥 빨리감기됩니다.


Aldo 'xoen' Giambelluca

TL;DR

의심스러운 경우 병합을 사용하십시오.

짧은 답변

리베이스와 병합의 유일한 차이점은 다음과 같습니다.

  • 히스토리의 결과 트리 구조(일반적으로 커밋 그래프를 볼 때만 알 수 있음)가 다릅니다(하나는 분기가 있고 다른 하나는 없음).
  • 병합은 일반적으로 추가 커밋(예: 트리의 노드)을 생성합니다.
  • 병합 및 리베이스는 충돌을 다르게 처리합니다. Rebase는 한 번에 한 커밋씩 충돌을 표시하고 병합은 한 번에 모두 표시합니다.

따라서 짧은 대답은 히스토리가 어떻게 생겼는지에 따라 rebase 또는 merge 를 선택하는 것입니다.

긴 답변

사용할 작업을 선택할 때 고려해야 할 몇 가지 요소가 있습니다.

변경 사항을 받고 있는 브랜치가 팀 외부의 다른 개발자(예: 오픈 소스, 공개)와 공유되고 있습니까?

그렇다면 리베이스하지 마십시오. Rebase는 브랜치를 파괴하고 해당 개발자는 git pull --rebase 를 사용하지 않는 한 손상되거나 일관성이 없는 리포지토리를 갖게 됩니다. 이것은 다른 개발자들을 빠르게 화나게 하는 좋은 방법입니다.

귀하의 개발 팀은 얼마나 숙련되어 있습니까?

Rebase는 파괴적인 작업입니다. 즉, 올바르게 적용하지 않으면 커밋된 작업이 손실되거나 다른 개발자 저장소의 일관성이 깨질 수 있습니다.

저는 회사에서 분기 및 병합을 처리할 전담 직원을 확보할 수 있었던 시기에 개발자들이 모두 나온 팀에서 일했습니다. 그 개발자들은 Git에 대해 많이 알지 못하며 많이 알고 싶어하지 않습니다. 이 팀에서는 어떤 이유로든 리베이스를 추천하는 위험을 감수하지 않을 것입니다.

지점 자체가 유용한 정보를 나타내는가

일부 팀은 각 분기가 기능(또는 버그 수정 또는 하위 기능 등)을 나타내는 기능별 분기 모델을 사용합니다. 이 모델에서 분기는 관련 커밋 세트를 식별하는 데 도움이 됩니다. 예를 들어, 해당 분기의 병합을 되돌리면 기능을 빠르게 되돌릴 수 있습니다(정확히 말하면 이것은 드문 작업입니다). 또는 두 가지를 비교하여 기능을 비교합니다(더 일반적임). Rebase는 분기를 파괴할 것이고 이것은 간단하지 않을 것입니다.

나는 또한 개발자당 분기 모델을 사용하는 팀에서 일한 적이 있습니다(우리 모두가 거기에 있었습니다). 이 경우 분기 자체는 추가 정보를 전달하지 않습니다(커밋에 이미 작성자가 있음). 리베이스에 해가 없을 것입니다.

어떤 이유로든 병합을 되돌리고 싶습니까?

리베이스를 되돌리는 것(실행 취소에서와 같이)은 병합을 되돌리는 것에 비해 상당히 어렵거나 불가능합니다(리베이스에 충돌이 있는 경우). 되돌릴 가능성이 있다고 생각되면 병합을 사용하십시오.

팀에서 일합니까? 그렇다면 이 지점에서 모두 또는 전혀 접근하지 않을 의향이 있습니까?

리베이스 작업은 해당하는 git pull --rebase 합니다. 혼자 작업하는 경우 적절한 시간에 사용해야 하는 것을 기억할 수 있습니다. 팀으로 작업하는 경우 조정하기가 매우 어렵습니다. 이것이 대부분의 rebase 워크플로에서 모든 병합에 대해 rebase 사용을 권장하는 이유입니다(모든 git pull --rebase ).

흔한 신화

병합은 기록을 파괴합니다(스쿼시 커밋).

다음 병합이 있다고 가정합니다.

 B -- C / \ A--------D

어떤 사람들은 병합이 커밋 기록을 "파괴"한다고 말할 것입니다. 왜냐하면 마스터 브랜치(A-D)의 로그만 보면 B와 C에 포함된 중요한 커밋 메시지를 놓치기 때문입니다.

이것이 사실이라면 우리는 이런 질문을 하지 않을 것입니다. 기본적으로 B와 C를 보지 않도록 명시적으로 요청하지 않는 한(--first-parent 사용) B와 C가 표시됩니다. 이것은 스스로 시도하기가 매우 쉽습니다.

Rebase는 더 안전하고 간단한 병합을 허용합니다.

두 가지 접근 방식은 서로 다르게 병합되지만 어느 쪽이 다른 쪽보다 항상 더 나은지 명확하지 않으며 개발자 워크플로에 따라 달라질 수 있습니다. 예를 들어, 개발자가 정기적으로 커밋하는 경향이 있는 경우(예: 직장에서 집으로 전환할 때 하루에 두 번 커밋) 지정된 분기에 대해 많은 커밋이 있을 수 있습니다. 이러한 커밋 중 많은 부분이 최종 제품처럼 보이지 않을 수 있습니다(저는 기능당 한 두 번 접근 방식을 리팩토링하는 경향이 있습니다). 다른 사람이 코드의 관련 영역에서 작업 중이고 내 변경 사항을 리베이스하려고 하면 상당히 지루한 작업이 될 수 있습니다.

Rebase는 더 멋지고/섹시하고/더 전문적입니다.

rmrm -rf 로 별칭을 지정하려면 rebase가 적합합니다.

내 두 센트

저는 항상 언젠가 Git rebase가 문제를 해결하는 멋진 도구인 시나리오를 접하게 될 것이라고 생각합니다. 내 생각처럼 Git reflog가 내 문제를 해결하는 멋진 도구인 시나리오를 접하게 될 것입니다. 저는 5년 넘게 Git을 사용해 왔습니다. 그것은 일어나지 않았다.

지저분한 역사는 나에게 실제로 문제가 된 적이 없습니다. 나는 결코 흥미진진한 소설처럼 내 커밋 히스토리를 읽지 않습니다. 대부분의 경우 히스토리가 필요합니다. 어쨌든 Git 비난이나 Git 이등분을 사용할 것입니다. 이 경우 병합 커밋을 갖는 것이 실제로 나에게 유용합니다. 병합으로 인해 문제가 발생했다면 그것은 나에게 의미 있는 정보이기 때문입니다.

업데이트(2017년 4월)

내 일반적인 조언은 여전히 유효하지만 나는 개인적으로 rebase를 사용하여 부드러워졌다는 것을 언급해야 할 의무가 있습니다. 저는 최근에 Angular 2 Material 프로젝트와 많은 교류를 했습니다. 그들은 매우 깨끗한 커밋 기록을 유지하기 위해 rebase를 사용했습니다. 이를 통해 주어진 결함을 수정한 커밋과 해당 커밋이 릴리스에 포함되었는지 여부를 매우 쉽게 확인할 수 있었습니다. rebase를 올바르게 사용하는 좋은 예입니다.


Pace

TSaper가 언급한 내 자신의 답변 을 보완하기 위해 ,

  • rebase는 병합 전에 수행하는 것이 좋은 생각인 경우가 많습니다. 왜냐하면 아이디어는 병합할 B 의 작업을 Y
    (: 같이 "분기의 최근 시점부터 내 지점에서 내 작품을 재생,"REBASE "즉, 그러나 다시 병합하기 전에, 당신은 당신의 지점에서 충돌을 해결 B ).
    올바르게 완료되면 브랜치에서 브랜치 B 로의 후속 병합이 빨리 감기될 수 있습니다.

  • 병합은 대상 분기 B 직접 영향을 미치므로 병합이 더 간단합니다. 그렇지 않으면 분기 B 가 안정적인 상태로 돌아가는 데 오래 걸릴 수 있습니다(모든 충돌을 해결할 시간).


rebase 후 병합의 요점은 무엇입니까?

내가 설명하는 경우에, 나는 리베이스 B 단지에서 최근 시점에서 내 작품을 재생 할 수있는 기회 가지고, 내 지점에 B 내 지점에 머물고 있지만,하면서.
이 경우 내 "재생된" 작업을 B 로 가져오려면 병합이 여전히 필요합니다.

다른 시나리오( 예를 들어 Git Ready에서 설명 B 직접 가져오는 것입니다(모든 멋진 커밋을 보존하거나 대화형 리베이스를 통해 다시 정렬할 수 있는 기회를 제공합니다).
그 경우(B 브랜치에 있는 동안 리베이스하는 경우) 당신이 옳습니다: 더 이상의 병합이 필요하지 않습니다:

병합하거나 리베이스하지 않은 경우 기본적으로 Git 트리

리베이스1

우리는 rebase함으로써 얻는다:

리베이스3

두 번째 시나리오는 새 기능을 마스터로 되돌리는 방법에 관한 것입니다.

제 요점은 첫 번째 리베이스 시나리오를 설명함으로써 모든 사람에게 리베이스가 이에 대한 예비 단계로 사용될 수 있음을 상기시키는 것입니다(즉, "새로운 기능을 마스터로 되돌리기").
rebase를 사용하여 먼저 마스터를 새 기능 분기 "안으로" 가져올 수 있습니다. 리베이스는 HEAD master 에서 새 기능 커밋을 재생하지만 여전히 새 기능 분기에 있으므로 이전 마스터 커밋에서 분기 시작점을 효과적으로 이동합니다. HEAD-master .
이를 통해 브랜치의 모든 충돌을 해결할 수 있습니다(즉, 충돌 해결 단계가 너무 오래 걸리는 경우 마스터가 계속 병렬로 발전할 수 있도록 격리됨).
그런 다음 마스터로 전환하고 new-feature 병합할 수 있습니다(또는 new-feature 분기에서 수행된 커밋을 보존하려는 경우 new-featuremaster 리베이스).

그래서:

  • master 에 대한 작업을 가져오는 두 가지 방법으로 볼 수 있습니다.
  • 그러나 "리베이스 후 병합"은 먼저 충돌을 분리하여 해결한 다음 작업을 다시 가져오는 유효한 워크플로가 될 수 있습니다.

VonC

여기에 많은 답변이 병합하면 모든 커밋이 하나로 바뀌므로 rebase를 사용하여 커밋을 보존할 것을 제안합니다. 이것은 잘못된 것입니다. 그리고 이미 커밋을 푸시했다면 나쁜 생각 입니다.

병합은 커밋을 지우지 않습니다. 병합은 역사를 보존합니다! (gitk를 보라) Rebase는 기록을 다시 씁니다. 이것은 당신이 그것을 푸시한 후에 나쁜 것입니다.

병합을 사용하십시오 -- 이미 푸시할 때마다 리베이스하지 마십시오.

다음은 Linus(Git의 저자)가 수행한 작업입니다 (현재 Wayback Machine에서 복구한 내 블로그에서 호스팅됨). 정말 잘 읽었습니다.

또는 아래에서 동일한 아이디어의 제 자신의 버전을 읽을 수 있습니다.

마스터에서 브랜치 리베이스:

  • 커밋이 어떻게 생성되었는지에 대한 잘못된 아이디어를 제공합니다.
  • 제대로 테스트되지 않았을 수 있는 중간 커밋으로 마스터를 오염시킵니다.
  • 원래 토픽 브랜치가 생성된 시점과 리베이스된 시점 사이에 마스터가 변경되었기 때문에 실제로 이러한 중간 커밋에 빌드 중단을 도입할 수 있습니다.
  • 마스터에서 결제하기 좋은 곳을 찾기가 어렵습니다.
  • 커밋의 타임스탬프가 트리의 시간 순서와 일치하지 않도록 합니다. 따라서 마스터에서는 커밋 A가 커밋 B보다 먼저 실행되지만 커밋 B가 먼저 작성되었음을 알 수 있습니다. (뭐?!)
  • 토픽 브랜치의 개별 커밋은 각각 개별적으로 해결되어야 하는 병합 충돌을 포함할 수 있기 때문에 더 많은 충돌을 생성합니다(각 커밋에서 발생한 일에 대한 기록에 더 있음).
  • 역사의 재작성이다. 리베이스되고 있는 브랜치가 다른 곳으로 푸시되었다면(자신이 아닌 다른 사람과 공유) 히스토리를 다시 작성한 이후로 해당 브랜치를 가진 다른 모든 사람을 망친 것입니다.

대조적으로, 주제 분기를 마스터로 병합:

  • 마스터에서 토픽 브랜치로의 병합을 포함하여 토픽 브랜치가 생성된 위치에 대한 기록을 보존하여 최신 상태로 유지합니다. 개발자가 빌드할 때 어떤 코드로 작업했는지 정확히 알 수 있습니다.
  • master는 대부분 병합으로 구성된 분기이며 각 병합 커밋은 일반적으로 확인하기에 안전한 기록의 '좋은 점'입니다. 왜냐하면 그곳에서 topic 분기가 통합될 준비가 되었기 때문입니다.
  • 토픽 브랜치에 있었다는 사실을 포함하여 토픽 브랜치의 모든 개별 커밋은 보존되므로 이러한 변경 사항을 격리하는 것이 자연스럽고 필요한 위치에서 드릴할 수 있습니다.
  • 병합 충돌은 (병합 시점에서) 한 번만 해결하면 되므로 토픽 분기에서 수행된 중간 커밋 변경은 독립적으로 해결될 필요가 없습니다.
  • 여러 번 원활하게 수행할 수 있습니다. 주기적으로 마스터하기 위해 토픽 브랜치를 통합하면 사람들은 토픽 브랜치를 계속 구축할 수 있고 독립적으로 계속 병합할 수 있습니다.

Andrew Arnott

이 질문에 답하는 내 말로 팀을 위한 FAQ를 만들었습니다. 공유하겠습니다:

merge 이란 무엇입니까?

다른 분기의 모든 변경 사항을 현재로 결합하는 커밋입니다.

rebase 란 무엇입니까?

현재 분기의 모든 커밋을 다른 기본 커밋으로 다시 커밋합니다.

mergerebase 의 주요 차이점은 무엇입니까?

  1. merge 는 하나의 새 커밋만 실행합니다. rebase 일반적으로 여러 번 (현재 분기의 커밋 수)를 실행합니다.
  2. merge새로 생성된 커밋(소위 merge-commit)을 생성합니다. rebase 는 기존 커밋만 이동합니다.

어떤 상황에서 merge 을 사용해야 합니까?

분기된 분기의 변경 사항을 기본 분기에 다시 merge 사용하십시오.

일반적으로 GitHub와 같은 Pull/Merge Requests에서 "Merge" 버튼을 클릭하면 됩니다.

어떤 상황에서 rebase 사용해야 합니까?

기본 분기의 변경 사항을 분기된 분기에 다시 추가 rebase 사용하십시오.

main 분기에 변경 사항이 있을 때마다 feature 분기에서 이 작업을 수행합니다.

기본 분기의 변경 사항을 기능 분기로 merge 하기 위해 병합을 사용하지 않는 이유는 무엇입니까?

  1. git 기록에는 불필요한 병합 커밋이 많이 포함됩니다. 기능 분기에 여러 병합이 필요한 경우 기능 분기는 실제 커밋보다 더 많은 병합 커밋을 보유할 수도 있습니다!

  2. 이것은 Git 히스토리의 시각화에 문제를 일으키는 Git이 설계된 멘탈 모델을 파괴 하는 루프를 생성합니다.

    강이 있다고 상상해보십시오(예: "나일강"). 물은 한 방향(Git 역사에서 시간의 방향)으로 흐릅니다. 때때로, 그 강에 가지가 있다고 상상하고 그 가지의 대부분이 다시 강으로 합쳐진다고 가정합니다. 그것이 강의 흐름이 자연스럽게 보일 수 있는 것입니다. 말된다.

    그런데 그 강의 작은 지류가 있다고 상상해 보세요. 그런 다음 어떤 이유에서인지 강은 가지에 합류 하고 가지가 거기에서 계속됩니다. 강은 이제 기술적으로 사라졌고 이제 지점에 있습니다. 하지만 어떻게든 마법처럼 그 가지가 다시 강으로 합쳐집니다. 어느 강을 물으십니까? 모르겠어요. 강은 실제로 지금 지점에 있어야 하지만 어떻게든 여전히 계속 존재하므로 지점을 다시 강으로 병합할 수 있습니다. 따라서 강은 강에 있습니다. 의미가 없습니다.

    이것은 기본 분기를 feature merge feature 분기가 완료되면 기본 분기로 다시 병합할 때 발생하는 일입니다. 멘탈 모델이 깨졌습니다. 그 때문에 별로 도움이 되지 않는 분기 시각화로 끝납니다.

merge 사용 시 Git 기록의 예:

병합 사용 시 Git 기록의 예

Merge branch 'main' into ... 시작하는 많은 커밋에 유의하십시오. 리베이스하면 존재하지도 않습니다(거기에서는 pull 요청 병합 커밋만 갖게 됩니다). 또한 많은 시각적 분기 병합 루프( main feature 에서 main 기능으로).

rebase 사용할 때 Git 기록의 예:

rebase 사용 시 Git 기록의 예

훨씬 적은 수의 병합 커밋과 복잡한 시각적 분기 병합 루프가 없는 훨씬 더 깨끗한 Git 기록.

rebase 단점이나 함정이 있습니까?

예:

  1. rebase 는 커밋을 이동하기 때문에(기술적으로 다시 실행), 이동된 모든 커밋의 커밋 날짜는 rebase 시간이 되며 git history는 초기 커밋 시간을 잃습니다 . 따라서 어떤 이유로 정확한 커밋 날짜가 필요한 경우 merge 이 더 나은 옵션입니다. 그러나 일반적으로 깨끗한 git 기록은 정확한 커밋 날짜보다 훨씬 더 유용합니다.
  2. 리베이스된 브랜치에 동일한 라인을 변경하는 커밋이 여러 개 있고 기본 브랜치에서도 해당 라인이 변경된 경우, 동일한 라인에 대한 병합 충돌을 여러 번 해결해야 할 수 있습니다. 이는 병합할 때 수행할 필요가 전혀 없습니다. 따라서 평균적으로 해결해야 할 병합 충돌이 더 많습니다.

rebase 사용할 때 병합 충돌을 줄이는 팁:

  1. 자주 리베이스하십시오 . 일반적으로 하루에 한 번 이상 수행하는 것이 좋습니다.
  2. 같은 줄의 변경 사항 을 가능한 한 한 커밋으로 스쿼시하십시오.

Jeehut

TLDR: 가장 중요한 것이 무엇인지에 달려 있습니다. 깔끔한 역사 또는 개발 순서의 진정한 표현

깔끔한 히스토리가 가장 중요하다면 먼저 리베이스한 다음 변경 사항을 병합하므로 새 코드가 무엇인지 정확히 알 수 있습니다. 이미 브랜치를 푸시했다면 결과에 대처할 수 없다면 리베이스하지 마십시오.

시퀀스의 진정한 표현이 가장 중요하다면 rebase 없이 병합할 것입니다.

병합은 다음을 의미합니다. 변경 사항을 대상에 병합하는 하나의 새 커밋을 만듭니다. 참고: 이 새 커밋에는 두 개의 부모가 있습니다. 커밋 문자열의 최신 커밋과 병합하려는 다른 분기의 최신 커밋입니다.

Rebase 의미: 현재 커밋 세트를 힌트로 사용하여 완전히 새로운 커밋 시리즈를 만듭니다. 다시 말해서, 내가 기초를 두고 있는 지점에서 변경 사항을 만들기 시작했다면 변경 사항이 어떻게 생겼을지 계산하십시오. 따라서 리베이스 후에 변경 사항을 다시 테스트해야 할 수 있으며 리베이스 중에 몇 가지 충돌이 발생할 수 있습니다.

이것을 감안할 때 왜 rebase를 하시겠습니까? 개발 이력을 명확하게 유지하기 위해서입니다. 기능 X에 대해 작업 중이고 작업이 완료되면 변경 사항을 병합한다고 가정해 보겠습니다. 이제 대상에는 "추가된 기능 X" 행을 따라 말하는 단일 커밋이 있습니다. 이제 병합하는 대신 기반을 다시 설정한 다음 병합하면 대상 개발 기록에 단일 논리적 진행의 모든 개별 커밋이 포함됩니다. 이렇게 하면 나중에 변경 사항을 훨씬 쉽게 검토할 수 있습니다. 50명의 개발자가 항상 다양한 기능을 병합했다면 개발 기록을 검토하는 것이 얼마나 어려울지 상상해보십시오.

즉, 업스트림에서 작업 중인 분기를 이미 푸시한 경우 리베이스하지 않고 대신 병합해야 합니다. 업스트림으로 푸시되지 않은 분기의 경우 리베이스, 테스트 및 병합합니다.

리베이스를 원할 수 있는 또 다른 경우는 업스트림을 푸시하기 전에 브랜치에서 커밋을 제거하려는 경우입니다. 예: 초기에 일부 디버깅 코드를 도입하는 커밋과 해당 코드를 정리하는 추가 커밋. 이를 수행하는 유일한 방법은 대화형 리베이스를 수행하는 것입니다. git rebase -i <branch/commit/tag>

업데이트: Git을 사용하여 비선형 기록(예 : Subversion) 을 지원하지 않는 버전 제어 시스템에 인터페이스할 때도 rebase를 사용하고 싶습니다. git-svn 브리지를 사용할 때 Subversion으로 다시 병합하는 변경 사항은 트렁크의 가장 최근 변경 사항 위에 변경 사항의 순차적 목록이라는 것이 매우 중요합니다. 이를 수행하는 방법은 두 가지뿐입니다. (1) 변경 사항을 수동으로 다시 생성하고 (2) 훨씬 더 빠른 rebase 명령을 사용합니다.

업데이트 2: 리베이스를 생각하는 또 다른 방법은 개발 스타일에서 커밋하려는 저장소에서 허용되는 스타일로 일종의 매핑을 가능하게 한다는 것입니다. 작고 작은 덩어리로 커밋하는 것을 좋아한다고 가정해 보겠습니다. 오타를 수정하기 위한 커밋, 사용하지 않는 코드를 제거하기 위한 커밋 등이 있습니다. 해야 할 일을 마치면 긴 커밋 시리즈가 생깁니다. 이제 커밋하는 저장소가 큰 커밋을 권장한다고 가정해 보겠습니다. 따라서 수행 중인 작업에 대해 하나 또는 두 개의 커밋이 예상됩니다. 커밋 문자열을 어떻게 가져와 예상대로 압축합니까? 대화형 리베이스를 사용하고 작은 커밋을 더 적은 수의 더 큰 청크로 스쿼시합니다. 반대가 필요한 경우에도 마찬가지입니다. 스타일이 몇 개의 큰 커밋이지만 저장소가 작은 커밋의 긴 문자열을 요구하는 경우입니다. 그렇게 하기 위해 rebase를 사용할 수도 있습니다. 대신 병합했다면 이제 커밋 스타일을 주 저장소에 접목한 것입니다. 개발자가 많다면 시간이 지나면 여러 커밋 스타일로 히스토리를 추적하는 것이 얼마나 어려울지 상상할 수 있습니다.

업데이트 3: Does one still need to merge after a successful rebase? 네, 그렇습니다. 그 이유는 리베이스가 본질적으로 커밋의 "이동"을 포함하기 때문입니다. 위에서 말했듯이 이러한 커밋은 계산되지만 분기 지점에서 14개의 커밋이 있는 경우 리베이스에 아무 문제가 없다고 가정하면 이후에 14개 커밋이 앞서게 됩니다(리베이스할 지점). 리베이스가 완료되었습니다. 리베이스 전에 분기가 있었습니다. 후에 같은 길이의 가지가 생깁니다. 변경 사항을 게시하기 전에 병합해야 합니다. 다시 말해, 원하는 만큼 리베이스하십시오(변경 사항을 업스트림으로 푸시하지 않은 경우에만). rebase한 후에만 병합하십시오.


Carl

병합은 확실히 변경 사항을 통합하는 가장 쉽고 가장 일반적인 방법이지만 유일한 방법은 아닙니다. Rebase 는 통합의 대체 수단입니다.

병합 이해하기

Git은 병합을 수행할 때 세 가지 커밋을 찾습니다.

  • (1) 공통 조상 커밋. 프로젝트에서 두 브랜치의 히스토리를 따르면 항상 최소한 하나의 공통 커밋이 있습니다. 이 시점에서 두 브랜치는 동일한 내용을 가지고 있었고 다르게 발전했습니다.
  • (2) + (3) 각 분기의 끝점. 통합의 목표는 두 분기의 현재 상태를 결합하는 것입니다. 따라서 각각의 최신 개정판은 특히 중요합니다. 이 세 가지 커밋을 결합하면 우리가 목표로 하는 통합이 이루어집니다.

빨리 감기 또는 병합 커밋

매우 간단한 경우에 두 분기 중 하나는 분기가 발생한 이후로 새 커밋이 없습니다. 최신 커밋은 여전히 공통 조상입니다.

여기에 이미지 설명 입력

이 경우 통합을 수행하는 것은 매우 간단합니다. Git은 공통 조상 커밋 위에 다른 분기의 모든 커밋을 추가할 수 있습니다. Git에서 이 가장 단순한 형태의 통합을 "빨리 감기" 병합이라고 합니다. 그런 다음 두 지점은 정확히 동일한 기록을 공유합니다.

여기에 이미지 설명 입력

그러나 많은 경우 두 지점이 개별적으로 진행되었습니다.

여기에 이미지 설명 입력

통합을 하기 위해 Git은 이들 간의 차이점을 포함하는 새로운 커밋(병합 커밋)을 생성해야 합니다.

여기에 이미지 설명 입력

휴먼 커밋 및 병합 커밋

일반적으로 커밋은 사람이 신중하게 만듭니다. 관련된 변경 사항만 래핑하고 주석으로 주석을 달 수 있는 의미 있는 단위입니다.

병합 커밋은 약간 다릅니다. 개발자가 생성하는 대신 Git이 자동으로 생성합니다. 그리고 관련 변경 세트를 래핑하는 대신 매듭처럼 두 가지를 연결하는 것이 목적입니다. 나중에 병합 작업을 이해하려면 두 분기의 이력과 해당 커밋 그래프를 살펴봐야 합니다.

Rebase와 통합

어떤 사람들은 그러한 자동 병합 커밋을 사용하지 않는 것을 선호합니다. 대신, 그들은 프로젝트의 역사가 마치 하나의 직선으로 진화한 것처럼 보이기를 원합니다. 어느 시점에서 여러 가지로 분할되었다는 표시는 남아 있지 않습니다.

여기에 이미지 설명 입력

rebase 작업을 단계별로 살펴보겠습니다. 시나리오는 이전 예제와 동일합니다. 우리는 브랜치 B에서 브랜치 A로 변경 사항을 통합하려고 하지만 지금은 rebase를 사용합니다.

여기에 이미지 설명 입력

우리는 이것을 3단계로 할 것입니다

  1. git rebase branch-A // Synchronises the history with branch-A
  2. git checkout branch-A // Change the current branch to branch-A
  3. git merge branch-B // Merge/take the changes from branch-B to branch-A

첫째, Git은 라인이 분기되기 시작한 후(공통 조상 커밋 이후) 발생한 모든 커밋을 분기 A에서 "실행 취소"합니다. 그러나 물론, 그것은 그것들을 버리지 않을 것입니다: 대신 당신은 그 커밋을 "일시적으로 저장되는" 것으로 생각할 수 있습니다.

여기에 이미지 설명 입력

다음으로 통합하려는 브랜치 B의 커밋을 적용합니다. 이 시점에서 두 분기는 정확히 동일하게 보입니다.

여기에 이미지 설명 입력

마지막 단계에서 지점 A에 대한 새 커밋이 이제 다시 적용되지만 지점 B의 통합 커밋 위에 새 위치에 있습니다(재 기반).

결과는 개발이 일직선으로 일어난 것처럼 보인다. 결합된 모든 변경 사항을 포함하는 병합 커밋 대신 원래 커밋 구조가 유지되었습니다.

여기에 이미지 설명 입력

마지막으로 원치 않는 자동 생성 커밋이 없는 깨끗한 분기 분기 A 를 얻습니다.

참고: git-tower 의 멋진 게시물 에서 가져왔습니다. rebase단점 도 같은 게시물에서 잘 읽었습니다.


Abdullah Khan

병합/리베이스 전:

 A <- B <- C [master] ^ \ D <- E [branch]

git merge master 후 :

 A <- B <- C ^ ^ \ \ D <- E <- F

git rebase master 후 :

 A <- B <- C <- D' <- E'

(A, B, C, D, E, F는 커밋)

이 예제와 Git에 대한 훨씬 더 잘 설명된 정보는 Git The Basics Tutorial 에서 찾을 수 있습니다.


guybrush

이 답변은 Git Flow를 중심으로 광범위하게 지향됩니다. 테이블은 멋진 ASCII 테이블 생성기 로 생성되었으며 이 멋진 명령( git lg별칭) 을 사용하여 기록 트리가 생성되었습니다.

 git log --graph --abbrev-commit --decorate --date=format:'%Y-%m-%d %H:%M:%S' --format=format:'%C(bold blue)%h%C(reset) - %C(bold cyan)%ad%C(reset) %C(bold green)(%ar)%C(reset)%C(bold yellow)%d%C(reset)%n'' %C(white)%s%C(reset) %C(dim white)- %an%C(reset)'

테이블은 히스토리 트리와 더 일관성을 유지하기 위해 역순으로 나열됩니다. 또한 사이의 차이를 참조 git mergegit merge --no-ff (당신이 일반적으로 사용하려는 첫 git merge --no-ff 이 현실에 가깝게 기록 모양을 만들면서 일) :

git merge

명령:

 Time Branch "develop" Branch "features/foo" ------- ------------------------------ ------------------------------- 15:04 git merge features/foo 15:03 git commit -m "Third commit" 15:02 git commit -m "Second commit" 15:01 git checkout -b features/foo 15:00 git commit -m "First commit"

결과:

 * 142a74a - YYYY-MM-DD 15:03:00 (XX minutes ago) (HEAD -> develop, features/foo) | Third commit - Christophe * 00d848c - YYYY-MM-DD 15:02:00 (XX minutes ago) | Second commit - Christophe * 298e9c5 - YYYY-MM-DD 15:00:00 (XX minutes ago) First commit - Christophe

git merge --no-ff

명령:

 Time Branch "develop" Branch "features/foo" ------- -------------------------------- ------------------------------- 15:04 git merge --no-ff features/foo 15:03 git commit -m "Third commit" 15:02 git commit -m "Second commit" 15:01 git checkout -b features/foo 15:00 git commit -m "First commit"

결과:

 * 1140d8c - YYYY-MM-DD 15:04:00 (XX minutes ago) (HEAD -> develop) |\ Merge branch 'features/foo' - Christophe | * 69f4a7a - YYYY-MM-DD 15:03:00 (XX minutes ago) (features/foo) | | Third commit - Christophe | * 2973183 - YYYY-MM-DD 15:02:00 (XX minutes ago) |/ Second commit - Christophe * c173472 - YYYY-MM-DD 15:00:00 (XX minutes ago) First commit - Christophe

git mergegit rebase

첫 번째 요점: 항상 기능을 개발로 병합하고 기능에서 개발을 리베이스하지 마십시오 . 이것은 Rebase의 황금률의 결과입니다.

git rebase 의 황금률 은 퍼블릭 브랜치에서 절대 사용하지 않는 것입니다.

다시 말해 :

당신이 어딘가에 밀어 넣은 것을 절대로 리베이스하지 마십시오.

나는 개인적으로 덧붙이고 싶다: 그것이 기능 브랜치이고 당신과 당신의 팀이 그 결과를 알고 있지 않는 한 .

따라서 git mergegit rebase 는 거의 기능 분기에만 적용됩니다(다음 예에서 --no-ff 는 병합할 때 항상 사용되었습니다). 더 나은 솔루션이 있는지 확신할 수 없기 때문에( 토론이 있음 ) 두 명령이 어떻게 작동하는지만 설명하겠습니다. 제 경우에는 더 멋진 히스토리 트리를 생성 git rebase 를 사용하는 것을 선호합니다. :)

기능 분기 사이

git merge

명령:

 Time Branch "develop" Branch "features/foo" Branch "features/bar" ------- -------------------------------- ------------------------------- -------------------------------- 15:10 git merge --no-ff features/bar 15:09 git merge --no-ff features/foo 15:08 git commit -m "Sixth commit" 15:07 git merge --no-ff features/foo 15:06 git commit -m "Fifth commit" 15:05 git commit -m "Fourth commit" 15:04 git commit -m "Third commit" 15:03 git commit -m "Second commit" 15:02 git checkout -b features/bar 15:01 git checkout -b features/foo 15:00 git commit -m "First commit"

결과:

 * c0a3b89 - YYYY-MM-DD 15:10:00 (XX minutes ago) (HEAD -> develop) |\ Merge branch 'features/bar' - Christophe | * 37e933e - YYYY-MM-DD 15:08:00 (XX minutes ago) (features/bar) | | Sixth commit - Christophe | * eb5e657 - YYYY-MM-DD 15:07:00 (XX minutes ago) | |\ Merge branch 'features/foo' into features/bar - Christophe | * | 2e4086f - YYYY-MM-DD 15:06:00 (XX minutes ago) | | | Fifth commit - Christophe | * | 31e3a60 - YYYY-MM-DD 15:05:00 (XX minutes ago) | | | Fourth commit - Christophe * | | 98b439f - YYYY-MM-DD 15:09:00 (XX minutes ago) |\ \ \ Merge branch 'features/foo' - Christophe | |/ / |/| / | |/ | * 6579c9c - YYYY-MM-DD 15:04:00 (XX minutes ago) (features/foo) | | Third commit - Christophe | * 3f41d96 - YYYY-MM-DD 15:03:00 (XX minutes ago) |/ Second commit - Christophe * 14edc68 - YYYY-MM-DD 15:00:00 (XX minutes ago) First commit - Christophe

git rebase

명령:

 Time Branch "develop" Branch "features/foo" Branch "features/bar" ------- -------------------------------- ------------------------------- ------------------------------- 15:10 git merge --no-ff features/bar 15:09 git merge --no-ff features/foo 15:08 git commit -m "Sixth commit" 15:07 git rebase features/foo 15:06 git commit -m "Fifth commit" 15:05 git commit -m "Fourth commit" 15:04 git commit -m "Third commit" 15:03 git commit -m "Second commit" 15:02 git checkout -b features/bar 15:01 git checkout -b features/foo 15:00 git commit -m "First commit"

결과:

 * 7a99663 - YYYY-MM-DD 15:10:00 (XX minutes ago) (HEAD -> develop) |\ Merge branch 'features/bar' - Christophe | * 708347a - YYYY-MM-DD 15:08:00 (XX minutes ago) (features/bar) | | Sixth commit - Christophe | * 949ae73 - YYYY-MM-DD 15:06:00 (XX minutes ago) | | Fifth commit - Christophe | * 108b4c7 - YYYY-MM-DD 15:05:00 (XX minutes ago) | | Fourth commit - Christophe * | 189de99 - YYYY-MM-DD 15:09:00 (XX minutes ago) |\ \ Merge branch 'features/foo' - Christophe | |/ | * 26835a0 - YYYY-MM-DD 15:04:00 (XX minutes ago) (features/foo) | | Third commit - Christophe | * a61dd08 - YYYY-MM-DD 15:03:00 (XX minutes ago) |/ Second commit - Christophe * ae6f5fc - YYYY-MM-DD 15:00:00 (XX minutes ago) First commit - Christophe

develop 에서 기능 분기로

git merge

명령:

 Time Branch "develop" Branch "features/foo" Branch "features/bar" ------- -------------------------------- ------------------------------- ------------------------------- 15:10 git merge --no-ff features/bar 15:09 git commit -m "Sixth commit" 15:08 git merge --no-ff develop 15:07 git merge --no-ff features/foo 15:06 git commit -m "Fifth commit" 15:05 git commit -m "Fourth commit" 15:04 git commit -m "Third commit" 15:03 git commit -m "Second commit" 15:02 git checkout -b features/bar 15:01 git checkout -b features/foo 15:00 git commit -m "First commit"

결과:

 * 9e6311a - YYYY-MM-DD 15:10:00 (XX minutes ago) (HEAD -> develop) |\ Merge branch 'features/bar' - Christophe | * 3ce9128 - YYYY-MM-DD 15:09:00 (XX minutes ago) (features/bar) | | Sixth commit - Christophe | * d0cd244 - YYYY-MM-DD 15:08:00 (XX minutes ago) | |\ Merge branch 'develop' into features/bar - Christophe | |/ |/| * | 5bd5f70 - YYYY-MM-DD 15:07:00 (XX minutes ago) |\ \ Merge branch 'features/foo' - Christophe | * | 4ef3853 - YYYY-MM-DD 15:04:00 (XX minutes ago) (features/foo) | | | Third commit - Christophe | * | 3227253 - YYYY-MM-DD 15:03:00 (XX minutes ago) |/ / Second commit - Christophe | * b5543a2 - YYYY-MM-DD 15:06:00 (XX minutes ago) | | Fifth commit - Christophe | * 5e84b79 - YYYY-MM-DD 15:05:00 (XX minutes ago) |/ Fourth commit - Christophe * 2da6d8d - YYYY-MM-DD 15:00:00 (XX minutes ago) First commit - Christophe

git rebase

명령:

 Time Branch "develop" Branch "features/foo" Branch "features/bar" ------- -------------------------------- ------------------------------- ------------------------------- 15:10 git merge --no-ff features/bar 15:09 git commit -m "Sixth commit" 15:08 git rebase develop 15:07 git merge --no-ff features/foo 15:06 git commit -m "Fifth commit" 15:05 git commit -m "Fourth commit" 15:04 git commit -m "Third commit" 15:03 git commit -m "Second commit" 15:02 git checkout -b features/bar 15:01 git checkout -b features/foo 15:00 git commit -m "First commit"

결과:

 * b0f6752 - YYYY-MM-DD 15:10:00 (XX minutes ago) (HEAD -> develop) |\ Merge branch 'features/bar' - Christophe | * 621ad5b - YYYY-MM-DD 15:09:00 (XX minutes ago) (features/bar) | | Sixth commit - Christophe | * 9cb1a16 - YYYY-MM-DD 15:06:00 (XX minutes ago) | | Fifth commit - Christophe | * b8ddd19 - YYYY-MM-DD 15:05:00 (XX minutes ago) |/ Fourth commit - Christophe * 856433e - YYYY-MM-DD 15:07:00 (XX minutes ago) |\ Merge branch 'features/foo' - Christophe | * 694ac81 - YYYY-MM-DD 15:04:00 (XX minutes ago) (features/foo) | | Third commit - Christophe | * 5fd94d3 - YYYY-MM-DD 15:03:00 (XX minutes ago) |/ Second commit - Christophe * d01d589 - YYYY-MM-DD 15:00:00 (XX minutes ago) First commit - Christophe

참고 사항

git cherry-pick

특정 커밋이 하나만 필요할 때 git cherry-pick 이 좋은 솔루션입니다( -x 옵션은 " (cherry pick from commit...) "라는 줄을 원래 커밋 메시지 본문에 추가하므로 일반적으로 좋은 생각입니다. 그것을 사용하려면 - 그것을 git log <commit_sha1> ):

명령:

 Time Branch "develop" Branch "features/foo" Branch "features/bar" ------- -------------------------------- ------------------------------- ----------------------------------------- 15:10 git merge --no-ff features/bar 15:09 git merge --no-ff features/foo 15:08 git commit -m "Sixth commit" 15:07 git cherry-pick -x <second_commit_sha1> 15:06 git commit -m "Fifth commit" 15:05 git commit -m "Fourth commit" 15:04 git commit -m "Third commit" 15:03 git commit -m "Second commit" 15:02 git checkout -b features/bar 15:01 git checkout -b features/foo 15:00 git commit -m "First commit"

결과:

 * 50839cd - YYYY-MM-DD 15:10:00 (XX minutes ago) (HEAD -> develop) |\ Merge branch 'features/bar' - Christophe | * 0cda99f - YYYY-MM-DD 15:08:00 (XX minutes ago) (features/bar) | | Sixth commit - Christophe | * f7d6c47 - YYYY-MM-DD 15:03:00 (XX minutes ago) | | Second commit - Christophe | * dd7d05a - YYYY-MM-DD 15:06:00 (XX minutes ago) | | Fifth commit - Christophe | * d0d759b - YYYY-MM-DD 15:05:00 (XX minutes ago) | | Fourth commit - Christophe * | 1a397c5 - YYYY-MM-DD 15:09:00 (XX minutes ago) |\ \ Merge branch 'features/foo' - Christophe | |/ |/| | * 0600a72 - YYYY-MM-DD 15:04:00 (XX minutes ago) (features/foo) | | Third commit - Christophe | * f4c127a - YYYY-MM-DD 15:03:00 (XX minutes ago) |/ Second commit - Christophe * 0cf894c - YYYY-MM-DD 15:00:00 (XX minutes ago) First commit - Christophe

git pull --rebase

나는보다 확실히 내가 더 잘 설명 할 수 아니에요 데릭 Gourlay은 ... 기본적으로 사용 git pull --rebase 대신 git pull 기사 불구하고 누락 무슨 :) 있다는 것입니다 당신이 기본적으로 사용하도록 설정할 수 있습니다 :

 git config --global pull.rebase true

git rerere

여기에서 다시 잘 설명합니다. 그러나 간단히 말해서 활성화하면 더 이상 동일한 충돌을 여러 번 해결할 필요가 없습니다.


sp00m

이 문장은 다음을 얻습니다.

일반적으로 두 세계를 최대한 활용하는 방법은 스토리를 정리하기 위해 푸시하기 전에 수행했지만 아직 공유하지 않은 로컬 변경 사항을 리베이스하는 것입니다. 그러나 어딘가에 푸시한 것은 절대로 리베이스하지 마십시오. .

출처: 3.6 Git 분기 - 리베이스, 리베이스 대 병합


Joaquin Sargiotto

Pro Git 책에는 rebase 페이지 에 대한 정말 좋은 설명이 있습니다.

기본적으로 병합은 두 개의 커밋을 가져와 결합합니다.

리베이스는 둘의 공통 조상으로 이동하고 변경 사항을 서로의 위에 점진적으로 적용합니다. 이것은 '더 깨끗하고' 선형적인 역사를 만듭니다.

그러나 리베이스할 때 이전 커밋을 버리고 새 커밋을 만듭니다. 따라서 공개된 리포지토리를 리베이스해서는 안 됩니다. 저장소에서 일하는 다른 사람들은 당신을 싫어할 것입니다.

그 이유만으로도 나는 거의 독점적으로 병합합니다. 99%의 경우 내 지점은 크게 다르지 않으므로 충돌이 있는 경우 한 두 곳에서만 발생합니다.


xero

  • 일반적으로 병합 사용
  • 개발자가 한 명인 경우 rebase 를 사용하여 명확한 기록을 가질 수 있습니다.
  • 공유 프로젝트에서 리베이스 캐시 합계를 사용할 때 변경됨

yoAlex5

Git rebase는 히스토리의 분기 경로를 더 깔끔하게 만들고 저장소 구조를 선형으로 만드는 데 사용됩니다.

또한 변경 사항을 리베이스하고 서버에 푸시한 후 분기를 삭제하면 작업한 분기의 증거가 없기 때문에 사용자가 만든 분기를 비공개로 유지하는 데 사용됩니다. 따라서 귀하의 지점은 이제 귀하의 지역 관심사입니다.

rebase를 수행한 후 정상적인 병합을 수행하는지 확인하는 데 사용했던 추가 커밋도 제거합니다.

그리고 예, rebase 명령은 rebase 중에 언급한 분기(예: master) 위에 작업을 넣고 master 분기의 직계 자손으로 분기의 첫 번째 커밋을 만들기 때문에 성공적인 rebase 후에 병합을 수행해야 합니다. . 이것은 우리가 이제 이 브랜치에서 마스터 브랜치로 변경 사항을 가져오기 위해 빨리 감기 병합을 수행할 수 있음을 의미합니다.


cvibha

Gerrit 가 검토 및 전달 통합에 사용되는 대규모 개발과 다소 관련이 있는 몇 가지 실제 예:

기능 분기를 새로운 원격 마스터로 올릴 때 병합합니다. 이것은 최소한의 향상 작업을 제공하고 예를 들어 gitk 에서 기능 개발의 이력을 쉽게 따라갈 수 있습니다.

 git fetch git checkout origin/my_feature git merge origin/master git commit git push origin HEAD:refs/for/my_feature

나는 배달 커밋을 준비할 때 병합합니다.

 git fetch git checkout origin/master git merge --squash origin/my_feature git commit git push origin HEAD:refs/for/master

어떤 이유로든 배달 커밋이 통합에 실패하면 리베이스하고 새로운 원격 마스터로 업데이트해야 합니다.

 git fetch git fetch <gerrit link> git checkout FETCH_HEAD git rebase origin/master git push origin HEAD:refs/for/master

Martin G

rebase와 merge가 무엇인지 여러 번 설명했지만 언제 무엇을 사용해야합니까?

언제 rebase를 사용해야 합니까?

Rebase는 변경 사항을 "제거"하고 rebase된 분기의 모든 변경 사항을 현재 분기에 넣은 다음 변경 사항을 맨 위에 놓습니다. 따라서 지점의 기록이 변경됩니다.

  • 당신이 분기를 푸시하지 않았을 때 / 아무도 그것에 대해 작업하지 않을 때
  • 소스 브랜치로 다시 병합할 때 모든 변경 사항을 한 지점에서 함께 보기를 원합니다.
  • 자동 생성된 "병합된 .." 커밋 메시지를 피하고 싶습니다.

나는 "모든 변경 사항을 한 곳에서 보고 싶다"고 말했는데, 때로는 병합 작업이 모든 변경 사항을 하나의 커밋에 함께 넣기 때문입니다(일부: ... 메시지에서 병합됨). Rebase는 변경 사항을 다른 사람이 중간에 수행하지 않고 커밋을 차례로 수행한 것처럼 보이게 합니다. 이렇게 하면 기능에 대해 변경한 내용을 더 쉽게 볼 수 있습니다.

그러나 git merge feature-branch --ff-only 를 사용하여 기능을 다시 개발/마스터에 병합할 때 단일 커밋을 생성하는 충돌이 없는지 확인하십시오.

병합은 언제 사용해야 합니까?

  • 당신이 브랜치를 밀었을 때 / 다른 사람들도 그 브랜치를 작업하고 있을 때 (다른 사람들이 그 브랜치에서 작업한다면 리베이스는 매우 복잡해집니다!)
  • 전체 기록(*)이 필요하지 않습니다. / 기능에 커밋이 모두 한 곳에 있을 필요는 없습니다.

(*) 먼저 개발 분기를 기능에 병합한 다음 기능을 다시 develeop에 병합하여 기능이 "병합된 .." 커밋을 하나만 가져오는 것을 방지할 수 있습니다. 이것은 여전히 "병합된 .." 커밋을 제공하지만 최소한 기능의 모든 커밋은 여전히 볼 수 있습니다.


Jeremy Benks

git rebase 는 언제 사용합니까? 역사를 다시 쓰기 때문에 거의 없습니다. git merge 는 프로젝트에서 실제로 일어난 일을 존중하기 때문에 거의 항상 선호되는 선택입니다.


Marnen Laibow-Koser

출처 : http:www.stackoverflow.com/questions/804115/when-do-you-use-git-rebase-instead-of-git-merge

반응형