如何在GIT中为单个文件打标签。

18

我对git非常陌生。我目前正在尝试使用它来跟踪我参与的一些连续活动的excel文件中的更改,以便熟悉它。所有文件都在一个单独的存储库中。我想要单独为每个文件标记它们的版本。这可行吗?到目前为止,我发现只能标记整个存储库。如果我所做的不正确,请指导我最佳做法。

提前感谢。

编辑

当我这样做时,我故意删除了之前的标签,使整个存储库被标记(因为我没有找到一种方法来标记单个文件)为v1.0。现在我想将标签名称恢复为文件名,并且对应的版本也已经确定好了,我该如何回滚删除并重命名之前的标签(被删除的标签)?


4
git 标记的是提交记录,而不是文件。你能告诉我们你想用这些“标签”做什么吗?如果你能回答这个问题,那么更恰当的答案就可能出现了。 - Noufal Ibrahim
6个回答

11

从技术角度来说,你可以给单个文件的内容打标签,而不需要使用文件名。但这样的标签用处有限。标签通常指向提交,而非提交的特殊标签行为与之截然不同(无法通过git checkout检出此类特殊标签)。因此,我强烈建议永远不要使用非提交标签。

如果你只想给某些文件打标签,最好将它们放在一个单独的仓库中,或者至少放在不同的分支中,因为Git在操作时总是会查看完整的树结构。

如果你仍然坚持创建这样一个特殊标签,那么可以采取以下步骤:

> git ls-tree HEAD
040000 tree 2c186ad49fa24695512df5e41cb5e6f2d33c119b    bar
100644 blob 409940768f2a684935a7d15a29f96e82c487f439    foo.txt

> git tag my-bar-tree 2c186ad49fa24695512df5e41cb5e6f2d33c119b
> git tag my-foo-file 409940768f2a684935a7d15a29f96e82c487f439

7

你只能给提交打标签,例如存储库历史记录中的某个快照。然而,Git将文件存储为blob,你可以使用git notes向blob添加注释,像这样:

$ git ls-tree HEAD
100644 blob 252f7c7df5bd181536a6c9d0f9c371ce1a5dd042    .gitignore
100644 blob 8150ada74aba86c983ac3f8f63ab26aaa76fdcb7    README
100644 blob c4b1ff6dcb2a8e50727df21ced8d2872cd91af79    TODO.txt

$ git notes add -m "Adding a note to TODO" c4b1ff6dcb2a8e507
$ git notes show c4b1ff6dcb2a8e507
Adding a note to TODO

请注意,这个注释(没有双关语的意思)仅附加在这个数据块上,因此每当文件更改时,将创建一个新的数据块(具有另一个sha1哈希值),新的数据块将不会包含此注释。


考虑到您的结尾注释(关于在文件更改后更改SHA):这对我来说看起来就像是预期的行为。不是吗? - exhuma

4

与您在问题中所建议的仓库整体不同,标签是放置在特定提交上的。

您所建议的一种方法是确保每次提交只更改单个文件,然后您可以使用例如file1-v1.0标记该提交以表示文件1的v1.0。

但是您想通过标记这些提交来表示什么?这将影响任何有关如何改进您的流程的建议。


感谢您。正如我在问题中所说,我的主要兴趣是了解更多关于git环境的知识。但在这个特定的实例中,我想要做的是跟踪一个特定的里程碑,以便在维护文档时达到预期目标。例如:当我完成初始工作时,我将其标记为v1.0,当我与其他人一起查看文档并进行所有必要的更改后,我想将其标记为v2.0等。实际上,如果我将一个提交标记为v1.0,将另一个提交标记为v2.0,则我将无法实现此目标。事实上,两者都应该是v1.0。 - picmate 涅

2

你应该考虑使用单独的分支来跟踪特定文件的更改。这样你就可以很好地处理它们。


1

不直接可以。

标签是指向存储库特定版本的指针。单个文件本身没有与存储库不同的版本。如果您想要单独的版本,您的选项是为每个文件创建一个单独的存储库,为每个文件创建一个单独的分支,或使用不同的面向文件的工具(例如RCS - 尽管它缺少许多git具有的良好功能)。

如果文件有任何关联,通常确实希望标记它们的特定版本。如果它们没有关联,您仍然可以使用该修订版中更改的每个文件的版本来标记整个组。限制每个修订版对一个文件的更改可以使此过程更易于管理。


谢谢,事实上你建议的最终是我想做的。此外,我一直只根据一个文件的基础进行更改。 - picmate 涅

1

这不是对原问题的回答,而是对原问题评论之一的回答。

git 标记提交,而不是文件。你能告诉我们你想用这些“标签”做什么吗?如果可以,我认为会有更合适的回答。- Noufal Ibrahim

我发表了这个扩展评论作为伪答案,

  • 因为stackoverflow的评论格式有限。
  • 我觉得在OP问题中添加我自己的解释,为什么我想要单个文件或子集标记,可能不太合适。

简短的回答:

当我在tag-for-code.txt--update-by-jim-April1sttag-for-code.txt--original-version-by-joe之间进行diff时, 我只想看到my-lib/import/new-module/code.txt的差异。 或者也许是my-lib/import/new-module。 我不想看到my-lib/import/module1的差异, 这应该是完全独立的[*]于* my-lib/import/new-module/code.txt。 不,我不想知道我应该过滤哪些部分。我可能不知道那些内容,除非深入挖掘。

我理解 git 标签是针对提交的,而提交本质上是整个仓库的快照。 所以我只是在寻找一种解决方法,允许我方便地说出... diff tag1 tag2 只引用明确标识为属于tag1和tag2的文件子集,并且不涉及一系列其他独立文件的更改。

例如,也许我应该有一个假设的子集标记,创建一个包含文件名列表和仓库提交ID的文件。因此,使用这样的子集标记的工具将仅过滤与其各自标记文件列表相关的信息。或者可能只是 blob-ID。无论哪种方式。

有没有人有一个做这个的最佳实践?

===

我发表了这篇伪答案,因为我长期以来一直因为 git 没有单个文件或子集标记而遭受知识上的痛苦。肯定有一个 git 等效物吧?尽管我怀疑没有,因为有太多离题的回复,通常是“你为什么要那样做?”的形式。

其他一些版本控制系统既有整个仓库,也有单个文件或子集标记。据我所知,git没有。为了不失一般性,我将提供一个来自CVS的示例,尽管这些概念同样适用于DVCS。

为什么你需要单个文件和子集文件组标记以及整个仓库标记的简要摘要

我们都同意符号标记游戏很好。对吧?

在过去,只有单文件标签。虽然通常您可以将相同的标签应用于文件组,但不能保证您将相同的标签应用于整个存储库。因此,如果您执行checkout -rTestsRunTag,期望能够成功构建和运行测试,则可能会失败,因为某些您不知道依赖于的文件未标记为TestsRunTag。因此更喜欢整个存储库标记。标记适用于整个存储库的快照。希望,如果您检出这样的整个存储库标记,则保证能够成功构建。对吗?实际上并不是。您是否将构建工具、编译器等放入了存储库中?尽管如此,整个存储库标记是迈向可重现构建和测试的非常好的一步。但是,仅适用于整个存储库标记的VCSes在解决问题时有所不足。仍然需要单文件标记。更经常地,需要对文件组进行标记,其中该组不是整个存储库。通常是目录子树,或者如果您相关这些事情。基本上,当标签仅与子集相关且对整个存储库而言无关甚至令人困惑时,您需要这种子集标记。或者在使用整个repo标记时不方便时。例如,当我在code.txt--update-by-jim-April1st和code.txt--original-version-by-joe之间进行diff时,我只想看到my-lib/import/new-module/code.txt的差异。或者也许是my-lib/import/new-module。但我不想看到my-lib/import/module1的差异,因为它被认为是完全独立于my-lib/import/new-module/code.txt。下面是一个关于需要非全存储库标记的简要示例。我有一个主要由独立组成的库。称之为my-lib。我从网络上收集了许多模块,将它们放在这个库中,例如my-lib/import/module1、my-lib/import/module2等,使它们相互分离,并与我的自己的东西(如my-lib/my-stuff-my-module1等)相分离。我正在向此库添加一个新模块,该模块来自某个网站,我们称之为my-lib/import/new-module。不幸的是,该模块没有自己的版本控制系统。它发布在讨论线程上,由不同的用户略有不同的版本。我不太确定要使用哪个版本,所以我要在我的库中放置其中的一些版本。

让我们以一个模块中的单个文件my-lib/import/new-module/code.txt为例进行讨论。

我首先下载了讨论线程上找到的第一个版本,并将其放入my-lib/import/new-module/code.txt中,然后提交检查。我想给它一个符号性名称,因为这比使用git哈希或CVS的1.1.1.1之类的数字版本号更好。例如,使用符号“tag”名称code.txt--original-version-by-joe,虽然我可能会在标记名称中添加一些日期,比如原始创建日期,以及与该标记描述相关的更多注释。

也许我会使用它一段时间。但最终我在讨论线程上看到了另一个版本。然后我下载了我在讨论线程中找到的第一个版本并将其放入my-lib/import/new-module/code.txt中,然后提交检查。我想给它一个不同的符号名称。例如,code.txt--update-by-jim-April1st。

我希望这足以说明为什么我希望标记适用于单个文件或文件子集,而不是整个存储库。

标记code.txt--original-version-by-joe和code.txt--update-by-jim-April1st仅与模块my-lib/import/new-module及其文件my-lib/import/new-module/code.txt相关。对于它完全独立的其他模块,如my-lib/import/module1、my-lib/import/module2和my-lib/my-stuff-my-module1,这些新模块标记是不相关的。

当我在code.txt--update-by-jim-April1st和code.txt--original-version-by-joe之间进行差异比较时,我只想看到my-lib/import/new-module/code.txt的差异。或者也许是my-lib/import/new-module。但是我不希望看到my-lib/import/module1的差异,因为它被认为是my-lib/import/new-module/code.txt完全独立[*]的。

注意:没有完全独立的东西,但是……

请注意,我说“据说完全独立”。这就是陷阱所在。即使完全独立的库模块也可能破坏交叉内容(如Makefile)的库基础设施,即使它们从未链接到同一个程序中。

但尽管如此,仅显示my-lib/import/new-module/code.txt的默认差异比较非常方便。

为了方便起见,我们可以有一些方法来表示“针对整个仓库的快照与单个文件标签code.txt--update-by-jim-April1st创建时的差异”。如果只有一个文件标签,那么这是明确无误的。

如果将同一标签应用于多个文件版本,而这些版本不在单个完整的仓库快照中(例如git提交),则会出现一些小问题,但您可以处理它们。

为什么不使用模块?

我能听到一些人说:“为什么不只是使用模块?” 我们不是在谈论将程序分成模块和子模块的通用编程部分,而是版本控制系统中的模块和子模块,例如git模块和子模块。

好吧,我刚才在上面的例子中使用了模块来简化讨论。

版本控制系统定义的模块具有一些开销。您必须事先设置好一些东西,这通常并不足够好。

许多系统最初作为某个大型父存储库中的单个文件开始,当它们不被视为单独的模块时。然后发展成为单独的模块。它们经常作为big_file_of_many_functions.c中的单个函数开始。然后您会意识到,这个函数和/或某些近亲应该在一个文件中,例如foo.c,如果您使用C/C ++,几乎总是需要一个头文件foo.h。最终,您会意识到,最好有一个单独的目录foo/,其中包含foo/foo.cfoo/foo.h。最终,您可能会添加foo/Makefilefoo/tests/test1.py

在从其他函数的较大文件中的函数到子树的演变过程中,您决定给它一个版本控制系统模块化子模块。

这很棒。但是确实很希望有一些方法来引用(a)较大存储库中的相关事物集合和(b)此相关事物集合的两个版本。

我再说一遍:

版本控制系统定义的模块具有一些开销。相当大的开销。这不仅是我的说法。这与关于单一存储库与多存储库之间的辩论密切相关,例如https://johnclarke73.medium.com/mono-or-multi-repo-6c3674142dfc。一些非常重要的软件开发人员和公司使用单一存储库。在我看来,部分原因是git和其他版本控制系统模块系统很麻烦

考虑每个子目录树都是一个单独的模块的树

我一直保持着个人代码库,这是一个深度嵌套的目录树。几乎每个子目录树都可以被视为独立的模块。通常,单个文件可以单独导出,例如C或C++中的头文件库。通常我更喜欢为每个最小的逻辑子系统设置一个目录,以便您可以拥有单独的makefile和测试脚本。
我经常与其他项目、公司和雇主共享此代码。但这些其他项目很少想要导入我的整个个人代码库树。因此,我希望能够仅检出子集,从单个文件到大多数情况下的子目录树,但有时需要一组必须一起使用的子目录树。
这些其他项目通常不想使用我的版本控制系统。太糟糕了,这就是生活。但有时他们会使用。
当然,现代DVCS对于检出任意子集的支持非常差。除了模块支持之外(请参见其他地方)。如果稀疏检出(有时称为稀疏分支,尽管术语很快进入特定版本控制系统的细节),则携带与未检出的项目相关的历史记录是不好的。如果不是这样,如果该稀疏分支不携带多余的历史记录和访问对象,则无法将其合并回具有在文件系统的不同部分中未检出的更多对象和历史记录的存储库。
当他们愿意共享版本控制系统时,它真的很令人困惑,因为他们检出的项目被标记为与他们完全无关的标签。
有时不仅仅是困惑。有时是安全漏洞。
无论如何,在这个巨大的个人单体库中,我喜欢为其他项目检出的子集打标签,并从这些其他项目导入。但是,那些标签对于那些其他项目不想查看的内容是无关紧要的。
例如,CVS标签=每个文件,但易于执行多个文件
CVS标签实际上是基于每个文件定义的。但通常情况下,CVS使其易于通过多个文件、子树或整个存储库应用相同的标签。例如,如果您只在特定目录中说“cvs tag tagname”,则CVS会将该标签应用于该目录及其子目录中的所有文件。如果您在您的CVS存储库顶部说“cvs tag tagname”,则该标签将应用于您的整个存储库。许多CVS用户都有一个别名或命令,允许您即使在子目录中也可以标记整个存储库。但是您也可以指定单个文件或多个独立文件和子目录 当然,CVS标签不能像git标签一样保证在整个存储库中保持一致。

但这是一个使用模型问题。 有时您需要保证跨存储库的标签一致性。 有时不需要。 通常使用命名约定来区分两者。 并使用工具确定这些命名约定是否正确应用,例如,如果一个被认为是整个存储库标记没有被应用到所有内容,则需要检查。


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