##// END OF EJS Templates
copies: split the copies api for "normal" and merge cases (API)
Matt Mackall -
r15774:0bd17a4b default
parent child Browse files
Show More
@@ -1,1282 +1,1282 b''
1 # cmdutil.py - help for command processing in mercurial
1 # cmdutil.py - help for command processing in mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from node import hex, nullid, nullrev, short
8 from node import hex, nullid, nullrev, short
9 from i18n import _
9 from i18n import _
10 import os, sys, errno, re, tempfile
10 import os, sys, errno, re, tempfile
11 import util, scmutil, templater, patch, error, templatekw, revlog, copies
11 import util, scmutil, templater, patch, error, templatekw, revlog, copies
12 import match as matchmod
12 import match as matchmod
13 import subrepo
13 import subrepo
14
14
15 def parsealiases(cmd):
15 def parsealiases(cmd):
16 return cmd.lstrip("^").split("|")
16 return cmd.lstrip("^").split("|")
17
17
18 def findpossible(cmd, table, strict=False):
18 def findpossible(cmd, table, strict=False):
19 """
19 """
20 Return cmd -> (aliases, command table entry)
20 Return cmd -> (aliases, command table entry)
21 for each matching command.
21 for each matching command.
22 Return debug commands (or their aliases) only if no normal command matches.
22 Return debug commands (or their aliases) only if no normal command matches.
23 """
23 """
24 choice = {}
24 choice = {}
25 debugchoice = {}
25 debugchoice = {}
26
26
27 if cmd in table:
27 if cmd in table:
28 # short-circuit exact matches, "log" alias beats "^log|history"
28 # short-circuit exact matches, "log" alias beats "^log|history"
29 keys = [cmd]
29 keys = [cmd]
30 else:
30 else:
31 keys = table.keys()
31 keys = table.keys()
32
32
33 for e in keys:
33 for e in keys:
34 aliases = parsealiases(e)
34 aliases = parsealiases(e)
35 found = None
35 found = None
36 if cmd in aliases:
36 if cmd in aliases:
37 found = cmd
37 found = cmd
38 elif not strict:
38 elif not strict:
39 for a in aliases:
39 for a in aliases:
40 if a.startswith(cmd):
40 if a.startswith(cmd):
41 found = a
41 found = a
42 break
42 break
43 if found is not None:
43 if found is not None:
44 if aliases[0].startswith("debug") or found.startswith("debug"):
44 if aliases[0].startswith("debug") or found.startswith("debug"):
45 debugchoice[found] = (aliases, table[e])
45 debugchoice[found] = (aliases, table[e])
46 else:
46 else:
47 choice[found] = (aliases, table[e])
47 choice[found] = (aliases, table[e])
48
48
49 if not choice and debugchoice:
49 if not choice and debugchoice:
50 choice = debugchoice
50 choice = debugchoice
51
51
52 return choice
52 return choice
53
53
54 def findcmd(cmd, table, strict=True):
54 def findcmd(cmd, table, strict=True):
55 """Return (aliases, command table entry) for command string."""
55 """Return (aliases, command table entry) for command string."""
56 choice = findpossible(cmd, table, strict)
56 choice = findpossible(cmd, table, strict)
57
57
58 if cmd in choice:
58 if cmd in choice:
59 return choice[cmd]
59 return choice[cmd]
60
60
61 if len(choice) > 1:
61 if len(choice) > 1:
62 clist = choice.keys()
62 clist = choice.keys()
63 clist.sort()
63 clist.sort()
64 raise error.AmbiguousCommand(cmd, clist)
64 raise error.AmbiguousCommand(cmd, clist)
65
65
66 if choice:
66 if choice:
67 return choice.values()[0]
67 return choice.values()[0]
68
68
69 raise error.UnknownCommand(cmd)
69 raise error.UnknownCommand(cmd)
70
70
71 def findrepo(p):
71 def findrepo(p):
72 while not os.path.isdir(os.path.join(p, ".hg")):
72 while not os.path.isdir(os.path.join(p, ".hg")):
73 oldp, p = p, os.path.dirname(p)
73 oldp, p = p, os.path.dirname(p)
74 if p == oldp:
74 if p == oldp:
75 return None
75 return None
76
76
77 return p
77 return p
78
78
79 def bailifchanged(repo):
79 def bailifchanged(repo):
80 if repo.dirstate.p2() != nullid:
80 if repo.dirstate.p2() != nullid:
81 raise util.Abort(_('outstanding uncommitted merge'))
81 raise util.Abort(_('outstanding uncommitted merge'))
82 modified, added, removed, deleted = repo.status()[:4]
82 modified, added, removed, deleted = repo.status()[:4]
83 if modified or added or removed or deleted:
83 if modified or added or removed or deleted:
84 raise util.Abort(_("outstanding uncommitted changes"))
84 raise util.Abort(_("outstanding uncommitted changes"))
85 ctx = repo[None]
85 ctx = repo[None]
86 for s in ctx.substate:
86 for s in ctx.substate:
87 if ctx.sub(s).dirty():
87 if ctx.sub(s).dirty():
88 raise util.Abort(_("uncommitted changes in subrepo %s") % s)
88 raise util.Abort(_("uncommitted changes in subrepo %s") % s)
89
89
90 def logmessage(ui, opts):
90 def logmessage(ui, opts):
91 """ get the log message according to -m and -l option """
91 """ get the log message according to -m and -l option """
92 message = opts.get('message')
92 message = opts.get('message')
93 logfile = opts.get('logfile')
93 logfile = opts.get('logfile')
94
94
95 if message and logfile:
95 if message and logfile:
96 raise util.Abort(_('options --message and --logfile are mutually '
96 raise util.Abort(_('options --message and --logfile are mutually '
97 'exclusive'))
97 'exclusive'))
98 if not message and logfile:
98 if not message and logfile:
99 try:
99 try:
100 if logfile == '-':
100 if logfile == '-':
101 message = ui.fin.read()
101 message = ui.fin.read()
102 else:
102 else:
103 message = '\n'.join(util.readfile(logfile).splitlines())
103 message = '\n'.join(util.readfile(logfile).splitlines())
104 except IOError, inst:
104 except IOError, inst:
105 raise util.Abort(_("can't read commit message '%s': %s") %
105 raise util.Abort(_("can't read commit message '%s': %s") %
106 (logfile, inst.strerror))
106 (logfile, inst.strerror))
107 return message
107 return message
108
108
109 def loglimit(opts):
109 def loglimit(opts):
110 """get the log limit according to option -l/--limit"""
110 """get the log limit according to option -l/--limit"""
111 limit = opts.get('limit')
111 limit = opts.get('limit')
112 if limit:
112 if limit:
113 try:
113 try:
114 limit = int(limit)
114 limit = int(limit)
115 except ValueError:
115 except ValueError:
116 raise util.Abort(_('limit must be a positive integer'))
116 raise util.Abort(_('limit must be a positive integer'))
117 if limit <= 0:
117 if limit <= 0:
118 raise util.Abort(_('limit must be positive'))
118 raise util.Abort(_('limit must be positive'))
119 else:
119 else:
120 limit = None
120 limit = None
121 return limit
121 return limit
122
122
123 def makefilename(repo, pat, node, desc=None,
123 def makefilename(repo, pat, node, desc=None,
124 total=None, seqno=None, revwidth=None, pathname=None):
124 total=None, seqno=None, revwidth=None, pathname=None):
125 node_expander = {
125 node_expander = {
126 'H': lambda: hex(node),
126 'H': lambda: hex(node),
127 'R': lambda: str(repo.changelog.rev(node)),
127 'R': lambda: str(repo.changelog.rev(node)),
128 'h': lambda: short(node),
128 'h': lambda: short(node),
129 'm': lambda: re.sub('[^\w]', '_', str(desc))
129 'm': lambda: re.sub('[^\w]', '_', str(desc))
130 }
130 }
131 expander = {
131 expander = {
132 '%': lambda: '%',
132 '%': lambda: '%',
133 'b': lambda: os.path.basename(repo.root),
133 'b': lambda: os.path.basename(repo.root),
134 }
134 }
135
135
136 try:
136 try:
137 if node:
137 if node:
138 expander.update(node_expander)
138 expander.update(node_expander)
139 if node:
139 if node:
140 expander['r'] = (lambda:
140 expander['r'] = (lambda:
141 str(repo.changelog.rev(node)).zfill(revwidth or 0))
141 str(repo.changelog.rev(node)).zfill(revwidth or 0))
142 if total is not None:
142 if total is not None:
143 expander['N'] = lambda: str(total)
143 expander['N'] = lambda: str(total)
144 if seqno is not None:
144 if seqno is not None:
145 expander['n'] = lambda: str(seqno)
145 expander['n'] = lambda: str(seqno)
146 if total is not None and seqno is not None:
146 if total is not None and seqno is not None:
147 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
147 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
148 if pathname is not None:
148 if pathname is not None:
149 expander['s'] = lambda: os.path.basename(pathname)
149 expander['s'] = lambda: os.path.basename(pathname)
150 expander['d'] = lambda: os.path.dirname(pathname) or '.'
150 expander['d'] = lambda: os.path.dirname(pathname) or '.'
151 expander['p'] = lambda: pathname
151 expander['p'] = lambda: pathname
152
152
153 newname = []
153 newname = []
154 patlen = len(pat)
154 patlen = len(pat)
155 i = 0
155 i = 0
156 while i < patlen:
156 while i < patlen:
157 c = pat[i]
157 c = pat[i]
158 if c == '%':
158 if c == '%':
159 i += 1
159 i += 1
160 c = pat[i]
160 c = pat[i]
161 c = expander[c]()
161 c = expander[c]()
162 newname.append(c)
162 newname.append(c)
163 i += 1
163 i += 1
164 return ''.join(newname)
164 return ''.join(newname)
165 except KeyError, inst:
165 except KeyError, inst:
166 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
166 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
167 inst.args[0])
167 inst.args[0])
168
168
169 def makefileobj(repo, pat, node=None, desc=None, total=None,
169 def makefileobj(repo, pat, node=None, desc=None, total=None,
170 seqno=None, revwidth=None, mode='wb', pathname=None):
170 seqno=None, revwidth=None, mode='wb', pathname=None):
171
171
172 writable = mode not in ('r', 'rb')
172 writable = mode not in ('r', 'rb')
173
173
174 if not pat or pat == '-':
174 if not pat or pat == '-':
175 fp = writable and repo.ui.fout or repo.ui.fin
175 fp = writable and repo.ui.fout or repo.ui.fin
176 if util.safehasattr(fp, 'fileno'):
176 if util.safehasattr(fp, 'fileno'):
177 return os.fdopen(os.dup(fp.fileno()), mode)
177 return os.fdopen(os.dup(fp.fileno()), mode)
178 else:
178 else:
179 # if this fp can't be duped properly, return
179 # if this fp can't be duped properly, return
180 # a dummy object that can be closed
180 # a dummy object that can be closed
181 class wrappedfileobj(object):
181 class wrappedfileobj(object):
182 noop = lambda x: None
182 noop = lambda x: None
183 def __init__(self, f):
183 def __init__(self, f):
184 self.f = f
184 self.f = f
185 def __getattr__(self, attr):
185 def __getattr__(self, attr):
186 if attr == 'close':
186 if attr == 'close':
187 return self.noop
187 return self.noop
188 else:
188 else:
189 return getattr(self.f, attr)
189 return getattr(self.f, attr)
190
190
191 return wrappedfileobj(fp)
191 return wrappedfileobj(fp)
192 if util.safehasattr(pat, 'write') and writable:
192 if util.safehasattr(pat, 'write') and writable:
193 return pat
193 return pat
194 if util.safehasattr(pat, 'read') and 'r' in mode:
194 if util.safehasattr(pat, 'read') and 'r' in mode:
195 return pat
195 return pat
196 return open(makefilename(repo, pat, node, desc, total, seqno, revwidth,
196 return open(makefilename(repo, pat, node, desc, total, seqno, revwidth,
197 pathname),
197 pathname),
198 mode)
198 mode)
199
199
200 def openrevlog(repo, cmd, file_, opts):
200 def openrevlog(repo, cmd, file_, opts):
201 """opens the changelog, manifest, a filelog or a given revlog"""
201 """opens the changelog, manifest, a filelog or a given revlog"""
202 cl = opts['changelog']
202 cl = opts['changelog']
203 mf = opts['manifest']
203 mf = opts['manifest']
204 msg = None
204 msg = None
205 if cl and mf:
205 if cl and mf:
206 msg = _('cannot specify --changelog and --manifest at the same time')
206 msg = _('cannot specify --changelog and --manifest at the same time')
207 elif cl or mf:
207 elif cl or mf:
208 if file_:
208 if file_:
209 msg = _('cannot specify filename with --changelog or --manifest')
209 msg = _('cannot specify filename with --changelog or --manifest')
210 elif not repo:
210 elif not repo:
211 msg = _('cannot specify --changelog or --manifest '
211 msg = _('cannot specify --changelog or --manifest '
212 'without a repository')
212 'without a repository')
213 if msg:
213 if msg:
214 raise util.Abort(msg)
214 raise util.Abort(msg)
215
215
216 r = None
216 r = None
217 if repo:
217 if repo:
218 if cl:
218 if cl:
219 r = repo.changelog
219 r = repo.changelog
220 elif mf:
220 elif mf:
221 r = repo.manifest
221 r = repo.manifest
222 elif file_:
222 elif file_:
223 filelog = repo.file(file_)
223 filelog = repo.file(file_)
224 if len(filelog):
224 if len(filelog):
225 r = filelog
225 r = filelog
226 if not r:
226 if not r:
227 if not file_:
227 if not file_:
228 raise error.CommandError(cmd, _('invalid arguments'))
228 raise error.CommandError(cmd, _('invalid arguments'))
229 if not os.path.isfile(file_):
229 if not os.path.isfile(file_):
230 raise util.Abort(_("revlog '%s' not found") % file_)
230 raise util.Abort(_("revlog '%s' not found") % file_)
231 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
231 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
232 file_[:-2] + ".i")
232 file_[:-2] + ".i")
233 return r
233 return r
234
234
235 def copy(ui, repo, pats, opts, rename=False):
235 def copy(ui, repo, pats, opts, rename=False):
236 # called with the repo lock held
236 # called with the repo lock held
237 #
237 #
238 # hgsep => pathname that uses "/" to separate directories
238 # hgsep => pathname that uses "/" to separate directories
239 # ossep => pathname that uses os.sep to separate directories
239 # ossep => pathname that uses os.sep to separate directories
240 cwd = repo.getcwd()
240 cwd = repo.getcwd()
241 targets = {}
241 targets = {}
242 after = opts.get("after")
242 after = opts.get("after")
243 dryrun = opts.get("dry_run")
243 dryrun = opts.get("dry_run")
244 wctx = repo[None]
244 wctx = repo[None]
245
245
246 def walkpat(pat):
246 def walkpat(pat):
247 srcs = []
247 srcs = []
248 badstates = after and '?' or '?r'
248 badstates = after and '?' or '?r'
249 m = scmutil.match(repo[None], [pat], opts, globbed=True)
249 m = scmutil.match(repo[None], [pat], opts, globbed=True)
250 for abs in repo.walk(m):
250 for abs in repo.walk(m):
251 state = repo.dirstate[abs]
251 state = repo.dirstate[abs]
252 rel = m.rel(abs)
252 rel = m.rel(abs)
253 exact = m.exact(abs)
253 exact = m.exact(abs)
254 if state in badstates:
254 if state in badstates:
255 if exact and state == '?':
255 if exact and state == '?':
256 ui.warn(_('%s: not copying - file is not managed\n') % rel)
256 ui.warn(_('%s: not copying - file is not managed\n') % rel)
257 if exact and state == 'r':
257 if exact and state == 'r':
258 ui.warn(_('%s: not copying - file has been marked for'
258 ui.warn(_('%s: not copying - file has been marked for'
259 ' remove\n') % rel)
259 ' remove\n') % rel)
260 continue
260 continue
261 # abs: hgsep
261 # abs: hgsep
262 # rel: ossep
262 # rel: ossep
263 srcs.append((abs, rel, exact))
263 srcs.append((abs, rel, exact))
264 return srcs
264 return srcs
265
265
266 # abssrc: hgsep
266 # abssrc: hgsep
267 # relsrc: ossep
267 # relsrc: ossep
268 # otarget: ossep
268 # otarget: ossep
269 def copyfile(abssrc, relsrc, otarget, exact):
269 def copyfile(abssrc, relsrc, otarget, exact):
270 abstarget = scmutil.canonpath(repo.root, cwd, otarget)
270 abstarget = scmutil.canonpath(repo.root, cwd, otarget)
271 reltarget = repo.pathto(abstarget, cwd)
271 reltarget = repo.pathto(abstarget, cwd)
272 target = repo.wjoin(abstarget)
272 target = repo.wjoin(abstarget)
273 src = repo.wjoin(abssrc)
273 src = repo.wjoin(abssrc)
274 state = repo.dirstate[abstarget]
274 state = repo.dirstate[abstarget]
275
275
276 scmutil.checkportable(ui, abstarget)
276 scmutil.checkportable(ui, abstarget)
277
277
278 # check for collisions
278 # check for collisions
279 prevsrc = targets.get(abstarget)
279 prevsrc = targets.get(abstarget)
280 if prevsrc is not None:
280 if prevsrc is not None:
281 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
281 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
282 (reltarget, repo.pathto(abssrc, cwd),
282 (reltarget, repo.pathto(abssrc, cwd),
283 repo.pathto(prevsrc, cwd)))
283 repo.pathto(prevsrc, cwd)))
284 return
284 return
285
285
286 # check for overwrites
286 # check for overwrites
287 exists = os.path.lexists(target)
287 exists = os.path.lexists(target)
288 if not after and exists or after and state in 'mn':
288 if not after and exists or after and state in 'mn':
289 if not opts['force']:
289 if not opts['force']:
290 ui.warn(_('%s: not overwriting - file exists\n') %
290 ui.warn(_('%s: not overwriting - file exists\n') %
291 reltarget)
291 reltarget)
292 return
292 return
293
293
294 if after:
294 if after:
295 if not exists:
295 if not exists:
296 if rename:
296 if rename:
297 ui.warn(_('%s: not recording move - %s does not exist\n') %
297 ui.warn(_('%s: not recording move - %s does not exist\n') %
298 (relsrc, reltarget))
298 (relsrc, reltarget))
299 else:
299 else:
300 ui.warn(_('%s: not recording copy - %s does not exist\n') %
300 ui.warn(_('%s: not recording copy - %s does not exist\n') %
301 (relsrc, reltarget))
301 (relsrc, reltarget))
302 return
302 return
303 elif not dryrun:
303 elif not dryrun:
304 try:
304 try:
305 if exists:
305 if exists:
306 os.unlink(target)
306 os.unlink(target)
307 targetdir = os.path.dirname(target) or '.'
307 targetdir = os.path.dirname(target) or '.'
308 if not os.path.isdir(targetdir):
308 if not os.path.isdir(targetdir):
309 os.makedirs(targetdir)
309 os.makedirs(targetdir)
310 util.copyfile(src, target)
310 util.copyfile(src, target)
311 srcexists = True
311 srcexists = True
312 except IOError, inst:
312 except IOError, inst:
313 if inst.errno == errno.ENOENT:
313 if inst.errno == errno.ENOENT:
314 ui.warn(_('%s: deleted in working copy\n') % relsrc)
314 ui.warn(_('%s: deleted in working copy\n') % relsrc)
315 srcexists = False
315 srcexists = False
316 else:
316 else:
317 ui.warn(_('%s: cannot copy - %s\n') %
317 ui.warn(_('%s: cannot copy - %s\n') %
318 (relsrc, inst.strerror))
318 (relsrc, inst.strerror))
319 return True # report a failure
319 return True # report a failure
320
320
321 if ui.verbose or not exact:
321 if ui.verbose or not exact:
322 if rename:
322 if rename:
323 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
323 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
324 else:
324 else:
325 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
325 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
326
326
327 targets[abstarget] = abssrc
327 targets[abstarget] = abssrc
328
328
329 # fix up dirstate
329 # fix up dirstate
330 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
330 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
331 dryrun=dryrun, cwd=cwd)
331 dryrun=dryrun, cwd=cwd)
332 if rename and not dryrun:
332 if rename and not dryrun:
333 if not after and srcexists:
333 if not after and srcexists:
334 util.unlinkpath(repo.wjoin(abssrc))
334 util.unlinkpath(repo.wjoin(abssrc))
335 wctx.forget([abssrc])
335 wctx.forget([abssrc])
336
336
337 # pat: ossep
337 # pat: ossep
338 # dest ossep
338 # dest ossep
339 # srcs: list of (hgsep, hgsep, ossep, bool)
339 # srcs: list of (hgsep, hgsep, ossep, bool)
340 # return: function that takes hgsep and returns ossep
340 # return: function that takes hgsep and returns ossep
341 def targetpathfn(pat, dest, srcs):
341 def targetpathfn(pat, dest, srcs):
342 if os.path.isdir(pat):
342 if os.path.isdir(pat):
343 abspfx = scmutil.canonpath(repo.root, cwd, pat)
343 abspfx = scmutil.canonpath(repo.root, cwd, pat)
344 abspfx = util.localpath(abspfx)
344 abspfx = util.localpath(abspfx)
345 if destdirexists:
345 if destdirexists:
346 striplen = len(os.path.split(abspfx)[0])
346 striplen = len(os.path.split(abspfx)[0])
347 else:
347 else:
348 striplen = len(abspfx)
348 striplen = len(abspfx)
349 if striplen:
349 if striplen:
350 striplen += len(os.sep)
350 striplen += len(os.sep)
351 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
351 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
352 elif destdirexists:
352 elif destdirexists:
353 res = lambda p: os.path.join(dest,
353 res = lambda p: os.path.join(dest,
354 os.path.basename(util.localpath(p)))
354 os.path.basename(util.localpath(p)))
355 else:
355 else:
356 res = lambda p: dest
356 res = lambda p: dest
357 return res
357 return res
358
358
359 # pat: ossep
359 # pat: ossep
360 # dest ossep
360 # dest ossep
361 # srcs: list of (hgsep, hgsep, ossep, bool)
361 # srcs: list of (hgsep, hgsep, ossep, bool)
362 # return: function that takes hgsep and returns ossep
362 # return: function that takes hgsep and returns ossep
363 def targetpathafterfn(pat, dest, srcs):
363 def targetpathafterfn(pat, dest, srcs):
364 if matchmod.patkind(pat):
364 if matchmod.patkind(pat):
365 # a mercurial pattern
365 # a mercurial pattern
366 res = lambda p: os.path.join(dest,
366 res = lambda p: os.path.join(dest,
367 os.path.basename(util.localpath(p)))
367 os.path.basename(util.localpath(p)))
368 else:
368 else:
369 abspfx = scmutil.canonpath(repo.root, cwd, pat)
369 abspfx = scmutil.canonpath(repo.root, cwd, pat)
370 if len(abspfx) < len(srcs[0][0]):
370 if len(abspfx) < len(srcs[0][0]):
371 # A directory. Either the target path contains the last
371 # A directory. Either the target path contains the last
372 # component of the source path or it does not.
372 # component of the source path or it does not.
373 def evalpath(striplen):
373 def evalpath(striplen):
374 score = 0
374 score = 0
375 for s in srcs:
375 for s in srcs:
376 t = os.path.join(dest, util.localpath(s[0])[striplen:])
376 t = os.path.join(dest, util.localpath(s[0])[striplen:])
377 if os.path.lexists(t):
377 if os.path.lexists(t):
378 score += 1
378 score += 1
379 return score
379 return score
380
380
381 abspfx = util.localpath(abspfx)
381 abspfx = util.localpath(abspfx)
382 striplen = len(abspfx)
382 striplen = len(abspfx)
383 if striplen:
383 if striplen:
384 striplen += len(os.sep)
384 striplen += len(os.sep)
385 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
385 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
386 score = evalpath(striplen)
386 score = evalpath(striplen)
387 striplen1 = len(os.path.split(abspfx)[0])
387 striplen1 = len(os.path.split(abspfx)[0])
388 if striplen1:
388 if striplen1:
389 striplen1 += len(os.sep)
389 striplen1 += len(os.sep)
390 if evalpath(striplen1) > score:
390 if evalpath(striplen1) > score:
391 striplen = striplen1
391 striplen = striplen1
392 res = lambda p: os.path.join(dest,
392 res = lambda p: os.path.join(dest,
393 util.localpath(p)[striplen:])
393 util.localpath(p)[striplen:])
394 else:
394 else:
395 # a file
395 # a file
396 if destdirexists:
396 if destdirexists:
397 res = lambda p: os.path.join(dest,
397 res = lambda p: os.path.join(dest,
398 os.path.basename(util.localpath(p)))
398 os.path.basename(util.localpath(p)))
399 else:
399 else:
400 res = lambda p: dest
400 res = lambda p: dest
401 return res
401 return res
402
402
403
403
404 pats = scmutil.expandpats(pats)
404 pats = scmutil.expandpats(pats)
405 if not pats:
405 if not pats:
406 raise util.Abort(_('no source or destination specified'))
406 raise util.Abort(_('no source or destination specified'))
407 if len(pats) == 1:
407 if len(pats) == 1:
408 raise util.Abort(_('no destination specified'))
408 raise util.Abort(_('no destination specified'))
409 dest = pats.pop()
409 dest = pats.pop()
410 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
410 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
411 if not destdirexists:
411 if not destdirexists:
412 if len(pats) > 1 or matchmod.patkind(pats[0]):
412 if len(pats) > 1 or matchmod.patkind(pats[0]):
413 raise util.Abort(_('with multiple sources, destination must be an '
413 raise util.Abort(_('with multiple sources, destination must be an '
414 'existing directory'))
414 'existing directory'))
415 if util.endswithsep(dest):
415 if util.endswithsep(dest):
416 raise util.Abort(_('destination %s is not a directory') % dest)
416 raise util.Abort(_('destination %s is not a directory') % dest)
417
417
418 tfn = targetpathfn
418 tfn = targetpathfn
419 if after:
419 if after:
420 tfn = targetpathafterfn
420 tfn = targetpathafterfn
421 copylist = []
421 copylist = []
422 for pat in pats:
422 for pat in pats:
423 srcs = walkpat(pat)
423 srcs = walkpat(pat)
424 if not srcs:
424 if not srcs:
425 continue
425 continue
426 copylist.append((tfn(pat, dest, srcs), srcs))
426 copylist.append((tfn(pat, dest, srcs), srcs))
427 if not copylist:
427 if not copylist:
428 raise util.Abort(_('no files to copy'))
428 raise util.Abort(_('no files to copy'))
429
429
430 errors = 0
430 errors = 0
431 for targetpath, srcs in copylist:
431 for targetpath, srcs in copylist:
432 for abssrc, relsrc, exact in srcs:
432 for abssrc, relsrc, exact in srcs:
433 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
433 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
434 errors += 1
434 errors += 1
435
435
436 if errors:
436 if errors:
437 ui.warn(_('(consider using --after)\n'))
437 ui.warn(_('(consider using --after)\n'))
438
438
439 return errors != 0
439 return errors != 0
440
440
441 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
441 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
442 runargs=None, appendpid=False):
442 runargs=None, appendpid=False):
443 '''Run a command as a service.'''
443 '''Run a command as a service.'''
444
444
445 if opts['daemon'] and not opts['daemon_pipefds']:
445 if opts['daemon'] and not opts['daemon_pipefds']:
446 # Signal child process startup with file removal
446 # Signal child process startup with file removal
447 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
447 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
448 os.close(lockfd)
448 os.close(lockfd)
449 try:
449 try:
450 if not runargs:
450 if not runargs:
451 runargs = util.hgcmd() + sys.argv[1:]
451 runargs = util.hgcmd() + sys.argv[1:]
452 runargs.append('--daemon-pipefds=%s' % lockpath)
452 runargs.append('--daemon-pipefds=%s' % lockpath)
453 # Don't pass --cwd to the child process, because we've already
453 # Don't pass --cwd to the child process, because we've already
454 # changed directory.
454 # changed directory.
455 for i in xrange(1, len(runargs)):
455 for i in xrange(1, len(runargs)):
456 if runargs[i].startswith('--cwd='):
456 if runargs[i].startswith('--cwd='):
457 del runargs[i]
457 del runargs[i]
458 break
458 break
459 elif runargs[i].startswith('--cwd'):
459 elif runargs[i].startswith('--cwd'):
460 del runargs[i:i + 2]
460 del runargs[i:i + 2]
461 break
461 break
462 def condfn():
462 def condfn():
463 return not os.path.exists(lockpath)
463 return not os.path.exists(lockpath)
464 pid = util.rundetached(runargs, condfn)
464 pid = util.rundetached(runargs, condfn)
465 if pid < 0:
465 if pid < 0:
466 raise util.Abort(_('child process failed to start'))
466 raise util.Abort(_('child process failed to start'))
467 finally:
467 finally:
468 try:
468 try:
469 os.unlink(lockpath)
469 os.unlink(lockpath)
470 except OSError, e:
470 except OSError, e:
471 if e.errno != errno.ENOENT:
471 if e.errno != errno.ENOENT:
472 raise
472 raise
473 if parentfn:
473 if parentfn:
474 return parentfn(pid)
474 return parentfn(pid)
475 else:
475 else:
476 return
476 return
477
477
478 if initfn:
478 if initfn:
479 initfn()
479 initfn()
480
480
481 if opts['pid_file']:
481 if opts['pid_file']:
482 mode = appendpid and 'a' or 'w'
482 mode = appendpid and 'a' or 'w'
483 fp = open(opts['pid_file'], mode)
483 fp = open(opts['pid_file'], mode)
484 fp.write(str(os.getpid()) + '\n')
484 fp.write(str(os.getpid()) + '\n')
485 fp.close()
485 fp.close()
486
486
487 if opts['daemon_pipefds']:
487 if opts['daemon_pipefds']:
488 lockpath = opts['daemon_pipefds']
488 lockpath = opts['daemon_pipefds']
489 try:
489 try:
490 os.setsid()
490 os.setsid()
491 except AttributeError:
491 except AttributeError:
492 pass
492 pass
493 os.unlink(lockpath)
493 os.unlink(lockpath)
494 util.hidewindow()
494 util.hidewindow()
495 sys.stdout.flush()
495 sys.stdout.flush()
496 sys.stderr.flush()
496 sys.stderr.flush()
497
497
498 nullfd = os.open(util.nulldev, os.O_RDWR)
498 nullfd = os.open(util.nulldev, os.O_RDWR)
499 logfilefd = nullfd
499 logfilefd = nullfd
500 if logfile:
500 if logfile:
501 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
501 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
502 os.dup2(nullfd, 0)
502 os.dup2(nullfd, 0)
503 os.dup2(logfilefd, 1)
503 os.dup2(logfilefd, 1)
504 os.dup2(logfilefd, 2)
504 os.dup2(logfilefd, 2)
505 if nullfd not in (0, 1, 2):
505 if nullfd not in (0, 1, 2):
506 os.close(nullfd)
506 os.close(nullfd)
507 if logfile and logfilefd not in (0, 1, 2):
507 if logfile and logfilefd not in (0, 1, 2):
508 os.close(logfilefd)
508 os.close(logfilefd)
509
509
510 if runfn:
510 if runfn:
511 return runfn()
511 return runfn()
512
512
513 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
513 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
514 opts=None):
514 opts=None):
515 '''export changesets as hg patches.'''
515 '''export changesets as hg patches.'''
516
516
517 total = len(revs)
517 total = len(revs)
518 revwidth = max([len(str(rev)) for rev in revs])
518 revwidth = max([len(str(rev)) for rev in revs])
519
519
520 def single(rev, seqno, fp):
520 def single(rev, seqno, fp):
521 ctx = repo[rev]
521 ctx = repo[rev]
522 node = ctx.node()
522 node = ctx.node()
523 parents = [p.node() for p in ctx.parents() if p]
523 parents = [p.node() for p in ctx.parents() if p]
524 branch = ctx.branch()
524 branch = ctx.branch()
525 if switch_parent:
525 if switch_parent:
526 parents.reverse()
526 parents.reverse()
527 prev = (parents and parents[0]) or nullid
527 prev = (parents and parents[0]) or nullid
528
528
529 shouldclose = False
529 shouldclose = False
530 if not fp:
530 if not fp:
531 desc_lines = ctx.description().rstrip().split('\n')
531 desc_lines = ctx.description().rstrip().split('\n')
532 desc = desc_lines[0] #Commit always has a first line.
532 desc = desc_lines[0] #Commit always has a first line.
533 fp = makefileobj(repo, template, node, desc=desc, total=total,
533 fp = makefileobj(repo, template, node, desc=desc, total=total,
534 seqno=seqno, revwidth=revwidth, mode='ab')
534 seqno=seqno, revwidth=revwidth, mode='ab')
535 if fp != template:
535 if fp != template:
536 shouldclose = True
536 shouldclose = True
537 if fp != sys.stdout and util.safehasattr(fp, 'name'):
537 if fp != sys.stdout and util.safehasattr(fp, 'name'):
538 repo.ui.note("%s\n" % fp.name)
538 repo.ui.note("%s\n" % fp.name)
539
539
540 fp.write("# HG changeset patch\n")
540 fp.write("# HG changeset patch\n")
541 fp.write("# User %s\n" % ctx.user())
541 fp.write("# User %s\n" % ctx.user())
542 fp.write("# Date %d %d\n" % ctx.date())
542 fp.write("# Date %d %d\n" % ctx.date())
543 if branch and branch != 'default':
543 if branch and branch != 'default':
544 fp.write("# Branch %s\n" % branch)
544 fp.write("# Branch %s\n" % branch)
545 fp.write("# Node ID %s\n" % hex(node))
545 fp.write("# Node ID %s\n" % hex(node))
546 fp.write("# Parent %s\n" % hex(prev))
546 fp.write("# Parent %s\n" % hex(prev))
547 if len(parents) > 1:
547 if len(parents) > 1:
548 fp.write("# Parent %s\n" % hex(parents[1]))
548 fp.write("# Parent %s\n" % hex(parents[1]))
549 fp.write(ctx.description().rstrip())
549 fp.write(ctx.description().rstrip())
550 fp.write("\n\n")
550 fp.write("\n\n")
551
551
552 for chunk in patch.diff(repo, prev, node, opts=opts):
552 for chunk in patch.diff(repo, prev, node, opts=opts):
553 fp.write(chunk)
553 fp.write(chunk)
554
554
555 if shouldclose:
555 if shouldclose:
556 fp.close()
556 fp.close()
557
557
558 for seqno, rev in enumerate(revs):
558 for seqno, rev in enumerate(revs):
559 single(rev, seqno + 1, fp)
559 single(rev, seqno + 1, fp)
560
560
561 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
561 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
562 changes=None, stat=False, fp=None, prefix='',
562 changes=None, stat=False, fp=None, prefix='',
563 listsubrepos=False):
563 listsubrepos=False):
564 '''show diff or diffstat.'''
564 '''show diff or diffstat.'''
565 if fp is None:
565 if fp is None:
566 write = ui.write
566 write = ui.write
567 else:
567 else:
568 def write(s, **kw):
568 def write(s, **kw):
569 fp.write(s)
569 fp.write(s)
570
570
571 if stat:
571 if stat:
572 diffopts = diffopts.copy(context=0)
572 diffopts = diffopts.copy(context=0)
573 width = 80
573 width = 80
574 if not ui.plain():
574 if not ui.plain():
575 width = ui.termwidth()
575 width = ui.termwidth()
576 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
576 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
577 prefix=prefix)
577 prefix=prefix)
578 for chunk, label in patch.diffstatui(util.iterlines(chunks),
578 for chunk, label in patch.diffstatui(util.iterlines(chunks),
579 width=width,
579 width=width,
580 git=diffopts.git):
580 git=diffopts.git):
581 write(chunk, label=label)
581 write(chunk, label=label)
582 else:
582 else:
583 for chunk, label in patch.diffui(repo, node1, node2, match,
583 for chunk, label in patch.diffui(repo, node1, node2, match,
584 changes, diffopts, prefix=prefix):
584 changes, diffopts, prefix=prefix):
585 write(chunk, label=label)
585 write(chunk, label=label)
586
586
587 if listsubrepos:
587 if listsubrepos:
588 ctx1 = repo[node1]
588 ctx1 = repo[node1]
589 ctx2 = repo[node2]
589 ctx2 = repo[node2]
590 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
590 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
591 tempnode2 = node2
591 tempnode2 = node2
592 try:
592 try:
593 if node2 is not None:
593 if node2 is not None:
594 tempnode2 = ctx2.substate[subpath][1]
594 tempnode2 = ctx2.substate[subpath][1]
595 except KeyError:
595 except KeyError:
596 # A subrepo that existed in node1 was deleted between node1 and
596 # A subrepo that existed in node1 was deleted between node1 and
597 # node2 (inclusive). Thus, ctx2's substate won't contain that
597 # node2 (inclusive). Thus, ctx2's substate won't contain that
598 # subpath. The best we can do is to ignore it.
598 # subpath. The best we can do is to ignore it.
599 tempnode2 = None
599 tempnode2 = None
600 submatch = matchmod.narrowmatcher(subpath, match)
600 submatch = matchmod.narrowmatcher(subpath, match)
601 sub.diff(diffopts, tempnode2, submatch, changes=changes,
601 sub.diff(diffopts, tempnode2, submatch, changes=changes,
602 stat=stat, fp=fp, prefix=prefix)
602 stat=stat, fp=fp, prefix=prefix)
603
603
604 class changeset_printer(object):
604 class changeset_printer(object):
605 '''show changeset information when templating not requested.'''
605 '''show changeset information when templating not requested.'''
606
606
607 def __init__(self, ui, repo, patch, diffopts, buffered):
607 def __init__(self, ui, repo, patch, diffopts, buffered):
608 self.ui = ui
608 self.ui = ui
609 self.repo = repo
609 self.repo = repo
610 self.buffered = buffered
610 self.buffered = buffered
611 self.patch = patch
611 self.patch = patch
612 self.diffopts = diffopts
612 self.diffopts = diffopts
613 self.header = {}
613 self.header = {}
614 self.hunk = {}
614 self.hunk = {}
615 self.lastheader = None
615 self.lastheader = None
616 self.footer = None
616 self.footer = None
617
617
618 def flush(self, rev):
618 def flush(self, rev):
619 if rev in self.header:
619 if rev in self.header:
620 h = self.header[rev]
620 h = self.header[rev]
621 if h != self.lastheader:
621 if h != self.lastheader:
622 self.lastheader = h
622 self.lastheader = h
623 self.ui.write(h)
623 self.ui.write(h)
624 del self.header[rev]
624 del self.header[rev]
625 if rev in self.hunk:
625 if rev in self.hunk:
626 self.ui.write(self.hunk[rev])
626 self.ui.write(self.hunk[rev])
627 del self.hunk[rev]
627 del self.hunk[rev]
628 return 1
628 return 1
629 return 0
629 return 0
630
630
631 def close(self):
631 def close(self):
632 if self.footer:
632 if self.footer:
633 self.ui.write(self.footer)
633 self.ui.write(self.footer)
634
634
635 def show(self, ctx, copies=None, matchfn=None, **props):
635 def show(self, ctx, copies=None, matchfn=None, **props):
636 if self.buffered:
636 if self.buffered:
637 self.ui.pushbuffer()
637 self.ui.pushbuffer()
638 self._show(ctx, copies, matchfn, props)
638 self._show(ctx, copies, matchfn, props)
639 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
639 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
640 else:
640 else:
641 self._show(ctx, copies, matchfn, props)
641 self._show(ctx, copies, matchfn, props)
642
642
643 def _show(self, ctx, copies, matchfn, props):
643 def _show(self, ctx, copies, matchfn, props):
644 '''show a single changeset or file revision'''
644 '''show a single changeset or file revision'''
645 changenode = ctx.node()
645 changenode = ctx.node()
646 rev = ctx.rev()
646 rev = ctx.rev()
647
647
648 if self.ui.quiet:
648 if self.ui.quiet:
649 self.ui.write("%d:%s\n" % (rev, short(changenode)),
649 self.ui.write("%d:%s\n" % (rev, short(changenode)),
650 label='log.node')
650 label='log.node')
651 return
651 return
652
652
653 log = self.repo.changelog
653 log = self.repo.changelog
654 date = util.datestr(ctx.date())
654 date = util.datestr(ctx.date())
655
655
656 hexfunc = self.ui.debugflag and hex or short
656 hexfunc = self.ui.debugflag and hex or short
657
657
658 parents = [(p, hexfunc(log.node(p)))
658 parents = [(p, hexfunc(log.node(p)))
659 for p in self._meaningful_parentrevs(log, rev)]
659 for p in self._meaningful_parentrevs(log, rev)]
660
660
661 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
661 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
662 label='log.changeset')
662 label='log.changeset')
663
663
664 branch = ctx.branch()
664 branch = ctx.branch()
665 # don't show the default branch name
665 # don't show the default branch name
666 if branch != 'default':
666 if branch != 'default':
667 self.ui.write(_("branch: %s\n") % branch,
667 self.ui.write(_("branch: %s\n") % branch,
668 label='log.branch')
668 label='log.branch')
669 for bookmark in self.repo.nodebookmarks(changenode):
669 for bookmark in self.repo.nodebookmarks(changenode):
670 self.ui.write(_("bookmark: %s\n") % bookmark,
670 self.ui.write(_("bookmark: %s\n") % bookmark,
671 label='log.bookmark')
671 label='log.bookmark')
672 for tag in self.repo.nodetags(changenode):
672 for tag in self.repo.nodetags(changenode):
673 self.ui.write(_("tag: %s\n") % tag,
673 self.ui.write(_("tag: %s\n") % tag,
674 label='log.tag')
674 label='log.tag')
675 for parent in parents:
675 for parent in parents:
676 self.ui.write(_("parent: %d:%s\n") % parent,
676 self.ui.write(_("parent: %d:%s\n") % parent,
677 label='log.parent')
677 label='log.parent')
678
678
679 if self.ui.debugflag:
679 if self.ui.debugflag:
680 mnode = ctx.manifestnode()
680 mnode = ctx.manifestnode()
681 self.ui.write(_("manifest: %d:%s\n") %
681 self.ui.write(_("manifest: %d:%s\n") %
682 (self.repo.manifest.rev(mnode), hex(mnode)),
682 (self.repo.manifest.rev(mnode), hex(mnode)),
683 label='ui.debug log.manifest')
683 label='ui.debug log.manifest')
684 self.ui.write(_("user: %s\n") % ctx.user(),
684 self.ui.write(_("user: %s\n") % ctx.user(),
685 label='log.user')
685 label='log.user')
686 self.ui.write(_("date: %s\n") % date,
686 self.ui.write(_("date: %s\n") % date,
687 label='log.date')
687 label='log.date')
688
688
689 if self.ui.debugflag:
689 if self.ui.debugflag:
690 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
690 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
691 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
691 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
692 files):
692 files):
693 if value:
693 if value:
694 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
694 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
695 label='ui.debug log.files')
695 label='ui.debug log.files')
696 elif ctx.files() and self.ui.verbose:
696 elif ctx.files() and self.ui.verbose:
697 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
697 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
698 label='ui.note log.files')
698 label='ui.note log.files')
699 if copies and self.ui.verbose:
699 if copies and self.ui.verbose:
700 copies = ['%s (%s)' % c for c in copies]
700 copies = ['%s (%s)' % c for c in copies]
701 self.ui.write(_("copies: %s\n") % ' '.join(copies),
701 self.ui.write(_("copies: %s\n") % ' '.join(copies),
702 label='ui.note log.copies')
702 label='ui.note log.copies')
703
703
704 extra = ctx.extra()
704 extra = ctx.extra()
705 if extra and self.ui.debugflag:
705 if extra and self.ui.debugflag:
706 for key, value in sorted(extra.items()):
706 for key, value in sorted(extra.items()):
707 self.ui.write(_("extra: %s=%s\n")
707 self.ui.write(_("extra: %s=%s\n")
708 % (key, value.encode('string_escape')),
708 % (key, value.encode('string_escape')),
709 label='ui.debug log.extra')
709 label='ui.debug log.extra')
710
710
711 description = ctx.description().strip()
711 description = ctx.description().strip()
712 if description:
712 if description:
713 if self.ui.verbose:
713 if self.ui.verbose:
714 self.ui.write(_("description:\n"),
714 self.ui.write(_("description:\n"),
715 label='ui.note log.description')
715 label='ui.note log.description')
716 self.ui.write(description,
716 self.ui.write(description,
717 label='ui.note log.description')
717 label='ui.note log.description')
718 self.ui.write("\n\n")
718 self.ui.write("\n\n")
719 else:
719 else:
720 self.ui.write(_("summary: %s\n") %
720 self.ui.write(_("summary: %s\n") %
721 description.splitlines()[0],
721 description.splitlines()[0],
722 label='log.summary')
722 label='log.summary')
723 self.ui.write("\n")
723 self.ui.write("\n")
724
724
725 self.showpatch(changenode, matchfn)
725 self.showpatch(changenode, matchfn)
726
726
727 def showpatch(self, node, matchfn):
727 def showpatch(self, node, matchfn):
728 if not matchfn:
728 if not matchfn:
729 matchfn = self.patch
729 matchfn = self.patch
730 if matchfn:
730 if matchfn:
731 stat = self.diffopts.get('stat')
731 stat = self.diffopts.get('stat')
732 diff = self.diffopts.get('patch')
732 diff = self.diffopts.get('patch')
733 diffopts = patch.diffopts(self.ui, self.diffopts)
733 diffopts = patch.diffopts(self.ui, self.diffopts)
734 prev = self.repo.changelog.parents(node)[0]
734 prev = self.repo.changelog.parents(node)[0]
735 if stat:
735 if stat:
736 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
736 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
737 match=matchfn, stat=True)
737 match=matchfn, stat=True)
738 if diff:
738 if diff:
739 if stat:
739 if stat:
740 self.ui.write("\n")
740 self.ui.write("\n")
741 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
741 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
742 match=matchfn, stat=False)
742 match=matchfn, stat=False)
743 self.ui.write("\n")
743 self.ui.write("\n")
744
744
745 def _meaningful_parentrevs(self, log, rev):
745 def _meaningful_parentrevs(self, log, rev):
746 """Return list of meaningful (or all if debug) parentrevs for rev.
746 """Return list of meaningful (or all if debug) parentrevs for rev.
747
747
748 For merges (two non-nullrev revisions) both parents are meaningful.
748 For merges (two non-nullrev revisions) both parents are meaningful.
749 Otherwise the first parent revision is considered meaningful if it
749 Otherwise the first parent revision is considered meaningful if it
750 is not the preceding revision.
750 is not the preceding revision.
751 """
751 """
752 parents = log.parentrevs(rev)
752 parents = log.parentrevs(rev)
753 if not self.ui.debugflag and parents[1] == nullrev:
753 if not self.ui.debugflag and parents[1] == nullrev:
754 if parents[0] >= rev - 1:
754 if parents[0] >= rev - 1:
755 parents = []
755 parents = []
756 else:
756 else:
757 parents = [parents[0]]
757 parents = [parents[0]]
758 return parents
758 return parents
759
759
760
760
761 class changeset_templater(changeset_printer):
761 class changeset_templater(changeset_printer):
762 '''format changeset information.'''
762 '''format changeset information.'''
763
763
764 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
764 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
765 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
765 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
766 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
766 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
767 defaulttempl = {
767 defaulttempl = {
768 'parent': '{rev}:{node|formatnode} ',
768 'parent': '{rev}:{node|formatnode} ',
769 'manifest': '{rev}:{node|formatnode}',
769 'manifest': '{rev}:{node|formatnode}',
770 'file_copy': '{name} ({source})',
770 'file_copy': '{name} ({source})',
771 'extra': '{key}={value|stringescape}'
771 'extra': '{key}={value|stringescape}'
772 }
772 }
773 # filecopy is preserved for compatibility reasons
773 # filecopy is preserved for compatibility reasons
774 defaulttempl['filecopy'] = defaulttempl['file_copy']
774 defaulttempl['filecopy'] = defaulttempl['file_copy']
775 self.t = templater.templater(mapfile, {'formatnode': formatnode},
775 self.t = templater.templater(mapfile, {'formatnode': formatnode},
776 cache=defaulttempl)
776 cache=defaulttempl)
777 self.cache = {}
777 self.cache = {}
778
778
779 def use_template(self, t):
779 def use_template(self, t):
780 '''set template string to use'''
780 '''set template string to use'''
781 self.t.cache['changeset'] = t
781 self.t.cache['changeset'] = t
782
782
783 def _meaningful_parentrevs(self, ctx):
783 def _meaningful_parentrevs(self, ctx):
784 """Return list of meaningful (or all if debug) parentrevs for rev.
784 """Return list of meaningful (or all if debug) parentrevs for rev.
785 """
785 """
786 parents = ctx.parents()
786 parents = ctx.parents()
787 if len(parents) > 1:
787 if len(parents) > 1:
788 return parents
788 return parents
789 if self.ui.debugflag:
789 if self.ui.debugflag:
790 return [parents[0], self.repo['null']]
790 return [parents[0], self.repo['null']]
791 if parents[0].rev() >= ctx.rev() - 1:
791 if parents[0].rev() >= ctx.rev() - 1:
792 return []
792 return []
793 return parents
793 return parents
794
794
795 def _show(self, ctx, copies, matchfn, props):
795 def _show(self, ctx, copies, matchfn, props):
796 '''show a single changeset or file revision'''
796 '''show a single changeset or file revision'''
797
797
798 showlist = templatekw.showlist
798 showlist = templatekw.showlist
799
799
800 # showparents() behaviour depends on ui trace level which
800 # showparents() behaviour depends on ui trace level which
801 # causes unexpected behaviours at templating level and makes
801 # causes unexpected behaviours at templating level and makes
802 # it harder to extract it in a standalone function. Its
802 # it harder to extract it in a standalone function. Its
803 # behaviour cannot be changed so leave it here for now.
803 # behaviour cannot be changed so leave it here for now.
804 def showparents(**args):
804 def showparents(**args):
805 ctx = args['ctx']
805 ctx = args['ctx']
806 parents = [[('rev', p.rev()), ('node', p.hex())]
806 parents = [[('rev', p.rev()), ('node', p.hex())]
807 for p in self._meaningful_parentrevs(ctx)]
807 for p in self._meaningful_parentrevs(ctx)]
808 return showlist('parent', parents, **args)
808 return showlist('parent', parents, **args)
809
809
810 props = props.copy()
810 props = props.copy()
811 props.update(templatekw.keywords)
811 props.update(templatekw.keywords)
812 props['parents'] = showparents
812 props['parents'] = showparents
813 props['templ'] = self.t
813 props['templ'] = self.t
814 props['ctx'] = ctx
814 props['ctx'] = ctx
815 props['repo'] = self.repo
815 props['repo'] = self.repo
816 props['revcache'] = {'copies': copies}
816 props['revcache'] = {'copies': copies}
817 props['cache'] = self.cache
817 props['cache'] = self.cache
818
818
819 # find correct templates for current mode
819 # find correct templates for current mode
820
820
821 tmplmodes = [
821 tmplmodes = [
822 (True, None),
822 (True, None),
823 (self.ui.verbose, 'verbose'),
823 (self.ui.verbose, 'verbose'),
824 (self.ui.quiet, 'quiet'),
824 (self.ui.quiet, 'quiet'),
825 (self.ui.debugflag, 'debug'),
825 (self.ui.debugflag, 'debug'),
826 ]
826 ]
827
827
828 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
828 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
829 for mode, postfix in tmplmodes:
829 for mode, postfix in tmplmodes:
830 for type in types:
830 for type in types:
831 cur = postfix and ('%s_%s' % (type, postfix)) or type
831 cur = postfix and ('%s_%s' % (type, postfix)) or type
832 if mode and cur in self.t:
832 if mode and cur in self.t:
833 types[type] = cur
833 types[type] = cur
834
834
835 try:
835 try:
836
836
837 # write header
837 # write header
838 if types['header']:
838 if types['header']:
839 h = templater.stringify(self.t(types['header'], **props))
839 h = templater.stringify(self.t(types['header'], **props))
840 if self.buffered:
840 if self.buffered:
841 self.header[ctx.rev()] = h
841 self.header[ctx.rev()] = h
842 else:
842 else:
843 if self.lastheader != h:
843 if self.lastheader != h:
844 self.lastheader = h
844 self.lastheader = h
845 self.ui.write(h)
845 self.ui.write(h)
846
846
847 # write changeset metadata, then patch if requested
847 # write changeset metadata, then patch if requested
848 key = types['changeset']
848 key = types['changeset']
849 self.ui.write(templater.stringify(self.t(key, **props)))
849 self.ui.write(templater.stringify(self.t(key, **props)))
850 self.showpatch(ctx.node(), matchfn)
850 self.showpatch(ctx.node(), matchfn)
851
851
852 if types['footer']:
852 if types['footer']:
853 if not self.footer:
853 if not self.footer:
854 self.footer = templater.stringify(self.t(types['footer'],
854 self.footer = templater.stringify(self.t(types['footer'],
855 **props))
855 **props))
856
856
857 except KeyError, inst:
857 except KeyError, inst:
858 msg = _("%s: no key named '%s'")
858 msg = _("%s: no key named '%s'")
859 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
859 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
860 except SyntaxError, inst:
860 except SyntaxError, inst:
861 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
861 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
862
862
863 def show_changeset(ui, repo, opts, buffered=False):
863 def show_changeset(ui, repo, opts, buffered=False):
864 """show one changeset using template or regular display.
864 """show one changeset using template or regular display.
865
865
866 Display format will be the first non-empty hit of:
866 Display format will be the first non-empty hit of:
867 1. option 'template'
867 1. option 'template'
868 2. option 'style'
868 2. option 'style'
869 3. [ui] setting 'logtemplate'
869 3. [ui] setting 'logtemplate'
870 4. [ui] setting 'style'
870 4. [ui] setting 'style'
871 If all of these values are either the unset or the empty string,
871 If all of these values are either the unset or the empty string,
872 regular display via changeset_printer() is done.
872 regular display via changeset_printer() is done.
873 """
873 """
874 # options
874 # options
875 patch = False
875 patch = False
876 if opts.get('patch') or opts.get('stat'):
876 if opts.get('patch') or opts.get('stat'):
877 patch = scmutil.matchall(repo)
877 patch = scmutil.matchall(repo)
878
878
879 tmpl = opts.get('template')
879 tmpl = opts.get('template')
880 style = None
880 style = None
881 if tmpl:
881 if tmpl:
882 tmpl = templater.parsestring(tmpl, quoted=False)
882 tmpl = templater.parsestring(tmpl, quoted=False)
883 else:
883 else:
884 style = opts.get('style')
884 style = opts.get('style')
885
885
886 # ui settings
886 # ui settings
887 if not (tmpl or style):
887 if not (tmpl or style):
888 tmpl = ui.config('ui', 'logtemplate')
888 tmpl = ui.config('ui', 'logtemplate')
889 if tmpl:
889 if tmpl:
890 tmpl = templater.parsestring(tmpl)
890 tmpl = templater.parsestring(tmpl)
891 else:
891 else:
892 style = util.expandpath(ui.config('ui', 'style', ''))
892 style = util.expandpath(ui.config('ui', 'style', ''))
893
893
894 if not (tmpl or style):
894 if not (tmpl or style):
895 return changeset_printer(ui, repo, patch, opts, buffered)
895 return changeset_printer(ui, repo, patch, opts, buffered)
896
896
897 mapfile = None
897 mapfile = None
898 if style and not tmpl:
898 if style and not tmpl:
899 mapfile = style
899 mapfile = style
900 if not os.path.split(mapfile)[0]:
900 if not os.path.split(mapfile)[0]:
901 mapname = (templater.templatepath('map-cmdline.' + mapfile)
901 mapname = (templater.templatepath('map-cmdline.' + mapfile)
902 or templater.templatepath(mapfile))
902 or templater.templatepath(mapfile))
903 if mapname:
903 if mapname:
904 mapfile = mapname
904 mapfile = mapname
905
905
906 try:
906 try:
907 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
907 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
908 except SyntaxError, inst:
908 except SyntaxError, inst:
909 raise util.Abort(inst.args[0])
909 raise util.Abort(inst.args[0])
910 if tmpl:
910 if tmpl:
911 t.use_template(tmpl)
911 t.use_template(tmpl)
912 return t
912 return t
913
913
914 def finddate(ui, repo, date):
914 def finddate(ui, repo, date):
915 """Find the tipmost changeset that matches the given date spec"""
915 """Find the tipmost changeset that matches the given date spec"""
916
916
917 df = util.matchdate(date)
917 df = util.matchdate(date)
918 m = scmutil.matchall(repo)
918 m = scmutil.matchall(repo)
919 results = {}
919 results = {}
920
920
921 def prep(ctx, fns):
921 def prep(ctx, fns):
922 d = ctx.date()
922 d = ctx.date()
923 if df(d[0]):
923 if df(d[0]):
924 results[ctx.rev()] = d
924 results[ctx.rev()] = d
925
925
926 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
926 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
927 rev = ctx.rev()
927 rev = ctx.rev()
928 if rev in results:
928 if rev in results:
929 ui.status(_("Found revision %s from %s\n") %
929 ui.status(_("Found revision %s from %s\n") %
930 (rev, util.datestr(results[rev])))
930 (rev, util.datestr(results[rev])))
931 return str(rev)
931 return str(rev)
932
932
933 raise util.Abort(_("revision matching date not found"))
933 raise util.Abort(_("revision matching date not found"))
934
934
935 def walkchangerevs(repo, match, opts, prepare):
935 def walkchangerevs(repo, match, opts, prepare):
936 '''Iterate over files and the revs in which they changed.
936 '''Iterate over files and the revs in which they changed.
937
937
938 Callers most commonly need to iterate backwards over the history
938 Callers most commonly need to iterate backwards over the history
939 in which they are interested. Doing so has awful (quadratic-looking)
939 in which they are interested. Doing so has awful (quadratic-looking)
940 performance, so we use iterators in a "windowed" way.
940 performance, so we use iterators in a "windowed" way.
941
941
942 We walk a window of revisions in the desired order. Within the
942 We walk a window of revisions in the desired order. Within the
943 window, we first walk forwards to gather data, then in the desired
943 window, we first walk forwards to gather data, then in the desired
944 order (usually backwards) to display it.
944 order (usually backwards) to display it.
945
945
946 This function returns an iterator yielding contexts. Before
946 This function returns an iterator yielding contexts. Before
947 yielding each context, the iterator will first call the prepare
947 yielding each context, the iterator will first call the prepare
948 function on each context in the window in forward order.'''
948 function on each context in the window in forward order.'''
949
949
950 def increasing_windows(start, end, windowsize=8, sizelimit=512):
950 def increasing_windows(start, end, windowsize=8, sizelimit=512):
951 if start < end:
951 if start < end:
952 while start < end:
952 while start < end:
953 yield start, min(windowsize, end - start)
953 yield start, min(windowsize, end - start)
954 start += windowsize
954 start += windowsize
955 if windowsize < sizelimit:
955 if windowsize < sizelimit:
956 windowsize *= 2
956 windowsize *= 2
957 else:
957 else:
958 while start > end:
958 while start > end:
959 yield start, min(windowsize, start - end - 1)
959 yield start, min(windowsize, start - end - 1)
960 start -= windowsize
960 start -= windowsize
961 if windowsize < sizelimit:
961 if windowsize < sizelimit:
962 windowsize *= 2
962 windowsize *= 2
963
963
964 follow = opts.get('follow') or opts.get('follow_first')
964 follow = opts.get('follow') or opts.get('follow_first')
965
965
966 if not len(repo):
966 if not len(repo):
967 return []
967 return []
968
968
969 if follow:
969 if follow:
970 defrange = '%s:0' % repo['.'].rev()
970 defrange = '%s:0' % repo['.'].rev()
971 else:
971 else:
972 defrange = '-1:0'
972 defrange = '-1:0'
973 revs = scmutil.revrange(repo, opts['rev'] or [defrange])
973 revs = scmutil.revrange(repo, opts['rev'] or [defrange])
974 if not revs:
974 if not revs:
975 return []
975 return []
976 wanted = set()
976 wanted = set()
977 slowpath = match.anypats() or (match.files() and opts.get('removed'))
977 slowpath = match.anypats() or (match.files() and opts.get('removed'))
978 fncache = {}
978 fncache = {}
979 change = util.cachefunc(repo.changectx)
979 change = util.cachefunc(repo.changectx)
980
980
981 # First step is to fill wanted, the set of revisions that we want to yield.
981 # First step is to fill wanted, the set of revisions that we want to yield.
982 # When it does not induce extra cost, we also fill fncache for revisions in
982 # When it does not induce extra cost, we also fill fncache for revisions in
983 # wanted: a cache of filenames that were changed (ctx.files()) and that
983 # wanted: a cache of filenames that were changed (ctx.files()) and that
984 # match the file filtering conditions.
984 # match the file filtering conditions.
985
985
986 if not slowpath and not match.files():
986 if not slowpath and not match.files():
987 # No files, no patterns. Display all revs.
987 # No files, no patterns. Display all revs.
988 wanted = set(revs)
988 wanted = set(revs)
989 copies = []
989 copies = []
990
990
991 if not slowpath:
991 if not slowpath:
992 # We only have to read through the filelog to find wanted revisions
992 # We only have to read through the filelog to find wanted revisions
993
993
994 minrev, maxrev = min(revs), max(revs)
994 minrev, maxrev = min(revs), max(revs)
995 def filerevgen(filelog, last):
995 def filerevgen(filelog, last):
996 """
996 """
997 Only files, no patterns. Check the history of each file.
997 Only files, no patterns. Check the history of each file.
998
998
999 Examines filelog entries within minrev, maxrev linkrev range
999 Examines filelog entries within minrev, maxrev linkrev range
1000 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1000 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1001 tuples in backwards order
1001 tuples in backwards order
1002 """
1002 """
1003 cl_count = len(repo)
1003 cl_count = len(repo)
1004 revs = []
1004 revs = []
1005 for j in xrange(0, last + 1):
1005 for j in xrange(0, last + 1):
1006 linkrev = filelog.linkrev(j)
1006 linkrev = filelog.linkrev(j)
1007 if linkrev < minrev:
1007 if linkrev < minrev:
1008 continue
1008 continue
1009 # only yield rev for which we have the changelog, it can
1009 # only yield rev for which we have the changelog, it can
1010 # happen while doing "hg log" during a pull or commit
1010 # happen while doing "hg log" during a pull or commit
1011 if linkrev >= cl_count:
1011 if linkrev >= cl_count:
1012 break
1012 break
1013
1013
1014 parentlinkrevs = []
1014 parentlinkrevs = []
1015 for p in filelog.parentrevs(j):
1015 for p in filelog.parentrevs(j):
1016 if p != nullrev:
1016 if p != nullrev:
1017 parentlinkrevs.append(filelog.linkrev(p))
1017 parentlinkrevs.append(filelog.linkrev(p))
1018 n = filelog.node(j)
1018 n = filelog.node(j)
1019 revs.append((linkrev, parentlinkrevs,
1019 revs.append((linkrev, parentlinkrevs,
1020 follow and filelog.renamed(n)))
1020 follow and filelog.renamed(n)))
1021
1021
1022 return reversed(revs)
1022 return reversed(revs)
1023 def iterfiles():
1023 def iterfiles():
1024 for filename in match.files():
1024 for filename in match.files():
1025 yield filename, None
1025 yield filename, None
1026 for filename_node in copies:
1026 for filename_node in copies:
1027 yield filename_node
1027 yield filename_node
1028 for file_, node in iterfiles():
1028 for file_, node in iterfiles():
1029 filelog = repo.file(file_)
1029 filelog = repo.file(file_)
1030 if not len(filelog):
1030 if not len(filelog):
1031 if node is None:
1031 if node is None:
1032 # A zero count may be a directory or deleted file, so
1032 # A zero count may be a directory or deleted file, so
1033 # try to find matching entries on the slow path.
1033 # try to find matching entries on the slow path.
1034 if follow:
1034 if follow:
1035 raise util.Abort(
1035 raise util.Abort(
1036 _('cannot follow nonexistent file: "%s"') % file_)
1036 _('cannot follow nonexistent file: "%s"') % file_)
1037 slowpath = True
1037 slowpath = True
1038 break
1038 break
1039 else:
1039 else:
1040 continue
1040 continue
1041
1041
1042 if node is None:
1042 if node is None:
1043 last = len(filelog) - 1
1043 last = len(filelog) - 1
1044 else:
1044 else:
1045 last = filelog.rev(node)
1045 last = filelog.rev(node)
1046
1046
1047
1047
1048 # keep track of all ancestors of the file
1048 # keep track of all ancestors of the file
1049 ancestors = set([filelog.linkrev(last)])
1049 ancestors = set([filelog.linkrev(last)])
1050
1050
1051 # iterate from latest to oldest revision
1051 # iterate from latest to oldest revision
1052 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1052 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1053 if not follow:
1053 if not follow:
1054 if rev > maxrev:
1054 if rev > maxrev:
1055 continue
1055 continue
1056 else:
1056 else:
1057 # Note that last might not be the first interesting
1057 # Note that last might not be the first interesting
1058 # rev to us:
1058 # rev to us:
1059 # if the file has been changed after maxrev, we'll
1059 # if the file has been changed after maxrev, we'll
1060 # have linkrev(last) > maxrev, and we still need
1060 # have linkrev(last) > maxrev, and we still need
1061 # to explore the file graph
1061 # to explore the file graph
1062 if rev not in ancestors:
1062 if rev not in ancestors:
1063 continue
1063 continue
1064 # XXX insert 1327 fix here
1064 # XXX insert 1327 fix here
1065 if flparentlinkrevs:
1065 if flparentlinkrevs:
1066 ancestors.update(flparentlinkrevs)
1066 ancestors.update(flparentlinkrevs)
1067
1067
1068 fncache.setdefault(rev, []).append(file_)
1068 fncache.setdefault(rev, []).append(file_)
1069 wanted.add(rev)
1069 wanted.add(rev)
1070 if copied:
1070 if copied:
1071 copies.append(copied)
1071 copies.append(copied)
1072 if slowpath:
1072 if slowpath:
1073 # We have to read the changelog to match filenames against
1073 # We have to read the changelog to match filenames against
1074 # changed files
1074 # changed files
1075
1075
1076 if follow:
1076 if follow:
1077 raise util.Abort(_('can only follow copies/renames for explicit '
1077 raise util.Abort(_('can only follow copies/renames for explicit '
1078 'filenames'))
1078 'filenames'))
1079
1079
1080 # The slow path checks files modified in every changeset.
1080 # The slow path checks files modified in every changeset.
1081 for i in sorted(revs):
1081 for i in sorted(revs):
1082 ctx = change(i)
1082 ctx = change(i)
1083 matches = filter(match, ctx.files())
1083 matches = filter(match, ctx.files())
1084 if matches:
1084 if matches:
1085 fncache[i] = matches
1085 fncache[i] = matches
1086 wanted.add(i)
1086 wanted.add(i)
1087
1087
1088 class followfilter(object):
1088 class followfilter(object):
1089 def __init__(self, onlyfirst=False):
1089 def __init__(self, onlyfirst=False):
1090 self.startrev = nullrev
1090 self.startrev = nullrev
1091 self.roots = set()
1091 self.roots = set()
1092 self.onlyfirst = onlyfirst
1092 self.onlyfirst = onlyfirst
1093
1093
1094 def match(self, rev):
1094 def match(self, rev):
1095 def realparents(rev):
1095 def realparents(rev):
1096 if self.onlyfirst:
1096 if self.onlyfirst:
1097 return repo.changelog.parentrevs(rev)[0:1]
1097 return repo.changelog.parentrevs(rev)[0:1]
1098 else:
1098 else:
1099 return filter(lambda x: x != nullrev,
1099 return filter(lambda x: x != nullrev,
1100 repo.changelog.parentrevs(rev))
1100 repo.changelog.parentrevs(rev))
1101
1101
1102 if self.startrev == nullrev:
1102 if self.startrev == nullrev:
1103 self.startrev = rev
1103 self.startrev = rev
1104 return True
1104 return True
1105
1105
1106 if rev > self.startrev:
1106 if rev > self.startrev:
1107 # forward: all descendants
1107 # forward: all descendants
1108 if not self.roots:
1108 if not self.roots:
1109 self.roots.add(self.startrev)
1109 self.roots.add(self.startrev)
1110 for parent in realparents(rev):
1110 for parent in realparents(rev):
1111 if parent in self.roots:
1111 if parent in self.roots:
1112 self.roots.add(rev)
1112 self.roots.add(rev)
1113 return True
1113 return True
1114 else:
1114 else:
1115 # backwards: all parents
1115 # backwards: all parents
1116 if not self.roots:
1116 if not self.roots:
1117 self.roots.update(realparents(self.startrev))
1117 self.roots.update(realparents(self.startrev))
1118 if rev in self.roots:
1118 if rev in self.roots:
1119 self.roots.remove(rev)
1119 self.roots.remove(rev)
1120 self.roots.update(realparents(rev))
1120 self.roots.update(realparents(rev))
1121 return True
1121 return True
1122
1122
1123 return False
1123 return False
1124
1124
1125 # it might be worthwhile to do this in the iterator if the rev range
1125 # it might be worthwhile to do this in the iterator if the rev range
1126 # is descending and the prune args are all within that range
1126 # is descending and the prune args are all within that range
1127 for rev in opts.get('prune', ()):
1127 for rev in opts.get('prune', ()):
1128 rev = repo.changelog.rev(repo.lookup(rev))
1128 rev = repo.changelog.rev(repo.lookup(rev))
1129 ff = followfilter()
1129 ff = followfilter()
1130 stop = min(revs[0], revs[-1])
1130 stop = min(revs[0], revs[-1])
1131 for x in xrange(rev, stop - 1, -1):
1131 for x in xrange(rev, stop - 1, -1):
1132 if ff.match(x):
1132 if ff.match(x):
1133 wanted.discard(x)
1133 wanted.discard(x)
1134
1134
1135 # Now that wanted is correctly initialized, we can iterate over the
1135 # Now that wanted is correctly initialized, we can iterate over the
1136 # revision range, yielding only revisions in wanted.
1136 # revision range, yielding only revisions in wanted.
1137 def iterate():
1137 def iterate():
1138 if follow and not match.files():
1138 if follow and not match.files():
1139 ff = followfilter(onlyfirst=opts.get('follow_first'))
1139 ff = followfilter(onlyfirst=opts.get('follow_first'))
1140 def want(rev):
1140 def want(rev):
1141 return ff.match(rev) and rev in wanted
1141 return ff.match(rev) and rev in wanted
1142 else:
1142 else:
1143 def want(rev):
1143 def want(rev):
1144 return rev in wanted
1144 return rev in wanted
1145
1145
1146 for i, window in increasing_windows(0, len(revs)):
1146 for i, window in increasing_windows(0, len(revs)):
1147 nrevs = [rev for rev in revs[i:i + window] if want(rev)]
1147 nrevs = [rev for rev in revs[i:i + window] if want(rev)]
1148 for rev in sorted(nrevs):
1148 for rev in sorted(nrevs):
1149 fns = fncache.get(rev)
1149 fns = fncache.get(rev)
1150 ctx = change(rev)
1150 ctx = change(rev)
1151 if not fns:
1151 if not fns:
1152 def fns_generator():
1152 def fns_generator():
1153 for f in ctx.files():
1153 for f in ctx.files():
1154 if match(f):
1154 if match(f):
1155 yield f
1155 yield f
1156 fns = fns_generator()
1156 fns = fns_generator()
1157 prepare(ctx, fns)
1157 prepare(ctx, fns)
1158 for rev in nrevs:
1158 for rev in nrevs:
1159 yield change(rev)
1159 yield change(rev)
1160 return iterate()
1160 return iterate()
1161
1161
1162 def add(ui, repo, match, dryrun, listsubrepos, prefix):
1162 def add(ui, repo, match, dryrun, listsubrepos, prefix):
1163 join = lambda f: os.path.join(prefix, f)
1163 join = lambda f: os.path.join(prefix, f)
1164 bad = []
1164 bad = []
1165 oldbad = match.bad
1165 oldbad = match.bad
1166 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1166 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1167 names = []
1167 names = []
1168 wctx = repo[None]
1168 wctx = repo[None]
1169 cca = None
1169 cca = None
1170 abort, warn = scmutil.checkportabilityalert(ui)
1170 abort, warn = scmutil.checkportabilityalert(ui)
1171 if abort or warn:
1171 if abort or warn:
1172 cca = scmutil.casecollisionauditor(ui, abort, wctx)
1172 cca = scmutil.casecollisionauditor(ui, abort, wctx)
1173 for f in repo.walk(match):
1173 for f in repo.walk(match):
1174 exact = match.exact(f)
1174 exact = match.exact(f)
1175 if exact or f not in repo.dirstate:
1175 if exact or f not in repo.dirstate:
1176 if cca:
1176 if cca:
1177 cca(f)
1177 cca(f)
1178 names.append(f)
1178 names.append(f)
1179 if ui.verbose or not exact:
1179 if ui.verbose or not exact:
1180 ui.status(_('adding %s\n') % match.rel(join(f)))
1180 ui.status(_('adding %s\n') % match.rel(join(f)))
1181
1181
1182 for subpath in wctx.substate:
1182 for subpath in wctx.substate:
1183 sub = wctx.sub(subpath)
1183 sub = wctx.sub(subpath)
1184 try:
1184 try:
1185 submatch = matchmod.narrowmatcher(subpath, match)
1185 submatch = matchmod.narrowmatcher(subpath, match)
1186 if listsubrepos:
1186 if listsubrepos:
1187 bad.extend(sub.add(ui, submatch, dryrun, prefix))
1187 bad.extend(sub.add(ui, submatch, dryrun, prefix))
1188 else:
1188 else:
1189 for f in sub.walk(submatch):
1189 for f in sub.walk(submatch):
1190 if submatch.exact(f):
1190 if submatch.exact(f):
1191 bad.extend(sub.add(ui, submatch, dryrun, prefix))
1191 bad.extend(sub.add(ui, submatch, dryrun, prefix))
1192 except error.LookupError:
1192 except error.LookupError:
1193 ui.status(_("skipping missing subrepository: %s\n")
1193 ui.status(_("skipping missing subrepository: %s\n")
1194 % join(subpath))
1194 % join(subpath))
1195
1195
1196 if not dryrun:
1196 if not dryrun:
1197 rejected = wctx.add(names, prefix)
1197 rejected = wctx.add(names, prefix)
1198 bad.extend(f for f in rejected if f in match.files())
1198 bad.extend(f for f in rejected if f in match.files())
1199 return bad
1199 return bad
1200
1200
1201 def duplicatecopies(repo, rev, p1, p2):
1201 def duplicatecopies(repo, rev, p1, p2):
1202 "Reproduce copies found in the source revision in the dirstate for grafts"
1202 "Reproduce copies found in the source revision in the dirstate for grafts"
1203 # Here we simulate the copies and renames in the source changeset
1203 # Here we simulate the copies and renames in the source changeset
1204 cop, diver = copies.copies(repo, repo[rev], repo[p1], repo[p2], True)
1204 cop, diver = copies.mergecopies(repo, repo[rev], repo[p1], repo[p2])
1205 m1 = repo[rev].manifest()
1205 m1 = repo[rev].manifest()
1206 m2 = repo[p1].manifest()
1206 m2 = repo[p1].manifest()
1207 for k, v in cop.iteritems():
1207 for k, v in cop.iteritems():
1208 if k in m1:
1208 if k in m1:
1209 if v in m1 or v in m2:
1209 if v in m1 or v in m2:
1210 repo.dirstate.copy(v, k)
1210 repo.dirstate.copy(v, k)
1211 if v in m2 and v not in m1 and k in m2:
1211 if v in m2 and v not in m1 and k in m2:
1212 repo.dirstate.remove(v)
1212 repo.dirstate.remove(v)
1213
1213
1214 def commit(ui, repo, commitfunc, pats, opts):
1214 def commit(ui, repo, commitfunc, pats, opts):
1215 '''commit the specified files or all outstanding changes'''
1215 '''commit the specified files or all outstanding changes'''
1216 date = opts.get('date')
1216 date = opts.get('date')
1217 if date:
1217 if date:
1218 opts['date'] = util.parsedate(date)
1218 opts['date'] = util.parsedate(date)
1219 message = logmessage(ui, opts)
1219 message = logmessage(ui, opts)
1220
1220
1221 # extract addremove carefully -- this function can be called from a command
1221 # extract addremove carefully -- this function can be called from a command
1222 # that doesn't support addremove
1222 # that doesn't support addremove
1223 if opts.get('addremove'):
1223 if opts.get('addremove'):
1224 scmutil.addremove(repo, pats, opts)
1224 scmutil.addremove(repo, pats, opts)
1225
1225
1226 return commitfunc(ui, repo, message,
1226 return commitfunc(ui, repo, message,
1227 scmutil.match(repo[None], pats, opts), opts)
1227 scmutil.match(repo[None], pats, opts), opts)
1228
1228
1229 def commiteditor(repo, ctx, subs):
1229 def commiteditor(repo, ctx, subs):
1230 if ctx.description():
1230 if ctx.description():
1231 return ctx.description()
1231 return ctx.description()
1232 return commitforceeditor(repo, ctx, subs)
1232 return commitforceeditor(repo, ctx, subs)
1233
1233
1234 def commitforceeditor(repo, ctx, subs):
1234 def commitforceeditor(repo, ctx, subs):
1235 edittext = []
1235 edittext = []
1236 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1236 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1237 if ctx.description():
1237 if ctx.description():
1238 edittext.append(ctx.description())
1238 edittext.append(ctx.description())
1239 edittext.append("")
1239 edittext.append("")
1240 edittext.append("") # Empty line between message and comments.
1240 edittext.append("") # Empty line between message and comments.
1241 edittext.append(_("HG: Enter commit message."
1241 edittext.append(_("HG: Enter commit message."
1242 " Lines beginning with 'HG:' are removed."))
1242 " Lines beginning with 'HG:' are removed."))
1243 edittext.append(_("HG: Leave message empty to abort commit."))
1243 edittext.append(_("HG: Leave message empty to abort commit."))
1244 edittext.append("HG: --")
1244 edittext.append("HG: --")
1245 edittext.append(_("HG: user: %s") % ctx.user())
1245 edittext.append(_("HG: user: %s") % ctx.user())
1246 if ctx.p2():
1246 if ctx.p2():
1247 edittext.append(_("HG: branch merge"))
1247 edittext.append(_("HG: branch merge"))
1248 if ctx.branch():
1248 if ctx.branch():
1249 edittext.append(_("HG: branch '%s'") % ctx.branch())
1249 edittext.append(_("HG: branch '%s'") % ctx.branch())
1250 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1250 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1251 edittext.extend([_("HG: added %s") % f for f in added])
1251 edittext.extend([_("HG: added %s") % f for f in added])
1252 edittext.extend([_("HG: changed %s") % f for f in modified])
1252 edittext.extend([_("HG: changed %s") % f for f in modified])
1253 edittext.extend([_("HG: removed %s") % f for f in removed])
1253 edittext.extend([_("HG: removed %s") % f for f in removed])
1254 if not added and not modified and not removed:
1254 if not added and not modified and not removed:
1255 edittext.append(_("HG: no files changed"))
1255 edittext.append(_("HG: no files changed"))
1256 edittext.append("")
1256 edittext.append("")
1257 # run editor in the repository root
1257 # run editor in the repository root
1258 olddir = os.getcwd()
1258 olddir = os.getcwd()
1259 os.chdir(repo.root)
1259 os.chdir(repo.root)
1260 text = repo.ui.edit("\n".join(edittext), ctx.user())
1260 text = repo.ui.edit("\n".join(edittext), ctx.user())
1261 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
1261 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
1262 os.chdir(olddir)
1262 os.chdir(olddir)
1263
1263
1264 if not text.strip():
1264 if not text.strip():
1265 raise util.Abort(_("empty commit message"))
1265 raise util.Abort(_("empty commit message"))
1266
1266
1267 return text
1267 return text
1268
1268
1269 def command(table):
1269 def command(table):
1270 '''returns a function object bound to table which can be used as
1270 '''returns a function object bound to table which can be used as
1271 a decorator for populating table as a command table'''
1271 a decorator for populating table as a command table'''
1272
1272
1273 def cmd(name, options, synopsis=None):
1273 def cmd(name, options, synopsis=None):
1274 def decorator(func):
1274 def decorator(func):
1275 if synopsis:
1275 if synopsis:
1276 table[name] = func, options[:], synopsis
1276 table[name] = func, options[:], synopsis
1277 else:
1277 else:
1278 table[name] = func, options[:]
1278 table[name] = func, options[:]
1279 return func
1279 return func
1280 return decorator
1280 return decorator
1281
1281
1282 return cmd
1282 return cmd
@@ -1,5711 +1,5710 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 _, gettext
10 from i18n import _, gettext
11 import os, re, difflib, time, tempfile, errno
11 import os, re, difflib, time, tempfile, errno
12 import hg, scmutil, util, revlog, extensions, copies, error, bookmarks
12 import hg, scmutil, util, revlog, extensions, copies, error, bookmarks
13 import patch, help, url, encoding, templatekw, discovery
13 import patch, help, url, encoding, templatekw, discovery
14 import archival, changegroup, cmdutil, hbisect
14 import archival, changegroup, cmdutil, hbisect
15 import sshserver, hgweb, hgweb.server, commandserver
15 import sshserver, hgweb, hgweb.server, commandserver
16 import match as matchmod
16 import match as matchmod
17 import merge as mergemod
17 import merge as mergemod
18 import minirst, revset, fileset
18 import minirst, revset, fileset
19 import dagparser, context, simplemerge
19 import dagparser, context, simplemerge
20 import random, setdiscovery, treediscovery, dagutil
20 import random, setdiscovery, treediscovery, dagutil
21
21
22 table = {}
22 table = {}
23
23
24 command = cmdutil.command(table)
24 command = cmdutil.command(table)
25
25
26 # common command options
26 # common command options
27
27
28 globalopts = [
28 globalopts = [
29 ('R', 'repository', '',
29 ('R', 'repository', '',
30 _('repository root directory or name of overlay bundle file'),
30 _('repository root directory or name of overlay bundle file'),
31 _('REPO')),
31 _('REPO')),
32 ('', 'cwd', '',
32 ('', 'cwd', '',
33 _('change working directory'), _('DIR')),
33 _('change working directory'), _('DIR')),
34 ('y', 'noninteractive', None,
34 ('y', 'noninteractive', None,
35 _('do not prompt, automatically pick the first choice for all prompts')),
35 _('do not prompt, automatically pick the first choice for all prompts')),
36 ('q', 'quiet', None, _('suppress output')),
36 ('q', 'quiet', None, _('suppress output')),
37 ('v', 'verbose', None, _('enable additional output')),
37 ('v', 'verbose', None, _('enable additional output')),
38 ('', 'config', [],
38 ('', 'config', [],
39 _('set/override config option (use \'section.name=value\')'),
39 _('set/override config option (use \'section.name=value\')'),
40 _('CONFIG')),
40 _('CONFIG')),
41 ('', 'debug', None, _('enable debugging output')),
41 ('', 'debug', None, _('enable debugging output')),
42 ('', 'debugger', None, _('start debugger')),
42 ('', 'debugger', None, _('start debugger')),
43 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
43 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
44 _('ENCODE')),
44 _('ENCODE')),
45 ('', 'encodingmode', encoding.encodingmode,
45 ('', 'encodingmode', encoding.encodingmode,
46 _('set the charset encoding mode'), _('MODE')),
46 _('set the charset encoding mode'), _('MODE')),
47 ('', 'traceback', None, _('always print a traceback on exception')),
47 ('', 'traceback', None, _('always print a traceback on exception')),
48 ('', 'time', None, _('time how long the command takes')),
48 ('', 'time', None, _('time how long the command takes')),
49 ('', 'profile', None, _('print command execution profile')),
49 ('', 'profile', None, _('print command execution profile')),
50 ('', 'version', None, _('output version information and exit')),
50 ('', 'version', None, _('output version information and exit')),
51 ('h', 'help', None, _('display help and exit')),
51 ('h', 'help', None, _('display help and exit')),
52 ]
52 ]
53
53
54 dryrunopts = [('n', 'dry-run', None,
54 dryrunopts = [('n', 'dry-run', None,
55 _('do not perform actions, just print output'))]
55 _('do not perform actions, just print output'))]
56
56
57 remoteopts = [
57 remoteopts = [
58 ('e', 'ssh', '',
58 ('e', 'ssh', '',
59 _('specify ssh command to use'), _('CMD')),
59 _('specify ssh command to use'), _('CMD')),
60 ('', 'remotecmd', '',
60 ('', 'remotecmd', '',
61 _('specify hg command to run on the remote side'), _('CMD')),
61 _('specify hg command to run on the remote side'), _('CMD')),
62 ('', 'insecure', None,
62 ('', 'insecure', None,
63 _('do not verify server certificate (ignoring web.cacerts config)')),
63 _('do not verify server certificate (ignoring web.cacerts config)')),
64 ]
64 ]
65
65
66 walkopts = [
66 walkopts = [
67 ('I', 'include', [],
67 ('I', 'include', [],
68 _('include names matching the given patterns'), _('PATTERN')),
68 _('include names matching the given patterns'), _('PATTERN')),
69 ('X', 'exclude', [],
69 ('X', 'exclude', [],
70 _('exclude names matching the given patterns'), _('PATTERN')),
70 _('exclude names matching the given patterns'), _('PATTERN')),
71 ]
71 ]
72
72
73 commitopts = [
73 commitopts = [
74 ('m', 'message', '',
74 ('m', 'message', '',
75 _('use text as commit message'), _('TEXT')),
75 _('use text as commit message'), _('TEXT')),
76 ('l', 'logfile', '',
76 ('l', 'logfile', '',
77 _('read commit message from file'), _('FILE')),
77 _('read commit message from file'), _('FILE')),
78 ]
78 ]
79
79
80 commitopts2 = [
80 commitopts2 = [
81 ('d', 'date', '',
81 ('d', 'date', '',
82 _('record the specified date as commit date'), _('DATE')),
82 _('record the specified date as commit date'), _('DATE')),
83 ('u', 'user', '',
83 ('u', 'user', '',
84 _('record the specified user as committer'), _('USER')),
84 _('record the specified user as committer'), _('USER')),
85 ]
85 ]
86
86
87 templateopts = [
87 templateopts = [
88 ('', 'style', '',
88 ('', 'style', '',
89 _('display using template map file'), _('STYLE')),
89 _('display using template map file'), _('STYLE')),
90 ('', 'template', '',
90 ('', 'template', '',
91 _('display with template'), _('TEMPLATE')),
91 _('display with template'), _('TEMPLATE')),
92 ]
92 ]
93
93
94 logopts = [
94 logopts = [
95 ('p', 'patch', None, _('show patch')),
95 ('p', 'patch', None, _('show patch')),
96 ('g', 'git', None, _('use git extended diff format')),
96 ('g', 'git', None, _('use git extended diff format')),
97 ('l', 'limit', '',
97 ('l', 'limit', '',
98 _('limit number of changes displayed'), _('NUM')),
98 _('limit number of changes displayed'), _('NUM')),
99 ('M', 'no-merges', None, _('do not show merges')),
99 ('M', 'no-merges', None, _('do not show merges')),
100 ('', 'stat', None, _('output diffstat-style summary of changes')),
100 ('', 'stat', None, _('output diffstat-style summary of changes')),
101 ] + templateopts
101 ] + templateopts
102
102
103 diffopts = [
103 diffopts = [
104 ('a', 'text', None, _('treat all files as text')),
104 ('a', 'text', None, _('treat all files as text')),
105 ('g', 'git', None, _('use git extended diff format')),
105 ('g', 'git', None, _('use git extended diff format')),
106 ('', 'nodates', None, _('omit dates from diff headers'))
106 ('', 'nodates', None, _('omit dates from diff headers'))
107 ]
107 ]
108
108
109 diffwsopts = [
109 diffwsopts = [
110 ('w', 'ignore-all-space', None,
110 ('w', 'ignore-all-space', None,
111 _('ignore white space when comparing lines')),
111 _('ignore white space when comparing lines')),
112 ('b', 'ignore-space-change', None,
112 ('b', 'ignore-space-change', None,
113 _('ignore changes in the amount of white space')),
113 _('ignore changes in the amount of white space')),
114 ('B', 'ignore-blank-lines', None,
114 ('B', 'ignore-blank-lines', None,
115 _('ignore changes whose lines are all blank')),
115 _('ignore changes whose lines are all blank')),
116 ]
116 ]
117
117
118 diffopts2 = [
118 diffopts2 = [
119 ('p', 'show-function', None, _('show which function each change is in')),
119 ('p', 'show-function', None, _('show which function each change is in')),
120 ('', 'reverse', None, _('produce a diff that undoes the changes')),
120 ('', 'reverse', None, _('produce a diff that undoes the changes')),
121 ] + diffwsopts + [
121 ] + diffwsopts + [
122 ('U', 'unified', '',
122 ('U', 'unified', '',
123 _('number of lines of context to show'), _('NUM')),
123 _('number of lines of context to show'), _('NUM')),
124 ('', 'stat', None, _('output diffstat-style summary of changes')),
124 ('', 'stat', None, _('output diffstat-style summary of changes')),
125 ]
125 ]
126
126
127 mergetoolopts = [
127 mergetoolopts = [
128 ('t', 'tool', '', _('specify merge tool')),
128 ('t', 'tool', '', _('specify merge tool')),
129 ]
129 ]
130
130
131 similarityopts = [
131 similarityopts = [
132 ('s', 'similarity', '',
132 ('s', 'similarity', '',
133 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
133 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
134 ]
134 ]
135
135
136 subrepoopts = [
136 subrepoopts = [
137 ('S', 'subrepos', None,
137 ('S', 'subrepos', None,
138 _('recurse into subrepositories'))
138 _('recurse into subrepositories'))
139 ]
139 ]
140
140
141 # Commands start here, listed alphabetically
141 # Commands start here, listed alphabetically
142
142
143 @command('^add',
143 @command('^add',
144 walkopts + subrepoopts + dryrunopts,
144 walkopts + subrepoopts + dryrunopts,
145 _('[OPTION]... [FILE]...'))
145 _('[OPTION]... [FILE]...'))
146 def add(ui, repo, *pats, **opts):
146 def add(ui, repo, *pats, **opts):
147 """add the specified files on the next commit
147 """add the specified files on the next commit
148
148
149 Schedule files to be version controlled and added to the
149 Schedule files to be version controlled and added to the
150 repository.
150 repository.
151
151
152 The files will be added to the repository at the next commit. To
152 The files will be added to the repository at the next commit. To
153 undo an add before that, see :hg:`forget`.
153 undo an add before that, see :hg:`forget`.
154
154
155 If no names are given, add all files to the repository.
155 If no names are given, add all files to the repository.
156
156
157 .. container:: verbose
157 .. container:: verbose
158
158
159 An example showing how new (unknown) files are added
159 An example showing how new (unknown) files are added
160 automatically by :hg:`add`::
160 automatically by :hg:`add`::
161
161
162 $ ls
162 $ ls
163 foo.c
163 foo.c
164 $ hg status
164 $ hg status
165 ? foo.c
165 ? foo.c
166 $ hg add
166 $ hg add
167 adding foo.c
167 adding foo.c
168 $ hg status
168 $ hg status
169 A foo.c
169 A foo.c
170
170
171 Returns 0 if all files are successfully added.
171 Returns 0 if all files are successfully added.
172 """
172 """
173
173
174 m = scmutil.match(repo[None], pats, opts)
174 m = scmutil.match(repo[None], pats, opts)
175 rejected = cmdutil.add(ui, repo, m, opts.get('dry_run'),
175 rejected = cmdutil.add(ui, repo, m, opts.get('dry_run'),
176 opts.get('subrepos'), prefix="")
176 opts.get('subrepos'), prefix="")
177 return rejected and 1 or 0
177 return rejected and 1 or 0
178
178
179 @command('addremove',
179 @command('addremove',
180 similarityopts + walkopts + dryrunopts,
180 similarityopts + walkopts + dryrunopts,
181 _('[OPTION]... [FILE]...'))
181 _('[OPTION]... [FILE]...'))
182 def addremove(ui, repo, *pats, **opts):
182 def addremove(ui, repo, *pats, **opts):
183 """add all new files, delete all missing files
183 """add all new files, delete all missing files
184
184
185 Add all new files and remove all missing files from the
185 Add all new files and remove all missing files from the
186 repository.
186 repository.
187
187
188 New files are ignored if they match any of the patterns in
188 New files are ignored if they match any of the patterns in
189 ``.hgignore``. As with add, these changes take effect at the next
189 ``.hgignore``. As with add, these changes take effect at the next
190 commit.
190 commit.
191
191
192 Use the -s/--similarity option to detect renamed files. With a
192 Use the -s/--similarity option to detect renamed files. With a
193 parameter greater than 0, this compares every removed file with
193 parameter greater than 0, this compares every removed file with
194 every added file and records those similar enough as renames. This
194 every added file and records those similar enough as renames. This
195 option takes a percentage between 0 (disabled) and 100 (files must
195 option takes a percentage between 0 (disabled) and 100 (files must
196 be identical) as its parameter. Detecting renamed files this way
196 be identical) as its parameter. Detecting renamed files this way
197 can be expensive. After using this option, :hg:`status -C` can be
197 can be expensive. After using this option, :hg:`status -C` can be
198 used to check which files were identified as moved or renamed.
198 used to check which files were identified as moved or renamed.
199
199
200 Returns 0 if all files are successfully added.
200 Returns 0 if all files are successfully added.
201 """
201 """
202 try:
202 try:
203 sim = float(opts.get('similarity') or 100)
203 sim = float(opts.get('similarity') or 100)
204 except ValueError:
204 except ValueError:
205 raise util.Abort(_('similarity must be a number'))
205 raise util.Abort(_('similarity must be a number'))
206 if sim < 0 or sim > 100:
206 if sim < 0 or sim > 100:
207 raise util.Abort(_('similarity must be between 0 and 100'))
207 raise util.Abort(_('similarity must be between 0 and 100'))
208 return scmutil.addremove(repo, pats, opts, similarity=sim / 100.0)
208 return scmutil.addremove(repo, pats, opts, similarity=sim / 100.0)
209
209
210 @command('^annotate|blame',
210 @command('^annotate|blame',
211 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
211 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
212 ('', 'follow', None,
212 ('', 'follow', None,
213 _('follow copies/renames and list the filename (DEPRECATED)')),
213 _('follow copies/renames and list the filename (DEPRECATED)')),
214 ('', 'no-follow', None, _("don't follow copies and renames")),
214 ('', 'no-follow', None, _("don't follow copies and renames")),
215 ('a', 'text', None, _('treat all files as text')),
215 ('a', 'text', None, _('treat all files as text')),
216 ('u', 'user', None, _('list the author (long with -v)')),
216 ('u', 'user', None, _('list the author (long with -v)')),
217 ('f', 'file', None, _('list the filename')),
217 ('f', 'file', None, _('list the filename')),
218 ('d', 'date', None, _('list the date (short with -q)')),
218 ('d', 'date', None, _('list the date (short with -q)')),
219 ('n', 'number', None, _('list the revision number (default)')),
219 ('n', 'number', None, _('list the revision number (default)')),
220 ('c', 'changeset', None, _('list the changeset')),
220 ('c', 'changeset', None, _('list the changeset')),
221 ('l', 'line-number', None, _('show line number at the first appearance'))
221 ('l', 'line-number', None, _('show line number at the first appearance'))
222 ] + diffwsopts + walkopts,
222 ] + diffwsopts + walkopts,
223 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'))
223 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'))
224 def annotate(ui, repo, *pats, **opts):
224 def annotate(ui, repo, *pats, **opts):
225 """show changeset information by line for each file
225 """show changeset information by line for each file
226
226
227 List changes in files, showing the revision id responsible for
227 List changes in files, showing the revision id responsible for
228 each line
228 each line
229
229
230 This command is useful for discovering when a change was made and
230 This command is useful for discovering when a change was made and
231 by whom.
231 by whom.
232
232
233 Without the -a/--text option, annotate will avoid processing files
233 Without the -a/--text option, annotate will avoid processing files
234 it detects as binary. With -a, annotate will annotate the file
234 it detects as binary. With -a, annotate will annotate the file
235 anyway, although the results will probably be neither useful
235 anyway, although the results will probably be neither useful
236 nor desirable.
236 nor desirable.
237
237
238 Returns 0 on success.
238 Returns 0 on success.
239 """
239 """
240 if opts.get('follow'):
240 if opts.get('follow'):
241 # --follow is deprecated and now just an alias for -f/--file
241 # --follow is deprecated and now just an alias for -f/--file
242 # to mimic the behavior of Mercurial before version 1.5
242 # to mimic the behavior of Mercurial before version 1.5
243 opts['file'] = True
243 opts['file'] = True
244
244
245 datefunc = ui.quiet and util.shortdate or util.datestr
245 datefunc = ui.quiet and util.shortdate or util.datestr
246 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
246 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
247
247
248 if not pats:
248 if not pats:
249 raise util.Abort(_('at least one filename or pattern is required'))
249 raise util.Abort(_('at least one filename or pattern is required'))
250
250
251 hexfn = ui.debugflag and hex or short
251 hexfn = ui.debugflag and hex or short
252
252
253 opmap = [('user', ' ', lambda x: ui.shortuser(x[0].user())),
253 opmap = [('user', ' ', lambda x: ui.shortuser(x[0].user())),
254 ('number', ' ', lambda x: str(x[0].rev())),
254 ('number', ' ', lambda x: str(x[0].rev())),
255 ('changeset', ' ', lambda x: hexfn(x[0].node())),
255 ('changeset', ' ', lambda x: hexfn(x[0].node())),
256 ('date', ' ', getdate),
256 ('date', ' ', getdate),
257 ('file', ' ', lambda x: x[0].path()),
257 ('file', ' ', lambda x: x[0].path()),
258 ('line_number', ':', lambda x: str(x[1])),
258 ('line_number', ':', lambda x: str(x[1])),
259 ]
259 ]
260
260
261 if (not opts.get('user') and not opts.get('changeset')
261 if (not opts.get('user') and not opts.get('changeset')
262 and not opts.get('date') and not opts.get('file')):
262 and not opts.get('date') and not opts.get('file')):
263 opts['number'] = True
263 opts['number'] = True
264
264
265 linenumber = opts.get('line_number') is not None
265 linenumber = opts.get('line_number') is not None
266 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
266 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
267 raise util.Abort(_('at least one of -n/-c is required for -l'))
267 raise util.Abort(_('at least one of -n/-c is required for -l'))
268
268
269 funcmap = [(func, sep) for op, sep, func in opmap if opts.get(op)]
269 funcmap = [(func, sep) for op, sep, func in opmap if opts.get(op)]
270 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
270 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
271
271
272 def bad(x, y):
272 def bad(x, y):
273 raise util.Abort("%s: %s" % (x, y))
273 raise util.Abort("%s: %s" % (x, y))
274
274
275 ctx = scmutil.revsingle(repo, opts.get('rev'))
275 ctx = scmutil.revsingle(repo, opts.get('rev'))
276 m = scmutil.match(ctx, pats, opts)
276 m = scmutil.match(ctx, pats, opts)
277 m.bad = bad
277 m.bad = bad
278 follow = not opts.get('no_follow')
278 follow = not opts.get('no_follow')
279 diffopts = patch.diffopts(ui, opts, section='annotate')
279 diffopts = patch.diffopts(ui, opts, section='annotate')
280 for abs in ctx.walk(m):
280 for abs in ctx.walk(m):
281 fctx = ctx[abs]
281 fctx = ctx[abs]
282 if not opts.get('text') and util.binary(fctx.data()):
282 if not opts.get('text') and util.binary(fctx.data()):
283 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
283 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
284 continue
284 continue
285
285
286 lines = fctx.annotate(follow=follow, linenumber=linenumber,
286 lines = fctx.annotate(follow=follow, linenumber=linenumber,
287 diffopts=diffopts)
287 diffopts=diffopts)
288 pieces = []
288 pieces = []
289
289
290 for f, sep in funcmap:
290 for f, sep in funcmap:
291 l = [f(n) for n, dummy in lines]
291 l = [f(n) for n, dummy in lines]
292 if l:
292 if l:
293 sized = [(x, encoding.colwidth(x)) for x in l]
293 sized = [(x, encoding.colwidth(x)) for x in l]
294 ml = max([w for x, w in sized])
294 ml = max([w for x, w in sized])
295 pieces.append(["%s%s%s" % (sep, ' ' * (ml - w), x)
295 pieces.append(["%s%s%s" % (sep, ' ' * (ml - w), x)
296 for x, w in sized])
296 for x, w in sized])
297
297
298 if pieces:
298 if pieces:
299 for p, l in zip(zip(*pieces), lines):
299 for p, l in zip(zip(*pieces), lines):
300 ui.write("%s: %s" % ("".join(p), l[1]))
300 ui.write("%s: %s" % ("".join(p), l[1]))
301
301
302 @command('archive',
302 @command('archive',
303 [('', 'no-decode', None, _('do not pass files through decoders')),
303 [('', 'no-decode', None, _('do not pass files through decoders')),
304 ('p', 'prefix', '', _('directory prefix for files in archive'),
304 ('p', 'prefix', '', _('directory prefix for files in archive'),
305 _('PREFIX')),
305 _('PREFIX')),
306 ('r', 'rev', '', _('revision to distribute'), _('REV')),
306 ('r', 'rev', '', _('revision to distribute'), _('REV')),
307 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
307 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
308 ] + subrepoopts + walkopts,
308 ] + subrepoopts + walkopts,
309 _('[OPTION]... DEST'))
309 _('[OPTION]... DEST'))
310 def archive(ui, repo, dest, **opts):
310 def archive(ui, repo, dest, **opts):
311 '''create an unversioned archive of a repository revision
311 '''create an unversioned archive of a repository revision
312
312
313 By default, the revision used is the parent of the working
313 By default, the revision used is the parent of the working
314 directory; use -r/--rev to specify a different revision.
314 directory; use -r/--rev to specify a different revision.
315
315
316 The archive type is automatically detected based on file
316 The archive type is automatically detected based on file
317 extension (or override using -t/--type).
317 extension (or override using -t/--type).
318
318
319 .. container:: verbose
319 .. container:: verbose
320
320
321 Examples:
321 Examples:
322
322
323 - create a zip file containing the 1.0 release::
323 - create a zip file containing the 1.0 release::
324
324
325 hg archive -r 1.0 project-1.0.zip
325 hg archive -r 1.0 project-1.0.zip
326
326
327 - create a tarball excluding .hg files::
327 - create a tarball excluding .hg files::
328
328
329 hg archive project.tar.gz -X ".hg*"
329 hg archive project.tar.gz -X ".hg*"
330
330
331 Valid types are:
331 Valid types are:
332
332
333 :``files``: a directory full of files (default)
333 :``files``: a directory full of files (default)
334 :``tar``: tar archive, uncompressed
334 :``tar``: tar archive, uncompressed
335 :``tbz2``: tar archive, compressed using bzip2
335 :``tbz2``: tar archive, compressed using bzip2
336 :``tgz``: tar archive, compressed using gzip
336 :``tgz``: tar archive, compressed using gzip
337 :``uzip``: zip archive, uncompressed
337 :``uzip``: zip archive, uncompressed
338 :``zip``: zip archive, compressed using deflate
338 :``zip``: zip archive, compressed using deflate
339
339
340 The exact name of the destination archive or directory is given
340 The exact name of the destination archive or directory is given
341 using a format string; see :hg:`help export` for details.
341 using a format string; see :hg:`help export` for details.
342
342
343 Each member added to an archive file has a directory prefix
343 Each member added to an archive file has a directory prefix
344 prepended. Use -p/--prefix to specify a format string for the
344 prepended. Use -p/--prefix to specify a format string for the
345 prefix. The default is the basename of the archive, with suffixes
345 prefix. The default is the basename of the archive, with suffixes
346 removed.
346 removed.
347
347
348 Returns 0 on success.
348 Returns 0 on success.
349 '''
349 '''
350
350
351 ctx = scmutil.revsingle(repo, opts.get('rev'))
351 ctx = scmutil.revsingle(repo, opts.get('rev'))
352 if not ctx:
352 if not ctx:
353 raise util.Abort(_('no working directory: please specify a revision'))
353 raise util.Abort(_('no working directory: please specify a revision'))
354 node = ctx.node()
354 node = ctx.node()
355 dest = cmdutil.makefilename(repo, dest, node)
355 dest = cmdutil.makefilename(repo, dest, node)
356 if os.path.realpath(dest) == repo.root:
356 if os.path.realpath(dest) == repo.root:
357 raise util.Abort(_('repository root cannot be destination'))
357 raise util.Abort(_('repository root cannot be destination'))
358
358
359 kind = opts.get('type') or archival.guesskind(dest) or 'files'
359 kind = opts.get('type') or archival.guesskind(dest) or 'files'
360 prefix = opts.get('prefix')
360 prefix = opts.get('prefix')
361
361
362 if dest == '-':
362 if dest == '-':
363 if kind == 'files':
363 if kind == 'files':
364 raise util.Abort(_('cannot archive plain files to stdout'))
364 raise util.Abort(_('cannot archive plain files to stdout'))
365 dest = cmdutil.makefileobj(repo, dest)
365 dest = cmdutil.makefileobj(repo, dest)
366 if not prefix:
366 if not prefix:
367 prefix = os.path.basename(repo.root) + '-%h'
367 prefix = os.path.basename(repo.root) + '-%h'
368
368
369 prefix = cmdutil.makefilename(repo, prefix, node)
369 prefix = cmdutil.makefilename(repo, prefix, node)
370 matchfn = scmutil.match(ctx, [], opts)
370 matchfn = scmutil.match(ctx, [], opts)
371 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
371 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
372 matchfn, prefix, subrepos=opts.get('subrepos'))
372 matchfn, prefix, subrepos=opts.get('subrepos'))
373
373
374 @command('backout',
374 @command('backout',
375 [('', 'merge', None, _('merge with old dirstate parent after backout')),
375 [('', 'merge', None, _('merge with old dirstate parent after backout')),
376 ('', 'parent', '',
376 ('', 'parent', '',
377 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
377 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
378 ('r', 'rev', '', _('revision to backout'), _('REV')),
378 ('r', 'rev', '', _('revision to backout'), _('REV')),
379 ] + mergetoolopts + walkopts + commitopts + commitopts2,
379 ] + mergetoolopts + walkopts + commitopts + commitopts2,
380 _('[OPTION]... [-r] REV'))
380 _('[OPTION]... [-r] REV'))
381 def backout(ui, repo, node=None, rev=None, **opts):
381 def backout(ui, repo, node=None, rev=None, **opts):
382 '''reverse effect of earlier changeset
382 '''reverse effect of earlier changeset
383
383
384 Prepare a new changeset with the effect of REV undone in the
384 Prepare a new changeset with the effect of REV undone in the
385 current working directory.
385 current working directory.
386
386
387 If REV is the parent of the working directory, then this new changeset
387 If REV is the parent of the working directory, then this new changeset
388 is committed automatically. Otherwise, hg needs to merge the
388 is committed automatically. Otherwise, hg needs to merge the
389 changes and the merged result is left uncommitted.
389 changes and the merged result is left uncommitted.
390
390
391 .. note::
391 .. note::
392 backout cannot be used to fix either an unwanted or
392 backout cannot be used to fix either an unwanted or
393 incorrect merge.
393 incorrect merge.
394
394
395 .. container:: verbose
395 .. container:: verbose
396
396
397 By default, the pending changeset will have one parent,
397 By default, the pending changeset will have one parent,
398 maintaining a linear history. With --merge, the pending
398 maintaining a linear history. With --merge, the pending
399 changeset will instead have two parents: the old parent of the
399 changeset will instead have two parents: the old parent of the
400 working directory and a new child of REV that simply undoes REV.
400 working directory and a new child of REV that simply undoes REV.
401
401
402 Before version 1.7, the behavior without --merge was equivalent
402 Before version 1.7, the behavior without --merge was equivalent
403 to specifying --merge followed by :hg:`update --clean .` to
403 to specifying --merge followed by :hg:`update --clean .` to
404 cancel the merge and leave the child of REV as a head to be
404 cancel the merge and leave the child of REV as a head to be
405 merged separately.
405 merged separately.
406
406
407 See :hg:`help dates` for a list of formats valid for -d/--date.
407 See :hg:`help dates` for a list of formats valid for -d/--date.
408
408
409 Returns 0 on success.
409 Returns 0 on success.
410 '''
410 '''
411 if rev and node:
411 if rev and node:
412 raise util.Abort(_("please specify just one revision"))
412 raise util.Abort(_("please specify just one revision"))
413
413
414 if not rev:
414 if not rev:
415 rev = node
415 rev = node
416
416
417 if not rev:
417 if not rev:
418 raise util.Abort(_("please specify a revision to backout"))
418 raise util.Abort(_("please specify a revision to backout"))
419
419
420 date = opts.get('date')
420 date = opts.get('date')
421 if date:
421 if date:
422 opts['date'] = util.parsedate(date)
422 opts['date'] = util.parsedate(date)
423
423
424 cmdutil.bailifchanged(repo)
424 cmdutil.bailifchanged(repo)
425 node = scmutil.revsingle(repo, rev).node()
425 node = scmutil.revsingle(repo, rev).node()
426
426
427 op1, op2 = repo.dirstate.parents()
427 op1, op2 = repo.dirstate.parents()
428 a = repo.changelog.ancestor(op1, node)
428 a = repo.changelog.ancestor(op1, node)
429 if a != node:
429 if a != node:
430 raise util.Abort(_('cannot backout change on a different branch'))
430 raise util.Abort(_('cannot backout change on a different branch'))
431
431
432 p1, p2 = repo.changelog.parents(node)
432 p1, p2 = repo.changelog.parents(node)
433 if p1 == nullid:
433 if p1 == nullid:
434 raise util.Abort(_('cannot backout a change with no parents'))
434 raise util.Abort(_('cannot backout a change with no parents'))
435 if p2 != nullid:
435 if p2 != nullid:
436 if not opts.get('parent'):
436 if not opts.get('parent'):
437 raise util.Abort(_('cannot backout a merge changeset'))
437 raise util.Abort(_('cannot backout a merge changeset'))
438 p = repo.lookup(opts['parent'])
438 p = repo.lookup(opts['parent'])
439 if p not in (p1, p2):
439 if p not in (p1, p2):
440 raise util.Abort(_('%s is not a parent of %s') %
440 raise util.Abort(_('%s is not a parent of %s') %
441 (short(p), short(node)))
441 (short(p), short(node)))
442 parent = p
442 parent = p
443 else:
443 else:
444 if opts.get('parent'):
444 if opts.get('parent'):
445 raise util.Abort(_('cannot use --parent on non-merge changeset'))
445 raise util.Abort(_('cannot use --parent on non-merge changeset'))
446 parent = p1
446 parent = p1
447
447
448 # the backout should appear on the same branch
448 # the backout should appear on the same branch
449 branch = repo.dirstate.branch()
449 branch = repo.dirstate.branch()
450 hg.clean(repo, node, show_stats=False)
450 hg.clean(repo, node, show_stats=False)
451 repo.dirstate.setbranch(branch)
451 repo.dirstate.setbranch(branch)
452 revert_opts = opts.copy()
452 revert_opts = opts.copy()
453 revert_opts['date'] = None
453 revert_opts['date'] = None
454 revert_opts['all'] = True
454 revert_opts['all'] = True
455 revert_opts['rev'] = hex(parent)
455 revert_opts['rev'] = hex(parent)
456 revert_opts['no_backup'] = None
456 revert_opts['no_backup'] = None
457 revert(ui, repo, **revert_opts)
457 revert(ui, repo, **revert_opts)
458 if not opts.get('merge') and op1 != node:
458 if not opts.get('merge') and op1 != node:
459 try:
459 try:
460 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
460 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
461 return hg.update(repo, op1)
461 return hg.update(repo, op1)
462 finally:
462 finally:
463 ui.setconfig('ui', 'forcemerge', '')
463 ui.setconfig('ui', 'forcemerge', '')
464
464
465 commit_opts = opts.copy()
465 commit_opts = opts.copy()
466 commit_opts['addremove'] = False
466 commit_opts['addremove'] = False
467 if not commit_opts['message'] and not commit_opts['logfile']:
467 if not commit_opts['message'] and not commit_opts['logfile']:
468 # we don't translate commit messages
468 # we don't translate commit messages
469 commit_opts['message'] = "Backed out changeset %s" % short(node)
469 commit_opts['message'] = "Backed out changeset %s" % short(node)
470 commit_opts['force_editor'] = True
470 commit_opts['force_editor'] = True
471 commit(ui, repo, **commit_opts)
471 commit(ui, repo, **commit_opts)
472 def nice(node):
472 def nice(node):
473 return '%d:%s' % (repo.changelog.rev(node), short(node))
473 return '%d:%s' % (repo.changelog.rev(node), short(node))
474 ui.status(_('changeset %s backs out changeset %s\n') %
474 ui.status(_('changeset %s backs out changeset %s\n') %
475 (nice(repo.changelog.tip()), nice(node)))
475 (nice(repo.changelog.tip()), nice(node)))
476 if opts.get('merge') and op1 != node:
476 if opts.get('merge') and op1 != node:
477 hg.clean(repo, op1, show_stats=False)
477 hg.clean(repo, op1, show_stats=False)
478 ui.status(_('merging with changeset %s\n')
478 ui.status(_('merging with changeset %s\n')
479 % nice(repo.changelog.tip()))
479 % nice(repo.changelog.tip()))
480 try:
480 try:
481 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
481 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
482 return hg.merge(repo, hex(repo.changelog.tip()))
482 return hg.merge(repo, hex(repo.changelog.tip()))
483 finally:
483 finally:
484 ui.setconfig('ui', 'forcemerge', '')
484 ui.setconfig('ui', 'forcemerge', '')
485 return 0
485 return 0
486
486
487 @command('bisect',
487 @command('bisect',
488 [('r', 'reset', False, _('reset bisect state')),
488 [('r', 'reset', False, _('reset bisect state')),
489 ('g', 'good', False, _('mark changeset good')),
489 ('g', 'good', False, _('mark changeset good')),
490 ('b', 'bad', False, _('mark changeset bad')),
490 ('b', 'bad', False, _('mark changeset bad')),
491 ('s', 'skip', False, _('skip testing changeset')),
491 ('s', 'skip', False, _('skip testing changeset')),
492 ('e', 'extend', False, _('extend the bisect range')),
492 ('e', 'extend', False, _('extend the bisect range')),
493 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
493 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
494 ('U', 'noupdate', False, _('do not update to target'))],
494 ('U', 'noupdate', False, _('do not update to target'))],
495 _("[-gbsr] [-U] [-c CMD] [REV]"))
495 _("[-gbsr] [-U] [-c CMD] [REV]"))
496 def bisect(ui, repo, rev=None, extra=None, command=None,
496 def bisect(ui, repo, rev=None, extra=None, command=None,
497 reset=None, good=None, bad=None, skip=None, extend=None,
497 reset=None, good=None, bad=None, skip=None, extend=None,
498 noupdate=None):
498 noupdate=None):
499 """subdivision search of changesets
499 """subdivision search of changesets
500
500
501 This command helps to find changesets which introduce problems. To
501 This command helps to find changesets which introduce problems. To
502 use, mark the earliest changeset you know exhibits the problem as
502 use, mark the earliest changeset you know exhibits the problem as
503 bad, then mark the latest changeset which is free from the problem
503 bad, then mark the latest changeset which is free from the problem
504 as good. Bisect will update your working directory to a revision
504 as good. Bisect will update your working directory to a revision
505 for testing (unless the -U/--noupdate option is specified). Once
505 for testing (unless the -U/--noupdate option is specified). Once
506 you have performed tests, mark the working directory as good or
506 you have performed tests, mark the working directory as good or
507 bad, and bisect will either update to another candidate changeset
507 bad, and bisect will either update to another candidate changeset
508 or announce that it has found the bad revision.
508 or announce that it has found the bad revision.
509
509
510 As a shortcut, you can also use the revision argument to mark a
510 As a shortcut, you can also use the revision argument to mark a
511 revision as good or bad without checking it out first.
511 revision as good or bad without checking it out first.
512
512
513 If you supply a command, it will be used for automatic bisection.
513 If you supply a command, it will be used for automatic bisection.
514 Its exit status will be used to mark revisions as good or bad:
514 Its exit status will be used to mark revisions as good or bad:
515 status 0 means good, 125 means to skip the revision, 127
515 status 0 means good, 125 means to skip the revision, 127
516 (command not found) will abort the bisection, and any other
516 (command not found) will abort the bisection, and any other
517 non-zero exit status means the revision is bad.
517 non-zero exit status means the revision is bad.
518
518
519 .. container:: verbose
519 .. container:: verbose
520
520
521 Some examples:
521 Some examples:
522
522
523 - start a bisection with known bad revision 12, and good revision 34::
523 - start a bisection with known bad revision 12, and good revision 34::
524
524
525 hg bisect --bad 34
525 hg bisect --bad 34
526 hg bisect --good 12
526 hg bisect --good 12
527
527
528 - advance the current bisection by marking current revision as good or
528 - advance the current bisection by marking current revision as good or
529 bad::
529 bad::
530
530
531 hg bisect --good
531 hg bisect --good
532 hg bisect --bad
532 hg bisect --bad
533
533
534 - mark the current revision, or a known revision, to be skipped (eg. if
534 - mark the current revision, or a known revision, to be skipped (eg. if
535 that revision is not usable because of another issue)::
535 that revision is not usable because of another issue)::
536
536
537 hg bisect --skip
537 hg bisect --skip
538 hg bisect --skip 23
538 hg bisect --skip 23
539
539
540 - forget the current bisection::
540 - forget the current bisection::
541
541
542 hg bisect --reset
542 hg bisect --reset
543
543
544 - use 'make && make tests' to automatically find the first broken
544 - use 'make && make tests' to automatically find the first broken
545 revision::
545 revision::
546
546
547 hg bisect --reset
547 hg bisect --reset
548 hg bisect --bad 34
548 hg bisect --bad 34
549 hg bisect --good 12
549 hg bisect --good 12
550 hg bisect --command 'make && make tests'
550 hg bisect --command 'make && make tests'
551
551
552 - see all changesets whose states are already known in the current
552 - see all changesets whose states are already known in the current
553 bisection::
553 bisection::
554
554
555 hg log -r "bisect(pruned)"
555 hg log -r "bisect(pruned)"
556
556
557 - see all changesets that took part in the current bisection::
557 - see all changesets that took part in the current bisection::
558
558
559 hg log -r "bisect(range)"
559 hg log -r "bisect(range)"
560
560
561 - with the graphlog extension, you can even get a nice graph::
561 - with the graphlog extension, you can even get a nice graph::
562
562
563 hg log --graph -r "bisect(range)"
563 hg log --graph -r "bisect(range)"
564
564
565 See :hg:`help revsets` for more about the `bisect()` keyword.
565 See :hg:`help revsets` for more about the `bisect()` keyword.
566
566
567 Returns 0 on success.
567 Returns 0 on success.
568 """
568 """
569 def extendbisectrange(nodes, good):
569 def extendbisectrange(nodes, good):
570 # bisect is incomplete when it ends on a merge node and
570 # bisect is incomplete when it ends on a merge node and
571 # one of the parent was not checked.
571 # one of the parent was not checked.
572 parents = repo[nodes[0]].parents()
572 parents = repo[nodes[0]].parents()
573 if len(parents) > 1:
573 if len(parents) > 1:
574 side = good and state['bad'] or state['good']
574 side = good and state['bad'] or state['good']
575 num = len(set(i.node() for i in parents) & set(side))
575 num = len(set(i.node() for i in parents) & set(side))
576 if num == 1:
576 if num == 1:
577 return parents[0].ancestor(parents[1])
577 return parents[0].ancestor(parents[1])
578 return None
578 return None
579
579
580 def print_result(nodes, good):
580 def print_result(nodes, good):
581 displayer = cmdutil.show_changeset(ui, repo, {})
581 displayer = cmdutil.show_changeset(ui, repo, {})
582 if len(nodes) == 1:
582 if len(nodes) == 1:
583 # narrowed it down to a single revision
583 # narrowed it down to a single revision
584 if good:
584 if good:
585 ui.write(_("The first good revision is:\n"))
585 ui.write(_("The first good revision is:\n"))
586 else:
586 else:
587 ui.write(_("The first bad revision is:\n"))
587 ui.write(_("The first bad revision is:\n"))
588 displayer.show(repo[nodes[0]])
588 displayer.show(repo[nodes[0]])
589 extendnode = extendbisectrange(nodes, good)
589 extendnode = extendbisectrange(nodes, good)
590 if extendnode is not None:
590 if extendnode is not None:
591 ui.write(_('Not all ancestors of this changeset have been'
591 ui.write(_('Not all ancestors of this changeset have been'
592 ' checked.\nUse bisect --extend to continue the '
592 ' checked.\nUse bisect --extend to continue the '
593 'bisection from\nthe common ancestor, %s.\n')
593 'bisection from\nthe common ancestor, %s.\n')
594 % extendnode)
594 % extendnode)
595 else:
595 else:
596 # multiple possible revisions
596 # multiple possible revisions
597 if good:
597 if good:
598 ui.write(_("Due to skipped revisions, the first "
598 ui.write(_("Due to skipped revisions, the first "
599 "good revision could be any of:\n"))
599 "good revision could be any of:\n"))
600 else:
600 else:
601 ui.write(_("Due to skipped revisions, the first "
601 ui.write(_("Due to skipped revisions, the first "
602 "bad revision could be any of:\n"))
602 "bad revision could be any of:\n"))
603 for n in nodes:
603 for n in nodes:
604 displayer.show(repo[n])
604 displayer.show(repo[n])
605 displayer.close()
605 displayer.close()
606
606
607 def check_state(state, interactive=True):
607 def check_state(state, interactive=True):
608 if not state['good'] or not state['bad']:
608 if not state['good'] or not state['bad']:
609 if (good or bad or skip or reset) and interactive:
609 if (good or bad or skip or reset) and interactive:
610 return
610 return
611 if not state['good']:
611 if not state['good']:
612 raise util.Abort(_('cannot bisect (no known good revisions)'))
612 raise util.Abort(_('cannot bisect (no known good revisions)'))
613 else:
613 else:
614 raise util.Abort(_('cannot bisect (no known bad revisions)'))
614 raise util.Abort(_('cannot bisect (no known bad revisions)'))
615 return True
615 return True
616
616
617 # backward compatibility
617 # backward compatibility
618 if rev in "good bad reset init".split():
618 if rev in "good bad reset init".split():
619 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
619 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
620 cmd, rev, extra = rev, extra, None
620 cmd, rev, extra = rev, extra, None
621 if cmd == "good":
621 if cmd == "good":
622 good = True
622 good = True
623 elif cmd == "bad":
623 elif cmd == "bad":
624 bad = True
624 bad = True
625 else:
625 else:
626 reset = True
626 reset = True
627 elif extra or good + bad + skip + reset + extend + bool(command) > 1:
627 elif extra or good + bad + skip + reset + extend + bool(command) > 1:
628 raise util.Abort(_('incompatible arguments'))
628 raise util.Abort(_('incompatible arguments'))
629
629
630 if reset:
630 if reset:
631 p = repo.join("bisect.state")
631 p = repo.join("bisect.state")
632 if os.path.exists(p):
632 if os.path.exists(p):
633 os.unlink(p)
633 os.unlink(p)
634 return
634 return
635
635
636 state = hbisect.load_state(repo)
636 state = hbisect.load_state(repo)
637
637
638 if command:
638 if command:
639 changesets = 1
639 changesets = 1
640 try:
640 try:
641 while changesets:
641 while changesets:
642 # update state
642 # update state
643 status = util.system(command, out=ui.fout)
643 status = util.system(command, out=ui.fout)
644 if status == 125:
644 if status == 125:
645 transition = "skip"
645 transition = "skip"
646 elif status == 0:
646 elif status == 0:
647 transition = "good"
647 transition = "good"
648 # status < 0 means process was killed
648 # status < 0 means process was killed
649 elif status == 127:
649 elif status == 127:
650 raise util.Abort(_("failed to execute %s") % command)
650 raise util.Abort(_("failed to execute %s") % command)
651 elif status < 0:
651 elif status < 0:
652 raise util.Abort(_("%s killed") % command)
652 raise util.Abort(_("%s killed") % command)
653 else:
653 else:
654 transition = "bad"
654 transition = "bad"
655 ctx = scmutil.revsingle(repo, rev)
655 ctx = scmutil.revsingle(repo, rev)
656 rev = None # clear for future iterations
656 rev = None # clear for future iterations
657 state[transition].append(ctx.node())
657 state[transition].append(ctx.node())
658 ui.status(_('Changeset %d:%s: %s\n') % (ctx, ctx, transition))
658 ui.status(_('Changeset %d:%s: %s\n') % (ctx, ctx, transition))
659 check_state(state, interactive=False)
659 check_state(state, interactive=False)
660 # bisect
660 # bisect
661 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
661 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
662 # update to next check
662 # update to next check
663 cmdutil.bailifchanged(repo)
663 cmdutil.bailifchanged(repo)
664 hg.clean(repo, nodes[0], show_stats=False)
664 hg.clean(repo, nodes[0], show_stats=False)
665 finally:
665 finally:
666 hbisect.save_state(repo, state)
666 hbisect.save_state(repo, state)
667 print_result(nodes, good)
667 print_result(nodes, good)
668 return
668 return
669
669
670 # update state
670 # update state
671
671
672 if rev:
672 if rev:
673 nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])]
673 nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])]
674 else:
674 else:
675 nodes = [repo.lookup('.')]
675 nodes = [repo.lookup('.')]
676
676
677 if good or bad or skip:
677 if good or bad or skip:
678 if good:
678 if good:
679 state['good'] += nodes
679 state['good'] += nodes
680 elif bad:
680 elif bad:
681 state['bad'] += nodes
681 state['bad'] += nodes
682 elif skip:
682 elif skip:
683 state['skip'] += nodes
683 state['skip'] += nodes
684 hbisect.save_state(repo, state)
684 hbisect.save_state(repo, state)
685
685
686 if not check_state(state):
686 if not check_state(state):
687 return
687 return
688
688
689 # actually bisect
689 # actually bisect
690 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
690 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
691 if extend:
691 if extend:
692 if not changesets:
692 if not changesets:
693 extendnode = extendbisectrange(nodes, good)
693 extendnode = extendbisectrange(nodes, good)
694 if extendnode is not None:
694 if extendnode is not None:
695 ui.write(_("Extending search to changeset %d:%s\n"
695 ui.write(_("Extending search to changeset %d:%s\n"
696 % (extendnode.rev(), extendnode)))
696 % (extendnode.rev(), extendnode)))
697 if noupdate:
697 if noupdate:
698 return
698 return
699 cmdutil.bailifchanged(repo)
699 cmdutil.bailifchanged(repo)
700 return hg.clean(repo, extendnode.node())
700 return hg.clean(repo, extendnode.node())
701 raise util.Abort(_("nothing to extend"))
701 raise util.Abort(_("nothing to extend"))
702
702
703 if changesets == 0:
703 if changesets == 0:
704 print_result(nodes, good)
704 print_result(nodes, good)
705 else:
705 else:
706 assert len(nodes) == 1 # only a single node can be tested next
706 assert len(nodes) == 1 # only a single node can be tested next
707 node = nodes[0]
707 node = nodes[0]
708 # compute the approximate number of remaining tests
708 # compute the approximate number of remaining tests
709 tests, size = 0, 2
709 tests, size = 0, 2
710 while size <= changesets:
710 while size <= changesets:
711 tests, size = tests + 1, size * 2
711 tests, size = tests + 1, size * 2
712 rev = repo.changelog.rev(node)
712 rev = repo.changelog.rev(node)
713 ui.write(_("Testing changeset %d:%s "
713 ui.write(_("Testing changeset %d:%s "
714 "(%d changesets remaining, ~%d tests)\n")
714 "(%d changesets remaining, ~%d tests)\n")
715 % (rev, short(node), changesets, tests))
715 % (rev, short(node), changesets, tests))
716 if not noupdate:
716 if not noupdate:
717 cmdutil.bailifchanged(repo)
717 cmdutil.bailifchanged(repo)
718 return hg.clean(repo, node)
718 return hg.clean(repo, node)
719
719
720 @command('bookmarks',
720 @command('bookmarks',
721 [('f', 'force', False, _('force')),
721 [('f', 'force', False, _('force')),
722 ('r', 'rev', '', _('revision'), _('REV')),
722 ('r', 'rev', '', _('revision'), _('REV')),
723 ('d', 'delete', False, _('delete a given bookmark')),
723 ('d', 'delete', False, _('delete a given bookmark')),
724 ('m', 'rename', '', _('rename a given bookmark'), _('NAME')),
724 ('m', 'rename', '', _('rename a given bookmark'), _('NAME')),
725 ('i', 'inactive', False, _('do not mark a new bookmark active'))],
725 ('i', 'inactive', False, _('do not mark a new bookmark active'))],
726 _('hg bookmarks [-f] [-d] [-i] [-m NAME] [-r REV] [NAME]'))
726 _('hg bookmarks [-f] [-d] [-i] [-m NAME] [-r REV] [NAME]'))
727 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False,
727 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False,
728 rename=None, inactive=False):
728 rename=None, inactive=False):
729 '''track a line of development with movable markers
729 '''track a line of development with movable markers
730
730
731 Bookmarks are pointers to certain commits that move when committing.
731 Bookmarks are pointers to certain commits that move when committing.
732 Bookmarks are local. They can be renamed, copied and deleted. It is
732 Bookmarks are local. They can be renamed, copied and deleted. It is
733 possible to use :hg:`merge NAME` to merge from a given bookmark, and
733 possible to use :hg:`merge NAME` to merge from a given bookmark, and
734 :hg:`update NAME` to update to a given bookmark.
734 :hg:`update NAME` to update to a given bookmark.
735
735
736 You can use :hg:`bookmark NAME` to set a bookmark on the working
736 You can use :hg:`bookmark NAME` to set a bookmark on the working
737 directory's parent revision with the given name. If you specify
737 directory's parent revision with the given name. If you specify
738 a revision using -r REV (where REV may be an existing bookmark),
738 a revision using -r REV (where REV may be an existing bookmark),
739 the bookmark is assigned to that revision.
739 the bookmark is assigned to that revision.
740
740
741 Bookmarks can be pushed and pulled between repositories (see :hg:`help
741 Bookmarks can be pushed and pulled between repositories (see :hg:`help
742 push` and :hg:`help pull`). This requires both the local and remote
742 push` and :hg:`help pull`). This requires both the local and remote
743 repositories to support bookmarks. For versions prior to 1.8, this means
743 repositories to support bookmarks. For versions prior to 1.8, this means
744 the bookmarks extension must be enabled.
744 the bookmarks extension must be enabled.
745 '''
745 '''
746 hexfn = ui.debugflag and hex or short
746 hexfn = ui.debugflag and hex or short
747 marks = repo._bookmarks
747 marks = repo._bookmarks
748 cur = repo.changectx('.').node()
748 cur = repo.changectx('.').node()
749
749
750 if delete:
750 if delete:
751 if mark is None:
751 if mark is None:
752 raise util.Abort(_("bookmark name required"))
752 raise util.Abort(_("bookmark name required"))
753 if mark not in marks:
753 if mark not in marks:
754 raise util.Abort(_("bookmark '%s' does not exist") % mark)
754 raise util.Abort(_("bookmark '%s' does not exist") % mark)
755 if mark == repo._bookmarkcurrent:
755 if mark == repo._bookmarkcurrent:
756 bookmarks.setcurrent(repo, None)
756 bookmarks.setcurrent(repo, None)
757 del marks[mark]
757 del marks[mark]
758 bookmarks.write(repo)
758 bookmarks.write(repo)
759 return
759 return
760
760
761 if rename:
761 if rename:
762 if rename not in marks:
762 if rename not in marks:
763 raise util.Abort(_("bookmark '%s' does not exist") % rename)
763 raise util.Abort(_("bookmark '%s' does not exist") % rename)
764 if mark in marks and not force:
764 if mark in marks and not force:
765 raise util.Abort(_("bookmark '%s' already exists "
765 raise util.Abort(_("bookmark '%s' already exists "
766 "(use -f to force)") % mark)
766 "(use -f to force)") % mark)
767 if mark is None:
767 if mark is None:
768 raise util.Abort(_("new bookmark name required"))
768 raise util.Abort(_("new bookmark name required"))
769 marks[mark] = marks[rename]
769 marks[mark] = marks[rename]
770 if repo._bookmarkcurrent == rename and not inactive:
770 if repo._bookmarkcurrent == rename and not inactive:
771 bookmarks.setcurrent(repo, mark)
771 bookmarks.setcurrent(repo, mark)
772 del marks[rename]
772 del marks[rename]
773 bookmarks.write(repo)
773 bookmarks.write(repo)
774 return
774 return
775
775
776 if mark is not None:
776 if mark is not None:
777 if "\n" in mark:
777 if "\n" in mark:
778 raise util.Abort(_("bookmark name cannot contain newlines"))
778 raise util.Abort(_("bookmark name cannot contain newlines"))
779 mark = mark.strip()
779 mark = mark.strip()
780 if not mark:
780 if not mark:
781 raise util.Abort(_("bookmark names cannot consist entirely of "
781 raise util.Abort(_("bookmark names cannot consist entirely of "
782 "whitespace"))
782 "whitespace"))
783 if inactive and mark == repo._bookmarkcurrent:
783 if inactive and mark == repo._bookmarkcurrent:
784 bookmarks.setcurrent(repo, None)
784 bookmarks.setcurrent(repo, None)
785 return
785 return
786 if mark in marks and not force:
786 if mark in marks and not force:
787 raise util.Abort(_("bookmark '%s' already exists "
787 raise util.Abort(_("bookmark '%s' already exists "
788 "(use -f to force)") % mark)
788 "(use -f to force)") % mark)
789 if ((mark in repo.branchtags() or mark == repo.dirstate.branch())
789 if ((mark in repo.branchtags() or mark == repo.dirstate.branch())
790 and not force):
790 and not force):
791 raise util.Abort(
791 raise util.Abort(
792 _("a bookmark cannot have the name of an existing branch"))
792 _("a bookmark cannot have the name of an existing branch"))
793 if rev:
793 if rev:
794 marks[mark] = repo.lookup(rev)
794 marks[mark] = repo.lookup(rev)
795 else:
795 else:
796 marks[mark] = cur
796 marks[mark] = cur
797 if not inactive and cur == marks[mark]:
797 if not inactive and cur == marks[mark]:
798 bookmarks.setcurrent(repo, mark)
798 bookmarks.setcurrent(repo, mark)
799 bookmarks.write(repo)
799 bookmarks.write(repo)
800 return
800 return
801
801
802 if mark is None:
802 if mark is None:
803 if rev:
803 if rev:
804 raise util.Abort(_("bookmark name required"))
804 raise util.Abort(_("bookmark name required"))
805 if len(marks) == 0:
805 if len(marks) == 0:
806 ui.status(_("no bookmarks set\n"))
806 ui.status(_("no bookmarks set\n"))
807 else:
807 else:
808 for bmark, n in sorted(marks.iteritems()):
808 for bmark, n in sorted(marks.iteritems()):
809 current = repo._bookmarkcurrent
809 current = repo._bookmarkcurrent
810 if bmark == current and n == cur:
810 if bmark == current and n == cur:
811 prefix, label = '*', 'bookmarks.current'
811 prefix, label = '*', 'bookmarks.current'
812 else:
812 else:
813 prefix, label = ' ', ''
813 prefix, label = ' ', ''
814
814
815 if ui.quiet:
815 if ui.quiet:
816 ui.write("%s\n" % bmark, label=label)
816 ui.write("%s\n" % bmark, label=label)
817 else:
817 else:
818 ui.write(" %s %-25s %d:%s\n" % (
818 ui.write(" %s %-25s %d:%s\n" % (
819 prefix, bmark, repo.changelog.rev(n), hexfn(n)),
819 prefix, bmark, repo.changelog.rev(n), hexfn(n)),
820 label=label)
820 label=label)
821 return
821 return
822
822
823 @command('branch',
823 @command('branch',
824 [('f', 'force', None,
824 [('f', 'force', None,
825 _('set branch name even if it shadows an existing branch')),
825 _('set branch name even if it shadows an existing branch')),
826 ('C', 'clean', None, _('reset branch name to parent branch name'))],
826 ('C', 'clean', None, _('reset branch name to parent branch name'))],
827 _('[-fC] [NAME]'))
827 _('[-fC] [NAME]'))
828 def branch(ui, repo, label=None, **opts):
828 def branch(ui, repo, label=None, **opts):
829 """set or show the current branch name
829 """set or show the current branch name
830
830
831 .. note::
831 .. note::
832 Branch names are permanent and global. Use :hg:`bookmark` to create a
832 Branch names are permanent and global. Use :hg:`bookmark` to create a
833 light-weight bookmark instead. See :hg:`help glossary` for more
833 light-weight bookmark instead. See :hg:`help glossary` for more
834 information about named branches and bookmarks.
834 information about named branches and bookmarks.
835
835
836 With no argument, show the current branch name. With one argument,
836 With no argument, show the current branch name. With one argument,
837 set the working directory branch name (the branch will not exist
837 set the working directory branch name (the branch will not exist
838 in the repository until the next commit). Standard practice
838 in the repository until the next commit). Standard practice
839 recommends that primary development take place on the 'default'
839 recommends that primary development take place on the 'default'
840 branch.
840 branch.
841
841
842 Unless -f/--force is specified, branch will not let you set a
842 Unless -f/--force is specified, branch will not let you set a
843 branch name that already exists, even if it's inactive.
843 branch name that already exists, even if it's inactive.
844
844
845 Use -C/--clean to reset the working directory branch to that of
845 Use -C/--clean to reset the working directory branch to that of
846 the parent of the working directory, negating a previous branch
846 the parent of the working directory, negating a previous branch
847 change.
847 change.
848
848
849 Use the command :hg:`update` to switch to an existing branch. Use
849 Use the command :hg:`update` to switch to an existing branch. Use
850 :hg:`commit --close-branch` to mark this branch as closed.
850 :hg:`commit --close-branch` to mark this branch as closed.
851
851
852 Returns 0 on success.
852 Returns 0 on success.
853 """
853 """
854
854
855 if opts.get('clean'):
855 if opts.get('clean'):
856 label = repo[None].p1().branch()
856 label = repo[None].p1().branch()
857 repo.dirstate.setbranch(label)
857 repo.dirstate.setbranch(label)
858 ui.status(_('reset working directory to branch %s\n') % label)
858 ui.status(_('reset working directory to branch %s\n') % label)
859 elif label:
859 elif label:
860 if not opts.get('force') and label in repo.branchtags():
860 if not opts.get('force') and label in repo.branchtags():
861 if label not in [p.branch() for p in repo.parents()]:
861 if label not in [p.branch() for p in repo.parents()]:
862 raise util.Abort(_('a branch of the same name already exists'),
862 raise util.Abort(_('a branch of the same name already exists'),
863 # i18n: "it" refers to an existing branch
863 # i18n: "it" refers to an existing branch
864 hint=_("use 'hg update' to switch to it"))
864 hint=_("use 'hg update' to switch to it"))
865 repo.dirstate.setbranch(label)
865 repo.dirstate.setbranch(label)
866 ui.status(_('marked working directory as branch %s\n') % label)
866 ui.status(_('marked working directory as branch %s\n') % label)
867 ui.status(_('(branches are permanent and global, '
867 ui.status(_('(branches are permanent and global, '
868 'did you want a bookmark?)\n'))
868 'did you want a bookmark?)\n'))
869 else:
869 else:
870 ui.write("%s\n" % repo.dirstate.branch())
870 ui.write("%s\n" % repo.dirstate.branch())
871
871
872 @command('branches',
872 @command('branches',
873 [('a', 'active', False, _('show only branches that have unmerged heads')),
873 [('a', 'active', False, _('show only branches that have unmerged heads')),
874 ('c', 'closed', False, _('show normal and closed branches'))],
874 ('c', 'closed', False, _('show normal and closed branches'))],
875 _('[-ac]'))
875 _('[-ac]'))
876 def branches(ui, repo, active=False, closed=False):
876 def branches(ui, repo, active=False, closed=False):
877 """list repository named branches
877 """list repository named branches
878
878
879 List the repository's named branches, indicating which ones are
879 List the repository's named branches, indicating which ones are
880 inactive. If -c/--closed is specified, also list branches which have
880 inactive. If -c/--closed is specified, also list branches which have
881 been marked closed (see :hg:`commit --close-branch`).
881 been marked closed (see :hg:`commit --close-branch`).
882
882
883 If -a/--active is specified, only show active branches. A branch
883 If -a/--active is specified, only show active branches. A branch
884 is considered active if it contains repository heads.
884 is considered active if it contains repository heads.
885
885
886 Use the command :hg:`update` to switch to an existing branch.
886 Use the command :hg:`update` to switch to an existing branch.
887
887
888 Returns 0.
888 Returns 0.
889 """
889 """
890
890
891 hexfunc = ui.debugflag and hex or short
891 hexfunc = ui.debugflag and hex or short
892 activebranches = [repo[n].branch() for n in repo.heads()]
892 activebranches = [repo[n].branch() for n in repo.heads()]
893 def testactive(tag, node):
893 def testactive(tag, node):
894 realhead = tag in activebranches
894 realhead = tag in activebranches
895 open = node in repo.branchheads(tag, closed=False)
895 open = node in repo.branchheads(tag, closed=False)
896 return realhead and open
896 return realhead and open
897 branches = sorted([(testactive(tag, node), repo.changelog.rev(node), tag)
897 branches = sorted([(testactive(tag, node), repo.changelog.rev(node), tag)
898 for tag, node in repo.branchtags().items()],
898 for tag, node in repo.branchtags().items()],
899 reverse=True)
899 reverse=True)
900
900
901 for isactive, node, tag in branches:
901 for isactive, node, tag in branches:
902 if (not active) or isactive:
902 if (not active) or isactive:
903 if ui.quiet:
903 if ui.quiet:
904 ui.write("%s\n" % tag)
904 ui.write("%s\n" % tag)
905 else:
905 else:
906 hn = repo.lookup(node)
906 hn = repo.lookup(node)
907 if isactive:
907 if isactive:
908 label = 'branches.active'
908 label = 'branches.active'
909 notice = ''
909 notice = ''
910 elif hn not in repo.branchheads(tag, closed=False):
910 elif hn not in repo.branchheads(tag, closed=False):
911 if not closed:
911 if not closed:
912 continue
912 continue
913 label = 'branches.closed'
913 label = 'branches.closed'
914 notice = _(' (closed)')
914 notice = _(' (closed)')
915 else:
915 else:
916 label = 'branches.inactive'
916 label = 'branches.inactive'
917 notice = _(' (inactive)')
917 notice = _(' (inactive)')
918 if tag == repo.dirstate.branch():
918 if tag == repo.dirstate.branch():
919 label = 'branches.current'
919 label = 'branches.current'
920 rev = str(node).rjust(31 - encoding.colwidth(tag))
920 rev = str(node).rjust(31 - encoding.colwidth(tag))
921 rev = ui.label('%s:%s' % (rev, hexfunc(hn)), 'log.changeset')
921 rev = ui.label('%s:%s' % (rev, hexfunc(hn)), 'log.changeset')
922 tag = ui.label(tag, label)
922 tag = ui.label(tag, label)
923 ui.write("%s %s%s\n" % (tag, rev, notice))
923 ui.write("%s %s%s\n" % (tag, rev, notice))
924
924
925 @command('bundle',
925 @command('bundle',
926 [('f', 'force', None, _('run even when the destination is unrelated')),
926 [('f', 'force', None, _('run even when the destination is unrelated')),
927 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
927 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
928 _('REV')),
928 _('REV')),
929 ('b', 'branch', [], _('a specific branch you would like to bundle'),
929 ('b', 'branch', [], _('a specific branch you would like to bundle'),
930 _('BRANCH')),
930 _('BRANCH')),
931 ('', 'base', [],
931 ('', 'base', [],
932 _('a base changeset assumed to be available at the destination'),
932 _('a base changeset assumed to be available at the destination'),
933 _('REV')),
933 _('REV')),
934 ('a', 'all', None, _('bundle all changesets in the repository')),
934 ('a', 'all', None, _('bundle all changesets in the repository')),
935 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
935 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
936 ] + remoteopts,
936 ] + remoteopts,
937 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
937 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
938 def bundle(ui, repo, fname, dest=None, **opts):
938 def bundle(ui, repo, fname, dest=None, **opts):
939 """create a changegroup file
939 """create a changegroup file
940
940
941 Generate a compressed changegroup file collecting changesets not
941 Generate a compressed changegroup file collecting changesets not
942 known to be in another repository.
942 known to be in another repository.
943
943
944 If you omit the destination repository, then hg assumes the
944 If you omit the destination repository, then hg assumes the
945 destination will have all the nodes you specify with --base
945 destination will have all the nodes you specify with --base
946 parameters. To create a bundle containing all changesets, use
946 parameters. To create a bundle containing all changesets, use
947 -a/--all (or --base null).
947 -a/--all (or --base null).
948
948
949 You can change compression method with the -t/--type option.
949 You can change compression method with the -t/--type option.
950 The available compression methods are: none, bzip2, and
950 The available compression methods are: none, bzip2, and
951 gzip (by default, bundles are compressed using bzip2).
951 gzip (by default, bundles are compressed using bzip2).
952
952
953 The bundle file can then be transferred using conventional means
953 The bundle file can then be transferred using conventional means
954 and applied to another repository with the unbundle or pull
954 and applied to another repository with the unbundle or pull
955 command. This is useful when direct push and pull are not
955 command. This is useful when direct push and pull are not
956 available or when exporting an entire repository is undesirable.
956 available or when exporting an entire repository is undesirable.
957
957
958 Applying bundles preserves all changeset contents including
958 Applying bundles preserves all changeset contents including
959 permissions, copy/rename information, and revision history.
959 permissions, copy/rename information, and revision history.
960
960
961 Returns 0 on success, 1 if no changes found.
961 Returns 0 on success, 1 if no changes found.
962 """
962 """
963 revs = None
963 revs = None
964 if 'rev' in opts:
964 if 'rev' in opts:
965 revs = scmutil.revrange(repo, opts['rev'])
965 revs = scmutil.revrange(repo, opts['rev'])
966
966
967 if opts.get('all'):
967 if opts.get('all'):
968 base = ['null']
968 base = ['null']
969 else:
969 else:
970 base = scmutil.revrange(repo, opts.get('base'))
970 base = scmutil.revrange(repo, opts.get('base'))
971 if base:
971 if base:
972 if dest:
972 if dest:
973 raise util.Abort(_("--base is incompatible with specifying "
973 raise util.Abort(_("--base is incompatible with specifying "
974 "a destination"))
974 "a destination"))
975 common = [repo.lookup(rev) for rev in base]
975 common = [repo.lookup(rev) for rev in base]
976 heads = revs and map(repo.lookup, revs) or revs
976 heads = revs and map(repo.lookup, revs) or revs
977 else:
977 else:
978 dest = ui.expandpath(dest or 'default-push', dest or 'default')
978 dest = ui.expandpath(dest or 'default-push', dest or 'default')
979 dest, branches = hg.parseurl(dest, opts.get('branch'))
979 dest, branches = hg.parseurl(dest, opts.get('branch'))
980 other = hg.peer(repo, opts, dest)
980 other = hg.peer(repo, opts, dest)
981 revs, checkout = hg.addbranchrevs(repo, other, branches, revs)
981 revs, checkout = hg.addbranchrevs(repo, other, branches, revs)
982 heads = revs and map(repo.lookup, revs) or revs
982 heads = revs and map(repo.lookup, revs) or revs
983 common, outheads = discovery.findcommonoutgoing(repo, other,
983 common, outheads = discovery.findcommonoutgoing(repo, other,
984 onlyheads=heads,
984 onlyheads=heads,
985 force=opts.get('force'))
985 force=opts.get('force'))
986
986
987 cg = repo.getbundle('bundle', common=common, heads=heads)
987 cg = repo.getbundle('bundle', common=common, heads=heads)
988 if not cg:
988 if not cg:
989 ui.status(_("no changes found\n"))
989 ui.status(_("no changes found\n"))
990 return 1
990 return 1
991
991
992 bundletype = opts.get('type', 'bzip2').lower()
992 bundletype = opts.get('type', 'bzip2').lower()
993 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
993 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
994 bundletype = btypes.get(bundletype)
994 bundletype = btypes.get(bundletype)
995 if bundletype not in changegroup.bundletypes:
995 if bundletype not in changegroup.bundletypes:
996 raise util.Abort(_('unknown bundle type specified with --type'))
996 raise util.Abort(_('unknown bundle type specified with --type'))
997
997
998 changegroup.writebundle(cg, fname, bundletype)
998 changegroup.writebundle(cg, fname, bundletype)
999
999
1000 @command('cat',
1000 @command('cat',
1001 [('o', 'output', '',
1001 [('o', 'output', '',
1002 _('print output to file with formatted name'), _('FORMAT')),
1002 _('print output to file with formatted name'), _('FORMAT')),
1003 ('r', 'rev', '', _('print the given revision'), _('REV')),
1003 ('r', 'rev', '', _('print the given revision'), _('REV')),
1004 ('', 'decode', None, _('apply any matching decode filter')),
1004 ('', 'decode', None, _('apply any matching decode filter')),
1005 ] + walkopts,
1005 ] + walkopts,
1006 _('[OPTION]... FILE...'))
1006 _('[OPTION]... FILE...'))
1007 def cat(ui, repo, file1, *pats, **opts):
1007 def cat(ui, repo, file1, *pats, **opts):
1008 """output the current or given revision of files
1008 """output the current or given revision of files
1009
1009
1010 Print the specified files as they were at the given revision. If
1010 Print the specified files as they were at the given revision. If
1011 no revision is given, the parent of the working directory is used,
1011 no revision is given, the parent of the working directory is used,
1012 or tip if no revision is checked out.
1012 or tip if no revision is checked out.
1013
1013
1014 Output may be to a file, in which case the name of the file is
1014 Output may be to a file, in which case the name of the file is
1015 given using a format string. The formatting rules are the same as
1015 given using a format string. The formatting rules are the same as
1016 for the export command, with the following additions:
1016 for the export command, with the following additions:
1017
1017
1018 :``%s``: basename of file being printed
1018 :``%s``: basename of file being printed
1019 :``%d``: dirname of file being printed, or '.' if in repository root
1019 :``%d``: dirname of file being printed, or '.' if in repository root
1020 :``%p``: root-relative path name of file being printed
1020 :``%p``: root-relative path name of file being printed
1021
1021
1022 Returns 0 on success.
1022 Returns 0 on success.
1023 """
1023 """
1024 ctx = scmutil.revsingle(repo, opts.get('rev'))
1024 ctx = scmutil.revsingle(repo, opts.get('rev'))
1025 err = 1
1025 err = 1
1026 m = scmutil.match(ctx, (file1,) + pats, opts)
1026 m = scmutil.match(ctx, (file1,) + pats, opts)
1027 for abs in ctx.walk(m):
1027 for abs in ctx.walk(m):
1028 fp = cmdutil.makefileobj(repo, opts.get('output'), ctx.node(),
1028 fp = cmdutil.makefileobj(repo, opts.get('output'), ctx.node(),
1029 pathname=abs)
1029 pathname=abs)
1030 data = ctx[abs].data()
1030 data = ctx[abs].data()
1031 if opts.get('decode'):
1031 if opts.get('decode'):
1032 data = repo.wwritedata(abs, data)
1032 data = repo.wwritedata(abs, data)
1033 fp.write(data)
1033 fp.write(data)
1034 fp.close()
1034 fp.close()
1035 err = 0
1035 err = 0
1036 return err
1036 return err
1037
1037
1038 @command('^clone',
1038 @command('^clone',
1039 [('U', 'noupdate', None,
1039 [('U', 'noupdate', None,
1040 _('the clone will include an empty working copy (only a repository)')),
1040 _('the clone will include an empty working copy (only a repository)')),
1041 ('u', 'updaterev', '', _('revision, tag or branch to check out'), _('REV')),
1041 ('u', 'updaterev', '', _('revision, tag or branch to check out'), _('REV')),
1042 ('r', 'rev', [], _('include the specified changeset'), _('REV')),
1042 ('r', 'rev', [], _('include the specified changeset'), _('REV')),
1043 ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')),
1043 ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')),
1044 ('', 'pull', None, _('use pull protocol to copy metadata')),
1044 ('', 'pull', None, _('use pull protocol to copy metadata')),
1045 ('', 'uncompressed', None, _('use uncompressed transfer (fast over LAN)')),
1045 ('', 'uncompressed', None, _('use uncompressed transfer (fast over LAN)')),
1046 ] + remoteopts,
1046 ] + remoteopts,
1047 _('[OPTION]... SOURCE [DEST]'))
1047 _('[OPTION]... SOURCE [DEST]'))
1048 def clone(ui, source, dest=None, **opts):
1048 def clone(ui, source, dest=None, **opts):
1049 """make a copy of an existing repository
1049 """make a copy of an existing repository
1050
1050
1051 Create a copy of an existing repository in a new directory.
1051 Create a copy of an existing repository in a new directory.
1052
1052
1053 If no destination directory name is specified, it defaults to the
1053 If no destination directory name is specified, it defaults to the
1054 basename of the source.
1054 basename of the source.
1055
1055
1056 The location of the source is added to the new repository's
1056 The location of the source is added to the new repository's
1057 ``.hg/hgrc`` file, as the default to be used for future pulls.
1057 ``.hg/hgrc`` file, as the default to be used for future pulls.
1058
1058
1059 Only local paths and ``ssh://`` URLs are supported as
1059 Only local paths and ``ssh://`` URLs are supported as
1060 destinations. For ``ssh://`` destinations, no working directory or
1060 destinations. For ``ssh://`` destinations, no working directory or
1061 ``.hg/hgrc`` will be created on the remote side.
1061 ``.hg/hgrc`` will be created on the remote side.
1062
1062
1063 To pull only a subset of changesets, specify one or more revisions
1063 To pull only a subset of changesets, specify one or more revisions
1064 identifiers with -r/--rev or branches with -b/--branch. The
1064 identifiers with -r/--rev or branches with -b/--branch. The
1065 resulting clone will contain only the specified changesets and
1065 resulting clone will contain only the specified changesets and
1066 their ancestors. These options (or 'clone src#rev dest') imply
1066 their ancestors. These options (or 'clone src#rev dest') imply
1067 --pull, even for local source repositories. Note that specifying a
1067 --pull, even for local source repositories. Note that specifying a
1068 tag will include the tagged changeset but not the changeset
1068 tag will include the tagged changeset but not the changeset
1069 containing the tag.
1069 containing the tag.
1070
1070
1071 To check out a particular version, use -u/--update, or
1071 To check out a particular version, use -u/--update, or
1072 -U/--noupdate to create a clone with no working directory.
1072 -U/--noupdate to create a clone with no working directory.
1073
1073
1074 .. container:: verbose
1074 .. container:: verbose
1075
1075
1076 For efficiency, hardlinks are used for cloning whenever the
1076 For efficiency, hardlinks are used for cloning whenever the
1077 source and destination are on the same filesystem (note this
1077 source and destination are on the same filesystem (note this
1078 applies only to the repository data, not to the working
1078 applies only to the repository data, not to the working
1079 directory). Some filesystems, such as AFS, implement hardlinking
1079 directory). Some filesystems, such as AFS, implement hardlinking
1080 incorrectly, but do not report errors. In these cases, use the
1080 incorrectly, but do not report errors. In these cases, use the
1081 --pull option to avoid hardlinking.
1081 --pull option to avoid hardlinking.
1082
1082
1083 In some cases, you can clone repositories and the working
1083 In some cases, you can clone repositories and the working
1084 directory using full hardlinks with ::
1084 directory using full hardlinks with ::
1085
1085
1086 $ cp -al REPO REPOCLONE
1086 $ cp -al REPO REPOCLONE
1087
1087
1088 This is the fastest way to clone, but it is not always safe. The
1088 This is the fastest way to clone, but it is not always safe. The
1089 operation is not atomic (making sure REPO is not modified during
1089 operation is not atomic (making sure REPO is not modified during
1090 the operation is up to you) and you have to make sure your
1090 the operation is up to you) and you have to make sure your
1091 editor breaks hardlinks (Emacs and most Linux Kernel tools do
1091 editor breaks hardlinks (Emacs and most Linux Kernel tools do
1092 so). Also, this is not compatible with certain extensions that
1092 so). Also, this is not compatible with certain extensions that
1093 place their metadata under the .hg directory, such as mq.
1093 place their metadata under the .hg directory, such as mq.
1094
1094
1095 Mercurial will update the working directory to the first applicable
1095 Mercurial will update the working directory to the first applicable
1096 revision from this list:
1096 revision from this list:
1097
1097
1098 a) null if -U or the source repository has no changesets
1098 a) null if -U or the source repository has no changesets
1099 b) if -u . and the source repository is local, the first parent of
1099 b) if -u . and the source repository is local, the first parent of
1100 the source repository's working directory
1100 the source repository's working directory
1101 c) the changeset specified with -u (if a branch name, this means the
1101 c) the changeset specified with -u (if a branch name, this means the
1102 latest head of that branch)
1102 latest head of that branch)
1103 d) the changeset specified with -r
1103 d) the changeset specified with -r
1104 e) the tipmost head specified with -b
1104 e) the tipmost head specified with -b
1105 f) the tipmost head specified with the url#branch source syntax
1105 f) the tipmost head specified with the url#branch source syntax
1106 g) the tipmost head of the default branch
1106 g) the tipmost head of the default branch
1107 h) tip
1107 h) tip
1108
1108
1109 Examples:
1109 Examples:
1110
1110
1111 - clone a remote repository to a new directory named hg/::
1111 - clone a remote repository to a new directory named hg/::
1112
1112
1113 hg clone http://selenic.com/hg
1113 hg clone http://selenic.com/hg
1114
1114
1115 - create a lightweight local clone::
1115 - create a lightweight local clone::
1116
1116
1117 hg clone project/ project-feature/
1117 hg clone project/ project-feature/
1118
1118
1119 - clone from an absolute path on an ssh server (note double-slash)::
1119 - clone from an absolute path on an ssh server (note double-slash)::
1120
1120
1121 hg clone ssh://user@server//home/projects/alpha/
1121 hg clone ssh://user@server//home/projects/alpha/
1122
1122
1123 - do a high-speed clone over a LAN while checking out a
1123 - do a high-speed clone over a LAN while checking out a
1124 specified version::
1124 specified version::
1125
1125
1126 hg clone --uncompressed http://server/repo -u 1.5
1126 hg clone --uncompressed http://server/repo -u 1.5
1127
1127
1128 - create a repository without changesets after a particular revision::
1128 - create a repository without changesets after a particular revision::
1129
1129
1130 hg clone -r 04e544 experimental/ good/
1130 hg clone -r 04e544 experimental/ good/
1131
1131
1132 - clone (and track) a particular named branch::
1132 - clone (and track) a particular named branch::
1133
1133
1134 hg clone http://selenic.com/hg#stable
1134 hg clone http://selenic.com/hg#stable
1135
1135
1136 See :hg:`help urls` for details on specifying URLs.
1136 See :hg:`help urls` for details on specifying URLs.
1137
1137
1138 Returns 0 on success.
1138 Returns 0 on success.
1139 """
1139 """
1140 if opts.get('noupdate') and opts.get('updaterev'):
1140 if opts.get('noupdate') and opts.get('updaterev'):
1141 raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
1141 raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
1142
1142
1143 r = hg.clone(ui, opts, source, dest,
1143 r = hg.clone(ui, opts, source, dest,
1144 pull=opts.get('pull'),
1144 pull=opts.get('pull'),
1145 stream=opts.get('uncompressed'),
1145 stream=opts.get('uncompressed'),
1146 rev=opts.get('rev'),
1146 rev=opts.get('rev'),
1147 update=opts.get('updaterev') or not opts.get('noupdate'),
1147 update=opts.get('updaterev') or not opts.get('noupdate'),
1148 branch=opts.get('branch'))
1148 branch=opts.get('branch'))
1149
1149
1150 return r is None
1150 return r is None
1151
1151
1152 @command('^commit|ci',
1152 @command('^commit|ci',
1153 [('A', 'addremove', None,
1153 [('A', 'addremove', None,
1154 _('mark new/missing files as added/removed before committing')),
1154 _('mark new/missing files as added/removed before committing')),
1155 ('', 'close-branch', None,
1155 ('', 'close-branch', None,
1156 _('mark a branch as closed, hiding it from the branch list')),
1156 _('mark a branch as closed, hiding it from the branch list')),
1157 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1157 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1158 _('[OPTION]... [FILE]...'))
1158 _('[OPTION]... [FILE]...'))
1159 def commit(ui, repo, *pats, **opts):
1159 def commit(ui, repo, *pats, **opts):
1160 """commit the specified files or all outstanding changes
1160 """commit the specified files or all outstanding changes
1161
1161
1162 Commit changes to the given files into the repository. Unlike a
1162 Commit changes to the given files into the repository. Unlike a
1163 centralized SCM, this operation is a local operation. See
1163 centralized SCM, this operation is a local operation. See
1164 :hg:`push` for a way to actively distribute your changes.
1164 :hg:`push` for a way to actively distribute your changes.
1165
1165
1166 If a list of files is omitted, all changes reported by :hg:`status`
1166 If a list of files is omitted, all changes reported by :hg:`status`
1167 will be committed.
1167 will be committed.
1168
1168
1169 If you are committing the result of a merge, do not provide any
1169 If you are committing the result of a merge, do not provide any
1170 filenames or -I/-X filters.
1170 filenames or -I/-X filters.
1171
1171
1172 If no commit message is specified, Mercurial starts your
1172 If no commit message is specified, Mercurial starts your
1173 configured editor where you can enter a message. In case your
1173 configured editor where you can enter a message. In case your
1174 commit fails, you will find a backup of your message in
1174 commit fails, you will find a backup of your message in
1175 ``.hg/last-message.txt``.
1175 ``.hg/last-message.txt``.
1176
1176
1177 See :hg:`help dates` for a list of formats valid for -d/--date.
1177 See :hg:`help dates` for a list of formats valid for -d/--date.
1178
1178
1179 Returns 0 on success, 1 if nothing changed.
1179 Returns 0 on success, 1 if nothing changed.
1180 """
1180 """
1181 if opts.get('subrepos'):
1181 if opts.get('subrepos'):
1182 # Let --subrepos on the command line overide config setting.
1182 # Let --subrepos on the command line overide config setting.
1183 ui.setconfig('ui', 'commitsubrepos', True)
1183 ui.setconfig('ui', 'commitsubrepos', True)
1184
1184
1185 extra = {}
1185 extra = {}
1186 if opts.get('close_branch'):
1186 if opts.get('close_branch'):
1187 if repo['.'].node() not in repo.branchheads():
1187 if repo['.'].node() not in repo.branchheads():
1188 # The topo heads set is included in the branch heads set of the
1188 # The topo heads set is included in the branch heads set of the
1189 # current branch, so it's sufficient to test branchheads
1189 # current branch, so it's sufficient to test branchheads
1190 raise util.Abort(_('can only close branch heads'))
1190 raise util.Abort(_('can only close branch heads'))
1191 extra['close'] = 1
1191 extra['close'] = 1
1192 e = cmdutil.commiteditor
1192 e = cmdutil.commiteditor
1193 if opts.get('force_editor'):
1193 if opts.get('force_editor'):
1194 e = cmdutil.commitforceeditor
1194 e = cmdutil.commitforceeditor
1195
1195
1196 def commitfunc(ui, repo, message, match, opts):
1196 def commitfunc(ui, repo, message, match, opts):
1197 return repo.commit(message, opts.get('user'), opts.get('date'), match,
1197 return repo.commit(message, opts.get('user'), opts.get('date'), match,
1198 editor=e, extra=extra)
1198 editor=e, extra=extra)
1199
1199
1200 branch = repo[None].branch()
1200 branch = repo[None].branch()
1201 bheads = repo.branchheads(branch)
1201 bheads = repo.branchheads(branch)
1202
1202
1203 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1203 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1204 if not node:
1204 if not node:
1205 stat = repo.status(match=scmutil.match(repo[None], pats, opts))
1205 stat = repo.status(match=scmutil.match(repo[None], pats, opts))
1206 if stat[3]:
1206 if stat[3]:
1207 ui.status(_("nothing changed (%d missing files, see 'hg status')\n")
1207 ui.status(_("nothing changed (%d missing files, see 'hg status')\n")
1208 % len(stat[3]))
1208 % len(stat[3]))
1209 else:
1209 else:
1210 ui.status(_("nothing changed\n"))
1210 ui.status(_("nothing changed\n"))
1211 return 1
1211 return 1
1212
1212
1213 ctx = repo[node]
1213 ctx = repo[node]
1214 parents = ctx.parents()
1214 parents = ctx.parents()
1215
1215
1216 if (bheads and node not in bheads and not
1216 if (bheads and node not in bheads and not
1217 [x for x in parents if x.node() in bheads and x.branch() == branch]):
1217 [x for x in parents if x.node() in bheads and x.branch() == branch]):
1218 ui.status(_('created new head\n'))
1218 ui.status(_('created new head\n'))
1219 # The message is not printed for initial roots. For the other
1219 # The message is not printed for initial roots. For the other
1220 # changesets, it is printed in the following situations:
1220 # changesets, it is printed in the following situations:
1221 #
1221 #
1222 # Par column: for the 2 parents with ...
1222 # Par column: for the 2 parents with ...
1223 # N: null or no parent
1223 # N: null or no parent
1224 # B: parent is on another named branch
1224 # B: parent is on another named branch
1225 # C: parent is a regular non head changeset
1225 # C: parent is a regular non head changeset
1226 # H: parent was a branch head of the current branch
1226 # H: parent was a branch head of the current branch
1227 # Msg column: whether we print "created new head" message
1227 # Msg column: whether we print "created new head" message
1228 # In the following, it is assumed that there already exists some
1228 # In the following, it is assumed that there already exists some
1229 # initial branch heads of the current branch, otherwise nothing is
1229 # initial branch heads of the current branch, otherwise nothing is
1230 # printed anyway.
1230 # printed anyway.
1231 #
1231 #
1232 # Par Msg Comment
1232 # Par Msg Comment
1233 # NN y additional topo root
1233 # NN y additional topo root
1234 #
1234 #
1235 # BN y additional branch root
1235 # BN y additional branch root
1236 # CN y additional topo head
1236 # CN y additional topo head
1237 # HN n usual case
1237 # HN n usual case
1238 #
1238 #
1239 # BB y weird additional branch root
1239 # BB y weird additional branch root
1240 # CB y branch merge
1240 # CB y branch merge
1241 # HB n merge with named branch
1241 # HB n merge with named branch
1242 #
1242 #
1243 # CC y additional head from merge
1243 # CC y additional head from merge
1244 # CH n merge with a head
1244 # CH n merge with a head
1245 #
1245 #
1246 # HH n head merge: head count decreases
1246 # HH n head merge: head count decreases
1247
1247
1248 if not opts.get('close_branch'):
1248 if not opts.get('close_branch'):
1249 for r in parents:
1249 for r in parents:
1250 if r.extra().get('close') and r.branch() == branch:
1250 if r.extra().get('close') and r.branch() == branch:
1251 ui.status(_('reopening closed branch head %d\n') % r)
1251 ui.status(_('reopening closed branch head %d\n') % r)
1252
1252
1253 if ui.debugflag:
1253 if ui.debugflag:
1254 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
1254 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
1255 elif ui.verbose:
1255 elif ui.verbose:
1256 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
1256 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
1257
1257
1258 @command('copy|cp',
1258 @command('copy|cp',
1259 [('A', 'after', None, _('record a copy that has already occurred')),
1259 [('A', 'after', None, _('record a copy that has already occurred')),
1260 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1260 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1261 ] + walkopts + dryrunopts,
1261 ] + walkopts + dryrunopts,
1262 _('[OPTION]... [SOURCE]... DEST'))
1262 _('[OPTION]... [SOURCE]... DEST'))
1263 def copy(ui, repo, *pats, **opts):
1263 def copy(ui, repo, *pats, **opts):
1264 """mark files as copied for the next commit
1264 """mark files as copied for the next commit
1265
1265
1266 Mark dest as having copies of source files. If dest is a
1266 Mark dest as having copies of source files. If dest is a
1267 directory, copies are put in that directory. If dest is a file,
1267 directory, copies are put in that directory. If dest is a file,
1268 the source must be a single file.
1268 the source must be a single file.
1269
1269
1270 By default, this command copies the contents of files as they
1270 By default, this command copies the contents of files as they
1271 exist in the working directory. If invoked with -A/--after, the
1271 exist in the working directory. If invoked with -A/--after, the
1272 operation is recorded, but no copying is performed.
1272 operation is recorded, but no copying is performed.
1273
1273
1274 This command takes effect with the next commit. To undo a copy
1274 This command takes effect with the next commit. To undo a copy
1275 before that, see :hg:`revert`.
1275 before that, see :hg:`revert`.
1276
1276
1277 Returns 0 on success, 1 if errors are encountered.
1277 Returns 0 on success, 1 if errors are encountered.
1278 """
1278 """
1279 wlock = repo.wlock(False)
1279 wlock = repo.wlock(False)
1280 try:
1280 try:
1281 return cmdutil.copy(ui, repo, pats, opts)
1281 return cmdutil.copy(ui, repo, pats, opts)
1282 finally:
1282 finally:
1283 wlock.release()
1283 wlock.release()
1284
1284
1285 @command('debugancestor', [], _('[INDEX] REV1 REV2'))
1285 @command('debugancestor', [], _('[INDEX] REV1 REV2'))
1286 def debugancestor(ui, repo, *args):
1286 def debugancestor(ui, repo, *args):
1287 """find the ancestor revision of two revisions in a given index"""
1287 """find the ancestor revision of two revisions in a given index"""
1288 if len(args) == 3:
1288 if len(args) == 3:
1289 index, rev1, rev2 = args
1289 index, rev1, rev2 = args
1290 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), index)
1290 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), index)
1291 lookup = r.lookup
1291 lookup = r.lookup
1292 elif len(args) == 2:
1292 elif len(args) == 2:
1293 if not repo:
1293 if not repo:
1294 raise util.Abort(_("there is no Mercurial repository here "
1294 raise util.Abort(_("there is no Mercurial repository here "
1295 "(.hg not found)"))
1295 "(.hg not found)"))
1296 rev1, rev2 = args
1296 rev1, rev2 = args
1297 r = repo.changelog
1297 r = repo.changelog
1298 lookup = repo.lookup
1298 lookup = repo.lookup
1299 else:
1299 else:
1300 raise util.Abort(_('either two or three arguments required'))
1300 raise util.Abort(_('either two or three arguments required'))
1301 a = r.ancestor(lookup(rev1), lookup(rev2))
1301 a = r.ancestor(lookup(rev1), lookup(rev2))
1302 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
1302 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
1303
1303
1304 @command('debugbuilddag',
1304 @command('debugbuilddag',
1305 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
1305 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
1306 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
1306 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
1307 ('n', 'new-file', None, _('add new file at each rev'))],
1307 ('n', 'new-file', None, _('add new file at each rev'))],
1308 _('[OPTION]... [TEXT]'))
1308 _('[OPTION]... [TEXT]'))
1309 def debugbuilddag(ui, repo, text=None,
1309 def debugbuilddag(ui, repo, text=None,
1310 mergeable_file=False,
1310 mergeable_file=False,
1311 overwritten_file=False,
1311 overwritten_file=False,
1312 new_file=False):
1312 new_file=False):
1313 """builds a repo with a given DAG from scratch in the current empty repo
1313 """builds a repo with a given DAG from scratch in the current empty repo
1314
1314
1315 The description of the DAG is read from stdin if not given on the
1315 The description of the DAG is read from stdin if not given on the
1316 command line.
1316 command line.
1317
1317
1318 Elements:
1318 Elements:
1319
1319
1320 - "+n" is a linear run of n nodes based on the current default parent
1320 - "+n" is a linear run of n nodes based on the current default parent
1321 - "." is a single node based on the current default parent
1321 - "." is a single node based on the current default parent
1322 - "$" resets the default parent to null (implied at the start);
1322 - "$" resets the default parent to null (implied at the start);
1323 otherwise the default parent is always the last node created
1323 otherwise the default parent is always the last node created
1324 - "<p" sets the default parent to the backref p
1324 - "<p" sets the default parent to the backref p
1325 - "*p" is a fork at parent p, which is a backref
1325 - "*p" is a fork at parent p, which is a backref
1326 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
1326 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
1327 - "/p2" is a merge of the preceding node and p2
1327 - "/p2" is a merge of the preceding node and p2
1328 - ":tag" defines a local tag for the preceding node
1328 - ":tag" defines a local tag for the preceding node
1329 - "@branch" sets the named branch for subsequent nodes
1329 - "@branch" sets the named branch for subsequent nodes
1330 - "#...\\n" is a comment up to the end of the line
1330 - "#...\\n" is a comment up to the end of the line
1331
1331
1332 Whitespace between the above elements is ignored.
1332 Whitespace between the above elements is ignored.
1333
1333
1334 A backref is either
1334 A backref is either
1335
1335
1336 - a number n, which references the node curr-n, where curr is the current
1336 - a number n, which references the node curr-n, where curr is the current
1337 node, or
1337 node, or
1338 - the name of a local tag you placed earlier using ":tag", or
1338 - the name of a local tag you placed earlier using ":tag", or
1339 - empty to denote the default parent.
1339 - empty to denote the default parent.
1340
1340
1341 All string valued-elements are either strictly alphanumeric, or must
1341 All string valued-elements are either strictly alphanumeric, or must
1342 be enclosed in double quotes ("..."), with "\\" as escape character.
1342 be enclosed in double quotes ("..."), with "\\" as escape character.
1343 """
1343 """
1344
1344
1345 if text is None:
1345 if text is None:
1346 ui.status(_("reading DAG from stdin\n"))
1346 ui.status(_("reading DAG from stdin\n"))
1347 text = ui.fin.read()
1347 text = ui.fin.read()
1348
1348
1349 cl = repo.changelog
1349 cl = repo.changelog
1350 if len(cl) > 0:
1350 if len(cl) > 0:
1351 raise util.Abort(_('repository is not empty'))
1351 raise util.Abort(_('repository is not empty'))
1352
1352
1353 # determine number of revs in DAG
1353 # determine number of revs in DAG
1354 total = 0
1354 total = 0
1355 for type, data in dagparser.parsedag(text):
1355 for type, data in dagparser.parsedag(text):
1356 if type == 'n':
1356 if type == 'n':
1357 total += 1
1357 total += 1
1358
1358
1359 if mergeable_file:
1359 if mergeable_file:
1360 linesperrev = 2
1360 linesperrev = 2
1361 # make a file with k lines per rev
1361 # make a file with k lines per rev
1362 initialmergedlines = [str(i) for i in xrange(0, total * linesperrev)]
1362 initialmergedlines = [str(i) for i in xrange(0, total * linesperrev)]
1363 initialmergedlines.append("")
1363 initialmergedlines.append("")
1364
1364
1365 tags = []
1365 tags = []
1366
1366
1367 tr = repo.transaction("builddag")
1367 tr = repo.transaction("builddag")
1368 try:
1368 try:
1369
1369
1370 at = -1
1370 at = -1
1371 atbranch = 'default'
1371 atbranch = 'default'
1372 nodeids = []
1372 nodeids = []
1373 ui.progress(_('building'), 0, unit=_('revisions'), total=total)
1373 ui.progress(_('building'), 0, unit=_('revisions'), total=total)
1374 for type, data in dagparser.parsedag(text):
1374 for type, data in dagparser.parsedag(text):
1375 if type == 'n':
1375 if type == 'n':
1376 ui.note('node %s\n' % str(data))
1376 ui.note('node %s\n' % str(data))
1377 id, ps = data
1377 id, ps = data
1378
1378
1379 files = []
1379 files = []
1380 fctxs = {}
1380 fctxs = {}
1381
1381
1382 p2 = None
1382 p2 = None
1383 if mergeable_file:
1383 if mergeable_file:
1384 fn = "mf"
1384 fn = "mf"
1385 p1 = repo[ps[0]]
1385 p1 = repo[ps[0]]
1386 if len(ps) > 1:
1386 if len(ps) > 1:
1387 p2 = repo[ps[1]]
1387 p2 = repo[ps[1]]
1388 pa = p1.ancestor(p2)
1388 pa = p1.ancestor(p2)
1389 base, local, other = [x[fn].data() for x in pa, p1, p2]
1389 base, local, other = [x[fn].data() for x in pa, p1, p2]
1390 m3 = simplemerge.Merge3Text(base, local, other)
1390 m3 = simplemerge.Merge3Text(base, local, other)
1391 ml = [l.strip() for l in m3.merge_lines()]
1391 ml = [l.strip() for l in m3.merge_lines()]
1392 ml.append("")
1392 ml.append("")
1393 elif at > 0:
1393 elif at > 0:
1394 ml = p1[fn].data().split("\n")
1394 ml = p1[fn].data().split("\n")
1395 else:
1395 else:
1396 ml = initialmergedlines
1396 ml = initialmergedlines
1397 ml[id * linesperrev] += " r%i" % id
1397 ml[id * linesperrev] += " r%i" % id
1398 mergedtext = "\n".join(ml)
1398 mergedtext = "\n".join(ml)
1399 files.append(fn)
1399 files.append(fn)
1400 fctxs[fn] = context.memfilectx(fn, mergedtext)
1400 fctxs[fn] = context.memfilectx(fn, mergedtext)
1401
1401
1402 if overwritten_file:
1402 if overwritten_file:
1403 fn = "of"
1403 fn = "of"
1404 files.append(fn)
1404 files.append(fn)
1405 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1405 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1406
1406
1407 if new_file:
1407 if new_file:
1408 fn = "nf%i" % id
1408 fn = "nf%i" % id
1409 files.append(fn)
1409 files.append(fn)
1410 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1410 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1411 if len(ps) > 1:
1411 if len(ps) > 1:
1412 if not p2:
1412 if not p2:
1413 p2 = repo[ps[1]]
1413 p2 = repo[ps[1]]
1414 for fn in p2:
1414 for fn in p2:
1415 if fn.startswith("nf"):
1415 if fn.startswith("nf"):
1416 files.append(fn)
1416 files.append(fn)
1417 fctxs[fn] = p2[fn]
1417 fctxs[fn] = p2[fn]
1418
1418
1419 def fctxfn(repo, cx, path):
1419 def fctxfn(repo, cx, path):
1420 return fctxs.get(path)
1420 return fctxs.get(path)
1421
1421
1422 if len(ps) == 0 or ps[0] < 0:
1422 if len(ps) == 0 or ps[0] < 0:
1423 pars = [None, None]
1423 pars = [None, None]
1424 elif len(ps) == 1:
1424 elif len(ps) == 1:
1425 pars = [nodeids[ps[0]], None]
1425 pars = [nodeids[ps[0]], None]
1426 else:
1426 else:
1427 pars = [nodeids[p] for p in ps]
1427 pars = [nodeids[p] for p in ps]
1428 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
1428 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
1429 date=(id, 0),
1429 date=(id, 0),
1430 user="debugbuilddag",
1430 user="debugbuilddag",
1431 extra={'branch': atbranch})
1431 extra={'branch': atbranch})
1432 nodeid = repo.commitctx(cx)
1432 nodeid = repo.commitctx(cx)
1433 nodeids.append(nodeid)
1433 nodeids.append(nodeid)
1434 at = id
1434 at = id
1435 elif type == 'l':
1435 elif type == 'l':
1436 id, name = data
1436 id, name = data
1437 ui.note('tag %s\n' % name)
1437 ui.note('tag %s\n' % name)
1438 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
1438 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
1439 elif type == 'a':
1439 elif type == 'a':
1440 ui.note('branch %s\n' % data)
1440 ui.note('branch %s\n' % data)
1441 atbranch = data
1441 atbranch = data
1442 ui.progress(_('building'), id, unit=_('revisions'), total=total)
1442 ui.progress(_('building'), id, unit=_('revisions'), total=total)
1443 tr.close()
1443 tr.close()
1444 finally:
1444 finally:
1445 ui.progress(_('building'), None)
1445 ui.progress(_('building'), None)
1446 tr.release()
1446 tr.release()
1447
1447
1448 if tags:
1448 if tags:
1449 repo.opener.write("localtags", "".join(tags))
1449 repo.opener.write("localtags", "".join(tags))
1450
1450
1451 @command('debugbundle', [('a', 'all', None, _('show all details'))], _('FILE'))
1451 @command('debugbundle', [('a', 'all', None, _('show all details'))], _('FILE'))
1452 def debugbundle(ui, bundlepath, all=None, **opts):
1452 def debugbundle(ui, bundlepath, all=None, **opts):
1453 """lists the contents of a bundle"""
1453 """lists the contents of a bundle"""
1454 f = url.open(ui, bundlepath)
1454 f = url.open(ui, bundlepath)
1455 try:
1455 try:
1456 gen = changegroup.readbundle(f, bundlepath)
1456 gen = changegroup.readbundle(f, bundlepath)
1457 if all:
1457 if all:
1458 ui.write("format: id, p1, p2, cset, delta base, len(delta)\n")
1458 ui.write("format: id, p1, p2, cset, delta base, len(delta)\n")
1459
1459
1460 def showchunks(named):
1460 def showchunks(named):
1461 ui.write("\n%s\n" % named)
1461 ui.write("\n%s\n" % named)
1462 chain = None
1462 chain = None
1463 while True:
1463 while True:
1464 chunkdata = gen.deltachunk(chain)
1464 chunkdata = gen.deltachunk(chain)
1465 if not chunkdata:
1465 if not chunkdata:
1466 break
1466 break
1467 node = chunkdata['node']
1467 node = chunkdata['node']
1468 p1 = chunkdata['p1']
1468 p1 = chunkdata['p1']
1469 p2 = chunkdata['p2']
1469 p2 = chunkdata['p2']
1470 cs = chunkdata['cs']
1470 cs = chunkdata['cs']
1471 deltabase = chunkdata['deltabase']
1471 deltabase = chunkdata['deltabase']
1472 delta = chunkdata['delta']
1472 delta = chunkdata['delta']
1473 ui.write("%s %s %s %s %s %s\n" %
1473 ui.write("%s %s %s %s %s %s\n" %
1474 (hex(node), hex(p1), hex(p2),
1474 (hex(node), hex(p1), hex(p2),
1475 hex(cs), hex(deltabase), len(delta)))
1475 hex(cs), hex(deltabase), len(delta)))
1476 chain = node
1476 chain = node
1477
1477
1478 chunkdata = gen.changelogheader()
1478 chunkdata = gen.changelogheader()
1479 showchunks("changelog")
1479 showchunks("changelog")
1480 chunkdata = gen.manifestheader()
1480 chunkdata = gen.manifestheader()
1481 showchunks("manifest")
1481 showchunks("manifest")
1482 while True:
1482 while True:
1483 chunkdata = gen.filelogheader()
1483 chunkdata = gen.filelogheader()
1484 if not chunkdata:
1484 if not chunkdata:
1485 break
1485 break
1486 fname = chunkdata['filename']
1486 fname = chunkdata['filename']
1487 showchunks(fname)
1487 showchunks(fname)
1488 else:
1488 else:
1489 chunkdata = gen.changelogheader()
1489 chunkdata = gen.changelogheader()
1490 chain = None
1490 chain = None
1491 while True:
1491 while True:
1492 chunkdata = gen.deltachunk(chain)
1492 chunkdata = gen.deltachunk(chain)
1493 if not chunkdata:
1493 if not chunkdata:
1494 break
1494 break
1495 node = chunkdata['node']
1495 node = chunkdata['node']
1496 ui.write("%s\n" % hex(node))
1496 ui.write("%s\n" % hex(node))
1497 chain = node
1497 chain = node
1498 finally:
1498 finally:
1499 f.close()
1499 f.close()
1500
1500
1501 @command('debugcheckstate', [], '')
1501 @command('debugcheckstate', [], '')
1502 def debugcheckstate(ui, repo):
1502 def debugcheckstate(ui, repo):
1503 """validate the correctness of the current dirstate"""
1503 """validate the correctness of the current dirstate"""
1504 parent1, parent2 = repo.dirstate.parents()
1504 parent1, parent2 = repo.dirstate.parents()
1505 m1 = repo[parent1].manifest()
1505 m1 = repo[parent1].manifest()
1506 m2 = repo[parent2].manifest()
1506 m2 = repo[parent2].manifest()
1507 errors = 0
1507 errors = 0
1508 for f in repo.dirstate:
1508 for f in repo.dirstate:
1509 state = repo.dirstate[f]
1509 state = repo.dirstate[f]
1510 if state in "nr" and f not in m1:
1510 if state in "nr" and f not in m1:
1511 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1511 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1512 errors += 1
1512 errors += 1
1513 if state in "a" and f in m1:
1513 if state in "a" and f in m1:
1514 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1514 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1515 errors += 1
1515 errors += 1
1516 if state in "m" and f not in m1 and f not in m2:
1516 if state in "m" and f not in m1 and f not in m2:
1517 ui.warn(_("%s in state %s, but not in either manifest\n") %
1517 ui.warn(_("%s in state %s, but not in either manifest\n") %
1518 (f, state))
1518 (f, state))
1519 errors += 1
1519 errors += 1
1520 for f in m1:
1520 for f in m1:
1521 state = repo.dirstate[f]
1521 state = repo.dirstate[f]
1522 if state not in "nrm":
1522 if state not in "nrm":
1523 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1523 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1524 errors += 1
1524 errors += 1
1525 if errors:
1525 if errors:
1526 error = _(".hg/dirstate inconsistent with current parent's manifest")
1526 error = _(".hg/dirstate inconsistent with current parent's manifest")
1527 raise util.Abort(error)
1527 raise util.Abort(error)
1528
1528
1529 @command('debugcommands', [], _('[COMMAND]'))
1529 @command('debugcommands', [], _('[COMMAND]'))
1530 def debugcommands(ui, cmd='', *args):
1530 def debugcommands(ui, cmd='', *args):
1531 """list all available commands and options"""
1531 """list all available commands and options"""
1532 for cmd, vals in sorted(table.iteritems()):
1532 for cmd, vals in sorted(table.iteritems()):
1533 cmd = cmd.split('|')[0].strip('^')
1533 cmd = cmd.split('|')[0].strip('^')
1534 opts = ', '.join([i[1] for i in vals[1]])
1534 opts = ', '.join([i[1] for i in vals[1]])
1535 ui.write('%s: %s\n' % (cmd, opts))
1535 ui.write('%s: %s\n' % (cmd, opts))
1536
1536
1537 @command('debugcomplete',
1537 @command('debugcomplete',
1538 [('o', 'options', None, _('show the command options'))],
1538 [('o', 'options', None, _('show the command options'))],
1539 _('[-o] CMD'))
1539 _('[-o] CMD'))
1540 def debugcomplete(ui, cmd='', **opts):
1540 def debugcomplete(ui, cmd='', **opts):
1541 """returns the completion list associated with the given command"""
1541 """returns the completion list associated with the given command"""
1542
1542
1543 if opts.get('options'):
1543 if opts.get('options'):
1544 options = []
1544 options = []
1545 otables = [globalopts]
1545 otables = [globalopts]
1546 if cmd:
1546 if cmd:
1547 aliases, entry = cmdutil.findcmd(cmd, table, False)
1547 aliases, entry = cmdutil.findcmd(cmd, table, False)
1548 otables.append(entry[1])
1548 otables.append(entry[1])
1549 for t in otables:
1549 for t in otables:
1550 for o in t:
1550 for o in t:
1551 if "(DEPRECATED)" in o[3]:
1551 if "(DEPRECATED)" in o[3]:
1552 continue
1552 continue
1553 if o[0]:
1553 if o[0]:
1554 options.append('-%s' % o[0])
1554 options.append('-%s' % o[0])
1555 options.append('--%s' % o[1])
1555 options.append('--%s' % o[1])
1556 ui.write("%s\n" % "\n".join(options))
1556 ui.write("%s\n" % "\n".join(options))
1557 return
1557 return
1558
1558
1559 cmdlist = cmdutil.findpossible(cmd, table)
1559 cmdlist = cmdutil.findpossible(cmd, table)
1560 if ui.verbose:
1560 if ui.verbose:
1561 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1561 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1562 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1562 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1563
1563
1564 @command('debugdag',
1564 @command('debugdag',
1565 [('t', 'tags', None, _('use tags as labels')),
1565 [('t', 'tags', None, _('use tags as labels')),
1566 ('b', 'branches', None, _('annotate with branch names')),
1566 ('b', 'branches', None, _('annotate with branch names')),
1567 ('', 'dots', None, _('use dots for runs')),
1567 ('', 'dots', None, _('use dots for runs')),
1568 ('s', 'spaces', None, _('separate elements by spaces'))],
1568 ('s', 'spaces', None, _('separate elements by spaces'))],
1569 _('[OPTION]... [FILE [REV]...]'))
1569 _('[OPTION]... [FILE [REV]...]'))
1570 def debugdag(ui, repo, file_=None, *revs, **opts):
1570 def debugdag(ui, repo, file_=None, *revs, **opts):
1571 """format the changelog or an index DAG as a concise textual description
1571 """format the changelog or an index DAG as a concise textual description
1572
1572
1573 If you pass a revlog index, the revlog's DAG is emitted. If you list
1573 If you pass a revlog index, the revlog's DAG is emitted. If you list
1574 revision numbers, they get labelled in the output as rN.
1574 revision numbers, they get labelled in the output as rN.
1575
1575
1576 Otherwise, the changelog DAG of the current repo is emitted.
1576 Otherwise, the changelog DAG of the current repo is emitted.
1577 """
1577 """
1578 spaces = opts.get('spaces')
1578 spaces = opts.get('spaces')
1579 dots = opts.get('dots')
1579 dots = opts.get('dots')
1580 if file_:
1580 if file_:
1581 rlog = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1581 rlog = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1582 revs = set((int(r) for r in revs))
1582 revs = set((int(r) for r in revs))
1583 def events():
1583 def events():
1584 for r in rlog:
1584 for r in rlog:
1585 yield 'n', (r, list(set(p for p in rlog.parentrevs(r) if p != -1)))
1585 yield 'n', (r, list(set(p for p in rlog.parentrevs(r) if p != -1)))
1586 if r in revs:
1586 if r in revs:
1587 yield 'l', (r, "r%i" % r)
1587 yield 'l', (r, "r%i" % r)
1588 elif repo:
1588 elif repo:
1589 cl = repo.changelog
1589 cl = repo.changelog
1590 tags = opts.get('tags')
1590 tags = opts.get('tags')
1591 branches = opts.get('branches')
1591 branches = opts.get('branches')
1592 if tags:
1592 if tags:
1593 labels = {}
1593 labels = {}
1594 for l, n in repo.tags().items():
1594 for l, n in repo.tags().items():
1595 labels.setdefault(cl.rev(n), []).append(l)
1595 labels.setdefault(cl.rev(n), []).append(l)
1596 def events():
1596 def events():
1597 b = "default"
1597 b = "default"
1598 for r in cl:
1598 for r in cl:
1599 if branches:
1599 if branches:
1600 newb = cl.read(cl.node(r))[5]['branch']
1600 newb = cl.read(cl.node(r))[5]['branch']
1601 if newb != b:
1601 if newb != b:
1602 yield 'a', newb
1602 yield 'a', newb
1603 b = newb
1603 b = newb
1604 yield 'n', (r, list(set(p for p in cl.parentrevs(r) if p != -1)))
1604 yield 'n', (r, list(set(p for p in cl.parentrevs(r) if p != -1)))
1605 if tags:
1605 if tags:
1606 ls = labels.get(r)
1606 ls = labels.get(r)
1607 if ls:
1607 if ls:
1608 for l in ls:
1608 for l in ls:
1609 yield 'l', (r, l)
1609 yield 'l', (r, l)
1610 else:
1610 else:
1611 raise util.Abort(_('need repo for changelog dag'))
1611 raise util.Abort(_('need repo for changelog dag'))
1612
1612
1613 for line in dagparser.dagtextlines(events(),
1613 for line in dagparser.dagtextlines(events(),
1614 addspaces=spaces,
1614 addspaces=spaces,
1615 wraplabels=True,
1615 wraplabels=True,
1616 wrapannotations=True,
1616 wrapannotations=True,
1617 wrapnonlinear=dots,
1617 wrapnonlinear=dots,
1618 usedots=dots,
1618 usedots=dots,
1619 maxlinewidth=70):
1619 maxlinewidth=70):
1620 ui.write(line)
1620 ui.write(line)
1621 ui.write("\n")
1621 ui.write("\n")
1622
1622
1623 @command('debugdata',
1623 @command('debugdata',
1624 [('c', 'changelog', False, _('open changelog')),
1624 [('c', 'changelog', False, _('open changelog')),
1625 ('m', 'manifest', False, _('open manifest'))],
1625 ('m', 'manifest', False, _('open manifest'))],
1626 _('-c|-m|FILE REV'))
1626 _('-c|-m|FILE REV'))
1627 def debugdata(ui, repo, file_, rev = None, **opts):
1627 def debugdata(ui, repo, file_, rev = None, **opts):
1628 """dump the contents of a data file revision"""
1628 """dump the contents of a data file revision"""
1629 if opts.get('changelog') or opts.get('manifest'):
1629 if opts.get('changelog') or opts.get('manifest'):
1630 file_, rev = None, file_
1630 file_, rev = None, file_
1631 elif rev is None:
1631 elif rev is None:
1632 raise error.CommandError('debugdata', _('invalid arguments'))
1632 raise error.CommandError('debugdata', _('invalid arguments'))
1633 r = cmdutil.openrevlog(repo, 'debugdata', file_, opts)
1633 r = cmdutil.openrevlog(repo, 'debugdata', file_, opts)
1634 try:
1634 try:
1635 ui.write(r.revision(r.lookup(rev)))
1635 ui.write(r.revision(r.lookup(rev)))
1636 except KeyError:
1636 except KeyError:
1637 raise util.Abort(_('invalid revision identifier %s') % rev)
1637 raise util.Abort(_('invalid revision identifier %s') % rev)
1638
1638
1639 @command('debugdate',
1639 @command('debugdate',
1640 [('e', 'extended', None, _('try extended date formats'))],
1640 [('e', 'extended', None, _('try extended date formats'))],
1641 _('[-e] DATE [RANGE]'))
1641 _('[-e] DATE [RANGE]'))
1642 def debugdate(ui, date, range=None, **opts):
1642 def debugdate(ui, date, range=None, **opts):
1643 """parse and display a date"""
1643 """parse and display a date"""
1644 if opts["extended"]:
1644 if opts["extended"]:
1645 d = util.parsedate(date, util.extendeddateformats)
1645 d = util.parsedate(date, util.extendeddateformats)
1646 else:
1646 else:
1647 d = util.parsedate(date)
1647 d = util.parsedate(date)
1648 ui.write("internal: %s %s\n" % d)
1648 ui.write("internal: %s %s\n" % d)
1649 ui.write("standard: %s\n" % util.datestr(d))
1649 ui.write("standard: %s\n" % util.datestr(d))
1650 if range:
1650 if range:
1651 m = util.matchdate(range)
1651 m = util.matchdate(range)
1652 ui.write("match: %s\n" % m(d[0]))
1652 ui.write("match: %s\n" % m(d[0]))
1653
1653
1654 @command('debugdiscovery',
1654 @command('debugdiscovery',
1655 [('', 'old', None, _('use old-style discovery')),
1655 [('', 'old', None, _('use old-style discovery')),
1656 ('', 'nonheads', None,
1656 ('', 'nonheads', None,
1657 _('use old-style discovery with non-heads included')),
1657 _('use old-style discovery with non-heads included')),
1658 ] + remoteopts,
1658 ] + remoteopts,
1659 _('[-l REV] [-r REV] [-b BRANCH]... [OTHER]'))
1659 _('[-l REV] [-r REV] [-b BRANCH]... [OTHER]'))
1660 def debugdiscovery(ui, repo, remoteurl="default", **opts):
1660 def debugdiscovery(ui, repo, remoteurl="default", **opts):
1661 """runs the changeset discovery protocol in isolation"""
1661 """runs the changeset discovery protocol in isolation"""
1662 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl), opts.get('branch'))
1662 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl), opts.get('branch'))
1663 remote = hg.peer(repo, opts, remoteurl)
1663 remote = hg.peer(repo, opts, remoteurl)
1664 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
1664 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
1665
1665
1666 # make sure tests are repeatable
1666 # make sure tests are repeatable
1667 random.seed(12323)
1667 random.seed(12323)
1668
1668
1669 def doit(localheads, remoteheads):
1669 def doit(localheads, remoteheads):
1670 if opts.get('old'):
1670 if opts.get('old'):
1671 if localheads:
1671 if localheads:
1672 raise util.Abort('cannot use localheads with old style discovery')
1672 raise util.Abort('cannot use localheads with old style discovery')
1673 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
1673 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
1674 force=True)
1674 force=True)
1675 common = set(common)
1675 common = set(common)
1676 if not opts.get('nonheads'):
1676 if not opts.get('nonheads'):
1677 ui.write("unpruned common: %s\n" % " ".join([short(n)
1677 ui.write("unpruned common: %s\n" % " ".join([short(n)
1678 for n in common]))
1678 for n in common]))
1679 dag = dagutil.revlogdag(repo.changelog)
1679 dag = dagutil.revlogdag(repo.changelog)
1680 all = dag.ancestorset(dag.internalizeall(common))
1680 all = dag.ancestorset(dag.internalizeall(common))
1681 common = dag.externalizeall(dag.headsetofconnecteds(all))
1681 common = dag.externalizeall(dag.headsetofconnecteds(all))
1682 else:
1682 else:
1683 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote)
1683 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote)
1684 common = set(common)
1684 common = set(common)
1685 rheads = set(hds)
1685 rheads = set(hds)
1686 lheads = set(repo.heads())
1686 lheads = set(repo.heads())
1687 ui.write("common heads: %s\n" % " ".join([short(n) for n in common]))
1687 ui.write("common heads: %s\n" % " ".join([short(n) for n in common]))
1688 if lheads <= common:
1688 if lheads <= common:
1689 ui.write("local is subset\n")
1689 ui.write("local is subset\n")
1690 elif rheads <= common:
1690 elif rheads <= common:
1691 ui.write("remote is subset\n")
1691 ui.write("remote is subset\n")
1692
1692
1693 serverlogs = opts.get('serverlog')
1693 serverlogs = opts.get('serverlog')
1694 if serverlogs:
1694 if serverlogs:
1695 for filename in serverlogs:
1695 for filename in serverlogs:
1696 logfile = open(filename, 'r')
1696 logfile = open(filename, 'r')
1697 try:
1697 try:
1698 line = logfile.readline()
1698 line = logfile.readline()
1699 while line:
1699 while line:
1700 parts = line.strip().split(';')
1700 parts = line.strip().split(';')
1701 op = parts[1]
1701 op = parts[1]
1702 if op == 'cg':
1702 if op == 'cg':
1703 pass
1703 pass
1704 elif op == 'cgss':
1704 elif op == 'cgss':
1705 doit(parts[2].split(' '), parts[3].split(' '))
1705 doit(parts[2].split(' '), parts[3].split(' '))
1706 elif op == 'unb':
1706 elif op == 'unb':
1707 doit(parts[3].split(' '), parts[2].split(' '))
1707 doit(parts[3].split(' '), parts[2].split(' '))
1708 line = logfile.readline()
1708 line = logfile.readline()
1709 finally:
1709 finally:
1710 logfile.close()
1710 logfile.close()
1711
1711
1712 else:
1712 else:
1713 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches,
1713 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches,
1714 opts.get('remote_head'))
1714 opts.get('remote_head'))
1715 localrevs = opts.get('local_head')
1715 localrevs = opts.get('local_head')
1716 doit(localrevs, remoterevs)
1716 doit(localrevs, remoterevs)
1717
1717
1718 @command('debugfileset', [], ('REVSPEC'))
1718 @command('debugfileset', [], ('REVSPEC'))
1719 def debugfileset(ui, repo, expr):
1719 def debugfileset(ui, repo, expr):
1720 '''parse and apply a fileset specification'''
1720 '''parse and apply a fileset specification'''
1721 if ui.verbose:
1721 if ui.verbose:
1722 tree = fileset.parse(expr)[0]
1722 tree = fileset.parse(expr)[0]
1723 ui.note(tree, "\n")
1723 ui.note(tree, "\n")
1724
1724
1725 for f in fileset.getfileset(repo[None], expr):
1725 for f in fileset.getfileset(repo[None], expr):
1726 ui.write("%s\n" % f)
1726 ui.write("%s\n" % f)
1727
1727
1728 @command('debugfsinfo', [], _('[PATH]'))
1728 @command('debugfsinfo', [], _('[PATH]'))
1729 def debugfsinfo(ui, path = "."):
1729 def debugfsinfo(ui, path = "."):
1730 """show information detected about current filesystem"""
1730 """show information detected about current filesystem"""
1731 util.writefile('.debugfsinfo', '')
1731 util.writefile('.debugfsinfo', '')
1732 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
1732 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
1733 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
1733 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
1734 ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
1734 ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
1735 and 'yes' or 'no'))
1735 and 'yes' or 'no'))
1736 os.unlink('.debugfsinfo')
1736 os.unlink('.debugfsinfo')
1737
1737
1738 @command('debuggetbundle',
1738 @command('debuggetbundle',
1739 [('H', 'head', [], _('id of head node'), _('ID')),
1739 [('H', 'head', [], _('id of head node'), _('ID')),
1740 ('C', 'common', [], _('id of common node'), _('ID')),
1740 ('C', 'common', [], _('id of common node'), _('ID')),
1741 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))],
1741 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))],
1742 _('REPO FILE [-H|-C ID]...'))
1742 _('REPO FILE [-H|-C ID]...'))
1743 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
1743 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
1744 """retrieves a bundle from a repo
1744 """retrieves a bundle from a repo
1745
1745
1746 Every ID must be a full-length hex node id string. Saves the bundle to the
1746 Every ID must be a full-length hex node id string. Saves the bundle to the
1747 given file.
1747 given file.
1748 """
1748 """
1749 repo = hg.peer(ui, opts, repopath)
1749 repo = hg.peer(ui, opts, repopath)
1750 if not repo.capable('getbundle'):
1750 if not repo.capable('getbundle'):
1751 raise util.Abort("getbundle() not supported by target repository")
1751 raise util.Abort("getbundle() not supported by target repository")
1752 args = {}
1752 args = {}
1753 if common:
1753 if common:
1754 args['common'] = [bin(s) for s in common]
1754 args['common'] = [bin(s) for s in common]
1755 if head:
1755 if head:
1756 args['heads'] = [bin(s) for s in head]
1756 args['heads'] = [bin(s) for s in head]
1757 bundle = repo.getbundle('debug', **args)
1757 bundle = repo.getbundle('debug', **args)
1758
1758
1759 bundletype = opts.get('type', 'bzip2').lower()
1759 bundletype = opts.get('type', 'bzip2').lower()
1760 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
1760 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
1761 bundletype = btypes.get(bundletype)
1761 bundletype = btypes.get(bundletype)
1762 if bundletype not in changegroup.bundletypes:
1762 if bundletype not in changegroup.bundletypes:
1763 raise util.Abort(_('unknown bundle type specified with --type'))
1763 raise util.Abort(_('unknown bundle type specified with --type'))
1764 changegroup.writebundle(bundle, bundlepath, bundletype)
1764 changegroup.writebundle(bundle, bundlepath, bundletype)
1765
1765
1766 @command('debugignore', [], '')
1766 @command('debugignore', [], '')
1767 def debugignore(ui, repo, *values, **opts):
1767 def debugignore(ui, repo, *values, **opts):
1768 """display the combined ignore pattern"""
1768 """display the combined ignore pattern"""
1769 ignore = repo.dirstate._ignore
1769 ignore = repo.dirstate._ignore
1770 includepat = getattr(ignore, 'includepat', None)
1770 includepat = getattr(ignore, 'includepat', None)
1771 if includepat is not None:
1771 if includepat is not None:
1772 ui.write("%s\n" % includepat)
1772 ui.write("%s\n" % includepat)
1773 else:
1773 else:
1774 raise util.Abort(_("no ignore patterns found"))
1774 raise util.Abort(_("no ignore patterns found"))
1775
1775
1776 @command('debugindex',
1776 @command('debugindex',
1777 [('c', 'changelog', False, _('open changelog')),
1777 [('c', 'changelog', False, _('open changelog')),
1778 ('m', 'manifest', False, _('open manifest')),
1778 ('m', 'manifest', False, _('open manifest')),
1779 ('f', 'format', 0, _('revlog format'), _('FORMAT'))],
1779 ('f', 'format', 0, _('revlog format'), _('FORMAT'))],
1780 _('[-f FORMAT] -c|-m|FILE'))
1780 _('[-f FORMAT] -c|-m|FILE'))
1781 def debugindex(ui, repo, file_ = None, **opts):
1781 def debugindex(ui, repo, file_ = None, **opts):
1782 """dump the contents of an index file"""
1782 """dump the contents of an index file"""
1783 r = cmdutil.openrevlog(repo, 'debugindex', file_, opts)
1783 r = cmdutil.openrevlog(repo, 'debugindex', file_, opts)
1784 format = opts.get('format', 0)
1784 format = opts.get('format', 0)
1785 if format not in (0, 1):
1785 if format not in (0, 1):
1786 raise util.Abort(_("unknown format %d") % format)
1786 raise util.Abort(_("unknown format %d") % format)
1787
1787
1788 generaldelta = r.version & revlog.REVLOGGENERALDELTA
1788 generaldelta = r.version & revlog.REVLOGGENERALDELTA
1789 if generaldelta:
1789 if generaldelta:
1790 basehdr = ' delta'
1790 basehdr = ' delta'
1791 else:
1791 else:
1792 basehdr = ' base'
1792 basehdr = ' base'
1793
1793
1794 if format == 0:
1794 if format == 0:
1795 ui.write(" rev offset length " + basehdr + " linkrev"
1795 ui.write(" rev offset length " + basehdr + " linkrev"
1796 " nodeid p1 p2\n")
1796 " nodeid p1 p2\n")
1797 elif format == 1:
1797 elif format == 1:
1798 ui.write(" rev flag offset length"
1798 ui.write(" rev flag offset length"
1799 " size " + basehdr + " link p1 p2 nodeid\n")
1799 " size " + basehdr + " link p1 p2 nodeid\n")
1800
1800
1801 for i in r:
1801 for i in r:
1802 node = r.node(i)
1802 node = r.node(i)
1803 if generaldelta:
1803 if generaldelta:
1804 base = r.deltaparent(i)
1804 base = r.deltaparent(i)
1805 else:
1805 else:
1806 base = r.chainbase(i)
1806 base = r.chainbase(i)
1807 if format == 0:
1807 if format == 0:
1808 try:
1808 try:
1809 pp = r.parents(node)
1809 pp = r.parents(node)
1810 except:
1810 except:
1811 pp = [nullid, nullid]
1811 pp = [nullid, nullid]
1812 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1812 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1813 i, r.start(i), r.length(i), base, r.linkrev(i),
1813 i, r.start(i), r.length(i), base, r.linkrev(i),
1814 short(node), short(pp[0]), short(pp[1])))
1814 short(node), short(pp[0]), short(pp[1])))
1815 elif format == 1:
1815 elif format == 1:
1816 pr = r.parentrevs(i)
1816 pr = r.parentrevs(i)
1817 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
1817 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
1818 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
1818 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
1819 base, r.linkrev(i), pr[0], pr[1], short(node)))
1819 base, r.linkrev(i), pr[0], pr[1], short(node)))
1820
1820
1821 @command('debugindexdot', [], _('FILE'))
1821 @command('debugindexdot', [], _('FILE'))
1822 def debugindexdot(ui, repo, file_):
1822 def debugindexdot(ui, repo, file_):
1823 """dump an index DAG as a graphviz dot file"""
1823 """dump an index DAG as a graphviz dot file"""
1824 r = None
1824 r = None
1825 if repo:
1825 if repo:
1826 filelog = repo.file(file_)
1826 filelog = repo.file(file_)
1827 if len(filelog):
1827 if len(filelog):
1828 r = filelog
1828 r = filelog
1829 if not r:
1829 if not r:
1830 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1830 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1831 ui.write("digraph G {\n")
1831 ui.write("digraph G {\n")
1832 for i in r:
1832 for i in r:
1833 node = r.node(i)
1833 node = r.node(i)
1834 pp = r.parents(node)
1834 pp = r.parents(node)
1835 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1835 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1836 if pp[1] != nullid:
1836 if pp[1] != nullid:
1837 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1837 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1838 ui.write("}\n")
1838 ui.write("}\n")
1839
1839
1840 @command('debuginstall', [], '')
1840 @command('debuginstall', [], '')
1841 def debuginstall(ui):
1841 def debuginstall(ui):
1842 '''test Mercurial installation
1842 '''test Mercurial installation
1843
1843
1844 Returns 0 on success.
1844 Returns 0 on success.
1845 '''
1845 '''
1846
1846
1847 def writetemp(contents):
1847 def writetemp(contents):
1848 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
1848 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
1849 f = os.fdopen(fd, "wb")
1849 f = os.fdopen(fd, "wb")
1850 f.write(contents)
1850 f.write(contents)
1851 f.close()
1851 f.close()
1852 return name
1852 return name
1853
1853
1854 problems = 0
1854 problems = 0
1855
1855
1856 # encoding
1856 # encoding
1857 ui.status(_("Checking encoding (%s)...\n") % encoding.encoding)
1857 ui.status(_("Checking encoding (%s)...\n") % encoding.encoding)
1858 try:
1858 try:
1859 encoding.fromlocal("test")
1859 encoding.fromlocal("test")
1860 except util.Abort, inst:
1860 except util.Abort, inst:
1861 ui.write(" %s\n" % inst)
1861 ui.write(" %s\n" % inst)
1862 ui.write(_(" (check that your locale is properly set)\n"))
1862 ui.write(_(" (check that your locale is properly set)\n"))
1863 problems += 1
1863 problems += 1
1864
1864
1865 # compiled modules
1865 # compiled modules
1866 ui.status(_("Checking installed modules (%s)...\n")
1866 ui.status(_("Checking installed modules (%s)...\n")
1867 % os.path.dirname(__file__))
1867 % os.path.dirname(__file__))
1868 try:
1868 try:
1869 import bdiff, mpatch, base85, osutil
1869 import bdiff, mpatch, base85, osutil
1870 dir(bdiff), dir(mpatch), dir(base85), dir(osutil) # quiet pyflakes
1870 dir(bdiff), dir(mpatch), dir(base85), dir(osutil) # quiet pyflakes
1871 except Exception, inst:
1871 except Exception, inst:
1872 ui.write(" %s\n" % inst)
1872 ui.write(" %s\n" % inst)
1873 ui.write(_(" One or more extensions could not be found"))
1873 ui.write(_(" One or more extensions could not be found"))
1874 ui.write(_(" (check that you compiled the extensions)\n"))
1874 ui.write(_(" (check that you compiled the extensions)\n"))
1875 problems += 1
1875 problems += 1
1876
1876
1877 # templates
1877 # templates
1878 import templater
1878 import templater
1879 p = templater.templatepath()
1879 p = templater.templatepath()
1880 ui.status(_("Checking templates (%s)...\n") % ' '.join(p))
1880 ui.status(_("Checking templates (%s)...\n") % ' '.join(p))
1881 try:
1881 try:
1882 templater.templater(templater.templatepath("map-cmdline.default"))
1882 templater.templater(templater.templatepath("map-cmdline.default"))
1883 except Exception, inst:
1883 except Exception, inst:
1884 ui.write(" %s\n" % inst)
1884 ui.write(" %s\n" % inst)
1885 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
1885 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
1886 problems += 1
1886 problems += 1
1887
1887
1888 # editor
1888 # editor
1889 ui.status(_("Checking commit editor...\n"))
1889 ui.status(_("Checking commit editor...\n"))
1890 editor = ui.geteditor()
1890 editor = ui.geteditor()
1891 cmdpath = util.findexe(editor) or util.findexe(editor.split()[0])
1891 cmdpath = util.findexe(editor) or util.findexe(editor.split()[0])
1892 if not cmdpath:
1892 if not cmdpath:
1893 if editor == 'vi':
1893 if editor == 'vi':
1894 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
1894 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
1895 ui.write(_(" (specify a commit editor in your configuration"
1895 ui.write(_(" (specify a commit editor in your configuration"
1896 " file)\n"))
1896 " file)\n"))
1897 else:
1897 else:
1898 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
1898 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
1899 ui.write(_(" (specify a commit editor in your configuration"
1899 ui.write(_(" (specify a commit editor in your configuration"
1900 " file)\n"))
1900 " file)\n"))
1901 problems += 1
1901 problems += 1
1902
1902
1903 # check username
1903 # check username
1904 ui.status(_("Checking username...\n"))
1904 ui.status(_("Checking username...\n"))
1905 try:
1905 try:
1906 ui.username()
1906 ui.username()
1907 except util.Abort, e:
1907 except util.Abort, e:
1908 ui.write(" %s\n" % e)
1908 ui.write(" %s\n" % e)
1909 ui.write(_(" (specify a username in your configuration file)\n"))
1909 ui.write(_(" (specify a username in your configuration file)\n"))
1910 problems += 1
1910 problems += 1
1911
1911
1912 if not problems:
1912 if not problems:
1913 ui.status(_("No problems detected\n"))
1913 ui.status(_("No problems detected\n"))
1914 else:
1914 else:
1915 ui.write(_("%s problems detected,"
1915 ui.write(_("%s problems detected,"
1916 " please check your install!\n") % problems)
1916 " please check your install!\n") % problems)
1917
1917
1918 return problems
1918 return problems
1919
1919
1920 @command('debugknown', [], _('REPO ID...'))
1920 @command('debugknown', [], _('REPO ID...'))
1921 def debugknown(ui, repopath, *ids, **opts):
1921 def debugknown(ui, repopath, *ids, **opts):
1922 """test whether node ids are known to a repo
1922 """test whether node ids are known to a repo
1923
1923
1924 Every ID must be a full-length hex node id string. Returns a list of 0s and 1s
1924 Every ID must be a full-length hex node id string. Returns a list of 0s and 1s
1925 indicating unknown/known.
1925 indicating unknown/known.
1926 """
1926 """
1927 repo = hg.peer(ui, opts, repopath)
1927 repo = hg.peer(ui, opts, repopath)
1928 if not repo.capable('known'):
1928 if not repo.capable('known'):
1929 raise util.Abort("known() not supported by target repository")
1929 raise util.Abort("known() not supported by target repository")
1930 flags = repo.known([bin(s) for s in ids])
1930 flags = repo.known([bin(s) for s in ids])
1931 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
1931 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
1932
1932
1933 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'))
1933 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'))
1934 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
1934 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
1935 '''access the pushkey key/value protocol
1935 '''access the pushkey key/value protocol
1936
1936
1937 With two args, list the keys in the given namespace.
1937 With two args, list the keys in the given namespace.
1938
1938
1939 With five args, set a key to new if it currently is set to old.
1939 With five args, set a key to new if it currently is set to old.
1940 Reports success or failure.
1940 Reports success or failure.
1941 '''
1941 '''
1942
1942
1943 target = hg.peer(ui, {}, repopath)
1943 target = hg.peer(ui, {}, repopath)
1944 if keyinfo:
1944 if keyinfo:
1945 key, old, new = keyinfo
1945 key, old, new = keyinfo
1946 r = target.pushkey(namespace, key, old, new)
1946 r = target.pushkey(namespace, key, old, new)
1947 ui.status(str(r) + '\n')
1947 ui.status(str(r) + '\n')
1948 return not r
1948 return not r
1949 else:
1949 else:
1950 for k, v in target.listkeys(namespace).iteritems():
1950 for k, v in target.listkeys(namespace).iteritems():
1951 ui.write("%s\t%s\n" % (k.encode('string-escape'),
1951 ui.write("%s\t%s\n" % (k.encode('string-escape'),
1952 v.encode('string-escape')))
1952 v.encode('string-escape')))
1953
1953
1954 @command('debugrebuildstate',
1954 @command('debugrebuildstate',
1955 [('r', 'rev', '', _('revision to rebuild to'), _('REV'))],
1955 [('r', 'rev', '', _('revision to rebuild to'), _('REV'))],
1956 _('[-r REV] [REV]'))
1956 _('[-r REV] [REV]'))
1957 def debugrebuildstate(ui, repo, rev="tip"):
1957 def debugrebuildstate(ui, repo, rev="tip"):
1958 """rebuild the dirstate as it would look like for the given revision"""
1958 """rebuild the dirstate as it would look like for the given revision"""
1959 ctx = scmutil.revsingle(repo, rev)
1959 ctx = scmutil.revsingle(repo, rev)
1960 wlock = repo.wlock()
1960 wlock = repo.wlock()
1961 try:
1961 try:
1962 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1962 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1963 finally:
1963 finally:
1964 wlock.release()
1964 wlock.release()
1965
1965
1966 @command('debugrename',
1966 @command('debugrename',
1967 [('r', 'rev', '', _('revision to debug'), _('REV'))],
1967 [('r', 'rev', '', _('revision to debug'), _('REV'))],
1968 _('[-r REV] FILE'))
1968 _('[-r REV] FILE'))
1969 def debugrename(ui, repo, file1, *pats, **opts):
1969 def debugrename(ui, repo, file1, *pats, **opts):
1970 """dump rename information"""
1970 """dump rename information"""
1971
1971
1972 ctx = scmutil.revsingle(repo, opts.get('rev'))
1972 ctx = scmutil.revsingle(repo, opts.get('rev'))
1973 m = scmutil.match(ctx, (file1,) + pats, opts)
1973 m = scmutil.match(ctx, (file1,) + pats, opts)
1974 for abs in ctx.walk(m):
1974 for abs in ctx.walk(m):
1975 fctx = ctx[abs]
1975 fctx = ctx[abs]
1976 o = fctx.filelog().renamed(fctx.filenode())
1976 o = fctx.filelog().renamed(fctx.filenode())
1977 rel = m.rel(abs)
1977 rel = m.rel(abs)
1978 if o:
1978 if o:
1979 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
1979 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
1980 else:
1980 else:
1981 ui.write(_("%s not renamed\n") % rel)
1981 ui.write(_("%s not renamed\n") % rel)
1982
1982
1983 @command('debugrevlog',
1983 @command('debugrevlog',
1984 [('c', 'changelog', False, _('open changelog')),
1984 [('c', 'changelog', False, _('open changelog')),
1985 ('m', 'manifest', False, _('open manifest')),
1985 ('m', 'manifest', False, _('open manifest')),
1986 ('d', 'dump', False, _('dump index data'))],
1986 ('d', 'dump', False, _('dump index data'))],
1987 _('-c|-m|FILE'))
1987 _('-c|-m|FILE'))
1988 def debugrevlog(ui, repo, file_ = None, **opts):
1988 def debugrevlog(ui, repo, file_ = None, **opts):
1989 """show data and statistics about a revlog"""
1989 """show data and statistics about a revlog"""
1990 r = cmdutil.openrevlog(repo, 'debugrevlog', file_, opts)
1990 r = cmdutil.openrevlog(repo, 'debugrevlog', file_, opts)
1991
1991
1992 if opts.get("dump"):
1992 if opts.get("dump"):
1993 numrevs = len(r)
1993 numrevs = len(r)
1994 ui.write("# rev p1rev p2rev start end deltastart base p1 p2"
1994 ui.write("# rev p1rev p2rev start end deltastart base p1 p2"
1995 " rawsize totalsize compression heads\n")
1995 " rawsize totalsize compression heads\n")
1996 ts = 0
1996 ts = 0
1997 heads = set()
1997 heads = set()
1998 for rev in xrange(numrevs):
1998 for rev in xrange(numrevs):
1999 dbase = r.deltaparent(rev)
1999 dbase = r.deltaparent(rev)
2000 if dbase == -1:
2000 if dbase == -1:
2001 dbase = rev
2001 dbase = rev
2002 cbase = r.chainbase(rev)
2002 cbase = r.chainbase(rev)
2003 p1, p2 = r.parentrevs(rev)
2003 p1, p2 = r.parentrevs(rev)
2004 rs = r.rawsize(rev)
2004 rs = r.rawsize(rev)
2005 ts = ts + rs
2005 ts = ts + rs
2006 heads -= set(r.parentrevs(rev))
2006 heads -= set(r.parentrevs(rev))
2007 heads.add(rev)
2007 heads.add(rev)
2008 ui.write("%d %d %d %d %d %d %d %d %d %d %d %d %d\n" %
2008 ui.write("%d %d %d %d %d %d %d %d %d %d %d %d %d\n" %
2009 (rev, p1, p2, r.start(rev), r.end(rev),
2009 (rev, p1, p2, r.start(rev), r.end(rev),
2010 r.start(dbase), r.start(cbase),
2010 r.start(dbase), r.start(cbase),
2011 r.start(p1), r.start(p2),
2011 r.start(p1), r.start(p2),
2012 rs, ts, ts / r.end(rev), len(heads)))
2012 rs, ts, ts / r.end(rev), len(heads)))
2013 return 0
2013 return 0
2014
2014
2015 v = r.version
2015 v = r.version
2016 format = v & 0xFFFF
2016 format = v & 0xFFFF
2017 flags = []
2017 flags = []
2018 gdelta = False
2018 gdelta = False
2019 if v & revlog.REVLOGNGINLINEDATA:
2019 if v & revlog.REVLOGNGINLINEDATA:
2020 flags.append('inline')
2020 flags.append('inline')
2021 if v & revlog.REVLOGGENERALDELTA:
2021 if v & revlog.REVLOGGENERALDELTA:
2022 gdelta = True
2022 gdelta = True
2023 flags.append('generaldelta')
2023 flags.append('generaldelta')
2024 if not flags:
2024 if not flags:
2025 flags = ['(none)']
2025 flags = ['(none)']
2026
2026
2027 nummerges = 0
2027 nummerges = 0
2028 numfull = 0
2028 numfull = 0
2029 numprev = 0
2029 numprev = 0
2030 nump1 = 0
2030 nump1 = 0
2031 nump2 = 0
2031 nump2 = 0
2032 numother = 0
2032 numother = 0
2033 nump1prev = 0
2033 nump1prev = 0
2034 nump2prev = 0
2034 nump2prev = 0
2035 chainlengths = []
2035 chainlengths = []
2036
2036
2037 datasize = [None, 0, 0L]
2037 datasize = [None, 0, 0L]
2038 fullsize = [None, 0, 0L]
2038 fullsize = [None, 0, 0L]
2039 deltasize = [None, 0, 0L]
2039 deltasize = [None, 0, 0L]
2040
2040
2041 def addsize(size, l):
2041 def addsize(size, l):
2042 if l[0] is None or size < l[0]:
2042 if l[0] is None or size < l[0]:
2043 l[0] = size
2043 l[0] = size
2044 if size > l[1]:
2044 if size > l[1]:
2045 l[1] = size
2045 l[1] = size
2046 l[2] += size
2046 l[2] += size
2047
2047
2048 numrevs = len(r)
2048 numrevs = len(r)
2049 for rev in xrange(numrevs):
2049 for rev in xrange(numrevs):
2050 p1, p2 = r.parentrevs(rev)
2050 p1, p2 = r.parentrevs(rev)
2051 delta = r.deltaparent(rev)
2051 delta = r.deltaparent(rev)
2052 if format > 0:
2052 if format > 0:
2053 addsize(r.rawsize(rev), datasize)
2053 addsize(r.rawsize(rev), datasize)
2054 if p2 != nullrev:
2054 if p2 != nullrev:
2055 nummerges += 1
2055 nummerges += 1
2056 size = r.length(rev)
2056 size = r.length(rev)
2057 if delta == nullrev:
2057 if delta == nullrev:
2058 chainlengths.append(0)
2058 chainlengths.append(0)
2059 numfull += 1
2059 numfull += 1
2060 addsize(size, fullsize)
2060 addsize(size, fullsize)
2061 else:
2061 else:
2062 chainlengths.append(chainlengths[delta] + 1)
2062 chainlengths.append(chainlengths[delta] + 1)
2063 addsize(size, deltasize)
2063 addsize(size, deltasize)
2064 if delta == rev - 1:
2064 if delta == rev - 1:
2065 numprev += 1
2065 numprev += 1
2066 if delta == p1:
2066 if delta == p1:
2067 nump1prev += 1
2067 nump1prev += 1
2068 elif delta == p2:
2068 elif delta == p2:
2069 nump2prev += 1
2069 nump2prev += 1
2070 elif delta == p1:
2070 elif delta == p1:
2071 nump1 += 1
2071 nump1 += 1
2072 elif delta == p2:
2072 elif delta == p2:
2073 nump2 += 1
2073 nump2 += 1
2074 elif delta != nullrev:
2074 elif delta != nullrev:
2075 numother += 1
2075 numother += 1
2076
2076
2077 numdeltas = numrevs - numfull
2077 numdeltas = numrevs - numfull
2078 numoprev = numprev - nump1prev - nump2prev
2078 numoprev = numprev - nump1prev - nump2prev
2079 totalrawsize = datasize[2]
2079 totalrawsize = datasize[2]
2080 datasize[2] /= numrevs
2080 datasize[2] /= numrevs
2081 fulltotal = fullsize[2]
2081 fulltotal = fullsize[2]
2082 fullsize[2] /= numfull
2082 fullsize[2] /= numfull
2083 deltatotal = deltasize[2]
2083 deltatotal = deltasize[2]
2084 deltasize[2] /= numrevs - numfull
2084 deltasize[2] /= numrevs - numfull
2085 totalsize = fulltotal + deltatotal
2085 totalsize = fulltotal + deltatotal
2086 avgchainlen = sum(chainlengths) / numrevs
2086 avgchainlen = sum(chainlengths) / numrevs
2087 compratio = totalrawsize / totalsize
2087 compratio = totalrawsize / totalsize
2088
2088
2089 basedfmtstr = '%%%dd\n'
2089 basedfmtstr = '%%%dd\n'
2090 basepcfmtstr = '%%%dd %s(%%5.2f%%%%)\n'
2090 basepcfmtstr = '%%%dd %s(%%5.2f%%%%)\n'
2091
2091
2092 def dfmtstr(max):
2092 def dfmtstr(max):
2093 return basedfmtstr % len(str(max))
2093 return basedfmtstr % len(str(max))
2094 def pcfmtstr(max, padding=0):
2094 def pcfmtstr(max, padding=0):
2095 return basepcfmtstr % (len(str(max)), ' ' * padding)
2095 return basepcfmtstr % (len(str(max)), ' ' * padding)
2096
2096
2097 def pcfmt(value, total):
2097 def pcfmt(value, total):
2098 return (value, 100 * float(value) / total)
2098 return (value, 100 * float(value) / total)
2099
2099
2100 ui.write('format : %d\n' % format)
2100 ui.write('format : %d\n' % format)
2101 ui.write('flags : %s\n' % ', '.join(flags))
2101 ui.write('flags : %s\n' % ', '.join(flags))
2102
2102
2103 ui.write('\n')
2103 ui.write('\n')
2104 fmt = pcfmtstr(totalsize)
2104 fmt = pcfmtstr(totalsize)
2105 fmt2 = dfmtstr(totalsize)
2105 fmt2 = dfmtstr(totalsize)
2106 ui.write('revisions : ' + fmt2 % numrevs)
2106 ui.write('revisions : ' + fmt2 % numrevs)
2107 ui.write(' merges : ' + fmt % pcfmt(nummerges, numrevs))
2107 ui.write(' merges : ' + fmt % pcfmt(nummerges, numrevs))
2108 ui.write(' normal : ' + fmt % pcfmt(numrevs - nummerges, numrevs))
2108 ui.write(' normal : ' + fmt % pcfmt(numrevs - nummerges, numrevs))
2109 ui.write('revisions : ' + fmt2 % numrevs)
2109 ui.write('revisions : ' + fmt2 % numrevs)
2110 ui.write(' full : ' + fmt % pcfmt(numfull, numrevs))
2110 ui.write(' full : ' + fmt % pcfmt(numfull, numrevs))
2111 ui.write(' deltas : ' + fmt % pcfmt(numdeltas, numrevs))
2111 ui.write(' deltas : ' + fmt % pcfmt(numdeltas, numrevs))
2112 ui.write('revision size : ' + fmt2 % totalsize)
2112 ui.write('revision size : ' + fmt2 % totalsize)
2113 ui.write(' full : ' + fmt % pcfmt(fulltotal, totalsize))
2113 ui.write(' full : ' + fmt % pcfmt(fulltotal, totalsize))
2114 ui.write(' deltas : ' + fmt % pcfmt(deltatotal, totalsize))
2114 ui.write(' deltas : ' + fmt % pcfmt(deltatotal, totalsize))
2115
2115
2116 ui.write('\n')
2116 ui.write('\n')
2117 fmt = dfmtstr(max(avgchainlen, compratio))
2117 fmt = dfmtstr(max(avgchainlen, compratio))
2118 ui.write('avg chain length : ' + fmt % avgchainlen)
2118 ui.write('avg chain length : ' + fmt % avgchainlen)
2119 ui.write('compression ratio : ' + fmt % compratio)
2119 ui.write('compression ratio : ' + fmt % compratio)
2120
2120
2121 if format > 0:
2121 if format > 0:
2122 ui.write('\n')
2122 ui.write('\n')
2123 ui.write('uncompressed data size (min/max/avg) : %d / %d / %d\n'
2123 ui.write('uncompressed data size (min/max/avg) : %d / %d / %d\n'
2124 % tuple(datasize))
2124 % tuple(datasize))
2125 ui.write('full revision size (min/max/avg) : %d / %d / %d\n'
2125 ui.write('full revision size (min/max/avg) : %d / %d / %d\n'
2126 % tuple(fullsize))
2126 % tuple(fullsize))
2127 ui.write('delta size (min/max/avg) : %d / %d / %d\n'
2127 ui.write('delta size (min/max/avg) : %d / %d / %d\n'
2128 % tuple(deltasize))
2128 % tuple(deltasize))
2129
2129
2130 if numdeltas > 0:
2130 if numdeltas > 0:
2131 ui.write('\n')
2131 ui.write('\n')
2132 fmt = pcfmtstr(numdeltas)
2132 fmt = pcfmtstr(numdeltas)
2133 fmt2 = pcfmtstr(numdeltas, 4)
2133 fmt2 = pcfmtstr(numdeltas, 4)
2134 ui.write('deltas against prev : ' + fmt % pcfmt(numprev, numdeltas))
2134 ui.write('deltas against prev : ' + fmt % pcfmt(numprev, numdeltas))
2135 if numprev > 0:
2135 if numprev > 0:
2136 ui.write(' where prev = p1 : ' + fmt2 % pcfmt(nump1prev, numprev))
2136 ui.write(' where prev = p1 : ' + fmt2 % pcfmt(nump1prev, numprev))
2137 ui.write(' where prev = p2 : ' + fmt2 % pcfmt(nump2prev, numprev))
2137 ui.write(' where prev = p2 : ' + fmt2 % pcfmt(nump2prev, numprev))
2138 ui.write(' other : ' + fmt2 % pcfmt(numoprev, numprev))
2138 ui.write(' other : ' + fmt2 % pcfmt(numoprev, numprev))
2139 if gdelta:
2139 if gdelta:
2140 ui.write('deltas against p1 : ' + fmt % pcfmt(nump1, numdeltas))
2140 ui.write('deltas against p1 : ' + fmt % pcfmt(nump1, numdeltas))
2141 ui.write('deltas against p2 : ' + fmt % pcfmt(nump2, numdeltas))
2141 ui.write('deltas against p2 : ' + fmt % pcfmt(nump2, numdeltas))
2142 ui.write('deltas against other : ' + fmt % pcfmt(numother, numdeltas))
2142 ui.write('deltas against other : ' + fmt % pcfmt(numother, numdeltas))
2143
2143
2144 @command('debugrevspec', [], ('REVSPEC'))
2144 @command('debugrevspec', [], ('REVSPEC'))
2145 def debugrevspec(ui, repo, expr):
2145 def debugrevspec(ui, repo, expr):
2146 '''parse and apply a revision specification'''
2146 '''parse and apply a revision specification'''
2147 if ui.verbose:
2147 if ui.verbose:
2148 tree = revset.parse(expr)[0]
2148 tree = revset.parse(expr)[0]
2149 ui.note(tree, "\n")
2149 ui.note(tree, "\n")
2150 newtree = revset.findaliases(ui, tree)
2150 newtree = revset.findaliases(ui, tree)
2151 if newtree != tree:
2151 if newtree != tree:
2152 ui.note(newtree, "\n")
2152 ui.note(newtree, "\n")
2153 func = revset.match(ui, expr)
2153 func = revset.match(ui, expr)
2154 for c in func(repo, range(len(repo))):
2154 for c in func(repo, range(len(repo))):
2155 ui.write("%s\n" % c)
2155 ui.write("%s\n" % c)
2156
2156
2157 @command('debugsetparents', [], _('REV1 [REV2]'))
2157 @command('debugsetparents', [], _('REV1 [REV2]'))
2158 def debugsetparents(ui, repo, rev1, rev2=None):
2158 def debugsetparents(ui, repo, rev1, rev2=None):
2159 """manually set the parents of the current working directory
2159 """manually set the parents of the current working directory
2160
2160
2161 This is useful for writing repository conversion tools, but should
2161 This is useful for writing repository conversion tools, but should
2162 be used with care.
2162 be used with care.
2163
2163
2164 Returns 0 on success.
2164 Returns 0 on success.
2165 """
2165 """
2166
2166
2167 r1 = scmutil.revsingle(repo, rev1).node()
2167 r1 = scmutil.revsingle(repo, rev1).node()
2168 r2 = scmutil.revsingle(repo, rev2, 'null').node()
2168 r2 = scmutil.revsingle(repo, rev2, 'null').node()
2169
2169
2170 wlock = repo.wlock()
2170 wlock = repo.wlock()
2171 try:
2171 try:
2172 repo.dirstate.setparents(r1, r2)
2172 repo.dirstate.setparents(r1, r2)
2173 finally:
2173 finally:
2174 wlock.release()
2174 wlock.release()
2175
2175
2176 @command('debugstate',
2176 @command('debugstate',
2177 [('', 'nodates', None, _('do not display the saved mtime')),
2177 [('', 'nodates', None, _('do not display the saved mtime')),
2178 ('', 'datesort', None, _('sort by saved mtime'))],
2178 ('', 'datesort', None, _('sort by saved mtime'))],
2179 _('[OPTION]...'))
2179 _('[OPTION]...'))
2180 def debugstate(ui, repo, nodates=None, datesort=None):
2180 def debugstate(ui, repo, nodates=None, datesort=None):
2181 """show the contents of the current dirstate"""
2181 """show the contents of the current dirstate"""
2182 timestr = ""
2182 timestr = ""
2183 showdate = not nodates
2183 showdate = not nodates
2184 if datesort:
2184 if datesort:
2185 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
2185 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
2186 else:
2186 else:
2187 keyfunc = None # sort by filename
2187 keyfunc = None # sort by filename
2188 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
2188 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
2189 if showdate:
2189 if showdate:
2190 if ent[3] == -1:
2190 if ent[3] == -1:
2191 # Pad or slice to locale representation
2191 # Pad or slice to locale representation
2192 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ",
2192 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ",
2193 time.localtime(0)))
2193 time.localtime(0)))
2194 timestr = 'unset'
2194 timestr = 'unset'
2195 timestr = (timestr[:locale_len] +
2195 timestr = (timestr[:locale_len] +
2196 ' ' * (locale_len - len(timestr)))
2196 ' ' * (locale_len - len(timestr)))
2197 else:
2197 else:
2198 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
2198 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
2199 time.localtime(ent[3]))
2199 time.localtime(ent[3]))
2200 if ent[1] & 020000:
2200 if ent[1] & 020000:
2201 mode = 'lnk'
2201 mode = 'lnk'
2202 else:
2202 else:
2203 mode = '%3o' % (ent[1] & 0777 & ~util.umask)
2203 mode = '%3o' % (ent[1] & 0777 & ~util.umask)
2204 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
2204 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
2205 for f in repo.dirstate.copies():
2205 for f in repo.dirstate.copies():
2206 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
2206 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
2207
2207
2208 @command('debugsub',
2208 @command('debugsub',
2209 [('r', 'rev', '',
2209 [('r', 'rev', '',
2210 _('revision to check'), _('REV'))],
2210 _('revision to check'), _('REV'))],
2211 _('[-r REV] [REV]'))
2211 _('[-r REV] [REV]'))
2212 def debugsub(ui, repo, rev=None):
2212 def debugsub(ui, repo, rev=None):
2213 ctx = scmutil.revsingle(repo, rev, None)
2213 ctx = scmutil.revsingle(repo, rev, None)
2214 for k, v in sorted(ctx.substate.items()):
2214 for k, v in sorted(ctx.substate.items()):
2215 ui.write('path %s\n' % k)
2215 ui.write('path %s\n' % k)
2216 ui.write(' source %s\n' % v[0])
2216 ui.write(' source %s\n' % v[0])
2217 ui.write(' revision %s\n' % v[1])
2217 ui.write(' revision %s\n' % v[1])
2218
2218
2219 @command('debugwalk', walkopts, _('[OPTION]... [FILE]...'))
2219 @command('debugwalk', walkopts, _('[OPTION]... [FILE]...'))
2220 def debugwalk(ui, repo, *pats, **opts):
2220 def debugwalk(ui, repo, *pats, **opts):
2221 """show how files match on given patterns"""
2221 """show how files match on given patterns"""
2222 m = scmutil.match(repo[None], pats, opts)
2222 m = scmutil.match(repo[None], pats, opts)
2223 items = list(repo.walk(m))
2223 items = list(repo.walk(m))
2224 if not items:
2224 if not items:
2225 return
2225 return
2226 fmt = 'f %%-%ds %%-%ds %%s' % (
2226 fmt = 'f %%-%ds %%-%ds %%s' % (
2227 max([len(abs) for abs in items]),
2227 max([len(abs) for abs in items]),
2228 max([len(m.rel(abs)) for abs in items]))
2228 max([len(m.rel(abs)) for abs in items]))
2229 for abs in items:
2229 for abs in items:
2230 line = fmt % (abs, m.rel(abs), m.exact(abs) and 'exact' or '')
2230 line = fmt % (abs, m.rel(abs), m.exact(abs) and 'exact' or '')
2231 ui.write("%s\n" % line.rstrip())
2231 ui.write("%s\n" % line.rstrip())
2232
2232
2233 @command('debugwireargs',
2233 @command('debugwireargs',
2234 [('', 'three', '', 'three'),
2234 [('', 'three', '', 'three'),
2235 ('', 'four', '', 'four'),
2235 ('', 'four', '', 'four'),
2236 ('', 'five', '', 'five'),
2236 ('', 'five', '', 'five'),
2237 ] + remoteopts,
2237 ] + remoteopts,
2238 _('REPO [OPTIONS]... [ONE [TWO]]'))
2238 _('REPO [OPTIONS]... [ONE [TWO]]'))
2239 def debugwireargs(ui, repopath, *vals, **opts):
2239 def debugwireargs(ui, repopath, *vals, **opts):
2240 repo = hg.peer(ui, opts, repopath)
2240 repo = hg.peer(ui, opts, repopath)
2241 for opt in remoteopts:
2241 for opt in remoteopts:
2242 del opts[opt[1]]
2242 del opts[opt[1]]
2243 args = {}
2243 args = {}
2244 for k, v in opts.iteritems():
2244 for k, v in opts.iteritems():
2245 if v:
2245 if v:
2246 args[k] = v
2246 args[k] = v
2247 # run twice to check that we don't mess up the stream for the next command
2247 # run twice to check that we don't mess up the stream for the next command
2248 res1 = repo.debugwireargs(*vals, **args)
2248 res1 = repo.debugwireargs(*vals, **args)
2249 res2 = repo.debugwireargs(*vals, **args)
2249 res2 = repo.debugwireargs(*vals, **args)
2250 ui.write("%s\n" % res1)
2250 ui.write("%s\n" % res1)
2251 if res1 != res2:
2251 if res1 != res2:
2252 ui.warn("%s\n" % res2)
2252 ui.warn("%s\n" % res2)
2253
2253
2254 @command('^diff',
2254 @command('^diff',
2255 [('r', 'rev', [], _('revision'), _('REV')),
2255 [('r', 'rev', [], _('revision'), _('REV')),
2256 ('c', 'change', '', _('change made by revision'), _('REV'))
2256 ('c', 'change', '', _('change made by revision'), _('REV'))
2257 ] + diffopts + diffopts2 + walkopts + subrepoopts,
2257 ] + diffopts + diffopts2 + walkopts + subrepoopts,
2258 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'))
2258 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'))
2259 def diff(ui, repo, *pats, **opts):
2259 def diff(ui, repo, *pats, **opts):
2260 """diff repository (or selected files)
2260 """diff repository (or selected files)
2261
2261
2262 Show differences between revisions for the specified files.
2262 Show differences between revisions for the specified files.
2263
2263
2264 Differences between files are shown using the unified diff format.
2264 Differences between files are shown using the unified diff format.
2265
2265
2266 .. note::
2266 .. note::
2267 diff may generate unexpected results for merges, as it will
2267 diff may generate unexpected results for merges, as it will
2268 default to comparing against the working directory's first
2268 default to comparing against the working directory's first
2269 parent changeset if no revisions are specified.
2269 parent changeset if no revisions are specified.
2270
2270
2271 When two revision arguments are given, then changes are shown
2271 When two revision arguments are given, then changes are shown
2272 between those revisions. If only one revision is specified then
2272 between those revisions. If only one revision is specified then
2273 that revision is compared to the working directory, and, when no
2273 that revision is compared to the working directory, and, when no
2274 revisions are specified, the working directory files are compared
2274 revisions are specified, the working directory files are compared
2275 to its parent.
2275 to its parent.
2276
2276
2277 Alternatively you can specify -c/--change with a revision to see
2277 Alternatively you can specify -c/--change with a revision to see
2278 the changes in that changeset relative to its first parent.
2278 the changes in that changeset relative to its first parent.
2279
2279
2280 Without the -a/--text option, diff will avoid generating diffs of
2280 Without the -a/--text option, diff will avoid generating diffs of
2281 files it detects as binary. With -a, diff will generate a diff
2281 files it detects as binary. With -a, diff will generate a diff
2282 anyway, probably with undesirable results.
2282 anyway, probably with undesirable results.
2283
2283
2284 Use the -g/--git option to generate diffs in the git extended diff
2284 Use the -g/--git option to generate diffs in the git extended diff
2285 format. For more information, read :hg:`help diffs`.
2285 format. For more information, read :hg:`help diffs`.
2286
2286
2287 .. container:: verbose
2287 .. container:: verbose
2288
2288
2289 Examples:
2289 Examples:
2290
2290
2291 - compare a file in the current working directory to its parent::
2291 - compare a file in the current working directory to its parent::
2292
2292
2293 hg diff foo.c
2293 hg diff foo.c
2294
2294
2295 - compare two historical versions of a directory, with rename info::
2295 - compare two historical versions of a directory, with rename info::
2296
2296
2297 hg diff --git -r 1.0:1.2 lib/
2297 hg diff --git -r 1.0:1.2 lib/
2298
2298
2299 - get change stats relative to the last change on some date::
2299 - get change stats relative to the last change on some date::
2300
2300
2301 hg diff --stat -r "date('may 2')"
2301 hg diff --stat -r "date('may 2')"
2302
2302
2303 - diff all newly-added files that contain a keyword::
2303 - diff all newly-added files that contain a keyword::
2304
2304
2305 hg diff "set:added() and grep(GNU)"
2305 hg diff "set:added() and grep(GNU)"
2306
2306
2307 - compare a revision and its parents::
2307 - compare a revision and its parents::
2308
2308
2309 hg diff -c 9353 # compare against first parent
2309 hg diff -c 9353 # compare against first parent
2310 hg diff -r 9353^:9353 # same using revset syntax
2310 hg diff -r 9353^:9353 # same using revset syntax
2311 hg diff -r 9353^2:9353 # compare against the second parent
2311 hg diff -r 9353^2:9353 # compare against the second parent
2312
2312
2313 Returns 0 on success.
2313 Returns 0 on success.
2314 """
2314 """
2315
2315
2316 revs = opts.get('rev')
2316 revs = opts.get('rev')
2317 change = opts.get('change')
2317 change = opts.get('change')
2318 stat = opts.get('stat')
2318 stat = opts.get('stat')
2319 reverse = opts.get('reverse')
2319 reverse = opts.get('reverse')
2320
2320
2321 if revs and change:
2321 if revs and change:
2322 msg = _('cannot specify --rev and --change at the same time')
2322 msg = _('cannot specify --rev and --change at the same time')
2323 raise util.Abort(msg)
2323 raise util.Abort(msg)
2324 elif change:
2324 elif change:
2325 node2 = scmutil.revsingle(repo, change, None).node()
2325 node2 = scmutil.revsingle(repo, change, None).node()
2326 node1 = repo[node2].p1().node()
2326 node1 = repo[node2].p1().node()
2327 else:
2327 else:
2328 node1, node2 = scmutil.revpair(repo, revs)
2328 node1, node2 = scmutil.revpair(repo, revs)
2329
2329
2330 if reverse:
2330 if reverse:
2331 node1, node2 = node2, node1
2331 node1, node2 = node2, node1
2332
2332
2333 diffopts = patch.diffopts(ui, opts)
2333 diffopts = patch.diffopts(ui, opts)
2334 m = scmutil.match(repo[node2], pats, opts)
2334 m = scmutil.match(repo[node2], pats, opts)
2335 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
2335 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
2336 listsubrepos=opts.get('subrepos'))
2336 listsubrepos=opts.get('subrepos'))
2337
2337
2338 @command('^export',
2338 @command('^export',
2339 [('o', 'output', '',
2339 [('o', 'output', '',
2340 _('print output to file with formatted name'), _('FORMAT')),
2340 _('print output to file with formatted name'), _('FORMAT')),
2341 ('', 'switch-parent', None, _('diff against the second parent')),
2341 ('', 'switch-parent', None, _('diff against the second parent')),
2342 ('r', 'rev', [], _('revisions to export'), _('REV')),
2342 ('r', 'rev', [], _('revisions to export'), _('REV')),
2343 ] + diffopts,
2343 ] + diffopts,
2344 _('[OPTION]... [-o OUTFILESPEC] REV...'))
2344 _('[OPTION]... [-o OUTFILESPEC] REV...'))
2345 def export(ui, repo, *changesets, **opts):
2345 def export(ui, repo, *changesets, **opts):
2346 """dump the header and diffs for one or more changesets
2346 """dump the header and diffs for one or more changesets
2347
2347
2348 Print the changeset header and diffs for one or more revisions.
2348 Print the changeset header and diffs for one or more revisions.
2349
2349
2350 The information shown in the changeset header is: author, date,
2350 The information shown in the changeset header is: author, date,
2351 branch name (if non-default), changeset hash, parent(s) and commit
2351 branch name (if non-default), changeset hash, parent(s) and commit
2352 comment.
2352 comment.
2353
2353
2354 .. note::
2354 .. note::
2355 export may generate unexpected diff output for merge
2355 export may generate unexpected diff output for merge
2356 changesets, as it will compare the merge changeset against its
2356 changesets, as it will compare the merge changeset against its
2357 first parent only.
2357 first parent only.
2358
2358
2359 Output may be to a file, in which case the name of the file is
2359 Output may be to a file, in which case the name of the file is
2360 given using a format string. The formatting rules are as follows:
2360 given using a format string. The formatting rules are as follows:
2361
2361
2362 :``%%``: literal "%" character
2362 :``%%``: literal "%" character
2363 :``%H``: changeset hash (40 hexadecimal digits)
2363 :``%H``: changeset hash (40 hexadecimal digits)
2364 :``%N``: number of patches being generated
2364 :``%N``: number of patches being generated
2365 :``%R``: changeset revision number
2365 :``%R``: changeset revision number
2366 :``%b``: basename of the exporting repository
2366 :``%b``: basename of the exporting repository
2367 :``%h``: short-form changeset hash (12 hexadecimal digits)
2367 :``%h``: short-form changeset hash (12 hexadecimal digits)
2368 :``%m``: first line of the commit message (only alphanumeric characters)
2368 :``%m``: first line of the commit message (only alphanumeric characters)
2369 :``%n``: zero-padded sequence number, starting at 1
2369 :``%n``: zero-padded sequence number, starting at 1
2370 :``%r``: zero-padded changeset revision number
2370 :``%r``: zero-padded changeset revision number
2371
2371
2372 Without the -a/--text option, export will avoid generating diffs
2372 Without the -a/--text option, export will avoid generating diffs
2373 of files it detects as binary. With -a, export will generate a
2373 of files it detects as binary. With -a, export will generate a
2374 diff anyway, probably with undesirable results.
2374 diff anyway, probably with undesirable results.
2375
2375
2376 Use the -g/--git option to generate diffs in the git extended diff
2376 Use the -g/--git option to generate diffs in the git extended diff
2377 format. See :hg:`help diffs` for more information.
2377 format. See :hg:`help diffs` for more information.
2378
2378
2379 With the --switch-parent option, the diff will be against the
2379 With the --switch-parent option, the diff will be against the
2380 second parent. It can be useful to review a merge.
2380 second parent. It can be useful to review a merge.
2381
2381
2382 .. container:: verbose
2382 .. container:: verbose
2383
2383
2384 Examples:
2384 Examples:
2385
2385
2386 - use export and import to transplant a bugfix to the current
2386 - use export and import to transplant a bugfix to the current
2387 branch::
2387 branch::
2388
2388
2389 hg export -r 9353 | hg import -
2389 hg export -r 9353 | hg import -
2390
2390
2391 - export all the changesets between two revisions to a file with
2391 - export all the changesets between two revisions to a file with
2392 rename information::
2392 rename information::
2393
2393
2394 hg export --git -r 123:150 > changes.txt
2394 hg export --git -r 123:150 > changes.txt
2395
2395
2396 - split outgoing changes into a series of patches with
2396 - split outgoing changes into a series of patches with
2397 descriptive names::
2397 descriptive names::
2398
2398
2399 hg export -r "outgoing()" -o "%n-%m.patch"
2399 hg export -r "outgoing()" -o "%n-%m.patch"
2400
2400
2401 Returns 0 on success.
2401 Returns 0 on success.
2402 """
2402 """
2403 changesets += tuple(opts.get('rev', []))
2403 changesets += tuple(opts.get('rev', []))
2404 if not changesets:
2404 if not changesets:
2405 raise util.Abort(_("export requires at least one changeset"))
2405 raise util.Abort(_("export requires at least one changeset"))
2406 revs = scmutil.revrange(repo, changesets)
2406 revs = scmutil.revrange(repo, changesets)
2407 if len(revs) > 1:
2407 if len(revs) > 1:
2408 ui.note(_('exporting patches:\n'))
2408 ui.note(_('exporting patches:\n'))
2409 else:
2409 else:
2410 ui.note(_('exporting patch:\n'))
2410 ui.note(_('exporting patch:\n'))
2411 cmdutil.export(repo, revs, template=opts.get('output'),
2411 cmdutil.export(repo, revs, template=opts.get('output'),
2412 switch_parent=opts.get('switch_parent'),
2412 switch_parent=opts.get('switch_parent'),
2413 opts=patch.diffopts(ui, opts))
2413 opts=patch.diffopts(ui, opts))
2414
2414
2415 @command('^forget', walkopts, _('[OPTION]... FILE...'))
2415 @command('^forget', walkopts, _('[OPTION]... FILE...'))
2416 def forget(ui, repo, *pats, **opts):
2416 def forget(ui, repo, *pats, **opts):
2417 """forget the specified files on the next commit
2417 """forget the specified files on the next commit
2418
2418
2419 Mark the specified files so they will no longer be tracked
2419 Mark the specified files so they will no longer be tracked
2420 after the next commit.
2420 after the next commit.
2421
2421
2422 This only removes files from the current branch, not from the
2422 This only removes files from the current branch, not from the
2423 entire project history, and it does not delete them from the
2423 entire project history, and it does not delete them from the
2424 working directory.
2424 working directory.
2425
2425
2426 To undo a forget before the next commit, see :hg:`add`.
2426 To undo a forget before the next commit, see :hg:`add`.
2427
2427
2428 .. container:: verbose
2428 .. container:: verbose
2429
2429
2430 Examples:
2430 Examples:
2431
2431
2432 - forget newly-added binary files::
2432 - forget newly-added binary files::
2433
2433
2434 hg forget "set:added() and binary()"
2434 hg forget "set:added() and binary()"
2435
2435
2436 - forget files that would be excluded by .hgignore::
2436 - forget files that would be excluded by .hgignore::
2437
2437
2438 hg forget "set:hgignore()"
2438 hg forget "set:hgignore()"
2439
2439
2440 Returns 0 on success.
2440 Returns 0 on success.
2441 """
2441 """
2442
2442
2443 if not pats:
2443 if not pats:
2444 raise util.Abort(_('no files specified'))
2444 raise util.Abort(_('no files specified'))
2445
2445
2446 wctx = repo[None]
2446 wctx = repo[None]
2447 m = scmutil.match(wctx, pats, opts)
2447 m = scmutil.match(wctx, pats, opts)
2448 s = repo.status(match=m, clean=True)
2448 s = repo.status(match=m, clean=True)
2449 forget = sorted(s[0] + s[1] + s[3] + s[6])
2449 forget = sorted(s[0] + s[1] + s[3] + s[6])
2450 subforget = {}
2450 subforget = {}
2451 errs = 0
2451 errs = 0
2452
2452
2453 for subpath in wctx.substate:
2453 for subpath in wctx.substate:
2454 sub = wctx.sub(subpath)
2454 sub = wctx.sub(subpath)
2455 try:
2455 try:
2456 submatch = matchmod.narrowmatcher(subpath, m)
2456 submatch = matchmod.narrowmatcher(subpath, m)
2457 for fsub in sub.walk(submatch):
2457 for fsub in sub.walk(submatch):
2458 if submatch.exact(fsub):
2458 if submatch.exact(fsub):
2459 subforget[subpath + '/' + fsub] = (fsub, sub)
2459 subforget[subpath + '/' + fsub] = (fsub, sub)
2460 except error.LookupError:
2460 except error.LookupError:
2461 ui.status(_("skipping missing subrepository: %s\n") % subpath)
2461 ui.status(_("skipping missing subrepository: %s\n") % subpath)
2462
2462
2463 for f in m.files():
2463 for f in m.files():
2464 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
2464 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
2465 if f not in subforget:
2465 if f not in subforget:
2466 if os.path.exists(m.rel(f)):
2466 if os.path.exists(m.rel(f)):
2467 ui.warn(_('not removing %s: file is already untracked\n')
2467 ui.warn(_('not removing %s: file is already untracked\n')
2468 % m.rel(f))
2468 % m.rel(f))
2469 errs = 1
2469 errs = 1
2470
2470
2471 for f in forget:
2471 for f in forget:
2472 if ui.verbose or not m.exact(f):
2472 if ui.verbose or not m.exact(f):
2473 ui.status(_('removing %s\n') % m.rel(f))
2473 ui.status(_('removing %s\n') % m.rel(f))
2474
2474
2475 if ui.verbose:
2475 if ui.verbose:
2476 for f in sorted(subforget.keys()):
2476 for f in sorted(subforget.keys()):
2477 ui.status(_('removing %s\n') % m.rel(f))
2477 ui.status(_('removing %s\n') % m.rel(f))
2478
2478
2479 wctx.forget(forget)
2479 wctx.forget(forget)
2480
2480
2481 for f in sorted(subforget.keys()):
2481 for f in sorted(subforget.keys()):
2482 fsub, sub = subforget[f]
2482 fsub, sub = subforget[f]
2483 sub.forget([fsub])
2483 sub.forget([fsub])
2484
2484
2485 return errs
2485 return errs
2486
2486
2487 @command(
2487 @command(
2488 'graft',
2488 'graft',
2489 [('c', 'continue', False, _('resume interrupted graft')),
2489 [('c', 'continue', False, _('resume interrupted graft')),
2490 ('e', 'edit', False, _('invoke editor on commit messages')),
2490 ('e', 'edit', False, _('invoke editor on commit messages')),
2491 ('D', 'currentdate', False,
2491 ('D', 'currentdate', False,
2492 _('record the current date as commit date')),
2492 _('record the current date as commit date')),
2493 ('U', 'currentuser', False,
2493 ('U', 'currentuser', False,
2494 _('record the current user as committer'), _('DATE'))]
2494 _('record the current user as committer'), _('DATE'))]
2495 + commitopts2 + mergetoolopts,
2495 + commitopts2 + mergetoolopts,
2496 _('[OPTION]... REVISION...'))
2496 _('[OPTION]... REVISION...'))
2497 def graft(ui, repo, *revs, **opts):
2497 def graft(ui, repo, *revs, **opts):
2498 '''copy changes from other branches onto the current branch
2498 '''copy changes from other branches onto the current branch
2499
2499
2500 This command uses Mercurial's merge logic to copy individual
2500 This command uses Mercurial's merge logic to copy individual
2501 changes from other branches without merging branches in the
2501 changes from other branches without merging branches in the
2502 history graph. This is sometimes known as 'backporting' or
2502 history graph. This is sometimes known as 'backporting' or
2503 'cherry-picking'. By default, graft will copy user, date, and
2503 'cherry-picking'. By default, graft will copy user, date, and
2504 description from the source changesets.
2504 description from the source changesets.
2505
2505
2506 Changesets that are ancestors of the current revision, that have
2506 Changesets that are ancestors of the current revision, that have
2507 already been grafted, or that are merges will be skipped.
2507 already been grafted, or that are merges will be skipped.
2508
2508
2509 If a graft merge results in conflicts, the graft process is
2509 If a graft merge results in conflicts, the graft process is
2510 interrupted so that the current merge can be manually resolved.
2510 interrupted so that the current merge can be manually resolved.
2511 Once all conflicts are addressed, the graft process can be
2511 Once all conflicts are addressed, the graft process can be
2512 continued with the -c/--continue option.
2512 continued with the -c/--continue option.
2513
2513
2514 .. note::
2514 .. note::
2515 The -c/--continue option does not reapply earlier options.
2515 The -c/--continue option does not reapply earlier options.
2516
2516
2517 .. container:: verbose
2517 .. container:: verbose
2518
2518
2519 Examples:
2519 Examples:
2520
2520
2521 - copy a single change to the stable branch and edit its description::
2521 - copy a single change to the stable branch and edit its description::
2522
2522
2523 hg update stable
2523 hg update stable
2524 hg graft --edit 9393
2524 hg graft --edit 9393
2525
2525
2526 - graft a range of changesets with one exception, updating dates::
2526 - graft a range of changesets with one exception, updating dates::
2527
2527
2528 hg graft -D "2085::2093 and not 2091"
2528 hg graft -D "2085::2093 and not 2091"
2529
2529
2530 - continue a graft after resolving conflicts::
2530 - continue a graft after resolving conflicts::
2531
2531
2532 hg graft -c
2532 hg graft -c
2533
2533
2534 - show the source of a grafted changeset::
2534 - show the source of a grafted changeset::
2535
2535
2536 hg log --debug -r tip
2536 hg log --debug -r tip
2537
2537
2538 Returns 0 on successful completion.
2538 Returns 0 on successful completion.
2539 '''
2539 '''
2540
2540
2541 if not opts.get('user') and opts.get('currentuser'):
2541 if not opts.get('user') and opts.get('currentuser'):
2542 opts['user'] = ui.username()
2542 opts['user'] = ui.username()
2543 if not opts.get('date') and opts.get('currentdate'):
2543 if not opts.get('date') and opts.get('currentdate'):
2544 opts['date'] = "%d %d" % util.makedate()
2544 opts['date'] = "%d %d" % util.makedate()
2545
2545
2546 editor = None
2546 editor = None
2547 if opts.get('edit'):
2547 if opts.get('edit'):
2548 editor = cmdutil.commitforceeditor
2548 editor = cmdutil.commitforceeditor
2549
2549
2550 cont = False
2550 cont = False
2551 if opts['continue']:
2551 if opts['continue']:
2552 cont = True
2552 cont = True
2553 if revs:
2553 if revs:
2554 raise util.Abort(_("can't specify --continue and revisions"))
2554 raise util.Abort(_("can't specify --continue and revisions"))
2555 # read in unfinished revisions
2555 # read in unfinished revisions
2556 try:
2556 try:
2557 nodes = repo.opener.read('graftstate').splitlines()
2557 nodes = repo.opener.read('graftstate').splitlines()
2558 revs = [repo[node].rev() for node in nodes]
2558 revs = [repo[node].rev() for node in nodes]
2559 except IOError, inst:
2559 except IOError, inst:
2560 if inst.errno != errno.ENOENT:
2560 if inst.errno != errno.ENOENT:
2561 raise
2561 raise
2562 raise util.Abort(_("no graft state found, can't continue"))
2562 raise util.Abort(_("no graft state found, can't continue"))
2563 else:
2563 else:
2564 cmdutil.bailifchanged(repo)
2564 cmdutil.bailifchanged(repo)
2565 if not revs:
2565 if not revs:
2566 raise util.Abort(_('no revisions specified'))
2566 raise util.Abort(_('no revisions specified'))
2567 revs = scmutil.revrange(repo, revs)
2567 revs = scmutil.revrange(repo, revs)
2568
2568
2569 # check for merges
2569 # check for merges
2570 for rev in repo.revs('%ld and merge()', revs):
2570 for rev in repo.revs('%ld and merge()', revs):
2571 ui.warn(_('skipping ungraftable merge revision %s\n') % rev)
2571 ui.warn(_('skipping ungraftable merge revision %s\n') % rev)
2572 revs.remove(rev)
2572 revs.remove(rev)
2573 if not revs:
2573 if not revs:
2574 return -1
2574 return -1
2575
2575
2576 # check for ancestors of dest branch
2576 # check for ancestors of dest branch
2577 for rev in repo.revs('::. and %ld', revs):
2577 for rev in repo.revs('::. and %ld', revs):
2578 ui.warn(_('skipping ancestor revision %s\n') % rev)
2578 ui.warn(_('skipping ancestor revision %s\n') % rev)
2579 revs.remove(rev)
2579 revs.remove(rev)
2580 if not revs:
2580 if not revs:
2581 return -1
2581 return -1
2582
2582
2583 # analyze revs for earlier grafts
2583 # analyze revs for earlier grafts
2584 ids = {}
2584 ids = {}
2585 for ctx in repo.set("%ld", revs):
2585 for ctx in repo.set("%ld", revs):
2586 ids[ctx.hex()] = ctx.rev()
2586 ids[ctx.hex()] = ctx.rev()
2587 n = ctx.extra().get('source')
2587 n = ctx.extra().get('source')
2588 if n:
2588 if n:
2589 ids[n] = ctx.rev()
2589 ids[n] = ctx.rev()
2590
2590
2591 # check ancestors for earlier grafts
2591 # check ancestors for earlier grafts
2592 ui.debug('scanning for duplicate grafts\n')
2592 ui.debug('scanning for duplicate grafts\n')
2593 for ctx in repo.set("::. - ::%ld", revs):
2593 for ctx in repo.set("::. - ::%ld", revs):
2594 n = ctx.extra().get('source')
2594 n = ctx.extra().get('source')
2595 if n in ids:
2595 if n in ids:
2596 r = repo[n].rev()
2596 r = repo[n].rev()
2597 if r in revs:
2597 if r in revs:
2598 ui.warn(_('skipping already grafted revision %s\n') % r)
2598 ui.warn(_('skipping already grafted revision %s\n') % r)
2599 revs.remove(r)
2599 revs.remove(r)
2600 elif ids[n] in revs:
2600 elif ids[n] in revs:
2601 ui.warn(_('skipping already grafted revision %s '
2601 ui.warn(_('skipping already grafted revision %s '
2602 '(same origin %d)\n') % (ids[n], r))
2602 '(same origin %d)\n') % (ids[n], r))
2603 revs.remove(ids[n])
2603 revs.remove(ids[n])
2604 elif ctx.hex() in ids:
2604 elif ctx.hex() in ids:
2605 r = ids[ctx.hex()]
2605 r = ids[ctx.hex()]
2606 ui.warn(_('skipping already grafted revision %s '
2606 ui.warn(_('skipping already grafted revision %s '
2607 '(was grafted from %d)\n') % (r, ctx.rev()))
2607 '(was grafted from %d)\n') % (r, ctx.rev()))
2608 revs.remove(r)
2608 revs.remove(r)
2609 if not revs:
2609 if not revs:
2610 return -1
2610 return -1
2611
2611
2612 for pos, ctx in enumerate(repo.set("%ld", revs)):
2612 for pos, ctx in enumerate(repo.set("%ld", revs)):
2613 current = repo['.']
2613 current = repo['.']
2614 ui.status(_('grafting revision %s\n') % ctx.rev())
2614 ui.status(_('grafting revision %s\n') % ctx.rev())
2615
2615
2616 # we don't merge the first commit when continuing
2616 # we don't merge the first commit when continuing
2617 if not cont:
2617 if not cont:
2618 # perform the graft merge with p1(rev) as 'ancestor'
2618 # perform the graft merge with p1(rev) as 'ancestor'
2619 try:
2619 try:
2620 # ui.forcemerge is an internal variable, do not document
2620 # ui.forcemerge is an internal variable, do not document
2621 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
2621 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
2622 stats = mergemod.update(repo, ctx.node(), True, True, False,
2622 stats = mergemod.update(repo, ctx.node(), True, True, False,
2623 ctx.p1().node())
2623 ctx.p1().node())
2624 finally:
2624 finally:
2625 ui.setconfig('ui', 'forcemerge', '')
2625 ui.setconfig('ui', 'forcemerge', '')
2626 # drop the second merge parent
2626 # drop the second merge parent
2627 repo.dirstate.setparents(current.node(), nullid)
2627 repo.dirstate.setparents(current.node(), nullid)
2628 repo.dirstate.write()
2628 repo.dirstate.write()
2629 # fix up dirstate for copies and renames
2629 # fix up dirstate for copies and renames
2630 cmdutil.duplicatecopies(repo, ctx.rev(), current.node(), nullid)
2630 cmdutil.duplicatecopies(repo, ctx.rev(), current.node(), nullid)
2631 # report any conflicts
2631 # report any conflicts
2632 if stats and stats[3] > 0:
2632 if stats and stats[3] > 0:
2633 # write out state for --continue
2633 # write out state for --continue
2634 nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]]
2634 nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]]
2635 repo.opener.write('graftstate', ''.join(nodelines))
2635 repo.opener.write('graftstate', ''.join(nodelines))
2636 raise util.Abort(
2636 raise util.Abort(
2637 _("unresolved conflicts, can't continue"),
2637 _("unresolved conflicts, can't continue"),
2638 hint=_('use hg resolve and hg graft --continue'))
2638 hint=_('use hg resolve and hg graft --continue'))
2639 else:
2639 else:
2640 cont = False
2640 cont = False
2641
2641
2642 # commit
2642 # commit
2643 source = ctx.extra().get('source')
2643 source = ctx.extra().get('source')
2644 if not source:
2644 if not source:
2645 source = ctx.hex()
2645 source = ctx.hex()
2646 extra = {'source': source}
2646 extra = {'source': source}
2647 user = ctx.user()
2647 user = ctx.user()
2648 if opts.get('user'):
2648 if opts.get('user'):
2649 user = opts['user']
2649 user = opts['user']
2650 date = ctx.date()
2650 date = ctx.date()
2651 if opts.get('date'):
2651 if opts.get('date'):
2652 date = opts['date']
2652 date = opts['date']
2653 repo.commit(text=ctx.description(), user=user,
2653 repo.commit(text=ctx.description(), user=user,
2654 date=date, extra=extra, editor=editor)
2654 date=date, extra=extra, editor=editor)
2655
2655
2656 # remove state when we complete successfully
2656 # remove state when we complete successfully
2657 if os.path.exists(repo.join('graftstate')):
2657 if os.path.exists(repo.join('graftstate')):
2658 util.unlinkpath(repo.join('graftstate'))
2658 util.unlinkpath(repo.join('graftstate'))
2659
2659
2660 return 0
2660 return 0
2661
2661
2662 @command('grep',
2662 @command('grep',
2663 [('0', 'print0', None, _('end fields with NUL')),
2663 [('0', 'print0', None, _('end fields with NUL')),
2664 ('', 'all', None, _('print all revisions that match')),
2664 ('', 'all', None, _('print all revisions that match')),
2665 ('a', 'text', None, _('treat all files as text')),
2665 ('a', 'text', None, _('treat all files as text')),
2666 ('f', 'follow', None,
2666 ('f', 'follow', None,
2667 _('follow changeset history,'
2667 _('follow changeset history,'
2668 ' or file history across copies and renames')),
2668 ' or file history across copies and renames')),
2669 ('i', 'ignore-case', None, _('ignore case when matching')),
2669 ('i', 'ignore-case', None, _('ignore case when matching')),
2670 ('l', 'files-with-matches', None,
2670 ('l', 'files-with-matches', None,
2671 _('print only filenames and revisions that match')),
2671 _('print only filenames and revisions that match')),
2672 ('n', 'line-number', None, _('print matching line numbers')),
2672 ('n', 'line-number', None, _('print matching line numbers')),
2673 ('r', 'rev', [],
2673 ('r', 'rev', [],
2674 _('only search files changed within revision range'), _('REV')),
2674 _('only search files changed within revision range'), _('REV')),
2675 ('u', 'user', None, _('list the author (long with -v)')),
2675 ('u', 'user', None, _('list the author (long with -v)')),
2676 ('d', 'date', None, _('list the date (short with -q)')),
2676 ('d', 'date', None, _('list the date (short with -q)')),
2677 ] + walkopts,
2677 ] + walkopts,
2678 _('[OPTION]... PATTERN [FILE]...'))
2678 _('[OPTION]... PATTERN [FILE]...'))
2679 def grep(ui, repo, pattern, *pats, **opts):
2679 def grep(ui, repo, pattern, *pats, **opts):
2680 """search for a pattern in specified files and revisions
2680 """search for a pattern in specified files and revisions
2681
2681
2682 Search revisions of files for a regular expression.
2682 Search revisions of files for a regular expression.
2683
2683
2684 This command behaves differently than Unix grep. It only accepts
2684 This command behaves differently than Unix grep. It only accepts
2685 Python/Perl regexps. It searches repository history, not the
2685 Python/Perl regexps. It searches repository history, not the
2686 working directory. It always prints the revision number in which a
2686 working directory. It always prints the revision number in which a
2687 match appears.
2687 match appears.
2688
2688
2689 By default, grep only prints output for the first revision of a
2689 By default, grep only prints output for the first revision of a
2690 file in which it finds a match. To get it to print every revision
2690 file in which it finds a match. To get it to print every revision
2691 that contains a change in match status ("-" for a match that
2691 that contains a change in match status ("-" for a match that
2692 becomes a non-match, or "+" for a non-match that becomes a match),
2692 becomes a non-match, or "+" for a non-match that becomes a match),
2693 use the --all flag.
2693 use the --all flag.
2694
2694
2695 Returns 0 if a match is found, 1 otherwise.
2695 Returns 0 if a match is found, 1 otherwise.
2696 """
2696 """
2697 reflags = re.M
2697 reflags = re.M
2698 if opts.get('ignore_case'):
2698 if opts.get('ignore_case'):
2699 reflags |= re.I
2699 reflags |= re.I
2700 try:
2700 try:
2701 regexp = re.compile(pattern, reflags)
2701 regexp = re.compile(pattern, reflags)
2702 except re.error, inst:
2702 except re.error, inst:
2703 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
2703 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
2704 return 1
2704 return 1
2705 sep, eol = ':', '\n'
2705 sep, eol = ':', '\n'
2706 if opts.get('print0'):
2706 if opts.get('print0'):
2707 sep = eol = '\0'
2707 sep = eol = '\0'
2708
2708
2709 getfile = util.lrucachefunc(repo.file)
2709 getfile = util.lrucachefunc(repo.file)
2710
2710
2711 def matchlines(body):
2711 def matchlines(body):
2712 begin = 0
2712 begin = 0
2713 linenum = 0
2713 linenum = 0
2714 while True:
2714 while True:
2715 match = regexp.search(body, begin)
2715 match = regexp.search(body, begin)
2716 if not match:
2716 if not match:
2717 break
2717 break
2718 mstart, mend = match.span()
2718 mstart, mend = match.span()
2719 linenum += body.count('\n', begin, mstart) + 1
2719 linenum += body.count('\n', begin, mstart) + 1
2720 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2720 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2721 begin = body.find('\n', mend) + 1 or len(body) + 1
2721 begin = body.find('\n', mend) + 1 or len(body) + 1
2722 lend = begin - 1
2722 lend = begin - 1
2723 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2723 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2724
2724
2725 class linestate(object):
2725 class linestate(object):
2726 def __init__(self, line, linenum, colstart, colend):
2726 def __init__(self, line, linenum, colstart, colend):
2727 self.line = line
2727 self.line = line
2728 self.linenum = linenum
2728 self.linenum = linenum
2729 self.colstart = colstart
2729 self.colstart = colstart
2730 self.colend = colend
2730 self.colend = colend
2731
2731
2732 def __hash__(self):
2732 def __hash__(self):
2733 return hash((self.linenum, self.line))
2733 return hash((self.linenum, self.line))
2734
2734
2735 def __eq__(self, other):
2735 def __eq__(self, other):
2736 return self.line == other.line
2736 return self.line == other.line
2737
2737
2738 matches = {}
2738 matches = {}
2739 copies = {}
2739 copies = {}
2740 def grepbody(fn, rev, body):
2740 def grepbody(fn, rev, body):
2741 matches[rev].setdefault(fn, [])
2741 matches[rev].setdefault(fn, [])
2742 m = matches[rev][fn]
2742 m = matches[rev][fn]
2743 for lnum, cstart, cend, line in matchlines(body):
2743 for lnum, cstart, cend, line in matchlines(body):
2744 s = linestate(line, lnum, cstart, cend)
2744 s = linestate(line, lnum, cstart, cend)
2745 m.append(s)
2745 m.append(s)
2746
2746
2747 def difflinestates(a, b):
2747 def difflinestates(a, b):
2748 sm = difflib.SequenceMatcher(None, a, b)
2748 sm = difflib.SequenceMatcher(None, a, b)
2749 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2749 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2750 if tag == 'insert':
2750 if tag == 'insert':
2751 for i in xrange(blo, bhi):
2751 for i in xrange(blo, bhi):
2752 yield ('+', b[i])
2752 yield ('+', b[i])
2753 elif tag == 'delete':
2753 elif tag == 'delete':
2754 for i in xrange(alo, ahi):
2754 for i in xrange(alo, ahi):
2755 yield ('-', a[i])
2755 yield ('-', a[i])
2756 elif tag == 'replace':
2756 elif tag == 'replace':
2757 for i in xrange(alo, ahi):
2757 for i in xrange(alo, ahi):
2758 yield ('-', a[i])
2758 yield ('-', a[i])
2759 for i in xrange(blo, bhi):
2759 for i in xrange(blo, bhi):
2760 yield ('+', b[i])
2760 yield ('+', b[i])
2761
2761
2762 def display(fn, ctx, pstates, states):
2762 def display(fn, ctx, pstates, states):
2763 rev = ctx.rev()
2763 rev = ctx.rev()
2764 datefunc = ui.quiet and util.shortdate or util.datestr
2764 datefunc = ui.quiet and util.shortdate or util.datestr
2765 found = False
2765 found = False
2766 filerevmatches = {}
2766 filerevmatches = {}
2767 def binary():
2767 def binary():
2768 flog = getfile(fn)
2768 flog = getfile(fn)
2769 return util.binary(flog.read(ctx.filenode(fn)))
2769 return util.binary(flog.read(ctx.filenode(fn)))
2770
2770
2771 if opts.get('all'):
2771 if opts.get('all'):
2772 iter = difflinestates(pstates, states)
2772 iter = difflinestates(pstates, states)
2773 else:
2773 else:
2774 iter = [('', l) for l in states]
2774 iter = [('', l) for l in states]
2775 for change, l in iter:
2775 for change, l in iter:
2776 cols = [fn, str(rev)]
2776 cols = [fn, str(rev)]
2777 before, match, after = None, None, None
2777 before, match, after = None, None, None
2778 if opts.get('line_number'):
2778 if opts.get('line_number'):
2779 cols.append(str(l.linenum))
2779 cols.append(str(l.linenum))
2780 if opts.get('all'):
2780 if opts.get('all'):
2781 cols.append(change)
2781 cols.append(change)
2782 if opts.get('user'):
2782 if opts.get('user'):
2783 cols.append(ui.shortuser(ctx.user()))
2783 cols.append(ui.shortuser(ctx.user()))
2784 if opts.get('date'):
2784 if opts.get('date'):
2785 cols.append(datefunc(ctx.date()))
2785 cols.append(datefunc(ctx.date()))
2786 if opts.get('files_with_matches'):
2786 if opts.get('files_with_matches'):
2787 c = (fn, rev)
2787 c = (fn, rev)
2788 if c in filerevmatches:
2788 if c in filerevmatches:
2789 continue
2789 continue
2790 filerevmatches[c] = 1
2790 filerevmatches[c] = 1
2791 else:
2791 else:
2792 before = l.line[:l.colstart]
2792 before = l.line[:l.colstart]
2793 match = l.line[l.colstart:l.colend]
2793 match = l.line[l.colstart:l.colend]
2794 after = l.line[l.colend:]
2794 after = l.line[l.colend:]
2795 ui.write(sep.join(cols))
2795 ui.write(sep.join(cols))
2796 if before is not None:
2796 if before is not None:
2797 if not opts.get('text') and binary():
2797 if not opts.get('text') and binary():
2798 ui.write(sep + " Binary file matches")
2798 ui.write(sep + " Binary file matches")
2799 else:
2799 else:
2800 ui.write(sep + before)
2800 ui.write(sep + before)
2801 ui.write(match, label='grep.match')
2801 ui.write(match, label='grep.match')
2802 ui.write(after)
2802 ui.write(after)
2803 ui.write(eol)
2803 ui.write(eol)
2804 found = True
2804 found = True
2805 return found
2805 return found
2806
2806
2807 skip = {}
2807 skip = {}
2808 revfiles = {}
2808 revfiles = {}
2809 matchfn = scmutil.match(repo[None], pats, opts)
2809 matchfn = scmutil.match(repo[None], pats, opts)
2810 found = False
2810 found = False
2811 follow = opts.get('follow')
2811 follow = opts.get('follow')
2812
2812
2813 def prep(ctx, fns):
2813 def prep(ctx, fns):
2814 rev = ctx.rev()
2814 rev = ctx.rev()
2815 pctx = ctx.p1()
2815 pctx = ctx.p1()
2816 parent = pctx.rev()
2816 parent = pctx.rev()
2817 matches.setdefault(rev, {})
2817 matches.setdefault(rev, {})
2818 matches.setdefault(parent, {})
2818 matches.setdefault(parent, {})
2819 files = revfiles.setdefault(rev, [])
2819 files = revfiles.setdefault(rev, [])
2820 for fn in fns:
2820 for fn in fns:
2821 flog = getfile(fn)
2821 flog = getfile(fn)
2822 try:
2822 try:
2823 fnode = ctx.filenode(fn)
2823 fnode = ctx.filenode(fn)
2824 except error.LookupError:
2824 except error.LookupError:
2825 continue
2825 continue
2826
2826
2827 copied = flog.renamed(fnode)
2827 copied = flog.renamed(fnode)
2828 copy = follow and copied and copied[0]
2828 copy = follow and copied and copied[0]
2829 if copy:
2829 if copy:
2830 copies.setdefault(rev, {})[fn] = copy
2830 copies.setdefault(rev, {})[fn] = copy
2831 if fn in skip:
2831 if fn in skip:
2832 if copy:
2832 if copy:
2833 skip[copy] = True
2833 skip[copy] = True
2834 continue
2834 continue
2835 files.append(fn)
2835 files.append(fn)
2836
2836
2837 if fn not in matches[rev]:
2837 if fn not in matches[rev]:
2838 grepbody(fn, rev, flog.read(fnode))
2838 grepbody(fn, rev, flog.read(fnode))
2839
2839
2840 pfn = copy or fn
2840 pfn = copy or fn
2841 if pfn not in matches[parent]:
2841 if pfn not in matches[parent]:
2842 try:
2842 try:
2843 fnode = pctx.filenode(pfn)
2843 fnode = pctx.filenode(pfn)
2844 grepbody(pfn, parent, flog.read(fnode))
2844 grepbody(pfn, parent, flog.read(fnode))
2845 except error.LookupError:
2845 except error.LookupError:
2846 pass
2846 pass
2847
2847
2848 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
2848 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
2849 rev = ctx.rev()
2849 rev = ctx.rev()
2850 parent = ctx.p1().rev()
2850 parent = ctx.p1().rev()
2851 for fn in sorted(revfiles.get(rev, [])):
2851 for fn in sorted(revfiles.get(rev, [])):
2852 states = matches[rev][fn]
2852 states = matches[rev][fn]
2853 copy = copies.get(rev, {}).get(fn)
2853 copy = copies.get(rev, {}).get(fn)
2854 if fn in skip:
2854 if fn in skip:
2855 if copy:
2855 if copy:
2856 skip[copy] = True
2856 skip[copy] = True
2857 continue
2857 continue
2858 pstates = matches.get(parent, {}).get(copy or fn, [])
2858 pstates = matches.get(parent, {}).get(copy or fn, [])
2859 if pstates or states:
2859 if pstates or states:
2860 r = display(fn, ctx, pstates, states)
2860 r = display(fn, ctx, pstates, states)
2861 found = found or r
2861 found = found or r
2862 if r and not opts.get('all'):
2862 if r and not opts.get('all'):
2863 skip[fn] = True
2863 skip[fn] = True
2864 if copy:
2864 if copy:
2865 skip[copy] = True
2865 skip[copy] = True
2866 del matches[rev]
2866 del matches[rev]
2867 del revfiles[rev]
2867 del revfiles[rev]
2868
2868
2869 return not found
2869 return not found
2870
2870
2871 @command('heads',
2871 @command('heads',
2872 [('r', 'rev', '',
2872 [('r', 'rev', '',
2873 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
2873 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
2874 ('t', 'topo', False, _('show topological heads only')),
2874 ('t', 'topo', False, _('show topological heads only')),
2875 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
2875 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
2876 ('c', 'closed', False, _('show normal and closed branch heads')),
2876 ('c', 'closed', False, _('show normal and closed branch heads')),
2877 ] + templateopts,
2877 ] + templateopts,
2878 _('[-ac] [-r STARTREV] [REV]...'))
2878 _('[-ac] [-r STARTREV] [REV]...'))
2879 def heads(ui, repo, *branchrevs, **opts):
2879 def heads(ui, repo, *branchrevs, **opts):
2880 """show current repository heads or show branch heads
2880 """show current repository heads or show branch heads
2881
2881
2882 With no arguments, show all repository branch heads.
2882 With no arguments, show all repository branch heads.
2883
2883
2884 Repository "heads" are changesets with no child changesets. They are
2884 Repository "heads" are changesets with no child changesets. They are
2885 where development generally takes place and are the usual targets
2885 where development generally takes place and are the usual targets
2886 for update and merge operations. Branch heads are changesets that have
2886 for update and merge operations. Branch heads are changesets that have
2887 no child changeset on the same branch.
2887 no child changeset on the same branch.
2888
2888
2889 If one or more REVs are given, only branch heads on the branches
2889 If one or more REVs are given, only branch heads on the branches
2890 associated with the specified changesets are shown. This means
2890 associated with the specified changesets are shown. This means
2891 that you can use :hg:`heads foo` to see the heads on a branch
2891 that you can use :hg:`heads foo` to see the heads on a branch
2892 named ``foo``.
2892 named ``foo``.
2893
2893
2894 If -c/--closed is specified, also show branch heads marked closed
2894 If -c/--closed is specified, also show branch heads marked closed
2895 (see :hg:`commit --close-branch`).
2895 (see :hg:`commit --close-branch`).
2896
2896
2897 If STARTREV is specified, only those heads that are descendants of
2897 If STARTREV is specified, only those heads that are descendants of
2898 STARTREV will be displayed.
2898 STARTREV will be displayed.
2899
2899
2900 If -t/--topo is specified, named branch mechanics will be ignored and only
2900 If -t/--topo is specified, named branch mechanics will be ignored and only
2901 changesets without children will be shown.
2901 changesets without children will be shown.
2902
2902
2903 Returns 0 if matching heads are found, 1 if not.
2903 Returns 0 if matching heads are found, 1 if not.
2904 """
2904 """
2905
2905
2906 start = None
2906 start = None
2907 if 'rev' in opts:
2907 if 'rev' in opts:
2908 start = scmutil.revsingle(repo, opts['rev'], None).node()
2908 start = scmutil.revsingle(repo, opts['rev'], None).node()
2909
2909
2910 if opts.get('topo'):
2910 if opts.get('topo'):
2911 heads = [repo[h] for h in repo.heads(start)]
2911 heads = [repo[h] for h in repo.heads(start)]
2912 else:
2912 else:
2913 heads = []
2913 heads = []
2914 for branch in repo.branchmap():
2914 for branch in repo.branchmap():
2915 heads += repo.branchheads(branch, start, opts.get('closed'))
2915 heads += repo.branchheads(branch, start, opts.get('closed'))
2916 heads = [repo[h] for h in heads]
2916 heads = [repo[h] for h in heads]
2917
2917
2918 if branchrevs:
2918 if branchrevs:
2919 branches = set(repo[br].branch() for br in branchrevs)
2919 branches = set(repo[br].branch() for br in branchrevs)
2920 heads = [h for h in heads if h.branch() in branches]
2920 heads = [h for h in heads if h.branch() in branches]
2921
2921
2922 if opts.get('active') and branchrevs:
2922 if opts.get('active') and branchrevs:
2923 dagheads = repo.heads(start)
2923 dagheads = repo.heads(start)
2924 heads = [h for h in heads if h.node() in dagheads]
2924 heads = [h for h in heads if h.node() in dagheads]
2925
2925
2926 if branchrevs:
2926 if branchrevs:
2927 haveheads = set(h.branch() for h in heads)
2927 haveheads = set(h.branch() for h in heads)
2928 if branches - haveheads:
2928 if branches - haveheads:
2929 headless = ', '.join(b for b in branches - haveheads)
2929 headless = ', '.join(b for b in branches - haveheads)
2930 msg = _('no open branch heads found on branches %s')
2930 msg = _('no open branch heads found on branches %s')
2931 if opts.get('rev'):
2931 if opts.get('rev'):
2932 msg += _(' (started at %s)' % opts['rev'])
2932 msg += _(' (started at %s)' % opts['rev'])
2933 ui.warn((msg + '\n') % headless)
2933 ui.warn((msg + '\n') % headless)
2934
2934
2935 if not heads:
2935 if not heads:
2936 return 1
2936 return 1
2937
2937
2938 heads = sorted(heads, key=lambda x: -x.rev())
2938 heads = sorted(heads, key=lambda x: -x.rev())
2939 displayer = cmdutil.show_changeset(ui, repo, opts)
2939 displayer = cmdutil.show_changeset(ui, repo, opts)
2940 for ctx in heads:
2940 for ctx in heads:
2941 displayer.show(ctx)
2941 displayer.show(ctx)
2942 displayer.close()
2942 displayer.close()
2943
2943
2944 @command('help',
2944 @command('help',
2945 [('e', 'extension', None, _('show only help for extensions')),
2945 [('e', 'extension', None, _('show only help for extensions')),
2946 ('c', 'command', None, _('show only help for commands'))],
2946 ('c', 'command', None, _('show only help for commands'))],
2947 _('[-ec] [TOPIC]'))
2947 _('[-ec] [TOPIC]'))
2948 def help_(ui, name=None, unknowncmd=False, full=True, **opts):
2948 def help_(ui, name=None, unknowncmd=False, full=True, **opts):
2949 """show help for a given topic or a help overview
2949 """show help for a given topic or a help overview
2950
2950
2951 With no arguments, print a list of commands with short help messages.
2951 With no arguments, print a list of commands with short help messages.
2952
2952
2953 Given a topic, extension, or command name, print help for that
2953 Given a topic, extension, or command name, print help for that
2954 topic.
2954 topic.
2955
2955
2956 Returns 0 if successful.
2956 Returns 0 if successful.
2957 """
2957 """
2958
2958
2959 textwidth = min(ui.termwidth(), 80) - 2
2959 textwidth = min(ui.termwidth(), 80) - 2
2960
2960
2961 def optrst(options):
2961 def optrst(options):
2962 data = []
2962 data = []
2963 multioccur = False
2963 multioccur = False
2964 for option in options:
2964 for option in options:
2965 if len(option) == 5:
2965 if len(option) == 5:
2966 shortopt, longopt, default, desc, optlabel = option
2966 shortopt, longopt, default, desc, optlabel = option
2967 else:
2967 else:
2968 shortopt, longopt, default, desc = option
2968 shortopt, longopt, default, desc = option
2969 optlabel = _("VALUE") # default label
2969 optlabel = _("VALUE") # default label
2970
2970
2971 if _("DEPRECATED") in desc and not ui.verbose:
2971 if _("DEPRECATED") in desc and not ui.verbose:
2972 continue
2972 continue
2973
2973
2974 so = ''
2974 so = ''
2975 if shortopt:
2975 if shortopt:
2976 so = '-' + shortopt
2976 so = '-' + shortopt
2977 lo = '--' + longopt
2977 lo = '--' + longopt
2978 if default:
2978 if default:
2979 desc += _(" (default: %s)") % default
2979 desc += _(" (default: %s)") % default
2980
2980
2981 if isinstance(default, list):
2981 if isinstance(default, list):
2982 lo += " %s [+]" % optlabel
2982 lo += " %s [+]" % optlabel
2983 multioccur = True
2983 multioccur = True
2984 elif (default is not None) and not isinstance(default, bool):
2984 elif (default is not None) and not isinstance(default, bool):
2985 lo += " %s" % optlabel
2985 lo += " %s" % optlabel
2986
2986
2987 data.append((so, lo, desc))
2987 data.append((so, lo, desc))
2988
2988
2989 rst = minirst.maketable(data, 1)
2989 rst = minirst.maketable(data, 1)
2990
2990
2991 if multioccur:
2991 if multioccur:
2992 rst += _("\n[+] marked option can be specified multiple times\n")
2992 rst += _("\n[+] marked option can be specified multiple times\n")
2993
2993
2994 return rst
2994 return rst
2995
2995
2996 # list all option lists
2996 # list all option lists
2997 def opttext(optlist, width):
2997 def opttext(optlist, width):
2998 rst = ''
2998 rst = ''
2999 if not optlist:
2999 if not optlist:
3000 return ''
3000 return ''
3001
3001
3002 for title, options in optlist:
3002 for title, options in optlist:
3003 rst += '\n%s\n' % title
3003 rst += '\n%s\n' % title
3004 if options:
3004 if options:
3005 rst += "\n"
3005 rst += "\n"
3006 rst += optrst(options)
3006 rst += optrst(options)
3007 rst += '\n'
3007 rst += '\n'
3008
3008
3009 return '\n' + minirst.format(rst, width)
3009 return '\n' + minirst.format(rst, width)
3010
3010
3011 def addglobalopts(optlist, aliases):
3011 def addglobalopts(optlist, aliases):
3012 if ui.quiet:
3012 if ui.quiet:
3013 return []
3013 return []
3014
3014
3015 if ui.verbose:
3015 if ui.verbose:
3016 optlist.append((_("global options:"), globalopts))
3016 optlist.append((_("global options:"), globalopts))
3017 if name == 'shortlist':
3017 if name == 'shortlist':
3018 optlist.append((_('use "hg help" for the full list '
3018 optlist.append((_('use "hg help" for the full list '
3019 'of commands'), ()))
3019 'of commands'), ()))
3020 else:
3020 else:
3021 if name == 'shortlist':
3021 if name == 'shortlist':
3022 msg = _('use "hg help" for the full list of commands '
3022 msg = _('use "hg help" for the full list of commands '
3023 'or "hg -v" for details')
3023 'or "hg -v" for details')
3024 elif name and not full:
3024 elif name and not full:
3025 msg = _('use "hg help %s" to show the full help text' % name)
3025 msg = _('use "hg help %s" to show the full help text' % name)
3026 elif aliases:
3026 elif aliases:
3027 msg = _('use "hg -v help%s" to show builtin aliases and '
3027 msg = _('use "hg -v help%s" to show builtin aliases and '
3028 'global options') % (name and " " + name or "")
3028 'global options') % (name and " " + name or "")
3029 else:
3029 else:
3030 msg = _('use "hg -v help %s" to show more info') % name
3030 msg = _('use "hg -v help %s" to show more info') % name
3031 optlist.append((msg, ()))
3031 optlist.append((msg, ()))
3032
3032
3033 def helpcmd(name):
3033 def helpcmd(name):
3034 try:
3034 try:
3035 aliases, entry = cmdutil.findcmd(name, table, strict=unknowncmd)
3035 aliases, entry = cmdutil.findcmd(name, table, strict=unknowncmd)
3036 except error.AmbiguousCommand, inst:
3036 except error.AmbiguousCommand, inst:
3037 # py3k fix: except vars can't be used outside the scope of the
3037 # py3k fix: except vars can't be used outside the scope of the
3038 # except block, nor can be used inside a lambda. python issue4617
3038 # except block, nor can be used inside a lambda. python issue4617
3039 prefix = inst.args[0]
3039 prefix = inst.args[0]
3040 select = lambda c: c.lstrip('^').startswith(prefix)
3040 select = lambda c: c.lstrip('^').startswith(prefix)
3041 helplist(select)
3041 helplist(select)
3042 return
3042 return
3043
3043
3044 # check if it's an invalid alias and display its error if it is
3044 # check if it's an invalid alias and display its error if it is
3045 if getattr(entry[0], 'badalias', False):
3045 if getattr(entry[0], 'badalias', False):
3046 if not unknowncmd:
3046 if not unknowncmd:
3047 entry[0](ui)
3047 entry[0](ui)
3048 return
3048 return
3049
3049
3050 rst = ""
3050 rst = ""
3051
3051
3052 # synopsis
3052 # synopsis
3053 if len(entry) > 2:
3053 if len(entry) > 2:
3054 if entry[2].startswith('hg'):
3054 if entry[2].startswith('hg'):
3055 rst += "%s\n" % entry[2]
3055 rst += "%s\n" % entry[2]
3056 else:
3056 else:
3057 rst += 'hg %s %s\n' % (aliases[0], entry[2])
3057 rst += 'hg %s %s\n' % (aliases[0], entry[2])
3058 else:
3058 else:
3059 rst += 'hg %s\n' % aliases[0]
3059 rst += 'hg %s\n' % aliases[0]
3060
3060
3061 # aliases
3061 # aliases
3062 if full and not ui.quiet and len(aliases) > 1:
3062 if full and not ui.quiet and len(aliases) > 1:
3063 rst += _("\naliases: %s\n") % ', '.join(aliases[1:])
3063 rst += _("\naliases: %s\n") % ', '.join(aliases[1:])
3064
3064
3065 # description
3065 # description
3066 doc = gettext(entry[0].__doc__)
3066 doc = gettext(entry[0].__doc__)
3067 if not doc:
3067 if not doc:
3068 doc = _("(no help text available)")
3068 doc = _("(no help text available)")
3069 if util.safehasattr(entry[0], 'definition'): # aliased command
3069 if util.safehasattr(entry[0], 'definition'): # aliased command
3070 if entry[0].definition.startswith('!'): # shell alias
3070 if entry[0].definition.startswith('!'): # shell alias
3071 doc = _('shell alias for::\n\n %s') % entry[0].definition[1:]
3071 doc = _('shell alias for::\n\n %s') % entry[0].definition[1:]
3072 else:
3072 else:
3073 doc = _('alias for: hg %s\n\n%s') % (entry[0].definition, doc)
3073 doc = _('alias for: hg %s\n\n%s') % (entry[0].definition, doc)
3074 if ui.quiet or not full:
3074 if ui.quiet or not full:
3075 doc = doc.splitlines()[0]
3075 doc = doc.splitlines()[0]
3076 rst += "\n" + doc + "\n"
3076 rst += "\n" + doc + "\n"
3077
3077
3078 # check if this command shadows a non-trivial (multi-line)
3078 # check if this command shadows a non-trivial (multi-line)
3079 # extension help text
3079 # extension help text
3080 try:
3080 try:
3081 mod = extensions.find(name)
3081 mod = extensions.find(name)
3082 doc = gettext(mod.__doc__) or ''
3082 doc = gettext(mod.__doc__) or ''
3083 if '\n' in doc.strip():
3083 if '\n' in doc.strip():
3084 msg = _('use "hg help -e %s" to show help for '
3084 msg = _('use "hg help -e %s" to show help for '
3085 'the %s extension') % (name, name)
3085 'the %s extension') % (name, name)
3086 rst += '\n%s\n' % msg
3086 rst += '\n%s\n' % msg
3087 except KeyError:
3087 except KeyError:
3088 pass
3088 pass
3089
3089
3090 # options
3090 # options
3091 if not ui.quiet and entry[1]:
3091 if not ui.quiet and entry[1]:
3092 rst += '\noptions:\n\n'
3092 rst += '\noptions:\n\n'
3093 rst += optrst(entry[1])
3093 rst += optrst(entry[1])
3094
3094
3095 if ui.verbose:
3095 if ui.verbose:
3096 rst += '\nglobal options:\n\n'
3096 rst += '\nglobal options:\n\n'
3097 rst += optrst(globalopts)
3097 rst += optrst(globalopts)
3098
3098
3099 keep = ui.verbose and ['verbose'] or []
3099 keep = ui.verbose and ['verbose'] or []
3100 formatted, pruned = minirst.format(rst, textwidth, keep=keep)
3100 formatted, pruned = minirst.format(rst, textwidth, keep=keep)
3101 ui.write(formatted)
3101 ui.write(formatted)
3102
3102
3103 if not ui.verbose:
3103 if not ui.verbose:
3104 if not full:
3104 if not full:
3105 ui.write(_('\nuse "hg help %s" to show the full help text\n')
3105 ui.write(_('\nuse "hg help %s" to show the full help text\n')
3106 % name)
3106 % name)
3107 elif not ui.quiet:
3107 elif not ui.quiet:
3108 ui.write(_('\nuse "hg -v help %s" to show more info\n') % name)
3108 ui.write(_('\nuse "hg -v help %s" to show more info\n') % name)
3109
3109
3110
3110
3111 def helplist(select=None):
3111 def helplist(select=None):
3112 # list of commands
3112 # list of commands
3113 if name == "shortlist":
3113 if name == "shortlist":
3114 header = _('basic commands:\n\n')
3114 header = _('basic commands:\n\n')
3115 else:
3115 else:
3116 header = _('list of commands:\n\n')
3116 header = _('list of commands:\n\n')
3117
3117
3118 h = {}
3118 h = {}
3119 cmds = {}
3119 cmds = {}
3120 for c, e in table.iteritems():
3120 for c, e in table.iteritems():
3121 f = c.split("|", 1)[0]
3121 f = c.split("|", 1)[0]
3122 if select and not select(f):
3122 if select and not select(f):
3123 continue
3123 continue
3124 if (not select and name != 'shortlist' and
3124 if (not select and name != 'shortlist' and
3125 e[0].__module__ != __name__):
3125 e[0].__module__ != __name__):
3126 continue
3126 continue
3127 if name == "shortlist" and not f.startswith("^"):
3127 if name == "shortlist" and not f.startswith("^"):
3128 continue
3128 continue
3129 f = f.lstrip("^")
3129 f = f.lstrip("^")
3130 if not ui.debugflag and f.startswith("debug"):
3130 if not ui.debugflag and f.startswith("debug"):
3131 continue
3131 continue
3132 doc = e[0].__doc__
3132 doc = e[0].__doc__
3133 if doc and 'DEPRECATED' in doc and not ui.verbose:
3133 if doc and 'DEPRECATED' in doc and not ui.verbose:
3134 continue
3134 continue
3135 doc = gettext(doc)
3135 doc = gettext(doc)
3136 if not doc:
3136 if not doc:
3137 doc = _("(no help text available)")
3137 doc = _("(no help text available)")
3138 h[f] = doc.splitlines()[0].rstrip()
3138 h[f] = doc.splitlines()[0].rstrip()
3139 cmds[f] = c.lstrip("^")
3139 cmds[f] = c.lstrip("^")
3140
3140
3141 if not h:
3141 if not h:
3142 ui.status(_('no commands defined\n'))
3142 ui.status(_('no commands defined\n'))
3143 return
3143 return
3144
3144
3145 ui.status(header)
3145 ui.status(header)
3146 fns = sorted(h)
3146 fns = sorted(h)
3147 m = max(map(len, fns))
3147 m = max(map(len, fns))
3148 for f in fns:
3148 for f in fns:
3149 if ui.verbose:
3149 if ui.verbose:
3150 commands = cmds[f].replace("|",", ")
3150 commands = cmds[f].replace("|",", ")
3151 ui.write(" %s:\n %s\n"%(commands, h[f]))
3151 ui.write(" %s:\n %s\n"%(commands, h[f]))
3152 else:
3152 else:
3153 ui.write('%s\n' % (util.wrap(h[f], textwidth,
3153 ui.write('%s\n' % (util.wrap(h[f], textwidth,
3154 initindent=' %-*s ' % (m, f),
3154 initindent=' %-*s ' % (m, f),
3155 hangindent=' ' * (m + 4))))
3155 hangindent=' ' * (m + 4))))
3156
3156
3157 if not name:
3157 if not name:
3158 text = help.listexts(_('enabled extensions:'), extensions.enabled())
3158 text = help.listexts(_('enabled extensions:'), extensions.enabled())
3159 if text:
3159 if text:
3160 ui.write("\n%s" % minirst.format(text, textwidth))
3160 ui.write("\n%s" % minirst.format(text, textwidth))
3161
3161
3162 ui.write(_("\nadditional help topics:\n\n"))
3162 ui.write(_("\nadditional help topics:\n\n"))
3163 topics = []
3163 topics = []
3164 for names, header, doc in help.helptable:
3164 for names, header, doc in help.helptable:
3165 topics.append((sorted(names, key=len, reverse=True)[0], header))
3165 topics.append((sorted(names, key=len, reverse=True)[0], header))
3166 topics_len = max([len(s[0]) for s in topics])
3166 topics_len = max([len(s[0]) for s in topics])
3167 for t, desc in topics:
3167 for t, desc in topics:
3168 ui.write(" %-*s %s\n" % (topics_len, t, desc))
3168 ui.write(" %-*s %s\n" % (topics_len, t, desc))
3169
3169
3170 optlist = []
3170 optlist = []
3171 addglobalopts(optlist, True)
3171 addglobalopts(optlist, True)
3172 ui.write(opttext(optlist, textwidth))
3172 ui.write(opttext(optlist, textwidth))
3173
3173
3174 def helptopic(name):
3174 def helptopic(name):
3175 for names, header, doc in help.helptable:
3175 for names, header, doc in help.helptable:
3176 if name in names:
3176 if name in names:
3177 break
3177 break
3178 else:
3178 else:
3179 raise error.UnknownCommand(name)
3179 raise error.UnknownCommand(name)
3180
3180
3181 # description
3181 # description
3182 if not doc:
3182 if not doc:
3183 doc = _("(no help text available)")
3183 doc = _("(no help text available)")
3184 if util.safehasattr(doc, '__call__'):
3184 if util.safehasattr(doc, '__call__'):
3185 doc = doc()
3185 doc = doc()
3186
3186
3187 ui.write("%s\n\n" % header)
3187 ui.write("%s\n\n" % header)
3188 ui.write("%s" % minirst.format(doc, textwidth, indent=4))
3188 ui.write("%s" % minirst.format(doc, textwidth, indent=4))
3189 try:
3189 try:
3190 cmdutil.findcmd(name, table)
3190 cmdutil.findcmd(name, table)
3191 ui.write(_('\nuse "hg help -c %s" to see help for '
3191 ui.write(_('\nuse "hg help -c %s" to see help for '
3192 'the %s command\n') % (name, name))
3192 'the %s command\n') % (name, name))
3193 except error.UnknownCommand:
3193 except error.UnknownCommand:
3194 pass
3194 pass
3195
3195
3196 def helpext(name):
3196 def helpext(name):
3197 try:
3197 try:
3198 mod = extensions.find(name)
3198 mod = extensions.find(name)
3199 doc = gettext(mod.__doc__) or _('no help text available')
3199 doc = gettext(mod.__doc__) or _('no help text available')
3200 except KeyError:
3200 except KeyError:
3201 mod = None
3201 mod = None
3202 doc = extensions.disabledext(name)
3202 doc = extensions.disabledext(name)
3203 if not doc:
3203 if not doc:
3204 raise error.UnknownCommand(name)
3204 raise error.UnknownCommand(name)
3205
3205
3206 if '\n' not in doc:
3206 if '\n' not in doc:
3207 head, tail = doc, ""
3207 head, tail = doc, ""
3208 else:
3208 else:
3209 head, tail = doc.split('\n', 1)
3209 head, tail = doc.split('\n', 1)
3210 ui.write(_('%s extension - %s\n\n') % (name.split('.')[-1], head))
3210 ui.write(_('%s extension - %s\n\n') % (name.split('.')[-1], head))
3211 if tail:
3211 if tail:
3212 ui.write(minirst.format(tail, textwidth))
3212 ui.write(minirst.format(tail, textwidth))
3213 ui.status('\n')
3213 ui.status('\n')
3214
3214
3215 if mod:
3215 if mod:
3216 try:
3216 try:
3217 ct = mod.cmdtable
3217 ct = mod.cmdtable
3218 except AttributeError:
3218 except AttributeError:
3219 ct = {}
3219 ct = {}
3220 modcmds = set([c.split('|', 1)[0] for c in ct])
3220 modcmds = set([c.split('|', 1)[0] for c in ct])
3221 helplist(modcmds.__contains__)
3221 helplist(modcmds.__contains__)
3222 else:
3222 else:
3223 ui.write(_('use "hg help extensions" for information on enabling '
3223 ui.write(_('use "hg help extensions" for information on enabling '
3224 'extensions\n'))
3224 'extensions\n'))
3225
3225
3226 def helpextcmd(name):
3226 def helpextcmd(name):
3227 cmd, ext, mod = extensions.disabledcmd(ui, name, ui.config('ui', 'strict'))
3227 cmd, ext, mod = extensions.disabledcmd(ui, name, ui.config('ui', 'strict'))
3228 doc = gettext(mod.__doc__).splitlines()[0]
3228 doc = gettext(mod.__doc__).splitlines()[0]
3229
3229
3230 msg = help.listexts(_("'%s' is provided by the following "
3230 msg = help.listexts(_("'%s' is provided by the following "
3231 "extension:") % cmd, {ext: doc}, indent=4)
3231 "extension:") % cmd, {ext: doc}, indent=4)
3232 ui.write(minirst.format(msg, textwidth))
3232 ui.write(minirst.format(msg, textwidth))
3233 ui.write('\n')
3233 ui.write('\n')
3234 ui.write(_('use "hg help extensions" for information on enabling '
3234 ui.write(_('use "hg help extensions" for information on enabling '
3235 'extensions\n'))
3235 'extensions\n'))
3236
3236
3237 if name and name != 'shortlist':
3237 if name and name != 'shortlist':
3238 i = None
3238 i = None
3239 if unknowncmd:
3239 if unknowncmd:
3240 queries = (helpextcmd,)
3240 queries = (helpextcmd,)
3241 elif opts.get('extension'):
3241 elif opts.get('extension'):
3242 queries = (helpext,)
3242 queries = (helpext,)
3243 elif opts.get('command'):
3243 elif opts.get('command'):
3244 queries = (helpcmd,)
3244 queries = (helpcmd,)
3245 else:
3245 else:
3246 queries = (helptopic, helpcmd, helpext, helpextcmd)
3246 queries = (helptopic, helpcmd, helpext, helpextcmd)
3247 for f in queries:
3247 for f in queries:
3248 try:
3248 try:
3249 f(name)
3249 f(name)
3250 i = None
3250 i = None
3251 break
3251 break
3252 except error.UnknownCommand, inst:
3252 except error.UnknownCommand, inst:
3253 i = inst
3253 i = inst
3254 if i:
3254 if i:
3255 raise i
3255 raise i
3256 else:
3256 else:
3257 # program name
3257 # program name
3258 ui.status(_("Mercurial Distributed SCM\n"))
3258 ui.status(_("Mercurial Distributed SCM\n"))
3259 ui.status('\n')
3259 ui.status('\n')
3260 helplist()
3260 helplist()
3261
3261
3262
3262
3263 @command('identify|id',
3263 @command('identify|id',
3264 [('r', 'rev', '',
3264 [('r', 'rev', '',
3265 _('identify the specified revision'), _('REV')),
3265 _('identify the specified revision'), _('REV')),
3266 ('n', 'num', None, _('show local revision number')),
3266 ('n', 'num', None, _('show local revision number')),
3267 ('i', 'id', None, _('show global revision id')),
3267 ('i', 'id', None, _('show global revision id')),
3268 ('b', 'branch', None, _('show branch')),
3268 ('b', 'branch', None, _('show branch')),
3269 ('t', 'tags', None, _('show tags')),
3269 ('t', 'tags', None, _('show tags')),
3270 ('B', 'bookmarks', None, _('show bookmarks')),
3270 ('B', 'bookmarks', None, _('show bookmarks')),
3271 ] + remoteopts,
3271 ] + remoteopts,
3272 _('[-nibtB] [-r REV] [SOURCE]'))
3272 _('[-nibtB] [-r REV] [SOURCE]'))
3273 def identify(ui, repo, source=None, rev=None,
3273 def identify(ui, repo, source=None, rev=None,
3274 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
3274 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
3275 """identify the working copy or specified revision
3275 """identify the working copy or specified revision
3276
3276
3277 Print a summary identifying the repository state at REV using one or
3277 Print a summary identifying the repository state at REV using one or
3278 two parent hash identifiers, followed by a "+" if the working
3278 two parent hash identifiers, followed by a "+" if the working
3279 directory has uncommitted changes, the branch name (if not default),
3279 directory has uncommitted changes, the branch name (if not default),
3280 a list of tags, and a list of bookmarks.
3280 a list of tags, and a list of bookmarks.
3281
3281
3282 When REV is not given, print a summary of the current state of the
3282 When REV is not given, print a summary of the current state of the
3283 repository.
3283 repository.
3284
3284
3285 Specifying a path to a repository root or Mercurial bundle will
3285 Specifying a path to a repository root or Mercurial bundle will
3286 cause lookup to operate on that repository/bundle.
3286 cause lookup to operate on that repository/bundle.
3287
3287
3288 .. container:: verbose
3288 .. container:: verbose
3289
3289
3290 Examples:
3290 Examples:
3291
3291
3292 - generate a build identifier for the working directory::
3292 - generate a build identifier for the working directory::
3293
3293
3294 hg id --id > build-id.dat
3294 hg id --id > build-id.dat
3295
3295
3296 - find the revision corresponding to a tag::
3296 - find the revision corresponding to a tag::
3297
3297
3298 hg id -n -r 1.3
3298 hg id -n -r 1.3
3299
3299
3300 - check the most recent revision of a remote repository::
3300 - check the most recent revision of a remote repository::
3301
3301
3302 hg id -r tip http://selenic.com/hg/
3302 hg id -r tip http://selenic.com/hg/
3303
3303
3304 Returns 0 if successful.
3304 Returns 0 if successful.
3305 """
3305 """
3306
3306
3307 if not repo and not source:
3307 if not repo and not source:
3308 raise util.Abort(_("there is no Mercurial repository here "
3308 raise util.Abort(_("there is no Mercurial repository here "
3309 "(.hg not found)"))
3309 "(.hg not found)"))
3310
3310
3311 hexfunc = ui.debugflag and hex or short
3311 hexfunc = ui.debugflag and hex or short
3312 default = not (num or id or branch or tags or bookmarks)
3312 default = not (num or id or branch or tags or bookmarks)
3313 output = []
3313 output = []
3314 revs = []
3314 revs = []
3315
3315
3316 if source:
3316 if source:
3317 source, branches = hg.parseurl(ui.expandpath(source))
3317 source, branches = hg.parseurl(ui.expandpath(source))
3318 repo = hg.peer(ui, opts, source)
3318 repo = hg.peer(ui, opts, source)
3319 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
3319 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
3320
3320
3321 if not repo.local():
3321 if not repo.local():
3322 if num or branch or tags:
3322 if num or branch or tags:
3323 raise util.Abort(
3323 raise util.Abort(
3324 _("can't query remote revision number, branch, or tags"))
3324 _("can't query remote revision number, branch, or tags"))
3325 if not rev and revs:
3325 if not rev and revs:
3326 rev = revs[0]
3326 rev = revs[0]
3327 if not rev:
3327 if not rev:
3328 rev = "tip"
3328 rev = "tip"
3329
3329
3330 remoterev = repo.lookup(rev)
3330 remoterev = repo.lookup(rev)
3331 if default or id:
3331 if default or id:
3332 output = [hexfunc(remoterev)]
3332 output = [hexfunc(remoterev)]
3333
3333
3334 def getbms():
3334 def getbms():
3335 bms = []
3335 bms = []
3336
3336
3337 if 'bookmarks' in repo.listkeys('namespaces'):
3337 if 'bookmarks' in repo.listkeys('namespaces'):
3338 hexremoterev = hex(remoterev)
3338 hexremoterev = hex(remoterev)
3339 bms = [bm for bm, bmr in repo.listkeys('bookmarks').iteritems()
3339 bms = [bm for bm, bmr in repo.listkeys('bookmarks').iteritems()
3340 if bmr == hexremoterev]
3340 if bmr == hexremoterev]
3341
3341
3342 return bms
3342 return bms
3343
3343
3344 if bookmarks:
3344 if bookmarks:
3345 output.extend(getbms())
3345 output.extend(getbms())
3346 elif default and not ui.quiet:
3346 elif default and not ui.quiet:
3347 # multiple bookmarks for a single parent separated by '/'
3347 # multiple bookmarks for a single parent separated by '/'
3348 bm = '/'.join(getbms())
3348 bm = '/'.join(getbms())
3349 if bm:
3349 if bm:
3350 output.append(bm)
3350 output.append(bm)
3351 else:
3351 else:
3352 if not rev:
3352 if not rev:
3353 ctx = repo[None]
3353 ctx = repo[None]
3354 parents = ctx.parents()
3354 parents = ctx.parents()
3355 changed = ""
3355 changed = ""
3356 if default or id or num:
3356 if default or id or num:
3357 changed = util.any(repo.status()) and "+" or ""
3357 changed = util.any(repo.status()) and "+" or ""
3358 if default or id:
3358 if default or id:
3359 output = ["%s%s" %
3359 output = ["%s%s" %
3360 ('+'.join([hexfunc(p.node()) for p in parents]), changed)]
3360 ('+'.join([hexfunc(p.node()) for p in parents]), changed)]
3361 if num:
3361 if num:
3362 output.append("%s%s" %
3362 output.append("%s%s" %
3363 ('+'.join([str(p.rev()) for p in parents]), changed))
3363 ('+'.join([str(p.rev()) for p in parents]), changed))
3364 else:
3364 else:
3365 ctx = scmutil.revsingle(repo, rev)
3365 ctx = scmutil.revsingle(repo, rev)
3366 if default or id:
3366 if default or id:
3367 output = [hexfunc(ctx.node())]
3367 output = [hexfunc(ctx.node())]
3368 if num:
3368 if num:
3369 output.append(str(ctx.rev()))
3369 output.append(str(ctx.rev()))
3370
3370
3371 if default and not ui.quiet:
3371 if default and not ui.quiet:
3372 b = ctx.branch()
3372 b = ctx.branch()
3373 if b != 'default':
3373 if b != 'default':
3374 output.append("(%s)" % b)
3374 output.append("(%s)" % b)
3375
3375
3376 # multiple tags for a single parent separated by '/'
3376 # multiple tags for a single parent separated by '/'
3377 t = '/'.join(ctx.tags())
3377 t = '/'.join(ctx.tags())
3378 if t:
3378 if t:
3379 output.append(t)
3379 output.append(t)
3380
3380
3381 # multiple bookmarks for a single parent separated by '/'
3381 # multiple bookmarks for a single parent separated by '/'
3382 bm = '/'.join(ctx.bookmarks())
3382 bm = '/'.join(ctx.bookmarks())
3383 if bm:
3383 if bm:
3384 output.append(bm)
3384 output.append(bm)
3385 else:
3385 else:
3386 if branch:
3386 if branch:
3387 output.append(ctx.branch())
3387 output.append(ctx.branch())
3388
3388
3389 if tags:
3389 if tags:
3390 output.extend(ctx.tags())
3390 output.extend(ctx.tags())
3391
3391
3392 if bookmarks:
3392 if bookmarks:
3393 output.extend(ctx.bookmarks())
3393 output.extend(ctx.bookmarks())
3394
3394
3395 ui.write("%s\n" % ' '.join(output))
3395 ui.write("%s\n" % ' '.join(output))
3396
3396
3397 @command('import|patch',
3397 @command('import|patch',
3398 [('p', 'strip', 1,
3398 [('p', 'strip', 1,
3399 _('directory strip option for patch. This has the same '
3399 _('directory strip option for patch. This has the same '
3400 'meaning as the corresponding patch option'), _('NUM')),
3400 'meaning as the corresponding patch option'), _('NUM')),
3401 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
3401 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
3402 ('e', 'edit', False, _('invoke editor on commit messages')),
3402 ('e', 'edit', False, _('invoke editor on commit messages')),
3403 ('f', 'force', None, _('skip check for outstanding uncommitted changes')),
3403 ('f', 'force', None, _('skip check for outstanding uncommitted changes')),
3404 ('', 'no-commit', None,
3404 ('', 'no-commit', None,
3405 _("don't commit, just update the working directory")),
3405 _("don't commit, just update the working directory")),
3406 ('', 'bypass', None,
3406 ('', 'bypass', None,
3407 _("apply patch without touching the working directory")),
3407 _("apply patch without touching the working directory")),
3408 ('', 'exact', None,
3408 ('', 'exact', None,
3409 _('apply patch to the nodes from which it was generated')),
3409 _('apply patch to the nodes from which it was generated')),
3410 ('', 'import-branch', None,
3410 ('', 'import-branch', None,
3411 _('use any branch information in patch (implied by --exact)'))] +
3411 _('use any branch information in patch (implied by --exact)'))] +
3412 commitopts + commitopts2 + similarityopts,
3412 commitopts + commitopts2 + similarityopts,
3413 _('[OPTION]... PATCH...'))
3413 _('[OPTION]... PATCH...'))
3414 def import_(ui, repo, patch1=None, *patches, **opts):
3414 def import_(ui, repo, patch1=None, *patches, **opts):
3415 """import an ordered set of patches
3415 """import an ordered set of patches
3416
3416
3417 Import a list of patches and commit them individually (unless
3417 Import a list of patches and commit them individually (unless
3418 --no-commit is specified).
3418 --no-commit is specified).
3419
3419
3420 If there are outstanding changes in the working directory, import
3420 If there are outstanding changes in the working directory, import
3421 will abort unless given the -f/--force flag.
3421 will abort unless given the -f/--force flag.
3422
3422
3423 You can import a patch straight from a mail message. Even patches
3423 You can import a patch straight from a mail message. Even patches
3424 as attachments work (to use the body part, it must have type
3424 as attachments work (to use the body part, it must have type
3425 text/plain or text/x-patch). From and Subject headers of email
3425 text/plain or text/x-patch). From and Subject headers of email
3426 message are used as default committer and commit message. All
3426 message are used as default committer and commit message. All
3427 text/plain body parts before first diff are added to commit
3427 text/plain body parts before first diff are added to commit
3428 message.
3428 message.
3429
3429
3430 If the imported patch was generated by :hg:`export`, user and
3430 If the imported patch was generated by :hg:`export`, user and
3431 description from patch override values from message headers and
3431 description from patch override values from message headers and
3432 body. Values given on command line with -m/--message and -u/--user
3432 body. Values given on command line with -m/--message and -u/--user
3433 override these.
3433 override these.
3434
3434
3435 If --exact is specified, import will set the working directory to
3435 If --exact is specified, import will set the working directory to
3436 the parent of each patch before applying it, and will abort if the
3436 the parent of each patch before applying it, and will abort if the
3437 resulting changeset has a different ID than the one recorded in
3437 resulting changeset has a different ID than the one recorded in
3438 the patch. This may happen due to character set problems or other
3438 the patch. This may happen due to character set problems or other
3439 deficiencies in the text patch format.
3439 deficiencies in the text patch format.
3440
3440
3441 Use --bypass to apply and commit patches directly to the
3441 Use --bypass to apply and commit patches directly to the
3442 repository, not touching the working directory. Without --exact,
3442 repository, not touching the working directory. Without --exact,
3443 patches will be applied on top of the working directory parent
3443 patches will be applied on top of the working directory parent
3444 revision.
3444 revision.
3445
3445
3446 With -s/--similarity, hg will attempt to discover renames and
3446 With -s/--similarity, hg will attempt to discover renames and
3447 copies in the patch in the same way as 'addremove'.
3447 copies in the patch in the same way as 'addremove'.
3448
3448
3449 To read a patch from standard input, use "-" as the patch name. If
3449 To read a patch from standard input, use "-" as the patch name. If
3450 a URL is specified, the patch will be downloaded from it.
3450 a URL is specified, the patch will be downloaded from it.
3451 See :hg:`help dates` for a list of formats valid for -d/--date.
3451 See :hg:`help dates` for a list of formats valid for -d/--date.
3452
3452
3453 .. container:: verbose
3453 .. container:: verbose
3454
3454
3455 Examples:
3455 Examples:
3456
3456
3457 - import a traditional patch from a website and detect renames::
3457 - import a traditional patch from a website and detect renames::
3458
3458
3459 hg import -s 80 http://example.com/bugfix.patch
3459 hg import -s 80 http://example.com/bugfix.patch
3460
3460
3461 - import a changeset from an hgweb server::
3461 - import a changeset from an hgweb server::
3462
3462
3463 hg import http://www.selenic.com/hg/rev/5ca8c111e9aa
3463 hg import http://www.selenic.com/hg/rev/5ca8c111e9aa
3464
3464
3465 - import all the patches in an Unix-style mbox::
3465 - import all the patches in an Unix-style mbox::
3466
3466
3467 hg import incoming-patches.mbox
3467 hg import incoming-patches.mbox
3468
3468
3469 - attempt to exactly restore an exported changeset (not always
3469 - attempt to exactly restore an exported changeset (not always
3470 possible)::
3470 possible)::
3471
3471
3472 hg import --exact proposed-fix.patch
3472 hg import --exact proposed-fix.patch
3473
3473
3474 Returns 0 on success.
3474 Returns 0 on success.
3475 """
3475 """
3476
3476
3477 if not patch1:
3477 if not patch1:
3478 raise util.Abort(_('need at least one patch to import'))
3478 raise util.Abort(_('need at least one patch to import'))
3479
3479
3480 patches = (patch1,) + patches
3480 patches = (patch1,) + patches
3481
3481
3482 date = opts.get('date')
3482 date = opts.get('date')
3483 if date:
3483 if date:
3484 opts['date'] = util.parsedate(date)
3484 opts['date'] = util.parsedate(date)
3485
3485
3486 editor = cmdutil.commiteditor
3486 editor = cmdutil.commiteditor
3487 if opts.get('edit'):
3487 if opts.get('edit'):
3488 editor = cmdutil.commitforceeditor
3488 editor = cmdutil.commitforceeditor
3489
3489
3490 update = not opts.get('bypass')
3490 update = not opts.get('bypass')
3491 if not update and opts.get('no_commit'):
3491 if not update and opts.get('no_commit'):
3492 raise util.Abort(_('cannot use --no-commit with --bypass'))
3492 raise util.Abort(_('cannot use --no-commit with --bypass'))
3493 try:
3493 try:
3494 sim = float(opts.get('similarity') or 0)
3494 sim = float(opts.get('similarity') or 0)
3495 except ValueError:
3495 except ValueError:
3496 raise util.Abort(_('similarity must be a number'))
3496 raise util.Abort(_('similarity must be a number'))
3497 if sim < 0 or sim > 100:
3497 if sim < 0 or sim > 100:
3498 raise util.Abort(_('similarity must be between 0 and 100'))
3498 raise util.Abort(_('similarity must be between 0 and 100'))
3499 if sim and not update:
3499 if sim and not update:
3500 raise util.Abort(_('cannot use --similarity with --bypass'))
3500 raise util.Abort(_('cannot use --similarity with --bypass'))
3501
3501
3502 if (opts.get('exact') or not opts.get('force')) and update:
3502 if (opts.get('exact') or not opts.get('force')) and update:
3503 cmdutil.bailifchanged(repo)
3503 cmdutil.bailifchanged(repo)
3504
3504
3505 base = opts["base"]
3505 base = opts["base"]
3506 strip = opts["strip"]
3506 strip = opts["strip"]
3507 wlock = lock = tr = None
3507 wlock = lock = tr = None
3508 msgs = []
3508 msgs = []
3509
3509
3510 def checkexact(repo, n, nodeid):
3510 def checkexact(repo, n, nodeid):
3511 if opts.get('exact') and hex(n) != nodeid:
3511 if opts.get('exact') and hex(n) != nodeid:
3512 repo.rollback()
3512 repo.rollback()
3513 raise util.Abort(_('patch is damaged or loses information'))
3513 raise util.Abort(_('patch is damaged or loses information'))
3514
3514
3515 def tryone(ui, hunk, parents):
3515 def tryone(ui, hunk, parents):
3516 tmpname, message, user, date, branch, nodeid, p1, p2 = \
3516 tmpname, message, user, date, branch, nodeid, p1, p2 = \
3517 patch.extract(ui, hunk)
3517 patch.extract(ui, hunk)
3518
3518
3519 if not tmpname:
3519 if not tmpname:
3520 return (None, None)
3520 return (None, None)
3521 msg = _('applied to working directory')
3521 msg = _('applied to working directory')
3522
3522
3523 try:
3523 try:
3524 cmdline_message = cmdutil.logmessage(ui, opts)
3524 cmdline_message = cmdutil.logmessage(ui, opts)
3525 if cmdline_message:
3525 if cmdline_message:
3526 # pickup the cmdline msg
3526 # pickup the cmdline msg
3527 message = cmdline_message
3527 message = cmdline_message
3528 elif message:
3528 elif message:
3529 # pickup the patch msg
3529 # pickup the patch msg
3530 message = message.strip()
3530 message = message.strip()
3531 else:
3531 else:
3532 # launch the editor
3532 # launch the editor
3533 message = None
3533 message = None
3534 ui.debug('message:\n%s\n' % message)
3534 ui.debug('message:\n%s\n' % message)
3535
3535
3536 if len(parents) == 1:
3536 if len(parents) == 1:
3537 parents.append(repo[nullid])
3537 parents.append(repo[nullid])
3538 if opts.get('exact'):
3538 if opts.get('exact'):
3539 if not nodeid or not p1:
3539 if not nodeid or not p1:
3540 raise util.Abort(_('not a Mercurial patch'))
3540 raise util.Abort(_('not a Mercurial patch'))
3541 p1 = repo[p1]
3541 p1 = repo[p1]
3542 p2 = repo[p2 or nullid]
3542 p2 = repo[p2 or nullid]
3543 elif p2:
3543 elif p2:
3544 try:
3544 try:
3545 p1 = repo[p1]
3545 p1 = repo[p1]
3546 p2 = repo[p2]
3546 p2 = repo[p2]
3547 # Without any options, consider p2 only if the
3547 # Without any options, consider p2 only if the
3548 # patch is being applied on top of the recorded
3548 # patch is being applied on top of the recorded
3549 # first parent.
3549 # first parent.
3550 if p1 != parents[0]:
3550 if p1 != parents[0]:
3551 p1 = parents[0]
3551 p1 = parents[0]
3552 p2 = repo[nullid]
3552 p2 = repo[nullid]
3553 except error.RepoError:
3553 except error.RepoError:
3554 p1, p2 = parents
3554 p1, p2 = parents
3555 else:
3555 else:
3556 p1, p2 = parents
3556 p1, p2 = parents
3557
3557
3558 n = None
3558 n = None
3559 if update:
3559 if update:
3560 if p1 != parents[0]:
3560 if p1 != parents[0]:
3561 hg.clean(repo, p1.node())
3561 hg.clean(repo, p1.node())
3562 if p2 != parents[1]:
3562 if p2 != parents[1]:
3563 repo.dirstate.setparents(p1.node(), p2.node())
3563 repo.dirstate.setparents(p1.node(), p2.node())
3564
3564
3565 if opts.get('exact') or opts.get('import_branch'):
3565 if opts.get('exact') or opts.get('import_branch'):
3566 repo.dirstate.setbranch(branch or 'default')
3566 repo.dirstate.setbranch(branch or 'default')
3567
3567
3568 files = set()
3568 files = set()
3569 patch.patch(ui, repo, tmpname, strip=strip, files=files,
3569 patch.patch(ui, repo, tmpname, strip=strip, files=files,
3570 eolmode=None, similarity=sim / 100.0)
3570 eolmode=None, similarity=sim / 100.0)
3571 files = list(files)
3571 files = list(files)
3572 if opts.get('no_commit'):
3572 if opts.get('no_commit'):
3573 if message:
3573 if message:
3574 msgs.append(message)
3574 msgs.append(message)
3575 else:
3575 else:
3576 if opts.get('exact') or p2:
3576 if opts.get('exact') or p2:
3577 # If you got here, you either use --force and know what
3577 # If you got here, you either use --force and know what
3578 # you are doing or used --exact or a merge patch while
3578 # you are doing or used --exact or a merge patch while
3579 # being updated to its first parent.
3579 # being updated to its first parent.
3580 m = None
3580 m = None
3581 else:
3581 else:
3582 m = scmutil.matchfiles(repo, files or [])
3582 m = scmutil.matchfiles(repo, files or [])
3583 n = repo.commit(message, opts.get('user') or user,
3583 n = repo.commit(message, opts.get('user') or user,
3584 opts.get('date') or date, match=m,
3584 opts.get('date') or date, match=m,
3585 editor=editor)
3585 editor=editor)
3586 checkexact(repo, n, nodeid)
3586 checkexact(repo, n, nodeid)
3587 else:
3587 else:
3588 if opts.get('exact') or opts.get('import_branch'):
3588 if opts.get('exact') or opts.get('import_branch'):
3589 branch = branch or 'default'
3589 branch = branch or 'default'
3590 else:
3590 else:
3591 branch = p1.branch()
3591 branch = p1.branch()
3592 store = patch.filestore()
3592 store = patch.filestore()
3593 try:
3593 try:
3594 files = set()
3594 files = set()
3595 try:
3595 try:
3596 patch.patchrepo(ui, repo, p1, store, tmpname, strip,
3596 patch.patchrepo(ui, repo, p1, store, tmpname, strip,
3597 files, eolmode=None)
3597 files, eolmode=None)
3598 except patch.PatchError, e:
3598 except patch.PatchError, e:
3599 raise util.Abort(str(e))
3599 raise util.Abort(str(e))
3600 memctx = patch.makememctx(repo, (p1.node(), p2.node()),
3600 memctx = patch.makememctx(repo, (p1.node(), p2.node()),
3601 message,
3601 message,
3602 opts.get('user') or user,
3602 opts.get('user') or user,
3603 opts.get('date') or date,
3603 opts.get('date') or date,
3604 branch, files, store,
3604 branch, files, store,
3605 editor=cmdutil.commiteditor)
3605 editor=cmdutil.commiteditor)
3606 repo.savecommitmessage(memctx.description())
3606 repo.savecommitmessage(memctx.description())
3607 n = memctx.commit()
3607 n = memctx.commit()
3608 checkexact(repo, n, nodeid)
3608 checkexact(repo, n, nodeid)
3609 finally:
3609 finally:
3610 store.close()
3610 store.close()
3611 if n:
3611 if n:
3612 # i18n: refers to a short changeset id
3612 # i18n: refers to a short changeset id
3613 msg = _('created %s') % short(n)
3613 msg = _('created %s') % short(n)
3614 return (msg, n)
3614 return (msg, n)
3615 finally:
3615 finally:
3616 os.unlink(tmpname)
3616 os.unlink(tmpname)
3617
3617
3618 try:
3618 try:
3619 try:
3619 try:
3620 wlock = repo.wlock()
3620 wlock = repo.wlock()
3621 lock = repo.lock()
3621 lock = repo.lock()
3622 tr = repo.transaction('import')
3622 tr = repo.transaction('import')
3623 parents = repo.parents()
3623 parents = repo.parents()
3624 for patchurl in patches:
3624 for patchurl in patches:
3625 if patchurl == '-':
3625 if patchurl == '-':
3626 ui.status(_('applying patch from stdin\n'))
3626 ui.status(_('applying patch from stdin\n'))
3627 patchfile = ui.fin
3627 patchfile = ui.fin
3628 patchurl = 'stdin' # for error message
3628 patchurl = 'stdin' # for error message
3629 else:
3629 else:
3630 patchurl = os.path.join(base, patchurl)
3630 patchurl = os.path.join(base, patchurl)
3631 ui.status(_('applying %s\n') % patchurl)
3631 ui.status(_('applying %s\n') % patchurl)
3632 patchfile = url.open(ui, patchurl)
3632 patchfile = url.open(ui, patchurl)
3633
3633
3634 haspatch = False
3634 haspatch = False
3635 for hunk in patch.split(patchfile):
3635 for hunk in patch.split(patchfile):
3636 (msg, node) = tryone(ui, hunk, parents)
3636 (msg, node) = tryone(ui, hunk, parents)
3637 if msg:
3637 if msg:
3638 haspatch = True
3638 haspatch = True
3639 ui.note(msg + '\n')
3639 ui.note(msg + '\n')
3640 if update or opts.get('exact'):
3640 if update or opts.get('exact'):
3641 parents = repo.parents()
3641 parents = repo.parents()
3642 else:
3642 else:
3643 parents = [repo[node]]
3643 parents = [repo[node]]
3644
3644
3645 if not haspatch:
3645 if not haspatch:
3646 raise util.Abort(_('%s: no diffs found') % patchurl)
3646 raise util.Abort(_('%s: no diffs found') % patchurl)
3647
3647
3648 tr.close()
3648 tr.close()
3649 if msgs:
3649 if msgs:
3650 repo.savecommitmessage('\n* * *\n'.join(msgs))
3650 repo.savecommitmessage('\n* * *\n'.join(msgs))
3651 except:
3651 except:
3652 # wlock.release() indirectly calls dirstate.write(): since
3652 # wlock.release() indirectly calls dirstate.write(): since
3653 # we're crashing, we do not want to change the working dir
3653 # we're crashing, we do not want to change the working dir
3654 # parent after all, so make sure it writes nothing
3654 # parent after all, so make sure it writes nothing
3655 repo.dirstate.invalidate()
3655 repo.dirstate.invalidate()
3656 raise
3656 raise
3657 finally:
3657 finally:
3658 if tr:
3658 if tr:
3659 tr.release()
3659 tr.release()
3660 release(lock, wlock)
3660 release(lock, wlock)
3661
3661
3662 @command('incoming|in',
3662 @command('incoming|in',
3663 [('f', 'force', None,
3663 [('f', 'force', None,
3664 _('run even if remote repository is unrelated')),
3664 _('run even if remote repository is unrelated')),
3665 ('n', 'newest-first', None, _('show newest record first')),
3665 ('n', 'newest-first', None, _('show newest record first')),
3666 ('', 'bundle', '',
3666 ('', 'bundle', '',
3667 _('file to store the bundles into'), _('FILE')),
3667 _('file to store the bundles into'), _('FILE')),
3668 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3668 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3669 ('B', 'bookmarks', False, _("compare bookmarks")),
3669 ('B', 'bookmarks', False, _("compare bookmarks")),
3670 ('b', 'branch', [],
3670 ('b', 'branch', [],
3671 _('a specific branch you would like to pull'), _('BRANCH')),
3671 _('a specific branch you would like to pull'), _('BRANCH')),
3672 ] + logopts + remoteopts + subrepoopts,
3672 ] + logopts + remoteopts + subrepoopts,
3673 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
3673 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
3674 def incoming(ui, repo, source="default", **opts):
3674 def incoming(ui, repo, source="default", **opts):
3675 """show new changesets found in source
3675 """show new changesets found in source
3676
3676
3677 Show new changesets found in the specified path/URL or the default
3677 Show new changesets found in the specified path/URL or the default
3678 pull location. These are the changesets that would have been pulled
3678 pull location. These are the changesets that would have been pulled
3679 if a pull at the time you issued this command.
3679 if a pull at the time you issued this command.
3680
3680
3681 For remote repository, using --bundle avoids downloading the
3681 For remote repository, using --bundle avoids downloading the
3682 changesets twice if the incoming is followed by a pull.
3682 changesets twice if the incoming is followed by a pull.
3683
3683
3684 See pull for valid source format details.
3684 See pull for valid source format details.
3685
3685
3686 Returns 0 if there are incoming changes, 1 otherwise.
3686 Returns 0 if there are incoming changes, 1 otherwise.
3687 """
3687 """
3688 if opts.get('bundle') and opts.get('subrepos'):
3688 if opts.get('bundle') and opts.get('subrepos'):
3689 raise util.Abort(_('cannot combine --bundle and --subrepos'))
3689 raise util.Abort(_('cannot combine --bundle and --subrepos'))
3690
3690
3691 if opts.get('bookmarks'):
3691 if opts.get('bookmarks'):
3692 source, branches = hg.parseurl(ui.expandpath(source),
3692 source, branches = hg.parseurl(ui.expandpath(source),
3693 opts.get('branch'))
3693 opts.get('branch'))
3694 other = hg.peer(repo, opts, source)
3694 other = hg.peer(repo, opts, source)
3695 if 'bookmarks' not in other.listkeys('namespaces'):
3695 if 'bookmarks' not in other.listkeys('namespaces'):
3696 ui.warn(_("remote doesn't support bookmarks\n"))
3696 ui.warn(_("remote doesn't support bookmarks\n"))
3697 return 0
3697 return 0
3698 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3698 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3699 return bookmarks.diff(ui, repo, other)
3699 return bookmarks.diff(ui, repo, other)
3700
3700
3701 repo._subtoppath = ui.expandpath(source)
3701 repo._subtoppath = ui.expandpath(source)
3702 try:
3702 try:
3703 return hg.incoming(ui, repo, source, opts)
3703 return hg.incoming(ui, repo, source, opts)
3704 finally:
3704 finally:
3705 del repo._subtoppath
3705 del repo._subtoppath
3706
3706
3707
3707
3708 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'))
3708 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'))
3709 def init(ui, dest=".", **opts):
3709 def init(ui, dest=".", **opts):
3710 """create a new repository in the given directory
3710 """create a new repository in the given directory
3711
3711
3712 Initialize a new repository in the given directory. If the given
3712 Initialize a new repository in the given directory. If the given
3713 directory does not exist, it will be created.
3713 directory does not exist, it will be created.
3714
3714
3715 If no directory is given, the current directory is used.
3715 If no directory is given, the current directory is used.
3716
3716
3717 It is possible to specify an ``ssh://`` URL as the destination.
3717 It is possible to specify an ``ssh://`` URL as the destination.
3718 See :hg:`help urls` for more information.
3718 See :hg:`help urls` for more information.
3719
3719
3720 Returns 0 on success.
3720 Returns 0 on success.
3721 """
3721 """
3722 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3722 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3723
3723
3724 @command('locate',
3724 @command('locate',
3725 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3725 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3726 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3726 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3727 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3727 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3728 ] + walkopts,
3728 ] + walkopts,
3729 _('[OPTION]... [PATTERN]...'))
3729 _('[OPTION]... [PATTERN]...'))
3730 def locate(ui, repo, *pats, **opts):
3730 def locate(ui, repo, *pats, **opts):
3731 """locate files matching specific patterns
3731 """locate files matching specific patterns
3732
3732
3733 Print files under Mercurial control in the working directory whose
3733 Print files under Mercurial control in the working directory whose
3734 names match the given patterns.
3734 names match the given patterns.
3735
3735
3736 By default, this command searches all directories in the working
3736 By default, this command searches all directories in the working
3737 directory. To search just the current directory and its
3737 directory. To search just the current directory and its
3738 subdirectories, use "--include .".
3738 subdirectories, use "--include .".
3739
3739
3740 If no patterns are given to match, this command prints the names
3740 If no patterns are given to match, this command prints the names
3741 of all files under Mercurial control in the working directory.
3741 of all files under Mercurial control in the working directory.
3742
3742
3743 If you want to feed the output of this command into the "xargs"
3743 If you want to feed the output of this command into the "xargs"
3744 command, use the -0 option to both this command and "xargs". This
3744 command, use the -0 option to both this command and "xargs". This
3745 will avoid the problem of "xargs" treating single filenames that
3745 will avoid the problem of "xargs" treating single filenames that
3746 contain whitespace as multiple filenames.
3746 contain whitespace as multiple filenames.
3747
3747
3748 Returns 0 if a match is found, 1 otherwise.
3748 Returns 0 if a match is found, 1 otherwise.
3749 """
3749 """
3750 end = opts.get('print0') and '\0' or '\n'
3750 end = opts.get('print0') and '\0' or '\n'
3751 rev = scmutil.revsingle(repo, opts.get('rev'), None).node()
3751 rev = scmutil.revsingle(repo, opts.get('rev'), None).node()
3752
3752
3753 ret = 1
3753 ret = 1
3754 m = scmutil.match(repo[rev], pats, opts, default='relglob')
3754 m = scmutil.match(repo[rev], pats, opts, default='relglob')
3755 m.bad = lambda x, y: False
3755 m.bad = lambda x, y: False
3756 for abs in repo[rev].walk(m):
3756 for abs in repo[rev].walk(m):
3757 if not rev and abs not in repo.dirstate:
3757 if not rev and abs not in repo.dirstate:
3758 continue
3758 continue
3759 if opts.get('fullpath'):
3759 if opts.get('fullpath'):
3760 ui.write(repo.wjoin(abs), end)
3760 ui.write(repo.wjoin(abs), end)
3761 else:
3761 else:
3762 ui.write(((pats and m.rel(abs)) or abs), end)
3762 ui.write(((pats and m.rel(abs)) or abs), end)
3763 ret = 0
3763 ret = 0
3764
3764
3765 return ret
3765 return ret
3766
3766
3767 @command('^log|history',
3767 @command('^log|history',
3768 [('f', 'follow', None,
3768 [('f', 'follow', None,
3769 _('follow changeset history, or file history across copies and renames')),
3769 _('follow changeset history, or file history across copies and renames')),
3770 ('', 'follow-first', None,
3770 ('', 'follow-first', None,
3771 _('only follow the first parent of merge changesets (DEPRECATED)')),
3771 _('only follow the first parent of merge changesets (DEPRECATED)')),
3772 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3772 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3773 ('C', 'copies', None, _('show copied files')),
3773 ('C', 'copies', None, _('show copied files')),
3774 ('k', 'keyword', [],
3774 ('k', 'keyword', [],
3775 _('do case-insensitive search for a given text'), _('TEXT')),
3775 _('do case-insensitive search for a given text'), _('TEXT')),
3776 ('r', 'rev', [], _('show the specified revision or range'), _('REV')),
3776 ('r', 'rev', [], _('show the specified revision or range'), _('REV')),
3777 ('', 'removed', None, _('include revisions where files were removed')),
3777 ('', 'removed', None, _('include revisions where files were removed')),
3778 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
3778 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
3779 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3779 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3780 ('', 'only-branch', [],
3780 ('', 'only-branch', [],
3781 _('show only changesets within the given named branch (DEPRECATED)'),
3781 _('show only changesets within the given named branch (DEPRECATED)'),
3782 _('BRANCH')),
3782 _('BRANCH')),
3783 ('b', 'branch', [],
3783 ('b', 'branch', [],
3784 _('show changesets within the given named branch'), _('BRANCH')),
3784 _('show changesets within the given named branch'), _('BRANCH')),
3785 ('P', 'prune', [],
3785 ('P', 'prune', [],
3786 _('do not display revision or any of its ancestors'), _('REV')),
3786 _('do not display revision or any of its ancestors'), _('REV')),
3787 ('', 'hidden', False, _('show hidden changesets (DEPRECATED)')),
3787 ('', 'hidden', False, _('show hidden changesets (DEPRECATED)')),
3788 ] + logopts + walkopts,
3788 ] + logopts + walkopts,
3789 _('[OPTION]... [FILE]'))
3789 _('[OPTION]... [FILE]'))
3790 def log(ui, repo, *pats, **opts):
3790 def log(ui, repo, *pats, **opts):
3791 """show revision history of entire repository or files
3791 """show revision history of entire repository or files
3792
3792
3793 Print the revision history of the specified files or the entire
3793 Print the revision history of the specified files or the entire
3794 project.
3794 project.
3795
3795
3796 If no revision range is specified, the default is ``tip:0`` unless
3796 If no revision range is specified, the default is ``tip:0`` unless
3797 --follow is set, in which case the working directory parent is
3797 --follow is set, in which case the working directory parent is
3798 used as the starting revision.
3798 used as the starting revision.
3799
3799
3800 File history is shown without following rename or copy history of
3800 File history is shown without following rename or copy history of
3801 files. Use -f/--follow with a filename to follow history across
3801 files. Use -f/--follow with a filename to follow history across
3802 renames and copies. --follow without a filename will only show
3802 renames and copies. --follow without a filename will only show
3803 ancestors or descendants of the starting revision.
3803 ancestors or descendants of the starting revision.
3804
3804
3805 By default this command prints revision number and changeset id,
3805 By default this command prints revision number and changeset id,
3806 tags, non-trivial parents, user, date and time, and a summary for
3806 tags, non-trivial parents, user, date and time, and a summary for
3807 each commit. When the -v/--verbose switch is used, the list of
3807 each commit. When the -v/--verbose switch is used, the list of
3808 changed files and full commit message are shown.
3808 changed files and full commit message are shown.
3809
3809
3810 .. note::
3810 .. note::
3811 log -p/--patch may generate unexpected diff output for merge
3811 log -p/--patch may generate unexpected diff output for merge
3812 changesets, as it will only compare the merge changeset against
3812 changesets, as it will only compare the merge changeset against
3813 its first parent. Also, only files different from BOTH parents
3813 its first parent. Also, only files different from BOTH parents
3814 will appear in files:.
3814 will appear in files:.
3815
3815
3816 .. note::
3816 .. note::
3817 for performance reasons, log FILE may omit duplicate changes
3817 for performance reasons, log FILE may omit duplicate changes
3818 made on branches and will not show deletions. To see all
3818 made on branches and will not show deletions. To see all
3819 changes including duplicates and deletions, use the --removed
3819 changes including duplicates and deletions, use the --removed
3820 switch.
3820 switch.
3821
3821
3822 .. container:: verbose
3822 .. container:: verbose
3823
3823
3824 Some examples:
3824 Some examples:
3825
3825
3826 - changesets with full descriptions and file lists::
3826 - changesets with full descriptions and file lists::
3827
3827
3828 hg log -v
3828 hg log -v
3829
3829
3830 - changesets ancestral to the working directory::
3830 - changesets ancestral to the working directory::
3831
3831
3832 hg log -f
3832 hg log -f
3833
3833
3834 - last 10 commits on the current branch::
3834 - last 10 commits on the current branch::
3835
3835
3836 hg log -l 10 -b .
3836 hg log -l 10 -b .
3837
3837
3838 - changesets showing all modifications of a file, including removals::
3838 - changesets showing all modifications of a file, including removals::
3839
3839
3840 hg log --removed file.c
3840 hg log --removed file.c
3841
3841
3842 - all changesets that touch a directory, with diffs, excluding merges::
3842 - all changesets that touch a directory, with diffs, excluding merges::
3843
3843
3844 hg log -Mp lib/
3844 hg log -Mp lib/
3845
3845
3846 - all revision numbers that match a keyword::
3846 - all revision numbers that match a keyword::
3847
3847
3848 hg log -k bug --template "{rev}\\n"
3848 hg log -k bug --template "{rev}\\n"
3849
3849
3850 - check if a given changeset is included is a tagged release::
3850 - check if a given changeset is included is a tagged release::
3851
3851
3852 hg log -r "a21ccf and ancestor(1.9)"
3852 hg log -r "a21ccf and ancestor(1.9)"
3853
3853
3854 - find all changesets by some user in a date range::
3854 - find all changesets by some user in a date range::
3855
3855
3856 hg log -k alice -d "may 2008 to jul 2008"
3856 hg log -k alice -d "may 2008 to jul 2008"
3857
3857
3858 - summary of all changesets after the last tag::
3858 - summary of all changesets after the last tag::
3859
3859
3860 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
3860 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
3861
3861
3862 See :hg:`help dates` for a list of formats valid for -d/--date.
3862 See :hg:`help dates` for a list of formats valid for -d/--date.
3863
3863
3864 See :hg:`help revisions` and :hg:`help revsets` for more about
3864 See :hg:`help revisions` and :hg:`help revsets` for more about
3865 specifying revisions.
3865 specifying revisions.
3866
3866
3867 Returns 0 on success.
3867 Returns 0 on success.
3868 """
3868 """
3869
3869
3870 matchfn = scmutil.match(repo[None], pats, opts)
3870 matchfn = scmutil.match(repo[None], pats, opts)
3871 limit = cmdutil.loglimit(opts)
3871 limit = cmdutil.loglimit(opts)
3872 count = 0
3872 count = 0
3873
3873
3874 endrev = None
3874 endrev = None
3875 if opts.get('copies') and opts.get('rev'):
3875 if opts.get('copies') and opts.get('rev'):
3876 endrev = max(scmutil.revrange(repo, opts.get('rev'))) + 1
3876 endrev = max(scmutil.revrange(repo, opts.get('rev'))) + 1
3877
3877
3878 df = False
3878 df = False
3879 if opts["date"]:
3879 if opts["date"]:
3880 df = util.matchdate(opts["date"])
3880 df = util.matchdate(opts["date"])
3881
3881
3882 branches = opts.get('branch', []) + opts.get('only_branch', [])
3882 branches = opts.get('branch', []) + opts.get('only_branch', [])
3883 opts['branch'] = [repo.lookupbranch(b) for b in branches]
3883 opts['branch'] = [repo.lookupbranch(b) for b in branches]
3884
3884
3885 displayer = cmdutil.show_changeset(ui, repo, opts, True)
3885 displayer = cmdutil.show_changeset(ui, repo, opts, True)
3886 def prep(ctx, fns):
3886 def prep(ctx, fns):
3887 rev = ctx.rev()
3887 rev = ctx.rev()
3888 parents = [p for p in repo.changelog.parentrevs(rev)
3888 parents = [p for p in repo.changelog.parentrevs(rev)
3889 if p != nullrev]
3889 if p != nullrev]
3890 if opts.get('no_merges') and len(parents) == 2:
3890 if opts.get('no_merges') and len(parents) == 2:
3891 return
3891 return
3892 if opts.get('only_merges') and len(parents) != 2:
3892 if opts.get('only_merges') and len(parents) != 2:
3893 return
3893 return
3894 if opts.get('branch') and ctx.branch() not in opts['branch']:
3894 if opts.get('branch') and ctx.branch() not in opts['branch']:
3895 return
3895 return
3896 if not opts.get('hidden') and ctx.hidden():
3896 if not opts.get('hidden') and ctx.hidden():
3897 return
3897 return
3898 if df and not df(ctx.date()[0]):
3898 if df and not df(ctx.date()[0]):
3899 return
3899 return
3900
3900
3901 lower = encoding.lower
3901 lower = encoding.lower
3902 if opts.get('user'):
3902 if opts.get('user'):
3903 luser = lower(ctx.user())
3903 luser = lower(ctx.user())
3904 for k in [lower(x) for x in opts['user']]:
3904 for k in [lower(x) for x in opts['user']]:
3905 if (k in luser):
3905 if (k in luser):
3906 break
3906 break
3907 else:
3907 else:
3908 return
3908 return
3909 if opts.get('keyword'):
3909 if opts.get('keyword'):
3910 luser = lower(ctx.user())
3910 luser = lower(ctx.user())
3911 ldesc = lower(ctx.description())
3911 ldesc = lower(ctx.description())
3912 lfiles = lower(" ".join(ctx.files()))
3912 lfiles = lower(" ".join(ctx.files()))
3913 for k in [lower(x) for x in opts['keyword']]:
3913 for k in [lower(x) for x in opts['keyword']]:
3914 if (k in luser or k in ldesc or k in lfiles):
3914 if (k in luser or k in ldesc or k in lfiles):
3915 break
3915 break
3916 else:
3916 else:
3917 return
3917 return
3918
3918
3919 copies = None
3919 copies = None
3920 if opts.get('copies') and rev:
3920 if opts.get('copies') and rev:
3921 copies = []
3921 copies = []
3922 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
3922 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
3923 for fn in ctx.files():
3923 for fn in ctx.files():
3924 rename = getrenamed(fn, rev)
3924 rename = getrenamed(fn, rev)
3925 if rename:
3925 if rename:
3926 copies.append((fn, rename[0]))
3926 copies.append((fn, rename[0]))
3927
3927
3928 revmatchfn = None
3928 revmatchfn = None
3929 if opts.get('patch') or opts.get('stat'):
3929 if opts.get('patch') or opts.get('stat'):
3930 if opts.get('follow') or opts.get('follow_first'):
3930 if opts.get('follow') or opts.get('follow_first'):
3931 # note: this might be wrong when following through merges
3931 # note: this might be wrong when following through merges
3932 revmatchfn = scmutil.match(repo[None], fns, default='path')
3932 revmatchfn = scmutil.match(repo[None], fns, default='path')
3933 else:
3933 else:
3934 revmatchfn = matchfn
3934 revmatchfn = matchfn
3935
3935
3936 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
3936 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
3937
3937
3938 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
3938 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
3939 if count == limit:
3939 if count == limit:
3940 break
3940 break
3941 if displayer.flush(ctx.rev()):
3941 if displayer.flush(ctx.rev()):
3942 count += 1
3942 count += 1
3943 displayer.close()
3943 displayer.close()
3944
3944
3945 @command('manifest',
3945 @command('manifest',
3946 [('r', 'rev', '', _('revision to display'), _('REV')),
3946 [('r', 'rev', '', _('revision to display'), _('REV')),
3947 ('', 'all', False, _("list files from all revisions"))],
3947 ('', 'all', False, _("list files from all revisions"))],
3948 _('[-r REV]'))
3948 _('[-r REV]'))
3949 def manifest(ui, repo, node=None, rev=None, **opts):
3949 def manifest(ui, repo, node=None, rev=None, **opts):
3950 """output the current or given revision of the project manifest
3950 """output the current or given revision of the project manifest
3951
3951
3952 Print a list of version controlled files for the given revision.
3952 Print a list of version controlled files for the given revision.
3953 If no revision is given, the first parent of the working directory
3953 If no revision is given, the first parent of the working directory
3954 is used, or the null revision if no revision is checked out.
3954 is used, or the null revision if no revision is checked out.
3955
3955
3956 With -v, print file permissions, symlink and executable bits.
3956 With -v, print file permissions, symlink and executable bits.
3957 With --debug, print file revision hashes.
3957 With --debug, print file revision hashes.
3958
3958
3959 If option --all is specified, the list of all files from all revisions
3959 If option --all is specified, the list of all files from all revisions
3960 is printed. This includes deleted and renamed files.
3960 is printed. This includes deleted and renamed files.
3961
3961
3962 Returns 0 on success.
3962 Returns 0 on success.
3963 """
3963 """
3964 if opts.get('all'):
3964 if opts.get('all'):
3965 if rev or node:
3965 if rev or node:
3966 raise util.Abort(_("can't specify a revision with --all"))
3966 raise util.Abort(_("can't specify a revision with --all"))
3967
3967
3968 res = []
3968 res = []
3969 prefix = "data/"
3969 prefix = "data/"
3970 suffix = ".i"
3970 suffix = ".i"
3971 plen = len(prefix)
3971 plen = len(prefix)
3972 slen = len(suffix)
3972 slen = len(suffix)
3973 lock = repo.lock()
3973 lock = repo.lock()
3974 try:
3974 try:
3975 for fn, b, size in repo.store.datafiles():
3975 for fn, b, size in repo.store.datafiles():
3976 if size != 0 and fn[-slen:] == suffix and fn[:plen] == prefix:
3976 if size != 0 and fn[-slen:] == suffix and fn[:plen] == prefix:
3977 res.append(fn[plen:-slen])
3977 res.append(fn[plen:-slen])
3978 finally:
3978 finally:
3979 lock.release()
3979 lock.release()
3980 for f in sorted(res):
3980 for f in sorted(res):
3981 ui.write("%s\n" % f)
3981 ui.write("%s\n" % f)
3982 return
3982 return
3983
3983
3984 if rev and node:
3984 if rev and node:
3985 raise util.Abort(_("please specify just one revision"))
3985 raise util.Abort(_("please specify just one revision"))
3986
3986
3987 if not node:
3987 if not node:
3988 node = rev
3988 node = rev
3989
3989
3990 decor = {'l':'644 @ ', 'x':'755 * ', '':'644 '}
3990 decor = {'l':'644 @ ', 'x':'755 * ', '':'644 '}
3991 ctx = scmutil.revsingle(repo, node)
3991 ctx = scmutil.revsingle(repo, node)
3992 for f in ctx:
3992 for f in ctx:
3993 if ui.debugflag:
3993 if ui.debugflag:
3994 ui.write("%40s " % hex(ctx.manifest()[f]))
3994 ui.write("%40s " % hex(ctx.manifest()[f]))
3995 if ui.verbose:
3995 if ui.verbose:
3996 ui.write(decor[ctx.flags(f)])
3996 ui.write(decor[ctx.flags(f)])
3997 ui.write("%s\n" % f)
3997 ui.write("%s\n" % f)
3998
3998
3999 @command('^merge',
3999 @command('^merge',
4000 [('f', 'force', None, _('force a merge with outstanding changes')),
4000 [('f', 'force', None, _('force a merge with outstanding changes')),
4001 ('r', 'rev', '', _('revision to merge'), _('REV')),
4001 ('r', 'rev', '', _('revision to merge'), _('REV')),
4002 ('P', 'preview', None,
4002 ('P', 'preview', None,
4003 _('review revisions to merge (no merge is performed)'))
4003 _('review revisions to merge (no merge is performed)'))
4004 ] + mergetoolopts,
4004 ] + mergetoolopts,
4005 _('[-P] [-f] [[-r] REV]'))
4005 _('[-P] [-f] [[-r] REV]'))
4006 def merge(ui, repo, node=None, **opts):
4006 def merge(ui, repo, node=None, **opts):
4007 """merge working directory with another revision
4007 """merge working directory with another revision
4008
4008
4009 The current working directory is updated with all changes made in
4009 The current working directory is updated with all changes made in
4010 the requested revision since the last common predecessor revision.
4010 the requested revision since the last common predecessor revision.
4011
4011
4012 Files that changed between either parent are marked as changed for
4012 Files that changed between either parent are marked as changed for
4013 the next commit and a commit must be performed before any further
4013 the next commit and a commit must be performed before any further
4014 updates to the repository are allowed. The next commit will have
4014 updates to the repository are allowed. The next commit will have
4015 two parents.
4015 two parents.
4016
4016
4017 ``--tool`` can be used to specify the merge tool used for file
4017 ``--tool`` can be used to specify the merge tool used for file
4018 merges. It overrides the HGMERGE environment variable and your
4018 merges. It overrides the HGMERGE environment variable and your
4019 configuration files. See :hg:`help merge-tools` for options.
4019 configuration files. See :hg:`help merge-tools` for options.
4020
4020
4021 If no revision is specified, the working directory's parent is a
4021 If no revision is specified, the working directory's parent is a
4022 head revision, and the current branch contains exactly one other
4022 head revision, and the current branch contains exactly one other
4023 head, the other head is merged with by default. Otherwise, an
4023 head, the other head is merged with by default. Otherwise, an
4024 explicit revision with which to merge with must be provided.
4024 explicit revision with which to merge with must be provided.
4025
4025
4026 :hg:`resolve` must be used to resolve unresolved files.
4026 :hg:`resolve` must be used to resolve unresolved files.
4027
4027
4028 To undo an uncommitted merge, use :hg:`update --clean .` which
4028 To undo an uncommitted merge, use :hg:`update --clean .` which
4029 will check out a clean copy of the original merge parent, losing
4029 will check out a clean copy of the original merge parent, losing
4030 all changes.
4030 all changes.
4031
4031
4032 Returns 0 on success, 1 if there are unresolved files.
4032 Returns 0 on success, 1 if there are unresolved files.
4033 """
4033 """
4034
4034
4035 if opts.get('rev') and node:
4035 if opts.get('rev') and node:
4036 raise util.Abort(_("please specify just one revision"))
4036 raise util.Abort(_("please specify just one revision"))
4037 if not node:
4037 if not node:
4038 node = opts.get('rev')
4038 node = opts.get('rev')
4039
4039
4040 if not node:
4040 if not node:
4041 branch = repo[None].branch()
4041 branch = repo[None].branch()
4042 bheads = repo.branchheads(branch)
4042 bheads = repo.branchheads(branch)
4043 if len(bheads) > 2:
4043 if len(bheads) > 2:
4044 raise util.Abort(_("branch '%s' has %d heads - "
4044 raise util.Abort(_("branch '%s' has %d heads - "
4045 "please merge with an explicit rev")
4045 "please merge with an explicit rev")
4046 % (branch, len(bheads)),
4046 % (branch, len(bheads)),
4047 hint=_("run 'hg heads .' to see heads"))
4047 hint=_("run 'hg heads .' to see heads"))
4048
4048
4049 parent = repo.dirstate.p1()
4049 parent = repo.dirstate.p1()
4050 if len(bheads) == 1:
4050 if len(bheads) == 1:
4051 if len(repo.heads()) > 1:
4051 if len(repo.heads()) > 1:
4052 raise util.Abort(_("branch '%s' has one head - "
4052 raise util.Abort(_("branch '%s' has one head - "
4053 "please merge with an explicit rev")
4053 "please merge with an explicit rev")
4054 % branch,
4054 % branch,
4055 hint=_("run 'hg heads' to see all heads"))
4055 hint=_("run 'hg heads' to see all heads"))
4056 msg, hint = _('nothing to merge'), None
4056 msg, hint = _('nothing to merge'), None
4057 if parent != repo.lookup(branch):
4057 if parent != repo.lookup(branch):
4058 hint = _("use 'hg update' instead")
4058 hint = _("use 'hg update' instead")
4059 raise util.Abort(msg, hint=hint)
4059 raise util.Abort(msg, hint=hint)
4060
4060
4061 if parent not in bheads:
4061 if parent not in bheads:
4062 raise util.Abort(_('working directory not at a head revision'),
4062 raise util.Abort(_('working directory not at a head revision'),
4063 hint=_("use 'hg update' or merge with an "
4063 hint=_("use 'hg update' or merge with an "
4064 "explicit revision"))
4064 "explicit revision"))
4065 node = parent == bheads[0] and bheads[-1] or bheads[0]
4065 node = parent == bheads[0] and bheads[-1] or bheads[0]
4066 else:
4066 else:
4067 node = scmutil.revsingle(repo, node).node()
4067 node = scmutil.revsingle(repo, node).node()
4068
4068
4069 if opts.get('preview'):
4069 if opts.get('preview'):
4070 # find nodes that are ancestors of p2 but not of p1
4070 # find nodes that are ancestors of p2 but not of p1
4071 p1 = repo.lookup('.')
4071 p1 = repo.lookup('.')
4072 p2 = repo.lookup(node)
4072 p2 = repo.lookup(node)
4073 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4073 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4074
4074
4075 displayer = cmdutil.show_changeset(ui, repo, opts)
4075 displayer = cmdutil.show_changeset(ui, repo, opts)
4076 for node in nodes:
4076 for node in nodes:
4077 displayer.show(repo[node])
4077 displayer.show(repo[node])
4078 displayer.close()
4078 displayer.close()
4079 return 0
4079 return 0
4080
4080
4081 try:
4081 try:
4082 # ui.forcemerge is an internal variable, do not document
4082 # ui.forcemerge is an internal variable, do not document
4083 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
4083 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
4084 return hg.merge(repo, node, force=opts.get('force'))
4084 return hg.merge(repo, node, force=opts.get('force'))
4085 finally:
4085 finally:
4086 ui.setconfig('ui', 'forcemerge', '')
4086 ui.setconfig('ui', 'forcemerge', '')
4087
4087
4088 @command('outgoing|out',
4088 @command('outgoing|out',
4089 [('f', 'force', None, _('run even when the destination is unrelated')),
4089 [('f', 'force', None, _('run even when the destination is unrelated')),
4090 ('r', 'rev', [],
4090 ('r', 'rev', [],
4091 _('a changeset intended to be included in the destination'), _('REV')),
4091 _('a changeset intended to be included in the destination'), _('REV')),
4092 ('n', 'newest-first', None, _('show newest record first')),
4092 ('n', 'newest-first', None, _('show newest record first')),
4093 ('B', 'bookmarks', False, _('compare bookmarks')),
4093 ('B', 'bookmarks', False, _('compare bookmarks')),
4094 ('b', 'branch', [], _('a specific branch you would like to push'),
4094 ('b', 'branch', [], _('a specific branch you would like to push'),
4095 _('BRANCH')),
4095 _('BRANCH')),
4096 ] + logopts + remoteopts + subrepoopts,
4096 ] + logopts + remoteopts + subrepoopts,
4097 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
4097 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
4098 def outgoing(ui, repo, dest=None, **opts):
4098 def outgoing(ui, repo, dest=None, **opts):
4099 """show changesets not found in the destination
4099 """show changesets not found in the destination
4100
4100
4101 Show changesets not found in the specified destination repository
4101 Show changesets not found in the specified destination repository
4102 or the default push location. These are the changesets that would
4102 or the default push location. These are the changesets that would
4103 be pushed if a push was requested.
4103 be pushed if a push was requested.
4104
4104
4105 See pull for details of valid destination formats.
4105 See pull for details of valid destination formats.
4106
4106
4107 Returns 0 if there are outgoing changes, 1 otherwise.
4107 Returns 0 if there are outgoing changes, 1 otherwise.
4108 """
4108 """
4109
4109
4110 if opts.get('bookmarks'):
4110 if opts.get('bookmarks'):
4111 dest = ui.expandpath(dest or 'default-push', dest or 'default')
4111 dest = ui.expandpath(dest or 'default-push', dest or 'default')
4112 dest, branches = hg.parseurl(dest, opts.get('branch'))
4112 dest, branches = hg.parseurl(dest, opts.get('branch'))
4113 other = hg.peer(repo, opts, dest)
4113 other = hg.peer(repo, opts, dest)
4114 if 'bookmarks' not in other.listkeys('namespaces'):
4114 if 'bookmarks' not in other.listkeys('namespaces'):
4115 ui.warn(_("remote doesn't support bookmarks\n"))
4115 ui.warn(_("remote doesn't support bookmarks\n"))
4116 return 0
4116 return 0
4117 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
4117 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
4118 return bookmarks.diff(ui, other, repo)
4118 return bookmarks.diff(ui, other, repo)
4119
4119
4120 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
4120 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
4121 try:
4121 try:
4122 return hg.outgoing(ui, repo, dest, opts)
4122 return hg.outgoing(ui, repo, dest, opts)
4123 finally:
4123 finally:
4124 del repo._subtoppath
4124 del repo._subtoppath
4125
4125
4126 @command('parents',
4126 @command('parents',
4127 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
4127 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
4128 ] + templateopts,
4128 ] + templateopts,
4129 _('[-r REV] [FILE]'))
4129 _('[-r REV] [FILE]'))
4130 def parents(ui, repo, file_=None, **opts):
4130 def parents(ui, repo, file_=None, **opts):
4131 """show the parents of the working directory or revision
4131 """show the parents of the working directory or revision
4132
4132
4133 Print the working directory's parent revisions. If a revision is
4133 Print the working directory's parent revisions. If a revision is
4134 given via -r/--rev, the parent of that revision will be printed.
4134 given via -r/--rev, the parent of that revision will be printed.
4135 If a file argument is given, the revision in which the file was
4135 If a file argument is given, the revision in which the file was
4136 last changed (before the working directory revision or the
4136 last changed (before the working directory revision or the
4137 argument to --rev if given) is printed.
4137 argument to --rev if given) is printed.
4138
4138
4139 Returns 0 on success.
4139 Returns 0 on success.
4140 """
4140 """
4141
4141
4142 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
4142 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
4143
4143
4144 if file_:
4144 if file_:
4145 m = scmutil.match(ctx, (file_,), opts)
4145 m = scmutil.match(ctx, (file_,), opts)
4146 if m.anypats() or len(m.files()) != 1:
4146 if m.anypats() or len(m.files()) != 1:
4147 raise util.Abort(_('can only specify an explicit filename'))
4147 raise util.Abort(_('can only specify an explicit filename'))
4148 file_ = m.files()[0]
4148 file_ = m.files()[0]
4149 filenodes = []
4149 filenodes = []
4150 for cp in ctx.parents():
4150 for cp in ctx.parents():
4151 if not cp:
4151 if not cp:
4152 continue
4152 continue
4153 try:
4153 try:
4154 filenodes.append(cp.filenode(file_))
4154 filenodes.append(cp.filenode(file_))
4155 except error.LookupError:
4155 except error.LookupError:
4156 pass
4156 pass
4157 if not filenodes:
4157 if not filenodes:
4158 raise util.Abort(_("'%s' not found in manifest!") % file_)
4158 raise util.Abort(_("'%s' not found in manifest!") % file_)
4159 fl = repo.file(file_)
4159 fl = repo.file(file_)
4160 p = [repo.lookup(fl.linkrev(fl.rev(fn))) for fn in filenodes]
4160 p = [repo.lookup(fl.linkrev(fl.rev(fn))) for fn in filenodes]
4161 else:
4161 else:
4162 p = [cp.node() for cp in ctx.parents()]
4162 p = [cp.node() for cp in ctx.parents()]
4163
4163
4164 displayer = cmdutil.show_changeset(ui, repo, opts)
4164 displayer = cmdutil.show_changeset(ui, repo, opts)
4165 for n in p:
4165 for n in p:
4166 if n != nullid:
4166 if n != nullid:
4167 displayer.show(repo[n])
4167 displayer.show(repo[n])
4168 displayer.close()
4168 displayer.close()
4169
4169
4170 @command('paths', [], _('[NAME]'))
4170 @command('paths', [], _('[NAME]'))
4171 def paths(ui, repo, search=None):
4171 def paths(ui, repo, search=None):
4172 """show aliases for remote repositories
4172 """show aliases for remote repositories
4173
4173
4174 Show definition of symbolic path name NAME. If no name is given,
4174 Show definition of symbolic path name NAME. If no name is given,
4175 show definition of all available names.
4175 show definition of all available names.
4176
4176
4177 Option -q/--quiet suppresses all output when searching for NAME
4177 Option -q/--quiet suppresses all output when searching for NAME
4178 and shows only the path names when listing all definitions.
4178 and shows only the path names when listing all definitions.
4179
4179
4180 Path names are defined in the [paths] section of your
4180 Path names are defined in the [paths] section of your
4181 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
4181 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
4182 repository, ``.hg/hgrc`` is used, too.
4182 repository, ``.hg/hgrc`` is used, too.
4183
4183
4184 The path names ``default`` and ``default-push`` have a special
4184 The path names ``default`` and ``default-push`` have a special
4185 meaning. When performing a push or pull operation, they are used
4185 meaning. When performing a push or pull operation, they are used
4186 as fallbacks if no location is specified on the command-line.
4186 as fallbacks if no location is specified on the command-line.
4187 When ``default-push`` is set, it will be used for push and
4187 When ``default-push`` is set, it will be used for push and
4188 ``default`` will be used for pull; otherwise ``default`` is used
4188 ``default`` will be used for pull; otherwise ``default`` is used
4189 as the fallback for both. When cloning a repository, the clone
4189 as the fallback for both. When cloning a repository, the clone
4190 source is written as ``default`` in ``.hg/hgrc``. Note that
4190 source is written as ``default`` in ``.hg/hgrc``. Note that
4191 ``default`` and ``default-push`` apply to all inbound (e.g.
4191 ``default`` and ``default-push`` apply to all inbound (e.g.
4192 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email` and
4192 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email` and
4193 :hg:`bundle`) operations.
4193 :hg:`bundle`) operations.
4194
4194
4195 See :hg:`help urls` for more information.
4195 See :hg:`help urls` for more information.
4196
4196
4197 Returns 0 on success.
4197 Returns 0 on success.
4198 """
4198 """
4199 if search:
4199 if search:
4200 for name, path in ui.configitems("paths"):
4200 for name, path in ui.configitems("paths"):
4201 if name == search:
4201 if name == search:
4202 ui.status("%s\n" % util.hidepassword(path))
4202 ui.status("%s\n" % util.hidepassword(path))
4203 return
4203 return
4204 if not ui.quiet:
4204 if not ui.quiet:
4205 ui.warn(_("not found!\n"))
4205 ui.warn(_("not found!\n"))
4206 return 1
4206 return 1
4207 else:
4207 else:
4208 for name, path in ui.configitems("paths"):
4208 for name, path in ui.configitems("paths"):
4209 if ui.quiet:
4209 if ui.quiet:
4210 ui.write("%s\n" % name)
4210 ui.write("%s\n" % name)
4211 else:
4211 else:
4212 ui.write("%s = %s\n" % (name, util.hidepassword(path)))
4212 ui.write("%s = %s\n" % (name, util.hidepassword(path)))
4213
4213
4214 def postincoming(ui, repo, modheads, optupdate, checkout):
4214 def postincoming(ui, repo, modheads, optupdate, checkout):
4215 if modheads == 0:
4215 if modheads == 0:
4216 return
4216 return
4217 if optupdate:
4217 if optupdate:
4218 try:
4218 try:
4219 return hg.update(repo, checkout)
4219 return hg.update(repo, checkout)
4220 except util.Abort, inst:
4220 except util.Abort, inst:
4221 ui.warn(_("not updating: %s\n" % str(inst)))
4221 ui.warn(_("not updating: %s\n" % str(inst)))
4222 return 0
4222 return 0
4223 if modheads > 1:
4223 if modheads > 1:
4224 currentbranchheads = len(repo.branchheads())
4224 currentbranchheads = len(repo.branchheads())
4225 if currentbranchheads == modheads:
4225 if currentbranchheads == modheads:
4226 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
4226 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
4227 elif currentbranchheads > 1:
4227 elif currentbranchheads > 1:
4228 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to merge)\n"))
4228 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to merge)\n"))
4229 else:
4229 else:
4230 ui.status(_("(run 'hg heads' to see heads)\n"))
4230 ui.status(_("(run 'hg heads' to see heads)\n"))
4231 else:
4231 else:
4232 ui.status(_("(run 'hg update' to get a working copy)\n"))
4232 ui.status(_("(run 'hg update' to get a working copy)\n"))
4233
4233
4234 @command('^pull',
4234 @command('^pull',
4235 [('u', 'update', None,
4235 [('u', 'update', None,
4236 _('update to new branch head if changesets were pulled')),
4236 _('update to new branch head if changesets were pulled')),
4237 ('f', 'force', None, _('run even when remote repository is unrelated')),
4237 ('f', 'force', None, _('run even when remote repository is unrelated')),
4238 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4238 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4239 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4239 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4240 ('b', 'branch', [], _('a specific branch you would like to pull'),
4240 ('b', 'branch', [], _('a specific branch you would like to pull'),
4241 _('BRANCH')),
4241 _('BRANCH')),
4242 ] + remoteopts,
4242 ] + remoteopts,
4243 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
4243 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
4244 def pull(ui, repo, source="default", **opts):
4244 def pull(ui, repo, source="default", **opts):
4245 """pull changes from the specified source
4245 """pull changes from the specified source
4246
4246
4247 Pull changes from a remote repository to a local one.
4247 Pull changes from a remote repository to a local one.
4248
4248
4249 This finds all changes from the repository at the specified path
4249 This finds all changes from the repository at the specified path
4250 or URL and adds them to a local repository (the current one unless
4250 or URL and adds them to a local repository (the current one unless
4251 -R is specified). By default, this does not update the copy of the
4251 -R is specified). By default, this does not update the copy of the
4252 project in the working directory.
4252 project in the working directory.
4253
4253
4254 Use :hg:`incoming` if you want to see what would have been added
4254 Use :hg:`incoming` if you want to see what would have been added
4255 by a pull at the time you issued this command. If you then decide
4255 by a pull at the time you issued this command. If you then decide
4256 to add those changes to the repository, you should use :hg:`pull
4256 to add those changes to the repository, you should use :hg:`pull
4257 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
4257 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
4258
4258
4259 If SOURCE is omitted, the 'default' path will be used.
4259 If SOURCE is omitted, the 'default' path will be used.
4260 See :hg:`help urls` for more information.
4260 See :hg:`help urls` for more information.
4261
4261
4262 Returns 0 on success, 1 if an update had unresolved files.
4262 Returns 0 on success, 1 if an update had unresolved files.
4263 """
4263 """
4264 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
4264 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
4265 other = hg.peer(repo, opts, source)
4265 other = hg.peer(repo, opts, source)
4266 ui.status(_('pulling from %s\n') % util.hidepassword(source))
4266 ui.status(_('pulling from %s\n') % util.hidepassword(source))
4267 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
4267 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
4268
4268
4269 if opts.get('bookmark'):
4269 if opts.get('bookmark'):
4270 if not revs:
4270 if not revs:
4271 revs = []
4271 revs = []
4272 rb = other.listkeys('bookmarks')
4272 rb = other.listkeys('bookmarks')
4273 for b in opts['bookmark']:
4273 for b in opts['bookmark']:
4274 if b not in rb:
4274 if b not in rb:
4275 raise util.Abort(_('remote bookmark %s not found!') % b)
4275 raise util.Abort(_('remote bookmark %s not found!') % b)
4276 revs.append(rb[b])
4276 revs.append(rb[b])
4277
4277
4278 if revs:
4278 if revs:
4279 try:
4279 try:
4280 revs = [other.lookup(rev) for rev in revs]
4280 revs = [other.lookup(rev) for rev in revs]
4281 except error.CapabilityError:
4281 except error.CapabilityError:
4282 err = _("other repository doesn't support revision lookup, "
4282 err = _("other repository doesn't support revision lookup, "
4283 "so a rev cannot be specified.")
4283 "so a rev cannot be specified.")
4284 raise util.Abort(err)
4284 raise util.Abort(err)
4285
4285
4286 modheads = repo.pull(other, heads=revs, force=opts.get('force'))
4286 modheads = repo.pull(other, heads=revs, force=opts.get('force'))
4287 bookmarks.updatefromremote(ui, repo, other, source)
4287 bookmarks.updatefromremote(ui, repo, other, source)
4288 if checkout:
4288 if checkout:
4289 checkout = str(repo.changelog.rev(other.lookup(checkout)))
4289 checkout = str(repo.changelog.rev(other.lookup(checkout)))
4290 repo._subtoppath = source
4290 repo._subtoppath = source
4291 try:
4291 try:
4292 ret = postincoming(ui, repo, modheads, opts.get('update'), checkout)
4292 ret = postincoming(ui, repo, modheads, opts.get('update'), checkout)
4293
4293
4294 finally:
4294 finally:
4295 del repo._subtoppath
4295 del repo._subtoppath
4296
4296
4297 # update specified bookmarks
4297 # update specified bookmarks
4298 if opts.get('bookmark'):
4298 if opts.get('bookmark'):
4299 for b in opts['bookmark']:
4299 for b in opts['bookmark']:
4300 # explicit pull overrides local bookmark if any
4300 # explicit pull overrides local bookmark if any
4301 ui.status(_("importing bookmark %s\n") % b)
4301 ui.status(_("importing bookmark %s\n") % b)
4302 repo._bookmarks[b] = repo[rb[b]].node()
4302 repo._bookmarks[b] = repo[rb[b]].node()
4303 bookmarks.write(repo)
4303 bookmarks.write(repo)
4304
4304
4305 return ret
4305 return ret
4306
4306
4307 @command('^push',
4307 @command('^push',
4308 [('f', 'force', None, _('force push')),
4308 [('f', 'force', None, _('force push')),
4309 ('r', 'rev', [],
4309 ('r', 'rev', [],
4310 _('a changeset intended to be included in the destination'),
4310 _('a changeset intended to be included in the destination'),
4311 _('REV')),
4311 _('REV')),
4312 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4312 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4313 ('b', 'branch', [],
4313 ('b', 'branch', [],
4314 _('a specific branch you would like to push'), _('BRANCH')),
4314 _('a specific branch you would like to push'), _('BRANCH')),
4315 ('', 'new-branch', False, _('allow pushing a new branch')),
4315 ('', 'new-branch', False, _('allow pushing a new branch')),
4316 ] + remoteopts,
4316 ] + remoteopts,
4317 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
4317 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
4318 def push(ui, repo, dest=None, **opts):
4318 def push(ui, repo, dest=None, **opts):
4319 """push changes to the specified destination
4319 """push changes to the specified destination
4320
4320
4321 Push changesets from the local repository to the specified
4321 Push changesets from the local repository to the specified
4322 destination.
4322 destination.
4323
4323
4324 This operation is symmetrical to pull: it is identical to a pull
4324 This operation is symmetrical to pull: it is identical to a pull
4325 in the destination repository from the current one.
4325 in the destination repository from the current one.
4326
4326
4327 By default, push will not allow creation of new heads at the
4327 By default, push will not allow creation of new heads at the
4328 destination, since multiple heads would make it unclear which head
4328 destination, since multiple heads would make it unclear which head
4329 to use. In this situation, it is recommended to pull and merge
4329 to use. In this situation, it is recommended to pull and merge
4330 before pushing.
4330 before pushing.
4331
4331
4332 Use --new-branch if you want to allow push to create a new named
4332 Use --new-branch if you want to allow push to create a new named
4333 branch that is not present at the destination. This allows you to
4333 branch that is not present at the destination. This allows you to
4334 only create a new branch without forcing other changes.
4334 only create a new branch without forcing other changes.
4335
4335
4336 Use -f/--force to override the default behavior and push all
4336 Use -f/--force to override the default behavior and push all
4337 changesets on all branches.
4337 changesets on all branches.
4338
4338
4339 If -r/--rev is used, the specified revision and all its ancestors
4339 If -r/--rev is used, the specified revision and all its ancestors
4340 will be pushed to the remote repository.
4340 will be pushed to the remote repository.
4341
4341
4342 Please see :hg:`help urls` for important details about ``ssh://``
4342 Please see :hg:`help urls` for important details about ``ssh://``
4343 URLs. If DESTINATION is omitted, a default path will be used.
4343 URLs. If DESTINATION is omitted, a default path will be used.
4344
4344
4345 Returns 0 if push was successful, 1 if nothing to push.
4345 Returns 0 if push was successful, 1 if nothing to push.
4346 """
4346 """
4347
4347
4348 if opts.get('bookmark'):
4348 if opts.get('bookmark'):
4349 for b in opts['bookmark']:
4349 for b in opts['bookmark']:
4350 # translate -B options to -r so changesets get pushed
4350 # translate -B options to -r so changesets get pushed
4351 if b in repo._bookmarks:
4351 if b in repo._bookmarks:
4352 opts.setdefault('rev', []).append(b)
4352 opts.setdefault('rev', []).append(b)
4353 else:
4353 else:
4354 # if we try to push a deleted bookmark, translate it to null
4354 # if we try to push a deleted bookmark, translate it to null
4355 # this lets simultaneous -r, -b options continue working
4355 # this lets simultaneous -r, -b options continue working
4356 opts.setdefault('rev', []).append("null")
4356 opts.setdefault('rev', []).append("null")
4357
4357
4358 dest = ui.expandpath(dest or 'default-push', dest or 'default')
4358 dest = ui.expandpath(dest or 'default-push', dest or 'default')
4359 dest, branches = hg.parseurl(dest, opts.get('branch'))
4359 dest, branches = hg.parseurl(dest, opts.get('branch'))
4360 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4360 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4361 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4361 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4362 other = hg.peer(repo, opts, dest)
4362 other = hg.peer(repo, opts, dest)
4363 if revs:
4363 if revs:
4364 revs = [repo.lookup(rev) for rev in revs]
4364 revs = [repo.lookup(rev) for rev in revs]
4365
4365
4366 repo._subtoppath = dest
4366 repo._subtoppath = dest
4367 try:
4367 try:
4368 # push subrepos depth-first for coherent ordering
4368 # push subrepos depth-first for coherent ordering
4369 c = repo['']
4369 c = repo['']
4370 subs = c.substate # only repos that are committed
4370 subs = c.substate # only repos that are committed
4371 for s in sorted(subs):
4371 for s in sorted(subs):
4372 if not c.sub(s).push(opts):
4372 if not c.sub(s).push(opts):
4373 return False
4373 return False
4374 finally:
4374 finally:
4375 del repo._subtoppath
4375 del repo._subtoppath
4376 result = repo.push(other, opts.get('force'), revs=revs,
4376 result = repo.push(other, opts.get('force'), revs=revs,
4377 newbranch=opts.get('new_branch'))
4377 newbranch=opts.get('new_branch'))
4378
4378
4379 result = (result == 0)
4379 result = (result == 0)
4380
4380
4381 if opts.get('bookmark'):
4381 if opts.get('bookmark'):
4382 rb = other.listkeys('bookmarks')
4382 rb = other.listkeys('bookmarks')
4383 for b in opts['bookmark']:
4383 for b in opts['bookmark']:
4384 # explicit push overrides remote bookmark if any
4384 # explicit push overrides remote bookmark if any
4385 if b in repo._bookmarks:
4385 if b in repo._bookmarks:
4386 ui.status(_("exporting bookmark %s\n") % b)
4386 ui.status(_("exporting bookmark %s\n") % b)
4387 new = repo[b].hex()
4387 new = repo[b].hex()
4388 elif b in rb:
4388 elif b in rb:
4389 ui.status(_("deleting remote bookmark %s\n") % b)
4389 ui.status(_("deleting remote bookmark %s\n") % b)
4390 new = '' # delete
4390 new = '' # delete
4391 else:
4391 else:
4392 ui.warn(_('bookmark %s does not exist on the local '
4392 ui.warn(_('bookmark %s does not exist on the local '
4393 'or remote repository!\n') % b)
4393 'or remote repository!\n') % b)
4394 return 2
4394 return 2
4395 old = rb.get(b, '')
4395 old = rb.get(b, '')
4396 r = other.pushkey('bookmarks', b, old, new)
4396 r = other.pushkey('bookmarks', b, old, new)
4397 if not r:
4397 if not r:
4398 ui.warn(_('updating bookmark %s failed!\n') % b)
4398 ui.warn(_('updating bookmark %s failed!\n') % b)
4399 if not result:
4399 if not result:
4400 result = 2
4400 result = 2
4401
4401
4402 return result
4402 return result
4403
4403
4404 @command('recover', [])
4404 @command('recover', [])
4405 def recover(ui, repo):
4405 def recover(ui, repo):
4406 """roll back an interrupted transaction
4406 """roll back an interrupted transaction
4407
4407
4408 Recover from an interrupted commit or pull.
4408 Recover from an interrupted commit or pull.
4409
4409
4410 This command tries to fix the repository status after an
4410 This command tries to fix the repository status after an
4411 interrupted operation. It should only be necessary when Mercurial
4411 interrupted operation. It should only be necessary when Mercurial
4412 suggests it.
4412 suggests it.
4413
4413
4414 Returns 0 if successful, 1 if nothing to recover or verify fails.
4414 Returns 0 if successful, 1 if nothing to recover or verify fails.
4415 """
4415 """
4416 if repo.recover():
4416 if repo.recover():
4417 return hg.verify(repo)
4417 return hg.verify(repo)
4418 return 1
4418 return 1
4419
4419
4420 @command('^remove|rm',
4420 @command('^remove|rm',
4421 [('A', 'after', None, _('record delete for missing files')),
4421 [('A', 'after', None, _('record delete for missing files')),
4422 ('f', 'force', None,
4422 ('f', 'force', None,
4423 _('remove (and delete) file even if added or modified')),
4423 _('remove (and delete) file even if added or modified')),
4424 ] + walkopts,
4424 ] + walkopts,
4425 _('[OPTION]... FILE...'))
4425 _('[OPTION]... FILE...'))
4426 def remove(ui, repo, *pats, **opts):
4426 def remove(ui, repo, *pats, **opts):
4427 """remove the specified files on the next commit
4427 """remove the specified files on the next commit
4428
4428
4429 Schedule the indicated files for removal from the current branch.
4429 Schedule the indicated files for removal from the current branch.
4430
4430
4431 This command schedules the files to be removed at the next commit.
4431 This command schedules the files to be removed at the next commit.
4432 To undo a remove before that, see :hg:`revert`. To undo added
4432 To undo a remove before that, see :hg:`revert`. To undo added
4433 files, see :hg:`forget`.
4433 files, see :hg:`forget`.
4434
4434
4435 .. container:: verbose
4435 .. container:: verbose
4436
4436
4437 -A/--after can be used to remove only files that have already
4437 -A/--after can be used to remove only files that have already
4438 been deleted, -f/--force can be used to force deletion, and -Af
4438 been deleted, -f/--force can be used to force deletion, and -Af
4439 can be used to remove files from the next revision without
4439 can be used to remove files from the next revision without
4440 deleting them from the working directory.
4440 deleting them from the working directory.
4441
4441
4442 The following table details the behavior of remove for different
4442 The following table details the behavior of remove for different
4443 file states (columns) and option combinations (rows). The file
4443 file states (columns) and option combinations (rows). The file
4444 states are Added [A], Clean [C], Modified [M] and Missing [!]
4444 states are Added [A], Clean [C], Modified [M] and Missing [!]
4445 (as reported by :hg:`status`). The actions are Warn, Remove
4445 (as reported by :hg:`status`). The actions are Warn, Remove
4446 (from branch) and Delete (from disk):
4446 (from branch) and Delete (from disk):
4447
4447
4448 ======= == == == ==
4448 ======= == == == ==
4449 A C M !
4449 A C M !
4450 ======= == == == ==
4450 ======= == == == ==
4451 none W RD W R
4451 none W RD W R
4452 -f R RD RD R
4452 -f R RD RD R
4453 -A W W W R
4453 -A W W W R
4454 -Af R R R R
4454 -Af R R R R
4455 ======= == == == ==
4455 ======= == == == ==
4456
4456
4457 Note that remove never deletes files in Added [A] state from the
4457 Note that remove never deletes files in Added [A] state from the
4458 working directory, not even if option --force is specified.
4458 working directory, not even if option --force is specified.
4459
4459
4460 Returns 0 on success, 1 if any warnings encountered.
4460 Returns 0 on success, 1 if any warnings encountered.
4461 """
4461 """
4462
4462
4463 ret = 0
4463 ret = 0
4464 after, force = opts.get('after'), opts.get('force')
4464 after, force = opts.get('after'), opts.get('force')
4465 if not pats and not after:
4465 if not pats and not after:
4466 raise util.Abort(_('no files specified'))
4466 raise util.Abort(_('no files specified'))
4467
4467
4468 m = scmutil.match(repo[None], pats, opts)
4468 m = scmutil.match(repo[None], pats, opts)
4469 s = repo.status(match=m, clean=True)
4469 s = repo.status(match=m, clean=True)
4470 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
4470 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
4471
4471
4472 for f in m.files():
4472 for f in m.files():
4473 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
4473 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
4474 if os.path.exists(m.rel(f)):
4474 if os.path.exists(m.rel(f)):
4475 ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
4475 ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
4476 ret = 1
4476 ret = 1
4477
4477
4478 if force:
4478 if force:
4479 list = modified + deleted + clean + added
4479 list = modified + deleted + clean + added
4480 elif after:
4480 elif after:
4481 list = deleted
4481 list = deleted
4482 for f in modified + added + clean:
4482 for f in modified + added + clean:
4483 ui.warn(_('not removing %s: file still exists (use -f'
4483 ui.warn(_('not removing %s: file still exists (use -f'
4484 ' to force removal)\n') % m.rel(f))
4484 ' to force removal)\n') % m.rel(f))
4485 ret = 1
4485 ret = 1
4486 else:
4486 else:
4487 list = deleted + clean
4487 list = deleted + clean
4488 for f in modified:
4488 for f in modified:
4489 ui.warn(_('not removing %s: file is modified (use -f'
4489 ui.warn(_('not removing %s: file is modified (use -f'
4490 ' to force removal)\n') % m.rel(f))
4490 ' to force removal)\n') % m.rel(f))
4491 ret = 1
4491 ret = 1
4492 for f in added:
4492 for f in added:
4493 ui.warn(_('not removing %s: file has been marked for add'
4493 ui.warn(_('not removing %s: file has been marked for add'
4494 ' (use forget to undo)\n') % m.rel(f))
4494 ' (use forget to undo)\n') % m.rel(f))
4495 ret = 1
4495 ret = 1
4496
4496
4497 for f in sorted(list):
4497 for f in sorted(list):
4498 if ui.verbose or not m.exact(f):
4498 if ui.verbose or not m.exact(f):
4499 ui.status(_('removing %s\n') % m.rel(f))
4499 ui.status(_('removing %s\n') % m.rel(f))
4500
4500
4501 wlock = repo.wlock()
4501 wlock = repo.wlock()
4502 try:
4502 try:
4503 if not after:
4503 if not after:
4504 for f in list:
4504 for f in list:
4505 if f in added:
4505 if f in added:
4506 continue # we never unlink added files on remove
4506 continue # we never unlink added files on remove
4507 try:
4507 try:
4508 util.unlinkpath(repo.wjoin(f))
4508 util.unlinkpath(repo.wjoin(f))
4509 except OSError, inst:
4509 except OSError, inst:
4510 if inst.errno != errno.ENOENT:
4510 if inst.errno != errno.ENOENT:
4511 raise
4511 raise
4512 repo[None].forget(list)
4512 repo[None].forget(list)
4513 finally:
4513 finally:
4514 wlock.release()
4514 wlock.release()
4515
4515
4516 return ret
4516 return ret
4517
4517
4518 @command('rename|move|mv',
4518 @command('rename|move|mv',
4519 [('A', 'after', None, _('record a rename that has already occurred')),
4519 [('A', 'after', None, _('record a rename that has already occurred')),
4520 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4520 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4521 ] + walkopts + dryrunopts,
4521 ] + walkopts + dryrunopts,
4522 _('[OPTION]... SOURCE... DEST'))
4522 _('[OPTION]... SOURCE... DEST'))
4523 def rename(ui, repo, *pats, **opts):
4523 def rename(ui, repo, *pats, **opts):
4524 """rename files; equivalent of copy + remove
4524 """rename files; equivalent of copy + remove
4525
4525
4526 Mark dest as copies of sources; mark sources for deletion. If dest
4526 Mark dest as copies of sources; mark sources for deletion. If dest
4527 is a directory, copies are put in that directory. If dest is a
4527 is a directory, copies are put in that directory. If dest is a
4528 file, there can only be one source.
4528 file, there can only be one source.
4529
4529
4530 By default, this command copies the contents of files as they
4530 By default, this command copies the contents of files as they
4531 exist in the working directory. If invoked with -A/--after, the
4531 exist in the working directory. If invoked with -A/--after, the
4532 operation is recorded, but no copying is performed.
4532 operation is recorded, but no copying is performed.
4533
4533
4534 This command takes effect at the next commit. To undo a rename
4534 This command takes effect at the next commit. To undo a rename
4535 before that, see :hg:`revert`.
4535 before that, see :hg:`revert`.
4536
4536
4537 Returns 0 on success, 1 if errors are encountered.
4537 Returns 0 on success, 1 if errors are encountered.
4538 """
4538 """
4539 wlock = repo.wlock(False)
4539 wlock = repo.wlock(False)
4540 try:
4540 try:
4541 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4541 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4542 finally:
4542 finally:
4543 wlock.release()
4543 wlock.release()
4544
4544
4545 @command('resolve',
4545 @command('resolve',
4546 [('a', 'all', None, _('select all unresolved files')),
4546 [('a', 'all', None, _('select all unresolved files')),
4547 ('l', 'list', None, _('list state of files needing merge')),
4547 ('l', 'list', None, _('list state of files needing merge')),
4548 ('m', 'mark', None, _('mark files as resolved')),
4548 ('m', 'mark', None, _('mark files as resolved')),
4549 ('u', 'unmark', None, _('mark files as unresolved')),
4549 ('u', 'unmark', None, _('mark files as unresolved')),
4550 ('n', 'no-status', None, _('hide status prefix'))]
4550 ('n', 'no-status', None, _('hide status prefix'))]
4551 + mergetoolopts + walkopts,
4551 + mergetoolopts + walkopts,
4552 _('[OPTION]... [FILE]...'))
4552 _('[OPTION]... [FILE]...'))
4553 def resolve(ui, repo, *pats, **opts):
4553 def resolve(ui, repo, *pats, **opts):
4554 """redo merges or set/view the merge status of files
4554 """redo merges or set/view the merge status of files
4555
4555
4556 Merges with unresolved conflicts are often the result of
4556 Merges with unresolved conflicts are often the result of
4557 non-interactive merging using the ``internal:merge`` configuration
4557 non-interactive merging using the ``internal:merge`` configuration
4558 setting, or a command-line merge tool like ``diff3``. The resolve
4558 setting, or a command-line merge tool like ``diff3``. The resolve
4559 command is used to manage the files involved in a merge, after
4559 command is used to manage the files involved in a merge, after
4560 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4560 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4561 working directory must have two parents).
4561 working directory must have two parents).
4562
4562
4563 The resolve command can be used in the following ways:
4563 The resolve command can be used in the following ways:
4564
4564
4565 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
4565 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
4566 files, discarding any previous merge attempts. Re-merging is not
4566 files, discarding any previous merge attempts. Re-merging is not
4567 performed for files already marked as resolved. Use ``--all/-a``
4567 performed for files already marked as resolved. Use ``--all/-a``
4568 to select all unresolved files. ``--tool`` can be used to specify
4568 to select all unresolved files. ``--tool`` can be used to specify
4569 the merge tool used for the given files. It overrides the HGMERGE
4569 the merge tool used for the given files. It overrides the HGMERGE
4570 environment variable and your configuration files. Previous file
4570 environment variable and your configuration files. Previous file
4571 contents are saved with a ``.orig`` suffix.
4571 contents are saved with a ``.orig`` suffix.
4572
4572
4573 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4573 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4574 (e.g. after having manually fixed-up the files). The default is
4574 (e.g. after having manually fixed-up the files). The default is
4575 to mark all unresolved files.
4575 to mark all unresolved files.
4576
4576
4577 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4577 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4578 default is to mark all resolved files.
4578 default is to mark all resolved files.
4579
4579
4580 - :hg:`resolve -l`: list files which had or still have conflicts.
4580 - :hg:`resolve -l`: list files which had or still have conflicts.
4581 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4581 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4582
4582
4583 Note that Mercurial will not let you commit files with unresolved
4583 Note that Mercurial will not let you commit files with unresolved
4584 merge conflicts. You must use :hg:`resolve -m ...` before you can
4584 merge conflicts. You must use :hg:`resolve -m ...` before you can
4585 commit after a conflicting merge.
4585 commit after a conflicting merge.
4586
4586
4587 Returns 0 on success, 1 if any files fail a resolve attempt.
4587 Returns 0 on success, 1 if any files fail a resolve attempt.
4588 """
4588 """
4589
4589
4590 all, mark, unmark, show, nostatus = \
4590 all, mark, unmark, show, nostatus = \
4591 [opts.get(o) for o in 'all mark unmark list no_status'.split()]
4591 [opts.get(o) for o in 'all mark unmark list no_status'.split()]
4592
4592
4593 if (show and (mark or unmark)) or (mark and unmark):
4593 if (show and (mark or unmark)) or (mark and unmark):
4594 raise util.Abort(_("too many options specified"))
4594 raise util.Abort(_("too many options specified"))
4595 if pats and all:
4595 if pats and all:
4596 raise util.Abort(_("can't specify --all and patterns"))
4596 raise util.Abort(_("can't specify --all and patterns"))
4597 if not (all or pats or show or mark or unmark):
4597 if not (all or pats or show or mark or unmark):
4598 raise util.Abort(_('no files or directories specified; '
4598 raise util.Abort(_('no files or directories specified; '
4599 'use --all to remerge all files'))
4599 'use --all to remerge all files'))
4600
4600
4601 ms = mergemod.mergestate(repo)
4601 ms = mergemod.mergestate(repo)
4602 m = scmutil.match(repo[None], pats, opts)
4602 m = scmutil.match(repo[None], pats, opts)
4603 ret = 0
4603 ret = 0
4604
4604
4605 for f in ms:
4605 for f in ms:
4606 if m(f):
4606 if m(f):
4607 if show:
4607 if show:
4608 if nostatus:
4608 if nostatus:
4609 ui.write("%s\n" % f)
4609 ui.write("%s\n" % f)
4610 else:
4610 else:
4611 ui.write("%s %s\n" % (ms[f].upper(), f),
4611 ui.write("%s %s\n" % (ms[f].upper(), f),
4612 label='resolve.' +
4612 label='resolve.' +
4613 {'u': 'unresolved', 'r': 'resolved'}[ms[f]])
4613 {'u': 'unresolved', 'r': 'resolved'}[ms[f]])
4614 elif mark:
4614 elif mark:
4615 ms.mark(f, "r")
4615 ms.mark(f, "r")
4616 elif unmark:
4616 elif unmark:
4617 ms.mark(f, "u")
4617 ms.mark(f, "u")
4618 else:
4618 else:
4619 wctx = repo[None]
4619 wctx = repo[None]
4620 mctx = wctx.parents()[-1]
4620 mctx = wctx.parents()[-1]
4621
4621
4622 # backup pre-resolve (merge uses .orig for its own purposes)
4622 # backup pre-resolve (merge uses .orig for its own purposes)
4623 a = repo.wjoin(f)
4623 a = repo.wjoin(f)
4624 util.copyfile(a, a + ".resolve")
4624 util.copyfile(a, a + ".resolve")
4625
4625
4626 try:
4626 try:
4627 # resolve file
4627 # resolve file
4628 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
4628 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
4629 if ms.resolve(f, wctx, mctx):
4629 if ms.resolve(f, wctx, mctx):
4630 ret = 1
4630 ret = 1
4631 finally:
4631 finally:
4632 ui.setconfig('ui', 'forcemerge', '')
4632 ui.setconfig('ui', 'forcemerge', '')
4633
4633
4634 # replace filemerge's .orig file with our resolve file
4634 # replace filemerge's .orig file with our resolve file
4635 util.rename(a + ".resolve", a + ".orig")
4635 util.rename(a + ".resolve", a + ".orig")
4636
4636
4637 ms.commit()
4637 ms.commit()
4638 return ret
4638 return ret
4639
4639
4640 @command('revert',
4640 @command('revert',
4641 [('a', 'all', None, _('revert all changes when no arguments given')),
4641 [('a', 'all', None, _('revert all changes when no arguments given')),
4642 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
4642 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
4643 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
4643 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
4644 ('C', 'no-backup', None, _('do not save backup copies of files')),
4644 ('C', 'no-backup', None, _('do not save backup copies of files')),
4645 ] + walkopts + dryrunopts,
4645 ] + walkopts + dryrunopts,
4646 _('[OPTION]... [-r REV] [NAME]...'))
4646 _('[OPTION]... [-r REV] [NAME]...'))
4647 def revert(ui, repo, *pats, **opts):
4647 def revert(ui, repo, *pats, **opts):
4648 """restore files to their checkout state
4648 """restore files to their checkout state
4649
4649
4650 .. note::
4650 .. note::
4651 To check out earlier revisions, you should use :hg:`update REV`.
4651 To check out earlier revisions, you should use :hg:`update REV`.
4652 To cancel a merge (and lose your changes), use :hg:`update --clean .`.
4652 To cancel a merge (and lose your changes), use :hg:`update --clean .`.
4653
4653
4654 With no revision specified, revert the specified files or directories
4654 With no revision specified, revert the specified files or directories
4655 to the contents they had in the parent of the working directory.
4655 to the contents they had in the parent of the working directory.
4656 This restores the contents of files to an unmodified
4656 This restores the contents of files to an unmodified
4657 state and unschedules adds, removes, copies, and renames. If the
4657 state and unschedules adds, removes, copies, and renames. If the
4658 working directory has two parents, you must explicitly specify a
4658 working directory has two parents, you must explicitly specify a
4659 revision.
4659 revision.
4660
4660
4661 Using the -r/--rev or -d/--date options, revert the given files or
4661 Using the -r/--rev or -d/--date options, revert the given files or
4662 directories to their states as of a specific revision. Because
4662 directories to their states as of a specific revision. Because
4663 revert does not change the working directory parents, this will
4663 revert does not change the working directory parents, this will
4664 cause these files to appear modified. This can be helpful to "back
4664 cause these files to appear modified. This can be helpful to "back
4665 out" some or all of an earlier change. See :hg:`backout` for a
4665 out" some or all of an earlier change. See :hg:`backout` for a
4666 related method.
4666 related method.
4667
4667
4668 Modified files are saved with a .orig suffix before reverting.
4668 Modified files are saved with a .orig suffix before reverting.
4669 To disable these backups, use --no-backup.
4669 To disable these backups, use --no-backup.
4670
4670
4671 See :hg:`help dates` for a list of formats valid for -d/--date.
4671 See :hg:`help dates` for a list of formats valid for -d/--date.
4672
4672
4673 Returns 0 on success.
4673 Returns 0 on success.
4674 """
4674 """
4675
4675
4676 if opts.get("date"):
4676 if opts.get("date"):
4677 if opts.get("rev"):
4677 if opts.get("rev"):
4678 raise util.Abort(_("you can't specify a revision and a date"))
4678 raise util.Abort(_("you can't specify a revision and a date"))
4679 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
4679 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
4680
4680
4681 parent, p2 = repo.dirstate.parents()
4681 parent, p2 = repo.dirstate.parents()
4682 if not opts.get('rev') and p2 != nullid:
4682 if not opts.get('rev') and p2 != nullid:
4683 # revert after merge is a trap for new users (issue2915)
4683 # revert after merge is a trap for new users (issue2915)
4684 raise util.Abort(_('uncommitted merge with no revision specified'),
4684 raise util.Abort(_('uncommitted merge with no revision specified'),
4685 hint=_('use "hg update" or see "hg help revert"'))
4685 hint=_('use "hg update" or see "hg help revert"'))
4686
4686
4687 ctx = scmutil.revsingle(repo, opts.get('rev'))
4687 ctx = scmutil.revsingle(repo, opts.get('rev'))
4688 node = ctx.node()
4688 node = ctx.node()
4689
4689
4690 if not pats and not opts.get('all'):
4690 if not pats and not opts.get('all'):
4691 msg = _("no files or directories specified")
4691 msg = _("no files or directories specified")
4692 if p2 != nullid:
4692 if p2 != nullid:
4693 hint = _("uncommitted merge, use --all to discard all changes,"
4693 hint = _("uncommitted merge, use --all to discard all changes,"
4694 " or 'hg update -C .' to abort the merge")
4694 " or 'hg update -C .' to abort the merge")
4695 raise util.Abort(msg, hint=hint)
4695 raise util.Abort(msg, hint=hint)
4696 dirty = util.any(repo.status())
4696 dirty = util.any(repo.status())
4697 if node != parent:
4697 if node != parent:
4698 if dirty:
4698 if dirty:
4699 hint = _("uncommitted changes, use --all to discard all"
4699 hint = _("uncommitted changes, use --all to discard all"
4700 " changes, or 'hg update %s' to update") % ctx.rev()
4700 " changes, or 'hg update %s' to update") % ctx.rev()
4701 else:
4701 else:
4702 hint = _("use --all to revert all files,"
4702 hint = _("use --all to revert all files,"
4703 " or 'hg update %s' to update") % ctx.rev()
4703 " or 'hg update %s' to update") % ctx.rev()
4704 elif dirty:
4704 elif dirty:
4705 hint = _("uncommitted changes, use --all to discard all changes")
4705 hint = _("uncommitted changes, use --all to discard all changes")
4706 else:
4706 else:
4707 hint = _("use --all to revert all files")
4707 hint = _("use --all to revert all files")
4708 raise util.Abort(msg, hint=hint)
4708 raise util.Abort(msg, hint=hint)
4709
4709
4710 mf = ctx.manifest()
4710 mf = ctx.manifest()
4711 if node == parent:
4711 if node == parent:
4712 pmf = mf
4712 pmf = mf
4713 else:
4713 else:
4714 pmf = None
4714 pmf = None
4715
4715
4716 # need all matching names in dirstate and manifest of target rev,
4716 # need all matching names in dirstate and manifest of target rev,
4717 # so have to walk both. do not print errors if files exist in one
4717 # so have to walk both. do not print errors if files exist in one
4718 # but not other.
4718 # but not other.
4719
4719
4720 names = {}
4720 names = {}
4721
4721
4722 wlock = repo.wlock()
4722 wlock = repo.wlock()
4723 try:
4723 try:
4724 # walk dirstate.
4724 # walk dirstate.
4725
4725
4726 m = scmutil.match(repo[None], pats, opts)
4726 m = scmutil.match(repo[None], pats, opts)
4727 m.bad = lambda x, y: False
4727 m.bad = lambda x, y: False
4728 for abs in repo.walk(m):
4728 for abs in repo.walk(m):
4729 names[abs] = m.rel(abs), m.exact(abs)
4729 names[abs] = m.rel(abs), m.exact(abs)
4730
4730
4731 # walk target manifest.
4731 # walk target manifest.
4732
4732
4733 def badfn(path, msg):
4733 def badfn(path, msg):
4734 if path in names:
4734 if path in names:
4735 return
4735 return
4736 if path in repo[node].substate:
4736 if path in repo[node].substate:
4737 ui.warn("%s: %s\n" % (m.rel(path),
4737 ui.warn("%s: %s\n" % (m.rel(path),
4738 'reverting subrepos is unsupported'))
4738 'reverting subrepos is unsupported'))
4739 return
4739 return
4740 path_ = path + '/'
4740 path_ = path + '/'
4741 for f in names:
4741 for f in names:
4742 if f.startswith(path_):
4742 if f.startswith(path_):
4743 return
4743 return
4744 ui.warn("%s: %s\n" % (m.rel(path), msg))
4744 ui.warn("%s: %s\n" % (m.rel(path), msg))
4745
4745
4746 m = scmutil.match(repo[node], pats, opts)
4746 m = scmutil.match(repo[node], pats, opts)
4747 m.bad = badfn
4747 m.bad = badfn
4748 for abs in repo[node].walk(m):
4748 for abs in repo[node].walk(m):
4749 if abs not in names:
4749 if abs not in names:
4750 names[abs] = m.rel(abs), m.exact(abs)
4750 names[abs] = m.rel(abs), m.exact(abs)
4751
4751
4752 m = scmutil.matchfiles(repo, names)
4752 m = scmutil.matchfiles(repo, names)
4753 changes = repo.status(match=m)[:4]
4753 changes = repo.status(match=m)[:4]
4754 modified, added, removed, deleted = map(set, changes)
4754 modified, added, removed, deleted = map(set, changes)
4755
4755
4756 # if f is a rename, also revert the source
4756 # if f is a rename, also revert the source
4757 cwd = repo.getcwd()
4757 cwd = repo.getcwd()
4758 for f in added:
4758 for f in added:
4759 src = repo.dirstate.copied(f)
4759 src = repo.dirstate.copied(f)
4760 if src and src not in names and repo.dirstate[src] == 'r':
4760 if src and src not in names and repo.dirstate[src] == 'r':
4761 removed.add(src)
4761 removed.add(src)
4762 names[src] = (repo.pathto(src, cwd), True)
4762 names[src] = (repo.pathto(src, cwd), True)
4763
4763
4764 def removeforget(abs):
4764 def removeforget(abs):
4765 if repo.dirstate[abs] == 'a':
4765 if repo.dirstate[abs] == 'a':
4766 return _('forgetting %s\n')
4766 return _('forgetting %s\n')
4767 return _('removing %s\n')
4767 return _('removing %s\n')
4768
4768
4769 revert = ([], _('reverting %s\n'))
4769 revert = ([], _('reverting %s\n'))
4770 add = ([], _('adding %s\n'))
4770 add = ([], _('adding %s\n'))
4771 remove = ([], removeforget)
4771 remove = ([], removeforget)
4772 undelete = ([], _('undeleting %s\n'))
4772 undelete = ([], _('undeleting %s\n'))
4773
4773
4774 disptable = (
4774 disptable = (
4775 # dispatch table:
4775 # dispatch table:
4776 # file state
4776 # file state
4777 # action if in target manifest
4777 # action if in target manifest
4778 # action if not in target manifest
4778 # action if not in target manifest
4779 # make backup if in target manifest
4779 # make backup if in target manifest
4780 # make backup if not in target manifest
4780 # make backup if not in target manifest
4781 (modified, revert, remove, True, True),
4781 (modified, revert, remove, True, True),
4782 (added, revert, remove, True, False),
4782 (added, revert, remove, True, False),
4783 (removed, undelete, None, False, False),
4783 (removed, undelete, None, False, False),
4784 (deleted, revert, remove, False, False),
4784 (deleted, revert, remove, False, False),
4785 )
4785 )
4786
4786
4787 for abs, (rel, exact) in sorted(names.items()):
4787 for abs, (rel, exact) in sorted(names.items()):
4788 mfentry = mf.get(abs)
4788 mfentry = mf.get(abs)
4789 target = repo.wjoin(abs)
4789 target = repo.wjoin(abs)
4790 def handle(xlist, dobackup):
4790 def handle(xlist, dobackup):
4791 xlist[0].append(abs)
4791 xlist[0].append(abs)
4792 if (dobackup and not opts.get('no_backup') and
4792 if (dobackup and not opts.get('no_backup') and
4793 os.path.lexists(target)):
4793 os.path.lexists(target)):
4794 bakname = "%s.orig" % rel
4794 bakname = "%s.orig" % rel
4795 ui.note(_('saving current version of %s as %s\n') %
4795 ui.note(_('saving current version of %s as %s\n') %
4796 (rel, bakname))
4796 (rel, bakname))
4797 if not opts.get('dry_run'):
4797 if not opts.get('dry_run'):
4798 util.rename(target, bakname)
4798 util.rename(target, bakname)
4799 if ui.verbose or not exact:
4799 if ui.verbose or not exact:
4800 msg = xlist[1]
4800 msg = xlist[1]
4801 if not isinstance(msg, basestring):
4801 if not isinstance(msg, basestring):
4802 msg = msg(abs)
4802 msg = msg(abs)
4803 ui.status(msg % rel)
4803 ui.status(msg % rel)
4804 for table, hitlist, misslist, backuphit, backupmiss in disptable:
4804 for table, hitlist, misslist, backuphit, backupmiss in disptable:
4805 if abs not in table:
4805 if abs not in table:
4806 continue
4806 continue
4807 # file has changed in dirstate
4807 # file has changed in dirstate
4808 if mfentry:
4808 if mfentry:
4809 handle(hitlist, backuphit)
4809 handle(hitlist, backuphit)
4810 elif misslist is not None:
4810 elif misslist is not None:
4811 handle(misslist, backupmiss)
4811 handle(misslist, backupmiss)
4812 break
4812 break
4813 else:
4813 else:
4814 if abs not in repo.dirstate:
4814 if abs not in repo.dirstate:
4815 if mfentry:
4815 if mfentry:
4816 handle(add, True)
4816 handle(add, True)
4817 elif exact:
4817 elif exact:
4818 ui.warn(_('file not managed: %s\n') % rel)
4818 ui.warn(_('file not managed: %s\n') % rel)
4819 continue
4819 continue
4820 # file has not changed in dirstate
4820 # file has not changed in dirstate
4821 if node == parent:
4821 if node == parent:
4822 if exact:
4822 if exact:
4823 ui.warn(_('no changes needed to %s\n') % rel)
4823 ui.warn(_('no changes needed to %s\n') % rel)
4824 continue
4824 continue
4825 if pmf is None:
4825 if pmf is None:
4826 # only need parent manifest in this unlikely case,
4826 # only need parent manifest in this unlikely case,
4827 # so do not read by default
4827 # so do not read by default
4828 pmf = repo[parent].manifest()
4828 pmf = repo[parent].manifest()
4829 if abs in pmf and mfentry:
4829 if abs in pmf and mfentry:
4830 # if version of file is same in parent and target
4830 # if version of file is same in parent and target
4831 # manifests, do nothing
4831 # manifests, do nothing
4832 if (pmf[abs] != mfentry or
4832 if (pmf[abs] != mfentry or
4833 pmf.flags(abs) != mf.flags(abs)):
4833 pmf.flags(abs) != mf.flags(abs)):
4834 handle(revert, False)
4834 handle(revert, False)
4835 else:
4835 else:
4836 handle(remove, False)
4836 handle(remove, False)
4837
4837
4838 if not opts.get('dry_run'):
4838 if not opts.get('dry_run'):
4839 def checkout(f):
4839 def checkout(f):
4840 fc = ctx[f]
4840 fc = ctx[f]
4841 repo.wwrite(f, fc.data(), fc.flags())
4841 repo.wwrite(f, fc.data(), fc.flags())
4842
4842
4843 audit_path = scmutil.pathauditor(repo.root)
4843 audit_path = scmutil.pathauditor(repo.root)
4844 for f in remove[0]:
4844 for f in remove[0]:
4845 if repo.dirstate[f] == 'a':
4845 if repo.dirstate[f] == 'a':
4846 repo.dirstate.drop(f)
4846 repo.dirstate.drop(f)
4847 continue
4847 continue
4848 audit_path(f)
4848 audit_path(f)
4849 try:
4849 try:
4850 util.unlinkpath(repo.wjoin(f))
4850 util.unlinkpath(repo.wjoin(f))
4851 except OSError:
4851 except OSError:
4852 pass
4852 pass
4853 repo.dirstate.remove(f)
4853 repo.dirstate.remove(f)
4854
4854
4855 normal = None
4855 normal = None
4856 if node == parent:
4856 if node == parent:
4857 # We're reverting to our parent. If possible, we'd like status
4857 # We're reverting to our parent. If possible, we'd like status
4858 # to report the file as clean. We have to use normallookup for
4858 # to report the file as clean. We have to use normallookup for
4859 # merges to avoid losing information about merged/dirty files.
4859 # merges to avoid losing information about merged/dirty files.
4860 if p2 != nullid:
4860 if p2 != nullid:
4861 normal = repo.dirstate.normallookup
4861 normal = repo.dirstate.normallookup
4862 else:
4862 else:
4863 normal = repo.dirstate.normal
4863 normal = repo.dirstate.normal
4864 for f in revert[0]:
4864 for f in revert[0]:
4865 checkout(f)
4865 checkout(f)
4866 if normal:
4866 if normal:
4867 normal(f)
4867 normal(f)
4868
4868
4869 for f in add[0]:
4869 for f in add[0]:
4870 checkout(f)
4870 checkout(f)
4871 repo.dirstate.add(f)
4871 repo.dirstate.add(f)
4872
4872
4873 normal = repo.dirstate.normallookup
4873 normal = repo.dirstate.normallookup
4874 if node == parent and p2 == nullid:
4874 if node == parent and p2 == nullid:
4875 normal = repo.dirstate.normal
4875 normal = repo.dirstate.normal
4876 for f in undelete[0]:
4876 for f in undelete[0]:
4877 checkout(f)
4877 checkout(f)
4878 normal(f)
4878 normal(f)
4879
4879
4880 finally:
4880 finally:
4881 wlock.release()
4881 wlock.release()
4882
4882
4883 @command('rollback', dryrunopts +
4883 @command('rollback', dryrunopts +
4884 [('f', 'force', False, _('ignore safety measures'))])
4884 [('f', 'force', False, _('ignore safety measures'))])
4885 def rollback(ui, repo, **opts):
4885 def rollback(ui, repo, **opts):
4886 """roll back the last transaction (dangerous)
4886 """roll back the last transaction (dangerous)
4887
4887
4888 This command should be used with care. There is only one level of
4888 This command should be used with care. There is only one level of
4889 rollback, and there is no way to undo a rollback. It will also
4889 rollback, and there is no way to undo a rollback. It will also
4890 restore the dirstate at the time of the last transaction, losing
4890 restore the dirstate at the time of the last transaction, losing
4891 any dirstate changes since that time. This command does not alter
4891 any dirstate changes since that time. This command does not alter
4892 the working directory.
4892 the working directory.
4893
4893
4894 Transactions are used to encapsulate the effects of all commands
4894 Transactions are used to encapsulate the effects of all commands
4895 that create new changesets or propagate existing changesets into a
4895 that create new changesets or propagate existing changesets into a
4896 repository. For example, the following commands are transactional,
4896 repository. For example, the following commands are transactional,
4897 and their effects can be rolled back:
4897 and their effects can be rolled back:
4898
4898
4899 - commit
4899 - commit
4900 - import
4900 - import
4901 - pull
4901 - pull
4902 - push (with this repository as the destination)
4902 - push (with this repository as the destination)
4903 - unbundle
4903 - unbundle
4904
4904
4905 To avoid permanent data loss, rollback will refuse to rollback a
4905 To avoid permanent data loss, rollback will refuse to rollback a
4906 commit transaction if it isn't checked out. Use --force to
4906 commit transaction if it isn't checked out. Use --force to
4907 override this protection.
4907 override this protection.
4908
4908
4909 This command is not intended for use on public repositories. Once
4909 This command is not intended for use on public repositories. Once
4910 changes are visible for pull by other users, rolling a transaction
4910 changes are visible for pull by other users, rolling a transaction
4911 back locally is ineffective (someone else may already have pulled
4911 back locally is ineffective (someone else may already have pulled
4912 the changes). Furthermore, a race is possible with readers of the
4912 the changes). Furthermore, a race is possible with readers of the
4913 repository; for example an in-progress pull from the repository
4913 repository; for example an in-progress pull from the repository
4914 may fail if a rollback is performed.
4914 may fail if a rollback is performed.
4915
4915
4916 Returns 0 on success, 1 if no rollback data is available.
4916 Returns 0 on success, 1 if no rollback data is available.
4917 """
4917 """
4918 return repo.rollback(dryrun=opts.get('dry_run'),
4918 return repo.rollback(dryrun=opts.get('dry_run'),
4919 force=opts.get('force'))
4919 force=opts.get('force'))
4920
4920
4921 @command('root', [])
4921 @command('root', [])
4922 def root(ui, repo):
4922 def root(ui, repo):
4923 """print the root (top) of the current working directory
4923 """print the root (top) of the current working directory
4924
4924
4925 Print the root directory of the current repository.
4925 Print the root directory of the current repository.
4926
4926
4927 Returns 0 on success.
4927 Returns 0 on success.
4928 """
4928 """
4929 ui.write(repo.root + "\n")
4929 ui.write(repo.root + "\n")
4930
4930
4931 @command('^serve',
4931 @command('^serve',
4932 [('A', 'accesslog', '', _('name of access log file to write to'),
4932 [('A', 'accesslog', '', _('name of access log file to write to'),
4933 _('FILE')),
4933 _('FILE')),
4934 ('d', 'daemon', None, _('run server in background')),
4934 ('d', 'daemon', None, _('run server in background')),
4935 ('', 'daemon-pipefds', '', _('used internally by daemon mode'), _('NUM')),
4935 ('', 'daemon-pipefds', '', _('used internally by daemon mode'), _('NUM')),
4936 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
4936 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
4937 # use string type, then we can check if something was passed
4937 # use string type, then we can check if something was passed
4938 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
4938 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
4939 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
4939 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
4940 _('ADDR')),
4940 _('ADDR')),
4941 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
4941 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
4942 _('PREFIX')),
4942 _('PREFIX')),
4943 ('n', 'name', '',
4943 ('n', 'name', '',
4944 _('name to show in web pages (default: working directory)'), _('NAME')),
4944 _('name to show in web pages (default: working directory)'), _('NAME')),
4945 ('', 'web-conf', '',
4945 ('', 'web-conf', '',
4946 _('name of the hgweb config file (see "hg help hgweb")'), _('FILE')),
4946 _('name of the hgweb config file (see "hg help hgweb")'), _('FILE')),
4947 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
4947 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
4948 _('FILE')),
4948 _('FILE')),
4949 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
4949 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
4950 ('', 'stdio', None, _('for remote clients')),
4950 ('', 'stdio', None, _('for remote clients')),
4951 ('', 'cmdserver', '', _('for remote clients'), _('MODE')),
4951 ('', 'cmdserver', '', _('for remote clients'), _('MODE')),
4952 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
4952 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
4953 ('', 'style', '', _('template style to use'), _('STYLE')),
4953 ('', 'style', '', _('template style to use'), _('STYLE')),
4954 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
4954 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
4955 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))],
4955 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))],
4956 _('[OPTION]...'))
4956 _('[OPTION]...'))
4957 def serve(ui, repo, **opts):
4957 def serve(ui, repo, **opts):
4958 """start stand-alone webserver
4958 """start stand-alone webserver
4959
4959
4960 Start a local HTTP repository browser and pull server. You can use
4960 Start a local HTTP repository browser and pull server. You can use
4961 this for ad-hoc sharing and browsing of repositories. It is
4961 this for ad-hoc sharing and browsing of repositories. It is
4962 recommended to use a real web server to serve a repository for
4962 recommended to use a real web server to serve a repository for
4963 longer periods of time.
4963 longer periods of time.
4964
4964
4965 Please note that the server does not implement access control.
4965 Please note that the server does not implement access control.
4966 This means that, by default, anybody can read from the server and
4966 This means that, by default, anybody can read from the server and
4967 nobody can write to it by default. Set the ``web.allow_push``
4967 nobody can write to it by default. Set the ``web.allow_push``
4968 option to ``*`` to allow everybody to push to the server. You
4968 option to ``*`` to allow everybody to push to the server. You
4969 should use a real web server if you need to authenticate users.
4969 should use a real web server if you need to authenticate users.
4970
4970
4971 By default, the server logs accesses to stdout and errors to
4971 By default, the server logs accesses to stdout and errors to
4972 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
4972 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
4973 files.
4973 files.
4974
4974
4975 To have the server choose a free port number to listen on, specify
4975 To have the server choose a free port number to listen on, specify
4976 a port number of 0; in this case, the server will print the port
4976 a port number of 0; in this case, the server will print the port
4977 number it uses.
4977 number it uses.
4978
4978
4979 Returns 0 on success.
4979 Returns 0 on success.
4980 """
4980 """
4981
4981
4982 if opts["stdio"] and opts["cmdserver"]:
4982 if opts["stdio"] and opts["cmdserver"]:
4983 raise util.Abort(_("cannot use --stdio with --cmdserver"))
4983 raise util.Abort(_("cannot use --stdio with --cmdserver"))
4984
4984
4985 def checkrepo():
4985 def checkrepo():
4986 if repo is None:
4986 if repo is None:
4987 raise error.RepoError(_("There is no Mercurial repository here"
4987 raise error.RepoError(_("There is no Mercurial repository here"
4988 " (.hg not found)"))
4988 " (.hg not found)"))
4989
4989
4990 if opts["stdio"]:
4990 if opts["stdio"]:
4991 checkrepo()
4991 checkrepo()
4992 s = sshserver.sshserver(ui, repo)
4992 s = sshserver.sshserver(ui, repo)
4993 s.serve_forever()
4993 s.serve_forever()
4994
4994
4995 if opts["cmdserver"]:
4995 if opts["cmdserver"]:
4996 checkrepo()
4996 checkrepo()
4997 s = commandserver.server(ui, repo, opts["cmdserver"])
4997 s = commandserver.server(ui, repo, opts["cmdserver"])
4998 return s.serve()
4998 return s.serve()
4999
4999
5000 # this way we can check if something was given in the command-line
5000 # this way we can check if something was given in the command-line
5001 if opts.get('port'):
5001 if opts.get('port'):
5002 opts['port'] = util.getport(opts.get('port'))
5002 opts['port'] = util.getport(opts.get('port'))
5003
5003
5004 baseui = repo and repo.baseui or ui
5004 baseui = repo and repo.baseui or ui
5005 optlist = ("name templates style address port prefix ipv6"
5005 optlist = ("name templates style address port prefix ipv6"
5006 " accesslog errorlog certificate encoding")
5006 " accesslog errorlog certificate encoding")
5007 for o in optlist.split():
5007 for o in optlist.split():
5008 val = opts.get(o, '')
5008 val = opts.get(o, '')
5009 if val in (None, ''): # should check against default options instead
5009 if val in (None, ''): # should check against default options instead
5010 continue
5010 continue
5011 baseui.setconfig("web", o, val)
5011 baseui.setconfig("web", o, val)
5012 if repo and repo.ui != baseui:
5012 if repo and repo.ui != baseui:
5013 repo.ui.setconfig("web", o, val)
5013 repo.ui.setconfig("web", o, val)
5014
5014
5015 o = opts.get('web_conf') or opts.get('webdir_conf')
5015 o = opts.get('web_conf') or opts.get('webdir_conf')
5016 if not o:
5016 if not o:
5017 if not repo:
5017 if not repo:
5018 raise error.RepoError(_("There is no Mercurial repository"
5018 raise error.RepoError(_("There is no Mercurial repository"
5019 " here (.hg not found)"))
5019 " here (.hg not found)"))
5020 o = repo.root
5020 o = repo.root
5021
5021
5022 app = hgweb.hgweb(o, baseui=ui)
5022 app = hgweb.hgweb(o, baseui=ui)
5023
5023
5024 class service(object):
5024 class service(object):
5025 def init(self):
5025 def init(self):
5026 util.setsignalhandler()
5026 util.setsignalhandler()
5027 self.httpd = hgweb.server.create_server(ui, app)
5027 self.httpd = hgweb.server.create_server(ui, app)
5028
5028
5029 if opts['port'] and not ui.verbose:
5029 if opts['port'] and not ui.verbose:
5030 return
5030 return
5031
5031
5032 if self.httpd.prefix:
5032 if self.httpd.prefix:
5033 prefix = self.httpd.prefix.strip('/') + '/'
5033 prefix = self.httpd.prefix.strip('/') + '/'
5034 else:
5034 else:
5035 prefix = ''
5035 prefix = ''
5036
5036
5037 port = ':%d' % self.httpd.port
5037 port = ':%d' % self.httpd.port
5038 if port == ':80':
5038 if port == ':80':
5039 port = ''
5039 port = ''
5040
5040
5041 bindaddr = self.httpd.addr
5041 bindaddr = self.httpd.addr
5042 if bindaddr == '0.0.0.0':
5042 if bindaddr == '0.0.0.0':
5043 bindaddr = '*'
5043 bindaddr = '*'
5044 elif ':' in bindaddr: # IPv6
5044 elif ':' in bindaddr: # IPv6
5045 bindaddr = '[%s]' % bindaddr
5045 bindaddr = '[%s]' % bindaddr
5046
5046
5047 fqaddr = self.httpd.fqaddr
5047 fqaddr = self.httpd.fqaddr
5048 if ':' in fqaddr:
5048 if ':' in fqaddr:
5049 fqaddr = '[%s]' % fqaddr
5049 fqaddr = '[%s]' % fqaddr
5050 if opts['port']:
5050 if opts['port']:
5051 write = ui.status
5051 write = ui.status
5052 else:
5052 else:
5053 write = ui.write
5053 write = ui.write
5054 write(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
5054 write(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
5055 (fqaddr, port, prefix, bindaddr, self.httpd.port))
5055 (fqaddr, port, prefix, bindaddr, self.httpd.port))
5056
5056
5057 def run(self):
5057 def run(self):
5058 self.httpd.serve_forever()
5058 self.httpd.serve_forever()
5059
5059
5060 service = service()
5060 service = service()
5061
5061
5062 cmdutil.service(opts, initfn=service.init, runfn=service.run)
5062 cmdutil.service(opts, initfn=service.init, runfn=service.run)
5063
5063
5064 @command('showconfig|debugconfig',
5064 @command('showconfig|debugconfig',
5065 [('u', 'untrusted', None, _('show untrusted configuration options'))],
5065 [('u', 'untrusted', None, _('show untrusted configuration options'))],
5066 _('[-u] [NAME]...'))
5066 _('[-u] [NAME]...'))
5067 def showconfig(ui, repo, *values, **opts):
5067 def showconfig(ui, repo, *values, **opts):
5068 """show combined config settings from all hgrc files
5068 """show combined config settings from all hgrc files
5069
5069
5070 With no arguments, print names and values of all config items.
5070 With no arguments, print names and values of all config items.
5071
5071
5072 With one argument of the form section.name, print just the value
5072 With one argument of the form section.name, print just the value
5073 of that config item.
5073 of that config item.
5074
5074
5075 With multiple arguments, print names and values of all config
5075 With multiple arguments, print names and values of all config
5076 items with matching section names.
5076 items with matching section names.
5077
5077
5078 With --debug, the source (filename and line number) is printed
5078 With --debug, the source (filename and line number) is printed
5079 for each config item.
5079 for each config item.
5080
5080
5081 Returns 0 on success.
5081 Returns 0 on success.
5082 """
5082 """
5083
5083
5084 for f in scmutil.rcpath():
5084 for f in scmutil.rcpath():
5085 ui.debug('read config from: %s\n' % f)
5085 ui.debug('read config from: %s\n' % f)
5086 untrusted = bool(opts.get('untrusted'))
5086 untrusted = bool(opts.get('untrusted'))
5087 if values:
5087 if values:
5088 sections = [v for v in values if '.' not in v]
5088 sections = [v for v in values if '.' not in v]
5089 items = [v for v in values if '.' in v]
5089 items = [v for v in values if '.' in v]
5090 if len(items) > 1 or items and sections:
5090 if len(items) > 1 or items and sections:
5091 raise util.Abort(_('only one config item permitted'))
5091 raise util.Abort(_('only one config item permitted'))
5092 for section, name, value in ui.walkconfig(untrusted=untrusted):
5092 for section, name, value in ui.walkconfig(untrusted=untrusted):
5093 value = str(value).replace('\n', '\\n')
5093 value = str(value).replace('\n', '\\n')
5094 sectname = section + '.' + name
5094 sectname = section + '.' + name
5095 if values:
5095 if values:
5096 for v in values:
5096 for v in values:
5097 if v == section:
5097 if v == section:
5098 ui.debug('%s: ' %
5098 ui.debug('%s: ' %
5099 ui.configsource(section, name, untrusted))
5099 ui.configsource(section, name, untrusted))
5100 ui.write('%s=%s\n' % (sectname, value))
5100 ui.write('%s=%s\n' % (sectname, value))
5101 elif v == sectname:
5101 elif v == sectname:
5102 ui.debug('%s: ' %
5102 ui.debug('%s: ' %
5103 ui.configsource(section, name, untrusted))
5103 ui.configsource(section, name, untrusted))
5104 ui.write(value, '\n')
5104 ui.write(value, '\n')
5105 else:
5105 else:
5106 ui.debug('%s: ' %
5106 ui.debug('%s: ' %
5107 ui.configsource(section, name, untrusted))
5107 ui.configsource(section, name, untrusted))
5108 ui.write('%s=%s\n' % (sectname, value))
5108 ui.write('%s=%s\n' % (sectname, value))
5109
5109
5110 @command('^status|st',
5110 @command('^status|st',
5111 [('A', 'all', None, _('show status of all files')),
5111 [('A', 'all', None, _('show status of all files')),
5112 ('m', 'modified', None, _('show only modified files')),
5112 ('m', 'modified', None, _('show only modified files')),
5113 ('a', 'added', None, _('show only added files')),
5113 ('a', 'added', None, _('show only added files')),
5114 ('r', 'removed', None, _('show only removed files')),
5114 ('r', 'removed', None, _('show only removed files')),
5115 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
5115 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
5116 ('c', 'clean', None, _('show only files without changes')),
5116 ('c', 'clean', None, _('show only files without changes')),
5117 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
5117 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
5118 ('i', 'ignored', None, _('show only ignored files')),
5118 ('i', 'ignored', None, _('show only ignored files')),
5119 ('n', 'no-status', None, _('hide status prefix')),
5119 ('n', 'no-status', None, _('hide status prefix')),
5120 ('C', 'copies', None, _('show source of copied files')),
5120 ('C', 'copies', None, _('show source of copied files')),
5121 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
5121 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
5122 ('', 'rev', [], _('show difference from revision'), _('REV')),
5122 ('', 'rev', [], _('show difference from revision'), _('REV')),
5123 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
5123 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
5124 ] + walkopts + subrepoopts,
5124 ] + walkopts + subrepoopts,
5125 _('[OPTION]... [FILE]...'))
5125 _('[OPTION]... [FILE]...'))
5126 def status(ui, repo, *pats, **opts):
5126 def status(ui, repo, *pats, **opts):
5127 """show changed files in the working directory
5127 """show changed files in the working directory
5128
5128
5129 Show status of files in the repository. If names are given, only
5129 Show status of files in the repository. If names are given, only
5130 files that match are shown. Files that are clean or ignored or
5130 files that match are shown. Files that are clean or ignored or
5131 the source of a copy/move operation, are not listed unless
5131 the source of a copy/move operation, are not listed unless
5132 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
5132 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
5133 Unless options described with "show only ..." are given, the
5133 Unless options described with "show only ..." are given, the
5134 options -mardu are used.
5134 options -mardu are used.
5135
5135
5136 Option -q/--quiet hides untracked (unknown and ignored) files
5136 Option -q/--quiet hides untracked (unknown and ignored) files
5137 unless explicitly requested with -u/--unknown or -i/--ignored.
5137 unless explicitly requested with -u/--unknown or -i/--ignored.
5138
5138
5139 .. note::
5139 .. note::
5140 status may appear to disagree with diff if permissions have
5140 status may appear to disagree with diff if permissions have
5141 changed or a merge has occurred. The standard diff format does
5141 changed or a merge has occurred. The standard diff format does
5142 not report permission changes and diff only reports changes
5142 not report permission changes and diff only reports changes
5143 relative to one merge parent.
5143 relative to one merge parent.
5144
5144
5145 If one revision is given, it is used as the base revision.
5145 If one revision is given, it is used as the base revision.
5146 If two revisions are given, the differences between them are
5146 If two revisions are given, the differences between them are
5147 shown. The --change option can also be used as a shortcut to list
5147 shown. The --change option can also be used as a shortcut to list
5148 the changed files of a revision from its first parent.
5148 the changed files of a revision from its first parent.
5149
5149
5150 The codes used to show the status of files are::
5150 The codes used to show the status of files are::
5151
5151
5152 M = modified
5152 M = modified
5153 A = added
5153 A = added
5154 R = removed
5154 R = removed
5155 C = clean
5155 C = clean
5156 ! = missing (deleted by non-hg command, but still tracked)
5156 ! = missing (deleted by non-hg command, but still tracked)
5157 ? = not tracked
5157 ? = not tracked
5158 I = ignored
5158 I = ignored
5159 = origin of the previous file listed as A (added)
5159 = origin of the previous file listed as A (added)
5160
5160
5161 .. container:: verbose
5161 .. container:: verbose
5162
5162
5163 Examples:
5163 Examples:
5164
5164
5165 - show changes in the working directory relative to a
5165 - show changes in the working directory relative to a
5166 changeset::
5166 changeset::
5167
5167
5168 hg status --rev 9353
5168 hg status --rev 9353
5169
5169
5170 - show all changes including copies in an existing changeset::
5170 - show all changes including copies in an existing changeset::
5171
5171
5172 hg status --copies --change 9353
5172 hg status --copies --change 9353
5173
5173
5174 - get a NUL separated list of added files, suitable for xargs::
5174 - get a NUL separated list of added files, suitable for xargs::
5175
5175
5176 hg status -an0
5176 hg status -an0
5177
5177
5178 Returns 0 on success.
5178 Returns 0 on success.
5179 """
5179 """
5180
5180
5181 revs = opts.get('rev')
5181 revs = opts.get('rev')
5182 change = opts.get('change')
5182 change = opts.get('change')
5183
5183
5184 if revs and change:
5184 if revs and change:
5185 msg = _('cannot specify --rev and --change at the same time')
5185 msg = _('cannot specify --rev and --change at the same time')
5186 raise util.Abort(msg)
5186 raise util.Abort(msg)
5187 elif change:
5187 elif change:
5188 node2 = scmutil.revsingle(repo, change, None).node()
5188 node2 = scmutil.revsingle(repo, change, None).node()
5189 node1 = repo[node2].p1().node()
5189 node1 = repo[node2].p1().node()
5190 else:
5190 else:
5191 node1, node2 = scmutil.revpair(repo, revs)
5191 node1, node2 = scmutil.revpair(repo, revs)
5192
5192
5193 cwd = (pats and repo.getcwd()) or ''
5193 cwd = (pats and repo.getcwd()) or ''
5194 end = opts.get('print0') and '\0' or '\n'
5194 end = opts.get('print0') and '\0' or '\n'
5195 copy = {}
5195 copy = {}
5196 states = 'modified added removed deleted unknown ignored clean'.split()
5196 states = 'modified added removed deleted unknown ignored clean'.split()
5197 show = [k for k in states if opts.get(k)]
5197 show = [k for k in states if opts.get(k)]
5198 if opts.get('all'):
5198 if opts.get('all'):
5199 show += ui.quiet and (states[:4] + ['clean']) or states
5199 show += ui.quiet and (states[:4] + ['clean']) or states
5200 if not show:
5200 if not show:
5201 show = ui.quiet and states[:4] or states[:5]
5201 show = ui.quiet and states[:4] or states[:5]
5202
5202
5203 stat = repo.status(node1, node2, scmutil.match(repo[node2], pats, opts),
5203 stat = repo.status(node1, node2, scmutil.match(repo[node2], pats, opts),
5204 'ignored' in show, 'clean' in show, 'unknown' in show,
5204 'ignored' in show, 'clean' in show, 'unknown' in show,
5205 opts.get('subrepos'))
5205 opts.get('subrepos'))
5206 changestates = zip(states, 'MAR!?IC', stat)
5206 changestates = zip(states, 'MAR!?IC', stat)
5207
5207
5208 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
5208 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
5209 ctxn = repo[nullid]
5210 ctx1 = repo[node1]
5209 ctx1 = repo[node1]
5211 ctx2 = repo[node2]
5210 ctx2 = repo[node2]
5212 added = stat[1]
5211 added = stat[1]
5213 if node2 is None:
5212 if node2 is None:
5214 added = stat[0] + stat[1] # merged?
5213 added = stat[0] + stat[1] # merged?
5215
5214
5216 for k, v in copies.copies(repo, ctx1, ctx2, ctxn)[0].iteritems():
5215 for k, v in copies.pathcopies(ctx1, ctx2).iteritems():
5217 if k in added:
5216 if k in added:
5218 copy[k] = v
5217 copy[k] = v
5219 elif v in added:
5218 elif v in added:
5220 copy[v] = k
5219 copy[v] = k
5221
5220
5222 for state, char, files in changestates:
5221 for state, char, files in changestates:
5223 if state in show:
5222 if state in show:
5224 format = "%s %%s%s" % (char, end)
5223 format = "%s %%s%s" % (char, end)
5225 if opts.get('no_status'):
5224 if opts.get('no_status'):
5226 format = "%%s%s" % end
5225 format = "%%s%s" % end
5227
5226
5228 for f in files:
5227 for f in files:
5229 ui.write(format % repo.pathto(f, cwd),
5228 ui.write(format % repo.pathto(f, cwd),
5230 label='status.' + state)
5229 label='status.' + state)
5231 if f in copy:
5230 if f in copy:
5232 ui.write(' %s%s' % (repo.pathto(copy[f], cwd), end),
5231 ui.write(' %s%s' % (repo.pathto(copy[f], cwd), end),
5233 label='status.copied')
5232 label='status.copied')
5234
5233
5235 @command('^summary|sum',
5234 @command('^summary|sum',
5236 [('', 'remote', None, _('check for push and pull'))], '[--remote]')
5235 [('', 'remote', None, _('check for push and pull'))], '[--remote]')
5237 def summary(ui, repo, **opts):
5236 def summary(ui, repo, **opts):
5238 """summarize working directory state
5237 """summarize working directory state
5239
5238
5240 This generates a brief summary of the working directory state,
5239 This generates a brief summary of the working directory state,
5241 including parents, branch, commit status, and available updates.
5240 including parents, branch, commit status, and available updates.
5242
5241
5243 With the --remote option, this will check the default paths for
5242 With the --remote option, this will check the default paths for
5244 incoming and outgoing changes. This can be time-consuming.
5243 incoming and outgoing changes. This can be time-consuming.
5245
5244
5246 Returns 0 on success.
5245 Returns 0 on success.
5247 """
5246 """
5248
5247
5249 ctx = repo[None]
5248 ctx = repo[None]
5250 parents = ctx.parents()
5249 parents = ctx.parents()
5251 pnode = parents[0].node()
5250 pnode = parents[0].node()
5252 marks = []
5251 marks = []
5253
5252
5254 for p in parents:
5253 for p in parents:
5255 # label with log.changeset (instead of log.parent) since this
5254 # label with log.changeset (instead of log.parent) since this
5256 # shows a working directory parent *changeset*:
5255 # shows a working directory parent *changeset*:
5257 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
5256 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
5258 label='log.changeset')
5257 label='log.changeset')
5259 ui.write(' '.join(p.tags()), label='log.tag')
5258 ui.write(' '.join(p.tags()), label='log.tag')
5260 if p.bookmarks():
5259 if p.bookmarks():
5261 marks.extend(p.bookmarks())
5260 marks.extend(p.bookmarks())
5262 if p.rev() == -1:
5261 if p.rev() == -1:
5263 if not len(repo):
5262 if not len(repo):
5264 ui.write(_(' (empty repository)'))
5263 ui.write(_(' (empty repository)'))
5265 else:
5264 else:
5266 ui.write(_(' (no revision checked out)'))
5265 ui.write(_(' (no revision checked out)'))
5267 ui.write('\n')
5266 ui.write('\n')
5268 if p.description():
5267 if p.description():
5269 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
5268 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
5270 label='log.summary')
5269 label='log.summary')
5271
5270
5272 branch = ctx.branch()
5271 branch = ctx.branch()
5273 bheads = repo.branchheads(branch)
5272 bheads = repo.branchheads(branch)
5274 m = _('branch: %s\n') % branch
5273 m = _('branch: %s\n') % branch
5275 if branch != 'default':
5274 if branch != 'default':
5276 ui.write(m, label='log.branch')
5275 ui.write(m, label='log.branch')
5277 else:
5276 else:
5278 ui.status(m, label='log.branch')
5277 ui.status(m, label='log.branch')
5279
5278
5280 if marks:
5279 if marks:
5281 current = repo._bookmarkcurrent
5280 current = repo._bookmarkcurrent
5282 ui.write(_('bookmarks:'), label='log.bookmark')
5281 ui.write(_('bookmarks:'), label='log.bookmark')
5283 if current is not None:
5282 if current is not None:
5284 try:
5283 try:
5285 marks.remove(current)
5284 marks.remove(current)
5286 ui.write(' *' + current, label='bookmarks.current')
5285 ui.write(' *' + current, label='bookmarks.current')
5287 except ValueError:
5286 except ValueError:
5288 # current bookmark not in parent ctx marks
5287 # current bookmark not in parent ctx marks
5289 pass
5288 pass
5290 for m in marks:
5289 for m in marks:
5291 ui.write(' ' + m, label='log.bookmark')
5290 ui.write(' ' + m, label='log.bookmark')
5292 ui.write('\n', label='log.bookmark')
5291 ui.write('\n', label='log.bookmark')
5293
5292
5294 st = list(repo.status(unknown=True))[:6]
5293 st = list(repo.status(unknown=True))[:6]
5295
5294
5296 c = repo.dirstate.copies()
5295 c = repo.dirstate.copies()
5297 copied, renamed = [], []
5296 copied, renamed = [], []
5298 for d, s in c.iteritems():
5297 for d, s in c.iteritems():
5299 if s in st[2]:
5298 if s in st[2]:
5300 st[2].remove(s)
5299 st[2].remove(s)
5301 renamed.append(d)
5300 renamed.append(d)
5302 else:
5301 else:
5303 copied.append(d)
5302 copied.append(d)
5304 if d in st[1]:
5303 if d in st[1]:
5305 st[1].remove(d)
5304 st[1].remove(d)
5306 st.insert(3, renamed)
5305 st.insert(3, renamed)
5307 st.insert(4, copied)
5306 st.insert(4, copied)
5308
5307
5309 ms = mergemod.mergestate(repo)
5308 ms = mergemod.mergestate(repo)
5310 st.append([f for f in ms if ms[f] == 'u'])
5309 st.append([f for f in ms if ms[f] == 'u'])
5311
5310
5312 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
5311 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
5313 st.append(subs)
5312 st.append(subs)
5314
5313
5315 labels = [ui.label(_('%d modified'), 'status.modified'),
5314 labels = [ui.label(_('%d modified'), 'status.modified'),
5316 ui.label(_('%d added'), 'status.added'),
5315 ui.label(_('%d added'), 'status.added'),
5317 ui.label(_('%d removed'), 'status.removed'),
5316 ui.label(_('%d removed'), 'status.removed'),
5318 ui.label(_('%d renamed'), 'status.copied'),
5317 ui.label(_('%d renamed'), 'status.copied'),
5319 ui.label(_('%d copied'), 'status.copied'),
5318 ui.label(_('%d copied'), 'status.copied'),
5320 ui.label(_('%d deleted'), 'status.deleted'),
5319 ui.label(_('%d deleted'), 'status.deleted'),
5321 ui.label(_('%d unknown'), 'status.unknown'),
5320 ui.label(_('%d unknown'), 'status.unknown'),
5322 ui.label(_('%d ignored'), 'status.ignored'),
5321 ui.label(_('%d ignored'), 'status.ignored'),
5323 ui.label(_('%d unresolved'), 'resolve.unresolved'),
5322 ui.label(_('%d unresolved'), 'resolve.unresolved'),
5324 ui.label(_('%d subrepos'), 'status.modified')]
5323 ui.label(_('%d subrepos'), 'status.modified')]
5325 t = []
5324 t = []
5326 for s, l in zip(st, labels):
5325 for s, l in zip(st, labels):
5327 if s:
5326 if s:
5328 t.append(l % len(s))
5327 t.append(l % len(s))
5329
5328
5330 t = ', '.join(t)
5329 t = ', '.join(t)
5331 cleanworkdir = False
5330 cleanworkdir = False
5332
5331
5333 if len(parents) > 1:
5332 if len(parents) > 1:
5334 t += _(' (merge)')
5333 t += _(' (merge)')
5335 elif branch != parents[0].branch():
5334 elif branch != parents[0].branch():
5336 t += _(' (new branch)')
5335 t += _(' (new branch)')
5337 elif (parents[0].extra().get('close') and
5336 elif (parents[0].extra().get('close') and
5338 pnode in repo.branchheads(branch, closed=True)):
5337 pnode in repo.branchheads(branch, closed=True)):
5339 t += _(' (head closed)')
5338 t += _(' (head closed)')
5340 elif not (st[0] or st[1] or st[2] or st[3] or st[4] or st[9]):
5339 elif not (st[0] or st[1] or st[2] or st[3] or st[4] or st[9]):
5341 t += _(' (clean)')
5340 t += _(' (clean)')
5342 cleanworkdir = True
5341 cleanworkdir = True
5343 elif pnode not in bheads:
5342 elif pnode not in bheads:
5344 t += _(' (new branch head)')
5343 t += _(' (new branch head)')
5345
5344
5346 if cleanworkdir:
5345 if cleanworkdir:
5347 ui.status(_('commit: %s\n') % t.strip())
5346 ui.status(_('commit: %s\n') % t.strip())
5348 else:
5347 else:
5349 ui.write(_('commit: %s\n') % t.strip())
5348 ui.write(_('commit: %s\n') % t.strip())
5350
5349
5351 # all ancestors of branch heads - all ancestors of parent = new csets
5350 # all ancestors of branch heads - all ancestors of parent = new csets
5352 new = [0] * len(repo)
5351 new = [0] * len(repo)
5353 cl = repo.changelog
5352 cl = repo.changelog
5354 for a in [cl.rev(n) for n in bheads]:
5353 for a in [cl.rev(n) for n in bheads]:
5355 new[a] = 1
5354 new[a] = 1
5356 for a in cl.ancestors(*[cl.rev(n) for n in bheads]):
5355 for a in cl.ancestors(*[cl.rev(n) for n in bheads]):
5357 new[a] = 1
5356 new[a] = 1
5358 for a in [p.rev() for p in parents]:
5357 for a in [p.rev() for p in parents]:
5359 if a >= 0:
5358 if a >= 0:
5360 new[a] = 0
5359 new[a] = 0
5361 for a in cl.ancestors(*[p.rev() for p in parents]):
5360 for a in cl.ancestors(*[p.rev() for p in parents]):
5362 new[a] = 0
5361 new[a] = 0
5363 new = sum(new)
5362 new = sum(new)
5364
5363
5365 if new == 0:
5364 if new == 0:
5366 ui.status(_('update: (current)\n'))
5365 ui.status(_('update: (current)\n'))
5367 elif pnode not in bheads:
5366 elif pnode not in bheads:
5368 ui.write(_('update: %d new changesets (update)\n') % new)
5367 ui.write(_('update: %d new changesets (update)\n') % new)
5369 else:
5368 else:
5370 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
5369 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
5371 (new, len(bheads)))
5370 (new, len(bheads)))
5372
5371
5373 if opts.get('remote'):
5372 if opts.get('remote'):
5374 t = []
5373 t = []
5375 source, branches = hg.parseurl(ui.expandpath('default'))
5374 source, branches = hg.parseurl(ui.expandpath('default'))
5376 other = hg.peer(repo, {}, source)
5375 other = hg.peer(repo, {}, source)
5377 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
5376 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
5378 ui.debug('comparing with %s\n' % util.hidepassword(source))
5377 ui.debug('comparing with %s\n' % util.hidepassword(source))
5379 repo.ui.pushbuffer()
5378 repo.ui.pushbuffer()
5380 commoninc = discovery.findcommonincoming(repo, other)
5379 commoninc = discovery.findcommonincoming(repo, other)
5381 _common, incoming, _rheads = commoninc
5380 _common, incoming, _rheads = commoninc
5382 repo.ui.popbuffer()
5381 repo.ui.popbuffer()
5383 if incoming:
5382 if incoming:
5384 t.append(_('1 or more incoming'))
5383 t.append(_('1 or more incoming'))
5385
5384
5386 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5385 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5387 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5386 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5388 if source != dest:
5387 if source != dest:
5389 other = hg.peer(repo, {}, dest)
5388 other = hg.peer(repo, {}, dest)
5390 commoninc = None
5389 commoninc = None
5391 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5390 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5392 repo.ui.pushbuffer()
5391 repo.ui.pushbuffer()
5393 common, outheads = discovery.findcommonoutgoing(repo, other,
5392 common, outheads = discovery.findcommonoutgoing(repo, other,
5394 commoninc=commoninc)
5393 commoninc=commoninc)
5395 repo.ui.popbuffer()
5394 repo.ui.popbuffer()
5396 o = repo.changelog.findmissing(common=common, heads=outheads)
5395 o = repo.changelog.findmissing(common=common, heads=outheads)
5397 if o:
5396 if o:
5398 t.append(_('%d outgoing') % len(o))
5397 t.append(_('%d outgoing') % len(o))
5399 if 'bookmarks' in other.listkeys('namespaces'):
5398 if 'bookmarks' in other.listkeys('namespaces'):
5400 lmarks = repo.listkeys('bookmarks')
5399 lmarks = repo.listkeys('bookmarks')
5401 rmarks = other.listkeys('bookmarks')
5400 rmarks = other.listkeys('bookmarks')
5402 diff = set(rmarks) - set(lmarks)
5401 diff = set(rmarks) - set(lmarks)
5403 if len(diff) > 0:
5402 if len(diff) > 0:
5404 t.append(_('%d incoming bookmarks') % len(diff))
5403 t.append(_('%d incoming bookmarks') % len(diff))
5405 diff = set(lmarks) - set(rmarks)
5404 diff = set(lmarks) - set(rmarks)
5406 if len(diff) > 0:
5405 if len(diff) > 0:
5407 t.append(_('%d outgoing bookmarks') % len(diff))
5406 t.append(_('%d outgoing bookmarks') % len(diff))
5408
5407
5409 if t:
5408 if t:
5410 ui.write(_('remote: %s\n') % (', '.join(t)))
5409 ui.write(_('remote: %s\n') % (', '.join(t)))
5411 else:
5410 else:
5412 ui.status(_('remote: (synced)\n'))
5411 ui.status(_('remote: (synced)\n'))
5413
5412
5414 @command('tag',
5413 @command('tag',
5415 [('f', 'force', None, _('force tag')),
5414 [('f', 'force', None, _('force tag')),
5416 ('l', 'local', None, _('make the tag local')),
5415 ('l', 'local', None, _('make the tag local')),
5417 ('r', 'rev', '', _('revision to tag'), _('REV')),
5416 ('r', 'rev', '', _('revision to tag'), _('REV')),
5418 ('', 'remove', None, _('remove a tag')),
5417 ('', 'remove', None, _('remove a tag')),
5419 # -l/--local is already there, commitopts cannot be used
5418 # -l/--local is already there, commitopts cannot be used
5420 ('e', 'edit', None, _('edit commit message')),
5419 ('e', 'edit', None, _('edit commit message')),
5421 ('m', 'message', '', _('use <text> as commit message'), _('TEXT')),
5420 ('m', 'message', '', _('use <text> as commit message'), _('TEXT')),
5422 ] + commitopts2,
5421 ] + commitopts2,
5423 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
5422 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
5424 def tag(ui, repo, name1, *names, **opts):
5423 def tag(ui, repo, name1, *names, **opts):
5425 """add one or more tags for the current or given revision
5424 """add one or more tags for the current or given revision
5426
5425
5427 Name a particular revision using <name>.
5426 Name a particular revision using <name>.
5428
5427
5429 Tags are used to name particular revisions of the repository and are
5428 Tags are used to name particular revisions of the repository and are
5430 very useful to compare different revisions, to go back to significant
5429 very useful to compare different revisions, to go back to significant
5431 earlier versions or to mark branch points as releases, etc. Changing
5430 earlier versions or to mark branch points as releases, etc. Changing
5432 an existing tag is normally disallowed; use -f/--force to override.
5431 an existing tag is normally disallowed; use -f/--force to override.
5433
5432
5434 If no revision is given, the parent of the working directory is
5433 If no revision is given, the parent of the working directory is
5435 used, or tip if no revision is checked out.
5434 used, or tip if no revision is checked out.
5436
5435
5437 To facilitate version control, distribution, and merging of tags,
5436 To facilitate version control, distribution, and merging of tags,
5438 they are stored as a file named ".hgtags" which is managed similarly
5437 they are stored as a file named ".hgtags" which is managed similarly
5439 to other project files and can be hand-edited if necessary. This
5438 to other project files and can be hand-edited if necessary. This
5440 also means that tagging creates a new commit. The file
5439 also means that tagging creates a new commit. The file
5441 ".hg/localtags" is used for local tags (not shared among
5440 ".hg/localtags" is used for local tags (not shared among
5442 repositories).
5441 repositories).
5443
5442
5444 Tag commits are usually made at the head of a branch. If the parent
5443 Tag commits are usually made at the head of a branch. If the parent
5445 of the working directory is not a branch head, :hg:`tag` aborts; use
5444 of the working directory is not a branch head, :hg:`tag` aborts; use
5446 -f/--force to force the tag commit to be based on a non-head
5445 -f/--force to force the tag commit to be based on a non-head
5447 changeset.
5446 changeset.
5448
5447
5449 See :hg:`help dates` for a list of formats valid for -d/--date.
5448 See :hg:`help dates` for a list of formats valid for -d/--date.
5450
5449
5451 Since tag names have priority over branch names during revision
5450 Since tag names have priority over branch names during revision
5452 lookup, using an existing branch name as a tag name is discouraged.
5451 lookup, using an existing branch name as a tag name is discouraged.
5453
5452
5454 Returns 0 on success.
5453 Returns 0 on success.
5455 """
5454 """
5456
5455
5457 rev_ = "."
5456 rev_ = "."
5458 names = [t.strip() for t in (name1,) + names]
5457 names = [t.strip() for t in (name1,) + names]
5459 if len(names) != len(set(names)):
5458 if len(names) != len(set(names)):
5460 raise util.Abort(_('tag names must be unique'))
5459 raise util.Abort(_('tag names must be unique'))
5461 for n in names:
5460 for n in names:
5462 if n in ['tip', '.', 'null']:
5461 if n in ['tip', '.', 'null']:
5463 raise util.Abort(_("the name '%s' is reserved") % n)
5462 raise util.Abort(_("the name '%s' is reserved") % n)
5464 if not n:
5463 if not n:
5465 raise util.Abort(_('tag names cannot consist entirely of whitespace'))
5464 raise util.Abort(_('tag names cannot consist entirely of whitespace'))
5466 if opts.get('rev') and opts.get('remove'):
5465 if opts.get('rev') and opts.get('remove'):
5467 raise util.Abort(_("--rev and --remove are incompatible"))
5466 raise util.Abort(_("--rev and --remove are incompatible"))
5468 if opts.get('rev'):
5467 if opts.get('rev'):
5469 rev_ = opts['rev']
5468 rev_ = opts['rev']
5470 message = opts.get('message')
5469 message = opts.get('message')
5471 if opts.get('remove'):
5470 if opts.get('remove'):
5472 expectedtype = opts.get('local') and 'local' or 'global'
5471 expectedtype = opts.get('local') and 'local' or 'global'
5473 for n in names:
5472 for n in names:
5474 if not repo.tagtype(n):
5473 if not repo.tagtype(n):
5475 raise util.Abort(_("tag '%s' does not exist") % n)
5474 raise util.Abort(_("tag '%s' does not exist") % n)
5476 if repo.tagtype(n) != expectedtype:
5475 if repo.tagtype(n) != expectedtype:
5477 if expectedtype == 'global':
5476 if expectedtype == 'global':
5478 raise util.Abort(_("tag '%s' is not a global tag") % n)
5477 raise util.Abort(_("tag '%s' is not a global tag") % n)
5479 else:
5478 else:
5480 raise util.Abort(_("tag '%s' is not a local tag") % n)
5479 raise util.Abort(_("tag '%s' is not a local tag") % n)
5481 rev_ = nullid
5480 rev_ = nullid
5482 if not message:
5481 if not message:
5483 # we don't translate commit messages
5482 # we don't translate commit messages
5484 message = 'Removed tag %s' % ', '.join(names)
5483 message = 'Removed tag %s' % ', '.join(names)
5485 elif not opts.get('force'):
5484 elif not opts.get('force'):
5486 for n in names:
5485 for n in names:
5487 if n in repo.tags():
5486 if n in repo.tags():
5488 raise util.Abort(_("tag '%s' already exists "
5487 raise util.Abort(_("tag '%s' already exists "
5489 "(use -f to force)") % n)
5488 "(use -f to force)") % n)
5490 if not opts.get('local'):
5489 if not opts.get('local'):
5491 p1, p2 = repo.dirstate.parents()
5490 p1, p2 = repo.dirstate.parents()
5492 if p2 != nullid:
5491 if p2 != nullid:
5493 raise util.Abort(_('uncommitted merge'))
5492 raise util.Abort(_('uncommitted merge'))
5494 bheads = repo.branchheads()
5493 bheads = repo.branchheads()
5495 if not opts.get('force') and bheads and p1 not in bheads:
5494 if not opts.get('force') and bheads and p1 not in bheads:
5496 raise util.Abort(_('not at a branch head (use -f to force)'))
5495 raise util.Abort(_('not at a branch head (use -f to force)'))
5497 r = scmutil.revsingle(repo, rev_).node()
5496 r = scmutil.revsingle(repo, rev_).node()
5498
5497
5499 if not message:
5498 if not message:
5500 # we don't translate commit messages
5499 # we don't translate commit messages
5501 message = ('Added tag %s for changeset %s' %
5500 message = ('Added tag %s for changeset %s' %
5502 (', '.join(names), short(r)))
5501 (', '.join(names), short(r)))
5503
5502
5504 date = opts.get('date')
5503 date = opts.get('date')
5505 if date:
5504 if date:
5506 date = util.parsedate(date)
5505 date = util.parsedate(date)
5507
5506
5508 if opts.get('edit'):
5507 if opts.get('edit'):
5509 message = ui.edit(message, ui.username())
5508 message = ui.edit(message, ui.username())
5510
5509
5511 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
5510 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
5512
5511
5513 @command('tags', [], '')
5512 @command('tags', [], '')
5514 def tags(ui, repo):
5513 def tags(ui, repo):
5515 """list repository tags
5514 """list repository tags
5516
5515
5517 This lists both regular and local tags. When the -v/--verbose
5516 This lists both regular and local tags. When the -v/--verbose
5518 switch is used, a third column "local" is printed for local tags.
5517 switch is used, a third column "local" is printed for local tags.
5519
5518
5520 Returns 0 on success.
5519 Returns 0 on success.
5521 """
5520 """
5522
5521
5523 hexfunc = ui.debugflag and hex or short
5522 hexfunc = ui.debugflag and hex or short
5524 tagtype = ""
5523 tagtype = ""
5525
5524
5526 for t, n in reversed(repo.tagslist()):
5525 for t, n in reversed(repo.tagslist()):
5527 if ui.quiet:
5526 if ui.quiet:
5528 ui.write("%s\n" % t, label='tags.normal')
5527 ui.write("%s\n" % t, label='tags.normal')
5529 continue
5528 continue
5530
5529
5531 hn = hexfunc(n)
5530 hn = hexfunc(n)
5532 r = "%5d:%s" % (repo.changelog.rev(n), hn)
5531 r = "%5d:%s" % (repo.changelog.rev(n), hn)
5533 rev = ui.label(r, 'log.changeset')
5532 rev = ui.label(r, 'log.changeset')
5534 spaces = " " * (30 - encoding.colwidth(t))
5533 spaces = " " * (30 - encoding.colwidth(t))
5535
5534
5536 tag = ui.label(t, 'tags.normal')
5535 tag = ui.label(t, 'tags.normal')
5537 if ui.verbose:
5536 if ui.verbose:
5538 if repo.tagtype(t) == 'local':
5537 if repo.tagtype(t) == 'local':
5539 tagtype = " local"
5538 tagtype = " local"
5540 tag = ui.label(t, 'tags.local')
5539 tag = ui.label(t, 'tags.local')
5541 else:
5540 else:
5542 tagtype = ""
5541 tagtype = ""
5543 ui.write("%s%s %s%s\n" % (tag, spaces, rev, tagtype))
5542 ui.write("%s%s %s%s\n" % (tag, spaces, rev, tagtype))
5544
5543
5545 @command('tip',
5544 @command('tip',
5546 [('p', 'patch', None, _('show patch')),
5545 [('p', 'patch', None, _('show patch')),
5547 ('g', 'git', None, _('use git extended diff format')),
5546 ('g', 'git', None, _('use git extended diff format')),
5548 ] + templateopts,
5547 ] + templateopts,
5549 _('[-p] [-g]'))
5548 _('[-p] [-g]'))
5550 def tip(ui, repo, **opts):
5549 def tip(ui, repo, **opts):
5551 """show the tip revision
5550 """show the tip revision
5552
5551
5553 The tip revision (usually just called the tip) is the changeset
5552 The tip revision (usually just called the tip) is the changeset
5554 most recently added to the repository (and therefore the most
5553 most recently added to the repository (and therefore the most
5555 recently changed head).
5554 recently changed head).
5556
5555
5557 If you have just made a commit, that commit will be the tip. If
5556 If you have just made a commit, that commit will be the tip. If
5558 you have just pulled changes from another repository, the tip of
5557 you have just pulled changes from another repository, the tip of
5559 that repository becomes the current tip. The "tip" tag is special
5558 that repository becomes the current tip. The "tip" tag is special
5560 and cannot be renamed or assigned to a different changeset.
5559 and cannot be renamed or assigned to a different changeset.
5561
5560
5562 Returns 0 on success.
5561 Returns 0 on success.
5563 """
5562 """
5564 displayer = cmdutil.show_changeset(ui, repo, opts)
5563 displayer = cmdutil.show_changeset(ui, repo, opts)
5565 displayer.show(repo[len(repo) - 1])
5564 displayer.show(repo[len(repo) - 1])
5566 displayer.close()
5565 displayer.close()
5567
5566
5568 @command('unbundle',
5567 @command('unbundle',
5569 [('u', 'update', None,
5568 [('u', 'update', None,
5570 _('update to new branch head if changesets were unbundled'))],
5569 _('update to new branch head if changesets were unbundled'))],
5571 _('[-u] FILE...'))
5570 _('[-u] FILE...'))
5572 def unbundle(ui, repo, fname1, *fnames, **opts):
5571 def unbundle(ui, repo, fname1, *fnames, **opts):
5573 """apply one or more changegroup files
5572 """apply one or more changegroup files
5574
5573
5575 Apply one or more compressed changegroup files generated by the
5574 Apply one or more compressed changegroup files generated by the
5576 bundle command.
5575 bundle command.
5577
5576
5578 Returns 0 on success, 1 if an update has unresolved files.
5577 Returns 0 on success, 1 if an update has unresolved files.
5579 """
5578 """
5580 fnames = (fname1,) + fnames
5579 fnames = (fname1,) + fnames
5581
5580
5582 lock = repo.lock()
5581 lock = repo.lock()
5583 wc = repo['.']
5582 wc = repo['.']
5584 try:
5583 try:
5585 for fname in fnames:
5584 for fname in fnames:
5586 f = url.open(ui, fname)
5585 f = url.open(ui, fname)
5587 gen = changegroup.readbundle(f, fname)
5586 gen = changegroup.readbundle(f, fname)
5588 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname)
5587 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname)
5589 bookmarks.updatecurrentbookmark(repo, wc.node(), wc.branch())
5588 bookmarks.updatecurrentbookmark(repo, wc.node(), wc.branch())
5590 finally:
5589 finally:
5591 lock.release()
5590 lock.release()
5592 return postincoming(ui, repo, modheads, opts.get('update'), None)
5591 return postincoming(ui, repo, modheads, opts.get('update'), None)
5593
5592
5594 @command('^update|up|checkout|co',
5593 @command('^update|up|checkout|co',
5595 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
5594 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
5596 ('c', 'check', None,
5595 ('c', 'check', None,
5597 _('update across branches if no uncommitted changes')),
5596 _('update across branches if no uncommitted changes')),
5598 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5597 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5599 ('r', 'rev', '', _('revision'), _('REV'))],
5598 ('r', 'rev', '', _('revision'), _('REV'))],
5600 _('[-c] [-C] [-d DATE] [[-r] REV]'))
5599 _('[-c] [-C] [-d DATE] [[-r] REV]'))
5601 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False):
5600 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False):
5602 """update working directory (or switch revisions)
5601 """update working directory (or switch revisions)
5603
5602
5604 Update the repository's working directory to the specified
5603 Update the repository's working directory to the specified
5605 changeset. If no changeset is specified, update to the tip of the
5604 changeset. If no changeset is specified, update to the tip of the
5606 current named branch.
5605 current named branch.
5607
5606
5608 If the changeset is not a descendant of the working directory's
5607 If the changeset is not a descendant of the working directory's
5609 parent, the update is aborted. With the -c/--check option, the
5608 parent, the update is aborted. With the -c/--check option, the
5610 working directory is checked for uncommitted changes; if none are
5609 working directory is checked for uncommitted changes; if none are
5611 found, the working directory is updated to the specified
5610 found, the working directory is updated to the specified
5612 changeset.
5611 changeset.
5613
5612
5614 Update sets the working directory's parent revison to the specified
5613 Update sets the working directory's parent revison to the specified
5615 changeset (see :hg:`help parents`).
5614 changeset (see :hg:`help parents`).
5616
5615
5617 The following rules apply when the working directory contains
5616 The following rules apply when the working directory contains
5618 uncommitted changes:
5617 uncommitted changes:
5619
5618
5620 1. If neither -c/--check nor -C/--clean is specified, and if
5619 1. If neither -c/--check nor -C/--clean is specified, and if
5621 the requested changeset is an ancestor or descendant of
5620 the requested changeset is an ancestor or descendant of
5622 the working directory's parent, the uncommitted changes
5621 the working directory's parent, the uncommitted changes
5623 are merged into the requested changeset and the merged
5622 are merged into the requested changeset and the merged
5624 result is left uncommitted. If the requested changeset is
5623 result is left uncommitted. If the requested changeset is
5625 not an ancestor or descendant (that is, it is on another
5624 not an ancestor or descendant (that is, it is on another
5626 branch), the update is aborted and the uncommitted changes
5625 branch), the update is aborted and the uncommitted changes
5627 are preserved.
5626 are preserved.
5628
5627
5629 2. With the -c/--check option, the update is aborted and the
5628 2. With the -c/--check option, the update is aborted and the
5630 uncommitted changes are preserved.
5629 uncommitted changes are preserved.
5631
5630
5632 3. With the -C/--clean option, uncommitted changes are discarded and
5631 3. With the -C/--clean option, uncommitted changes are discarded and
5633 the working directory is updated to the requested changeset.
5632 the working directory is updated to the requested changeset.
5634
5633
5635 Use null as the changeset to remove the working directory (like
5634 Use null as the changeset to remove the working directory (like
5636 :hg:`clone -U`).
5635 :hg:`clone -U`).
5637
5636
5638 If you want to revert just one file to an older revision, use
5637 If you want to revert just one file to an older revision, use
5639 :hg:`revert [-r REV] NAME`.
5638 :hg:`revert [-r REV] NAME`.
5640
5639
5641 See :hg:`help dates` for a list of formats valid for -d/--date.
5640 See :hg:`help dates` for a list of formats valid for -d/--date.
5642
5641
5643 Returns 0 on success, 1 if there are unresolved files.
5642 Returns 0 on success, 1 if there are unresolved files.
5644 """
5643 """
5645 if rev and node:
5644 if rev and node:
5646 raise util.Abort(_("please specify just one revision"))
5645 raise util.Abort(_("please specify just one revision"))
5647
5646
5648 if rev is None or rev == '':
5647 if rev is None or rev == '':
5649 rev = node
5648 rev = node
5650
5649
5651 # if we defined a bookmark, we have to remember the original bookmark name
5650 # if we defined a bookmark, we have to remember the original bookmark name
5652 brev = rev
5651 brev = rev
5653 rev = scmutil.revsingle(repo, rev, rev).rev()
5652 rev = scmutil.revsingle(repo, rev, rev).rev()
5654
5653
5655 if check and clean:
5654 if check and clean:
5656 raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
5655 raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
5657
5656
5658 if check:
5657 if check:
5659 # we could use dirty() but we can ignore merge and branch trivia
5658 # we could use dirty() but we can ignore merge and branch trivia
5660 c = repo[None]
5659 c = repo[None]
5661 if c.modified() or c.added() or c.removed():
5660 if c.modified() or c.added() or c.removed():
5662 raise util.Abort(_("uncommitted local changes"))
5661 raise util.Abort(_("uncommitted local changes"))
5663
5662
5664 if date:
5663 if date:
5665 if rev is not None:
5664 if rev is not None:
5666 raise util.Abort(_("you can't specify a revision and a date"))
5665 raise util.Abort(_("you can't specify a revision and a date"))
5667 rev = cmdutil.finddate(ui, repo, date)
5666 rev = cmdutil.finddate(ui, repo, date)
5668
5667
5669 if clean or check:
5668 if clean or check:
5670 ret = hg.clean(repo, rev)
5669 ret = hg.clean(repo, rev)
5671 else:
5670 else:
5672 ret = hg.update(repo, rev)
5671 ret = hg.update(repo, rev)
5673
5672
5674 if brev in repo._bookmarks:
5673 if brev in repo._bookmarks:
5675 bookmarks.setcurrent(repo, brev)
5674 bookmarks.setcurrent(repo, brev)
5676
5675
5677 return ret
5676 return ret
5678
5677
5679 @command('verify', [])
5678 @command('verify', [])
5680 def verify(ui, repo):
5679 def verify(ui, repo):
5681 """verify the integrity of the repository
5680 """verify the integrity of the repository
5682
5681
5683 Verify the integrity of the current repository.
5682 Verify the integrity of the current repository.
5684
5683
5685 This will perform an extensive check of the repository's
5684 This will perform an extensive check of the repository's
5686 integrity, validating the hashes and checksums of each entry in
5685 integrity, validating the hashes and checksums of each entry in
5687 the changelog, manifest, and tracked files, as well as the
5686 the changelog, manifest, and tracked files, as well as the
5688 integrity of their crosslinks and indices.
5687 integrity of their crosslinks and indices.
5689
5688
5690 Returns 0 on success, 1 if errors are encountered.
5689 Returns 0 on success, 1 if errors are encountered.
5691 """
5690 """
5692 return hg.verify(repo)
5691 return hg.verify(repo)
5693
5692
5694 @command('version', [])
5693 @command('version', [])
5695 def version_(ui):
5694 def version_(ui):
5696 """output version and copyright information"""
5695 """output version and copyright information"""
5697 ui.write(_("Mercurial Distributed SCM (version %s)\n")
5696 ui.write(_("Mercurial Distributed SCM (version %s)\n")
5698 % util.version())
5697 % util.version())
5699 ui.status(_(
5698 ui.status(_(
5700 "(see http://mercurial.selenic.com for more information)\n"
5699 "(see http://mercurial.selenic.com for more information)\n"
5701 "\nCopyright (C) 2005-2011 Matt Mackall and others\n"
5700 "\nCopyright (C) 2005-2011 Matt Mackall and others\n"
5702 "This is free software; see the source for copying conditions. "
5701 "This is free software; see the source for copying conditions. "
5703 "There is NO\nwarranty; "
5702 "There is NO\nwarranty; "
5704 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
5703 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
5705 ))
5704 ))
5706
5705
5707 norepo = ("clone init version help debugcommands debugcomplete"
5706 norepo = ("clone init version help debugcommands debugcomplete"
5708 " debugdate debuginstall debugfsinfo debugpushkey debugwireargs"
5707 " debugdate debuginstall debugfsinfo debugpushkey debugwireargs"
5709 " debugknown debuggetbundle debugbundle")
5708 " debugknown debuggetbundle debugbundle")
5710 optionalrepo = ("identify paths serve showconfig debugancestor debugdag"
5709 optionalrepo = ("identify paths serve showconfig debugancestor debugdag"
5711 " debugdata debugindex debugindexdot debugrevlog")
5710 " debugdata debugindex debugindexdot debugrevlog")
@@ -1,267 +1,270 b''
1 # copies.py - copy detection for Mercurial
1 # copies.py - copy detection for Mercurial
2 #
2 #
3 # Copyright 2008 Matt Mackall <mpm@selenic.com>
3 # Copyright 2008 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 util
8 import util
9 import heapq
9 import heapq
10
10
11 def _nonoverlap(d1, d2, d3):
11 def _nonoverlap(d1, d2, d3):
12 "Return list of elements in d1 not in d2 or d3"
12 "Return list of elements in d1 not in d2 or d3"
13 return sorted([d for d in d1 if d not in d3 and d not in d2])
13 return sorted([d for d in d1 if d not in d3 and d not in d2])
14
14
15 def _dirname(f):
15 def _dirname(f):
16 s = f.rfind("/")
16 s = f.rfind("/")
17 if s == -1:
17 if s == -1:
18 return ""
18 return ""
19 return f[:s]
19 return f[:s]
20
20
21 def _dirs(files):
21 def _dirs(files):
22 d = set()
22 d = set()
23 for f in files:
23 for f in files:
24 f = _dirname(f)
24 f = _dirname(f)
25 while f not in d:
25 while f not in d:
26 d.add(f)
26 d.add(f)
27 f = _dirname(f)
27 f = _dirname(f)
28 return d
28 return d
29
29
30 def _findlimit(repo, a, b):
30 def _findlimit(repo, a, b):
31 """Find the earliest revision that's an ancestor of a or b but not both,
31 """Find the earliest revision that's an ancestor of a or b but not both,
32 None if no such revision exists.
32 None if no such revision exists.
33 """
33 """
34 # basic idea:
34 # basic idea:
35 # - mark a and b with different sides
35 # - mark a and b with different sides
36 # - if a parent's children are all on the same side, the parent is
36 # - if a parent's children are all on the same side, the parent is
37 # on that side, otherwise it is on no side
37 # on that side, otherwise it is on no side
38 # - walk the graph in topological order with the help of a heap;
38 # - walk the graph in topological order with the help of a heap;
39 # - add unseen parents to side map
39 # - add unseen parents to side map
40 # - clear side of any parent that has children on different sides
40 # - clear side of any parent that has children on different sides
41 # - track number of interesting revs that might still be on a side
41 # - track number of interesting revs that might still be on a side
42 # - track the lowest interesting rev seen
42 # - track the lowest interesting rev seen
43 # - quit when interesting revs is zero
43 # - quit when interesting revs is zero
44
44
45 cl = repo.changelog
45 cl = repo.changelog
46 working = len(cl) # pseudo rev for the working directory
46 working = len(cl) # pseudo rev for the working directory
47 if a is None:
47 if a is None:
48 a = working
48 a = working
49 if b is None:
49 if b is None:
50 b = working
50 b = working
51
51
52 side = {a: -1, b: 1}
52 side = {a: -1, b: 1}
53 visit = [-a, -b]
53 visit = [-a, -b]
54 heapq.heapify(visit)
54 heapq.heapify(visit)
55 interesting = len(visit)
55 interesting = len(visit)
56 hascommonancestor = False
56 hascommonancestor = False
57 limit = working
57 limit = working
58
58
59 while interesting:
59 while interesting:
60 r = -heapq.heappop(visit)
60 r = -heapq.heappop(visit)
61 if r == working:
61 if r == working:
62 parents = [cl.rev(p) for p in repo.dirstate.parents()]
62 parents = [cl.rev(p) for p in repo.dirstate.parents()]
63 else:
63 else:
64 parents = cl.parentrevs(r)
64 parents = cl.parentrevs(r)
65 for p in parents:
65 for p in parents:
66 if p < 0:
66 if p < 0:
67 continue
67 continue
68 if p not in side:
68 if p not in side:
69 # first time we see p; add it to visit
69 # first time we see p; add it to visit
70 side[p] = side[r]
70 side[p] = side[r]
71 if side[p]:
71 if side[p]:
72 interesting += 1
72 interesting += 1
73 heapq.heappush(visit, -p)
73 heapq.heappush(visit, -p)
74 elif side[p] and side[p] != side[r]:
74 elif side[p] and side[p] != side[r]:
75 # p was interesting but now we know better
75 # p was interesting but now we know better
76 side[p] = 0
76 side[p] = 0
77 interesting -= 1
77 interesting -= 1
78 hascommonancestor = True
78 hascommonancestor = True
79 if side[r]:
79 if side[r]:
80 limit = r # lowest rev visited
80 limit = r # lowest rev visited
81 interesting -= 1
81 interesting -= 1
82
82
83 if not hascommonancestor:
83 if not hascommonancestor:
84 return None
84 return None
85 return limit
85 return limit
86
86
87 def copies(repo, c1, c2, ca, checkdirs=False):
87 def pathcopies(c1, c2):
88 return mergecopies(c1._repo, c1, c2, c1._repo["null"], False)[0]
89
90 def mergecopies(repo, c1, c2, ca, checkdirs=True):
88 """
91 """
89 Find moves and copies between context c1 and c2
92 Find moves and copies between context c1 and c2
90 """
93 """
91 # avoid silly behavior for update from empty dir
94 # avoid silly behavior for update from empty dir
92 if not c1 or not c2 or c1 == c2:
95 if not c1 or not c2 or c1 == c2:
93 return {}, {}
96 return {}, {}
94
97
95 # avoid silly behavior for parent -> working dir
98 # avoid silly behavior for parent -> working dir
96 if c2.node() is None and c1.node() == repo.dirstate.p1():
99 if c2.node() is None and c1.node() == repo.dirstate.p1():
97 return repo.dirstate.copies(), {}
100 return repo.dirstate.copies(), {}
98
101
99 limit = _findlimit(repo, c1.rev(), c2.rev())
102 limit = _findlimit(repo, c1.rev(), c2.rev())
100 if limit is None:
103 if limit is None:
101 # no common ancestor, no copies
104 # no common ancestor, no copies
102 return {}, {}
105 return {}, {}
103 m1 = c1.manifest()
106 m1 = c1.manifest()
104 m2 = c2.manifest()
107 m2 = c2.manifest()
105 ma = ca.manifest()
108 ma = ca.manifest()
106
109
107 def makectx(f, n):
110 def makectx(f, n):
108 if len(n) != 20: # in a working context?
111 if len(n) != 20: # in a working context?
109 if c1.rev() is None:
112 if c1.rev() is None:
110 return c1.filectx(f)
113 return c1.filectx(f)
111 return c2.filectx(f)
114 return c2.filectx(f)
112 return repo.filectx(f, fileid=n)
115 return repo.filectx(f, fileid=n)
113
116
114 ctx = util.lrucachefunc(makectx)
117 ctx = util.lrucachefunc(makectx)
115 copy = {}
118 copy = {}
116 fullcopy = {}
119 fullcopy = {}
117 diverge = {}
120 diverge = {}
118
121
119 def related(f1, f2, limit):
122 def related(f1, f2, limit):
120 # Walk back to common ancestor to see if the two files originate
123 # Walk back to common ancestor to see if the two files originate
121 # from the same file. Since workingfilectx's rev() is None it messes
124 # from the same file. Since workingfilectx's rev() is None it messes
122 # up the integer comparison logic, hence the pre-step check for
125 # up the integer comparison logic, hence the pre-step check for
123 # None (f1 and f2 can only be workingfilectx's initially).
126 # None (f1 and f2 can only be workingfilectx's initially).
124
127
125 if f1 == f2:
128 if f1 == f2:
126 return f1 # a match
129 return f1 # a match
127
130
128 g1, g2 = f1.ancestors(), f2.ancestors()
131 g1, g2 = f1.ancestors(), f2.ancestors()
129 try:
132 try:
130 f1r, f2r = f1.rev(), f2.rev()
133 f1r, f2r = f1.rev(), f2.rev()
131
134
132 if f1r is None:
135 if f1r is None:
133 f1 = g1.next()
136 f1 = g1.next()
134 if f2r is None:
137 if f2r is None:
135 f2 = g2.next()
138 f2 = g2.next()
136
139
137 while True:
140 while True:
138 f1r, f2r = f1.rev(), f2.rev()
141 f1r, f2r = f1.rev(), f2.rev()
139 if f1r > f2r:
142 if f1r > f2r:
140 f1 = g1.next()
143 f1 = g1.next()
141 elif f2r > f1r:
144 elif f2r > f1r:
142 f2 = g2.next()
145 f2 = g2.next()
143 elif f1 == f2:
146 elif f1 == f2:
144 return f1 # a match
147 return f1 # a match
145 elif f1r == f2r or f1r < limit or f2r < limit:
148 elif f1r == f2r or f1r < limit or f2r < limit:
146 return False # copy no longer relevant
149 return False # copy no longer relevant
147 except StopIteration:
150 except StopIteration:
148 return False
151 return False
149
152
150 def checkcopies(f, m1, m2):
153 def checkcopies(f, m1, m2):
151 '''check possible copies of f from m1 to m2'''
154 '''check possible copies of f from m1 to m2'''
152 of = None
155 of = None
153 seen = set([f])
156 seen = set([f])
154 for oc in ctx(f, m1[f]).ancestors():
157 for oc in ctx(f, m1[f]).ancestors():
155 ocr = oc.rev()
158 ocr = oc.rev()
156 of = oc.path()
159 of = oc.path()
157 if of in seen:
160 if of in seen:
158 # check limit late - grab last rename before
161 # check limit late - grab last rename before
159 if ocr < limit:
162 if ocr < limit:
160 break
163 break
161 continue
164 continue
162 seen.add(of)
165 seen.add(of)
163
166
164 fullcopy[f] = of # remember for dir rename detection
167 fullcopy[f] = of # remember for dir rename detection
165 if of not in m2:
168 if of not in m2:
166 continue # no match, keep looking
169 continue # no match, keep looking
167 if m2[of] == ma.get(of):
170 if m2[of] == ma.get(of):
168 break # no merge needed, quit early
171 break # no merge needed, quit early
169 c2 = ctx(of, m2[of])
172 c2 = ctx(of, m2[of])
170 cr = related(oc, c2, ca.rev())
173 cr = related(oc, c2, ca.rev())
171 if cr and (of == f or of == c2.path()): # non-divergent
174 if cr and (of == f or of == c2.path()): # non-divergent
172 copy[f] = of
175 copy[f] = of
173 of = None
176 of = None
174 break
177 break
175
178
176 if of in ma:
179 if of in ma:
177 diverge.setdefault(of, []).append(f)
180 diverge.setdefault(of, []).append(f)
178
181
179 repo.ui.debug(" searching for copies back to rev %d\n" % limit)
182 repo.ui.debug(" searching for copies back to rev %d\n" % limit)
180
183
181 u1 = _nonoverlap(m1, m2, ma)
184 u1 = _nonoverlap(m1, m2, ma)
182 u2 = _nonoverlap(m2, m1, ma)
185 u2 = _nonoverlap(m2, m1, ma)
183
186
184 if u1:
187 if u1:
185 repo.ui.debug(" unmatched files in local:\n %s\n"
188 repo.ui.debug(" unmatched files in local:\n %s\n"
186 % "\n ".join(u1))
189 % "\n ".join(u1))
187 if u2:
190 if u2:
188 repo.ui.debug(" unmatched files in other:\n %s\n"
191 repo.ui.debug(" unmatched files in other:\n %s\n"
189 % "\n ".join(u2))
192 % "\n ".join(u2))
190
193
191 for f in u1:
194 for f in u1:
192 checkcopies(f, m1, m2)
195 checkcopies(f, m1, m2)
193 for f in u2:
196 for f in u2:
194 checkcopies(f, m2, m1)
197 checkcopies(f, m2, m1)
195
198
196 diverge2 = set()
199 diverge2 = set()
197 for of, fl in diverge.items():
200 for of, fl in diverge.items():
198 if len(fl) == 1 or of in c2:
201 if len(fl) == 1 or of in c2:
199 del diverge[of] # not actually divergent, or not a rename
202 del diverge[of] # not actually divergent, or not a rename
200 else:
203 else:
201 diverge2.update(fl) # reverse map for below
204 diverge2.update(fl) # reverse map for below
202
205
203 if fullcopy:
206 if fullcopy:
204 repo.ui.debug(" all copies found (* = to merge, ! = divergent):\n")
207 repo.ui.debug(" all copies found (* = to merge, ! = divergent):\n")
205 for f in fullcopy:
208 for f in fullcopy:
206 note = ""
209 note = ""
207 if f in copy:
210 if f in copy:
208 note += "*"
211 note += "*"
209 if f in diverge2:
212 if f in diverge2:
210 note += "!"
213 note += "!"
211 repo.ui.debug(" %s -> %s %s\n" % (f, fullcopy[f], note))
214 repo.ui.debug(" %s -> %s %s\n" % (f, fullcopy[f], note))
212 del diverge2
215 del diverge2
213
216
214 if not fullcopy or not checkdirs:
217 if not fullcopy or not checkdirs:
215 return copy, diverge
218 return copy, diverge
216
219
217 repo.ui.debug(" checking for directory renames\n")
220 repo.ui.debug(" checking for directory renames\n")
218
221
219 # generate a directory move map
222 # generate a directory move map
220 d1, d2 = _dirs(m1), _dirs(m2)
223 d1, d2 = _dirs(m1), _dirs(m2)
221 invalid = set()
224 invalid = set()
222 dirmove = {}
225 dirmove = {}
223
226
224 # examine each file copy for a potential directory move, which is
227 # examine each file copy for a potential directory move, which is
225 # when all the files in a directory are moved to a new directory
228 # when all the files in a directory are moved to a new directory
226 for dst, src in fullcopy.iteritems():
229 for dst, src in fullcopy.iteritems():
227 dsrc, ddst = _dirname(src), _dirname(dst)
230 dsrc, ddst = _dirname(src), _dirname(dst)
228 if dsrc in invalid:
231 if dsrc in invalid:
229 # already seen to be uninteresting
232 # already seen to be uninteresting
230 continue
233 continue
231 elif dsrc in d1 and ddst in d1:
234 elif dsrc in d1 and ddst in d1:
232 # directory wasn't entirely moved locally
235 # directory wasn't entirely moved locally
233 invalid.add(dsrc)
236 invalid.add(dsrc)
234 elif dsrc in d2 and ddst in d2:
237 elif dsrc in d2 and ddst in d2:
235 # directory wasn't entirely moved remotely
238 # directory wasn't entirely moved remotely
236 invalid.add(dsrc)
239 invalid.add(dsrc)
237 elif dsrc in dirmove and dirmove[dsrc] != ddst:
240 elif dsrc in dirmove and dirmove[dsrc] != ddst:
238 # files from the same directory moved to two different places
241 # files from the same directory moved to two different places
239 invalid.add(dsrc)
242 invalid.add(dsrc)
240 else:
243 else:
241 # looks good so far
244 # looks good so far
242 dirmove[dsrc + "/"] = ddst + "/"
245 dirmove[dsrc + "/"] = ddst + "/"
243
246
244 for i in invalid:
247 for i in invalid:
245 if i in dirmove:
248 if i in dirmove:
246 del dirmove[i]
249 del dirmove[i]
247 del d1, d2, invalid
250 del d1, d2, invalid
248
251
249 if not dirmove:
252 if not dirmove:
250 return copy, diverge
253 return copy, diverge
251
254
252 for d in dirmove:
255 for d in dirmove:
253 repo.ui.debug(" dir %s -> %s\n" % (d, dirmove[d]))
256 repo.ui.debug(" dir %s -> %s\n" % (d, dirmove[d]))
254
257
255 # check unaccounted nonoverlapping files against directory moves
258 # check unaccounted nonoverlapping files against directory moves
256 for f in u1 + u2:
259 for f in u1 + u2:
257 if f not in fullcopy:
260 if f not in fullcopy:
258 for d in dirmove:
261 for d in dirmove:
259 if f.startswith(d):
262 if f.startswith(d):
260 # new file added in a directory that was moved, move it
263 # new file added in a directory that was moved, move it
261 df = dirmove[d] + f[len(d):]
264 df = dirmove[d] + f[len(d):]
262 if df not in copy:
265 if df not in copy:
263 copy[f] = df
266 copy[f] = df
264 repo.ui.debug(" file %s -> %s\n" % (f, copy[f]))
267 repo.ui.debug(" file %s -> %s\n" % (f, copy[f]))
265 break
268 break
266
269
267 return copy, diverge
270 return copy, diverge
@@ -1,584 +1,584 b''
1 # merge.py - directory-level update/merge handling for Mercurial
1 # merge.py - directory-level update/merge handling for Mercurial
2 #
2 #
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006, 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 nullid, nullrev, hex, bin
8 from node import nullid, nullrev, hex, bin
9 from i18n import _
9 from i18n import _
10 import scmutil, util, filemerge, copies, subrepo
10 import scmutil, util, filemerge, copies, subrepo
11 import errno, os, shutil
11 import errno, os, shutil
12
12
13 class mergestate(object):
13 class mergestate(object):
14 '''track 3-way merge state of individual files'''
14 '''track 3-way merge state of individual files'''
15 def __init__(self, repo):
15 def __init__(self, repo):
16 self._repo = repo
16 self._repo = repo
17 self._dirty = False
17 self._dirty = False
18 self._read()
18 self._read()
19 def reset(self, node=None):
19 def reset(self, node=None):
20 self._state = {}
20 self._state = {}
21 if node:
21 if node:
22 self._local = node
22 self._local = node
23 shutil.rmtree(self._repo.join("merge"), True)
23 shutil.rmtree(self._repo.join("merge"), True)
24 self._dirty = False
24 self._dirty = False
25 def _read(self):
25 def _read(self):
26 self._state = {}
26 self._state = {}
27 try:
27 try:
28 f = self._repo.opener("merge/state")
28 f = self._repo.opener("merge/state")
29 for i, l in enumerate(f):
29 for i, l in enumerate(f):
30 if i == 0:
30 if i == 0:
31 self._local = bin(l[:-1])
31 self._local = bin(l[:-1])
32 else:
32 else:
33 bits = l[:-1].split("\0")
33 bits = l[:-1].split("\0")
34 self._state[bits[0]] = bits[1:]
34 self._state[bits[0]] = bits[1:]
35 f.close()
35 f.close()
36 except IOError, err:
36 except IOError, err:
37 if err.errno != errno.ENOENT:
37 if err.errno != errno.ENOENT:
38 raise
38 raise
39 self._dirty = False
39 self._dirty = False
40 def commit(self):
40 def commit(self):
41 if self._dirty:
41 if self._dirty:
42 f = self._repo.opener("merge/state", "w")
42 f = self._repo.opener("merge/state", "w")
43 f.write(hex(self._local) + "\n")
43 f.write(hex(self._local) + "\n")
44 for d, v in self._state.iteritems():
44 for d, v in self._state.iteritems():
45 f.write("\0".join([d] + v) + "\n")
45 f.write("\0".join([d] + v) + "\n")
46 f.close()
46 f.close()
47 self._dirty = False
47 self._dirty = False
48 def add(self, fcl, fco, fca, fd, flags):
48 def add(self, fcl, fco, fca, fd, flags):
49 hash = util.sha1(fcl.path()).hexdigest()
49 hash = util.sha1(fcl.path()).hexdigest()
50 self._repo.opener.write("merge/" + hash, fcl.data())
50 self._repo.opener.write("merge/" + hash, fcl.data())
51 self._state[fd] = ['u', hash, fcl.path(), fca.path(),
51 self._state[fd] = ['u', hash, fcl.path(), fca.path(),
52 hex(fca.filenode()), fco.path(), flags]
52 hex(fca.filenode()), fco.path(), flags]
53 self._dirty = True
53 self._dirty = True
54 def __contains__(self, dfile):
54 def __contains__(self, dfile):
55 return dfile in self._state
55 return dfile in self._state
56 def __getitem__(self, dfile):
56 def __getitem__(self, dfile):
57 return self._state[dfile][0]
57 return self._state[dfile][0]
58 def __iter__(self):
58 def __iter__(self):
59 l = self._state.keys()
59 l = self._state.keys()
60 l.sort()
60 l.sort()
61 for f in l:
61 for f in l:
62 yield f
62 yield f
63 def mark(self, dfile, state):
63 def mark(self, dfile, state):
64 self._state[dfile][0] = state
64 self._state[dfile][0] = state
65 self._dirty = True
65 self._dirty = True
66 def resolve(self, dfile, wctx, octx):
66 def resolve(self, dfile, wctx, octx):
67 if self[dfile] == 'r':
67 if self[dfile] == 'r':
68 return 0
68 return 0
69 state, hash, lfile, afile, anode, ofile, flags = self._state[dfile]
69 state, hash, lfile, afile, anode, ofile, flags = self._state[dfile]
70 f = self._repo.opener("merge/" + hash)
70 f = self._repo.opener("merge/" + hash)
71 self._repo.wwrite(dfile, f.read(), flags)
71 self._repo.wwrite(dfile, f.read(), flags)
72 f.close()
72 f.close()
73 fcd = wctx[dfile]
73 fcd = wctx[dfile]
74 fco = octx[ofile]
74 fco = octx[ofile]
75 fca = self._repo.filectx(afile, fileid=anode)
75 fca = self._repo.filectx(afile, fileid=anode)
76 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
76 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
77 if r is None:
77 if r is None:
78 # no real conflict
78 # no real conflict
79 del self._state[dfile]
79 del self._state[dfile]
80 elif not r:
80 elif not r:
81 self.mark(dfile, 'r')
81 self.mark(dfile, 'r')
82 return r
82 return r
83
83
84 def _checkunknown(wctx, mctx, folding):
84 def _checkunknown(wctx, mctx, folding):
85 "check for collisions between unknown files and files in mctx"
85 "check for collisions between unknown files and files in mctx"
86 if folding:
86 if folding:
87 foldf = util.normcase
87 foldf = util.normcase
88 else:
88 else:
89 foldf = lambda fn: fn
89 foldf = lambda fn: fn
90 folded = {}
90 folded = {}
91 for fn in mctx:
91 for fn in mctx:
92 folded[foldf(fn)] = fn
92 folded[foldf(fn)] = fn
93 for fn in wctx.unknown():
93 for fn in wctx.unknown():
94 f = foldf(fn)
94 f = foldf(fn)
95 if f in folded and mctx[folded[f]].cmp(wctx[f]):
95 if f in folded and mctx[folded[f]].cmp(wctx[f]):
96 raise util.Abort(_("untracked file in working directory differs"
96 raise util.Abort(_("untracked file in working directory differs"
97 " from file in requested revision: '%s'") % fn)
97 " from file in requested revision: '%s'") % fn)
98
98
99 def _checkcollision(mctx, wctx):
99 def _checkcollision(mctx, wctx):
100 "check for case folding collisions in the destination context"
100 "check for case folding collisions in the destination context"
101 folded = {}
101 folded = {}
102 for fn in mctx:
102 for fn in mctx:
103 fold = util.normcase(fn)
103 fold = util.normcase(fn)
104 if fold in folded:
104 if fold in folded:
105 raise util.Abort(_("case-folding collision between %s and %s")
105 raise util.Abort(_("case-folding collision between %s and %s")
106 % (fn, folded[fold]))
106 % (fn, folded[fold]))
107 folded[fold] = fn
107 folded[fold] = fn
108
108
109 if wctx:
109 if wctx:
110 for fn in wctx:
110 for fn in wctx:
111 fold = util.normcase(fn)
111 fold = util.normcase(fn)
112 mfn = folded.get(fold, None)
112 mfn = folded.get(fold, None)
113 if mfn and (mfn != fn):
113 if mfn and (mfn != fn):
114 raise util.Abort(_("case-folding collision between %s and %s")
114 raise util.Abort(_("case-folding collision between %s and %s")
115 % (mfn, fn))
115 % (mfn, fn))
116
116
117 def _forgetremoved(wctx, mctx, branchmerge):
117 def _forgetremoved(wctx, mctx, branchmerge):
118 """
118 """
119 Forget removed files
119 Forget removed files
120
120
121 If we're jumping between revisions (as opposed to merging), and if
121 If we're jumping between revisions (as opposed to merging), and if
122 neither the working directory nor the target rev has the file,
122 neither the working directory nor the target rev has the file,
123 then we need to remove it from the dirstate, to prevent the
123 then we need to remove it from the dirstate, to prevent the
124 dirstate from listing the file when it is no longer in the
124 dirstate from listing the file when it is no longer in the
125 manifest.
125 manifest.
126
126
127 If we're merging, and the other revision has removed a file
127 If we're merging, and the other revision has removed a file
128 that is not present in the working directory, we need to mark it
128 that is not present in the working directory, we need to mark it
129 as removed.
129 as removed.
130 """
130 """
131
131
132 action = []
132 action = []
133 state = branchmerge and 'r' or 'f'
133 state = branchmerge and 'r' or 'f'
134 for f in wctx.deleted():
134 for f in wctx.deleted():
135 if f not in mctx:
135 if f not in mctx:
136 action.append((f, state))
136 action.append((f, state))
137
137
138 if not branchmerge:
138 if not branchmerge:
139 for f in wctx.removed():
139 for f in wctx.removed():
140 if f not in mctx:
140 if f not in mctx:
141 action.append((f, "f"))
141 action.append((f, "f"))
142
142
143 return action
143 return action
144
144
145 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
145 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
146 """
146 """
147 Merge p1 and p2 with ancestor pa and generate merge action list
147 Merge p1 and p2 with ancestor pa and generate merge action list
148
148
149 overwrite = whether we clobber working files
149 overwrite = whether we clobber working files
150 partial = function to filter file lists
150 partial = function to filter file lists
151 """
151 """
152
152
153 def fmerge(f, f2, fa):
153 def fmerge(f, f2, fa):
154 """merge flags"""
154 """merge flags"""
155 a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2)
155 a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2)
156 if m == n: # flags agree
156 if m == n: # flags agree
157 return m # unchanged
157 return m # unchanged
158 if m and n and not a: # flags set, don't agree, differ from parent
158 if m and n and not a: # flags set, don't agree, differ from parent
159 r = repo.ui.promptchoice(
159 r = repo.ui.promptchoice(
160 _(" conflicting flags for %s\n"
160 _(" conflicting flags for %s\n"
161 "(n)one, e(x)ec or sym(l)ink?") % f,
161 "(n)one, e(x)ec or sym(l)ink?") % f,
162 (_("&None"), _("E&xec"), _("Sym&link")), 0)
162 (_("&None"), _("E&xec"), _("Sym&link")), 0)
163 if r == 1:
163 if r == 1:
164 return "x" # Exec
164 return "x" # Exec
165 if r == 2:
165 if r == 2:
166 return "l" # Symlink
166 return "l" # Symlink
167 return ""
167 return ""
168 if m and m != a: # changed from a to m
168 if m and m != a: # changed from a to m
169 return m
169 return m
170 if n and n != a: # changed from a to n
170 if n and n != a: # changed from a to n
171 return n
171 return n
172 return '' # flag was cleared
172 return '' # flag was cleared
173
173
174 def act(msg, m, f, *args):
174 def act(msg, m, f, *args):
175 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
175 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
176 action.append((f, m) + args)
176 action.append((f, m) + args)
177
177
178 action, copy = [], {}
178 action, copy = [], {}
179
179
180 if overwrite:
180 if overwrite:
181 pa = p1
181 pa = p1
182 elif pa == p2: # backwards
182 elif pa == p2: # backwards
183 pa = p1.p1()
183 pa = p1.p1()
184 elif pa and repo.ui.configbool("merge", "followcopies", True):
184 elif pa and repo.ui.configbool("merge", "followcopies", True):
185 dirs = repo.ui.configbool("merge", "followdirs", True)
185 dirs = repo.ui.configbool("merge", "followdirs", True)
186 copy, diverge = copies.copies(repo, p1, p2, pa, dirs)
186 copy, diverge = copies.mergecopies(repo, p1, p2, pa, dirs)
187 for of, fl in diverge.iteritems():
187 for of, fl in diverge.iteritems():
188 act("divergent renames", "dr", of, fl)
188 act("divergent renames", "dr", of, fl)
189
189
190 repo.ui.note(_("resolving manifests\n"))
190 repo.ui.note(_("resolving manifests\n"))
191 repo.ui.debug(" overwrite: %s, partial: %s\n"
191 repo.ui.debug(" overwrite: %s, partial: %s\n"
192 % (bool(overwrite), bool(partial)))
192 % (bool(overwrite), bool(partial)))
193 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, p1, p2))
193 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, p1, p2))
194
194
195 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
195 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
196 copied = set(copy.values())
196 copied = set(copy.values())
197
197
198 if '.hgsubstate' in m1:
198 if '.hgsubstate' in m1:
199 # check whether sub state is modified
199 # check whether sub state is modified
200 for s in p1.substate:
200 for s in p1.substate:
201 if p1.sub(s).dirty():
201 if p1.sub(s).dirty():
202 m1['.hgsubstate'] += "+"
202 m1['.hgsubstate'] += "+"
203 break
203 break
204
204
205 # Compare manifests
205 # Compare manifests
206 for f, n in m1.iteritems():
206 for f, n in m1.iteritems():
207 if partial and not partial(f):
207 if partial and not partial(f):
208 continue
208 continue
209 if f in m2:
209 if f in m2:
210 rflags = fmerge(f, f, f)
210 rflags = fmerge(f, f, f)
211 a = ma.get(f, nullid)
211 a = ma.get(f, nullid)
212 if n == m2[f] or m2[f] == a: # same or local newer
212 if n == m2[f] or m2[f] == a: # same or local newer
213 # is file locally modified or flags need changing?
213 # is file locally modified or flags need changing?
214 # dirstate flags may need to be made current
214 # dirstate flags may need to be made current
215 if m1.flags(f) != rflags or n[20:]:
215 if m1.flags(f) != rflags or n[20:]:
216 act("update permissions", "e", f, rflags)
216 act("update permissions", "e", f, rflags)
217 elif n == a: # remote newer
217 elif n == a: # remote newer
218 act("remote is newer", "g", f, rflags)
218 act("remote is newer", "g", f, rflags)
219 else: # both changed
219 else: # both changed
220 act("versions differ", "m", f, f, f, rflags, False)
220 act("versions differ", "m", f, f, f, rflags, False)
221 elif f in copied: # files we'll deal with on m2 side
221 elif f in copied: # files we'll deal with on m2 side
222 pass
222 pass
223 elif f in copy:
223 elif f in copy:
224 f2 = copy[f]
224 f2 = copy[f]
225 if f2 not in m2: # directory rename
225 if f2 not in m2: # directory rename
226 act("remote renamed directory to " + f2, "d",
226 act("remote renamed directory to " + f2, "d",
227 f, None, f2, m1.flags(f))
227 f, None, f2, m1.flags(f))
228 else: # case 2 A,B/B/B or case 4,21 A/B/B
228 else: # case 2 A,B/B/B or case 4,21 A/B/B
229 act("local copied/moved to " + f2, "m",
229 act("local copied/moved to " + f2, "m",
230 f, f2, f, fmerge(f, f2, f2), False)
230 f, f2, f, fmerge(f, f2, f2), False)
231 elif f in ma: # clean, a different, no remote
231 elif f in ma: # clean, a different, no remote
232 if n != ma[f]:
232 if n != ma[f]:
233 if repo.ui.promptchoice(
233 if repo.ui.promptchoice(
234 _(" local changed %s which remote deleted\n"
234 _(" local changed %s which remote deleted\n"
235 "use (c)hanged version or (d)elete?") % f,
235 "use (c)hanged version or (d)elete?") % f,
236 (_("&Changed"), _("&Delete")), 0):
236 (_("&Changed"), _("&Delete")), 0):
237 act("prompt delete", "r", f)
237 act("prompt delete", "r", f)
238 else:
238 else:
239 act("prompt keep", "a", f)
239 act("prompt keep", "a", f)
240 elif n[20:] == "a": # added, no remote
240 elif n[20:] == "a": # added, no remote
241 act("remote deleted", "f", f)
241 act("remote deleted", "f", f)
242 elif n[20:] != "u":
242 elif n[20:] != "u":
243 act("other deleted", "r", f)
243 act("other deleted", "r", f)
244
244
245 for f, n in m2.iteritems():
245 for f, n in m2.iteritems():
246 if partial and not partial(f):
246 if partial and not partial(f):
247 continue
247 continue
248 if f in m1 or f in copied: # files already visited
248 if f in m1 or f in copied: # files already visited
249 continue
249 continue
250 if f in copy:
250 if f in copy:
251 f2 = copy[f]
251 f2 = copy[f]
252 if f2 not in m1: # directory rename
252 if f2 not in m1: # directory rename
253 act("local renamed directory to " + f2, "d",
253 act("local renamed directory to " + f2, "d",
254 None, f, f2, m2.flags(f))
254 None, f, f2, m2.flags(f))
255 elif f2 in m2: # rename case 1, A/A,B/A
255 elif f2 in m2: # rename case 1, A/A,B/A
256 act("remote copied to " + f, "m",
256 act("remote copied to " + f, "m",
257 f2, f, f, fmerge(f2, f, f2), False)
257 f2, f, f, fmerge(f2, f, f2), False)
258 else: # case 3,20 A/B/A
258 else: # case 3,20 A/B/A
259 act("remote moved to " + f, "m",
259 act("remote moved to " + f, "m",
260 f2, f, f, fmerge(f2, f, f2), True)
260 f2, f, f, fmerge(f2, f, f2), True)
261 elif f not in ma:
261 elif f not in ma:
262 act("remote created", "g", f, m2.flags(f))
262 act("remote created", "g", f, m2.flags(f))
263 elif n != ma[f]:
263 elif n != ma[f]:
264 if repo.ui.promptchoice(
264 if repo.ui.promptchoice(
265 _("remote changed %s which local deleted\n"
265 _("remote changed %s which local deleted\n"
266 "use (c)hanged version or leave (d)eleted?") % f,
266 "use (c)hanged version or leave (d)eleted?") % f,
267 (_("&Changed"), _("&Deleted")), 0) == 0:
267 (_("&Changed"), _("&Deleted")), 0) == 0:
268 act("prompt recreating", "g", f, m2.flags(f))
268 act("prompt recreating", "g", f, m2.flags(f))
269
269
270 return action
270 return action
271
271
272 def actionkey(a):
272 def actionkey(a):
273 return a[1] == 'r' and -1 or 0, a
273 return a[1] == 'r' and -1 or 0, a
274
274
275 def applyupdates(repo, action, wctx, mctx, actx, overwrite):
275 def applyupdates(repo, action, wctx, mctx, actx, overwrite):
276 """apply the merge action list to the working directory
276 """apply the merge action list to the working directory
277
277
278 wctx is the working copy context
278 wctx is the working copy context
279 mctx is the context to be merged into the working copy
279 mctx is the context to be merged into the working copy
280 actx is the context of the common ancestor
280 actx is the context of the common ancestor
281
281
282 Return a tuple of counts (updated, merged, removed, unresolved) that
282 Return a tuple of counts (updated, merged, removed, unresolved) that
283 describes how many files were affected by the update.
283 describes how many files were affected by the update.
284 """
284 """
285
285
286 updated, merged, removed, unresolved = 0, 0, 0, 0
286 updated, merged, removed, unresolved = 0, 0, 0, 0
287 ms = mergestate(repo)
287 ms = mergestate(repo)
288 ms.reset(wctx.p1().node())
288 ms.reset(wctx.p1().node())
289 moves = []
289 moves = []
290 action.sort(key=actionkey)
290 action.sort(key=actionkey)
291
291
292 # prescan for merges
292 # prescan for merges
293 for a in action:
293 for a in action:
294 f, m = a[:2]
294 f, m = a[:2]
295 if m == 'm': # merge
295 if m == 'm': # merge
296 f2, fd, flags, move = a[2:]
296 f2, fd, flags, move = a[2:]
297 if f == '.hgsubstate': # merged internally
297 if f == '.hgsubstate': # merged internally
298 continue
298 continue
299 repo.ui.debug("preserving %s for resolve of %s\n" % (f, fd))
299 repo.ui.debug("preserving %s for resolve of %s\n" % (f, fd))
300 fcl = wctx[f]
300 fcl = wctx[f]
301 fco = mctx[f2]
301 fco = mctx[f2]
302 if mctx == actx: # backwards, use working dir parent as ancestor
302 if mctx == actx: # backwards, use working dir parent as ancestor
303 if fcl.parents():
303 if fcl.parents():
304 fca = fcl.p1()
304 fca = fcl.p1()
305 else:
305 else:
306 fca = repo.filectx(f, fileid=nullrev)
306 fca = repo.filectx(f, fileid=nullrev)
307 else:
307 else:
308 fca = fcl.ancestor(fco, actx)
308 fca = fcl.ancestor(fco, actx)
309 if not fca:
309 if not fca:
310 fca = repo.filectx(f, fileid=nullrev)
310 fca = repo.filectx(f, fileid=nullrev)
311 ms.add(fcl, fco, fca, fd, flags)
311 ms.add(fcl, fco, fca, fd, flags)
312 if f != fd and move:
312 if f != fd and move:
313 moves.append(f)
313 moves.append(f)
314
314
315 audit = scmutil.pathauditor(repo.root)
315 audit = scmutil.pathauditor(repo.root)
316
316
317 # remove renamed files after safely stored
317 # remove renamed files after safely stored
318 for f in moves:
318 for f in moves:
319 if os.path.lexists(repo.wjoin(f)):
319 if os.path.lexists(repo.wjoin(f)):
320 repo.ui.debug("removing %s\n" % f)
320 repo.ui.debug("removing %s\n" % f)
321 audit(f)
321 audit(f)
322 os.unlink(repo.wjoin(f))
322 os.unlink(repo.wjoin(f))
323
323
324 numupdates = len(action)
324 numupdates = len(action)
325 for i, a in enumerate(action):
325 for i, a in enumerate(action):
326 f, m = a[:2]
326 f, m = a[:2]
327 repo.ui.progress(_('updating'), i + 1, item=f, total=numupdates,
327 repo.ui.progress(_('updating'), i + 1, item=f, total=numupdates,
328 unit=_('files'))
328 unit=_('files'))
329 if f and f[0] == "/":
329 if f and f[0] == "/":
330 continue
330 continue
331 if m == "r": # remove
331 if m == "r": # remove
332 repo.ui.note(_("removing %s\n") % f)
332 repo.ui.note(_("removing %s\n") % f)
333 audit(f)
333 audit(f)
334 if f == '.hgsubstate': # subrepo states need updating
334 if f == '.hgsubstate': # subrepo states need updating
335 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
335 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
336 try:
336 try:
337 util.unlinkpath(repo.wjoin(f))
337 util.unlinkpath(repo.wjoin(f))
338 except OSError, inst:
338 except OSError, inst:
339 if inst.errno != errno.ENOENT:
339 if inst.errno != errno.ENOENT:
340 repo.ui.warn(_("update failed to remove %s: %s!\n") %
340 repo.ui.warn(_("update failed to remove %s: %s!\n") %
341 (f, inst.strerror))
341 (f, inst.strerror))
342 removed += 1
342 removed += 1
343 elif m == "m": # merge
343 elif m == "m": # merge
344 if f == '.hgsubstate': # subrepo states need updating
344 if f == '.hgsubstate': # subrepo states need updating
345 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx), overwrite)
345 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx), overwrite)
346 continue
346 continue
347 f2, fd, flags, move = a[2:]
347 f2, fd, flags, move = a[2:]
348 repo.wopener.audit(fd)
348 repo.wopener.audit(fd)
349 r = ms.resolve(fd, wctx, mctx)
349 r = ms.resolve(fd, wctx, mctx)
350 if r is not None and r > 0:
350 if r is not None and r > 0:
351 unresolved += 1
351 unresolved += 1
352 else:
352 else:
353 if r is None:
353 if r is None:
354 updated += 1
354 updated += 1
355 else:
355 else:
356 merged += 1
356 merged += 1
357 util.setflags(repo.wjoin(fd), 'l' in flags, 'x' in flags)
357 util.setflags(repo.wjoin(fd), 'l' in flags, 'x' in flags)
358 if (move and repo.dirstate.normalize(fd) != f
358 if (move and repo.dirstate.normalize(fd) != f
359 and os.path.lexists(repo.wjoin(f))):
359 and os.path.lexists(repo.wjoin(f))):
360 repo.ui.debug("removing %s\n" % f)
360 repo.ui.debug("removing %s\n" % f)
361 audit(f)
361 audit(f)
362 os.unlink(repo.wjoin(f))
362 os.unlink(repo.wjoin(f))
363 elif m == "g": # get
363 elif m == "g": # get
364 flags = a[2]
364 flags = a[2]
365 repo.ui.note(_("getting %s\n") % f)
365 repo.ui.note(_("getting %s\n") % f)
366 t = mctx.filectx(f).data()
366 t = mctx.filectx(f).data()
367 repo.wwrite(f, t, flags)
367 repo.wwrite(f, t, flags)
368 t = None
368 t = None
369 updated += 1
369 updated += 1
370 if f == '.hgsubstate': # subrepo states need updating
370 if f == '.hgsubstate': # subrepo states need updating
371 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
371 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
372 elif m == "d": # directory rename
372 elif m == "d": # directory rename
373 f2, fd, flags = a[2:]
373 f2, fd, flags = a[2:]
374 if f:
374 if f:
375 repo.ui.note(_("moving %s to %s\n") % (f, fd))
375 repo.ui.note(_("moving %s to %s\n") % (f, fd))
376 audit(f)
376 audit(f)
377 t = wctx.filectx(f).data()
377 t = wctx.filectx(f).data()
378 repo.wwrite(fd, t, flags)
378 repo.wwrite(fd, t, flags)
379 util.unlinkpath(repo.wjoin(f))
379 util.unlinkpath(repo.wjoin(f))
380 if f2:
380 if f2:
381 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
381 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
382 t = mctx.filectx(f2).data()
382 t = mctx.filectx(f2).data()
383 repo.wwrite(fd, t, flags)
383 repo.wwrite(fd, t, flags)
384 updated += 1
384 updated += 1
385 elif m == "dr": # divergent renames
385 elif m == "dr": # divergent renames
386 fl = a[2]
386 fl = a[2]
387 repo.ui.warn(_("note: possible conflict - %s was renamed "
387 repo.ui.warn(_("note: possible conflict - %s was renamed "
388 "multiple times to:\n") % f)
388 "multiple times to:\n") % f)
389 for nf in fl:
389 for nf in fl:
390 repo.ui.warn(" %s\n" % nf)
390 repo.ui.warn(" %s\n" % nf)
391 elif m == "e": # exec
391 elif m == "e": # exec
392 flags = a[2]
392 flags = a[2]
393 repo.wopener.audit(f)
393 repo.wopener.audit(f)
394 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
394 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
395 ms.commit()
395 ms.commit()
396 repo.ui.progress(_('updating'), None, total=numupdates, unit=_('files'))
396 repo.ui.progress(_('updating'), None, total=numupdates, unit=_('files'))
397
397
398 return updated, merged, removed, unresolved
398 return updated, merged, removed, unresolved
399
399
400 def recordupdates(repo, action, branchmerge):
400 def recordupdates(repo, action, branchmerge):
401 "record merge actions to the dirstate"
401 "record merge actions to the dirstate"
402
402
403 for a in action:
403 for a in action:
404 f, m = a[:2]
404 f, m = a[:2]
405 if m == "r": # remove
405 if m == "r": # remove
406 if branchmerge:
406 if branchmerge:
407 repo.dirstate.remove(f)
407 repo.dirstate.remove(f)
408 else:
408 else:
409 repo.dirstate.drop(f)
409 repo.dirstate.drop(f)
410 elif m == "a": # re-add
410 elif m == "a": # re-add
411 if not branchmerge:
411 if not branchmerge:
412 repo.dirstate.add(f)
412 repo.dirstate.add(f)
413 elif m == "f": # forget
413 elif m == "f": # forget
414 repo.dirstate.drop(f)
414 repo.dirstate.drop(f)
415 elif m == "e": # exec change
415 elif m == "e": # exec change
416 repo.dirstate.normallookup(f)
416 repo.dirstate.normallookup(f)
417 elif m == "g": # get
417 elif m == "g": # get
418 if branchmerge:
418 if branchmerge:
419 repo.dirstate.otherparent(f)
419 repo.dirstate.otherparent(f)
420 else:
420 else:
421 repo.dirstate.normal(f)
421 repo.dirstate.normal(f)
422 elif m == "m": # merge
422 elif m == "m": # merge
423 f2, fd, flag, move = a[2:]
423 f2, fd, flag, move = a[2:]
424 if branchmerge:
424 if branchmerge:
425 # We've done a branch merge, mark this file as merged
425 # We've done a branch merge, mark this file as merged
426 # so that we properly record the merger later
426 # so that we properly record the merger later
427 repo.dirstate.merge(fd)
427 repo.dirstate.merge(fd)
428 if f != f2: # copy/rename
428 if f != f2: # copy/rename
429 if move:
429 if move:
430 repo.dirstate.remove(f)
430 repo.dirstate.remove(f)
431 if f != fd:
431 if f != fd:
432 repo.dirstate.copy(f, fd)
432 repo.dirstate.copy(f, fd)
433 else:
433 else:
434 repo.dirstate.copy(f2, fd)
434 repo.dirstate.copy(f2, fd)
435 else:
435 else:
436 # We've update-merged a locally modified file, so
436 # We've update-merged a locally modified file, so
437 # we set the dirstate to emulate a normal checkout
437 # we set the dirstate to emulate a normal checkout
438 # of that file some time in the past. Thus our
438 # of that file some time in the past. Thus our
439 # merge will appear as a normal local file
439 # merge will appear as a normal local file
440 # modification.
440 # modification.
441 if f2 == fd: # file not locally copied/moved
441 if f2 == fd: # file not locally copied/moved
442 repo.dirstate.normallookup(fd)
442 repo.dirstate.normallookup(fd)
443 if move:
443 if move:
444 repo.dirstate.drop(f)
444 repo.dirstate.drop(f)
445 elif m == "d": # directory rename
445 elif m == "d": # directory rename
446 f2, fd, flag = a[2:]
446 f2, fd, flag = a[2:]
447 if not f2 and f not in repo.dirstate:
447 if not f2 and f not in repo.dirstate:
448 # untracked file moved
448 # untracked file moved
449 continue
449 continue
450 if branchmerge:
450 if branchmerge:
451 repo.dirstate.add(fd)
451 repo.dirstate.add(fd)
452 if f:
452 if f:
453 repo.dirstate.remove(f)
453 repo.dirstate.remove(f)
454 repo.dirstate.copy(f, fd)
454 repo.dirstate.copy(f, fd)
455 if f2:
455 if f2:
456 repo.dirstate.copy(f2, fd)
456 repo.dirstate.copy(f2, fd)
457 else:
457 else:
458 repo.dirstate.normal(fd)
458 repo.dirstate.normal(fd)
459 if f:
459 if f:
460 repo.dirstate.drop(f)
460 repo.dirstate.drop(f)
461
461
462 def update(repo, node, branchmerge, force, partial, ancestor=None):
462 def update(repo, node, branchmerge, force, partial, ancestor=None):
463 """
463 """
464 Perform a merge between the working directory and the given node
464 Perform a merge between the working directory and the given node
465
465
466 node = the node to update to, or None if unspecified
466 node = the node to update to, or None if unspecified
467 branchmerge = whether to merge between branches
467 branchmerge = whether to merge between branches
468 force = whether to force branch merging or file overwriting
468 force = whether to force branch merging or file overwriting
469 partial = a function to filter file lists (dirstate not updated)
469 partial = a function to filter file lists (dirstate not updated)
470
470
471 The table below shows all the behaviors of the update command
471 The table below shows all the behaviors of the update command
472 given the -c and -C or no options, whether the working directory
472 given the -c and -C or no options, whether the working directory
473 is dirty, whether a revision is specified, and the relationship of
473 is dirty, whether a revision is specified, and the relationship of
474 the parent rev to the target rev (linear, on the same named
474 the parent rev to the target rev (linear, on the same named
475 branch, or on another named branch).
475 branch, or on another named branch).
476
476
477 This logic is tested by test-update-branches.t.
477 This logic is tested by test-update-branches.t.
478
478
479 -c -C dirty rev | linear same cross
479 -c -C dirty rev | linear same cross
480 n n n n | ok (1) x
480 n n n n | ok (1) x
481 n n n y | ok ok ok
481 n n n y | ok ok ok
482 n n y * | merge (2) (2)
482 n n y * | merge (2) (2)
483 n y * * | --- discard ---
483 n y * * | --- discard ---
484 y n y * | --- (3) ---
484 y n y * | --- (3) ---
485 y n n * | --- ok ---
485 y n n * | --- ok ---
486 y y * * | --- (4) ---
486 y y * * | --- (4) ---
487
487
488 x = can't happen
488 x = can't happen
489 * = don't-care
489 * = don't-care
490 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
490 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
491 2 = abort: crosses branches (use 'hg merge' to merge or
491 2 = abort: crosses branches (use 'hg merge' to merge or
492 use 'hg update -C' to discard changes)
492 use 'hg update -C' to discard changes)
493 3 = abort: uncommitted local changes
493 3 = abort: uncommitted local changes
494 4 = incompatible options (checked in commands.py)
494 4 = incompatible options (checked in commands.py)
495
495
496 Return the same tuple as applyupdates().
496 Return the same tuple as applyupdates().
497 """
497 """
498
498
499 onode = node
499 onode = node
500 wlock = repo.wlock()
500 wlock = repo.wlock()
501 try:
501 try:
502 wc = repo[None]
502 wc = repo[None]
503 if node is None:
503 if node is None:
504 # tip of current branch
504 # tip of current branch
505 try:
505 try:
506 node = repo.branchtags()[wc.branch()]
506 node = repo.branchtags()[wc.branch()]
507 except KeyError:
507 except KeyError:
508 if wc.branch() == "default": # no default branch!
508 if wc.branch() == "default": # no default branch!
509 node = repo.lookup("tip") # update to tip
509 node = repo.lookup("tip") # update to tip
510 else:
510 else:
511 raise util.Abort(_("branch %s not found") % wc.branch())
511 raise util.Abort(_("branch %s not found") % wc.branch())
512 overwrite = force and not branchmerge
512 overwrite = force and not branchmerge
513 pl = wc.parents()
513 pl = wc.parents()
514 p1, p2 = pl[0], repo[node]
514 p1, p2 = pl[0], repo[node]
515 if ancestor:
515 if ancestor:
516 pa = repo[ancestor]
516 pa = repo[ancestor]
517 else:
517 else:
518 pa = p1.ancestor(p2)
518 pa = p1.ancestor(p2)
519
519
520 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
520 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
521
521
522 ### check phase
522 ### check phase
523 if not overwrite and len(pl) > 1:
523 if not overwrite and len(pl) > 1:
524 raise util.Abort(_("outstanding uncommitted merges"))
524 raise util.Abort(_("outstanding uncommitted merges"))
525 if branchmerge:
525 if branchmerge:
526 if pa == p2:
526 if pa == p2:
527 raise util.Abort(_("merging with a working directory ancestor"
527 raise util.Abort(_("merging with a working directory ancestor"
528 " has no effect"))
528 " has no effect"))
529 elif pa == p1:
529 elif pa == p1:
530 if p1.branch() == p2.branch():
530 if p1.branch() == p2.branch():
531 raise util.Abort(_("nothing to merge"),
531 raise util.Abort(_("nothing to merge"),
532 hint=_("use 'hg update' "
532 hint=_("use 'hg update' "
533 "or check 'hg heads'"))
533 "or check 'hg heads'"))
534 if not force and (wc.files() or wc.deleted()):
534 if not force and (wc.files() or wc.deleted()):
535 raise util.Abort(_("outstanding uncommitted changes"),
535 raise util.Abort(_("outstanding uncommitted changes"),
536 hint=_("use 'hg status' to list changes"))
536 hint=_("use 'hg status' to list changes"))
537 for s in wc.substate:
537 for s in wc.substate:
538 if wc.sub(s).dirty():
538 if wc.sub(s).dirty():
539 raise util.Abort(_("outstanding uncommitted changes in "
539 raise util.Abort(_("outstanding uncommitted changes in "
540 "subrepository '%s'") % s)
540 "subrepository '%s'") % s)
541
541
542 elif not overwrite:
542 elif not overwrite:
543 if pa == p1 or pa == p2: # linear
543 if pa == p1 or pa == p2: # linear
544 pass # all good
544 pass # all good
545 elif wc.dirty(missing=True):
545 elif wc.dirty(missing=True):
546 raise util.Abort(_("crosses branches (merge branches or use"
546 raise util.Abort(_("crosses branches (merge branches or use"
547 " --clean to discard changes)"))
547 " --clean to discard changes)"))
548 elif onode is None:
548 elif onode is None:
549 raise util.Abort(_("crosses branches (merge branches or update"
549 raise util.Abort(_("crosses branches (merge branches or update"
550 " --check to force update)"))
550 " --check to force update)"))
551 else:
551 else:
552 # Allow jumping branches if clean and specific rev given
552 # Allow jumping branches if clean and specific rev given
553 overwrite = True
553 overwrite = True
554
554
555 ### calculate phase
555 ### calculate phase
556 action = []
556 action = []
557 wc.status(unknown=True) # prime cache
557 wc.status(unknown=True) # prime cache
558 folding = not util.checkcase(repo.path)
558 folding = not util.checkcase(repo.path)
559 if not force:
559 if not force:
560 _checkunknown(wc, p2, folding)
560 _checkunknown(wc, p2, folding)
561 if folding:
561 if folding:
562 _checkcollision(p2, branchmerge and p1)
562 _checkcollision(p2, branchmerge and p1)
563 action += _forgetremoved(wc, p2, branchmerge)
563 action += _forgetremoved(wc, p2, branchmerge)
564 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
564 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
565
565
566 ### apply phase
566 ### apply phase
567 if not branchmerge: # just jump to the new rev
567 if not branchmerge: # just jump to the new rev
568 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
568 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
569 if not partial:
569 if not partial:
570 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
570 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
571
571
572 stats = applyupdates(repo, action, wc, p2, pa, overwrite)
572 stats = applyupdates(repo, action, wc, p2, pa, overwrite)
573
573
574 if not partial:
574 if not partial:
575 repo.dirstate.setparents(fp1, fp2)
575 repo.dirstate.setparents(fp1, fp2)
576 recordupdates(repo, action, branchmerge)
576 recordupdates(repo, action, branchmerge)
577 if not branchmerge:
577 if not branchmerge:
578 repo.dirstate.setbranch(p2.branch())
578 repo.dirstate.setbranch(p2.branch())
579 finally:
579 finally:
580 wlock.release()
580 wlock.release()
581
581
582 if not partial:
582 if not partial:
583 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
583 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
584 return stats
584 return stats
@@ -1,1869 +1,1869 b''
1 # patch.py - patch file parsing routines
1 # patch.py - patch file parsing routines
2 #
2 #
3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
4 # Copyright 2007 Chris Mason <chris.mason@oracle.com>
4 # Copyright 2007 Chris Mason <chris.mason@oracle.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 import cStringIO, email.Parser, os, errno, re
9 import cStringIO, email.Parser, os, errno, re
10 import tempfile, zlib, shutil
10 import tempfile, zlib, shutil
11
11
12 from i18n import _
12 from i18n import _
13 from node import hex, nullid, short
13 from node import hex, nullid, short
14 import base85, mdiff, scmutil, util, diffhelpers, copies, encoding, error
14 import base85, mdiff, scmutil, util, diffhelpers, copies, encoding, error
15 import context
15 import context
16
16
17 gitre = re.compile('diff --git a/(.*) b/(.*)')
17 gitre = re.compile('diff --git a/(.*) b/(.*)')
18
18
19 class PatchError(Exception):
19 class PatchError(Exception):
20 pass
20 pass
21
21
22
22
23 # public functions
23 # public functions
24
24
25 def split(stream):
25 def split(stream):
26 '''return an iterator of individual patches from a stream'''
26 '''return an iterator of individual patches from a stream'''
27 def isheader(line, inheader):
27 def isheader(line, inheader):
28 if inheader and line[0] in (' ', '\t'):
28 if inheader and line[0] in (' ', '\t'):
29 # continuation
29 # continuation
30 return True
30 return True
31 if line[0] in (' ', '-', '+'):
31 if line[0] in (' ', '-', '+'):
32 # diff line - don't check for header pattern in there
32 # diff line - don't check for header pattern in there
33 return False
33 return False
34 l = line.split(': ', 1)
34 l = line.split(': ', 1)
35 return len(l) == 2 and ' ' not in l[0]
35 return len(l) == 2 and ' ' not in l[0]
36
36
37 def chunk(lines):
37 def chunk(lines):
38 return cStringIO.StringIO(''.join(lines))
38 return cStringIO.StringIO(''.join(lines))
39
39
40 def hgsplit(stream, cur):
40 def hgsplit(stream, cur):
41 inheader = True
41 inheader = True
42
42
43 for line in stream:
43 for line in stream:
44 if not line.strip():
44 if not line.strip():
45 inheader = False
45 inheader = False
46 if not inheader and line.startswith('# HG changeset patch'):
46 if not inheader and line.startswith('# HG changeset patch'):
47 yield chunk(cur)
47 yield chunk(cur)
48 cur = []
48 cur = []
49 inheader = True
49 inheader = True
50
50
51 cur.append(line)
51 cur.append(line)
52
52
53 if cur:
53 if cur:
54 yield chunk(cur)
54 yield chunk(cur)
55
55
56 def mboxsplit(stream, cur):
56 def mboxsplit(stream, cur):
57 for line in stream:
57 for line in stream:
58 if line.startswith('From '):
58 if line.startswith('From '):
59 for c in split(chunk(cur[1:])):
59 for c in split(chunk(cur[1:])):
60 yield c
60 yield c
61 cur = []
61 cur = []
62
62
63 cur.append(line)
63 cur.append(line)
64
64
65 if cur:
65 if cur:
66 for c in split(chunk(cur[1:])):
66 for c in split(chunk(cur[1:])):
67 yield c
67 yield c
68
68
69 def mimesplit(stream, cur):
69 def mimesplit(stream, cur):
70 def msgfp(m):
70 def msgfp(m):
71 fp = cStringIO.StringIO()
71 fp = cStringIO.StringIO()
72 g = email.Generator.Generator(fp, mangle_from_=False)
72 g = email.Generator.Generator(fp, mangle_from_=False)
73 g.flatten(m)
73 g.flatten(m)
74 fp.seek(0)
74 fp.seek(0)
75 return fp
75 return fp
76
76
77 for line in stream:
77 for line in stream:
78 cur.append(line)
78 cur.append(line)
79 c = chunk(cur)
79 c = chunk(cur)
80
80
81 m = email.Parser.Parser().parse(c)
81 m = email.Parser.Parser().parse(c)
82 if not m.is_multipart():
82 if not m.is_multipart():
83 yield msgfp(m)
83 yield msgfp(m)
84 else:
84 else:
85 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
85 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
86 for part in m.walk():
86 for part in m.walk():
87 ct = part.get_content_type()
87 ct = part.get_content_type()
88 if ct not in ok_types:
88 if ct not in ok_types:
89 continue
89 continue
90 yield msgfp(part)
90 yield msgfp(part)
91
91
92 def headersplit(stream, cur):
92 def headersplit(stream, cur):
93 inheader = False
93 inheader = False
94
94
95 for line in stream:
95 for line in stream:
96 if not inheader and isheader(line, inheader):
96 if not inheader and isheader(line, inheader):
97 yield chunk(cur)
97 yield chunk(cur)
98 cur = []
98 cur = []
99 inheader = True
99 inheader = True
100 if inheader and not isheader(line, inheader):
100 if inheader and not isheader(line, inheader):
101 inheader = False
101 inheader = False
102
102
103 cur.append(line)
103 cur.append(line)
104
104
105 if cur:
105 if cur:
106 yield chunk(cur)
106 yield chunk(cur)
107
107
108 def remainder(cur):
108 def remainder(cur):
109 yield chunk(cur)
109 yield chunk(cur)
110
110
111 class fiter(object):
111 class fiter(object):
112 def __init__(self, fp):
112 def __init__(self, fp):
113 self.fp = fp
113 self.fp = fp
114
114
115 def __iter__(self):
115 def __iter__(self):
116 return self
116 return self
117
117
118 def next(self):
118 def next(self):
119 l = self.fp.readline()
119 l = self.fp.readline()
120 if not l:
120 if not l:
121 raise StopIteration
121 raise StopIteration
122 return l
122 return l
123
123
124 inheader = False
124 inheader = False
125 cur = []
125 cur = []
126
126
127 mimeheaders = ['content-type']
127 mimeheaders = ['content-type']
128
128
129 if not util.safehasattr(stream, 'next'):
129 if not util.safehasattr(stream, 'next'):
130 # http responses, for example, have readline but not next
130 # http responses, for example, have readline but not next
131 stream = fiter(stream)
131 stream = fiter(stream)
132
132
133 for line in stream:
133 for line in stream:
134 cur.append(line)
134 cur.append(line)
135 if line.startswith('# HG changeset patch'):
135 if line.startswith('# HG changeset patch'):
136 return hgsplit(stream, cur)
136 return hgsplit(stream, cur)
137 elif line.startswith('From '):
137 elif line.startswith('From '):
138 return mboxsplit(stream, cur)
138 return mboxsplit(stream, cur)
139 elif isheader(line, inheader):
139 elif isheader(line, inheader):
140 inheader = True
140 inheader = True
141 if line.split(':', 1)[0].lower() in mimeheaders:
141 if line.split(':', 1)[0].lower() in mimeheaders:
142 # let email parser handle this
142 # let email parser handle this
143 return mimesplit(stream, cur)
143 return mimesplit(stream, cur)
144 elif line.startswith('--- ') and inheader:
144 elif line.startswith('--- ') and inheader:
145 # No evil headers seen by diff start, split by hand
145 # No evil headers seen by diff start, split by hand
146 return headersplit(stream, cur)
146 return headersplit(stream, cur)
147 # Not enough info, keep reading
147 # Not enough info, keep reading
148
148
149 # if we are here, we have a very plain patch
149 # if we are here, we have a very plain patch
150 return remainder(cur)
150 return remainder(cur)
151
151
152 def extract(ui, fileobj):
152 def extract(ui, fileobj):
153 '''extract patch from data read from fileobj.
153 '''extract patch from data read from fileobj.
154
154
155 patch can be a normal patch or contained in an email message.
155 patch can be a normal patch or contained in an email message.
156
156
157 return tuple (filename, message, user, date, branch, node, p1, p2).
157 return tuple (filename, message, user, date, branch, node, p1, p2).
158 Any item in the returned tuple can be None. If filename is None,
158 Any item in the returned tuple can be None. If filename is None,
159 fileobj did not contain a patch. Caller must unlink filename when done.'''
159 fileobj did not contain a patch. Caller must unlink filename when done.'''
160
160
161 # attempt to detect the start of a patch
161 # attempt to detect the start of a patch
162 # (this heuristic is borrowed from quilt)
162 # (this heuristic is borrowed from quilt)
163 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |'
163 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |'
164 r'retrieving revision [0-9]+(\.[0-9]+)*$|'
164 r'retrieving revision [0-9]+(\.[0-9]+)*$|'
165 r'---[ \t].*?^\+\+\+[ \t]|'
165 r'---[ \t].*?^\+\+\+[ \t]|'
166 r'\*\*\*[ \t].*?^---[ \t])', re.MULTILINE|re.DOTALL)
166 r'\*\*\*[ \t].*?^---[ \t])', re.MULTILINE|re.DOTALL)
167
167
168 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
168 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
169 tmpfp = os.fdopen(fd, 'w')
169 tmpfp = os.fdopen(fd, 'w')
170 try:
170 try:
171 msg = email.Parser.Parser().parse(fileobj)
171 msg = email.Parser.Parser().parse(fileobj)
172
172
173 subject = msg['Subject']
173 subject = msg['Subject']
174 user = msg['From']
174 user = msg['From']
175 if not subject and not user:
175 if not subject and not user:
176 # Not an email, restore parsed headers if any
176 # Not an email, restore parsed headers if any
177 subject = '\n'.join(': '.join(h) for h in msg.items()) + '\n'
177 subject = '\n'.join(': '.join(h) for h in msg.items()) + '\n'
178
178
179 gitsendmail = 'git-send-email' in msg.get('X-Mailer', '')
179 gitsendmail = 'git-send-email' in msg.get('X-Mailer', '')
180 # should try to parse msg['Date']
180 # should try to parse msg['Date']
181 date = None
181 date = None
182 nodeid = None
182 nodeid = None
183 branch = None
183 branch = None
184 parents = []
184 parents = []
185
185
186 if subject:
186 if subject:
187 if subject.startswith('[PATCH'):
187 if subject.startswith('[PATCH'):
188 pend = subject.find(']')
188 pend = subject.find(']')
189 if pend >= 0:
189 if pend >= 0:
190 subject = subject[pend + 1:].lstrip()
190 subject = subject[pend + 1:].lstrip()
191 subject = re.sub(r'\n[ \t]+', ' ', subject)
191 subject = re.sub(r'\n[ \t]+', ' ', subject)
192 ui.debug('Subject: %s\n' % subject)
192 ui.debug('Subject: %s\n' % subject)
193 if user:
193 if user:
194 ui.debug('From: %s\n' % user)
194 ui.debug('From: %s\n' % user)
195 diffs_seen = 0
195 diffs_seen = 0
196 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
196 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
197 message = ''
197 message = ''
198 for part in msg.walk():
198 for part in msg.walk():
199 content_type = part.get_content_type()
199 content_type = part.get_content_type()
200 ui.debug('Content-Type: %s\n' % content_type)
200 ui.debug('Content-Type: %s\n' % content_type)
201 if content_type not in ok_types:
201 if content_type not in ok_types:
202 continue
202 continue
203 payload = part.get_payload(decode=True)
203 payload = part.get_payload(decode=True)
204 m = diffre.search(payload)
204 m = diffre.search(payload)
205 if m:
205 if m:
206 hgpatch = False
206 hgpatch = False
207 hgpatchheader = False
207 hgpatchheader = False
208 ignoretext = False
208 ignoretext = False
209
209
210 ui.debug('found patch at byte %d\n' % m.start(0))
210 ui.debug('found patch at byte %d\n' % m.start(0))
211 diffs_seen += 1
211 diffs_seen += 1
212 cfp = cStringIO.StringIO()
212 cfp = cStringIO.StringIO()
213 for line in payload[:m.start(0)].splitlines():
213 for line in payload[:m.start(0)].splitlines():
214 if line.startswith('# HG changeset patch') and not hgpatch:
214 if line.startswith('# HG changeset patch') and not hgpatch:
215 ui.debug('patch generated by hg export\n')
215 ui.debug('patch generated by hg export\n')
216 hgpatch = True
216 hgpatch = True
217 hgpatchheader = True
217 hgpatchheader = True
218 # drop earlier commit message content
218 # drop earlier commit message content
219 cfp.seek(0)
219 cfp.seek(0)
220 cfp.truncate()
220 cfp.truncate()
221 subject = None
221 subject = None
222 elif hgpatchheader:
222 elif hgpatchheader:
223 if line.startswith('# User '):
223 if line.startswith('# User '):
224 user = line[7:]
224 user = line[7:]
225 ui.debug('From: %s\n' % user)
225 ui.debug('From: %s\n' % user)
226 elif line.startswith("# Date "):
226 elif line.startswith("# Date "):
227 date = line[7:]
227 date = line[7:]
228 elif line.startswith("# Branch "):
228 elif line.startswith("# Branch "):
229 branch = line[9:]
229 branch = line[9:]
230 elif line.startswith("# Node ID "):
230 elif line.startswith("# Node ID "):
231 nodeid = line[10:]
231 nodeid = line[10:]
232 elif line.startswith("# Parent "):
232 elif line.startswith("# Parent "):
233 parents.append(line[10:])
233 parents.append(line[10:])
234 elif not line.startswith("# "):
234 elif not line.startswith("# "):
235 hgpatchheader = False
235 hgpatchheader = False
236 elif line == '---' and gitsendmail:
236 elif line == '---' and gitsendmail:
237 ignoretext = True
237 ignoretext = True
238 if not hgpatchheader and not ignoretext:
238 if not hgpatchheader and not ignoretext:
239 cfp.write(line)
239 cfp.write(line)
240 cfp.write('\n')
240 cfp.write('\n')
241 message = cfp.getvalue()
241 message = cfp.getvalue()
242 if tmpfp:
242 if tmpfp:
243 tmpfp.write(payload)
243 tmpfp.write(payload)
244 if not payload.endswith('\n'):
244 if not payload.endswith('\n'):
245 tmpfp.write('\n')
245 tmpfp.write('\n')
246 elif not diffs_seen and message and content_type == 'text/plain':
246 elif not diffs_seen and message and content_type == 'text/plain':
247 message += '\n' + payload
247 message += '\n' + payload
248 except:
248 except:
249 tmpfp.close()
249 tmpfp.close()
250 os.unlink(tmpname)
250 os.unlink(tmpname)
251 raise
251 raise
252
252
253 if subject and not message.startswith(subject):
253 if subject and not message.startswith(subject):
254 message = '%s\n%s' % (subject, message)
254 message = '%s\n%s' % (subject, message)
255 tmpfp.close()
255 tmpfp.close()
256 if not diffs_seen:
256 if not diffs_seen:
257 os.unlink(tmpname)
257 os.unlink(tmpname)
258 return None, message, user, date, branch, None, None, None
258 return None, message, user, date, branch, None, None, None
259 p1 = parents and parents.pop(0) or None
259 p1 = parents and parents.pop(0) or None
260 p2 = parents and parents.pop(0) or None
260 p2 = parents and parents.pop(0) or None
261 return tmpname, message, user, date, branch, nodeid, p1, p2
261 return tmpname, message, user, date, branch, nodeid, p1, p2
262
262
263 class patchmeta(object):
263 class patchmeta(object):
264 """Patched file metadata
264 """Patched file metadata
265
265
266 'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
266 'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
267 or COPY. 'path' is patched file path. 'oldpath' is set to the
267 or COPY. 'path' is patched file path. 'oldpath' is set to the
268 origin file when 'op' is either COPY or RENAME, None otherwise. If
268 origin file when 'op' is either COPY or RENAME, None otherwise. If
269 file mode is changed, 'mode' is a tuple (islink, isexec) where
269 file mode is changed, 'mode' is a tuple (islink, isexec) where
270 'islink' is True if the file is a symlink and 'isexec' is True if
270 'islink' is True if the file is a symlink and 'isexec' is True if
271 the file is executable. Otherwise, 'mode' is None.
271 the file is executable. Otherwise, 'mode' is None.
272 """
272 """
273 def __init__(self, path):
273 def __init__(self, path):
274 self.path = path
274 self.path = path
275 self.oldpath = None
275 self.oldpath = None
276 self.mode = None
276 self.mode = None
277 self.op = 'MODIFY'
277 self.op = 'MODIFY'
278 self.binary = False
278 self.binary = False
279
279
280 def setmode(self, mode):
280 def setmode(self, mode):
281 islink = mode & 020000
281 islink = mode & 020000
282 isexec = mode & 0100
282 isexec = mode & 0100
283 self.mode = (islink, isexec)
283 self.mode = (islink, isexec)
284
284
285 def copy(self):
285 def copy(self):
286 other = patchmeta(self.path)
286 other = patchmeta(self.path)
287 other.oldpath = self.oldpath
287 other.oldpath = self.oldpath
288 other.mode = self.mode
288 other.mode = self.mode
289 other.op = self.op
289 other.op = self.op
290 other.binary = self.binary
290 other.binary = self.binary
291 return other
291 return other
292
292
293 def __repr__(self):
293 def __repr__(self):
294 return "<patchmeta %s %r>" % (self.op, self.path)
294 return "<patchmeta %s %r>" % (self.op, self.path)
295
295
296 def readgitpatch(lr):
296 def readgitpatch(lr):
297 """extract git-style metadata about patches from <patchname>"""
297 """extract git-style metadata about patches from <patchname>"""
298
298
299 # Filter patch for git information
299 # Filter patch for git information
300 gp = None
300 gp = None
301 gitpatches = []
301 gitpatches = []
302 for line in lr:
302 for line in lr:
303 line = line.rstrip(' \r\n')
303 line = line.rstrip(' \r\n')
304 if line.startswith('diff --git'):
304 if line.startswith('diff --git'):
305 m = gitre.match(line)
305 m = gitre.match(line)
306 if m:
306 if m:
307 if gp:
307 if gp:
308 gitpatches.append(gp)
308 gitpatches.append(gp)
309 dst = m.group(2)
309 dst = m.group(2)
310 gp = patchmeta(dst)
310 gp = patchmeta(dst)
311 elif gp:
311 elif gp:
312 if line.startswith('--- '):
312 if line.startswith('--- '):
313 gitpatches.append(gp)
313 gitpatches.append(gp)
314 gp = None
314 gp = None
315 continue
315 continue
316 if line.startswith('rename from '):
316 if line.startswith('rename from '):
317 gp.op = 'RENAME'
317 gp.op = 'RENAME'
318 gp.oldpath = line[12:]
318 gp.oldpath = line[12:]
319 elif line.startswith('rename to '):
319 elif line.startswith('rename to '):
320 gp.path = line[10:]
320 gp.path = line[10:]
321 elif line.startswith('copy from '):
321 elif line.startswith('copy from '):
322 gp.op = 'COPY'
322 gp.op = 'COPY'
323 gp.oldpath = line[10:]
323 gp.oldpath = line[10:]
324 elif line.startswith('copy to '):
324 elif line.startswith('copy to '):
325 gp.path = line[8:]
325 gp.path = line[8:]
326 elif line.startswith('deleted file'):
326 elif line.startswith('deleted file'):
327 gp.op = 'DELETE'
327 gp.op = 'DELETE'
328 elif line.startswith('new file mode '):
328 elif line.startswith('new file mode '):
329 gp.op = 'ADD'
329 gp.op = 'ADD'
330 gp.setmode(int(line[-6:], 8))
330 gp.setmode(int(line[-6:], 8))
331 elif line.startswith('new mode '):
331 elif line.startswith('new mode '):
332 gp.setmode(int(line[-6:], 8))
332 gp.setmode(int(line[-6:], 8))
333 elif line.startswith('GIT binary patch'):
333 elif line.startswith('GIT binary patch'):
334 gp.binary = True
334 gp.binary = True
335 if gp:
335 if gp:
336 gitpatches.append(gp)
336 gitpatches.append(gp)
337
337
338 return gitpatches
338 return gitpatches
339
339
340 class linereader(object):
340 class linereader(object):
341 # simple class to allow pushing lines back into the input stream
341 # simple class to allow pushing lines back into the input stream
342 def __init__(self, fp):
342 def __init__(self, fp):
343 self.fp = fp
343 self.fp = fp
344 self.buf = []
344 self.buf = []
345
345
346 def push(self, line):
346 def push(self, line):
347 if line is not None:
347 if line is not None:
348 self.buf.append(line)
348 self.buf.append(line)
349
349
350 def readline(self):
350 def readline(self):
351 if self.buf:
351 if self.buf:
352 l = self.buf[0]
352 l = self.buf[0]
353 del self.buf[0]
353 del self.buf[0]
354 return l
354 return l
355 return self.fp.readline()
355 return self.fp.readline()
356
356
357 def __iter__(self):
357 def __iter__(self):
358 while True:
358 while True:
359 l = self.readline()
359 l = self.readline()
360 if not l:
360 if not l:
361 break
361 break
362 yield l
362 yield l
363
363
364 class abstractbackend(object):
364 class abstractbackend(object):
365 def __init__(self, ui):
365 def __init__(self, ui):
366 self.ui = ui
366 self.ui = ui
367
367
368 def getfile(self, fname):
368 def getfile(self, fname):
369 """Return target file data and flags as a (data, (islink,
369 """Return target file data and flags as a (data, (islink,
370 isexec)) tuple.
370 isexec)) tuple.
371 """
371 """
372 raise NotImplementedError
372 raise NotImplementedError
373
373
374 def setfile(self, fname, data, mode, copysource):
374 def setfile(self, fname, data, mode, copysource):
375 """Write data to target file fname and set its mode. mode is a
375 """Write data to target file fname and set its mode. mode is a
376 (islink, isexec) tuple. If data is None, the file content should
376 (islink, isexec) tuple. If data is None, the file content should
377 be left unchanged. If the file is modified after being copied,
377 be left unchanged. If the file is modified after being copied,
378 copysource is set to the original file name.
378 copysource is set to the original file name.
379 """
379 """
380 raise NotImplementedError
380 raise NotImplementedError
381
381
382 def unlink(self, fname):
382 def unlink(self, fname):
383 """Unlink target file."""
383 """Unlink target file."""
384 raise NotImplementedError
384 raise NotImplementedError
385
385
386 def writerej(self, fname, failed, total, lines):
386 def writerej(self, fname, failed, total, lines):
387 """Write rejected lines for fname. total is the number of hunks
387 """Write rejected lines for fname. total is the number of hunks
388 which failed to apply and total the total number of hunks for this
388 which failed to apply and total the total number of hunks for this
389 files.
389 files.
390 """
390 """
391 pass
391 pass
392
392
393 def exists(self, fname):
393 def exists(self, fname):
394 raise NotImplementedError
394 raise NotImplementedError
395
395
396 class fsbackend(abstractbackend):
396 class fsbackend(abstractbackend):
397 def __init__(self, ui, basedir):
397 def __init__(self, ui, basedir):
398 super(fsbackend, self).__init__(ui)
398 super(fsbackend, self).__init__(ui)
399 self.opener = scmutil.opener(basedir)
399 self.opener = scmutil.opener(basedir)
400
400
401 def _join(self, f):
401 def _join(self, f):
402 return os.path.join(self.opener.base, f)
402 return os.path.join(self.opener.base, f)
403
403
404 def getfile(self, fname):
404 def getfile(self, fname):
405 path = self._join(fname)
405 path = self._join(fname)
406 if os.path.islink(path):
406 if os.path.islink(path):
407 return (os.readlink(path), (True, False))
407 return (os.readlink(path), (True, False))
408 isexec = False
408 isexec = False
409 try:
409 try:
410 isexec = os.lstat(path).st_mode & 0100 != 0
410 isexec = os.lstat(path).st_mode & 0100 != 0
411 except OSError, e:
411 except OSError, e:
412 if e.errno != errno.ENOENT:
412 if e.errno != errno.ENOENT:
413 raise
413 raise
414 return (self.opener.read(fname), (False, isexec))
414 return (self.opener.read(fname), (False, isexec))
415
415
416 def setfile(self, fname, data, mode, copysource):
416 def setfile(self, fname, data, mode, copysource):
417 islink, isexec = mode
417 islink, isexec = mode
418 if data is None:
418 if data is None:
419 util.setflags(self._join(fname), islink, isexec)
419 util.setflags(self._join(fname), islink, isexec)
420 return
420 return
421 if islink:
421 if islink:
422 self.opener.symlink(data, fname)
422 self.opener.symlink(data, fname)
423 else:
423 else:
424 self.opener.write(fname, data)
424 self.opener.write(fname, data)
425 if isexec:
425 if isexec:
426 util.setflags(self._join(fname), False, True)
426 util.setflags(self._join(fname), False, True)
427
427
428 def unlink(self, fname):
428 def unlink(self, fname):
429 try:
429 try:
430 util.unlinkpath(self._join(fname))
430 util.unlinkpath(self._join(fname))
431 except OSError, inst:
431 except OSError, inst:
432 if inst.errno != errno.ENOENT:
432 if inst.errno != errno.ENOENT:
433 raise
433 raise
434
434
435 def writerej(self, fname, failed, total, lines):
435 def writerej(self, fname, failed, total, lines):
436 fname = fname + ".rej"
436 fname = fname + ".rej"
437 self.ui.warn(
437 self.ui.warn(
438 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
438 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
439 (failed, total, fname))
439 (failed, total, fname))
440 fp = self.opener(fname, 'w')
440 fp = self.opener(fname, 'w')
441 fp.writelines(lines)
441 fp.writelines(lines)
442 fp.close()
442 fp.close()
443
443
444 def exists(self, fname):
444 def exists(self, fname):
445 return os.path.lexists(self._join(fname))
445 return os.path.lexists(self._join(fname))
446
446
447 class workingbackend(fsbackend):
447 class workingbackend(fsbackend):
448 def __init__(self, ui, repo, similarity):
448 def __init__(self, ui, repo, similarity):
449 super(workingbackend, self).__init__(ui, repo.root)
449 super(workingbackend, self).__init__(ui, repo.root)
450 self.repo = repo
450 self.repo = repo
451 self.similarity = similarity
451 self.similarity = similarity
452 self.removed = set()
452 self.removed = set()
453 self.changed = set()
453 self.changed = set()
454 self.copied = []
454 self.copied = []
455
455
456 def _checkknown(self, fname):
456 def _checkknown(self, fname):
457 if self.repo.dirstate[fname] == '?' and self.exists(fname):
457 if self.repo.dirstate[fname] == '?' and self.exists(fname):
458 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
458 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
459
459
460 def setfile(self, fname, data, mode, copysource):
460 def setfile(self, fname, data, mode, copysource):
461 self._checkknown(fname)
461 self._checkknown(fname)
462 super(workingbackend, self).setfile(fname, data, mode, copysource)
462 super(workingbackend, self).setfile(fname, data, mode, copysource)
463 if copysource is not None:
463 if copysource is not None:
464 self.copied.append((copysource, fname))
464 self.copied.append((copysource, fname))
465 self.changed.add(fname)
465 self.changed.add(fname)
466
466
467 def unlink(self, fname):
467 def unlink(self, fname):
468 self._checkknown(fname)
468 self._checkknown(fname)
469 super(workingbackend, self).unlink(fname)
469 super(workingbackend, self).unlink(fname)
470 self.removed.add(fname)
470 self.removed.add(fname)
471 self.changed.add(fname)
471 self.changed.add(fname)
472
472
473 def close(self):
473 def close(self):
474 wctx = self.repo[None]
474 wctx = self.repo[None]
475 addremoved = set(self.changed)
475 addremoved = set(self.changed)
476 for src, dst in self.copied:
476 for src, dst in self.copied:
477 scmutil.dirstatecopy(self.ui, self.repo, wctx, src, dst)
477 scmutil.dirstatecopy(self.ui, self.repo, wctx, src, dst)
478 addremoved.discard(src)
478 addremoved.discard(src)
479 if (not self.similarity) and self.removed:
479 if (not self.similarity) and self.removed:
480 wctx.forget(sorted(self.removed))
480 wctx.forget(sorted(self.removed))
481 if addremoved:
481 if addremoved:
482 cwd = self.repo.getcwd()
482 cwd = self.repo.getcwd()
483 if cwd:
483 if cwd:
484 addremoved = [util.pathto(self.repo.root, cwd, f)
484 addremoved = [util.pathto(self.repo.root, cwd, f)
485 for f in addremoved]
485 for f in addremoved]
486 scmutil.addremove(self.repo, addremoved, similarity=self.similarity)
486 scmutil.addremove(self.repo, addremoved, similarity=self.similarity)
487 return sorted(self.changed)
487 return sorted(self.changed)
488
488
489 class filestore(object):
489 class filestore(object):
490 def __init__(self, maxsize=None):
490 def __init__(self, maxsize=None):
491 self.opener = None
491 self.opener = None
492 self.files = {}
492 self.files = {}
493 self.created = 0
493 self.created = 0
494 self.maxsize = maxsize
494 self.maxsize = maxsize
495 if self.maxsize is None:
495 if self.maxsize is None:
496 self.maxsize = 4*(2**20)
496 self.maxsize = 4*(2**20)
497 self.size = 0
497 self.size = 0
498 self.data = {}
498 self.data = {}
499
499
500 def setfile(self, fname, data, mode, copied=None):
500 def setfile(self, fname, data, mode, copied=None):
501 if self.maxsize < 0 or (len(data) + self.size) <= self.maxsize:
501 if self.maxsize < 0 or (len(data) + self.size) <= self.maxsize:
502 self.data[fname] = (data, mode, copied)
502 self.data[fname] = (data, mode, copied)
503 self.size += len(data)
503 self.size += len(data)
504 else:
504 else:
505 if self.opener is None:
505 if self.opener is None:
506 root = tempfile.mkdtemp(prefix='hg-patch-')
506 root = tempfile.mkdtemp(prefix='hg-patch-')
507 self.opener = scmutil.opener(root)
507 self.opener = scmutil.opener(root)
508 # Avoid filename issues with these simple names
508 # Avoid filename issues with these simple names
509 fn = str(self.created)
509 fn = str(self.created)
510 self.opener.write(fn, data)
510 self.opener.write(fn, data)
511 self.created += 1
511 self.created += 1
512 self.files[fname] = (fn, mode, copied)
512 self.files[fname] = (fn, mode, copied)
513
513
514 def getfile(self, fname):
514 def getfile(self, fname):
515 if fname in self.data:
515 if fname in self.data:
516 return self.data[fname]
516 return self.data[fname]
517 if not self.opener or fname not in self.files:
517 if not self.opener or fname not in self.files:
518 raise IOError()
518 raise IOError()
519 fn, mode, copied = self.files[fname]
519 fn, mode, copied = self.files[fname]
520 return self.opener.read(fn), mode, copied
520 return self.opener.read(fn), mode, copied
521
521
522 def close(self):
522 def close(self):
523 if self.opener:
523 if self.opener:
524 shutil.rmtree(self.opener.base)
524 shutil.rmtree(self.opener.base)
525
525
526 class repobackend(abstractbackend):
526 class repobackend(abstractbackend):
527 def __init__(self, ui, repo, ctx, store):
527 def __init__(self, ui, repo, ctx, store):
528 super(repobackend, self).__init__(ui)
528 super(repobackend, self).__init__(ui)
529 self.repo = repo
529 self.repo = repo
530 self.ctx = ctx
530 self.ctx = ctx
531 self.store = store
531 self.store = store
532 self.changed = set()
532 self.changed = set()
533 self.removed = set()
533 self.removed = set()
534 self.copied = {}
534 self.copied = {}
535
535
536 def _checkknown(self, fname):
536 def _checkknown(self, fname):
537 if fname not in self.ctx:
537 if fname not in self.ctx:
538 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
538 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
539
539
540 def getfile(self, fname):
540 def getfile(self, fname):
541 try:
541 try:
542 fctx = self.ctx[fname]
542 fctx = self.ctx[fname]
543 except error.LookupError:
543 except error.LookupError:
544 raise IOError()
544 raise IOError()
545 flags = fctx.flags()
545 flags = fctx.flags()
546 return fctx.data(), ('l' in flags, 'x' in flags)
546 return fctx.data(), ('l' in flags, 'x' in flags)
547
547
548 def setfile(self, fname, data, mode, copysource):
548 def setfile(self, fname, data, mode, copysource):
549 if copysource:
549 if copysource:
550 self._checkknown(copysource)
550 self._checkknown(copysource)
551 if data is None:
551 if data is None:
552 data = self.ctx[fname].data()
552 data = self.ctx[fname].data()
553 self.store.setfile(fname, data, mode, copysource)
553 self.store.setfile(fname, data, mode, copysource)
554 self.changed.add(fname)
554 self.changed.add(fname)
555 if copysource:
555 if copysource:
556 self.copied[fname] = copysource
556 self.copied[fname] = copysource
557
557
558 def unlink(self, fname):
558 def unlink(self, fname):
559 self._checkknown(fname)
559 self._checkknown(fname)
560 self.removed.add(fname)
560 self.removed.add(fname)
561
561
562 def exists(self, fname):
562 def exists(self, fname):
563 return fname in self.ctx
563 return fname in self.ctx
564
564
565 def close(self):
565 def close(self):
566 return self.changed | self.removed
566 return self.changed | self.removed
567
567
568 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
568 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
569 unidesc = re.compile('@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@')
569 unidesc = re.compile('@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@')
570 contextdesc = re.compile('(?:---|\*\*\*) (\d+)(?:,(\d+))? (?:---|\*\*\*)')
570 contextdesc = re.compile('(?:---|\*\*\*) (\d+)(?:,(\d+))? (?:---|\*\*\*)')
571 eolmodes = ['strict', 'crlf', 'lf', 'auto']
571 eolmodes = ['strict', 'crlf', 'lf', 'auto']
572
572
573 class patchfile(object):
573 class patchfile(object):
574 def __init__(self, ui, gp, backend, store, eolmode='strict'):
574 def __init__(self, ui, gp, backend, store, eolmode='strict'):
575 self.fname = gp.path
575 self.fname = gp.path
576 self.eolmode = eolmode
576 self.eolmode = eolmode
577 self.eol = None
577 self.eol = None
578 self.backend = backend
578 self.backend = backend
579 self.ui = ui
579 self.ui = ui
580 self.lines = []
580 self.lines = []
581 self.exists = False
581 self.exists = False
582 self.missing = True
582 self.missing = True
583 self.mode = gp.mode
583 self.mode = gp.mode
584 self.copysource = gp.oldpath
584 self.copysource = gp.oldpath
585 self.create = gp.op in ('ADD', 'COPY', 'RENAME')
585 self.create = gp.op in ('ADD', 'COPY', 'RENAME')
586 self.remove = gp.op == 'DELETE'
586 self.remove = gp.op == 'DELETE'
587 try:
587 try:
588 if self.copysource is None:
588 if self.copysource is None:
589 data, mode = backend.getfile(self.fname)
589 data, mode = backend.getfile(self.fname)
590 self.exists = True
590 self.exists = True
591 else:
591 else:
592 data, mode = store.getfile(self.copysource)[:2]
592 data, mode = store.getfile(self.copysource)[:2]
593 self.exists = backend.exists(self.fname)
593 self.exists = backend.exists(self.fname)
594 self.missing = False
594 self.missing = False
595 if data:
595 if data:
596 self.lines = mdiff.splitnewlines(data)
596 self.lines = mdiff.splitnewlines(data)
597 if self.mode is None:
597 if self.mode is None:
598 self.mode = mode
598 self.mode = mode
599 if self.lines:
599 if self.lines:
600 # Normalize line endings
600 # Normalize line endings
601 if self.lines[0].endswith('\r\n'):
601 if self.lines[0].endswith('\r\n'):
602 self.eol = '\r\n'
602 self.eol = '\r\n'
603 elif self.lines[0].endswith('\n'):
603 elif self.lines[0].endswith('\n'):
604 self.eol = '\n'
604 self.eol = '\n'
605 if eolmode != 'strict':
605 if eolmode != 'strict':
606 nlines = []
606 nlines = []
607 for l in self.lines:
607 for l in self.lines:
608 if l.endswith('\r\n'):
608 if l.endswith('\r\n'):
609 l = l[:-2] + '\n'
609 l = l[:-2] + '\n'
610 nlines.append(l)
610 nlines.append(l)
611 self.lines = nlines
611 self.lines = nlines
612 except IOError:
612 except IOError:
613 if self.create:
613 if self.create:
614 self.missing = False
614 self.missing = False
615 if self.mode is None:
615 if self.mode is None:
616 self.mode = (False, False)
616 self.mode = (False, False)
617 if self.missing:
617 if self.missing:
618 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
618 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
619
619
620 self.hash = {}
620 self.hash = {}
621 self.dirty = 0
621 self.dirty = 0
622 self.offset = 0
622 self.offset = 0
623 self.skew = 0
623 self.skew = 0
624 self.rej = []
624 self.rej = []
625 self.fileprinted = False
625 self.fileprinted = False
626 self.printfile(False)
626 self.printfile(False)
627 self.hunks = 0
627 self.hunks = 0
628
628
629 def writelines(self, fname, lines, mode):
629 def writelines(self, fname, lines, mode):
630 if self.eolmode == 'auto':
630 if self.eolmode == 'auto':
631 eol = self.eol
631 eol = self.eol
632 elif self.eolmode == 'crlf':
632 elif self.eolmode == 'crlf':
633 eol = '\r\n'
633 eol = '\r\n'
634 else:
634 else:
635 eol = '\n'
635 eol = '\n'
636
636
637 if self.eolmode != 'strict' and eol and eol != '\n':
637 if self.eolmode != 'strict' and eol and eol != '\n':
638 rawlines = []
638 rawlines = []
639 for l in lines:
639 for l in lines:
640 if l and l[-1] == '\n':
640 if l and l[-1] == '\n':
641 l = l[:-1] + eol
641 l = l[:-1] + eol
642 rawlines.append(l)
642 rawlines.append(l)
643 lines = rawlines
643 lines = rawlines
644
644
645 self.backend.setfile(fname, ''.join(lines), mode, self.copysource)
645 self.backend.setfile(fname, ''.join(lines), mode, self.copysource)
646
646
647 def printfile(self, warn):
647 def printfile(self, warn):
648 if self.fileprinted:
648 if self.fileprinted:
649 return
649 return
650 if warn or self.ui.verbose:
650 if warn or self.ui.verbose:
651 self.fileprinted = True
651 self.fileprinted = True
652 s = _("patching file %s\n") % self.fname
652 s = _("patching file %s\n") % self.fname
653 if warn:
653 if warn:
654 self.ui.warn(s)
654 self.ui.warn(s)
655 else:
655 else:
656 self.ui.note(s)
656 self.ui.note(s)
657
657
658
658
659 def findlines(self, l, linenum):
659 def findlines(self, l, linenum):
660 # looks through the hash and finds candidate lines. The
660 # looks through the hash and finds candidate lines. The
661 # result is a list of line numbers sorted based on distance
661 # result is a list of line numbers sorted based on distance
662 # from linenum
662 # from linenum
663
663
664 cand = self.hash.get(l, [])
664 cand = self.hash.get(l, [])
665 if len(cand) > 1:
665 if len(cand) > 1:
666 # resort our list of potentials forward then back.
666 # resort our list of potentials forward then back.
667 cand.sort(key=lambda x: abs(x - linenum))
667 cand.sort(key=lambda x: abs(x - linenum))
668 return cand
668 return cand
669
669
670 def write_rej(self):
670 def write_rej(self):
671 # our rejects are a little different from patch(1). This always
671 # our rejects are a little different from patch(1). This always
672 # creates rejects in the same form as the original patch. A file
672 # creates rejects in the same form as the original patch. A file
673 # header is inserted so that you can run the reject through patch again
673 # header is inserted so that you can run the reject through patch again
674 # without having to type the filename.
674 # without having to type the filename.
675 if not self.rej:
675 if not self.rej:
676 return
676 return
677 base = os.path.basename(self.fname)
677 base = os.path.basename(self.fname)
678 lines = ["--- %s\n+++ %s\n" % (base, base)]
678 lines = ["--- %s\n+++ %s\n" % (base, base)]
679 for x in self.rej:
679 for x in self.rej:
680 for l in x.hunk:
680 for l in x.hunk:
681 lines.append(l)
681 lines.append(l)
682 if l[-1] != '\n':
682 if l[-1] != '\n':
683 lines.append("\n\ No newline at end of file\n")
683 lines.append("\n\ No newline at end of file\n")
684 self.backend.writerej(self.fname, len(self.rej), self.hunks, lines)
684 self.backend.writerej(self.fname, len(self.rej), self.hunks, lines)
685
685
686 def apply(self, h):
686 def apply(self, h):
687 if not h.complete():
687 if not h.complete():
688 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
688 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
689 (h.number, h.desc, len(h.a), h.lena, len(h.b),
689 (h.number, h.desc, len(h.a), h.lena, len(h.b),
690 h.lenb))
690 h.lenb))
691
691
692 self.hunks += 1
692 self.hunks += 1
693
693
694 if self.missing:
694 if self.missing:
695 self.rej.append(h)
695 self.rej.append(h)
696 return -1
696 return -1
697
697
698 if self.exists and self.create:
698 if self.exists and self.create:
699 if self.copysource:
699 if self.copysource:
700 self.ui.warn(_("cannot create %s: destination already "
700 self.ui.warn(_("cannot create %s: destination already "
701 "exists\n" % self.fname))
701 "exists\n" % self.fname))
702 else:
702 else:
703 self.ui.warn(_("file %s already exists\n") % self.fname)
703 self.ui.warn(_("file %s already exists\n") % self.fname)
704 self.rej.append(h)
704 self.rej.append(h)
705 return -1
705 return -1
706
706
707 if isinstance(h, binhunk):
707 if isinstance(h, binhunk):
708 if self.remove:
708 if self.remove:
709 self.backend.unlink(self.fname)
709 self.backend.unlink(self.fname)
710 else:
710 else:
711 self.lines[:] = h.new()
711 self.lines[:] = h.new()
712 self.offset += len(h.new())
712 self.offset += len(h.new())
713 self.dirty = True
713 self.dirty = True
714 return 0
714 return 0
715
715
716 horig = h
716 horig = h
717 if (self.eolmode in ('crlf', 'lf')
717 if (self.eolmode in ('crlf', 'lf')
718 or self.eolmode == 'auto' and self.eol):
718 or self.eolmode == 'auto' and self.eol):
719 # If new eols are going to be normalized, then normalize
719 # If new eols are going to be normalized, then normalize
720 # hunk data before patching. Otherwise, preserve input
720 # hunk data before patching. Otherwise, preserve input
721 # line-endings.
721 # line-endings.
722 h = h.getnormalized()
722 h = h.getnormalized()
723
723
724 # fast case first, no offsets, no fuzz
724 # fast case first, no offsets, no fuzz
725 old = h.old()
725 old = h.old()
726 start = h.starta + self.offset
726 start = h.starta + self.offset
727 # zero length hunk ranges already have their start decremented
727 # zero length hunk ranges already have their start decremented
728 if h.lena:
728 if h.lena:
729 start -= 1
729 start -= 1
730 orig_start = start
730 orig_start = start
731 # if there's skew we want to emit the "(offset %d lines)" even
731 # if there's skew we want to emit the "(offset %d lines)" even
732 # when the hunk cleanly applies at start + skew, so skip the
732 # when the hunk cleanly applies at start + skew, so skip the
733 # fast case code
733 # fast case code
734 if self.skew == 0 and diffhelpers.testhunk(old, self.lines, start) == 0:
734 if self.skew == 0 and diffhelpers.testhunk(old, self.lines, start) == 0:
735 if self.remove:
735 if self.remove:
736 self.backend.unlink(self.fname)
736 self.backend.unlink(self.fname)
737 else:
737 else:
738 self.lines[start : start + h.lena] = h.new()
738 self.lines[start : start + h.lena] = h.new()
739 self.offset += h.lenb - h.lena
739 self.offset += h.lenb - h.lena
740 self.dirty = True
740 self.dirty = True
741 return 0
741 return 0
742
742
743 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
743 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
744 self.hash = {}
744 self.hash = {}
745 for x, s in enumerate(self.lines):
745 for x, s in enumerate(self.lines):
746 self.hash.setdefault(s, []).append(x)
746 self.hash.setdefault(s, []).append(x)
747 if h.hunk[-1][0] != ' ':
747 if h.hunk[-1][0] != ' ':
748 # if the hunk tried to put something at the bottom of the file
748 # if the hunk tried to put something at the bottom of the file
749 # override the start line and use eof here
749 # override the start line and use eof here
750 search_start = len(self.lines)
750 search_start = len(self.lines)
751 else:
751 else:
752 search_start = orig_start + self.skew
752 search_start = orig_start + self.skew
753
753
754 for fuzzlen in xrange(3):
754 for fuzzlen in xrange(3):
755 for toponly in [True, False]:
755 for toponly in [True, False]:
756 old = h.old(fuzzlen, toponly)
756 old = h.old(fuzzlen, toponly)
757
757
758 cand = self.findlines(old[0][1:], search_start)
758 cand = self.findlines(old[0][1:], search_start)
759 for l in cand:
759 for l in cand:
760 if diffhelpers.testhunk(old, self.lines, l) == 0:
760 if diffhelpers.testhunk(old, self.lines, l) == 0:
761 newlines = h.new(fuzzlen, toponly)
761 newlines = h.new(fuzzlen, toponly)
762 self.lines[l : l + len(old)] = newlines
762 self.lines[l : l + len(old)] = newlines
763 self.offset += len(newlines) - len(old)
763 self.offset += len(newlines) - len(old)
764 self.skew = l - orig_start
764 self.skew = l - orig_start
765 self.dirty = True
765 self.dirty = True
766 offset = l - orig_start - fuzzlen
766 offset = l - orig_start - fuzzlen
767 if fuzzlen:
767 if fuzzlen:
768 msg = _("Hunk #%d succeeded at %d "
768 msg = _("Hunk #%d succeeded at %d "
769 "with fuzz %d "
769 "with fuzz %d "
770 "(offset %d lines).\n")
770 "(offset %d lines).\n")
771 self.printfile(True)
771 self.printfile(True)
772 self.ui.warn(msg %
772 self.ui.warn(msg %
773 (h.number, l + 1, fuzzlen, offset))
773 (h.number, l + 1, fuzzlen, offset))
774 else:
774 else:
775 msg = _("Hunk #%d succeeded at %d "
775 msg = _("Hunk #%d succeeded at %d "
776 "(offset %d lines).\n")
776 "(offset %d lines).\n")
777 self.ui.note(msg % (h.number, l + 1, offset))
777 self.ui.note(msg % (h.number, l + 1, offset))
778 return fuzzlen
778 return fuzzlen
779 self.printfile(True)
779 self.printfile(True)
780 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
780 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
781 self.rej.append(horig)
781 self.rej.append(horig)
782 return -1
782 return -1
783
783
784 def close(self):
784 def close(self):
785 if self.dirty:
785 if self.dirty:
786 self.writelines(self.fname, self.lines, self.mode)
786 self.writelines(self.fname, self.lines, self.mode)
787 self.write_rej()
787 self.write_rej()
788 return len(self.rej)
788 return len(self.rej)
789
789
790 class hunk(object):
790 class hunk(object):
791 def __init__(self, desc, num, lr, context):
791 def __init__(self, desc, num, lr, context):
792 self.number = num
792 self.number = num
793 self.desc = desc
793 self.desc = desc
794 self.hunk = [desc]
794 self.hunk = [desc]
795 self.a = []
795 self.a = []
796 self.b = []
796 self.b = []
797 self.starta = self.lena = None
797 self.starta = self.lena = None
798 self.startb = self.lenb = None
798 self.startb = self.lenb = None
799 if lr is not None:
799 if lr is not None:
800 if context:
800 if context:
801 self.read_context_hunk(lr)
801 self.read_context_hunk(lr)
802 else:
802 else:
803 self.read_unified_hunk(lr)
803 self.read_unified_hunk(lr)
804
804
805 def getnormalized(self):
805 def getnormalized(self):
806 """Return a copy with line endings normalized to LF."""
806 """Return a copy with line endings normalized to LF."""
807
807
808 def normalize(lines):
808 def normalize(lines):
809 nlines = []
809 nlines = []
810 for line in lines:
810 for line in lines:
811 if line.endswith('\r\n'):
811 if line.endswith('\r\n'):
812 line = line[:-2] + '\n'
812 line = line[:-2] + '\n'
813 nlines.append(line)
813 nlines.append(line)
814 return nlines
814 return nlines
815
815
816 # Dummy object, it is rebuilt manually
816 # Dummy object, it is rebuilt manually
817 nh = hunk(self.desc, self.number, None, None)
817 nh = hunk(self.desc, self.number, None, None)
818 nh.number = self.number
818 nh.number = self.number
819 nh.desc = self.desc
819 nh.desc = self.desc
820 nh.hunk = self.hunk
820 nh.hunk = self.hunk
821 nh.a = normalize(self.a)
821 nh.a = normalize(self.a)
822 nh.b = normalize(self.b)
822 nh.b = normalize(self.b)
823 nh.starta = self.starta
823 nh.starta = self.starta
824 nh.startb = self.startb
824 nh.startb = self.startb
825 nh.lena = self.lena
825 nh.lena = self.lena
826 nh.lenb = self.lenb
826 nh.lenb = self.lenb
827 return nh
827 return nh
828
828
829 def read_unified_hunk(self, lr):
829 def read_unified_hunk(self, lr):
830 m = unidesc.match(self.desc)
830 m = unidesc.match(self.desc)
831 if not m:
831 if not m:
832 raise PatchError(_("bad hunk #%d") % self.number)
832 raise PatchError(_("bad hunk #%d") % self.number)
833 self.starta, self.lena, self.startb, self.lenb = m.groups()
833 self.starta, self.lena, self.startb, self.lenb = m.groups()
834 if self.lena is None:
834 if self.lena is None:
835 self.lena = 1
835 self.lena = 1
836 else:
836 else:
837 self.lena = int(self.lena)
837 self.lena = int(self.lena)
838 if self.lenb is None:
838 if self.lenb is None:
839 self.lenb = 1
839 self.lenb = 1
840 else:
840 else:
841 self.lenb = int(self.lenb)
841 self.lenb = int(self.lenb)
842 self.starta = int(self.starta)
842 self.starta = int(self.starta)
843 self.startb = int(self.startb)
843 self.startb = int(self.startb)
844 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a, self.b)
844 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a, self.b)
845 # if we hit eof before finishing out the hunk, the last line will
845 # if we hit eof before finishing out the hunk, the last line will
846 # be zero length. Lets try to fix it up.
846 # be zero length. Lets try to fix it up.
847 while len(self.hunk[-1]) == 0:
847 while len(self.hunk[-1]) == 0:
848 del self.hunk[-1]
848 del self.hunk[-1]
849 del self.a[-1]
849 del self.a[-1]
850 del self.b[-1]
850 del self.b[-1]
851 self.lena -= 1
851 self.lena -= 1
852 self.lenb -= 1
852 self.lenb -= 1
853 self._fixnewline(lr)
853 self._fixnewline(lr)
854
854
855 def read_context_hunk(self, lr):
855 def read_context_hunk(self, lr):
856 self.desc = lr.readline()
856 self.desc = lr.readline()
857 m = contextdesc.match(self.desc)
857 m = contextdesc.match(self.desc)
858 if not m:
858 if not m:
859 raise PatchError(_("bad hunk #%d") % self.number)
859 raise PatchError(_("bad hunk #%d") % self.number)
860 self.starta, aend = m.groups()
860 self.starta, aend = m.groups()
861 self.starta = int(self.starta)
861 self.starta = int(self.starta)
862 if aend is None:
862 if aend is None:
863 aend = self.starta
863 aend = self.starta
864 self.lena = int(aend) - self.starta
864 self.lena = int(aend) - self.starta
865 if self.starta:
865 if self.starta:
866 self.lena += 1
866 self.lena += 1
867 for x in xrange(self.lena):
867 for x in xrange(self.lena):
868 l = lr.readline()
868 l = lr.readline()
869 if l.startswith('---'):
869 if l.startswith('---'):
870 # lines addition, old block is empty
870 # lines addition, old block is empty
871 lr.push(l)
871 lr.push(l)
872 break
872 break
873 s = l[2:]
873 s = l[2:]
874 if l.startswith('- ') or l.startswith('! '):
874 if l.startswith('- ') or l.startswith('! '):
875 u = '-' + s
875 u = '-' + s
876 elif l.startswith(' '):
876 elif l.startswith(' '):
877 u = ' ' + s
877 u = ' ' + s
878 else:
878 else:
879 raise PatchError(_("bad hunk #%d old text line %d") %
879 raise PatchError(_("bad hunk #%d old text line %d") %
880 (self.number, x))
880 (self.number, x))
881 self.a.append(u)
881 self.a.append(u)
882 self.hunk.append(u)
882 self.hunk.append(u)
883
883
884 l = lr.readline()
884 l = lr.readline()
885 if l.startswith('\ '):
885 if l.startswith('\ '):
886 s = self.a[-1][:-1]
886 s = self.a[-1][:-1]
887 self.a[-1] = s
887 self.a[-1] = s
888 self.hunk[-1] = s
888 self.hunk[-1] = s
889 l = lr.readline()
889 l = lr.readline()
890 m = contextdesc.match(l)
890 m = contextdesc.match(l)
891 if not m:
891 if not m:
892 raise PatchError(_("bad hunk #%d") % self.number)
892 raise PatchError(_("bad hunk #%d") % self.number)
893 self.startb, bend = m.groups()
893 self.startb, bend = m.groups()
894 self.startb = int(self.startb)
894 self.startb = int(self.startb)
895 if bend is None:
895 if bend is None:
896 bend = self.startb
896 bend = self.startb
897 self.lenb = int(bend) - self.startb
897 self.lenb = int(bend) - self.startb
898 if self.startb:
898 if self.startb:
899 self.lenb += 1
899 self.lenb += 1
900 hunki = 1
900 hunki = 1
901 for x in xrange(self.lenb):
901 for x in xrange(self.lenb):
902 l = lr.readline()
902 l = lr.readline()
903 if l.startswith('\ '):
903 if l.startswith('\ '):
904 # XXX: the only way to hit this is with an invalid line range.
904 # XXX: the only way to hit this is with an invalid line range.
905 # The no-eol marker is not counted in the line range, but I
905 # The no-eol marker is not counted in the line range, but I
906 # guess there are diff(1) out there which behave differently.
906 # guess there are diff(1) out there which behave differently.
907 s = self.b[-1][:-1]
907 s = self.b[-1][:-1]
908 self.b[-1] = s
908 self.b[-1] = s
909 self.hunk[hunki - 1] = s
909 self.hunk[hunki - 1] = s
910 continue
910 continue
911 if not l:
911 if not l:
912 # line deletions, new block is empty and we hit EOF
912 # line deletions, new block is empty and we hit EOF
913 lr.push(l)
913 lr.push(l)
914 break
914 break
915 s = l[2:]
915 s = l[2:]
916 if l.startswith('+ ') or l.startswith('! '):
916 if l.startswith('+ ') or l.startswith('! '):
917 u = '+' + s
917 u = '+' + s
918 elif l.startswith(' '):
918 elif l.startswith(' '):
919 u = ' ' + s
919 u = ' ' + s
920 elif len(self.b) == 0:
920 elif len(self.b) == 0:
921 # line deletions, new block is empty
921 # line deletions, new block is empty
922 lr.push(l)
922 lr.push(l)
923 break
923 break
924 else:
924 else:
925 raise PatchError(_("bad hunk #%d old text line %d") %
925 raise PatchError(_("bad hunk #%d old text line %d") %
926 (self.number, x))
926 (self.number, x))
927 self.b.append(s)
927 self.b.append(s)
928 while True:
928 while True:
929 if hunki >= len(self.hunk):
929 if hunki >= len(self.hunk):
930 h = ""
930 h = ""
931 else:
931 else:
932 h = self.hunk[hunki]
932 h = self.hunk[hunki]
933 hunki += 1
933 hunki += 1
934 if h == u:
934 if h == u:
935 break
935 break
936 elif h.startswith('-'):
936 elif h.startswith('-'):
937 continue
937 continue
938 else:
938 else:
939 self.hunk.insert(hunki - 1, u)
939 self.hunk.insert(hunki - 1, u)
940 break
940 break
941
941
942 if not self.a:
942 if not self.a:
943 # this happens when lines were only added to the hunk
943 # this happens when lines were only added to the hunk
944 for x in self.hunk:
944 for x in self.hunk:
945 if x.startswith('-') or x.startswith(' '):
945 if x.startswith('-') or x.startswith(' '):
946 self.a.append(x)
946 self.a.append(x)
947 if not self.b:
947 if not self.b:
948 # this happens when lines were only deleted from the hunk
948 # this happens when lines were only deleted from the hunk
949 for x in self.hunk:
949 for x in self.hunk:
950 if x.startswith('+') or x.startswith(' '):
950 if x.startswith('+') or x.startswith(' '):
951 self.b.append(x[1:])
951 self.b.append(x[1:])
952 # @@ -start,len +start,len @@
952 # @@ -start,len +start,len @@
953 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
953 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
954 self.startb, self.lenb)
954 self.startb, self.lenb)
955 self.hunk[0] = self.desc
955 self.hunk[0] = self.desc
956 self._fixnewline(lr)
956 self._fixnewline(lr)
957
957
958 def _fixnewline(self, lr):
958 def _fixnewline(self, lr):
959 l = lr.readline()
959 l = lr.readline()
960 if l.startswith('\ '):
960 if l.startswith('\ '):
961 diffhelpers.fix_newline(self.hunk, self.a, self.b)
961 diffhelpers.fix_newline(self.hunk, self.a, self.b)
962 else:
962 else:
963 lr.push(l)
963 lr.push(l)
964
964
965 def complete(self):
965 def complete(self):
966 return len(self.a) == self.lena and len(self.b) == self.lenb
966 return len(self.a) == self.lena and len(self.b) == self.lenb
967
967
968 def fuzzit(self, l, fuzz, toponly):
968 def fuzzit(self, l, fuzz, toponly):
969 # this removes context lines from the top and bottom of list 'l'. It
969 # this removes context lines from the top and bottom of list 'l'. It
970 # checks the hunk to make sure only context lines are removed, and then
970 # checks the hunk to make sure only context lines are removed, and then
971 # returns a new shortened list of lines.
971 # returns a new shortened list of lines.
972 fuzz = min(fuzz, len(l)-1)
972 fuzz = min(fuzz, len(l)-1)
973 if fuzz:
973 if fuzz:
974 top = 0
974 top = 0
975 bot = 0
975 bot = 0
976 hlen = len(self.hunk)
976 hlen = len(self.hunk)
977 for x in xrange(hlen - 1):
977 for x in xrange(hlen - 1):
978 # the hunk starts with the @@ line, so use x+1
978 # the hunk starts with the @@ line, so use x+1
979 if self.hunk[x + 1][0] == ' ':
979 if self.hunk[x + 1][0] == ' ':
980 top += 1
980 top += 1
981 else:
981 else:
982 break
982 break
983 if not toponly:
983 if not toponly:
984 for x in xrange(hlen - 1):
984 for x in xrange(hlen - 1):
985 if self.hunk[hlen - bot - 1][0] == ' ':
985 if self.hunk[hlen - bot - 1][0] == ' ':
986 bot += 1
986 bot += 1
987 else:
987 else:
988 break
988 break
989
989
990 # top and bot now count context in the hunk
990 # top and bot now count context in the hunk
991 # adjust them if either one is short
991 # adjust them if either one is short
992 context = max(top, bot, 3)
992 context = max(top, bot, 3)
993 if bot < context:
993 if bot < context:
994 bot = max(0, fuzz - (context - bot))
994 bot = max(0, fuzz - (context - bot))
995 else:
995 else:
996 bot = min(fuzz, bot)
996 bot = min(fuzz, bot)
997 if top < context:
997 if top < context:
998 top = max(0, fuzz - (context - top))
998 top = max(0, fuzz - (context - top))
999 else:
999 else:
1000 top = min(fuzz, top)
1000 top = min(fuzz, top)
1001
1001
1002 return l[top:len(l)-bot]
1002 return l[top:len(l)-bot]
1003 return l
1003 return l
1004
1004
1005 def old(self, fuzz=0, toponly=False):
1005 def old(self, fuzz=0, toponly=False):
1006 return self.fuzzit(self.a, fuzz, toponly)
1006 return self.fuzzit(self.a, fuzz, toponly)
1007
1007
1008 def new(self, fuzz=0, toponly=False):
1008 def new(self, fuzz=0, toponly=False):
1009 return self.fuzzit(self.b, fuzz, toponly)
1009 return self.fuzzit(self.b, fuzz, toponly)
1010
1010
1011 class binhunk(object):
1011 class binhunk(object):
1012 'A binary patch file. Only understands literals so far.'
1012 'A binary patch file. Only understands literals so far.'
1013 def __init__(self, lr):
1013 def __init__(self, lr):
1014 self.text = None
1014 self.text = None
1015 self.hunk = ['GIT binary patch\n']
1015 self.hunk = ['GIT binary patch\n']
1016 self._read(lr)
1016 self._read(lr)
1017
1017
1018 def complete(self):
1018 def complete(self):
1019 return self.text is not None
1019 return self.text is not None
1020
1020
1021 def new(self):
1021 def new(self):
1022 return [self.text]
1022 return [self.text]
1023
1023
1024 def _read(self, lr):
1024 def _read(self, lr):
1025 line = lr.readline()
1025 line = lr.readline()
1026 self.hunk.append(line)
1026 self.hunk.append(line)
1027 while line and not line.startswith('literal '):
1027 while line and not line.startswith('literal '):
1028 line = lr.readline()
1028 line = lr.readline()
1029 self.hunk.append(line)
1029 self.hunk.append(line)
1030 if not line:
1030 if not line:
1031 raise PatchError(_('could not extract binary patch'))
1031 raise PatchError(_('could not extract binary patch'))
1032 size = int(line[8:].rstrip())
1032 size = int(line[8:].rstrip())
1033 dec = []
1033 dec = []
1034 line = lr.readline()
1034 line = lr.readline()
1035 self.hunk.append(line)
1035 self.hunk.append(line)
1036 while len(line) > 1:
1036 while len(line) > 1:
1037 l = line[0]
1037 l = line[0]
1038 if l <= 'Z' and l >= 'A':
1038 if l <= 'Z' and l >= 'A':
1039 l = ord(l) - ord('A') + 1
1039 l = ord(l) - ord('A') + 1
1040 else:
1040 else:
1041 l = ord(l) - ord('a') + 27
1041 l = ord(l) - ord('a') + 27
1042 dec.append(base85.b85decode(line[1:-1])[:l])
1042 dec.append(base85.b85decode(line[1:-1])[:l])
1043 line = lr.readline()
1043 line = lr.readline()
1044 self.hunk.append(line)
1044 self.hunk.append(line)
1045 text = zlib.decompress(''.join(dec))
1045 text = zlib.decompress(''.join(dec))
1046 if len(text) != size:
1046 if len(text) != size:
1047 raise PatchError(_('binary patch is %d bytes, not %d') %
1047 raise PatchError(_('binary patch is %d bytes, not %d') %
1048 len(text), size)
1048 len(text), size)
1049 self.text = text
1049 self.text = text
1050
1050
1051 def parsefilename(str):
1051 def parsefilename(str):
1052 # --- filename \t|space stuff
1052 # --- filename \t|space stuff
1053 s = str[4:].rstrip('\r\n')
1053 s = str[4:].rstrip('\r\n')
1054 i = s.find('\t')
1054 i = s.find('\t')
1055 if i < 0:
1055 if i < 0:
1056 i = s.find(' ')
1056 i = s.find(' ')
1057 if i < 0:
1057 if i < 0:
1058 return s
1058 return s
1059 return s[:i]
1059 return s[:i]
1060
1060
1061 def pathstrip(path, strip):
1061 def pathstrip(path, strip):
1062 pathlen = len(path)
1062 pathlen = len(path)
1063 i = 0
1063 i = 0
1064 if strip == 0:
1064 if strip == 0:
1065 return '', path.rstrip()
1065 return '', path.rstrip()
1066 count = strip
1066 count = strip
1067 while count > 0:
1067 while count > 0:
1068 i = path.find('/', i)
1068 i = path.find('/', i)
1069 if i == -1:
1069 if i == -1:
1070 raise PatchError(_("unable to strip away %d of %d dirs from %s") %
1070 raise PatchError(_("unable to strip away %d of %d dirs from %s") %
1071 (count, strip, path))
1071 (count, strip, path))
1072 i += 1
1072 i += 1
1073 # consume '//' in the path
1073 # consume '//' in the path
1074 while i < pathlen - 1 and path[i] == '/':
1074 while i < pathlen - 1 and path[i] == '/':
1075 i += 1
1075 i += 1
1076 count -= 1
1076 count -= 1
1077 return path[:i].lstrip(), path[i:].rstrip()
1077 return path[:i].lstrip(), path[i:].rstrip()
1078
1078
1079 def makepatchmeta(backend, afile_orig, bfile_orig, hunk, strip):
1079 def makepatchmeta(backend, afile_orig, bfile_orig, hunk, strip):
1080 nulla = afile_orig == "/dev/null"
1080 nulla = afile_orig == "/dev/null"
1081 nullb = bfile_orig == "/dev/null"
1081 nullb = bfile_orig == "/dev/null"
1082 create = nulla and hunk.starta == 0 and hunk.lena == 0
1082 create = nulla and hunk.starta == 0 and hunk.lena == 0
1083 remove = nullb and hunk.startb == 0 and hunk.lenb == 0
1083 remove = nullb and hunk.startb == 0 and hunk.lenb == 0
1084 abase, afile = pathstrip(afile_orig, strip)
1084 abase, afile = pathstrip(afile_orig, strip)
1085 gooda = not nulla and backend.exists(afile)
1085 gooda = not nulla and backend.exists(afile)
1086 bbase, bfile = pathstrip(bfile_orig, strip)
1086 bbase, bfile = pathstrip(bfile_orig, strip)
1087 if afile == bfile:
1087 if afile == bfile:
1088 goodb = gooda
1088 goodb = gooda
1089 else:
1089 else:
1090 goodb = not nullb and backend.exists(bfile)
1090 goodb = not nullb and backend.exists(bfile)
1091 missing = not goodb and not gooda and not create
1091 missing = not goodb and not gooda and not create
1092
1092
1093 # some diff programs apparently produce patches where the afile is
1093 # some diff programs apparently produce patches where the afile is
1094 # not /dev/null, but afile starts with bfile
1094 # not /dev/null, but afile starts with bfile
1095 abasedir = afile[:afile.rfind('/') + 1]
1095 abasedir = afile[:afile.rfind('/') + 1]
1096 bbasedir = bfile[:bfile.rfind('/') + 1]
1096 bbasedir = bfile[:bfile.rfind('/') + 1]
1097 if (missing and abasedir == bbasedir and afile.startswith(bfile)
1097 if (missing and abasedir == bbasedir and afile.startswith(bfile)
1098 and hunk.starta == 0 and hunk.lena == 0):
1098 and hunk.starta == 0 and hunk.lena == 0):
1099 create = True
1099 create = True
1100 missing = False
1100 missing = False
1101
1101
1102 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
1102 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
1103 # diff is between a file and its backup. In this case, the original
1103 # diff is between a file and its backup. In this case, the original
1104 # file should be patched (see original mpatch code).
1104 # file should be patched (see original mpatch code).
1105 isbackup = (abase == bbase and bfile.startswith(afile))
1105 isbackup = (abase == bbase and bfile.startswith(afile))
1106 fname = None
1106 fname = None
1107 if not missing:
1107 if not missing:
1108 if gooda and goodb:
1108 if gooda and goodb:
1109 fname = isbackup and afile or bfile
1109 fname = isbackup and afile or bfile
1110 elif gooda:
1110 elif gooda:
1111 fname = afile
1111 fname = afile
1112
1112
1113 if not fname:
1113 if not fname:
1114 if not nullb:
1114 if not nullb:
1115 fname = isbackup and afile or bfile
1115 fname = isbackup and afile or bfile
1116 elif not nulla:
1116 elif not nulla:
1117 fname = afile
1117 fname = afile
1118 else:
1118 else:
1119 raise PatchError(_("undefined source and destination files"))
1119 raise PatchError(_("undefined source and destination files"))
1120
1120
1121 gp = patchmeta(fname)
1121 gp = patchmeta(fname)
1122 if create:
1122 if create:
1123 gp.op = 'ADD'
1123 gp.op = 'ADD'
1124 elif remove:
1124 elif remove:
1125 gp.op = 'DELETE'
1125 gp.op = 'DELETE'
1126 return gp
1126 return gp
1127
1127
1128 def scangitpatch(lr, firstline):
1128 def scangitpatch(lr, firstline):
1129 """
1129 """
1130 Git patches can emit:
1130 Git patches can emit:
1131 - rename a to b
1131 - rename a to b
1132 - change b
1132 - change b
1133 - copy a to c
1133 - copy a to c
1134 - change c
1134 - change c
1135
1135
1136 We cannot apply this sequence as-is, the renamed 'a' could not be
1136 We cannot apply this sequence as-is, the renamed 'a' could not be
1137 found for it would have been renamed already. And we cannot copy
1137 found for it would have been renamed already. And we cannot copy
1138 from 'b' instead because 'b' would have been changed already. So
1138 from 'b' instead because 'b' would have been changed already. So
1139 we scan the git patch for copy and rename commands so we can
1139 we scan the git patch for copy and rename commands so we can
1140 perform the copies ahead of time.
1140 perform the copies ahead of time.
1141 """
1141 """
1142 pos = 0
1142 pos = 0
1143 try:
1143 try:
1144 pos = lr.fp.tell()
1144 pos = lr.fp.tell()
1145 fp = lr.fp
1145 fp = lr.fp
1146 except IOError:
1146 except IOError:
1147 fp = cStringIO.StringIO(lr.fp.read())
1147 fp = cStringIO.StringIO(lr.fp.read())
1148 gitlr = linereader(fp)
1148 gitlr = linereader(fp)
1149 gitlr.push(firstline)
1149 gitlr.push(firstline)
1150 gitpatches = readgitpatch(gitlr)
1150 gitpatches = readgitpatch(gitlr)
1151 fp.seek(pos)
1151 fp.seek(pos)
1152 return gitpatches
1152 return gitpatches
1153
1153
1154 def iterhunks(fp):
1154 def iterhunks(fp):
1155 """Read a patch and yield the following events:
1155 """Read a patch and yield the following events:
1156 - ("file", afile, bfile, firsthunk): select a new target file.
1156 - ("file", afile, bfile, firsthunk): select a new target file.
1157 - ("hunk", hunk): a new hunk is ready to be applied, follows a
1157 - ("hunk", hunk): a new hunk is ready to be applied, follows a
1158 "file" event.
1158 "file" event.
1159 - ("git", gitchanges): current diff is in git format, gitchanges
1159 - ("git", gitchanges): current diff is in git format, gitchanges
1160 maps filenames to gitpatch records. Unique event.
1160 maps filenames to gitpatch records. Unique event.
1161 """
1161 """
1162 afile = ""
1162 afile = ""
1163 bfile = ""
1163 bfile = ""
1164 state = None
1164 state = None
1165 hunknum = 0
1165 hunknum = 0
1166 emitfile = newfile = False
1166 emitfile = newfile = False
1167 gitpatches = None
1167 gitpatches = None
1168
1168
1169 # our states
1169 # our states
1170 BFILE = 1
1170 BFILE = 1
1171 context = None
1171 context = None
1172 lr = linereader(fp)
1172 lr = linereader(fp)
1173
1173
1174 while True:
1174 while True:
1175 x = lr.readline()
1175 x = lr.readline()
1176 if not x:
1176 if not x:
1177 break
1177 break
1178 if state == BFILE and (
1178 if state == BFILE and (
1179 (not context and x[0] == '@')
1179 (not context and x[0] == '@')
1180 or (context is not False and x.startswith('***************'))
1180 or (context is not False and x.startswith('***************'))
1181 or x.startswith('GIT binary patch')):
1181 or x.startswith('GIT binary patch')):
1182 gp = None
1182 gp = None
1183 if (gitpatches and
1183 if (gitpatches and
1184 (gitpatches[-1][0] == afile or gitpatches[-1][1] == bfile)):
1184 (gitpatches[-1][0] == afile or gitpatches[-1][1] == bfile)):
1185 gp = gitpatches.pop()[2]
1185 gp = gitpatches.pop()[2]
1186 if x.startswith('GIT binary patch'):
1186 if x.startswith('GIT binary patch'):
1187 h = binhunk(lr)
1187 h = binhunk(lr)
1188 else:
1188 else:
1189 if context is None and x.startswith('***************'):
1189 if context is None and x.startswith('***************'):
1190 context = True
1190 context = True
1191 h = hunk(x, hunknum + 1, lr, context)
1191 h = hunk(x, hunknum + 1, lr, context)
1192 hunknum += 1
1192 hunknum += 1
1193 if emitfile:
1193 if emitfile:
1194 emitfile = False
1194 emitfile = False
1195 yield 'file', (afile, bfile, h, gp and gp.copy() or None)
1195 yield 'file', (afile, bfile, h, gp and gp.copy() or None)
1196 yield 'hunk', h
1196 yield 'hunk', h
1197 elif x.startswith('diff --git'):
1197 elif x.startswith('diff --git'):
1198 m = gitre.match(x)
1198 m = gitre.match(x)
1199 if not m:
1199 if not m:
1200 continue
1200 continue
1201 if not gitpatches:
1201 if not gitpatches:
1202 # scan whole input for git metadata
1202 # scan whole input for git metadata
1203 gitpatches = [('a/' + gp.path, 'b/' + gp.path, gp) for gp
1203 gitpatches = [('a/' + gp.path, 'b/' + gp.path, gp) for gp
1204 in scangitpatch(lr, x)]
1204 in scangitpatch(lr, x)]
1205 yield 'git', [g[2].copy() for g in gitpatches
1205 yield 'git', [g[2].copy() for g in gitpatches
1206 if g[2].op in ('COPY', 'RENAME')]
1206 if g[2].op in ('COPY', 'RENAME')]
1207 gitpatches.reverse()
1207 gitpatches.reverse()
1208 afile = 'a/' + m.group(1)
1208 afile = 'a/' + m.group(1)
1209 bfile = 'b/' + m.group(2)
1209 bfile = 'b/' + m.group(2)
1210 while afile != gitpatches[-1][0] and bfile != gitpatches[-1][1]:
1210 while afile != gitpatches[-1][0] and bfile != gitpatches[-1][1]:
1211 gp = gitpatches.pop()[2]
1211 gp = gitpatches.pop()[2]
1212 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1212 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1213 gp = gitpatches[-1][2]
1213 gp = gitpatches[-1][2]
1214 # copy/rename + modify should modify target, not source
1214 # copy/rename + modify should modify target, not source
1215 if gp.op in ('COPY', 'DELETE', 'RENAME', 'ADD') or gp.mode:
1215 if gp.op in ('COPY', 'DELETE', 'RENAME', 'ADD') or gp.mode:
1216 afile = bfile
1216 afile = bfile
1217 newfile = True
1217 newfile = True
1218 elif x.startswith('---'):
1218 elif x.startswith('---'):
1219 # check for a unified diff
1219 # check for a unified diff
1220 l2 = lr.readline()
1220 l2 = lr.readline()
1221 if not l2.startswith('+++'):
1221 if not l2.startswith('+++'):
1222 lr.push(l2)
1222 lr.push(l2)
1223 continue
1223 continue
1224 newfile = True
1224 newfile = True
1225 context = False
1225 context = False
1226 afile = parsefilename(x)
1226 afile = parsefilename(x)
1227 bfile = parsefilename(l2)
1227 bfile = parsefilename(l2)
1228 elif x.startswith('***'):
1228 elif x.startswith('***'):
1229 # check for a context diff
1229 # check for a context diff
1230 l2 = lr.readline()
1230 l2 = lr.readline()
1231 if not l2.startswith('---'):
1231 if not l2.startswith('---'):
1232 lr.push(l2)
1232 lr.push(l2)
1233 continue
1233 continue
1234 l3 = lr.readline()
1234 l3 = lr.readline()
1235 lr.push(l3)
1235 lr.push(l3)
1236 if not l3.startswith("***************"):
1236 if not l3.startswith("***************"):
1237 lr.push(l2)
1237 lr.push(l2)
1238 continue
1238 continue
1239 newfile = True
1239 newfile = True
1240 context = True
1240 context = True
1241 afile = parsefilename(x)
1241 afile = parsefilename(x)
1242 bfile = parsefilename(l2)
1242 bfile = parsefilename(l2)
1243
1243
1244 if newfile:
1244 if newfile:
1245 newfile = False
1245 newfile = False
1246 emitfile = True
1246 emitfile = True
1247 state = BFILE
1247 state = BFILE
1248 hunknum = 0
1248 hunknum = 0
1249
1249
1250 while gitpatches:
1250 while gitpatches:
1251 gp = gitpatches.pop()[2]
1251 gp = gitpatches.pop()[2]
1252 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1252 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1253
1253
1254 def applydiff(ui, fp, backend, store, strip=1, eolmode='strict'):
1254 def applydiff(ui, fp, backend, store, strip=1, eolmode='strict'):
1255 """Reads a patch from fp and tries to apply it.
1255 """Reads a patch from fp and tries to apply it.
1256
1256
1257 Returns 0 for a clean patch, -1 if any rejects were found and 1 if
1257 Returns 0 for a clean patch, -1 if any rejects were found and 1 if
1258 there was any fuzz.
1258 there was any fuzz.
1259
1259
1260 If 'eolmode' is 'strict', the patch content and patched file are
1260 If 'eolmode' is 'strict', the patch content and patched file are
1261 read in binary mode. Otherwise, line endings are ignored when
1261 read in binary mode. Otherwise, line endings are ignored when
1262 patching then normalized according to 'eolmode'.
1262 patching then normalized according to 'eolmode'.
1263 """
1263 """
1264 return _applydiff(ui, fp, patchfile, backend, store, strip=strip,
1264 return _applydiff(ui, fp, patchfile, backend, store, strip=strip,
1265 eolmode=eolmode)
1265 eolmode=eolmode)
1266
1266
1267 def _applydiff(ui, fp, patcher, backend, store, strip=1,
1267 def _applydiff(ui, fp, patcher, backend, store, strip=1,
1268 eolmode='strict'):
1268 eolmode='strict'):
1269
1269
1270 def pstrip(p):
1270 def pstrip(p):
1271 return pathstrip(p, strip - 1)[1]
1271 return pathstrip(p, strip - 1)[1]
1272
1272
1273 rejects = 0
1273 rejects = 0
1274 err = 0
1274 err = 0
1275 current_file = None
1275 current_file = None
1276
1276
1277 for state, values in iterhunks(fp):
1277 for state, values in iterhunks(fp):
1278 if state == 'hunk':
1278 if state == 'hunk':
1279 if not current_file:
1279 if not current_file:
1280 continue
1280 continue
1281 ret = current_file.apply(values)
1281 ret = current_file.apply(values)
1282 if ret > 0:
1282 if ret > 0:
1283 err = 1
1283 err = 1
1284 elif state == 'file':
1284 elif state == 'file':
1285 if current_file:
1285 if current_file:
1286 rejects += current_file.close()
1286 rejects += current_file.close()
1287 current_file = None
1287 current_file = None
1288 afile, bfile, first_hunk, gp = values
1288 afile, bfile, first_hunk, gp = values
1289 if gp:
1289 if gp:
1290 path = pstrip(gp.path)
1290 path = pstrip(gp.path)
1291 gp.path = pstrip(gp.path)
1291 gp.path = pstrip(gp.path)
1292 if gp.oldpath:
1292 if gp.oldpath:
1293 gp.oldpath = pstrip(gp.oldpath)
1293 gp.oldpath = pstrip(gp.oldpath)
1294 else:
1294 else:
1295 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip)
1295 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip)
1296 if gp.op == 'RENAME':
1296 if gp.op == 'RENAME':
1297 backend.unlink(gp.oldpath)
1297 backend.unlink(gp.oldpath)
1298 if not first_hunk:
1298 if not first_hunk:
1299 if gp.op == 'DELETE':
1299 if gp.op == 'DELETE':
1300 backend.unlink(gp.path)
1300 backend.unlink(gp.path)
1301 continue
1301 continue
1302 data, mode = None, None
1302 data, mode = None, None
1303 if gp.op in ('RENAME', 'COPY'):
1303 if gp.op in ('RENAME', 'COPY'):
1304 data, mode = store.getfile(gp.oldpath)[:2]
1304 data, mode = store.getfile(gp.oldpath)[:2]
1305 if gp.mode:
1305 if gp.mode:
1306 mode = gp.mode
1306 mode = gp.mode
1307 if gp.op == 'ADD':
1307 if gp.op == 'ADD':
1308 # Added files without content have no hunk and
1308 # Added files without content have no hunk and
1309 # must be created
1309 # must be created
1310 data = ''
1310 data = ''
1311 if data or mode:
1311 if data or mode:
1312 if (gp.op in ('ADD', 'RENAME', 'COPY')
1312 if (gp.op in ('ADD', 'RENAME', 'COPY')
1313 and backend.exists(gp.path)):
1313 and backend.exists(gp.path)):
1314 raise PatchError(_("cannot create %s: destination "
1314 raise PatchError(_("cannot create %s: destination "
1315 "already exists") % gp.path)
1315 "already exists") % gp.path)
1316 backend.setfile(gp.path, data, mode, gp.oldpath)
1316 backend.setfile(gp.path, data, mode, gp.oldpath)
1317 continue
1317 continue
1318 try:
1318 try:
1319 current_file = patcher(ui, gp, backend, store,
1319 current_file = patcher(ui, gp, backend, store,
1320 eolmode=eolmode)
1320 eolmode=eolmode)
1321 except PatchError, inst:
1321 except PatchError, inst:
1322 ui.warn(str(inst) + '\n')
1322 ui.warn(str(inst) + '\n')
1323 current_file = None
1323 current_file = None
1324 rejects += 1
1324 rejects += 1
1325 continue
1325 continue
1326 elif state == 'git':
1326 elif state == 'git':
1327 for gp in values:
1327 for gp in values:
1328 path = pstrip(gp.oldpath)
1328 path = pstrip(gp.oldpath)
1329 data, mode = backend.getfile(path)
1329 data, mode = backend.getfile(path)
1330 store.setfile(path, data, mode)
1330 store.setfile(path, data, mode)
1331 else:
1331 else:
1332 raise util.Abort(_('unsupported parser state: %s') % state)
1332 raise util.Abort(_('unsupported parser state: %s') % state)
1333
1333
1334 if current_file:
1334 if current_file:
1335 rejects += current_file.close()
1335 rejects += current_file.close()
1336
1336
1337 if rejects:
1337 if rejects:
1338 return -1
1338 return -1
1339 return err
1339 return err
1340
1340
1341 def _externalpatch(ui, repo, patcher, patchname, strip, files,
1341 def _externalpatch(ui, repo, patcher, patchname, strip, files,
1342 similarity):
1342 similarity):
1343 """use <patcher> to apply <patchname> to the working directory.
1343 """use <patcher> to apply <patchname> to the working directory.
1344 returns whether patch was applied with fuzz factor."""
1344 returns whether patch was applied with fuzz factor."""
1345
1345
1346 fuzz = False
1346 fuzz = False
1347 args = []
1347 args = []
1348 cwd = repo.root
1348 cwd = repo.root
1349 if cwd:
1349 if cwd:
1350 args.append('-d %s' % util.shellquote(cwd))
1350 args.append('-d %s' % util.shellquote(cwd))
1351 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
1351 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
1352 util.shellquote(patchname)))
1352 util.shellquote(patchname)))
1353 try:
1353 try:
1354 for line in fp:
1354 for line in fp:
1355 line = line.rstrip()
1355 line = line.rstrip()
1356 ui.note(line + '\n')
1356 ui.note(line + '\n')
1357 if line.startswith('patching file '):
1357 if line.startswith('patching file '):
1358 pf = util.parsepatchoutput(line)
1358 pf = util.parsepatchoutput(line)
1359 printed_file = False
1359 printed_file = False
1360 files.add(pf)
1360 files.add(pf)
1361 elif line.find('with fuzz') >= 0:
1361 elif line.find('with fuzz') >= 0:
1362 fuzz = True
1362 fuzz = True
1363 if not printed_file:
1363 if not printed_file:
1364 ui.warn(pf + '\n')
1364 ui.warn(pf + '\n')
1365 printed_file = True
1365 printed_file = True
1366 ui.warn(line + '\n')
1366 ui.warn(line + '\n')
1367 elif line.find('saving rejects to file') >= 0:
1367 elif line.find('saving rejects to file') >= 0:
1368 ui.warn(line + '\n')
1368 ui.warn(line + '\n')
1369 elif line.find('FAILED') >= 0:
1369 elif line.find('FAILED') >= 0:
1370 if not printed_file:
1370 if not printed_file:
1371 ui.warn(pf + '\n')
1371 ui.warn(pf + '\n')
1372 printed_file = True
1372 printed_file = True
1373 ui.warn(line + '\n')
1373 ui.warn(line + '\n')
1374 finally:
1374 finally:
1375 if files:
1375 if files:
1376 cfiles = list(files)
1376 cfiles = list(files)
1377 cwd = repo.getcwd()
1377 cwd = repo.getcwd()
1378 if cwd:
1378 if cwd:
1379 cfiles = [util.pathto(repo.root, cwd, f)
1379 cfiles = [util.pathto(repo.root, cwd, f)
1380 for f in cfiles]
1380 for f in cfiles]
1381 scmutil.addremove(repo, cfiles, similarity=similarity)
1381 scmutil.addremove(repo, cfiles, similarity=similarity)
1382 code = fp.close()
1382 code = fp.close()
1383 if code:
1383 if code:
1384 raise PatchError(_("patch command failed: %s") %
1384 raise PatchError(_("patch command failed: %s") %
1385 util.explainexit(code)[0])
1385 util.explainexit(code)[0])
1386 return fuzz
1386 return fuzz
1387
1387
1388 def patchbackend(ui, backend, patchobj, strip, files=None, eolmode='strict'):
1388 def patchbackend(ui, backend, patchobj, strip, files=None, eolmode='strict'):
1389 if files is None:
1389 if files is None:
1390 files = set()
1390 files = set()
1391 if eolmode is None:
1391 if eolmode is None:
1392 eolmode = ui.config('patch', 'eol', 'strict')
1392 eolmode = ui.config('patch', 'eol', 'strict')
1393 if eolmode.lower() not in eolmodes:
1393 if eolmode.lower() not in eolmodes:
1394 raise util.Abort(_('unsupported line endings type: %s') % eolmode)
1394 raise util.Abort(_('unsupported line endings type: %s') % eolmode)
1395 eolmode = eolmode.lower()
1395 eolmode = eolmode.lower()
1396
1396
1397 store = filestore()
1397 store = filestore()
1398 try:
1398 try:
1399 fp = open(patchobj, 'rb')
1399 fp = open(patchobj, 'rb')
1400 except TypeError:
1400 except TypeError:
1401 fp = patchobj
1401 fp = patchobj
1402 try:
1402 try:
1403 ret = applydiff(ui, fp, backend, store, strip=strip,
1403 ret = applydiff(ui, fp, backend, store, strip=strip,
1404 eolmode=eolmode)
1404 eolmode=eolmode)
1405 finally:
1405 finally:
1406 if fp != patchobj:
1406 if fp != patchobj:
1407 fp.close()
1407 fp.close()
1408 files.update(backend.close())
1408 files.update(backend.close())
1409 store.close()
1409 store.close()
1410 if ret < 0:
1410 if ret < 0:
1411 raise PatchError(_('patch failed to apply'))
1411 raise PatchError(_('patch failed to apply'))
1412 return ret > 0
1412 return ret > 0
1413
1413
1414 def internalpatch(ui, repo, patchobj, strip, files=None, eolmode='strict',
1414 def internalpatch(ui, repo, patchobj, strip, files=None, eolmode='strict',
1415 similarity=0):
1415 similarity=0):
1416 """use builtin patch to apply <patchobj> to the working directory.
1416 """use builtin patch to apply <patchobj> to the working directory.
1417 returns whether patch was applied with fuzz factor."""
1417 returns whether patch was applied with fuzz factor."""
1418 backend = workingbackend(ui, repo, similarity)
1418 backend = workingbackend(ui, repo, similarity)
1419 return patchbackend(ui, backend, patchobj, strip, files, eolmode)
1419 return patchbackend(ui, backend, patchobj, strip, files, eolmode)
1420
1420
1421 def patchrepo(ui, repo, ctx, store, patchobj, strip, files=None,
1421 def patchrepo(ui, repo, ctx, store, patchobj, strip, files=None,
1422 eolmode='strict'):
1422 eolmode='strict'):
1423 backend = repobackend(ui, repo, ctx, store)
1423 backend = repobackend(ui, repo, ctx, store)
1424 return patchbackend(ui, backend, patchobj, strip, files, eolmode)
1424 return patchbackend(ui, backend, patchobj, strip, files, eolmode)
1425
1425
1426 def makememctx(repo, parents, text, user, date, branch, files, store,
1426 def makememctx(repo, parents, text, user, date, branch, files, store,
1427 editor=None):
1427 editor=None):
1428 def getfilectx(repo, memctx, path):
1428 def getfilectx(repo, memctx, path):
1429 data, (islink, isexec), copied = store.getfile(path)
1429 data, (islink, isexec), copied = store.getfile(path)
1430 return context.memfilectx(path, data, islink=islink, isexec=isexec,
1430 return context.memfilectx(path, data, islink=islink, isexec=isexec,
1431 copied=copied)
1431 copied=copied)
1432 extra = {}
1432 extra = {}
1433 if branch:
1433 if branch:
1434 extra['branch'] = encoding.fromlocal(branch)
1434 extra['branch'] = encoding.fromlocal(branch)
1435 ctx = context.memctx(repo, parents, text, files, getfilectx, user,
1435 ctx = context.memctx(repo, parents, text, files, getfilectx, user,
1436 date, extra)
1436 date, extra)
1437 if editor:
1437 if editor:
1438 ctx._text = editor(repo, ctx, [])
1438 ctx._text = editor(repo, ctx, [])
1439 return ctx
1439 return ctx
1440
1440
1441 def patch(ui, repo, patchname, strip=1, files=None, eolmode='strict',
1441 def patch(ui, repo, patchname, strip=1, files=None, eolmode='strict',
1442 similarity=0):
1442 similarity=0):
1443 """Apply <patchname> to the working directory.
1443 """Apply <patchname> to the working directory.
1444
1444
1445 'eolmode' specifies how end of lines should be handled. It can be:
1445 'eolmode' specifies how end of lines should be handled. It can be:
1446 - 'strict': inputs are read in binary mode, EOLs are preserved
1446 - 'strict': inputs are read in binary mode, EOLs are preserved
1447 - 'crlf': EOLs are ignored when patching and reset to CRLF
1447 - 'crlf': EOLs are ignored when patching and reset to CRLF
1448 - 'lf': EOLs are ignored when patching and reset to LF
1448 - 'lf': EOLs are ignored when patching and reset to LF
1449 - None: get it from user settings, default to 'strict'
1449 - None: get it from user settings, default to 'strict'
1450 'eolmode' is ignored when using an external patcher program.
1450 'eolmode' is ignored when using an external patcher program.
1451
1451
1452 Returns whether patch was applied with fuzz factor.
1452 Returns whether patch was applied with fuzz factor.
1453 """
1453 """
1454 patcher = ui.config('ui', 'patch')
1454 patcher = ui.config('ui', 'patch')
1455 if files is None:
1455 if files is None:
1456 files = set()
1456 files = set()
1457 try:
1457 try:
1458 if patcher:
1458 if patcher:
1459 return _externalpatch(ui, repo, patcher, patchname, strip,
1459 return _externalpatch(ui, repo, patcher, patchname, strip,
1460 files, similarity)
1460 files, similarity)
1461 return internalpatch(ui, repo, patchname, strip, files, eolmode,
1461 return internalpatch(ui, repo, patchname, strip, files, eolmode,
1462 similarity)
1462 similarity)
1463 except PatchError, err:
1463 except PatchError, err:
1464 raise util.Abort(str(err))
1464 raise util.Abort(str(err))
1465
1465
1466 def changedfiles(ui, repo, patchpath, strip=1):
1466 def changedfiles(ui, repo, patchpath, strip=1):
1467 backend = fsbackend(ui, repo.root)
1467 backend = fsbackend(ui, repo.root)
1468 fp = open(patchpath, 'rb')
1468 fp = open(patchpath, 'rb')
1469 try:
1469 try:
1470 changed = set()
1470 changed = set()
1471 for state, values in iterhunks(fp):
1471 for state, values in iterhunks(fp):
1472 if state == 'file':
1472 if state == 'file':
1473 afile, bfile, first_hunk, gp = values
1473 afile, bfile, first_hunk, gp = values
1474 if gp:
1474 if gp:
1475 gp.path = pathstrip(gp.path, strip - 1)[1]
1475 gp.path = pathstrip(gp.path, strip - 1)[1]
1476 if gp.oldpath:
1476 if gp.oldpath:
1477 gp.oldpath = pathstrip(gp.oldpath, strip - 1)[1]
1477 gp.oldpath = pathstrip(gp.oldpath, strip - 1)[1]
1478 else:
1478 else:
1479 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip)
1479 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip)
1480 changed.add(gp.path)
1480 changed.add(gp.path)
1481 if gp.op == 'RENAME':
1481 if gp.op == 'RENAME':
1482 changed.add(gp.oldpath)
1482 changed.add(gp.oldpath)
1483 elif state not in ('hunk', 'git'):
1483 elif state not in ('hunk', 'git'):
1484 raise util.Abort(_('unsupported parser state: %s') % state)
1484 raise util.Abort(_('unsupported parser state: %s') % state)
1485 return changed
1485 return changed
1486 finally:
1486 finally:
1487 fp.close()
1487 fp.close()
1488
1488
1489 def b85diff(to, tn):
1489 def b85diff(to, tn):
1490 '''print base85-encoded binary diff'''
1490 '''print base85-encoded binary diff'''
1491 def gitindex(text):
1491 def gitindex(text):
1492 if not text:
1492 if not text:
1493 return hex(nullid)
1493 return hex(nullid)
1494 l = len(text)
1494 l = len(text)
1495 s = util.sha1('blob %d\0' % l)
1495 s = util.sha1('blob %d\0' % l)
1496 s.update(text)
1496 s.update(text)
1497 return s.hexdigest()
1497 return s.hexdigest()
1498
1498
1499 def fmtline(line):
1499 def fmtline(line):
1500 l = len(line)
1500 l = len(line)
1501 if l <= 26:
1501 if l <= 26:
1502 l = chr(ord('A') + l - 1)
1502 l = chr(ord('A') + l - 1)
1503 else:
1503 else:
1504 l = chr(l - 26 + ord('a') - 1)
1504 l = chr(l - 26 + ord('a') - 1)
1505 return '%c%s\n' % (l, base85.b85encode(line, True))
1505 return '%c%s\n' % (l, base85.b85encode(line, True))
1506
1506
1507 def chunk(text, csize=52):
1507 def chunk(text, csize=52):
1508 l = len(text)
1508 l = len(text)
1509 i = 0
1509 i = 0
1510 while i < l:
1510 while i < l:
1511 yield text[i:i + csize]
1511 yield text[i:i + csize]
1512 i += csize
1512 i += csize
1513
1513
1514 tohash = gitindex(to)
1514 tohash = gitindex(to)
1515 tnhash = gitindex(tn)
1515 tnhash = gitindex(tn)
1516 if tohash == tnhash:
1516 if tohash == tnhash:
1517 return ""
1517 return ""
1518
1518
1519 # TODO: deltas
1519 # TODO: deltas
1520 ret = ['index %s..%s\nGIT binary patch\nliteral %s\n' %
1520 ret = ['index %s..%s\nGIT binary patch\nliteral %s\n' %
1521 (tohash, tnhash, len(tn))]
1521 (tohash, tnhash, len(tn))]
1522 for l in chunk(zlib.compress(tn)):
1522 for l in chunk(zlib.compress(tn)):
1523 ret.append(fmtline(l))
1523 ret.append(fmtline(l))
1524 ret.append('\n')
1524 ret.append('\n')
1525 return ''.join(ret)
1525 return ''.join(ret)
1526
1526
1527 class GitDiffRequired(Exception):
1527 class GitDiffRequired(Exception):
1528 pass
1528 pass
1529
1529
1530 def diffopts(ui, opts=None, untrusted=False, section='diff'):
1530 def diffopts(ui, opts=None, untrusted=False, section='diff'):
1531 def get(key, name=None, getter=ui.configbool):
1531 def get(key, name=None, getter=ui.configbool):
1532 return ((opts and opts.get(key)) or
1532 return ((opts and opts.get(key)) or
1533 getter(section, name or key, None, untrusted=untrusted))
1533 getter(section, name or key, None, untrusted=untrusted))
1534 return mdiff.diffopts(
1534 return mdiff.diffopts(
1535 text=opts and opts.get('text'),
1535 text=opts and opts.get('text'),
1536 git=get('git'),
1536 git=get('git'),
1537 nodates=get('nodates'),
1537 nodates=get('nodates'),
1538 showfunc=get('show_function', 'showfunc'),
1538 showfunc=get('show_function', 'showfunc'),
1539 ignorews=get('ignore_all_space', 'ignorews'),
1539 ignorews=get('ignore_all_space', 'ignorews'),
1540 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
1540 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
1541 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'),
1541 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'),
1542 context=get('unified', getter=ui.config))
1542 context=get('unified', getter=ui.config))
1543
1543
1544 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None,
1544 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None,
1545 losedatafn=None, prefix=''):
1545 losedatafn=None, prefix=''):
1546 '''yields diff of changes to files between two nodes, or node and
1546 '''yields diff of changes to files between two nodes, or node and
1547 working directory.
1547 working directory.
1548
1548
1549 if node1 is None, use first dirstate parent instead.
1549 if node1 is None, use first dirstate parent instead.
1550 if node2 is None, compare node1 with working directory.
1550 if node2 is None, compare node1 with working directory.
1551
1551
1552 losedatafn(**kwarg) is a callable run when opts.upgrade=True and
1552 losedatafn(**kwarg) is a callable run when opts.upgrade=True and
1553 every time some change cannot be represented with the current
1553 every time some change cannot be represented with the current
1554 patch format. Return False to upgrade to git patch format, True to
1554 patch format. Return False to upgrade to git patch format, True to
1555 accept the loss or raise an exception to abort the diff. It is
1555 accept the loss or raise an exception to abort the diff. It is
1556 called with the name of current file being diffed as 'fn'. If set
1556 called with the name of current file being diffed as 'fn'. If set
1557 to None, patches will always be upgraded to git format when
1557 to None, patches will always be upgraded to git format when
1558 necessary.
1558 necessary.
1559
1559
1560 prefix is a filename prefix that is prepended to all filenames on
1560 prefix is a filename prefix that is prepended to all filenames on
1561 display (used for subrepos).
1561 display (used for subrepos).
1562 '''
1562 '''
1563
1563
1564 if opts is None:
1564 if opts is None:
1565 opts = mdiff.defaultopts
1565 opts = mdiff.defaultopts
1566
1566
1567 if not node1 and not node2:
1567 if not node1 and not node2:
1568 node1 = repo.dirstate.p1()
1568 node1 = repo.dirstate.p1()
1569
1569
1570 def lrugetfilectx():
1570 def lrugetfilectx():
1571 cache = {}
1571 cache = {}
1572 order = []
1572 order = []
1573 def getfilectx(f, ctx):
1573 def getfilectx(f, ctx):
1574 fctx = ctx.filectx(f, filelog=cache.get(f))
1574 fctx = ctx.filectx(f, filelog=cache.get(f))
1575 if f not in cache:
1575 if f not in cache:
1576 if len(cache) > 20:
1576 if len(cache) > 20:
1577 del cache[order.pop(0)]
1577 del cache[order.pop(0)]
1578 cache[f] = fctx.filelog()
1578 cache[f] = fctx.filelog()
1579 else:
1579 else:
1580 order.remove(f)
1580 order.remove(f)
1581 order.append(f)
1581 order.append(f)
1582 return fctx
1582 return fctx
1583 return getfilectx
1583 return getfilectx
1584 getfilectx = lrugetfilectx()
1584 getfilectx = lrugetfilectx()
1585
1585
1586 ctx1 = repo[node1]
1586 ctx1 = repo[node1]
1587 ctx2 = repo[node2]
1587 ctx2 = repo[node2]
1588
1588
1589 if not changes:
1589 if not changes:
1590 changes = repo.status(ctx1, ctx2, match=match)
1590 changes = repo.status(ctx1, ctx2, match=match)
1591 modified, added, removed = changes[:3]
1591 modified, added, removed = changes[:3]
1592
1592
1593 if not modified and not added and not removed:
1593 if not modified and not added and not removed:
1594 return []
1594 return []
1595
1595
1596 revs = None
1596 revs = None
1597 if not repo.ui.quiet:
1597 if not repo.ui.quiet:
1598 hexfunc = repo.ui.debugflag and hex or short
1598 hexfunc = repo.ui.debugflag and hex or short
1599 revs = [hexfunc(node) for node in [node1, node2] if node]
1599 revs = [hexfunc(node) for node in [node1, node2] if node]
1600
1600
1601 copy = {}
1601 copy = {}
1602 if opts.git or opts.upgrade:
1602 if opts.git or opts.upgrade:
1603 copy = copies.copies(repo, ctx1, ctx2, repo[nullid])[0]
1603 copy = copies.pathcopies(ctx1, ctx2)
1604
1604
1605 difffn = lambda opts, losedata: trydiff(repo, revs, ctx1, ctx2,
1605 difffn = lambda opts, losedata: trydiff(repo, revs, ctx1, ctx2,
1606 modified, added, removed, copy, getfilectx, opts, losedata, prefix)
1606 modified, added, removed, copy, getfilectx, opts, losedata, prefix)
1607 if opts.upgrade and not opts.git:
1607 if opts.upgrade and not opts.git:
1608 try:
1608 try:
1609 def losedata(fn):
1609 def losedata(fn):
1610 if not losedatafn or not losedatafn(fn=fn):
1610 if not losedatafn or not losedatafn(fn=fn):
1611 raise GitDiffRequired()
1611 raise GitDiffRequired()
1612 # Buffer the whole output until we are sure it can be generated
1612 # Buffer the whole output until we are sure it can be generated
1613 return list(difffn(opts.copy(git=False), losedata))
1613 return list(difffn(opts.copy(git=False), losedata))
1614 except GitDiffRequired:
1614 except GitDiffRequired:
1615 return difffn(opts.copy(git=True), None)
1615 return difffn(opts.copy(git=True), None)
1616 else:
1616 else:
1617 return difffn(opts, None)
1617 return difffn(opts, None)
1618
1618
1619 def difflabel(func, *args, **kw):
1619 def difflabel(func, *args, **kw):
1620 '''yields 2-tuples of (output, label) based on the output of func()'''
1620 '''yields 2-tuples of (output, label) based on the output of func()'''
1621 headprefixes = [('diff', 'diff.diffline'),
1621 headprefixes = [('diff', 'diff.diffline'),
1622 ('copy', 'diff.extended'),
1622 ('copy', 'diff.extended'),
1623 ('rename', 'diff.extended'),
1623 ('rename', 'diff.extended'),
1624 ('old', 'diff.extended'),
1624 ('old', 'diff.extended'),
1625 ('new', 'diff.extended'),
1625 ('new', 'diff.extended'),
1626 ('deleted', 'diff.extended'),
1626 ('deleted', 'diff.extended'),
1627 ('---', 'diff.file_a'),
1627 ('---', 'diff.file_a'),
1628 ('+++', 'diff.file_b')]
1628 ('+++', 'diff.file_b')]
1629 textprefixes = [('@', 'diff.hunk'),
1629 textprefixes = [('@', 'diff.hunk'),
1630 ('-', 'diff.deleted'),
1630 ('-', 'diff.deleted'),
1631 ('+', 'diff.inserted')]
1631 ('+', 'diff.inserted')]
1632 head = False
1632 head = False
1633 for chunk in func(*args, **kw):
1633 for chunk in func(*args, **kw):
1634 lines = chunk.split('\n')
1634 lines = chunk.split('\n')
1635 for i, line in enumerate(lines):
1635 for i, line in enumerate(lines):
1636 if i != 0:
1636 if i != 0:
1637 yield ('\n', '')
1637 yield ('\n', '')
1638 if head:
1638 if head:
1639 if line.startswith('@'):
1639 if line.startswith('@'):
1640 head = False
1640 head = False
1641 else:
1641 else:
1642 if line and not line[0] in ' +-@\\':
1642 if line and not line[0] in ' +-@\\':
1643 head = True
1643 head = True
1644 stripline = line
1644 stripline = line
1645 if not head and line and line[0] in '+-':
1645 if not head and line and line[0] in '+-':
1646 # highlight trailing whitespace, but only in changed lines
1646 # highlight trailing whitespace, but only in changed lines
1647 stripline = line.rstrip()
1647 stripline = line.rstrip()
1648 prefixes = textprefixes
1648 prefixes = textprefixes
1649 if head:
1649 if head:
1650 prefixes = headprefixes
1650 prefixes = headprefixes
1651 for prefix, label in prefixes:
1651 for prefix, label in prefixes:
1652 if stripline.startswith(prefix):
1652 if stripline.startswith(prefix):
1653 yield (stripline, label)
1653 yield (stripline, label)
1654 break
1654 break
1655 else:
1655 else:
1656 yield (line, '')
1656 yield (line, '')
1657 if line != stripline:
1657 if line != stripline:
1658 yield (line[len(stripline):], 'diff.trailingwhitespace')
1658 yield (line[len(stripline):], 'diff.trailingwhitespace')
1659
1659
1660 def diffui(*args, **kw):
1660 def diffui(*args, **kw):
1661 '''like diff(), but yields 2-tuples of (output, label) for ui.write()'''
1661 '''like diff(), but yields 2-tuples of (output, label) for ui.write()'''
1662 return difflabel(diff, *args, **kw)
1662 return difflabel(diff, *args, **kw)
1663
1663
1664
1664
1665 def _addmodehdr(header, omode, nmode):
1665 def _addmodehdr(header, omode, nmode):
1666 if omode != nmode:
1666 if omode != nmode:
1667 header.append('old mode %s\n' % omode)
1667 header.append('old mode %s\n' % omode)
1668 header.append('new mode %s\n' % nmode)
1668 header.append('new mode %s\n' % nmode)
1669
1669
1670 def trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
1670 def trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
1671 copy, getfilectx, opts, losedatafn, prefix):
1671 copy, getfilectx, opts, losedatafn, prefix):
1672
1672
1673 def join(f):
1673 def join(f):
1674 return os.path.join(prefix, f)
1674 return os.path.join(prefix, f)
1675
1675
1676 date1 = util.datestr(ctx1.date())
1676 date1 = util.datestr(ctx1.date())
1677 man1 = ctx1.manifest()
1677 man1 = ctx1.manifest()
1678
1678
1679 gone = set()
1679 gone = set()
1680 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
1680 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
1681
1681
1682 copyto = dict([(v, k) for k, v in copy.items()])
1682 copyto = dict([(v, k) for k, v in copy.items()])
1683
1683
1684 if opts.git:
1684 if opts.git:
1685 revs = None
1685 revs = None
1686
1686
1687 for f in sorted(modified + added + removed):
1687 for f in sorted(modified + added + removed):
1688 to = None
1688 to = None
1689 tn = None
1689 tn = None
1690 dodiff = True
1690 dodiff = True
1691 header = []
1691 header = []
1692 if f in man1:
1692 if f in man1:
1693 to = getfilectx(f, ctx1).data()
1693 to = getfilectx(f, ctx1).data()
1694 if f not in removed:
1694 if f not in removed:
1695 tn = getfilectx(f, ctx2).data()
1695 tn = getfilectx(f, ctx2).data()
1696 a, b = f, f
1696 a, b = f, f
1697 if opts.git or losedatafn:
1697 if opts.git or losedatafn:
1698 if f in added:
1698 if f in added:
1699 mode = gitmode[ctx2.flags(f)]
1699 mode = gitmode[ctx2.flags(f)]
1700 if f in copy or f in copyto:
1700 if f in copy or f in copyto:
1701 if opts.git:
1701 if opts.git:
1702 if f in copy:
1702 if f in copy:
1703 a = copy[f]
1703 a = copy[f]
1704 else:
1704 else:
1705 a = copyto[f]
1705 a = copyto[f]
1706 omode = gitmode[man1.flags(a)]
1706 omode = gitmode[man1.flags(a)]
1707 _addmodehdr(header, omode, mode)
1707 _addmodehdr(header, omode, mode)
1708 if a in removed and a not in gone:
1708 if a in removed and a not in gone:
1709 op = 'rename'
1709 op = 'rename'
1710 gone.add(a)
1710 gone.add(a)
1711 else:
1711 else:
1712 op = 'copy'
1712 op = 'copy'
1713 header.append('%s from %s\n' % (op, join(a)))
1713 header.append('%s from %s\n' % (op, join(a)))
1714 header.append('%s to %s\n' % (op, join(f)))
1714 header.append('%s to %s\n' % (op, join(f)))
1715 to = getfilectx(a, ctx1).data()
1715 to = getfilectx(a, ctx1).data()
1716 else:
1716 else:
1717 losedatafn(f)
1717 losedatafn(f)
1718 else:
1718 else:
1719 if opts.git:
1719 if opts.git:
1720 header.append('new file mode %s\n' % mode)
1720 header.append('new file mode %s\n' % mode)
1721 elif ctx2.flags(f):
1721 elif ctx2.flags(f):
1722 losedatafn(f)
1722 losedatafn(f)
1723 # In theory, if tn was copied or renamed we should check
1723 # In theory, if tn was copied or renamed we should check
1724 # if the source is binary too but the copy record already
1724 # if the source is binary too but the copy record already
1725 # forces git mode.
1725 # forces git mode.
1726 if util.binary(tn):
1726 if util.binary(tn):
1727 if opts.git:
1727 if opts.git:
1728 dodiff = 'binary'
1728 dodiff = 'binary'
1729 else:
1729 else:
1730 losedatafn(f)
1730 losedatafn(f)
1731 if not opts.git and not tn:
1731 if not opts.git and not tn:
1732 # regular diffs cannot represent new empty file
1732 # regular diffs cannot represent new empty file
1733 losedatafn(f)
1733 losedatafn(f)
1734 elif f in removed:
1734 elif f in removed:
1735 if opts.git:
1735 if opts.git:
1736 # have we already reported a copy above?
1736 # have we already reported a copy above?
1737 if ((f in copy and copy[f] in added
1737 if ((f in copy and copy[f] in added
1738 and copyto[copy[f]] == f) or
1738 and copyto[copy[f]] == f) or
1739 (f in copyto and copyto[f] in added
1739 (f in copyto and copyto[f] in added
1740 and copy[copyto[f]] == f)):
1740 and copy[copyto[f]] == f)):
1741 dodiff = False
1741 dodiff = False
1742 else:
1742 else:
1743 header.append('deleted file mode %s\n' %
1743 header.append('deleted file mode %s\n' %
1744 gitmode[man1.flags(f)])
1744 gitmode[man1.flags(f)])
1745 elif not to or util.binary(to):
1745 elif not to or util.binary(to):
1746 # regular diffs cannot represent empty file deletion
1746 # regular diffs cannot represent empty file deletion
1747 losedatafn(f)
1747 losedatafn(f)
1748 else:
1748 else:
1749 oflag = man1.flags(f)
1749 oflag = man1.flags(f)
1750 nflag = ctx2.flags(f)
1750 nflag = ctx2.flags(f)
1751 binary = util.binary(to) or util.binary(tn)
1751 binary = util.binary(to) or util.binary(tn)
1752 if opts.git:
1752 if opts.git:
1753 _addmodehdr(header, gitmode[oflag], gitmode[nflag])
1753 _addmodehdr(header, gitmode[oflag], gitmode[nflag])
1754 if binary:
1754 if binary:
1755 dodiff = 'binary'
1755 dodiff = 'binary'
1756 elif binary or nflag != oflag:
1756 elif binary or nflag != oflag:
1757 losedatafn(f)
1757 losedatafn(f)
1758 if opts.git:
1758 if opts.git:
1759 header.insert(0, mdiff.diffline(revs, join(a), join(b), opts))
1759 header.insert(0, mdiff.diffline(revs, join(a), join(b), opts))
1760
1760
1761 if dodiff:
1761 if dodiff:
1762 if dodiff == 'binary':
1762 if dodiff == 'binary':
1763 text = b85diff(to, tn)
1763 text = b85diff(to, tn)
1764 else:
1764 else:
1765 text = mdiff.unidiff(to, date1,
1765 text = mdiff.unidiff(to, date1,
1766 # ctx2 date may be dynamic
1766 # ctx2 date may be dynamic
1767 tn, util.datestr(ctx2.date()),
1767 tn, util.datestr(ctx2.date()),
1768 join(a), join(b), revs, opts=opts)
1768 join(a), join(b), revs, opts=opts)
1769 if header and (text or len(header) > 1):
1769 if header and (text or len(header) > 1):
1770 yield ''.join(header)
1770 yield ''.join(header)
1771 if text:
1771 if text:
1772 yield text
1772 yield text
1773
1773
1774 def diffstatsum(stats):
1774 def diffstatsum(stats):
1775 maxfile, maxtotal, addtotal, removetotal, binary = 0, 0, 0, 0, False
1775 maxfile, maxtotal, addtotal, removetotal, binary = 0, 0, 0, 0, False
1776 for f, a, r, b in stats:
1776 for f, a, r, b in stats:
1777 maxfile = max(maxfile, encoding.colwidth(f))
1777 maxfile = max(maxfile, encoding.colwidth(f))
1778 maxtotal = max(maxtotal, a + r)
1778 maxtotal = max(maxtotal, a + r)
1779 addtotal += a
1779 addtotal += a
1780 removetotal += r
1780 removetotal += r
1781 binary = binary or b
1781 binary = binary or b
1782
1782
1783 return maxfile, maxtotal, addtotal, removetotal, binary
1783 return maxfile, maxtotal, addtotal, removetotal, binary
1784
1784
1785 def diffstatdata(lines):
1785 def diffstatdata(lines):
1786 diffre = re.compile('^diff .*-r [a-z0-9]+\s(.*)$')
1786 diffre = re.compile('^diff .*-r [a-z0-9]+\s(.*)$')
1787
1787
1788 results = []
1788 results = []
1789 filename, adds, removes, isbinary = None, 0, 0, False
1789 filename, adds, removes, isbinary = None, 0, 0, False
1790
1790
1791 def addresult():
1791 def addresult():
1792 if filename:
1792 if filename:
1793 results.append((filename, adds, removes, isbinary))
1793 results.append((filename, adds, removes, isbinary))
1794
1794
1795 for line in lines:
1795 for line in lines:
1796 if line.startswith('diff'):
1796 if line.startswith('diff'):
1797 addresult()
1797 addresult()
1798 # set numbers to 0 anyway when starting new file
1798 # set numbers to 0 anyway when starting new file
1799 adds, removes, isbinary = 0, 0, False
1799 adds, removes, isbinary = 0, 0, False
1800 if line.startswith('diff --git'):
1800 if line.startswith('diff --git'):
1801 filename = gitre.search(line).group(1)
1801 filename = gitre.search(line).group(1)
1802 elif line.startswith('diff -r'):
1802 elif line.startswith('diff -r'):
1803 # format: "diff -r ... -r ... filename"
1803 # format: "diff -r ... -r ... filename"
1804 filename = diffre.search(line).group(1)
1804 filename = diffre.search(line).group(1)
1805 elif line.startswith('+') and not line.startswith('+++'):
1805 elif line.startswith('+') and not line.startswith('+++'):
1806 adds += 1
1806 adds += 1
1807 elif line.startswith('-') and not line.startswith('---'):
1807 elif line.startswith('-') and not line.startswith('---'):
1808 removes += 1
1808 removes += 1
1809 elif (line.startswith('GIT binary patch') or
1809 elif (line.startswith('GIT binary patch') or
1810 line.startswith('Binary file')):
1810 line.startswith('Binary file')):
1811 isbinary = True
1811 isbinary = True
1812 addresult()
1812 addresult()
1813 return results
1813 return results
1814
1814
1815 def diffstat(lines, width=80, git=False):
1815 def diffstat(lines, width=80, git=False):
1816 output = []
1816 output = []
1817 stats = diffstatdata(lines)
1817 stats = diffstatdata(lines)
1818 maxname, maxtotal, totaladds, totalremoves, hasbinary = diffstatsum(stats)
1818 maxname, maxtotal, totaladds, totalremoves, hasbinary = diffstatsum(stats)
1819
1819
1820 countwidth = len(str(maxtotal))
1820 countwidth = len(str(maxtotal))
1821 if hasbinary and countwidth < 3:
1821 if hasbinary and countwidth < 3:
1822 countwidth = 3
1822 countwidth = 3
1823 graphwidth = width - countwidth - maxname - 6
1823 graphwidth = width - countwidth - maxname - 6
1824 if graphwidth < 10:
1824 if graphwidth < 10:
1825 graphwidth = 10
1825 graphwidth = 10
1826
1826
1827 def scale(i):
1827 def scale(i):
1828 if maxtotal <= graphwidth:
1828 if maxtotal <= graphwidth:
1829 return i
1829 return i
1830 # If diffstat runs out of room it doesn't print anything,
1830 # If diffstat runs out of room it doesn't print anything,
1831 # which isn't very useful, so always print at least one + or -
1831 # which isn't very useful, so always print at least one + or -
1832 # if there were at least some changes.
1832 # if there were at least some changes.
1833 return max(i * graphwidth // maxtotal, int(bool(i)))
1833 return max(i * graphwidth // maxtotal, int(bool(i)))
1834
1834
1835 for filename, adds, removes, isbinary in stats:
1835 for filename, adds, removes, isbinary in stats:
1836 if isbinary:
1836 if isbinary:
1837 count = 'Bin'
1837 count = 'Bin'
1838 else:
1838 else:
1839 count = adds + removes
1839 count = adds + removes
1840 pluses = '+' * scale(adds)
1840 pluses = '+' * scale(adds)
1841 minuses = '-' * scale(removes)
1841 minuses = '-' * scale(removes)
1842 output.append(' %s%s | %*s %s%s\n' %
1842 output.append(' %s%s | %*s %s%s\n' %
1843 (filename, ' ' * (maxname - encoding.colwidth(filename)),
1843 (filename, ' ' * (maxname - encoding.colwidth(filename)),
1844 countwidth, count, pluses, minuses))
1844 countwidth, count, pluses, minuses))
1845
1845
1846 if stats:
1846 if stats:
1847 output.append(_(' %d files changed, %d insertions(+), %d deletions(-)\n')
1847 output.append(_(' %d files changed, %d insertions(+), %d deletions(-)\n')
1848 % (len(stats), totaladds, totalremoves))
1848 % (len(stats), totaladds, totalremoves))
1849
1849
1850 return ''.join(output)
1850 return ''.join(output)
1851
1851
1852 def diffstatui(*args, **kw):
1852 def diffstatui(*args, **kw):
1853 '''like diffstat(), but yields 2-tuples of (output, label) for
1853 '''like diffstat(), but yields 2-tuples of (output, label) for
1854 ui.write()
1854 ui.write()
1855 '''
1855 '''
1856
1856
1857 for line in diffstat(*args, **kw).splitlines():
1857 for line in diffstat(*args, **kw).splitlines():
1858 if line and line[-1] in '+-':
1858 if line and line[-1] in '+-':
1859 name, graph = line.rsplit(' ', 1)
1859 name, graph = line.rsplit(' ', 1)
1860 yield (name + ' ', '')
1860 yield (name + ' ', '')
1861 m = re.search(r'\++', graph)
1861 m = re.search(r'\++', graph)
1862 if m:
1862 if m:
1863 yield (m.group(0), 'diffstat.inserted')
1863 yield (m.group(0), 'diffstat.inserted')
1864 m = re.search(r'-+', graph)
1864 m = re.search(r'-+', graph)
1865 if m:
1865 if m:
1866 yield (m.group(0), 'diffstat.deleted')
1866 yield (m.group(0), 'diffstat.deleted')
1867 else:
1867 else:
1868 yield (line, '')
1868 yield (line, '')
1869 yield ('\n', '')
1869 yield ('\n', '')
General Comments 0
You need to be logged in to leave comments. Login now