SVN预提交挂钩以避免更改标记子目录

40

有没有人有明确的说明,如何添加一个 pre-commit 钩子来避免更改标签子目录?

我已经在互联网上搜索了很多。我找到了这个链接:SVN::Hooks::DenyChanges ,但我似乎无法编译它。

11个回答

39

我没有足够的声望来“评论”上面Raim的答案,但他的方法很好,只有一个例外,他的grep模式是错误的。

我仅使用以下内容作为我的预提交挂钩(如果您没有现有的挂钩,则需要合并):

#!/bin/sh

REPOS="$1"
TXN="$2"

SVNLOOK=/opt/local/bin/svnlook

# Committing to tags is not allowed
$SVNLOOK changed -t "$TXN" "$REPOS" | grep "^U\W.*\/tags\/" && /bin/echo "Cannot commit to tags!" 1>&2 && exit 1

# All checks passed, so allow the commit.
exit 0

Raim的grep模式唯一的问题在于它只匹配库“根目录”下的“标签”。由于我的库中有多个项目,他编写的脚本允许在标签分支上提交。此外,请确保按照指示进行chmod +x操作,否则您会认为它起作用了,因为提交失败,但实际上是因为无法执行pre-commit钩子而导致失败,而不是钩子起作用了。非常感谢Raim,这真的很棒。与所有其他建议相比,它更好、更轻量级,因为它没有依赖项!

2
你是对的。我没有考虑到在同一个代码库中有多个项目,因为这不是我自己使用的设置。 - raimue
太棒了,感谢您的验证和脚本!这绝对是解决问题最优雅的方案。 - apinstein
1
这个脚本是不完整的,它既没有处理删除操作,也没有处理添加或属性更改。 - Michael-O
1
我不得不将grep匹配改为grep "^U\W.*tags\/",因为我的svnlook输出在存储库路径上没有前导斜杠。 - Marcus
这并不完全正确。你应该防止用户在标签中添加、删除文件,而不仅仅是修改。 - mvd

16
以下是一个简短的Shell脚本,用于防止在标签创建后提交更改:
#!/bin/sh

REPOS="$1"
TXN="$2"

SVNLOOK=/usr/bin/svnlook

# Committing to tags is not allowed
$SVNLOOK changed -t "$TXN" "$REPOS" | grep "^U\W*tags" && /bin/echo "Cannot commit to tags!" 1>&2 && exit 1

# All checks passed, so allow the commit.
exit 0

将此文件保存在 Subversion 存储库的 hooks/pre-commit 目录下,并使用 chmod +x 命令授予执行权限。


7
这是我的Windows批处理文件pre-commit钩子。如果用户是管理员,则会跳过其他检查。它检查提交消息是否为空,以及提交是否为标签。注意:findstr是其他平台上grep的削弱替代品。
它检查提交是否为标签的方式是,首先检查svnlook changed是否包含“tags /”。然后,它检查svnlook changed是否匹配“^A. tags / [^ /] / $”,这意味着它将检查您是否正在添加一个新文件夹到tags /下。
允许用户创建新项目。预提交钩子允许用户创建trunk /,tags /和branches /文件夹。不允许用户删除trunk /,tags /和branches /文件夹。这适用于单个或多个项目的存储库。
 @echo off
 rem This pre-commit hook will block commits with no log messages and blocks commits on tags.
 rem Users may create tags, but not modify them.
 rem If the user is an Administrator the commit will succeed.

 rem Specify the username of the repository administrator
 rem commits by this user are not checked for comments or tags
 rem Recommended to change the Administrator only when an admin commit is neccessary
 rem then reset the Administrator after the admin commit is complete
 rem this way the admin user is only an administrator when neccessary
 set Administrator=Administrator

 setlocal

 rem Subversion sends through the path to the repository and transaction id.
 set REPOS=%1%
 set TXN=%2%

 :Main
 rem check if the user is an Administrator
 svnlook author %REPOS% -t %TXN% | findstr /r "^%Administrator%$" >nul
 if %errorlevel%==0 (exit 0)

 rem Check if the commit has an empty log message
 svnlook log %REPOS% -t %TXN% | findstr . > nul
 if %errorlevel% gtr 0 (goto CommentError)

 rem Block deletion of branches and trunk
 svnlook changed %REPOS% -t %TXN% | findstr /r "^D.*trunk/$ ^D.*branches/$" >nul
 if %errorlevel%==0 (goto DeleteBranchTrunkError)

 rem Check if the commit is to a tag
 svnlook changed %REPOS% -t %TXN% | findstr /r "^.*tags/" >nul
 if %errorlevel%==0 (goto TagCommit)
 exit 0

 :DeleteBranchTrunkError
 echo. 1>&2
 echo Trunk/Branch Delete Error: 1>&2
 echo     Only an Administrator may delete the branches or the trunk. 1>&2
 echo Commit details: 1>&2
 svnlook changed %REPOS% -t %TXN% 1>&2
 exit 1

 :TagCommit
 rem Check if the commit is creating a subdirectory under tags/ (tags/v1.0.0.1)
 svnlook changed %REPOS% -t %TXN% | findstr /r "^A.*tags/[^/]*/$" >nul
 if %errorlevel% gtr 0 (goto CheckCreatingTags)
 exit 0

 :CheckCreatingTags
 rem Check if the commit is creating a tags/ directory
 svnlook changed %REPOS% -t %TXN% | findstr /r "^A.*tags/$" >nul
 if %errorlevel% == 0 (exit 0)
 goto TagsCommitError

 :CommentError
 echo. 1>&2
 echo Comment Error: 1>&2
 echo     Your commit has been blocked because you didn't enter a comment. 1>&2
 echo     Write a log message describing your changes and try again. 1>&2
 exit 1

 :TagsCommitError
 echo. 1>&2
 echo %cd% 1>&2
 echo Tags Commit Error: 1>&2
 echo     Your commit to a tag has been blocked. 1>&2
 echo     You are only allowed to create tags. 1>&2
 echo     Tags may only be modified by an Administrator. 1>&2
 echo Commit details: 1>&2
 svnlook changed %REPOS% -t %TXN% 1>&2
 exit 1

一个经过精心打磨、可直接运行的脚本,涵盖了所提出的问题和更多内容 - 做得好! - Michael Sorens

6
这个答案晚了很多,但我发现svnlook changed命令的--copy-info参数。
该命令的输出在第三列中添加了一个“+”,以便您知道它是一份副本。您可以检查是否对标签目录进行了提交,并仅允许存在“+”的提交。
我在我的博客文章中添加了一些输出。

4
大多数之前写的脚本都不完整,因为涉及到的情况没有全面考虑。以下是我的脚本:
contains_tags_dir=`$SVNLOOK changed --copy-info -t "$TXN" "$REPOS" | head -1 | egrep "+\/tags\/.*$" | wc -l | sed "s/ //g"`

if [ $contains_tags_dir -gt 0 ]
then
  tags_dir_creation=`$SVNLOOK changed --copy-info -t "$TXN" "$REPOS" | head -1 | egrep "^A       .+\/tags\/$" | wc -l | sed "s/ //g"`
  if [ $tags_dir_creation -ne 1 ]
  then
    initial_add=`$SVNLOOK changed --copy-info -t "$TXN" "$REPOS" | head -1 | egrep "^A \+ .+\/tags\/.+\/$" | wc -l | sed "s/ //g"`
    if [ $initial_add -ne 1 ]
    then
      echo "Tags cannot be changed!" 1>&2
      exit 1
    fi
  fi
fi

这可能看起来很复杂,但你需要确保你正在 /tags 目录下,并且如果它不存在,你有创建 /tags 和所有后续文件夹的权限。任何其他更改都会被阻止。几乎没有之前的脚本涵盖了 Subversion 书中描述 svnlook changed ... 的所有情况。


使用此钩子需要什么要求?我愿意将其实现为阻止提交到指定目录...但我不明白我需要在哪里编辑它以更改已检查的目录...也许有一些应用程序可以通过将repo dir作为参数来生成这样的脚本...? - HX_unbanned
1
不,你必须将钩子放置在托管存储库的服务器的“hooks”目录中。请阅读有关此事的Subversion书籍。 - Michael-O
是的,我明白了;抱歉问题表述不清 :/ 我的英语对于编程方面来说并不是很好 :( 问题的意思是:如何指定除标签之外的其他存储库目录?例如,分支或ahead或任何其他...但是不要在脚本中硬编码该目录-将其作为参数传递? - HX_unbanned
除非您在上面的脚本中定义了一个可阻止目录的数组并迭代该数组,否则您实际上无法这样做。您的脚本只传递了两个参数。不多也不少。 - Michael-O

4
很晚才加入,但我为工作编写了一个基于log-police.py脚本的Python pre-commit钩子,该脚本可在http://subversion.tigris.org/上找到。
这个脚本应该能做你想要的事情,不过它还会检查日志信息是否存在,但是从脚本中删除应该很容易。
一些注意事项:
- 我是Python新手,所以它可能可以写得更好。 - 它只在Windows 2003上使用Python 2.5和Subversion 1.4进行了测试。
要求:
- Subversion - Python - Python的Subversion绑定
最后,附上代码:
#!/usr/bin/env python

#
# pre-commit.py:
#
# Performs the following:
#  - Makes sure the author has entered in a log message.
#  - Make sure author is only creating a tag, or if deleting a tag, author is a specific user
#
# Script based on http://svn.collab.net/repos/svn/trunk/tools/hook-scripts/log-police.py
#
# usage: pre-commit.py -t TXN_NAME REPOS
# E.g. in pre-commit.bat (under Windows)
#   python.exe {common_hooks_dir}\pre_commit.py -t %2 %1
#


import os
import sys
import getopt
try:
  my_getopt = getopt.gnu_getopt
except AttributeError:
  my_getopt = getopt.getopt

import re

import svn
import svn.fs
import svn.repos
import svn.core

#
# Check Tags functionality
#
def check_for_tags(txn):
  txn_root = svn.fs.svn_fs_txn_root(txn)
  changed_paths = svn.fs.paths_changed(txn_root)
  for path, change in changed_paths.iteritems():
    if is_path_within_a_tag(path): # else go to next path
      if is_path_a_tag(path):
        if (change.change_kind == svn.fs.path_change_delete):
          if not is_txn_author_allowed_to_delete(txn):
            sys.stderr.write("\nOnly an administrator can delete a tag.\n\nContact your Subversion Administrator for details.")
            return False
        elif (change.change_kind != svn.fs.path_change_add):
          sys.stderr.write("\nUnable to modify " + path + ".\n\nIt is within a tag and tags are read-only.\n\nContact your Subversion Administrator for details.")
          return False
        # else user is adding a tag, so accept this change
      else:
        sys.stderr.write("\nUnable to modify " + path + ".\n\nIt is within a tag and tags are read-only.\n\nContact your Subversion Administrator for details.")
        return False
  return True

def is_path_within_a_tag(path):
  return re.search('(?i)\/tags\/', path)

def is_path_a_tag(path):
  return re.search('(?i)\/tags\/[^\/]+\/?$', path)

def is_txn_author_allowed_to_delete(txn):
  author = get_txn_property(txn, 'svn:author')
  return (author == 'bob.smith')

#
# Check log message functionality
#
def check_log_message(txn):
  log_message = get_txn_property(txn, "svn:log")
  if log_message is None or log_message.strip() == "":
    sys.stderr.write("\nCannot enter in empty commit message.\n")
    return False
  else:
    return True

def get_txn_property(txn, prop_name):
  return svn.fs.svn_fs_txn_prop(txn, prop_name)

def usage_and_exit(error_msg=None):
  import os.path
  stream = error_msg and sys.stderr or sys.stdout
  if error_msg:
    stream.write("ERROR: %s\n\n" % error_msg)
  stream.write("USAGE: %s -t TXN_NAME REPOS\n"
               % (os.path.basename(sys.argv[0])))
  sys.exit(error_msg and 1 or 0)

def main(ignored_pool, argv):
  repos_path = None
  txn_name = None

  try:
    opts, args = my_getopt(argv[1:], 't:h?', ["help"])
  except:
    usage_and_exit("problem processing arguments / options.")
  for opt, value in opts:
    if opt == '--help' or opt == '-h' or opt == '-?':
      usage_and_exit()
    elif opt == '-t':
      txn_name = value
    else:
      usage_and_exit("unknown option '%s'." % opt)

  if txn_name is None:
    usage_and_exit("must provide -t argument")
  if len(args) != 1:
    usage_and_exit("only one argument allowed (the repository).")

  repos_path = svn.core.svn_path_canonicalize(args[0])

  fs = svn.repos.svn_repos_fs(svn.repos.svn_repos_open(repos_path))
  txn = svn.fs.svn_fs_open_txn(fs, txn_name)

  if check_log_message(txn) and check_for_tags(txn):
    sys.exit(0)
  else:
    sys.exit(1)

if __name__ == '__main__':
  sys.exit(svn.core.run_app(main, sys.argv))

3

接受的答案可以防止在标签中更新文件,但无法防止向标签中添加文件。以下版本可同时处理:

#!/bin/sh

REPOS="$1"
TXN="$2"
SVNLOOK="/home/staging/thirdparty/subversion-1.6.17/bin/svnlook"

# Committing to tags is not allowed
$SVNLOOK changed -t "$TXN" "$REPOS" --copy-info| grep -v "^ " | grep -P '^[AU]   \w+/tags/' && /bin/echo "Cannot update tags!" 1>&2 && exit 1

# All checks passed, so allow the commit.
exit 0

我能否将 svn://<root>/tags 中的标签更改为分支或其他子目录?例如,<root>/<some_directory>/<some_other_dir>? - HX_unbanned
由于某些原因,这对我不起作用。我尝试了这个,它运行良好。$ SVNLOOK changed -t“$TXN”“$REPOS”| grep“^ [AUD_]。 。*/tags/.*/。” && /bin/echo'无法更新标签!'1>&2 &&退出2 - VRK

1
如果您正在使用JIRA,您可以使用名为Commit Policy的插件来保护您的存储库中路径,而无需编写自定义挂钩
如何操作?使用名为Changed files must match a pattern的条件即可。
它具有正则表达式类型参数,必须匹配提交中的每个文件,否则提交将被拒绝。因此,在您的情况下,应该使用一个意思是“不以前缀/tags/开头”的正则表达式。
(您可以使用相同的插件实现许多其他智能检查。)
免责声明:我是这个付费插件的开发人员。

1

由于第一个答案没有防止添加/删除文件,并且防止新标签的创建,以及许多其他不完整或有缺陷的地方,因此我进行了重新制作。

这是我的预提交钩子:

  • 禁止在标签上提交(文件添加/删除/更新)
  • 不要阻止标签的创建

--------- 文件“pre-commit”(放在存储库的hooks文件夹中) ---------

#!/bin/sh

REPOS="$1"
TXN="$2"

SVNLOOK=/usr/bin/svnlook

#Logs
#$SVNLOOK changed -t "$TXN" "$REPOS" > /tmp/changes
#echo "$TXN" > /tmp/txn
#echo "$REPOS" > /tmp/repos

# Committing to tags is not allowed
# Forbidden changes are Update/Add/Delete.  /W = non alphanum char  Redirect is necessary to get the error message, since regular output is lost.
# BUT, we must allow tag creation / suppression

$SVNLOOK changed -t "$TXN" "$REPOS" | /bin/grep "^A\W.*tags\/[0-9._-]*\/." && /bin/echo "Commit to tags are NOT allowed ! (Admin custom rule)" 1>&2 && exit 101
$SVNLOOK changed -t "$TXN" "$REPOS" | /bin/grep "^U\W.*tags\/[0-9._-]*\/." && /bin/echo "Commit to tags are NOT allowed ! (Admin custom rule)" 1>&2 && exit 102
$SVNLOOK changed -t "$TXN" "$REPOS" | /bin/grep "^D\W.*tags\/[0-9._-]*\/." && /bin/echo "Commit to tags are NOT allowed ! (Admin custom rule)" 1>&2 && exit 104

# All checks passed, so allow the commit.
exit 0;

--------- 文件 "pre-commit" 结束 ---------

此外,我编写了两个 shell 脚本,以便将我的钩子复制到 svn 的每个项目中: 一个用于设置仓库只读:

--------- 脚本 "setOneRepoTagsReadOnly.sh" ---------

#!/bin/sh

cd /var/svn/repos/svn
zeFileName=$1/hooks/pre-commit
/bin/cp ./CUSTOM_HOOKS/pre-commit $zeFileName
chown www-data:www-data $zeFileName
chmod +x $zeFileName

--------- 文件 "setOneRepoTagsReadOnly.sh" 结束 ---------

并为每个仓库调用它,使我所有的仓库都变成只读:

--------- 文件 "makeTagsReadOnly.sh" ---------

#!/bin/shs/svn                                                                                                                                                                         
#Lists all repos, and adds the pre-commit hook to protect tags on each of them
find /var/svn/repos/svn/ -maxdepth 1 -mindepth 1 -type d -execdir '/var/svn/repos/svn/setOneRepoTagsReadOnly.sh' \{\} \;

--------- 文件“makeTagsReadOnly.sh”的结尾 ---------

我直接从svn“根目录”(在我的情况下为/var/svn/repos/svn)执行这些脚本。 顺便说一句,可以设置一个cron任务,每天自动执行这些脚本来修改新的repos。

希望能有所帮助。


1

我的版本只允许创建和删除标签。这应该处理所有特殊情况(如添加文件、更改属性等)。

#!/bin/sh

REPOS="$1"
TXN="$2"
SVNLOOK=/usr/local/bin/svnlook

output_error_and_exit() {
    echo "$1" >&2
    exit 1
}

changed_tags=$( $SVNLOOK changed -t "$TXN" "$REPOS" | grep "[ /]tags/." )

if [ "$changed_tags" ]
then 
    echo "$changed_tags" | egrep -v "^[AD] +(.*/)?tags/[^/]+/$" && output_error_and_exit "Modification of tags is not allowed."
fi 

exit 0

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