##// END OF EJS Templates
merge with main
Thomas Arendsen Hein -
r6036:c9f615ab merge default
parent child Browse files
Show More
@@ -1,520 +1,520 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.node import *
83 from mercurial.node import *
84 from mercurial.i18n import _
84 from mercurial.i18n import _
85 import re, shutil, sys, tempfile, time
85 import re, shutil, sys, tempfile, time
86
86
87 commands.optionalrepo += ' kwdemo'
87 commands.optionalrepo += ' kwdemo'
88
88
89 # hg commands that do not act on keywords
90 nokwcommands = ('add addremove bundle copy export grep identify incoming init'
91 ' log outgoing push remove rename rollback tip convert')
92
89 # hg commands that trigger expansion only when writing to working dir,
93 # hg commands that trigger expansion only when writing to working dir,
90 # not when reading filelog, and unexpand when reading from working dir
94 # not when reading filelog, and unexpand when reading from working dir
91 restricted = ('diff1', 'record',
95 restricted = 'diff1 record qfold qimport qnew qpush qrefresh qrecord'
92 'qfold', 'qimport', 'qnew', 'qpush', 'qrefresh', 'qrecord')
93
96
94 def utcdate(date):
97 def utcdate(date):
95 '''Returns hgdate in cvs-like UTC format.'''
98 '''Returns hgdate in cvs-like UTC format.'''
96 return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0]))
99 return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0]))
97
100
98
101
99 _kwtemplater = None
102 _kwtemplater = None
100
103
101 class kwtemplater(object):
104 class kwtemplater(object):
102 '''
105 '''
103 Sets up keyword templates, corresponding keyword regex, and
106 Sets up keyword templates, corresponding keyword regex, and
104 provides keyword substitution functions.
107 provides keyword substitution functions.
105 '''
108 '''
106 templates = {
109 templates = {
107 'Revision': '{node|short}',
110 'Revision': '{node|short}',
108 'Author': '{author|user}',
111 'Author': '{author|user}',
109 'Date': '{date|utcdate}',
112 'Date': '{date|utcdate}',
110 'RCSFile': '{file|basename},v',
113 'RCSFile': '{file|basename},v',
111 'Source': '{root}/{file},v',
114 'Source': '{root}/{file},v',
112 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
115 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
113 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
116 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
114 }
117 }
115
118
116 def __init__(self, ui, repo, inc, exc, hgcmd):
119 def __init__(self, ui, repo, inc, exc, restricted):
117 self.ui = ui
120 self.ui = ui
118 self.repo = repo
121 self.repo = repo
119 self.matcher = util.matcher(repo.root, inc=inc, exc=exc)[1]
122 self.matcher = util.matcher(repo.root, inc=inc, exc=exc)[1]
120 self.hgcmd = hgcmd
123 self.restricted = restricted
121 self.commitnode = None
124 self.commitnode = None
122 self.path = ''
125 self.path = ''
123
126
124 kwmaps = self.ui.configitems('keywordmaps')
127 kwmaps = self.ui.configitems('keywordmaps')
125 if kwmaps: # override default templates
128 if kwmaps: # override default templates
126 kwmaps = [(k, templater.parsestring(v, quoted=False))
129 kwmaps = [(k, templater.parsestring(v, quoted=False))
127 for (k, v) in kwmaps]
130 for (k, v) in kwmaps]
128 self.templates = dict(kwmaps)
131 self.templates = dict(kwmaps)
129 escaped = map(re.escape, self.templates.keys())
132 escaped = map(re.escape, self.templates.keys())
130 kwpat = r'\$(%s)(: [^$\n\r]*? )??\$' % '|'.join(escaped)
133 kwpat = r'\$(%s)(: [^$\n\r]*? )??\$' % '|'.join(escaped)
131 self.re_kw = re.compile(kwpat)
134 self.re_kw = re.compile(kwpat)
132
135
133 templatefilters.filters['utcdate'] = utcdate
136 templatefilters.filters['utcdate'] = utcdate
134 self.ct = cmdutil.changeset_templater(self.ui, self.repo,
137 self.ct = cmdutil.changeset_templater(self.ui, self.repo,
135 False, '', False)
138 False, '', False)
136
139
137 def substitute(self, node, data, subfunc):
140 def substitute(self, node, data, subfunc):
138 '''Obtains file's changenode if commit node not given,
141 '''Obtains file's changenode if commit node not given,
139 and calls given substitution function.'''
142 and calls given substitution function.'''
140 if self.commitnode:
143 if self.commitnode:
141 fnode = self.commitnode
144 fnode = self.commitnode
142 else:
145 else:
143 c = context.filectx(self.repo, self.path, fileid=node)
146 c = context.filectx(self.repo, self.path, fileid=node)
144 fnode = c.node()
147 fnode = c.node()
145
148
146 def kwsub(mobj):
149 def kwsub(mobj):
147 '''Substitutes keyword using corresponding template.'''
150 '''Substitutes keyword using corresponding template.'''
148 kw = mobj.group(1)
151 kw = mobj.group(1)
149 self.ct.use_template(self.templates[kw])
152 self.ct.use_template(self.templates[kw])
150 self.ui.pushbuffer()
153 self.ui.pushbuffer()
151 self.ct.show(changenode=fnode, root=self.repo.root, file=self.path)
154 self.ct.show(changenode=fnode, root=self.repo.root, file=self.path)
152 return '$%s: %s $' % (kw, templatefilters.firstline(
155 ekw = templatefilters.firstline(self.ui.popbuffer())
153 self.ui.popbuffer()))
156 return '$%s: %s $' % (kw, ekw)
154
157
155 return subfunc(kwsub, data)
158 return subfunc(kwsub, data)
156
159
157 def expand(self, node, data):
160 def expand(self, node, data):
158 '''Returns data with keywords expanded.'''
161 '''Returns data with keywords expanded.'''
159 if util.binary(data) or self.hgcmd in restricted:
162 if self.restricted or util.binary(data):
160 return data
163 return data
161 return self.substitute(node, data, self.re_kw.sub)
164 return self.substitute(node, data, self.re_kw.sub)
162
165
163 def process(self, node, data, expand):
166 def process(self, node, data, expand):
164 '''Returns a tuple: data, count.
167 '''Returns a tuple: data, count.
165 Count is number of keywords/keyword substitutions,
168 Count is number of keywords/keyword substitutions,
166 telling caller whether to act on file containing data.'''
169 telling caller whether to act on file containing data.'''
167 if util.binary(data):
170 if util.binary(data):
168 return data, None
171 return data, None
169 if expand:
172 if expand:
170 return self.substitute(node, data, self.re_kw.subn)
173 return self.substitute(node, data, self.re_kw.subn)
171 return data, self.re_kw.search(data)
174 return data, self.re_kw.search(data)
172
175
173 def shrink(self, text):
176 def shrink(self, text):
174 '''Returns text with all keyword substitutions removed.'''
177 '''Returns text with all keyword substitutions removed.'''
175 if util.binary(text):
178 if util.binary(text):
176 return text
179 return text
177 return self.re_kw.sub(r'$\1$', text)
180 return self.re_kw.sub(r'$\1$', text)
178
181
179 class kwfilelog(filelog.filelog):
182 class kwfilelog(filelog.filelog):
180 '''
183 '''
181 Subclass of filelog to hook into its read, add, cmp methods.
184 Subclass of filelog to hook into its read, add, cmp methods.
182 Keywords are "stored" unexpanded, and processed on reading.
185 Keywords are "stored" unexpanded, and processed on reading.
183 '''
186 '''
184 def __init__(self, opener, path):
187 def __init__(self, opener, path):
185 super(kwfilelog, self).__init__(opener, path)
188 super(kwfilelog, self).__init__(opener, path)
186 _kwtemplater.path = path
189 _kwtemplater.path = path
187
190
188 def kwctread(self, node, expand):
191 def kwctread(self, node, expand):
189 '''Reads expanding and counting keywords, called from _overwrite.'''
192 '''Reads expanding and counting keywords, called from _overwrite.'''
190 data = super(kwfilelog, self).read(node)
193 data = super(kwfilelog, self).read(node)
191 return _kwtemplater.process(node, data, expand)
194 return _kwtemplater.process(node, data, expand)
192
195
193 def read(self, node):
196 def read(self, node):
194 '''Expands keywords when reading filelog.'''
197 '''Expands keywords when reading filelog.'''
195 data = super(kwfilelog, self).read(node)
198 data = super(kwfilelog, self).read(node)
196 return _kwtemplater.expand(node, data)
199 return _kwtemplater.expand(node, data)
197
200
198 def add(self, text, meta, tr, link, p1=None, p2=None):
201 def add(self, text, meta, tr, link, p1=None, p2=None):
199 '''Removes keyword substitutions when adding to filelog.'''
202 '''Removes keyword substitutions when adding to filelog.'''
200 text = _kwtemplater.shrink(text)
203 text = _kwtemplater.shrink(text)
201 return super(kwfilelog, self).add(text, meta, tr, link, p1=p1, p2=p2)
204 return super(kwfilelog, self).add(text, meta, tr, link, p1=p1, p2=p2)
202
205
203 def cmp(self, node, text):
206 def cmp(self, node, text):
204 '''Removes keyword substitutions for comparison.'''
207 '''Removes keyword substitutions for comparison.'''
205 text = _kwtemplater.shrink(text)
208 text = _kwtemplater.shrink(text)
206 if self.renamed(node):
209 if self.renamed(node):
207 t2 = super(kwfilelog, self).read(node)
210 t2 = super(kwfilelog, self).read(node)
208 return t2 != text
211 return t2 != text
209 return revlog.revlog.cmp(self, node, text)
212 return revlog.revlog.cmp(self, node, text)
210
213
211
214
212 # store original patch.patchfile.__init__
215 # store original patch.patchfile.__init__
213 _patchfile_init = patch.patchfile.__init__
216 _patchfile_init = patch.patchfile.__init__
214
217
215 def _kwpatchfile_init(self, ui, fname, missing=False):
218 def _kwpatchfile_init(self, ui, fname, missing=False):
216 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
219 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
217 rejects or conflicts due to expanded keywords in working dir.'''
220 rejects or conflicts due to expanded keywords in working dir.'''
218 _patchfile_init(self, ui, fname, missing=missing)
221 _patchfile_init(self, ui, fname, missing=missing)
219
222
220 if _kwtemplater.matcher(self.fname):
223 if _kwtemplater.matcher(self.fname):
221 # shrink keywords read from working dir
224 # shrink keywords read from working dir
222 kwshrunk = _kwtemplater.shrink(''.join(self.lines))
225 kwshrunk = _kwtemplater.shrink(''.join(self.lines))
223 self.lines = kwshrunk.splitlines(True)
226 self.lines = kwshrunk.splitlines(True)
224
227
225
228
226 def _iskwfile(f, link):
229 def _iskwfile(f, link):
227 return not link(f) and _kwtemplater.matcher(f)
230 return not link(f) and _kwtemplater.matcher(f)
228
231
229 def _status(ui, repo, *pats, **opts):
232 def _status(ui, repo, *pats, **opts):
230 '''Bails out if [keyword] configuration is not active.
233 '''Bails out if [keyword] configuration is not active.
231 Returns status of working directory.'''
234 Returns status of working directory.'''
232 if _kwtemplater:
235 if _kwtemplater:
233 files, match, anypats = cmdutil.matchpats(repo, pats, opts)
236 files, match, anypats = cmdutil.matchpats(repo, pats, opts)
234 return repo.status(files=files, match=match, list_clean=True)
237 return repo.status(files=files, match=match, list_clean=True)
235 if ui.configitems('keyword'):
238 if ui.configitems('keyword'):
236 raise util.Abort(_('[keyword] patterns cannot match'))
239 raise util.Abort(_('[keyword] patterns cannot match'))
237 raise util.Abort(_('no [keyword] patterns configured'))
240 raise util.Abort(_('no [keyword] patterns configured'))
238
241
239 def _overwrite(ui, repo, node=None, expand=True, files=None):
242 def _overwrite(ui, repo, node=None, expand=True, files=None):
240 '''Overwrites selected files expanding/shrinking keywords.'''
243 '''Overwrites selected files expanding/shrinking keywords.'''
241 ctx = repo.changectx(node)
244 ctx = repo.changectx(node)
242 mf = ctx.manifest()
245 mf = ctx.manifest()
243 if node is not None: # commit
246 if node is not None: # commit
244 _kwtemplater.commitnode = node
247 _kwtemplater.commitnode = node
245 files = [f for f in ctx.files() if f in mf]
248 files = [f for f in ctx.files() if f in mf]
246 notify = ui.debug
249 notify = ui.debug
247 else: # kwexpand/kwshrink
250 else: # kwexpand/kwshrink
248 notify = ui.note
251 notify = ui.note
249 candidates = [f for f in files if _iskwfile(f, mf.linkf)]
252 candidates = [f for f in files if _iskwfile(f, mf.linkf)]
250 if candidates:
253 if candidates:
251 candidates.sort()
254 candidates.sort()
252 action = expand and 'expanding' or 'shrinking'
255 action = expand and 'expanding' or 'shrinking'
253 for f in candidates:
256 for f in candidates:
254 fp = repo.file(f, kwmatch=True)
257 fp = repo.file(f, kwmatch=True)
255 data, kwfound = fp.kwctread(mf[f], expand)
258 data, kwfound = fp.kwctread(mf[f], expand)
256 if kwfound:
259 if kwfound:
257 notify(_('overwriting %s %s keywords\n') % (f, action))
260 notify(_('overwriting %s %s keywords\n') % (f, action))
258 repo.wwrite(f, data, mf.flags(f))
261 repo.wwrite(f, data, mf.flags(f))
259 repo.dirstate.normal(f)
262 repo.dirstate.normal(f)
260
263
261 def _kwfwrite(ui, repo, expand, *pats, **opts):
264 def _kwfwrite(ui, repo, expand, *pats, **opts):
262 '''Selects files and passes them to _overwrite.'''
265 '''Selects files and passes them to _overwrite.'''
263 status = _status(ui, repo, *pats, **opts)
266 status = _status(ui, repo, *pats, **opts)
264 modified, added, removed, deleted, unknown, ignored, clean = status
267 modified, added, removed, deleted, unknown, ignored, clean = status
265 if modified or added or removed or deleted:
268 if modified or added or removed or deleted:
266 raise util.Abort(_('outstanding uncommitted changes in given files'))
269 raise util.Abort(_('outstanding uncommitted changes in given files'))
267 wlock = lock = None
270 wlock = lock = None
268 try:
271 try:
269 wlock = repo.wlock()
272 wlock = repo.wlock()
270 lock = repo.lock()
273 lock = repo.lock()
271 _overwrite(ui, repo, expand=expand, files=clean)
274 _overwrite(ui, repo, expand=expand, files=clean)
272 finally:
275 finally:
273 del wlock, lock
276 del wlock, lock
274
277
275
278
276 def demo(ui, repo, *args, **opts):
279 def demo(ui, repo, *args, **opts):
277 '''print [keywordmaps] configuration and an expansion example
280 '''print [keywordmaps] configuration and an expansion example
278
281
279 Show current, custom, or default keyword template maps
282 Show current, custom, or default keyword template maps
280 and their expansion.
283 and their expansion.
281
284
282 Extend current configuration by specifying maps as arguments
285 Extend current configuration by specifying maps as arguments
283 and optionally by reading from an additional hgrc file.
286 and optionally by reading from an additional hgrc file.
284
287
285 Override current keyword template maps with "default" option.
288 Override current keyword template maps with "default" option.
286 '''
289 '''
287 def demostatus(stat):
290 def demostatus(stat):
288 ui.status(_('\n\t%s\n') % stat)
291 ui.status(_('\n\t%s\n') % stat)
289
292
290 def demoitems(section, items):
293 def demoitems(section, items):
291 ui.write('[%s]\n' % section)
294 ui.write('[%s]\n' % section)
292 for k, v in items:
295 for k, v in items:
293 ui.write('%s = %s\n' % (k, v))
296 ui.write('%s = %s\n' % (k, v))
294
297
295 msg = 'hg keyword config and expansion example'
298 msg = 'hg keyword config and expansion example'
296 kwstatus = 'current'
299 kwstatus = 'current'
297 fn = 'demo.txt'
300 fn = 'demo.txt'
298 branchname = 'demobranch'
301 branchname = 'demobranch'
299 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
302 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
300 ui.note(_('creating temporary repo at %s\n') % tmpdir)
303 ui.note(_('creating temporary repo at %s\n') % tmpdir)
301 repo = localrepo.localrepository(ui, path=tmpdir, create=True)
304 repo = localrepo.localrepository(ui, path=tmpdir, create=True)
302 ui.setconfig('keyword', fn, '')
305 ui.setconfig('keyword', fn, '')
303 if args or opts.get('rcfile'):
306 if args or opts.get('rcfile'):
304 kwstatus = 'custom'
307 kwstatus = 'custom'
305 if opts.get('rcfile'):
308 if opts.get('rcfile'):
306 ui.readconfig(opts.get('rcfile'))
309 ui.readconfig(opts.get('rcfile'))
307 if opts.get('default'):
310 if opts.get('default'):
308 kwstatus = 'default'
311 kwstatus = 'default'
309 kwmaps = kwtemplater.templates
312 kwmaps = kwtemplater.templates
310 if ui.configitems('keywordmaps'):
313 if ui.configitems('keywordmaps'):
311 # override maps from optional rcfile
314 # override maps from optional rcfile
312 for k, v in kwmaps.iteritems():
315 for k, v in kwmaps.iteritems():
313 ui.setconfig('keywordmaps', k, v)
316 ui.setconfig('keywordmaps', k, v)
314 elif args:
317 elif args:
315 # simulate hgrc parsing
318 # simulate hgrc parsing
316 rcmaps = ['[keywordmaps]\n'] + [a + '\n' for a in args]
319 rcmaps = ['[keywordmaps]\n'] + [a + '\n' for a in args]
317 fp = repo.opener('hgrc', 'w')
320 fp = repo.opener('hgrc', 'w')
318 fp.writelines(rcmaps)
321 fp.writelines(rcmaps)
319 fp.close()
322 fp.close()
320 ui.readconfig(repo.join('hgrc'))
323 ui.readconfig(repo.join('hgrc'))
321 if not opts.get('default'):
324 if not opts.get('default'):
322 kwmaps = dict(ui.configitems('keywordmaps')) or kwtemplater.templates
325 kwmaps = dict(ui.configitems('keywordmaps')) or kwtemplater.templates
323 reposetup(ui, repo)
326 reposetup(ui, repo)
324 for k, v in ui.configitems('extensions'):
327 for k, v in ui.configitems('extensions'):
325 if k.endswith('keyword'):
328 if k.endswith('keyword'):
326 extension = '%s = %s' % (k, v)
329 extension = '%s = %s' % (k, v)
327 break
330 break
328 demostatus('config using %s keyword template maps' % kwstatus)
331 demostatus('config using %s keyword template maps' % kwstatus)
329 ui.write('[extensions]\n%s\n' % extension)
332 ui.write('[extensions]\n%s\n' % extension)
330 demoitems('keyword', ui.configitems('keyword'))
333 demoitems('keyword', ui.configitems('keyword'))
331 demoitems('keywordmaps', kwmaps.iteritems())
334 demoitems('keywordmaps', kwmaps.iteritems())
332 keywords = '$' + '$\n$'.join(kwmaps.keys()) + '$\n'
335 keywords = '$' + '$\n$'.join(kwmaps.keys()) + '$\n'
333 repo.wopener(fn, 'w').write(keywords)
336 repo.wopener(fn, 'w').write(keywords)
334 repo.add([fn])
337 repo.add([fn])
335 path = repo.wjoin(fn)
338 path = repo.wjoin(fn)
336 ui.note(_('\n%s keywords written to %s:\n') % (kwstatus, path))
339 ui.note(_('\n%s keywords written to %s:\n') % (kwstatus, path))
337 ui.note(keywords)
340 ui.note(keywords)
338 ui.note('\nhg -R "%s" branch "%s"\n' % (tmpdir, branchname))
341 ui.note('\nhg -R "%s" branch "%s"\n' % (tmpdir, branchname))
339 # silence branch command if not verbose
342 # silence branch command if not verbose
340 quiet = ui.quiet
343 quiet = ui.quiet
341 ui.quiet = not ui.verbose
344 ui.quiet = not ui.verbose
342 commands.branch(ui, repo, branchname)
345 commands.branch(ui, repo, branchname)
343 ui.quiet = quiet
346 ui.quiet = quiet
344 for name, cmd in ui.configitems('hooks'):
347 for name, cmd in ui.configitems('hooks'):
345 if name.split('.', 1)[0].find('commit') > -1:
348 if name.split('.', 1)[0].find('commit') > -1:
346 repo.ui.setconfig('hooks', name, '')
349 repo.ui.setconfig('hooks', name, '')
347 ui.note(_('unhooked all commit hooks\n'))
350 ui.note(_('unhooked all commit hooks\n'))
348 ui.note('hg -R "%s" ci -m "%s"\n' % (tmpdir, msg))
351 ui.note('hg -R "%s" ci -m "%s"\n' % (tmpdir, msg))
349 repo.commit(text=msg)
352 repo.commit(text=msg)
350 format = ui.verbose and ' in %s' % path or ''
353 format = ui.verbose and ' in %s' % path or ''
351 demostatus('%s keywords expanded%s' % (kwstatus, format))
354 demostatus('%s keywords expanded%s' % (kwstatus, format))
352 ui.write(repo.wread(fn))
355 ui.write(repo.wread(fn))
353 ui.debug(_('\nremoving temporary repo %s\n') % tmpdir)
356 ui.debug(_('\nremoving temporary repo %s\n') % tmpdir)
354 shutil.rmtree(tmpdir, ignore_errors=True)
357 shutil.rmtree(tmpdir, ignore_errors=True)
355
358
356 def expand(ui, repo, *pats, **opts):
359 def expand(ui, repo, *pats, **opts):
357 '''expand keywords in working directory
360 '''expand keywords in working directory
358
361
359 Run after (re)enabling keyword expansion.
362 Run after (re)enabling keyword expansion.
360
363
361 kwexpand refuses to run if given files contain local changes.
364 kwexpand refuses to run if given files contain local changes.
362 '''
365 '''
363 # 3rd argument sets expansion to True
366 # 3rd argument sets expansion to True
364 _kwfwrite(ui, repo, True, *pats, **opts)
367 _kwfwrite(ui, repo, True, *pats, **opts)
365
368
366 def files(ui, repo, *pats, **opts):
369 def files(ui, repo, *pats, **opts):
367 '''print files currently configured for keyword expansion
370 '''print files currently configured for keyword expansion
368
371
369 Crosscheck which files in working directory are potential targets for
372 Crosscheck which files in working directory are potential targets for
370 keyword expansion.
373 keyword expansion.
371 That is, files matched by [keyword] config patterns but not symlinks.
374 That is, files matched by [keyword] config patterns but not symlinks.
372 '''
375 '''
373 status = _status(ui, repo, *pats, **opts)
376 status = _status(ui, repo, *pats, **opts)
374 modified, added, removed, deleted, unknown, ignored, clean = status
377 modified, added, removed, deleted, unknown, ignored, clean = status
375 files = modified + added + clean
378 files = modified + added + clean
376 if opts.get('untracked'):
379 if opts.get('untracked'):
377 files += unknown
380 files += unknown
378 files.sort()
381 files.sort()
379 kwfiles = [f for f in files if _iskwfile(f, repo._link)]
382 kwfiles = [f for f in files if _iskwfile(f, repo._link)]
380 cwd = pats and repo.getcwd() or ''
383 cwd = pats and repo.getcwd() or ''
381 kwfstats = not opts.get('ignore') and (('K', kwfiles),) or ()
384 kwfstats = not opts.get('ignore') and (('K', kwfiles),) or ()
382 if opts.get('all') or opts.get('ignore'):
385 if opts.get('all') or opts.get('ignore'):
383 kwfstats += (('I', [f for f in files if f not in kwfiles]),)
386 kwfstats += (('I', [f for f in files if f not in kwfiles]),)
384 for char, filenames in kwfstats:
387 for char, filenames in kwfstats:
385 format = (opts.get('all') or ui.verbose) and '%s %%s\n' % char or '%s\n'
388 format = (opts.get('all') or ui.verbose) and '%s %%s\n' % char or '%s\n'
386 for f in filenames:
389 for f in filenames:
387 ui.write(format % repo.pathto(f, cwd))
390 ui.write(format % repo.pathto(f, cwd))
388
391
389 def shrink(ui, repo, *pats, **opts):
392 def shrink(ui, repo, *pats, **opts):
390 '''revert expanded keywords in working directory
393 '''revert expanded keywords in working directory
391
394
392 Run before changing/disabling active keywords
395 Run before changing/disabling active keywords
393 or if you experience problems with "hg import" or "hg merge".
396 or if you experience problems with "hg import" or "hg merge".
394
397
395 kwshrink refuses to run if given files contain local changes.
398 kwshrink refuses to run if given files contain local changes.
396 '''
399 '''
397 # 3rd argument sets expansion to False
400 # 3rd argument sets expansion to False
398 _kwfwrite(ui, repo, False, *pats, **opts)
401 _kwfwrite(ui, repo, False, *pats, **opts)
399
402
400
403
401 def reposetup(ui, repo):
404 def reposetup(ui, repo):
402 '''Sets up repo as kwrepo for keyword substitution.
405 '''Sets up repo as kwrepo for keyword substitution.
403 Overrides file method to return kwfilelog instead of filelog
406 Overrides file method to return kwfilelog instead of filelog
404 if file matches user configuration.
407 if file matches user configuration.
405 Wraps commit to overwrite configured files with updated
408 Wraps commit to overwrite configured files with updated
406 keyword substitutions.
409 keyword substitutions.
407 This is done for local repos only, and only if there are
410 This is done for local repos only, and only if there are
408 files configured at all for keyword substitution.'''
411 files configured at all for keyword substitution.'''
409
412
410 if not repo.local():
413 if not repo.local():
411 return
414 return
412
415
413 nokwcommands = ('add', 'addremove', 'bundle', 'clone', 'copy',
414 'export', 'grep', 'identify', 'incoming', 'init',
415 'log', 'outgoing', 'push', 'remove', 'rename',
416 'rollback', 'tip',
417 'convert')
418 hgcmd, func, args, opts, cmdopts = dispatch._parse(ui, sys.argv[1:])
416 hgcmd, func, args, opts, cmdopts = dispatch._parse(ui, sys.argv[1:])
419 if hgcmd in nokwcommands:
417 if hgcmd in nokwcommands.split():
420 return
418 return
421
419
422 if hgcmd == 'diff':
420 if hgcmd == 'diff':
423 # only expand if comparing against working dir
421 # only expand if comparing against working dir
424 node1, node2 = cmdutil.revpair(repo, cmdopts.get('rev'))
422 node1, node2 = cmdutil.revpair(repo, cmdopts.get('rev'))
425 if node2 is not None:
423 if node2 is not None:
426 return
424 return
427 # shrink if rev is not current node
425 # shrink if rev is not current node
428 if node1 is not None and node1 != repo.changectx().node():
426 if node1 is not None and node1 != repo.changectx().node():
429 hgcmd = 'diff1'
427 hgcmd = 'diff1'
430
428
431 inc, exc = [], ['.hgtags']
429 inc, exc = [], ['.hgtags']
432 for pat, opt in ui.configitems('keyword'):
430 for pat, opt in ui.configitems('keyword'):
433 if opt != 'ignore':
431 if opt != 'ignore':
434 inc.append(pat)
432 inc.append(pat)
435 else:
433 else:
436 exc.append(pat)
434 exc.append(pat)
437 if not inc:
435 if not inc:
438 return
436 return
439
437
440 global _kwtemplater
438 global _kwtemplater
441 _kwtemplater = kwtemplater(ui, repo, inc, exc, hgcmd)
439 _restricted = hgcmd in restricted.split()
440 _kwtemplater = kwtemplater(ui, repo, inc, exc, _restricted)
442
441
443 class kwrepo(repo.__class__):
442 class kwrepo(repo.__class__):
444 def file(self, f, kwmatch=False):
443 def file(self, f, kwmatch=False):
445 if f[0] == '/':
444 if f[0] == '/':
446 f = f[1:]
445 f = f[1:]
447 if kwmatch or _kwtemplater.matcher(f):
446 if kwmatch or _kwtemplater.matcher(f):
448 return kwfilelog(self.sopener, f)
447 return kwfilelog(self.sopener, f)
449 return filelog.filelog(self.sopener, f)
448 return filelog.filelog(self.sopener, f)
450
449
451 def wread(self, filename):
450 def wread(self, filename):
452 data = super(kwrepo, self).wread(filename)
451 data = super(kwrepo, self).wread(filename)
453 if hgcmd in restricted and _kwtemplater.matcher(filename):
452 if _restricted and _kwtemplater.matcher(filename):
454 return _kwtemplater.shrink(data)
453 return _kwtemplater.shrink(data)
455 return data
454 return data
456
455
457 def commit(self, files=None, text='', user=None, date=None,
456 def commit(self, files=None, text='', user=None, date=None,
458 match=util.always, force=False, force_editor=False,
457 match=util.always, force=False, force_editor=False,
459 p1=None, p2=None, extra={}):
458 p1=None, p2=None, extra={}, empty_ok=False):
460 wlock = lock = None
459 wlock = lock = None
461 _p1 = _p2 = None
460 _p1 = _p2 = None
462 try:
461 try:
463 wlock = self.wlock()
462 wlock = self.wlock()
464 lock = self.lock()
463 lock = self.lock()
465 # store and postpone commit hooks
464 # store and postpone commit hooks
466 commithooks = {}
465 commithooks = {}
467 for name, cmd in ui.configitems('hooks'):
466 for name, cmd in ui.configitems('hooks'):
468 if name.split('.', 1)[0] == 'commit':
467 if name.split('.', 1)[0] == 'commit':
469 commithooks[name] = cmd
468 commithooks[name] = cmd
470 ui.setconfig('hooks', name, None)
469 ui.setconfig('hooks', name, None)
471 if commithooks:
470 if commithooks:
472 # store parents for commit hook environment
471 # store parents for commit hook environment
473 if p1 is None:
472 if p1 is None:
474 _p1, _p2 = repo.dirstate.parents()
473 _p1, _p2 = repo.dirstate.parents()
475 else:
474 else:
476 _p1, _p2 = p1, p2 or nullid
475 _p1, _p2 = p1, p2 or nullid
477 _p1 = hex(_p1)
476 _p1 = hex(_p1)
478 if _p2 == nullid:
477 if _p2 == nullid:
479 _p2 = ''
478 _p2 = ''
480 else:
479 else:
481 _p2 = hex(_p2)
480 _p2 = hex(_p2)
482
481
483 node = super(kwrepo,
482 node = super(kwrepo,
484 self).commit(files=files, text=text, user=user,
483 self).commit(files=files, text=text, user=user,
485 date=date, match=match, force=force,
484 date=date, match=match, force=force,
486 force_editor=force_editor,
485 force_editor=force_editor,
487 p1=p1, p2=p2, extra=extra)
486 p1=p1, p2=p2, extra=extra,
487 empty_ok=empty_ok)
488
488
489 # restore commit hooks
489 # restore commit hooks
490 for name, cmd in commithooks.iteritems():
490 for name, cmd in commithooks.iteritems():
491 ui.setconfig('hooks', name, cmd)
491 ui.setconfig('hooks', name, cmd)
492 if node is not None:
492 if node is not None:
493 _overwrite(ui, self, node=node)
493 _overwrite(ui, self, node=node)
494 repo.hook('commit', node=node, parent1=_p1, parent2=_p2)
494 repo.hook('commit', node=node, parent1=_p1, parent2=_p2)
495 return node
495 return node
496 finally:
496 finally:
497 del wlock, lock
497 del wlock, lock
498
498
499 repo.__class__ = kwrepo
499 repo.__class__ = kwrepo
500 patch.patchfile.__init__ = _kwpatchfile_init
500 patch.patchfile.__init__ = _kwpatchfile_init
501
501
502
502
503 cmdtable = {
503 cmdtable = {
504 'kwdemo':
504 'kwdemo':
505 (demo,
505 (demo,
506 [('d', 'default', None, _('show default keyword template maps')),
506 [('d', 'default', None, _('show default keyword template maps')),
507 ('f', 'rcfile', [], _('read maps from rcfile'))],
507 ('f', 'rcfile', [], _('read maps from rcfile'))],
508 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...')),
508 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...')),
509 'kwexpand': (expand, commands.walkopts,
509 'kwexpand': (expand, commands.walkopts,
510 _('hg kwexpand [OPTION]... [FILE]...')),
510 _('hg kwexpand [OPTION]... [FILE]...')),
511 'kwfiles':
511 'kwfiles':
512 (files,
512 (files,
513 [('a', 'all', None, _('show keyword status flags of all files')),
513 [('a', 'all', None, _('show keyword status flags of all files')),
514 ('i', 'ignore', None, _('show files excluded from expansion')),
514 ('i', 'ignore', None, _('show files excluded from expansion')),
515 ('u', 'untracked', None, _('additionally show untracked files')),
515 ('u', 'untracked', None, _('additionally show untracked files')),
516 ] + commands.walkopts,
516 ] + commands.walkopts,
517 _('hg kwfiles [OPTION]... [FILE]...')),
517 _('hg kwfiles [OPTION]... [FILE]...')),
518 'kwshrink': (shrink, commands.walkopts,
518 'kwshrink': (shrink, commands.walkopts,
519 _('hg kwshrink [OPTION]... [FILE]...')),
519 _('hg kwshrink [OPTION]... [FILE]...')),
520 }
520 }
@@ -1,203 +1,206 b''
1 # filemerge.py - file-level merge handling for Mercurial
1 # filemerge.py - file-level merge handling for Mercurial
2 #
2 #
3 # Copyright 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
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 from node import *
8 from node import *
9 from i18n import _
9 from i18n import _
10 import util, os, tempfile, context, simplemerge, re
10 import util, os, tempfile, context, simplemerge, re
11
11
12 def _toolstr(ui, tool, part, default=""):
12 def _toolstr(ui, tool, part, default=""):
13 return ui.config("merge-tools", tool + "." + part, default)
13 return ui.config("merge-tools", tool + "." + part, default)
14
14
15 def _toolbool(ui, tool, part, default=False):
15 def _toolbool(ui, tool, part, default=False):
16 return ui.configbool("merge-tools", tool + "." + part, default)
16 return ui.configbool("merge-tools", tool + "." + part, default)
17
17
18 def _findtool(ui, tool):
18 def _findtool(ui, tool):
19 k = _toolstr(ui, tool, "regkey")
19 k = _toolstr(ui, tool, "regkey")
20 if k:
20 if k:
21 p = util.lookup_reg(k, _toolstr(ui, tool, "regname"))
21 p = util.lookup_reg(k, _toolstr(ui, tool, "regname"))
22 if p:
22 if p:
23 p = util.find_exe(p + _toolstr(ui, tool, "regappend"))
23 p = util.find_exe(p + _toolstr(ui, tool, "regappend"))
24 if p:
24 if p:
25 return p
25 return p
26 return util.find_exe(_toolstr(ui, tool, "executable", tool))
26 return util.find_exe(_toolstr(ui, tool, "executable", tool))
27
27
28 def _picktool(repo, ui, path, binary, symlink):
28 def _picktool(repo, ui, path, binary, symlink):
29 def check(tool, pat, symlink, binary):
29 def check(tool, pat, symlink, binary):
30 tmsg = tool
30 tmsg = tool
31 if pat:
31 if pat:
32 tmsg += " specified for " + pat
32 tmsg += " specified for " + pat
33 if pat and not _findtool(ui, tool): # skip search if not matching
33 if pat and not _findtool(ui, tool): # skip search if not matching
34 ui.warn(_("couldn't find merge tool %s\n") % tmsg)
34 ui.warn(_("couldn't find merge tool %s\n") % tmsg)
35 elif symlink and not _toolbool(ui, tool, "symlink"):
35 elif symlink and not _toolbool(ui, tool, "symlink"):
36 ui.warn(_("tool %s can't handle symlinks\n") % tmsg)
36 ui.warn(_("tool %s can't handle symlinks\n") % tmsg)
37 elif binary and not _toolbool(ui, tool, "binary"):
37 elif binary and not _toolbool(ui, tool, "binary"):
38 ui.warn(_("tool %s can't handle binary\n") % tmsg)
38 ui.warn(_("tool %s can't handle binary\n") % tmsg)
39 elif not util.gui() and _toolbool(ui, tool, "gui"):
39 elif not util.gui() and _toolbool(ui, tool, "gui"):
40 ui.warn(_("tool %s requires a GUI\n") % tmsg)
40 ui.warn(_("tool %s requires a GUI\n") % tmsg)
41 else:
41 else:
42 return True
42 return True
43 return False
43 return False
44
44
45 # HGMERGE takes precedence
45 # HGMERGE takes precedence
46 if os.environ.get("HGMERGE"):
46 hgmerge = os.environ.get("HGMERGE")
47 return os.environ.get("HGMERGE")
47 if hgmerge:
48 return (hgmerge, hgmerge)
48
49
49 # then patterns
50 # then patterns
50 for pat, tool in ui.configitems("merge-patterns"):
51 for pat, tool in ui.configitems("merge-patterns"):
51 mf = util.matcher(repo.root, "", [pat], [], [])[1]
52 mf = util.matcher(repo.root, "", [pat], [], [])[1]
52 if mf(path) and check(tool, pat, symlink, False):
53 if mf(path) and check(tool, pat, symlink, False):
53 return tool
54 toolpath = _findtool(ui, tool)
55 return (tool, '"' + toolpath + '"')
54
56
55 # then merge tools
57 # then merge tools
56 tools = {}
58 tools = {}
57 for k,v in ui.configitems("merge-tools"):
59 for k,v in ui.configitems("merge-tools"):
58 t = k.split('.')[0]
60 t = k.split('.')[0]
59 if t not in tools:
61 if t not in tools:
60 tools[t] = int(_toolstr(ui, t, "priority", "0"))
62 tools[t] = int(_toolstr(ui, t, "priority", "0"))
61 tools = [(-p,t) for t,p in tools.items()]
63 tools = [(-p,t) for t,p in tools.items()]
62 tools.sort()
64 tools.sort()
63 if ui.config("ui", "merge"):
65 if ui.config("ui", "merge"):
64 tools.insert(0, (None, ui.config("ui", "merge"))) # highest priority
66 tools.insert(0, (None, ui.config("ui", "merge"))) # highest priority
65 tools.append((None, "hgmerge")) # the old default, if found
67 tools.append((None, "hgmerge")) # the old default, if found
66 tools.append((None, "internal:merge")) # internal merge as last resort
67 for p,t in tools:
68 for p,t in tools:
68 if _findtool(ui, t) and check(t, None, symlink, binary):
69 toolpath = _findtool(ui, t)
69 return t
70 if toolpath and check(t, None, symlink, binary):
71 return (t, '"' + toolpath + '"')
72 # internal merge as last resort
73 return (not (symlink or binary) and "internal:merge" or None, None)
70
74
71 def _eoltype(data):
75 def _eoltype(data):
72 "Guess the EOL type of a file"
76 "Guess the EOL type of a file"
73 if '\0' in data: # binary
77 if '\0' in data: # binary
74 return None
78 return None
75 if '\r\n' in data: # Windows
79 if '\r\n' in data: # Windows
76 return '\r\n'
80 return '\r\n'
77 if '\r' in data: # Old Mac
81 if '\r' in data: # Old Mac
78 return '\r'
82 return '\r'
79 if '\n' in data: # UNIX
83 if '\n' in data: # UNIX
80 return '\n'
84 return '\n'
81 return None # unknown
85 return None # unknown
82
86
83 def _matcheol(file, origfile):
87 def _matcheol(file, origfile):
84 "Convert EOL markers in a file to match origfile"
88 "Convert EOL markers in a file to match origfile"
85 tostyle = _eoltype(open(origfile, "rb").read())
89 tostyle = _eoltype(open(origfile, "rb").read())
86 if tostyle:
90 if tostyle:
87 data = open(file, "rb").read()
91 data = open(file, "rb").read()
88 style = _eoltype(data)
92 style = _eoltype(data)
89 if style:
93 if style:
90 newdata = data.replace(style, tostyle)
94 newdata = data.replace(style, tostyle)
91 if newdata != data:
95 if newdata != data:
92 open(file, "wb").write(newdata)
96 open(file, "wb").write(newdata)
93
97
94 def filemerge(repo, fw, fd, fo, wctx, mctx):
98 def filemerge(repo, fw, fd, fo, wctx, mctx):
95 """perform a 3-way merge in the working directory
99 """perform a 3-way merge in the working directory
96
100
97 fw = original filename in the working directory
101 fw = original filename in the working directory
98 fd = destination filename in the working directory
102 fd = destination filename in the working directory
99 fo = filename in other parent
103 fo = filename in other parent
100 wctx, mctx = working and merge changecontexts
104 wctx, mctx = working and merge changecontexts
101 """
105 """
102
106
103 def temp(prefix, ctx):
107 def temp(prefix, ctx):
104 pre = "%s~%s." % (os.path.basename(ctx.path()), prefix)
108 pre = "%s~%s." % (os.path.basename(ctx.path()), prefix)
105 (fd, name) = tempfile.mkstemp(prefix=pre)
109 (fd, name) = tempfile.mkstemp(prefix=pre)
106 data = repo.wwritedata(ctx.path(), ctx.data())
110 data = repo.wwritedata(ctx.path(), ctx.data())
107 f = os.fdopen(fd, "wb")
111 f = os.fdopen(fd, "wb")
108 f.write(data)
112 f.write(data)
109 f.close()
113 f.close()
110 return name
114 return name
111
115
112 def isbin(ctx):
116 def isbin(ctx):
113 try:
117 try:
114 return util.binary(ctx.data())
118 return util.binary(ctx.data())
115 except IOError:
119 except IOError:
116 return False
120 return False
117
121
118 fco = mctx.filectx(fo)
122 fco = mctx.filectx(fo)
119 if not fco.cmp(wctx.filectx(fd).data()): # files identical?
123 if not fco.cmp(wctx.filectx(fd).data()): # files identical?
120 return None
124 return None
121
125
122 ui = repo.ui
126 ui = repo.ui
123 fcm = wctx.filectx(fw)
127 fcm = wctx.filectx(fw)
124 fca = fcm.ancestor(fco) or repo.filectx(fw, fileid=nullrev)
128 fca = fcm.ancestor(fco) or repo.filectx(fw, fileid=nullrev)
125 binary = isbin(fcm) or isbin(fco) or isbin(fca)
129 binary = isbin(fcm) or isbin(fco) or isbin(fca)
126 symlink = fcm.islink() or fco.islink()
130 symlink = fcm.islink() or fco.islink()
127 tool = _picktool(repo, ui, fw, binary, symlink)
131 tool, toolpath = _picktool(repo, ui, fw, binary, symlink)
128 ui.debug(_("picked tool '%s' for %s (binary %s symlink %s)\n") %
132 ui.debug(_("picked tool '%s' for %s (binary %s symlink %s)\n") %
129 (tool, fw, binary, symlink))
133 (tool, fw, binary, symlink))
130
134
131 if not tool:
135 if not tool:
132 tool = "internal:local"
136 tool = "internal:local"
133 if ui.prompt(_(" no tool found to merge %s\n"
137 if ui.prompt(_(" no tool found to merge %s\n"
134 "keep (l)ocal or take (o)ther?") % fw,
138 "keep (l)ocal or take (o)ther?") % fw,
135 _("[lo]"), _("l")) != _("l"):
139 _("[lo]"), _("l")) != _("l"):
136 tool = "internal:other"
140 tool = "internal:other"
137 if tool == "internal:local":
141 if tool == "internal:local":
138 return 0
142 return 0
139 if tool == "internal:other":
143 if tool == "internal:other":
140 repo.wwrite(fd, fco.data(), fco.fileflags())
144 repo.wwrite(fd, fco.data(), fco.fileflags())
141 return 0
145 return 0
142 if tool == "internal:fail":
146 if tool == "internal:fail":
143 return 1
147 return 1
144
148
145 # do the actual merge
149 # do the actual merge
146 a = repo.wjoin(fd)
150 a = repo.wjoin(fd)
147 b = temp("base", fca)
151 b = temp("base", fca)
148 c = temp("other", fco)
152 c = temp("other", fco)
149 out = ""
153 out = ""
150 back = a + ".orig"
154 back = a + ".orig"
151 util.copyfile(a, back)
155 util.copyfile(a, back)
152
156
153 if fw != fo:
157 if fw != fo:
154 repo.ui.status(_("merging %s and %s\n") % (fw, fo))
158 repo.ui.status(_("merging %s and %s\n") % (fw, fo))
155 else:
159 else:
156 repo.ui.status(_("merging %s\n") % fw)
160 repo.ui.status(_("merging %s\n") % fw)
157 repo.ui.debug(_("my %s other %s ancestor %s\n") % (fcm, fco, fca))
161 repo.ui.debug(_("my %s other %s ancestor %s\n") % (fcm, fco, fca))
158
162
159 # do we attempt to simplemerge first?
163 # do we attempt to simplemerge first?
160 if _toolbool(ui, tool, "premerge", not (binary or symlink)):
164 if _toolbool(ui, tool, "premerge", not (binary or symlink)):
161 r = simplemerge.simplemerge(a, b, c, quiet=True)
165 r = simplemerge.simplemerge(a, b, c, quiet=True)
162 if not r:
166 if not r:
163 ui.debug(_(" premerge successful\n"))
167 ui.debug(_(" premerge successful\n"))
164 os.unlink(back)
168 os.unlink(back)
165 os.unlink(b)
169 os.unlink(b)
166 os.unlink(c)
170 os.unlink(c)
167 return 0
171 return 0
168 util.copyfile(back, a) # restore from backup and try again
172 util.copyfile(back, a) # restore from backup and try again
169
173
170 env = dict(HG_FILE=fd,
174 env = dict(HG_FILE=fd,
171 HG_MY_NODE=str(wctx.parents()[0]),
175 HG_MY_NODE=str(wctx.parents()[0]),
172 HG_OTHER_NODE=str(mctx),
176 HG_OTHER_NODE=str(mctx),
173 HG_MY_ISLINK=fcm.islink(),
177 HG_MY_ISLINK=fcm.islink(),
174 HG_OTHER_ISLINK=fco.islink(),
178 HG_OTHER_ISLINK=fco.islink(),
175 HG_BASE_ISLINK=fca.islink())
179 HG_BASE_ISLINK=fca.islink())
176
180
177 if tool == "internal:merge":
181 if tool == "internal:merge":
178 r = simplemerge.simplemerge(a, b, c, label=['local', 'other'])
182 r = simplemerge.simplemerge(a, b, c, label=['local', 'other'])
179 else:
183 else:
180 toolpath = _findtool(ui, tool)
181 args = _toolstr(ui, tool, "args", '$local $base $other')
184 args = _toolstr(ui, tool, "args", '$local $base $other')
182 if "$output" in args:
185 if "$output" in args:
183 out, a = a, back # read input from backup, write to original
186 out, a = a, back # read input from backup, write to original
184 replace = dict(local=a, base=b, other=c, output=out)
187 replace = dict(local=a, base=b, other=c, output=out)
185 args = re.sub("\$(local|base|other|output)",
188 args = re.sub("\$(local|base|other|output)",
186 lambda x: '"%s"' % replace[x.group()[1:]], args)
189 lambda x: '"%s"' % replace[x.group()[1:]], args)
187 r = util.system(toolpath + ' ' + args, cwd=repo.root, environ=env)
190 r = util.system(toolpath + ' ' + args, cwd=repo.root, environ=env)
188
191
189 if not r and _toolbool(ui, tool, "checkconflicts"):
192 if not r and _toolbool(ui, tool, "checkconflicts"):
190 if re.match("^(<<<<<<< .*|=======|>>>>>>> .*)$", fcm.data()):
193 if re.match("^(<<<<<<< .*|=======|>>>>>>> .*)$", fcm.data()):
191 r = 1
194 r = 1
192
195
193 if _toolbool(ui, tool, "fixeol"):
196 if _toolbool(ui, tool, "fixeol"):
194 _matcheol(repo.wjoin(fd), back)
197 _matcheol(repo.wjoin(fd), back)
195
198
196 if r:
199 if r:
197 repo.ui.warn(_("merging %s failed!\n") % fd)
200 repo.ui.warn(_("merging %s failed!\n") % fd)
198 else:
201 else:
199 os.unlink(back)
202 os.unlink(back)
200
203
201 os.unlink(b)
204 os.unlink(b)
202 os.unlink(c)
205 os.unlink(c)
203 return r
206 return r
General Comments 0
You need to be logged in to leave comments. Login now