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