Follow-up to r29036: Now that the "mergeinfo" transaction file is no
[svn.git] / contrib / hook-scripts / remove-zombie-locks.py
bloba0b4b25a1840f1f36ea0d07d28cbc94eebbc3221
1 #!/usr/bin/env python
3 # ====================================================================
4 # Copyright (c) 2006 CollabNet. All rights reserved.
6 # This software is licensed as described in the file COPYING, which
7 # you should have received as part of this distribution. The terms
8 # are also available at http://subversion.tigris.org/license-1.html.
9 # If newer versions of this license are posted there, you may use a
10 # newer version instead, at your option.
12 # This software consists of voluntary contributions made by many
13 # individuals. For exact contribution history, see the revision
14 # history and logs, available at http://subversion.tigris.org/.
15 # ====================================================================
18 """\
19 remove-zombie-locks.py - remove zombie locks on deleted files
21 Usage: remove-zombie-locks.py REPOS-PATH <REVISION|all>
23 When REVISION (an interger) is specified this script scans a commited
24 revision for deleted files and checks if a lock exists for any of these
25 files. If any locks exist they are forcibly removed.
27 When "all" is specified this script scans the whole repository for
28 locks on files that don't exist in the HEAD revision, removing any found.
30 This script is a workaround for Subversion issue #2507
31 http://subversion.tigris.org/issues/show_bug.cgi?id=2507
33 Examples:
35 As a post-commit Hook script to prevent zombie locks:
36 remove-zombie-locks.py /var/svn/myrepo 6174
38 To clean a repository with existing zombie locks:
39 remove-zombie-locks.py /var/svn/myrepo all
41 For additional information read the commented notes in this script.
42 """
45 # ** FAQ **
47 # What is the problem, exactly?
49 # When you commit a file deletion with the --keep-locks option then
50 # though the file is deleted in the repository, the lock is not.
51 # This is a bug in Subversion. If locks are left on deleted files
52 # then any future attempt to add a file of the same name or
53 # delete/move/rename any parent directory will fail. This is very
54 # difficult for the end-user to fix since there's no easy way to find
55 # out the names of these zombie locks, and it is not simple to remove
56 # them even if you do know the names.
58 # Is this script 100% safe?
60 # There is a theoretical and very small chance that before this
61 # script runs another commit adds a file with the same path as one
62 # just deleted in this revision, and then a lock is aquired for it,
63 # resulting in this script unlocking the "wrong" file. In practice it
64 # seems highly improbable and would require very strange performance
65 # characteristics on your svn server. However, to minimize the
66 # window for error it is recommended to run this script first in your
67 # post-commit hook.
69 # How Do I Start Using This Script?
71 # 1. Once only, run this script in 'all' mode to start your repo out clean
72 # 2. Call this script from your post-commit hook to keep your repo clean
75 import os
76 import sys
78 import svn.core
79 import svn.repos
80 import svn.fs
82 assert (svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR) >= (1, 2), \
83 "Subversion 1.2 or later required but only have " \
84 + str(svn.core.SVN_VER_MAJOR) + "." + str(svn.core.SVN_VER_MINOR)
86 def usage_and_exit():
87 print >> sys.stderr, __doc__
88 sys.exit(1)
90 class RepositoryZombieLockRemover:
91 """Remove all locks on non-existant files in repository@HEAD"""
92 def __init__(self, repos_path, repos_subpath=""):
93 self.repos_path = repos_path # path to repository on disk
94 self.repos_subpath = repos_subpath # if only cleaning part of the repo
96 # init svn
97 svn.core.apr_initialize()
98 self.pool = svn.core.svn_pool_create(None)
99 self.repos_ptr = svn.repos.open(self.repos_path, self.pool)
100 self.fs_ptr = svn.repos.fs(self.repos_ptr)
101 self.rev_root = svn.fs.revision_root(self.fs_ptr,
102 svn.fs.youngest_rev(self.fs_ptr,
103 self.pool),
104 self.pool)
106 def __del__(self):
107 svn.core.svn_pool_destroy(self.pool)
108 svn.core.apr_terminate()
110 def unlock_nonexistant_files(self, lock, callback_pool):
111 """check if the file still exists in HEAD, removing the lock if not"""
112 if svn.fs.svn_fs_check_path(self.rev_root, lock.path, callback_pool) \
113 == svn.core.svn_node_none:
114 print lock.path
115 svn.repos.svn_repos_fs_unlock(self.repos_ptr, lock.path, lock.token,
116 True, callback_pool)
118 def run(self):
119 """iterate over every locked file in repo_path/repo_subpath,
120 calling unlock_nonexistant_files for each"""
122 print "Removing all zombie locks from repository at %s\n" \
123 "This may take several minutes..." % self.repos_path
124 svn.fs.svn_fs_get_locks(self.fs_ptr, self.repos_subpath,
125 self.unlock_nonexistant_files, self.pool)
126 print "Done."
129 class RevisionZombieLockRemover:
130 """Remove all locks on files deleted in a revision"""
131 def __init__(self, repos_path, repos_rev):
132 self.repos_path = repos_path # path to repository on disk
134 # init svn
135 svn.core.apr_initialize()
136 self.pool = svn.core.svn_pool_create(None)
137 self.repos_ptr = svn.repos.open(self.repos_path, self.pool)
138 self.fs_ptr = svn.repos.fs(self.repos_ptr)
139 self.rev_root = svn.fs.revision_root(self.fs_ptr, repos_rev, self.pool)
141 def __del__(self):
142 svn.core.svn_pool_destroy(self.pool)
143 svn.core.apr_terminate()
145 def get_deleted_paths(self):
146 """return list of deleted paths in a revision"""
147 deleted_paths = []
148 for path, change in \
149 svn.fs.paths_changed(self.rev_root, self.pool).iteritems():
150 if (change.change_kind == svn.fs.path_change_delete):
151 deleted_paths.append(path)
152 return deleted_paths
154 def run(self):
155 """remove any existing locks on files that are deleted in this revision"""
156 deleted_paths = self.get_deleted_paths()
157 subpool = svn.core.svn_pool_create(self.pool)
158 for path in deleted_paths:
159 svn.core.svn_pool_clear(subpool)
160 lock = svn.fs.svn_fs_get_lock(self.fs_ptr, path, subpool)
161 if lock:
162 svn.repos.svn_repos_fs_unlock(self.repos_ptr, path,
163 lock.token, True, subpool)
164 svn.core.svn_pool_destroy(subpool)
167 def main():
168 if len(sys.argv) < 3:
169 usage_and_exit()
170 repos_path = os.path.abspath(sys.argv[1])
171 if sys.argv[2].lower() == "all":
172 remover = RepositoryZombieLockRemover(repos_path, "")
173 else:
174 try:
175 repos_rev = int(sys.argv[2])
176 except ValueError:
177 usage_and_exit()
178 remover = RevisionZombieLockRemover(repos_path, repos_rev)
180 remover.run()
182 sys.exit(0)
185 if __name__ == "__main__":
186 main()