重写存储库主干中所有 svn:externals 的脚本/实用程序

发布于 2024-11-16 18:19:41 字数 303 浏览 5 评论 0原文

假设有人希望将其存储库中的所有绝对 svn:externals URL 转换为相对 URL。

或者,如果留意 svn:externals 文档中的提示 (“您应该认真考虑使用显式修订号...”),人们可能会发现自己需要定期在整个存储库的许多地方提取外部的新修订。

以编程方式更新大量 svn:externals 属性的最佳方法是什么?

我的解决方案发布在下面。

Say that one wishes to convert all absolute svn:externals URLS to relative URLS throughout their repository.

Alternatively, if heeding the tip in the svn:externals docs ("You should seriously consider using explicit revision numbers..."), one might find themselves needing to periodically pull new revisions for externals in many places throughout the repository.

What's the best way to programmatically update a large number of svn:externals properties?

My solution is posted below.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

滥情稳全场 2024-11-23 18:19:41

这是我的类,用于从 svn:externals 属性的单行中提取部分:

from urlparse import urlparse
import re
class SvnExternalsLine:
    '''Consult https://subversion.apache.org/docs/release-notes/1.5.html#externals for parsing algorithm.
    The old svn:externals format consists of:
        <local directory> [revision] <absolute remote URL>

    The NEW svn:externals format consists of:
        [revision] <absolute or relative remote URL> <local directory>

    Therefore, "relative" remote paths always come *after* the local path.
    One complication is the possibility of local paths with spaces.
    We just assume that the remote path cannot have spaces, and treat all other
    tokens (except the revision specifier) as part of the local path.
    '''

    REVISION_ARGUMENT_REGEXP = re.compile("-r(\d+)")

    def __init__(self, original_line):
        self.original_line = original_line

        self.pinned_revision_number = None
        self.repo_url = None
        self.local_pathname_components = []

        for token in self.original_line.split():

            revision_match = self.REVISION_ARGUMENT_REGEXP.match(token)
            if revision_match:
                self.pinned_revision_number = int(revision_match.group(1))
            elif urlparse(token).scheme or any(map(lambda p: token.startswith(p), ["^", "//", "/", "../"])):
                self.repo_url = token
            else:
                self.local_pathname_components.append(token)

    # ---------------------------------------------------------------------
    def constructLine(self):
        '''Reconstruct the externals line in the Subversion 1.5+ format'''

        tokens = []

        # Update the revision specifier if one existed
        if self.pinned_revision_number is not None:
            tokens.append( "-r%d" % (self.pinned_revision_number) )

        tokens.append( self.repo_url )
        tokens.extend( self.local_pathname_components )

        if self.repo_url is None:
            raise Exception("Found a bad externals property: %s; Original definition: %s" % (str(tokens), repr(self.original_line)))

        return " ".join(tokens)

我使用 pysvn 库递归地迭代拥有 svn:externals 属性的所有目录,然后将该属性值拆分为换行符,并根据解析的 SvnExternalsLine 对每一行进行操作。

该过程必须在存储库的本地签出上执行。下面介绍了如何使用 pysvn (propget)检索外部:

client.propget( "svn:externals", base_checkout_path, recurse=True)

迭代该函数的返回值,并在修改每个目录的属性后,

client.propset("svn:externals", new_externals_property, path)

Here's my class to extract parts from a single line of an svn:externals property:

from urlparse import urlparse
import re
class SvnExternalsLine:
    '''Consult https://subversion.apache.org/docs/release-notes/1.5.html#externals for parsing algorithm.
    The old svn:externals format consists of:
        <local directory> [revision] <absolute remote URL>

    The NEW svn:externals format consists of:
        [revision] <absolute or relative remote URL> <local directory>

    Therefore, "relative" remote paths always come *after* the local path.
    One complication is the possibility of local paths with spaces.
    We just assume that the remote path cannot have spaces, and treat all other
    tokens (except the revision specifier) as part of the local path.
    '''

    REVISION_ARGUMENT_REGEXP = re.compile("-r(\d+)")

    def __init__(self, original_line):
        self.original_line = original_line

        self.pinned_revision_number = None
        self.repo_url = None
        self.local_pathname_components = []

        for token in self.original_line.split():

            revision_match = self.REVISION_ARGUMENT_REGEXP.match(token)
            if revision_match:
                self.pinned_revision_number = int(revision_match.group(1))
            elif urlparse(token).scheme or any(map(lambda p: token.startswith(p), ["^", "//", "/", "../"])):
                self.repo_url = token
            else:
                self.local_pathname_components.append(token)

    # ---------------------------------------------------------------------
    def constructLine(self):
        '''Reconstruct the externals line in the Subversion 1.5+ format'''

        tokens = []

        # Update the revision specifier if one existed
        if self.pinned_revision_number is not None:
            tokens.append( "-r%d" % (self.pinned_revision_number) )

        tokens.append( self.repo_url )
        tokens.extend( self.local_pathname_components )

        if self.repo_url is None:
            raise Exception("Found a bad externals property: %s; Original definition: %s" % (str(tokens), repr(self.original_line)))

        return " ".join(tokens)

I use the pysvn library to iterate recursively through all of the directories possessing the svn:externals property, then split that property value by newlines, and act upon each line according to the parsed SvnExternalsLine.

The process must be performed on a local checkout of the repository. Here's how pysvn (propget) can be used to retrieve the externals:

client.propget( "svn:externals", base_checkout_path, recurse=True)

Iterate through the return value of this function, and and after modifying the property on each directory,

client.propset("svn:externals", new_externals_property, path)
溺ぐ爱和你が 2024-11-23 18:19:41

kostmo 的实现(十多年前提供)不支持带有空格的固定修订号定义。例如,在下面的有效外部定义中:

   foo/bar -r 1234 http://example.com/repos/zag
   -r 1234 http://example.com/repos/zag foo/bar1

“-r 1234”部分将被解析为本地路径,而不是修订规范。

我的实现如下,它解决了这个问题:

#!/usr/bin/python3

import re
import sys
import traceback
from urllib.parse import urlparse


class SvnExternalsLine:
   '''Consult https://subversion.apache.org/docs/release-notes/1.5.html#externals for parsing algorithm.
   The old svn:externals format consists of:
     <local directory> [revision] <absolute remote URL>

   The NEW svn:externals format consists of:
     [revision] <absolute or relative remote URL> <local directory>

   Therefore, "relative" remote paths always come *before* the local path.
   When Subversion sees an svn:externals without an absolute URL,
   it takes the first argument as a relative URL and the second as the target directory.
   One complication is the possibility of local paths with spaces.
   We just assume that the remote path cannot have spaces, and treat all other
   tokens (except the revision specifier) as part of the local path.
   '''

   OLD_FORMAT_REGEXP = re.compile(r'^\s*(?P<loc>.*?)(\s*-r\s*(?P<rev>\d+))?\s+(?P<url>\S+)\s*
)
   NEW_FORMAT_REGEXP = re.compile(r'^\s*(-r\s*(?P<rev>\d+)\s*)?(?P<url>\S+)\s+(?P<loc>.*?)\s*
)

   def __init__(self, original_line):
      self.original_line = original_line

      self.pinned_revision_number = None
      self.repo_url = None
      self.local_pathname = None

      is_abs_url = lambda s: urlparse(s).scheme
      is_old = is_abs_url(original_line.split()[-1])
      regexp = SvnExternalsLine.OLD_FORMAT_REGEXP if is_old else SvnExternalsLine.NEW_FORMAT_REGEXP

      m = regexp.fullmatch(original_line)
      self.repo_url = m.group('url')
      self.local_pathname = m.group('loc')
      self.pinned_revision_number = m.group('rev')


   def constructLine(self):
      '''Reconstruct the externals line in the Subversion 1.5+ format'''
      line = f'{self.repo_url} {self.local_pathname}'
      if self.pinned_revision_number is not None:
          line = f'-r{self.pinned_revision_number} {line}'

      return line

The kostmo's implementation (provided more than 10 years ago) does not support pinned revision number definitions with space. E.g. in the valid external definitions below:

   foo/bar -r 1234 http://example.com/repos/zag
   -r 1234 http://example.com/repos/zag foo/bar1

the "-r 1234" part will be parsed as a local path, instead of a revision specification.

My implementation below, which fixes this issue:

#!/usr/bin/python3

import re
import sys
import traceback
from urllib.parse import urlparse


class SvnExternalsLine:
   '''Consult https://subversion.apache.org/docs/release-notes/1.5.html#externals for parsing algorithm.
   The old svn:externals format consists of:
     <local directory> [revision] <absolute remote URL>

   The NEW svn:externals format consists of:
     [revision] <absolute or relative remote URL> <local directory>

   Therefore, "relative" remote paths always come *before* the local path.
   When Subversion sees an svn:externals without an absolute URL,
   it takes the first argument as a relative URL and the second as the target directory.
   One complication is the possibility of local paths with spaces.
   We just assume that the remote path cannot have spaces, and treat all other
   tokens (except the revision specifier) as part of the local path.
   '''

   OLD_FORMAT_REGEXP = re.compile(r'^\s*(?P<loc>.*?)(\s*-r\s*(?P<rev>\d+))?\s+(?P<url>\S+)\s*
)
   NEW_FORMAT_REGEXP = re.compile(r'^\s*(-r\s*(?P<rev>\d+)\s*)?(?P<url>\S+)\s+(?P<loc>.*?)\s*
)

   def __init__(self, original_line):
      self.original_line = original_line

      self.pinned_revision_number = None
      self.repo_url = None
      self.local_pathname = None

      is_abs_url = lambda s: urlparse(s).scheme
      is_old = is_abs_url(original_line.split()[-1])
      regexp = SvnExternalsLine.OLD_FORMAT_REGEXP if is_old else SvnExternalsLine.NEW_FORMAT_REGEXP

      m = regexp.fullmatch(original_line)
      self.repo_url = m.group('url')
      self.local_pathname = m.group('loc')
      self.pinned_revision_number = m.group('rev')


   def constructLine(self):
      '''Reconstruct the externals line in the Subversion 1.5+ format'''
      line = f'{self.repo_url} {self.local_pathname}'
      if self.pinned_revision_number is not None:
          line = f'-r{self.pinned_revision_number} {line}'

      return line
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文