Git замена ключевых слов, например, в Subversion?

Я работал в Subversion/SVN и мгновенно использовал приятную функцию, называемую заменой ключевого слова. Просто вставляя исходные файлы, например:

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

И каждый раз Subversion заменяет ключевые слова (Author, Rev, LastChangedDate) на фактические.

Некоторое время назад мне пришлось переместиться на Git и просто интересно, есть ли что-то похожее на подстановку ключевых слов Subversion в Git?

Ответ 1

Решение

Ну, вы можете легко реализовать такую ​​функцию самостоятельно.

В основном я встроил команду commit в оболочку script. Этот script сначала заменит нужные макросы, а затем зафиксирует изменения. Проект состоит из двух файлов:

Содержание

keysub, a bash shell script и keysub.awk awk script, чтобы заменить ключевые слова в определенном файле. Третий файл - это файл конфигурации, который содержит значения, которые следует заменить (помимо переменных, таких как количество фиксации и временная метка).

Как вы его используете?

Вы вызываете keysub вместо фиксации с теми же параметрами. Параметр -m или -a должен появиться перед любым другим параметром фиксации. Новая опция (которая всегда должна быть первой) - это -f, которая принимает файл конфигурации как значение. Пример:

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

или

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

keysub

#!/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}" "[email protected]"
              ;;
  "commit_a") git commit -a "[email protected]"
              ;;
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

Примечания

Я пытался документировать достаточно хорошо, чтобы вы могли легко реализовать его и изменить его на собственные, личные нужды. Обратите внимание, что вы можете указать макросам любое имя, которое вы хотите, до тех пор, пока вы измените его в исходном коде. Я также стремился к тому, чтобы упростить расширение script, вы должны легко добавлять новые макросы. Если вы заинтересованы в расширении или модификации script, вы можете также взглянуть на каталог .git, там должно быть много информации, которая может помочь улучшить script из-за нехватки времени Я не исследовал папку, хотя.

Ответ 2

Git не поставляется с этой функциональностью из коробки. Тем не менее, есть глава в Git книге Настройка Git, и один из примеров - использование атрибутов Git для реализовать аналогичный результат.

Оказывается, вы можете написать свои собственные фильтры для замены в файлах при фиксации/проверке. Они называются "чистыми" и "smudge". В файле .gitattributes вы можете установить фильтр для определенных путей, а затем настроить скрипты, которые будут обрабатывать файлы незадолго до того, как они проверили ( "размазать" ) и прямо перед ними ( "чистый" ). Эти фильтры могут быть настроены на всевозможные развлечения вещи.

Есть даже пример для $LastChangedDate: $:

Еще один интересный пример: расширение ключевого слова $Date$, стиль RCS. Чтобы сделать это правильно, вам понадобится небольшой script, который принимает имя файла, определяет последнюю дату фиксации для этого проекта и вставляет дата в файл. Вот небольшой Ruby script, который делает это:

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

Все script - это получение последней фиксации введите дату из команды git log, вставьте ее в любую строку $Date$. видит в stdin и печатает результаты - это должно быть просто сделать в любой язык, на котором вы наиболее комфортно. Вы можете назвать этот файл expand_date и поместите его на свой путь. Теперь вам нужно настроить фильтр в Git (назовите его dater) и скажите ему использовать ваш фильтр expand_date для размазать файлы при оформлении заказа. Вы используете выражение Perl для очистки что на commit:

$ 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) isnt, поэтому он не будет работать везде. Когда вы создаете эти фильтры, они должны быть способный изящно потерпеть неудачу, и проект по-прежнему работает должным образом.