| #!/usr/bin/env python |
| |
| # ==================================================================== |
| # |
| # svn-tweak-author.py |
| # |
| # This script allows for server-side tweaks of the svn:author property |
| # in two modes: single-revision and search-and-replace. Both modes |
| # bypass the repository hook subsystem. |
| # |
| # ==================================================================== |
| # Copyright (c) 2007 CollabNet. All rights reserved. |
| # |
| # This software is licensed as described in the file COPYING, which |
| # you should have received as part of this distribution. The terms |
| # are also available at http://subversion.tigris.org/license-1.html. |
| # If newer versions of this license are posted there, you may use a |
| # newer version instead, at your option. |
| # |
| # This software consists of voluntary contributions made by many |
| # individuals. For exact contribution history, see the revision |
| # history and logs, available at http://subversion.tigris.org/. |
| # ==================================================================== |
| |
| import sys |
| import os |
| from svn import repos, fs, core |
| |
| def error_and_exit(errmsg): |
| """Print ERRMSG as an error, and exit with a non-zero error code.""" |
| sys.stderr.write("\nERROR: %s\n" % (errmsg)) |
| sys.exit(1) |
| |
| def usage_and_exit(errmsg=None): |
| """Print the usage message, to stderr if ERRMSG is provided, to |
| stdout otherwise. If ERRMSG is provided, print it as an error, |
| too.""" |
| cmd = os.path.basename(sys.argv[0]) |
| stream = errmsg and sys.stderr or sys.stdout |
| stream.write("""Usage: 1. %s REPOS-PATH replace OLDAUTHOR [NEWAUTHOR] |
| 2. %s REPOS-PATH revision REV [NEWAUTHOR] |
| |
| Change the svn:author property for one or more revisions of the |
| repository located at REPOS-PATH. If in "replace" mode, any instance |
| of an author named OLDAUTHOR is changed to NEWAUTHOR. If in "revision" |
| mode, simply change the author of the single revision REV to NEWAUTHOR. |
| In either mode, if NEWAUTHOR is not provided, the existing author will |
| be deleted. |
| |
| WARNING: Changing revision properties is not a versioned event, and |
| this script will bypass the repository's hook subsystem (so |
| you won't see notification emails and such from any changes |
| made with this script). |
| """ % (cmd, cmd)) |
| if errmsg: |
| error_and_exit(errmsg) |
| else: |
| sys.exit(0) |
| |
| def fetch_rev_author(fs_obj, revision): |
| """Return the value of the svn:author property for REVISION in |
| repository filesystem FS_OBJ.""" |
| return fs.svn_fs_revision_prop(fs_obj, revision, |
| core.SVN_PROP_REVISION_AUTHOR) |
| |
| def tweak_rev_author(fs_obj, revision, author): |
| """Change the value of the svn:author property for REVISION in |
| repository filesystem FS_OBJ in AUTHOR.""" |
| if author is None: |
| sys.stdout.write("Deleting author for revision %d... " % (revision)) |
| else: |
| sys.stdout.write("Tweaking author for revision %d... " % (revision)) |
| try: |
| fs.svn_fs_change_rev_prop(fs_obj, revision, |
| core.SVN_PROP_REVISION_AUTHOR, author) |
| except: |
| print("") |
| raise |
| print("done.") |
| |
| def get_fs_obj(repos_path): |
| """Return a repository filesystem object for the repository |
| located at REPOS_PATH (which must obey the Subversion path |
| canonicalization rules).""" |
| return repos.svn_repos_fs(repos.svn_repos_open(repos_path)) |
| |
| def main(): |
| argc = len(sys.argv) |
| if argc < 4 or argc > 5: |
| usage_and_exit("Not enough arguments provided.") |
| try: |
| repos_path = core.svn_path_canonicalize(sys.argv[1]) |
| except AttributeError: |
| repos_path = os.path.normpath(sys.argv[1]) |
| if repos_path[-1] == '/' and len(repos_path) > 1: |
| repos_path = repos_path[:-1] |
| try: |
| author = sys.argv[4] |
| except IndexError: |
| author = None |
| mode = sys.argv[2] |
| try: |
| if mode == "replace": |
| old_author = sys.argv[3] |
| fs_obj = get_fs_obj(repos_path) |
| for revision in range(fs.svn_fs_youngest_rev(fs_obj) + 1): |
| if fetch_rev_author(fs_obj, revision) == old_author: |
| tweak_rev_author(fs_obj, revision, author) |
| elif mode == "revision": |
| try: |
| revision = int(sys.argv[3]) |
| except ValueError: |
| usage_and_exit("Invalid revision number (%s) provided." |
| % (sys.argv[3])) |
| tweak_rev_author(get_fs_obj(repos_path), revision, author) |
| else: |
| usage_and_exit("Invalid mode (%s) provided." % (mode)) |
| except SystemExit: |
| raise |
| except Exception as e: |
| error_and_exit(str(e)) |
| |
| if __name__ == "__main__": |
| main() |