从 CVS 迁移到 Git: $Id $等价?

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

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

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

如果我看到的是几个40个字符的散列,我不知道哪个是稍后的,也不知道有多久。如果我不得不手动检查 ChangeLogs 来了解我是如何过时的话,我会非常讨厌它。

作为一个开发人员,我希望将这种礼貌扩展到使用我的输出的人(也许我在自欺欺人地认为有人在使用我的输出,但是让我们暂时把它放在一边)。我不想每次都要自己记住增加该死的数字,或者时间戳之类的东西。这是一个真正的 PITA,我知道,从经验。

那么我还有什么选择呢?如果不能获得 $Id: $等价物,那么如何提供所需的内容?

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

83165 次浏览

不确定这会不会出现在 Git 中。对于 引用莱纳斯的话:

“关键字替换的整个概念是完全愚蠢的 在实际内容跟踪之外做一些琐碎的事情,如果你想的话 当做沥青球等释放树木时,有它。”

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

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

如果我理解正确的话,实际上,您想知道自从您上次更新以来,在给定文件上发生了多少次提交。

首先获取远程原点中的更改,但不要将它们合并到 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

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

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

另外,如果你不确定他们是否愿意,我也不会费心向任何人伸出友好之手。 :)

使用 Git 存储库完成的工作是使用 tag对象。这可以用于用任何类型的字符串标记提交,也可以用于标记版本。您可以使用 git tag命令在存储库中看到这些标记,该命令返回所有标记。

检查标签很容易。例如,如果有一个标记 v1.1,您可以将该标记检出到一个分支,如下所示:

git checkout -b v1.1

因为它是一个顶级对象,所以您将看到提交的整个历史,以及能够运行 diff、进行更改和合并。

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

如果拥有 $Keywords $对你来说是必不可少的,那么也许你可以尝试看看 反复无常代替?它有一个 hgkey 扩展,可以实现您想要的东西。不管怎样,Mercurial 作为 DVCS 是很有趣的。

正如我写的 之前:

使用像 Bazaar 这样的 DSCM 工具不可能自动生成 ID 标签来显示合理的版本号,因为每个人的开发路线都可能与其他人不同。所以有人可以引用一个文件的版本“1.41”,但是你的版本“1.41”是不同的。

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

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

例如,当我在 Java memcached 客户端源的主分支中运行 git describe时,我会得到以下结果:

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.

因为您使用 Emacs,所以您可能比较幸运:)

我碰巧遇到了这个问题,也碰巧几天前遇到了 活泼,它是一个 Emacs 包,允许在文档中使用 Emacs Lisp 的生动片段。说实话,我没有试过,但读到这里时,我突然想到了这一点。

现在已经有了对 $Id: $in Git 的支持。要为文件 自述启用它,需要在 。 gittribute中输入“ README 标识”。支持文件名上的通配符。详情请参阅 伙计们

这不是 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 存储库启用 id 字符串中有一些好的信息。

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

我使用了一个 Git pre-commit 钩子并修改了我的脚本,以便能够自动更新它自己。

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

例如,我有一个用 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) ,所以如果我把它称为“ tool-updateVersion”,那么它就会为这个工具调用 updateVersion,这个工具会自己修改“ MYVERION”值,然后退出(如果需要,你也可以让它更新其他文件,如果它们也被打开的话)。

设置好之后,我转到 Git 头部,在 .git/hooks/pre-commit中创建一个可执行的一行 bash 脚本。

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

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

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

您应该拥有一些自动发布工具,以便在标记发布时在源代码中设置版本 ID。

RCS ID 对于单文件项目来说是很好的,但对于其他任何文件,$Id $都没有提到项目(除非您强制对虚拟版本文件进行虚拟签入)。

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

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

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

在为下一个版本工作的时候,脚本会提取 .version编号,一些 Git 版本描述符(如 git describe)和 .build中的单调构建编号(加上主机和日期)到一个自动生成的源文件中,这个源文件链接到最终的程序,这样你就可以知道它是从哪个源文件和什么时候构建的。

我在不同的分支中开发新特性,首先要做的是将 n(表示“ next”)添加到 .version字符串中(来自同一根的多个分支将使用相同的临时 .version编号)。在发布之前,我决定合并哪些分支(希望所有分支都具有相同的 .version)。在提交合并之前,我将 .version更新到下一个数字(主要或次要更新,取决于合并的特性)。

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

我也遇到过类似的挑战。我想知道运行它的任何系统上的代码是什么版本。系统可能连接到任何网络,也可能不连接到任何网络。系统可能安装了 Git,也可能没有安装。系统上可能安装了 GitHub 存储库,也可能没有。

我想对几种类型的代码使用相同的解决方案(。嘘。去吧。对不起。Xml 等)。我希望任何不了解 Git 或 GitHub 的人都能够回答这个问题 “你在运行什么版本?”

因此,我编写了几个 Git 命令的包装器。我使用它来标记带有版本号和一些信息的文件。它解决了我的挑战。也许能帮到你。

Https://github.com/bradleya/markit

git clone https://github.com/BradleyA/markit
cd markit

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

* ident

为了看到效果,您需要首先对文件进行有效的签出,比如以任何方式删除或编辑它们。然后用以下方法恢复它们:

git checkout .

你应该看到 $Id$被替换成这样的东西:

$Id: ea701b0bb744c90c620f315e2438bc6b764cdb87 $

来自 man gitattributes:

身份证

当为路径设置属性 Id 时,Git 将替换 Blob 对象,后面跟着40个字符的十六进制 blob 对象 名称,后面跟着一个美元符号 $on checkout 在签入时,工作树文件中以 $Id 结尾的元素被替换为 $Id $。

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

如果您想让 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可以留给读者作为练习。

为了自己解决这个问题,我创建了一个小的“ hack”作为提交后挂钩:

echo | tee --append *
git checkout *

我博客上的这篇文章中有更详细的记录。

通过 gitattributes(5)export-subst特性,Git 现在可以将标记名称和其他相关信息直接编辑成文件。当然,这需要使用 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"

如果像 OP 一样使用 Emacs,则可以使用它的 time-stamp 函数来获得所需的自动属性更新。

让您的编辑器进行更新的好处是 文件内容更新时的时间戳 更改,而不是当某人仅仅提交文件到 版本控制或重建。

$Id$模板的时间戳等价于 Time-stamp: <> 而且,与 $Id $不同,它必须出现在文件的前8行内。

您需要在 Emacs init 文件中启用自动时间戳, 你可以这样做:

M-x customize-variable RET
before-save-hook RET

选中 time-stamp框并选择 Apply and Save