git branch、fork、fetch、merge、rebase和clone之间有什么区别?

518

我想了解Git中分支(branch)、复刻(fork)和克隆(clone)之间的区别?

类似地,当我执行git fetchgit pull时,它们有什么不同意义?

此外,在比较rebasemerge时,rebase是什么意思?

如何将单独的提交压缩在一起?

它们如何被使用、为什么使用它们以及它们代表什么?

GitHub如何发挥作用?


21
你能将采纳答案更改为Michael Durrant的回答吗? - siride
11
他当然可以,但这必须是他的选择,而且坦率地说,大多数来到这里的人(就像我一样)都希望得到更简洁的回答,就像他选择的那个答案一样,而这个答案恰好是你提供的 =) - Nike
5个回答

549

Git

本答案包括GitHub,因为很多人也问了这个。

本地仓库

Git(本地)有一个目录(.git),您将文件提交到该目录中,这是您的“本地存储库”。这不同于像SVN这样的系统,您可以立即添加和提交到远程存储库。

Git通过保存整个文件来存储每个更改版本的文件。在这方面它与SVN不同,因为你可以去到任何单独的版本而无需通过增量更改“重新创建”它。

Git根本不会“锁定”文件,因此避免了编辑的“独占锁定”功能(考虑旧系统如pvcs),因此所有文件始终可以进行编辑,即使离线也是如此。它实际上在拉取或从GitHub等远程存储库获取/推送时,在同一文件中合并文件更改(!)。唯一需要手动更改(实际上是编辑文件)的时间是如果两个更改涉及相同的代码行。


分支

分支允许您保存主代码('master' 分支),复制一份(新的分支)并在此新分支中进行工作。如果工作需要一段时间或自从创建分支后 'master' 分支有了很多更新,则应该进行合并或变基(通常更喜欢变基,因为可以获得更好的历史记录和更容易解决冲突)。完成后,您将合并在分支中所做的更改回到主存储库。许多组织针对每个工作单元(无论是一个功能、错误还是日常任务项)都使用分支。其他组织仅在进行重大更改(例如版本升级)时使用分支。

派生:使用分支时,您控制和管理分支,而使用派生时,其他人负责接受代码返回。

总的来说,有两种主要的分支方法。第一种是将大部分更改保留在主分支上,仅在需要不同版本的情况下使用分支,例如版本更改,这样您就可以拥有两个可用于不同需求的分支。第二种方法是基本上为每个功能请求、错误修复或琐事创建一个分支,然后手动决定何时将这些分支合并到主主分支中。虽然听起来很繁琐,但这是一种常见的方法,也是我目前使用和推荐的方法,因为这使主分支保持清洁,我们将其推广到生产,因此我们只希望通过重新基础和合并分支来获得已完成、经过测试的代码。
将分支“合并”到主分支的标准方法是进行合并。分支还可以“重新基础”以“清理”历史记录。它不会影响当前状态,而是为了给出“更清晰”的历史记录。
基本上,这个想法是你从某个点(通常是从主分支)开始创建分支。自从你创建了分支以来,“主分支”已经从那个分支点向前移动了。如果你在分支中所做的所有更改都与最新的“主分支”状态相匹配,那么它将会更加“干净”(更容易解决问题,历史记录也更容易理解)。因此,这个过程是:保存更改;获取“新”的主分支,然后再次应用(这就是rebase部分)更改与其相对应。请注意,rebase和merge一样,可能会导致冲突,您必须手动解决(即编辑和修复)。
一个需要注意的指南: 只有在分支是本地的并且你还没有将其推送到远程时才进行rebase! 这主要是因为rebase可能会改变其他人看到的历史记录,其中可能包括他们自己的提交。
跟踪分支

这些分支的名称为origin/branch_name(与仅使用branch_name相对)。当您将代码推送和拉取到远程存储库时,实际上就是通过此机制进行的。例如,当您git push一个名为building_groups的分支时,您的分支首先进入origin/building_groups,然后才会进入远程存储库。同样,如果您执行git fetch building_groups,则检索到的文件将放置在您的origin/building_groups分支中。然后,您可以选择将此分支合并到本地副本中。我们的惯例始终是进行git fetch和手动合并,而不是只进行git pull(它可以一次性完成上述两个步骤)。

获取新分支。

获取新分支:在克隆的初始点,您将拥有所有分支。然而,如果其他开发者添加了分支并将其推送到远程,则需要一种方法来“知道”这些分支及其名称,以便能够将它们本地拉下来。这是通过使用跟踪分支(例如origin/)执行 git fetch 来完成的,该命令将把所有新的和更改的分支获取到本地存储库中。一旦进行了fetch,就可以使用 git branch --remote 列出跟踪分支,并使用 git checkout [branch] 实际切换到任何一个分支。

合并

合并是将来自不同分支或同一分支不同版本的代码更改组合在一起的过程(例如当本地分支和远程分支不同步时)。如果一个人在分支中开发了工作,并且工作已经完成、准备好并经过测试,那么它可以合并到master分支中。这可以通过git checkout master切换到master分支,然后使用git merge your_branch完成。合并将把所有不同的文件和甚至是同一文件的不同更改合并在一起。这意味着它实际上会更改文件内部的代码以合并所有更改。

当执行 checkout 命令切换到 master 分支时,建议同时执行 git pull origin master 命令,以获取远程主分支的最新版本并合并到本地主分支中。如果远程主分支发生变化,即 向前推进,你会在执行 git pull 时看到反映这种变化的信息。如果是这种情况(主分支已更改),建议先执行 git checkout your_branch 命令,然后将其与主分支 rebase,以便你的更改实际上被 '重放' 在 '新' 主分支之上。然后,你可以按照下一段所示的方式继续更新主分支。

如果没有冲突,那么主分支将添加新的更改。如果有冲突,则意味着相同的文件在类似的代码行周围进行了更改,它无法自动合并。在这种情况下,git merge new_branch 将报告需要解决的冲突。您可以通过编辑文件(其中包含两个更改)来“解决”它们,选择您想要的更改,删除您不想要的更改行,然后保存文件。更改使用分隔符标记,例如========<<<<<<<<
解决任何冲突后,您将再次使用 git addgit commit 进行更改以继续合并(在此过程中,您将得到来自 git 的反馈以指导您)。
当过程不顺利时,您会发现 git merge --abort 非常方便,可重置事物。
交互式变基和压缩/重新排序/删除提交。
如果您已经按照许多小步骤完成了工作,例如每天提交“正在进行中”的代码,那么您可能希望将这些许多小提交压缩成几个较大的提交。当您想要与同事进行代码审查时,这特别有用。您不想重播您采取的所有“步骤”(通过提交),而是想说这是我对此工作所做出的所有更改的最终效果(差异)。
在考虑是否这样做时需要评估的关键因素是多个提交是否针对同一个文件或多个文件(在这种情况下最好压缩提交)。这可以使用交互式变基工具完成。此工具可让您压缩提交、删除提交、重新编写消息等。例如,“git rebase -i HEAD~10”(注意:这是一个“~”,而不是“-”)会显示以下内容:

interactive rebasing in Git

请小心使用这个工具,一次只做一个挤压/删除/重新排序操作,退出并保存该提交,然后重新进入工具。如果提交不连续,您可以重新排序它们(然后根据需要进行挤压)。您也可以在此处删除提交,但当您执行此操作时,确实需要确定自己在做什么!
分叉
Git存储库中的协作有两种主要方法。第一种是通过人们从/到分支直接拉取和推送。这些协作者已将其SSH密钥注册到远程存储库中。这将使他们能够直接推送到该存储库。缺点是您必须维护用户列表。
另一种方法-分叉-允许任何人“分叉”存储库,基本上在其自己的Git存储库帐户中制作本地副本。然后,他们可以进行更改,并在完成后发送“拉取请求”(实际上对于实际存储库维护者来说,这更像是从他们那里“推送”的“拉取”请求)以获取接受代码。
使用分叉的第二种方法不需要某人维护存储库的用户列表。

GitHub

GitHub(一个远程仓库)是一个远程源,通常情况下,您会将提交的更改推送到该仓库中,如果您有这样的仓库(或被添加到其中),那么本地和远程实际上是非常不同的。另一种思考远程仓库的方式是它是一个位于远程服务器上的 .git 目录结构。

当你“fork” - 在 GitHub 网页版 GUI 中,你可以点击这个按钮 Image of fork button - 你就在 你的 GitHub 账户中创建了代码的副本(“克隆”)。第一次做这件事可能有点微妙,所以请确保您查看代码库是由原始所有者还是“派生自”的列表 - 如下所示:

Image of name of forked repository

一旦你拥有本地副本,你可以随心所欲地进行更改(通过将它们拉取到本地机器并推送)。当你完成后,然后向原始仓库所有者/管理员提交一个“拉取请求”(听起来很花哨,但实际上你只需点击这个:Image of pull request button),他们会将其“拉入”。
更常见的是团队共同开发代码时“克隆”存储库(在存储库的主屏幕上点击“复制”图标)。然后,在本地键入git clone并粘贴。这将在本地设置好,你也可以推送和拉取到(共享的)GitHub 位置。
克隆
如GitHub部分所述,克隆是仓库的副本。当您拥有远程仓库时,您可以针对其URL发出git clone命令,然后您就会得到一个本地副本或克隆版本的仓库。这个克隆版本包含所有内容,包括文件、主分支、其他分支、所有现有提交和整个项目。您可以在这个克隆版本上进行添加和提交,然后将这些提交推送到远程仓库。正是这种本地/远程的概念使得Git(以及类似它的系统,如Mercurial)成为DVCS(Distributed Version Control System),而不是更传统的CVSs(Code Versioning Systems),如SVN、PVCS、CVS等,在这些系统中,您直接向远程仓库提交。

可视化

核心概念的可视化展示可以在以下链接查看:
http://marklodato.github.com/visual-git-guide/index-en.html
http://ndpsoftware.com/git-cheatsheet.html#loc=index

如果您想要一个可视化的展示来了解变化是如何工作的,那么您最好使用可视化工具gitg(macOS上的gitx),它有一个图形界面,我称之为“地铁地图”(尤其是伦敦地铁),非常适合展示谁做了什么,事物如何改变、分歧和合并等等。
您也可以使用它来添加、提交和管理您的更改!

Image of gitg/gitx interface

虽然 gitg/gitx 相对简洁,但是 GUI 工具的数量不断增加。许多 Mac 用户使用 brotherbard 的 gitx 分支,对于 Linux 用户来说,一个很好的选择是 smart-git,它有直观而强大的界面:

Image of smart-git GUI

请注意,即使使用GUI工具,您可能仍需要在命令行中执行许多命令。
为此,我在我的~/.bash_aliases文件中设置了以下别名(每个终端会话从我的~/.bashrc文件调用该文件):
# git
alias g='git status'
alias gcob='git checkout -b '
alias gcom='git checkout master'
alias gd='git diff'
alias gf='git fetch'
alias gfrm='git fetch; git reset --hard origin/master'
alias gg='git grep '
alias gits='alias | grep "^alias g.*git.*$"'
alias gl='git log'
alias gl1='git log --oneline'
alias glf='git log --name-status'
alias glp='git log -p'
alias gpull='git pull '
alias gpush='git push '

我在~/.gitconfig文件中设置了以下“git别名”- 为什么要这样做?
以便分支自动补全(使用TAB键)能够正常工作!

所以这些是:

[alias]
  co = checkout
  cob = checkout -b

示例用法:git co [branch] <- 分支的选项卡完成将起作用。

GUI学习工具

您可能会发现https://learngitbranching.js.org/在学习一些基本概念方面非常有用。屏幕截图:enter image description here
视频:https://youtu.be/23JqqcLPss0

最后,7个关键的救星!

  1. You make changes, add and commit them (but don't push) and then oh! you realize you are in master!

     git reset [filename(s)]
     git checkout -b [name_for_a_new_branch]
     git add [file(s)]
     git commit -m "A useful message"
    
     Voila!  You've moved that 'master' commit to its own branch !
    
  2. You mess up some files while working in a local branch and simply want to go back to what you had the last time you did a git pull:

     git reset --hard origin/master  # You will need to be comfortable doing this!
    
  3. You start making changes locally, you edit half a dozen files and then, oh crap, you're still in the master (or another) branch:

     git checkout -b new_branch_name  # just create a new branch
     git add .                      # add the changes files
     git commit -m"your message"    # and commit them
    
  4. You mess up one particular file in your current branch and want to basically 'reset' that file (lose changes) to how it was the the last time you pulled it from the remote repository:

     git checkout your/directories/filename
    

    This actually resets the file (like many Git commands it is not well named for what it is doing here).

  5. You make some changes locally, you want to make sure you don't lose them while you do a git reset or rebase: I often make a manual copy of the entire project (cp -r ../my_project ~/) when I am not sure if I might mess up in Git or lose important changes.

  6. You are rebasing but things gets messed up:

     git rebase --abort # To abandon interactive rebase and merge issues
    
  7. Add your Git branch to your PS1 prompt (see https://unix.stackexchange.com/a/127800/10043), e.g.

    Image of prompt

    The branch is selenium_rspec_conversion.


2
6/16/12 增加了克隆部分,使其更加完整。 - Michael Durrant
4
太多文字了!! 我还是坚持使用简单的Subversion :-) - Jonny
7
什么?一个Subversion用户也可以编写有关使用Subversion的书籍。我认为Subversion是一种功能较少的较老技术。就我个人而言,我发现Git非常容易使用。可能因人而异。 - Michael Durrant
1
为什么不使用 "git stash" 而不是 "cp -r"? - Harvey
4
哇,迈克尔!SO 就是关于分享知识的。感谢您出色的工作,绝对点赞(+1)。 - Michiel
显示剩余15条评论

372

克隆就是仓库的一个副本。在表面上,它的结果等同于svn checkout,您从其他仓库下载源代码。中心化版本控制系统(如Subversion)和分布式版本控制系统(如Git)之间的区别在于,在Git中,当您进行克隆时,实际上是在复制整个源代码仓库,包括所有历史和分支。现在,在您的计算机上有一个新的仓库,任何您所做的提交都将存储到该仓库中。除非您将这些提交推送到另一个仓库(或原始仓库),否则没有人会看到任何更改,或者直到其他人从您的仓库中拉取提交(如果它是公开可访问的)。

分支是仓库内的一部分。在概念上,它表示一条开发线程。通常您有一个主分支,但您也可以有一个分支,用于开发某些特性xyz,以及另一个分支,用于修复错误abc。当您检出分支后,您所做的任何提交都将留在该分支上,并且不与其他分支共享,直到您将它们合并或重新基于相关的分支。当然,当涉及到分支时,Git似乎有些奇怪,直到您查看分支实现的基本模型。相比之下,我已经说得太多了,我会链接到Git网站上有关Git如何模拟分支和提交的“计算机科学”解释:

http://eagain.net/articles/git-for-computer-scientists/

一个 fork 实际上并不是 Git 的概念,而是一个政治/社会思想。也就是说,如果有人对项目的进展不满意,他们可以获取源代码并在与原始开发者分离的情况下进行工作。这被认为是一个 fork。Git 使得 fork 变得容易,因为每个人都已经拥有自己的源代码“主”副本,所以只需要与原始项目开发者断开联系,而不需要像 SVN 那样从共享仓库中导出历史记录。编辑:由于我不知道现代“fork”的定义,例如 GitHub 等网站的使用,请查看评论以及我的回答下面的 Michael Durrant 提供的更多信息。

128
Fork并不一定表示开发者对主要代码库不满意,通常是另一个开发者已经有了该代码库的读取权限,但没有写入权限。开发者可以Fork该代码库,进行修改,但由于无法向主要代码库写入更改,因此必须将更改提交为补丁程序。因此,Fork也是一种鼓励合作而不授予写入权限的方式。 - brycemcd
6
我想这是真的。我只在创建一个新的、潜在竞争的项目版本的上下文中看到过"fork"这个词被使用。 - siride
33
可以说,分支是一种不被期望合并到上游的分叉。 - masonk
8
Github使用"fork"的意思就是分叉。这是一个存储在Github上与原始库分开的新存储库。然而,Github也很简单地实现了pull requests功能。Pull requests基本上是请求原始存储库的所有者从您的库的分支中“pull”更改回原始库。这样,每个人都可以使用源代码控制并拥有所有更改的历史记录,包括他们自己的,但不是每个人都需要对原始库具有写入访问权限。 - mklauber
5
我已经更新了我的回答,告诉人们查看Michael Durrant的回答以了解有关GitHub模型的更多信息。 - siride
显示剩余9条评论

146

以下是奥利弗·斯蒂尔(Oliver Steele)提供的图示,展示了所有相关内容的相互关系:

enter image description here


7
这个图片可以更新一下,加上“git clone”的步骤,我相信大多数人都会知道。 - Contango
3
@Gravitas,我真的很喜欢这张图表,但它没有告诉我文件何时被覆盖和何时被合并。你能告诉我这些命令哪个是覆盖命令,哪个是合并命令吗?也许可以将覆盖命令放在上面,将合并命令放在下面的驱动器中?谢谢。 - zylstra
据我所知,git pull会从远程拉取您请求的内容(因此,无论您要求什么样的主干),并立即将其合并到您在发出请求时所在的分支中。默认情况下,Pull是一个高级请求,它运行“fetch”,然后运行“merge”,或者使用“--rebase”进行变基。您可以不使用它,这只是一种方便。 - Contango
在这个图中,git clone应该放在哪里?还有git merge呢?我对git非常新手,但我喜欢这张图片。 - Mishelle
3
我会看看能否做出更新版本的图表。 - Contango

12

Fork vs. Clone - 两个词都意味着复制

请查看此图表。(原始来源:http://www.dataschool.io/content/images/2014/Mar/github1.png)。

.-------------------------.     1. Fork     .-------------------------.
| Your GitHub repo        | <-------------- | Joe's GitHub repo       |
| github.com/you/coolgame |                 | github.com/joe/coolgame |
| ----------------------- | 7. Pull Request | ----------------------- |
| master -> c224ff7       | --------------> | master -> c224ff7 (c)   |
| anidea -> 884faa1 (a)   |                 | anidea -> 884faa1 (b)   |
'-------------------------'                 '-------------------------'
    |                 ^
    | 2. Clone        |
    |                 |
    |                 |
    |                 |
    |                 |
    |                 | 6. Push (anidea => origin/anidea)
    v                 |
.-------------------------.
| Your computer           |  3. Create branch 'anidea'
| $HOME/coolgame          |
| ----------------------- |  4. Update a file
| master -> c224ff7       |
| anidea -> 884faa1       |  5. Commit (to 'anidea')
'-------------------------'

(a) - after you have pushed it
(b) - after Joe has accepted it
(c) - eventually Joe might merge 'anidea' (make 'master -> 884faa1')

Fork

  • 复制到你的远程仓库(云端),并将其链接到Joe的仓库
  • 你可以将它复制到本地仓库并更改
  • 完成后,你可以推送回远程仓库
  • 然后,通过点击pull-request,你可以询问Joe是否想在他的项目中使用它

Clone

  • 复制到你的本地仓库(硬盘)

1
请注意,真正的分布式版本控制系统的优势在于您不需要任何特定的访问权限来访问Joe的存储库。如果Joe希望您更经常地做出贡献,他可以授予您推送访问权限:然后您可以将“anidea”直接推送到他的存储库中,省去了保持您的分支最新的麻烦。另一方面,如果您无法与Joe达成协议,您可以继续开发和使用您的分支(并查看是否可以稍后说服他改变主意)。 - Alois Mahdal

6

补充其他人的意见,有关fork的说明。

需要明白的是,从技术上讲,复制存储库和派生存储库是相同的。请执行以下操作:

git clone $some_other_repo

你可以自豪地为自己鼓掌——你刚刚fork了其他的repo。

Git作为版本控制系统,实际上就是关于forking的。除了使用远程UI(如cgit)“只是浏览”之外,几乎没有什么与git repo无关的事情不涉及在某个时间点上forking克隆repo。

然而,

  • 当有人说“我fork了repo X”,他们的意思是他们已经在别处创建了一个repo的克隆,目的是将其公开给其他人,例如展示一些实验,或应用不同的访问控制机制(例如允许没有Github访问权限但具有公司内部帐户的人进行协作)。

    这个repo很可能是使用与git clone不同的其他命令创建的,它很可能托管在服务器上而不是某个人的笔记本电脑上,并且格式略有不同(它是一个“bare repo”,即没有工作树),这些都只是技术细节。

    它很可能包含不同的分支、标签或提交记录,这很可能是他们首先做这件事的原因。

    (当你点击“fork”时,Github所做的只是添加了一些糖:它为你克隆了repo,将其放在你的帐户下,记录了“forked from”的位置,添加了一个名为“upstream”的远程,并且最重要的是播放了漂亮的动画。)

  • 当有人说“我clone了repo X”,他们的意思是他们已经在本地计算机上创建了一个repo的克隆,目的是研究它、玩耍、为它做出贡献或从源代码中构建一些东西。

Git的美妙之处在于它可以完美地将这些内容结合在一起:所有这些repo共享提交链的公共部分,因此可以根据需要安全(请参见下面的注释)在所有这些repo之间来回合并更改。


注:只要不重写链的公共部分,并且更改没有冲突,“安全”就是保证的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接