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