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