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