##// END OF EJS Templates
debugobsolete: display parents information from markers...
Pierre-Yves David -
r22260:2229d757 default
parent child Browse files
Show More
@@ -1,2716 +1,2719
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 context, repair, graphmod, revset, phases, obsolete, pathutil
13 import context, repair, graphmod, revset, phases, obsolete, pathutil
14 import changelog
14 import changelog
15 import bookmarks
15 import bookmarks
16 import lock as lockmod
16 import lock as lockmod
17
17
18 def parsealiases(cmd):
18 def parsealiases(cmd):
19 return cmd.lstrip("^").split("|")
19 return cmd.lstrip("^").split("|")
20
20
21 def findpossible(cmd, table, strict=False):
21 def findpossible(cmd, table, strict=False):
22 """
22 """
23 Return cmd -> (aliases, command table entry)
23 Return cmd -> (aliases, command table entry)
24 for each matching command.
24 for each matching command.
25 Return debug commands (or their aliases) only if no normal command matches.
25 Return debug commands (or their aliases) only if no normal command matches.
26 """
26 """
27 choice = {}
27 choice = {}
28 debugchoice = {}
28 debugchoice = {}
29
29
30 if cmd in table:
30 if cmd in table:
31 # short-circuit exact matches, "log" alias beats "^log|history"
31 # short-circuit exact matches, "log" alias beats "^log|history"
32 keys = [cmd]
32 keys = [cmd]
33 else:
33 else:
34 keys = table.keys()
34 keys = table.keys()
35
35
36 for e in keys:
36 for e in keys:
37 aliases = parsealiases(e)
37 aliases = parsealiases(e)
38 found = None
38 found = None
39 if cmd in aliases:
39 if cmd in aliases:
40 found = cmd
40 found = cmd
41 elif not strict:
41 elif not strict:
42 for a in aliases:
42 for a in aliases:
43 if a.startswith(cmd):
43 if a.startswith(cmd):
44 found = a
44 found = a
45 break
45 break
46 if found is not None:
46 if found is not None:
47 if aliases[0].startswith("debug") or found.startswith("debug"):
47 if aliases[0].startswith("debug") or found.startswith("debug"):
48 debugchoice[found] = (aliases, table[e])
48 debugchoice[found] = (aliases, table[e])
49 else:
49 else:
50 choice[found] = (aliases, table[e])
50 choice[found] = (aliases, table[e])
51
51
52 if not choice and debugchoice:
52 if not choice and debugchoice:
53 choice = debugchoice
53 choice = debugchoice
54
54
55 return choice
55 return choice
56
56
57 def findcmd(cmd, table, strict=True):
57 def findcmd(cmd, table, strict=True):
58 """Return (aliases, command table entry) for command string."""
58 """Return (aliases, command table entry) for command string."""
59 choice = findpossible(cmd, table, strict)
59 choice = findpossible(cmd, table, strict)
60
60
61 if cmd in choice:
61 if cmd in choice:
62 return choice[cmd]
62 return choice[cmd]
63
63
64 if len(choice) > 1:
64 if len(choice) > 1:
65 clist = choice.keys()
65 clist = choice.keys()
66 clist.sort()
66 clist.sort()
67 raise error.AmbiguousCommand(cmd, clist)
67 raise error.AmbiguousCommand(cmd, clist)
68
68
69 if choice:
69 if choice:
70 return choice.values()[0]
70 return choice.values()[0]
71
71
72 raise error.UnknownCommand(cmd)
72 raise error.UnknownCommand(cmd)
73
73
74 def findrepo(p):
74 def findrepo(p):
75 while not os.path.isdir(os.path.join(p, ".hg")):
75 while not os.path.isdir(os.path.join(p, ".hg")):
76 oldp, p = p, os.path.dirname(p)
76 oldp, p = p, os.path.dirname(p)
77 if p == oldp:
77 if p == oldp:
78 return None
78 return None
79
79
80 return p
80 return p
81
81
82 def bailifchanged(repo):
82 def bailifchanged(repo):
83 if repo.dirstate.p2() != nullid:
83 if repo.dirstate.p2() != nullid:
84 raise util.Abort(_('outstanding uncommitted merge'))
84 raise util.Abort(_('outstanding uncommitted merge'))
85 modified, added, removed, deleted = repo.status()[:4]
85 modified, added, removed, deleted = repo.status()[:4]
86 if modified or added or removed or deleted:
86 if modified or added or removed or deleted:
87 raise util.Abort(_('uncommitted changes'))
87 raise util.Abort(_('uncommitted changes'))
88 ctx = repo[None]
88 ctx = repo[None]
89 for s in sorted(ctx.substate):
89 for s in sorted(ctx.substate):
90 if ctx.sub(s).dirty():
90 if ctx.sub(s).dirty():
91 raise util.Abort(_("uncommitted changes in subrepo %s") % s)
91 raise util.Abort(_("uncommitted changes in subrepo %s") % s)
92
92
93 def logmessage(ui, opts):
93 def logmessage(ui, opts):
94 """ get the log message according to -m and -l option """
94 """ get the log message according to -m and -l option """
95 message = opts.get('message')
95 message = opts.get('message')
96 logfile = opts.get('logfile')
96 logfile = opts.get('logfile')
97
97
98 if message and logfile:
98 if message and logfile:
99 raise util.Abort(_('options --message and --logfile are mutually '
99 raise util.Abort(_('options --message and --logfile are mutually '
100 'exclusive'))
100 'exclusive'))
101 if not message and logfile:
101 if not message and logfile:
102 try:
102 try:
103 if logfile == '-':
103 if logfile == '-':
104 message = ui.fin.read()
104 message = ui.fin.read()
105 else:
105 else:
106 message = '\n'.join(util.readfile(logfile).splitlines())
106 message = '\n'.join(util.readfile(logfile).splitlines())
107 except IOError, inst:
107 except IOError, inst:
108 raise util.Abort(_("can't read commit message '%s': %s") %
108 raise util.Abort(_("can't read commit message '%s': %s") %
109 (logfile, inst.strerror))
109 (logfile, inst.strerror))
110 return message
110 return message
111
111
112 def mergeeditform(ctxorbool, baseform):
112 def mergeeditform(ctxorbool, baseform):
113 """build appropriate editform from ctxorbool and baseform
113 """build appropriate editform from ctxorbool and baseform
114
114
115 'cxtorbool' is one of a ctx to be committed, or a bool whether
115 'cxtorbool' is one of a ctx to be committed, or a bool whether
116 merging is committed.
116 merging is committed.
117
117
118 This returns editform 'baseform' with '.merge' if merging is
118 This returns editform 'baseform' with '.merge' if merging is
119 committed, or one with '.normal' suffix otherwise.
119 committed, or one with '.normal' suffix otherwise.
120 """
120 """
121 if isinstance(ctxorbool, bool):
121 if isinstance(ctxorbool, bool):
122 if ctxorbool:
122 if ctxorbool:
123 return baseform + ".merge"
123 return baseform + ".merge"
124 elif 1 < len(ctxorbool.parents()):
124 elif 1 < len(ctxorbool.parents()):
125 return baseform + ".merge"
125 return baseform + ".merge"
126
126
127 return baseform + ".normal"
127 return baseform + ".normal"
128
128
129 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
129 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
130 editform='', **opts):
130 editform='', **opts):
131 """get appropriate commit message editor according to '--edit' option
131 """get appropriate commit message editor according to '--edit' option
132
132
133 'finishdesc' is a function to be called with edited commit message
133 'finishdesc' is a function to be called with edited commit message
134 (= 'description' of the new changeset) just after editing, but
134 (= 'description' of the new changeset) just after editing, but
135 before checking empty-ness. It should return actual text to be
135 before checking empty-ness. It should return actual text to be
136 stored into history. This allows to change description before
136 stored into history. This allows to change description before
137 storing.
137 storing.
138
138
139 'extramsg' is a extra message to be shown in the editor instead of
139 'extramsg' is a extra message to be shown in the editor instead of
140 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
140 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
141 is automatically added.
141 is automatically added.
142
142
143 'editform' is a dot-separated list of names, to distinguish
143 'editform' is a dot-separated list of names, to distinguish
144 the purpose of commit text editing.
144 the purpose of commit text editing.
145
145
146 'getcommiteditor' returns 'commitforceeditor' regardless of
146 'getcommiteditor' returns 'commitforceeditor' regardless of
147 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
147 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
148 they are specific for usage in MQ.
148 they are specific for usage in MQ.
149 """
149 """
150 if edit or finishdesc or extramsg:
150 if edit or finishdesc or extramsg:
151 return lambda r, c, s: commitforceeditor(r, c, s,
151 return lambda r, c, s: commitforceeditor(r, c, s,
152 finishdesc=finishdesc,
152 finishdesc=finishdesc,
153 extramsg=extramsg,
153 extramsg=extramsg,
154 editform=editform)
154 editform=editform)
155 elif editform:
155 elif editform:
156 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
156 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
157 else:
157 else:
158 return commiteditor
158 return commiteditor
159
159
160 def loglimit(opts):
160 def loglimit(opts):
161 """get the log limit according to option -l/--limit"""
161 """get the log limit according to option -l/--limit"""
162 limit = opts.get('limit')
162 limit = opts.get('limit')
163 if limit:
163 if limit:
164 try:
164 try:
165 limit = int(limit)
165 limit = int(limit)
166 except ValueError:
166 except ValueError:
167 raise util.Abort(_('limit must be a positive integer'))
167 raise util.Abort(_('limit must be a positive integer'))
168 if limit <= 0:
168 if limit <= 0:
169 raise util.Abort(_('limit must be positive'))
169 raise util.Abort(_('limit must be positive'))
170 else:
170 else:
171 limit = None
171 limit = None
172 return limit
172 return limit
173
173
174 def makefilename(repo, pat, node, desc=None,
174 def makefilename(repo, pat, node, desc=None,
175 total=None, seqno=None, revwidth=None, pathname=None):
175 total=None, seqno=None, revwidth=None, pathname=None):
176 node_expander = {
176 node_expander = {
177 'H': lambda: hex(node),
177 'H': lambda: hex(node),
178 'R': lambda: str(repo.changelog.rev(node)),
178 'R': lambda: str(repo.changelog.rev(node)),
179 'h': lambda: short(node),
179 'h': lambda: short(node),
180 'm': lambda: re.sub('[^\w]', '_', str(desc))
180 'm': lambda: re.sub('[^\w]', '_', str(desc))
181 }
181 }
182 expander = {
182 expander = {
183 '%': lambda: '%',
183 '%': lambda: '%',
184 'b': lambda: os.path.basename(repo.root),
184 'b': lambda: os.path.basename(repo.root),
185 }
185 }
186
186
187 try:
187 try:
188 if node:
188 if node:
189 expander.update(node_expander)
189 expander.update(node_expander)
190 if node:
190 if node:
191 expander['r'] = (lambda:
191 expander['r'] = (lambda:
192 str(repo.changelog.rev(node)).zfill(revwidth or 0))
192 str(repo.changelog.rev(node)).zfill(revwidth or 0))
193 if total is not None:
193 if total is not None:
194 expander['N'] = lambda: str(total)
194 expander['N'] = lambda: str(total)
195 if seqno is not None:
195 if seqno is not None:
196 expander['n'] = lambda: str(seqno)
196 expander['n'] = lambda: str(seqno)
197 if total is not None and seqno is not None:
197 if total is not None and seqno is not None:
198 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
198 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
199 if pathname is not None:
199 if pathname is not None:
200 expander['s'] = lambda: os.path.basename(pathname)
200 expander['s'] = lambda: os.path.basename(pathname)
201 expander['d'] = lambda: os.path.dirname(pathname) or '.'
201 expander['d'] = lambda: os.path.dirname(pathname) or '.'
202 expander['p'] = lambda: pathname
202 expander['p'] = lambda: pathname
203
203
204 newname = []
204 newname = []
205 patlen = len(pat)
205 patlen = len(pat)
206 i = 0
206 i = 0
207 while i < patlen:
207 while i < patlen:
208 c = pat[i]
208 c = pat[i]
209 if c == '%':
209 if c == '%':
210 i += 1
210 i += 1
211 c = pat[i]
211 c = pat[i]
212 c = expander[c]()
212 c = expander[c]()
213 newname.append(c)
213 newname.append(c)
214 i += 1
214 i += 1
215 return ''.join(newname)
215 return ''.join(newname)
216 except KeyError, inst:
216 except KeyError, inst:
217 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
217 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
218 inst.args[0])
218 inst.args[0])
219
219
220 def makefileobj(repo, pat, node=None, desc=None, total=None,
220 def makefileobj(repo, pat, node=None, desc=None, total=None,
221 seqno=None, revwidth=None, mode='wb', modemap=None,
221 seqno=None, revwidth=None, mode='wb', modemap=None,
222 pathname=None):
222 pathname=None):
223
223
224 writable = mode not in ('r', 'rb')
224 writable = mode not in ('r', 'rb')
225
225
226 if not pat or pat == '-':
226 if not pat or pat == '-':
227 fp = writable and repo.ui.fout or repo.ui.fin
227 fp = writable and repo.ui.fout or repo.ui.fin
228 if util.safehasattr(fp, 'fileno'):
228 if util.safehasattr(fp, 'fileno'):
229 return os.fdopen(os.dup(fp.fileno()), mode)
229 return os.fdopen(os.dup(fp.fileno()), mode)
230 else:
230 else:
231 # if this fp can't be duped properly, return
231 # if this fp can't be duped properly, return
232 # a dummy object that can be closed
232 # a dummy object that can be closed
233 class wrappedfileobj(object):
233 class wrappedfileobj(object):
234 noop = lambda x: None
234 noop = lambda x: None
235 def __init__(self, f):
235 def __init__(self, f):
236 self.f = f
236 self.f = f
237 def __getattr__(self, attr):
237 def __getattr__(self, attr):
238 if attr == 'close':
238 if attr == 'close':
239 return self.noop
239 return self.noop
240 else:
240 else:
241 return getattr(self.f, attr)
241 return getattr(self.f, attr)
242
242
243 return wrappedfileobj(fp)
243 return wrappedfileobj(fp)
244 if util.safehasattr(pat, 'write') and writable:
244 if util.safehasattr(pat, 'write') and writable:
245 return pat
245 return pat
246 if util.safehasattr(pat, 'read') and 'r' in mode:
246 if util.safehasattr(pat, 'read') and 'r' in mode:
247 return pat
247 return pat
248 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
248 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
249 if modemap is not None:
249 if modemap is not None:
250 mode = modemap.get(fn, mode)
250 mode = modemap.get(fn, mode)
251 if mode == 'wb':
251 if mode == 'wb':
252 modemap[fn] = 'ab'
252 modemap[fn] = 'ab'
253 return open(fn, mode)
253 return open(fn, mode)
254
254
255 def openrevlog(repo, cmd, file_, opts):
255 def openrevlog(repo, cmd, file_, opts):
256 """opens the changelog, manifest, a filelog or a given revlog"""
256 """opens the changelog, manifest, a filelog or a given revlog"""
257 cl = opts['changelog']
257 cl = opts['changelog']
258 mf = opts['manifest']
258 mf = opts['manifest']
259 msg = None
259 msg = None
260 if cl and mf:
260 if cl and mf:
261 msg = _('cannot specify --changelog and --manifest at the same time')
261 msg = _('cannot specify --changelog and --manifest at the same time')
262 elif cl or mf:
262 elif cl or mf:
263 if file_:
263 if file_:
264 msg = _('cannot specify filename with --changelog or --manifest')
264 msg = _('cannot specify filename with --changelog or --manifest')
265 elif not repo:
265 elif not repo:
266 msg = _('cannot specify --changelog or --manifest '
266 msg = _('cannot specify --changelog or --manifest '
267 'without a repository')
267 'without a repository')
268 if msg:
268 if msg:
269 raise util.Abort(msg)
269 raise util.Abort(msg)
270
270
271 r = None
271 r = None
272 if repo:
272 if repo:
273 if cl:
273 if cl:
274 r = repo.unfiltered().changelog
274 r = repo.unfiltered().changelog
275 elif mf:
275 elif mf:
276 r = repo.manifest
276 r = repo.manifest
277 elif file_:
277 elif file_:
278 filelog = repo.file(file_)
278 filelog = repo.file(file_)
279 if len(filelog):
279 if len(filelog):
280 r = filelog
280 r = filelog
281 if not r:
281 if not r:
282 if not file_:
282 if not file_:
283 raise error.CommandError(cmd, _('invalid arguments'))
283 raise error.CommandError(cmd, _('invalid arguments'))
284 if not os.path.isfile(file_):
284 if not os.path.isfile(file_):
285 raise util.Abort(_("revlog '%s' not found") % file_)
285 raise util.Abort(_("revlog '%s' not found") % file_)
286 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
286 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
287 file_[:-2] + ".i")
287 file_[:-2] + ".i")
288 return r
288 return r
289
289
290 def copy(ui, repo, pats, opts, rename=False):
290 def copy(ui, repo, pats, opts, rename=False):
291 # called with the repo lock held
291 # called with the repo lock held
292 #
292 #
293 # hgsep => pathname that uses "/" to separate directories
293 # hgsep => pathname that uses "/" to separate directories
294 # ossep => pathname that uses os.sep to separate directories
294 # ossep => pathname that uses os.sep to separate directories
295 cwd = repo.getcwd()
295 cwd = repo.getcwd()
296 targets = {}
296 targets = {}
297 after = opts.get("after")
297 after = opts.get("after")
298 dryrun = opts.get("dry_run")
298 dryrun = opts.get("dry_run")
299 wctx = repo[None]
299 wctx = repo[None]
300
300
301 def walkpat(pat):
301 def walkpat(pat):
302 srcs = []
302 srcs = []
303 badstates = after and '?' or '?r'
303 badstates = after and '?' or '?r'
304 m = scmutil.match(repo[None], [pat], opts, globbed=True)
304 m = scmutil.match(repo[None], [pat], opts, globbed=True)
305 for abs in repo.walk(m):
305 for abs in repo.walk(m):
306 state = repo.dirstate[abs]
306 state = repo.dirstate[abs]
307 rel = m.rel(abs)
307 rel = m.rel(abs)
308 exact = m.exact(abs)
308 exact = m.exact(abs)
309 if state in badstates:
309 if state in badstates:
310 if exact and state == '?':
310 if exact and state == '?':
311 ui.warn(_('%s: not copying - file is not managed\n') % rel)
311 ui.warn(_('%s: not copying - file is not managed\n') % rel)
312 if exact and state == 'r':
312 if exact and state == 'r':
313 ui.warn(_('%s: not copying - file has been marked for'
313 ui.warn(_('%s: not copying - file has been marked for'
314 ' remove\n') % rel)
314 ' remove\n') % rel)
315 continue
315 continue
316 # abs: hgsep
316 # abs: hgsep
317 # rel: ossep
317 # rel: ossep
318 srcs.append((abs, rel, exact))
318 srcs.append((abs, rel, exact))
319 return srcs
319 return srcs
320
320
321 # abssrc: hgsep
321 # abssrc: hgsep
322 # relsrc: ossep
322 # relsrc: ossep
323 # otarget: ossep
323 # otarget: ossep
324 def copyfile(abssrc, relsrc, otarget, exact):
324 def copyfile(abssrc, relsrc, otarget, exact):
325 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
325 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
326 if '/' in abstarget:
326 if '/' in abstarget:
327 # We cannot normalize abstarget itself, this would prevent
327 # We cannot normalize abstarget itself, this would prevent
328 # case only renames, like a => A.
328 # case only renames, like a => A.
329 abspath, absname = abstarget.rsplit('/', 1)
329 abspath, absname = abstarget.rsplit('/', 1)
330 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
330 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
331 reltarget = repo.pathto(abstarget, cwd)
331 reltarget = repo.pathto(abstarget, cwd)
332 target = repo.wjoin(abstarget)
332 target = repo.wjoin(abstarget)
333 src = repo.wjoin(abssrc)
333 src = repo.wjoin(abssrc)
334 state = repo.dirstate[abstarget]
334 state = repo.dirstate[abstarget]
335
335
336 scmutil.checkportable(ui, abstarget)
336 scmutil.checkportable(ui, abstarget)
337
337
338 # check for collisions
338 # check for collisions
339 prevsrc = targets.get(abstarget)
339 prevsrc = targets.get(abstarget)
340 if prevsrc is not None:
340 if prevsrc is not None:
341 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
341 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
342 (reltarget, repo.pathto(abssrc, cwd),
342 (reltarget, repo.pathto(abssrc, cwd),
343 repo.pathto(prevsrc, cwd)))
343 repo.pathto(prevsrc, cwd)))
344 return
344 return
345
345
346 # check for overwrites
346 # check for overwrites
347 exists = os.path.lexists(target)
347 exists = os.path.lexists(target)
348 samefile = False
348 samefile = False
349 if exists and abssrc != abstarget:
349 if exists and abssrc != abstarget:
350 if (repo.dirstate.normalize(abssrc) ==
350 if (repo.dirstate.normalize(abssrc) ==
351 repo.dirstate.normalize(abstarget)):
351 repo.dirstate.normalize(abstarget)):
352 if not rename:
352 if not rename:
353 ui.warn(_("%s: can't copy - same file\n") % reltarget)
353 ui.warn(_("%s: can't copy - same file\n") % reltarget)
354 return
354 return
355 exists = False
355 exists = False
356 samefile = True
356 samefile = True
357
357
358 if not after and exists or after and state in 'mn':
358 if not after and exists or after and state in 'mn':
359 if not opts['force']:
359 if not opts['force']:
360 ui.warn(_('%s: not overwriting - file exists\n') %
360 ui.warn(_('%s: not overwriting - file exists\n') %
361 reltarget)
361 reltarget)
362 return
362 return
363
363
364 if after:
364 if after:
365 if not exists:
365 if not exists:
366 if rename:
366 if rename:
367 ui.warn(_('%s: not recording move - %s does not exist\n') %
367 ui.warn(_('%s: not recording move - %s does not exist\n') %
368 (relsrc, reltarget))
368 (relsrc, reltarget))
369 else:
369 else:
370 ui.warn(_('%s: not recording copy - %s does not exist\n') %
370 ui.warn(_('%s: not recording copy - %s does not exist\n') %
371 (relsrc, reltarget))
371 (relsrc, reltarget))
372 return
372 return
373 elif not dryrun:
373 elif not dryrun:
374 try:
374 try:
375 if exists:
375 if exists:
376 os.unlink(target)
376 os.unlink(target)
377 targetdir = os.path.dirname(target) or '.'
377 targetdir = os.path.dirname(target) or '.'
378 if not os.path.isdir(targetdir):
378 if not os.path.isdir(targetdir):
379 os.makedirs(targetdir)
379 os.makedirs(targetdir)
380 if samefile:
380 if samefile:
381 tmp = target + "~hgrename"
381 tmp = target + "~hgrename"
382 os.rename(src, tmp)
382 os.rename(src, tmp)
383 os.rename(tmp, target)
383 os.rename(tmp, target)
384 else:
384 else:
385 util.copyfile(src, target)
385 util.copyfile(src, target)
386 srcexists = True
386 srcexists = True
387 except IOError, inst:
387 except IOError, inst:
388 if inst.errno == errno.ENOENT:
388 if inst.errno == errno.ENOENT:
389 ui.warn(_('%s: deleted in working copy\n') % relsrc)
389 ui.warn(_('%s: deleted in working copy\n') % relsrc)
390 srcexists = False
390 srcexists = False
391 else:
391 else:
392 ui.warn(_('%s: cannot copy - %s\n') %
392 ui.warn(_('%s: cannot copy - %s\n') %
393 (relsrc, inst.strerror))
393 (relsrc, inst.strerror))
394 return True # report a failure
394 return True # report a failure
395
395
396 if ui.verbose or not exact:
396 if ui.verbose or not exact:
397 if rename:
397 if rename:
398 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
398 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
399 else:
399 else:
400 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
400 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
401
401
402 targets[abstarget] = abssrc
402 targets[abstarget] = abssrc
403
403
404 # fix up dirstate
404 # fix up dirstate
405 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
405 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
406 dryrun=dryrun, cwd=cwd)
406 dryrun=dryrun, cwd=cwd)
407 if rename and not dryrun:
407 if rename and not dryrun:
408 if not after and srcexists and not samefile:
408 if not after and srcexists and not samefile:
409 util.unlinkpath(repo.wjoin(abssrc))
409 util.unlinkpath(repo.wjoin(abssrc))
410 wctx.forget([abssrc])
410 wctx.forget([abssrc])
411
411
412 # pat: ossep
412 # pat: ossep
413 # dest ossep
413 # dest ossep
414 # srcs: list of (hgsep, hgsep, ossep, bool)
414 # srcs: list of (hgsep, hgsep, ossep, bool)
415 # return: function that takes hgsep and returns ossep
415 # return: function that takes hgsep and returns ossep
416 def targetpathfn(pat, dest, srcs):
416 def targetpathfn(pat, dest, srcs):
417 if os.path.isdir(pat):
417 if os.path.isdir(pat):
418 abspfx = pathutil.canonpath(repo.root, cwd, pat)
418 abspfx = pathutil.canonpath(repo.root, cwd, pat)
419 abspfx = util.localpath(abspfx)
419 abspfx = util.localpath(abspfx)
420 if destdirexists:
420 if destdirexists:
421 striplen = len(os.path.split(abspfx)[0])
421 striplen = len(os.path.split(abspfx)[0])
422 else:
422 else:
423 striplen = len(abspfx)
423 striplen = len(abspfx)
424 if striplen:
424 if striplen:
425 striplen += len(os.sep)
425 striplen += len(os.sep)
426 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
426 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
427 elif destdirexists:
427 elif destdirexists:
428 res = lambda p: os.path.join(dest,
428 res = lambda p: os.path.join(dest,
429 os.path.basename(util.localpath(p)))
429 os.path.basename(util.localpath(p)))
430 else:
430 else:
431 res = lambda p: dest
431 res = lambda p: dest
432 return res
432 return res
433
433
434 # pat: ossep
434 # pat: ossep
435 # dest ossep
435 # dest ossep
436 # srcs: list of (hgsep, hgsep, ossep, bool)
436 # srcs: list of (hgsep, hgsep, ossep, bool)
437 # return: function that takes hgsep and returns ossep
437 # return: function that takes hgsep and returns ossep
438 def targetpathafterfn(pat, dest, srcs):
438 def targetpathafterfn(pat, dest, srcs):
439 if matchmod.patkind(pat):
439 if matchmod.patkind(pat):
440 # a mercurial pattern
440 # a mercurial pattern
441 res = lambda p: os.path.join(dest,
441 res = lambda p: os.path.join(dest,
442 os.path.basename(util.localpath(p)))
442 os.path.basename(util.localpath(p)))
443 else:
443 else:
444 abspfx = pathutil.canonpath(repo.root, cwd, pat)
444 abspfx = pathutil.canonpath(repo.root, cwd, pat)
445 if len(abspfx) < len(srcs[0][0]):
445 if len(abspfx) < len(srcs[0][0]):
446 # A directory. Either the target path contains the last
446 # A directory. Either the target path contains the last
447 # component of the source path or it does not.
447 # component of the source path or it does not.
448 def evalpath(striplen):
448 def evalpath(striplen):
449 score = 0
449 score = 0
450 for s in srcs:
450 for s in srcs:
451 t = os.path.join(dest, util.localpath(s[0])[striplen:])
451 t = os.path.join(dest, util.localpath(s[0])[striplen:])
452 if os.path.lexists(t):
452 if os.path.lexists(t):
453 score += 1
453 score += 1
454 return score
454 return score
455
455
456 abspfx = util.localpath(abspfx)
456 abspfx = util.localpath(abspfx)
457 striplen = len(abspfx)
457 striplen = len(abspfx)
458 if striplen:
458 if striplen:
459 striplen += len(os.sep)
459 striplen += len(os.sep)
460 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
460 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
461 score = evalpath(striplen)
461 score = evalpath(striplen)
462 striplen1 = len(os.path.split(abspfx)[0])
462 striplen1 = len(os.path.split(abspfx)[0])
463 if striplen1:
463 if striplen1:
464 striplen1 += len(os.sep)
464 striplen1 += len(os.sep)
465 if evalpath(striplen1) > score:
465 if evalpath(striplen1) > score:
466 striplen = striplen1
466 striplen = striplen1
467 res = lambda p: os.path.join(dest,
467 res = lambda p: os.path.join(dest,
468 util.localpath(p)[striplen:])
468 util.localpath(p)[striplen:])
469 else:
469 else:
470 # a file
470 # a file
471 if destdirexists:
471 if destdirexists:
472 res = lambda p: os.path.join(dest,
472 res = lambda p: os.path.join(dest,
473 os.path.basename(util.localpath(p)))
473 os.path.basename(util.localpath(p)))
474 else:
474 else:
475 res = lambda p: dest
475 res = lambda p: dest
476 return res
476 return res
477
477
478
478
479 pats = scmutil.expandpats(pats)
479 pats = scmutil.expandpats(pats)
480 if not pats:
480 if not pats:
481 raise util.Abort(_('no source or destination specified'))
481 raise util.Abort(_('no source or destination specified'))
482 if len(pats) == 1:
482 if len(pats) == 1:
483 raise util.Abort(_('no destination specified'))
483 raise util.Abort(_('no destination specified'))
484 dest = pats.pop()
484 dest = pats.pop()
485 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
485 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
486 if not destdirexists:
486 if not destdirexists:
487 if len(pats) > 1 or matchmod.patkind(pats[0]):
487 if len(pats) > 1 or matchmod.patkind(pats[0]):
488 raise util.Abort(_('with multiple sources, destination must be an '
488 raise util.Abort(_('with multiple sources, destination must be an '
489 'existing directory'))
489 'existing directory'))
490 if util.endswithsep(dest):
490 if util.endswithsep(dest):
491 raise util.Abort(_('destination %s is not a directory') % dest)
491 raise util.Abort(_('destination %s is not a directory') % dest)
492
492
493 tfn = targetpathfn
493 tfn = targetpathfn
494 if after:
494 if after:
495 tfn = targetpathafterfn
495 tfn = targetpathafterfn
496 copylist = []
496 copylist = []
497 for pat in pats:
497 for pat in pats:
498 srcs = walkpat(pat)
498 srcs = walkpat(pat)
499 if not srcs:
499 if not srcs:
500 continue
500 continue
501 copylist.append((tfn(pat, dest, srcs), srcs))
501 copylist.append((tfn(pat, dest, srcs), srcs))
502 if not copylist:
502 if not copylist:
503 raise util.Abort(_('no files to copy'))
503 raise util.Abort(_('no files to copy'))
504
504
505 errors = 0
505 errors = 0
506 for targetpath, srcs in copylist:
506 for targetpath, srcs in copylist:
507 for abssrc, relsrc, exact in srcs:
507 for abssrc, relsrc, exact in srcs:
508 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
508 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
509 errors += 1
509 errors += 1
510
510
511 if errors:
511 if errors:
512 ui.warn(_('(consider using --after)\n'))
512 ui.warn(_('(consider using --after)\n'))
513
513
514 return errors != 0
514 return errors != 0
515
515
516 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
516 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
517 runargs=None, appendpid=False):
517 runargs=None, appendpid=False):
518 '''Run a command as a service.'''
518 '''Run a command as a service.'''
519
519
520 def writepid(pid):
520 def writepid(pid):
521 if opts['pid_file']:
521 if opts['pid_file']:
522 mode = appendpid and 'a' or 'w'
522 mode = appendpid and 'a' or 'w'
523 fp = open(opts['pid_file'], mode)
523 fp = open(opts['pid_file'], mode)
524 fp.write(str(pid) + '\n')
524 fp.write(str(pid) + '\n')
525 fp.close()
525 fp.close()
526
526
527 if opts['daemon'] and not opts['daemon_pipefds']:
527 if opts['daemon'] and not opts['daemon_pipefds']:
528 # Signal child process startup with file removal
528 # Signal child process startup with file removal
529 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
529 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
530 os.close(lockfd)
530 os.close(lockfd)
531 try:
531 try:
532 if not runargs:
532 if not runargs:
533 runargs = util.hgcmd() + sys.argv[1:]
533 runargs = util.hgcmd() + sys.argv[1:]
534 runargs.append('--daemon-pipefds=%s' % lockpath)
534 runargs.append('--daemon-pipefds=%s' % lockpath)
535 # Don't pass --cwd to the child process, because we've already
535 # Don't pass --cwd to the child process, because we've already
536 # changed directory.
536 # changed directory.
537 for i in xrange(1, len(runargs)):
537 for i in xrange(1, len(runargs)):
538 if runargs[i].startswith('--cwd='):
538 if runargs[i].startswith('--cwd='):
539 del runargs[i]
539 del runargs[i]
540 break
540 break
541 elif runargs[i].startswith('--cwd'):
541 elif runargs[i].startswith('--cwd'):
542 del runargs[i:i + 2]
542 del runargs[i:i + 2]
543 break
543 break
544 def condfn():
544 def condfn():
545 return not os.path.exists(lockpath)
545 return not os.path.exists(lockpath)
546 pid = util.rundetached(runargs, condfn)
546 pid = util.rundetached(runargs, condfn)
547 if pid < 0:
547 if pid < 0:
548 raise util.Abort(_('child process failed to start'))
548 raise util.Abort(_('child process failed to start'))
549 writepid(pid)
549 writepid(pid)
550 finally:
550 finally:
551 try:
551 try:
552 os.unlink(lockpath)
552 os.unlink(lockpath)
553 except OSError, e:
553 except OSError, e:
554 if e.errno != errno.ENOENT:
554 if e.errno != errno.ENOENT:
555 raise
555 raise
556 if parentfn:
556 if parentfn:
557 return parentfn(pid)
557 return parentfn(pid)
558 else:
558 else:
559 return
559 return
560
560
561 if initfn:
561 if initfn:
562 initfn()
562 initfn()
563
563
564 if not opts['daemon']:
564 if not opts['daemon']:
565 writepid(os.getpid())
565 writepid(os.getpid())
566
566
567 if opts['daemon_pipefds']:
567 if opts['daemon_pipefds']:
568 lockpath = opts['daemon_pipefds']
568 lockpath = opts['daemon_pipefds']
569 try:
569 try:
570 os.setsid()
570 os.setsid()
571 except AttributeError:
571 except AttributeError:
572 pass
572 pass
573 os.unlink(lockpath)
573 os.unlink(lockpath)
574 util.hidewindow()
574 util.hidewindow()
575 sys.stdout.flush()
575 sys.stdout.flush()
576 sys.stderr.flush()
576 sys.stderr.flush()
577
577
578 nullfd = os.open(os.devnull, os.O_RDWR)
578 nullfd = os.open(os.devnull, os.O_RDWR)
579 logfilefd = nullfd
579 logfilefd = nullfd
580 if logfile:
580 if logfile:
581 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
581 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
582 os.dup2(nullfd, 0)
582 os.dup2(nullfd, 0)
583 os.dup2(logfilefd, 1)
583 os.dup2(logfilefd, 1)
584 os.dup2(logfilefd, 2)
584 os.dup2(logfilefd, 2)
585 if nullfd not in (0, 1, 2):
585 if nullfd not in (0, 1, 2):
586 os.close(nullfd)
586 os.close(nullfd)
587 if logfile and logfilefd not in (0, 1, 2):
587 if logfile and logfilefd not in (0, 1, 2):
588 os.close(logfilefd)
588 os.close(logfilefd)
589
589
590 if runfn:
590 if runfn:
591 return runfn()
591 return runfn()
592
592
593 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
593 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
594 """Utility function used by commands.import to import a single patch
594 """Utility function used by commands.import to import a single patch
595
595
596 This function is explicitly defined here to help the evolve extension to
596 This function is explicitly defined here to help the evolve extension to
597 wrap this part of the import logic.
597 wrap this part of the import logic.
598
598
599 The API is currently a bit ugly because it a simple code translation from
599 The API is currently a bit ugly because it a simple code translation from
600 the import command. Feel free to make it better.
600 the import command. Feel free to make it better.
601
601
602 :hunk: a patch (as a binary string)
602 :hunk: a patch (as a binary string)
603 :parents: nodes that will be parent of the created commit
603 :parents: nodes that will be parent of the created commit
604 :opts: the full dict of option passed to the import command
604 :opts: the full dict of option passed to the import command
605 :msgs: list to save commit message to.
605 :msgs: list to save commit message to.
606 (used in case we need to save it when failing)
606 (used in case we need to save it when failing)
607 :updatefunc: a function that update a repo to a given node
607 :updatefunc: a function that update a repo to a given node
608 updatefunc(<repo>, <node>)
608 updatefunc(<repo>, <node>)
609 """
609 """
610 tmpname, message, user, date, branch, nodeid, p1, p2 = \
610 tmpname, message, user, date, branch, nodeid, p1, p2 = \
611 patch.extract(ui, hunk)
611 patch.extract(ui, hunk)
612
612
613 update = not opts.get('bypass')
613 update = not opts.get('bypass')
614 strip = opts["strip"]
614 strip = opts["strip"]
615 sim = float(opts.get('similarity') or 0)
615 sim = float(opts.get('similarity') or 0)
616 if not tmpname:
616 if not tmpname:
617 return (None, None, False)
617 return (None, None, False)
618 msg = _('applied to working directory')
618 msg = _('applied to working directory')
619
619
620 rejects = False
620 rejects = False
621
621
622 try:
622 try:
623 cmdline_message = logmessage(ui, opts)
623 cmdline_message = logmessage(ui, opts)
624 if cmdline_message:
624 if cmdline_message:
625 # pickup the cmdline msg
625 # pickup the cmdline msg
626 message = cmdline_message
626 message = cmdline_message
627 elif message:
627 elif message:
628 # pickup the patch msg
628 # pickup the patch msg
629 message = message.strip()
629 message = message.strip()
630 else:
630 else:
631 # launch the editor
631 # launch the editor
632 message = None
632 message = None
633 ui.debug('message:\n%s\n' % message)
633 ui.debug('message:\n%s\n' % message)
634
634
635 if len(parents) == 1:
635 if len(parents) == 1:
636 parents.append(repo[nullid])
636 parents.append(repo[nullid])
637 if opts.get('exact'):
637 if opts.get('exact'):
638 if not nodeid or not p1:
638 if not nodeid or not p1:
639 raise util.Abort(_('not a Mercurial patch'))
639 raise util.Abort(_('not a Mercurial patch'))
640 p1 = repo[p1]
640 p1 = repo[p1]
641 p2 = repo[p2 or nullid]
641 p2 = repo[p2 or nullid]
642 elif p2:
642 elif p2:
643 try:
643 try:
644 p1 = repo[p1]
644 p1 = repo[p1]
645 p2 = repo[p2]
645 p2 = repo[p2]
646 # Without any options, consider p2 only if the
646 # Without any options, consider p2 only if the
647 # patch is being applied on top of the recorded
647 # patch is being applied on top of the recorded
648 # first parent.
648 # first parent.
649 if p1 != parents[0]:
649 if p1 != parents[0]:
650 p1 = parents[0]
650 p1 = parents[0]
651 p2 = repo[nullid]
651 p2 = repo[nullid]
652 except error.RepoError:
652 except error.RepoError:
653 p1, p2 = parents
653 p1, p2 = parents
654 else:
654 else:
655 p1, p2 = parents
655 p1, p2 = parents
656
656
657 n = None
657 n = None
658 if update:
658 if update:
659 if p1 != parents[0]:
659 if p1 != parents[0]:
660 updatefunc(repo, p1.node())
660 updatefunc(repo, p1.node())
661 if p2 != parents[1]:
661 if p2 != parents[1]:
662 repo.setparents(p1.node(), p2.node())
662 repo.setparents(p1.node(), p2.node())
663
663
664 if opts.get('exact') or opts.get('import_branch'):
664 if opts.get('exact') or opts.get('import_branch'):
665 repo.dirstate.setbranch(branch or 'default')
665 repo.dirstate.setbranch(branch or 'default')
666
666
667 partial = opts.get('partial', False)
667 partial = opts.get('partial', False)
668 files = set()
668 files = set()
669 try:
669 try:
670 patch.patch(ui, repo, tmpname, strip=strip, files=files,
670 patch.patch(ui, repo, tmpname, strip=strip, files=files,
671 eolmode=None, similarity=sim / 100.0)
671 eolmode=None, similarity=sim / 100.0)
672 except patch.PatchError, e:
672 except patch.PatchError, e:
673 if not partial:
673 if not partial:
674 raise util.Abort(str(e))
674 raise util.Abort(str(e))
675 if partial:
675 if partial:
676 rejects = True
676 rejects = True
677
677
678 files = list(files)
678 files = list(files)
679 if opts.get('no_commit'):
679 if opts.get('no_commit'):
680 if message:
680 if message:
681 msgs.append(message)
681 msgs.append(message)
682 else:
682 else:
683 if opts.get('exact') or p2:
683 if opts.get('exact') or p2:
684 # If you got here, you either use --force and know what
684 # If you got here, you either use --force and know what
685 # you are doing or used --exact or a merge patch while
685 # you are doing or used --exact or a merge patch while
686 # being updated to its first parent.
686 # being updated to its first parent.
687 m = None
687 m = None
688 else:
688 else:
689 m = scmutil.matchfiles(repo, files or [])
689 m = scmutil.matchfiles(repo, files or [])
690 editform = mergeeditform(repo[None], 'import.normal')
690 editform = mergeeditform(repo[None], 'import.normal')
691 editor = getcommiteditor(editform=editform, **opts)
691 editor = getcommiteditor(editform=editform, **opts)
692 n = repo.commit(message, opts.get('user') or user,
692 n = repo.commit(message, opts.get('user') or user,
693 opts.get('date') or date, match=m,
693 opts.get('date') or date, match=m,
694 editor=editor, force=partial)
694 editor=editor, force=partial)
695 else:
695 else:
696 if opts.get('exact') or opts.get('import_branch'):
696 if opts.get('exact') or opts.get('import_branch'):
697 branch = branch or 'default'
697 branch = branch or 'default'
698 else:
698 else:
699 branch = p1.branch()
699 branch = p1.branch()
700 store = patch.filestore()
700 store = patch.filestore()
701 try:
701 try:
702 files = set()
702 files = set()
703 try:
703 try:
704 patch.patchrepo(ui, repo, p1, store, tmpname, strip,
704 patch.patchrepo(ui, repo, p1, store, tmpname, strip,
705 files, eolmode=None)
705 files, eolmode=None)
706 except patch.PatchError, e:
706 except patch.PatchError, e:
707 raise util.Abort(str(e))
707 raise util.Abort(str(e))
708 editor = getcommiteditor(editform='import.bypass')
708 editor = getcommiteditor(editform='import.bypass')
709 memctx = context.makememctx(repo, (p1.node(), p2.node()),
709 memctx = context.makememctx(repo, (p1.node(), p2.node()),
710 message,
710 message,
711 opts.get('user') or user,
711 opts.get('user') or user,
712 opts.get('date') or date,
712 opts.get('date') or date,
713 branch, files, store,
713 branch, files, store,
714 editor=editor)
714 editor=editor)
715 n = memctx.commit()
715 n = memctx.commit()
716 finally:
716 finally:
717 store.close()
717 store.close()
718 if opts.get('exact') and hex(n) != nodeid:
718 if opts.get('exact') and hex(n) != nodeid:
719 raise util.Abort(_('patch is damaged or loses information'))
719 raise util.Abort(_('patch is damaged or loses information'))
720 if n:
720 if n:
721 # i18n: refers to a short changeset id
721 # i18n: refers to a short changeset id
722 msg = _('created %s') % short(n)
722 msg = _('created %s') % short(n)
723 return (msg, n, rejects)
723 return (msg, n, rejects)
724 finally:
724 finally:
725 os.unlink(tmpname)
725 os.unlink(tmpname)
726
726
727 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
727 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
728 opts=None):
728 opts=None):
729 '''export changesets as hg patches.'''
729 '''export changesets as hg patches.'''
730
730
731 total = len(revs)
731 total = len(revs)
732 revwidth = max([len(str(rev)) for rev in revs])
732 revwidth = max([len(str(rev)) for rev in revs])
733 filemode = {}
733 filemode = {}
734
734
735 def single(rev, seqno, fp):
735 def single(rev, seqno, fp):
736 ctx = repo[rev]
736 ctx = repo[rev]
737 node = ctx.node()
737 node = ctx.node()
738 parents = [p.node() for p in ctx.parents() if p]
738 parents = [p.node() for p in ctx.parents() if p]
739 branch = ctx.branch()
739 branch = ctx.branch()
740 if switch_parent:
740 if switch_parent:
741 parents.reverse()
741 parents.reverse()
742 prev = (parents and parents[0]) or nullid
742 prev = (parents and parents[0]) or nullid
743
743
744 shouldclose = False
744 shouldclose = False
745 if not fp and len(template) > 0:
745 if not fp and len(template) > 0:
746 desc_lines = ctx.description().rstrip().split('\n')
746 desc_lines = ctx.description().rstrip().split('\n')
747 desc = desc_lines[0] #Commit always has a first line.
747 desc = desc_lines[0] #Commit always has a first line.
748 fp = makefileobj(repo, template, node, desc=desc, total=total,
748 fp = makefileobj(repo, template, node, desc=desc, total=total,
749 seqno=seqno, revwidth=revwidth, mode='wb',
749 seqno=seqno, revwidth=revwidth, mode='wb',
750 modemap=filemode)
750 modemap=filemode)
751 if fp != template:
751 if fp != template:
752 shouldclose = True
752 shouldclose = True
753 if fp and fp != sys.stdout and util.safehasattr(fp, 'name'):
753 if fp and fp != sys.stdout and util.safehasattr(fp, 'name'):
754 repo.ui.note("%s\n" % fp.name)
754 repo.ui.note("%s\n" % fp.name)
755
755
756 if not fp:
756 if not fp:
757 write = repo.ui.write
757 write = repo.ui.write
758 else:
758 else:
759 def write(s, **kw):
759 def write(s, **kw):
760 fp.write(s)
760 fp.write(s)
761
761
762
762
763 write("# HG changeset patch\n")
763 write("# HG changeset patch\n")
764 write("# User %s\n" % ctx.user())
764 write("# User %s\n" % ctx.user())
765 write("# Date %d %d\n" % ctx.date())
765 write("# Date %d %d\n" % ctx.date())
766 write("# %s\n" % util.datestr(ctx.date()))
766 write("# %s\n" % util.datestr(ctx.date()))
767 if branch and branch != 'default':
767 if branch and branch != 'default':
768 write("# Branch %s\n" % branch)
768 write("# Branch %s\n" % branch)
769 write("# Node ID %s\n" % hex(node))
769 write("# Node ID %s\n" % hex(node))
770 write("# Parent %s\n" % hex(prev))
770 write("# Parent %s\n" % hex(prev))
771 if len(parents) > 1:
771 if len(parents) > 1:
772 write("# Parent %s\n" % hex(parents[1]))
772 write("# Parent %s\n" % hex(parents[1]))
773 write(ctx.description().rstrip())
773 write(ctx.description().rstrip())
774 write("\n\n")
774 write("\n\n")
775
775
776 for chunk, label in patch.diffui(repo, prev, node, opts=opts):
776 for chunk, label in patch.diffui(repo, prev, node, opts=opts):
777 write(chunk, label=label)
777 write(chunk, label=label)
778
778
779 if shouldclose:
779 if shouldclose:
780 fp.close()
780 fp.close()
781
781
782 for seqno, rev in enumerate(revs):
782 for seqno, rev in enumerate(revs):
783 single(rev, seqno + 1, fp)
783 single(rev, seqno + 1, fp)
784
784
785 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
785 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
786 changes=None, stat=False, fp=None, prefix='',
786 changes=None, stat=False, fp=None, prefix='',
787 listsubrepos=False):
787 listsubrepos=False):
788 '''show diff or diffstat.'''
788 '''show diff or diffstat.'''
789 if fp is None:
789 if fp is None:
790 write = ui.write
790 write = ui.write
791 else:
791 else:
792 def write(s, **kw):
792 def write(s, **kw):
793 fp.write(s)
793 fp.write(s)
794
794
795 if stat:
795 if stat:
796 diffopts = diffopts.copy(context=0)
796 diffopts = diffopts.copy(context=0)
797 width = 80
797 width = 80
798 if not ui.plain():
798 if not ui.plain():
799 width = ui.termwidth()
799 width = ui.termwidth()
800 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
800 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
801 prefix=prefix)
801 prefix=prefix)
802 for chunk, label in patch.diffstatui(util.iterlines(chunks),
802 for chunk, label in patch.diffstatui(util.iterlines(chunks),
803 width=width,
803 width=width,
804 git=diffopts.git):
804 git=diffopts.git):
805 write(chunk, label=label)
805 write(chunk, label=label)
806 else:
806 else:
807 for chunk, label in patch.diffui(repo, node1, node2, match,
807 for chunk, label in patch.diffui(repo, node1, node2, match,
808 changes, diffopts, prefix=prefix):
808 changes, diffopts, prefix=prefix):
809 write(chunk, label=label)
809 write(chunk, label=label)
810
810
811 if listsubrepos:
811 if listsubrepos:
812 ctx1 = repo[node1]
812 ctx1 = repo[node1]
813 ctx2 = repo[node2]
813 ctx2 = repo[node2]
814 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
814 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
815 tempnode2 = node2
815 tempnode2 = node2
816 try:
816 try:
817 if node2 is not None:
817 if node2 is not None:
818 tempnode2 = ctx2.substate[subpath][1]
818 tempnode2 = ctx2.substate[subpath][1]
819 except KeyError:
819 except KeyError:
820 # A subrepo that existed in node1 was deleted between node1 and
820 # A subrepo that existed in node1 was deleted between node1 and
821 # node2 (inclusive). Thus, ctx2's substate won't contain that
821 # node2 (inclusive). Thus, ctx2's substate won't contain that
822 # subpath. The best we can do is to ignore it.
822 # subpath. The best we can do is to ignore it.
823 tempnode2 = None
823 tempnode2 = None
824 submatch = matchmod.narrowmatcher(subpath, match)
824 submatch = matchmod.narrowmatcher(subpath, match)
825 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
825 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
826 stat=stat, fp=fp, prefix=prefix)
826 stat=stat, fp=fp, prefix=prefix)
827
827
828 class changeset_printer(object):
828 class changeset_printer(object):
829 '''show changeset information when templating not requested.'''
829 '''show changeset information when templating not requested.'''
830
830
831 def __init__(self, ui, repo, patch, diffopts, buffered):
831 def __init__(self, ui, repo, patch, diffopts, buffered):
832 self.ui = ui
832 self.ui = ui
833 self.repo = repo
833 self.repo = repo
834 self.buffered = buffered
834 self.buffered = buffered
835 self.patch = patch
835 self.patch = patch
836 self.diffopts = diffopts
836 self.diffopts = diffopts
837 self.header = {}
837 self.header = {}
838 self.hunk = {}
838 self.hunk = {}
839 self.lastheader = None
839 self.lastheader = None
840 self.footer = None
840 self.footer = None
841
841
842 def flush(self, rev):
842 def flush(self, rev):
843 if rev in self.header:
843 if rev in self.header:
844 h = self.header[rev]
844 h = self.header[rev]
845 if h != self.lastheader:
845 if h != self.lastheader:
846 self.lastheader = h
846 self.lastheader = h
847 self.ui.write(h)
847 self.ui.write(h)
848 del self.header[rev]
848 del self.header[rev]
849 if rev in self.hunk:
849 if rev in self.hunk:
850 self.ui.write(self.hunk[rev])
850 self.ui.write(self.hunk[rev])
851 del self.hunk[rev]
851 del self.hunk[rev]
852 return 1
852 return 1
853 return 0
853 return 0
854
854
855 def close(self):
855 def close(self):
856 if self.footer:
856 if self.footer:
857 self.ui.write(self.footer)
857 self.ui.write(self.footer)
858
858
859 def show(self, ctx, copies=None, matchfn=None, **props):
859 def show(self, ctx, copies=None, matchfn=None, **props):
860 if self.buffered:
860 if self.buffered:
861 self.ui.pushbuffer()
861 self.ui.pushbuffer()
862 self._show(ctx, copies, matchfn, props)
862 self._show(ctx, copies, matchfn, props)
863 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
863 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
864 else:
864 else:
865 self._show(ctx, copies, matchfn, props)
865 self._show(ctx, copies, matchfn, props)
866
866
867 def _show(self, ctx, copies, matchfn, props):
867 def _show(self, ctx, copies, matchfn, props):
868 '''show a single changeset or file revision'''
868 '''show a single changeset or file revision'''
869 changenode = ctx.node()
869 changenode = ctx.node()
870 rev = ctx.rev()
870 rev = ctx.rev()
871
871
872 if self.ui.quiet:
872 if self.ui.quiet:
873 self.ui.write("%d:%s\n" % (rev, short(changenode)),
873 self.ui.write("%d:%s\n" % (rev, short(changenode)),
874 label='log.node')
874 label='log.node')
875 return
875 return
876
876
877 log = self.repo.changelog
877 log = self.repo.changelog
878 date = util.datestr(ctx.date())
878 date = util.datestr(ctx.date())
879
879
880 hexfunc = self.ui.debugflag and hex or short
880 hexfunc = self.ui.debugflag and hex or short
881
881
882 parents = [(p, hexfunc(log.node(p)))
882 parents = [(p, hexfunc(log.node(p)))
883 for p in self._meaningful_parentrevs(log, rev)]
883 for p in self._meaningful_parentrevs(log, rev)]
884
884
885 # i18n: column positioning for "hg log"
885 # i18n: column positioning for "hg log"
886 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
886 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
887 label='log.changeset changeset.%s' % ctx.phasestr())
887 label='log.changeset changeset.%s' % ctx.phasestr())
888
888
889 branch = ctx.branch()
889 branch = ctx.branch()
890 # don't show the default branch name
890 # don't show the default branch name
891 if branch != 'default':
891 if branch != 'default':
892 # i18n: column positioning for "hg log"
892 # i18n: column positioning for "hg log"
893 self.ui.write(_("branch: %s\n") % branch,
893 self.ui.write(_("branch: %s\n") % branch,
894 label='log.branch')
894 label='log.branch')
895 for bookmark in self.repo.nodebookmarks(changenode):
895 for bookmark in self.repo.nodebookmarks(changenode):
896 # i18n: column positioning for "hg log"
896 # i18n: column positioning for "hg log"
897 self.ui.write(_("bookmark: %s\n") % bookmark,
897 self.ui.write(_("bookmark: %s\n") % bookmark,
898 label='log.bookmark')
898 label='log.bookmark')
899 for tag in self.repo.nodetags(changenode):
899 for tag in self.repo.nodetags(changenode):
900 # i18n: column positioning for "hg log"
900 # i18n: column positioning for "hg log"
901 self.ui.write(_("tag: %s\n") % tag,
901 self.ui.write(_("tag: %s\n") % tag,
902 label='log.tag')
902 label='log.tag')
903 if self.ui.debugflag and ctx.phase():
903 if self.ui.debugflag and ctx.phase():
904 # i18n: column positioning for "hg log"
904 # i18n: column positioning for "hg log"
905 self.ui.write(_("phase: %s\n") % _(ctx.phasestr()),
905 self.ui.write(_("phase: %s\n") % _(ctx.phasestr()),
906 label='log.phase')
906 label='log.phase')
907 for parent in parents:
907 for parent in parents:
908 # i18n: column positioning for "hg log"
908 # i18n: column positioning for "hg log"
909 self.ui.write(_("parent: %d:%s\n") % parent,
909 self.ui.write(_("parent: %d:%s\n") % parent,
910 label='log.parent changeset.%s' % ctx.phasestr())
910 label='log.parent changeset.%s' % ctx.phasestr())
911
911
912 if self.ui.debugflag:
912 if self.ui.debugflag:
913 mnode = ctx.manifestnode()
913 mnode = ctx.manifestnode()
914 # i18n: column positioning for "hg log"
914 # i18n: column positioning for "hg log"
915 self.ui.write(_("manifest: %d:%s\n") %
915 self.ui.write(_("manifest: %d:%s\n") %
916 (self.repo.manifest.rev(mnode), hex(mnode)),
916 (self.repo.manifest.rev(mnode), hex(mnode)),
917 label='ui.debug log.manifest')
917 label='ui.debug log.manifest')
918 # i18n: column positioning for "hg log"
918 # i18n: column positioning for "hg log"
919 self.ui.write(_("user: %s\n") % ctx.user(),
919 self.ui.write(_("user: %s\n") % ctx.user(),
920 label='log.user')
920 label='log.user')
921 # i18n: column positioning for "hg log"
921 # i18n: column positioning for "hg log"
922 self.ui.write(_("date: %s\n") % date,
922 self.ui.write(_("date: %s\n") % date,
923 label='log.date')
923 label='log.date')
924
924
925 if self.ui.debugflag:
925 if self.ui.debugflag:
926 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
926 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
927 for key, value in zip([# i18n: column positioning for "hg log"
927 for key, value in zip([# i18n: column positioning for "hg log"
928 _("files:"),
928 _("files:"),
929 # i18n: column positioning for "hg log"
929 # i18n: column positioning for "hg log"
930 _("files+:"),
930 _("files+:"),
931 # i18n: column positioning for "hg log"
931 # i18n: column positioning for "hg log"
932 _("files-:")], files):
932 _("files-:")], files):
933 if value:
933 if value:
934 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
934 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
935 label='ui.debug log.files')
935 label='ui.debug log.files')
936 elif ctx.files() and self.ui.verbose:
936 elif ctx.files() and self.ui.verbose:
937 # i18n: column positioning for "hg log"
937 # i18n: column positioning for "hg log"
938 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
938 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
939 label='ui.note log.files')
939 label='ui.note log.files')
940 if copies and self.ui.verbose:
940 if copies and self.ui.verbose:
941 copies = ['%s (%s)' % c for c in copies]
941 copies = ['%s (%s)' % c for c in copies]
942 # i18n: column positioning for "hg log"
942 # i18n: column positioning for "hg log"
943 self.ui.write(_("copies: %s\n") % ' '.join(copies),
943 self.ui.write(_("copies: %s\n") % ' '.join(copies),
944 label='ui.note log.copies')
944 label='ui.note log.copies')
945
945
946 extra = ctx.extra()
946 extra = ctx.extra()
947 if extra and self.ui.debugflag:
947 if extra and self.ui.debugflag:
948 for key, value in sorted(extra.items()):
948 for key, value in sorted(extra.items()):
949 # i18n: column positioning for "hg log"
949 # i18n: column positioning for "hg log"
950 self.ui.write(_("extra: %s=%s\n")
950 self.ui.write(_("extra: %s=%s\n")
951 % (key, value.encode('string_escape')),
951 % (key, value.encode('string_escape')),
952 label='ui.debug log.extra')
952 label='ui.debug log.extra')
953
953
954 description = ctx.description().strip()
954 description = ctx.description().strip()
955 if description:
955 if description:
956 if self.ui.verbose:
956 if self.ui.verbose:
957 self.ui.write(_("description:\n"),
957 self.ui.write(_("description:\n"),
958 label='ui.note log.description')
958 label='ui.note log.description')
959 self.ui.write(description,
959 self.ui.write(description,
960 label='ui.note log.description')
960 label='ui.note log.description')
961 self.ui.write("\n\n")
961 self.ui.write("\n\n")
962 else:
962 else:
963 # i18n: column positioning for "hg log"
963 # i18n: column positioning for "hg log"
964 self.ui.write(_("summary: %s\n") %
964 self.ui.write(_("summary: %s\n") %
965 description.splitlines()[0],
965 description.splitlines()[0],
966 label='log.summary')
966 label='log.summary')
967 self.ui.write("\n")
967 self.ui.write("\n")
968
968
969 self.showpatch(changenode, matchfn)
969 self.showpatch(changenode, matchfn)
970
970
971 def showpatch(self, node, matchfn):
971 def showpatch(self, node, matchfn):
972 if not matchfn:
972 if not matchfn:
973 matchfn = self.patch
973 matchfn = self.patch
974 if matchfn:
974 if matchfn:
975 stat = self.diffopts.get('stat')
975 stat = self.diffopts.get('stat')
976 diff = self.diffopts.get('patch')
976 diff = self.diffopts.get('patch')
977 diffopts = patch.diffopts(self.ui, self.diffopts)
977 diffopts = patch.diffopts(self.ui, self.diffopts)
978 prev = self.repo.changelog.parents(node)[0]
978 prev = self.repo.changelog.parents(node)[0]
979 if stat:
979 if stat:
980 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
980 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
981 match=matchfn, stat=True)
981 match=matchfn, stat=True)
982 if diff:
982 if diff:
983 if stat:
983 if stat:
984 self.ui.write("\n")
984 self.ui.write("\n")
985 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
985 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
986 match=matchfn, stat=False)
986 match=matchfn, stat=False)
987 self.ui.write("\n")
987 self.ui.write("\n")
988
988
989 def _meaningful_parentrevs(self, log, rev):
989 def _meaningful_parentrevs(self, log, rev):
990 """Return list of meaningful (or all if debug) parentrevs for rev.
990 """Return list of meaningful (or all if debug) parentrevs for rev.
991
991
992 For merges (two non-nullrev revisions) both parents are meaningful.
992 For merges (two non-nullrev revisions) both parents are meaningful.
993 Otherwise the first parent revision is considered meaningful if it
993 Otherwise the first parent revision is considered meaningful if it
994 is not the preceding revision.
994 is not the preceding revision.
995 """
995 """
996 parents = log.parentrevs(rev)
996 parents = log.parentrevs(rev)
997 if not self.ui.debugflag and parents[1] == nullrev:
997 if not self.ui.debugflag and parents[1] == nullrev:
998 if parents[0] >= rev - 1:
998 if parents[0] >= rev - 1:
999 parents = []
999 parents = []
1000 else:
1000 else:
1001 parents = [parents[0]]
1001 parents = [parents[0]]
1002 return parents
1002 return parents
1003
1003
1004
1004
1005 class changeset_templater(changeset_printer):
1005 class changeset_templater(changeset_printer):
1006 '''format changeset information.'''
1006 '''format changeset information.'''
1007
1007
1008 def __init__(self, ui, repo, patch, diffopts, tmpl, mapfile, buffered):
1008 def __init__(self, ui, repo, patch, diffopts, tmpl, mapfile, buffered):
1009 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
1009 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
1010 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
1010 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
1011 defaulttempl = {
1011 defaulttempl = {
1012 'parent': '{rev}:{node|formatnode} ',
1012 'parent': '{rev}:{node|formatnode} ',
1013 'manifest': '{rev}:{node|formatnode}',
1013 'manifest': '{rev}:{node|formatnode}',
1014 'file_copy': '{name} ({source})',
1014 'file_copy': '{name} ({source})',
1015 'extra': '{key}={value|stringescape}'
1015 'extra': '{key}={value|stringescape}'
1016 }
1016 }
1017 # filecopy is preserved for compatibility reasons
1017 # filecopy is preserved for compatibility reasons
1018 defaulttempl['filecopy'] = defaulttempl['file_copy']
1018 defaulttempl['filecopy'] = defaulttempl['file_copy']
1019 self.t = templater.templater(mapfile, {'formatnode': formatnode},
1019 self.t = templater.templater(mapfile, {'formatnode': formatnode},
1020 cache=defaulttempl)
1020 cache=defaulttempl)
1021 if tmpl:
1021 if tmpl:
1022 self.t.cache['changeset'] = tmpl
1022 self.t.cache['changeset'] = tmpl
1023
1023
1024 self.cache = {}
1024 self.cache = {}
1025
1025
1026 def _meaningful_parentrevs(self, ctx):
1026 def _meaningful_parentrevs(self, ctx):
1027 """Return list of meaningful (or all if debug) parentrevs for rev.
1027 """Return list of meaningful (or all if debug) parentrevs for rev.
1028 """
1028 """
1029 parents = ctx.parents()
1029 parents = ctx.parents()
1030 if len(parents) > 1:
1030 if len(parents) > 1:
1031 return parents
1031 return parents
1032 if self.ui.debugflag:
1032 if self.ui.debugflag:
1033 return [parents[0], self.repo['null']]
1033 return [parents[0], self.repo['null']]
1034 if parents[0].rev() >= ctx.rev() - 1:
1034 if parents[0].rev() >= ctx.rev() - 1:
1035 return []
1035 return []
1036 return parents
1036 return parents
1037
1037
1038 def _show(self, ctx, copies, matchfn, props):
1038 def _show(self, ctx, copies, matchfn, props):
1039 '''show a single changeset or file revision'''
1039 '''show a single changeset or file revision'''
1040
1040
1041 showlist = templatekw.showlist
1041 showlist = templatekw.showlist
1042
1042
1043 # showparents() behaviour depends on ui trace level which
1043 # showparents() behaviour depends on ui trace level which
1044 # causes unexpected behaviours at templating level and makes
1044 # causes unexpected behaviours at templating level and makes
1045 # it harder to extract it in a standalone function. Its
1045 # it harder to extract it in a standalone function. Its
1046 # behaviour cannot be changed so leave it here for now.
1046 # behaviour cannot be changed so leave it here for now.
1047 def showparents(**args):
1047 def showparents(**args):
1048 ctx = args['ctx']
1048 ctx = args['ctx']
1049 parents = [[('rev', p.rev()), ('node', p.hex())]
1049 parents = [[('rev', p.rev()), ('node', p.hex())]
1050 for p in self._meaningful_parentrevs(ctx)]
1050 for p in self._meaningful_parentrevs(ctx)]
1051 return showlist('parent', parents, **args)
1051 return showlist('parent', parents, **args)
1052
1052
1053 props = props.copy()
1053 props = props.copy()
1054 props.update(templatekw.keywords)
1054 props.update(templatekw.keywords)
1055 props['parents'] = showparents
1055 props['parents'] = showparents
1056 props['templ'] = self.t
1056 props['templ'] = self.t
1057 props['ctx'] = ctx
1057 props['ctx'] = ctx
1058 props['repo'] = self.repo
1058 props['repo'] = self.repo
1059 props['revcache'] = {'copies': copies}
1059 props['revcache'] = {'copies': copies}
1060 props['cache'] = self.cache
1060 props['cache'] = self.cache
1061
1061
1062 # find correct templates for current mode
1062 # find correct templates for current mode
1063
1063
1064 tmplmodes = [
1064 tmplmodes = [
1065 (True, None),
1065 (True, None),
1066 (self.ui.verbose, 'verbose'),
1066 (self.ui.verbose, 'verbose'),
1067 (self.ui.quiet, 'quiet'),
1067 (self.ui.quiet, 'quiet'),
1068 (self.ui.debugflag, 'debug'),
1068 (self.ui.debugflag, 'debug'),
1069 ]
1069 ]
1070
1070
1071 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
1071 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
1072 for mode, postfix in tmplmodes:
1072 for mode, postfix in tmplmodes:
1073 for type in types:
1073 for type in types:
1074 cur = postfix and ('%s_%s' % (type, postfix)) or type
1074 cur = postfix and ('%s_%s' % (type, postfix)) or type
1075 if mode and cur in self.t:
1075 if mode and cur in self.t:
1076 types[type] = cur
1076 types[type] = cur
1077
1077
1078 try:
1078 try:
1079
1079
1080 # write header
1080 # write header
1081 if types['header']:
1081 if types['header']:
1082 h = templater.stringify(self.t(types['header'], **props))
1082 h = templater.stringify(self.t(types['header'], **props))
1083 if self.buffered:
1083 if self.buffered:
1084 self.header[ctx.rev()] = h
1084 self.header[ctx.rev()] = h
1085 else:
1085 else:
1086 if self.lastheader != h:
1086 if self.lastheader != h:
1087 self.lastheader = h
1087 self.lastheader = h
1088 self.ui.write(h)
1088 self.ui.write(h)
1089
1089
1090 # write changeset metadata, then patch if requested
1090 # write changeset metadata, then patch if requested
1091 key = types['changeset']
1091 key = types['changeset']
1092 self.ui.write(templater.stringify(self.t(key, **props)))
1092 self.ui.write(templater.stringify(self.t(key, **props)))
1093 self.showpatch(ctx.node(), matchfn)
1093 self.showpatch(ctx.node(), matchfn)
1094
1094
1095 if types['footer']:
1095 if types['footer']:
1096 if not self.footer:
1096 if not self.footer:
1097 self.footer = templater.stringify(self.t(types['footer'],
1097 self.footer = templater.stringify(self.t(types['footer'],
1098 **props))
1098 **props))
1099
1099
1100 except KeyError, inst:
1100 except KeyError, inst:
1101 msg = _("%s: no key named '%s'")
1101 msg = _("%s: no key named '%s'")
1102 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1102 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1103 except SyntaxError, inst:
1103 except SyntaxError, inst:
1104 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1104 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1105
1105
1106 def gettemplate(ui, tmpl, style):
1106 def gettemplate(ui, tmpl, style):
1107 """
1107 """
1108 Find the template matching the given template spec or style.
1108 Find the template matching the given template spec or style.
1109 """
1109 """
1110
1110
1111 # ui settings
1111 # ui settings
1112 if not tmpl and not style:
1112 if not tmpl and not style:
1113 tmpl = ui.config('ui', 'logtemplate')
1113 tmpl = ui.config('ui', 'logtemplate')
1114 if tmpl:
1114 if tmpl:
1115 try:
1115 try:
1116 tmpl = templater.parsestring(tmpl)
1116 tmpl = templater.parsestring(tmpl)
1117 except SyntaxError:
1117 except SyntaxError:
1118 tmpl = templater.parsestring(tmpl, quoted=False)
1118 tmpl = templater.parsestring(tmpl, quoted=False)
1119 return tmpl, None
1119 return tmpl, None
1120 else:
1120 else:
1121 style = util.expandpath(ui.config('ui', 'style', ''))
1121 style = util.expandpath(ui.config('ui', 'style', ''))
1122
1122
1123 if style:
1123 if style:
1124 mapfile = style
1124 mapfile = style
1125 if not os.path.split(mapfile)[0]:
1125 if not os.path.split(mapfile)[0]:
1126 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1126 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1127 or templater.templatepath(mapfile))
1127 or templater.templatepath(mapfile))
1128 if mapname:
1128 if mapname:
1129 mapfile = mapname
1129 mapfile = mapname
1130 return None, mapfile
1130 return None, mapfile
1131
1131
1132 if not tmpl:
1132 if not tmpl:
1133 return None, None
1133 return None, None
1134
1134
1135 # looks like a literal template?
1135 # looks like a literal template?
1136 if '{' in tmpl:
1136 if '{' in tmpl:
1137 return tmpl, None
1137 return tmpl, None
1138
1138
1139 # perhaps a stock style?
1139 # perhaps a stock style?
1140 if not os.path.split(tmpl)[0]:
1140 if not os.path.split(tmpl)[0]:
1141 mapname = (templater.templatepath('map-cmdline.' + tmpl)
1141 mapname = (templater.templatepath('map-cmdline.' + tmpl)
1142 or templater.templatepath(tmpl))
1142 or templater.templatepath(tmpl))
1143 if mapname and os.path.isfile(mapname):
1143 if mapname and os.path.isfile(mapname):
1144 return None, mapname
1144 return None, mapname
1145
1145
1146 # perhaps it's a reference to [templates]
1146 # perhaps it's a reference to [templates]
1147 t = ui.config('templates', tmpl)
1147 t = ui.config('templates', tmpl)
1148 if t:
1148 if t:
1149 try:
1149 try:
1150 tmpl = templater.parsestring(t)
1150 tmpl = templater.parsestring(t)
1151 except SyntaxError:
1151 except SyntaxError:
1152 tmpl = templater.parsestring(t, quoted=False)
1152 tmpl = templater.parsestring(t, quoted=False)
1153 return tmpl, None
1153 return tmpl, None
1154
1154
1155 if tmpl == 'list':
1155 if tmpl == 'list':
1156 ui.write(_("available styles: %s\n") % templater.stylelist())
1156 ui.write(_("available styles: %s\n") % templater.stylelist())
1157 raise util.Abort(_("specify a template"))
1157 raise util.Abort(_("specify a template"))
1158
1158
1159 # perhaps it's a path to a map or a template
1159 # perhaps it's a path to a map or a template
1160 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
1160 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
1161 # is it a mapfile for a style?
1161 # is it a mapfile for a style?
1162 if os.path.basename(tmpl).startswith("map-"):
1162 if os.path.basename(tmpl).startswith("map-"):
1163 return None, os.path.realpath(tmpl)
1163 return None, os.path.realpath(tmpl)
1164 tmpl = open(tmpl).read()
1164 tmpl = open(tmpl).read()
1165 return tmpl, None
1165 return tmpl, None
1166
1166
1167 # constant string?
1167 # constant string?
1168 return tmpl, None
1168 return tmpl, None
1169
1169
1170 def show_changeset(ui, repo, opts, buffered=False):
1170 def show_changeset(ui, repo, opts, buffered=False):
1171 """show one changeset using template or regular display.
1171 """show one changeset using template or regular display.
1172
1172
1173 Display format will be the first non-empty hit of:
1173 Display format will be the first non-empty hit of:
1174 1. option 'template'
1174 1. option 'template'
1175 2. option 'style'
1175 2. option 'style'
1176 3. [ui] setting 'logtemplate'
1176 3. [ui] setting 'logtemplate'
1177 4. [ui] setting 'style'
1177 4. [ui] setting 'style'
1178 If all of these values are either the unset or the empty string,
1178 If all of these values are either the unset or the empty string,
1179 regular display via changeset_printer() is done.
1179 regular display via changeset_printer() is done.
1180 """
1180 """
1181 # options
1181 # options
1182 patch = None
1182 patch = None
1183 if opts.get('patch') or opts.get('stat'):
1183 if opts.get('patch') or opts.get('stat'):
1184 patch = scmutil.matchall(repo)
1184 patch = scmutil.matchall(repo)
1185
1185
1186 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1186 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1187
1187
1188 if not tmpl and not mapfile:
1188 if not tmpl and not mapfile:
1189 return changeset_printer(ui, repo, patch, opts, buffered)
1189 return changeset_printer(ui, repo, patch, opts, buffered)
1190
1190
1191 try:
1191 try:
1192 t = changeset_templater(ui, repo, patch, opts, tmpl, mapfile, buffered)
1192 t = changeset_templater(ui, repo, patch, opts, tmpl, mapfile, buffered)
1193 except SyntaxError, inst:
1193 except SyntaxError, inst:
1194 raise util.Abort(inst.args[0])
1194 raise util.Abort(inst.args[0])
1195 return t
1195 return t
1196
1196
1197 def showmarker(ui, marker):
1197 def showmarker(ui, marker):
1198 """utility function to display obsolescence marker in a readable way
1198 """utility function to display obsolescence marker in a readable way
1199
1199
1200 To be used by debug function."""
1200 To be used by debug function."""
1201 ui.write(hex(marker.precnode()))
1201 ui.write(hex(marker.precnode()))
1202 for repl in marker.succnodes():
1202 for repl in marker.succnodes():
1203 ui.write(' ')
1203 ui.write(' ')
1204 ui.write(hex(repl))
1204 ui.write(hex(repl))
1205 ui.write(' %X ' % marker.flags())
1205 ui.write(' %X ' % marker.flags())
1206 parents = marker.parentnodes()
1207 if parents is not None:
1208 ui.write('{%s} ' % ', '.join(hex(p) for p in parents))
1206 ui.write('(%s) ' % util.datestr(marker.date()))
1209 ui.write('(%s) ' % util.datestr(marker.date()))
1207 ui.write('{%s}' % (', '.join('%r: %r' % t for t in
1210 ui.write('{%s}' % (', '.join('%r: %r' % t for t in
1208 sorted(marker.metadata().items())
1211 sorted(marker.metadata().items())
1209 if t[0] != 'date')))
1212 if t[0] != 'date')))
1210 ui.write('\n')
1213 ui.write('\n')
1211
1214
1212 def finddate(ui, repo, date):
1215 def finddate(ui, repo, date):
1213 """Find the tipmost changeset that matches the given date spec"""
1216 """Find the tipmost changeset that matches the given date spec"""
1214
1217
1215 df = util.matchdate(date)
1218 df = util.matchdate(date)
1216 m = scmutil.matchall(repo)
1219 m = scmutil.matchall(repo)
1217 results = {}
1220 results = {}
1218
1221
1219 def prep(ctx, fns):
1222 def prep(ctx, fns):
1220 d = ctx.date()
1223 d = ctx.date()
1221 if df(d[0]):
1224 if df(d[0]):
1222 results[ctx.rev()] = d
1225 results[ctx.rev()] = d
1223
1226
1224 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1227 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1225 rev = ctx.rev()
1228 rev = ctx.rev()
1226 if rev in results:
1229 if rev in results:
1227 ui.status(_("found revision %s from %s\n") %
1230 ui.status(_("found revision %s from %s\n") %
1228 (rev, util.datestr(results[rev])))
1231 (rev, util.datestr(results[rev])))
1229 return str(rev)
1232 return str(rev)
1230
1233
1231 raise util.Abort(_("revision matching date not found"))
1234 raise util.Abort(_("revision matching date not found"))
1232
1235
1233 def increasingwindows(windowsize=8, sizelimit=512):
1236 def increasingwindows(windowsize=8, sizelimit=512):
1234 while True:
1237 while True:
1235 yield windowsize
1238 yield windowsize
1236 if windowsize < sizelimit:
1239 if windowsize < sizelimit:
1237 windowsize *= 2
1240 windowsize *= 2
1238
1241
1239 class FileWalkError(Exception):
1242 class FileWalkError(Exception):
1240 pass
1243 pass
1241
1244
1242 def walkfilerevs(repo, match, follow, revs, fncache):
1245 def walkfilerevs(repo, match, follow, revs, fncache):
1243 '''Walks the file history for the matched files.
1246 '''Walks the file history for the matched files.
1244
1247
1245 Returns the changeset revs that are involved in the file history.
1248 Returns the changeset revs that are involved in the file history.
1246
1249
1247 Throws FileWalkError if the file history can't be walked using
1250 Throws FileWalkError if the file history can't be walked using
1248 filelogs alone.
1251 filelogs alone.
1249 '''
1252 '''
1250 wanted = set()
1253 wanted = set()
1251 copies = []
1254 copies = []
1252 minrev, maxrev = min(revs), max(revs)
1255 minrev, maxrev = min(revs), max(revs)
1253 def filerevgen(filelog, last):
1256 def filerevgen(filelog, last):
1254 """
1257 """
1255 Only files, no patterns. Check the history of each file.
1258 Only files, no patterns. Check the history of each file.
1256
1259
1257 Examines filelog entries within minrev, maxrev linkrev range
1260 Examines filelog entries within minrev, maxrev linkrev range
1258 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1261 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1259 tuples in backwards order
1262 tuples in backwards order
1260 """
1263 """
1261 cl_count = len(repo)
1264 cl_count = len(repo)
1262 revs = []
1265 revs = []
1263 for j in xrange(0, last + 1):
1266 for j in xrange(0, last + 1):
1264 linkrev = filelog.linkrev(j)
1267 linkrev = filelog.linkrev(j)
1265 if linkrev < minrev:
1268 if linkrev < minrev:
1266 continue
1269 continue
1267 # only yield rev for which we have the changelog, it can
1270 # only yield rev for which we have the changelog, it can
1268 # happen while doing "hg log" during a pull or commit
1271 # happen while doing "hg log" during a pull or commit
1269 if linkrev >= cl_count:
1272 if linkrev >= cl_count:
1270 break
1273 break
1271
1274
1272 parentlinkrevs = []
1275 parentlinkrevs = []
1273 for p in filelog.parentrevs(j):
1276 for p in filelog.parentrevs(j):
1274 if p != nullrev:
1277 if p != nullrev:
1275 parentlinkrevs.append(filelog.linkrev(p))
1278 parentlinkrevs.append(filelog.linkrev(p))
1276 n = filelog.node(j)
1279 n = filelog.node(j)
1277 revs.append((linkrev, parentlinkrevs,
1280 revs.append((linkrev, parentlinkrevs,
1278 follow and filelog.renamed(n)))
1281 follow and filelog.renamed(n)))
1279
1282
1280 return reversed(revs)
1283 return reversed(revs)
1281 def iterfiles():
1284 def iterfiles():
1282 pctx = repo['.']
1285 pctx = repo['.']
1283 for filename in match.files():
1286 for filename in match.files():
1284 if follow:
1287 if follow:
1285 if filename not in pctx:
1288 if filename not in pctx:
1286 raise util.Abort(_('cannot follow file not in parent '
1289 raise util.Abort(_('cannot follow file not in parent '
1287 'revision: "%s"') % filename)
1290 'revision: "%s"') % filename)
1288 yield filename, pctx[filename].filenode()
1291 yield filename, pctx[filename].filenode()
1289 else:
1292 else:
1290 yield filename, None
1293 yield filename, None
1291 for filename_node in copies:
1294 for filename_node in copies:
1292 yield filename_node
1295 yield filename_node
1293
1296
1294 for file_, node in iterfiles():
1297 for file_, node in iterfiles():
1295 filelog = repo.file(file_)
1298 filelog = repo.file(file_)
1296 if not len(filelog):
1299 if not len(filelog):
1297 if node is None:
1300 if node is None:
1298 # A zero count may be a directory or deleted file, so
1301 # A zero count may be a directory or deleted file, so
1299 # try to find matching entries on the slow path.
1302 # try to find matching entries on the slow path.
1300 if follow:
1303 if follow:
1301 raise util.Abort(
1304 raise util.Abort(
1302 _('cannot follow nonexistent file: "%s"') % file_)
1305 _('cannot follow nonexistent file: "%s"') % file_)
1303 raise FileWalkError("Cannot walk via filelog")
1306 raise FileWalkError("Cannot walk via filelog")
1304 else:
1307 else:
1305 continue
1308 continue
1306
1309
1307 if node is None:
1310 if node is None:
1308 last = len(filelog) - 1
1311 last = len(filelog) - 1
1309 else:
1312 else:
1310 last = filelog.rev(node)
1313 last = filelog.rev(node)
1311
1314
1312
1315
1313 # keep track of all ancestors of the file
1316 # keep track of all ancestors of the file
1314 ancestors = set([filelog.linkrev(last)])
1317 ancestors = set([filelog.linkrev(last)])
1315
1318
1316 # iterate from latest to oldest revision
1319 # iterate from latest to oldest revision
1317 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1320 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1318 if not follow:
1321 if not follow:
1319 if rev > maxrev:
1322 if rev > maxrev:
1320 continue
1323 continue
1321 else:
1324 else:
1322 # Note that last might not be the first interesting
1325 # Note that last might not be the first interesting
1323 # rev to us:
1326 # rev to us:
1324 # if the file has been changed after maxrev, we'll
1327 # if the file has been changed after maxrev, we'll
1325 # have linkrev(last) > maxrev, and we still need
1328 # have linkrev(last) > maxrev, and we still need
1326 # to explore the file graph
1329 # to explore the file graph
1327 if rev not in ancestors:
1330 if rev not in ancestors:
1328 continue
1331 continue
1329 # XXX insert 1327 fix here
1332 # XXX insert 1327 fix here
1330 if flparentlinkrevs:
1333 if flparentlinkrevs:
1331 ancestors.update(flparentlinkrevs)
1334 ancestors.update(flparentlinkrevs)
1332
1335
1333 fncache.setdefault(rev, []).append(file_)
1336 fncache.setdefault(rev, []).append(file_)
1334 wanted.add(rev)
1337 wanted.add(rev)
1335 if copied:
1338 if copied:
1336 copies.append(copied)
1339 copies.append(copied)
1337
1340
1338 return wanted
1341 return wanted
1339
1342
1340 def walkchangerevs(repo, match, opts, prepare):
1343 def walkchangerevs(repo, match, opts, prepare):
1341 '''Iterate over files and the revs in which they changed.
1344 '''Iterate over files and the revs in which they changed.
1342
1345
1343 Callers most commonly need to iterate backwards over the history
1346 Callers most commonly need to iterate backwards over the history
1344 in which they are interested. Doing so has awful (quadratic-looking)
1347 in which they are interested. Doing so has awful (quadratic-looking)
1345 performance, so we use iterators in a "windowed" way.
1348 performance, so we use iterators in a "windowed" way.
1346
1349
1347 We walk a window of revisions in the desired order. Within the
1350 We walk a window of revisions in the desired order. Within the
1348 window, we first walk forwards to gather data, then in the desired
1351 window, we first walk forwards to gather data, then in the desired
1349 order (usually backwards) to display it.
1352 order (usually backwards) to display it.
1350
1353
1351 This function returns an iterator yielding contexts. Before
1354 This function returns an iterator yielding contexts. Before
1352 yielding each context, the iterator will first call the prepare
1355 yielding each context, the iterator will first call the prepare
1353 function on each context in the window in forward order.'''
1356 function on each context in the window in forward order.'''
1354
1357
1355 follow = opts.get('follow') or opts.get('follow_first')
1358 follow = opts.get('follow') or opts.get('follow_first')
1356
1359
1357 if opts.get('rev'):
1360 if opts.get('rev'):
1358 revs = scmutil.revrange(repo, opts.get('rev'))
1361 revs = scmutil.revrange(repo, opts.get('rev'))
1359 elif follow:
1362 elif follow:
1360 revs = repo.revs('reverse(:.)')
1363 revs = repo.revs('reverse(:.)')
1361 else:
1364 else:
1362 revs = revset.spanset(repo)
1365 revs = revset.spanset(repo)
1363 revs.reverse()
1366 revs.reverse()
1364 if not revs:
1367 if not revs:
1365 return []
1368 return []
1366 wanted = set()
1369 wanted = set()
1367 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1370 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1368 fncache = {}
1371 fncache = {}
1369 change = repo.changectx
1372 change = repo.changectx
1370
1373
1371 # First step is to fill wanted, the set of revisions that we want to yield.
1374 # First step is to fill wanted, the set of revisions that we want to yield.
1372 # When it does not induce extra cost, we also fill fncache for revisions in
1375 # When it does not induce extra cost, we also fill fncache for revisions in
1373 # wanted: a cache of filenames that were changed (ctx.files()) and that
1376 # wanted: a cache of filenames that were changed (ctx.files()) and that
1374 # match the file filtering conditions.
1377 # match the file filtering conditions.
1375
1378
1376 if not slowpath and not match.files():
1379 if not slowpath and not match.files():
1377 # No files, no patterns. Display all revs.
1380 # No files, no patterns. Display all revs.
1378 wanted = revs
1381 wanted = revs
1379
1382
1380 if not slowpath and match.files():
1383 if not slowpath and match.files():
1381 # We only have to read through the filelog to find wanted revisions
1384 # We only have to read through the filelog to find wanted revisions
1382
1385
1383 try:
1386 try:
1384 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1387 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1385 except FileWalkError:
1388 except FileWalkError:
1386 slowpath = True
1389 slowpath = True
1387
1390
1388 # We decided to fall back to the slowpath because at least one
1391 # We decided to fall back to the slowpath because at least one
1389 # of the paths was not a file. Check to see if at least one of them
1392 # of the paths was not a file. Check to see if at least one of them
1390 # existed in history, otherwise simply return
1393 # existed in history, otherwise simply return
1391 for path in match.files():
1394 for path in match.files():
1392 if path == '.' or path in repo.store:
1395 if path == '.' or path in repo.store:
1393 break
1396 break
1394 else:
1397 else:
1395 return []
1398 return []
1396
1399
1397 if slowpath:
1400 if slowpath:
1398 # We have to read the changelog to match filenames against
1401 # We have to read the changelog to match filenames against
1399 # changed files
1402 # changed files
1400
1403
1401 if follow:
1404 if follow:
1402 raise util.Abort(_('can only follow copies/renames for explicit '
1405 raise util.Abort(_('can only follow copies/renames for explicit '
1403 'filenames'))
1406 'filenames'))
1404
1407
1405 # The slow path checks files modified in every changeset.
1408 # The slow path checks files modified in every changeset.
1406 # This is really slow on large repos, so compute the set lazily.
1409 # This is really slow on large repos, so compute the set lazily.
1407 class lazywantedset(object):
1410 class lazywantedset(object):
1408 def __init__(self):
1411 def __init__(self):
1409 self.set = set()
1412 self.set = set()
1410 self.revs = set(revs)
1413 self.revs = set(revs)
1411
1414
1412 # No need to worry about locality here because it will be accessed
1415 # No need to worry about locality here because it will be accessed
1413 # in the same order as the increasing window below.
1416 # in the same order as the increasing window below.
1414 def __contains__(self, value):
1417 def __contains__(self, value):
1415 if value in self.set:
1418 if value in self.set:
1416 return True
1419 return True
1417 elif not value in self.revs:
1420 elif not value in self.revs:
1418 return False
1421 return False
1419 else:
1422 else:
1420 self.revs.discard(value)
1423 self.revs.discard(value)
1421 ctx = change(value)
1424 ctx = change(value)
1422 matches = filter(match, ctx.files())
1425 matches = filter(match, ctx.files())
1423 if matches:
1426 if matches:
1424 fncache[value] = matches
1427 fncache[value] = matches
1425 self.set.add(value)
1428 self.set.add(value)
1426 return True
1429 return True
1427 return False
1430 return False
1428
1431
1429 def discard(self, value):
1432 def discard(self, value):
1430 self.revs.discard(value)
1433 self.revs.discard(value)
1431 self.set.discard(value)
1434 self.set.discard(value)
1432
1435
1433 wanted = lazywantedset()
1436 wanted = lazywantedset()
1434
1437
1435 class followfilter(object):
1438 class followfilter(object):
1436 def __init__(self, onlyfirst=False):
1439 def __init__(self, onlyfirst=False):
1437 self.startrev = nullrev
1440 self.startrev = nullrev
1438 self.roots = set()
1441 self.roots = set()
1439 self.onlyfirst = onlyfirst
1442 self.onlyfirst = onlyfirst
1440
1443
1441 def match(self, rev):
1444 def match(self, rev):
1442 def realparents(rev):
1445 def realparents(rev):
1443 if self.onlyfirst:
1446 if self.onlyfirst:
1444 return repo.changelog.parentrevs(rev)[0:1]
1447 return repo.changelog.parentrevs(rev)[0:1]
1445 else:
1448 else:
1446 return filter(lambda x: x != nullrev,
1449 return filter(lambda x: x != nullrev,
1447 repo.changelog.parentrevs(rev))
1450 repo.changelog.parentrevs(rev))
1448
1451
1449 if self.startrev == nullrev:
1452 if self.startrev == nullrev:
1450 self.startrev = rev
1453 self.startrev = rev
1451 return True
1454 return True
1452
1455
1453 if rev > self.startrev:
1456 if rev > self.startrev:
1454 # forward: all descendants
1457 # forward: all descendants
1455 if not self.roots:
1458 if not self.roots:
1456 self.roots.add(self.startrev)
1459 self.roots.add(self.startrev)
1457 for parent in realparents(rev):
1460 for parent in realparents(rev):
1458 if parent in self.roots:
1461 if parent in self.roots:
1459 self.roots.add(rev)
1462 self.roots.add(rev)
1460 return True
1463 return True
1461 else:
1464 else:
1462 # backwards: all parents
1465 # backwards: all parents
1463 if not self.roots:
1466 if not self.roots:
1464 self.roots.update(realparents(self.startrev))
1467 self.roots.update(realparents(self.startrev))
1465 if rev in self.roots:
1468 if rev in self.roots:
1466 self.roots.remove(rev)
1469 self.roots.remove(rev)
1467 self.roots.update(realparents(rev))
1470 self.roots.update(realparents(rev))
1468 return True
1471 return True
1469
1472
1470 return False
1473 return False
1471
1474
1472 # it might be worthwhile to do this in the iterator if the rev range
1475 # it might be worthwhile to do this in the iterator if the rev range
1473 # is descending and the prune args are all within that range
1476 # is descending and the prune args are all within that range
1474 for rev in opts.get('prune', ()):
1477 for rev in opts.get('prune', ()):
1475 rev = repo[rev].rev()
1478 rev = repo[rev].rev()
1476 ff = followfilter()
1479 ff = followfilter()
1477 stop = min(revs[0], revs[-1])
1480 stop = min(revs[0], revs[-1])
1478 for x in xrange(rev, stop - 1, -1):
1481 for x in xrange(rev, stop - 1, -1):
1479 if ff.match(x):
1482 if ff.match(x):
1480 wanted = wanted - [x]
1483 wanted = wanted - [x]
1481
1484
1482 # Now that wanted is correctly initialized, we can iterate over the
1485 # Now that wanted is correctly initialized, we can iterate over the
1483 # revision range, yielding only revisions in wanted.
1486 # revision range, yielding only revisions in wanted.
1484 def iterate():
1487 def iterate():
1485 if follow and not match.files():
1488 if follow and not match.files():
1486 ff = followfilter(onlyfirst=opts.get('follow_first'))
1489 ff = followfilter(onlyfirst=opts.get('follow_first'))
1487 def want(rev):
1490 def want(rev):
1488 return ff.match(rev) and rev in wanted
1491 return ff.match(rev) and rev in wanted
1489 else:
1492 else:
1490 def want(rev):
1493 def want(rev):
1491 return rev in wanted
1494 return rev in wanted
1492
1495
1493 it = iter(revs)
1496 it = iter(revs)
1494 stopiteration = False
1497 stopiteration = False
1495 for windowsize in increasingwindows():
1498 for windowsize in increasingwindows():
1496 nrevs = []
1499 nrevs = []
1497 for i in xrange(windowsize):
1500 for i in xrange(windowsize):
1498 try:
1501 try:
1499 rev = it.next()
1502 rev = it.next()
1500 if want(rev):
1503 if want(rev):
1501 nrevs.append(rev)
1504 nrevs.append(rev)
1502 except (StopIteration):
1505 except (StopIteration):
1503 stopiteration = True
1506 stopiteration = True
1504 break
1507 break
1505 for rev in sorted(nrevs):
1508 for rev in sorted(nrevs):
1506 fns = fncache.get(rev)
1509 fns = fncache.get(rev)
1507 ctx = change(rev)
1510 ctx = change(rev)
1508 if not fns:
1511 if not fns:
1509 def fns_generator():
1512 def fns_generator():
1510 for f in ctx.files():
1513 for f in ctx.files():
1511 if match(f):
1514 if match(f):
1512 yield f
1515 yield f
1513 fns = fns_generator()
1516 fns = fns_generator()
1514 prepare(ctx, fns)
1517 prepare(ctx, fns)
1515 for rev in nrevs:
1518 for rev in nrevs:
1516 yield change(rev)
1519 yield change(rev)
1517
1520
1518 if stopiteration:
1521 if stopiteration:
1519 break
1522 break
1520
1523
1521 return iterate()
1524 return iterate()
1522
1525
1523 def _makefollowlogfilematcher(repo, files, followfirst):
1526 def _makefollowlogfilematcher(repo, files, followfirst):
1524 # When displaying a revision with --patch --follow FILE, we have
1527 # When displaying a revision with --patch --follow FILE, we have
1525 # to know which file of the revision must be diffed. With
1528 # to know which file of the revision must be diffed. With
1526 # --follow, we want the names of the ancestors of FILE in the
1529 # --follow, we want the names of the ancestors of FILE in the
1527 # revision, stored in "fcache". "fcache" is populated by
1530 # revision, stored in "fcache". "fcache" is populated by
1528 # reproducing the graph traversal already done by --follow revset
1531 # reproducing the graph traversal already done by --follow revset
1529 # and relating linkrevs to file names (which is not "correct" but
1532 # and relating linkrevs to file names (which is not "correct" but
1530 # good enough).
1533 # good enough).
1531 fcache = {}
1534 fcache = {}
1532 fcacheready = [False]
1535 fcacheready = [False]
1533 pctx = repo['.']
1536 pctx = repo['.']
1534
1537
1535 def populate():
1538 def populate():
1536 for fn in files:
1539 for fn in files:
1537 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1540 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1538 for c in i:
1541 for c in i:
1539 fcache.setdefault(c.linkrev(), set()).add(c.path())
1542 fcache.setdefault(c.linkrev(), set()).add(c.path())
1540
1543
1541 def filematcher(rev):
1544 def filematcher(rev):
1542 if not fcacheready[0]:
1545 if not fcacheready[0]:
1543 # Lazy initialization
1546 # Lazy initialization
1544 fcacheready[0] = True
1547 fcacheready[0] = True
1545 populate()
1548 populate()
1546 return scmutil.matchfiles(repo, fcache.get(rev, []))
1549 return scmutil.matchfiles(repo, fcache.get(rev, []))
1547
1550
1548 return filematcher
1551 return filematcher
1549
1552
1550 def _makenofollowlogfilematcher(repo, pats, opts):
1553 def _makenofollowlogfilematcher(repo, pats, opts):
1551 '''hook for extensions to override the filematcher for non-follow cases'''
1554 '''hook for extensions to override the filematcher for non-follow cases'''
1552 return None
1555 return None
1553
1556
1554 def _makelogrevset(repo, pats, opts, revs):
1557 def _makelogrevset(repo, pats, opts, revs):
1555 """Return (expr, filematcher) where expr is a revset string built
1558 """Return (expr, filematcher) where expr is a revset string built
1556 from log options and file patterns or None. If --stat or --patch
1559 from log options and file patterns or None. If --stat or --patch
1557 are not passed filematcher is None. Otherwise it is a callable
1560 are not passed filematcher is None. Otherwise it is a callable
1558 taking a revision number and returning a match objects filtering
1561 taking a revision number and returning a match objects filtering
1559 the files to be detailed when displaying the revision.
1562 the files to be detailed when displaying the revision.
1560 """
1563 """
1561 opt2revset = {
1564 opt2revset = {
1562 'no_merges': ('not merge()', None),
1565 'no_merges': ('not merge()', None),
1563 'only_merges': ('merge()', None),
1566 'only_merges': ('merge()', None),
1564 '_ancestors': ('ancestors(%(val)s)', None),
1567 '_ancestors': ('ancestors(%(val)s)', None),
1565 '_fancestors': ('_firstancestors(%(val)s)', None),
1568 '_fancestors': ('_firstancestors(%(val)s)', None),
1566 '_descendants': ('descendants(%(val)s)', None),
1569 '_descendants': ('descendants(%(val)s)', None),
1567 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1570 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1568 '_matchfiles': ('_matchfiles(%(val)s)', None),
1571 '_matchfiles': ('_matchfiles(%(val)s)', None),
1569 'date': ('date(%(val)r)', None),
1572 'date': ('date(%(val)r)', None),
1570 'branch': ('branch(%(val)r)', ' or '),
1573 'branch': ('branch(%(val)r)', ' or '),
1571 '_patslog': ('filelog(%(val)r)', ' or '),
1574 '_patslog': ('filelog(%(val)r)', ' or '),
1572 '_patsfollow': ('follow(%(val)r)', ' or '),
1575 '_patsfollow': ('follow(%(val)r)', ' or '),
1573 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1576 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1574 'keyword': ('keyword(%(val)r)', ' or '),
1577 'keyword': ('keyword(%(val)r)', ' or '),
1575 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1578 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1576 'user': ('user(%(val)r)', ' or '),
1579 'user': ('user(%(val)r)', ' or '),
1577 }
1580 }
1578
1581
1579 opts = dict(opts)
1582 opts = dict(opts)
1580 # follow or not follow?
1583 # follow or not follow?
1581 follow = opts.get('follow') or opts.get('follow_first')
1584 follow = opts.get('follow') or opts.get('follow_first')
1582 followfirst = opts.get('follow_first') and 1 or 0
1585 followfirst = opts.get('follow_first') and 1 or 0
1583 # --follow with FILE behaviour depends on revs...
1586 # --follow with FILE behaviour depends on revs...
1584 it = iter(revs)
1587 it = iter(revs)
1585 startrev = it.next()
1588 startrev = it.next()
1586 try:
1589 try:
1587 followdescendants = startrev < it.next()
1590 followdescendants = startrev < it.next()
1588 except (StopIteration):
1591 except (StopIteration):
1589 followdescendants = False
1592 followdescendants = False
1590
1593
1591 # branch and only_branch are really aliases and must be handled at
1594 # branch and only_branch are really aliases and must be handled at
1592 # the same time
1595 # the same time
1593 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1596 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1594 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1597 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1595 # pats/include/exclude are passed to match.match() directly in
1598 # pats/include/exclude are passed to match.match() directly in
1596 # _matchfiles() revset but walkchangerevs() builds its matcher with
1599 # _matchfiles() revset but walkchangerevs() builds its matcher with
1597 # scmutil.match(). The difference is input pats are globbed on
1600 # scmutil.match(). The difference is input pats are globbed on
1598 # platforms without shell expansion (windows).
1601 # platforms without shell expansion (windows).
1599 pctx = repo[None]
1602 pctx = repo[None]
1600 match, pats = scmutil.matchandpats(pctx, pats, opts)
1603 match, pats = scmutil.matchandpats(pctx, pats, opts)
1601 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1604 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1602 if not slowpath:
1605 if not slowpath:
1603 for f in match.files():
1606 for f in match.files():
1604 if follow and f not in pctx:
1607 if follow and f not in pctx:
1605 # If the file exists, it may be a directory, so let it
1608 # If the file exists, it may be a directory, so let it
1606 # take the slow path.
1609 # take the slow path.
1607 if os.path.exists(repo.wjoin(f)):
1610 if os.path.exists(repo.wjoin(f)):
1608 slowpath = True
1611 slowpath = True
1609 continue
1612 continue
1610 else:
1613 else:
1611 raise util.Abort(_('cannot follow file not in parent '
1614 raise util.Abort(_('cannot follow file not in parent '
1612 'revision: "%s"') % f)
1615 'revision: "%s"') % f)
1613 filelog = repo.file(f)
1616 filelog = repo.file(f)
1614 if not filelog:
1617 if not filelog:
1615 # A zero count may be a directory or deleted file, so
1618 # A zero count may be a directory or deleted file, so
1616 # try to find matching entries on the slow path.
1619 # try to find matching entries on the slow path.
1617 if follow:
1620 if follow:
1618 raise util.Abort(
1621 raise util.Abort(
1619 _('cannot follow nonexistent file: "%s"') % f)
1622 _('cannot follow nonexistent file: "%s"') % f)
1620 slowpath = True
1623 slowpath = True
1621
1624
1622 # We decided to fall back to the slowpath because at least one
1625 # We decided to fall back to the slowpath because at least one
1623 # of the paths was not a file. Check to see if at least one of them
1626 # of the paths was not a file. Check to see if at least one of them
1624 # existed in history - in that case, we'll continue down the
1627 # existed in history - in that case, we'll continue down the
1625 # slowpath; otherwise, we can turn off the slowpath
1628 # slowpath; otherwise, we can turn off the slowpath
1626 if slowpath:
1629 if slowpath:
1627 for path in match.files():
1630 for path in match.files():
1628 if path == '.' or path in repo.store:
1631 if path == '.' or path in repo.store:
1629 break
1632 break
1630 else:
1633 else:
1631 slowpath = False
1634 slowpath = False
1632
1635
1633 if slowpath:
1636 if slowpath:
1634 # See walkchangerevs() slow path.
1637 # See walkchangerevs() slow path.
1635 #
1638 #
1636 # pats/include/exclude cannot be represented as separate
1639 # pats/include/exclude cannot be represented as separate
1637 # revset expressions as their filtering logic applies at file
1640 # revset expressions as their filtering logic applies at file
1638 # level. For instance "-I a -X a" matches a revision touching
1641 # level. For instance "-I a -X a" matches a revision touching
1639 # "a" and "b" while "file(a) and not file(b)" does
1642 # "a" and "b" while "file(a) and not file(b)" does
1640 # not. Besides, filesets are evaluated against the working
1643 # not. Besides, filesets are evaluated against the working
1641 # directory.
1644 # directory.
1642 matchargs = ['r:', 'd:relpath']
1645 matchargs = ['r:', 'd:relpath']
1643 for p in pats:
1646 for p in pats:
1644 matchargs.append('p:' + p)
1647 matchargs.append('p:' + p)
1645 for p in opts.get('include', []):
1648 for p in opts.get('include', []):
1646 matchargs.append('i:' + p)
1649 matchargs.append('i:' + p)
1647 for p in opts.get('exclude', []):
1650 for p in opts.get('exclude', []):
1648 matchargs.append('x:' + p)
1651 matchargs.append('x:' + p)
1649 matchargs = ','.join(('%r' % p) for p in matchargs)
1652 matchargs = ','.join(('%r' % p) for p in matchargs)
1650 opts['_matchfiles'] = matchargs
1653 opts['_matchfiles'] = matchargs
1651 else:
1654 else:
1652 if follow:
1655 if follow:
1653 fpats = ('_patsfollow', '_patsfollowfirst')
1656 fpats = ('_patsfollow', '_patsfollowfirst')
1654 fnopats = (('_ancestors', '_fancestors'),
1657 fnopats = (('_ancestors', '_fancestors'),
1655 ('_descendants', '_fdescendants'))
1658 ('_descendants', '_fdescendants'))
1656 if pats:
1659 if pats:
1657 # follow() revset interprets its file argument as a
1660 # follow() revset interprets its file argument as a
1658 # manifest entry, so use match.files(), not pats.
1661 # manifest entry, so use match.files(), not pats.
1659 opts[fpats[followfirst]] = list(match.files())
1662 opts[fpats[followfirst]] = list(match.files())
1660 else:
1663 else:
1661 opts[fnopats[followdescendants][followfirst]] = str(startrev)
1664 opts[fnopats[followdescendants][followfirst]] = str(startrev)
1662 else:
1665 else:
1663 opts['_patslog'] = list(pats)
1666 opts['_patslog'] = list(pats)
1664
1667
1665 filematcher = None
1668 filematcher = None
1666 if opts.get('patch') or opts.get('stat'):
1669 if opts.get('patch') or opts.get('stat'):
1667 # When following files, track renames via a special matcher.
1670 # When following files, track renames via a special matcher.
1668 # If we're forced to take the slowpath it means we're following
1671 # If we're forced to take the slowpath it means we're following
1669 # at least one pattern/directory, so don't bother with rename tracking.
1672 # at least one pattern/directory, so don't bother with rename tracking.
1670 if follow and not match.always() and not slowpath:
1673 if follow and not match.always() and not slowpath:
1671 # _makelogfilematcher expects its files argument to be relative to
1674 # _makelogfilematcher expects its files argument to be relative to
1672 # the repo root, so use match.files(), not pats.
1675 # the repo root, so use match.files(), not pats.
1673 filematcher = _makefollowlogfilematcher(repo, match.files(),
1676 filematcher = _makefollowlogfilematcher(repo, match.files(),
1674 followfirst)
1677 followfirst)
1675 else:
1678 else:
1676 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
1679 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
1677 if filematcher is None:
1680 if filematcher is None:
1678 filematcher = lambda rev: match
1681 filematcher = lambda rev: match
1679
1682
1680 expr = []
1683 expr = []
1681 for op, val in opts.iteritems():
1684 for op, val in opts.iteritems():
1682 if not val:
1685 if not val:
1683 continue
1686 continue
1684 if op not in opt2revset:
1687 if op not in opt2revset:
1685 continue
1688 continue
1686 revop, andor = opt2revset[op]
1689 revop, andor = opt2revset[op]
1687 if '%(val)' not in revop:
1690 if '%(val)' not in revop:
1688 expr.append(revop)
1691 expr.append(revop)
1689 else:
1692 else:
1690 if not isinstance(val, list):
1693 if not isinstance(val, list):
1691 e = revop % {'val': val}
1694 e = revop % {'val': val}
1692 else:
1695 else:
1693 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
1696 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
1694 expr.append(e)
1697 expr.append(e)
1695
1698
1696 if expr:
1699 if expr:
1697 expr = '(' + ' and '.join(expr) + ')'
1700 expr = '(' + ' and '.join(expr) + ')'
1698 else:
1701 else:
1699 expr = None
1702 expr = None
1700 return expr, filematcher
1703 return expr, filematcher
1701
1704
1702 def getgraphlogrevs(repo, pats, opts):
1705 def getgraphlogrevs(repo, pats, opts):
1703 """Return (revs, expr, filematcher) where revs is an iterable of
1706 """Return (revs, expr, filematcher) where revs is an iterable of
1704 revision numbers, expr is a revset string built from log options
1707 revision numbers, expr is a revset string built from log options
1705 and file patterns or None, and used to filter 'revs'. If --stat or
1708 and file patterns or None, and used to filter 'revs'. If --stat or
1706 --patch are not passed filematcher is None. Otherwise it is a
1709 --patch are not passed filematcher is None. Otherwise it is a
1707 callable taking a revision number and returning a match objects
1710 callable taking a revision number and returning a match objects
1708 filtering the files to be detailed when displaying the revision.
1711 filtering the files to be detailed when displaying the revision.
1709 """
1712 """
1710 if not len(repo):
1713 if not len(repo):
1711 return [], None, None
1714 return [], None, None
1712 limit = loglimit(opts)
1715 limit = loglimit(opts)
1713 # Default --rev value depends on --follow but --follow behaviour
1716 # Default --rev value depends on --follow but --follow behaviour
1714 # depends on revisions resolved from --rev...
1717 # depends on revisions resolved from --rev...
1715 follow = opts.get('follow') or opts.get('follow_first')
1718 follow = opts.get('follow') or opts.get('follow_first')
1716 possiblyunsorted = False # whether revs might need sorting
1719 possiblyunsorted = False # whether revs might need sorting
1717 if opts.get('rev'):
1720 if opts.get('rev'):
1718 revs = scmutil.revrange(repo, opts['rev'])
1721 revs = scmutil.revrange(repo, opts['rev'])
1719 # Don't sort here because _makelogrevset might depend on the
1722 # Don't sort here because _makelogrevset might depend on the
1720 # order of revs
1723 # order of revs
1721 possiblyunsorted = True
1724 possiblyunsorted = True
1722 else:
1725 else:
1723 if follow and len(repo) > 0:
1726 if follow and len(repo) > 0:
1724 revs = repo.revs('reverse(:.)')
1727 revs = repo.revs('reverse(:.)')
1725 else:
1728 else:
1726 revs = revset.spanset(repo)
1729 revs = revset.spanset(repo)
1727 revs.reverse()
1730 revs.reverse()
1728 if not revs:
1731 if not revs:
1729 return revset.baseset(), None, None
1732 return revset.baseset(), None, None
1730 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
1733 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
1731 if possiblyunsorted:
1734 if possiblyunsorted:
1732 revs.sort(reverse=True)
1735 revs.sort(reverse=True)
1733 if expr:
1736 if expr:
1734 # Revset matchers often operate faster on revisions in changelog
1737 # Revset matchers often operate faster on revisions in changelog
1735 # order, because most filters deal with the changelog.
1738 # order, because most filters deal with the changelog.
1736 revs.reverse()
1739 revs.reverse()
1737 matcher = revset.match(repo.ui, expr)
1740 matcher = revset.match(repo.ui, expr)
1738 # Revset matches can reorder revisions. "A or B" typically returns
1741 # Revset matches can reorder revisions. "A or B" typically returns
1739 # returns the revision matching A then the revision matching B. Sort
1742 # returns the revision matching A then the revision matching B. Sort
1740 # again to fix that.
1743 # again to fix that.
1741 revs = matcher(repo, revs)
1744 revs = matcher(repo, revs)
1742 revs.sort(reverse=True)
1745 revs.sort(reverse=True)
1743 if limit is not None:
1746 if limit is not None:
1744 limitedrevs = revset.baseset()
1747 limitedrevs = revset.baseset()
1745 for idx, rev in enumerate(revs):
1748 for idx, rev in enumerate(revs):
1746 if idx >= limit:
1749 if idx >= limit:
1747 break
1750 break
1748 limitedrevs.append(rev)
1751 limitedrevs.append(rev)
1749 revs = limitedrevs
1752 revs = limitedrevs
1750
1753
1751 return revs, expr, filematcher
1754 return revs, expr, filematcher
1752
1755
1753 def getlogrevs(repo, pats, opts):
1756 def getlogrevs(repo, pats, opts):
1754 """Return (revs, expr, filematcher) where revs is an iterable of
1757 """Return (revs, expr, filematcher) where revs is an iterable of
1755 revision numbers, expr is a revset string built from log options
1758 revision numbers, expr is a revset string built from log options
1756 and file patterns or None, and used to filter 'revs'. If --stat or
1759 and file patterns or None, and used to filter 'revs'. If --stat or
1757 --patch are not passed filematcher is None. Otherwise it is a
1760 --patch are not passed filematcher is None. Otherwise it is a
1758 callable taking a revision number and returning a match objects
1761 callable taking a revision number and returning a match objects
1759 filtering the files to be detailed when displaying the revision.
1762 filtering the files to be detailed when displaying the revision.
1760 """
1763 """
1761 limit = loglimit(opts)
1764 limit = loglimit(opts)
1762 # Default --rev value depends on --follow but --follow behaviour
1765 # Default --rev value depends on --follow but --follow behaviour
1763 # depends on revisions resolved from --rev...
1766 # depends on revisions resolved from --rev...
1764 follow = opts.get('follow') or opts.get('follow_first')
1767 follow = opts.get('follow') or opts.get('follow_first')
1765 if opts.get('rev'):
1768 if opts.get('rev'):
1766 revs = scmutil.revrange(repo, opts['rev'])
1769 revs = scmutil.revrange(repo, opts['rev'])
1767 elif follow:
1770 elif follow:
1768 revs = repo.revs('reverse(:.)')
1771 revs = repo.revs('reverse(:.)')
1769 else:
1772 else:
1770 revs = revset.spanset(repo)
1773 revs = revset.spanset(repo)
1771 revs.reverse()
1774 revs.reverse()
1772 if not revs:
1775 if not revs:
1773 return revset.baseset([]), None, None
1776 return revset.baseset([]), None, None
1774 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
1777 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
1775 if expr:
1778 if expr:
1776 # Revset matchers often operate faster on revisions in changelog
1779 # Revset matchers often operate faster on revisions in changelog
1777 # order, because most filters deal with the changelog.
1780 # order, because most filters deal with the changelog.
1778 if not opts.get('rev'):
1781 if not opts.get('rev'):
1779 revs.reverse()
1782 revs.reverse()
1780 matcher = revset.match(repo.ui, expr)
1783 matcher = revset.match(repo.ui, expr)
1781 # Revset matches can reorder revisions. "A or B" typically returns
1784 # Revset matches can reorder revisions. "A or B" typically returns
1782 # returns the revision matching A then the revision matching B. Sort
1785 # returns the revision matching A then the revision matching B. Sort
1783 # again to fix that.
1786 # again to fix that.
1784 revs = matcher(repo, revs)
1787 revs = matcher(repo, revs)
1785 if not opts.get('rev'):
1788 if not opts.get('rev'):
1786 revs.sort(reverse=True)
1789 revs.sort(reverse=True)
1787 if limit is not None:
1790 if limit is not None:
1788 count = 0
1791 count = 0
1789 limitedrevs = revset.baseset([])
1792 limitedrevs = revset.baseset([])
1790 it = iter(revs)
1793 it = iter(revs)
1791 while count < limit:
1794 while count < limit:
1792 try:
1795 try:
1793 limitedrevs.append(it.next())
1796 limitedrevs.append(it.next())
1794 except (StopIteration):
1797 except (StopIteration):
1795 break
1798 break
1796 count += 1
1799 count += 1
1797 revs = limitedrevs
1800 revs = limitedrevs
1798
1801
1799 return revs, expr, filematcher
1802 return revs, expr, filematcher
1800
1803
1801 def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
1804 def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
1802 filematcher=None):
1805 filematcher=None):
1803 seen, state = [], graphmod.asciistate()
1806 seen, state = [], graphmod.asciistate()
1804 for rev, type, ctx, parents in dag:
1807 for rev, type, ctx, parents in dag:
1805 char = 'o'
1808 char = 'o'
1806 if ctx.node() in showparents:
1809 if ctx.node() in showparents:
1807 char = '@'
1810 char = '@'
1808 elif ctx.obsolete():
1811 elif ctx.obsolete():
1809 char = 'x'
1812 char = 'x'
1810 copies = None
1813 copies = None
1811 if getrenamed and ctx.rev():
1814 if getrenamed and ctx.rev():
1812 copies = []
1815 copies = []
1813 for fn in ctx.files():
1816 for fn in ctx.files():
1814 rename = getrenamed(fn, ctx.rev())
1817 rename = getrenamed(fn, ctx.rev())
1815 if rename:
1818 if rename:
1816 copies.append((fn, rename[0]))
1819 copies.append((fn, rename[0]))
1817 revmatchfn = None
1820 revmatchfn = None
1818 if filematcher is not None:
1821 if filematcher is not None:
1819 revmatchfn = filematcher(ctx.rev())
1822 revmatchfn = filematcher(ctx.rev())
1820 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
1823 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
1821 lines = displayer.hunk.pop(rev).split('\n')
1824 lines = displayer.hunk.pop(rev).split('\n')
1822 if not lines[-1]:
1825 if not lines[-1]:
1823 del lines[-1]
1826 del lines[-1]
1824 displayer.flush(rev)
1827 displayer.flush(rev)
1825 edges = edgefn(type, char, lines, seen, rev, parents)
1828 edges = edgefn(type, char, lines, seen, rev, parents)
1826 for type, char, lines, coldata in edges:
1829 for type, char, lines, coldata in edges:
1827 graphmod.ascii(ui, state, type, char, lines, coldata)
1830 graphmod.ascii(ui, state, type, char, lines, coldata)
1828 displayer.close()
1831 displayer.close()
1829
1832
1830 def graphlog(ui, repo, *pats, **opts):
1833 def graphlog(ui, repo, *pats, **opts):
1831 # Parameters are identical to log command ones
1834 # Parameters are identical to log command ones
1832 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
1835 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
1833 revdag = graphmod.dagwalker(repo, revs)
1836 revdag = graphmod.dagwalker(repo, revs)
1834
1837
1835 getrenamed = None
1838 getrenamed = None
1836 if opts.get('copies'):
1839 if opts.get('copies'):
1837 endrev = None
1840 endrev = None
1838 if opts.get('rev'):
1841 if opts.get('rev'):
1839 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
1842 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
1840 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
1843 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
1841 displayer = show_changeset(ui, repo, opts, buffered=True)
1844 displayer = show_changeset(ui, repo, opts, buffered=True)
1842 showparents = [ctx.node() for ctx in repo[None].parents()]
1845 showparents = [ctx.node() for ctx in repo[None].parents()]
1843 displaygraph(ui, revdag, displayer, showparents,
1846 displaygraph(ui, revdag, displayer, showparents,
1844 graphmod.asciiedges, getrenamed, filematcher)
1847 graphmod.asciiedges, getrenamed, filematcher)
1845
1848
1846 def checkunsupportedgraphflags(pats, opts):
1849 def checkunsupportedgraphflags(pats, opts):
1847 for op in ["newest_first"]:
1850 for op in ["newest_first"]:
1848 if op in opts and opts[op]:
1851 if op in opts and opts[op]:
1849 raise util.Abort(_("-G/--graph option is incompatible with --%s")
1852 raise util.Abort(_("-G/--graph option is incompatible with --%s")
1850 % op.replace("_", "-"))
1853 % op.replace("_", "-"))
1851
1854
1852 def graphrevs(repo, nodes, opts):
1855 def graphrevs(repo, nodes, opts):
1853 limit = loglimit(opts)
1856 limit = loglimit(opts)
1854 nodes.reverse()
1857 nodes.reverse()
1855 if limit is not None:
1858 if limit is not None:
1856 nodes = nodes[:limit]
1859 nodes = nodes[:limit]
1857 return graphmod.nodes(repo, nodes)
1860 return graphmod.nodes(repo, nodes)
1858
1861
1859 def add(ui, repo, match, dryrun, listsubrepos, prefix, explicitonly):
1862 def add(ui, repo, match, dryrun, listsubrepos, prefix, explicitonly):
1860 join = lambda f: os.path.join(prefix, f)
1863 join = lambda f: os.path.join(prefix, f)
1861 bad = []
1864 bad = []
1862 oldbad = match.bad
1865 oldbad = match.bad
1863 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1866 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1864 names = []
1867 names = []
1865 wctx = repo[None]
1868 wctx = repo[None]
1866 cca = None
1869 cca = None
1867 abort, warn = scmutil.checkportabilityalert(ui)
1870 abort, warn = scmutil.checkportabilityalert(ui)
1868 if abort or warn:
1871 if abort or warn:
1869 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
1872 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
1870 for f in repo.walk(match):
1873 for f in repo.walk(match):
1871 exact = match.exact(f)
1874 exact = match.exact(f)
1872 if exact or not explicitonly and f not in repo.dirstate:
1875 if exact or not explicitonly and f not in repo.dirstate:
1873 if cca:
1876 if cca:
1874 cca(f)
1877 cca(f)
1875 names.append(f)
1878 names.append(f)
1876 if ui.verbose or not exact:
1879 if ui.verbose or not exact:
1877 ui.status(_('adding %s\n') % match.rel(join(f)))
1880 ui.status(_('adding %s\n') % match.rel(join(f)))
1878
1881
1879 for subpath in sorted(wctx.substate):
1882 for subpath in sorted(wctx.substate):
1880 sub = wctx.sub(subpath)
1883 sub = wctx.sub(subpath)
1881 try:
1884 try:
1882 submatch = matchmod.narrowmatcher(subpath, match)
1885 submatch = matchmod.narrowmatcher(subpath, match)
1883 if listsubrepos:
1886 if listsubrepos:
1884 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1887 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1885 False))
1888 False))
1886 else:
1889 else:
1887 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1890 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1888 True))
1891 True))
1889 except error.LookupError:
1892 except error.LookupError:
1890 ui.status(_("skipping missing subrepository: %s\n")
1893 ui.status(_("skipping missing subrepository: %s\n")
1891 % join(subpath))
1894 % join(subpath))
1892
1895
1893 if not dryrun:
1896 if not dryrun:
1894 rejected = wctx.add(names, prefix)
1897 rejected = wctx.add(names, prefix)
1895 bad.extend(f for f in rejected if f in match.files())
1898 bad.extend(f for f in rejected if f in match.files())
1896 return bad
1899 return bad
1897
1900
1898 def forget(ui, repo, match, prefix, explicitonly):
1901 def forget(ui, repo, match, prefix, explicitonly):
1899 join = lambda f: os.path.join(prefix, f)
1902 join = lambda f: os.path.join(prefix, f)
1900 bad = []
1903 bad = []
1901 oldbad = match.bad
1904 oldbad = match.bad
1902 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1905 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1903 wctx = repo[None]
1906 wctx = repo[None]
1904 forgot = []
1907 forgot = []
1905 s = repo.status(match=match, clean=True)
1908 s = repo.status(match=match, clean=True)
1906 forget = sorted(s[0] + s[1] + s[3] + s[6])
1909 forget = sorted(s[0] + s[1] + s[3] + s[6])
1907 if explicitonly:
1910 if explicitonly:
1908 forget = [f for f in forget if match.exact(f)]
1911 forget = [f for f in forget if match.exact(f)]
1909
1912
1910 for subpath in sorted(wctx.substate):
1913 for subpath in sorted(wctx.substate):
1911 sub = wctx.sub(subpath)
1914 sub = wctx.sub(subpath)
1912 try:
1915 try:
1913 submatch = matchmod.narrowmatcher(subpath, match)
1916 submatch = matchmod.narrowmatcher(subpath, match)
1914 subbad, subforgot = sub.forget(ui, submatch, prefix)
1917 subbad, subforgot = sub.forget(ui, submatch, prefix)
1915 bad.extend([subpath + '/' + f for f in subbad])
1918 bad.extend([subpath + '/' + f for f in subbad])
1916 forgot.extend([subpath + '/' + f for f in subforgot])
1919 forgot.extend([subpath + '/' + f for f in subforgot])
1917 except error.LookupError:
1920 except error.LookupError:
1918 ui.status(_("skipping missing subrepository: %s\n")
1921 ui.status(_("skipping missing subrepository: %s\n")
1919 % join(subpath))
1922 % join(subpath))
1920
1923
1921 if not explicitonly:
1924 if not explicitonly:
1922 for f in match.files():
1925 for f in match.files():
1923 if f not in repo.dirstate and not os.path.isdir(match.rel(join(f))):
1926 if f not in repo.dirstate and not os.path.isdir(match.rel(join(f))):
1924 if f not in forgot:
1927 if f not in forgot:
1925 if os.path.exists(match.rel(join(f))):
1928 if os.path.exists(match.rel(join(f))):
1926 ui.warn(_('not removing %s: '
1929 ui.warn(_('not removing %s: '
1927 'file is already untracked\n')
1930 'file is already untracked\n')
1928 % match.rel(join(f)))
1931 % match.rel(join(f)))
1929 bad.append(f)
1932 bad.append(f)
1930
1933
1931 for f in forget:
1934 for f in forget:
1932 if ui.verbose or not match.exact(f):
1935 if ui.verbose or not match.exact(f):
1933 ui.status(_('removing %s\n') % match.rel(join(f)))
1936 ui.status(_('removing %s\n') % match.rel(join(f)))
1934
1937
1935 rejected = wctx.forget(forget, prefix)
1938 rejected = wctx.forget(forget, prefix)
1936 bad.extend(f for f in rejected if f in match.files())
1939 bad.extend(f for f in rejected if f in match.files())
1937 forgot.extend(forget)
1940 forgot.extend(forget)
1938 return bad, forgot
1941 return bad, forgot
1939
1942
1940 def cat(ui, repo, ctx, matcher, prefix, **opts):
1943 def cat(ui, repo, ctx, matcher, prefix, **opts):
1941 err = 1
1944 err = 1
1942
1945
1943 def write(path):
1946 def write(path):
1944 fp = makefileobj(repo, opts.get('output'), ctx.node(),
1947 fp = makefileobj(repo, opts.get('output'), ctx.node(),
1945 pathname=os.path.join(prefix, path))
1948 pathname=os.path.join(prefix, path))
1946 data = ctx[path].data()
1949 data = ctx[path].data()
1947 if opts.get('decode'):
1950 if opts.get('decode'):
1948 data = repo.wwritedata(path, data)
1951 data = repo.wwritedata(path, data)
1949 fp.write(data)
1952 fp.write(data)
1950 fp.close()
1953 fp.close()
1951
1954
1952 # Automation often uses hg cat on single files, so special case it
1955 # Automation often uses hg cat on single files, so special case it
1953 # for performance to avoid the cost of parsing the manifest.
1956 # for performance to avoid the cost of parsing the manifest.
1954 if len(matcher.files()) == 1 and not matcher.anypats():
1957 if len(matcher.files()) == 1 and not matcher.anypats():
1955 file = matcher.files()[0]
1958 file = matcher.files()[0]
1956 mf = repo.manifest
1959 mf = repo.manifest
1957 mfnode = ctx._changeset[0]
1960 mfnode = ctx._changeset[0]
1958 if mf.find(mfnode, file)[0]:
1961 if mf.find(mfnode, file)[0]:
1959 write(file)
1962 write(file)
1960 return 0
1963 return 0
1961
1964
1962 # Don't warn about "missing" files that are really in subrepos
1965 # Don't warn about "missing" files that are really in subrepos
1963 bad = matcher.bad
1966 bad = matcher.bad
1964
1967
1965 def badfn(path, msg):
1968 def badfn(path, msg):
1966 for subpath in ctx.substate:
1969 for subpath in ctx.substate:
1967 if path.startswith(subpath):
1970 if path.startswith(subpath):
1968 return
1971 return
1969 bad(path, msg)
1972 bad(path, msg)
1970
1973
1971 matcher.bad = badfn
1974 matcher.bad = badfn
1972
1975
1973 for abs in ctx.walk(matcher):
1976 for abs in ctx.walk(matcher):
1974 write(abs)
1977 write(abs)
1975 err = 0
1978 err = 0
1976
1979
1977 matcher.bad = bad
1980 matcher.bad = bad
1978
1981
1979 for subpath in sorted(ctx.substate):
1982 for subpath in sorted(ctx.substate):
1980 sub = ctx.sub(subpath)
1983 sub = ctx.sub(subpath)
1981 try:
1984 try:
1982 submatch = matchmod.narrowmatcher(subpath, matcher)
1985 submatch = matchmod.narrowmatcher(subpath, matcher)
1983
1986
1984 if not sub.cat(ui, submatch, os.path.join(prefix, sub._path),
1987 if not sub.cat(ui, submatch, os.path.join(prefix, sub._path),
1985 **opts):
1988 **opts):
1986 err = 0
1989 err = 0
1987 except error.RepoLookupError:
1990 except error.RepoLookupError:
1988 ui.status(_("skipping missing subrepository: %s\n")
1991 ui.status(_("skipping missing subrepository: %s\n")
1989 % os.path.join(prefix, subpath))
1992 % os.path.join(prefix, subpath))
1990
1993
1991 return err
1994 return err
1992
1995
1993 def duplicatecopies(repo, rev, fromrev, skiprev=None):
1996 def duplicatecopies(repo, rev, fromrev, skiprev=None):
1994 '''reproduce copies from fromrev to rev in the dirstate
1997 '''reproduce copies from fromrev to rev in the dirstate
1995
1998
1996 If skiprev is specified, it's a revision that should be used to
1999 If skiprev is specified, it's a revision that should be used to
1997 filter copy records. Any copies that occur between fromrev and
2000 filter copy records. Any copies that occur between fromrev and
1998 skiprev will not be duplicated, even if they appear in the set of
2001 skiprev will not be duplicated, even if they appear in the set of
1999 copies between fromrev and rev.
2002 copies between fromrev and rev.
2000 '''
2003 '''
2001 exclude = {}
2004 exclude = {}
2002 if skiprev is not None:
2005 if skiprev is not None:
2003 exclude = copies.pathcopies(repo[fromrev], repo[skiprev])
2006 exclude = copies.pathcopies(repo[fromrev], repo[skiprev])
2004 for dst, src in copies.pathcopies(repo[fromrev], repo[rev]).iteritems():
2007 for dst, src in copies.pathcopies(repo[fromrev], repo[rev]).iteritems():
2005 # copies.pathcopies returns backward renames, so dst might not
2008 # copies.pathcopies returns backward renames, so dst might not
2006 # actually be in the dirstate
2009 # actually be in the dirstate
2007 if dst in exclude:
2010 if dst in exclude:
2008 continue
2011 continue
2009 if repo.dirstate[dst] in "nma":
2012 if repo.dirstate[dst] in "nma":
2010 repo.dirstate.copy(src, dst)
2013 repo.dirstate.copy(src, dst)
2011
2014
2012 def commit(ui, repo, commitfunc, pats, opts):
2015 def commit(ui, repo, commitfunc, pats, opts):
2013 '''commit the specified files or all outstanding changes'''
2016 '''commit the specified files or all outstanding changes'''
2014 date = opts.get('date')
2017 date = opts.get('date')
2015 if date:
2018 if date:
2016 opts['date'] = util.parsedate(date)
2019 opts['date'] = util.parsedate(date)
2017 message = logmessage(ui, opts)
2020 message = logmessage(ui, opts)
2018
2021
2019 # extract addremove carefully -- this function can be called from a command
2022 # extract addremove carefully -- this function can be called from a command
2020 # that doesn't support addremove
2023 # that doesn't support addremove
2021 if opts.get('addremove'):
2024 if opts.get('addremove'):
2022 scmutil.addremove(repo, pats, opts)
2025 scmutil.addremove(repo, pats, opts)
2023
2026
2024 return commitfunc(ui, repo, message,
2027 return commitfunc(ui, repo, message,
2025 scmutil.match(repo[None], pats, opts), opts)
2028 scmutil.match(repo[None], pats, opts), opts)
2026
2029
2027 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2030 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2028 ui.note(_('amending changeset %s\n') % old)
2031 ui.note(_('amending changeset %s\n') % old)
2029 base = old.p1()
2032 base = old.p1()
2030
2033
2031 wlock = lock = newid = None
2034 wlock = lock = newid = None
2032 try:
2035 try:
2033 wlock = repo.wlock()
2036 wlock = repo.wlock()
2034 lock = repo.lock()
2037 lock = repo.lock()
2035 tr = repo.transaction('amend')
2038 tr = repo.transaction('amend')
2036 try:
2039 try:
2037 # See if we got a message from -m or -l, if not, open the editor
2040 # See if we got a message from -m or -l, if not, open the editor
2038 # with the message of the changeset to amend
2041 # with the message of the changeset to amend
2039 message = logmessage(ui, opts)
2042 message = logmessage(ui, opts)
2040 # ensure logfile does not conflict with later enforcement of the
2043 # ensure logfile does not conflict with later enforcement of the
2041 # message. potential logfile content has been processed by
2044 # message. potential logfile content has been processed by
2042 # `logmessage` anyway.
2045 # `logmessage` anyway.
2043 opts.pop('logfile')
2046 opts.pop('logfile')
2044 # First, do a regular commit to record all changes in the working
2047 # First, do a regular commit to record all changes in the working
2045 # directory (if there are any)
2048 # directory (if there are any)
2046 ui.callhooks = False
2049 ui.callhooks = False
2047 currentbookmark = repo._bookmarkcurrent
2050 currentbookmark = repo._bookmarkcurrent
2048 try:
2051 try:
2049 repo._bookmarkcurrent = None
2052 repo._bookmarkcurrent = None
2050 opts['message'] = 'temporary amend commit for %s' % old
2053 opts['message'] = 'temporary amend commit for %s' % old
2051 node = commit(ui, repo, commitfunc, pats, opts)
2054 node = commit(ui, repo, commitfunc, pats, opts)
2052 finally:
2055 finally:
2053 repo._bookmarkcurrent = currentbookmark
2056 repo._bookmarkcurrent = currentbookmark
2054 ui.callhooks = True
2057 ui.callhooks = True
2055 ctx = repo[node]
2058 ctx = repo[node]
2056
2059
2057 # Participating changesets:
2060 # Participating changesets:
2058 #
2061 #
2059 # node/ctx o - new (intermediate) commit that contains changes
2062 # node/ctx o - new (intermediate) commit that contains changes
2060 # | from working dir to go into amending commit
2063 # | from working dir to go into amending commit
2061 # | (or a workingctx if there were no changes)
2064 # | (or a workingctx if there were no changes)
2062 # |
2065 # |
2063 # old o - changeset to amend
2066 # old o - changeset to amend
2064 # |
2067 # |
2065 # base o - parent of amending changeset
2068 # base o - parent of amending changeset
2066
2069
2067 # Update extra dict from amended commit (e.g. to preserve graft
2070 # Update extra dict from amended commit (e.g. to preserve graft
2068 # source)
2071 # source)
2069 extra.update(old.extra())
2072 extra.update(old.extra())
2070
2073
2071 # Also update it from the intermediate commit or from the wctx
2074 # Also update it from the intermediate commit or from the wctx
2072 extra.update(ctx.extra())
2075 extra.update(ctx.extra())
2073
2076
2074 if len(old.parents()) > 1:
2077 if len(old.parents()) > 1:
2075 # ctx.files() isn't reliable for merges, so fall back to the
2078 # ctx.files() isn't reliable for merges, so fall back to the
2076 # slower repo.status() method
2079 # slower repo.status() method
2077 files = set([fn for st in repo.status(base, old)[:3]
2080 files = set([fn for st in repo.status(base, old)[:3]
2078 for fn in st])
2081 for fn in st])
2079 else:
2082 else:
2080 files = set(old.files())
2083 files = set(old.files())
2081
2084
2082 # Second, we use either the commit we just did, or if there were no
2085 # Second, we use either the commit we just did, or if there were no
2083 # changes the parent of the working directory as the version of the
2086 # changes the parent of the working directory as the version of the
2084 # files in the final amend commit
2087 # files in the final amend commit
2085 if node:
2088 if node:
2086 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2089 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2087
2090
2088 user = ctx.user()
2091 user = ctx.user()
2089 date = ctx.date()
2092 date = ctx.date()
2090 # Recompute copies (avoid recording a -> b -> a)
2093 # Recompute copies (avoid recording a -> b -> a)
2091 copied = copies.pathcopies(base, ctx)
2094 copied = copies.pathcopies(base, ctx)
2092
2095
2093 # Prune files which were reverted by the updates: if old
2096 # Prune files which were reverted by the updates: if old
2094 # introduced file X and our intermediate commit, node,
2097 # introduced file X and our intermediate commit, node,
2095 # renamed that file, then those two files are the same and
2098 # renamed that file, then those two files are the same and
2096 # we can discard X from our list of files. Likewise if X
2099 # we can discard X from our list of files. Likewise if X
2097 # was deleted, it's no longer relevant
2100 # was deleted, it's no longer relevant
2098 files.update(ctx.files())
2101 files.update(ctx.files())
2099
2102
2100 def samefile(f):
2103 def samefile(f):
2101 if f in ctx.manifest():
2104 if f in ctx.manifest():
2102 a = ctx.filectx(f)
2105 a = ctx.filectx(f)
2103 if f in base.manifest():
2106 if f in base.manifest():
2104 b = base.filectx(f)
2107 b = base.filectx(f)
2105 return (not a.cmp(b)
2108 return (not a.cmp(b)
2106 and a.flags() == b.flags())
2109 and a.flags() == b.flags())
2107 else:
2110 else:
2108 return False
2111 return False
2109 else:
2112 else:
2110 return f not in base.manifest()
2113 return f not in base.manifest()
2111 files = [f for f in files if not samefile(f)]
2114 files = [f for f in files if not samefile(f)]
2112
2115
2113 def filectxfn(repo, ctx_, path):
2116 def filectxfn(repo, ctx_, path):
2114 try:
2117 try:
2115 fctx = ctx[path]
2118 fctx = ctx[path]
2116 flags = fctx.flags()
2119 flags = fctx.flags()
2117 mctx = context.memfilectx(repo,
2120 mctx = context.memfilectx(repo,
2118 fctx.path(), fctx.data(),
2121 fctx.path(), fctx.data(),
2119 islink='l' in flags,
2122 islink='l' in flags,
2120 isexec='x' in flags,
2123 isexec='x' in flags,
2121 copied=copied.get(path))
2124 copied=copied.get(path))
2122 return mctx
2125 return mctx
2123 except KeyError:
2126 except KeyError:
2124 raise IOError
2127 raise IOError
2125 else:
2128 else:
2126 ui.note(_('copying changeset %s to %s\n') % (old, base))
2129 ui.note(_('copying changeset %s to %s\n') % (old, base))
2127
2130
2128 # Use version of files as in the old cset
2131 # Use version of files as in the old cset
2129 def filectxfn(repo, ctx_, path):
2132 def filectxfn(repo, ctx_, path):
2130 try:
2133 try:
2131 return old.filectx(path)
2134 return old.filectx(path)
2132 except KeyError:
2135 except KeyError:
2133 raise IOError
2136 raise IOError
2134
2137
2135 user = opts.get('user') or old.user()
2138 user = opts.get('user') or old.user()
2136 date = opts.get('date') or old.date()
2139 date = opts.get('date') or old.date()
2137 editform = mergeeditform(old, 'commit.amend')
2140 editform = mergeeditform(old, 'commit.amend')
2138 editor = getcommiteditor(editform=editform, **opts)
2141 editor = getcommiteditor(editform=editform, **opts)
2139 if not message:
2142 if not message:
2140 editor = getcommiteditor(edit=True, editform=editform)
2143 editor = getcommiteditor(edit=True, editform=editform)
2141 message = old.description()
2144 message = old.description()
2142
2145
2143 pureextra = extra.copy()
2146 pureextra = extra.copy()
2144 extra['amend_source'] = old.hex()
2147 extra['amend_source'] = old.hex()
2145
2148
2146 new = context.memctx(repo,
2149 new = context.memctx(repo,
2147 parents=[base.node(), old.p2().node()],
2150 parents=[base.node(), old.p2().node()],
2148 text=message,
2151 text=message,
2149 files=files,
2152 files=files,
2150 filectxfn=filectxfn,
2153 filectxfn=filectxfn,
2151 user=user,
2154 user=user,
2152 date=date,
2155 date=date,
2153 extra=extra,
2156 extra=extra,
2154 editor=editor)
2157 editor=editor)
2155
2158
2156 newdesc = changelog.stripdesc(new.description())
2159 newdesc = changelog.stripdesc(new.description())
2157 if ((not node)
2160 if ((not node)
2158 and newdesc == old.description()
2161 and newdesc == old.description()
2159 and user == old.user()
2162 and user == old.user()
2160 and date == old.date()
2163 and date == old.date()
2161 and pureextra == old.extra()):
2164 and pureextra == old.extra()):
2162 # nothing changed. continuing here would create a new node
2165 # nothing changed. continuing here would create a new node
2163 # anyway because of the amend_source noise.
2166 # anyway because of the amend_source noise.
2164 #
2167 #
2165 # This not what we expect from amend.
2168 # This not what we expect from amend.
2166 return old.node()
2169 return old.node()
2167
2170
2168 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2171 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2169 try:
2172 try:
2170 if opts.get('secret'):
2173 if opts.get('secret'):
2171 commitphase = 'secret'
2174 commitphase = 'secret'
2172 else:
2175 else:
2173 commitphase = old.phase()
2176 commitphase = old.phase()
2174 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2177 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2175 newid = repo.commitctx(new)
2178 newid = repo.commitctx(new)
2176 finally:
2179 finally:
2177 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2180 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2178 if newid != old.node():
2181 if newid != old.node():
2179 # Reroute the working copy parent to the new changeset
2182 # Reroute the working copy parent to the new changeset
2180 repo.setparents(newid, nullid)
2183 repo.setparents(newid, nullid)
2181
2184
2182 # Move bookmarks from old parent to amend commit
2185 # Move bookmarks from old parent to amend commit
2183 bms = repo.nodebookmarks(old.node())
2186 bms = repo.nodebookmarks(old.node())
2184 if bms:
2187 if bms:
2185 marks = repo._bookmarks
2188 marks = repo._bookmarks
2186 for bm in bms:
2189 for bm in bms:
2187 marks[bm] = newid
2190 marks[bm] = newid
2188 marks.write()
2191 marks.write()
2189 #commit the whole amend process
2192 #commit the whole amend process
2190 if obsolete._enabled and newid != old.node():
2193 if obsolete._enabled and newid != old.node():
2191 # mark the new changeset as successor of the rewritten one
2194 # mark the new changeset as successor of the rewritten one
2192 new = repo[newid]
2195 new = repo[newid]
2193 obs = [(old, (new,))]
2196 obs = [(old, (new,))]
2194 if node:
2197 if node:
2195 obs.append((ctx, ()))
2198 obs.append((ctx, ()))
2196
2199
2197 obsolete.createmarkers(repo, obs)
2200 obsolete.createmarkers(repo, obs)
2198 tr.close()
2201 tr.close()
2199 finally:
2202 finally:
2200 tr.release()
2203 tr.release()
2201 if (not obsolete._enabled) and newid != old.node():
2204 if (not obsolete._enabled) and newid != old.node():
2202 # Strip the intermediate commit (if there was one) and the amended
2205 # Strip the intermediate commit (if there was one) and the amended
2203 # commit
2206 # commit
2204 if node:
2207 if node:
2205 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2208 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2206 ui.note(_('stripping amended changeset %s\n') % old)
2209 ui.note(_('stripping amended changeset %s\n') % old)
2207 repair.strip(ui, repo, old.node(), topic='amend-backup')
2210 repair.strip(ui, repo, old.node(), topic='amend-backup')
2208 finally:
2211 finally:
2209 if newid is None:
2212 if newid is None:
2210 repo.dirstate.invalidate()
2213 repo.dirstate.invalidate()
2211 lockmod.release(lock, wlock)
2214 lockmod.release(lock, wlock)
2212 return newid
2215 return newid
2213
2216
2214 def commiteditor(repo, ctx, subs, editform=''):
2217 def commiteditor(repo, ctx, subs, editform=''):
2215 if ctx.description():
2218 if ctx.description():
2216 return ctx.description()
2219 return ctx.description()
2217 return commitforceeditor(repo, ctx, subs, editform=editform)
2220 return commitforceeditor(repo, ctx, subs, editform=editform)
2218
2221
2219 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2222 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2220 editform=''):
2223 editform=''):
2221 if not extramsg:
2224 if not extramsg:
2222 extramsg = _("Leave message empty to abort commit.")
2225 extramsg = _("Leave message empty to abort commit.")
2223
2226
2224 forms = [e for e in editform.split('.') if e]
2227 forms = [e for e in editform.split('.') if e]
2225 forms.insert(0, 'changeset')
2228 forms.insert(0, 'changeset')
2226 while forms:
2229 while forms:
2227 tmpl = repo.ui.config('committemplate', '.'.join(forms))
2230 tmpl = repo.ui.config('committemplate', '.'.join(forms))
2228 if tmpl:
2231 if tmpl:
2229 committext = buildcommittemplate(repo, ctx, subs, extramsg, tmpl)
2232 committext = buildcommittemplate(repo, ctx, subs, extramsg, tmpl)
2230 break
2233 break
2231 forms.pop()
2234 forms.pop()
2232 else:
2235 else:
2233 committext = buildcommittext(repo, ctx, subs, extramsg)
2236 committext = buildcommittext(repo, ctx, subs, extramsg)
2234
2237
2235 # run editor in the repository root
2238 # run editor in the repository root
2236 olddir = os.getcwd()
2239 olddir = os.getcwd()
2237 os.chdir(repo.root)
2240 os.chdir(repo.root)
2238 text = repo.ui.edit(committext, ctx.user(), ctx.extra(), editform=editform)
2241 text = repo.ui.edit(committext, ctx.user(), ctx.extra(), editform=editform)
2239 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2242 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2240 os.chdir(olddir)
2243 os.chdir(olddir)
2241
2244
2242 if finishdesc:
2245 if finishdesc:
2243 text = finishdesc(text)
2246 text = finishdesc(text)
2244 if not text.strip():
2247 if not text.strip():
2245 raise util.Abort(_("empty commit message"))
2248 raise util.Abort(_("empty commit message"))
2246
2249
2247 return text
2250 return text
2248
2251
2249 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2252 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2250 ui = repo.ui
2253 ui = repo.ui
2251 tmpl, mapfile = gettemplate(ui, tmpl, None)
2254 tmpl, mapfile = gettemplate(ui, tmpl, None)
2252
2255
2253 try:
2256 try:
2254 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2257 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2255 except SyntaxError, inst:
2258 except SyntaxError, inst:
2256 raise util.Abort(inst.args[0])
2259 raise util.Abort(inst.args[0])
2257
2260
2258 for k, v in repo.ui.configitems('committemplate'):
2261 for k, v in repo.ui.configitems('committemplate'):
2259 if k != 'changeset':
2262 if k != 'changeset':
2260 t.t.cache[k] = v
2263 t.t.cache[k] = v
2261
2264
2262 if not extramsg:
2265 if not extramsg:
2263 extramsg = '' # ensure that extramsg is string
2266 extramsg = '' # ensure that extramsg is string
2264
2267
2265 ui.pushbuffer()
2268 ui.pushbuffer()
2266 t.show(ctx, extramsg=extramsg)
2269 t.show(ctx, extramsg=extramsg)
2267 return ui.popbuffer()
2270 return ui.popbuffer()
2268
2271
2269 def buildcommittext(repo, ctx, subs, extramsg):
2272 def buildcommittext(repo, ctx, subs, extramsg):
2270 edittext = []
2273 edittext = []
2271 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2274 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2272 if ctx.description():
2275 if ctx.description():
2273 edittext.append(ctx.description())
2276 edittext.append(ctx.description())
2274 edittext.append("")
2277 edittext.append("")
2275 edittext.append("") # Empty line between message and comments.
2278 edittext.append("") # Empty line between message and comments.
2276 edittext.append(_("HG: Enter commit message."
2279 edittext.append(_("HG: Enter commit message."
2277 " Lines beginning with 'HG:' are removed."))
2280 " Lines beginning with 'HG:' are removed."))
2278 edittext.append("HG: %s" % extramsg)
2281 edittext.append("HG: %s" % extramsg)
2279 edittext.append("HG: --")
2282 edittext.append("HG: --")
2280 edittext.append(_("HG: user: %s") % ctx.user())
2283 edittext.append(_("HG: user: %s") % ctx.user())
2281 if ctx.p2():
2284 if ctx.p2():
2282 edittext.append(_("HG: branch merge"))
2285 edittext.append(_("HG: branch merge"))
2283 if ctx.branch():
2286 if ctx.branch():
2284 edittext.append(_("HG: branch '%s'") % ctx.branch())
2287 edittext.append(_("HG: branch '%s'") % ctx.branch())
2285 if bookmarks.iscurrent(repo):
2288 if bookmarks.iscurrent(repo):
2286 edittext.append(_("HG: bookmark '%s'") % repo._bookmarkcurrent)
2289 edittext.append(_("HG: bookmark '%s'") % repo._bookmarkcurrent)
2287 edittext.extend([_("HG: subrepo %s") % s for s in subs])
2290 edittext.extend([_("HG: subrepo %s") % s for s in subs])
2288 edittext.extend([_("HG: added %s") % f for f in added])
2291 edittext.extend([_("HG: added %s") % f for f in added])
2289 edittext.extend([_("HG: changed %s") % f for f in modified])
2292 edittext.extend([_("HG: changed %s") % f for f in modified])
2290 edittext.extend([_("HG: removed %s") % f for f in removed])
2293 edittext.extend([_("HG: removed %s") % f for f in removed])
2291 if not added and not modified and not removed:
2294 if not added and not modified and not removed:
2292 edittext.append(_("HG: no files changed"))
2295 edittext.append(_("HG: no files changed"))
2293 edittext.append("")
2296 edittext.append("")
2294
2297
2295 return "\n".join(edittext)
2298 return "\n".join(edittext)
2296
2299
2297 def commitstatus(repo, node, branch, bheads=None, opts={}):
2300 def commitstatus(repo, node, branch, bheads=None, opts={}):
2298 ctx = repo[node]
2301 ctx = repo[node]
2299 parents = ctx.parents()
2302 parents = ctx.parents()
2300
2303
2301 if (not opts.get('amend') and bheads and node not in bheads and not
2304 if (not opts.get('amend') and bheads and node not in bheads and not
2302 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2305 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2303 repo.ui.status(_('created new head\n'))
2306 repo.ui.status(_('created new head\n'))
2304 # The message is not printed for initial roots. For the other
2307 # The message is not printed for initial roots. For the other
2305 # changesets, it is printed in the following situations:
2308 # changesets, it is printed in the following situations:
2306 #
2309 #
2307 # Par column: for the 2 parents with ...
2310 # Par column: for the 2 parents with ...
2308 # N: null or no parent
2311 # N: null or no parent
2309 # B: parent is on another named branch
2312 # B: parent is on another named branch
2310 # C: parent is a regular non head changeset
2313 # C: parent is a regular non head changeset
2311 # H: parent was a branch head of the current branch
2314 # H: parent was a branch head of the current branch
2312 # Msg column: whether we print "created new head" message
2315 # Msg column: whether we print "created new head" message
2313 # In the following, it is assumed that there already exists some
2316 # In the following, it is assumed that there already exists some
2314 # initial branch heads of the current branch, otherwise nothing is
2317 # initial branch heads of the current branch, otherwise nothing is
2315 # printed anyway.
2318 # printed anyway.
2316 #
2319 #
2317 # Par Msg Comment
2320 # Par Msg Comment
2318 # N N y additional topo root
2321 # N N y additional topo root
2319 #
2322 #
2320 # B N y additional branch root
2323 # B N y additional branch root
2321 # C N y additional topo head
2324 # C N y additional topo head
2322 # H N n usual case
2325 # H N n usual case
2323 #
2326 #
2324 # B B y weird additional branch root
2327 # B B y weird additional branch root
2325 # C B y branch merge
2328 # C B y branch merge
2326 # H B n merge with named branch
2329 # H B n merge with named branch
2327 #
2330 #
2328 # C C y additional head from merge
2331 # C C y additional head from merge
2329 # C H n merge with a head
2332 # C H n merge with a head
2330 #
2333 #
2331 # H H n head merge: head count decreases
2334 # H H n head merge: head count decreases
2332
2335
2333 if not opts.get('close_branch'):
2336 if not opts.get('close_branch'):
2334 for r in parents:
2337 for r in parents:
2335 if r.closesbranch() and r.branch() == branch:
2338 if r.closesbranch() and r.branch() == branch:
2336 repo.ui.status(_('reopening closed branch head %d\n') % r)
2339 repo.ui.status(_('reopening closed branch head %d\n') % r)
2337
2340
2338 if repo.ui.debugflag:
2341 if repo.ui.debugflag:
2339 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2342 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2340 elif repo.ui.verbose:
2343 elif repo.ui.verbose:
2341 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2344 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2342
2345
2343 def revert(ui, repo, ctx, parents, *pats, **opts):
2346 def revert(ui, repo, ctx, parents, *pats, **opts):
2344 parent, p2 = parents
2347 parent, p2 = parents
2345 node = ctx.node()
2348 node = ctx.node()
2346
2349
2347 mf = ctx.manifest()
2350 mf = ctx.manifest()
2348 if node == p2:
2351 if node == p2:
2349 parent = p2
2352 parent = p2
2350 if node == parent:
2353 if node == parent:
2351 pmf = mf
2354 pmf = mf
2352 else:
2355 else:
2353 pmf = None
2356 pmf = None
2354
2357
2355 # need all matching names in dirstate and manifest of target rev,
2358 # need all matching names in dirstate and manifest of target rev,
2356 # so have to walk both. do not print errors if files exist in one
2359 # so have to walk both. do not print errors if files exist in one
2357 # but not other.
2360 # but not other.
2358
2361
2359 # `names` is a mapping for all elements in working copy and target revision
2362 # `names` is a mapping for all elements in working copy and target revision
2360 # The mapping is in the form:
2363 # The mapping is in the form:
2361 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2364 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2362 names = {}
2365 names = {}
2363
2366
2364 wlock = repo.wlock()
2367 wlock = repo.wlock()
2365 try:
2368 try:
2366 ## filling of the `names` mapping
2369 ## filling of the `names` mapping
2367 # walk dirstate to fill `names`
2370 # walk dirstate to fill `names`
2368
2371
2369 m = scmutil.match(repo[None], pats, opts)
2372 m = scmutil.match(repo[None], pats, opts)
2370 m.bad = lambda x, y: False
2373 m.bad = lambda x, y: False
2371 for abs in repo.walk(m):
2374 for abs in repo.walk(m):
2372 names[abs] = m.rel(abs), m.exact(abs)
2375 names[abs] = m.rel(abs), m.exact(abs)
2373
2376
2374 # walk target manifest to fill `names`
2377 # walk target manifest to fill `names`
2375
2378
2376 def badfn(path, msg):
2379 def badfn(path, msg):
2377 if path in names:
2380 if path in names:
2378 return
2381 return
2379 if path in ctx.substate:
2382 if path in ctx.substate:
2380 return
2383 return
2381 path_ = path + '/'
2384 path_ = path + '/'
2382 for f in names:
2385 for f in names:
2383 if f.startswith(path_):
2386 if f.startswith(path_):
2384 return
2387 return
2385 ui.warn("%s: %s\n" % (m.rel(path), msg))
2388 ui.warn("%s: %s\n" % (m.rel(path), msg))
2386
2389
2387 m = scmutil.match(ctx, pats, opts)
2390 m = scmutil.match(ctx, pats, opts)
2388 m.bad = badfn
2391 m.bad = badfn
2389 for abs in ctx.walk(m):
2392 for abs in ctx.walk(m):
2390 if abs not in names:
2393 if abs not in names:
2391 names[abs] = m.rel(abs), m.exact(abs)
2394 names[abs] = m.rel(abs), m.exact(abs)
2392
2395
2393 # get the list of subrepos that must be reverted
2396 # get the list of subrepos that must be reverted
2394 targetsubs = sorted(s for s in ctx.substate if m(s))
2397 targetsubs = sorted(s for s in ctx.substate if m(s))
2395
2398
2396 # Find status of all file in `names`.
2399 # Find status of all file in `names`.
2397 m = scmutil.matchfiles(repo, names)
2400 m = scmutil.matchfiles(repo, names)
2398
2401
2399 changes = repo.status(node1=node, match=m,
2402 changes = repo.status(node1=node, match=m,
2400 unknown=True, ignored=True, clean=True)
2403 unknown=True, ignored=True, clean=True)
2401 modified = set(changes[0])
2404 modified = set(changes[0])
2402 added = set(changes[1])
2405 added = set(changes[1])
2403 removed = set(changes[2])
2406 removed = set(changes[2])
2404 _deleted = set(changes[3])
2407 _deleted = set(changes[3])
2405 unknown = set(changes[4])
2408 unknown = set(changes[4])
2406 unknown.update(changes[5])
2409 unknown.update(changes[5])
2407 clean = set(changes[6])
2410 clean = set(changes[6])
2408
2411
2409 # split between files known in target manifest and the others
2412 # split between files known in target manifest and the others
2410 smf = set(mf)
2413 smf = set(mf)
2411
2414
2412 # determine the exact nature of the deleted changesets
2415 # determine the exact nature of the deleted changesets
2413 _deletedadded = _deleted - smf
2416 _deletedadded = _deleted - smf
2414 _deletedmodified = _deleted - _deletedadded
2417 _deletedmodified = _deleted - _deletedadded
2415 added |= _deletedadded
2418 added |= _deletedadded
2416 modified |= _deletedmodified
2419 modified |= _deletedmodified
2417
2420
2418 # We need to account for the state of file in the dirstate
2421 # We need to account for the state of file in the dirstate
2419 #
2422 #
2420 # Even, when we revert agains something else than parent. this will
2423 # Even, when we revert agains something else than parent. this will
2421 # slightly alter the behavior of revert (doing back up or not, delete
2424 # slightly alter the behavior of revert (doing back up or not, delete
2422 # or just forget etc)
2425 # or just forget etc)
2423 if parent == node:
2426 if parent == node:
2424 dsmodified = modified
2427 dsmodified = modified
2425 dsadded = added
2428 dsadded = added
2426 dsremoved = removed
2429 dsremoved = removed
2427 modified, added, removed = set(), set(), set()
2430 modified, added, removed = set(), set(), set()
2428 else:
2431 else:
2429 changes = repo.status(node1=parent, match=m)
2432 changes = repo.status(node1=parent, match=m)
2430 dsmodified = set(changes[0])
2433 dsmodified = set(changes[0])
2431 dsadded = set(changes[1])
2434 dsadded = set(changes[1])
2432 dsremoved = set(changes[2])
2435 dsremoved = set(changes[2])
2433
2436
2434 # only take into account for removes between wc and target
2437 # only take into account for removes between wc and target
2435 clean |= dsremoved - removed
2438 clean |= dsremoved - removed
2436 dsremoved &= removed
2439 dsremoved &= removed
2437 # distinct between dirstate remove and other
2440 # distinct between dirstate remove and other
2438 removed -= dsremoved
2441 removed -= dsremoved
2439
2442
2440 # tell newly modified apart.
2443 # tell newly modified apart.
2441 dsmodified &= modified
2444 dsmodified &= modified
2442 dsmodified |= modified & dsadded # dirstate added may needs backup
2445 dsmodified |= modified & dsadded # dirstate added may needs backup
2443 modified -= dsmodified
2446 modified -= dsmodified
2444
2447
2445 # There are three categories of added files
2448 # There are three categories of added files
2446 #
2449 #
2447 # 1. addition that just happened in the dirstate
2450 # 1. addition that just happened in the dirstate
2448 # (should be forgotten)
2451 # (should be forgotten)
2449 # 2. file is added since target revision and has local changes
2452 # 2. file is added since target revision and has local changes
2450 # (should be backed up and removed)
2453 # (should be backed up and removed)
2451 # 3. file is added since target revision and is clean
2454 # 3. file is added since target revision and is clean
2452 # (should be removed)
2455 # (should be removed)
2453 #
2456 #
2454 # However we do not need to split them yet. The current revert code
2457 # However we do not need to split them yet. The current revert code
2455 # will automatically recognize (1) when performing operation. And
2458 # will automatically recognize (1) when performing operation. And
2456 # the backup system is currently unabled to handle (2).
2459 # the backup system is currently unabled to handle (2).
2457 #
2460 #
2458 # So we just put them all in the same group.
2461 # So we just put them all in the same group.
2459 dsadded = added
2462 dsadded = added
2460
2463
2461 # in case of merge, files that are actually added can be reported as
2464 # in case of merge, files that are actually added can be reported as
2462 # modified, we need to post process the result
2465 # modified, we need to post process the result
2463 if p2 != nullid:
2466 if p2 != nullid:
2464 if pmf is None:
2467 if pmf is None:
2465 # only need parent manifest in the merge case,
2468 # only need parent manifest in the merge case,
2466 # so do not read by default
2469 # so do not read by default
2467 pmf = repo[parent].manifest()
2470 pmf = repo[parent].manifest()
2468 mergeadd = dsmodified - set(pmf)
2471 mergeadd = dsmodified - set(pmf)
2469 dsadded |= mergeadd
2472 dsadded |= mergeadd
2470 dsmodified -= mergeadd
2473 dsmodified -= mergeadd
2471
2474
2472 # if f is a rename, update `names` to also revert the source
2475 # if f is a rename, update `names` to also revert the source
2473 cwd = repo.getcwd()
2476 cwd = repo.getcwd()
2474 for f in dsadded:
2477 for f in dsadded:
2475 src = repo.dirstate.copied(f)
2478 src = repo.dirstate.copied(f)
2476 # XXX should we check for rename down to target node?
2479 # XXX should we check for rename down to target node?
2477 if src and src not in names and repo.dirstate[src] == 'r':
2480 if src and src not in names and repo.dirstate[src] == 'r':
2478 dsremoved.add(src)
2481 dsremoved.add(src)
2479 names[src] = (repo.pathto(src, cwd), True)
2482 names[src] = (repo.pathto(src, cwd), True)
2480
2483
2481 ## computation of the action to performs on `names` content.
2484 ## computation of the action to performs on `names` content.
2482
2485
2483 def removeforget(abs):
2486 def removeforget(abs):
2484 if repo.dirstate[abs] == 'a':
2487 if repo.dirstate[abs] == 'a':
2485 return _('forgetting %s\n')
2488 return _('forgetting %s\n')
2486 return _('removing %s\n')
2489 return _('removing %s\n')
2487
2490
2488 # action to be actually performed by revert
2491 # action to be actually performed by revert
2489 # (<list of file>, message>) tuple
2492 # (<list of file>, message>) tuple
2490 actions = {'revert': ([], _('reverting %s\n')),
2493 actions = {'revert': ([], _('reverting %s\n')),
2491 'add': ([], _('adding %s\n')),
2494 'add': ([], _('adding %s\n')),
2492 'remove': ([], removeforget),
2495 'remove': ([], removeforget),
2493 'undelete': ([], _('undeleting %s\n')),
2496 'undelete': ([], _('undeleting %s\n')),
2494 'noop': (None, _('no changes needed to %s\n')),
2497 'noop': (None, _('no changes needed to %s\n')),
2495 'unknown': (None, _('file not managed: %s\n')),
2498 'unknown': (None, _('file not managed: %s\n')),
2496 }
2499 }
2497
2500
2498
2501
2499 # should we do a backup?
2502 # should we do a backup?
2500 backup = not opts.get('no_backup')
2503 backup = not opts.get('no_backup')
2501 discard = False
2504 discard = False
2502
2505
2503 disptable = (
2506 disptable = (
2504 # dispatch table:
2507 # dispatch table:
2505 # file state
2508 # file state
2506 # action
2509 # action
2507 # make backup
2510 # make backup
2508 (modified, actions['revert'], discard),
2511 (modified, actions['revert'], discard),
2509 (dsmodified, actions['revert'], backup),
2512 (dsmodified, actions['revert'], backup),
2510 (dsadded, actions['remove'], backup),
2513 (dsadded, actions['remove'], backup),
2511 (removed, actions['add'], backup),
2514 (removed, actions['add'], backup),
2512 (dsremoved, actions['undelete'], backup),
2515 (dsremoved, actions['undelete'], backup),
2513 (clean, actions['noop'], discard),
2516 (clean, actions['noop'], discard),
2514 (unknown, actions['unknown'], discard),
2517 (unknown, actions['unknown'], discard),
2515 )
2518 )
2516
2519
2517 for abs, (rel, exact) in sorted(names.items()):
2520 for abs, (rel, exact) in sorted(names.items()):
2518 # target file to be touch on disk (relative to cwd)
2521 # target file to be touch on disk (relative to cwd)
2519 target = repo.wjoin(abs)
2522 target = repo.wjoin(abs)
2520 # search the entry in the dispatch table.
2523 # search the entry in the dispatch table.
2521 # if the file is in any of these sets, it was touched in the working
2524 # if the file is in any of these sets, it was touched in the working
2522 # directory parent and we are sure it needs to be reverted.
2525 # directory parent and we are sure it needs to be reverted.
2523 for table, (xlist, msg), dobackup in disptable:
2526 for table, (xlist, msg), dobackup in disptable:
2524 if abs not in table:
2527 if abs not in table:
2525 continue
2528 continue
2526 if xlist is not None:
2529 if xlist is not None:
2527 xlist.append(abs)
2530 xlist.append(abs)
2528 if (dobackup and os.path.lexists(target) and
2531 if (dobackup and os.path.lexists(target) and
2529 abs in ctx and repo[None][abs].cmp(ctx[abs])):
2532 abs in ctx and repo[None][abs].cmp(ctx[abs])):
2530 bakname = "%s.orig" % rel
2533 bakname = "%s.orig" % rel
2531 ui.note(_('saving current version of %s as %s\n') %
2534 ui.note(_('saving current version of %s as %s\n') %
2532 (rel, bakname))
2535 (rel, bakname))
2533 if not opts.get('dry_run'):
2536 if not opts.get('dry_run'):
2534 util.rename(target, bakname)
2537 util.rename(target, bakname)
2535 if ui.verbose or not exact:
2538 if ui.verbose or not exact:
2536 if not isinstance(msg, basestring):
2539 if not isinstance(msg, basestring):
2537 msg = msg(abs)
2540 msg = msg(abs)
2538 ui.status(msg % rel)
2541 ui.status(msg % rel)
2539 elif exact:
2542 elif exact:
2540 ui.warn(msg % rel)
2543 ui.warn(msg % rel)
2541 break
2544 break
2542
2545
2543
2546
2544 if not opts.get('dry_run'):
2547 if not opts.get('dry_run'):
2545 _performrevert(repo, parents, ctx, actions)
2548 _performrevert(repo, parents, ctx, actions)
2546
2549
2547 if targetsubs:
2550 if targetsubs:
2548 # Revert the subrepos on the revert list
2551 # Revert the subrepos on the revert list
2549 for sub in targetsubs:
2552 for sub in targetsubs:
2550 ctx.sub(sub).revert(ui, ctx.substate[sub], *pats, **opts)
2553 ctx.sub(sub).revert(ui, ctx.substate[sub], *pats, **opts)
2551 finally:
2554 finally:
2552 wlock.release()
2555 wlock.release()
2553
2556
2554 def _performrevert(repo, parents, ctx, actions):
2557 def _performrevert(repo, parents, ctx, actions):
2555 """function that actually perform all the actions computed for revert
2558 """function that actually perform all the actions computed for revert
2556
2559
2557 This is an independent function to let extension to plug in and react to
2560 This is an independent function to let extension to plug in and react to
2558 the imminent revert.
2561 the imminent revert.
2559
2562
2560 Make sure you have the working directory locked when calling this function.
2563 Make sure you have the working directory locked when calling this function.
2561 """
2564 """
2562 parent, p2 = parents
2565 parent, p2 = parents
2563 node = ctx.node()
2566 node = ctx.node()
2564 def checkout(f):
2567 def checkout(f):
2565 fc = ctx[f]
2568 fc = ctx[f]
2566 repo.wwrite(f, fc.data(), fc.flags())
2569 repo.wwrite(f, fc.data(), fc.flags())
2567
2570
2568 audit_path = pathutil.pathauditor(repo.root)
2571 audit_path = pathutil.pathauditor(repo.root)
2569 for f in actions['remove'][0]:
2572 for f in actions['remove'][0]:
2570 if repo.dirstate[f] == 'a':
2573 if repo.dirstate[f] == 'a':
2571 repo.dirstate.drop(f)
2574 repo.dirstate.drop(f)
2572 continue
2575 continue
2573 audit_path(f)
2576 audit_path(f)
2574 try:
2577 try:
2575 util.unlinkpath(repo.wjoin(f))
2578 util.unlinkpath(repo.wjoin(f))
2576 except OSError:
2579 except OSError:
2577 pass
2580 pass
2578 repo.dirstate.remove(f)
2581 repo.dirstate.remove(f)
2579
2582
2580 normal = None
2583 normal = None
2581 if node == parent:
2584 if node == parent:
2582 # We're reverting to our parent. If possible, we'd like status
2585 # We're reverting to our parent. If possible, we'd like status
2583 # to report the file as clean. We have to use normallookup for
2586 # to report the file as clean. We have to use normallookup for
2584 # merges to avoid losing information about merged/dirty files.
2587 # merges to avoid losing information about merged/dirty files.
2585 if p2 != nullid:
2588 if p2 != nullid:
2586 normal = repo.dirstate.normallookup
2589 normal = repo.dirstate.normallookup
2587 else:
2590 else:
2588 normal = repo.dirstate.normal
2591 normal = repo.dirstate.normal
2589 for f in actions['revert'][0]:
2592 for f in actions['revert'][0]:
2590 checkout(f)
2593 checkout(f)
2591 if normal:
2594 if normal:
2592 normal(f)
2595 normal(f)
2593
2596
2594 for f in actions['add'][0]:
2597 for f in actions['add'][0]:
2595 checkout(f)
2598 checkout(f)
2596 repo.dirstate.add(f)
2599 repo.dirstate.add(f)
2597
2600
2598 normal = repo.dirstate.normallookup
2601 normal = repo.dirstate.normallookup
2599 if node == parent and p2 == nullid:
2602 if node == parent and p2 == nullid:
2600 normal = repo.dirstate.normal
2603 normal = repo.dirstate.normal
2601 for f in actions['undelete'][0]:
2604 for f in actions['undelete'][0]:
2602 checkout(f)
2605 checkout(f)
2603 normal(f)
2606 normal(f)
2604
2607
2605 copied = copies.pathcopies(repo[parent], ctx)
2608 copied = copies.pathcopies(repo[parent], ctx)
2606
2609
2607 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
2610 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
2608 if f in copied:
2611 if f in copied:
2609 repo.dirstate.copy(copied[f], f)
2612 repo.dirstate.copy(copied[f], f)
2610
2613
2611 def command(table):
2614 def command(table):
2612 """Returns a function object to be used as a decorator for making commands.
2615 """Returns a function object to be used as a decorator for making commands.
2613
2616
2614 This function receives a command table as its argument. The table should
2617 This function receives a command table as its argument. The table should
2615 be a dict.
2618 be a dict.
2616
2619
2617 The returned function can be used as a decorator for adding commands
2620 The returned function can be used as a decorator for adding commands
2618 to that command table. This function accepts multiple arguments to define
2621 to that command table. This function accepts multiple arguments to define
2619 a command.
2622 a command.
2620
2623
2621 The first argument is the command name.
2624 The first argument is the command name.
2622
2625
2623 The options argument is an iterable of tuples defining command arguments.
2626 The options argument is an iterable of tuples defining command arguments.
2624 See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
2627 See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
2625
2628
2626 The synopsis argument defines a short, one line summary of how to use the
2629 The synopsis argument defines a short, one line summary of how to use the
2627 command. This shows up in the help output.
2630 command. This shows up in the help output.
2628
2631
2629 The norepo argument defines whether the command does not require a
2632 The norepo argument defines whether the command does not require a
2630 local repository. Most commands operate against a repository, thus the
2633 local repository. Most commands operate against a repository, thus the
2631 default is False.
2634 default is False.
2632
2635
2633 The optionalrepo argument defines whether the command optionally requires
2636 The optionalrepo argument defines whether the command optionally requires
2634 a local repository.
2637 a local repository.
2635
2638
2636 The inferrepo argument defines whether to try to find a repository from the
2639 The inferrepo argument defines whether to try to find a repository from the
2637 command line arguments. If True, arguments will be examined for potential
2640 command line arguments. If True, arguments will be examined for potential
2638 repository locations. See ``findrepo()``. If a repository is found, it
2641 repository locations. See ``findrepo()``. If a repository is found, it
2639 will be used.
2642 will be used.
2640 """
2643 """
2641 def cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False,
2644 def cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False,
2642 inferrepo=False):
2645 inferrepo=False):
2643 def decorator(func):
2646 def decorator(func):
2644 if synopsis:
2647 if synopsis:
2645 table[name] = func, list(options), synopsis
2648 table[name] = func, list(options), synopsis
2646 else:
2649 else:
2647 table[name] = func, list(options)
2650 table[name] = func, list(options)
2648
2651
2649 if norepo:
2652 if norepo:
2650 # Avoid import cycle.
2653 # Avoid import cycle.
2651 import commands
2654 import commands
2652 commands.norepo += ' %s' % ' '.join(parsealiases(name))
2655 commands.norepo += ' %s' % ' '.join(parsealiases(name))
2653
2656
2654 if optionalrepo:
2657 if optionalrepo:
2655 import commands
2658 import commands
2656 commands.optionalrepo += ' %s' % ' '.join(parsealiases(name))
2659 commands.optionalrepo += ' %s' % ' '.join(parsealiases(name))
2657
2660
2658 if inferrepo:
2661 if inferrepo:
2659 import commands
2662 import commands
2660 commands.inferrepo += ' %s' % ' '.join(parsealiases(name))
2663 commands.inferrepo += ' %s' % ' '.join(parsealiases(name))
2661
2664
2662 return func
2665 return func
2663 return decorator
2666 return decorator
2664
2667
2665 return cmd
2668 return cmd
2666
2669
2667 # a list of (ui, repo, otherpeer, opts, missing) functions called by
2670 # a list of (ui, repo, otherpeer, opts, missing) functions called by
2668 # commands.outgoing. "missing" is "missing" of the result of
2671 # commands.outgoing. "missing" is "missing" of the result of
2669 # "findcommonoutgoing()"
2672 # "findcommonoutgoing()"
2670 outgoinghooks = util.hooks()
2673 outgoinghooks = util.hooks()
2671
2674
2672 # a list of (ui, repo) functions called by commands.summary
2675 # a list of (ui, repo) functions called by commands.summary
2673 summaryhooks = util.hooks()
2676 summaryhooks = util.hooks()
2674
2677
2675 # a list of (ui, repo, opts, changes) functions called by commands.summary.
2678 # a list of (ui, repo, opts, changes) functions called by commands.summary.
2676 #
2679 #
2677 # functions should return tuple of booleans below, if 'changes' is None:
2680 # functions should return tuple of booleans below, if 'changes' is None:
2678 # (whether-incomings-are-needed, whether-outgoings-are-needed)
2681 # (whether-incomings-are-needed, whether-outgoings-are-needed)
2679 #
2682 #
2680 # otherwise, 'changes' is a tuple of tuples below:
2683 # otherwise, 'changes' is a tuple of tuples below:
2681 # - (sourceurl, sourcebranch, sourcepeer, incoming)
2684 # - (sourceurl, sourcebranch, sourcepeer, incoming)
2682 # - (desturl, destbranch, destpeer, outgoing)
2685 # - (desturl, destbranch, destpeer, outgoing)
2683 summaryremotehooks = util.hooks()
2686 summaryremotehooks = util.hooks()
2684
2687
2685 # A list of state files kept by multistep operations like graft.
2688 # A list of state files kept by multistep operations like graft.
2686 # Since graft cannot be aborted, it is considered 'clearable' by update.
2689 # Since graft cannot be aborted, it is considered 'clearable' by update.
2687 # note: bisect is intentionally excluded
2690 # note: bisect is intentionally excluded
2688 # (state file, clearable, allowcommit, error, hint)
2691 # (state file, clearable, allowcommit, error, hint)
2689 unfinishedstates = [
2692 unfinishedstates = [
2690 ('graftstate', True, False, _('graft in progress'),
2693 ('graftstate', True, False, _('graft in progress'),
2691 _("use 'hg graft --continue' or 'hg update' to abort")),
2694 _("use 'hg graft --continue' or 'hg update' to abort")),
2692 ('updatestate', True, False, _('last update was interrupted'),
2695 ('updatestate', True, False, _('last update was interrupted'),
2693 _("use 'hg update' to get a consistent checkout"))
2696 _("use 'hg update' to get a consistent checkout"))
2694 ]
2697 ]
2695
2698
2696 def checkunfinished(repo, commit=False):
2699 def checkunfinished(repo, commit=False):
2697 '''Look for an unfinished multistep operation, like graft, and abort
2700 '''Look for an unfinished multistep operation, like graft, and abort
2698 if found. It's probably good to check this right before
2701 if found. It's probably good to check this right before
2699 bailifchanged().
2702 bailifchanged().
2700 '''
2703 '''
2701 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2704 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2702 if commit and allowcommit:
2705 if commit and allowcommit:
2703 continue
2706 continue
2704 if repo.vfs.exists(f):
2707 if repo.vfs.exists(f):
2705 raise util.Abort(msg, hint=hint)
2708 raise util.Abort(msg, hint=hint)
2706
2709
2707 def clearunfinished(repo):
2710 def clearunfinished(repo):
2708 '''Check for unfinished operations (as above), and clear the ones
2711 '''Check for unfinished operations (as above), and clear the ones
2709 that are clearable.
2712 that are clearable.
2710 '''
2713 '''
2711 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2714 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2712 if not clearable and repo.vfs.exists(f):
2715 if not clearable and repo.vfs.exists(f):
2713 raise util.Abort(msg, hint=hint)
2716 raise util.Abort(msg, hint=hint)
2714 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2717 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2715 if clearable and repo.vfs.exists(f):
2718 if clearable and repo.vfs.exists(f):
2716 util.unlink(repo.join(f))
2719 util.unlink(repo.join(f))
@@ -1,463 +1,463
1 $ . "$TESTDIR/histedit-helpers.sh"
1 $ . "$TESTDIR/histedit-helpers.sh"
2
2
3 Enable obsolete
3 Enable obsolete
4
4
5 $ cat > ${TESTTMP}/obs.py << EOF
5 $ cat > ${TESTTMP}/obs.py << EOF
6 > import mercurial.obsolete
6 > import mercurial.obsolete
7 > mercurial.obsolete._enabled = True
7 > mercurial.obsolete._enabled = True
8 > EOF
8 > EOF
9
9
10 $ cat >> $HGRCPATH << EOF
10 $ cat >> $HGRCPATH << EOF
11 > [ui]
11 > [ui]
12 > logtemplate= {rev}:{node|short} {desc|firstline}
12 > logtemplate= {rev}:{node|short} {desc|firstline}
13 > [phases]
13 > [phases]
14 > publish=False
14 > publish=False
15 > [extensions]'
15 > [extensions]'
16 > histedit=
16 > histedit=
17 > rebase=
17 > rebase=
18 >
18 >
19 > obs=${TESTTMP}/obs.py
19 > obs=${TESTTMP}/obs.py
20 > EOF
20 > EOF
21
21
22 $ hg init base
22 $ hg init base
23 $ cd base
23 $ cd base
24
24
25 $ for x in a b c d e f ; do
25 $ for x in a b c d e f ; do
26 > echo $x > $x
26 > echo $x > $x
27 > hg add $x
27 > hg add $x
28 > hg ci -m $x
28 > hg ci -m $x
29 > done
29 > done
30
30
31 $ hg log --graph
31 $ hg log --graph
32 @ 5:652413bf663e f
32 @ 5:652413bf663e f
33 |
33 |
34 o 4:e860deea161a e
34 o 4:e860deea161a e
35 |
35 |
36 o 3:055a42cdd887 d
36 o 3:055a42cdd887 d
37 |
37 |
38 o 2:177f92b77385 c
38 o 2:177f92b77385 c
39 |
39 |
40 o 1:d2ae7f538514 b
40 o 1:d2ae7f538514 b
41 |
41 |
42 o 0:cb9a9f314b8b a
42 o 0:cb9a9f314b8b a
43
43
44
44
45 $ HGEDITOR=cat hg histedit 1
45 $ HGEDITOR=cat hg histedit 1
46 pick d2ae7f538514 1 b
46 pick d2ae7f538514 1 b
47 pick 177f92b77385 2 c
47 pick 177f92b77385 2 c
48 pick 055a42cdd887 3 d
48 pick 055a42cdd887 3 d
49 pick e860deea161a 4 e
49 pick e860deea161a 4 e
50 pick 652413bf663e 5 f
50 pick 652413bf663e 5 f
51
51
52 # Edit history between d2ae7f538514 and 652413bf663e
52 # Edit history between d2ae7f538514 and 652413bf663e
53 #
53 #
54 # Commits are listed from least to most recent
54 # Commits are listed from least to most recent
55 #
55 #
56 # Commands:
56 # Commands:
57 # p, pick = use commit
57 # p, pick = use commit
58 # e, edit = use commit, but stop for amending
58 # e, edit = use commit, but stop for amending
59 # f, fold = use commit, but combine it with the one above
59 # f, fold = use commit, but combine it with the one above
60 # r, roll = like fold, but discard this commit's description
60 # r, roll = like fold, but discard this commit's description
61 # d, drop = remove commit from history
61 # d, drop = remove commit from history
62 # m, mess = edit message without changing commit content
62 # m, mess = edit message without changing commit content
63 #
63 #
64 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
64 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
65 $ hg histedit 1 --commands - --verbose <<EOF | grep histedit
65 $ hg histedit 1 --commands - --verbose <<EOF | grep histedit
66 > pick 177f92b77385 2 c
66 > pick 177f92b77385 2 c
67 > drop d2ae7f538514 1 b
67 > drop d2ae7f538514 1 b
68 > pick 055a42cdd887 3 d
68 > pick 055a42cdd887 3 d
69 > fold e860deea161a 4 e
69 > fold e860deea161a 4 e
70 > pick 652413bf663e 5 f
70 > pick 652413bf663e 5 f
71 > EOF
71 > EOF
72 saved backup bundle to $TESTTMP/base/.hg/strip-backup/96e494a2d553-backup.hg (glob)
72 saved backup bundle to $TESTTMP/base/.hg/strip-backup/96e494a2d553-backup.hg (glob)
73 $ hg log --graph --hidden
73 $ hg log --graph --hidden
74 @ 8:cacdfd884a93 f
74 @ 8:cacdfd884a93 f
75 |
75 |
76 o 7:59d9f330561f d
76 o 7:59d9f330561f d
77 |
77 |
78 o 6:b346ab9a313d c
78 o 6:b346ab9a313d c
79 |
79 |
80 | x 5:652413bf663e f
80 | x 5:652413bf663e f
81 | |
81 | |
82 | x 4:e860deea161a e
82 | x 4:e860deea161a e
83 | |
83 | |
84 | x 3:055a42cdd887 d
84 | x 3:055a42cdd887 d
85 | |
85 | |
86 | x 2:177f92b77385 c
86 | x 2:177f92b77385 c
87 | |
87 | |
88 | x 1:d2ae7f538514 b
88 | x 1:d2ae7f538514 b
89 |/
89 |/
90 o 0:cb9a9f314b8b a
90 o 0:cb9a9f314b8b a
91
91
92 $ hg debugobsolete
92 $ hg debugobsolete
93 d2ae7f538514cd87c17547b0de4cea71fe1af9fb 0 (*) {'user': 'test'} (glob)
93 d2ae7f538514cd87c17547b0de4cea71fe1af9fb 0 {cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b} (*) {'user': 'test'} (glob)
94 177f92b773850b59254aa5e923436f921b55483b b346ab9a313db8537ecf96fca3ca3ca984ef3bd7 0 (*) {'user': 'test'} (glob)
94 177f92b773850b59254aa5e923436f921b55483b b346ab9a313db8537ecf96fca3ca3ca984ef3bd7 0 (*) {'user': 'test'} (glob)
95 055a42cdd88768532f9cf79daa407fc8d138de9b 59d9f330561fd6c88b1a6b32f0e45034d88db784 0 (*) {'user': 'test'} (glob)
95 055a42cdd88768532f9cf79daa407fc8d138de9b 59d9f330561fd6c88b1a6b32f0e45034d88db784 0 (*) {'user': 'test'} (glob)
96 e860deea161a2f77de56603b340ebbb4536308ae 59d9f330561fd6c88b1a6b32f0e45034d88db784 0 (*) {'user': 'test'} (glob)
96 e860deea161a2f77de56603b340ebbb4536308ae 59d9f330561fd6c88b1a6b32f0e45034d88db784 0 (*) {'user': 'test'} (glob)
97 652413bf663ef2a641cab26574e46d5f5a64a55a cacdfd884a9321ec4e1de275ef3949fa953a1f83 0 (*) {'user': 'test'} (glob)
97 652413bf663ef2a641cab26574e46d5f5a64a55a cacdfd884a9321ec4e1de275ef3949fa953a1f83 0 (*) {'user': 'test'} (glob)
98
98
99
99
100 Ensure hidden revision does not prevent histedit
100 Ensure hidden revision does not prevent histedit
101 -------------------------------------------------
101 -------------------------------------------------
102
102
103 create an hidden revision
103 create an hidden revision
104
104
105 $ hg histedit 6 --commands - << EOF
105 $ hg histedit 6 --commands - << EOF
106 > pick b346ab9a313d 6 c
106 > pick b346ab9a313d 6 c
107 > drop 59d9f330561f 7 d
107 > drop 59d9f330561f 7 d
108 > pick cacdfd884a93 8 f
108 > pick cacdfd884a93 8 f
109 > EOF
109 > EOF
110 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
110 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
111 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
111 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
112 $ hg log --graph
112 $ hg log --graph
113 @ 9:c13eb81022ca f
113 @ 9:c13eb81022ca f
114 |
114 |
115 o 6:b346ab9a313d c
115 o 6:b346ab9a313d c
116 |
116 |
117 o 0:cb9a9f314b8b a
117 o 0:cb9a9f314b8b a
118
118
119 check hidden revision are ignored (6 have hidden children 7 and 8)
119 check hidden revision are ignored (6 have hidden children 7 and 8)
120
120
121 $ hg histedit 6 --commands - << EOF
121 $ hg histedit 6 --commands - << EOF
122 > pick b346ab9a313d 6 c
122 > pick b346ab9a313d 6 c
123 > pick c13eb81022ca 8 f
123 > pick c13eb81022ca 8 f
124 > EOF
124 > EOF
125 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
125 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
126
126
127
127
128
128
129 Test that rewriting leaving instability behind is allowed
129 Test that rewriting leaving instability behind is allowed
130 ---------------------------------------------------------------------
130 ---------------------------------------------------------------------
131
131
132 $ hg up '.^'
132 $ hg up '.^'
133 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
133 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
134 $ hg log -r 'children(.)'
134 $ hg log -r 'children(.)'
135 9:c13eb81022ca f (no-eol)
135 9:c13eb81022ca f (no-eol)
136 $ hg histedit -r '.' --commands - <<EOF
136 $ hg histedit -r '.' --commands - <<EOF
137 > edit b346ab9a313d 6 c
137 > edit b346ab9a313d 6 c
138 > EOF
138 > EOF
139 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
139 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
140 adding c
140 adding c
141 Make changes as needed, you may commit or record as needed now.
141 Make changes as needed, you may commit or record as needed now.
142 When you are finished, run hg histedit --continue to resume.
142 When you are finished, run hg histedit --continue to resume.
143 [1]
143 [1]
144 $ echo c >> c
144 $ echo c >> c
145 $ hg histedit --continue
145 $ hg histedit --continue
146 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
146 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
147
147
148 $ hg log -r 'unstable()'
148 $ hg log -r 'unstable()'
149 9:c13eb81022ca f (no-eol)
149 9:c13eb81022ca f (no-eol)
150
150
151 stabilise
151 stabilise
152
152
153 $ hg rebase -r 'unstable()' -d .
153 $ hg rebase -r 'unstable()' -d .
154 $ hg up tip -q
154 $ hg up tip -q
155
155
156 Test dropping of changeset on the top of the stack
156 Test dropping of changeset on the top of the stack
157 -------------------------------------------------------
157 -------------------------------------------------------
158
158
159 Nothing is rewritten below, the working directory parent must be change for the
159 Nothing is rewritten below, the working directory parent must be change for the
160 dropped changeset to be hidden.
160 dropped changeset to be hidden.
161
161
162 $ cd ..
162 $ cd ..
163 $ hg clone base droplast
163 $ hg clone base droplast
164 updating to branch default
164 updating to branch default
165 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
165 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
166 $ cd droplast
166 $ cd droplast
167 $ hg histedit -r '40db8afa467b' --commands - << EOF
167 $ hg histedit -r '40db8afa467b' --commands - << EOF
168 > pick 40db8afa467b 10 c
168 > pick 40db8afa467b 10 c
169 > drop b449568bf7fc 11 f
169 > drop b449568bf7fc 11 f
170 > EOF
170 > EOF
171 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
171 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
172 $ hg log -G
172 $ hg log -G
173 @ 10:40db8afa467b c
173 @ 10:40db8afa467b c
174 |
174 |
175 o 0:cb9a9f314b8b a
175 o 0:cb9a9f314b8b a
176
176
177
177
178 With rewritten ancestors
178 With rewritten ancestors
179
179
180 $ echo e > e
180 $ echo e > e
181 $ hg add e
181 $ hg add e
182 $ hg commit -m g
182 $ hg commit -m g
183 $ echo f > f
183 $ echo f > f
184 $ hg add f
184 $ hg add f
185 $ hg commit -m h
185 $ hg commit -m h
186 $ hg histedit -r '40db8afa467b' --commands - << EOF
186 $ hg histedit -r '40db8afa467b' --commands - << EOF
187 > pick 47a8561c0449 12 g
187 > pick 47a8561c0449 12 g
188 > pick 40db8afa467b 10 c
188 > pick 40db8afa467b 10 c
189 > drop 1b3b05f35ff0 13 h
189 > drop 1b3b05f35ff0 13 h
190 > EOF
190 > EOF
191 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
191 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
192 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
192 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
193 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
193 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
194 $ hg log -G
194 $ hg log -G
195 @ 15:ee6544123ab8 c
195 @ 15:ee6544123ab8 c
196 |
196 |
197 o 14:269e713e9eae g
197 o 14:269e713e9eae g
198 |
198 |
199 o 0:cb9a9f314b8b a
199 o 0:cb9a9f314b8b a
200
200
201 $ cd ../base
201 $ cd ../base
202
202
203
203
204
204
205 Test phases support
205 Test phases support
206 ===========================================
206 ===========================================
207
207
208 Check that histedit respect immutability
208 Check that histedit respect immutability
209 -------------------------------------------
209 -------------------------------------------
210
210
211 $ cat >> $HGRCPATH << EOF
211 $ cat >> $HGRCPATH << EOF
212 > [ui]
212 > [ui]
213 > logtemplate= {rev}:{node|short} ({phase}) {desc|firstline}\n
213 > logtemplate= {rev}:{node|short} ({phase}) {desc|firstline}\n
214 > EOF
214 > EOF
215
215
216 $ hg ph -pv '.^'
216 $ hg ph -pv '.^'
217 phase changed for 2 changesets
217 phase changed for 2 changesets
218 $ hg log -G
218 $ hg log -G
219 @ 11:b449568bf7fc (draft) f
219 @ 11:b449568bf7fc (draft) f
220 |
220 |
221 o 10:40db8afa467b (public) c
221 o 10:40db8afa467b (public) c
222 |
222 |
223 o 0:cb9a9f314b8b (public) a
223 o 0:cb9a9f314b8b (public) a
224
224
225 $ hg histedit -r '.~2'
225 $ hg histedit -r '.~2'
226 abort: cannot edit immutable changeset: cb9a9f314b8b
226 abort: cannot edit immutable changeset: cb9a9f314b8b
227 [255]
227 [255]
228
228
229
229
230 Prepare further testing
230 Prepare further testing
231 -------------------------------------------
231 -------------------------------------------
232
232
233 $ for x in g h i j k ; do
233 $ for x in g h i j k ; do
234 > echo $x > $x
234 > echo $x > $x
235 > hg add $x
235 > hg add $x
236 > hg ci -m $x
236 > hg ci -m $x
237 > done
237 > done
238 $ hg phase --force --secret .~2
238 $ hg phase --force --secret .~2
239 $ hg log -G
239 $ hg log -G
240 @ 16:ee118ab9fa44 (secret) k
240 @ 16:ee118ab9fa44 (secret) k
241 |
241 |
242 o 15:3a6c53ee7f3d (secret) j
242 o 15:3a6c53ee7f3d (secret) j
243 |
243 |
244 o 14:b605fb7503f2 (secret) i
244 o 14:b605fb7503f2 (secret) i
245 |
245 |
246 o 13:7395e1ff83bd (draft) h
246 o 13:7395e1ff83bd (draft) h
247 |
247 |
248 o 12:6b70183d2492 (draft) g
248 o 12:6b70183d2492 (draft) g
249 |
249 |
250 o 11:b449568bf7fc (draft) f
250 o 11:b449568bf7fc (draft) f
251 |
251 |
252 o 10:40db8afa467b (public) c
252 o 10:40db8afa467b (public) c
253 |
253 |
254 o 0:cb9a9f314b8b (public) a
254 o 0:cb9a9f314b8b (public) a
255
255
256 $ cd ..
256 $ cd ..
257
257
258 simple phase conservation
258 simple phase conservation
259 -------------------------------------------
259 -------------------------------------------
260
260
261 Resulting changeset should conserve the phase of the original one whatever the
261 Resulting changeset should conserve the phase of the original one whatever the
262 phases.new-commit option is.
262 phases.new-commit option is.
263
263
264 New-commit as draft (default)
264 New-commit as draft (default)
265
265
266 $ cp -r base simple-draft
266 $ cp -r base simple-draft
267 $ cd simple-draft
267 $ cd simple-draft
268 $ hg histedit -r 'b449568bf7fc' --commands - << EOF
268 $ hg histedit -r 'b449568bf7fc' --commands - << EOF
269 > edit b449568bf7fc 11 f
269 > edit b449568bf7fc 11 f
270 > pick 6b70183d2492 12 g
270 > pick 6b70183d2492 12 g
271 > pick 7395e1ff83bd 13 h
271 > pick 7395e1ff83bd 13 h
272 > pick b605fb7503f2 14 i
272 > pick b605fb7503f2 14 i
273 > pick 3a6c53ee7f3d 15 j
273 > pick 3a6c53ee7f3d 15 j
274 > pick ee118ab9fa44 16 k
274 > pick ee118ab9fa44 16 k
275 > EOF
275 > EOF
276 0 files updated, 0 files merged, 6 files removed, 0 files unresolved
276 0 files updated, 0 files merged, 6 files removed, 0 files unresolved
277 adding f
277 adding f
278 Make changes as needed, you may commit or record as needed now.
278 Make changes as needed, you may commit or record as needed now.
279 When you are finished, run hg histedit --continue to resume.
279 When you are finished, run hg histedit --continue to resume.
280 [1]
280 [1]
281 $ echo f >> f
281 $ echo f >> f
282 $ hg histedit --continue
282 $ hg histedit --continue
283 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
283 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
284 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
284 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
285 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
285 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
286 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
286 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
287 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
287 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
288 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
288 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
289 $ hg log -G
289 $ hg log -G
290 @ 22:12e89af74238 (secret) k
290 @ 22:12e89af74238 (secret) k
291 |
291 |
292 o 21:636a8687b22e (secret) j
292 o 21:636a8687b22e (secret) j
293 |
293 |
294 o 20:ccaf0a38653f (secret) i
294 o 20:ccaf0a38653f (secret) i
295 |
295 |
296 o 19:11a89d1c2613 (draft) h
296 o 19:11a89d1c2613 (draft) h
297 |
297 |
298 o 18:c1dec7ca82ea (draft) g
298 o 18:c1dec7ca82ea (draft) g
299 |
299 |
300 o 17:087281e68428 (draft) f
300 o 17:087281e68428 (draft) f
301 |
301 |
302 o 10:40db8afa467b (public) c
302 o 10:40db8afa467b (public) c
303 |
303 |
304 o 0:cb9a9f314b8b (public) a
304 o 0:cb9a9f314b8b (public) a
305
305
306 $ cd ..
306 $ cd ..
307
307
308
308
309 New-commit as draft (default)
309 New-commit as draft (default)
310
310
311 $ cp -r base simple-secret
311 $ cp -r base simple-secret
312 $ cd simple-secret
312 $ cd simple-secret
313 $ cat >> .hg/hgrc << EOF
313 $ cat >> .hg/hgrc << EOF
314 > [phases]
314 > [phases]
315 > new-commit=secret
315 > new-commit=secret
316 > EOF
316 > EOF
317 $ hg histedit -r 'b449568bf7fc' --commands - << EOF
317 $ hg histedit -r 'b449568bf7fc' --commands - << EOF
318 > edit b449568bf7fc 11 f
318 > edit b449568bf7fc 11 f
319 > pick 6b70183d2492 12 g
319 > pick 6b70183d2492 12 g
320 > pick 7395e1ff83bd 13 h
320 > pick 7395e1ff83bd 13 h
321 > pick b605fb7503f2 14 i
321 > pick b605fb7503f2 14 i
322 > pick 3a6c53ee7f3d 15 j
322 > pick 3a6c53ee7f3d 15 j
323 > pick ee118ab9fa44 16 k
323 > pick ee118ab9fa44 16 k
324 > EOF
324 > EOF
325 0 files updated, 0 files merged, 6 files removed, 0 files unresolved
325 0 files updated, 0 files merged, 6 files removed, 0 files unresolved
326 adding f
326 adding f
327 Make changes as needed, you may commit or record as needed now.
327 Make changes as needed, you may commit or record as needed now.
328 When you are finished, run hg histedit --continue to resume.
328 When you are finished, run hg histedit --continue to resume.
329 [1]
329 [1]
330 $ echo f >> f
330 $ echo f >> f
331 $ hg histedit --continue
331 $ hg histedit --continue
332 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
332 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
333 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
333 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
334 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
334 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
335 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
335 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
336 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
336 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
337 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
337 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
338 $ hg log -G
338 $ hg log -G
339 @ 22:12e89af74238 (secret) k
339 @ 22:12e89af74238 (secret) k
340 |
340 |
341 o 21:636a8687b22e (secret) j
341 o 21:636a8687b22e (secret) j
342 |
342 |
343 o 20:ccaf0a38653f (secret) i
343 o 20:ccaf0a38653f (secret) i
344 |
344 |
345 o 19:11a89d1c2613 (draft) h
345 o 19:11a89d1c2613 (draft) h
346 |
346 |
347 o 18:c1dec7ca82ea (draft) g
347 o 18:c1dec7ca82ea (draft) g
348 |
348 |
349 o 17:087281e68428 (draft) f
349 o 17:087281e68428 (draft) f
350 |
350 |
351 o 10:40db8afa467b (public) c
351 o 10:40db8afa467b (public) c
352 |
352 |
353 o 0:cb9a9f314b8b (public) a
353 o 0:cb9a9f314b8b (public) a
354
354
355 $ cd ..
355 $ cd ..
356
356
357
357
358 Changeset reordering
358 Changeset reordering
359 -------------------------------------------
359 -------------------------------------------
360
360
361 If a secret changeset is put before a draft one, all descendant should be secret.
361 If a secret changeset is put before a draft one, all descendant should be secret.
362 It seems more important to present the secret phase.
362 It seems more important to present the secret phase.
363
363
364 $ cp -r base reorder
364 $ cp -r base reorder
365 $ cd reorder
365 $ cd reorder
366 $ hg histedit -r 'b449568bf7fc' --commands - << EOF
366 $ hg histedit -r 'b449568bf7fc' --commands - << EOF
367 > pick b449568bf7fc 11 f
367 > pick b449568bf7fc 11 f
368 > pick 3a6c53ee7f3d 15 j
368 > pick 3a6c53ee7f3d 15 j
369 > pick 6b70183d2492 12 g
369 > pick 6b70183d2492 12 g
370 > pick b605fb7503f2 14 i
370 > pick b605fb7503f2 14 i
371 > pick 7395e1ff83bd 13 h
371 > pick 7395e1ff83bd 13 h
372 > pick ee118ab9fa44 16 k
372 > pick ee118ab9fa44 16 k
373 > EOF
373 > EOF
374 0 files updated, 0 files merged, 5 files removed, 0 files unresolved
374 0 files updated, 0 files merged, 5 files removed, 0 files unresolved
375 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
375 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
376 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
376 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
377 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
377 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
378 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
378 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
379 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
379 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
380 $ hg log -G
380 $ hg log -G
381 @ 21:558246857888 (secret) k
381 @ 21:558246857888 (secret) k
382 |
382 |
383 o 20:28bd44768535 (secret) h
383 o 20:28bd44768535 (secret) h
384 |
384 |
385 o 19:d5395202aeb9 (secret) i
385 o 19:d5395202aeb9 (secret) i
386 |
386 |
387 o 18:21edda8e341b (secret) g
387 o 18:21edda8e341b (secret) g
388 |
388 |
389 o 17:5ab64f3a4832 (secret) j
389 o 17:5ab64f3a4832 (secret) j
390 |
390 |
391 o 11:b449568bf7fc (draft) f
391 o 11:b449568bf7fc (draft) f
392 |
392 |
393 o 10:40db8afa467b (public) c
393 o 10:40db8afa467b (public) c
394 |
394 |
395 o 0:cb9a9f314b8b (public) a
395 o 0:cb9a9f314b8b (public) a
396
396
397 $ cd ..
397 $ cd ..
398
398
399 Changeset folding
399 Changeset folding
400 -------------------------------------------
400 -------------------------------------------
401
401
402 Folding a secret changeset with a draft one turn the result secret (again,
402 Folding a secret changeset with a draft one turn the result secret (again,
403 better safe than sorry). Folding between same phase changeset still works
403 better safe than sorry). Folding between same phase changeset still works
404
404
405 Note that there is a few reordering in this series for more extensive test
405 Note that there is a few reordering in this series for more extensive test
406
406
407 $ cp -r base folding
407 $ cp -r base folding
408 $ cd folding
408 $ cd folding
409 $ cat >> .hg/hgrc << EOF
409 $ cat >> .hg/hgrc << EOF
410 > [phases]
410 > [phases]
411 > new-commit=secret
411 > new-commit=secret
412 > EOF
412 > EOF
413 $ hg histedit -r 'b449568bf7fc' --commands - << EOF
413 $ hg histedit -r 'b449568bf7fc' --commands - << EOF
414 > pick 7395e1ff83bd 13 h
414 > pick 7395e1ff83bd 13 h
415 > fold b449568bf7fc 11 f
415 > fold b449568bf7fc 11 f
416 > pick 6b70183d2492 12 g
416 > pick 6b70183d2492 12 g
417 > fold 3a6c53ee7f3d 15 j
417 > fold 3a6c53ee7f3d 15 j
418 > pick b605fb7503f2 14 i
418 > pick b605fb7503f2 14 i
419 > fold ee118ab9fa44 16 k
419 > fold ee118ab9fa44 16 k
420 > EOF
420 > EOF
421 0 files updated, 0 files merged, 6 files removed, 0 files unresolved
421 0 files updated, 0 files merged, 6 files removed, 0 files unresolved
422 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
422 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
423 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
423 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
424 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
424 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
425 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
425 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
426 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
426 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
427 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
427 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
428 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
428 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
429 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
429 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
430 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
430 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
431 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
431 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
432 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
432 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
433 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
433 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
434 saved backup bundle to $TESTTMP/folding/.hg/strip-backup/58019c66f35f-backup.hg (glob)
434 saved backup bundle to $TESTTMP/folding/.hg/strip-backup/58019c66f35f-backup.hg (glob)
435 saved backup bundle to $TESTTMP/folding/.hg/strip-backup/83d1858e070b-backup.hg (glob)
435 saved backup bundle to $TESTTMP/folding/.hg/strip-backup/83d1858e070b-backup.hg (glob)
436 saved backup bundle to $TESTTMP/folding/.hg/strip-backup/859969f5ed7e-backup.hg (glob)
436 saved backup bundle to $TESTTMP/folding/.hg/strip-backup/859969f5ed7e-backup.hg (glob)
437 $ hg log -G
437 $ hg log -G
438 @ 19:f9daec13fb98 (secret) i
438 @ 19:f9daec13fb98 (secret) i
439 |
439 |
440 o 18:49807617f46a (secret) g
440 o 18:49807617f46a (secret) g
441 |
441 |
442 o 17:050280826e04 (draft) h
442 o 17:050280826e04 (draft) h
443 |
443 |
444 o 10:40db8afa467b (public) c
444 o 10:40db8afa467b (public) c
445 |
445 |
446 o 0:cb9a9f314b8b (public) a
446 o 0:cb9a9f314b8b (public) a
447
447
448 $ hg co 18
448 $ hg co 18
449 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
449 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
450 $ echo wat >> wat
450 $ echo wat >> wat
451 $ hg add wat
451 $ hg add wat
452 $ hg ci -m 'add wat'
452 $ hg ci -m 'add wat'
453 created new head
453 created new head
454 $ hg merge 19
454 $ hg merge 19
455 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
455 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
456 (branch merge, don't forget to commit)
456 (branch merge, don't forget to commit)
457 $ hg ci -m 'merge'
457 $ hg ci -m 'merge'
458 $ echo not wat > wat
458 $ echo not wat > wat
459 $ hg ci -m 'modify wat'
459 $ hg ci -m 'modify wat'
460 $ hg histedit 17
460 $ hg histedit 17
461 abort: cannot edit history that contains merges
461 abort: cannot edit history that contains merges
462 [255]
462 [255]
463 $ cd ..
463 $ cd ..
@@ -1,473 +1,473
1 ==========================
1 ==========================
2 Test rebase with obsolete
2 Test rebase with obsolete
3 ==========================
3 ==========================
4
4
5 Enable obsolete
5 Enable obsolete
6
6
7 $ cat > ${TESTTMP}/obs.py << EOF
7 $ cat > ${TESTTMP}/obs.py << EOF
8 > import mercurial.obsolete
8 > import mercurial.obsolete
9 > mercurial.obsolete._enabled = True
9 > mercurial.obsolete._enabled = True
10 > EOF
10 > EOF
11 $ cat >> $HGRCPATH << EOF
11 $ cat >> $HGRCPATH << EOF
12 > [ui]
12 > [ui]
13 > logtemplate= {rev}:{node|short} {desc|firstline}
13 > logtemplate= {rev}:{node|short} {desc|firstline}
14 > [phases]
14 > [phases]
15 > publish=False
15 > publish=False
16 > [extensions]'
16 > [extensions]'
17 > rebase=
17 > rebase=
18 >
18 >
19 > obs=${TESTTMP}/obs.py
19 > obs=${TESTTMP}/obs.py
20 > EOF
20 > EOF
21
21
22 Setup rebase canonical repo
22 Setup rebase canonical repo
23
23
24 $ hg init base
24 $ hg init base
25 $ cd base
25 $ cd base
26 $ hg unbundle "$TESTDIR/bundles/rebase.hg"
26 $ hg unbundle "$TESTDIR/bundles/rebase.hg"
27 adding changesets
27 adding changesets
28 adding manifests
28 adding manifests
29 adding file changes
29 adding file changes
30 added 8 changesets with 7 changes to 7 files (+2 heads)
30 added 8 changesets with 7 changes to 7 files (+2 heads)
31 (run 'hg heads' to see heads, 'hg merge' to merge)
31 (run 'hg heads' to see heads, 'hg merge' to merge)
32 $ hg up tip
32 $ hg up tip
33 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
33 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
34 $ hg log -G
34 $ hg log -G
35 @ 7:02de42196ebe H
35 @ 7:02de42196ebe H
36 |
36 |
37 | o 6:eea13746799a G
37 | o 6:eea13746799a G
38 |/|
38 |/|
39 o | 5:24b6387c8c8c F
39 o | 5:24b6387c8c8c F
40 | |
40 | |
41 | o 4:9520eea781bc E
41 | o 4:9520eea781bc E
42 |/
42 |/
43 | o 3:32af7686d403 D
43 | o 3:32af7686d403 D
44 | |
44 | |
45 | o 2:5fddd98957c8 C
45 | o 2:5fddd98957c8 C
46 | |
46 | |
47 | o 1:42ccdea3bb16 B
47 | o 1:42ccdea3bb16 B
48 |/
48 |/
49 o 0:cd010b8cd998 A
49 o 0:cd010b8cd998 A
50
50
51 $ cd ..
51 $ cd ..
52
52
53 simple rebase
53 simple rebase
54 ---------------------------------
54 ---------------------------------
55
55
56 $ hg clone base simple
56 $ hg clone base simple
57 updating to branch default
57 updating to branch default
58 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
58 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
59 $ cd simple
59 $ cd simple
60 $ hg up 32af7686d403
60 $ hg up 32af7686d403
61 3 files updated, 0 files merged, 2 files removed, 0 files unresolved
61 3 files updated, 0 files merged, 2 files removed, 0 files unresolved
62 $ hg rebase -d eea13746799a
62 $ hg rebase -d eea13746799a
63 $ hg log -G
63 $ hg log -G
64 @ 10:8eeb3c33ad33 D
64 @ 10:8eeb3c33ad33 D
65 |
65 |
66 o 9:2327fea05063 C
66 o 9:2327fea05063 C
67 |
67 |
68 o 8:e4e5be0395b2 B
68 o 8:e4e5be0395b2 B
69 |
69 |
70 | o 7:02de42196ebe H
70 | o 7:02de42196ebe H
71 | |
71 | |
72 o | 6:eea13746799a G
72 o | 6:eea13746799a G
73 |\|
73 |\|
74 | o 5:24b6387c8c8c F
74 | o 5:24b6387c8c8c F
75 | |
75 | |
76 o | 4:9520eea781bc E
76 o | 4:9520eea781bc E
77 |/
77 |/
78 o 0:cd010b8cd998 A
78 o 0:cd010b8cd998 A
79
79
80 $ hg log --hidden -G
80 $ hg log --hidden -G
81 @ 10:8eeb3c33ad33 D
81 @ 10:8eeb3c33ad33 D
82 |
82 |
83 o 9:2327fea05063 C
83 o 9:2327fea05063 C
84 |
84 |
85 o 8:e4e5be0395b2 B
85 o 8:e4e5be0395b2 B
86 |
86 |
87 | o 7:02de42196ebe H
87 | o 7:02de42196ebe H
88 | |
88 | |
89 o | 6:eea13746799a G
89 o | 6:eea13746799a G
90 |\|
90 |\|
91 | o 5:24b6387c8c8c F
91 | o 5:24b6387c8c8c F
92 | |
92 | |
93 o | 4:9520eea781bc E
93 o | 4:9520eea781bc E
94 |/
94 |/
95 | x 3:32af7686d403 D
95 | x 3:32af7686d403 D
96 | |
96 | |
97 | x 2:5fddd98957c8 C
97 | x 2:5fddd98957c8 C
98 | |
98 | |
99 | x 1:42ccdea3bb16 B
99 | x 1:42ccdea3bb16 B
100 |/
100 |/
101 o 0:cd010b8cd998 A
101 o 0:cd010b8cd998 A
102
102
103 $ hg debugobsolete
103 $ hg debugobsolete
104 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 e4e5be0395b2cbd471ed22a26b1b6a1a0658a794 0 (*) {'user': 'test'} (glob)
104 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 e4e5be0395b2cbd471ed22a26b1b6a1a0658a794 0 (*) {'user': 'test'} (glob)
105 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 2327fea05063f39961b14cb69435a9898dc9a245 0 (*) {'user': 'test'} (glob)
105 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 2327fea05063f39961b14cb69435a9898dc9a245 0 (*) {'user': 'test'} (glob)
106 32af7686d403cf45b5d95f2d70cebea587ac806a 8eeb3c33ad33d452c89e5dcf611c347f978fb42b 0 (*) {'user': 'test'} (glob)
106 32af7686d403cf45b5d95f2d70cebea587ac806a 8eeb3c33ad33d452c89e5dcf611c347f978fb42b 0 (*) {'user': 'test'} (glob)
107
107
108
108
109 $ cd ..
109 $ cd ..
110
110
111 empty changeset
111 empty changeset
112 ---------------------------------
112 ---------------------------------
113
113
114 $ hg clone base empty
114 $ hg clone base empty
115 updating to branch default
115 updating to branch default
116 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
116 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
117 $ cd empty
117 $ cd empty
118 $ hg up eea13746799a
118 $ hg up eea13746799a
119 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
119 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
120
120
121 We make a copy of both the first changeset in the rebased and some other in the
121 We make a copy of both the first changeset in the rebased and some other in the
122 set.
122 set.
123
123
124 $ hg graft 42ccdea3bb16 32af7686d403
124 $ hg graft 42ccdea3bb16 32af7686d403
125 grafting revision 1
125 grafting revision 1
126 grafting revision 3
126 grafting revision 3
127 $ hg rebase -s 42ccdea3bb16 -d .
127 $ hg rebase -s 42ccdea3bb16 -d .
128 $ hg log -G
128 $ hg log -G
129 o 10:5ae4c968c6ac C
129 o 10:5ae4c968c6ac C
130 |
130 |
131 @ 9:08483444fef9 D
131 @ 9:08483444fef9 D
132 |
132 |
133 o 8:8877864f1edb B
133 o 8:8877864f1edb B
134 |
134 |
135 | o 7:02de42196ebe H
135 | o 7:02de42196ebe H
136 | |
136 | |
137 o | 6:eea13746799a G
137 o | 6:eea13746799a G
138 |\|
138 |\|
139 | o 5:24b6387c8c8c F
139 | o 5:24b6387c8c8c F
140 | |
140 | |
141 o | 4:9520eea781bc E
141 o | 4:9520eea781bc E
142 |/
142 |/
143 o 0:cd010b8cd998 A
143 o 0:cd010b8cd998 A
144
144
145 $ hg log --hidden -G
145 $ hg log --hidden -G
146 o 10:5ae4c968c6ac C
146 o 10:5ae4c968c6ac C
147 |
147 |
148 @ 9:08483444fef9 D
148 @ 9:08483444fef9 D
149 |
149 |
150 o 8:8877864f1edb B
150 o 8:8877864f1edb B
151 |
151 |
152 | o 7:02de42196ebe H
152 | o 7:02de42196ebe H
153 | |
153 | |
154 o | 6:eea13746799a G
154 o | 6:eea13746799a G
155 |\|
155 |\|
156 | o 5:24b6387c8c8c F
156 | o 5:24b6387c8c8c F
157 | |
157 | |
158 o | 4:9520eea781bc E
158 o | 4:9520eea781bc E
159 |/
159 |/
160 | x 3:32af7686d403 D
160 | x 3:32af7686d403 D
161 | |
161 | |
162 | x 2:5fddd98957c8 C
162 | x 2:5fddd98957c8 C
163 | |
163 | |
164 | x 1:42ccdea3bb16 B
164 | x 1:42ccdea3bb16 B
165 |/
165 |/
166 o 0:cd010b8cd998 A
166 o 0:cd010b8cd998 A
167
167
168 $ hg debugobsolete
168 $ hg debugobsolete
169 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 (*) {'user': 'test'} (glob)
169 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 {cd010b8cd998f3981a5a8115f94f8da4ab506089} (*) {'user': 'test'} (glob)
170 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 (*) {'user': 'test'} (glob)
170 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 (*) {'user': 'test'} (glob)
171 32af7686d403cf45b5d95f2d70cebea587ac806a 0 (*) {'user': 'test'} (glob)
171 32af7686d403cf45b5d95f2d70cebea587ac806a 0 {5fddd98957c8a54a4d436dfe1da9d87f21a1b97b} (*) {'user': 'test'} (glob)
172
172
173
173
174 More complex case were part of the rebase set were already rebased
174 More complex case were part of the rebase set were already rebased
175
175
176 $ hg rebase --rev 'desc(D)' --dest 'desc(H)'
176 $ hg rebase --rev 'desc(D)' --dest 'desc(H)'
177 $ hg debugobsolete
177 $ hg debugobsolete
178 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 (*) {'user': 'test'} (glob)
178 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 {cd010b8cd998f3981a5a8115f94f8da4ab506089} (*) {'user': 'test'} (glob)
179 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 (*) {'user': 'test'} (glob)
179 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 (*) {'user': 'test'} (glob)
180 32af7686d403cf45b5d95f2d70cebea587ac806a 0 (*) {'user': 'test'} (glob)
180 32af7686d403cf45b5d95f2d70cebea587ac806a 0 {5fddd98957c8a54a4d436dfe1da9d87f21a1b97b} (*) {'user': 'test'} (glob)
181 08483444fef91d6224f6655ee586a65d263ad34c 4596109a6a4328c398bde3a4a3b6737cfade3003 0 (*) {'user': 'test'} (glob)
181 08483444fef91d6224f6655ee586a65d263ad34c 4596109a6a4328c398bde3a4a3b6737cfade3003 0 (*) {'user': 'test'} (glob)
182 $ hg log -G
182 $ hg log -G
183 @ 11:4596109a6a43 D
183 @ 11:4596109a6a43 D
184 |
184 |
185 | o 10:5ae4c968c6ac C
185 | o 10:5ae4c968c6ac C
186 | |
186 | |
187 | x 9:08483444fef9 D
187 | x 9:08483444fef9 D
188 | |
188 | |
189 | o 8:8877864f1edb B
189 | o 8:8877864f1edb B
190 | |
190 | |
191 o | 7:02de42196ebe H
191 o | 7:02de42196ebe H
192 | |
192 | |
193 | o 6:eea13746799a G
193 | o 6:eea13746799a G
194 |/|
194 |/|
195 o | 5:24b6387c8c8c F
195 o | 5:24b6387c8c8c F
196 | |
196 | |
197 | o 4:9520eea781bc E
197 | o 4:9520eea781bc E
198 |/
198 |/
199 o 0:cd010b8cd998 A
199 o 0:cd010b8cd998 A
200
200
201 $ hg rebase --source 'desc(B)' --dest 'tip'
201 $ hg rebase --source 'desc(B)' --dest 'tip'
202 $ hg debugobsolete
202 $ hg debugobsolete
203 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 (*) {'user': 'test'} (glob)
203 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 {cd010b8cd998f3981a5a8115f94f8da4ab506089} (*) {'user': 'test'} (glob)
204 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 (*) {'user': 'test'} (glob)
204 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 (*) {'user': 'test'} (glob)
205 32af7686d403cf45b5d95f2d70cebea587ac806a 0 (*) {'user': 'test'} (glob)
205 32af7686d403cf45b5d95f2d70cebea587ac806a 0 {5fddd98957c8a54a4d436dfe1da9d87f21a1b97b} (*) {'user': 'test'} (glob)
206 08483444fef91d6224f6655ee586a65d263ad34c 4596109a6a4328c398bde3a4a3b6737cfade3003 0 (*) {'user': 'test'} (glob)
206 08483444fef91d6224f6655ee586a65d263ad34c 4596109a6a4328c398bde3a4a3b6737cfade3003 0 (*) {'user': 'test'} (glob)
207 8877864f1edb05d0e07dc4ba77b67a80a7b86672 462a34d07e599b87ea08676a449373fe4e2e1347 0 (*) {'user': 'test'} (glob)
207 8877864f1edb05d0e07dc4ba77b67a80a7b86672 462a34d07e599b87ea08676a449373fe4e2e1347 0 (*) {'user': 'test'} (glob)
208 08483444fef91d6224f6655ee586a65d263ad34c 0 (*) {'user': 'test'} (glob)
208 08483444fef91d6224f6655ee586a65d263ad34c 0 {8877864f1edb05d0e07dc4ba77b67a80a7b86672} (*) {'user': 'test'} (glob)
209 5ae4c968c6aca831df823664e706c9d4aa34473d 98f6af4ee9539e14da4465128f894c274900b6e5 0 (*) {'user': 'test'} (glob)
209 5ae4c968c6aca831df823664e706c9d4aa34473d 98f6af4ee9539e14da4465128f894c274900b6e5 0 (*) {'user': 'test'} (glob)
210 $ hg log --rev 'divergent()'
210 $ hg log --rev 'divergent()'
211 $ hg log -G
211 $ hg log -G
212 o 13:98f6af4ee953 C
212 o 13:98f6af4ee953 C
213 |
213 |
214 o 12:462a34d07e59 B
214 o 12:462a34d07e59 B
215 |
215 |
216 @ 11:4596109a6a43 D
216 @ 11:4596109a6a43 D
217 |
217 |
218 o 7:02de42196ebe H
218 o 7:02de42196ebe H
219 |
219 |
220 | o 6:eea13746799a G
220 | o 6:eea13746799a G
221 |/|
221 |/|
222 o | 5:24b6387c8c8c F
222 o | 5:24b6387c8c8c F
223 | |
223 | |
224 | o 4:9520eea781bc E
224 | o 4:9520eea781bc E
225 |/
225 |/
226 o 0:cd010b8cd998 A
226 o 0:cd010b8cd998 A
227
227
228 $ hg log --style default --debug -r 4596109a6a4328c398bde3a4a3b6737cfade3003
228 $ hg log --style default --debug -r 4596109a6a4328c398bde3a4a3b6737cfade3003
229 changeset: 11:4596109a6a4328c398bde3a4a3b6737cfade3003
229 changeset: 11:4596109a6a4328c398bde3a4a3b6737cfade3003
230 phase: draft
230 phase: draft
231 parent: 7:02de42196ebee42ef284b6780a87cdc96e8eaab6
231 parent: 7:02de42196ebee42ef284b6780a87cdc96e8eaab6
232 parent: -1:0000000000000000000000000000000000000000
232 parent: -1:0000000000000000000000000000000000000000
233 manifest: 11:a91006e3a02f1edf631f7018e6e5684cf27dd905
233 manifest: 11:a91006e3a02f1edf631f7018e6e5684cf27dd905
234 user: Nicolas Dumazet <nicdumz.commits@gmail.com>
234 user: Nicolas Dumazet <nicdumz.commits@gmail.com>
235 date: Sat Apr 30 15:24:48 2011 +0200
235 date: Sat Apr 30 15:24:48 2011 +0200
236 files+: D
236 files+: D
237 extra: branch=default
237 extra: branch=default
238 extra: rebase_source=08483444fef91d6224f6655ee586a65d263ad34c
238 extra: rebase_source=08483444fef91d6224f6655ee586a65d263ad34c
239 extra: source=32af7686d403cf45b5d95f2d70cebea587ac806a
239 extra: source=32af7686d403cf45b5d95f2d70cebea587ac806a
240 description:
240 description:
241 D
241 D
242
242
243
243
244 $ cd ..
244 $ cd ..
245
245
246 collapse rebase
246 collapse rebase
247 ---------------------------------
247 ---------------------------------
248
248
249 $ hg clone base collapse
249 $ hg clone base collapse
250 updating to branch default
250 updating to branch default
251 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
251 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
252 $ cd collapse
252 $ cd collapse
253 $ hg rebase -s 42ccdea3bb16 -d eea13746799a --collapse
253 $ hg rebase -s 42ccdea3bb16 -d eea13746799a --collapse
254 $ hg log -G
254 $ hg log -G
255 o 8:4dc2197e807b Collapsed revision
255 o 8:4dc2197e807b Collapsed revision
256 |
256 |
257 | @ 7:02de42196ebe H
257 | @ 7:02de42196ebe H
258 | |
258 | |
259 o | 6:eea13746799a G
259 o | 6:eea13746799a G
260 |\|
260 |\|
261 | o 5:24b6387c8c8c F
261 | o 5:24b6387c8c8c F
262 | |
262 | |
263 o | 4:9520eea781bc E
263 o | 4:9520eea781bc E
264 |/
264 |/
265 o 0:cd010b8cd998 A
265 o 0:cd010b8cd998 A
266
266
267 $ hg log --hidden -G
267 $ hg log --hidden -G
268 o 8:4dc2197e807b Collapsed revision
268 o 8:4dc2197e807b Collapsed revision
269 |
269 |
270 | @ 7:02de42196ebe H
270 | @ 7:02de42196ebe H
271 | |
271 | |
272 o | 6:eea13746799a G
272 o | 6:eea13746799a G
273 |\|
273 |\|
274 | o 5:24b6387c8c8c F
274 | o 5:24b6387c8c8c F
275 | |
275 | |
276 o | 4:9520eea781bc E
276 o | 4:9520eea781bc E
277 |/
277 |/
278 | x 3:32af7686d403 D
278 | x 3:32af7686d403 D
279 | |
279 | |
280 | x 2:5fddd98957c8 C
280 | x 2:5fddd98957c8 C
281 | |
281 | |
282 | x 1:42ccdea3bb16 B
282 | x 1:42ccdea3bb16 B
283 |/
283 |/
284 o 0:cd010b8cd998 A
284 o 0:cd010b8cd998 A
285
285
286 $ hg id --debug -r tip
286 $ hg id --debug -r tip
287 4dc2197e807bae9817f09905b50ab288be2dbbcf tip
287 4dc2197e807bae9817f09905b50ab288be2dbbcf tip
288 $ hg debugobsolete
288 $ hg debugobsolete
289 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 (*) {'user': 'test'} (glob)
289 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 (*) {'user': 'test'} (glob)
290 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 (*) {'user': 'test'} (glob)
290 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 (*) {'user': 'test'} (glob)
291 32af7686d403cf45b5d95f2d70cebea587ac806a 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 (*) {'user': 'test'} (glob)
291 32af7686d403cf45b5d95f2d70cebea587ac806a 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 (*) {'user': 'test'} (glob)
292
292
293 $ cd ..
293 $ cd ..
294
294
295 Rebase set has hidden descendants
295 Rebase set has hidden descendants
296 ---------------------------------
296 ---------------------------------
297
297
298 We rebase a changeset which has a hidden changeset. The hidden changeset must
298 We rebase a changeset which has a hidden changeset. The hidden changeset must
299 not be rebased.
299 not be rebased.
300
300
301 $ hg clone base hidden
301 $ hg clone base hidden
302 updating to branch default
302 updating to branch default
303 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
303 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
304 $ cd hidden
304 $ cd hidden
305 $ hg rebase -s 5fddd98957c8 -d eea13746799a
305 $ hg rebase -s 5fddd98957c8 -d eea13746799a
306 $ hg rebase -s 42ccdea3bb16 -d 02de42196ebe
306 $ hg rebase -s 42ccdea3bb16 -d 02de42196ebe
307 $ hg log -G
307 $ hg log -G
308 o 10:7c6027df6a99 B
308 o 10:7c6027df6a99 B
309 |
309 |
310 | o 9:cf44d2f5a9f4 D
310 | o 9:cf44d2f5a9f4 D
311 | |
311 | |
312 | o 8:e273c5e7d2d2 C
312 | o 8:e273c5e7d2d2 C
313 | |
313 | |
314 @ | 7:02de42196ebe H
314 @ | 7:02de42196ebe H
315 | |
315 | |
316 | o 6:eea13746799a G
316 | o 6:eea13746799a G
317 |/|
317 |/|
318 o | 5:24b6387c8c8c F
318 o | 5:24b6387c8c8c F
319 | |
319 | |
320 | o 4:9520eea781bc E
320 | o 4:9520eea781bc E
321 |/
321 |/
322 o 0:cd010b8cd998 A
322 o 0:cd010b8cd998 A
323
323
324 $ hg log --hidden -G
324 $ hg log --hidden -G
325 o 10:7c6027df6a99 B
325 o 10:7c6027df6a99 B
326 |
326 |
327 | o 9:cf44d2f5a9f4 D
327 | o 9:cf44d2f5a9f4 D
328 | |
328 | |
329 | o 8:e273c5e7d2d2 C
329 | o 8:e273c5e7d2d2 C
330 | |
330 | |
331 @ | 7:02de42196ebe H
331 @ | 7:02de42196ebe H
332 | |
332 | |
333 | o 6:eea13746799a G
333 | o 6:eea13746799a G
334 |/|
334 |/|
335 o | 5:24b6387c8c8c F
335 o | 5:24b6387c8c8c F
336 | |
336 | |
337 | o 4:9520eea781bc E
337 | o 4:9520eea781bc E
338 |/
338 |/
339 | x 3:32af7686d403 D
339 | x 3:32af7686d403 D
340 | |
340 | |
341 | x 2:5fddd98957c8 C
341 | x 2:5fddd98957c8 C
342 | |
342 | |
343 | x 1:42ccdea3bb16 B
343 | x 1:42ccdea3bb16 B
344 |/
344 |/
345 o 0:cd010b8cd998 A
345 o 0:cd010b8cd998 A
346
346
347 $ hg debugobsolete
347 $ hg debugobsolete
348 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b e273c5e7d2d29df783dce9f9eaa3ac4adc69c15d 0 (*) {'user': 'test'} (glob)
348 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b e273c5e7d2d29df783dce9f9eaa3ac4adc69c15d 0 (*) {'user': 'test'} (glob)
349 32af7686d403cf45b5d95f2d70cebea587ac806a cf44d2f5a9f4297a62be94cbdd3dff7c7dc54258 0 (*) {'user': 'test'} (glob)
349 32af7686d403cf45b5d95f2d70cebea587ac806a cf44d2f5a9f4297a62be94cbdd3dff7c7dc54258 0 (*) {'user': 'test'} (glob)
350 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 7c6027df6a99d93f461868e5433f63bde20b6dfb 0 (*) {'user': 'test'} (glob)
350 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 7c6027df6a99d93f461868e5433f63bde20b6dfb 0 (*) {'user': 'test'} (glob)
351
351
352 Test that rewriting leaving instability behind is allowed
352 Test that rewriting leaving instability behind is allowed
353 ---------------------------------------------------------------------
353 ---------------------------------------------------------------------
354
354
355 $ hg log -r 'children(8)'
355 $ hg log -r 'children(8)'
356 9:cf44d2f5a9f4 D (no-eol)
356 9:cf44d2f5a9f4 D (no-eol)
357 $ hg rebase -r 8
357 $ hg rebase -r 8
358 $ hg log -G
358 $ hg log -G
359 o 11:0d8f238b634c C
359 o 11:0d8f238b634c C
360 |
360 |
361 o 10:7c6027df6a99 B
361 o 10:7c6027df6a99 B
362 |
362 |
363 | o 9:cf44d2f5a9f4 D
363 | o 9:cf44d2f5a9f4 D
364 | |
364 | |
365 | x 8:e273c5e7d2d2 C
365 | x 8:e273c5e7d2d2 C
366 | |
366 | |
367 @ | 7:02de42196ebe H
367 @ | 7:02de42196ebe H
368 | |
368 | |
369 | o 6:eea13746799a G
369 | o 6:eea13746799a G
370 |/|
370 |/|
371 o | 5:24b6387c8c8c F
371 o | 5:24b6387c8c8c F
372 | |
372 | |
373 | o 4:9520eea781bc E
373 | o 4:9520eea781bc E
374 |/
374 |/
375 o 0:cd010b8cd998 A
375 o 0:cd010b8cd998 A
376
376
377
377
378
378
379 Test multiple root handling
379 Test multiple root handling
380 ------------------------------------
380 ------------------------------------
381
381
382 $ hg rebase --dest 4 --rev '7+11+9'
382 $ hg rebase --dest 4 --rev '7+11+9'
383 $ hg log -G
383 $ hg log -G
384 o 14:1e8370e38cca C
384 o 14:1e8370e38cca C
385 |
385 |
386 | o 13:102b4c1d889b D
386 | o 13:102b4c1d889b D
387 | |
387 | |
388 @ | 12:bfe264faf697 H
388 @ | 12:bfe264faf697 H
389 |/
389 |/
390 | o 10:7c6027df6a99 B
390 | o 10:7c6027df6a99 B
391 | |
391 | |
392 | x 7:02de42196ebe H
392 | x 7:02de42196ebe H
393 | |
393 | |
394 +---o 6:eea13746799a G
394 +---o 6:eea13746799a G
395 | |/
395 | |/
396 | o 5:24b6387c8c8c F
396 | o 5:24b6387c8c8c F
397 | |
397 | |
398 o | 4:9520eea781bc E
398 o | 4:9520eea781bc E
399 |/
399 |/
400 o 0:cd010b8cd998 A
400 o 0:cd010b8cd998 A
401
401
402 $ cd ..
402 $ cd ..
403
403
404 test on rebase dropping a merge
404 test on rebase dropping a merge
405
405
406 (setup)
406 (setup)
407
407
408 $ hg init dropmerge
408 $ hg init dropmerge
409 $ cd dropmerge
409 $ cd dropmerge
410 $ hg unbundle "$TESTDIR/bundles/rebase.hg"
410 $ hg unbundle "$TESTDIR/bundles/rebase.hg"
411 adding changesets
411 adding changesets
412 adding manifests
412 adding manifests
413 adding file changes
413 adding file changes
414 added 8 changesets with 7 changes to 7 files (+2 heads)
414 added 8 changesets with 7 changes to 7 files (+2 heads)
415 (run 'hg heads' to see heads, 'hg merge' to merge)
415 (run 'hg heads' to see heads, 'hg merge' to merge)
416 $ hg up 3
416 $ hg up 3
417 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
417 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
418 $ hg merge 7
418 $ hg merge 7
419 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
419 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
420 (branch merge, don't forget to commit)
420 (branch merge, don't forget to commit)
421 $ hg ci -m 'M'
421 $ hg ci -m 'M'
422 $ echo I > I
422 $ echo I > I
423 $ hg add I
423 $ hg add I
424 $ hg ci -m I
424 $ hg ci -m I
425 $ hg log -G
425 $ hg log -G
426 @ 9:4bde274eefcf I
426 @ 9:4bde274eefcf I
427 |
427 |
428 o 8:53a6a128b2b7 M
428 o 8:53a6a128b2b7 M
429 |\
429 |\
430 | o 7:02de42196ebe H
430 | o 7:02de42196ebe H
431 | |
431 | |
432 | | o 6:eea13746799a G
432 | | o 6:eea13746799a G
433 | |/|
433 | |/|
434 | o | 5:24b6387c8c8c F
434 | o | 5:24b6387c8c8c F
435 | | |
435 | | |
436 | | o 4:9520eea781bc E
436 | | o 4:9520eea781bc E
437 | |/
437 | |/
438 o | 3:32af7686d403 D
438 o | 3:32af7686d403 D
439 | |
439 | |
440 o | 2:5fddd98957c8 C
440 o | 2:5fddd98957c8 C
441 | |
441 | |
442 o | 1:42ccdea3bb16 B
442 o | 1:42ccdea3bb16 B
443 |/
443 |/
444 o 0:cd010b8cd998 A
444 o 0:cd010b8cd998 A
445
445
446 (actual test)
446 (actual test)
447
447
448 $ hg rebase --dest 6 --rev '((desc(H) + desc(D))::) - desc(M)'
448 $ hg rebase --dest 6 --rev '((desc(H) + desc(D))::) - desc(M)'
449 $ hg log -G
449 $ hg log -G
450 @ 12:acd174b7ab39 I
450 @ 12:acd174b7ab39 I
451 |
451 |
452 o 11:6c11a6218c97 H
452 o 11:6c11a6218c97 H
453 |
453 |
454 | o 10:b5313c85b22e D
454 | o 10:b5313c85b22e D
455 |/
455 |/
456 | o 8:53a6a128b2b7 M
456 | o 8:53a6a128b2b7 M
457 | |\
457 | |\
458 | | x 7:02de42196ebe H
458 | | x 7:02de42196ebe H
459 | | |
459 | | |
460 o---+ 6:eea13746799a G
460 o---+ 6:eea13746799a G
461 | | |
461 | | |
462 | | o 5:24b6387c8c8c F
462 | | o 5:24b6387c8c8c F
463 | | |
463 | | |
464 o---+ 4:9520eea781bc E
464 o---+ 4:9520eea781bc E
465 / /
465 / /
466 x | 3:32af7686d403 D
466 x | 3:32af7686d403 D
467 | |
467 | |
468 o | 2:5fddd98957c8 C
468 o | 2:5fddd98957c8 C
469 | |
469 | |
470 o | 1:42ccdea3bb16 B
470 o | 1:42ccdea3bb16 B
471 |/
471 |/
472 o 0:cd010b8cd998 A
472 o 0:cd010b8cd998 A
473
473
General Comments 0
You need to be logged in to leave comments. Login now