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