##// END OF EJS Templates
cmdutil: replace unix pipe handshake with file lock...
Patrick Mezard -
r10238:e22695b4 default
parent child Browse files
Show More
@@ -1,1165 +1,1172 b''
1 # cmdutil.py - help for command processing in mercurial
1 # cmdutil.py - help for command processing in mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2, incorporated herein by reference.
6 # GNU General Public License version 2, incorporated herein by reference.
7
7
8 from node import hex, nullid, nullrev, short
8 from node import hex, nullid, nullrev, short
9 from i18n import _
9 from i18n import _
10 import os, sys, errno, re, glob
10 import os, sys, errno, re, glob, tempfile, time
11 import mdiff, bdiff, util, templater, patch, error, encoding, templatekw
11 import mdiff, bdiff, util, templater, patch, error, encoding, templatekw
12 import match as _match
12 import match as _match
13
13
14 revrangesep = ':'
14 revrangesep = ':'
15
15
16 def findpossible(cmd, table, strict=False):
16 def findpossible(cmd, table, strict=False):
17 """
17 """
18 Return cmd -> (aliases, command table entry)
18 Return cmd -> (aliases, command table entry)
19 for each matching command.
19 for each matching command.
20 Return debug commands (or their aliases) only if no normal command matches.
20 Return debug commands (or their aliases) only if no normal command matches.
21 """
21 """
22 choice = {}
22 choice = {}
23 debugchoice = {}
23 debugchoice = {}
24 for e in table.keys():
24 for e in table.keys():
25 aliases = e.lstrip("^").split("|")
25 aliases = e.lstrip("^").split("|")
26 found = None
26 found = None
27 if cmd in aliases:
27 if cmd in aliases:
28 found = cmd
28 found = cmd
29 elif not strict:
29 elif not strict:
30 for a in aliases:
30 for a in aliases:
31 if a.startswith(cmd):
31 if a.startswith(cmd):
32 found = a
32 found = a
33 break
33 break
34 if found is not None:
34 if found is not None:
35 if aliases[0].startswith("debug") or found.startswith("debug"):
35 if aliases[0].startswith("debug") or found.startswith("debug"):
36 debugchoice[found] = (aliases, table[e])
36 debugchoice[found] = (aliases, table[e])
37 else:
37 else:
38 choice[found] = (aliases, table[e])
38 choice[found] = (aliases, table[e])
39
39
40 if not choice and debugchoice:
40 if not choice and debugchoice:
41 choice = debugchoice
41 choice = debugchoice
42
42
43 return choice
43 return choice
44
44
45 def findcmd(cmd, table, strict=True):
45 def findcmd(cmd, table, strict=True):
46 """Return (aliases, command table entry) for command string."""
46 """Return (aliases, command table entry) for command string."""
47 choice = findpossible(cmd, table, strict)
47 choice = findpossible(cmd, table, strict)
48
48
49 if cmd in choice:
49 if cmd in choice:
50 return choice[cmd]
50 return choice[cmd]
51
51
52 if len(choice) > 1:
52 if len(choice) > 1:
53 clist = choice.keys()
53 clist = choice.keys()
54 clist.sort()
54 clist.sort()
55 raise error.AmbiguousCommand(cmd, clist)
55 raise error.AmbiguousCommand(cmd, clist)
56
56
57 if choice:
57 if choice:
58 return choice.values()[0]
58 return choice.values()[0]
59
59
60 raise error.UnknownCommand(cmd)
60 raise error.UnknownCommand(cmd)
61
61
62 def bail_if_changed(repo):
62 def bail_if_changed(repo):
63 if repo.dirstate.parents()[1] != nullid:
63 if repo.dirstate.parents()[1] != nullid:
64 raise util.Abort(_('outstanding uncommitted merge'))
64 raise util.Abort(_('outstanding uncommitted merge'))
65 modified, added, removed, deleted = repo.status()[:4]
65 modified, added, removed, deleted = repo.status()[:4]
66 if modified or added or removed or deleted:
66 if modified or added or removed or deleted:
67 raise util.Abort(_("outstanding uncommitted changes"))
67 raise util.Abort(_("outstanding uncommitted changes"))
68
68
69 def logmessage(opts):
69 def logmessage(opts):
70 """ get the log message according to -m and -l option """
70 """ get the log message according to -m and -l option """
71 message = opts.get('message')
71 message = opts.get('message')
72 logfile = opts.get('logfile')
72 logfile = opts.get('logfile')
73
73
74 if message and logfile:
74 if message and logfile:
75 raise util.Abort(_('options --message and --logfile are mutually '
75 raise util.Abort(_('options --message and --logfile are mutually '
76 'exclusive'))
76 'exclusive'))
77 if not message and logfile:
77 if not message and logfile:
78 try:
78 try:
79 if logfile == '-':
79 if logfile == '-':
80 message = sys.stdin.read()
80 message = sys.stdin.read()
81 else:
81 else:
82 message = open(logfile).read()
82 message = open(logfile).read()
83 except IOError, inst:
83 except IOError, inst:
84 raise util.Abort(_("can't read commit message '%s': %s") %
84 raise util.Abort(_("can't read commit message '%s': %s") %
85 (logfile, inst.strerror))
85 (logfile, inst.strerror))
86 return message
86 return message
87
87
88 def loglimit(opts):
88 def loglimit(opts):
89 """get the log limit according to option -l/--limit"""
89 """get the log limit according to option -l/--limit"""
90 limit = opts.get('limit')
90 limit = opts.get('limit')
91 if limit:
91 if limit:
92 try:
92 try:
93 limit = int(limit)
93 limit = int(limit)
94 except ValueError:
94 except ValueError:
95 raise util.Abort(_('limit must be a positive integer'))
95 raise util.Abort(_('limit must be a positive integer'))
96 if limit <= 0: raise util.Abort(_('limit must be positive'))
96 if limit <= 0: raise util.Abort(_('limit must be positive'))
97 else:
97 else:
98 limit = None
98 limit = None
99 return limit
99 return limit
100
100
101 def remoteui(src, opts):
101 def remoteui(src, opts):
102 'build a remote ui from ui or repo and opts'
102 'build a remote ui from ui or repo and opts'
103 if hasattr(src, 'baseui'): # looks like a repository
103 if hasattr(src, 'baseui'): # looks like a repository
104 dst = src.baseui.copy() # drop repo-specific config
104 dst = src.baseui.copy() # drop repo-specific config
105 src = src.ui # copy target options from repo
105 src = src.ui # copy target options from repo
106 else: # assume it's a global ui object
106 else: # assume it's a global ui object
107 dst = src.copy() # keep all global options
107 dst = src.copy() # keep all global options
108
108
109 # copy ssh-specific options
109 # copy ssh-specific options
110 for o in 'ssh', 'remotecmd':
110 for o in 'ssh', 'remotecmd':
111 v = opts.get(o) or src.config('ui', o)
111 v = opts.get(o) or src.config('ui', o)
112 if v:
112 if v:
113 dst.setconfig("ui", o, v)
113 dst.setconfig("ui", o, v)
114
114
115 # copy bundle-specific options
115 # copy bundle-specific options
116 r = src.config('bundle', 'mainreporoot')
116 r = src.config('bundle', 'mainreporoot')
117 if r:
117 if r:
118 dst.setconfig('bundle', 'mainreporoot', r)
118 dst.setconfig('bundle', 'mainreporoot', r)
119
119
120 # copy auth section settings
120 # copy auth section settings
121 for key, val in src.configitems('auth'):
121 for key, val in src.configitems('auth'):
122 dst.setconfig('auth', key, val)
122 dst.setconfig('auth', key, val)
123
123
124 return dst
124 return dst
125
125
126 def revpair(repo, revs):
126 def revpair(repo, revs):
127 '''return pair of nodes, given list of revisions. second item can
127 '''return pair of nodes, given list of revisions. second item can
128 be None, meaning use working dir.'''
128 be None, meaning use working dir.'''
129
129
130 def revfix(repo, val, defval):
130 def revfix(repo, val, defval):
131 if not val and val != 0 and defval is not None:
131 if not val and val != 0 and defval is not None:
132 val = defval
132 val = defval
133 return repo.lookup(val)
133 return repo.lookup(val)
134
134
135 if not revs:
135 if not revs:
136 return repo.dirstate.parents()[0], None
136 return repo.dirstate.parents()[0], None
137 end = None
137 end = None
138 if len(revs) == 1:
138 if len(revs) == 1:
139 if revrangesep in revs[0]:
139 if revrangesep in revs[0]:
140 start, end = revs[0].split(revrangesep, 1)
140 start, end = revs[0].split(revrangesep, 1)
141 start = revfix(repo, start, 0)
141 start = revfix(repo, start, 0)
142 end = revfix(repo, end, len(repo) - 1)
142 end = revfix(repo, end, len(repo) - 1)
143 else:
143 else:
144 start = revfix(repo, revs[0], None)
144 start = revfix(repo, revs[0], None)
145 elif len(revs) == 2:
145 elif len(revs) == 2:
146 if revrangesep in revs[0] or revrangesep in revs[1]:
146 if revrangesep in revs[0] or revrangesep in revs[1]:
147 raise util.Abort(_('too many revisions specified'))
147 raise util.Abort(_('too many revisions specified'))
148 start = revfix(repo, revs[0], None)
148 start = revfix(repo, revs[0], None)
149 end = revfix(repo, revs[1], None)
149 end = revfix(repo, revs[1], None)
150 else:
150 else:
151 raise util.Abort(_('too many revisions specified'))
151 raise util.Abort(_('too many revisions specified'))
152 return start, end
152 return start, end
153
153
154 def revrange(repo, revs):
154 def revrange(repo, revs):
155 """Yield revision as strings from a list of revision specifications."""
155 """Yield revision as strings from a list of revision specifications."""
156
156
157 def revfix(repo, val, defval):
157 def revfix(repo, val, defval):
158 if not val and val != 0 and defval is not None:
158 if not val and val != 0 and defval is not None:
159 return defval
159 return defval
160 return repo.changelog.rev(repo.lookup(val))
160 return repo.changelog.rev(repo.lookup(val))
161
161
162 seen, l = set(), []
162 seen, l = set(), []
163 for spec in revs:
163 for spec in revs:
164 if revrangesep in spec:
164 if revrangesep in spec:
165 start, end = spec.split(revrangesep, 1)
165 start, end = spec.split(revrangesep, 1)
166 start = revfix(repo, start, 0)
166 start = revfix(repo, start, 0)
167 end = revfix(repo, end, len(repo) - 1)
167 end = revfix(repo, end, len(repo) - 1)
168 step = start > end and -1 or 1
168 step = start > end and -1 or 1
169 for rev in xrange(start, end+step, step):
169 for rev in xrange(start, end+step, step):
170 if rev in seen:
170 if rev in seen:
171 continue
171 continue
172 seen.add(rev)
172 seen.add(rev)
173 l.append(rev)
173 l.append(rev)
174 else:
174 else:
175 rev = revfix(repo, spec, None)
175 rev = revfix(repo, spec, None)
176 if rev in seen:
176 if rev in seen:
177 continue
177 continue
178 seen.add(rev)
178 seen.add(rev)
179 l.append(rev)
179 l.append(rev)
180
180
181 return l
181 return l
182
182
183 def make_filename(repo, pat, node,
183 def make_filename(repo, pat, node,
184 total=None, seqno=None, revwidth=None, pathname=None):
184 total=None, seqno=None, revwidth=None, pathname=None):
185 node_expander = {
185 node_expander = {
186 'H': lambda: hex(node),
186 'H': lambda: hex(node),
187 'R': lambda: str(repo.changelog.rev(node)),
187 'R': lambda: str(repo.changelog.rev(node)),
188 'h': lambda: short(node),
188 'h': lambda: short(node),
189 }
189 }
190 expander = {
190 expander = {
191 '%': lambda: '%',
191 '%': lambda: '%',
192 'b': lambda: os.path.basename(repo.root),
192 'b': lambda: os.path.basename(repo.root),
193 }
193 }
194
194
195 try:
195 try:
196 if node:
196 if node:
197 expander.update(node_expander)
197 expander.update(node_expander)
198 if node:
198 if node:
199 expander['r'] = (lambda:
199 expander['r'] = (lambda:
200 str(repo.changelog.rev(node)).zfill(revwidth or 0))
200 str(repo.changelog.rev(node)).zfill(revwidth or 0))
201 if total is not None:
201 if total is not None:
202 expander['N'] = lambda: str(total)
202 expander['N'] = lambda: str(total)
203 if seqno is not None:
203 if seqno is not None:
204 expander['n'] = lambda: str(seqno)
204 expander['n'] = lambda: str(seqno)
205 if total is not None and seqno is not None:
205 if total is not None and seqno is not None:
206 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
206 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
207 if pathname is not None:
207 if pathname is not None:
208 expander['s'] = lambda: os.path.basename(pathname)
208 expander['s'] = lambda: os.path.basename(pathname)
209 expander['d'] = lambda: os.path.dirname(pathname) or '.'
209 expander['d'] = lambda: os.path.dirname(pathname) or '.'
210 expander['p'] = lambda: pathname
210 expander['p'] = lambda: pathname
211
211
212 newname = []
212 newname = []
213 patlen = len(pat)
213 patlen = len(pat)
214 i = 0
214 i = 0
215 while i < patlen:
215 while i < patlen:
216 c = pat[i]
216 c = pat[i]
217 if c == '%':
217 if c == '%':
218 i += 1
218 i += 1
219 c = pat[i]
219 c = pat[i]
220 c = expander[c]()
220 c = expander[c]()
221 newname.append(c)
221 newname.append(c)
222 i += 1
222 i += 1
223 return ''.join(newname)
223 return ''.join(newname)
224 except KeyError, inst:
224 except KeyError, inst:
225 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
225 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
226 inst.args[0])
226 inst.args[0])
227
227
228 def make_file(repo, pat, node=None,
228 def make_file(repo, pat, node=None,
229 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
229 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
230
230
231 writable = 'w' in mode or 'a' in mode
231 writable = 'w' in mode or 'a' in mode
232
232
233 if not pat or pat == '-':
233 if not pat or pat == '-':
234 return writable and sys.stdout or sys.stdin
234 return writable and sys.stdout or sys.stdin
235 if hasattr(pat, 'write') and writable:
235 if hasattr(pat, 'write') and writable:
236 return pat
236 return pat
237 if hasattr(pat, 'read') and 'r' in mode:
237 if hasattr(pat, 'read') and 'r' in mode:
238 return pat
238 return pat
239 return open(make_filename(repo, pat, node, total, seqno, revwidth,
239 return open(make_filename(repo, pat, node, total, seqno, revwidth,
240 pathname),
240 pathname),
241 mode)
241 mode)
242
242
243 def expandpats(pats):
243 def expandpats(pats):
244 if not util.expandglobs:
244 if not util.expandglobs:
245 return list(pats)
245 return list(pats)
246 ret = []
246 ret = []
247 for p in pats:
247 for p in pats:
248 kind, name = _match._patsplit(p, None)
248 kind, name = _match._patsplit(p, None)
249 if kind is None:
249 if kind is None:
250 try:
250 try:
251 globbed = glob.glob(name)
251 globbed = glob.glob(name)
252 except re.error:
252 except re.error:
253 globbed = [name]
253 globbed = [name]
254 if globbed:
254 if globbed:
255 ret.extend(globbed)
255 ret.extend(globbed)
256 continue
256 continue
257 ret.append(p)
257 ret.append(p)
258 return ret
258 return ret
259
259
260 def match(repo, pats=[], opts={}, globbed=False, default='relpath'):
260 def match(repo, pats=[], opts={}, globbed=False, default='relpath'):
261 if not globbed and default == 'relpath':
261 if not globbed and default == 'relpath':
262 pats = expandpats(pats or [])
262 pats = expandpats(pats or [])
263 m = _match.match(repo.root, repo.getcwd(), pats,
263 m = _match.match(repo.root, repo.getcwd(), pats,
264 opts.get('include'), opts.get('exclude'), default)
264 opts.get('include'), opts.get('exclude'), default)
265 def badfn(f, msg):
265 def badfn(f, msg):
266 repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
266 repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
267 m.bad = badfn
267 m.bad = badfn
268 return m
268 return m
269
269
270 def matchall(repo):
270 def matchall(repo):
271 return _match.always(repo.root, repo.getcwd())
271 return _match.always(repo.root, repo.getcwd())
272
272
273 def matchfiles(repo, files):
273 def matchfiles(repo, files):
274 return _match.exact(repo.root, repo.getcwd(), files)
274 return _match.exact(repo.root, repo.getcwd(), files)
275
275
276 def findrenames(repo, added, removed, threshold):
276 def findrenames(repo, added, removed, threshold):
277 '''find renamed files -- yields (before, after, score) tuples'''
277 '''find renamed files -- yields (before, after, score) tuples'''
278 copies = {}
278 copies = {}
279 ctx = repo['.']
279 ctx = repo['.']
280 for r in removed:
280 for r in removed:
281 if r not in ctx:
281 if r not in ctx:
282 continue
282 continue
283 fctx = ctx.filectx(r)
283 fctx = ctx.filectx(r)
284
284
285 def score(text):
285 def score(text):
286 if not len(text):
286 if not len(text):
287 return 0.0
287 return 0.0
288 if not fctx.cmp(text):
288 if not fctx.cmp(text):
289 return 1.0
289 return 1.0
290 if threshold == 1.0:
290 if threshold == 1.0:
291 return 0.0
291 return 0.0
292 orig = fctx.data()
292 orig = fctx.data()
293 # bdiff.blocks() returns blocks of matching lines
293 # bdiff.blocks() returns blocks of matching lines
294 # count the number of bytes in each
294 # count the number of bytes in each
295 equal = 0
295 equal = 0
296 alines = mdiff.splitnewlines(text)
296 alines = mdiff.splitnewlines(text)
297 matches = bdiff.blocks(text, orig)
297 matches = bdiff.blocks(text, orig)
298 for x1, x2, y1, y2 in matches:
298 for x1, x2, y1, y2 in matches:
299 for line in alines[x1:x2]:
299 for line in alines[x1:x2]:
300 equal += len(line)
300 equal += len(line)
301
301
302 lengths = len(text) + len(orig)
302 lengths = len(text) + len(orig)
303 return equal * 2.0 / lengths
303 return equal * 2.0 / lengths
304
304
305 for a in added:
305 for a in added:
306 bestscore = copies.get(a, (None, threshold))[1]
306 bestscore = copies.get(a, (None, threshold))[1]
307 myscore = score(repo.wread(a))
307 myscore = score(repo.wread(a))
308 if myscore >= bestscore:
308 if myscore >= bestscore:
309 copies[a] = (r, myscore)
309 copies[a] = (r, myscore)
310
310
311 for dest, v in copies.iteritems():
311 for dest, v in copies.iteritems():
312 source, score = v
312 source, score = v
313 yield source, dest, score
313 yield source, dest, score
314
314
315 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
315 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
316 if dry_run is None:
316 if dry_run is None:
317 dry_run = opts.get('dry_run')
317 dry_run = opts.get('dry_run')
318 if similarity is None:
318 if similarity is None:
319 similarity = float(opts.get('similarity') or 0)
319 similarity = float(opts.get('similarity') or 0)
320 # we'd use status here, except handling of symlinks and ignore is tricky
320 # we'd use status here, except handling of symlinks and ignore is tricky
321 added, unknown, deleted, removed = [], [], [], []
321 added, unknown, deleted, removed = [], [], [], []
322 audit_path = util.path_auditor(repo.root)
322 audit_path = util.path_auditor(repo.root)
323 m = match(repo, pats, opts)
323 m = match(repo, pats, opts)
324 for abs in repo.walk(m):
324 for abs in repo.walk(m):
325 target = repo.wjoin(abs)
325 target = repo.wjoin(abs)
326 good = True
326 good = True
327 try:
327 try:
328 audit_path(abs)
328 audit_path(abs)
329 except:
329 except:
330 good = False
330 good = False
331 rel = m.rel(abs)
331 rel = m.rel(abs)
332 exact = m.exact(abs)
332 exact = m.exact(abs)
333 if good and abs not in repo.dirstate:
333 if good and abs not in repo.dirstate:
334 unknown.append(abs)
334 unknown.append(abs)
335 if repo.ui.verbose or not exact:
335 if repo.ui.verbose or not exact:
336 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
336 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
337 elif repo.dirstate[abs] != 'r' and (not good or not util.lexists(target)
337 elif repo.dirstate[abs] != 'r' and (not good or not util.lexists(target)
338 or (os.path.isdir(target) and not os.path.islink(target))):
338 or (os.path.isdir(target) and not os.path.islink(target))):
339 deleted.append(abs)
339 deleted.append(abs)
340 if repo.ui.verbose or not exact:
340 if repo.ui.verbose or not exact:
341 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
341 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
342 # for finding renames
342 # for finding renames
343 elif repo.dirstate[abs] == 'r':
343 elif repo.dirstate[abs] == 'r':
344 removed.append(abs)
344 removed.append(abs)
345 elif repo.dirstate[abs] == 'a':
345 elif repo.dirstate[abs] == 'a':
346 added.append(abs)
346 added.append(abs)
347 if not dry_run:
347 if not dry_run:
348 repo.remove(deleted)
348 repo.remove(deleted)
349 repo.add(unknown)
349 repo.add(unknown)
350 if similarity > 0:
350 if similarity > 0:
351 for old, new, score in findrenames(repo, added + unknown,
351 for old, new, score in findrenames(repo, added + unknown,
352 removed + deleted, similarity):
352 removed + deleted, similarity):
353 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
353 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
354 repo.ui.status(_('recording removal of %s as rename to %s '
354 repo.ui.status(_('recording removal of %s as rename to %s '
355 '(%d%% similar)\n') %
355 '(%d%% similar)\n') %
356 (m.rel(old), m.rel(new), score * 100))
356 (m.rel(old), m.rel(new), score * 100))
357 if not dry_run:
357 if not dry_run:
358 repo.copy(old, new)
358 repo.copy(old, new)
359
359
360 def copy(ui, repo, pats, opts, rename=False):
360 def copy(ui, repo, pats, opts, rename=False):
361 # called with the repo lock held
361 # called with the repo lock held
362 #
362 #
363 # hgsep => pathname that uses "/" to separate directories
363 # hgsep => pathname that uses "/" to separate directories
364 # ossep => pathname that uses os.sep to separate directories
364 # ossep => pathname that uses os.sep to separate directories
365 cwd = repo.getcwd()
365 cwd = repo.getcwd()
366 targets = {}
366 targets = {}
367 after = opts.get("after")
367 after = opts.get("after")
368 dryrun = opts.get("dry_run")
368 dryrun = opts.get("dry_run")
369
369
370 def walkpat(pat):
370 def walkpat(pat):
371 srcs = []
371 srcs = []
372 m = match(repo, [pat], opts, globbed=True)
372 m = match(repo, [pat], opts, globbed=True)
373 for abs in repo.walk(m):
373 for abs in repo.walk(m):
374 state = repo.dirstate[abs]
374 state = repo.dirstate[abs]
375 rel = m.rel(abs)
375 rel = m.rel(abs)
376 exact = m.exact(abs)
376 exact = m.exact(abs)
377 if state in '?r':
377 if state in '?r':
378 if exact and state == '?':
378 if exact and state == '?':
379 ui.warn(_('%s: not copying - file is not managed\n') % rel)
379 ui.warn(_('%s: not copying - file is not managed\n') % rel)
380 if exact and state == 'r':
380 if exact and state == 'r':
381 ui.warn(_('%s: not copying - file has been marked for'
381 ui.warn(_('%s: not copying - file has been marked for'
382 ' remove\n') % rel)
382 ' remove\n') % rel)
383 continue
383 continue
384 # abs: hgsep
384 # abs: hgsep
385 # rel: ossep
385 # rel: ossep
386 srcs.append((abs, rel, exact))
386 srcs.append((abs, rel, exact))
387 return srcs
387 return srcs
388
388
389 # abssrc: hgsep
389 # abssrc: hgsep
390 # relsrc: ossep
390 # relsrc: ossep
391 # otarget: ossep
391 # otarget: ossep
392 def copyfile(abssrc, relsrc, otarget, exact):
392 def copyfile(abssrc, relsrc, otarget, exact):
393 abstarget = util.canonpath(repo.root, cwd, otarget)
393 abstarget = util.canonpath(repo.root, cwd, otarget)
394 reltarget = repo.pathto(abstarget, cwd)
394 reltarget = repo.pathto(abstarget, cwd)
395 target = repo.wjoin(abstarget)
395 target = repo.wjoin(abstarget)
396 src = repo.wjoin(abssrc)
396 src = repo.wjoin(abssrc)
397 state = repo.dirstate[abstarget]
397 state = repo.dirstate[abstarget]
398
398
399 # check for collisions
399 # check for collisions
400 prevsrc = targets.get(abstarget)
400 prevsrc = targets.get(abstarget)
401 if prevsrc is not None:
401 if prevsrc is not None:
402 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
402 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
403 (reltarget, repo.pathto(abssrc, cwd),
403 (reltarget, repo.pathto(abssrc, cwd),
404 repo.pathto(prevsrc, cwd)))
404 repo.pathto(prevsrc, cwd)))
405 return
405 return
406
406
407 # check for overwrites
407 # check for overwrites
408 exists = os.path.exists(target)
408 exists = os.path.exists(target)
409 if not after and exists or after and state in 'mn':
409 if not after and exists or after and state in 'mn':
410 if not opts['force']:
410 if not opts['force']:
411 ui.warn(_('%s: not overwriting - file exists\n') %
411 ui.warn(_('%s: not overwriting - file exists\n') %
412 reltarget)
412 reltarget)
413 return
413 return
414
414
415 if after:
415 if after:
416 if not exists:
416 if not exists:
417 return
417 return
418 elif not dryrun:
418 elif not dryrun:
419 try:
419 try:
420 if exists:
420 if exists:
421 os.unlink(target)
421 os.unlink(target)
422 targetdir = os.path.dirname(target) or '.'
422 targetdir = os.path.dirname(target) or '.'
423 if not os.path.isdir(targetdir):
423 if not os.path.isdir(targetdir):
424 os.makedirs(targetdir)
424 os.makedirs(targetdir)
425 util.copyfile(src, target)
425 util.copyfile(src, target)
426 except IOError, inst:
426 except IOError, inst:
427 if inst.errno == errno.ENOENT:
427 if inst.errno == errno.ENOENT:
428 ui.warn(_('%s: deleted in working copy\n') % relsrc)
428 ui.warn(_('%s: deleted in working copy\n') % relsrc)
429 else:
429 else:
430 ui.warn(_('%s: cannot copy - %s\n') %
430 ui.warn(_('%s: cannot copy - %s\n') %
431 (relsrc, inst.strerror))
431 (relsrc, inst.strerror))
432 return True # report a failure
432 return True # report a failure
433
433
434 if ui.verbose or not exact:
434 if ui.verbose or not exact:
435 if rename:
435 if rename:
436 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
436 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
437 else:
437 else:
438 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
438 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
439
439
440 targets[abstarget] = abssrc
440 targets[abstarget] = abssrc
441
441
442 # fix up dirstate
442 # fix up dirstate
443 origsrc = repo.dirstate.copied(abssrc) or abssrc
443 origsrc = repo.dirstate.copied(abssrc) or abssrc
444 if abstarget == origsrc: # copying back a copy?
444 if abstarget == origsrc: # copying back a copy?
445 if state not in 'mn' and not dryrun:
445 if state not in 'mn' and not dryrun:
446 repo.dirstate.normallookup(abstarget)
446 repo.dirstate.normallookup(abstarget)
447 else:
447 else:
448 if repo.dirstate[origsrc] == 'a' and origsrc == abssrc:
448 if repo.dirstate[origsrc] == 'a' and origsrc == abssrc:
449 if not ui.quiet:
449 if not ui.quiet:
450 ui.warn(_("%s has not been committed yet, so no copy "
450 ui.warn(_("%s has not been committed yet, so no copy "
451 "data will be stored for %s.\n")
451 "data will be stored for %s.\n")
452 % (repo.pathto(origsrc, cwd), reltarget))
452 % (repo.pathto(origsrc, cwd), reltarget))
453 if repo.dirstate[abstarget] in '?r' and not dryrun:
453 if repo.dirstate[abstarget] in '?r' and not dryrun:
454 repo.add([abstarget])
454 repo.add([abstarget])
455 elif not dryrun:
455 elif not dryrun:
456 repo.copy(origsrc, abstarget)
456 repo.copy(origsrc, abstarget)
457
457
458 if rename and not dryrun:
458 if rename and not dryrun:
459 repo.remove([abssrc], not after)
459 repo.remove([abssrc], not after)
460
460
461 # pat: ossep
461 # pat: ossep
462 # dest ossep
462 # dest ossep
463 # srcs: list of (hgsep, hgsep, ossep, bool)
463 # srcs: list of (hgsep, hgsep, ossep, bool)
464 # return: function that takes hgsep and returns ossep
464 # return: function that takes hgsep and returns ossep
465 def targetpathfn(pat, dest, srcs):
465 def targetpathfn(pat, dest, srcs):
466 if os.path.isdir(pat):
466 if os.path.isdir(pat):
467 abspfx = util.canonpath(repo.root, cwd, pat)
467 abspfx = util.canonpath(repo.root, cwd, pat)
468 abspfx = util.localpath(abspfx)
468 abspfx = util.localpath(abspfx)
469 if destdirexists:
469 if destdirexists:
470 striplen = len(os.path.split(abspfx)[0])
470 striplen = len(os.path.split(abspfx)[0])
471 else:
471 else:
472 striplen = len(abspfx)
472 striplen = len(abspfx)
473 if striplen:
473 if striplen:
474 striplen += len(os.sep)
474 striplen += len(os.sep)
475 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
475 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
476 elif destdirexists:
476 elif destdirexists:
477 res = lambda p: os.path.join(dest,
477 res = lambda p: os.path.join(dest,
478 os.path.basename(util.localpath(p)))
478 os.path.basename(util.localpath(p)))
479 else:
479 else:
480 res = lambda p: dest
480 res = lambda p: dest
481 return res
481 return res
482
482
483 # pat: ossep
483 # pat: ossep
484 # dest ossep
484 # dest ossep
485 # srcs: list of (hgsep, hgsep, ossep, bool)
485 # srcs: list of (hgsep, hgsep, ossep, bool)
486 # return: function that takes hgsep and returns ossep
486 # return: function that takes hgsep and returns ossep
487 def targetpathafterfn(pat, dest, srcs):
487 def targetpathafterfn(pat, dest, srcs):
488 if _match.patkind(pat):
488 if _match.patkind(pat):
489 # a mercurial pattern
489 # a mercurial pattern
490 res = lambda p: os.path.join(dest,
490 res = lambda p: os.path.join(dest,
491 os.path.basename(util.localpath(p)))
491 os.path.basename(util.localpath(p)))
492 else:
492 else:
493 abspfx = util.canonpath(repo.root, cwd, pat)
493 abspfx = util.canonpath(repo.root, cwd, pat)
494 if len(abspfx) < len(srcs[0][0]):
494 if len(abspfx) < len(srcs[0][0]):
495 # A directory. Either the target path contains the last
495 # A directory. Either the target path contains the last
496 # component of the source path or it does not.
496 # component of the source path or it does not.
497 def evalpath(striplen):
497 def evalpath(striplen):
498 score = 0
498 score = 0
499 for s in srcs:
499 for s in srcs:
500 t = os.path.join(dest, util.localpath(s[0])[striplen:])
500 t = os.path.join(dest, util.localpath(s[0])[striplen:])
501 if os.path.exists(t):
501 if os.path.exists(t):
502 score += 1
502 score += 1
503 return score
503 return score
504
504
505 abspfx = util.localpath(abspfx)
505 abspfx = util.localpath(abspfx)
506 striplen = len(abspfx)
506 striplen = len(abspfx)
507 if striplen:
507 if striplen:
508 striplen += len(os.sep)
508 striplen += len(os.sep)
509 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
509 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
510 score = evalpath(striplen)
510 score = evalpath(striplen)
511 striplen1 = len(os.path.split(abspfx)[0])
511 striplen1 = len(os.path.split(abspfx)[0])
512 if striplen1:
512 if striplen1:
513 striplen1 += len(os.sep)
513 striplen1 += len(os.sep)
514 if evalpath(striplen1) > score:
514 if evalpath(striplen1) > score:
515 striplen = striplen1
515 striplen = striplen1
516 res = lambda p: os.path.join(dest,
516 res = lambda p: os.path.join(dest,
517 util.localpath(p)[striplen:])
517 util.localpath(p)[striplen:])
518 else:
518 else:
519 # a file
519 # a file
520 if destdirexists:
520 if destdirexists:
521 res = lambda p: os.path.join(dest,
521 res = lambda p: os.path.join(dest,
522 os.path.basename(util.localpath(p)))
522 os.path.basename(util.localpath(p)))
523 else:
523 else:
524 res = lambda p: dest
524 res = lambda p: dest
525 return res
525 return res
526
526
527
527
528 pats = expandpats(pats)
528 pats = expandpats(pats)
529 if not pats:
529 if not pats:
530 raise util.Abort(_('no source or destination specified'))
530 raise util.Abort(_('no source or destination specified'))
531 if len(pats) == 1:
531 if len(pats) == 1:
532 raise util.Abort(_('no destination specified'))
532 raise util.Abort(_('no destination specified'))
533 dest = pats.pop()
533 dest = pats.pop()
534 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
534 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
535 if not destdirexists:
535 if not destdirexists:
536 if len(pats) > 1 or _match.patkind(pats[0]):
536 if len(pats) > 1 or _match.patkind(pats[0]):
537 raise util.Abort(_('with multiple sources, destination must be an '
537 raise util.Abort(_('with multiple sources, destination must be an '
538 'existing directory'))
538 'existing directory'))
539 if util.endswithsep(dest):
539 if util.endswithsep(dest):
540 raise util.Abort(_('destination %s is not a directory') % dest)
540 raise util.Abort(_('destination %s is not a directory') % dest)
541
541
542 tfn = targetpathfn
542 tfn = targetpathfn
543 if after:
543 if after:
544 tfn = targetpathafterfn
544 tfn = targetpathafterfn
545 copylist = []
545 copylist = []
546 for pat in pats:
546 for pat in pats:
547 srcs = walkpat(pat)
547 srcs = walkpat(pat)
548 if not srcs:
548 if not srcs:
549 continue
549 continue
550 copylist.append((tfn(pat, dest, srcs), srcs))
550 copylist.append((tfn(pat, dest, srcs), srcs))
551 if not copylist:
551 if not copylist:
552 raise util.Abort(_('no files to copy'))
552 raise util.Abort(_('no files to copy'))
553
553
554 errors = 0
554 errors = 0
555 for targetpath, srcs in copylist:
555 for targetpath, srcs in copylist:
556 for abssrc, relsrc, exact in srcs:
556 for abssrc, relsrc, exact in srcs:
557 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
557 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
558 errors += 1
558 errors += 1
559
559
560 if errors:
560 if errors:
561 ui.warn(_('(consider using --after)\n'))
561 ui.warn(_('(consider using --after)\n'))
562
562
563 return errors
563 return errors
564
564
565 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
565 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
566 runargs=None, appendpid=False):
566 runargs=None, appendpid=False):
567 '''Run a command as a service.'''
567 '''Run a command as a service.'''
568
568
569 if opts['daemon'] and not opts['daemon_pipefds']:
569 if opts['daemon'] and not opts['daemon_pipefds']:
570 rfd, wfd = os.pipe()
570 # Signal child process startup with file removal
571 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
572 os.close(lockfd)
573 try:
571 if not runargs:
574 if not runargs:
572 runargs = sys.argv[:]
575 runargs = sys.argv[:]
573 runargs.append('--daemon-pipefds=%d,%d' % (rfd, wfd))
576 runargs.append('--daemon-pipefds=%s' % lockpath)
574 # Don't pass --cwd to the child process, because we've already
577 # Don't pass --cwd to the child process, because we've already
575 # changed directory.
578 # changed directory.
576 for i in xrange(1,len(runargs)):
579 for i in xrange(1,len(runargs)):
577 if runargs[i].startswith('--cwd='):
580 if runargs[i].startswith('--cwd='):
578 del runargs[i]
581 del runargs[i]
579 break
582 break
580 elif runargs[i].startswith('--cwd'):
583 elif runargs[i].startswith('--cwd'):
581 del runargs[i:i+2]
584 del runargs[i:i+2]
582 break
585 break
583 pid = util.spawndetached(runargs)
586 pid = util.spawndetached(runargs)
584 os.close(wfd)
587 while os.path.exists(lockpath):
585 os.read(rfd, 1)
588 time.sleep(0.1)
589 finally:
590 try:
591 os.unlink(lockpath)
592 except OSError, e:
593 if e.errno != errno.ENOENT:
594 raise
586 if parentfn:
595 if parentfn:
587 return parentfn(pid)
596 return parentfn(pid)
588 else:
597 else:
589 return
598 return
590
599
591 if initfn:
600 if initfn:
592 initfn()
601 initfn()
593
602
594 if opts['pid_file']:
603 if opts['pid_file']:
595 mode = appendpid and 'a' or 'w'
604 mode = appendpid and 'a' or 'w'
596 fp = open(opts['pid_file'], mode)
605 fp = open(opts['pid_file'], mode)
597 fp.write(str(os.getpid()) + '\n')
606 fp.write(str(os.getpid()) + '\n')
598 fp.close()
607 fp.close()
599
608
600 if opts['daemon_pipefds']:
609 if opts['daemon_pipefds']:
601 rfd, wfd = [int(x) for x in opts['daemon_pipefds'].split(',')]
610 lockpath = opts['daemon_pipefds']
602 os.close(rfd)
603 try:
611 try:
604 os.setsid()
612 os.setsid()
605 except AttributeError:
613 except AttributeError:
606 pass
614 pass
607 os.write(wfd, 'y')
615 os.unlink(lockpath)
608 os.close(wfd)
609 sys.stdout.flush()
616 sys.stdout.flush()
610 sys.stderr.flush()
617 sys.stderr.flush()
611
618
612 nullfd = os.open(util.nulldev, os.O_RDWR)
619 nullfd = os.open(util.nulldev, os.O_RDWR)
613 logfilefd = nullfd
620 logfilefd = nullfd
614 if logfile:
621 if logfile:
615 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
622 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
616 os.dup2(nullfd, 0)
623 os.dup2(nullfd, 0)
617 os.dup2(logfilefd, 1)
624 os.dup2(logfilefd, 1)
618 os.dup2(logfilefd, 2)
625 os.dup2(logfilefd, 2)
619 if nullfd not in (0, 1, 2):
626 if nullfd not in (0, 1, 2):
620 os.close(nullfd)
627 os.close(nullfd)
621 if logfile and logfilefd not in (0, 1, 2):
628 if logfile and logfilefd not in (0, 1, 2):
622 os.close(logfilefd)
629 os.close(logfilefd)
623
630
624 if runfn:
631 if runfn:
625 return runfn()
632 return runfn()
626
633
627 class changeset_printer(object):
634 class changeset_printer(object):
628 '''show changeset information when templating not requested.'''
635 '''show changeset information when templating not requested.'''
629
636
630 def __init__(self, ui, repo, patch, diffopts, buffered):
637 def __init__(self, ui, repo, patch, diffopts, buffered):
631 self.ui = ui
638 self.ui = ui
632 self.repo = repo
639 self.repo = repo
633 self.buffered = buffered
640 self.buffered = buffered
634 self.patch = patch
641 self.patch = patch
635 self.diffopts = diffopts
642 self.diffopts = diffopts
636 self.header = {}
643 self.header = {}
637 self.hunk = {}
644 self.hunk = {}
638 self.lastheader = None
645 self.lastheader = None
639 self.footer = None
646 self.footer = None
640
647
641 def flush(self, rev):
648 def flush(self, rev):
642 if rev in self.header:
649 if rev in self.header:
643 h = self.header[rev]
650 h = self.header[rev]
644 if h != self.lastheader:
651 if h != self.lastheader:
645 self.lastheader = h
652 self.lastheader = h
646 self.ui.write(h)
653 self.ui.write(h)
647 del self.header[rev]
654 del self.header[rev]
648 if rev in self.hunk:
655 if rev in self.hunk:
649 self.ui.write(self.hunk[rev])
656 self.ui.write(self.hunk[rev])
650 del self.hunk[rev]
657 del self.hunk[rev]
651 return 1
658 return 1
652 return 0
659 return 0
653
660
654 def close(self):
661 def close(self):
655 if self.footer:
662 if self.footer:
656 self.ui.write(self.footer)
663 self.ui.write(self.footer)
657
664
658 def show(self, ctx, copies=None, **props):
665 def show(self, ctx, copies=None, **props):
659 if self.buffered:
666 if self.buffered:
660 self.ui.pushbuffer()
667 self.ui.pushbuffer()
661 self._show(ctx, copies, props)
668 self._show(ctx, copies, props)
662 self.hunk[ctx.rev()] = self.ui.popbuffer()
669 self.hunk[ctx.rev()] = self.ui.popbuffer()
663 else:
670 else:
664 self._show(ctx, copies, props)
671 self._show(ctx, copies, props)
665
672
666 def _show(self, ctx, copies, props):
673 def _show(self, ctx, copies, props):
667 '''show a single changeset or file revision'''
674 '''show a single changeset or file revision'''
668 changenode = ctx.node()
675 changenode = ctx.node()
669 rev = ctx.rev()
676 rev = ctx.rev()
670
677
671 if self.ui.quiet:
678 if self.ui.quiet:
672 self.ui.write("%d:%s\n" % (rev, short(changenode)))
679 self.ui.write("%d:%s\n" % (rev, short(changenode)))
673 return
680 return
674
681
675 log = self.repo.changelog
682 log = self.repo.changelog
676 date = util.datestr(ctx.date())
683 date = util.datestr(ctx.date())
677
684
678 hexfunc = self.ui.debugflag and hex or short
685 hexfunc = self.ui.debugflag and hex or short
679
686
680 parents = [(p, hexfunc(log.node(p)))
687 parents = [(p, hexfunc(log.node(p)))
681 for p in self._meaningful_parentrevs(log, rev)]
688 for p in self._meaningful_parentrevs(log, rev)]
682
689
683 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)))
690 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)))
684
691
685 branch = ctx.branch()
692 branch = ctx.branch()
686 # don't show the default branch name
693 # don't show the default branch name
687 if branch != 'default':
694 if branch != 'default':
688 branch = encoding.tolocal(branch)
695 branch = encoding.tolocal(branch)
689 self.ui.write(_("branch: %s\n") % branch)
696 self.ui.write(_("branch: %s\n") % branch)
690 for tag in self.repo.nodetags(changenode):
697 for tag in self.repo.nodetags(changenode):
691 self.ui.write(_("tag: %s\n") % tag)
698 self.ui.write(_("tag: %s\n") % tag)
692 for parent in parents:
699 for parent in parents:
693 self.ui.write(_("parent: %d:%s\n") % parent)
700 self.ui.write(_("parent: %d:%s\n") % parent)
694
701
695 if self.ui.debugflag:
702 if self.ui.debugflag:
696 mnode = ctx.manifestnode()
703 mnode = ctx.manifestnode()
697 self.ui.write(_("manifest: %d:%s\n") %
704 self.ui.write(_("manifest: %d:%s\n") %
698 (self.repo.manifest.rev(mnode), hex(mnode)))
705 (self.repo.manifest.rev(mnode), hex(mnode)))
699 self.ui.write(_("user: %s\n") % ctx.user())
706 self.ui.write(_("user: %s\n") % ctx.user())
700 self.ui.write(_("date: %s\n") % date)
707 self.ui.write(_("date: %s\n") % date)
701
708
702 if self.ui.debugflag:
709 if self.ui.debugflag:
703 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
710 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
704 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
711 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
705 files):
712 files):
706 if value:
713 if value:
707 self.ui.write("%-12s %s\n" % (key, " ".join(value)))
714 self.ui.write("%-12s %s\n" % (key, " ".join(value)))
708 elif ctx.files() and self.ui.verbose:
715 elif ctx.files() and self.ui.verbose:
709 self.ui.write(_("files: %s\n") % " ".join(ctx.files()))
716 self.ui.write(_("files: %s\n") % " ".join(ctx.files()))
710 if copies and self.ui.verbose:
717 if copies and self.ui.verbose:
711 copies = ['%s (%s)' % c for c in copies]
718 copies = ['%s (%s)' % c for c in copies]
712 self.ui.write(_("copies: %s\n") % ' '.join(copies))
719 self.ui.write(_("copies: %s\n") % ' '.join(copies))
713
720
714 extra = ctx.extra()
721 extra = ctx.extra()
715 if extra and self.ui.debugflag:
722 if extra and self.ui.debugflag:
716 for key, value in sorted(extra.items()):
723 for key, value in sorted(extra.items()):
717 self.ui.write(_("extra: %s=%s\n")
724 self.ui.write(_("extra: %s=%s\n")
718 % (key, value.encode('string_escape')))
725 % (key, value.encode('string_escape')))
719
726
720 description = ctx.description().strip()
727 description = ctx.description().strip()
721 if description:
728 if description:
722 if self.ui.verbose:
729 if self.ui.verbose:
723 self.ui.write(_("description:\n"))
730 self.ui.write(_("description:\n"))
724 self.ui.write(description)
731 self.ui.write(description)
725 self.ui.write("\n\n")
732 self.ui.write("\n\n")
726 else:
733 else:
727 self.ui.write(_("summary: %s\n") %
734 self.ui.write(_("summary: %s\n") %
728 description.splitlines()[0])
735 description.splitlines()[0])
729 self.ui.write("\n")
736 self.ui.write("\n")
730
737
731 self.showpatch(changenode)
738 self.showpatch(changenode)
732
739
733 def showpatch(self, node):
740 def showpatch(self, node):
734 if self.patch:
741 if self.patch:
735 prev = self.repo.changelog.parents(node)[0]
742 prev = self.repo.changelog.parents(node)[0]
736 chunks = patch.diff(self.repo, prev, node, match=self.patch,
743 chunks = patch.diff(self.repo, prev, node, match=self.patch,
737 opts=patch.diffopts(self.ui, self.diffopts))
744 opts=patch.diffopts(self.ui, self.diffopts))
738 for chunk in chunks:
745 for chunk in chunks:
739 self.ui.write(chunk)
746 self.ui.write(chunk)
740 self.ui.write("\n")
747 self.ui.write("\n")
741
748
742 def _meaningful_parentrevs(self, log, rev):
749 def _meaningful_parentrevs(self, log, rev):
743 """Return list of meaningful (or all if debug) parentrevs for rev.
750 """Return list of meaningful (or all if debug) parentrevs for rev.
744
751
745 For merges (two non-nullrev revisions) both parents are meaningful.
752 For merges (two non-nullrev revisions) both parents are meaningful.
746 Otherwise the first parent revision is considered meaningful if it
753 Otherwise the first parent revision is considered meaningful if it
747 is not the preceding revision.
754 is not the preceding revision.
748 """
755 """
749 parents = log.parentrevs(rev)
756 parents = log.parentrevs(rev)
750 if not self.ui.debugflag and parents[1] == nullrev:
757 if not self.ui.debugflag and parents[1] == nullrev:
751 if parents[0] >= rev - 1:
758 if parents[0] >= rev - 1:
752 parents = []
759 parents = []
753 else:
760 else:
754 parents = [parents[0]]
761 parents = [parents[0]]
755 return parents
762 return parents
756
763
757
764
758 class changeset_templater(changeset_printer):
765 class changeset_templater(changeset_printer):
759 '''format changeset information.'''
766 '''format changeset information.'''
760
767
761 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
768 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
762 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
769 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
763 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
770 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
764 defaulttempl = {
771 defaulttempl = {
765 'parent': '{rev}:{node|formatnode} ',
772 'parent': '{rev}:{node|formatnode} ',
766 'manifest': '{rev}:{node|formatnode}',
773 'manifest': '{rev}:{node|formatnode}',
767 'file_copy': '{name} ({source})',
774 'file_copy': '{name} ({source})',
768 'extra': '{key}={value|stringescape}'
775 'extra': '{key}={value|stringescape}'
769 }
776 }
770 # filecopy is preserved for compatibility reasons
777 # filecopy is preserved for compatibility reasons
771 defaulttempl['filecopy'] = defaulttempl['file_copy']
778 defaulttempl['filecopy'] = defaulttempl['file_copy']
772 self.t = templater.templater(mapfile, {'formatnode': formatnode},
779 self.t = templater.templater(mapfile, {'formatnode': formatnode},
773 cache=defaulttempl)
780 cache=defaulttempl)
774 self.cache = {}
781 self.cache = {}
775
782
776 def use_template(self, t):
783 def use_template(self, t):
777 '''set template string to use'''
784 '''set template string to use'''
778 self.t.cache['changeset'] = t
785 self.t.cache['changeset'] = t
779
786
780 def _meaningful_parentrevs(self, ctx):
787 def _meaningful_parentrevs(self, ctx):
781 """Return list of meaningful (or all if debug) parentrevs for rev.
788 """Return list of meaningful (or all if debug) parentrevs for rev.
782 """
789 """
783 parents = ctx.parents()
790 parents = ctx.parents()
784 if len(parents) > 1:
791 if len(parents) > 1:
785 return parents
792 return parents
786 if self.ui.debugflag:
793 if self.ui.debugflag:
787 return [parents[0], self.repo['null']]
794 return [parents[0], self.repo['null']]
788 if parents[0].rev() >= ctx.rev() - 1:
795 if parents[0].rev() >= ctx.rev() - 1:
789 return []
796 return []
790 return parents
797 return parents
791
798
792 def _show(self, ctx, copies, props):
799 def _show(self, ctx, copies, props):
793 '''show a single changeset or file revision'''
800 '''show a single changeset or file revision'''
794
801
795 showlist = templatekw.showlist
802 showlist = templatekw.showlist
796
803
797 # showparents() behaviour depends on ui trace level which
804 # showparents() behaviour depends on ui trace level which
798 # causes unexpected behaviours at templating level and makes
805 # causes unexpected behaviours at templating level and makes
799 # it harder to extract it in a standalone function. Its
806 # it harder to extract it in a standalone function. Its
800 # behaviour cannot be changed so leave it here for now.
807 # behaviour cannot be changed so leave it here for now.
801 def showparents(repo, ctx, templ, **args):
808 def showparents(repo, ctx, templ, **args):
802 parents = [[('rev', p.rev()), ('node', p.hex())]
809 parents = [[('rev', p.rev()), ('node', p.hex())]
803 for p in self._meaningful_parentrevs(ctx)]
810 for p in self._meaningful_parentrevs(ctx)]
804 return showlist(templ, 'parent', parents, **args)
811 return showlist(templ, 'parent', parents, **args)
805
812
806 props = props.copy()
813 props = props.copy()
807 props.update(templatekw.keywords)
814 props.update(templatekw.keywords)
808 props['parents'] = showparents
815 props['parents'] = showparents
809 props['templ'] = self.t
816 props['templ'] = self.t
810 props['ctx'] = ctx
817 props['ctx'] = ctx
811 props['repo'] = self.repo
818 props['repo'] = self.repo
812 props['revcache'] = {'copies': copies}
819 props['revcache'] = {'copies': copies}
813 props['cache'] = self.cache
820 props['cache'] = self.cache
814
821
815 # find correct templates for current mode
822 # find correct templates for current mode
816
823
817 tmplmodes = [
824 tmplmodes = [
818 (True, None),
825 (True, None),
819 (self.ui.verbose, 'verbose'),
826 (self.ui.verbose, 'verbose'),
820 (self.ui.quiet, 'quiet'),
827 (self.ui.quiet, 'quiet'),
821 (self.ui.debugflag, 'debug'),
828 (self.ui.debugflag, 'debug'),
822 ]
829 ]
823
830
824 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
831 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
825 for mode, postfix in tmplmodes:
832 for mode, postfix in tmplmodes:
826 for type in types:
833 for type in types:
827 cur = postfix and ('%s_%s' % (type, postfix)) or type
834 cur = postfix and ('%s_%s' % (type, postfix)) or type
828 if mode and cur in self.t:
835 if mode and cur in self.t:
829 types[type] = cur
836 types[type] = cur
830
837
831 try:
838 try:
832
839
833 # write header
840 # write header
834 if types['header']:
841 if types['header']:
835 h = templater.stringify(self.t(types['header'], **props))
842 h = templater.stringify(self.t(types['header'], **props))
836 if self.buffered:
843 if self.buffered:
837 self.header[ctx.rev()] = h
844 self.header[ctx.rev()] = h
838 else:
845 else:
839 self.ui.write(h)
846 self.ui.write(h)
840
847
841 # write changeset metadata, then patch if requested
848 # write changeset metadata, then patch if requested
842 key = types['changeset']
849 key = types['changeset']
843 self.ui.write(templater.stringify(self.t(key, **props)))
850 self.ui.write(templater.stringify(self.t(key, **props)))
844 self.showpatch(ctx.node())
851 self.showpatch(ctx.node())
845
852
846 if types['footer']:
853 if types['footer']:
847 if not self.footer:
854 if not self.footer:
848 self.footer = templater.stringify(self.t(types['footer'],
855 self.footer = templater.stringify(self.t(types['footer'],
849 **props))
856 **props))
850
857
851 except KeyError, inst:
858 except KeyError, inst:
852 msg = _("%s: no key named '%s'")
859 msg = _("%s: no key named '%s'")
853 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
860 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
854 except SyntaxError, inst:
861 except SyntaxError, inst:
855 raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
862 raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
856
863
857 def show_changeset(ui, repo, opts, buffered=False, matchfn=False):
864 def show_changeset(ui, repo, opts, buffered=False, matchfn=False):
858 """show one changeset using template or regular display.
865 """show one changeset using template or regular display.
859
866
860 Display format will be the first non-empty hit of:
867 Display format will be the first non-empty hit of:
861 1. option 'template'
868 1. option 'template'
862 2. option 'style'
869 2. option 'style'
863 3. [ui] setting 'logtemplate'
870 3. [ui] setting 'logtemplate'
864 4. [ui] setting 'style'
871 4. [ui] setting 'style'
865 If all of these values are either the unset or the empty string,
872 If all of these values are either the unset or the empty string,
866 regular display via changeset_printer() is done.
873 regular display via changeset_printer() is done.
867 """
874 """
868 # options
875 # options
869 patch = False
876 patch = False
870 if opts.get('patch'):
877 if opts.get('patch'):
871 patch = matchfn or matchall(repo)
878 patch = matchfn or matchall(repo)
872
879
873 tmpl = opts.get('template')
880 tmpl = opts.get('template')
874 style = None
881 style = None
875 if tmpl:
882 if tmpl:
876 tmpl = templater.parsestring(tmpl, quoted=False)
883 tmpl = templater.parsestring(tmpl, quoted=False)
877 else:
884 else:
878 style = opts.get('style')
885 style = opts.get('style')
879
886
880 # ui settings
887 # ui settings
881 if not (tmpl or style):
888 if not (tmpl or style):
882 tmpl = ui.config('ui', 'logtemplate')
889 tmpl = ui.config('ui', 'logtemplate')
883 if tmpl:
890 if tmpl:
884 tmpl = templater.parsestring(tmpl)
891 tmpl = templater.parsestring(tmpl)
885 else:
892 else:
886 style = ui.config('ui', 'style')
893 style = ui.config('ui', 'style')
887
894
888 if not (tmpl or style):
895 if not (tmpl or style):
889 return changeset_printer(ui, repo, patch, opts, buffered)
896 return changeset_printer(ui, repo, patch, opts, buffered)
890
897
891 mapfile = None
898 mapfile = None
892 if style and not tmpl:
899 if style and not tmpl:
893 mapfile = style
900 mapfile = style
894 if not os.path.split(mapfile)[0]:
901 if not os.path.split(mapfile)[0]:
895 mapname = (templater.templatepath('map-cmdline.' + mapfile)
902 mapname = (templater.templatepath('map-cmdline.' + mapfile)
896 or templater.templatepath(mapfile))
903 or templater.templatepath(mapfile))
897 if mapname: mapfile = mapname
904 if mapname: mapfile = mapname
898
905
899 try:
906 try:
900 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
907 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
901 except SyntaxError, inst:
908 except SyntaxError, inst:
902 raise util.Abort(inst.args[0])
909 raise util.Abort(inst.args[0])
903 if tmpl: t.use_template(tmpl)
910 if tmpl: t.use_template(tmpl)
904 return t
911 return t
905
912
906 def finddate(ui, repo, date):
913 def finddate(ui, repo, date):
907 """Find the tipmost changeset that matches the given date spec"""
914 """Find the tipmost changeset that matches the given date spec"""
908
915
909 df = util.matchdate(date)
916 df = util.matchdate(date)
910 m = matchall(repo)
917 m = matchall(repo)
911 results = {}
918 results = {}
912
919
913 def prep(ctx, fns):
920 def prep(ctx, fns):
914 d = ctx.date()
921 d = ctx.date()
915 if df(d[0]):
922 if df(d[0]):
916 results[ctx.rev()] = d
923 results[ctx.rev()] = d
917
924
918 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
925 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
919 rev = ctx.rev()
926 rev = ctx.rev()
920 if rev in results:
927 if rev in results:
921 ui.status(_("Found revision %s from %s\n") %
928 ui.status(_("Found revision %s from %s\n") %
922 (rev, util.datestr(results[rev])))
929 (rev, util.datestr(results[rev])))
923 return str(rev)
930 return str(rev)
924
931
925 raise util.Abort(_("revision matching date not found"))
932 raise util.Abort(_("revision matching date not found"))
926
933
927 def walkchangerevs(repo, match, opts, prepare):
934 def walkchangerevs(repo, match, opts, prepare):
928 '''Iterate over files and the revs in which they changed.
935 '''Iterate over files and the revs in which they changed.
929
936
930 Callers most commonly need to iterate backwards over the history
937 Callers most commonly need to iterate backwards over the history
931 in which they are interested. Doing so has awful (quadratic-looking)
938 in which they are interested. Doing so has awful (quadratic-looking)
932 performance, so we use iterators in a "windowed" way.
939 performance, so we use iterators in a "windowed" way.
933
940
934 We walk a window of revisions in the desired order. Within the
941 We walk a window of revisions in the desired order. Within the
935 window, we first walk forwards to gather data, then in the desired
942 window, we first walk forwards to gather data, then in the desired
936 order (usually backwards) to display it.
943 order (usually backwards) to display it.
937
944
938 This function returns an iterator yielding contexts. Before
945 This function returns an iterator yielding contexts. Before
939 yielding each context, the iterator will first call the prepare
946 yielding each context, the iterator will first call the prepare
940 function on each context in the window in forward order.'''
947 function on each context in the window in forward order.'''
941
948
942 def increasing_windows(start, end, windowsize=8, sizelimit=512):
949 def increasing_windows(start, end, windowsize=8, sizelimit=512):
943 if start < end:
950 if start < end:
944 while start < end:
951 while start < end:
945 yield start, min(windowsize, end-start)
952 yield start, min(windowsize, end-start)
946 start += windowsize
953 start += windowsize
947 if windowsize < sizelimit:
954 if windowsize < sizelimit:
948 windowsize *= 2
955 windowsize *= 2
949 else:
956 else:
950 while start > end:
957 while start > end:
951 yield start, min(windowsize, start-end-1)
958 yield start, min(windowsize, start-end-1)
952 start -= windowsize
959 start -= windowsize
953 if windowsize < sizelimit:
960 if windowsize < sizelimit:
954 windowsize *= 2
961 windowsize *= 2
955
962
956 follow = opts.get('follow') or opts.get('follow_first')
963 follow = opts.get('follow') or opts.get('follow_first')
957
964
958 if not len(repo):
965 if not len(repo):
959 return []
966 return []
960
967
961 if follow:
968 if follow:
962 defrange = '%s:0' % repo['.'].rev()
969 defrange = '%s:0' % repo['.'].rev()
963 else:
970 else:
964 defrange = '-1:0'
971 defrange = '-1:0'
965 revs = revrange(repo, opts['rev'] or [defrange])
972 revs = revrange(repo, opts['rev'] or [defrange])
966 wanted = set()
973 wanted = set()
967 slowpath = match.anypats() or (match.files() and opts.get('removed'))
974 slowpath = match.anypats() or (match.files() and opts.get('removed'))
968 fncache = {}
975 fncache = {}
969 change = util.cachefunc(repo.changectx)
976 change = util.cachefunc(repo.changectx)
970
977
971 if not slowpath and not match.files():
978 if not slowpath and not match.files():
972 # No files, no patterns. Display all revs.
979 # No files, no patterns. Display all revs.
973 wanted = set(revs)
980 wanted = set(revs)
974 copies = []
981 copies = []
975
982
976 if not slowpath:
983 if not slowpath:
977 # Only files, no patterns. Check the history of each file.
984 # Only files, no patterns. Check the history of each file.
978 def filerevgen(filelog, node):
985 def filerevgen(filelog, node):
979 cl_count = len(repo)
986 cl_count = len(repo)
980 if node is None:
987 if node is None:
981 last = len(filelog) - 1
988 last = len(filelog) - 1
982 else:
989 else:
983 last = filelog.rev(node)
990 last = filelog.rev(node)
984 for i, window in increasing_windows(last, nullrev):
991 for i, window in increasing_windows(last, nullrev):
985 revs = []
992 revs = []
986 for j in xrange(i - window, i + 1):
993 for j in xrange(i - window, i + 1):
987 n = filelog.node(j)
994 n = filelog.node(j)
988 revs.append((filelog.linkrev(j),
995 revs.append((filelog.linkrev(j),
989 follow and filelog.renamed(n)))
996 follow and filelog.renamed(n)))
990 for rev in reversed(revs):
997 for rev in reversed(revs):
991 # only yield rev for which we have the changelog, it can
998 # only yield rev for which we have the changelog, it can
992 # happen while doing "hg log" during a pull or commit
999 # happen while doing "hg log" during a pull or commit
993 if rev[0] < cl_count:
1000 if rev[0] < cl_count:
994 yield rev
1001 yield rev
995 def iterfiles():
1002 def iterfiles():
996 for filename in match.files():
1003 for filename in match.files():
997 yield filename, None
1004 yield filename, None
998 for filename_node in copies:
1005 for filename_node in copies:
999 yield filename_node
1006 yield filename_node
1000 minrev, maxrev = min(revs), max(revs)
1007 minrev, maxrev = min(revs), max(revs)
1001 for file_, node in iterfiles():
1008 for file_, node in iterfiles():
1002 filelog = repo.file(file_)
1009 filelog = repo.file(file_)
1003 if not len(filelog):
1010 if not len(filelog):
1004 if node is None:
1011 if node is None:
1005 # A zero count may be a directory or deleted file, so
1012 # A zero count may be a directory or deleted file, so
1006 # try to find matching entries on the slow path.
1013 # try to find matching entries on the slow path.
1007 if follow:
1014 if follow:
1008 raise util.Abort(_('cannot follow nonexistent file: "%s"') % file_)
1015 raise util.Abort(_('cannot follow nonexistent file: "%s"') % file_)
1009 slowpath = True
1016 slowpath = True
1010 break
1017 break
1011 else:
1018 else:
1012 continue
1019 continue
1013 for rev, copied in filerevgen(filelog, node):
1020 for rev, copied in filerevgen(filelog, node):
1014 if rev <= maxrev:
1021 if rev <= maxrev:
1015 if rev < minrev:
1022 if rev < minrev:
1016 break
1023 break
1017 fncache.setdefault(rev, [])
1024 fncache.setdefault(rev, [])
1018 fncache[rev].append(file_)
1025 fncache[rev].append(file_)
1019 wanted.add(rev)
1026 wanted.add(rev)
1020 if follow and copied:
1027 if follow and copied:
1021 copies.append(copied)
1028 copies.append(copied)
1022 if slowpath:
1029 if slowpath:
1023 if follow:
1030 if follow:
1024 raise util.Abort(_('can only follow copies/renames for explicit '
1031 raise util.Abort(_('can only follow copies/renames for explicit '
1025 'filenames'))
1032 'filenames'))
1026
1033
1027 # The slow path checks files modified in every changeset.
1034 # The slow path checks files modified in every changeset.
1028 def changerevgen():
1035 def changerevgen():
1029 for i, window in increasing_windows(len(repo) - 1, nullrev):
1036 for i, window in increasing_windows(len(repo) - 1, nullrev):
1030 for j in xrange(i - window, i + 1):
1037 for j in xrange(i - window, i + 1):
1031 yield change(j)
1038 yield change(j)
1032
1039
1033 for ctx in changerevgen():
1040 for ctx in changerevgen():
1034 matches = filter(match, ctx.files())
1041 matches = filter(match, ctx.files())
1035 if matches:
1042 if matches:
1036 fncache[ctx.rev()] = matches
1043 fncache[ctx.rev()] = matches
1037 wanted.add(ctx.rev())
1044 wanted.add(ctx.rev())
1038
1045
1039 class followfilter(object):
1046 class followfilter(object):
1040 def __init__(self, onlyfirst=False):
1047 def __init__(self, onlyfirst=False):
1041 self.startrev = nullrev
1048 self.startrev = nullrev
1042 self.roots = set()
1049 self.roots = set()
1043 self.onlyfirst = onlyfirst
1050 self.onlyfirst = onlyfirst
1044
1051
1045 def match(self, rev):
1052 def match(self, rev):
1046 def realparents(rev):
1053 def realparents(rev):
1047 if self.onlyfirst:
1054 if self.onlyfirst:
1048 return repo.changelog.parentrevs(rev)[0:1]
1055 return repo.changelog.parentrevs(rev)[0:1]
1049 else:
1056 else:
1050 return filter(lambda x: x != nullrev,
1057 return filter(lambda x: x != nullrev,
1051 repo.changelog.parentrevs(rev))
1058 repo.changelog.parentrevs(rev))
1052
1059
1053 if self.startrev == nullrev:
1060 if self.startrev == nullrev:
1054 self.startrev = rev
1061 self.startrev = rev
1055 return True
1062 return True
1056
1063
1057 if rev > self.startrev:
1064 if rev > self.startrev:
1058 # forward: all descendants
1065 # forward: all descendants
1059 if not self.roots:
1066 if not self.roots:
1060 self.roots.add(self.startrev)
1067 self.roots.add(self.startrev)
1061 for parent in realparents(rev):
1068 for parent in realparents(rev):
1062 if parent in self.roots:
1069 if parent in self.roots:
1063 self.roots.add(rev)
1070 self.roots.add(rev)
1064 return True
1071 return True
1065 else:
1072 else:
1066 # backwards: all parents
1073 # backwards: all parents
1067 if not self.roots:
1074 if not self.roots:
1068 self.roots.update(realparents(self.startrev))
1075 self.roots.update(realparents(self.startrev))
1069 if rev in self.roots:
1076 if rev in self.roots:
1070 self.roots.remove(rev)
1077 self.roots.remove(rev)
1071 self.roots.update(realparents(rev))
1078 self.roots.update(realparents(rev))
1072 return True
1079 return True
1073
1080
1074 return False
1081 return False
1075
1082
1076 # it might be worthwhile to do this in the iterator if the rev range
1083 # it might be worthwhile to do this in the iterator if the rev range
1077 # is descending and the prune args are all within that range
1084 # is descending and the prune args are all within that range
1078 for rev in opts.get('prune', ()):
1085 for rev in opts.get('prune', ()):
1079 rev = repo.changelog.rev(repo.lookup(rev))
1086 rev = repo.changelog.rev(repo.lookup(rev))
1080 ff = followfilter()
1087 ff = followfilter()
1081 stop = min(revs[0], revs[-1])
1088 stop = min(revs[0], revs[-1])
1082 for x in xrange(rev, stop-1, -1):
1089 for x in xrange(rev, stop-1, -1):
1083 if ff.match(x):
1090 if ff.match(x):
1084 wanted.discard(x)
1091 wanted.discard(x)
1085
1092
1086 def iterate():
1093 def iterate():
1087 if follow and not match.files():
1094 if follow and not match.files():
1088 ff = followfilter(onlyfirst=opts.get('follow_first'))
1095 ff = followfilter(onlyfirst=opts.get('follow_first'))
1089 def want(rev):
1096 def want(rev):
1090 return ff.match(rev) and rev in wanted
1097 return ff.match(rev) and rev in wanted
1091 else:
1098 else:
1092 def want(rev):
1099 def want(rev):
1093 return rev in wanted
1100 return rev in wanted
1094
1101
1095 for i, window in increasing_windows(0, len(revs)):
1102 for i, window in increasing_windows(0, len(revs)):
1096 change = util.cachefunc(repo.changectx)
1103 change = util.cachefunc(repo.changectx)
1097 nrevs = [rev for rev in revs[i:i+window] if want(rev)]
1104 nrevs = [rev for rev in revs[i:i+window] if want(rev)]
1098 for rev in sorted(nrevs):
1105 for rev in sorted(nrevs):
1099 fns = fncache.get(rev)
1106 fns = fncache.get(rev)
1100 ctx = change(rev)
1107 ctx = change(rev)
1101 if not fns:
1108 if not fns:
1102 def fns_generator():
1109 def fns_generator():
1103 for f in ctx.files():
1110 for f in ctx.files():
1104 if match(f):
1111 if match(f):
1105 yield f
1112 yield f
1106 fns = fns_generator()
1113 fns = fns_generator()
1107 prepare(ctx, fns)
1114 prepare(ctx, fns)
1108 for rev in nrevs:
1115 for rev in nrevs:
1109 yield change(rev)
1116 yield change(rev)
1110 return iterate()
1117 return iterate()
1111
1118
1112 def commit(ui, repo, commitfunc, pats, opts):
1119 def commit(ui, repo, commitfunc, pats, opts):
1113 '''commit the specified files or all outstanding changes'''
1120 '''commit the specified files or all outstanding changes'''
1114 date = opts.get('date')
1121 date = opts.get('date')
1115 if date:
1122 if date:
1116 opts['date'] = util.parsedate(date)
1123 opts['date'] = util.parsedate(date)
1117 message = logmessage(opts)
1124 message = logmessage(opts)
1118
1125
1119 # extract addremove carefully -- this function can be called from a command
1126 # extract addremove carefully -- this function can be called from a command
1120 # that doesn't support addremove
1127 # that doesn't support addremove
1121 if opts.get('addremove'):
1128 if opts.get('addremove'):
1122 addremove(repo, pats, opts)
1129 addremove(repo, pats, opts)
1123
1130
1124 return commitfunc(ui, repo, message, match(repo, pats, opts), opts)
1131 return commitfunc(ui, repo, message, match(repo, pats, opts), opts)
1125
1132
1126 def commiteditor(repo, ctx, subs):
1133 def commiteditor(repo, ctx, subs):
1127 if ctx.description():
1134 if ctx.description():
1128 return ctx.description()
1135 return ctx.description()
1129 return commitforceeditor(repo, ctx, subs)
1136 return commitforceeditor(repo, ctx, subs)
1130
1137
1131 def commitforceeditor(repo, ctx, subs):
1138 def commitforceeditor(repo, ctx, subs):
1132 edittext = []
1139 edittext = []
1133 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1140 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1134 if ctx.description():
1141 if ctx.description():
1135 edittext.append(ctx.description())
1142 edittext.append(ctx.description())
1136 edittext.append("")
1143 edittext.append("")
1137 edittext.append("") # Empty line between message and comments.
1144 edittext.append("") # Empty line between message and comments.
1138 edittext.append(_("HG: Enter commit message."
1145 edittext.append(_("HG: Enter commit message."
1139 " Lines beginning with 'HG:' are removed."))
1146 " Lines beginning with 'HG:' are removed."))
1140 edittext.append(_("HG: Leave message empty to abort commit."))
1147 edittext.append(_("HG: Leave message empty to abort commit."))
1141 edittext.append("HG: --")
1148 edittext.append("HG: --")
1142 edittext.append(_("HG: user: %s") % ctx.user())
1149 edittext.append(_("HG: user: %s") % ctx.user())
1143 if ctx.p2():
1150 if ctx.p2():
1144 edittext.append(_("HG: branch merge"))
1151 edittext.append(_("HG: branch merge"))
1145 if ctx.branch():
1152 if ctx.branch():
1146 edittext.append(_("HG: branch '%s'")
1153 edittext.append(_("HG: branch '%s'")
1147 % encoding.tolocal(ctx.branch()))
1154 % encoding.tolocal(ctx.branch()))
1148 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1155 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1149 edittext.extend([_("HG: added %s") % f for f in added])
1156 edittext.extend([_("HG: added %s") % f for f in added])
1150 edittext.extend([_("HG: changed %s") % f for f in modified])
1157 edittext.extend([_("HG: changed %s") % f for f in modified])
1151 edittext.extend([_("HG: removed %s") % f for f in removed])
1158 edittext.extend([_("HG: removed %s") % f for f in removed])
1152 if not added and not modified and not removed:
1159 if not added and not modified and not removed:
1153 edittext.append(_("HG: no files changed"))
1160 edittext.append(_("HG: no files changed"))
1154 edittext.append("")
1161 edittext.append("")
1155 # run editor in the repository root
1162 # run editor in the repository root
1156 olddir = os.getcwd()
1163 olddir = os.getcwd()
1157 os.chdir(repo.root)
1164 os.chdir(repo.root)
1158 text = repo.ui.edit("\n".join(edittext), ctx.user())
1165 text = repo.ui.edit("\n".join(edittext), ctx.user())
1159 text = re.sub("(?m)^HG:.*\n", "", text)
1166 text = re.sub("(?m)^HG:.*\n", "", text)
1160 os.chdir(olddir)
1167 os.chdir(olddir)
1161
1168
1162 if not text.strip():
1169 if not text.strip():
1163 raise util.Abort(_("empty commit message"))
1170 raise util.Abort(_("empty commit message"))
1164
1171
1165 return text
1172 return text
General Comments 0
You need to be logged in to leave comments. Login now