社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  torek  »  全部回复
回复总数  53
6 年前
回复了 torek 创建的主题 » 向远程git服务器添加损坏或删除的提交

没那么多 文件夹 丢失或损坏的是内部对象(commit 02d8c9217333d89afd61da1788fa82329b692610 在服务器上)引用另一个内部对象( c168e82dd62c0cdbf3ea7c3be3a84218a12c8a03 )那东西不见了。当一些对象存储在单独的文件中时,其他对象则是 拥挤的: 一个文件中有数千个对象。

如果你有东西 c168e82dd62c0cdbf3ea7c3be3a84218a12c8a03型 您可以从自己的存储库中提取它,发送它,然后将它插入到另一个存储库中。然而,这通常比重新克隆 好的 存储库(前提是它拥有所有的东西不是每个克隆都是完整的,例如,它可能已经过时,或者它可能是 --single-branch 克隆)。

要提取一个对象,请首先查找其类型:

git cat-file -t c168e82dd62c0cdbf3ea7c3be3a84218a12c8a03

然后提取原始数据:

git cat-file -p c168e82dd62c0cdbf3ea7c3be3a84218a12c8a03 > /tmp/obj.data

复制对象数据并将其插入存储库:

scp /tmp/obj.data serverhost:/tmp/obj.data
ssh serverhost
cd ...
git hash-object -w -t $type /tmp/obj.data

在哪里? $type 类型是否来自 git cat-file -t 在存储库的好拷贝的机器上。

(添加丢失的对象可能会修复所有问题,或者只会暴露更多问题,这也是通常最好用另一个克隆完全替换坏的存储库的另一个原因。)

6 年前
回复了 torek 创建的主题 » 我该如何理解这个git回扣——举个例子?

由于时间不够,我在这里只给出一个简短的回答:

下面的命令是否也给出相同的结果

git rebase --onto master maint feature

?

对于这个特殊的图形 是的。对于(某些)其他图形,不(当然,对于其他图形,它们可能建议使用其他命令)。

这本书为什么用 maint^ 而不是 maint ?

不是作者,我只能猜测。这种特殊形式的 git rebase 匹配中的第一个语法 the SYNOPSIS section of the git rebase documentation :

 git rebase [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>]
         [<upstream> [<branch>]]

在这里,他们设置 newbase = master , upstream = 维特 branch = feature . 所以这将从 git checkout feature ,然后将枚举 maint^..HEAD 寻找复制的候选人。如果您查看您的图表,您将看到这正是提交 P Q :开始于 Q ,返回 ,从那里走到 Y Y 维特 .

使用 git rebase --onto master maint feature ,git将从 Q ,返回 ,返回 Y ,仍然停止,因为 维恩特 识别 Z 然后从 Z 把我们带到 Y 。但这需要一个额外的步骤 Z . 据推测,作者不愿意强迫读者在读者的头脑中多走一步。

6 年前
回复了 torek 创建的主题 » 如何正确地进行git合并

[分支] master branch2 ]只有一行的注释不同:

师父有意见://test 00

branch2有注释://test 99

这是。。。不是没用的,也不是完全用的 充满 信息。当你对什么感兴趣 git merge 好的,每个分支的提示内容是 答案的钥匙。更确切地说,它们是必要的,但严重不足。

首先,记住提交包含文件。我想当你说 主人 有X和 鳃2 你真的是说: 提示中的所有文件提交由 主人 鳃2 分别是相同的,除了一些文件 f ,其中有一些行 L 其文本不同:该行显示 //test 00 在里面 主人 //test 99 在里面 鳃2 .

也就是说,如果我们要绘制提交图,它可能如下所示:

          o--o--X   <-- master
         /
...--o--*
         \
          o--Y   <-- branch2 (HEAD)

(还有其他可能的形状,但是 合并分支 表明这很像。名字 HEAD 附加到 鳃2 因为那是你签过的。)

这里我用过 X Y 表示 提示提交 分支机构 主人 鳃2 。因为每个提交都包含它的直接父提交的散列ID,所以我们可以从上一个提交中向后工作。 主人 上一次提交 主人 ,从那里到 它的 上一次提交,依此类推。我们也可以从提交开始。 Y 向后移动。当我们同时从 二者都 提交,我们最终会达到一些连接点:提交 * ,而不是一个回合 o 提交。

第一个这样的连接点对于 合并分支 ,因为它是 合并基础 . 合并基础是理解 合并分支 会的。

合并的目的是合并工作 ,而不是简单地使两个分支相同。为了 结合 工作完成了,吉特必须找出 工作是什么 . 这项工作包括 自公共起点以来所做的所有更改 . 合并基础是共同的起点。

实际上,git现在将运行两个单独的 git diff 命令。一个人将比较提交 * 提交 X ,看看“他们”在分行做了什么 主人 . 另一个将比较提交 * 提交 Y ,看看 在你的树枝上, 鳃2 . 即:

git diff --find-renames <hash-of-*> <hash-of-Y>   # what did we change, on branch2?
git diff --find-renames <hash-of-*> <hash-of-X>   # what did they change, on master?

吉特 组合 这两组更改并将组合的更改应用于commit的内容 * .

自从你,在 鳃2 很明显,他们做的一切都一样 主人 的确如此, 除了 换行 L 文件的 f ,组合的更改将相同,但此行除外。现在的问题是: 谁改了线?

我们这么说吧 * ,该行显示 /测试00 。然后 换了线,所以Git会 你的 改变,结果就是这样 L 文件的 f 将阅读 /测试99 .

但是我们可以这么说 * ,该行显示 /测试99 。然后 他们 换了线,所以Git会 他们的 改变,结果就是这样 L 文件的 f 将阅读 /测试00 .

最后,有可能在 * ,该行完全读取了其他内容。在这种情况下,你和他们都改变了 相同的 直线 相同的 文件,但有两个不同的东西。在这种情况下, 合并分支 会宣布有冲突,会停下来留下你必须清理的烂摊子。由于这种情况没有发生,显然情况并非如此。

检查该文件将告诉您git保留了哪些更改,而这又将告诉您该行在合并基中的外观。或者您可以自己找到合并基,并在该特定提交中检查该文件的版本。但是根据您所告诉我们的,我们无法预测哪一行进入合并提交。

6 年前
回复了 torek 创建的主题 » 在执行git签出时查找标记名-f

首先,看看升级詹金斯系统是否有效。如果没有,尝试 git tag --points-at HEAD .

看待这个问题有两种方法。

  • 其中一个假设是,您试图“倒退”,即获取您发现处于分离状态的存储库 HEAD 猜猜它是怎么来的。第一个问题是它可能没有通过 git checkout <tag-name> 完全。第二个是即使在这种情况下, git describe --tags --abbrev=0 会给一个人看 git describe 显示可能不是实际用来完成签出的显示。

    原因是git的标记主要是用于从人类可读的名称转换为hash id的机制。可能有 多个名字 对于任何给定的提交哈希ID,如果是, GIT描述 就挑一个用它。在 Git versions 1.7.10 and later , Git标记——指向头部 将列出 全部的 指向当前提交的标记;然后可以尝试猜测其中的哪一个(如果有的话)。

    或者,您可以使用存储库的 ReFug,如果使用了标签,它将包含一个包含标签名称的行。这个 git status 命令,在现代git中,执行此操作,并打印 HEAD detached at tag-name 为了这个案子。这比 GIT描述 输出, 假如 有问题的存储库启用了reflog,并且有一个reflog条目。这两个reflogs都未启用,如果启用,则保证有合适的entry。因此,这种方法的描述和要点更加可靠。

  • 另一种看待这一点的方式是假设你希望“继续前进”:詹金斯自己已经做了 git checkout 操作。在这种情况下,您只需要说服jenkins为您生成它传递给您的标记名 GIT校验 .

    那里 应该 做一个这样的人。真的吗? 应该 做你已经尝试过的,即使用 env.TAG_NAME . 显然,这是, claimed to work now 。也许升级詹金斯可以做到这一点。

    我很高兴在我的声明中得到更正,詹金斯的文件 糟糕透了 通过找到一些完整、全面和准确的文档,包括哪些版本的jenkins支持哪些特性、哪些流水线阶段是并行运行的以及哪些不是并行运行的等等,这是非常不充分的,但我从来没有找到任何。(Git至少是 小的 更好的是可以搜索 release notes 以了解何时添加了特定功能,但在这里,情况可能会好得多。与python文档相比,python文档在某些特性(如 pathlib 是新的。

6 年前
回复了 torek 创建的主题 » 为什么github会显示两次相同的提交?

两次都不一样。这是两种不同的承诺。

犯罪人的身份 它的哈希ID。您列出的两个哈希ID是:

  • 5191cdba6da69bceea29c9c0231f2b17dffda620 .

    此提交有一个父提交, 389975b56cf4e5db190c1ed67fe7dab0363cb62f .

  • e41b0c40501ea8906fcbdcc7d37ff6ef0cd5cf02 .

    此提交有两个父提交。第一个是 8aa78399c3f9650ae9ac7db0f0fc520e11f3be6a ,第二个是 5191CDBA6DA69BCEEA29C9C0231F2B17DFFDA620 即,您询问的第二个提交的第二个父级是您询问的第一个提交。

也就是说, choroba commented ,其中第一个是进行了一些更改的提交,第二个是存储库的作者用于 结合 第一次提交到它们的提交集合中。

Git中的每个提交都有一个哈希ID,每个提交的哈希ID都是唯一的。 这就是为什么这些东西这么长这么丑:git需要 保证 与过去十年中的每一次提交(见脚注1)相比,这些散列ID现在不仅是唯一的,而且对于每一次提交 在下一个 一万 年(同样,见脚注1)。注意,哈希ID是通过对 提交中包含的数据 ,以便它不仅唯一地标识提交,还充当检查以确保没有人破坏内容:甚至更改一位数据 在内部 提交会产生一个全新的、不同的哈希ID。

因为散列ID又大又丑,人们通常不会直接使用它们。我们使用分支名称之类的东西 hash id,但是hash id会进化,所以它总是意味着 分支上的最新提交 或标记名。标记名记录一个散列ID,至少要更改它们记录的散列ID(即,只有在管理标记的人是恶意的或反复无常的情况下才会更改)。

给定包含其父提交或提交的哈希ID的提交,Git可以找到父级。这些父对象还包含 他们的 父母,所以Git可以找到祖父母。那些祖父母包含散列ID,所以Git可以找到更多的祖先。这意味着给定单个提交哈希ID,Git可以找到 那次犯罪的整个祖先 ,返回到时间的开始(好吧,提交链的时间)。因此,如果给git一个提交散列id列表 存储库中的每个分支和标记 ,Git可以找到 全部的 通过遍历这些父链,在存储库中提交。这是存储库的全部内容,除了诸如分支重新登录之类的辅助数据(它们是一个存储库的私有数据,不是跨克隆传输的)。


更准确地说,这种唯一性要求只有在将要混合的存储库中才是正确的。也就是说,一些特定的哈希ID可以用于两个不同的提交,但是 只有当您不想将这两个提交放在一个存储库中时。 预测哪个提交将进入哪个存储库太难了:向hash id中放入更多的位要简单得多。

git目前使用的sha-1散列使用160位,这一点慢慢被证明是不够的, 所以git人员有一个迁移计划,可以将其增加到256位。这会使“每次提交一个惟一的散列ID”的概念稍微混乱一点:相反,会有一个惟一的散列ID,使用旧样式或新样式。如果是新样式,提交将有第二个旧样式hash id,用于兼容性,但仅用于在缺少新样式hash id的紧急情况下(即与不理解新样式id的git交谈时)标识提交。

不足之处并不是因为没有足够的位来唯一地对宇宙中的每个原子进行编号,而是因为通过在问题上抛出足够的计算能力,坏的参与者可以产生蓄意的散列冲突。

6 年前
回复了 torek 创建的主题 » git pull from origin分支的奇怪问题

你把太多的魔法归咎于树枝。-)

git的工作方式非常简单。一 分支名称 只是一个git commit hash id的名称。 git pull 即使存在,但我们很快就会看到它是什么,以及如何使用它。)

关于提交、哈希ID、分支名称和提交链

让我们稍微讨论一下这些提交哈希ID。hash id是一个由字母和数字组成的难看的大字符串,例如 0d0ac3826a3bbb9247e39e12623bbcfdd722f24c . 这唯一地标识了一些git对象——通常是commit,当我们处理分支名称时,它总是,肯定是commit。每个提交都记录其 起源 ,或前置任务提交。这允许git将提交的内容串成一个向后看的链。

这意味着我们可以 这些承诺链。如果让一个大写字母代表一个丑陋的hash id,我们会得到如下结果:

... <-F <-G <-H   <--master

这个 名称 master 保存提交的实际哈希ID H . 让吉特 找到 G 在存储库内浮动的提交中。从 h Git可以得到哈希ID G ,这就是 H 的父母。所以现在Git可以找到 G 。使用 G ,Git可以找到 F ,等等,向后,向下。这里的箭头可以读为 指向: 主人 指向 h , H 指向 G 等等。

每次提交的内容完全、完全、100%冻结/只读。任何承诺都不能改变。所以我们不需要画内部箭头。但是,分支名称 改变。git添加 新的 致力于 主人 是写出一个commit对象,存储 H 新对象中的哈希ID,以及新的提交快照和任何其他 元数据 喜欢你的名字,电子邮件地址和日志信息。这将产生一个新的散列,我们将调用它 I 而不是试图猜测:

...--F--G--H--I

现在git只需要编写 在名字里 主人 ,这样 主人 现在指向 :

...--F--G--H--I   <-- master

如果你有多个分支,或者你有多个 远程跟踪名称 喜欢 origin/master origin/BranchA ,我们只需要把它们都画出来:

...--F--G--H   <-- master, origin/master
            \
             I--J   <-- origin/BranchA

(稍后我们将详细讨论远程跟踪名称。它们有点像树枝的名字,但有点扭曲。)

当您创建一个新的分支名称时,git所要做的就是使新名称指向一些现有的提交。例如,让我们创建自己的 BranchA 现在,使用 git checkout BranchA :

...--F--G--H   <-- master, origin/master
            \
             I--J   <-- BranchA, origin/BranchA

现在让我们创建 testBranch 同时,还指出 J :

...--F--G--H   <-- master, origin/master
            \
             I--J   <-- testBranch, BranchA, origin/BranchA

如果您现在创建一个新的提交,您的git需要知道 要更新哪个分支名称 . 所以你的Git有个特别的名字, HEAD ,所有大写字母都是这样写的。 Git将此名称附加到您的一个分支名称:

...--F--G--H   <-- master, origin/master
            \
             I--J   <-- testBranch (HEAD), BranchA, origin/BranchA

也就是说 测试分支 现在的 分支,因此是 git将更新的名称 当你奔跑 git commit 做出新的承诺。其中之一 git checkout 做的就是管理这个头部附件。


自从你 没有 布兰查 ,你可能会想: 我怎样检查它? 事实上,你 应该 想一想:这是个很好的问题。答案是你的git会 创造 你自己 布兰查 从远程跟踪名称 。所以你必须 git checkout -b testBranch 但不是 git checkout -b BranchA : -b 旗帜说 创造 ,如果没有它,git将只在名称不存在时创建 有一个远程跟踪名称 存在看起来是对的。不止这些,这是个好的开始。

由于怪癖,通常可以使用小写字母。 head 在windows和macos上,但在类unix系统(如linux)上没有。最好避免这种习惯,因为它在linux上不起作用:如果您不喜欢键入 在所有大写字母中,使用 @ ,这是魔法名称的同义词。


远程跟踪名称,或者,当有人在其他Git存储库中进行提交时会发生什么情况?

关于这些分支名称的问题是 具体到 你的 Git存储库 。你的 主人 你的 主人 。你的 布兰查 是你的 布兰查 你的 测试分支 也是你的。他们不会改变除非 改变它们。

事实上,甚至你的远程跟踪名称 原点/主 产地/布兰卡 也是你的,但让它们成为远程跟踪名的原因是你的git会 自动 更改它们,以记住您的git在其他git中看到的内容,只要您的git调用它们的git并询问它们 他们的 分支名称。也就是说,您的git有其他git存储库的url,列在 遥远的 名称 origin : 起源 是一个很长的短名称,可能很难键入url。你可以运行:

git fetch origin

你的git将调用他们的git,在下面列出的url 起源 ,并询问他们的git 他们的 树枝。他们会说: 哦,当然,给你:我的 主人 是<hash1>和我的 布兰查 是<哈希2>。 (要看这个,跑 git ls-remote origin ,就像 git fetch origin 不过,在获得远程名称和散列的列表后,它只是将它们打印出来。)

有了这个列表,你的git会继续向他们的git索要任何 新的 提交 他们 你没有。所以如果他们更新了 他们的 布兰查 ,你得到他们的新承诺。然后,不管发生了什么,您的git现在设置所有 远程跟踪名称 一开始 origin/ 。也就是说,假设他们有两个新的承诺。您自己的存储库现在如下所示:

...--F--G--H   <-- master, origin/master
            \
             I--J   <-- testBranch (HEAD), BranchA
                 \
                  K--L   <-- origin/BranchA

你自己 布兰查 测试分支 没有移动 。这些是 你的 树枝,所以它们只有在 移动它们。你的 原点/主 因为他们的 主人 没有动,但是你的 产地/布兰卡 感动,记住新的承诺 L 你刚从他们那里得到的,因为 他们的 布兰查 确实移动了,现在指向同一个提交 L .

(请记住,我们的大写字母代表实际的大而丑陋的唯一散列ID。如果他们做了新的提交,而您又做了新的提交,git保证 他们的 新的哈希ID与 每一个 你做的新提交哈希!您可以看到,使用一个活动的存储库,单个大写字母会跑得太快,而且很难使其成为唯一的。但它们更容易绘制,也更容易让我们讨论提交,所以我在这里使用它们。)

让你的分支名称移动

现在他们已经更新了 他们的 布兰查 ,你可能想拥有自己的 布兰查 也可以移动。这就是事情开始变得复杂的原因,但是让我们来看一个简单的方法。

我们从跑步开始 Git签出Brancha 再一次。这将附加 布兰查 ,以便使用 当前分支 正在使用 布兰查 。那我们就用 git merge ,在这种情况下,实际上不会进行任何合并!

git checkout BranchA
git merge origin/BranchA

之前 合并分支 ,我们的存储库中有:

...--F--G--H   <-- master, origin/master
            \
             I--J   <-- testBranch, BranchA (HEAD)
                 \
                  K--L   <-- origin/BranchA

这个 合并分支 产地/布兰卡 发现它指向 L . 看看我们现在的分行 附加到“并发现它指向 J 。它意识到,从 L 工作向后,它可以直接 J . 这意味着分支名称 布兰查 可以是“向前滑动”,就像是逆着内部的方向,向后指向的箭头。Git将此操作称为 快进 。在 合并分支 ,更像是 GIT校验 移动当前分支名称。也就是说,承诺 L 成为 当前提交 ,但它是通过 移动名称 布兰查 . 结果是:

...--F--G--H   <-- master, origin/master
            \
             I--J   <-- testBranch
                 \
                  K--L   <-- BranchA (HEAD), origin/BranchA

你现在有承诺了 L 作为你当前的承诺 L 正在填充 指数 以及 工作树 . 是时候谈谈这两个了。

索引和工作树

我们已经提到,提交中存储的文件完全、完全、100%冻结/只读。它们以一种特殊的、压缩的、仅限git的格式存储。这使git可以节省大量空间,并重用未更改的文件:如果新提交的文件与前一次提交的文件基本相同,则无需保存所有文件。旧的提交的副本被冻结,因此新的提交可以 分享 他们。(这个过程的细节在这里并不重要,但是git使用hash id,git调用的是 BLB对象 ,以实现此技巧。)

这对于Git来说是很好的,但是我们不能使用冻结的压缩Git文件来做任何事情。 其他的 。所以Git必须解冻和解压缩被冻结的文件,变成它们的日常形式,这样我们和我们计算机上的其他程序就可以了。 使用 他们。

解冻后的文件进入 工作树 ,这就是所谓的,因为那是我们研究它们的地方。在这里,我们可以做任何我们想用我们的文件。因此,对于每个文件,当前提交中都有一个冻结的副本,而工作树中有一个解冻的副本。(其他提交中也可能有冻结副本,但 现在的 提交是最有趣的,因为我们可以并且经常将它与工作树中的一个进行比较。

这个 指数 ,也被称为 分级区 或者有时 隐藏物 ,是Git特有的。其他版本控制系统也有冻结的提交和解冻的工作树,但也没有 一个索引,或者完全隐藏任何索引,这样你就不需要知道它了。另一方面,git会时不时地用索引打你的脸。你 必须 知道它,即使你不把它用于花哨的把戏。

从本质上说,索引保存的是每个文件的副本。也就是说,每个文件 当前提交 在索引中。索引副本是特殊的git-only格式。不过,与冻结提交副本不同的是,如果你愿意的话,这个副本只是半冻结的。你可以 代替 任何时候都可以用一个新的,不同的,合法的,半冷冻的拷贝。就是这样 git add does:it git确认文件的工作树副本,将其压缩为git-only格式并替换以前的索引副本。(如果新的匹配 任何 旧版本,在任何冻结的git提交中,它最终都会重新使用旧版本:节省空间!否则它就是一个新的git副本。)

制作一个 新的 在git中,commit只需要快速冻结这些索引副本。他们已经准备好了,这也是为什么 GIT提交 比其他版本控制系统快得多。但这也意味着指数可以被描述为 下一个任务是什么 。git从索引而不是从工作树构建新的提交。

你需要工作树来处理你的文件。git需要并使用索引来进行新的提交。索引和工作树副本可以不同,它是 你的 工作到 Git添加 在提交之前,工作树将复制以用更新的索引副本覆盖索引副本。

更新您的 测试分支

现在,让我们看看如何更新 测试分支 . 记住,我们跑了 git fetch 更新我们所有的 origin/* 名字,然后 Git签出Brancha git merge origin/BranchA 更新 布兰查 ,所以我们现在有了这个:

…--f--g--h<--master,源/主
\
i--j<--testbranch公司
\
K--L<--Brancha(头),产地/Brancha

我们现在需要 git checkout testBranch 对它。然后我们可以跑 git merge BranchA Git合并源/分支 :

git checkout testBranch
git merge <anything that identifies commit L>

这里的想法是让git看看commit L 。然后merge命令将查看是否可以执行它所执行的快速转发操作 布兰查 。答案是肯定的:从提交 J 直接承诺 L 。因此,在默认情况下,git会这样做,您将得到:

...--F--G--H   <-- master, origin/master
            \
             I--J
                 \
                  K--L   <-- testBranch, BranchA, origin/BranchA

请注意,我们可以做到这一点,即使我们 永远不要创造我们自己的 布兰查 ,因为不是 Git合并Brancha 我们可以跑 Git合并源/分支 。也就是说,如果我们有:

...--F--G--H   <-- master, origin/master
            \
             I--J   <-- testBranch (HEAD)
                 \
                  K--L   <-- origin/BranchA

并运行 Git合并源/分支 ,git将执行与使用名称的版本完全相同的快速转发。 布兰查 指向提交 L . 这里重要的不是分支名称,而是提交。 好吧,我们自己的分支名称,比如 测试分支 ,重要的是,我们需要让他们指出他们应该去的地方;但是 其他 名称远程跟踪名称我们只使用它们 查找提交 . 他们只是 可读性更强 而我们的git会 自动更新 吉特取出 .

因此,假设我们从未创造 布兰查 首先。假设我们做了:

$ git clone <url>
$ cd <repository>
$ git checkout -b testBranch origin/BranchA
... wait until colleague updates origin/BranchA ...
$ git fetch                      # defaults to using origin
$ git merge origin/BranchA

那我们就完蛋了,不用再摆弄了 我们的 布兰查 我们从未创造过。

如果你自己作出承诺的话,我就不提发生了什么。在这种情况下,可以得到一个真正的合并 合并分支 会看到的 可能只是快进,并将运行合并过程,然后进行类型为的提交 合并提交 . 相反,让我们来解决这个难题的最后一点, GIT拉力 .

关于 GIT拉力 (不要用它!)

我的建议 GIT拉力 作为一个初学者,你应该努力学习 避免 它。但是,其他人和文档会告诉您使用它,所以您至少应该知道它的作用。所有这些 GIT拉力 是和做就是跑 git命令。这是为了方便。问题是,有时它很方便,有时它很明显 -方便。在我看来,最好先学会使用两个底层git命令。

第一个git命令 GIT拉力 跑步就是 吉特取出 . 我们已经看到了:它会调用其他git,从中获取 它的 分支名称(和标记名称)和散列ID,并将您需要的任何提交带入您的存储库,以便您的Git可以更新所有 远程跟踪名称 . 然后就完成了:索引和工作树没有发生任何事情。跑起来很安全 吉特取出 随时 ,因为它只是添加新的提交和更新远程跟踪名称。

这个 第二 命令 GIT拉力 跑步是麻烦的来源。你可以选择 哪一个 它运行的第二个命令。通常,那是 合并分支 ,这就是我们上面看到的。但你可以让它运行 git rebase ,我们在这里没有讨论过。

不管是哪种情况, GIT拉力 将一些额外的参数传递给 合并分支 GIT重碱 命令。这些额外的争论造成了一些不便,因为它们 不同的 从你可能想要使用的参数。尤其是,如果您运行:

git pull origin master

这具有运行的效果:

git fetch origin master
git merge -m "merge branch 'master' of $url" origin/master

注意,最后一个argumentgit中的斜杠将合并现在由 原点/主 . 这个 -m (消息)包含从 起源 ,加上名字 主人 ,而不是名字 原点/主 但是 影响 对于合并,快进或实合并与合并更新的远程跟踪名称相同, 原点/主 .

如果你使用单独的 吉特取出 合并分支 命令,它们更有意义。当你使用 GIT拉力 ,列出的分支名称(如果列出一个分支名称)是 在另一个Git上 ,而不是中的远程跟踪名称 你的 Git。

同样的道理即使你有 GIT拉力 运行 GIT重碱 为你。在生命的最后一刻 -方便,决定是使用merge还是rebase是您有时应该做的决定。 之后 运行 吉特取出 . 也就是说,你应该看看 什么 吉特取出 ,以决定要运行的第二个命令。但是如果你用 GIT拉力 ,你必须做出这个决定 之前 你跑 吉特取出 所以你 不能 看。

一旦你使用了git一段时间,并且对两者都非常熟悉 合并分支 GIT重碱 , 然后 你可以开始使用 GIT拉力 安全地。(但我基本上还是没有。)


还有一个问题,Git的老版本:在Git版本1.8.4之前, GIT拉力 没有 更新远程跟踪名称。现代的git消除了这种奇怪的怪癖,但是有些系统仍然使用非常旧的git版本,所以了解它很重要。

6 年前
回复了 torek 创建的主题 » 从git存储库中排除文件类型的钩子类型

在服务器上,有两个选项,即 预接收 更新 钩子。(对于这种特殊情况,我可能会自己使用更新挂钩。)

预接收钩子调用一次,标准输入连接到包含所有建议的引用更新的管道。您应该读取所有stdin行,并使用新旧散列ID和所有引用的名称来决定 整个的 允许继续推送,或 整个的 一次拒绝所有名称更新。也就是说,假设某个客户端已运行:

git push origin hash1:name1 hash2:name2 ... hashN:nameN

所以有 n 上的更新请求 n stdin的行,你的预接收钩子要么接受全部,要么拒绝全部。若要接受全部,请以零状态退出;若要禁止全部,请以任何非零状态退出。最好打印 原因 对于拒绝,如果您退出非零,那么客户将看到您为什么这样做。

在预接收钩子(如果有)允许整个进程进入第二阶段之后,每次建议的更新都调用一次更新钩子。它有三个 位置的 参数,该参数提供与预接收挂钩的一个输入行上输入的信息相同的信息。您应该检查这两个散列ID和名称,并决定是否 这个特别的更新 是允许的。

也就是说,给定相同的客户端调用,将调用更新挂钩 n 分开的时间。例如,第二个将有:

$1: refs/heads/name2
$2: the old hash ID (or the all-zeros "null hash")
$3: the new hash ID (or all-zeros)

如果你愿意 name2 设置为指向新的散列ID,使更新挂钩以零(成功)状态退出。如果不是,让它以非零状态退出。同样,如果要拒绝更新,最好打印一些内容。

关于服务器端钩子

每个引用,钩子接收一个旧的散列ID( $old 在下面),一个新的( $new ) 全名 参考文献: refs/heads/ name 如果引用是分支名称, refs/tags/ name 如果是标记名, refs/notes/ name 如果它是一个注释引用,等等。更新挂钩具有更细的粒度,但无法将建议的更新作为一个整体查看。

最多一个 旧美元 $新 都是零。如果是这样,则引用将被createde.g.,一个新的分支或标记销毁。否则,它是一个就地更新:引用 目前 指向哈希ID 旧美元 还有跑步的人 git push 建议将其更改为指向哈希ID $新

这些钩子非常有效,但很难写。特别是,如果引用是 更新的 很清楚该做什么:更新将在 $old..$new 范围,因此:

git rev-list $old..$new | while read rev; do
    # examine the files in $rev
done

足以让你检查每个提议的内容 新的 承诺。(有些犯罪可能是 删除 这些可以通过检查找到。 $new..$old )

但是,如果引用是 新创建的 , 旧美元 都是零。仅凭这一特定的参考文献是不可能确定哪些参考文献是新引入的。你 可以 使用以下技巧:

git rev-list $new --not --all

枚举可从建议的新引用(而不是从任何当前引用)访问的提交。不过,这可能会产生误导:可能是请求创建三个新的分支名称:

...--o--o--o   <-- master
            \
             A--B   <-- newbranch1
                 \
                  C   <-- newbranch2
                   \
                    D--E--F   <-- newbranch3

在隔离状态下,设置 newbranch3 指向哈希ID为的提交 F 看起来像是一个添加所有六个提交的请求(它是!)但你可能更愿意 看法 作为一个请求 在其他添加的分支之后提交 newbranch2 ,例如。无法在更新挂钩中生成此视图。它 可能(但很难)在预接收钩子中产生它,因为它可以告诉所有三个 newbranch* 名字是新的。

6 年前
回复了 torek 创建的主题 » 签出以前的提交后,无法看到新的git提交

DR

只要你在一个分支上做了承诺,你只需要使用 git checkout branch 现在。

如果你承诺 分离头 模式下,必须找到它的哈希ID。之后给哈希ID一个人类可读的名称(可能是分支名称)是明智的。你可以使用 git reflog 帮你找到丢失的散列ID。

注意,我在这里给你的问题加了几句话。这种特异性很重要。

…然后我从日志中签出了另一个更早的[commit],即使用 git log 我跑 git checkout a123456... 或类似的)。我找不到要签出的新git commit['s hash id]。我丢失了所有更改,无法返回到最新提交

如您现在所知,git散列id是git用来查找每个提交的唯一名称。也就是说,每个git提交都有一些难看的字母和数字字符串,git使用它们来定位该提交:

git checkout 5d826e972970a784bd7a7bdf587512510097b8c7

例如,查找提交(假设它存在于Git存储库中的Git的一个提交,因此它可能不在您自己的存储库中),并将其内容提取到一个我们可以使用的表单中。

您还知道,每个提交都包含源代码的完整快照。每个提交也有一些 元数据 ,有关提交的数据,如您的姓名(或作出该提交的人的姓名)、电子邮件地址等。您可能不知道的是,每个提交还存储其 起源 (前一个或前一个)提交。例如,我在上面使用的哈希ID为其哈希ID以开头的提交的父级 b5796d9a .

当你奔跑 Git日志 ,git的默认设置是首先显示 现在的 交给你已经结帐的人。然后git使用该提交保存的父id,以便向您显示该提交之前的提交。那个父母有另一个父母,所以在给你看了父母之后,git会转到祖父母那里,依此类推。所以你看不到 最新的 承诺:你从一个旧的承诺开始,然后向后移动。

这是使用git的第一个关键: 吉特倒着工作。 它必须从末尾开始,并朝着开头移动。但它实际上是从你选择的任何时间点开始的,所以要回到“现在”,你需要做一些不同的事情。

注意git会记住哪个commit是 现在的 使用git调用的机制提交 HEAD (用这样的大写字母拼写),尽管你可以使用符号。 @ 如果你愿意的话)。下面,当我们继续提到 ,记住这是Git的记忆方式 你现在的承诺 .

分支和标记名称

正如您刚才看到的,hash id对人类不是很友好。因此,我们通常不太使用它们,至少不直接使用,git也不使用 我们用它们。相反,git提供了将人类可读的名称链接到各种提交的能力。最重要的名字是 分支名称 ,但从说明 标签名 相反,因为分支名称有几个特殊属性。

标签名 只是提交的可读名称。例如,在git的git存储库中,标记名 v2.20.0 标识维护人员指定的“git 2.20”提交。因此,这个名字 V2.20.0 和大丑八怪有同样的功能 5d826e9729... 事情。所以不是:

Git结帐5D826E972970A784BD7A7BDF587512510097B8C7

我能做到:

git checkout v2.20.0

最终结果完全相同:我最终签出了git版本2.20(确切地说,是2.20.0)。

如果我这样做,我会得到git所说的 分离头:

$ git checkout v2.20.0
Note: checking out 'v2.20.0'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b <new-branch-name>

HEAD is now at 5d826e9729 Git 2.20

GIT把所有这些都打印出来的原因是新近Git的人忘记了这会产生一个分离的头部,如果你忘记了,分离的头部可能会有点疼。

退出这个分离的头部模式,我可以 git checkout master 回到 master 分支机构:

$ git checkout master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.

现在一切又恢复理智了。

分支名称是特殊的

分支名称,如标记名称,标识一些特定的提交:

$ git rev-parse master
5d826e972970a784bd7a7bdf587512510097b8c7

这又是同一个散列ID。但是分支名称和任何 其他 命名提交的方式,比如 V2.20.0 5d826e972970a784bd7a7bdf587512510097b8c7 . 尤其是当你使用 git checkout 使用原始散列id或标记名,git将实际的提交散列id直接存储在 . 这就是我们刚才看到的这种分离的头部模式。

一旦你进入这种模式, 只有 git具有的用于查找当前散列id的位置是特殊名称 . 每次你使用 GIT校验 通过其他的提交,它覆盖了git所嵌入的hash id 早期的.而且,既然git工作了 向后的 ,很容易去 向后的 从这里开始很难前进!

但是当你付出 GIT校验 分支名称 喜欢 主人 ,git做了一些完全不同的事情。而不是建立它的 为了记住原始散列id,git将其设置为 记住 分支名称本身 :

$ cat .git/HEAD
ref: refs/heads/master

所以现在你 on branch master 作为 git status 把它放进去。git不会直接记住hash id,而是记住您在这个分支上的事实。同时 分支名称 记住提交哈希。所以 附加到名称,并且该名称具有哈希ID。

如果你现在 新的 commit,git在这一点上做的特别聪明。git像往常一样构建新提交,使用当前提交的hash id作为新提交的父级。然后,一旦新提交完成并为其分配了丑陋的hash id,git就会写入新提交的hash id 进入之内 这个 名称 主人 ,这是 附上。

我们可以把它画成图片,尽管它有助于用单个字母替换大的丑陋的哈希ID。假设我们正在进行哈希ID为的提交 H ,其父项具有哈希ID G ,其家长是 F ,等等。我们说每一个承诺 指向 它的父母:

... <-F <-G <-H

这个 分支名称 主人 持有 H 的哈希ID,所以 主人 它本身指向 H :

... <-F <-G <-H   <--master

最后, 附于 主人 ,以便如果有多个分支名称,git知道要使用哪一个:

...--F--G--H   <-- master (HEAD)
         \
          I--J--K   <-- develop

所以如果我们现在做出新的承诺,它将被分配一个新的哈希IDI将使用下一个字母, L ,这里–它指向 H ,然后git重写名称 主人 所以它指向 L :

...--F--G--H--L   <-- master (HEAD)
         \
          I--J--K   <-- develop

所以这是使用git的第二个关键: 分支名称移动。 分支名称包含 最后的 在分支上提交。制作一个 新的 承诺,当您有某个特定的分支签出时 附加到该分支名称 自动 移动分支名称。

自从你 附加到分支名称,分支名称现在标识 新的 最后提交, Git日志 将在提交时开始 L 向后看,给你看 L 然后 H 然后 G 等等。

这就是为什么分离的头有点痛

假设您有上面的原始序列,但是您使用 GIT校验 使用原始哈希ID选择历史提交 G . 为了使事情适合我们需要推动 H 有点:

          H   <-- master
         /
...--F--G   <-- HEAD
         \
          I--J--K   <-- develop

现在让我们做一个新的承诺 L :

          H   <-- master
         /
...--F--G--L   <-- HEAD
         \
          I--J--K   <-- develop

请注意 只有 新提交的名称 L . 现在让我们用 GIT校验 移动 我们的 或者重新连接。例如,我们把它重新连接到 主人 :

          H   <-- master (HEAD)
         /
...--F--G--L
         \
          I--J--K   <-- develop

这个 Git日志 命令将在提交时启动 H 向后看,给我们看 H 然后 G 然后 f ,依此类推。

如果我们还没有保存(写下来,或者在终端窗口中的scrollback中,或者其他什么)commit的hash id L ,我们将如何再次找到此提交?

这就是重新登录的地方

为了帮助我们摆脱错误,git存储了 以前的值 每一个不同的名字。也就是说,如果 主人 指向 G 在我们做出决定之前 H 然后我们 H , 主人 现在指向 H 但是 用于 指向 G ,因此git保留一个reflog条目,拼写为 master@{1} ,记住这一点 主人 曾经指向 G .

git对这个特殊的名字也有同样的作用 . 每次我们签出一些新的提交,git都会更新 重新漂浮。我们可以看到这种重演。 吉特雷弗洛格 ,它默认显示 :

$ git reflog
5d826e9729 (HEAD -> master, tag: v2.20.0, origin/master, origin/HEAD) HEAD@{0}: checkout: moving from 5d826e972970a784bd7a7bdf587512510097b8c7 to master
5d826e9729 (HEAD -> master, tag: v2.20.0, origin/master, origin/HEAD) HEAD@{1}: checkout: moving from master to v2.20.0

在本例中,前两个reflog条目并不特别有趣,因为这两个条目都是针对 主人 . 但是如果你做出这样的承诺 L 在上图中,现在需要找到它的散列ID, 吉特雷弗洛格 会很有帮助。

为现有提交提供新的分支名称

假设你确实 L 像这样,已经失去了它:

H<—主(头)
/
-----------L
\
发展

然后你使用 吉特雷弗洛格 找到它,你就找到了。你现在可以查出来:

git checkout llllllllllllllllllllllllllllllllllllllll

(当然,使用实际的hash id)。你现在将处于“独立的头脑”模式,现在是时候给你建议 GIT校验 当你第一次进入这个分离的头部模式时,它会自动打印。它说我们可以使用 git checkout -b :

git checkout -b xyz-branch

例如。

这是为了 创造 新的 分支名称,指向当前(即, )提交,然后立即附加 为了那份承诺。所以现在我们有:

          H   <-- master
         /
...--F--G--L   <-- xyz-branch (HEAD)
         \
          I--J--K   <-- develop

现在我们都准备好了,因为我们有一个 可读名称 (“XYZ分行”)代表 最后的 在这个新分支上提交。每次我们通过签出分支、做一些工作并提交来添加新的提交时,git都会自动更新新的分支名称。

6 年前
回复了 torek 创建的主题 » python2和python3之间的zipfile头语言编码位设置不同

编辑: 下面的代码适用于python 2.7,但不适用于3.6(有点神秘,今天晚上早些时候它似乎可以工作):

$ cat zipf.py
from __future__ import print_function

from zipfile import ZipFile, ZipInfo

with ZipFile("out.zip", 'w') as zf:
    content = "content"
    info = ZipInfo()
    info.filename = "file.txt"
    info.flag_bits = 0x800
    # don't set info.file_size here: zf.writestr() does that
    zf.writestr(info, content)

with open('out.zip', 'rb') as stream:
    byteseq = stream.read(8)
    for i in byteseq:
        if isinstance(i, str): i = ord(i)
        print('{:02x}'.format(i), end=' ')
    print()

运行如下:

$ python2.7 zipf.py
50 4b 03 04 14 00 00 08 

但是:

$ python3.6 zipf.py
50 4b 03 04 14 00 00 00 

当然有可能 制作 在创建 info 条目。但是,你必须避免 writestr ,而这只适用于Python3.6(而且似乎有点滥用):

from __future__ import print_function

from zipfile import ZipFile, ZipInfo

with ZipFile("out.zip", 'w') as zf:
    info = ZipInfo()
    info.filename = "file.txt"
    content = "content"
    if not isinstance(content, bytes):
        content = content.encode('utf8')
    info.file_size = len(content)
    with zf.open(info, 'w') as stream:
        info.flag_bits = 0x800
        stream.write(content)

with open('out.zip', 'rb') as stream:
    byteseq = stream.read(8)
    for i in byteseq:
        if isinstance(i, str): i = ord(i)
        print('{:02x}'.format(i), end=' ')
    print()

可能是3.6重置了所有 info.flag_bits (通过内部 open 这是不正确的,尽管我并不清楚。

原始答案如下

我无法重现,但如果文件名为unicode,并且ascii编码失败,则设置标志位中的位11是正确的:

def _encodeFilenameFlags(self):
    if isinstance(self.filename, unicode):
        try:
            return self.filename.encode('ascii'), self.flag_bits
        except UnicodeEncodeError:
            return self.filename.encode('utf-8'), self.flag_bits | 0x800
    else:
        return self.filename, self.flag_bits

(python 2.7 zipfile.py源代码)或:

def _encodeFilenameFlags(self):
    try:
        return self.filename.encode('ascii'), self.flag_bits
    except UnicodeEncodeError:
        return self.filename.encode('utf-8'), self.flag_bits | 0x800

(python 3.6zipfile.py源代码)。

要获得位集,您需要一个不能直接用ascii编码的文件名,例如:

info.filename = u"sch\N{latin small letter o with diaeresis}n" # "file.txt"

(此符号适用于Python2.7和3.6)。

我试图在创建zipinfo对象后通过设置标志来强制启用此位,但在_open_to_write()中它被重置回0x00。

如果我补充说:

info.filename = "file.txt"
info.flag_bits |= 0x0800

(刚将文件名设置为 u"schön" )在python 2.7或3.6下运行,我得到了头文件中的位集(当然zip目录中的文件名变回 file.txt )

6 年前
回复了 torek 创建的主题 » 新github分支的sha/hash

你要说的关键一点——问题的来源——是 分支名称 一点也不重要,在吉特。他们只是 可移动指针 根据定义,它指向 最后的 在分支中提交。多个名称可以指向任何一个提交。

在Git中,是 提交 那很重要。提交是git的 raison d'être . 提交只需创建一个hash id,因为与git的所有四种对象类型一样,hash id是提交内容的加密校验和。因为每个提交都是唯一的,所以它有一个时间戳来提供帮助,以防发生任何事情 其他的 关于提交的内容与前面的内容相同——每个提交都获取一个新的、唯一的散列ID。

然而,提交散列ID看起来是随机的,人类不可能记住或使用它。所以我们需要一些方法来命名我们想要记住的最新提交。一般来说,就是 分支名称 . 一旦我们有一个提交,我们可以指向它的任意数量的分支名称。

每个提交都会记住其父或父哈希ID,因此我们只需要记住最后一个,或者 提示 ,分支机构的承诺–所有早期的承诺都可以从最后开始并向后工作。因此分支名称标识 提示 只有承诺。

当git创建 新的 提交,git只需将新提交的hash id写入 当前分支 . 当前的分支是哪个分支?答案同样简单:特别的名字 HEAD 保存当前分支的名称。

确保任何有用的git提交 可达成的 因为Git最终会 垃圾收集 任何无法实现的提交。也就是说,如果 xyz 标识提交 a123456... ,该提交受到垃圾收集器的保护。因此,它承诺的父母(或父母),祖父母,等等。git给了您一些时间(默认情况下,14天)来连接对象,以便通过这种可达性思想来保护对象(包括提交):首先创建一个对象,比如blob、树或提交,然后更新任何需要的名称,以便能够找到该对象及其任何祖先。在创建对象之后,14天的窗口是完成名称更新的宽限期。

6 年前
回复了 torek 创建的主题 » 如何用git计算换行数?

就git的内部diff算法而言 变化 . 要么在那儿,要么不在那儿。如果它不在,以前也在,它就被删除了。如果它以前不存在,现在也不存在,那么它是被插入的。如果它曾经存在,现在仍然存在,它是不变的。

还有其他算法,以及git的后处理器 diff 输出,它将尝试在已删除/添加的节中查找“相似”行,并将它们显示为行内的更改。但git本身并没有做到这一点。

(原因是为了提高性能,git将每一行视为一个符号。符号本身可能会忽略空白或有一些其他特殊处理。您还可以让git将每个“单词”视为一个符号,以便它一次比较一个单词,并生成一个单词一个单词的diff。所有这些都输入到git内的四个差分引擎之一中。有关更多信息,请参阅《我的未完成》第3章的最后一节 book )

6 年前
回复了 torek 创建的主题 » git在我的repo中显示一堆文件为已删除,但它们不是

“要提交”的更改是存储在 指数 (阿卡 中转区 阿卡 隐藏物 )git进行新的提交 索引里有什么 马上 ,所以对索引的一个很好的描述是 你打算做什么 .

如果您复制了存储库,但没有复制索引(或以某种方式复制了索引,但没有保留所有索引),这将解释问题。索引既不是存储库本身,也不是工作树。相反,指数 之间 存储库和工作树。因此,文件可以在那里,在工作树中,按顺序排列,但在索引中丢失,git将告诉您,如果现在提交,删除将被提交。

你可以告诉git重新填写索引 当前提交,使用 git reset --mixed . 或者,可以使用 git add . 即:

  • Git重置--混合 :从复制(所有文件) HEAD 索引
  • git add file :复制(指定的 file )从工作树到索引

如果您将索引视为介于 提交和工作树,这一切都更有意义:

   HEAD           index         work-tree
----------      ----------      ----------
README.txt      README.txt      README.txt
file.ext        file.ext        file.ext
main.py         main.py         main.py
  • 这个 每个文件的副本都是只读的:任何提交都不能更改,它是已提交的副本。这些文件是压缩的,可能是高度压缩的,并且是一种特殊的、仅限Git的格式。

  • 每个文件的索引副本仅为git,就像 复制,除了它是读/写的:它已经 解冻 . 这个 下一个 提交将通过冻结索引副本来使用该索引副本(一旦冻结所有文件并完成提交,则该索引副本 变成 这个 复制是因为 对新提交的更改)。

  • 每个文件的工作树副本都是一个普通的文件,其格式是普通的。Git没有 使用 它做出承诺。吉特不太在乎 关于 它,真的;git只是为 使用,合作,如果你愿意的话可能会改变。

一旦你 改变 一个文件,你用 Git添加 将其复制回索引,覆盖旧的索引副本。这个 add process压缩并git验证文件,以便可以提交它。

这个 git commit 命令获取索引中的任何内容 马上 并将其冻结为新的提交。新的提交将成为 承诺。

当你第一次跑步时 git checkout branch ,git从tip commit填充索引和工作树 branch . 承诺变成 承诺,和 和索引匹配。当你做一个 新的 提交,新的提交将成为当前分支的尖端,并且 和索引匹配。请注意,工作树仅仅是一个 输出 最初的结帐,不是 输入 新的承诺。(或者更确切地说,其中的一些文件被放入 指数 如果你 Git添加 -编辑了一些或所有文件。)

通常,您可以使用 git rm :

  • git rm file 删除 文件 二者都 索引 工作树;或
  • 移除文件 文件 删除 文件 从索引中删除,但将其单独留在工作树中。

不管怎样,搬走之后,比如说, file.ext 从索引上看,你还保留着它 ,所以您有以下两个结果之一:

   HEAD           index         work-tree
----------      ----------      ----------
README.txt      README.txt      README.txt
file.ext                        file.ext
main.py         main.py         main.py

或:

   HEAD           index         work-tree
----------      ----------      ----------
README.txt      README.txt      README.txt
file.ext
main.py         main.py         main.py

在工作树中查找不会告诉您文件已从 指数 ,它只会告诉您文件是否已从 工作树 . 但是 git status 会告诉你的,因为 Git状态 git diff S:

  • 第一, Git状态 比较 提交到索引。不管有什么不同,git告诉你的是一个“需要承诺的改变”(“我猜是qui seront valides”)。不管是什么 相同的 ,吉特什么也没说。

  • 然后, Git状态 将索引与工作树进行比较。不管有什么不同,git告诉您,这是一个至少尚未承诺的变化。你可以 Git添加 当然,更新后的文件进入索引;之后,第一次比较, vs索引,会显示出一些不同的东西。

  • 一个不在索引中但在工作树中的文件被称为 未追踪 (“非瑞士人”)。即使文件在 承诺。(但是,请注意,您可以通过在 .gitignore )

结论:档案在 以及工作树,但不在索引中

你展示的结果正是这个案例的预期结果。文件在 但不在索引中:它将从 下一个 提交,除非你把它放回索引中。文件不在索引中,而是在工作树中:它是未跟踪的。

6 年前
回复了 torek 创建的主题 » 还原以前暂存的更改(或:撤消对.git/index的更改)

你的描述基本上是正确的。唯一不是百分之百与这部分有关的是:

每次转移更改时 git add blob对象是在 .git/对象/

在内部, Git添加 哈希工作树文件中数据的内容 git hash-object -w -t blob . 这不是 必要地 创建 新的 对象:如果哈希内容是 已经在存储库中 ,它只是重新使用现有对象。现有对象可能是 拥挤的 ,即,在 .git/objects/pack ,而不是 释放 作为一个独立的整体。

此外,写入blob对象的内容 可以 任意地 与工作树中的内容不同,因为 清洁过滤器 . 由于行结束设置的原因,更常见的情况是,CR LF行尾与工作树中的内容不同。通过您的 .gitattributes 文件,部分(或大部分)通过配置中的设置。

在任何情况下,重要的是获得blob对象的hash id。blob对象肯定存在于 .git/objects 作为松散对象或在包文件中的目录。现在 Git添加 可以写入 .git/index (或其他任何文件 GIT_INDEX_FILE 指示):它将在临时槽0的索引中存储给定 path ,使用计算的blob散列和模式 100644 100755 取决于以后是否应将工作树文件标记为可执行。

如果你失去了它,你多半是运气不好

[场景被截取,但以 git checkout HEAD -- path 用它的 $path 代表 $blobhash 和模式 $mode 信息, 在中删除文件的工作树副本 路径 )

除非垃圾收集在技术上仍然存在。但我不知道怎样才能把它弄回来,然后手动地寻找散列,并用 git cat-file

实际上,您不能:哈希ID计算是 trapdoor function ,只有当你 有散列可以让git溢出内容,但如果没有散列,就需要有内容。那是你的 Catch-22 situation

如果 这是一个相当重要的“如果”的内容 独一无二,所以 Git添加 真的创造了 新的 blob对象, 您刚刚覆盖了索引中的blob引用,该blob对象确实不再在任何地方被引用。另一方面,如果 git hash-object -w 在重用某些现有blob时,blob对象仍然被以前引用它的任何对象引用。所以现在有两个有趣的例子:blob 唯一的,现在可以进行垃圾收集,或者,blob是 独一无二的。

使用 git fsck --lost-found git fsck --unreachable git fsck --dangling (默认情况下),您可以让git遍历整个对象数据库,确定哪些对象是 可达成的 不是的,告诉你一些或所有无法到达的,和/或从他们那里复制或关于他们的信息到 .git/lost-found . 如果blob对象 遥不可及,它 被列为这些无法到达或悬挂的斑点之一,或将其内容还原为 .git/失物招领 .

这里的缺点是可能有几十个甚至几百个悬空的blob对象。现在你的任务已经从“猜杂烩”(实际上是不可能的)变成了“大海捞针”(不是那么困难,但是很乏味,你很可能会发现 错误的 针线不是大海捞针,毕竟是一堆针)。当然,这只适用于“blob是唯一的”情况。

特定问题的答案

(顺便说一下,这个问题 不是 真的是 Can git undo a checkout of unstaged files . 但那本书还是很有用的,所以你也可以看看。)

有什么像 git reflog 为了索引?

不,你 可以 自己制作备份:只是 cp .git/index 在某个地方。但Git不会自己这么做。你可以在 Git结账头-- 路径 操作,通过用于执行此类危险操作的别名或shell函数。

注意git不知道这些备份副本,所以 git gc 不考虑引用的对象受保护。将备份与管道命令一起使用的步骤 git ls-files ,将路径名放入 git_索引文件 在命令执行期间。

git checkout @ -- 被认为是危险命令的文件,如 git reset --hard 你可能会失去工作的地方?

答案取决于谁在考虑。我建议你自己也考虑一下,因为你问的是这个问题。-)

有手动更改/重写索引的管道命令吗?(见上例,物体仍在那里)

对: git update-index 是一次一项更新程序(使用 --cacheinfo --stdin 提供原始索引项数据,而不是让它重复很多 Git添加 工作)。许多其他命令也会部分或整体更新索引。

如果您有一个在 git checkout HEAD -- ... 操作,可以从备份索引中读取条目(使用 GIT_INDEX_FILE=... git ls-files 例如)然后使用 git更新索引 , 没有 git_索引文件 设置,将信息放入常规索引中。当然,这是一个index-overwrite-y操作,您可能希望首先对索引进行另一次备份。

有没有另一种方法来签出单个文件而不立即暂存它?

不,只是因为动词 结帐 在这里。到 查看内容 在索引中或在任何commitso中的文件,其内容的名称为 git rev-parse 能理解使用 git show :

git show :file          # file in index at stage zero
git show :3:file        # file in index at stage three, during merge conflict
git show HEAD:file      # file in current commit
git show master~7:file  # file in commit 7 first-parent hops back from master

还要注意 git reset 可以覆盖索引中的一个或多个文件,而不接触工作树中的文件:

git reset HEAD -- file  # copy HEAD:file to :file leaving work-tree file undisturbed

如果你给 Git重置 一个目录的路径,它重置所有已经在索引中并且位于目录中的文件。

6 年前
回复了 torek 创建的主题 » Git,Rebase v Interactive Rebase,Git目录结构

你已经发现了 git format-patch | git am 样式REBASE和A git cherry-pick 样式重新平衡。AS jthill noted in a comment 在大多数情况下,对于大多数用户来说,这些并不打算有所不同。它们最初被分成两个不同的后端,拼写为 git-rebase--am git-rebase--interactive . (它们仍然存在,但区别正在减弱。)

两者之间有一个重要的区别,与文件重命名检测有关。因为樱桃采摘钢筋字面上使用 吉特樱桃采摘 使用底层的机器 合并 机器,这将启用重命名检测。因为格式补丁REBASE没有,所以它没有。重命名检测速度很慢,因此如果没有重命名,有时格式修补程序的变化速度会更快。

你可以强迫 git rebase 使用cherry-pick模式而不通过添加 -m -s <strategy> -X <extended-strategy-option> 任何基的参数。

6 年前
回复了 torek 创建的主题 » 如何让git理解mac(cr)行结尾

DR

创建筛选器驱动程序升级版 .gitattributes :创建 污迹过滤器 那是跑步 tr '\n' '\r' 和一个 清洁过滤器 那是跑步 tr '\r' '\n' ,并将有问题的文件标记为使用此筛选器。使用仅限lf的行尾将文件存储在git中。(筛选器驱动程序在 .git/config $HOME/.gitconfig 文件和文件的名称或名称模式进入 .gitattributes属性 )

正如您所看到的,Git非常喜欢以换行结尾的行。(它可以处理换行分隔的行,其中最后一行缺少终止符,但这意味着添加一行会导致对上一个最后一行的更改,因为它现在有一个换行终止符,而新的最后一行缺少换行终止符。)这对e单个快照,但对于产生有用的差异很重要。

像其他人一样,现代的MacOS使用新品。只有古老的向后兼容格式才有CR行结尾。参见,例如, this SuperUser Stack Exchange web site posting .

Git没有 内置 用于转换此类行尾或从行尾转换的筛选器。吉特 但是,有一个 通用机制 用于更改工作树文件。

记住,当Git在快照中存储任何文件时,该文件由Git调用的 BLOB对象 它以一种特殊的、压缩的(有时是高度压缩的)仅Git形式存储在内部。此表单对任何内容都无效 但是 Git,所以当您通过 git checkout 例如,Git将它们扩展为计算机的常规形式。同时,任何时候,当您使用这样的普通文件并将其转换为仅Git格式时,Git都会将该文件压缩为仅Git格式。当你把文件复制回Git的时候就会发生这种情况。 指数 使用 git add .

每个文件的索引副本在工作树就位时就存在,就像提交的副本一样。索引副本采用相同的仅Git格式。这里的关键区别在于提交的副本 不能 被更改,但索引副本 可以 被改变。正在运行 git commit 对索引中的内容进行快照 就在那时 并使其成为新提交的新快照。因此,索引起到了 下一次提交将进行什么 . 使用 Git结账 ,您复制一些现有的提交 进入之内 索引,并让git将其扩展到工作树中;然后使用 Git添加 ,您可以有选择地用已更改的工作树文件的压缩版本替换特定的索引副本。

这种向索引树和工作树或从索引树和工作树复制是进行Windows样式的LF到CRLF转换的理想点,反之亦然,所以这就是Git所做的。如果你有 其他 要执行的转换,而不是直接内置到Git,这是您告诉Git执行的地方。

污迹和清洁过滤器

污迹过滤器 是Git在将文件从压缩索引副本转换为工作树副本时应用的。在这里,如果您选择用CRLF Windows样式的换行符或分隔符替换换行符,那么Git有一个内部转换器可以做到这一点: eol=crlf . 一 清洁过滤器 是Git在将文件从未压缩的工作树副本转换为压缩的索引副本时应用的;这里再次说明, EOL=CRLF 指示Git进行向后转换。

如果只想用CR替换换行符,就必须发明自己的转换器。假设你称整个过程为 convert-cr :

*.csv   filter=convert-cr

(而不是 *.csv eol=crlf )。这条线进入 .gitattributes属性 (这是一个可提交的文件,您应该提交它)。

现在您必须定义 转换CR 过滤。这是一个git配置文件,这里我们发现了一个小缺陷:配置文件不可提交。这是一个安全问题:Git将在这里运行任意命令,如果我可以提交这个文件并克隆它,您将运行这些命令 指定,但没有机会先审查它们。所以你必须把这个放进你的 .git/配置 您自己,或进入您的全局配置( git config --global --edit 例如):

[filter "convert-cr"]
    clean = tr '\r' '\n'
    smudge = tr '\n' '\r'

现在只要Git转换 仅Git格式,它会将换行符转换为CRS,并且每当Git转换时 仅Git格式,它将CRS转换为换行。

这对现有存储文件没有帮助

您今天拥有的任何现有快照 \r 在它们里面,永远以这种方式储存。Git永远不会更改任何现有的存储文件!存储的数据是宝贵的和不可侵犯的。你对此无能为力。嗯,有 几乎 什么都不做:您可以完全放弃这些提交,转而生成新的和改进的提交。但那是非常痛苦的:每一个承诺都会记住 起源 提交,因此如果替换存储库中的早期提交,则必须替换 每一个 孩子、孙子等等,这样他们都能记住这一新的承诺序列。( git filter-branch 做这个工作。)

但是,您可以指导Git如何 微分 现有提交中的特定文件,也使用 .gitattributes属性 差异驱动程序 . 有多种方法可以做到这一点,但最简单的方法是定义 文本转换 属性,它将“二进制”文件(如其存储版本可能只有CR字符的文件)转换为文本(面向行,即基于换行)文件。这里要使用的textconv过滤器与污点过滤器完全相同。

有关详细信息,请参阅 the gitattributes documentation .

6 年前
回复了 torek 创建的主题 » git deploy web hook命令-不太明白。

这将使用git作为部署工具,这有点像使用扳手作为锤子:它可以工作,但有很好的理由不这样做。

正在运行:

git --work-tree=/location/ --git-dir=/source.git/ checkout -f

与跑步非常相似:

git checkout -f

我想你已经习惯了 git checkout 用一个 分支机构名称 签出一个特定的分支。如果你咨询的话 the git checkout documentation ,你会发现你也可以跑步 Git结账 没有 分支名称:

你可以省略 分支 ,在这种情况下,命令退化为“检查当前分支”,这是一个光荣的无操作,具有相当昂贵的副作用,仅显示当前分支的跟踪信息(如果存在)。

也就是说,如果这个特定的存储库当前在分支上 master ,正在运行 Git结账 方法 git checkout master ,已经打开了。现在让我们注意一下 --git-dir --work-tree 期权,也就是说 the front-end git command documentation . 最重要的是 --工作树 选项,因为 --Git目录 参数已由以下事实设置:它作为Git挂钩运行(Git挂钩使用 $GIT_DIR 预设):

--work-tree= path

    设置工作树的路径。…

基本上,这告诉Git: 不要在通常的地方寻找工作树,无论在哪里,都要在另一个地方寻找。 path 位置 . 在“裸”Git存储库(在Web服务器上)中,通常将Git存储库本身设置为 工作树。因此,您必须使用 --工作树 (或任何等效的)强制Git假定此其他目录,例如 /location/ ,包含已签出的存储库。

这个 checkout 然后,verb告诉git接受当前工作树中的任何内容(在另一个位置),并重新签出当前分支,无论当前分支是什么(可能是 主人 )这个 -f 期权是指 Git结账 它代表 ,意思是: 即使结帐会破坏一些正在进行的工作,无论如何也要这样做。 正常情况下 Git结账 小心不要覆盖正在进行的工作。

当然,在您的Web服务器上,不应该有任何正在进行的工作:部署位置中的文件可能 应该 匹配Git认为的内容。所以 -F 应该 通常是不必要的。然而,这整个想法 Git认为在工作树中 本身就是个问题。吉特 跟踪 通过Git称之为 指数 . 如果你把部署的想法推得太紧,使用git本身作为部署工具,你会发现git的跟踪会破坏部署。这是 为什么? 这就像用扳手当锤子:如果你轻轻地用它,或者知道你在做什么并且很小心,它就会起作用,但是如果你做了大量的锤击,扳手可能会折断。

有关pro-git as部署工具文章,它涵盖了这两个参数中的一些,请参见 https://www.freelock.com/blog/john-locke/2015-06/case-git-deployment-tool . 另请参见 Deploy code using GIT - checkout vs reset --hard? git post-receive hook doesn't seem to process other branches 也。

6 年前
回复了 torek 创建的主题 » Git提交未完成的合并

简短的回答是不,这是不可能的。

我一直想写一个剧本,尽可能地接近。在冲突解决过程中, 索引中每个文件的副本,再加上工作树中的第四个文件。(添加不可更改的副本 HEAD 实际上, 每个文件的活动副本,但我们只需要保存其中的四个,因为 头部 无法更改。)可以用与 git stash 是的,这将允许通过常见的Git机制传输“正在进行的隐藏合并”。 这将包括 案例。但是,在 部分 -已解决合并根本无法保留的“reuc”撤消项,以及 全部的 合并无法记录有关高级冲突(例如重命名/重命名冲突)的一些重要信息,这些信息在执行此操作之前应该以某种方式公开。

如果您不需要撤销条目,我没有写的这个脚本可能会起作用,但是,唉,我没有写。:-)不过,基本思想是将当前(合并冲突)索引读取/复制到三个或四个未冲突的临时索引副本中:每个阶段1条目一个,每个阶段2条目一个,每个阶段3条目一个。我们还需要一个用于所有0阶段的条目,尽管这里可能可以使用原始索引。添加最后一个临时索引以保存实际工作树状态 w ,包含包含冲突标记的文件(可能是 u 提交未跟踪的文件以防有人使用 git mergetool ?).make从所有临时索引文件中提交,以某种有用的方式将它们绑定到 头部 以同样的方式 暂存 用它做一个藏匿袋 i W 承诺。那就去吧 git reset --hard ,同样的方法 暂存 做。

要恢复冲突状态,首先确保所有内容都是干净的,然后将每个提交读取到临时索引中。使用生成的索引文件构建一个新的冲突索引,其中条目位于0、1、2和3阶段(视情况而定)。使用工作树提交从保存的合并中建立工作树状态 W 承诺(事实上,可能先这么做)。


技术上可以运输 暂存 在今天的存储库之间提交,但是 暂存 前端和内部工作使它有点棘手。因为,或者至少 这个脚本的要点是使传输一个正在进行的合并变得容易,无论您想到什么脚本,都需要提供一个机制和一些清晰的指令来完成这个操作。

6 年前
回复了 torek 创建的主题 » Git如何将其对象的更改反映到文件系统中?

README.txt HEAD

git add README.txt

git checkout master git checkout develop

master git read-tree develop

F

xyz.txt --force git checkout


首先,我建议您在 Think Like (a) Git . 它们对于理解Git的工作方式至关重要。

至于发生了什么,我做了一个小仓库,尽我所能地重复了你的一些命令:

git checkout develop
git merge feature-x --no-commit --no-ff

因此,现在开发将我的特性X更改放在最前面,未提交/未提交。

不:你现在正在进行一次未完成的合并。这个 --no-commit 选项取消提交,将您留在该合并中。

$ git merge feature-x --no-commit --no-ff
Automatic merge went well; stopped before committing as requested

此外,Git 自动将所有成功合并的文件复制到临时区域。 集结区也被称为 指数 我倾向于用这个名字来称呼Moreholds 全部的 提交的文件。每次提交都是 每个文件 以便以后可以完全完整地提取。

$ git status
On branch develop
All conflicts fixed but you are still merging.
  (use "git commit" to conclude merge)

Changes to be committed:

        modified:   README
        new file:   new.1
        new file:   new.2

如果你使用 git reset (在许多不同的模式中,命令执行了太多不同的操作,在我看来,因此很难谈论它),您可以将其中一个提交中的文件复制回临时区域,以便某些文件的临时区域版本与 develop 文件的版本,或者根本没有文件的副本(如果文件是新的),而不包含某些合并操作的结果。

我准备并提交了两个文件(称为提交“两个文件”)…

不,此时您只需完成合并。Git现在知道这两个分支上的一系列提交的正确组合是这个特定提交的结果。

注意 git commit --only 此时不能使用:

$ git commit --only README
fatal: cannot do a partial commit during a merge.

另一方面, git commit --include 可以 但这相当于跑步 git add 然后 git commit ,以便提交合并。

$ git commit

此时,一个编辑器打开一个文本文件,读取:

Merge branch 'feature-x' into develop
# 
# It looks like you may be committing a merge.
# If this is not correct, please remove the file
#       .git/MERGE_HEAD
# and try again.


# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# On branch develop
# All conflicts fixed but you are still merging.
#
# Changes to be committed:
#       modified:   README
#       new file:   new.1
#       new file:   new.2
#

将此文件写入并退出将产生:

[develop 2313673] Merge branch 'feature-x' into develop

我的结论是,要么您运行了一个不显示的命令,这可能是关键的/要么您提交了合并,这将解释您看到的行为。特别地:

做了 merge --no-commit --no-ff 到上面 发展 也改变 feature-x ?

否:但是提交合并会更改 合并基础 未来的运营和变化 哪些承诺可以实现 来自不同的分支名称。这种影响 git rebase . 特别是,您在WIP提交时 特性-X 现在可从访问,因此包含在 发展 所以制作 特性-X 从…的尖端延伸 发展 不再需要了 特性-X 包含WIP提交的副本,因为它已经 在里面 这个 发展 分支机构。因此,REBASE不会费心复制提交:它将在 二者都 从现在开始。

6 年前
回复了 torek 创建的主题 » Git交换机分支,丢失未跟踪的文件

我换了那个[ dev ]分支并切换回 master

最有可能的是那些文件 主人 并列入 .gitignore 目前 在小费中 DEV .

做:

git checkout dev

告诉你的Git: 将我的索引和工作树内容替换为提交到的内容 DEV 点。 因此,您的Git会找到冻结在特定提交中的所有文件,从该提交中提取它们,将它们放入索引中,然后将它们还原(解冻并解压缩)到您的工作树中。

这自然覆盖了 未跟踪的 工作树中的文件。但没什么大不了的,对吧?他们在承诺中。

然后你告诉Git: 将我的索引和工作树内容替换为提交到的内容 主人 点。 因此,您的Git会在 主人 ,提取它们,并像以前一样将它们放入索引和工作树中。这次,那些文件 不是 在承诺中。但它们被跟踪,并与提交的副本匹配,所以Git 移除 他们。

现在他们走了!

你可以在 DEV ,而不进行跟踪,使用 git show :

git show dev:file > file

这将提取提交时由 DEV ,生成输出到stdout,然后重定向到该文件。不幸的是它能让你得到一切 冻结在那个承诺中 不一定符合 在你的工作树上 当你跑 git checkout dev 原来。

通常,Git会在其中一个步骤中警告您这一点。如果您注意到这些警告并将文件移到一边,那么您已经将这些移到一边了。但是列出文件 吉蒂格诺 在某些情况下,具有给予Git的副作用 允许破坏其内容 . 所以有几个角落的情况下,Git会清除当前的内容,假设因为文件 吉蒂格诺 D,它们在任何方面都不珍贵。

恢复这些文件的唯一方法是在Git之外,使用诸如Mac上的Time Machine或系统备份。

6 年前
回复了 torek 创建的主题 » git别名ssh add.gitconfig

让我们从一个比 ssh-agent -s :

$ sh -c 'FOO=bar; echo FOO is now $FOO'
FOO is now bar
$ echo FOO is now $FOO
FOO is now

为什么这个不起作用?为什么我的登录外壳是 FOO 变量未设置,当我设置其他shell实例的 变量?

好吧,我希望答案是显而易见的: 我的 壳牌没有 集合。我设置了一些 其他 壳牌公司 变量。我告诉过你 其他 shell把它打印出来,然后就设置好了。然后 其他 shell完成了退出并将控制权还给了我的登录shell,当我告诉我的登录shell打印我的登录shell时 ,未设置。

什么 SSH试剂-S 确实是 打印输出 一些作业。让我们试试看:

$ sh -c 'echo FOO=bar'
FOO=bar
$ echo FOO is now $FOO
FOO is now

当然,这也不管用,因为我只是 打印了一些说明 . 没有人遵守这些指示。

所以,让我们再试一件事:

$ eval `sh -c 'echo FOO=bar'`
$ echo FOO is now $FOO
FOO is now bar

这次,我:

  • 让另一个命令打印一些指令
  • 告诉 我的 外壳到 跟随 那些指示
  • 然后让我的壳打印出我的壳的设置 $FOO

既然我的指令是这样的 改变 某物 在里面 我的贝壳, 现在 我的外壳的foo设置好了。

设计为 ssh-agent 是相同的:它可以做一些工作,然后以各种模式“工作”,包括 -s 艾斯 打印出说明 为了一些贝壳。你需要 eval 要使外壳符合这些说明。

这一切都很好,只要外壳遵循这些说明 你的 壳牌。但是,当您创建Git别名时,Git本身运行一个 新的,不同的 壳牌。这个shell遵循指令,当它完成时,您的shell不会受到影响。

这意味着你不能用git别名做你想做的事情。必须使用将影响 你的 壳,而不是一个会启动一个新的壳,影响它,然后让它终止并蒸发这些影响的壳。

注意一些变化,例如 git config --global 存储在 文件夹 而不是在你的壳里。这些变化 不要 当贝壳消失时蒸发。这是一个关键区别:文件系统状态存储在各个进程之外。如果一切 SSH剂 是以文件系统状态存储的,它 能够 是为了工作,但有些事情 SSH剂 不存储在那里。

6 年前
回复了 torek 创建的主题 » git--version命令返回的结果与实际安装的结果不同

大概你有 二者都 安装的版本相互独立。

使用 type git which git 看看你在跑哪一个。可能是 /usr/bin/git . BREW安装的可能是 /usr/local/bin/git . 改变你 $PATH (或) $path 或者不管你的壳用什么 /usr/local/bin 之前 /usr/bin 以便 git 将运行新的而不是旧的,或使用 /usr/本地/bin/git 运行新的。

(作为) Greg Bacon notes in a comment ,请注意,您可能需要运行 hash -r 在各种外壳中安装东西后让它们再次检查 /urr/本地/ bin 如果他们已经决定 吉特 方法 /UR/BIN/Git 尽管 $路径 /urr/本地/ bin 早期的。在CSH/TCSH中,这是 rehash 而不是 哈希- R )

(原来是散列问题),你的壳早就决定 吉特 意思是老的那个。再次登录也可以清除这类问题,但这很痛苦!)

如果你真的想要它立刻消失,它会自己消失。 最后 ,这要容易得多,你必须摆脱那些 eftshift0 mentions in a comment . 可能正好有两个:一个用于前一个值 HEAD ,并为分支的前一个值 本身的名字。在某些情况下,可能会有更多;很少,可能会有更少。

要做到这一点,使用 git reflog expire --expire-unreachable=now --all . 请注意,这将删除 全部的 无法从中重新记录条目 全部的 参考加 . 定义 不可达 这里和里面的一样 Think Like (a) Git :在这种特殊情况下,“可访问”是指“可从引用本身的当前值访问”。此特定更改无法撤消,因此在执行此操作之前,您应该非常确定不希望任何放弃的提交返回。

一旦你做到了,就跑吧 git gc --prune=now 删除未引用的提交及其未引用的文件。