Fix compiler warning due to missing function prototype.
[svn.git] / contrib / hook-scripts / case-insensitive.py
blobc9e835a2959a8242efc901b6e2ef361bebdc1590
1 #!/usr/bin/env python
3 # Licensed under the same terms as Subversion.
5 # A pre-commit hook to detect case-insensitive filename clashes.
7 # What this script does:
8 # - Detects new paths that 'clash' with existing, or other new, paths.
9 # - Ignores existings paths that already 'clash'
10 # - Exits with an error code, and a diagnostic on stderr, if 'clashes'
11 # are detected.
13 # How it does it:
14 # - Get a list of changed paths.
15 # - From that list extract the new paths that represent adds or replaces.
16 # - For each new path:
17 # - Split the path into a directory and a name.
18 # - Get the names of all the entries in the version of the directory
19 # within the txn.
20 # - Compare the canonical new name with each canonical entry name.
21 # - If the canonical names match and the pristine names do not match
22 # then we have a 'clash'.
24 # Notes:
25 # - All the paths from the Subversion filesystem bindings are encoded
26 # in UTF-8 and the separator is '/' on all OS's.
27 # - The canonical form determines what constitutes a 'clash', at present
28 # a simple 'lower case' is used. That's probably not identical to the
29 # behaviour of Windows or OSX, but it might be good enough.
30 # - Hooks get invoked with an empty environment so this script explicitly
31 # sets a locale; make sure it is a sensible value.
32 # - If used with Apache the 'clash' diagnostic must be ASCII irrespective
33 # of the locale, see the 'Force' comment near the end of the script for
34 # one way to achieve this.
36 # How to call it:
38 # On a Unix system put this script in the hooks directory and add this to
39 # the pre-commit script:
41 # $REPOS/hooks/case-insensitive.py "$REPOS" "$TXN" || exit 1
43 # On a windows machine add this to pre-commit.bat:
45 # python <path-to-script>\case-insensitive.py %1 %2
46 # if errorlevel 1 goto :ERROR
47 # exit 0
48 # :ERROR
49 # echo Error found in commit 1>&2
50 # exit 1
52 # Make sure the python bindings are installed and working on Windows. The
53 # zip file can be downloaded from the Subversion site. The bindings depend
54 # on dll's shipped as part of the Subversion binaries, if the script cannot
55 # load the _fs dll it is because it cannot find the other Subversion dll's.
57 # $HeadURL$
58 # $LastChangedRevision$
59 # $LastChangedDate$
60 # $LastChangedBy$
62 import sys, locale
63 sys.path.append('/usr/local/subversion/lib/svn-python')
64 from svn import repos, fs
65 locale.setlocale(locale.LC_ALL, 'en_GB')
67 def canonicalize(path):
68 return path.decode('utf-8').lower().encode('utf-8')
70 def get_new_paths(txn_root):
71 new_paths = []
72 for path, change in fs.paths_changed(txn_root).iteritems():
73 if (change.change_kind == fs.path_change_add
74 or change.change_kind == fs.path_change_replace):
75 new_paths.append(path)
76 return new_paths
78 def split_path(path):
79 slash = path.rindex('/')
80 if (slash == 0):
81 return '/', path[1:]
82 return path[:slash], path[slash+1:]
84 def join_path(dir, name):
85 if (dir == '/'):
86 return '/' + name
87 return dir + '/' + name
89 def ensure_names(path, names, txn_root):
90 if (not names.has_key(path)):
91 names[path] = []
92 for name, dirent in fs.dir_entries(txn_root, path).iteritems():
93 names[path].append([canonicalize(name), name])
95 names = {} # map of: key - path, value - list of two element lists of names
96 clashes = {} # map of: key - path, value - map of: key - path, value - dummy
98 native = locale.getlocale()[1]
99 if not native: native = 'ascii'
100 repos_handle = repos.open(sys.argv[1].decode(native).encode('utf-8'))
101 fs_handle = repos.fs(repos_handle)
102 txn_handle = fs.open_txn(fs_handle, sys.argv[2].decode(native).encode('utf-8'))
103 txn_root = fs.txn_root(txn_handle)
105 new_paths = get_new_paths(txn_root)
106 for path in new_paths:
107 dir, name = split_path(path)
108 canonical = canonicalize(name)
109 ensure_names(dir, names, txn_root)
110 for name_pair in names[dir]:
111 if (name_pair[0] == canonical and name_pair[1] != name):
112 canonical_path = join_path(dir, canonical)
113 if (not clashes.has_key(canonical_path)):
114 clashes[canonical_path] = {}
115 clashes[canonical_path][join_path(dir, name)] = True
116 clashes[canonical_path][join_path(dir, name_pair[1])] = True
118 if (clashes):
119 # native = 'ascii' # Force ASCII output for Apache
120 for canonical_path in clashes.iterkeys():
121 sys.stderr.write(u'Clash:'.encode(native))
122 for path in clashes[canonical_path].iterkeys():
123 sys.stderr.write(u' \''.encode(native) +
124 str(path).decode('utf-8').encode(native, 'replace') +
125 u'\''.encode(native))
126 sys.stderr.write(u'\n'.encode(native))
127 sys.exit(1)