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'
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
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'.
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.
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
49 # echo Error found in commit 1>&2
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.
58 # $LastChangedRevision$
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
):
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
)
79 slash
= path
.rindex('/')
82 return path
[:slash
], path
[slash
+1:]
84 def join_path(dir, name
):
87 return dir + '/' + name
89 def ensure_names(path
, names
, txn_root
):
90 if (not names
.has_key(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
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
))