【问题标题】:Which items from the local .git directory get copied to/from the remote upon pushing/fetching?本地 .git 目录中的哪些项目在推送/获取时被复制到远程/从远程复制?
【发布时间】:2023-03-15 11:29:01
【问题描述】:
  1. 当您push 到远程存储库时,本地.git 目录中的哪些项目会被复制到远程.git 目录?

  2. 反方向的同一个问题,当你执行fetch时。

【问题讨论】:

    标签: git git-push git-fetch


    【解决方案1】:

    其中很多是实现细节——Git 如何存储信息,而不是什么信息Git 存储。因此它是无关紧要的:您对其他 Git 存储库的了解只是 它将向您显示它拥有的内容,而不是 它放在哪里

    尽管如此,在典型设置中,位于某个远程的是另一个 Git 存储库,就像您的一样。事实上,从他们的角度来看,你是遥控器。 他们是真实的,你是克隆。从你的角度来看,他们是复制品,而你是真实的东西。从外部观察者的角度来看,您的克隆和它们的克隆只是两个独立的 Git 存储库。

    当您运行 git push 时,您的 Git 会调用他们的 Git,并且您的 Git 和他们的 Git 会进行对话。这种对话有一个最终目标,如果我们从那里开始并向后工作,那就更有意义了。最终目标是您希望 他们的 Git 设置一个 他们的 名称(其存储库中的分支或标签名称)以记住一个特定的提交。因此,您将向他们发送一个礼貌的请求或强制命令,格式如下:将您的 refs/heads/master 设置为提交哈希 ID a123456...

    (这个名字可以是任何东西,包括他们已经拥有的名字,或者他们还没有的名字。接受/拒绝规则取决于名字的拼写以及他们是否有,但是直到接收 Git。通常接收者不喜欢不知名的名称,例如,refs/heads/*refs/tags/*refs/notes/*。GitHub 将拒绝任何设置 refs/pull/* 名称的尝试,例如.)

    他们可能会拒绝——即使你发送的是命令而不是礼貌的请求——但如果他们不这样做,他们必须在他们的存储库中提交a123456...。因此,作为这次对话的一部分,您的 Git 将向他们提供该提交。他们要么说我已经有了这个,要么说是的,我想要那个。如果提交a123456... 依赖任何更早的提交,通过将它们作为其父提交,你的 Git 现在也必须向他们的 Git 提供这些提交。如果这些提交依赖于之前的提交,你的 Git 必须继续为他们提供更多的提交,直到你提供的提交是他们已经拥有的提交,或者你已经提供了你拥有的每一个提交(以先到者为准)。

    要给另一个 Git 一个提交,你还必须给它提交的每个 file in,除非 - 再次 - 他们已经拥有该文件的那个版本。所以其中一些也进入了对话,尽管其中很多是暗示的:如果他们没有提交 X 但确实有提交 W 那么他们肯定拥有所有W 中的文件,其中许多可能与 X 中的文件完全相同,因此即使我们发送提交 X。

    所以这是对话的最早部分,在连接你的两个 Git 之后:你通过哈希 ID 向他们提供一些提交和文件。他们选择他们想要的和已经拥有的,然后你的 Git 打包这些对象。

    那些对象——提交、树、文件和注释标签的内部 Git 存储形式——可能存在于您的 .git/objects/ 目录中,或者可能打包到一个打包文件中 在您的 .git/objects/pack/ 子目录中。但是您通常不会向他们提供文件。 (具体细节取决于传输协议。)通常,您的 Git 会制作一个 new 包,它是一种称为 thin 包的特殊“精简”类型。你的 Git 会向他们的 Git 发送精简包。他们的 Git 把它变成了一个普通的包。如果他们最终接受了你所有的提交和文件,他们可能只是把这个包放到他们的.git/objects/pack/ 子目录中,尽管你永远无法确定,因为在未来与他们的对话中,你的 Git 和他们的 Git 会只讨论 对象,通过它们的哈希 ID。

    (他们也可能完全拆除增肥包并为自己构建新的包,这些包比仅仅推入增肥版本的瘦包更有效。再说一次,你永远不会真正知道,除非你可以登录其他机器并在其.git 目录中四处寻找。)

    最后,一旦他们接受了他们将要接受的任何内容,并遵守了他们设置refs/heads/<em>whatever</em> 的请求或命令,他们将更新他们的该引用的副本,但是他们将其存储为:作为@ 中的文件可能是 987654336@,或者作为某个参考数据库中的数据库条目,或者可能完全是其他东西。

    因此,简短的回答是,通常 .git 文件会直接复制。相反,信息被重新打包成合适的“有线格式”,以这种方式传递,然后重新打包成合适的“存储格式”。

    git fetch 也一样,除了在对话开始时,你的 Git 有他们的 Git 列出他们的名字和哈希 ID;他们的 Git 通过哈希 ID 提供你的对象,而你的说想要/拥有这些 ID;最后,他们永远不会向您发送任何请求或命令。相反,在获得他们的对象后,您的 Git 会更新您的 远程跟踪名称,这就是您的 Git 如何记住他们的 Git 所说的 refs/heads/master 是什么:这成为您的 refs/remotes/origin/master,您对他们的记忆refs/heads/master.

    【讨论】:

    • 谢谢!为什么我需要在我的推送命令中提及 ref(如您的示例:set your refs/heads/master to commit hash ID a123456)?为什么我不能直接说:add commit hash ID a123456 to your repository
    • Git finds 通过 refname 提交。该名称会找到一个提交,这使得该提交是可访问的。该提交有其前身,这使得这些提交也可以访问,并且这些提交有他们的父母,现在可以访问等等,但是这个过程从任何 Git 拥有的一组 refs 开始。因此,如果他们添加了a123456,但没有其他名称或提交发现 a123456,他们的清理过程会发现nobody 调用保留a123456,并且会删除它。 (此清理是git gc。另见think-like-a-git.net
    • 我想我没有说清楚。请允许我改述一下。假设远程有一个名为b 的分支,并且假设该分支上的最后一次提交具有哈希 ID 1。现在我已将此存储库克隆到我的本地计算机,并添加了一个哈希 ID 为 2 的提交,其父级是其哈希的提交ID 是 1。现在我想将新的提交推送到远程。为什么我必须在我的命令中指定set your refs/heads/b to commit hash ID 2?为什么我不能简单地说:add commit hash ID 2 to your repository?新的提交可以通过 ref b 在远程访问,不是吗?
    • 啊:答案是。像 refs/heads/b 这样的分支名称只包含一个哈希 ID。该哈希 ID 定义为分支中的最后一次提交。因此,对于包含新提交的分支,必须用新的哈希 ID 覆盖引用。这使得新的提交可以访问,保持旧的提交可以访问,并将新的提交添加到他们的“分支 B”概念中,一气呵成。
    • 并且不能 git 自己弄清楚如果我添加提交 2,其父提交 1,后者存在于远程并由分支 b 指向,以更新分支b 指向2?
    【解决方案2】:

    fetchpush 操作期间基本上共享两件事:对象和引用。请注意,接收仓库可能会将给定的信息存储在与发送服务器存储该信息的位置不同的文件中。

    对象包括提交、“树”对象(表示内容目录)和“blob”对象(表示内容的单个文件)等。这些共同构成了您项目的历史。它们以“松散”形式(每个对象一个文件,在其名称源自对象 ID 的目录/文件中 - 这是对象数据的 SHA 哈希)或“打包”形式(在.git/objects/packs 下的文件)。repos 之间的传输使用打包形式,如果它认为合适,则由接收 repo 重新组织其包。

    Refs 是分支、标签和其他东西;它们提供了历史的“入口点”。它们以“松散”形式存储在 .git/refs 下或以打包形式存储在打包参考文件中。不仅本地松散的参考最终可能在接收端打包,而且根据用于共享 ref 的 refspec,远程可能会更新一个完全不同的 ref(例如,在获取时您通常会更新跟踪 ref 以匹配远程的分支)

    【讨论】:

    • 谢谢!如果我创建了一个名为 X 的本地分支,并且远程上有一个名为 X 的不同分支,并且该分支被获取,会发生什么情况?
    • @EvanAad - 好吧,会发生什么取决于您的参考规范。默认情况下,当您获取本地分支时保持不变,并创建一个远程跟踪 ref(例如,您有分支 x,您从远程命名的 origin 获取,它可能会给您 refs/remotes/origin/x)。然后你决定什么是有意义的。这真的是一个单独的分支吗?我应该将它们合并在一起吗?还有什么...?
    • 当我推送时,我的分支名称会传递到远程吗?
    • @EvanAad - 这同样取决于您的配置,也可能取决于您使用的确切命令。通常,如果您推送分支x,您将尝试在远程更新分支x,但如果更新会导致任何提交从历史记录中删除,这将不起作用(不采取额外措施使其工作) x
    【解决方案3】:

    当我们从上游创建(使用 git init)或克隆一个 git repo 时,git create .git 目录包含许多关于我们本地 repo 和远程 repo 的信息,这有助于我们对代码进行版本控制。如果我们删除.git目录,那么我们将得到fatal: Not a git repository (or any of the parent directories): .git

    我认为我们在远程仓库中没有 .git 目录,我认为 .git 目录仅在我们的本地仓库中找到,用于跟踪分支、提交、用户配置、添加远程仓库......我们远程仓库中只有.gitignore 文件,如果我们添加它以忽略文件...

    如果您查看.git 目录,您可以看到以下文件和文件夹...

    ├── HEAD
    ├── branches
    ├── config
    ├── description
    ├── hooks
    │ ├── pre-commit.sample
    │ ├── pre-push.sample
    │ └── ...
    ├── info
    │ └── exclude
    ├── objects
    │ ├── info
    │ └── pack
    └── refs
     ├── heads
     └── tags
    

    【讨论】:

    • 那么遥控器在哪里保存分支信息,例如?
    • @EvanAad,发现 git 在他们这边使用 DGit 来管理存储库。这个链接可能对一些人有帮助。githubengineering.com/introducing-dgit
    猜你喜欢
    • 2020-03-11
    • 1970-01-01
    • 2019-05-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-11-07
    相关资源
    最近更新 更多