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