51

我曾经在 Subversion/SVN 下工作,并且立即使用了称为关键字替换的好功能。只需放入源文件,例如:

/*
 *   $Author: ivanovpv $
 *   $Rev: 42 $
 *   $LastChangedDate: 2012-05-25 21:47:42 +0200 (Fri, 25 May 2012) $
 */

并且每次 Subversion 都用实际的关键字(Author、Rev、LastChangedDate)替换关键字。

前段时间我被迫迁移到 Git,只是想知道 Git 中是否有类似于 Subversion 的关键字替换的东西?

4

3 回答 3

36

Git 没有提供开箱即用的这个功能。但是,Git Book 中有一个章节是关于自定义 Git的,其中一个示例是如何使用 git 属性来实现类似的结果。

事实证明,您可以编写自己的过滤器,以便在提交/签出时在文件中进行替换。这些被称为“清洁”和“涂抹”过滤器。在.gitattributes文件中,您可以为特定路径设置过滤器,然后设置脚本,在文件被检出(“涂抹”)和暂存(“干净”)之前处理文件。这些过滤器可以设置为做各种有趣的事情。

甚至还有一个例子$LastChangedDate: $

另一个有趣的例子是$Date$关键字扩展,RCS 风格。要正确执行此操作,您需要一个小脚本,该脚本采用文件名,计算该项目的最后提交日期,并将日期插入文件中。这是一个执行此操作的小型 Rub​​y 脚本:

#! /usr/bin/env ruby
data = STDIN.read
last_date = `git log --pretty=format:"%ad" -1`
puts data.gsub('$Date$', '$Date: ' + last_date.to_s + '$')

脚本所做的就是从命令中获取最新的提交日期,将其git log粘贴到$Date$它在标准输入中看到的任何字符串中,然后打印结果——用你最熟悉的任何语言都应该很简单。你可以命名这个文件 expand_date并把它放在你的路上。现在,您需要在 Git 中设置一个过滤器(调用它dater)并告诉它使用您的expand_date过滤器在结帐时涂抹文件。您将使用 Perl 表达式在提交时对其进行清理:

$ git config filter.dater.smudge expand_date
$ git config filter.dater.clean 'perl -pe "s/\\\$Date[^\\\$]*\\\$/\\\$Date\\\$/"'

这个 Perl 片段会删除它在$Date$字符串中看到的任何内容,以回到您开始的地方。现在您的过滤器已准备就绪,您可以通过为该文件设置一个与新过滤器相关的 Git 属性并使用您的$Date$关键字创建一个文件来测试它:

date*.txt filter=dater
$ echo '# $Date$' > date_test.txt If you commit

这些更改并再次检查文件,您会看到正确替换的关键字:

$ git add date_test.txt .gitattributes
$ git commit -m "Testing date expansion in Git"
$ rm date_test.txt
$ git checkout date_test.txt
$ cat date_test.txt
# $Date: Tue Apr 21 07:26:52 2009 -0700$

您可以看到这种技术对于定制应用程序有多么强大。但是,您必须小心,因为该.gitattributes文件已提交并随项目一起传递,但驱动程序(在本例中dater为 )不是,因此它不会在任何地方工作。当您设计这些过滤器时,它们应该能够优雅地失败并让项目仍然正常工作。

于 2012-07-18T06:19:37.150 回答
17

解决方案

好吧,您可以自己轻松实现这样的功能。

基本上我将提交命令嵌入到了一个 shell 脚本中。此脚本将首先替换所需的宏,然后提交更改。该项目由两个文件组成:

内容?

keysub,一个 bash shell 脚本和keysub.awk一个 awk 脚本来替换特定文件中的关键字。第三个文件是一个配置文件,其中包含应该替换的值(除了提交计数和时间戳等变量内容)。

你如何使用它?

您使用相同的选项调用keysub而不是提交。-mor选项应该在-a任何其他提交选项之前。一个新选项(应该始终放在第一位)是-f将配置文件作为值。例子:

$ git add 'someJavaFile.java'
$ keysub -m 'fixed concurrent thread issue'
$ git push

或者

$ git -f .myfile.cnf -m 'enhanced javadoc entries'

键子

#!/bin/bash

# 0 -- functions/methods
#########################
# <Function description>
function get_timestamp () {
  date    # change this to get a custom timestamp
}

# 1 -- Variable declarations
#############################
# input file for mapping
file=".keysub.cnf"
timestamp=$(get_timestamp)


# 2 -- Argument parsing and flag checks
########################################

# Parsing flag-list
while getopts ":f:m:a" opt;
do
  case $opt in
    f) file=${OPTARG}
       ;;
    a) echo 'Warning, keyword substitution will be incomplete when invoked'
       echo 'with the -a flag. The commit message will not be substituted into'
       echo 'source files. Use -m "message" for full substitutions.'
       echo -e 'Would you like to continue [y/n]? \c'
       read answer
       [[ ${answer} =~ [Yy] ]] || exit 3
       unset answer
       type="commit_a"
       break
       ;;
    m) type="commit_m"
       commitmsg=${OPTARG}
       break
       ;;
   \?) break
       ;;
  esac
done
shift $(($OPTIND - 1))

# check file for typing
if [[ ! -f ${file} ]]
then
  echo 'No valid config file found.'
  exit 1
fi

# check if commit type was supplied
if [[ -z ${type} ]]
then
  echo 'No commit parameters/flags supplied...'
  exit 2
fi

# 3 -- write config file
#########################
sed "
  /timestamp:/ {
    s/\(timestamp:\).*/\1${timestamp}/
  }
  /commitmsg:/ {
    s/\(commitmsg:\).*/\1${commitmsg:-default commit message}/
  }
" ${file} > tmp

mv tmp ${file}

# 4 -- get remaining tags
##########################
author=$(grep 'author' ${file} | cut -f1 -d':' --complement)


# 5 -- get files ready to commit
#################################
git status -s | grep '^[MARCU]' | cut -c1-3 --complement > tmplist

# 6 -- invoke awk and perform substitution
###########################################
# beware to change path to your location of the awk script
for item in $(cat tmplist)
do
  echo ${item}
  awk -v "commitmsg=${commitmsg}" -v "author=${author}" \
      -v "timestamp=${timestamp}" -f "${HOME}/lib/awk/keysub.awk" ${item} \
      > tmpfile
  mv tmpfile ${item}
done
rm tmplist

# 5 -- invoke git commit
#########################
case ${type} in
  "commit_m") git commit -m "${commitmsg}" "$@"
              ;;
  "commit_a") git commit -a "$@"
              ;;
esac

# exit using success code
exit 0

keysub.awk

# 0 BEGIN
##########
BEGIN {
  FS=":"
  OFS=": "
}

# 1 parse source files 
########################
# update author
$0 ~ /.*\$Author.*\$.*/ {
  $2=author " $"
}

# update timestamp
$0 ~ /.*\$LastChangedDate.*\$.*/ {
  $0=$1
  $2=timestamp " $"
}

# update commit message
$0 ~ /.*\$LastChangeMessage.*\$.*/ {
  $2=commitmsg " $"
}

# update commit counts
$0 ~ /.*\$Rev.*\$.*/ {
  ++$2
  $2=$2 " $"
}

# print line
{
  print
}

配置文件

author:ubunut-420
timestamp:Fri Jun 21 20:42:54 CEST 2013
commitmsg:default commit message

评论

我已经尝试过足够好的文档,以便您可以轻松地实现它并根据您自己的个人需求对其进行修改。请注意,您可以为宏指定任何您想要的名称,只要您在源代码中对其进行修改即可。我还打算让扩展脚本相对容易,你应该能够相当容易地添加新的宏。如果你对扩展或修改脚本感兴趣,你可能也想看看 .git 目录,那里应该有很多信息可以帮助增强脚本,因为我没有时间调查该文件夹。

于 2013-06-21T19:13:44.343 回答
8

可惜不是原生的。

Git有关键字扩展吗?不建议使用关键字扩展。关键字扩展会导致各种奇怪的问题,而且无论如何都不是真正有用的,尤其是在 SCM 的上下文中。您可以使用自定义脚本在 Git 之外执行关键字扩展。Linux 内核导出脚本执行此操作以在 Makefile 中设置 EXTRA_VERSION 变量。

如果你真的想这样做,请参阅 gitattributes(5)。如果您的翻译不可逆(例如 SCCS 关键字扩展),这可能会有问题。(提示:提供的 $Id$-expansion 将 40 个字符的十六进制 blob 对象名称放入 id;您可以使用这样的脚本找出哪些提交包含此 blob。)

阅读他们的文档,附上链接: 关键字扩展

于 2012-07-18T05:20:20.763 回答