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