##// END OF EJS Templates
add: pass options via keyword args...
Matt Harbison -
r23885:9994f45b default
parent child Browse files
Show More
@@ -1,2979 +1,2977 b''
1 # cmdutil.py - help for command processing in mercurial
1 # cmdutil.py - help for command processing in mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from node import hex, nullid, nullrev, short
8 from node import hex, nullid, nullrev, short
9 from i18n import _
9 from i18n import _
10 import os, sys, errno, re, tempfile
10 import os, sys, errno, re, tempfile
11 import util, scmutil, templater, patch, error, templatekw, revlog, copies
11 import util, scmutil, templater, patch, error, templatekw, revlog, copies
12 import match as matchmod
12 import match as matchmod
13 import 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 encoding
16 import encoding
17 import lock as lockmod
17 import lock as lockmod
18
18
19 def parsealiases(cmd):
19 def parsealiases(cmd):
20 return cmd.lstrip("^").split("|")
20 return cmd.lstrip("^").split("|")
21
21
22 def findpossible(cmd, table, strict=False):
22 def findpossible(cmd, table, strict=False):
23 """
23 """
24 Return cmd -> (aliases, command table entry)
24 Return cmd -> (aliases, command table entry)
25 for each matching command.
25 for each matching command.
26 Return debug commands (or their aliases) only if no normal command matches.
26 Return debug commands (or their aliases) only if no normal command matches.
27 """
27 """
28 choice = {}
28 choice = {}
29 debugchoice = {}
29 debugchoice = {}
30
30
31 if cmd in table:
31 if cmd in table:
32 # short-circuit exact matches, "log" alias beats "^log|history"
32 # short-circuit exact matches, "log" alias beats "^log|history"
33 keys = [cmd]
33 keys = [cmd]
34 else:
34 else:
35 keys = table.keys()
35 keys = table.keys()
36
36
37 for e in keys:
37 for e in keys:
38 aliases = parsealiases(e)
38 aliases = parsealiases(e)
39 found = None
39 found = None
40 if cmd in aliases:
40 if cmd in aliases:
41 found = cmd
41 found = cmd
42 elif not strict:
42 elif not strict:
43 for a in aliases:
43 for a in aliases:
44 if a.startswith(cmd):
44 if a.startswith(cmd):
45 found = a
45 found = a
46 break
46 break
47 if found is not None:
47 if found is not None:
48 if aliases[0].startswith("debug") or found.startswith("debug"):
48 if aliases[0].startswith("debug") or found.startswith("debug"):
49 debugchoice[found] = (aliases, table[e])
49 debugchoice[found] = (aliases, table[e])
50 else:
50 else:
51 choice[found] = (aliases, table[e])
51 choice[found] = (aliases, table[e])
52
52
53 if not choice and debugchoice:
53 if not choice and debugchoice:
54 choice = debugchoice
54 choice = debugchoice
55
55
56 return choice
56 return choice
57
57
58 def findcmd(cmd, table, strict=True):
58 def findcmd(cmd, table, strict=True):
59 """Return (aliases, command table entry) for command string."""
59 """Return (aliases, command table entry) for command string."""
60 choice = findpossible(cmd, table, strict)
60 choice = findpossible(cmd, table, strict)
61
61
62 if cmd in choice:
62 if cmd in choice:
63 return choice[cmd]
63 return choice[cmd]
64
64
65 if len(choice) > 1:
65 if len(choice) > 1:
66 clist = choice.keys()
66 clist = choice.keys()
67 clist.sort()
67 clist.sort()
68 raise error.AmbiguousCommand(cmd, clist)
68 raise error.AmbiguousCommand(cmd, clist)
69
69
70 if choice:
70 if choice:
71 return choice.values()[0]
71 return choice.values()[0]
72
72
73 raise error.UnknownCommand(cmd)
73 raise error.UnknownCommand(cmd)
74
74
75 def findrepo(p):
75 def findrepo(p):
76 while not os.path.isdir(os.path.join(p, ".hg")):
76 while not os.path.isdir(os.path.join(p, ".hg")):
77 oldp, p = p, os.path.dirname(p)
77 oldp, p = p, os.path.dirname(p)
78 if p == oldp:
78 if p == oldp:
79 return None
79 return None
80
80
81 return p
81 return p
82
82
83 def bailifchanged(repo):
83 def bailifchanged(repo):
84 if repo.dirstate.p2() != nullid:
84 if repo.dirstate.p2() != nullid:
85 raise util.Abort(_('outstanding uncommitted merge'))
85 raise util.Abort(_('outstanding uncommitted merge'))
86 modified, added, removed, deleted = repo.status()[:4]
86 modified, added, removed, deleted = repo.status()[:4]
87 if modified or added or removed or deleted:
87 if modified or added or removed or deleted:
88 raise util.Abort(_('uncommitted changes'))
88 raise util.Abort(_('uncommitted changes'))
89 ctx = repo[None]
89 ctx = repo[None]
90 for s in sorted(ctx.substate):
90 for s in sorted(ctx.substate):
91 if ctx.sub(s).dirty():
91 if ctx.sub(s).dirty():
92 raise util.Abort(_("uncommitted changes in subrepo %s") % s)
92 raise util.Abort(_("uncommitted changes in subrepo %s") % s)
93
93
94 def logmessage(ui, opts):
94 def logmessage(ui, opts):
95 """ get the log message according to -m and -l option """
95 """ get the log message according to -m and -l option """
96 message = opts.get('message')
96 message = opts.get('message')
97 logfile = opts.get('logfile')
97 logfile = opts.get('logfile')
98
98
99 if message and logfile:
99 if message and logfile:
100 raise util.Abort(_('options --message and --logfile are mutually '
100 raise util.Abort(_('options --message and --logfile are mutually '
101 'exclusive'))
101 'exclusive'))
102 if not message and logfile:
102 if not message and logfile:
103 try:
103 try:
104 if logfile == '-':
104 if logfile == '-':
105 message = ui.fin.read()
105 message = ui.fin.read()
106 else:
106 else:
107 message = '\n'.join(util.readfile(logfile).splitlines())
107 message = '\n'.join(util.readfile(logfile).splitlines())
108 except IOError, inst:
108 except IOError, inst:
109 raise util.Abort(_("can't read commit message '%s': %s") %
109 raise util.Abort(_("can't read commit message '%s': %s") %
110 (logfile, inst.strerror))
110 (logfile, inst.strerror))
111 return message
111 return message
112
112
113 def mergeeditform(ctxorbool, baseform):
113 def mergeeditform(ctxorbool, baseform):
114 """build appropriate editform from ctxorbool and baseform
114 """build appropriate editform from ctxorbool and baseform
115
115
116 'ctxorbool' is one of a ctx to be committed, or a bool whether
116 'ctxorbool' is one of a ctx to be committed, or a bool whether
117 merging is committed.
117 merging is committed.
118
118
119 This returns editform 'baseform' with '.merge' if merging is
119 This returns editform 'baseform' with '.merge' if merging is
120 committed, or one with '.normal' suffix otherwise.
120 committed, or one with '.normal' suffix otherwise.
121 """
121 """
122 if isinstance(ctxorbool, bool):
122 if isinstance(ctxorbool, bool):
123 if ctxorbool:
123 if ctxorbool:
124 return baseform + ".merge"
124 return baseform + ".merge"
125 elif 1 < len(ctxorbool.parents()):
125 elif 1 < len(ctxorbool.parents()):
126 return baseform + ".merge"
126 return baseform + ".merge"
127
127
128 return baseform + ".normal"
128 return baseform + ".normal"
129
129
130 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
130 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
131 editform='', **opts):
131 editform='', **opts):
132 """get appropriate commit message editor according to '--edit' option
132 """get appropriate commit message editor according to '--edit' option
133
133
134 'finishdesc' is a function to be called with edited commit message
134 'finishdesc' is a function to be called with edited commit message
135 (= 'description' of the new changeset) just after editing, but
135 (= 'description' of the new changeset) just after editing, but
136 before checking empty-ness. It should return actual text to be
136 before checking empty-ness. It should return actual text to be
137 stored into history. This allows to change description before
137 stored into history. This allows to change description before
138 storing.
138 storing.
139
139
140 'extramsg' is a extra message to be shown in the editor instead of
140 'extramsg' is a extra message to be shown in the editor instead of
141 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
141 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
142 is automatically added.
142 is automatically added.
143
143
144 'editform' is a dot-separated list of names, to distinguish
144 'editform' is a dot-separated list of names, to distinguish
145 the purpose of commit text editing.
145 the purpose of commit text editing.
146
146
147 'getcommiteditor' returns 'commitforceeditor' regardless of
147 'getcommiteditor' returns 'commitforceeditor' regardless of
148 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
148 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
149 they are specific for usage in MQ.
149 they are specific for usage in MQ.
150 """
150 """
151 if edit or finishdesc or extramsg:
151 if edit or finishdesc or extramsg:
152 return lambda r, c, s: commitforceeditor(r, c, s,
152 return lambda r, c, s: commitforceeditor(r, c, s,
153 finishdesc=finishdesc,
153 finishdesc=finishdesc,
154 extramsg=extramsg,
154 extramsg=extramsg,
155 editform=editform)
155 editform=editform)
156 elif editform:
156 elif editform:
157 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
157 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
158 else:
158 else:
159 return commiteditor
159 return commiteditor
160
160
161 def loglimit(opts):
161 def loglimit(opts):
162 """get the log limit according to option -l/--limit"""
162 """get the log limit according to option -l/--limit"""
163 limit = opts.get('limit')
163 limit = opts.get('limit')
164 if limit:
164 if limit:
165 try:
165 try:
166 limit = int(limit)
166 limit = int(limit)
167 except ValueError:
167 except ValueError:
168 raise util.Abort(_('limit must be a positive integer'))
168 raise util.Abort(_('limit must be a positive integer'))
169 if limit <= 0:
169 if limit <= 0:
170 raise util.Abort(_('limit must be positive'))
170 raise util.Abort(_('limit must be positive'))
171 else:
171 else:
172 limit = None
172 limit = None
173 return limit
173 return limit
174
174
175 def makefilename(repo, pat, node, desc=None,
175 def makefilename(repo, pat, node, desc=None,
176 total=None, seqno=None, revwidth=None, pathname=None):
176 total=None, seqno=None, revwidth=None, pathname=None):
177 node_expander = {
177 node_expander = {
178 'H': lambda: hex(node),
178 'H': lambda: hex(node),
179 'R': lambda: str(repo.changelog.rev(node)),
179 'R': lambda: str(repo.changelog.rev(node)),
180 'h': lambda: short(node),
180 'h': lambda: short(node),
181 'm': lambda: re.sub('[^\w]', '_', str(desc))
181 'm': lambda: re.sub('[^\w]', '_', str(desc))
182 }
182 }
183 expander = {
183 expander = {
184 '%': lambda: '%',
184 '%': lambda: '%',
185 'b': lambda: os.path.basename(repo.root),
185 'b': lambda: os.path.basename(repo.root),
186 }
186 }
187
187
188 try:
188 try:
189 if node:
189 if node:
190 expander.update(node_expander)
190 expander.update(node_expander)
191 if node:
191 if node:
192 expander['r'] = (lambda:
192 expander['r'] = (lambda:
193 str(repo.changelog.rev(node)).zfill(revwidth or 0))
193 str(repo.changelog.rev(node)).zfill(revwidth or 0))
194 if total is not None:
194 if total is not None:
195 expander['N'] = lambda: str(total)
195 expander['N'] = lambda: str(total)
196 if seqno is not None:
196 if seqno is not None:
197 expander['n'] = lambda: str(seqno)
197 expander['n'] = lambda: str(seqno)
198 if total is not None and seqno is not None:
198 if total is not None and seqno is not None:
199 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
199 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
200 if pathname is not None:
200 if pathname is not None:
201 expander['s'] = lambda: os.path.basename(pathname)
201 expander['s'] = lambda: os.path.basename(pathname)
202 expander['d'] = lambda: os.path.dirname(pathname) or '.'
202 expander['d'] = lambda: os.path.dirname(pathname) or '.'
203 expander['p'] = lambda: pathname
203 expander['p'] = lambda: pathname
204
204
205 newname = []
205 newname = []
206 patlen = len(pat)
206 patlen = len(pat)
207 i = 0
207 i = 0
208 while i < patlen:
208 while i < patlen:
209 c = pat[i]
209 c = pat[i]
210 if c == '%':
210 if c == '%':
211 i += 1
211 i += 1
212 c = pat[i]
212 c = pat[i]
213 c = expander[c]()
213 c = expander[c]()
214 newname.append(c)
214 newname.append(c)
215 i += 1
215 i += 1
216 return ''.join(newname)
216 return ''.join(newname)
217 except KeyError, inst:
217 except KeyError, inst:
218 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
218 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
219 inst.args[0])
219 inst.args[0])
220
220
221 def makefileobj(repo, pat, node=None, desc=None, total=None,
221 def makefileobj(repo, pat, node=None, desc=None, total=None,
222 seqno=None, revwidth=None, mode='wb', modemap=None,
222 seqno=None, revwidth=None, mode='wb', modemap=None,
223 pathname=None):
223 pathname=None):
224
224
225 writable = mode not in ('r', 'rb')
225 writable = mode not in ('r', 'rb')
226
226
227 if not pat or pat == '-':
227 if not pat or pat == '-':
228 fp = writable and repo.ui.fout or repo.ui.fin
228 fp = writable and repo.ui.fout or repo.ui.fin
229 if util.safehasattr(fp, 'fileno'):
229 if util.safehasattr(fp, 'fileno'):
230 return os.fdopen(os.dup(fp.fileno()), mode)
230 return os.fdopen(os.dup(fp.fileno()), mode)
231 else:
231 else:
232 # if this fp can't be duped properly, return
232 # if this fp can't be duped properly, return
233 # a dummy object that can be closed
233 # a dummy object that can be closed
234 class wrappedfileobj(object):
234 class wrappedfileobj(object):
235 noop = lambda x: None
235 noop = lambda x: None
236 def __init__(self, f):
236 def __init__(self, f):
237 self.f = f
237 self.f = f
238 def __getattr__(self, attr):
238 def __getattr__(self, attr):
239 if attr == 'close':
239 if attr == 'close':
240 return self.noop
240 return self.noop
241 else:
241 else:
242 return getattr(self.f, attr)
242 return getattr(self.f, attr)
243
243
244 return wrappedfileobj(fp)
244 return wrappedfileobj(fp)
245 if util.safehasattr(pat, 'write') and writable:
245 if util.safehasattr(pat, 'write') and writable:
246 return pat
246 return pat
247 if util.safehasattr(pat, 'read') and 'r' in mode:
247 if util.safehasattr(pat, 'read') and 'r' in mode:
248 return pat
248 return pat
249 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
249 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
250 if modemap is not None:
250 if modemap is not None:
251 mode = modemap.get(fn, mode)
251 mode = modemap.get(fn, mode)
252 if mode == 'wb':
252 if mode == 'wb':
253 modemap[fn] = 'ab'
253 modemap[fn] = 'ab'
254 return open(fn, mode)
254 return open(fn, mode)
255
255
256 def openrevlog(repo, cmd, file_, opts):
256 def openrevlog(repo, cmd, file_, opts):
257 """opens the changelog, manifest, a filelog or a given revlog"""
257 """opens the changelog, manifest, a filelog or a given revlog"""
258 cl = opts['changelog']
258 cl = opts['changelog']
259 mf = opts['manifest']
259 mf = opts['manifest']
260 msg = None
260 msg = None
261 if cl and mf:
261 if cl and mf:
262 msg = _('cannot specify --changelog and --manifest at the same time')
262 msg = _('cannot specify --changelog and --manifest at the same time')
263 elif cl or mf:
263 elif cl or mf:
264 if file_:
264 if file_:
265 msg = _('cannot specify filename with --changelog or --manifest')
265 msg = _('cannot specify filename with --changelog or --manifest')
266 elif not repo:
266 elif not repo:
267 msg = _('cannot specify --changelog or --manifest '
267 msg = _('cannot specify --changelog or --manifest '
268 'without a repository')
268 'without a repository')
269 if msg:
269 if msg:
270 raise util.Abort(msg)
270 raise util.Abort(msg)
271
271
272 r = None
272 r = None
273 if repo:
273 if repo:
274 if cl:
274 if cl:
275 r = repo.unfiltered().changelog
275 r = repo.unfiltered().changelog
276 elif mf:
276 elif mf:
277 r = repo.manifest
277 r = repo.manifest
278 elif file_:
278 elif file_:
279 filelog = repo.file(file_)
279 filelog = repo.file(file_)
280 if len(filelog):
280 if len(filelog):
281 r = filelog
281 r = filelog
282 if not r:
282 if not r:
283 if not file_:
283 if not file_:
284 raise error.CommandError(cmd, _('invalid arguments'))
284 raise error.CommandError(cmd, _('invalid arguments'))
285 if not os.path.isfile(file_):
285 if not os.path.isfile(file_):
286 raise util.Abort(_("revlog '%s' not found") % file_)
286 raise util.Abort(_("revlog '%s' not found") % file_)
287 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
287 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
288 file_[:-2] + ".i")
288 file_[:-2] + ".i")
289 return r
289 return r
290
290
291 def copy(ui, repo, pats, opts, rename=False):
291 def copy(ui, repo, pats, opts, rename=False):
292 # called with the repo lock held
292 # called with the repo lock held
293 #
293 #
294 # hgsep => pathname that uses "/" to separate directories
294 # hgsep => pathname that uses "/" to separate directories
295 # ossep => pathname that uses os.sep to separate directories
295 # ossep => pathname that uses os.sep to separate directories
296 cwd = repo.getcwd()
296 cwd = repo.getcwd()
297 targets = {}
297 targets = {}
298 after = opts.get("after")
298 after = opts.get("after")
299 dryrun = opts.get("dry_run")
299 dryrun = opts.get("dry_run")
300 wctx = repo[None]
300 wctx = repo[None]
301
301
302 def walkpat(pat):
302 def walkpat(pat):
303 srcs = []
303 srcs = []
304 badstates = after and '?' or '?r'
304 badstates = after and '?' or '?r'
305 m = scmutil.match(repo[None], [pat], opts, globbed=True)
305 m = scmutil.match(repo[None], [pat], opts, globbed=True)
306 for abs in repo.walk(m):
306 for abs in repo.walk(m):
307 state = repo.dirstate[abs]
307 state = repo.dirstate[abs]
308 rel = m.rel(abs)
308 rel = m.rel(abs)
309 exact = m.exact(abs)
309 exact = m.exact(abs)
310 if state in badstates:
310 if state in badstates:
311 if exact and state == '?':
311 if exact and state == '?':
312 ui.warn(_('%s: not copying - file is not managed\n') % rel)
312 ui.warn(_('%s: not copying - file is not managed\n') % rel)
313 if exact and state == 'r':
313 if exact and state == 'r':
314 ui.warn(_('%s: not copying - file has been marked for'
314 ui.warn(_('%s: not copying - file has been marked for'
315 ' remove\n') % rel)
315 ' remove\n') % rel)
316 continue
316 continue
317 # abs: hgsep
317 # abs: hgsep
318 # rel: ossep
318 # rel: ossep
319 srcs.append((abs, rel, exact))
319 srcs.append((abs, rel, exact))
320 return srcs
320 return srcs
321
321
322 # abssrc: hgsep
322 # abssrc: hgsep
323 # relsrc: ossep
323 # relsrc: ossep
324 # otarget: ossep
324 # otarget: ossep
325 def copyfile(abssrc, relsrc, otarget, exact):
325 def copyfile(abssrc, relsrc, otarget, exact):
326 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
326 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
327 if '/' in abstarget:
327 if '/' in abstarget:
328 # We cannot normalize abstarget itself, this would prevent
328 # We cannot normalize abstarget itself, this would prevent
329 # case only renames, like a => A.
329 # case only renames, like a => A.
330 abspath, absname = abstarget.rsplit('/', 1)
330 abspath, absname = abstarget.rsplit('/', 1)
331 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
331 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
332 reltarget = repo.pathto(abstarget, cwd)
332 reltarget = repo.pathto(abstarget, cwd)
333 target = repo.wjoin(abstarget)
333 target = repo.wjoin(abstarget)
334 src = repo.wjoin(abssrc)
334 src = repo.wjoin(abssrc)
335 state = repo.dirstate[abstarget]
335 state = repo.dirstate[abstarget]
336
336
337 scmutil.checkportable(ui, abstarget)
337 scmutil.checkportable(ui, abstarget)
338
338
339 # check for collisions
339 # check for collisions
340 prevsrc = targets.get(abstarget)
340 prevsrc = targets.get(abstarget)
341 if prevsrc is not None:
341 if prevsrc is not None:
342 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
342 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
343 (reltarget, repo.pathto(abssrc, cwd),
343 (reltarget, repo.pathto(abssrc, cwd),
344 repo.pathto(prevsrc, cwd)))
344 repo.pathto(prevsrc, cwd)))
345 return
345 return
346
346
347 # check for overwrites
347 # check for overwrites
348 exists = os.path.lexists(target)
348 exists = os.path.lexists(target)
349 samefile = False
349 samefile = False
350 if exists and abssrc != abstarget:
350 if exists and abssrc != abstarget:
351 if (repo.dirstate.normalize(abssrc) ==
351 if (repo.dirstate.normalize(abssrc) ==
352 repo.dirstate.normalize(abstarget)):
352 repo.dirstate.normalize(abstarget)):
353 if not rename:
353 if not rename:
354 ui.warn(_("%s: can't copy - same file\n") % reltarget)
354 ui.warn(_("%s: can't copy - same file\n") % reltarget)
355 return
355 return
356 exists = False
356 exists = False
357 samefile = True
357 samefile = True
358
358
359 if not after and exists or after and state in 'mn':
359 if not after and exists or after and state in 'mn':
360 if not opts['force']:
360 if not opts['force']:
361 ui.warn(_('%s: not overwriting - file exists\n') %
361 ui.warn(_('%s: not overwriting - file exists\n') %
362 reltarget)
362 reltarget)
363 return
363 return
364
364
365 if after:
365 if after:
366 if not exists:
366 if not exists:
367 if rename:
367 if rename:
368 ui.warn(_('%s: not recording move - %s does not exist\n') %
368 ui.warn(_('%s: not recording move - %s does not exist\n') %
369 (relsrc, reltarget))
369 (relsrc, reltarget))
370 else:
370 else:
371 ui.warn(_('%s: not recording copy - %s does not exist\n') %
371 ui.warn(_('%s: not recording copy - %s does not exist\n') %
372 (relsrc, reltarget))
372 (relsrc, reltarget))
373 return
373 return
374 elif not dryrun:
374 elif not dryrun:
375 try:
375 try:
376 if exists:
376 if exists:
377 os.unlink(target)
377 os.unlink(target)
378 targetdir = os.path.dirname(target) or '.'
378 targetdir = os.path.dirname(target) or '.'
379 if not os.path.isdir(targetdir):
379 if not os.path.isdir(targetdir):
380 os.makedirs(targetdir)
380 os.makedirs(targetdir)
381 if samefile:
381 if samefile:
382 tmp = target + "~hgrename"
382 tmp = target + "~hgrename"
383 os.rename(src, tmp)
383 os.rename(src, tmp)
384 os.rename(tmp, target)
384 os.rename(tmp, target)
385 else:
385 else:
386 util.copyfile(src, target)
386 util.copyfile(src, target)
387 srcexists = True
387 srcexists = True
388 except IOError, inst:
388 except IOError, inst:
389 if inst.errno == errno.ENOENT:
389 if inst.errno == errno.ENOENT:
390 ui.warn(_('%s: deleted in working copy\n') % relsrc)
390 ui.warn(_('%s: deleted in working copy\n') % relsrc)
391 srcexists = False
391 srcexists = False
392 else:
392 else:
393 ui.warn(_('%s: cannot copy - %s\n') %
393 ui.warn(_('%s: cannot copy - %s\n') %
394 (relsrc, inst.strerror))
394 (relsrc, inst.strerror))
395 return True # report a failure
395 return True # report a failure
396
396
397 if ui.verbose or not exact:
397 if ui.verbose or not exact:
398 if rename:
398 if rename:
399 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
399 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
400 else:
400 else:
401 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
401 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
402
402
403 targets[abstarget] = abssrc
403 targets[abstarget] = abssrc
404
404
405 # fix up dirstate
405 # fix up dirstate
406 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
406 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
407 dryrun=dryrun, cwd=cwd)
407 dryrun=dryrun, cwd=cwd)
408 if rename and not dryrun:
408 if rename and not dryrun:
409 if not after and srcexists and not samefile:
409 if not after and srcexists and not samefile:
410 util.unlinkpath(repo.wjoin(abssrc))
410 util.unlinkpath(repo.wjoin(abssrc))
411 wctx.forget([abssrc])
411 wctx.forget([abssrc])
412
412
413 # pat: ossep
413 # pat: ossep
414 # dest ossep
414 # dest ossep
415 # srcs: list of (hgsep, hgsep, ossep, bool)
415 # srcs: list of (hgsep, hgsep, ossep, bool)
416 # return: function that takes hgsep and returns ossep
416 # return: function that takes hgsep and returns ossep
417 def targetpathfn(pat, dest, srcs):
417 def targetpathfn(pat, dest, srcs):
418 if os.path.isdir(pat):
418 if os.path.isdir(pat):
419 abspfx = pathutil.canonpath(repo.root, cwd, pat)
419 abspfx = pathutil.canonpath(repo.root, cwd, pat)
420 abspfx = util.localpath(abspfx)
420 abspfx = util.localpath(abspfx)
421 if destdirexists:
421 if destdirexists:
422 striplen = len(os.path.split(abspfx)[0])
422 striplen = len(os.path.split(abspfx)[0])
423 else:
423 else:
424 striplen = len(abspfx)
424 striplen = len(abspfx)
425 if striplen:
425 if striplen:
426 striplen += len(os.sep)
426 striplen += len(os.sep)
427 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
427 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
428 elif destdirexists:
428 elif destdirexists:
429 res = lambda p: os.path.join(dest,
429 res = lambda p: os.path.join(dest,
430 os.path.basename(util.localpath(p)))
430 os.path.basename(util.localpath(p)))
431 else:
431 else:
432 res = lambda p: dest
432 res = lambda p: dest
433 return res
433 return res
434
434
435 # pat: ossep
435 # pat: ossep
436 # dest ossep
436 # dest ossep
437 # srcs: list of (hgsep, hgsep, ossep, bool)
437 # srcs: list of (hgsep, hgsep, ossep, bool)
438 # return: function that takes hgsep and returns ossep
438 # return: function that takes hgsep and returns ossep
439 def targetpathafterfn(pat, dest, srcs):
439 def targetpathafterfn(pat, dest, srcs):
440 if matchmod.patkind(pat):
440 if matchmod.patkind(pat):
441 # a mercurial pattern
441 # a mercurial pattern
442 res = lambda p: os.path.join(dest,
442 res = lambda p: os.path.join(dest,
443 os.path.basename(util.localpath(p)))
443 os.path.basename(util.localpath(p)))
444 else:
444 else:
445 abspfx = pathutil.canonpath(repo.root, cwd, pat)
445 abspfx = pathutil.canonpath(repo.root, cwd, pat)
446 if len(abspfx) < len(srcs[0][0]):
446 if len(abspfx) < len(srcs[0][0]):
447 # A directory. Either the target path contains the last
447 # A directory. Either the target path contains the last
448 # component of the source path or it does not.
448 # component of the source path or it does not.
449 def evalpath(striplen):
449 def evalpath(striplen):
450 score = 0
450 score = 0
451 for s in srcs:
451 for s in srcs:
452 t = os.path.join(dest, util.localpath(s[0])[striplen:])
452 t = os.path.join(dest, util.localpath(s[0])[striplen:])
453 if os.path.lexists(t):
453 if os.path.lexists(t):
454 score += 1
454 score += 1
455 return score
455 return score
456
456
457 abspfx = util.localpath(abspfx)
457 abspfx = util.localpath(abspfx)
458 striplen = len(abspfx)
458 striplen = len(abspfx)
459 if striplen:
459 if striplen:
460 striplen += len(os.sep)
460 striplen += len(os.sep)
461 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
461 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
462 score = evalpath(striplen)
462 score = evalpath(striplen)
463 striplen1 = len(os.path.split(abspfx)[0])
463 striplen1 = len(os.path.split(abspfx)[0])
464 if striplen1:
464 if striplen1:
465 striplen1 += len(os.sep)
465 striplen1 += len(os.sep)
466 if evalpath(striplen1) > score:
466 if evalpath(striplen1) > score:
467 striplen = striplen1
467 striplen = striplen1
468 res = lambda p: os.path.join(dest,
468 res = lambda p: os.path.join(dest,
469 util.localpath(p)[striplen:])
469 util.localpath(p)[striplen:])
470 else:
470 else:
471 # a file
471 # a file
472 if destdirexists:
472 if destdirexists:
473 res = lambda p: os.path.join(dest,
473 res = lambda p: os.path.join(dest,
474 os.path.basename(util.localpath(p)))
474 os.path.basename(util.localpath(p)))
475 else:
475 else:
476 res = lambda p: dest
476 res = lambda p: dest
477 return res
477 return res
478
478
479
479
480 pats = scmutil.expandpats(pats)
480 pats = scmutil.expandpats(pats)
481 if not pats:
481 if not pats:
482 raise util.Abort(_('no source or destination specified'))
482 raise util.Abort(_('no source or destination specified'))
483 if len(pats) == 1:
483 if len(pats) == 1:
484 raise util.Abort(_('no destination specified'))
484 raise util.Abort(_('no destination specified'))
485 dest = pats.pop()
485 dest = pats.pop()
486 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
486 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
487 if not destdirexists:
487 if not destdirexists:
488 if len(pats) > 1 or matchmod.patkind(pats[0]):
488 if len(pats) > 1 or matchmod.patkind(pats[0]):
489 raise util.Abort(_('with multiple sources, destination must be an '
489 raise util.Abort(_('with multiple sources, destination must be an '
490 'existing directory'))
490 'existing directory'))
491 if util.endswithsep(dest):
491 if util.endswithsep(dest):
492 raise util.Abort(_('destination %s is not a directory') % dest)
492 raise util.Abort(_('destination %s is not a directory') % dest)
493
493
494 tfn = targetpathfn
494 tfn = targetpathfn
495 if after:
495 if after:
496 tfn = targetpathafterfn
496 tfn = targetpathafterfn
497 copylist = []
497 copylist = []
498 for pat in pats:
498 for pat in pats:
499 srcs = walkpat(pat)
499 srcs = walkpat(pat)
500 if not srcs:
500 if not srcs:
501 continue
501 continue
502 copylist.append((tfn(pat, dest, srcs), srcs))
502 copylist.append((tfn(pat, dest, srcs), srcs))
503 if not copylist:
503 if not copylist:
504 raise util.Abort(_('no files to copy'))
504 raise util.Abort(_('no files to copy'))
505
505
506 errors = 0
506 errors = 0
507 for targetpath, srcs in copylist:
507 for targetpath, srcs in copylist:
508 for abssrc, relsrc, exact in srcs:
508 for abssrc, relsrc, exact in srcs:
509 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
509 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
510 errors += 1
510 errors += 1
511
511
512 if errors:
512 if errors:
513 ui.warn(_('(consider using --after)\n'))
513 ui.warn(_('(consider using --after)\n'))
514
514
515 return errors != 0
515 return errors != 0
516
516
517 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
517 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
518 runargs=None, appendpid=False):
518 runargs=None, appendpid=False):
519 '''Run a command as a service.'''
519 '''Run a command as a service.'''
520
520
521 def writepid(pid):
521 def writepid(pid):
522 if opts['pid_file']:
522 if opts['pid_file']:
523 mode = appendpid and 'a' or 'w'
523 mode = appendpid and 'a' or 'w'
524 fp = open(opts['pid_file'], mode)
524 fp = open(opts['pid_file'], mode)
525 fp.write(str(pid) + '\n')
525 fp.write(str(pid) + '\n')
526 fp.close()
526 fp.close()
527
527
528 if opts['daemon'] and not opts['daemon_pipefds']:
528 if opts['daemon'] and not opts['daemon_pipefds']:
529 # Signal child process startup with file removal
529 # Signal child process startup with file removal
530 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
530 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
531 os.close(lockfd)
531 os.close(lockfd)
532 try:
532 try:
533 if not runargs:
533 if not runargs:
534 runargs = util.hgcmd() + sys.argv[1:]
534 runargs = util.hgcmd() + sys.argv[1:]
535 runargs.append('--daemon-pipefds=%s' % lockpath)
535 runargs.append('--daemon-pipefds=%s' % lockpath)
536 # Don't pass --cwd to the child process, because we've already
536 # Don't pass --cwd to the child process, because we've already
537 # changed directory.
537 # changed directory.
538 for i in xrange(1, len(runargs)):
538 for i in xrange(1, len(runargs)):
539 if runargs[i].startswith('--cwd='):
539 if runargs[i].startswith('--cwd='):
540 del runargs[i]
540 del runargs[i]
541 break
541 break
542 elif runargs[i].startswith('--cwd'):
542 elif runargs[i].startswith('--cwd'):
543 del runargs[i:i + 2]
543 del runargs[i:i + 2]
544 break
544 break
545 def condfn():
545 def condfn():
546 return not os.path.exists(lockpath)
546 return not os.path.exists(lockpath)
547 pid = util.rundetached(runargs, condfn)
547 pid = util.rundetached(runargs, condfn)
548 if pid < 0:
548 if pid < 0:
549 raise util.Abort(_('child process failed to start'))
549 raise util.Abort(_('child process failed to start'))
550 writepid(pid)
550 writepid(pid)
551 finally:
551 finally:
552 try:
552 try:
553 os.unlink(lockpath)
553 os.unlink(lockpath)
554 except OSError, e:
554 except OSError, e:
555 if e.errno != errno.ENOENT:
555 if e.errno != errno.ENOENT:
556 raise
556 raise
557 if parentfn:
557 if parentfn:
558 return parentfn(pid)
558 return parentfn(pid)
559 else:
559 else:
560 return
560 return
561
561
562 if initfn:
562 if initfn:
563 initfn()
563 initfn()
564
564
565 if not opts['daemon']:
565 if not opts['daemon']:
566 writepid(os.getpid())
566 writepid(os.getpid())
567
567
568 if opts['daemon_pipefds']:
568 if opts['daemon_pipefds']:
569 lockpath = opts['daemon_pipefds']
569 lockpath = opts['daemon_pipefds']
570 try:
570 try:
571 os.setsid()
571 os.setsid()
572 except AttributeError:
572 except AttributeError:
573 pass
573 pass
574 os.unlink(lockpath)
574 os.unlink(lockpath)
575 util.hidewindow()
575 util.hidewindow()
576 sys.stdout.flush()
576 sys.stdout.flush()
577 sys.stderr.flush()
577 sys.stderr.flush()
578
578
579 nullfd = os.open(os.devnull, os.O_RDWR)
579 nullfd = os.open(os.devnull, os.O_RDWR)
580 logfilefd = nullfd
580 logfilefd = nullfd
581 if logfile:
581 if logfile:
582 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
582 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
583 os.dup2(nullfd, 0)
583 os.dup2(nullfd, 0)
584 os.dup2(logfilefd, 1)
584 os.dup2(logfilefd, 1)
585 os.dup2(logfilefd, 2)
585 os.dup2(logfilefd, 2)
586 if nullfd not in (0, 1, 2):
586 if nullfd not in (0, 1, 2):
587 os.close(nullfd)
587 os.close(nullfd)
588 if logfile and logfilefd not in (0, 1, 2):
588 if logfile and logfilefd not in (0, 1, 2):
589 os.close(logfilefd)
589 os.close(logfilefd)
590
590
591 if runfn:
591 if runfn:
592 return runfn()
592 return runfn()
593
593
594 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
594 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
595 """Utility function used by commands.import to import a single patch
595 """Utility function used by commands.import to import a single patch
596
596
597 This function is explicitly defined here to help the evolve extension to
597 This function is explicitly defined here to help the evolve extension to
598 wrap this part of the import logic.
598 wrap this part of the import logic.
599
599
600 The API is currently a bit ugly because it a simple code translation from
600 The API is currently a bit ugly because it a simple code translation from
601 the import command. Feel free to make it better.
601 the import command. Feel free to make it better.
602
602
603 :hunk: a patch (as a binary string)
603 :hunk: a patch (as a binary string)
604 :parents: nodes that will be parent of the created commit
604 :parents: nodes that will be parent of the created commit
605 :opts: the full dict of option passed to the import command
605 :opts: the full dict of option passed to the import command
606 :msgs: list to save commit message to.
606 :msgs: list to save commit message to.
607 (used in case we need to save it when failing)
607 (used in case we need to save it when failing)
608 :updatefunc: a function that update a repo to a given node
608 :updatefunc: a function that update a repo to a given node
609 updatefunc(<repo>, <node>)
609 updatefunc(<repo>, <node>)
610 """
610 """
611 tmpname, message, user, date, branch, nodeid, p1, p2 = \
611 tmpname, message, user, date, branch, nodeid, p1, p2 = \
612 patch.extract(ui, hunk)
612 patch.extract(ui, hunk)
613
613
614 update = not opts.get('bypass')
614 update = not opts.get('bypass')
615 strip = opts["strip"]
615 strip = opts["strip"]
616 sim = float(opts.get('similarity') or 0)
616 sim = float(opts.get('similarity') or 0)
617 if not tmpname:
617 if not tmpname:
618 return (None, None, False)
618 return (None, None, False)
619 msg = _('applied to working directory')
619 msg = _('applied to working directory')
620
620
621 rejects = False
621 rejects = False
622
622
623 try:
623 try:
624 cmdline_message = logmessage(ui, opts)
624 cmdline_message = logmessage(ui, opts)
625 if cmdline_message:
625 if cmdline_message:
626 # pickup the cmdline msg
626 # pickup the cmdline msg
627 message = cmdline_message
627 message = cmdline_message
628 elif message:
628 elif message:
629 # pickup the patch msg
629 # pickup the patch msg
630 message = message.strip()
630 message = message.strip()
631 else:
631 else:
632 # launch the editor
632 # launch the editor
633 message = None
633 message = None
634 ui.debug('message:\n%s\n' % message)
634 ui.debug('message:\n%s\n' % message)
635
635
636 if len(parents) == 1:
636 if len(parents) == 1:
637 parents.append(repo[nullid])
637 parents.append(repo[nullid])
638 if opts.get('exact'):
638 if opts.get('exact'):
639 if not nodeid or not p1:
639 if not nodeid or not p1:
640 raise util.Abort(_('not a Mercurial patch'))
640 raise util.Abort(_('not a Mercurial patch'))
641 p1 = repo[p1]
641 p1 = repo[p1]
642 p2 = repo[p2 or nullid]
642 p2 = repo[p2 or nullid]
643 elif p2:
643 elif p2:
644 try:
644 try:
645 p1 = repo[p1]
645 p1 = repo[p1]
646 p2 = repo[p2]
646 p2 = repo[p2]
647 # Without any options, consider p2 only if the
647 # Without any options, consider p2 only if the
648 # patch is being applied on top of the recorded
648 # patch is being applied on top of the recorded
649 # first parent.
649 # first parent.
650 if p1 != parents[0]:
650 if p1 != parents[0]:
651 p1 = parents[0]
651 p1 = parents[0]
652 p2 = repo[nullid]
652 p2 = repo[nullid]
653 except error.RepoError:
653 except error.RepoError:
654 p1, p2 = parents
654 p1, p2 = parents
655 if p2.node() == nullid:
655 if p2.node() == nullid:
656 ui.warn(_("warning: import the patch as a normal revision\n"
656 ui.warn(_("warning: import the patch as a normal revision\n"
657 "(use --exact to import the patch as a merge)\n"))
657 "(use --exact to import the patch as a merge)\n"))
658 else:
658 else:
659 p1, p2 = parents
659 p1, p2 = parents
660
660
661 n = None
661 n = None
662 if update:
662 if update:
663 repo.dirstate.beginparentchange()
663 repo.dirstate.beginparentchange()
664 if p1 != parents[0]:
664 if p1 != parents[0]:
665 updatefunc(repo, p1.node())
665 updatefunc(repo, p1.node())
666 if p2 != parents[1]:
666 if p2 != parents[1]:
667 repo.setparents(p1.node(), p2.node())
667 repo.setparents(p1.node(), p2.node())
668
668
669 if opts.get('exact') or opts.get('import_branch'):
669 if opts.get('exact') or opts.get('import_branch'):
670 repo.dirstate.setbranch(branch or 'default')
670 repo.dirstate.setbranch(branch or 'default')
671
671
672 partial = opts.get('partial', False)
672 partial = opts.get('partial', False)
673 files = set()
673 files = set()
674 try:
674 try:
675 patch.patch(ui, repo, tmpname, strip=strip, files=files,
675 patch.patch(ui, repo, tmpname, strip=strip, files=files,
676 eolmode=None, similarity=sim / 100.0)
676 eolmode=None, similarity=sim / 100.0)
677 except patch.PatchError, e:
677 except patch.PatchError, e:
678 if not partial:
678 if not partial:
679 raise util.Abort(str(e))
679 raise util.Abort(str(e))
680 if partial:
680 if partial:
681 rejects = True
681 rejects = True
682
682
683 files = list(files)
683 files = list(files)
684 if opts.get('no_commit'):
684 if opts.get('no_commit'):
685 if message:
685 if message:
686 msgs.append(message)
686 msgs.append(message)
687 else:
687 else:
688 if opts.get('exact') or p2:
688 if opts.get('exact') or p2:
689 # If you got here, you either use --force and know what
689 # If you got here, you either use --force and know what
690 # you are doing or used --exact or a merge patch while
690 # you are doing or used --exact or a merge patch while
691 # being updated to its first parent.
691 # being updated to its first parent.
692 m = None
692 m = None
693 else:
693 else:
694 m = scmutil.matchfiles(repo, files or [])
694 m = scmutil.matchfiles(repo, files or [])
695 editform = mergeeditform(repo[None], 'import.normal')
695 editform = mergeeditform(repo[None], 'import.normal')
696 if opts.get('exact'):
696 if opts.get('exact'):
697 editor = None
697 editor = None
698 else:
698 else:
699 editor = getcommiteditor(editform=editform, **opts)
699 editor = getcommiteditor(editform=editform, **opts)
700 n = repo.commit(message, opts.get('user') or user,
700 n = repo.commit(message, opts.get('user') or user,
701 opts.get('date') or date, match=m,
701 opts.get('date') or date, match=m,
702 editor=editor, force=partial)
702 editor=editor, force=partial)
703 repo.dirstate.endparentchange()
703 repo.dirstate.endparentchange()
704 else:
704 else:
705 if opts.get('exact') or opts.get('import_branch'):
705 if opts.get('exact') or opts.get('import_branch'):
706 branch = branch or 'default'
706 branch = branch or 'default'
707 else:
707 else:
708 branch = p1.branch()
708 branch = p1.branch()
709 store = patch.filestore()
709 store = patch.filestore()
710 try:
710 try:
711 files = set()
711 files = set()
712 try:
712 try:
713 patch.patchrepo(ui, repo, p1, store, tmpname, strip,
713 patch.patchrepo(ui, repo, p1, store, tmpname, strip,
714 files, eolmode=None)
714 files, eolmode=None)
715 except patch.PatchError, e:
715 except patch.PatchError, e:
716 raise util.Abort(str(e))
716 raise util.Abort(str(e))
717 if opts.get('exact'):
717 if opts.get('exact'):
718 editor = None
718 editor = None
719 else:
719 else:
720 editor = getcommiteditor(editform='import.bypass')
720 editor = getcommiteditor(editform='import.bypass')
721 memctx = context.makememctx(repo, (p1.node(), p2.node()),
721 memctx = context.makememctx(repo, (p1.node(), p2.node()),
722 message,
722 message,
723 opts.get('user') or user,
723 opts.get('user') or user,
724 opts.get('date') or date,
724 opts.get('date') or date,
725 branch, files, store,
725 branch, files, store,
726 editor=editor)
726 editor=editor)
727 n = memctx.commit()
727 n = memctx.commit()
728 finally:
728 finally:
729 store.close()
729 store.close()
730 if opts.get('exact') and opts.get('no_commit'):
730 if opts.get('exact') and opts.get('no_commit'):
731 # --exact with --no-commit is still useful in that it does merge
731 # --exact with --no-commit is still useful in that it does merge
732 # and branch bits
732 # and branch bits
733 ui.warn(_("warning: can't check exact import with --no-commit\n"))
733 ui.warn(_("warning: can't check exact import with --no-commit\n"))
734 elif opts.get('exact') and hex(n) != nodeid:
734 elif opts.get('exact') and hex(n) != nodeid:
735 raise util.Abort(_('patch is damaged or loses information'))
735 raise util.Abort(_('patch is damaged or loses information'))
736 if n:
736 if n:
737 # i18n: refers to a short changeset id
737 # i18n: refers to a short changeset id
738 msg = _('created %s') % short(n)
738 msg = _('created %s') % short(n)
739 return (msg, n, rejects)
739 return (msg, n, rejects)
740 finally:
740 finally:
741 os.unlink(tmpname)
741 os.unlink(tmpname)
742
742
743 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
743 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
744 opts=None):
744 opts=None):
745 '''export changesets as hg patches.'''
745 '''export changesets as hg patches.'''
746
746
747 total = len(revs)
747 total = len(revs)
748 revwidth = max([len(str(rev)) for rev in revs])
748 revwidth = max([len(str(rev)) for rev in revs])
749 filemode = {}
749 filemode = {}
750
750
751 def single(rev, seqno, fp):
751 def single(rev, seqno, fp):
752 ctx = repo[rev]
752 ctx = repo[rev]
753 node = ctx.node()
753 node = ctx.node()
754 parents = [p.node() for p in ctx.parents() if p]
754 parents = [p.node() for p in ctx.parents() if p]
755 branch = ctx.branch()
755 branch = ctx.branch()
756 if switch_parent:
756 if switch_parent:
757 parents.reverse()
757 parents.reverse()
758 prev = (parents and parents[0]) or nullid
758 prev = (parents and parents[0]) or nullid
759
759
760 shouldclose = False
760 shouldclose = False
761 if not fp and len(template) > 0:
761 if not fp and len(template) > 0:
762 desc_lines = ctx.description().rstrip().split('\n')
762 desc_lines = ctx.description().rstrip().split('\n')
763 desc = desc_lines[0] #Commit always has a first line.
763 desc = desc_lines[0] #Commit always has a first line.
764 fp = makefileobj(repo, template, node, desc=desc, total=total,
764 fp = makefileobj(repo, template, node, desc=desc, total=total,
765 seqno=seqno, revwidth=revwidth, mode='wb',
765 seqno=seqno, revwidth=revwidth, mode='wb',
766 modemap=filemode)
766 modemap=filemode)
767 if fp != template:
767 if fp != template:
768 shouldclose = True
768 shouldclose = True
769 if fp and fp != sys.stdout and util.safehasattr(fp, 'name'):
769 if fp and fp != sys.stdout and util.safehasattr(fp, 'name'):
770 repo.ui.note("%s\n" % fp.name)
770 repo.ui.note("%s\n" % fp.name)
771
771
772 if not fp:
772 if not fp:
773 write = repo.ui.write
773 write = repo.ui.write
774 else:
774 else:
775 def write(s, **kw):
775 def write(s, **kw):
776 fp.write(s)
776 fp.write(s)
777
777
778
778
779 write("# HG changeset patch\n")
779 write("# HG changeset patch\n")
780 write("# User %s\n" % ctx.user())
780 write("# User %s\n" % ctx.user())
781 write("# Date %d %d\n" % ctx.date())
781 write("# Date %d %d\n" % ctx.date())
782 write("# %s\n" % util.datestr(ctx.date()))
782 write("# %s\n" % util.datestr(ctx.date()))
783 if branch and branch != 'default':
783 if branch and branch != 'default':
784 write("# Branch %s\n" % branch)
784 write("# Branch %s\n" % branch)
785 write("# Node ID %s\n" % hex(node))
785 write("# Node ID %s\n" % hex(node))
786 write("# Parent %s\n" % hex(prev))
786 write("# Parent %s\n" % hex(prev))
787 if len(parents) > 1:
787 if len(parents) > 1:
788 write("# Parent %s\n" % hex(parents[1]))
788 write("# Parent %s\n" % hex(parents[1]))
789 write(ctx.description().rstrip())
789 write(ctx.description().rstrip())
790 write("\n\n")
790 write("\n\n")
791
791
792 for chunk, label in patch.diffui(repo, prev, node, opts=opts):
792 for chunk, label in patch.diffui(repo, prev, node, opts=opts):
793 write(chunk, label=label)
793 write(chunk, label=label)
794
794
795 if shouldclose:
795 if shouldclose:
796 fp.close()
796 fp.close()
797
797
798 for seqno, rev in enumerate(revs):
798 for seqno, rev in enumerate(revs):
799 single(rev, seqno + 1, fp)
799 single(rev, seqno + 1, fp)
800
800
801 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
801 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
802 changes=None, stat=False, fp=None, prefix='',
802 changes=None, stat=False, fp=None, prefix='',
803 listsubrepos=False):
803 listsubrepos=False):
804 '''show diff or diffstat.'''
804 '''show diff or diffstat.'''
805 if fp is None:
805 if fp is None:
806 write = ui.write
806 write = ui.write
807 else:
807 else:
808 def write(s, **kw):
808 def write(s, **kw):
809 fp.write(s)
809 fp.write(s)
810
810
811 if stat:
811 if stat:
812 diffopts = diffopts.copy(context=0)
812 diffopts = diffopts.copy(context=0)
813 width = 80
813 width = 80
814 if not ui.plain():
814 if not ui.plain():
815 width = ui.termwidth()
815 width = ui.termwidth()
816 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
816 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
817 prefix=prefix)
817 prefix=prefix)
818 for chunk, label in patch.diffstatui(util.iterlines(chunks),
818 for chunk, label in patch.diffstatui(util.iterlines(chunks),
819 width=width,
819 width=width,
820 git=diffopts.git):
820 git=diffopts.git):
821 write(chunk, label=label)
821 write(chunk, label=label)
822 else:
822 else:
823 for chunk, label in patch.diffui(repo, node1, node2, match,
823 for chunk, label in patch.diffui(repo, node1, node2, match,
824 changes, diffopts, prefix=prefix):
824 changes, diffopts, prefix=prefix):
825 write(chunk, label=label)
825 write(chunk, label=label)
826
826
827 if listsubrepos:
827 if listsubrepos:
828 ctx1 = repo[node1]
828 ctx1 = repo[node1]
829 ctx2 = repo[node2]
829 ctx2 = repo[node2]
830 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
830 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
831 tempnode2 = node2
831 tempnode2 = node2
832 try:
832 try:
833 if node2 is not None:
833 if node2 is not None:
834 tempnode2 = ctx2.substate[subpath][1]
834 tempnode2 = ctx2.substate[subpath][1]
835 except KeyError:
835 except KeyError:
836 # A subrepo that existed in node1 was deleted between node1 and
836 # A subrepo that existed in node1 was deleted between node1 and
837 # node2 (inclusive). Thus, ctx2's substate won't contain that
837 # node2 (inclusive). Thus, ctx2's substate won't contain that
838 # subpath. The best we can do is to ignore it.
838 # subpath. The best we can do is to ignore it.
839 tempnode2 = None
839 tempnode2 = None
840 submatch = matchmod.narrowmatcher(subpath, match)
840 submatch = matchmod.narrowmatcher(subpath, match)
841 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
841 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
842 stat=stat, fp=fp, prefix=prefix)
842 stat=stat, fp=fp, prefix=prefix)
843
843
844 class changeset_printer(object):
844 class changeset_printer(object):
845 '''show changeset information when templating not requested.'''
845 '''show changeset information when templating not requested.'''
846
846
847 def __init__(self, ui, repo, matchfn, diffopts, buffered):
847 def __init__(self, ui, repo, matchfn, diffopts, buffered):
848 self.ui = ui
848 self.ui = ui
849 self.repo = repo
849 self.repo = repo
850 self.buffered = buffered
850 self.buffered = buffered
851 self.matchfn = matchfn
851 self.matchfn = matchfn
852 self.diffopts = diffopts
852 self.diffopts = diffopts
853 self.header = {}
853 self.header = {}
854 self.hunk = {}
854 self.hunk = {}
855 self.lastheader = None
855 self.lastheader = None
856 self.footer = None
856 self.footer = None
857
857
858 def flush(self, rev):
858 def flush(self, rev):
859 if rev in self.header:
859 if rev in self.header:
860 h = self.header[rev]
860 h = self.header[rev]
861 if h != self.lastheader:
861 if h != self.lastheader:
862 self.lastheader = h
862 self.lastheader = h
863 self.ui.write(h)
863 self.ui.write(h)
864 del self.header[rev]
864 del self.header[rev]
865 if rev in self.hunk:
865 if rev in self.hunk:
866 self.ui.write(self.hunk[rev])
866 self.ui.write(self.hunk[rev])
867 del self.hunk[rev]
867 del self.hunk[rev]
868 return 1
868 return 1
869 return 0
869 return 0
870
870
871 def close(self):
871 def close(self):
872 if self.footer:
872 if self.footer:
873 self.ui.write(self.footer)
873 self.ui.write(self.footer)
874
874
875 def show(self, ctx, copies=None, matchfn=None, **props):
875 def show(self, ctx, copies=None, matchfn=None, **props):
876 if self.buffered:
876 if self.buffered:
877 self.ui.pushbuffer()
877 self.ui.pushbuffer()
878 self._show(ctx, copies, matchfn, props)
878 self._show(ctx, copies, matchfn, props)
879 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
879 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
880 else:
880 else:
881 self._show(ctx, copies, matchfn, props)
881 self._show(ctx, copies, matchfn, props)
882
882
883 def _show(self, ctx, copies, matchfn, props):
883 def _show(self, ctx, copies, matchfn, props):
884 '''show a single changeset or file revision'''
884 '''show a single changeset or file revision'''
885 changenode = ctx.node()
885 changenode = ctx.node()
886 rev = ctx.rev()
886 rev = ctx.rev()
887
887
888 if self.ui.quiet:
888 if self.ui.quiet:
889 self.ui.write("%d:%s\n" % (rev, short(changenode)),
889 self.ui.write("%d:%s\n" % (rev, short(changenode)),
890 label='log.node')
890 label='log.node')
891 return
891 return
892
892
893 log = self.repo.changelog
893 log = self.repo.changelog
894 date = util.datestr(ctx.date())
894 date = util.datestr(ctx.date())
895
895
896 hexfunc = self.ui.debugflag and hex or short
896 hexfunc = self.ui.debugflag and hex or short
897
897
898 parents = [(p, hexfunc(log.node(p)))
898 parents = [(p, hexfunc(log.node(p)))
899 for p in self._meaningful_parentrevs(log, rev)]
899 for p in self._meaningful_parentrevs(log, rev)]
900
900
901 # i18n: column positioning for "hg log"
901 # i18n: column positioning for "hg log"
902 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
902 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
903 label='log.changeset changeset.%s' % ctx.phasestr())
903 label='log.changeset changeset.%s' % ctx.phasestr())
904
904
905 # branches are shown first before any other names due to backwards
905 # branches are shown first before any other names due to backwards
906 # compatibility
906 # compatibility
907 branch = ctx.branch()
907 branch = ctx.branch()
908 # don't show the default branch name
908 # don't show the default branch name
909 if branch != 'default':
909 if branch != 'default':
910 # i18n: column positioning for "hg log"
910 # i18n: column positioning for "hg log"
911 self.ui.write(_("branch: %s\n") % branch,
911 self.ui.write(_("branch: %s\n") % branch,
912 label='log.branch')
912 label='log.branch')
913
913
914 for name, ns in self.repo.names.iteritems():
914 for name, ns in self.repo.names.iteritems():
915 # branches has special logic already handled above, so here we just
915 # branches has special logic already handled above, so here we just
916 # skip it
916 # skip it
917 if name == 'branches':
917 if name == 'branches':
918 continue
918 continue
919 # we will use the templatename as the color name since those two
919 # we will use the templatename as the color name since those two
920 # should be the same
920 # should be the same
921 for name in ns.names(self.repo, changenode):
921 for name in ns.names(self.repo, changenode):
922 # i18n: column positioning for "hg log"
922 # i18n: column positioning for "hg log"
923 name = _(("%s:" % ns.logname).ljust(13) + "%s\n") % name
923 name = _(("%s:" % ns.logname).ljust(13) + "%s\n") % name
924 self.ui.write("%s" % name, label='log.%s' % ns.colorname)
924 self.ui.write("%s" % name, label='log.%s' % ns.colorname)
925 if self.ui.debugflag:
925 if self.ui.debugflag:
926 # i18n: column positioning for "hg log"
926 # i18n: column positioning for "hg log"
927 self.ui.write(_("phase: %s\n") % _(ctx.phasestr()),
927 self.ui.write(_("phase: %s\n") % _(ctx.phasestr()),
928 label='log.phase')
928 label='log.phase')
929 for parent in parents:
929 for parent in parents:
930 label = 'log.parent changeset.%s' % self.repo[parent[0]].phasestr()
930 label = 'log.parent changeset.%s' % self.repo[parent[0]].phasestr()
931 # i18n: column positioning for "hg log"
931 # i18n: column positioning for "hg log"
932 self.ui.write(_("parent: %d:%s\n") % parent,
932 self.ui.write(_("parent: %d:%s\n") % parent,
933 label=label)
933 label=label)
934
934
935 if self.ui.debugflag:
935 if self.ui.debugflag:
936 mnode = ctx.manifestnode()
936 mnode = ctx.manifestnode()
937 # i18n: column positioning for "hg log"
937 # i18n: column positioning for "hg log"
938 self.ui.write(_("manifest: %d:%s\n") %
938 self.ui.write(_("manifest: %d:%s\n") %
939 (self.repo.manifest.rev(mnode), hex(mnode)),
939 (self.repo.manifest.rev(mnode), hex(mnode)),
940 label='ui.debug log.manifest')
940 label='ui.debug log.manifest')
941 # i18n: column positioning for "hg log"
941 # i18n: column positioning for "hg log"
942 self.ui.write(_("user: %s\n") % ctx.user(),
942 self.ui.write(_("user: %s\n") % ctx.user(),
943 label='log.user')
943 label='log.user')
944 # i18n: column positioning for "hg log"
944 # i18n: column positioning for "hg log"
945 self.ui.write(_("date: %s\n") % date,
945 self.ui.write(_("date: %s\n") % date,
946 label='log.date')
946 label='log.date')
947
947
948 if self.ui.debugflag:
948 if self.ui.debugflag:
949 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
949 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
950 for key, value in zip([# i18n: column positioning for "hg log"
950 for key, value in zip([# i18n: column positioning for "hg log"
951 _("files:"),
951 _("files:"),
952 # i18n: column positioning for "hg log"
952 # i18n: column positioning for "hg log"
953 _("files+:"),
953 _("files+:"),
954 # i18n: column positioning for "hg log"
954 # i18n: column positioning for "hg log"
955 _("files-:")], files):
955 _("files-:")], files):
956 if value:
956 if value:
957 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
957 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
958 label='ui.debug log.files')
958 label='ui.debug log.files')
959 elif ctx.files() and self.ui.verbose:
959 elif ctx.files() and self.ui.verbose:
960 # i18n: column positioning for "hg log"
960 # i18n: column positioning for "hg log"
961 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
961 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
962 label='ui.note log.files')
962 label='ui.note log.files')
963 if copies and self.ui.verbose:
963 if copies and self.ui.verbose:
964 copies = ['%s (%s)' % c for c in copies]
964 copies = ['%s (%s)' % c for c in copies]
965 # i18n: column positioning for "hg log"
965 # i18n: column positioning for "hg log"
966 self.ui.write(_("copies: %s\n") % ' '.join(copies),
966 self.ui.write(_("copies: %s\n") % ' '.join(copies),
967 label='ui.note log.copies')
967 label='ui.note log.copies')
968
968
969 extra = ctx.extra()
969 extra = ctx.extra()
970 if extra and self.ui.debugflag:
970 if extra and self.ui.debugflag:
971 for key, value in sorted(extra.items()):
971 for key, value in sorted(extra.items()):
972 # i18n: column positioning for "hg log"
972 # i18n: column positioning for "hg log"
973 self.ui.write(_("extra: %s=%s\n")
973 self.ui.write(_("extra: %s=%s\n")
974 % (key, value.encode('string_escape')),
974 % (key, value.encode('string_escape')),
975 label='ui.debug log.extra')
975 label='ui.debug log.extra')
976
976
977 description = ctx.description().strip()
977 description = ctx.description().strip()
978 if description:
978 if description:
979 if self.ui.verbose:
979 if self.ui.verbose:
980 self.ui.write(_("description:\n"),
980 self.ui.write(_("description:\n"),
981 label='ui.note log.description')
981 label='ui.note log.description')
982 self.ui.write(description,
982 self.ui.write(description,
983 label='ui.note log.description')
983 label='ui.note log.description')
984 self.ui.write("\n\n")
984 self.ui.write("\n\n")
985 else:
985 else:
986 # i18n: column positioning for "hg log"
986 # i18n: column positioning for "hg log"
987 self.ui.write(_("summary: %s\n") %
987 self.ui.write(_("summary: %s\n") %
988 description.splitlines()[0],
988 description.splitlines()[0],
989 label='log.summary')
989 label='log.summary')
990 self.ui.write("\n")
990 self.ui.write("\n")
991
991
992 self.showpatch(changenode, matchfn)
992 self.showpatch(changenode, matchfn)
993
993
994 def showpatch(self, node, matchfn):
994 def showpatch(self, node, matchfn):
995 if not matchfn:
995 if not matchfn:
996 matchfn = self.matchfn
996 matchfn = self.matchfn
997 if matchfn:
997 if matchfn:
998 stat = self.diffopts.get('stat')
998 stat = self.diffopts.get('stat')
999 diff = self.diffopts.get('patch')
999 diff = self.diffopts.get('patch')
1000 diffopts = patch.diffallopts(self.ui, self.diffopts)
1000 diffopts = patch.diffallopts(self.ui, self.diffopts)
1001 prev = self.repo.changelog.parents(node)[0]
1001 prev = self.repo.changelog.parents(node)[0]
1002 if stat:
1002 if stat:
1003 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1003 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1004 match=matchfn, stat=True)
1004 match=matchfn, stat=True)
1005 if diff:
1005 if diff:
1006 if stat:
1006 if stat:
1007 self.ui.write("\n")
1007 self.ui.write("\n")
1008 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1008 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1009 match=matchfn, stat=False)
1009 match=matchfn, stat=False)
1010 self.ui.write("\n")
1010 self.ui.write("\n")
1011
1011
1012 def _meaningful_parentrevs(self, log, rev):
1012 def _meaningful_parentrevs(self, log, rev):
1013 """Return list of meaningful (or all if debug) parentrevs for rev.
1013 """Return list of meaningful (or all if debug) parentrevs for rev.
1014
1014
1015 For merges (two non-nullrev revisions) both parents are meaningful.
1015 For merges (two non-nullrev revisions) both parents are meaningful.
1016 Otherwise the first parent revision is considered meaningful if it
1016 Otherwise the first parent revision is considered meaningful if it
1017 is not the preceding revision.
1017 is not the preceding revision.
1018 """
1018 """
1019 parents = log.parentrevs(rev)
1019 parents = log.parentrevs(rev)
1020 if not self.ui.debugflag and parents[1] == nullrev:
1020 if not self.ui.debugflag and parents[1] == nullrev:
1021 if parents[0] >= rev - 1:
1021 if parents[0] >= rev - 1:
1022 parents = []
1022 parents = []
1023 else:
1023 else:
1024 parents = [parents[0]]
1024 parents = [parents[0]]
1025 return parents
1025 return parents
1026
1026
1027 class jsonchangeset(changeset_printer):
1027 class jsonchangeset(changeset_printer):
1028 '''format changeset information.'''
1028 '''format changeset information.'''
1029
1029
1030 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1030 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1031 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1031 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1032 self.cache = {}
1032 self.cache = {}
1033 self._first = True
1033 self._first = True
1034
1034
1035 def close(self):
1035 def close(self):
1036 if not self._first:
1036 if not self._first:
1037 self.ui.write("\n]\n")
1037 self.ui.write("\n]\n")
1038 else:
1038 else:
1039 self.ui.write("[]\n")
1039 self.ui.write("[]\n")
1040
1040
1041 def _show(self, ctx, copies, matchfn, props):
1041 def _show(self, ctx, copies, matchfn, props):
1042 '''show a single changeset or file revision'''
1042 '''show a single changeset or file revision'''
1043 hexnode = hex(ctx.node())
1043 hexnode = hex(ctx.node())
1044 rev = ctx.rev()
1044 rev = ctx.rev()
1045 j = encoding.jsonescape
1045 j = encoding.jsonescape
1046
1046
1047 if self._first:
1047 if self._first:
1048 self.ui.write("[\n {")
1048 self.ui.write("[\n {")
1049 self._first = False
1049 self._first = False
1050 else:
1050 else:
1051 self.ui.write(",\n {")
1051 self.ui.write(",\n {")
1052
1052
1053 if self.ui.quiet:
1053 if self.ui.quiet:
1054 self.ui.write('\n "rev": %d' % rev)
1054 self.ui.write('\n "rev": %d' % rev)
1055 self.ui.write(',\n "node": "%s"' % hexnode)
1055 self.ui.write(',\n "node": "%s"' % hexnode)
1056 self.ui.write('\n }')
1056 self.ui.write('\n }')
1057 return
1057 return
1058
1058
1059 self.ui.write('\n "rev": %d' % rev)
1059 self.ui.write('\n "rev": %d' % rev)
1060 self.ui.write(',\n "node": "%s"' % hexnode)
1060 self.ui.write(',\n "node": "%s"' % hexnode)
1061 self.ui.write(',\n "branch": "%s"' % j(ctx.branch()))
1061 self.ui.write(',\n "branch": "%s"' % j(ctx.branch()))
1062 self.ui.write(',\n "phase": "%s"' % ctx.phasestr())
1062 self.ui.write(',\n "phase": "%s"' % ctx.phasestr())
1063 self.ui.write(',\n "user": "%s"' % j(ctx.user()))
1063 self.ui.write(',\n "user": "%s"' % j(ctx.user()))
1064 self.ui.write(',\n "date": [%d, %d]' % ctx.date())
1064 self.ui.write(',\n "date": [%d, %d]' % ctx.date())
1065 self.ui.write(',\n "desc": "%s"' % j(ctx.description()))
1065 self.ui.write(',\n "desc": "%s"' % j(ctx.description()))
1066
1066
1067 self.ui.write(',\n "bookmarks": [%s]' %
1067 self.ui.write(',\n "bookmarks": [%s]' %
1068 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1068 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1069 self.ui.write(',\n "tags": [%s]' %
1069 self.ui.write(',\n "tags": [%s]' %
1070 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1070 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1071 self.ui.write(',\n "parents": [%s]' %
1071 self.ui.write(',\n "parents": [%s]' %
1072 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1072 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1073
1073
1074 if self.ui.debugflag:
1074 if self.ui.debugflag:
1075 self.ui.write(',\n "manifest": "%s"' % hex(ctx.manifestnode()))
1075 self.ui.write(',\n "manifest": "%s"' % hex(ctx.manifestnode()))
1076
1076
1077 self.ui.write(',\n "extra": {%s}' %
1077 self.ui.write(',\n "extra": {%s}' %
1078 ", ".join('"%s": "%s"' % (j(k), j(v))
1078 ", ".join('"%s": "%s"' % (j(k), j(v))
1079 for k, v in ctx.extra().items()))
1079 for k, v in ctx.extra().items()))
1080
1080
1081 files = ctx.p1().status(ctx)
1081 files = ctx.p1().status(ctx)
1082 self.ui.write(',\n "modified": [%s]' %
1082 self.ui.write(',\n "modified": [%s]' %
1083 ", ".join('"%s"' % j(f) for f in files[0]))
1083 ", ".join('"%s"' % j(f) for f in files[0]))
1084 self.ui.write(',\n "added": [%s]' %
1084 self.ui.write(',\n "added": [%s]' %
1085 ", ".join('"%s"' % j(f) for f in files[1]))
1085 ", ".join('"%s"' % j(f) for f in files[1]))
1086 self.ui.write(',\n "removed": [%s]' %
1086 self.ui.write(',\n "removed": [%s]' %
1087 ", ".join('"%s"' % j(f) for f in files[2]))
1087 ", ".join('"%s"' % j(f) for f in files[2]))
1088
1088
1089 elif self.ui.verbose:
1089 elif self.ui.verbose:
1090 self.ui.write(',\n "files": [%s]' %
1090 self.ui.write(',\n "files": [%s]' %
1091 ", ".join('"%s"' % j(f) for f in ctx.files()))
1091 ", ".join('"%s"' % j(f) for f in ctx.files()))
1092
1092
1093 if copies:
1093 if copies:
1094 self.ui.write(',\n "copies": {%s}' %
1094 self.ui.write(',\n "copies": {%s}' %
1095 ", ".join('"%s": %s' % (j(k), j(copies[k]))
1095 ", ".join('"%s": %s' % (j(k), j(copies[k]))
1096 for k in copies))
1096 for k in copies))
1097
1097
1098 matchfn = self.matchfn
1098 matchfn = self.matchfn
1099 if matchfn:
1099 if matchfn:
1100 stat = self.diffopts.get('stat')
1100 stat = self.diffopts.get('stat')
1101 diff = self.diffopts.get('patch')
1101 diff = self.diffopts.get('patch')
1102 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1102 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1103 node, prev = ctx.node(), ctx.p1().node()
1103 node, prev = ctx.node(), ctx.p1().node()
1104 if stat:
1104 if stat:
1105 self.ui.pushbuffer()
1105 self.ui.pushbuffer()
1106 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1106 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1107 match=matchfn, stat=True)
1107 match=matchfn, stat=True)
1108 self.ui.write(',\n "diffstat": "%s"' % j(self.ui.popbuffer()))
1108 self.ui.write(',\n "diffstat": "%s"' % j(self.ui.popbuffer()))
1109 if diff:
1109 if diff:
1110 self.ui.pushbuffer()
1110 self.ui.pushbuffer()
1111 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1111 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1112 match=matchfn, stat=False)
1112 match=matchfn, stat=False)
1113 self.ui.write(',\n "diff": "%s"' % j(self.ui.popbuffer()))
1113 self.ui.write(',\n "diff": "%s"' % j(self.ui.popbuffer()))
1114
1114
1115 self.ui.write("\n }")
1115 self.ui.write("\n }")
1116
1116
1117 class changeset_templater(changeset_printer):
1117 class changeset_templater(changeset_printer):
1118 '''format changeset information.'''
1118 '''format changeset information.'''
1119
1119
1120 def __init__(self, ui, repo, matchfn, diffopts, tmpl, mapfile, buffered):
1120 def __init__(self, ui, repo, matchfn, diffopts, tmpl, mapfile, buffered):
1121 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1121 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1122 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
1122 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
1123 defaulttempl = {
1123 defaulttempl = {
1124 'parent': '{rev}:{node|formatnode} ',
1124 'parent': '{rev}:{node|formatnode} ',
1125 'manifest': '{rev}:{node|formatnode}',
1125 'manifest': '{rev}:{node|formatnode}',
1126 'file_copy': '{name} ({source})',
1126 'file_copy': '{name} ({source})',
1127 'extra': '{key}={value|stringescape}'
1127 'extra': '{key}={value|stringescape}'
1128 }
1128 }
1129 # filecopy is preserved for compatibility reasons
1129 # filecopy is preserved for compatibility reasons
1130 defaulttempl['filecopy'] = defaulttempl['file_copy']
1130 defaulttempl['filecopy'] = defaulttempl['file_copy']
1131 self.t = templater.templater(mapfile, {'formatnode': formatnode},
1131 self.t = templater.templater(mapfile, {'formatnode': formatnode},
1132 cache=defaulttempl)
1132 cache=defaulttempl)
1133 if tmpl:
1133 if tmpl:
1134 self.t.cache['changeset'] = tmpl
1134 self.t.cache['changeset'] = tmpl
1135
1135
1136 self.cache = {}
1136 self.cache = {}
1137
1137
1138 def _meaningful_parentrevs(self, ctx):
1138 def _meaningful_parentrevs(self, ctx):
1139 """Return list of meaningful (or all if debug) parentrevs for rev.
1139 """Return list of meaningful (or all if debug) parentrevs for rev.
1140 """
1140 """
1141 parents = ctx.parents()
1141 parents = ctx.parents()
1142 if len(parents) > 1:
1142 if len(parents) > 1:
1143 return parents
1143 return parents
1144 if self.ui.debugflag:
1144 if self.ui.debugflag:
1145 return [parents[0], self.repo['null']]
1145 return [parents[0], self.repo['null']]
1146 if parents[0].rev() >= ctx.rev() - 1:
1146 if parents[0].rev() >= ctx.rev() - 1:
1147 return []
1147 return []
1148 return parents
1148 return parents
1149
1149
1150 def _show(self, ctx, copies, matchfn, props):
1150 def _show(self, ctx, copies, matchfn, props):
1151 '''show a single changeset or file revision'''
1151 '''show a single changeset or file revision'''
1152
1152
1153 showlist = templatekw.showlist
1153 showlist = templatekw.showlist
1154
1154
1155 # showparents() behaviour depends on ui trace level which
1155 # showparents() behaviour depends on ui trace level which
1156 # causes unexpected behaviours at templating level and makes
1156 # causes unexpected behaviours at templating level and makes
1157 # it harder to extract it in a standalone function. Its
1157 # it harder to extract it in a standalone function. Its
1158 # behaviour cannot be changed so leave it here for now.
1158 # behaviour cannot be changed so leave it here for now.
1159 def showparents(**args):
1159 def showparents(**args):
1160 ctx = args['ctx']
1160 ctx = args['ctx']
1161 parents = [[('rev', p.rev()),
1161 parents = [[('rev', p.rev()),
1162 ('node', p.hex()),
1162 ('node', p.hex()),
1163 ('phase', p.phasestr())]
1163 ('phase', p.phasestr())]
1164 for p in self._meaningful_parentrevs(ctx)]
1164 for p in self._meaningful_parentrevs(ctx)]
1165 return showlist('parent', parents, **args)
1165 return showlist('parent', parents, **args)
1166
1166
1167 props = props.copy()
1167 props = props.copy()
1168 props.update(templatekw.keywords)
1168 props.update(templatekw.keywords)
1169 props['parents'] = showparents
1169 props['parents'] = showparents
1170 props['templ'] = self.t
1170 props['templ'] = self.t
1171 props['ctx'] = ctx
1171 props['ctx'] = ctx
1172 props['repo'] = self.repo
1172 props['repo'] = self.repo
1173 props['revcache'] = {'copies': copies}
1173 props['revcache'] = {'copies': copies}
1174 props['cache'] = self.cache
1174 props['cache'] = self.cache
1175
1175
1176 # find correct templates for current mode
1176 # find correct templates for current mode
1177
1177
1178 tmplmodes = [
1178 tmplmodes = [
1179 (True, None),
1179 (True, None),
1180 (self.ui.verbose, 'verbose'),
1180 (self.ui.verbose, 'verbose'),
1181 (self.ui.quiet, 'quiet'),
1181 (self.ui.quiet, 'quiet'),
1182 (self.ui.debugflag, 'debug'),
1182 (self.ui.debugflag, 'debug'),
1183 ]
1183 ]
1184
1184
1185 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
1185 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
1186 for mode, postfix in tmplmodes:
1186 for mode, postfix in tmplmodes:
1187 for type in types:
1187 for type in types:
1188 cur = postfix and ('%s_%s' % (type, postfix)) or type
1188 cur = postfix and ('%s_%s' % (type, postfix)) or type
1189 if mode and cur in self.t:
1189 if mode and cur in self.t:
1190 types[type] = cur
1190 types[type] = cur
1191
1191
1192 try:
1192 try:
1193
1193
1194 # write header
1194 # write header
1195 if types['header']:
1195 if types['header']:
1196 h = templater.stringify(self.t(types['header'], **props))
1196 h = templater.stringify(self.t(types['header'], **props))
1197 if self.buffered:
1197 if self.buffered:
1198 self.header[ctx.rev()] = h
1198 self.header[ctx.rev()] = h
1199 else:
1199 else:
1200 if self.lastheader != h:
1200 if self.lastheader != h:
1201 self.lastheader = h
1201 self.lastheader = h
1202 self.ui.write(h)
1202 self.ui.write(h)
1203
1203
1204 # write changeset metadata, then patch if requested
1204 # write changeset metadata, then patch if requested
1205 key = types['changeset']
1205 key = types['changeset']
1206 self.ui.write(templater.stringify(self.t(key, **props)))
1206 self.ui.write(templater.stringify(self.t(key, **props)))
1207 self.showpatch(ctx.node(), matchfn)
1207 self.showpatch(ctx.node(), matchfn)
1208
1208
1209 if types['footer']:
1209 if types['footer']:
1210 if not self.footer:
1210 if not self.footer:
1211 self.footer = templater.stringify(self.t(types['footer'],
1211 self.footer = templater.stringify(self.t(types['footer'],
1212 **props))
1212 **props))
1213
1213
1214 except KeyError, inst:
1214 except KeyError, inst:
1215 msg = _("%s: no key named '%s'")
1215 msg = _("%s: no key named '%s'")
1216 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1216 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1217 except SyntaxError, inst:
1217 except SyntaxError, inst:
1218 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1218 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1219
1219
1220 def gettemplate(ui, tmpl, style):
1220 def gettemplate(ui, tmpl, style):
1221 """
1221 """
1222 Find the template matching the given template spec or style.
1222 Find the template matching the given template spec or style.
1223 """
1223 """
1224
1224
1225 # ui settings
1225 # ui settings
1226 if not tmpl and not style: # template are stronger than style
1226 if not tmpl and not style: # template are stronger than style
1227 tmpl = ui.config('ui', 'logtemplate')
1227 tmpl = ui.config('ui', 'logtemplate')
1228 if tmpl:
1228 if tmpl:
1229 try:
1229 try:
1230 tmpl = templater.parsestring(tmpl)
1230 tmpl = templater.parsestring(tmpl)
1231 except SyntaxError:
1231 except SyntaxError:
1232 tmpl = templater.parsestring(tmpl, quoted=False)
1232 tmpl = templater.parsestring(tmpl, quoted=False)
1233 return tmpl, None
1233 return tmpl, None
1234 else:
1234 else:
1235 style = util.expandpath(ui.config('ui', 'style', ''))
1235 style = util.expandpath(ui.config('ui', 'style', ''))
1236
1236
1237 if not tmpl and style:
1237 if not tmpl and style:
1238 mapfile = style
1238 mapfile = style
1239 if not os.path.split(mapfile)[0]:
1239 if not os.path.split(mapfile)[0]:
1240 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1240 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1241 or templater.templatepath(mapfile))
1241 or templater.templatepath(mapfile))
1242 if mapname:
1242 if mapname:
1243 mapfile = mapname
1243 mapfile = mapname
1244 return None, mapfile
1244 return None, mapfile
1245
1245
1246 if not tmpl:
1246 if not tmpl:
1247 return None, None
1247 return None, None
1248
1248
1249 # looks like a literal template?
1249 # looks like a literal template?
1250 if '{' in tmpl:
1250 if '{' in tmpl:
1251 return tmpl, None
1251 return tmpl, None
1252
1252
1253 # perhaps a stock style?
1253 # perhaps a stock style?
1254 if not os.path.split(tmpl)[0]:
1254 if not os.path.split(tmpl)[0]:
1255 mapname = (templater.templatepath('map-cmdline.' + tmpl)
1255 mapname = (templater.templatepath('map-cmdline.' + tmpl)
1256 or templater.templatepath(tmpl))
1256 or templater.templatepath(tmpl))
1257 if mapname and os.path.isfile(mapname):
1257 if mapname and os.path.isfile(mapname):
1258 return None, mapname
1258 return None, mapname
1259
1259
1260 # perhaps it's a reference to [templates]
1260 # perhaps it's a reference to [templates]
1261 t = ui.config('templates', tmpl)
1261 t = ui.config('templates', tmpl)
1262 if t:
1262 if t:
1263 try:
1263 try:
1264 tmpl = templater.parsestring(t)
1264 tmpl = templater.parsestring(t)
1265 except SyntaxError:
1265 except SyntaxError:
1266 tmpl = templater.parsestring(t, quoted=False)
1266 tmpl = templater.parsestring(t, quoted=False)
1267 return tmpl, None
1267 return tmpl, None
1268
1268
1269 if tmpl == 'list':
1269 if tmpl == 'list':
1270 ui.write(_("available styles: %s\n") % templater.stylelist())
1270 ui.write(_("available styles: %s\n") % templater.stylelist())
1271 raise util.Abort(_("specify a template"))
1271 raise util.Abort(_("specify a template"))
1272
1272
1273 # perhaps it's a path to a map or a template
1273 # perhaps it's a path to a map or a template
1274 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
1274 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
1275 # is it a mapfile for a style?
1275 # is it a mapfile for a style?
1276 if os.path.basename(tmpl).startswith("map-"):
1276 if os.path.basename(tmpl).startswith("map-"):
1277 return None, os.path.realpath(tmpl)
1277 return None, os.path.realpath(tmpl)
1278 tmpl = open(tmpl).read()
1278 tmpl = open(tmpl).read()
1279 return tmpl, None
1279 return tmpl, None
1280
1280
1281 # constant string?
1281 # constant string?
1282 return tmpl, None
1282 return tmpl, None
1283
1283
1284 def show_changeset(ui, repo, opts, buffered=False):
1284 def show_changeset(ui, repo, opts, buffered=False):
1285 """show one changeset using template or regular display.
1285 """show one changeset using template or regular display.
1286
1286
1287 Display format will be the first non-empty hit of:
1287 Display format will be the first non-empty hit of:
1288 1. option 'template'
1288 1. option 'template'
1289 2. option 'style'
1289 2. option 'style'
1290 3. [ui] setting 'logtemplate'
1290 3. [ui] setting 'logtemplate'
1291 4. [ui] setting 'style'
1291 4. [ui] setting 'style'
1292 If all of these values are either the unset or the empty string,
1292 If all of these values are either the unset or the empty string,
1293 regular display via changeset_printer() is done.
1293 regular display via changeset_printer() is done.
1294 """
1294 """
1295 # options
1295 # options
1296 matchfn = None
1296 matchfn = None
1297 if opts.get('patch') or opts.get('stat'):
1297 if opts.get('patch') or opts.get('stat'):
1298 matchfn = scmutil.matchall(repo)
1298 matchfn = scmutil.matchall(repo)
1299
1299
1300 if opts.get('template') == 'json':
1300 if opts.get('template') == 'json':
1301 return jsonchangeset(ui, repo, matchfn, opts, buffered)
1301 return jsonchangeset(ui, repo, matchfn, opts, buffered)
1302
1302
1303 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1303 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1304
1304
1305 if not tmpl and not mapfile:
1305 if not tmpl and not mapfile:
1306 return changeset_printer(ui, repo, matchfn, opts, buffered)
1306 return changeset_printer(ui, repo, matchfn, opts, buffered)
1307
1307
1308 try:
1308 try:
1309 t = changeset_templater(ui, repo, matchfn, opts, tmpl, mapfile,
1309 t = changeset_templater(ui, repo, matchfn, opts, tmpl, mapfile,
1310 buffered)
1310 buffered)
1311 except SyntaxError, inst:
1311 except SyntaxError, inst:
1312 raise util.Abort(inst.args[0])
1312 raise util.Abort(inst.args[0])
1313 return t
1313 return t
1314
1314
1315 def showmarker(ui, marker):
1315 def showmarker(ui, marker):
1316 """utility function to display obsolescence marker in a readable way
1316 """utility function to display obsolescence marker in a readable way
1317
1317
1318 To be used by debug function."""
1318 To be used by debug function."""
1319 ui.write(hex(marker.precnode()))
1319 ui.write(hex(marker.precnode()))
1320 for repl in marker.succnodes():
1320 for repl in marker.succnodes():
1321 ui.write(' ')
1321 ui.write(' ')
1322 ui.write(hex(repl))
1322 ui.write(hex(repl))
1323 ui.write(' %X ' % marker.flags())
1323 ui.write(' %X ' % marker.flags())
1324 parents = marker.parentnodes()
1324 parents = marker.parentnodes()
1325 if parents is not None:
1325 if parents is not None:
1326 ui.write('{%s} ' % ', '.join(hex(p) for p in parents))
1326 ui.write('{%s} ' % ', '.join(hex(p) for p in parents))
1327 ui.write('(%s) ' % util.datestr(marker.date()))
1327 ui.write('(%s) ' % util.datestr(marker.date()))
1328 ui.write('{%s}' % (', '.join('%r: %r' % t for t in
1328 ui.write('{%s}' % (', '.join('%r: %r' % t for t in
1329 sorted(marker.metadata().items())
1329 sorted(marker.metadata().items())
1330 if t[0] != 'date')))
1330 if t[0] != 'date')))
1331 ui.write('\n')
1331 ui.write('\n')
1332
1332
1333 def finddate(ui, repo, date):
1333 def finddate(ui, repo, date):
1334 """Find the tipmost changeset that matches the given date spec"""
1334 """Find the tipmost changeset that matches the given date spec"""
1335
1335
1336 df = util.matchdate(date)
1336 df = util.matchdate(date)
1337 m = scmutil.matchall(repo)
1337 m = scmutil.matchall(repo)
1338 results = {}
1338 results = {}
1339
1339
1340 def prep(ctx, fns):
1340 def prep(ctx, fns):
1341 d = ctx.date()
1341 d = ctx.date()
1342 if df(d[0]):
1342 if df(d[0]):
1343 results[ctx.rev()] = d
1343 results[ctx.rev()] = d
1344
1344
1345 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1345 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1346 rev = ctx.rev()
1346 rev = ctx.rev()
1347 if rev in results:
1347 if rev in results:
1348 ui.status(_("found revision %s from %s\n") %
1348 ui.status(_("found revision %s from %s\n") %
1349 (rev, util.datestr(results[rev])))
1349 (rev, util.datestr(results[rev])))
1350 return str(rev)
1350 return str(rev)
1351
1351
1352 raise util.Abort(_("revision matching date not found"))
1352 raise util.Abort(_("revision matching date not found"))
1353
1353
1354 def increasingwindows(windowsize=8, sizelimit=512):
1354 def increasingwindows(windowsize=8, sizelimit=512):
1355 while True:
1355 while True:
1356 yield windowsize
1356 yield windowsize
1357 if windowsize < sizelimit:
1357 if windowsize < sizelimit:
1358 windowsize *= 2
1358 windowsize *= 2
1359
1359
1360 class FileWalkError(Exception):
1360 class FileWalkError(Exception):
1361 pass
1361 pass
1362
1362
1363 def walkfilerevs(repo, match, follow, revs, fncache):
1363 def walkfilerevs(repo, match, follow, revs, fncache):
1364 '''Walks the file history for the matched files.
1364 '''Walks the file history for the matched files.
1365
1365
1366 Returns the changeset revs that are involved in the file history.
1366 Returns the changeset revs that are involved in the file history.
1367
1367
1368 Throws FileWalkError if the file history can't be walked using
1368 Throws FileWalkError if the file history can't be walked using
1369 filelogs alone.
1369 filelogs alone.
1370 '''
1370 '''
1371 wanted = set()
1371 wanted = set()
1372 copies = []
1372 copies = []
1373 minrev, maxrev = min(revs), max(revs)
1373 minrev, maxrev = min(revs), max(revs)
1374 def filerevgen(filelog, last):
1374 def filerevgen(filelog, last):
1375 """
1375 """
1376 Only files, no patterns. Check the history of each file.
1376 Only files, no patterns. Check the history of each file.
1377
1377
1378 Examines filelog entries within minrev, maxrev linkrev range
1378 Examines filelog entries within minrev, maxrev linkrev range
1379 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1379 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1380 tuples in backwards order
1380 tuples in backwards order
1381 """
1381 """
1382 cl_count = len(repo)
1382 cl_count = len(repo)
1383 revs = []
1383 revs = []
1384 for j in xrange(0, last + 1):
1384 for j in xrange(0, last + 1):
1385 linkrev = filelog.linkrev(j)
1385 linkrev = filelog.linkrev(j)
1386 if linkrev < minrev:
1386 if linkrev < minrev:
1387 continue
1387 continue
1388 # only yield rev for which we have the changelog, it can
1388 # only yield rev for which we have the changelog, it can
1389 # happen while doing "hg log" during a pull or commit
1389 # happen while doing "hg log" during a pull or commit
1390 if linkrev >= cl_count:
1390 if linkrev >= cl_count:
1391 break
1391 break
1392
1392
1393 parentlinkrevs = []
1393 parentlinkrevs = []
1394 for p in filelog.parentrevs(j):
1394 for p in filelog.parentrevs(j):
1395 if p != nullrev:
1395 if p != nullrev:
1396 parentlinkrevs.append(filelog.linkrev(p))
1396 parentlinkrevs.append(filelog.linkrev(p))
1397 n = filelog.node(j)
1397 n = filelog.node(j)
1398 revs.append((linkrev, parentlinkrevs,
1398 revs.append((linkrev, parentlinkrevs,
1399 follow and filelog.renamed(n)))
1399 follow and filelog.renamed(n)))
1400
1400
1401 return reversed(revs)
1401 return reversed(revs)
1402 def iterfiles():
1402 def iterfiles():
1403 pctx = repo['.']
1403 pctx = repo['.']
1404 for filename in match.files():
1404 for filename in match.files():
1405 if follow:
1405 if follow:
1406 if filename not in pctx:
1406 if filename not in pctx:
1407 raise util.Abort(_('cannot follow file not in parent '
1407 raise util.Abort(_('cannot follow file not in parent '
1408 'revision: "%s"') % filename)
1408 'revision: "%s"') % filename)
1409 yield filename, pctx[filename].filenode()
1409 yield filename, pctx[filename].filenode()
1410 else:
1410 else:
1411 yield filename, None
1411 yield filename, None
1412 for filename_node in copies:
1412 for filename_node in copies:
1413 yield filename_node
1413 yield filename_node
1414
1414
1415 for file_, node in iterfiles():
1415 for file_, node in iterfiles():
1416 filelog = repo.file(file_)
1416 filelog = repo.file(file_)
1417 if not len(filelog):
1417 if not len(filelog):
1418 if node is None:
1418 if node is None:
1419 # A zero count may be a directory or deleted file, so
1419 # A zero count may be a directory or deleted file, so
1420 # try to find matching entries on the slow path.
1420 # try to find matching entries on the slow path.
1421 if follow:
1421 if follow:
1422 raise util.Abort(
1422 raise util.Abort(
1423 _('cannot follow nonexistent file: "%s"') % file_)
1423 _('cannot follow nonexistent file: "%s"') % file_)
1424 raise FileWalkError("Cannot walk via filelog")
1424 raise FileWalkError("Cannot walk via filelog")
1425 else:
1425 else:
1426 continue
1426 continue
1427
1427
1428 if node is None:
1428 if node is None:
1429 last = len(filelog) - 1
1429 last = len(filelog) - 1
1430 else:
1430 else:
1431 last = filelog.rev(node)
1431 last = filelog.rev(node)
1432
1432
1433
1433
1434 # keep track of all ancestors of the file
1434 # keep track of all ancestors of the file
1435 ancestors = set([filelog.linkrev(last)])
1435 ancestors = set([filelog.linkrev(last)])
1436
1436
1437 # iterate from latest to oldest revision
1437 # iterate from latest to oldest revision
1438 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1438 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1439 if not follow:
1439 if not follow:
1440 if rev > maxrev:
1440 if rev > maxrev:
1441 continue
1441 continue
1442 else:
1442 else:
1443 # Note that last might not be the first interesting
1443 # Note that last might not be the first interesting
1444 # rev to us:
1444 # rev to us:
1445 # if the file has been changed after maxrev, we'll
1445 # if the file has been changed after maxrev, we'll
1446 # have linkrev(last) > maxrev, and we still need
1446 # have linkrev(last) > maxrev, and we still need
1447 # to explore the file graph
1447 # to explore the file graph
1448 if rev not in ancestors:
1448 if rev not in ancestors:
1449 continue
1449 continue
1450 # XXX insert 1327 fix here
1450 # XXX insert 1327 fix here
1451 if flparentlinkrevs:
1451 if flparentlinkrevs:
1452 ancestors.update(flparentlinkrevs)
1452 ancestors.update(flparentlinkrevs)
1453
1453
1454 fncache.setdefault(rev, []).append(file_)
1454 fncache.setdefault(rev, []).append(file_)
1455 wanted.add(rev)
1455 wanted.add(rev)
1456 if copied:
1456 if copied:
1457 copies.append(copied)
1457 copies.append(copied)
1458
1458
1459 return wanted
1459 return wanted
1460
1460
1461 def walkchangerevs(repo, match, opts, prepare):
1461 def walkchangerevs(repo, match, opts, prepare):
1462 '''Iterate over files and the revs in which they changed.
1462 '''Iterate over files and the revs in which they changed.
1463
1463
1464 Callers most commonly need to iterate backwards over the history
1464 Callers most commonly need to iterate backwards over the history
1465 in which they are interested. Doing so has awful (quadratic-looking)
1465 in which they are interested. Doing so has awful (quadratic-looking)
1466 performance, so we use iterators in a "windowed" way.
1466 performance, so we use iterators in a "windowed" way.
1467
1467
1468 We walk a window of revisions in the desired order. Within the
1468 We walk a window of revisions in the desired order. Within the
1469 window, we first walk forwards to gather data, then in the desired
1469 window, we first walk forwards to gather data, then in the desired
1470 order (usually backwards) to display it.
1470 order (usually backwards) to display it.
1471
1471
1472 This function returns an iterator yielding contexts. Before
1472 This function returns an iterator yielding contexts. Before
1473 yielding each context, the iterator will first call the prepare
1473 yielding each context, the iterator will first call the prepare
1474 function on each context in the window in forward order.'''
1474 function on each context in the window in forward order.'''
1475
1475
1476 follow = opts.get('follow') or opts.get('follow_first')
1476 follow = opts.get('follow') or opts.get('follow_first')
1477
1477
1478 if opts.get('rev'):
1478 if opts.get('rev'):
1479 revs = scmutil.revrange(repo, opts.get('rev'))
1479 revs = scmutil.revrange(repo, opts.get('rev'))
1480 elif follow:
1480 elif follow:
1481 revs = repo.revs('reverse(:.)')
1481 revs = repo.revs('reverse(:.)')
1482 else:
1482 else:
1483 revs = revset.spanset(repo)
1483 revs = revset.spanset(repo)
1484 revs.reverse()
1484 revs.reverse()
1485 if not revs:
1485 if not revs:
1486 return []
1486 return []
1487 wanted = set()
1487 wanted = set()
1488 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1488 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1489 fncache = {}
1489 fncache = {}
1490 change = repo.changectx
1490 change = repo.changectx
1491
1491
1492 # First step is to fill wanted, the set of revisions that we want to yield.
1492 # First step is to fill wanted, the set of revisions that we want to yield.
1493 # When it does not induce extra cost, we also fill fncache for revisions in
1493 # When it does not induce extra cost, we also fill fncache for revisions in
1494 # wanted: a cache of filenames that were changed (ctx.files()) and that
1494 # wanted: a cache of filenames that were changed (ctx.files()) and that
1495 # match the file filtering conditions.
1495 # match the file filtering conditions.
1496
1496
1497 if not slowpath and not match.files():
1497 if not slowpath and not match.files():
1498 # No files, no patterns. Display all revs.
1498 # No files, no patterns. Display all revs.
1499 wanted = revs
1499 wanted = revs
1500
1500
1501 if not slowpath and match.files():
1501 if not slowpath and match.files():
1502 # We only have to read through the filelog to find wanted revisions
1502 # We only have to read through the filelog to find wanted revisions
1503
1503
1504 try:
1504 try:
1505 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1505 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1506 except FileWalkError:
1506 except FileWalkError:
1507 slowpath = True
1507 slowpath = True
1508
1508
1509 # We decided to fall back to the slowpath because at least one
1509 # We decided to fall back to the slowpath because at least one
1510 # of the paths was not a file. Check to see if at least one of them
1510 # of the paths was not a file. Check to see if at least one of them
1511 # existed in history, otherwise simply return
1511 # existed in history, otherwise simply return
1512 for path in match.files():
1512 for path in match.files():
1513 if path == '.' or path in repo.store:
1513 if path == '.' or path in repo.store:
1514 break
1514 break
1515 else:
1515 else:
1516 return []
1516 return []
1517
1517
1518 if slowpath:
1518 if slowpath:
1519 # We have to read the changelog to match filenames against
1519 # We have to read the changelog to match filenames against
1520 # changed files
1520 # changed files
1521
1521
1522 if follow:
1522 if follow:
1523 raise util.Abort(_('can only follow copies/renames for explicit '
1523 raise util.Abort(_('can only follow copies/renames for explicit '
1524 'filenames'))
1524 'filenames'))
1525
1525
1526 # The slow path checks files modified in every changeset.
1526 # The slow path checks files modified in every changeset.
1527 # This is really slow on large repos, so compute the set lazily.
1527 # This is really slow on large repos, so compute the set lazily.
1528 class lazywantedset(object):
1528 class lazywantedset(object):
1529 def __init__(self):
1529 def __init__(self):
1530 self.set = set()
1530 self.set = set()
1531 self.revs = set(revs)
1531 self.revs = set(revs)
1532
1532
1533 # No need to worry about locality here because it will be accessed
1533 # No need to worry about locality here because it will be accessed
1534 # in the same order as the increasing window below.
1534 # in the same order as the increasing window below.
1535 def __contains__(self, value):
1535 def __contains__(self, value):
1536 if value in self.set:
1536 if value in self.set:
1537 return True
1537 return True
1538 elif not value in self.revs:
1538 elif not value in self.revs:
1539 return False
1539 return False
1540 else:
1540 else:
1541 self.revs.discard(value)
1541 self.revs.discard(value)
1542 ctx = change(value)
1542 ctx = change(value)
1543 matches = filter(match, ctx.files())
1543 matches = filter(match, ctx.files())
1544 if matches:
1544 if matches:
1545 fncache[value] = matches
1545 fncache[value] = matches
1546 self.set.add(value)
1546 self.set.add(value)
1547 return True
1547 return True
1548 return False
1548 return False
1549
1549
1550 def discard(self, value):
1550 def discard(self, value):
1551 self.revs.discard(value)
1551 self.revs.discard(value)
1552 self.set.discard(value)
1552 self.set.discard(value)
1553
1553
1554 wanted = lazywantedset()
1554 wanted = lazywantedset()
1555
1555
1556 class followfilter(object):
1556 class followfilter(object):
1557 def __init__(self, onlyfirst=False):
1557 def __init__(self, onlyfirst=False):
1558 self.startrev = nullrev
1558 self.startrev = nullrev
1559 self.roots = set()
1559 self.roots = set()
1560 self.onlyfirst = onlyfirst
1560 self.onlyfirst = onlyfirst
1561
1561
1562 def match(self, rev):
1562 def match(self, rev):
1563 def realparents(rev):
1563 def realparents(rev):
1564 if self.onlyfirst:
1564 if self.onlyfirst:
1565 return repo.changelog.parentrevs(rev)[0:1]
1565 return repo.changelog.parentrevs(rev)[0:1]
1566 else:
1566 else:
1567 return filter(lambda x: x != nullrev,
1567 return filter(lambda x: x != nullrev,
1568 repo.changelog.parentrevs(rev))
1568 repo.changelog.parentrevs(rev))
1569
1569
1570 if self.startrev == nullrev:
1570 if self.startrev == nullrev:
1571 self.startrev = rev
1571 self.startrev = rev
1572 return True
1572 return True
1573
1573
1574 if rev > self.startrev:
1574 if rev > self.startrev:
1575 # forward: all descendants
1575 # forward: all descendants
1576 if not self.roots:
1576 if not self.roots:
1577 self.roots.add(self.startrev)
1577 self.roots.add(self.startrev)
1578 for parent in realparents(rev):
1578 for parent in realparents(rev):
1579 if parent in self.roots:
1579 if parent in self.roots:
1580 self.roots.add(rev)
1580 self.roots.add(rev)
1581 return True
1581 return True
1582 else:
1582 else:
1583 # backwards: all parents
1583 # backwards: all parents
1584 if not self.roots:
1584 if not self.roots:
1585 self.roots.update(realparents(self.startrev))
1585 self.roots.update(realparents(self.startrev))
1586 if rev in self.roots:
1586 if rev in self.roots:
1587 self.roots.remove(rev)
1587 self.roots.remove(rev)
1588 self.roots.update(realparents(rev))
1588 self.roots.update(realparents(rev))
1589 return True
1589 return True
1590
1590
1591 return False
1591 return False
1592
1592
1593 # it might be worthwhile to do this in the iterator if the rev range
1593 # it might be worthwhile to do this in the iterator if the rev range
1594 # is descending and the prune args are all within that range
1594 # is descending and the prune args are all within that range
1595 for rev in opts.get('prune', ()):
1595 for rev in opts.get('prune', ()):
1596 rev = repo[rev].rev()
1596 rev = repo[rev].rev()
1597 ff = followfilter()
1597 ff = followfilter()
1598 stop = min(revs[0], revs[-1])
1598 stop = min(revs[0], revs[-1])
1599 for x in xrange(rev, stop - 1, -1):
1599 for x in xrange(rev, stop - 1, -1):
1600 if ff.match(x):
1600 if ff.match(x):
1601 wanted = wanted - [x]
1601 wanted = wanted - [x]
1602
1602
1603 # Now that wanted is correctly initialized, we can iterate over the
1603 # Now that wanted is correctly initialized, we can iterate over the
1604 # revision range, yielding only revisions in wanted.
1604 # revision range, yielding only revisions in wanted.
1605 def iterate():
1605 def iterate():
1606 if follow and not match.files():
1606 if follow and not match.files():
1607 ff = followfilter(onlyfirst=opts.get('follow_first'))
1607 ff = followfilter(onlyfirst=opts.get('follow_first'))
1608 def want(rev):
1608 def want(rev):
1609 return ff.match(rev) and rev in wanted
1609 return ff.match(rev) and rev in wanted
1610 else:
1610 else:
1611 def want(rev):
1611 def want(rev):
1612 return rev in wanted
1612 return rev in wanted
1613
1613
1614 it = iter(revs)
1614 it = iter(revs)
1615 stopiteration = False
1615 stopiteration = False
1616 for windowsize in increasingwindows():
1616 for windowsize in increasingwindows():
1617 nrevs = []
1617 nrevs = []
1618 for i in xrange(windowsize):
1618 for i in xrange(windowsize):
1619 try:
1619 try:
1620 rev = it.next()
1620 rev = it.next()
1621 if want(rev):
1621 if want(rev):
1622 nrevs.append(rev)
1622 nrevs.append(rev)
1623 except (StopIteration):
1623 except (StopIteration):
1624 stopiteration = True
1624 stopiteration = True
1625 break
1625 break
1626 for rev in sorted(nrevs):
1626 for rev in sorted(nrevs):
1627 fns = fncache.get(rev)
1627 fns = fncache.get(rev)
1628 ctx = change(rev)
1628 ctx = change(rev)
1629 if not fns:
1629 if not fns:
1630 def fns_generator():
1630 def fns_generator():
1631 for f in ctx.files():
1631 for f in ctx.files():
1632 if match(f):
1632 if match(f):
1633 yield f
1633 yield f
1634 fns = fns_generator()
1634 fns = fns_generator()
1635 prepare(ctx, fns)
1635 prepare(ctx, fns)
1636 for rev in nrevs:
1636 for rev in nrevs:
1637 yield change(rev)
1637 yield change(rev)
1638
1638
1639 if stopiteration:
1639 if stopiteration:
1640 break
1640 break
1641
1641
1642 return iterate()
1642 return iterate()
1643
1643
1644 def _makefollowlogfilematcher(repo, files, followfirst):
1644 def _makefollowlogfilematcher(repo, files, followfirst):
1645 # When displaying a revision with --patch --follow FILE, we have
1645 # When displaying a revision with --patch --follow FILE, we have
1646 # to know which file of the revision must be diffed. With
1646 # to know which file of the revision must be diffed. With
1647 # --follow, we want the names of the ancestors of FILE in the
1647 # --follow, we want the names of the ancestors of FILE in the
1648 # revision, stored in "fcache". "fcache" is populated by
1648 # revision, stored in "fcache". "fcache" is populated by
1649 # reproducing the graph traversal already done by --follow revset
1649 # reproducing the graph traversal already done by --follow revset
1650 # and relating linkrevs to file names (which is not "correct" but
1650 # and relating linkrevs to file names (which is not "correct" but
1651 # good enough).
1651 # good enough).
1652 fcache = {}
1652 fcache = {}
1653 fcacheready = [False]
1653 fcacheready = [False]
1654 pctx = repo['.']
1654 pctx = repo['.']
1655
1655
1656 def populate():
1656 def populate():
1657 for fn in files:
1657 for fn in files:
1658 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1658 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1659 for c in i:
1659 for c in i:
1660 fcache.setdefault(c.linkrev(), set()).add(c.path())
1660 fcache.setdefault(c.linkrev(), set()).add(c.path())
1661
1661
1662 def filematcher(rev):
1662 def filematcher(rev):
1663 if not fcacheready[0]:
1663 if not fcacheready[0]:
1664 # Lazy initialization
1664 # Lazy initialization
1665 fcacheready[0] = True
1665 fcacheready[0] = True
1666 populate()
1666 populate()
1667 return scmutil.matchfiles(repo, fcache.get(rev, []))
1667 return scmutil.matchfiles(repo, fcache.get(rev, []))
1668
1668
1669 return filematcher
1669 return filematcher
1670
1670
1671 def _makenofollowlogfilematcher(repo, pats, opts):
1671 def _makenofollowlogfilematcher(repo, pats, opts):
1672 '''hook for extensions to override the filematcher for non-follow cases'''
1672 '''hook for extensions to override the filematcher for non-follow cases'''
1673 return None
1673 return None
1674
1674
1675 def _makelogrevset(repo, pats, opts, revs):
1675 def _makelogrevset(repo, pats, opts, revs):
1676 """Return (expr, filematcher) where expr is a revset string built
1676 """Return (expr, filematcher) where expr is a revset string built
1677 from log options and file patterns or None. If --stat or --patch
1677 from log options and file patterns or None. If --stat or --patch
1678 are not passed filematcher is None. Otherwise it is a callable
1678 are not passed filematcher is None. Otherwise it is a callable
1679 taking a revision number and returning a match objects filtering
1679 taking a revision number and returning a match objects filtering
1680 the files to be detailed when displaying the revision.
1680 the files to be detailed when displaying the revision.
1681 """
1681 """
1682 opt2revset = {
1682 opt2revset = {
1683 'no_merges': ('not merge()', None),
1683 'no_merges': ('not merge()', None),
1684 'only_merges': ('merge()', None),
1684 'only_merges': ('merge()', None),
1685 '_ancestors': ('ancestors(%(val)s)', None),
1685 '_ancestors': ('ancestors(%(val)s)', None),
1686 '_fancestors': ('_firstancestors(%(val)s)', None),
1686 '_fancestors': ('_firstancestors(%(val)s)', None),
1687 '_descendants': ('descendants(%(val)s)', None),
1687 '_descendants': ('descendants(%(val)s)', None),
1688 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1688 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1689 '_matchfiles': ('_matchfiles(%(val)s)', None),
1689 '_matchfiles': ('_matchfiles(%(val)s)', None),
1690 'date': ('date(%(val)r)', None),
1690 'date': ('date(%(val)r)', None),
1691 'branch': ('branch(%(val)r)', ' or '),
1691 'branch': ('branch(%(val)r)', ' or '),
1692 '_patslog': ('filelog(%(val)r)', ' or '),
1692 '_patslog': ('filelog(%(val)r)', ' or '),
1693 '_patsfollow': ('follow(%(val)r)', ' or '),
1693 '_patsfollow': ('follow(%(val)r)', ' or '),
1694 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1694 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1695 'keyword': ('keyword(%(val)r)', ' or '),
1695 'keyword': ('keyword(%(val)r)', ' or '),
1696 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1696 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1697 'user': ('user(%(val)r)', ' or '),
1697 'user': ('user(%(val)r)', ' or '),
1698 }
1698 }
1699
1699
1700 opts = dict(opts)
1700 opts = dict(opts)
1701 # follow or not follow?
1701 # follow or not follow?
1702 follow = opts.get('follow') or opts.get('follow_first')
1702 follow = opts.get('follow') or opts.get('follow_first')
1703 followfirst = opts.get('follow_first') and 1 or 0
1703 followfirst = opts.get('follow_first') and 1 or 0
1704 # --follow with FILE behaviour depends on revs...
1704 # --follow with FILE behaviour depends on revs...
1705 it = iter(revs)
1705 it = iter(revs)
1706 startrev = it.next()
1706 startrev = it.next()
1707 try:
1707 try:
1708 followdescendants = startrev < it.next()
1708 followdescendants = startrev < it.next()
1709 except (StopIteration):
1709 except (StopIteration):
1710 followdescendants = False
1710 followdescendants = False
1711
1711
1712 # branch and only_branch are really aliases and must be handled at
1712 # branch and only_branch are really aliases and must be handled at
1713 # the same time
1713 # the same time
1714 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1714 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1715 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1715 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1716 # pats/include/exclude are passed to match.match() directly in
1716 # pats/include/exclude are passed to match.match() directly in
1717 # _matchfiles() revset but walkchangerevs() builds its matcher with
1717 # _matchfiles() revset but walkchangerevs() builds its matcher with
1718 # scmutil.match(). The difference is input pats are globbed on
1718 # scmutil.match(). The difference is input pats are globbed on
1719 # platforms without shell expansion (windows).
1719 # platforms without shell expansion (windows).
1720 pctx = repo[None]
1720 pctx = repo[None]
1721 match, pats = scmutil.matchandpats(pctx, pats, opts)
1721 match, pats = scmutil.matchandpats(pctx, pats, opts)
1722 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1722 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1723 if not slowpath:
1723 if not slowpath:
1724 for f in match.files():
1724 for f in match.files():
1725 if follow and f not in pctx:
1725 if follow and f not in pctx:
1726 # If the file exists, it may be a directory, so let it
1726 # If the file exists, it may be a directory, so let it
1727 # take the slow path.
1727 # take the slow path.
1728 if os.path.exists(repo.wjoin(f)):
1728 if os.path.exists(repo.wjoin(f)):
1729 slowpath = True
1729 slowpath = True
1730 continue
1730 continue
1731 else:
1731 else:
1732 raise util.Abort(_('cannot follow file not in parent '
1732 raise util.Abort(_('cannot follow file not in parent '
1733 'revision: "%s"') % f)
1733 'revision: "%s"') % f)
1734 filelog = repo.file(f)
1734 filelog = repo.file(f)
1735 if not filelog:
1735 if not filelog:
1736 # A zero count may be a directory or deleted file, so
1736 # A zero count may be a directory or deleted file, so
1737 # try to find matching entries on the slow path.
1737 # try to find matching entries on the slow path.
1738 if follow:
1738 if follow:
1739 raise util.Abort(
1739 raise util.Abort(
1740 _('cannot follow nonexistent file: "%s"') % f)
1740 _('cannot follow nonexistent file: "%s"') % f)
1741 slowpath = True
1741 slowpath = True
1742
1742
1743 # We decided to fall back to the slowpath because at least one
1743 # We decided to fall back to the slowpath because at least one
1744 # of the paths was not a file. Check to see if at least one of them
1744 # of the paths was not a file. Check to see if at least one of them
1745 # existed in history - in that case, we'll continue down the
1745 # existed in history - in that case, we'll continue down the
1746 # slowpath; otherwise, we can turn off the slowpath
1746 # slowpath; otherwise, we can turn off the slowpath
1747 if slowpath:
1747 if slowpath:
1748 for path in match.files():
1748 for path in match.files():
1749 if path == '.' or path in repo.store:
1749 if path == '.' or path in repo.store:
1750 break
1750 break
1751 else:
1751 else:
1752 slowpath = False
1752 slowpath = False
1753
1753
1754 fpats = ('_patsfollow', '_patsfollowfirst')
1754 fpats = ('_patsfollow', '_patsfollowfirst')
1755 fnopats = (('_ancestors', '_fancestors'),
1755 fnopats = (('_ancestors', '_fancestors'),
1756 ('_descendants', '_fdescendants'))
1756 ('_descendants', '_fdescendants'))
1757 if slowpath:
1757 if slowpath:
1758 # See walkchangerevs() slow path.
1758 # See walkchangerevs() slow path.
1759 #
1759 #
1760 # pats/include/exclude cannot be represented as separate
1760 # pats/include/exclude cannot be represented as separate
1761 # revset expressions as their filtering logic applies at file
1761 # revset expressions as their filtering logic applies at file
1762 # level. For instance "-I a -X a" matches a revision touching
1762 # level. For instance "-I a -X a" matches a revision touching
1763 # "a" and "b" while "file(a) and not file(b)" does
1763 # "a" and "b" while "file(a) and not file(b)" does
1764 # not. Besides, filesets are evaluated against the working
1764 # not. Besides, filesets are evaluated against the working
1765 # directory.
1765 # directory.
1766 matchargs = ['r:', 'd:relpath']
1766 matchargs = ['r:', 'd:relpath']
1767 for p in pats:
1767 for p in pats:
1768 matchargs.append('p:' + p)
1768 matchargs.append('p:' + p)
1769 for p in opts.get('include', []):
1769 for p in opts.get('include', []):
1770 matchargs.append('i:' + p)
1770 matchargs.append('i:' + p)
1771 for p in opts.get('exclude', []):
1771 for p in opts.get('exclude', []):
1772 matchargs.append('x:' + p)
1772 matchargs.append('x:' + p)
1773 matchargs = ','.join(('%r' % p) for p in matchargs)
1773 matchargs = ','.join(('%r' % p) for p in matchargs)
1774 opts['_matchfiles'] = matchargs
1774 opts['_matchfiles'] = matchargs
1775 if follow:
1775 if follow:
1776 opts[fnopats[0][followfirst]] = '.'
1776 opts[fnopats[0][followfirst]] = '.'
1777 else:
1777 else:
1778 if follow:
1778 if follow:
1779 if pats:
1779 if pats:
1780 # follow() revset interprets its file argument as a
1780 # follow() revset interprets its file argument as a
1781 # manifest entry, so use match.files(), not pats.
1781 # manifest entry, so use match.files(), not pats.
1782 opts[fpats[followfirst]] = list(match.files())
1782 opts[fpats[followfirst]] = list(match.files())
1783 else:
1783 else:
1784 opts[fnopats[followdescendants][followfirst]] = str(startrev)
1784 opts[fnopats[followdescendants][followfirst]] = str(startrev)
1785 else:
1785 else:
1786 opts['_patslog'] = list(pats)
1786 opts['_patslog'] = list(pats)
1787
1787
1788 filematcher = None
1788 filematcher = None
1789 if opts.get('patch') or opts.get('stat'):
1789 if opts.get('patch') or opts.get('stat'):
1790 # When following files, track renames via a special matcher.
1790 # When following files, track renames via a special matcher.
1791 # If we're forced to take the slowpath it means we're following
1791 # If we're forced to take the slowpath it means we're following
1792 # at least one pattern/directory, so don't bother with rename tracking.
1792 # at least one pattern/directory, so don't bother with rename tracking.
1793 if follow and not match.always() and not slowpath:
1793 if follow and not match.always() and not slowpath:
1794 # _makefollowlogfilematcher expects its files argument to be
1794 # _makefollowlogfilematcher expects its files argument to be
1795 # relative to the repo root, so use match.files(), not pats.
1795 # relative to the repo root, so use match.files(), not pats.
1796 filematcher = _makefollowlogfilematcher(repo, match.files(),
1796 filematcher = _makefollowlogfilematcher(repo, match.files(),
1797 followfirst)
1797 followfirst)
1798 else:
1798 else:
1799 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
1799 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
1800 if filematcher is None:
1800 if filematcher is None:
1801 filematcher = lambda rev: match
1801 filematcher = lambda rev: match
1802
1802
1803 expr = []
1803 expr = []
1804 for op, val in sorted(opts.iteritems()):
1804 for op, val in sorted(opts.iteritems()):
1805 if not val:
1805 if not val:
1806 continue
1806 continue
1807 if op not in opt2revset:
1807 if op not in opt2revset:
1808 continue
1808 continue
1809 revop, andor = opt2revset[op]
1809 revop, andor = opt2revset[op]
1810 if '%(val)' not in revop:
1810 if '%(val)' not in revop:
1811 expr.append(revop)
1811 expr.append(revop)
1812 else:
1812 else:
1813 if not isinstance(val, list):
1813 if not isinstance(val, list):
1814 e = revop % {'val': val}
1814 e = revop % {'val': val}
1815 else:
1815 else:
1816 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
1816 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
1817 expr.append(e)
1817 expr.append(e)
1818
1818
1819 if expr:
1819 if expr:
1820 expr = '(' + ' and '.join(expr) + ')'
1820 expr = '(' + ' and '.join(expr) + ')'
1821 else:
1821 else:
1822 expr = None
1822 expr = None
1823 return expr, filematcher
1823 return expr, filematcher
1824
1824
1825 def getgraphlogrevs(repo, pats, opts):
1825 def getgraphlogrevs(repo, pats, opts):
1826 """Return (revs, expr, filematcher) where revs is an iterable of
1826 """Return (revs, expr, filematcher) where revs is an iterable of
1827 revision numbers, expr is a revset string built from log options
1827 revision numbers, expr is a revset string built from log options
1828 and file patterns or None, and used to filter 'revs'. If --stat or
1828 and file patterns or None, and used to filter 'revs'. If --stat or
1829 --patch are not passed filematcher is None. Otherwise it is a
1829 --patch are not passed filematcher is None. Otherwise it is a
1830 callable taking a revision number and returning a match objects
1830 callable taking a revision number and returning a match objects
1831 filtering the files to be detailed when displaying the revision.
1831 filtering the files to be detailed when displaying the revision.
1832 """
1832 """
1833 if not len(repo):
1833 if not len(repo):
1834 return [], None, None
1834 return [], None, None
1835 limit = loglimit(opts)
1835 limit = loglimit(opts)
1836 # Default --rev value depends on --follow but --follow behaviour
1836 # Default --rev value depends on --follow but --follow behaviour
1837 # depends on revisions resolved from --rev...
1837 # depends on revisions resolved from --rev...
1838 follow = opts.get('follow') or opts.get('follow_first')
1838 follow = opts.get('follow') or opts.get('follow_first')
1839 possiblyunsorted = False # whether revs might need sorting
1839 possiblyunsorted = False # whether revs might need sorting
1840 if opts.get('rev'):
1840 if opts.get('rev'):
1841 revs = scmutil.revrange(repo, opts['rev'])
1841 revs = scmutil.revrange(repo, opts['rev'])
1842 # Don't sort here because _makelogrevset might depend on the
1842 # Don't sort here because _makelogrevset might depend on the
1843 # order of revs
1843 # order of revs
1844 possiblyunsorted = True
1844 possiblyunsorted = True
1845 else:
1845 else:
1846 if follow and len(repo) > 0:
1846 if follow and len(repo) > 0:
1847 revs = repo.revs('reverse(:.)')
1847 revs = repo.revs('reverse(:.)')
1848 else:
1848 else:
1849 revs = revset.spanset(repo)
1849 revs = revset.spanset(repo)
1850 revs.reverse()
1850 revs.reverse()
1851 if not revs:
1851 if not revs:
1852 return revset.baseset(), None, None
1852 return revset.baseset(), None, None
1853 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
1853 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
1854 if possiblyunsorted:
1854 if possiblyunsorted:
1855 revs.sort(reverse=True)
1855 revs.sort(reverse=True)
1856 if expr:
1856 if expr:
1857 # Revset matchers often operate faster on revisions in changelog
1857 # Revset matchers often operate faster on revisions in changelog
1858 # order, because most filters deal with the changelog.
1858 # order, because most filters deal with the changelog.
1859 revs.reverse()
1859 revs.reverse()
1860 matcher = revset.match(repo.ui, expr)
1860 matcher = revset.match(repo.ui, expr)
1861 # Revset matches can reorder revisions. "A or B" typically returns
1861 # Revset matches can reorder revisions. "A or B" typically returns
1862 # returns the revision matching A then the revision matching B. Sort
1862 # returns the revision matching A then the revision matching B. Sort
1863 # again to fix that.
1863 # again to fix that.
1864 revs = matcher(repo, revs)
1864 revs = matcher(repo, revs)
1865 revs.sort(reverse=True)
1865 revs.sort(reverse=True)
1866 if limit is not None:
1866 if limit is not None:
1867 limitedrevs = []
1867 limitedrevs = []
1868 for idx, rev in enumerate(revs):
1868 for idx, rev in enumerate(revs):
1869 if idx >= limit:
1869 if idx >= limit:
1870 break
1870 break
1871 limitedrevs.append(rev)
1871 limitedrevs.append(rev)
1872 revs = revset.baseset(limitedrevs)
1872 revs = revset.baseset(limitedrevs)
1873
1873
1874 return revs, expr, filematcher
1874 return revs, expr, filematcher
1875
1875
1876 def getlogrevs(repo, pats, opts):
1876 def getlogrevs(repo, pats, opts):
1877 """Return (revs, expr, filematcher) where revs is an iterable of
1877 """Return (revs, expr, filematcher) where revs is an iterable of
1878 revision numbers, expr is a revset string built from log options
1878 revision numbers, expr is a revset string built from log options
1879 and file patterns or None, and used to filter 'revs'. If --stat or
1879 and file patterns or None, and used to filter 'revs'. If --stat or
1880 --patch are not passed filematcher is None. Otherwise it is a
1880 --patch are not passed filematcher is None. Otherwise it is a
1881 callable taking a revision number and returning a match objects
1881 callable taking a revision number and returning a match objects
1882 filtering the files to be detailed when displaying the revision.
1882 filtering the files to be detailed when displaying the revision.
1883 """
1883 """
1884 limit = loglimit(opts)
1884 limit = loglimit(opts)
1885 # Default --rev value depends on --follow but --follow behaviour
1885 # Default --rev value depends on --follow but --follow behaviour
1886 # depends on revisions resolved from --rev...
1886 # depends on revisions resolved from --rev...
1887 follow = opts.get('follow') or opts.get('follow_first')
1887 follow = opts.get('follow') or opts.get('follow_first')
1888 if opts.get('rev'):
1888 if opts.get('rev'):
1889 revs = scmutil.revrange(repo, opts['rev'])
1889 revs = scmutil.revrange(repo, opts['rev'])
1890 elif follow:
1890 elif follow:
1891 revs = repo.revs('reverse(:.)')
1891 revs = repo.revs('reverse(:.)')
1892 else:
1892 else:
1893 revs = revset.spanset(repo)
1893 revs = revset.spanset(repo)
1894 revs.reverse()
1894 revs.reverse()
1895 if not revs:
1895 if not revs:
1896 return revset.baseset([]), None, None
1896 return revset.baseset([]), None, None
1897 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
1897 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
1898 if expr:
1898 if expr:
1899 # Revset matchers often operate faster on revisions in changelog
1899 # Revset matchers often operate faster on revisions in changelog
1900 # order, because most filters deal with the changelog.
1900 # order, because most filters deal with the changelog.
1901 if not opts.get('rev'):
1901 if not opts.get('rev'):
1902 revs.reverse()
1902 revs.reverse()
1903 matcher = revset.match(repo.ui, expr)
1903 matcher = revset.match(repo.ui, expr)
1904 # Revset matches can reorder revisions. "A or B" typically returns
1904 # Revset matches can reorder revisions. "A or B" typically returns
1905 # returns the revision matching A then the revision matching B. Sort
1905 # returns the revision matching A then the revision matching B. Sort
1906 # again to fix that.
1906 # again to fix that.
1907 revs = matcher(repo, revs)
1907 revs = matcher(repo, revs)
1908 if not opts.get('rev'):
1908 if not opts.get('rev'):
1909 revs.sort(reverse=True)
1909 revs.sort(reverse=True)
1910 if limit is not None:
1910 if limit is not None:
1911 count = 0
1911 count = 0
1912 limitedrevs = []
1912 limitedrevs = []
1913 it = iter(revs)
1913 it = iter(revs)
1914 while count < limit:
1914 while count < limit:
1915 try:
1915 try:
1916 limitedrevs.append(it.next())
1916 limitedrevs.append(it.next())
1917 except (StopIteration):
1917 except (StopIteration):
1918 break
1918 break
1919 count += 1
1919 count += 1
1920 revs = revset.baseset(limitedrevs)
1920 revs = revset.baseset(limitedrevs)
1921
1921
1922 return revs, expr, filematcher
1922 return revs, expr, filematcher
1923
1923
1924 def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
1924 def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
1925 filematcher=None):
1925 filematcher=None):
1926 seen, state = [], graphmod.asciistate()
1926 seen, state = [], graphmod.asciistate()
1927 for rev, type, ctx, parents in dag:
1927 for rev, type, ctx, parents in dag:
1928 char = 'o'
1928 char = 'o'
1929 if ctx.node() in showparents:
1929 if ctx.node() in showparents:
1930 char = '@'
1930 char = '@'
1931 elif ctx.obsolete():
1931 elif ctx.obsolete():
1932 char = 'x'
1932 char = 'x'
1933 copies = None
1933 copies = None
1934 if getrenamed and ctx.rev():
1934 if getrenamed and ctx.rev():
1935 copies = []
1935 copies = []
1936 for fn in ctx.files():
1936 for fn in ctx.files():
1937 rename = getrenamed(fn, ctx.rev())
1937 rename = getrenamed(fn, ctx.rev())
1938 if rename:
1938 if rename:
1939 copies.append((fn, rename[0]))
1939 copies.append((fn, rename[0]))
1940 revmatchfn = None
1940 revmatchfn = None
1941 if filematcher is not None:
1941 if filematcher is not None:
1942 revmatchfn = filematcher(ctx.rev())
1942 revmatchfn = filematcher(ctx.rev())
1943 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
1943 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
1944 lines = displayer.hunk.pop(rev).split('\n')
1944 lines = displayer.hunk.pop(rev).split('\n')
1945 if not lines[-1]:
1945 if not lines[-1]:
1946 del lines[-1]
1946 del lines[-1]
1947 displayer.flush(rev)
1947 displayer.flush(rev)
1948 edges = edgefn(type, char, lines, seen, rev, parents)
1948 edges = edgefn(type, char, lines, seen, rev, parents)
1949 for type, char, lines, coldata in edges:
1949 for type, char, lines, coldata in edges:
1950 graphmod.ascii(ui, state, type, char, lines, coldata)
1950 graphmod.ascii(ui, state, type, char, lines, coldata)
1951 displayer.close()
1951 displayer.close()
1952
1952
1953 def graphlog(ui, repo, *pats, **opts):
1953 def graphlog(ui, repo, *pats, **opts):
1954 # Parameters are identical to log command ones
1954 # Parameters are identical to log command ones
1955 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
1955 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
1956 revdag = graphmod.dagwalker(repo, revs)
1956 revdag = graphmod.dagwalker(repo, revs)
1957
1957
1958 getrenamed = None
1958 getrenamed = None
1959 if opts.get('copies'):
1959 if opts.get('copies'):
1960 endrev = None
1960 endrev = None
1961 if opts.get('rev'):
1961 if opts.get('rev'):
1962 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
1962 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
1963 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
1963 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
1964 displayer = show_changeset(ui, repo, opts, buffered=True)
1964 displayer = show_changeset(ui, repo, opts, buffered=True)
1965 showparents = [ctx.node() for ctx in repo[None].parents()]
1965 showparents = [ctx.node() for ctx in repo[None].parents()]
1966 displaygraph(ui, revdag, displayer, showparents,
1966 displaygraph(ui, revdag, displayer, showparents,
1967 graphmod.asciiedges, getrenamed, filematcher)
1967 graphmod.asciiedges, getrenamed, filematcher)
1968
1968
1969 def checkunsupportedgraphflags(pats, opts):
1969 def checkunsupportedgraphflags(pats, opts):
1970 for op in ["newest_first"]:
1970 for op in ["newest_first"]:
1971 if op in opts and opts[op]:
1971 if op in opts and opts[op]:
1972 raise util.Abort(_("-G/--graph option is incompatible with --%s")
1972 raise util.Abort(_("-G/--graph option is incompatible with --%s")
1973 % op.replace("_", "-"))
1973 % op.replace("_", "-"))
1974
1974
1975 def graphrevs(repo, nodes, opts):
1975 def graphrevs(repo, nodes, opts):
1976 limit = loglimit(opts)
1976 limit = loglimit(opts)
1977 nodes.reverse()
1977 nodes.reverse()
1978 if limit is not None:
1978 if limit is not None:
1979 nodes = nodes[:limit]
1979 nodes = nodes[:limit]
1980 return graphmod.nodes(repo, nodes)
1980 return graphmod.nodes(repo, nodes)
1981
1981
1982 def add(ui, repo, match, dryrun, listsubrepos, prefix, explicitonly):
1982 def add(ui, repo, match, prefix, explicitonly, **opts):
1983 join = lambda f: os.path.join(prefix, f)
1983 join = lambda f: os.path.join(prefix, f)
1984 bad = []
1984 bad = []
1985 oldbad = match.bad
1985 oldbad = match.bad
1986 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1986 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1987 names = []
1987 names = []
1988 wctx = repo[None]
1988 wctx = repo[None]
1989 cca = None
1989 cca = None
1990 abort, warn = scmutil.checkportabilityalert(ui)
1990 abort, warn = scmutil.checkportabilityalert(ui)
1991 if abort or warn:
1991 if abort or warn:
1992 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
1992 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
1993 for f in wctx.walk(match):
1993 for f in wctx.walk(match):
1994 exact = match.exact(f)
1994 exact = match.exact(f)
1995 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
1995 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
1996 if cca:
1996 if cca:
1997 cca(f)
1997 cca(f)
1998 names.append(f)
1998 names.append(f)
1999 if ui.verbose or not exact:
1999 if ui.verbose or not exact:
2000 ui.status(_('adding %s\n') % match.rel(f))
2000 ui.status(_('adding %s\n') % match.rel(f))
2001
2001
2002 for subpath in sorted(wctx.substate):
2002 for subpath in sorted(wctx.substate):
2003 sub = wctx.sub(subpath)
2003 sub = wctx.sub(subpath)
2004 try:
2004 try:
2005 submatch = matchmod.narrowmatcher(subpath, match)
2005 submatch = matchmod.narrowmatcher(subpath, match)
2006 if listsubrepos:
2006 if opts.get('subrepos'):
2007 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
2007 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2008 False))
2009 else:
2008 else:
2010 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
2009 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2011 True))
2012 except error.LookupError:
2010 except error.LookupError:
2013 ui.status(_("skipping missing subrepository: %s\n")
2011 ui.status(_("skipping missing subrepository: %s\n")
2014 % join(subpath))
2012 % join(subpath))
2015
2013
2016 if not dryrun:
2014 if not opts.get('dry_run'):
2017 rejected = wctx.add(names, prefix)
2015 rejected = wctx.add(names, prefix)
2018 bad.extend(f for f in rejected if f in match.files())
2016 bad.extend(f for f in rejected if f in match.files())
2019 return bad
2017 return bad
2020
2018
2021 def forget(ui, repo, match, prefix, explicitonly):
2019 def forget(ui, repo, match, prefix, explicitonly):
2022 join = lambda f: os.path.join(prefix, f)
2020 join = lambda f: os.path.join(prefix, f)
2023 bad = []
2021 bad = []
2024 oldbad = match.bad
2022 oldbad = match.bad
2025 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
2023 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
2026 wctx = repo[None]
2024 wctx = repo[None]
2027 forgot = []
2025 forgot = []
2028 s = repo.status(match=match, clean=True)
2026 s = repo.status(match=match, clean=True)
2029 forget = sorted(s[0] + s[1] + s[3] + s[6])
2027 forget = sorted(s[0] + s[1] + s[3] + s[6])
2030 if explicitonly:
2028 if explicitonly:
2031 forget = [f for f in forget if match.exact(f)]
2029 forget = [f for f in forget if match.exact(f)]
2032
2030
2033 for subpath in sorted(wctx.substate):
2031 for subpath in sorted(wctx.substate):
2034 sub = wctx.sub(subpath)
2032 sub = wctx.sub(subpath)
2035 try:
2033 try:
2036 submatch = matchmod.narrowmatcher(subpath, match)
2034 submatch = matchmod.narrowmatcher(subpath, match)
2037 subbad, subforgot = sub.forget(submatch, prefix)
2035 subbad, subforgot = sub.forget(submatch, prefix)
2038 bad.extend([subpath + '/' + f for f in subbad])
2036 bad.extend([subpath + '/' + f for f in subbad])
2039 forgot.extend([subpath + '/' + f for f in subforgot])
2037 forgot.extend([subpath + '/' + f for f in subforgot])
2040 except error.LookupError:
2038 except error.LookupError:
2041 ui.status(_("skipping missing subrepository: %s\n")
2039 ui.status(_("skipping missing subrepository: %s\n")
2042 % join(subpath))
2040 % join(subpath))
2043
2041
2044 if not explicitonly:
2042 if not explicitonly:
2045 for f in match.files():
2043 for f in match.files():
2046 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2044 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2047 if f not in forgot:
2045 if f not in forgot:
2048 if repo.wvfs.exists(f):
2046 if repo.wvfs.exists(f):
2049 ui.warn(_('not removing %s: '
2047 ui.warn(_('not removing %s: '
2050 'file is already untracked\n')
2048 'file is already untracked\n')
2051 % match.rel(f))
2049 % match.rel(f))
2052 bad.append(f)
2050 bad.append(f)
2053
2051
2054 for f in forget:
2052 for f in forget:
2055 if ui.verbose or not match.exact(f):
2053 if ui.verbose or not match.exact(f):
2056 ui.status(_('removing %s\n') % match.rel(f))
2054 ui.status(_('removing %s\n') % match.rel(f))
2057
2055
2058 rejected = wctx.forget(forget, prefix)
2056 rejected = wctx.forget(forget, prefix)
2059 bad.extend(f for f in rejected if f in match.files())
2057 bad.extend(f for f in rejected if f in match.files())
2060 forgot.extend(f for f in forget if f not in rejected)
2058 forgot.extend(f for f in forget if f not in rejected)
2061 return bad, forgot
2059 return bad, forgot
2062
2060
2063 def remove(ui, repo, m, prefix, after, force, subrepos):
2061 def remove(ui, repo, m, prefix, after, force, subrepos):
2064 join = lambda f: os.path.join(prefix, f)
2062 join = lambda f: os.path.join(prefix, f)
2065 ret = 0
2063 ret = 0
2066 s = repo.status(match=m, clean=True)
2064 s = repo.status(match=m, clean=True)
2067 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2065 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2068
2066
2069 wctx = repo[None]
2067 wctx = repo[None]
2070
2068
2071 for subpath in sorted(wctx.substate):
2069 for subpath in sorted(wctx.substate):
2072 def matchessubrepo(matcher, subpath):
2070 def matchessubrepo(matcher, subpath):
2073 if matcher.exact(subpath):
2071 if matcher.exact(subpath):
2074 return True
2072 return True
2075 for f in matcher.files():
2073 for f in matcher.files():
2076 if f.startswith(subpath):
2074 if f.startswith(subpath):
2077 return True
2075 return True
2078 return False
2076 return False
2079
2077
2080 if subrepos or matchessubrepo(m, subpath):
2078 if subrepos or matchessubrepo(m, subpath):
2081 sub = wctx.sub(subpath)
2079 sub = wctx.sub(subpath)
2082 try:
2080 try:
2083 submatch = matchmod.narrowmatcher(subpath, m)
2081 submatch = matchmod.narrowmatcher(subpath, m)
2084 if sub.removefiles(submatch, prefix, after, force, subrepos):
2082 if sub.removefiles(submatch, prefix, after, force, subrepos):
2085 ret = 1
2083 ret = 1
2086 except error.LookupError:
2084 except error.LookupError:
2087 ui.status(_("skipping missing subrepository: %s\n")
2085 ui.status(_("skipping missing subrepository: %s\n")
2088 % join(subpath))
2086 % join(subpath))
2089
2087
2090 # warn about failure to delete explicit files/dirs
2088 # warn about failure to delete explicit files/dirs
2091 for f in m.files():
2089 for f in m.files():
2092 def insubrepo():
2090 def insubrepo():
2093 for subpath in wctx.substate:
2091 for subpath in wctx.substate:
2094 if f.startswith(subpath):
2092 if f.startswith(subpath):
2095 return True
2093 return True
2096 return False
2094 return False
2097
2095
2098 if f in repo.dirstate or f in wctx.dirs() or f == '.' or insubrepo():
2096 if f in repo.dirstate or f in wctx.dirs() or f == '.' or insubrepo():
2099 continue
2097 continue
2100
2098
2101 if repo.wvfs.exists(f):
2099 if repo.wvfs.exists(f):
2102 if repo.wvfs.isdir(f):
2100 if repo.wvfs.isdir(f):
2103 ui.warn(_('not removing %s: no tracked files\n')
2101 ui.warn(_('not removing %s: no tracked files\n')
2104 % m.rel(f))
2102 % m.rel(f))
2105 else:
2103 else:
2106 ui.warn(_('not removing %s: file is untracked\n')
2104 ui.warn(_('not removing %s: file is untracked\n')
2107 % m.rel(f))
2105 % m.rel(f))
2108 # missing files will generate a warning elsewhere
2106 # missing files will generate a warning elsewhere
2109 ret = 1
2107 ret = 1
2110
2108
2111 if force:
2109 if force:
2112 list = modified + deleted + clean + added
2110 list = modified + deleted + clean + added
2113 elif after:
2111 elif after:
2114 list = deleted
2112 list = deleted
2115 for f in modified + added + clean:
2113 for f in modified + added + clean:
2116 ui.warn(_('not removing %s: file still exists\n') % m.rel(f))
2114 ui.warn(_('not removing %s: file still exists\n') % m.rel(f))
2117 ret = 1
2115 ret = 1
2118 else:
2116 else:
2119 list = deleted + clean
2117 list = deleted + clean
2120 for f in modified:
2118 for f in modified:
2121 ui.warn(_('not removing %s: file is modified (use -f'
2119 ui.warn(_('not removing %s: file is modified (use -f'
2122 ' to force removal)\n') % m.rel(f))
2120 ' to force removal)\n') % m.rel(f))
2123 ret = 1
2121 ret = 1
2124 for f in added:
2122 for f in added:
2125 ui.warn(_('not removing %s: file has been marked for add'
2123 ui.warn(_('not removing %s: file has been marked for add'
2126 ' (use forget to undo)\n') % m.rel(f))
2124 ' (use forget to undo)\n') % m.rel(f))
2127 ret = 1
2125 ret = 1
2128
2126
2129 for f in sorted(list):
2127 for f in sorted(list):
2130 if ui.verbose or not m.exact(f):
2128 if ui.verbose or not m.exact(f):
2131 ui.status(_('removing %s\n') % m.rel(f))
2129 ui.status(_('removing %s\n') % m.rel(f))
2132
2130
2133 wlock = repo.wlock()
2131 wlock = repo.wlock()
2134 try:
2132 try:
2135 if not after:
2133 if not after:
2136 for f in list:
2134 for f in list:
2137 if f in added:
2135 if f in added:
2138 continue # we never unlink added files on remove
2136 continue # we never unlink added files on remove
2139 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
2137 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
2140 repo[None].forget(list)
2138 repo[None].forget(list)
2141 finally:
2139 finally:
2142 wlock.release()
2140 wlock.release()
2143
2141
2144 return ret
2142 return ret
2145
2143
2146 def cat(ui, repo, ctx, matcher, prefix, **opts):
2144 def cat(ui, repo, ctx, matcher, prefix, **opts):
2147 err = 1
2145 err = 1
2148
2146
2149 def write(path):
2147 def write(path):
2150 fp = makefileobj(repo, opts.get('output'), ctx.node(),
2148 fp = makefileobj(repo, opts.get('output'), ctx.node(),
2151 pathname=os.path.join(prefix, path))
2149 pathname=os.path.join(prefix, path))
2152 data = ctx[path].data()
2150 data = ctx[path].data()
2153 if opts.get('decode'):
2151 if opts.get('decode'):
2154 data = repo.wwritedata(path, data)
2152 data = repo.wwritedata(path, data)
2155 fp.write(data)
2153 fp.write(data)
2156 fp.close()
2154 fp.close()
2157
2155
2158 # Automation often uses hg cat on single files, so special case it
2156 # Automation often uses hg cat on single files, so special case it
2159 # for performance to avoid the cost of parsing the manifest.
2157 # for performance to avoid the cost of parsing the manifest.
2160 if len(matcher.files()) == 1 and not matcher.anypats():
2158 if len(matcher.files()) == 1 and not matcher.anypats():
2161 file = matcher.files()[0]
2159 file = matcher.files()[0]
2162 mf = repo.manifest
2160 mf = repo.manifest
2163 mfnode = ctx._changeset[0]
2161 mfnode = ctx._changeset[0]
2164 if mf.find(mfnode, file)[0]:
2162 if mf.find(mfnode, file)[0]:
2165 write(file)
2163 write(file)
2166 return 0
2164 return 0
2167
2165
2168 # Don't warn about "missing" files that are really in subrepos
2166 # Don't warn about "missing" files that are really in subrepos
2169 bad = matcher.bad
2167 bad = matcher.bad
2170
2168
2171 def badfn(path, msg):
2169 def badfn(path, msg):
2172 for subpath in ctx.substate:
2170 for subpath in ctx.substate:
2173 if path.startswith(subpath):
2171 if path.startswith(subpath):
2174 return
2172 return
2175 bad(path, msg)
2173 bad(path, msg)
2176
2174
2177 matcher.bad = badfn
2175 matcher.bad = badfn
2178
2176
2179 for abs in ctx.walk(matcher):
2177 for abs in ctx.walk(matcher):
2180 write(abs)
2178 write(abs)
2181 err = 0
2179 err = 0
2182
2180
2183 matcher.bad = bad
2181 matcher.bad = bad
2184
2182
2185 for subpath in sorted(ctx.substate):
2183 for subpath in sorted(ctx.substate):
2186 sub = ctx.sub(subpath)
2184 sub = ctx.sub(subpath)
2187 try:
2185 try:
2188 submatch = matchmod.narrowmatcher(subpath, matcher)
2186 submatch = matchmod.narrowmatcher(subpath, matcher)
2189
2187
2190 if not sub.cat(submatch, os.path.join(prefix, sub._path),
2188 if not sub.cat(submatch, os.path.join(prefix, sub._path),
2191 **opts):
2189 **opts):
2192 err = 0
2190 err = 0
2193 except error.RepoLookupError:
2191 except error.RepoLookupError:
2194 ui.status(_("skipping missing subrepository: %s\n")
2192 ui.status(_("skipping missing subrepository: %s\n")
2195 % os.path.join(prefix, subpath))
2193 % os.path.join(prefix, subpath))
2196
2194
2197 return err
2195 return err
2198
2196
2199 def commit(ui, repo, commitfunc, pats, opts):
2197 def commit(ui, repo, commitfunc, pats, opts):
2200 '''commit the specified files or all outstanding changes'''
2198 '''commit the specified files or all outstanding changes'''
2201 date = opts.get('date')
2199 date = opts.get('date')
2202 if date:
2200 if date:
2203 opts['date'] = util.parsedate(date)
2201 opts['date'] = util.parsedate(date)
2204 message = logmessage(ui, opts)
2202 message = logmessage(ui, opts)
2205 matcher = scmutil.match(repo[None], pats, opts)
2203 matcher = scmutil.match(repo[None], pats, opts)
2206
2204
2207 # extract addremove carefully -- this function can be called from a command
2205 # extract addremove carefully -- this function can be called from a command
2208 # that doesn't support addremove
2206 # that doesn't support addremove
2209 if opts.get('addremove'):
2207 if opts.get('addremove'):
2210 if scmutil.addremove(repo, matcher, "", opts) != 0:
2208 if scmutil.addremove(repo, matcher, "", opts) != 0:
2211 raise util.Abort(
2209 raise util.Abort(
2212 _("failed to mark all new/missing files as added/removed"))
2210 _("failed to mark all new/missing files as added/removed"))
2213
2211
2214 return commitfunc(ui, repo, message, matcher, opts)
2212 return commitfunc(ui, repo, message, matcher, opts)
2215
2213
2216 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2214 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2217 # amend will reuse the existing user if not specified, but the obsolete
2215 # amend will reuse the existing user if not specified, but the obsolete
2218 # marker creation requires that the current user's name is specified.
2216 # marker creation requires that the current user's name is specified.
2219 if obsolete._enabled:
2217 if obsolete._enabled:
2220 ui.username() # raise exception if username not set
2218 ui.username() # raise exception if username not set
2221
2219
2222 ui.note(_('amending changeset %s\n') % old)
2220 ui.note(_('amending changeset %s\n') % old)
2223 base = old.p1()
2221 base = old.p1()
2224
2222
2225 wlock = lock = newid = None
2223 wlock = lock = newid = None
2226 try:
2224 try:
2227 wlock = repo.wlock()
2225 wlock = repo.wlock()
2228 lock = repo.lock()
2226 lock = repo.lock()
2229 tr = repo.transaction('amend')
2227 tr = repo.transaction('amend')
2230 try:
2228 try:
2231 # See if we got a message from -m or -l, if not, open the editor
2229 # See if we got a message from -m or -l, if not, open the editor
2232 # with the message of the changeset to amend
2230 # with the message of the changeset to amend
2233 message = logmessage(ui, opts)
2231 message = logmessage(ui, opts)
2234 # ensure logfile does not conflict with later enforcement of the
2232 # ensure logfile does not conflict with later enforcement of the
2235 # message. potential logfile content has been processed by
2233 # message. potential logfile content has been processed by
2236 # `logmessage` anyway.
2234 # `logmessage` anyway.
2237 opts.pop('logfile')
2235 opts.pop('logfile')
2238 # First, do a regular commit to record all changes in the working
2236 # First, do a regular commit to record all changes in the working
2239 # directory (if there are any)
2237 # directory (if there are any)
2240 ui.callhooks = False
2238 ui.callhooks = False
2241 currentbookmark = repo._bookmarkcurrent
2239 currentbookmark = repo._bookmarkcurrent
2242 try:
2240 try:
2243 repo._bookmarkcurrent = None
2241 repo._bookmarkcurrent = None
2244 opts['message'] = 'temporary amend commit for %s' % old
2242 opts['message'] = 'temporary amend commit for %s' % old
2245 node = commit(ui, repo, commitfunc, pats, opts)
2243 node = commit(ui, repo, commitfunc, pats, opts)
2246 finally:
2244 finally:
2247 repo._bookmarkcurrent = currentbookmark
2245 repo._bookmarkcurrent = currentbookmark
2248 ui.callhooks = True
2246 ui.callhooks = True
2249 ctx = repo[node]
2247 ctx = repo[node]
2250
2248
2251 # Participating changesets:
2249 # Participating changesets:
2252 #
2250 #
2253 # node/ctx o - new (intermediate) commit that contains changes
2251 # node/ctx o - new (intermediate) commit that contains changes
2254 # | from working dir to go into amending commit
2252 # | from working dir to go into amending commit
2255 # | (or a workingctx if there were no changes)
2253 # | (or a workingctx if there were no changes)
2256 # |
2254 # |
2257 # old o - changeset to amend
2255 # old o - changeset to amend
2258 # |
2256 # |
2259 # base o - parent of amending changeset
2257 # base o - parent of amending changeset
2260
2258
2261 # Update extra dict from amended commit (e.g. to preserve graft
2259 # Update extra dict from amended commit (e.g. to preserve graft
2262 # source)
2260 # source)
2263 extra.update(old.extra())
2261 extra.update(old.extra())
2264
2262
2265 # Also update it from the intermediate commit or from the wctx
2263 # Also update it from the intermediate commit or from the wctx
2266 extra.update(ctx.extra())
2264 extra.update(ctx.extra())
2267
2265
2268 if len(old.parents()) > 1:
2266 if len(old.parents()) > 1:
2269 # ctx.files() isn't reliable for merges, so fall back to the
2267 # ctx.files() isn't reliable for merges, so fall back to the
2270 # slower repo.status() method
2268 # slower repo.status() method
2271 files = set([fn for st in repo.status(base, old)[:3]
2269 files = set([fn for st in repo.status(base, old)[:3]
2272 for fn in st])
2270 for fn in st])
2273 else:
2271 else:
2274 files = set(old.files())
2272 files = set(old.files())
2275
2273
2276 # Second, we use either the commit we just did, or if there were no
2274 # Second, we use either the commit we just did, or if there were no
2277 # changes the parent of the working directory as the version of the
2275 # changes the parent of the working directory as the version of the
2278 # files in the final amend commit
2276 # files in the final amend commit
2279 if node:
2277 if node:
2280 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2278 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2281
2279
2282 user = ctx.user()
2280 user = ctx.user()
2283 date = ctx.date()
2281 date = ctx.date()
2284 # Recompute copies (avoid recording a -> b -> a)
2282 # Recompute copies (avoid recording a -> b -> a)
2285 copied = copies.pathcopies(base, ctx)
2283 copied = copies.pathcopies(base, ctx)
2286
2284
2287 # Prune files which were reverted by the updates: if old
2285 # Prune files which were reverted by the updates: if old
2288 # introduced file X and our intermediate commit, node,
2286 # introduced file X and our intermediate commit, node,
2289 # renamed that file, then those two files are the same and
2287 # renamed that file, then those two files are the same and
2290 # we can discard X from our list of files. Likewise if X
2288 # we can discard X from our list of files. Likewise if X
2291 # was deleted, it's no longer relevant
2289 # was deleted, it's no longer relevant
2292 files.update(ctx.files())
2290 files.update(ctx.files())
2293
2291
2294 def samefile(f):
2292 def samefile(f):
2295 if f in ctx.manifest():
2293 if f in ctx.manifest():
2296 a = ctx.filectx(f)
2294 a = ctx.filectx(f)
2297 if f in base.manifest():
2295 if f in base.manifest():
2298 b = base.filectx(f)
2296 b = base.filectx(f)
2299 return (not a.cmp(b)
2297 return (not a.cmp(b)
2300 and a.flags() == b.flags())
2298 and a.flags() == b.flags())
2301 else:
2299 else:
2302 return False
2300 return False
2303 else:
2301 else:
2304 return f not in base.manifest()
2302 return f not in base.manifest()
2305 files = [f for f in files if not samefile(f)]
2303 files = [f for f in files if not samefile(f)]
2306
2304
2307 def filectxfn(repo, ctx_, path):
2305 def filectxfn(repo, ctx_, path):
2308 try:
2306 try:
2309 fctx = ctx[path]
2307 fctx = ctx[path]
2310 flags = fctx.flags()
2308 flags = fctx.flags()
2311 mctx = context.memfilectx(repo,
2309 mctx = context.memfilectx(repo,
2312 fctx.path(), fctx.data(),
2310 fctx.path(), fctx.data(),
2313 islink='l' in flags,
2311 islink='l' in flags,
2314 isexec='x' in flags,
2312 isexec='x' in flags,
2315 copied=copied.get(path))
2313 copied=copied.get(path))
2316 return mctx
2314 return mctx
2317 except KeyError:
2315 except KeyError:
2318 return None
2316 return None
2319 else:
2317 else:
2320 ui.note(_('copying changeset %s to %s\n') % (old, base))
2318 ui.note(_('copying changeset %s to %s\n') % (old, base))
2321
2319
2322 # Use version of files as in the old cset
2320 # Use version of files as in the old cset
2323 def filectxfn(repo, ctx_, path):
2321 def filectxfn(repo, ctx_, path):
2324 try:
2322 try:
2325 return old.filectx(path)
2323 return old.filectx(path)
2326 except KeyError:
2324 except KeyError:
2327 return None
2325 return None
2328
2326
2329 user = opts.get('user') or old.user()
2327 user = opts.get('user') or old.user()
2330 date = opts.get('date') or old.date()
2328 date = opts.get('date') or old.date()
2331 editform = mergeeditform(old, 'commit.amend')
2329 editform = mergeeditform(old, 'commit.amend')
2332 editor = getcommiteditor(editform=editform, **opts)
2330 editor = getcommiteditor(editform=editform, **opts)
2333 if not message:
2331 if not message:
2334 editor = getcommiteditor(edit=True, editform=editform)
2332 editor = getcommiteditor(edit=True, editform=editform)
2335 message = old.description()
2333 message = old.description()
2336
2334
2337 pureextra = extra.copy()
2335 pureextra = extra.copy()
2338 extra['amend_source'] = old.hex()
2336 extra['amend_source'] = old.hex()
2339
2337
2340 new = context.memctx(repo,
2338 new = context.memctx(repo,
2341 parents=[base.node(), old.p2().node()],
2339 parents=[base.node(), old.p2().node()],
2342 text=message,
2340 text=message,
2343 files=files,
2341 files=files,
2344 filectxfn=filectxfn,
2342 filectxfn=filectxfn,
2345 user=user,
2343 user=user,
2346 date=date,
2344 date=date,
2347 extra=extra,
2345 extra=extra,
2348 editor=editor)
2346 editor=editor)
2349
2347
2350 newdesc = changelog.stripdesc(new.description())
2348 newdesc = changelog.stripdesc(new.description())
2351 if ((not node)
2349 if ((not node)
2352 and newdesc == old.description()
2350 and newdesc == old.description()
2353 and user == old.user()
2351 and user == old.user()
2354 and date == old.date()
2352 and date == old.date()
2355 and pureextra == old.extra()):
2353 and pureextra == old.extra()):
2356 # nothing changed. continuing here would create a new node
2354 # nothing changed. continuing here would create a new node
2357 # anyway because of the amend_source noise.
2355 # anyway because of the amend_source noise.
2358 #
2356 #
2359 # This not what we expect from amend.
2357 # This not what we expect from amend.
2360 return old.node()
2358 return old.node()
2361
2359
2362 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2360 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2363 try:
2361 try:
2364 if opts.get('secret'):
2362 if opts.get('secret'):
2365 commitphase = 'secret'
2363 commitphase = 'secret'
2366 else:
2364 else:
2367 commitphase = old.phase()
2365 commitphase = old.phase()
2368 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2366 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2369 newid = repo.commitctx(new)
2367 newid = repo.commitctx(new)
2370 finally:
2368 finally:
2371 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2369 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2372 if newid != old.node():
2370 if newid != old.node():
2373 # Reroute the working copy parent to the new changeset
2371 # Reroute the working copy parent to the new changeset
2374 repo.setparents(newid, nullid)
2372 repo.setparents(newid, nullid)
2375
2373
2376 # Move bookmarks from old parent to amend commit
2374 # Move bookmarks from old parent to amend commit
2377 bms = repo.nodebookmarks(old.node())
2375 bms = repo.nodebookmarks(old.node())
2378 if bms:
2376 if bms:
2379 marks = repo._bookmarks
2377 marks = repo._bookmarks
2380 for bm in bms:
2378 for bm in bms:
2381 marks[bm] = newid
2379 marks[bm] = newid
2382 marks.write()
2380 marks.write()
2383 #commit the whole amend process
2381 #commit the whole amend process
2384 createmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
2382 createmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
2385 if createmarkers and newid != old.node():
2383 if createmarkers and newid != old.node():
2386 # mark the new changeset as successor of the rewritten one
2384 # mark the new changeset as successor of the rewritten one
2387 new = repo[newid]
2385 new = repo[newid]
2388 obs = [(old, (new,))]
2386 obs = [(old, (new,))]
2389 if node:
2387 if node:
2390 obs.append((ctx, ()))
2388 obs.append((ctx, ()))
2391
2389
2392 obsolete.createmarkers(repo, obs)
2390 obsolete.createmarkers(repo, obs)
2393 tr.close()
2391 tr.close()
2394 finally:
2392 finally:
2395 tr.release()
2393 tr.release()
2396 if not createmarkers and newid != old.node():
2394 if not createmarkers and newid != old.node():
2397 # Strip the intermediate commit (if there was one) and the amended
2395 # Strip the intermediate commit (if there was one) and the amended
2398 # commit
2396 # commit
2399 if node:
2397 if node:
2400 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2398 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2401 ui.note(_('stripping amended changeset %s\n') % old)
2399 ui.note(_('stripping amended changeset %s\n') % old)
2402 repair.strip(ui, repo, old.node(), topic='amend-backup')
2400 repair.strip(ui, repo, old.node(), topic='amend-backup')
2403 finally:
2401 finally:
2404 if newid is None:
2402 if newid is None:
2405 repo.dirstate.invalidate()
2403 repo.dirstate.invalidate()
2406 lockmod.release(lock, wlock)
2404 lockmod.release(lock, wlock)
2407 return newid
2405 return newid
2408
2406
2409 def commiteditor(repo, ctx, subs, editform=''):
2407 def commiteditor(repo, ctx, subs, editform=''):
2410 if ctx.description():
2408 if ctx.description():
2411 return ctx.description()
2409 return ctx.description()
2412 return commitforceeditor(repo, ctx, subs, editform=editform)
2410 return commitforceeditor(repo, ctx, subs, editform=editform)
2413
2411
2414 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2412 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2415 editform=''):
2413 editform=''):
2416 if not extramsg:
2414 if not extramsg:
2417 extramsg = _("Leave message empty to abort commit.")
2415 extramsg = _("Leave message empty to abort commit.")
2418
2416
2419 forms = [e for e in editform.split('.') if e]
2417 forms = [e for e in editform.split('.') if e]
2420 forms.insert(0, 'changeset')
2418 forms.insert(0, 'changeset')
2421 while forms:
2419 while forms:
2422 tmpl = repo.ui.config('committemplate', '.'.join(forms))
2420 tmpl = repo.ui.config('committemplate', '.'.join(forms))
2423 if tmpl:
2421 if tmpl:
2424 committext = buildcommittemplate(repo, ctx, subs, extramsg, tmpl)
2422 committext = buildcommittemplate(repo, ctx, subs, extramsg, tmpl)
2425 break
2423 break
2426 forms.pop()
2424 forms.pop()
2427 else:
2425 else:
2428 committext = buildcommittext(repo, ctx, subs, extramsg)
2426 committext = buildcommittext(repo, ctx, subs, extramsg)
2429
2427
2430 # run editor in the repository root
2428 # run editor in the repository root
2431 olddir = os.getcwd()
2429 olddir = os.getcwd()
2432 os.chdir(repo.root)
2430 os.chdir(repo.root)
2433 text = repo.ui.edit(committext, ctx.user(), ctx.extra(), editform=editform)
2431 text = repo.ui.edit(committext, ctx.user(), ctx.extra(), editform=editform)
2434 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2432 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2435 os.chdir(olddir)
2433 os.chdir(olddir)
2436
2434
2437 if finishdesc:
2435 if finishdesc:
2438 text = finishdesc(text)
2436 text = finishdesc(text)
2439 if not text.strip():
2437 if not text.strip():
2440 raise util.Abort(_("empty commit message"))
2438 raise util.Abort(_("empty commit message"))
2441
2439
2442 return text
2440 return text
2443
2441
2444 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2442 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2445 ui = repo.ui
2443 ui = repo.ui
2446 tmpl, mapfile = gettemplate(ui, tmpl, None)
2444 tmpl, mapfile = gettemplate(ui, tmpl, None)
2447
2445
2448 try:
2446 try:
2449 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2447 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2450 except SyntaxError, inst:
2448 except SyntaxError, inst:
2451 raise util.Abort(inst.args[0])
2449 raise util.Abort(inst.args[0])
2452
2450
2453 for k, v in repo.ui.configitems('committemplate'):
2451 for k, v in repo.ui.configitems('committemplate'):
2454 if k != 'changeset':
2452 if k != 'changeset':
2455 t.t.cache[k] = v
2453 t.t.cache[k] = v
2456
2454
2457 if not extramsg:
2455 if not extramsg:
2458 extramsg = '' # ensure that extramsg is string
2456 extramsg = '' # ensure that extramsg is string
2459
2457
2460 ui.pushbuffer()
2458 ui.pushbuffer()
2461 t.show(ctx, extramsg=extramsg)
2459 t.show(ctx, extramsg=extramsg)
2462 return ui.popbuffer()
2460 return ui.popbuffer()
2463
2461
2464 def buildcommittext(repo, ctx, subs, extramsg):
2462 def buildcommittext(repo, ctx, subs, extramsg):
2465 edittext = []
2463 edittext = []
2466 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2464 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2467 if ctx.description():
2465 if ctx.description():
2468 edittext.append(ctx.description())
2466 edittext.append(ctx.description())
2469 edittext.append("")
2467 edittext.append("")
2470 edittext.append("") # Empty line between message and comments.
2468 edittext.append("") # Empty line between message and comments.
2471 edittext.append(_("HG: Enter commit message."
2469 edittext.append(_("HG: Enter commit message."
2472 " Lines beginning with 'HG:' are removed."))
2470 " Lines beginning with 'HG:' are removed."))
2473 edittext.append("HG: %s" % extramsg)
2471 edittext.append("HG: %s" % extramsg)
2474 edittext.append("HG: --")
2472 edittext.append("HG: --")
2475 edittext.append(_("HG: user: %s") % ctx.user())
2473 edittext.append(_("HG: user: %s") % ctx.user())
2476 if ctx.p2():
2474 if ctx.p2():
2477 edittext.append(_("HG: branch merge"))
2475 edittext.append(_("HG: branch merge"))
2478 if ctx.branch():
2476 if ctx.branch():
2479 edittext.append(_("HG: branch '%s'") % ctx.branch())
2477 edittext.append(_("HG: branch '%s'") % ctx.branch())
2480 if bookmarks.iscurrent(repo):
2478 if bookmarks.iscurrent(repo):
2481 edittext.append(_("HG: bookmark '%s'") % repo._bookmarkcurrent)
2479 edittext.append(_("HG: bookmark '%s'") % repo._bookmarkcurrent)
2482 edittext.extend([_("HG: subrepo %s") % s for s in subs])
2480 edittext.extend([_("HG: subrepo %s") % s for s in subs])
2483 edittext.extend([_("HG: added %s") % f for f in added])
2481 edittext.extend([_("HG: added %s") % f for f in added])
2484 edittext.extend([_("HG: changed %s") % f for f in modified])
2482 edittext.extend([_("HG: changed %s") % f for f in modified])
2485 edittext.extend([_("HG: removed %s") % f for f in removed])
2483 edittext.extend([_("HG: removed %s") % f for f in removed])
2486 if not added and not modified and not removed:
2484 if not added and not modified and not removed:
2487 edittext.append(_("HG: no files changed"))
2485 edittext.append(_("HG: no files changed"))
2488 edittext.append("")
2486 edittext.append("")
2489
2487
2490 return "\n".join(edittext)
2488 return "\n".join(edittext)
2491
2489
2492 def commitstatus(repo, node, branch, bheads=None, opts={}):
2490 def commitstatus(repo, node, branch, bheads=None, opts={}):
2493 ctx = repo[node]
2491 ctx = repo[node]
2494 parents = ctx.parents()
2492 parents = ctx.parents()
2495
2493
2496 if (not opts.get('amend') and bheads and node not in bheads and not
2494 if (not opts.get('amend') and bheads and node not in bheads and not
2497 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2495 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2498 repo.ui.status(_('created new head\n'))
2496 repo.ui.status(_('created new head\n'))
2499 # The message is not printed for initial roots. For the other
2497 # The message is not printed for initial roots. For the other
2500 # changesets, it is printed in the following situations:
2498 # changesets, it is printed in the following situations:
2501 #
2499 #
2502 # Par column: for the 2 parents with ...
2500 # Par column: for the 2 parents with ...
2503 # N: null or no parent
2501 # N: null or no parent
2504 # B: parent is on another named branch
2502 # B: parent is on another named branch
2505 # C: parent is a regular non head changeset
2503 # C: parent is a regular non head changeset
2506 # H: parent was a branch head of the current branch
2504 # H: parent was a branch head of the current branch
2507 # Msg column: whether we print "created new head" message
2505 # Msg column: whether we print "created new head" message
2508 # In the following, it is assumed that there already exists some
2506 # In the following, it is assumed that there already exists some
2509 # initial branch heads of the current branch, otherwise nothing is
2507 # initial branch heads of the current branch, otherwise nothing is
2510 # printed anyway.
2508 # printed anyway.
2511 #
2509 #
2512 # Par Msg Comment
2510 # Par Msg Comment
2513 # N N y additional topo root
2511 # N N y additional topo root
2514 #
2512 #
2515 # B N y additional branch root
2513 # B N y additional branch root
2516 # C N y additional topo head
2514 # C N y additional topo head
2517 # H N n usual case
2515 # H N n usual case
2518 #
2516 #
2519 # B B y weird additional branch root
2517 # B B y weird additional branch root
2520 # C B y branch merge
2518 # C B y branch merge
2521 # H B n merge with named branch
2519 # H B n merge with named branch
2522 #
2520 #
2523 # C C y additional head from merge
2521 # C C y additional head from merge
2524 # C H n merge with a head
2522 # C H n merge with a head
2525 #
2523 #
2526 # H H n head merge: head count decreases
2524 # H H n head merge: head count decreases
2527
2525
2528 if not opts.get('close_branch'):
2526 if not opts.get('close_branch'):
2529 for r in parents:
2527 for r in parents:
2530 if r.closesbranch() and r.branch() == branch:
2528 if r.closesbranch() and r.branch() == branch:
2531 repo.ui.status(_('reopening closed branch head %d\n') % r)
2529 repo.ui.status(_('reopening closed branch head %d\n') % r)
2532
2530
2533 if repo.ui.debugflag:
2531 if repo.ui.debugflag:
2534 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2532 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2535 elif repo.ui.verbose:
2533 elif repo.ui.verbose:
2536 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2534 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2537
2535
2538 def revert(ui, repo, ctx, parents, *pats, **opts):
2536 def revert(ui, repo, ctx, parents, *pats, **opts):
2539 parent, p2 = parents
2537 parent, p2 = parents
2540 node = ctx.node()
2538 node = ctx.node()
2541
2539
2542 mf = ctx.manifest()
2540 mf = ctx.manifest()
2543 if node == p2:
2541 if node == p2:
2544 parent = p2
2542 parent = p2
2545 if node == parent:
2543 if node == parent:
2546 pmf = mf
2544 pmf = mf
2547 else:
2545 else:
2548 pmf = None
2546 pmf = None
2549
2547
2550 # need all matching names in dirstate and manifest of target rev,
2548 # need all matching names in dirstate and manifest of target rev,
2551 # so have to walk both. do not print errors if files exist in one
2549 # so have to walk both. do not print errors if files exist in one
2552 # but not other.
2550 # but not other.
2553
2551
2554 # `names` is a mapping for all elements in working copy and target revision
2552 # `names` is a mapping for all elements in working copy and target revision
2555 # The mapping is in the form:
2553 # The mapping is in the form:
2556 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2554 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2557 names = {}
2555 names = {}
2558
2556
2559 wlock = repo.wlock()
2557 wlock = repo.wlock()
2560 try:
2558 try:
2561 ## filling of the `names` mapping
2559 ## filling of the `names` mapping
2562 # walk dirstate to fill `names`
2560 # walk dirstate to fill `names`
2563
2561
2564 m = scmutil.match(repo[None], pats, opts)
2562 m = scmutil.match(repo[None], pats, opts)
2565 if not m.always() or node != parent:
2563 if not m.always() or node != parent:
2566 m.bad = lambda x, y: False
2564 m.bad = lambda x, y: False
2567 for abs in repo.walk(m):
2565 for abs in repo.walk(m):
2568 names[abs] = m.rel(abs), m.exact(abs)
2566 names[abs] = m.rel(abs), m.exact(abs)
2569
2567
2570 # walk target manifest to fill `names`
2568 # walk target manifest to fill `names`
2571
2569
2572 def badfn(path, msg):
2570 def badfn(path, msg):
2573 if path in names:
2571 if path in names:
2574 return
2572 return
2575 if path in ctx.substate:
2573 if path in ctx.substate:
2576 return
2574 return
2577 path_ = path + '/'
2575 path_ = path + '/'
2578 for f in names:
2576 for f in names:
2579 if f.startswith(path_):
2577 if f.startswith(path_):
2580 return
2578 return
2581 ui.warn("%s: %s\n" % (m.rel(path), msg))
2579 ui.warn("%s: %s\n" % (m.rel(path), msg))
2582
2580
2583 m = scmutil.match(ctx, pats, opts)
2581 m = scmutil.match(ctx, pats, opts)
2584 m.bad = badfn
2582 m.bad = badfn
2585 for abs in ctx.walk(m):
2583 for abs in ctx.walk(m):
2586 if abs not in names:
2584 if abs not in names:
2587 names[abs] = m.rel(abs), m.exact(abs)
2585 names[abs] = m.rel(abs), m.exact(abs)
2588
2586
2589 # Find status of all file in `names`.
2587 # Find status of all file in `names`.
2590 m = scmutil.matchfiles(repo, names)
2588 m = scmutil.matchfiles(repo, names)
2591
2589
2592 changes = repo.status(node1=node, match=m,
2590 changes = repo.status(node1=node, match=m,
2593 unknown=True, ignored=True, clean=True)
2591 unknown=True, ignored=True, clean=True)
2594 else:
2592 else:
2595 changes = repo.status(match=m)
2593 changes = repo.status(match=m)
2596 for kind in changes:
2594 for kind in changes:
2597 for abs in kind:
2595 for abs in kind:
2598 names[abs] = m.rel(abs), m.exact(abs)
2596 names[abs] = m.rel(abs), m.exact(abs)
2599
2597
2600 m = scmutil.matchfiles(repo, names)
2598 m = scmutil.matchfiles(repo, names)
2601
2599
2602 modified = set(changes.modified)
2600 modified = set(changes.modified)
2603 added = set(changes.added)
2601 added = set(changes.added)
2604 removed = set(changes.removed)
2602 removed = set(changes.removed)
2605 _deleted = set(changes.deleted)
2603 _deleted = set(changes.deleted)
2606 unknown = set(changes.unknown)
2604 unknown = set(changes.unknown)
2607 unknown.update(changes.ignored)
2605 unknown.update(changes.ignored)
2608 clean = set(changes.clean)
2606 clean = set(changes.clean)
2609 modadded = set()
2607 modadded = set()
2610
2608
2611 # split between files known in target manifest and the others
2609 # split between files known in target manifest and the others
2612 smf = set(mf)
2610 smf = set(mf)
2613
2611
2614 # determine the exact nature of the deleted changesets
2612 # determine the exact nature of the deleted changesets
2615 deladded = _deleted - smf
2613 deladded = _deleted - smf
2616 deleted = _deleted - deladded
2614 deleted = _deleted - deladded
2617
2615
2618 # We need to account for the state of file in the dirstate.
2616 # We need to account for the state of file in the dirstate.
2619 #
2617 #
2620 # Even, when we revert against something else than parent. This will
2618 # Even, when we revert against something else than parent. This will
2621 # slightly alter the behavior of revert (doing back up or not, delete
2619 # slightly alter the behavior of revert (doing back up or not, delete
2622 # or just forget etc).
2620 # or just forget etc).
2623 if parent == node:
2621 if parent == node:
2624 dsmodified = modified
2622 dsmodified = modified
2625 dsadded = added
2623 dsadded = added
2626 dsremoved = removed
2624 dsremoved = removed
2627 # store all local modifications, useful later for rename detection
2625 # store all local modifications, useful later for rename detection
2628 localchanges = dsmodified | dsadded
2626 localchanges = dsmodified | dsadded
2629 modified, added, removed = set(), set(), set()
2627 modified, added, removed = set(), set(), set()
2630 else:
2628 else:
2631 changes = repo.status(node1=parent, match=m)
2629 changes = repo.status(node1=parent, match=m)
2632 dsmodified = set(changes.modified)
2630 dsmodified = set(changes.modified)
2633 dsadded = set(changes.added)
2631 dsadded = set(changes.added)
2634 dsremoved = set(changes.removed)
2632 dsremoved = set(changes.removed)
2635 # store all local modifications, useful later for rename detection
2633 # store all local modifications, useful later for rename detection
2636 localchanges = dsmodified | dsadded
2634 localchanges = dsmodified | dsadded
2637
2635
2638 # only take into account for removes between wc and target
2636 # only take into account for removes between wc and target
2639 clean |= dsremoved - removed
2637 clean |= dsremoved - removed
2640 dsremoved &= removed
2638 dsremoved &= removed
2641 # distinct between dirstate remove and other
2639 # distinct between dirstate remove and other
2642 removed -= dsremoved
2640 removed -= dsremoved
2643
2641
2644 modadded = added & dsmodified
2642 modadded = added & dsmodified
2645 added -= modadded
2643 added -= modadded
2646
2644
2647 # tell newly modified apart.
2645 # tell newly modified apart.
2648 dsmodified &= modified
2646 dsmodified &= modified
2649 dsmodified |= modified & dsadded # dirstate added may needs backup
2647 dsmodified |= modified & dsadded # dirstate added may needs backup
2650 modified -= dsmodified
2648 modified -= dsmodified
2651
2649
2652 # We need to wait for some post-processing to update this set
2650 # We need to wait for some post-processing to update this set
2653 # before making the distinction. The dirstate will be used for
2651 # before making the distinction. The dirstate will be used for
2654 # that purpose.
2652 # that purpose.
2655 dsadded = added
2653 dsadded = added
2656
2654
2657 # in case of merge, files that are actually added can be reported as
2655 # in case of merge, files that are actually added can be reported as
2658 # modified, we need to post process the result
2656 # modified, we need to post process the result
2659 if p2 != nullid:
2657 if p2 != nullid:
2660 if pmf is None:
2658 if pmf is None:
2661 # only need parent manifest in the merge case,
2659 # only need parent manifest in the merge case,
2662 # so do not read by default
2660 # so do not read by default
2663 pmf = repo[parent].manifest()
2661 pmf = repo[parent].manifest()
2664 mergeadd = dsmodified - set(pmf)
2662 mergeadd = dsmodified - set(pmf)
2665 dsadded |= mergeadd
2663 dsadded |= mergeadd
2666 dsmodified -= mergeadd
2664 dsmodified -= mergeadd
2667
2665
2668 # if f is a rename, update `names` to also revert the source
2666 # if f is a rename, update `names` to also revert the source
2669 cwd = repo.getcwd()
2667 cwd = repo.getcwd()
2670 for f in localchanges:
2668 for f in localchanges:
2671 src = repo.dirstate.copied(f)
2669 src = repo.dirstate.copied(f)
2672 # XXX should we check for rename down to target node?
2670 # XXX should we check for rename down to target node?
2673 if src and src not in names and repo.dirstate[src] == 'r':
2671 if src and src not in names and repo.dirstate[src] == 'r':
2674 dsremoved.add(src)
2672 dsremoved.add(src)
2675 names[src] = (repo.pathto(src, cwd), True)
2673 names[src] = (repo.pathto(src, cwd), True)
2676
2674
2677 # distinguish between file to forget and the other
2675 # distinguish between file to forget and the other
2678 added = set()
2676 added = set()
2679 for abs in dsadded:
2677 for abs in dsadded:
2680 if repo.dirstate[abs] != 'a':
2678 if repo.dirstate[abs] != 'a':
2681 added.add(abs)
2679 added.add(abs)
2682 dsadded -= added
2680 dsadded -= added
2683
2681
2684 for abs in deladded:
2682 for abs in deladded:
2685 if repo.dirstate[abs] == 'a':
2683 if repo.dirstate[abs] == 'a':
2686 dsadded.add(abs)
2684 dsadded.add(abs)
2687 deladded -= dsadded
2685 deladded -= dsadded
2688
2686
2689 # For files marked as removed, we check if an unknown file is present at
2687 # For files marked as removed, we check if an unknown file is present at
2690 # the same path. If a such file exists it may need to be backed up.
2688 # the same path. If a such file exists it may need to be backed up.
2691 # Making the distinction at this stage helps have simpler backup
2689 # Making the distinction at this stage helps have simpler backup
2692 # logic.
2690 # logic.
2693 removunk = set()
2691 removunk = set()
2694 for abs in removed:
2692 for abs in removed:
2695 target = repo.wjoin(abs)
2693 target = repo.wjoin(abs)
2696 if os.path.lexists(target):
2694 if os.path.lexists(target):
2697 removunk.add(abs)
2695 removunk.add(abs)
2698 removed -= removunk
2696 removed -= removunk
2699
2697
2700 dsremovunk = set()
2698 dsremovunk = set()
2701 for abs in dsremoved:
2699 for abs in dsremoved:
2702 target = repo.wjoin(abs)
2700 target = repo.wjoin(abs)
2703 if os.path.lexists(target):
2701 if os.path.lexists(target):
2704 dsremovunk.add(abs)
2702 dsremovunk.add(abs)
2705 dsremoved -= dsremovunk
2703 dsremoved -= dsremovunk
2706
2704
2707 # action to be actually performed by revert
2705 # action to be actually performed by revert
2708 # (<list of file>, message>) tuple
2706 # (<list of file>, message>) tuple
2709 actions = {'revert': ([], _('reverting %s\n')),
2707 actions = {'revert': ([], _('reverting %s\n')),
2710 'add': ([], _('adding %s\n')),
2708 'add': ([], _('adding %s\n')),
2711 'remove': ([], _('removing %s\n')),
2709 'remove': ([], _('removing %s\n')),
2712 'drop': ([], _('removing %s\n')),
2710 'drop': ([], _('removing %s\n')),
2713 'forget': ([], _('forgetting %s\n')),
2711 'forget': ([], _('forgetting %s\n')),
2714 'undelete': ([], _('undeleting %s\n')),
2712 'undelete': ([], _('undeleting %s\n')),
2715 'noop': (None, _('no changes needed to %s\n')),
2713 'noop': (None, _('no changes needed to %s\n')),
2716 'unknown': (None, _('file not managed: %s\n')),
2714 'unknown': (None, _('file not managed: %s\n')),
2717 }
2715 }
2718
2716
2719 # "constant" that convey the backup strategy.
2717 # "constant" that convey the backup strategy.
2720 # All set to `discard` if `no-backup` is set do avoid checking
2718 # All set to `discard` if `no-backup` is set do avoid checking
2721 # no_backup lower in the code.
2719 # no_backup lower in the code.
2722 # These values are ordered for comparison purposes
2720 # These values are ordered for comparison purposes
2723 backup = 2 # unconditionally do backup
2721 backup = 2 # unconditionally do backup
2724 check = 1 # check if the existing file differs from target
2722 check = 1 # check if the existing file differs from target
2725 discard = 0 # never do backup
2723 discard = 0 # never do backup
2726 if opts.get('no_backup'):
2724 if opts.get('no_backup'):
2727 backup = check = discard
2725 backup = check = discard
2728
2726
2729 backupanddel = actions['remove']
2727 backupanddel = actions['remove']
2730 if not opts.get('no_backup'):
2728 if not opts.get('no_backup'):
2731 backupanddel = actions['drop']
2729 backupanddel = actions['drop']
2732
2730
2733 disptable = (
2731 disptable = (
2734 # dispatch table:
2732 # dispatch table:
2735 # file state
2733 # file state
2736 # action
2734 # action
2737 # make backup
2735 # make backup
2738
2736
2739 ## Sets that results that will change file on disk
2737 ## Sets that results that will change file on disk
2740 # Modified compared to target, no local change
2738 # Modified compared to target, no local change
2741 (modified, actions['revert'], discard),
2739 (modified, actions['revert'], discard),
2742 # Modified compared to target, but local file is deleted
2740 # Modified compared to target, but local file is deleted
2743 (deleted, actions['revert'], discard),
2741 (deleted, actions['revert'], discard),
2744 # Modified compared to target, local change
2742 # Modified compared to target, local change
2745 (dsmodified, actions['revert'], backup),
2743 (dsmodified, actions['revert'], backup),
2746 # Added since target
2744 # Added since target
2747 (added, actions['remove'], discard),
2745 (added, actions['remove'], discard),
2748 # Added in working directory
2746 # Added in working directory
2749 (dsadded, actions['forget'], discard),
2747 (dsadded, actions['forget'], discard),
2750 # Added since target, have local modification
2748 # Added since target, have local modification
2751 (modadded, backupanddel, backup),
2749 (modadded, backupanddel, backup),
2752 # Added since target but file is missing in working directory
2750 # Added since target but file is missing in working directory
2753 (deladded, actions['drop'], discard),
2751 (deladded, actions['drop'], discard),
2754 # Removed since target, before working copy parent
2752 # Removed since target, before working copy parent
2755 (removed, actions['add'], discard),
2753 (removed, actions['add'], discard),
2756 # Same as `removed` but an unknown file exists at the same path
2754 # Same as `removed` but an unknown file exists at the same path
2757 (removunk, actions['add'], check),
2755 (removunk, actions['add'], check),
2758 # Removed since targe, marked as such in working copy parent
2756 # Removed since targe, marked as such in working copy parent
2759 (dsremoved, actions['undelete'], discard),
2757 (dsremoved, actions['undelete'], discard),
2760 # Same as `dsremoved` but an unknown file exists at the same path
2758 # Same as `dsremoved` but an unknown file exists at the same path
2761 (dsremovunk, actions['undelete'], check),
2759 (dsremovunk, actions['undelete'], check),
2762 ## the following sets does not result in any file changes
2760 ## the following sets does not result in any file changes
2763 # File with no modification
2761 # File with no modification
2764 (clean, actions['noop'], discard),
2762 (clean, actions['noop'], discard),
2765 # Existing file, not tracked anywhere
2763 # Existing file, not tracked anywhere
2766 (unknown, actions['unknown'], discard),
2764 (unknown, actions['unknown'], discard),
2767 )
2765 )
2768
2766
2769 needdata = ('revert', 'add', 'undelete')
2767 needdata = ('revert', 'add', 'undelete')
2770 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
2768 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
2771
2769
2772 wctx = repo[None]
2770 wctx = repo[None]
2773 for abs, (rel, exact) in sorted(names.items()):
2771 for abs, (rel, exact) in sorted(names.items()):
2774 # target file to be touch on disk (relative to cwd)
2772 # target file to be touch on disk (relative to cwd)
2775 target = repo.wjoin(abs)
2773 target = repo.wjoin(abs)
2776 # search the entry in the dispatch table.
2774 # search the entry in the dispatch table.
2777 # if the file is in any of these sets, it was touched in the working
2775 # if the file is in any of these sets, it was touched in the working
2778 # directory parent and we are sure it needs to be reverted.
2776 # directory parent and we are sure it needs to be reverted.
2779 for table, (xlist, msg), dobackup in disptable:
2777 for table, (xlist, msg), dobackup in disptable:
2780 if abs not in table:
2778 if abs not in table:
2781 continue
2779 continue
2782 if xlist is not None:
2780 if xlist is not None:
2783 xlist.append(abs)
2781 xlist.append(abs)
2784 if dobackup and (backup <= dobackup
2782 if dobackup and (backup <= dobackup
2785 or wctx[abs].cmp(ctx[abs])):
2783 or wctx[abs].cmp(ctx[abs])):
2786 bakname = "%s.orig" % rel
2784 bakname = "%s.orig" % rel
2787 ui.note(_('saving current version of %s as %s\n') %
2785 ui.note(_('saving current version of %s as %s\n') %
2788 (rel, bakname))
2786 (rel, bakname))
2789 if not opts.get('dry_run'):
2787 if not opts.get('dry_run'):
2790 util.rename(target, bakname)
2788 util.rename(target, bakname)
2791 if ui.verbose or not exact:
2789 if ui.verbose or not exact:
2792 if not isinstance(msg, basestring):
2790 if not isinstance(msg, basestring):
2793 msg = msg(abs)
2791 msg = msg(abs)
2794 ui.status(msg % rel)
2792 ui.status(msg % rel)
2795 elif exact:
2793 elif exact:
2796 ui.warn(msg % rel)
2794 ui.warn(msg % rel)
2797 break
2795 break
2798
2796
2799
2797
2800 if not opts.get('dry_run'):
2798 if not opts.get('dry_run'):
2801 _performrevert(repo, parents, ctx, actions)
2799 _performrevert(repo, parents, ctx, actions)
2802
2800
2803 # get the list of subrepos that must be reverted
2801 # get the list of subrepos that must be reverted
2804 subrepomatch = scmutil.match(ctx, pats, opts)
2802 subrepomatch = scmutil.match(ctx, pats, opts)
2805 targetsubs = sorted(s for s in ctx.substate if subrepomatch(s))
2803 targetsubs = sorted(s for s in ctx.substate if subrepomatch(s))
2806
2804
2807 if targetsubs:
2805 if targetsubs:
2808 # Revert the subrepos on the revert list
2806 # Revert the subrepos on the revert list
2809 for sub in targetsubs:
2807 for sub in targetsubs:
2810 ctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
2808 ctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
2811 finally:
2809 finally:
2812 wlock.release()
2810 wlock.release()
2813
2811
2814 def _revertprefetch(repo, ctx, *files):
2812 def _revertprefetch(repo, ctx, *files):
2815 """Let extension changing the storage layer prefetch content"""
2813 """Let extension changing the storage layer prefetch content"""
2816 pass
2814 pass
2817
2815
2818 def _performrevert(repo, parents, ctx, actions):
2816 def _performrevert(repo, parents, ctx, actions):
2819 """function that actually perform all the actions computed for revert
2817 """function that actually perform all the actions computed for revert
2820
2818
2821 This is an independent function to let extension to plug in and react to
2819 This is an independent function to let extension to plug in and react to
2822 the imminent revert.
2820 the imminent revert.
2823
2821
2824 Make sure you have the working directory locked when calling this function.
2822 Make sure you have the working directory locked when calling this function.
2825 """
2823 """
2826 parent, p2 = parents
2824 parent, p2 = parents
2827 node = ctx.node()
2825 node = ctx.node()
2828 def checkout(f):
2826 def checkout(f):
2829 fc = ctx[f]
2827 fc = ctx[f]
2830 repo.wwrite(f, fc.data(), fc.flags())
2828 repo.wwrite(f, fc.data(), fc.flags())
2831
2829
2832 audit_path = pathutil.pathauditor(repo.root)
2830 audit_path = pathutil.pathauditor(repo.root)
2833 for f in actions['forget'][0]:
2831 for f in actions['forget'][0]:
2834 repo.dirstate.drop(f)
2832 repo.dirstate.drop(f)
2835 for f in actions['remove'][0]:
2833 for f in actions['remove'][0]:
2836 audit_path(f)
2834 audit_path(f)
2837 util.unlinkpath(repo.wjoin(f))
2835 util.unlinkpath(repo.wjoin(f))
2838 repo.dirstate.remove(f)
2836 repo.dirstate.remove(f)
2839 for f in actions['drop'][0]:
2837 for f in actions['drop'][0]:
2840 audit_path(f)
2838 audit_path(f)
2841 repo.dirstate.remove(f)
2839 repo.dirstate.remove(f)
2842
2840
2843 normal = None
2841 normal = None
2844 if node == parent:
2842 if node == parent:
2845 # We're reverting to our parent. If possible, we'd like status
2843 # We're reverting to our parent. If possible, we'd like status
2846 # to report the file as clean. We have to use normallookup for
2844 # to report the file as clean. We have to use normallookup for
2847 # merges to avoid losing information about merged/dirty files.
2845 # merges to avoid losing information about merged/dirty files.
2848 if p2 != nullid:
2846 if p2 != nullid:
2849 normal = repo.dirstate.normallookup
2847 normal = repo.dirstate.normallookup
2850 else:
2848 else:
2851 normal = repo.dirstate.normal
2849 normal = repo.dirstate.normal
2852 for f in actions['revert'][0]:
2850 for f in actions['revert'][0]:
2853 checkout(f)
2851 checkout(f)
2854 if normal:
2852 if normal:
2855 normal(f)
2853 normal(f)
2856
2854
2857 for f in actions['add'][0]:
2855 for f in actions['add'][0]:
2858 checkout(f)
2856 checkout(f)
2859 repo.dirstate.add(f)
2857 repo.dirstate.add(f)
2860
2858
2861 normal = repo.dirstate.normallookup
2859 normal = repo.dirstate.normallookup
2862 if node == parent and p2 == nullid:
2860 if node == parent and p2 == nullid:
2863 normal = repo.dirstate.normal
2861 normal = repo.dirstate.normal
2864 for f in actions['undelete'][0]:
2862 for f in actions['undelete'][0]:
2865 checkout(f)
2863 checkout(f)
2866 normal(f)
2864 normal(f)
2867
2865
2868 copied = copies.pathcopies(repo[parent], ctx)
2866 copied = copies.pathcopies(repo[parent], ctx)
2869
2867
2870 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
2868 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
2871 if f in copied:
2869 if f in copied:
2872 repo.dirstate.copy(copied[f], f)
2870 repo.dirstate.copy(copied[f], f)
2873
2871
2874 def command(table):
2872 def command(table):
2875 """Returns a function object to be used as a decorator for making commands.
2873 """Returns a function object to be used as a decorator for making commands.
2876
2874
2877 This function receives a command table as its argument. The table should
2875 This function receives a command table as its argument. The table should
2878 be a dict.
2876 be a dict.
2879
2877
2880 The returned function can be used as a decorator for adding commands
2878 The returned function can be used as a decorator for adding commands
2881 to that command table. This function accepts multiple arguments to define
2879 to that command table. This function accepts multiple arguments to define
2882 a command.
2880 a command.
2883
2881
2884 The first argument is the command name.
2882 The first argument is the command name.
2885
2883
2886 The options argument is an iterable of tuples defining command arguments.
2884 The options argument is an iterable of tuples defining command arguments.
2887 See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
2885 See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
2888
2886
2889 The synopsis argument defines a short, one line summary of how to use the
2887 The synopsis argument defines a short, one line summary of how to use the
2890 command. This shows up in the help output.
2888 command. This shows up in the help output.
2891
2889
2892 The norepo argument defines whether the command does not require a
2890 The norepo argument defines whether the command does not require a
2893 local repository. Most commands operate against a repository, thus the
2891 local repository. Most commands operate against a repository, thus the
2894 default is False.
2892 default is False.
2895
2893
2896 The optionalrepo argument defines whether the command optionally requires
2894 The optionalrepo argument defines whether the command optionally requires
2897 a local repository.
2895 a local repository.
2898
2896
2899 The inferrepo argument defines whether to try to find a repository from the
2897 The inferrepo argument defines whether to try to find a repository from the
2900 command line arguments. If True, arguments will be examined for potential
2898 command line arguments. If True, arguments will be examined for potential
2901 repository locations. See ``findrepo()``. If a repository is found, it
2899 repository locations. See ``findrepo()``. If a repository is found, it
2902 will be used.
2900 will be used.
2903 """
2901 """
2904 def cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False,
2902 def cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False,
2905 inferrepo=False):
2903 inferrepo=False):
2906 def decorator(func):
2904 def decorator(func):
2907 if synopsis:
2905 if synopsis:
2908 table[name] = func, list(options), synopsis
2906 table[name] = func, list(options), synopsis
2909 else:
2907 else:
2910 table[name] = func, list(options)
2908 table[name] = func, list(options)
2911
2909
2912 if norepo:
2910 if norepo:
2913 # Avoid import cycle.
2911 # Avoid import cycle.
2914 import commands
2912 import commands
2915 commands.norepo += ' %s' % ' '.join(parsealiases(name))
2913 commands.norepo += ' %s' % ' '.join(parsealiases(name))
2916
2914
2917 if optionalrepo:
2915 if optionalrepo:
2918 import commands
2916 import commands
2919 commands.optionalrepo += ' %s' % ' '.join(parsealiases(name))
2917 commands.optionalrepo += ' %s' % ' '.join(parsealiases(name))
2920
2918
2921 if inferrepo:
2919 if inferrepo:
2922 import commands
2920 import commands
2923 commands.inferrepo += ' %s' % ' '.join(parsealiases(name))
2921 commands.inferrepo += ' %s' % ' '.join(parsealiases(name))
2924
2922
2925 return func
2923 return func
2926 return decorator
2924 return decorator
2927
2925
2928 return cmd
2926 return cmd
2929
2927
2930 # a list of (ui, repo, otherpeer, opts, missing) functions called by
2928 # a list of (ui, repo, otherpeer, opts, missing) functions called by
2931 # commands.outgoing. "missing" is "missing" of the result of
2929 # commands.outgoing. "missing" is "missing" of the result of
2932 # "findcommonoutgoing()"
2930 # "findcommonoutgoing()"
2933 outgoinghooks = util.hooks()
2931 outgoinghooks = util.hooks()
2934
2932
2935 # a list of (ui, repo) functions called by commands.summary
2933 # a list of (ui, repo) functions called by commands.summary
2936 summaryhooks = util.hooks()
2934 summaryhooks = util.hooks()
2937
2935
2938 # a list of (ui, repo, opts, changes) functions called by commands.summary.
2936 # a list of (ui, repo, opts, changes) functions called by commands.summary.
2939 #
2937 #
2940 # functions should return tuple of booleans below, if 'changes' is None:
2938 # functions should return tuple of booleans below, if 'changes' is None:
2941 # (whether-incomings-are-needed, whether-outgoings-are-needed)
2939 # (whether-incomings-are-needed, whether-outgoings-are-needed)
2942 #
2940 #
2943 # otherwise, 'changes' is a tuple of tuples below:
2941 # otherwise, 'changes' is a tuple of tuples below:
2944 # - (sourceurl, sourcebranch, sourcepeer, incoming)
2942 # - (sourceurl, sourcebranch, sourcepeer, incoming)
2945 # - (desturl, destbranch, destpeer, outgoing)
2943 # - (desturl, destbranch, destpeer, outgoing)
2946 summaryremotehooks = util.hooks()
2944 summaryremotehooks = util.hooks()
2947
2945
2948 # A list of state files kept by multistep operations like graft.
2946 # A list of state files kept by multistep operations like graft.
2949 # Since graft cannot be aborted, it is considered 'clearable' by update.
2947 # Since graft cannot be aborted, it is considered 'clearable' by update.
2950 # note: bisect is intentionally excluded
2948 # note: bisect is intentionally excluded
2951 # (state file, clearable, allowcommit, error, hint)
2949 # (state file, clearable, allowcommit, error, hint)
2952 unfinishedstates = [
2950 unfinishedstates = [
2953 ('graftstate', True, False, _('graft in progress'),
2951 ('graftstate', True, False, _('graft in progress'),
2954 _("use 'hg graft --continue' or 'hg update' to abort")),
2952 _("use 'hg graft --continue' or 'hg update' to abort")),
2955 ('updatestate', True, False, _('last update was interrupted'),
2953 ('updatestate', True, False, _('last update was interrupted'),
2956 _("use 'hg update' to get a consistent checkout"))
2954 _("use 'hg update' to get a consistent checkout"))
2957 ]
2955 ]
2958
2956
2959 def checkunfinished(repo, commit=False):
2957 def checkunfinished(repo, commit=False):
2960 '''Look for an unfinished multistep operation, like graft, and abort
2958 '''Look for an unfinished multistep operation, like graft, and abort
2961 if found. It's probably good to check this right before
2959 if found. It's probably good to check this right before
2962 bailifchanged().
2960 bailifchanged().
2963 '''
2961 '''
2964 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2962 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2965 if commit and allowcommit:
2963 if commit and allowcommit:
2966 continue
2964 continue
2967 if repo.vfs.exists(f):
2965 if repo.vfs.exists(f):
2968 raise util.Abort(msg, hint=hint)
2966 raise util.Abort(msg, hint=hint)
2969
2967
2970 def clearunfinished(repo):
2968 def clearunfinished(repo):
2971 '''Check for unfinished operations (as above), and clear the ones
2969 '''Check for unfinished operations (as above), and clear the ones
2972 that are clearable.
2970 that are clearable.
2973 '''
2971 '''
2974 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2972 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2975 if not clearable and repo.vfs.exists(f):
2973 if not clearable and repo.vfs.exists(f):
2976 raise util.Abort(msg, hint=hint)
2974 raise util.Abort(msg, hint=hint)
2977 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2975 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2978 if clearable and repo.vfs.exists(f):
2976 if clearable and repo.vfs.exists(f):
2979 util.unlink(repo.join(f))
2977 util.unlink(repo.join(f))
@@ -1,6282 +1,6281 b''
1 # commands.py - command processing for mercurial
1 # commands.py - command processing for 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, bin, nullid, nullrev, short
8 from node import hex, bin, nullid, nullrev, short
9 from lock import release
9 from lock import release
10 from i18n import _
10 from i18n import _
11 import os, re, difflib, time, tempfile, errno, shlex
11 import os, re, difflib, time, tempfile, errno, shlex
12 import sys, socket
12 import sys, socket
13 import hg, scmutil, util, revlog, copies, error, bookmarks
13 import hg, scmutil, util, revlog, copies, error, bookmarks
14 import patch, help, encoding, templatekw, discovery
14 import patch, help, encoding, templatekw, discovery
15 import archival, changegroup, cmdutil, hbisect
15 import archival, changegroup, cmdutil, hbisect
16 import sshserver, hgweb, commandserver
16 import sshserver, hgweb, commandserver
17 import extensions
17 import extensions
18 from hgweb import server as hgweb_server
18 from hgweb import server as hgweb_server
19 import merge as mergemod
19 import merge as mergemod
20 import minirst, revset, fileset
20 import minirst, revset, fileset
21 import dagparser, context, simplemerge, graphmod, copies
21 import dagparser, context, simplemerge, graphmod, copies
22 import random
22 import random
23 import setdiscovery, treediscovery, dagutil, pvec, localrepo
23 import setdiscovery, treediscovery, dagutil, pvec, localrepo
24 import phases, obsolete, exchange
24 import phases, obsolete, exchange
25 import ui as uimod
25 import ui as uimod
26
26
27 table = {}
27 table = {}
28
28
29 command = cmdutil.command(table)
29 command = cmdutil.command(table)
30
30
31 # Space delimited list of commands that don't require local repositories.
31 # Space delimited list of commands that don't require local repositories.
32 # This should be populated by passing norepo=True into the @command decorator.
32 # This should be populated by passing norepo=True into the @command decorator.
33 norepo = ''
33 norepo = ''
34 # Space delimited list of commands that optionally require local repositories.
34 # Space delimited list of commands that optionally require local repositories.
35 # This should be populated by passing optionalrepo=True into the @command
35 # This should be populated by passing optionalrepo=True into the @command
36 # decorator.
36 # decorator.
37 optionalrepo = ''
37 optionalrepo = ''
38 # Space delimited list of commands that will examine arguments looking for
38 # Space delimited list of commands that will examine arguments looking for
39 # a repository. This should be populated by passing inferrepo=True into the
39 # a repository. This should be populated by passing inferrepo=True into the
40 # @command decorator.
40 # @command decorator.
41 inferrepo = ''
41 inferrepo = ''
42
42
43 # common command options
43 # common command options
44
44
45 globalopts = [
45 globalopts = [
46 ('R', 'repository', '',
46 ('R', 'repository', '',
47 _('repository root directory or name of overlay bundle file'),
47 _('repository root directory or name of overlay bundle file'),
48 _('REPO')),
48 _('REPO')),
49 ('', 'cwd', '',
49 ('', 'cwd', '',
50 _('change working directory'), _('DIR')),
50 _('change working directory'), _('DIR')),
51 ('y', 'noninteractive', None,
51 ('y', 'noninteractive', None,
52 _('do not prompt, automatically pick the first choice for all prompts')),
52 _('do not prompt, automatically pick the first choice for all prompts')),
53 ('q', 'quiet', None, _('suppress output')),
53 ('q', 'quiet', None, _('suppress output')),
54 ('v', 'verbose', None, _('enable additional output')),
54 ('v', 'verbose', None, _('enable additional output')),
55 ('', 'config', [],
55 ('', 'config', [],
56 _('set/override config option (use \'section.name=value\')'),
56 _('set/override config option (use \'section.name=value\')'),
57 _('CONFIG')),
57 _('CONFIG')),
58 ('', 'debug', None, _('enable debugging output')),
58 ('', 'debug', None, _('enable debugging output')),
59 ('', 'debugger', None, _('start debugger')),
59 ('', 'debugger', None, _('start debugger')),
60 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
60 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
61 _('ENCODE')),
61 _('ENCODE')),
62 ('', 'encodingmode', encoding.encodingmode,
62 ('', 'encodingmode', encoding.encodingmode,
63 _('set the charset encoding mode'), _('MODE')),
63 _('set the charset encoding mode'), _('MODE')),
64 ('', 'traceback', None, _('always print a traceback on exception')),
64 ('', 'traceback', None, _('always print a traceback on exception')),
65 ('', 'time', None, _('time how long the command takes')),
65 ('', 'time', None, _('time how long the command takes')),
66 ('', 'profile', None, _('print command execution profile')),
66 ('', 'profile', None, _('print command execution profile')),
67 ('', 'version', None, _('output version information and exit')),
67 ('', 'version', None, _('output version information and exit')),
68 ('h', 'help', None, _('display help and exit')),
68 ('h', 'help', None, _('display help and exit')),
69 ('', 'hidden', False, _('consider hidden changesets')),
69 ('', 'hidden', False, _('consider hidden changesets')),
70 ]
70 ]
71
71
72 dryrunopts = [('n', 'dry-run', None,
72 dryrunopts = [('n', 'dry-run', None,
73 _('do not perform actions, just print output'))]
73 _('do not perform actions, just print output'))]
74
74
75 remoteopts = [
75 remoteopts = [
76 ('e', 'ssh', '',
76 ('e', 'ssh', '',
77 _('specify ssh command to use'), _('CMD')),
77 _('specify ssh command to use'), _('CMD')),
78 ('', 'remotecmd', '',
78 ('', 'remotecmd', '',
79 _('specify hg command to run on the remote side'), _('CMD')),
79 _('specify hg command to run on the remote side'), _('CMD')),
80 ('', 'insecure', None,
80 ('', 'insecure', None,
81 _('do not verify server certificate (ignoring web.cacerts config)')),
81 _('do not verify server certificate (ignoring web.cacerts config)')),
82 ]
82 ]
83
83
84 walkopts = [
84 walkopts = [
85 ('I', 'include', [],
85 ('I', 'include', [],
86 _('include names matching the given patterns'), _('PATTERN')),
86 _('include names matching the given patterns'), _('PATTERN')),
87 ('X', 'exclude', [],
87 ('X', 'exclude', [],
88 _('exclude names matching the given patterns'), _('PATTERN')),
88 _('exclude names matching the given patterns'), _('PATTERN')),
89 ]
89 ]
90
90
91 commitopts = [
91 commitopts = [
92 ('m', 'message', '',
92 ('m', 'message', '',
93 _('use text as commit message'), _('TEXT')),
93 _('use text as commit message'), _('TEXT')),
94 ('l', 'logfile', '',
94 ('l', 'logfile', '',
95 _('read commit message from file'), _('FILE')),
95 _('read commit message from file'), _('FILE')),
96 ]
96 ]
97
97
98 commitopts2 = [
98 commitopts2 = [
99 ('d', 'date', '',
99 ('d', 'date', '',
100 _('record the specified date as commit date'), _('DATE')),
100 _('record the specified date as commit date'), _('DATE')),
101 ('u', 'user', '',
101 ('u', 'user', '',
102 _('record the specified user as committer'), _('USER')),
102 _('record the specified user as committer'), _('USER')),
103 ]
103 ]
104
104
105 # hidden for now
105 # hidden for now
106 formatteropts = [
106 formatteropts = [
107 ('T', 'template', '',
107 ('T', 'template', '',
108 _('display with template (DEPRECATED)'), _('TEMPLATE')),
108 _('display with template (DEPRECATED)'), _('TEMPLATE')),
109 ]
109 ]
110
110
111 templateopts = [
111 templateopts = [
112 ('', 'style', '',
112 ('', 'style', '',
113 _('display using template map file (DEPRECATED)'), _('STYLE')),
113 _('display using template map file (DEPRECATED)'), _('STYLE')),
114 ('T', 'template', '',
114 ('T', 'template', '',
115 _('display with template'), _('TEMPLATE')),
115 _('display with template'), _('TEMPLATE')),
116 ]
116 ]
117
117
118 logopts = [
118 logopts = [
119 ('p', 'patch', None, _('show patch')),
119 ('p', 'patch', None, _('show patch')),
120 ('g', 'git', None, _('use git extended diff format')),
120 ('g', 'git', None, _('use git extended diff format')),
121 ('l', 'limit', '',
121 ('l', 'limit', '',
122 _('limit number of changes displayed'), _('NUM')),
122 _('limit number of changes displayed'), _('NUM')),
123 ('M', 'no-merges', None, _('do not show merges')),
123 ('M', 'no-merges', None, _('do not show merges')),
124 ('', 'stat', None, _('output diffstat-style summary of changes')),
124 ('', 'stat', None, _('output diffstat-style summary of changes')),
125 ('G', 'graph', None, _("show the revision DAG")),
125 ('G', 'graph', None, _("show the revision DAG")),
126 ] + templateopts
126 ] + templateopts
127
127
128 diffopts = [
128 diffopts = [
129 ('a', 'text', None, _('treat all files as text')),
129 ('a', 'text', None, _('treat all files as text')),
130 ('g', 'git', None, _('use git extended diff format')),
130 ('g', 'git', None, _('use git extended diff format')),
131 ('', 'nodates', None, _('omit dates from diff headers'))
131 ('', 'nodates', None, _('omit dates from diff headers'))
132 ]
132 ]
133
133
134 diffwsopts = [
134 diffwsopts = [
135 ('w', 'ignore-all-space', None,
135 ('w', 'ignore-all-space', None,
136 _('ignore white space when comparing lines')),
136 _('ignore white space when comparing lines')),
137 ('b', 'ignore-space-change', None,
137 ('b', 'ignore-space-change', None,
138 _('ignore changes in the amount of white space')),
138 _('ignore changes in the amount of white space')),
139 ('B', 'ignore-blank-lines', None,
139 ('B', 'ignore-blank-lines', None,
140 _('ignore changes whose lines are all blank')),
140 _('ignore changes whose lines are all blank')),
141 ]
141 ]
142
142
143 diffopts2 = [
143 diffopts2 = [
144 ('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')),
144 ('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')),
145 ('p', 'show-function', None, _('show which function each change is in')),
145 ('p', 'show-function', None, _('show which function each change is in')),
146 ('', 'reverse', None, _('produce a diff that undoes the changes')),
146 ('', 'reverse', None, _('produce a diff that undoes the changes')),
147 ] + diffwsopts + [
147 ] + diffwsopts + [
148 ('U', 'unified', '',
148 ('U', 'unified', '',
149 _('number of lines of context to show'), _('NUM')),
149 _('number of lines of context to show'), _('NUM')),
150 ('', 'stat', None, _('output diffstat-style summary of changes')),
150 ('', 'stat', None, _('output diffstat-style summary of changes')),
151 ]
151 ]
152
152
153 mergetoolopts = [
153 mergetoolopts = [
154 ('t', 'tool', '', _('specify merge tool')),
154 ('t', 'tool', '', _('specify merge tool')),
155 ]
155 ]
156
156
157 similarityopts = [
157 similarityopts = [
158 ('s', 'similarity', '',
158 ('s', 'similarity', '',
159 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
159 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
160 ]
160 ]
161
161
162 subrepoopts = [
162 subrepoopts = [
163 ('S', 'subrepos', None,
163 ('S', 'subrepos', None,
164 _('recurse into subrepositories'))
164 _('recurse into subrepositories'))
165 ]
165 ]
166
166
167 # Commands start here, listed alphabetically
167 # Commands start here, listed alphabetically
168
168
169 @command('^add',
169 @command('^add',
170 walkopts + subrepoopts + dryrunopts,
170 walkopts + subrepoopts + dryrunopts,
171 _('[OPTION]... [FILE]...'),
171 _('[OPTION]... [FILE]...'),
172 inferrepo=True)
172 inferrepo=True)
173 def add(ui, repo, *pats, **opts):
173 def add(ui, repo, *pats, **opts):
174 """add the specified files on the next commit
174 """add the specified files on the next commit
175
175
176 Schedule files to be version controlled and added to the
176 Schedule files to be version controlled and added to the
177 repository.
177 repository.
178
178
179 The files will be added to the repository at the next commit. To
179 The files will be added to the repository at the next commit. To
180 undo an add before that, see :hg:`forget`.
180 undo an add before that, see :hg:`forget`.
181
181
182 If no names are given, add all files to the repository.
182 If no names are given, add all files to the repository.
183
183
184 .. container:: verbose
184 .. container:: verbose
185
185
186 An example showing how new (unknown) files are added
186 An example showing how new (unknown) files are added
187 automatically by :hg:`add`::
187 automatically by :hg:`add`::
188
188
189 $ ls
189 $ ls
190 foo.c
190 foo.c
191 $ hg status
191 $ hg status
192 ? foo.c
192 ? foo.c
193 $ hg add
193 $ hg add
194 adding foo.c
194 adding foo.c
195 $ hg status
195 $ hg status
196 A foo.c
196 A foo.c
197
197
198 Returns 0 if all files are successfully added.
198 Returns 0 if all files are successfully added.
199 """
199 """
200
200
201 m = scmutil.match(repo[None], pats, opts)
201 m = scmutil.match(repo[None], pats, opts)
202 rejected = cmdutil.add(ui, repo, m, opts.get('dry_run'),
202 rejected = cmdutil.add(ui, repo, m, "", False, **opts)
203 opts.get('subrepos'), prefix="", explicitonly=False)
204 return rejected and 1 or 0
203 return rejected and 1 or 0
205
204
206 @command('addremove',
205 @command('addremove',
207 similarityopts + subrepoopts + walkopts + dryrunopts,
206 similarityopts + subrepoopts + walkopts + dryrunopts,
208 _('[OPTION]... [FILE]...'),
207 _('[OPTION]... [FILE]...'),
209 inferrepo=True)
208 inferrepo=True)
210 def addremove(ui, repo, *pats, **opts):
209 def addremove(ui, repo, *pats, **opts):
211 """add all new files, delete all missing files
210 """add all new files, delete all missing files
212
211
213 Add all new files and remove all missing files from the
212 Add all new files and remove all missing files from the
214 repository.
213 repository.
215
214
216 New files are ignored if they match any of the patterns in
215 New files are ignored if they match any of the patterns in
217 ``.hgignore``. As with add, these changes take effect at the next
216 ``.hgignore``. As with add, these changes take effect at the next
218 commit.
217 commit.
219
218
220 Use the -s/--similarity option to detect renamed files. This
219 Use the -s/--similarity option to detect renamed files. This
221 option takes a percentage between 0 (disabled) and 100 (files must
220 option takes a percentage between 0 (disabled) and 100 (files must
222 be identical) as its parameter. With a parameter greater than 0,
221 be identical) as its parameter. With a parameter greater than 0,
223 this compares every removed file with every added file and records
222 this compares every removed file with every added file and records
224 those similar enough as renames. Detecting renamed files this way
223 those similar enough as renames. Detecting renamed files this way
225 can be expensive. After using this option, :hg:`status -C` can be
224 can be expensive. After using this option, :hg:`status -C` can be
226 used to check which files were identified as moved or renamed. If
225 used to check which files were identified as moved or renamed. If
227 not specified, -s/--similarity defaults to 100 and only renames of
226 not specified, -s/--similarity defaults to 100 and only renames of
228 identical files are detected.
227 identical files are detected.
229
228
230 Returns 0 if all files are successfully added.
229 Returns 0 if all files are successfully added.
231 """
230 """
232 try:
231 try:
233 sim = float(opts.get('similarity') or 100)
232 sim = float(opts.get('similarity') or 100)
234 except ValueError:
233 except ValueError:
235 raise util.Abort(_('similarity must be a number'))
234 raise util.Abort(_('similarity must be a number'))
236 if sim < 0 or sim > 100:
235 if sim < 0 or sim > 100:
237 raise util.Abort(_('similarity must be between 0 and 100'))
236 raise util.Abort(_('similarity must be between 0 and 100'))
238 matcher = scmutil.match(repo[None], pats, opts)
237 matcher = scmutil.match(repo[None], pats, opts)
239 return scmutil.addremove(repo, matcher, "", opts, similarity=sim / 100.0)
238 return scmutil.addremove(repo, matcher, "", opts, similarity=sim / 100.0)
240
239
241 @command('^annotate|blame',
240 @command('^annotate|blame',
242 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
241 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
243 ('', 'follow', None,
242 ('', 'follow', None,
244 _('follow copies/renames and list the filename (DEPRECATED)')),
243 _('follow copies/renames and list the filename (DEPRECATED)')),
245 ('', 'no-follow', None, _("don't follow copies and renames")),
244 ('', 'no-follow', None, _("don't follow copies and renames")),
246 ('a', 'text', None, _('treat all files as text')),
245 ('a', 'text', None, _('treat all files as text')),
247 ('u', 'user', None, _('list the author (long with -v)')),
246 ('u', 'user', None, _('list the author (long with -v)')),
248 ('f', 'file', None, _('list the filename')),
247 ('f', 'file', None, _('list the filename')),
249 ('d', 'date', None, _('list the date (short with -q)')),
248 ('d', 'date', None, _('list the date (short with -q)')),
250 ('n', 'number', None, _('list the revision number (default)')),
249 ('n', 'number', None, _('list the revision number (default)')),
251 ('c', 'changeset', None, _('list the changeset')),
250 ('c', 'changeset', None, _('list the changeset')),
252 ('l', 'line-number', None, _('show line number at the first appearance'))
251 ('l', 'line-number', None, _('show line number at the first appearance'))
253 ] + diffwsopts + walkopts + formatteropts,
252 ] + diffwsopts + walkopts + formatteropts,
254 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
253 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
255 inferrepo=True)
254 inferrepo=True)
256 def annotate(ui, repo, *pats, **opts):
255 def annotate(ui, repo, *pats, **opts):
257 """show changeset information by line for each file
256 """show changeset information by line for each file
258
257
259 List changes in files, showing the revision id responsible for
258 List changes in files, showing the revision id responsible for
260 each line
259 each line
261
260
262 This command is useful for discovering when a change was made and
261 This command is useful for discovering when a change was made and
263 by whom.
262 by whom.
264
263
265 Without the -a/--text option, annotate will avoid processing files
264 Without the -a/--text option, annotate will avoid processing files
266 it detects as binary. With -a, annotate will annotate the file
265 it detects as binary. With -a, annotate will annotate the file
267 anyway, although the results will probably be neither useful
266 anyway, although the results will probably be neither useful
268 nor desirable.
267 nor desirable.
269
268
270 Returns 0 on success.
269 Returns 0 on success.
271 """
270 """
272 if not pats:
271 if not pats:
273 raise util.Abort(_('at least one filename or pattern is required'))
272 raise util.Abort(_('at least one filename or pattern is required'))
274
273
275 if opts.get('follow'):
274 if opts.get('follow'):
276 # --follow is deprecated and now just an alias for -f/--file
275 # --follow is deprecated and now just an alias for -f/--file
277 # to mimic the behavior of Mercurial before version 1.5
276 # to mimic the behavior of Mercurial before version 1.5
278 opts['file'] = True
277 opts['file'] = True
279
278
280 fm = ui.formatter('annotate', opts)
279 fm = ui.formatter('annotate', opts)
281 datefunc = ui.quiet and util.shortdate or util.datestr
280 datefunc = ui.quiet and util.shortdate or util.datestr
282 hexfn = fm.hexfunc
281 hexfn = fm.hexfunc
283
282
284 opmap = [('user', ' ', lambda x: x[0].user(), ui.shortuser),
283 opmap = [('user', ' ', lambda x: x[0].user(), ui.shortuser),
285 ('number', ' ', lambda x: x[0].rev(), str),
284 ('number', ' ', lambda x: x[0].rev(), str),
286 ('changeset', ' ', lambda x: hexfn(x[0].node()), str),
285 ('changeset', ' ', lambda x: hexfn(x[0].node()), str),
287 ('date', ' ', lambda x: x[0].date(), util.cachefunc(datefunc)),
286 ('date', ' ', lambda x: x[0].date(), util.cachefunc(datefunc)),
288 ('file', ' ', lambda x: x[0].path(), str),
287 ('file', ' ', lambda x: x[0].path(), str),
289 ('line_number', ':', lambda x: x[1], str),
288 ('line_number', ':', lambda x: x[1], str),
290 ]
289 ]
291 fieldnamemap = {'number': 'rev', 'changeset': 'node'}
290 fieldnamemap = {'number': 'rev', 'changeset': 'node'}
292
291
293 if (not opts.get('user') and not opts.get('changeset')
292 if (not opts.get('user') and not opts.get('changeset')
294 and not opts.get('date') and not opts.get('file')):
293 and not opts.get('date') and not opts.get('file')):
295 opts['number'] = True
294 opts['number'] = True
296
295
297 linenumber = opts.get('line_number') is not None
296 linenumber = opts.get('line_number') is not None
298 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
297 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
299 raise util.Abort(_('at least one of -n/-c is required for -l'))
298 raise util.Abort(_('at least one of -n/-c is required for -l'))
300
299
301 if fm:
300 if fm:
302 def makefunc(get, fmt):
301 def makefunc(get, fmt):
303 return get
302 return get
304 else:
303 else:
305 def makefunc(get, fmt):
304 def makefunc(get, fmt):
306 return lambda x: fmt(get(x))
305 return lambda x: fmt(get(x))
307 funcmap = [(makefunc(get, fmt), sep) for op, sep, get, fmt in opmap
306 funcmap = [(makefunc(get, fmt), sep) for op, sep, get, fmt in opmap
308 if opts.get(op)]
307 if opts.get(op)]
309 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
308 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
310 fields = ' '.join(fieldnamemap.get(op, op) for op, sep, get, fmt in opmap
309 fields = ' '.join(fieldnamemap.get(op, op) for op, sep, get, fmt in opmap
311 if opts.get(op))
310 if opts.get(op))
312
311
313 def bad(x, y):
312 def bad(x, y):
314 raise util.Abort("%s: %s" % (x, y))
313 raise util.Abort("%s: %s" % (x, y))
315
314
316 ctx = scmutil.revsingle(repo, opts.get('rev'))
315 ctx = scmutil.revsingle(repo, opts.get('rev'))
317 m = scmutil.match(ctx, pats, opts)
316 m = scmutil.match(ctx, pats, opts)
318 m.bad = bad
317 m.bad = bad
319 follow = not opts.get('no_follow')
318 follow = not opts.get('no_follow')
320 diffopts = patch.difffeatureopts(ui, opts, section='annotate',
319 diffopts = patch.difffeatureopts(ui, opts, section='annotate',
321 whitespace=True)
320 whitespace=True)
322 for abs in ctx.walk(m):
321 for abs in ctx.walk(m):
323 fctx = ctx[abs]
322 fctx = ctx[abs]
324 if not opts.get('text') and util.binary(fctx.data()):
323 if not opts.get('text') and util.binary(fctx.data()):
325 fm.plain(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
324 fm.plain(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
326 continue
325 continue
327
326
328 lines = fctx.annotate(follow=follow, linenumber=linenumber,
327 lines = fctx.annotate(follow=follow, linenumber=linenumber,
329 diffopts=diffopts)
328 diffopts=diffopts)
330 formats = []
329 formats = []
331 pieces = []
330 pieces = []
332
331
333 for f, sep in funcmap:
332 for f, sep in funcmap:
334 l = [f(n) for n, dummy in lines]
333 l = [f(n) for n, dummy in lines]
335 if l:
334 if l:
336 if fm:
335 if fm:
337 formats.append(['%s' for x in l])
336 formats.append(['%s' for x in l])
338 else:
337 else:
339 sizes = [encoding.colwidth(x) for x in l]
338 sizes = [encoding.colwidth(x) for x in l]
340 ml = max(sizes)
339 ml = max(sizes)
341 formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
340 formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
342 pieces.append(l)
341 pieces.append(l)
343
342
344 for f, p, l in zip(zip(*formats), zip(*pieces), lines):
343 for f, p, l in zip(zip(*formats), zip(*pieces), lines):
345 fm.startitem()
344 fm.startitem()
346 fm.write(fields, "".join(f), *p)
345 fm.write(fields, "".join(f), *p)
347 fm.write('line', ": %s", l[1])
346 fm.write('line', ": %s", l[1])
348
347
349 if lines and not lines[-1][1].endswith('\n'):
348 if lines and not lines[-1][1].endswith('\n'):
350 fm.plain('\n')
349 fm.plain('\n')
351
350
352 fm.end()
351 fm.end()
353
352
354 @command('archive',
353 @command('archive',
355 [('', 'no-decode', None, _('do not pass files through decoders')),
354 [('', 'no-decode', None, _('do not pass files through decoders')),
356 ('p', 'prefix', '', _('directory prefix for files in archive'),
355 ('p', 'prefix', '', _('directory prefix for files in archive'),
357 _('PREFIX')),
356 _('PREFIX')),
358 ('r', 'rev', '', _('revision to distribute'), _('REV')),
357 ('r', 'rev', '', _('revision to distribute'), _('REV')),
359 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
358 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
360 ] + subrepoopts + walkopts,
359 ] + subrepoopts + walkopts,
361 _('[OPTION]... DEST'))
360 _('[OPTION]... DEST'))
362 def archive(ui, repo, dest, **opts):
361 def archive(ui, repo, dest, **opts):
363 '''create an unversioned archive of a repository revision
362 '''create an unversioned archive of a repository revision
364
363
365 By default, the revision used is the parent of the working
364 By default, the revision used is the parent of the working
366 directory; use -r/--rev to specify a different revision.
365 directory; use -r/--rev to specify a different revision.
367
366
368 The archive type is automatically detected based on file
367 The archive type is automatically detected based on file
369 extension (or override using -t/--type).
368 extension (or override using -t/--type).
370
369
371 .. container:: verbose
370 .. container:: verbose
372
371
373 Examples:
372 Examples:
374
373
375 - create a zip file containing the 1.0 release::
374 - create a zip file containing the 1.0 release::
376
375
377 hg archive -r 1.0 project-1.0.zip
376 hg archive -r 1.0 project-1.0.zip
378
377
379 - create a tarball excluding .hg files::
378 - create a tarball excluding .hg files::
380
379
381 hg archive project.tar.gz -X ".hg*"
380 hg archive project.tar.gz -X ".hg*"
382
381
383 Valid types are:
382 Valid types are:
384
383
385 :``files``: a directory full of files (default)
384 :``files``: a directory full of files (default)
386 :``tar``: tar archive, uncompressed
385 :``tar``: tar archive, uncompressed
387 :``tbz2``: tar archive, compressed using bzip2
386 :``tbz2``: tar archive, compressed using bzip2
388 :``tgz``: tar archive, compressed using gzip
387 :``tgz``: tar archive, compressed using gzip
389 :``uzip``: zip archive, uncompressed
388 :``uzip``: zip archive, uncompressed
390 :``zip``: zip archive, compressed using deflate
389 :``zip``: zip archive, compressed using deflate
391
390
392 The exact name of the destination archive or directory is given
391 The exact name of the destination archive or directory is given
393 using a format string; see :hg:`help export` for details.
392 using a format string; see :hg:`help export` for details.
394
393
395 Each member added to an archive file has a directory prefix
394 Each member added to an archive file has a directory prefix
396 prepended. Use -p/--prefix to specify a format string for the
395 prepended. Use -p/--prefix to specify a format string for the
397 prefix. The default is the basename of the archive, with suffixes
396 prefix. The default is the basename of the archive, with suffixes
398 removed.
397 removed.
399
398
400 Returns 0 on success.
399 Returns 0 on success.
401 '''
400 '''
402
401
403 ctx = scmutil.revsingle(repo, opts.get('rev'))
402 ctx = scmutil.revsingle(repo, opts.get('rev'))
404 if not ctx:
403 if not ctx:
405 raise util.Abort(_('no working directory: please specify a revision'))
404 raise util.Abort(_('no working directory: please specify a revision'))
406 node = ctx.node()
405 node = ctx.node()
407 dest = cmdutil.makefilename(repo, dest, node)
406 dest = cmdutil.makefilename(repo, dest, node)
408 if os.path.realpath(dest) == repo.root:
407 if os.path.realpath(dest) == repo.root:
409 raise util.Abort(_('repository root cannot be destination'))
408 raise util.Abort(_('repository root cannot be destination'))
410
409
411 kind = opts.get('type') or archival.guesskind(dest) or 'files'
410 kind = opts.get('type') or archival.guesskind(dest) or 'files'
412 prefix = opts.get('prefix')
411 prefix = opts.get('prefix')
413
412
414 if dest == '-':
413 if dest == '-':
415 if kind == 'files':
414 if kind == 'files':
416 raise util.Abort(_('cannot archive plain files to stdout'))
415 raise util.Abort(_('cannot archive plain files to stdout'))
417 dest = cmdutil.makefileobj(repo, dest)
416 dest = cmdutil.makefileobj(repo, dest)
418 if not prefix:
417 if not prefix:
419 prefix = os.path.basename(repo.root) + '-%h'
418 prefix = os.path.basename(repo.root) + '-%h'
420
419
421 prefix = cmdutil.makefilename(repo, prefix, node)
420 prefix = cmdutil.makefilename(repo, prefix, node)
422 matchfn = scmutil.match(ctx, [], opts)
421 matchfn = scmutil.match(ctx, [], opts)
423 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
422 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
424 matchfn, prefix, subrepos=opts.get('subrepos'))
423 matchfn, prefix, subrepos=opts.get('subrepos'))
425
424
426 @command('backout',
425 @command('backout',
427 [('', 'merge', None, _('merge with old dirstate parent after backout')),
426 [('', 'merge', None, _('merge with old dirstate parent after backout')),
428 ('', 'commit', None, _('commit if no conflicts were encountered')),
427 ('', 'commit', None, _('commit if no conflicts were encountered')),
429 ('', 'parent', '',
428 ('', 'parent', '',
430 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
429 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
431 ('r', 'rev', '', _('revision to backout'), _('REV')),
430 ('r', 'rev', '', _('revision to backout'), _('REV')),
432 ('e', 'edit', False, _('invoke editor on commit messages')),
431 ('e', 'edit', False, _('invoke editor on commit messages')),
433 ] + mergetoolopts + walkopts + commitopts + commitopts2,
432 ] + mergetoolopts + walkopts + commitopts + commitopts2,
434 _('[OPTION]... [-r] REV'))
433 _('[OPTION]... [-r] REV'))
435 def backout(ui, repo, node=None, rev=None, commit=False, **opts):
434 def backout(ui, repo, node=None, rev=None, commit=False, **opts):
436 '''reverse effect of earlier changeset
435 '''reverse effect of earlier changeset
437
436
438 Prepare a new changeset with the effect of REV undone in the
437 Prepare a new changeset with the effect of REV undone in the
439 current working directory.
438 current working directory.
440
439
441 If REV is the parent of the working directory, then this new changeset
440 If REV is the parent of the working directory, then this new changeset
442 is committed automatically. Otherwise, hg needs to merge the
441 is committed automatically. Otherwise, hg needs to merge the
443 changes and the merged result is left uncommitted.
442 changes and the merged result is left uncommitted.
444
443
445 .. note::
444 .. note::
446
445
447 backout cannot be used to fix either an unwanted or
446 backout cannot be used to fix either an unwanted or
448 incorrect merge.
447 incorrect merge.
449
448
450 .. container:: verbose
449 .. container:: verbose
451
450
452 By default, the pending changeset will have one parent,
451 By default, the pending changeset will have one parent,
453 maintaining a linear history. With --merge, the pending
452 maintaining a linear history. With --merge, the pending
454 changeset will instead have two parents: the old parent of the
453 changeset will instead have two parents: the old parent of the
455 working directory and a new child of REV that simply undoes REV.
454 working directory and a new child of REV that simply undoes REV.
456
455
457 Before version 1.7, the behavior without --merge was equivalent
456 Before version 1.7, the behavior without --merge was equivalent
458 to specifying --merge followed by :hg:`update --clean .` to
457 to specifying --merge followed by :hg:`update --clean .` to
459 cancel the merge and leave the child of REV as a head to be
458 cancel the merge and leave the child of REV as a head to be
460 merged separately.
459 merged separately.
461
460
462 See :hg:`help dates` for a list of formats valid for -d/--date.
461 See :hg:`help dates` for a list of formats valid for -d/--date.
463
462
464 Returns 0 on success, 1 if nothing to backout or there are unresolved
463 Returns 0 on success, 1 if nothing to backout or there are unresolved
465 files.
464 files.
466 '''
465 '''
467 if rev and node:
466 if rev and node:
468 raise util.Abort(_("please specify just one revision"))
467 raise util.Abort(_("please specify just one revision"))
469
468
470 if not rev:
469 if not rev:
471 rev = node
470 rev = node
472
471
473 if not rev:
472 if not rev:
474 raise util.Abort(_("please specify a revision to backout"))
473 raise util.Abort(_("please specify a revision to backout"))
475
474
476 date = opts.get('date')
475 date = opts.get('date')
477 if date:
476 if date:
478 opts['date'] = util.parsedate(date)
477 opts['date'] = util.parsedate(date)
479
478
480 cmdutil.checkunfinished(repo)
479 cmdutil.checkunfinished(repo)
481 cmdutil.bailifchanged(repo)
480 cmdutil.bailifchanged(repo)
482 node = scmutil.revsingle(repo, rev).node()
481 node = scmutil.revsingle(repo, rev).node()
483
482
484 op1, op2 = repo.dirstate.parents()
483 op1, op2 = repo.dirstate.parents()
485 if not repo.changelog.isancestor(node, op1):
484 if not repo.changelog.isancestor(node, op1):
486 raise util.Abort(_('cannot backout change that is not an ancestor'))
485 raise util.Abort(_('cannot backout change that is not an ancestor'))
487
486
488 p1, p2 = repo.changelog.parents(node)
487 p1, p2 = repo.changelog.parents(node)
489 if p1 == nullid:
488 if p1 == nullid:
490 raise util.Abort(_('cannot backout a change with no parents'))
489 raise util.Abort(_('cannot backout a change with no parents'))
491 if p2 != nullid:
490 if p2 != nullid:
492 if not opts.get('parent'):
491 if not opts.get('parent'):
493 raise util.Abort(_('cannot backout a merge changeset'))
492 raise util.Abort(_('cannot backout a merge changeset'))
494 p = repo.lookup(opts['parent'])
493 p = repo.lookup(opts['parent'])
495 if p not in (p1, p2):
494 if p not in (p1, p2):
496 raise util.Abort(_('%s is not a parent of %s') %
495 raise util.Abort(_('%s is not a parent of %s') %
497 (short(p), short(node)))
496 (short(p), short(node)))
498 parent = p
497 parent = p
499 else:
498 else:
500 if opts.get('parent'):
499 if opts.get('parent'):
501 raise util.Abort(_('cannot use --parent on non-merge changeset'))
500 raise util.Abort(_('cannot use --parent on non-merge changeset'))
502 parent = p1
501 parent = p1
503
502
504 # the backout should appear on the same branch
503 # the backout should appear on the same branch
505 wlock = repo.wlock()
504 wlock = repo.wlock()
506 try:
505 try:
507 branch = repo.dirstate.branch()
506 branch = repo.dirstate.branch()
508 bheads = repo.branchheads(branch)
507 bheads = repo.branchheads(branch)
509 rctx = scmutil.revsingle(repo, hex(parent))
508 rctx = scmutil.revsingle(repo, hex(parent))
510 if not opts.get('merge') and op1 != node:
509 if not opts.get('merge') and op1 != node:
511 try:
510 try:
512 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
511 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
513 'backout')
512 'backout')
514 repo.dirstate.beginparentchange()
513 repo.dirstate.beginparentchange()
515 stats = mergemod.update(repo, parent, True, True, False,
514 stats = mergemod.update(repo, parent, True, True, False,
516 node, False)
515 node, False)
517 repo.setparents(op1, op2)
516 repo.setparents(op1, op2)
518 repo.dirstate.endparentchange()
517 repo.dirstate.endparentchange()
519 hg._showstats(repo, stats)
518 hg._showstats(repo, stats)
520 if stats[3]:
519 if stats[3]:
521 repo.ui.status(_("use 'hg resolve' to retry unresolved "
520 repo.ui.status(_("use 'hg resolve' to retry unresolved "
522 "file merges\n"))
521 "file merges\n"))
523 return 1
522 return 1
524 elif not commit:
523 elif not commit:
525 msg = _("changeset %s backed out, "
524 msg = _("changeset %s backed out, "
526 "don't forget to commit.\n")
525 "don't forget to commit.\n")
527 ui.status(msg % short(node))
526 ui.status(msg % short(node))
528 return 0
527 return 0
529 finally:
528 finally:
530 ui.setconfig('ui', 'forcemerge', '', '')
529 ui.setconfig('ui', 'forcemerge', '', '')
531 else:
530 else:
532 hg.clean(repo, node, show_stats=False)
531 hg.clean(repo, node, show_stats=False)
533 repo.dirstate.setbranch(branch)
532 repo.dirstate.setbranch(branch)
534 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
533 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
535
534
536
535
537 def commitfunc(ui, repo, message, match, opts):
536 def commitfunc(ui, repo, message, match, opts):
538 editform = 'backout'
537 editform = 'backout'
539 e = cmdutil.getcommiteditor(editform=editform, **opts)
538 e = cmdutil.getcommiteditor(editform=editform, **opts)
540 if not message:
539 if not message:
541 # we don't translate commit messages
540 # we don't translate commit messages
542 message = "Backed out changeset %s" % short(node)
541 message = "Backed out changeset %s" % short(node)
543 e = cmdutil.getcommiteditor(edit=True, editform=editform)
542 e = cmdutil.getcommiteditor(edit=True, editform=editform)
544 return repo.commit(message, opts.get('user'), opts.get('date'),
543 return repo.commit(message, opts.get('user'), opts.get('date'),
545 match, editor=e)
544 match, editor=e)
546 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
545 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
547 if not newnode:
546 if not newnode:
548 ui.status(_("nothing changed\n"))
547 ui.status(_("nothing changed\n"))
549 return 1
548 return 1
550 cmdutil.commitstatus(repo, newnode, branch, bheads)
549 cmdutil.commitstatus(repo, newnode, branch, bheads)
551
550
552 def nice(node):
551 def nice(node):
553 return '%d:%s' % (repo.changelog.rev(node), short(node))
552 return '%d:%s' % (repo.changelog.rev(node), short(node))
554 ui.status(_('changeset %s backs out changeset %s\n') %
553 ui.status(_('changeset %s backs out changeset %s\n') %
555 (nice(repo.changelog.tip()), nice(node)))
554 (nice(repo.changelog.tip()), nice(node)))
556 if opts.get('merge') and op1 != node:
555 if opts.get('merge') and op1 != node:
557 hg.clean(repo, op1, show_stats=False)
556 hg.clean(repo, op1, show_stats=False)
558 ui.status(_('merging with changeset %s\n')
557 ui.status(_('merging with changeset %s\n')
559 % nice(repo.changelog.tip()))
558 % nice(repo.changelog.tip()))
560 try:
559 try:
561 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
560 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
562 'backout')
561 'backout')
563 return hg.merge(repo, hex(repo.changelog.tip()))
562 return hg.merge(repo, hex(repo.changelog.tip()))
564 finally:
563 finally:
565 ui.setconfig('ui', 'forcemerge', '', '')
564 ui.setconfig('ui', 'forcemerge', '', '')
566 finally:
565 finally:
567 wlock.release()
566 wlock.release()
568 return 0
567 return 0
569
568
570 @command('bisect',
569 @command('bisect',
571 [('r', 'reset', False, _('reset bisect state')),
570 [('r', 'reset', False, _('reset bisect state')),
572 ('g', 'good', False, _('mark changeset good')),
571 ('g', 'good', False, _('mark changeset good')),
573 ('b', 'bad', False, _('mark changeset bad')),
572 ('b', 'bad', False, _('mark changeset bad')),
574 ('s', 'skip', False, _('skip testing changeset')),
573 ('s', 'skip', False, _('skip testing changeset')),
575 ('e', 'extend', False, _('extend the bisect range')),
574 ('e', 'extend', False, _('extend the bisect range')),
576 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
575 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
577 ('U', 'noupdate', False, _('do not update to target'))],
576 ('U', 'noupdate', False, _('do not update to target'))],
578 _("[-gbsr] [-U] [-c CMD] [REV]"))
577 _("[-gbsr] [-U] [-c CMD] [REV]"))
579 def bisect(ui, repo, rev=None, extra=None, command=None,
578 def bisect(ui, repo, rev=None, extra=None, command=None,
580 reset=None, good=None, bad=None, skip=None, extend=None,
579 reset=None, good=None, bad=None, skip=None, extend=None,
581 noupdate=None):
580 noupdate=None):
582 """subdivision search of changesets
581 """subdivision search of changesets
583
582
584 This command helps to find changesets which introduce problems. To
583 This command helps to find changesets which introduce problems. To
585 use, mark the earliest changeset you know exhibits the problem as
584 use, mark the earliest changeset you know exhibits the problem as
586 bad, then mark the latest changeset which is free from the problem
585 bad, then mark the latest changeset which is free from the problem
587 as good. Bisect will update your working directory to a revision
586 as good. Bisect will update your working directory to a revision
588 for testing (unless the -U/--noupdate option is specified). Once
587 for testing (unless the -U/--noupdate option is specified). Once
589 you have performed tests, mark the working directory as good or
588 you have performed tests, mark the working directory as good or
590 bad, and bisect will either update to another candidate changeset
589 bad, and bisect will either update to another candidate changeset
591 or announce that it has found the bad revision.
590 or announce that it has found the bad revision.
592
591
593 As a shortcut, you can also use the revision argument to mark a
592 As a shortcut, you can also use the revision argument to mark a
594 revision as good or bad without checking it out first.
593 revision as good or bad without checking it out first.
595
594
596 If you supply a command, it will be used for automatic bisection.
595 If you supply a command, it will be used for automatic bisection.
597 The environment variable HG_NODE will contain the ID of the
596 The environment variable HG_NODE will contain the ID of the
598 changeset being tested. The exit status of the command will be
597 changeset being tested. The exit status of the command will be
599 used to mark revisions as good or bad: status 0 means good, 125
598 used to mark revisions as good or bad: status 0 means good, 125
600 means to skip the revision, 127 (command not found) will abort the
599 means to skip the revision, 127 (command not found) will abort the
601 bisection, and any other non-zero exit status means the revision
600 bisection, and any other non-zero exit status means the revision
602 is bad.
601 is bad.
603
602
604 .. container:: verbose
603 .. container:: verbose
605
604
606 Some examples:
605 Some examples:
607
606
608 - start a bisection with known bad revision 34, and good revision 12::
607 - start a bisection with known bad revision 34, and good revision 12::
609
608
610 hg bisect --bad 34
609 hg bisect --bad 34
611 hg bisect --good 12
610 hg bisect --good 12
612
611
613 - advance the current bisection by marking current revision as good or
612 - advance the current bisection by marking current revision as good or
614 bad::
613 bad::
615
614
616 hg bisect --good
615 hg bisect --good
617 hg bisect --bad
616 hg bisect --bad
618
617
619 - mark the current revision, or a known revision, to be skipped (e.g. if
618 - mark the current revision, or a known revision, to be skipped (e.g. if
620 that revision is not usable because of another issue)::
619 that revision is not usable because of another issue)::
621
620
622 hg bisect --skip
621 hg bisect --skip
623 hg bisect --skip 23
622 hg bisect --skip 23
624
623
625 - skip all revisions that do not touch directories ``foo`` or ``bar``::
624 - skip all revisions that do not touch directories ``foo`` or ``bar``::
626
625
627 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
626 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
628
627
629 - forget the current bisection::
628 - forget the current bisection::
630
629
631 hg bisect --reset
630 hg bisect --reset
632
631
633 - use 'make && make tests' to automatically find the first broken
632 - use 'make && make tests' to automatically find the first broken
634 revision::
633 revision::
635
634
636 hg bisect --reset
635 hg bisect --reset
637 hg bisect --bad 34
636 hg bisect --bad 34
638 hg bisect --good 12
637 hg bisect --good 12
639 hg bisect --command "make && make tests"
638 hg bisect --command "make && make tests"
640
639
641 - see all changesets whose states are already known in the current
640 - see all changesets whose states are already known in the current
642 bisection::
641 bisection::
643
642
644 hg log -r "bisect(pruned)"
643 hg log -r "bisect(pruned)"
645
644
646 - see the changeset currently being bisected (especially useful
645 - see the changeset currently being bisected (especially useful
647 if running with -U/--noupdate)::
646 if running with -U/--noupdate)::
648
647
649 hg log -r "bisect(current)"
648 hg log -r "bisect(current)"
650
649
651 - see all changesets that took part in the current bisection::
650 - see all changesets that took part in the current bisection::
652
651
653 hg log -r "bisect(range)"
652 hg log -r "bisect(range)"
654
653
655 - you can even get a nice graph::
654 - you can even get a nice graph::
656
655
657 hg log --graph -r "bisect(range)"
656 hg log --graph -r "bisect(range)"
658
657
659 See :hg:`help revsets` for more about the `bisect()` keyword.
658 See :hg:`help revsets` for more about the `bisect()` keyword.
660
659
661 Returns 0 on success.
660 Returns 0 on success.
662 """
661 """
663 def extendbisectrange(nodes, good):
662 def extendbisectrange(nodes, good):
664 # bisect is incomplete when it ends on a merge node and
663 # bisect is incomplete when it ends on a merge node and
665 # one of the parent was not checked.
664 # one of the parent was not checked.
666 parents = repo[nodes[0]].parents()
665 parents = repo[nodes[0]].parents()
667 if len(parents) > 1:
666 if len(parents) > 1:
668 side = good and state['bad'] or state['good']
667 side = good and state['bad'] or state['good']
669 num = len(set(i.node() for i in parents) & set(side))
668 num = len(set(i.node() for i in parents) & set(side))
670 if num == 1:
669 if num == 1:
671 return parents[0].ancestor(parents[1])
670 return parents[0].ancestor(parents[1])
672 return None
671 return None
673
672
674 def print_result(nodes, good):
673 def print_result(nodes, good):
675 displayer = cmdutil.show_changeset(ui, repo, {})
674 displayer = cmdutil.show_changeset(ui, repo, {})
676 if len(nodes) == 1:
675 if len(nodes) == 1:
677 # narrowed it down to a single revision
676 # narrowed it down to a single revision
678 if good:
677 if good:
679 ui.write(_("The first good revision is:\n"))
678 ui.write(_("The first good revision is:\n"))
680 else:
679 else:
681 ui.write(_("The first bad revision is:\n"))
680 ui.write(_("The first bad revision is:\n"))
682 displayer.show(repo[nodes[0]])
681 displayer.show(repo[nodes[0]])
683 extendnode = extendbisectrange(nodes, good)
682 extendnode = extendbisectrange(nodes, good)
684 if extendnode is not None:
683 if extendnode is not None:
685 ui.write(_('Not all ancestors of this changeset have been'
684 ui.write(_('Not all ancestors of this changeset have been'
686 ' checked.\nUse bisect --extend to continue the '
685 ' checked.\nUse bisect --extend to continue the '
687 'bisection from\nthe common ancestor, %s.\n')
686 'bisection from\nthe common ancestor, %s.\n')
688 % extendnode)
687 % extendnode)
689 else:
688 else:
690 # multiple possible revisions
689 # multiple possible revisions
691 if good:
690 if good:
692 ui.write(_("Due to skipped revisions, the first "
691 ui.write(_("Due to skipped revisions, the first "
693 "good revision could be any of:\n"))
692 "good revision could be any of:\n"))
694 else:
693 else:
695 ui.write(_("Due to skipped revisions, the first "
694 ui.write(_("Due to skipped revisions, the first "
696 "bad revision could be any of:\n"))
695 "bad revision could be any of:\n"))
697 for n in nodes:
696 for n in nodes:
698 displayer.show(repo[n])
697 displayer.show(repo[n])
699 displayer.close()
698 displayer.close()
700
699
701 def check_state(state, interactive=True):
700 def check_state(state, interactive=True):
702 if not state['good'] or not state['bad']:
701 if not state['good'] or not state['bad']:
703 if (good or bad or skip or reset) and interactive:
702 if (good or bad or skip or reset) and interactive:
704 return
703 return
705 if not state['good']:
704 if not state['good']:
706 raise util.Abort(_('cannot bisect (no known good revisions)'))
705 raise util.Abort(_('cannot bisect (no known good revisions)'))
707 else:
706 else:
708 raise util.Abort(_('cannot bisect (no known bad revisions)'))
707 raise util.Abort(_('cannot bisect (no known bad revisions)'))
709 return True
708 return True
710
709
711 # backward compatibility
710 # backward compatibility
712 if rev in "good bad reset init".split():
711 if rev in "good bad reset init".split():
713 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
712 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
714 cmd, rev, extra = rev, extra, None
713 cmd, rev, extra = rev, extra, None
715 if cmd == "good":
714 if cmd == "good":
716 good = True
715 good = True
717 elif cmd == "bad":
716 elif cmd == "bad":
718 bad = True
717 bad = True
719 else:
718 else:
720 reset = True
719 reset = True
721 elif extra or good + bad + skip + reset + extend + bool(command) > 1:
720 elif extra or good + bad + skip + reset + extend + bool(command) > 1:
722 raise util.Abort(_('incompatible arguments'))
721 raise util.Abort(_('incompatible arguments'))
723
722
724 cmdutil.checkunfinished(repo)
723 cmdutil.checkunfinished(repo)
725
724
726 if reset:
725 if reset:
727 p = repo.join("bisect.state")
726 p = repo.join("bisect.state")
728 if os.path.exists(p):
727 if os.path.exists(p):
729 os.unlink(p)
728 os.unlink(p)
730 return
729 return
731
730
732 state = hbisect.load_state(repo)
731 state = hbisect.load_state(repo)
733
732
734 if command:
733 if command:
735 changesets = 1
734 changesets = 1
736 if noupdate:
735 if noupdate:
737 try:
736 try:
738 node = state['current'][0]
737 node = state['current'][0]
739 except LookupError:
738 except LookupError:
740 raise util.Abort(_('current bisect revision is unknown - '
739 raise util.Abort(_('current bisect revision is unknown - '
741 'start a new bisect to fix'))
740 'start a new bisect to fix'))
742 else:
741 else:
743 node, p2 = repo.dirstate.parents()
742 node, p2 = repo.dirstate.parents()
744 if p2 != nullid:
743 if p2 != nullid:
745 raise util.Abort(_('current bisect revision is a merge'))
744 raise util.Abort(_('current bisect revision is a merge'))
746 try:
745 try:
747 while changesets:
746 while changesets:
748 # update state
747 # update state
749 state['current'] = [node]
748 state['current'] = [node]
750 hbisect.save_state(repo, state)
749 hbisect.save_state(repo, state)
751 status = ui.system(command, environ={'HG_NODE': hex(node)})
750 status = ui.system(command, environ={'HG_NODE': hex(node)})
752 if status == 125:
751 if status == 125:
753 transition = "skip"
752 transition = "skip"
754 elif status == 0:
753 elif status == 0:
755 transition = "good"
754 transition = "good"
756 # status < 0 means process was killed
755 # status < 0 means process was killed
757 elif status == 127:
756 elif status == 127:
758 raise util.Abort(_("failed to execute %s") % command)
757 raise util.Abort(_("failed to execute %s") % command)
759 elif status < 0:
758 elif status < 0:
760 raise util.Abort(_("%s killed") % command)
759 raise util.Abort(_("%s killed") % command)
761 else:
760 else:
762 transition = "bad"
761 transition = "bad"
763 ctx = scmutil.revsingle(repo, rev, node)
762 ctx = scmutil.revsingle(repo, rev, node)
764 rev = None # clear for future iterations
763 rev = None # clear for future iterations
765 state[transition].append(ctx.node())
764 state[transition].append(ctx.node())
766 ui.status(_('changeset %d:%s: %s\n') % (ctx, ctx, transition))
765 ui.status(_('changeset %d:%s: %s\n') % (ctx, ctx, transition))
767 check_state(state, interactive=False)
766 check_state(state, interactive=False)
768 # bisect
767 # bisect
769 nodes, changesets, bgood = hbisect.bisect(repo.changelog, state)
768 nodes, changesets, bgood = hbisect.bisect(repo.changelog, state)
770 # update to next check
769 # update to next check
771 node = nodes[0]
770 node = nodes[0]
772 if not noupdate:
771 if not noupdate:
773 cmdutil.bailifchanged(repo)
772 cmdutil.bailifchanged(repo)
774 hg.clean(repo, node, show_stats=False)
773 hg.clean(repo, node, show_stats=False)
775 finally:
774 finally:
776 state['current'] = [node]
775 state['current'] = [node]
777 hbisect.save_state(repo, state)
776 hbisect.save_state(repo, state)
778 print_result(nodes, bgood)
777 print_result(nodes, bgood)
779 return
778 return
780
779
781 # update state
780 # update state
782
781
783 if rev:
782 if rev:
784 nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])]
783 nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])]
785 else:
784 else:
786 nodes = [repo.lookup('.')]
785 nodes = [repo.lookup('.')]
787
786
788 if good or bad or skip:
787 if good or bad or skip:
789 if good:
788 if good:
790 state['good'] += nodes
789 state['good'] += nodes
791 elif bad:
790 elif bad:
792 state['bad'] += nodes
791 state['bad'] += nodes
793 elif skip:
792 elif skip:
794 state['skip'] += nodes
793 state['skip'] += nodes
795 hbisect.save_state(repo, state)
794 hbisect.save_state(repo, state)
796
795
797 if not check_state(state):
796 if not check_state(state):
798 return
797 return
799
798
800 # actually bisect
799 # actually bisect
801 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
800 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
802 if extend:
801 if extend:
803 if not changesets:
802 if not changesets:
804 extendnode = extendbisectrange(nodes, good)
803 extendnode = extendbisectrange(nodes, good)
805 if extendnode is not None:
804 if extendnode is not None:
806 ui.write(_("Extending search to changeset %d:%s\n")
805 ui.write(_("Extending search to changeset %d:%s\n")
807 % (extendnode.rev(), extendnode))
806 % (extendnode.rev(), extendnode))
808 state['current'] = [extendnode.node()]
807 state['current'] = [extendnode.node()]
809 hbisect.save_state(repo, state)
808 hbisect.save_state(repo, state)
810 if noupdate:
809 if noupdate:
811 return
810 return
812 cmdutil.bailifchanged(repo)
811 cmdutil.bailifchanged(repo)
813 return hg.clean(repo, extendnode.node())
812 return hg.clean(repo, extendnode.node())
814 raise util.Abort(_("nothing to extend"))
813 raise util.Abort(_("nothing to extend"))
815
814
816 if changesets == 0:
815 if changesets == 0:
817 print_result(nodes, good)
816 print_result(nodes, good)
818 else:
817 else:
819 assert len(nodes) == 1 # only a single node can be tested next
818 assert len(nodes) == 1 # only a single node can be tested next
820 node = nodes[0]
819 node = nodes[0]
821 # compute the approximate number of remaining tests
820 # compute the approximate number of remaining tests
822 tests, size = 0, 2
821 tests, size = 0, 2
823 while size <= changesets:
822 while size <= changesets:
824 tests, size = tests + 1, size * 2
823 tests, size = tests + 1, size * 2
825 rev = repo.changelog.rev(node)
824 rev = repo.changelog.rev(node)
826 ui.write(_("Testing changeset %d:%s "
825 ui.write(_("Testing changeset %d:%s "
827 "(%d changesets remaining, ~%d tests)\n")
826 "(%d changesets remaining, ~%d tests)\n")
828 % (rev, short(node), changesets, tests))
827 % (rev, short(node), changesets, tests))
829 state['current'] = [node]
828 state['current'] = [node]
830 hbisect.save_state(repo, state)
829 hbisect.save_state(repo, state)
831 if not noupdate:
830 if not noupdate:
832 cmdutil.bailifchanged(repo)
831 cmdutil.bailifchanged(repo)
833 return hg.clean(repo, node)
832 return hg.clean(repo, node)
834
833
835 @command('bookmarks|bookmark',
834 @command('bookmarks|bookmark',
836 [('f', 'force', False, _('force')),
835 [('f', 'force', False, _('force')),
837 ('r', 'rev', '', _('revision'), _('REV')),
836 ('r', 'rev', '', _('revision'), _('REV')),
838 ('d', 'delete', False, _('delete a given bookmark')),
837 ('d', 'delete', False, _('delete a given bookmark')),
839 ('m', 'rename', '', _('rename a given bookmark'), _('NAME')),
838 ('m', 'rename', '', _('rename a given bookmark'), _('NAME')),
840 ('i', 'inactive', False, _('mark a bookmark inactive')),
839 ('i', 'inactive', False, _('mark a bookmark inactive')),
841 ] + formatteropts,
840 ] + formatteropts,
842 _('hg bookmarks [OPTIONS]... [NAME]...'))
841 _('hg bookmarks [OPTIONS]... [NAME]...'))
843 def bookmark(ui, repo, *names, **opts):
842 def bookmark(ui, repo, *names, **opts):
844 '''create a new bookmark or list existing bookmarks
843 '''create a new bookmark or list existing bookmarks
845
844
846 Bookmarks are labels on changesets to help track lines of development.
845 Bookmarks are labels on changesets to help track lines of development.
847 Bookmarks are unversioned and can be moved, renamed and deleted.
846 Bookmarks are unversioned and can be moved, renamed and deleted.
848 Deleting or moving a bookmark has no effect on the associated changesets.
847 Deleting or moving a bookmark has no effect on the associated changesets.
849
848
850 Creating or updating to a bookmark causes it to be marked as 'active'.
849 Creating or updating to a bookmark causes it to be marked as 'active'.
851 The active bookmark is indicated with a '*'.
850 The active bookmark is indicated with a '*'.
852 When a commit is made, the active bookmark will advance to the new commit.
851 When a commit is made, the active bookmark will advance to the new commit.
853 A plain :hg:`update` will also advance an active bookmark, if possible.
852 A plain :hg:`update` will also advance an active bookmark, if possible.
854 Updating away from a bookmark will cause it to be deactivated.
853 Updating away from a bookmark will cause it to be deactivated.
855
854
856 Bookmarks can be pushed and pulled between repositories (see
855 Bookmarks can be pushed and pulled between repositories (see
857 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
856 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
858 diverged, a new 'divergent bookmark' of the form 'name@path' will
857 diverged, a new 'divergent bookmark' of the form 'name@path' will
859 be created. Using :hg:`merge` will resolve the divergence.
858 be created. Using :hg:`merge` will resolve the divergence.
860
859
861 A bookmark named '@' has the special property that :hg:`clone` will
860 A bookmark named '@' has the special property that :hg:`clone` will
862 check it out by default if it exists.
861 check it out by default if it exists.
863
862
864 .. container:: verbose
863 .. container:: verbose
865
864
866 Examples:
865 Examples:
867
866
868 - create an active bookmark for a new line of development::
867 - create an active bookmark for a new line of development::
869
868
870 hg book new-feature
869 hg book new-feature
871
870
872 - create an inactive bookmark as a place marker::
871 - create an inactive bookmark as a place marker::
873
872
874 hg book -i reviewed
873 hg book -i reviewed
875
874
876 - create an inactive bookmark on another changeset::
875 - create an inactive bookmark on another changeset::
877
876
878 hg book -r .^ tested
877 hg book -r .^ tested
879
878
880 - move the '@' bookmark from another branch::
879 - move the '@' bookmark from another branch::
881
880
882 hg book -f @
881 hg book -f @
883 '''
882 '''
884 force = opts.get('force')
883 force = opts.get('force')
885 rev = opts.get('rev')
884 rev = opts.get('rev')
886 delete = opts.get('delete')
885 delete = opts.get('delete')
887 rename = opts.get('rename')
886 rename = opts.get('rename')
888 inactive = opts.get('inactive')
887 inactive = opts.get('inactive')
889
888
890 def checkformat(mark):
889 def checkformat(mark):
891 mark = mark.strip()
890 mark = mark.strip()
892 if not mark:
891 if not mark:
893 raise util.Abort(_("bookmark names cannot consist entirely of "
892 raise util.Abort(_("bookmark names cannot consist entirely of "
894 "whitespace"))
893 "whitespace"))
895 scmutil.checknewlabel(repo, mark, 'bookmark')
894 scmutil.checknewlabel(repo, mark, 'bookmark')
896 return mark
895 return mark
897
896
898 def checkconflict(repo, mark, cur, force=False, target=None):
897 def checkconflict(repo, mark, cur, force=False, target=None):
899 if mark in marks and not force:
898 if mark in marks and not force:
900 if target:
899 if target:
901 if marks[mark] == target and target == cur:
900 if marks[mark] == target and target == cur:
902 # re-activating a bookmark
901 # re-activating a bookmark
903 return
902 return
904 anc = repo.changelog.ancestors([repo[target].rev()])
903 anc = repo.changelog.ancestors([repo[target].rev()])
905 bmctx = repo[marks[mark]]
904 bmctx = repo[marks[mark]]
906 divs = [repo[b].node() for b in marks
905 divs = [repo[b].node() for b in marks
907 if b.split('@', 1)[0] == mark.split('@', 1)[0]]
906 if b.split('@', 1)[0] == mark.split('@', 1)[0]]
908
907
909 # allow resolving a single divergent bookmark even if moving
908 # allow resolving a single divergent bookmark even if moving
910 # the bookmark across branches when a revision is specified
909 # the bookmark across branches when a revision is specified
911 # that contains a divergent bookmark
910 # that contains a divergent bookmark
912 if bmctx.rev() not in anc and target in divs:
911 if bmctx.rev() not in anc and target in divs:
913 bookmarks.deletedivergent(repo, [target], mark)
912 bookmarks.deletedivergent(repo, [target], mark)
914 return
913 return
915
914
916 deletefrom = [b for b in divs
915 deletefrom = [b for b in divs
917 if repo[b].rev() in anc or b == target]
916 if repo[b].rev() in anc or b == target]
918 bookmarks.deletedivergent(repo, deletefrom, mark)
917 bookmarks.deletedivergent(repo, deletefrom, mark)
919 if bookmarks.validdest(repo, bmctx, repo[target]):
918 if bookmarks.validdest(repo, bmctx, repo[target]):
920 ui.status(_("moving bookmark '%s' forward from %s\n") %
919 ui.status(_("moving bookmark '%s' forward from %s\n") %
921 (mark, short(bmctx.node())))
920 (mark, short(bmctx.node())))
922 return
921 return
923 raise util.Abort(_("bookmark '%s' already exists "
922 raise util.Abort(_("bookmark '%s' already exists "
924 "(use -f to force)") % mark)
923 "(use -f to force)") % mark)
925 if ((mark in repo.branchmap() or mark == repo.dirstate.branch())
924 if ((mark in repo.branchmap() or mark == repo.dirstate.branch())
926 and not force):
925 and not force):
927 raise util.Abort(
926 raise util.Abort(
928 _("a bookmark cannot have the name of an existing branch"))
927 _("a bookmark cannot have the name of an existing branch"))
929
928
930 if delete and rename:
929 if delete and rename:
931 raise util.Abort(_("--delete and --rename are incompatible"))
930 raise util.Abort(_("--delete and --rename are incompatible"))
932 if delete and rev:
931 if delete and rev:
933 raise util.Abort(_("--rev is incompatible with --delete"))
932 raise util.Abort(_("--rev is incompatible with --delete"))
934 if rename and rev:
933 if rename and rev:
935 raise util.Abort(_("--rev is incompatible with --rename"))
934 raise util.Abort(_("--rev is incompatible with --rename"))
936 if not names and (delete or rev):
935 if not names and (delete or rev):
937 raise util.Abort(_("bookmark name required"))
936 raise util.Abort(_("bookmark name required"))
938
937
939 if delete or rename or names or inactive:
938 if delete or rename or names or inactive:
940 wlock = repo.wlock()
939 wlock = repo.wlock()
941 try:
940 try:
942 cur = repo.changectx('.').node()
941 cur = repo.changectx('.').node()
943 marks = repo._bookmarks
942 marks = repo._bookmarks
944 if delete:
943 if delete:
945 for mark in names:
944 for mark in names:
946 if mark not in marks:
945 if mark not in marks:
947 raise util.Abort(_("bookmark '%s' does not exist") %
946 raise util.Abort(_("bookmark '%s' does not exist") %
948 mark)
947 mark)
949 if mark == repo._bookmarkcurrent:
948 if mark == repo._bookmarkcurrent:
950 bookmarks.unsetcurrent(repo)
949 bookmarks.unsetcurrent(repo)
951 del marks[mark]
950 del marks[mark]
952 marks.write()
951 marks.write()
953
952
954 elif rename:
953 elif rename:
955 if not names:
954 if not names:
956 raise util.Abort(_("new bookmark name required"))
955 raise util.Abort(_("new bookmark name required"))
957 elif len(names) > 1:
956 elif len(names) > 1:
958 raise util.Abort(_("only one new bookmark name allowed"))
957 raise util.Abort(_("only one new bookmark name allowed"))
959 mark = checkformat(names[0])
958 mark = checkformat(names[0])
960 if rename not in marks:
959 if rename not in marks:
961 raise util.Abort(_("bookmark '%s' does not exist") % rename)
960 raise util.Abort(_("bookmark '%s' does not exist") % rename)
962 checkconflict(repo, mark, cur, force)
961 checkconflict(repo, mark, cur, force)
963 marks[mark] = marks[rename]
962 marks[mark] = marks[rename]
964 if repo._bookmarkcurrent == rename and not inactive:
963 if repo._bookmarkcurrent == rename and not inactive:
965 bookmarks.setcurrent(repo, mark)
964 bookmarks.setcurrent(repo, mark)
966 del marks[rename]
965 del marks[rename]
967 marks.write()
966 marks.write()
968
967
969 elif names:
968 elif names:
970 newact = None
969 newact = None
971 for mark in names:
970 for mark in names:
972 mark = checkformat(mark)
971 mark = checkformat(mark)
973 if newact is None:
972 if newact is None:
974 newact = mark
973 newact = mark
975 if inactive and mark == repo._bookmarkcurrent:
974 if inactive and mark == repo._bookmarkcurrent:
976 bookmarks.unsetcurrent(repo)
975 bookmarks.unsetcurrent(repo)
977 return
976 return
978 tgt = cur
977 tgt = cur
979 if rev:
978 if rev:
980 tgt = scmutil.revsingle(repo, rev).node()
979 tgt = scmutil.revsingle(repo, rev).node()
981 checkconflict(repo, mark, cur, force, tgt)
980 checkconflict(repo, mark, cur, force, tgt)
982 marks[mark] = tgt
981 marks[mark] = tgt
983 if not inactive and cur == marks[newact] and not rev:
982 if not inactive and cur == marks[newact] and not rev:
984 bookmarks.setcurrent(repo, newact)
983 bookmarks.setcurrent(repo, newact)
985 elif cur != tgt and newact == repo._bookmarkcurrent:
984 elif cur != tgt and newact == repo._bookmarkcurrent:
986 bookmarks.unsetcurrent(repo)
985 bookmarks.unsetcurrent(repo)
987 marks.write()
986 marks.write()
988
987
989 elif inactive:
988 elif inactive:
990 if len(marks) == 0:
989 if len(marks) == 0:
991 ui.status(_("no bookmarks set\n"))
990 ui.status(_("no bookmarks set\n"))
992 elif not repo._bookmarkcurrent:
991 elif not repo._bookmarkcurrent:
993 ui.status(_("no active bookmark\n"))
992 ui.status(_("no active bookmark\n"))
994 else:
993 else:
995 bookmarks.unsetcurrent(repo)
994 bookmarks.unsetcurrent(repo)
996 finally:
995 finally:
997 wlock.release()
996 wlock.release()
998 else: # show bookmarks
997 else: # show bookmarks
999 fm = ui.formatter('bookmarks', opts)
998 fm = ui.formatter('bookmarks', opts)
1000 hexfn = fm.hexfunc
999 hexfn = fm.hexfunc
1001 marks = repo._bookmarks
1000 marks = repo._bookmarks
1002 if len(marks) == 0 and not fm:
1001 if len(marks) == 0 and not fm:
1003 ui.status(_("no bookmarks set\n"))
1002 ui.status(_("no bookmarks set\n"))
1004 for bmark, n in sorted(marks.iteritems()):
1003 for bmark, n in sorted(marks.iteritems()):
1005 current = repo._bookmarkcurrent
1004 current = repo._bookmarkcurrent
1006 if bmark == current:
1005 if bmark == current:
1007 prefix, label = '*', 'bookmarks.current'
1006 prefix, label = '*', 'bookmarks.current'
1008 else:
1007 else:
1009 prefix, label = ' ', ''
1008 prefix, label = ' ', ''
1010
1009
1011 fm.startitem()
1010 fm.startitem()
1012 if not ui.quiet:
1011 if not ui.quiet:
1013 fm.plain(' %s ' % prefix, label=label)
1012 fm.plain(' %s ' % prefix, label=label)
1014 fm.write('bookmark', '%s', bmark, label=label)
1013 fm.write('bookmark', '%s', bmark, label=label)
1015 pad = " " * (25 - encoding.colwidth(bmark))
1014 pad = " " * (25 - encoding.colwidth(bmark))
1016 fm.condwrite(not ui.quiet, 'rev node', pad + ' %d:%s',
1015 fm.condwrite(not ui.quiet, 'rev node', pad + ' %d:%s',
1017 repo.changelog.rev(n), hexfn(n), label=label)
1016 repo.changelog.rev(n), hexfn(n), label=label)
1018 fm.data(active=(bmark == current))
1017 fm.data(active=(bmark == current))
1019 fm.plain('\n')
1018 fm.plain('\n')
1020 fm.end()
1019 fm.end()
1021
1020
1022 @command('branch',
1021 @command('branch',
1023 [('f', 'force', None,
1022 [('f', 'force', None,
1024 _('set branch name even if it shadows an existing branch')),
1023 _('set branch name even if it shadows an existing branch')),
1025 ('C', 'clean', None, _('reset branch name to parent branch name'))],
1024 ('C', 'clean', None, _('reset branch name to parent branch name'))],
1026 _('[-fC] [NAME]'))
1025 _('[-fC] [NAME]'))
1027 def branch(ui, repo, label=None, **opts):
1026 def branch(ui, repo, label=None, **opts):
1028 """set or show the current branch name
1027 """set or show the current branch name
1029
1028
1030 .. note::
1029 .. note::
1031
1030
1032 Branch names are permanent and global. Use :hg:`bookmark` to create a
1031 Branch names are permanent and global. Use :hg:`bookmark` to create a
1033 light-weight bookmark instead. See :hg:`help glossary` for more
1032 light-weight bookmark instead. See :hg:`help glossary` for more
1034 information about named branches and bookmarks.
1033 information about named branches and bookmarks.
1035
1034
1036 With no argument, show the current branch name. With one argument,
1035 With no argument, show the current branch name. With one argument,
1037 set the working directory branch name (the branch will not exist
1036 set the working directory branch name (the branch will not exist
1038 in the repository until the next commit). Standard practice
1037 in the repository until the next commit). Standard practice
1039 recommends that primary development take place on the 'default'
1038 recommends that primary development take place on the 'default'
1040 branch.
1039 branch.
1041
1040
1042 Unless -f/--force is specified, branch will not let you set a
1041 Unless -f/--force is specified, branch will not let you set a
1043 branch name that already exists.
1042 branch name that already exists.
1044
1043
1045 Use -C/--clean to reset the working directory branch to that of
1044 Use -C/--clean to reset the working directory branch to that of
1046 the parent of the working directory, negating a previous branch
1045 the parent of the working directory, negating a previous branch
1047 change.
1046 change.
1048
1047
1049 Use the command :hg:`update` to switch to an existing branch. Use
1048 Use the command :hg:`update` to switch to an existing branch. Use
1050 :hg:`commit --close-branch` to mark this branch as closed.
1049 :hg:`commit --close-branch` to mark this branch as closed.
1051
1050
1052 Returns 0 on success.
1051 Returns 0 on success.
1053 """
1052 """
1054 if label:
1053 if label:
1055 label = label.strip()
1054 label = label.strip()
1056
1055
1057 if not opts.get('clean') and not label:
1056 if not opts.get('clean') and not label:
1058 ui.write("%s\n" % repo.dirstate.branch())
1057 ui.write("%s\n" % repo.dirstate.branch())
1059 return
1058 return
1060
1059
1061 wlock = repo.wlock()
1060 wlock = repo.wlock()
1062 try:
1061 try:
1063 if opts.get('clean'):
1062 if opts.get('clean'):
1064 label = repo[None].p1().branch()
1063 label = repo[None].p1().branch()
1065 repo.dirstate.setbranch(label)
1064 repo.dirstate.setbranch(label)
1066 ui.status(_('reset working directory to branch %s\n') % label)
1065 ui.status(_('reset working directory to branch %s\n') % label)
1067 elif label:
1066 elif label:
1068 if not opts.get('force') and label in repo.branchmap():
1067 if not opts.get('force') and label in repo.branchmap():
1069 if label not in [p.branch() for p in repo.parents()]:
1068 if label not in [p.branch() for p in repo.parents()]:
1070 raise util.Abort(_('a branch of the same name already'
1069 raise util.Abort(_('a branch of the same name already'
1071 ' exists'),
1070 ' exists'),
1072 # i18n: "it" refers to an existing branch
1071 # i18n: "it" refers to an existing branch
1073 hint=_("use 'hg update' to switch to it"))
1072 hint=_("use 'hg update' to switch to it"))
1074 scmutil.checknewlabel(repo, label, 'branch')
1073 scmutil.checknewlabel(repo, label, 'branch')
1075 repo.dirstate.setbranch(label)
1074 repo.dirstate.setbranch(label)
1076 ui.status(_('marked working directory as branch %s\n') % label)
1075 ui.status(_('marked working directory as branch %s\n') % label)
1077 ui.status(_('(branches are permanent and global, '
1076 ui.status(_('(branches are permanent and global, '
1078 'did you want a bookmark?)\n'))
1077 'did you want a bookmark?)\n'))
1079 finally:
1078 finally:
1080 wlock.release()
1079 wlock.release()
1081
1080
1082 @command('branches',
1081 @command('branches',
1083 [('a', 'active', False,
1082 [('a', 'active', False,
1084 _('show only branches that have unmerged heads (DEPRECATED)')),
1083 _('show only branches that have unmerged heads (DEPRECATED)')),
1085 ('c', 'closed', False, _('show normal and closed branches')),
1084 ('c', 'closed', False, _('show normal and closed branches')),
1086 ] + formatteropts,
1085 ] + formatteropts,
1087 _('[-ac]'))
1086 _('[-ac]'))
1088 def branches(ui, repo, active=False, closed=False, **opts):
1087 def branches(ui, repo, active=False, closed=False, **opts):
1089 """list repository named branches
1088 """list repository named branches
1090
1089
1091 List the repository's named branches, indicating which ones are
1090 List the repository's named branches, indicating which ones are
1092 inactive. If -c/--closed is specified, also list branches which have
1091 inactive. If -c/--closed is specified, also list branches which have
1093 been marked closed (see :hg:`commit --close-branch`).
1092 been marked closed (see :hg:`commit --close-branch`).
1094
1093
1095 Use the command :hg:`update` to switch to an existing branch.
1094 Use the command :hg:`update` to switch to an existing branch.
1096
1095
1097 Returns 0.
1096 Returns 0.
1098 """
1097 """
1099
1098
1100 fm = ui.formatter('branches', opts)
1099 fm = ui.formatter('branches', opts)
1101 hexfunc = fm.hexfunc
1100 hexfunc = fm.hexfunc
1102
1101
1103 allheads = set(repo.heads())
1102 allheads = set(repo.heads())
1104 branches = []
1103 branches = []
1105 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1104 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1106 isactive = not isclosed and bool(set(heads) & allheads)
1105 isactive = not isclosed and bool(set(heads) & allheads)
1107 branches.append((tag, repo[tip], isactive, not isclosed))
1106 branches.append((tag, repo[tip], isactive, not isclosed))
1108 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
1107 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
1109 reverse=True)
1108 reverse=True)
1110
1109
1111 for tag, ctx, isactive, isopen in branches:
1110 for tag, ctx, isactive, isopen in branches:
1112 if active and not isactive:
1111 if active and not isactive:
1113 continue
1112 continue
1114 if isactive:
1113 if isactive:
1115 label = 'branches.active'
1114 label = 'branches.active'
1116 notice = ''
1115 notice = ''
1117 elif not isopen:
1116 elif not isopen:
1118 if not closed:
1117 if not closed:
1119 continue
1118 continue
1120 label = 'branches.closed'
1119 label = 'branches.closed'
1121 notice = _(' (closed)')
1120 notice = _(' (closed)')
1122 else:
1121 else:
1123 label = 'branches.inactive'
1122 label = 'branches.inactive'
1124 notice = _(' (inactive)')
1123 notice = _(' (inactive)')
1125 current = (tag == repo.dirstate.branch())
1124 current = (tag == repo.dirstate.branch())
1126 if current:
1125 if current:
1127 label = 'branches.current'
1126 label = 'branches.current'
1128
1127
1129 fm.startitem()
1128 fm.startitem()
1130 fm.write('branch', '%s', tag, label=label)
1129 fm.write('branch', '%s', tag, label=label)
1131 rev = ctx.rev()
1130 rev = ctx.rev()
1132 padsize = max(31 - len(str(rev)) - encoding.colwidth(tag), 0)
1131 padsize = max(31 - len(str(rev)) - encoding.colwidth(tag), 0)
1133 fmt = ' ' * padsize + ' %d:%s'
1132 fmt = ' ' * padsize + ' %d:%s'
1134 fm.condwrite(not ui.quiet, 'rev node', fmt, rev, hexfunc(ctx.node()),
1133 fm.condwrite(not ui.quiet, 'rev node', fmt, rev, hexfunc(ctx.node()),
1135 label='log.changeset changeset.%s' % ctx.phasestr())
1134 label='log.changeset changeset.%s' % ctx.phasestr())
1136 fm.data(active=isactive, closed=not isopen, current=current)
1135 fm.data(active=isactive, closed=not isopen, current=current)
1137 if not ui.quiet:
1136 if not ui.quiet:
1138 fm.plain(notice)
1137 fm.plain(notice)
1139 fm.plain('\n')
1138 fm.plain('\n')
1140 fm.end()
1139 fm.end()
1141
1140
1142 @command('bundle',
1141 @command('bundle',
1143 [('f', 'force', None, _('run even when the destination is unrelated')),
1142 [('f', 'force', None, _('run even when the destination is unrelated')),
1144 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1143 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1145 _('REV')),
1144 _('REV')),
1146 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1145 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1147 _('BRANCH')),
1146 _('BRANCH')),
1148 ('', 'base', [],
1147 ('', 'base', [],
1149 _('a base changeset assumed to be available at the destination'),
1148 _('a base changeset assumed to be available at the destination'),
1150 _('REV')),
1149 _('REV')),
1151 ('a', 'all', None, _('bundle all changesets in the repository')),
1150 ('a', 'all', None, _('bundle all changesets in the repository')),
1152 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1151 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1153 ] + remoteopts,
1152 ] + remoteopts,
1154 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
1153 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
1155 def bundle(ui, repo, fname, dest=None, **opts):
1154 def bundle(ui, repo, fname, dest=None, **opts):
1156 """create a changegroup file
1155 """create a changegroup file
1157
1156
1158 Generate a compressed changegroup file collecting changesets not
1157 Generate a compressed changegroup file collecting changesets not
1159 known to be in another repository.
1158 known to be in another repository.
1160
1159
1161 If you omit the destination repository, then hg assumes the
1160 If you omit the destination repository, then hg assumes the
1162 destination will have all the nodes you specify with --base
1161 destination will have all the nodes you specify with --base
1163 parameters. To create a bundle containing all changesets, use
1162 parameters. To create a bundle containing all changesets, use
1164 -a/--all (or --base null).
1163 -a/--all (or --base null).
1165
1164
1166 You can change compression method with the -t/--type option.
1165 You can change compression method with the -t/--type option.
1167 The available compression methods are: none, bzip2, and
1166 The available compression methods are: none, bzip2, and
1168 gzip (by default, bundles are compressed using bzip2).
1167 gzip (by default, bundles are compressed using bzip2).
1169
1168
1170 The bundle file can then be transferred using conventional means
1169 The bundle file can then be transferred using conventional means
1171 and applied to another repository with the unbundle or pull
1170 and applied to another repository with the unbundle or pull
1172 command. This is useful when direct push and pull are not
1171 command. This is useful when direct push and pull are not
1173 available or when exporting an entire repository is undesirable.
1172 available or when exporting an entire repository is undesirable.
1174
1173
1175 Applying bundles preserves all changeset contents including
1174 Applying bundles preserves all changeset contents including
1176 permissions, copy/rename information, and revision history.
1175 permissions, copy/rename information, and revision history.
1177
1176
1178 Returns 0 on success, 1 if no changes found.
1177 Returns 0 on success, 1 if no changes found.
1179 """
1178 """
1180 revs = None
1179 revs = None
1181 if 'rev' in opts:
1180 if 'rev' in opts:
1182 revs = scmutil.revrange(repo, opts['rev'])
1181 revs = scmutil.revrange(repo, opts['rev'])
1183
1182
1184 bundletype = opts.get('type', 'bzip2').lower()
1183 bundletype = opts.get('type', 'bzip2').lower()
1185 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
1184 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
1186 bundletype = btypes.get(bundletype)
1185 bundletype = btypes.get(bundletype)
1187 if bundletype not in changegroup.bundletypes:
1186 if bundletype not in changegroup.bundletypes:
1188 raise util.Abort(_('unknown bundle type specified with --type'))
1187 raise util.Abort(_('unknown bundle type specified with --type'))
1189
1188
1190 if opts.get('all'):
1189 if opts.get('all'):
1191 base = ['null']
1190 base = ['null']
1192 else:
1191 else:
1193 base = scmutil.revrange(repo, opts.get('base'))
1192 base = scmutil.revrange(repo, opts.get('base'))
1194 # TODO: get desired bundlecaps from command line.
1193 # TODO: get desired bundlecaps from command line.
1195 bundlecaps = None
1194 bundlecaps = None
1196 if base:
1195 if base:
1197 if dest:
1196 if dest:
1198 raise util.Abort(_("--base is incompatible with specifying "
1197 raise util.Abort(_("--base is incompatible with specifying "
1199 "a destination"))
1198 "a destination"))
1200 common = [repo.lookup(rev) for rev in base]
1199 common = [repo.lookup(rev) for rev in base]
1201 heads = revs and map(repo.lookup, revs) or revs
1200 heads = revs and map(repo.lookup, revs) or revs
1202 cg = changegroup.getchangegroup(repo, 'bundle', heads=heads,
1201 cg = changegroup.getchangegroup(repo, 'bundle', heads=heads,
1203 common=common, bundlecaps=bundlecaps)
1202 common=common, bundlecaps=bundlecaps)
1204 outgoing = None
1203 outgoing = None
1205 else:
1204 else:
1206 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1205 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1207 dest, branches = hg.parseurl(dest, opts.get('branch'))
1206 dest, branches = hg.parseurl(dest, opts.get('branch'))
1208 other = hg.peer(repo, opts, dest)
1207 other = hg.peer(repo, opts, dest)
1209 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1208 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1210 heads = revs and map(repo.lookup, revs) or revs
1209 heads = revs and map(repo.lookup, revs) or revs
1211 outgoing = discovery.findcommonoutgoing(repo, other,
1210 outgoing = discovery.findcommonoutgoing(repo, other,
1212 onlyheads=heads,
1211 onlyheads=heads,
1213 force=opts.get('force'),
1212 force=opts.get('force'),
1214 portable=True)
1213 portable=True)
1215 cg = changegroup.getlocalchangegroup(repo, 'bundle', outgoing,
1214 cg = changegroup.getlocalchangegroup(repo, 'bundle', outgoing,
1216 bundlecaps)
1215 bundlecaps)
1217 if not cg:
1216 if not cg:
1218 scmutil.nochangesfound(ui, repo, outgoing and outgoing.excluded)
1217 scmutil.nochangesfound(ui, repo, outgoing and outgoing.excluded)
1219 return 1
1218 return 1
1220
1219
1221 changegroup.writebundle(cg, fname, bundletype)
1220 changegroup.writebundle(cg, fname, bundletype)
1222
1221
1223 @command('cat',
1222 @command('cat',
1224 [('o', 'output', '',
1223 [('o', 'output', '',
1225 _('print output to file with formatted name'), _('FORMAT')),
1224 _('print output to file with formatted name'), _('FORMAT')),
1226 ('r', 'rev', '', _('print the given revision'), _('REV')),
1225 ('r', 'rev', '', _('print the given revision'), _('REV')),
1227 ('', 'decode', None, _('apply any matching decode filter')),
1226 ('', 'decode', None, _('apply any matching decode filter')),
1228 ] + walkopts,
1227 ] + walkopts,
1229 _('[OPTION]... FILE...'),
1228 _('[OPTION]... FILE...'),
1230 inferrepo=True)
1229 inferrepo=True)
1231 def cat(ui, repo, file1, *pats, **opts):
1230 def cat(ui, repo, file1, *pats, **opts):
1232 """output the current or given revision of files
1231 """output the current or given revision of files
1233
1232
1234 Print the specified files as they were at the given revision. If
1233 Print the specified files as they were at the given revision. If
1235 no revision is given, the parent of the working directory is used.
1234 no revision is given, the parent of the working directory is used.
1236
1235
1237 Output may be to a file, in which case the name of the file is
1236 Output may be to a file, in which case the name of the file is
1238 given using a format string. The formatting rules as follows:
1237 given using a format string. The formatting rules as follows:
1239
1238
1240 :``%%``: literal "%" character
1239 :``%%``: literal "%" character
1241 :``%s``: basename of file being printed
1240 :``%s``: basename of file being printed
1242 :``%d``: dirname of file being printed, or '.' if in repository root
1241 :``%d``: dirname of file being printed, or '.' if in repository root
1243 :``%p``: root-relative path name of file being printed
1242 :``%p``: root-relative path name of file being printed
1244 :``%H``: changeset hash (40 hexadecimal digits)
1243 :``%H``: changeset hash (40 hexadecimal digits)
1245 :``%R``: changeset revision number
1244 :``%R``: changeset revision number
1246 :``%h``: short-form changeset hash (12 hexadecimal digits)
1245 :``%h``: short-form changeset hash (12 hexadecimal digits)
1247 :``%r``: zero-padded changeset revision number
1246 :``%r``: zero-padded changeset revision number
1248 :``%b``: basename of the exporting repository
1247 :``%b``: basename of the exporting repository
1249
1248
1250 Returns 0 on success.
1249 Returns 0 on success.
1251 """
1250 """
1252 ctx = scmutil.revsingle(repo, opts.get('rev'))
1251 ctx = scmutil.revsingle(repo, opts.get('rev'))
1253 m = scmutil.match(ctx, (file1,) + pats, opts)
1252 m = scmutil.match(ctx, (file1,) + pats, opts)
1254
1253
1255 return cmdutil.cat(ui, repo, ctx, m, '', **opts)
1254 return cmdutil.cat(ui, repo, ctx, m, '', **opts)
1256
1255
1257 @command('^clone',
1256 @command('^clone',
1258 [('U', 'noupdate', None,
1257 [('U', 'noupdate', None,
1259 _('the clone will include an empty working copy (only a repository)')),
1258 _('the clone will include an empty working copy (only a repository)')),
1260 ('u', 'updaterev', '', _('revision, tag or branch to check out'), _('REV')),
1259 ('u', 'updaterev', '', _('revision, tag or branch to check out'), _('REV')),
1261 ('r', 'rev', [], _('include the specified changeset'), _('REV')),
1260 ('r', 'rev', [], _('include the specified changeset'), _('REV')),
1262 ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')),
1261 ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')),
1263 ('', 'pull', None, _('use pull protocol to copy metadata')),
1262 ('', 'pull', None, _('use pull protocol to copy metadata')),
1264 ('', 'uncompressed', None, _('use uncompressed transfer (fast over LAN)')),
1263 ('', 'uncompressed', None, _('use uncompressed transfer (fast over LAN)')),
1265 ] + remoteopts,
1264 ] + remoteopts,
1266 _('[OPTION]... SOURCE [DEST]'),
1265 _('[OPTION]... SOURCE [DEST]'),
1267 norepo=True)
1266 norepo=True)
1268 def clone(ui, source, dest=None, **opts):
1267 def clone(ui, source, dest=None, **opts):
1269 """make a copy of an existing repository
1268 """make a copy of an existing repository
1270
1269
1271 Create a copy of an existing repository in a new directory.
1270 Create a copy of an existing repository in a new directory.
1272
1271
1273 If no destination directory name is specified, it defaults to the
1272 If no destination directory name is specified, it defaults to the
1274 basename of the source.
1273 basename of the source.
1275
1274
1276 The location of the source is added to the new repository's
1275 The location of the source is added to the new repository's
1277 ``.hg/hgrc`` file, as the default to be used for future pulls.
1276 ``.hg/hgrc`` file, as the default to be used for future pulls.
1278
1277
1279 Only local paths and ``ssh://`` URLs are supported as
1278 Only local paths and ``ssh://`` URLs are supported as
1280 destinations. For ``ssh://`` destinations, no working directory or
1279 destinations. For ``ssh://`` destinations, no working directory or
1281 ``.hg/hgrc`` will be created on the remote side.
1280 ``.hg/hgrc`` will be created on the remote side.
1282
1281
1283 To pull only a subset of changesets, specify one or more revisions
1282 To pull only a subset of changesets, specify one or more revisions
1284 identifiers with -r/--rev or branches with -b/--branch. The
1283 identifiers with -r/--rev or branches with -b/--branch. The
1285 resulting clone will contain only the specified changesets and
1284 resulting clone will contain only the specified changesets and
1286 their ancestors. These options (or 'clone src#rev dest') imply
1285 their ancestors. These options (or 'clone src#rev dest') imply
1287 --pull, even for local source repositories. Note that specifying a
1286 --pull, even for local source repositories. Note that specifying a
1288 tag will include the tagged changeset but not the changeset
1287 tag will include the tagged changeset but not the changeset
1289 containing the tag.
1288 containing the tag.
1290
1289
1291 If the source repository has a bookmark called '@' set, that
1290 If the source repository has a bookmark called '@' set, that
1292 revision will be checked out in the new repository by default.
1291 revision will be checked out in the new repository by default.
1293
1292
1294 To check out a particular version, use -u/--update, or
1293 To check out a particular version, use -u/--update, or
1295 -U/--noupdate to create a clone with no working directory.
1294 -U/--noupdate to create a clone with no working directory.
1296
1295
1297 .. container:: verbose
1296 .. container:: verbose
1298
1297
1299 For efficiency, hardlinks are used for cloning whenever the
1298 For efficiency, hardlinks are used for cloning whenever the
1300 source and destination are on the same filesystem (note this
1299 source and destination are on the same filesystem (note this
1301 applies only to the repository data, not to the working
1300 applies only to the repository data, not to the working
1302 directory). Some filesystems, such as AFS, implement hardlinking
1301 directory). Some filesystems, such as AFS, implement hardlinking
1303 incorrectly, but do not report errors. In these cases, use the
1302 incorrectly, but do not report errors. In these cases, use the
1304 --pull option to avoid hardlinking.
1303 --pull option to avoid hardlinking.
1305
1304
1306 In some cases, you can clone repositories and the working
1305 In some cases, you can clone repositories and the working
1307 directory using full hardlinks with ::
1306 directory using full hardlinks with ::
1308
1307
1309 $ cp -al REPO REPOCLONE
1308 $ cp -al REPO REPOCLONE
1310
1309
1311 This is the fastest way to clone, but it is not always safe. The
1310 This is the fastest way to clone, but it is not always safe. The
1312 operation is not atomic (making sure REPO is not modified during
1311 operation is not atomic (making sure REPO is not modified during
1313 the operation is up to you) and you have to make sure your
1312 the operation is up to you) and you have to make sure your
1314 editor breaks hardlinks (Emacs and most Linux Kernel tools do
1313 editor breaks hardlinks (Emacs and most Linux Kernel tools do
1315 so). Also, this is not compatible with certain extensions that
1314 so). Also, this is not compatible with certain extensions that
1316 place their metadata under the .hg directory, such as mq.
1315 place their metadata under the .hg directory, such as mq.
1317
1316
1318 Mercurial will update the working directory to the first applicable
1317 Mercurial will update the working directory to the first applicable
1319 revision from this list:
1318 revision from this list:
1320
1319
1321 a) null if -U or the source repository has no changesets
1320 a) null if -U or the source repository has no changesets
1322 b) if -u . and the source repository is local, the first parent of
1321 b) if -u . and the source repository is local, the first parent of
1323 the source repository's working directory
1322 the source repository's working directory
1324 c) the changeset specified with -u (if a branch name, this means the
1323 c) the changeset specified with -u (if a branch name, this means the
1325 latest head of that branch)
1324 latest head of that branch)
1326 d) the changeset specified with -r
1325 d) the changeset specified with -r
1327 e) the tipmost head specified with -b
1326 e) the tipmost head specified with -b
1328 f) the tipmost head specified with the url#branch source syntax
1327 f) the tipmost head specified with the url#branch source syntax
1329 g) the revision marked with the '@' bookmark, if present
1328 g) the revision marked with the '@' bookmark, if present
1330 h) the tipmost head of the default branch
1329 h) the tipmost head of the default branch
1331 i) tip
1330 i) tip
1332
1331
1333 Examples:
1332 Examples:
1334
1333
1335 - clone a remote repository to a new directory named hg/::
1334 - clone a remote repository to a new directory named hg/::
1336
1335
1337 hg clone http://selenic.com/hg
1336 hg clone http://selenic.com/hg
1338
1337
1339 - create a lightweight local clone::
1338 - create a lightweight local clone::
1340
1339
1341 hg clone project/ project-feature/
1340 hg clone project/ project-feature/
1342
1341
1343 - clone from an absolute path on an ssh server (note double-slash)::
1342 - clone from an absolute path on an ssh server (note double-slash)::
1344
1343
1345 hg clone ssh://user@server//home/projects/alpha/
1344 hg clone ssh://user@server//home/projects/alpha/
1346
1345
1347 - do a high-speed clone over a LAN while checking out a
1346 - do a high-speed clone over a LAN while checking out a
1348 specified version::
1347 specified version::
1349
1348
1350 hg clone --uncompressed http://server/repo -u 1.5
1349 hg clone --uncompressed http://server/repo -u 1.5
1351
1350
1352 - create a repository without changesets after a particular revision::
1351 - create a repository without changesets after a particular revision::
1353
1352
1354 hg clone -r 04e544 experimental/ good/
1353 hg clone -r 04e544 experimental/ good/
1355
1354
1356 - clone (and track) a particular named branch::
1355 - clone (and track) a particular named branch::
1357
1356
1358 hg clone http://selenic.com/hg#stable
1357 hg clone http://selenic.com/hg#stable
1359
1358
1360 See :hg:`help urls` for details on specifying URLs.
1359 See :hg:`help urls` for details on specifying URLs.
1361
1360
1362 Returns 0 on success.
1361 Returns 0 on success.
1363 """
1362 """
1364 if opts.get('noupdate') and opts.get('updaterev'):
1363 if opts.get('noupdate') and opts.get('updaterev'):
1365 raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
1364 raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
1366
1365
1367 r = hg.clone(ui, opts, source, dest,
1366 r = hg.clone(ui, opts, source, dest,
1368 pull=opts.get('pull'),
1367 pull=opts.get('pull'),
1369 stream=opts.get('uncompressed'),
1368 stream=opts.get('uncompressed'),
1370 rev=opts.get('rev'),
1369 rev=opts.get('rev'),
1371 update=opts.get('updaterev') or not opts.get('noupdate'),
1370 update=opts.get('updaterev') or not opts.get('noupdate'),
1372 branch=opts.get('branch'))
1371 branch=opts.get('branch'))
1373
1372
1374 return r is None
1373 return r is None
1375
1374
1376 @command('^commit|ci',
1375 @command('^commit|ci',
1377 [('A', 'addremove', None,
1376 [('A', 'addremove', None,
1378 _('mark new/missing files as added/removed before committing')),
1377 _('mark new/missing files as added/removed before committing')),
1379 ('', 'close-branch', None,
1378 ('', 'close-branch', None,
1380 _('mark a branch as closed, hiding it from the branch list')),
1379 _('mark a branch as closed, hiding it from the branch list')),
1381 ('', 'amend', None, _('amend the parent of the working dir')),
1380 ('', 'amend', None, _('amend the parent of the working dir')),
1382 ('s', 'secret', None, _('use the secret phase for committing')),
1381 ('s', 'secret', None, _('use the secret phase for committing')),
1383 ('e', 'edit', None, _('invoke editor on commit messages')),
1382 ('e', 'edit', None, _('invoke editor on commit messages')),
1384 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1383 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1385 _('[OPTION]... [FILE]...'),
1384 _('[OPTION]... [FILE]...'),
1386 inferrepo=True)
1385 inferrepo=True)
1387 def commit(ui, repo, *pats, **opts):
1386 def commit(ui, repo, *pats, **opts):
1388 """commit the specified files or all outstanding changes
1387 """commit the specified files or all outstanding changes
1389
1388
1390 Commit changes to the given files into the repository. Unlike a
1389 Commit changes to the given files into the repository. Unlike a
1391 centralized SCM, this operation is a local operation. See
1390 centralized SCM, this operation is a local operation. See
1392 :hg:`push` for a way to actively distribute your changes.
1391 :hg:`push` for a way to actively distribute your changes.
1393
1392
1394 If a list of files is omitted, all changes reported by :hg:`status`
1393 If a list of files is omitted, all changes reported by :hg:`status`
1395 will be committed.
1394 will be committed.
1396
1395
1397 If you are committing the result of a merge, do not provide any
1396 If you are committing the result of a merge, do not provide any
1398 filenames or -I/-X filters.
1397 filenames or -I/-X filters.
1399
1398
1400 If no commit message is specified, Mercurial starts your
1399 If no commit message is specified, Mercurial starts your
1401 configured editor where you can enter a message. In case your
1400 configured editor where you can enter a message. In case your
1402 commit fails, you will find a backup of your message in
1401 commit fails, you will find a backup of your message in
1403 ``.hg/last-message.txt``.
1402 ``.hg/last-message.txt``.
1404
1403
1405 The --amend flag can be used to amend the parent of the
1404 The --amend flag can be used to amend the parent of the
1406 working directory with a new commit that contains the changes
1405 working directory with a new commit that contains the changes
1407 in the parent in addition to those currently reported by :hg:`status`,
1406 in the parent in addition to those currently reported by :hg:`status`,
1408 if there are any. The old commit is stored in a backup bundle in
1407 if there are any. The old commit is stored in a backup bundle in
1409 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1408 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1410 on how to restore it).
1409 on how to restore it).
1411
1410
1412 Message, user and date are taken from the amended commit unless
1411 Message, user and date are taken from the amended commit unless
1413 specified. When a message isn't specified on the command line,
1412 specified. When a message isn't specified on the command line,
1414 the editor will open with the message of the amended commit.
1413 the editor will open with the message of the amended commit.
1415
1414
1416 It is not possible to amend public changesets (see :hg:`help phases`)
1415 It is not possible to amend public changesets (see :hg:`help phases`)
1417 or changesets that have children.
1416 or changesets that have children.
1418
1417
1419 See :hg:`help dates` for a list of formats valid for -d/--date.
1418 See :hg:`help dates` for a list of formats valid for -d/--date.
1420
1419
1421 Returns 0 on success, 1 if nothing changed.
1420 Returns 0 on success, 1 if nothing changed.
1422 """
1421 """
1423 if opts.get('subrepos'):
1422 if opts.get('subrepos'):
1424 if opts.get('amend'):
1423 if opts.get('amend'):
1425 raise util.Abort(_('cannot amend with --subrepos'))
1424 raise util.Abort(_('cannot amend with --subrepos'))
1426 # Let --subrepos on the command line override config setting.
1425 # Let --subrepos on the command line override config setting.
1427 ui.setconfig('ui', 'commitsubrepos', True, 'commit')
1426 ui.setconfig('ui', 'commitsubrepos', True, 'commit')
1428
1427
1429 cmdutil.checkunfinished(repo, commit=True)
1428 cmdutil.checkunfinished(repo, commit=True)
1430
1429
1431 branch = repo[None].branch()
1430 branch = repo[None].branch()
1432 bheads = repo.branchheads(branch)
1431 bheads = repo.branchheads(branch)
1433
1432
1434 extra = {}
1433 extra = {}
1435 if opts.get('close_branch'):
1434 if opts.get('close_branch'):
1436 extra['close'] = 1
1435 extra['close'] = 1
1437
1436
1438 if not bheads:
1437 if not bheads:
1439 raise util.Abort(_('can only close branch heads'))
1438 raise util.Abort(_('can only close branch heads'))
1440 elif opts.get('amend'):
1439 elif opts.get('amend'):
1441 if repo.parents()[0].p1().branch() != branch and \
1440 if repo.parents()[0].p1().branch() != branch and \
1442 repo.parents()[0].p2().branch() != branch:
1441 repo.parents()[0].p2().branch() != branch:
1443 raise util.Abort(_('can only close branch heads'))
1442 raise util.Abort(_('can only close branch heads'))
1444
1443
1445 if opts.get('amend'):
1444 if opts.get('amend'):
1446 if ui.configbool('ui', 'commitsubrepos'):
1445 if ui.configbool('ui', 'commitsubrepos'):
1447 raise util.Abort(_('cannot amend with ui.commitsubrepos enabled'))
1446 raise util.Abort(_('cannot amend with ui.commitsubrepos enabled'))
1448
1447
1449 old = repo['.']
1448 old = repo['.']
1450 if not old.mutable():
1449 if not old.mutable():
1451 raise util.Abort(_('cannot amend public changesets'))
1450 raise util.Abort(_('cannot amend public changesets'))
1452 if len(repo[None].parents()) > 1:
1451 if len(repo[None].parents()) > 1:
1453 raise util.Abort(_('cannot amend while merging'))
1452 raise util.Abort(_('cannot amend while merging'))
1454 allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
1453 allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
1455 if not allowunstable and old.children():
1454 if not allowunstable and old.children():
1456 raise util.Abort(_('cannot amend changeset with children'))
1455 raise util.Abort(_('cannot amend changeset with children'))
1457
1456
1458 # commitfunc is used only for temporary amend commit by cmdutil.amend
1457 # commitfunc is used only for temporary amend commit by cmdutil.amend
1459 def commitfunc(ui, repo, message, match, opts):
1458 def commitfunc(ui, repo, message, match, opts):
1460 return repo.commit(message,
1459 return repo.commit(message,
1461 opts.get('user') or old.user(),
1460 opts.get('user') or old.user(),
1462 opts.get('date') or old.date(),
1461 opts.get('date') or old.date(),
1463 match,
1462 match,
1464 extra=extra)
1463 extra=extra)
1465
1464
1466 current = repo._bookmarkcurrent
1465 current = repo._bookmarkcurrent
1467 marks = old.bookmarks()
1466 marks = old.bookmarks()
1468 node = cmdutil.amend(ui, repo, commitfunc, old, extra, pats, opts)
1467 node = cmdutil.amend(ui, repo, commitfunc, old, extra, pats, opts)
1469 if node == old.node():
1468 if node == old.node():
1470 ui.status(_("nothing changed\n"))
1469 ui.status(_("nothing changed\n"))
1471 return 1
1470 return 1
1472 elif marks:
1471 elif marks:
1473 ui.debug('moving bookmarks %r from %s to %s\n' %
1472 ui.debug('moving bookmarks %r from %s to %s\n' %
1474 (marks, old.hex(), hex(node)))
1473 (marks, old.hex(), hex(node)))
1475 newmarks = repo._bookmarks
1474 newmarks = repo._bookmarks
1476 for bm in marks:
1475 for bm in marks:
1477 newmarks[bm] = node
1476 newmarks[bm] = node
1478 if bm == current:
1477 if bm == current:
1479 bookmarks.setcurrent(repo, bm)
1478 bookmarks.setcurrent(repo, bm)
1480 newmarks.write()
1479 newmarks.write()
1481 else:
1480 else:
1482 def commitfunc(ui, repo, message, match, opts):
1481 def commitfunc(ui, repo, message, match, opts):
1483 backup = ui.backupconfig('phases', 'new-commit')
1482 backup = ui.backupconfig('phases', 'new-commit')
1484 baseui = repo.baseui
1483 baseui = repo.baseui
1485 basebackup = baseui.backupconfig('phases', 'new-commit')
1484 basebackup = baseui.backupconfig('phases', 'new-commit')
1486 try:
1485 try:
1487 if opts.get('secret'):
1486 if opts.get('secret'):
1488 ui.setconfig('phases', 'new-commit', 'secret', 'commit')
1487 ui.setconfig('phases', 'new-commit', 'secret', 'commit')
1489 # Propagate to subrepos
1488 # Propagate to subrepos
1490 baseui.setconfig('phases', 'new-commit', 'secret', 'commit')
1489 baseui.setconfig('phases', 'new-commit', 'secret', 'commit')
1491
1490
1492 editform = cmdutil.mergeeditform(repo[None], 'commit.normal')
1491 editform = cmdutil.mergeeditform(repo[None], 'commit.normal')
1493 editor = cmdutil.getcommiteditor(editform=editform, **opts)
1492 editor = cmdutil.getcommiteditor(editform=editform, **opts)
1494 return repo.commit(message, opts.get('user'), opts.get('date'),
1493 return repo.commit(message, opts.get('user'), opts.get('date'),
1495 match,
1494 match,
1496 editor=editor,
1495 editor=editor,
1497 extra=extra)
1496 extra=extra)
1498 finally:
1497 finally:
1499 ui.restoreconfig(backup)
1498 ui.restoreconfig(backup)
1500 repo.baseui.restoreconfig(basebackup)
1499 repo.baseui.restoreconfig(basebackup)
1501
1500
1502
1501
1503 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1502 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1504
1503
1505 if not node:
1504 if not node:
1506 stat = repo.status(match=scmutil.match(repo[None], pats, opts))
1505 stat = repo.status(match=scmutil.match(repo[None], pats, opts))
1507 if stat[3]:
1506 if stat[3]:
1508 ui.status(_("nothing changed (%d missing files, see "
1507 ui.status(_("nothing changed (%d missing files, see "
1509 "'hg status')\n") % len(stat[3]))
1508 "'hg status')\n") % len(stat[3]))
1510 else:
1509 else:
1511 ui.status(_("nothing changed\n"))
1510 ui.status(_("nothing changed\n"))
1512 return 1
1511 return 1
1513
1512
1514 cmdutil.commitstatus(repo, node, branch, bheads, opts)
1513 cmdutil.commitstatus(repo, node, branch, bheads, opts)
1515
1514
1516 @command('config|showconfig|debugconfig',
1515 @command('config|showconfig|debugconfig',
1517 [('u', 'untrusted', None, _('show untrusted configuration options')),
1516 [('u', 'untrusted', None, _('show untrusted configuration options')),
1518 ('e', 'edit', None, _('edit user config')),
1517 ('e', 'edit', None, _('edit user config')),
1519 ('l', 'local', None, _('edit repository config')),
1518 ('l', 'local', None, _('edit repository config')),
1520 ('g', 'global', None, _('edit global config'))],
1519 ('g', 'global', None, _('edit global config'))],
1521 _('[-u] [NAME]...'),
1520 _('[-u] [NAME]...'),
1522 optionalrepo=True)
1521 optionalrepo=True)
1523 def config(ui, repo, *values, **opts):
1522 def config(ui, repo, *values, **opts):
1524 """show combined config settings from all hgrc files
1523 """show combined config settings from all hgrc files
1525
1524
1526 With no arguments, print names and values of all config items.
1525 With no arguments, print names and values of all config items.
1527
1526
1528 With one argument of the form section.name, print just the value
1527 With one argument of the form section.name, print just the value
1529 of that config item.
1528 of that config item.
1530
1529
1531 With multiple arguments, print names and values of all config
1530 With multiple arguments, print names and values of all config
1532 items with matching section names.
1531 items with matching section names.
1533
1532
1534 With --edit, start an editor on the user-level config file. With
1533 With --edit, start an editor on the user-level config file. With
1535 --global, edit the system-wide config file. With --local, edit the
1534 --global, edit the system-wide config file. With --local, edit the
1536 repository-level config file.
1535 repository-level config file.
1537
1536
1538 With --debug, the source (filename and line number) is printed
1537 With --debug, the source (filename and line number) is printed
1539 for each config item.
1538 for each config item.
1540
1539
1541 See :hg:`help config` for more information about config files.
1540 See :hg:`help config` for more information about config files.
1542
1541
1543 Returns 0 on success, 1 if NAME does not exist.
1542 Returns 0 on success, 1 if NAME does not exist.
1544
1543
1545 """
1544 """
1546
1545
1547 if opts.get('edit') or opts.get('local') or opts.get('global'):
1546 if opts.get('edit') or opts.get('local') or opts.get('global'):
1548 if opts.get('local') and opts.get('global'):
1547 if opts.get('local') and opts.get('global'):
1549 raise util.Abort(_("can't use --local and --global together"))
1548 raise util.Abort(_("can't use --local and --global together"))
1550
1549
1551 if opts.get('local'):
1550 if opts.get('local'):
1552 if not repo:
1551 if not repo:
1553 raise util.Abort(_("can't use --local outside a repository"))
1552 raise util.Abort(_("can't use --local outside a repository"))
1554 paths = [repo.join('hgrc')]
1553 paths = [repo.join('hgrc')]
1555 elif opts.get('global'):
1554 elif opts.get('global'):
1556 paths = scmutil.systemrcpath()
1555 paths = scmutil.systemrcpath()
1557 else:
1556 else:
1558 paths = scmutil.userrcpath()
1557 paths = scmutil.userrcpath()
1559
1558
1560 for f in paths:
1559 for f in paths:
1561 if os.path.exists(f):
1560 if os.path.exists(f):
1562 break
1561 break
1563 else:
1562 else:
1564 if opts.get('global'):
1563 if opts.get('global'):
1565 samplehgrc = uimod.samplehgrcs['global']
1564 samplehgrc = uimod.samplehgrcs['global']
1566 elif opts.get('local'):
1565 elif opts.get('local'):
1567 samplehgrc = uimod.samplehgrcs['local']
1566 samplehgrc = uimod.samplehgrcs['local']
1568 else:
1567 else:
1569 samplehgrc = uimod.samplehgrcs['user']
1568 samplehgrc = uimod.samplehgrcs['user']
1570
1569
1571 f = paths[0]
1570 f = paths[0]
1572 fp = open(f, "w")
1571 fp = open(f, "w")
1573 fp.write(samplehgrc)
1572 fp.write(samplehgrc)
1574 fp.close()
1573 fp.close()
1575
1574
1576 editor = ui.geteditor()
1575 editor = ui.geteditor()
1577 ui.system("%s \"%s\"" % (editor, f),
1576 ui.system("%s \"%s\"" % (editor, f),
1578 onerr=util.Abort, errprefix=_("edit failed"))
1577 onerr=util.Abort, errprefix=_("edit failed"))
1579 return
1578 return
1580
1579
1581 for f in scmutil.rcpath():
1580 for f in scmutil.rcpath():
1582 ui.debug('read config from: %s\n' % f)
1581 ui.debug('read config from: %s\n' % f)
1583 untrusted = bool(opts.get('untrusted'))
1582 untrusted = bool(opts.get('untrusted'))
1584 if values:
1583 if values:
1585 sections = [v for v in values if '.' not in v]
1584 sections = [v for v in values if '.' not in v]
1586 items = [v for v in values if '.' in v]
1585 items = [v for v in values if '.' in v]
1587 if len(items) > 1 or items and sections:
1586 if len(items) > 1 or items and sections:
1588 raise util.Abort(_('only one config item permitted'))
1587 raise util.Abort(_('only one config item permitted'))
1589 matched = False
1588 matched = False
1590 for section, name, value in ui.walkconfig(untrusted=untrusted):
1589 for section, name, value in ui.walkconfig(untrusted=untrusted):
1591 value = str(value).replace('\n', '\\n')
1590 value = str(value).replace('\n', '\\n')
1592 sectname = section + '.' + name
1591 sectname = section + '.' + name
1593 if values:
1592 if values:
1594 for v in values:
1593 for v in values:
1595 if v == section:
1594 if v == section:
1596 ui.debug('%s: ' %
1595 ui.debug('%s: ' %
1597 ui.configsource(section, name, untrusted))
1596 ui.configsource(section, name, untrusted))
1598 ui.write('%s=%s\n' % (sectname, value))
1597 ui.write('%s=%s\n' % (sectname, value))
1599 matched = True
1598 matched = True
1600 elif v == sectname:
1599 elif v == sectname:
1601 ui.debug('%s: ' %
1600 ui.debug('%s: ' %
1602 ui.configsource(section, name, untrusted))
1601 ui.configsource(section, name, untrusted))
1603 ui.write(value, '\n')
1602 ui.write(value, '\n')
1604 matched = True
1603 matched = True
1605 else:
1604 else:
1606 ui.debug('%s: ' %
1605 ui.debug('%s: ' %
1607 ui.configsource(section, name, untrusted))
1606 ui.configsource(section, name, untrusted))
1608 ui.write('%s=%s\n' % (sectname, value))
1607 ui.write('%s=%s\n' % (sectname, value))
1609 matched = True
1608 matched = True
1610 if matched:
1609 if matched:
1611 return 0
1610 return 0
1612 return 1
1611 return 1
1613
1612
1614 @command('copy|cp',
1613 @command('copy|cp',
1615 [('A', 'after', None, _('record a copy that has already occurred')),
1614 [('A', 'after', None, _('record a copy that has already occurred')),
1616 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1615 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1617 ] + walkopts + dryrunopts,
1616 ] + walkopts + dryrunopts,
1618 _('[OPTION]... [SOURCE]... DEST'))
1617 _('[OPTION]... [SOURCE]... DEST'))
1619 def copy(ui, repo, *pats, **opts):
1618 def copy(ui, repo, *pats, **opts):
1620 """mark files as copied for the next commit
1619 """mark files as copied for the next commit
1621
1620
1622 Mark dest as having copies of source files. If dest is a
1621 Mark dest as having copies of source files. If dest is a
1623 directory, copies are put in that directory. If dest is a file,
1622 directory, copies are put in that directory. If dest is a file,
1624 the source must be a single file.
1623 the source must be a single file.
1625
1624
1626 By default, this command copies the contents of files as they
1625 By default, this command copies the contents of files as they
1627 exist in the working directory. If invoked with -A/--after, the
1626 exist in the working directory. If invoked with -A/--after, the
1628 operation is recorded, but no copying is performed.
1627 operation is recorded, but no copying is performed.
1629
1628
1630 This command takes effect with the next commit. To undo a copy
1629 This command takes effect with the next commit. To undo a copy
1631 before that, see :hg:`revert`.
1630 before that, see :hg:`revert`.
1632
1631
1633 Returns 0 on success, 1 if errors are encountered.
1632 Returns 0 on success, 1 if errors are encountered.
1634 """
1633 """
1635 wlock = repo.wlock(False)
1634 wlock = repo.wlock(False)
1636 try:
1635 try:
1637 return cmdutil.copy(ui, repo, pats, opts)
1636 return cmdutil.copy(ui, repo, pats, opts)
1638 finally:
1637 finally:
1639 wlock.release()
1638 wlock.release()
1640
1639
1641 @command('debugancestor', [], _('[INDEX] REV1 REV2'), optionalrepo=True)
1640 @command('debugancestor', [], _('[INDEX] REV1 REV2'), optionalrepo=True)
1642 def debugancestor(ui, repo, *args):
1641 def debugancestor(ui, repo, *args):
1643 """find the ancestor revision of two revisions in a given index"""
1642 """find the ancestor revision of two revisions in a given index"""
1644 if len(args) == 3:
1643 if len(args) == 3:
1645 index, rev1, rev2 = args
1644 index, rev1, rev2 = args
1646 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), index)
1645 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), index)
1647 lookup = r.lookup
1646 lookup = r.lookup
1648 elif len(args) == 2:
1647 elif len(args) == 2:
1649 if not repo:
1648 if not repo:
1650 raise util.Abort(_("there is no Mercurial repository here "
1649 raise util.Abort(_("there is no Mercurial repository here "
1651 "(.hg not found)"))
1650 "(.hg not found)"))
1652 rev1, rev2 = args
1651 rev1, rev2 = args
1653 r = repo.changelog
1652 r = repo.changelog
1654 lookup = repo.lookup
1653 lookup = repo.lookup
1655 else:
1654 else:
1656 raise util.Abort(_('either two or three arguments required'))
1655 raise util.Abort(_('either two or three arguments required'))
1657 a = r.ancestor(lookup(rev1), lookup(rev2))
1656 a = r.ancestor(lookup(rev1), lookup(rev2))
1658 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
1657 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
1659
1658
1660 @command('debugbuilddag',
1659 @command('debugbuilddag',
1661 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
1660 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
1662 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
1661 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
1663 ('n', 'new-file', None, _('add new file at each rev'))],
1662 ('n', 'new-file', None, _('add new file at each rev'))],
1664 _('[OPTION]... [TEXT]'))
1663 _('[OPTION]... [TEXT]'))
1665 def debugbuilddag(ui, repo, text=None,
1664 def debugbuilddag(ui, repo, text=None,
1666 mergeable_file=False,
1665 mergeable_file=False,
1667 overwritten_file=False,
1666 overwritten_file=False,
1668 new_file=False):
1667 new_file=False):
1669 """builds a repo with a given DAG from scratch in the current empty repo
1668 """builds a repo with a given DAG from scratch in the current empty repo
1670
1669
1671 The description of the DAG is read from stdin if not given on the
1670 The description of the DAG is read from stdin if not given on the
1672 command line.
1671 command line.
1673
1672
1674 Elements:
1673 Elements:
1675
1674
1676 - "+n" is a linear run of n nodes based on the current default parent
1675 - "+n" is a linear run of n nodes based on the current default parent
1677 - "." is a single node based on the current default parent
1676 - "." is a single node based on the current default parent
1678 - "$" resets the default parent to null (implied at the start);
1677 - "$" resets the default parent to null (implied at the start);
1679 otherwise the default parent is always the last node created
1678 otherwise the default parent is always the last node created
1680 - "<p" sets the default parent to the backref p
1679 - "<p" sets the default parent to the backref p
1681 - "*p" is a fork at parent p, which is a backref
1680 - "*p" is a fork at parent p, which is a backref
1682 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
1681 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
1683 - "/p2" is a merge of the preceding node and p2
1682 - "/p2" is a merge of the preceding node and p2
1684 - ":tag" defines a local tag for the preceding node
1683 - ":tag" defines a local tag for the preceding node
1685 - "@branch" sets the named branch for subsequent nodes
1684 - "@branch" sets the named branch for subsequent nodes
1686 - "#...\\n" is a comment up to the end of the line
1685 - "#...\\n" is a comment up to the end of the line
1687
1686
1688 Whitespace between the above elements is ignored.
1687 Whitespace between the above elements is ignored.
1689
1688
1690 A backref is either
1689 A backref is either
1691
1690
1692 - a number n, which references the node curr-n, where curr is the current
1691 - a number n, which references the node curr-n, where curr is the current
1693 node, or
1692 node, or
1694 - the name of a local tag you placed earlier using ":tag", or
1693 - the name of a local tag you placed earlier using ":tag", or
1695 - empty to denote the default parent.
1694 - empty to denote the default parent.
1696
1695
1697 All string valued-elements are either strictly alphanumeric, or must
1696 All string valued-elements are either strictly alphanumeric, or must
1698 be enclosed in double quotes ("..."), with "\\" as escape character.
1697 be enclosed in double quotes ("..."), with "\\" as escape character.
1699 """
1698 """
1700
1699
1701 if text is None:
1700 if text is None:
1702 ui.status(_("reading DAG from stdin\n"))
1701 ui.status(_("reading DAG from stdin\n"))
1703 text = ui.fin.read()
1702 text = ui.fin.read()
1704
1703
1705 cl = repo.changelog
1704 cl = repo.changelog
1706 if len(cl) > 0:
1705 if len(cl) > 0:
1707 raise util.Abort(_('repository is not empty'))
1706 raise util.Abort(_('repository is not empty'))
1708
1707
1709 # determine number of revs in DAG
1708 # determine number of revs in DAG
1710 total = 0
1709 total = 0
1711 for type, data in dagparser.parsedag(text):
1710 for type, data in dagparser.parsedag(text):
1712 if type == 'n':
1711 if type == 'n':
1713 total += 1
1712 total += 1
1714
1713
1715 if mergeable_file:
1714 if mergeable_file:
1716 linesperrev = 2
1715 linesperrev = 2
1717 # make a file with k lines per rev
1716 # make a file with k lines per rev
1718 initialmergedlines = [str(i) for i in xrange(0, total * linesperrev)]
1717 initialmergedlines = [str(i) for i in xrange(0, total * linesperrev)]
1719 initialmergedlines.append("")
1718 initialmergedlines.append("")
1720
1719
1721 tags = []
1720 tags = []
1722
1721
1723 lock = tr = None
1722 lock = tr = None
1724 try:
1723 try:
1725 lock = repo.lock()
1724 lock = repo.lock()
1726 tr = repo.transaction("builddag")
1725 tr = repo.transaction("builddag")
1727
1726
1728 at = -1
1727 at = -1
1729 atbranch = 'default'
1728 atbranch = 'default'
1730 nodeids = []
1729 nodeids = []
1731 id = 0
1730 id = 0
1732 ui.progress(_('building'), id, unit=_('revisions'), total=total)
1731 ui.progress(_('building'), id, unit=_('revisions'), total=total)
1733 for type, data in dagparser.parsedag(text):
1732 for type, data in dagparser.parsedag(text):
1734 if type == 'n':
1733 if type == 'n':
1735 ui.note(('node %s\n' % str(data)))
1734 ui.note(('node %s\n' % str(data)))
1736 id, ps = data
1735 id, ps = data
1737
1736
1738 files = []
1737 files = []
1739 fctxs = {}
1738 fctxs = {}
1740
1739
1741 p2 = None
1740 p2 = None
1742 if mergeable_file:
1741 if mergeable_file:
1743 fn = "mf"
1742 fn = "mf"
1744 p1 = repo[ps[0]]
1743 p1 = repo[ps[0]]
1745 if len(ps) > 1:
1744 if len(ps) > 1:
1746 p2 = repo[ps[1]]
1745 p2 = repo[ps[1]]
1747 pa = p1.ancestor(p2)
1746 pa = p1.ancestor(p2)
1748 base, local, other = [x[fn].data() for x in (pa, p1,
1747 base, local, other = [x[fn].data() for x in (pa, p1,
1749 p2)]
1748 p2)]
1750 m3 = simplemerge.Merge3Text(base, local, other)
1749 m3 = simplemerge.Merge3Text(base, local, other)
1751 ml = [l.strip() for l in m3.merge_lines()]
1750 ml = [l.strip() for l in m3.merge_lines()]
1752 ml.append("")
1751 ml.append("")
1753 elif at > 0:
1752 elif at > 0:
1754 ml = p1[fn].data().split("\n")
1753 ml = p1[fn].data().split("\n")
1755 else:
1754 else:
1756 ml = initialmergedlines
1755 ml = initialmergedlines
1757 ml[id * linesperrev] += " r%i" % id
1756 ml[id * linesperrev] += " r%i" % id
1758 mergedtext = "\n".join(ml)
1757 mergedtext = "\n".join(ml)
1759 files.append(fn)
1758 files.append(fn)
1760 fctxs[fn] = context.memfilectx(repo, fn, mergedtext)
1759 fctxs[fn] = context.memfilectx(repo, fn, mergedtext)
1761
1760
1762 if overwritten_file:
1761 if overwritten_file:
1763 fn = "of"
1762 fn = "of"
1764 files.append(fn)
1763 files.append(fn)
1765 fctxs[fn] = context.memfilectx(repo, fn, "r%i\n" % id)
1764 fctxs[fn] = context.memfilectx(repo, fn, "r%i\n" % id)
1766
1765
1767 if new_file:
1766 if new_file:
1768 fn = "nf%i" % id
1767 fn = "nf%i" % id
1769 files.append(fn)
1768 files.append(fn)
1770 fctxs[fn] = context.memfilectx(repo, fn, "r%i\n" % id)
1769 fctxs[fn] = context.memfilectx(repo, fn, "r%i\n" % id)
1771 if len(ps) > 1:
1770 if len(ps) > 1:
1772 if not p2:
1771 if not p2:
1773 p2 = repo[ps[1]]
1772 p2 = repo[ps[1]]
1774 for fn in p2:
1773 for fn in p2:
1775 if fn.startswith("nf"):
1774 if fn.startswith("nf"):
1776 files.append(fn)
1775 files.append(fn)
1777 fctxs[fn] = p2[fn]
1776 fctxs[fn] = p2[fn]
1778
1777
1779 def fctxfn(repo, cx, path):
1778 def fctxfn(repo, cx, path):
1780 return fctxs.get(path)
1779 return fctxs.get(path)
1781
1780
1782 if len(ps) == 0 or ps[0] < 0:
1781 if len(ps) == 0 or ps[0] < 0:
1783 pars = [None, None]
1782 pars = [None, None]
1784 elif len(ps) == 1:
1783 elif len(ps) == 1:
1785 pars = [nodeids[ps[0]], None]
1784 pars = [nodeids[ps[0]], None]
1786 else:
1785 else:
1787 pars = [nodeids[p] for p in ps]
1786 pars = [nodeids[p] for p in ps]
1788 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
1787 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
1789 date=(id, 0),
1788 date=(id, 0),
1790 user="debugbuilddag",
1789 user="debugbuilddag",
1791 extra={'branch': atbranch})
1790 extra={'branch': atbranch})
1792 nodeid = repo.commitctx(cx)
1791 nodeid = repo.commitctx(cx)
1793 nodeids.append(nodeid)
1792 nodeids.append(nodeid)
1794 at = id
1793 at = id
1795 elif type == 'l':
1794 elif type == 'l':
1796 id, name = data
1795 id, name = data
1797 ui.note(('tag %s\n' % name))
1796 ui.note(('tag %s\n' % name))
1798 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
1797 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
1799 elif type == 'a':
1798 elif type == 'a':
1800 ui.note(('branch %s\n' % data))
1799 ui.note(('branch %s\n' % data))
1801 atbranch = data
1800 atbranch = data
1802 ui.progress(_('building'), id, unit=_('revisions'), total=total)
1801 ui.progress(_('building'), id, unit=_('revisions'), total=total)
1803 tr.close()
1802 tr.close()
1804
1803
1805 if tags:
1804 if tags:
1806 repo.vfs.write("localtags", "".join(tags))
1805 repo.vfs.write("localtags", "".join(tags))
1807 finally:
1806 finally:
1808 ui.progress(_('building'), None)
1807 ui.progress(_('building'), None)
1809 release(tr, lock)
1808 release(tr, lock)
1810
1809
1811 @command('debugbundle',
1810 @command('debugbundle',
1812 [('a', 'all', None, _('show all details'))],
1811 [('a', 'all', None, _('show all details'))],
1813 _('FILE'),
1812 _('FILE'),
1814 norepo=True)
1813 norepo=True)
1815 def debugbundle(ui, bundlepath, all=None, **opts):
1814 def debugbundle(ui, bundlepath, all=None, **opts):
1816 """lists the contents of a bundle"""
1815 """lists the contents of a bundle"""
1817 f = hg.openpath(ui, bundlepath)
1816 f = hg.openpath(ui, bundlepath)
1818 try:
1817 try:
1819 gen = exchange.readbundle(ui, f, bundlepath)
1818 gen = exchange.readbundle(ui, f, bundlepath)
1820 if all:
1819 if all:
1821 ui.write(("format: id, p1, p2, cset, delta base, len(delta)\n"))
1820 ui.write(("format: id, p1, p2, cset, delta base, len(delta)\n"))
1822
1821
1823 def showchunks(named):
1822 def showchunks(named):
1824 ui.write("\n%s\n" % named)
1823 ui.write("\n%s\n" % named)
1825 chain = None
1824 chain = None
1826 while True:
1825 while True:
1827 chunkdata = gen.deltachunk(chain)
1826 chunkdata = gen.deltachunk(chain)
1828 if not chunkdata:
1827 if not chunkdata:
1829 break
1828 break
1830 node = chunkdata['node']
1829 node = chunkdata['node']
1831 p1 = chunkdata['p1']
1830 p1 = chunkdata['p1']
1832 p2 = chunkdata['p2']
1831 p2 = chunkdata['p2']
1833 cs = chunkdata['cs']
1832 cs = chunkdata['cs']
1834 deltabase = chunkdata['deltabase']
1833 deltabase = chunkdata['deltabase']
1835 delta = chunkdata['delta']
1834 delta = chunkdata['delta']
1836 ui.write("%s %s %s %s %s %s\n" %
1835 ui.write("%s %s %s %s %s %s\n" %
1837 (hex(node), hex(p1), hex(p2),
1836 (hex(node), hex(p1), hex(p2),
1838 hex(cs), hex(deltabase), len(delta)))
1837 hex(cs), hex(deltabase), len(delta)))
1839 chain = node
1838 chain = node
1840
1839
1841 chunkdata = gen.changelogheader()
1840 chunkdata = gen.changelogheader()
1842 showchunks("changelog")
1841 showchunks("changelog")
1843 chunkdata = gen.manifestheader()
1842 chunkdata = gen.manifestheader()
1844 showchunks("manifest")
1843 showchunks("manifest")
1845 while True:
1844 while True:
1846 chunkdata = gen.filelogheader()
1845 chunkdata = gen.filelogheader()
1847 if not chunkdata:
1846 if not chunkdata:
1848 break
1847 break
1849 fname = chunkdata['filename']
1848 fname = chunkdata['filename']
1850 showchunks(fname)
1849 showchunks(fname)
1851 else:
1850 else:
1852 chunkdata = gen.changelogheader()
1851 chunkdata = gen.changelogheader()
1853 chain = None
1852 chain = None
1854 while True:
1853 while True:
1855 chunkdata = gen.deltachunk(chain)
1854 chunkdata = gen.deltachunk(chain)
1856 if not chunkdata:
1855 if not chunkdata:
1857 break
1856 break
1858 node = chunkdata['node']
1857 node = chunkdata['node']
1859 ui.write("%s\n" % hex(node))
1858 ui.write("%s\n" % hex(node))
1860 chain = node
1859 chain = node
1861 finally:
1860 finally:
1862 f.close()
1861 f.close()
1863
1862
1864 @command('debugcheckstate', [], '')
1863 @command('debugcheckstate', [], '')
1865 def debugcheckstate(ui, repo):
1864 def debugcheckstate(ui, repo):
1866 """validate the correctness of the current dirstate"""
1865 """validate the correctness of the current dirstate"""
1867 parent1, parent2 = repo.dirstate.parents()
1866 parent1, parent2 = repo.dirstate.parents()
1868 m1 = repo[parent1].manifest()
1867 m1 = repo[parent1].manifest()
1869 m2 = repo[parent2].manifest()
1868 m2 = repo[parent2].manifest()
1870 errors = 0
1869 errors = 0
1871 for f in repo.dirstate:
1870 for f in repo.dirstate:
1872 state = repo.dirstate[f]
1871 state = repo.dirstate[f]
1873 if state in "nr" and f not in m1:
1872 if state in "nr" and f not in m1:
1874 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1873 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1875 errors += 1
1874 errors += 1
1876 if state in "a" and f in m1:
1875 if state in "a" and f in m1:
1877 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1876 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1878 errors += 1
1877 errors += 1
1879 if state in "m" and f not in m1 and f not in m2:
1878 if state in "m" and f not in m1 and f not in m2:
1880 ui.warn(_("%s in state %s, but not in either manifest\n") %
1879 ui.warn(_("%s in state %s, but not in either manifest\n") %
1881 (f, state))
1880 (f, state))
1882 errors += 1
1881 errors += 1
1883 for f in m1:
1882 for f in m1:
1884 state = repo.dirstate[f]
1883 state = repo.dirstate[f]
1885 if state not in "nrm":
1884 if state not in "nrm":
1886 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1885 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1887 errors += 1
1886 errors += 1
1888 if errors:
1887 if errors:
1889 error = _(".hg/dirstate inconsistent with current parent's manifest")
1888 error = _(".hg/dirstate inconsistent with current parent's manifest")
1890 raise util.Abort(error)
1889 raise util.Abort(error)
1891
1890
1892 @command('debugcommands', [], _('[COMMAND]'), norepo=True)
1891 @command('debugcommands', [], _('[COMMAND]'), norepo=True)
1893 def debugcommands(ui, cmd='', *args):
1892 def debugcommands(ui, cmd='', *args):
1894 """list all available commands and options"""
1893 """list all available commands and options"""
1895 for cmd, vals in sorted(table.iteritems()):
1894 for cmd, vals in sorted(table.iteritems()):
1896 cmd = cmd.split('|')[0].strip('^')
1895 cmd = cmd.split('|')[0].strip('^')
1897 opts = ', '.join([i[1] for i in vals[1]])
1896 opts = ', '.join([i[1] for i in vals[1]])
1898 ui.write('%s: %s\n' % (cmd, opts))
1897 ui.write('%s: %s\n' % (cmd, opts))
1899
1898
1900 @command('debugcomplete',
1899 @command('debugcomplete',
1901 [('o', 'options', None, _('show the command options'))],
1900 [('o', 'options', None, _('show the command options'))],
1902 _('[-o] CMD'),
1901 _('[-o] CMD'),
1903 norepo=True)
1902 norepo=True)
1904 def debugcomplete(ui, cmd='', **opts):
1903 def debugcomplete(ui, cmd='', **opts):
1905 """returns the completion list associated with the given command"""
1904 """returns the completion list associated with the given command"""
1906
1905
1907 if opts.get('options'):
1906 if opts.get('options'):
1908 options = []
1907 options = []
1909 otables = [globalopts]
1908 otables = [globalopts]
1910 if cmd:
1909 if cmd:
1911 aliases, entry = cmdutil.findcmd(cmd, table, False)
1910 aliases, entry = cmdutil.findcmd(cmd, table, False)
1912 otables.append(entry[1])
1911 otables.append(entry[1])
1913 for t in otables:
1912 for t in otables:
1914 for o in t:
1913 for o in t:
1915 if "(DEPRECATED)" in o[3]:
1914 if "(DEPRECATED)" in o[3]:
1916 continue
1915 continue
1917 if o[0]:
1916 if o[0]:
1918 options.append('-%s' % o[0])
1917 options.append('-%s' % o[0])
1919 options.append('--%s' % o[1])
1918 options.append('--%s' % o[1])
1920 ui.write("%s\n" % "\n".join(options))
1919 ui.write("%s\n" % "\n".join(options))
1921 return
1920 return
1922
1921
1923 cmdlist = cmdutil.findpossible(cmd, table)
1922 cmdlist = cmdutil.findpossible(cmd, table)
1924 if ui.verbose:
1923 if ui.verbose:
1925 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1924 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1926 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1925 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1927
1926
1928 @command('debugdag',
1927 @command('debugdag',
1929 [('t', 'tags', None, _('use tags as labels')),
1928 [('t', 'tags', None, _('use tags as labels')),
1930 ('b', 'branches', None, _('annotate with branch names')),
1929 ('b', 'branches', None, _('annotate with branch names')),
1931 ('', 'dots', None, _('use dots for runs')),
1930 ('', 'dots', None, _('use dots for runs')),
1932 ('s', 'spaces', None, _('separate elements by spaces'))],
1931 ('s', 'spaces', None, _('separate elements by spaces'))],
1933 _('[OPTION]... [FILE [REV]...]'),
1932 _('[OPTION]... [FILE [REV]...]'),
1934 optionalrepo=True)
1933 optionalrepo=True)
1935 def debugdag(ui, repo, file_=None, *revs, **opts):
1934 def debugdag(ui, repo, file_=None, *revs, **opts):
1936 """format the changelog or an index DAG as a concise textual description
1935 """format the changelog or an index DAG as a concise textual description
1937
1936
1938 If you pass a revlog index, the revlog's DAG is emitted. If you list
1937 If you pass a revlog index, the revlog's DAG is emitted. If you list
1939 revision numbers, they get labeled in the output as rN.
1938 revision numbers, they get labeled in the output as rN.
1940
1939
1941 Otherwise, the changelog DAG of the current repo is emitted.
1940 Otherwise, the changelog DAG of the current repo is emitted.
1942 """
1941 """
1943 spaces = opts.get('spaces')
1942 spaces = opts.get('spaces')
1944 dots = opts.get('dots')
1943 dots = opts.get('dots')
1945 if file_:
1944 if file_:
1946 rlog = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1945 rlog = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1947 revs = set((int(r) for r in revs))
1946 revs = set((int(r) for r in revs))
1948 def events():
1947 def events():
1949 for r in rlog:
1948 for r in rlog:
1950 yield 'n', (r, list(p for p in rlog.parentrevs(r)
1949 yield 'n', (r, list(p for p in rlog.parentrevs(r)
1951 if p != -1))
1950 if p != -1))
1952 if r in revs:
1951 if r in revs:
1953 yield 'l', (r, "r%i" % r)
1952 yield 'l', (r, "r%i" % r)
1954 elif repo:
1953 elif repo:
1955 cl = repo.changelog
1954 cl = repo.changelog
1956 tags = opts.get('tags')
1955 tags = opts.get('tags')
1957 branches = opts.get('branches')
1956 branches = opts.get('branches')
1958 if tags:
1957 if tags:
1959 labels = {}
1958 labels = {}
1960 for l, n in repo.tags().items():
1959 for l, n in repo.tags().items():
1961 labels.setdefault(cl.rev(n), []).append(l)
1960 labels.setdefault(cl.rev(n), []).append(l)
1962 def events():
1961 def events():
1963 b = "default"
1962 b = "default"
1964 for r in cl:
1963 for r in cl:
1965 if branches:
1964 if branches:
1966 newb = cl.read(cl.node(r))[5]['branch']
1965 newb = cl.read(cl.node(r))[5]['branch']
1967 if newb != b:
1966 if newb != b:
1968 yield 'a', newb
1967 yield 'a', newb
1969 b = newb
1968 b = newb
1970 yield 'n', (r, list(p for p in cl.parentrevs(r)
1969 yield 'n', (r, list(p for p in cl.parentrevs(r)
1971 if p != -1))
1970 if p != -1))
1972 if tags:
1971 if tags:
1973 ls = labels.get(r)
1972 ls = labels.get(r)
1974 if ls:
1973 if ls:
1975 for l in ls:
1974 for l in ls:
1976 yield 'l', (r, l)
1975 yield 'l', (r, l)
1977 else:
1976 else:
1978 raise util.Abort(_('need repo for changelog dag'))
1977 raise util.Abort(_('need repo for changelog dag'))
1979
1978
1980 for line in dagparser.dagtextlines(events(),
1979 for line in dagparser.dagtextlines(events(),
1981 addspaces=spaces,
1980 addspaces=spaces,
1982 wraplabels=True,
1981 wraplabels=True,
1983 wrapannotations=True,
1982 wrapannotations=True,
1984 wrapnonlinear=dots,
1983 wrapnonlinear=dots,
1985 usedots=dots,
1984 usedots=dots,
1986 maxlinewidth=70):
1985 maxlinewidth=70):
1987 ui.write(line)
1986 ui.write(line)
1988 ui.write("\n")
1987 ui.write("\n")
1989
1988
1990 @command('debugdata',
1989 @command('debugdata',
1991 [('c', 'changelog', False, _('open changelog')),
1990 [('c', 'changelog', False, _('open changelog')),
1992 ('m', 'manifest', False, _('open manifest'))],
1991 ('m', 'manifest', False, _('open manifest'))],
1993 _('-c|-m|FILE REV'))
1992 _('-c|-m|FILE REV'))
1994 def debugdata(ui, repo, file_, rev=None, **opts):
1993 def debugdata(ui, repo, file_, rev=None, **opts):
1995 """dump the contents of a data file revision"""
1994 """dump the contents of a data file revision"""
1996 if opts.get('changelog') or opts.get('manifest'):
1995 if opts.get('changelog') or opts.get('manifest'):
1997 file_, rev = None, file_
1996 file_, rev = None, file_
1998 elif rev is None:
1997 elif rev is None:
1999 raise error.CommandError('debugdata', _('invalid arguments'))
1998 raise error.CommandError('debugdata', _('invalid arguments'))
2000 r = cmdutil.openrevlog(repo, 'debugdata', file_, opts)
1999 r = cmdutil.openrevlog(repo, 'debugdata', file_, opts)
2001 try:
2000 try:
2002 ui.write(r.revision(r.lookup(rev)))
2001 ui.write(r.revision(r.lookup(rev)))
2003 except KeyError:
2002 except KeyError:
2004 raise util.Abort(_('invalid revision identifier %s') % rev)
2003 raise util.Abort(_('invalid revision identifier %s') % rev)
2005
2004
2006 @command('debugdate',
2005 @command('debugdate',
2007 [('e', 'extended', None, _('try extended date formats'))],
2006 [('e', 'extended', None, _('try extended date formats'))],
2008 _('[-e] DATE [RANGE]'),
2007 _('[-e] DATE [RANGE]'),
2009 norepo=True, optionalrepo=True)
2008 norepo=True, optionalrepo=True)
2010 def debugdate(ui, date, range=None, **opts):
2009 def debugdate(ui, date, range=None, **opts):
2011 """parse and display a date"""
2010 """parse and display a date"""
2012 if opts["extended"]:
2011 if opts["extended"]:
2013 d = util.parsedate(date, util.extendeddateformats)
2012 d = util.parsedate(date, util.extendeddateformats)
2014 else:
2013 else:
2015 d = util.parsedate(date)
2014 d = util.parsedate(date)
2016 ui.write(("internal: %s %s\n") % d)
2015 ui.write(("internal: %s %s\n") % d)
2017 ui.write(("standard: %s\n") % util.datestr(d))
2016 ui.write(("standard: %s\n") % util.datestr(d))
2018 if range:
2017 if range:
2019 m = util.matchdate(range)
2018 m = util.matchdate(range)
2020 ui.write(("match: %s\n") % m(d[0]))
2019 ui.write(("match: %s\n") % m(d[0]))
2021
2020
2022 @command('debugdiscovery',
2021 @command('debugdiscovery',
2023 [('', 'old', None, _('use old-style discovery')),
2022 [('', 'old', None, _('use old-style discovery')),
2024 ('', 'nonheads', None,
2023 ('', 'nonheads', None,
2025 _('use old-style discovery with non-heads included')),
2024 _('use old-style discovery with non-heads included')),
2026 ] + remoteopts,
2025 ] + remoteopts,
2027 _('[-l REV] [-r REV] [-b BRANCH]... [OTHER]'))
2026 _('[-l REV] [-r REV] [-b BRANCH]... [OTHER]'))
2028 def debugdiscovery(ui, repo, remoteurl="default", **opts):
2027 def debugdiscovery(ui, repo, remoteurl="default", **opts):
2029 """runs the changeset discovery protocol in isolation"""
2028 """runs the changeset discovery protocol in isolation"""
2030 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl),
2029 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl),
2031 opts.get('branch'))
2030 opts.get('branch'))
2032 remote = hg.peer(repo, opts, remoteurl)
2031 remote = hg.peer(repo, opts, remoteurl)
2033 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
2032 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
2034
2033
2035 # make sure tests are repeatable
2034 # make sure tests are repeatable
2036 random.seed(12323)
2035 random.seed(12323)
2037
2036
2038 def doit(localheads, remoteheads, remote=remote):
2037 def doit(localheads, remoteheads, remote=remote):
2039 if opts.get('old'):
2038 if opts.get('old'):
2040 if localheads:
2039 if localheads:
2041 raise util.Abort('cannot use localheads with old style '
2040 raise util.Abort('cannot use localheads with old style '
2042 'discovery')
2041 'discovery')
2043 if not util.safehasattr(remote, 'branches'):
2042 if not util.safehasattr(remote, 'branches'):
2044 # enable in-client legacy support
2043 # enable in-client legacy support
2045 remote = localrepo.locallegacypeer(remote.local())
2044 remote = localrepo.locallegacypeer(remote.local())
2046 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
2045 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
2047 force=True)
2046 force=True)
2048 common = set(common)
2047 common = set(common)
2049 if not opts.get('nonheads'):
2048 if not opts.get('nonheads'):
2050 ui.write(("unpruned common: %s\n") %
2049 ui.write(("unpruned common: %s\n") %
2051 " ".join(sorted(short(n) for n in common)))
2050 " ".join(sorted(short(n) for n in common)))
2052 dag = dagutil.revlogdag(repo.changelog)
2051 dag = dagutil.revlogdag(repo.changelog)
2053 all = dag.ancestorset(dag.internalizeall(common))
2052 all = dag.ancestorset(dag.internalizeall(common))
2054 common = dag.externalizeall(dag.headsetofconnecteds(all))
2053 common = dag.externalizeall(dag.headsetofconnecteds(all))
2055 else:
2054 else:
2056 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote)
2055 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote)
2057 common = set(common)
2056 common = set(common)
2058 rheads = set(hds)
2057 rheads = set(hds)
2059 lheads = set(repo.heads())
2058 lheads = set(repo.heads())
2060 ui.write(("common heads: %s\n") %
2059 ui.write(("common heads: %s\n") %
2061 " ".join(sorted(short(n) for n in common)))
2060 " ".join(sorted(short(n) for n in common)))
2062 if lheads <= common:
2061 if lheads <= common:
2063 ui.write(("local is subset\n"))
2062 ui.write(("local is subset\n"))
2064 elif rheads <= common:
2063 elif rheads <= common:
2065 ui.write(("remote is subset\n"))
2064 ui.write(("remote is subset\n"))
2066
2065
2067 serverlogs = opts.get('serverlog')
2066 serverlogs = opts.get('serverlog')
2068 if serverlogs:
2067 if serverlogs:
2069 for filename in serverlogs:
2068 for filename in serverlogs:
2070 logfile = open(filename, 'r')
2069 logfile = open(filename, 'r')
2071 try:
2070 try:
2072 line = logfile.readline()
2071 line = logfile.readline()
2073 while line:
2072 while line:
2074 parts = line.strip().split(';')
2073 parts = line.strip().split(';')
2075 op = parts[1]
2074 op = parts[1]
2076 if op == 'cg':
2075 if op == 'cg':
2077 pass
2076 pass
2078 elif op == 'cgss':
2077 elif op == 'cgss':
2079 doit(parts[2].split(' '), parts[3].split(' '))
2078 doit(parts[2].split(' '), parts[3].split(' '))
2080 elif op == 'unb':
2079 elif op == 'unb':
2081 doit(parts[3].split(' '), parts[2].split(' '))
2080 doit(parts[3].split(' '), parts[2].split(' '))
2082 line = logfile.readline()
2081 line = logfile.readline()
2083 finally:
2082 finally:
2084 logfile.close()
2083 logfile.close()
2085
2084
2086 else:
2085 else:
2087 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches,
2086 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches,
2088 opts.get('remote_head'))
2087 opts.get('remote_head'))
2089 localrevs = opts.get('local_head')
2088 localrevs = opts.get('local_head')
2090 doit(localrevs, remoterevs)
2089 doit(localrevs, remoterevs)
2091
2090
2092 @command('debugfileset',
2091 @command('debugfileset',
2093 [('r', 'rev', '', _('apply the filespec on this revision'), _('REV'))],
2092 [('r', 'rev', '', _('apply the filespec on this revision'), _('REV'))],
2094 _('[-r REV] FILESPEC'))
2093 _('[-r REV] FILESPEC'))
2095 def debugfileset(ui, repo, expr, **opts):
2094 def debugfileset(ui, repo, expr, **opts):
2096 '''parse and apply a fileset specification'''
2095 '''parse and apply a fileset specification'''
2097 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
2096 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
2098 if ui.verbose:
2097 if ui.verbose:
2099 tree = fileset.parse(expr)[0]
2098 tree = fileset.parse(expr)[0]
2100 ui.note(tree, "\n")
2099 ui.note(tree, "\n")
2101
2100
2102 for f in ctx.getfileset(expr):
2101 for f in ctx.getfileset(expr):
2103 ui.write("%s\n" % f)
2102 ui.write("%s\n" % f)
2104
2103
2105 @command('debugfsinfo', [], _('[PATH]'), norepo=True)
2104 @command('debugfsinfo', [], _('[PATH]'), norepo=True)
2106 def debugfsinfo(ui, path="."):
2105 def debugfsinfo(ui, path="."):
2107 """show information detected about current filesystem"""
2106 """show information detected about current filesystem"""
2108 util.writefile('.debugfsinfo', '')
2107 util.writefile('.debugfsinfo', '')
2109 ui.write(('exec: %s\n') % (util.checkexec(path) and 'yes' or 'no'))
2108 ui.write(('exec: %s\n') % (util.checkexec(path) and 'yes' or 'no'))
2110 ui.write(('symlink: %s\n') % (util.checklink(path) and 'yes' or 'no'))
2109 ui.write(('symlink: %s\n') % (util.checklink(path) and 'yes' or 'no'))
2111 ui.write(('hardlink: %s\n') % (util.checknlink(path) and 'yes' or 'no'))
2110 ui.write(('hardlink: %s\n') % (util.checknlink(path) and 'yes' or 'no'))
2112 ui.write(('case-sensitive: %s\n') % (util.checkcase('.debugfsinfo')
2111 ui.write(('case-sensitive: %s\n') % (util.checkcase('.debugfsinfo')
2113 and 'yes' or 'no'))
2112 and 'yes' or 'no'))
2114 os.unlink('.debugfsinfo')
2113 os.unlink('.debugfsinfo')
2115
2114
2116 @command('debuggetbundle',
2115 @command('debuggetbundle',
2117 [('H', 'head', [], _('id of head node'), _('ID')),
2116 [('H', 'head', [], _('id of head node'), _('ID')),
2118 ('C', 'common', [], _('id of common node'), _('ID')),
2117 ('C', 'common', [], _('id of common node'), _('ID')),
2119 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))],
2118 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))],
2120 _('REPO FILE [-H|-C ID]...'),
2119 _('REPO FILE [-H|-C ID]...'),
2121 norepo=True)
2120 norepo=True)
2122 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
2121 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
2123 """retrieves a bundle from a repo
2122 """retrieves a bundle from a repo
2124
2123
2125 Every ID must be a full-length hex node id string. Saves the bundle to the
2124 Every ID must be a full-length hex node id string. Saves the bundle to the
2126 given file.
2125 given file.
2127 """
2126 """
2128 repo = hg.peer(ui, opts, repopath)
2127 repo = hg.peer(ui, opts, repopath)
2129 if not repo.capable('getbundle'):
2128 if not repo.capable('getbundle'):
2130 raise util.Abort("getbundle() not supported by target repository")
2129 raise util.Abort("getbundle() not supported by target repository")
2131 args = {}
2130 args = {}
2132 if common:
2131 if common:
2133 args['common'] = [bin(s) for s in common]
2132 args['common'] = [bin(s) for s in common]
2134 if head:
2133 if head:
2135 args['heads'] = [bin(s) for s in head]
2134 args['heads'] = [bin(s) for s in head]
2136 # TODO: get desired bundlecaps from command line.
2135 # TODO: get desired bundlecaps from command line.
2137 args['bundlecaps'] = None
2136 args['bundlecaps'] = None
2138 bundle = repo.getbundle('debug', **args)
2137 bundle = repo.getbundle('debug', **args)
2139
2138
2140 bundletype = opts.get('type', 'bzip2').lower()
2139 bundletype = opts.get('type', 'bzip2').lower()
2141 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
2140 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
2142 bundletype = btypes.get(bundletype)
2141 bundletype = btypes.get(bundletype)
2143 if bundletype not in changegroup.bundletypes:
2142 if bundletype not in changegroup.bundletypes:
2144 raise util.Abort(_('unknown bundle type specified with --type'))
2143 raise util.Abort(_('unknown bundle type specified with --type'))
2145 changegroup.writebundle(bundle, bundlepath, bundletype)
2144 changegroup.writebundle(bundle, bundlepath, bundletype)
2146
2145
2147 @command('debugignore', [], '')
2146 @command('debugignore', [], '')
2148 def debugignore(ui, repo, *values, **opts):
2147 def debugignore(ui, repo, *values, **opts):
2149 """display the combined ignore pattern"""
2148 """display the combined ignore pattern"""
2150 ignore = repo.dirstate._ignore
2149 ignore = repo.dirstate._ignore
2151 includepat = getattr(ignore, 'includepat', None)
2150 includepat = getattr(ignore, 'includepat', None)
2152 if includepat is not None:
2151 if includepat is not None:
2153 ui.write("%s\n" % includepat)
2152 ui.write("%s\n" % includepat)
2154 else:
2153 else:
2155 raise util.Abort(_("no ignore patterns found"))
2154 raise util.Abort(_("no ignore patterns found"))
2156
2155
2157 @command('debugindex',
2156 @command('debugindex',
2158 [('c', 'changelog', False, _('open changelog')),
2157 [('c', 'changelog', False, _('open changelog')),
2159 ('m', 'manifest', False, _('open manifest')),
2158 ('m', 'manifest', False, _('open manifest')),
2160 ('f', 'format', 0, _('revlog format'), _('FORMAT'))],
2159 ('f', 'format', 0, _('revlog format'), _('FORMAT'))],
2161 _('[-f FORMAT] -c|-m|FILE'),
2160 _('[-f FORMAT] -c|-m|FILE'),
2162 optionalrepo=True)
2161 optionalrepo=True)
2163 def debugindex(ui, repo, file_=None, **opts):
2162 def debugindex(ui, repo, file_=None, **opts):
2164 """dump the contents of an index file"""
2163 """dump the contents of an index file"""
2165 r = cmdutil.openrevlog(repo, 'debugindex', file_, opts)
2164 r = cmdutil.openrevlog(repo, 'debugindex', file_, opts)
2166 format = opts.get('format', 0)
2165 format = opts.get('format', 0)
2167 if format not in (0, 1):
2166 if format not in (0, 1):
2168 raise util.Abort(_("unknown format %d") % format)
2167 raise util.Abort(_("unknown format %d") % format)
2169
2168
2170 generaldelta = r.version & revlog.REVLOGGENERALDELTA
2169 generaldelta = r.version & revlog.REVLOGGENERALDELTA
2171 if generaldelta:
2170 if generaldelta:
2172 basehdr = ' delta'
2171 basehdr = ' delta'
2173 else:
2172 else:
2174 basehdr = ' base'
2173 basehdr = ' base'
2175
2174
2176 if ui.debugflag:
2175 if ui.debugflag:
2177 shortfn = hex
2176 shortfn = hex
2178 else:
2177 else:
2179 shortfn = short
2178 shortfn = short
2180
2179
2181 # There might not be anything in r, so have a sane default
2180 # There might not be anything in r, so have a sane default
2182 idlen = 12
2181 idlen = 12
2183 for i in r:
2182 for i in r:
2184 idlen = len(shortfn(r.node(i)))
2183 idlen = len(shortfn(r.node(i)))
2185 break
2184 break
2186
2185
2187 if format == 0:
2186 if format == 0:
2188 ui.write(" rev offset length " + basehdr + " linkrev"
2187 ui.write(" rev offset length " + basehdr + " linkrev"
2189 " %s %s p2\n" % ("nodeid".ljust(idlen), "p1".ljust(idlen)))
2188 " %s %s p2\n" % ("nodeid".ljust(idlen), "p1".ljust(idlen)))
2190 elif format == 1:
2189 elif format == 1:
2191 ui.write(" rev flag offset length"
2190 ui.write(" rev flag offset length"
2192 " size " + basehdr + " link p1 p2"
2191 " size " + basehdr + " link p1 p2"
2193 " %s\n" % "nodeid".rjust(idlen))
2192 " %s\n" % "nodeid".rjust(idlen))
2194
2193
2195 for i in r:
2194 for i in r:
2196 node = r.node(i)
2195 node = r.node(i)
2197 if generaldelta:
2196 if generaldelta:
2198 base = r.deltaparent(i)
2197 base = r.deltaparent(i)
2199 else:
2198 else:
2200 base = r.chainbase(i)
2199 base = r.chainbase(i)
2201 if format == 0:
2200 if format == 0:
2202 try:
2201 try:
2203 pp = r.parents(node)
2202 pp = r.parents(node)
2204 except Exception:
2203 except Exception:
2205 pp = [nullid, nullid]
2204 pp = [nullid, nullid]
2206 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
2205 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
2207 i, r.start(i), r.length(i), base, r.linkrev(i),
2206 i, r.start(i), r.length(i), base, r.linkrev(i),
2208 shortfn(node), shortfn(pp[0]), shortfn(pp[1])))
2207 shortfn(node), shortfn(pp[0]), shortfn(pp[1])))
2209 elif format == 1:
2208 elif format == 1:
2210 pr = r.parentrevs(i)
2209 pr = r.parentrevs(i)
2211 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
2210 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
2212 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
2211 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
2213 base, r.linkrev(i), pr[0], pr[1], shortfn(node)))
2212 base, r.linkrev(i), pr[0], pr[1], shortfn(node)))
2214
2213
2215 @command('debugindexdot', [], _('FILE'), optionalrepo=True)
2214 @command('debugindexdot', [], _('FILE'), optionalrepo=True)
2216 def debugindexdot(ui, repo, file_):
2215 def debugindexdot(ui, repo, file_):
2217 """dump an index DAG as a graphviz dot file"""
2216 """dump an index DAG as a graphviz dot file"""
2218 r = None
2217 r = None
2219 if repo:
2218 if repo:
2220 filelog = repo.file(file_)
2219 filelog = repo.file(file_)
2221 if len(filelog):
2220 if len(filelog):
2222 r = filelog
2221 r = filelog
2223 if not r:
2222 if not r:
2224 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
2223 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
2225 ui.write(("digraph G {\n"))
2224 ui.write(("digraph G {\n"))
2226 for i in r:
2225 for i in r:
2227 node = r.node(i)
2226 node = r.node(i)
2228 pp = r.parents(node)
2227 pp = r.parents(node)
2229 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
2228 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
2230 if pp[1] != nullid:
2229 if pp[1] != nullid:
2231 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
2230 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
2232 ui.write("}\n")
2231 ui.write("}\n")
2233
2232
2234 @command('debuginstall', [], '', norepo=True)
2233 @command('debuginstall', [], '', norepo=True)
2235 def debuginstall(ui):
2234 def debuginstall(ui):
2236 '''test Mercurial installation
2235 '''test Mercurial installation
2237
2236
2238 Returns 0 on success.
2237 Returns 0 on success.
2239 '''
2238 '''
2240
2239
2241 def writetemp(contents):
2240 def writetemp(contents):
2242 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
2241 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
2243 f = os.fdopen(fd, "wb")
2242 f = os.fdopen(fd, "wb")
2244 f.write(contents)
2243 f.write(contents)
2245 f.close()
2244 f.close()
2246 return name
2245 return name
2247
2246
2248 problems = 0
2247 problems = 0
2249
2248
2250 # encoding
2249 # encoding
2251 ui.status(_("checking encoding (%s)...\n") % encoding.encoding)
2250 ui.status(_("checking encoding (%s)...\n") % encoding.encoding)
2252 try:
2251 try:
2253 encoding.fromlocal("test")
2252 encoding.fromlocal("test")
2254 except util.Abort, inst:
2253 except util.Abort, inst:
2255 ui.write(" %s\n" % inst)
2254 ui.write(" %s\n" % inst)
2256 ui.write(_(" (check that your locale is properly set)\n"))
2255 ui.write(_(" (check that your locale is properly set)\n"))
2257 problems += 1
2256 problems += 1
2258
2257
2259 # Python
2258 # Python
2260 ui.status(_("checking Python executable (%s)\n") % sys.executable)
2259 ui.status(_("checking Python executable (%s)\n") % sys.executable)
2261 ui.status(_("checking Python version (%s)\n")
2260 ui.status(_("checking Python version (%s)\n")
2262 % ("%s.%s.%s" % sys.version_info[:3]))
2261 % ("%s.%s.%s" % sys.version_info[:3]))
2263 ui.status(_("checking Python lib (%s)...\n")
2262 ui.status(_("checking Python lib (%s)...\n")
2264 % os.path.dirname(os.__file__))
2263 % os.path.dirname(os.__file__))
2265
2264
2266 # compiled modules
2265 # compiled modules
2267 ui.status(_("checking installed modules (%s)...\n")
2266 ui.status(_("checking installed modules (%s)...\n")
2268 % os.path.dirname(__file__))
2267 % os.path.dirname(__file__))
2269 try:
2268 try:
2270 import bdiff, mpatch, base85, osutil
2269 import bdiff, mpatch, base85, osutil
2271 dir(bdiff), dir(mpatch), dir(base85), dir(osutil) # quiet pyflakes
2270 dir(bdiff), dir(mpatch), dir(base85), dir(osutil) # quiet pyflakes
2272 except Exception, inst:
2271 except Exception, inst:
2273 ui.write(" %s\n" % inst)
2272 ui.write(" %s\n" % inst)
2274 ui.write(_(" One or more extensions could not be found"))
2273 ui.write(_(" One or more extensions could not be found"))
2275 ui.write(_(" (check that you compiled the extensions)\n"))
2274 ui.write(_(" (check that you compiled the extensions)\n"))
2276 problems += 1
2275 problems += 1
2277
2276
2278 # templates
2277 # templates
2279 import templater
2278 import templater
2280 p = templater.templatepaths()
2279 p = templater.templatepaths()
2281 ui.status(_("checking templates (%s)...\n") % ' '.join(p))
2280 ui.status(_("checking templates (%s)...\n") % ' '.join(p))
2282 if p:
2281 if p:
2283 m = templater.templatepath("map-cmdline.default")
2282 m = templater.templatepath("map-cmdline.default")
2284 if m:
2283 if m:
2285 # template found, check if it is working
2284 # template found, check if it is working
2286 try:
2285 try:
2287 templater.templater(m)
2286 templater.templater(m)
2288 except Exception, inst:
2287 except Exception, inst:
2289 ui.write(" %s\n" % inst)
2288 ui.write(" %s\n" % inst)
2290 p = None
2289 p = None
2291 else:
2290 else:
2292 ui.write(_(" template 'default' not found\n"))
2291 ui.write(_(" template 'default' not found\n"))
2293 p = None
2292 p = None
2294 else:
2293 else:
2295 ui.write(_(" no template directories found\n"))
2294 ui.write(_(" no template directories found\n"))
2296 if not p:
2295 if not p:
2297 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
2296 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
2298 problems += 1
2297 problems += 1
2299
2298
2300 # editor
2299 # editor
2301 ui.status(_("checking commit editor...\n"))
2300 ui.status(_("checking commit editor...\n"))
2302 editor = ui.geteditor()
2301 editor = ui.geteditor()
2303 cmdpath = util.findexe(shlex.split(editor)[0])
2302 cmdpath = util.findexe(shlex.split(editor)[0])
2304 if not cmdpath:
2303 if not cmdpath:
2305 if editor == 'vi':
2304 if editor == 'vi':
2306 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
2305 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
2307 ui.write(_(" (specify a commit editor in your configuration"
2306 ui.write(_(" (specify a commit editor in your configuration"
2308 " file)\n"))
2307 " file)\n"))
2309 else:
2308 else:
2310 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
2309 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
2311 ui.write(_(" (specify a commit editor in your configuration"
2310 ui.write(_(" (specify a commit editor in your configuration"
2312 " file)\n"))
2311 " file)\n"))
2313 problems += 1
2312 problems += 1
2314
2313
2315 # check username
2314 # check username
2316 ui.status(_("checking username...\n"))
2315 ui.status(_("checking username...\n"))
2317 try:
2316 try:
2318 ui.username()
2317 ui.username()
2319 except util.Abort, e:
2318 except util.Abort, e:
2320 ui.write(" %s\n" % e)
2319 ui.write(" %s\n" % e)
2321 ui.write(_(" (specify a username in your configuration file)\n"))
2320 ui.write(_(" (specify a username in your configuration file)\n"))
2322 problems += 1
2321 problems += 1
2323
2322
2324 if not problems:
2323 if not problems:
2325 ui.status(_("no problems detected\n"))
2324 ui.status(_("no problems detected\n"))
2326 else:
2325 else:
2327 ui.write(_("%s problems detected,"
2326 ui.write(_("%s problems detected,"
2328 " please check your install!\n") % problems)
2327 " please check your install!\n") % problems)
2329
2328
2330 return problems
2329 return problems
2331
2330
2332 @command('debugknown', [], _('REPO ID...'), norepo=True)
2331 @command('debugknown', [], _('REPO ID...'), norepo=True)
2333 def debugknown(ui, repopath, *ids, **opts):
2332 def debugknown(ui, repopath, *ids, **opts):
2334 """test whether node ids are known to a repo
2333 """test whether node ids are known to a repo
2335
2334
2336 Every ID must be a full-length hex node id string. Returns a list of 0s
2335 Every ID must be a full-length hex node id string. Returns a list of 0s
2337 and 1s indicating unknown/known.
2336 and 1s indicating unknown/known.
2338 """
2337 """
2339 repo = hg.peer(ui, opts, repopath)
2338 repo = hg.peer(ui, opts, repopath)
2340 if not repo.capable('known'):
2339 if not repo.capable('known'):
2341 raise util.Abort("known() not supported by target repository")
2340 raise util.Abort("known() not supported by target repository")
2342 flags = repo.known([bin(s) for s in ids])
2341 flags = repo.known([bin(s) for s in ids])
2343 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
2342 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
2344
2343
2345 @command('debuglabelcomplete', [], _('LABEL...'))
2344 @command('debuglabelcomplete', [], _('LABEL...'))
2346 def debuglabelcomplete(ui, repo, *args):
2345 def debuglabelcomplete(ui, repo, *args):
2347 '''backwards compatibility with old bash completion scripts (DEPRECATED)'''
2346 '''backwards compatibility with old bash completion scripts (DEPRECATED)'''
2348 debugnamecomplete(ui, repo, *args)
2347 debugnamecomplete(ui, repo, *args)
2349
2348
2350 @command('debugnamecomplete', [], _('NAME...'))
2349 @command('debugnamecomplete', [], _('NAME...'))
2351 def debugnamecomplete(ui, repo, *args):
2350 def debugnamecomplete(ui, repo, *args):
2352 '''complete "names" - tags, open branch names, bookmark names'''
2351 '''complete "names" - tags, open branch names, bookmark names'''
2353
2352
2354 names = set()
2353 names = set()
2355 # since we previously only listed open branches, we will handle that
2354 # since we previously only listed open branches, we will handle that
2356 # specially (after this for loop)
2355 # specially (after this for loop)
2357 for name, ns in repo.names.iteritems():
2356 for name, ns in repo.names.iteritems():
2358 if name != 'branches':
2357 if name != 'branches':
2359 names.update(ns.listnames(repo))
2358 names.update(ns.listnames(repo))
2360 names.update(tag for (tag, heads, tip, closed)
2359 names.update(tag for (tag, heads, tip, closed)
2361 in repo.branchmap().iterbranches() if not closed)
2360 in repo.branchmap().iterbranches() if not closed)
2362 completions = set()
2361 completions = set()
2363 if not args:
2362 if not args:
2364 args = ['']
2363 args = ['']
2365 for a in args:
2364 for a in args:
2366 completions.update(n for n in names if n.startswith(a))
2365 completions.update(n for n in names if n.startswith(a))
2367 ui.write('\n'.join(sorted(completions)))
2366 ui.write('\n'.join(sorted(completions)))
2368 ui.write('\n')
2367 ui.write('\n')
2369
2368
2370 @command('debuglocks',
2369 @command('debuglocks',
2371 [('L', 'force-lock', None, _('free the store lock (DANGEROUS)')),
2370 [('L', 'force-lock', None, _('free the store lock (DANGEROUS)')),
2372 ('W', 'force-wlock', None,
2371 ('W', 'force-wlock', None,
2373 _('free the working state lock (DANGEROUS)'))],
2372 _('free the working state lock (DANGEROUS)'))],
2374 _('[OPTION]...'))
2373 _('[OPTION]...'))
2375 def debuglocks(ui, repo, **opts):
2374 def debuglocks(ui, repo, **opts):
2376 """show or modify state of locks
2375 """show or modify state of locks
2377
2376
2378 By default, this command will show which locks are held. This
2377 By default, this command will show which locks are held. This
2379 includes the user and process holding the lock, the amount of time
2378 includes the user and process holding the lock, the amount of time
2380 the lock has been held, and the machine name where the process is
2379 the lock has been held, and the machine name where the process is
2381 running if it's not local.
2380 running if it's not local.
2382
2381
2383 Locks protect the integrity of Mercurial's data, so should be
2382 Locks protect the integrity of Mercurial's data, so should be
2384 treated with care. System crashes or other interruptions may cause
2383 treated with care. System crashes or other interruptions may cause
2385 locks to not be properly released, though Mercurial will usually
2384 locks to not be properly released, though Mercurial will usually
2386 detect and remove such stale locks automatically.
2385 detect and remove such stale locks automatically.
2387
2386
2388 However, detecting stale locks may not always be possible (for
2387 However, detecting stale locks may not always be possible (for
2389 instance, on a shared filesystem). Removing locks may also be
2388 instance, on a shared filesystem). Removing locks may also be
2390 blocked by filesystem permissions.
2389 blocked by filesystem permissions.
2391
2390
2392 Returns 0 if no locks are held.
2391 Returns 0 if no locks are held.
2393
2392
2394 """
2393 """
2395
2394
2396 if opts.get('force_lock'):
2395 if opts.get('force_lock'):
2397 repo.svfs.unlink('lock')
2396 repo.svfs.unlink('lock')
2398 if opts.get('force_wlock'):
2397 if opts.get('force_wlock'):
2399 repo.vfs.unlink('wlock')
2398 repo.vfs.unlink('wlock')
2400 if opts.get('force_lock') or opts.get('force_lock'):
2399 if opts.get('force_lock') or opts.get('force_lock'):
2401 return 0
2400 return 0
2402
2401
2403 now = time.time()
2402 now = time.time()
2404 held = 0
2403 held = 0
2405
2404
2406 def report(vfs, name, method):
2405 def report(vfs, name, method):
2407 # this causes stale locks to get reaped for more accurate reporting
2406 # this causes stale locks to get reaped for more accurate reporting
2408 try:
2407 try:
2409 l = method(False)
2408 l = method(False)
2410 except error.LockHeld:
2409 except error.LockHeld:
2411 l = None
2410 l = None
2412
2411
2413 if l:
2412 if l:
2414 l.release()
2413 l.release()
2415 else:
2414 else:
2416 try:
2415 try:
2417 stat = repo.svfs.lstat(name)
2416 stat = repo.svfs.lstat(name)
2418 age = now - stat.st_mtime
2417 age = now - stat.st_mtime
2419 user = util.username(stat.st_uid)
2418 user = util.username(stat.st_uid)
2420 locker = vfs.readlock(name)
2419 locker = vfs.readlock(name)
2421 if ":" in locker:
2420 if ":" in locker:
2422 host, pid = locker.split(':')
2421 host, pid = locker.split(':')
2423 if host == socket.gethostname():
2422 if host == socket.gethostname():
2424 locker = 'user %s, process %s' % (user, pid)
2423 locker = 'user %s, process %s' % (user, pid)
2425 else:
2424 else:
2426 locker = 'user %s, process %s, host %s' \
2425 locker = 'user %s, process %s, host %s' \
2427 % (user, pid, host)
2426 % (user, pid, host)
2428 ui.write("%-6s %s (%ds)\n" % (name + ":", locker, age))
2427 ui.write("%-6s %s (%ds)\n" % (name + ":", locker, age))
2429 return 1
2428 return 1
2430 except OSError, e:
2429 except OSError, e:
2431 if e.errno != errno.ENOENT:
2430 if e.errno != errno.ENOENT:
2432 raise
2431 raise
2433
2432
2434 ui.write("%-6s free\n" % (name + ":"))
2433 ui.write("%-6s free\n" % (name + ":"))
2435 return 0
2434 return 0
2436
2435
2437 held += report(repo.svfs, "lock", repo.lock)
2436 held += report(repo.svfs, "lock", repo.lock)
2438 held += report(repo.vfs, "wlock", repo.wlock)
2437 held += report(repo.vfs, "wlock", repo.wlock)
2439
2438
2440 return held
2439 return held
2441
2440
2442 @command('debugobsolete',
2441 @command('debugobsolete',
2443 [('', 'flags', 0, _('markers flag')),
2442 [('', 'flags', 0, _('markers flag')),
2444 ('', 'record-parents', False,
2443 ('', 'record-parents', False,
2445 _('record parent information for the precursor')),
2444 _('record parent information for the precursor')),
2446 ('r', 'rev', [], _('display markers relevant to REV')),
2445 ('r', 'rev', [], _('display markers relevant to REV')),
2447 ] + commitopts2,
2446 ] + commitopts2,
2448 _('[OBSOLETED [REPLACEMENT] [REPL... ]'))
2447 _('[OBSOLETED [REPLACEMENT] [REPL... ]'))
2449 def debugobsolete(ui, repo, precursor=None, *successors, **opts):
2448 def debugobsolete(ui, repo, precursor=None, *successors, **opts):
2450 """create arbitrary obsolete marker
2449 """create arbitrary obsolete marker
2451
2450
2452 With no arguments, displays the list of obsolescence markers."""
2451 With no arguments, displays the list of obsolescence markers."""
2453
2452
2454 def parsenodeid(s):
2453 def parsenodeid(s):
2455 try:
2454 try:
2456 # We do not use revsingle/revrange functions here to accept
2455 # We do not use revsingle/revrange functions here to accept
2457 # arbitrary node identifiers, possibly not present in the
2456 # arbitrary node identifiers, possibly not present in the
2458 # local repository.
2457 # local repository.
2459 n = bin(s)
2458 n = bin(s)
2460 if len(n) != len(nullid):
2459 if len(n) != len(nullid):
2461 raise TypeError()
2460 raise TypeError()
2462 return n
2461 return n
2463 except TypeError:
2462 except TypeError:
2464 raise util.Abort('changeset references must be full hexadecimal '
2463 raise util.Abort('changeset references must be full hexadecimal '
2465 'node identifiers')
2464 'node identifiers')
2466
2465
2467 if precursor is not None:
2466 if precursor is not None:
2468 if opts['rev']:
2467 if opts['rev']:
2469 raise util.Abort('cannot select revision when creating marker')
2468 raise util.Abort('cannot select revision when creating marker')
2470 metadata = {}
2469 metadata = {}
2471 metadata['user'] = opts['user'] or ui.username()
2470 metadata['user'] = opts['user'] or ui.username()
2472 succs = tuple(parsenodeid(succ) for succ in successors)
2471 succs = tuple(parsenodeid(succ) for succ in successors)
2473 l = repo.lock()
2472 l = repo.lock()
2474 try:
2473 try:
2475 tr = repo.transaction('debugobsolete')
2474 tr = repo.transaction('debugobsolete')
2476 try:
2475 try:
2477 try:
2476 try:
2478 date = opts.get('date')
2477 date = opts.get('date')
2479 if date:
2478 if date:
2480 date = util.parsedate(date)
2479 date = util.parsedate(date)
2481 else:
2480 else:
2482 date = None
2481 date = None
2483 prec = parsenodeid(precursor)
2482 prec = parsenodeid(precursor)
2484 parents = None
2483 parents = None
2485 if opts['record_parents']:
2484 if opts['record_parents']:
2486 if prec not in repo.unfiltered():
2485 if prec not in repo.unfiltered():
2487 raise util.Abort('cannot used --record-parents on '
2486 raise util.Abort('cannot used --record-parents on '
2488 'unknown changesets')
2487 'unknown changesets')
2489 parents = repo.unfiltered()[prec].parents()
2488 parents = repo.unfiltered()[prec].parents()
2490 parents = tuple(p.node() for p in parents)
2489 parents = tuple(p.node() for p in parents)
2491 repo.obsstore.create(tr, prec, succs, opts['flags'],
2490 repo.obsstore.create(tr, prec, succs, opts['flags'],
2492 parents=parents, date=date,
2491 parents=parents, date=date,
2493 metadata=metadata)
2492 metadata=metadata)
2494 tr.close()
2493 tr.close()
2495 except ValueError, exc:
2494 except ValueError, exc:
2496 raise util.Abort(_('bad obsmarker input: %s') % exc)
2495 raise util.Abort(_('bad obsmarker input: %s') % exc)
2497 finally:
2496 finally:
2498 tr.release()
2497 tr.release()
2499 finally:
2498 finally:
2500 l.release()
2499 l.release()
2501 else:
2500 else:
2502 if opts['rev']:
2501 if opts['rev']:
2503 revs = scmutil.revrange(repo, opts['rev'])
2502 revs = scmutil.revrange(repo, opts['rev'])
2504 nodes = [repo[r].node() for r in revs]
2503 nodes = [repo[r].node() for r in revs]
2505 markers = list(obsolete.getmarkers(repo, nodes=nodes))
2504 markers = list(obsolete.getmarkers(repo, nodes=nodes))
2506 markers.sort(key=lambda x: x._data)
2505 markers.sort(key=lambda x: x._data)
2507 else:
2506 else:
2508 markers = obsolete.getmarkers(repo)
2507 markers = obsolete.getmarkers(repo)
2509
2508
2510 for m in markers:
2509 for m in markers:
2511 cmdutil.showmarker(ui, m)
2510 cmdutil.showmarker(ui, m)
2512
2511
2513 @command('debugpathcomplete',
2512 @command('debugpathcomplete',
2514 [('f', 'full', None, _('complete an entire path')),
2513 [('f', 'full', None, _('complete an entire path')),
2515 ('n', 'normal', None, _('show only normal files')),
2514 ('n', 'normal', None, _('show only normal files')),
2516 ('a', 'added', None, _('show only added files')),
2515 ('a', 'added', None, _('show only added files')),
2517 ('r', 'removed', None, _('show only removed files'))],
2516 ('r', 'removed', None, _('show only removed files'))],
2518 _('FILESPEC...'))
2517 _('FILESPEC...'))
2519 def debugpathcomplete(ui, repo, *specs, **opts):
2518 def debugpathcomplete(ui, repo, *specs, **opts):
2520 '''complete part or all of a tracked path
2519 '''complete part or all of a tracked path
2521
2520
2522 This command supports shells that offer path name completion. It
2521 This command supports shells that offer path name completion. It
2523 currently completes only files already known to the dirstate.
2522 currently completes only files already known to the dirstate.
2524
2523
2525 Completion extends only to the next path segment unless
2524 Completion extends only to the next path segment unless
2526 --full is specified, in which case entire paths are used.'''
2525 --full is specified, in which case entire paths are used.'''
2527
2526
2528 def complete(path, acceptable):
2527 def complete(path, acceptable):
2529 dirstate = repo.dirstate
2528 dirstate = repo.dirstate
2530 spec = os.path.normpath(os.path.join(os.getcwd(), path))
2529 spec = os.path.normpath(os.path.join(os.getcwd(), path))
2531 rootdir = repo.root + os.sep
2530 rootdir = repo.root + os.sep
2532 if spec != repo.root and not spec.startswith(rootdir):
2531 if spec != repo.root and not spec.startswith(rootdir):
2533 return [], []
2532 return [], []
2534 if os.path.isdir(spec):
2533 if os.path.isdir(spec):
2535 spec += '/'
2534 spec += '/'
2536 spec = spec[len(rootdir):]
2535 spec = spec[len(rootdir):]
2537 fixpaths = os.sep != '/'
2536 fixpaths = os.sep != '/'
2538 if fixpaths:
2537 if fixpaths:
2539 spec = spec.replace(os.sep, '/')
2538 spec = spec.replace(os.sep, '/')
2540 speclen = len(spec)
2539 speclen = len(spec)
2541 fullpaths = opts['full']
2540 fullpaths = opts['full']
2542 files, dirs = set(), set()
2541 files, dirs = set(), set()
2543 adddir, addfile = dirs.add, files.add
2542 adddir, addfile = dirs.add, files.add
2544 for f, st in dirstate.iteritems():
2543 for f, st in dirstate.iteritems():
2545 if f.startswith(spec) and st[0] in acceptable:
2544 if f.startswith(spec) and st[0] in acceptable:
2546 if fixpaths:
2545 if fixpaths:
2547 f = f.replace('/', os.sep)
2546 f = f.replace('/', os.sep)
2548 if fullpaths:
2547 if fullpaths:
2549 addfile(f)
2548 addfile(f)
2550 continue
2549 continue
2551 s = f.find(os.sep, speclen)
2550 s = f.find(os.sep, speclen)
2552 if s >= 0:
2551 if s >= 0:
2553 adddir(f[:s])
2552 adddir(f[:s])
2554 else:
2553 else:
2555 addfile(f)
2554 addfile(f)
2556 return files, dirs
2555 return files, dirs
2557
2556
2558 acceptable = ''
2557 acceptable = ''
2559 if opts['normal']:
2558 if opts['normal']:
2560 acceptable += 'nm'
2559 acceptable += 'nm'
2561 if opts['added']:
2560 if opts['added']:
2562 acceptable += 'a'
2561 acceptable += 'a'
2563 if opts['removed']:
2562 if opts['removed']:
2564 acceptable += 'r'
2563 acceptable += 'r'
2565 cwd = repo.getcwd()
2564 cwd = repo.getcwd()
2566 if not specs:
2565 if not specs:
2567 specs = ['.']
2566 specs = ['.']
2568
2567
2569 files, dirs = set(), set()
2568 files, dirs = set(), set()
2570 for spec in specs:
2569 for spec in specs:
2571 f, d = complete(spec, acceptable or 'nmar')
2570 f, d = complete(spec, acceptable or 'nmar')
2572 files.update(f)
2571 files.update(f)
2573 dirs.update(d)
2572 dirs.update(d)
2574 files.update(dirs)
2573 files.update(dirs)
2575 ui.write('\n'.join(repo.pathto(p, cwd) for p in sorted(files)))
2574 ui.write('\n'.join(repo.pathto(p, cwd) for p in sorted(files)))
2576 ui.write('\n')
2575 ui.write('\n')
2577
2576
2578 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'), norepo=True)
2577 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'), norepo=True)
2579 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
2578 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
2580 '''access the pushkey key/value protocol
2579 '''access the pushkey key/value protocol
2581
2580
2582 With two args, list the keys in the given namespace.
2581 With two args, list the keys in the given namespace.
2583
2582
2584 With five args, set a key to new if it currently is set to old.
2583 With five args, set a key to new if it currently is set to old.
2585 Reports success or failure.
2584 Reports success or failure.
2586 '''
2585 '''
2587
2586
2588 target = hg.peer(ui, {}, repopath)
2587 target = hg.peer(ui, {}, repopath)
2589 if keyinfo:
2588 if keyinfo:
2590 key, old, new = keyinfo
2589 key, old, new = keyinfo
2591 r = target.pushkey(namespace, key, old, new)
2590 r = target.pushkey(namespace, key, old, new)
2592 ui.status(str(r) + '\n')
2591 ui.status(str(r) + '\n')
2593 return not r
2592 return not r
2594 else:
2593 else:
2595 for k, v in sorted(target.listkeys(namespace).iteritems()):
2594 for k, v in sorted(target.listkeys(namespace).iteritems()):
2596 ui.write("%s\t%s\n" % (k.encode('string-escape'),
2595 ui.write("%s\t%s\n" % (k.encode('string-escape'),
2597 v.encode('string-escape')))
2596 v.encode('string-escape')))
2598
2597
2599 @command('debugpvec', [], _('A B'))
2598 @command('debugpvec', [], _('A B'))
2600 def debugpvec(ui, repo, a, b=None):
2599 def debugpvec(ui, repo, a, b=None):
2601 ca = scmutil.revsingle(repo, a)
2600 ca = scmutil.revsingle(repo, a)
2602 cb = scmutil.revsingle(repo, b)
2601 cb = scmutil.revsingle(repo, b)
2603 pa = pvec.ctxpvec(ca)
2602 pa = pvec.ctxpvec(ca)
2604 pb = pvec.ctxpvec(cb)
2603 pb = pvec.ctxpvec(cb)
2605 if pa == pb:
2604 if pa == pb:
2606 rel = "="
2605 rel = "="
2607 elif pa > pb:
2606 elif pa > pb:
2608 rel = ">"
2607 rel = ">"
2609 elif pa < pb:
2608 elif pa < pb:
2610 rel = "<"
2609 rel = "<"
2611 elif pa | pb:
2610 elif pa | pb:
2612 rel = "|"
2611 rel = "|"
2613 ui.write(_("a: %s\n") % pa)
2612 ui.write(_("a: %s\n") % pa)
2614 ui.write(_("b: %s\n") % pb)
2613 ui.write(_("b: %s\n") % pb)
2615 ui.write(_("depth(a): %d depth(b): %d\n") % (pa._depth, pb._depth))
2614 ui.write(_("depth(a): %d depth(b): %d\n") % (pa._depth, pb._depth))
2616 ui.write(_("delta: %d hdist: %d distance: %d relation: %s\n") %
2615 ui.write(_("delta: %d hdist: %d distance: %d relation: %s\n") %
2617 (abs(pa._depth - pb._depth), pvec._hamming(pa._vec, pb._vec),
2616 (abs(pa._depth - pb._depth), pvec._hamming(pa._vec, pb._vec),
2618 pa.distance(pb), rel))
2617 pa.distance(pb), rel))
2619
2618
2620 @command('debugrebuilddirstate|debugrebuildstate',
2619 @command('debugrebuilddirstate|debugrebuildstate',
2621 [('r', 'rev', '', _('revision to rebuild to'), _('REV'))],
2620 [('r', 'rev', '', _('revision to rebuild to'), _('REV'))],
2622 _('[-r REV]'))
2621 _('[-r REV]'))
2623 def debugrebuilddirstate(ui, repo, rev):
2622 def debugrebuilddirstate(ui, repo, rev):
2624 """rebuild the dirstate as it would look like for the given revision
2623 """rebuild the dirstate as it would look like for the given revision
2625
2624
2626 If no revision is specified the first current parent will be used.
2625 If no revision is specified the first current parent will be used.
2627
2626
2628 The dirstate will be set to the files of the given revision.
2627 The dirstate will be set to the files of the given revision.
2629 The actual working directory content or existing dirstate
2628 The actual working directory content or existing dirstate
2630 information such as adds or removes is not considered.
2629 information such as adds or removes is not considered.
2631
2630
2632 One use of this command is to make the next :hg:`status` invocation
2631 One use of this command is to make the next :hg:`status` invocation
2633 check the actual file content.
2632 check the actual file content.
2634 """
2633 """
2635 ctx = scmutil.revsingle(repo, rev)
2634 ctx = scmutil.revsingle(repo, rev)
2636 wlock = repo.wlock()
2635 wlock = repo.wlock()
2637 try:
2636 try:
2638 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
2637 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
2639 finally:
2638 finally:
2640 wlock.release()
2639 wlock.release()
2641
2640
2642 @command('debugrename',
2641 @command('debugrename',
2643 [('r', 'rev', '', _('revision to debug'), _('REV'))],
2642 [('r', 'rev', '', _('revision to debug'), _('REV'))],
2644 _('[-r REV] FILE'))
2643 _('[-r REV] FILE'))
2645 def debugrename(ui, repo, file1, *pats, **opts):
2644 def debugrename(ui, repo, file1, *pats, **opts):
2646 """dump rename information"""
2645 """dump rename information"""
2647
2646
2648 ctx = scmutil.revsingle(repo, opts.get('rev'))
2647 ctx = scmutil.revsingle(repo, opts.get('rev'))
2649 m = scmutil.match(ctx, (file1,) + pats, opts)
2648 m = scmutil.match(ctx, (file1,) + pats, opts)
2650 for abs in ctx.walk(m):
2649 for abs in ctx.walk(m):
2651 fctx = ctx[abs]
2650 fctx = ctx[abs]
2652 o = fctx.filelog().renamed(fctx.filenode())
2651 o = fctx.filelog().renamed(fctx.filenode())
2653 rel = m.rel(abs)
2652 rel = m.rel(abs)
2654 if o:
2653 if o:
2655 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
2654 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
2656 else:
2655 else:
2657 ui.write(_("%s not renamed\n") % rel)
2656 ui.write(_("%s not renamed\n") % rel)
2658
2657
2659 @command('debugrevlog',
2658 @command('debugrevlog',
2660 [('c', 'changelog', False, _('open changelog')),
2659 [('c', 'changelog', False, _('open changelog')),
2661 ('m', 'manifest', False, _('open manifest')),
2660 ('m', 'manifest', False, _('open manifest')),
2662 ('d', 'dump', False, _('dump index data'))],
2661 ('d', 'dump', False, _('dump index data'))],
2663 _('-c|-m|FILE'),
2662 _('-c|-m|FILE'),
2664 optionalrepo=True)
2663 optionalrepo=True)
2665 def debugrevlog(ui, repo, file_=None, **opts):
2664 def debugrevlog(ui, repo, file_=None, **opts):
2666 """show data and statistics about a revlog"""
2665 """show data and statistics about a revlog"""
2667 r = cmdutil.openrevlog(repo, 'debugrevlog', file_, opts)
2666 r = cmdutil.openrevlog(repo, 'debugrevlog', file_, opts)
2668
2667
2669 if opts.get("dump"):
2668 if opts.get("dump"):
2670 numrevs = len(r)
2669 numrevs = len(r)
2671 ui.write("# rev p1rev p2rev start end deltastart base p1 p2"
2670 ui.write("# rev p1rev p2rev start end deltastart base p1 p2"
2672 " rawsize totalsize compression heads chainlen\n")
2671 " rawsize totalsize compression heads chainlen\n")
2673 ts = 0
2672 ts = 0
2674 heads = set()
2673 heads = set()
2675
2674
2676 for rev in xrange(numrevs):
2675 for rev in xrange(numrevs):
2677 dbase = r.deltaparent(rev)
2676 dbase = r.deltaparent(rev)
2678 if dbase == -1:
2677 if dbase == -1:
2679 dbase = rev
2678 dbase = rev
2680 cbase = r.chainbase(rev)
2679 cbase = r.chainbase(rev)
2681 clen = r.chainlen(rev)
2680 clen = r.chainlen(rev)
2682 p1, p2 = r.parentrevs(rev)
2681 p1, p2 = r.parentrevs(rev)
2683 rs = r.rawsize(rev)
2682 rs = r.rawsize(rev)
2684 ts = ts + rs
2683 ts = ts + rs
2685 heads -= set(r.parentrevs(rev))
2684 heads -= set(r.parentrevs(rev))
2686 heads.add(rev)
2685 heads.add(rev)
2687 ui.write("%5d %5d %5d %5d %5d %10d %4d %4d %4d %7d %9d "
2686 ui.write("%5d %5d %5d %5d %5d %10d %4d %4d %4d %7d %9d "
2688 "%11d %5d %8d\n" %
2687 "%11d %5d %8d\n" %
2689 (rev, p1, p2, r.start(rev), r.end(rev),
2688 (rev, p1, p2, r.start(rev), r.end(rev),
2690 r.start(dbase), r.start(cbase),
2689 r.start(dbase), r.start(cbase),
2691 r.start(p1), r.start(p2),
2690 r.start(p1), r.start(p2),
2692 rs, ts, ts / r.end(rev), len(heads), clen))
2691 rs, ts, ts / r.end(rev), len(heads), clen))
2693 return 0
2692 return 0
2694
2693
2695 v = r.version
2694 v = r.version
2696 format = v & 0xFFFF
2695 format = v & 0xFFFF
2697 flags = []
2696 flags = []
2698 gdelta = False
2697 gdelta = False
2699 if v & revlog.REVLOGNGINLINEDATA:
2698 if v & revlog.REVLOGNGINLINEDATA:
2700 flags.append('inline')
2699 flags.append('inline')
2701 if v & revlog.REVLOGGENERALDELTA:
2700 if v & revlog.REVLOGGENERALDELTA:
2702 gdelta = True
2701 gdelta = True
2703 flags.append('generaldelta')
2702 flags.append('generaldelta')
2704 if not flags:
2703 if not flags:
2705 flags = ['(none)']
2704 flags = ['(none)']
2706
2705
2707 nummerges = 0
2706 nummerges = 0
2708 numfull = 0
2707 numfull = 0
2709 numprev = 0
2708 numprev = 0
2710 nump1 = 0
2709 nump1 = 0
2711 nump2 = 0
2710 nump2 = 0
2712 numother = 0
2711 numother = 0
2713 nump1prev = 0
2712 nump1prev = 0
2714 nump2prev = 0
2713 nump2prev = 0
2715 chainlengths = []
2714 chainlengths = []
2716
2715
2717 datasize = [None, 0, 0L]
2716 datasize = [None, 0, 0L]
2718 fullsize = [None, 0, 0L]
2717 fullsize = [None, 0, 0L]
2719 deltasize = [None, 0, 0L]
2718 deltasize = [None, 0, 0L]
2720
2719
2721 def addsize(size, l):
2720 def addsize(size, l):
2722 if l[0] is None or size < l[0]:
2721 if l[0] is None or size < l[0]:
2723 l[0] = size
2722 l[0] = size
2724 if size > l[1]:
2723 if size > l[1]:
2725 l[1] = size
2724 l[1] = size
2726 l[2] += size
2725 l[2] += size
2727
2726
2728 numrevs = len(r)
2727 numrevs = len(r)
2729 for rev in xrange(numrevs):
2728 for rev in xrange(numrevs):
2730 p1, p2 = r.parentrevs(rev)
2729 p1, p2 = r.parentrevs(rev)
2731 delta = r.deltaparent(rev)
2730 delta = r.deltaparent(rev)
2732 if format > 0:
2731 if format > 0:
2733 addsize(r.rawsize(rev), datasize)
2732 addsize(r.rawsize(rev), datasize)
2734 if p2 != nullrev:
2733 if p2 != nullrev:
2735 nummerges += 1
2734 nummerges += 1
2736 size = r.length(rev)
2735 size = r.length(rev)
2737 if delta == nullrev:
2736 if delta == nullrev:
2738 chainlengths.append(0)
2737 chainlengths.append(0)
2739 numfull += 1
2738 numfull += 1
2740 addsize(size, fullsize)
2739 addsize(size, fullsize)
2741 else:
2740 else:
2742 chainlengths.append(chainlengths[delta] + 1)
2741 chainlengths.append(chainlengths[delta] + 1)
2743 addsize(size, deltasize)
2742 addsize(size, deltasize)
2744 if delta == rev - 1:
2743 if delta == rev - 1:
2745 numprev += 1
2744 numprev += 1
2746 if delta == p1:
2745 if delta == p1:
2747 nump1prev += 1
2746 nump1prev += 1
2748 elif delta == p2:
2747 elif delta == p2:
2749 nump2prev += 1
2748 nump2prev += 1
2750 elif delta == p1:
2749 elif delta == p1:
2751 nump1 += 1
2750 nump1 += 1
2752 elif delta == p2:
2751 elif delta == p2:
2753 nump2 += 1
2752 nump2 += 1
2754 elif delta != nullrev:
2753 elif delta != nullrev:
2755 numother += 1
2754 numother += 1
2756
2755
2757 # Adjust size min value for empty cases
2756 # Adjust size min value for empty cases
2758 for size in (datasize, fullsize, deltasize):
2757 for size in (datasize, fullsize, deltasize):
2759 if size[0] is None:
2758 if size[0] is None:
2760 size[0] = 0
2759 size[0] = 0
2761
2760
2762 numdeltas = numrevs - numfull
2761 numdeltas = numrevs - numfull
2763 numoprev = numprev - nump1prev - nump2prev
2762 numoprev = numprev - nump1prev - nump2prev
2764 totalrawsize = datasize[2]
2763 totalrawsize = datasize[2]
2765 datasize[2] /= numrevs
2764 datasize[2] /= numrevs
2766 fulltotal = fullsize[2]
2765 fulltotal = fullsize[2]
2767 fullsize[2] /= numfull
2766 fullsize[2] /= numfull
2768 deltatotal = deltasize[2]
2767 deltatotal = deltasize[2]
2769 if numrevs - numfull > 0:
2768 if numrevs - numfull > 0:
2770 deltasize[2] /= numrevs - numfull
2769 deltasize[2] /= numrevs - numfull
2771 totalsize = fulltotal + deltatotal
2770 totalsize = fulltotal + deltatotal
2772 avgchainlen = sum(chainlengths) / numrevs
2771 avgchainlen = sum(chainlengths) / numrevs
2773 compratio = totalrawsize / totalsize
2772 compratio = totalrawsize / totalsize
2774
2773
2775 basedfmtstr = '%%%dd\n'
2774 basedfmtstr = '%%%dd\n'
2776 basepcfmtstr = '%%%dd %s(%%5.2f%%%%)\n'
2775 basepcfmtstr = '%%%dd %s(%%5.2f%%%%)\n'
2777
2776
2778 def dfmtstr(max):
2777 def dfmtstr(max):
2779 return basedfmtstr % len(str(max))
2778 return basedfmtstr % len(str(max))
2780 def pcfmtstr(max, padding=0):
2779 def pcfmtstr(max, padding=0):
2781 return basepcfmtstr % (len(str(max)), ' ' * padding)
2780 return basepcfmtstr % (len(str(max)), ' ' * padding)
2782
2781
2783 def pcfmt(value, total):
2782 def pcfmt(value, total):
2784 return (value, 100 * float(value) / total)
2783 return (value, 100 * float(value) / total)
2785
2784
2786 ui.write(('format : %d\n') % format)
2785 ui.write(('format : %d\n') % format)
2787 ui.write(('flags : %s\n') % ', '.join(flags))
2786 ui.write(('flags : %s\n') % ', '.join(flags))
2788
2787
2789 ui.write('\n')
2788 ui.write('\n')
2790 fmt = pcfmtstr(totalsize)
2789 fmt = pcfmtstr(totalsize)
2791 fmt2 = dfmtstr(totalsize)
2790 fmt2 = dfmtstr(totalsize)
2792 ui.write(('revisions : ') + fmt2 % numrevs)
2791 ui.write(('revisions : ') + fmt2 % numrevs)
2793 ui.write((' merges : ') + fmt % pcfmt(nummerges, numrevs))
2792 ui.write((' merges : ') + fmt % pcfmt(nummerges, numrevs))
2794 ui.write((' normal : ') + fmt % pcfmt(numrevs - nummerges, numrevs))
2793 ui.write((' normal : ') + fmt % pcfmt(numrevs - nummerges, numrevs))
2795 ui.write(('revisions : ') + fmt2 % numrevs)
2794 ui.write(('revisions : ') + fmt2 % numrevs)
2796 ui.write((' full : ') + fmt % pcfmt(numfull, numrevs))
2795 ui.write((' full : ') + fmt % pcfmt(numfull, numrevs))
2797 ui.write((' deltas : ') + fmt % pcfmt(numdeltas, numrevs))
2796 ui.write((' deltas : ') + fmt % pcfmt(numdeltas, numrevs))
2798 ui.write(('revision size : ') + fmt2 % totalsize)
2797 ui.write(('revision size : ') + fmt2 % totalsize)
2799 ui.write((' full : ') + fmt % pcfmt(fulltotal, totalsize))
2798 ui.write((' full : ') + fmt % pcfmt(fulltotal, totalsize))
2800 ui.write((' deltas : ') + fmt % pcfmt(deltatotal, totalsize))
2799 ui.write((' deltas : ') + fmt % pcfmt(deltatotal, totalsize))
2801
2800
2802 ui.write('\n')
2801 ui.write('\n')
2803 fmt = dfmtstr(max(avgchainlen, compratio))
2802 fmt = dfmtstr(max(avgchainlen, compratio))
2804 ui.write(('avg chain length : ') + fmt % avgchainlen)
2803 ui.write(('avg chain length : ') + fmt % avgchainlen)
2805 ui.write(('compression ratio : ') + fmt % compratio)
2804 ui.write(('compression ratio : ') + fmt % compratio)
2806
2805
2807 if format > 0:
2806 if format > 0:
2808 ui.write('\n')
2807 ui.write('\n')
2809 ui.write(('uncompressed data size (min/max/avg) : %d / %d / %d\n')
2808 ui.write(('uncompressed data size (min/max/avg) : %d / %d / %d\n')
2810 % tuple(datasize))
2809 % tuple(datasize))
2811 ui.write(('full revision size (min/max/avg) : %d / %d / %d\n')
2810 ui.write(('full revision size (min/max/avg) : %d / %d / %d\n')
2812 % tuple(fullsize))
2811 % tuple(fullsize))
2813 ui.write(('delta size (min/max/avg) : %d / %d / %d\n')
2812 ui.write(('delta size (min/max/avg) : %d / %d / %d\n')
2814 % tuple(deltasize))
2813 % tuple(deltasize))
2815
2814
2816 if numdeltas > 0:
2815 if numdeltas > 0:
2817 ui.write('\n')
2816 ui.write('\n')
2818 fmt = pcfmtstr(numdeltas)
2817 fmt = pcfmtstr(numdeltas)
2819 fmt2 = pcfmtstr(numdeltas, 4)
2818 fmt2 = pcfmtstr(numdeltas, 4)
2820 ui.write(('deltas against prev : ') + fmt % pcfmt(numprev, numdeltas))
2819 ui.write(('deltas against prev : ') + fmt % pcfmt(numprev, numdeltas))
2821 if numprev > 0:
2820 if numprev > 0:
2822 ui.write((' where prev = p1 : ') + fmt2 % pcfmt(nump1prev,
2821 ui.write((' where prev = p1 : ') + fmt2 % pcfmt(nump1prev,
2823 numprev))
2822 numprev))
2824 ui.write((' where prev = p2 : ') + fmt2 % pcfmt(nump2prev,
2823 ui.write((' where prev = p2 : ') + fmt2 % pcfmt(nump2prev,
2825 numprev))
2824 numprev))
2826 ui.write((' other : ') + fmt2 % pcfmt(numoprev,
2825 ui.write((' other : ') + fmt2 % pcfmt(numoprev,
2827 numprev))
2826 numprev))
2828 if gdelta:
2827 if gdelta:
2829 ui.write(('deltas against p1 : ')
2828 ui.write(('deltas against p1 : ')
2830 + fmt % pcfmt(nump1, numdeltas))
2829 + fmt % pcfmt(nump1, numdeltas))
2831 ui.write(('deltas against p2 : ')
2830 ui.write(('deltas against p2 : ')
2832 + fmt % pcfmt(nump2, numdeltas))
2831 + fmt % pcfmt(nump2, numdeltas))
2833 ui.write(('deltas against other : ') + fmt % pcfmt(numother,
2832 ui.write(('deltas against other : ') + fmt % pcfmt(numother,
2834 numdeltas))
2833 numdeltas))
2835
2834
2836 @command('debugrevspec',
2835 @command('debugrevspec',
2837 [('', 'optimize', None, _('print parsed tree after optimizing'))],
2836 [('', 'optimize', None, _('print parsed tree after optimizing'))],
2838 ('REVSPEC'))
2837 ('REVSPEC'))
2839 def debugrevspec(ui, repo, expr, **opts):
2838 def debugrevspec(ui, repo, expr, **opts):
2840 """parse and apply a revision specification
2839 """parse and apply a revision specification
2841
2840
2842 Use --verbose to print the parsed tree before and after aliases
2841 Use --verbose to print the parsed tree before and after aliases
2843 expansion.
2842 expansion.
2844 """
2843 """
2845 if ui.verbose:
2844 if ui.verbose:
2846 tree = revset.parse(expr)[0]
2845 tree = revset.parse(expr)[0]
2847 ui.note(revset.prettyformat(tree), "\n")
2846 ui.note(revset.prettyformat(tree), "\n")
2848 newtree = revset.findaliases(ui, tree)
2847 newtree = revset.findaliases(ui, tree)
2849 if newtree != tree:
2848 if newtree != tree:
2850 ui.note(revset.prettyformat(newtree), "\n")
2849 ui.note(revset.prettyformat(newtree), "\n")
2851 tree = newtree
2850 tree = newtree
2852 newtree = revset.foldconcat(tree)
2851 newtree = revset.foldconcat(tree)
2853 if newtree != tree:
2852 if newtree != tree:
2854 ui.note(revset.prettyformat(newtree), "\n")
2853 ui.note(revset.prettyformat(newtree), "\n")
2855 if opts["optimize"]:
2854 if opts["optimize"]:
2856 weight, optimizedtree = revset.optimize(newtree, True)
2855 weight, optimizedtree = revset.optimize(newtree, True)
2857 ui.note("* optimized:\n", revset.prettyformat(optimizedtree), "\n")
2856 ui.note("* optimized:\n", revset.prettyformat(optimizedtree), "\n")
2858 func = revset.match(ui, expr)
2857 func = revset.match(ui, expr)
2859 for c in func(repo, revset.spanset(repo)):
2858 for c in func(repo, revset.spanset(repo)):
2860 ui.write("%s\n" % c)
2859 ui.write("%s\n" % c)
2861
2860
2862 @command('debugsetparents', [], _('REV1 [REV2]'))
2861 @command('debugsetparents', [], _('REV1 [REV2]'))
2863 def debugsetparents(ui, repo, rev1, rev2=None):
2862 def debugsetparents(ui, repo, rev1, rev2=None):
2864 """manually set the parents of the current working directory
2863 """manually set the parents of the current working directory
2865
2864
2866 This is useful for writing repository conversion tools, but should
2865 This is useful for writing repository conversion tools, but should
2867 be used with care.
2866 be used with care.
2868
2867
2869 Returns 0 on success.
2868 Returns 0 on success.
2870 """
2869 """
2871
2870
2872 r1 = scmutil.revsingle(repo, rev1).node()
2871 r1 = scmutil.revsingle(repo, rev1).node()
2873 r2 = scmutil.revsingle(repo, rev2, 'null').node()
2872 r2 = scmutil.revsingle(repo, rev2, 'null').node()
2874
2873
2875 wlock = repo.wlock()
2874 wlock = repo.wlock()
2876 try:
2875 try:
2877 repo.dirstate.beginparentchange()
2876 repo.dirstate.beginparentchange()
2878 repo.setparents(r1, r2)
2877 repo.setparents(r1, r2)
2879 repo.dirstate.endparentchange()
2878 repo.dirstate.endparentchange()
2880 finally:
2879 finally:
2881 wlock.release()
2880 wlock.release()
2882
2881
2883 @command('debugdirstate|debugstate',
2882 @command('debugdirstate|debugstate',
2884 [('', 'nodates', None, _('do not display the saved mtime')),
2883 [('', 'nodates', None, _('do not display the saved mtime')),
2885 ('', 'datesort', None, _('sort by saved mtime'))],
2884 ('', 'datesort', None, _('sort by saved mtime'))],
2886 _('[OPTION]...'))
2885 _('[OPTION]...'))
2887 def debugstate(ui, repo, nodates=None, datesort=None):
2886 def debugstate(ui, repo, nodates=None, datesort=None):
2888 """show the contents of the current dirstate"""
2887 """show the contents of the current dirstate"""
2889 timestr = ""
2888 timestr = ""
2890 if datesort:
2889 if datesort:
2891 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
2890 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
2892 else:
2891 else:
2893 keyfunc = None # sort by filename
2892 keyfunc = None # sort by filename
2894 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
2893 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
2895 if ent[3] == -1:
2894 if ent[3] == -1:
2896 timestr = 'unset '
2895 timestr = 'unset '
2897 elif nodates:
2896 elif nodates:
2898 timestr = 'set '
2897 timestr = 'set '
2899 else:
2898 else:
2900 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
2899 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
2901 time.localtime(ent[3]))
2900 time.localtime(ent[3]))
2902 if ent[1] & 020000:
2901 if ent[1] & 020000:
2903 mode = 'lnk'
2902 mode = 'lnk'
2904 else:
2903 else:
2905 mode = '%3o' % (ent[1] & 0777 & ~util.umask)
2904 mode = '%3o' % (ent[1] & 0777 & ~util.umask)
2906 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
2905 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
2907 for f in repo.dirstate.copies():
2906 for f in repo.dirstate.copies():
2908 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
2907 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
2909
2908
2910 @command('debugsub',
2909 @command('debugsub',
2911 [('r', 'rev', '',
2910 [('r', 'rev', '',
2912 _('revision to check'), _('REV'))],
2911 _('revision to check'), _('REV'))],
2913 _('[-r REV] [REV]'))
2912 _('[-r REV] [REV]'))
2914 def debugsub(ui, repo, rev=None):
2913 def debugsub(ui, repo, rev=None):
2915 ctx = scmutil.revsingle(repo, rev, None)
2914 ctx = scmutil.revsingle(repo, rev, None)
2916 for k, v in sorted(ctx.substate.items()):
2915 for k, v in sorted(ctx.substate.items()):
2917 ui.write(('path %s\n') % k)
2916 ui.write(('path %s\n') % k)
2918 ui.write((' source %s\n') % v[0])
2917 ui.write((' source %s\n') % v[0])
2919 ui.write((' revision %s\n') % v[1])
2918 ui.write((' revision %s\n') % v[1])
2920
2919
2921 @command('debugsuccessorssets',
2920 @command('debugsuccessorssets',
2922 [],
2921 [],
2923 _('[REV]'))
2922 _('[REV]'))
2924 def debugsuccessorssets(ui, repo, *revs):
2923 def debugsuccessorssets(ui, repo, *revs):
2925 """show set of successors for revision
2924 """show set of successors for revision
2926
2925
2927 A successors set of changeset A is a consistent group of revisions that
2926 A successors set of changeset A is a consistent group of revisions that
2928 succeed A. It contains non-obsolete changesets only.
2927 succeed A. It contains non-obsolete changesets only.
2929
2928
2930 In most cases a changeset A has a single successors set containing a single
2929 In most cases a changeset A has a single successors set containing a single
2931 successor (changeset A replaced by A').
2930 successor (changeset A replaced by A').
2932
2931
2933 A changeset that is made obsolete with no successors are called "pruned".
2932 A changeset that is made obsolete with no successors are called "pruned".
2934 Such changesets have no successors sets at all.
2933 Such changesets have no successors sets at all.
2935
2934
2936 A changeset that has been "split" will have a successors set containing
2935 A changeset that has been "split" will have a successors set containing
2937 more than one successor.
2936 more than one successor.
2938
2937
2939 A changeset that has been rewritten in multiple different ways is called
2938 A changeset that has been rewritten in multiple different ways is called
2940 "divergent". Such changesets have multiple successor sets (each of which
2939 "divergent". Such changesets have multiple successor sets (each of which
2941 may also be split, i.e. have multiple successors).
2940 may also be split, i.e. have multiple successors).
2942
2941
2943 Results are displayed as follows::
2942 Results are displayed as follows::
2944
2943
2945 <rev1>
2944 <rev1>
2946 <successors-1A>
2945 <successors-1A>
2947 <rev2>
2946 <rev2>
2948 <successors-2A>
2947 <successors-2A>
2949 <successors-2B1> <successors-2B2> <successors-2B3>
2948 <successors-2B1> <successors-2B2> <successors-2B3>
2950
2949
2951 Here rev2 has two possible (i.e. divergent) successors sets. The first
2950 Here rev2 has two possible (i.e. divergent) successors sets. The first
2952 holds one element, whereas the second holds three (i.e. the changeset has
2951 holds one element, whereas the second holds three (i.e. the changeset has
2953 been split).
2952 been split).
2954 """
2953 """
2955 # passed to successorssets caching computation from one call to another
2954 # passed to successorssets caching computation from one call to another
2956 cache = {}
2955 cache = {}
2957 ctx2str = str
2956 ctx2str = str
2958 node2str = short
2957 node2str = short
2959 if ui.debug():
2958 if ui.debug():
2960 def ctx2str(ctx):
2959 def ctx2str(ctx):
2961 return ctx.hex()
2960 return ctx.hex()
2962 node2str = hex
2961 node2str = hex
2963 for rev in scmutil.revrange(repo, revs):
2962 for rev in scmutil.revrange(repo, revs):
2964 ctx = repo[rev]
2963 ctx = repo[rev]
2965 ui.write('%s\n'% ctx2str(ctx))
2964 ui.write('%s\n'% ctx2str(ctx))
2966 for succsset in obsolete.successorssets(repo, ctx.node(), cache):
2965 for succsset in obsolete.successorssets(repo, ctx.node(), cache):
2967 if succsset:
2966 if succsset:
2968 ui.write(' ')
2967 ui.write(' ')
2969 ui.write(node2str(succsset[0]))
2968 ui.write(node2str(succsset[0]))
2970 for node in succsset[1:]:
2969 for node in succsset[1:]:
2971 ui.write(' ')
2970 ui.write(' ')
2972 ui.write(node2str(node))
2971 ui.write(node2str(node))
2973 ui.write('\n')
2972 ui.write('\n')
2974
2973
2975 @command('debugwalk', walkopts, _('[OPTION]... [FILE]...'), inferrepo=True)
2974 @command('debugwalk', walkopts, _('[OPTION]... [FILE]...'), inferrepo=True)
2976 def debugwalk(ui, repo, *pats, **opts):
2975 def debugwalk(ui, repo, *pats, **opts):
2977 """show how files match on given patterns"""
2976 """show how files match on given patterns"""
2978 m = scmutil.match(repo[None], pats, opts)
2977 m = scmutil.match(repo[None], pats, opts)
2979 items = list(repo.walk(m))
2978 items = list(repo.walk(m))
2980 if not items:
2979 if not items:
2981 return
2980 return
2982 f = lambda fn: fn
2981 f = lambda fn: fn
2983 if ui.configbool('ui', 'slash') and os.sep != '/':
2982 if ui.configbool('ui', 'slash') and os.sep != '/':
2984 f = lambda fn: util.normpath(fn)
2983 f = lambda fn: util.normpath(fn)
2985 fmt = 'f %%-%ds %%-%ds %%s' % (
2984 fmt = 'f %%-%ds %%-%ds %%s' % (
2986 max([len(abs) for abs in items]),
2985 max([len(abs) for abs in items]),
2987 max([len(m.rel(abs)) for abs in items]))
2986 max([len(m.rel(abs)) for abs in items]))
2988 for abs in items:
2987 for abs in items:
2989 line = fmt % (abs, f(m.rel(abs)), m.exact(abs) and 'exact' or '')
2988 line = fmt % (abs, f(m.rel(abs)), m.exact(abs) and 'exact' or '')
2990 ui.write("%s\n" % line.rstrip())
2989 ui.write("%s\n" % line.rstrip())
2991
2990
2992 @command('debugwireargs',
2991 @command('debugwireargs',
2993 [('', 'three', '', 'three'),
2992 [('', 'three', '', 'three'),
2994 ('', 'four', '', 'four'),
2993 ('', 'four', '', 'four'),
2995 ('', 'five', '', 'five'),
2994 ('', 'five', '', 'five'),
2996 ] + remoteopts,
2995 ] + remoteopts,
2997 _('REPO [OPTIONS]... [ONE [TWO]]'),
2996 _('REPO [OPTIONS]... [ONE [TWO]]'),
2998 norepo=True)
2997 norepo=True)
2999 def debugwireargs(ui, repopath, *vals, **opts):
2998 def debugwireargs(ui, repopath, *vals, **opts):
3000 repo = hg.peer(ui, opts, repopath)
2999 repo = hg.peer(ui, opts, repopath)
3001 for opt in remoteopts:
3000 for opt in remoteopts:
3002 del opts[opt[1]]
3001 del opts[opt[1]]
3003 args = {}
3002 args = {}
3004 for k, v in opts.iteritems():
3003 for k, v in opts.iteritems():
3005 if v:
3004 if v:
3006 args[k] = v
3005 args[k] = v
3007 # run twice to check that we don't mess up the stream for the next command
3006 # run twice to check that we don't mess up the stream for the next command
3008 res1 = repo.debugwireargs(*vals, **args)
3007 res1 = repo.debugwireargs(*vals, **args)
3009 res2 = repo.debugwireargs(*vals, **args)
3008 res2 = repo.debugwireargs(*vals, **args)
3010 ui.write("%s\n" % res1)
3009 ui.write("%s\n" % res1)
3011 if res1 != res2:
3010 if res1 != res2:
3012 ui.warn("%s\n" % res2)
3011 ui.warn("%s\n" % res2)
3013
3012
3014 @command('^diff',
3013 @command('^diff',
3015 [('r', 'rev', [], _('revision'), _('REV')),
3014 [('r', 'rev', [], _('revision'), _('REV')),
3016 ('c', 'change', '', _('change made by revision'), _('REV'))
3015 ('c', 'change', '', _('change made by revision'), _('REV'))
3017 ] + diffopts + diffopts2 + walkopts + subrepoopts,
3016 ] + diffopts + diffopts2 + walkopts + subrepoopts,
3018 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
3017 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
3019 inferrepo=True)
3018 inferrepo=True)
3020 def diff(ui, repo, *pats, **opts):
3019 def diff(ui, repo, *pats, **opts):
3021 """diff repository (or selected files)
3020 """diff repository (or selected files)
3022
3021
3023 Show differences between revisions for the specified files.
3022 Show differences between revisions for the specified files.
3024
3023
3025 Differences between files are shown using the unified diff format.
3024 Differences between files are shown using the unified diff format.
3026
3025
3027 .. note::
3026 .. note::
3028
3027
3029 diff may generate unexpected results for merges, as it will
3028 diff may generate unexpected results for merges, as it will
3030 default to comparing against the working directory's first
3029 default to comparing against the working directory's first
3031 parent changeset if no revisions are specified.
3030 parent changeset if no revisions are specified.
3032
3031
3033 When two revision arguments are given, then changes are shown
3032 When two revision arguments are given, then changes are shown
3034 between those revisions. If only one revision is specified then
3033 between those revisions. If only one revision is specified then
3035 that revision is compared to the working directory, and, when no
3034 that revision is compared to the working directory, and, when no
3036 revisions are specified, the working directory files are compared
3035 revisions are specified, the working directory files are compared
3037 to its parent.
3036 to its parent.
3038
3037
3039 Alternatively you can specify -c/--change with a revision to see
3038 Alternatively you can specify -c/--change with a revision to see
3040 the changes in that changeset relative to its first parent.
3039 the changes in that changeset relative to its first parent.
3041
3040
3042 Without the -a/--text option, diff will avoid generating diffs of
3041 Without the -a/--text option, diff will avoid generating diffs of
3043 files it detects as binary. With -a, diff will generate a diff
3042 files it detects as binary. With -a, diff will generate a diff
3044 anyway, probably with undesirable results.
3043 anyway, probably with undesirable results.
3045
3044
3046 Use the -g/--git option to generate diffs in the git extended diff
3045 Use the -g/--git option to generate diffs in the git extended diff
3047 format. For more information, read :hg:`help diffs`.
3046 format. For more information, read :hg:`help diffs`.
3048
3047
3049 .. container:: verbose
3048 .. container:: verbose
3050
3049
3051 Examples:
3050 Examples:
3052
3051
3053 - compare a file in the current working directory to its parent::
3052 - compare a file in the current working directory to its parent::
3054
3053
3055 hg diff foo.c
3054 hg diff foo.c
3056
3055
3057 - compare two historical versions of a directory, with rename info::
3056 - compare two historical versions of a directory, with rename info::
3058
3057
3059 hg diff --git -r 1.0:1.2 lib/
3058 hg diff --git -r 1.0:1.2 lib/
3060
3059
3061 - get change stats relative to the last change on some date::
3060 - get change stats relative to the last change on some date::
3062
3061
3063 hg diff --stat -r "date('may 2')"
3062 hg diff --stat -r "date('may 2')"
3064
3063
3065 - diff all newly-added files that contain a keyword::
3064 - diff all newly-added files that contain a keyword::
3066
3065
3067 hg diff "set:added() and grep(GNU)"
3066 hg diff "set:added() and grep(GNU)"
3068
3067
3069 - compare a revision and its parents::
3068 - compare a revision and its parents::
3070
3069
3071 hg diff -c 9353 # compare against first parent
3070 hg diff -c 9353 # compare against first parent
3072 hg diff -r 9353^:9353 # same using revset syntax
3071 hg diff -r 9353^:9353 # same using revset syntax
3073 hg diff -r 9353^2:9353 # compare against the second parent
3072 hg diff -r 9353^2:9353 # compare against the second parent
3074
3073
3075 Returns 0 on success.
3074 Returns 0 on success.
3076 """
3075 """
3077
3076
3078 revs = opts.get('rev')
3077 revs = opts.get('rev')
3079 change = opts.get('change')
3078 change = opts.get('change')
3080 stat = opts.get('stat')
3079 stat = opts.get('stat')
3081 reverse = opts.get('reverse')
3080 reverse = opts.get('reverse')
3082
3081
3083 if revs and change:
3082 if revs and change:
3084 msg = _('cannot specify --rev and --change at the same time')
3083 msg = _('cannot specify --rev and --change at the same time')
3085 raise util.Abort(msg)
3084 raise util.Abort(msg)
3086 elif change:
3085 elif change:
3087 node2 = scmutil.revsingle(repo, change, None).node()
3086 node2 = scmutil.revsingle(repo, change, None).node()
3088 node1 = repo[node2].p1().node()
3087 node1 = repo[node2].p1().node()
3089 else:
3088 else:
3090 node1, node2 = scmutil.revpair(repo, revs)
3089 node1, node2 = scmutil.revpair(repo, revs)
3091
3090
3092 if reverse:
3091 if reverse:
3093 node1, node2 = node2, node1
3092 node1, node2 = node2, node1
3094
3093
3095 diffopts = patch.diffallopts(ui, opts)
3094 diffopts = patch.diffallopts(ui, opts)
3096 m = scmutil.match(repo[node2], pats, opts)
3095 m = scmutil.match(repo[node2], pats, opts)
3097 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
3096 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
3098 listsubrepos=opts.get('subrepos'))
3097 listsubrepos=opts.get('subrepos'))
3099
3098
3100 @command('^export',
3099 @command('^export',
3101 [('o', 'output', '',
3100 [('o', 'output', '',
3102 _('print output to file with formatted name'), _('FORMAT')),
3101 _('print output to file with formatted name'), _('FORMAT')),
3103 ('', 'switch-parent', None, _('diff against the second parent')),
3102 ('', 'switch-parent', None, _('diff against the second parent')),
3104 ('r', 'rev', [], _('revisions to export'), _('REV')),
3103 ('r', 'rev', [], _('revisions to export'), _('REV')),
3105 ] + diffopts,
3104 ] + diffopts,
3106 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'))
3105 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'))
3107 def export(ui, repo, *changesets, **opts):
3106 def export(ui, repo, *changesets, **opts):
3108 """dump the header and diffs for one or more changesets
3107 """dump the header and diffs for one or more changesets
3109
3108
3110 Print the changeset header and diffs for one or more revisions.
3109 Print the changeset header and diffs for one or more revisions.
3111 If no revision is given, the parent of the working directory is used.
3110 If no revision is given, the parent of the working directory is used.
3112
3111
3113 The information shown in the changeset header is: author, date,
3112 The information shown in the changeset header is: author, date,
3114 branch name (if non-default), changeset hash, parent(s) and commit
3113 branch name (if non-default), changeset hash, parent(s) and commit
3115 comment.
3114 comment.
3116
3115
3117 .. note::
3116 .. note::
3118
3117
3119 export may generate unexpected diff output for merge
3118 export may generate unexpected diff output for merge
3120 changesets, as it will compare the merge changeset against its
3119 changesets, as it will compare the merge changeset against its
3121 first parent only.
3120 first parent only.
3122
3121
3123 Output may be to a file, in which case the name of the file is
3122 Output may be to a file, in which case the name of the file is
3124 given using a format string. The formatting rules are as follows:
3123 given using a format string. The formatting rules are as follows:
3125
3124
3126 :``%%``: literal "%" character
3125 :``%%``: literal "%" character
3127 :``%H``: changeset hash (40 hexadecimal digits)
3126 :``%H``: changeset hash (40 hexadecimal digits)
3128 :``%N``: number of patches being generated
3127 :``%N``: number of patches being generated
3129 :``%R``: changeset revision number
3128 :``%R``: changeset revision number
3130 :``%b``: basename of the exporting repository
3129 :``%b``: basename of the exporting repository
3131 :``%h``: short-form changeset hash (12 hexadecimal digits)
3130 :``%h``: short-form changeset hash (12 hexadecimal digits)
3132 :``%m``: first line of the commit message (only alphanumeric characters)
3131 :``%m``: first line of the commit message (only alphanumeric characters)
3133 :``%n``: zero-padded sequence number, starting at 1
3132 :``%n``: zero-padded sequence number, starting at 1
3134 :``%r``: zero-padded changeset revision number
3133 :``%r``: zero-padded changeset revision number
3135
3134
3136 Without the -a/--text option, export will avoid generating diffs
3135 Without the -a/--text option, export will avoid generating diffs
3137 of files it detects as binary. With -a, export will generate a
3136 of files it detects as binary. With -a, export will generate a
3138 diff anyway, probably with undesirable results.
3137 diff anyway, probably with undesirable results.
3139
3138
3140 Use the -g/--git option to generate diffs in the git extended diff
3139 Use the -g/--git option to generate diffs in the git extended diff
3141 format. See :hg:`help diffs` for more information.
3140 format. See :hg:`help diffs` for more information.
3142
3141
3143 With the --switch-parent option, the diff will be against the
3142 With the --switch-parent option, the diff will be against the
3144 second parent. It can be useful to review a merge.
3143 second parent. It can be useful to review a merge.
3145
3144
3146 .. container:: verbose
3145 .. container:: verbose
3147
3146
3148 Examples:
3147 Examples:
3149
3148
3150 - use export and import to transplant a bugfix to the current
3149 - use export and import to transplant a bugfix to the current
3151 branch::
3150 branch::
3152
3151
3153 hg export -r 9353 | hg import -
3152 hg export -r 9353 | hg import -
3154
3153
3155 - export all the changesets between two revisions to a file with
3154 - export all the changesets between two revisions to a file with
3156 rename information::
3155 rename information::
3157
3156
3158 hg export --git -r 123:150 > changes.txt
3157 hg export --git -r 123:150 > changes.txt
3159
3158
3160 - split outgoing changes into a series of patches with
3159 - split outgoing changes into a series of patches with
3161 descriptive names::
3160 descriptive names::
3162
3161
3163 hg export -r "outgoing()" -o "%n-%m.patch"
3162 hg export -r "outgoing()" -o "%n-%m.patch"
3164
3163
3165 Returns 0 on success.
3164 Returns 0 on success.
3166 """
3165 """
3167 changesets += tuple(opts.get('rev', []))
3166 changesets += tuple(opts.get('rev', []))
3168 if not changesets:
3167 if not changesets:
3169 changesets = ['.']
3168 changesets = ['.']
3170 revs = scmutil.revrange(repo, changesets)
3169 revs = scmutil.revrange(repo, changesets)
3171 if not revs:
3170 if not revs:
3172 raise util.Abort(_("export requires at least one changeset"))
3171 raise util.Abort(_("export requires at least one changeset"))
3173 if len(revs) > 1:
3172 if len(revs) > 1:
3174 ui.note(_('exporting patches:\n'))
3173 ui.note(_('exporting patches:\n'))
3175 else:
3174 else:
3176 ui.note(_('exporting patch:\n'))
3175 ui.note(_('exporting patch:\n'))
3177 cmdutil.export(repo, revs, template=opts.get('output'),
3176 cmdutil.export(repo, revs, template=opts.get('output'),
3178 switch_parent=opts.get('switch_parent'),
3177 switch_parent=opts.get('switch_parent'),
3179 opts=patch.diffallopts(ui, opts))
3178 opts=patch.diffallopts(ui, opts))
3180
3179
3181 @command('files',
3180 @command('files',
3182 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3181 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3183 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3182 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3184 ] + walkopts + formatteropts,
3183 ] + walkopts + formatteropts,
3185 _('[OPTION]... [PATTERN]...'))
3184 _('[OPTION]... [PATTERN]...'))
3186 def files(ui, repo, *pats, **opts):
3185 def files(ui, repo, *pats, **opts):
3187 """list tracked files
3186 """list tracked files
3188
3187
3189 Print files under Mercurial control in the working directory or
3188 Print files under Mercurial control in the working directory or
3190 specified revision whose names match the given patterns (excluding
3189 specified revision whose names match the given patterns (excluding
3191 removed files).
3190 removed files).
3192
3191
3193 If no patterns are given to match, this command prints the names
3192 If no patterns are given to match, this command prints the names
3194 of all files under Mercurial control in the working copy.
3193 of all files under Mercurial control in the working copy.
3195
3194
3196 .. container:: verbose
3195 .. container:: verbose
3197
3196
3198 Examples:
3197 Examples:
3199
3198
3200 - list all files under the current directory::
3199 - list all files under the current directory::
3201
3200
3202 hg files .
3201 hg files .
3203
3202
3204 - shows sizes and flags for current revision::
3203 - shows sizes and flags for current revision::
3205
3204
3206 hg files -vr .
3205 hg files -vr .
3207
3206
3208 - list all files named README::
3207 - list all files named README::
3209
3208
3210 hg files -I "**/README"
3209 hg files -I "**/README"
3211
3210
3212 - list all binary files::
3211 - list all binary files::
3213
3212
3214 hg files "set:binary()"
3213 hg files "set:binary()"
3215
3214
3216 - find files containing a regular expression::
3215 - find files containing a regular expression::
3217
3216
3218 hg files "set:grep('bob')"
3217 hg files "set:grep('bob')"
3219
3218
3220 - search tracked file contents with xargs and grep::
3219 - search tracked file contents with xargs and grep::
3221
3220
3222 hg files -0 | xargs -0 grep foo
3221 hg files -0 | xargs -0 grep foo
3223
3222
3224 See :hg:`help patterns` and :hg:`help filesets` for more information
3223 See :hg:`help patterns` and :hg:`help filesets` for more information
3225 on specifying file patterns.
3224 on specifying file patterns.
3226
3225
3227 Returns 0 if a match is found, 1 otherwise.
3226 Returns 0 if a match is found, 1 otherwise.
3228
3227
3229 """
3228 """
3230 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
3229 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
3231 rev = ctx.rev()
3230 rev = ctx.rev()
3232 ret = 1
3231 ret = 1
3233
3232
3234 end = '\n'
3233 end = '\n'
3235 if opts.get('print0'):
3234 if opts.get('print0'):
3236 end = '\0'
3235 end = '\0'
3237 fm = ui.formatter('files', opts)
3236 fm = ui.formatter('files', opts)
3238 fmt = '%s' + end
3237 fmt = '%s' + end
3239
3238
3240 m = scmutil.match(ctx, pats, opts)
3239 m = scmutil.match(ctx, pats, opts)
3241 ds = repo.dirstate
3240 ds = repo.dirstate
3242 for f in ctx.matches(m):
3241 for f in ctx.matches(m):
3243 if rev is None and ds[f] == 'r':
3242 if rev is None and ds[f] == 'r':
3244 continue
3243 continue
3245 fm.startitem()
3244 fm.startitem()
3246 if ui.verbose:
3245 if ui.verbose:
3247 fc = ctx[f]
3246 fc = ctx[f]
3248 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
3247 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
3249 fm.data(abspath=f)
3248 fm.data(abspath=f)
3250 fm.write('path', fmt, m.rel(f))
3249 fm.write('path', fmt, m.rel(f))
3251 ret = 0
3250 ret = 0
3252
3251
3253 fm.end()
3252 fm.end()
3254
3253
3255 return ret
3254 return ret
3256
3255
3257 @command('^forget', walkopts, _('[OPTION]... FILE...'), inferrepo=True)
3256 @command('^forget', walkopts, _('[OPTION]... FILE...'), inferrepo=True)
3258 def forget(ui, repo, *pats, **opts):
3257 def forget(ui, repo, *pats, **opts):
3259 """forget the specified files on the next commit
3258 """forget the specified files on the next commit
3260
3259
3261 Mark the specified files so they will no longer be tracked
3260 Mark the specified files so they will no longer be tracked
3262 after the next commit.
3261 after the next commit.
3263
3262
3264 This only removes files from the current branch, not from the
3263 This only removes files from the current branch, not from the
3265 entire project history, and it does not delete them from the
3264 entire project history, and it does not delete them from the
3266 working directory.
3265 working directory.
3267
3266
3268 To undo a forget before the next commit, see :hg:`add`.
3267 To undo a forget before the next commit, see :hg:`add`.
3269
3268
3270 .. container:: verbose
3269 .. container:: verbose
3271
3270
3272 Examples:
3271 Examples:
3273
3272
3274 - forget newly-added binary files::
3273 - forget newly-added binary files::
3275
3274
3276 hg forget "set:added() and binary()"
3275 hg forget "set:added() and binary()"
3277
3276
3278 - forget files that would be excluded by .hgignore::
3277 - forget files that would be excluded by .hgignore::
3279
3278
3280 hg forget "set:hgignore()"
3279 hg forget "set:hgignore()"
3281
3280
3282 Returns 0 on success.
3281 Returns 0 on success.
3283 """
3282 """
3284
3283
3285 if not pats:
3284 if not pats:
3286 raise util.Abort(_('no files specified'))
3285 raise util.Abort(_('no files specified'))
3287
3286
3288 m = scmutil.match(repo[None], pats, opts)
3287 m = scmutil.match(repo[None], pats, opts)
3289 rejected = cmdutil.forget(ui, repo, m, prefix="", explicitonly=False)[0]
3288 rejected = cmdutil.forget(ui, repo, m, prefix="", explicitonly=False)[0]
3290 return rejected and 1 or 0
3289 return rejected and 1 or 0
3291
3290
3292 @command(
3291 @command(
3293 'graft',
3292 'graft',
3294 [('r', 'rev', [], _('revisions to graft'), _('REV')),
3293 [('r', 'rev', [], _('revisions to graft'), _('REV')),
3295 ('c', 'continue', False, _('resume interrupted graft')),
3294 ('c', 'continue', False, _('resume interrupted graft')),
3296 ('e', 'edit', False, _('invoke editor on commit messages')),
3295 ('e', 'edit', False, _('invoke editor on commit messages')),
3297 ('', 'log', None, _('append graft info to log message')),
3296 ('', 'log', None, _('append graft info to log message')),
3298 ('f', 'force', False, _('force graft')),
3297 ('f', 'force', False, _('force graft')),
3299 ('D', 'currentdate', False,
3298 ('D', 'currentdate', False,
3300 _('record the current date as commit date')),
3299 _('record the current date as commit date')),
3301 ('U', 'currentuser', False,
3300 ('U', 'currentuser', False,
3302 _('record the current user as committer'), _('DATE'))]
3301 _('record the current user as committer'), _('DATE'))]
3303 + commitopts2 + mergetoolopts + dryrunopts,
3302 + commitopts2 + mergetoolopts + dryrunopts,
3304 _('[OPTION]... [-r] REV...'))
3303 _('[OPTION]... [-r] REV...'))
3305 def graft(ui, repo, *revs, **opts):
3304 def graft(ui, repo, *revs, **opts):
3306 '''copy changes from other branches onto the current branch
3305 '''copy changes from other branches onto the current branch
3307
3306
3308 This command uses Mercurial's merge logic to copy individual
3307 This command uses Mercurial's merge logic to copy individual
3309 changes from other branches without merging branches in the
3308 changes from other branches without merging branches in the
3310 history graph. This is sometimes known as 'backporting' or
3309 history graph. This is sometimes known as 'backporting' or
3311 'cherry-picking'. By default, graft will copy user, date, and
3310 'cherry-picking'. By default, graft will copy user, date, and
3312 description from the source changesets.
3311 description from the source changesets.
3313
3312
3314 Changesets that are ancestors of the current revision, that have
3313 Changesets that are ancestors of the current revision, that have
3315 already been grafted, or that are merges will be skipped.
3314 already been grafted, or that are merges will be skipped.
3316
3315
3317 If --log is specified, log messages will have a comment appended
3316 If --log is specified, log messages will have a comment appended
3318 of the form::
3317 of the form::
3319
3318
3320 (grafted from CHANGESETHASH)
3319 (grafted from CHANGESETHASH)
3321
3320
3322 If --force is specified, revisions will be grafted even if they
3321 If --force is specified, revisions will be grafted even if they
3323 are already ancestors of or have been grafted to the destination.
3322 are already ancestors of or have been grafted to the destination.
3324 This is useful when the revisions have since been backed out.
3323 This is useful when the revisions have since been backed out.
3325
3324
3326 If a graft merge results in conflicts, the graft process is
3325 If a graft merge results in conflicts, the graft process is
3327 interrupted so that the current merge can be manually resolved.
3326 interrupted so that the current merge can be manually resolved.
3328 Once all conflicts are addressed, the graft process can be
3327 Once all conflicts are addressed, the graft process can be
3329 continued with the -c/--continue option.
3328 continued with the -c/--continue option.
3330
3329
3331 .. note::
3330 .. note::
3332
3331
3333 The -c/--continue option does not reapply earlier options, except
3332 The -c/--continue option does not reapply earlier options, except
3334 for --force.
3333 for --force.
3335
3334
3336 .. container:: verbose
3335 .. container:: verbose
3337
3336
3338 Examples:
3337 Examples:
3339
3338
3340 - copy a single change to the stable branch and edit its description::
3339 - copy a single change to the stable branch and edit its description::
3341
3340
3342 hg update stable
3341 hg update stable
3343 hg graft --edit 9393
3342 hg graft --edit 9393
3344
3343
3345 - graft a range of changesets with one exception, updating dates::
3344 - graft a range of changesets with one exception, updating dates::
3346
3345
3347 hg graft -D "2085::2093 and not 2091"
3346 hg graft -D "2085::2093 and not 2091"
3348
3347
3349 - continue a graft after resolving conflicts::
3348 - continue a graft after resolving conflicts::
3350
3349
3351 hg graft -c
3350 hg graft -c
3352
3351
3353 - show the source of a grafted changeset::
3352 - show the source of a grafted changeset::
3354
3353
3355 hg log --debug -r .
3354 hg log --debug -r .
3356
3355
3357 See :hg:`help revisions` and :hg:`help revsets` for more about
3356 See :hg:`help revisions` and :hg:`help revsets` for more about
3358 specifying revisions.
3357 specifying revisions.
3359
3358
3360 Returns 0 on successful completion.
3359 Returns 0 on successful completion.
3361 '''
3360 '''
3362
3361
3363 revs = list(revs)
3362 revs = list(revs)
3364 revs.extend(opts['rev'])
3363 revs.extend(opts['rev'])
3365
3364
3366 if not opts.get('user') and opts.get('currentuser'):
3365 if not opts.get('user') and opts.get('currentuser'):
3367 opts['user'] = ui.username()
3366 opts['user'] = ui.username()
3368 if not opts.get('date') and opts.get('currentdate'):
3367 if not opts.get('date') and opts.get('currentdate'):
3369 opts['date'] = "%d %d" % util.makedate()
3368 opts['date'] = "%d %d" % util.makedate()
3370
3369
3371 editor = cmdutil.getcommiteditor(editform='graft', **opts)
3370 editor = cmdutil.getcommiteditor(editform='graft', **opts)
3372
3371
3373 cont = False
3372 cont = False
3374 if opts['continue']:
3373 if opts['continue']:
3375 cont = True
3374 cont = True
3376 if revs:
3375 if revs:
3377 raise util.Abort(_("can't specify --continue and revisions"))
3376 raise util.Abort(_("can't specify --continue and revisions"))
3378 # read in unfinished revisions
3377 # read in unfinished revisions
3379 try:
3378 try:
3380 nodes = repo.vfs.read('graftstate').splitlines()
3379 nodes = repo.vfs.read('graftstate').splitlines()
3381 revs = [repo[node].rev() for node in nodes]
3380 revs = [repo[node].rev() for node in nodes]
3382 except IOError, inst:
3381 except IOError, inst:
3383 if inst.errno != errno.ENOENT:
3382 if inst.errno != errno.ENOENT:
3384 raise
3383 raise
3385 raise util.Abort(_("no graft state found, can't continue"))
3384 raise util.Abort(_("no graft state found, can't continue"))
3386 else:
3385 else:
3387 cmdutil.checkunfinished(repo)
3386 cmdutil.checkunfinished(repo)
3388 cmdutil.bailifchanged(repo)
3387 cmdutil.bailifchanged(repo)
3389 if not revs:
3388 if not revs:
3390 raise util.Abort(_('no revisions specified'))
3389 raise util.Abort(_('no revisions specified'))
3391 revs = scmutil.revrange(repo, revs)
3390 revs = scmutil.revrange(repo, revs)
3392
3391
3393 skipped = set()
3392 skipped = set()
3394 # check for merges
3393 # check for merges
3395 for rev in repo.revs('%ld and merge()', revs):
3394 for rev in repo.revs('%ld and merge()', revs):
3396 ui.warn(_('skipping ungraftable merge revision %s\n') % rev)
3395 ui.warn(_('skipping ungraftable merge revision %s\n') % rev)
3397 skipped.add(rev)
3396 skipped.add(rev)
3398 revs = [r for r in revs if r not in skipped]
3397 revs = [r for r in revs if r not in skipped]
3399 if not revs:
3398 if not revs:
3400 return -1
3399 return -1
3401
3400
3402 # Don't check in the --continue case, in effect retaining --force across
3401 # Don't check in the --continue case, in effect retaining --force across
3403 # --continues. That's because without --force, any revisions we decided to
3402 # --continues. That's because without --force, any revisions we decided to
3404 # skip would have been filtered out here, so they wouldn't have made their
3403 # skip would have been filtered out here, so they wouldn't have made their
3405 # way to the graftstate. With --force, any revisions we would have otherwise
3404 # way to the graftstate. With --force, any revisions we would have otherwise
3406 # skipped would not have been filtered out, and if they hadn't been applied
3405 # skipped would not have been filtered out, and if they hadn't been applied
3407 # already, they'd have been in the graftstate.
3406 # already, they'd have been in the graftstate.
3408 if not (cont or opts.get('force')):
3407 if not (cont or opts.get('force')):
3409 # check for ancestors of dest branch
3408 # check for ancestors of dest branch
3410 crev = repo['.'].rev()
3409 crev = repo['.'].rev()
3411 ancestors = repo.changelog.ancestors([crev], inclusive=True)
3410 ancestors = repo.changelog.ancestors([crev], inclusive=True)
3412 # Cannot use x.remove(y) on smart set, this has to be a list.
3411 # Cannot use x.remove(y) on smart set, this has to be a list.
3413 # XXX make this lazy in the future
3412 # XXX make this lazy in the future
3414 revs = list(revs)
3413 revs = list(revs)
3415 # don't mutate while iterating, create a copy
3414 # don't mutate while iterating, create a copy
3416 for rev in list(revs):
3415 for rev in list(revs):
3417 if rev in ancestors:
3416 if rev in ancestors:
3418 ui.warn(_('skipping ancestor revision %d:%s\n') %
3417 ui.warn(_('skipping ancestor revision %d:%s\n') %
3419 (rev, repo[rev]))
3418 (rev, repo[rev]))
3420 # XXX remove on list is slow
3419 # XXX remove on list is slow
3421 revs.remove(rev)
3420 revs.remove(rev)
3422 if not revs:
3421 if not revs:
3423 return -1
3422 return -1
3424
3423
3425 # analyze revs for earlier grafts
3424 # analyze revs for earlier grafts
3426 ids = {}
3425 ids = {}
3427 for ctx in repo.set("%ld", revs):
3426 for ctx in repo.set("%ld", revs):
3428 ids[ctx.hex()] = ctx.rev()
3427 ids[ctx.hex()] = ctx.rev()
3429 n = ctx.extra().get('source')
3428 n = ctx.extra().get('source')
3430 if n:
3429 if n:
3431 ids[n] = ctx.rev()
3430 ids[n] = ctx.rev()
3432
3431
3433 # check ancestors for earlier grafts
3432 # check ancestors for earlier grafts
3434 ui.debug('scanning for duplicate grafts\n')
3433 ui.debug('scanning for duplicate grafts\n')
3435
3434
3436 for rev in repo.changelog.findmissingrevs(revs, [crev]):
3435 for rev in repo.changelog.findmissingrevs(revs, [crev]):
3437 ctx = repo[rev]
3436 ctx = repo[rev]
3438 n = ctx.extra().get('source')
3437 n = ctx.extra().get('source')
3439 if n in ids:
3438 if n in ids:
3440 try:
3439 try:
3441 r = repo[n].rev()
3440 r = repo[n].rev()
3442 except error.RepoLookupError:
3441 except error.RepoLookupError:
3443 r = None
3442 r = None
3444 if r in revs:
3443 if r in revs:
3445 ui.warn(_('skipping revision %d:%s '
3444 ui.warn(_('skipping revision %d:%s '
3446 '(already grafted to %d:%s)\n')
3445 '(already grafted to %d:%s)\n')
3447 % (r, repo[r], rev, ctx))
3446 % (r, repo[r], rev, ctx))
3448 revs.remove(r)
3447 revs.remove(r)
3449 elif ids[n] in revs:
3448 elif ids[n] in revs:
3450 if r is None:
3449 if r is None:
3451 ui.warn(_('skipping already grafted revision %d:%s '
3450 ui.warn(_('skipping already grafted revision %d:%s '
3452 '(%d:%s also has unknown origin %s)\n')
3451 '(%d:%s also has unknown origin %s)\n')
3453 % (ids[n], repo[ids[n]], rev, ctx, n[:12]))
3452 % (ids[n], repo[ids[n]], rev, ctx, n[:12]))
3454 else:
3453 else:
3455 ui.warn(_('skipping already grafted revision %d:%s '
3454 ui.warn(_('skipping already grafted revision %d:%s '
3456 '(%d:%s also has origin %d:%s)\n')
3455 '(%d:%s also has origin %d:%s)\n')
3457 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12]))
3456 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12]))
3458 revs.remove(ids[n])
3457 revs.remove(ids[n])
3459 elif ctx.hex() in ids:
3458 elif ctx.hex() in ids:
3460 r = ids[ctx.hex()]
3459 r = ids[ctx.hex()]
3461 ui.warn(_('skipping already grafted revision %d:%s '
3460 ui.warn(_('skipping already grafted revision %d:%s '
3462 '(was grafted from %d:%s)\n') %
3461 '(was grafted from %d:%s)\n') %
3463 (r, repo[r], rev, ctx))
3462 (r, repo[r], rev, ctx))
3464 revs.remove(r)
3463 revs.remove(r)
3465 if not revs:
3464 if not revs:
3466 return -1
3465 return -1
3467
3466
3468 wlock = repo.wlock()
3467 wlock = repo.wlock()
3469 try:
3468 try:
3470 for pos, ctx in enumerate(repo.set("%ld", revs)):
3469 for pos, ctx in enumerate(repo.set("%ld", revs)):
3471 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
3470 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
3472 ctx.description().split('\n', 1)[0])
3471 ctx.description().split('\n', 1)[0])
3473 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
3472 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
3474 if names:
3473 if names:
3475 desc += ' (%s)' % ' '.join(names)
3474 desc += ' (%s)' % ' '.join(names)
3476 ui.status(_('grafting %s\n') % desc)
3475 ui.status(_('grafting %s\n') % desc)
3477 if opts.get('dry_run'):
3476 if opts.get('dry_run'):
3478 continue
3477 continue
3479
3478
3480 source = ctx.extra().get('source')
3479 source = ctx.extra().get('source')
3481 if not source:
3480 if not source:
3482 source = ctx.hex()
3481 source = ctx.hex()
3483 extra = {'source': source}
3482 extra = {'source': source}
3484 user = ctx.user()
3483 user = ctx.user()
3485 if opts.get('user'):
3484 if opts.get('user'):
3486 user = opts['user']
3485 user = opts['user']
3487 date = ctx.date()
3486 date = ctx.date()
3488 if opts.get('date'):
3487 if opts.get('date'):
3489 date = opts['date']
3488 date = opts['date']
3490 message = ctx.description()
3489 message = ctx.description()
3491 if opts.get('log'):
3490 if opts.get('log'):
3492 message += '\n(grafted from %s)' % ctx.hex()
3491 message += '\n(grafted from %s)' % ctx.hex()
3493
3492
3494 # we don't merge the first commit when continuing
3493 # we don't merge the first commit when continuing
3495 if not cont:
3494 if not cont:
3496 # perform the graft merge with p1(rev) as 'ancestor'
3495 # perform the graft merge with p1(rev) as 'ancestor'
3497 try:
3496 try:
3498 # ui.forcemerge is an internal variable, do not document
3497 # ui.forcemerge is an internal variable, do not document
3499 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
3498 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
3500 'graft')
3499 'graft')
3501 stats = mergemod.graft(repo, ctx, ctx.p1(),
3500 stats = mergemod.graft(repo, ctx, ctx.p1(),
3502 ['local', 'graft'])
3501 ['local', 'graft'])
3503 finally:
3502 finally:
3504 repo.ui.setconfig('ui', 'forcemerge', '', 'graft')
3503 repo.ui.setconfig('ui', 'forcemerge', '', 'graft')
3505 # report any conflicts
3504 # report any conflicts
3506 if stats and stats[3] > 0:
3505 if stats and stats[3] > 0:
3507 # write out state for --continue
3506 # write out state for --continue
3508 nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]]
3507 nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]]
3509 repo.vfs.write('graftstate', ''.join(nodelines))
3508 repo.vfs.write('graftstate', ''.join(nodelines))
3510 raise util.Abort(
3509 raise util.Abort(
3511 _("unresolved conflicts, can't continue"),
3510 _("unresolved conflicts, can't continue"),
3512 hint=_('use hg resolve and hg graft --continue'))
3511 hint=_('use hg resolve and hg graft --continue'))
3513 else:
3512 else:
3514 cont = False
3513 cont = False
3515
3514
3516 # commit
3515 # commit
3517 node = repo.commit(text=message, user=user,
3516 node = repo.commit(text=message, user=user,
3518 date=date, extra=extra, editor=editor)
3517 date=date, extra=extra, editor=editor)
3519 if node is None:
3518 if node is None:
3520 ui.warn(
3519 ui.warn(
3521 _('note: graft of %d:%s created no changes to commit\n') %
3520 _('note: graft of %d:%s created no changes to commit\n') %
3522 (ctx.rev(), ctx))
3521 (ctx.rev(), ctx))
3523 finally:
3522 finally:
3524 wlock.release()
3523 wlock.release()
3525
3524
3526 # remove state when we complete successfully
3525 # remove state when we complete successfully
3527 if not opts.get('dry_run'):
3526 if not opts.get('dry_run'):
3528 util.unlinkpath(repo.join('graftstate'), ignoremissing=True)
3527 util.unlinkpath(repo.join('graftstate'), ignoremissing=True)
3529
3528
3530 return 0
3529 return 0
3531
3530
3532 @command('grep',
3531 @command('grep',
3533 [('0', 'print0', None, _('end fields with NUL')),
3532 [('0', 'print0', None, _('end fields with NUL')),
3534 ('', 'all', None, _('print all revisions that match')),
3533 ('', 'all', None, _('print all revisions that match')),
3535 ('a', 'text', None, _('treat all files as text')),
3534 ('a', 'text', None, _('treat all files as text')),
3536 ('f', 'follow', None,
3535 ('f', 'follow', None,
3537 _('follow changeset history,'
3536 _('follow changeset history,'
3538 ' or file history across copies and renames')),
3537 ' or file history across copies and renames')),
3539 ('i', 'ignore-case', None, _('ignore case when matching')),
3538 ('i', 'ignore-case', None, _('ignore case when matching')),
3540 ('l', 'files-with-matches', None,
3539 ('l', 'files-with-matches', None,
3541 _('print only filenames and revisions that match')),
3540 _('print only filenames and revisions that match')),
3542 ('n', 'line-number', None, _('print matching line numbers')),
3541 ('n', 'line-number', None, _('print matching line numbers')),
3543 ('r', 'rev', [],
3542 ('r', 'rev', [],
3544 _('only search files changed within revision range'), _('REV')),
3543 _('only search files changed within revision range'), _('REV')),
3545 ('u', 'user', None, _('list the author (long with -v)')),
3544 ('u', 'user', None, _('list the author (long with -v)')),
3546 ('d', 'date', None, _('list the date (short with -q)')),
3545 ('d', 'date', None, _('list the date (short with -q)')),
3547 ] + walkopts,
3546 ] + walkopts,
3548 _('[OPTION]... PATTERN [FILE]...'),
3547 _('[OPTION]... PATTERN [FILE]...'),
3549 inferrepo=True)
3548 inferrepo=True)
3550 def grep(ui, repo, pattern, *pats, **opts):
3549 def grep(ui, repo, pattern, *pats, **opts):
3551 """search for a pattern in specified files and revisions
3550 """search for a pattern in specified files and revisions
3552
3551
3553 Search revisions of files for a regular expression.
3552 Search revisions of files for a regular expression.
3554
3553
3555 This command behaves differently than Unix grep. It only accepts
3554 This command behaves differently than Unix grep. It only accepts
3556 Python/Perl regexps. It searches repository history, not the
3555 Python/Perl regexps. It searches repository history, not the
3557 working directory. It always prints the revision number in which a
3556 working directory. It always prints the revision number in which a
3558 match appears.
3557 match appears.
3559
3558
3560 By default, grep only prints output for the first revision of a
3559 By default, grep only prints output for the first revision of a
3561 file in which it finds a match. To get it to print every revision
3560 file in which it finds a match. To get it to print every revision
3562 that contains a change in match status ("-" for a match that
3561 that contains a change in match status ("-" for a match that
3563 becomes a non-match, or "+" for a non-match that becomes a match),
3562 becomes a non-match, or "+" for a non-match that becomes a match),
3564 use the --all flag.
3563 use the --all flag.
3565
3564
3566 Returns 0 if a match is found, 1 otherwise.
3565 Returns 0 if a match is found, 1 otherwise.
3567 """
3566 """
3568 reflags = re.M
3567 reflags = re.M
3569 if opts.get('ignore_case'):
3568 if opts.get('ignore_case'):
3570 reflags |= re.I
3569 reflags |= re.I
3571 try:
3570 try:
3572 regexp = util.re.compile(pattern, reflags)
3571 regexp = util.re.compile(pattern, reflags)
3573 except re.error, inst:
3572 except re.error, inst:
3574 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
3573 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
3575 return 1
3574 return 1
3576 sep, eol = ':', '\n'
3575 sep, eol = ':', '\n'
3577 if opts.get('print0'):
3576 if opts.get('print0'):
3578 sep = eol = '\0'
3577 sep = eol = '\0'
3579
3578
3580 getfile = util.lrucachefunc(repo.file)
3579 getfile = util.lrucachefunc(repo.file)
3581
3580
3582 def matchlines(body):
3581 def matchlines(body):
3583 begin = 0
3582 begin = 0
3584 linenum = 0
3583 linenum = 0
3585 while begin < len(body):
3584 while begin < len(body):
3586 match = regexp.search(body, begin)
3585 match = regexp.search(body, begin)
3587 if not match:
3586 if not match:
3588 break
3587 break
3589 mstart, mend = match.span()
3588 mstart, mend = match.span()
3590 linenum += body.count('\n', begin, mstart) + 1
3589 linenum += body.count('\n', begin, mstart) + 1
3591 lstart = body.rfind('\n', begin, mstart) + 1 or begin
3590 lstart = body.rfind('\n', begin, mstart) + 1 or begin
3592 begin = body.find('\n', mend) + 1 or len(body) + 1
3591 begin = body.find('\n', mend) + 1 or len(body) + 1
3593 lend = begin - 1
3592 lend = begin - 1
3594 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
3593 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
3595
3594
3596 class linestate(object):
3595 class linestate(object):
3597 def __init__(self, line, linenum, colstart, colend):
3596 def __init__(self, line, linenum, colstart, colend):
3598 self.line = line
3597 self.line = line
3599 self.linenum = linenum
3598 self.linenum = linenum
3600 self.colstart = colstart
3599 self.colstart = colstart
3601 self.colend = colend
3600 self.colend = colend
3602
3601
3603 def __hash__(self):
3602 def __hash__(self):
3604 return hash((self.linenum, self.line))
3603 return hash((self.linenum, self.line))
3605
3604
3606 def __eq__(self, other):
3605 def __eq__(self, other):
3607 return self.line == other.line
3606 return self.line == other.line
3608
3607
3609 def __iter__(self):
3608 def __iter__(self):
3610 yield (self.line[:self.colstart], '')
3609 yield (self.line[:self.colstart], '')
3611 yield (self.line[self.colstart:self.colend], 'grep.match')
3610 yield (self.line[self.colstart:self.colend], 'grep.match')
3612 rest = self.line[self.colend:]
3611 rest = self.line[self.colend:]
3613 while rest != '':
3612 while rest != '':
3614 match = regexp.search(rest)
3613 match = regexp.search(rest)
3615 if not match:
3614 if not match:
3616 yield (rest, '')
3615 yield (rest, '')
3617 break
3616 break
3618 mstart, mend = match.span()
3617 mstart, mend = match.span()
3619 yield (rest[:mstart], '')
3618 yield (rest[:mstart], '')
3620 yield (rest[mstart:mend], 'grep.match')
3619 yield (rest[mstart:mend], 'grep.match')
3621 rest = rest[mend:]
3620 rest = rest[mend:]
3622
3621
3623 matches = {}
3622 matches = {}
3624 copies = {}
3623 copies = {}
3625 def grepbody(fn, rev, body):
3624 def grepbody(fn, rev, body):
3626 matches[rev].setdefault(fn, [])
3625 matches[rev].setdefault(fn, [])
3627 m = matches[rev][fn]
3626 m = matches[rev][fn]
3628 for lnum, cstart, cend, line in matchlines(body):
3627 for lnum, cstart, cend, line in matchlines(body):
3629 s = linestate(line, lnum, cstart, cend)
3628 s = linestate(line, lnum, cstart, cend)
3630 m.append(s)
3629 m.append(s)
3631
3630
3632 def difflinestates(a, b):
3631 def difflinestates(a, b):
3633 sm = difflib.SequenceMatcher(None, a, b)
3632 sm = difflib.SequenceMatcher(None, a, b)
3634 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
3633 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
3635 if tag == 'insert':
3634 if tag == 'insert':
3636 for i in xrange(blo, bhi):
3635 for i in xrange(blo, bhi):
3637 yield ('+', b[i])
3636 yield ('+', b[i])
3638 elif tag == 'delete':
3637 elif tag == 'delete':
3639 for i in xrange(alo, ahi):
3638 for i in xrange(alo, ahi):
3640 yield ('-', a[i])
3639 yield ('-', a[i])
3641 elif tag == 'replace':
3640 elif tag == 'replace':
3642 for i in xrange(alo, ahi):
3641 for i in xrange(alo, ahi):
3643 yield ('-', a[i])
3642 yield ('-', a[i])
3644 for i in xrange(blo, bhi):
3643 for i in xrange(blo, bhi):
3645 yield ('+', b[i])
3644 yield ('+', b[i])
3646
3645
3647 def display(fn, ctx, pstates, states):
3646 def display(fn, ctx, pstates, states):
3648 rev = ctx.rev()
3647 rev = ctx.rev()
3649 datefunc = ui.quiet and util.shortdate or util.datestr
3648 datefunc = ui.quiet and util.shortdate or util.datestr
3650 found = False
3649 found = False
3651 @util.cachefunc
3650 @util.cachefunc
3652 def binary():
3651 def binary():
3653 flog = getfile(fn)
3652 flog = getfile(fn)
3654 return util.binary(flog.read(ctx.filenode(fn)))
3653 return util.binary(flog.read(ctx.filenode(fn)))
3655
3654
3656 if opts.get('all'):
3655 if opts.get('all'):
3657 iter = difflinestates(pstates, states)
3656 iter = difflinestates(pstates, states)
3658 else:
3657 else:
3659 iter = [('', l) for l in states]
3658 iter = [('', l) for l in states]
3660 for change, l in iter:
3659 for change, l in iter:
3661 cols = [(fn, 'grep.filename'), (str(rev), 'grep.rev')]
3660 cols = [(fn, 'grep.filename'), (str(rev), 'grep.rev')]
3662
3661
3663 if opts.get('line_number'):
3662 if opts.get('line_number'):
3664 cols.append((str(l.linenum), 'grep.linenumber'))
3663 cols.append((str(l.linenum), 'grep.linenumber'))
3665 if opts.get('all'):
3664 if opts.get('all'):
3666 cols.append((change, 'grep.change'))
3665 cols.append((change, 'grep.change'))
3667 if opts.get('user'):
3666 if opts.get('user'):
3668 cols.append((ui.shortuser(ctx.user()), 'grep.user'))
3667 cols.append((ui.shortuser(ctx.user()), 'grep.user'))
3669 if opts.get('date'):
3668 if opts.get('date'):
3670 cols.append((datefunc(ctx.date()), 'grep.date'))
3669 cols.append((datefunc(ctx.date()), 'grep.date'))
3671 for col, label in cols[:-1]:
3670 for col, label in cols[:-1]:
3672 ui.write(col, label=label)
3671 ui.write(col, label=label)
3673 ui.write(sep, label='grep.sep')
3672 ui.write(sep, label='grep.sep')
3674 ui.write(cols[-1][0], label=cols[-1][1])
3673 ui.write(cols[-1][0], label=cols[-1][1])
3675 if not opts.get('files_with_matches'):
3674 if not opts.get('files_with_matches'):
3676 ui.write(sep, label='grep.sep')
3675 ui.write(sep, label='grep.sep')
3677 if not opts.get('text') and binary():
3676 if not opts.get('text') and binary():
3678 ui.write(" Binary file matches")
3677 ui.write(" Binary file matches")
3679 else:
3678 else:
3680 for s, label in l:
3679 for s, label in l:
3681 ui.write(s, label=label)
3680 ui.write(s, label=label)
3682 ui.write(eol)
3681 ui.write(eol)
3683 found = True
3682 found = True
3684 if opts.get('files_with_matches'):
3683 if opts.get('files_with_matches'):
3685 break
3684 break
3686 return found
3685 return found
3687
3686
3688 skip = {}
3687 skip = {}
3689 revfiles = {}
3688 revfiles = {}
3690 matchfn = scmutil.match(repo[None], pats, opts)
3689 matchfn = scmutil.match(repo[None], pats, opts)
3691 found = False
3690 found = False
3692 follow = opts.get('follow')
3691 follow = opts.get('follow')
3693
3692
3694 def prep(ctx, fns):
3693 def prep(ctx, fns):
3695 rev = ctx.rev()
3694 rev = ctx.rev()
3696 pctx = ctx.p1()
3695 pctx = ctx.p1()
3697 parent = pctx.rev()
3696 parent = pctx.rev()
3698 matches.setdefault(rev, {})
3697 matches.setdefault(rev, {})
3699 matches.setdefault(parent, {})
3698 matches.setdefault(parent, {})
3700 files = revfiles.setdefault(rev, [])
3699 files = revfiles.setdefault(rev, [])
3701 for fn in fns:
3700 for fn in fns:
3702 flog = getfile(fn)
3701 flog = getfile(fn)
3703 try:
3702 try:
3704 fnode = ctx.filenode(fn)
3703 fnode = ctx.filenode(fn)
3705 except error.LookupError:
3704 except error.LookupError:
3706 continue
3705 continue
3707
3706
3708 copied = flog.renamed(fnode)
3707 copied = flog.renamed(fnode)
3709 copy = follow and copied and copied[0]
3708 copy = follow and copied and copied[0]
3710 if copy:
3709 if copy:
3711 copies.setdefault(rev, {})[fn] = copy
3710 copies.setdefault(rev, {})[fn] = copy
3712 if fn in skip:
3711 if fn in skip:
3713 if copy:
3712 if copy:
3714 skip[copy] = True
3713 skip[copy] = True
3715 continue
3714 continue
3716 files.append(fn)
3715 files.append(fn)
3717
3716
3718 if fn not in matches[rev]:
3717 if fn not in matches[rev]:
3719 grepbody(fn, rev, flog.read(fnode))
3718 grepbody(fn, rev, flog.read(fnode))
3720
3719
3721 pfn = copy or fn
3720 pfn = copy or fn
3722 if pfn not in matches[parent]:
3721 if pfn not in matches[parent]:
3723 try:
3722 try:
3724 fnode = pctx.filenode(pfn)
3723 fnode = pctx.filenode(pfn)
3725 grepbody(pfn, parent, flog.read(fnode))
3724 grepbody(pfn, parent, flog.read(fnode))
3726 except error.LookupError:
3725 except error.LookupError:
3727 pass
3726 pass
3728
3727
3729 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
3728 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
3730 rev = ctx.rev()
3729 rev = ctx.rev()
3731 parent = ctx.p1().rev()
3730 parent = ctx.p1().rev()
3732 for fn in sorted(revfiles.get(rev, [])):
3731 for fn in sorted(revfiles.get(rev, [])):
3733 states = matches[rev][fn]
3732 states = matches[rev][fn]
3734 copy = copies.get(rev, {}).get(fn)
3733 copy = copies.get(rev, {}).get(fn)
3735 if fn in skip:
3734 if fn in skip:
3736 if copy:
3735 if copy:
3737 skip[copy] = True
3736 skip[copy] = True
3738 continue
3737 continue
3739 pstates = matches.get(parent, {}).get(copy or fn, [])
3738 pstates = matches.get(parent, {}).get(copy or fn, [])
3740 if pstates or states:
3739 if pstates or states:
3741 r = display(fn, ctx, pstates, states)
3740 r = display(fn, ctx, pstates, states)
3742 found = found or r
3741 found = found or r
3743 if r and not opts.get('all'):
3742 if r and not opts.get('all'):
3744 skip[fn] = True
3743 skip[fn] = True
3745 if copy:
3744 if copy:
3746 skip[copy] = True
3745 skip[copy] = True
3747 del matches[rev]
3746 del matches[rev]
3748 del revfiles[rev]
3747 del revfiles[rev]
3749
3748
3750 return not found
3749 return not found
3751
3750
3752 @command('heads',
3751 @command('heads',
3753 [('r', 'rev', '',
3752 [('r', 'rev', '',
3754 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
3753 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
3755 ('t', 'topo', False, _('show topological heads only')),
3754 ('t', 'topo', False, _('show topological heads only')),
3756 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
3755 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
3757 ('c', 'closed', False, _('show normal and closed branch heads')),
3756 ('c', 'closed', False, _('show normal and closed branch heads')),
3758 ] + templateopts,
3757 ] + templateopts,
3759 _('[-ct] [-r STARTREV] [REV]...'))
3758 _('[-ct] [-r STARTREV] [REV]...'))
3760 def heads(ui, repo, *branchrevs, **opts):
3759 def heads(ui, repo, *branchrevs, **opts):
3761 """show branch heads
3760 """show branch heads
3762
3761
3763 With no arguments, show all open branch heads in the repository.
3762 With no arguments, show all open branch heads in the repository.
3764 Branch heads are changesets that have no descendants on the
3763 Branch heads are changesets that have no descendants on the
3765 same branch. They are where development generally takes place and
3764 same branch. They are where development generally takes place and
3766 are the usual targets for update and merge operations.
3765 are the usual targets for update and merge operations.
3767
3766
3768 If one or more REVs are given, only open branch heads on the
3767 If one or more REVs are given, only open branch heads on the
3769 branches associated with the specified changesets are shown. This
3768 branches associated with the specified changesets are shown. This
3770 means that you can use :hg:`heads .` to see the heads on the
3769 means that you can use :hg:`heads .` to see the heads on the
3771 currently checked-out branch.
3770 currently checked-out branch.
3772
3771
3773 If -c/--closed is specified, also show branch heads marked closed
3772 If -c/--closed is specified, also show branch heads marked closed
3774 (see :hg:`commit --close-branch`).
3773 (see :hg:`commit --close-branch`).
3775
3774
3776 If STARTREV is specified, only those heads that are descendants of
3775 If STARTREV is specified, only those heads that are descendants of
3777 STARTREV will be displayed.
3776 STARTREV will be displayed.
3778
3777
3779 If -t/--topo is specified, named branch mechanics will be ignored and only
3778 If -t/--topo is specified, named branch mechanics will be ignored and only
3780 topological heads (changesets with no children) will be shown.
3779 topological heads (changesets with no children) will be shown.
3781
3780
3782 Returns 0 if matching heads are found, 1 if not.
3781 Returns 0 if matching heads are found, 1 if not.
3783 """
3782 """
3784
3783
3785 start = None
3784 start = None
3786 if 'rev' in opts:
3785 if 'rev' in opts:
3787 start = scmutil.revsingle(repo, opts['rev'], None).node()
3786 start = scmutil.revsingle(repo, opts['rev'], None).node()
3788
3787
3789 if opts.get('topo'):
3788 if opts.get('topo'):
3790 heads = [repo[h] for h in repo.heads(start)]
3789 heads = [repo[h] for h in repo.heads(start)]
3791 else:
3790 else:
3792 heads = []
3791 heads = []
3793 for branch in repo.branchmap():
3792 for branch in repo.branchmap():
3794 heads += repo.branchheads(branch, start, opts.get('closed'))
3793 heads += repo.branchheads(branch, start, opts.get('closed'))
3795 heads = [repo[h] for h in heads]
3794 heads = [repo[h] for h in heads]
3796
3795
3797 if branchrevs:
3796 if branchrevs:
3798 branches = set(repo[br].branch() for br in branchrevs)
3797 branches = set(repo[br].branch() for br in branchrevs)
3799 heads = [h for h in heads if h.branch() in branches]
3798 heads = [h for h in heads if h.branch() in branches]
3800
3799
3801 if opts.get('active') and branchrevs:
3800 if opts.get('active') and branchrevs:
3802 dagheads = repo.heads(start)
3801 dagheads = repo.heads(start)
3803 heads = [h for h in heads if h.node() in dagheads]
3802 heads = [h for h in heads if h.node() in dagheads]
3804
3803
3805 if branchrevs:
3804 if branchrevs:
3806 haveheads = set(h.branch() for h in heads)
3805 haveheads = set(h.branch() for h in heads)
3807 if branches - haveheads:
3806 if branches - haveheads:
3808 headless = ', '.join(b for b in branches - haveheads)
3807 headless = ', '.join(b for b in branches - haveheads)
3809 msg = _('no open branch heads found on branches %s')
3808 msg = _('no open branch heads found on branches %s')
3810 if opts.get('rev'):
3809 if opts.get('rev'):
3811 msg += _(' (started at %s)') % opts['rev']
3810 msg += _(' (started at %s)') % opts['rev']
3812 ui.warn((msg + '\n') % headless)
3811 ui.warn((msg + '\n') % headless)
3813
3812
3814 if not heads:
3813 if not heads:
3815 return 1
3814 return 1
3816
3815
3817 heads = sorted(heads, key=lambda x: -x.rev())
3816 heads = sorted(heads, key=lambda x: -x.rev())
3818 displayer = cmdutil.show_changeset(ui, repo, opts)
3817 displayer = cmdutil.show_changeset(ui, repo, opts)
3819 for ctx in heads:
3818 for ctx in heads:
3820 displayer.show(ctx)
3819 displayer.show(ctx)
3821 displayer.close()
3820 displayer.close()
3822
3821
3823 @command('help',
3822 @command('help',
3824 [('e', 'extension', None, _('show only help for extensions')),
3823 [('e', 'extension', None, _('show only help for extensions')),
3825 ('c', 'command', None, _('show only help for commands')),
3824 ('c', 'command', None, _('show only help for commands')),
3826 ('k', 'keyword', '', _('show topics matching keyword')),
3825 ('k', 'keyword', '', _('show topics matching keyword')),
3827 ],
3826 ],
3828 _('[-ec] [TOPIC]'),
3827 _('[-ec] [TOPIC]'),
3829 norepo=True)
3828 norepo=True)
3830 def help_(ui, name=None, **opts):
3829 def help_(ui, name=None, **opts):
3831 """show help for a given topic or a help overview
3830 """show help for a given topic or a help overview
3832
3831
3833 With no arguments, print a list of commands with short help messages.
3832 With no arguments, print a list of commands with short help messages.
3834
3833
3835 Given a topic, extension, or command name, print help for that
3834 Given a topic, extension, or command name, print help for that
3836 topic.
3835 topic.
3837
3836
3838 Returns 0 if successful.
3837 Returns 0 if successful.
3839 """
3838 """
3840
3839
3841 textwidth = min(ui.termwidth(), 80) - 2
3840 textwidth = min(ui.termwidth(), 80) - 2
3842
3841
3843 keep = []
3842 keep = []
3844 if ui.verbose:
3843 if ui.verbose:
3845 keep.append('verbose')
3844 keep.append('verbose')
3846 if sys.platform.startswith('win'):
3845 if sys.platform.startswith('win'):
3847 keep.append('windows')
3846 keep.append('windows')
3848 elif sys.platform == 'OpenVMS':
3847 elif sys.platform == 'OpenVMS':
3849 keep.append('vms')
3848 keep.append('vms')
3850 elif sys.platform == 'plan9':
3849 elif sys.platform == 'plan9':
3851 keep.append('plan9')
3850 keep.append('plan9')
3852 else:
3851 else:
3853 keep.append('unix')
3852 keep.append('unix')
3854 keep.append(sys.platform.lower())
3853 keep.append(sys.platform.lower())
3855
3854
3856 section = None
3855 section = None
3857 if name and '.' in name:
3856 if name and '.' in name:
3858 name, section = name.split('.', 1)
3857 name, section = name.split('.', 1)
3859
3858
3860 text = help.help_(ui, name, **opts)
3859 text = help.help_(ui, name, **opts)
3861
3860
3862 formatted, pruned = minirst.format(text, textwidth, keep=keep,
3861 formatted, pruned = minirst.format(text, textwidth, keep=keep,
3863 section=section)
3862 section=section)
3864 if section and not formatted:
3863 if section and not formatted:
3865 raise util.Abort(_("help section not found"))
3864 raise util.Abort(_("help section not found"))
3866
3865
3867 if 'verbose' in pruned:
3866 if 'verbose' in pruned:
3868 keep.append('omitted')
3867 keep.append('omitted')
3869 else:
3868 else:
3870 keep.append('notomitted')
3869 keep.append('notomitted')
3871 formatted, pruned = minirst.format(text, textwidth, keep=keep,
3870 formatted, pruned = minirst.format(text, textwidth, keep=keep,
3872 section=section)
3871 section=section)
3873 ui.write(formatted)
3872 ui.write(formatted)
3874
3873
3875
3874
3876 @command('identify|id',
3875 @command('identify|id',
3877 [('r', 'rev', '',
3876 [('r', 'rev', '',
3878 _('identify the specified revision'), _('REV')),
3877 _('identify the specified revision'), _('REV')),
3879 ('n', 'num', None, _('show local revision number')),
3878 ('n', 'num', None, _('show local revision number')),
3880 ('i', 'id', None, _('show global revision id')),
3879 ('i', 'id', None, _('show global revision id')),
3881 ('b', 'branch', None, _('show branch')),
3880 ('b', 'branch', None, _('show branch')),
3882 ('t', 'tags', None, _('show tags')),
3881 ('t', 'tags', None, _('show tags')),
3883 ('B', 'bookmarks', None, _('show bookmarks')),
3882 ('B', 'bookmarks', None, _('show bookmarks')),
3884 ] + remoteopts,
3883 ] + remoteopts,
3885 _('[-nibtB] [-r REV] [SOURCE]'),
3884 _('[-nibtB] [-r REV] [SOURCE]'),
3886 optionalrepo=True)
3885 optionalrepo=True)
3887 def identify(ui, repo, source=None, rev=None,
3886 def identify(ui, repo, source=None, rev=None,
3888 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
3887 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
3889 """identify the working copy or specified revision
3888 """identify the working copy or specified revision
3890
3889
3891 Print a summary identifying the repository state at REV using one or
3890 Print a summary identifying the repository state at REV using one or
3892 two parent hash identifiers, followed by a "+" if the working
3891 two parent hash identifiers, followed by a "+" if the working
3893 directory has uncommitted changes, the branch name (if not default),
3892 directory has uncommitted changes, the branch name (if not default),
3894 a list of tags, and a list of bookmarks.
3893 a list of tags, and a list of bookmarks.
3895
3894
3896 When REV is not given, print a summary of the current state of the
3895 When REV is not given, print a summary of the current state of the
3897 repository.
3896 repository.
3898
3897
3899 Specifying a path to a repository root or Mercurial bundle will
3898 Specifying a path to a repository root or Mercurial bundle will
3900 cause lookup to operate on that repository/bundle.
3899 cause lookup to operate on that repository/bundle.
3901
3900
3902 .. container:: verbose
3901 .. container:: verbose
3903
3902
3904 Examples:
3903 Examples:
3905
3904
3906 - generate a build identifier for the working directory::
3905 - generate a build identifier for the working directory::
3907
3906
3908 hg id --id > build-id.dat
3907 hg id --id > build-id.dat
3909
3908
3910 - find the revision corresponding to a tag::
3909 - find the revision corresponding to a tag::
3911
3910
3912 hg id -n -r 1.3
3911 hg id -n -r 1.3
3913
3912
3914 - check the most recent revision of a remote repository::
3913 - check the most recent revision of a remote repository::
3915
3914
3916 hg id -r tip http://selenic.com/hg/
3915 hg id -r tip http://selenic.com/hg/
3917
3916
3918 Returns 0 if successful.
3917 Returns 0 if successful.
3919 """
3918 """
3920
3919
3921 if not repo and not source:
3920 if not repo and not source:
3922 raise util.Abort(_("there is no Mercurial repository here "
3921 raise util.Abort(_("there is no Mercurial repository here "
3923 "(.hg not found)"))
3922 "(.hg not found)"))
3924
3923
3925 hexfunc = ui.debugflag and hex or short
3924 hexfunc = ui.debugflag and hex or short
3926 default = not (num or id or branch or tags or bookmarks)
3925 default = not (num or id or branch or tags or bookmarks)
3927 output = []
3926 output = []
3928 revs = []
3927 revs = []
3929
3928
3930 if source:
3929 if source:
3931 source, branches = hg.parseurl(ui.expandpath(source))
3930 source, branches = hg.parseurl(ui.expandpath(source))
3932 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
3931 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
3933 repo = peer.local()
3932 repo = peer.local()
3934 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3933 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3935
3934
3936 if not repo:
3935 if not repo:
3937 if num or branch or tags:
3936 if num or branch or tags:
3938 raise util.Abort(
3937 raise util.Abort(
3939 _("can't query remote revision number, branch, or tags"))
3938 _("can't query remote revision number, branch, or tags"))
3940 if not rev and revs:
3939 if not rev and revs:
3941 rev = revs[0]
3940 rev = revs[0]
3942 if not rev:
3941 if not rev:
3943 rev = "tip"
3942 rev = "tip"
3944
3943
3945 remoterev = peer.lookup(rev)
3944 remoterev = peer.lookup(rev)
3946 if default or id:
3945 if default or id:
3947 output = [hexfunc(remoterev)]
3946 output = [hexfunc(remoterev)]
3948
3947
3949 def getbms():
3948 def getbms():
3950 bms = []
3949 bms = []
3951
3950
3952 if 'bookmarks' in peer.listkeys('namespaces'):
3951 if 'bookmarks' in peer.listkeys('namespaces'):
3953 hexremoterev = hex(remoterev)
3952 hexremoterev = hex(remoterev)
3954 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
3953 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
3955 if bmr == hexremoterev]
3954 if bmr == hexremoterev]
3956
3955
3957 return sorted(bms)
3956 return sorted(bms)
3958
3957
3959 if bookmarks:
3958 if bookmarks:
3960 output.extend(getbms())
3959 output.extend(getbms())
3961 elif default and not ui.quiet:
3960 elif default and not ui.quiet:
3962 # multiple bookmarks for a single parent separated by '/'
3961 # multiple bookmarks for a single parent separated by '/'
3963 bm = '/'.join(getbms())
3962 bm = '/'.join(getbms())
3964 if bm:
3963 if bm:
3965 output.append(bm)
3964 output.append(bm)
3966 else:
3965 else:
3967 if not rev:
3966 if not rev:
3968 ctx = repo[None]
3967 ctx = repo[None]
3969 parents = ctx.parents()
3968 parents = ctx.parents()
3970 changed = ""
3969 changed = ""
3971 if default or id or num:
3970 if default or id or num:
3972 if (util.any(repo.status())
3971 if (util.any(repo.status())
3973 or util.any(ctx.sub(s).dirty() for s in ctx.substate)):
3972 or util.any(ctx.sub(s).dirty() for s in ctx.substate)):
3974 changed = '+'
3973 changed = '+'
3975 if default or id:
3974 if default or id:
3976 output = ["%s%s" %
3975 output = ["%s%s" %
3977 ('+'.join([hexfunc(p.node()) for p in parents]), changed)]
3976 ('+'.join([hexfunc(p.node()) for p in parents]), changed)]
3978 if num:
3977 if num:
3979 output.append("%s%s" %
3978 output.append("%s%s" %
3980 ('+'.join([str(p.rev()) for p in parents]), changed))
3979 ('+'.join([str(p.rev()) for p in parents]), changed))
3981 else:
3980 else:
3982 ctx = scmutil.revsingle(repo, rev)
3981 ctx = scmutil.revsingle(repo, rev)
3983 if default or id:
3982 if default or id:
3984 output = [hexfunc(ctx.node())]
3983 output = [hexfunc(ctx.node())]
3985 if num:
3984 if num:
3986 output.append(str(ctx.rev()))
3985 output.append(str(ctx.rev()))
3987
3986
3988 if default and not ui.quiet:
3987 if default and not ui.quiet:
3989 b = ctx.branch()
3988 b = ctx.branch()
3990 if b != 'default':
3989 if b != 'default':
3991 output.append("(%s)" % b)
3990 output.append("(%s)" % b)
3992
3991
3993 # multiple tags for a single parent separated by '/'
3992 # multiple tags for a single parent separated by '/'
3994 t = '/'.join(ctx.tags())
3993 t = '/'.join(ctx.tags())
3995 if t:
3994 if t:
3996 output.append(t)
3995 output.append(t)
3997
3996
3998 # multiple bookmarks for a single parent separated by '/'
3997 # multiple bookmarks for a single parent separated by '/'
3999 bm = '/'.join(ctx.bookmarks())
3998 bm = '/'.join(ctx.bookmarks())
4000 if bm:
3999 if bm:
4001 output.append(bm)
4000 output.append(bm)
4002 else:
4001 else:
4003 if branch:
4002 if branch:
4004 output.append(ctx.branch())
4003 output.append(ctx.branch())
4005
4004
4006 if tags:
4005 if tags:
4007 output.extend(ctx.tags())
4006 output.extend(ctx.tags())
4008
4007
4009 if bookmarks:
4008 if bookmarks:
4010 output.extend(ctx.bookmarks())
4009 output.extend(ctx.bookmarks())
4011
4010
4012 ui.write("%s\n" % ' '.join(output))
4011 ui.write("%s\n" % ' '.join(output))
4013
4012
4014 @command('import|patch',
4013 @command('import|patch',
4015 [('p', 'strip', 1,
4014 [('p', 'strip', 1,
4016 _('directory strip option for patch. This has the same '
4015 _('directory strip option for patch. This has the same '
4017 'meaning as the corresponding patch option'), _('NUM')),
4016 'meaning as the corresponding patch option'), _('NUM')),
4018 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
4017 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
4019 ('e', 'edit', False, _('invoke editor on commit messages')),
4018 ('e', 'edit', False, _('invoke editor on commit messages')),
4020 ('f', 'force', None,
4019 ('f', 'force', None,
4021 _('skip check for outstanding uncommitted changes (DEPRECATED)')),
4020 _('skip check for outstanding uncommitted changes (DEPRECATED)')),
4022 ('', 'no-commit', None,
4021 ('', 'no-commit', None,
4023 _("don't commit, just update the working directory")),
4022 _("don't commit, just update the working directory")),
4024 ('', 'bypass', None,
4023 ('', 'bypass', None,
4025 _("apply patch without touching the working directory")),
4024 _("apply patch without touching the working directory")),
4026 ('', 'partial', None,
4025 ('', 'partial', None,
4027 _('commit even if some hunks fail')),
4026 _('commit even if some hunks fail')),
4028 ('', 'exact', None,
4027 ('', 'exact', None,
4029 _('apply patch to the nodes from which it was generated')),
4028 _('apply patch to the nodes from which it was generated')),
4030 ('', 'import-branch', None,
4029 ('', 'import-branch', None,
4031 _('use any branch information in patch (implied by --exact)'))] +
4030 _('use any branch information in patch (implied by --exact)'))] +
4032 commitopts + commitopts2 + similarityopts,
4031 commitopts + commitopts2 + similarityopts,
4033 _('[OPTION]... PATCH...'))
4032 _('[OPTION]... PATCH...'))
4034 def import_(ui, repo, patch1=None, *patches, **opts):
4033 def import_(ui, repo, patch1=None, *patches, **opts):
4035 """import an ordered set of patches
4034 """import an ordered set of patches
4036
4035
4037 Import a list of patches and commit them individually (unless
4036 Import a list of patches and commit them individually (unless
4038 --no-commit is specified).
4037 --no-commit is specified).
4039
4038
4040 Because import first applies changes to the working directory,
4039 Because import first applies changes to the working directory,
4041 import will abort if there are outstanding changes.
4040 import will abort if there are outstanding changes.
4042
4041
4043 You can import a patch straight from a mail message. Even patches
4042 You can import a patch straight from a mail message. Even patches
4044 as attachments work (to use the body part, it must have type
4043 as attachments work (to use the body part, it must have type
4045 text/plain or text/x-patch). From and Subject headers of email
4044 text/plain or text/x-patch). From and Subject headers of email
4046 message are used as default committer and commit message. All
4045 message are used as default committer and commit message. All
4047 text/plain body parts before first diff are added to commit
4046 text/plain body parts before first diff are added to commit
4048 message.
4047 message.
4049
4048
4050 If the imported patch was generated by :hg:`export`, user and
4049 If the imported patch was generated by :hg:`export`, user and
4051 description from patch override values from message headers and
4050 description from patch override values from message headers and
4052 body. Values given on command line with -m/--message and -u/--user
4051 body. Values given on command line with -m/--message and -u/--user
4053 override these.
4052 override these.
4054
4053
4055 If --exact is specified, import will set the working directory to
4054 If --exact is specified, import will set the working directory to
4056 the parent of each patch before applying it, and will abort if the
4055 the parent of each patch before applying it, and will abort if the
4057 resulting changeset has a different ID than the one recorded in
4056 resulting changeset has a different ID than the one recorded in
4058 the patch. This may happen due to character set problems or other
4057 the patch. This may happen due to character set problems or other
4059 deficiencies in the text patch format.
4058 deficiencies in the text patch format.
4060
4059
4061 Use --bypass to apply and commit patches directly to the
4060 Use --bypass to apply and commit patches directly to the
4062 repository, not touching the working directory. Without --exact,
4061 repository, not touching the working directory. Without --exact,
4063 patches will be applied on top of the working directory parent
4062 patches will be applied on top of the working directory parent
4064 revision.
4063 revision.
4065
4064
4066 With -s/--similarity, hg will attempt to discover renames and
4065 With -s/--similarity, hg will attempt to discover renames and
4067 copies in the patch in the same way as :hg:`addremove`.
4066 copies in the patch in the same way as :hg:`addremove`.
4068
4067
4069 Use --partial to ensure a changeset will be created from the patch
4068 Use --partial to ensure a changeset will be created from the patch
4070 even if some hunks fail to apply. Hunks that fail to apply will be
4069 even if some hunks fail to apply. Hunks that fail to apply will be
4071 written to a <target-file>.rej file. Conflicts can then be resolved
4070 written to a <target-file>.rej file. Conflicts can then be resolved
4072 by hand before :hg:`commit --amend` is run to update the created
4071 by hand before :hg:`commit --amend` is run to update the created
4073 changeset. This flag exists to let people import patches that
4072 changeset. This flag exists to let people import patches that
4074 partially apply without losing the associated metadata (author,
4073 partially apply without losing the associated metadata (author,
4075 date, description, ...). Note that when none of the hunk applies
4074 date, description, ...). Note that when none of the hunk applies
4076 cleanly, :hg:`import --partial` will create an empty changeset,
4075 cleanly, :hg:`import --partial` will create an empty changeset,
4077 importing only the patch metadata.
4076 importing only the patch metadata.
4078
4077
4079 To read a patch from standard input, use "-" as the patch name. If
4078 To read a patch from standard input, use "-" as the patch name. If
4080 a URL is specified, the patch will be downloaded from it.
4079 a URL is specified, the patch will be downloaded from it.
4081 See :hg:`help dates` for a list of formats valid for -d/--date.
4080 See :hg:`help dates` for a list of formats valid for -d/--date.
4082
4081
4083 .. container:: verbose
4082 .. container:: verbose
4084
4083
4085 Examples:
4084 Examples:
4086
4085
4087 - import a traditional patch from a website and detect renames::
4086 - import a traditional patch from a website and detect renames::
4088
4087
4089 hg import -s 80 http://example.com/bugfix.patch
4088 hg import -s 80 http://example.com/bugfix.patch
4090
4089
4091 - import a changeset from an hgweb server::
4090 - import a changeset from an hgweb server::
4092
4091
4093 hg import http://www.selenic.com/hg/rev/5ca8c111e9aa
4092 hg import http://www.selenic.com/hg/rev/5ca8c111e9aa
4094
4093
4095 - import all the patches in an Unix-style mbox::
4094 - import all the patches in an Unix-style mbox::
4096
4095
4097 hg import incoming-patches.mbox
4096 hg import incoming-patches.mbox
4098
4097
4099 - attempt to exactly restore an exported changeset (not always
4098 - attempt to exactly restore an exported changeset (not always
4100 possible)::
4099 possible)::
4101
4100
4102 hg import --exact proposed-fix.patch
4101 hg import --exact proposed-fix.patch
4103
4102
4104 Returns 0 on success, 1 on partial success (see --partial).
4103 Returns 0 on success, 1 on partial success (see --partial).
4105 """
4104 """
4106
4105
4107 if not patch1:
4106 if not patch1:
4108 raise util.Abort(_('need at least one patch to import'))
4107 raise util.Abort(_('need at least one patch to import'))
4109
4108
4110 patches = (patch1,) + patches
4109 patches = (patch1,) + patches
4111
4110
4112 date = opts.get('date')
4111 date = opts.get('date')
4113 if date:
4112 if date:
4114 opts['date'] = util.parsedate(date)
4113 opts['date'] = util.parsedate(date)
4115
4114
4116 update = not opts.get('bypass')
4115 update = not opts.get('bypass')
4117 if not update and opts.get('no_commit'):
4116 if not update and opts.get('no_commit'):
4118 raise util.Abort(_('cannot use --no-commit with --bypass'))
4117 raise util.Abort(_('cannot use --no-commit with --bypass'))
4119 try:
4118 try:
4120 sim = float(opts.get('similarity') or 0)
4119 sim = float(opts.get('similarity') or 0)
4121 except ValueError:
4120 except ValueError:
4122 raise util.Abort(_('similarity must be a number'))
4121 raise util.Abort(_('similarity must be a number'))
4123 if sim < 0 or sim > 100:
4122 if sim < 0 or sim > 100:
4124 raise util.Abort(_('similarity must be between 0 and 100'))
4123 raise util.Abort(_('similarity must be between 0 and 100'))
4125 if sim and not update:
4124 if sim and not update:
4126 raise util.Abort(_('cannot use --similarity with --bypass'))
4125 raise util.Abort(_('cannot use --similarity with --bypass'))
4127 if opts.get('exact') and opts.get('edit'):
4126 if opts.get('exact') and opts.get('edit'):
4128 raise util.Abort(_('cannot use --exact with --edit'))
4127 raise util.Abort(_('cannot use --exact with --edit'))
4129
4128
4130 if update:
4129 if update:
4131 cmdutil.checkunfinished(repo)
4130 cmdutil.checkunfinished(repo)
4132 if (opts.get('exact') or not opts.get('force')) and update:
4131 if (opts.get('exact') or not opts.get('force')) and update:
4133 cmdutil.bailifchanged(repo)
4132 cmdutil.bailifchanged(repo)
4134
4133
4135 base = opts["base"]
4134 base = opts["base"]
4136 wlock = lock = tr = None
4135 wlock = lock = tr = None
4137 msgs = []
4136 msgs = []
4138 ret = 0
4137 ret = 0
4139
4138
4140
4139
4141 try:
4140 try:
4142 try:
4141 try:
4143 wlock = repo.wlock()
4142 wlock = repo.wlock()
4144 repo.dirstate.beginparentchange()
4143 repo.dirstate.beginparentchange()
4145 if not opts.get('no_commit'):
4144 if not opts.get('no_commit'):
4146 lock = repo.lock()
4145 lock = repo.lock()
4147 tr = repo.transaction('import')
4146 tr = repo.transaction('import')
4148 parents = repo.parents()
4147 parents = repo.parents()
4149 for patchurl in patches:
4148 for patchurl in patches:
4150 if patchurl == '-':
4149 if patchurl == '-':
4151 ui.status(_('applying patch from stdin\n'))
4150 ui.status(_('applying patch from stdin\n'))
4152 patchfile = ui.fin
4151 patchfile = ui.fin
4153 patchurl = 'stdin' # for error message
4152 patchurl = 'stdin' # for error message
4154 else:
4153 else:
4155 patchurl = os.path.join(base, patchurl)
4154 patchurl = os.path.join(base, patchurl)
4156 ui.status(_('applying %s\n') % patchurl)
4155 ui.status(_('applying %s\n') % patchurl)
4157 patchfile = hg.openpath(ui, patchurl)
4156 patchfile = hg.openpath(ui, patchurl)
4158
4157
4159 haspatch = False
4158 haspatch = False
4160 for hunk in patch.split(patchfile):
4159 for hunk in patch.split(patchfile):
4161 (msg, node, rej) = cmdutil.tryimportone(ui, repo, hunk,
4160 (msg, node, rej) = cmdutil.tryimportone(ui, repo, hunk,
4162 parents, opts,
4161 parents, opts,
4163 msgs, hg.clean)
4162 msgs, hg.clean)
4164 if msg:
4163 if msg:
4165 haspatch = True
4164 haspatch = True
4166 ui.note(msg + '\n')
4165 ui.note(msg + '\n')
4167 if update or opts.get('exact'):
4166 if update or opts.get('exact'):
4168 parents = repo.parents()
4167 parents = repo.parents()
4169 else:
4168 else:
4170 parents = [repo[node]]
4169 parents = [repo[node]]
4171 if rej:
4170 if rej:
4172 ui.write_err(_("patch applied partially\n"))
4171 ui.write_err(_("patch applied partially\n"))
4173 ui.write_err(_("(fix the .rej files and run "
4172 ui.write_err(_("(fix the .rej files and run "
4174 "`hg commit --amend`)\n"))
4173 "`hg commit --amend`)\n"))
4175 ret = 1
4174 ret = 1
4176 break
4175 break
4177
4176
4178 if not haspatch:
4177 if not haspatch:
4179 raise util.Abort(_('%s: no diffs found') % patchurl)
4178 raise util.Abort(_('%s: no diffs found') % patchurl)
4180
4179
4181 if tr:
4180 if tr:
4182 tr.close()
4181 tr.close()
4183 if msgs:
4182 if msgs:
4184 repo.savecommitmessage('\n* * *\n'.join(msgs))
4183 repo.savecommitmessage('\n* * *\n'.join(msgs))
4185 repo.dirstate.endparentchange()
4184 repo.dirstate.endparentchange()
4186 return ret
4185 return ret
4187 except: # re-raises
4186 except: # re-raises
4188 # wlock.release() indirectly calls dirstate.write(): since
4187 # wlock.release() indirectly calls dirstate.write(): since
4189 # we're crashing, we do not want to change the working dir
4188 # we're crashing, we do not want to change the working dir
4190 # parent after all, so make sure it writes nothing
4189 # parent after all, so make sure it writes nothing
4191 repo.dirstate.invalidate()
4190 repo.dirstate.invalidate()
4192 raise
4191 raise
4193 finally:
4192 finally:
4194 if tr:
4193 if tr:
4195 tr.release()
4194 tr.release()
4196 release(lock, wlock)
4195 release(lock, wlock)
4197
4196
4198 @command('incoming|in',
4197 @command('incoming|in',
4199 [('f', 'force', None,
4198 [('f', 'force', None,
4200 _('run even if remote repository is unrelated')),
4199 _('run even if remote repository is unrelated')),
4201 ('n', 'newest-first', None, _('show newest record first')),
4200 ('n', 'newest-first', None, _('show newest record first')),
4202 ('', 'bundle', '',
4201 ('', 'bundle', '',
4203 _('file to store the bundles into'), _('FILE')),
4202 _('file to store the bundles into'), _('FILE')),
4204 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4203 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4205 ('B', 'bookmarks', False, _("compare bookmarks")),
4204 ('B', 'bookmarks', False, _("compare bookmarks")),
4206 ('b', 'branch', [],
4205 ('b', 'branch', [],
4207 _('a specific branch you would like to pull'), _('BRANCH')),
4206 _('a specific branch you would like to pull'), _('BRANCH')),
4208 ] + logopts + remoteopts + subrepoopts,
4207 ] + logopts + remoteopts + subrepoopts,
4209 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
4208 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
4210 def incoming(ui, repo, source="default", **opts):
4209 def incoming(ui, repo, source="default", **opts):
4211 """show new changesets found in source
4210 """show new changesets found in source
4212
4211
4213 Show new changesets found in the specified path/URL or the default
4212 Show new changesets found in the specified path/URL or the default
4214 pull location. These are the changesets that would have been pulled
4213 pull location. These are the changesets that would have been pulled
4215 if a pull at the time you issued this command.
4214 if a pull at the time you issued this command.
4216
4215
4217 For remote repository, using --bundle avoids downloading the
4216 For remote repository, using --bundle avoids downloading the
4218 changesets twice if the incoming is followed by a pull.
4217 changesets twice if the incoming is followed by a pull.
4219
4218
4220 See pull for valid source format details.
4219 See pull for valid source format details.
4221
4220
4222 .. container:: verbose
4221 .. container:: verbose
4223
4222
4224 Examples:
4223 Examples:
4225
4224
4226 - show incoming changes with patches and full description::
4225 - show incoming changes with patches and full description::
4227
4226
4228 hg incoming -vp
4227 hg incoming -vp
4229
4228
4230 - show incoming changes excluding merges, store a bundle::
4229 - show incoming changes excluding merges, store a bundle::
4231
4230
4232 hg in -vpM --bundle incoming.hg
4231 hg in -vpM --bundle incoming.hg
4233 hg pull incoming.hg
4232 hg pull incoming.hg
4234
4233
4235 - briefly list changes inside a bundle::
4234 - briefly list changes inside a bundle::
4236
4235
4237 hg in changes.hg -T "{desc|firstline}\\n"
4236 hg in changes.hg -T "{desc|firstline}\\n"
4238
4237
4239 Returns 0 if there are incoming changes, 1 otherwise.
4238 Returns 0 if there are incoming changes, 1 otherwise.
4240 """
4239 """
4241 if opts.get('graph'):
4240 if opts.get('graph'):
4242 cmdutil.checkunsupportedgraphflags([], opts)
4241 cmdutil.checkunsupportedgraphflags([], opts)
4243 def display(other, chlist, displayer):
4242 def display(other, chlist, displayer):
4244 revdag = cmdutil.graphrevs(other, chlist, opts)
4243 revdag = cmdutil.graphrevs(other, chlist, opts)
4245 showparents = [ctx.node() for ctx in repo[None].parents()]
4244 showparents = [ctx.node() for ctx in repo[None].parents()]
4246 cmdutil.displaygraph(ui, revdag, displayer, showparents,
4245 cmdutil.displaygraph(ui, revdag, displayer, showparents,
4247 graphmod.asciiedges)
4246 graphmod.asciiedges)
4248
4247
4249 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
4248 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
4250 return 0
4249 return 0
4251
4250
4252 if opts.get('bundle') and opts.get('subrepos'):
4251 if opts.get('bundle') and opts.get('subrepos'):
4253 raise util.Abort(_('cannot combine --bundle and --subrepos'))
4252 raise util.Abort(_('cannot combine --bundle and --subrepos'))
4254
4253
4255 if opts.get('bookmarks'):
4254 if opts.get('bookmarks'):
4256 source, branches = hg.parseurl(ui.expandpath(source),
4255 source, branches = hg.parseurl(ui.expandpath(source),
4257 opts.get('branch'))
4256 opts.get('branch'))
4258 other = hg.peer(repo, opts, source)
4257 other = hg.peer(repo, opts, source)
4259 if 'bookmarks' not in other.listkeys('namespaces'):
4258 if 'bookmarks' not in other.listkeys('namespaces'):
4260 ui.warn(_("remote doesn't support bookmarks\n"))
4259 ui.warn(_("remote doesn't support bookmarks\n"))
4261 return 0
4260 return 0
4262 ui.status(_('comparing with %s\n') % util.hidepassword(source))
4261 ui.status(_('comparing with %s\n') % util.hidepassword(source))
4263 return bookmarks.diff(ui, repo, other)
4262 return bookmarks.diff(ui, repo, other)
4264
4263
4265 repo._subtoppath = ui.expandpath(source)
4264 repo._subtoppath = ui.expandpath(source)
4266 try:
4265 try:
4267 return hg.incoming(ui, repo, source, opts)
4266 return hg.incoming(ui, repo, source, opts)
4268 finally:
4267 finally:
4269 del repo._subtoppath
4268 del repo._subtoppath
4270
4269
4271
4270
4272 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'),
4271 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'),
4273 norepo=True)
4272 norepo=True)
4274 def init(ui, dest=".", **opts):
4273 def init(ui, dest=".", **opts):
4275 """create a new repository in the given directory
4274 """create a new repository in the given directory
4276
4275
4277 Initialize a new repository in the given directory. If the given
4276 Initialize a new repository in the given directory. If the given
4278 directory does not exist, it will be created.
4277 directory does not exist, it will be created.
4279
4278
4280 If no directory is given, the current directory is used.
4279 If no directory is given, the current directory is used.
4281
4280
4282 It is possible to specify an ``ssh://`` URL as the destination.
4281 It is possible to specify an ``ssh://`` URL as the destination.
4283 See :hg:`help urls` for more information.
4282 See :hg:`help urls` for more information.
4284
4283
4285 Returns 0 on success.
4284 Returns 0 on success.
4286 """
4285 """
4287 hg.peer(ui, opts, ui.expandpath(dest), create=True)
4286 hg.peer(ui, opts, ui.expandpath(dest), create=True)
4288
4287
4289 @command('locate',
4288 @command('locate',
4290 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
4289 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
4291 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
4290 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
4292 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
4291 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
4293 ] + walkopts,
4292 ] + walkopts,
4294 _('[OPTION]... [PATTERN]...'))
4293 _('[OPTION]... [PATTERN]...'))
4295 def locate(ui, repo, *pats, **opts):
4294 def locate(ui, repo, *pats, **opts):
4296 """locate files matching specific patterns (DEPRECATED)
4295 """locate files matching specific patterns (DEPRECATED)
4297
4296
4298 Print files under Mercurial control in the working directory whose
4297 Print files under Mercurial control in the working directory whose
4299 names match the given patterns.
4298 names match the given patterns.
4300
4299
4301 By default, this command searches all directories in the working
4300 By default, this command searches all directories in the working
4302 directory. To search just the current directory and its
4301 directory. To search just the current directory and its
4303 subdirectories, use "--include .".
4302 subdirectories, use "--include .".
4304
4303
4305 If no patterns are given to match, this command prints the names
4304 If no patterns are given to match, this command prints the names
4306 of all files under Mercurial control in the working directory.
4305 of all files under Mercurial control in the working directory.
4307
4306
4308 If you want to feed the output of this command into the "xargs"
4307 If you want to feed the output of this command into the "xargs"
4309 command, use the -0 option to both this command and "xargs". This
4308 command, use the -0 option to both this command and "xargs". This
4310 will avoid the problem of "xargs" treating single filenames that
4309 will avoid the problem of "xargs" treating single filenames that
4311 contain whitespace as multiple filenames.
4310 contain whitespace as multiple filenames.
4312
4311
4313 See :hg:`help files` for a more versatile command.
4312 See :hg:`help files` for a more versatile command.
4314
4313
4315 Returns 0 if a match is found, 1 otherwise.
4314 Returns 0 if a match is found, 1 otherwise.
4316 """
4315 """
4317 end = opts.get('print0') and '\0' or '\n'
4316 end = opts.get('print0') and '\0' or '\n'
4318 rev = scmutil.revsingle(repo, opts.get('rev'), None).node()
4317 rev = scmutil.revsingle(repo, opts.get('rev'), None).node()
4319
4318
4320 ret = 1
4319 ret = 1
4321 ctx = repo[rev]
4320 ctx = repo[rev]
4322 m = scmutil.match(ctx, pats, opts, default='relglob')
4321 m = scmutil.match(ctx, pats, opts, default='relglob')
4323 m.bad = lambda x, y: False
4322 m.bad = lambda x, y: False
4324
4323
4325 for abs in ctx.matches(m):
4324 for abs in ctx.matches(m):
4326 if opts.get('fullpath'):
4325 if opts.get('fullpath'):
4327 ui.write(repo.wjoin(abs), end)
4326 ui.write(repo.wjoin(abs), end)
4328 else:
4327 else:
4329 ui.write(((pats and m.rel(abs)) or abs), end)
4328 ui.write(((pats and m.rel(abs)) or abs), end)
4330 ret = 0
4329 ret = 0
4331
4330
4332 return ret
4331 return ret
4333
4332
4334 @command('^log|history',
4333 @command('^log|history',
4335 [('f', 'follow', None,
4334 [('f', 'follow', None,
4336 _('follow changeset history, or file history across copies and renames')),
4335 _('follow changeset history, or file history across copies and renames')),
4337 ('', 'follow-first', None,
4336 ('', 'follow-first', None,
4338 _('only follow the first parent of merge changesets (DEPRECATED)')),
4337 _('only follow the first parent of merge changesets (DEPRECATED)')),
4339 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
4338 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
4340 ('C', 'copies', None, _('show copied files')),
4339 ('C', 'copies', None, _('show copied files')),
4341 ('k', 'keyword', [],
4340 ('k', 'keyword', [],
4342 _('do case-insensitive search for a given text'), _('TEXT')),
4341 _('do case-insensitive search for a given text'), _('TEXT')),
4343 ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
4342 ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
4344 ('', 'removed', None, _('include revisions where files were removed')),
4343 ('', 'removed', None, _('include revisions where files were removed')),
4345 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
4344 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
4346 ('u', 'user', [], _('revisions committed by user'), _('USER')),
4345 ('u', 'user', [], _('revisions committed by user'), _('USER')),
4347 ('', 'only-branch', [],
4346 ('', 'only-branch', [],
4348 _('show only changesets within the given named branch (DEPRECATED)'),
4347 _('show only changesets within the given named branch (DEPRECATED)'),
4349 _('BRANCH')),
4348 _('BRANCH')),
4350 ('b', 'branch', [],
4349 ('b', 'branch', [],
4351 _('show changesets within the given named branch'), _('BRANCH')),
4350 _('show changesets within the given named branch'), _('BRANCH')),
4352 ('P', 'prune', [],
4351 ('P', 'prune', [],
4353 _('do not display revision or any of its ancestors'), _('REV')),
4352 _('do not display revision or any of its ancestors'), _('REV')),
4354 ] + logopts + walkopts,
4353 ] + logopts + walkopts,
4355 _('[OPTION]... [FILE]'),
4354 _('[OPTION]... [FILE]'),
4356 inferrepo=True)
4355 inferrepo=True)
4357 def log(ui, repo, *pats, **opts):
4356 def log(ui, repo, *pats, **opts):
4358 """show revision history of entire repository or files
4357 """show revision history of entire repository or files
4359
4358
4360 Print the revision history of the specified files or the entire
4359 Print the revision history of the specified files or the entire
4361 project.
4360 project.
4362
4361
4363 If no revision range is specified, the default is ``tip:0`` unless
4362 If no revision range is specified, the default is ``tip:0`` unless
4364 --follow is set, in which case the working directory parent is
4363 --follow is set, in which case the working directory parent is
4365 used as the starting revision.
4364 used as the starting revision.
4366
4365
4367 File history is shown without following rename or copy history of
4366 File history is shown without following rename or copy history of
4368 files. Use -f/--follow with a filename to follow history across
4367 files. Use -f/--follow with a filename to follow history across
4369 renames and copies. --follow without a filename will only show
4368 renames and copies. --follow without a filename will only show
4370 ancestors or descendants of the starting revision.
4369 ancestors or descendants of the starting revision.
4371
4370
4372 By default this command prints revision number and changeset id,
4371 By default this command prints revision number and changeset id,
4373 tags, non-trivial parents, user, date and time, and a summary for
4372 tags, non-trivial parents, user, date and time, and a summary for
4374 each commit. When the -v/--verbose switch is used, the list of
4373 each commit. When the -v/--verbose switch is used, the list of
4375 changed files and full commit message are shown.
4374 changed files and full commit message are shown.
4376
4375
4377 With --graph the revisions are shown as an ASCII art DAG with the most
4376 With --graph the revisions are shown as an ASCII art DAG with the most
4378 recent changeset at the top.
4377 recent changeset at the top.
4379 'o' is a changeset, '@' is a working directory parent, 'x' is obsolete,
4378 'o' is a changeset, '@' is a working directory parent, 'x' is obsolete,
4380 and '+' represents a fork where the changeset from the lines below is a
4379 and '+' represents a fork where the changeset from the lines below is a
4381 parent of the 'o' merge on the same line.
4380 parent of the 'o' merge on the same line.
4382
4381
4383 .. note::
4382 .. note::
4384
4383
4385 log -p/--patch may generate unexpected diff output for merge
4384 log -p/--patch may generate unexpected diff output for merge
4386 changesets, as it will only compare the merge changeset against
4385 changesets, as it will only compare the merge changeset against
4387 its first parent. Also, only files different from BOTH parents
4386 its first parent. Also, only files different from BOTH parents
4388 will appear in files:.
4387 will appear in files:.
4389
4388
4390 .. note::
4389 .. note::
4391
4390
4392 for performance reasons, log FILE may omit duplicate changes
4391 for performance reasons, log FILE may omit duplicate changes
4393 made on branches and will not show removals or mode changes. To
4392 made on branches and will not show removals or mode changes. To
4394 see all such changes, use the --removed switch.
4393 see all such changes, use the --removed switch.
4395
4394
4396 .. container:: verbose
4395 .. container:: verbose
4397
4396
4398 Some examples:
4397 Some examples:
4399
4398
4400 - changesets with full descriptions and file lists::
4399 - changesets with full descriptions and file lists::
4401
4400
4402 hg log -v
4401 hg log -v
4403
4402
4404 - changesets ancestral to the working directory::
4403 - changesets ancestral to the working directory::
4405
4404
4406 hg log -f
4405 hg log -f
4407
4406
4408 - last 10 commits on the current branch::
4407 - last 10 commits on the current branch::
4409
4408
4410 hg log -l 10 -b .
4409 hg log -l 10 -b .
4411
4410
4412 - changesets showing all modifications of a file, including removals::
4411 - changesets showing all modifications of a file, including removals::
4413
4412
4414 hg log --removed file.c
4413 hg log --removed file.c
4415
4414
4416 - all changesets that touch a directory, with diffs, excluding merges::
4415 - all changesets that touch a directory, with diffs, excluding merges::
4417
4416
4418 hg log -Mp lib/
4417 hg log -Mp lib/
4419
4418
4420 - all revision numbers that match a keyword::
4419 - all revision numbers that match a keyword::
4421
4420
4422 hg log -k bug --template "{rev}\\n"
4421 hg log -k bug --template "{rev}\\n"
4423
4422
4424 - list available log templates::
4423 - list available log templates::
4425
4424
4426 hg log -T list
4425 hg log -T list
4427
4426
4428 - check if a given changeset is included in a tagged release::
4427 - check if a given changeset is included in a tagged release::
4429
4428
4430 hg log -r "a21ccf and ancestor(1.9)"
4429 hg log -r "a21ccf and ancestor(1.9)"
4431
4430
4432 - find all changesets by some user in a date range::
4431 - find all changesets by some user in a date range::
4433
4432
4434 hg log -k alice -d "may 2008 to jul 2008"
4433 hg log -k alice -d "may 2008 to jul 2008"
4435
4434
4436 - summary of all changesets after the last tag::
4435 - summary of all changesets after the last tag::
4437
4436
4438 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
4437 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
4439
4438
4440 See :hg:`help dates` for a list of formats valid for -d/--date.
4439 See :hg:`help dates` for a list of formats valid for -d/--date.
4441
4440
4442 See :hg:`help revisions` and :hg:`help revsets` for more about
4441 See :hg:`help revisions` and :hg:`help revsets` for more about
4443 specifying revisions.
4442 specifying revisions.
4444
4443
4445 See :hg:`help templates` for more about pre-packaged styles and
4444 See :hg:`help templates` for more about pre-packaged styles and
4446 specifying custom templates.
4445 specifying custom templates.
4447
4446
4448 Returns 0 on success.
4447 Returns 0 on success.
4449
4448
4450 """
4449 """
4451 if opts.get('graph'):
4450 if opts.get('graph'):
4452 return cmdutil.graphlog(ui, repo, *pats, **opts)
4451 return cmdutil.graphlog(ui, repo, *pats, **opts)
4453
4452
4454 revs, expr, filematcher = cmdutil.getlogrevs(repo, pats, opts)
4453 revs, expr, filematcher = cmdutil.getlogrevs(repo, pats, opts)
4455 limit = cmdutil.loglimit(opts)
4454 limit = cmdutil.loglimit(opts)
4456 count = 0
4455 count = 0
4457
4456
4458 getrenamed = None
4457 getrenamed = None
4459 if opts.get('copies'):
4458 if opts.get('copies'):
4460 endrev = None
4459 endrev = None
4461 if opts.get('rev'):
4460 if opts.get('rev'):
4462 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
4461 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
4463 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
4462 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
4464
4463
4465 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
4464 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
4466 for rev in revs:
4465 for rev in revs:
4467 if count == limit:
4466 if count == limit:
4468 break
4467 break
4469 ctx = repo[rev]
4468 ctx = repo[rev]
4470 copies = None
4469 copies = None
4471 if getrenamed is not None and rev:
4470 if getrenamed is not None and rev:
4472 copies = []
4471 copies = []
4473 for fn in ctx.files():
4472 for fn in ctx.files():
4474 rename = getrenamed(fn, rev)
4473 rename = getrenamed(fn, rev)
4475 if rename:
4474 if rename:
4476 copies.append((fn, rename[0]))
4475 copies.append((fn, rename[0]))
4477 revmatchfn = filematcher and filematcher(ctx.rev()) or None
4476 revmatchfn = filematcher and filematcher(ctx.rev()) or None
4478 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
4477 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
4479 if displayer.flush(rev):
4478 if displayer.flush(rev):
4480 count += 1
4479 count += 1
4481
4480
4482 displayer.close()
4481 displayer.close()
4483
4482
4484 @command('manifest',
4483 @command('manifest',
4485 [('r', 'rev', '', _('revision to display'), _('REV')),
4484 [('r', 'rev', '', _('revision to display'), _('REV')),
4486 ('', 'all', False, _("list files from all revisions"))]
4485 ('', 'all', False, _("list files from all revisions"))]
4487 + formatteropts,
4486 + formatteropts,
4488 _('[-r REV]'))
4487 _('[-r REV]'))
4489 def manifest(ui, repo, node=None, rev=None, **opts):
4488 def manifest(ui, repo, node=None, rev=None, **opts):
4490 """output the current or given revision of the project manifest
4489 """output the current or given revision of the project manifest
4491
4490
4492 Print a list of version controlled files for the given revision.
4491 Print a list of version controlled files for the given revision.
4493 If no revision is given, the first parent of the working directory
4492 If no revision is given, the first parent of the working directory
4494 is used, or the null revision if no revision is checked out.
4493 is used, or the null revision if no revision is checked out.
4495
4494
4496 With -v, print file permissions, symlink and executable bits.
4495 With -v, print file permissions, symlink and executable bits.
4497 With --debug, print file revision hashes.
4496 With --debug, print file revision hashes.
4498
4497
4499 If option --all is specified, the list of all files from all revisions
4498 If option --all is specified, the list of all files from all revisions
4500 is printed. This includes deleted and renamed files.
4499 is printed. This includes deleted and renamed files.
4501
4500
4502 Returns 0 on success.
4501 Returns 0 on success.
4503 """
4502 """
4504
4503
4505 fm = ui.formatter('manifest', opts)
4504 fm = ui.formatter('manifest', opts)
4506
4505
4507 if opts.get('all'):
4506 if opts.get('all'):
4508 if rev or node:
4507 if rev or node:
4509 raise util.Abort(_("can't specify a revision with --all"))
4508 raise util.Abort(_("can't specify a revision with --all"))
4510
4509
4511 res = []
4510 res = []
4512 prefix = "data/"
4511 prefix = "data/"
4513 suffix = ".i"
4512 suffix = ".i"
4514 plen = len(prefix)
4513 plen = len(prefix)
4515 slen = len(suffix)
4514 slen = len(suffix)
4516 lock = repo.lock()
4515 lock = repo.lock()
4517 try:
4516 try:
4518 for fn, b, size in repo.store.datafiles():
4517 for fn, b, size in repo.store.datafiles():
4519 if size != 0 and fn[-slen:] == suffix and fn[:plen] == prefix:
4518 if size != 0 and fn[-slen:] == suffix and fn[:plen] == prefix:
4520 res.append(fn[plen:-slen])
4519 res.append(fn[plen:-slen])
4521 finally:
4520 finally:
4522 lock.release()
4521 lock.release()
4523 for f in res:
4522 for f in res:
4524 fm.startitem()
4523 fm.startitem()
4525 fm.write("path", '%s\n', f)
4524 fm.write("path", '%s\n', f)
4526 fm.end()
4525 fm.end()
4527 return
4526 return
4528
4527
4529 if rev and node:
4528 if rev and node:
4530 raise util.Abort(_("please specify just one revision"))
4529 raise util.Abort(_("please specify just one revision"))
4531
4530
4532 if not node:
4531 if not node:
4533 node = rev
4532 node = rev
4534
4533
4535 char = {'l': '@', 'x': '*', '': ''}
4534 char = {'l': '@', 'x': '*', '': ''}
4536 mode = {'l': '644', 'x': '755', '': '644'}
4535 mode = {'l': '644', 'x': '755', '': '644'}
4537 ctx = scmutil.revsingle(repo, node)
4536 ctx = scmutil.revsingle(repo, node)
4538 mf = ctx.manifest()
4537 mf = ctx.manifest()
4539 for f in ctx:
4538 for f in ctx:
4540 fm.startitem()
4539 fm.startitem()
4541 fl = ctx[f].flags()
4540 fl = ctx[f].flags()
4542 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
4541 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
4543 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
4542 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
4544 fm.write('path', '%s\n', f)
4543 fm.write('path', '%s\n', f)
4545 fm.end()
4544 fm.end()
4546
4545
4547 @command('^merge',
4546 @command('^merge',
4548 [('f', 'force', None,
4547 [('f', 'force', None,
4549 _('force a merge including outstanding changes (DEPRECATED)')),
4548 _('force a merge including outstanding changes (DEPRECATED)')),
4550 ('r', 'rev', '', _('revision to merge'), _('REV')),
4549 ('r', 'rev', '', _('revision to merge'), _('REV')),
4551 ('P', 'preview', None,
4550 ('P', 'preview', None,
4552 _('review revisions to merge (no merge is performed)'))
4551 _('review revisions to merge (no merge is performed)'))
4553 ] + mergetoolopts,
4552 ] + mergetoolopts,
4554 _('[-P] [-f] [[-r] REV]'))
4553 _('[-P] [-f] [[-r] REV]'))
4555 def merge(ui, repo, node=None, **opts):
4554 def merge(ui, repo, node=None, **opts):
4556 """merge another revision into working directory
4555 """merge another revision into working directory
4557
4556
4558 The current working directory is updated with all changes made in
4557 The current working directory is updated with all changes made in
4559 the requested revision since the last common predecessor revision.
4558 the requested revision since the last common predecessor revision.
4560
4559
4561 Files that changed between either parent are marked as changed for
4560 Files that changed between either parent are marked as changed for
4562 the next commit and a commit must be performed before any further
4561 the next commit and a commit must be performed before any further
4563 updates to the repository are allowed. The next commit will have
4562 updates to the repository are allowed. The next commit will have
4564 two parents.
4563 two parents.
4565
4564
4566 ``--tool`` can be used to specify the merge tool used for file
4565 ``--tool`` can be used to specify the merge tool used for file
4567 merges. It overrides the HGMERGE environment variable and your
4566 merges. It overrides the HGMERGE environment variable and your
4568 configuration files. See :hg:`help merge-tools` for options.
4567 configuration files. See :hg:`help merge-tools` for options.
4569
4568
4570 If no revision is specified, the working directory's parent is a
4569 If no revision is specified, the working directory's parent is a
4571 head revision, and the current branch contains exactly one other
4570 head revision, and the current branch contains exactly one other
4572 head, the other head is merged with by default. Otherwise, an
4571 head, the other head is merged with by default. Otherwise, an
4573 explicit revision with which to merge with must be provided.
4572 explicit revision with which to merge with must be provided.
4574
4573
4575 :hg:`resolve` must be used to resolve unresolved files.
4574 :hg:`resolve` must be used to resolve unresolved files.
4576
4575
4577 To undo an uncommitted merge, use :hg:`update --clean .` which
4576 To undo an uncommitted merge, use :hg:`update --clean .` which
4578 will check out a clean copy of the original merge parent, losing
4577 will check out a clean copy of the original merge parent, losing
4579 all changes.
4578 all changes.
4580
4579
4581 Returns 0 on success, 1 if there are unresolved files.
4580 Returns 0 on success, 1 if there are unresolved files.
4582 """
4581 """
4583
4582
4584 if opts.get('rev') and node:
4583 if opts.get('rev') and node:
4585 raise util.Abort(_("please specify just one revision"))
4584 raise util.Abort(_("please specify just one revision"))
4586 if not node:
4585 if not node:
4587 node = opts.get('rev')
4586 node = opts.get('rev')
4588
4587
4589 if node:
4588 if node:
4590 node = scmutil.revsingle(repo, node).node()
4589 node = scmutil.revsingle(repo, node).node()
4591
4590
4592 if not node and repo._bookmarkcurrent:
4591 if not node and repo._bookmarkcurrent:
4593 bmheads = repo.bookmarkheads(repo._bookmarkcurrent)
4592 bmheads = repo.bookmarkheads(repo._bookmarkcurrent)
4594 curhead = repo[repo._bookmarkcurrent].node()
4593 curhead = repo[repo._bookmarkcurrent].node()
4595 if len(bmheads) == 2:
4594 if len(bmheads) == 2:
4596 if curhead == bmheads[0]:
4595 if curhead == bmheads[0]:
4597 node = bmheads[1]
4596 node = bmheads[1]
4598 else:
4597 else:
4599 node = bmheads[0]
4598 node = bmheads[0]
4600 elif len(bmheads) > 2:
4599 elif len(bmheads) > 2:
4601 raise util.Abort(_("multiple matching bookmarks to merge - "
4600 raise util.Abort(_("multiple matching bookmarks to merge - "
4602 "please merge with an explicit rev or bookmark"),
4601 "please merge with an explicit rev or bookmark"),
4603 hint=_("run 'hg heads' to see all heads"))
4602 hint=_("run 'hg heads' to see all heads"))
4604 elif len(bmheads) <= 1:
4603 elif len(bmheads) <= 1:
4605 raise util.Abort(_("no matching bookmark to merge - "
4604 raise util.Abort(_("no matching bookmark to merge - "
4606 "please merge with an explicit rev or bookmark"),
4605 "please merge with an explicit rev or bookmark"),
4607 hint=_("run 'hg heads' to see all heads"))
4606 hint=_("run 'hg heads' to see all heads"))
4608
4607
4609 if not node and not repo._bookmarkcurrent:
4608 if not node and not repo._bookmarkcurrent:
4610 branch = repo[None].branch()
4609 branch = repo[None].branch()
4611 bheads = repo.branchheads(branch)
4610 bheads = repo.branchheads(branch)
4612 nbhs = [bh for bh in bheads if not repo[bh].bookmarks()]
4611 nbhs = [bh for bh in bheads if not repo[bh].bookmarks()]
4613
4612
4614 if len(nbhs) > 2:
4613 if len(nbhs) > 2:
4615 raise util.Abort(_("branch '%s' has %d heads - "
4614 raise util.Abort(_("branch '%s' has %d heads - "
4616 "please merge with an explicit rev")
4615 "please merge with an explicit rev")
4617 % (branch, len(bheads)),
4616 % (branch, len(bheads)),
4618 hint=_("run 'hg heads .' to see heads"))
4617 hint=_("run 'hg heads .' to see heads"))
4619
4618
4620 parent = repo.dirstate.p1()
4619 parent = repo.dirstate.p1()
4621 if len(nbhs) <= 1:
4620 if len(nbhs) <= 1:
4622 if len(bheads) > 1:
4621 if len(bheads) > 1:
4623 raise util.Abort(_("heads are bookmarked - "
4622 raise util.Abort(_("heads are bookmarked - "
4624 "please merge with an explicit rev"),
4623 "please merge with an explicit rev"),
4625 hint=_("run 'hg heads' to see all heads"))
4624 hint=_("run 'hg heads' to see all heads"))
4626 if len(repo.heads()) > 1:
4625 if len(repo.heads()) > 1:
4627 raise util.Abort(_("branch '%s' has one head - "
4626 raise util.Abort(_("branch '%s' has one head - "
4628 "please merge with an explicit rev")
4627 "please merge with an explicit rev")
4629 % branch,
4628 % branch,
4630 hint=_("run 'hg heads' to see all heads"))
4629 hint=_("run 'hg heads' to see all heads"))
4631 msg, hint = _('nothing to merge'), None
4630 msg, hint = _('nothing to merge'), None
4632 if parent != repo.lookup(branch):
4631 if parent != repo.lookup(branch):
4633 hint = _("use 'hg update' instead")
4632 hint = _("use 'hg update' instead")
4634 raise util.Abort(msg, hint=hint)
4633 raise util.Abort(msg, hint=hint)
4635
4634
4636 if parent not in bheads:
4635 if parent not in bheads:
4637 raise util.Abort(_('working directory not at a head revision'),
4636 raise util.Abort(_('working directory not at a head revision'),
4638 hint=_("use 'hg update' or merge with an "
4637 hint=_("use 'hg update' or merge with an "
4639 "explicit revision"))
4638 "explicit revision"))
4640 if parent == nbhs[0]:
4639 if parent == nbhs[0]:
4641 node = nbhs[-1]
4640 node = nbhs[-1]
4642 else:
4641 else:
4643 node = nbhs[0]
4642 node = nbhs[0]
4644
4643
4645 if opts.get('preview'):
4644 if opts.get('preview'):
4646 # find nodes that are ancestors of p2 but not of p1
4645 # find nodes that are ancestors of p2 but not of p1
4647 p1 = repo.lookup('.')
4646 p1 = repo.lookup('.')
4648 p2 = repo.lookup(node)
4647 p2 = repo.lookup(node)
4649 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4648 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4650
4649
4651 displayer = cmdutil.show_changeset(ui, repo, opts)
4650 displayer = cmdutil.show_changeset(ui, repo, opts)
4652 for node in nodes:
4651 for node in nodes:
4653 displayer.show(repo[node])
4652 displayer.show(repo[node])
4654 displayer.close()
4653 displayer.close()
4655 return 0
4654 return 0
4656
4655
4657 try:
4656 try:
4658 # ui.forcemerge is an internal variable, do not document
4657 # ui.forcemerge is an internal variable, do not document
4659 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'merge')
4658 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'merge')
4660 return hg.merge(repo, node, force=opts.get('force'))
4659 return hg.merge(repo, node, force=opts.get('force'))
4661 finally:
4660 finally:
4662 ui.setconfig('ui', 'forcemerge', '', 'merge')
4661 ui.setconfig('ui', 'forcemerge', '', 'merge')
4663
4662
4664 @command('outgoing|out',
4663 @command('outgoing|out',
4665 [('f', 'force', None, _('run even when the destination is unrelated')),
4664 [('f', 'force', None, _('run even when the destination is unrelated')),
4666 ('r', 'rev', [],
4665 ('r', 'rev', [],
4667 _('a changeset intended to be included in the destination'), _('REV')),
4666 _('a changeset intended to be included in the destination'), _('REV')),
4668 ('n', 'newest-first', None, _('show newest record first')),
4667 ('n', 'newest-first', None, _('show newest record first')),
4669 ('B', 'bookmarks', False, _('compare bookmarks')),
4668 ('B', 'bookmarks', False, _('compare bookmarks')),
4670 ('b', 'branch', [], _('a specific branch you would like to push'),
4669 ('b', 'branch', [], _('a specific branch you would like to push'),
4671 _('BRANCH')),
4670 _('BRANCH')),
4672 ] + logopts + remoteopts + subrepoopts,
4671 ] + logopts + remoteopts + subrepoopts,
4673 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
4672 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
4674 def outgoing(ui, repo, dest=None, **opts):
4673 def outgoing(ui, repo, dest=None, **opts):
4675 """show changesets not found in the destination
4674 """show changesets not found in the destination
4676
4675
4677 Show changesets not found in the specified destination repository
4676 Show changesets not found in the specified destination repository
4678 or the default push location. These are the changesets that would
4677 or the default push location. These are the changesets that would
4679 be pushed if a push was requested.
4678 be pushed if a push was requested.
4680
4679
4681 See pull for details of valid destination formats.
4680 See pull for details of valid destination formats.
4682
4681
4683 Returns 0 if there are outgoing changes, 1 otherwise.
4682 Returns 0 if there are outgoing changes, 1 otherwise.
4684 """
4683 """
4685 if opts.get('graph'):
4684 if opts.get('graph'):
4686 cmdutil.checkunsupportedgraphflags([], opts)
4685 cmdutil.checkunsupportedgraphflags([], opts)
4687 o, other = hg._outgoing(ui, repo, dest, opts)
4686 o, other = hg._outgoing(ui, repo, dest, opts)
4688 if not o:
4687 if not o:
4689 cmdutil.outgoinghooks(ui, repo, other, opts, o)
4688 cmdutil.outgoinghooks(ui, repo, other, opts, o)
4690 return
4689 return
4691
4690
4692 revdag = cmdutil.graphrevs(repo, o, opts)
4691 revdag = cmdutil.graphrevs(repo, o, opts)
4693 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
4692 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
4694 showparents = [ctx.node() for ctx in repo[None].parents()]
4693 showparents = [ctx.node() for ctx in repo[None].parents()]
4695 cmdutil.displaygraph(ui, revdag, displayer, showparents,
4694 cmdutil.displaygraph(ui, revdag, displayer, showparents,
4696 graphmod.asciiedges)
4695 graphmod.asciiedges)
4697 cmdutil.outgoinghooks(ui, repo, other, opts, o)
4696 cmdutil.outgoinghooks(ui, repo, other, opts, o)
4698 return 0
4697 return 0
4699
4698
4700 if opts.get('bookmarks'):
4699 if opts.get('bookmarks'):
4701 dest = ui.expandpath(dest or 'default-push', dest or 'default')
4700 dest = ui.expandpath(dest or 'default-push', dest or 'default')
4702 dest, branches = hg.parseurl(dest, opts.get('branch'))
4701 dest, branches = hg.parseurl(dest, opts.get('branch'))
4703 other = hg.peer(repo, opts, dest)
4702 other = hg.peer(repo, opts, dest)
4704 if 'bookmarks' not in other.listkeys('namespaces'):
4703 if 'bookmarks' not in other.listkeys('namespaces'):
4705 ui.warn(_("remote doesn't support bookmarks\n"))
4704 ui.warn(_("remote doesn't support bookmarks\n"))
4706 return 0
4705 return 0
4707 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
4706 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
4708 return bookmarks.diff(ui, other, repo)
4707 return bookmarks.diff(ui, other, repo)
4709
4708
4710 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
4709 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
4711 try:
4710 try:
4712 return hg.outgoing(ui, repo, dest, opts)
4711 return hg.outgoing(ui, repo, dest, opts)
4713 finally:
4712 finally:
4714 del repo._subtoppath
4713 del repo._subtoppath
4715
4714
4716 @command('parents',
4715 @command('parents',
4717 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
4716 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
4718 ] + templateopts,
4717 ] + templateopts,
4719 _('[-r REV] [FILE]'),
4718 _('[-r REV] [FILE]'),
4720 inferrepo=True)
4719 inferrepo=True)
4721 def parents(ui, repo, file_=None, **opts):
4720 def parents(ui, repo, file_=None, **opts):
4722 """show the parents of the working directory or revision (DEPRECATED)
4721 """show the parents of the working directory or revision (DEPRECATED)
4723
4722
4724 Print the working directory's parent revisions. If a revision is
4723 Print the working directory's parent revisions. If a revision is
4725 given via -r/--rev, the parent of that revision will be printed.
4724 given via -r/--rev, the parent of that revision will be printed.
4726 If a file argument is given, the revision in which the file was
4725 If a file argument is given, the revision in which the file was
4727 last changed (before the working directory revision or the
4726 last changed (before the working directory revision or the
4728 argument to --rev if given) is printed.
4727 argument to --rev if given) is printed.
4729
4728
4730 See :hg:`summary` and :hg:`help revsets` for related information.
4729 See :hg:`summary` and :hg:`help revsets` for related information.
4731
4730
4732 Returns 0 on success.
4731 Returns 0 on success.
4733 """
4732 """
4734
4733
4735 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
4734 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
4736
4735
4737 if file_:
4736 if file_:
4738 m = scmutil.match(ctx, (file_,), opts)
4737 m = scmutil.match(ctx, (file_,), opts)
4739 if m.anypats() or len(m.files()) != 1:
4738 if m.anypats() or len(m.files()) != 1:
4740 raise util.Abort(_('can only specify an explicit filename'))
4739 raise util.Abort(_('can only specify an explicit filename'))
4741 file_ = m.files()[0]
4740 file_ = m.files()[0]
4742 filenodes = []
4741 filenodes = []
4743 for cp in ctx.parents():
4742 for cp in ctx.parents():
4744 if not cp:
4743 if not cp:
4745 continue
4744 continue
4746 try:
4745 try:
4747 filenodes.append(cp.filenode(file_))
4746 filenodes.append(cp.filenode(file_))
4748 except error.LookupError:
4747 except error.LookupError:
4749 pass
4748 pass
4750 if not filenodes:
4749 if not filenodes:
4751 raise util.Abort(_("'%s' not found in manifest!") % file_)
4750 raise util.Abort(_("'%s' not found in manifest!") % file_)
4752 p = []
4751 p = []
4753 for fn in filenodes:
4752 for fn in filenodes:
4754 fctx = repo.filectx(file_, fileid=fn)
4753 fctx = repo.filectx(file_, fileid=fn)
4755 p.append(fctx.node())
4754 p.append(fctx.node())
4756 else:
4755 else:
4757 p = [cp.node() for cp in ctx.parents()]
4756 p = [cp.node() for cp in ctx.parents()]
4758
4757
4759 displayer = cmdutil.show_changeset(ui, repo, opts)
4758 displayer = cmdutil.show_changeset(ui, repo, opts)
4760 for n in p:
4759 for n in p:
4761 if n != nullid:
4760 if n != nullid:
4762 displayer.show(repo[n])
4761 displayer.show(repo[n])
4763 displayer.close()
4762 displayer.close()
4764
4763
4765 @command('paths', [], _('[NAME]'), optionalrepo=True)
4764 @command('paths', [], _('[NAME]'), optionalrepo=True)
4766 def paths(ui, repo, search=None):
4765 def paths(ui, repo, search=None):
4767 """show aliases for remote repositories
4766 """show aliases for remote repositories
4768
4767
4769 Show definition of symbolic path name NAME. If no name is given,
4768 Show definition of symbolic path name NAME. If no name is given,
4770 show definition of all available names.
4769 show definition of all available names.
4771
4770
4772 Option -q/--quiet suppresses all output when searching for NAME
4771 Option -q/--quiet suppresses all output when searching for NAME
4773 and shows only the path names when listing all definitions.
4772 and shows only the path names when listing all definitions.
4774
4773
4775 Path names are defined in the [paths] section of your
4774 Path names are defined in the [paths] section of your
4776 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
4775 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
4777 repository, ``.hg/hgrc`` is used, too.
4776 repository, ``.hg/hgrc`` is used, too.
4778
4777
4779 The path names ``default`` and ``default-push`` have a special
4778 The path names ``default`` and ``default-push`` have a special
4780 meaning. When performing a push or pull operation, they are used
4779 meaning. When performing a push or pull operation, they are used
4781 as fallbacks if no location is specified on the command-line.
4780 as fallbacks if no location is specified on the command-line.
4782 When ``default-push`` is set, it will be used for push and
4781 When ``default-push`` is set, it will be used for push and
4783 ``default`` will be used for pull; otherwise ``default`` is used
4782 ``default`` will be used for pull; otherwise ``default`` is used
4784 as the fallback for both. When cloning a repository, the clone
4783 as the fallback for both. When cloning a repository, the clone
4785 source is written as ``default`` in ``.hg/hgrc``. Note that
4784 source is written as ``default`` in ``.hg/hgrc``. Note that
4786 ``default`` and ``default-push`` apply to all inbound (e.g.
4785 ``default`` and ``default-push`` apply to all inbound (e.g.
4787 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email` and
4786 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email` and
4788 :hg:`bundle`) operations.
4787 :hg:`bundle`) operations.
4789
4788
4790 See :hg:`help urls` for more information.
4789 See :hg:`help urls` for more information.
4791
4790
4792 Returns 0 on success.
4791 Returns 0 on success.
4793 """
4792 """
4794 if search:
4793 if search:
4795 for name, path in ui.configitems("paths"):
4794 for name, path in ui.configitems("paths"):
4796 if name == search:
4795 if name == search:
4797 ui.status("%s\n" % util.hidepassword(path))
4796 ui.status("%s\n" % util.hidepassword(path))
4798 return
4797 return
4799 if not ui.quiet:
4798 if not ui.quiet:
4800 ui.warn(_("not found!\n"))
4799 ui.warn(_("not found!\n"))
4801 return 1
4800 return 1
4802 else:
4801 else:
4803 for name, path in ui.configitems("paths"):
4802 for name, path in ui.configitems("paths"):
4804 if ui.quiet:
4803 if ui.quiet:
4805 ui.write("%s\n" % name)
4804 ui.write("%s\n" % name)
4806 else:
4805 else:
4807 ui.write("%s = %s\n" % (name, util.hidepassword(path)))
4806 ui.write("%s = %s\n" % (name, util.hidepassword(path)))
4808
4807
4809 @command('phase',
4808 @command('phase',
4810 [('p', 'public', False, _('set changeset phase to public')),
4809 [('p', 'public', False, _('set changeset phase to public')),
4811 ('d', 'draft', False, _('set changeset phase to draft')),
4810 ('d', 'draft', False, _('set changeset phase to draft')),
4812 ('s', 'secret', False, _('set changeset phase to secret')),
4811 ('s', 'secret', False, _('set changeset phase to secret')),
4813 ('f', 'force', False, _('allow to move boundary backward')),
4812 ('f', 'force', False, _('allow to move boundary backward')),
4814 ('r', 'rev', [], _('target revision'), _('REV')),
4813 ('r', 'rev', [], _('target revision'), _('REV')),
4815 ],
4814 ],
4816 _('[-p|-d|-s] [-f] [-r] REV...'))
4815 _('[-p|-d|-s] [-f] [-r] REV...'))
4817 def phase(ui, repo, *revs, **opts):
4816 def phase(ui, repo, *revs, **opts):
4818 """set or show the current phase name
4817 """set or show the current phase name
4819
4818
4820 With no argument, show the phase name of specified revisions.
4819 With no argument, show the phase name of specified revisions.
4821
4820
4822 With one of -p/--public, -d/--draft or -s/--secret, change the
4821 With one of -p/--public, -d/--draft or -s/--secret, change the
4823 phase value of the specified revisions.
4822 phase value of the specified revisions.
4824
4823
4825 Unless -f/--force is specified, :hg:`phase` won't move changeset from a
4824 Unless -f/--force is specified, :hg:`phase` won't move changeset from a
4826 lower phase to an higher phase. Phases are ordered as follows::
4825 lower phase to an higher phase. Phases are ordered as follows::
4827
4826
4828 public < draft < secret
4827 public < draft < secret
4829
4828
4830 Returns 0 on success, 1 if no phases were changed or some could not
4829 Returns 0 on success, 1 if no phases were changed or some could not
4831 be changed.
4830 be changed.
4832 """
4831 """
4833 # search for a unique phase argument
4832 # search for a unique phase argument
4834 targetphase = None
4833 targetphase = None
4835 for idx, name in enumerate(phases.phasenames):
4834 for idx, name in enumerate(phases.phasenames):
4836 if opts[name]:
4835 if opts[name]:
4837 if targetphase is not None:
4836 if targetphase is not None:
4838 raise util.Abort(_('only one phase can be specified'))
4837 raise util.Abort(_('only one phase can be specified'))
4839 targetphase = idx
4838 targetphase = idx
4840
4839
4841 # look for specified revision
4840 # look for specified revision
4842 revs = list(revs)
4841 revs = list(revs)
4843 revs.extend(opts['rev'])
4842 revs.extend(opts['rev'])
4844 if not revs:
4843 if not revs:
4845 raise util.Abort(_('no revisions specified'))
4844 raise util.Abort(_('no revisions specified'))
4846
4845
4847 revs = scmutil.revrange(repo, revs)
4846 revs = scmutil.revrange(repo, revs)
4848
4847
4849 lock = None
4848 lock = None
4850 ret = 0
4849 ret = 0
4851 if targetphase is None:
4850 if targetphase is None:
4852 # display
4851 # display
4853 for r in revs:
4852 for r in revs:
4854 ctx = repo[r]
4853 ctx = repo[r]
4855 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
4854 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
4856 else:
4855 else:
4857 tr = None
4856 tr = None
4858 lock = repo.lock()
4857 lock = repo.lock()
4859 try:
4858 try:
4860 tr = repo.transaction("phase")
4859 tr = repo.transaction("phase")
4861 # set phase
4860 # set phase
4862 if not revs:
4861 if not revs:
4863 raise util.Abort(_('empty revision set'))
4862 raise util.Abort(_('empty revision set'))
4864 nodes = [repo[r].node() for r in revs]
4863 nodes = [repo[r].node() for r in revs]
4865 # moving revision from public to draft may hide them
4864 # moving revision from public to draft may hide them
4866 # We have to check result on an unfiltered repository
4865 # We have to check result on an unfiltered repository
4867 unfi = repo.unfiltered()
4866 unfi = repo.unfiltered()
4868 getphase = unfi._phasecache.phase
4867 getphase = unfi._phasecache.phase
4869 olddata = [getphase(unfi, r) for r in unfi]
4868 olddata = [getphase(unfi, r) for r in unfi]
4870 phases.advanceboundary(repo, tr, targetphase, nodes)
4869 phases.advanceboundary(repo, tr, targetphase, nodes)
4871 if opts['force']:
4870 if opts['force']:
4872 phases.retractboundary(repo, tr, targetphase, nodes)
4871 phases.retractboundary(repo, tr, targetphase, nodes)
4873 tr.close()
4872 tr.close()
4874 finally:
4873 finally:
4875 if tr is not None:
4874 if tr is not None:
4876 tr.release()
4875 tr.release()
4877 lock.release()
4876 lock.release()
4878 getphase = unfi._phasecache.phase
4877 getphase = unfi._phasecache.phase
4879 newdata = [getphase(unfi, r) for r in unfi]
4878 newdata = [getphase(unfi, r) for r in unfi]
4880 changes = sum(newdata[r] != olddata[r] for r in unfi)
4879 changes = sum(newdata[r] != olddata[r] for r in unfi)
4881 cl = unfi.changelog
4880 cl = unfi.changelog
4882 rejected = [n for n in nodes
4881 rejected = [n for n in nodes
4883 if newdata[cl.rev(n)] < targetphase]
4882 if newdata[cl.rev(n)] < targetphase]
4884 if rejected:
4883 if rejected:
4885 ui.warn(_('cannot move %i changesets to a higher '
4884 ui.warn(_('cannot move %i changesets to a higher '
4886 'phase, use --force\n') % len(rejected))
4885 'phase, use --force\n') % len(rejected))
4887 ret = 1
4886 ret = 1
4888 if changes:
4887 if changes:
4889 msg = _('phase changed for %i changesets\n') % changes
4888 msg = _('phase changed for %i changesets\n') % changes
4890 if ret:
4889 if ret:
4891 ui.status(msg)
4890 ui.status(msg)
4892 else:
4891 else:
4893 ui.note(msg)
4892 ui.note(msg)
4894 else:
4893 else:
4895 ui.warn(_('no phases changed\n'))
4894 ui.warn(_('no phases changed\n'))
4896 ret = 1
4895 ret = 1
4897 return ret
4896 return ret
4898
4897
4899 def postincoming(ui, repo, modheads, optupdate, checkout):
4898 def postincoming(ui, repo, modheads, optupdate, checkout):
4900 if modheads == 0:
4899 if modheads == 0:
4901 return
4900 return
4902 if optupdate:
4901 if optupdate:
4903 checkout, movemarkfrom = bookmarks.calculateupdate(ui, repo, checkout)
4902 checkout, movemarkfrom = bookmarks.calculateupdate(ui, repo, checkout)
4904 try:
4903 try:
4905 ret = hg.update(repo, checkout)
4904 ret = hg.update(repo, checkout)
4906 except util.Abort, inst:
4905 except util.Abort, inst:
4907 ui.warn(_("not updating: %s\n") % str(inst))
4906 ui.warn(_("not updating: %s\n") % str(inst))
4908 if inst.hint:
4907 if inst.hint:
4909 ui.warn(_("(%s)\n") % inst.hint)
4908 ui.warn(_("(%s)\n") % inst.hint)
4910 return 0
4909 return 0
4911 if not ret and not checkout:
4910 if not ret and not checkout:
4912 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
4911 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
4913 ui.status(_("updating bookmark %s\n") % repo._bookmarkcurrent)
4912 ui.status(_("updating bookmark %s\n") % repo._bookmarkcurrent)
4914 return ret
4913 return ret
4915 if modheads > 1:
4914 if modheads > 1:
4916 currentbranchheads = len(repo.branchheads())
4915 currentbranchheads = len(repo.branchheads())
4917 if currentbranchheads == modheads:
4916 if currentbranchheads == modheads:
4918 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
4917 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
4919 elif currentbranchheads > 1:
4918 elif currentbranchheads > 1:
4920 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
4919 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
4921 "merge)\n"))
4920 "merge)\n"))
4922 else:
4921 else:
4923 ui.status(_("(run 'hg heads' to see heads)\n"))
4922 ui.status(_("(run 'hg heads' to see heads)\n"))
4924 else:
4923 else:
4925 ui.status(_("(run 'hg update' to get a working copy)\n"))
4924 ui.status(_("(run 'hg update' to get a working copy)\n"))
4926
4925
4927 @command('^pull',
4926 @command('^pull',
4928 [('u', 'update', None,
4927 [('u', 'update', None,
4929 _('update to new branch head if changesets were pulled')),
4928 _('update to new branch head if changesets were pulled')),
4930 ('f', 'force', None, _('run even when remote repository is unrelated')),
4929 ('f', 'force', None, _('run even when remote repository is unrelated')),
4931 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4930 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4932 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4931 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4933 ('b', 'branch', [], _('a specific branch you would like to pull'),
4932 ('b', 'branch', [], _('a specific branch you would like to pull'),
4934 _('BRANCH')),
4933 _('BRANCH')),
4935 ] + remoteopts,
4934 ] + remoteopts,
4936 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
4935 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
4937 def pull(ui, repo, source="default", **opts):
4936 def pull(ui, repo, source="default", **opts):
4938 """pull changes from the specified source
4937 """pull changes from the specified source
4939
4938
4940 Pull changes from a remote repository to a local one.
4939 Pull changes from a remote repository to a local one.
4941
4940
4942 This finds all changes from the repository at the specified path
4941 This finds all changes from the repository at the specified path
4943 or URL and adds them to a local repository (the current one unless
4942 or URL and adds them to a local repository (the current one unless
4944 -R is specified). By default, this does not update the copy of the
4943 -R is specified). By default, this does not update the copy of the
4945 project in the working directory.
4944 project in the working directory.
4946
4945
4947 Use :hg:`incoming` if you want to see what would have been added
4946 Use :hg:`incoming` if you want to see what would have been added
4948 by a pull at the time you issued this command. If you then decide
4947 by a pull at the time you issued this command. If you then decide
4949 to add those changes to the repository, you should use :hg:`pull
4948 to add those changes to the repository, you should use :hg:`pull
4950 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
4949 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
4951
4950
4952 If SOURCE is omitted, the 'default' path will be used.
4951 If SOURCE is omitted, the 'default' path will be used.
4953 See :hg:`help urls` for more information.
4952 See :hg:`help urls` for more information.
4954
4953
4955 Returns 0 on success, 1 if an update had unresolved files.
4954 Returns 0 on success, 1 if an update had unresolved files.
4956 """
4955 """
4957 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
4956 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
4958 other = hg.peer(repo, opts, source)
4957 other = hg.peer(repo, opts, source)
4959 try:
4958 try:
4960 ui.status(_('pulling from %s\n') % util.hidepassword(source))
4959 ui.status(_('pulling from %s\n') % util.hidepassword(source))
4961 revs, checkout = hg.addbranchrevs(repo, other, branches,
4960 revs, checkout = hg.addbranchrevs(repo, other, branches,
4962 opts.get('rev'))
4961 opts.get('rev'))
4963
4962
4964 remotebookmarks = other.listkeys('bookmarks')
4963 remotebookmarks = other.listkeys('bookmarks')
4965
4964
4966 if opts.get('bookmark'):
4965 if opts.get('bookmark'):
4967 if not revs:
4966 if not revs:
4968 revs = []
4967 revs = []
4969 for b in opts['bookmark']:
4968 for b in opts['bookmark']:
4970 if b not in remotebookmarks:
4969 if b not in remotebookmarks:
4971 raise util.Abort(_('remote bookmark %s not found!') % b)
4970 raise util.Abort(_('remote bookmark %s not found!') % b)
4972 revs.append(remotebookmarks[b])
4971 revs.append(remotebookmarks[b])
4973
4972
4974 if revs:
4973 if revs:
4975 try:
4974 try:
4976 revs = [other.lookup(rev) for rev in revs]
4975 revs = [other.lookup(rev) for rev in revs]
4977 except error.CapabilityError:
4976 except error.CapabilityError:
4978 err = _("other repository doesn't support revision lookup, "
4977 err = _("other repository doesn't support revision lookup, "
4979 "so a rev cannot be specified.")
4978 "so a rev cannot be specified.")
4980 raise util.Abort(err)
4979 raise util.Abort(err)
4981
4980
4982 modheads = exchange.pull(repo, other, heads=revs,
4981 modheads = exchange.pull(repo, other, heads=revs,
4983 force=opts.get('force'),
4982 force=opts.get('force'),
4984 bookmarks=opts.get('bookmark', ())).cgresult
4983 bookmarks=opts.get('bookmark', ())).cgresult
4985 if checkout:
4984 if checkout:
4986 checkout = str(repo.changelog.rev(other.lookup(checkout)))
4985 checkout = str(repo.changelog.rev(other.lookup(checkout)))
4987 repo._subtoppath = source
4986 repo._subtoppath = source
4988 try:
4987 try:
4989 ret = postincoming(ui, repo, modheads, opts.get('update'), checkout)
4988 ret = postincoming(ui, repo, modheads, opts.get('update'), checkout)
4990
4989
4991 finally:
4990 finally:
4992 del repo._subtoppath
4991 del repo._subtoppath
4993
4992
4994 finally:
4993 finally:
4995 other.close()
4994 other.close()
4996 return ret
4995 return ret
4997
4996
4998 @command('^push',
4997 @command('^push',
4999 [('f', 'force', None, _('force push')),
4998 [('f', 'force', None, _('force push')),
5000 ('r', 'rev', [],
4999 ('r', 'rev', [],
5001 _('a changeset intended to be included in the destination'),
5000 _('a changeset intended to be included in the destination'),
5002 _('REV')),
5001 _('REV')),
5003 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
5002 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
5004 ('b', 'branch', [],
5003 ('b', 'branch', [],
5005 _('a specific branch you would like to push'), _('BRANCH')),
5004 _('a specific branch you would like to push'), _('BRANCH')),
5006 ('', 'new-branch', False, _('allow pushing a new branch')),
5005 ('', 'new-branch', False, _('allow pushing a new branch')),
5007 ] + remoteopts,
5006 ] + remoteopts,
5008 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
5007 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
5009 def push(ui, repo, dest=None, **opts):
5008 def push(ui, repo, dest=None, **opts):
5010 """push changes to the specified destination
5009 """push changes to the specified destination
5011
5010
5012 Push changesets from the local repository to the specified
5011 Push changesets from the local repository to the specified
5013 destination.
5012 destination.
5014
5013
5015 This operation is symmetrical to pull: it is identical to a pull
5014 This operation is symmetrical to pull: it is identical to a pull
5016 in the destination repository from the current one.
5015 in the destination repository from the current one.
5017
5016
5018 By default, push will not allow creation of new heads at the
5017 By default, push will not allow creation of new heads at the
5019 destination, since multiple heads would make it unclear which head
5018 destination, since multiple heads would make it unclear which head
5020 to use. In this situation, it is recommended to pull and merge
5019 to use. In this situation, it is recommended to pull and merge
5021 before pushing.
5020 before pushing.
5022
5021
5023 Use --new-branch if you want to allow push to create a new named
5022 Use --new-branch if you want to allow push to create a new named
5024 branch that is not present at the destination. This allows you to
5023 branch that is not present at the destination. This allows you to
5025 only create a new branch without forcing other changes.
5024 only create a new branch without forcing other changes.
5026
5025
5027 .. note::
5026 .. note::
5028
5027
5029 Extra care should be taken with the -f/--force option,
5028 Extra care should be taken with the -f/--force option,
5030 which will push all new heads on all branches, an action which will
5029 which will push all new heads on all branches, an action which will
5031 almost always cause confusion for collaborators.
5030 almost always cause confusion for collaborators.
5032
5031
5033 If -r/--rev is used, the specified revision and all its ancestors
5032 If -r/--rev is used, the specified revision and all its ancestors
5034 will be pushed to the remote repository.
5033 will be pushed to the remote repository.
5035
5034
5036 If -B/--bookmark is used, the specified bookmarked revision, its
5035 If -B/--bookmark is used, the specified bookmarked revision, its
5037 ancestors, and the bookmark will be pushed to the remote
5036 ancestors, and the bookmark will be pushed to the remote
5038 repository.
5037 repository.
5039
5038
5040 Please see :hg:`help urls` for important details about ``ssh://``
5039 Please see :hg:`help urls` for important details about ``ssh://``
5041 URLs. If DESTINATION is omitted, a default path will be used.
5040 URLs. If DESTINATION is omitted, a default path will be used.
5042
5041
5043 Returns 0 if push was successful, 1 if nothing to push.
5042 Returns 0 if push was successful, 1 if nothing to push.
5044 """
5043 """
5045
5044
5046 if opts.get('bookmark'):
5045 if opts.get('bookmark'):
5047 ui.setconfig('bookmarks', 'pushing', opts['bookmark'], 'push')
5046 ui.setconfig('bookmarks', 'pushing', opts['bookmark'], 'push')
5048 for b in opts['bookmark']:
5047 for b in opts['bookmark']:
5049 # translate -B options to -r so changesets get pushed
5048 # translate -B options to -r so changesets get pushed
5050 if b in repo._bookmarks:
5049 if b in repo._bookmarks:
5051 opts.setdefault('rev', []).append(b)
5050 opts.setdefault('rev', []).append(b)
5052 else:
5051 else:
5053 # if we try to push a deleted bookmark, translate it to null
5052 # if we try to push a deleted bookmark, translate it to null
5054 # this lets simultaneous -r, -b options continue working
5053 # this lets simultaneous -r, -b options continue working
5055 opts.setdefault('rev', []).append("null")
5054 opts.setdefault('rev', []).append("null")
5056
5055
5057 dest = ui.expandpath(dest or 'default-push', dest or 'default')
5056 dest = ui.expandpath(dest or 'default-push', dest or 'default')
5058 dest, branches = hg.parseurl(dest, opts.get('branch'))
5057 dest, branches = hg.parseurl(dest, opts.get('branch'))
5059 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
5058 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
5060 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
5059 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
5061 try:
5060 try:
5062 other = hg.peer(repo, opts, dest)
5061 other = hg.peer(repo, opts, dest)
5063 except error.RepoError:
5062 except error.RepoError:
5064 if dest == "default-push":
5063 if dest == "default-push":
5065 raise util.Abort(_("default repository not configured!"),
5064 raise util.Abort(_("default repository not configured!"),
5066 hint=_('see the "path" section in "hg help config"'))
5065 hint=_('see the "path" section in "hg help config"'))
5067 else:
5066 else:
5068 raise
5067 raise
5069
5068
5070 if revs:
5069 if revs:
5071 revs = [repo.lookup(r) for r in scmutil.revrange(repo, revs)]
5070 revs = [repo.lookup(r) for r in scmutil.revrange(repo, revs)]
5072
5071
5073 repo._subtoppath = dest
5072 repo._subtoppath = dest
5074 try:
5073 try:
5075 # push subrepos depth-first for coherent ordering
5074 # push subrepos depth-first for coherent ordering
5076 c = repo['']
5075 c = repo['']
5077 subs = c.substate # only repos that are committed
5076 subs = c.substate # only repos that are committed
5078 for s in sorted(subs):
5077 for s in sorted(subs):
5079 result = c.sub(s).push(opts)
5078 result = c.sub(s).push(opts)
5080 if result == 0:
5079 if result == 0:
5081 return not result
5080 return not result
5082 finally:
5081 finally:
5083 del repo._subtoppath
5082 del repo._subtoppath
5084 pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
5083 pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
5085 newbranch=opts.get('new_branch'),
5084 newbranch=opts.get('new_branch'),
5086 bookmarks=opts.get('bookmark', ()))
5085 bookmarks=opts.get('bookmark', ()))
5087
5086
5088 result = not pushop.cgresult
5087 result = not pushop.cgresult
5089
5088
5090 if pushop.bkresult is not None:
5089 if pushop.bkresult is not None:
5091 if pushop.bkresult == 2:
5090 if pushop.bkresult == 2:
5092 result = 2
5091 result = 2
5093 elif not result and pushop.bkresult:
5092 elif not result and pushop.bkresult:
5094 result = 2
5093 result = 2
5095
5094
5096 return result
5095 return result
5097
5096
5098 @command('recover', [])
5097 @command('recover', [])
5099 def recover(ui, repo):
5098 def recover(ui, repo):
5100 """roll back an interrupted transaction
5099 """roll back an interrupted transaction
5101
5100
5102 Recover from an interrupted commit or pull.
5101 Recover from an interrupted commit or pull.
5103
5102
5104 This command tries to fix the repository status after an
5103 This command tries to fix the repository status after an
5105 interrupted operation. It should only be necessary when Mercurial
5104 interrupted operation. It should only be necessary when Mercurial
5106 suggests it.
5105 suggests it.
5107
5106
5108 Returns 0 if successful, 1 if nothing to recover or verify fails.
5107 Returns 0 if successful, 1 if nothing to recover or verify fails.
5109 """
5108 """
5110 if repo.recover():
5109 if repo.recover():
5111 return hg.verify(repo)
5110 return hg.verify(repo)
5112 return 1
5111 return 1
5113
5112
5114 @command('^remove|rm',
5113 @command('^remove|rm',
5115 [('A', 'after', None, _('record delete for missing files')),
5114 [('A', 'after', None, _('record delete for missing files')),
5116 ('f', 'force', None,
5115 ('f', 'force', None,
5117 _('remove (and delete) file even if added or modified')),
5116 _('remove (and delete) file even if added or modified')),
5118 ] + subrepoopts + walkopts,
5117 ] + subrepoopts + walkopts,
5119 _('[OPTION]... FILE...'),
5118 _('[OPTION]... FILE...'),
5120 inferrepo=True)
5119 inferrepo=True)
5121 def remove(ui, repo, *pats, **opts):
5120 def remove(ui, repo, *pats, **opts):
5122 """remove the specified files on the next commit
5121 """remove the specified files on the next commit
5123
5122
5124 Schedule the indicated files for removal from the current branch.
5123 Schedule the indicated files for removal from the current branch.
5125
5124
5126 This command schedules the files to be removed at the next commit.
5125 This command schedules the files to be removed at the next commit.
5127 To undo a remove before that, see :hg:`revert`. To undo added
5126 To undo a remove before that, see :hg:`revert`. To undo added
5128 files, see :hg:`forget`.
5127 files, see :hg:`forget`.
5129
5128
5130 .. container:: verbose
5129 .. container:: verbose
5131
5130
5132 -A/--after can be used to remove only files that have already
5131 -A/--after can be used to remove only files that have already
5133 been deleted, -f/--force can be used to force deletion, and -Af
5132 been deleted, -f/--force can be used to force deletion, and -Af
5134 can be used to remove files from the next revision without
5133 can be used to remove files from the next revision without
5135 deleting them from the working directory.
5134 deleting them from the working directory.
5136
5135
5137 The following table details the behavior of remove for different
5136 The following table details the behavior of remove for different
5138 file states (columns) and option combinations (rows). The file
5137 file states (columns) and option combinations (rows). The file
5139 states are Added [A], Clean [C], Modified [M] and Missing [!]
5138 states are Added [A], Clean [C], Modified [M] and Missing [!]
5140 (as reported by :hg:`status`). The actions are Warn, Remove
5139 (as reported by :hg:`status`). The actions are Warn, Remove
5141 (from branch) and Delete (from disk):
5140 (from branch) and Delete (from disk):
5142
5141
5143 ========= == == == ==
5142 ========= == == == ==
5144 opt/state A C M !
5143 opt/state A C M !
5145 ========= == == == ==
5144 ========= == == == ==
5146 none W RD W R
5145 none W RD W R
5147 -f R RD RD R
5146 -f R RD RD R
5148 -A W W W R
5147 -A W W W R
5149 -Af R R R R
5148 -Af R R R R
5150 ========= == == == ==
5149 ========= == == == ==
5151
5150
5152 Note that remove never deletes files in Added [A] state from the
5151 Note that remove never deletes files in Added [A] state from the
5153 working directory, not even if option --force is specified.
5152 working directory, not even if option --force is specified.
5154
5153
5155 Returns 0 on success, 1 if any warnings encountered.
5154 Returns 0 on success, 1 if any warnings encountered.
5156 """
5155 """
5157
5156
5158 after, force = opts.get('after'), opts.get('force')
5157 after, force = opts.get('after'), opts.get('force')
5159 if not pats and not after:
5158 if not pats and not after:
5160 raise util.Abort(_('no files specified'))
5159 raise util.Abort(_('no files specified'))
5161
5160
5162 m = scmutil.match(repo[None], pats, opts)
5161 m = scmutil.match(repo[None], pats, opts)
5163 subrepos = opts.get('subrepos')
5162 subrepos = opts.get('subrepos')
5164 return cmdutil.remove(ui, repo, m, "", after, force, subrepos)
5163 return cmdutil.remove(ui, repo, m, "", after, force, subrepos)
5165
5164
5166 @command('rename|move|mv',
5165 @command('rename|move|mv',
5167 [('A', 'after', None, _('record a rename that has already occurred')),
5166 [('A', 'after', None, _('record a rename that has already occurred')),
5168 ('f', 'force', None, _('forcibly copy over an existing managed file')),
5167 ('f', 'force', None, _('forcibly copy over an existing managed file')),
5169 ] + walkopts + dryrunopts,
5168 ] + walkopts + dryrunopts,
5170 _('[OPTION]... SOURCE... DEST'))
5169 _('[OPTION]... SOURCE... DEST'))
5171 def rename(ui, repo, *pats, **opts):
5170 def rename(ui, repo, *pats, **opts):
5172 """rename files; equivalent of copy + remove
5171 """rename files; equivalent of copy + remove
5173
5172
5174 Mark dest as copies of sources; mark sources for deletion. If dest
5173 Mark dest as copies of sources; mark sources for deletion. If dest
5175 is a directory, copies are put in that directory. If dest is a
5174 is a directory, copies are put in that directory. If dest is a
5176 file, there can only be one source.
5175 file, there can only be one source.
5177
5176
5178 By default, this command copies the contents of files as they
5177 By default, this command copies the contents of files as they
5179 exist in the working directory. If invoked with -A/--after, the
5178 exist in the working directory. If invoked with -A/--after, the
5180 operation is recorded, but no copying is performed.
5179 operation is recorded, but no copying is performed.
5181
5180
5182 This command takes effect at the next commit. To undo a rename
5181 This command takes effect at the next commit. To undo a rename
5183 before that, see :hg:`revert`.
5182 before that, see :hg:`revert`.
5184
5183
5185 Returns 0 on success, 1 if errors are encountered.
5184 Returns 0 on success, 1 if errors are encountered.
5186 """
5185 """
5187 wlock = repo.wlock(False)
5186 wlock = repo.wlock(False)
5188 try:
5187 try:
5189 return cmdutil.copy(ui, repo, pats, opts, rename=True)
5188 return cmdutil.copy(ui, repo, pats, opts, rename=True)
5190 finally:
5189 finally:
5191 wlock.release()
5190 wlock.release()
5192
5191
5193 @command('resolve',
5192 @command('resolve',
5194 [('a', 'all', None, _('select all unresolved files')),
5193 [('a', 'all', None, _('select all unresolved files')),
5195 ('l', 'list', None, _('list state of files needing merge')),
5194 ('l', 'list', None, _('list state of files needing merge')),
5196 ('m', 'mark', None, _('mark files as resolved')),
5195 ('m', 'mark', None, _('mark files as resolved')),
5197 ('u', 'unmark', None, _('mark files as unresolved')),
5196 ('u', 'unmark', None, _('mark files as unresolved')),
5198 ('n', 'no-status', None, _('hide status prefix'))]
5197 ('n', 'no-status', None, _('hide status prefix'))]
5199 + mergetoolopts + walkopts,
5198 + mergetoolopts + walkopts,
5200 _('[OPTION]... [FILE]...'),
5199 _('[OPTION]... [FILE]...'),
5201 inferrepo=True)
5200 inferrepo=True)
5202 def resolve(ui, repo, *pats, **opts):
5201 def resolve(ui, repo, *pats, **opts):
5203 """redo merges or set/view the merge status of files
5202 """redo merges or set/view the merge status of files
5204
5203
5205 Merges with unresolved conflicts are often the result of
5204 Merges with unresolved conflicts are often the result of
5206 non-interactive merging using the ``internal:merge`` configuration
5205 non-interactive merging using the ``internal:merge`` configuration
5207 setting, or a command-line merge tool like ``diff3``. The resolve
5206 setting, or a command-line merge tool like ``diff3``. The resolve
5208 command is used to manage the files involved in a merge, after
5207 command is used to manage the files involved in a merge, after
5209 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
5208 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
5210 working directory must have two parents). See :hg:`help
5209 working directory must have two parents). See :hg:`help
5211 merge-tools` for information on configuring merge tools.
5210 merge-tools` for information on configuring merge tools.
5212
5211
5213 The resolve command can be used in the following ways:
5212 The resolve command can be used in the following ways:
5214
5213
5215 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
5214 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
5216 files, discarding any previous merge attempts. Re-merging is not
5215 files, discarding any previous merge attempts. Re-merging is not
5217 performed for files already marked as resolved. Use ``--all/-a``
5216 performed for files already marked as resolved. Use ``--all/-a``
5218 to select all unresolved files. ``--tool`` can be used to specify
5217 to select all unresolved files. ``--tool`` can be used to specify
5219 the merge tool used for the given files. It overrides the HGMERGE
5218 the merge tool used for the given files. It overrides the HGMERGE
5220 environment variable and your configuration files. Previous file
5219 environment variable and your configuration files. Previous file
5221 contents are saved with a ``.orig`` suffix.
5220 contents are saved with a ``.orig`` suffix.
5222
5221
5223 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
5222 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
5224 (e.g. after having manually fixed-up the files). The default is
5223 (e.g. after having manually fixed-up the files). The default is
5225 to mark all unresolved files.
5224 to mark all unresolved files.
5226
5225
5227 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
5226 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
5228 default is to mark all resolved files.
5227 default is to mark all resolved files.
5229
5228
5230 - :hg:`resolve -l`: list files which had or still have conflicts.
5229 - :hg:`resolve -l`: list files which had or still have conflicts.
5231 In the printed list, ``U`` = unresolved and ``R`` = resolved.
5230 In the printed list, ``U`` = unresolved and ``R`` = resolved.
5232
5231
5233 Note that Mercurial will not let you commit files with unresolved
5232 Note that Mercurial will not let you commit files with unresolved
5234 merge conflicts. You must use :hg:`resolve -m ...` before you can
5233 merge conflicts. You must use :hg:`resolve -m ...` before you can
5235 commit after a conflicting merge.
5234 commit after a conflicting merge.
5236
5235
5237 Returns 0 on success, 1 if any files fail a resolve attempt.
5236 Returns 0 on success, 1 if any files fail a resolve attempt.
5238 """
5237 """
5239
5238
5240 all, mark, unmark, show, nostatus = \
5239 all, mark, unmark, show, nostatus = \
5241 [opts.get(o) for o in 'all mark unmark list no_status'.split()]
5240 [opts.get(o) for o in 'all mark unmark list no_status'.split()]
5242
5241
5243 if (show and (mark or unmark)) or (mark and unmark):
5242 if (show and (mark or unmark)) or (mark and unmark):
5244 raise util.Abort(_("too many options specified"))
5243 raise util.Abort(_("too many options specified"))
5245 if pats and all:
5244 if pats and all:
5246 raise util.Abort(_("can't specify --all and patterns"))
5245 raise util.Abort(_("can't specify --all and patterns"))
5247 if not (all or pats or show or mark or unmark):
5246 if not (all or pats or show or mark or unmark):
5248 raise util.Abort(_('no files or directories specified'),
5247 raise util.Abort(_('no files or directories specified'),
5249 hint=('use --all to remerge all files'))
5248 hint=('use --all to remerge all files'))
5250
5249
5251 wlock = repo.wlock()
5250 wlock = repo.wlock()
5252 try:
5251 try:
5253 ms = mergemod.mergestate(repo)
5252 ms = mergemod.mergestate(repo)
5254
5253
5255 if not (ms.active() or repo.dirstate.p2() != nullid) and not show:
5254 if not (ms.active() or repo.dirstate.p2() != nullid) and not show:
5256 raise util.Abort(
5255 raise util.Abort(
5257 _('resolve command not applicable when not merging'))
5256 _('resolve command not applicable when not merging'))
5258
5257
5259 m = scmutil.match(repo[None], pats, opts)
5258 m = scmutil.match(repo[None], pats, opts)
5260 ret = 0
5259 ret = 0
5261 didwork = False
5260 didwork = False
5262
5261
5263 for f in ms:
5262 for f in ms:
5264 if not m(f):
5263 if not m(f):
5265 continue
5264 continue
5266
5265
5267 didwork = True
5266 didwork = True
5268
5267
5269 if show:
5268 if show:
5270 if nostatus:
5269 if nostatus:
5271 ui.write("%s\n" % f)
5270 ui.write("%s\n" % f)
5272 else:
5271 else:
5273 ui.write("%s %s\n" % (ms[f].upper(), f),
5272 ui.write("%s %s\n" % (ms[f].upper(), f),
5274 label='resolve.' +
5273 label='resolve.' +
5275 {'u': 'unresolved', 'r': 'resolved'}[ms[f]])
5274 {'u': 'unresolved', 'r': 'resolved'}[ms[f]])
5276 elif mark:
5275 elif mark:
5277 ms.mark(f, "r")
5276 ms.mark(f, "r")
5278 elif unmark:
5277 elif unmark:
5279 ms.mark(f, "u")
5278 ms.mark(f, "u")
5280 else:
5279 else:
5281 wctx = repo[None]
5280 wctx = repo[None]
5282
5281
5283 # backup pre-resolve (merge uses .orig for its own purposes)
5282 # backup pre-resolve (merge uses .orig for its own purposes)
5284 a = repo.wjoin(f)
5283 a = repo.wjoin(f)
5285 util.copyfile(a, a + ".resolve")
5284 util.copyfile(a, a + ".resolve")
5286
5285
5287 try:
5286 try:
5288 # resolve file
5287 # resolve file
5289 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
5288 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
5290 'resolve')
5289 'resolve')
5291 if ms.resolve(f, wctx):
5290 if ms.resolve(f, wctx):
5292 ret = 1
5291 ret = 1
5293 finally:
5292 finally:
5294 ui.setconfig('ui', 'forcemerge', '', 'resolve')
5293 ui.setconfig('ui', 'forcemerge', '', 'resolve')
5295 ms.commit()
5294 ms.commit()
5296
5295
5297 # replace filemerge's .orig file with our resolve file
5296 # replace filemerge's .orig file with our resolve file
5298 util.rename(a + ".resolve", a + ".orig")
5297 util.rename(a + ".resolve", a + ".orig")
5299
5298
5300 ms.commit()
5299 ms.commit()
5301
5300
5302 if not didwork and pats:
5301 if not didwork and pats:
5303 ui.warn(_("arguments do not match paths that need resolving\n"))
5302 ui.warn(_("arguments do not match paths that need resolving\n"))
5304
5303
5305 finally:
5304 finally:
5306 wlock.release()
5305 wlock.release()
5307
5306
5308 # Nudge users into finishing an unfinished operation. We don't print
5307 # Nudge users into finishing an unfinished operation. We don't print
5309 # this with the list/show operation because we want list/show to remain
5308 # this with the list/show operation because we want list/show to remain
5310 # machine readable.
5309 # machine readable.
5311 if not list(ms.unresolved()) and not show:
5310 if not list(ms.unresolved()) and not show:
5312 ui.status(_('(no more unresolved files)\n'))
5311 ui.status(_('(no more unresolved files)\n'))
5313
5312
5314 return ret
5313 return ret
5315
5314
5316 @command('revert',
5315 @command('revert',
5317 [('a', 'all', None, _('revert all changes when no arguments given')),
5316 [('a', 'all', None, _('revert all changes when no arguments given')),
5318 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5317 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5319 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
5318 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
5320 ('C', 'no-backup', None, _('do not save backup copies of files')),
5319 ('C', 'no-backup', None, _('do not save backup copies of files')),
5321 ] + walkopts + dryrunopts,
5320 ] + walkopts + dryrunopts,
5322 _('[OPTION]... [-r REV] [NAME]...'))
5321 _('[OPTION]... [-r REV] [NAME]...'))
5323 def revert(ui, repo, *pats, **opts):
5322 def revert(ui, repo, *pats, **opts):
5324 """restore files to their checkout state
5323 """restore files to their checkout state
5325
5324
5326 .. note::
5325 .. note::
5327
5326
5328 To check out earlier revisions, you should use :hg:`update REV`.
5327 To check out earlier revisions, you should use :hg:`update REV`.
5329 To cancel an uncommitted merge (and lose your changes),
5328 To cancel an uncommitted merge (and lose your changes),
5330 use :hg:`update --clean .`.
5329 use :hg:`update --clean .`.
5331
5330
5332 With no revision specified, revert the specified files or directories
5331 With no revision specified, revert the specified files or directories
5333 to the contents they had in the parent of the working directory.
5332 to the contents they had in the parent of the working directory.
5334 This restores the contents of files to an unmodified
5333 This restores the contents of files to an unmodified
5335 state and unschedules adds, removes, copies, and renames. If the
5334 state and unschedules adds, removes, copies, and renames. If the
5336 working directory has two parents, you must explicitly specify a
5335 working directory has two parents, you must explicitly specify a
5337 revision.
5336 revision.
5338
5337
5339 Using the -r/--rev or -d/--date options, revert the given files or
5338 Using the -r/--rev or -d/--date options, revert the given files or
5340 directories to their states as of a specific revision. Because
5339 directories to their states as of a specific revision. Because
5341 revert does not change the working directory parents, this will
5340 revert does not change the working directory parents, this will
5342 cause these files to appear modified. This can be helpful to "back
5341 cause these files to appear modified. This can be helpful to "back
5343 out" some or all of an earlier change. See :hg:`backout` for a
5342 out" some or all of an earlier change. See :hg:`backout` for a
5344 related method.
5343 related method.
5345
5344
5346 Modified files are saved with a .orig suffix before reverting.
5345 Modified files are saved with a .orig suffix before reverting.
5347 To disable these backups, use --no-backup.
5346 To disable these backups, use --no-backup.
5348
5347
5349 See :hg:`help dates` for a list of formats valid for -d/--date.
5348 See :hg:`help dates` for a list of formats valid for -d/--date.
5350
5349
5351 Returns 0 on success.
5350 Returns 0 on success.
5352 """
5351 """
5353
5352
5354 if opts.get("date"):
5353 if opts.get("date"):
5355 if opts.get("rev"):
5354 if opts.get("rev"):
5356 raise util.Abort(_("you can't specify a revision and a date"))
5355 raise util.Abort(_("you can't specify a revision and a date"))
5357 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
5356 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
5358
5357
5359 parent, p2 = repo.dirstate.parents()
5358 parent, p2 = repo.dirstate.parents()
5360 if not opts.get('rev') and p2 != nullid:
5359 if not opts.get('rev') and p2 != nullid:
5361 # revert after merge is a trap for new users (issue2915)
5360 # revert after merge is a trap for new users (issue2915)
5362 raise util.Abort(_('uncommitted merge with no revision specified'),
5361 raise util.Abort(_('uncommitted merge with no revision specified'),
5363 hint=_('use "hg update" or see "hg help revert"'))
5362 hint=_('use "hg update" or see "hg help revert"'))
5364
5363
5365 ctx = scmutil.revsingle(repo, opts.get('rev'))
5364 ctx = scmutil.revsingle(repo, opts.get('rev'))
5366
5365
5367 if not pats and not opts.get('all'):
5366 if not pats and not opts.get('all'):
5368 msg = _("no files or directories specified")
5367 msg = _("no files or directories specified")
5369 if p2 != nullid:
5368 if p2 != nullid:
5370 hint = _("uncommitted merge, use --all to discard all changes,"
5369 hint = _("uncommitted merge, use --all to discard all changes,"
5371 " or 'hg update -C .' to abort the merge")
5370 " or 'hg update -C .' to abort the merge")
5372 raise util.Abort(msg, hint=hint)
5371 raise util.Abort(msg, hint=hint)
5373 dirty = util.any(repo.status())
5372 dirty = util.any(repo.status())
5374 node = ctx.node()
5373 node = ctx.node()
5375 if node != parent:
5374 if node != parent:
5376 if dirty:
5375 if dirty:
5377 hint = _("uncommitted changes, use --all to discard all"
5376 hint = _("uncommitted changes, use --all to discard all"
5378 " changes, or 'hg update %s' to update") % ctx.rev()
5377 " changes, or 'hg update %s' to update") % ctx.rev()
5379 else:
5378 else:
5380 hint = _("use --all to revert all files,"
5379 hint = _("use --all to revert all files,"
5381 " or 'hg update %s' to update") % ctx.rev()
5380 " or 'hg update %s' to update") % ctx.rev()
5382 elif dirty:
5381 elif dirty:
5383 hint = _("uncommitted changes, use --all to discard all changes")
5382 hint = _("uncommitted changes, use --all to discard all changes")
5384 else:
5383 else:
5385 hint = _("use --all to revert all files")
5384 hint = _("use --all to revert all files")
5386 raise util.Abort(msg, hint=hint)
5385 raise util.Abort(msg, hint=hint)
5387
5386
5388 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats, **opts)
5387 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats, **opts)
5389
5388
5390 @command('rollback', dryrunopts +
5389 @command('rollback', dryrunopts +
5391 [('f', 'force', False, _('ignore safety measures'))])
5390 [('f', 'force', False, _('ignore safety measures'))])
5392 def rollback(ui, repo, **opts):
5391 def rollback(ui, repo, **opts):
5393 """roll back the last transaction (DANGEROUS) (DEPRECATED)
5392 """roll back the last transaction (DANGEROUS) (DEPRECATED)
5394
5393
5395 Please use :hg:`commit --amend` instead of rollback to correct
5394 Please use :hg:`commit --amend` instead of rollback to correct
5396 mistakes in the last commit.
5395 mistakes in the last commit.
5397
5396
5398 This command should be used with care. There is only one level of
5397 This command should be used with care. There is only one level of
5399 rollback, and there is no way to undo a rollback. It will also
5398 rollback, and there is no way to undo a rollback. It will also
5400 restore the dirstate at the time of the last transaction, losing
5399 restore the dirstate at the time of the last transaction, losing
5401 any dirstate changes since that time. This command does not alter
5400 any dirstate changes since that time. This command does not alter
5402 the working directory.
5401 the working directory.
5403
5402
5404 Transactions are used to encapsulate the effects of all commands
5403 Transactions are used to encapsulate the effects of all commands
5405 that create new changesets or propagate existing changesets into a
5404 that create new changesets or propagate existing changesets into a
5406 repository.
5405 repository.
5407
5406
5408 .. container:: verbose
5407 .. container:: verbose
5409
5408
5410 For example, the following commands are transactional, and their
5409 For example, the following commands are transactional, and their
5411 effects can be rolled back:
5410 effects can be rolled back:
5412
5411
5413 - commit
5412 - commit
5414 - import
5413 - import
5415 - pull
5414 - pull
5416 - push (with this repository as the destination)
5415 - push (with this repository as the destination)
5417 - unbundle
5416 - unbundle
5418
5417
5419 To avoid permanent data loss, rollback will refuse to rollback a
5418 To avoid permanent data loss, rollback will refuse to rollback a
5420 commit transaction if it isn't checked out. Use --force to
5419 commit transaction if it isn't checked out. Use --force to
5421 override this protection.
5420 override this protection.
5422
5421
5423 This command is not intended for use on public repositories. Once
5422 This command is not intended for use on public repositories. Once
5424 changes are visible for pull by other users, rolling a transaction
5423 changes are visible for pull by other users, rolling a transaction
5425 back locally is ineffective (someone else may already have pulled
5424 back locally is ineffective (someone else may already have pulled
5426 the changes). Furthermore, a race is possible with readers of the
5425 the changes). Furthermore, a race is possible with readers of the
5427 repository; for example an in-progress pull from the repository
5426 repository; for example an in-progress pull from the repository
5428 may fail if a rollback is performed.
5427 may fail if a rollback is performed.
5429
5428
5430 Returns 0 on success, 1 if no rollback data is available.
5429 Returns 0 on success, 1 if no rollback data is available.
5431 """
5430 """
5432 return repo.rollback(dryrun=opts.get('dry_run'),
5431 return repo.rollback(dryrun=opts.get('dry_run'),
5433 force=opts.get('force'))
5432 force=opts.get('force'))
5434
5433
5435 @command('root', [])
5434 @command('root', [])
5436 def root(ui, repo):
5435 def root(ui, repo):
5437 """print the root (top) of the current working directory
5436 """print the root (top) of the current working directory
5438
5437
5439 Print the root directory of the current repository.
5438 Print the root directory of the current repository.
5440
5439
5441 Returns 0 on success.
5440 Returns 0 on success.
5442 """
5441 """
5443 ui.write(repo.root + "\n")
5442 ui.write(repo.root + "\n")
5444
5443
5445 @command('^serve',
5444 @command('^serve',
5446 [('A', 'accesslog', '', _('name of access log file to write to'),
5445 [('A', 'accesslog', '', _('name of access log file to write to'),
5447 _('FILE')),
5446 _('FILE')),
5448 ('d', 'daemon', None, _('run server in background')),
5447 ('d', 'daemon', None, _('run server in background')),
5449 ('', 'daemon-pipefds', '', _('used internally by daemon mode'), _('FILE')),
5448 ('', 'daemon-pipefds', '', _('used internally by daemon mode'), _('FILE')),
5450 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
5449 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
5451 # use string type, then we can check if something was passed
5450 # use string type, then we can check if something was passed
5452 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
5451 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
5453 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
5452 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
5454 _('ADDR')),
5453 _('ADDR')),
5455 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
5454 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
5456 _('PREFIX')),
5455 _('PREFIX')),
5457 ('n', 'name', '',
5456 ('n', 'name', '',
5458 _('name to show in web pages (default: working directory)'), _('NAME')),
5457 _('name to show in web pages (default: working directory)'), _('NAME')),
5459 ('', 'web-conf', '',
5458 ('', 'web-conf', '',
5460 _('name of the hgweb config file (see "hg help hgweb")'), _('FILE')),
5459 _('name of the hgweb config file (see "hg help hgweb")'), _('FILE')),
5461 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
5460 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
5462 _('FILE')),
5461 _('FILE')),
5463 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
5462 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
5464 ('', 'stdio', None, _('for remote clients')),
5463 ('', 'stdio', None, _('for remote clients')),
5465 ('', 'cmdserver', '', _('for remote clients'), _('MODE')),
5464 ('', 'cmdserver', '', _('for remote clients'), _('MODE')),
5466 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
5465 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
5467 ('', 'style', '', _('template style to use'), _('STYLE')),
5466 ('', 'style', '', _('template style to use'), _('STYLE')),
5468 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
5467 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
5469 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))],
5468 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))],
5470 _('[OPTION]...'),
5469 _('[OPTION]...'),
5471 optionalrepo=True)
5470 optionalrepo=True)
5472 def serve(ui, repo, **opts):
5471 def serve(ui, repo, **opts):
5473 """start stand-alone webserver
5472 """start stand-alone webserver
5474
5473
5475 Start a local HTTP repository browser and pull server. You can use
5474 Start a local HTTP repository browser and pull server. You can use
5476 this for ad-hoc sharing and browsing of repositories. It is
5475 this for ad-hoc sharing and browsing of repositories. It is
5477 recommended to use a real web server to serve a repository for
5476 recommended to use a real web server to serve a repository for
5478 longer periods of time.
5477 longer periods of time.
5479
5478
5480 Please note that the server does not implement access control.
5479 Please note that the server does not implement access control.
5481 This means that, by default, anybody can read from the server and
5480 This means that, by default, anybody can read from the server and
5482 nobody can write to it by default. Set the ``web.allow_push``
5481 nobody can write to it by default. Set the ``web.allow_push``
5483 option to ``*`` to allow everybody to push to the server. You
5482 option to ``*`` to allow everybody to push to the server. You
5484 should use a real web server if you need to authenticate users.
5483 should use a real web server if you need to authenticate users.
5485
5484
5486 By default, the server logs accesses to stdout and errors to
5485 By default, the server logs accesses to stdout and errors to
5487 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
5486 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
5488 files.
5487 files.
5489
5488
5490 To have the server choose a free port number to listen on, specify
5489 To have the server choose a free port number to listen on, specify
5491 a port number of 0; in this case, the server will print the port
5490 a port number of 0; in this case, the server will print the port
5492 number it uses.
5491 number it uses.
5493
5492
5494 Returns 0 on success.
5493 Returns 0 on success.
5495 """
5494 """
5496
5495
5497 if opts["stdio"] and opts["cmdserver"]:
5496 if opts["stdio"] and opts["cmdserver"]:
5498 raise util.Abort(_("cannot use --stdio with --cmdserver"))
5497 raise util.Abort(_("cannot use --stdio with --cmdserver"))
5499
5498
5500 if opts["stdio"]:
5499 if opts["stdio"]:
5501 if repo is None:
5500 if repo is None:
5502 raise error.RepoError(_("there is no Mercurial repository here"
5501 raise error.RepoError(_("there is no Mercurial repository here"
5503 " (.hg not found)"))
5502 " (.hg not found)"))
5504 s = sshserver.sshserver(ui, repo)
5503 s = sshserver.sshserver(ui, repo)
5505 s.serve_forever()
5504 s.serve_forever()
5506
5505
5507 if opts["cmdserver"]:
5506 if opts["cmdserver"]:
5508 service = commandserver.createservice(ui, repo, opts)
5507 service = commandserver.createservice(ui, repo, opts)
5509 return cmdutil.service(opts, initfn=service.init, runfn=service.run)
5508 return cmdutil.service(opts, initfn=service.init, runfn=service.run)
5510
5509
5511 # this way we can check if something was given in the command-line
5510 # this way we can check if something was given in the command-line
5512 if opts.get('port'):
5511 if opts.get('port'):
5513 opts['port'] = util.getport(opts.get('port'))
5512 opts['port'] = util.getport(opts.get('port'))
5514
5513
5515 baseui = repo and repo.baseui or ui
5514 baseui = repo and repo.baseui or ui
5516 optlist = ("name templates style address port prefix ipv6"
5515 optlist = ("name templates style address port prefix ipv6"
5517 " accesslog errorlog certificate encoding")
5516 " accesslog errorlog certificate encoding")
5518 for o in optlist.split():
5517 for o in optlist.split():
5519 val = opts.get(o, '')
5518 val = opts.get(o, '')
5520 if val in (None, ''): # should check against default options instead
5519 if val in (None, ''): # should check against default options instead
5521 continue
5520 continue
5522 baseui.setconfig("web", o, val, 'serve')
5521 baseui.setconfig("web", o, val, 'serve')
5523 if repo and repo.ui != baseui:
5522 if repo and repo.ui != baseui:
5524 repo.ui.setconfig("web", o, val, 'serve')
5523 repo.ui.setconfig("web", o, val, 'serve')
5525
5524
5526 o = opts.get('web_conf') or opts.get('webdir_conf')
5525 o = opts.get('web_conf') or opts.get('webdir_conf')
5527 if not o:
5526 if not o:
5528 if not repo:
5527 if not repo:
5529 raise error.RepoError(_("there is no Mercurial repository"
5528 raise error.RepoError(_("there is no Mercurial repository"
5530 " here (.hg not found)"))
5529 " here (.hg not found)"))
5531 o = repo
5530 o = repo
5532
5531
5533 app = hgweb.hgweb(o, baseui=baseui)
5532 app = hgweb.hgweb(o, baseui=baseui)
5534 service = httpservice(ui, app, opts)
5533 service = httpservice(ui, app, opts)
5535 cmdutil.service(opts, initfn=service.init, runfn=service.run)
5534 cmdutil.service(opts, initfn=service.init, runfn=service.run)
5536
5535
5537 class httpservice(object):
5536 class httpservice(object):
5538 def __init__(self, ui, app, opts):
5537 def __init__(self, ui, app, opts):
5539 self.ui = ui
5538 self.ui = ui
5540 self.app = app
5539 self.app = app
5541 self.opts = opts
5540 self.opts = opts
5542
5541
5543 def init(self):
5542 def init(self):
5544 util.setsignalhandler()
5543 util.setsignalhandler()
5545 self.httpd = hgweb_server.create_server(self.ui, self.app)
5544 self.httpd = hgweb_server.create_server(self.ui, self.app)
5546
5545
5547 if self.opts['port'] and not self.ui.verbose:
5546 if self.opts['port'] and not self.ui.verbose:
5548 return
5547 return
5549
5548
5550 if self.httpd.prefix:
5549 if self.httpd.prefix:
5551 prefix = self.httpd.prefix.strip('/') + '/'
5550 prefix = self.httpd.prefix.strip('/') + '/'
5552 else:
5551 else:
5553 prefix = ''
5552 prefix = ''
5554
5553
5555 port = ':%d' % self.httpd.port
5554 port = ':%d' % self.httpd.port
5556 if port == ':80':
5555 if port == ':80':
5557 port = ''
5556 port = ''
5558
5557
5559 bindaddr = self.httpd.addr
5558 bindaddr = self.httpd.addr
5560 if bindaddr == '0.0.0.0':
5559 if bindaddr == '0.0.0.0':
5561 bindaddr = '*'
5560 bindaddr = '*'
5562 elif ':' in bindaddr: # IPv6
5561 elif ':' in bindaddr: # IPv6
5563 bindaddr = '[%s]' % bindaddr
5562 bindaddr = '[%s]' % bindaddr
5564
5563
5565 fqaddr = self.httpd.fqaddr
5564 fqaddr = self.httpd.fqaddr
5566 if ':' in fqaddr:
5565 if ':' in fqaddr:
5567 fqaddr = '[%s]' % fqaddr
5566 fqaddr = '[%s]' % fqaddr
5568 if self.opts['port']:
5567 if self.opts['port']:
5569 write = self.ui.status
5568 write = self.ui.status
5570 else:
5569 else:
5571 write = self.ui.write
5570 write = self.ui.write
5572 write(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
5571 write(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
5573 (fqaddr, port, prefix, bindaddr, self.httpd.port))
5572 (fqaddr, port, prefix, bindaddr, self.httpd.port))
5574 self.ui.flush() # avoid buffering of status message
5573 self.ui.flush() # avoid buffering of status message
5575
5574
5576 def run(self):
5575 def run(self):
5577 self.httpd.serve_forever()
5576 self.httpd.serve_forever()
5578
5577
5579
5578
5580 @command('^status|st',
5579 @command('^status|st',
5581 [('A', 'all', None, _('show status of all files')),
5580 [('A', 'all', None, _('show status of all files')),
5582 ('m', 'modified', None, _('show only modified files')),
5581 ('m', 'modified', None, _('show only modified files')),
5583 ('a', 'added', None, _('show only added files')),
5582 ('a', 'added', None, _('show only added files')),
5584 ('r', 'removed', None, _('show only removed files')),
5583 ('r', 'removed', None, _('show only removed files')),
5585 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
5584 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
5586 ('c', 'clean', None, _('show only files without changes')),
5585 ('c', 'clean', None, _('show only files without changes')),
5587 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
5586 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
5588 ('i', 'ignored', None, _('show only ignored files')),
5587 ('i', 'ignored', None, _('show only ignored files')),
5589 ('n', 'no-status', None, _('hide status prefix')),
5588 ('n', 'no-status', None, _('hide status prefix')),
5590 ('C', 'copies', None, _('show source of copied files')),
5589 ('C', 'copies', None, _('show source of copied files')),
5591 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
5590 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
5592 ('', 'rev', [], _('show difference from revision'), _('REV')),
5591 ('', 'rev', [], _('show difference from revision'), _('REV')),
5593 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
5592 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
5594 ] + walkopts + subrepoopts + formatteropts,
5593 ] + walkopts + subrepoopts + formatteropts,
5595 _('[OPTION]... [FILE]...'),
5594 _('[OPTION]... [FILE]...'),
5596 inferrepo=True)
5595 inferrepo=True)
5597 def status(ui, repo, *pats, **opts):
5596 def status(ui, repo, *pats, **opts):
5598 """show changed files in the working directory
5597 """show changed files in the working directory
5599
5598
5600 Show status of files in the repository. If names are given, only
5599 Show status of files in the repository. If names are given, only
5601 files that match are shown. Files that are clean or ignored or
5600 files that match are shown. Files that are clean or ignored or
5602 the source of a copy/move operation, are not listed unless
5601 the source of a copy/move operation, are not listed unless
5603 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
5602 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
5604 Unless options described with "show only ..." are given, the
5603 Unless options described with "show only ..." are given, the
5605 options -mardu are used.
5604 options -mardu are used.
5606
5605
5607 Option -q/--quiet hides untracked (unknown and ignored) files
5606 Option -q/--quiet hides untracked (unknown and ignored) files
5608 unless explicitly requested with -u/--unknown or -i/--ignored.
5607 unless explicitly requested with -u/--unknown or -i/--ignored.
5609
5608
5610 .. note::
5609 .. note::
5611
5610
5612 status may appear to disagree with diff if permissions have
5611 status may appear to disagree with diff if permissions have
5613 changed or a merge has occurred. The standard diff format does
5612 changed or a merge has occurred. The standard diff format does
5614 not report permission changes and diff only reports changes
5613 not report permission changes and diff only reports changes
5615 relative to one merge parent.
5614 relative to one merge parent.
5616
5615
5617 If one revision is given, it is used as the base revision.
5616 If one revision is given, it is used as the base revision.
5618 If two revisions are given, the differences between them are
5617 If two revisions are given, the differences between them are
5619 shown. The --change option can also be used as a shortcut to list
5618 shown. The --change option can also be used as a shortcut to list
5620 the changed files of a revision from its first parent.
5619 the changed files of a revision from its first parent.
5621
5620
5622 The codes used to show the status of files are::
5621 The codes used to show the status of files are::
5623
5622
5624 M = modified
5623 M = modified
5625 A = added
5624 A = added
5626 R = removed
5625 R = removed
5627 C = clean
5626 C = clean
5628 ! = missing (deleted by non-hg command, but still tracked)
5627 ! = missing (deleted by non-hg command, but still tracked)
5629 ? = not tracked
5628 ? = not tracked
5630 I = ignored
5629 I = ignored
5631 = origin of the previous file (with --copies)
5630 = origin of the previous file (with --copies)
5632
5631
5633 .. container:: verbose
5632 .. container:: verbose
5634
5633
5635 Examples:
5634 Examples:
5636
5635
5637 - show changes in the working directory relative to a
5636 - show changes in the working directory relative to a
5638 changeset::
5637 changeset::
5639
5638
5640 hg status --rev 9353
5639 hg status --rev 9353
5641
5640
5642 - show all changes including copies in an existing changeset::
5641 - show all changes including copies in an existing changeset::
5643
5642
5644 hg status --copies --change 9353
5643 hg status --copies --change 9353
5645
5644
5646 - get a NUL separated list of added files, suitable for xargs::
5645 - get a NUL separated list of added files, suitable for xargs::
5647
5646
5648 hg status -an0
5647 hg status -an0
5649
5648
5650 Returns 0 on success.
5649 Returns 0 on success.
5651 """
5650 """
5652
5651
5653 revs = opts.get('rev')
5652 revs = opts.get('rev')
5654 change = opts.get('change')
5653 change = opts.get('change')
5655
5654
5656 if revs and change:
5655 if revs and change:
5657 msg = _('cannot specify --rev and --change at the same time')
5656 msg = _('cannot specify --rev and --change at the same time')
5658 raise util.Abort(msg)
5657 raise util.Abort(msg)
5659 elif change:
5658 elif change:
5660 node2 = scmutil.revsingle(repo, change, None).node()
5659 node2 = scmutil.revsingle(repo, change, None).node()
5661 node1 = repo[node2].p1().node()
5660 node1 = repo[node2].p1().node()
5662 else:
5661 else:
5663 node1, node2 = scmutil.revpair(repo, revs)
5662 node1, node2 = scmutil.revpair(repo, revs)
5664
5663
5665 cwd = (pats and repo.getcwd()) or ''
5664 cwd = (pats and repo.getcwd()) or ''
5666 end = opts.get('print0') and '\0' or '\n'
5665 end = opts.get('print0') and '\0' or '\n'
5667 copy = {}
5666 copy = {}
5668 states = 'modified added removed deleted unknown ignored clean'.split()
5667 states = 'modified added removed deleted unknown ignored clean'.split()
5669 show = [k for k in states if opts.get(k)]
5668 show = [k for k in states if opts.get(k)]
5670 if opts.get('all'):
5669 if opts.get('all'):
5671 show += ui.quiet and (states[:4] + ['clean']) or states
5670 show += ui.quiet and (states[:4] + ['clean']) or states
5672 if not show:
5671 if not show:
5673 show = ui.quiet and states[:4] or states[:5]
5672 show = ui.quiet and states[:4] or states[:5]
5674
5673
5675 stat = repo.status(node1, node2, scmutil.match(repo[node2], pats, opts),
5674 stat = repo.status(node1, node2, scmutil.match(repo[node2], pats, opts),
5676 'ignored' in show, 'clean' in show, 'unknown' in show,
5675 'ignored' in show, 'clean' in show, 'unknown' in show,
5677 opts.get('subrepos'))
5676 opts.get('subrepos'))
5678 changestates = zip(states, 'MAR!?IC', stat)
5677 changestates = zip(states, 'MAR!?IC', stat)
5679
5678
5680 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
5679 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
5681 copy = copies.pathcopies(repo[node1], repo[node2])
5680 copy = copies.pathcopies(repo[node1], repo[node2])
5682
5681
5683 fm = ui.formatter('status', opts)
5682 fm = ui.formatter('status', opts)
5684 fmt = '%s' + end
5683 fmt = '%s' + end
5685 showchar = not opts.get('no_status')
5684 showchar = not opts.get('no_status')
5686
5685
5687 for state, char, files in changestates:
5686 for state, char, files in changestates:
5688 if state in show:
5687 if state in show:
5689 label = 'status.' + state
5688 label = 'status.' + state
5690 for f in files:
5689 for f in files:
5691 fm.startitem()
5690 fm.startitem()
5692 fm.condwrite(showchar, 'status', '%s ', char, label=label)
5691 fm.condwrite(showchar, 'status', '%s ', char, label=label)
5693 fm.write('path', fmt, repo.pathto(f, cwd), label=label)
5692 fm.write('path', fmt, repo.pathto(f, cwd), label=label)
5694 if f in copy:
5693 if f in copy:
5695 fm.write("copy", ' %s' + end, repo.pathto(copy[f], cwd),
5694 fm.write("copy", ' %s' + end, repo.pathto(copy[f], cwd),
5696 label='status.copied')
5695 label='status.copied')
5697 fm.end()
5696 fm.end()
5698
5697
5699 @command('^summary|sum',
5698 @command('^summary|sum',
5700 [('', 'remote', None, _('check for push and pull'))], '[--remote]')
5699 [('', 'remote', None, _('check for push and pull'))], '[--remote]')
5701 def summary(ui, repo, **opts):
5700 def summary(ui, repo, **opts):
5702 """summarize working directory state
5701 """summarize working directory state
5703
5702
5704 This generates a brief summary of the working directory state,
5703 This generates a brief summary of the working directory state,
5705 including parents, branch, commit status, and available updates.
5704 including parents, branch, commit status, and available updates.
5706
5705
5707 With the --remote option, this will check the default paths for
5706 With the --remote option, this will check the default paths for
5708 incoming and outgoing changes. This can be time-consuming.
5707 incoming and outgoing changes. This can be time-consuming.
5709
5708
5710 Returns 0 on success.
5709 Returns 0 on success.
5711 """
5710 """
5712
5711
5713 ctx = repo[None]
5712 ctx = repo[None]
5714 parents = ctx.parents()
5713 parents = ctx.parents()
5715 pnode = parents[0].node()
5714 pnode = parents[0].node()
5716 marks = []
5715 marks = []
5717
5716
5718 for p in parents:
5717 for p in parents:
5719 # label with log.changeset (instead of log.parent) since this
5718 # label with log.changeset (instead of log.parent) since this
5720 # shows a working directory parent *changeset*:
5719 # shows a working directory parent *changeset*:
5721 # i18n: column positioning for "hg summary"
5720 # i18n: column positioning for "hg summary"
5722 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
5721 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
5723 label='log.changeset changeset.%s' % p.phasestr())
5722 label='log.changeset changeset.%s' % p.phasestr())
5724 ui.write(' '.join(p.tags()), label='log.tag')
5723 ui.write(' '.join(p.tags()), label='log.tag')
5725 if p.bookmarks():
5724 if p.bookmarks():
5726 marks.extend(p.bookmarks())
5725 marks.extend(p.bookmarks())
5727 if p.rev() == -1:
5726 if p.rev() == -1:
5728 if not len(repo):
5727 if not len(repo):
5729 ui.write(_(' (empty repository)'))
5728 ui.write(_(' (empty repository)'))
5730 else:
5729 else:
5731 ui.write(_(' (no revision checked out)'))
5730 ui.write(_(' (no revision checked out)'))
5732 ui.write('\n')
5731 ui.write('\n')
5733 if p.description():
5732 if p.description():
5734 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
5733 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
5735 label='log.summary')
5734 label='log.summary')
5736
5735
5737 branch = ctx.branch()
5736 branch = ctx.branch()
5738 bheads = repo.branchheads(branch)
5737 bheads = repo.branchheads(branch)
5739 # i18n: column positioning for "hg summary"
5738 # i18n: column positioning for "hg summary"
5740 m = _('branch: %s\n') % branch
5739 m = _('branch: %s\n') % branch
5741 if branch != 'default':
5740 if branch != 'default':
5742 ui.write(m, label='log.branch')
5741 ui.write(m, label='log.branch')
5743 else:
5742 else:
5744 ui.status(m, label='log.branch')
5743 ui.status(m, label='log.branch')
5745
5744
5746 if marks:
5745 if marks:
5747 current = repo._bookmarkcurrent
5746 current = repo._bookmarkcurrent
5748 # i18n: column positioning for "hg summary"
5747 # i18n: column positioning for "hg summary"
5749 ui.write(_('bookmarks:'), label='log.bookmark')
5748 ui.write(_('bookmarks:'), label='log.bookmark')
5750 if current is not None:
5749 if current is not None:
5751 if current in marks:
5750 if current in marks:
5752 ui.write(' *' + current, label='bookmarks.current')
5751 ui.write(' *' + current, label='bookmarks.current')
5753 marks.remove(current)
5752 marks.remove(current)
5754 else:
5753 else:
5755 ui.write(' [%s]' % current, label='bookmarks.current')
5754 ui.write(' [%s]' % current, label='bookmarks.current')
5756 for m in marks:
5755 for m in marks:
5757 ui.write(' ' + m, label='log.bookmark')
5756 ui.write(' ' + m, label='log.bookmark')
5758 ui.write('\n', label='log.bookmark')
5757 ui.write('\n', label='log.bookmark')
5759
5758
5760 status = repo.status(unknown=True)
5759 status = repo.status(unknown=True)
5761
5760
5762 c = repo.dirstate.copies()
5761 c = repo.dirstate.copies()
5763 copied, renamed = [], []
5762 copied, renamed = [], []
5764 for d, s in c.iteritems():
5763 for d, s in c.iteritems():
5765 if s in status.removed:
5764 if s in status.removed:
5766 status.removed.remove(s)
5765 status.removed.remove(s)
5767 renamed.append(d)
5766 renamed.append(d)
5768 else:
5767 else:
5769 copied.append(d)
5768 copied.append(d)
5770 if d in status.added:
5769 if d in status.added:
5771 status.added.remove(d)
5770 status.added.remove(d)
5772
5771
5773 ms = mergemod.mergestate(repo)
5772 ms = mergemod.mergestate(repo)
5774 unresolved = [f for f in ms if ms[f] == 'u']
5773 unresolved = [f for f in ms if ms[f] == 'u']
5775
5774
5776 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
5775 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
5777
5776
5778 labels = [(ui.label(_('%d modified'), 'status.modified'), status.modified),
5777 labels = [(ui.label(_('%d modified'), 'status.modified'), status.modified),
5779 (ui.label(_('%d added'), 'status.added'), status.added),
5778 (ui.label(_('%d added'), 'status.added'), status.added),
5780 (ui.label(_('%d removed'), 'status.removed'), status.removed),
5779 (ui.label(_('%d removed'), 'status.removed'), status.removed),
5781 (ui.label(_('%d renamed'), 'status.copied'), renamed),
5780 (ui.label(_('%d renamed'), 'status.copied'), renamed),
5782 (ui.label(_('%d copied'), 'status.copied'), copied),
5781 (ui.label(_('%d copied'), 'status.copied'), copied),
5783 (ui.label(_('%d deleted'), 'status.deleted'), status.deleted),
5782 (ui.label(_('%d deleted'), 'status.deleted'), status.deleted),
5784 (ui.label(_('%d unknown'), 'status.unknown'), status.unknown),
5783 (ui.label(_('%d unknown'), 'status.unknown'), status.unknown),
5785 (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved),
5784 (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved),
5786 (ui.label(_('%d subrepos'), 'status.modified'), subs)]
5785 (ui.label(_('%d subrepos'), 'status.modified'), subs)]
5787 t = []
5786 t = []
5788 for l, s in labels:
5787 for l, s in labels:
5789 if s:
5788 if s:
5790 t.append(l % len(s))
5789 t.append(l % len(s))
5791
5790
5792 t = ', '.join(t)
5791 t = ', '.join(t)
5793 cleanworkdir = False
5792 cleanworkdir = False
5794
5793
5795 if repo.vfs.exists('updatestate'):
5794 if repo.vfs.exists('updatestate'):
5796 t += _(' (interrupted update)')
5795 t += _(' (interrupted update)')
5797 elif len(parents) > 1:
5796 elif len(parents) > 1:
5798 t += _(' (merge)')
5797 t += _(' (merge)')
5799 elif branch != parents[0].branch():
5798 elif branch != parents[0].branch():
5800 t += _(' (new branch)')
5799 t += _(' (new branch)')
5801 elif (parents[0].closesbranch() and
5800 elif (parents[0].closesbranch() and
5802 pnode in repo.branchheads(branch, closed=True)):
5801 pnode in repo.branchheads(branch, closed=True)):
5803 t += _(' (head closed)')
5802 t += _(' (head closed)')
5804 elif not (status.modified or status.added or status.removed or renamed or
5803 elif not (status.modified or status.added or status.removed or renamed or
5805 copied or subs):
5804 copied or subs):
5806 t += _(' (clean)')
5805 t += _(' (clean)')
5807 cleanworkdir = True
5806 cleanworkdir = True
5808 elif pnode not in bheads:
5807 elif pnode not in bheads:
5809 t += _(' (new branch head)')
5808 t += _(' (new branch head)')
5810
5809
5811 if cleanworkdir:
5810 if cleanworkdir:
5812 # i18n: column positioning for "hg summary"
5811 # i18n: column positioning for "hg summary"
5813 ui.status(_('commit: %s\n') % t.strip())
5812 ui.status(_('commit: %s\n') % t.strip())
5814 else:
5813 else:
5815 # i18n: column positioning for "hg summary"
5814 # i18n: column positioning for "hg summary"
5816 ui.write(_('commit: %s\n') % t.strip())
5815 ui.write(_('commit: %s\n') % t.strip())
5817
5816
5818 # all ancestors of branch heads - all ancestors of parent = new csets
5817 # all ancestors of branch heads - all ancestors of parent = new csets
5819 new = len(repo.changelog.findmissing([pctx.node() for pctx in parents],
5818 new = len(repo.changelog.findmissing([pctx.node() for pctx in parents],
5820 bheads))
5819 bheads))
5821
5820
5822 if new == 0:
5821 if new == 0:
5823 # i18n: column positioning for "hg summary"
5822 # i18n: column positioning for "hg summary"
5824 ui.status(_('update: (current)\n'))
5823 ui.status(_('update: (current)\n'))
5825 elif pnode not in bheads:
5824 elif pnode not in bheads:
5826 # i18n: column positioning for "hg summary"
5825 # i18n: column positioning for "hg summary"
5827 ui.write(_('update: %d new changesets (update)\n') % new)
5826 ui.write(_('update: %d new changesets (update)\n') % new)
5828 else:
5827 else:
5829 # i18n: column positioning for "hg summary"
5828 # i18n: column positioning for "hg summary"
5830 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
5829 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
5831 (new, len(bheads)))
5830 (new, len(bheads)))
5832
5831
5833 cmdutil.summaryhooks(ui, repo)
5832 cmdutil.summaryhooks(ui, repo)
5834
5833
5835 if opts.get('remote'):
5834 if opts.get('remote'):
5836 needsincoming, needsoutgoing = True, True
5835 needsincoming, needsoutgoing = True, True
5837 else:
5836 else:
5838 needsincoming, needsoutgoing = False, False
5837 needsincoming, needsoutgoing = False, False
5839 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
5838 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
5840 if i:
5839 if i:
5841 needsincoming = True
5840 needsincoming = True
5842 if o:
5841 if o:
5843 needsoutgoing = True
5842 needsoutgoing = True
5844 if not needsincoming and not needsoutgoing:
5843 if not needsincoming and not needsoutgoing:
5845 return
5844 return
5846
5845
5847 def getincoming():
5846 def getincoming():
5848 source, branches = hg.parseurl(ui.expandpath('default'))
5847 source, branches = hg.parseurl(ui.expandpath('default'))
5849 sbranch = branches[0]
5848 sbranch = branches[0]
5850 try:
5849 try:
5851 other = hg.peer(repo, {}, source)
5850 other = hg.peer(repo, {}, source)
5852 except error.RepoError:
5851 except error.RepoError:
5853 if opts.get('remote'):
5852 if opts.get('remote'):
5854 raise
5853 raise
5855 return source, sbranch, None, None, None
5854 return source, sbranch, None, None, None
5856 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
5855 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
5857 if revs:
5856 if revs:
5858 revs = [other.lookup(rev) for rev in revs]
5857 revs = [other.lookup(rev) for rev in revs]
5859 ui.debug('comparing with %s\n' % util.hidepassword(source))
5858 ui.debug('comparing with %s\n' % util.hidepassword(source))
5860 repo.ui.pushbuffer()
5859 repo.ui.pushbuffer()
5861 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
5860 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
5862 repo.ui.popbuffer()
5861 repo.ui.popbuffer()
5863 return source, sbranch, other, commoninc, commoninc[1]
5862 return source, sbranch, other, commoninc, commoninc[1]
5864
5863
5865 if needsincoming:
5864 if needsincoming:
5866 source, sbranch, sother, commoninc, incoming = getincoming()
5865 source, sbranch, sother, commoninc, incoming = getincoming()
5867 else:
5866 else:
5868 source = sbranch = sother = commoninc = incoming = None
5867 source = sbranch = sother = commoninc = incoming = None
5869
5868
5870 def getoutgoing():
5869 def getoutgoing():
5871 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5870 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5872 dbranch = branches[0]
5871 dbranch = branches[0]
5873 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5872 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5874 if source != dest:
5873 if source != dest:
5875 try:
5874 try:
5876 dother = hg.peer(repo, {}, dest)
5875 dother = hg.peer(repo, {}, dest)
5877 except error.RepoError:
5876 except error.RepoError:
5878 if opts.get('remote'):
5877 if opts.get('remote'):
5879 raise
5878 raise
5880 return dest, dbranch, None, None
5879 return dest, dbranch, None, None
5881 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5880 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5882 elif sother is None:
5881 elif sother is None:
5883 # there is no explicit destination peer, but source one is invalid
5882 # there is no explicit destination peer, but source one is invalid
5884 return dest, dbranch, None, None
5883 return dest, dbranch, None, None
5885 else:
5884 else:
5886 dother = sother
5885 dother = sother
5887 if (source != dest or (sbranch is not None and sbranch != dbranch)):
5886 if (source != dest or (sbranch is not None and sbranch != dbranch)):
5888 common = None
5887 common = None
5889 else:
5888 else:
5890 common = commoninc
5889 common = commoninc
5891 if revs:
5890 if revs:
5892 revs = [repo.lookup(rev) for rev in revs]
5891 revs = [repo.lookup(rev) for rev in revs]
5893 repo.ui.pushbuffer()
5892 repo.ui.pushbuffer()
5894 outgoing = discovery.findcommonoutgoing(repo, dother, onlyheads=revs,
5893 outgoing = discovery.findcommonoutgoing(repo, dother, onlyheads=revs,
5895 commoninc=common)
5894 commoninc=common)
5896 repo.ui.popbuffer()
5895 repo.ui.popbuffer()
5897 return dest, dbranch, dother, outgoing
5896 return dest, dbranch, dother, outgoing
5898
5897
5899 if needsoutgoing:
5898 if needsoutgoing:
5900 dest, dbranch, dother, outgoing = getoutgoing()
5899 dest, dbranch, dother, outgoing = getoutgoing()
5901 else:
5900 else:
5902 dest = dbranch = dother = outgoing = None
5901 dest = dbranch = dother = outgoing = None
5903
5902
5904 if opts.get('remote'):
5903 if opts.get('remote'):
5905 t = []
5904 t = []
5906 if incoming:
5905 if incoming:
5907 t.append(_('1 or more incoming'))
5906 t.append(_('1 or more incoming'))
5908 o = outgoing.missing
5907 o = outgoing.missing
5909 if o:
5908 if o:
5910 t.append(_('%d outgoing') % len(o))
5909 t.append(_('%d outgoing') % len(o))
5911 other = dother or sother
5910 other = dother or sother
5912 if 'bookmarks' in other.listkeys('namespaces'):
5911 if 'bookmarks' in other.listkeys('namespaces'):
5913 lmarks = repo.listkeys('bookmarks')
5912 lmarks = repo.listkeys('bookmarks')
5914 rmarks = other.listkeys('bookmarks')
5913 rmarks = other.listkeys('bookmarks')
5915 diff = set(rmarks) - set(lmarks)
5914 diff = set(rmarks) - set(lmarks)
5916 if len(diff) > 0:
5915 if len(diff) > 0:
5917 t.append(_('%d incoming bookmarks') % len(diff))
5916 t.append(_('%d incoming bookmarks') % len(diff))
5918 diff = set(lmarks) - set(rmarks)
5917 diff = set(lmarks) - set(rmarks)
5919 if len(diff) > 0:
5918 if len(diff) > 0:
5920 t.append(_('%d outgoing bookmarks') % len(diff))
5919 t.append(_('%d outgoing bookmarks') % len(diff))
5921
5920
5922 if t:
5921 if t:
5923 # i18n: column positioning for "hg summary"
5922 # i18n: column positioning for "hg summary"
5924 ui.write(_('remote: %s\n') % (', '.join(t)))
5923 ui.write(_('remote: %s\n') % (', '.join(t)))
5925 else:
5924 else:
5926 # i18n: column positioning for "hg summary"
5925 # i18n: column positioning for "hg summary"
5927 ui.status(_('remote: (synced)\n'))
5926 ui.status(_('remote: (synced)\n'))
5928
5927
5929 cmdutil.summaryremotehooks(ui, repo, opts,
5928 cmdutil.summaryremotehooks(ui, repo, opts,
5930 ((source, sbranch, sother, commoninc),
5929 ((source, sbranch, sother, commoninc),
5931 (dest, dbranch, dother, outgoing)))
5930 (dest, dbranch, dother, outgoing)))
5932
5931
5933 @command('tag',
5932 @command('tag',
5934 [('f', 'force', None, _('force tag')),
5933 [('f', 'force', None, _('force tag')),
5935 ('l', 'local', None, _('make the tag local')),
5934 ('l', 'local', None, _('make the tag local')),
5936 ('r', 'rev', '', _('revision to tag'), _('REV')),
5935 ('r', 'rev', '', _('revision to tag'), _('REV')),
5937 ('', 'remove', None, _('remove a tag')),
5936 ('', 'remove', None, _('remove a tag')),
5938 # -l/--local is already there, commitopts cannot be used
5937 # -l/--local is already there, commitopts cannot be used
5939 ('e', 'edit', None, _('invoke editor on commit messages')),
5938 ('e', 'edit', None, _('invoke editor on commit messages')),
5940 ('m', 'message', '', _('use text as commit message'), _('TEXT')),
5939 ('m', 'message', '', _('use text as commit message'), _('TEXT')),
5941 ] + commitopts2,
5940 ] + commitopts2,
5942 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
5941 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
5943 def tag(ui, repo, name1, *names, **opts):
5942 def tag(ui, repo, name1, *names, **opts):
5944 """add one or more tags for the current or given revision
5943 """add one or more tags for the current or given revision
5945
5944
5946 Name a particular revision using <name>.
5945 Name a particular revision using <name>.
5947
5946
5948 Tags are used to name particular revisions of the repository and are
5947 Tags are used to name particular revisions of the repository and are
5949 very useful to compare different revisions, to go back to significant
5948 very useful to compare different revisions, to go back to significant
5950 earlier versions or to mark branch points as releases, etc. Changing
5949 earlier versions or to mark branch points as releases, etc. Changing
5951 an existing tag is normally disallowed; use -f/--force to override.
5950 an existing tag is normally disallowed; use -f/--force to override.
5952
5951
5953 If no revision is given, the parent of the working directory is
5952 If no revision is given, the parent of the working directory is
5954 used.
5953 used.
5955
5954
5956 To facilitate version control, distribution, and merging of tags,
5955 To facilitate version control, distribution, and merging of tags,
5957 they are stored as a file named ".hgtags" which is managed similarly
5956 they are stored as a file named ".hgtags" which is managed similarly
5958 to other project files and can be hand-edited if necessary. This
5957 to other project files and can be hand-edited if necessary. This
5959 also means that tagging creates a new commit. The file
5958 also means that tagging creates a new commit. The file
5960 ".hg/localtags" is used for local tags (not shared among
5959 ".hg/localtags" is used for local tags (not shared among
5961 repositories).
5960 repositories).
5962
5961
5963 Tag commits are usually made at the head of a branch. If the parent
5962 Tag commits are usually made at the head of a branch. If the parent
5964 of the working directory is not a branch head, :hg:`tag` aborts; use
5963 of the working directory is not a branch head, :hg:`tag` aborts; use
5965 -f/--force to force the tag commit to be based on a non-head
5964 -f/--force to force the tag commit to be based on a non-head
5966 changeset.
5965 changeset.
5967
5966
5968 See :hg:`help dates` for a list of formats valid for -d/--date.
5967 See :hg:`help dates` for a list of formats valid for -d/--date.
5969
5968
5970 Since tag names have priority over branch names during revision
5969 Since tag names have priority over branch names during revision
5971 lookup, using an existing branch name as a tag name is discouraged.
5970 lookup, using an existing branch name as a tag name is discouraged.
5972
5971
5973 Returns 0 on success.
5972 Returns 0 on success.
5974 """
5973 """
5975 wlock = lock = None
5974 wlock = lock = None
5976 try:
5975 try:
5977 wlock = repo.wlock()
5976 wlock = repo.wlock()
5978 lock = repo.lock()
5977 lock = repo.lock()
5979 rev_ = "."
5978 rev_ = "."
5980 names = [t.strip() for t in (name1,) + names]
5979 names = [t.strip() for t in (name1,) + names]
5981 if len(names) != len(set(names)):
5980 if len(names) != len(set(names)):
5982 raise util.Abort(_('tag names must be unique'))
5981 raise util.Abort(_('tag names must be unique'))
5983 for n in names:
5982 for n in names:
5984 scmutil.checknewlabel(repo, n, 'tag')
5983 scmutil.checknewlabel(repo, n, 'tag')
5985 if not n:
5984 if not n:
5986 raise util.Abort(_('tag names cannot consist entirely of '
5985 raise util.Abort(_('tag names cannot consist entirely of '
5987 'whitespace'))
5986 'whitespace'))
5988 if opts.get('rev') and opts.get('remove'):
5987 if opts.get('rev') and opts.get('remove'):
5989 raise util.Abort(_("--rev and --remove are incompatible"))
5988 raise util.Abort(_("--rev and --remove are incompatible"))
5990 if opts.get('rev'):
5989 if opts.get('rev'):
5991 rev_ = opts['rev']
5990 rev_ = opts['rev']
5992 message = opts.get('message')
5991 message = opts.get('message')
5993 if opts.get('remove'):
5992 if opts.get('remove'):
5994 expectedtype = opts.get('local') and 'local' or 'global'
5993 expectedtype = opts.get('local') and 'local' or 'global'
5995 for n in names:
5994 for n in names:
5996 if not repo.tagtype(n):
5995 if not repo.tagtype(n):
5997 raise util.Abort(_("tag '%s' does not exist") % n)
5996 raise util.Abort(_("tag '%s' does not exist") % n)
5998 if repo.tagtype(n) != expectedtype:
5997 if repo.tagtype(n) != expectedtype:
5999 if expectedtype == 'global':
5998 if expectedtype == 'global':
6000 raise util.Abort(_("tag '%s' is not a global tag") % n)
5999 raise util.Abort(_("tag '%s' is not a global tag") % n)
6001 else:
6000 else:
6002 raise util.Abort(_("tag '%s' is not a local tag") % n)
6001 raise util.Abort(_("tag '%s' is not a local tag") % n)
6003 rev_ = nullid
6002 rev_ = nullid
6004 if not message:
6003 if not message:
6005 # we don't translate commit messages
6004 # we don't translate commit messages
6006 message = 'Removed tag %s' % ', '.join(names)
6005 message = 'Removed tag %s' % ', '.join(names)
6007 elif not opts.get('force'):
6006 elif not opts.get('force'):
6008 for n in names:
6007 for n in names:
6009 if n in repo.tags():
6008 if n in repo.tags():
6010 raise util.Abort(_("tag '%s' already exists "
6009 raise util.Abort(_("tag '%s' already exists "
6011 "(use -f to force)") % n)
6010 "(use -f to force)") % n)
6012 if not opts.get('local'):
6011 if not opts.get('local'):
6013 p1, p2 = repo.dirstate.parents()
6012 p1, p2 = repo.dirstate.parents()
6014 if p2 != nullid:
6013 if p2 != nullid:
6015 raise util.Abort(_('uncommitted merge'))
6014 raise util.Abort(_('uncommitted merge'))
6016 bheads = repo.branchheads()
6015 bheads = repo.branchheads()
6017 if not opts.get('force') and bheads and p1 not in bheads:
6016 if not opts.get('force') and bheads and p1 not in bheads:
6018 raise util.Abort(_('not at a branch head (use -f to force)'))
6017 raise util.Abort(_('not at a branch head (use -f to force)'))
6019 r = scmutil.revsingle(repo, rev_).node()
6018 r = scmutil.revsingle(repo, rev_).node()
6020
6019
6021 if not message:
6020 if not message:
6022 # we don't translate commit messages
6021 # we don't translate commit messages
6023 message = ('Added tag %s for changeset %s' %
6022 message = ('Added tag %s for changeset %s' %
6024 (', '.join(names), short(r)))
6023 (', '.join(names), short(r)))
6025
6024
6026 date = opts.get('date')
6025 date = opts.get('date')
6027 if date:
6026 if date:
6028 date = util.parsedate(date)
6027 date = util.parsedate(date)
6029
6028
6030 if opts.get('remove'):
6029 if opts.get('remove'):
6031 editform = 'tag.remove'
6030 editform = 'tag.remove'
6032 else:
6031 else:
6033 editform = 'tag.add'
6032 editform = 'tag.add'
6034 editor = cmdutil.getcommiteditor(editform=editform, **opts)
6033 editor = cmdutil.getcommiteditor(editform=editform, **opts)
6035
6034
6036 # don't allow tagging the null rev
6035 # don't allow tagging the null rev
6037 if (not opts.get('remove') and
6036 if (not opts.get('remove') and
6038 scmutil.revsingle(repo, rev_).rev() == nullrev):
6037 scmutil.revsingle(repo, rev_).rev() == nullrev):
6039 raise util.Abort(_("cannot tag null revision"))
6038 raise util.Abort(_("cannot tag null revision"))
6040
6039
6041 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date,
6040 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date,
6042 editor=editor)
6041 editor=editor)
6043 finally:
6042 finally:
6044 release(lock, wlock)
6043 release(lock, wlock)
6045
6044
6046 @command('tags', formatteropts, '')
6045 @command('tags', formatteropts, '')
6047 def tags(ui, repo, **opts):
6046 def tags(ui, repo, **opts):
6048 """list repository tags
6047 """list repository tags
6049
6048
6050 This lists both regular and local tags. When the -v/--verbose
6049 This lists both regular and local tags. When the -v/--verbose
6051 switch is used, a third column "local" is printed for local tags.
6050 switch is used, a third column "local" is printed for local tags.
6052
6051
6053 Returns 0 on success.
6052 Returns 0 on success.
6054 """
6053 """
6055
6054
6056 fm = ui.formatter('tags', opts)
6055 fm = ui.formatter('tags', opts)
6057 hexfunc = fm.hexfunc
6056 hexfunc = fm.hexfunc
6058 tagtype = ""
6057 tagtype = ""
6059
6058
6060 for t, n in reversed(repo.tagslist()):
6059 for t, n in reversed(repo.tagslist()):
6061 hn = hexfunc(n)
6060 hn = hexfunc(n)
6062 label = 'tags.normal'
6061 label = 'tags.normal'
6063 tagtype = ''
6062 tagtype = ''
6064 if repo.tagtype(t) == 'local':
6063 if repo.tagtype(t) == 'local':
6065 label = 'tags.local'
6064 label = 'tags.local'
6066 tagtype = 'local'
6065 tagtype = 'local'
6067
6066
6068 fm.startitem()
6067 fm.startitem()
6069 fm.write('tag', '%s', t, label=label)
6068 fm.write('tag', '%s', t, label=label)
6070 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
6069 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
6071 fm.condwrite(not ui.quiet, 'rev node', fmt,
6070 fm.condwrite(not ui.quiet, 'rev node', fmt,
6072 repo.changelog.rev(n), hn, label=label)
6071 repo.changelog.rev(n), hn, label=label)
6073 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
6072 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
6074 tagtype, label=label)
6073 tagtype, label=label)
6075 fm.plain('\n')
6074 fm.plain('\n')
6076 fm.end()
6075 fm.end()
6077
6076
6078 @command('tip',
6077 @command('tip',
6079 [('p', 'patch', None, _('show patch')),
6078 [('p', 'patch', None, _('show patch')),
6080 ('g', 'git', None, _('use git extended diff format')),
6079 ('g', 'git', None, _('use git extended diff format')),
6081 ] + templateopts,
6080 ] + templateopts,
6082 _('[-p] [-g]'))
6081 _('[-p] [-g]'))
6083 def tip(ui, repo, **opts):
6082 def tip(ui, repo, **opts):
6084 """show the tip revision (DEPRECATED)
6083 """show the tip revision (DEPRECATED)
6085
6084
6086 The tip revision (usually just called the tip) is the changeset
6085 The tip revision (usually just called the tip) is the changeset
6087 most recently added to the repository (and therefore the most
6086 most recently added to the repository (and therefore the most
6088 recently changed head).
6087 recently changed head).
6089
6088
6090 If you have just made a commit, that commit will be the tip. If
6089 If you have just made a commit, that commit will be the tip. If
6091 you have just pulled changes from another repository, the tip of
6090 you have just pulled changes from another repository, the tip of
6092 that repository becomes the current tip. The "tip" tag is special
6091 that repository becomes the current tip. The "tip" tag is special
6093 and cannot be renamed or assigned to a different changeset.
6092 and cannot be renamed or assigned to a different changeset.
6094
6093
6095 This command is deprecated, please use :hg:`heads` instead.
6094 This command is deprecated, please use :hg:`heads` instead.
6096
6095
6097 Returns 0 on success.
6096 Returns 0 on success.
6098 """
6097 """
6099 displayer = cmdutil.show_changeset(ui, repo, opts)
6098 displayer = cmdutil.show_changeset(ui, repo, opts)
6100 displayer.show(repo['tip'])
6099 displayer.show(repo['tip'])
6101 displayer.close()
6100 displayer.close()
6102
6101
6103 @command('unbundle',
6102 @command('unbundle',
6104 [('u', 'update', None,
6103 [('u', 'update', None,
6105 _('update to new branch head if changesets were unbundled'))],
6104 _('update to new branch head if changesets were unbundled'))],
6106 _('[-u] FILE...'))
6105 _('[-u] FILE...'))
6107 def unbundle(ui, repo, fname1, *fnames, **opts):
6106 def unbundle(ui, repo, fname1, *fnames, **opts):
6108 """apply one or more changegroup files
6107 """apply one or more changegroup files
6109
6108
6110 Apply one or more compressed changegroup files generated by the
6109 Apply one or more compressed changegroup files generated by the
6111 bundle command.
6110 bundle command.
6112
6111
6113 Returns 0 on success, 1 if an update has unresolved files.
6112 Returns 0 on success, 1 if an update has unresolved files.
6114 """
6113 """
6115 fnames = (fname1,) + fnames
6114 fnames = (fname1,) + fnames
6116
6115
6117 lock = repo.lock()
6116 lock = repo.lock()
6118 try:
6117 try:
6119 for fname in fnames:
6118 for fname in fnames:
6120 f = hg.openpath(ui, fname)
6119 f = hg.openpath(ui, fname)
6121 gen = exchange.readbundle(ui, f, fname)
6120 gen = exchange.readbundle(ui, f, fname)
6122 modheads = changegroup.addchangegroup(repo, gen, 'unbundle',
6121 modheads = changegroup.addchangegroup(repo, gen, 'unbundle',
6123 'bundle:' + fname)
6122 'bundle:' + fname)
6124 finally:
6123 finally:
6125 lock.release()
6124 lock.release()
6126
6125
6127 return postincoming(ui, repo, modheads, opts.get('update'), None)
6126 return postincoming(ui, repo, modheads, opts.get('update'), None)
6128
6127
6129 @command('^update|up|checkout|co',
6128 @command('^update|up|checkout|co',
6130 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
6129 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
6131 ('c', 'check', None,
6130 ('c', 'check', None,
6132 _('update across branches if no uncommitted changes')),
6131 _('update across branches if no uncommitted changes')),
6133 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
6132 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
6134 ('r', 'rev', '', _('revision'), _('REV'))
6133 ('r', 'rev', '', _('revision'), _('REV'))
6135 ] + mergetoolopts,
6134 ] + mergetoolopts,
6136 _('[-c] [-C] [-d DATE] [[-r] REV]'))
6135 _('[-c] [-C] [-d DATE] [[-r] REV]'))
6137 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False,
6136 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False,
6138 tool=None):
6137 tool=None):
6139 """update working directory (or switch revisions)
6138 """update working directory (or switch revisions)
6140
6139
6141 Update the repository's working directory to the specified
6140 Update the repository's working directory to the specified
6142 changeset. If no changeset is specified, update to the tip of the
6141 changeset. If no changeset is specified, update to the tip of the
6143 current named branch and move the current bookmark (see :hg:`help
6142 current named branch and move the current bookmark (see :hg:`help
6144 bookmarks`).
6143 bookmarks`).
6145
6144
6146 Update sets the working directory's parent revision to the specified
6145 Update sets the working directory's parent revision to the specified
6147 changeset (see :hg:`help parents`).
6146 changeset (see :hg:`help parents`).
6148
6147
6149 If the changeset is not a descendant or ancestor of the working
6148 If the changeset is not a descendant or ancestor of the working
6150 directory's parent, the update is aborted. With the -c/--check
6149 directory's parent, the update is aborted. With the -c/--check
6151 option, the working directory is checked for uncommitted changes; if
6150 option, the working directory is checked for uncommitted changes; if
6152 none are found, the working directory is updated to the specified
6151 none are found, the working directory is updated to the specified
6153 changeset.
6152 changeset.
6154
6153
6155 .. container:: verbose
6154 .. container:: verbose
6156
6155
6157 The following rules apply when the working directory contains
6156 The following rules apply when the working directory contains
6158 uncommitted changes:
6157 uncommitted changes:
6159
6158
6160 1. If neither -c/--check nor -C/--clean is specified, and if
6159 1. If neither -c/--check nor -C/--clean is specified, and if
6161 the requested changeset is an ancestor or descendant of
6160 the requested changeset is an ancestor or descendant of
6162 the working directory's parent, the uncommitted changes
6161 the working directory's parent, the uncommitted changes
6163 are merged into the requested changeset and the merged
6162 are merged into the requested changeset and the merged
6164 result is left uncommitted. If the requested changeset is
6163 result is left uncommitted. If the requested changeset is
6165 not an ancestor or descendant (that is, it is on another
6164 not an ancestor or descendant (that is, it is on another
6166 branch), the update is aborted and the uncommitted changes
6165 branch), the update is aborted and the uncommitted changes
6167 are preserved.
6166 are preserved.
6168
6167
6169 2. With the -c/--check option, the update is aborted and the
6168 2. With the -c/--check option, the update is aborted and the
6170 uncommitted changes are preserved.
6169 uncommitted changes are preserved.
6171
6170
6172 3. With the -C/--clean option, uncommitted changes are discarded and
6171 3. With the -C/--clean option, uncommitted changes are discarded and
6173 the working directory is updated to the requested changeset.
6172 the working directory is updated to the requested changeset.
6174
6173
6175 To cancel an uncommitted merge (and lose your changes), use
6174 To cancel an uncommitted merge (and lose your changes), use
6176 :hg:`update --clean .`.
6175 :hg:`update --clean .`.
6177
6176
6178 Use null as the changeset to remove the working directory (like
6177 Use null as the changeset to remove the working directory (like
6179 :hg:`clone -U`).
6178 :hg:`clone -U`).
6180
6179
6181 If you want to revert just one file to an older revision, use
6180 If you want to revert just one file to an older revision, use
6182 :hg:`revert [-r REV] NAME`.
6181 :hg:`revert [-r REV] NAME`.
6183
6182
6184 See :hg:`help dates` for a list of formats valid for -d/--date.
6183 See :hg:`help dates` for a list of formats valid for -d/--date.
6185
6184
6186 Returns 0 on success, 1 if there are unresolved files.
6185 Returns 0 on success, 1 if there are unresolved files.
6187 """
6186 """
6188 if rev and node:
6187 if rev and node:
6189 raise util.Abort(_("please specify just one revision"))
6188 raise util.Abort(_("please specify just one revision"))
6190
6189
6191 if rev is None or rev == '':
6190 if rev is None or rev == '':
6192 rev = node
6191 rev = node
6193
6192
6194 cmdutil.clearunfinished(repo)
6193 cmdutil.clearunfinished(repo)
6195
6194
6196 # with no argument, we also move the current bookmark, if any
6195 # with no argument, we also move the current bookmark, if any
6197 rev, movemarkfrom = bookmarks.calculateupdate(ui, repo, rev)
6196 rev, movemarkfrom = bookmarks.calculateupdate(ui, repo, rev)
6198
6197
6199 # if we defined a bookmark, we have to remember the original bookmark name
6198 # if we defined a bookmark, we have to remember the original bookmark name
6200 brev = rev
6199 brev = rev
6201 rev = scmutil.revsingle(repo, rev, rev).rev()
6200 rev = scmutil.revsingle(repo, rev, rev).rev()
6202
6201
6203 if check and clean:
6202 if check and clean:
6204 raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
6203 raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
6205
6204
6206 if date:
6205 if date:
6207 if rev is not None:
6206 if rev is not None:
6208 raise util.Abort(_("you can't specify a revision and a date"))
6207 raise util.Abort(_("you can't specify a revision and a date"))
6209 rev = cmdutil.finddate(ui, repo, date)
6208 rev = cmdutil.finddate(ui, repo, date)
6210
6209
6211 if check:
6210 if check:
6212 c = repo[None]
6211 c = repo[None]
6213 if c.dirty(merge=False, branch=False, missing=True):
6212 if c.dirty(merge=False, branch=False, missing=True):
6214 raise util.Abort(_("uncommitted changes"))
6213 raise util.Abort(_("uncommitted changes"))
6215 if rev is None:
6214 if rev is None:
6216 rev = repo[repo[None].branch()].rev()
6215 rev = repo[repo[None].branch()].rev()
6217
6216
6218 repo.ui.setconfig('ui', 'forcemerge', tool, 'update')
6217 repo.ui.setconfig('ui', 'forcemerge', tool, 'update')
6219
6218
6220 if clean:
6219 if clean:
6221 ret = hg.clean(repo, rev)
6220 ret = hg.clean(repo, rev)
6222 else:
6221 else:
6223 ret = hg.update(repo, rev)
6222 ret = hg.update(repo, rev)
6224
6223
6225 if not ret and movemarkfrom:
6224 if not ret and movemarkfrom:
6226 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
6225 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
6227 ui.status(_("updating bookmark %s\n") % repo._bookmarkcurrent)
6226 ui.status(_("updating bookmark %s\n") % repo._bookmarkcurrent)
6228 elif brev in repo._bookmarks:
6227 elif brev in repo._bookmarks:
6229 bookmarks.setcurrent(repo, brev)
6228 bookmarks.setcurrent(repo, brev)
6230 ui.status(_("(activating bookmark %s)\n") % brev)
6229 ui.status(_("(activating bookmark %s)\n") % brev)
6231 elif brev:
6230 elif brev:
6232 if repo._bookmarkcurrent:
6231 if repo._bookmarkcurrent:
6233 ui.status(_("(leaving bookmark %s)\n") %
6232 ui.status(_("(leaving bookmark %s)\n") %
6234 repo._bookmarkcurrent)
6233 repo._bookmarkcurrent)
6235 bookmarks.unsetcurrent(repo)
6234 bookmarks.unsetcurrent(repo)
6236
6235
6237 return ret
6236 return ret
6238
6237
6239 @command('verify', [])
6238 @command('verify', [])
6240 def verify(ui, repo):
6239 def verify(ui, repo):
6241 """verify the integrity of the repository
6240 """verify the integrity of the repository
6242
6241
6243 Verify the integrity of the current repository.
6242 Verify the integrity of the current repository.
6244
6243
6245 This will perform an extensive check of the repository's
6244 This will perform an extensive check of the repository's
6246 integrity, validating the hashes and checksums of each entry in
6245 integrity, validating the hashes and checksums of each entry in
6247 the changelog, manifest, and tracked files, as well as the
6246 the changelog, manifest, and tracked files, as well as the
6248 integrity of their crosslinks and indices.
6247 integrity of their crosslinks and indices.
6249
6248
6250 Please see http://mercurial.selenic.com/wiki/RepositoryCorruption
6249 Please see http://mercurial.selenic.com/wiki/RepositoryCorruption
6251 for more information about recovery from corruption of the
6250 for more information about recovery from corruption of the
6252 repository.
6251 repository.
6253
6252
6254 Returns 0 on success, 1 if errors are encountered.
6253 Returns 0 on success, 1 if errors are encountered.
6255 """
6254 """
6256 return hg.verify(repo)
6255 return hg.verify(repo)
6257
6256
6258 @command('version', [], norepo=True)
6257 @command('version', [], norepo=True)
6259 def version_(ui):
6258 def version_(ui):
6260 """output version and copyright information"""
6259 """output version and copyright information"""
6261 ui.write(_("Mercurial Distributed SCM (version %s)\n")
6260 ui.write(_("Mercurial Distributed SCM (version %s)\n")
6262 % util.version())
6261 % util.version())
6263 ui.status(_(
6262 ui.status(_(
6264 "(see http://mercurial.selenic.com for more information)\n"
6263 "(see http://mercurial.selenic.com for more information)\n"
6265 "\nCopyright (C) 2005-2014 Matt Mackall and others\n"
6264 "\nCopyright (C) 2005-2014 Matt Mackall and others\n"
6266 "This is free software; see the source for copying conditions. "
6265 "This is free software; see the source for copying conditions. "
6267 "There is NO\nwarranty; "
6266 "There is NO\nwarranty; "
6268 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
6267 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
6269 ))
6268 ))
6270
6269
6271 ui.note(_("\nEnabled extensions:\n\n"))
6270 ui.note(_("\nEnabled extensions:\n\n"))
6272 if ui.verbose:
6271 if ui.verbose:
6273 # format names and versions into columns
6272 # format names and versions into columns
6274 names = []
6273 names = []
6275 vers = []
6274 vers = []
6276 for name, module in extensions.extensions():
6275 for name, module in extensions.extensions():
6277 names.append(name)
6276 names.append(name)
6278 vers.append(extensions.moduleversion(module))
6277 vers.append(extensions.moduleversion(module))
6279 if names:
6278 if names:
6280 maxnamelen = max(len(n) for n in names)
6279 maxnamelen = max(len(n) for n in names)
6281 for i, name in enumerate(names):
6280 for i, name in enumerate(names):
6282 ui.write(" %-*s %s\n" % (maxnamelen, name, vers[i]))
6281 ui.write(" %-*s %s\n" % (maxnamelen, name, vers[i]))
@@ -1,1681 +1,1682 b''
1 # subrepo.py - sub-repository handling for Mercurial
1 # subrepo.py - sub-repository handling for Mercurial
2 #
2 #
3 # Copyright 2009-2010 Matt Mackall <mpm@selenic.com>
3 # Copyright 2009-2010 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 import copy
8 import copy
9 import errno, os, re, shutil, posixpath, sys
9 import errno, os, re, shutil, posixpath, sys
10 import xml.dom.minidom
10 import xml.dom.minidom
11 import stat, subprocess, tarfile
11 import stat, subprocess, tarfile
12 from i18n import _
12 from i18n import _
13 import config, util, node, error, cmdutil, scmutil, match as matchmod
13 import config, util, node, error, cmdutil, scmutil, match as matchmod
14 import phases
14 import phases
15 import pathutil
15 import pathutil
16 import exchange
16 import exchange
17 hg = None
17 hg = None
18 propertycache = util.propertycache
18 propertycache = util.propertycache
19
19
20 nullstate = ('', '', 'empty')
20 nullstate = ('', '', 'empty')
21
21
22 def _expandedabspath(path):
22 def _expandedabspath(path):
23 '''
23 '''
24 get a path or url and if it is a path expand it and return an absolute path
24 get a path or url and if it is a path expand it and return an absolute path
25 '''
25 '''
26 expandedpath = util.urllocalpath(util.expandpath(path))
26 expandedpath = util.urllocalpath(util.expandpath(path))
27 u = util.url(expandedpath)
27 u = util.url(expandedpath)
28 if not u.scheme:
28 if not u.scheme:
29 path = util.normpath(os.path.abspath(u.path))
29 path = util.normpath(os.path.abspath(u.path))
30 return path
30 return path
31
31
32 def _getstorehashcachename(remotepath):
32 def _getstorehashcachename(remotepath):
33 '''get a unique filename for the store hash cache of a remote repository'''
33 '''get a unique filename for the store hash cache of a remote repository'''
34 return util.sha1(_expandedabspath(remotepath)).hexdigest()[0:12]
34 return util.sha1(_expandedabspath(remotepath)).hexdigest()[0:12]
35
35
36 class SubrepoAbort(error.Abort):
36 class SubrepoAbort(error.Abort):
37 """Exception class used to avoid handling a subrepo error more than once"""
37 """Exception class used to avoid handling a subrepo error more than once"""
38 def __init__(self, *args, **kw):
38 def __init__(self, *args, **kw):
39 error.Abort.__init__(self, *args, **kw)
39 error.Abort.__init__(self, *args, **kw)
40 self.subrepo = kw.get('subrepo')
40 self.subrepo = kw.get('subrepo')
41 self.cause = kw.get('cause')
41 self.cause = kw.get('cause')
42
42
43 def annotatesubrepoerror(func):
43 def annotatesubrepoerror(func):
44 def decoratedmethod(self, *args, **kargs):
44 def decoratedmethod(self, *args, **kargs):
45 try:
45 try:
46 res = func(self, *args, **kargs)
46 res = func(self, *args, **kargs)
47 except SubrepoAbort, ex:
47 except SubrepoAbort, ex:
48 # This exception has already been handled
48 # This exception has already been handled
49 raise ex
49 raise ex
50 except error.Abort, ex:
50 except error.Abort, ex:
51 subrepo = subrelpath(self)
51 subrepo = subrelpath(self)
52 errormsg = str(ex) + ' ' + _('(in subrepo %s)') % subrepo
52 errormsg = str(ex) + ' ' + _('(in subrepo %s)') % subrepo
53 # avoid handling this exception by raising a SubrepoAbort exception
53 # avoid handling this exception by raising a SubrepoAbort exception
54 raise SubrepoAbort(errormsg, hint=ex.hint, subrepo=subrepo,
54 raise SubrepoAbort(errormsg, hint=ex.hint, subrepo=subrepo,
55 cause=sys.exc_info())
55 cause=sys.exc_info())
56 return res
56 return res
57 return decoratedmethod
57 return decoratedmethod
58
58
59 def state(ctx, ui):
59 def state(ctx, ui):
60 """return a state dict, mapping subrepo paths configured in .hgsub
60 """return a state dict, mapping subrepo paths configured in .hgsub
61 to tuple: (source from .hgsub, revision from .hgsubstate, kind
61 to tuple: (source from .hgsub, revision from .hgsubstate, kind
62 (key in types dict))
62 (key in types dict))
63 """
63 """
64 p = config.config()
64 p = config.config()
65 def read(f, sections=None, remap=None):
65 def read(f, sections=None, remap=None):
66 if f in ctx:
66 if f in ctx:
67 try:
67 try:
68 data = ctx[f].data()
68 data = ctx[f].data()
69 except IOError, err:
69 except IOError, err:
70 if err.errno != errno.ENOENT:
70 if err.errno != errno.ENOENT:
71 raise
71 raise
72 # handle missing subrepo spec files as removed
72 # handle missing subrepo spec files as removed
73 ui.warn(_("warning: subrepo spec file %s not found\n") % f)
73 ui.warn(_("warning: subrepo spec file %s not found\n") % f)
74 return
74 return
75 p.parse(f, data, sections, remap, read)
75 p.parse(f, data, sections, remap, read)
76 else:
76 else:
77 raise util.Abort(_("subrepo spec file %s not found") % f)
77 raise util.Abort(_("subrepo spec file %s not found") % f)
78
78
79 if '.hgsub' in ctx:
79 if '.hgsub' in ctx:
80 read('.hgsub')
80 read('.hgsub')
81
81
82 for path, src in ui.configitems('subpaths'):
82 for path, src in ui.configitems('subpaths'):
83 p.set('subpaths', path, src, ui.configsource('subpaths', path))
83 p.set('subpaths', path, src, ui.configsource('subpaths', path))
84
84
85 rev = {}
85 rev = {}
86 if '.hgsubstate' in ctx:
86 if '.hgsubstate' in ctx:
87 try:
87 try:
88 for i, l in enumerate(ctx['.hgsubstate'].data().splitlines()):
88 for i, l in enumerate(ctx['.hgsubstate'].data().splitlines()):
89 l = l.lstrip()
89 l = l.lstrip()
90 if not l:
90 if not l:
91 continue
91 continue
92 try:
92 try:
93 revision, path = l.split(" ", 1)
93 revision, path = l.split(" ", 1)
94 except ValueError:
94 except ValueError:
95 raise util.Abort(_("invalid subrepository revision "
95 raise util.Abort(_("invalid subrepository revision "
96 "specifier in .hgsubstate line %d")
96 "specifier in .hgsubstate line %d")
97 % (i + 1))
97 % (i + 1))
98 rev[path] = revision
98 rev[path] = revision
99 except IOError, err:
99 except IOError, err:
100 if err.errno != errno.ENOENT:
100 if err.errno != errno.ENOENT:
101 raise
101 raise
102
102
103 def remap(src):
103 def remap(src):
104 for pattern, repl in p.items('subpaths'):
104 for pattern, repl in p.items('subpaths'):
105 # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub
105 # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub
106 # does a string decode.
106 # does a string decode.
107 repl = repl.encode('string-escape')
107 repl = repl.encode('string-escape')
108 # However, we still want to allow back references to go
108 # However, we still want to allow back references to go
109 # through unharmed, so we turn r'\\1' into r'\1'. Again,
109 # through unharmed, so we turn r'\\1' into r'\1'. Again,
110 # extra escapes are needed because re.sub string decodes.
110 # extra escapes are needed because re.sub string decodes.
111 repl = re.sub(r'\\\\([0-9]+)', r'\\\1', repl)
111 repl = re.sub(r'\\\\([0-9]+)', r'\\\1', repl)
112 try:
112 try:
113 src = re.sub(pattern, repl, src, 1)
113 src = re.sub(pattern, repl, src, 1)
114 except re.error, e:
114 except re.error, e:
115 raise util.Abort(_("bad subrepository pattern in %s: %s")
115 raise util.Abort(_("bad subrepository pattern in %s: %s")
116 % (p.source('subpaths', pattern), e))
116 % (p.source('subpaths', pattern), e))
117 return src
117 return src
118
118
119 state = {}
119 state = {}
120 for path, src in p[''].items():
120 for path, src in p[''].items():
121 kind = 'hg'
121 kind = 'hg'
122 if src.startswith('['):
122 if src.startswith('['):
123 if ']' not in src:
123 if ']' not in src:
124 raise util.Abort(_('missing ] in subrepo source'))
124 raise util.Abort(_('missing ] in subrepo source'))
125 kind, src = src.split(']', 1)
125 kind, src = src.split(']', 1)
126 kind = kind[1:]
126 kind = kind[1:]
127 src = src.lstrip() # strip any extra whitespace after ']'
127 src = src.lstrip() # strip any extra whitespace after ']'
128
128
129 if not util.url(src).isabs():
129 if not util.url(src).isabs():
130 parent = _abssource(ctx._repo, abort=False)
130 parent = _abssource(ctx._repo, abort=False)
131 if parent:
131 if parent:
132 parent = util.url(parent)
132 parent = util.url(parent)
133 parent.path = posixpath.join(parent.path or '', src)
133 parent.path = posixpath.join(parent.path or '', src)
134 parent.path = posixpath.normpath(parent.path)
134 parent.path = posixpath.normpath(parent.path)
135 joined = str(parent)
135 joined = str(parent)
136 # Remap the full joined path and use it if it changes,
136 # Remap the full joined path and use it if it changes,
137 # else remap the original source.
137 # else remap the original source.
138 remapped = remap(joined)
138 remapped = remap(joined)
139 if remapped == joined:
139 if remapped == joined:
140 src = remap(src)
140 src = remap(src)
141 else:
141 else:
142 src = remapped
142 src = remapped
143
143
144 src = remap(src)
144 src = remap(src)
145 state[util.pconvert(path)] = (src.strip(), rev.get(path, ''), kind)
145 state[util.pconvert(path)] = (src.strip(), rev.get(path, ''), kind)
146
146
147 return state
147 return state
148
148
149 def writestate(repo, state):
149 def writestate(repo, state):
150 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
150 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
151 lines = ['%s %s\n' % (state[s][1], s) for s in sorted(state)]
151 lines = ['%s %s\n' % (state[s][1], s) for s in sorted(state)]
152 repo.wwrite('.hgsubstate', ''.join(lines), '')
152 repo.wwrite('.hgsubstate', ''.join(lines), '')
153
153
154 def submerge(repo, wctx, mctx, actx, overwrite):
154 def submerge(repo, wctx, mctx, actx, overwrite):
155 """delegated from merge.applyupdates: merging of .hgsubstate file
155 """delegated from merge.applyupdates: merging of .hgsubstate file
156 in working context, merging context and ancestor context"""
156 in working context, merging context and ancestor context"""
157 if mctx == actx: # backwards?
157 if mctx == actx: # backwards?
158 actx = wctx.p1()
158 actx = wctx.p1()
159 s1 = wctx.substate
159 s1 = wctx.substate
160 s2 = mctx.substate
160 s2 = mctx.substate
161 sa = actx.substate
161 sa = actx.substate
162 sm = {}
162 sm = {}
163
163
164 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
164 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
165
165
166 def debug(s, msg, r=""):
166 def debug(s, msg, r=""):
167 if r:
167 if r:
168 r = "%s:%s:%s" % r
168 r = "%s:%s:%s" % r
169 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
169 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
170
170
171 for s, l in sorted(s1.iteritems()):
171 for s, l in sorted(s1.iteritems()):
172 a = sa.get(s, nullstate)
172 a = sa.get(s, nullstate)
173 ld = l # local state with possible dirty flag for compares
173 ld = l # local state with possible dirty flag for compares
174 if wctx.sub(s).dirty():
174 if wctx.sub(s).dirty():
175 ld = (l[0], l[1] + "+")
175 ld = (l[0], l[1] + "+")
176 if wctx == actx: # overwrite
176 if wctx == actx: # overwrite
177 a = ld
177 a = ld
178
178
179 if s in s2:
179 if s in s2:
180 r = s2[s]
180 r = s2[s]
181 if ld == r or r == a: # no change or local is newer
181 if ld == r or r == a: # no change or local is newer
182 sm[s] = l
182 sm[s] = l
183 continue
183 continue
184 elif ld == a: # other side changed
184 elif ld == a: # other side changed
185 debug(s, "other changed, get", r)
185 debug(s, "other changed, get", r)
186 wctx.sub(s).get(r, overwrite)
186 wctx.sub(s).get(r, overwrite)
187 sm[s] = r
187 sm[s] = r
188 elif ld[0] != r[0]: # sources differ
188 elif ld[0] != r[0]: # sources differ
189 if repo.ui.promptchoice(
189 if repo.ui.promptchoice(
190 _(' subrepository sources for %s differ\n'
190 _(' subrepository sources for %s differ\n'
191 'use (l)ocal source (%s) or (r)emote source (%s)?'
191 'use (l)ocal source (%s) or (r)emote source (%s)?'
192 '$$ &Local $$ &Remote') % (s, l[0], r[0]), 0):
192 '$$ &Local $$ &Remote') % (s, l[0], r[0]), 0):
193 debug(s, "prompt changed, get", r)
193 debug(s, "prompt changed, get", r)
194 wctx.sub(s).get(r, overwrite)
194 wctx.sub(s).get(r, overwrite)
195 sm[s] = r
195 sm[s] = r
196 elif ld[1] == a[1]: # local side is unchanged
196 elif ld[1] == a[1]: # local side is unchanged
197 debug(s, "other side changed, get", r)
197 debug(s, "other side changed, get", r)
198 wctx.sub(s).get(r, overwrite)
198 wctx.sub(s).get(r, overwrite)
199 sm[s] = r
199 sm[s] = r
200 else:
200 else:
201 debug(s, "both sides changed")
201 debug(s, "both sides changed")
202 srepo = wctx.sub(s)
202 srepo = wctx.sub(s)
203 option = repo.ui.promptchoice(
203 option = repo.ui.promptchoice(
204 _(' subrepository %s diverged (local revision: %s, '
204 _(' subrepository %s diverged (local revision: %s, '
205 'remote revision: %s)\n'
205 'remote revision: %s)\n'
206 '(M)erge, keep (l)ocal or keep (r)emote?'
206 '(M)erge, keep (l)ocal or keep (r)emote?'
207 '$$ &Merge $$ &Local $$ &Remote')
207 '$$ &Merge $$ &Local $$ &Remote')
208 % (s, srepo.shortid(l[1]), srepo.shortid(r[1])), 0)
208 % (s, srepo.shortid(l[1]), srepo.shortid(r[1])), 0)
209 if option == 0:
209 if option == 0:
210 wctx.sub(s).merge(r)
210 wctx.sub(s).merge(r)
211 sm[s] = l
211 sm[s] = l
212 debug(s, "merge with", r)
212 debug(s, "merge with", r)
213 elif option == 1:
213 elif option == 1:
214 sm[s] = l
214 sm[s] = l
215 debug(s, "keep local subrepo revision", l)
215 debug(s, "keep local subrepo revision", l)
216 else:
216 else:
217 wctx.sub(s).get(r, overwrite)
217 wctx.sub(s).get(r, overwrite)
218 sm[s] = r
218 sm[s] = r
219 debug(s, "get remote subrepo revision", r)
219 debug(s, "get remote subrepo revision", r)
220 elif ld == a: # remote removed, local unchanged
220 elif ld == a: # remote removed, local unchanged
221 debug(s, "remote removed, remove")
221 debug(s, "remote removed, remove")
222 wctx.sub(s).remove()
222 wctx.sub(s).remove()
223 elif a == nullstate: # not present in remote or ancestor
223 elif a == nullstate: # not present in remote or ancestor
224 debug(s, "local added, keep")
224 debug(s, "local added, keep")
225 sm[s] = l
225 sm[s] = l
226 continue
226 continue
227 else:
227 else:
228 if repo.ui.promptchoice(
228 if repo.ui.promptchoice(
229 _(' local changed subrepository %s which remote removed\n'
229 _(' local changed subrepository %s which remote removed\n'
230 'use (c)hanged version or (d)elete?'
230 'use (c)hanged version or (d)elete?'
231 '$$ &Changed $$ &Delete') % s, 0):
231 '$$ &Changed $$ &Delete') % s, 0):
232 debug(s, "prompt remove")
232 debug(s, "prompt remove")
233 wctx.sub(s).remove()
233 wctx.sub(s).remove()
234
234
235 for s, r in sorted(s2.items()):
235 for s, r in sorted(s2.items()):
236 if s in s1:
236 if s in s1:
237 continue
237 continue
238 elif s not in sa:
238 elif s not in sa:
239 debug(s, "remote added, get", r)
239 debug(s, "remote added, get", r)
240 mctx.sub(s).get(r)
240 mctx.sub(s).get(r)
241 sm[s] = r
241 sm[s] = r
242 elif r != sa[s]:
242 elif r != sa[s]:
243 if repo.ui.promptchoice(
243 if repo.ui.promptchoice(
244 _(' remote changed subrepository %s which local removed\n'
244 _(' remote changed subrepository %s which local removed\n'
245 'use (c)hanged version or (d)elete?'
245 'use (c)hanged version or (d)elete?'
246 '$$ &Changed $$ &Delete') % s, 0) == 0:
246 '$$ &Changed $$ &Delete') % s, 0) == 0:
247 debug(s, "prompt recreate", r)
247 debug(s, "prompt recreate", r)
248 wctx.sub(s).get(r)
248 wctx.sub(s).get(r)
249 sm[s] = r
249 sm[s] = r
250
250
251 # record merged .hgsubstate
251 # record merged .hgsubstate
252 writestate(repo, sm)
252 writestate(repo, sm)
253 return sm
253 return sm
254
254
255 def _updateprompt(ui, sub, dirty, local, remote):
255 def _updateprompt(ui, sub, dirty, local, remote):
256 if dirty:
256 if dirty:
257 msg = (_(' subrepository sources for %s differ\n'
257 msg = (_(' subrepository sources for %s differ\n'
258 'use (l)ocal source (%s) or (r)emote source (%s)?'
258 'use (l)ocal source (%s) or (r)emote source (%s)?'
259 '$$ &Local $$ &Remote')
259 '$$ &Local $$ &Remote')
260 % (subrelpath(sub), local, remote))
260 % (subrelpath(sub), local, remote))
261 else:
261 else:
262 msg = (_(' subrepository sources for %s differ (in checked out '
262 msg = (_(' subrepository sources for %s differ (in checked out '
263 'version)\n'
263 'version)\n'
264 'use (l)ocal source (%s) or (r)emote source (%s)?'
264 'use (l)ocal source (%s) or (r)emote source (%s)?'
265 '$$ &Local $$ &Remote')
265 '$$ &Local $$ &Remote')
266 % (subrelpath(sub), local, remote))
266 % (subrelpath(sub), local, remote))
267 return ui.promptchoice(msg, 0)
267 return ui.promptchoice(msg, 0)
268
268
269 def reporelpath(repo):
269 def reporelpath(repo):
270 """return path to this (sub)repo as seen from outermost repo"""
270 """return path to this (sub)repo as seen from outermost repo"""
271 parent = repo
271 parent = repo
272 while util.safehasattr(parent, '_subparent'):
272 while util.safehasattr(parent, '_subparent'):
273 parent = parent._subparent
273 parent = parent._subparent
274 return repo.root[len(pathutil.normasprefix(parent.root)):]
274 return repo.root[len(pathutil.normasprefix(parent.root)):]
275
275
276 def subrelpath(sub):
276 def subrelpath(sub):
277 """return path to this subrepo as seen from outermost repo"""
277 """return path to this subrepo as seen from outermost repo"""
278 if util.safehasattr(sub, '_relpath'):
278 if util.safehasattr(sub, '_relpath'):
279 return sub._relpath
279 return sub._relpath
280 if not util.safehasattr(sub, '_repo'):
280 if not util.safehasattr(sub, '_repo'):
281 return sub._path
281 return sub._path
282 return reporelpath(sub._repo)
282 return reporelpath(sub._repo)
283
283
284 def _abssource(repo, push=False, abort=True):
284 def _abssource(repo, push=False, abort=True):
285 """return pull/push path of repo - either based on parent repo .hgsub info
285 """return pull/push path of repo - either based on parent repo .hgsub info
286 or on the top repo config. Abort or return None if no source found."""
286 or on the top repo config. Abort or return None if no source found."""
287 if util.safehasattr(repo, '_subparent'):
287 if util.safehasattr(repo, '_subparent'):
288 source = util.url(repo._subsource)
288 source = util.url(repo._subsource)
289 if source.isabs():
289 if source.isabs():
290 return str(source)
290 return str(source)
291 source.path = posixpath.normpath(source.path)
291 source.path = posixpath.normpath(source.path)
292 parent = _abssource(repo._subparent, push, abort=False)
292 parent = _abssource(repo._subparent, push, abort=False)
293 if parent:
293 if parent:
294 parent = util.url(util.pconvert(parent))
294 parent = util.url(util.pconvert(parent))
295 parent.path = posixpath.join(parent.path or '', source.path)
295 parent.path = posixpath.join(parent.path or '', source.path)
296 parent.path = posixpath.normpath(parent.path)
296 parent.path = posixpath.normpath(parent.path)
297 return str(parent)
297 return str(parent)
298 else: # recursion reached top repo
298 else: # recursion reached top repo
299 if util.safehasattr(repo, '_subtoppath'):
299 if util.safehasattr(repo, '_subtoppath'):
300 return repo._subtoppath
300 return repo._subtoppath
301 if push and repo.ui.config('paths', 'default-push'):
301 if push and repo.ui.config('paths', 'default-push'):
302 return repo.ui.config('paths', 'default-push')
302 return repo.ui.config('paths', 'default-push')
303 if repo.ui.config('paths', 'default'):
303 if repo.ui.config('paths', 'default'):
304 return repo.ui.config('paths', 'default')
304 return repo.ui.config('paths', 'default')
305 if repo.shared():
305 if repo.shared():
306 # chop off the .hg component to get the default path form
306 # chop off the .hg component to get the default path form
307 return os.path.dirname(repo.sharedpath)
307 return os.path.dirname(repo.sharedpath)
308 if abort:
308 if abort:
309 raise util.Abort(_("default path for subrepository not found"))
309 raise util.Abort(_("default path for subrepository not found"))
310
310
311 def _sanitize(ui, path, ignore):
311 def _sanitize(ui, path, ignore):
312 for dirname, dirs, names in os.walk(path):
312 for dirname, dirs, names in os.walk(path):
313 for i, d in enumerate(dirs):
313 for i, d in enumerate(dirs):
314 if d.lower() == ignore:
314 if d.lower() == ignore:
315 del dirs[i]
315 del dirs[i]
316 break
316 break
317 if os.path.basename(dirname).lower() != '.hg':
317 if os.path.basename(dirname).lower() != '.hg':
318 continue
318 continue
319 for f in names:
319 for f in names:
320 if f.lower() == 'hgrc':
320 if f.lower() == 'hgrc':
321 ui.warn(_("warning: removing potentially hostile 'hgrc' "
321 ui.warn(_("warning: removing potentially hostile 'hgrc' "
322 "in '%s'\n") % dirname)
322 "in '%s'\n") % dirname)
323 os.unlink(os.path.join(dirname, f))
323 os.unlink(os.path.join(dirname, f))
324
324
325 def subrepo(ctx, path):
325 def subrepo(ctx, path):
326 """return instance of the right subrepo class for subrepo in path"""
326 """return instance of the right subrepo class for subrepo in path"""
327 # subrepo inherently violates our import layering rules
327 # subrepo inherently violates our import layering rules
328 # because it wants to make repo objects from deep inside the stack
328 # because it wants to make repo objects from deep inside the stack
329 # so we manually delay the circular imports to not break
329 # so we manually delay the circular imports to not break
330 # scripts that don't use our demand-loading
330 # scripts that don't use our demand-loading
331 global hg
331 global hg
332 import hg as h
332 import hg as h
333 hg = h
333 hg = h
334
334
335 pathutil.pathauditor(ctx._repo.root)(path)
335 pathutil.pathauditor(ctx._repo.root)(path)
336 state = ctx.substate[path]
336 state = ctx.substate[path]
337 if state[2] not in types:
337 if state[2] not in types:
338 raise util.Abort(_('unknown subrepo type %s') % state[2])
338 raise util.Abort(_('unknown subrepo type %s') % state[2])
339 return types[state[2]](ctx, path, state[:2])
339 return types[state[2]](ctx, path, state[:2])
340
340
341 def newcommitphase(ui, ctx):
341 def newcommitphase(ui, ctx):
342 commitphase = phases.newcommitphase(ui)
342 commitphase = phases.newcommitphase(ui)
343 substate = getattr(ctx, "substate", None)
343 substate = getattr(ctx, "substate", None)
344 if not substate:
344 if not substate:
345 return commitphase
345 return commitphase
346 check = ui.config('phases', 'checksubrepos', 'follow')
346 check = ui.config('phases', 'checksubrepos', 'follow')
347 if check not in ('ignore', 'follow', 'abort'):
347 if check not in ('ignore', 'follow', 'abort'):
348 raise util.Abort(_('invalid phases.checksubrepos configuration: %s')
348 raise util.Abort(_('invalid phases.checksubrepos configuration: %s')
349 % (check))
349 % (check))
350 if check == 'ignore':
350 if check == 'ignore':
351 return commitphase
351 return commitphase
352 maxphase = phases.public
352 maxphase = phases.public
353 maxsub = None
353 maxsub = None
354 for s in sorted(substate):
354 for s in sorted(substate):
355 sub = ctx.sub(s)
355 sub = ctx.sub(s)
356 subphase = sub.phase(substate[s][1])
356 subphase = sub.phase(substate[s][1])
357 if maxphase < subphase:
357 if maxphase < subphase:
358 maxphase = subphase
358 maxphase = subphase
359 maxsub = s
359 maxsub = s
360 if commitphase < maxphase:
360 if commitphase < maxphase:
361 if check == 'abort':
361 if check == 'abort':
362 raise util.Abort(_("can't commit in %s phase"
362 raise util.Abort(_("can't commit in %s phase"
363 " conflicting %s from subrepository %s") %
363 " conflicting %s from subrepository %s") %
364 (phases.phasenames[commitphase],
364 (phases.phasenames[commitphase],
365 phases.phasenames[maxphase], maxsub))
365 phases.phasenames[maxphase], maxsub))
366 ui.warn(_("warning: changes are committed in"
366 ui.warn(_("warning: changes are committed in"
367 " %s phase from subrepository %s\n") %
367 " %s phase from subrepository %s\n") %
368 (phases.phasenames[maxphase], maxsub))
368 (phases.phasenames[maxphase], maxsub))
369 return maxphase
369 return maxphase
370 return commitphase
370 return commitphase
371
371
372 # subrepo classes need to implement the following abstract class:
372 # subrepo classes need to implement the following abstract class:
373
373
374 class abstractsubrepo(object):
374 class abstractsubrepo(object):
375
375
376 def __init__(self, ui):
376 def __init__(self, ui):
377 self.ui = ui
377 self.ui = ui
378
378
379 def storeclean(self, path):
379 def storeclean(self, path):
380 """
380 """
381 returns true if the repository has not changed since it was last
381 returns true if the repository has not changed since it was last
382 cloned from or pushed to a given repository.
382 cloned from or pushed to a given repository.
383 """
383 """
384 return False
384 return False
385
385
386 def dirty(self, ignoreupdate=False):
386 def dirty(self, ignoreupdate=False):
387 """returns true if the dirstate of the subrepo is dirty or does not
387 """returns true if the dirstate of the subrepo is dirty or does not
388 match current stored state. If ignoreupdate is true, only check
388 match current stored state. If ignoreupdate is true, only check
389 whether the subrepo has uncommitted changes in its dirstate.
389 whether the subrepo has uncommitted changes in its dirstate.
390 """
390 """
391 raise NotImplementedError
391 raise NotImplementedError
392
392
393 def basestate(self):
393 def basestate(self):
394 """current working directory base state, disregarding .hgsubstate
394 """current working directory base state, disregarding .hgsubstate
395 state and working directory modifications"""
395 state and working directory modifications"""
396 raise NotImplementedError
396 raise NotImplementedError
397
397
398 def checknested(self, path):
398 def checknested(self, path):
399 """check if path is a subrepository within this repository"""
399 """check if path is a subrepository within this repository"""
400 return False
400 return False
401
401
402 def commit(self, text, user, date):
402 def commit(self, text, user, date):
403 """commit the current changes to the subrepo with the given
403 """commit the current changes to the subrepo with the given
404 log message. Use given user and date if possible. Return the
404 log message. Use given user and date if possible. Return the
405 new state of the subrepo.
405 new state of the subrepo.
406 """
406 """
407 raise NotImplementedError
407 raise NotImplementedError
408
408
409 def phase(self, state):
409 def phase(self, state):
410 """returns phase of specified state in the subrepository.
410 """returns phase of specified state in the subrepository.
411 """
411 """
412 return phases.public
412 return phases.public
413
413
414 def remove(self):
414 def remove(self):
415 """remove the subrepo
415 """remove the subrepo
416
416
417 (should verify the dirstate is not dirty first)
417 (should verify the dirstate is not dirty first)
418 """
418 """
419 raise NotImplementedError
419 raise NotImplementedError
420
420
421 def get(self, state, overwrite=False):
421 def get(self, state, overwrite=False):
422 """run whatever commands are needed to put the subrepo into
422 """run whatever commands are needed to put the subrepo into
423 this state
423 this state
424 """
424 """
425 raise NotImplementedError
425 raise NotImplementedError
426
426
427 def merge(self, state):
427 def merge(self, state):
428 """merge currently-saved state with the new state."""
428 """merge currently-saved state with the new state."""
429 raise NotImplementedError
429 raise NotImplementedError
430
430
431 def push(self, opts):
431 def push(self, opts):
432 """perform whatever action is analogous to 'hg push'
432 """perform whatever action is analogous to 'hg push'
433
433
434 This may be a no-op on some systems.
434 This may be a no-op on some systems.
435 """
435 """
436 raise NotImplementedError
436 raise NotImplementedError
437
437
438 def add(self, ui, match, dryrun, listsubrepos, prefix, explicitonly):
438 def add(self, ui, match, dryrun, listsubrepos, prefix, explicitonly):
439 return []
439 return []
440
440
441 def addremove(self, matcher, prefix, opts, dry_run, similarity):
441 def addremove(self, matcher, prefix, opts, dry_run, similarity):
442 self.ui.warn("%s: %s" % (prefix, _("addremove is not supported")))
442 self.ui.warn("%s: %s" % (prefix, _("addremove is not supported")))
443 return 1
443 return 1
444
444
445 def cat(self, match, prefix, **opts):
445 def cat(self, match, prefix, **opts):
446 return 1
446 return 1
447
447
448 def status(self, rev2, **opts):
448 def status(self, rev2, **opts):
449 return scmutil.status([], [], [], [], [], [], [])
449 return scmutil.status([], [], [], [], [], [], [])
450
450
451 def diff(self, ui, diffopts, node2, match, prefix, **opts):
451 def diff(self, ui, diffopts, node2, match, prefix, **opts):
452 pass
452 pass
453
453
454 def outgoing(self, ui, dest, opts):
454 def outgoing(self, ui, dest, opts):
455 return 1
455 return 1
456
456
457 def incoming(self, ui, source, opts):
457 def incoming(self, ui, source, opts):
458 return 1
458 return 1
459
459
460 def files(self):
460 def files(self):
461 """return filename iterator"""
461 """return filename iterator"""
462 raise NotImplementedError
462 raise NotImplementedError
463
463
464 def filedata(self, name):
464 def filedata(self, name):
465 """return file data"""
465 """return file data"""
466 raise NotImplementedError
466 raise NotImplementedError
467
467
468 def fileflags(self, name):
468 def fileflags(self, name):
469 """return file flags"""
469 """return file flags"""
470 return ''
470 return ''
471
471
472 def archive(self, archiver, prefix, match=None):
472 def archive(self, archiver, prefix, match=None):
473 if match is not None:
473 if match is not None:
474 files = [f for f in self.files() if match(f)]
474 files = [f for f in self.files() if match(f)]
475 else:
475 else:
476 files = self.files()
476 files = self.files()
477 total = len(files)
477 total = len(files)
478 relpath = subrelpath(self)
478 relpath = subrelpath(self)
479 self.ui.progress(_('archiving (%s)') % relpath, 0,
479 self.ui.progress(_('archiving (%s)') % relpath, 0,
480 unit=_('files'), total=total)
480 unit=_('files'), total=total)
481 for i, name in enumerate(files):
481 for i, name in enumerate(files):
482 flags = self.fileflags(name)
482 flags = self.fileflags(name)
483 mode = 'x' in flags and 0755 or 0644
483 mode = 'x' in flags and 0755 or 0644
484 symlink = 'l' in flags
484 symlink = 'l' in flags
485 archiver.addfile(os.path.join(prefix, self._path, name),
485 archiver.addfile(os.path.join(prefix, self._path, name),
486 mode, symlink, self.filedata(name))
486 mode, symlink, self.filedata(name))
487 self.ui.progress(_('archiving (%s)') % relpath, i + 1,
487 self.ui.progress(_('archiving (%s)') % relpath, i + 1,
488 unit=_('files'), total=total)
488 unit=_('files'), total=total)
489 self.ui.progress(_('archiving (%s)') % relpath, None)
489 self.ui.progress(_('archiving (%s)') % relpath, None)
490 return total
490 return total
491
491
492 def walk(self, match):
492 def walk(self, match):
493 '''
493 '''
494 walk recursively through the directory tree, finding all files
494 walk recursively through the directory tree, finding all files
495 matched by the match function
495 matched by the match function
496 '''
496 '''
497 pass
497 pass
498
498
499 def forget(self, match, prefix):
499 def forget(self, match, prefix):
500 return ([], [])
500 return ([], [])
501
501
502 def removefiles(self, matcher, prefix, after, force, subrepos):
502 def removefiles(self, matcher, prefix, after, force, subrepos):
503 """remove the matched files from the subrepository and the filesystem,
503 """remove the matched files from the subrepository and the filesystem,
504 possibly by force and/or after the file has been removed from the
504 possibly by force and/or after the file has been removed from the
505 filesystem. Return 0 on success, 1 on any warning.
505 filesystem. Return 0 on success, 1 on any warning.
506 """
506 """
507 return 1
507 return 1
508
508
509 def revert(self, substate, *pats, **opts):
509 def revert(self, substate, *pats, **opts):
510 self.ui.warn('%s: reverting %s subrepos is unsupported\n' \
510 self.ui.warn('%s: reverting %s subrepos is unsupported\n' \
511 % (substate[0], substate[2]))
511 % (substate[0], substate[2]))
512 return []
512 return []
513
513
514 def shortid(self, revid):
514 def shortid(self, revid):
515 return revid
515 return revid
516
516
517 class hgsubrepo(abstractsubrepo):
517 class hgsubrepo(abstractsubrepo):
518 def __init__(self, ctx, path, state):
518 def __init__(self, ctx, path, state):
519 super(hgsubrepo, self).__init__(ctx._repo.ui)
519 super(hgsubrepo, self).__init__(ctx._repo.ui)
520 self._path = path
520 self._path = path
521 self._state = state
521 self._state = state
522 r = ctx._repo
522 r = ctx._repo
523 root = r.wjoin(path)
523 root = r.wjoin(path)
524 create = not r.wvfs.exists('%s/.hg' % path)
524 create = not r.wvfs.exists('%s/.hg' % path)
525 self._repo = hg.repository(r.baseui, root, create=create)
525 self._repo = hg.repository(r.baseui, root, create=create)
526 self.ui = self._repo.ui
526 self.ui = self._repo.ui
527 for s, k in [('ui', 'commitsubrepos')]:
527 for s, k in [('ui', 'commitsubrepos')]:
528 v = r.ui.config(s, k)
528 v = r.ui.config(s, k)
529 if v:
529 if v:
530 self.ui.setconfig(s, k, v, 'subrepo')
530 self.ui.setconfig(s, k, v, 'subrepo')
531 self.ui.setconfig('ui', '_usedassubrepo', 'True', 'subrepo')
531 self.ui.setconfig('ui', '_usedassubrepo', 'True', 'subrepo')
532 self._initrepo(r, state[0], create)
532 self._initrepo(r, state[0], create)
533
533
534 def storeclean(self, path):
534 def storeclean(self, path):
535 lock = self._repo.lock()
535 lock = self._repo.lock()
536 try:
536 try:
537 return self._storeclean(path)
537 return self._storeclean(path)
538 finally:
538 finally:
539 lock.release()
539 lock.release()
540
540
541 def _storeclean(self, path):
541 def _storeclean(self, path):
542 clean = True
542 clean = True
543 itercache = self._calcstorehash(path)
543 itercache = self._calcstorehash(path)
544 try:
544 try:
545 for filehash in self._readstorehashcache(path):
545 for filehash in self._readstorehashcache(path):
546 if filehash != itercache.next():
546 if filehash != itercache.next():
547 clean = False
547 clean = False
548 break
548 break
549 except StopIteration:
549 except StopIteration:
550 # the cached and current pull states have a different size
550 # the cached and current pull states have a different size
551 clean = False
551 clean = False
552 if clean:
552 if clean:
553 try:
553 try:
554 itercache.next()
554 itercache.next()
555 # the cached and current pull states have a different size
555 # the cached and current pull states have a different size
556 clean = False
556 clean = False
557 except StopIteration:
557 except StopIteration:
558 pass
558 pass
559 return clean
559 return clean
560
560
561 def _calcstorehash(self, remotepath):
561 def _calcstorehash(self, remotepath):
562 '''calculate a unique "store hash"
562 '''calculate a unique "store hash"
563
563
564 This method is used to to detect when there are changes that may
564 This method is used to to detect when there are changes that may
565 require a push to a given remote path.'''
565 require a push to a given remote path.'''
566 # sort the files that will be hashed in increasing (likely) file size
566 # sort the files that will be hashed in increasing (likely) file size
567 filelist = ('bookmarks', 'store/phaseroots', 'store/00changelog.i')
567 filelist = ('bookmarks', 'store/phaseroots', 'store/00changelog.i')
568 yield '# %s\n' % _expandedabspath(remotepath)
568 yield '# %s\n' % _expandedabspath(remotepath)
569 vfs = self._repo.vfs
569 vfs = self._repo.vfs
570 for relname in filelist:
570 for relname in filelist:
571 filehash = util.sha1(vfs.tryread(relname)).hexdigest()
571 filehash = util.sha1(vfs.tryread(relname)).hexdigest()
572 yield '%s = %s\n' % (relname, filehash)
572 yield '%s = %s\n' % (relname, filehash)
573
573
574 @propertycache
574 @propertycache
575 def _cachestorehashvfs(self):
575 def _cachestorehashvfs(self):
576 return scmutil.vfs(self._repo.join('cache/storehash'))
576 return scmutil.vfs(self._repo.join('cache/storehash'))
577
577
578 def _readstorehashcache(self, remotepath):
578 def _readstorehashcache(self, remotepath):
579 '''read the store hash cache for a given remote repository'''
579 '''read the store hash cache for a given remote repository'''
580 cachefile = _getstorehashcachename(remotepath)
580 cachefile = _getstorehashcachename(remotepath)
581 return self._cachestorehashvfs.tryreadlines(cachefile, 'r')
581 return self._cachestorehashvfs.tryreadlines(cachefile, 'r')
582
582
583 def _cachestorehash(self, remotepath):
583 def _cachestorehash(self, remotepath):
584 '''cache the current store hash
584 '''cache the current store hash
585
585
586 Each remote repo requires its own store hash cache, because a subrepo
586 Each remote repo requires its own store hash cache, because a subrepo
587 store may be "clean" versus a given remote repo, but not versus another
587 store may be "clean" versus a given remote repo, but not versus another
588 '''
588 '''
589 cachefile = _getstorehashcachename(remotepath)
589 cachefile = _getstorehashcachename(remotepath)
590 lock = self._repo.lock()
590 lock = self._repo.lock()
591 try:
591 try:
592 storehash = list(self._calcstorehash(remotepath))
592 storehash = list(self._calcstorehash(remotepath))
593 vfs = self._cachestorehashvfs
593 vfs = self._cachestorehashvfs
594 vfs.writelines(cachefile, storehash, mode='w', notindexed=True)
594 vfs.writelines(cachefile, storehash, mode='w', notindexed=True)
595 finally:
595 finally:
596 lock.release()
596 lock.release()
597
597
598 @annotatesubrepoerror
598 @annotatesubrepoerror
599 def _initrepo(self, parentrepo, source, create):
599 def _initrepo(self, parentrepo, source, create):
600 self._repo._subparent = parentrepo
600 self._repo._subparent = parentrepo
601 self._repo._subsource = source
601 self._repo._subsource = source
602
602
603 if create:
603 if create:
604 lines = ['[paths]\n']
604 lines = ['[paths]\n']
605
605
606 def addpathconfig(key, value):
606 def addpathconfig(key, value):
607 if value:
607 if value:
608 lines.append('%s = %s\n' % (key, value))
608 lines.append('%s = %s\n' % (key, value))
609 self.ui.setconfig('paths', key, value, 'subrepo')
609 self.ui.setconfig('paths', key, value, 'subrepo')
610
610
611 defpath = _abssource(self._repo, abort=False)
611 defpath = _abssource(self._repo, abort=False)
612 defpushpath = _abssource(self._repo, True, abort=False)
612 defpushpath = _abssource(self._repo, True, abort=False)
613 addpathconfig('default', defpath)
613 addpathconfig('default', defpath)
614 if defpath != defpushpath:
614 if defpath != defpushpath:
615 addpathconfig('default-push', defpushpath)
615 addpathconfig('default-push', defpushpath)
616
616
617 fp = self._repo.vfs("hgrc", "w", text=True)
617 fp = self._repo.vfs("hgrc", "w", text=True)
618 try:
618 try:
619 fp.write(''.join(lines))
619 fp.write(''.join(lines))
620 finally:
620 finally:
621 fp.close()
621 fp.close()
622
622
623 @annotatesubrepoerror
623 @annotatesubrepoerror
624 def add(self, ui, match, dryrun, listsubrepos, prefix, explicitonly):
624 def add(self, ui, match, prefix, explicitonly, **opts):
625 return cmdutil.add(ui, self._repo, match, dryrun, listsubrepos,
625 return cmdutil.add(ui, self._repo, match,
626 os.path.join(prefix, self._path), explicitonly)
626 os.path.join(prefix, self._path), explicitonly,
627 **opts)
627
628
628 def addremove(self, m, prefix, opts, dry_run, similarity):
629 def addremove(self, m, prefix, opts, dry_run, similarity):
629 # In the same way as sub directories are processed, once in a subrepo,
630 # In the same way as sub directories are processed, once in a subrepo,
630 # always entry any of its subrepos. Don't corrupt the options that will
631 # always entry any of its subrepos. Don't corrupt the options that will
631 # be used to process sibling subrepos however.
632 # be used to process sibling subrepos however.
632 opts = copy.copy(opts)
633 opts = copy.copy(opts)
633 opts['subrepos'] = True
634 opts['subrepos'] = True
634 return scmutil.addremove(self._repo, m,
635 return scmutil.addremove(self._repo, m,
635 os.path.join(prefix, self._path), opts,
636 os.path.join(prefix, self._path), opts,
636 dry_run, similarity)
637 dry_run, similarity)
637
638
638 @annotatesubrepoerror
639 @annotatesubrepoerror
639 def cat(self, match, prefix, **opts):
640 def cat(self, match, prefix, **opts):
640 rev = self._state[1]
641 rev = self._state[1]
641 ctx = self._repo[rev]
642 ctx = self._repo[rev]
642 return cmdutil.cat(self.ui, self._repo, ctx, match, prefix, **opts)
643 return cmdutil.cat(self.ui, self._repo, ctx, match, prefix, **opts)
643
644
644 @annotatesubrepoerror
645 @annotatesubrepoerror
645 def status(self, rev2, **opts):
646 def status(self, rev2, **opts):
646 try:
647 try:
647 rev1 = self._state[1]
648 rev1 = self._state[1]
648 ctx1 = self._repo[rev1]
649 ctx1 = self._repo[rev1]
649 ctx2 = self._repo[rev2]
650 ctx2 = self._repo[rev2]
650 return self._repo.status(ctx1, ctx2, **opts)
651 return self._repo.status(ctx1, ctx2, **opts)
651 except error.RepoLookupError, inst:
652 except error.RepoLookupError, inst:
652 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
653 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
653 % (inst, subrelpath(self)))
654 % (inst, subrelpath(self)))
654 return scmutil.status([], [], [], [], [], [], [])
655 return scmutil.status([], [], [], [], [], [], [])
655
656
656 @annotatesubrepoerror
657 @annotatesubrepoerror
657 def diff(self, ui, diffopts, node2, match, prefix, **opts):
658 def diff(self, ui, diffopts, node2, match, prefix, **opts):
658 try:
659 try:
659 node1 = node.bin(self._state[1])
660 node1 = node.bin(self._state[1])
660 # We currently expect node2 to come from substate and be
661 # We currently expect node2 to come from substate and be
661 # in hex format
662 # in hex format
662 if node2 is not None:
663 if node2 is not None:
663 node2 = node.bin(node2)
664 node2 = node.bin(node2)
664 cmdutil.diffordiffstat(ui, self._repo, diffopts,
665 cmdutil.diffordiffstat(ui, self._repo, diffopts,
665 node1, node2, match,
666 node1, node2, match,
666 prefix=posixpath.join(prefix, self._path),
667 prefix=posixpath.join(prefix, self._path),
667 listsubrepos=True, **opts)
668 listsubrepos=True, **opts)
668 except error.RepoLookupError, inst:
669 except error.RepoLookupError, inst:
669 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
670 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
670 % (inst, subrelpath(self)))
671 % (inst, subrelpath(self)))
671
672
672 @annotatesubrepoerror
673 @annotatesubrepoerror
673 def archive(self, archiver, prefix, match=None):
674 def archive(self, archiver, prefix, match=None):
674 self._get(self._state + ('hg',))
675 self._get(self._state + ('hg',))
675 total = abstractsubrepo.archive(self, archiver, prefix, match)
676 total = abstractsubrepo.archive(self, archiver, prefix, match)
676 rev = self._state[1]
677 rev = self._state[1]
677 ctx = self._repo[rev]
678 ctx = self._repo[rev]
678 for subpath in ctx.substate:
679 for subpath in ctx.substate:
679 s = subrepo(ctx, subpath)
680 s = subrepo(ctx, subpath)
680 submatch = matchmod.narrowmatcher(subpath, match)
681 submatch = matchmod.narrowmatcher(subpath, match)
681 total += s.archive(
682 total += s.archive(
682 archiver, os.path.join(prefix, self._path), submatch)
683 archiver, os.path.join(prefix, self._path), submatch)
683 return total
684 return total
684
685
685 @annotatesubrepoerror
686 @annotatesubrepoerror
686 def dirty(self, ignoreupdate=False):
687 def dirty(self, ignoreupdate=False):
687 r = self._state[1]
688 r = self._state[1]
688 if r == '' and not ignoreupdate: # no state recorded
689 if r == '' and not ignoreupdate: # no state recorded
689 return True
690 return True
690 w = self._repo[None]
691 w = self._repo[None]
691 if r != w.p1().hex() and not ignoreupdate:
692 if r != w.p1().hex() and not ignoreupdate:
692 # different version checked out
693 # different version checked out
693 return True
694 return True
694 return w.dirty() # working directory changed
695 return w.dirty() # working directory changed
695
696
696 def basestate(self):
697 def basestate(self):
697 return self._repo['.'].hex()
698 return self._repo['.'].hex()
698
699
699 def checknested(self, path):
700 def checknested(self, path):
700 return self._repo._checknested(self._repo.wjoin(path))
701 return self._repo._checknested(self._repo.wjoin(path))
701
702
702 @annotatesubrepoerror
703 @annotatesubrepoerror
703 def commit(self, text, user, date):
704 def commit(self, text, user, date):
704 # don't bother committing in the subrepo if it's only been
705 # don't bother committing in the subrepo if it's only been
705 # updated
706 # updated
706 if not self.dirty(True):
707 if not self.dirty(True):
707 return self._repo['.'].hex()
708 return self._repo['.'].hex()
708 self.ui.debug("committing subrepo %s\n" % subrelpath(self))
709 self.ui.debug("committing subrepo %s\n" % subrelpath(self))
709 n = self._repo.commit(text, user, date)
710 n = self._repo.commit(text, user, date)
710 if not n:
711 if not n:
711 return self._repo['.'].hex() # different version checked out
712 return self._repo['.'].hex() # different version checked out
712 return node.hex(n)
713 return node.hex(n)
713
714
714 @annotatesubrepoerror
715 @annotatesubrepoerror
715 def phase(self, state):
716 def phase(self, state):
716 return self._repo[state].phase()
717 return self._repo[state].phase()
717
718
718 @annotatesubrepoerror
719 @annotatesubrepoerror
719 def remove(self):
720 def remove(self):
720 # we can't fully delete the repository as it may contain
721 # we can't fully delete the repository as it may contain
721 # local-only history
722 # local-only history
722 self.ui.note(_('removing subrepo %s\n') % subrelpath(self))
723 self.ui.note(_('removing subrepo %s\n') % subrelpath(self))
723 hg.clean(self._repo, node.nullid, False)
724 hg.clean(self._repo, node.nullid, False)
724
725
725 def _get(self, state):
726 def _get(self, state):
726 source, revision, kind = state
727 source, revision, kind = state
727 if revision in self._repo.unfiltered():
728 if revision in self._repo.unfiltered():
728 return True
729 return True
729 self._repo._subsource = source
730 self._repo._subsource = source
730 srcurl = _abssource(self._repo)
731 srcurl = _abssource(self._repo)
731 other = hg.peer(self._repo, {}, srcurl)
732 other = hg.peer(self._repo, {}, srcurl)
732 if len(self._repo) == 0:
733 if len(self._repo) == 0:
733 self.ui.status(_('cloning subrepo %s from %s\n')
734 self.ui.status(_('cloning subrepo %s from %s\n')
734 % (subrelpath(self), srcurl))
735 % (subrelpath(self), srcurl))
735 parentrepo = self._repo._subparent
736 parentrepo = self._repo._subparent
736 shutil.rmtree(self._repo.path)
737 shutil.rmtree(self._repo.path)
737 other, cloned = hg.clone(self._repo._subparent.baseui, {},
738 other, cloned = hg.clone(self._repo._subparent.baseui, {},
738 other, self._repo.root,
739 other, self._repo.root,
739 update=False)
740 update=False)
740 self._repo = cloned.local()
741 self._repo = cloned.local()
741 self._initrepo(parentrepo, source, create=True)
742 self._initrepo(parentrepo, source, create=True)
742 self._cachestorehash(srcurl)
743 self._cachestorehash(srcurl)
743 else:
744 else:
744 self.ui.status(_('pulling subrepo %s from %s\n')
745 self.ui.status(_('pulling subrepo %s from %s\n')
745 % (subrelpath(self), srcurl))
746 % (subrelpath(self), srcurl))
746 cleansub = self.storeclean(srcurl)
747 cleansub = self.storeclean(srcurl)
747 exchange.pull(self._repo, other)
748 exchange.pull(self._repo, other)
748 if cleansub:
749 if cleansub:
749 # keep the repo clean after pull
750 # keep the repo clean after pull
750 self._cachestorehash(srcurl)
751 self._cachestorehash(srcurl)
751 return False
752 return False
752
753
753 @annotatesubrepoerror
754 @annotatesubrepoerror
754 def get(self, state, overwrite=False):
755 def get(self, state, overwrite=False):
755 inrepo = self._get(state)
756 inrepo = self._get(state)
756 source, revision, kind = state
757 source, revision, kind = state
757 repo = self._repo
758 repo = self._repo
758 repo.ui.debug("getting subrepo %s\n" % self._path)
759 repo.ui.debug("getting subrepo %s\n" % self._path)
759 if inrepo:
760 if inrepo:
760 urepo = repo.unfiltered()
761 urepo = repo.unfiltered()
761 ctx = urepo[revision]
762 ctx = urepo[revision]
762 if ctx.hidden():
763 if ctx.hidden():
763 urepo.ui.warn(
764 urepo.ui.warn(
764 _('revision %s in subrepo %s is hidden\n') \
765 _('revision %s in subrepo %s is hidden\n') \
765 % (revision[0:12], self._path))
766 % (revision[0:12], self._path))
766 repo = urepo
767 repo = urepo
767 hg.updaterepo(repo, revision, overwrite)
768 hg.updaterepo(repo, revision, overwrite)
768
769
769 @annotatesubrepoerror
770 @annotatesubrepoerror
770 def merge(self, state):
771 def merge(self, state):
771 self._get(state)
772 self._get(state)
772 cur = self._repo['.']
773 cur = self._repo['.']
773 dst = self._repo[state[1]]
774 dst = self._repo[state[1]]
774 anc = dst.ancestor(cur)
775 anc = dst.ancestor(cur)
775
776
776 def mergefunc():
777 def mergefunc():
777 if anc == cur and dst.branch() == cur.branch():
778 if anc == cur and dst.branch() == cur.branch():
778 self.ui.debug("updating subrepo %s\n" % subrelpath(self))
779 self.ui.debug("updating subrepo %s\n" % subrelpath(self))
779 hg.update(self._repo, state[1])
780 hg.update(self._repo, state[1])
780 elif anc == dst:
781 elif anc == dst:
781 self.ui.debug("skipping subrepo %s\n" % subrelpath(self))
782 self.ui.debug("skipping subrepo %s\n" % subrelpath(self))
782 else:
783 else:
783 self.ui.debug("merging subrepo %s\n" % subrelpath(self))
784 self.ui.debug("merging subrepo %s\n" % subrelpath(self))
784 hg.merge(self._repo, state[1], remind=False)
785 hg.merge(self._repo, state[1], remind=False)
785
786
786 wctx = self._repo[None]
787 wctx = self._repo[None]
787 if self.dirty():
788 if self.dirty():
788 if anc != dst:
789 if anc != dst:
789 if _updateprompt(self.ui, self, wctx.dirty(), cur, dst):
790 if _updateprompt(self.ui, self, wctx.dirty(), cur, dst):
790 mergefunc()
791 mergefunc()
791 else:
792 else:
792 mergefunc()
793 mergefunc()
793 else:
794 else:
794 mergefunc()
795 mergefunc()
795
796
796 @annotatesubrepoerror
797 @annotatesubrepoerror
797 def push(self, opts):
798 def push(self, opts):
798 force = opts.get('force')
799 force = opts.get('force')
799 newbranch = opts.get('new_branch')
800 newbranch = opts.get('new_branch')
800 ssh = opts.get('ssh')
801 ssh = opts.get('ssh')
801
802
802 # push subrepos depth-first for coherent ordering
803 # push subrepos depth-first for coherent ordering
803 c = self._repo['']
804 c = self._repo['']
804 subs = c.substate # only repos that are committed
805 subs = c.substate # only repos that are committed
805 for s in sorted(subs):
806 for s in sorted(subs):
806 if c.sub(s).push(opts) == 0:
807 if c.sub(s).push(opts) == 0:
807 return False
808 return False
808
809
809 dsturl = _abssource(self._repo, True)
810 dsturl = _abssource(self._repo, True)
810 if not force:
811 if not force:
811 if self.storeclean(dsturl):
812 if self.storeclean(dsturl):
812 self.ui.status(
813 self.ui.status(
813 _('no changes made to subrepo %s since last push to %s\n')
814 _('no changes made to subrepo %s since last push to %s\n')
814 % (subrelpath(self), dsturl))
815 % (subrelpath(self), dsturl))
815 return None
816 return None
816 self.ui.status(_('pushing subrepo %s to %s\n') %
817 self.ui.status(_('pushing subrepo %s to %s\n') %
817 (subrelpath(self), dsturl))
818 (subrelpath(self), dsturl))
818 other = hg.peer(self._repo, {'ssh': ssh}, dsturl)
819 other = hg.peer(self._repo, {'ssh': ssh}, dsturl)
819 res = exchange.push(self._repo, other, force, newbranch=newbranch)
820 res = exchange.push(self._repo, other, force, newbranch=newbranch)
820
821
821 # the repo is now clean
822 # the repo is now clean
822 self._cachestorehash(dsturl)
823 self._cachestorehash(dsturl)
823 return res.cgresult
824 return res.cgresult
824
825
825 @annotatesubrepoerror
826 @annotatesubrepoerror
826 def outgoing(self, ui, dest, opts):
827 def outgoing(self, ui, dest, opts):
827 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
828 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
828
829
829 @annotatesubrepoerror
830 @annotatesubrepoerror
830 def incoming(self, ui, source, opts):
831 def incoming(self, ui, source, opts):
831 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
832 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
832
833
833 @annotatesubrepoerror
834 @annotatesubrepoerror
834 def files(self):
835 def files(self):
835 rev = self._state[1]
836 rev = self._state[1]
836 ctx = self._repo[rev]
837 ctx = self._repo[rev]
837 return ctx.manifest()
838 return ctx.manifest()
838
839
839 def filedata(self, name):
840 def filedata(self, name):
840 rev = self._state[1]
841 rev = self._state[1]
841 return self._repo[rev][name].data()
842 return self._repo[rev][name].data()
842
843
843 def fileflags(self, name):
844 def fileflags(self, name):
844 rev = self._state[1]
845 rev = self._state[1]
845 ctx = self._repo[rev]
846 ctx = self._repo[rev]
846 return ctx.flags(name)
847 return ctx.flags(name)
847
848
848 def walk(self, match):
849 def walk(self, match):
849 ctx = self._repo[None]
850 ctx = self._repo[None]
850 return ctx.walk(match)
851 return ctx.walk(match)
851
852
852 @annotatesubrepoerror
853 @annotatesubrepoerror
853 def forget(self, match, prefix):
854 def forget(self, match, prefix):
854 return cmdutil.forget(self.ui, self._repo, match,
855 return cmdutil.forget(self.ui, self._repo, match,
855 os.path.join(prefix, self._path), True)
856 os.path.join(prefix, self._path), True)
856
857
857 @annotatesubrepoerror
858 @annotatesubrepoerror
858 def removefiles(self, matcher, prefix, after, force, subrepos):
859 def removefiles(self, matcher, prefix, after, force, subrepos):
859 return cmdutil.remove(self.ui, self._repo, matcher,
860 return cmdutil.remove(self.ui, self._repo, matcher,
860 os.path.join(prefix, self._path), after, force,
861 os.path.join(prefix, self._path), after, force,
861 subrepos)
862 subrepos)
862
863
863 @annotatesubrepoerror
864 @annotatesubrepoerror
864 def revert(self, substate, *pats, **opts):
865 def revert(self, substate, *pats, **opts):
865 # reverting a subrepo is a 2 step process:
866 # reverting a subrepo is a 2 step process:
866 # 1. if the no_backup is not set, revert all modified
867 # 1. if the no_backup is not set, revert all modified
867 # files inside the subrepo
868 # files inside the subrepo
868 # 2. update the subrepo to the revision specified in
869 # 2. update the subrepo to the revision specified in
869 # the corresponding substate dictionary
870 # the corresponding substate dictionary
870 self.ui.status(_('reverting subrepo %s\n') % substate[0])
871 self.ui.status(_('reverting subrepo %s\n') % substate[0])
871 if not opts.get('no_backup'):
872 if not opts.get('no_backup'):
872 # Revert all files on the subrepo, creating backups
873 # Revert all files on the subrepo, creating backups
873 # Note that this will not recursively revert subrepos
874 # Note that this will not recursively revert subrepos
874 # We could do it if there was a set:subrepos() predicate
875 # We could do it if there was a set:subrepos() predicate
875 opts = opts.copy()
876 opts = opts.copy()
876 opts['date'] = None
877 opts['date'] = None
877 opts['rev'] = substate[1]
878 opts['rev'] = substate[1]
878
879
879 pats = []
880 pats = []
880 if not opts.get('all'):
881 if not opts.get('all'):
881 pats = ['set:modified()']
882 pats = ['set:modified()']
882 self.filerevert(*pats, **opts)
883 self.filerevert(*pats, **opts)
883
884
884 # Update the repo to the revision specified in the given substate
885 # Update the repo to the revision specified in the given substate
885 self.get(substate, overwrite=True)
886 self.get(substate, overwrite=True)
886
887
887 def filerevert(self, *pats, **opts):
888 def filerevert(self, *pats, **opts):
888 ctx = self._repo[opts['rev']]
889 ctx = self._repo[opts['rev']]
889 parents = self._repo.dirstate.parents()
890 parents = self._repo.dirstate.parents()
890 if opts.get('all'):
891 if opts.get('all'):
891 pats = ['set:modified()']
892 pats = ['set:modified()']
892 else:
893 else:
893 pats = []
894 pats = []
894 cmdutil.revert(self.ui, self._repo, ctx, parents, *pats, **opts)
895 cmdutil.revert(self.ui, self._repo, ctx, parents, *pats, **opts)
895
896
896 def shortid(self, revid):
897 def shortid(self, revid):
897 return revid[:12]
898 return revid[:12]
898
899
899 class svnsubrepo(abstractsubrepo):
900 class svnsubrepo(abstractsubrepo):
900 def __init__(self, ctx, path, state):
901 def __init__(self, ctx, path, state):
901 super(svnsubrepo, self).__init__(ctx._repo.ui)
902 super(svnsubrepo, self).__init__(ctx._repo.ui)
902 self._path = path
903 self._path = path
903 self._state = state
904 self._state = state
904 self._ctx = ctx
905 self._ctx = ctx
905 self._exe = util.findexe('svn')
906 self._exe = util.findexe('svn')
906 if not self._exe:
907 if not self._exe:
907 raise util.Abort(_("'svn' executable not found for subrepo '%s'")
908 raise util.Abort(_("'svn' executable not found for subrepo '%s'")
908 % self._path)
909 % self._path)
909
910
910 def _svncommand(self, commands, filename='', failok=False):
911 def _svncommand(self, commands, filename='', failok=False):
911 cmd = [self._exe]
912 cmd = [self._exe]
912 extrakw = {}
913 extrakw = {}
913 if not self.ui.interactive():
914 if not self.ui.interactive():
914 # Making stdin be a pipe should prevent svn from behaving
915 # Making stdin be a pipe should prevent svn from behaving
915 # interactively even if we can't pass --non-interactive.
916 # interactively even if we can't pass --non-interactive.
916 extrakw['stdin'] = subprocess.PIPE
917 extrakw['stdin'] = subprocess.PIPE
917 # Starting in svn 1.5 --non-interactive is a global flag
918 # Starting in svn 1.5 --non-interactive is a global flag
918 # instead of being per-command, but we need to support 1.4 so
919 # instead of being per-command, but we need to support 1.4 so
919 # we have to be intelligent about what commands take
920 # we have to be intelligent about what commands take
920 # --non-interactive.
921 # --non-interactive.
921 if commands[0] in ('update', 'checkout', 'commit'):
922 if commands[0] in ('update', 'checkout', 'commit'):
922 cmd.append('--non-interactive')
923 cmd.append('--non-interactive')
923 cmd.extend(commands)
924 cmd.extend(commands)
924 if filename is not None:
925 if filename is not None:
925 path = os.path.join(self._ctx._repo.origroot, self._path, filename)
926 path = os.path.join(self._ctx._repo.origroot, self._path, filename)
926 cmd.append(path)
927 cmd.append(path)
927 env = dict(os.environ)
928 env = dict(os.environ)
928 # Avoid localized output, preserve current locale for everything else.
929 # Avoid localized output, preserve current locale for everything else.
929 lc_all = env.get('LC_ALL')
930 lc_all = env.get('LC_ALL')
930 if lc_all:
931 if lc_all:
931 env['LANG'] = lc_all
932 env['LANG'] = lc_all
932 del env['LC_ALL']
933 del env['LC_ALL']
933 env['LC_MESSAGES'] = 'C'
934 env['LC_MESSAGES'] = 'C'
934 p = subprocess.Popen(cmd, bufsize=-1, close_fds=util.closefds,
935 p = subprocess.Popen(cmd, bufsize=-1, close_fds=util.closefds,
935 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
936 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
936 universal_newlines=True, env=env, **extrakw)
937 universal_newlines=True, env=env, **extrakw)
937 stdout, stderr = p.communicate()
938 stdout, stderr = p.communicate()
938 stderr = stderr.strip()
939 stderr = stderr.strip()
939 if not failok:
940 if not failok:
940 if p.returncode:
941 if p.returncode:
941 raise util.Abort(stderr or 'exited with code %d' % p.returncode)
942 raise util.Abort(stderr or 'exited with code %d' % p.returncode)
942 if stderr:
943 if stderr:
943 self.ui.warn(stderr + '\n')
944 self.ui.warn(stderr + '\n')
944 return stdout, stderr
945 return stdout, stderr
945
946
946 @propertycache
947 @propertycache
947 def _svnversion(self):
948 def _svnversion(self):
948 output, err = self._svncommand(['--version', '--quiet'], filename=None)
949 output, err = self._svncommand(['--version', '--quiet'], filename=None)
949 m = re.search(r'^(\d+)\.(\d+)', output)
950 m = re.search(r'^(\d+)\.(\d+)', output)
950 if not m:
951 if not m:
951 raise util.Abort(_('cannot retrieve svn tool version'))
952 raise util.Abort(_('cannot retrieve svn tool version'))
952 return (int(m.group(1)), int(m.group(2)))
953 return (int(m.group(1)), int(m.group(2)))
953
954
954 def _wcrevs(self):
955 def _wcrevs(self):
955 # Get the working directory revision as well as the last
956 # Get the working directory revision as well as the last
956 # commit revision so we can compare the subrepo state with
957 # commit revision so we can compare the subrepo state with
957 # both. We used to store the working directory one.
958 # both. We used to store the working directory one.
958 output, err = self._svncommand(['info', '--xml'])
959 output, err = self._svncommand(['info', '--xml'])
959 doc = xml.dom.minidom.parseString(output)
960 doc = xml.dom.minidom.parseString(output)
960 entries = doc.getElementsByTagName('entry')
961 entries = doc.getElementsByTagName('entry')
961 lastrev, rev = '0', '0'
962 lastrev, rev = '0', '0'
962 if entries:
963 if entries:
963 rev = str(entries[0].getAttribute('revision')) or '0'
964 rev = str(entries[0].getAttribute('revision')) or '0'
964 commits = entries[0].getElementsByTagName('commit')
965 commits = entries[0].getElementsByTagName('commit')
965 if commits:
966 if commits:
966 lastrev = str(commits[0].getAttribute('revision')) or '0'
967 lastrev = str(commits[0].getAttribute('revision')) or '0'
967 return (lastrev, rev)
968 return (lastrev, rev)
968
969
969 def _wcrev(self):
970 def _wcrev(self):
970 return self._wcrevs()[0]
971 return self._wcrevs()[0]
971
972
972 def _wcchanged(self):
973 def _wcchanged(self):
973 """Return (changes, extchanges, missing) where changes is True
974 """Return (changes, extchanges, missing) where changes is True
974 if the working directory was changed, extchanges is
975 if the working directory was changed, extchanges is
975 True if any of these changes concern an external entry and missing
976 True if any of these changes concern an external entry and missing
976 is True if any change is a missing entry.
977 is True if any change is a missing entry.
977 """
978 """
978 output, err = self._svncommand(['status', '--xml'])
979 output, err = self._svncommand(['status', '--xml'])
979 externals, changes, missing = [], [], []
980 externals, changes, missing = [], [], []
980 doc = xml.dom.minidom.parseString(output)
981 doc = xml.dom.minidom.parseString(output)
981 for e in doc.getElementsByTagName('entry'):
982 for e in doc.getElementsByTagName('entry'):
982 s = e.getElementsByTagName('wc-status')
983 s = e.getElementsByTagName('wc-status')
983 if not s:
984 if not s:
984 continue
985 continue
985 item = s[0].getAttribute('item')
986 item = s[0].getAttribute('item')
986 props = s[0].getAttribute('props')
987 props = s[0].getAttribute('props')
987 path = e.getAttribute('path')
988 path = e.getAttribute('path')
988 if item == 'external':
989 if item == 'external':
989 externals.append(path)
990 externals.append(path)
990 elif item == 'missing':
991 elif item == 'missing':
991 missing.append(path)
992 missing.append(path)
992 if (item not in ('', 'normal', 'unversioned', 'external')
993 if (item not in ('', 'normal', 'unversioned', 'external')
993 or props not in ('', 'none', 'normal')):
994 or props not in ('', 'none', 'normal')):
994 changes.append(path)
995 changes.append(path)
995 for path in changes:
996 for path in changes:
996 for ext in externals:
997 for ext in externals:
997 if path == ext or path.startswith(ext + os.sep):
998 if path == ext or path.startswith(ext + os.sep):
998 return True, True, bool(missing)
999 return True, True, bool(missing)
999 return bool(changes), False, bool(missing)
1000 return bool(changes), False, bool(missing)
1000
1001
1001 def dirty(self, ignoreupdate=False):
1002 def dirty(self, ignoreupdate=False):
1002 if not self._wcchanged()[0]:
1003 if not self._wcchanged()[0]:
1003 if self._state[1] in self._wcrevs() or ignoreupdate:
1004 if self._state[1] in self._wcrevs() or ignoreupdate:
1004 return False
1005 return False
1005 return True
1006 return True
1006
1007
1007 def basestate(self):
1008 def basestate(self):
1008 lastrev, rev = self._wcrevs()
1009 lastrev, rev = self._wcrevs()
1009 if lastrev != rev:
1010 if lastrev != rev:
1010 # Last committed rev is not the same than rev. We would
1011 # Last committed rev is not the same than rev. We would
1011 # like to take lastrev but we do not know if the subrepo
1012 # like to take lastrev but we do not know if the subrepo
1012 # URL exists at lastrev. Test it and fallback to rev it
1013 # URL exists at lastrev. Test it and fallback to rev it
1013 # is not there.
1014 # is not there.
1014 try:
1015 try:
1015 self._svncommand(['list', '%s@%s' % (self._state[0], lastrev)])
1016 self._svncommand(['list', '%s@%s' % (self._state[0], lastrev)])
1016 return lastrev
1017 return lastrev
1017 except error.Abort:
1018 except error.Abort:
1018 pass
1019 pass
1019 return rev
1020 return rev
1020
1021
1021 @annotatesubrepoerror
1022 @annotatesubrepoerror
1022 def commit(self, text, user, date):
1023 def commit(self, text, user, date):
1023 # user and date are out of our hands since svn is centralized
1024 # user and date are out of our hands since svn is centralized
1024 changed, extchanged, missing = self._wcchanged()
1025 changed, extchanged, missing = self._wcchanged()
1025 if not changed:
1026 if not changed:
1026 return self.basestate()
1027 return self.basestate()
1027 if extchanged:
1028 if extchanged:
1028 # Do not try to commit externals
1029 # Do not try to commit externals
1029 raise util.Abort(_('cannot commit svn externals'))
1030 raise util.Abort(_('cannot commit svn externals'))
1030 if missing:
1031 if missing:
1031 # svn can commit with missing entries but aborting like hg
1032 # svn can commit with missing entries but aborting like hg
1032 # seems a better approach.
1033 # seems a better approach.
1033 raise util.Abort(_('cannot commit missing svn entries'))
1034 raise util.Abort(_('cannot commit missing svn entries'))
1034 commitinfo, err = self._svncommand(['commit', '-m', text])
1035 commitinfo, err = self._svncommand(['commit', '-m', text])
1035 self.ui.status(commitinfo)
1036 self.ui.status(commitinfo)
1036 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
1037 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
1037 if not newrev:
1038 if not newrev:
1038 if not commitinfo.strip():
1039 if not commitinfo.strip():
1039 # Sometimes, our definition of "changed" differs from
1040 # Sometimes, our definition of "changed" differs from
1040 # svn one. For instance, svn ignores missing files
1041 # svn one. For instance, svn ignores missing files
1041 # when committing. If there are only missing files, no
1042 # when committing. If there are only missing files, no
1042 # commit is made, no output and no error code.
1043 # commit is made, no output and no error code.
1043 raise util.Abort(_('failed to commit svn changes'))
1044 raise util.Abort(_('failed to commit svn changes'))
1044 raise util.Abort(commitinfo.splitlines()[-1])
1045 raise util.Abort(commitinfo.splitlines()[-1])
1045 newrev = newrev.groups()[0]
1046 newrev = newrev.groups()[0]
1046 self.ui.status(self._svncommand(['update', '-r', newrev])[0])
1047 self.ui.status(self._svncommand(['update', '-r', newrev])[0])
1047 return newrev
1048 return newrev
1048
1049
1049 @annotatesubrepoerror
1050 @annotatesubrepoerror
1050 def remove(self):
1051 def remove(self):
1051 if self.dirty():
1052 if self.dirty():
1052 self.ui.warn(_('not removing repo %s because '
1053 self.ui.warn(_('not removing repo %s because '
1053 'it has changes.\n') % self._path)
1054 'it has changes.\n') % self._path)
1054 return
1055 return
1055 self.ui.note(_('removing subrepo %s\n') % self._path)
1056 self.ui.note(_('removing subrepo %s\n') % self._path)
1056
1057
1057 def onerror(function, path, excinfo):
1058 def onerror(function, path, excinfo):
1058 if function is not os.remove:
1059 if function is not os.remove:
1059 raise
1060 raise
1060 # read-only files cannot be unlinked under Windows
1061 # read-only files cannot be unlinked under Windows
1061 s = os.stat(path)
1062 s = os.stat(path)
1062 if (s.st_mode & stat.S_IWRITE) != 0:
1063 if (s.st_mode & stat.S_IWRITE) != 0:
1063 raise
1064 raise
1064 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
1065 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
1065 os.remove(path)
1066 os.remove(path)
1066
1067
1067 path = self._ctx._repo.wjoin(self._path)
1068 path = self._ctx._repo.wjoin(self._path)
1068 shutil.rmtree(path, onerror=onerror)
1069 shutil.rmtree(path, onerror=onerror)
1069 try:
1070 try:
1070 os.removedirs(os.path.dirname(path))
1071 os.removedirs(os.path.dirname(path))
1071 except OSError:
1072 except OSError:
1072 pass
1073 pass
1073
1074
1074 @annotatesubrepoerror
1075 @annotatesubrepoerror
1075 def get(self, state, overwrite=False):
1076 def get(self, state, overwrite=False):
1076 if overwrite:
1077 if overwrite:
1077 self._svncommand(['revert', '--recursive'])
1078 self._svncommand(['revert', '--recursive'])
1078 args = ['checkout']
1079 args = ['checkout']
1079 if self._svnversion >= (1, 5):
1080 if self._svnversion >= (1, 5):
1080 args.append('--force')
1081 args.append('--force')
1081 # The revision must be specified at the end of the URL to properly
1082 # The revision must be specified at the end of the URL to properly
1082 # update to a directory which has since been deleted and recreated.
1083 # update to a directory which has since been deleted and recreated.
1083 args.append('%s@%s' % (state[0], state[1]))
1084 args.append('%s@%s' % (state[0], state[1]))
1084 status, err = self._svncommand(args, failok=True)
1085 status, err = self._svncommand(args, failok=True)
1085 _sanitize(self.ui, self._ctx._repo.wjoin(self._path), '.svn')
1086 _sanitize(self.ui, self._ctx._repo.wjoin(self._path), '.svn')
1086 if not re.search('Checked out revision [0-9]+.', status):
1087 if not re.search('Checked out revision [0-9]+.', status):
1087 if ('is already a working copy for a different URL' in err
1088 if ('is already a working copy for a different URL' in err
1088 and (self._wcchanged()[:2] == (False, False))):
1089 and (self._wcchanged()[:2] == (False, False))):
1089 # obstructed but clean working copy, so just blow it away.
1090 # obstructed but clean working copy, so just blow it away.
1090 self.remove()
1091 self.remove()
1091 self.get(state, overwrite=False)
1092 self.get(state, overwrite=False)
1092 return
1093 return
1093 raise util.Abort((status or err).splitlines()[-1])
1094 raise util.Abort((status or err).splitlines()[-1])
1094 self.ui.status(status)
1095 self.ui.status(status)
1095
1096
1096 @annotatesubrepoerror
1097 @annotatesubrepoerror
1097 def merge(self, state):
1098 def merge(self, state):
1098 old = self._state[1]
1099 old = self._state[1]
1099 new = state[1]
1100 new = state[1]
1100 wcrev = self._wcrev()
1101 wcrev = self._wcrev()
1101 if new != wcrev:
1102 if new != wcrev:
1102 dirty = old == wcrev or self._wcchanged()[0]
1103 dirty = old == wcrev or self._wcchanged()[0]
1103 if _updateprompt(self.ui, self, dirty, wcrev, new):
1104 if _updateprompt(self.ui, self, dirty, wcrev, new):
1104 self.get(state, False)
1105 self.get(state, False)
1105
1106
1106 def push(self, opts):
1107 def push(self, opts):
1107 # push is a no-op for SVN
1108 # push is a no-op for SVN
1108 return True
1109 return True
1109
1110
1110 @annotatesubrepoerror
1111 @annotatesubrepoerror
1111 def files(self):
1112 def files(self):
1112 output = self._svncommand(['list', '--recursive', '--xml'])[0]
1113 output = self._svncommand(['list', '--recursive', '--xml'])[0]
1113 doc = xml.dom.minidom.parseString(output)
1114 doc = xml.dom.minidom.parseString(output)
1114 paths = []
1115 paths = []
1115 for e in doc.getElementsByTagName('entry'):
1116 for e in doc.getElementsByTagName('entry'):
1116 kind = str(e.getAttribute('kind'))
1117 kind = str(e.getAttribute('kind'))
1117 if kind != 'file':
1118 if kind != 'file':
1118 continue
1119 continue
1119 name = ''.join(c.data for c
1120 name = ''.join(c.data for c
1120 in e.getElementsByTagName('name')[0].childNodes
1121 in e.getElementsByTagName('name')[0].childNodes
1121 if c.nodeType == c.TEXT_NODE)
1122 if c.nodeType == c.TEXT_NODE)
1122 paths.append(name.encode('utf-8'))
1123 paths.append(name.encode('utf-8'))
1123 return paths
1124 return paths
1124
1125
1125 def filedata(self, name):
1126 def filedata(self, name):
1126 return self._svncommand(['cat'], name)[0]
1127 return self._svncommand(['cat'], name)[0]
1127
1128
1128
1129
1129 class gitsubrepo(abstractsubrepo):
1130 class gitsubrepo(abstractsubrepo):
1130 def __init__(self, ctx, path, state):
1131 def __init__(self, ctx, path, state):
1131 super(gitsubrepo, self).__init__(ctx._repo.ui)
1132 super(gitsubrepo, self).__init__(ctx._repo.ui)
1132 self._state = state
1133 self._state = state
1133 self._ctx = ctx
1134 self._ctx = ctx
1134 self._path = path
1135 self._path = path
1135 self._relpath = os.path.join(reporelpath(ctx._repo), path)
1136 self._relpath = os.path.join(reporelpath(ctx._repo), path)
1136 self._abspath = ctx._repo.wjoin(path)
1137 self._abspath = ctx._repo.wjoin(path)
1137 self._subparent = ctx._repo
1138 self._subparent = ctx._repo
1138 self._ensuregit()
1139 self._ensuregit()
1139
1140
1140 def _ensuregit(self):
1141 def _ensuregit(self):
1141 try:
1142 try:
1142 self._gitexecutable = 'git'
1143 self._gitexecutable = 'git'
1143 out, err = self._gitnodir(['--version'])
1144 out, err = self._gitnodir(['--version'])
1144 except OSError, e:
1145 except OSError, e:
1145 if e.errno != 2 or os.name != 'nt':
1146 if e.errno != 2 or os.name != 'nt':
1146 raise
1147 raise
1147 self._gitexecutable = 'git.cmd'
1148 self._gitexecutable = 'git.cmd'
1148 out, err = self._gitnodir(['--version'])
1149 out, err = self._gitnodir(['--version'])
1149 versionstatus = self._checkversion(out)
1150 versionstatus = self._checkversion(out)
1150 if versionstatus == 'unknown':
1151 if versionstatus == 'unknown':
1151 self.ui.warn(_('cannot retrieve git version\n'))
1152 self.ui.warn(_('cannot retrieve git version\n'))
1152 elif versionstatus == 'abort':
1153 elif versionstatus == 'abort':
1153 raise util.Abort(_('git subrepo requires at least 1.6.0 or later'))
1154 raise util.Abort(_('git subrepo requires at least 1.6.0 or later'))
1154 elif versionstatus == 'warning':
1155 elif versionstatus == 'warning':
1155 self.ui.warn(_('git subrepo requires at least 1.6.0 or later\n'))
1156 self.ui.warn(_('git subrepo requires at least 1.6.0 or later\n'))
1156
1157
1157 @staticmethod
1158 @staticmethod
1158 def _gitversion(out):
1159 def _gitversion(out):
1159 m = re.search(r'^git version (\d+)\.(\d+)\.(\d+)', out)
1160 m = re.search(r'^git version (\d+)\.(\d+)\.(\d+)', out)
1160 if m:
1161 if m:
1161 return (int(m.group(1)), int(m.group(2)), int(m.group(3)))
1162 return (int(m.group(1)), int(m.group(2)), int(m.group(3)))
1162
1163
1163 m = re.search(r'^git version (\d+)\.(\d+)', out)
1164 m = re.search(r'^git version (\d+)\.(\d+)', out)
1164 if m:
1165 if m:
1165 return (int(m.group(1)), int(m.group(2)), 0)
1166 return (int(m.group(1)), int(m.group(2)), 0)
1166
1167
1167 return -1
1168 return -1
1168
1169
1169 @staticmethod
1170 @staticmethod
1170 def _checkversion(out):
1171 def _checkversion(out):
1171 '''ensure git version is new enough
1172 '''ensure git version is new enough
1172
1173
1173 >>> _checkversion = gitsubrepo._checkversion
1174 >>> _checkversion = gitsubrepo._checkversion
1174 >>> _checkversion('git version 1.6.0')
1175 >>> _checkversion('git version 1.6.0')
1175 'ok'
1176 'ok'
1176 >>> _checkversion('git version 1.8.5')
1177 >>> _checkversion('git version 1.8.5')
1177 'ok'
1178 'ok'
1178 >>> _checkversion('git version 1.4.0')
1179 >>> _checkversion('git version 1.4.0')
1179 'abort'
1180 'abort'
1180 >>> _checkversion('git version 1.5.0')
1181 >>> _checkversion('git version 1.5.0')
1181 'warning'
1182 'warning'
1182 >>> _checkversion('git version 1.9-rc0')
1183 >>> _checkversion('git version 1.9-rc0')
1183 'ok'
1184 'ok'
1184 >>> _checkversion('git version 1.9.0.265.g81cdec2')
1185 >>> _checkversion('git version 1.9.0.265.g81cdec2')
1185 'ok'
1186 'ok'
1186 >>> _checkversion('git version 1.9.0.GIT')
1187 >>> _checkversion('git version 1.9.0.GIT')
1187 'ok'
1188 'ok'
1188 >>> _checkversion('git version 12345')
1189 >>> _checkversion('git version 12345')
1189 'unknown'
1190 'unknown'
1190 >>> _checkversion('no')
1191 >>> _checkversion('no')
1191 'unknown'
1192 'unknown'
1192 '''
1193 '''
1193 version = gitsubrepo._gitversion(out)
1194 version = gitsubrepo._gitversion(out)
1194 # git 1.4.0 can't work at all, but 1.5.X can in at least some cases,
1195 # git 1.4.0 can't work at all, but 1.5.X can in at least some cases,
1195 # despite the docstring comment. For now, error on 1.4.0, warn on
1196 # despite the docstring comment. For now, error on 1.4.0, warn on
1196 # 1.5.0 but attempt to continue.
1197 # 1.5.0 but attempt to continue.
1197 if version == -1:
1198 if version == -1:
1198 return 'unknown'
1199 return 'unknown'
1199 if version < (1, 5, 0):
1200 if version < (1, 5, 0):
1200 return 'abort'
1201 return 'abort'
1201 elif version < (1, 6, 0):
1202 elif version < (1, 6, 0):
1202 return 'warning'
1203 return 'warning'
1203 return 'ok'
1204 return 'ok'
1204
1205
1205 def _gitcommand(self, commands, env=None, stream=False):
1206 def _gitcommand(self, commands, env=None, stream=False):
1206 return self._gitdir(commands, env=env, stream=stream)[0]
1207 return self._gitdir(commands, env=env, stream=stream)[0]
1207
1208
1208 def _gitdir(self, commands, env=None, stream=False):
1209 def _gitdir(self, commands, env=None, stream=False):
1209 return self._gitnodir(commands, env=env, stream=stream,
1210 return self._gitnodir(commands, env=env, stream=stream,
1210 cwd=self._abspath)
1211 cwd=self._abspath)
1211
1212
1212 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
1213 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
1213 """Calls the git command
1214 """Calls the git command
1214
1215
1215 The methods tries to call the git command. versions prior to 1.6.0
1216 The methods tries to call the git command. versions prior to 1.6.0
1216 are not supported and very probably fail.
1217 are not supported and very probably fail.
1217 """
1218 """
1218 self.ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
1219 self.ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
1219 # unless ui.quiet is set, print git's stderr,
1220 # unless ui.quiet is set, print git's stderr,
1220 # which is mostly progress and useful info
1221 # which is mostly progress and useful info
1221 errpipe = None
1222 errpipe = None
1222 if self.ui.quiet:
1223 if self.ui.quiet:
1223 errpipe = open(os.devnull, 'w')
1224 errpipe = open(os.devnull, 'w')
1224 p = subprocess.Popen([self._gitexecutable] + commands, bufsize=-1,
1225 p = subprocess.Popen([self._gitexecutable] + commands, bufsize=-1,
1225 cwd=cwd, env=env, close_fds=util.closefds,
1226 cwd=cwd, env=env, close_fds=util.closefds,
1226 stdout=subprocess.PIPE, stderr=errpipe)
1227 stdout=subprocess.PIPE, stderr=errpipe)
1227 if stream:
1228 if stream:
1228 return p.stdout, None
1229 return p.stdout, None
1229
1230
1230 retdata = p.stdout.read().strip()
1231 retdata = p.stdout.read().strip()
1231 # wait for the child to exit to avoid race condition.
1232 # wait for the child to exit to avoid race condition.
1232 p.wait()
1233 p.wait()
1233
1234
1234 if p.returncode != 0 and p.returncode != 1:
1235 if p.returncode != 0 and p.returncode != 1:
1235 # there are certain error codes that are ok
1236 # there are certain error codes that are ok
1236 command = commands[0]
1237 command = commands[0]
1237 if command in ('cat-file', 'symbolic-ref'):
1238 if command in ('cat-file', 'symbolic-ref'):
1238 return retdata, p.returncode
1239 return retdata, p.returncode
1239 # for all others, abort
1240 # for all others, abort
1240 raise util.Abort('git %s error %d in %s' %
1241 raise util.Abort('git %s error %d in %s' %
1241 (command, p.returncode, self._relpath))
1242 (command, p.returncode, self._relpath))
1242
1243
1243 return retdata, p.returncode
1244 return retdata, p.returncode
1244
1245
1245 def _gitmissing(self):
1246 def _gitmissing(self):
1246 return not os.path.exists(os.path.join(self._abspath, '.git'))
1247 return not os.path.exists(os.path.join(self._abspath, '.git'))
1247
1248
1248 def _gitstate(self):
1249 def _gitstate(self):
1249 return self._gitcommand(['rev-parse', 'HEAD'])
1250 return self._gitcommand(['rev-parse', 'HEAD'])
1250
1251
1251 def _gitcurrentbranch(self):
1252 def _gitcurrentbranch(self):
1252 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet'])
1253 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet'])
1253 if err:
1254 if err:
1254 current = None
1255 current = None
1255 return current
1256 return current
1256
1257
1257 def _gitremote(self, remote):
1258 def _gitremote(self, remote):
1258 out = self._gitcommand(['remote', 'show', '-n', remote])
1259 out = self._gitcommand(['remote', 'show', '-n', remote])
1259 line = out.split('\n')[1]
1260 line = out.split('\n')[1]
1260 i = line.index('URL: ') + len('URL: ')
1261 i = line.index('URL: ') + len('URL: ')
1261 return line[i:]
1262 return line[i:]
1262
1263
1263 def _githavelocally(self, revision):
1264 def _githavelocally(self, revision):
1264 out, code = self._gitdir(['cat-file', '-e', revision])
1265 out, code = self._gitdir(['cat-file', '-e', revision])
1265 return code == 0
1266 return code == 0
1266
1267
1267 def _gitisancestor(self, r1, r2):
1268 def _gitisancestor(self, r1, r2):
1268 base = self._gitcommand(['merge-base', r1, r2])
1269 base = self._gitcommand(['merge-base', r1, r2])
1269 return base == r1
1270 return base == r1
1270
1271
1271 def _gitisbare(self):
1272 def _gitisbare(self):
1272 return self._gitcommand(['config', '--bool', 'core.bare']) == 'true'
1273 return self._gitcommand(['config', '--bool', 'core.bare']) == 'true'
1273
1274
1274 def _gitupdatestat(self):
1275 def _gitupdatestat(self):
1275 """This must be run before git diff-index.
1276 """This must be run before git diff-index.
1276 diff-index only looks at changes to file stat;
1277 diff-index only looks at changes to file stat;
1277 this command looks at file contents and updates the stat."""
1278 this command looks at file contents and updates the stat."""
1278 self._gitcommand(['update-index', '-q', '--refresh'])
1279 self._gitcommand(['update-index', '-q', '--refresh'])
1279
1280
1280 def _gitbranchmap(self):
1281 def _gitbranchmap(self):
1281 '''returns 2 things:
1282 '''returns 2 things:
1282 a map from git branch to revision
1283 a map from git branch to revision
1283 a map from revision to branches'''
1284 a map from revision to branches'''
1284 branch2rev = {}
1285 branch2rev = {}
1285 rev2branch = {}
1286 rev2branch = {}
1286
1287
1287 out = self._gitcommand(['for-each-ref', '--format',
1288 out = self._gitcommand(['for-each-ref', '--format',
1288 '%(objectname) %(refname)'])
1289 '%(objectname) %(refname)'])
1289 for line in out.split('\n'):
1290 for line in out.split('\n'):
1290 revision, ref = line.split(' ')
1291 revision, ref = line.split(' ')
1291 if (not ref.startswith('refs/heads/') and
1292 if (not ref.startswith('refs/heads/') and
1292 not ref.startswith('refs/remotes/')):
1293 not ref.startswith('refs/remotes/')):
1293 continue
1294 continue
1294 if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
1295 if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
1295 continue # ignore remote/HEAD redirects
1296 continue # ignore remote/HEAD redirects
1296 branch2rev[ref] = revision
1297 branch2rev[ref] = revision
1297 rev2branch.setdefault(revision, []).append(ref)
1298 rev2branch.setdefault(revision, []).append(ref)
1298 return branch2rev, rev2branch
1299 return branch2rev, rev2branch
1299
1300
1300 def _gittracking(self, branches):
1301 def _gittracking(self, branches):
1301 'return map of remote branch to local tracking branch'
1302 'return map of remote branch to local tracking branch'
1302 # assumes no more than one local tracking branch for each remote
1303 # assumes no more than one local tracking branch for each remote
1303 tracking = {}
1304 tracking = {}
1304 for b in branches:
1305 for b in branches:
1305 if b.startswith('refs/remotes/'):
1306 if b.startswith('refs/remotes/'):
1306 continue
1307 continue
1307 bname = b.split('/', 2)[2]
1308 bname = b.split('/', 2)[2]
1308 remote = self._gitcommand(['config', 'branch.%s.remote' % bname])
1309 remote = self._gitcommand(['config', 'branch.%s.remote' % bname])
1309 if remote:
1310 if remote:
1310 ref = self._gitcommand(['config', 'branch.%s.merge' % bname])
1311 ref = self._gitcommand(['config', 'branch.%s.merge' % bname])
1311 tracking['refs/remotes/%s/%s' %
1312 tracking['refs/remotes/%s/%s' %
1312 (remote, ref.split('/', 2)[2])] = b
1313 (remote, ref.split('/', 2)[2])] = b
1313 return tracking
1314 return tracking
1314
1315
1315 def _abssource(self, source):
1316 def _abssource(self, source):
1316 if '://' not in source:
1317 if '://' not in source:
1317 # recognize the scp syntax as an absolute source
1318 # recognize the scp syntax as an absolute source
1318 colon = source.find(':')
1319 colon = source.find(':')
1319 if colon != -1 and '/' not in source[:colon]:
1320 if colon != -1 and '/' not in source[:colon]:
1320 return source
1321 return source
1321 self._subsource = source
1322 self._subsource = source
1322 return _abssource(self)
1323 return _abssource(self)
1323
1324
1324 def _fetch(self, source, revision):
1325 def _fetch(self, source, revision):
1325 if self._gitmissing():
1326 if self._gitmissing():
1326 source = self._abssource(source)
1327 source = self._abssource(source)
1327 self.ui.status(_('cloning subrepo %s from %s\n') %
1328 self.ui.status(_('cloning subrepo %s from %s\n') %
1328 (self._relpath, source))
1329 (self._relpath, source))
1329 self._gitnodir(['clone', source, self._abspath])
1330 self._gitnodir(['clone', source, self._abspath])
1330 if self._githavelocally(revision):
1331 if self._githavelocally(revision):
1331 return
1332 return
1332 self.ui.status(_('pulling subrepo %s from %s\n') %
1333 self.ui.status(_('pulling subrepo %s from %s\n') %
1333 (self._relpath, self._gitremote('origin')))
1334 (self._relpath, self._gitremote('origin')))
1334 # try only origin: the originally cloned repo
1335 # try only origin: the originally cloned repo
1335 self._gitcommand(['fetch'])
1336 self._gitcommand(['fetch'])
1336 if not self._githavelocally(revision):
1337 if not self._githavelocally(revision):
1337 raise util.Abort(_("revision %s does not exist in subrepo %s\n") %
1338 raise util.Abort(_("revision %s does not exist in subrepo %s\n") %
1338 (revision, self._relpath))
1339 (revision, self._relpath))
1339
1340
1340 @annotatesubrepoerror
1341 @annotatesubrepoerror
1341 def dirty(self, ignoreupdate=False):
1342 def dirty(self, ignoreupdate=False):
1342 if self._gitmissing():
1343 if self._gitmissing():
1343 return self._state[1] != ''
1344 return self._state[1] != ''
1344 if self._gitisbare():
1345 if self._gitisbare():
1345 return True
1346 return True
1346 if not ignoreupdate and self._state[1] != self._gitstate():
1347 if not ignoreupdate and self._state[1] != self._gitstate():
1347 # different version checked out
1348 # different version checked out
1348 return True
1349 return True
1349 # check for staged changes or modified files; ignore untracked files
1350 # check for staged changes or modified files; ignore untracked files
1350 self._gitupdatestat()
1351 self._gitupdatestat()
1351 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1352 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1352 return code == 1
1353 return code == 1
1353
1354
1354 def basestate(self):
1355 def basestate(self):
1355 return self._gitstate()
1356 return self._gitstate()
1356
1357
1357 @annotatesubrepoerror
1358 @annotatesubrepoerror
1358 def get(self, state, overwrite=False):
1359 def get(self, state, overwrite=False):
1359 source, revision, kind = state
1360 source, revision, kind = state
1360 if not revision:
1361 if not revision:
1361 self.remove()
1362 self.remove()
1362 return
1363 return
1363 self._fetch(source, revision)
1364 self._fetch(source, revision)
1364 # if the repo was set to be bare, unbare it
1365 # if the repo was set to be bare, unbare it
1365 if self._gitisbare():
1366 if self._gitisbare():
1366 self._gitcommand(['config', 'core.bare', 'false'])
1367 self._gitcommand(['config', 'core.bare', 'false'])
1367 if self._gitstate() == revision:
1368 if self._gitstate() == revision:
1368 self._gitcommand(['reset', '--hard', 'HEAD'])
1369 self._gitcommand(['reset', '--hard', 'HEAD'])
1369 return
1370 return
1370 elif self._gitstate() == revision:
1371 elif self._gitstate() == revision:
1371 if overwrite:
1372 if overwrite:
1372 # first reset the index to unmark new files for commit, because
1373 # first reset the index to unmark new files for commit, because
1373 # reset --hard will otherwise throw away files added for commit,
1374 # reset --hard will otherwise throw away files added for commit,
1374 # not just unmark them.
1375 # not just unmark them.
1375 self._gitcommand(['reset', 'HEAD'])
1376 self._gitcommand(['reset', 'HEAD'])
1376 self._gitcommand(['reset', '--hard', 'HEAD'])
1377 self._gitcommand(['reset', '--hard', 'HEAD'])
1377 return
1378 return
1378 branch2rev, rev2branch = self._gitbranchmap()
1379 branch2rev, rev2branch = self._gitbranchmap()
1379
1380
1380 def checkout(args):
1381 def checkout(args):
1381 cmd = ['checkout']
1382 cmd = ['checkout']
1382 if overwrite:
1383 if overwrite:
1383 # first reset the index to unmark new files for commit, because
1384 # first reset the index to unmark new files for commit, because
1384 # the -f option will otherwise throw away files added for
1385 # the -f option will otherwise throw away files added for
1385 # commit, not just unmark them.
1386 # commit, not just unmark them.
1386 self._gitcommand(['reset', 'HEAD'])
1387 self._gitcommand(['reset', 'HEAD'])
1387 cmd.append('-f')
1388 cmd.append('-f')
1388 self._gitcommand(cmd + args)
1389 self._gitcommand(cmd + args)
1389 _sanitize(self.ui, self._abspath, '.git')
1390 _sanitize(self.ui, self._abspath, '.git')
1390
1391
1391 def rawcheckout():
1392 def rawcheckout():
1392 # no branch to checkout, check it out with no branch
1393 # no branch to checkout, check it out with no branch
1393 self.ui.warn(_('checking out detached HEAD in subrepo %s\n') %
1394 self.ui.warn(_('checking out detached HEAD in subrepo %s\n') %
1394 self._relpath)
1395 self._relpath)
1395 self.ui.warn(_('check out a git branch if you intend '
1396 self.ui.warn(_('check out a git branch if you intend '
1396 'to make changes\n'))
1397 'to make changes\n'))
1397 checkout(['-q', revision])
1398 checkout(['-q', revision])
1398
1399
1399 if revision not in rev2branch:
1400 if revision not in rev2branch:
1400 rawcheckout()
1401 rawcheckout()
1401 return
1402 return
1402 branches = rev2branch[revision]
1403 branches = rev2branch[revision]
1403 firstlocalbranch = None
1404 firstlocalbranch = None
1404 for b in branches:
1405 for b in branches:
1405 if b == 'refs/heads/master':
1406 if b == 'refs/heads/master':
1406 # master trumps all other branches
1407 # master trumps all other branches
1407 checkout(['refs/heads/master'])
1408 checkout(['refs/heads/master'])
1408 return
1409 return
1409 if not firstlocalbranch and not b.startswith('refs/remotes/'):
1410 if not firstlocalbranch and not b.startswith('refs/remotes/'):
1410 firstlocalbranch = b
1411 firstlocalbranch = b
1411 if firstlocalbranch:
1412 if firstlocalbranch:
1412 checkout([firstlocalbranch])
1413 checkout([firstlocalbranch])
1413 return
1414 return
1414
1415
1415 tracking = self._gittracking(branch2rev.keys())
1416 tracking = self._gittracking(branch2rev.keys())
1416 # choose a remote branch already tracked if possible
1417 # choose a remote branch already tracked if possible
1417 remote = branches[0]
1418 remote = branches[0]
1418 if remote not in tracking:
1419 if remote not in tracking:
1419 for b in branches:
1420 for b in branches:
1420 if b in tracking:
1421 if b in tracking:
1421 remote = b
1422 remote = b
1422 break
1423 break
1423
1424
1424 if remote not in tracking:
1425 if remote not in tracking:
1425 # create a new local tracking branch
1426 # create a new local tracking branch
1426 local = remote.split('/', 3)[3]
1427 local = remote.split('/', 3)[3]
1427 checkout(['-b', local, remote])
1428 checkout(['-b', local, remote])
1428 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
1429 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
1429 # When updating to a tracked remote branch,
1430 # When updating to a tracked remote branch,
1430 # if the local tracking branch is downstream of it,
1431 # if the local tracking branch is downstream of it,
1431 # a normal `git pull` would have performed a "fast-forward merge"
1432 # a normal `git pull` would have performed a "fast-forward merge"
1432 # which is equivalent to updating the local branch to the remote.
1433 # which is equivalent to updating the local branch to the remote.
1433 # Since we are only looking at branching at update, we need to
1434 # Since we are only looking at branching at update, we need to
1434 # detect this situation and perform this action lazily.
1435 # detect this situation and perform this action lazily.
1435 if tracking[remote] != self._gitcurrentbranch():
1436 if tracking[remote] != self._gitcurrentbranch():
1436 checkout([tracking[remote]])
1437 checkout([tracking[remote]])
1437 self._gitcommand(['merge', '--ff', remote])
1438 self._gitcommand(['merge', '--ff', remote])
1438 _sanitize(self.ui, self._abspath, '.git')
1439 _sanitize(self.ui, self._abspath, '.git')
1439 else:
1440 else:
1440 # a real merge would be required, just checkout the revision
1441 # a real merge would be required, just checkout the revision
1441 rawcheckout()
1442 rawcheckout()
1442
1443
1443 @annotatesubrepoerror
1444 @annotatesubrepoerror
1444 def commit(self, text, user, date):
1445 def commit(self, text, user, date):
1445 if self._gitmissing():
1446 if self._gitmissing():
1446 raise util.Abort(_("subrepo %s is missing") % self._relpath)
1447 raise util.Abort(_("subrepo %s is missing") % self._relpath)
1447 cmd = ['commit', '-a', '-m', text]
1448 cmd = ['commit', '-a', '-m', text]
1448 env = os.environ.copy()
1449 env = os.environ.copy()
1449 if user:
1450 if user:
1450 cmd += ['--author', user]
1451 cmd += ['--author', user]
1451 if date:
1452 if date:
1452 # git's date parser silently ignores when seconds < 1e9
1453 # git's date parser silently ignores when seconds < 1e9
1453 # convert to ISO8601
1454 # convert to ISO8601
1454 env['GIT_AUTHOR_DATE'] = util.datestr(date,
1455 env['GIT_AUTHOR_DATE'] = util.datestr(date,
1455 '%Y-%m-%dT%H:%M:%S %1%2')
1456 '%Y-%m-%dT%H:%M:%S %1%2')
1456 self._gitcommand(cmd, env=env)
1457 self._gitcommand(cmd, env=env)
1457 # make sure commit works otherwise HEAD might not exist under certain
1458 # make sure commit works otherwise HEAD might not exist under certain
1458 # circumstances
1459 # circumstances
1459 return self._gitstate()
1460 return self._gitstate()
1460
1461
1461 @annotatesubrepoerror
1462 @annotatesubrepoerror
1462 def merge(self, state):
1463 def merge(self, state):
1463 source, revision, kind = state
1464 source, revision, kind = state
1464 self._fetch(source, revision)
1465 self._fetch(source, revision)
1465 base = self._gitcommand(['merge-base', revision, self._state[1]])
1466 base = self._gitcommand(['merge-base', revision, self._state[1]])
1466 self._gitupdatestat()
1467 self._gitupdatestat()
1467 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1468 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1468
1469
1469 def mergefunc():
1470 def mergefunc():
1470 if base == revision:
1471 if base == revision:
1471 self.get(state) # fast forward merge
1472 self.get(state) # fast forward merge
1472 elif base != self._state[1]:
1473 elif base != self._state[1]:
1473 self._gitcommand(['merge', '--no-commit', revision])
1474 self._gitcommand(['merge', '--no-commit', revision])
1474 _sanitize(self.ui, self._abspath, '.git')
1475 _sanitize(self.ui, self._abspath, '.git')
1475
1476
1476 if self.dirty():
1477 if self.dirty():
1477 if self._gitstate() != revision:
1478 if self._gitstate() != revision:
1478 dirty = self._gitstate() == self._state[1] or code != 0
1479 dirty = self._gitstate() == self._state[1] or code != 0
1479 if _updateprompt(self.ui, self, dirty,
1480 if _updateprompt(self.ui, self, dirty,
1480 self._state[1][:7], revision[:7]):
1481 self._state[1][:7], revision[:7]):
1481 mergefunc()
1482 mergefunc()
1482 else:
1483 else:
1483 mergefunc()
1484 mergefunc()
1484
1485
1485 @annotatesubrepoerror
1486 @annotatesubrepoerror
1486 def push(self, opts):
1487 def push(self, opts):
1487 force = opts.get('force')
1488 force = opts.get('force')
1488
1489
1489 if not self._state[1]:
1490 if not self._state[1]:
1490 return True
1491 return True
1491 if self._gitmissing():
1492 if self._gitmissing():
1492 raise util.Abort(_("subrepo %s is missing") % self._relpath)
1493 raise util.Abort(_("subrepo %s is missing") % self._relpath)
1493 # if a branch in origin contains the revision, nothing to do
1494 # if a branch in origin contains the revision, nothing to do
1494 branch2rev, rev2branch = self._gitbranchmap()
1495 branch2rev, rev2branch = self._gitbranchmap()
1495 if self._state[1] in rev2branch:
1496 if self._state[1] in rev2branch:
1496 for b in rev2branch[self._state[1]]:
1497 for b in rev2branch[self._state[1]]:
1497 if b.startswith('refs/remotes/origin/'):
1498 if b.startswith('refs/remotes/origin/'):
1498 return True
1499 return True
1499 for b, revision in branch2rev.iteritems():
1500 for b, revision in branch2rev.iteritems():
1500 if b.startswith('refs/remotes/origin/'):
1501 if b.startswith('refs/remotes/origin/'):
1501 if self._gitisancestor(self._state[1], revision):
1502 if self._gitisancestor(self._state[1], revision):
1502 return True
1503 return True
1503 # otherwise, try to push the currently checked out branch
1504 # otherwise, try to push the currently checked out branch
1504 cmd = ['push']
1505 cmd = ['push']
1505 if force:
1506 if force:
1506 cmd.append('--force')
1507 cmd.append('--force')
1507
1508
1508 current = self._gitcurrentbranch()
1509 current = self._gitcurrentbranch()
1509 if current:
1510 if current:
1510 # determine if the current branch is even useful
1511 # determine if the current branch is even useful
1511 if not self._gitisancestor(self._state[1], current):
1512 if not self._gitisancestor(self._state[1], current):
1512 self.ui.warn(_('unrelated git branch checked out '
1513 self.ui.warn(_('unrelated git branch checked out '
1513 'in subrepo %s\n') % self._relpath)
1514 'in subrepo %s\n') % self._relpath)
1514 return False
1515 return False
1515 self.ui.status(_('pushing branch %s of subrepo %s\n') %
1516 self.ui.status(_('pushing branch %s of subrepo %s\n') %
1516 (current.split('/', 2)[2], self._relpath))
1517 (current.split('/', 2)[2], self._relpath))
1517 ret = self._gitdir(cmd + ['origin', current])
1518 ret = self._gitdir(cmd + ['origin', current])
1518 return ret[1] == 0
1519 return ret[1] == 0
1519 else:
1520 else:
1520 self.ui.warn(_('no branch checked out in subrepo %s\n'
1521 self.ui.warn(_('no branch checked out in subrepo %s\n'
1521 'cannot push revision %s\n') %
1522 'cannot push revision %s\n') %
1522 (self._relpath, self._state[1]))
1523 (self._relpath, self._state[1]))
1523 return False
1524 return False
1524
1525
1525 @annotatesubrepoerror
1526 @annotatesubrepoerror
1526 def remove(self):
1527 def remove(self):
1527 if self._gitmissing():
1528 if self._gitmissing():
1528 return
1529 return
1529 if self.dirty():
1530 if self.dirty():
1530 self.ui.warn(_('not removing repo %s because '
1531 self.ui.warn(_('not removing repo %s because '
1531 'it has changes.\n') % self._relpath)
1532 'it has changes.\n') % self._relpath)
1532 return
1533 return
1533 # we can't fully delete the repository as it may contain
1534 # we can't fully delete the repository as it may contain
1534 # local-only history
1535 # local-only history
1535 self.ui.note(_('removing subrepo %s\n') % self._relpath)
1536 self.ui.note(_('removing subrepo %s\n') % self._relpath)
1536 self._gitcommand(['config', 'core.bare', 'true'])
1537 self._gitcommand(['config', 'core.bare', 'true'])
1537 for f in os.listdir(self._abspath):
1538 for f in os.listdir(self._abspath):
1538 if f == '.git':
1539 if f == '.git':
1539 continue
1540 continue
1540 path = os.path.join(self._abspath, f)
1541 path = os.path.join(self._abspath, f)
1541 if os.path.isdir(path) and not os.path.islink(path):
1542 if os.path.isdir(path) and not os.path.islink(path):
1542 shutil.rmtree(path)
1543 shutil.rmtree(path)
1543 else:
1544 else:
1544 os.remove(path)
1545 os.remove(path)
1545
1546
1546 def archive(self, archiver, prefix, match=None):
1547 def archive(self, archiver, prefix, match=None):
1547 total = 0
1548 total = 0
1548 source, revision = self._state
1549 source, revision = self._state
1549 if not revision:
1550 if not revision:
1550 return total
1551 return total
1551 self._fetch(source, revision)
1552 self._fetch(source, revision)
1552
1553
1553 # Parse git's native archive command.
1554 # Parse git's native archive command.
1554 # This should be much faster than manually traversing the trees
1555 # This should be much faster than manually traversing the trees
1555 # and objects with many subprocess calls.
1556 # and objects with many subprocess calls.
1556 tarstream = self._gitcommand(['archive', revision], stream=True)
1557 tarstream = self._gitcommand(['archive', revision], stream=True)
1557 tar = tarfile.open(fileobj=tarstream, mode='r|')
1558 tar = tarfile.open(fileobj=tarstream, mode='r|')
1558 relpath = subrelpath(self)
1559 relpath = subrelpath(self)
1559 self.ui.progress(_('archiving (%s)') % relpath, 0, unit=_('files'))
1560 self.ui.progress(_('archiving (%s)') % relpath, 0, unit=_('files'))
1560 for i, info in enumerate(tar):
1561 for i, info in enumerate(tar):
1561 if info.isdir():
1562 if info.isdir():
1562 continue
1563 continue
1563 if match and not match(info.name):
1564 if match and not match(info.name):
1564 continue
1565 continue
1565 if info.issym():
1566 if info.issym():
1566 data = info.linkname
1567 data = info.linkname
1567 else:
1568 else:
1568 data = tar.extractfile(info).read()
1569 data = tar.extractfile(info).read()
1569 archiver.addfile(os.path.join(prefix, self._path, info.name),
1570 archiver.addfile(os.path.join(prefix, self._path, info.name),
1570 info.mode, info.issym(), data)
1571 info.mode, info.issym(), data)
1571 total += 1
1572 total += 1
1572 self.ui.progress(_('archiving (%s)') % relpath, i + 1,
1573 self.ui.progress(_('archiving (%s)') % relpath, i + 1,
1573 unit=_('files'))
1574 unit=_('files'))
1574 self.ui.progress(_('archiving (%s)') % relpath, None)
1575 self.ui.progress(_('archiving (%s)') % relpath, None)
1575 return total
1576 return total
1576
1577
1577
1578
1578 @annotatesubrepoerror
1579 @annotatesubrepoerror
1579 def status(self, rev2, **opts):
1580 def status(self, rev2, **opts):
1580 rev1 = self._state[1]
1581 rev1 = self._state[1]
1581 if self._gitmissing() or not rev1:
1582 if self._gitmissing() or not rev1:
1582 # if the repo is missing, return no results
1583 # if the repo is missing, return no results
1583 return [], [], [], [], [], [], []
1584 return [], [], [], [], [], [], []
1584 modified, added, removed = [], [], []
1585 modified, added, removed = [], [], []
1585 self._gitupdatestat()
1586 self._gitupdatestat()
1586 if rev2:
1587 if rev2:
1587 command = ['diff-tree', rev1, rev2]
1588 command = ['diff-tree', rev1, rev2]
1588 else:
1589 else:
1589 command = ['diff-index', rev1]
1590 command = ['diff-index', rev1]
1590 out = self._gitcommand(command)
1591 out = self._gitcommand(command)
1591 for line in out.split('\n'):
1592 for line in out.split('\n'):
1592 tab = line.find('\t')
1593 tab = line.find('\t')
1593 if tab == -1:
1594 if tab == -1:
1594 continue
1595 continue
1595 status, f = line[tab - 1], line[tab + 1:]
1596 status, f = line[tab - 1], line[tab + 1:]
1596 if status == 'M':
1597 if status == 'M':
1597 modified.append(f)
1598 modified.append(f)
1598 elif status == 'A':
1599 elif status == 'A':
1599 added.append(f)
1600 added.append(f)
1600 elif status == 'D':
1601 elif status == 'D':
1601 removed.append(f)
1602 removed.append(f)
1602
1603
1603 deleted, unknown, ignored, clean = [], [], [], []
1604 deleted, unknown, ignored, clean = [], [], [], []
1604
1605
1605 if not rev2:
1606 if not rev2:
1606 command = ['ls-files', '--others', '--exclude-standard']
1607 command = ['ls-files', '--others', '--exclude-standard']
1607 out = self._gitcommand(command)
1608 out = self._gitcommand(command)
1608 for line in out.split('\n'):
1609 for line in out.split('\n'):
1609 if len(line) == 0:
1610 if len(line) == 0:
1610 continue
1611 continue
1611 unknown.append(line)
1612 unknown.append(line)
1612
1613
1613 return scmutil.status(modified, added, removed, deleted,
1614 return scmutil.status(modified, added, removed, deleted,
1614 unknown, ignored, clean)
1615 unknown, ignored, clean)
1615
1616
1616 @annotatesubrepoerror
1617 @annotatesubrepoerror
1617 def diff(self, ui, diffopts, node2, match, prefix, **opts):
1618 def diff(self, ui, diffopts, node2, match, prefix, **opts):
1618 node1 = self._state[1]
1619 node1 = self._state[1]
1619 cmd = ['diff']
1620 cmd = ['diff']
1620 if opts['stat']:
1621 if opts['stat']:
1621 cmd.append('--stat')
1622 cmd.append('--stat')
1622 else:
1623 else:
1623 # for Git, this also implies '-p'
1624 # for Git, this also implies '-p'
1624 cmd.append('-U%d' % diffopts.context)
1625 cmd.append('-U%d' % diffopts.context)
1625
1626
1626 gitprefix = os.path.join(prefix, self._path)
1627 gitprefix = os.path.join(prefix, self._path)
1627
1628
1628 if diffopts.noprefix:
1629 if diffopts.noprefix:
1629 cmd.extend(['--src-prefix=%s/' % gitprefix,
1630 cmd.extend(['--src-prefix=%s/' % gitprefix,
1630 '--dst-prefix=%s/' % gitprefix])
1631 '--dst-prefix=%s/' % gitprefix])
1631 else:
1632 else:
1632 cmd.extend(['--src-prefix=a/%s/' % gitprefix,
1633 cmd.extend(['--src-prefix=a/%s/' % gitprefix,
1633 '--dst-prefix=b/%s/' % gitprefix])
1634 '--dst-prefix=b/%s/' % gitprefix])
1634
1635
1635 if diffopts.ignorews:
1636 if diffopts.ignorews:
1636 cmd.append('--ignore-all-space')
1637 cmd.append('--ignore-all-space')
1637 if diffopts.ignorewsamount:
1638 if diffopts.ignorewsamount:
1638 cmd.append('--ignore-space-change')
1639 cmd.append('--ignore-space-change')
1639 if self._gitversion(self._gitcommand(['--version'])) >= (1, 8, 4) \
1640 if self._gitversion(self._gitcommand(['--version'])) >= (1, 8, 4) \
1640 and diffopts.ignoreblanklines:
1641 and diffopts.ignoreblanklines:
1641 cmd.append('--ignore-blank-lines')
1642 cmd.append('--ignore-blank-lines')
1642
1643
1643 cmd.append(node1)
1644 cmd.append(node1)
1644 if node2:
1645 if node2:
1645 cmd.append(node2)
1646 cmd.append(node2)
1646
1647
1647 if match.anypats():
1648 if match.anypats():
1648 return #No support for include/exclude yet
1649 return #No support for include/exclude yet
1649
1650
1650 if match.always():
1651 if match.always():
1651 ui.write(self._gitcommand(cmd))
1652 ui.write(self._gitcommand(cmd))
1652 elif match.files():
1653 elif match.files():
1653 for f in match.files():
1654 for f in match.files():
1654 ui.write(self._gitcommand(cmd + [f]))
1655 ui.write(self._gitcommand(cmd + [f]))
1655 elif match(gitprefix): #Subrepo is matched
1656 elif match(gitprefix): #Subrepo is matched
1656 ui.write(self._gitcommand(cmd))
1657 ui.write(self._gitcommand(cmd))
1657
1658
1658 @annotatesubrepoerror
1659 @annotatesubrepoerror
1659 def revert(self, substate, *pats, **opts):
1660 def revert(self, substate, *pats, **opts):
1660 self.ui.status(_('reverting subrepo %s\n') % substate[0])
1661 self.ui.status(_('reverting subrepo %s\n') % substate[0])
1661 if not opts.get('no_backup'):
1662 if not opts.get('no_backup'):
1662 status = self.status(None)
1663 status = self.status(None)
1663 names = status.modified
1664 names = status.modified
1664 for name in names:
1665 for name in names:
1665 bakname = "%s.orig" % name
1666 bakname = "%s.orig" % name
1666 self.ui.note(_('saving current version of %s as %s\n') %
1667 self.ui.note(_('saving current version of %s as %s\n') %
1667 (name, bakname))
1668 (name, bakname))
1668 util.rename(os.path.join(self._abspath, name),
1669 util.rename(os.path.join(self._abspath, name),
1669 os.path.join(self._abspath, bakname))
1670 os.path.join(self._abspath, bakname))
1670
1671
1671 self.get(substate, overwrite=True)
1672 self.get(substate, overwrite=True)
1672 return []
1673 return []
1673
1674
1674 def shortid(self, revid):
1675 def shortid(self, revid):
1675 return revid[:7]
1676 return revid[:7]
1676
1677
1677 types = {
1678 types = {
1678 'hg': hgsubrepo,
1679 'hg': hgsubrepo,
1679 'svn': svnsubrepo,
1680 'svn': svnsubrepo,
1680 'git': gitsubrepo,
1681 'git': gitsubrepo,
1681 }
1682 }
General Comments 0
You need to be logged in to leave comments. Login now