私信  •  关注

torek

torek 最近创建的主题
torek 最近回复了
3 年前
回复了 torek 创建的主题 » git pull如何管理提交历史?

最短的答案(不是100%准确,但非常接近)是 git pull 管理好历史。什么 git pull 对您来说,它的作用是运行两个Git命令,作为初学者,我建议您单独运行:

  • 第一 git pull 表演 git fetch .这个命令非常简单明了(但有些曲折)。它从其他存储库中获取新的提交:Git调用其他Git、Git和他们的Git exchange提交哈希ID,从中,Git发现你需要从他们那里获取哪些提交(以及相关文件),这样你就可以通过互联网以合理的最小数据量完成所有提交。

  • 一旦完成了, git pull 运行第二个Git命令。这就是大部分复杂性所在。(这些第二个命令往往有很多选项、模式和功能,所以它几乎就像运行十几个命令中的一个。)

这个 选择 第二个Git命令是你的,但是当你使用 git pull ,你被迫 制作 在你有机会看到什么之前 git fetch 行。我认为这是不好的(大写B不好,但不是粗体或斜体不好,所以只有中等程度的不好)。一旦你经常使用Git,并且知道fetch是如何工作的,也许更重要的是,你已经发现某些同事、同事或朋友是如何使用Git的——这些都会影响到什么 git fetch will doit可以在获取提交之前安全地决定如何集成获取的提交。但在早期,我认为这有点太过分了。 1.


1. 总是有可能 打开 第二个命令所做的事情,但你需要了解第二个命令的所有内容。作为初学者,您可能甚至没有意识到这里有两个不同的命令。你肯定不知道如何撤销每个命令的每个模式的每个效果。


之后你就有了正确的设置 git fetch

假设我克隆了一个远程存储库,到目前为止它有1个commit=> A .然后,我向我的本地分支机构做出了两项承诺,因此它变成了=> A - B - C .然而,我的同事同时向他们当地的分支机构提交了另外两份提交,因此他们的提交历史记录变成=> A - D - E .然后他们将[这个]推送到[共享的远程]存储库。

当他们把你揍一顿 git push 向共享(第三)存储库“wins”提交 在里面 共享的第三个存储库现在拥有 A-D-E 表格:

A--D--E   <-- main

(这里的分支名称并不那么重要,但我使用的是 main 因为GitHub现在使用它作为默认值,而您提到 在标签中。)

什么 git fetch 这一步让你明白了 D E .你已经承诺了 A. ,并且在提交后不能更改任何提交。 2. 所以你只需要 D-E ,它会像这样出现在您的存储库中:

  B--C   <-- main
 /
A
 \
  D--E   <-- origin/main

名字 origin/main 是你的Git的吗 远程跟踪名称 ,这是你的Git从他们的Git中创建的 树枝 名称 主要的 .您的Git会获取每个Git的分支名称并对其进行更改,以生成这些远程跟踪名称。因为远程追踪的名字不是 树枝 姓名,有什么变化吗 git fetch 让他们——处理其他Git存储库中发生的任何事情——不会影响任何 你的 树枝。因此,跑步总是安全的 git fetch . 3.

我画了承诺 A. 强调这只是一个承诺,由两条开发线共享。还有一些关于 树枝 是一个 发展路线 那不是吗 起源/主要 A. 树枝 差不多吧?这是“分支”的模糊定义, 4. 但事实证明,它在一瞬间是有用的。


2. 注意 git commit --amend 例如,实际上 改变 承诺。相反,它制造了一个 提交,并让您使用该提交,而不是您正在使用的其他提交。现在,您有两个几乎相同的提交,其中一个被推到一边并被忽略。

3. 可以 设置 git fetch ,或者给它一些理由,让它做“不安全”的事情,但这很难。通常的简单方法是创建镜像克隆,但镜像克隆是自动生成的 --bare 而且一个赤裸裸的克隆人不会让你在里面做任何工作。(镜像克隆只适用于特殊情况,不适用于普通的日常工作。)

4. Git对 树枝 故意 脆弱而模糊,小心地说 分支机构名称 相反分支名称定义明确,没有这种哲学上的模糊性。A. 远程跟踪名称 显然不同于 分支机构名称 ,尽管这两种名称都让Git找到提交,而提交本身形成了我们(人类)喜欢的“分支”。所以从这个意义上说, 起源/主要 是找到分支的名称。这不是一个好主意 树枝 名称:内部拼写为 refs/remotes/origin/main ,其中分支名称必须以开头 refs/heads/ 这个 树枝 名称 主要的 拼写正确 refs/heads/main 内部。另见 What exactly do we mean by "branch"?


第二条命令:你的选择 git merge git rebase

这个 第二 命令 git pull 跑步是大部分真实动作发生的地方。这不是 合并分支 git rebase . 5. 这些解决了你和你的朋友之间的分歧 git fetch .每一个都使用不同的方法。

从根本上讲,合并比重新调整基期简单。这是因为rebase由 抄袭 提交,就像通过运行 git cherry-pick 一些形式的 git rebase 字面上使用 吉特樱桃采摘 其他人则使用近似值,每个樱桃采摘本身就是一种合并。这意味着,当你重新设定三次提交的基准时,你将执行三次合并。rebase执行的复制之后是一个又一个内部Git操作,而许多形式的 合并分支 一步到位。


5. 从技术上讲 git pull 能跑吗 git checkout 在一种特殊情况下,但这种情况不适用于这里。


合并

从根本上说,合并是关于合并工作。

请注意,当我们遇到像上面所画的情况时,我们必须合并工作,其中有一些共同的起点(提交) A. )然后是发散的工作。然而,在有些情况下,“合并工作”并不重要:

A   <-- theirs
 \
  B--C   <-- ours

在这里,“他们”——不管他们是谁——实际上并不是 做什么工作 ,所以要“结合”你的工作和他们的工作,你可以让Git切换到你的最新提交:

A--B--C   <-- (combined successfully)

Git将这种“组合”称为 快进操作 ,什么时候 合并分支 是吗,Git称之为 快进合并 .一般来说,如果 合并分支 可以 做一个快进合并,它 做一个。如果没有,它将进行全面合并。

完全合并会发现 合并基 正在进行的共享提交 两个分支 ,故意使用松散的 树枝 我在前面提到过,并将该特定提交中的快照与两个分支提示提交中的快照进行了比较。这使Git能够了解“我们改变了什么”以及“他们改变了什么”:

B--C<——主要的
/
A.
\
D--E<——起源/主要

区别于 A. C 显示什么 我们 我们的两次承诺发生了变化。区别于 A. E 显示什么 他们 换了 他们的 两次犯罪。

Git然后尝试组合并应用 两组变化 到提交中的快照 A. .如果Git认为这一切进展顺利,Git将继续并根据结果制作一个新的快照——“一个新的提交”。通过获取我们的更改并添加他们的更改(或者,等效地,获取他们的更改并添加我们的更改),Git的合并提交将具有以下快照:?对的结合体这里的问号是因为Git只是使用简单的逐行规则。结果可能是 从另一个意义上说,要正确:按照Git的规则,这是正确的。

无论如何,新的 合并提交 Git现在将链接回 二者都 我们目前的承诺 C 他们的承诺 E :

  B--C
 /    \
A      F   <-- main
 \    /
  D--E   <-- origin/main

我们的分支机构名称, 主要的 ,现在选择新的合并提交 F .注意 F 有一个快照,就像任何普通的提交一样,还有一个日志消息和作者等等,就像任何普通的提交一样。这个 只有 有什么特别的吗 F 是不是指的不是 上一次提交时,它指向两个。

然而,这会产生巨大的后果,因为Git 发现 commits是从某个名称(通常是分支名称)开始的,尽管任何类型的名称都可以使用它来定位 最后的 承诺,然后追随 全部的 反向链接到 全部的 上一次提交。所以从 F ,Git倒退到两者 C E “同时”。 6.


6. 由于这不太可能,Git必须使用某种近似值。Git的某些部分使用广度优先搜索算法,而其他部分则使用各种技巧。


重新定基

从根本上说,重定基期是指做出一些“还行,但还不够好”的承诺 抄袭 然后,他们会做出(据推测)更好的新的和改进的承诺 放弃原件,取而代之的是新的和改进的副本 .

这样做有几个问题:

  • Git“喜欢”添加新提交。它“不喜欢”抛弃旧的承诺。Rebase迫使Git放弃旧版本,转而使用新的和改进的版本,就目前而言,这很好,但是。。。

  • 我们将提交从一个Git存储库发送到另一个Git存储库。一旦它们被复制——一旦马走出谷仓并被克隆——摧毁其中一些是没有好处的。如果我们有新的和改进的替代品,我们必须 每一个 吉特,那是 副本 原作的一部分会被挑选出来,换成新的和改进的替代品。这意味着我们需要强制其他Git放弃一些现有的提交。

一条始终有效的简单规则是: 只有你从未放弃的承诺。 这是因为如果你只有一个副本,你的新的和改进的替代品就不需要得到任何副本 另外 我真想把旧的扔掉。没有其他Git存储库!但它太简单了,至少对于许多GitHub工作流来说是如此。

更复杂的处理方法是: 只有您和这些存储库的所有其他用户事先同意的替换提交才能被替换。 如果其他用户注意到了,他们至少会注意到替换件并将其捡起来。

不谈所有细节,什么 git rebase 是不是:

  • 列出要复制的提交(散列ID);
  • 使用Git的 分离头 避免设立临时分支机构的模式;
  • 检查目标提交副本的目标;
  • 使用一个接一个地复制要复制的提交 吉特樱桃采摘 或类似的;最后呢
  • 移动分支名称以指向上次复制的提交。

在这种情况下,您可以将现有的两个提交重新设置(复制)为两个新的和改进的提交:

  B--C   <-- main
 /
A      B'-C'  <-- HEAD
 \    /
  D--E   <-- origin/main

哪里 B' C' 这些是 B C .中的快照 B' 是通过制造 变化 到中的快照 E ; 将要做出的改变是 比较 A. B .中的快照 C' 是相似的,但是通过从 B C .

一旦拷贝完成,Git就会把旧的剥离掉 主要的 给旧标签 C 提交并粘贴到新的 C' 承诺:

  B--C   [abandoned]
 /
A      B'-C'  <-- main (HEAD)
 \    /
  D--E   <-- origin/main

原著 B C commit仍然存在一段时间,但是没有一个简单的方法来实现 发现 他们,你就是不知道 看见 他们再也没有了。如果您没有仔细记下原始文件的真正哈希ID B C ,你会认为他们的新的和改进的替代品以某种神奇的方式 改变 B C 在正确的位置但他们没有:他们是 完全新的 ,旧的承诺仍然存在。旧的承诺很简单 不用 。一段时间后,defaultGit将至少30天视为垃圾,并最终使用“垃圾收集”它们 git gc (哪个Git会自动为您运行,通过 git gc --auto 从各种Git命令中分离出来,而无需执行任何操作)。

如果一切顺利,重定基调的承诺“保留你工作的本质”,让它看起来像你开始工作一样 之后 你看到你的同事要做什么了。不过,复制的提交中的日期和时间戳更为复杂:

  • 这个 作者日期 是您最初编写提交时保存的。
  • 这个 提交日期 是您上次使用rebase复制它们的时间。

您可以重复地重新设置提交的基础,并且作者时间戳在每个副本中保持不变。要查看这两个时间戳,请使用 git log --pretty=fuller 例如。

3 年前
回复了 torek 创建的主题 » 无法将git分支更改为主分支

你似乎遇到了几个问题,考虑到问题本身的问题,很难确定解决这些问题的具体顺序。问题包括:

  • 这个 permission denied 错误:这意味着您可能以超级用户的身份运行了一些操作( sudo command (例如)。您可能需要将这些文件的所有权更改回您自己,以便您可以使用它们。但我们不能确切地看到问题是什么,所以我也不能确切地说如何解决它。

  • 这个 rm -rf .git/rebase-merge :这让Git“忘记”您正处于一个不完整的回退过程中。 你过去和现在都处在这个不完整的返利过程中 ,但现在不可能让Git正常地将您从中解救出来,因为您破坏了Git需要的信息。

我认为解决这个问题最有希望的方法是首先解决权限问题。然后你就可以使用 git checkout main ,这将使您或多或少摆脱不完整的重新设置基础,就像您使用了 git rebase --abort .然后你可以重新开始。

或者,您可以完全放弃这个克隆(以及您在这个克隆中所做的任何尚未在GitHub上的提交)。这不是一个好主意 很好 当然是解决方案。如果你能解决权限问题, Alex028502's answer 这可能是一个更好的方法。

3 年前
回复了 torek 创建的主题 » 如何在git钩子中获取当前项目目录?

the githooks documentation 说:

在Git调用钩子之前,它会将其工作目录更改为 $GIT_DIR 在光秃秃的存储库中或 非裸存储库。

(阅读链接文档了解例外情况。)

因此,您可以检查当前工作目录,以找到工作树的顶层,假设是非裸存储库。

3 年前
回复了 torek 创建的主题 » 计算git存储库之外的文件或目录的git哈希

Files are easy; directories are hard. 阅读目录的Python代码;但文件只是文件内容的校验和(现在是SHA-1,将来是SHA-256),前面有一个blob头,其中包括blob的大小(十进制ASCII码),再加上一个字节将头与数据分开。也就是说,对于一个12字节的文件,我们有 blob 12\0hello world\n 作为输入 sha1sum 或者无论您的本地命令或计算SHA-1校验和的方法是什么。

(你也可以简单地使用 git hash-object ,用于普通文件。目录仍然很难找到。)

3 年前
回复了 torek 创建的主题 » `git checkout refs merge`not merging with HEAD of`main`

根据文件,它应该模拟一个合并,这将发生在你合并公关。。。

这不是一个 模拟的 合并相反,GitHub实际上 合并了 .

我本以为这会模拟与main的HEAD合并,但如果与历史上一个奇怪的commit合并的话。

合并,在Git中(因此也在GitHub中) 1. ),而不是 树枝 .这是因为从重要的意义上说,分支甚至 存在 用吉特。 合并提交 肯定 存在,所以如果我们绕过这个困惑 "what exactly is a branch anyway" 提问并直接进入合并提交,我们可以很容易地讨论:

  • A. 合并 提交与常规提交非常相似。常规提交有一个快照,即您(或任何人)提交时出现的每个文件的完整副本,以及元数据,用于说明提交人、提交时间等。

  • 数据库的元数据 典型的 commit有一个 单亲提交哈希ID 储存在里面。这将提交链接在一起,形成一个向后看的链。这一连串的承诺 Git存储库中的历史:提交就是历史;历史不过是一系列的承诺。

  • 数据库的元数据 合并 commit使提交成为合并提交:它列出 父母 3. 这两个家长的承诺是 二者都

GitHub用作 第一 父母是承诺 在您(或任何人)发布拉取请求时,“基本分支”(GitHub特有的术语)的提示。GitHub将用作 第二 在你(或任何人的)公关的顶端,父母是提交人。从那时起,其他人有可能向“基本分支”添加新的提交。测试合并是对的 古老的 分支提示提交。任何承诺,一旦做出,都可以 ,所以这个测试合并永远是这样的。你的未来 真实的 合并,如果你这样做, 可能不同: 不同的提交,具有不同的哈希ID和不同的父级。未来实际合并的第一个父级将是GitHub进行最终合并时的分支尖端;未来实际合并的第二个父级将是拉取请求的提示,您可以从现在到那时更新它。

您是否以及何时应该使用此测试合并,或者进行自己的合并,这只是一个问题 我能回答。你控制着这里: 选择你可以用 refs/pull/534/head 参考PR本身的提示提交(测试合并中的父级#2),如果存在,可以使用 refs/pull/534/merge 参考GitHub测试合并。如果GitHub测试 合并冲突 , 参考/拉动/534/合并 不存在,除非有人使用GitHub接口解决合并冲突。


1. GitHub实际上并不直接使用Git。相反,他们使用了一种Git的增广子集。但是,如果合并没有冲突,合并结果是相同的。当合并发生冲突时,GitHub最初根本无法进行合并;现在,通过它们的扩展,它们提供了一种解决冲突的方法,而这不是Git的一部分。这种非Git GitHub特定的冲突解决方法仅在GitHub上可用,因为它们是GitHub自己创建的。

2. 当然,在其他意义上,分支确实存在。诀窍在于准确定义我们所说的 树枝 .另见 this SO question ,它深入到 存在,我们 呼叫 以及人们如何将它们相互混淆。另见 this Wikipedia article ,涉及命名的形而上学。

3. 所谓的 章鱼合并 有两个以上的父母。不过,它们并没有做任何普通合并所不能做的事情,所以我们将直接跳过它们来获得这个答案。

3 年前
回复了 torek 创建的主题 » Git没有添加一些文件

你提到:

目录 .emacs.d/ 包含一个 .git 存储库。

这就是问题所在。Git将拒绝在Git存储库中存储Git存储库。这有很多好的理由。理论上,你可以通过重命名 吉特先生 例如。, .got 然后再加上,但不要这样做。

如果你试图 git add 从一个Git存储库到另一个Git存储库,“外部”存储库最终将包含 提及 (不是)内部存储库的副本。在内部,这个引用采用Git称为 gitlink .gitlink是Git的关键部分 子模块 ,但不是所有的子模块:为了让子模块“正常工作”,Git存储了第二个关键部分 .gitmodules 这个 git添加 命令将创建或更新gitlink,但不会创建正确的 .Git模块 文件

你可以:

  • 停止使用 分离 内部的Git存储库 .emacs.d
  • 只需将其视为两个存储库(处理或不处理) emacs先生。D 并备份和/或复制这两个Git存储库。

治疗 emacs先生。D git submodule add 至少一次来创建 .Git模块 向上级(或 超级项目 )数据库所在的存储库 --git-dir=$HOME/config_repo .在这种情况下 gitc submodule add ,这可能有效,但我不知道与 --git-dir 参数与子模块(它们本身使用 --git目录 内部)通常在Git构建时进行测试。为了避免将其作为子模块使用,只需继续您已经在做的事情,并且 不要 gitc add .emacs.d .

3 年前
回复了 torek 创建的主题 » Git提交可以有多个树对象吗?

一个提交对象是否总是只有一个[top-level]树对象?

我想不出哪种情况会是这样,但我也看不到任何明确说明这一点的文档。

这是设计的一部分:没有它, git rev-parse commit ^{tree} 不会有明确的结果。

我们可以设想一个系统 相关的 到Git,提交可以在其中存储多个树, 1. 但首先需要解决一些设计问题。


1. 我在几个方面考虑过这一点,包括“子模块是错误的,子树更好,但需要更好的支持”之一。我还没有解决所有问题。

请注意,提交对象也必须只有一个 author 还有一个 committer 线 git fsck 如果格式不正确,将反对提交。

这是因为有一个坏的树对象,即 6e6758bea668ae2fb6271dec137927981548b581 .对象本身要么根本不存在,要么在内部无效;这个 git fsck 输出意味着前者。

现在还不清楚你是怎么陷入这种境地的,但是 git log 它自己永远不会注意到,因为它获得了 犯罪 ac1d9fec39372683cd20fba15f9c5318b957cf25 它本身是完整的。只是这件事 丢失的树对象。只要软件从不试图检索丢失的对象,就没有人注意到它丢失了。坏的(因为缺少树,但本身并不坏)提交也指以前的提交 e6cdf4125529fcb8c0b0e131b12c4ab24012cdfd ,这一切都很好,之前的所有提交都很好。

如果您可以找到或重新创建丢失的树对象,存储库将恢复可用性。或者,如果可以用引用现有或新树对象的好提交替换坏提交,那么整个存储库就可以了,尽管 快照 这是我的承诺 6e6758bea668ae2fb6271dec137927981548b581 他走了。

你的 Word Guess 目录(文件夹,如果您愿意)包含一个Git存储库。Git存储库不能包含另一个Git存储库,因此“外部”Git拒绝添加整个内部存储库。它确实增加了 某物 ,但它添加的不是Git存储库:而是一个 子模块 ,这是一个 提及 另一个Git存储库。

这使得外部Git充当所谓的 超级项目 。克隆超级项目时,超级项目包含对其他Git项目的引用,以及 git submodule init git submodule update --init 知道如何阅读“从何处克隆另一个Git项目”的说明并克隆它。A以后 git checkout --recursive git submodule update --checkout ,使用存储的有关 犯罪 将在子模块中签出。

要正确添加子模块,需要使用 git submodule add git add .你用 git添加 使现代化 子模块链接,但仅限于 之后 git子模块添加 正确添加子模块。如果你使用 git添加 在最初添加子模块时,您会得到我有时称之为“半途而废”的子模块:一个缺少关于Git在超级项目的其他克隆中应该在何处克隆子模块的说明的子模块 从…起 .如果没有这些信息,超级项目的新克隆就会知道它需要提交 a123456 (或者其他什么)的Git存储库,但不知道应该在哪里克隆另一个Git存储库 从…起 (除非你计划自己手动克隆子模块,否则这不是很有用,这就是为什么我称之为“半途而废”。)

在Git 1.5、1.6和1.7早期糟糕的旧时代,人们通常将子模块称为sob模块,因为工具太差了,以至于让人大哭。还是。。。不是很好,我认为有些人仍然使用这个短语。如果你真的想要一个子模块,请 真的确定吗 你想要一个子模块。

3 年前
回复了 torek 创建的主题 » 如何将目录和子目录同时添加到Git

Git不存储 目录 .Git商店 文件夹 ,或者更准确地说,Git商店 承诺 (然后存储文件——参见下面的挑剔区别)。但在你的情况下,问题是这些文件 已经在另一个Git存储库中 因此Git不会将它们添加到 存储库。

这是因为子目录也包含git吗?

更准确地说,这是因为这些子目录 Git存储库(有点像;请参阅下面的挑剔区别)。

那么我该怎么解决这个问题呢?

你必须立即(在你真正理解所有后果之前)做出一个具有深远影响的决定:

  • 你想用Git的吗 子模块
  • 你想吗 删除其他存储库 这样你就可以把这些文件直接存储在 仓库?

你需要知道的挑剔的区别

笨蛋 存储库 主要是一系列 承诺 .我们通常通过 分支机构名称 ,尽管这条规则也有例外。 克隆 存储库复制该存储库的所有(或至少大部分)提交,但 不复制其分支名称 .相反,我们的GitTour软件在创建和填充我们的存储库时,会读取另一个存储库的 树枝 命名并将其更改为 远程跟踪名称 .

因此,存储库本身主要由两个数据库组成(在此之后,添加了许多辅助内容,以使它们更可用)。其中一个,通常是最大的,持有commitsGit称之为 提交对象 有时还会加上Git需要的其他内部Git对象。这个对象数据库是一个简单的 key-value store 其中 钥匙 哈希ID: 巨大丑陋的字母和数字字符串,实际上是十六进制数,是加密校验和函数的输出。另一个数据库保存 姓名: 分支名称、标记名称、远程跟踪名称等等。每个名字都适用 散列ID;分支机构的名称尤其重要 犯罪 散列ID,因此让我们找到这些特定的提交。

每个 犯罪 反过来又有两件事:

  • 每个提交都有一个 每个文件的完整快照 。这些文件以特殊、只读、仅Git、压缩和消除重复的形式存储。它们不是普通文件,它们的名称中嵌入了前斜杠,例如 path/to/file.ext ,甚至在窗户上;它们区分大小写,即使您使用的是不区分大小写的Windows或macOS系统;等等

  • 每个提交也包含一些 元数据: 关于那个特别的承诺的信息。这包括提交人的姓名和电子邮件地址。它包括几个日期和时间戳,这有助于使每个提交都是唯一的。而且,对于Git的内部操作来说至关重要的是,每个提交都包含一个 以前的 提交哈希ID。

大多数提交中的前一个提交列表只有一个条目长,因此每个提交只记住其(单个)的原始散列ID 父母亲 犯罪这会将提交形成向后指向的链,其中 最近的 用一些大而丑陋的随机散列ID提交;我们就叫它吧 H — 指向 一些早期承诺:

            <-H

让我们把上一个提交称为“提交”,它的真实名称也是一个大而丑陋的随机散列ID G 这样我们就可以讨论它,并把它吸引进来:

        <-G <-H

犯罪 G ,作为提交,存储一些甚至更早提交的哈希ID F ,等等:

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

每个提交还具有每个文件的完整快照,尽管有重复数据消除功能,以便 G 与中的文件完全匹配 H ,存储库中实际上只有该文件的一个副本。(Git通过哈希和它所称的 水滴状物体 ,但你通常不需要在意。)

通过 比较 中的快照 G 在这方面 H ,Git可以告诉您哪些文件是相同的——可能是大多数——哪些文件是不同的。这个 git diff , git show git log -p 命令都可以进行这种比较:它们将显示不同的文件,默认情况下,还会显示一个将左侧文件更改为右侧文件的配方。所以比较 G vs H 告诉您哪些文件发生了更改,以及发生了什么 那些文件。 H 但仍然存储完整的快照:为了找出发生了什么变化,Git必须找到 G .它能做到 因为 H 指向 G .

你不能 使用 直接提交,因为 只读,而且实际上只有Git可以读取。你也无法完成任何新工作,因为它是只读的。为了解决这个问题,我们实际上并不直接使用commit。相反,我们使用 git checkout git switch 摘录 承诺。这是:

  • 从之前的提交中删除所有以前提取的文件,然后
  • 从我们要移动的提交中提取所有文件

现在我们有了 可用的 每个文件的副本。这些可用的拷贝被Git称为 工作树 工作树 简而言之。

这些工作树文件就是您实际需要的文件 看见 合作 。它们是普通文件,由您的计算机以普通方式存储,因此它们位于目录中(有些人更喜欢这里的“文件夹”一词;任何一个都可以)。 这些文件不是Git格式的。 他们很可能已经来了 从…里面 吉特,维娅 git签出 但现在他们已经不在Git了。你对他们做的任何事情都不会影响Git。

如果您修改了这些文件,或者删除了一些文件,或者创建了新的文件,那么您最终必须告诉Git这一点。Git会将这些文件复制回它所称的 指数 集结区 .关于这一点有很多需要了解(你不能略过这些知识,因为这对理解a的概念至关重要。) 追踪 vs 未追踪 文件,以及如何 .gitignore 真的很管用),但我们这里不讨论这个问题,但你会跑的 git add ,然后最终 git commit .commit动词将使 当文件出现在Git的暂存区域时提交,然后更新当前文件 分支机构名称 将新提交记录为此分支上的最新提交。

这个 存储库本身 通常是 1. 被关在一个 .git 工作树顶层的目录(或文件夹)。也就是说,如果您的存储库处于 /Users/you/work/repo2 ,有一个 /Users/you/work/repo2/.git 包含 存储库 ,然后是 /用户/你/工作/报告2 这些文件是从 当前提交 你之前和他一起退房的 git签出 git交换机 .


1. 形容词 正常地 这是因为子模块和添加的工作树以及其他一些特殊情况会导致 文件 命名的 吉特先生 .Git在有令人信服的理由时就会使用这个技巧 移动 这个 吉特先生 目录 离开 从工作树上。子模块需要为一些其他丑陋的情况这样做;见下文。


子模块

Git的子模块在这张相对简单的图片上添加了一个褶皱。A. 子模块 在很大程度上,它只是一个包含在另一个存储库中的存储库。也就是说,有一个 吉特先生 某个Git存储库的工作树的子目录之一中的目录(或文件,如脚注1所示)。

如果Git存储库正在使用子模块,我们将该Git存储库称为 超级项目 超级项目和子模块仍然具有Git存储库的所有常规属性:它们在 吉特先生 目录(或在中找到的路径) 吉特先生 文件),以及签出提交的工作树。但是超级项目需要包含一些额外的内容,我们马上就会看到。

同时,子模块是 由超级项目控制 .子模块 仍然是一个存储库和工作树,所以仍然有提交,您可以签出一个。除了:通常 不要结账。你有 超级项目 那样做。

假设您克隆了一些Git存储库 R ,然后您现在查看您将使用的第一个提交 在里面 R ,以分支命名 B .以及 文件夹 这与承诺有关 B ,有一条指令,存储在commit for B 也就是说,实际上: 现在克隆并使用另一个Git存储库 另外 存储库是 s ,子模块。 R 现在是 超级项目 ,顾名思义。

为了克隆 s ,Git需要:

  • 要克隆的存储库的URL;
  • 工作树中用于签出的路径名 R ; 和
  • 提交哈希ID到 git checkout --detach 在里面 s 一旦制作完成 s .

Git从名为 gitlink ,并从同一gitlink获取提交哈希ID。gitlink来自commit B 然后进入索引/暂存区 R ,以及克隆 s ,Git在文件中查找gitlink的路径 .gitmodules 在commit中可以找到 B .此文件具有URL,因此超级项目Git能够运行正确的 git clone 命令:

git clone <url-for-S> path/to/s

例如,在你的情况下:

git clone <url> Child_folder_1

要使子模块克隆工作,必须有 .Git模块 用正确的信息归档。 这个 git submodule 命令是构建和维护这个 .Git模块 文件因此 Child_folder_1 作为子模块,必须运行:

git submodule add <repository-url> Child_folder_1

这将负责创建或更新 .Git模块 文件,这样克隆说明就会出现;这个 repository-url 你提供的就是 git克隆 我会晚一点回来。

如果你只是 git add Child_folder_1 Git补充道 gitlink 后期克隆所需指令集的一半。但这并没有增加 .Git模块 条目。这就是为什么会出现以下错误消息:

warning: adding embedded git repository: Child_folder_1
hint: You've added another git repository inside your current repository.
hint: Clones of the outer repository will not contain the contents of
hint: the embedded repository and will not know how to obtain it.
hint: If you meant to add a submodule, use:
hint:
hint:   git submodule add <url> Child_folder_1
[snip]

这是在告诉您,您错误地添加了子模块。

为什么我们需要子模块?

我们没有。

然而,我们确实需要理解,Git存储库是 不允许包含另一个Git存储库 吉特先生 不会 添加名为 吉特先生 (或 .Git .GIT .giT ,或任何其他类似拼写)。更准确地说, 吉特先生 禁止将大写或小写字母混合作为名称组件。所以如果你 在存储库中有一个存储库, git添加 不会添加 文件夹 在这里相反,它将添加Git称之为 gitlink ,这将为您提供子模块样式的行为。Git的其他部分,包括 git status ,也不会抱怨这些文件未被跟踪(尽管它可能会提到 子模块存储库本身 在状态中)。

你可以,如果你愿意, 去除 这个 吉特先生 每个子模块工作树中的文件或目录。如果你这么做了,你就不再是这样了 对应Git存储库的工作树。相反,这些文件 计算机普通格式的普通文件现在是普通的,但是 未追踪 存储库工作树中的文件。我们可以用这个短语 这个 存储库,因为只有一个存储库。当你有子模块时,你正在使用两个或更多的存储库,所以每次有人说“存储库”,你必须停下来问:“等等, 哪一个 仓库?"

但是: 如果完全删除子模块存储库,则子模块存储库中不再有提交。 没有子模块存储库,因此没有提交。所以你的未追踪文件 这个 (顶层)存储库工作树是 只有副本 你所有的那些文件。你有 扔掉 所有的 另外 ,提交,这些文件的副本。

现在可以添加并提交 子文件夹1 保存当前工作目录(包括子目录)的(单个)存储库中的文件 子文件夹1 .但你只能 这个版本 这些文件的一部分。作为一个子模块,你会得到一个 gitlink 也就是说: 检查克隆后的提交(用哈希ID填写空白) ,以及 git克隆 副本 全部的 提交来自其他存储库,因此每个文件都有多个版本。

当你有一个子模块时,你可以 更新说明 在超级项目中。你可以说: 签出提交第二页(这次用一个不同的哈希ID填空) 这个 git submodule update 命令在超级项目中运行时,将切换到 那个 犯罪工作树文件将与其他提交文件匹配。

你甚至可以说 去除 那个子模块。这个 git子模块更新 命令将完全删除工作树文件。(以避免移除 吉特先生 目录,实际的 存储库 住在超级项目里 吉特先生 目录,这样就可以安全地删除它 吉特先生 文件。)

所以,你必须做出决定

子模块非常不方便,以至于很多人称它们为“哭泣模块”,因为它们让用户哭泣。但是他们做了一些没有他们你做不到的事情。你想做这件事吗?或者,您想要一个更简单的操作,在该操作中放弃 另外 Git存储库,只需 这是这些文件的一个版本 如果将子模块作为子模块添加到超级项目中,会是什么?

最简单快捷的方法就是放弃所有的历史。如果您今天不需要它,明天也不需要它,那么这会让您回到单一Git存储库的情况,而不需要子模块的复杂性。明天你就不会叫他们哭泣模块了。但如果你 如果你明天需要它,将来可能会因为昨天没有使用这些子模块而哭泣。

你现在有了我能给你的尽可能多的信息来做出这个决定。要么使用 git submodule add 将这些模块制作成适当的子模块,或移除 吉特先生 并将其添加为普通文件。

4 年前
回复了 torek 创建的主题 » Git合并冲突对我来说毫无意义

这一特殊冲突:

CONFLICT (file location): dan/homebase.md added in HEAD inside a
directory that was renamed in master, suggesting it should perhaps
be moved to wordpressed/homebase.md.

是Git中一项相对现代的发明。Git现在试图找到 目录重命名 在合并基本提交和每个提示提交之间。也就是说,已经跑了 git diff --find-renames <base> <left> git diff --find-renames <base> <right> ,它在左侧检测到一些重命名,在右侧检测到一些重命名(总数为正数)。至少其中一些重命名意味着目录名的更改。所以Git现在认为 另外 合并的一侧应该应用相同的目录名更改。

(大约在2.19世纪中期,Git的预发布版本添加了各种实验版本的代码,我忘记了它何时真正稳定下来。仔细阅读发行说明,看起来2.17就是它发布的时候,然后在2.19中修复了一个后续的角落案例。)

[Edit,2021年4月15日:]当这项新功能投入使用时,一个新的控制旋钮也投入使用: merge.directoryRenames .其默认值为 conflict ,但您可以将其设置为 true (意思是总是做出它已经做出的假设,但不要称之为冲突)或 false (意思是永远不要做出新的假设,因此不要称之为冲突)。

这是Git宣布的唯一冲突,因此它是最后一个“未合并文件”部分显示的唯一文件:

Unmerged paths:
  (use "git add <file>..." to mark resolution)
    added by us:     wordpressed/homebase.md

Git在这方面采纳了自己的内部建议:它认为,一切都会增加 dan/ 在合并基和 HEAD 应该被添加到 wordpressed/ 因为它看到了合并基部和顶部之间的变化 master .

如果您觉得这是正确的,Git希望您通过运行 git add 在那个路径名上。如果没有,Git希望您将文件移动到您认为应该去的任何地方 git添加 结果(并移除,使用 git rm 或者是 git添加 ,Git使用的名称)。

3 年前
回复了 torek 创建的主题 » 重新排序git历史记录并保留内容,而不是提交/修补

Rebase是一个错误的工具,但是Git并没有为你的工作提供正确的工具,因为这个工作。。。Git从来没有设计过这样的功能 .

不过,吉特 可以 去做吧。这只是使用底层组件的问题,而不是任何面向用户的顶级(“瓷器”)命令。

我可以想到的方法是对所有提交进行git归档(或git diff和根提交),对它们进行排序,并创建一个全新的历史记录。有更好的方法吗?

使用 git archive 会是 可以 ,但这项工作超出了需要。使用 git diff 每一次都会有更多的工作要做,而且不太合适。

最简单的方法是使用 git commit-tree .这是一个低级(“管道”)命令,需要三个输入:

  • 它需要一个 父哈希ID列表 。在父提交(单数)时,您将选择第一次提交的根提交,然后选择您刚刚为每个后续提交所做的最后一次提交。

  • 它需要一个(单一的) 树哈希ID 用于将源树存储在提交中。对于每一次新的提交,您都将选择与当天提交相关联的源代码树。

  • 最后,它需要一个 提交消息 .你可以把当天的承诺信息反馈给它。

您可能还希望覆盖提交人和提交人日期以及作者和作者日期的默认设置。你可以从四个到六个独立的环境变量, GIT_AUTHOR_NAME , GIT_AUTHOR_EMAIL , GIT_AUTHOR_DATE ,以及相应的三个变量 COMMITTER 代替 AUTHOR .或者,在tcsh/bash大括号扩展语法中, GIT_{AUTHOR,COMMITTER}_{NAME,EMAIL,DATE} .获取这些值的地方将是您转换的原始提交。

总体而言,您的脚本可能如下所示:

last=$root    # find and set the root commit hash ID

for commit in $(get-list-of-original-commits-to-copy-in-new-order); do
    set_author_and_committer $commit   # set-and-export GIT_{AUTHOR,COMMITTER}_*
    new=$(git log --no-walk --format=%B $commit |
          git commit-tree -p $last ${commit}^{tree})
    last=$new
done
git branch rewrite $last

(未经测试,需要编写一些函数)。这个 git log --no-walk --format=%B 提取表单中的原始提交消息 git提交树 需要,以及 git提交树 默认情况下从stdin读取提交消息,因此这里没有特殊工作。我们只是通过 -p 以及通过符号方法指定与被复制的提交相关联的树的树散列(参见 the gitrevisions documentation ).

用于设置和导出 GIT_* 变量可以在 the old git filter-branch script .

请注意,此脚本会破坏提交中的任何GPG签名;处理它们需要一些更高级的东西。

5 年前
回复了 torek 创建的主题 » 如何将除前两个参数外的所有bash参数设置为git alias

Git在解析 .gitconfig 行。因此,当你写道:

"!f() { branchNumber=${1}; type=${2}; shift; shift; subject=${*}; git commit -m '['"$branchNumber"'] '"$type"': '"$subject"' '; }; f"

什么 看到的是:

!f() { branchNumber=${1}; type=${2}; shift; shift; subject=${*}; git commit -m '['$branchNumber'] '$type': '$subject' '; }; f

$subject 展开后,它变成单独的单词:

git c 1000 feat add new sales controller

git
commit
-m
[1000] feat: add
new
sales
controller 

(我把每个“词”单独画在一条线上)。(单词后面还有一个空白 controller ,因为这是由单引号保护的。)如果您愿意,另一种用于StackOverflow过帐的方法可能是:

git commit -m [1000] feat: add new sales

也就是说,命令本身将每一个都视为一个单独的“单词”:因此提交日志消息是 [1000]专长:添加 ; , 出售 控制器 被视为 路径规范 论据。

一个简单的解决方法是用反斜杠双引号替换要保留的每个双引号:

    c = "!f() { branchNumber=${1}; type=${2}; shift; shift; subject=${*}; git commit -m '['\"$branchNumber\"'] '\"$type\"': '\"$subject\"' '; }; f"

为了使它更易读,请注意双引号足以 参数(这里的不同地方不需要大括号):

    c = "!f() { branchNumber=$1; type=$2; shift 2; subject=$*; git commit -m \"[$branchNumber] ${type}: $subject\"; }; f"
5 年前
回复了 torek 创建的主题 » git合并的奇怪之处?

Git认为,如果对同一原始源代码行进行不同的更改,则更改会相互冲突 如果这两个改变“接触”的边缘。后者的原因是Git不知道 秩序 把它们放进去。

也就是说,假设从同一个空的开始 test.txt 你的 变化是:

+Test 1

Test 1 )以及 变化是:

+Test 2
+Hotfix

Git不知道是先把你的行放在他的两行后面,还是先把他的两行放在你的行后面。

你说他做的事改变了“第三行”。没有3号线。也许最初的空文件实际上是 空白的 行吗?那么 你的

-
+Test 1

也就是说, 去除 测试1 . 伊斯

+Test 2

+Hotfix

(也就是说, 保持 改变 改变 添加和/或删除 线条,这是另一个改变的原因,就像这种冲突一样

无论哪种方式,如果您更改了不同的行或没有“接触”彼此,就不会发生合并冲突。也就是说,假设 text.text 文件读取:

This test file
has several
lines in it.

测试1 顶部 . 他做了一个改变 Test 2 Hotfix 在底部。当Git将文件的三行开头的版本与四行开头的版本和五行开头的版本进行比较时,Git发现 你的 更改是在“第0行”(文件顶部)和第1行之间插入,其更改是在“第4行”(文件底部)之前的第3行之后插入。这些变化 不要

Test 1
This test file
has several
lines in it.
Test 2
Hotfix

想想 合并基

另一种说法是:Git没有将其更改添加到 你的版本 他的版本 文件的。Git正在寻找 合并基

记住,每次提交都会存储每个文件的完整快照。如果Git能找到 任何 二者都

合并基础是“最好的”提交,从中开始的是最接近双方最终结果的提交。因为你们都是从 相同的 承诺,你们两个都有 此文件的版本。因此Git可以运行两个 git diff 操作。一个人发现了什么 改变。Git的工作是将这两组更改组合起来,并将这些组合的更改应用到 基础

当这些更改影响不同的行并且不重叠时,Git可以自己组合它们。当它们影响到同一行时,或者执行abut时,Git不能单独组合它们。

6 年前
回复了 torek 创建的主题 » 使用通配符来git show

git show 接受 gitrevisions ,不包括路径规范。它确实允许路径,如图所示 here 在你成功的指挥下。

fatal 错误,我明白了 吉特秀

$ git show 'b5101f929789889c2e536d915698f58d5c5c6b7a:*kefile'
$ git show 'b5101f929789889c2e536d915698f58d5c5c6b7a:./*kefile'

这个错误似乎更好。)

我在git结账时尝试了通配符。。。

git checkout 命令接受 路径规范 as documented not-quite-so-well here 并在 the gitglossary . 默认情况下,它们确实接受通配符匹配。

在开始之前,这里有一些有用的背景知识需要了解:

  • 仅限Git商店 ,而不是文件夹。
  • 每个Git commit存储一个 完整快照 属于 全部的 坚信的 未跟踪的文件 进来吧。
  • 指数 或者你的 中转区 隐藏物 . 这三个名字都差不多。)

提交 . Git实际上就是提交。每个提交都是数字形式的,但不是一个简单的顺序“提交1,提交2,…”方式。相反,每个提交都得到一个唯一的 ,哈希ID完全随机,与以前的提交无关。这些散列ID是由字母和数字组成的难看的大字符串,如 83232e38648b51abbcbdb56c94632b6906cc85a6 git log 吐出来。

自从 每一个 每一个 提交,对于Git来说,重要的是以一种不会立即耗尽整个磁盘驱动器的方式保存它们。所以保存的文件被压缩,而且, 共享 跨越不同的承诺。Git可以做到这一点,因为它使用一种特殊的、仅限Git的、冻干格式来存储文件。此表单中的文件 ,但可以共享。这意味着 不能更改现有提交 . 存储库中的提交或多或少是永久存档的。 1个 认为承诺是永久的 是)并且是不变的。他们


1个 可以删除提交,但有点困难,Git通常不会立即执行,即使您 一个承诺已经没有了,现在找不到,可能还在里面。


现在,这对于归档来说是很好的,但是这些只读的冻干文件对于实际的 工作 工作树 . 那只是你工作的地方。

在工作树中,Git从一些commit中提取冻干的文件,对它们进行再水化,使它们具有正常的日常形式。现在您可以查看和处理这些文件。你只需选择一个承诺,即 最后的 承诺一些 分支 给我那个承诺 ,Git就这么做了。它查找冻结提交并枚举其中的所有文件:

  • main-folder/sub1/file1 : 吉特说, 这个工作树没有 main-folder ,我们来做一个。它没有 sub1 主文件夹 主文件夹/sub1/file1 .
  • main-folder/sub1/file2 : 吉特说, 已经有一个主文件夹/sub1,我可以创建新文件 file2 在里面。

文件夹 ,如提交中所列,它必须重新构造。完成后,如果工作树 文件夹 已经储存了,但没有必要储存。

如果你现在从 那个 提交到另一个提交,Git将 去除 它为该提交创建的所有文件,并用其他不同提交的文件替换它们。如果它移除 全部的 文件来自 main-folder/sub1 主文件夹/sub1 . 如果最后把里面的东西都拿走了 ,它也会删除。然后从提交文件中提取所有文件, 创建 任何需要的目录/文件夹。

a123456... 承诺 b789abc... ,两次提交中99%的文件是 ,好吧,毕竟没必要在工作树上和他们混在一起,对吧?在这种特殊的形式下 git checkout ,Git在切换提交之前添加安全检查: 如果文件是“干净的”,则可以安全地删除或替换它。如果它是“脏的”,如果您在Git提取它之后更改了它,并且您可能希望保留您的更改,那么切换会使clobberGit对此发出警告,并且在默认情况下拒绝切换提交。

在这个过程中有一个巨大的鸣音皱纹。通读以上,你会想: 但是Git有第三个实体 之间

与提交本身一样,索引基本上是不可见的。实际上只是一个普通的文件, .git/index 在大多数情况下,这最终会变得更复杂,但它开始只是一个普通的文件。文件中的内容实质上是您提取的提交的副本所有冻结的文件,仅使用散列ID(如提交散列ID)来标识它们。不过,与提交中实际冻结的文件不同,索引中的副本 被改变。

这就是 git add 它会把文件冻干然后粘起来 索引中的版本。如果文件 不是 在以前的索引中,现在是。如果它 在之前的索引中,这将踢出以前的版本。 无论哪种情况,新的冻干文件都可以提交。 当你跑的时候 git commit ,Git只是将所有准备就绪的文件从索引打包到新的提交中。那是 git提交

path/to/file1 这些 path/to/file2 冷冻干燥内容物等。但不管怎样,文件的存在 在里面 跟踪 . 一个 文件在索引中,所以 一个 文件只是工作树中的任何文件,而不是索引中的任何文件。 自从 git提交 指数 跟踪 提交文件。


这里棘手的部分是,将一个新文件放入索引实际上是冻结干燥文件并将其存储在 存储库 ,如果新内容确实是新的,则创建新的哈希ID;如果冻结的内容与任何现有文件匹配,则共享某些现有哈希ID。现在,这个被认为是新文件的文件被简化为一个散列ID,它与旧文件所占的索引位置相同!


这样一来,答案就很简单了

进行新的提交,在该提交中存储 只有 某些文件,只要设置好 里面只有那些文件。要做到这一点, 去除 从索引中删除所有完全不需要的文件,这也将删除工作树副本:

git rm ...

相对于工作树顶部的路径 ,您将希望保存所有要保存的文件。最简单的方法就是 它们都在工作树上

git mv main-folder/sub1 sub1

它将(在本例中,通过重命名)创建 第1款 如果需要,请将文件夹重命名为 索引中的文件记住 git mv 主文件夹/sub1/file1 等有路可走 sub1/file1 重命名 git rm 命令,然后拖动工作树文件。

(当 就地重命名文件夹,也可以重命名任何 里面的文件。因为Git的其他部分对未跟踪的文件并不感兴趣,稍后 git签出

姓名 提交必须存储全名和散列ID,并且 不能轻易在这里分享,因为它们是不同的 但实际内容与其他提交中具有不同名称的文件共享。

注意,当您在这个提交之间来回切换时 sub1/文件1 键入名称,以执行任何具有 主文件夹/sub1/file1 键入名称,Git可能不得不在工作树上使劲搅拌,删除所有 先命名,然后创建新的空 主文件夹 主文件夹/sub1 要保存的目录(最后相同!)以前的文件 sub1/文件1 重命名 工作树中的那些文件,Git可能会这样做,但是Git通常以一种简单而愚蠢的方式开始,就是删除并重新创建它们。这将显示在操作系统级的文件时间戳中:如果Git删除一个文件并重新创建它,它将“现在”作为其磁盘上的工作树文件时间戳。


在commits内部而不是indexGit中,可以直接返回到树结构的命名方案。所以如果 第1款 在这个新提交的顶层中,与 那是在 主文件夹/sub1 在其他一些提交中,Git实际上将共享底层 对象 新提交的根目录树的子目录树。当然,根树的名称会有所不同 第1款 作为它的一个子树,而不是名字 作为它的一个子树。但所有这些都只是实现细节:没有一个显示在索引和工作树中。

5 年前
回复了 torek 创建的主题 » 如何撤消Git主分支签出

(从评论中复制,为回答问题编辑了一点)

在这种情况下,第一步是:不要惊慌!你 坚信的 ,所以代码应该还在里面。现在的问题是 它。

git reflog 输出。我怀疑(并且评论后续证实)你所做的是使用Git所称的“分离的头”进行一次提交。当您 git checkout 一些分支,像是被抛弃的承诺,一般来说,他们 默认情况下至少还要30天。

从命令行解释器 bash cmd.exe 或者powershell或者其他什么 git刷新 . 仔细查看每个提交消息。如果其中任何一个是您提交的内容,请获取左侧的缩写哈希ID,然后使用:

git branch newname hash

为那个散列ID创建一个新的分支名称。这个新名称现在允许您访问在“分离头”模式下所做的提交,以及任何以前的提交。它现在也应该出现在所有的图形用户界面中,比如Git GUI和Visual Studio代码(尽管我从未使用过VS)。

犯罪人的身份(或者,就此而言, 任何 Git中的对象)是其内容的加密校验和。提交的内容包括作者和提交人的姓名和电子邮件地址。如果您进行的另一个提交与具有未隐藏名称和/或电子邮件地址但名称和/或电子邮件地址被隐藏的提交完全相同,则该另一个提交是 不同的承诺

其结果是,您有两个完全不同的、不兼容的存储库。这是一个维护噩梦。它 可以

5 年前
回复了 torek 创建的主题 » 如何理解交叉Git日志?

这将是一个评论,但没有空间,它需要格式化。

此时的部分:

| | | *   27ac7b7 merge
| | | |\  
| | |_|/  
| |/| |   
| | | * 95b2c48 (origin/f-3) 
| | | * xxxxxxx 

| | | * xxxxxxx 
| | |/
| | * yyyyyyy
| |/
| * zzzzzzz
|/
* sssssss

(但我们不能真的猜测,而且可能要复杂得多)。

有了足够多的附加图,我们可以进一步了解commit 27ac7b7 95b2c48 (上面有标签 origin/f-3 ). 我们在这里看不到第二个父提交的散列ID,但是沿着图行,您最终会到达第二个父提交。

(你问的第一次犯罪 0ab11dd 是与父级的合并提交 xxxxxxx bd7278a ,跟随最初向右下降然后立即反转并向下交叉的线的结果 bd7278a型 . git log --graph --first-parent 选择 git log .)

5 年前
回复了 torek 创建的主题 » 在Git中使用远程分支的方法

从技术上讲,这些都不能让你 远程跟踪名称 1个 origin/master origin/feature/short . 他们只是 创造 master feature/short 已经 原点/特征/短 设置为其 .

在这种情况下,它是一组庞大的TMTOWTDI,因为您还可以:

git checkout feature/short

git checkout -t origin/feature/short ,和/或您可以使用 git branch 要在任何开始提交时创建分支名称,请使用 git branch --set-upstream-to 设置所创建名称的上游。

一旦你用过 git checkout git switch 要在(本地)分支(名称)上,您所做的新提交将以通常的方式更新该名称。作为 eftshift0 commented ,可以使用分离的头 任何特定的提交,包括由远程跟踪名称标识的提交。


1个 远程跟踪名称 ,根据官方的Git条款, 远程跟踪分支名称 等等。这些名称确实会跟踪其他Gits的分支名称,所以“远程跟踪分支名称”(都是一个大短语)是合适的,但将其缩短为“远程跟踪分支”,然后将其转换为“远程跟踪分支”,然后认为您可以以与(真实/本地)分支名称相同的方式使用其中一个分支名称,你不能。

(那,还有这个可怜的词 分支 很快就被打败了。我们已经用了太多的词,比如 ;我们可以友好地省略这个词 分支 完全没有失去意义。)

5 年前
回复了 torek 创建的主题 » 无法解决与git子模块文件夹的合并冲突

问题是 library 已在两个分支中修改,与合并基提交相关。这里的技巧是理解子模块被“修改”的含义。

记住,任何子模块的本质都是您的超级项目 其他一些Git存储库。另一个Git存储库 作为 子模块不知道它被用作子模块。它只是一个普通的旧Git存储库。 1个 它有承诺。任何一个提交的真实名称都是它的哈希ID 使用 此子模块运行Git命令 在里面 告诉它的子模块 git checkout 一些特别的承诺 通过 哈希ID,导致子模块Git位于 分离头 模式。

同时,回到超级项目:这个存储库是一个普通的Git存储库。它有承诺。每个提交的真实名称都是一些散列ID,不过通常 git签出 某个分支名称。分支名称将解析为某个特定的hash ID,您的超级项目Git将通过分支名称检查提交,例如。, develop 现在做一些特别的事情,例如。, a123456... . 所以假设你是在承诺 a123456 .

某处 在里面 犯罪 a123456号 ,有一个类似文件的对象实际上不是文件,而是 gitlink公司 . 提交中的这个GITLink对象包含子模块中存在的一些提交的原始哈希ID。在您的例子中,这个gitlink是名称的条目 图书馆 它可以保存,例如。, 589a7ae5 . 那是 子模块的 承诺:如果您运行 git submodule update ,输入子模块Git并命令它 git checkout 589a7ae5 .

因此:超级项目中的每个提交都有一个名为 图书馆 这确实是一个gitlink,它存储了一些散列ID。现在您可以运行:

git checkout develop
git merge redesign

(或者反之亦然)。这个 git merge 命令已找到由分支名称指定的提交 发展 redesign ,以及第三次提交 合并基 其他两项犯罪。

这三件事都有(或缺:你有一件是 00000000 )名为 图书馆 . 这些gitlinks中的三个hash id都是不同的。Git正在尝试合并两个差异:

  • 有人说 从合并基中的gitlink X开始,用hash ID Y替换该gitlink .
  • 另一个人说 从合并基中的X开始,替换为Z .

这两个方向用Y代替,用Zconflict代替。Git不知道哪一个是正确的(如果事实上任何一个都是正确的)。

为了解决这个特殊的合并冲突,您的工作是选择 对的 子模块的哈希ID。一旦你知道正确的散列ID 怎样 你发现这取决于你 2个 您只需临时切换到子模块Git存储库并运行 git checkout hash ,然后返回超级项目并运行 git add library . 您的超级项目Git现在在gitlink中记录将进入合并提交的新散列ID,合并冲突得到解决。

当您解决了所有合并冲突(包括普通文件冲突)并准备提交合并时,请运行 git merge --continue git commit 完成合并。合并提交将具有 它的 的gitlink 图书馆 ,添加的哈希ID。

(如果正确的做法是完全停止使用子模块,则可以 git rm library 而不是先签出正确的子模块哈希,然后 git add 名字)


1个 这跳过了一些技术细节,通过这些细节可以发现这个子模块存储库位于某个超级项目中。重要的是,大多数Git都没有意识到这些细节:子模块 思考 它是独立的。此外,典型的子模块Git repo是其他Git repo的克隆,并且 其他 Git回购 完全不知道克隆,所以即使你使用的克隆子模块知道它是一个子模块, 它的 origin 仍然不知道。

2个 查找正确散列ID的典型示例方法开始于:

cd library

接着是各种各样的 git log 和/或 git show 和/或 git签出 命令,或者 gitk --all 或者您想用来查看Git存储库的任何东西。最后,你发现了一些看起来不错的散列ID并运行 git签出 在它上,以便更新此存储库中的工作树,然后 cd 退出子模块并构建和测试项目。这个过程自然会让子模块处于正确的提交状态,这样您就不必重新- git签出 正确的哈希ID。

6 年前
回复了 torek 创建的主题 » Git是按文件还是按目录合并信息?

两者都不是。

Subversion在这里有点奇怪,因为它建立在一个系统之上,这个系统从根本上处理 目录 . 这意味着您可以使用Subversion签出一个特定的目录well,类似于:您也可以得到它的子目录。但它以一个目录开始:因此是目录级的合并信息。

Git的基本单元既不是文件也不是目录,而是 犯罪 . 你不能得到少于一个完整的承诺。 1个 当你跑的时候 git checkout commit-specifier ,Git复制 整个提交树 在索引/暂存区域这占用的空间相对较小;请参见脚注1将所有这些文件从索引复制到 工作树 你可以在那里工作。

每个Git提交都是 全部的 项目中的文件,或者更准确地说,提交时索引中的所有文件。由于索引以当前提交中的所有文件开始,因此它在下一次提交中也将继续拥有所有文件。合并提交与其他提交没有什么不同:它拥有所有的文件,在它们的全部和完整的荣耀中,但是压缩、冻结和Git化,任何提交中的所有文件都是压缩、冻结和Git化的。因为它们是冰冻的你不能改变 任何 部分 任何 承诺,永远 共享 如果它们没有改变,那么如果你提交一个100兆字节的文件10次,甚至100万次,那么 文件 使用的磁盘空间量与只提交一次100兆字节的文件相同,因为实际上 . (每次提交都会在文件顶部为提交的元数据添加一点空间,但是如果您熟悉 链接 在Unix/Linux系统上,您可以将每次提交都看作与一个底层文件有一个硬链接。)

同时,事实上 通过 起源 一个或多个哈希ID存储在每个提交中。每个提交都会记住原始散列ID(s)的真实名称,因为它是紧接着这个特定提交之前的提交的真实名称。对于大多数提交,只有一个这样的散列ID,我们最后得到一个向后看的链,因此如果我们从 最近的 提交其哈希值为某个大而丑陋的哈希值,我们将只调用该哈希值 H ,我们可以找到它的父级,我们将调用其散列 G . 我们可以用 找到 ,我们称之为 F ,然后使用 F型 寻找 F型 的父级,依此类推:

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

我们只需要知道这个问题的答案: 的哈希ID是什么 最后的 在这个链条上承诺? 为了找到答案,我们查了 分行名称 例如 master . 这个 名称 包含 最新的 仅此而已!其他一切都来自承诺。

合并提交的唯一特殊之处是它至少记录 父哈希:

...--F--G--H
            \
             M--...
            /
...--J--K--L

合并信息在于 M 有两个父母, 小时 L . 从 我们要么回去 小时 ,然后沿着那条链条往回走,或者我们可以回到 然后沿着那条链子往回走。三项承诺 , 小时 ,和 是完整的快照,所以如果我们想了解顶级链如何提交 小时 通过合并底部链进行修改 投入其中,我们可以比较 小时 . 如果我们想看看下链如何提交 通过合并顶级链提交进行了修改 小时 我们可以比较一下 . 这些是 相同的 例如,当我们想知道 小时 修改:我们比较 小时 ,这是两个快照,以查看 小时 .

如果我们想先看看这两条锁链在哪 发散的 ,我们只需要更广阔的视野:

...--E---F---G----H
      \            \
       \            M--...
        \          /
         I--J--K--L

如果 F型 的父级是 E ,和 J 的父级是 I 他的父母是 E类 ,然后我们可以看到 E类 是我们合并时的合并基地吗 小时 . 因为所有提交都会被冻结,如果 E类 当时是合并基地, E类 现在仍然是合并基础。

这意味着你的问题的答案是: 根本没有显式的合并信息。存在合并以及重复合并所需的信息这一事实由提交图暗示。 提交图是您(或Git)通过读取各个提交而派生的数据结构。因为commit是基本单元,所以总是有一个完整的commit。 2个 在一个 浅的 克隆,你被允许失踪 早期的 提交,但您可以通过返回克隆的位置并“取消”克隆来填充这些内容(或者将其深入到足以看到需要看到的内容)。


1个 Git确实支持 稀疏签出 ,但你还是得到了全部承诺。 全部 它的文件仍会复制到索引中。稀疏签出仅限制将哪些文件从索引复制到工作树。由于索引副本大多是冻结的,并且可以直接共享提交中的副本,这就减少了所需的磁盘空间量,因为有些文件永远不需要从其提交中冻结的格式中解压出来。下一次提交(如果您确实进行了另一次提交)是从索引中的内容进行的,而不是从工作树中的内容进行的,因此新的提交将继续 全部的 尽管签出的文件很少。

2个 Git添加了一种允许占位符的思想,其形式是 承诺人包 ,其中可以有一个提交,但缺少一些内部数据。也就是说,提交本身仍然是基本单元,但是您将知道提交 C类 有树 T型 ,您可能缺少对象 T型 直到有人明确要求它,在那一点上,Git会试图通过打电话给做出承诺的人来实现承诺。这实际上还不在Git中,但它正在慢慢进入代码库。

注意,这有点类似于浅层克隆。对于浅层克隆,您知道提交 C 有父级 第页 ,但你缺少对象 第页 因为有一个浅的移植物使得Git的某些部分假装 C 没有父母。使用 git fetch --deepen= number ,你可以让Git获得 第页 ,但也许是一些祖父母 第页 . 这只是用一个新的移植点替换了那个祖父母的浅移植点,或者如果你已经得到了所有的父母,比如,通过 git fetch --unshallow .

5 年前
回复了 torek 创建的主题 » 对git头的质疑

在技术上,但完全是具体的, HEAD 实际上是一个文件, .git/HEAD . 它包含:

  • 分支机构的名称,或
  • 提交的哈希ID。

什么时候? 头部 包含提交的哈希ID,仅此而已。Git称这种情况为 分离头 唯一可用的信息是提交的散列ID。

什么时候? 头部 包含分支的名称,但是,现在有两种方法可以询问Git:

  • 嘿,吉特,告诉我什么散列ID 头部 名字。 吉特看着 头部 ,看到它的名字 master ,然后看 主人 查看什么散列ID 主人 名字。这就是你问题的答案。

  • 或者, 嘿,吉特,告诉我哪个分支 头部 名字。 吉特看着 头部 ,看到它的名字 主人 ,并告诉你 主人 作为你问题的答案。

分行名称 始终只包含一个提交的哈希ID,因此如果 头部 包含分支名称,Git始终可以执行 头部 -通过执行这两个步骤来散列ID。什么时候? 头部 但是,包含原始哈希ID,如果试图询问Git所包含的分支名称,则会出现错误:

$ git checkout --detach master
HEAD is now at 83232e3864 The seventh batch
$ git symbolic-ref HEAD
fatal: ref HEAD is not a symbolic ref

或者,使用 git rev-parse --symbolic-full-name ,只是指纹 头部 :

$ git rev-parse --symbolic-full-name HEAD
HEAD

重新连接 头部 主人 使它们都产生分支名称:

$ git checkout master
Switched to branch 'master'
$ git symbolic-ref HEAD
refs/heads/master
$ git rev-parse --symbolic-full-name HEAD
refs/heads/master

所以就这样 头部 可以是分支名称或提交哈希,除非它只能是提交哈希。

6 年前
回复了 torek 创建的主题 » Git完全删除未合并的根节点

从你的文字描述来看,我 认为 您要说的是,您有一个单独的存储库,其中包含两个独立的(不相交的,技术上的)提交子图。例如,这里是这样一个存储库的图表:

     C--D
    /    \
A--B      G--H   <-- branch1
    \    /
     E--F   <-- branch2

I--J--K--L   <-- master
       \
        M--N   <-- develop

这个特殊的图有四个“入口点”,即提交 F H 在根于的子图中 A 并承诺 L N 在根于的子图中 I .

虽然这样一个存储库没有本质上的错误,也没有被破坏,但是将它拆分成两个独立的存储库相对容易。简单地从两个克隆开始,它们都是这样的。(您可以使用 git clone --mirror 以生成保留所有引用的镜像克隆。一定要删除他们的 origin 这样它们就不会同时指向原始的组合存储库。)

在一个这样的克隆中,如果存在任何其他引用,则删除任何外部标记分支名称、标签名和其他引用。 两个子图中:

     C--D
    /    \
A--B      G--H   <-- branch1
    \    /
     E--F   <-- branch2

I--J--K--L   [abandoned]
       \
        M--N   [abandoned]

请确保包含指向有意放弃的子图的任何标记或其他名称。正常的图形查看命令,如 git log ,将不显示未引用的子图:它将 出现 将被移除,尽管它仍然存在。最终,未引用的子图将消失,或者您可以使用 git gc . 克隆 此存储库将没有未引用的子图。

在其他两个克隆中,删除对 其他 子图:

     C--D
    /    \
A--B      G--H   [abandoned]
    \    /
     E--F   [abandoned]

I--J--K--L   <-- master
       \
        M--N   <-- develop

和以前一样,未被引用的子图最终会消失。

注意,原始的两个独立子图存储库的任何克隆 可以 习惯于 git push 任何一个 这些分裂的克隆体。因此,任何第三个完全独立的存储库都可以 用于推送到这两个克隆体中的任何一个。为独立子图中的提交添加名称的任何推送都将导致整个独立子图进入推送的接收者。 As you surmise in a comment ,我怀疑这就是这种情况最初的起因。

您可以添加一个pre-receive钩子,该钩子拒绝添加新根提交的新名称,尽管据我所知,此表单没有方便的pre-receive钩子。做起来容易,但速度慢:快跑 git rev-list --all --max-parents=0 --count 计算现有根和第二根 git rev-list --all --max-parents=0 --count <hash> 若接受来自 git推送 . 如果计数增加,新的推送将添加一个新的根。

注意,可以添加一个新的根 不相交子图的一部分。例如,考虑“before and proposed after”图:

before:
A--B--C  <-- master

after:

A--B--C--F--G  <-- master
        /
    D--E

这种预接收钩子会拒绝这样的推送。这可能是你想要的,但可能不是;小心你的程序。:-)

5 年前
回复了 torek 创建的主题 » 可视化git分支依赖关系

重要的是要认识到 姓名 其实没什么意思。

分支名称只是指向提交的标签或指针把它们想象成黄色的便签,甚至 sticky arrows ,可以粘贴到提交上是那个 提交 那很重要提交的标识是其哈希ID。提交是永久的 不可更改,并且每个提交都记录其直接父级(大多数提交)或父级(合并提交为2个或更多)的哈希ID这就是为什么 提交图 ,但你总是可以 添加 对它来说,是永恒不变的。

图形本身决定了存储库的实际“分支度”:

(older commits towards the left, newer ones towards the right)

          o--A
         /
...--o--o
         \
          o--o--B

图的这一部分显示了两个分支,以两个结束 提示提交 A B . 添加新的提交过去 一个 给了我们 C ,它会记住 一个 作为其母公司:

          o--A--C
         /
...--o--o
         \
          o--o--B

如果我们现在添加 合并提交 我们称之为 M 对于父母是 二者都 C类 ,我们得到:

          o--A--C
         /       \
...--o--o         M
         \       /
          o--o--B

需要两个标签两个粘滞的便条箭头才能记住这两个 一个 ,或两者都有 C类 ,但现在我们有了 ,我们可以丢弃其中一个标签,只需保留一个指向 本身:

          o--A--C
         /       \
...--o--o         M   <-- branch
         \       /
          o--o--B

这就是分支名称的作用:它是一个指针,指向 一个 提交,哪个Git将被认为是 提示 一些向后看的提交链的提交当Git向后工作时,从这个tip commit开始,Git将遍历 全部的 合并的分支,以查找通过向后工作可以到达的所有提交自从 有两个父母 C类 ,Git将访问commit C 然后 一个 等等,还有 然后所有人都向左边承诺 .

当然,我们可以让标签指向 一个 和/或 和/或 C类 :

                .... <-- tag: v1.0
               .
              .
          o--A--C
         /       \
...--o--o         M   <-- branch
         \       /
          o--o--B   <-- feature

如图所示,标签不必 分支 名字分支名称的特殊功能是通过“on”分支,使用 git checkout name ,任何 新的 我们所做的提交将自动更新名称以指向新的提交新的提交将指向上一个提交。

这就是背后的概念 git branch --contains 也是因为承诺 可从 犯罪 ,从 向后退一步, git branch --contains specifier-for- B 会把名字打印出来 branch . 这个名字指向 达到 . 更一般地说,Git反复询问并回答问题:

  • 是承诺 X 犯罪的始祖 Y ?

git branch --contains anything that finds some commit ,Git询问有关每个分支名称的每个分支提示提交的问题:

target=$(git rev-parse $argument) || exit 1

git for-each-ref --format='%(refname)' refs/heads |
    while read fullbranchname; do
        branchtip=$(git rev-parse $fullbranchname)
        if git merge-base --is-ancestor $branchtip $target; then
            echo "branch ${fullbranchname#refs/heads/} contains $argument"
        fi
    done

如果 $argument 分支名称是 分支 ,将其转换为哈希ID的操作将选择提交的哈希ID . 然后手术开始 refs/heads/feature 在哈希ID中选择 ,和 git merge-base --is-ancestor 回答问题 是承诺 犯罪的始祖 (事实确实如此)。

你的要求有点复杂:你希望,对于每个分支名称,不仅要确定 此分支的提示提交是否早于某个选定分支的提示提交 (作为 git分支--包含 是的),但是 对于每一个这样的分支名称,哪一个是更多的祖先y,哪一个是更少的祖先y . 这个问题 可以 得到回答,但是 只是在某些情况下 .

例如,请考虑以下图表:

...--o--o--o--o---M1--M2-o   <-- master
      \     \    /   /
       \     o--o   /  <-- feature1
        \          /
         o--o--o--o   <-- feature2

我想你想要 feature2 被称为“后” feature1 ,因为合并提交会带来 特点2 在合并提交之后出现 特点1 . 但合并并不总是带来 共同承诺。 考虑:

          o--o--o   <-- feature1
         /       \
...--o--o--o--o---M1--o   <-- master
         \       /
          o--o--o   <-- feature2

现在没有明显的顺序: 特点1 特点2 两人都是同时被带进来的,是一次犯罪。

(当然,如果我们删除其中一个 feature 名字,这就解决了问题如果我们删除 二者都 名字,更容易解决!)


可以 通过删除所有访问权限来删除提交 承诺Git从已知的namesbranch和标记名开始,所有其他形式的referenceandworkbackwardthroughthegraph如果此向后工作到达提交,则必须保留提交如果没有,提交就有资格被删除除了明显的名称之外,还有其他名称,这些名称通常会使提交活动至少延长30天。

我们可以消除 git rev-parse 打电话是因为 git merge-base 接受一个分支名称来代替散列ID。当给定一个名称时,它只会找到提交散列,同样的方式 git版本分析 会的我把它们放进去主要是为了说明。

要产生这种排序,您必须编写自己的代码注意,这并不像比较不同分支的tip提交那么简单;我们需要知道哪些合并(如果有的话),引入那些tip,并比较合并我们还必须考虑通过将其尖端嵌入非merge-y子图而形成的分支:

             ..... <-- branch3
            .
...--o--o--o--o    <-- branch1
         .
          ........ <-- branch2

在这里,我想我们要索赔 branch2 < branch3 .

即使在一对一次合并的情况下,您也可以重复地将一些commit与一些主线合并,这会给问题带来麻烦(在 可能的顺序,但您必须选择一些图遍历算法来选择此处的总顺序)本质上,记住当你和 DAG ,你正在处理一个 poset .

5 年前
回复了 torek 创建的主题 » 如何从GitHub存储库下载最新的文件?

有多种方法;有些是GitHub特有的特别是github提供了一个web服务端点,可以从一个特定的提交中下载一个特定的文件,您可以通过分支名称引用提交(这样github将为您解析提交散列ID):

https://raw.githubusercontent.com/git/git/master/checkout.c

比如说你 checkout.c 不管做什么 master 在Git存储库中标识 github.com/git/git .

如果你担心克隆,尽管:

我不想每天都克隆整个文件夹。

首先,你不能克隆 文件夹 是的。你克隆了一个 存储库 是的。一旦你这样做了,你就 全部的 承诺 全部的 文件,第二天,你就可以运行 git fetch 在这个存储库中 新的 在保留所有现有提交的同时提交。git在这些更新方面非常有效。根据所涉及的JSON文件和其他存储库活动,这可能是 更快 而不是下载一个原始文件。

如果存储库是纯只读的,并且上游表现良好,则可以运行 git pull 而不是 git获取 然后是第二个git命令。第二个git命令是 git merge ,和 吉特拉力 方法 运行 git获取 ,然后运行第二个git命令 第二个命令默认为 合并分支 是的。本例中的合并步骤将是 快进 操作。

(如果上游存储库是 表现好,你的第二个命令是 git reset --hard origin/master 你需要两个命令。)

6 年前
回复了 torek 创建的主题 » 了解git重置对索引的影响

TL;博士

索引总是更新的。指数保持不变 下一次你打算做出的承诺 ,所以它永远不会是空的。(什么,从来没有?好吧,几乎从来没有:里面是空的 新的 您刚刚创建的存储库没有文件,如果您运行 git commit 马上。它也是空的如果你 git rm 一切 (第三章)

你在这里的困惑几乎肯定与 the comment PetSerAl made 是的。那些对Git不熟悉的人经常被告知或展示,或者至少让他们相信提交和/或Git的索引包含 变化 ,但这是假的一旦你摆脱了这种不正确的信念,git的一些奥秘就开始变得更有意义了。(不是 全部的 对任何人都有意义,甚至对我。所以不用担心如果需要很长时间 grok 吉特。)

在吉特,a 犯罪 包含 所有文件的完整快照 是的。它还包含一些元数据信息 关于 提交本身,如您的姓名、电子邮件地址和时间戳。元数据中包含提交的 起源 提交,或者,对于合并提交,多个父级,复数形式 比较 向他们的父母承诺git会向您显示更改。每个提交都有自己唯一的散列ID,例如 8858448bb49332d353febc078ce4a3abcc962efe (这是git存储库中git的提交的id)。该提交是快照,但该提交有父级(在本例中, 67f673aa4a... ),所以Git可以 显示 8858448bb4... 通过提取 二者都 较早的 67f673aa4a 8858448bb4 ,然后比较两者这个 git show 命令就是这么做的,所以你看到的就是 改变 在里面 8858448bb4号 ,而不是什么 在里面 8858448BB4号 是的。

(这就像告诉你今天比昨天暖和或凉爽5度,风或多或少,而不是把天气当成一堆数字数据库存储绝对值,但我们主要想知道它是否更好。)

索引存储下一次可以执行的提交

您可以通过各种方式看到git的提交,当然也可以按照它们的hash id命名它们,正如我在上面所做的那样。你可以看到你的 工作树 这就是git允许您直接查看和编辑文件的地方:在您的计算机上,它们以正常的日常形式存在。但你不能 看见 指数很好。有点看不见这是个问题,因为它也很关键。

大多数版本控制系统根本没有索引,或者如果有类似的索引,就保留它 好隐蔽 你不必知道但是吉特做了一件奇怪的事 强迫 你要了解git的索引,同时还要把它隐藏起来。

如果您真的想查看索引中的文件列表,可以使用 git ls-files :

$ git ls-files | head
.clang-format
.editorconfig
.gitattributes
.github/CONTRIBUTING.md
.github/PULL_REQUEST_TEMPLATE.md
.gitignore
.gitmodules
.mailmap
.travis.yml
.tsan-suppressions
$ git ls-files | wc -l
    3454

在这个Git的Git存储库中,索引中有将近3500个文件那是很多文件这是 为什么 Git把它隐藏起来:里面有太多东西无法理解。

但这也是 为什么 Git通过将他们与他们的父母进行比较来显示我们的承诺显示 全部内容 8858448bb4号 太多了,所以 git show 8858448bb4 告诉我们什么 改变 在里面 8858448bb4号 ,与其父对象的比较git对索引采取了相同的策略,向我们展示了我们所拥有的 改变 而不是把整件事都扔了。

我认为,这正是人们认为Git正在存储更改的原因吉特 显示 更改,因此git必须 储存 他们但不是Git存储所有快照吉特 算出 每次你让Git给你看东西的时候都会有变化。

考虑到这一点,让我们看看 看见 索引。

索引位于 之间 当前提交和工作树

我们现在知道,每个提交都是一个完整的快照。如果Git在我们每次提交文件时都为每个文件创建一个新副本,那么存储库将很快变得非常大所以它不会这么做,而且 方式 但这并不简单。每次提交时 完整的快照,文件 里面 每次提交都是完全、完全、100%只读的。他们谁也不能 曾经 改变。这意味着每次提交都可以 分享 它的一些或所有文件与一些早期提交!

Git只需要确保每次我们运行 git提交 ,它 冻结 所有的文件内容,如果不是永远的话,至少只要这个新的承诺继续存在。所以每次提交中的文件都会被冻结他们也是 压缩的 变成一种特殊的git-only格式(对于文本文件非常有效,但对于像图像这样的二进制文件通常不太好)。这种压缩需要时间,有时需要很多时间,但它使存储库保持较小。

显然,冻结的git-only文件只对git本身有用,因此我们需要 现在的 提交取出,解冻,解压缩,并使有用这些有用的拷贝 工作树 ,我们工作的地方。

其他版本控制系统也做同样的事情。在假设的XYZ版本控制系统中,运行 xyz checkout commit 它从deep freeze warehouse中复制提交,解冻,解压缩,并将其存储在您的工作树中你做了一些工作,最后你跑了 xyz commit 是的。现在它会扫描整个工作树,重新压缩每个文件,冻结它,并检查仓库中是否已经有了冻结的版本,或者还需要将这个版本放入其中当你去喝咖啡或其他什么的时候,每一个步骤都需要几秒钟或几分钟。

git使用它的索引所做的是非常聪明的:索引是 中转区 ,介于deep freeze warehouse(包含提交的存储库)和有用表单(工作树中解冻的文件)之间。最初,它包含 相同的 文件在深度冻结它们已经解冻(有点),但仍然是特殊的git-only形式,并且它们与工作树中完全解冻的解压版本配对。

就像你一样 改变 工作树中的文件,或添加和/或删除文件,索引副本将与工作树不同步现在吉特可以 比较 索引副本到工作树副本,并告诉您 已更改但尚未上演 是的。

一旦你有了你想要的文件,你就可以运行 git add file 是的。这个 重新压缩文件 ,并将该副本放入索引中现在,索引副本(这是一个完整的副本,只是压缩的)与 工作树 复制,但与 坚信的 收到。

在任何时候,你都可以让Git比较 坚信的 ( HEAD )将每个文件的副本复制到 指数 副本:

git diff --cached

对于相同的文件,Git什么也不说对于不同的文件,Git列出了文件并显示了不同之处。

类似地,在任何时候,您都可以让git比较 指数 将每个文件的副本复制到 工作树 副本:

git diff

对于相同的文件,Git什么也不说对于不同的文件,Git列出了文件并显示了不同之处。

(注:增加 --name-status git diff 显示文件名,前缀为 M 对于修改过的,如果修改过的话Git使用 A 对于新添加的文件, D 对于已删除的文件,等等。文件是 删除 在索引中,只需将其从索引中完全删除文件是 补充 在索引中,如果它在索引中,但不在 头部 .)

这个 git status 命令运行 这两种比较 ,和 --名称状态 限制器对于不同于 头部 索引,这些是 准备提交 是的。对于索引和工作树不同的文件,它们是 不准备提交 .


形象地说:

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

这个 头部 副本被冻结,因为它处于提交状态索引和工作树副本可以更改,但最初, 三个都匹配 . 更改工作树副本并使用 git add 复制回来 进入之内 索引,压缩并生成它(如果“en Git ing”是一个单词,则不是)如果你根本不想在索引中更改它,你可以使用 git reset (违约 --mixed 操作,或它在任何单个文件上的工作方式)将冻结的文件复制回索引中。

这也是为什么 Git提交 是如此之快,相比之下 XYZ提交

当你跑的时候 Git提交 ,git已经拥有了所有将以正确形式提交到新提交中的文件。它不必重新压缩所有的工作树文件,看看它们是否与冻结的提交版本匹配这个 指数 所有这些都准备好了:它所要做的就是冻结索引副本,如果这与之前的提交相同, 分享 上次提交的文件。

此外,由于索引“知道”哪些文件与工作树匹配,哪些不匹配, 还有关于存储库中内容的额外信息,这使得 git checkout 速度也更快假设你在 master 有大约3500个文件,而你 git签出 另一个分支大约有3300个文件都是完全相同的两次提交之间大约有200个文件不同(可能有一些是新的或删除的)。Git可以使用 指数 要知道它可能需要在 工作树 ,并且完全避免接触那些大约3300个文件。

因此,代替xyz系统扫描,可能接触3500个文件,git扫描,可能接触200个文件,节省了超过94%的工作。


这通常需要扫描工作树索引保存( 缓存 )数据 关于 工作树,以便加快速度。这就是为什么索引有时被称为 隐藏物 . 其他vcse,比如Mercurial,有一个工作树缓存(Mercurial称之为 dirstate公司 ,但与git的索引不同,它被正确地隐藏了:您不必知道它。

5 年前
回复了 torek 创建的主题 » target/vs/target/in.gitignore

你可能想要 /target/ .

让我们从一个清晰的定义开始 工作树 (来自 the gitglossary ,拼写的位置 working tree ):

实际签出文件的树工作树通常包含 HEAD commit的树,加上您所做但尚未提交的任何本地更改。

我们需要记住,git存储的内容以及与其他git存储库的交换是 提交 是的。每次提交都会冻结一些文件集,以便将来随时可以告诉git 让我承诺 a123456... 把你提交的文件都拿回来 123456… 是的。(每个提交都有一个唯一的、大的、丑陋的hash id,如下所示 git log 输出和其他方面。)

提交与工作树

提交中的文件以一种特殊的、仅Git的、压缩的、重复数据消除的只读形式存储。我喜欢把这些文件 冻干 . 他们真的 不能 被改变。所以它们可以存档,但完全没用 工作 完成。因此,Git需要能够 提取 任何给定的提交,“再水化”冻干的文件,并把它们变成普通的日常文件,你可以看到,使用和使用。你放这些文件的地方是 工作树 工作树 是的。

当然,工作树有一个顶级目录(或者文件夹,如果您喜欢这个术语的话),您可以在其中存储各种文件,包括主目录 .gitignore 文件顶层目录可以有子目录(子文件夹),每个子文件夹可以有自己的子目录 .gitignore命令 文件也是。当你问起 /target target ,例如。

GitIgnore条目

中的条目 .gitignore命令 文件可以采用以下任何形式:

  • name (没有特殊字符,如 * )
  • name.* *.txt 甚至 name*txt
  • folder/
  • folder/*
  • folder/name
  • folder/name*txt 或是这些变体中的任何一个
  • folder/subfolder/
  • folder/subfolder/*
  • 上面的任何一个前缀是斜线,例如, /name /folder/ /folder/name
  • 以上任何一项,包括以斜线为前缀,然后以斜线为前缀 ! ,例如, !/folder/name

这不是故意的 详尽的 列出(您列出了其他几种形式),而不是说明一些基本原则:

  1. 一个简单的文件名意味着 具有此名称的任何文件或目录 是的。
  2. 名字 后缀 用斜线表示 具有此名称的任何目录(文件夹) 是的。作为文件的实体与此类条目不匹配。
  3. 条目可以有 嵌入的 斜线不在前面,也不在后面的斜线,例如 文件夹/名称 是的。
  4. 条目可以有 主要的 斜线,如 姓名 /文件夹/ ,或 二者都 前导斜线 嵌入斜线,例如 /文件夹/名称 是的。
  5. 条目可以有 地球仪 人物, * ** ,在不同的地方。
  6. 条目的前缀可以是 ! 是的。

gitignore条目的规则变得相当复杂,但一开始就足够简单了记住 .gitignore命令 可能在工作树的顶层文件夹中,或者在某个子文件夹中!

  • 一个没有嵌入斜杠或前导斜杠的普通名称与此文件夹或其任何子文件夹中的任何文件或文件夹匹配。
  • 斜线后缀的名称,没有嵌入斜线或前导斜线,与此文件夹或其任何子文件夹中的任何文件夹(但不是文件)匹配。
  • 如果条目有斜线前缀 一个 嵌入的 斜杠-一个就足够了-条目只匹配中的文件和/或文件夹 文件夹。因此 文件夹/名称 /文件夹/名称 意思是 同样的事情: 匹配名为 文件夹/名称 在里面 文件夹,即包含 .gitignore命令 文件与文件不匹配 sub/folder/name ,例如。
  • 如果一个条目以斜线结尾,则它只匹配文件夹(与其他内容无关)。

你说:

我想排除Maven目标文件夹

这需要回答一个子问题: 这个Maven目标文件夹在哪里?只有一个这样的文件夹吗,或者 target/ 子文件夹中的实体? (还有一个单独的问题,就是 .gitignore命令 指令并不完全代表人们认为它们的意思,你需要注意你的 指数 ,但我们将把它留给另一个部分。)

如果这意味着: 不包含任何内容 目标 在我的工作树的顶层,但是请继续并包括,例如,名为 sub/target/file 那么你应该使用:

/target/

作为 .gitignore命令 在你工作树的顶层有点多余,因为你已经知道了 /目标 一个文件夹,但它清楚地表示您希望忽略 文件夹 命名的 目标 在你工作树的顶层。

如果这意味着: 不包含任何内容 build-artifacts/target/ ,然后可以放置:

build-artifacts/target/

或:

/build-artifacts/target/

进入高层 .gitignore命令 ; 你可以放:

/目标/

进入之内 build-artifacts/.gitignore 是的。里面的那个 生成工件/.gitignore 需要一个前导斜杠,因为 /目标/ 没有 嵌入的 斜线,而最高层的那个 .gitignore命令 不需要前导斜杠,因为它有嵌入斜杠。

如果,在第三只手上(第一只脚?),要求是忽略 全部的 文件 任何 文件夹路径为的文件夹 包含 目标 组件,例如,您不仅要忽略 target/file 而且 sub/target/file2 sub/target/sub2/file3 –那么您应该使用:

target/

作为你的 .gitignore命令 可能在你工作树的顶层。

索引/暂存区域的角色

这个 .gitignore命令 文件是关于 工作树 ,但git不会生成新的提交 你的工作树相反,git从它调用的中间事物(不同地,git调用 指数 或者 中转区 . (这两个术语指同一实体。)

虽然索引有其他一些角色,但它的主要角色,特别是在这里,是它保存每个文件的副本 您提取的原始提交、更新的副本或全新的文件也就是说,如果你提取了 只是 两个文件 file1 folder/file2 ,您的索引现在将具有 文件1 文件夹/文件2 在里面。

索引中的副本与提交中的副本采用相同的冻干格式。不同的是你可以 代替 索引中的副本或添加到索引中,甚至减去索引中的副本。也就是说,你可以跑 git add file1 拿走 有用的 版本 文件1 在你的工作树上,把它冻干,然后把它塞进索引里你也可以这样做 文件夹/文件2 ,你可以把 新的 像这样的文件 folder2/file3 ./file4 我也是什么 git add 简言之,就是冻结文件的工作树版本并将其填充到索引中。

当你跑的时候 git commit ,git只需将索引中的所有内容打包 就在那时 从中做出新的承诺所以你必须 Git添加 所有文件:每次更改工作树副本时,都需要更新索引副本,否则git不会保存新版本:git只会再次保存旧版本。(为了节省空间,提交保存旧文件的相同版本 再利用 旧的冻干文件他们能做到的 因为 这些文件是只读的找到一个旧副本并重新使用它总是安全的,因为根据定义,git中的所有内容都会一直冻结。只能更改索引和工作树副本!)

换句话说,可以将索引看作 建议的下一次提交 . 将文件复制到其中以更新 建议的下一次提交 . 完全删除文件 建议的下一次提交,使用 git rm --cached git rm (没有 --cached ):git将从索引中删除该文件,也可能从工作树中删除,现在您提议的下一次提交根本没有该文件。

文件可以在索引/暂存区域中 在工作树上。这种事经常发生。这样的文件叫做 跟踪 是的。内容不必匹配:只是文件在索引中 马上 ,以及在工作树中,用于跟踪工作树文件。

如果文件被跟踪–如果它在索引中 马上 那你就不用 .gitignore命令 会影响它的。为了成功 已跟踪,必须将其从索引中移除。

如果从索引器中删除文件(如果它现在不在索引中,因为它不在您先前签出的提交中),则工作树副本是 未追踪 . 现在 这个 .gitignore命令 入境事宜这个 .gitignore命令 条目告诉Git:

  • 不要抱怨这个文件。 通常情况下, git status 会对你发牢骚,告诉你文件没有被追踪,天哪,天哪,你不应该 Git添加 是吗?这个 .gitignore命令 让Git对那个文件闭嘴。

  • 不要自动添加此文件。 如果你使用 git add . git add * 或者类似的事情,你告诉吉特: 添加所有内容。 这个 .gitignore命令 将其修改为: 添加所有内容 除了 这些未跟踪的文件也会被忽略,不要添加!

  • 它有第三个效果,就是给git许可 击球员 工作树文件在某些(罕见的)情况下,并改变 git clean 使用 -x -X 是的。

真的,不应该调用该文件 .gitignore命令 ,但更确切地说 .git-dont-whine-about-these-files-and-do-not-auto-add-them-either-and-maybe-occasionally-do-clobber-or-clean-them 是的。但谁想一直打这个?所以, .gitignore命令 是的。

结论

还有更多的事情要知道 .gitignore命令 但这已经够长了(可能太长了)摘要版本为:

  • .gitignore命令 只影响 未追踪 文件夹;
  • 主要是关上抱怨,避免自动添加;以及
  • 用斜杠表示 目录 / 文件夹 (不管你喜欢哪个词)和一个前导斜杠 发现于 目录 是的。当您有复杂的条目(带有嵌入斜杠)时,前导斜杠是多余的,但会传达您的意图。

如果你 不要 想要前导斜线效果,但是 如果需要嵌入斜杠,则必须将忽略条目分发到子目录/子文件夹,或者使用 ** 与任意数量的路径组件匹配的符号(作为前导组件)。否则很少需要 ** 完全。

这里不包括:一旦Git意识到它不必 阅读 一个工作树目录,它不需要读它因此,忽略子目录通常会导致无法取消忽略(使用 ! 规则)任何东西 在内部 子目录。

这不是一个不合理的问题。但这个也是:

您可以运行:

git write-tree
git commit-tree -p HEAD -m message <hash-from-git-write-tree>
git branch -f <current-branch> <hash-from-git-commit-tree>

这也一样,如果没有错误的话:

git commit -m message

为什么我们有一个 git commit 完全是命令?

以下是一个后续问题:哪些更易于使用?


这个 git clone 命令实际上相当于运行 命令,其中五个是git命令。涉及的六个步骤 Git克隆 是:

  1. mkdir path ,使新的空目录在其中运行 git init 是的。(在您给出 Git克隆 现有的但空目录的路径名,在这种情况下,它使用。

  2. 初始化 ,以在新目录中创建新的空存储库。

  3. git remote add remote url ,其中 remote 是你的 Git克隆 选项 -o ,或是 origin 如果未指定此选项,则 url 是您在clone命令中指定的。

  4. 任何额外的 git config 提供给的选项中的命令 Git克隆 是的。

  5. git fetch remote ,以从git获取所提供url的提交。

  6. git checkout branch ,其中 branch 是您指定的 -b 你的选择 Git克隆 命令,或由 其他 git如果您没有指定分支,或者 master 如果另一个git没有指定任何分支。

这个 Git克隆 命令还处理在此过程中可能发生的各种错误,否则您必须自己处理这些错误。


这个 git pull 命令等同于运行 git fetch 然后是第二个git命令。第二个命令通常是 git merge ,尽管您可以告诉git使用 git rebase 相反。但是 吉特拉力 还有一个特别的模式:当你 吉特拉力 在一个完全空的存储库中,正如您在问题中所做的,它可以运行 git checkout 作为第二个命令而不是其他两个。


注:与 Git克隆 ,很少需要任何东西 之间 六个步骤 吉特拉力 很常见的是 需要 检查犯下的罪行 Git获取 获得 在我执行第二个命令之前。所以我通常避免 吉特拉力 完全是因为它迫使我选择要运行的第二个命令,并在fetch步骤完成后立即运行它,这时我想在fetch之后执行其他操作, 之前 我选择是使用merge,还是rebase,或者完全使用其他方法。

5 年前
回复了 torek 创建的主题 » git将分离的头推送到remote的dev分支

问题是:

我应该吗?

充满了危险,不管空白处是什么。 1个

关于你是否 可以 这样做要简单得多:是的,你可以这样做( git push origin HEAD:develop )中。这样做的目的是调用另一个git,向它们发送任何需要的新提交,比如 0091d9a 然后让他们设定 他们的 分支 develop 指向这个特定的承诺。如果 他们 我觉得可以 他们 设置 他们的 发展 ,他们会这么做的。如果他们认为这不好,他们会拒绝这个要求。幸运的是,如果他们拒绝了你的请求,他们也会告诉你 为什么 他们认为这不好。

如果他们接受了,他们就在 发展 ,以及所有已经发生在 发展 留在他们的 发展 是的。这可能是相当安全的,但是我们对其他人如何使用这个git存储库,以及您的提交了解不够,无法回答此类问题。


1个 我应该开快车吗(在“快”的意思是,比如说,50到70英里/小时或大约100公里/小时)?有危险吗?如果我在高速公路上的一辆车里,其他的车都开得很快呢?那样的话,开得很慢会有危险吗?如果我在高速公路的快车道上以每小时15英里/25公里的速度行驶呢?如果我在停车场或住宅街上开车呢?

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天的窗口是完成名称更新的宽限期。