##// END OF EJS Templates
merge with stable
Matt Mackall -
r19130:70f0d1da merge default
parent child Browse files
Show More
@@ -1,2078 +1,2084 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 opts.get('rev'):
1025 if opts.get('rev'):
1026 revs = scmutil.revrange(repo, opts.get('rev'))
1026 revs = scmutil.revrange(repo, opts.get('rev'))
1027 elif follow:
1027 elif follow:
1028 revs = repo.revs('reverse(:.)')
1028 revs = repo.revs('reverse(:.)')
1029 else:
1029 else:
1030 revs = list(repo)
1030 revs = list(repo)
1031 revs.reverse()
1031 revs.reverse()
1032 if not revs:
1032 if not revs:
1033 return []
1033 return []
1034 wanted = set()
1034 wanted = set()
1035 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1035 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1036 fncache = {}
1036 fncache = {}
1037 change = repo.changectx
1037 change = repo.changectx
1038
1038
1039 # First step is to fill wanted, the set of revisions that we want to yield.
1039 # First step is to fill wanted, the set of revisions that we want to yield.
1040 # When it does not induce extra cost, we also fill fncache for revisions in
1040 # When it does not induce extra cost, we also fill fncache for revisions in
1041 # wanted: a cache of filenames that were changed (ctx.files()) and that
1041 # wanted: a cache of filenames that were changed (ctx.files()) and that
1042 # match the file filtering conditions.
1042 # match the file filtering conditions.
1043
1043
1044 if not slowpath and not match.files():
1044 if not slowpath and not match.files():
1045 # No files, no patterns. Display all revs.
1045 # No files, no patterns. Display all revs.
1046 wanted = set(revs)
1046 wanted = set(revs)
1047 copies = []
1047 copies = []
1048
1048
1049 if not slowpath and match.files():
1049 if not slowpath and match.files():
1050 # We only have to read through the filelog to find wanted revisions
1050 # We only have to read through the filelog to find wanted revisions
1051
1051
1052 minrev, maxrev = min(revs), max(revs)
1052 minrev, maxrev = min(revs), max(revs)
1053 def filerevgen(filelog, last):
1053 def filerevgen(filelog, last):
1054 """
1054 """
1055 Only files, no patterns. Check the history of each file.
1055 Only files, no patterns. Check the history of each file.
1056
1056
1057 Examines filelog entries within minrev, maxrev linkrev range
1057 Examines filelog entries within minrev, maxrev linkrev range
1058 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1058 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1059 tuples in backwards order
1059 tuples in backwards order
1060 """
1060 """
1061 cl_count = len(repo)
1061 cl_count = len(repo)
1062 revs = []
1062 revs = []
1063 for j in xrange(0, last + 1):
1063 for j in xrange(0, last + 1):
1064 linkrev = filelog.linkrev(j)
1064 linkrev = filelog.linkrev(j)
1065 if linkrev < minrev:
1065 if linkrev < minrev:
1066 continue
1066 continue
1067 # only yield rev for which we have the changelog, it can
1067 # only yield rev for which we have the changelog, it can
1068 # happen while doing "hg log" during a pull or commit
1068 # happen while doing "hg log" during a pull or commit
1069 if linkrev >= cl_count:
1069 if linkrev >= cl_count:
1070 break
1070 break
1071
1071
1072 parentlinkrevs = []
1072 parentlinkrevs = []
1073 for p in filelog.parentrevs(j):
1073 for p in filelog.parentrevs(j):
1074 if p != nullrev:
1074 if p != nullrev:
1075 parentlinkrevs.append(filelog.linkrev(p))
1075 parentlinkrevs.append(filelog.linkrev(p))
1076 n = filelog.node(j)
1076 n = filelog.node(j)
1077 revs.append((linkrev, parentlinkrevs,
1077 revs.append((linkrev, parentlinkrevs,
1078 follow and filelog.renamed(n)))
1078 follow and filelog.renamed(n)))
1079
1079
1080 return reversed(revs)
1080 return reversed(revs)
1081 def iterfiles():
1081 def iterfiles():
1082 pctx = repo['.']
1082 pctx = repo['.']
1083 for filename in match.files():
1083 for filename in match.files():
1084 if follow:
1084 if follow:
1085 if filename not in pctx:
1085 if filename not in pctx:
1086 raise util.Abort(_('cannot follow file not in parent '
1086 raise util.Abort(_('cannot follow file not in parent '
1087 'revision: "%s"') % filename)
1087 'revision: "%s"') % filename)
1088 yield filename, pctx[filename].filenode()
1088 yield filename, pctx[filename].filenode()
1089 else:
1089 else:
1090 yield filename, None
1090 yield filename, None
1091 for filename_node in copies:
1091 for filename_node in copies:
1092 yield filename_node
1092 yield filename_node
1093 for file_, node in iterfiles():
1093 for file_, node in iterfiles():
1094 filelog = repo.file(file_)
1094 filelog = repo.file(file_)
1095 if not len(filelog):
1095 if not len(filelog):
1096 if node is None:
1096 if node is None:
1097 # A zero count may be a directory or deleted file, so
1097 # A zero count may be a directory or deleted file, so
1098 # try to find matching entries on the slow path.
1098 # try to find matching entries on the slow path.
1099 if follow:
1099 if follow:
1100 raise util.Abort(
1100 raise util.Abort(
1101 _('cannot follow nonexistent file: "%s"') % file_)
1101 _('cannot follow nonexistent file: "%s"') % file_)
1102 slowpath = True
1102 slowpath = True
1103 break
1103 break
1104 else:
1104 else:
1105 continue
1105 continue
1106
1106
1107 if node is None:
1107 if node is None:
1108 last = len(filelog) - 1
1108 last = len(filelog) - 1
1109 else:
1109 else:
1110 last = filelog.rev(node)
1110 last = filelog.rev(node)
1111
1111
1112
1112
1113 # keep track of all ancestors of the file
1113 # keep track of all ancestors of the file
1114 ancestors = set([filelog.linkrev(last)])
1114 ancestors = set([filelog.linkrev(last)])
1115
1115
1116 # iterate from latest to oldest revision
1116 # iterate from latest to oldest revision
1117 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1117 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1118 if not follow:
1118 if not follow:
1119 if rev > maxrev:
1119 if rev > maxrev:
1120 continue
1120 continue
1121 else:
1121 else:
1122 # Note that last might not be the first interesting
1122 # Note that last might not be the first interesting
1123 # rev to us:
1123 # rev to us:
1124 # if the file has been changed after maxrev, we'll
1124 # if the file has been changed after maxrev, we'll
1125 # have linkrev(last) > maxrev, and we still need
1125 # have linkrev(last) > maxrev, and we still need
1126 # to explore the file graph
1126 # to explore the file graph
1127 if rev not in ancestors:
1127 if rev not in ancestors:
1128 continue
1128 continue
1129 # XXX insert 1327 fix here
1129 # XXX insert 1327 fix here
1130 if flparentlinkrevs:
1130 if flparentlinkrevs:
1131 ancestors.update(flparentlinkrevs)
1131 ancestors.update(flparentlinkrevs)
1132
1132
1133 fncache.setdefault(rev, []).append(file_)
1133 fncache.setdefault(rev, []).append(file_)
1134 wanted.add(rev)
1134 wanted.add(rev)
1135 if copied:
1135 if copied:
1136 copies.append(copied)
1136 copies.append(copied)
1137
1137
1138 # We decided to fall back to the slowpath because at least one
1138 # We decided to fall back to the slowpath because at least one
1139 # of the paths was not a file. Check to see if at least one of them
1139 # of the paths was not a file. Check to see if at least one of them
1140 # existed in history, otherwise simply return
1140 # existed in history, otherwise simply return
1141 if slowpath:
1141 if slowpath:
1142 for path in match.files():
1142 for path in match.files():
1143 if path == '.' or path in repo.store:
1143 if path == '.' or path in repo.store:
1144 break
1144 break
1145 else:
1145 else:
1146 return []
1146 return []
1147
1147
1148 if slowpath:
1148 if slowpath:
1149 # We have to read the changelog to match filenames against
1149 # We have to read the changelog to match filenames against
1150 # changed files
1150 # changed files
1151
1151
1152 if follow:
1152 if follow:
1153 raise util.Abort(_('can only follow copies/renames for explicit '
1153 raise util.Abort(_('can only follow copies/renames for explicit '
1154 'filenames'))
1154 'filenames'))
1155
1155
1156 # The slow path checks files modified in every changeset.
1156 # The slow path checks files modified in every changeset.
1157 for i in sorted(revs):
1157 for i in sorted(revs):
1158 ctx = change(i)
1158 ctx = change(i)
1159 matches = filter(match, ctx.files())
1159 matches = filter(match, ctx.files())
1160 if matches:
1160 if matches:
1161 fncache[i] = matches
1161 fncache[i] = matches
1162 wanted.add(i)
1162 wanted.add(i)
1163
1163
1164 class followfilter(object):
1164 class followfilter(object):
1165 def __init__(self, onlyfirst=False):
1165 def __init__(self, onlyfirst=False):
1166 self.startrev = nullrev
1166 self.startrev = nullrev
1167 self.roots = set()
1167 self.roots = set()
1168 self.onlyfirst = onlyfirst
1168 self.onlyfirst = onlyfirst
1169
1169
1170 def match(self, rev):
1170 def match(self, rev):
1171 def realparents(rev):
1171 def realparents(rev):
1172 if self.onlyfirst:
1172 if self.onlyfirst:
1173 return repo.changelog.parentrevs(rev)[0:1]
1173 return repo.changelog.parentrevs(rev)[0:1]
1174 else:
1174 else:
1175 return filter(lambda x: x != nullrev,
1175 return filter(lambda x: x != nullrev,
1176 repo.changelog.parentrevs(rev))
1176 repo.changelog.parentrevs(rev))
1177
1177
1178 if self.startrev == nullrev:
1178 if self.startrev == nullrev:
1179 self.startrev = rev
1179 self.startrev = rev
1180 return True
1180 return True
1181
1181
1182 if rev > self.startrev:
1182 if rev > self.startrev:
1183 # forward: all descendants
1183 # forward: all descendants
1184 if not self.roots:
1184 if not self.roots:
1185 self.roots.add(self.startrev)
1185 self.roots.add(self.startrev)
1186 for parent in realparents(rev):
1186 for parent in realparents(rev):
1187 if parent in self.roots:
1187 if parent in self.roots:
1188 self.roots.add(rev)
1188 self.roots.add(rev)
1189 return True
1189 return True
1190 else:
1190 else:
1191 # backwards: all parents
1191 # backwards: all parents
1192 if not self.roots:
1192 if not self.roots:
1193 self.roots.update(realparents(self.startrev))
1193 self.roots.update(realparents(self.startrev))
1194 if rev in self.roots:
1194 if rev in self.roots:
1195 self.roots.remove(rev)
1195 self.roots.remove(rev)
1196 self.roots.update(realparents(rev))
1196 self.roots.update(realparents(rev))
1197 return True
1197 return True
1198
1198
1199 return False
1199 return False
1200
1200
1201 # it might be worthwhile to do this in the iterator if the rev range
1201 # it might be worthwhile to do this in the iterator if the rev range
1202 # is descending and the prune args are all within that range
1202 # is descending and the prune args are all within that range
1203 for rev in opts.get('prune', ()):
1203 for rev in opts.get('prune', ()):
1204 rev = repo[rev].rev()
1204 rev = repo[rev].rev()
1205 ff = followfilter()
1205 ff = followfilter()
1206 stop = min(revs[0], revs[-1])
1206 stop = min(revs[0], revs[-1])
1207 for x in xrange(rev, stop - 1, -1):
1207 for x in xrange(rev, stop - 1, -1):
1208 if ff.match(x):
1208 if ff.match(x):
1209 wanted.discard(x)
1209 wanted.discard(x)
1210
1210
1211 # Choose a small initial window if we will probably only visit a
1211 # Choose a small initial window if we will probably only visit a
1212 # few commits.
1212 # few commits.
1213 limit = loglimit(opts)
1213 limit = loglimit(opts)
1214 windowsize = 8
1214 windowsize = 8
1215 if limit:
1215 if limit:
1216 windowsize = min(limit, windowsize)
1216 windowsize = min(limit, windowsize)
1217
1217
1218 # Now that wanted is correctly initialized, we can iterate over the
1218 # Now that wanted is correctly initialized, we can iterate over the
1219 # revision range, yielding only revisions in wanted.
1219 # revision range, yielding only revisions in wanted.
1220 def iterate():
1220 def iterate():
1221 if follow and not match.files():
1221 if follow and not match.files():
1222 ff = followfilter(onlyfirst=opts.get('follow_first'))
1222 ff = followfilter(onlyfirst=opts.get('follow_first'))
1223 def want(rev):
1223 def want(rev):
1224 return ff.match(rev) and rev in wanted
1224 return ff.match(rev) and rev in wanted
1225 else:
1225 else:
1226 def want(rev):
1226 def want(rev):
1227 return rev in wanted
1227 return rev in wanted
1228
1228
1229 for i, window in increasingwindows(0, len(revs), windowsize):
1229 for i, window in increasingwindows(0, len(revs), windowsize):
1230 nrevs = [rev for rev in revs[i:i + window] if want(rev)]
1230 nrevs = [rev for rev in revs[i:i + window] if want(rev)]
1231 for rev in sorted(nrevs):
1231 for rev in sorted(nrevs):
1232 fns = fncache.get(rev)
1232 fns = fncache.get(rev)
1233 ctx = change(rev)
1233 ctx = change(rev)
1234 if not fns:
1234 if not fns:
1235 def fns_generator():
1235 def fns_generator():
1236 for f in ctx.files():
1236 for f in ctx.files():
1237 if match(f):
1237 if match(f):
1238 yield f
1238 yield f
1239 fns = fns_generator()
1239 fns = fns_generator()
1240 prepare(ctx, fns)
1240 prepare(ctx, fns)
1241 for rev in nrevs:
1241 for rev in nrevs:
1242 yield change(rev)
1242 yield change(rev)
1243 return iterate()
1243 return iterate()
1244
1244
1245 def _makegraphfilematcher(repo, pats, followfirst):
1245 def _makegraphfilematcher(repo, pats, followfirst):
1246 # When displaying a revision with --patch --follow FILE, we have
1246 # When displaying a revision with --patch --follow FILE, we have
1247 # to know which file of the revision must be diffed. With
1247 # to know which file of the revision must be diffed. With
1248 # --follow, we want the names of the ancestors of FILE in the
1248 # --follow, we want the names of the ancestors of FILE in the
1249 # revision, stored in "fcache". "fcache" is populated by
1249 # revision, stored in "fcache". "fcache" is populated by
1250 # reproducing the graph traversal already done by --follow revset
1250 # reproducing the graph traversal already done by --follow revset
1251 # and relating linkrevs to file names (which is not "correct" but
1251 # and relating linkrevs to file names (which is not "correct" but
1252 # good enough).
1252 # good enough).
1253 fcache = {}
1253 fcache = {}
1254 fcacheready = [False]
1254 fcacheready = [False]
1255 pctx = repo['.']
1255 pctx = repo['.']
1256 wctx = repo[None]
1256 wctx = repo[None]
1257
1257
1258 def populate():
1258 def populate():
1259 for fn in pats:
1259 for fn in pats:
1260 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1260 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1261 for c in i:
1261 for c in i:
1262 fcache.setdefault(c.linkrev(), set()).add(c.path())
1262 fcache.setdefault(c.linkrev(), set()).add(c.path())
1263
1263
1264 def filematcher(rev):
1264 def filematcher(rev):
1265 if not fcacheready[0]:
1265 if not fcacheready[0]:
1266 # Lazy initialization
1266 # Lazy initialization
1267 fcacheready[0] = True
1267 fcacheready[0] = True
1268 populate()
1268 populate()
1269 return scmutil.match(wctx, fcache.get(rev, []), default='path')
1269 return scmutil.match(wctx, fcache.get(rev, []), default='path')
1270
1270
1271 return filematcher
1271 return filematcher
1272
1272
1273 def _makegraphlogrevset(repo, pats, opts, revs):
1273 def _makegraphlogrevset(repo, pats, opts, revs):
1274 """Return (expr, filematcher) where expr is a revset string built
1274 """Return (expr, filematcher) where expr is a revset string built
1275 from log options and file patterns or None. If --stat or --patch
1275 from log options and file patterns or None. If --stat or --patch
1276 are not passed filematcher is None. Otherwise it is a callable
1276 are not passed filematcher is None. Otherwise it is a callable
1277 taking a revision number and returning a match objects filtering
1277 taking a revision number and returning a match objects filtering
1278 the files to be detailed when displaying the revision.
1278 the files to be detailed when displaying the revision.
1279 """
1279 """
1280 opt2revset = {
1280 opt2revset = {
1281 'no_merges': ('not merge()', None),
1281 'no_merges': ('not merge()', None),
1282 'only_merges': ('merge()', None),
1282 'only_merges': ('merge()', None),
1283 '_ancestors': ('ancestors(%(val)s)', None),
1283 '_ancestors': ('ancestors(%(val)s)', None),
1284 '_fancestors': ('_firstancestors(%(val)s)', None),
1284 '_fancestors': ('_firstancestors(%(val)s)', None),
1285 '_descendants': ('descendants(%(val)s)', None),
1285 '_descendants': ('descendants(%(val)s)', None),
1286 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1286 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1287 '_matchfiles': ('_matchfiles(%(val)s)', None),
1287 '_matchfiles': ('_matchfiles(%(val)s)', None),
1288 'date': ('date(%(val)r)', None),
1288 'date': ('date(%(val)r)', None),
1289 'branch': ('branch(%(val)r)', ' or '),
1289 'branch': ('branch(%(val)r)', ' or '),
1290 '_patslog': ('filelog(%(val)r)', ' or '),
1290 '_patslog': ('filelog(%(val)r)', ' or '),
1291 '_patsfollow': ('follow(%(val)r)', ' or '),
1291 '_patsfollow': ('follow(%(val)r)', ' or '),
1292 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1292 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1293 'keyword': ('keyword(%(val)r)', ' or '),
1293 'keyword': ('keyword(%(val)r)', ' or '),
1294 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1294 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1295 'user': ('user(%(val)r)', ' or '),
1295 'user': ('user(%(val)r)', ' or '),
1296 }
1296 }
1297
1297
1298 opts = dict(opts)
1298 opts = dict(opts)
1299 # follow or not follow?
1299 # follow or not follow?
1300 follow = opts.get('follow') or opts.get('follow_first')
1300 follow = opts.get('follow') or opts.get('follow_first')
1301 followfirst = opts.get('follow_first') and 1 or 0
1301 followfirst = opts.get('follow_first') and 1 or 0
1302 # --follow with FILE behaviour depends on revs...
1302 # --follow with FILE behaviour depends on revs...
1303 startrev = revs[0]
1303 startrev = revs[0]
1304 followdescendants = (len(revs) > 1 and revs[0] < revs[1]) and 1 or 0
1304 followdescendants = (len(revs) > 1 and revs[0] < revs[1]) and 1 or 0
1305
1305
1306 # branch and only_branch are really aliases and must be handled at
1306 # branch and only_branch are really aliases and must be handled at
1307 # the same time
1307 # the same time
1308 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1308 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1309 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1309 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1310 # pats/include/exclude are passed to match.match() directly in
1310 # pats/include/exclude are passed to match.match() directly in
1311 # _matchfiles() revset but walkchangerevs() builds its matcher with
1311 # _matchfiles() revset but walkchangerevs() builds its matcher with
1312 # scmutil.match(). The difference is input pats are globbed on
1312 # scmutil.match(). The difference is input pats are globbed on
1313 # platforms without shell expansion (windows).
1313 # platforms without shell expansion (windows).
1314 pctx = repo[None]
1314 pctx = repo[None]
1315 match, pats = scmutil.matchandpats(pctx, pats, opts)
1315 match, pats = scmutil.matchandpats(pctx, pats, opts)
1316 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1316 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1317 if not slowpath:
1317 if not slowpath:
1318 for f in match.files():
1318 for f in match.files():
1319 if follow and f not in pctx:
1319 if follow and f not in pctx:
1320 raise util.Abort(_('cannot follow file not in parent '
1320 raise util.Abort(_('cannot follow file not in parent '
1321 'revision: "%s"') % f)
1321 'revision: "%s"') % f)
1322 filelog = repo.file(f)
1322 filelog = repo.file(f)
1323 if not len(filelog):
1323 if not len(filelog):
1324 # A zero count may be a directory or deleted file, so
1324 # A zero count may be a directory or deleted file, so
1325 # try to find matching entries on the slow path.
1325 # try to find matching entries on the slow path.
1326 if follow:
1326 if follow:
1327 raise util.Abort(
1327 raise util.Abort(
1328 _('cannot follow nonexistent file: "%s"') % f)
1328 _('cannot follow nonexistent file: "%s"') % f)
1329 slowpath = True
1329 slowpath = True
1330
1330
1331 # We decided to fall back to the slowpath because at least one
1331 # We decided to fall back to the slowpath because at least one
1332 # of the paths was not a file. Check to see if at least one of them
1332 # of the paths was not a file. Check to see if at least one of them
1333 # existed in history - in that case, we'll continue down the
1333 # existed in history - in that case, we'll continue down the
1334 # slowpath; otherwise, we can turn off the slowpath
1334 # slowpath; otherwise, we can turn off the slowpath
1335 if slowpath:
1335 if slowpath:
1336 for path in match.files():
1336 for path in match.files():
1337 if path == '.' or path in repo.store:
1337 if path == '.' or path in repo.store:
1338 break
1338 break
1339 else:
1339 else:
1340 slowpath = False
1340 slowpath = False
1341
1341
1342 if slowpath:
1342 if slowpath:
1343 # See walkchangerevs() slow path.
1343 # See walkchangerevs() slow path.
1344 #
1344 #
1345 if follow:
1345 if follow:
1346 raise util.Abort(_('can only follow copies/renames for explicit '
1346 raise util.Abort(_('can only follow copies/renames for explicit '
1347 'filenames'))
1347 'filenames'))
1348 # pats/include/exclude cannot be represented as separate
1348 # pats/include/exclude cannot be represented as separate
1349 # revset expressions as their filtering logic applies at file
1349 # revset expressions as their filtering logic applies at file
1350 # level. For instance "-I a -X a" matches a revision touching
1350 # level. For instance "-I a -X a" matches a revision touching
1351 # "a" and "b" while "file(a) and not file(b)" does
1351 # "a" and "b" while "file(a) and not file(b)" does
1352 # not. Besides, filesets are evaluated against the working
1352 # not. Besides, filesets are evaluated against the working
1353 # directory.
1353 # directory.
1354 matchargs = ['r:', 'd:relpath']
1354 matchargs = ['r:', 'd:relpath']
1355 for p in pats:
1355 for p in pats:
1356 matchargs.append('p:' + p)
1356 matchargs.append('p:' + p)
1357 for p in opts.get('include', []):
1357 for p in opts.get('include', []):
1358 matchargs.append('i:' + p)
1358 matchargs.append('i:' + p)
1359 for p in opts.get('exclude', []):
1359 for p in opts.get('exclude', []):
1360 matchargs.append('x:' + p)
1360 matchargs.append('x:' + p)
1361 matchargs = ','.join(('%r' % p) for p in matchargs)
1361 matchargs = ','.join(('%r' % p) for p in matchargs)
1362 opts['_matchfiles'] = matchargs
1362 opts['_matchfiles'] = matchargs
1363 else:
1363 else:
1364 if follow:
1364 if follow:
1365 fpats = ('_patsfollow', '_patsfollowfirst')
1365 fpats = ('_patsfollow', '_patsfollowfirst')
1366 fnopats = (('_ancestors', '_fancestors'),
1366 fnopats = (('_ancestors', '_fancestors'),
1367 ('_descendants', '_fdescendants'))
1367 ('_descendants', '_fdescendants'))
1368 if pats:
1368 if pats:
1369 # follow() revset interprets its file argument as a
1369 # follow() revset interprets its file argument as a
1370 # manifest entry, so use match.files(), not pats.
1370 # manifest entry, so use match.files(), not pats.
1371 opts[fpats[followfirst]] = list(match.files())
1371 opts[fpats[followfirst]] = list(match.files())
1372 else:
1372 else:
1373 opts[fnopats[followdescendants][followfirst]] = str(startrev)
1373 opts[fnopats[followdescendants][followfirst]] = str(startrev)
1374 else:
1374 else:
1375 opts['_patslog'] = list(pats)
1375 opts['_patslog'] = list(pats)
1376
1376
1377 filematcher = None
1377 filematcher = None
1378 if opts.get('patch') or opts.get('stat'):
1378 if opts.get('patch') or opts.get('stat'):
1379 if follow:
1379 if follow:
1380 filematcher = _makegraphfilematcher(repo, pats, followfirst)
1380 filematcher = _makegraphfilematcher(repo, pats, followfirst)
1381 else:
1381 else:
1382 filematcher = lambda rev: match
1382 filematcher = lambda rev: match
1383
1383
1384 expr = []
1384 expr = []
1385 for op, val in opts.iteritems():
1385 for op, val in opts.iteritems():
1386 if not val:
1386 if not val:
1387 continue
1387 continue
1388 if op not in opt2revset:
1388 if op not in opt2revset:
1389 continue
1389 continue
1390 revop, andor = opt2revset[op]
1390 revop, andor = opt2revset[op]
1391 if '%(val)' not in revop:
1391 if '%(val)' not in revop:
1392 expr.append(revop)
1392 expr.append(revop)
1393 else:
1393 else:
1394 if not isinstance(val, list):
1394 if not isinstance(val, list):
1395 e = revop % {'val': val}
1395 e = revop % {'val': val}
1396 else:
1396 else:
1397 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
1397 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
1398 expr.append(e)
1398 expr.append(e)
1399
1399
1400 if expr:
1400 if expr:
1401 expr = '(' + ' and '.join(expr) + ')'
1401 expr = '(' + ' and '.join(expr) + ')'
1402 else:
1402 else:
1403 expr = None
1403 expr = None
1404 return expr, filematcher
1404 return expr, filematcher
1405
1405
1406 def getgraphlogrevs(repo, pats, opts):
1406 def getgraphlogrevs(repo, pats, opts):
1407 """Return (revs, expr, filematcher) where revs is an iterable of
1407 """Return (revs, expr, filematcher) where revs is an iterable of
1408 revision numbers, expr is a revset string built from log options
1408 revision numbers, expr is a revset string built from log options
1409 and file patterns or None, and used to filter 'revs'. If --stat or
1409 and file patterns or None, and used to filter 'revs'. If --stat or
1410 --patch are not passed filematcher is None. Otherwise it is a
1410 --patch are not passed filematcher is None. Otherwise it is a
1411 callable taking a revision number and returning a match objects
1411 callable taking a revision number and returning a match objects
1412 filtering the files to be detailed when displaying the revision.
1412 filtering the files to be detailed when displaying the revision.
1413 """
1413 """
1414 if not len(repo):
1414 if not len(repo):
1415 return [], None, None
1415 return [], None, None
1416 limit = loglimit(opts)
1416 limit = loglimit(opts)
1417 # Default --rev value depends on --follow but --follow behaviour
1417 # Default --rev value depends on --follow but --follow behaviour
1418 # depends on revisions resolved from --rev...
1418 # depends on revisions resolved from --rev...
1419 follow = opts.get('follow') or opts.get('follow_first')
1419 follow = opts.get('follow') or opts.get('follow_first')
1420 possiblyunsorted = False # whether revs might need sorting
1420 possiblyunsorted = False # whether revs might need sorting
1421 if opts.get('rev'):
1421 if opts.get('rev'):
1422 revs = scmutil.revrange(repo, opts['rev'])
1422 revs = scmutil.revrange(repo, opts['rev'])
1423 # Don't sort here because _makegraphlogrevset might depend on the
1423 # Don't sort here because _makegraphlogrevset might depend on the
1424 # order of revs
1424 # order of revs
1425 possiblyunsorted = True
1425 possiblyunsorted = True
1426 else:
1426 else:
1427 if follow and len(repo) > 0:
1427 if follow and len(repo) > 0:
1428 revs = repo.revs('reverse(:.)')
1428 revs = repo.revs('reverse(:.)')
1429 else:
1429 else:
1430 revs = list(repo.changelog)
1430 revs = list(repo.changelog)
1431 revs.reverse()
1431 revs.reverse()
1432 if not revs:
1432 if not revs:
1433 return [], None, None
1433 return [], None, None
1434 expr, filematcher = _makegraphlogrevset(repo, pats, opts, revs)
1434 expr, filematcher = _makegraphlogrevset(repo, pats, opts, revs)
1435 if possiblyunsorted:
1435 if possiblyunsorted:
1436 revs.sort(reverse=True)
1436 revs.sort(reverse=True)
1437 if expr:
1437 if expr:
1438 # Revset matchers often operate faster on revisions in changelog
1438 # Revset matchers often operate faster on revisions in changelog
1439 # order, because most filters deal with the changelog.
1439 # order, because most filters deal with the changelog.
1440 revs.reverse()
1440 revs.reverse()
1441 matcher = revset.match(repo.ui, expr)
1441 matcher = revset.match(repo.ui, expr)
1442 # Revset matches can reorder revisions. "A or B" typically returns
1442 # Revset matches can reorder revisions. "A or B" typically returns
1443 # returns the revision matching A then the revision matching B. Sort
1443 # returns the revision matching A then the revision matching B. Sort
1444 # again to fix that.
1444 # again to fix that.
1445 revs = matcher(repo, revs)
1445 revs = matcher(repo, revs)
1446 revs.sort(reverse=True)
1446 revs.sort(reverse=True)
1447 if limit is not None:
1447 if limit is not None:
1448 revs = revs[:limit]
1448 revs = revs[:limit]
1449
1449
1450 return revs, expr, filematcher
1450 return revs, expr, filematcher
1451
1451
1452 def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
1452 def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
1453 filematcher=None):
1453 filematcher=None):
1454 seen, state = [], graphmod.asciistate()
1454 seen, state = [], graphmod.asciistate()
1455 for rev, type, ctx, parents in dag:
1455 for rev, type, ctx, parents in dag:
1456 char = 'o'
1456 char = 'o'
1457 if ctx.node() in showparents:
1457 if ctx.node() in showparents:
1458 char = '@'
1458 char = '@'
1459 elif ctx.obsolete():
1459 elif ctx.obsolete():
1460 char = 'x'
1460 char = 'x'
1461 copies = None
1461 copies = None
1462 if getrenamed and ctx.rev():
1462 if getrenamed and ctx.rev():
1463 copies = []
1463 copies = []
1464 for fn in ctx.files():
1464 for fn in ctx.files():
1465 rename = getrenamed(fn, ctx.rev())
1465 rename = getrenamed(fn, ctx.rev())
1466 if rename:
1466 if rename:
1467 copies.append((fn, rename[0]))
1467 copies.append((fn, rename[0]))
1468 revmatchfn = None
1468 revmatchfn = None
1469 if filematcher is not None:
1469 if filematcher is not None:
1470 revmatchfn = filematcher(ctx.rev())
1470 revmatchfn = filematcher(ctx.rev())
1471 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
1471 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
1472 lines = displayer.hunk.pop(rev).split('\n')
1472 lines = displayer.hunk.pop(rev).split('\n')
1473 if not lines[-1]:
1473 if not lines[-1]:
1474 del lines[-1]
1474 del lines[-1]
1475 displayer.flush(rev)
1475 displayer.flush(rev)
1476 edges = edgefn(type, char, lines, seen, rev, parents)
1476 edges = edgefn(type, char, lines, seen, rev, parents)
1477 for type, char, lines, coldata in edges:
1477 for type, char, lines, coldata in edges:
1478 graphmod.ascii(ui, state, type, char, lines, coldata)
1478 graphmod.ascii(ui, state, type, char, lines, coldata)
1479 displayer.close()
1479 displayer.close()
1480
1480
1481 def graphlog(ui, repo, *pats, **opts):
1481 def graphlog(ui, repo, *pats, **opts):
1482 # Parameters are identical to log command ones
1482 # Parameters are identical to log command ones
1483 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
1483 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
1484 revdag = graphmod.dagwalker(repo, revs)
1484 revdag = graphmod.dagwalker(repo, revs)
1485
1485
1486 getrenamed = None
1486 getrenamed = None
1487 if opts.get('copies'):
1487 if opts.get('copies'):
1488 endrev = None
1488 endrev = None
1489 if opts.get('rev'):
1489 if opts.get('rev'):
1490 endrev = max(scmutil.revrange(repo, opts.get('rev'))) + 1
1490 endrev = max(scmutil.revrange(repo, opts.get('rev'))) + 1
1491 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
1491 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
1492 displayer = show_changeset(ui, repo, opts, buffered=True)
1492 displayer = show_changeset(ui, repo, opts, buffered=True)
1493 showparents = [ctx.node() for ctx in repo[None].parents()]
1493 showparents = [ctx.node() for ctx in repo[None].parents()]
1494 displaygraph(ui, revdag, displayer, showparents,
1494 displaygraph(ui, revdag, displayer, showparents,
1495 graphmod.asciiedges, getrenamed, filematcher)
1495 graphmod.asciiedges, getrenamed, filematcher)
1496
1496
1497 def checkunsupportedgraphflags(pats, opts):
1497 def checkunsupportedgraphflags(pats, opts):
1498 for op in ["newest_first"]:
1498 for op in ["newest_first"]:
1499 if op in opts and opts[op]:
1499 if op in opts and opts[op]:
1500 raise util.Abort(_("-G/--graph option is incompatible with --%s")
1500 raise util.Abort(_("-G/--graph option is incompatible with --%s")
1501 % op.replace("_", "-"))
1501 % op.replace("_", "-"))
1502
1502
1503 def graphrevs(repo, nodes, opts):
1503 def graphrevs(repo, nodes, opts):
1504 limit = loglimit(opts)
1504 limit = loglimit(opts)
1505 nodes.reverse()
1505 nodes.reverse()
1506 if limit is not None:
1506 if limit is not None:
1507 nodes = nodes[:limit]
1507 nodes = nodes[:limit]
1508 return graphmod.nodes(repo, nodes)
1508 return graphmod.nodes(repo, nodes)
1509
1509
1510 def add(ui, repo, match, dryrun, listsubrepos, prefix, explicitonly):
1510 def add(ui, repo, match, dryrun, listsubrepos, prefix, explicitonly):
1511 join = lambda f: os.path.join(prefix, f)
1511 join = lambda f: os.path.join(prefix, f)
1512 bad = []
1512 bad = []
1513 oldbad = match.bad
1513 oldbad = match.bad
1514 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1514 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1515 names = []
1515 names = []
1516 wctx = repo[None]
1516 wctx = repo[None]
1517 cca = None
1517 cca = None
1518 abort, warn = scmutil.checkportabilityalert(ui)
1518 abort, warn = scmutil.checkportabilityalert(ui)
1519 if abort or warn:
1519 if abort or warn:
1520 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
1520 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
1521 for f in repo.walk(match):
1521 for f in repo.walk(match):
1522 exact = match.exact(f)
1522 exact = match.exact(f)
1523 if exact or not explicitonly and f not in repo.dirstate:
1523 if exact or not explicitonly and f not in repo.dirstate:
1524 if cca:
1524 if cca:
1525 cca(f)
1525 cca(f)
1526 names.append(f)
1526 names.append(f)
1527 if ui.verbose or not exact:
1527 if ui.verbose or not exact:
1528 ui.status(_('adding %s\n') % match.rel(join(f)))
1528 ui.status(_('adding %s\n') % match.rel(join(f)))
1529
1529
1530 for subpath in sorted(wctx.substate):
1530 for subpath in sorted(wctx.substate):
1531 sub = wctx.sub(subpath)
1531 sub = wctx.sub(subpath)
1532 try:
1532 try:
1533 submatch = matchmod.narrowmatcher(subpath, match)
1533 submatch = matchmod.narrowmatcher(subpath, match)
1534 if listsubrepos:
1534 if listsubrepos:
1535 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1535 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1536 False))
1536 False))
1537 else:
1537 else:
1538 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1538 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1539 True))
1539 True))
1540 except error.LookupError:
1540 except error.LookupError:
1541 ui.status(_("skipping missing subrepository: %s\n")
1541 ui.status(_("skipping missing subrepository: %s\n")
1542 % join(subpath))
1542 % join(subpath))
1543
1543
1544 if not dryrun:
1544 if not dryrun:
1545 rejected = wctx.add(names, prefix)
1545 rejected = wctx.add(names, prefix)
1546 bad.extend(f for f in rejected if f in match.files())
1546 bad.extend(f for f in rejected if f in match.files())
1547 return bad
1547 return bad
1548
1548
1549 def forget(ui, repo, match, prefix, explicitonly):
1549 def forget(ui, repo, match, prefix, explicitonly):
1550 join = lambda f: os.path.join(prefix, f)
1550 join = lambda f: os.path.join(prefix, f)
1551 bad = []
1551 bad = []
1552 oldbad = match.bad
1552 oldbad = match.bad
1553 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1553 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1554 wctx = repo[None]
1554 wctx = repo[None]
1555 forgot = []
1555 forgot = []
1556 s = repo.status(match=match, clean=True)
1556 s = repo.status(match=match, clean=True)
1557 forget = sorted(s[0] + s[1] + s[3] + s[6])
1557 forget = sorted(s[0] + s[1] + s[3] + s[6])
1558 if explicitonly:
1558 if explicitonly:
1559 forget = [f for f in forget if match.exact(f)]
1559 forget = [f for f in forget if match.exact(f)]
1560
1560
1561 for subpath in sorted(wctx.substate):
1561 for subpath in sorted(wctx.substate):
1562 sub = wctx.sub(subpath)
1562 sub = wctx.sub(subpath)
1563 try:
1563 try:
1564 submatch = matchmod.narrowmatcher(subpath, match)
1564 submatch = matchmod.narrowmatcher(subpath, match)
1565 subbad, subforgot = sub.forget(ui, submatch, prefix)
1565 subbad, subforgot = sub.forget(ui, submatch, prefix)
1566 bad.extend([subpath + '/' + f for f in subbad])
1566 bad.extend([subpath + '/' + f for f in subbad])
1567 forgot.extend([subpath + '/' + f for f in subforgot])
1567 forgot.extend([subpath + '/' + f for f in subforgot])
1568 except error.LookupError:
1568 except error.LookupError:
1569 ui.status(_("skipping missing subrepository: %s\n")
1569 ui.status(_("skipping missing subrepository: %s\n")
1570 % join(subpath))
1570 % join(subpath))
1571
1571
1572 if not explicitonly:
1572 if not explicitonly:
1573 for f in match.files():
1573 for f in match.files():
1574 if f not in repo.dirstate and not os.path.isdir(match.rel(join(f))):
1574 if f not in repo.dirstate and not os.path.isdir(match.rel(join(f))):
1575 if f not in forgot:
1575 if f not in forgot:
1576 if os.path.exists(match.rel(join(f))):
1576 if os.path.exists(match.rel(join(f))):
1577 ui.warn(_('not removing %s: '
1577 ui.warn(_('not removing %s: '
1578 'file is already untracked\n')
1578 'file is already untracked\n')
1579 % match.rel(join(f)))
1579 % match.rel(join(f)))
1580 bad.append(f)
1580 bad.append(f)
1581
1581
1582 for f in forget:
1582 for f in forget:
1583 if ui.verbose or not match.exact(f):
1583 if ui.verbose or not match.exact(f):
1584 ui.status(_('removing %s\n') % match.rel(join(f)))
1584 ui.status(_('removing %s\n') % match.rel(join(f)))
1585
1585
1586 rejected = wctx.forget(forget, prefix)
1586 rejected = wctx.forget(forget, prefix)
1587 bad.extend(f for f in rejected if f in match.files())
1587 bad.extend(f for f in rejected if f in match.files())
1588 forgot.extend(forget)
1588 forgot.extend(forget)
1589 return bad, forgot
1589 return bad, forgot
1590
1590
1591 def duplicatecopies(repo, rev, fromrev):
1591 def duplicatecopies(repo, rev, fromrev):
1592 '''reproduce copies from fromrev to rev in the dirstate'''
1592 '''reproduce copies from fromrev to rev in the dirstate'''
1593 for dst, src in copies.pathcopies(repo[fromrev], repo[rev]).iteritems():
1593 for dst, src in copies.pathcopies(repo[fromrev], repo[rev]).iteritems():
1594 # copies.pathcopies returns backward renames, so dst might not
1594 # copies.pathcopies returns backward renames, so dst might not
1595 # actually be in the dirstate
1595 # actually be in the dirstate
1596 if repo.dirstate[dst] in "nma":
1596 if repo.dirstate[dst] in "nma":
1597 repo.dirstate.copy(src, dst)
1597 repo.dirstate.copy(src, dst)
1598
1598
1599 def commit(ui, repo, commitfunc, pats, opts):
1599 def commit(ui, repo, commitfunc, pats, opts):
1600 '''commit the specified files or all outstanding changes'''
1600 '''commit the specified files or all outstanding changes'''
1601 date = opts.get('date')
1601 date = opts.get('date')
1602 if date:
1602 if date:
1603 opts['date'] = util.parsedate(date)
1603 opts['date'] = util.parsedate(date)
1604 message = logmessage(ui, opts)
1604 message = logmessage(ui, opts)
1605
1605
1606 # extract addremove carefully -- this function can be called from a command
1606 # extract addremove carefully -- this function can be called from a command
1607 # that doesn't support addremove
1607 # that doesn't support addremove
1608 if opts.get('addremove'):
1608 if opts.get('addremove'):
1609 scmutil.addremove(repo, pats, opts)
1609 scmutil.addremove(repo, pats, opts)
1610
1610
1611 return commitfunc(ui, repo, message,
1611 return commitfunc(ui, repo, message,
1612 scmutil.match(repo[None], pats, opts), opts)
1612 scmutil.match(repo[None], pats, opts), opts)
1613
1613
1614 def amend(ui, repo, commitfunc, old, extra, pats, opts):
1614 def amend(ui, repo, commitfunc, old, extra, pats, opts):
1615 ui.note(_('amending changeset %s\n') % old)
1615 ui.note(_('amending changeset %s\n') % old)
1616 base = old.p1()
1616 base = old.p1()
1617
1617
1618 wlock = lock = newid = None
1618 wlock = lock = newid = None
1619 try:
1619 try:
1620 wlock = repo.wlock()
1620 wlock = repo.wlock()
1621 lock = repo.lock()
1621 lock = repo.lock()
1622 tr = repo.transaction('amend')
1622 tr = repo.transaction('amend')
1623 try:
1623 try:
1624 # See if we got a message from -m or -l, if not, open the editor
1624 # See if we got a message from -m or -l, if not, open the editor
1625 # with the message of the changeset to amend
1625 # with the message of the changeset to amend
1626 message = logmessage(ui, opts)
1626 message = logmessage(ui, opts)
1627 # ensure logfile does not conflict with later enforcement of the
1627 # ensure logfile does not conflict with later enforcement of the
1628 # message. potential logfile content has been processed by
1628 # message. potential logfile content has been processed by
1629 # `logmessage` anyway.
1629 # `logmessage` anyway.
1630 opts.pop('logfile')
1630 opts.pop('logfile')
1631 # First, do a regular commit to record all changes in the working
1631 # First, do a regular commit to record all changes in the working
1632 # directory (if there are any)
1632 # directory (if there are any)
1633 ui.callhooks = False
1633 ui.callhooks = False
1634 currentbookmark = repo._bookmarkcurrent
1634 currentbookmark = repo._bookmarkcurrent
1635 try:
1635 try:
1636 repo._bookmarkcurrent = None
1636 repo._bookmarkcurrent = None
1637 opts['message'] = 'temporary amend commit for %s' % old
1637 opts['message'] = 'temporary amend commit for %s' % old
1638 node = commit(ui, repo, commitfunc, pats, opts)
1638 node = commit(ui, repo, commitfunc, pats, opts)
1639 finally:
1639 finally:
1640 repo._bookmarkcurrent = currentbookmark
1640 repo._bookmarkcurrent = currentbookmark
1641 ui.callhooks = True
1641 ui.callhooks = True
1642 ctx = repo[node]
1642 ctx = repo[node]
1643
1643
1644 # Participating changesets:
1644 # Participating changesets:
1645 #
1645 #
1646 # node/ctx o - new (intermediate) commit that contains changes
1646 # node/ctx o - new (intermediate) commit that contains changes
1647 # | from working dir to go into amending commit
1647 # | from working dir to go into amending commit
1648 # | (or a workingctx if there were no changes)
1648 # | (or a workingctx if there were no changes)
1649 # |
1649 # |
1650 # old o - changeset to amend
1650 # old o - changeset to amend
1651 # |
1651 # |
1652 # base o - parent of amending changeset
1652 # base o - parent of amending changeset
1653
1653
1654 # Update extra dict from amended commit (e.g. to preserve graft
1654 # Update extra dict from amended commit (e.g. to preserve graft
1655 # source)
1655 # source)
1656 extra.update(old.extra())
1656 extra.update(old.extra())
1657
1657
1658 # Also update it from the intermediate commit or from the wctx
1658 # Also update it from the intermediate commit or from the wctx
1659 extra.update(ctx.extra())
1659 extra.update(ctx.extra())
1660
1660
1661 if len(old.parents()) > 1:
1661 if len(old.parents()) > 1:
1662 # ctx.files() isn't reliable for merges, so fall back to the
1662 # ctx.files() isn't reliable for merges, so fall back to the
1663 # slower repo.status() method
1663 # slower repo.status() method
1664 files = set([fn for st in repo.status(base, old)[:3]
1664 files = set([fn for st in repo.status(base, old)[:3]
1665 for fn in st])
1665 for fn in st])
1666 else:
1666 else:
1667 files = set(old.files())
1667 files = set(old.files())
1668
1668
1669 # Second, we use either the commit we just did, or if there were no
1669 # Second, we use either the commit we just did, or if there were no
1670 # changes the parent of the working directory as the version of the
1670 # changes the parent of the working directory as the version of the
1671 # files in the final amend commit
1671 # files in the final amend commit
1672 if node:
1672 if node:
1673 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
1673 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
1674
1674
1675 user = ctx.user()
1675 user = ctx.user()
1676 date = ctx.date()
1676 date = ctx.date()
1677 # Recompute copies (avoid recording a -> b -> a)
1677 # Recompute copies (avoid recording a -> b -> a)
1678 copied = copies.pathcopies(base, ctx)
1678 copied = copies.pathcopies(base, ctx)
1679
1679
1680 # Prune files which were reverted by the updates: if old
1680 # Prune files which were reverted by the updates: if old
1681 # introduced file X and our intermediate commit, node,
1681 # introduced file X and our intermediate commit, node,
1682 # renamed that file, then those two files are the same and
1682 # renamed that file, then those two files are the same and
1683 # we can discard X from our list of files. Likewise if X
1683 # we can discard X from our list of files. Likewise if X
1684 # was deleted, it's no longer relevant
1684 # was deleted, it's no longer relevant
1685 files.update(ctx.files())
1685 files.update(ctx.files())
1686
1686
1687 def samefile(f):
1687 def samefile(f):
1688 if f in ctx.manifest():
1688 if f in ctx.manifest():
1689 a = ctx.filectx(f)
1689 a = ctx.filectx(f)
1690 if f in base.manifest():
1690 if f in base.manifest():
1691 b = base.filectx(f)
1691 b = base.filectx(f)
1692 return (not a.cmp(b)
1692 return (not a.cmp(b)
1693 and a.flags() == b.flags())
1693 and a.flags() == b.flags())
1694 else:
1694 else:
1695 return False
1695 return False
1696 else:
1696 else:
1697 return f not in base.manifest()
1697 return f not in base.manifest()
1698 files = [f for f in files if not samefile(f)]
1698 files = [f for f in files if not samefile(f)]
1699
1699
1700 def filectxfn(repo, ctx_, path):
1700 def filectxfn(repo, ctx_, path):
1701 try:
1701 try:
1702 fctx = ctx[path]
1702 fctx = ctx[path]
1703 flags = fctx.flags()
1703 flags = fctx.flags()
1704 mctx = context.memfilectx(fctx.path(), fctx.data(),
1704 mctx = context.memfilectx(fctx.path(), fctx.data(),
1705 islink='l' in flags,
1705 islink='l' in flags,
1706 isexec='x' in flags,
1706 isexec='x' in flags,
1707 copied=copied.get(path))
1707 copied=copied.get(path))
1708 return mctx
1708 return mctx
1709 except KeyError:
1709 except KeyError:
1710 raise IOError
1710 raise IOError
1711 else:
1711 else:
1712 ui.note(_('copying changeset %s to %s\n') % (old, base))
1712 ui.note(_('copying changeset %s to %s\n') % (old, base))
1713
1713
1714 # Use version of files as in the old cset
1714 # Use version of files as in the old cset
1715 def filectxfn(repo, ctx_, path):
1715 def filectxfn(repo, ctx_, path):
1716 try:
1716 try:
1717 return old.filectx(path)
1717 return old.filectx(path)
1718 except KeyError:
1718 except KeyError:
1719 raise IOError
1719 raise IOError
1720
1720
1721 user = opts.get('user') or old.user()
1721 user = opts.get('user') or old.user()
1722 date = opts.get('date') or old.date()
1722 date = opts.get('date') or old.date()
1723 editmsg = False
1723 editmsg = False
1724 if not message:
1724 if not message:
1725 editmsg = True
1725 editmsg = True
1726 message = old.description()
1726 message = old.description()
1727
1727
1728 pureextra = extra.copy()
1728 pureextra = extra.copy()
1729 extra['amend_source'] = old.hex()
1729 extra['amend_source'] = old.hex()
1730
1730
1731 new = context.memctx(repo,
1731 new = context.memctx(repo,
1732 parents=[base.node(), old.p2().node()],
1732 parents=[base.node(), old.p2().node()],
1733 text=message,
1733 text=message,
1734 files=files,
1734 files=files,
1735 filectxfn=filectxfn,
1735 filectxfn=filectxfn,
1736 user=user,
1736 user=user,
1737 date=date,
1737 date=date,
1738 extra=extra)
1738 extra=extra)
1739 if editmsg:
1739 if editmsg:
1740 new._text = commitforceeditor(repo, new, [])
1740 new._text = commitforceeditor(repo, new, [])
1741
1741
1742 newdesc = changelog.stripdesc(new.description())
1742 newdesc = changelog.stripdesc(new.description())
1743 if ((not node)
1743 if ((not node)
1744 and newdesc == old.description()
1744 and newdesc == old.description()
1745 and user == old.user()
1745 and user == old.user()
1746 and date == old.date()
1746 and date == old.date()
1747 and pureextra == old.extra()):
1747 and pureextra == old.extra()):
1748 # nothing changed. continuing here would create a new node
1748 # nothing changed. continuing here would create a new node
1749 # anyway because of the amend_source noise.
1749 # anyway because of the amend_source noise.
1750 #
1750 #
1751 # This not what we expect from amend.
1751 # This not what we expect from amend.
1752 return old.node()
1752 return old.node()
1753
1753
1754 ph = repo.ui.config('phases', 'new-commit', phases.draft)
1754 ph = repo.ui.config('phases', 'new-commit', phases.draft)
1755 try:
1755 try:
1756 repo.ui.setconfig('phases', 'new-commit', old.phase())
1756 repo.ui.setconfig('phases', 'new-commit', old.phase())
1757 newid = repo.commitctx(new)
1757 newid = repo.commitctx(new)
1758 finally:
1758 finally:
1759 repo.ui.setconfig('phases', 'new-commit', ph)
1759 repo.ui.setconfig('phases', 'new-commit', ph)
1760 if newid != old.node():
1760 if newid != old.node():
1761 # Reroute the working copy parent to the new changeset
1761 # Reroute the working copy parent to the new changeset
1762 repo.setparents(newid, nullid)
1762 repo.setparents(newid, nullid)
1763
1763
1764 # Move bookmarks from old parent to amend commit
1764 # Move bookmarks from old parent to amend commit
1765 bms = repo.nodebookmarks(old.node())
1765 bms = repo.nodebookmarks(old.node())
1766 if bms:
1766 if bms:
1767 marks = repo._bookmarks
1767 marks = repo._bookmarks
1768 for bm in bms:
1768 for bm in bms:
1769 marks[bm] = newid
1769 marks[bm] = newid
1770 marks.write()
1770 marks.write()
1771 #commit the whole amend process
1771 #commit the whole amend process
1772 if obsolete._enabled and newid != old.node():
1772 if obsolete._enabled and newid != old.node():
1773 # mark the new changeset as successor of the rewritten one
1773 # mark the new changeset as successor of the rewritten one
1774 new = repo[newid]
1774 new = repo[newid]
1775 obs = [(old, (new,))]
1775 obs = [(old, (new,))]
1776 if node:
1776 if node:
1777 obs.append((ctx, ()))
1777 obs.append((ctx, ()))
1778
1778
1779 obsolete.createmarkers(repo, obs)
1779 obsolete.createmarkers(repo, obs)
1780 tr.close()
1780 tr.close()
1781 finally:
1781 finally:
1782 tr.release()
1782 tr.release()
1783 if (not obsolete._enabled) and newid != old.node():
1783 if (not obsolete._enabled) and newid != old.node():
1784 # Strip the intermediate commit (if there was one) and the amended
1784 # Strip the intermediate commit (if there was one) and the amended
1785 # commit
1785 # commit
1786 if node:
1786 if node:
1787 ui.note(_('stripping intermediate changeset %s\n') % ctx)
1787 ui.note(_('stripping intermediate changeset %s\n') % ctx)
1788 ui.note(_('stripping amended changeset %s\n') % old)
1788 ui.note(_('stripping amended changeset %s\n') % old)
1789 repair.strip(ui, repo, old.node(), topic='amend-backup')
1789 repair.strip(ui, repo, old.node(), topic='amend-backup')
1790 finally:
1790 finally:
1791 if newid is None:
1791 if newid is None:
1792 repo.dirstate.invalidate()
1792 repo.dirstate.invalidate()
1793 lockmod.release(lock, wlock)
1793 lockmod.release(lock, wlock)
1794 return newid
1794 return newid
1795
1795
1796 def commiteditor(repo, ctx, subs):
1796 def commiteditor(repo, ctx, subs):
1797 if ctx.description():
1797 if ctx.description():
1798 return ctx.description()
1798 return ctx.description()
1799 return commitforceeditor(repo, ctx, subs)
1799 return commitforceeditor(repo, ctx, subs)
1800
1800
1801 def commitforceeditor(repo, ctx, subs):
1801 def commitforceeditor(repo, ctx, subs):
1802 edittext = []
1802 edittext = []
1803 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1803 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1804 if ctx.description():
1804 if ctx.description():
1805 edittext.append(ctx.description())
1805 edittext.append(ctx.description())
1806 edittext.append("")
1806 edittext.append("")
1807 edittext.append("") # Empty line between message and comments.
1807 edittext.append("") # Empty line between message and comments.
1808 edittext.append(_("HG: Enter commit message."
1808 edittext.append(_("HG: Enter commit message."
1809 " Lines beginning with 'HG:' are removed."))
1809 " Lines beginning with 'HG:' are removed."))
1810 edittext.append(_("HG: Leave message empty to abort commit."))
1810 edittext.append(_("HG: Leave message empty to abort commit."))
1811 edittext.append("HG: --")
1811 edittext.append("HG: --")
1812 edittext.append(_("HG: user: %s") % ctx.user())
1812 edittext.append(_("HG: user: %s") % ctx.user())
1813 if ctx.p2():
1813 if ctx.p2():
1814 edittext.append(_("HG: branch merge"))
1814 edittext.append(_("HG: branch merge"))
1815 if ctx.branch():
1815 if ctx.branch():
1816 edittext.append(_("HG: branch '%s'") % ctx.branch())
1816 edittext.append(_("HG: branch '%s'") % ctx.branch())
1817 if bookmarks.iscurrent(repo):
1817 if bookmarks.iscurrent(repo):
1818 edittext.append(_("HG: bookmark '%s'") % repo._bookmarkcurrent)
1818 edittext.append(_("HG: bookmark '%s'") % repo._bookmarkcurrent)
1819 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1819 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1820 edittext.extend([_("HG: added %s") % f for f in added])
1820 edittext.extend([_("HG: added %s") % f for f in added])
1821 edittext.extend([_("HG: changed %s") % f for f in modified])
1821 edittext.extend([_("HG: changed %s") % f for f in modified])
1822 edittext.extend([_("HG: removed %s") % f for f in removed])
1822 edittext.extend([_("HG: removed %s") % f for f in removed])
1823 if not added and not modified and not removed:
1823 if not added and not modified and not removed:
1824 edittext.append(_("HG: no files changed"))
1824 edittext.append(_("HG: no files changed"))
1825 edittext.append("")
1825 edittext.append("")
1826 # run editor in the repository root
1826 # run editor in the repository root
1827 olddir = os.getcwd()
1827 olddir = os.getcwd()
1828 os.chdir(repo.root)
1828 os.chdir(repo.root)
1829 text = repo.ui.edit("\n".join(edittext), ctx.user())
1829 text = repo.ui.edit("\n".join(edittext), ctx.user())
1830 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
1830 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
1831 os.chdir(olddir)
1831 os.chdir(olddir)
1832
1832
1833 if not text.strip():
1833 if not text.strip():
1834 raise util.Abort(_("empty commit message"))
1834 raise util.Abort(_("empty commit message"))
1835
1835
1836 return text
1836 return text
1837
1837
1838 def commitstatus(repo, node, branch, bheads=None, opts={}):
1838 def commitstatus(repo, node, branch, bheads=None, opts={}):
1839 ctx = repo[node]
1839 ctx = repo[node]
1840 parents = ctx.parents()
1840 parents = ctx.parents()
1841
1841
1842 if (not opts.get('amend') and bheads and node not in bheads and not
1842 if (not opts.get('amend') and bheads and node not in bheads and not
1843 [x for x in parents if x.node() in bheads and x.branch() == branch]):
1843 [x for x in parents if x.node() in bheads and x.branch() == branch]):
1844 repo.ui.status(_('created new head\n'))
1844 repo.ui.status(_('created new head\n'))
1845 # The message is not printed for initial roots. For the other
1845 # The message is not printed for initial roots. For the other
1846 # changesets, it is printed in the following situations:
1846 # changesets, it is printed in the following situations:
1847 #
1847 #
1848 # Par column: for the 2 parents with ...
1848 # Par column: for the 2 parents with ...
1849 # N: null or no parent
1849 # N: null or no parent
1850 # B: parent is on another named branch
1850 # B: parent is on another named branch
1851 # C: parent is a regular non head changeset
1851 # C: parent is a regular non head changeset
1852 # H: parent was a branch head of the current branch
1852 # H: parent was a branch head of the current branch
1853 # Msg column: whether we print "created new head" message
1853 # Msg column: whether we print "created new head" message
1854 # In the following, it is assumed that there already exists some
1854 # In the following, it is assumed that there already exists some
1855 # initial branch heads of the current branch, otherwise nothing is
1855 # initial branch heads of the current branch, otherwise nothing is
1856 # printed anyway.
1856 # printed anyway.
1857 #
1857 #
1858 # Par Msg Comment
1858 # Par Msg Comment
1859 # N N y additional topo root
1859 # N N y additional topo root
1860 #
1860 #
1861 # B N y additional branch root
1861 # B N y additional branch root
1862 # C N y additional topo head
1862 # C N y additional topo head
1863 # H N n usual case
1863 # H N n usual case
1864 #
1864 #
1865 # B B y weird additional branch root
1865 # B B y weird additional branch root
1866 # C B y branch merge
1866 # C B y branch merge
1867 # H B n merge with named branch
1867 # H B n merge with named branch
1868 #
1868 #
1869 # C C y additional head from merge
1869 # C C y additional head from merge
1870 # C H n merge with a head
1870 # C H n merge with a head
1871 #
1871 #
1872 # H H n head merge: head count decreases
1872 # H H n head merge: head count decreases
1873
1873
1874 if not opts.get('close_branch'):
1874 if not opts.get('close_branch'):
1875 for r in parents:
1875 for r in parents:
1876 if r.closesbranch() and r.branch() == branch:
1876 if r.closesbranch() and r.branch() == branch:
1877 repo.ui.status(_('reopening closed branch head %d\n') % r)
1877 repo.ui.status(_('reopening closed branch head %d\n') % r)
1878
1878
1879 if repo.ui.debugflag:
1879 if repo.ui.debugflag:
1880 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
1880 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
1881 elif repo.ui.verbose:
1881 elif repo.ui.verbose:
1882 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
1882 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
1883
1883
1884 def revert(ui, repo, ctx, parents, *pats, **opts):
1884 def revert(ui, repo, ctx, parents, *pats, **opts):
1885 parent, p2 = parents
1885 parent, p2 = parents
1886 node = ctx.node()
1886 node = ctx.node()
1887
1887
1888 mf = ctx.manifest()
1888 mf = ctx.manifest()
1889 if node == parent:
1889 if node == parent:
1890 pmf = mf
1890 pmf = mf
1891 else:
1891 else:
1892 pmf = None
1892 pmf = None
1893
1893
1894 # need all matching names in dirstate and manifest of target rev,
1894 # need all matching names in dirstate and manifest of target rev,
1895 # so have to walk both. do not print errors if files exist in one
1895 # so have to walk both. do not print errors if files exist in one
1896 # but not other.
1896 # but not other.
1897
1897
1898 names = {}
1898 names = {}
1899
1899
1900 wlock = repo.wlock()
1900 wlock = repo.wlock()
1901 try:
1901 try:
1902 # walk dirstate.
1902 # walk dirstate.
1903
1903
1904 m = scmutil.match(repo[None], pats, opts)
1904 m = scmutil.match(repo[None], pats, opts)
1905 m.bad = lambda x, y: False
1905 m.bad = lambda x, y: False
1906 for abs in repo.walk(m):
1906 for abs in repo.walk(m):
1907 names[abs] = m.rel(abs), m.exact(abs)
1907 names[abs] = m.rel(abs), m.exact(abs)
1908
1908
1909 # walk target manifest.
1909 # walk target manifest.
1910
1910
1911 def badfn(path, msg):
1911 def badfn(path, msg):
1912 if path in names:
1912 if path in names:
1913 return
1913 return
1914 if path in ctx.substate:
1914 if path in ctx.substate:
1915 return
1915 return
1916 path_ = path + '/'
1916 path_ = path + '/'
1917 for f in names:
1917 for f in names:
1918 if f.startswith(path_):
1918 if f.startswith(path_):
1919 return
1919 return
1920 ui.warn("%s: %s\n" % (m.rel(path), msg))
1920 ui.warn("%s: %s\n" % (m.rel(path), msg))
1921
1921
1922 m = scmutil.match(ctx, pats, opts)
1922 m = scmutil.match(ctx, pats, opts)
1923 m.bad = badfn
1923 m.bad = badfn
1924 for abs in ctx.walk(m):
1924 for abs in ctx.walk(m):
1925 if abs not in names:
1925 if abs not in names:
1926 names[abs] = m.rel(abs), m.exact(abs)
1926 names[abs] = m.rel(abs), m.exact(abs)
1927
1927
1928 # get the list of subrepos that must be reverted
1928 # get the list of subrepos that must be reverted
1929 targetsubs = sorted(s for s in ctx.substate if m(s))
1929 targetsubs = sorted(s for s in ctx.substate if m(s))
1930 m = scmutil.matchfiles(repo, names)
1930 m = scmutil.matchfiles(repo, names)
1931 changes = repo.status(match=m)[:4]
1931 changes = repo.status(match=m)[:4]
1932 modified, added, removed, deleted = map(set, changes)
1932 modified, added, removed, deleted = map(set, changes)
1933
1933
1934 # if f is a rename, also revert the source
1934 # if f is a rename, also revert the source
1935 cwd = repo.getcwd()
1935 cwd = repo.getcwd()
1936 for f in added:
1936 for f in added:
1937 src = repo.dirstate.copied(f)
1937 src = repo.dirstate.copied(f)
1938 if src and src not in names and repo.dirstate[src] == 'r':
1938 if src and src not in names and repo.dirstate[src] == 'r':
1939 removed.add(src)
1939 removed.add(src)
1940 names[src] = (repo.pathto(src, cwd), True)
1940 names[src] = (repo.pathto(src, cwd), True)
1941
1941
1942 def removeforget(abs):
1942 def removeforget(abs):
1943 if repo.dirstate[abs] == 'a':
1943 if repo.dirstate[abs] == 'a':
1944 return _('forgetting %s\n')
1944 return _('forgetting %s\n')
1945 return _('removing %s\n')
1945 return _('removing %s\n')
1946
1946
1947 revert = ([], _('reverting %s\n'))
1947 revert = ([], _('reverting %s\n'))
1948 add = ([], _('adding %s\n'))
1948 add = ([], _('adding %s\n'))
1949 remove = ([], removeforget)
1949 remove = ([], removeforget)
1950 undelete = ([], _('undeleting %s\n'))
1950 undelete = ([], _('undeleting %s\n'))
1951
1951
1952 disptable = (
1952 disptable = (
1953 # dispatch table:
1953 # dispatch table:
1954 # file state
1954 # file state
1955 # action if in target manifest
1955 # action if in target manifest
1956 # action if not in target manifest
1956 # action if not in target manifest
1957 # make backup if in target manifest
1957 # make backup if in target manifest
1958 # make backup if not in target manifest
1958 # make backup if not in target manifest
1959 (modified, revert, remove, True, True),
1959 (modified, revert, remove, True, True),
1960 (added, revert, remove, True, False),
1960 (added, revert, remove, True, False),
1961 (removed, undelete, None, False, False),
1961 (removed, undelete, None, False, False),
1962 (deleted, revert, remove, False, False),
1962 (deleted, revert, remove, False, False),
1963 )
1963 )
1964
1964
1965 for abs, (rel, exact) in sorted(names.items()):
1965 for abs, (rel, exact) in sorted(names.items()):
1966 mfentry = mf.get(abs)
1966 mfentry = mf.get(abs)
1967 target = repo.wjoin(abs)
1967 target = repo.wjoin(abs)
1968 def handle(xlist, dobackup):
1968 def handle(xlist, dobackup):
1969 xlist[0].append(abs)
1969 xlist[0].append(abs)
1970 if (dobackup and not opts.get('no_backup') and
1970 if (dobackup and not opts.get('no_backup') and
1971 os.path.lexists(target)):
1971 os.path.lexists(target)):
1972 bakname = "%s.orig" % rel
1972 bakname = "%s.orig" % rel
1973 ui.note(_('saving current version of %s as %s\n') %
1973 ui.note(_('saving current version of %s as %s\n') %
1974 (rel, bakname))
1974 (rel, bakname))
1975 if not opts.get('dry_run'):
1975 if not opts.get('dry_run'):
1976 util.rename(target, bakname)
1976 util.rename(target, bakname)
1977 if ui.verbose or not exact:
1977 if ui.verbose or not exact:
1978 msg = xlist[1]
1978 msg = xlist[1]
1979 if not isinstance(msg, basestring):
1979 if not isinstance(msg, basestring):
1980 msg = msg(abs)
1980 msg = msg(abs)
1981 ui.status(msg % rel)
1981 ui.status(msg % rel)
1982 for table, hitlist, misslist, backuphit, backupmiss in disptable:
1982 for table, hitlist, misslist, backuphit, backupmiss in disptable:
1983 if abs not in table:
1983 if abs not in table:
1984 continue
1984 continue
1985 # file has changed in dirstate
1985 # file has changed in dirstate
1986 if mfentry:
1986 if mfentry:
1987 handle(hitlist, backuphit)
1987 handle(hitlist, backuphit)
1988 elif misslist is not None:
1988 elif misslist is not None:
1989 handle(misslist, backupmiss)
1989 handle(misslist, backupmiss)
1990 break
1990 break
1991 else:
1991 else:
1992 if abs not in repo.dirstate:
1992 if abs not in repo.dirstate:
1993 if mfentry:
1993 if mfentry:
1994 handle(add, True)
1994 handle(add, True)
1995 elif exact:
1995 elif exact:
1996 ui.warn(_('file not managed: %s\n') % rel)
1996 ui.warn(_('file not managed: %s\n') % rel)
1997 continue
1997 continue
1998 # file has not changed in dirstate
1998 # file has not changed in dirstate
1999 if node == parent:
1999 if node == parent:
2000 if exact:
2000 if exact:
2001 ui.warn(_('no changes needed to %s\n') % rel)
2001 ui.warn(_('no changes needed to %s\n') % rel)
2002 continue
2002 continue
2003 if pmf is None:
2003 if pmf is None:
2004 # only need parent manifest in this unlikely case,
2004 # only need parent manifest in this unlikely case,
2005 # so do not read by default
2005 # so do not read by default
2006 pmf = repo[parent].manifest()
2006 pmf = repo[parent].manifest()
2007 if abs in pmf and mfentry:
2007 if abs in pmf and mfentry:
2008 # if version of file is same in parent and target
2008 # if version of file is same in parent and target
2009 # manifests, do nothing
2009 # manifests, do nothing
2010 if (pmf[abs] != mfentry or
2010 if (pmf[abs] != mfentry or
2011 pmf.flags(abs) != mf.flags(abs)):
2011 pmf.flags(abs) != mf.flags(abs)):
2012 handle(revert, False)
2012 handle(revert, False)
2013 else:
2013 else:
2014 handle(remove, False)
2014 handle(remove, False)
2015
2015
2016 if not opts.get('dry_run'):
2016 if not opts.get('dry_run'):
2017 def checkout(f):
2017 def checkout(f):
2018 fc = ctx[f]
2018 fc = ctx[f]
2019 repo.wwrite(f, fc.data(), fc.flags())
2019 repo.wwrite(f, fc.data(), fc.flags())
2020
2020
2021 audit_path = scmutil.pathauditor(repo.root)
2021 audit_path = scmutil.pathauditor(repo.root)
2022 for f in remove[0]:
2022 for f in remove[0]:
2023 if repo.dirstate[f] == 'a':
2023 if repo.dirstate[f] == 'a':
2024 repo.dirstate.drop(f)
2024 repo.dirstate.drop(f)
2025 continue
2025 continue
2026 audit_path(f)
2026 audit_path(f)
2027 try:
2027 try:
2028 util.unlinkpath(repo.wjoin(f))
2028 util.unlinkpath(repo.wjoin(f))
2029 except OSError:
2029 except OSError:
2030 pass
2030 pass
2031 repo.dirstate.remove(f)
2031 repo.dirstate.remove(f)
2032
2032
2033 normal = None
2033 normal = None
2034 if node == parent:
2034 if node == parent:
2035 # We're reverting to our parent. If possible, we'd like status
2035 # We're reverting to our parent. If possible, we'd like status
2036 # to report the file as clean. We have to use normallookup for
2036 # to report the file as clean. We have to use normallookup for
2037 # merges to avoid losing information about merged/dirty files.
2037 # merges to avoid losing information about merged/dirty files.
2038 if p2 != nullid:
2038 if p2 != nullid:
2039 normal = repo.dirstate.normallookup
2039 normal = repo.dirstate.normallookup
2040 else:
2040 else:
2041 normal = repo.dirstate.normal
2041 normal = repo.dirstate.normal
2042 for f in revert[0]:
2042 for f in revert[0]:
2043 checkout(f)
2043 checkout(f)
2044 if normal:
2044 if normal:
2045 normal(f)
2045 normal(f)
2046
2046
2047 for f in add[0]:
2047 for f in add[0]:
2048 checkout(f)
2048 checkout(f)
2049 repo.dirstate.add(f)
2049 repo.dirstate.add(f)
2050
2050
2051 normal = repo.dirstate.normallookup
2051 normal = repo.dirstate.normallookup
2052 if node == parent and p2 == nullid:
2052 if node == parent and p2 == nullid:
2053 normal = repo.dirstate.normal
2053 normal = repo.dirstate.normal
2054 for f in undelete[0]:
2054 for f in undelete[0]:
2055 checkout(f)
2055 checkout(f)
2056 normal(f)
2056 normal(f)
2057
2057
2058 copied = copies.pathcopies(repo[parent], ctx)
2059
2060 for f in add[0] + undelete[0] + revert[0]:
2061 if f in copied:
2062 repo.dirstate.copy(copied[f], f)
2063
2058 if targetsubs:
2064 if targetsubs:
2059 # Revert the subrepos on the revert list
2065 # Revert the subrepos on the revert list
2060 for sub in targetsubs:
2066 for sub in targetsubs:
2061 ctx.sub(sub).revert(ui, ctx.substate[sub], *pats, **opts)
2067 ctx.sub(sub).revert(ui, ctx.substate[sub], *pats, **opts)
2062 finally:
2068 finally:
2063 wlock.release()
2069 wlock.release()
2064
2070
2065 def command(table):
2071 def command(table):
2066 '''returns a function object bound to table which can be used as
2072 '''returns a function object bound to table which can be used as
2067 a decorator for populating table as a command table'''
2073 a decorator for populating table as a command table'''
2068
2074
2069 def cmd(name, options=(), synopsis=None):
2075 def cmd(name, options=(), synopsis=None):
2070 def decorator(func):
2076 def decorator(func):
2071 if synopsis:
2077 if synopsis:
2072 table[name] = func, list(options), synopsis
2078 table[name] = func, list(options), synopsis
2073 else:
2079 else:
2074 table[name] = func, list(options)
2080 table[name] = func, list(options)
2075 return func
2081 return func
2076 return decorator
2082 return decorator
2077
2083
2078 return cmd
2084 return cmd
@@ -1,815 +1,815 b''
1 # dirstate.py - working directory tracking for mercurial
1 # dirstate.py - working directory tracking for 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 import errno
7 import errno
8
8
9 from node import nullid
9 from node import nullid
10 from i18n import _
10 from i18n import _
11 import scmutil, util, ignore, osutil, parsers, encoding
11 import scmutil, util, ignore, osutil, parsers, encoding
12 import os, stat, errno, gc
12 import os, stat, errno, gc
13
13
14 propertycache = util.propertycache
14 propertycache = util.propertycache
15 filecache = scmutil.filecache
15 filecache = scmutil.filecache
16 _rangemask = 0x7fffffff
16 _rangemask = 0x7fffffff
17
17
18 class repocache(filecache):
18 class repocache(filecache):
19 """filecache for files in .hg/"""
19 """filecache for files in .hg/"""
20 def join(self, obj, fname):
20 def join(self, obj, fname):
21 return obj._opener.join(fname)
21 return obj._opener.join(fname)
22
22
23 class rootcache(filecache):
23 class rootcache(filecache):
24 """filecache for files in the repository root"""
24 """filecache for files in the repository root"""
25 def join(self, obj, fname):
25 def join(self, obj, fname):
26 return obj._join(fname)
26 return obj._join(fname)
27
27
28 class dirstate(object):
28 class dirstate(object):
29
29
30 def __init__(self, opener, ui, root, validate):
30 def __init__(self, opener, ui, root, validate):
31 '''Create a new dirstate object.
31 '''Create a new dirstate object.
32
32
33 opener is an open()-like callable that can be used to open the
33 opener is an open()-like callable that can be used to open the
34 dirstate file; root is the root of the directory tracked by
34 dirstate file; root is the root of the directory tracked by
35 the dirstate.
35 the dirstate.
36 '''
36 '''
37 self._opener = opener
37 self._opener = opener
38 self._validate = validate
38 self._validate = validate
39 self._root = root
39 self._root = root
40 self._rootdir = os.path.join(root, '')
40 self._rootdir = os.path.join(root, '')
41 self._dirty = False
41 self._dirty = False
42 self._dirtypl = False
42 self._dirtypl = False
43 self._lastnormaltime = 0
43 self._lastnormaltime = 0
44 self._ui = ui
44 self._ui = ui
45 self._filecache = {}
45 self._filecache = {}
46
46
47 @propertycache
47 @propertycache
48 def _map(self):
48 def _map(self):
49 '''Return the dirstate contents as a map from filename to
49 '''Return the dirstate contents as a map from filename to
50 (state, mode, size, time).'''
50 (state, mode, size, time).'''
51 self._read()
51 self._read()
52 return self._map
52 return self._map
53
53
54 @propertycache
54 @propertycache
55 def _copymap(self):
55 def _copymap(self):
56 self._read()
56 self._read()
57 return self._copymap
57 return self._copymap
58
58
59 @propertycache
59 @propertycache
60 def _foldmap(self):
60 def _foldmap(self):
61 f = {}
61 f = {}
62 for name, s in self._map.iteritems():
62 for name, s in self._map.iteritems():
63 if s[0] != 'r':
63 if s[0] != 'r':
64 f[util.normcase(name)] = name
64 f[util.normcase(name)] = name
65 for name in self._dirs:
65 for name in self._dirs:
66 f[util.normcase(name)] = name
66 f[util.normcase(name)] = name
67 f['.'] = '.' # prevents useless util.fspath() invocation
67 f['.'] = '.' # prevents useless util.fspath() invocation
68 return f
68 return f
69
69
70 @repocache('branch')
70 @repocache('branch')
71 def _branch(self):
71 def _branch(self):
72 try:
72 try:
73 return self._opener.read("branch").strip() or "default"
73 return self._opener.read("branch").strip() or "default"
74 except IOError, inst:
74 except IOError, inst:
75 if inst.errno != errno.ENOENT:
75 if inst.errno != errno.ENOENT:
76 raise
76 raise
77 return "default"
77 return "default"
78
78
79 @propertycache
79 @propertycache
80 def _pl(self):
80 def _pl(self):
81 try:
81 try:
82 fp = self._opener("dirstate")
82 fp = self._opener("dirstate")
83 st = fp.read(40)
83 st = fp.read(40)
84 fp.close()
84 fp.close()
85 l = len(st)
85 l = len(st)
86 if l == 40:
86 if l == 40:
87 return st[:20], st[20:40]
87 return st[:20], st[20:40]
88 elif l > 0 and l < 40:
88 elif l > 0 and l < 40:
89 raise util.Abort(_('working directory state appears damaged!'))
89 raise util.Abort(_('working directory state appears damaged!'))
90 except IOError, err:
90 except IOError, err:
91 if err.errno != errno.ENOENT:
91 if err.errno != errno.ENOENT:
92 raise
92 raise
93 return [nullid, nullid]
93 return [nullid, nullid]
94
94
95 @propertycache
95 @propertycache
96 def _dirs(self):
96 def _dirs(self):
97 return scmutil.dirs(self._map, 'r')
97 return scmutil.dirs(self._map, 'r')
98
98
99 def dirs(self):
99 def dirs(self):
100 return self._dirs
100 return self._dirs
101
101
102 @rootcache('.hgignore')
102 @rootcache('.hgignore')
103 def _ignore(self):
103 def _ignore(self):
104 files = [self._join('.hgignore')]
104 files = [self._join('.hgignore')]
105 for name, path in self._ui.configitems("ui"):
105 for name, path in self._ui.configitems("ui"):
106 if name == 'ignore' or name.startswith('ignore.'):
106 if name == 'ignore' or name.startswith('ignore.'):
107 files.append(util.expandpath(path))
107 files.append(util.expandpath(path))
108 return ignore.ignore(self._root, files, self._ui.warn)
108 return ignore.ignore(self._root, files, self._ui.warn)
109
109
110 @propertycache
110 @propertycache
111 def _slash(self):
111 def _slash(self):
112 return self._ui.configbool('ui', 'slash') and os.sep != '/'
112 return self._ui.configbool('ui', 'slash') and os.sep != '/'
113
113
114 @propertycache
114 @propertycache
115 def _checklink(self):
115 def _checklink(self):
116 return util.checklink(self._root)
116 return util.checklink(self._root)
117
117
118 @propertycache
118 @propertycache
119 def _checkexec(self):
119 def _checkexec(self):
120 return util.checkexec(self._root)
120 return util.checkexec(self._root)
121
121
122 @propertycache
122 @propertycache
123 def _checkcase(self):
123 def _checkcase(self):
124 return not util.checkcase(self._join('.hg'))
124 return not util.checkcase(self._join('.hg'))
125
125
126 def _join(self, f):
126 def _join(self, f):
127 # much faster than os.path.join()
127 # much faster than os.path.join()
128 # it's safe because f is always a relative path
128 # it's safe because f is always a relative path
129 return self._rootdir + f
129 return self._rootdir + f
130
130
131 def flagfunc(self, buildfallback):
131 def flagfunc(self, buildfallback):
132 if self._checklink and self._checkexec:
132 if self._checklink and self._checkexec:
133 def f(x):
133 def f(x):
134 try:
134 try:
135 st = os.lstat(self._join(x))
135 st = os.lstat(self._join(x))
136 if util.statislink(st):
136 if util.statislink(st):
137 return 'l'
137 return 'l'
138 if util.statisexec(st):
138 if util.statisexec(st):
139 return 'x'
139 return 'x'
140 except OSError:
140 except OSError:
141 pass
141 pass
142 return ''
142 return ''
143 return f
143 return f
144
144
145 fallback = buildfallback()
145 fallback = buildfallback()
146 if self._checklink:
146 if self._checklink:
147 def f(x):
147 def f(x):
148 if os.path.islink(self._join(x)):
148 if os.path.islink(self._join(x)):
149 return 'l'
149 return 'l'
150 if 'x' in fallback(x):
150 if 'x' in fallback(x):
151 return 'x'
151 return 'x'
152 return ''
152 return ''
153 return f
153 return f
154 if self._checkexec:
154 if self._checkexec:
155 def f(x):
155 def f(x):
156 if 'l' in fallback(x):
156 if 'l' in fallback(x):
157 return 'l'
157 return 'l'
158 if util.isexec(self._join(x)):
158 if util.isexec(self._join(x)):
159 return 'x'
159 return 'x'
160 return ''
160 return ''
161 return f
161 return f
162 else:
162 else:
163 return fallback
163 return fallback
164
164
165 def getcwd(self):
165 def getcwd(self):
166 cwd = os.getcwd()
166 cwd = os.getcwd()
167 if cwd == self._root:
167 if cwd == self._root:
168 return ''
168 return ''
169 # self._root ends with a path separator if self._root is '/' or 'C:\'
169 # self._root ends with a path separator if self._root is '/' or 'C:\'
170 rootsep = self._root
170 rootsep = self._root
171 if not util.endswithsep(rootsep):
171 if not util.endswithsep(rootsep):
172 rootsep += os.sep
172 rootsep += os.sep
173 if cwd.startswith(rootsep):
173 if cwd.startswith(rootsep):
174 return cwd[len(rootsep):]
174 return cwd[len(rootsep):]
175 else:
175 else:
176 # we're outside the repo. return an absolute path.
176 # we're outside the repo. return an absolute path.
177 return cwd
177 return cwd
178
178
179 def pathto(self, f, cwd=None):
179 def pathto(self, f, cwd=None):
180 if cwd is None:
180 if cwd is None:
181 cwd = self.getcwd()
181 cwd = self.getcwd()
182 path = util.pathto(self._root, cwd, f)
182 path = util.pathto(self._root, cwd, f)
183 if self._slash:
183 if self._slash:
184 return util.normpath(path)
184 return util.normpath(path)
185 return path
185 return path
186
186
187 def __getitem__(self, key):
187 def __getitem__(self, key):
188 '''Return the current state of key (a filename) in the dirstate.
188 '''Return the current state of key (a filename) in the dirstate.
189
189
190 States are:
190 States are:
191 n normal
191 n normal
192 m needs merging
192 m needs merging
193 r marked for removal
193 r marked for removal
194 a marked for addition
194 a marked for addition
195 ? not tracked
195 ? not tracked
196 '''
196 '''
197 return self._map.get(key, ("?",))[0]
197 return self._map.get(key, ("?",))[0]
198
198
199 def __contains__(self, key):
199 def __contains__(self, key):
200 return key in self._map
200 return key in self._map
201
201
202 def __iter__(self):
202 def __iter__(self):
203 for x in sorted(self._map):
203 for x in sorted(self._map):
204 yield x
204 yield x
205
205
206 def iteritems(self):
206 def iteritems(self):
207 return self._map.iteritems()
207 return self._map.iteritems()
208
208
209 def parents(self):
209 def parents(self):
210 return [self._validate(p) for p in self._pl]
210 return [self._validate(p) for p in self._pl]
211
211
212 def p1(self):
212 def p1(self):
213 return self._validate(self._pl[0])
213 return self._validate(self._pl[0])
214
214
215 def p2(self):
215 def p2(self):
216 return self._validate(self._pl[1])
216 return self._validate(self._pl[1])
217
217
218 def branch(self):
218 def branch(self):
219 return encoding.tolocal(self._branch)
219 return encoding.tolocal(self._branch)
220
220
221 def setparents(self, p1, p2=nullid):
221 def setparents(self, p1, p2=nullid):
222 """Set dirstate parents to p1 and p2.
222 """Set dirstate parents to p1 and p2.
223
223
224 When moving from two parents to one, 'm' merged entries a
224 When moving from two parents to one, 'm' merged entries a
225 adjusted to normal and previous copy records discarded and
225 adjusted to normal and previous copy records discarded and
226 returned by the call.
226 returned by the call.
227
227
228 See localrepo.setparents()
228 See localrepo.setparents()
229 """
229 """
230 self._dirty = self._dirtypl = True
230 self._dirty = self._dirtypl = True
231 oldp2 = self._pl[1]
231 oldp2 = self._pl[1]
232 self._pl = p1, p2
232 self._pl = p1, p2
233 copies = {}
233 copies = {}
234 if oldp2 != nullid and p2 == nullid:
234 if oldp2 != nullid and p2 == nullid:
235 # Discard 'm' markers when moving away from a merge state
235 # Discard 'm' markers when moving away from a merge state
236 for f, s in self._map.iteritems():
236 for f, s in self._map.iteritems():
237 if s[0] == 'm':
237 if s[0] == 'm':
238 if f in self._copymap:
238 if f in self._copymap:
239 copies[f] = self._copymap[f]
239 copies[f] = self._copymap[f]
240 self.normallookup(f)
240 self.normallookup(f)
241 return copies
241 return copies
242
242
243 def setbranch(self, branch):
243 def setbranch(self, branch):
244 self._branch = encoding.fromlocal(branch)
244 self._branch = encoding.fromlocal(branch)
245 f = self._opener('branch', 'w', atomictemp=True)
245 f = self._opener('branch', 'w', atomictemp=True)
246 try:
246 try:
247 f.write(self._branch + '\n')
247 f.write(self._branch + '\n')
248 f.close()
248 f.close()
249
249
250 # make sure filecache has the correct stat info for _branch after
250 # make sure filecache has the correct stat info for _branch after
251 # replacing the underlying file
251 # replacing the underlying file
252 ce = self._filecache['_branch']
252 ce = self._filecache['_branch']
253 if ce:
253 if ce:
254 ce.refresh()
254 ce.refresh()
255 except: # re-raises
255 except: # re-raises
256 f.discard()
256 f.discard()
257 raise
257 raise
258
258
259 def _read(self):
259 def _read(self):
260 self._map = {}
260 self._map = {}
261 self._copymap = {}
261 self._copymap = {}
262 try:
262 try:
263 st = self._opener.read("dirstate")
263 st = self._opener.read("dirstate")
264 except IOError, err:
264 except IOError, err:
265 if err.errno != errno.ENOENT:
265 if err.errno != errno.ENOENT:
266 raise
266 raise
267 return
267 return
268 if not st:
268 if not st:
269 return
269 return
270
270
271 # Python's garbage collector triggers a GC each time a certain number
271 # Python's garbage collector triggers a GC each time a certain number
272 # of container objects (the number being defined by
272 # of container objects (the number being defined by
273 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
273 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
274 # for each file in the dirstate. The C version then immediately marks
274 # for each file in the dirstate. The C version then immediately marks
275 # them as not to be tracked by the collector. However, this has no
275 # them as not to be tracked by the collector. However, this has no
276 # effect on when GCs are triggered, only on what objects the GC looks
276 # effect on when GCs are triggered, only on what objects the GC looks
277 # into. This means that O(number of files) GCs are unavoidable.
277 # into. This means that O(number of files) GCs are unavoidable.
278 # Depending on when in the process's lifetime the dirstate is parsed,
278 # Depending on when in the process's lifetime the dirstate is parsed,
279 # this can get very expensive. As a workaround, disable GC while
279 # this can get very expensive. As a workaround, disable GC while
280 # parsing the dirstate.
280 # parsing the dirstate.
281 gcenabled = gc.isenabled()
281 gcenabled = gc.isenabled()
282 gc.disable()
282 gc.disable()
283 try:
283 try:
284 p = parsers.parse_dirstate(self._map, self._copymap, st)
284 p = parsers.parse_dirstate(self._map, self._copymap, st)
285 finally:
285 finally:
286 if gcenabled:
286 if gcenabled:
287 gc.enable()
287 gc.enable()
288 if not self._dirtypl:
288 if not self._dirtypl:
289 self._pl = p
289 self._pl = p
290
290
291 def invalidate(self):
291 def invalidate(self):
292 for a in ("_map", "_copymap", "_foldmap", "_branch", "_pl", "_dirs",
292 for a in ("_map", "_copymap", "_foldmap", "_branch", "_pl", "_dirs",
293 "_ignore"):
293 "_ignore"):
294 if a in self.__dict__:
294 if a in self.__dict__:
295 delattr(self, a)
295 delattr(self, a)
296 self._lastnormaltime = 0
296 self._lastnormaltime = 0
297 self._dirty = False
297 self._dirty = False
298
298
299 def copy(self, source, dest):
299 def copy(self, source, dest):
300 """Mark dest as a copy of source. Unmark dest if source is None."""
300 """Mark dest as a copy of source. Unmark dest if source is None."""
301 if source == dest:
301 if source == dest:
302 return
302 return
303 self._dirty = True
303 self._dirty = True
304 if source is not None:
304 if source is not None:
305 self._copymap[dest] = source
305 self._copymap[dest] = source
306 elif dest in self._copymap:
306 elif dest in self._copymap:
307 del self._copymap[dest]
307 del self._copymap[dest]
308
308
309 def copied(self, file):
309 def copied(self, file):
310 return self._copymap.get(file, None)
310 return self._copymap.get(file, None)
311
311
312 def copies(self):
312 def copies(self):
313 return self._copymap
313 return self._copymap
314
314
315 def _droppath(self, f):
315 def _droppath(self, f):
316 if self[f] not in "?r" and "_dirs" in self.__dict__:
316 if self[f] not in "?r" and "_dirs" in self.__dict__:
317 self._dirs.delpath(f)
317 self._dirs.delpath(f)
318
318
319 def _addpath(self, f, state, mode, size, mtime):
319 def _addpath(self, f, state, mode, size, mtime):
320 oldstate = self[f]
320 oldstate = self[f]
321 if state == 'a' or oldstate == 'r':
321 if state == 'a' or oldstate == 'r':
322 scmutil.checkfilename(f)
322 scmutil.checkfilename(f)
323 if f in self._dirs:
323 if f in self._dirs:
324 raise util.Abort(_('directory %r already in dirstate') % f)
324 raise util.Abort(_('directory %r already in dirstate') % f)
325 # shadows
325 # shadows
326 for d in scmutil.finddirs(f):
326 for d in scmutil.finddirs(f):
327 if d in self._dirs:
327 if d in self._dirs:
328 break
328 break
329 if d in self._map and self[d] != 'r':
329 if d in self._map and self[d] != 'r':
330 raise util.Abort(
330 raise util.Abort(
331 _('file %r in dirstate clashes with %r') % (d, f))
331 _('file %r in dirstate clashes with %r') % (d, f))
332 if oldstate in "?r" and "_dirs" in self.__dict__:
332 if oldstate in "?r" and "_dirs" in self.__dict__:
333 self._dirs.addpath(f)
333 self._dirs.addpath(f)
334 self._dirty = True
334 self._dirty = True
335 self._map[f] = (state, mode, size, mtime)
335 self._map[f] = (state, mode, size, mtime)
336
336
337 def normal(self, f):
337 def normal(self, f):
338 '''Mark a file normal and clean.'''
338 '''Mark a file normal and clean.'''
339 s = os.lstat(self._join(f))
339 s = os.lstat(self._join(f))
340 mtime = int(s.st_mtime)
340 mtime = int(s.st_mtime)
341 self._addpath(f, 'n', s.st_mode,
341 self._addpath(f, 'n', s.st_mode,
342 s.st_size & _rangemask, mtime & _rangemask)
342 s.st_size & _rangemask, mtime & _rangemask)
343 if f in self._copymap:
343 if f in self._copymap:
344 del self._copymap[f]
344 del self._copymap[f]
345 if mtime > self._lastnormaltime:
345 if mtime > self._lastnormaltime:
346 # Remember the most recent modification timeslot for status(),
346 # Remember the most recent modification timeslot for status(),
347 # to make sure we won't miss future size-preserving file content
347 # to make sure we won't miss future size-preserving file content
348 # modifications that happen within the same timeslot.
348 # modifications that happen within the same timeslot.
349 self._lastnormaltime = mtime
349 self._lastnormaltime = mtime
350
350
351 def normallookup(self, f):
351 def normallookup(self, f):
352 '''Mark a file normal, but possibly dirty.'''
352 '''Mark a file normal, but possibly dirty.'''
353 if self._pl[1] != nullid and f in self._map:
353 if self._pl[1] != nullid and f in self._map:
354 # if there is a merge going on and the file was either
354 # if there is a merge going on and the file was either
355 # in state 'm' (-1) or coming from other parent (-2) before
355 # in state 'm' (-1) or coming from other parent (-2) before
356 # being removed, restore that state.
356 # being removed, restore that state.
357 entry = self._map[f]
357 entry = self._map[f]
358 if entry[0] == 'r' and entry[2] in (-1, -2):
358 if entry[0] == 'r' and entry[2] in (-1, -2):
359 source = self._copymap.get(f)
359 source = self._copymap.get(f)
360 if entry[2] == -1:
360 if entry[2] == -1:
361 self.merge(f)
361 self.merge(f)
362 elif entry[2] == -2:
362 elif entry[2] == -2:
363 self.otherparent(f)
363 self.otherparent(f)
364 if source:
364 if source:
365 self.copy(source, f)
365 self.copy(source, f)
366 return
366 return
367 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
367 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
368 return
368 return
369 self._addpath(f, 'n', 0, -1, -1)
369 self._addpath(f, 'n', 0, -1, -1)
370 if f in self._copymap:
370 if f in self._copymap:
371 del self._copymap[f]
371 del self._copymap[f]
372
372
373 def otherparent(self, f):
373 def otherparent(self, f):
374 '''Mark as coming from the other parent, always dirty.'''
374 '''Mark as coming from the other parent, always dirty.'''
375 if self._pl[1] == nullid:
375 if self._pl[1] == nullid:
376 raise util.Abort(_("setting %r to other parent "
376 raise util.Abort(_("setting %r to other parent "
377 "only allowed in merges") % f)
377 "only allowed in merges") % f)
378 self._addpath(f, 'n', 0, -2, -1)
378 self._addpath(f, 'n', 0, -2, -1)
379 if f in self._copymap:
379 if f in self._copymap:
380 del self._copymap[f]
380 del self._copymap[f]
381
381
382 def add(self, f):
382 def add(self, f):
383 '''Mark a file added.'''
383 '''Mark a file added.'''
384 self._addpath(f, 'a', 0, -1, -1)
384 self._addpath(f, 'a', 0, -1, -1)
385 if f in self._copymap:
385 if f in self._copymap:
386 del self._copymap[f]
386 del self._copymap[f]
387
387
388 def remove(self, f):
388 def remove(self, f):
389 '''Mark a file removed.'''
389 '''Mark a file removed.'''
390 self._dirty = True
390 self._dirty = True
391 self._droppath(f)
391 self._droppath(f)
392 size = 0
392 size = 0
393 if self._pl[1] != nullid and f in self._map:
393 if self._pl[1] != nullid and f in self._map:
394 # backup the previous state
394 # backup the previous state
395 entry = self._map[f]
395 entry = self._map[f]
396 if entry[0] == 'm': # merge
396 if entry[0] == 'm': # merge
397 size = -1
397 size = -1
398 elif entry[0] == 'n' and entry[2] == -2: # other parent
398 elif entry[0] == 'n' and entry[2] == -2: # other parent
399 size = -2
399 size = -2
400 self._map[f] = ('r', 0, size, 0)
400 self._map[f] = ('r', 0, size, 0)
401 if size == 0 and f in self._copymap:
401 if size == 0 and f in self._copymap:
402 del self._copymap[f]
402 del self._copymap[f]
403
403
404 def merge(self, f):
404 def merge(self, f):
405 '''Mark a file merged.'''
405 '''Mark a file merged.'''
406 if self._pl[1] == nullid:
406 if self._pl[1] == nullid:
407 return self.normallookup(f)
407 return self.normallookup(f)
408 s = os.lstat(self._join(f))
408 s = os.lstat(self._join(f))
409 self._addpath(f, 'm', s.st_mode,
409 self._addpath(f, 'm', s.st_mode,
410 s.st_size & _rangemask, int(s.st_mtime) & _rangemask)
410 s.st_size & _rangemask, int(s.st_mtime) & _rangemask)
411 if f in self._copymap:
411 if f in self._copymap:
412 del self._copymap[f]
412 del self._copymap[f]
413
413
414 def drop(self, f):
414 def drop(self, f):
415 '''Drop a file from the dirstate'''
415 '''Drop a file from the dirstate'''
416 if f in self._map:
416 if f in self._map:
417 self._dirty = True
417 self._dirty = True
418 self._droppath(f)
418 self._droppath(f)
419 del self._map[f]
419 del self._map[f]
420
420
421 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
421 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
422 normed = util.normcase(path)
422 normed = util.normcase(path)
423 folded = self._foldmap.get(normed, None)
423 folded = self._foldmap.get(normed, None)
424 if folded is None:
424 if folded is None:
425 if isknown:
425 if isknown:
426 folded = path
426 folded = path
427 else:
427 else:
428 if exists is None:
428 if exists is None:
429 exists = os.path.lexists(os.path.join(self._root, path))
429 exists = os.path.lexists(os.path.join(self._root, path))
430 if not exists:
430 if not exists:
431 # Maybe a path component exists
431 # Maybe a path component exists
432 if not ignoremissing and '/' in path:
432 if not ignoremissing and '/' in path:
433 d, f = path.rsplit('/', 1)
433 d, f = path.rsplit('/', 1)
434 d = self._normalize(d, isknown, ignoremissing, None)
434 d = self._normalize(d, isknown, ignoremissing, None)
435 folded = d + "/" + f
435 folded = d + "/" + f
436 else:
436 else:
437 # No path components, preserve original case
437 # No path components, preserve original case
438 folded = path
438 folded = path
439 else:
439 else:
440 # recursively normalize leading directory components
440 # recursively normalize leading directory components
441 # against dirstate
441 # against dirstate
442 if '/' in normed:
442 if '/' in normed:
443 d, f = normed.rsplit('/', 1)
443 d, f = normed.rsplit('/', 1)
444 d = self._normalize(d, isknown, ignoremissing, True)
444 d = self._normalize(d, isknown, ignoremissing, True)
445 r = self._root + "/" + d
445 r = self._root + "/" + d
446 folded = d + "/" + util.fspath(f, r)
446 folded = d + "/" + util.fspath(f, r)
447 else:
447 else:
448 folded = util.fspath(normed, self._root)
448 folded = util.fspath(normed, self._root)
449 self._foldmap[normed] = folded
449 self._foldmap[normed] = folded
450
450
451 return folded
451 return folded
452
452
453 def normalize(self, path, isknown=False, ignoremissing=False):
453 def normalize(self, path, isknown=False, ignoremissing=False):
454 '''
454 '''
455 normalize the case of a pathname when on a casefolding filesystem
455 normalize the case of a pathname when on a casefolding filesystem
456
456
457 isknown specifies whether the filename came from walking the
457 isknown specifies whether the filename came from walking the
458 disk, to avoid extra filesystem access.
458 disk, to avoid extra filesystem access.
459
459
460 If ignoremissing is True, missing path are returned
460 If ignoremissing is True, missing path are returned
461 unchanged. Otherwise, we try harder to normalize possibly
461 unchanged. Otherwise, we try harder to normalize possibly
462 existing path components.
462 existing path components.
463
463
464 The normalized case is determined based on the following precedence:
464 The normalized case is determined based on the following precedence:
465
465
466 - version of name already stored in the dirstate
466 - version of name already stored in the dirstate
467 - version of name stored on disk
467 - version of name stored on disk
468 - version provided via command arguments
468 - version provided via command arguments
469 '''
469 '''
470
470
471 if self._checkcase:
471 if self._checkcase:
472 return self._normalize(path, isknown, ignoremissing)
472 return self._normalize(path, isknown, ignoremissing)
473 return path
473 return path
474
474
475 def clear(self):
475 def clear(self):
476 self._map = {}
476 self._map = {}
477 if "_dirs" in self.__dict__:
477 if "_dirs" in self.__dict__:
478 delattr(self, "_dirs")
478 delattr(self, "_dirs")
479 self._copymap = {}
479 self._copymap = {}
480 self._pl = [nullid, nullid]
480 self._pl = [nullid, nullid]
481 self._lastnormaltime = 0
481 self._lastnormaltime = 0
482 self._dirty = True
482 self._dirty = True
483
483
484 def rebuild(self, parent, allfiles, changedfiles=None):
484 def rebuild(self, parent, allfiles, changedfiles=None):
485 changedfiles = changedfiles or allfiles
485 changedfiles = changedfiles or allfiles
486 oldmap = self._map
486 oldmap = self._map
487 self.clear()
487 self.clear()
488 for f in allfiles:
488 for f in allfiles:
489 if f not in changedfiles:
489 if f not in changedfiles:
490 self._map[f] = oldmap[f]
490 self._map[f] = oldmap[f]
491 else:
491 else:
492 if 'x' in allfiles.flags(f):
492 if 'x' in allfiles.flags(f):
493 self._map[f] = ('n', 0777, -1, 0)
493 self._map[f] = ('n', 0777, -1, 0)
494 else:
494 else:
495 self._map[f] = ('n', 0666, -1, 0)
495 self._map[f] = ('n', 0666, -1, 0)
496 self._pl = (parent, nullid)
496 self._pl = (parent, nullid)
497 self._dirty = True
497 self._dirty = True
498
498
499 def write(self):
499 def write(self):
500 if not self._dirty:
500 if not self._dirty:
501 return
501 return
502 st = self._opener("dirstate", "w", atomictemp=True)
502 st = self._opener("dirstate", "w", atomictemp=True)
503
503
504 def finish(s):
504 def finish(s):
505 st.write(s)
505 st.write(s)
506 st.close()
506 st.close()
507 self._lastnormaltime = 0
507 self._lastnormaltime = 0
508 self._dirty = self._dirtypl = False
508 self._dirty = self._dirtypl = False
509
509
510 # use the modification time of the newly created temporary file as the
510 # use the modification time of the newly created temporary file as the
511 # filesystem's notion of 'now'
511 # filesystem's notion of 'now'
512 now = util.fstat(st).st_mtime
512 now = util.fstat(st).st_mtime
513 finish(parsers.pack_dirstate(self._map, self._copymap, self._pl, now))
513 finish(parsers.pack_dirstate(self._map, self._copymap, self._pl, now))
514
514
515 def _dirignore(self, f):
515 def _dirignore(self, f):
516 if f == '.':
516 if f == '.':
517 return False
517 return False
518 if self._ignore(f):
518 if self._ignore(f):
519 return True
519 return True
520 for p in scmutil.finddirs(f):
520 for p in scmutil.finddirs(f):
521 if self._ignore(p):
521 if self._ignore(p):
522 return True
522 return True
523 return False
523 return False
524
524
525 def walk(self, match, subrepos, unknown, ignored):
525 def walk(self, match, subrepos, unknown, ignored):
526 '''
526 '''
527 Walk recursively through the directory tree, finding all files
527 Walk recursively through the directory tree, finding all files
528 matched by match.
528 matched by match.
529
529
530 Return a dict mapping filename to stat-like object (either
530 Return a dict mapping filename to stat-like object (either
531 mercurial.osutil.stat instance or return value of os.stat()).
531 mercurial.osutil.stat instance or return value of os.stat()).
532 '''
532 '''
533
533
534 def fwarn(f, msg):
534 def fwarn(f, msg):
535 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
535 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
536 return False
536 return False
537
537
538 def badtype(mode):
538 def badtype(mode):
539 kind = _('unknown')
539 kind = _('unknown')
540 if stat.S_ISCHR(mode):
540 if stat.S_ISCHR(mode):
541 kind = _('character device')
541 kind = _('character device')
542 elif stat.S_ISBLK(mode):
542 elif stat.S_ISBLK(mode):
543 kind = _('block device')
543 kind = _('block device')
544 elif stat.S_ISFIFO(mode):
544 elif stat.S_ISFIFO(mode):
545 kind = _('fifo')
545 kind = _('fifo')
546 elif stat.S_ISSOCK(mode):
546 elif stat.S_ISSOCK(mode):
547 kind = _('socket')
547 kind = _('socket')
548 elif stat.S_ISDIR(mode):
548 elif stat.S_ISDIR(mode):
549 kind = _('directory')
549 kind = _('directory')
550 return _('unsupported file type (type is %s)') % kind
550 return _('unsupported file type (type is %s)') % kind
551
551
552 ignore = self._ignore
552 ignore = self._ignore
553 dirignore = self._dirignore
553 dirignore = self._dirignore
554 if ignored:
554 if ignored:
555 ignore = util.never
555 ignore = util.never
556 dirignore = util.never
556 dirignore = util.never
557 elif not unknown:
557 elif not unknown:
558 # if unknown and ignored are False, skip step 2
558 # if unknown and ignored are False, skip step 2
559 ignore = util.always
559 ignore = util.always
560 dirignore = util.always
560 dirignore = util.always
561
561
562 matchfn = match.matchfn
562 matchfn = match.matchfn
563 matchalways = match.always()
563 matchalways = match.always()
564 badfn = match.bad
564 badfn = match.bad
565 dmap = self._map
565 dmap = self._map
566 normpath = util.normpath
566 normpath = util.normpath
567 listdir = osutil.listdir
567 listdir = osutil.listdir
568 lstat = os.lstat
568 lstat = os.lstat
569 getkind = stat.S_IFMT
569 getkind = stat.S_IFMT
570 dirkind = stat.S_IFDIR
570 dirkind = stat.S_IFDIR
571 regkind = stat.S_IFREG
571 regkind = stat.S_IFREG
572 lnkkind = stat.S_IFLNK
572 lnkkind = stat.S_IFLNK
573 join = self._join
573 join = self._join
574 work = []
574 work = []
575 wadd = work.append
575 wadd = work.append
576
576
577 exact = skipstep3 = False
577 exact = skipstep3 = False
578 if matchfn == match.exact: # match.exact
578 if matchfn == match.exact: # match.exact
579 exact = True
579 exact = True
580 dirignore = util.always # skip step 2
580 dirignore = util.always # skip step 2
581 elif match.files() and not match.anypats(): # match.match, no patterns
581 elif match.files() and not match.anypats(): # match.match, no patterns
582 skipstep3 = True
582 skipstep3 = True
583
583
584 if not exact and self._checkcase:
584 if not exact and self._checkcase:
585 normalize = self._normalize
585 normalize = self._normalize
586 skipstep3 = False
586 skipstep3 = False
587 else:
587 else:
588 normalize = None
588 normalize = None
589
589
590 files = sorted(match.files())
590 files = sorted(match.files())
591 subrepos.sort()
591 subrepos.sort()
592 i, j = 0, 0
592 i, j = 0, 0
593 while i < len(files) and j < len(subrepos):
593 while i < len(files) and j < len(subrepos):
594 subpath = subrepos[j] + "/"
594 subpath = subrepos[j] + "/"
595 if files[i] < subpath:
595 if files[i] < subpath:
596 i += 1
596 i += 1
597 continue
597 continue
598 while i < len(files) and files[i].startswith(subpath):
598 while i < len(files) and files[i].startswith(subpath):
599 del files[i]
599 del files[i]
600 j += 1
600 j += 1
601
601
602 if not files or '.' in files:
602 if not files or '.' in files:
603 files = ['']
603 files = ['']
604 results = dict.fromkeys(subrepos)
604 results = dict.fromkeys(subrepos)
605 results['.hg'] = None
605 results['.hg'] = None
606
606
607 # step 1: find all explicit files
607 # step 1: find all explicit files
608 for ff in files:
608 for ff in files:
609 if normalize:
609 if normalize:
610 nf = normalize(normpath(ff), False, True)
610 nf = normalize(normpath(ff), False, True)
611 else:
611 else:
612 nf = normpath(ff)
612 nf = normpath(ff)
613 if nf in results:
613 if nf in results:
614 continue
614 continue
615
615
616 try:
616 try:
617 st = lstat(join(nf))
617 st = lstat(join(nf))
618 kind = getkind(st.st_mode)
618 kind = getkind(st.st_mode)
619 if kind == dirkind:
619 if kind == dirkind:
620 skipstep3 = False
620 skipstep3 = False
621 if nf in dmap:
621 if nf in dmap:
622 #file deleted on disk but still in dirstate
622 #file deleted on disk but still in dirstate
623 results[nf] = None
623 results[nf] = None
624 match.dir(nf)
624 match.dir(nf)
625 if not dirignore(nf):
625 if not dirignore(nf):
626 wadd(nf)
626 wadd(nf)
627 elif kind == regkind or kind == lnkkind:
627 elif kind == regkind or kind == lnkkind:
628 results[nf] = st
628 results[nf] = st
629 else:
629 else:
630 badfn(ff, badtype(kind))
630 badfn(ff, badtype(kind))
631 if nf in dmap:
631 if nf in dmap:
632 results[nf] = None
632 results[nf] = None
633 except OSError, inst:
633 except OSError, inst:
634 if nf in dmap: # does it exactly match a file?
634 if nf in dmap: # does it exactly match a file?
635 results[nf] = None
635 results[nf] = None
636 else: # does it match a directory?
636 else: # does it match a directory?
637 prefix = nf + "/"
637 prefix = nf + "/"
638 for fn in dmap:
638 for fn in dmap:
639 if fn.startswith(prefix):
639 if fn.startswith(prefix):
640 match.dir(nf)
640 match.dir(nf)
641 skipstep3 = False
641 skipstep3 = False
642 break
642 break
643 else:
643 else:
644 badfn(ff, inst.strerror)
644 badfn(ff, inst.strerror)
645
645
646 # step 2: visit subdirectories
646 # step 2: visit subdirectories
647 while work:
647 while work:
648 nd = work.pop()
648 nd = work.pop()
649 skip = None
649 skip = None
650 if nd == '.':
650 if nd == '.':
651 nd = ''
651 nd = ''
652 else:
652 else:
653 skip = '.hg'
653 skip = '.hg'
654 try:
654 try:
655 entries = listdir(join(nd), stat=True, skip=skip)
655 entries = listdir(join(nd), stat=True, skip=skip)
656 except OSError, inst:
656 except OSError, inst:
657 if inst.errno in (errno.EACCES, errno.ENOENT):
657 if inst.errno in (errno.EACCES, errno.ENOENT):
658 fwarn(nd, inst.strerror)
658 fwarn(nd, inst.strerror)
659 continue
659 continue
660 raise
660 raise
661 for f, kind, st in entries:
661 for f, kind, st in entries:
662 if normalize:
662 if normalize:
663 nf = normalize(nd and (nd + "/" + f) or f, True, True)
663 nf = normalize(nd and (nd + "/" + f) or f, True, True)
664 else:
664 else:
665 nf = nd and (nd + "/" + f) or f
665 nf = nd and (nd + "/" + f) or f
666 if nf not in results:
666 if nf not in results:
667 if kind == dirkind:
667 if kind == dirkind:
668 if not ignore(nf):
668 if not ignore(nf):
669 match.dir(nf)
669 match.dir(nf)
670 wadd(nf)
670 wadd(nf)
671 if nf in dmap and (matchalways or matchfn(nf)):
671 if nf in dmap and (matchalways or matchfn(nf)):
672 results[nf] = None
672 results[nf] = None
673 elif kind == regkind or kind == lnkkind:
673 elif kind == regkind or kind == lnkkind:
674 if nf in dmap:
674 if nf in dmap:
675 if matchalways or matchfn(nf):
675 if matchalways or matchfn(nf):
676 results[nf] = st
676 results[nf] = st
677 elif (matchalways or matchfn(nf)) and not ignore(nf):
677 elif (matchalways or matchfn(nf)) and not ignore(nf):
678 results[nf] = st
678 results[nf] = st
679 elif nf in dmap and (matchalways or matchfn(nf)):
679 elif nf in dmap and (matchalways or matchfn(nf)):
680 results[nf] = None
680 results[nf] = None
681
681
682 for s in subrepos:
682 for s in subrepos:
683 del results[s]
683 del results[s]
684 del results['.hg']
684 del results['.hg']
685
685
686 # step 3: report unseen items in the dmap hash
686 # step 3: report unseen items in the dmap hash
687 if not skipstep3 and not exact:
687 if not skipstep3 and not exact:
688 if not results and matchalways:
688 if not results and matchalways:
689 visit = dmap.keys()
689 visit = dmap.keys()
690 else:
690 else:
691 visit = [f for f in dmap if f not in results and matchfn(f)]
691 visit = [f for f in dmap if f not in results and matchfn(f)]
692 visit.sort()
692 visit.sort()
693
693
694 if unknown:
694 if unknown:
695 # unknown == True means we walked the full directory tree above.
695 # unknown == True means we walked the full directory tree above.
696 # So if a file is not seen it was either a) not matching matchfn
696 # So if a file is not seen it was either a) not matching matchfn
697 # b) ignored, c) missing, or d) under a symlink directory.
697 # b) ignored, c) missing, or d) under a symlink directory.
698 audit_path = scmutil.pathauditor(self._root)
698 audit_path = scmutil.pathauditor(self._root)
699
699
700 for nf in iter(visit):
700 for nf in iter(visit):
701 # Report ignored items in the dmap as long as they are not
701 # Report ignored items in the dmap as long as they are not
702 # under a symlink directory.
702 # under a symlink directory.
703 if ignore(nf) and audit_path.check(nf):
703 if audit_path.check(nf):
704 try:
704 try:
705 results[nf] = lstat(join(nf))
705 results[nf] = lstat(join(nf))
706 except OSError:
706 except OSError:
707 # file doesn't exist
707 # file doesn't exist
708 results[nf] = None
708 results[nf] = None
709 else:
709 else:
710 # It's either missing or under a symlink directory
710 # It's either missing or under a symlink directory
711 results[nf] = None
711 results[nf] = None
712 else:
712 else:
713 # We may not have walked the full directory tree above,
713 # We may not have walked the full directory tree above,
714 # so stat everything we missed.
714 # so stat everything we missed.
715 nf = iter(visit).next
715 nf = iter(visit).next
716 for st in util.statfiles([join(i) for i in visit]):
716 for st in util.statfiles([join(i) for i in visit]):
717 results[nf()] = st
717 results[nf()] = st
718 return results
718 return results
719
719
720 def status(self, match, subrepos, ignored, clean, unknown):
720 def status(self, match, subrepos, ignored, clean, unknown):
721 '''Determine the status of the working copy relative to the
721 '''Determine the status of the working copy relative to the
722 dirstate and return a tuple of lists (unsure, modified, added,
722 dirstate and return a tuple of lists (unsure, modified, added,
723 removed, deleted, unknown, ignored, clean), where:
723 removed, deleted, unknown, ignored, clean), where:
724
724
725 unsure:
725 unsure:
726 files that might have been modified since the dirstate was
726 files that might have been modified since the dirstate was
727 written, but need to be read to be sure (size is the same
727 written, but need to be read to be sure (size is the same
728 but mtime differs)
728 but mtime differs)
729 modified:
729 modified:
730 files that have definitely been modified since the dirstate
730 files that have definitely been modified since the dirstate
731 was written (different size or mode)
731 was written (different size or mode)
732 added:
732 added:
733 files that have been explicitly added with hg add
733 files that have been explicitly added with hg add
734 removed:
734 removed:
735 files that have been explicitly removed with hg remove
735 files that have been explicitly removed with hg remove
736 deleted:
736 deleted:
737 files that have been deleted through other means ("missing")
737 files that have been deleted through other means ("missing")
738 unknown:
738 unknown:
739 files not in the dirstate that are not ignored
739 files not in the dirstate that are not ignored
740 ignored:
740 ignored:
741 files not in the dirstate that are ignored
741 files not in the dirstate that are ignored
742 (by _dirignore())
742 (by _dirignore())
743 clean:
743 clean:
744 files that have definitely not been modified since the
744 files that have definitely not been modified since the
745 dirstate was written
745 dirstate was written
746 '''
746 '''
747 listignored, listclean, listunknown = ignored, clean, unknown
747 listignored, listclean, listunknown = ignored, clean, unknown
748 lookup, modified, added, unknown, ignored = [], [], [], [], []
748 lookup, modified, added, unknown, ignored = [], [], [], [], []
749 removed, deleted, clean = [], [], []
749 removed, deleted, clean = [], [], []
750
750
751 dmap = self._map
751 dmap = self._map
752 ladd = lookup.append # aka "unsure"
752 ladd = lookup.append # aka "unsure"
753 madd = modified.append
753 madd = modified.append
754 aadd = added.append
754 aadd = added.append
755 uadd = unknown.append
755 uadd = unknown.append
756 iadd = ignored.append
756 iadd = ignored.append
757 radd = removed.append
757 radd = removed.append
758 dadd = deleted.append
758 dadd = deleted.append
759 cadd = clean.append
759 cadd = clean.append
760 mexact = match.exact
760 mexact = match.exact
761 dirignore = self._dirignore
761 dirignore = self._dirignore
762 checkexec = self._checkexec
762 checkexec = self._checkexec
763 checklink = self._checklink
763 checklink = self._checklink
764 copymap = self._copymap
764 copymap = self._copymap
765 lastnormaltime = self._lastnormaltime
765 lastnormaltime = self._lastnormaltime
766
766
767 lnkkind = stat.S_IFLNK
767 lnkkind = stat.S_IFLNK
768
768
769 for fn, st in self.walk(match, subrepos, listunknown,
769 for fn, st in self.walk(match, subrepos, listunknown,
770 listignored).iteritems():
770 listignored).iteritems():
771 if fn not in dmap:
771 if fn not in dmap:
772 if (listignored or mexact(fn)) and dirignore(fn):
772 if (listignored or mexact(fn)) and dirignore(fn):
773 if listignored:
773 if listignored:
774 iadd(fn)
774 iadd(fn)
775 elif listunknown:
775 elif listunknown:
776 uadd(fn)
776 uadd(fn)
777 continue
777 continue
778
778
779 state, mode, size, time = dmap[fn]
779 state, mode, size, time = dmap[fn]
780
780
781 if not st and state in "nma":
781 if not st and state in "nma":
782 dadd(fn)
782 dadd(fn)
783 elif state == 'n':
783 elif state == 'n':
784 # The "mode & lnkkind != lnkkind or self._checklink"
784 # The "mode & lnkkind != lnkkind or self._checklink"
785 # lines are an expansion of "islink => checklink"
785 # lines are an expansion of "islink => checklink"
786 # where islink means "is this a link?" and checklink
786 # where islink means "is this a link?" and checklink
787 # means "can we check links?".
787 # means "can we check links?".
788 mtime = int(st.st_mtime)
788 mtime = int(st.st_mtime)
789 if (size >= 0 and
789 if (size >= 0 and
790 ((size != st.st_size and size != st.st_size & _rangemask)
790 ((size != st.st_size and size != st.st_size & _rangemask)
791 or ((mode ^ st.st_mode) & 0100 and checkexec))
791 or ((mode ^ st.st_mode) & 0100 and checkexec))
792 and (mode & lnkkind != lnkkind or checklink)
792 and (mode & lnkkind != lnkkind or checklink)
793 or size == -2 # other parent
793 or size == -2 # other parent
794 or fn in copymap):
794 or fn in copymap):
795 madd(fn)
795 madd(fn)
796 elif ((time != mtime and time != mtime & _rangemask)
796 elif ((time != mtime and time != mtime & _rangemask)
797 and (mode & lnkkind != lnkkind or checklink)):
797 and (mode & lnkkind != lnkkind or checklink)):
798 ladd(fn)
798 ladd(fn)
799 elif mtime == lastnormaltime:
799 elif mtime == lastnormaltime:
800 # fn may have been changed in the same timeslot without
800 # fn may have been changed in the same timeslot without
801 # changing its size. This can happen if we quickly do
801 # changing its size. This can happen if we quickly do
802 # multiple commits in a single transaction.
802 # multiple commits in a single transaction.
803 # Force lookup, so we don't miss such a racy file change.
803 # Force lookup, so we don't miss such a racy file change.
804 ladd(fn)
804 ladd(fn)
805 elif listclean:
805 elif listclean:
806 cadd(fn)
806 cadd(fn)
807 elif state == 'm':
807 elif state == 'm':
808 madd(fn)
808 madd(fn)
809 elif state == 'a':
809 elif state == 'a':
810 aadd(fn)
810 aadd(fn)
811 elif state == 'r':
811 elif state == 'r':
812 radd(fn)
812 radd(fn)
813
813
814 return (lookup, modified, added, removed, deleted, unknown, ignored,
814 return (lookup, modified, added, removed, deleted, unknown, ignored,
815 clean)
815 clean)
@@ -1,126 +1,136 b''
1 $ hg init
1 $ hg init
2
2
3 Issue562: .hgignore requires newline at end:
3 Issue562: .hgignore requires newline at end:
4
4
5 $ touch foo
5 $ touch foo
6 $ touch bar
6 $ touch bar
7 $ touch baz
7 $ touch baz
8 $ cat > makeignore.py <<EOF
8 $ cat > makeignore.py <<EOF
9 > f = open(".hgignore", "w")
9 > f = open(".hgignore", "w")
10 > f.write("ignore\n")
10 > f.write("ignore\n")
11 > f.write("foo\n")
11 > f.write("foo\n")
12 > # No EOL here
12 > # No EOL here
13 > f.write("bar")
13 > f.write("bar")
14 > f.close()
14 > f.close()
15 > EOF
15 > EOF
16
16
17 $ python makeignore.py
17 $ python makeignore.py
18
18
19 Should display baz only:
19 Should display baz only:
20
20
21 $ hg status
21 $ hg status
22 ? baz
22 ? baz
23
23
24 $ rm foo bar baz .hgignore makeignore.py
24 $ rm foo bar baz .hgignore makeignore.py
25
25
26 $ touch a.o
26 $ touch a.o
27 $ touch a.c
27 $ touch a.c
28 $ touch syntax
28 $ touch syntax
29 $ mkdir dir
29 $ mkdir dir
30 $ touch dir/a.o
30 $ touch dir/a.o
31 $ touch dir/b.o
31 $ touch dir/b.o
32 $ touch dir/c.o
32 $ touch dir/c.o
33
33
34 $ hg add dir/a.o
34 $ hg add dir/a.o
35 $ hg commit -m 0
35 $ hg commit -m 0
36 $ hg add dir/b.o
36 $ hg add dir/b.o
37
37
38 $ hg status
38 $ hg status
39 A dir/b.o
39 A dir/b.o
40 ? a.c
40 ? a.c
41 ? a.o
41 ? a.o
42 ? dir/c.o
42 ? dir/c.o
43 ? syntax
43 ? syntax
44
44
45 $ echo "*.o" > .hgignore
45 $ echo "*.o" > .hgignore
46 $ hg status
46 $ hg status
47 abort: $TESTTMP/.hgignore: invalid pattern (relre): *.o (glob)
47 abort: $TESTTMP/.hgignore: invalid pattern (relre): *.o (glob)
48 [255]
48 [255]
49
49
50 $ echo ".*\.o" > .hgignore
50 $ echo ".*\.o" > .hgignore
51 $ hg status
51 $ hg status
52 A dir/b.o
52 A dir/b.o
53 ? .hgignore
53 ? .hgignore
54 ? a.c
54 ? a.c
55 ? syntax
55 ? syntax
56
56
57 Check it does not ignore the current directory '.':
57 Check it does not ignore the current directory '.':
58
58
59 $ echo "^\." > .hgignore
59 $ echo "^\." > .hgignore
60 $ hg status
60 $ hg status
61 A dir/b.o
61 A dir/b.o
62 ? a.c
62 ? a.c
63 ? a.o
63 ? a.o
64 ? dir/c.o
64 ? dir/c.o
65 ? syntax
65 ? syntax
66
66
67 $ echo "glob:**.o" > .hgignore
67 $ echo "glob:**.o" > .hgignore
68 $ hg status
68 $ hg status
69 A dir/b.o
69 A dir/b.o
70 ? .hgignore
70 ? .hgignore
71 ? a.c
71 ? a.c
72 ? syntax
72 ? syntax
73
73
74 $ echo "glob:*.o" > .hgignore
74 $ echo "glob:*.o" > .hgignore
75 $ hg status
75 $ hg status
76 A dir/b.o
76 A dir/b.o
77 ? .hgignore
77 ? .hgignore
78 ? a.c
78 ? a.c
79 ? syntax
79 ? syntax
80
80
81 $ echo "syntax: glob" > .hgignore
81 $ echo "syntax: glob" > .hgignore
82 $ echo "re:.*\.o" >> .hgignore
82 $ echo "re:.*\.o" >> .hgignore
83 $ hg status
83 $ hg status
84 A dir/b.o
84 A dir/b.o
85 ? .hgignore
85 ? .hgignore
86 ? a.c
86 ? a.c
87 ? syntax
87 ? syntax
88
88
89 $ echo "syntax: invalid" > .hgignore
89 $ echo "syntax: invalid" > .hgignore
90 $ hg status
90 $ hg status
91 $TESTTMP/.hgignore: ignoring invalid syntax 'invalid' (glob)
91 $TESTTMP/.hgignore: ignoring invalid syntax 'invalid' (glob)
92 A dir/b.o
92 A dir/b.o
93 ? .hgignore
93 ? .hgignore
94 ? a.c
94 ? a.c
95 ? a.o
95 ? a.o
96 ? dir/c.o
96 ? dir/c.o
97 ? syntax
97 ? syntax
98
98
99 $ echo "syntax: glob" > .hgignore
99 $ echo "syntax: glob" > .hgignore
100 $ echo "*.o" >> .hgignore
100 $ echo "*.o" >> .hgignore
101 $ hg status
101 $ hg status
102 A dir/b.o
102 A dir/b.o
103 ? .hgignore
103 ? .hgignore
104 ? a.c
104 ? a.c
105 ? syntax
105 ? syntax
106
106
107 $ echo "relglob:syntax*" > .hgignore
107 $ echo "relglob:syntax*" > .hgignore
108 $ hg status
108 $ hg status
109 A dir/b.o
109 A dir/b.o
110 ? .hgignore
110 ? .hgignore
111 ? a.c
111 ? a.c
112 ? a.o
112 ? a.o
113 ? dir/c.o
113 ? dir/c.o
114
114
115 $ echo "relglob:*" > .hgignore
115 $ echo "relglob:*" > .hgignore
116 $ hg status
116 $ hg status
117 A dir/b.o
117 A dir/b.o
118
118
119 $ cd dir
119 $ cd dir
120 $ hg status .
120 $ hg status .
121 A b.o
121 A b.o
122
122
123 $ hg debugignore
123 $ hg debugignore
124 (?:(?:|.*/)[^/]*(?:/|$))
124 (?:(?:|.*/)[^/]*(?:/|$))
125
125
126 $ cd ..
126 $ cd ..
127
128 Check patterns that match only the directory
129
130 $ echo "^dir\$" > .hgignore
131 $ hg status
132 A dir/b.o
133 ? .hgignore
134 ? a.c
135 ? a.o
136 ? syntax
@@ -1,278 +1,302 b''
1 $ hg init repo
1 $ hg init repo
2 $ cd repo
2 $ cd repo
3 $ echo 123 > a
3 $ echo 123 > a
4 $ echo 123 > c
4 $ echo 123 > c
5 $ echo 123 > e
5 $ echo 123 > e
6 $ hg add a c e
6 $ hg add a c e
7 $ hg commit -m "first" a c e
7 $ hg commit -m "first" a c e
8
8
9 nothing changed
9 nothing changed
10
10
11 $ hg revert
11 $ hg revert
12 abort: no files or directories specified
12 abort: no files or directories specified
13 (use --all to revert all files)
13 (use --all to revert all files)
14 [255]
14 [255]
15 $ hg revert --all
15 $ hg revert --all
16
16
17 $ echo 123 > b
17 $ echo 123 > b
18
18
19 should show b unknown
19 should show b unknown
20
20
21 $ hg status
21 $ hg status
22 ? b
22 ? b
23 $ echo 12 > c
23 $ echo 12 > c
24
24
25 should show b unknown and c modified
25 should show b unknown and c modified
26
26
27 $ hg status
27 $ hg status
28 M c
28 M c
29 ? b
29 ? b
30 $ hg add b
30 $ hg add b
31
31
32 should show b added and c modified
32 should show b added and c modified
33
33
34 $ hg status
34 $ hg status
35 M c
35 M c
36 A b
36 A b
37 $ hg rm a
37 $ hg rm a
38
38
39 should show a removed, b added and c modified
39 should show a removed, b added and c modified
40
40
41 $ hg status
41 $ hg status
42 M c
42 M c
43 A b
43 A b
44 R a
44 R a
45 $ hg revert a
45 $ hg revert a
46
46
47 should show b added, copy saved, and c modified
47 should show b added, copy saved, and c modified
48
48
49 $ hg status
49 $ hg status
50 M c
50 M c
51 A b
51 A b
52 $ hg revert b
52 $ hg revert b
53
53
54 should show b unknown, and c modified
54 should show b unknown, and c modified
55
55
56 $ hg status
56 $ hg status
57 M c
57 M c
58 ? b
58 ? b
59 $ hg revert --no-backup c
59 $ hg revert --no-backup c
60
60
61 should show unknown: b
61 should show unknown: b
62
62
63 $ hg status
63 $ hg status
64 ? b
64 ? b
65 $ hg add b
65 $ hg add b
66
66
67 should show b added
67 should show b added
68
68
69 $ hg status b
69 $ hg status b
70 A b
70 A b
71 $ rm b
71 $ rm b
72
72
73 should show b deleted
73 should show b deleted
74
74
75 $ hg status b
75 $ hg status b
76 ! b
76 ! b
77 $ hg revert -v b
77 $ hg revert -v b
78 forgetting b
78 forgetting b
79
79
80 should not find b
80 should not find b
81
81
82 $ hg status b
82 $ hg status b
83 b: * (glob)
83 b: * (glob)
84
84
85 should show a c e
85 should show a c e
86
86
87 $ ls
87 $ ls
88 a
88 a
89 c
89 c
90 e
90 e
91
91
92 should verbosely save backup to e.orig
92 should verbosely save backup to e.orig
93
93
94 $ echo z > e
94 $ echo z > e
95 $ hg revert --all -v
95 $ hg revert --all -v
96 saving current version of e as e.orig
96 saving current version of e as e.orig
97 reverting e
97 reverting e
98
98
99 should say no changes needed
99 should say no changes needed
100
100
101 $ hg revert a
101 $ hg revert a
102 no changes needed to a
102 no changes needed to a
103
103
104 should say file not managed
104 should say file not managed
105
105
106 $ echo q > q
106 $ echo q > q
107 $ hg revert q
107 $ hg revert q
108 file not managed: q
108 file not managed: q
109 $ rm q
109 $ rm q
110
110
111 should say file not found
111 should say file not found
112
112
113 $ hg revert notfound
113 $ hg revert notfound
114 notfound: no such file in rev 334a9e57682c
114 notfound: no such file in rev 334a9e57682c
115 $ touch d
115 $ touch d
116 $ hg add d
116 $ hg add d
117 $ hg rm a
117 $ hg rm a
118 $ hg commit -m "second"
118 $ hg commit -m "second"
119 $ echo z > z
119 $ echo z > z
120 $ hg add z
120 $ hg add z
121 $ hg st
121 $ hg st
122 A z
122 A z
123 ? e.orig
123 ? e.orig
124
124
125 should add a, remove d, forget z
125 should add a, remove d, forget z
126
126
127 $ hg revert --all -r0
127 $ hg revert --all -r0
128 adding a
128 adding a
129 removing d
129 removing d
130 forgetting z
130 forgetting z
131
131
132 should forget a, undelete d
132 should forget a, undelete d
133
133
134 $ hg revert --all -rtip
134 $ hg revert --all -rtip
135 forgetting a
135 forgetting a
136 undeleting d
136 undeleting d
137 $ rm a *.orig
137 $ rm a *.orig
138
138
139 should silently add a
139 should silently add a
140
140
141 $ hg revert -r0 a
141 $ hg revert -r0 a
142 $ hg st a
142 $ hg st a
143 A a
143 A a
144 $ hg rm d
144 $ hg rm d
145 $ hg st d
145 $ hg st d
146 R d
146 R d
147
147
148 should silently keep d removed
148 should silently keep d removed
149
149
150 $ hg revert -r0 d
150 $ hg revert -r0 d
151 $ hg st d
151 $ hg st d
152 R d
152 R d
153
153
154 $ hg update -C
154 $ hg update -C
155 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
155 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
156 #if execbit
156 #if execbit
157 $ chmod +x c
157 $ chmod +x c
158 $ hg revert --all
158 $ hg revert --all
159 reverting c
159 reverting c
160
160
161 should print non-executable
161 should print non-executable
162
162
163 $ test -x c || echo non-executable
163 $ test -x c || echo non-executable
164 non-executable
164 non-executable
165
165
166 $ chmod +x c
166 $ chmod +x c
167 $ hg commit -m exe
167 $ hg commit -m exe
168
168
169 $ chmod -x c
169 $ chmod -x c
170 $ hg revert --all
170 $ hg revert --all
171 reverting c
171 reverting c
172
172
173 should print executable
173 should print executable
174
174
175 $ test -x c && echo executable
175 $ test -x c && echo executable
176 executable
176 executable
177 #endif
177 #endif
178
178
179 $ cd ..
179 $ cd ..
180
180
181
181
182 Issue241: update and revert produces inconsistent repositories
182 Issue241: update and revert produces inconsistent repositories
183
183
184 $ hg init a
184 $ hg init a
185 $ cd a
185 $ cd a
186 $ echo a >> a
186 $ echo a >> a
187 $ hg commit -A -d '1 0' -m a
187 $ hg commit -A -d '1 0' -m a
188 adding a
188 adding a
189 $ echo a >> a
189 $ echo a >> a
190 $ hg commit -d '2 0' -m a
190 $ hg commit -d '2 0' -m a
191 $ hg update 0
191 $ hg update 0
192 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
192 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
193 $ mkdir b
193 $ mkdir b
194 $ echo b > b/b
194 $ echo b > b/b
195
195
196 should fail - no arguments
196 should fail - no arguments
197
197
198 $ hg revert -rtip
198 $ hg revert -rtip
199 abort: no files or directories specified
199 abort: no files or directories specified
200 (use --all to revert all files, or 'hg update 1' to update)
200 (use --all to revert all files, or 'hg update 1' to update)
201 [255]
201 [255]
202
202
203 should succeed
203 should succeed
204
204
205 $ hg revert --all -rtip
205 $ hg revert --all -rtip
206 reverting a
206 reverting a
207
207
208
208
209 Issue332: confusing message when reverting directory
209 Issue332: confusing message when reverting directory
210
210
211 $ hg ci -A -m b
211 $ hg ci -A -m b
212 adding b/b
212 adding b/b
213 created new head
213 created new head
214 $ echo foobar > b/b
214 $ echo foobar > b/b
215 $ mkdir newdir
215 $ mkdir newdir
216 $ echo foo > newdir/newfile
216 $ echo foo > newdir/newfile
217 $ hg add newdir/newfile
217 $ hg add newdir/newfile
218 $ hg revert b newdir
218 $ hg revert b newdir
219 reverting b/b (glob)
219 reverting b/b (glob)
220 forgetting newdir/newfile (glob)
220 forgetting newdir/newfile (glob)
221 $ echo foobar > b/b
221 $ echo foobar > b/b
222 $ hg revert .
222 $ hg revert .
223 reverting b/b (glob)
223 reverting b/b (glob)
224
224
225
225
226 reverting a rename target should revert the source
226 reverting a rename target should revert the source
227
227
228 $ hg mv a newa
228 $ hg mv a newa
229 $ hg revert newa
229 $ hg revert newa
230 $ hg st a newa
230 $ hg st a newa
231 ? newa
231 ? newa
232
232
233 $ cd ..
233 $ cd ..
234
234
235 $ hg init ignored
235 $ hg init ignored
236 $ cd ignored
236 $ cd ignored
237 $ echo '^ignored$' > .hgignore
237 $ echo '^ignored$' > .hgignore
238 $ echo '^ignoreddir$' >> .hgignore
238 $ echo '^ignoreddir$' >> .hgignore
239 $ echo '^removed$' >> .hgignore
239 $ echo '^removed$' >> .hgignore
240
240
241 $ mkdir ignoreddir
241 $ mkdir ignoreddir
242 $ touch ignoreddir/file
242 $ touch ignoreddir/file
243 $ touch ignoreddir/removed
243 $ touch ignoreddir/removed
244 $ touch ignored
244 $ touch ignored
245 $ touch removed
245 $ touch removed
246
246
247 4 ignored files (we will add/commit everything)
247 4 ignored files (we will add/commit everything)
248
248
249 $ hg st -A -X .hgignore
249 $ hg st -A -X .hgignore
250 I ignored
250 I ignored
251 I ignoreddir/file
251 I ignoreddir/file
252 I ignoreddir/removed
252 I ignoreddir/removed
253 I removed
253 I removed
254 $ hg ci -qAm 'add files' ignored ignoreddir/file ignoreddir/removed removed
254 $ hg ci -qAm 'add files' ignored ignoreddir/file ignoreddir/removed removed
255
255
256 $ echo >> ignored
256 $ echo >> ignored
257 $ echo >> ignoreddir/file
257 $ echo >> ignoreddir/file
258 $ hg rm removed ignoreddir/removed
258 $ hg rm removed ignoreddir/removed
259
259
260 should revert ignored* and undelete *removed
260 should revert ignored* and undelete *removed
261
261
262 $ hg revert -a --no-backup
262 $ hg revert -a --no-backup
263 reverting ignored
263 reverting ignored
264 reverting ignoreddir/file (glob)
264 reverting ignoreddir/file (glob)
265 undeleting ignoreddir/removed (glob)
265 undeleting ignoreddir/removed (glob)
266 undeleting removed
266 undeleting removed
267 $ hg st -mardi
267 $ hg st -mardi
268
268
269 $ hg up -qC
269 $ hg up -qC
270 $ echo >> ignored
270 $ echo >> ignored
271 $ hg rm removed
271 $ hg rm removed
272
272
273 should silently revert the named files
273 should silently revert the named files
274
274
275 $ hg revert --no-backup ignored removed
275 $ hg revert --no-backup ignored removed
276 $ hg st -mardi
276 $ hg st -mardi
277
277
278 someone set up us the copies
279
280 $ rm .hgignore
281 $ hg update -C
282 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
283 $ hg mv ignored allyour
284 $ hg copy removed base
285 $ hg commit -m rename
286
287 copies and renames, you have no chance to survive make your time (issue3920)
288
289 $ hg update '.^'
290 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
291 $ hg revert -rtip -a
292 adding allyour
293 adding base
294 removing ignored
295 $ hg status -C
296 A allyour
297 ignored
298 A base
299 removed
300 R ignored
301
278 $ cd ..
302 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now