git branch、fork、fetch、merge、rebase 和 clone 有啥区别?

Posted

技术标签:

【中文标题】git branch、fork、fetch、merge、rebase 和 clone 有啥区别?【英文标题】:What are the differences between git branch, fork, fetch, merge, rebase and clone?git branch、fork、fetch、merge、rebase 和 clone 有什么区别? 【发布时间】:2011-03-20 18:51:57 【问题描述】:

我想了解 Git 中分支、分叉和克隆之间的区别?

同样,当我执行 git fetch 而不是 git pull 时,这意味着什么?

另外,与merge 相比,rebase 是什么意思?

我怎样才能将个人的承诺压缩在一起?

它们是如何使用的,为什么使用它们以及它们代表什么?

GitHub 是如何发挥作用的?

【问题讨论】:

你能把接受的答案改成迈克尔杜兰特的答案吗? 他当然可以,但这必须是他的选择,坦率地说,大多数到达这里的人(如我)都想要更简洁的东西,就像他的答案一样选的,这个时候是你自己的=) 【参考方案1】:

Git

这个答案包括 GitHub,因为许多人也曾问过这个问题。

本地存储库

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

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

Git 根本不“锁定”文件,因此避免了编辑时的“独占锁定”功能(想到像 pvcs 这样的旧系统),因此即使在离线时也可以随时编辑所有文件。实际上,它在将文件更改(在同一个文件中!)合并到一个远程存储库(如 GitHub)期间完成了一项了不起的工作。唯一需要手动更改(实际上是编辑文件)的情况是两个更改涉及相同的代码行。


分支机构

分支允许您保留主要代码(“主”分支)、制作副本(新分支),然后在该新分支中工作。如果工作需要一段时间,或者 master 在创建分支后得到了很多更新,那么应该针对 master 分支进行合并或变基(通常是更好的历史记录和更容易解决冲突的首选)。完成后,将分支中所做的更改合并回主存储库。许多组织对每项工作都使用分支,无论是功能、错误还是杂项。其他组织仅将分支用于版本升级等重大更改。

Fork:通过分支,您可以控制和管理分支,而通过 fork,其他人可以控制接受代码返回。

一般来说,有两种主要的方法来做分支。第一个是将大多数更改保留在主分支上,仅将分支用于更大且运行时间更长的事情,例如版本更改,您希望有两个分支可用于不同的需求。第二个是您基本上为每个功能请求、错误修复或杂务创建一个分支,然后手动决定何时将这些分支实际合并到主主分支中。虽然这听起来很乏味,但这是一种常见的方法,也是我目前使用和推荐的方法,因为这可以保持主分支更清洁,并且它是我们提升到生产环境的主分支,所以我们只需要完成、测试过的代码,通过变基和合并分支。

将分支“引入”到 master 的标准方法是执行merge。分支也可以“重新定位”以“清理”历史记录。它不会影响当前状态,这样做是为了提供“更清晰”的历史记录。

基本上,这个想法是您从某个点(通常从 master)分支。自从你分支以来,'master' 本身已经从那个分支点向前移动了。如果您在分支中所做的所有更改都针对 master 的当前状态及其所有最新更改,它将是“更干净”(更容易解决问题并且历史将更容易理解)。所以,过程是:保存更改;获取“新”主控,然后重新应用(这是变基部分)再次针对该更改进行更改。请注意,与合并一样,rebase 可能会导致您必须手动解决(即编辑和修复)的冲突。

需要注意的一条准则:仅当分支位于本地且您尚未将其推送到远程时才重新设置基准! 这主要是因为变基可以改变其他人看到的历史记录,其中可能包括他们自己的提交。

跟踪分支

这些分支被命名为origin/branch_name(而不仅仅是branch_name)。当您向远程存储库推送和拉取代码时,这实际上是发生这种情况的机制。例如,当您 git push 一个名为 building_groups 的分支时,您的分支首先转到 origin/building_groups,然后转到远程存储库。同样,如果您执行git fetch building_groups,则检索到的文件将放置在您的origin/building_groups 分支中。然后,您可以选择将此分支合并到您的本地副本中。我们的做法是始终执行git fetch 和手动合并,而不仅仅是git pull(一步完成以上两项)。

获取新分支。

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

合并

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

在执行mastercheckout 时,还建议执行git pull origin master 以将最新版本的远程主机合并到本地主机中。如果远程主机发生变化,即moved forward,您将看到在git pull 期间反映该情况的信息。如果是这种情况(master 已更改),建议您先使用 git checkout your_branch,然后将 rebase 改为 master,这样您的更改实际上会在“新”master 之上“重播”。然后,您将继续使 master 保持最新,如下一段所示。

如果没有冲突,那么 master 将添加新的更改。如果有冲突,这意味着相同的文件在相似的代码行周围有更改,它不能自动合并。在这种情况下,git merge new_branch 将报告有冲突需要解决。您可以通过编辑文件(其中包含两个更改)、选择您想要的更改、从字面上删除您不想要的更改的行然后保存文件来“解决”它们。更改用========<<<<<<<< 等分隔符标记。

一旦您解决了任何冲突,您将再次 git addgit commit 这些更改以继续合并(在此过程中您将获得来自 git 的反馈以指导您)。

当过程不顺利时,您会发现git merge --abort 非常方便重置。

交互式变基和压缩/重新排序/删除提交

如果您已经完成了很多小步骤的工作,例如,您每天都将代码提交为“正在进行的工作”,那么您可能希望将这些小提交“压缩”成几个更大的提交。当您想与同事进行代码审查时,这可能特别有用。您不想重播您采取的所有“步骤”(通过提交),您只想说这是我在一次提交中对这项工作所做的所有更改的最终效果(差异)。

在考虑是否这样做时要评估的关键因素是多个提交是否针对同一个文件或多个文件(在这种情况下最好压缩提交)。这是通过交互式变基工具完成的。此工具可让您压缩提交、删除提交、改写消息等。例如,git rebase -i HEAD~10注意:这是~,而不是-)会显示以下内容: p>

不过要小心并“谨慎地”使用此工具。一次执行一个 squash/delete/reorder,退出并保存该提交,然后重新进入该工具。如果提交不连续,您可以重新排序它们(然后根据需要压缩)。您实际上也可以在此处删除提交,但您确实需要确定在执行此操作时您在做什么!

分叉

在 Git 存储库中有两种主要的协作方法。上面详述的第一个是直接通过人们拉出/推入的分支。这些协作者的 SSH 密钥已注册到远程存储库。这将让他们直接推送到该存储库。缺点是您必须维护用户列表。 另一种方法 - 分叉 - 允许任何人“分叉”存储库,基本上是在他们自己的 Git 存储库帐户中制作本地副本。然后他们可以进行更改,并在完成后发送“拉取请求”(实际上,这更像是来自他们的“推送”和对实际存储库维护者的“拉取”请求)以使代码被接受。

第二种方法使用分叉,需要有人维护存储库的用户列表。


GitHub

GitHub(远程存储库)是一个远程源,如果您拥有(或被添加到)这样的存储库,您通常会将这些已提交的更改推送和拉取到,因此本地和远程实际上是完全不同的。考虑远程存储库的另一种方式是它是一个位于远程服务器上的.git 目录结构。

当您“分叉”时 - 在 GitHub 网络浏览器 GUI 中,您可以单击此按钮 - 您在您的 GitHub 帐户中创建代码的副本(“克隆”)。第一次这样做可能有点微妙,所以请务必查看代码库在哪个存储库下列出 - 原始所有者或“派生自”以及您,例如,像这样:

获得本地副本后,您可以根据需要进行更改(通过拉取并推送到本地计算机)。完成后,您向原始存储库所有者/管理员提交一个“拉取请求”(听起来很花哨,但实际上您只需点击此:)然后他们“拉取”它。

对于一起处理代码的团队来说,更常见的是“克隆”存储库(单击存储库主屏幕上的“复制”图标)。然后,在本地输入git clone 并粘贴。这将在本地设置您,您还可以推送和拉到(共享)GitHub 位置。

克隆

如 GitHub 部分所述,克隆是存储库的副本。当您拥有远程存储库时,您可以针对其 URL 发出 git clone 命令,然后您将获得该存储库的本地副本或克隆。这个克隆有一切、文件、master 分支、其他分支、所有现有的提交、整个 shebang。您对这个克隆进行添加和提交,然后远程存储库本身就是您将这些提交推送到的内容。正是这种本地/远程概念使 Git(以及与其类似的系统,例如 Mercurial)成为 DVCS(分布式 版本控制系统),而不是更传统的 CVS(代码版本控制系统),例如 SVN, PVCS、CVS 等,您可以直接提交到远程存储库。

可视化

核心概念的可视化可见http://marklodato.github.com/visual-git-guide/index-en.html和http://ndpsoftware.com/git-cheatsheet.html#loc=index

如果您想要直观地显示更改的工作方式,您无法使用可视化工具 gitg(对于 macOS 为 gitx)使用我称之为“地铁地图”的 GUI(尤其是伦敦地铁) ),非常适合展示谁做了什么、事情如何变化、分歧和合并等。

您还可以使用它来添加、提交和管理您的更改!

虽然 gitg/gitx 相当少,但 GUI 工具的数量仍在不断增加。许多 Mac 用户使用 Brotherbard 的 gitx 分支,对于 Linux,一个不错的选择是 smart-git,它具有直观而强大的界面:

请注意,即使使用 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]

图形化学习工具

您可能会发现https://learngitbranching.js.org/ 有助于学习一些基本概念。截图: 视频:https://youtu.be/23JqqcLPss0

最后,7 个关键的救星!

    您进行更改,添加并提交它们(但不要推送)然后哦!你意识到你是大师!

     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 !
    

    您在本地分支中工作时弄乱了一些文件,只是想回到上次执行git pull 时的内容:

     git reset --hard origin/master  # You will need to be comfortable doing this!
    

    您开始在本地进行更改,您编辑了六个文件,然后,哦,废话,您仍然在主(或另一个)分支中:

     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
    

    您弄乱了当前分支中的一个特定文件,并希望基本上将该文件“重置”(丢失更改)到您上次从远程存储库中提取它时的状态:

     git checkout your/directories/filename
    

    这实际上重置了文件(就像许多 Git 命令一样,它在这里所做的事情并没有很好地命名)。

    您在本地进行了一些更改,您想确保在执行 git resetrebase 时不会丢失它们:我经常手动复制整个项目 (cp -r ../my_project ~/)不确定我是否会在 Git 中搞砸或丢失重要的更改。

    你正在变基,但事情变得一团糟:

     git rebase --abort # To abandon interactive rebase and merge issues
    

    将您的 Git 分支添加到您的 PS1 提示符(参见 https://unix.stackexchange.com/a/127800/10043),例如

    分支是selenium_rspec_conversion

【讨论】:

2/20/12 添加了有关合并与变基的信息 2012 年 6 月 16 日添加了关于克隆的部分以使其更加完整。 这么多文字!!我会坚持我的简单 Subversion :-) 嗯?一个颠覆用户也可以写一本关于使用颠覆的书。我认为颠覆是一种功能较少的旧技术。我个人觉得 git 非常好用。 ymmv 哇,迈克尔! SO 就是分享知识。感谢您的出色工作,绝对 +1【参考方案2】:

克隆只是存储库的副本。从表面上看,它的结果相当于svn checkout,您可以从其他存储库下载源代码。像 Subversion 这样的集中式 VCS 和像 Git 这样的 DVCS 之间的区别在于,在 Git 中,当您克隆时,您实际上是在复制整个源存储库,包括所有历史记录和分支。现在,您的机器上有一个新的存储库,您所做的任何提交都会进入该存储库。在您将这些提交推送到另一个存储库(或原始存储库)或有人从您的存储库中提取提交(如果该存储库可公开访问)之前,没有人会看到任何更改。

分支是存储库中的东西。从概念上讲,它代表了一条发展的线索。您通常有一个 master 分支,但您也可能有一个分支正在处理某些功能 xyz,以及另一个用于修复错误 abc 的分支。当您签出一个分支时,您所做的任何提交都将保留在该分支上,并且不会与其他分支共享,直到您将它们与相关分支合并或重新定位到相关分支上。当然,在您查看分支如何实现的底层模型之前,Git 在分支方面似乎有点奇怪。与其自己解释(我已经说得太多了,我想),我将链接到 Git 如何对分支和提交建模的“计算机科学”解释,取自 Git 网站:

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

分叉并不是一个 Git 概念,它更像是一个政治/社会概念。也就是说,如果有些人对项目的进展方式不满意,他们可以获取源代码并自己独立于原始开发人员进行处理。那将被视为叉子。 Git 使分叉变得容易,因为每个人都已经拥有自己的源代码“主”副本,因此它就像与原始项目开发人员切断联系一样简单,并且不需要像使用 SVN 那样从共享存储库中导出历史记录.

编辑:由于我不知道 GitHub 等网站使用的“fork”的现代定义,请查看我的 cmets 和下面的 Michael Durrant's answer 以获取更多信息。

【讨论】:

分叉并不一定意味着开发人员对主仓库不满意。通常,这意味着另一个开发人员已读取但未写入该存储库的访问权限。开发人员可以分叉存储库,进行更改,但由于他无法写入主存储库,因此他必须将更改作为补丁提交。因此,分叉也是一种在不授予写入权限的情况下鼓励协作的方法。 我想这是真的。我只见过在创建一个新的、潜在的竞争版本项目的上下文中使用“fork”。 你可以说fork是一个不希望被上游合并的分支 Git hub 使用“fork”作为 fork 的意思。这是一个存储在 github 上的新存储库,与原始存储库分开。然而,github 也使得实现拉取请求变得非常简单。拉取请求本质上是要求原始存储库的所有者将更改从您的 repo 分支“拉回”回源。这样,每个人都可以使用源代码控制并拥有所有更改的历史记录,包括他们的更改,但并不是每个人都需要对原始存储库的写入权限。 我已经更新了我的答案,告诉人们查看 Michael Durrant 的答案以了解有关 github 模型的更多信息。【参考方案3】:

这是 Oliver Steele 对这一切如何组合在一起的形象:

【讨论】:

这张图片可以更新以添加我相信大多数人无论如何都熟悉的“git clone”。 @Gravitas,我真的很喜欢这张图片,但它并没有告诉我文件何时被覆盖以及何时被合并。你能告诉我这些命令哪个是哪个吗?也许顶部的覆盖命令和驱动器下方的合并命令?谢谢。 据我了解, git pull 会从远程下拉任何你要求的(所以,无论你要求什么主干),并在你制作时立即将它合并到你所在的分支中要求。 Pull 是一个高级请求,它运行“fetch”,然后默认运行“merge”,或者使用“-rebase”进行 rebase。你可以不用它,这只是一种方便。 git clone 在这个图中究竟会去哪里?还有 git 合并?我对 git 很陌生,但我喜欢这张照片。 我看看能不能更新一下图表。【参考方案4】:

分叉与。克隆 - 两个都表示复制的词

请看这个diagram.(原文来自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')

分叉

将其链接到 Joe's 的远程存储库(云)的副本 然后您可以克隆到本地存储库和 F*%$-up 的副本 完成后,您可以推送回遥控器 然后您可以通过单击 pull-request 来询问 Joe 他是否想在他的项目中使用它

克隆

本地存储库(硬盘)的副本

【讨论】:

请注意,真正的 DVCS 优势是您不需要 对 Joe 的 repo 的任何特定访问权限来执行此操作。如果 Joe 希望您更频繁地贡献,他可以授予您推送访问权限:您可以将 anidea 推送到他的 repo 并为您节省保持 fork 最新的琐事。 OTOH,如果您无法与 Joe 达成协议,您可以继续开发和使用您的 fork(然后看看您是否可以让他改变主意)。【参考方案5】:

只是为了给其他人添加一个特定于分叉的注释。

很高兴认识到,从技术上讲,克隆 repo 和 fork repo 是一回事。做:

git clone $some_other_repo

你可以轻拍自己的后背——你刚刚 fork 了其他一些 repo。

Git,作为一个 VCS,实际上就是 cloning 分叉。除了使用远程用户界面(如 cgit)“只是浏览”之外,与 git repo 几乎没有什么关系,它不涉及 forking 在某些时候克隆 repo。

然而,

当有人说I forked repo X时,他们的意思是他们已经创建了 将 repo 克隆到其他地方,意图将其公开 其他,例如展示一些实验,或应用不同的 访问控制机制(例如,允许没有 Github 访问权限但 与公司内部帐户进行协作)。

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

它很可能包含不同的分支集, 标签或提交很可能是他们第一次这样做的原因 地点。

(当你点击“fork”时,Github 所做的只是克隆并添加 糖:它为您克隆回购,将其放在您的帐户下,记录 从某处“分叉”,添加名为“上游”的远程,以及大多数 重要的是,播放漂亮的动画。)

当有人说我克隆了repo X时,他们的意思是他们已经创建了 有意在他们的笔记本电脑或台式机上本地克隆 repo 研究它,玩它,为它做贡献,或者从源代码构建一些东西 代码在里面。

Git 的美妙之处在于它使这一切完美地结合在一起:所有这些 repos 共享 block 提交链的共同部分,因此可以安全地(见下面的注释)来回合并更改在你认为合适的所有这些回购之间。


注意:“安全”,只要您不重写链的公共部分,并且只要更改不冲突。

【讨论】:

以上是关于git branch、fork、fetch、merge、rebase 和 clone 有啥区别?的主要内容,如果未能解决你的问题,请参考以下文章

这些词在 Git 中是啥意思:Repository、fork、branch、clone、track?

git fetch 取回所有分支(branch)的更新(转)

GIT:fork和clone的区别,fetch与pull的区别

Git 协作:Fetch Pull Push Branch Remote相关

如何同步master的代码到fork分支代码

Git branch 和fetch 指令总结:分支查看和远程分支同步