[PATCH 8 of 9 RFC] normfn: introduce normfn extension to manage filename normalization policy per repository
FUJIWARA Katsunori
foozy at lares.dti.ne.jp
Fri May 25 10:00:57 CDT 2012
# HG changeset patch
# User FUJIWARA Katsunori <foozy at lares.dti.ne.jp>
# Date 1337957587 -32400
# Node ID 69cdf8af32ac0098c86d8f64832f027f3fb6f939
# Parent 3b83b592a5b4289ecb65cf9b26225bce20162ada
normfn: introduce normfn extension to manage filename normalization policy per repository
now, this just enables normalization functisons, only when:
- hg runs on MacOS environment, and
- '[normfn] type' value in '.hgnormfn' is set as 'NFC'
diff -r 3b83b592a5b4 -r 69cdf8af32ac hgext/normfn.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/normfn.py Fri May 25 23:53:07 2012 +0900
@@ -0,0 +1,161 @@
+# normfn.py - unify the normalization type for filenames in the history
+#
+# Copyright (C) 2012 FUJIWARA Katsunori <foozy at lares.dti.ne.jp>
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+'''unify the normalization type for filenames in the history
+
+Example of ``.hgnormfn`` file::
+
+ [normalize]
+ type = nfc|nfd|none
+
+To force Mercurial to use specific normalization type,
+add configuration below to hgrc::
+
+ [normfn]
+ type = <type of normalize>
+
+For example, when you should work on the filesystem which does not
+normalize filenames (e.g.: FAT or NFS) in the Mac OS X environment,
+this configuration can prevent normfn extension from doing as same
+as on HFS+.
+'''
+
+import sys, unicodedata
+from mercurial import util, encoding, config
+from mercurial.i18n import _
+
+def normalize(s, type, ignorecase):
+ try:
+ u = s.decode(encoding.encoding, encoding.encodingmode)
+ except UnicodeDecodeError, inst:
+ sub = s[max(0, inst.start - 10):inst.start + 10]
+ raise util.Abort("decoding near '%s': %s!" % (sub, inst))
+ except LookupError, k:
+ raise util.Abort(k, hint="please check your locale settings")
+
+ nu = unicodedata.normalize(type, u)
+ if ignorecase:
+ return nu.lower().encode(encoding.encoding)
+ else:
+ return nu.encode(encoding.encoding)
+
+def tonfc(s):
+ return normalize(s, 'NFC', False)
+
+def tonfd(s):
+ return normalize(s, 'NFD', False)
+
+# on darwin on other platforms
+# NFC: convert abort for filename in NFD
+# NFD: nop abort for filename in NFC
+# ---: nop warn for colliding filenames
+
+def reposetupnfc(ui, repo, cfg):
+ if sys.platform == 'darwin':
+ ui.debug("[normfn] activated on darwin\n")
+ repo.fntolocal = tonfd
+ repo.fnfromlocal = tonfc
+ else:
+ # add protability check (abort)
+ # add colliding check (abort)
+ pass
+
+def reposetupnfd(ui, repo, cfg):
+ # add protability check (abort)
+ # add colliding check (abort)
+ pass
+
+def reposetupnone(ui, repo, cfg):
+ pass
+
+normtypes = {
+ 'nfc': reposetupnfc,
+ 'nfd': reposetupnfd,
+ 'none': reposetupnone,
+}
+
+def normalizetype(cfg, cfgsrc):
+ normtype = cfg.get('normalize', 'type', 'none')
+ if normtype.lower() not in normtypes:
+ # invalid type
+ raise util.Abort(_('unknown normalization type "%s" in %s')
+ % (normtype, cfgsrc),
+ hint=_('see "hg help normfn" for detail'))
+ return normtype.lower()
+
+def loadconfig(repo, node):
+ try:
+ if node is None:
+ data = repo.wfile('.hgnormfn').read()
+ cfgsrc = _('.hgnormfn of current working directory')
+ else:
+ ctx = repo[node]
+ data = ctx['.hgnormfn'].data()
+ cfgsrc = _('.hgnormfn of revision %s') % ctx.rev()
+ except (IOError, LookupError):
+ fl = repo.file('.hgnormfn')
+ if not len(fl):
+ return config.config(), None # empty cfg
+ # use the latest '.hgnormfn' in repository
+ flrev = len(fl) - 1
+ data = fl.read(flrev)
+ cfgsrc = _('.hgnormfn of revision %s') % fl.linkrev(flrev)
+ cfg = config.config()
+ cfg.parse(cfgsrc, data)
+ return cfg, cfgsrc
+
+# some changelogs/manifests are already loaded into memory
+def preupdate(ui, repo, hooktype, parent1, parent2):
+ if parent2:
+ p1cfg, p1src = loadconfig(repo, parent1)
+ p2cfg, p2src = loadconfig(repo, parent2)
+ p1type = normalizetype(p1cfg, p1src)
+ p2type = normalizetype(p2cfg, p2src)
+ if p1type != p2type:
+ # abort if:
+ # - p2 has files not in p1type(may be None), or
+ # - p1 has files not in p2type(may be None)
+ pass
+ else:
+ curcfg, cursrc = loadconfig(repo, None)
+ p1cfg, p1src = loadconfig(repo, parent1)
+ curtype = normalizetype(curcfg, cursrc)
+ p1type = normalizetype(p1cfg, p1src)
+ if curtype != p1type:
+ # invalidate cache for changelogs/manifests for safety
+ # - localrepository.invalidate
+
+ # if p1 has files not in curtype, this update should be
+
+ pass
+ normtypes[p1type](ui, repo, p1cfg)
+ return False
+
+# some changelogs/manifests are already loaded into memory
+def pretxncommit(ui, repo, hooktype, node, parent1, parent2, pending):
+ cfg, cfgsrc = loadconfig(repo, node)
+ normtype = normalizetype(cfg, cfgsrc)
+ if normtype is None:
+ return False
+ # check whether manifest has any files not in normtype
+
+def debug(ui, s):
+ if '--debug' in sys.argv:
+ ui.write(s)
+
+def reposetup(ui, repo):
+# ui.setconfig('hooks', 'preupdate.normfn', preupdate)
+# ui.setconfig('hooks', 'pretxncommit.normfn', pretxncommit)
+ cfg, cfgsrc = loadconfig(repo, None)
+ type = normalizetype(cfg, cfgsrc)
+ debug(ui, "[normfn] enable %s normalization\n" % (type))
+ normtypes[type](ui, repo, cfg)
+
+# operations for data exchanging:
+# - bundle/unbundle
+# - export/import/diff
+# - archive
More information about the Mercurial-devel
mailing list