128

我阅读了一堆关于简单源代码控制工具的问题,Git 似乎是一个合理的选择。我已经启动并运行它,到目前为止它运行良好。我喜欢 CVS 的一个方面是版本号的自动递增。

我知道这在分布式存储库中意义不大,但作为开发人员,我想要/需要这样的东西。让我解释一下原因:

我使用 Emacs。我会定期检查并寻找第三方软件包的 Lisp 源文件的新版本。假设我有一个文件 foo.el,根据标题,它是 1.3 版;如果我查看最新版本并看到它是 1.143 或 2.6 或其他什么,我知道我已经远远落后了。

相反,如果我看到几个 40 个字符的散列,我将不知道哪个更晚,或者知道它晚了多少。如果我不得不手动检查 ChangeLogs 以了解我有多过时,我绝对会讨厌它。

作为一名开发人员,我想将这种礼貌,正如我所看到的,扩展到使用我的输出的人(也许我在开玩笑说任何人都是,但让我们暂时搁置一下)。我不想每次都记得自己增加该死的数字,或者时间戳或类似的东西。那是真正的 PITA,我从经验中知道这一点。

那么我有什么选择呢?如果我无法获得 $Id:$ 等价物,我还能如何提供我正在寻找的东西?

我应该提一下,我的期望是最终用户不会安装 Git,即使他们安装了,也不会拥有本地存储库(实际上,我希望不会以这种方式提供它)。

4

18 回答 18

68

SHA 只是版本的一种表示(尽管是规范的)。该git describe命令提供了其他命令并且做得很好。

例如,当我git describe在我的Java memcached 客户端源的 master 分支中运行时,我得到这个:

2.2-16-gc0cd61a

这说明了两个重要的事情:

  1. 自 2.2 以来,这棵树中恰好有 16 次提交
  2. 确切的源代码树可以显示在任何其他人的克隆上。

例如,假设您将一个version文件与源打包在一起(或者甚至重写所有内容以进行分发)以显示该数字。假设打包版本是2.2-12-g6c4ae7a(不是发行版,而是有效版本)。

您现在可以确切地看到您落后了多远(4 次提交),并且您可以准确地看到哪 4 次提交:

# The RHS of the .. can be origin/master or empty, or whatever you want.
% git log --pretty=format:"%h %an %s" 2.2-12-g6c4ae7a..2.2-16-gc0cd61a
c0cd61a Dustin Sallings More tries to get a timeout.
8c489ff Dustin Sallings Made the timeout test run on every protocol on every bui
fb326d5 Dustin Sallings Added a test for bug 35.
fba04e9 Valeri Felberg Support passing an expiration date into CAS operations.
于 2008-12-22T04:03:29.063 回答
57

到目前为止,Git 中已经支持 $Id:$。要为文件README启用它,您可以将“README ident”放入.gitattributes。支持文件名上的通配符。有关详细信息,请参阅man gitattributes

于 2011-02-16T22:21:14.560 回答
32

这不是 OP 的不合理要求。

我的用例是:

  1. 我将 Git 用于我自己的个人代码,因此不与他人合作。
  2. 我将系统 Bash 脚本保存在那里,/usr/local/bin它们准备好后可能会进入。

我使用三台独立的机器,上面有相同的 Git 存储库。/usr/local/bin无需手动执行“diff -u <repo version> <version in /usr/local/bin>”即可知道我当前拥有的文件的“版本”会很高兴。

对于那些消极的人,请记住还有其他用例。不是每个人都使用 Git 进行协作,将 Git 存储库中的文件作为他们的“最终”位置。

无论如何,我这样做的方式是在存储库中创建一个属性文件,如下所示:

cat .git/info/attributes
# see man gitattributes
*.sh ident
*.pl ident
*.cgi ident

然后将 $Id$ 放在文件中的某个位置(我喜欢把它放在 shebang 之后)。

提交。请注意,这不会像我预期的那样自动进行扩展。您必须重新合作文件,例如,

git commit foo.sh
rm foo.sh
git co foo.sh

然后你会看到扩展,例如:

$ head foo.sh
#!/bin/sh

# $Id: e184834e6757aac77fd0f71344934b1cd774e6d4 $

如何为 Git 存储库启用 ident 字符串?.

于 2012-08-04T09:38:10.240 回答
23

不确定这是否会出现在 Git 中。引用 Linus 的话:

“关键字替换的整个概念完全是愚蠢的。如果您想在将发布树作为 tar-ball 等时使用它,那么在实际内容跟踪之外进行“外部”是微不足道的。”

不过,检查日志非常容易——如果您正在跟踪 foo.el 的稳定分支,您可以看到稳定分支的日志中有哪些新提交不在您的本地副本中。如果要模拟 CVS 的内部版本号,可以比较最后一次提交的时间戳。

编辑:您应该为此编写或使用其他人的脚本,当然,不要手动执行此操作。

于 2008-12-21T04:54:49.507 回答
21

正如我之前写

使用 Bazaar 等 DSCM 工具自动生成显示合理版本号的 Id 标签是不可能的,因为每个人的开发线都可能与其他人不同。因此,有人可能会引用文件的“1.41”版本,但该文件的“1.41”版本是不同的。

基本上,$Id$ 对 Bazaar、Git 和其他分布式源代码管理工具没有任何意义。

于 2008-12-21T16:37:14.630 回答
10

我有同样的问题。我需要一个比哈希字符串更简单的版本,并且可供使用该工具的人使用,而无需连接到存储库。

我使用 Git 预提交钩子完成了它,并更改了我的脚本以能够自动更新自身。

我根据完成的提交数量来确定版本。这是一个轻微的竞争条件,因为两个人可以同时提交并且都认为他们提交的是相同的版本号,但是我们在这个项目上没有很多开发人员。

举个例子,我有一个我签入的 Ruby 脚本,我将这段代码添加到其中 - 这是非常简单的代码,因此如果您要签入不同语言的内容,很容易移植到不同的语言(尽管显然这不会轻易使用不可运行的签入,例如文本文件)。我已经添加:

MYVERSION = '1.090'
## Call script to do updateVersion from .git/hooks/pre-commit
def updateVersion
  # We add 1 because the next commit is probably one more - though this is a race
  commits = %x[git log #{$0} | grep '^commit ' | wc -l].to_i + 1
  vers = "1.%0.3d" % commits

  t = File.read($0)
  t.gsub!(/^MYVERSION = '(.*)'$/, "MYVERSION = '#{vers}'")
  bak = $0+'.bak'
  File.open(bak,'w') { |f| f.puts t }
  perm = File.stat($0).mode & 0xfff
  File.rename(bak,$0)
  File.chmod(perm,$0)
  exit
end

然后我在脚本中添加了一个命令行选项(-updateVersion),所以如果我将它称为“工具-updateVersion”,那么它只会为修改“MYVERSION”值的工具调用updateVersion,然后退出(你可以如果您愿意,它还可以更新其他文件(如果它们也被打开)。

设置完成后,我转到 Git 头并在.git/hooks/pre-commit.

该脚本只是更改为 Git 目录的头部,并使用-updateVersion.

每次我签入 pre-commit 脚本时都会运行它,它使用 -updateVersion 运行我的脚本,然后根据提交的数量更新 MYVERSION 变量。魔法!

于 2012-11-12T23:25:24.637 回答
8

使用 Git 存储库所做的事情是使用该tag对象。这可用于使用任何类型的字符串标记提交,并可用于标记版本。您可以使用git tag返回所有标签的命令查看存储库中的标签。

签出标签很容易。例如,如果有标签v1.1,您可以像这样将该标签签出到分支:

git checkout -b v1.1

由于它是顶级对象,您将看到该提交的整个历史记录,并且能够运行差异、进行更改和合并。

不仅如此,标签仍然存在,即使它所在的分支已被删除而没有合并回主线。

于 2008-12-21T10:18:25.033 回答
8

如果 $Keywords$ 对您来说是必不可少的,那么也许您可以尝试使用Mercurial?它有一个 hgkeyword 扩展来实现你想要的。Mercurial 作为 DVCS 还是很有趣的。

于 2008-12-21T10:35:42.020 回答
5

要将扩展应用于存储库中所有子目录中的所有文件,请将.gitattributes文件添加到存储库的顶级目录(即通常放置.gitignore文件的位置),其中包含:

* ident

要真正看到这一点,您需要先对文件进行有效的签出,例如以任何方式删除或编辑它们。然后使用以下命令恢复它们:

git checkout .

你应该看到$Id$替换为:

$Id: ea701b0bb744c90c620f315e2438bc6b764cdb87 $

来自man gitattributes

识别

当为路径设置属性 ident 时,Git 将 blob 对象中的 $Id$ 替换为 $Id:,后跟 40 个字符的十六进制 blob 对象名称,然后是结帐时的美元符号 $。在签入时,工作树文件中任何以 $Id: 开头并以 $ 结尾的字节序列都将替换为 $Id$。

每次提交文件的新版本时,此 ID 都会更改。

于 2018-08-17T10:40:54.433 回答
4

如果我理解正确,本质上,您想知道自上次更新以来给定文件发生了多少次提交。

首先获取远程源中的更改,但不要将它们合并到您的master分支中:

% git fetch

master然后获取您的分支和远程之间给定文件上发生的更改的日志origin/master

% git log master..origin/master foo.el

这为您提供了自上次合并origin/master到您的master.

如果您只想计算更改的数量,请将其通过管道传输到wc. 说,像这样:

% git rev-list master..origin/master foo.el | wc -l
于 2008-12-21T06:14:06.413 回答
4

如果您只是希望人们能够了解他们已经过时了多长时间,Git 可以通过几种相当简单的方式告知他们这一点。例如,他们比较他们的主干和你的主干上最后一次提交的日期。他们可以git cherry用来查看您的主干中发生了多少不存在于他们的主干中的提交。

如果这就是您想要的全部,我会寻找一种没有版本号的方法来提供它。

另外,除非您确定他们想要,否则我不会费心向任何人提供礼貌。:)

于 2008-12-21T06:33:20.043 回答
3

RCS ID 非常适合单文件项目,但对于其他任何项目,$Id$ 都没有说明该项目(除非您强制虚拟签入到虚拟版本文件)。

仍然有人可能对如何在每个文件级别或提交级别上获取 $Author$、$Date$、$Revision$、$RCSfile$ 等等价物感兴趣(如何将它们放在某些关键字所在的位置是另一回事问题)。我对这些没有答案,但看到更新这些的要求,特别是当文件(现在在 Git 中)来自 RCS 兼容系统 (CVS) 时。

如果源代码与任何 Git 存储库分开分发(我也是这样做的),那么这些关键字可能会很有趣。我的解决方案是这样的:

每个项目都有自己的目录,在项目根目录中我有一个文本文件.version,其内容描述当前版本(导出源时将使用的名称)。

在为下一个版本工作时,一个脚本会提取该编号.version、一些 Git 版本描述符(git describe.build从什么来源以及何时构建的。

我在单独的分支中开发新功能,我做的第一件事是将n(用于“下一个”)添加到.version字符串(源自同一根的多个分支将使用相同的临时.version编号)。在发布之前,我决定合并哪些分支(希望所有分支都具有相同的.version)。在提交合并之前,我更新.version到下一个数字(主要或次要更新,取决于合并的功能)。

于 2017-01-11T10:40:10.293 回答
3

如果您希望 git 提交信息可以访问到您的代码中,那么您必须执行预构建步骤才能将其获取。在 C/C++ 的 bash 中,它可能看起来像这样:

prebuild.sh

#!/bin/bash
commit=$(git rev-parse HEAD)
tag=$(git describe --tags --always ${commit})
cat <<EOF >version.c
#include "version.h"
const char* git_tag="${tag}";
const char* git_commit="${commit}";
EOF

version.h看起来像:

#pragma once
const char* git_tag;
const char* git_commit;

然后,在您的代码#include "version.h"和参考中需要它的任何地方git_taggit_commit根据需要。

Makefile可能有这样的事情:

all: package
version:
  ./prebuild.sh
package: version
  # the normal build stuff for your project

这有以下好处:

  • 无论分支、合并樱桃采摘等如何,都可以获得构建的当前正确值。

这种实现prepublish.sh有以下缺点:

  • 即使git_tag/git_commit没有改变,也强制重新编译。
  • 它不考虑尚未提交但影响构建的本地修改文件。
    • 用于git describe --tags --always --dirty捕获该用例。
  • 污染全局命名空间。

prebuild.sh可以避免这些问题的鸽友留给读者作为练习。

于 2019-06-10T23:08:31.853 回答
1

我同意那些认为令牌替换属于构建工具而不是版本控制工具的人。

您应该有一些自动发布工具来在标记发布时在源中设置版本 ID。

于 2016-08-10T21:17:40.157 回答
1

标记名称和其他相关信息现在可以直接由 Gitexport-subst通过gitattributes(5). 这当然需要使用git archive来创建版本,并且只有在生成的 tar 文件中才能看到替换编辑。

例如在.gitattributes文件中放入以下行:

* export-subst

然后在源文件中,您可以添加如下一行:

#ident  "@(#)PROJECTNAME:FILENAME:$Format:%D:%ci:%cN:%h$"

并且它将在由以下人员创建的版本中扩展为如下所示git archive v1.2.0.90

#ident  "@(#)PROJECTNAME:FILENAME:HEAD -> master, tag: v1.2.0.90:2020-04-03 18:40:44 -0700:Greg A. Woods:e48f949"
于 2020-04-04T02:00:03.897 回答
0

由于您使用 Emacs,您可能会很幸运 :)

我碰巧遇到了这个问题,而且几天前我偶然发现了Lively ,这是一个 Emacs 包,它允许在您的文档中包含生动的 Emacs Lisp 片段。老实说,我没有尝试过,但是当我读到这篇文章时,我想到了它。

于 2009-04-26T18:17:31.687 回答
0

我也来自 SCCS、RCS 和 CVS ( %W% %G% %U%)。

我也有类似的挑战。我想知道一段代码在运行它的任何系统上的版本。该系统可能连接或可能不连接到任何网络。系统可能安装了也可能没有安装 Git。系统可能安装了也可能没有安装 GitHub 存储库。

对于几种类型的代码(.sh、.go、.yml、.xml 等),我想要相同的解决方案。我希望任何不了解 Git 或 GitHub 的人都能够回答“你在运行什么版本?”这个问题。

所以,我写了一些我称之为包装器的 Git 命令。我用它来标记带有版本号和一些信息的文件。它解决了我的挑战。它可能会帮助你。

https://github.com/BradleyA/markit

git clone https://github.com/BradleyA/markit
cd markit
于 2018-02-13T04:57:48.787 回答
0

为了自己解决这个问题,我创建了一个小的“hack”作为 post-commit 钩子:

echo | tee --append *
git checkout *

更详细地记录在我博客上的这篇文章中

于 2019-12-05T16:35:33.433 回答