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