| #!/usr/bin/env python |
| # |
| # Adds missing svn:needs-lock property directly on repository files. |
| # Direct access to the repository is required. |
| # |
| # Specify -d to perform a "dry run". This just indicates what needs to be done. |
| # Specify -i REGEXP to only include file names matching the regular expression. |
| # (Defaults to all files) |
| # Specify -e REGEXP to exclude file names matching the regular expression. |
| # Specify -r REV to operate only on the files added in the specified revision. |
| # |
| # Example: Add the svn:needs-lock property to any non .c files in /trunk |
| # |
| # add-needs-lock.py -i "/trunk/.*" -e ".*\.c" /path/to/repo |
| # |
| # |
| # Copyright 2008 Kevin Radke <kmradke@gmail.com> |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| # |
| # $HeadURL$ |
| # $LastChangedDate$ |
| # $LastChangedBy$ |
| # $LastChangedRevision$ |
| |
| import sys |
| import os |
| import re |
| import getopt |
| try: |
| my_getopt = getopt.gnu_getopt |
| except AttributeError: |
| my_getopt = getopt.getopt |
| |
| try: |
| import svn.core |
| import svn.fs |
| import svn.repos |
| except ImportError as e: |
| sys.stderr.write("ERROR: Unable to import Subversion's Python bindings: '%s'\n" % e) |
| sys.exit(1) |
| |
| # Walk a tree returning file paths |
| ################################################################################ |
| def walk_tree(root, path): |
| files = [] |
| |
| for name in svn.fs.dir_entries(root, path).keys(): |
| full = path + '/' + name |
| if svn.fs.is_dir(root, full): |
| subfiles = walk_tree(root, full) |
| for subfile in subfiles: |
| files.append(subfile) |
| else: |
| files.append(full) |
| |
| return files |
| |
| # Get a list of files |
| ################################################################################ |
| def get_file_list(root, included, excluded): |
| files = [] |
| regexp = re.compile(included) |
| regexpout = re.compile(excluded) |
| all_files = walk_tree(root, '') |
| |
| for path in all_files: |
| # Must match include and not match exclude regexp |
| if regexp.match(path) and not regexpout.match(path): |
| files.append(path) |
| |
| return files |
| |
| # Get a list of files added in the specified revision |
| ################################################################################ |
| def get_rev_file_list(revroot, included, excluded): |
| files = [] |
| regexp = re.compile(included) |
| regexpout = re.compile(excluded) |
| |
| for path, change in svn.fs.paths_changed(revroot).iteritems(): |
| # Must be an add or replace |
| if (change.change_kind == svn.fs.path_change_add |
| or change.change_kind == svn.fs.path_change_replace): |
| # Must be a file |
| if (svn.fs.check_path(revroot, path) == svn.core.svn_node_file): |
| # Must match include and not match exclude regexp |
| if regexp.match(path) and not regexpout.match(path): |
| files.append(path) |
| |
| return files |
| |
| # Add missing svn:needs-lock to any files directly in the repository |
| ################################################################################ |
| def addneedslock(repos_path, uname='', commitmsg='', included='.*', excluded='^$', rev=None, dryrun=None): |
| canon_path = svn.core.svn_path_canonicalize(repos_path) |
| repos_ptr = svn.repos.open(canon_path) |
| fsob = svn.repos.fs(repos_ptr) |
| |
| # Get the HEAD revision |
| headrev = svn.fs.youngest_rev(fsob) |
| root = svn.fs.revision_root(fsob, headrev) |
| |
| if rev is None: |
| # Get list of all latest files in repository |
| files = get_file_list(root, included, excluded) |
| else: |
| # Get list of all files changed in the revision |
| revroot = svn.fs.revision_root(fsob, rev) |
| files = get_rev_file_list(revroot, included, excluded) |
| |
| interesting_files = [] |
| |
| print('Searching ' + str(len(files)) + ' file(s)...') |
| |
| for path in files: |
| locked_val = svn.fs.get_lock(fsob, path) |
| # Must not be locked |
| if locked_val is None: |
| needslock_prop_val = svn.fs.node_prop(root, path, svn.core.SVN_PROP_NEEDS_LOCK) |
| # Must not already have svn:needs-lock property set |
| if needslock_prop_val is None: |
| interesting_files.append(path) |
| |
| if interesting_files: |
| if dryrun: |
| for path in interesting_files: |
| print("Need to add svn:needs-lock to '" + path + "'") |
| else: |
| # open a transaction against HEAD |
| headrev = svn.fs.youngest_rev(fsob) |
| txn = svn.repos.fs_begin_txn_for_commit(repos_ptr, headrev, uname, commitmsg) |
| root = svn.fs.txn_root(txn) |
| |
| for path in interesting_files: |
| print("Adding svn:needs-lock to '" + path + "'...") |
| svn.fs.change_node_prop(root, path, svn.core.SVN_PROP_NEEDS_LOCK, '*') |
| |
| conflict, newrev = svn.fs.commit_txn(txn) |
| if conflict: |
| raise Exception("Conflict encountered (%s)" % conflict) |
| |
| print('Created revision: ', newrev) |
| else: |
| print('Nothing changed. Current Revision: ', headrev) |
| |
| |
| ################################################################################ |
| def usage(): |
| print("USAGE: add-needs-lock.py [-u username] [-m commitmsg] [-i includeregexp] [-e excluderegexp] [-r REV] [-d] REPOS-PATH") |
| sys.exit(1) |
| |
| |
| ################################################################################ |
| def main(): |
| opts, args = my_getopt(sys.argv[1:], 'u:m:i:e:r:d') |
| |
| uname = 'svnadmin' |
| commitmsg = 'Added missing svn:needs-lock property' |
| included = '.*' |
| excluded = '^$' |
| rev = None |
| dryrun = None |
| |
| for name, value in opts: |
| if name == '-u': |
| uname = value |
| if name == '-m': |
| commitmsg = value |
| if name == '-i': |
| included = value |
| if name == '-e': |
| excluded = value |
| if name == '-r': |
| rev = int(value) |
| if name == '-d': |
| print('Performing dry run...') |
| dryrun = 1 |
| if rev is None: |
| print('Searching all files...') |
| else: |
| print('Searching revision: ' + str(rev) + '...') |
| if len(args) == 1: |
| addneedslock(args[0], uname, commitmsg, included, excluded, rev, dryrun) |
| else: |
| usage() |
| |
| if __name__ == '__main__': |
| main() |
| sys.exit(0) |