##// END OF EJS Templates
Get rid of reimplementations of util.binary
Bryan O'Sullivan -
r6508:4b2c266b default
parent child Browse files
Show More
@@ -1,561 +1,556 b''
1 # keyword.py - $Keyword$ expansion for Mercurial
1 # keyword.py - $Keyword$ expansion for Mercurial
2 #
2 #
3 # Copyright 2007, 2008 Christian Ebert <blacktrash@gmx.net>
3 # Copyright 2007, 2008 Christian Ebert <blacktrash@gmx.net>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7 #
7 #
8 # $Id$
8 # $Id$
9 #
9 #
10 # Keyword expansion hack against the grain of a DSCM
10 # Keyword expansion hack against the grain of a DSCM
11 #
11 #
12 # There are many good reasons why this is not needed in a distributed
12 # There are many good reasons why this is not needed in a distributed
13 # SCM, still it may be useful in very small projects based on single
13 # SCM, still it may be useful in very small projects based on single
14 # files (like LaTeX packages), that are mostly addressed to an audience
14 # files (like LaTeX packages), that are mostly addressed to an audience
15 # not running a version control system.
15 # not running a version control system.
16 #
16 #
17 # For in-depth discussion refer to
17 # For in-depth discussion refer to
18 # <http://www.selenic.com/mercurial/wiki/index.cgi/KeywordPlan>.
18 # <http://www.selenic.com/mercurial/wiki/index.cgi/KeywordPlan>.
19 #
19 #
20 # Keyword expansion is based on Mercurial's changeset template mappings.
20 # Keyword expansion is based on Mercurial's changeset template mappings.
21 #
21 #
22 # Binary files are not touched.
22 # Binary files are not touched.
23 #
23 #
24 # Setup in hgrc:
24 # Setup in hgrc:
25 #
25 #
26 # [extensions]
26 # [extensions]
27 # # enable extension
27 # # enable extension
28 # hgext.keyword =
28 # hgext.keyword =
29 #
29 #
30 # Files to act upon/ignore are specified in the [keyword] section.
30 # Files to act upon/ignore are specified in the [keyword] section.
31 # Customized keyword template mappings in the [keywordmaps] section.
31 # Customized keyword template mappings in the [keywordmaps] section.
32 #
32 #
33 # Run "hg help keyword" and "hg kwdemo" to get info on configuration.
33 # Run "hg help keyword" and "hg kwdemo" to get info on configuration.
34
34
35 '''keyword expansion in local repositories
35 '''keyword expansion in local repositories
36
36
37 This extension expands RCS/CVS-like or self-customized $Keywords$
37 This extension expands RCS/CVS-like or self-customized $Keywords$
38 in tracked text files selected by your configuration.
38 in tracked text files selected by your configuration.
39
39
40 Keywords are only expanded in local repositories and not stored in
40 Keywords are only expanded in local repositories and not stored in
41 the change history. The mechanism can be regarded as a convenience
41 the change history. The mechanism can be regarded as a convenience
42 for the current user or for archive distribution.
42 for the current user or for archive distribution.
43
43
44 Configuration is done in the [keyword] and [keywordmaps] sections
44 Configuration is done in the [keyword] and [keywordmaps] sections
45 of hgrc files.
45 of hgrc files.
46
46
47 Example:
47 Example:
48
48
49 [keyword]
49 [keyword]
50 # expand keywords in every python file except those matching "x*"
50 # expand keywords in every python file except those matching "x*"
51 **.py =
51 **.py =
52 x* = ignore
52 x* = ignore
53
53
54 Note: the more specific you are in your filename patterns
54 Note: the more specific you are in your filename patterns
55 the less you lose speed in huge repos.
55 the less you lose speed in huge repos.
56
56
57 For [keywordmaps] template mapping and expansion demonstration and
57 For [keywordmaps] template mapping and expansion demonstration and
58 control run "hg kwdemo".
58 control run "hg kwdemo".
59
59
60 An additional date template filter {date|utcdate} is provided.
60 An additional date template filter {date|utcdate} is provided.
61
61
62 The default template mappings (view with "hg kwdemo -d") can be replaced
62 The default template mappings (view with "hg kwdemo -d") can be replaced
63 with customized keywords and templates.
63 with customized keywords and templates.
64 Again, run "hg kwdemo" to control the results of your config changes.
64 Again, run "hg kwdemo" to control the results of your config changes.
65
65
66 Before changing/disabling active keywords, run "hg kwshrink" to avoid
66 Before changing/disabling active keywords, run "hg kwshrink" to avoid
67 the risk of inadvertedly storing expanded keywords in the change history.
67 the risk of inadvertedly storing expanded keywords in the change history.
68
68
69 To force expansion after enabling it, or a configuration change, run
69 To force expansion after enabling it, or a configuration change, run
70 "hg kwexpand".
70 "hg kwexpand".
71
71
72 Also, when committing with the record extension or using mq's qrecord, be aware
72 Also, when committing with the record extension or using mq's qrecord, be aware
73 that keywords cannot be updated. Again, run "hg kwexpand" on the files in
73 that keywords cannot be updated. Again, run "hg kwexpand" on the files in
74 question to update keyword expansions after all changes have been checked in.
74 question to update keyword expansions after all changes have been checked in.
75
75
76 Expansions spanning more than one line and incremental expansions,
76 Expansions spanning more than one line and incremental expansions,
77 like CVS' $Log$, are not supported. A keyword template map
77 like CVS' $Log$, are not supported. A keyword template map
78 "Log = {desc}" expands to the first line of the changeset description.
78 "Log = {desc}" expands to the first line of the changeset description.
79 '''
79 '''
80
80
81 from mercurial import commands, cmdutil, context, dispatch, filelog, revlog
81 from mercurial import commands, cmdutil, context, dispatch, filelog, revlog
82 from mercurial import patch, localrepo, templater, templatefilters, util
82 from mercurial import patch, localrepo, templater, templatefilters, util
83 from mercurial.hgweb import webcommands
83 from mercurial.hgweb import webcommands
84 from mercurial.node import nullid, hex
84 from mercurial.node import nullid, hex
85 from mercurial.i18n import _
85 from mercurial.i18n import _
86 import re, shutil, tempfile, time
86 import re, shutil, tempfile, time
87
87
88 commands.optionalrepo += ' kwdemo'
88 commands.optionalrepo += ' kwdemo'
89
89
90 # hg commands that do not act on keywords
90 # hg commands that do not act on keywords
91 nokwcommands = ('add addremove bundle copy export grep incoming init'
91 nokwcommands = ('add addremove bundle copy export grep incoming init'
92 ' log outgoing push rename rollback tip'
92 ' log outgoing push rename rollback tip'
93 ' convert email glog')
93 ' convert email glog')
94
94
95 # hg commands that trigger expansion only when writing to working dir,
95 # hg commands that trigger expansion only when writing to working dir,
96 # not when reading filelog, and unexpand when reading from working dir
96 # not when reading filelog, and unexpand when reading from working dir
97 restricted = 'record qfold qimport qnew qpush qrefresh qrecord'
97 restricted = 'record qfold qimport qnew qpush qrefresh qrecord'
98
98
99 def utcdate(date):
99 def utcdate(date):
100 '''Returns hgdate in cvs-like UTC format.'''
100 '''Returns hgdate in cvs-like UTC format.'''
101 return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0]))
101 return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0]))
102
102
103 def textsafe(s):
104 '''Safe version of util.binary with reversed logic.
105 Note: argument may not be None, which is allowed for util.binary.'''
106 return '\0' not in s
107
108 # make keyword tools accessible
103 # make keyword tools accessible
109 kwtools = {'templater': None, 'hgcmd': '', 'inc': [], 'exc': ['.hg*']}
104 kwtools = {'templater': None, 'hgcmd': '', 'inc': [], 'exc': ['.hg*']}
110
105
111
106
112 class kwtemplater(object):
107 class kwtemplater(object):
113 '''
108 '''
114 Sets up keyword templates, corresponding keyword regex, and
109 Sets up keyword templates, corresponding keyword regex, and
115 provides keyword substitution functions.
110 provides keyword substitution functions.
116 '''
111 '''
117 templates = {
112 templates = {
118 'Revision': '{node|short}',
113 'Revision': '{node|short}',
119 'Author': '{author|user}',
114 'Author': '{author|user}',
120 'Date': '{date|utcdate}',
115 'Date': '{date|utcdate}',
121 'RCSFile': '{file|basename},v',
116 'RCSFile': '{file|basename},v',
122 'Source': '{root}/{file},v',
117 'Source': '{root}/{file},v',
123 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
118 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
124 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
119 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
125 }
120 }
126
121
127 def __init__(self, ui, repo):
122 def __init__(self, ui, repo):
128 self.ui = ui
123 self.ui = ui
129 self.repo = repo
124 self.repo = repo
130 self.matcher = util.matcher(repo.root,
125 self.matcher = util.matcher(repo.root,
131 inc=kwtools['inc'], exc=kwtools['exc'])[1]
126 inc=kwtools['inc'], exc=kwtools['exc'])[1]
132 self.restrict = kwtools['hgcmd'] in restricted.split()
127 self.restrict = kwtools['hgcmd'] in restricted.split()
133
128
134 kwmaps = self.ui.configitems('keywordmaps')
129 kwmaps = self.ui.configitems('keywordmaps')
135 if kwmaps: # override default templates
130 if kwmaps: # override default templates
136 kwmaps = [(k, templater.parsestring(v, False))
131 kwmaps = [(k, templater.parsestring(v, False))
137 for (k, v) in kwmaps]
132 for (k, v) in kwmaps]
138 self.templates = dict(kwmaps)
133 self.templates = dict(kwmaps)
139 escaped = map(re.escape, self.templates.keys())
134 escaped = map(re.escape, self.templates.keys())
140 kwpat = r'\$(%s)(: [^$\n\r]*? )??\$' % '|'.join(escaped)
135 kwpat = r'\$(%s)(: [^$\n\r]*? )??\$' % '|'.join(escaped)
141 self.re_kw = re.compile(kwpat)
136 self.re_kw = re.compile(kwpat)
142
137
143 templatefilters.filters['utcdate'] = utcdate
138 templatefilters.filters['utcdate'] = utcdate
144 self.ct = cmdutil.changeset_templater(self.ui, self.repo,
139 self.ct = cmdutil.changeset_templater(self.ui, self.repo,
145 False, '', False)
140 False, '', False)
146
141
147 def getnode(self, path, fnode):
142 def getnode(self, path, fnode):
148 '''Derives changenode from file path and filenode.'''
143 '''Derives changenode from file path and filenode.'''
149 # used by kwfilelog.read and kwexpand
144 # used by kwfilelog.read and kwexpand
150 c = context.filectx(self.repo, path, fileid=fnode)
145 c = context.filectx(self.repo, path, fileid=fnode)
151 return c.node()
146 return c.node()
152
147
153 def substitute(self, data, path, node, subfunc):
148 def substitute(self, data, path, node, subfunc):
154 '''Replaces keywords in data with expanded template.'''
149 '''Replaces keywords in data with expanded template.'''
155 def kwsub(mobj):
150 def kwsub(mobj):
156 kw = mobj.group(1)
151 kw = mobj.group(1)
157 self.ct.use_template(self.templates[kw])
152 self.ct.use_template(self.templates[kw])
158 self.ui.pushbuffer()
153 self.ui.pushbuffer()
159 self.ct.show(changenode=node, root=self.repo.root, file=path)
154 self.ct.show(changenode=node, root=self.repo.root, file=path)
160 ekw = templatefilters.firstline(self.ui.popbuffer())
155 ekw = templatefilters.firstline(self.ui.popbuffer())
161 return '$%s: %s $' % (kw, ekw)
156 return '$%s: %s $' % (kw, ekw)
162 return subfunc(kwsub, data)
157 return subfunc(kwsub, data)
163
158
164 def expand(self, path, node, data):
159 def expand(self, path, node, data):
165 '''Returns data with keywords expanded.'''
160 '''Returns data with keywords expanded.'''
166 if not self.restrict and self.matcher(path) and textsafe(data):
161 if not self.restrict and self.matcher(path) and not util.binary(data):
167 changenode = self.getnode(path, node)
162 changenode = self.getnode(path, node)
168 return self.substitute(data, path, changenode, self.re_kw.sub)
163 return self.substitute(data, path, changenode, self.re_kw.sub)
169 return data
164 return data
170
165
171 def iskwfile(self, path, islink):
166 def iskwfile(self, path, islink):
172 '''Returns true if path matches [keyword] pattern
167 '''Returns true if path matches [keyword] pattern
173 and is not a symbolic link.
168 and is not a symbolic link.
174 Caveat: localrepository._link fails on Windows.'''
169 Caveat: localrepository._link fails on Windows.'''
175 return self.matcher(path) and not islink(path)
170 return self.matcher(path) and not islink(path)
176
171
177 def overwrite(self, node, expand, files):
172 def overwrite(self, node, expand, files):
178 '''Overwrites selected files expanding/shrinking keywords.'''
173 '''Overwrites selected files expanding/shrinking keywords.'''
179 ctx = self.repo.changectx(node)
174 ctx = self.repo.changectx(node)
180 mf = ctx.manifest()
175 mf = ctx.manifest()
181 if node is not None: # commit
176 if node is not None: # commit
182 files = [f for f in ctx.files() if f in mf]
177 files = [f for f in ctx.files() if f in mf]
183 notify = self.ui.debug
178 notify = self.ui.debug
184 else: # kwexpand/kwshrink
179 else: # kwexpand/kwshrink
185 notify = self.ui.note
180 notify = self.ui.note
186 candidates = [f for f in files if self.iskwfile(f, mf.linkf)]
181 candidates = [f for f in files if self.iskwfile(f, mf.linkf)]
187 if candidates:
182 if candidates:
188 self.restrict = True # do not expand when reading
183 self.restrict = True # do not expand when reading
189 candidates.sort()
184 candidates.sort()
190 action = expand and 'expanding' or 'shrinking'
185 action = expand and 'expanding' or 'shrinking'
191 for f in candidates:
186 for f in candidates:
192 fp = self.repo.file(f)
187 fp = self.repo.file(f)
193 data = fp.read(mf[f])
188 data = fp.read(mf[f])
194 if not textsafe(data):
189 if util.binary(data):
195 continue
190 continue
196 if expand:
191 if expand:
197 changenode = node or self.getnode(f, mf[f])
192 changenode = node or self.getnode(f, mf[f])
198 data, found = self.substitute(data, f, changenode,
193 data, found = self.substitute(data, f, changenode,
199 self.re_kw.subn)
194 self.re_kw.subn)
200 else:
195 else:
201 found = self.re_kw.search(data)
196 found = self.re_kw.search(data)
202 if found:
197 if found:
203 notify(_('overwriting %s %s keywords\n') % (f, action))
198 notify(_('overwriting %s %s keywords\n') % (f, action))
204 self.repo.wwrite(f, data, mf.flags(f))
199 self.repo.wwrite(f, data, mf.flags(f))
205 self.repo.dirstate.normal(f)
200 self.repo.dirstate.normal(f)
206 self.restrict = False
201 self.restrict = False
207
202
208 def shrinktext(self, text):
203 def shrinktext(self, text):
209 '''Unconditionally removes all keyword substitutions from text.'''
204 '''Unconditionally removes all keyword substitutions from text.'''
210 return self.re_kw.sub(r'$\1$', text)
205 return self.re_kw.sub(r'$\1$', text)
211
206
212 def shrink(self, fname, text):
207 def shrink(self, fname, text):
213 '''Returns text with all keyword substitutions removed.'''
208 '''Returns text with all keyword substitutions removed.'''
214 if self.matcher(fname) and textsafe(text):
209 if self.matcher(fname) and not util.binary(text):
215 return self.shrinktext(text)
210 return self.shrinktext(text)
216 return text
211 return text
217
212
218 def shrinklines(self, fname, lines):
213 def shrinklines(self, fname, lines):
219 '''Returns lines with keyword substitutions removed.'''
214 '''Returns lines with keyword substitutions removed.'''
220 if self.matcher(fname):
215 if self.matcher(fname):
221 text = ''.join(lines)
216 text = ''.join(lines)
222 if textsafe(text):
217 if not util.binary(text):
223 return self.shrinktext(text).splitlines(True)
218 return self.shrinktext(text).splitlines(True)
224 return lines
219 return lines
225
220
226 def wread(self, fname, data):
221 def wread(self, fname, data):
227 '''If in restricted mode returns data read from wdir with
222 '''If in restricted mode returns data read from wdir with
228 keyword substitutions removed.'''
223 keyword substitutions removed.'''
229 return self.restrict and self.shrink(fname, data) or data
224 return self.restrict and self.shrink(fname, data) or data
230
225
231 class kwfilelog(filelog.filelog):
226 class kwfilelog(filelog.filelog):
232 '''
227 '''
233 Subclass of filelog to hook into its read, add, cmp methods.
228 Subclass of filelog to hook into its read, add, cmp methods.
234 Keywords are "stored" unexpanded, and processed on reading.
229 Keywords are "stored" unexpanded, and processed on reading.
235 '''
230 '''
236 def __init__(self, opener, kwt, path):
231 def __init__(self, opener, kwt, path):
237 super(kwfilelog, self).__init__(opener, path)
232 super(kwfilelog, self).__init__(opener, path)
238 self.kwt = kwt
233 self.kwt = kwt
239 self.path = path
234 self.path = path
240
235
241 def read(self, node):
236 def read(self, node):
242 '''Expands keywords when reading filelog.'''
237 '''Expands keywords when reading filelog.'''
243 data = super(kwfilelog, self).read(node)
238 data = super(kwfilelog, self).read(node)
244 return self.kwt.expand(self.path, node, data)
239 return self.kwt.expand(self.path, node, data)
245
240
246 def add(self, text, meta, tr, link, p1=None, p2=None):
241 def add(self, text, meta, tr, link, p1=None, p2=None):
247 '''Removes keyword substitutions when adding to filelog.'''
242 '''Removes keyword substitutions when adding to filelog.'''
248 text = self.kwt.shrink(self.path, text)
243 text = self.kwt.shrink(self.path, text)
249 return super(kwfilelog, self).add(text, meta, tr, link, p1, p2)
244 return super(kwfilelog, self).add(text, meta, tr, link, p1, p2)
250
245
251 def cmp(self, node, text):
246 def cmp(self, node, text):
252 '''Removes keyword substitutions for comparison.'''
247 '''Removes keyword substitutions for comparison.'''
253 text = self.kwt.shrink(self.path, text)
248 text = self.kwt.shrink(self.path, text)
254 if self.renamed(node):
249 if self.renamed(node):
255 t2 = super(kwfilelog, self).read(node)
250 t2 = super(kwfilelog, self).read(node)
256 return t2 != text
251 return t2 != text
257 return revlog.revlog.cmp(self, node, text)
252 return revlog.revlog.cmp(self, node, text)
258
253
259 def _status(ui, repo, kwt, *pats, **opts):
254 def _status(ui, repo, kwt, *pats, **opts):
260 '''Bails out if [keyword] configuration is not active.
255 '''Bails out if [keyword] configuration is not active.
261 Returns status of working directory.'''
256 Returns status of working directory.'''
262 if kwt:
257 if kwt:
263 files, match, anypats = cmdutil.matchpats(repo, pats, opts)
258 files, match, anypats = cmdutil.matchpats(repo, pats, opts)
264 return repo.status(files=files, match=match, list_clean=True)
259 return repo.status(files=files, match=match, list_clean=True)
265 if ui.configitems('keyword'):
260 if ui.configitems('keyword'):
266 raise util.Abort(_('[keyword] patterns cannot match'))
261 raise util.Abort(_('[keyword] patterns cannot match'))
267 raise util.Abort(_('no [keyword] patterns configured'))
262 raise util.Abort(_('no [keyword] patterns configured'))
268
263
269 def _kwfwrite(ui, repo, expand, *pats, **opts):
264 def _kwfwrite(ui, repo, expand, *pats, **opts):
270 '''Selects files and passes them to kwtemplater.overwrite.'''
265 '''Selects files and passes them to kwtemplater.overwrite.'''
271 kwt = kwtools['templater']
266 kwt = kwtools['templater']
272 status = _status(ui, repo, kwt, *pats, **opts)
267 status = _status(ui, repo, kwt, *pats, **opts)
273 modified, added, removed, deleted, unknown, ignored, clean = status
268 modified, added, removed, deleted, unknown, ignored, clean = status
274 if modified or added or removed or deleted:
269 if modified or added or removed or deleted:
275 raise util.Abort(_('outstanding uncommitted changes in given files'))
270 raise util.Abort(_('outstanding uncommitted changes in given files'))
276 wlock = lock = None
271 wlock = lock = None
277 try:
272 try:
278 wlock = repo.wlock()
273 wlock = repo.wlock()
279 lock = repo.lock()
274 lock = repo.lock()
280 kwt.overwrite(None, expand, clean)
275 kwt.overwrite(None, expand, clean)
281 finally:
276 finally:
282 del wlock, lock
277 del wlock, lock
283
278
284
279
285 def demo(ui, repo, *args, **opts):
280 def demo(ui, repo, *args, **opts):
286 '''print [keywordmaps] configuration and an expansion example
281 '''print [keywordmaps] configuration and an expansion example
287
282
288 Show current, custom, or default keyword template maps
283 Show current, custom, or default keyword template maps
289 and their expansion.
284 and their expansion.
290
285
291 Extend current configuration by specifying maps as arguments
286 Extend current configuration by specifying maps as arguments
292 and optionally by reading from an additional hgrc file.
287 and optionally by reading from an additional hgrc file.
293
288
294 Override current keyword template maps with "default" option.
289 Override current keyword template maps with "default" option.
295 '''
290 '''
296 def demostatus(stat):
291 def demostatus(stat):
297 ui.status(_('\n\t%s\n') % stat)
292 ui.status(_('\n\t%s\n') % stat)
298
293
299 def demoitems(section, items):
294 def demoitems(section, items):
300 ui.write('[%s]\n' % section)
295 ui.write('[%s]\n' % section)
301 for k, v in items:
296 for k, v in items:
302 ui.write('%s = %s\n' % (k, v))
297 ui.write('%s = %s\n' % (k, v))
303
298
304 msg = 'hg keyword config and expansion example'
299 msg = 'hg keyword config and expansion example'
305 kwstatus = 'current'
300 kwstatus = 'current'
306 fn = 'demo.txt'
301 fn = 'demo.txt'
307 branchname = 'demobranch'
302 branchname = 'demobranch'
308 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
303 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
309 ui.note(_('creating temporary repo at %s\n') % tmpdir)
304 ui.note(_('creating temporary repo at %s\n') % tmpdir)
310 repo = localrepo.localrepository(ui, tmpdir, True)
305 repo = localrepo.localrepository(ui, tmpdir, True)
311 ui.setconfig('keyword', fn, '')
306 ui.setconfig('keyword', fn, '')
312 if args or opts.get('rcfile'):
307 if args or opts.get('rcfile'):
313 kwstatus = 'custom'
308 kwstatus = 'custom'
314 if opts.get('rcfile'):
309 if opts.get('rcfile'):
315 ui.readconfig(opts.get('rcfile'))
310 ui.readconfig(opts.get('rcfile'))
316 if opts.get('default'):
311 if opts.get('default'):
317 kwstatus = 'default'
312 kwstatus = 'default'
318 kwmaps = kwtemplater.templates
313 kwmaps = kwtemplater.templates
319 if ui.configitems('keywordmaps'):
314 if ui.configitems('keywordmaps'):
320 # override maps from optional rcfile
315 # override maps from optional rcfile
321 for k, v in kwmaps.iteritems():
316 for k, v in kwmaps.iteritems():
322 ui.setconfig('keywordmaps', k, v)
317 ui.setconfig('keywordmaps', k, v)
323 elif args:
318 elif args:
324 # simulate hgrc parsing
319 # simulate hgrc parsing
325 rcmaps = ['[keywordmaps]\n'] + [a + '\n' for a in args]
320 rcmaps = ['[keywordmaps]\n'] + [a + '\n' for a in args]
326 fp = repo.opener('hgrc', 'w')
321 fp = repo.opener('hgrc', 'w')
327 fp.writelines(rcmaps)
322 fp.writelines(rcmaps)
328 fp.close()
323 fp.close()
329 ui.readconfig(repo.join('hgrc'))
324 ui.readconfig(repo.join('hgrc'))
330 if not opts.get('default'):
325 if not opts.get('default'):
331 kwmaps = dict(ui.configitems('keywordmaps')) or kwtemplater.templates
326 kwmaps = dict(ui.configitems('keywordmaps')) or kwtemplater.templates
332 uisetup(ui)
327 uisetup(ui)
333 reposetup(ui, repo)
328 reposetup(ui, repo)
334 for k, v in ui.configitems('extensions'):
329 for k, v in ui.configitems('extensions'):
335 if k.endswith('keyword'):
330 if k.endswith('keyword'):
336 extension = '%s = %s' % (k, v)
331 extension = '%s = %s' % (k, v)
337 break
332 break
338 demostatus('config using %s keyword template maps' % kwstatus)
333 demostatus('config using %s keyword template maps' % kwstatus)
339 ui.write('[extensions]\n%s\n' % extension)
334 ui.write('[extensions]\n%s\n' % extension)
340 demoitems('keyword', ui.configitems('keyword'))
335 demoitems('keyword', ui.configitems('keyword'))
341 demoitems('keywordmaps', kwmaps.iteritems())
336 demoitems('keywordmaps', kwmaps.iteritems())
342 keywords = '$' + '$\n$'.join(kwmaps.keys()) + '$\n'
337 keywords = '$' + '$\n$'.join(kwmaps.keys()) + '$\n'
343 repo.wopener(fn, 'w').write(keywords)
338 repo.wopener(fn, 'w').write(keywords)
344 repo.add([fn])
339 repo.add([fn])
345 path = repo.wjoin(fn)
340 path = repo.wjoin(fn)
346 ui.note(_('\n%s keywords written to %s:\n') % (kwstatus, path))
341 ui.note(_('\n%s keywords written to %s:\n') % (kwstatus, path))
347 ui.note(keywords)
342 ui.note(keywords)
348 ui.note('\nhg -R "%s" branch "%s"\n' % (tmpdir, branchname))
343 ui.note('\nhg -R "%s" branch "%s"\n' % (tmpdir, branchname))
349 # silence branch command if not verbose
344 # silence branch command if not verbose
350 quiet = ui.quiet
345 quiet = ui.quiet
351 ui.quiet = not ui.verbose
346 ui.quiet = not ui.verbose
352 commands.branch(ui, repo, branchname)
347 commands.branch(ui, repo, branchname)
353 ui.quiet = quiet
348 ui.quiet = quiet
354 for name, cmd in ui.configitems('hooks'):
349 for name, cmd in ui.configitems('hooks'):
355 if name.split('.', 1)[0].find('commit') > -1:
350 if name.split('.', 1)[0].find('commit') > -1:
356 repo.ui.setconfig('hooks', name, '')
351 repo.ui.setconfig('hooks', name, '')
357 ui.note(_('unhooked all commit hooks\n'))
352 ui.note(_('unhooked all commit hooks\n'))
358 ui.note('hg -R "%s" ci -m "%s"\n' % (tmpdir, msg))
353 ui.note('hg -R "%s" ci -m "%s"\n' % (tmpdir, msg))
359 repo.commit(text=msg)
354 repo.commit(text=msg)
360 format = ui.verbose and ' in %s' % path or ''
355 format = ui.verbose and ' in %s' % path or ''
361 demostatus('%s keywords expanded%s' % (kwstatus, format))
356 demostatus('%s keywords expanded%s' % (kwstatus, format))
362 ui.write(repo.wread(fn))
357 ui.write(repo.wread(fn))
363 ui.debug(_('\nremoving temporary repo %s\n') % tmpdir)
358 ui.debug(_('\nremoving temporary repo %s\n') % tmpdir)
364 shutil.rmtree(tmpdir, ignore_errors=True)
359 shutil.rmtree(tmpdir, ignore_errors=True)
365
360
366 def expand(ui, repo, *pats, **opts):
361 def expand(ui, repo, *pats, **opts):
367 '''expand keywords in working directory
362 '''expand keywords in working directory
368
363
369 Run after (re)enabling keyword expansion.
364 Run after (re)enabling keyword expansion.
370
365
371 kwexpand refuses to run if given files contain local changes.
366 kwexpand refuses to run if given files contain local changes.
372 '''
367 '''
373 # 3rd argument sets expansion to True
368 # 3rd argument sets expansion to True
374 _kwfwrite(ui, repo, True, *pats, **opts)
369 _kwfwrite(ui, repo, True, *pats, **opts)
375
370
376 def files(ui, repo, *pats, **opts):
371 def files(ui, repo, *pats, **opts):
377 '''print files currently configured for keyword expansion
372 '''print files currently configured for keyword expansion
378
373
379 Crosscheck which files in working directory are potential targets for
374 Crosscheck which files in working directory are potential targets for
380 keyword expansion.
375 keyword expansion.
381 That is, files matched by [keyword] config patterns but not symlinks.
376 That is, files matched by [keyword] config patterns but not symlinks.
382 '''
377 '''
383 kwt = kwtools['templater']
378 kwt = kwtools['templater']
384 status = _status(ui, repo, kwt, *pats, **opts)
379 status = _status(ui, repo, kwt, *pats, **opts)
385 modified, added, removed, deleted, unknown, ignored, clean = status
380 modified, added, removed, deleted, unknown, ignored, clean = status
386 files = modified + added + clean
381 files = modified + added + clean
387 if opts.get('untracked'):
382 if opts.get('untracked'):
388 files += unknown
383 files += unknown
389 files.sort()
384 files.sort()
390 wctx = repo.workingctx()
385 wctx = repo.workingctx()
391 islink = lambda p: 'l' in wctx.fileflags(p)
386 islink = lambda p: 'l' in wctx.fileflags(p)
392 kwfiles = [f for f in files if kwt.iskwfile(f, islink)]
387 kwfiles = [f for f in files if kwt.iskwfile(f, islink)]
393 cwd = pats and repo.getcwd() or ''
388 cwd = pats and repo.getcwd() or ''
394 kwfstats = not opts.get('ignore') and (('K', kwfiles),) or ()
389 kwfstats = not opts.get('ignore') and (('K', kwfiles),) or ()
395 if opts.get('all') or opts.get('ignore'):
390 if opts.get('all') or opts.get('ignore'):
396 kwfstats += (('I', [f for f in files if f not in kwfiles]),)
391 kwfstats += (('I', [f for f in files if f not in kwfiles]),)
397 for char, filenames in kwfstats:
392 for char, filenames in kwfstats:
398 format = (opts.get('all') or ui.verbose) and '%s %%s\n' % char or '%s\n'
393 format = (opts.get('all') or ui.verbose) and '%s %%s\n' % char or '%s\n'
399 for f in filenames:
394 for f in filenames:
400 ui.write(format % repo.pathto(f, cwd))
395 ui.write(format % repo.pathto(f, cwd))
401
396
402 def shrink(ui, repo, *pats, **opts):
397 def shrink(ui, repo, *pats, **opts):
403 '''revert expanded keywords in working directory
398 '''revert expanded keywords in working directory
404
399
405 Run before changing/disabling active keywords
400 Run before changing/disabling active keywords
406 or if you experience problems with "hg import" or "hg merge".
401 or if you experience problems with "hg import" or "hg merge".
407
402
408 kwshrink refuses to run if given files contain local changes.
403 kwshrink refuses to run if given files contain local changes.
409 '''
404 '''
410 # 3rd argument sets expansion to False
405 # 3rd argument sets expansion to False
411 _kwfwrite(ui, repo, False, *pats, **opts)
406 _kwfwrite(ui, repo, False, *pats, **opts)
412
407
413
408
414 def uisetup(ui):
409 def uisetup(ui):
415 '''Collects [keyword] config in kwtools.
410 '''Collects [keyword] config in kwtools.
416 Monkeypatches dispatch._parse if needed.'''
411 Monkeypatches dispatch._parse if needed.'''
417
412
418 for pat, opt in ui.configitems('keyword'):
413 for pat, opt in ui.configitems('keyword'):
419 if opt != 'ignore':
414 if opt != 'ignore':
420 kwtools['inc'].append(pat)
415 kwtools['inc'].append(pat)
421 else:
416 else:
422 kwtools['exc'].append(pat)
417 kwtools['exc'].append(pat)
423
418
424 if kwtools['inc']:
419 if kwtools['inc']:
425 def kwdispatch_parse(ui, args):
420 def kwdispatch_parse(ui, args):
426 '''Monkeypatch dispatch._parse to obtain running hg command.'''
421 '''Monkeypatch dispatch._parse to obtain running hg command.'''
427 cmd, func, args, options, cmdoptions = dispatch_parse(ui, args)
422 cmd, func, args, options, cmdoptions = dispatch_parse(ui, args)
428 kwtools['hgcmd'] = cmd
423 kwtools['hgcmd'] = cmd
429 return cmd, func, args, options, cmdoptions
424 return cmd, func, args, options, cmdoptions
430
425
431 dispatch_parse = dispatch._parse
426 dispatch_parse = dispatch._parse
432 dispatch._parse = kwdispatch_parse
427 dispatch._parse = kwdispatch_parse
433
428
434 def reposetup(ui, repo):
429 def reposetup(ui, repo):
435 '''Sets up repo as kwrepo for keyword substitution.
430 '''Sets up repo as kwrepo for keyword substitution.
436 Overrides file method to return kwfilelog instead of filelog
431 Overrides file method to return kwfilelog instead of filelog
437 if file matches user configuration.
432 if file matches user configuration.
438 Wraps commit to overwrite configured files with updated
433 Wraps commit to overwrite configured files with updated
439 keyword substitutions.
434 keyword substitutions.
440 Monkeypatches patch and webcommands.'''
435 Monkeypatches patch and webcommands.'''
441
436
442 try:
437 try:
443 if (not repo.local() or not kwtools['inc']
438 if (not repo.local() or not kwtools['inc']
444 or kwtools['hgcmd'] in nokwcommands.split()
439 or kwtools['hgcmd'] in nokwcommands.split()
445 or '.hg' in util.splitpath(repo.root)
440 or '.hg' in util.splitpath(repo.root)
446 or repo._url.startswith('bundle:')):
441 or repo._url.startswith('bundle:')):
447 return
442 return
448 except AttributeError:
443 except AttributeError:
449 pass
444 pass
450
445
451 kwtools['templater'] = kwt = kwtemplater(ui, repo)
446 kwtools['templater'] = kwt = kwtemplater(ui, repo)
452
447
453 class kwrepo(repo.__class__):
448 class kwrepo(repo.__class__):
454 def file(self, f):
449 def file(self, f):
455 if f[0] == '/':
450 if f[0] == '/':
456 f = f[1:]
451 f = f[1:]
457 return kwfilelog(self.sopener, kwt, f)
452 return kwfilelog(self.sopener, kwt, f)
458
453
459 def wread(self, filename):
454 def wread(self, filename):
460 data = super(kwrepo, self).wread(filename)
455 data = super(kwrepo, self).wread(filename)
461 return kwt.wread(filename, data)
456 return kwt.wread(filename, data)
462
457
463 def commit(self, files=None, text='', user=None, date=None,
458 def commit(self, files=None, text='', user=None, date=None,
464 match=util.always, force=False, force_editor=False,
459 match=util.always, force=False, force_editor=False,
465 p1=None, p2=None, extra={}, empty_ok=False):
460 p1=None, p2=None, extra={}, empty_ok=False):
466 wlock = lock = None
461 wlock = lock = None
467 _p1 = _p2 = None
462 _p1 = _p2 = None
468 try:
463 try:
469 wlock = self.wlock()
464 wlock = self.wlock()
470 lock = self.lock()
465 lock = self.lock()
471 # store and postpone commit hooks
466 # store and postpone commit hooks
472 commithooks = {}
467 commithooks = {}
473 for name, cmd in ui.configitems('hooks'):
468 for name, cmd in ui.configitems('hooks'):
474 if name.split('.', 1)[0] == 'commit':
469 if name.split('.', 1)[0] == 'commit':
475 commithooks[name] = cmd
470 commithooks[name] = cmd
476 ui.setconfig('hooks', name, None)
471 ui.setconfig('hooks', name, None)
477 if commithooks:
472 if commithooks:
478 # store parents for commit hook environment
473 # store parents for commit hook environment
479 if p1 is None:
474 if p1 is None:
480 _p1, _p2 = repo.dirstate.parents()
475 _p1, _p2 = repo.dirstate.parents()
481 else:
476 else:
482 _p1, _p2 = p1, p2 or nullid
477 _p1, _p2 = p1, p2 or nullid
483 _p1 = hex(_p1)
478 _p1 = hex(_p1)
484 if _p2 == nullid:
479 if _p2 == nullid:
485 _p2 = ''
480 _p2 = ''
486 else:
481 else:
487 _p2 = hex(_p2)
482 _p2 = hex(_p2)
488
483
489 n = super(kwrepo, self).commit(files, text, user, date, match,
484 n = super(kwrepo, self).commit(files, text, user, date, match,
490 force, force_editor, p1, p2,
485 force, force_editor, p1, p2,
491 extra, empty_ok)
486 extra, empty_ok)
492
487
493 # restore commit hooks
488 # restore commit hooks
494 for name, cmd in commithooks.iteritems():
489 for name, cmd in commithooks.iteritems():
495 ui.setconfig('hooks', name, cmd)
490 ui.setconfig('hooks', name, cmd)
496 if n is not None:
491 if n is not None:
497 kwt.overwrite(n, True, None)
492 kwt.overwrite(n, True, None)
498 repo.hook('commit', node=n, parent1=_p1, parent2=_p2)
493 repo.hook('commit', node=n, parent1=_p1, parent2=_p2)
499 return n
494 return n
500 finally:
495 finally:
501 del wlock, lock
496 del wlock, lock
502
497
503 # monkeypatches
498 # monkeypatches
504 def kwpatchfile_init(self, ui, fname, missing=False):
499 def kwpatchfile_init(self, ui, fname, missing=False):
505 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
500 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
506 rejects or conflicts due to expanded keywords in working dir.'''
501 rejects or conflicts due to expanded keywords in working dir.'''
507 patchfile_init(self, ui, fname, missing)
502 patchfile_init(self, ui, fname, missing)
508 # shrink keywords read from working dir
503 # shrink keywords read from working dir
509 self.lines = kwt.shrinklines(self.fname, self.lines)
504 self.lines = kwt.shrinklines(self.fname, self.lines)
510
505
511 def kw_diff(repo, node1=None, node2=None, files=None, match=util.always,
506 def kw_diff(repo, node1=None, node2=None, files=None, match=util.always,
512 fp=None, changes=None, opts=None):
507 fp=None, changes=None, opts=None):
513 '''Monkeypatch patch.diff to avoid expansion except when
508 '''Monkeypatch patch.diff to avoid expansion except when
514 comparing against working dir.'''
509 comparing against working dir.'''
515 if node2 is not None:
510 if node2 is not None:
516 kwt.matcher = util.never
511 kwt.matcher = util.never
517 elif node1 is not None and node1 != repo.changectx().node():
512 elif node1 is not None and node1 != repo.changectx().node():
518 kwt.restrict = True
513 kwt.restrict = True
519 patch_diff(repo, node1, node2, files, match, fp, changes, opts)
514 patch_diff(repo, node1, node2, files, match, fp, changes, opts)
520
515
521 def kwweb_changeset(web, req, tmpl):
516 def kwweb_changeset(web, req, tmpl):
522 '''Wraps webcommands.changeset turning off keyword expansion.'''
517 '''Wraps webcommands.changeset turning off keyword expansion.'''
523 kwt.matcher = util.never
518 kwt.matcher = util.never
524 return webcommands_changeset(web, req, tmpl)
519 return webcommands_changeset(web, req, tmpl)
525
520
526 def kwweb_filediff(web, req, tmpl):
521 def kwweb_filediff(web, req, tmpl):
527 '''Wraps webcommands.filediff turning off keyword expansion.'''
522 '''Wraps webcommands.filediff turning off keyword expansion.'''
528 kwt.matcher = util.never
523 kwt.matcher = util.never
529 return webcommands_filediff(web, req, tmpl)
524 return webcommands_filediff(web, req, tmpl)
530
525
531 repo.__class__ = kwrepo
526 repo.__class__ = kwrepo
532
527
533 patchfile_init = patch.patchfile.__init__
528 patchfile_init = patch.patchfile.__init__
534 patch_diff = patch.diff
529 patch_diff = patch.diff
535 webcommands_changeset = webcommands.changeset
530 webcommands_changeset = webcommands.changeset
536 webcommands_filediff = webcommands.filediff
531 webcommands_filediff = webcommands.filediff
537
532
538 patch.patchfile.__init__ = kwpatchfile_init
533 patch.patchfile.__init__ = kwpatchfile_init
539 patch.diff = kw_diff
534 patch.diff = kw_diff
540 webcommands.changeset = webcommands.rev = kwweb_changeset
535 webcommands.changeset = webcommands.rev = kwweb_changeset
541 webcommands.filediff = webcommands.diff = kwweb_filediff
536 webcommands.filediff = webcommands.diff = kwweb_filediff
542
537
543
538
544 cmdtable = {
539 cmdtable = {
545 'kwdemo':
540 'kwdemo':
546 (demo,
541 (demo,
547 [('d', 'default', None, _('show default keyword template maps')),
542 [('d', 'default', None, _('show default keyword template maps')),
548 ('f', 'rcfile', [], _('read maps from rcfile'))],
543 ('f', 'rcfile', [], _('read maps from rcfile'))],
549 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...')),
544 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...')),
550 'kwexpand': (expand, commands.walkopts,
545 'kwexpand': (expand, commands.walkopts,
551 _('hg kwexpand [OPTION]... [FILE]...')),
546 _('hg kwexpand [OPTION]... [FILE]...')),
552 'kwfiles':
547 'kwfiles':
553 (files,
548 (files,
554 [('a', 'all', None, _('show keyword status flags of all files')),
549 [('a', 'all', None, _('show keyword status flags of all files')),
555 ('i', 'ignore', None, _('show files excluded from expansion')),
550 ('i', 'ignore', None, _('show files excluded from expansion')),
556 ('u', 'untracked', None, _('additionally show untracked files')),
551 ('u', 'untracked', None, _('additionally show untracked files')),
557 ] + commands.walkopts,
552 ] + commands.walkopts,
558 _('hg kwfiles [OPTION]... [FILE]...')),
553 _('hg kwfiles [OPTION]... [FILE]...')),
559 'kwshrink': (shrink, commands.walkopts,
554 'kwshrink': (shrink, commands.walkopts,
560 _('hg kwshrink [OPTION]... [FILE]...')),
555 _('hg kwshrink [OPTION]... [FILE]...')),
561 }
556 }
@@ -1,147 +1,143 b''
1 # win32text.py - LF <-> CRLF/CR translation utilities for Windows/Mac users
1 # win32text.py - LF <-> CRLF/CR translation utilities for Windows/Mac users
2 #
2 #
3 # This software may be used and distributed according to the terms
3 # This software may be used and distributed according to the terms
4 # of the GNU General Public License, incorporated herein by reference.
4 # of the GNU General Public License, incorporated herein by reference.
5 #
5 #
6 # To perform automatic newline conversion, use:
6 # To perform automatic newline conversion, use:
7 #
7 #
8 # [extensions]
8 # [extensions]
9 # hgext.win32text =
9 # hgext.win32text =
10 # [encode]
10 # [encode]
11 # ** = cleverencode:
11 # ** = cleverencode:
12 # # or ** = macencode:
12 # # or ** = macencode:
13 # [decode]
13 # [decode]
14 # ** = cleverdecode:
14 # ** = cleverdecode:
15 # # or ** = macdecode:
15 # # or ** = macdecode:
16 #
16 #
17 # If not doing conversion, to make sure you do not commit CRLF/CR by accident:
17 # If not doing conversion, to make sure you do not commit CRLF/CR by accident:
18 #
18 #
19 # [hooks]
19 # [hooks]
20 # pretxncommit.crlf = python:hgext.win32text.forbidcrlf
20 # pretxncommit.crlf = python:hgext.win32text.forbidcrlf
21 # # or pretxncommit.cr = python:hgext.win32text.forbidcr
21 # # or pretxncommit.cr = python:hgext.win32text.forbidcr
22 #
22 #
23 # To do the same check on a server to prevent CRLF/CR from being pushed or
23 # To do the same check on a server to prevent CRLF/CR from being pushed or
24 # pulled:
24 # pulled:
25 #
25 #
26 # [hooks]
26 # [hooks]
27 # pretxnchangegroup.crlf = python:hgext.win32text.forbidcrlf
27 # pretxnchangegroup.crlf = python:hgext.win32text.forbidcrlf
28 # # or pretxnchangegroup.cr = python:hgext.win32text.forbidcr
28 # # or pretxnchangegroup.cr = python:hgext.win32text.forbidcr
29
29
30 from mercurial.i18n import gettext as _
30 from mercurial.i18n import gettext as _
31 from mercurial.node import bin, short
31 from mercurial.node import bin, short
32 import re
32 import re
33
33
34 # regexp for single LF without CR preceding.
34 # regexp for single LF without CR preceding.
35 re_single_lf = re.compile('(^|[^\r])\n', re.MULTILINE)
35 re_single_lf = re.compile('(^|[^\r])\n', re.MULTILINE)
36
36
37 newlinestr = {'\r\n': 'CRLF', '\r': 'CR'}
37 newlinestr = {'\r\n': 'CRLF', '\r': 'CR'}
38 filterstr = {'\r\n': 'clever', '\r': 'mac'}
38 filterstr = {'\r\n': 'clever', '\r': 'mac'}
39
39
40 def checknewline(s, newline, ui=None, repo=None, filename=None):
40 def checknewline(s, newline, ui=None, repo=None, filename=None):
41 # warn if already has 'newline' in repository.
41 # warn if already has 'newline' in repository.
42 # it might cause unexpected eol conversion.
42 # it might cause unexpected eol conversion.
43 # see issue 302:
43 # see issue 302:
44 # http://www.selenic.com/mercurial/bts/issue302
44 # http://www.selenic.com/mercurial/bts/issue302
45 if newline in s and ui and filename and repo:
45 if newline in s and ui and filename and repo:
46 ui.warn(_('WARNING: %s already has %s line endings\n'
46 ui.warn(_('WARNING: %s already has %s line endings\n'
47 'and does not need EOL conversion by the win32text plugin.\n'
47 'and does not need EOL conversion by the win32text plugin.\n'
48 'Before your next commit, please reconsider your '
48 'Before your next commit, please reconsider your '
49 'encode/decode settings in \nMercurial.ini or %s.\n') %
49 'encode/decode settings in \nMercurial.ini or %s.\n') %
50 (filename, newlinestr[newline], repo.join('hgrc')))
50 (filename, newlinestr[newline], repo.join('hgrc')))
51
51
52 def dumbdecode(s, cmd, **kwargs):
52 def dumbdecode(s, cmd, **kwargs):
53 checknewline(s, '\r\n', **kwargs)
53 checknewline(s, '\r\n', **kwargs)
54 # replace single LF to CRLF
54 # replace single LF to CRLF
55 return re_single_lf.sub('\\1\r\n', s)
55 return re_single_lf.sub('\\1\r\n', s)
56
56
57 def dumbencode(s, cmd):
57 def dumbencode(s, cmd):
58 return s.replace('\r\n', '\n')
58 return s.replace('\r\n', '\n')
59
59
60 def macdumbdecode(s, cmd, **kwargs):
60 def macdumbdecode(s, cmd, **kwargs):
61 checknewline(s, '\r', **kwargs)
61 checknewline(s, '\r', **kwargs)
62 return s.replace('\n', '\r')
62 return s.replace('\n', '\r')
63
63
64 def macdumbencode(s, cmd):
64 def macdumbencode(s, cmd):
65 return s.replace('\r', '\n')
65 return s.replace('\r', '\n')
66
66
67 def clevertest(s, cmd):
68 if '\0' in s: return False
69 return True
70
71 def cleverdecode(s, cmd, **kwargs):
67 def cleverdecode(s, cmd, **kwargs):
72 if clevertest(s, cmd):
68 if not util.binary(s):
73 return dumbdecode(s, cmd, **kwargs)
69 return dumbdecode(s, cmd, **kwargs)
74 return s
70 return s
75
71
76 def cleverencode(s, cmd):
72 def cleverencode(s, cmd):
77 if clevertest(s, cmd):
73 if not util.binary(s):
78 return dumbencode(s, cmd)
74 return dumbencode(s, cmd)
79 return s
75 return s
80
76
81 def macdecode(s, cmd, **kwargs):
77 def macdecode(s, cmd, **kwargs):
82 if clevertest(s, cmd):
78 if not util.binary(s):
83 return macdumbdecode(s, cmd, **kwargs)
79 return macdumbdecode(s, cmd, **kwargs)
84 return s
80 return s
85
81
86 def macencode(s, cmd):
82 def macencode(s, cmd):
87 if clevertest(s, cmd):
83 if not util.binary(s):
88 return macdumbencode(s, cmd)
84 return macdumbencode(s, cmd)
89 return s
85 return s
90
86
91 _filters = {
87 _filters = {
92 'dumbdecode:': dumbdecode,
88 'dumbdecode:': dumbdecode,
93 'dumbencode:': dumbencode,
89 'dumbencode:': dumbencode,
94 'cleverdecode:': cleverdecode,
90 'cleverdecode:': cleverdecode,
95 'cleverencode:': cleverencode,
91 'cleverencode:': cleverencode,
96 'macdumbdecode:': macdumbdecode,
92 'macdumbdecode:': macdumbdecode,
97 'macdumbencode:': macdumbencode,
93 'macdumbencode:': macdumbencode,
98 'macdecode:': macdecode,
94 'macdecode:': macdecode,
99 'macencode:': macencode,
95 'macencode:': macencode,
100 }
96 }
101
97
102 def forbidnewline(ui, repo, hooktype, node, newline, **kwargs):
98 def forbidnewline(ui, repo, hooktype, node, newline, **kwargs):
103 halt = False
99 halt = False
104 for rev in xrange(repo.changelog.rev(bin(node)), repo.changelog.count()):
100 for rev in xrange(repo.changelog.rev(bin(node)), repo.changelog.count()):
105 c = repo.changectx(rev)
101 c = repo.changectx(rev)
106 for f in c.files():
102 for f in c.files():
107 if f not in c:
103 if f not in c:
108 continue
104 continue
109 data = c[f].data()
105 data = c[f].data()
110 if '\0' not in data and newline in data:
106 if not util.binary(data) and newline in data:
111 if not halt:
107 if not halt:
112 ui.warn(_('Attempt to commit or push text file(s) '
108 ui.warn(_('Attempt to commit or push text file(s) '
113 'using %s line endings\n') %
109 'using %s line endings\n') %
114 newlinestr[newline])
110 newlinestr[newline])
115 ui.warn(_('in %s: %s\n') % (short(c.node()), f))
111 ui.warn(_('in %s: %s\n') % (short(c.node()), f))
116 halt = True
112 halt = True
117 if halt and hooktype == 'pretxnchangegroup':
113 if halt and hooktype == 'pretxnchangegroup':
118 crlf = newlinestr[newline].lower()
114 crlf = newlinestr[newline].lower()
119 filter = filterstr[newline]
115 filter = filterstr[newline]
120 ui.warn(_('\nTo prevent this mistake in your local repository,\n'
116 ui.warn(_('\nTo prevent this mistake in your local repository,\n'
121 'add to Mercurial.ini or .hg/hgrc:\n'
117 'add to Mercurial.ini or .hg/hgrc:\n'
122 '\n'
118 '\n'
123 '[hooks]\n'
119 '[hooks]\n'
124 'pretxncommit.%s = python:hgext.win32text.forbid%s\n'
120 'pretxncommit.%s = python:hgext.win32text.forbid%s\n'
125 '\n'
121 '\n'
126 'and also consider adding:\n'
122 'and also consider adding:\n'
127 '\n'
123 '\n'
128 '[extensions]\n'
124 '[extensions]\n'
129 'hgext.win32text =\n'
125 'hgext.win32text =\n'
130 '[encode]\n'
126 '[encode]\n'
131 '** = %sencode:\n'
127 '** = %sencode:\n'
132 '[decode]\n'
128 '[decode]\n'
133 '** = %sdecode:\n') % (crlf, crlf, filter, filter))
129 '** = %sdecode:\n') % (crlf, crlf, filter, filter))
134 return halt
130 return halt
135
131
136 def forbidcrlf(ui, repo, hooktype, node, **kwargs):
132 def forbidcrlf(ui, repo, hooktype, node, **kwargs):
137 return forbidnewline(ui, repo, hooktype, node, '\r\n', **kwargs)
133 return forbidnewline(ui, repo, hooktype, node, '\r\n', **kwargs)
138
134
139 def forbidcr(ui, repo, hooktype, node, **kwargs):
135 def forbidcr(ui, repo, hooktype, node, **kwargs):
140 return forbidnewline(ui, repo, hooktype, node, '\r', **kwargs)
136 return forbidnewline(ui, repo, hooktype, node, '\r', **kwargs)
141
137
142 def reposetup(ui, repo):
138 def reposetup(ui, repo):
143 if not repo.local():
139 if not repo.local():
144 return
140 return
145 for name, fn in _filters.iteritems():
141 for name, fn in _filters.iteritems():
146 repo.adddatafilter(name, fn)
142 repo.adddatafilter(name, fn)
147
143
General Comments 0
You need to be logged in to leave comments. Login now