##// END OF EJS Templates
cmdutil: introduce "getcommiteditor()" to simplify code paths to choose editor...
FUJIWARA Katsunori -
r21405:dcf20f24 default
parent child Browse files
Show More
@@ -1,2476 +1,2483 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 or any later version.
6 # GNU General Public License version 2 or any later version.
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, tempfile
10 import os, sys, errno, re, tempfile
11 import util, scmutil, templater, patch, error, templatekw, revlog, copies
11 import util, scmutil, templater, patch, error, templatekw, revlog, copies
12 import match as matchmod
12 import match as matchmod
13 import context, repair, graphmod, revset, phases, obsolete, pathutil
13 import context, repair, graphmod, revset, phases, obsolete, pathutil
14 import changelog
14 import changelog
15 import bookmarks
15 import bookmarks
16 import lock as lockmod
16 import lock as lockmod
17
17
18 def parsealiases(cmd):
18 def parsealiases(cmd):
19 return cmd.lstrip("^").split("|")
19 return cmd.lstrip("^").split("|")
20
20
21 def findpossible(cmd, table, strict=False):
21 def findpossible(cmd, table, strict=False):
22 """
22 """
23 Return cmd -> (aliases, command table entry)
23 Return cmd -> (aliases, command table entry)
24 for each matching command.
24 for each matching command.
25 Return debug commands (or their aliases) only if no normal command matches.
25 Return debug commands (or their aliases) only if no normal command matches.
26 """
26 """
27 choice = {}
27 choice = {}
28 debugchoice = {}
28 debugchoice = {}
29
29
30 if cmd in table:
30 if cmd in table:
31 # short-circuit exact matches, "log" alias beats "^log|history"
31 # short-circuit exact matches, "log" alias beats "^log|history"
32 keys = [cmd]
32 keys = [cmd]
33 else:
33 else:
34 keys = table.keys()
34 keys = table.keys()
35
35
36 for e in keys:
36 for e in keys:
37 aliases = parsealiases(e)
37 aliases = parsealiases(e)
38 found = None
38 found = None
39 if cmd in aliases:
39 if cmd in aliases:
40 found = cmd
40 found = cmd
41 elif not strict:
41 elif not strict:
42 for a in aliases:
42 for a in aliases:
43 if a.startswith(cmd):
43 if a.startswith(cmd):
44 found = a
44 found = a
45 break
45 break
46 if found is not None:
46 if found is not None:
47 if aliases[0].startswith("debug") or found.startswith("debug"):
47 if aliases[0].startswith("debug") or found.startswith("debug"):
48 debugchoice[found] = (aliases, table[e])
48 debugchoice[found] = (aliases, table[e])
49 else:
49 else:
50 choice[found] = (aliases, table[e])
50 choice[found] = (aliases, table[e])
51
51
52 if not choice and debugchoice:
52 if not choice and debugchoice:
53 choice = debugchoice
53 choice = debugchoice
54
54
55 return choice
55 return choice
56
56
57 def findcmd(cmd, table, strict=True):
57 def findcmd(cmd, table, strict=True):
58 """Return (aliases, command table entry) for command string."""
58 """Return (aliases, command table entry) for command string."""
59 choice = findpossible(cmd, table, strict)
59 choice = findpossible(cmd, table, strict)
60
60
61 if cmd in choice:
61 if cmd in choice:
62 return choice[cmd]
62 return choice[cmd]
63
63
64 if len(choice) > 1:
64 if len(choice) > 1:
65 clist = choice.keys()
65 clist = choice.keys()
66 clist.sort()
66 clist.sort()
67 raise error.AmbiguousCommand(cmd, clist)
67 raise error.AmbiguousCommand(cmd, clist)
68
68
69 if choice:
69 if choice:
70 return choice.values()[0]
70 return choice.values()[0]
71
71
72 raise error.UnknownCommand(cmd)
72 raise error.UnknownCommand(cmd)
73
73
74 def findrepo(p):
74 def findrepo(p):
75 while not os.path.isdir(os.path.join(p, ".hg")):
75 while not os.path.isdir(os.path.join(p, ".hg")):
76 oldp, p = p, os.path.dirname(p)
76 oldp, p = p, os.path.dirname(p)
77 if p == oldp:
77 if p == oldp:
78 return None
78 return None
79
79
80 return p
80 return p
81
81
82 def bailifchanged(repo):
82 def bailifchanged(repo):
83 if repo.dirstate.p2() != nullid:
83 if repo.dirstate.p2() != nullid:
84 raise util.Abort(_('outstanding uncommitted merge'))
84 raise util.Abort(_('outstanding uncommitted merge'))
85 modified, added, removed, deleted = repo.status()[:4]
85 modified, added, removed, deleted = repo.status()[:4]
86 if modified or added or removed or deleted:
86 if modified or added or removed or deleted:
87 raise util.Abort(_('uncommitted changes'))
87 raise util.Abort(_('uncommitted changes'))
88 ctx = repo[None]
88 ctx = repo[None]
89 for s in sorted(ctx.substate):
89 for s in sorted(ctx.substate):
90 if ctx.sub(s).dirty():
90 if ctx.sub(s).dirty():
91 raise util.Abort(_("uncommitted changes in subrepo %s") % s)
91 raise util.Abort(_("uncommitted changes in subrepo %s") % s)
92
92
93 def logmessage(ui, opts):
93 def logmessage(ui, opts):
94 """ get the log message according to -m and -l option """
94 """ get the log message according to -m and -l option """
95 message = opts.get('message')
95 message = opts.get('message')
96 logfile = opts.get('logfile')
96 logfile = opts.get('logfile')
97
97
98 if message and logfile:
98 if message and logfile:
99 raise util.Abort(_('options --message and --logfile are mutually '
99 raise util.Abort(_('options --message and --logfile are mutually '
100 'exclusive'))
100 'exclusive'))
101 if not message and logfile:
101 if not message and logfile:
102 try:
102 try:
103 if logfile == '-':
103 if logfile == '-':
104 message = ui.fin.read()
104 message = ui.fin.read()
105 else:
105 else:
106 message = '\n'.join(util.readfile(logfile).splitlines())
106 message = '\n'.join(util.readfile(logfile).splitlines())
107 except IOError, inst:
107 except IOError, inst:
108 raise util.Abort(_("can't read commit message '%s': %s") %
108 raise util.Abort(_("can't read commit message '%s': %s") %
109 (logfile, inst.strerror))
109 (logfile, inst.strerror))
110 return message
110 return message
111
111
112 def getcommiteditor(edit=False, **opts):
113 """get appropriate commit message editor according to '--edit' option"""
114 if edit:
115 return commitforceeditor
116 else:
117 return commiteditor
118
112 def loglimit(opts):
119 def loglimit(opts):
113 """get the log limit according to option -l/--limit"""
120 """get the log limit according to option -l/--limit"""
114 limit = opts.get('limit')
121 limit = opts.get('limit')
115 if limit:
122 if limit:
116 try:
123 try:
117 limit = int(limit)
124 limit = int(limit)
118 except ValueError:
125 except ValueError:
119 raise util.Abort(_('limit must be a positive integer'))
126 raise util.Abort(_('limit must be a positive integer'))
120 if limit <= 0:
127 if limit <= 0:
121 raise util.Abort(_('limit must be positive'))
128 raise util.Abort(_('limit must be positive'))
122 else:
129 else:
123 limit = None
130 limit = None
124 return limit
131 return limit
125
132
126 def makefilename(repo, pat, node, desc=None,
133 def makefilename(repo, pat, node, desc=None,
127 total=None, seqno=None, revwidth=None, pathname=None):
134 total=None, seqno=None, revwidth=None, pathname=None):
128 node_expander = {
135 node_expander = {
129 'H': lambda: hex(node),
136 'H': lambda: hex(node),
130 'R': lambda: str(repo.changelog.rev(node)),
137 'R': lambda: str(repo.changelog.rev(node)),
131 'h': lambda: short(node),
138 'h': lambda: short(node),
132 'm': lambda: re.sub('[^\w]', '_', str(desc))
139 'm': lambda: re.sub('[^\w]', '_', str(desc))
133 }
140 }
134 expander = {
141 expander = {
135 '%': lambda: '%',
142 '%': lambda: '%',
136 'b': lambda: os.path.basename(repo.root),
143 'b': lambda: os.path.basename(repo.root),
137 }
144 }
138
145
139 try:
146 try:
140 if node:
147 if node:
141 expander.update(node_expander)
148 expander.update(node_expander)
142 if node:
149 if node:
143 expander['r'] = (lambda:
150 expander['r'] = (lambda:
144 str(repo.changelog.rev(node)).zfill(revwidth or 0))
151 str(repo.changelog.rev(node)).zfill(revwidth or 0))
145 if total is not None:
152 if total is not None:
146 expander['N'] = lambda: str(total)
153 expander['N'] = lambda: str(total)
147 if seqno is not None:
154 if seqno is not None:
148 expander['n'] = lambda: str(seqno)
155 expander['n'] = lambda: str(seqno)
149 if total is not None and seqno is not None:
156 if total is not None and seqno is not None:
150 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
157 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
151 if pathname is not None:
158 if pathname is not None:
152 expander['s'] = lambda: os.path.basename(pathname)
159 expander['s'] = lambda: os.path.basename(pathname)
153 expander['d'] = lambda: os.path.dirname(pathname) or '.'
160 expander['d'] = lambda: os.path.dirname(pathname) or '.'
154 expander['p'] = lambda: pathname
161 expander['p'] = lambda: pathname
155
162
156 newname = []
163 newname = []
157 patlen = len(pat)
164 patlen = len(pat)
158 i = 0
165 i = 0
159 while i < patlen:
166 while i < patlen:
160 c = pat[i]
167 c = pat[i]
161 if c == '%':
168 if c == '%':
162 i += 1
169 i += 1
163 c = pat[i]
170 c = pat[i]
164 c = expander[c]()
171 c = expander[c]()
165 newname.append(c)
172 newname.append(c)
166 i += 1
173 i += 1
167 return ''.join(newname)
174 return ''.join(newname)
168 except KeyError, inst:
175 except KeyError, inst:
169 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
176 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
170 inst.args[0])
177 inst.args[0])
171
178
172 def makefileobj(repo, pat, node=None, desc=None, total=None,
179 def makefileobj(repo, pat, node=None, desc=None, total=None,
173 seqno=None, revwidth=None, mode='wb', modemap=None,
180 seqno=None, revwidth=None, mode='wb', modemap=None,
174 pathname=None):
181 pathname=None):
175
182
176 writable = mode not in ('r', 'rb')
183 writable = mode not in ('r', 'rb')
177
184
178 if not pat or pat == '-':
185 if not pat or pat == '-':
179 fp = writable and repo.ui.fout or repo.ui.fin
186 fp = writable and repo.ui.fout or repo.ui.fin
180 if util.safehasattr(fp, 'fileno'):
187 if util.safehasattr(fp, 'fileno'):
181 return os.fdopen(os.dup(fp.fileno()), mode)
188 return os.fdopen(os.dup(fp.fileno()), mode)
182 else:
189 else:
183 # if this fp can't be duped properly, return
190 # if this fp can't be duped properly, return
184 # a dummy object that can be closed
191 # a dummy object that can be closed
185 class wrappedfileobj(object):
192 class wrappedfileobj(object):
186 noop = lambda x: None
193 noop = lambda x: None
187 def __init__(self, f):
194 def __init__(self, f):
188 self.f = f
195 self.f = f
189 def __getattr__(self, attr):
196 def __getattr__(self, attr):
190 if attr == 'close':
197 if attr == 'close':
191 return self.noop
198 return self.noop
192 else:
199 else:
193 return getattr(self.f, attr)
200 return getattr(self.f, attr)
194
201
195 return wrappedfileobj(fp)
202 return wrappedfileobj(fp)
196 if util.safehasattr(pat, 'write') and writable:
203 if util.safehasattr(pat, 'write') and writable:
197 return pat
204 return pat
198 if util.safehasattr(pat, 'read') and 'r' in mode:
205 if util.safehasattr(pat, 'read') and 'r' in mode:
199 return pat
206 return pat
200 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
207 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
201 if modemap is not None:
208 if modemap is not None:
202 mode = modemap.get(fn, mode)
209 mode = modemap.get(fn, mode)
203 if mode == 'wb':
210 if mode == 'wb':
204 modemap[fn] = 'ab'
211 modemap[fn] = 'ab'
205 return open(fn, mode)
212 return open(fn, mode)
206
213
207 def openrevlog(repo, cmd, file_, opts):
214 def openrevlog(repo, cmd, file_, opts):
208 """opens the changelog, manifest, a filelog or a given revlog"""
215 """opens the changelog, manifest, a filelog or a given revlog"""
209 cl = opts['changelog']
216 cl = opts['changelog']
210 mf = opts['manifest']
217 mf = opts['manifest']
211 msg = None
218 msg = None
212 if cl and mf:
219 if cl and mf:
213 msg = _('cannot specify --changelog and --manifest at the same time')
220 msg = _('cannot specify --changelog and --manifest at the same time')
214 elif cl or mf:
221 elif cl or mf:
215 if file_:
222 if file_:
216 msg = _('cannot specify filename with --changelog or --manifest')
223 msg = _('cannot specify filename with --changelog or --manifest')
217 elif not repo:
224 elif not repo:
218 msg = _('cannot specify --changelog or --manifest '
225 msg = _('cannot specify --changelog or --manifest '
219 'without a repository')
226 'without a repository')
220 if msg:
227 if msg:
221 raise util.Abort(msg)
228 raise util.Abort(msg)
222
229
223 r = None
230 r = None
224 if repo:
231 if repo:
225 if cl:
232 if cl:
226 r = repo.unfiltered().changelog
233 r = repo.unfiltered().changelog
227 elif mf:
234 elif mf:
228 r = repo.manifest
235 r = repo.manifest
229 elif file_:
236 elif file_:
230 filelog = repo.file(file_)
237 filelog = repo.file(file_)
231 if len(filelog):
238 if len(filelog):
232 r = filelog
239 r = filelog
233 if not r:
240 if not r:
234 if not file_:
241 if not file_:
235 raise error.CommandError(cmd, _('invalid arguments'))
242 raise error.CommandError(cmd, _('invalid arguments'))
236 if not os.path.isfile(file_):
243 if not os.path.isfile(file_):
237 raise util.Abort(_("revlog '%s' not found") % file_)
244 raise util.Abort(_("revlog '%s' not found") % file_)
238 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
245 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
239 file_[:-2] + ".i")
246 file_[:-2] + ".i")
240 return r
247 return r
241
248
242 def copy(ui, repo, pats, opts, rename=False):
249 def copy(ui, repo, pats, opts, rename=False):
243 # called with the repo lock held
250 # called with the repo lock held
244 #
251 #
245 # hgsep => pathname that uses "/" to separate directories
252 # hgsep => pathname that uses "/" to separate directories
246 # ossep => pathname that uses os.sep to separate directories
253 # ossep => pathname that uses os.sep to separate directories
247 cwd = repo.getcwd()
254 cwd = repo.getcwd()
248 targets = {}
255 targets = {}
249 after = opts.get("after")
256 after = opts.get("after")
250 dryrun = opts.get("dry_run")
257 dryrun = opts.get("dry_run")
251 wctx = repo[None]
258 wctx = repo[None]
252
259
253 def walkpat(pat):
260 def walkpat(pat):
254 srcs = []
261 srcs = []
255 badstates = after and '?' or '?r'
262 badstates = after and '?' or '?r'
256 m = scmutil.match(repo[None], [pat], opts, globbed=True)
263 m = scmutil.match(repo[None], [pat], opts, globbed=True)
257 for abs in repo.walk(m):
264 for abs in repo.walk(m):
258 state = repo.dirstate[abs]
265 state = repo.dirstate[abs]
259 rel = m.rel(abs)
266 rel = m.rel(abs)
260 exact = m.exact(abs)
267 exact = m.exact(abs)
261 if state in badstates:
268 if state in badstates:
262 if exact and state == '?':
269 if exact and state == '?':
263 ui.warn(_('%s: not copying - file is not managed\n') % rel)
270 ui.warn(_('%s: not copying - file is not managed\n') % rel)
264 if exact and state == 'r':
271 if exact and state == 'r':
265 ui.warn(_('%s: not copying - file has been marked for'
272 ui.warn(_('%s: not copying - file has been marked for'
266 ' remove\n') % rel)
273 ' remove\n') % rel)
267 continue
274 continue
268 # abs: hgsep
275 # abs: hgsep
269 # rel: ossep
276 # rel: ossep
270 srcs.append((abs, rel, exact))
277 srcs.append((abs, rel, exact))
271 return srcs
278 return srcs
272
279
273 # abssrc: hgsep
280 # abssrc: hgsep
274 # relsrc: ossep
281 # relsrc: ossep
275 # otarget: ossep
282 # otarget: ossep
276 def copyfile(abssrc, relsrc, otarget, exact):
283 def copyfile(abssrc, relsrc, otarget, exact):
277 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
284 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
278 if '/' in abstarget:
285 if '/' in abstarget:
279 # We cannot normalize abstarget itself, this would prevent
286 # We cannot normalize abstarget itself, this would prevent
280 # case only renames, like a => A.
287 # case only renames, like a => A.
281 abspath, absname = abstarget.rsplit('/', 1)
288 abspath, absname = abstarget.rsplit('/', 1)
282 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
289 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
283 reltarget = repo.pathto(abstarget, cwd)
290 reltarget = repo.pathto(abstarget, cwd)
284 target = repo.wjoin(abstarget)
291 target = repo.wjoin(abstarget)
285 src = repo.wjoin(abssrc)
292 src = repo.wjoin(abssrc)
286 state = repo.dirstate[abstarget]
293 state = repo.dirstate[abstarget]
287
294
288 scmutil.checkportable(ui, abstarget)
295 scmutil.checkportable(ui, abstarget)
289
296
290 # check for collisions
297 # check for collisions
291 prevsrc = targets.get(abstarget)
298 prevsrc = targets.get(abstarget)
292 if prevsrc is not None:
299 if prevsrc is not None:
293 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
300 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
294 (reltarget, repo.pathto(abssrc, cwd),
301 (reltarget, repo.pathto(abssrc, cwd),
295 repo.pathto(prevsrc, cwd)))
302 repo.pathto(prevsrc, cwd)))
296 return
303 return
297
304
298 # check for overwrites
305 # check for overwrites
299 exists = os.path.lexists(target)
306 exists = os.path.lexists(target)
300 samefile = False
307 samefile = False
301 if exists and abssrc != abstarget:
308 if exists and abssrc != abstarget:
302 if (repo.dirstate.normalize(abssrc) ==
309 if (repo.dirstate.normalize(abssrc) ==
303 repo.dirstate.normalize(abstarget)):
310 repo.dirstate.normalize(abstarget)):
304 if not rename:
311 if not rename:
305 ui.warn(_("%s: can't copy - same file\n") % reltarget)
312 ui.warn(_("%s: can't copy - same file\n") % reltarget)
306 return
313 return
307 exists = False
314 exists = False
308 samefile = True
315 samefile = True
309
316
310 if not after and exists or after and state in 'mn':
317 if not after and exists or after and state in 'mn':
311 if not opts['force']:
318 if not opts['force']:
312 ui.warn(_('%s: not overwriting - file exists\n') %
319 ui.warn(_('%s: not overwriting - file exists\n') %
313 reltarget)
320 reltarget)
314 return
321 return
315
322
316 if after:
323 if after:
317 if not exists:
324 if not exists:
318 if rename:
325 if rename:
319 ui.warn(_('%s: not recording move - %s does not exist\n') %
326 ui.warn(_('%s: not recording move - %s does not exist\n') %
320 (relsrc, reltarget))
327 (relsrc, reltarget))
321 else:
328 else:
322 ui.warn(_('%s: not recording copy - %s does not exist\n') %
329 ui.warn(_('%s: not recording copy - %s does not exist\n') %
323 (relsrc, reltarget))
330 (relsrc, reltarget))
324 return
331 return
325 elif not dryrun:
332 elif not dryrun:
326 try:
333 try:
327 if exists:
334 if exists:
328 os.unlink(target)
335 os.unlink(target)
329 targetdir = os.path.dirname(target) or '.'
336 targetdir = os.path.dirname(target) or '.'
330 if not os.path.isdir(targetdir):
337 if not os.path.isdir(targetdir):
331 os.makedirs(targetdir)
338 os.makedirs(targetdir)
332 if samefile:
339 if samefile:
333 tmp = target + "~hgrename"
340 tmp = target + "~hgrename"
334 os.rename(src, tmp)
341 os.rename(src, tmp)
335 os.rename(tmp, target)
342 os.rename(tmp, target)
336 else:
343 else:
337 util.copyfile(src, target)
344 util.copyfile(src, target)
338 srcexists = True
345 srcexists = True
339 except IOError, inst:
346 except IOError, inst:
340 if inst.errno == errno.ENOENT:
347 if inst.errno == errno.ENOENT:
341 ui.warn(_('%s: deleted in working copy\n') % relsrc)
348 ui.warn(_('%s: deleted in working copy\n') % relsrc)
342 srcexists = False
349 srcexists = False
343 else:
350 else:
344 ui.warn(_('%s: cannot copy - %s\n') %
351 ui.warn(_('%s: cannot copy - %s\n') %
345 (relsrc, inst.strerror))
352 (relsrc, inst.strerror))
346 return True # report a failure
353 return True # report a failure
347
354
348 if ui.verbose or not exact:
355 if ui.verbose or not exact:
349 if rename:
356 if rename:
350 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
357 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
351 else:
358 else:
352 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
359 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
353
360
354 targets[abstarget] = abssrc
361 targets[abstarget] = abssrc
355
362
356 # fix up dirstate
363 # fix up dirstate
357 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
364 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
358 dryrun=dryrun, cwd=cwd)
365 dryrun=dryrun, cwd=cwd)
359 if rename and not dryrun:
366 if rename and not dryrun:
360 if not after and srcexists and not samefile:
367 if not after and srcexists and not samefile:
361 util.unlinkpath(repo.wjoin(abssrc))
368 util.unlinkpath(repo.wjoin(abssrc))
362 wctx.forget([abssrc])
369 wctx.forget([abssrc])
363
370
364 # pat: ossep
371 # pat: ossep
365 # dest ossep
372 # dest ossep
366 # srcs: list of (hgsep, hgsep, ossep, bool)
373 # srcs: list of (hgsep, hgsep, ossep, bool)
367 # return: function that takes hgsep and returns ossep
374 # return: function that takes hgsep and returns ossep
368 def targetpathfn(pat, dest, srcs):
375 def targetpathfn(pat, dest, srcs):
369 if os.path.isdir(pat):
376 if os.path.isdir(pat):
370 abspfx = pathutil.canonpath(repo.root, cwd, pat)
377 abspfx = pathutil.canonpath(repo.root, cwd, pat)
371 abspfx = util.localpath(abspfx)
378 abspfx = util.localpath(abspfx)
372 if destdirexists:
379 if destdirexists:
373 striplen = len(os.path.split(abspfx)[0])
380 striplen = len(os.path.split(abspfx)[0])
374 else:
381 else:
375 striplen = len(abspfx)
382 striplen = len(abspfx)
376 if striplen:
383 if striplen:
377 striplen += len(os.sep)
384 striplen += len(os.sep)
378 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
385 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
379 elif destdirexists:
386 elif destdirexists:
380 res = lambda p: os.path.join(dest,
387 res = lambda p: os.path.join(dest,
381 os.path.basename(util.localpath(p)))
388 os.path.basename(util.localpath(p)))
382 else:
389 else:
383 res = lambda p: dest
390 res = lambda p: dest
384 return res
391 return res
385
392
386 # pat: ossep
393 # pat: ossep
387 # dest ossep
394 # dest ossep
388 # srcs: list of (hgsep, hgsep, ossep, bool)
395 # srcs: list of (hgsep, hgsep, ossep, bool)
389 # return: function that takes hgsep and returns ossep
396 # return: function that takes hgsep and returns ossep
390 def targetpathafterfn(pat, dest, srcs):
397 def targetpathafterfn(pat, dest, srcs):
391 if matchmod.patkind(pat):
398 if matchmod.patkind(pat):
392 # a mercurial pattern
399 # a mercurial pattern
393 res = lambda p: os.path.join(dest,
400 res = lambda p: os.path.join(dest,
394 os.path.basename(util.localpath(p)))
401 os.path.basename(util.localpath(p)))
395 else:
402 else:
396 abspfx = pathutil.canonpath(repo.root, cwd, pat)
403 abspfx = pathutil.canonpath(repo.root, cwd, pat)
397 if len(abspfx) < len(srcs[0][0]):
404 if len(abspfx) < len(srcs[0][0]):
398 # A directory. Either the target path contains the last
405 # A directory. Either the target path contains the last
399 # component of the source path or it does not.
406 # component of the source path or it does not.
400 def evalpath(striplen):
407 def evalpath(striplen):
401 score = 0
408 score = 0
402 for s in srcs:
409 for s in srcs:
403 t = os.path.join(dest, util.localpath(s[0])[striplen:])
410 t = os.path.join(dest, util.localpath(s[0])[striplen:])
404 if os.path.lexists(t):
411 if os.path.lexists(t):
405 score += 1
412 score += 1
406 return score
413 return score
407
414
408 abspfx = util.localpath(abspfx)
415 abspfx = util.localpath(abspfx)
409 striplen = len(abspfx)
416 striplen = len(abspfx)
410 if striplen:
417 if striplen:
411 striplen += len(os.sep)
418 striplen += len(os.sep)
412 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
419 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
413 score = evalpath(striplen)
420 score = evalpath(striplen)
414 striplen1 = len(os.path.split(abspfx)[0])
421 striplen1 = len(os.path.split(abspfx)[0])
415 if striplen1:
422 if striplen1:
416 striplen1 += len(os.sep)
423 striplen1 += len(os.sep)
417 if evalpath(striplen1) > score:
424 if evalpath(striplen1) > score:
418 striplen = striplen1
425 striplen = striplen1
419 res = lambda p: os.path.join(dest,
426 res = lambda p: os.path.join(dest,
420 util.localpath(p)[striplen:])
427 util.localpath(p)[striplen:])
421 else:
428 else:
422 # a file
429 # a file
423 if destdirexists:
430 if destdirexists:
424 res = lambda p: os.path.join(dest,
431 res = lambda p: os.path.join(dest,
425 os.path.basename(util.localpath(p)))
432 os.path.basename(util.localpath(p)))
426 else:
433 else:
427 res = lambda p: dest
434 res = lambda p: dest
428 return res
435 return res
429
436
430
437
431 pats = scmutil.expandpats(pats)
438 pats = scmutil.expandpats(pats)
432 if not pats:
439 if not pats:
433 raise util.Abort(_('no source or destination specified'))
440 raise util.Abort(_('no source or destination specified'))
434 if len(pats) == 1:
441 if len(pats) == 1:
435 raise util.Abort(_('no destination specified'))
442 raise util.Abort(_('no destination specified'))
436 dest = pats.pop()
443 dest = pats.pop()
437 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
444 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
438 if not destdirexists:
445 if not destdirexists:
439 if len(pats) > 1 or matchmod.patkind(pats[0]):
446 if len(pats) > 1 or matchmod.patkind(pats[0]):
440 raise util.Abort(_('with multiple sources, destination must be an '
447 raise util.Abort(_('with multiple sources, destination must be an '
441 'existing directory'))
448 'existing directory'))
442 if util.endswithsep(dest):
449 if util.endswithsep(dest):
443 raise util.Abort(_('destination %s is not a directory') % dest)
450 raise util.Abort(_('destination %s is not a directory') % dest)
444
451
445 tfn = targetpathfn
452 tfn = targetpathfn
446 if after:
453 if after:
447 tfn = targetpathafterfn
454 tfn = targetpathafterfn
448 copylist = []
455 copylist = []
449 for pat in pats:
456 for pat in pats:
450 srcs = walkpat(pat)
457 srcs = walkpat(pat)
451 if not srcs:
458 if not srcs:
452 continue
459 continue
453 copylist.append((tfn(pat, dest, srcs), srcs))
460 copylist.append((tfn(pat, dest, srcs), srcs))
454 if not copylist:
461 if not copylist:
455 raise util.Abort(_('no files to copy'))
462 raise util.Abort(_('no files to copy'))
456
463
457 errors = 0
464 errors = 0
458 for targetpath, srcs in copylist:
465 for targetpath, srcs in copylist:
459 for abssrc, relsrc, exact in srcs:
466 for abssrc, relsrc, exact in srcs:
460 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
467 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
461 errors += 1
468 errors += 1
462
469
463 if errors:
470 if errors:
464 ui.warn(_('(consider using --after)\n'))
471 ui.warn(_('(consider using --after)\n'))
465
472
466 return errors != 0
473 return errors != 0
467
474
468 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
475 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
469 runargs=None, appendpid=False):
476 runargs=None, appendpid=False):
470 '''Run a command as a service.'''
477 '''Run a command as a service.'''
471
478
472 def writepid(pid):
479 def writepid(pid):
473 if opts['pid_file']:
480 if opts['pid_file']:
474 mode = appendpid and 'a' or 'w'
481 mode = appendpid and 'a' or 'w'
475 fp = open(opts['pid_file'], mode)
482 fp = open(opts['pid_file'], mode)
476 fp.write(str(pid) + '\n')
483 fp.write(str(pid) + '\n')
477 fp.close()
484 fp.close()
478
485
479 if opts['daemon'] and not opts['daemon_pipefds']:
486 if opts['daemon'] and not opts['daemon_pipefds']:
480 # Signal child process startup with file removal
487 # Signal child process startup with file removal
481 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
488 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
482 os.close(lockfd)
489 os.close(lockfd)
483 try:
490 try:
484 if not runargs:
491 if not runargs:
485 runargs = util.hgcmd() + sys.argv[1:]
492 runargs = util.hgcmd() + sys.argv[1:]
486 runargs.append('--daemon-pipefds=%s' % lockpath)
493 runargs.append('--daemon-pipefds=%s' % lockpath)
487 # Don't pass --cwd to the child process, because we've already
494 # Don't pass --cwd to the child process, because we've already
488 # changed directory.
495 # changed directory.
489 for i in xrange(1, len(runargs)):
496 for i in xrange(1, len(runargs)):
490 if runargs[i].startswith('--cwd='):
497 if runargs[i].startswith('--cwd='):
491 del runargs[i]
498 del runargs[i]
492 break
499 break
493 elif runargs[i].startswith('--cwd'):
500 elif runargs[i].startswith('--cwd'):
494 del runargs[i:i + 2]
501 del runargs[i:i + 2]
495 break
502 break
496 def condfn():
503 def condfn():
497 return not os.path.exists(lockpath)
504 return not os.path.exists(lockpath)
498 pid = util.rundetached(runargs, condfn)
505 pid = util.rundetached(runargs, condfn)
499 if pid < 0:
506 if pid < 0:
500 raise util.Abort(_('child process failed to start'))
507 raise util.Abort(_('child process failed to start'))
501 writepid(pid)
508 writepid(pid)
502 finally:
509 finally:
503 try:
510 try:
504 os.unlink(lockpath)
511 os.unlink(lockpath)
505 except OSError, e:
512 except OSError, e:
506 if e.errno != errno.ENOENT:
513 if e.errno != errno.ENOENT:
507 raise
514 raise
508 if parentfn:
515 if parentfn:
509 return parentfn(pid)
516 return parentfn(pid)
510 else:
517 else:
511 return
518 return
512
519
513 if initfn:
520 if initfn:
514 initfn()
521 initfn()
515
522
516 if not opts['daemon']:
523 if not opts['daemon']:
517 writepid(os.getpid())
524 writepid(os.getpid())
518
525
519 if opts['daemon_pipefds']:
526 if opts['daemon_pipefds']:
520 lockpath = opts['daemon_pipefds']
527 lockpath = opts['daemon_pipefds']
521 try:
528 try:
522 os.setsid()
529 os.setsid()
523 except AttributeError:
530 except AttributeError:
524 pass
531 pass
525 os.unlink(lockpath)
532 os.unlink(lockpath)
526 util.hidewindow()
533 util.hidewindow()
527 sys.stdout.flush()
534 sys.stdout.flush()
528 sys.stderr.flush()
535 sys.stderr.flush()
529
536
530 nullfd = os.open(os.devnull, os.O_RDWR)
537 nullfd = os.open(os.devnull, os.O_RDWR)
531 logfilefd = nullfd
538 logfilefd = nullfd
532 if logfile:
539 if logfile:
533 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
540 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
534 os.dup2(nullfd, 0)
541 os.dup2(nullfd, 0)
535 os.dup2(logfilefd, 1)
542 os.dup2(logfilefd, 1)
536 os.dup2(logfilefd, 2)
543 os.dup2(logfilefd, 2)
537 if nullfd not in (0, 1, 2):
544 if nullfd not in (0, 1, 2):
538 os.close(nullfd)
545 os.close(nullfd)
539 if logfile and logfilefd not in (0, 1, 2):
546 if logfile and logfilefd not in (0, 1, 2):
540 os.close(logfilefd)
547 os.close(logfilefd)
541
548
542 if runfn:
549 if runfn:
543 return runfn()
550 return runfn()
544
551
545 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
552 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
546 """Utility function used by commands.import to import a single patch
553 """Utility function used by commands.import to import a single patch
547
554
548 This function is explicitly defined here to help the evolve extension to
555 This function is explicitly defined here to help the evolve extension to
549 wrap this part of the import logic.
556 wrap this part of the import logic.
550
557
551 The API is currently a bit ugly because it a simple code translation from
558 The API is currently a bit ugly because it a simple code translation from
552 the import command. Feel free to make it better.
559 the import command. Feel free to make it better.
553
560
554 :hunk: a patch (as a binary string)
561 :hunk: a patch (as a binary string)
555 :parents: nodes that will be parent of the created commit
562 :parents: nodes that will be parent of the created commit
556 :opts: the full dict of option passed to the import command
563 :opts: the full dict of option passed to the import command
557 :msgs: list to save commit message to.
564 :msgs: list to save commit message to.
558 (used in case we need to save it when failing)
565 (used in case we need to save it when failing)
559 :updatefunc: a function that update a repo to a given node
566 :updatefunc: a function that update a repo to a given node
560 updatefunc(<repo>, <node>)
567 updatefunc(<repo>, <node>)
561 """
568 """
562 tmpname, message, user, date, branch, nodeid, p1, p2 = \
569 tmpname, message, user, date, branch, nodeid, p1, p2 = \
563 patch.extract(ui, hunk)
570 patch.extract(ui, hunk)
564
571
565 editor = commiteditor
572 editor = commiteditor
566 if opts.get('edit'):
573 if opts.get('edit'):
567 editor = commitforceeditor
574 editor = commitforceeditor
568 update = not opts.get('bypass')
575 update = not opts.get('bypass')
569 strip = opts["strip"]
576 strip = opts["strip"]
570 sim = float(opts.get('similarity') or 0)
577 sim = float(opts.get('similarity') or 0)
571 if not tmpname:
578 if not tmpname:
572 return (None, None)
579 return (None, None)
573 msg = _('applied to working directory')
580 msg = _('applied to working directory')
574
581
575 try:
582 try:
576 cmdline_message = logmessage(ui, opts)
583 cmdline_message = logmessage(ui, opts)
577 if cmdline_message:
584 if cmdline_message:
578 # pickup the cmdline msg
585 # pickup the cmdline msg
579 message = cmdline_message
586 message = cmdline_message
580 elif message:
587 elif message:
581 # pickup the patch msg
588 # pickup the patch msg
582 message = message.strip()
589 message = message.strip()
583 else:
590 else:
584 # launch the editor
591 # launch the editor
585 message = None
592 message = None
586 ui.debug('message:\n%s\n' % message)
593 ui.debug('message:\n%s\n' % message)
587
594
588 if len(parents) == 1:
595 if len(parents) == 1:
589 parents.append(repo[nullid])
596 parents.append(repo[nullid])
590 if opts.get('exact'):
597 if opts.get('exact'):
591 if not nodeid or not p1:
598 if not nodeid or not p1:
592 raise util.Abort(_('not a Mercurial patch'))
599 raise util.Abort(_('not a Mercurial patch'))
593 p1 = repo[p1]
600 p1 = repo[p1]
594 p2 = repo[p2 or nullid]
601 p2 = repo[p2 or nullid]
595 elif p2:
602 elif p2:
596 try:
603 try:
597 p1 = repo[p1]
604 p1 = repo[p1]
598 p2 = repo[p2]
605 p2 = repo[p2]
599 # Without any options, consider p2 only if the
606 # Without any options, consider p2 only if the
600 # patch is being applied on top of the recorded
607 # patch is being applied on top of the recorded
601 # first parent.
608 # first parent.
602 if p1 != parents[0]:
609 if p1 != parents[0]:
603 p1 = parents[0]
610 p1 = parents[0]
604 p2 = repo[nullid]
611 p2 = repo[nullid]
605 except error.RepoError:
612 except error.RepoError:
606 p1, p2 = parents
613 p1, p2 = parents
607 else:
614 else:
608 p1, p2 = parents
615 p1, p2 = parents
609
616
610 n = None
617 n = None
611 if update:
618 if update:
612 if p1 != parents[0]:
619 if p1 != parents[0]:
613 updatefunc(repo, p1.node())
620 updatefunc(repo, p1.node())
614 if p2 != parents[1]:
621 if p2 != parents[1]:
615 repo.setparents(p1.node(), p2.node())
622 repo.setparents(p1.node(), p2.node())
616
623
617 if opts.get('exact') or opts.get('import_branch'):
624 if opts.get('exact') or opts.get('import_branch'):
618 repo.dirstate.setbranch(branch or 'default')
625 repo.dirstate.setbranch(branch or 'default')
619
626
620 files = set()
627 files = set()
621 patch.patch(ui, repo, tmpname, strip=strip, files=files,
628 patch.patch(ui, repo, tmpname, strip=strip, files=files,
622 eolmode=None, similarity=sim / 100.0)
629 eolmode=None, similarity=sim / 100.0)
623 files = list(files)
630 files = list(files)
624 if opts.get('no_commit'):
631 if opts.get('no_commit'):
625 if message:
632 if message:
626 msgs.append(message)
633 msgs.append(message)
627 else:
634 else:
628 if opts.get('exact') or p2:
635 if opts.get('exact') or p2:
629 # If you got here, you either use --force and know what
636 # If you got here, you either use --force and know what
630 # you are doing or used --exact or a merge patch while
637 # you are doing or used --exact or a merge patch while
631 # being updated to its first parent.
638 # being updated to its first parent.
632 m = None
639 m = None
633 else:
640 else:
634 m = scmutil.matchfiles(repo, files or [])
641 m = scmutil.matchfiles(repo, files or [])
635 n = repo.commit(message, opts.get('user') or user,
642 n = repo.commit(message, opts.get('user') or user,
636 opts.get('date') or date, match=m,
643 opts.get('date') or date, match=m,
637 editor=editor)
644 editor=editor)
638 else:
645 else:
639 if opts.get('exact') or opts.get('import_branch'):
646 if opts.get('exact') or opts.get('import_branch'):
640 branch = branch or 'default'
647 branch = branch or 'default'
641 else:
648 else:
642 branch = p1.branch()
649 branch = p1.branch()
643 store = patch.filestore()
650 store = patch.filestore()
644 try:
651 try:
645 files = set()
652 files = set()
646 try:
653 try:
647 patch.patchrepo(ui, repo, p1, store, tmpname, strip,
654 patch.patchrepo(ui, repo, p1, store, tmpname, strip,
648 files, eolmode=None)
655 files, eolmode=None)
649 except patch.PatchError, e:
656 except patch.PatchError, e:
650 raise util.Abort(str(e))
657 raise util.Abort(str(e))
651 memctx = context.makememctx(repo, (p1.node(), p2.node()),
658 memctx = context.makememctx(repo, (p1.node(), p2.node()),
652 message,
659 message,
653 opts.get('user') or user,
660 opts.get('user') or user,
654 opts.get('date') or date,
661 opts.get('date') or date,
655 branch, files, store,
662 branch, files, store,
656 editor=commiteditor)
663 editor=commiteditor)
657 n = memctx.commit()
664 n = memctx.commit()
658 finally:
665 finally:
659 store.close()
666 store.close()
660 if opts.get('exact') and hex(n) != nodeid:
667 if opts.get('exact') and hex(n) != nodeid:
661 raise util.Abort(_('patch is damaged or loses information'))
668 raise util.Abort(_('patch is damaged or loses information'))
662 if n:
669 if n:
663 # i18n: refers to a short changeset id
670 # i18n: refers to a short changeset id
664 msg = _('created %s') % short(n)
671 msg = _('created %s') % short(n)
665 return (msg, n)
672 return (msg, n)
666 finally:
673 finally:
667 os.unlink(tmpname)
674 os.unlink(tmpname)
668
675
669 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
676 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
670 opts=None):
677 opts=None):
671 '''export changesets as hg patches.'''
678 '''export changesets as hg patches.'''
672
679
673 total = len(revs)
680 total = len(revs)
674 revwidth = max([len(str(rev)) for rev in revs])
681 revwidth = max([len(str(rev)) for rev in revs])
675 filemode = {}
682 filemode = {}
676
683
677 def single(rev, seqno, fp):
684 def single(rev, seqno, fp):
678 ctx = repo[rev]
685 ctx = repo[rev]
679 node = ctx.node()
686 node = ctx.node()
680 parents = [p.node() for p in ctx.parents() if p]
687 parents = [p.node() for p in ctx.parents() if p]
681 branch = ctx.branch()
688 branch = ctx.branch()
682 if switch_parent:
689 if switch_parent:
683 parents.reverse()
690 parents.reverse()
684 prev = (parents and parents[0]) or nullid
691 prev = (parents and parents[0]) or nullid
685
692
686 shouldclose = False
693 shouldclose = False
687 if not fp and len(template) > 0:
694 if not fp and len(template) > 0:
688 desc_lines = ctx.description().rstrip().split('\n')
695 desc_lines = ctx.description().rstrip().split('\n')
689 desc = desc_lines[0] #Commit always has a first line.
696 desc = desc_lines[0] #Commit always has a first line.
690 fp = makefileobj(repo, template, node, desc=desc, total=total,
697 fp = makefileobj(repo, template, node, desc=desc, total=total,
691 seqno=seqno, revwidth=revwidth, mode='wb',
698 seqno=seqno, revwidth=revwidth, mode='wb',
692 modemap=filemode)
699 modemap=filemode)
693 if fp != template:
700 if fp != template:
694 shouldclose = True
701 shouldclose = True
695 if fp and fp != sys.stdout and util.safehasattr(fp, 'name'):
702 if fp and fp != sys.stdout and util.safehasattr(fp, 'name'):
696 repo.ui.note("%s\n" % fp.name)
703 repo.ui.note("%s\n" % fp.name)
697
704
698 if not fp:
705 if not fp:
699 write = repo.ui.write
706 write = repo.ui.write
700 else:
707 else:
701 def write(s, **kw):
708 def write(s, **kw):
702 fp.write(s)
709 fp.write(s)
703
710
704
711
705 write("# HG changeset patch\n")
712 write("# HG changeset patch\n")
706 write("# User %s\n" % ctx.user())
713 write("# User %s\n" % ctx.user())
707 write("# Date %d %d\n" % ctx.date())
714 write("# Date %d %d\n" % ctx.date())
708 write("# %s\n" % util.datestr(ctx.date()))
715 write("# %s\n" % util.datestr(ctx.date()))
709 if branch and branch != 'default':
716 if branch and branch != 'default':
710 write("# Branch %s\n" % branch)
717 write("# Branch %s\n" % branch)
711 write("# Node ID %s\n" % hex(node))
718 write("# Node ID %s\n" % hex(node))
712 write("# Parent %s\n" % hex(prev))
719 write("# Parent %s\n" % hex(prev))
713 if len(parents) > 1:
720 if len(parents) > 1:
714 write("# Parent %s\n" % hex(parents[1]))
721 write("# Parent %s\n" % hex(parents[1]))
715 write(ctx.description().rstrip())
722 write(ctx.description().rstrip())
716 write("\n\n")
723 write("\n\n")
717
724
718 for chunk, label in patch.diffui(repo, prev, node, opts=opts):
725 for chunk, label in patch.diffui(repo, prev, node, opts=opts):
719 write(chunk, label=label)
726 write(chunk, label=label)
720
727
721 if shouldclose:
728 if shouldclose:
722 fp.close()
729 fp.close()
723
730
724 for seqno, rev in enumerate(revs):
731 for seqno, rev in enumerate(revs):
725 single(rev, seqno + 1, fp)
732 single(rev, seqno + 1, fp)
726
733
727 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
734 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
728 changes=None, stat=False, fp=None, prefix='',
735 changes=None, stat=False, fp=None, prefix='',
729 listsubrepos=False):
736 listsubrepos=False):
730 '''show diff or diffstat.'''
737 '''show diff or diffstat.'''
731 if fp is None:
738 if fp is None:
732 write = ui.write
739 write = ui.write
733 else:
740 else:
734 def write(s, **kw):
741 def write(s, **kw):
735 fp.write(s)
742 fp.write(s)
736
743
737 if stat:
744 if stat:
738 diffopts = diffopts.copy(context=0)
745 diffopts = diffopts.copy(context=0)
739 width = 80
746 width = 80
740 if not ui.plain():
747 if not ui.plain():
741 width = ui.termwidth()
748 width = ui.termwidth()
742 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
749 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
743 prefix=prefix)
750 prefix=prefix)
744 for chunk, label in patch.diffstatui(util.iterlines(chunks),
751 for chunk, label in patch.diffstatui(util.iterlines(chunks),
745 width=width,
752 width=width,
746 git=diffopts.git):
753 git=diffopts.git):
747 write(chunk, label=label)
754 write(chunk, label=label)
748 else:
755 else:
749 for chunk, label in patch.diffui(repo, node1, node2, match,
756 for chunk, label in patch.diffui(repo, node1, node2, match,
750 changes, diffopts, prefix=prefix):
757 changes, diffopts, prefix=prefix):
751 write(chunk, label=label)
758 write(chunk, label=label)
752
759
753 if listsubrepos:
760 if listsubrepos:
754 ctx1 = repo[node1]
761 ctx1 = repo[node1]
755 ctx2 = repo[node2]
762 ctx2 = repo[node2]
756 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
763 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
757 tempnode2 = node2
764 tempnode2 = node2
758 try:
765 try:
759 if node2 is not None:
766 if node2 is not None:
760 tempnode2 = ctx2.substate[subpath][1]
767 tempnode2 = ctx2.substate[subpath][1]
761 except KeyError:
768 except KeyError:
762 # A subrepo that existed in node1 was deleted between node1 and
769 # A subrepo that existed in node1 was deleted between node1 and
763 # node2 (inclusive). Thus, ctx2's substate won't contain that
770 # node2 (inclusive). Thus, ctx2's substate won't contain that
764 # subpath. The best we can do is to ignore it.
771 # subpath. The best we can do is to ignore it.
765 tempnode2 = None
772 tempnode2 = None
766 submatch = matchmod.narrowmatcher(subpath, match)
773 submatch = matchmod.narrowmatcher(subpath, match)
767 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
774 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
768 stat=stat, fp=fp, prefix=prefix)
775 stat=stat, fp=fp, prefix=prefix)
769
776
770 class changeset_printer(object):
777 class changeset_printer(object):
771 '''show changeset information when templating not requested.'''
778 '''show changeset information when templating not requested.'''
772
779
773 def __init__(self, ui, repo, patch, diffopts, buffered):
780 def __init__(self, ui, repo, patch, diffopts, buffered):
774 self.ui = ui
781 self.ui = ui
775 self.repo = repo
782 self.repo = repo
776 self.buffered = buffered
783 self.buffered = buffered
777 self.patch = patch
784 self.patch = patch
778 self.diffopts = diffopts
785 self.diffopts = diffopts
779 self.header = {}
786 self.header = {}
780 self.hunk = {}
787 self.hunk = {}
781 self.lastheader = None
788 self.lastheader = None
782 self.footer = None
789 self.footer = None
783
790
784 def flush(self, rev):
791 def flush(self, rev):
785 if rev in self.header:
792 if rev in self.header:
786 h = self.header[rev]
793 h = self.header[rev]
787 if h != self.lastheader:
794 if h != self.lastheader:
788 self.lastheader = h
795 self.lastheader = h
789 self.ui.write(h)
796 self.ui.write(h)
790 del self.header[rev]
797 del self.header[rev]
791 if rev in self.hunk:
798 if rev in self.hunk:
792 self.ui.write(self.hunk[rev])
799 self.ui.write(self.hunk[rev])
793 del self.hunk[rev]
800 del self.hunk[rev]
794 return 1
801 return 1
795 return 0
802 return 0
796
803
797 def close(self):
804 def close(self):
798 if self.footer:
805 if self.footer:
799 self.ui.write(self.footer)
806 self.ui.write(self.footer)
800
807
801 def show(self, ctx, copies=None, matchfn=None, **props):
808 def show(self, ctx, copies=None, matchfn=None, **props):
802 if self.buffered:
809 if self.buffered:
803 self.ui.pushbuffer()
810 self.ui.pushbuffer()
804 self._show(ctx, copies, matchfn, props)
811 self._show(ctx, copies, matchfn, props)
805 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
812 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
806 else:
813 else:
807 self._show(ctx, copies, matchfn, props)
814 self._show(ctx, copies, matchfn, props)
808
815
809 def _show(self, ctx, copies, matchfn, props):
816 def _show(self, ctx, copies, matchfn, props):
810 '''show a single changeset or file revision'''
817 '''show a single changeset or file revision'''
811 changenode = ctx.node()
818 changenode = ctx.node()
812 rev = ctx.rev()
819 rev = ctx.rev()
813
820
814 if self.ui.quiet:
821 if self.ui.quiet:
815 self.ui.write("%d:%s\n" % (rev, short(changenode)),
822 self.ui.write("%d:%s\n" % (rev, short(changenode)),
816 label='log.node')
823 label='log.node')
817 return
824 return
818
825
819 log = self.repo.changelog
826 log = self.repo.changelog
820 date = util.datestr(ctx.date())
827 date = util.datestr(ctx.date())
821
828
822 hexfunc = self.ui.debugflag and hex or short
829 hexfunc = self.ui.debugflag and hex or short
823
830
824 parents = [(p, hexfunc(log.node(p)))
831 parents = [(p, hexfunc(log.node(p)))
825 for p in self._meaningful_parentrevs(log, rev)]
832 for p in self._meaningful_parentrevs(log, rev)]
826
833
827 # i18n: column positioning for "hg log"
834 # i18n: column positioning for "hg log"
828 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
835 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
829 label='log.changeset changeset.%s' % ctx.phasestr())
836 label='log.changeset changeset.%s' % ctx.phasestr())
830
837
831 branch = ctx.branch()
838 branch = ctx.branch()
832 # don't show the default branch name
839 # don't show the default branch name
833 if branch != 'default':
840 if branch != 'default':
834 # i18n: column positioning for "hg log"
841 # i18n: column positioning for "hg log"
835 self.ui.write(_("branch: %s\n") % branch,
842 self.ui.write(_("branch: %s\n") % branch,
836 label='log.branch')
843 label='log.branch')
837 for bookmark in self.repo.nodebookmarks(changenode):
844 for bookmark in self.repo.nodebookmarks(changenode):
838 # i18n: column positioning for "hg log"
845 # i18n: column positioning for "hg log"
839 self.ui.write(_("bookmark: %s\n") % bookmark,
846 self.ui.write(_("bookmark: %s\n") % bookmark,
840 label='log.bookmark')
847 label='log.bookmark')
841 for tag in self.repo.nodetags(changenode):
848 for tag in self.repo.nodetags(changenode):
842 # i18n: column positioning for "hg log"
849 # i18n: column positioning for "hg log"
843 self.ui.write(_("tag: %s\n") % tag,
850 self.ui.write(_("tag: %s\n") % tag,
844 label='log.tag')
851 label='log.tag')
845 if self.ui.debugflag and ctx.phase():
852 if self.ui.debugflag and ctx.phase():
846 # i18n: column positioning for "hg log"
853 # i18n: column positioning for "hg log"
847 self.ui.write(_("phase: %s\n") % _(ctx.phasestr()),
854 self.ui.write(_("phase: %s\n") % _(ctx.phasestr()),
848 label='log.phase')
855 label='log.phase')
849 for parent in parents:
856 for parent in parents:
850 # i18n: column positioning for "hg log"
857 # i18n: column positioning for "hg log"
851 self.ui.write(_("parent: %d:%s\n") % parent,
858 self.ui.write(_("parent: %d:%s\n") % parent,
852 label='log.parent changeset.%s' % ctx.phasestr())
859 label='log.parent changeset.%s' % ctx.phasestr())
853
860
854 if self.ui.debugflag:
861 if self.ui.debugflag:
855 mnode = ctx.manifestnode()
862 mnode = ctx.manifestnode()
856 # i18n: column positioning for "hg log"
863 # i18n: column positioning for "hg log"
857 self.ui.write(_("manifest: %d:%s\n") %
864 self.ui.write(_("manifest: %d:%s\n") %
858 (self.repo.manifest.rev(mnode), hex(mnode)),
865 (self.repo.manifest.rev(mnode), hex(mnode)),
859 label='ui.debug log.manifest')
866 label='ui.debug log.manifest')
860 # i18n: column positioning for "hg log"
867 # i18n: column positioning for "hg log"
861 self.ui.write(_("user: %s\n") % ctx.user(),
868 self.ui.write(_("user: %s\n") % ctx.user(),
862 label='log.user')
869 label='log.user')
863 # i18n: column positioning for "hg log"
870 # i18n: column positioning for "hg log"
864 self.ui.write(_("date: %s\n") % date,
871 self.ui.write(_("date: %s\n") % date,
865 label='log.date')
872 label='log.date')
866
873
867 if self.ui.debugflag:
874 if self.ui.debugflag:
868 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
875 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
869 for key, value in zip([# i18n: column positioning for "hg log"
876 for key, value in zip([# i18n: column positioning for "hg log"
870 _("files:"),
877 _("files:"),
871 # i18n: column positioning for "hg log"
878 # i18n: column positioning for "hg log"
872 _("files+:"),
879 _("files+:"),
873 # i18n: column positioning for "hg log"
880 # i18n: column positioning for "hg log"
874 _("files-:")], files):
881 _("files-:")], files):
875 if value:
882 if value:
876 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
883 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
877 label='ui.debug log.files')
884 label='ui.debug log.files')
878 elif ctx.files() and self.ui.verbose:
885 elif ctx.files() and self.ui.verbose:
879 # i18n: column positioning for "hg log"
886 # i18n: column positioning for "hg log"
880 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
887 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
881 label='ui.note log.files')
888 label='ui.note log.files')
882 if copies and self.ui.verbose:
889 if copies and self.ui.verbose:
883 copies = ['%s (%s)' % c for c in copies]
890 copies = ['%s (%s)' % c for c in copies]
884 # i18n: column positioning for "hg log"
891 # i18n: column positioning for "hg log"
885 self.ui.write(_("copies: %s\n") % ' '.join(copies),
892 self.ui.write(_("copies: %s\n") % ' '.join(copies),
886 label='ui.note log.copies')
893 label='ui.note log.copies')
887
894
888 extra = ctx.extra()
895 extra = ctx.extra()
889 if extra and self.ui.debugflag:
896 if extra and self.ui.debugflag:
890 for key, value in sorted(extra.items()):
897 for key, value in sorted(extra.items()):
891 # i18n: column positioning for "hg log"
898 # i18n: column positioning for "hg log"
892 self.ui.write(_("extra: %s=%s\n")
899 self.ui.write(_("extra: %s=%s\n")
893 % (key, value.encode('string_escape')),
900 % (key, value.encode('string_escape')),
894 label='ui.debug log.extra')
901 label='ui.debug log.extra')
895
902
896 description = ctx.description().strip()
903 description = ctx.description().strip()
897 if description:
904 if description:
898 if self.ui.verbose:
905 if self.ui.verbose:
899 self.ui.write(_("description:\n"),
906 self.ui.write(_("description:\n"),
900 label='ui.note log.description')
907 label='ui.note log.description')
901 self.ui.write(description,
908 self.ui.write(description,
902 label='ui.note log.description')
909 label='ui.note log.description')
903 self.ui.write("\n\n")
910 self.ui.write("\n\n")
904 else:
911 else:
905 # i18n: column positioning for "hg log"
912 # i18n: column positioning for "hg log"
906 self.ui.write(_("summary: %s\n") %
913 self.ui.write(_("summary: %s\n") %
907 description.splitlines()[0],
914 description.splitlines()[0],
908 label='log.summary')
915 label='log.summary')
909 self.ui.write("\n")
916 self.ui.write("\n")
910
917
911 self.showpatch(changenode, matchfn)
918 self.showpatch(changenode, matchfn)
912
919
913 def showpatch(self, node, matchfn):
920 def showpatch(self, node, matchfn):
914 if not matchfn:
921 if not matchfn:
915 matchfn = self.patch
922 matchfn = self.patch
916 if matchfn:
923 if matchfn:
917 stat = self.diffopts.get('stat')
924 stat = self.diffopts.get('stat')
918 diff = self.diffopts.get('patch')
925 diff = self.diffopts.get('patch')
919 diffopts = patch.diffopts(self.ui, self.diffopts)
926 diffopts = patch.diffopts(self.ui, self.diffopts)
920 prev = self.repo.changelog.parents(node)[0]
927 prev = self.repo.changelog.parents(node)[0]
921 if stat:
928 if stat:
922 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
929 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
923 match=matchfn, stat=True)
930 match=matchfn, stat=True)
924 if diff:
931 if diff:
925 if stat:
932 if stat:
926 self.ui.write("\n")
933 self.ui.write("\n")
927 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
934 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
928 match=matchfn, stat=False)
935 match=matchfn, stat=False)
929 self.ui.write("\n")
936 self.ui.write("\n")
930
937
931 def _meaningful_parentrevs(self, log, rev):
938 def _meaningful_parentrevs(self, log, rev):
932 """Return list of meaningful (or all if debug) parentrevs for rev.
939 """Return list of meaningful (or all if debug) parentrevs for rev.
933
940
934 For merges (two non-nullrev revisions) both parents are meaningful.
941 For merges (two non-nullrev revisions) both parents are meaningful.
935 Otherwise the first parent revision is considered meaningful if it
942 Otherwise the first parent revision is considered meaningful if it
936 is not the preceding revision.
943 is not the preceding revision.
937 """
944 """
938 parents = log.parentrevs(rev)
945 parents = log.parentrevs(rev)
939 if not self.ui.debugflag and parents[1] == nullrev:
946 if not self.ui.debugflag and parents[1] == nullrev:
940 if parents[0] >= rev - 1:
947 if parents[0] >= rev - 1:
941 parents = []
948 parents = []
942 else:
949 else:
943 parents = [parents[0]]
950 parents = [parents[0]]
944 return parents
951 return parents
945
952
946
953
947 class changeset_templater(changeset_printer):
954 class changeset_templater(changeset_printer):
948 '''format changeset information.'''
955 '''format changeset information.'''
949
956
950 def __init__(self, ui, repo, patch, diffopts, tmpl, mapfile, buffered):
957 def __init__(self, ui, repo, patch, diffopts, tmpl, mapfile, buffered):
951 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
958 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
952 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
959 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
953 defaulttempl = {
960 defaulttempl = {
954 'parent': '{rev}:{node|formatnode} ',
961 'parent': '{rev}:{node|formatnode} ',
955 'manifest': '{rev}:{node|formatnode}',
962 'manifest': '{rev}:{node|formatnode}',
956 'file_copy': '{name} ({source})',
963 'file_copy': '{name} ({source})',
957 'extra': '{key}={value|stringescape}'
964 'extra': '{key}={value|stringescape}'
958 }
965 }
959 # filecopy is preserved for compatibility reasons
966 # filecopy is preserved for compatibility reasons
960 defaulttempl['filecopy'] = defaulttempl['file_copy']
967 defaulttempl['filecopy'] = defaulttempl['file_copy']
961 self.t = templater.templater(mapfile, {'formatnode': formatnode},
968 self.t = templater.templater(mapfile, {'formatnode': formatnode},
962 cache=defaulttempl)
969 cache=defaulttempl)
963 if tmpl:
970 if tmpl:
964 self.t.cache['changeset'] = tmpl
971 self.t.cache['changeset'] = tmpl
965
972
966 self.cache = {}
973 self.cache = {}
967
974
968 def _meaningful_parentrevs(self, ctx):
975 def _meaningful_parentrevs(self, ctx):
969 """Return list of meaningful (or all if debug) parentrevs for rev.
976 """Return list of meaningful (or all if debug) parentrevs for rev.
970 """
977 """
971 parents = ctx.parents()
978 parents = ctx.parents()
972 if len(parents) > 1:
979 if len(parents) > 1:
973 return parents
980 return parents
974 if self.ui.debugflag:
981 if self.ui.debugflag:
975 return [parents[0], self.repo['null']]
982 return [parents[0], self.repo['null']]
976 if parents[0].rev() >= ctx.rev() - 1:
983 if parents[0].rev() >= ctx.rev() - 1:
977 return []
984 return []
978 return parents
985 return parents
979
986
980 def _show(self, ctx, copies, matchfn, props):
987 def _show(self, ctx, copies, matchfn, props):
981 '''show a single changeset or file revision'''
988 '''show a single changeset or file revision'''
982
989
983 showlist = templatekw.showlist
990 showlist = templatekw.showlist
984
991
985 # showparents() behaviour depends on ui trace level which
992 # showparents() behaviour depends on ui trace level which
986 # causes unexpected behaviours at templating level and makes
993 # causes unexpected behaviours at templating level and makes
987 # it harder to extract it in a standalone function. Its
994 # it harder to extract it in a standalone function. Its
988 # behaviour cannot be changed so leave it here for now.
995 # behaviour cannot be changed so leave it here for now.
989 def showparents(**args):
996 def showparents(**args):
990 ctx = args['ctx']
997 ctx = args['ctx']
991 parents = [[('rev', p.rev()), ('node', p.hex())]
998 parents = [[('rev', p.rev()), ('node', p.hex())]
992 for p in self._meaningful_parentrevs(ctx)]
999 for p in self._meaningful_parentrevs(ctx)]
993 return showlist('parent', parents, **args)
1000 return showlist('parent', parents, **args)
994
1001
995 props = props.copy()
1002 props = props.copy()
996 props.update(templatekw.keywords)
1003 props.update(templatekw.keywords)
997 props['parents'] = showparents
1004 props['parents'] = showparents
998 props['templ'] = self.t
1005 props['templ'] = self.t
999 props['ctx'] = ctx
1006 props['ctx'] = ctx
1000 props['repo'] = self.repo
1007 props['repo'] = self.repo
1001 props['revcache'] = {'copies': copies}
1008 props['revcache'] = {'copies': copies}
1002 props['cache'] = self.cache
1009 props['cache'] = self.cache
1003
1010
1004 # find correct templates for current mode
1011 # find correct templates for current mode
1005
1012
1006 tmplmodes = [
1013 tmplmodes = [
1007 (True, None),
1014 (True, None),
1008 (self.ui.verbose, 'verbose'),
1015 (self.ui.verbose, 'verbose'),
1009 (self.ui.quiet, 'quiet'),
1016 (self.ui.quiet, 'quiet'),
1010 (self.ui.debugflag, 'debug'),
1017 (self.ui.debugflag, 'debug'),
1011 ]
1018 ]
1012
1019
1013 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
1020 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
1014 for mode, postfix in tmplmodes:
1021 for mode, postfix in tmplmodes:
1015 for type in types:
1022 for type in types:
1016 cur = postfix and ('%s_%s' % (type, postfix)) or type
1023 cur = postfix and ('%s_%s' % (type, postfix)) or type
1017 if mode and cur in self.t:
1024 if mode and cur in self.t:
1018 types[type] = cur
1025 types[type] = cur
1019
1026
1020 try:
1027 try:
1021
1028
1022 # write header
1029 # write header
1023 if types['header']:
1030 if types['header']:
1024 h = templater.stringify(self.t(types['header'], **props))
1031 h = templater.stringify(self.t(types['header'], **props))
1025 if self.buffered:
1032 if self.buffered:
1026 self.header[ctx.rev()] = h
1033 self.header[ctx.rev()] = h
1027 else:
1034 else:
1028 if self.lastheader != h:
1035 if self.lastheader != h:
1029 self.lastheader = h
1036 self.lastheader = h
1030 self.ui.write(h)
1037 self.ui.write(h)
1031
1038
1032 # write changeset metadata, then patch if requested
1039 # write changeset metadata, then patch if requested
1033 key = types['changeset']
1040 key = types['changeset']
1034 self.ui.write(templater.stringify(self.t(key, **props)))
1041 self.ui.write(templater.stringify(self.t(key, **props)))
1035 self.showpatch(ctx.node(), matchfn)
1042 self.showpatch(ctx.node(), matchfn)
1036
1043
1037 if types['footer']:
1044 if types['footer']:
1038 if not self.footer:
1045 if not self.footer:
1039 self.footer = templater.stringify(self.t(types['footer'],
1046 self.footer = templater.stringify(self.t(types['footer'],
1040 **props))
1047 **props))
1041
1048
1042 except KeyError, inst:
1049 except KeyError, inst:
1043 msg = _("%s: no key named '%s'")
1050 msg = _("%s: no key named '%s'")
1044 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1051 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1045 except SyntaxError, inst:
1052 except SyntaxError, inst:
1046 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1053 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1047
1054
1048 def gettemplate(ui, tmpl, style):
1055 def gettemplate(ui, tmpl, style):
1049 """
1056 """
1050 Find the template matching the given template spec or style.
1057 Find the template matching the given template spec or style.
1051 """
1058 """
1052
1059
1053 # ui settings
1060 # ui settings
1054 if not tmpl and not style:
1061 if not tmpl and not style:
1055 tmpl = ui.config('ui', 'logtemplate')
1062 tmpl = ui.config('ui', 'logtemplate')
1056 if tmpl:
1063 if tmpl:
1057 try:
1064 try:
1058 tmpl = templater.parsestring(tmpl)
1065 tmpl = templater.parsestring(tmpl)
1059 except SyntaxError:
1066 except SyntaxError:
1060 tmpl = templater.parsestring(tmpl, quoted=False)
1067 tmpl = templater.parsestring(tmpl, quoted=False)
1061 return tmpl, None
1068 return tmpl, None
1062 else:
1069 else:
1063 style = util.expandpath(ui.config('ui', 'style', ''))
1070 style = util.expandpath(ui.config('ui', 'style', ''))
1064
1071
1065 if style:
1072 if style:
1066 mapfile = style
1073 mapfile = style
1067 if not os.path.split(mapfile)[0]:
1074 if not os.path.split(mapfile)[0]:
1068 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1075 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1069 or templater.templatepath(mapfile))
1076 or templater.templatepath(mapfile))
1070 if mapname:
1077 if mapname:
1071 mapfile = mapname
1078 mapfile = mapname
1072 return None, mapfile
1079 return None, mapfile
1073
1080
1074 if not tmpl:
1081 if not tmpl:
1075 return None, None
1082 return None, None
1076
1083
1077 # looks like a literal template?
1084 # looks like a literal template?
1078 if '{' in tmpl:
1085 if '{' in tmpl:
1079 return tmpl, None
1086 return tmpl, None
1080
1087
1081 # perhaps a stock style?
1088 # perhaps a stock style?
1082 if not os.path.split(tmpl)[0]:
1089 if not os.path.split(tmpl)[0]:
1083 mapname = (templater.templatepath('map-cmdline.' + tmpl)
1090 mapname = (templater.templatepath('map-cmdline.' + tmpl)
1084 or templater.templatepath(tmpl))
1091 or templater.templatepath(tmpl))
1085 if mapname and os.path.isfile(mapname):
1092 if mapname and os.path.isfile(mapname):
1086 return None, mapname
1093 return None, mapname
1087
1094
1088 # perhaps it's a reference to [templates]
1095 # perhaps it's a reference to [templates]
1089 t = ui.config('templates', tmpl)
1096 t = ui.config('templates', tmpl)
1090 if t:
1097 if t:
1091 try:
1098 try:
1092 tmpl = templater.parsestring(t)
1099 tmpl = templater.parsestring(t)
1093 except SyntaxError:
1100 except SyntaxError:
1094 tmpl = templater.parsestring(t, quoted=False)
1101 tmpl = templater.parsestring(t, quoted=False)
1095 return tmpl, None
1102 return tmpl, None
1096
1103
1097 # perhaps it's a path to a map or a template
1104 # perhaps it's a path to a map or a template
1098 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
1105 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
1099 # is it a mapfile for a style?
1106 # is it a mapfile for a style?
1100 if os.path.basename(tmpl).startswith("map-"):
1107 if os.path.basename(tmpl).startswith("map-"):
1101 return None, os.path.realpath(tmpl)
1108 return None, os.path.realpath(tmpl)
1102 tmpl = open(tmpl).read()
1109 tmpl = open(tmpl).read()
1103 return tmpl, None
1110 return tmpl, None
1104
1111
1105 # constant string?
1112 # constant string?
1106 return tmpl, None
1113 return tmpl, None
1107
1114
1108 def show_changeset(ui, repo, opts, buffered=False):
1115 def show_changeset(ui, repo, opts, buffered=False):
1109 """show one changeset using template or regular display.
1116 """show one changeset using template or regular display.
1110
1117
1111 Display format will be the first non-empty hit of:
1118 Display format will be the first non-empty hit of:
1112 1. option 'template'
1119 1. option 'template'
1113 2. option 'style'
1120 2. option 'style'
1114 3. [ui] setting 'logtemplate'
1121 3. [ui] setting 'logtemplate'
1115 4. [ui] setting 'style'
1122 4. [ui] setting 'style'
1116 If all of these values are either the unset or the empty string,
1123 If all of these values are either the unset or the empty string,
1117 regular display via changeset_printer() is done.
1124 regular display via changeset_printer() is done.
1118 """
1125 """
1119 # options
1126 # options
1120 patch = None
1127 patch = None
1121 if opts.get('patch') or opts.get('stat'):
1128 if opts.get('patch') or opts.get('stat'):
1122 patch = scmutil.matchall(repo)
1129 patch = scmutil.matchall(repo)
1123
1130
1124 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1131 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1125
1132
1126 if not tmpl and not mapfile:
1133 if not tmpl and not mapfile:
1127 return changeset_printer(ui, repo, patch, opts, buffered)
1134 return changeset_printer(ui, repo, patch, opts, buffered)
1128
1135
1129 try:
1136 try:
1130 t = changeset_templater(ui, repo, patch, opts, tmpl, mapfile, buffered)
1137 t = changeset_templater(ui, repo, patch, opts, tmpl, mapfile, buffered)
1131 except SyntaxError, inst:
1138 except SyntaxError, inst:
1132 raise util.Abort(inst.args[0])
1139 raise util.Abort(inst.args[0])
1133 return t
1140 return t
1134
1141
1135 def showmarker(ui, marker):
1142 def showmarker(ui, marker):
1136 """utility function to display obsolescence marker in a readable way
1143 """utility function to display obsolescence marker in a readable way
1137
1144
1138 To be used by debug function."""
1145 To be used by debug function."""
1139 ui.write(hex(marker.precnode()))
1146 ui.write(hex(marker.precnode()))
1140 for repl in marker.succnodes():
1147 for repl in marker.succnodes():
1141 ui.write(' ')
1148 ui.write(' ')
1142 ui.write(hex(repl))
1149 ui.write(hex(repl))
1143 ui.write(' %X ' % marker._data[2])
1150 ui.write(' %X ' % marker._data[2])
1144 ui.write('{%s}' % (', '.join('%r: %r' % t for t in
1151 ui.write('{%s}' % (', '.join('%r: %r' % t for t in
1145 sorted(marker.metadata().items()))))
1152 sorted(marker.metadata().items()))))
1146 ui.write('\n')
1153 ui.write('\n')
1147
1154
1148 def finddate(ui, repo, date):
1155 def finddate(ui, repo, date):
1149 """Find the tipmost changeset that matches the given date spec"""
1156 """Find the tipmost changeset that matches the given date spec"""
1150
1157
1151 df = util.matchdate(date)
1158 df = util.matchdate(date)
1152 m = scmutil.matchall(repo)
1159 m = scmutil.matchall(repo)
1153 results = {}
1160 results = {}
1154
1161
1155 def prep(ctx, fns):
1162 def prep(ctx, fns):
1156 d = ctx.date()
1163 d = ctx.date()
1157 if df(d[0]):
1164 if df(d[0]):
1158 results[ctx.rev()] = d
1165 results[ctx.rev()] = d
1159
1166
1160 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1167 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1161 rev = ctx.rev()
1168 rev = ctx.rev()
1162 if rev in results:
1169 if rev in results:
1163 ui.status(_("found revision %s from %s\n") %
1170 ui.status(_("found revision %s from %s\n") %
1164 (rev, util.datestr(results[rev])))
1171 (rev, util.datestr(results[rev])))
1165 return str(rev)
1172 return str(rev)
1166
1173
1167 raise util.Abort(_("revision matching date not found"))
1174 raise util.Abort(_("revision matching date not found"))
1168
1175
1169 def increasingwindows(windowsize=8, sizelimit=512):
1176 def increasingwindows(windowsize=8, sizelimit=512):
1170 while True:
1177 while True:
1171 yield windowsize
1178 yield windowsize
1172 if windowsize < sizelimit:
1179 if windowsize < sizelimit:
1173 windowsize *= 2
1180 windowsize *= 2
1174
1181
1175 class FileWalkError(Exception):
1182 class FileWalkError(Exception):
1176 pass
1183 pass
1177
1184
1178 def walkfilerevs(repo, match, follow, revs, fncache):
1185 def walkfilerevs(repo, match, follow, revs, fncache):
1179 '''Walks the file history for the matched files.
1186 '''Walks the file history for the matched files.
1180
1187
1181 Returns the changeset revs that are involved in the file history.
1188 Returns the changeset revs that are involved in the file history.
1182
1189
1183 Throws FileWalkError if the file history can't be walked using
1190 Throws FileWalkError if the file history can't be walked using
1184 filelogs alone.
1191 filelogs alone.
1185 '''
1192 '''
1186 wanted = set()
1193 wanted = set()
1187 copies = []
1194 copies = []
1188 minrev, maxrev = min(revs), max(revs)
1195 minrev, maxrev = min(revs), max(revs)
1189 def filerevgen(filelog, last):
1196 def filerevgen(filelog, last):
1190 """
1197 """
1191 Only files, no patterns. Check the history of each file.
1198 Only files, no patterns. Check the history of each file.
1192
1199
1193 Examines filelog entries within minrev, maxrev linkrev range
1200 Examines filelog entries within minrev, maxrev linkrev range
1194 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1201 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1195 tuples in backwards order
1202 tuples in backwards order
1196 """
1203 """
1197 cl_count = len(repo)
1204 cl_count = len(repo)
1198 revs = []
1205 revs = []
1199 for j in xrange(0, last + 1):
1206 for j in xrange(0, last + 1):
1200 linkrev = filelog.linkrev(j)
1207 linkrev = filelog.linkrev(j)
1201 if linkrev < minrev:
1208 if linkrev < minrev:
1202 continue
1209 continue
1203 # only yield rev for which we have the changelog, it can
1210 # only yield rev for which we have the changelog, it can
1204 # happen while doing "hg log" during a pull or commit
1211 # happen while doing "hg log" during a pull or commit
1205 if linkrev >= cl_count:
1212 if linkrev >= cl_count:
1206 break
1213 break
1207
1214
1208 parentlinkrevs = []
1215 parentlinkrevs = []
1209 for p in filelog.parentrevs(j):
1216 for p in filelog.parentrevs(j):
1210 if p != nullrev:
1217 if p != nullrev:
1211 parentlinkrevs.append(filelog.linkrev(p))
1218 parentlinkrevs.append(filelog.linkrev(p))
1212 n = filelog.node(j)
1219 n = filelog.node(j)
1213 revs.append((linkrev, parentlinkrevs,
1220 revs.append((linkrev, parentlinkrevs,
1214 follow and filelog.renamed(n)))
1221 follow and filelog.renamed(n)))
1215
1222
1216 return reversed(revs)
1223 return reversed(revs)
1217 def iterfiles():
1224 def iterfiles():
1218 pctx = repo['.']
1225 pctx = repo['.']
1219 for filename in match.files():
1226 for filename in match.files():
1220 if follow:
1227 if follow:
1221 if filename not in pctx:
1228 if filename not in pctx:
1222 raise util.Abort(_('cannot follow file not in parent '
1229 raise util.Abort(_('cannot follow file not in parent '
1223 'revision: "%s"') % filename)
1230 'revision: "%s"') % filename)
1224 yield filename, pctx[filename].filenode()
1231 yield filename, pctx[filename].filenode()
1225 else:
1232 else:
1226 yield filename, None
1233 yield filename, None
1227 for filename_node in copies:
1234 for filename_node in copies:
1228 yield filename_node
1235 yield filename_node
1229
1236
1230 for file_, node in iterfiles():
1237 for file_, node in iterfiles():
1231 filelog = repo.file(file_)
1238 filelog = repo.file(file_)
1232 if not len(filelog):
1239 if not len(filelog):
1233 if node is None:
1240 if node is None:
1234 # A zero count may be a directory or deleted file, so
1241 # A zero count may be a directory or deleted file, so
1235 # try to find matching entries on the slow path.
1242 # try to find matching entries on the slow path.
1236 if follow:
1243 if follow:
1237 raise util.Abort(
1244 raise util.Abort(
1238 _('cannot follow nonexistent file: "%s"') % file_)
1245 _('cannot follow nonexistent file: "%s"') % file_)
1239 raise FileWalkError("Cannot walk via filelog")
1246 raise FileWalkError("Cannot walk via filelog")
1240 else:
1247 else:
1241 continue
1248 continue
1242
1249
1243 if node is None:
1250 if node is None:
1244 last = len(filelog) - 1
1251 last = len(filelog) - 1
1245 else:
1252 else:
1246 last = filelog.rev(node)
1253 last = filelog.rev(node)
1247
1254
1248
1255
1249 # keep track of all ancestors of the file
1256 # keep track of all ancestors of the file
1250 ancestors = set([filelog.linkrev(last)])
1257 ancestors = set([filelog.linkrev(last)])
1251
1258
1252 # iterate from latest to oldest revision
1259 # iterate from latest to oldest revision
1253 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1260 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1254 if not follow:
1261 if not follow:
1255 if rev > maxrev:
1262 if rev > maxrev:
1256 continue
1263 continue
1257 else:
1264 else:
1258 # Note that last might not be the first interesting
1265 # Note that last might not be the first interesting
1259 # rev to us:
1266 # rev to us:
1260 # if the file has been changed after maxrev, we'll
1267 # if the file has been changed after maxrev, we'll
1261 # have linkrev(last) > maxrev, and we still need
1268 # have linkrev(last) > maxrev, and we still need
1262 # to explore the file graph
1269 # to explore the file graph
1263 if rev not in ancestors:
1270 if rev not in ancestors:
1264 continue
1271 continue
1265 # XXX insert 1327 fix here
1272 # XXX insert 1327 fix here
1266 if flparentlinkrevs:
1273 if flparentlinkrevs:
1267 ancestors.update(flparentlinkrevs)
1274 ancestors.update(flparentlinkrevs)
1268
1275
1269 fncache.setdefault(rev, []).append(file_)
1276 fncache.setdefault(rev, []).append(file_)
1270 wanted.add(rev)
1277 wanted.add(rev)
1271 if copied:
1278 if copied:
1272 copies.append(copied)
1279 copies.append(copied)
1273
1280
1274 return wanted
1281 return wanted
1275
1282
1276 def walkchangerevs(repo, match, opts, prepare):
1283 def walkchangerevs(repo, match, opts, prepare):
1277 '''Iterate over files and the revs in which they changed.
1284 '''Iterate over files and the revs in which they changed.
1278
1285
1279 Callers most commonly need to iterate backwards over the history
1286 Callers most commonly need to iterate backwards over the history
1280 in which they are interested. Doing so has awful (quadratic-looking)
1287 in which they are interested. Doing so has awful (quadratic-looking)
1281 performance, so we use iterators in a "windowed" way.
1288 performance, so we use iterators in a "windowed" way.
1282
1289
1283 We walk a window of revisions in the desired order. Within the
1290 We walk a window of revisions in the desired order. Within the
1284 window, we first walk forwards to gather data, then in the desired
1291 window, we first walk forwards to gather data, then in the desired
1285 order (usually backwards) to display it.
1292 order (usually backwards) to display it.
1286
1293
1287 This function returns an iterator yielding contexts. Before
1294 This function returns an iterator yielding contexts. Before
1288 yielding each context, the iterator will first call the prepare
1295 yielding each context, the iterator will first call the prepare
1289 function on each context in the window in forward order.'''
1296 function on each context in the window in forward order.'''
1290
1297
1291 follow = opts.get('follow') or opts.get('follow_first')
1298 follow = opts.get('follow') or opts.get('follow_first')
1292
1299
1293 if opts.get('rev'):
1300 if opts.get('rev'):
1294 revs = scmutil.revrange(repo, opts.get('rev'))
1301 revs = scmutil.revrange(repo, opts.get('rev'))
1295 elif follow:
1302 elif follow:
1296 revs = repo.revs('reverse(:.)')
1303 revs = repo.revs('reverse(:.)')
1297 else:
1304 else:
1298 revs = revset.spanset(repo)
1305 revs = revset.spanset(repo)
1299 revs.reverse()
1306 revs.reverse()
1300 if not revs:
1307 if not revs:
1301 return []
1308 return []
1302 wanted = set()
1309 wanted = set()
1303 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1310 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1304 fncache = {}
1311 fncache = {}
1305 change = repo.changectx
1312 change = repo.changectx
1306
1313
1307 # First step is to fill wanted, the set of revisions that we want to yield.
1314 # First step is to fill wanted, the set of revisions that we want to yield.
1308 # When it does not induce extra cost, we also fill fncache for revisions in
1315 # When it does not induce extra cost, we also fill fncache for revisions in
1309 # wanted: a cache of filenames that were changed (ctx.files()) and that
1316 # wanted: a cache of filenames that were changed (ctx.files()) and that
1310 # match the file filtering conditions.
1317 # match the file filtering conditions.
1311
1318
1312 if not slowpath and not match.files():
1319 if not slowpath and not match.files():
1313 # No files, no patterns. Display all revs.
1320 # No files, no patterns. Display all revs.
1314 wanted = revs
1321 wanted = revs
1315
1322
1316 if not slowpath and match.files():
1323 if not slowpath and match.files():
1317 # We only have to read through the filelog to find wanted revisions
1324 # We only have to read through the filelog to find wanted revisions
1318
1325
1319 try:
1326 try:
1320 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1327 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1321 except FileWalkError:
1328 except FileWalkError:
1322 slowpath = True
1329 slowpath = True
1323
1330
1324 # We decided to fall back to the slowpath because at least one
1331 # We decided to fall back to the slowpath because at least one
1325 # of the paths was not a file. Check to see if at least one of them
1332 # of the paths was not a file. Check to see if at least one of them
1326 # existed in history, otherwise simply return
1333 # existed in history, otherwise simply return
1327 for path in match.files():
1334 for path in match.files():
1328 if path == '.' or path in repo.store:
1335 if path == '.' or path in repo.store:
1329 break
1336 break
1330 else:
1337 else:
1331 return []
1338 return []
1332
1339
1333 if slowpath:
1340 if slowpath:
1334 # We have to read the changelog to match filenames against
1341 # We have to read the changelog to match filenames against
1335 # changed files
1342 # changed files
1336
1343
1337 if follow:
1344 if follow:
1338 raise util.Abort(_('can only follow copies/renames for explicit '
1345 raise util.Abort(_('can only follow copies/renames for explicit '
1339 'filenames'))
1346 'filenames'))
1340
1347
1341 # The slow path checks files modified in every changeset.
1348 # The slow path checks files modified in every changeset.
1342 # This is really slow on large repos, so compute the set lazily.
1349 # This is really slow on large repos, so compute the set lazily.
1343 class lazywantedset(object):
1350 class lazywantedset(object):
1344 def __init__(self):
1351 def __init__(self):
1345 self.set = set()
1352 self.set = set()
1346 self.revs = set(revs)
1353 self.revs = set(revs)
1347
1354
1348 # No need to worry about locality here because it will be accessed
1355 # No need to worry about locality here because it will be accessed
1349 # in the same order as the increasing window below.
1356 # in the same order as the increasing window below.
1350 def __contains__(self, value):
1357 def __contains__(self, value):
1351 if value in self.set:
1358 if value in self.set:
1352 return True
1359 return True
1353 elif not value in self.revs:
1360 elif not value in self.revs:
1354 return False
1361 return False
1355 else:
1362 else:
1356 self.revs.discard(value)
1363 self.revs.discard(value)
1357 ctx = change(value)
1364 ctx = change(value)
1358 matches = filter(match, ctx.files())
1365 matches = filter(match, ctx.files())
1359 if matches:
1366 if matches:
1360 fncache[value] = matches
1367 fncache[value] = matches
1361 self.set.add(value)
1368 self.set.add(value)
1362 return True
1369 return True
1363 return False
1370 return False
1364
1371
1365 def discard(self, value):
1372 def discard(self, value):
1366 self.revs.discard(value)
1373 self.revs.discard(value)
1367 self.set.discard(value)
1374 self.set.discard(value)
1368
1375
1369 wanted = lazywantedset()
1376 wanted = lazywantedset()
1370
1377
1371 class followfilter(object):
1378 class followfilter(object):
1372 def __init__(self, onlyfirst=False):
1379 def __init__(self, onlyfirst=False):
1373 self.startrev = nullrev
1380 self.startrev = nullrev
1374 self.roots = set()
1381 self.roots = set()
1375 self.onlyfirst = onlyfirst
1382 self.onlyfirst = onlyfirst
1376
1383
1377 def match(self, rev):
1384 def match(self, rev):
1378 def realparents(rev):
1385 def realparents(rev):
1379 if self.onlyfirst:
1386 if self.onlyfirst:
1380 return repo.changelog.parentrevs(rev)[0:1]
1387 return repo.changelog.parentrevs(rev)[0:1]
1381 else:
1388 else:
1382 return filter(lambda x: x != nullrev,
1389 return filter(lambda x: x != nullrev,
1383 repo.changelog.parentrevs(rev))
1390 repo.changelog.parentrevs(rev))
1384
1391
1385 if self.startrev == nullrev:
1392 if self.startrev == nullrev:
1386 self.startrev = rev
1393 self.startrev = rev
1387 return True
1394 return True
1388
1395
1389 if rev > self.startrev:
1396 if rev > self.startrev:
1390 # forward: all descendants
1397 # forward: all descendants
1391 if not self.roots:
1398 if not self.roots:
1392 self.roots.add(self.startrev)
1399 self.roots.add(self.startrev)
1393 for parent in realparents(rev):
1400 for parent in realparents(rev):
1394 if parent in self.roots:
1401 if parent in self.roots:
1395 self.roots.add(rev)
1402 self.roots.add(rev)
1396 return True
1403 return True
1397 else:
1404 else:
1398 # backwards: all parents
1405 # backwards: all parents
1399 if not self.roots:
1406 if not self.roots:
1400 self.roots.update(realparents(self.startrev))
1407 self.roots.update(realparents(self.startrev))
1401 if rev in self.roots:
1408 if rev in self.roots:
1402 self.roots.remove(rev)
1409 self.roots.remove(rev)
1403 self.roots.update(realparents(rev))
1410 self.roots.update(realparents(rev))
1404 return True
1411 return True
1405
1412
1406 return False
1413 return False
1407
1414
1408 # it might be worthwhile to do this in the iterator if the rev range
1415 # it might be worthwhile to do this in the iterator if the rev range
1409 # is descending and the prune args are all within that range
1416 # is descending and the prune args are all within that range
1410 for rev in opts.get('prune', ()):
1417 for rev in opts.get('prune', ()):
1411 rev = repo[rev].rev()
1418 rev = repo[rev].rev()
1412 ff = followfilter()
1419 ff = followfilter()
1413 stop = min(revs[0], revs[-1])
1420 stop = min(revs[0], revs[-1])
1414 for x in xrange(rev, stop - 1, -1):
1421 for x in xrange(rev, stop - 1, -1):
1415 if ff.match(x):
1422 if ff.match(x):
1416 wanted = wanted - [x]
1423 wanted = wanted - [x]
1417
1424
1418 # Now that wanted is correctly initialized, we can iterate over the
1425 # Now that wanted is correctly initialized, we can iterate over the
1419 # revision range, yielding only revisions in wanted.
1426 # revision range, yielding only revisions in wanted.
1420 def iterate():
1427 def iterate():
1421 if follow and not match.files():
1428 if follow and not match.files():
1422 ff = followfilter(onlyfirst=opts.get('follow_first'))
1429 ff = followfilter(onlyfirst=opts.get('follow_first'))
1423 def want(rev):
1430 def want(rev):
1424 return ff.match(rev) and rev in wanted
1431 return ff.match(rev) and rev in wanted
1425 else:
1432 else:
1426 def want(rev):
1433 def want(rev):
1427 return rev in wanted
1434 return rev in wanted
1428
1435
1429 it = iter(revs)
1436 it = iter(revs)
1430 stopiteration = False
1437 stopiteration = False
1431 for windowsize in increasingwindows():
1438 for windowsize in increasingwindows():
1432 nrevs = []
1439 nrevs = []
1433 for i in xrange(windowsize):
1440 for i in xrange(windowsize):
1434 try:
1441 try:
1435 rev = it.next()
1442 rev = it.next()
1436 if want(rev):
1443 if want(rev):
1437 nrevs.append(rev)
1444 nrevs.append(rev)
1438 except (StopIteration):
1445 except (StopIteration):
1439 stopiteration = True
1446 stopiteration = True
1440 break
1447 break
1441 for rev in sorted(nrevs):
1448 for rev in sorted(nrevs):
1442 fns = fncache.get(rev)
1449 fns = fncache.get(rev)
1443 ctx = change(rev)
1450 ctx = change(rev)
1444 if not fns:
1451 if not fns:
1445 def fns_generator():
1452 def fns_generator():
1446 for f in ctx.files():
1453 for f in ctx.files():
1447 if match(f):
1454 if match(f):
1448 yield f
1455 yield f
1449 fns = fns_generator()
1456 fns = fns_generator()
1450 prepare(ctx, fns)
1457 prepare(ctx, fns)
1451 for rev in nrevs:
1458 for rev in nrevs:
1452 yield change(rev)
1459 yield change(rev)
1453
1460
1454 if stopiteration:
1461 if stopiteration:
1455 break
1462 break
1456
1463
1457 return iterate()
1464 return iterate()
1458
1465
1459 def _makelogfilematcher(repo, pats, followfirst):
1466 def _makelogfilematcher(repo, pats, followfirst):
1460 # When displaying a revision with --patch --follow FILE, we have
1467 # When displaying a revision with --patch --follow FILE, we have
1461 # to know which file of the revision must be diffed. With
1468 # to know which file of the revision must be diffed. With
1462 # --follow, we want the names of the ancestors of FILE in the
1469 # --follow, we want the names of the ancestors of FILE in the
1463 # revision, stored in "fcache". "fcache" is populated by
1470 # revision, stored in "fcache". "fcache" is populated by
1464 # reproducing the graph traversal already done by --follow revset
1471 # reproducing the graph traversal already done by --follow revset
1465 # and relating linkrevs to file names (which is not "correct" but
1472 # and relating linkrevs to file names (which is not "correct" but
1466 # good enough).
1473 # good enough).
1467 fcache = {}
1474 fcache = {}
1468 fcacheready = [False]
1475 fcacheready = [False]
1469 pctx = repo['.']
1476 pctx = repo['.']
1470 wctx = repo[None]
1477 wctx = repo[None]
1471
1478
1472 def populate():
1479 def populate():
1473 for fn in pats:
1480 for fn in pats:
1474 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1481 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1475 for c in i:
1482 for c in i:
1476 fcache.setdefault(c.linkrev(), set()).add(c.path())
1483 fcache.setdefault(c.linkrev(), set()).add(c.path())
1477
1484
1478 def filematcher(rev):
1485 def filematcher(rev):
1479 if not fcacheready[0]:
1486 if not fcacheready[0]:
1480 # Lazy initialization
1487 # Lazy initialization
1481 fcacheready[0] = True
1488 fcacheready[0] = True
1482 populate()
1489 populate()
1483 return scmutil.match(wctx, fcache.get(rev, []), default='path')
1490 return scmutil.match(wctx, fcache.get(rev, []), default='path')
1484
1491
1485 return filematcher
1492 return filematcher
1486
1493
1487 def _makelogrevset(repo, pats, opts, revs):
1494 def _makelogrevset(repo, pats, opts, revs):
1488 """Return (expr, filematcher) where expr is a revset string built
1495 """Return (expr, filematcher) where expr is a revset string built
1489 from log options and file patterns or None. If --stat or --patch
1496 from log options and file patterns or None. If --stat or --patch
1490 are not passed filematcher is None. Otherwise it is a callable
1497 are not passed filematcher is None. Otherwise it is a callable
1491 taking a revision number and returning a match objects filtering
1498 taking a revision number and returning a match objects filtering
1492 the files to be detailed when displaying the revision.
1499 the files to be detailed when displaying the revision.
1493 """
1500 """
1494 opt2revset = {
1501 opt2revset = {
1495 'no_merges': ('not merge()', None),
1502 'no_merges': ('not merge()', None),
1496 'only_merges': ('merge()', None),
1503 'only_merges': ('merge()', None),
1497 '_ancestors': ('ancestors(%(val)s)', None),
1504 '_ancestors': ('ancestors(%(val)s)', None),
1498 '_fancestors': ('_firstancestors(%(val)s)', None),
1505 '_fancestors': ('_firstancestors(%(val)s)', None),
1499 '_descendants': ('descendants(%(val)s)', None),
1506 '_descendants': ('descendants(%(val)s)', None),
1500 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1507 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1501 '_matchfiles': ('_matchfiles(%(val)s)', None),
1508 '_matchfiles': ('_matchfiles(%(val)s)', None),
1502 'date': ('date(%(val)r)', None),
1509 'date': ('date(%(val)r)', None),
1503 'branch': ('branch(%(val)r)', ' or '),
1510 'branch': ('branch(%(val)r)', ' or '),
1504 '_patslog': ('filelog(%(val)r)', ' or '),
1511 '_patslog': ('filelog(%(val)r)', ' or '),
1505 '_patsfollow': ('follow(%(val)r)', ' or '),
1512 '_patsfollow': ('follow(%(val)r)', ' or '),
1506 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1513 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1507 'keyword': ('keyword(%(val)r)', ' or '),
1514 'keyword': ('keyword(%(val)r)', ' or '),
1508 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1515 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1509 'user': ('user(%(val)r)', ' or '),
1516 'user': ('user(%(val)r)', ' or '),
1510 }
1517 }
1511
1518
1512 opts = dict(opts)
1519 opts = dict(opts)
1513 # follow or not follow?
1520 # follow or not follow?
1514 follow = opts.get('follow') or opts.get('follow_first')
1521 follow = opts.get('follow') or opts.get('follow_first')
1515 followfirst = opts.get('follow_first') and 1 or 0
1522 followfirst = opts.get('follow_first') and 1 or 0
1516 # --follow with FILE behaviour depends on revs...
1523 # --follow with FILE behaviour depends on revs...
1517 it = iter(revs)
1524 it = iter(revs)
1518 startrev = it.next()
1525 startrev = it.next()
1519 try:
1526 try:
1520 followdescendants = startrev < it.next()
1527 followdescendants = startrev < it.next()
1521 except (StopIteration):
1528 except (StopIteration):
1522 followdescendants = False
1529 followdescendants = False
1523
1530
1524 # branch and only_branch are really aliases and must be handled at
1531 # branch and only_branch are really aliases and must be handled at
1525 # the same time
1532 # the same time
1526 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1533 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1527 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1534 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1528 # pats/include/exclude are passed to match.match() directly in
1535 # pats/include/exclude are passed to match.match() directly in
1529 # _matchfiles() revset but walkchangerevs() builds its matcher with
1536 # _matchfiles() revset but walkchangerevs() builds its matcher with
1530 # scmutil.match(). The difference is input pats are globbed on
1537 # scmutil.match(). The difference is input pats are globbed on
1531 # platforms without shell expansion (windows).
1538 # platforms without shell expansion (windows).
1532 pctx = repo[None]
1539 pctx = repo[None]
1533 match, pats = scmutil.matchandpats(pctx, pats, opts)
1540 match, pats = scmutil.matchandpats(pctx, pats, opts)
1534 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1541 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1535 if not slowpath:
1542 if not slowpath:
1536 for f in match.files():
1543 for f in match.files():
1537 if follow and f not in pctx:
1544 if follow and f not in pctx:
1538 raise util.Abort(_('cannot follow file not in parent '
1545 raise util.Abort(_('cannot follow file not in parent '
1539 'revision: "%s"') % f)
1546 'revision: "%s"') % f)
1540 filelog = repo.file(f)
1547 filelog = repo.file(f)
1541 if not filelog:
1548 if not filelog:
1542 # A zero count may be a directory or deleted file, so
1549 # A zero count may be a directory or deleted file, so
1543 # try to find matching entries on the slow path.
1550 # try to find matching entries on the slow path.
1544 if follow:
1551 if follow:
1545 raise util.Abort(
1552 raise util.Abort(
1546 _('cannot follow nonexistent file: "%s"') % f)
1553 _('cannot follow nonexistent file: "%s"') % f)
1547 slowpath = True
1554 slowpath = True
1548
1555
1549 # We decided to fall back to the slowpath because at least one
1556 # We decided to fall back to the slowpath because at least one
1550 # of the paths was not a file. Check to see if at least one of them
1557 # of the paths was not a file. Check to see if at least one of them
1551 # existed in history - in that case, we'll continue down the
1558 # existed in history - in that case, we'll continue down the
1552 # slowpath; otherwise, we can turn off the slowpath
1559 # slowpath; otherwise, we can turn off the slowpath
1553 if slowpath:
1560 if slowpath:
1554 for path in match.files():
1561 for path in match.files():
1555 if path == '.' or path in repo.store:
1562 if path == '.' or path in repo.store:
1556 break
1563 break
1557 else:
1564 else:
1558 slowpath = False
1565 slowpath = False
1559
1566
1560 if slowpath:
1567 if slowpath:
1561 # See walkchangerevs() slow path.
1568 # See walkchangerevs() slow path.
1562 #
1569 #
1563 if follow:
1570 if follow:
1564 raise util.Abort(_('can only follow copies/renames for explicit '
1571 raise util.Abort(_('can only follow copies/renames for explicit '
1565 'filenames'))
1572 'filenames'))
1566 # pats/include/exclude cannot be represented as separate
1573 # pats/include/exclude cannot be represented as separate
1567 # revset expressions as their filtering logic applies at file
1574 # revset expressions as their filtering logic applies at file
1568 # level. For instance "-I a -X a" matches a revision touching
1575 # level. For instance "-I a -X a" matches a revision touching
1569 # "a" and "b" while "file(a) and not file(b)" does
1576 # "a" and "b" while "file(a) and not file(b)" does
1570 # not. Besides, filesets are evaluated against the working
1577 # not. Besides, filesets are evaluated against the working
1571 # directory.
1578 # directory.
1572 matchargs = ['r:', 'd:relpath']
1579 matchargs = ['r:', 'd:relpath']
1573 for p in pats:
1580 for p in pats:
1574 matchargs.append('p:' + p)
1581 matchargs.append('p:' + p)
1575 for p in opts.get('include', []):
1582 for p in opts.get('include', []):
1576 matchargs.append('i:' + p)
1583 matchargs.append('i:' + p)
1577 for p in opts.get('exclude', []):
1584 for p in opts.get('exclude', []):
1578 matchargs.append('x:' + p)
1585 matchargs.append('x:' + p)
1579 matchargs = ','.join(('%r' % p) for p in matchargs)
1586 matchargs = ','.join(('%r' % p) for p in matchargs)
1580 opts['_matchfiles'] = matchargs
1587 opts['_matchfiles'] = matchargs
1581 else:
1588 else:
1582 if follow:
1589 if follow:
1583 fpats = ('_patsfollow', '_patsfollowfirst')
1590 fpats = ('_patsfollow', '_patsfollowfirst')
1584 fnopats = (('_ancestors', '_fancestors'),
1591 fnopats = (('_ancestors', '_fancestors'),
1585 ('_descendants', '_fdescendants'))
1592 ('_descendants', '_fdescendants'))
1586 if pats:
1593 if pats:
1587 # follow() revset interprets its file argument as a
1594 # follow() revset interprets its file argument as a
1588 # manifest entry, so use match.files(), not pats.
1595 # manifest entry, so use match.files(), not pats.
1589 opts[fpats[followfirst]] = list(match.files())
1596 opts[fpats[followfirst]] = list(match.files())
1590 else:
1597 else:
1591 opts[fnopats[followdescendants][followfirst]] = str(startrev)
1598 opts[fnopats[followdescendants][followfirst]] = str(startrev)
1592 else:
1599 else:
1593 opts['_patslog'] = list(pats)
1600 opts['_patslog'] = list(pats)
1594
1601
1595 filematcher = None
1602 filematcher = None
1596 if opts.get('patch') or opts.get('stat'):
1603 if opts.get('patch') or opts.get('stat'):
1597 if follow:
1604 if follow:
1598 filematcher = _makelogfilematcher(repo, pats, followfirst)
1605 filematcher = _makelogfilematcher(repo, pats, followfirst)
1599 else:
1606 else:
1600 filematcher = lambda rev: match
1607 filematcher = lambda rev: match
1601
1608
1602 expr = []
1609 expr = []
1603 for op, val in opts.iteritems():
1610 for op, val in opts.iteritems():
1604 if not val:
1611 if not val:
1605 continue
1612 continue
1606 if op not in opt2revset:
1613 if op not in opt2revset:
1607 continue
1614 continue
1608 revop, andor = opt2revset[op]
1615 revop, andor = opt2revset[op]
1609 if '%(val)' not in revop:
1616 if '%(val)' not in revop:
1610 expr.append(revop)
1617 expr.append(revop)
1611 else:
1618 else:
1612 if not isinstance(val, list):
1619 if not isinstance(val, list):
1613 e = revop % {'val': val}
1620 e = revop % {'val': val}
1614 else:
1621 else:
1615 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
1622 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
1616 expr.append(e)
1623 expr.append(e)
1617
1624
1618 if expr:
1625 if expr:
1619 expr = '(' + ' and '.join(expr) + ')'
1626 expr = '(' + ' and '.join(expr) + ')'
1620 else:
1627 else:
1621 expr = None
1628 expr = None
1622 return expr, filematcher
1629 return expr, filematcher
1623
1630
1624 def getgraphlogrevs(repo, pats, opts):
1631 def getgraphlogrevs(repo, pats, opts):
1625 """Return (revs, expr, filematcher) where revs is an iterable of
1632 """Return (revs, expr, filematcher) where revs is an iterable of
1626 revision numbers, expr is a revset string built from log options
1633 revision numbers, expr is a revset string built from log options
1627 and file patterns or None, and used to filter 'revs'. If --stat or
1634 and file patterns or None, and used to filter 'revs'. If --stat or
1628 --patch are not passed filematcher is None. Otherwise it is a
1635 --patch are not passed filematcher is None. Otherwise it is a
1629 callable taking a revision number and returning a match objects
1636 callable taking a revision number and returning a match objects
1630 filtering the files to be detailed when displaying the revision.
1637 filtering the files to be detailed when displaying the revision.
1631 """
1638 """
1632 if not len(repo):
1639 if not len(repo):
1633 return [], None, None
1640 return [], None, None
1634 limit = loglimit(opts)
1641 limit = loglimit(opts)
1635 # Default --rev value depends on --follow but --follow behaviour
1642 # Default --rev value depends on --follow but --follow behaviour
1636 # depends on revisions resolved from --rev...
1643 # depends on revisions resolved from --rev...
1637 follow = opts.get('follow') or opts.get('follow_first')
1644 follow = opts.get('follow') or opts.get('follow_first')
1638 possiblyunsorted = False # whether revs might need sorting
1645 possiblyunsorted = False # whether revs might need sorting
1639 if opts.get('rev'):
1646 if opts.get('rev'):
1640 revs = scmutil.revrange(repo, opts['rev'])
1647 revs = scmutil.revrange(repo, opts['rev'])
1641 # Don't sort here because _makelogrevset might depend on the
1648 # Don't sort here because _makelogrevset might depend on the
1642 # order of revs
1649 # order of revs
1643 possiblyunsorted = True
1650 possiblyunsorted = True
1644 else:
1651 else:
1645 if follow and len(repo) > 0:
1652 if follow and len(repo) > 0:
1646 revs = repo.revs('reverse(:.)')
1653 revs = repo.revs('reverse(:.)')
1647 else:
1654 else:
1648 revs = revset.spanset(repo)
1655 revs = revset.spanset(repo)
1649 revs.reverse()
1656 revs.reverse()
1650 if not revs:
1657 if not revs:
1651 return revset.baseset(), None, None
1658 return revset.baseset(), None, None
1652 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
1659 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
1653 if possiblyunsorted:
1660 if possiblyunsorted:
1654 revs.sort(reverse=True)
1661 revs.sort(reverse=True)
1655 if expr:
1662 if expr:
1656 # Revset matchers often operate faster on revisions in changelog
1663 # Revset matchers often operate faster on revisions in changelog
1657 # order, because most filters deal with the changelog.
1664 # order, because most filters deal with the changelog.
1658 revs.reverse()
1665 revs.reverse()
1659 matcher = revset.match(repo.ui, expr)
1666 matcher = revset.match(repo.ui, expr)
1660 # Revset matches can reorder revisions. "A or B" typically returns
1667 # Revset matches can reorder revisions. "A or B" typically returns
1661 # returns the revision matching A then the revision matching B. Sort
1668 # returns the revision matching A then the revision matching B. Sort
1662 # again to fix that.
1669 # again to fix that.
1663 revs = matcher(repo, revs)
1670 revs = matcher(repo, revs)
1664 revs.sort(reverse=True)
1671 revs.sort(reverse=True)
1665 if limit is not None:
1672 if limit is not None:
1666 limitedrevs = revset.baseset()
1673 limitedrevs = revset.baseset()
1667 for idx, rev in enumerate(revs):
1674 for idx, rev in enumerate(revs):
1668 if idx >= limit:
1675 if idx >= limit:
1669 break
1676 break
1670 limitedrevs.append(rev)
1677 limitedrevs.append(rev)
1671 revs = limitedrevs
1678 revs = limitedrevs
1672
1679
1673 return revs, expr, filematcher
1680 return revs, expr, filematcher
1674
1681
1675 def getlogrevs(repo, pats, opts):
1682 def getlogrevs(repo, pats, opts):
1676 """Return (revs, expr, filematcher) where revs is an iterable of
1683 """Return (revs, expr, filematcher) where revs is an iterable of
1677 revision numbers, expr is a revset string built from log options
1684 revision numbers, expr is a revset string built from log options
1678 and file patterns or None, and used to filter 'revs'. If --stat or
1685 and file patterns or None, and used to filter 'revs'. If --stat or
1679 --patch are not passed filematcher is None. Otherwise it is a
1686 --patch are not passed filematcher is None. Otherwise it is a
1680 callable taking a revision number and returning a match objects
1687 callable taking a revision number and returning a match objects
1681 filtering the files to be detailed when displaying the revision.
1688 filtering the files to be detailed when displaying the revision.
1682 """
1689 """
1683 limit = loglimit(opts)
1690 limit = loglimit(opts)
1684 # Default --rev value depends on --follow but --follow behaviour
1691 # Default --rev value depends on --follow but --follow behaviour
1685 # depends on revisions resolved from --rev...
1692 # depends on revisions resolved from --rev...
1686 follow = opts.get('follow') or opts.get('follow_first')
1693 follow = opts.get('follow') or opts.get('follow_first')
1687 if opts.get('rev'):
1694 if opts.get('rev'):
1688 revs = scmutil.revrange(repo, opts['rev'])
1695 revs = scmutil.revrange(repo, opts['rev'])
1689 elif follow:
1696 elif follow:
1690 revs = revset.baseset(repo.revs('reverse(:.)'))
1697 revs = revset.baseset(repo.revs('reverse(:.)'))
1691 else:
1698 else:
1692 revs = revset.spanset(repo)
1699 revs = revset.spanset(repo)
1693 revs.reverse()
1700 revs.reverse()
1694 if not revs:
1701 if not revs:
1695 return revset.baseset([]), None, None
1702 return revset.baseset([]), None, None
1696 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
1703 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
1697 if expr:
1704 if expr:
1698 # Revset matchers often operate faster on revisions in changelog
1705 # Revset matchers often operate faster on revisions in changelog
1699 # order, because most filters deal with the changelog.
1706 # order, because most filters deal with the changelog.
1700 if not opts.get('rev'):
1707 if not opts.get('rev'):
1701 revs.reverse()
1708 revs.reverse()
1702 matcher = revset.match(repo.ui, expr)
1709 matcher = revset.match(repo.ui, expr)
1703 # Revset matches can reorder revisions. "A or B" typically returns
1710 # Revset matches can reorder revisions. "A or B" typically returns
1704 # returns the revision matching A then the revision matching B. Sort
1711 # returns the revision matching A then the revision matching B. Sort
1705 # again to fix that.
1712 # again to fix that.
1706 revs = matcher(repo, revs)
1713 revs = matcher(repo, revs)
1707 if not opts.get('rev'):
1714 if not opts.get('rev'):
1708 revs.sort(reverse=True)
1715 revs.sort(reverse=True)
1709 if limit is not None:
1716 if limit is not None:
1710 count = 0
1717 count = 0
1711 limitedrevs = revset.baseset([])
1718 limitedrevs = revset.baseset([])
1712 it = iter(revs)
1719 it = iter(revs)
1713 while count < limit:
1720 while count < limit:
1714 try:
1721 try:
1715 limitedrevs.append(it.next())
1722 limitedrevs.append(it.next())
1716 except (StopIteration):
1723 except (StopIteration):
1717 break
1724 break
1718 count += 1
1725 count += 1
1719 revs = limitedrevs
1726 revs = limitedrevs
1720
1727
1721 return revs, expr, filematcher
1728 return revs, expr, filematcher
1722
1729
1723 def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
1730 def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
1724 filematcher=None):
1731 filematcher=None):
1725 seen, state = [], graphmod.asciistate()
1732 seen, state = [], graphmod.asciistate()
1726 for rev, type, ctx, parents in dag:
1733 for rev, type, ctx, parents in dag:
1727 char = 'o'
1734 char = 'o'
1728 if ctx.node() in showparents:
1735 if ctx.node() in showparents:
1729 char = '@'
1736 char = '@'
1730 elif ctx.obsolete():
1737 elif ctx.obsolete():
1731 char = 'x'
1738 char = 'x'
1732 copies = None
1739 copies = None
1733 if getrenamed and ctx.rev():
1740 if getrenamed and ctx.rev():
1734 copies = []
1741 copies = []
1735 for fn in ctx.files():
1742 for fn in ctx.files():
1736 rename = getrenamed(fn, ctx.rev())
1743 rename = getrenamed(fn, ctx.rev())
1737 if rename:
1744 if rename:
1738 copies.append((fn, rename[0]))
1745 copies.append((fn, rename[0]))
1739 revmatchfn = None
1746 revmatchfn = None
1740 if filematcher is not None:
1747 if filematcher is not None:
1741 revmatchfn = filematcher(ctx.rev())
1748 revmatchfn = filematcher(ctx.rev())
1742 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
1749 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
1743 lines = displayer.hunk.pop(rev).split('\n')
1750 lines = displayer.hunk.pop(rev).split('\n')
1744 if not lines[-1]:
1751 if not lines[-1]:
1745 del lines[-1]
1752 del lines[-1]
1746 displayer.flush(rev)
1753 displayer.flush(rev)
1747 edges = edgefn(type, char, lines, seen, rev, parents)
1754 edges = edgefn(type, char, lines, seen, rev, parents)
1748 for type, char, lines, coldata in edges:
1755 for type, char, lines, coldata in edges:
1749 graphmod.ascii(ui, state, type, char, lines, coldata)
1756 graphmod.ascii(ui, state, type, char, lines, coldata)
1750 displayer.close()
1757 displayer.close()
1751
1758
1752 def graphlog(ui, repo, *pats, **opts):
1759 def graphlog(ui, repo, *pats, **opts):
1753 # Parameters are identical to log command ones
1760 # Parameters are identical to log command ones
1754 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
1761 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
1755 revdag = graphmod.dagwalker(repo, revs)
1762 revdag = graphmod.dagwalker(repo, revs)
1756
1763
1757 getrenamed = None
1764 getrenamed = None
1758 if opts.get('copies'):
1765 if opts.get('copies'):
1759 endrev = None
1766 endrev = None
1760 if opts.get('rev'):
1767 if opts.get('rev'):
1761 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
1768 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
1762 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
1769 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
1763 displayer = show_changeset(ui, repo, opts, buffered=True)
1770 displayer = show_changeset(ui, repo, opts, buffered=True)
1764 showparents = [ctx.node() for ctx in repo[None].parents()]
1771 showparents = [ctx.node() for ctx in repo[None].parents()]
1765 displaygraph(ui, revdag, displayer, showparents,
1772 displaygraph(ui, revdag, displayer, showparents,
1766 graphmod.asciiedges, getrenamed, filematcher)
1773 graphmod.asciiedges, getrenamed, filematcher)
1767
1774
1768 def checkunsupportedgraphflags(pats, opts):
1775 def checkunsupportedgraphflags(pats, opts):
1769 for op in ["newest_first"]:
1776 for op in ["newest_first"]:
1770 if op in opts and opts[op]:
1777 if op in opts and opts[op]:
1771 raise util.Abort(_("-G/--graph option is incompatible with --%s")
1778 raise util.Abort(_("-G/--graph option is incompatible with --%s")
1772 % op.replace("_", "-"))
1779 % op.replace("_", "-"))
1773
1780
1774 def graphrevs(repo, nodes, opts):
1781 def graphrevs(repo, nodes, opts):
1775 limit = loglimit(opts)
1782 limit = loglimit(opts)
1776 nodes.reverse()
1783 nodes.reverse()
1777 if limit is not None:
1784 if limit is not None:
1778 nodes = nodes[:limit]
1785 nodes = nodes[:limit]
1779 return graphmod.nodes(repo, nodes)
1786 return graphmod.nodes(repo, nodes)
1780
1787
1781 def add(ui, repo, match, dryrun, listsubrepos, prefix, explicitonly):
1788 def add(ui, repo, match, dryrun, listsubrepos, prefix, explicitonly):
1782 join = lambda f: os.path.join(prefix, f)
1789 join = lambda f: os.path.join(prefix, f)
1783 bad = []
1790 bad = []
1784 oldbad = match.bad
1791 oldbad = match.bad
1785 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1792 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1786 names = []
1793 names = []
1787 wctx = repo[None]
1794 wctx = repo[None]
1788 cca = None
1795 cca = None
1789 abort, warn = scmutil.checkportabilityalert(ui)
1796 abort, warn = scmutil.checkportabilityalert(ui)
1790 if abort or warn:
1797 if abort or warn:
1791 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
1798 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
1792 for f in repo.walk(match):
1799 for f in repo.walk(match):
1793 exact = match.exact(f)
1800 exact = match.exact(f)
1794 if exact or not explicitonly and f not in repo.dirstate:
1801 if exact or not explicitonly and f not in repo.dirstate:
1795 if cca:
1802 if cca:
1796 cca(f)
1803 cca(f)
1797 names.append(f)
1804 names.append(f)
1798 if ui.verbose or not exact:
1805 if ui.verbose or not exact:
1799 ui.status(_('adding %s\n') % match.rel(join(f)))
1806 ui.status(_('adding %s\n') % match.rel(join(f)))
1800
1807
1801 for subpath in sorted(wctx.substate):
1808 for subpath in sorted(wctx.substate):
1802 sub = wctx.sub(subpath)
1809 sub = wctx.sub(subpath)
1803 try:
1810 try:
1804 submatch = matchmod.narrowmatcher(subpath, match)
1811 submatch = matchmod.narrowmatcher(subpath, match)
1805 if listsubrepos:
1812 if listsubrepos:
1806 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1813 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1807 False))
1814 False))
1808 else:
1815 else:
1809 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1816 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1810 True))
1817 True))
1811 except error.LookupError:
1818 except error.LookupError:
1812 ui.status(_("skipping missing subrepository: %s\n")
1819 ui.status(_("skipping missing subrepository: %s\n")
1813 % join(subpath))
1820 % join(subpath))
1814
1821
1815 if not dryrun:
1822 if not dryrun:
1816 rejected = wctx.add(names, prefix)
1823 rejected = wctx.add(names, prefix)
1817 bad.extend(f for f in rejected if f in match.files())
1824 bad.extend(f for f in rejected if f in match.files())
1818 return bad
1825 return bad
1819
1826
1820 def forget(ui, repo, match, prefix, explicitonly):
1827 def forget(ui, repo, match, prefix, explicitonly):
1821 join = lambda f: os.path.join(prefix, f)
1828 join = lambda f: os.path.join(prefix, f)
1822 bad = []
1829 bad = []
1823 oldbad = match.bad
1830 oldbad = match.bad
1824 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1831 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1825 wctx = repo[None]
1832 wctx = repo[None]
1826 forgot = []
1833 forgot = []
1827 s = repo.status(match=match, clean=True)
1834 s = repo.status(match=match, clean=True)
1828 forget = sorted(s[0] + s[1] + s[3] + s[6])
1835 forget = sorted(s[0] + s[1] + s[3] + s[6])
1829 if explicitonly:
1836 if explicitonly:
1830 forget = [f for f in forget if match.exact(f)]
1837 forget = [f for f in forget if match.exact(f)]
1831
1838
1832 for subpath in sorted(wctx.substate):
1839 for subpath in sorted(wctx.substate):
1833 sub = wctx.sub(subpath)
1840 sub = wctx.sub(subpath)
1834 try:
1841 try:
1835 submatch = matchmod.narrowmatcher(subpath, match)
1842 submatch = matchmod.narrowmatcher(subpath, match)
1836 subbad, subforgot = sub.forget(ui, submatch, prefix)
1843 subbad, subforgot = sub.forget(ui, submatch, prefix)
1837 bad.extend([subpath + '/' + f for f in subbad])
1844 bad.extend([subpath + '/' + f for f in subbad])
1838 forgot.extend([subpath + '/' + f for f in subforgot])
1845 forgot.extend([subpath + '/' + f for f in subforgot])
1839 except error.LookupError:
1846 except error.LookupError:
1840 ui.status(_("skipping missing subrepository: %s\n")
1847 ui.status(_("skipping missing subrepository: %s\n")
1841 % join(subpath))
1848 % join(subpath))
1842
1849
1843 if not explicitonly:
1850 if not explicitonly:
1844 for f in match.files():
1851 for f in match.files():
1845 if f not in repo.dirstate and not os.path.isdir(match.rel(join(f))):
1852 if f not in repo.dirstate and not os.path.isdir(match.rel(join(f))):
1846 if f not in forgot:
1853 if f not in forgot:
1847 if os.path.exists(match.rel(join(f))):
1854 if os.path.exists(match.rel(join(f))):
1848 ui.warn(_('not removing %s: '
1855 ui.warn(_('not removing %s: '
1849 'file is already untracked\n')
1856 'file is already untracked\n')
1850 % match.rel(join(f)))
1857 % match.rel(join(f)))
1851 bad.append(f)
1858 bad.append(f)
1852
1859
1853 for f in forget:
1860 for f in forget:
1854 if ui.verbose or not match.exact(f):
1861 if ui.verbose or not match.exact(f):
1855 ui.status(_('removing %s\n') % match.rel(join(f)))
1862 ui.status(_('removing %s\n') % match.rel(join(f)))
1856
1863
1857 rejected = wctx.forget(forget, prefix)
1864 rejected = wctx.forget(forget, prefix)
1858 bad.extend(f for f in rejected if f in match.files())
1865 bad.extend(f for f in rejected if f in match.files())
1859 forgot.extend(forget)
1866 forgot.extend(forget)
1860 return bad, forgot
1867 return bad, forgot
1861
1868
1862 def cat(ui, repo, ctx, matcher, prefix, **opts):
1869 def cat(ui, repo, ctx, matcher, prefix, **opts):
1863 err = 1
1870 err = 1
1864
1871
1865 def write(path):
1872 def write(path):
1866 fp = makefileobj(repo, opts.get('output'), ctx.node(),
1873 fp = makefileobj(repo, opts.get('output'), ctx.node(),
1867 pathname=os.path.join(prefix, path))
1874 pathname=os.path.join(prefix, path))
1868 data = ctx[path].data()
1875 data = ctx[path].data()
1869 if opts.get('decode'):
1876 if opts.get('decode'):
1870 data = repo.wwritedata(path, data)
1877 data = repo.wwritedata(path, data)
1871 fp.write(data)
1878 fp.write(data)
1872 fp.close()
1879 fp.close()
1873
1880
1874 # Automation often uses hg cat on single files, so special case it
1881 # Automation often uses hg cat on single files, so special case it
1875 # for performance to avoid the cost of parsing the manifest.
1882 # for performance to avoid the cost of parsing the manifest.
1876 if len(matcher.files()) == 1 and not matcher.anypats():
1883 if len(matcher.files()) == 1 and not matcher.anypats():
1877 file = matcher.files()[0]
1884 file = matcher.files()[0]
1878 mf = repo.manifest
1885 mf = repo.manifest
1879 mfnode = ctx._changeset[0]
1886 mfnode = ctx._changeset[0]
1880 if mf.find(mfnode, file)[0]:
1887 if mf.find(mfnode, file)[0]:
1881 write(file)
1888 write(file)
1882 return 0
1889 return 0
1883
1890
1884 # Don't warn about "missing" files that are really in subrepos
1891 # Don't warn about "missing" files that are really in subrepos
1885 bad = matcher.bad
1892 bad = matcher.bad
1886
1893
1887 def badfn(path, msg):
1894 def badfn(path, msg):
1888 for subpath in ctx.substate:
1895 for subpath in ctx.substate:
1889 if path.startswith(subpath):
1896 if path.startswith(subpath):
1890 return
1897 return
1891 bad(path, msg)
1898 bad(path, msg)
1892
1899
1893 matcher.bad = badfn
1900 matcher.bad = badfn
1894
1901
1895 for abs in ctx.walk(matcher):
1902 for abs in ctx.walk(matcher):
1896 write(abs)
1903 write(abs)
1897 err = 0
1904 err = 0
1898
1905
1899 matcher.bad = bad
1906 matcher.bad = bad
1900
1907
1901 for subpath in sorted(ctx.substate):
1908 for subpath in sorted(ctx.substate):
1902 sub = ctx.sub(subpath)
1909 sub = ctx.sub(subpath)
1903 try:
1910 try:
1904 submatch = matchmod.narrowmatcher(subpath, matcher)
1911 submatch = matchmod.narrowmatcher(subpath, matcher)
1905
1912
1906 if not sub.cat(ui, submatch, os.path.join(prefix, sub._path),
1913 if not sub.cat(ui, submatch, os.path.join(prefix, sub._path),
1907 **opts):
1914 **opts):
1908 err = 0
1915 err = 0
1909 except error.RepoLookupError:
1916 except error.RepoLookupError:
1910 ui.status(_("skipping missing subrepository: %s\n")
1917 ui.status(_("skipping missing subrepository: %s\n")
1911 % os.path.join(prefix, subpath))
1918 % os.path.join(prefix, subpath))
1912
1919
1913 return err
1920 return err
1914
1921
1915 def duplicatecopies(repo, rev, fromrev):
1922 def duplicatecopies(repo, rev, fromrev):
1916 '''reproduce copies from fromrev to rev in the dirstate'''
1923 '''reproduce copies from fromrev to rev in the dirstate'''
1917 for dst, src in copies.pathcopies(repo[fromrev], repo[rev]).iteritems():
1924 for dst, src in copies.pathcopies(repo[fromrev], repo[rev]).iteritems():
1918 # copies.pathcopies returns backward renames, so dst might not
1925 # copies.pathcopies returns backward renames, so dst might not
1919 # actually be in the dirstate
1926 # actually be in the dirstate
1920 if repo.dirstate[dst] in "nma":
1927 if repo.dirstate[dst] in "nma":
1921 repo.dirstate.copy(src, dst)
1928 repo.dirstate.copy(src, dst)
1922
1929
1923 def commit(ui, repo, commitfunc, pats, opts):
1930 def commit(ui, repo, commitfunc, pats, opts):
1924 '''commit the specified files or all outstanding changes'''
1931 '''commit the specified files or all outstanding changes'''
1925 date = opts.get('date')
1932 date = opts.get('date')
1926 if date:
1933 if date:
1927 opts['date'] = util.parsedate(date)
1934 opts['date'] = util.parsedate(date)
1928 message = logmessage(ui, opts)
1935 message = logmessage(ui, opts)
1929
1936
1930 # extract addremove carefully -- this function can be called from a command
1937 # extract addremove carefully -- this function can be called from a command
1931 # that doesn't support addremove
1938 # that doesn't support addremove
1932 if opts.get('addremove'):
1939 if opts.get('addremove'):
1933 scmutil.addremove(repo, pats, opts)
1940 scmutil.addremove(repo, pats, opts)
1934
1941
1935 return commitfunc(ui, repo, message,
1942 return commitfunc(ui, repo, message,
1936 scmutil.match(repo[None], pats, opts), opts)
1943 scmutil.match(repo[None], pats, opts), opts)
1937
1944
1938 def amend(ui, repo, commitfunc, old, extra, pats, opts):
1945 def amend(ui, repo, commitfunc, old, extra, pats, opts):
1939 ui.note(_('amending changeset %s\n') % old)
1946 ui.note(_('amending changeset %s\n') % old)
1940 base = old.p1()
1947 base = old.p1()
1941
1948
1942 wlock = lock = newid = None
1949 wlock = lock = newid = None
1943 try:
1950 try:
1944 wlock = repo.wlock()
1951 wlock = repo.wlock()
1945 lock = repo.lock()
1952 lock = repo.lock()
1946 tr = repo.transaction('amend')
1953 tr = repo.transaction('amend')
1947 try:
1954 try:
1948 # See if we got a message from -m or -l, if not, open the editor
1955 # See if we got a message from -m or -l, if not, open the editor
1949 # with the message of the changeset to amend
1956 # with the message of the changeset to amend
1950 message = logmessage(ui, opts)
1957 message = logmessage(ui, opts)
1951 # ensure logfile does not conflict with later enforcement of the
1958 # ensure logfile does not conflict with later enforcement of the
1952 # message. potential logfile content has been processed by
1959 # message. potential logfile content has been processed by
1953 # `logmessage` anyway.
1960 # `logmessage` anyway.
1954 opts.pop('logfile')
1961 opts.pop('logfile')
1955 # First, do a regular commit to record all changes in the working
1962 # First, do a regular commit to record all changes in the working
1956 # directory (if there are any)
1963 # directory (if there are any)
1957 ui.callhooks = False
1964 ui.callhooks = False
1958 currentbookmark = repo._bookmarkcurrent
1965 currentbookmark = repo._bookmarkcurrent
1959 try:
1966 try:
1960 repo._bookmarkcurrent = None
1967 repo._bookmarkcurrent = None
1961 opts['message'] = 'temporary amend commit for %s' % old
1968 opts['message'] = 'temporary amend commit for %s' % old
1962 node = commit(ui, repo, commitfunc, pats, opts)
1969 node = commit(ui, repo, commitfunc, pats, opts)
1963 finally:
1970 finally:
1964 repo._bookmarkcurrent = currentbookmark
1971 repo._bookmarkcurrent = currentbookmark
1965 ui.callhooks = True
1972 ui.callhooks = True
1966 ctx = repo[node]
1973 ctx = repo[node]
1967
1974
1968 # Participating changesets:
1975 # Participating changesets:
1969 #
1976 #
1970 # node/ctx o - new (intermediate) commit that contains changes
1977 # node/ctx o - new (intermediate) commit that contains changes
1971 # | from working dir to go into amending commit
1978 # | from working dir to go into amending commit
1972 # | (or a workingctx if there were no changes)
1979 # | (or a workingctx if there were no changes)
1973 # |
1980 # |
1974 # old o - changeset to amend
1981 # old o - changeset to amend
1975 # |
1982 # |
1976 # base o - parent of amending changeset
1983 # base o - parent of amending changeset
1977
1984
1978 # Update extra dict from amended commit (e.g. to preserve graft
1985 # Update extra dict from amended commit (e.g. to preserve graft
1979 # source)
1986 # source)
1980 extra.update(old.extra())
1987 extra.update(old.extra())
1981
1988
1982 # Also update it from the intermediate commit or from the wctx
1989 # Also update it from the intermediate commit or from the wctx
1983 extra.update(ctx.extra())
1990 extra.update(ctx.extra())
1984
1991
1985 if len(old.parents()) > 1:
1992 if len(old.parents()) > 1:
1986 # ctx.files() isn't reliable for merges, so fall back to the
1993 # ctx.files() isn't reliable for merges, so fall back to the
1987 # slower repo.status() method
1994 # slower repo.status() method
1988 files = set([fn for st in repo.status(base, old)[:3]
1995 files = set([fn for st in repo.status(base, old)[:3]
1989 for fn in st])
1996 for fn in st])
1990 else:
1997 else:
1991 files = set(old.files())
1998 files = set(old.files())
1992
1999
1993 # Second, we use either the commit we just did, or if there were no
2000 # Second, we use either the commit we just did, or if there were no
1994 # changes the parent of the working directory as the version of the
2001 # changes the parent of the working directory as the version of the
1995 # files in the final amend commit
2002 # files in the final amend commit
1996 if node:
2003 if node:
1997 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2004 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
1998
2005
1999 user = ctx.user()
2006 user = ctx.user()
2000 date = ctx.date()
2007 date = ctx.date()
2001 # Recompute copies (avoid recording a -> b -> a)
2008 # Recompute copies (avoid recording a -> b -> a)
2002 copied = copies.pathcopies(base, ctx)
2009 copied = copies.pathcopies(base, ctx)
2003
2010
2004 # Prune files which were reverted by the updates: if old
2011 # Prune files which were reverted by the updates: if old
2005 # introduced file X and our intermediate commit, node,
2012 # introduced file X and our intermediate commit, node,
2006 # renamed that file, then those two files are the same and
2013 # renamed that file, then those two files are the same and
2007 # we can discard X from our list of files. Likewise if X
2014 # we can discard X from our list of files. Likewise if X
2008 # was deleted, it's no longer relevant
2015 # was deleted, it's no longer relevant
2009 files.update(ctx.files())
2016 files.update(ctx.files())
2010
2017
2011 def samefile(f):
2018 def samefile(f):
2012 if f in ctx.manifest():
2019 if f in ctx.manifest():
2013 a = ctx.filectx(f)
2020 a = ctx.filectx(f)
2014 if f in base.manifest():
2021 if f in base.manifest():
2015 b = base.filectx(f)
2022 b = base.filectx(f)
2016 return (not a.cmp(b)
2023 return (not a.cmp(b)
2017 and a.flags() == b.flags())
2024 and a.flags() == b.flags())
2018 else:
2025 else:
2019 return False
2026 return False
2020 else:
2027 else:
2021 return f not in base.manifest()
2028 return f not in base.manifest()
2022 files = [f for f in files if not samefile(f)]
2029 files = [f for f in files if not samefile(f)]
2023
2030
2024 def filectxfn(repo, ctx_, path):
2031 def filectxfn(repo, ctx_, path):
2025 try:
2032 try:
2026 fctx = ctx[path]
2033 fctx = ctx[path]
2027 flags = fctx.flags()
2034 flags = fctx.flags()
2028 mctx = context.memfilectx(fctx.path(), fctx.data(),
2035 mctx = context.memfilectx(fctx.path(), fctx.data(),
2029 islink='l' in flags,
2036 islink='l' in flags,
2030 isexec='x' in flags,
2037 isexec='x' in flags,
2031 copied=copied.get(path))
2038 copied=copied.get(path))
2032 return mctx
2039 return mctx
2033 except KeyError:
2040 except KeyError:
2034 raise IOError
2041 raise IOError
2035 else:
2042 else:
2036 ui.note(_('copying changeset %s to %s\n') % (old, base))
2043 ui.note(_('copying changeset %s to %s\n') % (old, base))
2037
2044
2038 # Use version of files as in the old cset
2045 # Use version of files as in the old cset
2039 def filectxfn(repo, ctx_, path):
2046 def filectxfn(repo, ctx_, path):
2040 try:
2047 try:
2041 return old.filectx(path)
2048 return old.filectx(path)
2042 except KeyError:
2049 except KeyError:
2043 raise IOError
2050 raise IOError
2044
2051
2045 user = opts.get('user') or old.user()
2052 user = opts.get('user') or old.user()
2046 date = opts.get('date') or old.date()
2053 date = opts.get('date') or old.date()
2047 editor = commiteditor
2054 editor = commiteditor
2048 if not message:
2055 if not message:
2049 editor = commitforceeditor
2056 editor = commitforceeditor
2050 message = old.description()
2057 message = old.description()
2051 elif opts.get('edit'):
2058 elif opts.get('edit'):
2052 editor = commitforceeditor
2059 editor = commitforceeditor
2053
2060
2054 pureextra = extra.copy()
2061 pureextra = extra.copy()
2055 extra['amend_source'] = old.hex()
2062 extra['amend_source'] = old.hex()
2056
2063
2057 new = context.memctx(repo,
2064 new = context.memctx(repo,
2058 parents=[base.node(), old.p2().node()],
2065 parents=[base.node(), old.p2().node()],
2059 text=message,
2066 text=message,
2060 files=files,
2067 files=files,
2061 filectxfn=filectxfn,
2068 filectxfn=filectxfn,
2062 user=user,
2069 user=user,
2063 date=date,
2070 date=date,
2064 extra=extra,
2071 extra=extra,
2065 editor=editor)
2072 editor=editor)
2066
2073
2067 newdesc = changelog.stripdesc(new.description())
2074 newdesc = changelog.stripdesc(new.description())
2068 if ((not node)
2075 if ((not node)
2069 and newdesc == old.description()
2076 and newdesc == old.description()
2070 and user == old.user()
2077 and user == old.user()
2071 and date == old.date()
2078 and date == old.date()
2072 and pureextra == old.extra()):
2079 and pureextra == old.extra()):
2073 # nothing changed. continuing here would create a new node
2080 # nothing changed. continuing here would create a new node
2074 # anyway because of the amend_source noise.
2081 # anyway because of the amend_source noise.
2075 #
2082 #
2076 # This not what we expect from amend.
2083 # This not what we expect from amend.
2077 return old.node()
2084 return old.node()
2078
2085
2079 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2086 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2080 try:
2087 try:
2081 if opts.get('secret'):
2088 if opts.get('secret'):
2082 commitphase = 'secret'
2089 commitphase = 'secret'
2083 else:
2090 else:
2084 commitphase = old.phase()
2091 commitphase = old.phase()
2085 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2092 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2086 newid = repo.commitctx(new)
2093 newid = repo.commitctx(new)
2087 finally:
2094 finally:
2088 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2095 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2089 if newid != old.node():
2096 if newid != old.node():
2090 # Reroute the working copy parent to the new changeset
2097 # Reroute the working copy parent to the new changeset
2091 repo.setparents(newid, nullid)
2098 repo.setparents(newid, nullid)
2092
2099
2093 # Move bookmarks from old parent to amend commit
2100 # Move bookmarks from old parent to amend commit
2094 bms = repo.nodebookmarks(old.node())
2101 bms = repo.nodebookmarks(old.node())
2095 if bms:
2102 if bms:
2096 marks = repo._bookmarks
2103 marks = repo._bookmarks
2097 for bm in bms:
2104 for bm in bms:
2098 marks[bm] = newid
2105 marks[bm] = newid
2099 marks.write()
2106 marks.write()
2100 #commit the whole amend process
2107 #commit the whole amend process
2101 if obsolete._enabled and newid != old.node():
2108 if obsolete._enabled and newid != old.node():
2102 # mark the new changeset as successor of the rewritten one
2109 # mark the new changeset as successor of the rewritten one
2103 new = repo[newid]
2110 new = repo[newid]
2104 obs = [(old, (new,))]
2111 obs = [(old, (new,))]
2105 if node:
2112 if node:
2106 obs.append((ctx, ()))
2113 obs.append((ctx, ()))
2107
2114
2108 obsolete.createmarkers(repo, obs)
2115 obsolete.createmarkers(repo, obs)
2109 tr.close()
2116 tr.close()
2110 finally:
2117 finally:
2111 tr.release()
2118 tr.release()
2112 if (not obsolete._enabled) and newid != old.node():
2119 if (not obsolete._enabled) and newid != old.node():
2113 # Strip the intermediate commit (if there was one) and the amended
2120 # Strip the intermediate commit (if there was one) and the amended
2114 # commit
2121 # commit
2115 if node:
2122 if node:
2116 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2123 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2117 ui.note(_('stripping amended changeset %s\n') % old)
2124 ui.note(_('stripping amended changeset %s\n') % old)
2118 repair.strip(ui, repo, old.node(), topic='amend-backup')
2125 repair.strip(ui, repo, old.node(), topic='amend-backup')
2119 finally:
2126 finally:
2120 if newid is None:
2127 if newid is None:
2121 repo.dirstate.invalidate()
2128 repo.dirstate.invalidate()
2122 lockmod.release(lock, wlock)
2129 lockmod.release(lock, wlock)
2123 return newid
2130 return newid
2124
2131
2125 def commiteditor(repo, ctx, subs):
2132 def commiteditor(repo, ctx, subs):
2126 if ctx.description():
2133 if ctx.description():
2127 return ctx.description()
2134 return ctx.description()
2128 return commitforceeditor(repo, ctx, subs)
2135 return commitforceeditor(repo, ctx, subs)
2129
2136
2130 def commitforceeditor(repo, ctx, subs):
2137 def commitforceeditor(repo, ctx, subs):
2131 edittext = []
2138 edittext = []
2132 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2139 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2133 if ctx.description():
2140 if ctx.description():
2134 edittext.append(ctx.description())
2141 edittext.append(ctx.description())
2135 edittext.append("")
2142 edittext.append("")
2136 edittext.append("") # Empty line between message and comments.
2143 edittext.append("") # Empty line between message and comments.
2137 edittext.append(_("HG: Enter commit message."
2144 edittext.append(_("HG: Enter commit message."
2138 " Lines beginning with 'HG:' are removed."))
2145 " Lines beginning with 'HG:' are removed."))
2139 edittext.append(_("HG: Leave message empty to abort commit."))
2146 edittext.append(_("HG: Leave message empty to abort commit."))
2140 edittext.append("HG: --")
2147 edittext.append("HG: --")
2141 edittext.append(_("HG: user: %s") % ctx.user())
2148 edittext.append(_("HG: user: %s") % ctx.user())
2142 if ctx.p2():
2149 if ctx.p2():
2143 edittext.append(_("HG: branch merge"))
2150 edittext.append(_("HG: branch merge"))
2144 if ctx.branch():
2151 if ctx.branch():
2145 edittext.append(_("HG: branch '%s'") % ctx.branch())
2152 edittext.append(_("HG: branch '%s'") % ctx.branch())
2146 if bookmarks.iscurrent(repo):
2153 if bookmarks.iscurrent(repo):
2147 edittext.append(_("HG: bookmark '%s'") % repo._bookmarkcurrent)
2154 edittext.append(_("HG: bookmark '%s'") % repo._bookmarkcurrent)
2148 edittext.extend([_("HG: subrepo %s") % s for s in subs])
2155 edittext.extend([_("HG: subrepo %s") % s for s in subs])
2149 edittext.extend([_("HG: added %s") % f for f in added])
2156 edittext.extend([_("HG: added %s") % f for f in added])
2150 edittext.extend([_("HG: changed %s") % f for f in modified])
2157 edittext.extend([_("HG: changed %s") % f for f in modified])
2151 edittext.extend([_("HG: removed %s") % f for f in removed])
2158 edittext.extend([_("HG: removed %s") % f for f in removed])
2152 if not added and not modified and not removed:
2159 if not added and not modified and not removed:
2153 edittext.append(_("HG: no files changed"))
2160 edittext.append(_("HG: no files changed"))
2154 edittext.append("")
2161 edittext.append("")
2155 # run editor in the repository root
2162 # run editor in the repository root
2156 olddir = os.getcwd()
2163 olddir = os.getcwd()
2157 os.chdir(repo.root)
2164 os.chdir(repo.root)
2158 text = repo.ui.edit("\n".join(edittext), ctx.user(), ctx.extra())
2165 text = repo.ui.edit("\n".join(edittext), ctx.user(), ctx.extra())
2159 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2166 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2160 os.chdir(olddir)
2167 os.chdir(olddir)
2161
2168
2162 if not text.strip():
2169 if not text.strip():
2163 raise util.Abort(_("empty commit message"))
2170 raise util.Abort(_("empty commit message"))
2164
2171
2165 return text
2172 return text
2166
2173
2167 def commitstatus(repo, node, branch, bheads=None, opts={}):
2174 def commitstatus(repo, node, branch, bheads=None, opts={}):
2168 ctx = repo[node]
2175 ctx = repo[node]
2169 parents = ctx.parents()
2176 parents = ctx.parents()
2170
2177
2171 if (not opts.get('amend') and bheads and node not in bheads and not
2178 if (not opts.get('amend') and bheads and node not in bheads and not
2172 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2179 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2173 repo.ui.status(_('created new head\n'))
2180 repo.ui.status(_('created new head\n'))
2174 # The message is not printed for initial roots. For the other
2181 # The message is not printed for initial roots. For the other
2175 # changesets, it is printed in the following situations:
2182 # changesets, it is printed in the following situations:
2176 #
2183 #
2177 # Par column: for the 2 parents with ...
2184 # Par column: for the 2 parents with ...
2178 # N: null or no parent
2185 # N: null or no parent
2179 # B: parent is on another named branch
2186 # B: parent is on another named branch
2180 # C: parent is a regular non head changeset
2187 # C: parent is a regular non head changeset
2181 # H: parent was a branch head of the current branch
2188 # H: parent was a branch head of the current branch
2182 # Msg column: whether we print "created new head" message
2189 # Msg column: whether we print "created new head" message
2183 # In the following, it is assumed that there already exists some
2190 # In the following, it is assumed that there already exists some
2184 # initial branch heads of the current branch, otherwise nothing is
2191 # initial branch heads of the current branch, otherwise nothing is
2185 # printed anyway.
2192 # printed anyway.
2186 #
2193 #
2187 # Par Msg Comment
2194 # Par Msg Comment
2188 # N N y additional topo root
2195 # N N y additional topo root
2189 #
2196 #
2190 # B N y additional branch root
2197 # B N y additional branch root
2191 # C N y additional topo head
2198 # C N y additional topo head
2192 # H N n usual case
2199 # H N n usual case
2193 #
2200 #
2194 # B B y weird additional branch root
2201 # B B y weird additional branch root
2195 # C B y branch merge
2202 # C B y branch merge
2196 # H B n merge with named branch
2203 # H B n merge with named branch
2197 #
2204 #
2198 # C C y additional head from merge
2205 # C C y additional head from merge
2199 # C H n merge with a head
2206 # C H n merge with a head
2200 #
2207 #
2201 # H H n head merge: head count decreases
2208 # H H n head merge: head count decreases
2202
2209
2203 if not opts.get('close_branch'):
2210 if not opts.get('close_branch'):
2204 for r in parents:
2211 for r in parents:
2205 if r.closesbranch() and r.branch() == branch:
2212 if r.closesbranch() and r.branch() == branch:
2206 repo.ui.status(_('reopening closed branch head %d\n') % r)
2213 repo.ui.status(_('reopening closed branch head %d\n') % r)
2207
2214
2208 if repo.ui.debugflag:
2215 if repo.ui.debugflag:
2209 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2216 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2210 elif repo.ui.verbose:
2217 elif repo.ui.verbose:
2211 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2218 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2212
2219
2213 def revert(ui, repo, ctx, parents, *pats, **opts):
2220 def revert(ui, repo, ctx, parents, *pats, **opts):
2214 parent, p2 = parents
2221 parent, p2 = parents
2215 node = ctx.node()
2222 node = ctx.node()
2216
2223
2217 mf = ctx.manifest()
2224 mf = ctx.manifest()
2218 if node == parent:
2225 if node == parent:
2219 pmf = mf
2226 pmf = mf
2220 else:
2227 else:
2221 pmf = None
2228 pmf = None
2222
2229
2223 # need all matching names in dirstate and manifest of target rev,
2230 # need all matching names in dirstate and manifest of target rev,
2224 # so have to walk both. do not print errors if files exist in one
2231 # so have to walk both. do not print errors if files exist in one
2225 # but not other.
2232 # but not other.
2226
2233
2227 names = {}
2234 names = {}
2228
2235
2229 wlock = repo.wlock()
2236 wlock = repo.wlock()
2230 try:
2237 try:
2231 # walk dirstate.
2238 # walk dirstate.
2232
2239
2233 m = scmutil.match(repo[None], pats, opts)
2240 m = scmutil.match(repo[None], pats, opts)
2234 m.bad = lambda x, y: False
2241 m.bad = lambda x, y: False
2235 for abs in repo.walk(m):
2242 for abs in repo.walk(m):
2236 names[abs] = m.rel(abs), m.exact(abs)
2243 names[abs] = m.rel(abs), m.exact(abs)
2237
2244
2238 # walk target manifest.
2245 # walk target manifest.
2239
2246
2240 def badfn(path, msg):
2247 def badfn(path, msg):
2241 if path in names:
2248 if path in names:
2242 return
2249 return
2243 if path in ctx.substate:
2250 if path in ctx.substate:
2244 return
2251 return
2245 path_ = path + '/'
2252 path_ = path + '/'
2246 for f in names:
2253 for f in names:
2247 if f.startswith(path_):
2254 if f.startswith(path_):
2248 return
2255 return
2249 ui.warn("%s: %s\n" % (m.rel(path), msg))
2256 ui.warn("%s: %s\n" % (m.rel(path), msg))
2250
2257
2251 m = scmutil.match(ctx, pats, opts)
2258 m = scmutil.match(ctx, pats, opts)
2252 m.bad = badfn
2259 m.bad = badfn
2253 for abs in ctx.walk(m):
2260 for abs in ctx.walk(m):
2254 if abs not in names:
2261 if abs not in names:
2255 names[abs] = m.rel(abs), m.exact(abs)
2262 names[abs] = m.rel(abs), m.exact(abs)
2256
2263
2257 # get the list of subrepos that must be reverted
2264 # get the list of subrepos that must be reverted
2258 targetsubs = sorted(s for s in ctx.substate if m(s))
2265 targetsubs = sorted(s for s in ctx.substate if m(s))
2259 m = scmutil.matchfiles(repo, names)
2266 m = scmutil.matchfiles(repo, names)
2260 changes = repo.status(match=m)[:4]
2267 changes = repo.status(match=m)[:4]
2261 modified, added, removed, deleted = map(set, changes)
2268 modified, added, removed, deleted = map(set, changes)
2262
2269
2263 # if f is a rename, also revert the source
2270 # if f is a rename, also revert the source
2264 cwd = repo.getcwd()
2271 cwd = repo.getcwd()
2265 for f in added:
2272 for f in added:
2266 src = repo.dirstate.copied(f)
2273 src = repo.dirstate.copied(f)
2267 if src and src not in names and repo.dirstate[src] == 'r':
2274 if src and src not in names and repo.dirstate[src] == 'r':
2268 removed.add(src)
2275 removed.add(src)
2269 names[src] = (repo.pathto(src, cwd), True)
2276 names[src] = (repo.pathto(src, cwd), True)
2270
2277
2271 def removeforget(abs):
2278 def removeforget(abs):
2272 if repo.dirstate[abs] == 'a':
2279 if repo.dirstate[abs] == 'a':
2273 return _('forgetting %s\n')
2280 return _('forgetting %s\n')
2274 return _('removing %s\n')
2281 return _('removing %s\n')
2275
2282
2276 revert = ([], _('reverting %s\n'))
2283 revert = ([], _('reverting %s\n'))
2277 add = ([], _('adding %s\n'))
2284 add = ([], _('adding %s\n'))
2278 remove = ([], removeforget)
2285 remove = ([], removeforget)
2279 undelete = ([], _('undeleting %s\n'))
2286 undelete = ([], _('undeleting %s\n'))
2280
2287
2281 disptable = (
2288 disptable = (
2282 # dispatch table:
2289 # dispatch table:
2283 # file state
2290 # file state
2284 # action if in target manifest
2291 # action if in target manifest
2285 # action if not in target manifest
2292 # action if not in target manifest
2286 # make backup if in target manifest
2293 # make backup if in target manifest
2287 # make backup if not in target manifest
2294 # make backup if not in target manifest
2288 (modified, revert, remove, True, True),
2295 (modified, revert, remove, True, True),
2289 (added, revert, remove, True, False),
2296 (added, revert, remove, True, False),
2290 (removed, undelete, None, True, False),
2297 (removed, undelete, None, True, False),
2291 (deleted, revert, remove, False, False),
2298 (deleted, revert, remove, False, False),
2292 )
2299 )
2293
2300
2294 for abs, (rel, exact) in sorted(names.items()):
2301 for abs, (rel, exact) in sorted(names.items()):
2295 mfentry = mf.get(abs)
2302 mfentry = mf.get(abs)
2296 target = repo.wjoin(abs)
2303 target = repo.wjoin(abs)
2297 def handle(xlist, dobackup):
2304 def handle(xlist, dobackup):
2298 xlist[0].append(abs)
2305 xlist[0].append(abs)
2299 if (dobackup and not opts.get('no_backup') and
2306 if (dobackup and not opts.get('no_backup') and
2300 os.path.lexists(target) and
2307 os.path.lexists(target) and
2301 abs in ctx and repo[None][abs].cmp(ctx[abs])):
2308 abs in ctx and repo[None][abs].cmp(ctx[abs])):
2302 bakname = "%s.orig" % rel
2309 bakname = "%s.orig" % rel
2303 ui.note(_('saving current version of %s as %s\n') %
2310 ui.note(_('saving current version of %s as %s\n') %
2304 (rel, bakname))
2311 (rel, bakname))
2305 if not opts.get('dry_run'):
2312 if not opts.get('dry_run'):
2306 util.rename(target, bakname)
2313 util.rename(target, bakname)
2307 if ui.verbose or not exact:
2314 if ui.verbose or not exact:
2308 msg = xlist[1]
2315 msg = xlist[1]
2309 if not isinstance(msg, basestring):
2316 if not isinstance(msg, basestring):
2310 msg = msg(abs)
2317 msg = msg(abs)
2311 ui.status(msg % rel)
2318 ui.status(msg % rel)
2312 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2319 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2313 if abs not in table:
2320 if abs not in table:
2314 continue
2321 continue
2315 # file has changed in dirstate
2322 # file has changed in dirstate
2316 if mfentry:
2323 if mfentry:
2317 handle(hitlist, backuphit)
2324 handle(hitlist, backuphit)
2318 elif misslist is not None:
2325 elif misslist is not None:
2319 handle(misslist, backupmiss)
2326 handle(misslist, backupmiss)
2320 break
2327 break
2321 else:
2328 else:
2322 if abs not in repo.dirstate:
2329 if abs not in repo.dirstate:
2323 if mfentry:
2330 if mfentry:
2324 handle(add, True)
2331 handle(add, True)
2325 elif exact:
2332 elif exact:
2326 ui.warn(_('file not managed: %s\n') % rel)
2333 ui.warn(_('file not managed: %s\n') % rel)
2327 continue
2334 continue
2328 # file has not changed in dirstate
2335 # file has not changed in dirstate
2329 if node == parent:
2336 if node == parent:
2330 if exact:
2337 if exact:
2331 ui.warn(_('no changes needed to %s\n') % rel)
2338 ui.warn(_('no changes needed to %s\n') % rel)
2332 continue
2339 continue
2333 if pmf is None:
2340 if pmf is None:
2334 # only need parent manifest in this unlikely case,
2341 # only need parent manifest in this unlikely case,
2335 # so do not read by default
2342 # so do not read by default
2336 pmf = repo[parent].manifest()
2343 pmf = repo[parent].manifest()
2337 if abs in pmf and mfentry:
2344 if abs in pmf and mfentry:
2338 # if version of file is same in parent and target
2345 # if version of file is same in parent and target
2339 # manifests, do nothing
2346 # manifests, do nothing
2340 if (pmf[abs] != mfentry or
2347 if (pmf[abs] != mfentry or
2341 pmf.flags(abs) != mf.flags(abs)):
2348 pmf.flags(abs) != mf.flags(abs)):
2342 handle(revert, False)
2349 handle(revert, False)
2343 else:
2350 else:
2344 handle(remove, False)
2351 handle(remove, False)
2345 if not opts.get('dry_run'):
2352 if not opts.get('dry_run'):
2346 _performrevert(repo, parents, ctx, revert, add, remove, undelete)
2353 _performrevert(repo, parents, ctx, revert, add, remove, undelete)
2347
2354
2348 if targetsubs:
2355 if targetsubs:
2349 # Revert the subrepos on the revert list
2356 # Revert the subrepos on the revert list
2350 for sub in targetsubs:
2357 for sub in targetsubs:
2351 ctx.sub(sub).revert(ui, ctx.substate[sub], *pats, **opts)
2358 ctx.sub(sub).revert(ui, ctx.substate[sub], *pats, **opts)
2352 finally:
2359 finally:
2353 wlock.release()
2360 wlock.release()
2354
2361
2355 def _performrevert(repo, parents, ctx, revert, add, remove, undelete):
2362 def _performrevert(repo, parents, ctx, revert, add, remove, undelete):
2356 """function that actually perform all the action computed for revert
2363 """function that actually perform all the action computed for revert
2357
2364
2358 This is an independent function to let extension to plug in and react to
2365 This is an independent function to let extension to plug in and react to
2359 the imminent revert.
2366 the imminent revert.
2360
2367
2361 Make sure you have the working directory locked when calling this function.
2368 Make sure you have the working directory locked when calling this function.
2362 """
2369 """
2363 parent, p2 = parents
2370 parent, p2 = parents
2364 node = ctx.node()
2371 node = ctx.node()
2365 def checkout(f):
2372 def checkout(f):
2366 fc = ctx[f]
2373 fc = ctx[f]
2367 repo.wwrite(f, fc.data(), fc.flags())
2374 repo.wwrite(f, fc.data(), fc.flags())
2368
2375
2369 audit_path = pathutil.pathauditor(repo.root)
2376 audit_path = pathutil.pathauditor(repo.root)
2370 for f in remove[0]:
2377 for f in remove[0]:
2371 if repo.dirstate[f] == 'a':
2378 if repo.dirstate[f] == 'a':
2372 repo.dirstate.drop(f)
2379 repo.dirstate.drop(f)
2373 continue
2380 continue
2374 audit_path(f)
2381 audit_path(f)
2375 try:
2382 try:
2376 util.unlinkpath(repo.wjoin(f))
2383 util.unlinkpath(repo.wjoin(f))
2377 except OSError:
2384 except OSError:
2378 pass
2385 pass
2379 repo.dirstate.remove(f)
2386 repo.dirstate.remove(f)
2380
2387
2381 normal = None
2388 normal = None
2382 if node == parent:
2389 if node == parent:
2383 # We're reverting to our parent. If possible, we'd like status
2390 # We're reverting to our parent. If possible, we'd like status
2384 # to report the file as clean. We have to use normallookup for
2391 # to report the file as clean. We have to use normallookup for
2385 # merges to avoid losing information about merged/dirty files.
2392 # merges to avoid losing information about merged/dirty files.
2386 if p2 != nullid:
2393 if p2 != nullid:
2387 normal = repo.dirstate.normallookup
2394 normal = repo.dirstate.normallookup
2388 else:
2395 else:
2389 normal = repo.dirstate.normal
2396 normal = repo.dirstate.normal
2390 for f in revert[0]:
2397 for f in revert[0]:
2391 checkout(f)
2398 checkout(f)
2392 if normal:
2399 if normal:
2393 normal(f)
2400 normal(f)
2394
2401
2395 for f in add[0]:
2402 for f in add[0]:
2396 checkout(f)
2403 checkout(f)
2397 repo.dirstate.add(f)
2404 repo.dirstate.add(f)
2398
2405
2399 normal = repo.dirstate.normallookup
2406 normal = repo.dirstate.normallookup
2400 if node == parent and p2 == nullid:
2407 if node == parent and p2 == nullid:
2401 normal = repo.dirstate.normal
2408 normal = repo.dirstate.normal
2402 for f in undelete[0]:
2409 for f in undelete[0]:
2403 checkout(f)
2410 checkout(f)
2404 normal(f)
2411 normal(f)
2405
2412
2406 copied = copies.pathcopies(repo[parent], ctx)
2413 copied = copies.pathcopies(repo[parent], ctx)
2407
2414
2408 for f in add[0] + undelete[0] + revert[0]:
2415 for f in add[0] + undelete[0] + revert[0]:
2409 if f in copied:
2416 if f in copied:
2410 repo.dirstate.copy(copied[f], f)
2417 repo.dirstate.copy(copied[f], f)
2411
2418
2412 def command(table):
2419 def command(table):
2413 '''returns a function object bound to table which can be used as
2420 '''returns a function object bound to table which can be used as
2414 a decorator for populating table as a command table'''
2421 a decorator for populating table as a command table'''
2415
2422
2416 def cmd(name, options=(), synopsis=None):
2423 def cmd(name, options=(), synopsis=None):
2417 def decorator(func):
2424 def decorator(func):
2418 if synopsis:
2425 if synopsis:
2419 table[name] = func, list(options), synopsis
2426 table[name] = func, list(options), synopsis
2420 else:
2427 else:
2421 table[name] = func, list(options)
2428 table[name] = func, list(options)
2422 return func
2429 return func
2423 return decorator
2430 return decorator
2424
2431
2425 return cmd
2432 return cmd
2426
2433
2427 # a list of (ui, repo, otherpeer, opts, missing) functions called by
2434 # a list of (ui, repo, otherpeer, opts, missing) functions called by
2428 # commands.outgoing. "missing" is "missing" of the result of
2435 # commands.outgoing. "missing" is "missing" of the result of
2429 # "findcommonoutgoing()"
2436 # "findcommonoutgoing()"
2430 outgoinghooks = util.hooks()
2437 outgoinghooks = util.hooks()
2431
2438
2432 # a list of (ui, repo) functions called by commands.summary
2439 # a list of (ui, repo) functions called by commands.summary
2433 summaryhooks = util.hooks()
2440 summaryhooks = util.hooks()
2434
2441
2435 # a list of (ui, repo, opts, changes) functions called by commands.summary.
2442 # a list of (ui, repo, opts, changes) functions called by commands.summary.
2436 #
2443 #
2437 # functions should return tuple of booleans below, if 'changes' is None:
2444 # functions should return tuple of booleans below, if 'changes' is None:
2438 # (whether-incomings-are-needed, whether-outgoings-are-needed)
2445 # (whether-incomings-are-needed, whether-outgoings-are-needed)
2439 #
2446 #
2440 # otherwise, 'changes' is a tuple of tuples below:
2447 # otherwise, 'changes' is a tuple of tuples below:
2441 # - (sourceurl, sourcebranch, sourcepeer, incoming)
2448 # - (sourceurl, sourcebranch, sourcepeer, incoming)
2442 # - (desturl, destbranch, destpeer, outgoing)
2449 # - (desturl, destbranch, destpeer, outgoing)
2443 summaryremotehooks = util.hooks()
2450 summaryremotehooks = util.hooks()
2444
2451
2445 # A list of state files kept by multistep operations like graft.
2452 # A list of state files kept by multistep operations like graft.
2446 # Since graft cannot be aborted, it is considered 'clearable' by update.
2453 # Since graft cannot be aborted, it is considered 'clearable' by update.
2447 # note: bisect is intentionally excluded
2454 # note: bisect is intentionally excluded
2448 # (state file, clearable, allowcommit, error, hint)
2455 # (state file, clearable, allowcommit, error, hint)
2449 unfinishedstates = [
2456 unfinishedstates = [
2450 ('graftstate', True, False, _('graft in progress'),
2457 ('graftstate', True, False, _('graft in progress'),
2451 _("use 'hg graft --continue' or 'hg update' to abort")),
2458 _("use 'hg graft --continue' or 'hg update' to abort")),
2452 ('updatestate', True, False, _('last update was interrupted'),
2459 ('updatestate', True, False, _('last update was interrupted'),
2453 _("use 'hg update' to get a consistent checkout"))
2460 _("use 'hg update' to get a consistent checkout"))
2454 ]
2461 ]
2455
2462
2456 def checkunfinished(repo, commit=False):
2463 def checkunfinished(repo, commit=False):
2457 '''Look for an unfinished multistep operation, like graft, and abort
2464 '''Look for an unfinished multistep operation, like graft, and abort
2458 if found. It's probably good to check this right before
2465 if found. It's probably good to check this right before
2459 bailifchanged().
2466 bailifchanged().
2460 '''
2467 '''
2461 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2468 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2462 if commit and allowcommit:
2469 if commit and allowcommit:
2463 continue
2470 continue
2464 if repo.vfs.exists(f):
2471 if repo.vfs.exists(f):
2465 raise util.Abort(msg, hint=hint)
2472 raise util.Abort(msg, hint=hint)
2466
2473
2467 def clearunfinished(repo):
2474 def clearunfinished(repo):
2468 '''Check for unfinished operations (as above), and clear the ones
2475 '''Check for unfinished operations (as above), and clear the ones
2469 that are clearable.
2476 that are clearable.
2470 '''
2477 '''
2471 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2478 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2472 if not clearable and repo.vfs.exists(f):
2479 if not clearable and repo.vfs.exists(f):
2473 raise util.Abort(msg, hint=hint)
2480 raise util.Abort(msg, hint=hint)
2474 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2481 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2475 if clearable and repo.vfs.exists(f):
2482 if clearable and repo.vfs.exists(f):
2476 util.unlink(repo.join(f))
2483 util.unlink(repo.join(f))
General Comments 0
You need to be logged in to leave comments. Login now